0

tesselでYo

tesselは普通にnpmが使えるので、Yoとかも送れる

ただ、multipart/form-dataがpostできないのであまり大きな通信はできないみたいだけど、Yoぐらいなら簡単に送れる。

まあYo送るのが簡単なのはどうでもよくて、プログラムの中にAPI Tokenなどの設定値を書きたくないけど実行時に渡すようなのは(環境変数とか)tesselではどうやるのかな、というのを調べたりした。


MacからYoを送る


久しぶりにYo見てみたら、Yo Developer Dashboardからアカウントを作ったり、APIのTOKENを取得できるようになってた。以前のように待たされることなくすぐ作れる。

% npm install yo-api -save
% export YO_TOKEN=a1b2c3defg45678

Yo = require 'yo-api'

yo = new Yo process.env.YO_TOKEN

yo.yo_link 'SHOKAI', 'http://shokai.org', (err, res, body) ->
console.log body


tesselからYoを送る

tessel-study/yo at master · shokai/tessel-study

前に書いたように起動してすぐwifiを再起動して、on ‘connect’後にネットワークを使う処理をするようにする。

var tessel = require('tessel');
var wifi = require('wifi-cc3000');
var Yo = require('yo-api');

var yo_token = process.argv[2];
var yo = new Yo(yo_token);

var led_green = tessel.led[0].output(1);
setInterval(function(){
if(wifi.isConnected()) led_green.toggle()
}, 500);

// wifiを再起動
wifi.reset();

wifi.on('connect', function(){
console.log('wifi connect');
yo.yo('SHOKAI', function(err, res, body){
console.log(err);
console.log(body.toString());
});
});


tesselに引数を渡す

普通API Tokenとかはコード中に書きたくないので、環境変数とか設定ファイルに書く。

tesselの場合、実行時の引数に渡せる。
コード中でprocess.argv[2]でyo_tokenを取得しているが、これは

% tessel run main.js $YO_TOKEN
% tessel push main.js --args=$YO_TOKEN --logs

こうするとargvに渡せる。

package.jsonのscriptsに書いておくとnpm testやnpm startで書き込めて便利。
{
"name": "yo",
"version": "1.0.0",
"description": "",
"main": "main.js",
"scripts": {
"test": "tessel run main.js $YO_TOKEN",
"start": "tessel push main.js --args=$YO_TOKEN --logs"
},
"author": "",
"license": "ISC",
"dependencies": {
"wifi-cc3000": "0.0.0",
"yo-api": "^1.0.0"
}
}

bundleFiles関数でtessel上でのコードにprocess.argvが設定されて、この後Luaに変換されてボードに書き込まれる。
これがtessel pushからはargvが渡らないバグがあったので修正したプルリクを送ったらmergeされた。

tessel runでは今のバージョンでもargv渡せる。tessel pushの方はそのうちnpmが更新されて使えるようになるまで待つか、俺のgithubからインストールするとかする。


nomnom

tessel cliではnomnomというoption parserが使われている。

複数のargvを渡す場合、–argsオプションを複数書く。

% tessel push app.js --args=foo --args=bar --args=baz

これでprocess.argvは
["tessel", "app.js", "foo", "bar", "baz"]
になる。

0

OAuth版Gyazo APIのNode.jsラッパー作った

OAuthで使う新しいGyazo APIができてたので、Node.js用のラッパーを作った。今までのupload.cgiにアップロードする奴とは別の最近できたAPIっぽい。


https://www.npmjs.org/package/gyazo-api
https://github.com/shokai/node-gyazo-api

% npm install gyazo-api


アップロード


開発者としてアプリケーションを登録して、Access Tokenを取得してから

こういう風にアップロードできる。
var Gyazo  = require('gyazo-api');
var client = new Gyazo('ACCESS_TOKEN');

client.upload('/path/to/file.jpg')
.then(function(res){
console.log(res.data.image_id);
console.log(res.data.permalink_url);
})
.catch(function(err){
console.error(err.stack);
});
upload関数はファイルパスでなくstreamやbufferも渡せる。

他にアップロードした画像のリストを取得したり、削除もできる。
Gyazo API、response bodyにJSONで結果が返ってくるんだけどHTTP Headerにもx-per-pageとかx-current-pageとかついているので、res.responseにレスポンスオブジェクトそのまま付けておいた。



Travis CIで俺のアイコンをアップロードしたり消したりしているので、テスト実行する度にgyazo.com/historyに俺の写真が上がったり消えたりしててつらい

0

tesselをカメラ付きwebサーバーにする

Node.jsが動くマイコンボードtesselを買ったのでさっそくカメラモジュールwifitiny-routerというWAFを使ってtessel自体をwebサーバーにし、撮影した画像を配信できるようにしてみた。


ライブカメラというほどの速度は出ないけど、tessel単体で定期的な撮影とhttpでの配信ができた。

マイコンでNode.jsが動くとはいっても、例えばTCPのlistenはWiFiチップと通信しているからチップの状態次第で失敗するし、WiFiの設定をしている間にcameraのreadyイベント取りこぼしたりするから色々とタイミングがシビアでつらい所がある。でもNodeなのでeventemitterがあるからそれほどストレスフルではなくて面白い。


tessel


WiFi

tesselのwifiは802.11b/gの1〜11チャンネルしか使えない。WiFiアクセスポイントがチャンネル12〜14の場合接続できないので注意する。

tesselのwifiは弱いので同じチャンネルが混んでいると接続できない。

Macだと
% /System/Library/PrivateFrameworks/Apple80211.framework/Versions/A/Resources/airport -s
で確認できる。


普通のwebサーバー

まず普通のwebサーバーはこんな感じで書ける。これはnodeのhttpモジュールしか使ってないので、Macでもtesselでも動作する。
tessel-study/main.js at master · shokai/tessel-study
var http = require('http');

http.createServer(function(req, res){
res.writeHead(200, {'Content-Type' : 'text/html'});
res.end('<h1>Hello!!</h1>');
}).listen(80);

console.log('server start at PORT: 80');


普通のwebサーバーの起動

tesselをUSBで接続して、Macにnodeをインストール、ボードのファームウェアアップデート、wifiに接続する
% brew install node
% npm install tessel -g
% tessel update
% tessel wifi -n [WiFi AP NAME] -p [PASSWORD] -s wpa2

そして起動
% tessel run http-server.js

これでtesselが普通のwebサーバーになる。簡単。
tesselのボード上での標準/エラー出力もCLIに表示される。

単体で動作させる

runではなくpushするとプログラムをフラッシュメモリに保存して、Macから離しても単体で動くようになる。
% tessel push http-server.js


単体で動かない

動くと思ったら動かない。socketが開けないエラーが出る

Error: ENOENT: Cannot open another socket.



tesselはLuaでNode.js互換の実行環境を実装してあって、そこに手元で書いたjsファイルとnode_modulesディレクトリ以下を全部Luaに変換して送り込んで実行している。tessel上ではjavascriptではなくLuaが動いている。(なのでevalは使えない)

で、netなどのネットワーク周りのライブラリもTCPをListenする関数とかちゃんと実装してあるんだけど、その実体はtessel上のwifiモジュールのICと通信するという事になっている。

このプログラムが動かない原因は、tesselのMPUが起動してnodeが走っていてもまだwifiモジュールが起きていない(or wifiに接続されていない)から、netまわりの関数が実行できていないということのようだ。


単体で動かせるwifi+httpサーバ+カメラモジュールを実装する

という事で色々とマイコンの気持ちになって実行順を考えて実装したらうまく動くようになった。

tesselはカメラやwifiのモジュールのラッパーがnpmになっているので、インストールしておく。アプリケーションのnode_modules/ディレクトリ以下にインストールしておくと全部まとめてLuaに変換されてtesselに転送される。
% npm i wifi-cc3000 camera-vc0706 tiny-router -save


まず工夫した所・ハマりどころを書いておく。

tiny-routerを使う

expressはtesselで動かせなかった。Errorのスタックトレースを読むあたりの関数とか、色々とtesselのNodeではまだ実装されていない部分があって動かない。
tiny-routerという組み込み用の軽量WAFを使うと軽くて良かった。

wifiを毎回リセットして、接続成功してからhttpサーバー立てる

wifiのAPI見ると、自分でwifiをリセットしたり、wifi.on(‘connect’, callback)とかがある。
毎回プログラムが起動するたびにwifiモジュールをリセットして、on “connect”イベントの後でhttpサーバーを立てるとtessel pushでもtessel runでも動かせる。
既にwifi接続していたらすぐhttpサーバー立てる、接続してなかったらon “connect”で立てる、という方がスマートじゃんと思ったけど、開発中のプログラムが不正終了した場合に(たぶん)wifiモジュールが既にTCPをlistenしているのにMPUはlistenしてないような感じになってて最終的にUSBも認識しなくなったりする事が頻発したので、毎回wifiリセットするようにした。

disconnectとconnectだとwifiのパスワードをプログラム内に書かなければならなくなるので良くない。resetならそのまま再接続してくれる。


カメラの解像度

githubの方のcamera-vc0706のドキュメントを見ると、解像度を下げる方法が書いてある。
camera.setResolution("解像度", callback);
で指定できる。vga, qvga, qqvgaが指定できる。
初期値のvgaのままだとMPUとwifiモジュール間の通信速度がせいぜい数十kbpsしかないはずなので、画像が全然落ちてこなくなる。


cameraとwifiを同時にセットアップしない

var camera = require('camera-vc0706').use(tessel.port['A']);
を実行すると、カメラの初期化が始まって、camera.on(‘ready’, callback)などのイベントが呼ばれるようになる。nodeだと並列に色々やりたいから同時にwifiのリセット〜httpサーバの起動などもやりたくなるけど、1つずつやらないと動かない。

カメラセットアップ→wifi再起動→httpサーバー起動
の順にやる事にした。


末尾ループがわりのsetInterval

httpサーバーも起動せず、何もsetInterval等も動いていない状態だとNodeはふつうにプログラムが末尾で終了する。
終了してしまうので、wifiをresetしてもon “connect”も取れなくなる。
とりあえずsetIntervalで常に基板上のLEDを点滅させておくようにした。動作してるか確認にも使えて便利。


プログラム

色々試行錯誤した結果こうなった。eventemitterのおかげでなんとかなっている感じがする。

tessel-study/camera-server.js at master · shokai/tessel-study

// カメラで撮影してWiFi+HTTPサーバーで配信する
// 15秒間隔で撮影する

var tessel = require('tessel');
var wifi = require('wifi-cc3000');
var router = require('tiny-router');
var camera = require('camera-vc0706').use(tessel.port['A']);
var image = null;

var led_green = tessel.led[0].output(1);
setInterval(function(){
if(wifi.isConnected()) led_green.toggle()
}, 500);

// 解像度設定
camera.on('ready', function() {
console.log('camera ready');
camera.setResolution('qqvga', function(err, res){
if(err) throw 'setting camera resolution failed!';
camera.emit('ready:capture');
});
});

// 撮影準備完了
camera.on('ready:capture', function(){
console.log('camera ready:capture');
camera.startCapture(function(err, res){
if(err) return console.error(err);
image = res;
console.log('capture done');
}, 15000); // 15 sec interval
});

// 定期的に撮影する
camera.startCapture = function(callback, interval){
if(typeof interval !== 'number' || interval < 1){
throw 'interval must be number (msec)';
}
camera.takePicture(function(err, res){
if(typeof callback === 'function') callback(err, res);
setTimeout(function(){
camera.startCapture(callback, interval);
}, interval);
});
};

// カメラの準備ができてからwifiを再起動する
camera.on('ready:capture', function(){
wifi.reset();
});

// wifiが接続できたら、httpサーバーを起動する
wifi.on('connect', function(){
console.log('wifi connect');
var port = (process.env.PORT || 80) - 0;
router.listen(port);
console.log('start HTTP server at PORT: '+port);
});

router.get('/', function(req, res){
console.log(req.method + ': ' + req.url);
res.send('camera-server.js');
});

router.get('/camera.jpg', function(req, res){
console.log(req.method + ': ' + req.url);
if(!image){
res.writeHead(500);
res.end('no capture image');
return;
}
res.sendImage(image);
});

0

Herokuが寝ないようにする

herokuは1時間アクセスが無いとDynoが寝る

たとえsocket.ioをつなぎっぱなしにして頻繁に通信していても寝てしまうのでこうして20分おきに自分で自分にHEADリクエストを送って寝ないようにしてる

request = require 'request'

module.exports = (app) ->

return unless /^https?:\/\/.+/.test process.env.HEROKU_URL

setInterval ->
console.log 'ping'
url = "#{process.env.HEROKU_URL}?time=#{Date.now()}"
request.head url, (err, res) ->
if !err and res.statusCode is 200
console.log 'pong'
, 60 * 1000 * 20 # 20 min


環境変数HEROKU_URLに自分自身のURLを設定して使う

% heroku config:set HEROKU_URL=https://(APP_NAME).herokuapp.com

0

GyazzにDeploy to Herokuボタンを付けた

githubにherokuへのデプロイボタンが付けれるようになった
Heroku | Introducing Heroku Button
Creating a 'Deploy to Heroku' Button | Heroku Dev Center


ので、node版gyazzのREADME.mdにDeploy to Herokuボタンを付けた。



ボタンを押すと、2分ぐらいで自分のHerokuアカウントでGyazzが動かせる。
アドオンも使える。

アドオンや環境変数の設定はapp.json Schema | Heroku Dev Centerで説明されているとおり、リポジトリのルートにapp.jsonを置いて指定する。city72のapp.jsonも参考になった。

Herokuボタンは https://heroku.com/deploy へアクセスした時のリファラがgithubのリポジトリだったらそれをcloneしてデプロイする、という簡単な仕様なので、もともとherokuで動かせるアプリなら本体のコードに手を入れること無く本当にREADMEにimgタグとaタグでボタン付けるだけで済んでしまっている

また、リファラのかわりにGETパラメータに?template=(githubのURL)を付けてもよい。
これ↓押すとgyazzが自分のherokuアカウントにデプロイされる。


すごい簡単に自分のところでprivate gyazzが動かせる。
簡単なツールみたいなwebアプリを、自分のところでサーバー建てて使ってねってのが言いやすいのはよい。気兼ねなくMongoDBとかmemcacheとか使うようにできるので、よくあるwikiやCMSとかみたいにSQLiteしか使わない縛りとか考えなくていい。

プルリクのメニューにある「browse code」でコミットのREADMEに飛んでからHerokuボタン押すと、そのプルリクをHerokuでデプロイして試せるのはすごい。例えばスマホWebアプリだったら、スマホでプルリク見てHerokuで試してmergeしてCI通して自動デプロイとかまでできる感じがある。ネイティブアプリよりプルリクを取り込みやすくなりそう。

設定


app.json
{
"name": "Node Gyazz",
"description": "Gyazz on Node.js",
"website": "https://github.com/masuilab/gyazz",
"success_url": "/memo/test",
"addons": [
"mongolab",
"memcachier"
],
"env": {
"NODE_ENV": "production",
"DEBUG": "gyazz:*",
"GYAZZ_URL": "https://(YOUR_APP_NAME).herokuapp.com",
"TZ": "Asia/Tokyo"
}
}

ボタン
[![Deploy](https://www.herokucdn.com/deploy/button.png)](https://heroku.com/deploy)