using System; using System.Collections.Generic; using System.IO; using System.IO.Ports; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; namespace MV485.helper { //设备升级(xModemSender升级包的部分) public class DeviceUpgrade { private const byte SOH = 0x01; private const byte STX = 0x02; private const byte EOT = 0x04; private const byte ACK = 0x06; private const byte NAK = 0x15; private const byte CAN = 0x18; private const byte C = 0x43; private SerialPort _serialPort; private readonly int _packetSize = 128; //1024; private readonly int _maxRetry = 10; private readonly int _ackTimeout = 500; //2000; private IProgress _progress; //private CancellationToken _cancellationToken; private CancellationTokenSource _cts; public bool IsOpen => _serialPort?.IsOpen ?? false; public event Action OnProgress; private string _message; private string _portName; private int _baudrate; public DeviceUpgrade() { } private bool OpenPort(string portName, int baudRate = 115200, int dataBits = 8, Parity parity = Parity.None, StopBits stopBits = StopBits.One) { ClosePort(); try { _serialPort = new SerialPort(portName, baudRate, parity, dataBits, stopBits) { ReadTimeout = 1000, WriteTimeout = 1000 }; _serialPort.Open(); _progress?.Report($"串口{portName}连接成功"); Logger.Info($"串口{portName}连接成功"); return true; } catch (Exception) { _progress?.Report($"串口{portName}连接失败"); Logger.Info($"串口{portName}连接失败"); return false; } } private void ClosePort() { try { if (_serialPort != null && _serialPort.IsOpen) { _serialPort.Close(); _serialPort.Dispose(); _serialPort = null; _message = $"串口{_portName}已关闭"; _progress.Report(_message); Logger.Info(_message); } } catch(Exception ex) { _message = $"关闭串口失败: {ex.Message}"; _progress?.Report(_message); Logger.Info(_message); } } public void StopSendFile() { _cts?.Cancel(); _progress?.Report("用户取消了升级任务"); } //开始异步发送升级包文件 public async Task StartSendFileAsync(IProgress progress, string portName,int baudRate,byte[] fileData) { _portName = portName; _baudrate = baudRate; try { _progress = progress; //_cancellationToken = new CancellationToken(); _cts = new CancellationTokenSource(); bool blSend = false; if (OpenPort(portName, baudRate)) { blSend = await SendFileAsync(fileData, progress, _cts.Token); } return blSend; } catch (Exception ex) { progress.Report(ex.Message); } finally { ClosePort(); //_cancellationToken = null; } return false; } private async Task SendFileAsync(byte[] fileData, IProgress progress, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); //byte[] fileData = File.ReadAllBytes(filePath); //常用的 trick,用于模拟 向上取整除法 int totalPackets = (fileData.Length + _packetSize - 1) / _packetSize; //这里一定是C,嵌入式设备已经定义好了,不需要处理其它情况 progress.Report("等待设备发出 'NAK' 信号..."); await Task.Delay(2000); byte handshakeChar = await WaitForByteAsync(cancellationToken, NAK); //progress.Report($"{handshakeChar:02x}"); if(handshakeChar != NAK) { progress.Report("未收到 'NAK',设备未准备好。"); return false; } progress.Report($"{(handshakeChar == NAK ?"已开始传输":"未收到NAK")}"); for (int packetNum = 1; packetNum <= totalPackets; packetNum++) { cancellationToken.ThrowIfCancellationRequested(); int offset = (packetNum - 1) * _packetSize; int length = Math.Min(_packetSize, fileData.Length - offset); byte[] dataBlock = new byte[_packetSize]; Array.Copy(fileData, offset, dataBlock, 0, length); if(length < _packetSize) { for(int i = length; i < _packetSize; i++) { dataBlock[i] = 0x1A; } } for (int attempt = 0; attempt < _maxRetry; attempt++) { //progress.Report($"发送第 {packetNum}/{totalPackets} 包,尝试 {attempt + 1}"); byte[] packet = BuildPacket(packetNum, dataBlock); try { //progress?.Report($"发送{packet.Length}字节"); //progress?.Report(BitConverter.ToString(packet).Replace("-", "")); _serialPort.Write(packet, 0, packet.Length); } catch (Exception ex) { progress.Report($"发送失败:{ex.Message}"); continue; } var result = await WaitForByteAsync(cancellationToken,ACK,NAK,CAN); if (result == ACK) { //progress?.Report("接收到ACK"); //发送成功 OnProgress?.Invoke(packetNum, totalPackets); break; } if(result == CAN) { progress.Report("接收到CAN取消信号,终止发送。"); _serialPort.Write(new[] { CAN, CAN }, 0, 2); return false; } if(result == NAK) { progress?.Report("接收到NAK"); } if (attempt == _maxRetry - 1) { //progress.Report("发送失败,超出重试次数。"); progress.Report("发送失败,超出重试次数。发送 CAN 通知设备..."); _serialPort.Write(new[] { CAN, CAN }, 0, 2); return false; } } } //_serialPort.Write(new[] { EOT }, 0, 1); //progress.Report("发送 EOT,等待 ACK..."); if (!await SendEotWithRetryAsync(cancellationToken)) { progress.Report("未收到 EOT 的 ACK,升级失败。发送 CAN 通知设备..."); _serialPort.Write(new[] { CAN, CAN }, 0, 2); return false; } progress.Report("升级完成。"); return true; } private async Task SendEotWithRetryAsync(CancellationToken token) { //等待10秒 for (int attempt = 0; attempt < _maxRetry; attempt++) { _serialPort.Write(new[] { EOT }, 0, 1); //await Task.Delay(100, token); _progress.Report("发送 EOT,等待 ACK..."); var result = await WaitForByteAsync(token, ACK); if (result == ACK) { _progress.Report("收到EOT的ACK"); return true; } } return false; } private byte[] BuildPacket(int packetNum, byte[] data) { byte[] packet = new byte[3 + _packetSize + 1]; packet[0] = SOH; //STX; //帧头 //改为128,1024的包有问题 packet[1] = (byte)(packetNum % 256); //包序号 packet[2] = (byte)(255 - packet[1]); //包序号反码 Array.Copy(data, 0, packet, 3, _packetSize); //计算CRC //ushort crc = CRC16_XModem(data, 0, _packetSize); //packet[3 + _packetSize] = (byte)(crc >> 8); //packet[4 + _packetSize] = (byte)(crc & 0xFF); int sum = 0; foreach(byte bt in data) { sum = (sum + bt) % 256; } packet[3 + _packetSize] = (byte)sum; return packet; } private async Task WaitForByteAsync(CancellationToken token, params byte[] expected) { int timeout = _ackTimeout; while (timeout > 0 && !token.IsCancellationRequested) { if (_serialPort.BytesToRead > 0) { int value = _serialPort.ReadByte(); if (Array.Exists(expected, b => b == (byte)value)) return (byte)value; } await Task.Delay(10, token); timeout -= 10; } return 0; } public static ushort CRC16_XModem(byte[] data, int offset, int count) { ushort crc = 0; for (int i = 0; i < count; i++) { crc ^= (ushort)(data[offset + i] << 8); for (int j = 0; j < 8; j++) { if ((crc & 0x8000) != 0) crc = (ushort)((crc << 1) ^ 0x1021); else crc <<= 1; } } return crc; } //----------------------------------------------------- } }