在Vue中如何连接串口

本文详细介绍了如何在Vue3+Vite+Electron框架中处理串口通信,包括串口概念、JavaScript宿主环境的重要性、如何使用`serialport`库、串口号查询、本地调试以及开发串口通信调试助手的代码示例。
摘要由CSDN通过智能技术生成

1. 前言: 在Vue中如何连接串口,需要首先了解串口是什么,如下图

串口介绍

简单来说,串口就是一个通信协议,功能就是收发数据

2. js的宿主环境

问: 为什么需要了解js的宿主环境呢?

答: 因为在不同的宿主环境中,js的API是不一样的,例如在浏览器宿主环境中,可以使用DOM,BOM等API,但是这些API就无法再Nodejs中使用,同样的道理,Nodejs中的fs模块,path模块等也无法在浏览器宿主环境中使用

问题所在: 连接串口,需要Nodejs环境支持,因为串口并不是一个网络通信,而是通过串口线连接COM扣实现的,所以需要使用到电脑本身的一些API,这些API浏览器端是不支持的,只有Nodejs支持

如何解决问题: 使用Vue3+Vite+Electron框架实现,因为Electron是构建桌面端应用的,并且Electron已经内置了浏览器内核,所以他的宿主环境不是浏览器环境,而是nodejs环境,所以在这个框架中可以直接使用Nodejs的API

3. 框架搭建(这块内容比较繁琐,我后面专门写一篇文章来讲解)

4. 串口连接

  1. 第三方库下载: serialport(可以通过npm 或 yarn进行下载 npm i serialport 或 yarn add serialport)

  2. 使用serialport对串口进行连接

// 导入串口第三方库(现在是Nodejs环境,所以可以使用require)
const { SerialPort } = window.require("serialport");

// 建立串口连接
const sp = new SerialPort({
          // 要连接的串口号
          path: "COM1",
          // 比特率
          baudRate: 9600,
          // 数据位
          dataBits: 8,
          // 校验位
          parity: "none"
});

// 监听串口是否连接成功
sp.on("open", () => {
     console.log("串口连接成功")
});

// 接受串口消息
sp.on("data",data => {
    console.log(data+":接收到的串口数据")
})

// 监听串口错误信息
sp.on("error",error => {
    console.log(error+":串口错误信息")
})

// 发送串口消息
sp.write("我是发送过去的串口信息")

// 手动触发关闭串口连接
sp.close()

5. 串口号查询

如果不清楚要连接的串口号是多少,可以通过以下方式来查找

右击win徽标键

右击win徽标键

选择设备管理器

选择设备管理器

选择端口(COM和LPT) tips: 如果没有这个选项,就表示你的设备没有连接任何串口,请连接串口后重试

选择端口(COM和LPT)

通过以上三步就可以找到想要连接的COM口了

6. 本地调试串口

  1. 建立虚拟串口软件

    如果没有串口线连接,但是有需要开发的,可以自己本地建立一个虚拟串口用于调试使用,这个软件Virtual Serial Port Tools可以新建串口(虽然软件是收费的,但是貌似这里的建立串口连接不需要收费)

    软件下载完成后进入软件,然后鼠标悬停第一个选项并点击create local brideg创建串口

    image.png

    然后就可以选择收发的两端COM口的串口号了

    image.png

    点击create,显示出如下图这种情况就是虚拟串口创建完毕了

    image.png

    也可以点击选项对建立的串口进行关闭

    image.png

  2. 串口通信调试软件

    可以直接去电脑的微软应用商店下载一个应用叫 串口调试助手

    image.png

7. 自己开发一个串口通信调试助手(以下是基于Vite+Vue2+Electron框架实现的,正常的Vite+Vue是无法实现的,后面我会分享这个框架)

<template>
  <div class="container">
    <aside class="aside">
      <el-button size="mini" type="danger" icon="el-icon-back" @click="$router.back()">返回</el-button>
      <!-- 串口号 -->
      <el-row class="aside_row" type="flex" justify="space-between" align="middle">
        <span>串口号</span>
        <div>
          <i @click="refreshSerialPort" class="el-icon-refresh"></i>
          <el-select :disabled="isOpen" style="width:150px" size="mini" v-model="currentSerialPort">
            <el-option v-for="item in serialPortList" :value="item.path" :key="item.path">{{ item.path }}</el-option>
          </el-select>
        </div>
      </el-row>

      <!-- 波特率 -->
      <el-row class="aside_row" type="flex" justify="space-between" align="middle">
        <span>波特率</span>
        <div>
          <i @click="writeBaud" class="el-icon-edit"></i>
          <el-select :disabled="isOpen" v-if="!isHandBaud" style="width:150px" size="mini" v-model="currentBaud">
            <el-option v-for="item in baudList" :value="item" :key="item">{{
              item
            }}</el-option>
          </el-select>
          <el-input :disabled="isOpen" style="width:150px" size="mini" v-else v-model="currentBaud"></el-input>
        </div>
      </el-row>

      <!-- 数据位 -->
      <el-row class="aside_row" type="flex" justify="space-between" align="middle">
        <span>数据位</span>
        <div>
          <el-select :disabled="isOpen" style="width:150px" size="mini" v-model="currentDataBit">
            <el-option v-for="item in dataBits" :value="item" :key="item">{{
              item
            }}</el-option>
          </el-select>
        </div>
      </el-row>

      <!-- 校验位 -->
      <el-row class="aside_row" type="flex" justify="space-between" align="middle">
        <span>校验位</span>
        <div>
          <el-select :disabled="isOpen" style="width:150px" size="mini" v-model="currentVaild">
            <el-option v-for="item in vaildBits" :value="item" :key="item">{{
              item
            }}</el-option>
          </el-select>
        </div>
      </el-row>

      <el-button style="width:100%" @click="openSerialPort" :type="isOpen ? 'primary' : 'none'">{{ isOpen ? "关闭" : "打开"
      }}串口</el-button>
      <el-button style="width:100%;margin: 10px auto;" :type="isUpdateFile ? 'primary' : 'none'" @click="downloadFile">{{
        isUpdateFile ? "不保存到文件" : "保存到文件" }}</el-button>

      <!-- 是否转换为JSON -->
      <el-row type="flex" justify="center">
        <el-switch v-model="conversionJSON" inactive-text="是否转换为JSON" :inactive-value="false" :active-value="true">
        </el-switch>
      </el-row>
    </aside>
    <main class="main">
      <div class="content_area">
        <el-row type="flex" justify="space-between" style="padding-right:10px;margin: 5px 0;">
          <span style="margin:5px">发送区域:</span>
          <el-button @click="sendContent = ''" type="info" size="mini" style="margin-bottom: 5px;">清空</el-button>
        </el-row>
        <el-input @wheel.native="wheelFn" disabled class="content" type="textarea" :rows="15"
          v-model="sendContent"></el-input>

        <el-row type="flex" justify="space-between" style="padding-right:10px;margin: 5px 0;">
          <span style="margin:5px">接收区域:</span>
          <el-button @click="receiveContent = ''" type="info" size="mini" style="margin-bottom: 5px;">清空</el-button>
        </el-row>
        <el-input @wheel.native="wheelFn" disabled class="content" type="textarea" :rows="15"
          v-model="receiveContent"></el-input>
      </div>
      <div class="footer">
        <el-input @keypress.native="handleKeyDown" v-model="sendValue" type="textarea" :rows="7"></el-input>
        <div class="submit" @click="sendMsg">
          <i class="el-icon-position"></i>
        </div>
      </div>
    </main>
  </div>
</template>

<script>
// 导入串口第三方库
const { SerialPort } = window.require("serialport");
// 导入ipcRenderer,用于和electron主线程通信
const { ipcRenderer } = window.require("electron");

export default {
  data() {
    return {
      port: "",
      // 串口列表
      serialPortList: [],
      currentSerialPort: localStorage.getItem("currentSerialPort") || "COM1",
      // 波特率列表
      baudList: [
        300,
        600,
        1200,
        4800,
        9600,
        14400,
        19200,
        38400,
        56000,
        57600,
        115200,
        12800,
        25600,
        460800,
        512000,
        750000,
        921600,
        1500000
      ],
      currentBaud: 512000,
      // 是否手动写入波特率
      isHandBaud: false,

      // 数据位
      dataBits: [5, 6, 7, 8],
      currentDataBit: 8,

      // 校验位
      vaildBits: ["even", "mark", "none", "odd"],
      currentVaild: "none",

      receiveContent: "",
      sendContent: "",
      sendValue: "",

      // 串口是否打开
      isOpen: localStorage.getItem("isOpen") === "true",

      // 是否允许保存数据到文件
      isUpdateFile: localStorage.getItem("isUpdateFile") === "true",

      // 是否转换为JSON
      conversionJSON: true,

      // 是否滚动到底部
      isBottom: true
    };
  },
  async created() {
    this.serialPortList = await SerialPort.list();
  },
  methods: {
    // 发送消息
    async sendMsg() {
      if (!this.port || (await SerialPort.list().length) === 0) {
        // 关闭串口
        this.isOpen = false;
        localStorage.setItem("isOpen", "false");
        return this.$message({ message: "串口未连接", type: "warning" });
      }
      let obj = this.sendValue.split(',').map(item => Number(item))
      this.port.write(
        this.conversionJSON ? JSON.stringify(obj) : this.sendValue
      ); // 发送字符串
      this.sendContent += this.conversionJSON
        ? JSON.stringify(obj)
        : this.sendValue;

      // 文本域滚动到底部
      this.scrollBottom();
    },
    // 连接/关闭串口
    async openSerialPort() {
      // 获取选中的串口
      let serialPortItem = this.serialPortList.find(
        item => item.path === this.currentSerialPort
      );
      console.log(await SerialPort.list(), "serialPortItem");

      // 关闭串口
      if (this.isOpen) {
        // 关闭串口
        console.log(this.port, "关闭串口");
        this.isOpen = false;
        localStorage.setItem("isOpen", "false");

        this.port && this.port.close();
      } else {
        // 打开串口
        console.log("打开串口");
        this.isOpen = true;
        localStorage.setItem("isOpen", "true");

        // 连接串口,配置对应的数据
        this.port = new SerialPort({
          // 连接的串口信息数据
          ...serialPortItem,
          // 比特率
          baudRate: this.currentBaud,
          // 数据位
          dataBits: this.currentDataBit,
          // 校验位
          parity: this.currentVaild
        });

        this.port.on("open", () => {
          localStorage.setItem("currentSerialPort", this.port.path);
        });

        // 接收消息
        this.port.on("data", data => {
          this.receiveContent += data.toString();
          console.log(`接收到了消息:`, data.toString());

          // 数据传递给electron主线程,主线程保存到文件中
          if (this.isUpdateFile) {
            console.log("保存文件");
            ipcRenderer.send("serial-port-message", data.toString());
          }

          // 文本域滚动到底部
          this.scrollBottom();
        });

        // 监听错误
        let that = this;
        that.port.on("error", function (err) {
          // 串口正在打开错误不提示
          if (err.toString() === "Error: Port is opening") return;
          that.$message({ message: err, type: "error" });
          console.log("errorr");
          that.isOpen = !that.isOpen;
          localStorage.setItem("isOpen", that.isOpen);
        });
      }
    },
    // 刷新串口列表
    async refreshSerialPort() {
      this.serialPortList = await SerialPort.list();
      console.log("已刷新");
    },
    // 手动写入波特率
    writeBaud() {
      this.isHandBaud = !this.isHandBaud;

      // 判断是否为下拉
      if (!this.isHandBaud) {
        // 判断下拉列表中是否有对应的值
        this.currentBaud =
          this.baudList.find(item => item === Number(this.currentBaud)) || 9600;
      }
    },
    // 切换是否保存文件
    downloadFile() {
      this.isUpdateFile = !this.isUpdateFile;
      localStorage.setItem("isUpdateFile", this.isUpdateFile);
    },
    // 按下 Shift 和 Enter 键时发送消息
    handleKeyDown(event) {
      if (event.shiftKey && event.keyCode === 13) {
        this.sendMsg();
      }
    },
    // 用户滚动鼠标时取消滚动到最底部行为
    wheelFn() {
      this.isBottom = false;
    },
    // 默认滚动到最底部,用户控制时取消,三秒后恢复
    scrollBottom() {
      this.$nextTick(() => {
        const textarea = document.querySelectorAll(".content textarea");
        if (this.isBottom) {
          for (let i = 0; i < textarea.length; i++) {
            textarea[i].scrollTo({
              top: textarea[i].scrollHeight,
              behavior: "smooth"
            });
          }
        }
      });
    }
  },
  watch: {
    isBottom(val) {
      if (!val) {
        setTimeout(() => {
          this.isBottom = true;
        }, 1500);
      }
    }
  },
  mounted() { },
  beforeDestroy() {
    // 关闭与electron主线程的通信通道
    ipcRenderer.removeAllListeners("message");

    // 获取连接的所有串口
    SerialPort.list().then(ports => {
      // 关闭每个链接
      ports.forEach(port => {
        console.log(port, "port");
        if (!port) return;
        let sp = new SerialPort({ ...port, baudRate: 9600 });
        sp.close();
      });
    });
  }
};
</script>

<style scoped>
.container {
  display: flex;
  width: 100%;
  max-height: 92.5vh;
}

.aside {
  flex: 1;
  min-height: 100vh;
  background-color: #f2f2f2;
  padding: 10px;
  box-sizing: border-box;
}

.main {
  display: flex;
  flex-direction: column;
  flex: 6;
  min-height: 100vh;
  padding: 5px;
}

.footer {
  flex: 2;
  display: flex;
  box-sizing: border-box;
  padding-top: 5px;
  margin-bottom: 60px;
}

.content_area {
  flex: 8;
}

.aside_row {
  margin: 5px 0;
}

.submit {
  width: 200px;
  height: 85%;
  margin-left: 10px;
  border-radius: 8px;
  background: #cccccc;
  font-size: 65px;
  display: flex;
  justify-content: center;
  align-items: center;
  cursor: pointer;
}

.submit:hover,
.submit:active {
  background-color: #999999;
}

.el-icon-position {
  transform: rotate(45deg);
}
</style>

效果图:

效果图

要在Vue进行串口通信,你需要使用一些第三方库。一个常用的库是`serialport`,它提供了Node.js串口通信功能。你可以使用`vue-serialport`插件来将`serialport`集成到Vue。 首先,在你的项目安装`serialport`库: ``` npm install serialport ``` 然后,安装`vue-serialport`插件: ``` npm install vue-serialport ``` 接下来,你需要在Vue创建一个组件来处理串口通信。下面是一个简单的示例: ```vue <template> <div> <button @click="connect">Connect</button> <button @click="disconnect">Disconnect</button> </div> </template> <script> import SerialPort from 'serialport'; import VueSerialport from 'vue-serialport'; export default { data() { return { port: null, isConnected: false, }; }, methods: { connect() { this.port = new SerialPort('/dev/ttyUSB0', { baudRate: 9600 }); this.port.on('open', () => { console.log('Port is open'); this.isConnected = true; }); this.port.on('data', (data) => { console.log('Received data:', data); }); }, disconnect() { if (this.isConnected) { this.port.close(() => { console.log('Port is closed'); this.isConnected = false; }); } }, }, created() { VueSerialport.requestPort().then((port) => { console.log('Selected port:', port.path); }); }, }; </script> ``` 在上面的示例,我们首先导入了`serialport`和`vue-serialport`库。然后,我们在`data`定义了一个`port`变量来保存串口连接,以及一个`isConnected`变量来表示是否连接成功。 在`connect`方法,我们创建了一个新的`SerialPort`实例,并在`open`事件设置`isConnected`为`true`。我们还监听`data`事件以接收从串口发送过来的数据。 在`disconnect`方法,我们关闭了串口连接并将`isConnected`设置为`false`。 最后,在`created`钩子,我们使用`VueSerialport.requestPort()`方法来请求串口连接。该方法返回一个Promise,它会在用户选择一个可用的串口端口后被解析。你可以在这里设置串口的参数,例如波特率等。 这只是一个简单的示例,如果你需要更复杂的功能,可以查看`serialport`和`vue-serialport`文档来获取更多信息。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值