using System; using System.Text; using Protocol376Simulator.Interfaces; namespace Protocol376Simulator.Models { /// /// 376.1协议消息类 /// public class Protocol376Message : IProtocolMessage { public byte StartByte { get; set; } = 0x68; public byte Length { get; set; } public byte ControlCode { get; set; } public byte[] Address { get; set; } = new byte[4]; public byte[] Data { get; set; } public byte CheckSum { get; set; } public byte EndByte { get; set; } = 0x16; public MessageType Type { get; set; } /// /// 将消息转换为字节数组 /// public byte[] ToBytes() { // 根据模板生成报文:68 36 00 36 00 68 C9 XX XX XX XX 07 02 70 00 00 04 00 29 F4 CS 16 var dataLength = Data?.Length ?? 0; var message = new byte[dataLength + 13]; // 计算实际长度 // 前缀部分 message[0] = 0x68; // 起始字节 message[1] = 0x36; // 长度域1 message[2] = 0x00; // 长度域2 message[3] = 0x36; // 长度域3(重复) message[4] = 0x00; // 长度域4(重复) message[5] = 0x68; // 起始字节(重复) message[6] = ControlCode; // 地址域 Array.Copy(Address, 0, message, 7, 4); // 数据域 if (Data != null) { Array.Copy(Data, 0, message, 11, dataLength); } // 计算校验和 byte sum = 0; for (int i = 5; i < message.Length - 2; i++) // 从第二个68H开始计算校验和 { sum += message[i]; } message[message.Length - 2] = sum; // 结束字节 message[message.Length - 1] = 0x16; return message; } /// /// 获取消息详细信息 /// public string GetMessageInfo() { var sb = new StringBuilder(); // 打印完整报文 var fullMessage = ToBytes(); sb.Append("完整报文: "); foreach (var b in fullMessage) { sb.Append($"{b:X2} "); } sb.AppendLine(); // 打印报文内容解析 sb.AppendLine("报文解析:"); sb.AppendLine($"报文类型: {Type}"); sb.AppendLine($"控制码: 0x{ControlCode:X2}"); // 地址域 sb.Append("地址域: "); foreach (var b in Address) { sb.Append($"{b:X2} "); } sb.AppendLine(); // 数据域 if (Data != null && Data.Length > 0) { sb.Append("数据域: "); foreach (var b in Data) { sb.Append($"{b:X2} "); } sb.AppendLine(); // 根据不同报文类型解析数据域 sb.AppendLine(ParseDataByType()); } else { sb.AppendLine("数据域: 无"); } return sb.ToString(); } private string ParseDataByType() { if (Data == null || Data.Length == 0) return "数据域解析: 无数据"; var sb = new StringBuilder(); sb.AppendLine("数据域解析:"); switch (Type) { case MessageType.Login: sb.AppendLine(" 登录报文"); if (Data.Length >= 7) { sb.AppendLine($" AFN: 0x{Data[0]:X2} - 登录请求"); sb.AppendLine($" 数据单元标识: {BitConverter.ToString(Data, 1, 2).Replace("-", " ")}"); } break; case MessageType.Heartbeat: sb.AppendLine(" 心跳报文"); if (Data.Length >= 9) { sb.AppendLine($" AFN: 0x{Data[0]:X2} - 心跳"); sb.AppendLine($" 数据单元标识: {BitConverter.ToString(Data, 1, 2).Replace("-", " ")}"); sb.AppendLine($" 自检状态信息: {BitConverter.ToString(Data, 7, 2).Replace("-", " ")}"); } break; case MessageType.ValveControl: sb.AppendLine(" 阀控操作报文"); if (Data.Length >= 7) { sb.AppendLine($" AFN: 0x{Data[0]:X2}"); sb.AppendLine($" 数据单元标识: {BitConverter.ToString(Data, 1, 2).Replace("-", " ")}"); sb.AppendLine($" 阀控操作码: 0x{Data[5]:X2}"); string operation = Data[5] switch { 0x01 => "开阀", 0x02 => "关阀", 0x03 => "查询状态", _ => "未知操作" }; sb.AppendLine($" 操作: {operation}"); } break; case MessageType.DataUpload: sb.AppendLine(" 数据上传报文"); if (Data.Length >= 5) { sb.AppendLine($" AFN: 0x{Data[0]:X2}"); sb.AppendLine($" 数据单元标识: {BitConverter.ToString(Data, 1, 2).Replace("-", " ")}"); sb.AppendLine($" 数据长度: {Data.Length - 5} 字节"); } break; case MessageType.Response: sb.AppendLine(" 响应报文"); if (Data.Length >= 3) { sb.AppendLine($" AFN: 0x{Data[0]:X2}"); sb.AppendLine($" 数据单元标识: {BitConverter.ToString(Data, 1, 2).Replace("-", " ")}"); if (Data.Length > 3) { sb.AppendLine($" 结果码: 0x{Data[3]:X2}"); string result = Data[3] == 0 ? "成功" : "失败"; sb.AppendLine($" 操作结果: {result}"); } } break; default: sb.AppendLine(" 未知报文类型"); break; } return sb.ToString(); } /// /// 转换集中器地址为字节数组 /// private static byte[] ConvertAddress(string concentratorAddress) { try { // 使用 BCD 编码转换集中器地址 var address = new byte[4]; // 假设集中器地址是9位数字,转换为4字节BCD if (concentratorAddress.Length != 9) { throw new ArgumentException("集中器地址必须是9位数字"); } // 左边高字节,右边低字节 address[0] = byte.Parse(concentratorAddress.Substring(0, 2), System.Globalization.NumberStyles.HexNumber); address[1] = byte.Parse(concentratorAddress.Substring(2, 2), System.Globalization.NumberStyles.HexNumber); address[2] = byte.Parse(concentratorAddress.Substring(4, 2), System.Globalization.NumberStyles.HexNumber); address[3] = byte.Parse(concentratorAddress.Substring(6, 2) + "0", System.Globalization.NumberStyles.HexNumber); return address; } catch (Exception ex) { throw new ArgumentException($"集中器地址转换失败: {ex.Message}", ex); } } /// /// 创建登录消息 /// /// 集中器地址 public static Protocol376Message CreateLoginMessage(string concentratorAddress) { var message = new Protocol376Message { ControlCode = 0xC9, // 控制码 Address = ConvertAddress(concentratorAddress), // 数据域 - 登录请求 Data = new byte[] { 0x07, 0x02, 0x70, 0x00, 0x00, 0x04, 0x00, 0x29, 0xF4 }, Type = MessageType.Login }; return message; } /// /// 创建心跳消息 /// /// 集中器地址 public static Protocol376Message CreateHeartbeatMessage(string concentratorAddress) { var message = new Protocol376Message { ControlCode = 0xC9, // 控制码 Address = ConvertAddress(concentratorAddress), // 数据域 - 心跳 Data = new byte[] { 0x08, 0x02, 0x70, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00 }, Type = MessageType.Heartbeat }; return message; } /// /// 创建阀控消息 /// /// 集中器地址 /// 阀门操作:1=开阀,2=关阀,3=查询状态 public static Protocol376Message CreateValveControlMessage(string concentratorAddress, byte valveOperation) { var message = new Protocol376Message { ControlCode = 0xC9, // 控制码 Address = ConvertAddress(concentratorAddress), // 数据域 - 阀控操作 Data = new byte[] { 0x09, 0x02, 0x70, 0x00, 0x00, valveOperation }, Type = MessageType.ValveControl }; return message; } /// /// 创建数据上传消息 /// /// 集中器地址 /// 表计数据 public static Protocol376Message CreateDataUploadMessage(string concentratorAddress, byte[] data) { if (data == null || data.Length == 0) { throw new ArgumentException("表计数据不能为空"); } // 构造数据域 var dataField = new byte[5 + data.Length]; dataField[0] = 0x0A; // AFN dataField[1] = 0x02; // 数据单元标识1 dataField[2] = 0x70; // 数据单元标识2 dataField[3] = 0x00; // 时间标签1 dataField[4] = 0x00; // 时间标签2 // 拷贝表计数据 Array.Copy(data, 0, dataField, 5, data.Length); var message = new Protocol376Message { ControlCode = 0xC9, // 控制码 Address = ConvertAddress(concentratorAddress), Data = dataField, Type = MessageType.DataUpload }; return message; } /// /// 创建读数据消息 /// /// 集中器地址 /// 数据类型:1=水表,2=电表,3=气表,4=状态 public static Protocol376Message CreateReadDataMessage(string concentratorAddress, byte dataType) { var message = new Protocol376Message { ControlCode = 0xC9, // 控制码 Address = ConvertAddress(concentratorAddress), // 数据域 - 读数据 Data = new byte[] { 0x0B, 0x02, 0x70, 0x00, 0x00, dataType }, Type = MessageType.ReadData }; return message; } /// /// 创建设置参数消息 /// /// 集中器地址 /// 参数类型 /// 参数数据 public static Protocol376Message CreateSetParameterMessage(string concentratorAddress, byte paramType, byte[] paramData) { if (paramData == null || paramData.Length == 0) { throw new ArgumentException("参数数据不能为空"); } // 构造数据域 var dataField = new byte[6 + paramData.Length]; dataField[0] = 0x0C; // AFN dataField[1] = 0x02; // 数据单元标识1 dataField[2] = 0x70; // 数据单元标识2 dataField[3] = 0x00; // 时间标签1 dataField[4] = 0x00; // 时间标签2 dataField[5] = paramType; // 参数类型 // 拷贝参数数据 Array.Copy(paramData, 0, dataField, 6, paramData.Length); var message = new Protocol376Message { ControlCode = 0xC9, // 控制码 Address = ConvertAddress(concentratorAddress), Data = dataField, Type = MessageType.SetParameter }; return message; } /// /// 从字节数组解析消息 /// /// 消息字节数组 /// 解析后的消息对象 public static Protocol376Message ParseFromBytes(byte[] messageBytes) { if (messageBytes == null || messageBytes.Length < 13) { throw new ArgumentException("消息字节数组格式无效"); } // 检查起始字节和结束字节 if (messageBytes[0] != 0x68 || messageBytes[5] != 0x68 || messageBytes[messageBytes.Length - 1] != 0x16) { throw new ArgumentException("消息格式无效:起始或结束字节错误"); } // 计算校验和 byte calculatedChecksum = 0; for (int i = 5; i < messageBytes.Length - 2; i++) { calculatedChecksum += messageBytes[i]; } // 验证校验和 byte receivedChecksum = messageBytes[messageBytes.Length - 2]; if (calculatedChecksum != receivedChecksum) { throw new ArgumentException($"校验和错误: 计算={calculatedChecksum:X2}, 接收={receivedChecksum:X2}"); } // 提取控制码 byte controlCode = messageBytes[6]; // 提取地址域 byte[] address = new byte[4]; Array.Copy(messageBytes, 7, address, 0, 4); // 提取数据域 int dataLength = messageBytes.Length - 13; byte[] data = new byte[dataLength]; if (dataLength > 0) { Array.Copy(messageBytes, 11, data, 0, dataLength); } // 识别消息类型 MessageType messageType = MessageType.Response; // 默认为响应类型 if (dataLength > 0) { messageType = data[0] switch { 0x07 => MessageType.Login, 0x08 => MessageType.Heartbeat, 0x09 => MessageType.ValveControl, 0x0A => MessageType.DataUpload, 0x0B => MessageType.ReadData, 0x0C => MessageType.SetParameter, _ => MessageType.Response }; } // 构造消息对象 var message = new Protocol376Message { ControlCode = controlCode, Address = address, Data = data, Type = messageType, CheckSum = receivedChecksum }; return message; } } }