using CanTest;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace CCDCount.DLL.CanBus
{
///
/// CANopen主站管理器
/// 负责管理多个从站节点,实现NMT控制、SDO读写、PDO通信等功能
///
public class CanOpenMaster : IDisposable
{
#region 私有字段
private CanOpenManager m_canManager;
private Dictionary m_nodes;
private Dictionary m_edsDevices; // EDS设备信息缓存
private Thread m_receiveThread;
private volatile bool m_isRunning = false;
private CancellationTokenSource m_cancellationTokenSource;
// SYNC相关
private Timer m_syncTimer;
private byte m_syncCounter = 0;
private bool m_syncEnabled = false;
// 事件回调
public event Action OnTpdoReceived; // TPDO接收事件(节点ID, 数据)
public event Action OnHeartbeatReceived; // 心跳接收事件(节点ID, 状态)
public event Action OnEmergencyReceived; // 紧急消息事件(节点ID, 错误代码)
public event Action OnLogMessage; // 日志消息事件
#endregion
#region 属性
///
/// 已注册的节点列表
///
public List NodeIds => m_nodes.Keys.ToList();
///
/// 主站是否正在运行
///
public bool IsRunning => m_isRunning;
#endregion
#region 构造函数和初始化
///
/// 构造函数
///
/// 设备类型(默认4=USBCAN2)
/// 设备索引
/// CAN通道索引
public CanOpenMaster(UInt32 deviceType = 4, UInt32 deviceIndex = 0, UInt32 canIndex = 0)
{
m_canManager = new CanOpenManager(deviceType, deviceIndex, canIndex);
m_nodes = new Dictionary();
m_edsDevices = new Dictionary();
m_cancellationTokenSource = new CancellationTokenSource();
Log("CANopen主站对象创建成功");
}
///
/// 初始化主站
///
/// 波特率
/// 是否成功
public bool Initialize(CanBaudRate baudRate = CanBaudRate.BaudRate_500K)
{
try
{
Log($"正在初始化CANopen主站,波特率:{baudRate}...");
if (!m_canManager.Initialize(baudRate))
{
Log("CAN设备初始化失败");
return false;
}
// 启动接收线程
m_isRunning = true;
m_receiveThread = new Thread(ReceiveLoop);
m_receiveThread.IsBackground = true;
m_receiveThread.Start();
Log("CANopen主站初始化成功");
return true;
}
catch (Exception ex)
{
Log($"初始化失败: {ex.Message}");
return false;
}
}
#endregion
#region EDS文件支持(可选)
///
/// 为节点加载EDS文件(可选功能)
///
/// 节点ID
/// EDS文件路径
/// 是否成功
public bool LoadEdsFile(byte nodeId, string edsFilePath)
{
if (!System.IO.File.Exists(edsFilePath))
{
Log($"EDS文件不存在: {edsFilePath}");
return false;
}
try
{
var edsInfo = ParseEdsFile(edsFilePath);
if (edsInfo != null)
{
m_edsDevices[nodeId] = edsInfo;
Log($"节点{nodeId} EDS文件加载成功: {edsInfo.DeviceName}");
return true;
}
else
{
Log($"解析EDS文件失败: {edsFilePath}");
return false;
}
}
catch (Exception ex)
{
Log($"加载EDS文件异常: {ex.Message}");
return false;
}
}
///
/// 获取节点的EDS信息
///
public EdsDeviceInfo GetEdsInfo(byte nodeId)
{
m_edsDevices.TryGetValue(nodeId, out var info);
return info;
}
///
/// 根据EDS信息自动配置PDO映射
///
public bool AutoConfigurePdoFromEds(byte nodeId)
{
var edsInfo = GetEdsInfo(nodeId);
if (edsInfo == null)
{
Log($"节点{nodeId}没有EDS信息,无法自动配置PDO");
return false;
}
try
{
// 配置TPDO传输类型
foreach (var tpdo in edsInfo.TpdoMappings)
{
ConfigureTpdoTransmissionType(nodeId, tpdo.PdoNumber, tpdo.TransmissionType);
Log($"配置TPDO{tpdo.PdoNumber}: 传输类型={tpdo.TransmissionType}");
}
// 配置RPDO
foreach (var rpdo in edsInfo.RpdoMappings)
{
Log($"RPDO{rpdo.PdoNumber}已配置: {rpdo.MappedObjects.Count}个对象");
}
return true;
}
catch (Exception ex)
{
Log($"自动配置PDO失败: {ex.Message}");
return false;
}
}
///
/// 解析EDS文件(简化版)
///
private EdsDeviceInfo ParseEdsFile(string filePath)
{
var info = new EdsDeviceInfo();
info.DeviceName = System.IO.Path.GetFileNameWithoutExtension(filePath);
// TODO: 实现完整的EDS解析逻辑
// 这里提供一个简化版本,实际项目中可以使用现有的EdsFileGenerator反向解析
var lines = System.IO.File.ReadAllLines(filePath);
bool inDeviceInfo = false;
foreach (var line in lines)
{
var trimmed = line.Trim();
// 解析设备信息
if (trimmed == "[DeviceInfo]")
{
inDeviceInfo = true;
continue;
}
else if (trimmed.StartsWith("[") && inDeviceInfo)
{
inDeviceInfo = false;
}
if (inDeviceInfo)
{
if (trimmed.StartsWith("VendorName="))
info.VendorName = trimmed.Substring(11);
else if (trimmed.StartsWith("ProductName="))
info.ProductName = trimmed.Substring(12);
else if (trimmed.StartsWith("VendorNumber="))
{
if (uint.TryParse(trimmed.Substring(13), out uint vendorId))
info.VendorId = vendorId;
}
else if (trimmed.StartsWith("ProductNumber="))
{
if (uint.TryParse(trimmed.Substring(14), out uint productCode))
info.ProductCode = productCode;
}
}
// 解析PDO映射
if (trimmed.StartsWith("[1A00sub0]"))
{
// TPDO1映射对象数量
}
// ... 更多解析逻辑
}
return info;
}
#endregion
#region 节点管理
///
/// 添加从站节点
///
/// 节点ID(1-127)
/// 节点名称(可选)
/// EDS文件路径(可选)
/// 是否成功
public bool AddNode(byte nodeId, string nodeName = null, string edsFilePath = null)
{
if (nodeId == 0 || nodeId > 127)
{
Log($"无效的节点ID: {nodeId}");
return false;
}
if (m_nodes.ContainsKey(nodeId))
{
Log($"节点{nodeId}已存在");
return false;
}
var node = new CanOpenNode(nodeId, nodeName ?? $"Node_{nodeId}");
m_nodes[nodeId] = node;
// 如果提供了EDS文件,加载它
if (!string.IsNullOrEmpty(edsFilePath))
{
LoadEdsFile(nodeId, edsFilePath);
}
Log($"添加节点: ID={nodeId}, Name={node.NodeName}" +
(edsFilePath != null ? $", EDS={System.IO.Path.GetFileName(edsFilePath)}" : ""));
return true;
}
///
/// 移除从站节点
///
public bool RemoveNode(byte nodeId)
{
if (!m_nodes.ContainsKey(nodeId))
{
Log($"节点{nodeId}不存在");
return false;
}
m_nodes.Remove(nodeId);
Log($"移除节点: {nodeId}");
return true;
}
///
/// 获取节点对象
///
public CanOpenNode GetNode(byte nodeId)
{
if (m_nodes.TryGetValue(nodeId, out var node))
{
return node;
}
return null;
}
///
/// 批量添加节点
///
public void AddNodes(byte[] nodeIds)
{
foreach (var nodeId in nodeIds)
{
AddNode(nodeId);
}
}
#endregion
#region NMT控制
///
/// 启动所有节点
///
public void StartAllNodes()
{
Log("启动所有节点...");
foreach (var nodeId in m_nodes.Keys)
{
NmtStartNode(nodeId);
Thread.Sleep(10); // 避免总线拥塞
}
}
///
/// 停止所有节点
///
public void StopAllNodes()
{
Log("停止所有节点...");
foreach (var nodeId in m_nodes.Keys)
{
NmtStopNode(nodeId);
Thread.Sleep(10);
}
}
///
/// NMT启动指定节点
///
public void NmtStartNode(byte nodeId)
{
if (!ValidateNodeId(nodeId)) return;
m_canManager.NmtStartNode(nodeId);
UpdateNodeState(nodeId, NodeState.Operational);
Log($"NMT启动节点: {nodeId}");
}
///
/// NMT停止指定节点
///
public void NmtStopNode(byte nodeId)
{
if (!ValidateNodeId(nodeId)) return;
m_canManager.NmtStopNode(nodeId);
UpdateNodeState(nodeId, NodeState.Stopped);
Log($"NMT停止节点: {nodeId}");
}
///
/// NMT进入预操作状态
///
public void NmtEnterPreOperational(byte nodeId)
{
if (!ValidateNodeId(nodeId)) return;
m_canManager.NmtEnterPreOperational(nodeId);
UpdateNodeState(nodeId, NodeState.PreOperational);
Log($"NMT进入预操作状态: {nodeId}");
}
///
/// NMT重置节点
///
public void NmtResetNode(byte nodeId)
{
if (!ValidateNodeId(nodeId)) return;
m_canManager.NmtResetNode(nodeId);
UpdateNodeState(nodeId, NodeState.Initializing);
Log($"NMT重置节点: {nodeId}");
}
///
/// NMT重置通信
///
public void NmtResetCommunication(byte nodeId)
{
if (!ValidateNodeId(nodeId)) return;
m_canManager.NmtResetCommunication(nodeId);
Log($"NMT重置通信: {nodeId}");
}
#endregion
#region SDO通信
///
/// SDO读取(快速,不等待响应)
///
public void SdoReadQuick(byte nodeId, ushort index, byte subIndex)
{
if (!ValidateNodeId(nodeId)) return;
m_canManager.SdoReadQuick(nodeId, index, subIndex);
}
///
/// SDO写入(快速,不等待响应)
///
public void SdoWriteQuick(byte nodeId, ushort index, byte subIndex, uint data)
{
if (!ValidateNodeId(nodeId)) return;
m_canManager.SdoWrite(nodeId, index, subIndex, data);
}
///
/// SDO读取并等待响应
///
public byte[] SdoReadAndWait(byte nodeId, ushort index, byte subIndex, int timeoutMs = 1000)
{
if (!ValidateNodeId(nodeId)) return null;
var response = m_canManager.SdoReadAndWait(nodeId, index, subIndex, timeoutMs);
if (response != null)
{
var node = GetNode(nodeId);
if (node != null)
{
node.LastSdoResponseTime = DateTime.Now;
node.SdoSuccessCount++;
}
}
else
{
var node = GetNode(nodeId);
if (node != null)
{
node.SdoTimeoutCount++;
}
}
return response;
}
///
/// SDO写入并等待确认
///
public bool SdoWriteAndWait(byte nodeId, ushort index, byte subIndex, uint data, int timeoutMs = 1000)
{
if (!ValidateNodeId(nodeId)) return false;
var success = m_canManager.SdoWriteAndWait(nodeId, index, subIndex, data, timeoutMs);
var node = GetNode(nodeId);
if (node != null)
{
node.LastSdoResponseTime = DateTime.Now;
if (success)
node.SdoSuccessCount++;
else
node.SdoTimeoutCount++;
}
return success;
}
///
/// 读取设备类型
///
public uint ReadDeviceType(byte nodeId)
{
var response = SdoReadAndWait(nodeId, 0x1000, 0x00);
if (response != null && response.Length >= 4)
{
return BitConverter.ToUInt32(response, 4);
}
return 0;
}
///
/// 读取厂商ID
///
public uint ReadVendorId(byte nodeId)
{
var response = SdoReadAndWait(nodeId, 0x1018, 0x01);
if (response != null && response.Length >= 4)
{
return BitConverter.ToUInt32(response, 4);
}
return 0;
}
///
/// 读取产品代码
///
public uint ReadProductCode(byte nodeId)
{
var response = SdoReadAndWait(nodeId, 0x1018, 0x02);
if (response != null && response.Length >= 4)
{
return BitConverter.ToUInt32(response, 4);
}
return 0;
}
///
/// 读取序列号
///
public uint ReadSerialNumber(byte nodeId)
{
var response = SdoReadAndWait(nodeId, 0x1018, 0x04);
if (response != null && response.Length >= 4)
{
return BitConverter.ToUInt32(response, 4);
}
return 0;
}
#endregion
#region PDO通信
///
/// 发送SYNC帧
///
public void SendSync(byte counter = 0)
{
m_canManager.SendSync(counter);
}
///
/// 启用周期性SYNC
///
/// SYNC周期(毫秒)
public void EnableSync(int intervalMs = 10)
{
if (m_syncTimer != null)
{
m_syncTimer.Dispose();
}
m_syncEnabled = true;
m_syncCounter = 0;
m_syncTimer = new Timer(state =>
{
if (m_syncEnabled)
{
m_syncCounter++;
SendSync(m_syncCounter);
}
}, null, 0, intervalMs);
Log($"启用周期性SYNC,周期:{intervalMs}ms");
}
///
/// 禁用SYNC
///
public void DisableSync()
{
m_syncEnabled = false;
m_syncTimer?.Dispose();
m_syncTimer = null;
Log("禁用SYNC");
}
///
/// 配置TPDO传输类型
///
public void ConfigureTpdoTransmissionType(byte nodeId, byte pdoNumber, byte transmissionType)
{
if (!ValidateNodeId(nodeId)) return;
m_canManager.ConfigureTpdoTransmissionType(nodeId, pdoNumber, transmissionType);
}
///
/// 触发节点发送TPDO
///
public void TriggerTpdo(byte nodeId, byte pdoNumber)
{
if (!ValidateNodeId(nodeId)) return;
// 通过SDO设置TPDO传输类型为"事件驱动"
ushort index = (ushort)(0x1800 + pdoNumber - 1);
SdoWriteAndWait(nodeId, index, 0x02, 0xFF); // 0xFF表示事件驱动
Log($"触发节点{nodeId}发送TPDO{pdoNumber}");
}
///
/// 主站向从站发送RPDO数据(写入PDO)
///
/// 目标节点ID
/// RPDO编号(1-4)
/// 要发送的数据
/// 是否成功
public bool SendRpdo(byte nodeId, byte pdoNumber, byte[] data)
{
if (!ValidateNodeId(nodeId)) return false;
if (pdoNumber < 1 || pdoNumber > 4)
{
Log($"无效的RPDO编号: {pdoNumber}");
return false;
}
if (data == null || data.Length > 8)
{
Log("RPDO数据无效(必须1-8字节)");
return false;
}
// 计算RPDO的COB-ID
uint cobId = GetRpdoCobId(pdoNumber, nodeId);
// 发送CAN帧
bool success = m_canManager.SendCanFrame(cobId, data);
if (success)
{
var node = GetNode(nodeId);
if (node != null)
{
node.LastTpdoTime[pdoNumber] = DateTime.Now;
}
Log($"发送RPDO{pdoNumber}到节点{nodeId}: COB-ID=0x{cobId:X3}, 数据长度={data.Length}");
}
else
{
Log($"发送RPDO{pdoNumber}失败");
}
return success;
}
///
/// 发送RPDO并等待从站确认(通过心跳或SDO响应)
///
public bool SendRpdoAndWait(byte nodeId, byte pdoNumber, byte[] data, int timeoutMs = 100)
{
if (!SendRpdo(nodeId, pdoNumber, data))
return false;
// 等待一段时间,让从站处理数据
Thread.Sleep(timeoutMs);
// 检查从站是否在线(有心跳)
var node = GetNode(nodeId);
if (node != null)
{
return node.IsOnline(timeoutMs * 2);
}
return true;
}
///
/// 配置RPDO映射(通过SDO修改从站的对象字典)
///
/// 节点ID
/// RPDO编号(1-4)
/// 映射对象列表
/// 是否成功
public bool ConfigureRpdoMapping(byte nodeId, byte pdoNumber, List mappedObjects)
{
if (!ValidateNodeId(nodeId)) return false;
if (pdoNumber < 1 || pdoNumber > 4)
{
Log($"无效的RPDO编号: {pdoNumber}");
return false;
}
try
{
ushort mappingIndex = (ushort)(0x1600 + pdoNumber - 1); // RPDO映射索引
// 1. 先禁用RPDO(设置COB-ID最高位为1)
ushort commIndex = (ushort)(0x1400 + pdoNumber - 1);
uint currentCobId = BitConverter.ToUInt32(SdoReadAndWait(nodeId, commIndex, 0x01), 4);
uint disabledCobId = currentCobId | 0x80000000;
SdoWriteAndWait(nodeId, commIndex, 0x01, disabledCobId);
// 2. 设置映射对象数量
SdoWriteAndWait(nodeId, mappingIndex, 0x00, (uint)mappedObjects.Count);
// 3. 配置每个映射对象
for (int i = 0; i < mappedObjects.Count; i++)
{
var obj = mappedObjects[i];
byte subIndex = (byte)(i + 1);
// 打包映射条目: 0xIIIISSLL
uint mappingEntry = ((uint)obj.Index << 16) |
((uint)obj.SubIndex << 8) |
(uint)obj.BitLength;
SdoWriteAndWait(nodeId, mappingIndex, subIndex, mappingEntry);
Log($"配置RPDO{pdoNumber}映射[{subIndex}]: 0x{obj.Index:X4}:{obj.SubIndex:X2} ({obj.BitLength}位)");
}
// 4. 重新启用RPDO(清除COB-ID最高位)
uint enabledCobId = currentCobId & 0x7FFFFFFF;
SdoWriteAndWait(nodeId, commIndex, 0x01, enabledCobId);
Log($"RPDO{pdoNumber}映射配置完成: {mappedObjects.Count}个对象");
return true;
}
catch (Exception ex)
{
Log($"配置RPDO映射失败: {ex.Message}");
return false;
}
}
///
/// 配置TPDO映射(通过SDO修改从站的对象字典)
///
public bool ConfigureTpdoMapping(byte nodeId, byte pdoNumber, List mappedObjects)
{
if (!ValidateNodeId(nodeId)) return false;
if (pdoNumber < 1 || pdoNumber > 4)
{
Log($"无效的TPDO编号: {pdoNumber}");
return false;
}
try
{
ushort mappingIndex = (ushort)(0x1A00 + pdoNumber - 1); // TPDO映射索引
// 1. 先禁用TPDO
ushort commIndex = (ushort)(0x1800 + pdoNumber - 1);
uint currentCobId = BitConverter.ToUInt32(SdoReadAndWait(nodeId, commIndex, 0x01), 4);
uint disabledCobId = currentCobId | 0x80000000;
SdoWriteAndWait(nodeId, commIndex, 0x01, disabledCobId);
// 2. 设置映射对象数量
SdoWriteAndWait(nodeId, mappingIndex, 0x00, (uint)mappedObjects.Count);
// 3. 配置每个映射对象
for (int i = 0; i < mappedObjects.Count; i++)
{
var obj = mappedObjects[i];
byte subIndex = (byte)(i + 1);
uint mappingEntry = ((uint)obj.Index << 16) |
((uint)obj.SubIndex << 8) |
(uint)obj.BitLength;
SdoWriteAndWait(nodeId, mappingIndex, subIndex, mappingEntry);
}
// 4. 重新启用TPDO
uint enabledCobId = currentCobId & 0x7FFFFFFF;
SdoWriteAndWait(nodeId, commIndex, 0x01, enabledCobId);
Log($"TPDO{pdoNumber}映射配置完成: {mappedObjects.Count}个对象");
return true;
}
catch (Exception ex)
{
Log($"配置TPDO映射失败: {ex.Message}");
return false;
}
}
///
/// 获取RPDO的COB-ID
///
private uint GetRpdoCobId(byte pdoNumber, byte nodeId)
{
switch (pdoNumber)
{
case 1: return (uint)(0x200 + nodeId); // RPDO1
case 2: return (uint)(0x300 + nodeId); // RPDO2
case 3: return (uint)(0x400 + nodeId); // RPDO3
case 4: return (uint)(0x500 + nodeId); // RPDO4
default: throw new ArgumentException("无效的RPDO编号");
}
}
#endregion
#region 接收处理线程
private void ReceiveLoop()
{
Log("接收线程启动");
while (m_isRunning && !m_cancellationTokenSource.Token.IsCancellationRequested)
{
try
{
var frames = m_canManager.ReceiveCanFrames(100);
foreach (var frame in frames)
{
ProcessCanOpenFrame(frame);
}
}
catch (Exception ex)
{
Log($"接收线程异常: {ex.Message}");
}
}
Log("接收线程退出");
}
private void ProcessCanOpenFrame(CanOpenFrame frame)
{
try
{
// 解析COB-ID功能码
byte functionCode = (byte)((frame.CobId >> 7) & 0x0F);
byte nodeId = (byte)(frame.CobId & 0x7F);
switch (functionCode)
{
case 0x00: // NMT
ProcessNmtFrame(frame);
break;
case 0x01: // SYNC
// SYNC通常由主站发送,这里忽略
break;
case 0x02: // EMCY (紧急消息)
ProcessEmergencyFrame(nodeId, frame.Data);
break;
case 0x03: // TIME (时间戳对象)
// 可选功能,暂不处理
break;
case 0x05: // EMCY (扩展)
ProcessEmergencyFrame(nodeId, frame.Data);
break;
case 0x07: // Heartbeat / NMT Error Control
ProcessHeartbeatFrame(nodeId, frame.Data);
break;
case 0x08: // SDO Response
ProcessSdoResponse(nodeId, frame.Data);
break;
case 0x09: // SDO Request (主站通常不接收)
break;
default:
// PDO (0x0B-0x0F for TPDO, 0x13-0x17 for RPDO)
if (functionCode >= 0x0B && functionCode <= 0x0F)
{
ProcessTpdoFrame(nodeId, functionCode, frame.Data);
}
break;
}
}
catch (Exception ex)
{
Log($"处理帧异常: {ex.Message}");
}
}
private void ProcessNmtFrame(CanOpenFrame frame)
{
// NMT通常由主站发送,这里可以记录从站的NMT响应(如果有)
}
private void ProcessEmergencyFrame(byte nodeId, byte[] data)
{
if (data.Length >= 2)
{
ushort errorCode = m_canManager.ParseEmergencyMessage(data);
OnEmergencyReceived?.Invoke(nodeId, errorCode);
var node = GetNode(nodeId);
if (node != null)
{
node.CurrentErrorRegister = data.Length > 2 ? data[2] : (byte)0;
node.LastEmergencyTime = DateTime.Now;
node.EmergencyCount++;
}
Log($"收到紧急消息 - 节点{nodeId}: 错误代码0x{errorCode:X4}");
}
}
private void ProcessHeartbeatFrame(byte nodeId, byte[] data)
{
if (data.Length >= 1)
{
byte status = m_canManager.ParseHeartbeat(nodeId, data);
OnHeartbeatReceived?.Invoke(nodeId, status);
var node = GetNode(nodeId);
if (node != null)
{
node.CurrentState = (NodeState)(status & 0x7F);
node.LastHeartbeatTime = DateTime.Now;
node.HeartbeatCount++;
}
}
}
private void ProcessSdoResponse(byte nodeId, byte[] data)
{
// SDO响应已在CanOpenManager中处理
// 这里可以做额外的日志记录
}
private void ProcessTpdoFrame(byte nodeId, byte functionCode, byte[] data)
{
byte pdoNumber = (byte)(functionCode - 0x0A); // TPDO1=1, TPDO2=2, ...
OnTpdoReceived?.Invoke(nodeId, data);
var node = GetNode(nodeId);
if (node != null)
{
node.LastTpdoTime[pdoNumber] = DateTime.Now;
node.TpdoCount[pdoNumber]++;
// 存储最新TPDO数据
node.LastTpdoData[pdoNumber] = data;
}
}
#endregion
#region 辅助方法
private bool ValidateNodeId(byte nodeId)
{
if (nodeId == 0 || nodeId > 127)
{
Log($"无效的节点ID: {nodeId}");
return false;
}
if (!m_nodes.ContainsKey(nodeId))
{
Log($"节点{nodeId}未注册");
return false;
}
return true;
}
private void UpdateNodeState(byte nodeId, NodeState state)
{
var node = GetNode(nodeId);
if (node != null)
{
node.CurrentState = state;
node.LastStateChangeTime = DateTime.Now;
}
}
private void Log(string message)
{
string logMsg = $"[{DateTime.Now:HH:mm:ss.fff}] {message}";
Console.WriteLine(logMsg);
OnLogMessage?.Invoke(logMsg);
}
#endregion
#region 资源管理
///
/// 关闭主站
///
public void Close()
{
Log("正在关闭CANopen主站...");
// 停止接收线程
m_isRunning = false;
m_cancellationTokenSource?.Cancel();
if (m_receiveThread != null && m_receiveThread.IsAlive)
{
m_receiveThread.Join(1000);
}
// 禁用SYNC
DisableSync();
// 关闭CAN设备
m_canManager?.Close();
Log("CANopen主站已关闭");
}
///
/// 释放资源
///
public void Dispose()
{
Close();
m_cancellationTokenSource?.Dispose();
m_syncTimer?.Dispose();
}
#endregion
}
///
/// CANopen从站节点信息
///
public class CanOpenNode
{
#region 属性
///
/// 节点ID
///
public byte NodeId { get; private set; }
///
/// 节点名称
///
public string NodeName { get; private set; }
///
/// 当前状态
///
public NodeState CurrentState { get; set; }
///
/// 最后心跳时间
///
public DateTime LastHeartbeatTime { get; set; }
///
/// 心跳计数
///
public uint HeartbeatCount { get; set; }
///
/// 最后TPDO时间
///
public Dictionary LastTpdoTime { get; private set; }
///
/// TPDO计数
///
public Dictionary TpdoCount { get; private set; }
///
/// 最后TPDO数据
///
public Dictionary LastTpdoData { get; private set; }
///
/// 最后SDO响应时间
///
public DateTime LastSdoResponseTime { get; set; }
///
/// SDO成功计数
///
public uint SdoSuccessCount { get; set; }
///
/// SDO超时计数
///
public uint SdoTimeoutCount { get; set; }
///
/// 最后紧急消息时间
///
public DateTime LastEmergencyTime { get; set; }
///
/// 紧急消息计数
///
public uint EmergencyCount { get; set; }
///
/// 当前错误寄存器
///
public byte CurrentErrorRegister { get; set; }
///
/// 最后状态改变时间
///
public DateTime LastStateChangeTime { get; set; }
#endregion
#region 构造函数
public CanOpenNode(byte nodeId, string nodeName)
{
NodeId = nodeId;
NodeName = nodeName;
CurrentState = NodeState.Unknown;
LastTpdoTime = new Dictionary();
TpdoCount = new Dictionary();
LastTpdoData = new Dictionary();
// 初始化4个TPDO
for (byte i = 1; i <= 4; i++)
{
LastTpdoTime[i] = DateTime.MinValue;
TpdoCount[i] = 0;
LastTpdoData[i] = null;
}
}
#endregion
#region 方法
///
/// 检查节点是否在线(基于心跳超时判断)
///
/// 超时时间(毫秒)
public bool IsOnline(int timeoutMs = 1000)
{
if (LastHeartbeatTime == DateTime.MinValue)
return false;
return (DateTime.Now - LastHeartbeatTime).TotalMilliseconds < timeoutMs;
}
///
/// 获取节点状态字符串
///
public string GetStateString()
{
switch (CurrentState)
{
case NodeState.Initializing: return "Initializing";
case NodeState.Disconnected: return "Disconnected";
case NodeState.Connecting: return "Connecting";
case NodeState.PreOperational: return "Pre-Operational";
case NodeState.Operational: return "Operational";
case NodeState.Stopped: return "Stopped";
default: return "Unknown";
}
}
///
/// 获取节点统计信息
///
public override string ToString()
{
return $"Node {NodeId} ({NodeName}): State={GetStateString()}, " +
$"Heartbeats={HeartbeatCount}, " +
$"SDO(Success={SdoSuccessCount}, Timeout={SdoTimeoutCount}), " +
$"Emergencies={EmergencyCount}";
}
#endregion
}
///
/// 节点状态枚举
///
public enum NodeState
{
Unknown = 0x00,
Initializing = 0x00,
Disconnected = 0x01,
Connecting = 0x02,
PreOperational = 0x7F,
Operational = 0x05,
Stopped = 0x04
}
///
/// EDS设备信息(简化版)
///
public class EdsDeviceInfo
{
///
/// 设备名称
///
public string DeviceName { get; set; }
///
/// 厂商名称
///
public string VendorName { get; set; }
///
/// 产品名称
///
public string ProductName { get; set; }
///
/// 厂商ID
///
public uint VendorId { get; set; }
///
/// 产品代码
///
public uint ProductCode { get; set; }
///
/// 设备类型
///
public uint DeviceType { get; set; }
///
/// TPDO映射配置
///
public List TpdoMappings { get; set; } = new List();
///
/// RPDO映射配置
///
public List RpdoMappings { get; set; } = new List();
///
/// 对象字典条目
///
public Dictionary ObjectDictionary { get; set; }
= new Dictionary();
public override string ToString()
{
return $"{DeviceName} (Vendor={VendorName}, Product={ProductName})";
}
}
///
/// PDO映射配置
///
public class PdoMappingConfig
{
///
/// PDO编号(1-4)
///
public byte PdoNumber { get; set; }
///
/// COB-ID
///
public uint CobId { get; set; }
///
/// 传输类型
/// 0=禁止, 1=同步周期, 254=远程请求, 255=事件驱动
///
public byte TransmissionType { get; set; }
///
/// 映射的对象列表
///
public List MappedObjects { get; set; } = new List();
public override string ToString()
{
return $"PDO{PdoNumber}: COB-ID=0x{CobId:X3}, Type={TransmissionType}, Objects={MappedObjects.Count}";
}
}
///
/// 映射对象
///
public class MappedObject
{
///
/// 对象索引
///
public ushort Index { get; set; }
///
/// 子索引
///
public byte SubIndex { get; set; }
///
/// 数据长度(位)
///
public byte BitLength { get; set; }
public override string ToString()
{
return $"0x{Index:X4}:{SubIndex:X2} ({BitLength} bits)";
}
}
///
/// 对象字典条目
///
public class ObjectDictionaryEntry
{
///
/// 索引
///
public ushort Index { get; set; }
///
/// 对象名称
///
public string Name { get; set; }
///
/// 对象类型(VAR/RECORD/ARRAY)
///
public string ObjectType { get; set; }
///
/// 数据类型
///
public string DataType { get; set; }
///
/// 访问权限(ro/rw/wo)
///
public string AccessType { get; set; }
///
/// 默认值
///
public string DefaultValue { get; set; }
///
/// 子对象(如果是RECORD或ARRAY)
///
public Dictionary SubObjects { get; set; }
= new Dictionary();
public override string ToString()
{
return $"0x{Index:X4} {Name} ({ObjectType}, {DataType}, {AccessType})";
}
}
}