256 lines
11 KiB
C#
256 lines
11 KiB
C#
|
||
using Infrastructure;
|
||
using System;
|
||
using System.Collections.Generic;
|
||
using System.Linq;
|
||
using System.Text;
|
||
using System.Threading;
|
||
using System.Threading.Tasks;
|
||
|
||
namespace DOAN.ServiceCore.MyMatchPush
|
||
{
|
||
|
||
public class Watchup
|
||
{
|
||
// 修复:线程安全的包序号(Interlocked确保多线程不重复,比自增更可靠)
|
||
private static int _packageSort = 1;
|
||
|
||
public static bool StartPush(string message, SocketGatewayServer _socketGateway, string watchLocalAddress)
|
||
{
|
||
// 仅注册一次GBK编码(避免重复注册报错)
|
||
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
|
||
Encoding GBKEncoding = Encoding.GetEncoding(936);
|
||
|
||
// 前置校验:网关实例/消息不能为空
|
||
if (_socketGateway == null)
|
||
{
|
||
Console.WriteLine("错误:网关实例不能为空");
|
||
return false;
|
||
}
|
||
if (string.IsNullOrEmpty(message))
|
||
{
|
||
Console.WriteLine("错误:发送消息不能为空");
|
||
return false;
|
||
}
|
||
|
||
try
|
||
{
|
||
// 1. 包头(固定0x5A)
|
||
byte[] packageHeader = { 0x5A };
|
||
|
||
// 2. 包序号:线程安全自增+1-255循环(修复核心问题)
|
||
byte packSort = (byte)Interlocked.Increment(ref _packageSort);
|
||
if (_packageSort > 255)
|
||
{
|
||
Interlocked.Exchange(ref _packageSort, 1);
|
||
packSort = 1;
|
||
}
|
||
byte[] packsort = { packSort };
|
||
|
||
// 3. 地址:广播地址(可替换为手表主机地址/本机地址)
|
||
//byte[] IPaddress = { 0xff, 0xff, 0xff, 0xff };
|
||
|
||
// 3. 核心修改:根据传入的本机地址生成目标地址字节数组(替换原广播地址)
|
||
byte[] IPaddress;
|
||
if (string.IsNullOrEmpty(watchLocalAddress))
|
||
{
|
||
// 空地址则用广播地址(兼容原有逻辑)
|
||
IPaddress = new byte[] { 0xff, 0xff, 0xff, 0xff };
|
||
Console.WriteLine("未指定手表地址,使用广播模式");
|
||
}
|
||
else
|
||
{
|
||
// 将手表本机地址(如012-034-056-078)转换为4字节数组
|
||
try
|
||
{
|
||
IPaddress = ConvertWatchAddressToBytes(watchLocalAddress);
|
||
Console.WriteLine($"目标手表地址转换完成:{watchLocalAddress} → {BitConverter.ToString(IPaddress).Replace("-", " ")}");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine($"地址转换失败:{ex.Message}");
|
||
return false;
|
||
}
|
||
}
|
||
|
||
|
||
// 4. 命令码:0xE0(显示文字)
|
||
byte[] CommandCode = { 0xE0 };
|
||
|
||
//5. 数据段:GBK编码+限制84字节(修复超长问题)
|
||
byte[] MessageData = GBKEncoding.GetBytes(message);
|
||
const int MaxDataLength = 84;
|
||
if (MessageData.Length > MaxDataLength)
|
||
{
|
||
// 反向查找:确保截断位置不是中文的第二个字节(GBK中文占2字节)
|
||
MessageData = SafeTruncateGBKBytes(MessageData, MaxDataLength);
|
||
Console.WriteLine($"[警告] 消息超长,已安全截断为{MessageData.Length}字节");
|
||
}
|
||
|
||
byte messageLength = (byte)MessageData.Length;
|
||
if (messageLength != MessageData.Length)
|
||
{
|
||
Console.WriteLine("[错误] 消息长度超出byte范围");
|
||
return false;
|
||
}
|
||
byte[] MessageLength = { messageLength };
|
||
// 6. 拼接除CRC外的所有数据
|
||
byte[] dataWithoutCRC = packageHeader
|
||
.Concat(packsort)
|
||
.Concat(IPaddress)
|
||
.Concat(CommandCode)
|
||
.Concat(MessageLength)
|
||
.Concat(MessageData).ToArray();
|
||
|
||
// 7. 校验和:内置正确实现(符合手表协议:所有字节之和%256)
|
||
byte crc = CalculateChecksum(dataWithoutCRC);
|
||
byte[] CRC = { crc };
|
||
|
||
// 8. 最终手表指令包
|
||
byte[] body = dataWithoutCRC.Concat(CRC).ToArray();
|
||
|
||
// 调试输出:确认手表指令包格式
|
||
string hexString = BitConverter.ToString(body).Replace("-", " ");
|
||
Console.WriteLine($"生成手表指令包:{hexString}");
|
||
|
||
// 9. 关键修复:按网关协议封装后再发送(核心改进处)
|
||
byte[] gatewayPackage = PackToGatewayProtocol(body);
|
||
|
||
// 调试输出:封装后的网关包(便于核对协议格式)
|
||
string gatewayHex = BitConverter.ToString(gatewayPackage).Replace("-", " ");
|
||
Console.WriteLine($"封装后的网关协议包:{gatewayHex}");
|
||
|
||
return _socketGateway.SendToGateway(gatewayPackage);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine($"指令包生成/发送失败:{ex.Message}");
|
||
return false;
|
||
}
|
||
}
|
||
|
||
|
||
/// <summary>
|
||
/// 将手表本机地址(格式:XXX-XXX-XXX-XXX)转换为4字节数组
|
||
/// 示例:012-034-056-078 → [0x0C, 0x22, 0x38, 0x4E](12→0C,34→22,56→38,78→4E)
|
||
/// </summary>
|
||
/// <param name="addressStr">手表本机地址(如012-034-056-078)</param>
|
||
/// <returns>4字节地址数组</returns>
|
||
private static byte[] ConvertWatchAddressToBytes(string addressStr)
|
||
{
|
||
// 校验地址格式
|
||
string[] segments = addressStr.Split('-');
|
||
if (segments.Length != 4)
|
||
{
|
||
throw new ArgumentException($"地址格式错误,需为XXX-XXX-XXX-XXX,当前:{addressStr}");
|
||
}
|
||
|
||
byte[] addressBytes = new byte[4];
|
||
for (int i = 0; i < 4; i++)
|
||
{
|
||
// 转换每段为数字,并校验范围(0-255)
|
||
if (!int.TryParse(segments[i], out int segmentValue) || segmentValue < 0 || segmentValue > 255)
|
||
{
|
||
throw new ArgumentException($"地址段{segments[i]}无效,需为0-255之间的整数");
|
||
}
|
||
addressBytes[i] = (byte)segmentValue;
|
||
}
|
||
|
||
return addressBytes;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 内置校验和计算(符合手表协议:所有字节之和%256)
|
||
/// </summary>
|
||
private static byte CalculateChecksum(byte[] data)
|
||
{
|
||
if (data == null || data.Length == 0) return 0;
|
||
long sum = 0;
|
||
foreach (byte b in data) sum += b;
|
||
return (byte)(sum % 256);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 关键改进:严格按网关协议封装(适配LoRa转发指令)
|
||
/// 修复点:网关MAC替换为实际值、校验和计算规则、字段顺序验证
|
||
/// </summary>
|
||
private static byte[] PackToGatewayProtocol(byte[] watchPackage)
|
||
{
|
||
// 网关协议格式(严格对照产品手册):
|
||
// 68(帧头) + 网关MAC(6字节) + 命令类型(1字节) + 命令码(1字节) + 数据长度(2字节) + 手表指令包 + 校验和(2字节) + 16(帧尾)
|
||
|
||
// 改进1:替换为网关实际MAC(从网关返回包中提取:30 9C 23 DA 8B 00)
|
||
byte[] frameHeader = { 0x68 }; // 网关帧头(固定)
|
||
byte[] gatewayMac = { 0x30, 0x9C, 0x23, 0xDA, 0x8B, 0x00 }; // 核心修改:改为网关实际MAC(之前是示例值)
|
||
byte[] cmdType = { 0xE4 }; // 网关命令类型(固定:LoRa数据转发)
|
||
byte[] cmdCode = { 0xA1 }; // 网关命令码(固定:转发手表数据)
|
||
|
||
// 改进2:数据长度严格按协议要求(高位在前,确保网关识别)
|
||
ushort dataLengthValue = (ushort)watchPackage.Length;
|
||
byte[] dataLen = new byte[2];
|
||
dataLen[0] = (byte)(dataLengthValue >> 8); // 高位字节
|
||
dataLen[1] = (byte)(dataLengthValue & 0xFF); // 低位字节
|
||
|
||
// 拼接网关包(除校验和、帧尾)
|
||
byte[] gatewayDataWithoutCrc = frameHeader
|
||
.Concat(gatewayMac)
|
||
.Concat(cmdType)
|
||
.Concat(cmdCode)
|
||
.Concat(dataLen)
|
||
.Concat(watchPackage)
|
||
.ToArray();
|
||
|
||
// 改进3:网关校验和严格按协议(求和取低位2字节,手动计算避免大小端问题)
|
||
ushort gatewayCrc = (ushort)(gatewayDataWithoutCrc.Sum(b => b) % 65536);
|
||
byte[] gatewayCrcBytes = new byte[2];
|
||
gatewayCrcBytes[0] = (byte)(gatewayCrc >> 8); // 校验和高位
|
||
gatewayCrcBytes[1] = (byte)(gatewayCrc & 0xFF); // 校验和低位
|
||
|
||
// 拼接最终网关包
|
||
byte[] frameTail = { 0x16 }; // 网关帧尾(固定)
|
||
return gatewayDataWithoutCrc
|
||
.Concat(gatewayCrcBytes)
|
||
.Concat(frameTail)
|
||
.ToArray();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 安全截断GBK字节数组(避免截断到中文半个字符)
|
||
/// </summary>
|
||
/// <param name="bytes">原始GBK字节数组</param>
|
||
/// <param name="maxLength">最大长度</param>
|
||
/// <returns>截断后的字节数组</returns>
|
||
private static byte[] SafeTruncateGBKBytes(byte[] bytes, int maxLength)
|
||
{
|
||
if (bytes == null || maxLength <= 0) return Array.Empty<byte>();
|
||
if (bytes.Length <= maxLength) return bytes;
|
||
|
||
// GBK编码规则:
|
||
// - 单字节:0x00-0x7F(字母/数字/符号)
|
||
// - 双字节:0x81-0xFE 开头,第二个字节 0x40-0xFE
|
||
int truncateLength = maxLength;
|
||
// 检查截断位置的前一个字节是否是中文的第二个字节
|
||
while (truncateLength > 0)
|
||
{
|
||
byte b = bytes[truncateLength - 1];
|
||
// 如果是双字节的第二个字节,向前退一位
|
||
if (b >= 0x40 && b <= 0xFE && truncateLength > 1)
|
||
{
|
||
byte prevB = bytes[truncateLength - 2];
|
||
if (prevB >= 0x81 && prevB <= 0xFE)
|
||
{
|
||
truncateLength--;
|
||
continue;
|
||
}
|
||
}
|
||
break;
|
||
}
|
||
|
||
byte[] result = new byte[truncateLength];
|
||
Array.Copy(bytes, result, truncateLength);
|
||
return result;
|
||
}
|
||
}
|
||
|
||
}
|