0

hubot-rss-readerの本文を見やすくした

hubot-rss-reader

hubot-rss-reader

チャットチャンネルを個別にRSSリーダーにできるhubotスクリプト。
% npm install hubot-rss-reader
でインストールできる。

本文を見やすくした


今までsummaryを表示していたけど、githubのRSS等はsummaryが無くてdescriptionだけある。どちらかある方を表示するようにした。

summaryがHTMLで書かれている場合は、HTMLタグを除去した。

一番最初のimgタグの中身を記事の先頭に持ってくるようにした。slackなど画像の自動ロードが有効なチャット環境の場合、ヘッダ画像のようになって見やすい。

無駄な改行を除去した。3つ以上改行のみの行が続く場合、改行2つにまとめた。

スクリーンショット



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

Macのlaunchdからnodeが起動しなくなったのでpathを追加した

何もしていないのに壊れたんです

brew updateしてnodeをv0.10.28(だったかな?)からv0.10.32にしただけでlaunchdから起動しなくなった
普通にCLIからnpm startとかcoffeeとかからなら起動するけど、
launchd→npm start→coffee が起動しない。


env: node: No such file or directory


というエラーがログに出る


これやったら解決した
MacOSX – Mountain Lionでのシステムワイド環境変数の設定方法 – Qiita

/etc/launchd.conf を作って

setenv PATH /sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin

OS再起動したら元気にnodeが起動した

0

hubotに付いているcoffeeが古くてPromiseが動かない

起こった問題


hubotにごはんを選んでもらうをPromiseで書きなおして、Herokuで動かしているhubotの中で使ったら

[TypeError: Object http://ja.wikipedia.org/wiki/Category:料理 has no method ‘then’]


というエラーがでてNodeプロセスが死んで困っていた。ローカルでは動いている。

hubot-gohan/gohan.coffee at be90c1c25219c66295b1c1a10be507c9731cbfaf · shokai/hubot-gohan

  getGohan: ->
debug 'getting Gohan..'
@getPagesCached "#{@baseUrl}/wiki/Category:料理" ## ここでエラー
.then (pages) =>
return new Promise (resolve) =>
categories = _.filter pages, (page) -> /^\/wiki\/Category:/.test page.link
category = _.sample categories
resolve category
.then (category) =>
@getPagesCached "#{@baseUrl}#{category.link}"
.then (pages) =>
return new Promise (resolve) =>
pages = _.filter pages, (page) ->
!(/^\/wiki\/Category:/.test page.link) and
/^\/wiki\/.+/.test(page.link) and
page.title?
debug "got #{pages.length} pages"
gohan = _.sample pages
resolve {url: "#{@baseUrl}#{gohan.link}", title: gohan.title}


解決方法

hubot –createでhubotのテンプレを作ると付属しているcoffee-script npmが1.6.3なので動かない。

新しいcoffee (1.8.x)を入れるとちゃんとPromiseが動くようになる。
% hubot --create my-hubot
% cd my-hubot/
% npm install coffee-script -save