リスナーを使ったスクリプトー完成? [python]
しばらく更新をサボっておりましたが、
先週末に作成中のスクリプトの修正を行い、何とか動くようになりましたので、ご報告申し上げます。
スクリプト中の
self.throttle.speedSetting = self.sp
で列車の速度を変えるのですが、どうも空振りすることがあることが判明しました。
このときJMRIのスロットルは、100%の速度になっているのですが、実際には列車にこのコマンドが通らず、列車が動き出さないという事例が極々稀に発生しました。
100%の時はまだましなのですが、赤現示で0%(=ストップ)がスルーされると大事故になります。
実際に試運転時に2,3回起こってしまいました。
理由は全く分かりません。全くのお手上げ状態。
多くのLogixなどが走っているせいでしょうか。
そこで、苦肉の策ですが、
self.spo = self.throttle.getSpeedSetting()
self.throttle.speedSetting = 0.95*self.sp+0.05*self.spo
self.throttle.speedSetting = self.sp
変化分の95%と100%を2段階で入れることにしました。どっちかは通るだろうと。
何となく場当たり的な対策ですが、これにより30分くらいの運転でしたが、コマンドがスルーされることはなくなりました。
完全に安全な自動運転に出来ないところが、どうにも弱いです。
綺麗なスクリプトをと思ってリスナーに挑戦してみましたが、結局はなんだか後手後手のものが出来てしまいました。
リスナーの削除 [python]
リスナーのスクリプトをテストしていて困ったことに、設定したリスナーは自分で消さない限り、スクリプトを実行するたびにどんどんと蓄積されていきます。
kill出来ないので。
スクリプトを実行し、不具合など訂正、再度実行すると前のスクリプトの動作も残ってしまっていて、多重に動作します。
そのたびにアプリを起動し直すのは手間ですので、センサーを取り除くスクリプトを追加しました。
リスナーを追加したスクリプトに加えておかないと上手く動作しないようです。
sensors.provideSensor(i).addPropertyChangeListener(d)
addを
sensors.provideSensor(i).removePropertyChangeListener(d)
removeにかえて、登録したのと同じforループを回します。
これをIS26センサーのリスナーにしておきます。
class delListener(java.beans.PropertyChangeListener):
def propertyChange(self, event):
if event.newValue == 2:
sensors.provideSensor("IS26").setState(4)
for i in singoList:
signals.getSignalHead(i).removePropertyChangeListener(b)
for i in memoriList:
memories.provideMemory(i).removePropertyChangeListener(a)
for i in sensList:
sensors.provideSensor(i).removePropertyChangeListener(d)
sensors.provideSensor("IS26").removePropertyChangeListener(e)
print "remove all listener!"
e = delListener()
sensors.provideSensor("IS26").addPropertyChangeListener(e)
リスナーの使い方をご紹介してきました。
これで各パーツは出来上がりましたので、これらをマージして実際の制御に使ってみます。
と言いますのも、実は動作するかはバーチャルモードでprintの出力で見ていただけなんです。
ある程度動く状態まで持っていかないと、列車が制御不能になると怖いですから。。。
明日からは、怖々の実車でのテストです。
信号機、センサーのリスナー [python]
スクリプトにトライされる方で、私のようにプログラミングに不慣れなものはJMRIのスクリプトサンプルを見ての解読になると思いますが、例が少なく情報を求めてネット上をさまよい歩きましたので、どなたかの参考になればと思っています。
今回は信号機のリスナーで、現示が変わったときに動作するリスナーです。
class sListener(java.beans.PropertyChangeListener):
def propertyChange(self, event):
basho = singoList.index(event.source.systemName)
train = memories.getMemory(memoriList[basho]).getValue()
if train != "": #(1) そこに列車がいるときのみ以下の動作をする。
genji = signals.getSignalHead(singoList[basho]).getAppearanceName()
print "signal", train, basho, genji
c.cont(train, basho, genji)
#以下、リスナーの登録
singoList = ['IH:SE8C:"LT273";"LT274"', 'IH:SE8C:"LT275";"LT276"', 'IH:SE8C:"LT287";"LT288"', 'IH:SE8C:"LT285";"LT286"', 'IH:SE8C:"LT283";"LT284"', 'IH43', 'IH:SE8C:"LT261";"LT262"', 'IH:SE8C:"LT263";"LT264"', 'IH:SE8C:"LT301";"LT302"', 'IH:SE8C:"LT303";"LT304"', 'IH45', 'IH:SE8C:"LT265";"LT266"', 'IH:SE8C:"LT267";"LT268"', 'IH:SE8C:"LT305";"LT306"', 'IH:SE8C:"LT307";"LT308"', 'IH46', 'IH:SE8C:"LT257";"LT258"', 'IH:SE8C:"LT259";"LT260"', 'IH:SE8C:"LT277";"LT278"', 'IH:SE8C:"LT279";"LT280"', 'IH:SE8C:"LT281";"LT282"', 'IH44', 'IH:SE8C:"LT269";"LT270"', 'IH:SE8C:"LT271";"LT272"']
b = sListener()
for i in singoList:
signals.getSignalHead(i).addPropertyChangeListener(b)
信号現示が変化したらその場所を信号機リストから探し出し、場所を特定、
そこの列車IDをメモリー値から読み出します。
(1)で列車がいるときのみ、信号現示を読み出し、c.contの列車を制御する関数をコールします。
分かってしまえば特段難しくはありません。
同様にセンサーのリストです。
センサーリストはIS25は場内または閉塞区間のセンサーです。これらのセンサーは今のところなんの機能も持たせてないのですが、
緊急停止みたいなことをやってみようかと思っています。現在は設定していませんので、ifで飛ばしてます。
センサーの値はactiveのときは2、inactiveのときは4が入っていました。(スクリプトを書いて調べました。)
センサーがactiveのときは現示を赤に、それ以外のときは、参照すべき信号機を見るようにして、c.contの列車を制御する関数をコールします。
class sensListener(java.beans.PropertyChangeListener):
def propertyChange(self, event):
if (event.source.systemName != "IS25"):#イベントが起こったセンサー名がIS25以外の時
basho = sensList.index(event.source.systemName)
train = memories.getMemory(memoriList[basho]).getValue()
if (event.newValue == 2):
genji = "Red"
elif (event.newValue == 4):
genji = signals.getSignalHead(singoList[basho]).getAppearanceName()
print "sensor", train, basho, genji
c.cont(train, basho, genji)
sensList = ['IS25', 'IS25', 'IS133', 'IS134', 'IS25', 'IS25', 'IS25', 'IS25', 'IS132', 'IS131', 'IS25', 'IS25', 'IS25', 'IS130', 'IS129', 'IS25', 'IS25', 'IS25', 'IS136', 'IS135', 'IS25', 'IS25', 'IS25', 'IS25']
d = sensListener()
for i in sensList:
sensors.provideSensor(i).addPropertyChangeListener(d)
たぶん同じようにポイントのリスナーも作れると思います。
メモリーのリスナー [python]
class mListener(java.beans.PropertyChangeListener):
def propertyChange(self, event):
if event.newValue != "": #(1)
basho = memoriList.index(event.source.systemName)
train = event.newValue
genji = signals.getSignalHead(singoList[basho]).getAppearanceName()
print "memory", train, basho, genji
c.cont(train, basho, genji)
memoriList = ['IM:AUTO:0005', 'IM:AUTO:0004', 'IM:AUTO:0020', 'IM:AUTO:0019', 'IM:AUTO:0018', 'IM:AUTO:0014', 'IM:AUTO:0013', 'IM:AUTO:0012', 'IM:AUTO:0021', 'IM:AUTO:0022', 'IM:AUTO:0011', 'IM:AUTO:0010', 'IM:AUTO:0009', 'IM:AUTO:0023', 'IM:AUTO:0024', 'IM:AUTO:0001', 'IM:AUTO:0002', 'IM:AUTO:0003', 'IM:AUTO:0015', 'IM:AUTO:0016', 'IM:AUTO:0017', 'IM:AUTO:0008', 'IM:AUTO:0007', 'IM:AUTO:0006']
a = mListener()
for i in memoriList:
memories.provideMemory(i).addPropertyChangeListener(a)
自動運転スクリプトの構想 [python]
自動運転のスクリプトをいじっています。
自動で速度を制御しながら、場合によっては手動でもできる、という具合に改良中です。
ControlTrainというクラスの定義の部分だけお示しします。
import jarray
import jmri
class ControlTrain(jmri.jmrit.automat.AbstractAutomaton) :
def __init__(self, train, basho, genji):
print "OK"
def cont(self, train, basho, genji):
self.tr = int(train)
self.throttle = self.getThrottle(self.tr, False)
self.sp = self.throttle.getSpeedSetting()
self.syudo = self.throttle.getF4() #F4を読み込む
if self.syudo == 0: #F4がオフの時のみ速度調整をする
if genji == 'Red':
self.sp = 0
elif genji == 'Yellow':
self.sp = halfList[basho]
elif genji == 'Green':
self.sp = 1
else:
self.sp = 0
self.throttle.speedSetting = self.sp
if self.syudo == 1 and self.sp > 0.8:#手動でスピードが80%以上になったら手動を解除
self.throttle.setF4(0)
print self.tr, self.sp
train = ""
basho = ""
genji = ""
c = ControlTrain(train, basho, genji)
こんかいはstart()で回し続けるのではなく、リスナーからcontをコールするようにしてます。
最終行で
c = ControlTrain(train, basho, genji)
とすることで、インスタンスが出来ますので、(理解が正しいか怪しいですが。。。)
あとはリスナーで現示が動いたときに
c.cont(train, basho, genji)
などと、列車のID(train)列車の場所(basho)信号現示(genji)の3つを引数としてコールしてやれば速度を変えてくれます。
(この文はリスナーの動作のところに書いておきます)
bashoは今は使ってませんが、将来場所ごとに動作を変えたり出来るように考えています。
#例えば出発信号機と閉塞信号機で動作を変えるなど。
自動と手動の切り換えは、スロットルのファンクション4(F4)のオンオフでやります。
self.syudo = self.throttle.getF4()
でF4の状態をself.syudoに格納、これが0、ファンクションがオフの時にはスピードを信号現示に合わせて変えます。
つまり自動運転です。
これが1、オンのときはスピード調節はしません。つまり手動運転となります。
いつまでも手動だと、忘れて事故を起こすので、
手動でスロットル速度を80%以上にすると、一旦手動を解除します。
黄現示などで速度が低下した場合に、ファンクション4をオンにすると手動に変更できます。
閉塞信号機で赤現示でもファンクション4を押して侵入できます。本物っぽい?
まだ、このスロットル制御のクラスだけしか出来てませんので、リスナーからコールして上手く動くかは分かりませんけど、構想だけはなんとか出来つつあります。
リスナーの一括登録 [python]
memoriList = ['IM:AUTO:0005', 'IM:AUTO:0004', 'IM:AUTO:0020', 'IM:AUTO:0019', 'IM:AUTO:0018', 'IM:AUTO:0014', 'IM:AUTO:0013', 'IM:AUTO:0012', 'IM:AUTO:0021', 'IM:AUTO:0022', 'IM:AUTO:0011', 'IM:AUTO:0010', 'IM:AUTO:0009', 'IM:AUTO:0023', 'IM:AUTO:0024', 'IM:AUTO:0001', 'IM:AUTO:0002', 'IM:AUTO:0003', 'IM:AUTO:0015', 'IM:AUTO:0016', 'IM:AUTO:0017', 'IM:AUTO:0008', 'IM:AUTO:0007', 'IM:AUTO:0006']
a = mListener()
for i in memoriList:
memories.provideMemory(i).addPropertyChangeListener(a)
読み出すためのクラスは
class mListener(java.beans.PropertyChangeListener):
def propertyChange(self, event):
if event.newValue != "":
print event.newValue
どれに入ったかは
event.source.systemNameで取り出せます。
print event.source.systemName, "が", event.oldValue, "から", event.newValue, "に変わりました。"
のように使えます。
event.newValueには制御すべき列車のIDがはいってますから、これを使ってスロットル値を変えます。
リスナーの勉強を [python]
http://passingtrainslayout.blog.so-net.ne.jp/2013-05-11
自動運転と手動を併用できるようにやってみようと思い立ちました。出来る目算はないのですが。。。
まずはループを回して列車を見つけてましたが、
http://passingtrainslayout.blog.so-net.ne.jp/2013-05-07
変化を検知して動作するリスナーを使ってみようかと。おっさんには辛い勉強です。。。
先ずはリスナーの勉強からです。
始めてスクリプトを書いたときよりは少々理解が進んでいますので、当時はさっぱり分からなかったサンプルも理解できるようになってました。
import jarray
import jmri
class MyListener(java.beans.PropertyChangeListener):
def propertyChange(self, event):
if event.newValue == "":
print "out"
else:
print event.newValue
m = memories.provideMemory("IM:AUTO:0005")
a = MyListener()
m.addPropertyChangeListener(a)
IM:AUTO:0005のメモリー値(その区間にいる列車ID)に変化があったときに
そのIDを表示する、または、区間を抜けた場合はoutと表示するスクリプトです。
def 以下のところに、動作を書いていきます。信号現示を読み取り、スロットルを制御すれば。。。
でも、これを全区間に設定していく?のかなぁ。
あまりエレガントではないような。。。。
さてどうなりますやら。
スロットルのスティール [python]
つまり自分でレバーを操作して列車を列車を運転しているときに、PC側が勝手に前方の赤現示で減速停止してくれません。
_________self.throttle = self.getThrottle(int(trainList[j]), False)
_________self.throttle.speedSetting = self.sp
のような形で各車両のDCCデコーダーにスピードを指令するわけですが、
デコーダーを制御する権利を複数のコントローラーに持たせることが出来ないのです。
制御権を横取りすることをDCCではスティールと言っています。
PCで持っている制御権はコントローラーからスティール出来ますが、
逆にPCからはうまくスティール出来ません。
「うまく」と書いたのは、コマンドは通るのですが、相手からリリースさせることが出来ないために、コントローラーからの命令が入るとそちらも通ってしまい、結果的に強制的な停止とはならないことを意味します。
PCからスピード0を命令しても、コントローラーでスピード100を入れてしまうと、100になってしまうのです。
ですので、人間が運転しているものの安全を確保することは出来ません。
なんか、PCに任せっきりにしなければならないということで、実は当初の目的とは違ったものが出来上がっています。
もう一点、スクリプトを走らせる前の注意点ですが、
getThrottleコマンドの前に、PC側で一度バーチャルスロットルを起動し、PCで列車を制御できる状態にしておかなければならないようです。
getThrottleコマンドがうまく通らないと、スクリプトはそこで止まってしまいます。
赤枠で囲んだウィンドウがJMRIのバーチャルスロットルで、制御したい列車のこれを全て開いておく必要があります。(必要はないのかもしれませんが、こうしておくと上手く動きます。)
手作りなので、使い勝手に癖(流儀)がかなりあり、初めての人が簡単にということにはなりませんね。
黄現示のスピードの個別設定 [python]
入線する場内信号機の黄現示で駅に侵入し、出発信号機の赤現示で停止することになります。
場内の区間をセットしているギャップ(これはDCCの特性上、導電性フログのポイントを使用するときにギャップを切る必要があります)から、停止位置までの距離は各駅で同じではありませんので、上手くホームに停止させるのは調整が難しいことが分かりました。
(最悪はオーバーランで、開通していないポイントに逆方向から突っ込むとショートします。)
そこで、まず、基本となる車両を選び、DCCのCV値を決めます。
Decelerationを大きくすると列車はゆっくりと止まります。ここでは31です。
(以前にもご紹介しましたが、KATOから提供されるマニュアルでは最大値31ですが、JMRIではそれ以上の値も設定できます。)
また、黄現示から減速していきますので、スピードテーブルの傾きも影響してきます。
これらをまず固定し、黄現示から駅に侵入させてみますと、どうも感覚的にゆっくりとホームに入ってくる感じです。
ですので、黄現示のときの速度を0.5(最高速度の半分)ではなく、0.7くらいにしてみました。
そうすると、おおむね良いことが分かってきました。しかしこれでは各場内で場内ギャップからホームまでの位置が一定ではないので、調製しきれません。そこで、例によって、各ブロックでの黄現示のときの速度をリストとして与えることにします。
halfList = [0.6, 0.6, 0.6, 0.6, 0.6, 0.71, 0.6, 0.6, 0.6, 0.6, 0.75, 0.6, 0.6, 0.6, 0.6, 0.73, 0.6, 0.6, 0.6, 0.6, 0.6, 0.71, 0.6, 0.6]
こんな感じです。
閉塞信号機や出発信号機のところは、一律0.6で、場内のところだけ、0.71、0.75など微妙に調製できるようにしました。
スピード値の代入のところで、
_________elif self.iro == 'Yellow':
____________self.sp = halfList[i]
とやってやれば、リストの該当する数値を使って減速できるようにしました。
ここまで、1つの代表車両で設定をしておき、あとは各車両間の個体差をDCCのCV値を変えながら調製していきます。
各列車のDecelerationとスピードテーブルをいじります。
Decelerationを大きくすると調整が難しくなりますので、50を超えてくるような場合は
スピードテーブルで最高速度はそのままに、70%くらいのところを上に凸に膨らませてやると、
侵入速度が上がりますので、停止距離が伸びます。
貨物列車では大きなDecelerationにしておくと、実感的になりますね。
このような調子で、全列車のCV値を設定していきました。
あと、1カ所だけ、場内のギャップからホームまで距離が著しく長いところがあります。
ここの調整を何とかしなければなりません。
信号現示で列車を制御するスクリプトー完成ー [python]
全くエレガントではない、べたべたのスクリプトですが、全容は以下のようになりました。
import jarray
import jmri
class stopHitachi(jmri.jmrit.automat.AbstractAutomaton) :
___def init(self):
______self.c = []
______for self.b in singoList:
_________self.c.append(signals.getSignalHead(self.b))
______print "Go!"
______return
___def handle(self):
______self.waitChange(jarray.array(self.c,jmri.NamedBean))
______self.waitMsec(100)
______for j in range(len(trainList)):
_________for i in range(24):
____________self.ichi = memories.getMemory(memoriList[i]).getValue()
____________if self.ichi == trainList[j]:
_______________if (i == 0) and (memories.getMemory(memoriList[23]).getValue() == trainList[j]):
__________________i = 23
_______________self.singo = singoList[i]
_______________self.iro = signals.getSignalHead(self.singo).getAppearanceName()
_______________break
_________if self.singo == '':
____________self.iro = 'Red'
_________if self.iro == 'Green':
____________self.sp = 1
_________elif self.iro == 'Yellow':
____________self.sp = 0.5
_________elif self.iro == 'Red':
____________self.sp = 0
_________if sensors.getSensor(sensList[i]).state == 2:
____________self.sp = 0
_________print trainList[j], i, self.singo, self.iro, sensors.getSensor(sensList[i]).state, self.sp
_________self.throttle = self.getThrottle(int(trainList[j]), False)
_________self.throttle.speedSetting = self.sp
_________self.singo = ''
______return 1
trainList = ['7', '11', '31', '21', '22']
memoriList = ['IM:AUTO:0005', 'IM:AUTO:0004', 'IM:AUTO:0020', 'IM:AUTO:0019', 'IM:AUTO:0018', 'IM:AUTO:0014', 'IM:AUTO:0013', 'IM:AUTO:0012', 'IM:AUTO:0021', 'IM:AUTO:0022', 'IM:AUTO:0011', 'IM:AUTO:0010', 'IM:AUTO:0009', 'IM:AUTO:0023', 'IM:AUTO:0024', 'IM:AUTO:0001', 'IM:AUTO:0002', 'IM:AUTO:0003', 'IM:AUTO:0015', 'IM:AUTO:0016', 'IM:AUTO:0017', 'IM:AUTO:0008', 'IM:AUTO:0007', 'IM:AUTO:0006']
singoList = ['IH:SE8C:"LT273";"LT274"', 'IH:SE8C:"LT275";"LT276"', 'IH:SE8C:"LT287";"LT288"', 'IH:SE8C:"LT285";"LT286"', 'IH:SE8C:"LT283";"LT284"', 'IH43', 'IH:SE8C:"LT261";"LT262"', 'IH:SE8C:"LT263";"LT264"', 'IH:SE8C:"LT301";"LT302"', 'IH:SE8C:"LT303";"LT304"', 'IH45', 'IH:SE8C:"LT265";"LT266"', 'IH:SE8C:"LT267";"LT268"', 'IH:SE8C:"LT305";"LT306"', 'IH:SE8C:"LT307";"LT308"', 'IH46', 'IH:SE8C:"LT257";"LT258"', 'IH:SE8C:"LT259";"LT260"', 'IH:SE8C:"LT277";"LT278"', 'IH:SE8C:"LT279";"LT280"', 'IH:SE8C:"LT281";"LT282"', 'IH44', 'IH:SE8C:"LT269";"LT270"', 'IH:SE8C:"LT271";"LT272"', 'IH47']
sensList = ['IS25', 'IS25', 'IS133', 'IS134', 'IS25', 'IS25', 'IS25', 'IS25', 'IS132', 'IS131', 'IS25', 'IS25', 'IS25', 'IS130', 'IS129', 'IS25', 'IS25', 'IS25', 'IS136', 'IS135', 'IS25', 'IS25', 'IS25', 'IS25']
a = stopHitachi()
a.setName("stopHitachi")
a.start()
これをRun Scriptで実行してやれば、Thread Monitorに”stopHitachi”という名前でプロセスが走ります。(問題があったらkillできます。)
5つの列車が、前方の信号機が青現示でフルスピード、黄現示で50%、赤現示で停止します。
指令は出発信号機に対して出発許可を出してやるだけです。2区間前に先行列車がいれば黄現示でゆっくりと発車していきます。
場内信号機が全部赤現示なら、2区間前で減速、さらに駅手前で停止、場内が空けば自動的に侵入していきます。
各列車には過減速のdelayが設定されていますので、いきなり100%や0%スロットルを与えても、ゆっくりと加速、減速していきます。
これで5つの列車をひとりで運転できるようになりました。(眺められるようになりました。)
おおむね完成しましたが、細かい問題が出てきました。
次回から、ご紹介していきます。