2台の合流を順番に通す
合流区間の使用状態を共有し、もう1台を待たせます。
- 必要な本体
- 2台ちょうど。
- 線路・準備
- 内外の周回を分岐でつなぎ、2台は同じ向き。両入口の手前に白→紫。合流レールの内蔵色で退出を検出。
- 終了方法
- Enterで購読解除・各台の停止・共有状態の終了・切断。
- 保存する場所
matrain-python-async/examples/teamwork/single_merge_protection.py- 実行する場所
matrain-python-asyncフォルダー内。.venvのPythonを使います。
Mac:.venv/bin/python examples/teamwork/single_merge_protection.pyWindows:.venv\Scripts\python.exe examples\teamwork\single_merge_protection.py
通常終了の処理を原例のまま掲載しています。例外やCtrl+Cでの停止が保証されないため、異常時は本体のボタンを短く押して止めます。切断だけを停止とみなさないでください。
コード全文
teamwork/single_merge_protection.py
# Matrain publication modification: licensing/source notice added.
# Executable behavior is unchanged except for the adaptations documented below.
# Source: https://github.com/intelino-code/intelino-trainlib-async-py
# Source commit: 298e1fd4091e069180c30f338b4c285b13accbd0
# This software is made available by Innokind, Inc. DBA Intelino, under the
# terms of the Intelino Public License Agreement, Version 1.0 located at
# https://intelino.com/intelino-public-license. BY INSTALLING, DOWNLOADING,
# ACCESSING, USING OR DISTRIBUTING ANY OF THE SOFTWARE, YOU AGREE TO THE TERMS OF
# SUCH LICENSE AGREEMENT.
"""Teamwork example: Merge protection to avoid collisions (for two trains).
Track requirements:
- track with a split track (e.g. small and big loop), both trains driving
in the same direction
- snaps:
- white-magenta: identifies the entry to the critical section
Notes (corner cases):
- Since both entries are identified the same way, the second train will always
need to wait until the first one leaves the critical section, even if they
are both coming from the same side.
- When the critical section is freed, all waiting trains are released. That
means this example code works only when there is exactly one train waiting.
"""
import asyncio
import aioconsole
from enum import Enum
from rx import operators as ops
from rx.core.typing import Disposable
from rx.subject import BehaviorSubject
from typing import List, NamedTuple
from intelino.trainlib_async import (
TrainScanner,
Train,
)
from intelino.trainlib_async.messages import (
TrainMsgEventSnapCommandDetected,
)
from intelino.trainlib_async.enums import SnapColorValue as Color
from intelino.trainlib_async.helpers import AsyncObserver
#
# Data types
#
SectionState = Enum("SectionState", "FREE OCCUPIED_BY")
class SectionInfo(NamedTuple):
state: SectionState
train_id: int
class SharedState:
def __init__(self):
self.exit_event = asyncio.Event()
# critical section info stream
self.critical_section = BehaviorSubject(SectionInfo(SectionState.FREE, -1))
def dispose(self):
self.critical_section.on_completed()
self.critical_section.dispose()
class LocalState:
def __init__(self):
# waiting on the entry to the critical section
self.waiting = False
#
# Functions
#
async def waiter(event: asyncio.Event):
"""Let the train program run until the 'enter' key is pressed."""
await aioconsole.ainput("Press <Enter> to exit at any time...\n")
event.set()
async def merge_protection(train: Train, index: int, shared_state: SharedState):
"""Two trains prevent collisions on a single merge."""
state = LocalState()
async def milestone(msg: TrainMsgEventSnapCommandDetected):
if msg.colors == (Color.WHITE, Color.MAGENTA, Color.BLACK, Color.BLACK):
# critical section entry
if shared_state.critical_section.value.state == SectionState.FREE:
# when free, let others know we are entering it
shared_state.critical_section.on_next(
SectionInfo(SectionState.OCCUPIED_BY, index)
)
else:
# when not free, wait until it is freed
state.waiting = True
await train.stop_driving()
elif msg.colors == (Color.CYAN, Color.BLACK, Color.BLACK, Color.BLACK):
# since we are protecting a single merge track, we can use
# its built-in snaps to detect when are we leaving the critical
# section
if shared_state.critical_section.value == (SectionState.OCCUPIED_BY, index):
# only if it was occupied by us, free it
shared_state.critical_section.on_next(
SectionInfo(SectionState.FREE, index)
)
async def section_freed(info: SectionInfo):
if state.waiting:
shared_state.critical_section.on_next(
SectionInfo(SectionState.OCCUPIED_BY, index)
)
await train.drive_at_speed(40)
state.waiting = False
# create a list of subscriptions
subscriptions: List[Disposable] = []
sub = train.notifications.pipe(
# we are interested only in snap events, so we filter the stream
ops.filter(lambda msg: isinstance(msg, TrainMsgEventSnapCommandDetected)),
).subscribe(AsyncObserver(milestone))
subscriptions.append(sub)
sub = shared_state.critical_section.pipe(
ops.filter(lambda section: section.state == SectionState.FREE),
).subscribe(AsyncObserver(section_freed))
subscriptions.append(sub)
# clear all custom snap commands stored on the train (just to be sure)
await train.clear_custom_snap_commands()
await train.drive_at_speed(40)
# wait for the exit event to happen
await shared_state.exit_event.wait()
# cleanup
for subscription in subscriptions:
subscription.dispose()
await train.stop_driving()
async def main():
# connect to the first 2 trains discovered
trains = await TrainScanner().get_trains(count=2)
shared_state = SharedState()
# run our train program with all our trains
await asyncio.gather(
waiter(shared_state.exit_event),
*(
merge_protection(train, idx, shared_state)
for idx, train in enumerate(trains)
),
)
# after the program finished, clean up and disconnect from the trains
shared_state.dispose()
for train in trains:
await train.disconnect()
if __name__ == "__main__":
asyncio.run(main())
冒頭に配布条件と出典のコメントを追加しています。表示している全文と、個別保存・ZIP内のこのファイルは同じです。ライセンス・出典を読む
