using CanTest;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace CCDCount.DLL.CanBus
{
///
/// CANopen从站 - 基于创芯科技CAN卡底层库实现
/// 支持SDO、PDO、NMT、心跳等标准CANopen从站功能
///
public class CanOpenSlave : IDisposable
{
#region 私有字段
private CanOpenManager m_canManager;
private ObjectDictionary m_objectDict;
private byte m_nodeId;
private NmtState m_currentState = NmtState.Initializing;
private bool m_isRunning = false;
private Thread m_receiveThread;
private CancellationTokenSource m_cancellationTokenSource;
// PDO配置
private PdoConfig[] m_tpdoConfigs = new PdoConfig[4];
private PdoConfig[] m_rpdoConfigs = new PdoConfig[4];
// 心跳配置
private ushort m_heartbeatTime = 1000; // 毫秒
private DateTime m_lastHeartbeatTime = DateTime.MinValue; // 初始化为最小值,确保首次立即发送
#endregion
#region 构造函数和初始化
///
/// 构造函数
///
/// 节点ID(1-127)
/// 设备类型
/// 设备索引
/// CAN通道索引
public CanOpenSlave(byte nodeId, UInt32 deviceType = 4, UInt32 deviceIndex = 0, UInt32 canIndex = 0)
{
if (nodeId < 1 || nodeId > 127)
throw new ArgumentException("节点ID必须在1-127之间");
m_nodeId = nodeId;
m_canManager = new CanOpenManager(deviceType, deviceIndex, canIndex);
m_objectDict = new ObjectDictionary(nodeId);
InitializePdoConfigs();
}
private void InitializePdoConfigs()
{
for (int i = 0; i < 4; i++)
{
m_tpdoConfigs[i] = new PdoConfig { Enabled = false, TransmissionType = 255 };
m_rpdoConfigs[i] = new PdoConfig { Enabled = false };
}
}
///
/// 初始化从站
///
/// 波特率
/// 是否成功
public bool Initialize(CanBaudRate baudRate = CanBaudRate.BaudRate_500K)
{
if (!m_canManager.Initialize(baudRate))
return false;
// 设置初始状态为预操作
m_currentState = NmtState.PreOperational;
// 立即发送第一个心跳,让PLC能够快速发现设备
SendHeartbeatImmediately();
Console.WriteLine($"CANopen从站初始化完成 - 节点ID: {m_nodeId}");
return true;
}
///
/// 立即发送心跳报文(用于设备发现)
///
private void SendHeartbeatImmediately()
{
byte statusByte = GetNodeStatusByte();
byte[] heartbeatData = new byte[] { statusByte };
uint heartbeatCobId = (uint)(0x700 + m_nodeId);
m_canManager.SendCanFrame(heartbeatCobId, heartbeatData);
m_lastHeartbeatTime = DateTime.Now;
}
///
/// 启动从站
///
public void Start()
{
if (m_isRunning)
return;
m_isRunning = true;
m_cancellationTokenSource = new CancellationTokenSource();
m_receiveThread = new Thread(ReceiveLoop);
m_receiveThread.IsBackground = true;
m_receiveThread.Start();
Console.WriteLine("CANopen从站已启动");
}
///
/// 停止从站
///
public void Stop()
{
m_isRunning = false;
m_cancellationTokenSource?.Cancel();
m_receiveThread?.Join(1000);
Console.WriteLine("CANopen从站已停止");
}
#endregion
#region 接收处理线程
private void ReceiveLoop()
{
while (m_isRunning && !m_cancellationTokenSource.Token.IsCancellationRequested)
{
try
{
var frames = m_canManager.ReceiveCanFrames(50);
foreach (var frame in frames)
{
ProcessCanFrame(frame);
}
// 发送心跳
SendHeartbeat();
Thread.Sleep(1);
}
catch (Exception ex)
{
Console.WriteLine($"接收线程异常: {ex.Message}");
}
}
}
private void ProcessCanFrame(CanOpenFrame frame)
{
uint cobId = frame.CobId;
// NMT消息处理(COB-ID = 0x000)
if (cobId == 0x000)
{
ProcessNmtMessage(frame);
return;
}
// SDO请求处理
if ((cobId & 0x780) == 0x600)
{
byte remoteNodeId = (byte)(cobId & 0x7F);
if (remoteNodeId == m_nodeId)
{
ProcessSdoRequest(frame);
}
return;
}
// RPDO处理
if ((cobId & 0x780) >= 0x200 && (cobId & 0x780) <= 0x500)
{
ProcessRpdo(frame);
return;
}
}
#endregion
#region NMT处理
private void ProcessNmtMessage(CanOpenFrame frame)
{
if (frame.DataLength < 2)
return;
byte command = frame.Data[0];
byte nodeId = frame.Data[1];
// 0表示广播到所有节点
if (nodeId != 0 && nodeId != m_nodeId)
return;
switch (command)
{
case 0x01: // 启动节点
if (m_currentState == NmtState.PreOperational || m_currentState == NmtState.Stopped)
{
m_currentState = NmtState.Operational;
Console.WriteLine($"NMT: 节点{m_nodeId}进入运行状态");
}
break;
case 0x02: // 停止节点
if (m_currentState == NmtState.Operational)
{
m_currentState = NmtState.Stopped;
Console.WriteLine($"NMT: 节点{m_nodeId}进入停止状态");
}
break;
case 0x80: // 进入预操作
m_currentState = NmtState.PreOperational;
Console.WriteLine($"NMT: 节点{m_nodeId}进入预操作状态");
break;
case 0x81: // 重置节点
ResetNode();
break;
case 0x82: // 重置通信
ResetCommunication();
break;
}
}
private void ResetNode()
{
Console.WriteLine($"NMT: 节点{m_nodeId}重置");
m_currentState = NmtState.Initializing;
Thread.Sleep(100);
m_currentState = NmtState.PreOperational;
}
private void ResetCommunication()
{
Console.WriteLine($"NMT: 节点{m_nodeId}重置通信");
// 重置PDO配置等通信参数
InitializePdoConfigs();
m_currentState = NmtState.PreOperational;
}
#endregion
#region SDO处理
private void ProcessSdoRequest(CanOpenFrame frame)
{
if (frame.DataLength < 8)
return;
byte sdoCommand = frame.Data[0];
ushort index = (ushort)(frame.Data[1] | (frame.Data[2] << 8));
byte subIndex = frame.Data[3];
// 判断是读取还是写入
if ((sdoCommand & 0xE0) == 0x40) // SDO上传请求(读)
{
HandleSdoRead(index, subIndex);
}
else if ((sdoCommand & 0xE0) == 0x20) // SDO下载请求(写)
{
HandleSdoWrite(sdoCommand, index, subIndex, frame.Data);
}
}
private void HandleSdoRead(ushort index, byte subIndex)
{
byte[] responseData = new byte[8];
// 从对象字典读取数据
object value = m_objectDict.Read(index, subIndex);
if (value == null)
{
// 对象不存在,返回错误
responseData[0] = 0x80; // abort transfer
responseData[1] = (byte)(index & 0xFF);
responseData[2] = (byte)(index >> 8);
responseData[3] = subIndex;
responseData[4] = 0x06; // 对象不存在
responseData[5] = 0x09;
responseData[6] = 0x00;
responseData[7] = 0x00;
SendSdoResponse(responseData);
Console.WriteLine($"SDO读失败: 0x{index:X4}sub{subIndex} 不存在");
return;
}
// 特殊处理字符串类型(设备名称、版本等)
if (value is string strVal)
{
// 对于可见字符串,需要分段传输或使用快速上传
byte[] strBytes = Encoding.ASCII.GetBytes(strVal);
int maxLen = Math.Min(strBytes.Length, 4); // 最多4字节用于快速上传
responseData[0] = (byte)(0x4F | ((4 - maxLen) << 2)); // 快速上传,指定有效字节数
responseData[1] = (byte)(index & 0xFF);
responseData[2] = (byte)(index >> 8);
responseData[3] = subIndex;
for (int i = 0; i < maxLen; i++)
{
responseData[4 + i] = strBytes[i];
}
SendSdoResponse(responseData);
Console.WriteLine($"SDO读字符串: 0x{index:X4}sub{subIndex} = {strVal.Substring(0, maxLen)}...");
return;
}
// 根据数据类型构造响应
if (value is byte byteVal)
{
responseData[0] = 0x4F; // 快速上传,1字节
responseData[1] = (byte)(index & 0xFF);
responseData[2] = (byte)(index >> 8);
responseData[3] = subIndex;
responseData[4] = byteVal;
}
else if (value is ushort ushortVal)
{
responseData[0] = 0x4B; // 快速上传,2字节
responseData[1] = (byte)(index & 0xFF);
responseData[2] = (byte)(index >> 8);
responseData[3] = subIndex;
responseData[4] = (byte)(ushortVal & 0xFF);
responseData[5] = (byte)(ushortVal >> 8);
}
else if (value is uint uintVal)
{
responseData[0] = 0x43; // 快速上传,4字节
responseData[1] = (byte)(index & 0xFF);
responseData[2] = (byte)(index >> 8);
responseData[3] = subIndex;
responseData[4] = (byte)(uintVal & 0xFF);
responseData[5] = (byte)((uintVal >> 8) & 0xFF);
responseData[6] = (byte)((uintVal >> 16) & 0xFF);
responseData[7] = (byte)((uintVal >> 24) & 0xFF);
}
else
{
// 不支持的类型
responseData[0] = 0x80;
responseData[4] = 0x06; // 对象不存在
Console.WriteLine($"SDO读失败: 0x{index:X4}sub{subIndex} 类型不支持");
}
SendSdoResponse(responseData);
}
private void HandleSdoWrite(byte command, ushort index, byte subIndex, byte[] data)
{
byte[] responseData = new byte[8];
bool success = false;
// 解析写入的值
uint writeValue = 0;
if ((command & 0x03) == 0x01) // 1字节
{
writeValue = data[4];
}
else if ((command & 0x03) == 0x02) // 2字节
{
writeValue = (uint)(data[4] | (data[5] << 8));
}
else if ((command & 0x03) == 0x00) // 4字节
{
writeValue = (uint)(data[4] | (data[5] << 8) | (data[6] << 16) | (data[7] << 24));
}
// 写入对象字典
success = m_objectDict.Write(index, subIndex, writeValue);
if (success)
{
// 发送确认响应
responseData[0] = 0x60;
responseData[1] = (byte)(index & 0xFF);
responseData[2] = (byte)(index >> 8);
responseData[3] = subIndex;
SendSdoResponse(responseData);
}
else
{
// 发送错误响应
responseData[0] = 0x80;
responseData[1] = (byte)(index & 0xFF);
responseData[2] = (byte)(index >> 8);
responseData[3] = subIndex;
responseData[4] = 0x06; // 对象不存在或只读
responseData[5] = 0x09;
responseData[6] = 0x00;
responseData[7] = 0x00;
SendSdoResponse(responseData);
}
}
private void SendSdoResponse(byte[] data)
{
uint responseCobId = (uint)(0x580 + m_nodeId);
m_canManager.SendCanFrame(responseCobId, data);
}
#endregion
#region PDO处理
private void ProcessRpdo(CanOpenFrame frame)
{
if (m_currentState != NmtState.Operational)
return;
// 解析COB-ID确定是哪个RPDO
uint baseId = frame.CobId & 0x780;
byte pdoNumber = 0;
if (baseId == 0x200) pdoNumber = 1;
else if (baseId == 0x300) pdoNumber = 2;
else if (baseId == 0x400) pdoNumber = 3;
else if (baseId == 0x500) pdoNumber = 4;
if (pdoNumber == 0)
return;
int configIndex = pdoNumber - 1;
if (!m_rpdoConfigs[configIndex].Enabled)
return;
// 将RPDO数据映射到对象字典
MapRpdoToObjectDict(pdoNumber, frame.Data, frame.DataLength);
Console.WriteLine($"收到RPDO{pdoNumber}: 节点{m_nodeId}, 长度{frame.DataLength}");
}
private void MapRpdoToObjectDict(byte pdoNumber, byte[] data, byte length)
{
// 根据配置的映射关系将数据写入对象字典
// 这里简化处理,实际应用中需要根据具体映射配置
ushort mappingIndex = (ushort)(0x1600 + pdoNumber - 1);
// 示例:假设第一个映射项是0x6000:00
object mappingEntry = m_objectDict.Read(mappingIndex, 0x01);
if (mappingEntry != null && mappingEntry is uint mapping)
{
ushort objIndex = (ushort)(mapping >> 16);
byte objSubIndex = (byte)(mapping & 0xFF);
if (length >= 1)
{
m_objectDict.Write(objIndex, objSubIndex, data[0]);
}
}
}
///
/// 发送TPDO
///
public void SendTpdo(byte pdoNumber)
{
if (m_currentState != NmtState.Operational)
return;
if (pdoNumber < 1 || pdoNumber > 4)
return;
int configIndex = pdoNumber - 1;
if (!m_tpdoConfigs[configIndex].Enabled)
return;
// 从对象字典读取数据并打包
byte[] tpdoData = PackTpdoData(pdoNumber);
if (tpdoData != null && tpdoData.Length > 0)
{
m_canManager.SendTpdo(m_nodeId, pdoNumber, tpdoData);
}
}
private byte[] PackTpdoData(byte pdoNumber)
{
List data = new List();
ushort mappingIndex = (ushort)(0x1A00 + pdoNumber - 1);
// 读取映射条目数量
object entryCountObj = m_objectDict.Read(mappingIndex, 0x00);
if (entryCountObj == null)
return null;
byte entryCount = Convert.ToByte(entryCountObj);
// 遍历所有映射项
for (byte i = 1; i <= entryCount; i++)
{
object mappingObj = m_objectDict.Read(mappingIndex, i);
if (mappingObj == null)
continue;
uint mapping = Convert.ToUInt32(mappingObj);
ushort objIndex = (ushort)(mapping >> 16);
byte objSubIndex = (byte)((mapping >> 8) & 0xFF);
byte dataSize = (byte)(mapping & 0xFF);
object value = m_objectDict.Read(objIndex, objSubIndex);
if (value == null)
continue;
// 根据数据大小添加字节
if (dataSize == 8 && value is byte byteVal)
{
data.Add(byteVal);
}
else if (dataSize == 16 && value is ushort ushortVal)
{
data.Add((byte)(ushortVal & 0xFF));
data.Add((byte)(ushortVal >> 8));
}
else if (dataSize == 32 && value is uint uintVal)
{
data.Add((byte)(uintVal & 0xFF));
data.Add((byte)((uintVal >> 8) & 0xFF));
data.Add((byte)((uintVal >> 16) & 0xFF));
data.Add((byte)((uintVal >> 24) & 0xFF));
}
}
return data.ToArray();
}
#endregion
#region 心跳机制
private void SendHeartbeat()
{
if (m_heartbeatTime == 0)
return;
TimeSpan elapsed = DateTime.Now - m_lastHeartbeatTime;
if (elapsed.TotalMilliseconds >= m_heartbeatTime)
{
byte statusByte = GetNodeStatusByte();
byte[] heartbeatData = new byte[] { statusByte };
uint heartbeatCobId = (uint)(0x700 + m_nodeId);
m_canManager.SendCanFrame(heartbeatCobId, heartbeatData);
m_lastHeartbeatTime = DateTime.Now;
}
}
private byte GetNodeStatusByte()
{
switch (m_currentState)
{
case NmtState.Initializing:
return 0x00;
case NmtState.PreOperational:
return 0x7F;
case NmtState.Operational:
return 0x05;
case NmtState.Stopped:
return 0x04;
default:
return 0x7F;
}
}
///
/// 设置心跳时间
///
public void SetHeartbeatTime(ushort milliseconds)
{
m_heartbeatTime = milliseconds;
m_objectDict.Write(0x1017, 0x00, milliseconds);
}
#endregion
#region 公共API
///
/// 设置对象字典值
///
public bool SetObjectValue(ushort index, byte subIndex, object value)
{
return m_objectDict.Write(index, subIndex, value);
}
///
/// 获取对象字典值
///
public object GetObjectValue(ushort index, byte subIndex)
{
return m_objectDict.Read(index, subIndex);
}
///
/// 配置TPDO
///
public void ConfigureTpdo(byte pdoNumber, bool enabled, byte transmissionType = 255)
{
if (pdoNumber < 1 || pdoNumber > 4)
return;
int index = pdoNumber - 1;
m_tpdoConfigs[index].Enabled = enabled;
m_tpdoConfigs[index].TransmissionType = transmissionType;
// 更新对象字典
ushort commIndex = (ushort)(0x1800 + index);
m_objectDict.Write(commIndex, 0x01, enabled ? (uint)(0x180 + m_nodeId + index * 0x100) : (uint)(0x180 + m_nodeId + index * 0x100 | 0x80000000));
m_objectDict.Write(commIndex, 0x02, transmissionType);
}
///
/// 配置RPDO
///
public void ConfigureRpdo(byte pdoNumber, bool enabled)
{
if (pdoNumber < 1 || pdoNumber > 4)
return;
int index = pdoNumber - 1;
m_rpdoConfigs[index].Enabled = enabled;
// 更新对象字典
ushort commIndex = (ushort)(0x1400 + index);
m_objectDict.Write(commIndex, 0x01, enabled ? (uint)(0x200 + m_nodeId + index * 0x100) : (uint)(0x200 + m_nodeId + index * 0x100 | 0x80000000));
}
///
/// 获取当前NMT状态
///
public NmtState GetCurrentState()
{
return m_currentState;
}
///
/// 生成EDS文件
///
public string GenerateEdsFile(string filePath = null)
{
var edsGenerator = new EdsFileGenerator(m_nodeId);
return edsGenerator.Generate(m_objectDict, filePath);
}
#endregion
#region 资源管理
public void Dispose()
{
Stop();
m_canManager?.Dispose();
}
#endregion
}
#region 辅助类
///
/// NMT状态枚举
///
public enum NmtState
{
Initializing,
PreOperational,
Operational,
Stopped
}
///
/// PDO配置
///
public class PdoConfig
{
public bool Enabled { get; set; }
public byte TransmissionType { get; set; }
public uint CobId { get; set; }
}
#endregion
}