374 lines
15 KiB
C#
374 lines
15 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using System.Linq;
|
||
using System.Text;
|
||
|
||
namespace Protocol376Simulator.Models
|
||
{
|
||
public class MessageAnalyzer
|
||
{
|
||
// AFN功能码描述
|
||
private static readonly Dictionary<byte, string> AfnDescriptions = new Dictionary<byte, string>
|
||
{
|
||
{ 0x00, "确认/否认" },
|
||
{ 0x01, "复位" },
|
||
{ 0x02, "链路接口检测" },
|
||
{ 0x04, "设置参数" },
|
||
{ 0x05, "控制命令" },
|
||
{ 0x06, "身份认证及密钥协商" },
|
||
{ 0x07, "自定义数据请求" },
|
||
{ 0x08, "主动上报" },
|
||
{ 0x09, "读取数据" },
|
||
{ 0x0A, "查询参数" },
|
||
{ 0x0B, "软件升级" },
|
||
{ 0x0F, "文件传输" }
|
||
};
|
||
|
||
/// <summary>
|
||
/// 分析报文内容并生成详细解释
|
||
/// </summary>
|
||
/// <param name="message">原始报文字节数组</param>
|
||
/// <returns>报文分析结果</returns>
|
||
public static string AnalyzeMessage(byte[] message)
|
||
{
|
||
var sb = new StringBuilder();
|
||
sb.AppendLine("====== 报文详细分析 ======");
|
||
|
||
// 检查报文合法性
|
||
if (message.Length < 13 || message[0] != 0x68 || message[5] != 0x68 || message[message.Length - 1] != 0x16)
|
||
{
|
||
sb.AppendLine("❌ 报文格式不合法!");
|
||
sb.AppendLine($"原始报文: {BitConverter.ToString(message).Replace("-", " ")}");
|
||
return sb.ToString();
|
||
}
|
||
|
||
// 报文基本信息
|
||
sb.AppendLine($"原始报文: {BitConverter.ToString(message).Replace("-", " ")}");
|
||
sb.AppendLine($"报文长度: {message.Length} 字节");
|
||
|
||
// 头部分析
|
||
sb.AppendLine("\n【帧头结构】");
|
||
sb.AppendLine($"起始字节1: 0x{message[0]:X2}");
|
||
sb.AppendLine($"长度域1: 0x{message[1]:X2} 0x{message[2]:X2}");
|
||
sb.AppendLine($"长度域2: 0x{message[3]:X2} 0x{message[4]:X2}");
|
||
sb.AppendLine($"起始字节2: 0x{message[5]:X2}");
|
||
|
||
// 控制域分析
|
||
byte controlCode = message[6];
|
||
sb.AppendLine("\n【控制域】");
|
||
sb.AppendLine($"控制码: 0x{controlCode:X2}");
|
||
|
||
// 解析控制码
|
||
sb.AppendLine(AnalyzeControlCode(controlCode));
|
||
|
||
// 地址域分析
|
||
byte[] address = message.Skip(7).Take(4).ToArray();
|
||
sb.AppendLine("\n【地址域】");
|
||
sb.AppendLine($"地址域: {BitConverter.ToString(address).Replace("-", " ")}");
|
||
sb.AppendLine($"BCD编码地址: {BcdToString(address)}");
|
||
|
||
// 数据域分析
|
||
int dataLength = message.Length - 13; // 减去帧头、控制域、地址域、校验和、结束符
|
||
if (dataLength > 0)
|
||
{
|
||
byte[] data = message.Skip(11).Take(dataLength).ToArray();
|
||
sb.AppendLine("\n【数据域】");
|
||
sb.AppendLine($"数据域: {BitConverter.ToString(data).Replace("-", " ")}");
|
||
sb.AppendLine($"数据长度: {dataLength} 字节");
|
||
|
||
// AFN分析
|
||
if (data.Length > 0)
|
||
{
|
||
byte afn = data[0];
|
||
string afnDesc = AfnDescriptions.ContainsKey(afn) ? AfnDescriptions[afn] : "未知功能";
|
||
sb.AppendLine($"应用功能码(AFN): 0x{afn:X2} - {afnDesc}");
|
||
}
|
||
|
||
// 数据单元标识分析
|
||
if (data.Length > 2)
|
||
{
|
||
sb.AppendLine($"数据单元标识: 0x{data[1]:X2} 0x{data[2]:X2}");
|
||
}
|
||
|
||
// 根据AFN进一步分析数据内容
|
||
if (data.Length > 0)
|
||
{
|
||
sb.AppendLine(AnalyzeDataByAfn(data));
|
||
}
|
||
}
|
||
else
|
||
{
|
||
sb.AppendLine("\n【数据域】");
|
||
sb.AppendLine("无数据域内容");
|
||
}
|
||
|
||
// 校验和分析
|
||
byte calculatedCs = CalculateChecksum(message);
|
||
byte actualCs = message[message.Length - 2];
|
||
sb.AppendLine("\n【校验和】");
|
||
sb.AppendLine($"校验和: 0x{actualCs:X2} ({(calculatedCs == actualCs ? "✓ 正确" : "❌ 错误,应为 0x" + calculatedCs.ToString("X2"))})");
|
||
|
||
// 结束符
|
||
sb.AppendLine("\n【结束符】");
|
||
sb.AppendLine($"结束字节: 0x{message[message.Length - 1]:X2}");
|
||
|
||
return sb.ToString();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 解析控制码
|
||
/// </summary>
|
||
private static string AnalyzeControlCode(byte controlCode)
|
||
{
|
||
var sb = new StringBuilder();
|
||
|
||
// 方向位D7 (DIR)
|
||
bool isDownlink = (controlCode & 0x80) != 0;
|
||
sb.AppendLine($"方向位(D7): {(isDownlink ? "下行 (主站→集中器)" : "上行 (集中器→主站)")}");
|
||
|
||
// 启动标志位D6 (PRM)
|
||
bool isPrimaryStation = (controlCode & 0x40) != 0;
|
||
sb.AppendLine($"启动标志位(D6): {(isPrimaryStation ? "启动站" : "从动站")}");
|
||
|
||
// 帧计数位D5 (FCB)
|
||
bool fcb = (controlCode & 0x20) != 0;
|
||
|
||
// 功能码D0-D3
|
||
byte functionCode = (byte)(controlCode & 0x0F);
|
||
|
||
// 根据PRM和功能码解析不同的含义
|
||
if (isPrimaryStation)
|
||
{
|
||
sb.AppendLine($"帧计数位(D5): {fcb}");
|
||
sb.AppendLine($"功能码(D0-D3): 0x{functionCode:X1} - {GetPrimaryFunctionCodeDesc(functionCode)}");
|
||
}
|
||
else
|
||
{
|
||
bool dfc = (controlCode & 0x10) != 0;
|
||
sb.AppendLine($"数据流控制位(D4): {dfc} - {(dfc ? "从站接收缓冲区已满" : "从站可以接收数据")}");
|
||
sb.AppendLine($"功能码(D0-D3): 0x{functionCode:X1} - {GetSecondaryFunctionCodeDesc(functionCode)}");
|
||
}
|
||
|
||
return sb.ToString();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取启动站功能码描述
|
||
/// </summary>
|
||
private static string GetPrimaryFunctionCodeDesc(byte code)
|
||
{
|
||
return code switch
|
||
{
|
||
0 => "复位远方链路",
|
||
1 => "读取状态",
|
||
3 => "发送/确认用户数据",
|
||
4 => "发送/无需确认用户数据",
|
||
9 => "请求/响应链路状态",
|
||
10 => "请求/响应用户数据1",
|
||
11 => "请求/响应用户数据2",
|
||
_ => "未知功能"
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取从动站功能码描述
|
||
/// </summary>
|
||
private static string GetSecondaryFunctionCodeDesc(byte code)
|
||
{
|
||
return code switch
|
||
{
|
||
0 => "确认",
|
||
1 => "链路忙",
|
||
9 => "从站状态",
|
||
11 => "响应用户数据",
|
||
_ => "未知功能"
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 根据不同的AFN值分析数据域
|
||
/// </summary>
|
||
private static string AnalyzeDataByAfn(byte[] data)
|
||
{
|
||
if (data.Length == 0)
|
||
return "数据为空";
|
||
|
||
var sb = new StringBuilder();
|
||
byte afn = data[0];
|
||
|
||
sb.AppendLine("\n【数据域解析】");
|
||
|
||
switch (afn)
|
||
{
|
||
case 0x00: // 确认/否认
|
||
sb.AppendLine("确认/否认报文");
|
||
if (data.Length > 5)
|
||
{
|
||
byte resultCode = data[5];
|
||
sb.AppendLine($"结果码: 0x{resultCode:X2} - {(resultCode == 0 ? "成功" : "失败")}");
|
||
}
|
||
break;
|
||
|
||
case 0x02: // 链路接口检测
|
||
sb.AppendLine("链路接口检测报文");
|
||
break;
|
||
|
||
case 0x04: // 设置参数
|
||
sb.AppendLine("设置参数报文");
|
||
if (data.Length > 5)
|
||
{
|
||
byte paramType = data[5];
|
||
sb.AppendLine($"参数类型: 0x{paramType:X2}");
|
||
|
||
// 解析参数内容
|
||
if (data.Length > 7)
|
||
{
|
||
byte paramLength = data[6];
|
||
byte[] paramValue = data.Skip(7).Take(paramLength).ToArray();
|
||
sb.AppendLine($"参数长度: {paramLength} 字节");
|
||
sb.AppendLine($"参数值: {BitConverter.ToString(paramValue).Replace("-", " ")}");
|
||
|
||
// 尝试转换为ASCII显示
|
||
try
|
||
{
|
||
string asciiValue = System.Text.Encoding.ASCII.GetString(paramValue)
|
||
.Replace('\0', '.')
|
||
.Replace('\r', '.')
|
||
.Replace('\n', '.');
|
||
sb.AppendLine($"参数值(ASCII): \"{asciiValue}\"");
|
||
}
|
||
catch
|
||
{
|
||
// 忽略无法转换的情况
|
||
}
|
||
}
|
||
}
|
||
break;
|
||
|
||
case 0x07: // 自定义数据
|
||
sb.AppendLine("自定义数据报文");
|
||
if (data.Length > 2)
|
||
{
|
||
byte dataFlag = data[1];
|
||
|
||
if (dataFlag == 0x01) // 登录请求
|
||
{
|
||
sb.AppendLine("登录请求");
|
||
}
|
||
else if (dataFlag == 0x02) // 心跳
|
||
{
|
||
sb.AppendLine("心跳报文");
|
||
if (data.Length > 8)
|
||
{
|
||
byte[] statusInfo = data.Skip(7).Take(2).ToArray();
|
||
sb.AppendLine($"自检状态信息: 0x{statusInfo[0]:X2} 0x{statusInfo[1]:X2}");
|
||
}
|
||
}
|
||
else if (dataFlag == 0x03) // 阀控
|
||
{
|
||
sb.AppendLine("阀控操作报文");
|
||
if (data.Length > 5)
|
||
{
|
||
byte operation = data[5];
|
||
string operationDesc = operation switch
|
||
{
|
||
0x01 => "开阀",
|
||
0x02 => "关阀",
|
||
0x03 => "查询状态",
|
||
_ => $"未知操作(0x{operation:X2})"
|
||
};
|
||
sb.AppendLine($"阀控操作: {operationDesc}");
|
||
}
|
||
}
|
||
}
|
||
break;
|
||
|
||
case 0x08: // 主动上报
|
||
sb.AppendLine("数据上传报文");
|
||
if (data.Length > 5)
|
||
{
|
||
byte dataType = data[5];
|
||
byte dataLen = data.Length > 6 ? data[6] : (byte)0;
|
||
|
||
string dataTypeDesc = dataType switch
|
||
{
|
||
0x01 => "水表数据",
|
||
0x02 => "电表数据",
|
||
0x03 => "气表数据",
|
||
0x04 => "状态数据",
|
||
_ => $"未知类型(0x{dataType:X2})"
|
||
};
|
||
|
||
sb.AppendLine($"数据类型: {dataTypeDesc}");
|
||
sb.AppendLine($"数据长度: {dataLen} 字节");
|
||
|
||
if (data.Length > 7 && dataLen > 0)
|
||
{
|
||
byte[] meterData = data.Skip(7).Take(dataLen).ToArray();
|
||
sb.AppendLine($"表计数据: {BitConverter.ToString(meterData).Replace("-", " ")}");
|
||
|
||
// 转换为数值显示(大端序)
|
||
if (meterData.Length == 4)
|
||
{
|
||
uint value = ((uint)meterData[0] << 24) | ((uint)meterData[1] << 16) |
|
||
((uint)meterData[2] << 8) | meterData[3];
|
||
sb.AppendLine($"数值: {value}");
|
||
}
|
||
}
|
||
}
|
||
break;
|
||
|
||
case 0x09: // 读取数据
|
||
sb.AppendLine("数据读取报文");
|
||
if (data.Length > 5)
|
||
{
|
||
byte dataType = data[5];
|
||
string dataTypeDesc = dataType switch
|
||
{
|
||
0x01 => "水表数据",
|
||
0x02 => "电表数据",
|
||
0x03 => "气表数据",
|
||
0x04 => "状态数据",
|
||
_ => $"未知类型(0x{dataType:X2})"
|
||
};
|
||
sb.AppendLine($"请求的数据类型: {dataTypeDesc}");
|
||
}
|
||
break;
|
||
|
||
default:
|
||
sb.AppendLine($"未知AFN(0x{afn:X2})报文,无法解析具体内容");
|
||
break;
|
||
}
|
||
|
||
return sb.ToString();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 计算校验和
|
||
/// </summary>
|
||
private static byte CalculateChecksum(byte[] message)
|
||
{
|
||
byte sum = 0;
|
||
// 从第二个68H开始计算校验和
|
||
for (int i = 5; i < message.Length - 2; i++)
|
||
{
|
||
sum += message[i];
|
||
}
|
||
return sum;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 将BCD码转换为字符串
|
||
/// </summary>
|
||
private static string BcdToString(byte[] bcd)
|
||
{
|
||
var sb = new StringBuilder();
|
||
foreach (byte b in bcd)
|
||
{
|
||
sb.Append((b >> 4) & 0x0F);
|
||
sb.Append(b & 0x0F);
|
||
}
|
||
return sb.ToString().TrimStart('0');
|
||
}
|
||
}
|
||
} |