はじめに
今日、昨年夏に Back していた Tessel が届きました。
Tessel は、Node.js ベースの JavaScript 環境を利用してハードウェア制御可能なマイコンボードです。スタンドアロンで WiFi 接続可能で、USB による電源供給のみで動作します。本体にはモジュール拡張用に 4 つのポートがついており、ここに SD カード読み込みモジュールやオーディオ入出力モジュール、加速度や温度・照度などのセンサモジュールなどの様々なモジュールを差し込むことで拡張が可能です。そしてこの一つ一つのハードウェアモジュールを操作するための Node モジュールが npm で公開されており、バグ修正も含めて Node.js のプラットフォームの上に乗っかっている形でトキメキます。Node のレゴブロックのように小さなモジュールを組み合わせてプログラムを作成する、という概念をハードウェアにまで拡張したみたいなイメージで、とても面白いです。もちろん主要な Node.js のモジュールも(C++ モジュールを除いておおよそ)動作するため、HTTP サーバを立てるのも require('http')
するだけで可能です。
性能としてはプロセッサは LPC1830(Cortex-M3, 180 MHz)、32 MB の Flash および RAM を備えているため、ATmega を積んだ Arduino よりは大分高性能になっていますが、Intel の Galileo と比較すると見劣りする感じです。が、上述のモジュールの話もそうですが、Node.js の EventEmitter をベースにした非同期周りの処理をコーディング出来るのは、人間による操作や、あるスレッショルドを超えたセンサからやってくるイベント通知などを非常にハンドリングしやすいという魅力があると思います。大きさは、モジュールを外した状態で Arduino UNO よりちょっと小さいくらいです。
Tessel は Technical Machine という会社によるプロジェクトです。Dragon Innovation というスマートウォッチの Pebble や 3D プリンタの MakerBot を成功に導いたコンサルティングファームに支援されており、クラウドファンディングによる出資でスタートしました。
- Pebbleの1000万ドル調達を陰から支えた、ハードウェアスタートアップ特化のプロデューサーDragon Innovation - Build Something!
- Tessel - Dragon Innovation
そして、2013/10/5 に目標額($50,000)の 393% にあたる $196,682 を獲得して成功に至ります。そこからおよそ9ヶ月が経過して、ついに昨日手元に届きました(Twitter を見ていると早い人は先週手にしていました)。私は $349 の「The Master Pack」に Back していたのでモジュールも色々届き、加速度 / サーボ / Micro SD / 照度・騒音 / 赤外線 / リレー / BLE / GPS / カメラ / RFID / オーディオが今手元にあります。現在の価格は本体 + モジュール1個で $99~ 、モジュールはひとつ $25~ です。
本エントリでは Tessel のチュートリアルを含めた外観と、このモジュールたちの一部の紹介、そして Tessel の仕組みについて調べてみた内容を紹介したいと思います。
環境
- Node 0.10.29
- Mac OS X 10.9.3
環境構築 〜 L チカ(本体)まで
まず、以下にアクセスして手順に従って設定していきます。
$ brew install node $ npm install -g tessel
今回は Mac で作業しているので brew
で Node.js をインストールしましたが、Windows なら公式のインストーラから、Linux なら apt-get
などで入れれば、同様に npm install
コマンドで tessel
コマンドを導入することが出来ます(すでに導入済みなら不要)。そして、Tessel を USB でつないで以下のコマンドを実行します。
$ tessel update
なんとこれだけでファームウェアのアップデートが終わり準備完了です。最初に L チカするために以下のコードを blinky.js
という名前で保存しておきます。
var tessel = require('tessel'); var led1 = tessel.led[0].output(1); var led2 = tessel.led[1].output(0); setInterval(function () { led1.toggle(); led2.toggle(); }, 100);
コンソールから $ tessel run
コマンドでこの JS を Tessel 側にインストールして実行します。
$ tessel run blinky.js
これだけで 100 msec おきにライトが交互に点滅します。 大変簡単です。
なお、$ tessel push
コマンドを使うとコードを Flash に保存し、電源投入時に自動実行してくれるようになります。
$ tessel push blinky.js
モジュールの利用
Tessel には 4 つのモジュールポートが備えられており、様々な機能を搭載したモジュールをそこへ差し込むことで利用できるようになっています。
モジュールは Servo や、Camera など現在14種類用意されています。
各モジュールの使い方や仕様は以下のページにまとまっています。
ここでは試しに Servo と Camera を使ってみます。
Servo
サーボモジュールは 3x16 pin が配置されたモジュールです。ここに 3 pin(GND、VCC、制御信号)を備えたサーボを最大 16 個つけられる形になっています。チュートリアルに書いてある向きで配線を行えば OK です。なお、サーボは ES3001 が付属で1つついてきました。
次に Servo を制御するために Node モジュールをインストールします。
$ npm install servo-pca9685
モジュールはデバイスを便利に扱えるように抽象化したもので、 JS のレイヤのみで書かれているためビルドなどは走りません。そして次のようなコードを書きます。
var tessel = require('tessel'); var servolib = require('servo-pca9685'); // サーボモジュールを挿したポートを指定 var servo = servolib.use(tessel.port.A); // サーボモジュールにサーボを挿したピン位置を指定(1 ~ 16) var pin = 1; servo.on('ready', function () { // 回転位置 (0.0 ~ 1.0) var pos = 0; // ピン位置、PWMの設定(min/max 時の duty 比)、コールバック servo.configure(pin, 0.05, 0.12, function() { // 500 msec 置きに回転させる setInterval(function() { pos = (pos > 1.0) ? 0.0 : (pos + 0.1); servo.move(pin, pos); }, 500); }); });
サーボの個体差も考慮出来るように作っているようで、最低限のコードを書くだけで動きます。
問題などあれば、モジュールごとにフォーラムが開かれているようなので、過去ログを見たり質問すれば良さそうです。
Camera
次にカメラモジュールです。
var tessel = require('tessel'); var camera = require('camera-vc0706').use(tessel.port.A); var led = tessel.led[0]; camera.on('ready', function() { led.high(); camera.takePicture(function(err, image) { if (err) throw err; led.low(); process.sendfile('image.jpg', image); camera.disable(); }); });
これで動作時に LED が点灯して LED が消えるとファイルの保存が完了です。process.sendfile()
でローカル(PC側)にファイルを転送しています。転送先は tessel run
コマンドに --upload-dir
オプションを付けて指定します。
$ tessel run camera.js --upload-dir .`
これで撮影した画像が以下になります。
動画のストリーミングなどは出来ないようです。カメラのピントの調節はフォーラムに書いてありましたが、カメラ上部を回せば良いようです。
長くなるので、モジュール紹介はこの辺りに留めておこうと思います。その他にも、SD Card、Ambient、IR、Relay、Audio、Accel、RFID、GPS、BLE のモジュールを注文していますので、これらについては別エントリにて紹介したいと思います。
WiFi の接続
Tessel は tessel wifi
コマンドで設定することで、単体で 2.4 GHz 帯の WiFi に接続することが出来ます。つまり、USB 電源供給だけでスタンドアロンで HTTP アクセス可能な Node.js 実行環境が簡単に出来るわけです。
$ tessel wifi -n [network name] -p [password] -s [security type*]
$ tessel wifi -l
で利用可能な WiFi リストを確認できるようです(何故か、どれかに接続するまでは一覧が見えず There are no visible networks yet.
となってしまいましたが...)。
$ tessel wifi -n HOGEHOGE -p ******** -s wpa2 TESSEL! Connected to TM-00-04-f0009a30-006e4f43-3c9865c2. INFO Connecting to "HOGEHOGE" with wpa2 security... INFO Acquiring IP address. ................... timeout. INFO Retrying... INFO Acquiring IP address. . INFO Connected! IP 192.168.0.12 DNS 192.168.0.1 DHCP 192.168.0.1 Gateway 192.168.0.1
接続が完了すると、オレンジ色の Conn という名前のついた(小さく書いてある)ライトが光るようになります。
例えば、http.get()
を使ったコードを書いてみます。
var http = require('http'); var weatherApi = 'http://api.openweathermap.org/data/2.5/weather?q=Tokyo,jp'; var req = http.get(weatherApi, function(res) { var body = ''; res.on('data', function(chunk) { body += chunk.toString(); }); res.on('end', function() { var json = JSON.parse(body); console.log(json.weather[0].main); }); }); req.on('error', function(err) { console.log(err); });
これは node
コマンドでも動くコードですが、$ tessel run
してみると、同様に動きます。
$ node http.js Clouds $ tessel run http.js TESSEL! Connected to TM-00-04-f0009a30-006e4f43-3c9865c2. INFO Bundling directory /Users/hecomi/Dropbox/Program/js/tessel (~237.08 KB) INFO Deploying bundle (325.00 KB)... INFO Running script... Clouds
天気に応じて LED の色を変えるとかも簡単にできるわけですね。
HTTP サーバを建てることも出来ます。リクエストを送ると何か動くみたいなことも簡単です。次のコードはアクセスすると Ambient モジュールで取得した周囲の明るさと音量を JSON で返すものです。
var tessel = require('tessel'); var ambient = require('ambient-attx4').use(tessel.port.A); var http = require('http'); var ip = require('os').networkInterfaces().en1[0].address; var port = 80; ambient.on('ready', function() { http.createServer(function(req, res) { ambient.getLightLevel(function(err, light) { ambient.getSoundLevel(function(err, sound) { res.writeHead(200); res.end( JSON.stringify({ light : light.toFixed(4), sound : sound.toFixed(4) }) ); }); }); }).listen(port); console.log(ip, port); });
$ tessel push
しておけば、ハードウェアと連携するスタンドアロンな HTTP サーバの出来上がりです。
現在は、Tessel への WiFi を経由したコード転送は出来ませんが、ロードマップ上には乗っているようで、近いうちに可能になる見込みのようです。
- https://forums.tessel.io/t/upload-code-over-wifi/69
- Technical Machine — JS on MCUs – Only Getting Better From Here
GPIO などのハードウェアの利用
以下にハードウェア周りの API がまとまっています。
npm モジュールで動作する外付けのモジュールと異なり、組み込みの tessel
モジュールをベースに制御を行います。
本体 LED の操作
本体には操作可能な 4 つの LED が搭載されています。
はじめに L チカした blinky.js
では緑の tessel.led[0]
と青の tessel.led[0]
を操作していましたが、この他にエラーを表現する赤の tessel.led[2]
と WiFi の項で見た黄の tessel.led[3]
が制御可能です。
ボタンの利用
本体には 2 つのボタンが搭載されています。1 つはリセットを行うボタンなので、プログラムからはコンフィグ用のボタンが操作可能です。
var tessel = require('tessel'); tessel.button.on('press', function(time) { console.log('the button was pressed!', time); }); tessel.button.on('release', function(time) { console.log('button was released', time); });
ちょっとしたことを行う際に手動のトリガとして使うのに便利です。
GPIO について
これまで、tessel.port.A
で A ポートに接続したモジュールを操作してきましたが、ポートは外付けの A
、B
、C
、D
に加えて GPIO
が使用可能です。
GPIO の口は 20 pin あって、そのうち汎用的に使えるアナログは 6 pin(A1 〜 A6)、デジタルも 6 pin(G1 〜 G6)あります。GPIO を使った L チカはこんなコードで書けます。
var tessel = require('tessel'); var gpio = tessel.port.GPIO; var pin = gpio.digital[0]; (function led(on) { pin.write(on); setTimeout(led, 100, !on); })(true);
ちなみに、モジュール用のポートも使用可能です。
var tessel = require('tessel'); var gpio = tessel.port.A; // GPIO --> A var pin = gpio.digital[0]; (function led(on) { pin.write(on); setTimeout(led, 100, !on); })(true);
アナログピンを使ってセンサの値を取得するのも簡単です。以下は例として圧電センサの値を取得してみたコードです(抵抗値変えて電圧見てるだけです)。
var tessel = require('tessel'); var gpio = tessel.port.GPIO; var pin = gpio.analog[0]; setInterval(function() { console.log(pin.read()); }, 100);
また、割り込み機構も用意されていて、Tessel ではこれを EventEmitter
を使って実現しています。
var tessel = require('tessel'); var pin = tessel.port.GPIO.digital[0]; pin.on('rise', function() { console.log('high'); }); pin.on('change', function() { console.log('change', pin.read()); }); pin.on('fall', function() { console.log('low'); });
割り込みについては以下に詳しく書かれています。
他にも色々と出来るので詳しくはドキュメントをご参照下さい。
通信
組み込みのモジュールで SPI、I2C、UART が提供されています。
Arduino との接続
Tessel と Arudino を連携させることも出来ます。データは UART でやりとりします。Arduino では UART のソフトウェアシリアルのライブラリを利用し、Tessel では組み込みの UART ライブラリ(JS)を利用するようです。
時間があったらやってみます。
ハードウェアについて
ハードウェアについては以下に記載されています。モジュールの位置やピン配置が記載されています。
冒頭でも述べましたが、CPU は 180 MHz の LPC1830(Cortex-M3)、Flash / RAM は 32 MB、WiFi モジュールは TI の CC3300 で、技適承認済みのようです(本体に技適マークがあります)。
ソフトウェア側の仕組みについて
個人的に一番興味があるのがソフトウェアのアーキテクチャです。一体どうやって Tessel で Node.js を動かしているのか見ていきます。
Tessel では JavaScript を Lua にコンパイルして本体に転送しているようです。このコンパイラは Colony というもので、Tessel 専用の Lua とともに github に公開されています。
この Colony は timcameronryan さんが作られたもので、2011 年辺りから公開されています。
そして、timcameronryan さんは Technical Machine の co-founder で Tessel の作者でもあります。
tessel
コマンドは Node.js のスクリプトになっていて、tessel run
をすると、tessel-run.js
スクリプトが走るようになっています。この中で Colony を走らせて文字列化し、tessel へ転送を行っています。
var data = convertToContext(cmd.slice(1, -2)); var script = 'local function _run ()\n' + colonyCompiler.colonize(data, {returnLastStatement: true, wrap: false}) + '\nend\nsetfenv(_run, colony.global);\nreturn _run()'; client.send(script);
さて、すると Lua に変換しているということは Node.js はどうなってるの?ということが気になります。色々調べてみてまだ不明確なのですが、どうやら Node.js の各 API を自前で再実装して Lua とバインディングしているように見えます。以下が Tessel の firmeware と runtime のリポジトリです。
例えば、joyent の http-parser ですが、Node.js では以下のように node_http_parser.cc
で V8 とバインディングを行い、http.js
でユーザが使用するモジュールに仕立てていると思います。
- node/src/node_http_parser.cc at master · joyent/node · GitHub
- node/lib/http.js at master · joyent/node · GitHub
- node/lib/_http_client.js at master · joyent/node · GitHub
これに対し、Tessel の http.js
を見てみます。
COPYRIGHT を見ても分かる通り、どうやら書き直しをしているようです。process.binding
で http-parser
を読み込んでいますが、じゃぁこのバインディングはどこでやってるのか...、と探してみると以下のファイルのようです。
Lua とのバインディングがゴリゴリ書いてあるように見えます。なんという力技...。つまり、Tessel 内では Node.js は動いておらず、Node.js の様な何かが動いているものと思われます。
イベントループ部も見てみます。Node.js では libuv の uv_run
が以下の場所で動いています。
- node/src/node.cc at v0.11.13-release · joyent/node · GitHub
- node/src/node.cc at v0.10.29-release · joyent/node · GitHub
これに対し、Tessel はエントリポイントから順に追っていくと以下に行き着きます(あってるか不明)。
- スタンドアロン時
- USB 転送時
完全に独自で回しているようです。やはり Node.js のような何かが動いている説が濃厚なようです。つまり、Tessel は JavaScript のランタイムは持たず、JavaScript を Lua に変換して、その Lua スクリプトを予め Node.js の API を再実装しておいた Lua スクリプトと共に動かす、みたいなことをやっているのかなと思います。
Compatibility のところでも書いてありましたが、いくつかの API が not implemented yet なステータスになっているのも納得できます。
すごいことをしている...。
ネイティブ拡張
C 言語と JS のバインディングについて以下で説明されています。
ただ、ソフトウェア側の仕組みで見たようにNode の C++ モジュールのように V8 ベースにモジュールを書けるわけではなく、firmware リポジトリで作成している組み込みの tessel
モジュールの Lua-C バインディングをいじってリビルドし、ファームウェアを書き換える、という形になっているようです。よっぽどのことがないとやる気は起きません。
その他
インタラクティブシェル
$ tessel repl
コマンドでインタラクティブシェルを立ち上げることが出来ます。
$ tessel repl TESSEL! Connected to TM-00-04-f0009a30-006e4f43-3c9865c2. INFO Bundling directory /Users/hecomi/.nodebrew/node/v0.10.29/lib/node_modules/tessel/scripts/repl (~953 bytes) INFO Deploying bundle (6.00 KB)... INFO Running script... > process.versions { tessel_board: 4, colony: '0.10.0', node: '0.10.0' } > (^C again to quit) > $
Node.js のバージョンは 0.10.0 と出ていますが、先述のように中で動いているのは Node.js のような何かだと思われるので、単に Colony と同じバージョンを出力しているのかなと思います。
このように tessel
コマンドでは色んな便利機能が定義されています。他にどんなことが出来るかは tessel
コマンドに聞くかドキュメントを見ると分かります。
$ tessel --help Tessel CLI Usage: tessel list tessel logs tessel run <filename> [args...] run a script temporarily without writing it to flash -s push the specified file only (rather than associated files and modules) tessel push <filename> [options] see 'tessel push --help' for options list tessel erase [--force] erases saved usercode (JavaScript) on Tessel tessel repl interactive JavaScript shell tessel wifi -n <ssid> -p <pass> -s <security (wep/wpa/wpa2, wpa2 by default)> tessel wifi -n <ssid> connects to a wifi network without a password tessel wifi -l see current wifi status tessel stop tessel check <file> dumps the tessel binary code tessel blink uploads test blinky script tessel update [--list] updates tessel to the newest released firmware. Optionally can list all builds/revert to older builds. tessel debug [script] runs through debug script and uploads logs tessel version [--board] show version of tessel cli. If --board is specified, shows version of the connected Tessel
LIBUSB_TRANSFER_TIMED_OUT
よく LIBUSB_TRANSFER_TIMED_OUT
というエラーに出くわします。$ tessel erase --force
でリセットしようとしても Failed to load bootloader: found state app
とエラーが出てしまい手詰まりになります。この解決方法についてはフォーラムに書かれていました。
本体に Reset と Config ボタンが有るのですが、「Reset 押す -> Config 押す -> Reset 離す -> Config 離す」をしてハードリセットしてから、 $ tessel erase --force
をすれば良いようです。
Ambient モジュールでのエラー
使おうとすると、Error retrieving firmware version
というエラーが出ました。これは現在修正中のようです。
対症療法としては、以下のコードを実行することです。
var tessel = require('tessel'); var ambientLib = require('ambient-attx4'); ambientLib.updateFirmware('node_modules/ambient-attx4/firmware/src/ambient-attx4.hex');
おわりに
JavaScript によるハードウェア制御自体はそれほど珍しくありません。例えば node-serialport
を使えば色々出来ますし、私も以前 ZigBee 制御に使ってみたりしましたし、スタンドアロンという意味では Rasberry Pi x Node.js で GPIO 制御をされた方もいました(モジュールもあります)。
- node-xbee を使って JavaScript で XBee から温度取得してみた - 凹みTips
- Raspberry Piに接続したフルカラーLEDをNode.jsから制御する - 人と技術のマッシュアップ
- pi-gpio
しかしながら、ここまで色々なハードウェアがモジュール化され、しかも Node.js 自体がマイコンに組み込まれた環境というのはありませんでした。冒頭でも述べましたが、更にハードウェア自体がパッケージマネージャで管理される、というのもとても新しくナウい感じがしますし、非同期イベントのハンドリングがとても楽なのも魅力的です。
ただ Arduino と比較すると、向こうはノウハウも多くシールドも豊富なので見劣りするところもあります。今後、どれだけ多くの人が参加して盛り上げていくかがとても重要になりそうですが、ソフトウェアエンジニア的には魅力的に感じるところも多いので、興味を持たれた方はちょっとお高めですが是非注文して色々遊んでみてください。