iT邦幫忙

2021 iThome 鐵人賽

DAY 7
0

本系列文已改編成書「Arduino 自造趣:結合 JavaScript x Vue x Phaser 輕鬆打造個人遊戲機」,本書改用 Vue3 與 TypeScript 全面重構且加上更詳細的說明,

在此感謝 iT 邦幫忙、博碩文化與編輯小 p 的協助,歡迎大家前往購書,鱈魚在此感謝大家 (。・∀・)。

若想 DIY 卻不知道零件去哪裡買的讀者,可以參考此連結 。( •̀ ω •́ )✧


讓我們透過 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

成功的話,目前畫面應該會像下圖:

https://ithelp.ithome.com.tw/upload/images/20240116/20140213CBLxEBz9G8.png

瀏覽器不支援 API 的話,會變這樣子。

https://ithelp.ithome.com.tw/upload/images/20240116/20140213JqDHB7Mfvm.png

如果沒有選擇 Port,點擊 Dialog 以外的地方會有錯誤訊息跳出。

D06 - 選擇 Port.gif

如果點擊「選擇 COM Port」,則瀏覽器會跳出彈出視窗,選擇 Port 後會自動關閉 Dialog。

以上我們完成 Serial API 的第一步驟「選擇 Port」了!

總結

  • 建立選擇 Port 對話框
  • 透過 navigator.serial.requestPort() 取得 COM 存取權限
  • 將選擇 Port 儲存至 Vuex

以上程式碼已同步至 GitLab,大家可以前往下載:

GitLab - D06


上一篇
D05 - 準備前端環境:建立 Vue 開發環境
下一篇
D07 - 聽話,給我資料!
系列文
你渴望連結嗎?將 Web 與硬體連上線吧!33
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言