XModemReceiver.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361
  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. //发送方:
  12. //等待接收方发送 ‘C’(表示使用CRC模式)或 NAK(使用Checksum)。
  13. //发送文件数据,每次128字节,并计算 Checksum 或 CRC。
  14. //发送 EOT(表示传输完成)。
  15. //接收方:
  16. //发送 ‘C’ 或 NAK 以请求数据。
  17. //解析数据包并校验。
  18. //发送 ACK 确认正确接收。
  19. //每一帧的结构如下,共 132 字节:
  20. //字节位置 名称 值 / 范围 描述
  21. //1 SOH 0x01 起始标志,表示是 128 字节数据包
  22. //2 Block Number 0x01 ~ 0xFF 当前包编号(从1开始)
  23. //3 Block Number ^ 255 - Block Number 包编号的反码(用于校验)
  24. //4-131 Data[128] 任意数据 实际数据,末尾不足用 0x1A 填充
  25. //132 Checksum 所有数据字节的累加和 校验和:Data[0] + ... + Data[127](8-bit)
  26. public class XModemReceiver
  27. {
  28. private SerialPort _serialPort;
  29. private string _portName;
  30. private const int FRAME_MAX_MILLISECONDS = 2000; //每一帧最大超时时间
  31. private const int PACKET_SIZE = 128;
  32. private const byte SOH = 0x01; //128字节的头标志
  33. private const byte EOT = 0x04; //发送结束的标志
  34. private const byte ACK = 0x06;
  35. private const byte NAK = 0x15; //
  36. private const byte CAN = 0x18;
  37. private const byte C = 0x43;
  38. private string _message;
  39. public event Action<string> RWLog;
  40. public event Action<bool> SerialConnected; //串口是否已连接
  41. public event Action<bool> SerialStatusChanged; //串口连接状态变化
  42. public event Action<ushort,ushort> OnProgress;
  43. private bool _isReceiving = false;
  44. public XModemReceiver()
  45. {
  46. }
  47. //开始接收文件
  48. public bool StartReceiveFile(string portName,int baudRate,string savePath,ushort fileSize)
  49. {
  50. bool blResult = false;
  51. _portName = portName;
  52. if (OpenSerial(portName, baudRate))
  53. {
  54. _isReceiving = true;
  55. blResult = ReceiveFile(savePath,fileSize);
  56. }
  57. CloseSerial();
  58. return blResult;
  59. }
  60. private bool ReceiveFile(string savePath,ushort fileSize)
  61. {
  62. bool blResult = false;
  63. List<byte> dataByteList = new List<byte>();
  64. int blockNumber = 1;
  65. try
  66. {
  67. _serialPort.DiscardInBuffer();
  68. //发送NAK请求数据(使用校验和)
  69. _serialPort.Write(new byte[] { NAK }, 0, 1);
  70. RWLog?.Invoke($"发送NAK: {NAK:X2}");
  71. Thread.Sleep(100);
  72. while (_isReceiving)
  73. {
  74. //Thread.Sleep(10);
  75. //---开始接收一帧数据---
  76. DateTime frameStartTime = DateTime.Now;
  77. bool frameReceived = false;
  78. // 在内部循环中尝试接收完整一帧,2秒内必须完成
  79. while (!frameReceived)
  80. {
  81. //Thread.Sleep(100);
  82. if ((DateTime.Now - frameStartTime).TotalMilliseconds > FRAME_MAX_MILLISECONDS)
  83. {
  84. //int n = _serialPort.Read(data, read, PACKET_SIZE - read);
  85. byte[] tempData = new byte[PACKET_SIZE];
  86. int n = _serialPort.Read(tempData, 0, PACKET_SIZE);
  87. if (n > 0)
  88. {
  89. _message = BitConverter.ToString(tempData, 0, n).Replace("-", " ");
  90. RWLog?.Invoke($"读取到: {_message}");
  91. }
  92. RWLog?.Invoke("接收当前帧超时(超过2秒),终止文件接收。");
  93. return false;
  94. }
  95. //10ms内读取串口中的第一个字节
  96. int b = ReadByte(100);
  97. if (b == -1)
  98. {
  99. // 未读取到数据,发送 NAK 重试
  100. _serialPort.Write(new byte[] { NAK }, 0, 1);
  101. RWLog?.Invoke($"发送NAK: {NAK:X2}");
  102. continue;
  103. }
  104. //接收到EOT,表示文件传输完成
  105. if (b == EOT)
  106. {
  107. _serialPort.Write(new byte[] { ACK }, 0, 1);
  108. RWLog?.Invoke($"发送ACK: {ACK:X2}");
  109. RWLog?.Invoke("文件传输完成");
  110. blResult = true;
  111. frameReceived = true; // 退出当前帧循环
  112. _isReceiving = false;
  113. break;
  114. }
  115. //头标志不是SOH(128字节的xModem的头标志)
  116. if (b != SOH)
  117. {
  118. RWLog?.Invoke($"收到数据帧头标志错误: 0x{b:X2}");
  119. _serialPort.Write(new byte[] { NAK }, 0, 1);
  120. RWLog?.Invoke($"发送NAK: {NAK:X2}");
  121. continue;
  122. }
  123. // 读取包号与包号补码
  124. int blockNum = ReadByte(100);
  125. int blockNumComp = ReadByte(100);
  126. if ((blockNum + blockNumComp) != 0xFF)
  127. {
  128. _serialPort.Write(new byte[] { NAK }, 0, 1);
  129. RWLog?.Invoke($"发送NAK: {NAK:X2}");
  130. RWLog?.Invoke("包号补码错误");
  131. continue;
  132. }
  133. // 检查是否为重复帧(重复帧直接 ACK 后跳过)
  134. if (blockNum == ((blockNumber - 1) & 0xFF))
  135. {
  136. RWLog?.Invoke($"重复数据包 #{blockNum},重新发送 ACK");
  137. _serialPort.Write(new byte[] { ACK }, 0, 1);
  138. RWLog?.Invoke($"发送ACK: {ACK:X2}");
  139. frameReceived = true;
  140. break;
  141. }
  142. // 读取 128 字节数据,整个读取也必须在2秒内完成
  143. if (blockNum != (blockNumber & 0xFF))
  144. {
  145. RWLog?.Invoke("包号不符合预期");
  146. _serialPort.Write(new byte[] { NAK }, 0, 1);
  147. RWLog?.Invoke($"发送NAK: {NAK:X2}");
  148. continue;
  149. }
  150. //读取128字节的数据
  151. byte[] data = new byte[PACKET_SIZE];
  152. int read = 0;
  153. while (read < PACKET_SIZE && (DateTime.Now - frameStartTime).TotalMilliseconds < 2000)
  154. {
  155. if (_serialPort.BytesToRead > 0)
  156. {
  157. int n = _serialPort.Read(data, read, PACKET_SIZE - read);
  158. read += n;
  159. }
  160. else
  161. {
  162. Thread.Sleep(10);
  163. }
  164. }
  165. if (read < PACKET_SIZE)
  166. {
  167. RWLog?.Invoke("数据包接收超时或不完整");
  168. _serialPort.Write(new byte[] { NAK }, 0, 1);
  169. RWLog?.Invoke($"发送NAK: {NAK:X2}");
  170. continue;
  171. }
  172. //读取校验和
  173. int checksum = ReadByte(100);
  174. int sum = 0;
  175. foreach (byte bt in data)
  176. {
  177. sum = (sum + bt) % 256;
  178. }
  179. //校验和不通过
  180. if (checksum != sum)
  181. {
  182. RWLog?.Invoke($"校验和验证不通过");
  183. _serialPort.Write(new byte[] { NAK }, 0, 1);
  184. RWLog?.Invoke($"发送NAK: {NAK:X2}");
  185. continue;
  186. }
  187. // 当前帧数据接收成功
  188. dataByteList.AddRange(data);
  189. //RWLog?.Invoke($"第{blockNumber}包");
  190. blockNumber++;
  191. frameReceived = true;
  192. _serialPort.Write(new byte[] { ACK }, 0, 1);
  193. //RWLog?.Invoke($"发送ACK: {ACK:X2}");
  194. }// end of while(!frameReceived)
  195. //double progress = ((double)dataByteList.Count) / fileSize;
  196. //progress = progress > 1 ? 1.0 : progress;
  197. OnProgress?.Invoke((ushort)dataByteList.Count,fileSize);
  198. // 如果文件数据已经足够,则退出整体接收循环
  199. //if (dataByteList.Count >= fileSize || blResult)
  200. // break;
  201. }// end of while(_isReceiving)
  202. //保存文件
  203. //if (blResult && dataByteList.Count >= fileSize)
  204. if (blResult || dataByteList.Count >= fileSize)
  205. {
  206. //移除填充的0x1A,因为已经知道文件大小了
  207. byte[] fileBytes = new byte[fileSize];
  208. Array.Copy(dataByteList.ToArray(), fileBytes, fileSize);
  209. string directory = Path.GetDirectoryName(savePath);
  210. if (!Directory.Exists(directory))
  211. {
  212. Directory.CreateDirectory(directory);
  213. }
  214. File.WriteAllBytes(savePath, fileBytes);
  215. RWLog?.Invoke($"已保存文件{savePath}");
  216. }
  217. else
  218. {
  219. RWLog?.Invoke($"未完整接收采样照片");
  220. }
  221. }
  222. catch(IOException ex)
  223. {
  224. _message = $"接收文件IO错误:{ex.Message}";
  225. RWLog?.Invoke(_message);
  226. blResult = false;
  227. }
  228. catch(Exception ex)
  229. {
  230. _message = $"接收文件错误:{ex.Message}";
  231. RWLog?.Invoke(_message);
  232. blResult = false;
  233. }
  234. return blResult;
  235. }
  236. public void StopReceiveFile()
  237. {
  238. _isReceiving = false;
  239. if (_serialPort?.IsOpen == true)
  240. {
  241. _serialPort.Write(new byte[] { CAN, CAN }, 0, 2);
  242. Thread.Sleep(100);
  243. }
  244. }
  245. private int ReadByte(int timeoutMilliseconds)
  246. {
  247. DateTime deadline = DateTime.Now.AddMilliseconds(timeoutMilliseconds);
  248. while (DateTime.Now < deadline)
  249. {
  250. if (_serialPort.BytesToRead > 0)
  251. return _serialPort.ReadByte();
  252. Thread.Sleep(10); // 睡眠 10 毫秒,避免过于频繁地轮询
  253. }
  254. return -1; // 超时未读取到数据
  255. }
  256. private byte[] TrimTrailingPadding(byte[] data, int maxLength)
  257. {
  258. if (data.Length > maxLength)
  259. Array.Resize(ref data, maxLength);
  260. return data;
  261. }
  262. private bool OpenSerial(string portName, int baudRate)
  263. {
  264. CloseSerial();
  265. try
  266. {
  267. //除去波特率外,其它参数默认为不会被修改
  268. SerialPort serialPort = new SerialPort(portName, baudRate, Parity.None, 8, StopBits.One);
  269. serialPort.ReadTimeout = 1000; //读取的超时时间应该短一些
  270. serialPort.WriteTimeout = 1000;
  271. serialPort.Open();
  272. _serialPort = serialPort;
  273. _message = $"串口{portName}连接成功";
  274. RWLog?.Invoke(_message);
  275. Logger.Info(_message);
  276. SerialConnected?.Invoke(true);
  277. return true;
  278. }
  279. catch (Exception ex)
  280. {
  281. _message = $"串口{portName}连接失败";
  282. RWLog?.Invoke(_message);
  283. SerialConnected?.Invoke(false);
  284. Logger.Info(_message);
  285. Logger.Error(ex.Message);
  286. return false;
  287. }
  288. }
  289. private void CloseSerial()
  290. {
  291. try
  292. {
  293. if (_serialPort != null)
  294. {
  295. if (_serialPort.IsOpen)
  296. {
  297. _serialPort.Close();
  298. _message = $"串口{_portName}已关闭";
  299. SerialStatusChanged?.Invoke(false); //串口关闭
  300. RWLog?.Invoke(_message);
  301. Logger.Info(_message);
  302. }
  303. _serialPort.Dispose();
  304. _serialPort = null;
  305. }
  306. }
  307. catch (Exception ex)
  308. {
  309. _message = $"关闭串口{_portName}失败: {ex.Message}";
  310. RWLog?.Invoke(_message);
  311. Logger.Error(_message);
  312. }
  313. }
  314. //----------------------------------------------------------
  315. }
  316. }