using System; using System.Collections; using System.Collections.Generic; using System.IO; using Unity.VisualScripting; using UnityEngine; using UnityEngine.SceneManagement; using MySerialPortInterface; using MyInfraredInsertcoin; public class SerialPortExample : MonoBehaviour { public int baudrate = 115200; //使用自定义串口 private SerialPortInterface serialPortInterface; private static int baseAddress; private static int offsetAddress; private Queue tempDataQueue; public string PortName = ""; private int coinsAmount = 0; private const int SlotCount = 1; bool urlBack = false; byte dataIndex = 1; // private static Dictionary _isInit = new(); private void Start() { //LOG($"_isInit contains {gameObject.name}: {_isInit.ContainsKey(gameObject.name)}"); //if (!_isInit.TryGetValue(gameObject.name, out int instanceID) && instanceID == 0) //{ // DontDestroyOnLoad(gameObject); // Init(); //} DontDestroyOnLoad(gameObject); Init(); } private void LOG(string msg) { Debug.Log($"{msg}"); } private void Init() { //_isInit[gameObject.name] = GetInstanceID(); #if UNITY_ANDROID && !UNITY_EDITOR serialPortInterface = gameObject.GetComponent(); serialPortInterface?.Open(); LOG($"{PortName} 串口打开"); if(serialPortInterface.DeviceName == "ttyS6") { //拉取二维码 RequestUrl(dataIndex); //拉取后台数据 ReqSettingData(); } #endif #if UNITY_EDITOR TestBackUrl(); //serialPortInterface = gameObject.GetComponent(); //if (serialPortInterface.DeviceName == "ttyS6") //{ // //List data = new List(); // //data.Add(0XAA); // //data.Add(0X04);//帧头 // //data.Add(0x06);//帧长度 // //data.Add(0x00);//数据编号 // //data.Add(0X02);//指令 // //data.Add(0X55);//数据 // //HandleSerialPortUpdate(data.ToArray(), 6); //} #endif } void Update() { #region 测试代码 #if UNITY_EDITOR if (Input.GetMouseButtonDown(0)) { //TestPayQR(); } #endif #endregion //if (serialPortUtility != null && serialPortUtility.IsOpened()) // { // Debug.Log(serialPortUtility.GetSerialDebugString); // Debug.Log("串口接收到数据"); // } } void TestPay() { if (!PortName.Contains("/dev/ttyS0")) return; Debug.LogError("TestPay"); List data = new List(); data.Add(0XAA); data.Add(0X55);//帧头 data.Add(0x04);//帧长度 data.Add(0x01);//数据编号 data.Add(0XA6);//指令 data.Add(0X01);//数据 data.Add(0X00);//校验位 PrintFrame(data.ToArray()); } public void TestSetting() { List data = new List(); data.Add(0XAA); data.Add(0X0F);// data.Add(0x05);// data.Add(0x0A);// data.Add(0X55);// HandleSerialPortUpdate(data.ToArray(), 6); } void TestPayQR() { if (!PortName.Contains("/dev/ttyS0")) return; List data = new List(); data.Add(0XAA); data.Add(0X55);//帧头 data.Add(0x05);//帧长度 data.Add(0xDA);//数据编号 data.Add(0XA1);//指令 data.Add(0X02);//数据 data.Add(0X64);//数据 byte temp = 0; for (int i = 2; i < data.Count; i++) { temp ^= data[i]; } data.Add(temp);//校验位 Debug.LogError($"TestPayQR temp={temp}"); PrintFrame(data.ToArray()); } void OnUrlPayResponse(byte[] bytes) { //计算校验值 byte tempResponseCheck = 0; for (int i = 2; i < bytes.Length - 1; i++) { tempResponseCheck ^= bytes[i]; } byte responseCheck = bytes[bytes.Length - 1]; //回复云上分 List response = new List(); response.Add(0XAA); response.Add(0X55);//帧头 response.Add(0X04);//帧长 var dataIndex = bytes[3]; response.Add(dataIndex);//数据编号 response.Add(0XA1);//数据编号 var data = responseCheck == tempResponseCheck ? 0X01 : 0x00; response.Add((byte)data);//数据 var endIdx = bytes[3]; byte temp = 0; for (int i = 2; i < response.Count; i++) { temp ^= response[i]; } response.Add(temp);//校验位 Debug.LogError($"responseCheck={responseCheck} tempResponseCheck={tempResponseCheck} data ={data} dataIndex={dataIndex} temp={temp}"); serialPortInterface?.Write(response.ToArray()); if (data == 0X01) StandaloneAPI.InsertCoint(bytes[6]); } /// /// 请求后台数据 /// public void ReqSettingData() { //数据包开始------->(1个字节)->AA //命令------------->(1个字节)->08(取值范围->00到FF) //数据长度--------->(1个字节)->05(取值范围->00到FF) //校验------------->(1个字节)->0D(取值范围->00到FF)(校验 = 命令 ^ 数据长度) //数据包结束------->(1个字节)->55 //例如:AA 08 05 0D 55 List request = new List(); request.Add(0xAA); request.Add(0x08);//命令 request.Add(0x05);//数据长度 byte temp = (byte)(request[1] ^ request[2]); request.Add(temp);//校验 (校验 = 命令 ^ 数据长度) request.Add(0x55);//结束 serialPortInterface?.Write(request.ToArray()); LOG($"请求后台数据!"); } /// /// 后台数据消息回复 /// /// private void OnSettingDataBack(byte[] bytes) //AA 08 14 03 00 64 00 64 00 00 00 01 00 00 00 01 01 01 1F 55 { //数据包开始-------------------------- > (1个字节)->AA //命令--------------------------------->(1个字节)->08(取值范围->00到FF) //数据长度---------------------------- > (1个字节)->14(取值范围->00到FF) //多少币玩一局游戏(1 - 10)------------ > (1个字节)->03(取值范围->00到FF) var coinPreGame = bytes[3]; UserSettings.ins.PerRoundCoin = coinPreGame; //一局游戏玩多长时间(1 - 1200)------->(2个字节)->00 64(取值范围->00 00到FF FF) var time1 = bytes[4]; var time2 = bytes[5]; int time = (time1 << 8) | time2; UserSettings.ins.PerRoundSeconds = time; //多少分兑换一张彩票(1 - 10000)------ > (2个字节)->00 64(取值范围->00 00到FF FF) var changeTicket1 = bytes[6]; var changeTicket2 = bytes[7]; int changeTicket = (changeTicket1 << 8) | changeTicket2; UserSettings.ins.ChangeTicket = changeTicket; //累计投币数(0 - 4294967295)-------- > (4个字节)->00 00 00 01(取值范围->00 00 00 00到FF FF FF FF) var totalCoinNum1 = bytes[8]; var totalCoinNum2 = bytes[9]; var totalCoinNum3 = bytes[10]; var totalCoinNum4 = bytes[11]; UserSettings.ins.TotalCoinsNum = (totalCoinNum1 << 24) | (totalCoinNum2 << 16) | (totalCoinNum3 << 8) | totalCoinNum4; //累计出彩票数(0 - 4294967295)------ > (4个字节)->00 00 00 01(取值范围->00 00 00 00到FF FF FF FF) var totalTicketNum1 = bytes[12]; var totalTicketNum2 = bytes[13]; var totalTicketNum3 = bytes[14]; var totalTicketNum4 = bytes[15]; UserSettings.ins.TotalTicketNum = (totalTicketNum1 << 24) | (totalTicketNum2 << 16) | (totalTicketNum3 << 8) | totalTicketNum4; //是否需要投币(0 - 1)----------------->(1个字节)->01(取值范围->00到FF) UserSettings.ins.NeedCoin = bytes[16]; //是否出彩票(0 - 1)------------------->(1个字节)->01(取值范围->00到FF) UserSettings.ins.GiveTicket = bytes[17]; //校验-------------------------------- > (1个字节)->1F(取值范围->00到FF)(校验 = 命令 ^ 数据长度 ^ ......^是否出彩票) byte temp = 0; for (int i = 1; i < bytes.Length - 2; i++) { temp ^= bytes[i]; } //数据包结束------------------------->(1个字节)->55 LOG($"收到后台数据--->coinPreGame:{coinPreGame} time={time} 校验结果={bytes[18] == temp} temp={temp} bytes[18]={bytes[18]} !"); } /// /// 请求保存后台数据 /// public void ReqSavingSettingData() //AA 07 14 03 00 64 00 65 00 00 00 01 00 00 00 02 01 01 12 55 { List request = new List(); //数据包开始-------------------------- > (1个字节)->AA request.Add(0xAA); //命令--------------------------------->(1个字节)->07(取值范围->00到FF) request.Add(0x07); //数据长度---------------------------- > (1个字节)->14(取值范围->00到FF) request.Add(0x14); //多少币玩一局游戏(1 - 10)------------ > (1个字节)->03(取值范围->00到FF) request.Add((byte)UserSettings.ins.PerRoundCoin); //一局游戏玩多长时间(1 - 1200)------->(2个字节)->00 64(取值范围->00 00到FF FF) int time = UserSettings.ins.PerRoundSeconds; request.Add((byte)(time >> 8)); request.Add((byte)(time)); //多少分兑换一张彩票(1 - 10000)------ > (2个字节)->00 64(取值范围->00 00到FF FF) int changeTicket = UserSettings.ins.ChangeTicket; request.Add((byte)(changeTicket >> 8)); request.Add((byte)(changeTicket)); //累计投币数(0 - 4294967295)-------- > (4个字节)->00 00 00 01(取值范围->00 00 00 00到FF FF FF FF) int totalCoins = UserSettings.ins.TotalCoinsNum; request.Add((byte)(totalCoins >> 24)); request.Add((byte)(totalCoins >> 16)); request.Add((byte)(totalCoins >> 8)); request.Add((byte)(totalCoins)); //累计出彩票数(0 - 4294967295)------ > (4个字节)->00 00 00 01(取值范围->00 00 00 00到FF FF FF FF) int totalTicketNum = UserSettings.ins.TotalTicketNum; request.Add((byte)(totalTicketNum >> 24)); request.Add((byte)(totalTicketNum >> 16)); request.Add((byte)(totalTicketNum >> 8)); request.Add((byte)(totalTicketNum)); //是否需要投币(0 - 1)----------------->(1个字节)->01(取值范围->00到FF) request.Add((byte)UserSettings.ins.NeedCoin); //是否出彩票(0 - 1)------------------->(1个字节)->01(取值范围->00到FF) request.Add((byte)UserSettings.ins.GiveTicket); //校验-------------------------------- > (1个字节)->12(取值范围->00到FF)(校验 = 命令 ^ 数据长度 ^ ......^是否出彩票) byte temp = 0; for (int i = 2; i < request.Count; i++) { temp ^= request[i]; } request.Add(temp); //数据包结束------------------------->(1个字节)->55 request.Add(0x55);//结束 serialPortInterface?.Write(request.ToArray()); LOG($"请求保存后台数据!"); } /// /// 保存后台数据回复 /// private void OnSaveSettingDataBack(byte[] bytes)//AA 07 05 02 55 { //--> 回复(保存后台数据)的命令 //数据包开始------->(1个字节)->AA //命令------------->(1个字节)->07(取值范围->00到FF) //数据长度--------->(1个字节)->05(取值范围->00到FF) //校验------------->(1个字节)->02(取值范围->00到FF)(校验 = 命令 ^ 数据长度) //数据包结束------->(1个字节)->55 LOG($"保存后台数据回复!"); } /// /// 串口读取二进制流数据 /// /// public void ReadStreamingBinary(object data) { var bin = data as byte[]; PrintFrame(bin); } //自定义端口使用,根据 public void HandleSerialPortUpdate(byte[] buffer, int size) { //Debug.Log($"Received Data: {BitConverter.ToString(buffer)} of size {size}"); //PrintFrame(buffer); // 将新的数据加入缓存 dataCache.AddRange(buffer); // 尝试解析缓存中的数据 while (TryParseNextFrame(ref dataCache, out byte[] frame)) { // 如果成功解析一帧数据,处理该帧 // Debug.Log($"[SerialPortExample] frame Data: {BitConverter.ToString(frame)}"); PrintFrame(frame); } } public void PrintFrame(byte[] bytes) { //string logStr = ""; //for (int i = 0; i < bytes.Length; i++) //{ // logStr += $"i={bytes[i]}-"; //} // Debug.Log($"{PortName} 接收到的串口命令:{logStr}"); Debug.Log($" [SerialPortExample] {PortName} 接收到的串口命令:{BitConverter.ToString(bytes)}, 长度{bytes.Length}"); if (bytes[0] == 0xAA) { if (bytes[1] == 0x55)//旧 { if (bytes[4] == 0xA6)//投币 { Debug.Log("旧版投币数量:" + bytes[4]); StandaloneAPI.InsertCoint(bytes[5]); } else if (bytes[4] == 0xAA) //支付盒子链接请求返回 { Debug.Log("支付盒子链接请求返回:" + bytes[4]); OnUrlResponse(bytes); } else if (bytes[4] == 0xA1) //支付盒子支付返回(云上分) { Debug.Log("支付盒子支付返回(云上分):" + bytes[4]); OnUrlPayResponse(bytes); } } else { //0x04:为按键是否按下的命令 if (bytes[1] == 0x04)//退出 { Debug.Log("确认按键退出游戏命令"); if (bytes[3] == 0x01) { Debug.Log("确认按下按键"); // AimHandler.ins.ExitIntoEvent(); if (SceneManager.GetActiveScene().name == "Home") { StandbyVideoManager._ins?.StopVideo(); } else { SceneManager.LoadScene("Home", LoadSceneMode.Single); } SendMessage(bytes, 3); return; } else if (bytes[3] == 0x00) { Debug.Log("松开按键"); SendMessage(bytes, 3); return; } } else if (bytes[1] == 0x01)//投币 { Debug.Log("确认投币命令"); StandaloneAPI.InsertCoint(bytes[3]); SendMessage(bytes, 3); } else if (bytes[1] == 0x0F)//进入设置 AA 0F 05 0A 55 OpenSetUp(); else if(bytes[1] == 0x07)//回复保存后台数据 AA 07 05 02 55 OnSaveSettingDataBack(bytes); else if (bytes[1] == 0x08)//回复读后台数据 AA 08 14 03 00 64 00 64 00 00 00 01 00 00 00 01 01 01 1F 55 OnSettingDataBack(bytes); } } } /// /// 打开设置 /// GameObject settingsViewObj; private void OpenSetUp() { AudioMgr.ins.PlayBtn(); if (settingsViewObj) return; settingsViewObj = ViewManager2.getGameObjectAndShowView(ViewManager2.Path_SettingsView); settingsViewObj.GetComponent().ShowBoxSound(true); } /// /// 请求二维码链接 /// /// 数据编号 从1开始 private void RequestUrl(byte dataIndex) { LOG($"请求后台支付URL"); byte[] datas = new byte[7] { 0xAA, 0x55, 0x04, dataIndex, 0xAA, 0x01, 0xAE }; datas[6] = GetXor(datas); serialPortInterface?.Write(datas); Invoke("CheckURLBack", 5f); } private void CheckURLBack() { if (!urlBack && dataIndex <= 3) { Debug.LogWarning("没有拉取到支付url"); RequestUrl(++dataIndex); } } /// /// 测试URL回包 /// private void TestBackUrl() { var url = $"http://pay.sy1999.com:8901?productNumber=SY4G4365"; var urlData = System.Text.UTF8Encoding.Default.GetBytes(url); List data = new List(); data.Add(0XAA); data.Add(0X55);//帧头 data.Add((byte)(urlData.Length + 3));//帧长度(数据编号到校验位的所有数据长度) data.Add(0X01);//数据编号 data.Add(0XAA);//指令 data.AddRange(urlData);//数据 data.Add(0X00);//校验位 Debug.Log($"Test: url={url} length={urlData.Length}"); PrintFrame(data.ToArray()); } /// /// URL请求返回 /// /// //void OnUrlResponse(byte[] bytes) //{ // urlBack = true; // int frameLength = bytes[2]; // frameLength = frameLength - 3; // List datas = new List(); // var startPos = 5; // for (int i = startPos; i < frameLength + startPos; i++) // { // datas.Add(bytes[i]); // } // byte[] bs = datas.ToArray(); // var url = System.Text.Encoding.Default.GetString(bs); // StandaloneAPI.url = url; // var homeView = FindObjectOfType(); // if (homeView != null) // homeView.FlushUrlQR(); // SerialPortHelper.ins.GetPort()?.RequestLightState(true); //} void OnUrlResponse(byte[] bytes) { // 检查数据长度 if (bytes.Length < 3) { Debug.LogError("Received data too short."); return; } int frameLength = bytes[2] - 3; // 确保 frameLength 合理,防止越界 if (frameLength < 0 || frameLength + 5 > bytes.Length) { Debug.LogError("Invalid frame length."); return; } urlBack = true; List datas = new List(); var startPos = 5; // 提取数据 for (int i = startPos; i < startPos + frameLength; i++) { if (i >= bytes.Length) break; // 防止越界 datas.Add(bytes[i]); } byte[] bs = datas.ToArray(); // 使用 UTF-8 编码解析 URL var url = System.Text.Encoding.UTF8.GetString(bs); StandaloneAPI.url = url; var homeView = FindObjectOfType(); if (homeView != null) homeView.FlushUrlQR(); if(!CommonConfig.bOpenTheLaserWithoutQRCode) SerialPortHelper.ins.GetPort()?.RequestLightState(true); } void SendMessage(byte[] bytes,int index) { List s = new List(bytes); s.RemoveAt(index); bytes = s.ToArray(); serialPortInterface?.Write(bytes); } byte GetXor(byte[] btyes) { byte temp = 0; for (int i = 2; i < btyes.Length; i++) { temp ^= btyes[i]; } return temp; } private void OnDestroy() { //if (_isInit.TryGetValue(gameObject.name, out var instanceID) && instanceID == GetInstanceID()) //{ // LOG($"{PortName} 串口程序被销毁"); // serialPortInterface?.Close(); // _isInit.Remove(gameObject.name); //} } #region 解析自定义缓存 private List dataCache = new List(); // 用于缓存接收到的数据 private const byte FrameHeader1 = 0xAA; // 帧头1 private const byte FrameHeader2 = 0x55; // 帧头2 // 尝试从缓存中解析出下一帧 private bool TryParseNextFrame(ref List cache, out byte[] frame) { frame = null; // 检查缓存中是否有足够的数据进行解析(至少需要4个字节:帧头 + 帧长 + 数据编号 + 校验位) if (cache.Count < 4) return false; // 先检查以 0xAA 0x55 为帧头的格式 for (int i = 0; i < cache.Count - 1; i++) { // 检查帧头:0xAA 0x55 if (cache[i] == FrameHeader1 && cache[i + 1] == FrameHeader2) { // 找到帧头,检查数据长度 if (cache.Count < i + 4) // 至少需要 4 字节:帧头 + 帧长 + 数据编号 return false; // 获取帧长 byte frameLength = cache[i + 2]; // 帧长是第3字节 // 检查缓存中是否有足够的数据来构成一个完整的帧 if (cache.Count < i + 3 + frameLength) return false; // 解析出一帧数据 frame = cache.GetRange(i, 3 + frameLength).ToArray(); // 移除已经解析的帧 cache.RemoveRange(0, i + 3 + frameLength); return true; } } // 如果没有解析到 0xAA 0x55 格式的数据帧,再检查 0xAA 开头,0x55 结尾的格式 for (int i = 0; i < cache.Count - 1; i++) { // 检查帧头:0xAA if (cache[i] == 0xAA) { // 找到帧头,继续向后查找数据是否满足格式 // 查找帧尾:0x55 for (int j = i + 1; j < cache.Count; j++) { if (cache[j] == 0x55) { // 确保数据包格式正确 if (j - i < 4) // 至少需要 4 字节:命令 + 数据长度 + 校验 + 帧尾 continue; // 获取完整的帧数据(包括帧头、命令、数据长度、数据、校验和帧尾) frame = cache.GetRange(i, j - i + 1).ToArray(); // 从帧头到帧尾的所有数据 // 移除已经解析的帧 cache.RemoveRange(0, j + 1); // 从缓存中移除已解析的部分(包括帧头和帧尾) return true; } } } } // 如果没有找到符合条件的帧 return false; } #endregion }