diff --git a/LoraGamepad/LoraGamepad.csproj b/LoraGamepad/LoraGamepad.csproj index bbce3b1..8370805 100644 --- a/LoraGamepad/LoraGamepad.csproj +++ b/LoraGamepad/LoraGamepad.csproj @@ -1,6 +1,6 @@  - WinExe + Exe net6.0 enable diff --git a/LoraGamepad/Util/CrcUtil.cs b/LoraGamepad/Util/CrcUtil.cs new file mode 100644 index 0000000..6e02bdf --- /dev/null +++ b/LoraGamepad/Util/CrcUtil.cs @@ -0,0 +1,40 @@ +namespace LoraGamepad.Util; + +public class CrcUtil +{ + /// + /// CRC8——MAXIM校验字节 + /// + /// 带校验数据 + /// 校验字节 + public static byte CRC8_Calculate(byte[] data) + { + byte crc8 = 0; + for( int i = 0 ; i < data.Length; i++){ + crc8 = CRC8Table[crc8^data[i]]; + } + return(crc8); + } + + /// + /// CRC8——MAXIM校验表 + /// + private static byte[] CRC8Table = { + 0, 94, 188, 226, 97, 63, 221, 131, 194, 156, 126, 32, 163, 253, 31, 65, + 157, 195, 33, 127, 252, 162, 64, 30, 95, 1, 227, 189, 62, 96, 130, 220, + 35, 125, 159, 193, 66, 28, 254, 160, 225, 191, 93, 3, 128, 222, 60, 98, + 190, 224, 2, 92, 223, 129, 99, 61, 124, 34, 192, 158, 29, 67, 161, 255, + 70, 24, 250, 164, 39, 121, 155, 197, 132, 218, 56, 102, 229, 187, 89, 7, + 219, 133, 103, 57, 186, 228, 6, 88, 25, 71, 165, 251, 120, 38, 196, 154, + 101, 59, 217, 135, 4, 90, 184, 230, 167, 249, 27, 69, 198, 152, 122, 36, + 248, 166, 68, 26, 153, 199, 37, 123, 58, 100, 134, 216, 91, 5, 231, 185, + 140, 210, 48, 110, 237, 179, 81, 15, 78, 16, 242, 172, 47, 113, 147, 205, + 17, 79, 173, 243, 112, 46, 204, 146, 211, 141, 111, 49, 178, 236, 14, 80, + 175, 241, 19, 77, 206, 144, 114, 44, 109, 51, 209, 143, 12, 82, 176, 238, + 50, 108, 142, 208, 83, 13, 239, 177, 240, 174, 76, 18, 145, 207, 45, 115, + 202, 148, 118, 40, 171, 245, 23, 73, 8, 86, 180, 234, 105, 55, 213, 139, + 87, 9, 235, 181, 54, 104, 138, 212, 149, 203, 41, 119, 244, 170, 72, 22, + 233, 183, 85, 11, 136, 214, 52, 106, 43, 117, 151, 201, 74, 20, 246, 168, + 116, 42, 200, 150, 21, 75, 169, 247, 182, 232, 10, 84, 215, 137, 107, 53 + }; +} \ No newline at end of file diff --git a/LoraGamepad/Util/IAsyncPipe.cs b/LoraGamepad/Util/IAsyncPipe.cs new file mode 100644 index 0000000..8caeeaf --- /dev/null +++ b/LoraGamepad/Util/IAsyncPipe.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Concurrent; +using System.Threading; + +namespace LoraGamepad.Util; + /// + /// 异步管道 + /// + /// 流入数据类型 + /// 流出数据类型 +public abstract class IAsyncPipe : IPipe { + private readonly ConcurrentQueue _dataQueue = new(); + private bool _isWorking; + private DateTime _urgeTime; + private DateTime _workTime; + private readonly string _threadName; + private readonly TimeSpan _waitWorkTimeMax; + protected IAsyncPipe(string threadName = "IAsyncPipe", long waitWorkMills = 3000) { + _threadName = threadName; + _waitWorkTimeMax = TimeSpan.FromMilliseconds(waitWorkMills); + } + + /// + /// 催工 + /// + private void UrgeWork() { + _urgeTime = DateTime.Now; + if (_isWorking) { + // 在干活?打扰了.. + return; + } + + _isWorking = true; + var thread = new Thread(() => { + // 在失业的边缘反复横跳 + while (!TryStrike()) { + // 干活 + Process(_dataQueue); + } + }) { + IsBackground = true, + Name = _threadName + }; + thread.Start(); + } + + /// + /// 尝试罢工 + /// + /// 是否罢工成功 + private bool TryStrike() { + if (!_dataQueue.IsEmpty) { + // 有活继续干 + _workTime = DateTime.Now; + } else if (DateTime.Now - _workTime > _waitWorkTimeMax) { + // 闲3秒罢工 + _isWorking = false; + } + + return !_isWorking; + } + + protected abstract void Process(ConcurrentQueue queue); + + public override void Push(T1 pack) { + if (pack == null) { + return; + } + + _dataQueue.Enqueue(pack); + UrgeWork(); + } + + /// + /// 数据是否停止流入达到指定时间 + /// + /// + /// + protected bool IsPushStop(TimeSpan time) { + return DateTime.Now - _urgeTime > time; + } +} + diff --git a/LoraGamepad/Util/IPipe.cs b/LoraGamepad/Util/IPipe.cs new file mode 100644 index 0000000..68a16c0 --- /dev/null +++ b/LoraGamepad/Util/IPipe.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; + +namespace LoraGamepad.Util; +/// +/// 数据管道基类 +/// +/// 入口数据类型 +/// 出口数据类型 +public abstract class IPipe { + public delegate void OutFuncDelegate(T2 data); + + public OutFuncDelegate OnOut; + public abstract void Push(T1 data); + + public void Push(IEnumerable data) { + foreach (var x in data) { + Push(x); + } + } +} diff --git a/LoraGamepad/Util/SerialPipeIN.cs b/LoraGamepad/Util/SerialPipeIN.cs new file mode 100644 index 0000000..42d420d --- /dev/null +++ b/LoraGamepad/Util/SerialPipeIN.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Concurrent; +using System.Linq; +using System.Threading; + +namespace LoraGamepad.Util; + +public class SerialPipeIn: IAsyncPipe +{ + protected override void Process(ConcurrentQueue queue) + { + if (!queue.TryPeek(out var first)) + { + SpinWait.SpinUntil(() => false, 1); + // 队列无数据 + return; + } + + if (0xFE != first) { + // 未匹配到包头,扔掉继续 + queue.TryDequeue(out _); + return; + } + + if (queue.Count < 14) { + // 数据长度过低 + return; + } + + var pack = queue.Take(14).ToArray(); + if (pack[13] != CrcUtil.CRC8_Calculate(pack.Skip(1).SkipLast(1).ToArray())) { + // crc校验失败,砍头 + queue.TryDequeue(out _); + return; + } + + // 队列消除整包数据 + for (var i = 0; i < 14; i++) { + queue.TryDequeue(out _); + } + + // 输出数据,砍头砍尾 + // Console.WriteLine(BitConverter.ToString(pack)); + OnOut(pack.Skip(1).SkipLast(1).ToArray()); + + } + + +} \ No newline at end of file diff --git a/LoraGamepad/ViewModels/TProViewModel.cs b/LoraGamepad/ViewModels/TProViewModel.cs index 0648a9f..882c866 100644 --- a/LoraGamepad/ViewModels/TProViewModel.cs +++ b/LoraGamepad/ViewModels/TProViewModel.cs @@ -1,7 +1,11 @@ +using System; using System.Collections.ObjectModel; +using System.Diagnostics; using System.Linq; using System.Reactive; +using System.Threading; using LoraGamepad.Models; +using LoraGamepad.Util; using ReactiveUI; namespace LoraGamepad.ViewModels; @@ -15,14 +19,34 @@ public class TProViewModel : ViewModelBase private ObservableCollection _portList = new(); public CBPortItem PortSelectItem { get; set; } + private readonly SerialPipeIn _serialPipeIn; + public ReactiveCommand OpenPort { get; } public ReactiveCommand ClosePort { get; } + + public Thread ReadThread; public TProViewModel() { PortList = CBPortItem.GetPortList(); + _serialPipeIn = new SerialPipeIn(); + ReadThread = new Thread(ReadThreadEntry); OpenPort = ReactiveCommand.Create(PortOpenButtonClick); ClosePort = ReactiveCommand.Create(PortCloseButtonClick); + + } + + public void ReadThreadEntry() + { + while (true) + { + if (SerialPort.Me.TryRead(out var data)) + { + // Console.WriteLine(BitConverter.ToString(data)); + _serialPipeIn.Push(data); + } + Thread.Sleep(1); + } } /// @@ -72,7 +96,7 @@ public class TProViewModel : ViewModelBase SerialPort.Me.Open(PortSelectItem.PortName,420000,1000); IsOpenPortButtonVisible = false; IsClosePortButtonVisible = true; - + ReadThread.Start(); } ///