ArrowSerialPort.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472
  1. using System;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. using MySerialPortInterface;
  5. public class ArrowSerialPort : MonoBehaviour
  6. {
  7. //使用自定义串口
  8. private SerialPortInterface serialPortInterface;
  9. private int baudrate = 115200;
  10. //private int baudrate = 921600;
  11. //private int baudrate = 460800;
  12. private string PortName = "/dev/ttyS0";
  13. //private static Dictionary<string, int> _isInit = new();
  14. public bool testMode = true;
  15. private void Start()
  16. {
  17. //if (!_isInit.TryGetValue(gameObject.name, out int instanceID) && instanceID == 0)
  18. //{
  19. // DontDestroyOnLoad(gameObject);
  20. // Init();
  21. //}
  22. DontDestroyOnLoad(gameObject);
  23. Init();
  24. }
  25. private void LOG(string msg, bool warning = false)
  26. {
  27. if (!warning)
  28. Debug.Log($"<color=#00FF00>{msg}</color>");
  29. else
  30. Debug.LogWarning($"<color=#00FF00>{msg}</color>");
  31. }
  32. private void Init()
  33. {
  34. //_isInit[gameObject.name] = GetInstanceID();
  35. #if UNITY_ANDROID && !UNITY_EDITOR
  36. serialPortInterface = gameObject.GetComponent<SerialPortInterface>();
  37. serialPortInterface.SystemEventObject.AddListener(SystemEventObject);
  38. TrySerialOpenPort();
  39. #endif
  40. #if UNITY_EDITOR
  41. if (testMode)
  42. {
  43. var testBoard = Resources.Load<GameObject>("SerialPortTest");
  44. GameObject.Instantiate(testBoard);
  45. }
  46. //模拟连接设备
  47. //List<byte> data = new List<byte>();
  48. //data.Add(0xaa);
  49. //data.Add(0x80);
  50. //data.Add(0x07);
  51. //data.Add(0x02);
  52. //data.Add(0x01);
  53. //data.Add(0x84);
  54. //data.Add(0x55);
  55. //HandleSerialPortUpdate(data.ToArray(), 7);
  56. #endif
  57. }
  58. private void TrySerialOpenPort()
  59. {
  60. serialPortInterface?.Open();
  61. }
  62. private void SystemEventObject(SerialPortInterface arg0, string msg)
  63. {
  64. // || msg.Equals("PERMISSION_ERROR")
  65. if (msg.Equals("OPEN_ERROR"))//串口开启失败 定时重试
  66. {
  67. #if UNITY_ANDROID && !UNITY_EDITOR
  68. Invoke("TrySerialOpenPort", 1f);
  69. #endif
  70. // LOG($"{PortName} 串口打开失败 重试中", true);
  71. }
  72. else if (msg.Equals("OPENED"))
  73. {
  74. SerialPortHelper.ins.OnConnect(arg0.DeviceName);
  75. //应用启动 间隔十秒请求一次设备信息
  76. CancelInvoke("RequestDeviceIno");
  77. InvokeRepeating("RequestDeviceIno", 0f, 10f);
  78. //LOG($"{PortName} 串口打开成功!");
  79. if (CommonConfig.bOpenTheLaserWithoutQRCode)
  80. {
  81. //没有二维码直接开启激光
  82. //激光默认打开
  83. RequestLightState(true);
  84. }
  85. }
  86. else if (msg.Equals("CLOSED"))//串口断开
  87. {
  88. SerialPortHelper.ins.OnDisConnect(arg0.DeviceName);
  89. LOG($"{PortName} 串口关闭!");
  90. }
  91. }
  92. private void OnDestroy()
  93. {
  94. //if (_isInit.TryGetValue(gameObject.name, out var instanceID) && instanceID == GetInstanceID())
  95. //{
  96. // LOG($"{PortName} 串口关闭");
  97. // SerialPortHelper.ins.OnDisConnect(serialPortInterface?.DeviceName);
  98. // serialPortInterface?.Close();
  99. // _isInit.Remove(gameObject.name);
  100. //}
  101. }
  102. /// <summary>
  103. /// 串口读取二进制流数据(界面挂载调用)
  104. /// </summary>
  105. /// <param name="data"></param>
  106. public void ReadStreamingBinary(object data)
  107. {
  108. var realData = new List<byte>();
  109. var tempData = data as byte[];
  110. for (int i = 0; i < tempData.Length; i++)
  111. {
  112. realData.Add(tempData[i]);
  113. if (tempData.Length > i + 1)
  114. {
  115. if (tempData[i + 1] == 0XAA) //多条消息 粘包位置
  116. {
  117. //把上一条发送
  118. PhraseData(realData.ToArray());
  119. realData.Clear();
  120. }
  121. }
  122. else
  123. PhraseData(realData.ToArray());
  124. }
  125. }
  126. //自定义端口使用
  127. public void HandleSerialPortUpdate(byte[] buffer, int size)
  128. {
  129. //Debug.Log($"Received Data: {BitConverter.ToString(buffer)} of size {size}");
  130. //PhraseData(buffer);
  131. // 将新的数据加入缓存
  132. dataCache.AddRange(buffer);
  133. // 尝试解析缓存中的数据
  134. while (TryParseNextFrame(ref dataCache, out byte[] frame))
  135. {
  136. // 如果成功解析一帧数据,处理该帧
  137. // Debug.Log($"[ArrowSerialPort] frame Data: {BitConverter.ToString(frame)}");
  138. PhraseData(frame);
  139. }
  140. }
  141. public void TestRead(byte[] bytes)
  142. {
  143. ReadStreamingBinary(bytes);
  144. }
  145. /// <summary>
  146. /// 解析串口数据
  147. /// </summary>
  148. /// <param name="bytes"></param>
  149. private void PhraseData(byte[] bytes)
  150. {
  151. if (bytes[0] == 0xAA)
  152. {
  153. string msg = string.Empty;
  154. for (int i = 0; i < bytes.Length; i++)
  155. {
  156. msg += bytes[i].ToString("x2") + " ";
  157. }
  158. // 获取当前时间
  159. DateTime currentTime = DateTime.Now;
  160. LOG($"[ArrowSerialPort] {PortName} 收到串口信息 msg={msg}!,接收时间:" + currentTime.ToString("yyyy-MM-dd HH:mm:ss.fff"));
  161. var cmdID = bytes[1];
  162. switch (cmdID)
  163. {
  164. case 0x80://设备信息响应
  165. OnDeviceInfoBack(bytes);
  166. break;
  167. case 0x81://射击消息
  168. if (CommonConfig.bDisableBluetooth) {
  169. // 获取当前时间
  170. DateTime currentTime1 = DateTime.Now;
  171. // 打印带毫秒的时间戳
  172. Debug.Log("射箭前:CurrentTime1: " + currentTime1.ToString("yyyy-MM-dd HH:mm:ss.fff"));
  173. //3 - APP在收到射箭消息后,立即回复相同的内容给设备端;
  174. serialPortInterface?.Write(bytes);
  175. DateTime currentTime3 = DateTime.Now;
  176. // 打印带毫秒的时间戳
  177. Debug.Log("*** 回复相同的内容给设备端 CurrentTime3: " + currentTime3.ToString("yyyy-MM-dd HH:mm:ss.fff"));
  178. }
  179. OnDeviceShoot(bytes);
  180. if (CommonConfig.bDisableBluetooth)
  181. {
  182. // 获取当前时间
  183. DateTime currentTime2 = DateTime.Now;
  184. // 打印带毫秒的时间戳
  185. Debug.Log("射箭后:CurrentTime2: " + currentTime2.ToString("yyyy-MM-dd HH:mm:ss.fff"));
  186. }
  187. break;
  188. case 0x82://按键消息
  189. OnDeviceButton(bytes);
  190. break;
  191. case 0x83://激光控制
  192. OnLightChange(bytes);
  193. break;
  194. case 0x84://弹夹消息(仅手枪)
  195. OnMagazinesInfo(bytes);
  196. break;
  197. }
  198. }
  199. else
  200. {
  201. if (CommonConfig.bDisableBluetooth) {
  202. //byte[] bytes = new byte[] { 0x0A, 0x00, 0x00, 0x00, // 1st timestamp
  203. // 0x0B, 0x00, 0x00, 0x00, // 2nd timestamp
  204. // 0x00, 0x01, 0x00, 0x00 // 3rd timestamp
  205. //};
  206. if (bytes.Length == 12)
  207. {
  208. // 解析并转换时间戳
  209. for (int i = 0; i < bytes.Length; i += 4)
  210. {
  211. // 提取 4 字节的时间戳
  212. uint timestamp = BitConverter.ToUInt32(bytes, i);
  213. // 转换为毫秒(单位是 100us)
  214. float timeInMilliseconds = timestamp * 0.1f;
  215. // 打印转换结果
  216. Debug.Log($"Timestamp (U32): {timestamp} => Time: {timeInMilliseconds} ms");
  217. }
  218. }
  219. }
  220. }
  221. }
  222. bool lightWaitClose = false;
  223. public void DelayCloseLight()
  224. {
  225. if (!lightWaitClose && hasOnceInceCoin)
  226. {
  227. LOG("游戏时间结束 延迟关闭激光");
  228. Invoke("DoDelayCloseLight", 60f);
  229. lightWaitClose = true;
  230. }
  231. }
  232. public void CancelDelayCloseLight()
  233. {
  234. LOG("关闭延迟关闭激光");
  235. lightWaitClose = false;
  236. CancelInvoke("DoDelayCloseLight");
  237. }
  238. private void DoDelayCloseLight()
  239. {
  240. RequestLightState(false);
  241. }
  242. //是否有过首次投币 没投币过 不关闭摄像头
  243. bool hasOnceInceCoin = false;
  244. #region APP请求
  245. // 异或校验内容:命令+长度+数据内容
  246. /// <summary>
  247. /// 设置激光状态
  248. /// </summary>
  249. public void RequestLightState(bool on, bool insertCoin = false)
  250. {
  251. if (insertCoin)
  252. hasOnceInceCoin = true;
  253. List<byte> data = new List<byte>();
  254. data.Add(0xAA);//起始码
  255. data.Add(0x83);//命令号
  256. data.Add(0x06);//长度
  257. data.Add((byte)(on ? 0x01 : 0x00));//长度
  258. byte temp = 0;
  259. for (int i = 1; i < data.Count; i++)
  260. {
  261. temp ^= data[i];
  262. }
  263. data.Add(temp);//异或校验
  264. data.Add(0x55);//结束码
  265. serialPortInterface?.Write(data.ToArray());
  266. LOG($"设置激光状态:{on}!");
  267. OnLightChange(data.ToArray());
  268. }
  269. /// <summary>
  270. /// app请求设备信息
  271. /// </summary>
  272. public void RequestDeviceIno()
  273. {
  274. List<byte> data = new List<byte>();
  275. data.Add(0xAA);//起始码
  276. data.Add(0x80);//命令号
  277. data.Add(0x05);//长度
  278. data.Add(0x85);//异或校验
  279. data.Add(0x55);//结束码
  280. serialPortInterface?.Write(data.ToArray());
  281. Debug.Log("发送心跳包");
  282. DevicesHolder.ins?.ShowConnectTip();
  283. }
  284. #endregion
  285. #region 返回消息处理
  286. /// <summary>
  287. /// 弹夹消息
  288. /// </summary>
  289. /// <param name="bytes"></param>
  290. private void OnMagazinesInfo(byte[] bytes)
  291. {
  292. Debug.Log($"{PortName} 收到弹夹消息!");
  293. List<byte> data = new List<byte>();
  294. data.Add(0x5C);//起始码
  295. data.Add(bytes[3]);//命令号 0x01 0x00
  296. SerialPortHelper.ins.OnMagazineChange(data.ToArray());
  297. }
  298. private void OnLightChange(byte[] bytes)// 0xAA 0x83 0x06 0x01 0x84 0x55
  299. {
  300. lightWaitClose = false;
  301. Debug.Log($"{PortName} 激光状态变更回包!");
  302. var isOn = bytes[3] == 0x01;
  303. UserSettings.ins.lightState = isOn;
  304. }
  305. /// <summary>
  306. /// 设备信息响应
  307. /// </summary>
  308. private void OnDeviceInfoBack(byte[] bytes)// 0xAA 0x80 0x07 0x01 0x01 0x89 0x5D
  309. {
  310. Debug.Log($"{PortName} 收到设备信息响应!");
  311. var check = bytes[1] + bytes[2] + bytes[3] + bytes[4];//校验:命令+长度+数据内容
  312. if (HomeView.ins == null) {
  313. return;
  314. }
  315. //0x01 HOUYI Pro
  316. //0x02 ARTEMIS Pro
  317. //0x03 Pistol 1
  318. var deviceType = bytes[3];//设备类型
  319. Debug.Log("设备类型 Device Type: " + deviceType);
  320. switch (deviceType)
  321. {
  322. case 0x01:
  323. UserSettings.ins.selectDevicesName = "HOUYI Pro";
  324. DevicesHolder.ins.SwitchDeviceByType(AimDeviceType.HOUYIPRO);
  325. break;
  326. case 0x02:
  327. UserSettings.ins.selectDevicesName = "ARTEMIS Pro";
  328. DevicesHolder.ins.SwitchDeviceByType(AimDeviceType.ARTEMISPRO);
  329. break;
  330. case 0x03:
  331. UserSettings.ins.selectDevicesName = "Pistol M9";
  332. DevicesHolder.ins.SwitchDeviceByType(AimDeviceType.Gun);
  333. break;
  334. }
  335. UserSettings.ins.Save();
  336. //刷新界面
  337. var setting = FindAnyObjectByType<CustomUIView.BoxUserSettings>();
  338. setting?.FlushDeviceSelect();
  339. }
  340. /// <summary>
  341. /// 射击消息
  342. /// </summary>
  343. private void OnDeviceShoot(byte[] bytes)
  344. {
  345. LOG($"{PortName} 收到设备射击消息!");
  346. var check = bytes[1] + bytes[2] + bytes[3] + bytes[4];//校验:命令+长度+数据内容
  347. //if (check != bytes[5])
  348. //{
  349. // LOG("OnDeviceShoot 数据校验错误!");
  350. //}else
  351. SerialPortHelper.shoot?.Invoke(bytes);
  352. }
  353. /// <summary>
  354. /// 按键消息
  355. /// </summary>
  356. /// <param name="bytes"></param>
  357. private void OnDeviceButton(byte[] bytes)
  358. {
  359. Debug.Log($"{PortName} 收到设备按键消息!");
  360. var check = bytes[1] + bytes[2] + bytes[3];
  361. //校验:命令+长度+数据内容
  362. //if (check != bytes[4])
  363. //{
  364. // LOG("OnDeviceButton 数据校验错误!");
  365. //}
  366. //else
  367. if (string.IsNullOrEmpty(UserSettings.ins.selectDevicesName))
  368. //开机
  369. RequestDeviceIno();
  370. SerialPortHelper.aim?.Invoke(bytes);
  371. }
  372. #endregion
  373. #region 解析自定义缓存
  374. private List<byte> dataCache = new List<byte>(); // 用于缓存接收到的数据
  375. private const byte FrameHeader1 = 0xAA; // 帧头1
  376. private const byte FrameHeader2 = 0x55; // 帧头2
  377. // 尝试从缓存中解析出下一帧
  378. private bool TryParseNextFrame(ref List<byte> cache, out byte[] frame)
  379. {
  380. frame = null;
  381. // 检查缓存中是否有足够的数据进行解析(至少需要 3 个字节:帧头 + 命令 + 数据长度)
  382. if (cache.Count < 3)
  383. return false;
  384. // 查找帧头:0xAA
  385. for (int i = 0; i < cache.Count - 1; i++)
  386. {
  387. if (cache[i] == 0xAA)
  388. {
  389. // 如果找到帧头,确保后面有足够的数据(命令 + 数据长度字节)
  390. if (cache.Count < i + 3)
  391. continue;
  392. byte command = cache[i + 1]; // 命令字节
  393. byte dataLength = cache[i + 2]; // 数据长度字节(表示整个数据帧的总长度)
  394. // 如果数据长度不合理,跳过该帧
  395. if (dataLength == 0 || cache.Count < i + dataLength)
  396. continue;
  397. // 计算校验值(命令 + 数据长度 + 数据部分)
  398. byte calculatedChecksum = command;
  399. calculatedChecksum ^= dataLength;
  400. for (int j = i + 3; j < i + dataLength - 2; j++) // 从数据部分开始
  401. {
  402. calculatedChecksum ^= cache[j]; // 与数据内容进行异或
  403. }
  404. byte checksum = cache[i + dataLength - 2]; // 校验字节位于数据部分之后
  405. // 校验通过后,提取数据
  406. if (checksum == calculatedChecksum)
  407. {
  408. // 获取完整的数据帧,包括帧头、命令、数据长度、数据、校验和帧尾
  409. frame = cache.GetRange(i, dataLength).ToArray(); // 数据帧总长度 = 数据长度字节指定的长度
  410. // 移除已经解析的帧
  411. cache.RemoveRange(0, i + dataLength); // 更新缓存,移除已解析的部分
  412. return true;
  413. }
  414. }
  415. }
  416. // 如果没有找到符合条件的帧,返回 false
  417. return false;
  418. }
  419. #endregion
  420. }