bottleでScratchXとDigisparkをつなげる

f:id:uitspitss:20160123181812j:plain

タイトルの簡単な説明

  • bottle → python で作られたシンプルな Webフレームワーク。
  • ScratchX → ウェブブラウザベースな Scratch2.0プラグイン拡張をできるようにしたもの?(曖昧)
  • Digispark → 1000円ちょっとで買える、小さくて Arduino クローンなすごいやつ。

やること

DigisparkのRGB Shield(LED Shieldではなく)を使ってプログラミング入門的なことをしたいので、
プログラミング入門に最適であろう Scratch (ScratchX) からDigisparkを操作する。
※実際にやる際は、ご自分の責任でお願いいたします。

動作の流れ

環境

Digispark

  • DigisparkのWikiを参考にArduino IDEをインストールする。
  • 上記ページを参考にArduino IDEにDigisparkのボードマネージャーを入れる。
  • ArduinoIDEで、ファイル > スケッチの例 > DigisparkUSB > DigiBlink を開いて、Digisparkに書き込む。
    ※"→"のアイコンのボタンが書き込むボタン。"Plug in device now..."の表示が出てきたら、Digisparkを抜き差しする。上手く書き込めなかった場合は、諦めず、また書き込む。2,3回の書き込み失敗はよくある。諦めない、大事。

libusb

USBライブラリ

Windows(Win7)
  • Windowsではドライバのせいか、うまくいかなかったので、ドライバを書き換えた。
  • zadig(usbドライバ書き換えソフト)をダウンロードする。
  • zadigでDigiUSBのドライバをlibusb-win32に変更する。※他のデバイスのドライバを書き変えるとマズいので、慎重に…
    追記 2016/09/26
    他のサイトなどを見ると、WinUSBのほうが多数なので、私の方でもWinUSBに変更するようにしています。
    追記終わり
OSX(El Capitan 10.11)
  • homebrewでlibusbをインストールする。
brew install libusb
Linux(Ubuntu14.04)
  • apt-getでlibusbをインストールする。(なんとなくdevバージョンを入れた)
sudo apt-get install libusb-dev

Python

※いずれの環境下でも、バージョンは 3.5.0 だった

  • pythonをインストール

  • pip(pythonパッケージマネージャー)をインストール
    easy_install pip

  • bottleとpyusbをインストール
    pip install bottle pyusb

  • LinuxでユーザーローカルのPythonだと、pyusbを使ってUSBにアクセスするとき、パーミッションエラーが出たので、Stack Overflowのこの投稿USB Devices - Google Chrome を参考に解決した。
    (Chromeのページを見ると、pyusbに限ったことではない感じ。)

  • 私の場合は、/etc/udev/rules.d/50ーdigispark.rulesというファイルを作り、

SUBSYSTEMS=="usb", ATTRS{idVendor}=="16c0", ATTRS{idProduct}=="05df", MODE="0664", GROUP="plugdev"

と書き込み、再起動した。

ウェブブラウザ

ScratchX が Flash Player で動作してるので、 Flash Player が動くブラウザなら何でも良いはず。

プログラム

app.py
  • bottle のローカルサーバーを建てて、ScratchX で動いている javascript からRGBデータを受ける。
  • pyusbでDigisparkに命令を送る。
#!python
#-*-coding:utf-8-*-
from __future__ import print_function
from __future__ import unicode_literals
from __future__ import division


# for bottle
from bottle import route, run, template, request, response, static_file

# for scratchx
import usb
import sys
sys.path.append("..")
from arduino.usbdevice import ArduinoUsbDevice

theDevice = None

@route("/")
def top():
    return '''
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title></title>
</head>
<body>
  <a href="http://scratchx.org/?url=http://localhost:9000/main.js">ScratchX</a>
</body>
</html>
'''

@route("/<filepath:path>")
def server_static(filepath):
    return static_file(filepath, root="./")

@route("/blink")
def blink():
    theDevice = ArduinoUsbDevice(idVendor=0x16c0, idProduct=0x05df)

    red = request.query.get('red')
    green = request.query.get('green')
    blue = request.query.get('blue')

    print("red:{}, green:{}, blue:{}".format(red, green, blue))

    red = int(mapping(red))
    green = int(mapping(green))
    blue = int(mapping(blue))

    theDevice.write(ord("s"))
    theDevice.write(red)
    theDevice.write(green)
    theDevice.write(blue)

    print("mred:{}, mgreen:{}, mblue:{}".format(red, green, blue))

def mapping(arg):
    arg = float(arg)
    if arg < 0:
        return 0
    elif arg > 100:
        return 255
    else:
        return arg * 0.01 * 255

if __name__ == '__main__':
    run(host="localhost", port=9000, debug=True, reloader=True)
main.js
  • ローカルサーバーにRGBデータを送るためのブロックを ScratchX に作る。
(function(ext){
  var device = null;

  ext._deviceConnected = function(dev){
    if(device) return;
    device = dev;
    console.log(device);
    device.open();
  };

  ext._deviceRemoved = function(dev){
    if(device != dev) return;
    device = null;
  };

  ext._shutdown = function(){
    if(device) device.close();
    device = null;
  };

  ext._getStatus = function(){
    if(!device) return {status: 1, msg: 'digiUSB disconnected'};
    return {status: 2, msg: 'digiUSB connectd'};
  };

  ext.blink = function(r, g, b){
    $.ajax({
      type: "GET",
      url: "http://localhost:9000/blink",
      dataType: "script",
      data: {
        red: r,
        green: g,
        blue: b
      }
    });
  };

  var descriptor = {
    blocks: [
      ["",  "red: %n, green: %n, blue: %n で光らせる", "blink",
       "100", "100", "100"]
    ],
    menus: {},
    url: 'http://localhost:9000'
  };

  var hid_info = {type: 'hid', vendor: 0x16c0, product: 0x05df};
  console.log(ScratchExtensions.register('DigiUSB', descriptor, ext, hid_info));
})({});

動作手順

Directory
|- arduino/
|- app.py
|- main.js
  • bottleを起動する。
    python app.py
  • ウェブブラウザでhttp://localhost:9000にアクセス。"ScratchX"というリンクがあるので、リンク先に飛ぶと同階層にあるmain.jsが読み込まれたScratchXのページが開く。
  • スクリプトタブの"その他"にある、"~で光らせる"ブロックを使ってプログラムを作成すると、その通り動く。 (RGBがそれぞれ2byteのデータ(数値では0~255)を受けるが、入門状態ではその説明からすることになり面倒なので、app.pyの中で0~100を0~255にスケールしている。要は、ScratchX上では0~100の範囲で数値を吐き出そう、ということ)

最後に

  • ScratchXのWiki を見ると、USB-HID に javascript からデータを直接送れるようになっているはずだが、上手くいかなかったので、今回のようなまわりくどい形になってしまった…
    上記ページのコードを見ると、このあたりを使っている感じではある。
  • chrome.hid
  • chrome.usb

  • main.jsにはその名残りを残してて、Scratch Extensions Browser Pluginプラグインを入れると、ScratchX上の "その他のシグナルが未接続状態の赤ではなく、接続状態の緑色になる。(OSX, Chromeでのみ確認)

  • 今回はRGB Shieldを使ったが、今は在庫切れのようなので、たぶん同じことができるLED Shiledを取り寄せ中。Wikiを見る限り、AdafruitのNeoPixelクローンのような感じ。(曖昧)

  • 前述の通り、ScratchXは Flash Player 上で動作をする。しかし、LLKのgithubを見ると、Playerであるものの ScratchのHTML5バージョンのリポジトリもあって、今後の完全HTML5化に期待。できるのかは不明。

  • プログラミングの敷居は本当に低くなった(技術的にも、経済的にも)。フィジカルコンピューティングまがいなこともいろいろとできる。「プログラミングの更なる低年齢化」が話題になってほしい。

参考ページなど