Arduino Firmata gemにdRubyを用いたプロセス間共有機能を標準で入れようと考えている。
マイコンを複数プロセスで共有して同時に使えると、実にコードが綺麗になってRubyらしくていいと思う。
ここで試している
share an arduino with dRuby · Issue #25 · shokai/arduino_firmata
Arduinoを複数プロセスで共有したい
Lindaで色々やっていると、センサーで色々値を取ったり、モーターを動かしたり、色々やりたくなる。Arduinoを複数のプロセスで共有し、プログラムの密結合を解きアプリケーションごとに別のコードとして分けたい。
ハードウェアをネットワークに接続して複数のプロセスを起動し、色々なサービスを同時に走らせる時に、1つの仮想シリアルポートは1プロセスからしか同時アクセスできない問題がある。(Arduinoはいまだに仮想シリアルポートなのだ)
解決方法は2つで、Arduinoを複数使う。しかしこれは格好悪いし金もかかるし電気も食うから良くない。
もしくは1つのプログラム内でThreadを立てて、1プロセスで複数のアプリケーションを動かす方法があるけどこれも格好悪い。プログラムをアプリケーション毎にバージョン管理できない。
dRuby
dRubyはRubyに標準で付いている分散オブジェクトライブラリであり、複数プロセス間でオブジェクトが共有できてメソッド呼び出しもできる。marshalされたオブジェクトがTCP上で飛んでいくらしい。$stdoutなんかも別プロセスに渡せる。
先日のRubyHirobaでのtoRubyの人たちによるワークショップでちょっと触った瞬間にArduino共有したいと思った。
実験
0.5秒間隔で13番ピンのLEDを点滅させるプログラムと、0.1秒ごとにADコンバータでセンサーを読んで11番ピンにPWM出力するプログラムを1台のArduinoで同時に走らせた。
server.rb
require 'rubygems'
require 'arduino_firmata'
require 'drb/drb'
arduino = ArduinoFirmata.connect
DRb.start_service "druby://localhost:5010", arduino
loop do
arduino.digital_write 13, true
sleep 0.5
arduino.digital_write 13, false
sleep 0.5
end
client.rb
require 'drb/drb'
DRb.start_service
arduino = DRbObject.new_with_uri "druby://localhost:5010"
loop do
ad = arduino.analog_read 0
puts ad
arduino.analog_write 11, ad/4
sleep 0.1
end
すんなりと両方同時に1台のArduinoの上で動いた。というのも当たり前で、firmataはArduinoから逐次送られてくるinput系の値はRuby VM上のArduinoFirmataインスタンスに貯めておくし、write系は即時にシリアル通信で送られてArduinoの出力ピンに反映される。通信は全二重で行われるので複数プロセスが1台のArduinoにfirmataで同時にアクセスしても何も問題はない。
無理なのはlambda渡しでイベント登録してコールバック受ける arduino.on(:analog_read, &block) や arduino.on(:sysex, &block) で、これはしょうがないかもしれない。
常に使う機能では無いし。
問題
開いているポートを探す
普通、RubyでTCPSocketなどを使おうとするとportが開いていなかったら例外が発生するのだが、dRubyでstart_serviceするとそのまま開けてしまう。他のプロセスにもdRubyのメッセージが行ってしまう。
先にTCPSocket開いてそのインスタンスをdRubyで使うとかしたい。
だれがサーバーになるか
普通に考えて最初にArduinoを開いた人がサーバーになるだろうけど、その人が強制終了したら全員死んでしまいます。オプションでもいい気がしてきた
arduino = ArduinoFirmata.connect "デバイス名", :share => trueとかでいいかも