iT邦幫忙

2021 iThome 鐵人賽

DAY 20
0
Modern Web

你渴望連結嗎?將 Web 與硬體連上線吧!系列 第 20

D19 - 「吶,你想要成為什麼顏色?」:打造一個調色盤吧!

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

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

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


接下來我們要利用「 PWM」與「RGB LED」設計調色盤功能。

首先來認識一下新朋友 - 「RGB LED」

RGB LED

https://ithelp.ithome.com.tw/upload/images/20240125/20140213ZaDjmHtuev.png

電子助教:「所以為甚麼要把三種 LED 合在一起?( ´ ▽ ` )ノ」

鱈魚:「這就是一個很長的故事了。很久以前,R LED 與 G LED 的祖先們發生了不可告人的...」

電子助教:「不要瞎掰好嗎,明明就是為了要混色!(°c_,°`)」

鱈魚:「你明明就知道是問個毛線喔... ('◉◞⊖◟◉` )」

https://ithelp.ithome.com.tw/upload/images/20240125/20140213OZT1cF4XM3.png

硬體實作

需要準備以下設備與零件:

  • 三用電表 * 1

  • 麵包板 * 1

  • RGB LED * 1

    推薦大家買這種模組式的 RGB LED,因為上面已經配好電阻,可以直接接線。

    https://ithelp.ithome.com.tw/upload/images/20240125/201402135OTAG04QWF.jpg

檢查硬體

老樣子要檢查小夥伴們是否正常。

RGB LED

測試方式與一般 LED 概念相同。
https://ithelp.ithome.com.tw/upload/images/20240125/20140213tyUufGn66A.png

若有任一種顏色沒有亮,表示此顏色之 LED 損壞,請換一個 RGB LED。

連接電路

以下為參考接線方式,可以不用完全相同,只要效果相同即可。

使用 Uno 板子上的 5V 為 +、GND 為 -。
https://ithelp.ithome.com.tw/upload/images/20240125/2014021315yLF2CWDb.png

建立視窗

window-example.vue 複製一份後改個名字,建立 window-app-rgb-led-palette.vue

調色盤相關檔案建立一個 src\components\window-app-rgb-led-palette\ 資料夾整理。

src\components\window-app-rgb-led-palette\window-app-rgb-led-palette.vue

<template lang="pug">
base-window.window-app-rgb-led-palette(
  :pos='pos',
  header-icon-color='blue-grey',
  header-icon='r_palette',
  body-class='c-col h-330px',
  title='RGB LED 調色盤'
)
</template>

<style lang="sass">
</style>

<script>
import mixinWindow from '@/mixins/mixin-window';

import BaseWindow from '@/components/base-window.vue';

export default {
  name: 'WindowAppRgbLedPalette',
  components: {
    'base-window': BaseWindow,
  },
  mixins: [mixinWindow],
  props: {},
  data() {
    return {};
  },
  computed: {},
  watch: {},
  created() {
    console.log(`[ ${this.$options.name} created ] id : `, this.id);
  },
  mounted() {},
  methods: {},
};
</script>

接著回到 app.vue,將右鍵選單內加入『新增「RGB LED 調色盤」』選項,並引入組件。

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

.screen(@click='handleClick')
  // ...

	// 右鍵選單
  q-menu(context-menu, content-class='border-radius-s')
    q-list.min-w-260px
      q-item(@click='addWindow("window-digital-io")', clickable, v-close-popup)
        q-item-section
          | 新增「數位 I/O 視窗」
			q-item(@click='addWindow("window-analog-input")', clickable, v-close-popup)
        q-item-section
          | 新增「類比輸入視窗」
			q-item(@click='addWindow("window-pwm-output")', clickable, v-close-popup)
        q-item-section
          | 新增「PWM 輸出視窗」
			q-separator

      q-item(
        @click='addWindow("window-app-rgb-led-palette")',
        clickable,
        v-close-popup
      )
        q-item-section
          | 新增「RGB LED 調色盤」

src\app.vue <script>

// ...

import WindowDigitalIo from '@/components/window-digital-io.vue';
import WindowAnalogInput from '@/components/window-analog-input.vue';
import WindowPwmOutput from '@/components/window-pwm-output.vue';

import WindowAppRgbLedPalette from '@/components/window-app-rgb-led-palette/window-app-rgb-led-palette.vue';

export default {
  name: 'App',
  components: {
		'dialog-system-setting': DialogSystemSetting,
    
    'window-digital-io': WindowDigitalIo,
    'window-analog-input': WindowAnalogInput,
    'window-pwm-output': WindowPwmOutput,

		'window-app-rgb-led-palette': WindowAppRgbLedPalette,
  },
  // ...
};

規劃 UI

https://ithelp.ithome.com.tw/upload/images/20240125/2014021348Daq9LpiL.png

實作設定欄位

增加以下程式:

  • 引入 base-select-pin.vue
  • data()
    • ledPin 儲存 RGB 3 個腳位。
    • colorVal 儲存目前顏色。
  • methods
    • handleErr(msg) 處理錯誤訊息

src\components\window-app-rgb-led-palette\window-app-rgb-led-palette.vue <script>

import mixinWindow from '@/mixins/mixin-window';

import BaseWindow from '@/components/base-window.vue';
import BaseSelectPin from '@/components/base-select-pin.vue';

export default {
  name: 'WindowAppRgbLedPalette',
  components: {
    'base-window': BaseWindow,
    'base-select-pin': BaseSelectPin,
  },
  mixins: [mixinWindow],
  props: {},
  data() {
    return {
      ledPin: {
        r: null,
        g: null,
        b: null,
      },

      colorVal: '',
    };
  },
  computed: {},
  watch: {},
  created() {
    console.log(`[ ${this.$options.name} created ] id : `, this.id);
  },
  mounted() {},
  methods: {
		handleErr(msg) {
      this.$q.notify({
        type: 'negative',
        message: msg,
      });
    },
	},
};

src\components\window-app-rgb-led-palette\window-app-rgb-led-palette.vue <template lang="pug">

base-window.window-app-rgb-led-palette(
  :pos='pos',
  header-icon-color='blue-grey',
  header-icon='r_palette',
  body-class='c-col h-330px',
  title='RGB LED 調色盤'
)
  .h-full.overflow-hidden
    .setting-form
      .text-14px.mb-14px
        | 設定 RGB LED 腳位
      .led-item
        q-icon(name='r_emoji_objects', color='red-5', size='34px')
        base-select-pin.w-full(
          v-model='ledPin.r',
          color='red-2',
          placeholder='點擊選擇',
          @err='handleErr'
        )

      .led-item
        q-icon(name='r_emoji_objects', color='green-5', size='34px')
        base-select-pin.w-full(
          v-model='ledPin.g',
          color='green-2',
          placeholder='點擊選擇',
          @err='handleErr'
        )

      .led-item
        q-icon(name='r_emoji_objects', color='blue-5', size='34px')
        base-select-pin.w-full(
          v-model='ledPin.b',
          color='blue-2',
          placeholder='點擊選擇',
          @err='handleErr'
        )

src\components\window-app-rgb-led-palette\window-app-rgb-led-palette.vue <style lang="sass">

@import '@/styles/quasar.variables.sass'

.window-app-rgb-led-palette
  width: 300px
  .setting-form
    position: absolute
    padding: 30px
    width: 100%
    height: 100%
    .led-item
      display: flex
      align-items: center
      margin-bottom: 10px
      .q-icon
        margin-right: 14px

設定欄位外觀完成。

https://ithelp.ithome.com.tw/upload/images/20240125/20140213odYYfvdt2i.png
功能需求為:

  • 提供所有支援 PWM 功能腳位清單。
  • 判斷是否所有腳位都選擇完成。
  • 回報 Vuex 新增、移除占用腳位。

src\components\window-app-rgb-led-palette\window-app-rgb-led-palette.vue <script>

/**
 * @typedef {import('@/script/modules/port-transceiver').default} PortTransceiver
 *
 * @typedef {import('@/types/type').PinInfo} PinInfo
 * @typedef {import('@/types/type').PinCapability} PinCapability
 */

import { mapState } from 'vuex';

// ...

import { PinMode } from '@/script/utils/firmata.utils';
const { PWM } = PinMode;

export default {
  name: 'WindowAppRgbLedPalette',
	// ...
	watch: {
    'ledPin.r': {
      handler(newVal, oldVal) {
        this.handlePinSelect(newVal, oldVal);
      },
    },
    'ledPin.g': {
      handler(newVal, oldVal) {
        this.handlePinSelect(newVal, oldVal);
      },
    },
    'ledPin.b': {
      handler(newVal, oldVal) {
        this.handlePinSelect(newVal, oldVal);
      },
    },
  },
  // ...
  computed: {
    ...mapState({
      boardPins: (state) => state.board.info.pins,

      /** @type {PortTransceiver} */
      portTransceiver: (state) => state.core.transceiver,
    }),

		/** 支援功能的腳位 */
    supportPins() {
      /** @type {PinInfo[]} */
      const boardPins = this.boardPins;

      return boardPins.filter((pin) =>
        pin.capabilities.some((capability) => PWM === capability.mode)
      );
    },

    /** 判斷所有 Pin 腳是否選擇完成 */
    isSettingOk() {
      return Object.values(this.ledPin).every((pin) => pin);
    },
  },
  // ...
	methods: {
    // ...

    /** 處理腳位選擇事件,回報 Vuex 新增、移除占用腳位
     * @param {PinInfo} newVal
     * @param {PinInfo} oldVal
     */
    handlePinSelect(newVal, oldVal) {
      if (newVal) {
        this.$store.commit('window/addOccupiedPin', {
          id: this.id,
          pin: newVal,
        });
      }

      if (oldVal) {
        this.$store.commit('window/deleteOccupiedPin', {
          id: this.id,
          pin: oldVal,
        });
      }
    },
  },
};

將每個 base-select-pin 傳入 supportPins

src\components\window-app-rgb-led-palette\window-app-rgb-led-palette.vue <template lang="pug">

base-window.window-app-rgb-led-palette(
  :pos='pos',
  header-icon-color='blue-grey',
  header-icon='r_palette',
  body-class='c-col h-330px',
  title='RGB LED 調色盤'
)
  .h-full.overflow-hidden
    .setting-form
      .text-14px.mb-14px
        | 設定 RGB LED 腳位
      .led-item
        q-icon(name='r_emoji_objects', color='red-5', size='34px')
        base-select-pin.w-full(
          v-model='ledPin.r',
          :pins='supportPins',
          color='red-2',
          placeholder='點擊選擇',
          @err='handleErr'
        )

      .led-item
        q-icon(name='r_emoji_objects', color='green-5', size='34px')
        base-select-pin.w-full(
          v-model='ledPin.g',
          :pins='supportPins',
          color='green-2',
          placeholder='點擊選擇',
          @err='handleErr'
        )

      .led-item
        q-icon(name='r_emoji_objects', color='blue-5', size='34px')
        base-select-pin.w-full(
          v-model='ledPin.b',
          :pins='supportPins',
          color='blue-2',
          placeholder='點擊選擇',
          @err='handleErr'
        )

嘗試看看。

D19 - 判斷所有腳位是否選擇完成.gif

成功!再來就是自動切換畫面的部分,我們使用 transition 達成效果。

src\components\window-app-rgb-led-palette\window-app-rgb-led-palette.vue <template lang="pug">

base-window.window-app-rgb-led-palette(
  :pos='pos',
  header-icon-color='blue-grey',
  header-icon='r_palette',
  body-class='c-col h-330px',
  title='RGB LED 調色盤'
)
  .h-full.overflow-hidden
		// 設定完成後,顯示這一區
    transition(name='switch-right')
      .text-30px.absolute(v-if='isSettingOk') (´,,•ω•,,)

		// 設定完成前,顯示設定欄位
    transition(name='switch-left')
      .setting-form(v-if='!isSettingOk')
        .text-14px.mb-14px
          | 設定 RGB LED 腳位
        .led-item
          q-icon(name='r_emoji_objects', color='red-5', size='34px')
          base-select-pin.w-full(
            v-model='ledPin.r',
            :pins='supportPins',
            color='red-2',
            placeholder='點擊選擇',
            @err='handleErr'
          )

        .led-item
          q-icon(name='r_emoji_objects', color='green-5', size='34px')
          base-select-pin.w-full(
            v-model='ledPin.g',
            :pins='supportPins',
            color='green-2',
            placeholder='點擊選擇',
            @err='handleErr'
          )

        .led-item
          q-icon(name='r_emoji_objects', color='blue-5', size='34px')
          base-select-pin.w-full(
            v-model='ledPin.b',
            :pins='supportPins',
            color='blue-2',
            placeholder='點擊選擇',
            @err='handleErr'
          )

定義過場動畫。

src\components\window-app-rgb-led-palette\window-app-rgb-led-palette.vue <style lang="sass">

@import '@/styles/quasar.variables.sass'

.window-app-rgb-led-palette
  width: 300px
  // ...

  .switch-left-enter-active, .switch-left-leave-active
    transition-timing-function: cubic-bezier(0.945, -0.290, 0.165, 1.360)
    transition-duration: 0.8s
    pointer-events: none
  .switch-left-enter, .switch-left-leave-to
    transform: translateX(100%) !important
    opacity: 0 !important

  .switch-right-enter-active, .switch-right-leave-active
    transition-timing-function: cubic-bezier(0.945, -0.290, 0.165, 1.360)
    transition-duration: 0.8s
    pointer-events: none
  .switch-right-enter, .switch-right-leave-to
    transform: translateX(-100%) !important
    opacity: 0 !important

實測效果。

D19 - 腳位設定完成後自動切換畫面.gif

成功切換畫面了!✧*。٩(ˊᗜˋ*)و✧*。

下一章節進入實作彩球的部分!

總結

  • 認識 RGB LED。
  • 成功建立「RGB LED 調色盤」視窗。

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

GitLab - D19


上一篇
D18 -「脈衝×寬度×調變」:建立控制組件
下一篇
D20 - 「吶,你想要成為什麼顏色?」:將色彩傳下去
系列文
你渴望連結嗎?將 Web 與硬體連上線吧!33
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言