服务器网页版上位机设计 - 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) >>
// 直接赋值,简单方便
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) >>
- 在开始前还需要先准备一下串口工具,因为手头可能没有能提供串口连接的硬件设备.使用虚拟串口做实验更加方便.使用
VSPD
创建虚拟串口,然后网页版上位机连接一个,串口调试助手连接一个.
推荐参考:
<< Virtual Serial Port Driver 10 破解版 - 星光的博客 (starxg.com) >> (半天找不到免安装,就安装一下吧)
<< 友善串口调试助手下载与安装 - 知乎 (zhihu.com) >> (找了个可以免安装的)
3.1.异步&延时
- 这里介绍一个重要概念,异步.为了防止程序堵塞,浏览器的串口模块是需要异步使用的.使用关键词
async
和await
,使用的时候只需要注意两点即可,1)不能用返回值,2)await
必须在async
函数内使用.
- 在串口通讯时,也很难避免会用到延时等待.在
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 - 上位机 (完结)的主要内容,如果未能解决你的问题,请参考以下文章