本系列文已改編成書「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');
由於 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 組件。
功能需求為:
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
成功的話,目前畫面應該會像下圖:
瀏覽器不支援 API 的話,會變這樣子。
如果沒有選擇 Port,點擊 Dialog 以外的地方會有錯誤訊息跳出。
如果點擊「選擇 COM Port」,則瀏覽器會跳出彈出視窗,選擇 Port 後會自動關閉 Dialog。
以上我們完成 Serial API 的第一步驟「選擇 Port」了!
navigator.serial.requestPort()
取得 COM 存取權限以上程式碼已同步至 GitLab,大家可以前往下載: