从0到1打造一款WebStyle串口调试工具

news2024/11/24 5:43:43

Tip:No Ego

Some programmers have a huge problem: their own ego. But there is no time for developing an ego. There is no time for being a rockstar.

Who is it who decides about your quality as programmer? You? No. The others? Probably. But can you really compare an Apple with a Banana? No. You are an individual. You cannot compare your whole self with another human being. You can only compare a few facettes.

A facet is nothing what you can be proud of. You are good at Java? Cool. The other guy is not as good as you, but better with bowling. Is Java more important than bowling? It depends on the situation. Probably you earn more money with Java, but the other guy might have more fun in life because of his bowling friends.

Can you really be proud because you are a geek? Programmers with ego don’t learn. Learn from everybody, from the experienced and from the noobs at the same time.

Kodo Sawaki once said: you are not important.

Think about it.

——The 10 rules of a Zen programmer

零、背景:为什么要造这个轮子

传统的桌面应用大多数是低代码例如 WinForm、WPF、QT 等基于现有的组件进行拖拽式开发,如果没有特别去优化改善界面,用户体验感是很差的,因此衍生出一种嵌入式浏览器方案 CEF,尝试使用现有的前端技术去解决桌面 UI 问题。

基于这个背景下,本文从学习研究的角度实现一个示例以探索 CEF 解决方案在工业领域的应用,现模拟一个工业调试设备的场景,例如从称重机中获取重量、发送亮灯信号、控制电路开关等。串口调试工具用于检验硬件设备是否能够正常运作,如下图所示:

  • Step1、界面上选择设备的串口参数
  • Step2、根据串口参数连接到设备
  • Step3、读取并解析设备返回的数据
  • Step4、将数据回显到界面上
  • Step5、根据界面的数据判断设备运行情况

一、技术栈

Vite + Vue3 + TS + WebSocket+ ElementUI(plus) + .NET Framework 4.7.2 + WPF + SQLITE3,开发环境为 Win10,VS2019,VS Code。 

二、后端设计与实现

开发环境(补充)

1、WS服务器类WebSocketServer

安装 fleck 库,这里使用的版本是 1.2.0,

using Fleck;
using System.Diagnostics;

namespace SerialDevTool.WS
{
    class MyWebSocketServer
    {
        /// <summary>
        /// 运行 WS 服务器
        /// </summary>
        public static void Run()
        {
            FleckLog.Level = LogLevel.Debug;
            var server = new WebSocketServer("ws://127.0.0.1:3000");
            server.Start(socket =>
            {
                // 建立连接
                socket.OnOpen = () =>
                {
                    Debug.WriteLine("客户端连接成功");
                };
                // 关闭连接
                socket.OnClose = () =>
                {
                    Debug.WriteLine("客户端已经关闭");
                };
                // 收到消息
                socket.OnMessage = message =>
                {
                    Debug.WriteLine(string.Format("收到客户端信息:{0}",message));
                    socket.Send(message);
                };
                // 发生错误
                socket.OnError = exception => {
                    Debug.WriteLine(string.Format("发生错误:{0}",exception.Message));
                };
            });
            Debug.WriteLine("WS服务器已启动");
        }
    }
}

这里我们创建了一个 WS 服务器,地址为 ws://127.0.0.1:3000 ,并且实现了 OnOpen、OnClose 、OnMessage、OnError 对应的方法,启动方式如下,

Task.Run(() =>
{
    MyWebSocketServer.Run();
});

使用 Postman 测试 WS,点击左上角 File–> New,选择 WebSocket,

可以看到,Postman 向服务器发送 hello world,服务器也向 Postman 返回 hello world,

2、串口通讯工具类SerialPortlUtil

using System;
using System.Diagnostics;
using System.IO.Ports;

namespace SerialDevTool.Utils
{
    /// <summary>
    /// 串口工具类
    /// </summary>
    public class SerialPortlUtil
    {
        /// <summary>
        /// 默认偏移
        /// </summary>
        private static readonly int OFFSET = 0;
        /// <summary>
        /// 默认数据位
        /// </summary>
        private static readonly int COUNT = 8;
        /// <summary>
        /// 默认超时时间,单位 ms
        /// </summary>
        private static readonly int DEFAULT_TIMEOUT = 500;
        /// <summary>
        /// 默认COM口
        /// </summary>
        private static readonly string DEFAULT_COM = "COM1";
        /// <summary>
        /// 默认波特率
        /// </summary>
        private static readonly int DEFAULT_BAUDRATE = 9600;
        /// <summary>
        /// 默认校验位
        /// </summary>        
        private static readonly Parity DEFAULT_PARITY = Parity.None;
        /// <summary>
        /// 默认数据位
        /// </summary>
        private static readonly int DEFAULT_DATABITS = 8;
        /// <summary>
        /// 默认停止位
        /// </summary>
        private static readonly StopBits DEFAULT_STOPBITS = StopBits.One;

        /// <summary>
        /// 获取默认串口实例
        /// </summary>
        public static SerialPort GetDefaultSerialPortInstance()
        {
            return GetSerialPortInstance(DEFAULT_COM);
        }
        /// <summary>
        /// 获取串口实例
        /// </summary>
        /// <param name="com"></param>
        /// <returns></returns>
        public static SerialPort GetSerialPortInstance(string com)
        {
            // COM1,9600,0,8,1
            if (com.Contains(","))
            {
                string[] comParams = com.Split(new string[] { "," }, StringSplitOptions.None);

                return new SerialPort(comParams[0], int.Parse(comParams[1]), GetParity(comParams[2]), int.Parse(comParams[3]), GetStopBits(comParams[4]))
                {
                    ReadTimeout = DEFAULT_TIMEOUT,
                    WriteTimeout = DEFAULT_TIMEOUT
                };
            }

            // COM1
            return new SerialPort(com, DEFAULT_BAUDRATE, DEFAULT_PARITY, DEFAULT_DATABITS, DEFAULT_STOPBITS)
            {
                ReadTimeout = DEFAULT_TIMEOUT,
                WriteTimeout = DEFAULT_TIMEOUT
            };
        }

        /// <summary>
        /// 解析停止位
        /// </summary>
        /// <param name="stopBits"></param>
        /// <returns></returns>
        public static StopBits GetStopBits(string stopBits)
        {
            switch (stopBits)
            {
                case "0":
                    {
                        return StopBits.None;
                    }
                case "1":
                    {
                        return StopBits.One;
                    }
                case "2":
                    {
                        return StopBits.Two;
                    }
                case "3":
                    {
                        return StopBits.OnePointFive;
                    }
                default:
                    return StopBits.One;
            }
        }

        /// <summary>
        /// 解析校验位
        /// </summary>
        /// <param name="parity"></param>
        /// <returns></returns>
        public static Parity GetParity(string parity)
        {
            switch (parity)
            {
                case "0":
                    {
                        return Parity.None;
                    }
                case "1":
                    {
                        return Parity.Odd;
                    }
                case "2":
                    {
                        return Parity.Even;
                    }
                case "3":
                    {
                        return Parity.Mark;
                    }
                case "4":
                    {
                        return Parity.Space;
                    }
                default:
                    return Parity.None;
            }
        }

        /// <summary>
        /// 写入 8 位字节数据
        /// </summary>
        /// <param name="serialPort"></param>
        /// <param name="buffer"></param>
        public static void Write(SerialPort serialPort, byte[] buffer)
        {
            try
            {
                if (!serialPort.IsOpen)
                {
                    serialPort.Open();
                }
                serialPort.Write(buffer, OFFSET, COUNT);
            }
            catch (Exception ex)
            {
                Debug.WriteLine(string.Format("Write Exception: {0}", ex.Message));
            }
        }
        /// <summary>
        /// 将指定的字符串和 System.IO.Ports.SerialPort.NewLine 值写入输出缓冲区。
        /// </summary>
        /// <param name="serialPort"></param>
        /// <param name="text"></param>
        public static void WriteLine(SerialPort serialPort, string text)
        {
            try
            {
                if (!serialPort.IsOpen)
                {
                    serialPort.Open();
                }
                serialPort.WriteLine(text);
            }
            catch (Exception ex)
            {
                Debug.WriteLine(string.Format("WriteLine Exception: {0}", ex.Message));
            }
        }

        /// <summary>
        /// 读 8 位字节数据
        /// </summary>
        /// <param name="serialPort"></param>
        /// <param name="buffer"></param>
        public static int Read(SerialPort serialPort, byte[] buffer)
        {
            try
            {
                if (!serialPort.IsOpen)
                {
                    serialPort.Open();
                }
                return serialPort.Read(buffer, OFFSET, COUNT);
            }
            catch (Exception ex)
            {
                Debug.WriteLine(string.Format("Read Exception: {0}", ex.Message));
            }
            return 0;
        }

        /// <summary>
        ///  一直读取到输入缓冲区中的 System.IO.Ports.SerialPort.NewLine 值。
        /// </summary>
        /// <param name="serialPort"></param>
        /// <returns></returns>
        public static string ReadLine(SerialPort serialPort)
        {
            string line = "";
            try
            {
                if (!serialPort.IsOpen)
                {
                    serialPort.Open();
                }
                line = serialPort.ReadLine();
            }
            catch (Exception ex)
            {
                Debug.WriteLine(string.Format("ReadLine Exception: {0}", ex.Message));
            }
            return line;
        }
    }
}

使用虚拟串口测试,

        /// <summary>
        /// 测试串口通讯
        /// </summary>
        private void TestSerialPort()
        {
            Task.Run(() =>
            {
                SerialPort readCom = SerialPortlUtil.GetSerialPortInstance("COM6");
                int length = 0;
                while (true)
                {
                    byte[] buffer = new byte[8];
                    length = SerialPortlUtil.Read(readCom, buffer);
                    if (length > 0)
                    {
                        Debug.Write("receive: ");
                        for (int i = 0; i < length; i++)
                        {
                            Debug.Write(string.Format("{0} ", buffer[i]));
                        }
                        Debug.Write("\n");
                        Thread.Sleep(1000);
                    }
                }
            });
            Task.Run(() =>
            {
                SerialPort writeCom = SerialPortlUtil.GetSerialPortInstance("COM5");
                while (true)
                {
                    SerialPortlUtil.Write(writeCom, new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08 });
                    Thread.Sleep(500);
                }
            });
        }

这里的虚拟串口 COM5 每 500ms 向缓存区写入数据,COM6 每 1000ms 从缓存区中读取数据,SerialPort 读写数据类型均支持 Byte、Char、String,

3、将串口通讯绑定到WS方法

using Fleck;
using SerialDevTool.Utils;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO.Ports;
using System.Threading;
using System.Threading.Tasks;

namespace SerialDevTool.WS
{
    class MyWebSocketServer
    {
        /// <summary>
        /// 写标志
        /// </summary>
        private const string WRITE_FLAG = "##WRITE##";
        private readonly string[] WRITE_FLAG_SEPARATOR = new string[] { WRITE_FLAG };

        /// <summary>
        /// 打开串口标志
        /// </summary>
        private const string OPEN_FLAG = "##OPEN##";
        private readonly string[] OPEN_FLAG_SEPARATOR = new string[] { OPEN_FLAG };
        /// <summary>
        /// 关闭串口标志
        /// </summary>
        private const string CLOSE_FLAG = "##CLOSE##";
        private readonly string[] CLOSE_FLAG_SEPARATOR = new string[] { CLOSE_FLAG };

        /// <summary>
        /// 当前连接的 socket
        /// </summary>
        private Dictionary<string,IWebSocketConnection> _webSocketDic;

        /// <summary>
        /// 当前连接的串口
        /// </summary>
        private Dictionary<string, SerialPort> _serialPortDic;

        public MyWebSocketServer(){
            this._webSocketDic = new Dictionary<string, IWebSocketConnection>();
            this._ser

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

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

相关文章

拥抱鸿蒙 - 在展讯T606平台上的探索与实践

前 言 自OpenHarmony 问世后受到了社会各界的广泛关注&#xff0c;OpenHarmony 的生态系统在如火如荼的发展。 酷派作为一家积极拥抱变化的公司&#xff0c;经过一段时间的探索与实践&#xff0c;成功实现将OpenHarmony 系统接入到展讯平台上&#xff0c;我们相信这是一个重要…

Infant-freesurfer安装和使用,适用于0-2岁婴幼儿大脑自动分割

Infant-freesurfer安装和使用 #Note https://surfer.nmr.mgh.harvard.edu/fswiki/infantFS --官网申请下载infant_recon_all --s SUBJ --age age_in_months--newborn flag (it aut

AVP摄像头与ECU交互需求规范

目录 1 文档范围及控制方法... 5 1.1 目的.... 5 1.2 文档授权... 5 1.3 文档变更管理... 5 1.4 缩写.... 5 1.5 术语.... 5 2 系统组成... 6 2.1 系统框图... 6 2.2 电源供应和时序要求... 7 2.2.1 摄像头供电控制... 7 2.2.2 摄像头上电时序要求…

Linux Centos7 安装NVIDIA 驱动

cat /etc/redhat-release 查看系统版本 服务器IP 系统版本 内核 GPU 内存和硬盘 后期扩容 10.0.2.125 CentOS Linux release 7.9.2009 5.3.10-1.el7.elrepo.x86_64 4 张 Tesla T4&#xff08;16G&#xff09; 376G 1.5T 物理机 10.0.2.130 CentOS Linux release …

消息中间件的介绍

消息中间件&#xff08;message oriented middleware&#xff09;是指支持与保障分布式应用程序之间同步/异步收发消息的中间件。消息是分布式应用之间进行数据交换的基本信息单位&#xff0c;分布式应用程序 之间的通信接口由消息中间件提供。其中&#xff0c;异步方式指消息发…

T2I-Adapter: 让马良之神笔(扩散模型)从文本生成图像更加可控

文章信息 单位&#xff1a;北大深张健团队&#xff0c;腾讯ARC lab 源码: https://github.com/TencentARC/T2I-Adapter 图1. 插个DXL的渲染图&#xff0c;这么真实的光感&#xff0c;感觉PS都可以被取代了 目录 文章信息前言一、介绍二、相关工作1.图像合成与转换2 扩散模型3 适…

多维时序 | MATLAB实现SSA-CNN-SVM麻雀算法优化卷积神经网络-支持向量机多变量时间序列预测

多维时序 | MATLAB实现SSA-CNN-SVM麻雀算法优化卷积神经网络-支持向量机多变量时间序列预测 目录 多维时序 | MATLAB实现SSA-CNN-SVM麻雀算法优化卷积神经网络-支持向量机多变量时间序列预测预测效果基本介绍模型描述程序设计参考资料 预测效果 基本介绍 多维时序 | MATLAB实现…

【计算机图形学】Ditto: Building Digital Twins of Articulated Objects from Interaction

项目主页&#xff1a;Ditto: Building Digital Twins of Articulated Objects from Interaction 文章目录 1. 为什么要做这件事2. 做了件什么事3. 之前的工作&#xff08;Related work&#xff09;铰接模型估计铰接物体的3D重建隐式神经表达物理仿真中的铰接物体 4. 问题定义5…

腾讯云服务器上传文件 :Permission denied (os error 13) ,由于权限无法上传

根据网上的修改云服务器上传文件目录的权限&#xff0c;或是用root权限上传本地文件&#xff0c;均失败。 正解办法&#xff1a; ubuntu:/home/wwwroot# sudo passwd root Enter new UNIX password: Retype new UNIX password: passwd: password updated successfully首先修…

Windows常用的功能命令

Win操作方法 快捷键winR&#xff0c;输入cmd回车&#xff0c;然后就可以输入cmd命令了&#xff0c;赶紧收藏起来&#xff0c;用的时候更方便 打开程序和功能 快捷键winR&#xff0c;输入appwiz.cpl回车 启动计算器 快捷键winR&#xff0c;输入calc回车 计算机管理 …

saas供应链批发订货系统源码整套输出的3大好处

随着电子商务的快速发展&#xff0c;越来越多的企业开始关注和采用供应链批发订货系统&#xff0c;以提高其供应链管理的管理效率和数据沉淀。现在大多企业还是使用SaaS供应链系统&#xff0c;而源码整套输出的3大好处、尤其是第三个可让企业受益匪浅。 1. 定制化能力&#xff…

Leetcode 55 跳跃游戏

题意理解&#xff1a; 非负整数数组 nums, 最初位于数组的 第一个下标 。 数组中的每个元素代表你在该位置可以跳跃的最大长度。 需要跳到nums最后一个元素即为成功。 目标&#xff1a;是否能够跳到最后一个元素。 解题思路&#xff1a; 使用贪心算法来解题&#xff0c;需要理解…

Unity中Shader平移矩阵

文章目录 前言方式一&#xff1a;对顶点本地空间下的坐标进行相加平移1、在属性面板定义一个四维变量记录在 xyz 上平移多少。2、在常量缓冲区进行申明3、在顶点着色器中&#xff0c;在进行其他坐标转化之前&#xff0c;对模型顶点本地空间下的坐标进行转化4、我们来看看效果 方…

NIO的实战教程(简单且高效)

1. 参考 建议按顺序阅读以下三篇文章 为什么NIO被称为同步非阻塞&#xff1f; Java IO 与 NIO&#xff1a;高效的输入输出操作探究 【Java.NIO】Selector&#xff0c;及SelectionKey 2. 实战 我们将模拟一个简单的HTTP服务器&#xff0c;它将响应客户端请求并返回一个固定的…

​ 全球云科技基础设施:亚马逊云科技的海外服务器网络如何演进

在当今数字化浪潮愈发汹涌的时代&#xff0c;科技公司的发展不仅需要更强大的计算能力和创新性技术&#xff0c;还需要对环境的高度责任感。在这一背景下&#xff0c;亚马逊云科技的海外服务器产品成为了推动清洁、高效数字未来的领导者之一。亚马逊云科技的高级主管阿比谢克夏…

【MyBatis学习笔记】MyBatis基础学习

MyBatis基础 MyBatis简介MyBatis特性MyBatis下载和其他持久化层技术对比 核心配置文件详解默认的类型别名 搭建MyBatis开发环境创建maven工程创建MyBatis的核心配置文件创建mapper接口创建MyBatis的映射文件通过junit测试功能加入log4j日志功能 MyBatis获取参数值的两种方式&am…

vue-pure-admin源码解读与使用

vue-pure-admin 全面使用ESMVue3ViteElement-PlusTypeScript编写的一款后台管理系统&#xff08;兼容移动端&#xff09;,目前斩获11.5k个star。 界面构成 主题Layout的组成 左边sidebar由Vertical组件定义tab标签栏由layoutHeader组件定义中间Body由appMain组件定义 为何点…

@RequestParam、@PathVariable、@RequestBody、@RequestAttribute详解

一、RequestParam注解 作用&#xff1a;用于将指定的请求参数赋值给方法中的形参。 属性&#xff1a; 1&#xff09;value&#xff1a;请求参数名&#xff08;必须配置&#xff09; 2&#xff09;required&#xff1a;是否必需&#xff0c;默认为 true&#xff0c;即请求中必须…

GESP认证第四次考试百分榜考生名单及学校

考生姓名 所在学校 编程语言 得分 谢秉修 上海市民办华育中学 C 八级 100 张洪森 淄博市张店区第八中学 C 四级 100 高希文 宜兴市实验中学 C 四级 100 窦铭泽 温州市实验中学教育集团府东分校 C 四级 100 袁…

浅析 fuse kernel mmap write 过程及性能问题

前言 最近在项目里面用到了fuse文件系统&#xff0c;在使用过程中遇到了一个内核在做mmap write的一个bug&#xff0c;目前并没有从根本上解决这个bug&#xff0c;而是通过修改fuse kernel module的一些参数&#xff0c;绕开了这个bug。这里记录一下这个问题&#xff0c;并顺便…