From dcff37841f434c103f4beddabed82628a3bc0d3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=99=BA=E8=83=BD=E5=A4=A7=E7=9F=B3=E5=A4=B4?= Date: Thu, 18 Apr 2024 20:59:22 +0800 Subject: [PATCH] =?UTF-8?q?XCoderLinux=E6=94=AF=E6=8C=81=E5=9C=A8Ubuntu?= =?UTF-8?q?=E6=A1=8C=E9=9D=A2=E4=B8=8A=E8=BF=90=E8=A1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- XCoder/XCom/SerialTransport.cs | 801 ++++++++++++++++----------------- 1 file changed, 397 insertions(+), 404 deletions(-) diff --git a/XCoder/XCom/SerialTransport.cs b/XCoder/XCom/SerialTransport.cs index a37f8ab..0730da3 100644 --- a/XCoder/XCom/SerialTransport.cs +++ b/XCoder/XCom/SerialTransport.cs @@ -1,530 +1,523 @@ -using System; -using System.Collections.Generic; -using System.IO.Ports; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; +using System.IO.Ports; using Microsoft.Win32; using NewLife.Data; using NewLife.Log; using NewLife.Threading; -namespace NewLife.Net +namespace NewLife.Net; + +/// 串口传输 +/// +/// 标准例程: +/// +/// var st = new SerialTransport(); +/// st.PortName = "COM65"; // 通讯口 +/// st.FrameSize = 16; // 数据帧大小 +/// +/// st.Received += (s, e) => +/// { +/// Console.WriteLine("收到 {0}", e.ToHex()); +/// }; +/// // 开始异步操作 +/// st.Open(); +/// +/// //var buf = "01080000801A".ToHex(); +/// var buf = "0111C02C".ToHex(); +/// for (int i = 0; i < 100; i++) +/// { +/// Console.WriteLine("发送 {0}", buf.ToHex()); +/// st.Send(buf); +/// +/// Thread.Sleep(1000); +/// } +/// +/// +public class SerialTransport : DisposeBase, ITransport { - /// 串口传输 - /// - /// 标准例程: - /// - /// var st = new SerialTransport(); - /// st.PortName = "COM65"; // 通讯口 - /// st.FrameSize = 16; // 数据帧大小 - /// - /// st.Received += (s, e) => - /// { - /// Console.WriteLine("收到 {0}", e.ToHex()); - /// }; - /// // 开始异步操作 - /// st.Open(); - /// - /// //var buf = "01080000801A".ToHex(); - /// var buf = "0111C02C".ToHex(); - /// for (int i = 0; i < 100; i++) - /// { - /// Console.WriteLine("发送 {0}", buf.ToHex()); - /// st.Send(buf); - /// - /// Thread.Sleep(1000); - /// } - /// - /// - public class SerialTransport : DisposeBase, ITransport + #region 属性 + private SerialPort _Serial; + /// 串口对象 + public SerialPort Serial { - #region 属性 - private SerialPort _Serial; - /// 串口对象 - public SerialPort Serial + get { return _Serial; } + set { - get { return _Serial; } - set + _Serial = value; + if (_Serial != null) { - _Serial = value; - if (_Serial != null) - { - PortName = _Serial.PortName; - BaudRate = _Serial.BaudRate; - Parity = _Serial.Parity; - DataBits = _Serial.DataBits; - StopBits = _Serial.StopBits; - } + PortName = _Serial.PortName; + BaudRate = _Serial.BaudRate; + Parity = _Serial.Parity; + DataBits = _Serial.DataBits; + StopBits = _Serial.StopBits; } } + } - /// 端口名称。默认COM1 - public String PortName { get; set; } = "COM1"; + /// 端口名称。默认COM1 + public String PortName { get; set; } = "COM1"; - /// 波特率。默认115200 - public Int32 BaudRate { get; set; } = 115200; + /// 波特率。默认115200 + public Int32 BaudRate { get; set; } = 115200; - /// 奇偶校验位。默认None - public Parity Parity { get; set; } = Parity.None; + /// 奇偶校验位。默认None + public Parity Parity { get; set; } = Parity.None; - /// 数据位。默认8 - public Int32 DataBits { get; set; } = 8; + /// 数据位。默认8 + public Int32 DataBits { get; set; } = 8; - /// 停止位。默认One - public StopBits StopBits { get; set; } = StopBits.One; + /// 停止位。默认One + public StopBits StopBits { get; set; } = StopBits.One; - /// 超时时间。超过该大小未收到数据,说明是另一帧。默认10ms - public Int32 Timeout { get; set; } = 10; + /// 超时时间。超过该大小未收到数据,说明是另一帧。默认10ms + public Int32 Timeout { get; set; } = 10; - private String _Description; - /// 描述信息 - public String Description + private String _Description; + /// 描述信息 + public String Description + { + get { - get + if (_Description == null) { - if (_Description == null) - { - var dic = GetNames(); - if (!dic.TryGetValue(PortName, out _Description)) - _Description = ""; - } - return _Description; + var dic = GetNames(); + if (!dic.TryGetValue(PortName, out _Description)) + _Description = ""; } + return _Description; } + } - ///// 粘包处理接口 - //public IPacket Packet { get; set; } + ///// 粘包处理接口 + //public IPacket Packet { get; set; } - /// 字节超时。数据包间隔,默认20ms - public Int32 ByteTimeout { get; set; } = 20; - #endregion + /// 字节超时。数据包间隔,默认20ms + public Int32 ByteTimeout { get; set; } = 20; + #endregion - #region 构造 - /// 串口传输 - public SerialTransport() - { - // 每隔一段时间检查一次串口是否已经关闭,如果串口已经不存在,则关闭该传输口 - timer = new TimerX(CheckDisconnect, null, 3000, 3000) { Async = true }; - } + #region 构造 + /// 串口传输 + public SerialTransport() + { + // 每隔一段时间检查一次串口是否已经关闭,如果串口已经不存在,则关闭该传输口 + timer = new TimerX(CheckDisconnect, null, 3000, 3000) { Async = true }; + } - /// 销毁 - /// - //protected override void Dispose(Boolean disposing) - //{ - // base.Dispose(disposing); - - // try - // { - // if (Serial != null) Close(); - // if (timer != null) timer.Dispose(); - // } - // catch { } - //} - #endregion - - #region 方法 - /// 确保创建 - public virtual void EnsureCreate() + /// 销毁 + /// + //protected override void Dispose(Boolean disposing) + //{ + // base.Dispose(disposing); + + // try + // { + // if (Serial != null) Close(); + // if (timer != null) timer.Dispose(); + // } + // catch { } + //} + #endregion + + #region 方法 + /// 确保创建 + public virtual void EnsureCreate() + { + if (Serial == null) { - if (Serial == null) - { - Serial = new SerialPort(PortName, BaudRate, Parity, DataBits, StopBits); + Serial = new SerialPort(PortName, BaudRate, Parity, DataBits, StopBits); - _Description = null; - } + _Description = null; } + } - /// 打开 - public virtual Boolean Open() - { - EnsureCreate(); - - if (!Serial.IsOpen) - { - Serial.Open(); - if (Received != null) Serial.DataReceived += DataReceived; - } + /// 打开 + public virtual Boolean Open() + { + EnsureCreate(); - return true; + if (!Serial.IsOpen) + { + Serial.Open(); + if (Received != null) Serial.DataReceived += DataReceived; } - /// 关闭 - public virtual Boolean Close() - { - // 关闭时必须清空,否则更换属性后再次打开也无法改变属性 - var sp = Serial; - if (sp != null) - { - Serial = null; - if (Received != null) sp.DataReceived -= DataReceived; - if (sp.IsOpen) sp.Close(); + return true; + } - OnDisconnect(); - } + /// 关闭 + public virtual Boolean Close() + { + // 关闭时必须清空,否则更换属性后再次打开也无法改变属性 + var sp = Serial; + if (sp != null) + { + Serial = null; + if (Received != null) sp.DataReceived -= DataReceived; + if (sp.IsOpen) sp.Close(); - return true; + OnDisconnect(); } - #endregion - #region 发送 - /// 写入数据 - /// 数据包 - public virtual Int32 Send(Packet pk) - { - if (!Open()) return -1; + return true; + } + #endregion - WriteLog("Send:{0}", pk.ToHex()); + #region 发送 + /// 写入数据 + /// 数据包 + public virtual Int32 Send(Packet pk) + { + if (!Open()) return -1; - var sp = Serial; - lock (sp) - { - sp.Write(pk.Data, pk.Offset, pk.Count); - } + WriteLog("Send:{0}", pk.ToHex()); - return pk.Total; + var sp = Serial; + lock (sp) + { + sp.Write(pk.Data, pk.Offset, pk.Count); } - /// 异步发送数据并等待响应 - /// - /// - public virtual async Task SendAsync(Packet pk) - { - if (!Open()) return null; + return pk.Total; + } - //if (Packet == null) Packet = new PacketProvider(); + /// 异步发送数据并等待响应 + /// + /// + public virtual async Task SendAsync(Packet pk) + { + if (!Open()) return null; - //var task = Packet.Add(pk, null, Timeout); + //if (Packet == null) Packet = new PacketProvider(); - _Source = new TaskCompletionSource(); + //var task = Packet.Add(pk, null, Timeout); - if (pk != null) - { - WriteLog("SendAsync:{0}", pk.ToHex()); + _Source = new TaskCompletionSource(); - // 发送数据 - Serial.Write(pk.Data, pk.Offset, pk.Count); - } + if (pk != null) + { + WriteLog("SendAsync:{0}", pk.ToHex()); - return await _Source.Task; + // 发送数据 + Serial.Write(pk.Data, pk.Offset, pk.Count); } - /// 接收数据 - /// - public virtual Packet Receive() - { - if (!Open()) return null; + return await _Source.Task; + } - var task = SendAsync(null); - if (Timeout > 0 && !task.Wait(Timeout)) return null; + /// 接收数据 + /// + public virtual Packet Receive() + { + if (!Open()) return null; - return task.Result; - } - #endregion + var task = SendAsync(null); + if (Timeout > 0 && !task.Wait(Timeout)) return null; + + return task.Result; + } + #endregion - #region 异步接收 - void DataReceived(Object sender, SerialDataReceivedEventArgs e) + #region 异步接收 + void DataReceived(Object sender, SerialDataReceivedEventArgs e) + { + // 发送者必须保持一定间隔,每个报文不能太大,否则会因为粘包拆包而出错 + try { - // 发送者必须保持一定间隔,每个报文不能太大,否则会因为粘包拆包而出错 - try + var sp = sender as SerialPort; + WaitMore(); + if (sp.BytesToRead > 0) { - var sp = sender as SerialPort; - WaitMore(); - if (sp.BytesToRead > 0) - { - var buf = new Byte[sp.BytesToRead]; + var buf = new Byte[sp.BytesToRead]; - var count = sp.Read(buf, 0, buf.Length); - //if (count != buf.Length) buf = buf.ReadBytes(0, count); - //var ms = new MemoryStream(buf, 0, count, false); - var pk = new Packet(buf, 0, count); + var count = sp.Read(buf, 0, buf.Length); + //if (count != buf.Length) buf = buf.ReadBytes(0, count); + //var ms = new MemoryStream(buf, 0, count, false); + var pk = new Packet(buf, 0, count); - ProcessReceive(pk); - } - } - catch (Exception ex) - { - //WriteLog("Error " + ex.Message); - if (Log != null) Log.Error("DataReceived Error {0}", ex.Message); + ProcessReceive(pk); } } - - void WaitMore() + catch (Exception ex) { - var sp = Serial; - - var ms = ByteTimeout; - var end = DateTime.Now.AddMilliseconds(ms); - var count = sp.BytesToRead; - while (sp.IsOpen && end > DateTime.Now) - { - //Thread.SpinWait(1); - Thread.Sleep(ms); - if (count != sp.BytesToRead) - { - end = DateTime.Now.AddMilliseconds(ms); - count = sp.BytesToRead; - } - } + //WriteLog("Error " + ex.Message); + if (Log != null) Log.Error("DataReceived Error {0}", ex.Message); } + } + + void WaitMore() + { + var sp = Serial; - void ProcessReceive(Packet pk) + var ms = ByteTimeout; + var end = DateTime.Now.AddMilliseconds(ms); + var count = sp.BytesToRead; + while (sp.IsOpen && end > DateTime.Now) { - try + //Thread.SpinWait(1); + Thread.Sleep(ms); + if (count != sp.BytesToRead) { - //if (Packet == null) - OnReceive(pk); - //else - //{ - // // 拆包,多个包多次调用处理程序 - // foreach (var msg in Packet.Parse(pk)) - // { - // OnReceive(msg); - // } - //} - } - catch (Exception ex) - { - if (!ex.IsDisposed()) Log.Error("{0}.OnReceive {1}", PortName, ex.Message); + end = DateTime.Now.AddMilliseconds(ms); + count = sp.BytesToRead; } } + } - private TaskCompletionSource _Source; - /// 处理收到的数据。默认匹配同步接收委托 - /// - internal virtual void OnReceive(Packet pk) + void ProcessReceive(Packet pk) + { + try { - //// 同步匹配 - //if (Packet != null && Packet.Match(pk, null)) return; + //if (Packet == null) + OnReceive(pk); + //else + //{ + // // 拆包,多个包多次调用处理程序 + // foreach (var msg in Packet.Parse(pk)) + // { + // OnReceive(msg); + // } + //} + } + catch (Exception ex) + { + if (!ex.IsDisposed()) Log.Error("{0}.OnReceive {1}", PortName, ex.Message); + } + } - if (_Source != null) - { - _Source.SetResult(pk); - _Source = null; - return; - } + private TaskCompletionSource _Source; + /// 处理收到的数据。默认匹配同步接收委托 + /// + internal virtual void OnReceive(Packet pk) + { + //// 同步匹配 + //if (Packet != null && Packet.Match(pk, null)) return; - // 触发事件 - Received?.Invoke(this, new ReceivedEventArgs { Packet = pk }); + if (_Source != null) + { + _Source.SetResult(pk); + _Source = null; + return; } - /// 数据到达事件 - public event EventHandler Received; - #endregion + // 触发事件 + Received?.Invoke(this, new ReceivedEventArgs { Packet = pk }); + } - #region 自动检测串口断开 - /// 断开时触发,可能是人为断开,也可能是串口链路断开 - public event EventHandler Disconnected; + /// 数据到达事件 + public event EventHandler Received; + #endregion - Boolean isInEvent; - void OnDisconnect() + #region 自动检测串口断开 + /// 断开时触发,可能是人为断开,也可能是串口链路断开 + public event EventHandler Disconnected; + + Boolean isInEvent; + void OnDisconnect() + { + if (Disconnected != null) { - if (Disconnected != null) + // 判断是否在事件中,避免外部在断开时间中调用Close造成死循环 + if (!isInEvent) { - // 判断是否在事件中,避免外部在断开时间中调用Close造成死循环 - if (!isInEvent) - { - isInEvent = true; + isInEvent = true; - Disconnected(this, EventArgs.Empty); + Disconnected(this, EventArgs.Empty); - isInEvent = false; - } + isInEvent = false; } } + } + + TimerX timer; + /// 检查串口是否已经断开 + /// + /// FX串口异步操作有严重的泄漏缺陷,如果外部硬件长时间断开, + /// SerialPort.IsOpen检测不到,并且会无限大占用内存。 + /// + /// + void CheckDisconnect(Object state) + { + if (String.IsNullOrEmpty(PortName) || Serial == null || !Serial.IsOpen) return; - TimerX timer; - /// 检查串口是否已经断开 - /// - /// FX串口异步操作有严重的泄漏缺陷,如果外部硬件长时间断开, - /// SerialPort.IsOpen检测不到,并且会无限大占用内存。 - /// - /// - void CheckDisconnect(Object state) + // 如果端口已经不存在,则断开吧 + if (!SerialPort.GetPortNames().Contains(PortName)) { - if (String.IsNullOrEmpty(PortName) || Serial == null || !Serial.IsOpen) return; + WriteLog("串口{0}已经不存在,准备关闭!", PortName); - // 如果端口已经不存在,则断开吧 - if (!SerialPort.GetPortNames().Contains(PortName)) - { - WriteLog("串口{0}已经不存在,准备关闭!", PortName); + //OnDisconnect(); + Close(); + } + } + #endregion - //OnDisconnect(); - Close(); - } + #region 辅助 + /// 获取带有描述的串口名,没有时返回空数组 + /// + public static String[] GetPortNames() + { + var list = new List(); + foreach (var item in GetNames()) + { + list.Add(String.Format("{0}({1})", item.Key, item.Value)); } - #endregion + return list.ToArray(); + } - #region 辅助 - /// 获取带有描述的串口名,没有时返回空数组 - /// - public static String[] GetPortNames() + /// 获取串口列表,名称和描述 + /// + public static Dictionary GetNames() + { + var dic = new Dictionary(StringComparer.OrdinalIgnoreCase); + foreach (var item in SerialPort.GetPortNames()) { - var list = new List(); - foreach (var item in GetNames()) - { - list.Add(String.Format("{0}({1})", item.Key, item.Value)); - } - return list.ToArray(); + dic.Add(item, ""); } - /// 获取串口列表,名称和描述 - /// - public static Dictionary GetNames() + if (Runtime.Windows) { - var dic = new Dictionary(StringComparer.OrdinalIgnoreCase); -#if NC30 - foreach (var item in SerialPort.GetPortNames()) + using var key = Registry.LocalMachine.OpenSubKey(@"HARDWARE\DEVICEMAP\SERIALCOMM", false); + using var usb = Registry.LocalMachine.OpenSubKey(@"SYSTEM\CurrentControlSet\Enum\USB", false); + if (key != null) { - dic.Add(item, ""); - } -#else - using (var key = Registry.LocalMachine.OpenSubKey(@"HARDWARE\DEVICEMAP\SERIALCOMM", false)) - using (var usb = Registry.LocalMachine.OpenSubKey(@"SYSTEM\CurrentControlSet\Enum\USB", false)) - { - if (key != null) + foreach (var item in key.GetValueNames()) { - foreach (var item in key.GetValueNames()) - { - var name = key.GetValue(item) + ""; - var des = ""; + var name = key.GetValue(item) + ""; + var des = ""; - // 尝试枚举USB串口 - foreach (var vid in usb.GetSubKeyNames()) + // 尝试枚举USB串口 + foreach (var vid in usb.GetSubKeyNames()) + { + var usbvid = usb.OpenSubKey(vid); + foreach (var elm in usbvid.GetSubKeyNames()) { - var usbvid = usb.OpenSubKey(vid); - foreach (var elm in usbvid.GetSubKeyNames()) + var sub = usbvid.OpenSubKey(elm); + //if (sub.GetValue("Class") + "" == "Ports") { - var sub = usbvid.OpenSubKey(elm); - //if (sub.GetValue("Class") + "" == "Ports") + var FriendlyName = sub.GetValue("FriendlyName") + ""; + if (FriendlyName.Contains($"({name})")) { - var FriendlyName = sub.GetValue("FriendlyName") + ""; - if (FriendlyName.Contains($"({name})")) - { - des = FriendlyName.TrimEnd($"({name})").Trim(); - break; - } + des = FriendlyName.TrimEnd($"({name})").Trim(); + break; } } - if (!des.IsNullOrEmpty()) break; - } - - // 最后选择设备映射的串口名 - if (des.IsNullOrEmpty()) - { - des = item; - var p = item.LastIndexOf('\\'); - if (p >= 0) des = des.Substring(p + 1); } + if (!des.IsNullOrEmpty()) break; + } - //dic.Add(name, des); - // 某台机器上发现,串口有重复 - dic[name] = des; + // 最后选择设备映射的串口名 + if (des.IsNullOrEmpty()) + { + des = item; + var p = item.LastIndexOf('\\'); + if (p >= 0) des = des.Substring(p + 1); } + + //dic.Add(name, des); + // 某台机器上发现,串口有重复 + dic[name] = des; } } -#endif - - return dic; } - /// 从串口列表选择串口,支持自动选择关键字 - /// 串口名称或者描述符的关键字 - /// - public static SerialTransport Choose(String keyWord = null) - { - var ns = GetNames(); - if (ns.Count == 0) - { - Console.WriteLine("没有可用串口!"); - return null; - } + return dic; + } - var name = ""; - var des = ""; + /// 从串口列表选择串口,支持自动选择关键字 + /// 串口名称或者描述符的关键字 + /// + public static SerialTransport Choose(String keyWord = null) + { + var ns = GetNames(); + if (ns.Count == 0) + { + Console.WriteLine("没有可用串口!"); + return null; + } - Console.WriteLine("可用串口:"); - Console.ForegroundColor = ConsoleColor.Green; - foreach (var item in ns) - { - if (item.Value == "Serial0") continue; + var name = ""; + var des = ""; - if (keyWord != null && (item.Key.EqualIgnoreCase(keyWord) || item.Value.Contains(keyWord))) - { - name = item.Key; - des = item.Value; - } + Console.WriteLine("可用串口:"); + Console.ForegroundColor = ConsoleColor.Green; + foreach (var item in ns) + { + if (item.Value == "Serial0") continue; - //Console.WriteLine(item); - Console.WriteLine("{0,5}({1})", item.Key, item.Value); - } - // 没有自动选择,则默认最后一个 - if (name.IsNullOrEmpty()) + if (keyWord != null && (item.Key.EqualIgnoreCase(keyWord) || item.Value.Contains(keyWord))) { - var item = ns.Last(); name = item.Key; des = item.Value; } - while (true) - { - Console.ResetColor(); - Console.Write("请输入串口名称(默认 "); - Console.ForegroundColor = ConsoleColor.Red; - Console.Write("{0}", name); - Console.ResetColor(); - Console.Write("):"); - - var str = Console.ReadLine(); - if (str.IsNullOrEmpty()) break; - - // 只有输入有效串口名称才行 - if (ns.ContainsKey(str)) - { - name = str; - des = ns[str]; - break; - } - } - - Console.WriteLine(); - Console.Write("正在打开串口 "); - Console.ForegroundColor = ConsoleColor.Green; - Console.WriteLine("{0}({1})", name, des); + //Console.WriteLine(item); + Console.WriteLine("{0,5}({1})", item.Key, item.Value); + } + // 没有自动选择,则默认最后一个 + if (name.IsNullOrEmpty()) + { + var item = ns.Last(); + name = item.Key; + des = item.Value; + } + while (true) + { + Console.ResetColor(); + Console.Write("请输入串口名称(默认 "); + Console.ForegroundColor = ConsoleColor.Red; + Console.Write("{0}", name); Console.ResetColor(); + Console.Write("):"); - var sp = new SerialTransport - { - PortName = name - }; + var str = Console.ReadLine(); + if (str.IsNullOrEmpty()) break; - return sp; + // 只有输入有效串口名称才行 + if (ns.ContainsKey(str)) + { + name = str; + des = ns[str]; + break; + } } - #endregion - #region 日志 - /// 日志对象 - public ILog Log { get; set; } = Logger.Null; + Console.WriteLine(); + Console.Write("正在打开串口 "); + Console.ForegroundColor = ConsoleColor.Green; + Console.WriteLine("{0}({1})", name, des); - /// 输出日志 - /// - /// - public void WriteLog(String format, params Object[] args) - { - if (Log != null && Log.Enable) Log.Info(format, args); - } + Console.ResetColor(); - /// 已重载 - /// - public override String ToString() + var sp = new SerialTransport { - if (!String.IsNullOrEmpty(PortName)) - return PortName; - else - return "(SerialPort)"; - } + PortName = name + }; + return sp; + } + #endregion + + #region 日志 + /// 日志对象 + public ILog Log { get; set; } = Logger.Null; + + /// 输出日志 + /// + /// + public void WriteLog(String format, params Object[] args) + { + if (Log != null && Log.Enable) Log.Info(format, args); + } - #endregion + /// 已重载 + /// + public override String ToString() + { + if (!String.IsNullOrEmpty(PortName)) + return PortName; + else + return "(SerialPort)"; } + + + #endregion } \ No newline at end of file