串口通信

发布时间 2024-01-08 10:54:22作者: TanZhiWei

1 封装串口通信

using System;
using System.IO.Ports;
using System.Linq;

namespace Business
{

    /// <summary>
    /// 串口通信服务
    /// </summary>
    public class SerialPortService
    {
        //串口
        private readonly SerialPort _serialPort = new SerialPort();

        /// <summary>
        /// 串口服务构造
        /// </summary>
        ///<remarks>设备未固定通信串口</remarks>
        /// <param name="baudRate">比特率</param>
        /// <param name="dataBits">数据位数</param>
        /// <param name="stopBits">停止位</param>
        /// <param name="parity">校验位</param>
        /// <param name="handshake">握手协议</param>
        public SerialPortService(int baudRate, int dataBits, StopBits stopBits, Parity parity, Handshake handshake)
        {
            _serialPort.BaudRate = baudRate;
            _serialPort.DataBits = dataBits;
            _serialPort.StopBits = stopBits;
            _serialPort.Parity = parity;
            _serialPort.Handshake = handshake;
            _serialPort.DataReceived += OnDataReceived;
            _serialPort.ErrorReceived += OnErrorReceived;
        }

        /// <summary>
        /// 串口通信服务构造
        /// </summary>
        /// <remarks>设备固定通信串口</remarks>
        /// <param name="portName">串口名称</param>
        /// <param name="baudRate">比特率</param>
        /// <param name="dataBits">数据位数</param>
        /// <param name="stopBits">停止位</param>
        /// <param name="parity">校验位</param>
        /// <param name="handshake">握手协议</param>
        public SerialPortService(string portName, int baudRate, int dataBits, StopBits stopBits, Parity parity, Handshake handshake)
            : this(baudRate, dataBits, stopBits, parity, handshake)
        {
            var portNames = SerialPort.GetPortNames();
            if (!portNames.Any(x => x.Equals(portName)))
                throw new NotSupportedException($"无法找到串口名为{portName}的串口");
            _serialPort.PortName = portName;
        }

        /// <summary>
        /// 设置串口绑定设备名称
        /// </summary>
        /// <param name="portName"></param>
        public void SetPortName(string portName)
        {
            _serialPort.PortName = portName;
        }

        /// <summary>
        /// 获取串口绑定设备名称
        /// </summary>
        public string GetPortName()
        {
           return _serialPort.PortName;
        }

        /// <summary>
        /// 停止
        /// </summary>
        public void Close()
        {
            if (!_serialPort.IsOpen) return;
            _serialPort.Close();
        }

        /// <summary>
        /// 开始
        /// </summary>
        public void Open()
        {
            if (_serialPort.IsOpen) return;
            _serialPort.Open();
        }

        /// <summary>
        /// 接收数据
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnDataReceived(object sender, SerialDataReceivedEventArgs e)
        {
            try
            {
                if (_serialPort.BytesToRead == 0) return;
                var receivedData = new byte[_serialPort.BytesToRead];
                _serialPort.Read(receivedData, 0, receivedData.Length);
                var hex = StringHexHelper.ByteToHexStr(receivedData, 0, receivedData.Length);
                DataReceived?.Invoke(this, hex);
            }
            catch (Exception exception)
            {
                ErrorReceived?.Invoke(sender, $"{_serialPort.PortName} 数据读取异常: {exception.Message}");
            }
        }

        /// <summary>
        /// 接收错误
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnErrorReceived(object sender, SerialErrorReceivedEventArgs e)
        {
            ErrorReceived?.Invoke(sender, e.EventType.ToString());
        }

        /// <summary>
        /// 发送Hex格式的数据
        /// </summary>
        /// <param name="text"></param>
        public void WriteHex(string text)
        {
            if (!_serialPort.IsOpen) return;
            var textBytes = StringHexHelper.StrToHexBytes(text);
            _serialPort.Write(textBytes, 0, textBytes.Length);
            DataSent?.Invoke(this, text);
        }

        /// <summary>
        /// 接收到完成的事件
        /// </summary>
        public event EventHandler<string> DataReceived;
        /// <summary>
        /// 接收到错误的事件
        /// </summary>
        public event EventHandler<string> ErrorReceived;
        /// <summary>
        /// 发送完成事件之后触发
        /// </summary>
        public event EventHandler<string> DataSent;
    }
}

2 通信进制转换

using System;
using System.Text;

namespace Business
{
    /// <summary>
    /// 字符串和16进制字符串转换
    /// </summary>
    internal class StringHexHelper
    {
        /// <summary>
        /// 字符串转为16进制字符串
        /// </summary>
        /// <param name="s"></param>
        /// <param name="encode"></param>
        /// <returns></returns>
        public static string StringToHexString(string s, Encoding encode)
        {
            byte[] b = encode.GetBytes(s);//按照指定编码将string编程字节数组
            string result = string.Empty;
            for (int i = 0; i < b.Length; i++)//逐字节变为16进制字符,以%隔开
            {
                result += "%" + Convert.ToString(b[i], 16);
            }
            return result;
        }

        /// <summary>
        /// 16进制字符串转为字符串
        /// </summary>
        /// <param name="hs"></param>
        /// <param name="encode"></param>
        /// <returns></returns>
        public static string HexStringToString(string hs, Encoding encode)
        {
            //以%分割字符串,并去掉空字符
            string[] chars = hs.Split(new char[] { '%' }, StringSplitOptions.RemoveEmptyEntries);
            byte[] b = new byte[chars.Length];
            //逐个字符变为16进制字节数据
            for (int i = 0; i < chars.Length; i++)
            {
                b[i] = Convert.ToByte(chars[i], 16);
            }
            //按照指定编码将字节数组变为字符串
            return encode.GetString(b);
        }
        /// <summary>
        /// 字符串转换16进制字节数组
        /// </summary>
        /// <param name="hexString"></param>
        /// <returns></returns>

        public static byte[] StrToHexBytes(string hexString)
        {

            //清除所有空格
            hexString = hexString.Replace(" ", "");
            //若字符个数为奇数,补一个0
            hexString += hexString.Length % 2 != 0 ? "0" : "";

            byte[] result = new byte[hexString.Length / 2];
            for (int i = 0, c = result.Length; i < c; i++)
            {
                result[i] = Convert.ToByte(hexString.Substring(i * 2, 2), 16);
            }
            return result;
        }

        /// <summary>
        /// 字节数组转16进制字符串
        /// </summary>
        /// <param name="bytes"></param>
        /// <returns></returns>
        public static string ByteToHexStr(byte[] bytes, int start = 0, int length = 10)
        {
            var returnStr = "";
            if (bytes == null) return string.IsNullOrEmpty(returnStr) ? "0" : returnStr;
            for (var i = start; i < bytes.Length && i < length; i++)
            {
                var aa = bytes[i].ToString("X2");

                returnStr += aa;
            }
            return string.IsNullOrEmpty(returnStr) ? "0" : returnStr;
        }
    }
}

3 确认通信可用的串口

using System;
using System.IO.Ports;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace MagicWindowsCenter.Business
{
    /// <summary>
    /// 设备服务:用于获取设备模型、Bios Sn
    /// </summary>
    internal class DeviceService
    {
        //串口通信服务
        private readonly SerialPortService _serialPortService;
        //缓冲接收包
        private readonly StringBuilder _receivedBuffer = new StringBuilder();
        //同步发送到接收信号
        private readonly AutoResetEvent _sendReceiveSignal = new AutoResetEvent(false);
        //找到Jd98寸通信COM端口
        private bool _isFoundCom = false;
        public DeviceService()
        {
            _serialPortService = new SerialPortService(9600, 8, StopBits.One, Parity.None, Handshake.None);
            _serialPortService.DataReceived += OnDataReceived;
            _serialPortService.ErrorReceived += OnErrorReceived;
            _serialPortService.DataSent += OnDataSent;
        }

        #region 私有方案

        /// <summary>
        /// 通过通信命令获取串口通信的端口
        /// </summary>
        /// <param name="commands"></param>
        /// <exception cref="NotSupportedException"></exception>
        private async Task<string> GetPortByProtocolAsync(string commands)
        {
            return await Task.Run(() =>
            {
                var portNames = SerialPort.GetPortNames();
                if (!portNames.Any())
                    throw new NotSupportedException($"本地设备不存在串口设备");

                Console.WriteLine("通过命令{commands}  轮询获取对应的串口");
                foreach (var portName in portNames)
                {
                    try
                    {
                        _serialPortService.SetPortName(portName);
                        _serialPortService.Open();
                        _serialPortService.WriteHex(commands);
                        if (_sendReceiveSignal.WaitOne(1500))
                        {
                            Console.WriteLine($"通过命令{commands} 获取串口: {portName}");
                            return portName;
                        }
                        Console.WriteLine($"通过命令{commands} 获取串口: {portName} -->超时");
                    }
                    catch (Exception e)
                    {
                        Console.WriteLine($"通过命令{commands} 获取串口: {portName} -->错误:{e.Message}");
                    }
                    finally
                    {
                        _serialPortService.Close();
                    }
                }
                return string.Empty;
            });
        }

        private void OnDataSent(object sender, string sendData)
        {
            Console.WriteLine($"已发送串口信息:{sendData}");
        }

        private void OnErrorReceived(object sender, string e)
        {
            Console.WriteLine($"接收串口错误信息: {e}");
        }

        private void OnDataReceived(object sender, string data)
        {
            _receivedBuffer.Append(data);
            var packet = _receivedBuffer.ToString();
            var isOK = packet.StartsWith(ProtocolStart) && packet.EndsWith(ProtocolEnd);
            if (isOK)
            {
                Console.WriteLine($"接收串口信息: {packet}");
                _sendReceiveSignal.Set();
                // 清除已处理的数据
                _receivedBuffer.Clear();
            }
        }

        /// <summary>
        ///开启串口
        /// </summary>
        /// <returns></returns>
        private async Task<bool> OpenSerialPortAsync()
        {
            try
            {
                if (_isFoundCom)
                {
                    _serialPortService.Open();
                    Console.WriteLine($"打开串口: {_serialPortService.GetPortName()}");
                    return true;
                }

                var portName = await GetPortByProtocolAsync(Protocol98ObtainMuteStatus);
                if (string.IsNullOrWhiteSpace(portName)) return false;
                _serialPortService.SetPortName(portName);
                _serialPortService.Open();
                Console.WriteLine($"打开串口: {portName}");
                _isFoundCom = true;
                return true;
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
            }
            return false;
        }

        /// <summary>
        /// 关闭串口
        /// </summary>
        private void CloseSerialPort()
        {
            _serialPortService.Close();
        }
        #endregion

        /// <summary>
        /// 获取设备型号
        /// </summary>
        /// <returns></returns>
        public async Task<(bool success, string deviceModel)> GetDeviceModelAsync()
        {
            try
            {
                var open = await OpenSerialPortAsync();
                if (open)
                {
                    return (true, "XX-XX");
                }

                return (false, string.Empty);
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
                return (false, string.Empty);
            }
            finally
            {
                CloseSerialPort();
            }
        }

        /// <summary>
        /// 获取Bios Sn
        /// </summary>
        /// <returns></returns>
        public async Task<(bool success, string biosSn)> GetDeviceBiosSnAsync()
        {
            try
            {
                var open = await OpenSerialPortAsync();
                if (!open) return (false, string.Empty);

                var biosSn = "XXXXXXXXX";
                return (true, biosSn);
            }
            catch (Exception e)
            {
                MagicCenters.Log.Error(e);
                return (false, string.Empty);
            }
            finally
            {
                CloseSerialPort();
            }
        }

        //协议头
        private const string ProtocolStart = "FE";

        //协议尾
        private const string ProtocolEnd = "CF";

        //获取静音状态:确认串口
        private const string Protocol98ObtainMuteStatus = "FE XX XX CF";
    }
}