using SerialPortUtility; using System; using System.Collections.Generic; using UnityEngine; public class ArrowSerialPort : MonoBehaviour { private SerialPortUtilityPro serialPortUtility; private SerialPortUtilityPro.OpenSystem openMode = SerialPortUtilityPro.OpenSystem.PCI; private int baudrate = 115200; //private int baudrate = 921600; //private int baudrate = 460800; private string PortName = "/dev/ttyS0"; private static Dictionary _isInit = new(); public bool testMode = true; private void Awake() { if (!_isInit.TryGetValue(gameObject.name, out int instanceID) && instanceID == 0) { DontDestroyOnLoad(gameObject); Init(); } } private void LOG(string msg, bool warning = false) { if (!warning) Debug.Log($"{msg}"); else Debug.LogWarning($"{msg}"); } private void Init() { _isInit[gameObject.name] = GetInstanceID(); #if UNITY_ANDROID && !UNITY_EDITOR serialPortUtility = gameObject.GetComponent(); serialPortUtility.OpenMethod = openMode; serialPortUtility.DeviceName = PortName; serialPortUtility.BaudRate = baudrate; serialPortUtility.StopBit = SerialPortUtilityPro.StopBitEnum.OneBit; serialPortUtility.DataBit = SerialPortUtilityPro.DataBitEnum.EightBit; serialPortUtility.SystemEventObject.AddListener(SystemEventObject); TrySerialOpenPort(); #endif #if UNITY_EDITOR if (testMode) { var testBoard = Resources.Load("SerialPortTest"); GameObject.Instantiate(testBoard); } #endif } private void TrySerialOpenPort() { serialPortUtility?.Open(); } private void SystemEventObject(SerialPortUtilityPro arg0, string msg) { if (msg.Equals("OPEN_ERROR") || msg.Equals("PERMISSION_ERROR"))//串口开启失败 定时重试 { #if UNITY_ANDROID && !UNITY_EDITOR Invoke("TrySerialOpenPort", 1f); #endif // LOG($"{PortName} 串口打开失败 重试中", true); } else if (msg.Equals("OPENED")) { SerialPortHelper.ins.OnConnect(arg0.DeviceName); //应用启动 间隔十秒请求一次设备信息 CancelInvoke("RequestDeviceIno"); InvokeRepeating("RequestDeviceIno", 0f, 10f); //LOG($"{PortName} 串口打开成功!"); ////激光默认打开 //RequestLightState(true); } else if (msg.Equals("CLOSED"))//串口断开 { SerialPortHelper.ins.OnDisConnect(arg0.DeviceName); LOG($"{PortName} 串口关闭!"); } } private void OnDestroy() { if (_isInit.TryGetValue(gameObject.name, out var instanceID) && instanceID == GetInstanceID()) { LOG($"{PortName} 串口关闭"); SerialPortHelper.ins.OnDisConnect(serialPortUtility?.DeviceName); serialPortUtility?.Close(); _isInit.Remove(gameObject.name); } } /// /// 串口读取二进制流数据(界面挂载调用) /// /// public void ReadStreamingBinary(object data) { var realData = new List(); var tempData = data as byte[]; for (int i = 0; i < tempData.Length; i++) { realData.Add(tempData[i]); if (tempData.Length > i + 1) { if (tempData[i + 1] == 0XAA) //多条消息 粘包位置 { //把上一条发送 PhraseData(realData.ToArray()); realData.Clear(); } } else PhraseData(realData.ToArray()); } } public void TestRead(byte[] bytes) { ReadStreamingBinary(bytes); } /// /// 解析串口数据 /// /// private void PhraseData(byte[] bytes) { string msg = string.Empty; for (int i = 0; i < bytes.Length; i++) { msg += bytes[i].ToString("x2") + " "; } // 获取当前时间 DateTime currentTime = DateTime.Now; LOG($"{PortName} 收到串口信息 msg={msg}!,接收时间:" + currentTime.ToString("yyyy-MM-dd HH:mm:ss.fff")); if (bytes[0] == 0xAA) { var cmdID = bytes[1]; switch (cmdID) { case 0x80://设备信息响应 OnDeviceInfoBack(bytes); break; case 0x81://射击消息 if (CommonConfig.bDisableBluetooth) { // 获取当前时间 DateTime currentTime1 = DateTime.Now; // 打印带毫秒的时间戳 Debug.Log("射箭前:CurrentTime1: " + currentTime1.ToString("yyyy-MM-dd HH:mm:ss.fff")); //3 - APP在收到射箭消息后,立即回复相同的内容给设备端; serialPortUtility?.Write(bytes); DateTime currentTime3 = DateTime.Now; // 打印带毫秒的时间戳 Debug.Log("*** 回复相同的内容给设备端 CurrentTime3: " + currentTime3.ToString("yyyy-MM-dd HH:mm:ss.fff")); } OnDeviceShoot(bytes); if (CommonConfig.bDisableBluetooth) { // 获取当前时间 DateTime currentTime2 = DateTime.Now; // 打印带毫秒的时间戳 Debug.Log("射箭后:CurrentTime2: " + currentTime2.ToString("yyyy-MM-dd HH:mm:ss.fff")); } break; case 0x82://按键消息 OnDeviceButton(bytes); break; case 0x83://激光控制 OnLightChange(bytes); break; case 0x84://弹夹消息(仅手枪) OnMagazinesInfo(bytes); break; } } else { if (CommonConfig.bDisableBluetooth) { //byte[] bytes = new byte[] { 0x0A, 0x00, 0x00, 0x00, // 1st timestamp // 0x0B, 0x00, 0x00, 0x00, // 2nd timestamp // 0x00, 0x01, 0x00, 0x00 // 3rd timestamp //}; if (bytes.Length == 12) { // 解析并转换时间戳 for (int i = 0; i < bytes.Length; i += 4) { // 提取 4 字节的时间戳 uint timestamp = BitConverter.ToUInt32(bytes, i); // 转换为毫秒(单位是 100us) float timeInMilliseconds = timestamp * 0.1f; // 打印转换结果 Debug.Log($"Timestamp (U32): {timestamp} => Time: {timeInMilliseconds} ms"); } } } } } bool lightWaitClose = false; public void DelayCloseLight() { if (!lightWaitClose && hasOnceInceCoin) { LOG("游戏时间结束 延迟关闭激光"); Invoke("DoDelayCloseLight", 60f); lightWaitClose = true; } } public void CancelDelayCloseLight() { LOG("关闭延迟关闭激光"); lightWaitClose = false; CancelInvoke("DoDelayCloseLight"); } private void DoDelayCloseLight() { RequestLightState(false); } //是否有过首次投币 没投币过 不关闭摄像头 bool hasOnceInceCoin = false; #region APP请求 // 异或校验内容:命令+长度+数据内容 /// /// 设置激光状态 /// public void RequestLightState(bool on, bool insertCoin = false) { if (insertCoin) hasOnceInceCoin = true; List data = new List(); data.Add(0xAA);//起始码 data.Add(0x83);//命令号 data.Add(0x06);//长度 data.Add((byte)(on ? 0x01 : 0x00));//长度 byte temp = 0; for (int i = 1; i < data.Count; i++) { temp ^= data[i]; } data.Add(temp);//异或校验 data.Add(0x55);//结束码 serialPortUtility?.Write(data.ToArray()); LOG($"设置激光状态:{on}!"); OnLightChange(data.ToArray()); } /// /// app请求设备信息 /// public void RequestDeviceIno() { List data = new List(); data.Add(0xAA);//起始码 data.Add(0x80);//命令号 data.Add(0x05);//长度 data.Add(0x85);//异或校验 data.Add(0x55);//结束码 serialPortUtility?.Write(data.ToArray()); Debug.Log("发送心跳包"); DevicesHolder.ins?.ShowConnectTip(); } #endregion #region 返回消息处理 /// /// 弹夹消息 /// /// private void OnMagazinesInfo(byte[] bytes) { Debug.Log($"{PortName} 收到弹夹消息!"); SerialPortHelper.ins.OnMagazineChange(bytes); } private void OnLightChange(byte[] bytes)// 0xAA 0x83 0x06 0x01 0x84 0x55 { lightWaitClose = false; Debug.Log($"{PortName} 激光状态变更回包!"); var isOn = bytes[3] == 0x01; UserSettings.ins.lightState = isOn; } /// /// 设备信息响应 /// private void OnDeviceInfoBack(byte[] bytes)// 0xAA 0x80 0x07 0x01 0x01 0x89 0x5D { Debug.Log($"{PortName} 收到设备信息响应!"); var check = bytes[1] + bytes[2] + bytes[3] + bytes[4];//校验:命令+长度+数据内容 if (HomeView.ins == null) { return; } //0x01 HOUYI Pro //0x02 ARTEMIS Pro //0x03 Pistol 1 var deviceType = bytes[3];//设备类型 Debug.Log("设备类型 Device Type: " + deviceType); switch (deviceType) { case 0x01: UserSettings.ins.selectDevicesName = "HOUYI Pro"; DevicesHolder.ins.SwitchDeviceByType(AimDeviceType.HOUYIPRO); break; case 0x02: UserSettings.ins.selectDevicesName = "ARTEMIS Pro"; DevicesHolder.ins.SwitchDeviceByType(AimDeviceType.ARTEMISPRO); break; case 0x03: UserSettings.ins.selectDevicesName = "Pistol M9"; DevicesHolder.ins.SwitchDeviceByType(AimDeviceType.Gun); break; } UserSettings.ins.Save(); //刷新界面 var setting = FindAnyObjectByType(); setting?.FlushDeviceSelect(); } /// /// 射击消息 /// private void OnDeviceShoot(byte[] bytes) { LOG($"{PortName} 收到设备射击消息!"); var check = bytes[1] + bytes[2] + bytes[3] + bytes[4];//校验:命令+长度+数据内容 //if (check != bytes[5]) //{ // LOG("OnDeviceShoot 数据校验错误!"); //}else SerialPortHelper.shoot?.Invoke(bytes); } /// /// 按键消息 /// /// private void OnDeviceButton(byte[] bytes) { Debug.Log($"{PortName} 收到设备按键消息!"); var check = bytes[1] + bytes[2] + bytes[3]; //校验:命令+长度+数据内容 //if (check != bytes[4]) //{ // LOG("OnDeviceButton 数据校验错误!"); //} //else if (string.IsNullOrEmpty(UserSettings.ins.selectDevicesName)) //开机 RequestDeviceIno(); SerialPortHelper.aim?.Invoke(bytes); } #endregion }