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})"; } } }