服务器网页版上位机设计 - 03 - 上位机 (完结)

Posted L建豪 忄YH

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了服务器网页版上位机设计 - 03 - 上位机 (完结)相关的知识,希望对你有一定的参考价值。

服务器网页版上位机设计 03 上位机 (完结)

本设计主要涉及三个方面: 服务器,网页版,上位机.

书接上回,介绍完网页页面的设计,现在来说说上位机的功能设计.

也就是js文件的内容编写.

1.获取html对象

  • 在网页设计时,设置了4个按钮,现在为它们添加回调函数.

  • 首先需要在js中获取html的元素.有多种方法,我选择类似css类选择器的方法,这是为了和我的css筛选方式相同.

参考: << 【JavaScript】 JS中获取HTML元素值的三种方法_L5Butterfly的博客-CSDN博客_js 获取元素值 >>

  • 1.js文件中添加如下代码:
// 通过选择器获取一个元素
const f1 =  document.querySelector('div.d11 > form');
const f2 =  document.querySelector('div.d12 > div.d21 > form');
const f3 =  document.querySelector('div.d12 > div.d22 > form');
  • 然后就可以通过这3个对象获取元素了.想打印看看是什么内容?可以直接在浏览器控制台中输入代码,这个控制台其实就是js环境,类似python,逐行编译.
  • 输入f1就能看到提示,第一行代表有创建这个变量.第二和第三行代表输入历史值,我之前测试时有输入过,现在快捷显示.然后左边的页面能看到<form>标签元素高亮了,发现并没有包裹所有表单元素,因为我们没有设置大小,也不需要设置.

  • 当你按下回车,会发现打印的就是html内的文本内容(没想到居然就是文本内容),再把鼠标移动到个别元素上,会再次高亮个别元素.

  • 那么怎么方便明了的获取<form>内的元素呢?可以直接通过html中设置的name元素,获取到个别元素.不需要再通过选择器等方法获取了.而且很关键的是,这个name元素是字符串的形式,是支持中文的.用起来也很像访问字典数据.
  • 例如,输入f1["波特率"]就能获取到波特率的元素.非常方便.接下来就可以通过这个方法获取按钮元素,然后添加回调函数了.

2.添加回调函数

  • js中对html进行操作,这一行为有个专业术语:DOM,(Document Object Model) 译为文档对象模型.

参考: << HTML DOM 教程 | 菜鸟教程 (runoob.com) >>

  • 想介绍的东西都有多,不过不跑题了,就说添加回调函数,也就是按下按键时,触发的函数.关键词是onclick .有两种方法,一种是直接为onclick属性赋值,另一个是指定为onclick赋值.(我一开始用后者,回来才知道前者,所以都介绍一下.)

参考: << HTML DOM 事件 | 菜鸟教程 (runoob.com) >>

<< HTML DOM addEventListener() 方法 | 菜鸟教程 (runoob.com) >>

// 直接赋值,简单方便
f1["端口号"].onclick = function();
// 指定赋值,灵活
f1["端口号"].addEventListener("click", function());
  • 这里选择使用前者,方便快捷.添加代码前,先规划好每个按钮的不同状态,一开始要先将框架定下来,之后再一点点添加内容.这样代码写起来才不会乱.

f1[“开关键”] -> 打开 / 关闭

f2[“按钮”] -> 开始读取 / 停止读取

f3[“按钮”] -> 开始发送 / 停止发送

f1[“端口号”] -> 显示当前端口ID

(本来想做个选择端口的功能的,但是打开端口时就已经包含了选择功能,虽然可以分开,但是因为无法识别端口名字,只能获取到端口id,所以选择端口只能看着一堆数字也分辨不出来.所以干脆就不分开了.然后这个端口按钮就只是用来查看已经连接的端口id)

  • 设计好大概框架后,就可以写代码了,在1.js中继续添加如下代码:
// 按钮的回调函数
f1["端口号"].onclick = function()

    if (f1["开关键"].value == "打开")
    
        alert(`未打开串口`); // 弹窗:提示没有打开串口
    
    else if (f1["开关键"].value == "关闭") 
    
        alert(`已打开串口\\n$2048`); // 弹窗:反馈已打开串口的信息
    
    else
    
        new Error(this); // 意料之外,报错
    
;
f1["开关键"].onclick = function()

    if (f1["开关键"].value == "打开")
    
        console.log(`开启串口`); // 以下调用 开启串口 函数

        f1["开关键"].value = "关闭"; // 执行完毕,最后修改按钮文本
    
    else if (f1["开关键"].value == "关闭") 
    
        console.log(`关闭串口`); // 以下调用 关闭串口 函数

        f1["开关键"].value = "打开"; // 执行完毕,最后修改按钮文本
    
    else
    
        new Error(this); // 意料之外,报错
    
;
f2["按钮"].onclick = function()

    if (f2["按钮"].value == "开始接收")
    
        console.log(`开始接收`); // 以下调用 开始接收 函数

        f2["按钮"].value = "停止接收"; // 执行完毕,最后修改按钮文本
    
    else if (f2["按钮"].value == "停止接收") 
    
        console.log(`停止接收`); // 以下调用 停止接收 函数

        f2["按钮"].value = "开始接收"; // 执行完毕,最后修改按钮文本
    
    else
    
        new Error(this); // 意料之外,报错
    
;
f3["按钮"].onclick = function()

    if (f3["按钮"].value == "开始发送")
    
        console.log(`开始发送`); // 以下调用 开始发送 函数

        f3["按钮"].value = "停止发送"; // 执行完毕,最后修改按钮文本
    
    else if (f3["按钮"].value == "停止发送") 
    
        console.log(`停止发送`); // 以下调用 停止发送 函数

        f3["按钮"].value = "开始发送"; // 执行完毕,最后修改按钮文本
    
    else
    
        new Error(this); // 意料之外,报错
     
;
  • 上面代码中还用到了new Error(this),这个是反馈行号用的,可以自行在控制台输入查看效果.

参考: << js获取当前代码行号_60rzvvbj的博客-CSDN博客_js获取当前行数 >>

(其实在浏览器中,随便打印输出点东西就可以了,因为控制台会显示执行的行号,还能快捷跳转查看)

3.Web Serial API

  • 一哟一哟,终于开始正片内容了.我们将开始网页访问串口的功能实现.以下内容写在2.js文件中.在上一篇讲网页设计时已经完成脚本导入的步骤了.所以现在2个文件可以互相访问.没错,并不是只有1.js能访问2.js,而是二者可以互相访问,这也是使用requirejs的一大好处.
  • 推荐一系列教程,大部分其实都是源自官方的手册翻译.大同小异.

推荐参考:

<< Web Serial API,web端通过串口与硬件通信 - 掘金 (juejin.cn) >>

<< 什么,网页也能直接与硬件通信?Web Serial API!|8月更文挑战 - 掘金 (juejin.cn) >>

<< Web Serial API - Web APIs | MDN (mozilla.org) >>

<< Web Serial API (wicg.github.io) >>

  • 在开始前还需要先准备一下串口工具,因为手头可能没有能提供串口连接的硬件设备.使用虚拟串口做实验更加方便.使用VSPD创建虚拟串口,然后网页版上位机连接一个,串口调试助手连接一个.

推荐参考:

<< Virtual Serial Port Driver 10 破解版 - 星光的博客 (starxg.com) >> (半天找不到免安装,就安装一下吧)

<< 友善串口调试助手下载与安装 - 知乎 (zhihu.com) >> (找了个可以免安装的)

3.1.异步&延时

  • 这里介绍一个重要概念,异步.为了防止程序堵塞,浏览器的串口模块是需要异步使用的.使用关键词asyncawait ,使用的时候只需要注意两点即可,1)不能用返回值,2)await必须在async函数内使用.

参考: << JavaScript 入门笔记 - 下 - 函数语法_L建豪 忄YH的博客-CSDN博客 >>

  • 在串口通讯时,也很难避免会用到延时等待.在js中很特殊,没有直接的sleep等待函数.只有一个创建异步的setTimeout函数,能创建一个x毫秒后执行的异步函数.不过可以自行嵌套创建.

参考: << javascript里的sleep()方法_clschen的博客-CSDN博客_js sleep() >>

  • 2.js中添加如下内容:
// 延时函数,异步中调用
const sleep = (delay) => new Promise((resolve) => setTimeout(resolve, delay));
// 调用时要加上 await,代表只能在异步函数 async 中使用.
// await sleep(1000);
  • 如果是需要多段延时等待的话,sleep使用会很方便,如果只是一段的话,还是直接使用setTimeout会更加直接,也不需要使用await关键词.

3.2.打开串口

推荐参考: << Web Serial API (wicg.github.io) >> 4.4 open() method ;

  • 2.js中添加如下内容:
/* 说明: 全局变量
*/
let port_g = null; // 储存端口对象
let reader_g = null; // 储存读对象
let writer_g = null; // 储存写对象

/* 说明: 选择端口,并打开
*  参数: 波特率
*/
async function open(baudRate)

    if (!("serial" in navigator)) // 保护措施
    
        console.log(`本浏览器不支持串口功能.`);
        return; // 跳过剩余内容
    

    console.log(`选择端口`);
    try
    
        console.log(`选择成功`);
        port_g = await navigator.serial.requestPort();
    
    catch
    
        console.log(`选择失败`);
        port_g = null;
    

    if (port_g != null) // 保护措施
    
        console.log(`打开端口`);
        await port_g.open(
            baudRate: baudRate, 	    // 波特率
            dataBits: 8, 				// 数据位
            stopBits: 1, 				// 停止位
            parity: 'none', 			// 奇偶校验
            bufferSize = 255,			// 缓冲区大小
            flowControl: 'none'			// 流控模式
        );
    


  • 需要注意的有3点: 1)选择端口和打开端口都是带有await关键词的异步函数,所以要使用async函数.2)选择端口时需要使用try关键词,因为如果没有在弹窗中选择端口时,会引发错误.3)open()函数的传参是字典,其中波特率是没有默认值的,其他参数都有默认值.不写也可以.
  • 然后在1.js文件内的按钮回调函数中调用.
...
f1["开关键"].onclick = function()

    if (f1["开关键"].value == "打开")
    
        console.log(`开启串口`); // 以下调用 开启串口 函数

        open(f1["波特率"].options[f1["波特率"].selectedIndex].value); // 取出波特率,然后打开串口
        
        f1["开关键"].value = "关闭"; // 执行完毕,最后修改按钮文本
    
...
  • 这个时候发现一个bug,虽然不选择端口后控制台不会报错,但是按钮的文本却发生了改变.如果打开失败,应该是不改变文本.

  • 如果是以前c语言的思维,应该有个返回值,成功返回真,失败返回假.但是因为异步函数的原因,返回值并不好用.结合js的特点,用传入函数的方式解决.
  • 2.js文件修改成如下:
async function open(baudRate, fun)

...
    if (port_g != null) // 保护措施
    
        console.log(`打开端口`);
        await port_g.open(
            baudRate: baudRate, 	    // 波特率
            dataBits: 8, 				// 数据位
            parity: 'none', 			// 奇偶校验
            stopBits: 1, 				// 停止位
            flowControl: 'none'			// 流控模式
        );
    

    if (port_g != null) // 保护措施
    
        fun(); // 执行完毕,最后附带执行
    

  • 1.js文件修改成如下:
...
f1["开关键"].onclick = function()

    if (f1["开关键"].value == "打开")
    
        console.log(`开启串口`); // 以下调用 开启串口 函数

        open(f1["波特率"].options[f1["波特率"].selectedIndex].value, ()=>
        
            f1["开关键"].value = "关闭"; // 执行完毕,最后修改按钮文本
        ); // 取出波特率,然后打开串口
    
...
  • 这样就完美许多了,而且很灵活,fun()函数的调用甚至可以根据需要而变动.

3.3.端口信息

推荐参考: << Web Serial API (wicg.github.io) >> 4.3 getInfo() method ;

<< JavaScript中判断一个对象是否为一个类的实例_oldjwu的博客-CSDN博客_js 判断是否是实例 >>

  • 2.js中添加如下内容:
/* 说明: 获取端口信息
*  参数: 无
*/
function getInfo(fun)

    if (port_g.constructor == SerialPort) // 保护措施,判断对象是否为类的实例
    
        let usbVendorId = port_g.getInfo()["usbVendorId"]; // 供应商 ID
        if (usbVendorId == undefined)
        
            usbVendorId = "未定义"; // 虚拟串口或部分杂牌usb设备可能没有设置这个参数
        

        let usbProductId = port_g.getInfo()["usbProductId"]; // 产品 ID
        if (usbProductId == undefined)
        
            usbProductId = "未定义";
        

        fun(usbVendorId, usbProductId);
    

  • 获取usb设备id的主要作用其实是用在打开端口上.目前我的设计是选择授权哪个端口然后就打开哪个端口.官方推荐的设计是,可以授权多个端口,只要网页不关闭授权就有效.然后记录ID,在打开端口时可以通过ID过滤器选择打开哪个端口.(我个人感觉这样好麻烦啊,还是整一对一比较方便,一般人使用应该不会有一拖多的情况吧.)
  • 这2个usb设备id都可以在pc的设备管理器-端口(COM和LPT)中查看得到.不过因为我的是虚拟串口,且我忘记是哪个选项了,所以没找到.如果你有硬件设备的话可以找找.验证验证.

  • 另外注意,getInfo()是不需要await关键字的,所以可以不需要async.如果要加上也可以.这里我还是继续采用fun()传参的方式.
  • 1.js文件修改成如下:(箭头函数真方便)
...
f1["端口号"].onclick = function()

    if (f1["开关键"].value == "打开")
    
        alert(`未打开串口`); // 提示没有打开串口
    
    else if (f1["开关键"].value == "关闭") 
    
        getInfo((usbVendorId, usbProductId)=>
        
            alert(`已打开串口:
            供应商 ID : $usbVendorId
            产品 ID   : $usbProductId`); // 反馈已打开串口的信息
        );
    
...

3.4.写数据

推荐参考: << Web Serial API (wicg.github.io) >> 4.6 writable attribute ;

  • 2.js中添加如下内容:
/* 说明: 写数据
*  参数: 数据
*/
async function write(data, fun)

    if (port_g.constructor == SerialPort) // 保护措施
    
        const encoder = new TextEncoder(); // 创建实例
        writer_g = port_g.writable.getWriter(); // 创建写对象
        writer_g.write(encoder.encode(data)); // 开始发送
        // writer_g.close(); // 写终止,停止发送内容,这里用不到,循环发送时才可能用到,后面讲解
        writer_g.releaseLock(); // 写完毕,允许稍后关闭端口
        writer_g = null; // 清空变量

        console.log(data);
        fun(); // 执行完毕,额外执行
    

  • 1.js文件修改成如下:
... 
f3["按钮"].onclick = function()

    if (f3["按钮"].value == "开始发送")
    
        console.log(`开始发送`); // 以下调用 开始发送 函数
        f3["按钮"].value = "停止发送"; // 开始执行,修改按钮文本

        write(f3["发送"].value, ()=>
        
            console.log(`停止发送`);
            f3["按钮"].value = "开始发送"; // 执行完毕,重置文本
        );
    
    else if (f3["按钮"].value == "停止发送") 
    
        console.log(`停止发送`); // 以下调用 停止发送 函数

        // 暂不写手动关闭发送的功能,所以不可以手动将按钮从"停止发送"改为"开始发送".

        // f3["按钮"].value = "开始发送";  // 使用 disabled 属性禁用按钮,或是直接屏蔽这一行,也能起到同样效果
    
...
  • 注意,记得调用writer_g.releaseLock(),如果没有调用这个函数,将无法关闭端口.而调用这个之前写任务必须结束,如果没有结束,需要调用writer_g.close()函数来手动结束写任务.不过如果只是发送一段数据,一般不会用得到.应该是在循环的 写数据流 中会用到.大概.
  • 总得来说简单的写数据实现挺容易的,以下是效果图.网页端选择COM1.

3.5.读数据

推荐参考: << Web Serial API (wicg.github.io) >> 4.5 readable attribute

  • 2.js中添加如下内容:
/* 说明: 读数据
*  参数: 最大读取时间
*/
async function read(time, fun)

    let data = []; // 储存接收数据

    if (port_g.constructor == SerialPort) // 保护措施
    
        if (port_g.readable) // 判断是否可用,只执行一次,不使用 while
        
            reader_g = port_g.readable.getReader(); // 创建读对象

            setTimeout(()=>
            
                if (reader_g != null)
                
                    reader_g.cancel(); // 读终止,停止读任务,
                
   

以上是关于服务器网页版上位机设计 - 03 - 上位机 (完结)的主要内容,如果未能解决你的问题,请参考以下文章

服务器网页版上位机设计 - 02 - 网页版

服务器网页版上位机设计 - 02 - 网页版

服务器网页版上位机设计 - 01 - 服务器

服务器网页版上位机设计 - 01 - 服务器

从零开始 - 网页上位机 - 01

家居环境监測系统设计(PC上位机版)(手机APP版待定)