using SerialPortUtility; using System; using System.Collections; using System.Collections.Generic; using System.IO; using Unity.VisualScripting; using UnityEngine; public class SerialPortExample : MonoBehaviour { public int baudrate = 115200; public SerialPortUtilityPro serialPortUtility; public SerialPortUtilityPro.OpenSystem openMode = SerialPortUtilityPro.OpenSystem.PCI; 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 Awake() { if (!_isInit.TryGetValue(gameObject.name, out int instanceID) && instanceID == 0) { DontDestroyOnLoad(gameObject); Init(); } } private void LOG(string msg) { Debug.Log($"{msg}"); } private void Init() { _isInit[gameObject.name] = GetInstanceID(); #if UNITY_ANDROID && !UNITY_EDITOR serialPortUtility = gameObject.GetComponent(); serialPortUtility.OpenMethod = openMode; serialPortUtility.DeviceName = PortName; serialPortUtility.StopBit = SerialPortUtilityPro.StopBitEnum.OneBit; serialPortUtility.DataBit = SerialPortUtilityPro.DataBitEnum.EightBit; serialPortUtility.Open(); LOG($"{PortName} 串口打开"); if (PortName.Contains("/dev/ttyS0")) { //拉取二维码 RequestUrl(dataIndex); //拉取后台数据 ReqSettingData(); } #endif #if UNITY_EDITOR TestBackUrl(); #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()); } 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) { if (!PortName.Contains("/dev/ttyS0")) return; //计算校验值 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}"); serialPortUtility?.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);//结束 serialPortUtility?.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);//结束 serialPortUtility?.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 PrintFrame(byte[] bytes) { string logStr = ""; for (int i = 0; i < bytes.Length; i++) { logStr += $"i={bytes[i]}-"; } Debug.Log($"{PortName} 接收到的串口命令:{logStr}"); if (bytes[0] == 0xAA) { if (bytes[1] == 0x55)//旧 { if (!PortName.Contains("/dev/ttyS8")) return; if (bytes[4] == 0xA6)//投币 { Debug.Log("旧版投币数量:" + bytes[4]); StandaloneAPI.InsertCoint(bytes[5]); } else if (bytes[4] == 0xAA) //支付盒子链接请求返回 OnUrlResponse(bytes); else if (bytes[4] == 0xA1) //支付盒子支付返回(云上分) OnUrlPayResponse(bytes); } else { if (!PortName.Contains("/dev/ttyS0")) return; //0x04:为按键是否按下的命令 if (bytes[1] == 0x04)//退出 { Debug.Log("确认按键退出游戏命令"); if (bytes[3] == 0x01) { Debug.Log("确认按下按键"); AimHandler.ins.ExitIntoEvent(); 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); } } } /// /// 打开设置 /// private void OpenSetUp() { AudioMgr.ins.PlayBtn(); GameObject 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); serialPortUtility?.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(); } void SendMessage(byte[] bytes,int index) { List s = new List(bytes); s.RemoveAt(index); bytes = s.ToArray(); serialPortUtility?.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} 串口程序被销毁"); serialPortUtility?.Close(); _isInit.Remove(gameObject.name); } } }