can(6) canopen python库使用

1.发送消息

python 复制代码
# canopen_sender.py - CANopen 发送端(主节点)
import canopen
import can
import time

def start_canopen_sender():
    """启动 CANopen 发送端,连接虚拟总线并发送消息"""
    # 统一的虚拟总线配置(必须与 bus_creator.py 一致)
    VIRTUAL_CHANNEL = "virtual_canopen_bus"
    BITRATE = 500000
    MASTER_NODE_ID = 1  # 主节点 ID
    SLAVE_NODE_ID = 2   # 接收端(从节点)ID

    # 1. 初始化 CANopen 网络,连接虚拟总线

    network = canopen.Network()
    network.connect(
        interface="virtual",
        channel=VIRTUAL_CHANNEL,
        bitrate=BITRATE
    )
    print(f"✅ CANopen 发送端已连接虚拟总线:{VIRTUAL_CHANNEL}")
    master_node = network.add_node(MASTER_NODE_ID, object_dictionary=None)

    # 2. 发送 CANopen 心跳报文(主节点自身心跳)
    print("\n===== 发送主节点心跳报文 =====")
    heartbeat_id = 0x700 + MASTER_NODE_ID  # CANopen 心跳 ID 规则:0x700 + 节点ID
    heartbeat_msg = can.Message(
        arbitration_id=heartbeat_id,
        data=[0x05],  # 0x05=运行状态
        is_extended_id=False,
        channel=VIRTUAL_CHANNEL
    )
    network.bus.send(heartbeat_msg)
    print(f"📤 发送心跳报文:ID=0x{heartbeat_id:X},数据=0x05(运行状态)")

    # 3. 发送 SDO 请求(向从节点读取设备名称 0x1008:0x00)
    print("\n===== 发送 SDO 读取请求 =====")
    sdo_request_id = 0x600 + SLAVE_NODE_ID  # SDO 请求 ID 规则:0x600 + 从节点ID
    # SDO 读取请求数据(格式:0x40 + 索引高字节 + 索引低字节 + 子索引 + 保留)
    sdo_request_data = b'\x40\x08\x10\x00\x00\x00\x00\x00'  # 读取 0x1008:0x00
    sdo_msg = can.Message(
        arbitration_id=sdo_request_id,
        data=sdo_request_data,
        is_extended_id=False,
        channel=VIRTUAL_CHANNEL
    )
    network.bus.send(sdo_msg)
    print(f"📤 发送 SDO 请求:ID=0x{sdo_request_id:X},数据={sdo_request_data.hex()}")

    # 4. 监听是否有从节点响应(可选)
    print("\n===== 监听从节点响应(5秒)=====")
    start_time = time.time()
    while time.time() - start_time < 5:
        resp_msg = network.bus.recv(timeout=0.5)
        if resp_msg:
            print(f"📥 收到响应:ID=0x{resp_msg.arbitration_id:X},数据={resp_msg.data.hex()}")


    network.disconnect()


if __name__ == "__main__":
    start_canopen_sender()

2.接收

python 复制代码
# canopen_receiver.py - CANopen 接收端(从节点)
import canopen
import can
import time
import threading

class CANopenReceiver:
    """CANopen 接收端,监听并响应虚拟总线消息"""
    def __init__(self):
        # 统一的虚拟总线配置(与 bus_creator.py 一致)
        self.VIRTUAL_CHANNEL = "virtual_canopen_bus"
        self.BITRATE = 500000
        self.SLAVE_NODE_ID = 2  # 从节点 ID
        self.network: canopen.Network = None
        self.running = False

    def start(self):
        """启动接收端,连接虚拟总线并监听消息"""
        try:
            # 1. 连接虚拟总线
            self.network = canopen.Network()
            self.network.connect(
                interface="virtual",
                channel=self.VIRTUAL_CHANNEL,
                bitrate=self.BITRATE
            )
            print(f"✅ CANopen 接收端已连接虚拟总线:{self.VIRTUAL_CHANNEL}")
            self.running = True

            # 2. 启动监听线程
            listen_thread = threading.Thread(target=self._listen_loop, daemon=True)
            listen_thread.start()
            print(f"ℹ️  开始监听虚拟总线消息(节点 ID={self.SLAVE_NODE_ID}),按 Ctrl+C 退出")

            # 3. 保持脚本运行
            while self.running:
                time.sleep(1)
        except KeyboardInterrupt:
            print("\n⚠️  接收到退出信号,停止监听...")
        except Exception as e:
            print(f"❌ 接收端启动失败:{e}")
        finally:
            self.running = False
            self.network.disconnect()
            print("✅ 接收端已断开虚拟总线连接")

    def _listen_loop(self):
        """循环监听虚拟总线消息,并响应 SDO 请求"""
        while self.running:
            msg = self.network.bus.recv(timeout=0.1)  # 非阻塞接收
            if msg:
                self._process_message(msg)

    def _process_message(self, msg: can.Message):
        """解析并处理收到的 CANopen 消息"""
        arb_id = msg.arbitration_id
        data = msg.data.hex()
        print(f"\n📥 收到消息:ID=0x{arb_id:X},数据={data}")

        # 识别并响应 SDO 请求(0x600 + 从节点ID)
        if arb_id == 0x600 + self.SLAVE_NODE_ID:
            print("   → 收到 SDO 读取请求,准备响应...")
            self._respond_sdo_request(msg)

        # 识别心跳报文(0x700 + 节点ID)
        elif 0x700 <= arb_id < 0x800:
            node_id = arb_id - 0x700
            state = msg.data[0] if len(msg.data) > 0 else 0
            state_map = {0: "初始化", 5: "运行", 127: "故障"}
            state_desc = state_map.get(state, f"未知({state})")
            print(f"   → 心跳报文:节点{node_id} 状态={state_desc}")

    def _respond_sdo_request(self, req_msg: can.Message):
        """响应 SDO 读取请求(模拟从节点返回设备名称)"""
        # SDO 响应 ID 规则:0x580 + 从节点ID
        sdo_resp_id = 0x580 + self.SLAVE_NODE_ID
        # 模拟返回设备名称 "VirtualCAN"(截断为 4 字节示例)
        resp_data = b'\x43\x08\x10\x00\x08\x56\x69\x72'  # 响应格式 + "Vir..."
        resp_msg = can.Message(
            arbitration_id=sdo_resp_id,
            data=resp_data,
            is_extended_id=False,
            channel=self.VIRTUAL_CHANNEL
        )
        self.network.bus.send(resp_msg)
        print(f"📤 发送 SDO 响应:ID=0x{sdo_resp_id:X},数据={resp_data.hex()}")

if __name__ == "__main__":
    receiver = CANopenReceiver()
    receiver.start()

3.代码解释

3.1 interface="virtual", -- CAN 总线的通信接口类型

其他类型

  • Windows:interface='pcan'(PCAN 硬件)、interface='kvaser'(Kvaser 硬件);
  • 虚拟总线(跨平台):interface='virtual'(python-can 内置虚拟总线)。
  • interface = 'socketcan' 是 **Linux 系统下 Python 操作 CAN 总线的专属接口类型,**依赖 Linux 的 SocketCAN 协议栈,需配合 Linux 的 CAN 接口(物理 / 虚拟)使用
相关推荐
_风华ts2 小时前
UObject复制与RPC
网络·c++·网络协议·rpc·虚幻
rgeshfgreh2 小时前
Python变量与类型:从入门到精通
python
cc_beolus2 小时前
服务器介绍
运维·服务器
杨超越luckly2 小时前
HTML应用指南:利用GET请求获取网易云热歌榜
前端·python·html·数据可视化·网易云热榜
青果网络_xz2 小时前
IP静态是什么意思?静态IP适用于哪些业务场景?
网络·网络协议·tcp/ip
灯下夜无眠2 小时前
sklearn中fit、transform、fit_transform用法详解
人工智能·python·sklearn
小鸡脚来咯2 小时前
Linux 服务器问题排查指南(面试标准回答)
linux·服务器·面试
数据大魔方2 小时前
【期货量化入门】股指期货量化入门:IF/IC/IH交易全攻略(TqSdk完整教程)
开发语言·python
sunfove2 小时前
空间几何的基石:直角、柱、球坐标系的原理与转换详解
人工智能·python·机器学习