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

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

  • 一、关于开发 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 串口插入:
在这里插入图片描述

五、软件下载

百度云盘:
https://pan.baidu.com/s/1ExIa8IE6q98mZ3_A_ROQQw?pwd=ZYYU

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

已解决java.rmi.activation.ActivationException异常的正确解决方法,亲测有效!!!

已解决java.rmi.activation.ActivationException异常的正确解决方法&#xff0c;亲测有效&#xff01;&#xff01;&#xff01; 问题分析 java.rmi.activation.ActivationException 是与Java RMI&#xff08;远程方法调用&#xff09;的激活机制相关的一种已检查异常。这个异…

港湾周评|高盛眼中的618增长

《港湾商业观察》李镭 年中最重要的购物节618终于尘埃落定了。2024年的618各大电商平台竞技情况如何&#xff1f;又有哪些新的亮点&#xff1f;都成为外界观察消费行为的参考指标。 根据京东618数据显示&#xff1a;累计成交额过10亿的品牌83个&#xff0c;超15万个中小商家销…

Ubuntu22.04开机后发现IP地址变成127.0.0.1

开机就是这个样子 解决办法 ip地址可能被释放&#xff0c;需要重新设置成自动分配 sudo dhclient -v可能网卡未加托管 查看方式: nmcli n若是enable就是已被托管,若是disabled&#xff0c;说明网卡未被托管 解决办法: nmcli n on搞定

车辆数据的提取、定位和融合(其一 共十二篇)

第一篇&#xff1a; System Introduction 第二篇&#xff1a;State of the Art 第三篇&#xff1a;localization 第四篇&#xff1a;Submapping and temporal weighting 第五篇&#xff1a;Mapping of Point-shaped landmark data 第六篇&#xff1a;Clustering of landma…

现在这个行情,又又又要开始准备面试了~~

亲爱的程序员朋友们: 这些资料曾经帮助过许多有志之士顺利拿下抖音、快手、阿里等大厂的Offer&#xff0c;现在也希望它们能为你的面试旅程助力&#xff01; 关注【程序员世杰】回复【1024】惊喜等你来拿&#xff01; 截图 关注【程序员世杰】回复【1024】惊喜等你来拿&#xf…

8.12 矢量图层面要素单一符号使用五(栅格数据填充)

文章目录 前言栅格数据填充&#xff08;Raster image fill&#xff09;QGis设置面符号为栅格数据填充&#xff08;Raster image fill&#xff09;二次开发代码实现栅格数据填充&#xff08;Raster image fill&#xff09; 总结 前言 本章介绍矢量图层线要素单一符号中使用栅格…

功能测试【测试用例模板、Bug模板、手机App测试】

功能测试 Day01 web项目环境与测试流程、业务流程测试一、【了解】web项目环境说明1.1 环境的定义&#xff1a;项目运行所需要的所有的软件和硬件组合1.2 环境(服务器)的组成&#xff1a;操作系统数据库web应用程序项目代码1.3 面试题&#xff1a;你们公司有几套环境&#xff1…

【SpringCloud-Seata客户端源码分析01】

文章目录 启动seata客户端1.导入依赖2.自动装配 发送请求的核心方法客户端开启事务的核心流程服务端分布式事务的处理机制 启动seata客户端 1.导入依赖 <parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent…

昇腾Ascend上使用分布式训练

一、环境搭建 1、使用hccn_tool配置昇腾训练卡的芯片网络&#xff0c;包括ip地址和掩码 命令原型 hccn_tool [-i %d] -ip -s [address %s] [netmask %s] 使用样例(配置两张卡)&#xff1a; hccn_tool -i 0 -ip -s address 192.168.2.10 netmask 255.255.255.0 hccn_tool …

python例子:翻译器(简单)

作品介绍 作品名称&#xff1a;翻译器 开发环境&#xff1a;PyCharm 2023.3.4 python3.7 用到的库&#xff1a;PyQt5、translate、sys 作品简介&#xff1a;“输入内容”输入要翻译的中文内容&#xff0c;“选择语言”选择要翻译的语种&#xff0c;最后点击“开始翻译”&a…

PyScada(一)简介

PyScada的相关资料 PyScada是具有 HTML5 HMI 的开源 SCADA 系统&#xff0c;使用 Django 框架构建。 SCADA是什么 SCADA&#xff08;Supervisory Control and Data Acquisition&#xff0c;监控与数据采集系统&#xff09;是一种用于实时监控和控制工业过程的自动化系统。它通…

基于JSP技术的家用电器销售网站

开头语&#xff1a;你好呀&#xff0c;我是计算机学长猫哥&#xff01;如果有相关需求&#xff0c;文末可以找到我的联系方式。 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;JSPJava 工具&#xff1a;ECLIPSE、MySQL数据库管理工具、Tomcat 系统展…

海外短剧系统如何征服观众心

海外短剧系统要征服观众的心&#xff0c;需要综合考虑多个方面。 1、紧凑的剧情设计&#xff1a; 短小精悍&#xff1a;海外短剧通常每集时长不超过半小时&#xff0c;甚至有的仅有几分钟。这种紧凑的剧情设计让观众能够在短时间内迅速沉浸在故事中&#xff0c;无需花费大量时间…

kafka进阶核心原理详解:案例解析(第11天)

系列文章目录 kafka高级&#xff08;重点&#xff09; kafka核心概念汇总 kafka的数据位移offset Kafka的基准/压力测试 Kafka的分片副本机制 kafka如何保证数据不丢失 kafka的消息存储及查询机制 生产者数据分发策略 消费者负载均衡机制 kafka的监控工具:kafka-eagle…

集智书童 | 深度学习与先验方法在遥感与无人机影像去雾中的应用与挑战!

本文来源公众号“集智书童”&#xff0c;仅用于学术分享&#xff0c;侵权删&#xff0c;干货满满。 原文链接&#xff1a;深度学习与先验方法在遥感与无人机影像去雾中的应用与挑战&#xff01; 论文链接&#xff1a;[2405.07520] Dehazing Remote Sensing and UAV Imagery: A…

解决企业微信内嵌H5页面导航栏返回上一级是空白页面问题

在项目中,产品要求返回上一级不能空白页,可以是工作台,所以要引入企业微信的返回按钮的用法,以下是详细步骤: 1.引入企业微信的版本内容 <script src"https://res.wx.qq.com/wwopen/js/jsapi/jweixin-1.0.0.js"></script> 在public底下的index.html底…

【计算机网络仿真】b站湖科大教书匠思科Packet Tracer——实验6 生成树协议STP的功能

一、实验目的 1.验证以太网交换机生成树协议的功能&#xff1b; 2.理解网络环路对网络的负面效应&#xff1b; 3.理解生成树协议的作用。 二、实验要求 1.使用Cisco Packet Tracer仿真平台&#xff1b; 2.观看B站湖科大教书匠仿真实验视频&#xff0c;完成对应实验。 三、实…

【机器学习】基于Softmax松弛技术的离散数据采样

1.引言 1.1.离散数据采样的意义 离散数据采样在深度学习中起着至关重要的作用&#xff0c;它直接影响到模型的性能、泛化能力、训练效率、鲁棒性和解释性。 首先&#xff0c;采样方法能够有效地平衡数据集中不同类别的样本数量&#xff0c;使得模型在训练时能够更均衡地学习…

spring源码环境的搭建

为什么要编译spring源码 为了高效调试Spring源码、验证个人猜想&#xff0c;并从开发者的视角深化理解&#xff0c;编译自定义的Spring源码版本显得尤为重要。这样可以避免因缺乏预编译版本而带来的不便&#xff0c;并允许直接在源码上进行注释或修改&#xff0c;以记录学习心…

Matlab基础篇:绘图与可视化

目录 前言 一、二维绘图 二、图形属性设置 三、多图绘制 四、三维绘图 五、绘图技巧 六、绘图的高级技巧 七、实例示范&#xff1a;绘制多功能图形 八、总结 前言 在数据分析和数学建模中&#xff0c;可视化是一个非常关键的步骤。Matlab 提供了丰富的绘图和可视化工…