2台で共有区間を譲り合う
共有区間の使用状態を通知し、対向する2台の進入を制御します。
- 必要な本体
- 2台。
- 線路・準備
- 内外の2周回と2つの共有区間。外側は時計回り、内側は反時計回り。黄・紫で各区間の入口と出口を示します。
- 終了方法
- Enterで購読解除・各台の停止・共有状態の終了・切断。
- 保存する場所
matrain-python-async/examples/teamwork/collision_avoidance_1.py- 実行する場所
matrain-python-asyncフォルダー内。.venvのPythonを使います。
Mac:.venv/bin/python examples/teamwork/collision_avoidance_1.pyWindows:.venv\Scripts\python.exe examples\teamwork\collision_avoidance_1.py
通常終了の処理を原例のまま掲載しています。例外やCtrl+Cでの停止が保証されないため、異常時は本体のボタンを短く押して止めます。切断だけを停止とみなさないでください。
コード全文
teamwork/collision_avoidance_1.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: Collision avoidance (v1) for two trains.
Track requirements:
- Double loop (inner + outer) with two "shared" sections
- outer loop is clockwise
- inner loop is counter-clockwise
- Yellow and magenta snaps are used to identify the shared sections.
Notes (corner cases):
- When the critical section is freed, all waiting trains are allowed to enter.
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 Subject
from typing import List, NamedTuple
from intelino.trainlib_async import (
TrainScanner,
Train,
)
from intelino.trainlib_async.messages import (
TrainMsgEventFrontColorChanged,
TrainMsgMovement,
)
from intelino.trainlib_async.enums import SnapColorValue as Color, SteeringDecision
from intelino.trainlib_async.helpers import AsyncObserver
#
# Data types
#
SectionState = Enum("SectionState", "FREE OCCUPIED_BY")
class SectionInfo(NamedTuple):
state: SectionState
train_id: int
class Message(NamedTuple):
"""The message format to be passed in the shared stream."""
section_color: Color
section_info: SectionInfo
class SharedState:
"""Class for a global state shared between all train programs and also
the shared logic.
"""
def __init__(self):
self.exit_event = asyncio.Event()
# critical section update stream
self.stream = Subject() # type: Subject[Message]
# critical section state (updated by the SharedLogic from the stream)
self.critical_section = {
Color.YELLOW: SectionInfo(SectionState.FREE, -1),
Color.MAGENTA: SectionInfo(SectionState.FREE, -1),
}
def dispose(self):
self.stream.on_completed()
self.stream.dispose()
class LocalState:
def __init__(self):
# waiting on the entry to the critical section
self.waiting = {
Color.YELLOW: False,
Color.MAGENTA: False,
}
#
# Shared logic
#
class SharedLogic:
"""The programs' own logic can execute actions based on the shared state
and the shared data stream. It can manipulate the shared state
but communication with trains is strictly through message passing.
"""
def __init__(self, shared_state: SharedState):
self.shared_state = shared_state
async def run(self):
# Any "active" logic (e.g. user inputs, signalling to trains etc.)
# would be run through this function.
subscription = self.shared_state.stream.subscribe(
AsyncObserver(self.update_crical_section_state)
)
await self.waiter()
subscription.dispose()
async def update_crical_section_state(self, m: Message):
self.shared_state.critical_section[m.section_color] = m.section_info
async def waiter(self):
"""Let the train program run until the 'enter' key is pressed."""
await aioconsole.ainput("Press <Enter> to exit at any time...\n")
self.shared_state.exit_event.set()
#
# Train program (for all trains)
#
async def collision_avoidance(train: Train, index: int, shared_state: SharedState):
"""Two trains prevent head-on collision on two shared sections."""
state = LocalState()
async def enter_section(color: Color):
shared_state.stream.on_next(
Message(color, SectionInfo(SectionState.OCCUPIED_BY, index))
)
state.waiting[color] = False
async def leave_section(color: Color):
shared_state.stream.on_next(
Message(color, SectionInfo(SectionState.FREE, index))
)
async def wait_to_enter_section(color: Color):
state.waiting[color] = True
await train.stop_driving()
async def milestone(msg: TrainMsgEventFrontColorChanged):
if msg.color in shared_state.critical_section:
# the color identified a critical section entry/exit
critical_section = shared_state.critical_section[msg.color]
if critical_section.state == SectionState.FREE:
# when free, let others know we are entering it
await enter_section(msg.color)
elif critical_section == (SectionState.OCCUPIED_BY, index):
# when it was occupied by us, we are leaving it
await leave_section(msg.color)
else:
# all other cases require us to wait
await wait_to_enter_section(msg.color)
async def section_freed(message: Message):
if state.waiting[message.section_color]:
await enter_section(message.section_color)
await train.drive_at_speed(40)
async def update_steering_decision(msg: TrainMsgMovement):
# since we have 2 trains and they go in opposite directions,
# one on the inner loop and one on the outer loop, they use the
# same decision side on splits
await train.set_next_split_steering_decision(SteeringDecision.LEFT)
# create a list of subscriptions
subscriptions: List[Disposable] = []
# start the movement stream
movement_stream = await train.movement_notification_stream(True)
sub = movement_stream.pipe(
# we are interested only if the next decision is not set
ops.filter(lambda msg: msg.next_split_decision == SteeringDecision.NONE),
).subscribe(AsyncObserver(update_steering_decision))
subscriptions.append(sub)
sub = train.notifications.pipe(
# we are interested only in color changes
ops.filter(lambda msg: isinstance(msg, TrainMsgEventFrontColorChanged)),
).subscribe(AsyncObserver(milestone))
subscriptions.append(sub)
sub = shared_state.stream.pipe(
ops.filter(lambda msg: msg.section_info.state == SectionState.FREE),
).subscribe(AsyncObserver(section_freed))
subscriptions.append(sub)
await train.drive_at_speed(35)
# 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()
shared_logic = SharedLogic(shared_state)
# run our train program with all our trains
await asyncio.gather(
shared_logic.run(),
*(
collision_avoidance(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内のこのファイルは同じです。ライセンス・出典を読む
