using System; using System.Collections.Generic; using System.IO; using System.Text; namespace CCDCount.DLL.CanBus { /// /// EDS文件生成器 /// 用于生成PLC可识别的CANopen从站配置文件 /// public class EdsFileGenerator { private byte m_nodeId; public EdsFileGenerator(byte nodeId) { m_nodeId = nodeId; } /// /// 生成EDS文件 /// public string Generate(ObjectDictionary objectDict, string filePath = null) { StringBuilder sb = new StringBuilder(); // 文件头 sb.AppendLine("[FileInfo]"); sb.AppendLine("FileName=CanOpenSlave.eds"); sb.AppendLine("FileVersion=1"); sb.AppendLine("FileRevision=1"); sb.AppendLine($"CreationDate={DateTime.Now:MM-dd-yyyy}"); sb.AppendLine($"CreationTime={DateTime.Now:HH:mm:ss}"); sb.AppendLine("CreatedBy=CCDCount CANopen Slave Generator"); sb.AppendLine(""); // 设备信息 sb.AppendLine("[DeviceInfo]"); sb.AppendLine("VendorName=Custom"); sb.AppendLine("ProductName=CCDCount CANopen Slave Device"); sb.AppendLine("OrderCode=COS-001"); sb.AppendLine("VendorNumber=0x00000133"); sb.AppendLine("ProductNumber=0x00000001"); sb.AppendLine("RevisionNo=0x00010001"); sb.AppendLine($"SerialNumber=0x{m_nodeId:X8}"); sb.AppendLine("BaudRate_10=1"); sb.AppendLine("BaudRate_20=1"); sb.AppendLine("BaudRate_50=1"); sb.AppendLine("BaudRate_125=1"); sb.AppendLine("BaudRate_250=1"); sb.AppendLine("BaudRate_500=1"); sb.AppendLine("BaudRate_800=1"); sb.AppendLine("BaudRate_1000=1"); sb.AppendLine("SimpleBootUpMaster=0"); sb.AppendLine("SimpleBootUpSlave=1"); sb.AppendLine("GroupMessaging=0"); sb.AppendLine("NrOfRXPDO=4"); sb.AppendLine("NrOfTXPDO=4"); sb.AppendLine("LSS_Supported=0"); sb.AppendLine(""); // dummy usage sb.AppendLine("[DummyUsage]"); sb.AppendLine("Dummy0001=0"); sb.AppendLine("Dummy0002=0"); sb.AppendLine("Dummy0003=0"); sb.AppendLine("Dummy0004=0"); sb.AppendLine("Dummy0005=1"); sb.AppendLine("Dummy0006=0"); sb.AppendLine("Dummy0007=0"); sb.AppendLine(""); // 评论 sb.AppendLine("[Comments]"); sb.AppendLine("Lines=2"); sb.AppendLine("Line1=CANopen Slave Device"); sb.AppendLine($"Line2=Node ID: {m_nodeId}"); sb.AppendLine(""); // 强制对象 sb.AppendLine("[MandatoryObjects]"); sb.AppendLine("SupportedObjects=3"); sb.AppendLine("1=0x1000"); sb.AppendLine("2=0x1001"); sb.AppendLine("3=0x1018"); sb.AppendLine(""); // 制造商特定对象 sb.AppendLine("[ManufacturerObjects]"); sb.AppendLine("SupportedObjects=0"); sb.AppendLine(""); // 可选对象列表(不包含必选对象) var allObjects = objectDict.GetAllObjects(); List optionalIndexes = new List(); foreach (var index in allObjects.Keys) { // 排除必选对象(0x1000、0x1001、0x1018) if (index == 0x1000 || index == 0x1001 || index == 0x1018) continue; if ((index >= 0x1000 && index <= 0x1FFF) || (index >= 0x2000 && index <= 0x5FFF) || (index >= 0x1400 && index <= 0x15FF) || (index >= 0x1600 && index <= 0x17FF) || (index >= 0x1800 && index <= 0x19FF) || (index >= 0x1A00 && index <= 0x1BFF)) { if (!optionalIndexes.Contains(index)) optionalIndexes.Add(index); } } // 排序可选对象 optionalIndexes.Sort(); sb.AppendLine("[OptionalObjects]"); sb.AppendLine($"SupportedObjects={optionalIndexes.Count}"); for (int i = 0; i < optionalIndexes.Count; i++) { sb.AppendLine($"{i + 1}=0x{optionalIndexes[i]:X4}"); } sb.AppendLine(""); // 添加ObjectList部分(CANopen标准要求的完整对象列表) // ObjectList应该包含所有对象: 必选 + 可选 + 制造商特定 List allObjectIndexes = new List(); // 首先添加必选对象(InoProShop要求必选对象在前) allObjectIndexes.Add(0x1000); allObjectIndexes.Add(0x1001); allObjectIndexes.Add(0x1018); // 添加其他对象(已排除必选对象) allObjectIndexes.AddRange(optionalIndexes); sb.AppendLine("[ObjectList]"); sb.AppendLine($"SupportedObjects={allObjectIndexes.Count}"); for (int i = 0; i < allObjectIndexes.Count; i++) { sb.AppendLine($"{i + 1}=0x{allObjectIndexes[i]:X4}"); } sb.AppendLine(""); // 生成每个对象的详细信息 Console.WriteLine($"开始生成对象详细信息,共有 {allObjects.Count} 个对象"); foreach (var kvp in allObjects) { ushort index = kvp.Key; var subObjects = kvp.Value; // 只处理标准范围内的对象 if (!((index >= 0x1000 && index <= 0x1FFF) || (index >= 0x2000 && index <= 0x5FFF) || (index >= 0x1400 && index <= 0x15FF) || (index >= 0x1600 && index <= 0x17FF) || (index >= 0x1800 && index <= 0x19FF) || (index >= 0x1A00 && index <= 0x1BFF))) continue; Console.WriteLine($"生成对象 0x{index:X4}, 子对象数量: {subObjects.Count}"); GenerateObjectDetails(sb, index, subObjects, objectDict); } // 确保必选对象一定存在(0x1000, 0x1001, 0x1018) Console.WriteLine("检查必选对象..."); EnsureMandatoryObjectsExist(sb, allObjects, objectDict); string edsContent = sb.ToString(); // 如果指定了文件路径,则保存到文件 if (!string.IsNullOrEmpty(filePath)) { File.WriteAllText(filePath, edsContent, Encoding.ASCII); Console.WriteLine($"EDS文件已保存到: {filePath}"); } return edsContent; } private void GenerateObjectDetails(StringBuilder sb, ushort index, Dictionary subObjects, ObjectDictionary objectDict) { sb.AppendLine($"[0x{index:X4}]"); sb.AppendLine($"ParameterName={GetParameterName(index)}"); // 判断是否为复合类型(RECORD) // PDO通信参数、PDO映射参数、身份对象等都是复合类型 bool isRecordType = (index >= 0x1400 && index <= 0x15FF) || // RPDO通信参数 (index >= 0x1800 && index <= 0x19FF) || // TPDO通信参数 (index >= 0x1600 && index <= 0x17FF) || // RPDO映射参数 - 修正为RECORD类型 (index >= 0x1A00 && index <= 0x1BFF) || // TPDO映射参数 - 修正为RECORD类型 (index == 0x1018) || // 身份对象 (subObjects.Count > 1); // 其他有多个子对象的 byte objectTypeCode = isRecordType ? (byte)0x09 : (byte)0x07; sb.AppendLine($"ObjectType=0x{objectTypeCode:X2}"); if (isRecordType) { // 复合类型(有子索引) byte maxSubIndex = 0; foreach (var subIdx in subObjects.Keys) { if (subIdx > maxSubIndex) maxSubIndex = subIdx; } sb.AppendLine("SubNumber=" + (maxSubIndex + 1)); sb.AppendLine(""); // 生成每个子对象 for (byte subIdx = 0; subIdx <= maxSubIndex; subIdx++) { if (subObjects.ContainsKey(subIdx)) { sb.AppendLine($"[0x{index:X4}sub{subIdx}]"); sb.AppendLine($"ParameterName={GetSubParameterName(index, subIdx)}"); sb.AppendLine($"ObjectType=0x07"); var value = subObjects[subIdx]; DataTypeEnum dataType = GetDataType(value); sb.AppendLine($"DataType=0x{(int)dataType:X4}"); sb.AppendLine($"AccessType={GetAccessType(index, subIdx)}"); sb.AppendLine($"DefaultValue={FormatValue(value, dataType)}"); sb.AppendLine("PDOMapping=0"); sb.AppendLine("LowLimit=0"); sb.AppendLine("HighLimit=0"); sb.AppendLine(""); } } } else { // 简单类型 var value = subObjects.ContainsKey(0) ? subObjects[0] : null; if (value != null) { DataTypeEnum dataType = GetDataType(value); sb.AppendLine($"DataType=0x{(int)dataType:X4}"); sb.AppendLine($"AccessType={GetAccessType(index, 0)}"); sb.AppendLine($"DefaultValue={FormatValue(value, dataType)}"); sb.AppendLine("PDOMapping=0"); sb.AppendLine(""); } } } private string GetParameterName(ushort index) { switch (index) { case 0x1000: return "Device Type"; case 0x1001: return "Error Register"; case 0x1005: return "COB-ID SYNC"; case 0x1006: return "Sync Window Time"; case 0x1007: return "Sync Overflow Cycle"; case 0x1008: return "Manufacturer Device Name"; case 0x1009: return "Manufacturer Hardware Version"; case 0x100A: return "Manufacturer Software Version"; case 0x1017: return "Producer Heartbeat Time"; case 0x1018: return "Identity Object"; default: if (index >= 0x1400 && index <= 0x1403) return $"RPDO {index - 0x1400 + 1} Communication Parameter"; if (index >= 0x1600 && index <= 0x1603) return $"RPDO {index - 0x1600 + 1} Mapping Parameter"; if (index >= 0x1800 && index <= 0x1803) return $"TPDO {index - 0x1800 + 1} Communication Parameter"; if (index >= 0x1A00 && index <= 0x1A03) return $"TPDO {index - 0x1A00 + 1} Mapping Parameter"; if (index >= 0x6000 && index <= 0x600F) return "Digital Input"; if (index >= 0x6200 && index <= 0x620F) return "Digital Output"; if (index >= 0x6400 && index <= 0x640F) return "Analog Input"; if (index >= 0x6410 && index <= 0x641F) return "Analog Output"; return $"Unknown_0x{index:X4}"; } } private string GetSubParameterName(ushort index, byte subIndex) { if (subIndex == 0) return "Highest sub-index supported"; switch (index) { case 0x1018: switch (subIndex) { case 1: return "Vendor ID"; case 2: return "Product Code"; case 3: return "Revision Number"; case 4: return "Serial Number"; } break; case 0x1400: case 0x1401: case 0x1402: case 0x1403: switch (subIndex) { case 1: return "COB-ID used by RPDO"; case 2: return "Transmission Type"; } break; case 0x1800: case 0x1801: case 0x1802: case 0x1803: switch (subIndex) { case 1: return "COB-ID used by TPDO"; case 2: return "Transmission Type"; case 3: return "Inhibit Time"; case 5: return "Event Timer"; case 6: return "SYNC start value"; } break; case 0x1600: case 0x1601: case 0x1602: case 0x1603: case 0x1A00: case 0x1A01: case 0x1A02: case 0x1A03: return $"PDO mapping entry {subIndex}"; case 0x6000: case 0x6200: return $"Bit {subIndex}"; case 0x6400: case 0x6410: return $"Channel {subIndex}"; } return $"SubIndex_{subIndex}"; } private byte GetObjectTypeCode(ushort index, ObjectDictionary objectDict) { var allObjects = objectDict.GetAllObjects(); if (allObjects.ContainsKey(index)) { return (byte)(allObjects[index].Count > 1 ? 0x09 : 0x07); } return 0x07; } private DataTypeEnum GetDataType(object value) { if (value is byte) return DataTypeEnum.Unsigned8; if (value is sbyte) return DataTypeEnum.Integer8; if (value is ushort) return DataTypeEnum.Unsigned16; if (value is short) return DataTypeEnum.Integer16; if (value is uint) return DataTypeEnum.Unsigned32; if (value is int) return DataTypeEnum.Integer32; if (value is string) return DataTypeEnum.VisibleString; return DataTypeEnum.Unsigned32; } private string GetAccessType(ushort index, byte subIndex) { // 心跳时间可读写 if (index == 0x1017 && subIndex == 0) return "rw"; // PDO通信参数可读写 if ((index >= 0x1400 && index <= 0x1403) || (index >= 0x1800 && index <= 0x1803)) return "rw"; // PDO映射参数可读写 if ((index >= 0x1600 && index <= 0x1603) || (index >= 0x1A00 && index <= 0x1A03)) return "rw"; // 错误寄存器只读 if (index == 0x1001) return "ro"; // 设备信息只读 if (index >= 0x1008 && index <= 0x100A) return "ro"; if (index == 0x1018) return "ro"; // 默认读写 return "rw"; } private string FormatValue(object value, DataTypeEnum dataType) { if (value == null) return "0"; switch (dataType) { case DataTypeEnum.Unsigned8: return $"{Convert.ToByte(value)}"; case DataTypeEnum.Unsigned16: return $"{Convert.ToUInt16(value)}"; case DataTypeEnum.Unsigned32: return $"0x{Convert.ToUInt32(value):X8}"; case DataTypeEnum.Integer8: return $"{Convert.ToSByte(value)}"; case DataTypeEnum.Integer16: return $"{Convert.ToInt16(value)}"; case DataTypeEnum.Integer32: return $"{Convert.ToInt32(value)}"; case DataTypeEnum.VisibleString: return $"\"{value}\""; default: return value.ToString(); } } private void EnsureMandatoryObjectsExist(StringBuilder sb, Dictionary> allObjects, ObjectDictionary objectDict) { // 必选对象列表: 0x1000, 0x1001, 0x1018 ushort[] mandatoryIndexes = { 0x1000, 0x1001, 0x1018 }; foreach (ushort index in mandatoryIndexes) { // 如果该对象还没有被生成,则强制生成 if (!allObjects.ContainsKey(index)) { // 创建一个空的子对象字典,从objectDict中读取值 var subObjects = new Dictionary(); // 尝试从objectDict中获取该对象的值 try { if (index == 0x1018) { // 0x1018是复合类型,需要所有子索引 for (byte subIdx = 0; subIdx <= 4; subIdx++) { var value = objectDict.Read(index, subIdx); if (value != null) subObjects[subIdx] = value; } } else { // 0x1000和0x1001是简单类型 var value = objectDict.Read(index, 0); if (value != null) subObjects[0] = value; } } catch { // 如果读取失败,使用默认值 if (index == 0x1000) subObjects[0] = (uint)0x00000190; // 设备类型 else if (index == 0x1001) subObjects[0] = (byte)0x00; // 错误寄存器 else if (index == 0x1018) { subObjects[0] = (byte)4; subObjects[1] = (uint)0x00000133; // Vendor ID subObjects[2] = (uint)0x00000001; // Product Code subObjects[3] = (uint)0x00010001; // Revision Number subObjects[4] = (uint)m_nodeId; // Serial Number } } // 生成该对象的详细信息 if (subObjects.Count > 0) { GenerateObjectDetails(sb, index, subObjects, objectDict); } } } } } /// /// 数据类型枚举 /// public enum DataTypeEnum { Boolean = 0x0001, Integer8 = 0x0002, Integer16 = 0x0003, Integer32 = 0x0004, Unsigned8 = 0x0005, Unsigned16 = 0x0006, Unsigned32 = 0x0007, Real32 = 0x0008, VisibleString = 0x0009, OctetString = 0x000A, UnicodeString = 0x000B, TimeOfDay = 0x000C, TimeDifference = 0x000D } }