JMRIでトランスポンディング(3) [JMRI]
lr1 = reporters.provideReporter("1") class LrListener(java.beans.PropertyChangeListener): def propertyChange(self, event): value = event.newValue.split(" ") if value[1] == 'enter': self.enter(value[0]) elif value[1] == 'exits': self.exits(value[0]) def enter(self, value): if value in ['アドレス1','アドレス2',・・・]: <処理を記述> def exits(self, value): if value in ['アドレス3','アドレス4',・・・]: <処理を記述> lrlistener = LrListener() lr1.addPropertyChangeListener(lrlistener)1行目で定義したreporterの出力からzone進入時にdef enter()が、外れた時にdef exits()が呼ばれ、それぞれif value以下の[]内に記述したアドレスと検知したアドレスが一致した時に処理が行われます。
特定のアドレスを検知した時にポイントを切り換える例です。
lr1 = reporters.provideReporter("1") lt1 = turnouts.provideTurnout("1") class LrListener(java.beans.PropertyChangeListener): def propertyChange(self, event): value = event.newValue.split(" ") if value[1] == 'enter': self.enter(value[0]) elif value[1] == 'exits': self.exits(value[0]) def enter(self, value): if value in ['64']: lt1.setCommandedState(THROWN) elif value in ['65', '301']: lt1.setCommandedState(CLOSED) def exits(self, value): pass lrlistener = LrListener() lr1.addPropertyChangeListener(lrlistener)2行目で定義したアドレス1のポイントに対して処理を行います。
この例ではアドレス64の車両がzoneに進入した際にポイントがTHROWNに、アドレス65と301の車両が進入した際にCLOSEDに切り替わります。また車両がzoneを抜ける際には何も処理を行わないのでdef exits()には「pass」と記述します(何も書かないとエラーになるため)。
JMRIでトランスポンディング(2) [JMRI]
アドレスの取得にはセンサーの時と同様にListenerを使いました。まずトランスポンダの反応時に出力されるevent.newValueの値をScript Outputに出力して調べてみます。
lr1 = reporters.provideReporter("1") class LrListener(java.beans.PropertyChangeListener): def propertyChange(self, event): print event.newValue lrlistener = LrListener() lr1.addPropertyChangeListener(lrlistener)スクリプトを実行します。
1行目がzone進入時、2行目が外れた時の出力です。Reporter Iconの表示と同じ内容が出力されているようです。
この値を使って処理を記述することで、車両によってポイントの開通方向を切り換えたり、特定の車両のみ減速→停車させたりと色々なことができそうです。
JMRIでトランスポンディング [JMRI]
まずLoconet Trafficでトランスポンディングが機能していることを確認します。問題なければ下のようなメッセージが表示されます。
一行目の「Transponder present in 〜」がzone進入時、三行目の「Transponder absent in 〜」が外れた時のメッセージです。
Panelに車両のアドレスを表示します。
トランスポンディングで取得したアドレスは「Reporter Icon」で表示できます。
アイコンの追加は「Editor」で行います。「x」と「y」に表示位置を入れ「Add reporter」に番号を入れてボタンをクリックします(「Panel」を表示した状態で「Editor」が表示されていなければ「Edit」→「Open editor」で表示されます)。
また「Add reporter」に入れる番号はBDL168のBoard addressが「1」の場合、zoneA→「1」 zoneB→「2」…zoneD→「4」という具合になっているようです。
初期状態ではReporter Icon は<blank>を表示しています(この例ではさらにSensor Iconを追加しています)。
車両ががzoneに進入すると「address + enter」に変わりました。
さらにzoneから抜けると「address + exits」と表示されます。
OS XでJMRI [JMRI]
普段メインマシンにはMacを使っているのですが、DCC導入時に購入したMS100ではMac版のJMRIは使えないようなのです。
で、かなり古いWindowsマシンをJMRI用に使っていたのですが、今回やっとRR-CirkitsのLocoBuffer-USBを導入しました。
JMRIは最新の2.1.4はなぜか?ディスクイメージをマウントできなかったので2.0をインストールしました。また、OS Xの場合別途serial communications librariesをインストールする必要があります。
Power Panel と Loconet Traffic で軽く動作確認しました。うまく動いているようです。
スロットル操作を検知する(2) [JMRI]
スロットル定義とリスナーのクラスは一つにまとめることができるようです。
前回のサンプルは下のように書き換えることができます。
import jmri class ThrottleProp(jmri.jmrit.automat.AbstractAutomaton, java.beans.PropertyChangeListener):......(1) def init(self): global throttle throttle = self.getThrottle(301, True) if throttle == None : print "Couldn't assign throttle!" throttle.propertyChange = self.throttleEvent......(2) return def handle(self): return False def throttleEvent(self, event):......(3) print "propertyname:", event.propertyName print "oldvalue:", event.oldValue print "newvalue:", event.newValue ThrottleProp().start()
AbstractAutomatonとPropertyChangeListenerを多重継承した(1)クラスに処理を記述していきます。
initメソッド中のリスナークラスのインスタンス生成とthrottleオブジェクトにアタッチする部分を(2)のように書き換えます。ここでpropertyChange属性に代入したthrottleListenerメソッド(3)にリスナー反応時の処理を記述していきます。
リスナーの処理を他のクラスで使い回さなければ、こちらの方がシンプルで良さげですね。
スロットル操作を検知する [JMRI]
現在スロットルの状態変化を記録する方法を探っています。手始めに今回はスロットルの操作を検知してみました。
import jmri class ThrottleListener(java.beans.PropertyChangeListener): def propertyChange(self, event): ......(1) print "propertyname:", event.propertyName print "oldvalue:", event.oldValue print "newvalue:", event.newValue class LoadThrottle(jmri.jmrit.automat.AbstractAutomaton) : ......(2) def init(self): global throttle......(3) throttle = self.getThrottle(301, True) if throttle == None : print "Couldn't assign throttle!" throttleListener = ThrottleListener() throttle.addPropertyChangeListener(throttleListener) return def handle(self):......(4) return False LoadThrottle().start()......(5)
スロットルの状態が変化するとpropertyname(スピード、方向、ファンクションなど値の種類)、oldvalue(変化前の値)、newvalue(変化後の値)をscript outputに出力するサンプルです。
検知にはセンサーの反応検知に使ったリスナーを利用しました。propertyChangeメソッド
(1)に反応時の処理を記述しています。
throttleオブジェクトの生成にはAbstractAutomatonクラス(2)を利用します。jmriサンプルスクリプトのthrottles.pyを参考にしたので以前の方法(Pythonで電車を動かす)とは少し定義の仕方が異なります。
今回はthottleをグローバル変数として定義(3)しました。そのためLoadThrottleクラスのインスタンスは不要になるので直接実行(5)しています。また今回はthrottleオブジェクトの生成だけが目的なのでhandleメソッド(4)にはreturn Falseのみ記述します。
JMRI2.0&2.1.1 [JMRI]
いつの間にかJMRI2.0&2.1.1がリリースされていましたね。インストールはしてみたもののまだあまり使っていないのであまり変更点を理解していないのですが、お気に入りの変更点をひとつだけ。[Run Script...]で.pyファイルを開くとき、今までアプリケーションを立ち上げる度にjythonフォルダにたどり着くまでとても面倒だったのですが、今回からデフォルトがjythonフォルダになって一気に楽になりました。もっとも今までも設定ファイルか何かをいじって変更できたのかもしれませんが。。。
さて、前々回の新・車両位置計算スクリプトの続きです。
sp値-速度対応表(速度−スロットル値テーブル→VTテーブルと名付けました)を補正するフィードバック機能の追加のために情報を色々と探しまわっているのですがなかなかぴったりとくるものが見つかりません。なのでとりあえずのところ思いついたままトライ&エラーでこの機能に挑戦してみることにしました。
最初に試してみたいのは、車両がエンドレスを1周する間のスロットル値をVTテーブルで換算した速度と時間の積算を記録しておき、実際の距離との差からテーブルを補正する方法です。で、まずはスロットル値を記録するところから手をつけてみようと思っています。
センサーの反応検知にリスナーを使ってみる [JMRI]
以前作ったスクリプトではwaitSensorActiveとwaitSensorInactiveを使ってセンサーの反応を検知していたのですが、リスナーを使った検知方法がようやくわかってきました。waitSensorActiveなどを使った方法ではセンサーが反応するまで処理が止まってしまうため何かと不便なのです。。。
リスナーは車両がセンサーに差し掛かる、センサーから外れるなどの変化(イベント)に反応して実行されます。
まず、JMRI付属の「ListenerExample.py」を参考にセンサーの反応をScript Outputに出力するようにしてみます。
1: import jarray 2: import jmri 3: class SnListener(java.beans.PropertyChangeListener): 4: def propertyChange(self, event): 5: if event.newValue == 2: 6: print "active" 7: else: 8: print "inactive" 9: sn1 = sensors.provideSensor("1") 10: snlistener = SnListener() 11: sn1.addPropertyChangeListener(snlistener)
スクリプトの説明
3〜8行目のclass SnListenerにイベント発生時の処理を記述します(実際は4行目以降のdef propertyChangeの内容が実行されます)。
5行目以降ではevent.newValueにイベントの状態が入っているので、条件分岐でactive、inactiveを出力します。ちなみにセンサーの場合車両がさしかかっている状態で2、車両が外れたときには4が入っています。
9行目ではアドレス1のセンサーをsn1と定義しています。
10行目でSnListenerクラスのインスタンスを生成します。クラスとインスタンスの関係は私も上手く説明できるほど理解できていないのですが、友人の話では「設計図」と「その設計図から作られた製品」の関係のようなものであるようです。
11行目でsn1にリスナーをアタッチします。
ちなみに、外部からこのセンサーの状態を問い合わせるにはsn1.getState()でevent.newValueの値が返ってきます。
新・車両位置計算スクリプト(1) [JMRI]
前回作った車両位置計算スクリプトですが、実際に使ってみると思いのほか誤差が大きく(しかも走らせている間に誤差量が変わってくる!)、また将来的に自動制御と手動制御を混在させることを考えると0.05刻みの対応リストでは厳しいこともあり、もう一度考え直してみることにしました。
また以前DCモーターの特性について詳しい方に話を聞く機会があり、簡単な関数で初期値を決めておき、そこからフィードバックして正確な値に補正していくのが良いのでは?とアドバイスをいただきました。
まず、前回の対応リストをグラフにしてみました。
このグラフからかなり大雑把に、とりあえず初期値の関数を速度(mm/s) = 250 x sp - 25(赤色の直線)とします。
この関数から新しく0.01刻みの対応リストを作り直しますが、フィードバックの仕組みを追加するためにスクリプトもかなり作り直す必要がありそうです。
車両位置計算スクリプト [JMRI]
ずいぶん間が空いてしまいましたが...以前測定したスピード値−速度の対応リストを使って、現在の車両位置を計算するスクリプトを作りました。
このスクリプトの概略は
(1)getThrottleで得たアドレスに対して
(2)1/10秒毎にスピード値と進行方向を得て
(3)それを元に対応リストから速度を計算してこの間に進んだ距離を算出し
(4)それを積算して車両の位置を変数locationに入れる
といった感じです。
locationCalc.py
1: import jarray 2: import jmri 3: import speedList 4: class locationCal(jmri.jmrit.automat.AbstractAutomaton): 5: def init(self): 6: self.throttle = self.getThrottle(1234,True) 7: self.location = 0 8: return 9: def handle(self): 10: self.location += self.calcDistance() 11: self.waitMsec(100) 12: return 1 13: def getLocoStatus(self): 14: self.sp = round(self.throttle.getSpeedSetting(),2) 15: self.is = self.throttle.getIsForward() 16: return self.sp,self.is 17: def calcDistance(self): 18: distance = speedList.splist[self.getLocoStatus()] * .1 19: return distance 20: c = locationCal() 21: c.setName("Location calculate script") 22: c.start()
3行目: 後述するspeedList.pyをインポートして、このスクリプト内で使えるようにします。
5〜8行目: initはスクリプト実行時の最初に一度だけ実行されるメソッドです。throttleという変数にアドレス1234番を割り当てて、locationという変数に0を代入します。
9〜12行目: このスクリプトのメインルーティン。0.1秒ごとにメソッドcalcDisance(17〜19行目)を呼び出し、返ってきた値を車両位置を表す変数locationに加えます。return 1とすることでメソッドhandleはループします。
13〜16行目: メソッドinitで取得したthrottle(アドレス1234のデコーダ)の状態を得るメソッド。14行目でスピード値を1/100まで丸めたものを、15行目で進行方向を取得して返します。
17〜19行目: 車両の走行距離を計算するメソッド。18行目でgetLocoStatusを呼び出して、戻ってきた車両の状態(sp、is)に対応するspListの値を1/10(リストの速度が秒速なので)にして戻します。
20〜22行目: お決まりのインスタンス化〜Script Nameのセット〜実行です。
次にスピード値−速度の対応リストspeedList.pyをつくります。
今回はスピード値と進行方向という2つの値に対応する速度を取り出すため「辞書」とよばれるものを使いました。(speedList.py)
それでは、実行してみます。
まず、以前作ったスクリプトを起動して車両を走らせた状態で今回のスクリプトを実行してみました。
"Script Entry"に左の文を入れて"Execute"をクリックして、車両位置(location)の状態を出力してみます。
きちんと計算されているようです。
これで一応車両の位置が把握できるようになりました。ここまでくれば位置情報を利用して自動制御してみたり、他の車両情報を読み込んで続行運転に挑戦したりと色々な可能性が現実的になってきました。