JavaScript
Arduino
vue.js
IoT
WebUSB

WebUSBを使ってブラウザのJavaScriptからArduinoを制御してみよう!

WebUSBの動向を最近追えてなかったので久々に触ってみます。

そして思ったより簡単に出来ました。

こんなの作ってみました。

WebブラウザのカラーピッカーをグリグリやるとLEDの色が変わります。

WebUSB

"ブラウザの"JavaScriptからUSBデバイスへ"直接"アクセスできる技術です。

Chromeだとnavigator.usbが使えます。

結構前から注目はしていたけど実際にちゃんとArduinoで動かしてみるのは初めてです。

少し前に調べてた時よりも情報が増えてきてて嬉しい限りです。

参考: GoogleのエンジニアがUSBデバイスとウェブを直接接続できるAPIを作成
参考: Webブラウザからハードウェアにアクセス!WebUSB APIを使ってログイン認証を実装してみよう

環境

  • 母艦
    • Mac Book Pro 2015
    • macOS Mojave 10.14
    • Arduino IDE 1.8.7
    • Google Chrome 70
  • マイコン
    • Arduino Leonardo
      • Amazonで買うと安いかも

とりあえずWebUSBを試してみる

WebUSB/Arduinoを試してみた | Arduino | kosakalabの記事の内容を試してみました。

Arduino向けのWebUSBファームを書き込む

kimio-kosakaさんが公開してくれているファームを使います。感謝です。それにしてもすごい家系(わかる人はわかる)。

Arduino/WebUSBライブラリをインストール

https://github.com/kimio-kosaka/webUSB-arduino/releases

このzipファイルをDLして、Arduino IDEの.ZIP形式のライブラリをインクルードからインストールします。

サンプルコードを書き込み

  • ファイル>スケッチ例>WebUSB>consoleのサンプルコードを開きます

最初だけ写真のようにファイル>スケッチ例>互換性なし>WebUSB>consoleになってました。

ボードをArduino Leonardoにして書き込みます。Arduino LeonardoはUSB MicroBケーブルでPCと接続しましょう。

これでOKです。

WebサイトからLチカ操作してみる

  • https://kimio-kosaka.github.io/webUSB-arduino/console/ にアクセスします。
  • connectボタンを押すと接続しているUSBデバイスが表示されます。
  • Arduino Leonardoが表示されるので選択して接続するとconnectedに表示が変わります。

  • キーボードのHを押すとLEDが付いてLを押すとLEDが消えます。

内蔵のLEDでも確認出来ますが落ちてたLEDを13番に付けてみました。

すごい、さすがに反応が早い。

フルカラーLEDのサンプル

  • 同様にファイル>スケッチ例>WebUSB>rgbのサンプルを書き込む
  • フルカラーLEDを以下のように配線
    • ちなみにフルカラーLEDはオフィスに落ちてたやつなので型番とか分からないけど多分これでいけます。カソード顧問(左から3番目がマイナスなのでGNDに接続)。


引用: https://make.kosakalab.com/nodejs/webusb-arduino/

自前で作ってみた

今回はArduinoスケッチをいじらずにRGBカラーを変えるコードを作ってみます。

CodePenにあったRadial Color Picker - Vueのデモを元に改良してみます。イメージしてるカラーピッカーのサンプルを探したらVue.jsであったのでVue.js使っての実装です。

このライブラリの挙動に関してはradial-color-picker/vue-color-pickerを見ると良いです。

  • Arudinoのスケッチ

元のスケッチのままなのですが、localhostだったり自分の環境で試したいので3行目のhost指定の部分をこんな感じにしました。

rgb.ino
WebUSB WebUSBSerial(1, "localhost");
  • html
index.html
省略

    <div id="app">
        <button id="connect" @click="connectButtonAction" v-cloak>{{connectButtonText}}</button>
        <span id="status" v-cloak>{{statusText}}</span>

        <color-picker v-model="color" @input="onColorInput"></color-picker>
        <h1 v-text="msg"></h1>
        <pre v-html="color"></pre>

        <script src="https://jp.vuejs.org/js/vue.min.js"></script>
        <script src="https://unpkg.com/@radial-color-picker/vue-color-picker"></script>
        <script src="serial.js"></script>
        <script src="app.js"></script>
     </div>

省略        

CSSも入っているので試す場合は元のコードを見た方が良いです。

  • serial.js

こちらをそのまま利用させてもらってます。

  • app.js

利用したカラーピッカーがRGBじゃなくてHSL形式で値をとっているため、変換させる必要があってこちらのコードを入れ込んでます。
(変換ロジックが上手くいってないのか、なぜかちょっと色がずれてる感じがするんですが...)

app.js
var ColorPicker = VueColorPicker;

var app = new Vue({
    el: '#app',
    components: {
        ColorPicker: ColorPicker
    },
    data: {
        msg: 'WebUSB & Radial Color Picker - Vue',
        color: {
            hue: 50,
            saturation: 100,
            luminosity: 50,
            alpha: 1
        },
        statusText: '',
        connectButtonText: 'Connect',
        port: {},
    },

    methods: {
        // HSV(L)をRGBに変換
        hsv2rgb: function(hue, saturation, value) {
          // まるっとここの中身なので省略 http://shanabrian.com/web/javascript/color-code-convert-hsv-to-10rgb.php
        },

        //デバイスとの接続
        connect: async function() {
            try {
                await this.port.connect();
                console.log('connecting...');
                this.statusText = 'connected';
                this.connectButtonText = 'Disconnect';

                //デバイス側から値が送られてくるのを待ち受ける
                this.port.onReceive = data => {
                    let textDecoder = new TextDecoder();
                    console.log(textDecoder.decode(data));
                }

                this.port.onReceiveError = error => console.error(error);                
            } catch (error) {
                console.log(error);
                this.statusText = error;                
            }
        },

        //カラーピッカーの値が変わると反応
        onColorInput: function() {
            if (!this.port) return;
            let view = new Uint8Array(3);

            //HSV(L)の値をRGBに変換
            const color = this.hsv2rgb(this.color.hue,this.color.saturation,this.color.luminosity);

            view[0] = parseInt(color.red);
            view[1] = parseInt(color.green);
            view[2] = parseInt(color.blue);
            this.port.send(view); //データをUSBデバイスに送信

            console.log(this.color.hue,this.color.saturation,this.color.luminosity);
            console.log(this.hsv2rgb(this.color.hue,this.color.saturation,this.color.luminosity));
        },

        //connectボタンのトグル処理
        connectButtonAction: async function(){
            if (this.port) {
                this.port.disconnect();
                this.connectButtonText = 'Connect';
                this.statusText = '';
                this.port = null;
            } else {
                try {
                    const selectedPort = await serial.requestPort();
                    this.port = selectedPort;
                    this.connect();                    
                } catch (error) {
                    this.statusText = error;                    
                }
            }
        }
    },
    //ページ立ち上げ時
    mounted: async function() {
        const ports = await serial.getPorts();
        if (ports.length == 0) {
            this.statusText = 'No device found.';
        } else {
            this.statusText = 'Connecting...';
            this.port = ports[0];
            this.connect();
        }
    }
});

作ったコードはGitHubにも載せておきます。

https://github.com/n0bisuke/webusb_vue_sample/tree/master/colorpicker

Nuxt.jsとSkyWayで1時間でビデオチャットを作ってみるを書いた時もそうですが、navigatorにアクセスする初期起動はmountedに入れてあげて、その関数はmethodsに入れてあげればだいたい移植できそうですね。

参考にさせてもらってる元のrgb.jsと見比べてみてください。

所感

楽しい。

WebブラウザのJavaScriptが直接Arduinoにつながるってすごいですね。

今回はブラウザ -> ArudinoだったのでArduino -> ブラウザもそのうち試してみます。

WebBluetoothと同じような楽しさがあるんですけど@masciiくんの記事にも書いているようにブラウザが直接デバイスに繋がると色々と面白いことできそうですよね。

皆さんもお試しください!

実装してて詰まったところ

  • "DOMException: Unable to claim interface"

調べたら、こちらに当たりました。

Failed to claim interface 0: Device or resource busy

他のタブで開いてて、すでにUSB接続してる場合に怒られるみたいです。
他のタブやウィンドウなどで開いてないか確認して閉じてから再トライしましょう。
僕はこれが原因でした。

参考

めちゃ参考になりました!現状のWebUSBはとりあえずここ読んでおけばOKって感じがします。