もともと実験的に作ったwebアプリとか、twitter botとか置いておくのに使っていた。
ソースはgithubで管理してるから、必要なものはすぐ再セットアップできるだろと思って再インストールしたら、再セットアップのやる気がでなかった。
やる気がでなかったのは最近のgit pushすればすぐデプロイできるPaaS環境に慣れすぎてしまっていて、ちまちまセットアップするのが面倒になってしまった為。
ちょっとしたwebアプリならherokuにgit pushすればすぐ動くし、静的なページならgit push origin master:gh-pagesすればgithubでホストしてもらえるしでVPSが必要なくなってしまった。
botとかは、最近自宅のMacMini用に16GBのメモリが1.5万円ぐらいだったので買って付けたら、すごい快適になってVirtualBox上のlinuxで十分になってしまった
あまりにも世情にうといので作った。
NHKのRSSを定期的にチェックして、新着ニュースの動画を連続自動再生する。
とてもテレビっぽい。おかげで4日後にオリンピックが開催されるという事を知れた。
ソースコード
https://github.com/shokai/nhk-news-app
zipでダウンロード
https://github.com/shokai/nhk-news-app/releases
node-webkit
node-webkitはネイティブアプリ作成のためにwebkitが改造されたwebブラウザで、HTML/JavaScript/CSSが実行できるだけでなくnode.jsのAPIもそのまま呼び出せる。つまりjQueryでDOM操作すると同時にnodeのライブラリを使うような処理が、同じプログラムファイルにまとめて書ける。変にブリッジを書く事なくいつものnodeのように require(‘モジュール名’) するだけなのでとても簡単。
今回は配布するアプリとして実装したけど、デジタルサイネージなどの端末にインストールして置いておく系アプリの実装にも便利だと思う。
アプリを作るのも、HTMLとJSとpackage.jsonを1つのzipにかためてnode-webkit.app/Contents/Resources/app.nwにリネームして配置するだけなので簡単だった。
node-webkit.app自体はhttps://github.com/rogerwang/node-webkitのdownloadsから安定版をダウンロードするといい(Macのrc版は開発パネルを開くまでjsが止まるバグがあった)
npmを使う場合も、node_modulesディレクトリ以下もzipにまとめてしまえば普通に読み込める。
くわしい解説は
node-webkitを触ってみた – 終わる世界とコンテンツ
と
https://github.com/rogerwang/node-webkitのquick startを見るとわかりやすい。
nhk-news.appの実装
色々工夫してる。ネイティブアプリで作らないと実装できなかったと思う。最初ただスクレイピングしてmp4持ってきてローカルで再生させようと思って、NHKのニュースのHTMLを見ていたんだけど、どうしてもmp4のURLがわかりそうでわからなかったのでブラウザ拡張を作ろうと思った。
それで久しぶりにGreasemonkey使おうかと思ったけどせっかくだからnode-webkit使ってみた。
新着の取得
nodeのfeedparser npmでNHKのRSSから新着取得し、requestとcheerioとasyncで事前にニュースページのHTML内容を確認、動画が無いニュースは除外する表示
新着ニュースをiframeに読み込む(jqueryで)ふつうiframeの中が別ドメインだと操作できないけど、node-webkitだとsame-originポリシーが無いのか操作できた。
NHKニュースは動画のサムネイルをクリックするとFlashを読み込んで再生開始してくれる。iframeがロードされたらサムネイルにクリックイベントを送るようにした。
また動画の表示サイズを大きくしている。
動画の終了を判定
動画はFlashなので、再生状況を外部から取得できない。定期的にWindow.capturePageする事で比較できた。DATA URLでjpeg/png画像が取れる。
nodeのライブラリ
ふつうnodeではnpm installするとnode_moduelsというディレクトリが作られて、その中にライブラリがインストールされる。node-webkitで使うにはnode_modulesディレクトリもまとめてzipに固めてしまえばいい。cheerio – DOMのparser
request – HTTPリクエストする
async – 複数の非同期処理をまとめて扱える
feedparser – RSS/Atom Feedのパーサー
lodash – underscoreの速いやつ
eventemitter2 – eventemitterの速いやつ
を使った。npm installするだけでこれらをjQueryと一緒に使えるので楽で良い。
ビルド
Rakefileを書いた。rake debugでcoffeeのソースマップ付きjsを吐いてアプリに固めたりとか。rake releaseで開発パネルが無効化されたアプリを書き出す。
raspbianの場合、aptからインストールできるnodeは0.6.xなので古すぎる。
しかしソースからビルドするにもRaspberry PiのCPUがあまりにも弱いので2時間かかる。
よく見たらビルド済みのバイナリがあったのでコピーするだけだった
wget http://nodejs.org/dist/v0.10.24/node-v0.10.24-linux-arm-pi.tar.gz
tar -zxvf node-v0.10.24-linux-arm-pi.tar.gz
sudo mv node-v0.10.24-linux-arm-pi /usr/local/node
export PATH=/usr/local/node/bin:$PATH
node -v
v0.10.24
ただ、nodeだけgrunt-cliやsupertestのpackage.jsonがJSON parse errorにでインストールできない事がある。よくわからないけど
nodeにarduinoのコードを埋め込めるarduino-firmata npmを古いArduino(diecimila、duemillanove、Seeduino等)に対応させた。
arduino-firmataを使うとnodeとarduinoのそれぞれのコードを書いて通信させるのではなく、nodeの中にarduino.digitalWrite(13, true)とかarduino.analogRead(3)とか書けるのでコードが綺麗になって大変便利です。ご利用ください。
実装としては古来からあるMIDIをベースにしたFirmataというプロトコルを使っている。
現行最新のArduino LeonardやUNOとdiecimila/duemillanove等の違いは、USBシリアル通信変換機能が新しいArduinoのAVRマイコンには内蔵されているけど古いArduinoでは外部のFTDI等のチップを使っている事で、つまりPCから使う時にドライバが違う。
ドライバの差はserialport npmがどうにかしてくれるので良いが、node内でのシリアルポート関連のイベントのタイミングが違うので個別に処理をわける必要がある。
ちなみに同様の処理をRubyの方のarduino_firmata gemでも実装している。
古いArduinoと新しいArduinoを見分ける方法
USBのデバイス名で見分ける事もできるけど、それだと無数にあるArduinoクローンに対応できない。古いArduinoを見つけたいだけなのでデバイスファイル名で判定した。
Debian/Ubuntu/Raspberry pi(raspbian)など
- /dev/ttyACM0 -> Leonard, Micro, UNO
- /dev/ttyUSB0 -> Decimilla, Duemillanove
Mac OSX
- /dev/cu.usbmodem1234 -> Leonard, Micro, UNO
- /dev/cu.usbserial-A1234 -> Decimilla, Duemillanove
古いArduinoでやらなければならない処理
古いArduinoだとシリアルポートが開いてから実際に通信が可能になるまで2〜5秒程度待たなければならない。この時間は一定ではない。これはFTDIチップとそのドライバのせいだと思う。また、nodeやRubyからREPORT_VERSIONをリクエストして、返答がArduinoボードから返ってきた後に、IOの初期化命令を送るのをだいたい3秒ぐらい待たなければならない。
新しいArduinoだとノータイムでIOの初期化命令を送って良い。
こっちはCPUの性能だと思う。REPORT_VERSIONが往復しているわけだからArduino側でFirmataのプログラムは起動して通信できているんだろうけど、何かが遅いっぽい。
FirmataにおけるArduinoボード初期化処理の流れ
IOの初期化とは、アナログピン0〜5番は逐次アナログ値を計測して送ってくれとか役割を指示する処理のこと。シリアルポート開く→REPORT_VERSIONをボードに送る→versionが返ってくる→IOを初期化する
という流れだが、古いボードの場合
シリアルポート開く→(2〜5秒待つ)→REPORT_VERSIONをボードに送る→versionが返ってくる→(3秒待つ)→IOを初期化する
という風になる。IO初期化はarduinoから値が返ってこないのでちゃんと初期化できるタイミングまで待ってやる必要がある。
古いArduinoで使われているFTDIのチップは、RubyでもNodeでも、各言語のシリアルポートライブラリが発行するopenイベントより数秒経ってから実際の通信ができるようになるのと、ちょっとよくわからないけど古いAVRマイコンだとFirmata自体が動き始めるまで時間がかかるみたいでversionが返されてからも待たないといけないらしい。
この辺はFirmata内部のコードも全部把握してるけど、特に変な事はしてないみたいなので、マイコンの世代差だと思う。
という処理をしている。
主にこの辺でやってる
https://github.com/shokai/node-arduino-firmata/blob/v0.3.0/src/arduino-firmata.coffee#L58-L95
typo
if a == bのつもりで
if a = bと書くと、常に真になり、文法も間違っていないので普通に実行されてエラーでない。
このtypoに気づくのに1時間以上かかった。
coffee-scriptだったので今後は==はやめてisを使うようにしたい。
if a is bisも==もcoffeeではコンパイルすると===になる。怖いからもう==使いたくない。
ちなみにisntは!==になる。
Rubyでis, isnt
class Object
alias_method :is, :==
alias_method :isnt, :!=
end
puts 1.is 1 # => true
puts 1.is 2 # => false
puts 1.isnt 2 # => true
puts 1 is 1 # = > syntax error
Rubyでは==や!=もObjectに定義されたメソッドなのでis/isntできるかと思ったけど、スペース空けると文法エラーになる
もしくはif文と同じ行で代入してたら警告する機能とかほしい