前端javascript实现与外接设备之间的串口通信

前言

最近在项目中需要实现通过串口与外接电流表之间的通信,拿到电流表的实时测试电流同步显示在页面上,由于公司人员不多,这个重任自然而然落到我的头上,起初我也是不敢相信javascript有这么强大,还能通过串口操纵外接硬件设备,但我冷静之后百度了一下果然百度出来serialport.js这么个东西。
开发环境:electron-vue+node+serialport.js
先上效果图:
在这里插入图片描述
在这里插入图片描述

实现的功能

  1. 页面加载默获取所有串口列表遍历出需要打开的串口,将串口打开
  2. 点击开始测试,每一秒钟向电流表写入一次返回电流值得指令,从而达到每秒中更新一次页面上的电流值,实现页面电流测试值与电流表显示一致
  3. 点击停止测试,中止电流测试,停止向电流表写入指令
  4. 点击打开串口,将串口打开。再次点击关闭串口

安装serialport.js

安装这个东西有点麻烦,大家自行百度安装方法,且安装之后会导致各种报错,比如我就经历了只要 npm i 其他包就会导致serialport.js包报错,这时候只要一开始就把node_modules中的serialport备份,报错时删除替换即可解决了。

实现步骤

  1. 在conponent创建电流测试组件
    在这里插入图片描述
  2. 定义视图层,一个按钮,一个dialog弹窗
<template>
  <div>
    <el-button
       style="width: 74%;"
            class="m-t-15 f-r m-r-15"
      type="primary"
      @click="openElectricity"
      >电流测试</el-button
    >
    <el-dialog
      :title="'电流测试  串口号-' + portName"
      center
      :visible.sync="dialogVisible"
      width="40%"
      @close="close"
      @opened="opened"
    >
      <div class="content">
        <div
          class="mytext"
          :class="{
            green: ElectricityData <= standard,
            red: ElectricityData > standard,
          }"
        >
          {{ ElectricityData }} mA
        </div>
        <el-progress
          :stroke-width="20"
          :percentage="percentage"
          :format="format"
        ></el-progress>
        <div class="result">
          电流测试结果:
          <span :class="{ red: ElectricityData > standard }">{{
            resultVal
          }}</span>
        </div>
        <div class="testTime">
          测试时间
          <el-input
            @keyup.enter.native="setTime"
            v-model.number="testTime"
            style="width: 80px; margin: 0 15px"
          ></el-input
          ></div>
      </div>
      <span slot="footer" class="dialog-footer">
        <el-button type="primary" @click="startTest">开始测试</el-button>
        <el-button @click="endTest">停止测试</el-button>
        <el-button @click="closePort" type="success">{{
          isOpen ? "关闭串口" : "打开串口"
        }}</el-button>
      </span>
    </el-dialog>
  </div>
</template>
  1. 组件script标签内定义一个prot变量 methods中定义openPort打开串口方法
<script>
var port;
export default {
  async mounted() {
    await this.openPort();
  },
  methods: {
    //  打开串口方法
    async openPort() {
      // 串口操作--------获取串口信息
      var serialport = require("serialport");
      //  获取串口列表
      const res = await serialport.list();
      let portName = "";
      res.forEach((val) => {
        if (val.manufacturer === "FTDI") {
          portName = val.path;
          this.portName = val.path;
        }
      });
      if (portName === "") return this.$message.error("串口未找到!");
      //  设置打开串口参数
      port = new serialport(
        portName,
        {
          baudRate: 9600,
          dataBits: 8,
          autoOpen: false,
        },
        false
      );
      // 打开串口并提示
      port.open((error) => {
        if (error) {
          this.$message.error("串口打开失败!");
          this.isOpen = false;
          console.log("打开端口" + "错误:" + error);
        } else {
          this.$message.success("串口开启!");
          console.log("打开端口成功,正在监听数据中");
          this.isOpen = true;
        }
      });
      //  设置接收值为hex类型
      port.setEncoding("hex");
      //  监听数据 并将返回的16进制转为浮点数 利用引入HexToSingle 方法
      port.on("data", (data) => {
        if (data.length === 16) {
          let str = data.slice(6, 14);
          let str2 = HexToSingle(str);
          str2 = str2 == 0 ? 0 : str2.toFixed(4);
          this.ElectricityData = str2;
          console.log(this.ElectricityData);
        } else {
          this.hexData += data;
          let reg = /.{4}/g;
          let rs = this.hexData.match(reg);
          if (this.hexData.length === 16) {
            let str3 = this.hexData.slice(6, 14);
            let str4 = HexToSingle(str3);
            str4 = str4 == 0 ? 0 : str4.toFixed(4);
            this.ElectricityData = str4;
            console.log(this.ElectricityData);
          }
        }
      });
      //  监听数据
      port.on("error", () => {
        console.log("hiulhgiolgui");
      });
     },
    }
  }
}
</script>
  1. methods中定义一个向串口写入数据的方法,点击开始测试定时想串口写入数据,设备响应的定时返回数据,页面数据也响应的更新,这里设置每秒写入一次。同时开始测试前判断串口是否打开,未打开则提示打开。用户点击打开串口遍历所有串口找到需要打开的那个。
var port;
export default {
  data() {
    return {
      dialogVisible: false, //  测试弹窗显隐
      writeData: "88AE0011", //  写入的数据
      ElectricityData: 0, //  电流测试值
      hexData: "", //  16进制转浮点数的结果
      portName: "", //端口名称
      stertId: null, //  计时器id
      percentage: 0, //  进度条的值
      resultVal: "待测试", //  测试结果提示
      standard: 0.05, //  电流测试标准值
      isOpen: false, //  串口是否打开
      testTime: 25, //  测试时间
      add: 4, //  进度条步长
    };
  },
  async mounted() {
    await this.openPort();
  },
  methods: {
    format(percentage) {
      let str = "";
      if (percentage === 0) {
        str = `等待测试`;
      } else if (percentage > 0 && percentage < 100) {
        str = "正在测试...";
      } else if (percentage > 99) {
        str = "测试完成";
      }
      return str;
    },
    //  打开串口方法
    async openPort() {
      // 串口操作--------获取串口信息
      var serialport = require("serialport");
      //  获取串口列表
      const res = await serialport.list();
      let portName = "";
      res.forEach((val) => {
        if (val.manufacturer === "FTDI") {
          portName = val.path;
          this.portName = val.path;
        }
      });
      if (portName === "") return this.$message.error("串口未找到!");
      //  设置打开串口参数
      port = new serialport(
        portName,
        {
          baudRate: 9600,
          dataBits: 8,
          autoOpen: false,
        },
        false
      );
      // 打开串口并提示
      port.open((error) => {
        if (error) {
          this.$message.error("串口打开失败!");
          this.isOpen = false;
          console.log("打开端口" + "错误:" + error);
        } else {
          this.$message.success("串口开启!");
          console.log("打开端口成功,正在监听数据中");
          this.isOpen = true;
        }
      });
      //  设置接收值为hex类型
      port.setEncoding("hex");
      //  监听数据
      port.on("data", (data) => {
        if (data.length === 16) {
          let str = data.slice(6, 14);
          let str2 = HexToSingle(str);
          str2 = str2 == 0 ? 0 : str2.toFixed(4);
          this.ElectricityData = str2;
          console.log(this.ElectricityData);
        } else {
          this.hexData += data;
          let reg = /.{4}/g;
          let rs = this.hexData.match(reg);
          if (this.hexData.length === 16) {
            let str3 = this.hexData.slice(6, 14);
            let str4 = HexToSingle(str3);
            str4 = str4 == 0 ? 0 : str4.toFixed(4);
            this.ElectricityData = str4;
            console.log(this.ElectricityData);
          }
        }
      });
    },
    //  写入数据方法
    writeport() {
      this.hexData = "";
      port.write(this.writeData, "hex", function (err, a) {
        if (err) {
          console.log(err);
          return console.log("Error on write: ", err.message);
        }
      });
    },

    //  电流测试按钮点击
    openElectricity() {
      this.dialogVisible = true;
    },
    //  点击开始测试
    startTest() {
      clearInterval(this.stertId);
      this.percentage = 0;
      if (!this.isOpen) return this.$message.error("请先打开串口!");
      this.$message.success("电流测试开始!");
      this.stertId = setInterval(() => {
        this.percentage += this.add;
        this.writeport();
        if (this.percentage > 100) this.endTest();
      }, 1000);
      this.resultVal = "待测试";
    },
    //  点击停止测试
    endTest() {
      this.$message.success("电流测试停止!");
      clearInterval(this.stertId);
      this.stertId = null;
      this.percentage = 0;
      //  进行合格判断显示
      setTimeout(() => {
        if (this.ElectricityData <= this.standard) {
          this.resultVal = "测试合格";
        } else if (this.ElectricityData > this.standard) {
          this.resultVal = "测试不合格";
        }
      }, 1000);
    },
    //  弹窗关闭
    close() {
      this.ElectricityData = 0;
      this.endTest();
    },
    opened() {
      this.startTest();
    },
    //  关闭串口
    closePort() {
      if (this.isOpen) {
        this.endTest();
        port.close((err) => {
          console.log(err);
          if (!err) {
            this.$message.success("串口关闭成功!");
            this.isOpen = false;
          } else {
            this.$message.success("串口已经关闭!");
            this.isOpen = false;
          }
        });
      } else {
        this.openPort();
      }
    },
    //  测试时长设置
    setTime() {
      if (this.testTime < 25) {
        this.testTime = 25;
        return this.$message.error("测试时间过低!");
      }
      this.$message.success("测试时间设置成功!");
      this.add = 100 / this.testTime;
      this.startTest();
    },
  },
};
  1. 就到这里为止页面引入组件即可,这里附上电流测试串口通信组件完整代码。
<template>
  <div>
    <el-button
       style="width: 74%;"
            class="m-t-15 f-r m-r-15"
      type="primary"
      @click="openElectricity"
      >电流测试</el-button
    >
    <el-dialog
      :title="'电流测试  串口号-' + portName"
      center
      :visible.sync="dialogVisible"
      width="40%"
      @close="close"
      @opened="opened"
    >
      <div class="content">
        <div
          class="mytext"
          :class="{
            green: ElectricityData <= standard,
            red: ElectricityData > standard,
          }"
        >
          {{ ElectricityData }} mA
        </div>
        <el-progress
          :stroke-width="20"
          :percentage="percentage"
          :format="format"
        ></el-progress>
        <div class="result">
          电流测试结果:
          <span :class="{ red: ElectricityData > standard }">{{
            resultVal
          }}</span>
        </div>
        <div class="testTime">
          测试时间
          <el-input
            @keyup.enter.native="setTime"
            v-model.number="testTime"
            style="width: 80px; margin: 0 15px"
          ></el-input
          ></div>
      </div>
      <span slot="footer" class="dialog-footer">
        <el-button type="primary" @click="startTest">开始测试</el-button>
        <el-button @click="endTest">停止测试</el-button>
        <el-button @click="closePort" type="success">{{
          isOpen ? "关闭串口" : "打开串口"
        }}</el-button>
      </span>
    </el-dialog>
  </div>
</template>
<script>
import { HexToSingle } from "../utils/hextosingle";
var port;
export default {
  data() {
    return {
      dialogVisible: false, //  测试弹窗显隐
      writeData: "88AE0011", //  写入的数据
      ElectricityData: 0, //  电流测试值
      hexData: "", //  16进制转浮点数的结果
      portName: "", //端口名称
      stertId: null, //  计时器id
      percentage: 0, //  进度条的值
      resultVal: "待测试", //  测试结果提示
      standard: 0.05, //  电流测试标准值
      isOpen: false, //  串口是否打开
      testTime: 25, //  测试时间
      add: 4, //  进度条步长
    };
  },
  async mounted() {
    await this.openPort();
  },
  methods: {
    format(percentage) {
      let str = "";
      if (percentage === 0) {
        str = `等待测试`;
      } else if (percentage > 0 && percentage < 100) {
        str = "正在测试...";
      } else if (percentage > 99) {
        str = "测试完成";
      }
      return str;
    },
    //  打开串口方法
    async openPort() {
      // 串口操作--------获取串口信息
      var serialport = require("serialport");
      //  获取串口列表
      const res = await serialport.list();
      let portName = "";
      res.forEach((val) => {
        if (val.manufacturer === "FTDI") {
          portName = val.path;
          this.portName = val.path;
        }
      });
      if (portName === "") return this.$message.error("串口未找到!");
      //  设置打开串口参数
      port = new serialport(
        portName,
        {
          baudRate: 9600,
          dataBits: 8,
          autoOpen: false,
        },
        false
      );
      // 打开串口并提示
      port.open((error) => {
        if (error) {
          this.$message.error("串口打开失败!");
          this.isOpen = false;
          console.log("打开端口" + "错误:" + error);
        } else {
          this.$message.success("串口开启!");
          console.log("打开端口成功,正在监听数据中");
          this.isOpen = true;
        }
      });
      //  设置接收值为hex类型
      port.setEncoding("hex");
      //  监听数据
      port.on("data", (data) => {
        if (data.length === 16) {
          let str = data.slice(6, 14);
          let str2 = HexToSingle(str);
          str2 = str2 == 0 ? 0 : str2.toFixed(4);
          this.ElectricityData = str2;
          console.log(this.ElectricityData);
        } else {
          this.hexData += data;
          let reg = /.{4}/g;
          let rs = this.hexData.match(reg);
          if (this.hexData.length === 16) {
            let str3 = this.hexData.slice(6, 14);
            let str4 = HexToSingle(str3);
            str4 = str4 == 0 ? 0 : str4.toFixed(4);
            this.ElectricityData = str4;
            console.log(this.ElectricityData);
          }
        }
      });
    },
    //  写入数据方法
    writeport() {
      this.hexData = "";
      port.write(this.writeData, "hex", function (err, a) {
        if (err) {
          console.log(err);
          return console.log("Error on write: ", err.message);
        }
      });
    },

    //  电流测试按钮点击
    openElectricity() {
      this.dialogVisible = true;
    },
    //  点击开始测试
    startTest() {
      clearInterval(this.stertId);
      this.percentage = 0;
      if (!this.isOpen) return this.$message.error("请先打开串口!");
      this.$message.success("电流测试开始!");
      this.stertId = setInterval(() => {
        this.percentage += this.add;
        this.writeport();
        if (this.percentage > 100) this.endTest();
      }, 1000);
      this.resultVal = "待测试";
    },
    //  点击停止测试
    endTest() {
      this.$message.success("电流测试停止!");
      clearInterval(this.stertId);
      this.stertId = null;
      this.percentage = 0;
      //  进行合格判断显示
      setTimeout(() => {
        if (this.ElectricityData <= this.standard) {
          this.resultVal = "测试合格";
        } else if (this.ElectricityData > this.standard) {
          this.resultVal = "测试不合格";
        }
      }, 1000);
    },
    //  弹窗关闭
    close() {
      this.ElectricityData = 0;
      this.endTest();
    },
    opened() {
      this.startTest();
    },
    //  关闭串口
    closePort() {
      if (this.isOpen) {
        this.endTest();
        port.close((err) => {
          console.log(err);
          if (!err) {
            this.$message.success("串口关闭成功!");
            this.isOpen = false;
          } else {
            this.$message.success("串口已经关闭!");
            this.isOpen = false;
          }
        });
      } else {
        this.openPort();
      }
    },
    //  测试时长设置
    setTime() {
      if (this.testTime < 25) {
        this.testTime = 25;
        return this.$message.error("测试时间过低!");
      }
      this.$message.success("测试时间设置成功!");
      this.add = 100 / this.testTime;
      this.startTest();
    },
  },
};
</script>
<style  scoped>
.content {
  text-align: center;
}
.mytext {
  text-align: center;
  height: 150px;
  line-height: 150px;
  font-size: 50px;
  font-weight: 700;
  border: 5px solid #111;
  width: 300px;
  margin: 0 auto;
  background-color: #555;
  color: #eee;
}
.el-progress {
  width: 400px;
  margin: 40px auto;
}
.green {
  color: greenyellow;
}
.red {
  color: red;
}
.result {
  margin-top: 40px;
  font-size: 26px;
  font-weight: 700;
}
.testTime {
  margin-top: 20px;
  font-size: 24px;
}
</style>
  1. 这里是16进制转浮点数方法代码,因为设备返回的是16进制数据,所以要转为浮点数渲染到页面,utils文件夹新建hextosingle.js文件 电流测试组件中引入使用。
//  16进制转浮点数
function InsertString(t, c, n) {
    var r = new Array();
    for (var i = 0; i * 2 < t.length; i++) {
        r.push(t.substr(i * 2, n));
    }
    return r.join(c);
}
//需要用到的函数
function FillString(t, c, n, b) {
    if ((t == "") || (c.length != 1) || (n <= t.length)) {
        return t;
    }
    var l = t.length;
    for (var i = 0; i < n - l; i++) {
        if (b == true) {
            t = c + t;
        }
        else {
            t += c;
        }
    }
    return t;
}
/**
 * 
 * @param {String|Number} t 16进制数据
 * @returns {String} 返回16进制转化后的浮点数
 */
//16进制转浮点数
export function HexToSingle(t) {
    let jiang = t
    t = jiang.replace(/\s+/g, "");
    if (t == "") {
        return "";
    }
    if (t == "00000000") {
        return "0";
    }
    if ((t.length > 8) || (isNaN(parseInt(t, 16)))) {
        return "Error";
    }
    if (t.length < 8) {
        t = FillString(t, "0", 8, true);
    }
    t = parseInt(t, 16).toString(2);
    t = FillString(t, "0", 32, true);
    var s = t.substring(0, 1);
    var e = t.substring(1, 9);
    var m = t.substring(9);
    e = parseInt(e, 2) - 127;
    m = "1" + m;
    if (e >= 0) {
        m = m.substr(0, e + 1) + "." + m.substring(e + 1)
    }
    else {
        m = "0." + FillString(m, "0", m.length - e - 1, true)
    }
    if (m.indexOf(".") == -1) {
        m = m + ".0";
    }
    var a = m.split(".");
    var mi = parseInt(a[0], 2);
    var mf = 0;
    for (var i = 0; i < a[1].length; i++) {
        mf += parseFloat(a[1].charAt(i)) * Math.pow(2, -(i + 1));
    }
    m = parseInt(mi) + parseFloat(mf);
    if (s == 1) {
        m = 0 - m;
    }
    return m;
}

ok 跑路!

  • 3
    点赞
  • 59
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值