iT邦幫忙

2021 iThome 鐵人賽

DAY 21
1
Modern Web

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

D20 - 「吶,你想要成為什麼顏色?」:將色彩傳下去

  • 分享至 

  • xImage
  •  

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

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

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


這個章節來實作彩球組件,讓使用者可以選擇顏色。

D19 - window-app-rgb-led-palette 線框 (1).png

建立顏色控制器

建立 color-ball.vue 組件。

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

<template lang="pug">
.color-ball-section
  .color-ball
</template>

<style lang="sass">
@import '@/styles/quasar.variables.sass'

.color-ball-section
  position: absolute
  padding: 30px
  width: 100%
  height: 100%
  display: flex
  justify-content: center
  align-items: center

  .color-ball
    $size: 180px
    width: $size
    height: $size
    box-shadow: 0px 0px 20px rgba(black, 0.1)
    border-radius: 99999px
    transition: background 0s, transform 0.4s
    &:hover
      transition-duration: 0.2s
      transform: scale(1.04)
      transition-timing-function: cubic-bezier(0.385, 1.055, 0.465, 1.190)
    &:active
      transition-duration: 0.2s
      transform: scale(1)
      transition-timing-function: cubic-bezier(0.385, 1.055, 0.535, 1.570)
</style>

<script>
export default {
  name: 'ColorBall',
  components: {},
  props: {},
  data() {
    return {};
  },
  computed: {},
  watch: {},
  created() {},
  mounted() {},
  methods: {},
};
</script>

接著在 window-app-rgb-led-palette.vue 引入 color-ball.vue

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

// ...

import BaseWindow from '@/components/base-window.vue';
import BaseSelectPin from '@/components/base-select-pin.vue';
import ColorBall from '@/components/window-app-rgb-led-palette/color-ball.vue';

// ...

export default {
  name: 'WindowAppRgbLedPalette',
  components: {
    'base-window': BaseWindow,
    'base-select-pin': BaseSelectPin,

    'color-ball': ColorBall,
  },
  // ...
};

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

base-window.window-app-rgb-led-palette(
  // ...
)
  .h-full.overflow-hidden
    transition(name='switch-right')
      color-ball(v-if='isSettingOk', v-model='colorVal')
		// ...

D20 - 完成彩球基本樣式.gif

球球出現了!

接下來依序完成 color-ball.vue 功能,需求為:

  • 提供父元件綁定 v-model
  • 提供 Quasar Color Picker
  • 球的背景色隨著目前選擇顏色變化

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

export default {
  name: 'ColorBall',
  components: {},
  props: {
    value: {
      type: String,
      default: '',
    },
  },
  data() {
    return {
      colorVal: '',
    };
  },
  computed: {
		/** 提供 background 顏色 */
    style() {
      return {
        background: this.colorVal,
      };
    },
  },
  watch: {
    /** 偵測選擇變化,透過 emit('input') 更新 v-model 數值 */
    colorVal(val) {
      this.$emit('input', val);
    },
  },
  created() {
    // 儲存初始顏色
    this.colorVal = this.value;
  },
  mounted() {},
  methods: {},
};

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

.color-ball-section
  // 使用 q-menu 提供彈出選單功能
  q-menu(anchor='center end', self='center start')
    q-color(v-model='colorVal')

  // 綁定 style,變更 background 顏色
  .color-ball(:style='style')

實測功能。

D20 - 彩球選擇顏色功能.gif

成功變色!最後讓我們加入色環。

實作以下功能:

  • 使用 Quasar Knob 建立色環。
  • 將 16 進位表示色票轉為 R、G、B 數值

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

import { colors } from 'quasar';
const { hexToRgb } = colors;

export default {
  name: 'ColorBall',
  // ...
  computed: {
    // ...

    /** 根據 colorVal 計算之 RGB 數值
     * 使用 Quasar 提供之 Color Utils 達成
     * https://v1.quasar.dev/quasar-utils/color-utils
     */
    rgbVal() {
      if (this.colorVal === '') {
        return {
          r: 0,
          g: 0,
          b: 0,
        };
      }

      return hexToRgb(this.colorVal);
    },
  },
  // ...
};

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

.color-ball-section
  // 使用 q-menu 提供彈出選單功能
  q-menu(anchor='center end', self='center start')
    q-color(v-model='colorVal')

  q-knob(
    v-model='rgbVal.r',
    color='red-4',
    size='220px',
    :thickness='0.05',
    readonly,
    :angle='0',
    :min='0',
    :max='765'
  )
  q-knob(
    v-model='rgbVal.g',
    color='green-4',
    size='220px',
    :thickness='0.05',
    readonly,
    :angle='120',
    :min='0',
    :max='765'
  )
  q-knob(
    v-model='rgbVal.b',
    color='blue-4',
    size='220px',
    :thickness='0.05',
    readonly,
    :angle='240',
    :min='0',
    :max='765'
  )

  // 綁定 style,變更 background 顏色
  .color-ball(:style='style')

knob 之max 設為 765,是因為 RGB 最大值為 255,設 765 剛好可以讓每個 knob 各占 3 分之 1。

看看效果如何。

D20 - 色環效果.gif

感覺真不錯!(ง •̀_•́)ง

最後就是實際讓 RGB LED 發亮了!讓我們回到 window-app-rgb-led-palette.vue

功能需求為:

  • 腳位設定完成後,初始化所有腳位為 PWM 模式。
  • colorVal 16 進位顯示色票轉為 RGB 數值。
  • 偵測 colorVal 變化,並轉換為 PWM 數值發送。

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

// ...

export default {
  name: 'WindowAppRgbLedPalette',
  // ...
	computed: {
    // ...

    /** 根據 colorVal 計算之 RGB 數值 */
    rgbVal() {
      if (this.colorVal === '') {
        return {
          r: 0,
          g: 0,
          b: 0,
        };
      }

      return hexToRgb(this.colorVal);
    },
  },
  watch: {
    // ...

    isSettingOk(isOk) {
      if (!isOk) return;

      this.initPins();
    },

		/** 若數值發生變化,發送命令 */
		rgbVal(val) {
      if (!this.isSettingOk) return;

      this.writePinsVal(val);
    },
  },
  // ...
  methods: {
    // ...

    /** 初始化所有腳位 */
    initPins() {
      /** @type {PortTransceiver} */
      const portTransceiver = this.portTransceiver;

      Object.values(this.ledPin).forEach((/** @type {PinInfo} */ pin) => {
        portTransceiver.addCmd('setMode', {
          pin: pin.number,
          mode: PWM,
        });
      });
    },

    /** 發送 PWM 數值
     * @param {Object} rgbVal
     * @param {number} rgbVal.r
     * @param {number} rgbVal.g
     * @param {number} rgbVal.b
     */
    writePinsVal(rgbVal) {
      /** @type {PortTransceiver} */
      const portTransceiver = this.portTransceiver;

      Object.entries(this.ledPin).forEach(
        ([key, /** @type {PinInfo} */ pin]) => {
          const value = rgbVal[key];

          portTransceiver.addCmd('setPwmValue', {
            pin: pin.number,
            value,
          });
        }
      );
    },
  },
};

成功色彩數值轉為 PWM 發送!

D20 - 顏色輸出至 RGB LED.gif

可以發現一樣有塞車延遲的問題,所以一樣請出老朋友 throttle

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

// ...

import { throttle } from 'lodash-es';

// ...

export default {
  name: 'WindowAppRgbLedPalette',
  // ...
  data() {
    return {
      ledPin: {
        r: null,
        g: null,
        b: null,
      },

      colorVal: '',

      throttle: {
        writePinsVal: null,
      },
    };
  },
  // ...
  watch: {
    // ...

    rgbVal(val) {
      if (!this.isSettingOk) return;

      this.throttle.writePinsVal(val);
    },
  },
  created() {
    console.log(`[ ${this.$options.name} created ] id : `, this.id);

    this.throttle.writePinsVal = throttle(this.writePinsVal, 100);
  },
  // ....
};

D20 - PWM 輸出加入 throttle.gif

成功成為調色大師!(≧∀≦)

至此我們成功完成「RGB LED 調色盤」視窗,所以最後你想成為甚麼顏色呢?( ´ ▽ ` )ノ

如果 RGB LED 的混色效果不佳,可以拿砂紙將 LED 磨成霧面或是貼上霧面貼紙,會讓混色效果好一點。

進階挑戰:

細心的讀者們可能會注意到 RGB LED 的 3 種顏色發亮程度不同,這是因為不同 LED 的內阻、發光效率等等特性一定會有些許差異,導致同樣輸出下,亮度卻不一樣。

如何修正這個問題就留給各位讀者囉!(≖‿ゝ≖)✧

總結

  • 將色彩數值透過 RGB LED 與 PWM 調光呈現。
  • 完成「RGB LED 調色盤」視窗。

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

GitLab - D20


上一篇
D19 - 「吶,你想要成為什麼顏色?」:打造一個調色盤吧!
下一篇
D21 - 「不斷線的侏儸紀」:萃取 DNA
系列文
你渴望連結嗎?將 Web 與硬體連上線吧!33
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言