123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300 |
- 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<string> _progress;
- //private CancellationToken _cancellationToken;
- private CancellationTokenSource _cts;
- public bool IsOpen => _serialPort?.IsOpen ?? false;
- public event Action<int, int> 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<bool> StartSendFileAsync(IProgress<string> 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<bool> SendFileAsync(byte[] fileData, IProgress<string> 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<bool> 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<byte> 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;
- }
- //-----------------------------------------------------
- }
- }
|