D06 - Web Serial API 初体验

让我们透过 Web Serial API 连接 COM 吧!

首先来建立页面。

建立共用样式

准备撰写组件之前,先来建立一些全局共用的样式变数与 Class。

直接将共用变数新增至 Quasar 已存在的档案 src\styles\quasar.variables.sass 中:

$primary   : #027BE3
$secondary : #26A69A
$accent    : #9C27B0

$dark      : #1D1D1D

$positive  : #21BA45
$negative  : #C10015
$info      : #31CCEC
$warning   : #F2C037

// 整体视觉以圆角为主,建立基准值
$border-radius-s: 12px
$border-radius-m: 20px

@import '~quasar-variables-styl'

建立档案 src\styles\global.sass

// 引入变数
@import '@/styles/quasar.variables.sass'

.c-row
  display: flex
.c-col
  display: flex
  flex-direction: column

// 圆角基准样式
.border-radius-m
  border-radius: $border-radius-m !important
.border-radius-s
  border-radius: $border-radius-s !important

// 滚动条样式
::-webkit-scrollbar 
  width: 3px
  height: 3px  
::-webkit-scrollbar-track
  padding: 5px
  border-radius: 7.5px
::-webkit-scrollbar-thumb 
  border-radius: 7.5px

最後在 src\main.js 引入 global.sass

import Vue from 'vue';
import App from './app.vue';
import router from './router/router';
import store from './store/store';
import './quasar';
import i18n from './i18n';

import '@/styles/global.sass';
import 'windi.css';

Vue.config.productionTip = false;

new Vue({
  router,
  store,
  i18n,
  render: (h) => h(App),
}).$mount('#app');

建立 Port 设定对话框

由於 Web Serial API 建立成功後的 Serial Port 物件会被多个卡片共用,所以在 Vuex 中建立 core 模组,用来储存 Port 与各类系统设定。

src\store\modules\core.store.js

/**
 * 管理 Port 物件、系统主要设定
 */

/**
 * @typedef {import('vuex').Module} Module
 */

/** @type {Module} */
const self = {
  namespaced: true,
  state: () => ({
    port: null,
  }),
  mutations: {
    setPort(state, port) {
      state.port = port;
    },
  },
  actions: {
  },
  modules: {
  },
};

export default self;

并在 Vuex 中的 modules 引入 core.store.js

src\store\store.js

import Vue from 'vue';
import Vuex from 'vuex';

Vue.use(Vuex);

import core from './modules/core.store';

export default new Vuex.Store({
  state: {
  },
  mutations: {
  },
  actions: {
  },
  modules: {
    core,
  },
});

接着建立用来选择 Port 的设定对话框,这里使用 Quasar Dialog 组件

功能需求为:

  • 若浏览器不支援 Web Serial API,则显示「浏览器不支援 Web Serial API,请改用支援此 API 之浏览器」并提供打开参考资料网页的按钮。
  • 若浏览器支援 Web Serial API,则提供按钮进行 Port 选择。
  • 选择完成後自动关闭 Dialog

src\components\dialog-system-setting.vue <script>

import { mapState } from 'vuex';
import to from 'safe-await';

export default {
  name: 'DialogSystemSetting',
  components: {},
  props: {},
  data() {
    return {};
  },
  computed: {
    ...mapState({
      port: (state) => state.core.port,
    }),

    notSupportSerialApi() {
      return !navigator?.serial;
    },

    /** 判断 Dialog 是否可以关闭 */
    isPersistent() {
      if (this.errMsg) {
        return true;
      }

      return false;
    },

    errMsg() {
      if (this.notSupportSerialApi) {
        return '浏览器不支援 Web Serial API';
      }

      if (!this.port) {
        return '请选择 COM Port';
      }

      return null;
    },
  },
  watch: {
    /** 如果 Dialog 可以关闭,则自动关闭 */
    isPersistent(val) {
      if (!val) {
        this.hide();
      }
    },
  },
  created() {},
  mounted() {},
  methods: {
    hide() {
      this.$refs.dialog.hide();
    },

    /** 开启外部连结 */
    openRefWeb() {
      window.open('https://caniuse.com/?search=Web%20serial%20API');
    },

    /** 请求选择 COM */
    async requestPort() {
      /** 请求连线 Port
       * https://developer.mozilla.org/en-US/docs/Web/API/Web_Serial_API#checking_for_available_ports
       */
      const [err, port] = await to(navigator.serial.requestPort());
      if (err) {
        // 使用者取消选择不弹出错误提示
        if (`${err}`.includes('No port selected by the user')) {
          return;
        }

        /** 其余错误则透过 Quasar Notify 显示
         * 参考:https://v1.quasar.dev/quasar-plugins/notify
         */
        this.$q.notify({
          type: 'negative',
          message: `选择 COM Port 发生错误 : ${err}`,
        });
        console.error(`[ requestPort ] err : `, err);
        return;
      }

      // 储存至 Vuex
      this.$store.commit('core/setPort', port);
    },

    /** 处理 Dialog shake 事件
     * 参考 https://v1.quasar.dev/vue-components/dialog#different-modes
     */
    handleShake() {
      if (!this.errMsg) {
        return;
      }

      this.$q.notify({
        type: 'negative',
        message: this.errMsg,
      });
    },
  },
};

src\components\dialog-system-setting.vue <template lang="pug">

q-dialog(value, :persistent='isPersistent', @shake='handleShake', ref='dialog')
  // 如果浏览器不支援 Web Serial API,显示此区块
  q-card.border-radius-m(v-if='notSupportSerialApi')
    q-card-section.c-col.flex-center.p-30px
      .text-18px.text-red.mb-30px
        | 浏览器不支援 Web Serial API,请改用支援此 API 之浏览器
      q-btn(@click='openRefWeb()', color='primary') 参考资料

  // 否则显示此区块
  q-card.border-radius-m(v-else)
    q-card-section.min-w-450px
      q-list
        q-item-label(header)
          | 系统设定
        q-item.border-radius-m(@click='requestPort', clickable)
          q-item-section(avatar)
            q-icon(name='r_developer_board', color='grey-7')
          q-item-section
            q-item-label
              | 选择 COM Port
            q-item-label(caption)
              | 点击选择指定 COM Port

          q-item-section(v-if='!port', side)
            q-icon(name='r_error', color='red')

safe-await 是一种包装 await 的方法,可以在不使用 try catch 的情况下使用 await,配合 Return Early Pattern,个人觉得这样可读性比较好 (´,,•ω•,,)

详细探讨过程与介绍可以参考作者的 Github

dialog-system-setting.vue 组件引入 src\app.vue 中:

src\app.vue <script>

import DialogSystemSetting from '@/components/dialog-system-setting.vue';

export default {
  name: 'App',
  components: {
    'dialog-system-setting': DialogSystemSetting,
  },
  data() {
    return {};
  },
  computed: {},
  watch: {},
  created() {},
  mounted() {},
  methods: {},
};

src\app.vue <template lang="pug">

.screen
  dialog-system-setting

成功的话,目前画面应该会像下图:

Untitled

浏览器不支援 API 的话,会变这样子。

Untitled

如果没有选择 Port,点击 Dialog 以外的地方会有错误讯息跳出。

D06 - Dialog Persistent 效果.gif

如果点击「选择 COM Port」,则浏览器会跳出弹出视窗,选择 Port 後会自动关闭 Dialog。

D06 - 选择 Port.gif

以上我们完成 Serial API 的第一步骤「选择 Port」了!

总结

  • 建立选择 Port 对话框
  • 透过 navigator.serial.requestPort() 取得 COM 存取权限
  • 将选择 Port 储存至 Vuex

以上程序码已同步至 GitLab,大家可以前往下载:

GitLab - D06


<<:  Day5 用python写UI-聊聊视窗控件配置管理员-place方法

>>:  Day05 - 使用 Link 实作换页

人脸辨识-day26

在人脸辨识中,常见的是用影像的方式来进行辨识,利用摄影机的方式来撷取影像,利用相似的方式让测试者与资...

Day 14 AWS云端实作起手式第四弹 图文档案与程序码备份与URL重写

今天简单看一下如何设定CloudFront和做图文档和程序码备份 步骤 9 设定CloudFront...

Day10 Pandas模组二

今天的影片为接续上一部的内容,以及介绍几个简单的统计函数 (还有短短的英文小教室...我要去跟英文老...

用科学化除错方法替你的 zk 程序除错之二

b. 分析找到的资料并给出一个对根因的假设 在取得资料之後,就要针对这些资料提出一个假设。如果对 Z...

Day29 实作信件发送功能(2)

昨天我们已经做好事前准备了,那我们今天就回到views里面,来撰写我们的程序吧! 而我们这次使用的函...