编写水文专业串口通讯软件的开发经历

news2024/11/28 14:49:26

编写水文专业串口通讯软件的开发经历

  • 一、关于开发 YAC9900 水位雨量 RTU 通讯软件
  • 二、软件开发遇到的问题和困难
    • 1、开发架构的适应
    • 2、开发语言的学习
    • 3、.net core 8 架构中串口构建的难点
    • 4、YAC9900 水位雨量 RTU 通讯软件开发中的 UI 冻结
  • 三、发现问题解决问题的具体办法
    • 1、预置是否没有执行完 invoke 的 bool 开关,是否关闭串口的 bool 开关,是否连续发送命令的 bool 开关
    • 2、在串口打开或关闭中处理上面第一项的 bool 关系
    • 3、在串口SerialDataReceivedEventHandler(GetMessageFromEquipment)事件中处理中断
  • 四、程序界面

一、关于开发 YAC9900 水位雨量 RTU 通讯软件

YAC9900 水位雨量 RTU 是长江一方公司开发的一款用于水文测量的水位雨量记录 RTU,能接入多种水位传感器。新版 YAC9900 主板重新设计后功能强大,但用于 YAC9900 通讯和设置参数的软件很旧,尽管用起来不错。于是开发一款新的软件,采用 Microsoft Visual Studio C# 开发,分别采用 .net core 8 架构和 .net Framework 4.8 进行编译。

二、软件开发遇到的问题和困难

1、开发架构的适应

以前学习 .net 开发,都是在 .net Framework 4.8 进行,这次迁移到 .net core 8 架构,学习了不少知识。.net core 8 语言更加简练和方便,提示更加全面。唯一的遗憾是编译后,产生的库文件太多,尽管程序用到库不多,但 Visual Studio 还没有智能到只生成程序依赖的库,所以一股脑的把很多库都给塞进了编译输出目录,其中很多都是不需要的库。.net core 8 架构可以编译发布产生单文件执行软件,这个单文件也是一股脑的把很多库都给包进了编译的单文件,导致单文件有 145M,其实不包含库程序不到 1M。

2、开发语言的学习

.net core 8 架构中,学习了很多,如 ? 和 ?? 运算符、三元条件运算符,索引和范围范围运算符 […index],替代了很多 if else 、 Substring 、IndexOf、LastIndexOf 语句,包括检索字符串 Contains 语句等等。学习了与 .net Framework 4.8 很多的不同点。

3、.net core 8 架构中串口构建的难点

.net core 8 架构中,System.IO.Ports 组件不再像 .net Framework 那样内置,需要通过管理 NuGet 程序包下载。高版本的.net core 8 架构更多涉及软件的安全性能,所以在串口访问中,对于数据处理的结果,运用到 UI 界面,不能再像 Visual Studio 2017 以前那样处理,处理不好就导致程序界面冻结、卡死、死锁,因为 UI 界面的刷新必须使用和考虑线程和委托的开始和终结。如何让 UI 主线程与串口通讯线程和委托互不影响和干扰,就很重要了。

4、YAC9900 水位雨量 RTU 通讯软件开发中的 UI 冻结

由于软件开发中采用各种通讯指令连续多发,在关闭串口或窗口时,极易发生 UI 冻结,软件死锁卡死,只能在任务管理器中终结进程。原因在于窗口通讯线程任务在进行中,没有处理好中断任务,导致线程打架引起程序 UI 冻结。

三、发现问题解决问题的具体办法

出现最大的问题就是 UI 界面刷新和界面冻结,关键在于串口的事件 SerialDataReceivedEventHandler 和 UI 界面刷新的委托 Invoke 处理完善。问题就得到完美的解决。

1、预置是否没有执行完 invoke 的 bool 开关,是否关闭串口的 bool 开关,是否连续发送命令的 bool 开关

  public partial class Form1 : Form

    {
        string[] StationType = new string[] { "雨量站", "并行水位站", "并行水文站", "串行水位站", "串行水文站", "水温站" };
        string[] Channel = new string[] { "无效", "PSTN", "北斗卫星", "GSM", "GPRS" };
        string[] DebugMsg = new string[] { "打开", "关闭" };
        string[] SensorType = new string[] {"SDI-12 WL3100 (HS40)","SDI-12 WFX-40 (伟思浮子式)","RS485 WFX-40 (伟思浮子式)","RS485 OTT (德国HACH)",
            "RS485 ISO","Sens","RS485 MPM (麦克压阻式)","RS485 Tem","Sens8 (武汉环宇压阻式)","VEGA","Sens10 (XYJ固件VEGAM)","Sens11 (XYJ固件HXDLS)",
            "VEGAM (XYJ固件HXRad)","Sens13 (XYJ固件WFX40G)","Sens14","Sens15","Sens16","Sens17","Sens18"};
        private static SerialPort serialPort = new SerialPort();

        private bool WhenInvokeg = false;//是否没有执行完invoke相关操作  
        private bool closing = false;//是否正在关闭串口,执行Application.DoEvents,并阻止再次invoke
        private bool SendCommand = false;//是否连续发送命令

        public Form1()
        {
            InitializeComponent();
            InitializeCustom();
        }
    
  }    

2、在串口打开或关闭中处理上面第一项的 bool 关系

关键在于及时取消连续发送命令 SendCommand = false,并中断串口 GetMessageFromEquipment 事件继续 closing = true 。

        /// <summary>打开串口过程</summary>
        private void OpenPort()
        {
            try
            {
                if (serialPort != null && serialPort.IsOpen)
                {
                    SendCommand = false;//取消连续发送命令
                    closing = true;//是要关闭串口,中断串口 GetMessageFromEquipment 事件继续

                    serialPort.DataReceived -= GetMessageFromEquipment;
                    while (WhenInvokeg) Application.DoEvents();//执行完 invoke 才能关闭串口

                    serialPort.Close();
                    closing = false;//

                    StatusMessage2.Text = "";
                    StatusMessage4.Text = "已经关闭端口 " + serialPort.PortName;
                    StatusMessage6.Text = "可以将消息框内容转换为16进制了";
                    OpenPortToolButton.Image = Resources.Close;
                }
                else
                {
                    if (serialPort == null) serialPort = new SerialPort();//如果不存在则新建端口

                    serialPort.ReadBufferSize = 4096;//缓冲大小
                    serialPort.WriteBufferSize = 4096;//缓冲大小
                    serialPort.PortName = _PortName; // 设置串口名称
                    serialPort.BaudRate = _BaudRate; // 设置波特率
                    serialPort.Parity = _Parity; // 设置奇偶校验
                    serialPort.DataBits = _DataBit; // 设置数据位数
                    serialPort.StopBits = _StopBits; // 设置停止位
                    serialPort.Handshake = _Handshake; // 设置握手协议
                                                       //serialPort.ReadIntervalTimeout = 100;
                                                       // serialPort.NewLine = "\r\n";//解释 ReadLine( )和WriteLine( )方法调用结束的值    默认值“\n”
                                                       //serialPort.RtsEnable = true;
                                                       //  serialPort.Encoding = Encoding.GetEncoding("iso-8859-1"); //支持汉字显示//"GB2312"//"iso-8859-1"
                    serialPort.DataReceived += new SerialDataReceivedEventHandler(GetMessageFromEquipment);

                    serialPort.Open(); // 打开串口

                    StatusMessage2.Text = "";
                    StatusMessage4.Text = "已经打开端口 " + serialPort.PortName;
                    StatusMessage6.Text = "";
                    OpenPortToolButton.Image = Resources.Open;
                }
            }
            catch (Exception ex)
            {
                if (serialPort == null) serialPort = new SerialPort();//如果不存在则新建端口
                serialPort.PortName = _PortName; // 设置串口名称
                serialPort.BaudRate = _BaudRate; // 设置波特率
                StatusMessage2.Text = ex.Message;
                //StatusMessage4.Text = Messaging(ex.Message);
                // 处理异常消息(VS2022使用Trace进行调试显示)
                //Trace.WriteLine(ex.Source); Trace.WriteLine(ex.StackTrace); Trace.WriteLine(ex.Message);
                // Trace.WriteLine(ex.GetType().Name);Trace.WriteLine(ex.ToString());
                //MessageBox.Show(ex.Message + "\r" + ex.Source + "\r" + ex.StackTrace, "错误消息");
            }

        }

3、在串口SerialDataReceivedEventHandler(GetMessageFromEquipment)事件中处理中断

关键在于 closing = true 中断串口 GetMessageFromEquipment 事件继续委托线程。使 WhenInvokeg = false 不再发生委托。

       private void GetMessageFromEquipment(object sender, SerialDataReceivedEventArgs e)//从设备中获取信息(串口)
        {
            if (closing) return;//如果正在关闭,忽略操作,直接返回
            string PortMessage = "";//串口消息
            WhenInvokeg = true;//设置在委托调用标记,已经开始接收数据
            if (InvokeRequired)
            {//更新UI的同步委托
             //BeginInvoke(new Action(() =>      
                Invoke(new Action(() =>
                //  Invoke((EventHandler)(delegate
                {
                    try
                    {   // 更新UI的代码
                        StatusMessage4.Text = "串口正在通讯,线程委托正在更新UI界面!";
                        Application.DoEvents();
                        Delayed(PortBufferInterval);//等待缓冲数据
                        PortMessage = serialPort.ReadLine().Trim();//去前后空格和回车后的端口消息 ,读行
                                                                   // int nums = serialPort.BytesToRead;
                                                                   // byte[] receiveBytes = new byte[nums];
                                                                   // serialPort.Read(receiveBytes, 0, nums);//读字节
                                                                   // PortMessage = Encoding.ASCII.GetString(receiveBytes);
                        if (PortMessage.Length > 0)
                        {
                            switch (TabWorkbenches.SelectedIndex)
                            {
                                case 0:
                                case 1:
                                    Yac9900PortMessaging(PortMessage);//消息处理和界面更新
                                    break;
                                case 2:
                                    break;
                                case 3:
                                    break;
                            }

                        }
                    }
                    catch (Exception ex)
                    {   // 处理异常
                        StatusMessage2.Text = "程序线程上执行的委托异常!" + ex.Message;
                        //Trace.WriteLine(ex.Message);//VS2022使用 Trace 显示调试
                        //Console.WriteLine(ex.Message);
                        MessageBox.Show(ex.Message + "\r" + ex.Source + "\r" + ex.StackTrace, "错误消息");
                    }
                    finally
                    {
                        StatusMessage4.Text = "通讯串口消息已经完成! 等待新的指令或消息!";
                        WhenInvokeg = false;//没有调用了,UI可以关闭串口了。  
                    }
                }));
            }
            else
            {
                try
                {   // 更新UI的代码
                    StatusMessage4.Text = "串口正在通讯,并更新UI界面!";
                    Application.DoEvents();
                    Delayed(PortBufferInterval);//等待缓冲数据
                    PortMessage = serialPort.ReadLine().Trim();
                    if (PortMessage.Length > 0)
                    {
                        switch (TabWorkbenches.SelectedIndex)
                        {
                            case 0:
                            case 1:
                                Yac9900PortMessaging(PortMessage);
                                break;
                            case 2:
                                break;
                            case 3:
                                break;
                        }
                    }
                }
                catch (Exception ex)
                {
                    // 处理异常
                    StatusMessage2.Text = ex.Message;
                    // StatusMessage4.Text = Messaging(ex.Message);
                    //Trace.WriteLine(ex.Message);
                    //Console.WriteLine(ex.Message);
                    MessageBox.Show(ex.Message + "\r" + ex.Source + "\r" + ex.StackTrace, "错误消息");
                }
                finally
                {
                    StatusMessage4.Text = "通讯串口消息已经完成! 等待新的指令或消息!";
                    WhenInvokeg = false;//没有调用了,UI可以关闭串口了。  
                }
            }
        }

4、在窗体关闭前处理消息
关键在于及时取消连续发送命令 SendCommand = false 。避免继续产生串口事件,产生委托线程打架。

    private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            if (serialPort.IsOpen)
            {
                try
                {
                    if (WhenInvokeg)//如果没有委托,可以关闭程序
                    {

                        SendCommand = false;//取消连续发送命令
                        StatusMessage6.Text = "已取消剩下的发送命令!再次点击就退出!";                        
                        e.Cancel = true; // 暂时不能退出窗体,串口指令和消息完成后再关闭窗口
                    }
                    else
                    {
                        if (serialPort != null && serialPort.IsOpen) OpenPort();    // 关闭串口
                        e.Cancel = false;
                    }
                }
                catch (Exception ex)
                {
                    MessageBox.Show("无法关闭串口:" + ex.Message);
                }
            }
        }

5、串口连续指令发送的中断处理
关键在于 SendCommand = false 取消连续发生指令,不再发生新的串口事件。

       /// <summary>执行可以读写的YAC9900命令</summary>
        private void CanRwCommands()//可读写命令区
        {
            try
            {
                string setStr = "";
                if (checkDT.Checked && SendCommand)//是否选中时间读写和允许发送指令,
                {
                    if (CheckSet.Checked)//是否设置
                    {
                        if (CheckUseSystemTime.Checked)
                        {
                            setStr = DateTime.Now.ToString("yyyyMMddHHmmss").Trim();
                        }
                        else
                        {
                            setStr = DT_Picker.Value.ToString("yyyyMMddHHmmss").Trim();
                        }
                    }
                    serialPort.Write("DT" + setStr);
                    Delayed(SendCommandInterval);
                }

                if (checkStationCode.Checked && SendCommand)
                {
                //发送指令与上雷同
                }

                if (checkStorageWater.Checked && SendCommand)
                {
                //发送指令与上雷同
                }
                if (checkWaterBase.Checked && SendCommand)
                {
                //以下省略很多指令
                }
    
            }
            catch (Exception ex)
            {
                // 处理异常
                StatusMessage2.Text = ex.Message;
                StatusMessage4.Text = Messaging(ex.Message);
                Trace.WriteLine(ex.Source);
                //Console.WriteLine(ex.Message);
                //MessageBox.Show(ex.Message + "\r" + ex.Source + "\r" + ex.StackTrace, "错误消息");
            }
        }

四、程序界面

程序主界面:
在这里插入图片描述
串口设置界面,自动搜索串口集合,自动捕获 USB 串口插入:
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1844775.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

会声会影2024专业免费版下载附带激活码序列号

&#x1f31f; 会声会影2024&#xff1a;你的视频编辑新伙伴&#xff01;大家好&#xff0c;今天来给你们安利一个超级棒的视频编辑软件——会声会影2024最新版本&#xff01;作为一位热爱创作的小伙伴&#xff0c;找到一款既强大又易用的视频编辑工具真的太重要了。而会声会影…

什么是嵌入式,单片机又是什么,两者有什么关联又有什么区别?

在开始前刚好我有一些资料&#xff0c;是我根据网友给的问题精心整理了一份「嵌入式的资料从专业入门到高级教程」&#xff0c; 点个关注在评论区回复“888”之后私信回复“888”&#xff0c;全部无偿共享给大家&#xff01;&#xff01;&#xff01;从科普的角度&#xff0c;…

【减法网络】Minusformer:通过逐步学习残差来改进时间序列预测

摘要 本文发现泛在时间序列(TS)预测模型容易出现严重的过拟合。为了解决这个问题&#xff0c;我们采用了一种去冗余的方法来逐步恢复TS的真实值。具体来说&#xff0c;我们引入了一种双流和减法机制&#xff0c;这是一种深度Boosting集成学习方法。通过将信息聚合机制从加法转…

Java线程池七个参数详解

ThreadPoolExecutor 是JDK中的线程池实现&#xff0c;这个类实现了一个线程池需要的各个方法&#xff0c;它提供了任务提交、线程管理、监控等方法 下面是 ThreadPoolExecutor 类的构造方法源码&#xff0c;其他创建线程池的方法最终都会导向这个构造方法&#xff0c;共有7个参…

Spring Boot -- 图书管理系统(登录、展示+翻页、添加/修改图书)

文章目录 一、应用分层二、数据库的设计三、登录功能四、展示列表&#xff08;使用虚构的数据&#xff09;五、翻页 展示功能六、添加图书七、修改图书 一、应用分层 为什么我们需要应用分层&#xff1a;当代码量很多时&#xff0c;将其全部放在一起查找起来就会很麻烦&#…

通过MindSpore API实现深度学习模型

快速入门 将相应的包逐一导入到项目中&#xff0c;这是制作项目的第一步。 import mindspore from mindspore import nn from mindspore.dataset import vision, transforms from mindspore.dataset import MnistDataset 处理数据集 先从网上下载对应的数据集文件,MindSpor…

【LeedCode】二分查找算法(一)

二分查找算法的时间复杂度是O(logN) &#xff0c;更优于传统的遍历数组算法值得我们学习。 注意二分查找一般使用的前提是&#xff1a;待操作的数组的元素有某种规律也就是要有二阶性&#xff0c;二阶性就是在数组中选取一点根据该数组元素某种规律可以把数组分为两部分&#x…

Kafka基础教程

Kafka基础教程 资料来源&#xff1a;Apache Kafka - Introduction (tutorialspoint.com) Apache Kafka起源于LinkedIn&#xff0c;后来在2011年成为一个开源Apache项目&#xff0c;然后在2012年成为一流的Apache项目。Kafka是用Scala和Java编写的。Apache Kafka是基于发布-订…

2024广东省职业技能大赛云计算赛项实战——Minio服务搭建

Minio服务搭建 前言 这道题是比赛时考到的&#xff0c;没找到具体题目&#xff0c;但在公布的样题中找到了&#xff0c;虽然很短~ 使用提供的 OpenStack 云平台&#xff0c;申请一台云主机&#xff0c;使用提供的软件包安装部署 MINIO 服务并使用 systemctl 管理 Minio是一个…

SAR动目标检测系列:【4】动目标二维速度估计

在三大类杂波抑制技术(ATI、DPCA和STAP)中&#xff0c;STAP技术利用杂波与动目标在二维空时谱的差异&#xff0c;以信噪比最优为准则&#xff0c;对地杂波抑制的同时有效保留动目标后向散射能量&#xff0c;有效提高运动目标的检测概率和动目标信号输出信杂比&#xff0c;提供理…

2024华为OD机试真题- 计算三叉搜索树的高度-(C++/Python)-C卷D卷-100分

2024华为OD机试题库-(C卷+D卷)-(JAVA、Python、C++) 题目描述 定义构造三叉搜索树规则如下: 每个节点都存有一个数,当插入一个新的数时,从根节点向下寻找,直到找到一个合适的空节点插入。查找的规则是: 1.如果数小于节点的数减去500,则将数插入节点的左子树 2.如果数大于…

微软Azure AI更新视频翻译和语音翻译 API 功能!企业适用TTS文本转语音

很高兴与大家分享 Azure AI 语音翻译产品套件的两个重大更新&#xff01; 分别是视频翻译和增强的实时语音翻译 API。 视频翻译&#xff08;批量&#xff09; 微软宣布推出视频翻译预览版&#xff0c;这是一项突破性的服务&#xff0c;旨在改变企业本地化视频内容的方式。 随着…

STM32通过Flymcu串口下载程序

文章目录 1. Flymcu 2. 操作流程 2.1 设备准备 2.2 硬件连接 2.3 设置BOOT引脚 2.4 配置 2.5 下载程序 1. Flymcu Flymcu软件可以通过串口给STM32下载程序&#xff0c;如果没有STLINK的时候&#xff0c;就可以使用这个来烧录程序。软件不用安装&#xff0c;直接打开就行…

【Linux】使用ntpdate同步

ntpdate 是一个在 Linux 系统中用于同步系统时间的命令行工具&#xff0c;它通过与 NTP 服务器通信来调整本地系统时钟。然而&#xff0c;需要注意的是&#xff0c;ntpdate 已经被许多现代 Linux 发行版弃用。 安装 yum install -y ntpdate 查看时间 date同步时间 ntpdate ntp…

条码工具 Dynamic Web TWAIN HTML5 版本的工作原理

Dynamic Web TWAIN 是一个专为Web应用程序设计的TWAIN扫描识别控件。你只需在TWAIN接口写几行代码&#xff0c;就可以用兼容TWAIN的扫描仪扫描文档或从数码相机/采集卡中获取图像。然后用户可以编辑图像并将图像保存为多种格式&#xff0c;用户可保存图像到远程数据库或者Share…

【Proteus仿真】【51单片机】基于物联网新能源电动车检测系统设计

文章目录 一、功能简介二、软件设计三、实验现象联系作者 一、功能简介 本项目使用Proteus8仿真51单片机控制器&#xff0c;使用LCD1602液晶显示模块、WIFI模块、蜂鸣器、LED按键、ADC PCF8591、DS18B20温度传感器等。 主要功能&#xff1a; 系统运行后&#xff0c;LCD1602显…

sed去除文件中的引号

文件中有很多双引号: 使用sed命令去除 sed -i s/"//g movies.csv

Redis—String数据类型及其常用命令详解

文章目录 Redis概述1.Redis-String数据类型概述2.常用命令2.1 SET&#xff1a;添加或者修改已经存在的一个String类型的键值对2.2 GET&#xff1a;根据key获取String类型的value2.3 MSET&#xff1a;批量添加多个String类型的键值对2.4 MGET&#xff1a;根据多个key获取多个Str…

Hadoop简单应用程序实例

Hadoop是一个分布式系统基础架构&#xff0c;主要用于大数据的存储和处理。它允许使用简单的编程模型跨集群处理和生成大数据集。Hadoop主要由HDFS&#xff08;Hadoop Distributed FileSystem&#xff0c;分布式文件系统&#xff09;和MapReduce编程模型两部分组成。 准备工作…

利用星穹云Serverless云平台高效开发小程序的技术实践

一、引言 在快速迭代、持续创新的互联网环境下&#xff0c;开发者们面临着巨大的挑战。传统的小程序开发模式不仅需要掌握后端技术&#xff0c;还需要关注服务器部署、维护等一系列问题&#xff0c;这无疑增加了开发者的学习成本和项目复杂度。而Serverless架构的兴起&#xf…