纯vue 获取usb串口,实现电子秤的对接

news2024/11/29 16:42:56

说明:解决生产上过秤重量手动输入出错问题

效果图:

 一:代码部分

        1、创建一个名字为seriaport.js文件(随便定义,为下面页面引入使用)

        

 
export default class MySerialPort  {
  constructor() {
    this.state = {
      portIndex: undefined,
      ports: [],
      isOpen: false,
      writeType: 1,
      readType: 1,
      isScroll: true,
      readValue: [],
      status:false,
      //port参数
      baudRate: "9600",
      dataBits: "8",
      stopBits: "1",
      parity: "none",
      flowControl: "none",
    };
    this.keepReading=false;
    this.getPorts = this.getPorts.bind(this);
    this.handleRequestPort = this.handleRequestPort.bind(this);
    this.handleChildrenChange = this.handleChildrenChange.bind(this);
    this.readText = this.readText.bind(this);
    this.writeText = this.writeText.bind(this);
    this.handleClear = this.handleClear.bind(this);
    this.a2hex = this.a2hex.bind(this);
    this.hex2a = this.hex2a.bind(this);
    this.hex2atostr=this.hex2atostr.bind(this);
    this.reader={};
    this.closed;
  }
 
  async getPorts() {
    // 获取已授权的全部串口
    let ports = await navigator.serial.getPorts();
    this.setState({
      ports,
    });
  }
  async handleRequestPort() {
    // 请求授权
    try {
      await navigator.serial.requestPort();
      await this.getPorts();
    } catch (e) {
      this.$message.error(e.toString());
    }
  }
  async openPort(portIndex, isOpen,callBack=null) {
    // 打开串口
    let port = this.state.ports[portIndex];
    if (!isOpen) {
      // 关闭串口
      this.keepReading = false;
      this.reader.cancel();
      await this.closed;
      this.handlePortOpen({
        portIndex,
        isOpen,
      });
    } else {
      await port.open({
        baudRate: this.state.baudRate,
        dataBits: this.state.dataBits,
        stopBits: this.state.stopBits,
        parity: this.state.parity,
        flowControl: this.state.flowControl,
      });
      this.handlePortOpen({
        portIndex,
        isOpen,
      });
      this.keepReading = true;
      this.closed=this.readUntilClosed(portIndex,callBack);
    }
  }
  async readUntilClosed(portIndex,callBack=null) {
    let port = this.state.ports[portIndex];
    while (port.readable && this.keepReading) {
      this.reader = port.readable.getReader();
      try {
        let readCache=[]
        while (true) {
          const { value, done } = await this.reader.read();
          if (done) {
            break;
          }
          readCache.push(...value)
          setTimeout(() => {
          if(readCache.length>0){
            this.readText(readCache);
            callBack(readCache)
            readCache=[]
          }
          }, 300);//串口缓存
        }
      } catch (error) {
        this.$message.error(error.toString());
      } finally {
        this.reader.releaseLock();
      }
      await port.close();
    }
  }
  handlePortOpen({ portIndex, isOpen }) {
    // 处理打开串口
    this.setState({
      portIndex,
      isOpen,
    });
  }
  handleChildrenChange(type, value) {
    this.setState({
      [type]: value,
    });
  }
  portWrite(value) {
    return new Promise(async (resolve, reject) => {
      if (!this.state.isOpen) {
        this.$message.error("串口未打开");
        reject();
        return;
      } else {
        let port = this.state.ports[this.state.portIndex];
        const writer = port.writable.getWriter();
        await writer.write(new Uint8Array(value));
        writer.releaseLock();
        resolve(value);
      }
    });
  }
  readText(value) {
    // console.log(value, "读取");
    let newValue = this.state.readValue.concat({
      value,
      type: 1,
    });
    this.setState({
      readValue: newValue,
    });
  }
  writeText(value) {
    // console.log(value, "写入");
    this.portWrite(value).then((res) => {
      let newValue = this.state.readValue.concat({
        value: res,
        type: 1,
      });
      this.setState({
        readValue: newValue,
      });
    });
  }
  handleClear() {
    this.setState({
      readValue: [],
    });
  }
  componentDidMount() {
    this.getPorts();
  }
  handleState(status) {
    this.setState({
      status,
    });
  }
  setState(obj){
    Object.keys(this.state).forEach(key => {
           if(obj[key]!=undefined){
            this.state[key]=obj[key]
           }
      });
  }
  //字节转字符串
  hex2atostr (arr) {
  
    return String.fromCharCode.apply(String, arr);
 
  }
   hex2a(hexx) {
    return String.fromCharCode(hexx);
  }
  //字符转16进制
 a2hex(str) {
    return str.charCodeAt(0);
}
}

        2、创建一个为usb.json 文件(随便定义,为下面页面引入使用)

        文件过大,下载地址(因为大部分是参考这位大佬的,一部分是按照自己的需求进行更改的ZhangY1217的博客_CSDN博客-java,c#,java23种设计模式领域博主)

文件下载链接:https://download.csdn.net/download/ZhangY1217/86662302

3、页面代码部分

<template>
  <span>
    <a-row :gutter="[10, 1]">
      <a-col :span="6">
             <a-form-item :labelCol="{ span: 6 }" :wrapperCol="{ span: 16 }">
                  <template v-slot:label>
//v-has="'cont:Scales'" 为管理员权限,方便调试,普通用户进入页面直接授权,是为了第一次进行调试
                    <a v-has="'cont:Scales'" @click="getScales"><a-icon type="windows"/></a>
                    <span> 重&nbsp;&nbsp; &nbsp; &nbsp; 量 </span>
                  </template>
                  <!-- <a-input-number /> -->
                  <a-input-search
                    placeholder=" "
                    enter-button="换端口"
                    v-model="ObjFrom.qtReal"
                    @pressEnter="OnScreen"
                    :formatter="value => `${value}`.replace(/\B(?=(\d{3})+(?!\d))/g, ',')"
                    :parser="value => `${value}`.replace(/\$\s?|(,*)/g, '')"
                    :precision="2"
                    style="width: 100%"
                    @search="obtainAuthorization"
                    :key="time1"
                  />
                </a-form-item>
</a-col>
</a-row>
</span>
</template>
//js部分
<script>
//第一步新建的文件名称
import MySerialPort from '@api/seriaport.js'
//第二步新建的
import USBDevice from '@api/usb.json'
export default {
  name: 'MzInProducts',
  components: {
    MzOrderlist,
    LbCard,
    modalScales
  },
  data() {
    return {
      // 电子秤
      form: {
        baudRate: '9600',
        dataBits: '8',
        stopBits: '1',
        parity: 'none',
        flowControl: 'none',
        desc: '',
        type: '1',
        isShowHistory: false,
        port: '1'
      },
      btnType: 'primary',
    //这个portsList是我自己写死的,因为我的电子秤是这个名称
      portsList: [
        {
          label: 'PL2303 Serial Port / Mobile Action MA-8910P',
          value: 1
        }
      ],

ObjFrom:{},
      readType: 1,
      time1: '',
    }
  },
  mounted() {
    this.loadData()
  },
  methods: {
    // 链接配置页面

    // 电子称配置
    loadData() {
      console.log(navigator)
      console.log('serial' in navigator)
      if ('serial' in navigator) {
        this.myserialport = new MySerialPort()
        this.getPorts()
        navigator.serial.addEventListener('connect', e => {
          this.$message.success('电子秤已连接')
          this.getPorts()
        })
        navigator.serial.addEventListener('disconnect', e => {
          this.$message.error('设备已断开')
        })
        this.restaurants = this.loadAll()
      } else {
        this.$message.error('当前浏览器不支持,请换成谷歌浏览器8 9 以上浏览器')
      }
    },
    //   链接电子秤
    async connectBtn() {
      if (this.btnType == 'primary') {
        try {
          this.myserialport.state.baudRate = this.form.baudRate
          this.myserialport.state.dataBits = this.form.dataBits
          this.myserialport.state.stopBits = this.form.stopBits
          this.myserialport.state.parity = this.form.parity
          this.myserialport.state.flowControl = this.form.flowControl
          await this.myserialport.openPort(this.form.port, true, this.callBack)
        } catch (error) {
          this.$message.error('串口连接失败!请关闭其他页面占用电子称的系统,确保电子秤的是唯一的值')
        }
        if (this.myserialport.state.isOpen) {
          this.$message.success('电子秤链接成功')
          this.btnType = 'dashed'
          this.btnText = '关闭电子称'
        }
      } else {
        this.myserialport.openPort(this.form.port, false, this.callBack)
        this.$message.success('串口关闭成功')
        this.btnType = 'primary'
        this.btnText = '链接电子秤'
      }
    },
    //接受数据的回调
    callBack(value) {
//this.ObjFrom.qtReal 是input 的值
      if (this.form.isShowHistory) this.ObjFrom.qtReal = this.getNumberFromStr(this.readLi().join(''))
      else {
        if (value.length > 0) this.ObjFrom.qtReal = this.getNumberFromStr(this.myserialport.hex2atostr(value))
      }
      console.log(this.ObjFrom.qtReal)
      this.time1 = Date.now()
    },
    clearHistory() {
      this.form.desc = ''
      this.myserialport.state.readValue = []
    },
    loadHistory() {
      if (this.form.isShowHistory) {
        this.form.desc = this.readLi().join('')

        console.log(temp[temp.length - 1].join(''))
      } else {
        let temp = this.readLi()
        if (temp.length > 0) this.form.desc = temp[temp.length - 1].join('')
        console.log(temp[temp.length - 1].join(''))
      }
    },
    readLi() {
      let readType = this.readType
      return this.myserialport.state.readValue.map((items, index) => {
        const item = items.value
        const type = items.type // 1接收,2发送
        let body = []
        if (item !== undefined) {
          let strArr = []
          for (let hex of Array.from(item)) {
            strArr.push(hex.toString(16).toLocaleUpperCase())
          }
          if (strArr.includes('D') && strArr.includes('A')) {
            if (strArr.indexOf('A') - strArr.indexOf('D') === 1) {
              strArr.splice(strArr.indexOf('D'), 1)
              strArr.splice(strArr.indexOf('A'), 1, <br key={0} />)
            }
          }
          strArr = strArr.map(item => {
            if (typeof item === 'string') {
              if (readType === 1) {
                return this.myserialport.hex2a(parseInt(item, 16))
              } else if (readType === 2) {
                return item + ' '
              }
            }
            return item
          })
          if (typeof strArr[strArr.length - 1] === 'string') {
            strArr.push('\r\n')
          }
          body.push(strArr.join(''))
        }
        return body
      })
    },
   
    //授权
    async obtainAuthorization() {
      if ('serial' in navigator) {
        console.log('The Web Serial API is supported.')
        if (!this.myserialport) this.myserialport = new MySerialPort()
        try {
          await this.myserialport.handleRequestPort()
          this.$message.success('串口授权成功')
          this.getPortInfo(this.myserialport.state.ports)
        } catch (error) {
          this.$message.warning('未选择新串口授权!')
        }
      } else {
        this.$message.error('当前为HTTP模式或者浏览器版本过低,不支持网页连接串口')
      }
    },
    //串口列表初始化
    getPortInfo(portList) {
      this.portsList = []
      portList.map((port, index) => {
        const { usbProductId, usbVendorId } = port.getInfo()
        if (usbProductId === undefined || usbVendorId === undefined) {
          // this.portsList.push({ label: "未知设备" + index, value: index });
        } else {
          const usbVendor = USBDevice.filter(item => parseInt(item.vendor, 16) === usbVendorId)
          let usbProduct = []
          if (usbVendor.length === 1) {
            usbProduct = usbVendor[0].devices.filter(item => parseInt(item.devid, 16) === usbProductId)
          }
          console.log(usbProduct[0].devname)
          this.portsList.push({ label: usbProduct[0].devname, value: index })
          if (this.portsList.length > 0) {
            this.form.port = this.portsList[0].value
          }
        }
      })
    },
    async getPorts() {
      await this.myserialport.getPorts()
      this.getPortInfo(this.myserialport.state.ports)
      this.connectBtn()
    },
    querySearch(queryString, cb) {
      var restaurants = this.restaurants
      var results = queryString ? restaurants.filter(this.createFilter(queryString)) : restaurants
      // 调用 callback 返回建议列表的数据
      cb(results)
    },
    createFilter(queryString) {
      return restaurant => {
        return restaurant.value.toLowerCase().indexOf(queryString.toLowerCase()) === 0
      }
    },
    loadAll() {
      return [
        { value: '110' },
        { value: '300' },
        { value: '600' },
        { value: '1200' },
        { value: '2400' },
        { value: '4800' },
        { value: '7200' },
        { value: '9600' },
        { value: '14400' },
        { value: '19200' },
        { value: '28800' },
        { value: '38400' },
        { value: '56000' },
        { value: '57600' },
        { value: '76800' },
        { value: '115200' },
        { value: '230400' },
        { value: '460800' }
      ]
    },
    // 截取方法
    getNumberFromStr(str) {
      let matchArr = str.match(/\d+\.\d+/)
      if (matchArr && matchArr.length) {
        let num = parseFloat(matchArr[0])
          .toFixed(2)
          .toString()
        if (num.indexOf('.') === -1) {
          return num
        }
        let numArr = num.split('.')
        if (numArr[0] !== '') {
          numArr[0] = parseInt(numArr[0]).toString()
        }
        if (numArr[0] === '0' && numArr[0].length > 1) {
          numArr[0] = numArr[0].slice(1)
        }
        return numArr.join('.')
      }
      return ''
    },
// ScalesFrom页面是调试页面
    getScales() {
      this.$refs.ScalesFrom.showDrawer()
    }
  }
}
</script>

注: ScalesFrom 后期我在更新上去

4、这个搭建只适合(只支持本机访问和https的,你这个要弄nginx和自己生成证书来用https的才能访问)但是可以通过别的方式解决本地服务器,使用一个局域网访问的需求(解决方法5)

5、解决谷歌服务器安全机制(因为谷歌浏览器有安全机制,我们是http访问,而不是https,https不会有这种问题

解决方案:

1、谷歌浏览器访问:chrome://flags/#unsafely-treat-insecure-origin-as-secure

设置为Enabled  ---一定点击下面Relaunch

 2、打开电脑终端:

chrome(为你自己安装路径) --unsafely-treat-insecure-origin-as-secure=http://192.168.0.99(http://192.168.0.99 这个是我们服务地址,改为你自己的)

我的地址是("C:\Program Files (x86)\Chromiumbrowser\Chromium.exe")则是"C:\Program Files (x86)\Chromiumbrowser\Chromium.exe"  --unsafely-treat-insecure-origin-as-secure=http://192.168.0.99
图片:

 设置成功之后图片:(这个X号不能删除,否则会清空,我自己电脑试过几次会清空)

问题图片

注:有问题私信留言

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

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

相关文章

超级干货!前端入门先学什么?前端自学路线分享!

各位同学&#xff0c;下午好~之前给大家分享了前端岗位的面试题&#xff0c;小源能看的出来&#xff0c;还是有不少同学想入行前端的&#xff01;那除了会面试&#xff0c;还要有充足丰富的知识储备&#xff0c;这样才能拿下工作&#xff01; 好程序员今天就给大家整理了一份前…

C++进阶 —— set

目录 一&#xff0c;set介绍 二&#xff0c;set使用 一&#xff0c;set介绍 set是按照特定次序存储元素的关联式容器&#xff0c;元素不可重复&#xff1b;set中的元素不能在容器中修改(元素总是const)&#xff0c;但是可从容器中插入和删除它们&#xff1b;set中的元素总是按…

【Linux】进程间通信详解

环境&#xff1a;centos7.6&#xff0c;腾讯云服务器Linux文章都放在了专栏&#xff1a;【Linux】欢迎支持订阅 进程间通信介绍 什么是进程间通信&#xff1f; 进程间通信&#xff08;Interprocess communication&#xff0c;简称IPC&#xff09;就是让程序员能够协调不同的进…

【Apache 网页优化】

文章目录 一、Apahce 网页优化1、网页压缩2、网页缓存 二、Apachen的安全优化1、隐藏版本信息2、Apache 防盗链 一、Apahce 网页优化 1、网页压缩 1.检查是否安装 mod_deflate 模块 apachectl -t -D DUMP_MODULES | grep "deflate"2.如果没有安装mod_deflate 模块…

Java基础 流程控制语句

顺序结构 顺序结构就是程序从上到下逐行地执行。表达式语句都是顺序执行的。并且上一 行对某个变量的修改对下一行会产生影响。 public class StatementTest{public static void main(String[] args){int x 1;int y 2; System.out.println("x " x);System.out.p…

非科班自学一年心得,学弟学妹别瞎学了

大家好&#xff0c;我是帅地。 前两天我发了一篇亲学弟自学一年拿大厂 offer 的文章&#xff1a;非科班&#xff0c;帅地亲学弟自学一年拿到大厂offer了 不过那一篇只写了自己转行开发岗的心里变化&#xff0c; 这两天学弟又在知识星球发了一篇关于找工作的万字长文 说实话&…

ISO21434 项目网络安全管理

目录 一、概述 二、目标 三、输入 3.1 先决条件 3.2 进一步支持信息 四、要求和建议 4.1 网络安全责任 4.2 网络安全规划 4.3 裁剪 4.4 重用 4.5 非上下文组件 4.6 现成组件 4.7 网络安全案例&#xff08;Cybersecurity case&#xff09; 4.8 网络安全评估&#…

【惊叹】AI进步的速度太快,我们赶不上了?

文章目录 前言一、LoRA二、QLoRA1、环境准备2、推理就是直接 跑shscripts/generate.sh。3、前面的环境和数据都没问题了&#xff0c;运行scripts/generate.sh。 总结 前言 AI 领域的技术&#xff0c;真是隔一段时间就有一个新突破&#xff01; 全民都能训练大模型的时代&…

TypeScript算法题实战——剑指 Offer篇(3)

随着TypeScript的流行&#xff0c;越来越多的开发者开始使用TypeScript来解决算法问题。 在本文中&#xff0c;我们将使用TypeScript来解决剑指offer的算法题。这些问题涵盖了各种各样的主题&#xff0c;包括数组、字符串、链表、树、排序和搜索等。我们将使用TypeScript的强类…

【MySQL高级篇笔记 (中-索引的数据结构) 】

此笔记为尚硅谷MySQL高级篇部分内容 目录 一、索引及其优缺点 1、索引概述 2、优点 3、缺点 二、InnoDB中索引的推演 1、设计索引 1.一个简单的索引设计方案 2.InnoDB中的索引方案 2、常见索引概念 1. 聚簇索引 2. 二级索引&#xff08;辅助索引、非聚簇索引&#…

Node.js详解(一):基础知识

文章目录 一、Node.js介绍二、Node.js的优势三、Node.js的特点1、V8虚拟机2、事件驱动3、异步、非堵塞I/O 四、NodeJS带来的对系统瓶颈的解决方案1. 并发连接2. I/O阻塞 五、NodeJS的优缺点1、优点&#xff1a;2、缺点&#xff1a; 六、适合NodeJS的场景1、RESTful API2、统一W…

VMware、Ubuntu安装以及虚拟机复制粘贴问题

安装VMware 下载阿里云链接&#xff08;16 pro&#xff09;&#xff1a;VMware https://www.aliyundrive.com/s/ot9dhPNdSwC 安装&#xff1a;选一下安装地址&#xff0c;一直下一步即可。&#xff08;可能会要求重启电脑&#xff0c;重启即可&#xff09; 然后点击“许可证”…

Java 高级应用-多线程-(四)FutureTask的介绍及使用

Java多线程之FutureTask的介绍及使用 FutureTask属于java.util.concurrent 包&#xff1b;FutureTask表示可取消的异步计算。FutureTask类提供了一个Future的基本实现 &#xff0c;具有启动和取消计算的方法&#xff0c;查询计算是否完整&#xff0c;并检索计算结果。结果只能…

Camtasia2023试用版新功能介绍

Camtasia 2023在易用性更进一步&#xff0c;再一次降低了制作精美视频的门槛&#xff0c;下面看一看&#xff0c;Camtasia 2023有哪些的新功能&#xff01;包括影像、音效、鼠标移动轨迹、解说声音等等内容的录制&#xff0c;并且软件还可以提供即时播放和编辑压缩的功能&#…

如何监控电动车充电桩能耗?

一 背景 随着新能源汽车的快速发展&#xff0c;像特斯拉、BYD、蔚来、小鹏和理想等品牌的电动汽车在我们的日常生活中越来越多了&#xff0c;可见电动汽车如今已逐渐被我们所认可了。同汽油车需要加油一样&#xff0c;电动汽车需要充电&#xff0c;如此一来&#xff0c;电动汽…

第14届蓝桥杯省赛真题剖析-2023年5月7日Scratch编程中级组

[导读]&#xff1a;超平老师的《Scratch蓝桥杯真题解析100讲》已经全部完成&#xff0c;后续会不定期解读蓝桥杯真题&#xff0c;这是Scratch蓝桥杯真题解析第142讲。 第14届蓝桥杯Scratch省赛真题&#xff0c;这是2023年5月7日举办的省赛中级组试题&#xff0c;比赛仍然采取线…

洗地机充电底座语音芯片选型?NV040DS语音芯片

一、洗地机语音提示功能的价值 洗地机充电底座加入语音提示功能&#xff0c;主要是为了提高洗地机的智能化程度和使用便利性&#xff01; 1. 提高使用效率&#xff1a;底座语音提示充电状态可以使用户更方便地掌握底座电量和洗地机的使用情况&#xff0c;从而更快捷地对底座进…

ProtoBuf 语法(二)

系列文章 ProtoBuf 语法&#xff08;一&#xff09; ProtoBuf 语法&#xff08;三&#xff09; 文章目录 八、更新消息8.1 更新规则8.2 reserved 保留字段8.3 验证错误删除字段造成的数据损坏8.4 未知字段及其获取方法8.5 验证未知字段 八、更新消息 8.1 更新规则 如果现有的…

mysql中的count(1)、count(*)、count(id)哪个更快?

今天和大家聊一下mysql中的count()方法 我们日常开发中&#xff0c;经常会用到count()命令&#xff0c;有的人用count(*)&#xff0c;有的人用count(1)&#xff0c;还有的人用count(id)&#xff0c;那么这几种写法都有什么区别呢&#xff1f;哪种方法效率更高呢&#xff1f;今…

LangChain 查询使用指「北」

一只鹦鹉加上一根链条&#xff0c;组成了时下最流行的 AI 话题热门榜选手——LangChain。 LangChain 是一种 AI 代理工具&#xff0c;可以为以 ChatGPT 为代表的额大语言模型&#xff08;LLM&#xff09;增添更多功能。此外&#xff0c;LangChain 还具备 token 和上下文管理功能…