0

Node.jsのEventEmitterをRubyに移植した

ものすごい必要だったので作った。


rubygemsに登録しておいたので

gem install event_emitter
で使える。

ドキュメントはここ
Ruby EventEmitter

バグ、機能要望などは
githubのissuetwitterかメールでお願いします


EventEmitterとは


Node.jsのコアライブラリ。lib/events.jsにある。
nvmでnodeをインストールすると ~/.nvm/src/node-v0.8.14/lib/events.js とかにある。

どんなオブジェクトにでも一発でonとemitという関数を追加できて、
obj.on(‘イベント名’, コールバック)でコールバック登録して
obj.emit(‘イベント名’, 引数)でイベントを発火させる。

nodeの全てのライブラリがイベント駆動するためによく使ってる機能。
addEventListenerとかよりも使いやすい。


ruby実装


onとemitをクラス宣言時にmix-inしたり、既存のクラスや特定のインスタンスに後から追加したりできる。

require "rubygems"
require "event_emitter"

class User
include EventEmitter ## mix-in
attr_accessor :name
end

user = User.new
user.name = "shokai"
user.on :go do |data|
puts "#{name} #{data[:place]}に行く"
end

user.emit :go, {:place => "かずすけ"}
# => "shokai かずすけに行く"
userインスタンスに登録したイベントはuserインスタンスの中で実行されるので、”name”だけでuser.nameの中身の”shokai”が参照できている。



onのかわりにonceで登録すると、一度だけ実行されるイベントを登録できる。
user.once :eat do |data|
puts "#{name} -> #{data}"
end

user.emit :eat, "BEEF" # => "shokai -> BEEF"
user.emit :eat, "Ramen" # => 実行されない


ごく普通のインスタンスにEventEmitter.applyすると、on/emitが追加される。特異メソッド。
EventEmitter.apply インスタンス名


既存のクラスにapplyするとクラスが拡張される
EventEmitter.apply クラス名


イベント駆動じゃないクラスとかも強引にイベント駆動にできそうですね。


参考にした


JSだと全部オブジェクトなのでシンプルなんだけど、Rubyのクラスやインスタンスやら全部にon/emit追加できるようにするのちょっと理解が大変だった。
このへんの黒魔術を参考にした。

0

jQuery.editable plugin

のgithub pagesを作ってみた

jQuery.editable


30分ぐらいで作れるので楽しい。

けっこう増えてきた http://shokai.github.com

0

プログラムを一切書かずに、ネットワーク越しにArduinoを操作する

sshdが起動しているサーバーにArduino Firmata on Rubyをインストールして、firmataをインストールしたArduinoを刺すだけでok


Arduinoの準備

Arduino IDEで [File] -> [Examples] -> [Firmata] -> [StandardFirmata] を書き込む


サーバー側の準備

ArduinoをUSBポートに刺す。


rubyのarduino_firmata gemをインストールする。
sudo gem install arduino_firmata
arduino_firmata --help
arduino_firmata gemをインストールすると、arduino_firmataという実行コマンドが使えるようになる。

arduino_firmata analog_read 1
これだけでanalog 1の値が読める。

arduino_firmata servo_write 11, 135
11ピンに接続したサーボモータが135度に回転させる。
コマンドからarduinoの基本的な機能は全部使える。


手元のMacからサーバーのArduinoを動かす

sshコマンドプロキシでセンサー読める
ssh user@example.com arduino_firmata analog_read 0
sshは、「ssh 接続先 コマンド」でサーバーにコマンドを実行させて結果を得てすぐ切断する事ができる。
arduino_firmataコマンドと組み合わせれば、全くプログラム書かずにコマンド1行で家のサーバーに刺さってるArduinoのCdSで明るさが取得したり、サーボモーターを動かしたりできる。


その他の例

例えばtweetさせたり
echo "明るさ `arduino_firmata analog_read 0`" | tw --pipe
Twを使ってる




Ruby以外の言語からでもセンサーを読むぐらいならできるし、cronに組み込んだりもできるし、そこそこ便利なのでは

sinatra/streamingでcomet (long polling)する はコメントを受け付けていません。

sinatra/streamingでcomet (long polling)する

良い感じのgem sinatra-cometioを作ったのでこっちを使うと良いと思います




チャットを作ってみた。
http://shokai-comet-chat.herokuapp.com/
Chrome/Safari/Firefox/Android/iPod touchで動いた。


sinatra/streaming

sinatra-contribにsinatra/streamingというのがあった。eventmachineを使っているサーバー(thinなど)で動くらしい。herokuでも動いた。

Sinatra::Streaming (part of Sinatra::Contrib)


本気で使うには厳しいしnode.jsを使えばいいと思うけど、Rubyでちょっとしたツールを作るのには便利そう。
一応同時に1000接続までは耐えた。ただ、Cometだとクライアント側とサーバー側両方で1分ぐらいでタイムアウトさせて接続しなおしたりするわけだから、タイミングが悪かったり接続が残ったりする事があると思うので少なめに見積もったほうがいいと思う。
実装は楽だし、ポートを1つしか使わないのでイントラ内で使うようなちょっとしたツールにリッチなUI付けたい時とかに便利そう。
arduino_firmataをwebからゴリゴリ触れるとか。



getやpostの中でstreamブロックを宣言すると、任意のタイミングでレスポンスを返せる。IOっぽく扱える。

getするとpostでデータが入るまで待たされる簡単な例
https://gist.github.com/3989831
#!/usr/bin/env ruby
require 'rubygems'
require 'sinatra'
require 'thin'
require 'sinatra/streaming'

@@comet = Hash.new

post '/comet/:channel' do
channel = params['channel']
data = params[:data]
@@comet[channel] = data
data
end

get '/comet/:channel' do
channel = params['channel']
stream do |s|
data = nil
loop do
break if (data = @@comet.delete channel)
sleep 1
end
s.write data
s.flush
end
end

getすると待たされる
% curl http://localhost:4567/comet/test

postするとgetの方に”hello”がでる
% curl -d 'data=hello' http://localhost:4567/comet/test


chat


チャット
http://shokai-comet-chat.herokuapp.com/


ソースコード https://github.com/shokai/sinatra-comet-chat


/chatにgetするとlong pollingで待たされるので
https://github.com/shokai/sinatra-comet-chat/blob/master/controllers/comet.rb


ajaxしてレスポンスを待つ。受信したらすぐajaxしなおす。
https://github.com/shokai/sinatra-comet-chat/blob/master/public/js/chat.js

このchatには同時に1000接続までなら動いた。1200ぐらい接続したらthinごと死亡した。


FirefoxとChromeは、同じマシンで2つウィンドウを開いてチャットすると交互にしか受信できない。

Safariは問題ない。Chromeはシークレットモードだと両方受信できるらしい。

0

部屋の温度が低い時に警告するようにした

最近寒いんだけど、明らかに寒いというほどでもない。
夜中に3時間ぐらい座って作業しているとじわじわと冷えていて次第に手足が動かなくなり、キーボードが打てなくなってようやく寒さに気づく。
つまり、集中しているとじわじわとした寒さに気づかない。

計測してみるとだいたい室温19度で数時間経つと冷えきるっぽいので、20度未満の時に「お体に障りますよ」と警告するようにした。
20度以上でも、「現在の気温 21度です」とか読み上げるようになっている。
これを30分おきにcronで動かしているので最近はもう凍えないようになった。ヤバくなる前にスープとか作って飲む。


まず、気温とか明るさをtweetするようにしたで作ったプログラムをarduino_firmataで簡単にした。
https://github.com/shokai/tweet_env
これがJSONでtweetする。


次に、tweetをチェックして温度が20度以下だったら「お体に障りますよ」と警告するプログラムを作った
https://github.com/shokai/okaradani-sawarimasuyo


git clone git@github.com:shokai/okaradani-sawarimasuyo.git
cd okaradani-sawarimasuyo
gem install bundler
bundle install
ruby okaradani-sawarimasuyo.rb

crontabに登録、毎時20と50分にしゃべる
20,50 * * * * cd $HOME/src/ruby/okaradani-sawarimasuyo && ruby okaradani-sawarimasuyo.rb > /dev/null 2>&1

Macのsaykanaを使ってるけど、他にもいろいろあるので別の声にしたければどうぞ → Mac/Linuxに日本語を喋らせる