Protocol376Simulator/Models/Protocol376Message.cs
2025-05-08 17:26:10 +08:00

443 lines
16 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System;
using System.Text;
using Protocol376Simulator.Interfaces;
namespace Protocol376Simulator.Models
{
/// <summary>
/// 376.1协议消息类
/// </summary>
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; }
/// <summary>
/// 将消息转换为字节数组
/// </summary>
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;
}
/// <summary>
/// 获取消息详细信息
/// </summary>
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();
}
/// <summary>
/// 转换集中器地址为字节数组
/// </summary>
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);
}
}
/// <summary>
/// 创建登录消息
/// </summary>
/// <param name="concentratorAddress">集中器地址</param>
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;
}
/// <summary>
/// 创建心跳消息
/// </summary>
/// <param name="concentratorAddress">集中器地址</param>
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;
}
/// <summary>
/// 创建阀控消息
/// </summary>
/// <param name="concentratorAddress">集中器地址</param>
/// <param name="valveOperation">阀门操作1=开阀2=关阀3=查询状态</param>
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;
}
/// <summary>
/// 创建数据上传消息
/// </summary>
/// <param name="concentratorAddress">集中器地址</param>
/// <param name="data">表计数据</param>
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;
}
/// <summary>
/// 创建读数据消息
/// </summary>
/// <param name="concentratorAddress">集中器地址</param>
/// <param name="dataType">数据类型1=水表2=电表3=气表4=状态</param>
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;
}
/// <summary>
/// 创建设置参数消息
/// </summary>
/// <param name="concentratorAddress">集中器地址</param>
/// <param name="paramType">参数类型</param>
/// <param name="paramData">参数数据</param>
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;
}
/// <summary>
/// 从字节数组解析消息
/// </summary>
/// <param name="messageBytes">消息字节数组</param>
/// <returns>解析后的消息对象</returns>
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;
}
}
}