DeviceUpgrade.cs 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300
  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using System.IO.Ports;
  5. using System.Linq;
  6. using System.Text;
  7. using System.Threading;
  8. using System.Threading.Tasks;
  9. namespace MV485.helper
  10. {
  11. //设备升级(xModemSender升级包的部分)
  12. public class DeviceUpgrade
  13. {
  14. private const byte SOH = 0x01;
  15. private const byte STX = 0x02;
  16. private const byte EOT = 0x04;
  17. private const byte ACK = 0x06;
  18. private const byte NAK = 0x15;
  19. private const byte CAN = 0x18;
  20. private const byte C = 0x43;
  21. private SerialPort _serialPort;
  22. private readonly int _packetSize = 128; //1024;
  23. private readonly int _maxRetry = 10;
  24. private readonly int _ackTimeout = 500; //2000;
  25. private IProgress<string> _progress;
  26. //private CancellationToken _cancellationToken;
  27. private CancellationTokenSource _cts;
  28. public bool IsOpen => _serialPort?.IsOpen ?? false;
  29. public event Action<int, int> OnProgress;
  30. private string _message;
  31. private string _portName;
  32. private int _baudrate;
  33. public DeviceUpgrade()
  34. {
  35. }
  36. private bool OpenPort(string portName, int baudRate = 115200, int dataBits = 8, Parity parity = Parity.None, StopBits stopBits = StopBits.One)
  37. {
  38. ClosePort();
  39. try
  40. {
  41. _serialPort = new SerialPort(portName, baudRate, parity, dataBits, stopBits)
  42. {
  43. ReadTimeout = 1000,
  44. WriteTimeout = 1000
  45. };
  46. _serialPort.Open();
  47. _progress?.Report($"串口{portName}连接成功");
  48. Logger.Info($"串口{portName}连接成功");
  49. return true;
  50. }
  51. catch (Exception)
  52. {
  53. _progress?.Report($"串口{portName}连接失败");
  54. Logger.Info($"串口{portName}连接失败");
  55. return false;
  56. }
  57. }
  58. private void ClosePort()
  59. {
  60. try
  61. {
  62. if (_serialPort != null && _serialPort.IsOpen)
  63. {
  64. _serialPort.Close();
  65. _serialPort.Dispose();
  66. _serialPort = null;
  67. _message = $"串口{_portName}已关闭";
  68. _progress.Report(_message);
  69. Logger.Info(_message);
  70. }
  71. }
  72. catch(Exception ex)
  73. {
  74. _message = $"关闭串口失败: {ex.Message}";
  75. _progress?.Report(_message);
  76. Logger.Info(_message);
  77. }
  78. }
  79. public void StopSendFile()
  80. {
  81. _cts?.Cancel();
  82. _progress?.Report("用户取消了升级任务");
  83. }
  84. //开始异步发送升级包文件
  85. public async Task<bool> StartSendFileAsync(IProgress<string> progress, string portName,int baudRate,byte[] fileData)
  86. {
  87. _portName = portName;
  88. _baudrate = baudRate;
  89. try
  90. {
  91. _progress = progress;
  92. //_cancellationToken = new CancellationToken();
  93. _cts = new CancellationTokenSource();
  94. bool blSend = false;
  95. if (OpenPort(portName, baudRate))
  96. {
  97. blSend = await SendFileAsync(fileData, progress, _cts.Token);
  98. }
  99. return blSend;
  100. }
  101. catch (Exception ex)
  102. {
  103. progress.Report(ex.Message);
  104. }
  105. finally
  106. {
  107. ClosePort();
  108. //_cancellationToken = null;
  109. }
  110. return false;
  111. }
  112. private async Task<bool> SendFileAsync(byte[] fileData, IProgress<string> progress, CancellationToken cancellationToken)
  113. {
  114. cancellationToken.ThrowIfCancellationRequested();
  115. //byte[] fileData = File.ReadAllBytes(filePath);
  116. //常用的 trick,用于模拟 向上取整除法
  117. int totalPackets = (fileData.Length + _packetSize - 1) / _packetSize;
  118. //这里一定是C,嵌入式设备已经定义好了,不需要处理其它情况
  119. progress.Report("等待设备发出 'NAK' 信号...");
  120. await Task.Delay(2000);
  121. byte handshakeChar = await WaitForByteAsync(cancellationToken, NAK);
  122. //progress.Report($"{handshakeChar:02x}");
  123. if(handshakeChar != NAK)
  124. {
  125. progress.Report("未收到 'NAK',设备未准备好。");
  126. return false;
  127. }
  128. progress.Report($"{(handshakeChar == NAK ?"已开始传输":"未收到NAK")}");
  129. for (int packetNum = 1; packetNum <= totalPackets; packetNum++)
  130. {
  131. cancellationToken.ThrowIfCancellationRequested();
  132. int offset = (packetNum - 1) * _packetSize;
  133. int length = Math.Min(_packetSize, fileData.Length - offset);
  134. byte[] dataBlock = new byte[_packetSize];
  135. Array.Copy(fileData, offset, dataBlock, 0, length);
  136. if(length < _packetSize)
  137. {
  138. for(int i = length; i < _packetSize; i++)
  139. {
  140. dataBlock[i] = 0x1A;
  141. }
  142. }
  143. for (int attempt = 0; attempt < _maxRetry; attempt++)
  144. {
  145. //progress.Report($"发送第 {packetNum}/{totalPackets} 包,尝试 {attempt + 1}");
  146. byte[] packet = BuildPacket(packetNum, dataBlock);
  147. try
  148. {
  149. //progress?.Report($"发送{packet.Length}字节");
  150. //progress?.Report(BitConverter.ToString(packet).Replace("-", ""));
  151. _serialPort.Write(packet, 0, packet.Length);
  152. }
  153. catch (Exception ex)
  154. {
  155. progress.Report($"发送失败:{ex.Message}");
  156. continue;
  157. }
  158. var result = await WaitForByteAsync(cancellationToken,ACK,NAK,CAN);
  159. if (result == ACK)
  160. {
  161. //progress?.Report("接收到ACK");
  162. //发送成功
  163. OnProgress?.Invoke(packetNum, totalPackets);
  164. break;
  165. }
  166. if(result == CAN)
  167. {
  168. progress.Report("接收到CAN取消信号,终止发送。");
  169. _serialPort.Write(new[] { CAN, CAN }, 0, 2);
  170. return false;
  171. }
  172. if(result == NAK)
  173. {
  174. progress?.Report("接收到NAK");
  175. }
  176. if (attempt == _maxRetry - 1)
  177. {
  178. //progress.Report("发送失败,超出重试次数。");
  179. progress.Report("发送失败,超出重试次数。发送 CAN 通知设备...");
  180. _serialPort.Write(new[] { CAN, CAN }, 0, 2);
  181. return false;
  182. }
  183. }
  184. }
  185. //_serialPort.Write(new[] { EOT }, 0, 1);
  186. //progress.Report("发送 EOT,等待 ACK...");
  187. if (!await SendEotWithRetryAsync(cancellationToken))
  188. {
  189. progress.Report("未收到 EOT 的 ACK,升级失败。发送 CAN 通知设备...");
  190. _serialPort.Write(new[] { CAN, CAN }, 0, 2);
  191. return false;
  192. }
  193. progress.Report("升级完成。");
  194. return true;
  195. }
  196. private async Task<bool> SendEotWithRetryAsync(CancellationToken token)
  197. {
  198. //等待10秒
  199. for (int attempt = 0; attempt < _maxRetry; attempt++)
  200. {
  201. _serialPort.Write(new[] { EOT }, 0, 1);
  202. //await Task.Delay(100, token);
  203. _progress.Report("发送 EOT,等待 ACK...");
  204. var result = await WaitForByteAsync(token, ACK);
  205. if (result == ACK)
  206. {
  207. _progress.Report("收到EOT的ACK");
  208. return true;
  209. }
  210. }
  211. return false;
  212. }
  213. private byte[] BuildPacket(int packetNum, byte[] data)
  214. {
  215. byte[] packet = new byte[3 + _packetSize + 1];
  216. packet[0] = SOH; //STX; //帧头 //改为128,1024的包有问题
  217. packet[1] = (byte)(packetNum % 256); //包序号
  218. packet[2] = (byte)(255 - packet[1]); //包序号反码
  219. Array.Copy(data, 0, packet, 3, _packetSize);
  220. //计算CRC
  221. //ushort crc = CRC16_XModem(data, 0, _packetSize);
  222. //packet[3 + _packetSize] = (byte)(crc >> 8);
  223. //packet[4 + _packetSize] = (byte)(crc & 0xFF);
  224. int sum = 0;
  225. foreach(byte bt in data)
  226. {
  227. sum = (sum + bt) % 256;
  228. }
  229. packet[3 + _packetSize] = (byte)sum;
  230. return packet;
  231. }
  232. private async Task<byte> WaitForByteAsync(CancellationToken token, params byte[] expected)
  233. {
  234. int timeout = _ackTimeout;
  235. while (timeout > 0 && !token.IsCancellationRequested)
  236. {
  237. if (_serialPort.BytesToRead > 0)
  238. {
  239. int value = _serialPort.ReadByte();
  240. if (Array.Exists(expected, b => b == (byte)value))
  241. return (byte)value;
  242. }
  243. await Task.Delay(10, token);
  244. timeout -= 10;
  245. }
  246. return 0;
  247. }
  248. public static ushort CRC16_XModem(byte[] data, int offset, int count)
  249. {
  250. ushort crc = 0;
  251. for (int i = 0; i < count; i++)
  252. {
  253. crc ^= (ushort)(data[offset + i] << 8);
  254. for (int j = 0; j < 8; j++)
  255. {
  256. if ((crc & 0x8000) != 0)
  257. crc = (ushort)((crc << 1) ^ 0x1021);
  258. else
  259. crc <<= 1;
  260. }
  261. }
  262. return crc;
  263. }
  264. //-----------------------------------------------------
  265. }
  266. }