远程编程

板子的可以通过SSH,VNC、MobaXterm、VSCode连接

连接方式 特点 场景
VSCode 编程快捷键、代码编写与检查等比较方便,可配合cline或者copilot,实现AI coding 日常编程
MobaXterm 命令行终端 追求性能、效率极简、传文件
VNC 像windows一样有图形界面 需要桌面操作

vscode编程树莓派容器的代码

本地 VSCode → SSH → 树莓派 → Docker 容器 → 代码

vscode SSH连接方式

SSH pi@192.168.31.98

连接时会在上方提示输入密码

1 安装扩展:Dev Containers
2 Ctrl + Shift + P输入 Dev Containers: Attach to Running Container ,选择容器

3 选择容器后,会打开新的窗口并再次提示输入密码,连接成功:

复制容器代码到树莓派板子(容器的宿主机)

根据需要,可能会想把代码从容器中挪出来进行版本管理

(没直接在板子里接入AI 模型 AI coding、进行git版本管理,因为考虑到树莓派板子内存有限,追求性能)AI也这么说:

操作:

复制容器的代码到宿主机(树莓派板子),再下载到windows,然后推送github

docker cp ArmPiUltra:/home/ubuntu/ros2_ws/src /home/pi/my_code

进入容器ArmPiUltra

docker exec -it -u ubuntu -w /home/ubuntu ArmPiUltra /bin/zsh

1 开箱demo

1.1 启动 launch 文件控制

launch 文件位于:/home/ubuntu/ros2_ws/src/example/example/simple/bus_servo.launch.py

ros2 launch example bus_servo.launch.py

git push远程报错

描述

本地git init 默认分支是master,远程新建仓库的默认分支是main,直接执行 git push -u origin main报错:       

$ git push -u origin main
error: src refspec main does not match any
error: failed to push some refs to 'github.com:xxx.git'

但执行git push -u origin master:main仍无法成功推送

$ git push -u origin master:main
To github.com:xxx.git
 ! [rejected]        master -> main (non-fast-forward)
error: failed to push some refs to 'github.com:xxx.git'
hint: Updates were rejected because a pushed branch tip is behind its remote
hint: counterpart. If you want to integrate the remote changes, use 'git pull'
hint: before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.

解决办法

# 拉取远程内容并合并
git pull origin main --allow-unrelated-histories --no-edit

# 推送到远程
git push -u origin master:main

可以看到推送成功:

$ git pull origin main --allow-unrelated-histories --no-edit
From github.com:xxx
 * branch            main       -> FETCH_HEAD
Merge made by the 'ort' strategy.
 README.md | 6 ++++++
 1 file changed, 6 insertions(+)
 create mode 100644 README.md

$ git push -u origin master:main
Enumerating objects: 11, done.
Counting objects: 100% (11/11), done.
Delta compression using up to 24 threads
Compressing objects: 100% (3/3), done.
Writing objects: 100% (10/10), 746 bytes | 746.00 KiB/s, done.
Total 10 (delta 0), reused 0 (delta 0), pack-reused 0 (from 0)
To github.com:xxx.git
   ddxx4c3..015xx2e  master -> main
branch 'master' set up to track 'origin/main'.

git 强制修改远程最新提交

git push --force-with-lease origin <本地分支名>:<远程分支名>

git push --force-with-lease origin master:main

Python首行惯例

#!/usr/bin/env python3
# encoding: utf-8
  • #!/usr/bin/env python3:当文件被赋予可执行权限直接 ./xxx.py 跑时,Linux 会用 python3 解释。不过ros2项目的话,被 colcon 通过 entry_points 包装成可执行后这一行可有可无,但保留是惯例
  • # encoding: utf-8:源码编码声明,Python 3 默认就是 UTF-8,写这行主要是防止某些老编辑器/工具误判中文注释

代码片段解读

import rclpy
from rclpy.node import Node
from std_srvs.srv import Trigger
from ros_robot_controller_msgs.msg import ServoPosition, ServosPosition
导入 作用
rclpy ROS 2 的 Python 客户端库;rclpy.init() 初始化、rclpy.ok() 判断节点是否还活、rclpy.shutdown() 收尾
rclpy.node.Node 自定义节点都继承它
std_srvs.srv.Trigger ROS 2 标准空请求服务类型(请求体为空、响应只有 bool success + string message)。这里只用它的"类型"作为客户端等待的依据
ros_robot_controller_msgs.msg.{ServoPosition, ServosPosition} 自定义消息:内层 ServoPosition{uint16 id, uint16 position},外层 ServosPosition{float64 duration, ServoPosition[] position}

关于消息:duration 是从当前位置插值到目标位置所用的秒数;position 是一组 (舵机ID, 目标值) 数组(值域大致 0–1000,500 代表中位)。这种"批量+时长"的设计可以让多关节同时同步运动

class ServoController(Node):
    def __init__(self):
        super().__init__('servo_control_demo')   # 用父类 Node 的构造函数,把节点名字注册为 servo_control_demo

        self.pub = self.create_publisher(ServosPosition, '/ros_robot_controller/bus_servo/set_position', 1) # 创建一个发布者:消息类型是 ServosPosition,话题名是 /ros_robot_controller/bus_servo/set_position, Qos消息队列长度为 1(只缓存最新一条命令,可能会覆盖还没被消化的上一条信息)

        # Wait for the underlying control service of the robotic arm to start (等待机械臂底层控制服务启动)
        self.client = self.create_client(Trigger, '/ros_robot_controller/init_finish') # 为服务 /ros_robot_controller/init_finish 创建一个客户端,只用来探测“服务是否注册”
        self.client.wait_for_service() # 阻塞等待这个服务出现;只有底层控制器节点起来了它才出现。这一步保证我们不会在底层还没就绪时就发指令

    def set_servo_position(self, duration, positions): # 打包并发布舵机指令。`duration` 是运动时长(秒),`positions` 是若干 `(id, 位置)` 元组的集合。

        msg = ServosPosition() # 建一个空的 `ServosPosition` 消息对象
        msg.duration = float(duration) # 把运动时长写入消息的 duration 字段,强转成 float,匹配 .msg 中 float64 的类型要求
        position_list = []
        for i in positions:
            position = ServoPosition()     # 为当前舵机新建一条 ServoPosition 子消息
            position.id = i[0]             # 舵机编号
            position.position = int(i[1])  # 舵机目标位置
            position_list.append(position)
        msg.position = position_list
        self.pub.publish(msg)            # 发布者把这条命令发出去,底层控制器收到后会真的转动舵机
        for pos in position_list:
            self.get_logger().info(f'duration={msg.duration}, id={pos.id}, position={pos.position}')  # 通过 ROS 2 的日志接口打印出本次发的 duration、id、position,方便在终端观察
def main(args=None):  # 定义模块入口函数 main,args 用于接收命令行参数(ros2 run 会传进来)
    rclpy.init(args=args) # 初始化 rclpy 上下文,必须在创建任何节点之前调用
    controller = ServoController() # 实例化节点。这一步内部会因 wait_for_service() 阻塞,直到底层就绪才返回

    try:
        while rclpy.ok():
            controller.set_servo_position(0.5, ((4, 200),))   # Set servo ID 4 to position 200 (设置舵机 ID 4 到位置 200)
            time.sleep(0.5)  # Wait for 0.5 seconds (等待 0.5 秒)
            controller.set_servo_position(0.5, ((4, 500),))   # Set servo ID 4 to position 500 (设置舵机 ID 4 到位置 500)
            time.sleep(0.5)  # Wait for 0.5 seconds (等待 0.5 秒)
    except KeyboardInterrupt: # 捕获 `Ctrl+C` 触发的 `KeyboardInterrupt`
        pass
    finally:
        controller.destroy_node()   # Clean up the node (清理节点) # 销毁节点,释放发布者、客户端等资源
        rclpy.shutdown()  # Shut down ROS 2 (关闭 ROS 2) # 关闭 rclpy 上下文
if __name__ == '__main__':  # 直接运行时调用 main()。通过 ros2 run example bus_servo 启动时,实际入口由 setup.py 的 entry point 注册,但调用的最终也是这个 main 函数
    main()

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐