WIN11系统环境松灵机器人SCOUT2.0底盘CAN通信控制测试

参考资料

用户手册 https://new.agilex.ai/raw/upload/20230718/SCOUT 2.0用户手册20230718_74677.pdf
如何通过开源SDK控制松灵机器人SCOUT底盘? https://blog.csdn.net/AgileX/article/details/106730024
SCOUT底盘测试 通过CAN总线和RS232控制松灵地盘 https://blog.csdn.net/qsl56789/article/details/135264569

测试环境

win11
python 3.10
需要安装包库
pip install python-can
pip install cantact
硬件连接使用官方配备的usb转can线
在windows设备管理器中发现usb-can adapter后,使用以下代码检测是否连接到usb转can设备

import can
print("可用CAN接口:", can.detect_available_configs())

可能存在的回复:可用CAN接口: [{'interface': 'cantact', 'channel': 'ch:0'}, {'interface': 'virtual', 'channel': 'channel-2798'}, {'interface': 'serial', 'channel': 'COM3'}, {'interface': 'serial', 'channel': 'COM4'}]
其中{'interface': 'cantact', 'channel': 'ch:0'}就是默认的usb-can设备

测试代码

import can
import time
import threading
import struct

# print("可用CAN接口:", can.detect_available_configs())

class ScoutCANController:
    def __init__(self, channel=0):
        """初始化CAN控制器"""
        self.bus = None
        self.channel = channel
        self.connected = False
        self._stop_event = threading.Event()
        self._receive_thread = None

        # CAN ID定义
        self.CAN_ID_CONTROL_MODE = 0x421  # 控制模式设定
        self.CAN_ID_MOTION_CMD = 0x111  # 运动控制命令
        self.CAN_ID_VEHICLE_STATE = 0x162  # 车辆状态反馈

        # 存储状态数据
        self.state = {
            'linear_velocity': 0.0,  # m/s
            'angular_velocity': 0.0,  # rad/s
            'battery_voltage': 0.0,  # V
            'system_status': 0,
            'control_mode': 0
        }

    def connect(self):
        """连接到CAN总线"""
        try:
            self.bus = can.interface.Bus(
                interface='cantact',
                channel=self.channel,
                bitrate=500000
            )
            self.connected = True
            print(f"成功连接到CAN接口: 通道 {self.channel}")

            # 启动接收线程
            self._stop_event.clear()
            self._receive_thread = threading.Thread(target=self._receive_loop)
            self._receive_thread.daemon = True
            self._receive_thread.start()

            # 先获取当前状态
            self.request_vehicle_state()
            time.sleep(0.5)

            return True
        except Exception as e:
            print(f"CAN连接失败: {e}")
            return False

    def _receive_loop(self):
        """接收CAN消息循环"""
        while not self._stop_event.is_set() and self.connected:
            try:
                msg = self.bus.recv(timeout=0.1)
                if msg:
                    self._process_message(msg)
            except can.CanError as e:
                if not self._stop_event.is_set():
                    print(f"CAN接收错误: {e}")
            except Exception as e:
                print(f"接收处理错误: {e}")

    def _process_message(self, msg):
        """处理接收到的CAN消息"""
        if msg.arbitration_id == self.CAN_ID_VEHICLE_STATE:
            self._parse_vehicle_state(msg.data)
        elif msg.arbitration_id == self.CAN_ID_CONTROL_MODE:
            if len(msg.data) >= 1:
                self.state['control_mode'] = msg.data[0]
                mode_str = "CAN控制模式" if msg.data[0] == 0x01 else "待机模式"
                print(f"接收到控制模式状态: {mode_str}")

    def _parse_vehicle_state(self, data):
        """解析车辆状态数据"""
        if len(data) < 8:
            print(f"状态数据长度不足: {len(data)} < 8")
            return

        # 线速度 (mm/s -> m/s)
        linear_speed_mm = struct.unpack('<h', data[0:2])[0]
        self.state['linear_velocity'] = linear_speed_mm / 1000.0

        # 角速度 (0.001rad/s -> rad/s)
        angular_speed_unit = struct.unpack('<h', data[2:4])[0]
        self.state['angular_velocity'] = angular_speed_unit * 0.001

        # 电池电压 (0.1V -> V)
        battery_voltage_tenth = struct.unpack('<B', data[4:5])[0]
        self.state['battery_voltage'] = battery_voltage_tenth / 10.0

        # 系统状态
        if len(data) >= 6:
            self.state['system_status'] = data[5]

        # 打印状态更新
        print(f"【状态反馈】速度: {self.state['linear_velocity']:.2f}m/s, "
              f"角速度: {self.state['angular_velocity']:.3f}rad/s, "
              f"电池: {self.state['battery_voltage']:.1f}V, "
              f"状态: {self.state['system_status']}")

    def set_control_mode(self, enable=True):
        """设置控制模式

        Args:
            enable: True=使能CAN控制模式, False=待机模式
        """
        if not self.connected:
            print("未连接到CAN总线")
            return False

        # 构建控制模式命令
        data = [0x01 if enable else 0x00]  # 0x01: CAN控制使能, 0x00: 待机

        msg = can.Message(
            arbitration_id=self.CAN_ID_CONTROL_MODE,
            data=data,
            is_extended_id=False
        )

        try:
            self.bus.send(msg)
            mode_str = "使能CAN控制模式" if enable else "设置待机模式"
            print(f"【已发送】{mode_str} 命令 (ID: 0x{self.CAN_ID_CONTROL_MODE:X})")
            return True
        except can.CanError as e:
            print(f"发送控制模式命令失败: {e}")
            return False

    def set_motion_command(self, linear_vel=0.0, angular_vel=0.0):
        """设置运动命令

        Args:
            linear_vel: 线速度 (m/s), 范围[-1.5, 1.5]
            angular_vel: 角速度 (rad/s), 范围[-0.523, 0.523]
        """
        if not self.connected:
            print("未连接到CAN总线")
            return False

        # 限制速度范围
        linear_vel = max(min(linear_vel, 1.5), -1.5)
        angular_vel = max(min(angular_vel, 0.523), -0.523)

        # 转换为协议要求的单位
        linear_speed_mm = int(linear_vel * 1000)  # m/s -> mm/s
        angular_speed_unit = int(angular_vel / 0.001)  # rad/s -> 0.001rad/s

        # 构建运动控制命令
        data = bytearray(8)

        # 线速度 (小端格式)
        data[0] = linear_speed_mm & 0xFF
        data[1] = (linear_speed_mm >> 8) & 0xFF

        # 角速度 (小端格式)
        data[2] = angular_speed_unit & 0xFF
        data[3] = (angular_speed_unit >> 8) & 0xFF

        # 保留字节 (根据协议必须为0)
        data[4] = 0x00
        data[5] = 0x00
        data[6] = 0x00
        data[7] = 0x00

        msg = can.Message(
            arbitration_id=self.CAN_ID_MOTION_CMD,
            data=data,
            is_extended_id=False
        )

        try:
            self.bus.send(msg)
            print(f"【已发送】运动命令 (ID: 0x{self.CAN_ID_MOTION_CMD:X}): "
                  f"线速度={linear_vel:.2f}m/s, 角速度={angular_vel:.3f}rad/s")
            return True
        except can.CanError as e:
            print(f"发送运动命令失败: {e}")
            return False

    def request_vehicle_state(self):
        """请求车辆状态"""
        # 对于Scout 2.0,状态通常是主动发送的,但我们可以尝试发送请求
        try:
            # 发送一个空请求,有些设备会响应
            msg = can.Message(
                arbitration_id=self.CAN_ID_VEHICLE_STATE,
                data=[0x00],
                is_extended_id=False
            )
            self.bus.send(msg)
            print(f"【已发送】状态请求 (ID: 0x{self.CAN_ID_VEHICLE_STATE:X})")
            return True
        except can.CanError as e:
            print(f"发送状态请求失败: {e}")
            return False

    def get_state(self):
        """获取当前状态"""
        return self.state.copy()

    def disconnect(self):
        """断开连接"""
        self._stop_event.set()
        if self._receive_thread and self._receive_thread.is_alive():
            self._receive_thread.join(timeout=1.0)

        if self.bus:
            # 发送停止命令
            self.set_motion_command(0.0, 0.0)
            time.sleep(0.2)

            # 停止接收
            self.bus.shutdown()
            print("CAN连接已关闭")
        self.connected = False


# 主程序
if __name__ == "__main__":
    # 创建控制器
    controller = ScoutCANController(channel=0)

    try:
        print("=== 开始连接到Scout 2.0 ===")
        if not controller.connect():
            print("连接失败,退出程序")
            exit(1)

        # 等待初始状态
        print("\n=== 等待初始状态反馈 ===")
        time.sleep(1.0)

        # 步骤1: 使能CAN控制模式
        print("\n=== 步骤1: 使能CAN控制模式 ===")
        if not controller.set_control_mode(True):
            print("警告: 控制模式设置失败,继续尝试运动命令...")
        time.sleep(0.5)

        # 步骤2: 向前移动
        print("\n=== 步骤2: 向前移动 (0.2 m/s) ===")
        controller.set_motion_command(linear_vel=0.1, angular_vel=0.0)
        start_time = time.time()
        while time.time() - start_time < 2.0:
            current_state = controller.get_state()
            print(f"  当前速度: {current_state['linear_velocity']:.2f}m/s")
            time.sleep(0.2)

        # 步骤3: 原地旋转
        print("\n=== 步骤3: 原地旋转 (0.2 rad/s) ===")
        controller.set_motion_command(linear_vel=0.0, angular_vel=0.1)
        start_time = time.time()
        while time.time() - start_time < 2.0:
            current_state = controller.get_state()
            print(f"  当前角速度: {current_state['angular_velocity']:.3f}rad/s")
            time.sleep(0.2)

        # 步骤4: 曲线运动
        print("\n=== 步骤4: 曲线运动 (前进+旋转) ===")
        controller.set_motion_command(linear_vel=0.1, angular_vel=0.1)
        time.sleep(2.0)

        # 步骤5: 停止
        print("\n=== 步骤5: 停止 ===")
        controller.set_motion_command(0.0, 0.0)
        time.sleep(1.0)

        # 显示最终状态
        final_state = controller.get_state()
        print("\n=== 最终状态 ===")
        print(f"线速度: {final_state['linear_velocity']:.3f} m/s")
        print(f"角速度: {final_state['angular_velocity']:.3f} rad/s")
        print(f"电池电压: {final_state['battery_voltage']:.1f} V")
        print(f"系统状态: {final_state['system_status']}")
        print(f"控制模式: {'CAN控制' if final_state['control_mode'] == 1 else '待机'}")

    except KeyboardInterrupt:
        print("\n程序被用户中断")
    except Exception as e:
        print(f"发生错误: {e}")
    finally:
        print("\n=== 安全关闭 ===")
        if controller.connected:
            # 确保机器人停止
            print("发送停止命令...")
            controller.set_motion_command(0.0, 0.0)
            time.sleep(0.5)

            # 恢复待机模式
            print("恢复待机模式...")
            controller.set_control_mode(False)
            time.sleep(0.3)

            # 断开连接
            controller.disconnect()
            print("程序结束")

posted on 2025-12-03 23:44  fmix  阅读(0)  评论(0)    收藏  举报

导航