这是一个文件的内容import subprocess
import threading
import time
import numpy as np
import can
from canBase import CanBase, CanPowerBase
from canPower import CanPower
# from BaseModel import config
# from BaseModel.hhlog import HHlog
# from model.util import log
# from model.util.config import Config
# from model.param.wireconfig import WireConfig
class config:
TaskRun = True
class CanReceiver(CanBase):
def __init__(self, channel="can0", bitrate=500000):
super().__init__()
self.channel = channel
self.bitrate = bitrate
# self.bus = None
# self.buttonData = ButtonSignals() # 当前按钮状态
# self.previous_button_state = ButtonSignals() # 上次按钮状态
self.get_program_no()
self.in_signal = None
self.in_Data = {}
self.last_in_signal = None
self.in_edge = None # 1 上升沿,按钮按下 0状态没变 -1 下降沿,按钮松开
self.bus =None
self.start_bt = False
self.in_signal185 = None
def connect(self):
try:
self.setup_can_interface()
self.can_power = CanPower(channel=self.channel, bustype="socketcan", bitrate=self.bitrate)
self.can_power.connect()
self.bus = self.can_power.bus
# self.bus=can.interface.Bus(channel=self.channel, bustype="socketcan", bitrate=self.bitrate)
# HHlog.logger.info(f"can通信初始化init成功success: 状态:{self.bus.state}, Channel={self.channel}, 比特率={self.bitrate}")
# log.operateLogWrite(
# f"can通信初始化成功: 状态:{self.bus.state}, Channel={self.channel}, 比特率={self.bitrate}")
self.car_comm_thread()
print("can success")
except Exception as e:
print("can error ", e)
return
# HHlog.logger.warning("can init fail: {self.bus}")
# log.operateLogWrite(f"初始化 CAN 总线失败: {e}")
def get_program_no(self):
"""
获取设置 程序号和 有丝/无丝
"""
return
config = Config()
process_no = int(config.read_conf("WORK-NO", 'worknum'))
weld_type = str(config.read_conf("WORK-NO", 'worktype'))
CanBase.program_no = process_no
data = WireConfig.select().where((WireConfig.pgNo == process_no) & (WireConfig.pgType == weld_type)).first()
if data:
if data.isWire:
CanBase.have_or_no_silk = 1
else:
CanBase.have_or_no_silk = 0
def car_comm_thread(self):
"""
car通信线程
"""
def send_comm():
# HHlog.logger.info("can发送通信 启动")
msg = can.Message(
arbitration_id=0x000,
data=[0x01, 0x01],
is_extended_id=False
)
self.bus.send(msg)
while config.TaskRun:
data_201, data_301, data_401 = self.send_package()
messages = [
(0x0, [0x01]),
(0x00000201, data_201),
(0x00000301, data_301),
(0x00000401, data_401)
]
# CanPowerBase.r_pdo()
messages += CanPowerBase.r_pdo()
# if "can_send_message" not in HHlog.alarm_Always:
# HHlog.alarm_Always["can_send_message"] = messages
# HHlog.logger.debug(str(messages))
self.send_message(messages)
self.process_message()
time.sleep(0.1)
# HHlog.logger.error("can发送通信 结束")
print("can发送通信 结束")
# car_send_thread = MonitorThread(send_comm, None, 0.1, "can发送通信")
# car_send_thread.start()
car_send_thread = threading.Thread(target=send_comm)
car_send_thread.start()
def send_package(self):
bits = np.array([
0, 0, 0, 0, CanBase.lack_silk, CanBase.lack_water, CanBase.lack_gas, CanBase.short_circuit,
0, 0, 0, 0, 0, CanBase.have_or_no_silk, CanBase.simulation_welding, CanBase.manual_automatic
], dtype=np.bool_)
d201 = np.array([CanBase.program_no, CanBase.interval_no, CanBase.welding_stroke], dtype="<u2")
data_201 = np.packbits(bits, bitorder="big").tobytes() + d201.tobytes()
data_301 = np.array([CanBase.weld_speed, CanBase.weld_current, CanBase.real_v, CanBase.sway_speed],
dtype="<u2").tobytes()
data_401 = np.array([CanBase.sway_width, CanBase.give_silk, CanBase.distance, 0], dtype="<u2").tobytes()
return data_201, data_301, data_401
def setup_can_interface(self):
"""
设置 CAN 接口为 UP
"""
try:
# 关闭can0接口
subprocess.run(f"sudo ip link set {self.channel} down", shell=True, check=True)
# 设置 can0口的比特率
subprocess.run(f"sudo ip link set {self.channel} type can bitrate {self.bitrate}", shell=True, check=True)
# 设置发送缓冲队列大小为 1000(默认值可能是 10)
subprocess.run(f"sudo ip link set {self.channel} txqueuelen 1000", shell=True, check=True)
# 将 can0设置为 UP 状态
subprocess.run(f"sudo ip link set {self.channel} up", shell=True, check=True)
except subprocess.CalledProcessError as e:
print(f"set CAN fail: {e}")
raise
def stop_can(self):
"""停止 CAN 总线通信"""
if self.bus is None:
return
try:
print(self.bus.state, can.interface.Bus.state)
if self.bus and self.bus.state == can.BusState.ACTIVE:
# self.bus.shutdown()
time.sleep(2)
subprocess.run(f"sudo ip link set {self.channel} down", shell=True, check=True)
# log.operateLogWrite("CAN总线通信已成功停止")
# 停止通信处理线程
CanBase.stop_thread_sign = True
if hasattr(self, 'car_send_thread') and self.car_send_thread.is_alive():
self.car_send_thread.join() # 等待线程结束
print("car_send_thread over")
print(f"over,{self.bus.state},{can.interface.Bus.state}")
else:
# log.operateLogWrite("not find CAN")
print("not find can")
except Exception as e:
# HHlog.logger.warning(f"关闭 CAN 总线通信失败: {e}")
print(f"close CAN 。Can Communication failed总线通信失败: {e}")
pass
# log.operateLogWrite(f"关闭 CAN 总线通信失败: {e}")
def send_message(self, messages):
"""
发送 CAN 数据帧
:param can_id: CAN 帧 ID
:param data: 数据列表(长度最大 8 字节)
"""
# return
for can_id, data in messages:
try:
message = can.Message(
arbitration_id=can_id,
data=data,
is_extended_id=False # 使用标准帧
)
self.bus.send(message, timeout=0.01)
except can.CanError as e:
print(f"send fall: {e}")
# if "CanError" not in HHlog.alarm_Always:
# HHlog.alarm_Always["CanError"] = e
# HHlog.logger.debug("CanError" + str(e))
# pass
def process_message(self):
"""
处理接收到的 CAN 消息
"""
max_messages_per_cycle = 20
count = 0
ox181, ox185 = False, False
while count < max_messages_per_cycle:
try:
message = self.bus.recv(timeout=0.0) # 非阻塞接收
if message is None:
break # 没有消息了,退出循环
count += 1
if message.arbitration_id == 0x181 and (ox181 is False): #
# 处理0x181消息
self.in_Data["181"] = message.data
self.in_signal = np.unpackbits(np.frombuffer(message.data, dtype=np.uint8), axis=-1,
bitorder="little")
ox181 = True
elif message.arbitration_id == 0x185 and (ox185 is False): # and ox185 is False
self.in_Data["185"] = message.data
print("0x185 ", message.data)
self.in_signal185 = np.unpackbits(np.frombuffer(message.data, dtype=np.uint8), axis=-1,
bitorder="little")
ox185 = True
elif message.arbitration_id in (0x701, 0x705):
pass # 心跳,不做处理
# elif message.arbitration_id == 0x4:
# pass # 不清楚是什么
else:
print(f"收到未知ID: 0x{message.arbitration_id:X} {message.data}")
if ox185 and ox181:
break
except can.CanError as e:
print(f"处理CAN消息出错: {e}")
break
finally:
# print(count)
if ox181:
if self.start_bt is False and self.get_in("启动") == 1:
self.start_bt = True
if self.last_in_signal is not None:
self.in_edge = self.in_signal - self.last_in_signal
if CanBase.manual_automatic == 0 and self.start_bt:
self.hander_inSignal()
self.last_in_signal = self.in_signal
if ox185:
CanPowerBase.t_pdo(self.in_Data["185"])
# try:
# message = self.bus.recv(timeout=0.01)
# if message:
# if message.arbitration_id == 0x181:
# # 0 or 1
# # 获取本次信号
# self.in_signal = np.unpackbits(np.frombuffer(message.data, dtype=np.uint8), axis=-1,
# bitorder="little")
#
# if self.start_bt is False and self.get_in("启动") == 1:
# self.start_bt = True
#
# # 计算沿
# if self.last_in_signal is not None:
# self.in_edge = self.in_signal - self.last_in_signal
# # if np.where(self.in_edge==255, True, False).any():
# # print(self.in_edge)
# # 有信号 ,手动模式 且 按下开始按钮
# if CanBase.manual_automatic == 0 and self.start_bt:
# self.hander_inSignal()
#
# # 更新上次信号
# self.last_in_signal = self.in_signal
#
# elif message.arbitration_id == 0x185:
# self.in_signal185 = np.unpackbits(np.frombuffer(message.data, dtype=np.uint8), axis=-1,
# bitorder="little")
# # if self.last_in_signal is not None:
# # self.in_edge = self.in_signal - self.last_in_signal
# # self.last_in_signal = self.in_signal
# elif message.arbitration_id == 0x701:
# # 心跳
# pass
# elif message.arbitration_id == 0x705:
# # 心跳
# pass
# else:
# print(message.arbitration_id, message.data)
# # if "arbitration_id" not in HHlog.alarm_Always:
# # HHlog.alarm_Always["arbitration_id"] = message.arbitration_id
# # HHlog.logger.debug("arbitration_id error" + str(message.arbitration_id))
#
# # else:
#
# # # if "can_message" not in HHlog.alarm_Always:
# # # HHlog.alarm_Always["can_message"] = message
# # # HHlog.logger.debug(str(message))
#
# except can.CanError as e:
# print(f"process can fail: {e}")
def hander_inSignal(self):
"""
get_edge 沿检测 1 按键按下瞬间 -1 按键抬升瞬间
get_in 按键触发 1 按键按下 0 按键抬升
Returns
-------
"""
T_sawing_axis0 = self.get_edge("T横摆-")
P_sawing_axis0 = self.get_edge("P横摆-")
T_avc_axis0 = self.get_edge("T弧长-")
P_avc_axis0 = self.get_edge("P弧长-")
swing_axis0 = self.get_edge("送丝")
weld_axis0 = self.get_edge('后退')
T_sawing_axis1 = self.get_edge("T横摆+")
P_sawing_axis1 = self.get_edge("P横摆+")
T_avc_axis1 = self.get_edge("T弧长+")
P_avc_axis1 = self.get_edge("P弧长+")
swing_axis1 = self.get_edge("回丝")
weld_axis1 = self.get_edge('前进')
if self.get_in('锁定/解锁') == 0: # 解锁 and 手动
if self.get_edge('模拟/焊接') == 1: # 上升沿
print("模拟信号")
# if Task.isImitate:
# Task.isImitate = True # 模拟
# tube_sign.set_weld_open_sign.emit()
# else:
# Task.isImitate = False # 焊接
# tube_sign.set_weld_close_sign.emit()
# if self.get_edge()
weld_bt = self.get_in('后退') + self.get_in("前进")
swing_bt = self.get_in('送丝') + self.get_in("回丝")
p_avc_bt = self.get_in("P弧长-") + self.get_in("P弧长+")
t_avc_bt = self.get_in("T弧长-") + self.get_in("T弧长+")
sawing = self.get_in("T横摆-") + self.get_in("T横摆+") + self.get_in("P横摆-") + self.get_in("P横摆+")
# 前进后退只有一个按键按下
if weld_bt == 1:
if weld_axis1 == 1:
# 前进按下
print("运动轴", "遥控 小车轴+前进weld_axis +")
elif weld_axis0 == 1:
# 后退按下
print("运动轴", "遥控 小车轴-后退 weld_axis -")
# 送丝与回丝只有一个按键按下
if swing_bt == 1:
if swing_axis1 == 1:
# 回丝按下
print("送丝轴", "遥控 送丝 swing +")
elif swing_axis0 == 1:
# 送丝按下
print("送丝轴", "遥控 回丝swing -")
# P弧长+ P弧长- 只有一个按键按下
if p_avc_bt == 1:
if P_avc_axis1 == 1:
# P弧长+按下 但没有按下 P弧长-
print("弧长轴", "P弧长+ P_avc_axis+")
elif P_avc_axis0 == 1:
# P弧长-按下 但没有按下 P弧长+
print("弧长轴", "P弧长-P_avc_axis-")
# T弧长+ T弧长- 只有一个按键按下
if t_avc_bt == 1:
if T_avc_axis1 == 1:
# T弧长+按下
print("弧长轴", "T弧长+ T_avc_axis1+")
elif T_avc_axis0 == 1:
# T弧长-按下
print("弧长轴", "T弧长- T_avc_axis1-")
# T横摆- T横摆+ P横摆- P横摆+ 四个按钮只有一个被按下
if sawing == 1:
if T_sawing_axis1 == 1:
# Comm.set_DO("P/T切换", False)
# time.sleep(0.2)
print("横摆轴", "遙控 T横摆+ T_sawing_axis1+")
elif T_sawing_axis0 == 1:
# Comm.set_DO("P/T切换", False)
# time.sleep(0.2)
print("横摆轴", "遥控T横摆- T_sawing_axis1-")
elif P_sawing_axis1 == 1:
# Comm.set_DO("P/T切换", True)
# time.sleep(0.2)
print("横摆轴", "遥控 P横摆- P_sawing_axis1+")
elif P_sawing_axis0 == 1:
# Comm.set_DO("P/T切换", True)
# time.sleep(0.2)
print("横摆轴", "遥控 P横摆- P_sawing_axis-")
if weld_axis0 == 255 or weld_axis1 == 255:
print(0, "遥控 松开小车 weld_axis reset")
if swing_axis0 == 255 or swing_axis1 == 255:
print(1, "遥控 松开送丝/回丝 swing_axis reset")
if P_avc_axis0 == 255 or P_avc_axis1 == 255:
print(2, "遥控 松开P弧长 P_avc_axis reset")
if T_avc_axis0 == 255 or T_avc_axis1 == 255:
print(3, "遥控 松开T弧长 , T_avc_axis reset")
if (T_sawing_axis0 == 255 or T_sawing_axis1 == 255 or
P_sawing_axis0 == 255 or P_sawing_axis1 == 255):
print(4, "遥控 松开横摆 sawing_axis reset")
def get_in(self, name) -> int:
"""
Parameters
----------
name 信号名称
Returns 1 按钮按下 0 按钮抬起
-------
"""
return self.in_signal[CanBase.header_0x181[name]]
def get_edge(self, name):
"""
Parameters
----------
name 信号名称
Returns: 1 上升沿 0 保持 -1 下降沿
-------
"""
return self.in_edge[CanBase.header_0x181[name]]
def __del__(self):
self.stop_can()
# def errorRecord(text):
# log.errorLogWrite(text, "1")
# Task.estop_weld_sign = True
# taskMethod.taskStop()
Can = CanReceiver()
if __name__ == "__main__":
Can.connect()
# def bytes2bool(arr: bytes, bitorder='big'):
# """
# 将字节数组按位转换为布尔值数组,支持不同NumPy版本兼容
#
# 参数:
# arr: bytes
# bitorder: 'big'(高位在前) 或 'little'(低位在前)
#
# 返回:
# bool数组,形状为(arr.shape + (-1,8)) 1 or 0
# """
#
# uint8_arr = np.frombuffer(arr, dtype=np.uint8)
# # 处理版本兼容性
# if np.lib.NumpyVersion(np.__version__) >= '1.7.0':
# # 使用unpackbits标准方法
# unpacked = np.unpackbits(uint8_arr, axis=-1, bitorder=bitorder).reshape(-1, 8)
# # astype(bool)
# else:
# # 手动实现兼容旧版
# if bitorder == "big":
# mask = np.array([128, 64, 32, 16, 8, 4, 2, 1], dtype=np.uint8) # 位掩码
# else: # little
# mask = np.array([1, 2, 4, 8, 16, 32, 64, 128], dtype=np.uint8)
# # unpacked = (uint8_arr[..., None] & mask) != 0 # 广播按位与
# unpacked = np.where(uint8_arr[..., None] & mask, 1, 0) # 广播按位与
#
# return unpacked
下面是被导入的文件
import can
import time
from canBase import CanPowerBase
"""
80 00 16 00 10 00 06 07
在SDO中止传输帧中,数据部分的第4到第7字节组成的32位整数表示中止代码(Abort Code)。根据返回的数据:
第1字节: 0x80 (命令说明符)
第2字节: 0x00
第3字节: 0x16
第4字节: 0x00
第5字节: 0x10
第6字节: 0x00
第7字节: 0x06
第8字节: 0x07
其中,第5到第8字节(小端序)组成32位中止代码。由于CANopen协议规定多字节整数使用小端序,所以中止代码为0x07061000。
我们可以查阅CANopen的中止代码表:
0x06010000: 不支持的访问方式
0x06010001: 读一个只写对象
0x06010002: 写一个只读对象
0x06040041: 对象字典中不存在
0x06040042: 对象无法映射到PDO
0x06040043: 映射的PDO数量超出
0x06040047: 参数不兼容
0x06060000: 硬件错误
0x06070010: 数据类型不匹配(长度不同)
0x06070012: 数据长度超过
0x06090011: 子索引不存在
0x06090030: 值超出范围
0x08000000: 通用错误
"""
class CanPower():
# 帧类型定义 (CANopen标准)
NMT_CTRL = 0x000 # NMT控制帧
SYNC = 0x080 # SYNC帧
TIME_STAMP = 0x100 # 时间戳帧
PDO1_TX = 0x180 # TPDO1 (主站接收)
PDO1_RX = 0x200 # RPDO1 (主站发送)
PDO2_TX = 0x280 # TPDO2
PDO2_RX = 0x300 # RPDO2
SDO_TX = 0x580 # SDO传输 (从站->主站)
SDO_RX = 0x600 # SDO接收 (主站->从站)
NMT_GUARD = 0x700 # 节点保护帧
# 节点ID配置
NODE_ID = 0x05 # 从站节点ID
PROTECTED_NODES = [0x01] # 节点1受保护
def __init__(self, channel='can0', bustype='socketcan', bitrate=500000):
self.bus = None
self.channel = channel # 通道
self.bustype = bustype # 类型
self.bitrate = bitrate # 波特率
# 节点ID配置
self.NODE_ID5 = 0x05 # 从站节点ID
def sdo_write(self, node_id, index, subindex, value, data_length=None):
"""SDO写入对象字典(指定目标节点)
参数:
node_id: 目标CANopen节点的ID (0-127)
index: 对象字典的16位索引 (如0x2000)
subindex: 对象字典的8位子索引 (如0x00)
value: 要写入的值
data_length: 可选,指定写入的数据长度(字节数)。如果不指定,则根据value自动判断:
value<=0xFF -> 1字节
value<=0xFFFF -> 2字节
value<=0xFFFFFFFF -> 4字节
"""
if node_id in CanPower.PROTECTED_NODES:
raise PermissionError(f"禁止修改受保护节点{node_id}")
sdo_rx = 0x600 + node_id # SDO客户端->服务器地址
# 确定写入数据的长度
if data_length is None:
if value <= 0xFF:
data_length = 1
elif value <= 0xFFFF:
data_length = 2
elif value <= 0xFFFFFFFF:
data_length = 4
else:
raise ValueError("值太大,不支持")
else:
# 检查传入的data_length是否合理
max_value = (1 << (data_length * 8)) - 1
if value > max_value:
raise ValueError(f"值0x{value:X}超过{data_length}字节可表示的范围(0-0x{max_value:X})")
# 命令字节:
# data_length=1: 0x2F
# data_length=2: 0x2B
# data_length=4: 0x23
if data_length == 1:
command_byte = 0x2F
elif data_length == 2:
command_byte = 0x2B
elif data_length == 4:
command_byte = 0x23
else:
raise ValueError("data_length must be 1, 2, or 4")
# 构建数据帧
data = [
command_byte, # 命令字节
index & 0xFF, # 索引低字节
(index >> 8) & 0xFF, # 索引高字节
subindex, # 子索引
]
# 填充数据(小端序)
for i in range(data_length):
data.append((value >> (i * 8)) & 0xFF)
# 填充剩余部分为0
while len(data) < 8:
data.append(0)
msg = can.Message(
arbitration_id=sdo_rx,
data=data,
is_extended_id=False
)
self.bus.send(msg)
# 等待确认(只接收目标节点的响应)
start_time = time.time()
while time.time() - start_time < 1.0:
response = self.bus.recv(timeout=0.1)
if response and response.arbitration_id == (0x580 + node_id):
return response.data[0] == 0x60
return False
# def sdo_write(self, node_id, index, subindex, value, data_length=None):
# """增强版SDO写入函数,处理数值范围验证"""
# if node_id in self.PROTECTED_NODES:
# raise PermissionError(f"禁止修改受保护节点{node_id}")
#
# # 自动确定数据长度(如果未指定)
# if data_length is None:
# if value == 0:
# # 值为0时默认为4字节(最常见情况)
# data_length = 4
# elif value <= 0xFF:
# data_length = 1
# elif value <= 0xFFFF:
# data_length = 2
# elif value <= 0xFFFFFFFF:
# data_length = 4
# else:
# raise ValueError("值太大,不支持")
#
# # 验证值是否在指定长度范围内
# max_value = (1 << (data_length * 8)) - 1
# if value > max_value:
# error_msg = f"值0x{value:X}超过{data_length}字节范围(0-0x{max_value:X})"
# print(error_msg)
# # 尝试自动调整到最大有效值
# adjusted_value = min(value, max_value)
# print(f"自动调整为0x{adjusted_value:X}")
# value = adjusted_value
#
# # 命令字节映射
# length_to_cmd = {
# 1: 0x2F, # 写入1字节
# 2: 0x2B, # 写入2字节
# 4: 0x23 # 写入4字节
# }
#
# if data_length not in length_to_cmd:
# raise ValueError(f"无效的数据长度: {data_length}, 只支持1,2,4字节")
#
# command_byte = length_to_cmd[data_length]
#
# # 构建数据帧
# data = [
# command_byte,
# index & 0xFF, # 索引低字节
# (index >> 8) & 0xFF, # 索引高字节
# subindex, # 子索引
# ]
#
# # 添加数据值(小端序)
# for i in range(data_length):
# data.append((value >> (i * 8)) & 0xFF)
#
# # 填充剩余字节为0
# while len(data) < 8:
# data.append(0)
#
# # 发送SDO请求
# sdo_rx = 0x600 + node_id
# msg = can.Message(
# arbitration_id=sdo_rx,
# data=data,
# is_extended_id=False
# )
# self.bus.send(msg)
#
# # 等待响应并验证
# start_time = time.time()
# while time.time() - start_time < 1.0:
# response = self.bus.recv(timeout=0.1)
# if response and response.arbitration_id == (0x580 + node_id):
# # 检查响应类型
# if response.data[0] == 0x60: # 成功确认
# return True
# elif response.data[0] == 0x80: # 中止传输
# self.handle_sdo_abort(response, node_id, index, subindex, value)
# return False
#
# print("SDO写入超时,未收到响应")
# return False
def handle_sdo_abort(self, response, node_id, index, subindex, value):
"""处理SDO中止传输帧"""
if len(response.data) >= 8:
# 解析中止代码(小端序)
abort_code = (response.data[7] << 24) | (response.data[6] << 16) | \
(response.data[5] << 8) | response.data[4]
print(f"SDO中止: 索引0x{index:04X}.{subindex}, 值0x{value:X}, 代码0x{abort_code:08X}")
# 常见中止代码处理
abort_messages = {
0x06090030: "值超出范围",
0x06090011: "子索引不存在",
0x06040041: "对象字典中不存在",
0x06040042: "对象无法映射到PDO",
0x06070010: "数据类型不匹配",
0x06010002: "写入只读对象"
}
msg = abort_messages.get(abort_code, "未知错误")
print(f"错误原因: {msg}")
# 特殊处理:值超出范围
if abort_code == 0x06090030:
# 尝试查询有效范围
min_val = self.sdo_read(node_id, index, subindex + 1)
max_val = self.sdo_read(node_id, index, subindex + 2)
if min_val is not False and max_val is not False:
print(f"有效范围: {min_val}-{max_val}")
# 尝试使用边界值重试
adjusted_value = min(max(min_val, value), max_val)
print(f"尝试使用边界值: {adjusted_value}")
return self.sdo_write(node_id, index, subindex, adjusted_value)
return False
def sdo_read(self, node_id, index, subindex=0):
"""SDO读取对象字典"""
SDO_RX = CanPower.SDO_RX + node_id
SDO_TX = CanPower.SDO_TX + node_id
data = [
0x40, # 读取命令
index & 0xFF,
(index >> 8) & 0xFF,
subindex,
0, 0, 0, 0
]
msg = can.Message(
arbitration_id=SDO_RX,
data=data,
is_extended_id=False
)
self.bus.send(msg)
# 等待确认(只接收目标节点的响应)
start_time = time.time()
while time.time() - start_time < 1.0:
response = self.bus.recv(timeout=0.1)
if response and response.arbitration_id == SDO_TX:
if response.data[0] == 0x43: # 成功 done响应
return response.data[4] | (response.data[5] << 8) | \
(response.data[6] << 16) | (response.data[7] << 24)
return False
def reset_node_configuration(self, node_id):
"""
恢复节点的默认配置
步骤:
1. disabled 所有PDO映射
2. 重置PDO映射参数
3. 重启节点
"""
# 1. disabled 所有PDO
for pdo_type in ["RPDO", "TPDO"]:
for instance in range(1, 5): # PDO1-4
if pdo_type == "RPDO":
comm_index = 0x1400 + (instance - 1)
else:
comm_index = 0x1800 + (instance - 1)
# 读取当前COB-ID并设置disabled 位
self.sdo_write(node_id, comm_index, 1, 0x80000000)
# 2. 重置映射参数
for map_type in ["RPDO", "TPDO"]:
for instance in range(1, 5):
if map_type == "RPDO":
map_index = 0x1600 + (instance - 1)
else:
map_index = 0x1A00 + (instance - 1)
# 设置映射条目数为0
self.sdo_write(node_id, map_index, 0, 0)
# 清除所有映射条目
for subindex in range(1, 9):
self.sdo_write(node_id, map_index, subindex, 0)
# 3. 发送NMT重启命令
# NMT命令帧:ID=0x00, 数据=[命令, 节点ID]
nmt_msg = can.Message(
arbitration_id=0x000,
data=[0x81, node_id, 0, 0, 0, 0, 0, 0], # 0x81 = 复位节点
is_extended_id=False
)
self.bus.send(nmt_msg)
print(f"send node_id={node_id} reset 重启命令")
# 等待节点重新上线
time.sleep(2)
return True
def verify_node_config(self, node_id):
"""验证节点配置是否恢复默认"""
# 检查TPDO1映射条目数(应为0)
map_index = 0x1A00 # TPDO1映射参数
num_entries = self.sdo_read(node_id, map_index, 0)
if num_entries != 0:
print(f"节点{node_id}配置未完全恢复! node rsset Failure Mapping Entry数={num_entries}")
return False
# 检查RPDO1状态
comm_index = 0x1400
cobid = self.sdo_read(node_id, comm_index, 1)
if cobid & 0x80000000: # 检查disabled 位
print(f"节点{node_id} RPDO1仍被disabled ")
return False
return True
def disable_pdo(self, node_id, pdo_type, instance=1):
"""disabled PDO映射"""
if pdo_type == "RPDO":
comm_index = 0x1400 + (instance - 1)
else: # TPDO
comm_index = 0x1800 + (instance - 1)
# 设置COB-ID最高位为1 (disabled PDO)
current_cobid = self.sdo_read(node_id, comm_index, 1)
return self.sdo_write(node_id, comm_index, 1, current_cobid | 0x80000000)
def configure_pdo_mapping(self, node_id, pdo_type, instance, mappings):
"""
配置PDO映射
:param pdo_type: 'RPDO' 或 'TPDO'
:param instance: PDO实例 (1-4)
:param mappings: 映射列表,格式: [(对象索引, 子索引, 数据长度(位))]
"""
assert pdo_type in ['RPDO' , 'TPDO']
# 确定映射对象索引
print("start pdo_mapping","="*60)
if pdo_type == "RPDO":
comm_index = 0x1400 + (instance - 1)
map_index = 0x1600 + (instance - 1)
else: # TPDO
comm_index = 0x1800 + (instance - 1)
map_index = 0x1A00 + (instance - 1)
# disabled PDO
if not self.disable_pdo(node_id, pdo_type, instance):
print(f"disabled {pdo_type}{instance}失败 Failure")
return False
else:
print(f"disabled {pdo_type}{instance} Success")
# 设置映射条目数量
num_entries = len(mappings)
if not self.sdo_write(node_id, map_index, 0, num_entries):
print(f"设置 Seting Mapping Entry失败 Failure (Index 0x{map_index:04X})")
return False
# 配置每个映射条目
for i, mapping in enumerate(mappings, start=1):
obj_index, subindex, bit_length = mapping
# 构建映射值: 对象索引 + 子索引 + 数据长度
mapping_value = (obj_index << 16) | (subindex << 8) | (bit_length & 0xFF)
if not self.sdo_write(node_id, map_index, i, mapping_value):
print(f"Mapping Entry {i} 配置失败 Failure")
return False
# 重新启用PDO
if pdo_type == "RPDO":
cobid_base = 0x200 + node_id # NODE_ID
else:
cobid_base = 0x180 + node_id # NODE_ID
# 计算当前实例的COB-ID
cobid = cobid_base + (instance - 1) * 0x100
print("end pdo_mapping", "=" * 60)
# print(cobid)
return self.sdo_write(node_id, comm_index, 1, cobid)
def configure_all_mappings(self, node_id):
"""配置所有需要的PDO映射"""
# 1. 配置RPDO1 (0x205) 映射
rpdo1_mappings = [
(0x2000, 0, 1), # bAuto
(0x2001, 0, 1), # bWeldDisenable
(0x2002, 0, 1), # bPowerEn
(0x2003, 0, 1), # bPilotArcEn
(0x2010, 0, 1), # bCurrentLockEn
(0x2011, 0, 1), # bCurrentLockMode
(0x2004, 0, 1), # bPulseEn
]
if self.configure_pdo_mapping(node_id, "RPDO", 1, rpdo1_mappings):
print("RPDO1 (0x205) 映射配置成功 done")
else:
print("RPDO1 (0x205) 映射配置失败 Failure")
return False
# 2. 配置RPDO2 (0x305) 映射
rpdo2_mappings = [
(0x2005, 0, 16), # iCurrent1
(0x2006, 0, 16), # iCurrent2
(0x2007, 0, 16), # iHZ1
(0x2008, 0, 16), # iDutyCycle1
]
if self.configure_pdo_mapping(node_id,"RPDO", 2, rpdo2_mappings):
print("RPDO2 (0x305) 映射配置成功 done")
else:
print("RPDO2 (0x305) 映射配置失败 Failure")
return False
# 3. 配置TPDO1 (0x185) 映射
tpdo1_mappings = [
(0x2009, 0, 1), # bPowerAlarm
(0x200A, 0, 1), # bPowerReady
(0x200B, 0, 1), # bIGRO
(0x200C, 0, 1), # bPilotArcOk
(0x200D, 0, 1), # bPeakCurrentWorking
(0x2012, 0, 8), #
(0x200E, 0, 16), # iCurrentFeedback
(0x200F, 0, 16), # iActVoltage
]
if self.configure_pdo_mapping(node_id, "TPDO", 1, tpdo1_mappings):
print("TPDO1 (0x185) 映射配置成功 done")
else:
print("TPDO1 (0x185) 映射配置失败 Failure")
return False
"""
0xFF: 异步事件驱动 (制造商特定)
0xFE: 制造商特定同步帧
0x00-0xF0: 同步帧计数器值"""
tpdo1_comm_index = 0x1800 # TPDO1通信参数索引
# 2. 配置传输类型为异步事件驱动 (0xFF)
# 子索引2: 传输类型
if not self.sdo_write(node_id, tpdo1_comm_index, 2, 0xFF):
print("设置传输类型失败")
print("0x1800 ")
return False
# 3. 设置最小事件定时器 (1ms)
# 子索引5: 事件定时器 (单位ms)
if not self.sdo_write(node_id, tpdo1_comm_index, 5, 4):
print("设置事件定时器失败")
return False
# 4. 禁用禁止时间
# 子索引3: 禁止时间 (设为0表示禁用)
if not self.sdo_write(node_id, tpdo1_comm_index, 3, 0):
print("禁用禁止时间失败")
return False
# 4. 重新启用所有PDO(推荐)
# self.enable_all_pdo(node_id)
return True
def enable_all_pdo(self, node_id):
"""重新启用所有PDO"""
for pdo_type in ["RPDO", "TPDO"]:
for instance in range(1, 5): # PDO1-4
if pdo_type == "RPDO":
comm_index = 0x1400 + (instance - 1)
cobid_base = 0x200 + node_id # RPDO基础COB-ID
else:
comm_index = 0x1800 + (instance - 1)
cobid_base = 0x180 + node_id # TPDO基础COB-ID
# 计算当前实例的COB-ID
cobid = cobid_base + (instance - 1) * 0x100
# 清除禁用位(最高位)
enabled_cobid = cobid & 0x7FFFFFFF
# 重新启用PDO
self.sdo_write(node_id, comm_index, 1, enabled_cobid)
print(f"reboot node_id 启用节点{node_id}的所有PDO")
def check_node_state(self, node_id):
"""检查节点是否处于操作状态"""
# 读取节点状态 (索引0x2100)
state = self.sdo_read(node_id, 0x2100, 0)
if state is False:
print(f"无法读取节点{node_id}状态")
print(f"0x2100 not exisit")
return False
# CANopen节点状态定义:
# 0: 初始化
# 1: 未连接
# 2: 预操作
# 3: 停止
# 4: 操作
# 5: 预操作+启动
if state != 4: # 不是操作状态
print(f"节点{node_id}处于非操作状态(状态码:{state}),尝试设置为操作状态")
# 发送NMT命令设置节点为操作状态
nmt_msg = can.Message(
arbitration_id=0x000,
data=[0x01, node_id, 0, 0, 0, 0, 0, 0], # 0x01 = 启动节点
is_extended_id=False
)
self.bus.send(nmt_msg)
# 等待状态切换
time.sleep(0.5)
new_state = self.sdo_read(node_id, 0x2100, 0)
if new_state == 4:
print(f"节点{node_id}已成功设置为操作状态 Success")
return True
else:
print(f"无法设置节点{node_id}为操作状态 Failure")
return False
return True
def set_nmt_state(self, node_id, state):
"""设置节点NMT状态
state: 0x01 (启动), 0x02 (停止), 0x80 (预操作), 0x81 (复位节点), 0x82 (复位通信)
"""
msg = can.Message(
arbitration_id=0x000,
data=[state, node_id],
is_extended_id=False
)
self.bus.send(msg)
def connect(self, node_id = 0x05):
# node_id = 0x05
self.bus = can.interface.Bus(
channel=self.channel,
bustype=self.bustype,
bitrate=self.bitrate)
# # 添加过滤器,只接收需要的帧
# filters = [
# {"can_id": 0x580 + node_id, "can_mask": 0x7FF, "extended": False}, # SDO响应
# {"can_id": 0x180 + node_id, "can_mask": 0x7FF, "extended": False}, # TPDO1
# {"can_id": 0x580 + 0x01, "can_mask": 0x7FF, "extended": False}, # SDO响应
# {"can_id": 0x180 + 0x01, "can_mask": 0x7FF, "extended": False}, # TPDO1
# {"can_id": 0x000, "can_mask": 0x7FF, "extended": False}, # NMT
# ]
# self.bus.set_filters(filters)
#
#
# # 配置站点5的信号
self.reset_node_configuration(node_id)
# if not self.configure_all_mappings(node_id):
# print(" node id 0x05 Set Error ! reset......")
# self.reset_node_configuration(node_id)
# #
#
# self.set_nmt_state(node_id, 0x01)
return self.bus
# self.bus.shutdown()
# def ctyle(self):
if __name__ == "__main__":
power = CanPower()
power.connect()
关于can python 有俩个节点 1与5 。关于接收信号,我可以接收到701 与707 节点的数据帧\x05,和0x181,但收不到0x185的节点信息。
最新发布