升级基础组件,使用最新IPacket,其中Modbus还需要优化
大石头 authored at 2024-12-10 11:36:27
16.51 KiB
XCoder
using System.ComponentModel;
using System.Text;
using Gtk;
using NewLife.Data;
using NewLife.Log;
using NewLife.Net;
using NewLife.Threading;
using XCoder.Util;

namespace NewLife.Windows;

/// <summary>串口列表控件</summary>
[DefaultEvent("ReceivedString")]
public partial class SerialPortList : HBox
{
    #region 属性
    /// <summary>端口</summary>
    public SerialTransport Port { get; set; }

    /// <summary>选择的端口</summary>
    public String SelectedPort { get { return cbName.GetActiveObject() + ""; } set { } }

    /// <summary>收到的字节数</summary>
    public Int32 BytesOfReceived { get; set; }

    /// <summary>发送的字节数</summary>
    public Int32 BytesOfSent { get; set; }
    #endregion

    #region 构造
    /// <summary></summary>
    public SerialPortList()
    {
        InitializeComponent();
    }

    TimerX _timer;
    private void SerialPortList_Load(Object sender, EventArgs e)
    {
        LoadInfo();

        // 挂载定时器
        _timer = new TimerX(OnCheck, null, 300, 300) { Async = true };

        //if (Parent is Form frm)
        //{
        //    frm.FormClosing += frm_FormClosing;
        //}
    }

    //void frm_FormClosing(Object sender, FormClosingEventArgs e)
    //{
    //    SaveInfo();

    //    _timer.Dispose();
    //    _timer = null;

    //    if (Port != null) Port.Close();
    //}
    #endregion

    #region 加载保存信息
    /// <summary>加载配置信息</summary>
    public void LoadInfo()
    {
        ShowPorts();

        //BindMenu(mi数据位, On数据位Click, new Int32[] { 5, 6, 7, 8 });
        //BindMenu(mi停止位, On停止位Click, Enum.GetValues(typeof(StopBits)));
        //BindMenu(mi校验, On校验Click, Enum.GetValues(typeof(Parity)));

        //var arr = new Int32[] { 1200, 2400, 4800, 9600, 14400, 19200, 38400, 56000, 57600, 115200, 128000, 194000, 256000, 512000, 1024000, 2048000 };
        var bs = new List<Int32>(new Int32[] { 1200, 2400, 4800, 9600, 14400, 19200, 38400, 56000, 57600, 115200, 128000, 194000, 256000, 512000, 1024000, 2048000 });

        var cfg = SerialPortConfig.Current;
        if (cfg.BaudRate > 0 && !bs.Contains(cfg.BaudRate)) bs.Add(cfg.BaudRate);
        cbBaudrate.AppendValues(bs);

        cbName.SetActive(cfg.PortName);
        cbBaudrate.SetActive(cfg.BaudRate);
        //SetMenuItem(mi数据位, cfg.DataBits);
        //SetMenuItem(mi停止位, cfg.StopBits);
        //SetMenuItem(mi校验, cfg.Parity);

        //cbEncoding.DataSource = new String[] { Encoding.UTF8.WebName, Encoding.ASCII.WebName, Encoding.UTF8.WebName };
        // 添加编码子菜单
        var encs = new Encoding[] { Encoding.UTF8, Encoding.ASCII, Encoding.UTF7, Encoding.Unicode, Encoding.BigEndianUnicode/*, Encoding.UTF32,Encoding.GetEncoding("GB2312") */};
        var list = new List<Encoding>(encs);
        // 暂时不用这么多编码
        //list.AddRange(Encoding.GetEncodings().Select(e => e.GetEncoding()).Where(e => !encs.Contains(e)));
        var k = 0;
        //foreach (var item in list)
        //{
        //    if (k++ == encs.Length)
        //    {
        //        var sep = new ToolStripSeparator();
        //        mi字符串编码.DropDownItems.Add(sep);
        //    }
        //    var ti = mi字符串编码.DropDownItems.Add(item.EncodingName) as ToolStripMenuItem;
        //    ti.Name = item.WebName;
        //    ti.Tag = item;
        //    ti.Checked = item.WebName.EqualIgnoreCase(cfg.WebEncoding);
        //    ti.Click += On编码Click;
        //}

        //miHEX编码接收.Checked = cfg.HexShow;
        //mi字符串编码.Checked = !cfg.HexShow;
        //miHex不换行.Checked = !cfg.HexNewLine;
        //miHex自动换行.Checked = cfg.HexNewLine;
        //miHEX编码发送.Checked = cfg.HexSend;
        //miDTR.Checked = cfg.DtrEnable;
        //miRTS.Checked = cfg.RtsEnable;
        //miBreak.Checked = cfg.BreakState;
    }

    /// <summary>保存配置信息</summary>
    public void SaveInfo()
    {
        try
        {
            var cfg = SerialPortConfig.Current;
            cfg.PortName = cbName.GetActiveObject() + "";
            if (cbBaudrate.GetActiveObject() != null)
                cfg.BaudRate = cbBaudrate.GetActiveObject().ToInt();
            //else
            //    cfg.BaudRate = cbBaudrate.ToInt();
            //cfg.DataBits = (Int32)cbDataBit.GetActiveObject();
            //cfg.StopBits = (StopBits)cbStopBit.GetActiveObject();
            //cfg.Parity = (Parity)cbParity.GetActiveObject();
            //cfg.Encoding = (Encoding)cbEncoding.GetActiveObject();
            //cfg.WebEncoding = cbEncoding.GetActiveObject() + "";

            //cfg.HexSend = chkHEXSend.Checked;
            //cfg.HexShow = chkHEXShow.Checked;

            cfg.Save();
        }
        catch (Exception ex)
        {
            XTrace.WriteException(ex);
        }
    }

    String _ports = null;
    DateTime _nextport = DateTime.MinValue;
    /// <summary>下拉框显示串口</summary>
    public void ShowPorts()
    {
        if (_nextport > DateTime.Now) return;
        _nextport = DateTime.Now.AddSeconds(1);

        var ps = SerialTransport.GetPortNames();
        var str = String.Join(",", ps);
        // 如果端口有所改变,则重新绑定
        if (_ports != str)
        {
            if (_ports != null) "串口有改变".SpeechTip();

            _ports = str;

            Application.Invoke((s, e) =>
            {
                var old = cbName.GetActiveObject() + "";
                cbName.AppendValues(ps.ToList());
                if (!String.IsNullOrEmpty(old) && Array.IndexOf(ps, old) >= 0) cbName.SetActive(old);
            });
        }
    }
    #endregion

    #region 菜单设置
    ///// <summary>右键菜单</summary>
    //public ContextMenuStrip Menu { get { return contextMenuStrip1; } }

    //void On数据位Click(Object sender, EventArgs e)
    //{
    //    SelectMenu(sender);

    //    var mi = sender as ToolStripMenuItem;
    //    var cfg = SerialPortConfig.Current;
    //    cfg.DataBits = (Int32)mi.Tag;
    //}

    //void On停止位Click(Object sender, EventArgs e)
    //{
    //    SelectMenu(sender);

    //    var mi = sender as ToolStripMenuItem;
    //    var cfg = SerialPortConfig.Current;
    //    cfg.StopBits = (StopBits)mi.Tag;
    //}

    //void On校验Click(Object sender, EventArgs e)
    //{
    //    SelectMenu(sender);

    //    var mi = sender as ToolStripMenuItem;
    //    var cfg = SerialPortConfig.Current;
    //    cfg.Parity = (Parity)mi.Tag;
    //}

    //void On编码Click(Object sender, EventArgs e)
    //{
    //    // 不要选其它
    //    var mi = sender as ToolStripMenuItem;
    //    if (mi == null) return;
    //    //foreach (ToolStripMenuItem item in (mi.OwnerItem as ToolStripMenuItem).DropDownItems)
    //    //{
    //    //    item.Checked = item == mi;
    //    //}
    //    SelectMenu(sender);

    //    // 保存编码
    //    var cfg = SerialPortConfig.Current;
    //    cfg.WebEncoding = mi.Name;
    //}

    //private void mi字符串编码_Click(Object sender, EventArgs e)
    //{
    //    var cfg = SerialPortConfig.Current;
    //    //cfg.HexShow = miHEX编码接收.Checked = !mi字符串编码.Checked;
    //    cfg.HexShow = SelectMenu(sender, mi字符串编码, miHEX编码接收) == 1;

    //    // 收起菜单
    //    contextMenuStrip1.Hide();
    //}

    //private void miHex自动换行_Click(Object sender, EventArgs e)
    //{
    //    var cfg = SerialPortConfig.Current;
    //    //cfg.HexNewLine = ti.Tag.ToBoolean();
    //    cfg.HexNewLine = SelectMenu(sender) == 1;
    //}

    //private void miHEX编码发送_Click(Object sender, EventArgs e)
    //{
    //    var cfg = SerialPortConfig.Current;
    //    miHEX编码发送.Checked = !miHEX编码发送.Checked;
    //    cfg.HexSend = miHEX编码发送.Checked;
    //}

    //private void miDTR_Click(Object sender, EventArgs e)
    //{
    //    var mi = sender as ToolStripMenuItem;
    //    mi.Checked = !mi.Checked;
    //}
    #endregion

    #region 窗体
    /// <summary>连接串口</summary>
    public void Connect()
    {
        var name = cbName.GetActiveObject() + "";
        if (String.IsNullOrEmpty(name))
        {
            "请选择串口".SpeechTip();
            MessageBox.Show("请选择串口!");
            //cbName.Focus();
            return;
        }
        var p = name.IndexOf("(");
        if (p > 0) name = name.Substring(0, p);

        SaveInfo();
        var cfg = SerialPortConfig.Current;

        var pt = Port;
        if (pt != null)
        {
            // 如果上次没有关闭,则关闭
            try
            {
                pt.Close();
            }
            catch
            {
                pt = null;
            }
        }
        if (pt == null) pt = new SerialTransport();
        pt.PortName = name;
        pt.BaudRate = cfg.BaudRate;
        pt.Parity = cfg.Parity;
        pt.DataBits = cfg.DataBits;
        pt.StopBits = cfg.StopBits;

        //pt.Open();

        pt.Disconnected += Port_Disconnected;
        pt.Received += OnReceived;
        pt.Open();

        //pt.EnsureCreate();

        Port = pt;

        var sp = pt.Serial;
        // 这几个需要打开端口以后才能设置
        //try
        //{
        //    if (sp.DtrEnable != miDTR.Checked) sp.DtrEnable = miDTR.Checked;
        //    if (sp.RtsEnable != miRTS.Checked) sp.RtsEnable = miRTS.Checked;
        //    if (sp.BreakState != miBreak.Checked) sp.BreakState = miBreak.Checked;
        //}
        //catch { }

        Sensitive = false;
    }

    void Port_Disconnected(Object sender, EventArgs e)
    {
        Disconnect();
    }

    /// <summary>断开串口连接</summary>
    public void Disconnect()
    {
        var pt = Port;
        if (pt != null)
        {
            Port = null;
            pt.Disconnected -= Port_Disconnected;
            pt.Received -= OnReceived;
            pt.Close();
        }

        ShowPorts();

        Sensitive = true;
    }

    private void OnCheck(Object state)
    {
        if (Sensitive) ShowPorts();
    }
    #endregion

    #region 菜单辅助
    //ToolStripItem FindMenu(String name)
    //{
    //    return contextMenuStrip1.Items.Find(name, false)[0];
    //}

    //void BindMenu(ToolStripMenuItem ti, EventHandler handler, IEnumerable em)
    //{
    //    ti.DropDownItems.Clear();
    //    foreach (var item in em)
    //    {
    //        var tsi = ti.DropDownItems.Add(item + "");
    //        tsi.Tag = item;
    //        tsi.Click += handler;
    //    }
    //}

    //void SetMenuItem(ToolStripMenuItem ti, Object value)
    //{
    //    foreach (ToolStripMenuItem item in ti.DropDownItems)
    //    {
    //        item.Checked = item + "" == value + "";
    //    }
    //}

    ///// <summary>在一个菜单列表中点击某一个菜单,改变菜单选择并返回选中的菜单索引</summary>
    ///// <param name="sender"></param>
    ///// <param name="ms"></param>
    //Int32 SelectMenu(Object sender, params ToolStripMenuItem[] ms)
    //{
    //    var mi = sender as ToolStripMenuItem;
    //    if (mi == null) return -1;

    //    var idx = -1;
    //    for (var i = 0; i < ms.Length; i++)
    //    {
    //        if (ms[i] == mi)
    //        {
    //            ms[i].Checked = true;
    //            idx = i;
    //        }
    //        else
    //            ms[i].Checked = false;
    //    }
    //    return idx;
    //}

    ///// <summary>在同级菜单中选择</summary>
    ///// <param name="sender"></param>
    ///// <param name="tsi"></param>
    ///// <returns></returns>
    //Int32 SelectMenu(Object sender, ToolStripItem tsi = null)
    //{
    //    if (tsi == null) tsi = (sender as ToolStripMenuItem).OwnerItem;

    //    var ms = (tsi as ToolStripMenuItem).DropDownItems;
    //    if (ms == null) return -1;

    //    var arr = new ToolStripMenuItem[ms.Count];
    //    ms.CopyTo(arr, 0);

    //    return SelectMenu(sender, arr);
    //}
    #endregion

    #region 收发数据
    /// <summary>发送字节数组</summary>
    /// <param name="data"></param>
    public void Send(Byte[] data)
    {
        if (data == null || data.Length <= 0) return;

        BytesOfSent += data.Length;

        Port.Send(data);
    }

    /// <summary>发送字符串。根据配置进行十六进制编码</summary>
    /// <param name="str"></param>
    /// <returns>发送字节数</returns>
    public Int32 Send(String str)
    {
        var cfg = SerialPortConfig.Current;
        // 16进制发送
        Byte[] data = null;
        if (cfg.HexSend)
            data = str.ToHex();
        else
            data = cfg.Encoding.GetBytes(str);

        Send(data);

        return data.Length;
    }

    //public event SerialReceived Received;
    /// <summary>收到数据时触发。第一个参数是数据,第二个参数返回是否继续往下传递数据</summary>
    [Browsable(true)]
    [EditorBrowsable(EditorBrowsableState.Always)]
    public event EventHandler<BufferEventArgs> Received;

    /// <summary>收到数据时转为字符串后触发该事件。注意字符串编码和十六进制编码。</summary>
    /// <remarks>如果需要收到的数据,可直接在<seealso cref="Port"/>上挂载事件</remarks>
    [Browsable(true)]
    [EditorBrowsable(EditorBrowsableState.Always)]
    public event EventHandler<StringEventArgs> ReceivedString;

    MemoryStream _stream;
    StreamReader _reader;
    void OnReceived(Object sender, ReceivedEventArgs e)
    {
        var data = e.Packet.ReadBytes();
        if (data == null || data.Length < 1) return;

        BytesOfReceived += data.Length;

        // 处理数据委托
        if (Received != null)
        {
            var e2 = new BufferEventArgs { Value = data };
            Received(this, e2);
            if (!e2.Cancel) return;
            // 外部可能修改了数据
            data = e2.Value;
            //if (!BufferEventArgs.Invoke(Received, data)) return null;
        }

        // 处理字符串委托
        if (ReceivedString == null) return;

        var cfg = SerialPortConfig.Current;

        var line = "";
        if (cfg.HexShow)
        {
            if (data.Length > 32)
                line = $"[{data.Length}]=\r\n{data.ToHex("-", 32)}";
            else
                line = $"[{data.Length}]={data.ToHex("-", 32)}";
            if (cfg.HexNewLine) line += Environment.NewLine;
        }
        else
        {
            line = cfg.Encoding.GetString(data);
            if (_stream == null)
                _stream = new MemoryStream();
            else if (_stream.Length > 10 * 1024 && _stream.Position == _stream.Length) // 达到最大大小时,从头开始使用
                _stream = new MemoryStream();
            _stream.Write(data);
            _stream.Seek(-1 * data.Length, SeekOrigin.Current);

            if (_reader == null ||
                _reader.BaseStream != _stream ||
                _reader.CurrentEncoding != cfg.Encoding)
                _reader = new StreamReader(_stream, cfg.Encoding);
            line = _reader.ReadToEnd();
            // 替换掉无效数据
            line = line.Replace("\0", null);
        }

        ReceivedString?.Invoke(this, new StringEventArgs { Value = line });
    }
    #endregion

    #region 收发统计
    /// <summary>清空发送</summary>
    public void ClearSend()
    {
        BytesOfSent = 0;

        var sp = Port;
        if (sp != null)
        {
            try
            {
                if (sp.Serial != null) sp.Serial.DiscardOutBuffer();
            }
            catch { }
        }
    }

    /// <summary>清空接收</summary>
    public void ClearReceive()
    {
        BytesOfReceived = 0;
        if (_stream != null) _stream.SetLength(0);

        var sp = Port;
        if (sp != null)
        {
            try
            {
                if (sp.Serial != null) sp.Serial.DiscardInBuffer();
            }
            catch { }
        }
    }
    #endregion
}

/// <summary>字符串事件参数</summary>
public class StringEventArgs : EventArgs
{
    private String _Value;
    /// <summary>字符串值</summary>
    public String Value { get { return _Value; } set { _Value = value; } }
}

/// <summary>缓冲区事件参数</summary>
public class BufferEventArgs : CancelEventArgs
{
    private Byte[] _Value;
    /// <summary>缓冲区数据</summary>
    public Byte[] Value { get { return _Value; } set { _Value = value; } }

    //public static Boolean Invoke(EventHandler<BufferEventArgs> handler, Object sender, Byte[] buf)
    //{
    //    var e = new BufferEventArgs { Value = buf, Cancel = false };
    //    handler(sender, e);
    //    return e.Cancel;
    //}
}