路面電車の模型を作る〜下回り編〜(1) [車両製作]
久々に車両工作を始めました。ネタはかなり以前から放置していたタツヤ模型製の広島電鉄900型です。
この状態で放置してました。
まずは未着手だった下回りから…
MODOMOの東京都電7000型(更新後)をベースにプラ材で車体サイズに合わせ拡幅しました。また台車枠は形態の似ている東急デハ80のものと交換しています。
デコーダはDigitraxのDZ143を組み込み。スペースを捻出するためにダイキャストを一部(シャフト上部分)切り取りました。
折角のDCCなのでライトの点灯化を目論んで定電流ダイオードを組み込んでみました。チップLEDを使ってファンクション出力のF0/F1でヘッド/テールライトを、そして残りのF2/F3でブレーキランプを点灯させてみようと思っています。
ヘッドライト用LEDのステーをプラ棒で立て、LEDを仕込むための切り欠きを設けました。
この車両サイズでは定電流ダイオードもかなりの大きさに感じます。チップ抵抗にしておけば良かったなんて考えつつ、このまま進めることにします。
オブジェクト指向? [JMRI]
オブジェクト指向というのは…
「独立した様々な機能を持つ部品を組み合わせていくことでシステムを構築する」
といった感じのことらしいです。
概念的にはまだいまいち理解できていないのですが、例えば今進めている車両位置表示スクリプトの場合、センサーの状態を把握するスクリプト、車両の位置を計算するスクリプト、位置情報を表示するスクリプトというように独立したスクリプトを組み合わせて、それぞれが情報をやりとりするようにしてやることで車両の位置を表示させる、といった感じでしょうか。
この考え方を取り入れれば、機能ごとにスクリプトを作っていくことができるので複雑なものでも割と楽に作れそうです。また機能の追加拡張も簡単にできそうな気がします。
まずはスクリプト同士で情報をやり取りする方法から探ってみたいと思います。
車両速度計測スクリプト(3) [JMRI]
今回は各スピード値での速度を自動計測するために対象車両のスピードを変えながら速度を計測する機能を追加します。
まずスピード値の計測ステップを決めます。以前作った自動制御スクリプトでは0.05刻みの制御で十分自然に見えたので今回もこれに合わせることにします。また、車両構造のせいか前後進でスピードが結構変わるのでそれぞれ計測するよう組みました。
1: import jarray 2: import jmri 3: import time 4: class LocoSpeedCal(jmri.jmrit.automat.AbstractAutomaton): 5: def init(self): 6: self.sensor1 = sensors.provideSensor("1") 7: self.throttle = self.getThrottle(1234, True) 8: self.spList = [] 9: self.sp = 0. 10: self.spStep = .05 11: self.spMin = .1 12: return 13: def handle(self): 14: self.sp = (self.getCount() + 1.) * self.spStep 15: if self.sp >= self.spMin: 16: self.throttle.speedSetting = self.sp 17: self.throttle.setIsForward(True) 18: self.spList.append(self.measurement(self.sp)) 19: self.throttle.setIsForward(False) 20: self.spList.append(self.measurement(self.sp)) 21: else: 22: self.spList.extend([0,0]) 23: if self.sp >= 1.: 24: endFlag = 0 25: print self.spList 26: self.throttle.speedSetting = 0. 27: self.throttle.release() 28: else: 29: endFlag = 1 30: return endFlag 31: def measurement(self,sp): 32: count = 0 33: totalTime = 0. 34: t1 = 0. 35: while count <= 10: 36: self.waitSensorActive(self.sensor1) 37: t2 = time.clock() 38: if count >= 1: 39: lapTime = t2 - t1 40: totalTime += lapTime 41: self.waitMsec(int((4.5/self.sp-3.)*1000)) 42: self.waitSensorInactive(self.sensor1) 43: t1 = t2 44: count += 1 45: mmPerSec = 1159.2 / (totalTime / 10) 46: return mmPerSec 47: a = LocoSpeedCal() 48: a.setName("Speed Calcurate Script") 49: a.start()スクリプトがずいぶん長くなってきたので前回のhandle()部分を別メソッドにまとめました。
8行目: 測定結果を入れるリストspListを初期化
10行目: 計測ステップspStepを設定。
11行目: 最低測定スピードspMinを設定。極低速時の測定はかなりの時間が掛かるため、spMin未満は速度0として計測を省略することにしました。
14行目: getSpeed()を利用してhandle()を1回実行するごとにspの値をspStep分増やす
15〜20行目: spがspMin以上のとき、測定車両のスピード値をspに、進行方向を前進にセットしてmeasurement()(実際に速度を測定するメソッド)の戻り値をspListに追記し、その後進行方向を後進にセットして同様に処理
21〜22行目: spがspMin未満のとき、(測定しないで)spListに0を2回追記
23〜27行目: sp = 1.0まで測定が終わった時の終了処理。endFlagを0にセット、spListの値をScript Outputに出力、測定車両のスピードを0にした後アドレスを解放します。
28〜29行目: sp = 1.0未満のときendFlagを1にセット。
30行目: endFlagが1のときhandle()を繰り返し実行し、0であれば終了。
このスクリプトを利用して無事スピード値−速度の対応リストができあがりましたが、spMinを設定したにもかかわらず測定には結局5時間も掛かってしまいました。
さて、これでいよいよ車両位置表示スクリプトの制作に進めます。今回取得したリストが果たしてどれくらいの精度で出来上がっているのか、結果がわかるのは楽しみでもあり恐ろしくもあり・・・。
車両速度計測スクリプト(2) [JMRI]
エンドレス周回にかかる時間を正確に計るために前回のスクリプトに先頭車輪以外の反応をキャンセルする仕組みを追加して、車両速度を計算させてみます。
前回の実験で車両の速度によってセンサーの反応回数が変わってくることがわかったため、とりあえず今回は速度に応じた通過時間の概算を出して、その間の反応をキャンセルすることにしました。このキャンセル時間は
車両が完全にセンサーを外れる時間 < キャンセル時間 < 車両がエンドレスを一周する時間
を満たす必要があります。前回のスクリプトで通過時間とエンドレス一周にかかる時間を計り、キャンセル時間を(4.5/sp-3)秒と設定しました。
また誤差を減らすためにエンドレス10周分の時間を計り、平均周回時間から速度を計算するようにしました。
1: import jarray 2: import jmri 3: import time 4: class LocoSpeedCal(jmri.jmrit.automat.AbstractAutomaton): 5: def init(self): 6: self.sensor1 = sensors.provideSensor("1") 7: self.throttle = self.getThrottle(1234, True) 8: self.sp = 1. 9: self.throttle.speedSetting = self.sp 10: return 11: def handle(self): 12: count = 0 13: totalTime = 0. 14: t1 = 0. 15: while count <= 10: 16: self.waitSensorActive(self.sensor1) 17: t2 = time.clock() 18: if count >= 1: 19: lapTime = t2 - t1 20: totalTime += lapTime 21: self.waitMsec(int((4.5/self.sp-3.)*1000)) 22: self.waitSensorInactive(self.sensor1) 23: t1 = t2 24: count += 1 25: mmPerSec = 1159.2 / (totalTime / 10) 26: print mmPerSec 27: return 28: a = LocoSpeedCal() 29: a.setName("Speed Calcurate Script") 30: a.start()
太字が前回からの変更部分です。
12行目: 15〜24行目のループをカウントする変数countを初期化
13行目: 1周ごとの時間を積算する変数totalTimeを初期化
14行目: (init()から移動)
15行目: 10周分の時間を計るために24行目までループ
18行目: (getCount関数を使ったループ回数のカウントから変更)
19行目: エンドレス1周にかかる時間(t2 - t1)をlapTimeに代入
20行目: totalTimeにlapTimeを加算
21行目: 先頭車輪以外のセンサー反応をキャンセルするための待ち時間。waitMsecの値は整数のミリ秒なので、1000倍してint()で整数化しました。
24行目: カウントアップ
25行目: totalTimeとエンドレスの距離から速度を計算してmmPerSecに代入。距離はS140×2=280mm、C140×360°分=879.2mmで計算しました。
26行目: mmPerSecをScript OutPutに出力
次は自動的に車両のスピードを変えながら各スピード値に対して速度を計測して、結果をリストにまとめる機能を追加します。これでやっと計測スクリプトの完成です。
車両速度計測スクリプト(1) [JMRI]
Pythonでレイアウト上での車両位置を計算するスクリプトを作り始めました。
今のところセンサーの位置を基準にして、車両通過時からの速度と時間を積算していけば上手くいくかな?と考えています。
で、このスクリプトを実現するにはPython上でのスピード値(speedSettingでセットする値)と実際の車両速度との対応リストが必要になりますが、手計算するのは大変なのでリストを自動生成するスクリプトを作ります。
最初にセンサーがONになる間隔を計測してScript Outputに出力するスクリプトを作りました。
1: import jarray 2: import jmri 3: import time 4: class LocoSpeedCal(jmri.jmrit.automat.AbstractAutomaton): 5: def init(self): 6: self.sensor1 = sensors.provideSensor("1") 7: self.throttle = self.getThrottle(1234, True) 8: self.sp = 1. 9: self.t1 = 0. 10: self.throttle.speedSetting = self.sp 11: return 12: def handle(self): 13: self.waitSensorActive(self.sensor1) 14: t2 = time.clock() 15: if self.getCount() >= 1: 16: print t2 - self.t1 17: self.waitSensorInactive(self.sensor1) 18: self.t1 = t2 19: return 1 20: a = LocoSpeedCal() 21: a.setName("Speed Calcurate Script") 22: a.start()
3行目: 時刻を扱うためのtimeモジュールをインポート
6行目: sensor1にアドレス1のセンサーを割り当て
8行目: spにスピード値を代入
13行目: sensor1がONになるのを待つ
14行目: 時間をt2に代入
15行目: getCountが1以上であれば13行目を実行する。getCount()はhandleのループ回数(0からカウント)を返します。
16行目: センサーON反応時(t2)から前回のON反応時(t1)を引いた時間、つまり反応の間隔をScript Outputに出力
17行目: sensor1がOFFになるのを待つ
18行目: 次のループのためにt2をt1に代入
19行目: handleをループ
実行してみます。
sp = 1.0
sp = 0.1
実験には6軸の車両を使っているのでセンサーの反応も6回であることが理想ですが、実際にはセンサーの接点長と車輪間の距離が近いところで2輪分の反応を一度に拾ってしまうことがあるようです。また、低速域では瞬間的なON-OFFも拾ってしまい反応の回数が増えてしまいます。
次回はこのスクリプトに先頭車輪以外の反応をキャンセルする仕組みを追加します。
DS64+TCSセンサーレール [JMRI]
ここしばらくBD4でのピンポイントな位置検知方法を探っていたのですが、なかなか良い結果が得られませんでした。そこで代替となるセンサーを探し始めたところTOMIXのTCSセンサーレールがちょっと良さげだったので試してみました。
このセンサーは電車の車輪がレール間に設けられた接点に接触しレールの電流が接点に流れるのを検知するもので、車両の内部回路が関わらない分より確実に反応するのでは?と考えたのです。
とはいえ、当然のことながらこのセンサーレールはDS64と接続することを前提に作られているわけではなく中の回路がどうなっているかも不明です。なぜ出力が3本あるのかも良くわかりません。というわけでとりあえず分解してみました。
クリーム色の2つの物体はフォトモスリレーでしょうか。接点に流れた電流で駆動するようです。2つのリレーの入力側は+ーが互いに逆ににつながっているので恐らく通常のDC運転では走行方向によってどちらか片方のリレーを駆動し、コネクタの右端子(+)→中央端子(ー)または左端子(+)→中央端子(ー)がONになるのでしょう。出力が3本ある理由がやっとわかりました。
中途半端な電気知識でアレですが、レールに流れているパルスをそのまま回路に流すのもちょっと怖い気がしたので元々ついている抵抗を少しずらしてショットキーバリアダイオードを直列に追加、フォトモスリレーに流れる電流を半波整流してみました。リレーの一つは使えなくなりますがどのみちDCCでは方向検知できないのでこれで十分です。
DS64につないでみました。センサーの+側をDS64の+COM端子に、ー側を入力端子(A1〜S4のいずれか)に接続します。DS64にスイッチをつなぐのと同じ要領です。
電車を走らせてセンサーの反応をLocoNet Monitorで見てみました。
とりあえず先頭車輪から確実にセンサーが反応するようになりました。接点部分の動きもごく軽いため、通過時の走行抵抗もほとんど無くかなり良い感じです。
ただしやはりDCC用に作られているセンサーではないので、このままで問題ないのか少し心配です。しばらく使って様子を見てみようと思っています。
ともあれこれで車両位置検知に目処がつきそうです。
DS64+BD4(3) [JMRI]
現在、Pythonスクリプトによる車両位置検知の第一歩としてセンサーを使った車両速度検出スクリプトを組んでいるのですが、BD4の反応がイマイチ不安定なようでうまく行きません。
車両のスピードが出ているときは良いのですが、ある一定の速度以下で検知区間に差し掛かかる(ギャップをまたいでいる)間にセンサーが頻繁にON-OFFを繰り返してしまいます。
またセンサーがONになる位置も車両によってまちまちで、最初の車輪が検知区間に入った瞬間にONになるものもあれば全ての車輪が入って初めてONになるものもあり、さらには車両の向きによっても反応位置がかわってくるという具合になかなか一筋縄ではいかないようです。
車両が完全にセンサー区間に入ってしまえば動作は安定するので通常の使い方では全く問題ないと思うのですが、車輪がギャップをまたぐ瞬間を検知するようなピンポイントな使い方にはちょいと工夫が要りそうです。
DS64+BD4(2) [JMRI]
JMRIにセンサーを認識させてみます。
マニュアルにはセンサーをPCで使うためにはDS64のBoard IDをセットする必要があるという感じの記述があったのですが、最初全く意味がわかりませんでした。そこでとりあえずLocoNet Monitorでセンサー反応時のメッセージを見てヒントを得ようと思ったのですが・・・
・・・全く反応がありません。オプション設定を手当たり次第にいじったものの状態は変わらず。途方に暮れつつマニュアルを読み直してみるとDS64からセンサーの反応メッセージを送る場合、LocoNetを接続する必要がある旨の記述を見つけてしまいました。配線はできるだけシンプルにしたかったのですが、必要とあれば仕方ないのでLocoNetポートにケーブルを接続するとあっさりとそれらしいメッセージ(赤線部)が流れてきました。
「contact 1」ということはアドレスは1で良いのでしょうか。
とりあえずPanelにアドレス1でセンサーを配置して反応を確かめてみました。
Panelの設定方法については gionseijinさんがご自身のBlog で詳細に解説されています。
車両がセンサーにかかるとアイコンが変化しました。
センサーから外れるときもきちんと反応しました。BD4のON・OFF共にメッセージが送られるようなので、上手くいけばPythonで1つのセンサーから2箇所の位置検知ができそうです。
次にDS64のBoard IDを色々変えてみてアドレスとの関係をLocoNet Monitorで調べてみました。どうやらセンサーのアドレスはDS64のBoard IDに関連づけられているようです。
ID=1のときA1=1,S1=2,A2=3,S2=4...S4=8
ID=2のときA1=9,S1=10...S4=16
:
ID=256のとき、A1=2041,S1=2042... S4=2048
というようにIDが1増えるごとに各入力のアドレスが8づつ増えるようです。つまり
A1のアドレス = Board ID × 8 - 7
S1のアドレス = Board ID × 8 - 6
A2のアドレス = Board ID × 8 - 5
:
A4のアドレス = Board ID × 8 - 1
S4のアドレス = Board ID × 8
という式でアドレスが計算できます。
ここまでやってみて、やっとマニュアルに書いてあることの意味が理解できました。
Board IDの変更はDS64のSTATボタンを長押しして、LEDが早い点滅→遅い点滅になったところで手を離し、LEDが赤⇔緑の点滅になった状態でスロットルのSWモードから任意の番号でTHROWN(またはCLOSED)することで設定できました。
DS64+BD4(1) [JMRI]
DigitraxのDS64とBD4を取り寄せました。これから車両位置検知に取りかかります。
今のところ位置検知はPythonスクリプトで車両の速度から走行距離を計算して位置を把握することを考えています。というのは一般的なセンサーによる位置検知では車両毎に異なる車体長や車両端から車輪(センサー反応位置)までのオフセットに対応するのが難しく、停留所などで先行車両の後ろにぴったりとつけて停車するような路面電車独特の車間距離制御には不向きかな?と考えたからです。
とはいえ、ちょっと実験してみたところスクリプトのみでは車輪のスリップや集電不良による誤差が意外と大きかったため、適宜センサーを設置して誤差を補正することにしました。
BDL168もかなり魅力的(RX4を追加してトランスポンディングなど)だったのですが、この用途にはセンサー数が多すぎていささかオーバースペックに思えたのでDS64+BD4にしました。
早速接続してみました。
DS64には電源とLocoNetケーブルを接続するコネクタがありますが、配線を極力増やしたくなかったためRAIL端子への接続のみで済ませました。
電車を走らせてみました。センサーは正常動作しているようです。でもギャップをまたいでいる時の挙動が少し不安定?BD4の説明書にはセンサー感度を変える方法っぽい記述があったので、今後必要があれば試してみたいと思います。
DS64の設定方法をまだ理解できていないので、追々探ってみようと思います。
路面電車の信号機(2) [JMRI]
信号機とポイントを連動制御するスクリプトを組んで信号機を点灯させてみました。
デコーダアドレスはポイントを制御するDS52を1、信号機を制御するDS44は分岐線向きを51、エンドレス内回り向きを52、外回り向きを53と54に設定しています。
制御自体は一定時間ごとに信号を赤にしてポイントを切り換え、開通方向の信号を黄色(矢印はついていませんが)にする単純なものですが、アドレスが多くスクリプトが煩雑になりそうだったので新たにメソッドというものを使ってみました。
メソッドとは一連の処理をひとかたまりにまとめるもので、handleから呼び出すことで処理を実行することができます。メソッドを使ったことでhandle内に処理を全て羅列するのに比べてずいぶん見易くなりました。また同じ処理を何度も実行する場合も一度の記述で良いのでスクリプトが短くなります。
1: import jarray 2: import jmri 3: class SignalCtrl(jmri.jmrit.automat.AbstractAutomaton): 4: def init(self): #初期設定(変数にアドレスを代入) 5: self.lt = turnouts.provideTurnout("1") 6: self.sg1 = turnouts.provideTurnout("51") 7: self.sg2 = turnouts.provideTurnout("52") 8: self.sg3 = turnouts.provideTurnout("53") 9: self.sg4 = turnouts.provideTurnout("54") 10: return 11: def handle(self): #メインルーチン(各メソッド呼び出し) 12: self.signal_allRed() 13: self.turnout_thrown() 14: self.signal_thrown() 15: self.signal_allRed() 16: self.turnout_closed() 17: self.signal_closed() 18: return 1 19: def signal_thrown(self): #メソッドsignal_thrownを定義 20: self.sg1.setCommandedState(CLOSED) 21: self.sg2.setCommandedState(THROWN) 22: self.sg4.setCommandedState(THROWN) 23: self.waitMsec(20000) 24: return 25: def signal_closed(self): #メソッドsignal_closedを定義 26: self.sg1.setCommandedState(THROWN) 27: self.sg2.setCommandedState(CLOSED) 28: self.sg3.setCommandedState(THROWN) 29: self.sg4.setCommandedState(CLOSED) 30: self.waitMsec(20000) 31: return 32: def signal_allRed(self): #メソッドsignal_allRedを定義 33: self.sg1.setCommandedState(CLOSED) 34: self.sg2.setCommandedState(CLOSED) 35: self.sg3.setCommandedState(CLOSED) 36: self.sg4.setCommandedState(CLOSED) 37: self.waitMsec(10000) 38: return 39: def turnout_thrown(self): #メソッドturnout_thrownを定義 40: self.lt.setCommandedState(THROWN) 41: return 42: def turnout_closed(self): #メソッドturnout_closedを定義 43: self.lt.setCommandedState(CLOSED) 44: return 45: a = SignalCtrl() 46: a.setName("Signal Control Script") 46: a.start()
signal_thrown実行。
signal_closed実行。
signal_allRed実行。自動車用の赤信号はレールの電流をショットキーバリアダイオードで整流して常時点灯させています。