From 336c326556cc36f7fee3a048583ae85c86ce0ed7 Mon Sep 17 00:00:00 2001 From: Arne Boockmeyer Date: Tue, 27 Jun 2023 15:41:03 +0200 Subject: [PATCH 01/16] Add basic monitor support --- interlocking/interlockinginterface.py | 10 +++++++++- test_interlocking.py | 28 +++++++++++++++++++-------- 2 files changed, 29 insertions(+), 9 deletions(-) diff --git a/interlocking/interlockinginterface.py b/interlocking/interlockinginterface.py index f9fc9ac..85dbb49 100644 --- a/interlocking/interlockinginterface.py +++ b/interlocking/interlockinginterface.py @@ -1,6 +1,7 @@ from interlocking.interlockingcontroller import PointController, SignalController, TrackController, TrainDetectionController from interlocking.model import Point, Track, Signal, Route from interlocking.model.helper import SetRouteResult, Settings, InterlockingOperationType +from interlockinglogicmonitor import InterlockingLogicMonitor import asyncio import time import logging @@ -8,11 +9,15 @@ class Interlocking(object): - def __init__(self, infrastructure_providers, settings=Settings()): + def __init__(self, + infrastructure_providers, + settings=Settings(), + interlocking_logic_monitor: InterlockingLogicMonitor = None): if not isinstance(infrastructure_providers, list): infrastructure_providers = [infrastructure_providers] self.infrastructure_providers = infrastructure_providers self.settings = settings + self.interlocking_logic_monitor = interlocking_logic_monitor self.point_controller = PointController(self.infrastructure_providers, self.settings) self.signal_controller = SignalController(self.infrastructure_providers) @@ -139,6 +144,7 @@ async def set_route(self, yaramo_route, train_id: str): await self.reset_route(yaramo_route, train_id) set_route_result.success = False set_route_result.route_formation_time = time.time() - route_formation_time_start + self.interlocking_logic_monitor.monitor_set_route(yaramo_route) return set_route_result def can_route_be_set(self, yaramo_route, train_id: str): @@ -158,6 +164,7 @@ def free_route(self, yaramo_route, train_id: str): route = self.get_route_from_yaramo_route(yaramo_route) self.track_controller.free_route(route, train_id) self.active_routes.remove(route) + self.interlocking_logic_monitor.monitor_free_route(yaramo_route) async def reset_route(self, yaramo_route, train_id: str): route = self.get_route_from_yaramo_route(yaramo_route) @@ -166,6 +173,7 @@ async def reset_route(self, yaramo_route, train_id: str): self.train_detection_controller.reset_track_segments_of_route(route) await self.signal_controller.reset_route(route) self.active_routes.remove(route) + self.interlocking_logic_monitor.monitor_reset_route(yaramo_route) def get_route_from_yaramo_route(self, yaramo_route): for route in self.routes: diff --git a/test_interlocking.py b/test_interlocking.py index 5c1d838..77cea4a 100644 --- a/test_interlocking.py +++ b/test_interlocking.py @@ -3,6 +3,7 @@ from interlocking.interlockinginterface import Interlocking from interlocking.infrastructureprovider import LoggingInfrastructureProvider, RandomWaitInfrastructureProvider from interlocking.model.helper import Settings +from interlockinglogicmonitor import MonitorInfrastructureProvider, InterlockingLogicMonitor, Evaluation, CoverageCriteria import asyncio import logging @@ -27,9 +28,12 @@ async def test_01(): generator = RouteGenerator(topology) generator.generate_routes() - infrastructure_provider = [LoggingInfrastructureProvider(), RandomWaitInfrastructureProvider(allow_fail=False)] + infrastructure_provider = [LoggingInfrastructureProvider(), + #RandomWaitInfrastructureProvider(allow_fail=False), + MonitorInfrastructureProvider(topology)] - interlocking = Interlocking(infrastructure_provider, Settings(max_number_of_points_at_same_time=3)) + monitor = InterlockingLogicMonitor(topology) + interlocking = Interlocking(infrastructure_provider, Settings(max_number_of_points_at_same_time=3), monitor) interlocking.prepare(topology) interlocking.print_state() @@ -39,7 +43,7 @@ async def set_route(_start_signal_name, _end_signal_name, _should_be_able_to_set if _route.start_signal.name == _start_signal_name and _route.end_signal.name == _end_signal_name: logging.debug(f"### Set Route {_start_signal_name} -> {_end_signal_name}") _set_route_result = await interlocking.set_route(_route, _train_id) - # assert (_set_route_result.success == _should_be_able_to_set) + assert (_set_route_result.success == _should_be_able_to_set) interlocking.print_state() return _set_route_result @@ -113,16 +117,24 @@ async def drive_some_route_forwards(): free_route("60BS2", "60BS3", "RB101") interlocking.print_state() - await drive_some_route_forwards() - await interlocking.reset() + #await drive_some_route_forwards() + #evaluation = Evaluation(monitor, infrastructure_provider[-1]) + #logging.info(f"Coverage: {evaluation.get_coverage(CoverageCriteria.ALL)} " + # f"{evaluation.get_coverage(CoverageCriteria.INFRASTRUCTURE_ONLY)} " + # f"{evaluation.get_coverage(CoverageCriteria.ROUTES_ONLY)}") + #await interlocking.reset() await set_route("60ES2", "60AS4", True, "RB101") - await set_route("60ES2", "60AS3", False, "RB101") + evaluation = Evaluation(monitor, infrastructure_provider[-1]) + await set_route("60ES2", "60AS3", False, "RB102") + evaluation = Evaluation(monitor, infrastructure_provider[-1]) await reset_route("60ES2", "60AS4", "RB101") - await set_route("60ES2", "60AS4", True, "RB101") + await set_route("60ES2", "60AS4", True, "RB102") await interlocking.reset() + evaluation = Evaluation(monitor, infrastructure_provider[-1]) + evaluation.print_evaluation() # Get conflicts: for route_uuid_1 in topology.routes: @@ -135,7 +147,7 @@ async def drive_some_route_forwards(): if __name__ == "__main__": - logging.basicConfig(level=logging.INFO) + logging.basicConfig(level=logging.DEBUG) asyncio.run(test_01()) From 1c4b061d95e820299de186f904779fdf038dd590 Mon Sep 17 00:00:00 2001 From: Arne Boockmeyer Date: Tue, 27 Jun 2023 15:52:34 +0200 Subject: [PATCH 02/16] Change example --- test_interlocking.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/test_interlocking.py b/test_interlocking.py index 77cea4a..6fd24f6 100644 --- a/test_interlocking.py +++ b/test_interlocking.py @@ -117,20 +117,21 @@ async def drive_some_route_forwards(): free_route("60BS2", "60BS3", "RB101") interlocking.print_state() - #await drive_some_route_forwards() - #evaluation = Evaluation(monitor, infrastructure_provider[-1]) - #logging.info(f"Coverage: {evaluation.get_coverage(CoverageCriteria.ALL)} " - # f"{evaluation.get_coverage(CoverageCriteria.INFRASTRUCTURE_ONLY)} " - # f"{evaluation.get_coverage(CoverageCriteria.ROUTES_ONLY)}") - #await interlocking.reset() + await drive_some_route_forwards() + evaluation = Evaluation(monitor, infrastructure_provider[-1]) + logging.info(f"Coverage: {evaluation.get_coverage(CoverageCriteria.ALL)} " + f"{evaluation.get_coverage(CoverageCriteria.INFRASTRUCTURE_ONLY)} " + f"{evaluation.get_coverage(CoverageCriteria.ROUTES_ONLY)}") + await interlocking.reset() await set_route("60ES2", "60AS4", True, "RB101") evaluation = Evaluation(monitor, infrastructure_provider[-1]) + evaluation.print_evaluation() await set_route("60ES2", "60AS3", False, "RB102") evaluation = Evaluation(monitor, infrastructure_provider[-1]) - + evaluation.print_evaluation() await reset_route("60ES2", "60AS4", "RB101") - await set_route("60ES2", "60AS4", True, "RB102") + await set_route("60ES2", "60AS3", True, "RB102") await interlocking.reset() evaluation = Evaluation(monitor, infrastructure_provider[-1]) From a9fc9f44a6b3bf65ffdb2bc90b87c7fefcc45c23 Mon Sep 17 00:00:00 2001 From: Arne Boockmeyer Date: Tue, 18 Jul 2023 12:18:05 +0200 Subject: [PATCH 03/16] Allow the monitor to be None --- interlocking/interlockinginterface.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/interlocking/interlockinginterface.py b/interlocking/interlockinginterface.py index efcd78f..852f8b2 100644 --- a/interlocking/interlockinginterface.py +++ b/interlocking/interlockinginterface.py @@ -144,7 +144,8 @@ async def set_route(self, yaramo_route, train_id: str): await self.reset_route(yaramo_route, train_id) set_route_result.success = False set_route_result.route_formation_time = time.time() - route_formation_time_start - self.interlocking_logic_monitor.monitor_set_route(yaramo_route) + if self.interlocking_logic_monitor is not None: + self.interlocking_logic_monitor.monitor_set_route(yaramo_route) return set_route_result def can_route_be_set(self, yaramo_route, train_id: str): @@ -164,7 +165,8 @@ def free_route(self, yaramo_route, train_id: str): route = self.get_route_from_yaramo_route(yaramo_route) self.track_controller.free_route(route, train_id) self.active_routes.remove(route) - self.interlocking_logic_monitor.monitor_free_route(yaramo_route) + if self.interlocking_logic_monitor is not None: + self.interlocking_logic_monitor.monitor_free_route(yaramo_route) async def reset_route(self, yaramo_route, train_id: str): route = self.get_route_from_yaramo_route(yaramo_route) @@ -173,7 +175,8 @@ async def reset_route(self, yaramo_route, train_id: str): self.train_detection_controller.reset_track_segments_of_route(route) await self.signal_controller.reset_route(route) self.active_routes.remove(route) - self.interlocking_logic_monitor.monitor_reset_route(yaramo_route) + if self.interlocking_logic_monitor is not None: + self.interlocking_logic_monitor.monitor_reset_route(yaramo_route) def get_route_from_yaramo_route(self, yaramo_route): for route in self.routes: From 748daf4fbcc1cfc6d8a61ec68fab43e01e269d2a Mon Sep 17 00:00:00 2001 From: Arne Boockmeyer Date: Tue, 18 Jul 2023 13:20:45 +0200 Subject: [PATCH 04/16] Add interlocking logic monitor as dependency --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 28cb455..8927c0c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,6 +10,7 @@ python = "^3.11" yaramo = { git = "https://github.com/simulate-digital-rail/yaramo" } planpro-importer = { git = "https://github.com/simulate-digital-rail/planpro-importer" } railway-route-generator = { git = "https://github.com/simulate-digital-rail/railway-route-generator" } +interlocking-logic-monitor = { git = "https://github.com/simulate-digital-rail/interlocking-logic-monitor" } #yaramo = { git = "https://github.com/bp2022-ap1/yaramo", branch="refactor/replace-connected-nodes-with-edges" } #planpro-importer = { git = "https://github.com/bp2022-ap1/planpro-importer", branch = "refactor/replace-connected-nodes-edges" } #railway-route-generator = { git = "https://github.com/bp2022-ap1/railway-route-generator", branch = "refactor/replace-connected-nodes-edges" } From dd92c0ef0690b2572514be6a1cc6c4ec5c3252ac Mon Sep 17 00:00:00 2001 From: Arne Boockmeyer Date: Wed, 19 Jul 2023 18:24:28 +0200 Subject: [PATCH 05/16] Add tests for exceptions --- test/interlocking_test.py | 66 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/test/interlocking_test.py b/test/interlocking_test.py index 867e362..8b438b0 100644 --- a/test/interlocking_test.py +++ b/test/interlocking_test.py @@ -3,6 +3,7 @@ from interlocking.infrastructureprovider import RandomWaitInfrastructureProvider from yaramo.model import Route as YaramoRoute, Signal as YaramoSignal, Edge as YaramoEdge, Node as YaramoNode import asyncio +import unittest def test_reset(): @@ -211,6 +212,71 @@ def test_failing_signal(): asyncio.run(interlockinghelper.set_route(interlocking, route_bs2_bs3, True, "RB101")) +class TestFreeRouteWithoutSettingRouteBefore(unittest.TestCase): + + def test_free_route_without_setting_route_before(self): + topology = topologyhelper.get_topology_from_planpro_file("./complex-example.ppxml") + interlocking = interlockinghelper.get_interlocking(topology) + + some_route: YaramoRoute = list(topology.routes.values())[0] + with self.assertRaises(Exception) as exception: + interlocking.free_route(some_route, "RB101") + + self.assertEqual(str(exception.exception), f"Route from {some_route.start_signal.name} to " + f"{some_route.end_signal.name} is not set.") + + +class TestFreeRouteWithWrongTrainID(unittest.TestCase): + + def test_free_route_with_wrong_train_id(self): + topology = topologyhelper.get_topology_from_planpro_file("./complex-example.ppxml") + interlocking = interlockinghelper.get_interlocking(topology) + + some_route: YaramoRoute = list(topology.routes.values())[0] + correct_train_id = "RB101" + other_train_id = "OtherTrainID102" + asyncio.run(interlockinghelper.set_route(interlocking, some_route, True, correct_train_id)) + + with self.assertRaises(Exception) as exception: + interlocking.free_route(some_route, other_train_id) + + self.assertEqual(str(exception.exception), f"Wrong Train ID: The route from {some_route.start_signal.name} to " + f"{some_route.end_signal.name} was not set with the train id " + f"{other_train_id}.") + + +class TestResetRouteWithoutSettingRouteBefore(unittest.TestCase): + + def test_reset_route_without_setting_route_before(self): + topology = topologyhelper.get_topology_from_planpro_file("./complex-example.ppxml") + interlocking = interlockinghelper.get_interlocking(topology) + + some_route: YaramoRoute = list(topology.routes.values())[0] + with self.assertRaises(Exception) as exception: + asyncio.run(interlocking.reset_route(some_route, "RB101")) + + self.assertEqual(str(exception.exception), f"Route from {some_route.start_signal.name} to " + f"{some_route.end_signal.name} is not set.") + + +class TestResetRouteWithWrongTrainID(unittest.TestCase): + + def test_reset_route_with_wrong_train_id(self): + topology = topologyhelper.get_topology_from_planpro_file("./complex-example.ppxml") + interlocking = interlockinghelper.get_interlocking(topology) + + some_route: YaramoRoute = list(topology.routes.values())[0] + correct_train_id = "RB101" + other_train_id = "OtherTrainID102" + asyncio.run(interlockinghelper.set_route(interlocking, some_route, True, correct_train_id)) + + with self.assertRaises(Exception) as exception: + asyncio.run(interlocking.reset_route(some_route, other_train_id)) + + self.assertEqual(str(exception.exception), f"Wrong Train ID: The route from {some_route.start_signal.name} to " + f"{some_route.end_signal.name} was not set with the train id " + f"{other_train_id}.") + # if __name__ == "__main__": # logging.basicConfig(level=logging.DEBUG) # test_driving() From 0f409b46a958ff0bfa086450519f6c7632b0f80d Mon Sep 17 00:00:00 2001 From: Arne Boockmeyer Date: Wed, 19 Jul 2023 18:32:42 +0200 Subject: [PATCH 06/16] Restructure Tests --- test/interlocking_test.py | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/test/interlocking_test.py b/test/interlocking_test.py index 8b438b0..c590906 100644 --- a/test/interlocking_test.py +++ b/test/interlocking_test.py @@ -212,7 +212,7 @@ def test_failing_signal(): asyncio.run(interlockinghelper.set_route(interlocking, route_bs2_bs3, True, "RB101")) -class TestFreeRouteWithoutSettingRouteBefore(unittest.TestCase): +class TestFreeRouteExceptions(unittest.TestCase): def test_free_route_without_setting_route_before(self): topology = topologyhelper.get_topology_from_planpro_file("./complex-example.ppxml") @@ -223,10 +223,7 @@ def test_free_route_without_setting_route_before(self): interlocking.free_route(some_route, "RB101") self.assertEqual(str(exception.exception), f"Route from {some_route.start_signal.name} to " - f"{some_route.end_signal.name} is not set.") - - -class TestFreeRouteWithWrongTrainID(unittest.TestCase): + f"{some_route.end_signal.name} was not set.") def test_free_route_with_wrong_train_id(self): topology = topologyhelper.get_topology_from_planpro_file("./complex-example.ppxml") @@ -245,7 +242,7 @@ def test_free_route_with_wrong_train_id(self): f"{other_train_id}.") -class TestResetRouteWithoutSettingRouteBefore(unittest.TestCase): +class TestResetRouteExceptions(unittest.TestCase): def test_reset_route_without_setting_route_before(self): topology = topologyhelper.get_topology_from_planpro_file("./complex-example.ppxml") @@ -256,10 +253,7 @@ def test_reset_route_without_setting_route_before(self): asyncio.run(interlocking.reset_route(some_route, "RB101")) self.assertEqual(str(exception.exception), f"Route from {some_route.start_signal.name} to " - f"{some_route.end_signal.name} is not set.") - - -class TestResetRouteWithWrongTrainID(unittest.TestCase): + f"{some_route.end_signal.name} was not set.") def test_reset_route_with_wrong_train_id(self): topology = topologyhelper.get_topology_from_planpro_file("./complex-example.ppxml") From 1755b5844adbd26f11bd16a068b057964a76c9cc Mon Sep 17 00:00:00 2001 From: Arne Boockmeyer Date: Wed, 19 Jul 2023 18:35:11 +0200 Subject: [PATCH 07/16] Add exceptions for free route and reset route in corner cases --- interlocking/interlockinginterface.py | 23 ++++++++++++++++++++--- interlocking/model/route.py | 1 + 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/interlocking/interlockinginterface.py b/interlocking/interlockinginterface.py index c064689..8c8e691 100644 --- a/interlocking/interlockinginterface.py +++ b/interlocking/interlockinginterface.py @@ -122,7 +122,8 @@ async def set_route(self, yaramo_route, train_id: str): if not self.can_route_be_set(yaramo_route, train_id): set_route_result.success = False return set_route_result - route = self.get_route_from_yaramo_route(yaramo_route) + route: Route = self.get_route_from_yaramo_route(yaramo_route) + route.used_by = train_id self.active_routes.append(route) async with asyncio.TaskGroup() as tg: @@ -155,17 +156,33 @@ def do_two_routes_collide(self, yaramo_route_1, yaramo_route_2): return do_collide def free_route(self, yaramo_route, train_id: str): - route = self.get_route_from_yaramo_route(yaramo_route) + route: Route = self.get_route_from_yaramo_route(yaramo_route) + if route not in self.active_routes: + raise Exception(f"Route from {yaramo_route.start_signal.name} to " + f"{yaramo_route.end_signal.name} was not set.") + if route.used_by != train_id: + raise Exception(f"Wrong Train ID: The route from {yaramo_route.start_signal.name} to " + f"{yaramo_route.end_signal.name} was not set with the train id " + f"{train_id}.") self.track_controller.free_route(route, train_id) self.active_routes.remove(route) + route.used_by = None async def reset_route(self, yaramo_route, train_id: str): - route = self.get_route_from_yaramo_route(yaramo_route) + route: Route = self.get_route_from_yaramo_route(yaramo_route) + if route not in self.active_routes: + raise Exception(f"Route from {yaramo_route.start_signal.name} to " + f"{yaramo_route.end_signal.name} was not set.") + if route.used_by != train_id: + raise Exception(f"Wrong Train ID: The route from {yaramo_route.start_signal.name} to " + f"{yaramo_route.end_signal.name} was not set with the train id " + f"{train_id}.") self.point_controller.reset_route(route, train_id) self.track_controller.reset_route(route, train_id) self.train_detection_controller.reset_track_segments_of_route(route) await self.signal_controller.reset_route(route) self.active_routes.remove(route) + route.used_by = None def get_route_from_yaramo_route(self, yaramo_route): for route in self.routes: diff --git a/interlocking/model/route.py b/interlocking/model/route.py index 4b83f0f..cf51555 100644 --- a/interlocking/model/route.py +++ b/interlocking/model/route.py @@ -12,6 +12,7 @@ def __init__(self, yaramo_route): self.end_signal = None self.tracks = [] self.overlap = None + self.used_by = None def contains_segment(self, segment): for track in self.tracks: From 437c0047b1c64558cb57e1b0a47be3aadc0f0785 Mon Sep 17 00:00:00 2001 From: Arne Boockmeyer Date: Fri, 21 Jul 2023 11:47:05 +0200 Subject: [PATCH 08/16] Add tests for the limiters --- test/helper/__init__.py | 1 + test/helper/interlockinghelper.py | 15 +++- .../trackoperationsinfrastructureprovider.py | 31 ++++++++ test/infrastructureprovider_test.py | 74 +++++++++++++++++++ test/interlocking_test.py | 1 - 5 files changed, 120 insertions(+), 2 deletions(-) create mode 100644 test/helper/trackoperationsinfrastructureprovider.py create mode 100644 test/infrastructureprovider_test.py diff --git a/test/helper/__init__.py b/test/helper/__init__.py index 3121374..f5e1f59 100644 --- a/test/helper/__init__.py +++ b/test/helper/__init__.py @@ -1,2 +1,3 @@ from .topologyhelper import get_topology_from_planpro_file, get_route_by_signal_names from .interlockinghelper import get_interlocking, set_route, test_point, free_route, test_track, test_signal +from .trackoperationsinfrastructureprovider import TrackOperationsInfrastructureProvider diff --git a/test/helper/interlockinghelper.py b/test/helper/interlockinghelper.py index 8b98267..81de0f4 100644 --- a/test/helper/interlockinghelper.py +++ b/test/helper/interlockinghelper.py @@ -1,7 +1,7 @@ from interlocking.interlockinginterface import Interlocking from interlocking.infrastructureprovider import InfrastructureProvider, LoggingInfrastructureProvider from yaramo.model import Route, Topology -from interlocking.model import OccupancyState, TrackSegment, Track, Signal +from interlocking.model import OccupancyState, TrackSegment, Track, Signal, Point def get_interlocking(topology: Topology, infrastructure_provider: list[InfrastructureProvider] = None): @@ -24,6 +24,19 @@ def free_route(interlocking: Interlocking, route: Route, train_id: str): interlocking.free_route(route, train_id) +def get_interlocking_signal_by_name(interlocking: Interlocking, signal_name: str): + for signal_uuid in interlocking.signal_controller.signals: + signal = interlocking.signal_controller.signals[signal_uuid] + if signal.yaramo_signal.name == signal_name: + return signal + + +def get_interlocking_point_by_id(interlocking: Interlocking, point_id: str): + for _point_id in interlocking.point_controller.points: + if _point_id == point_id: + return interlocking.point_controller.points[point_id] + + def test_point(interlocking: Interlocking, point_id: str, train_id: str, orientation: str, state: OccupancyState): point = interlocking.point_controller.points[point_id] assert point.state == state diff --git a/test/helper/trackoperationsinfrastructureprovider.py b/test/helper/trackoperationsinfrastructureprovider.py new file mode 100644 index 0000000..760adb0 --- /dev/null +++ b/test/helper/trackoperationsinfrastructureprovider.py @@ -0,0 +1,31 @@ +from yaramo.model import Signal, Node +from interlocking.infrastructureprovider import InfrastructureProvider + + +class TrackOperationsInfrastructureProvider(InfrastructureProvider): + def __init__(self, **kwargs): + super().__init__(**kwargs) + self.point_operations: set[tuple[Node, str]] = set() + self.signal_operations: set[tuple[Signal, str]] = set() + + async def set_signal_state(self, yaramo_signal: Signal, target_aspect: str): + await super().set_signal_state(yaramo_signal, target_aspect) + self.signal_operations.add((yaramo_signal, target_aspect)) + return True + + async def turn_point(self, yaramo_point: Node, target_orientation: str): + await super().turn_point(yaramo_point, target_orientation) + self.point_operations.add((yaramo_point, target_orientation)) + return True + + def was_signal_set_to_aspect(self, yaramo_signal: Signal, aspect: str): + for signal_operation in self.signal_operations: + if signal_operation[0].name == yaramo_signal.name and signal_operation[1] == aspect: + return True + return False + + def was_point_turned_to(self, yaramo_point: Node, target_orientation: str): + for point_operation in self.point_operations: + if point_operation[0].uuid == yaramo_point.uuid and point_operation[1] == target_orientation: + return True + return False diff --git a/test/infrastructureprovider_test.py b/test/infrastructureprovider_test.py new file mode 100644 index 0000000..5b9fdc3 --- /dev/null +++ b/test/infrastructureprovider_test.py @@ -0,0 +1,74 @@ +from .helper import topologyhelper, interlockinghelper, trackoperationsinfrastructureprovider +import asyncio +import unittest + + +class TestUsingBothLimiters(unittest.TestCase): + + def test_using_both_signal_limiters(self): + with self.assertRaises(Exception) as exception: + ip = trackoperationsinfrastructureprovider.TrackOperationsInfrastructureProvider( + only_apply_for_signals=["99N1"], + apply_for_all_signals_except=["99N2"] + ) + + self.assertEqual(str(exception.exception), f"You can not limit the infrastructure provider with " + f"only_apply_for_signals and apply_for_all_signals_except at " + f"the same time.") + + def test_using_both_point_limiters(self): + with self.assertRaises(Exception) as exception: + ip = trackoperationsinfrastructureprovider.TrackOperationsInfrastructureProvider( + only_apply_for_points=["abcde"], + apply_for_all_points_except=["abcde"] + ) + + self.assertEqual(str(exception.exception), f"You can not limit the infrastructure provider with " + f"only_apply_for_points and apply_for_all_points_except at " + f"the same time.") + + +def test_only_apply_for(): + topology = topologyhelper.get_topology_from_planpro_file("./complex-example.ppxml") + ip = trackoperationsinfrastructureprovider.TrackOperationsInfrastructureProvider(only_apply_for_signals=["60ES1"], + only_apply_for_points=["fd73d"]) + interlocking = interlockinghelper.get_interlocking(topology, infrastructure_provider=[ip]) + route_es1_as1 = topologyhelper.get_route_by_signal_names(topology, "60ES1", "60AS1") + asyncio.run(interlockinghelper.set_route(interlocking, route_es1_as1, True, "RB101")) + route_bs2_bs3 = topologyhelper.get_route_by_signal_names(topology, "60BS2", "60BS3") + asyncio.run(interlockinghelper.set_route(interlocking, route_bs2_bs3, True, "RB101")) + + signal_es1 = interlockinghelper.get_interlocking_signal_by_name(interlocking, "60ES1").yaramo_signal + signal_bs2 = interlockinghelper.get_interlocking_signal_by_name(interlocking, "60BS2").yaramo_signal + point_on_first_route = interlockinghelper.get_interlocking_point_by_id(interlocking, "fd73d").yaramo_node + point_on_second_route = interlockinghelper.get_interlocking_point_by_id(interlocking, "e641b").yaramo_node + assert ip.was_signal_set_to_aspect(signal_es1, "go") + assert not ip.was_signal_set_to_aspect(signal_bs2, "go") + assert ip.was_point_turned_to(point_on_first_route, "right") + assert not ip.was_point_turned_to(point_on_second_route, "right") + + +def test_apply_for_all_except(): + topology = topologyhelper.get_topology_from_planpro_file("./complex-example.ppxml") + ip = trackoperationsinfrastructureprovider.TrackOperationsInfrastructureProvider( + apply_for_all_signals_except=["60ES1"], + apply_for_all_points_except=["fd73d"] + ) + interlocking = interlockinghelper.get_interlocking(topology, infrastructure_provider=[ip]) + route_es1_as1 = topologyhelper.get_route_by_signal_names(topology, "60ES1", "60AS1") + asyncio.run(interlockinghelper.set_route(interlocking, route_es1_as1, True, "RB101")) + route_bs2_bs3 = topologyhelper.get_route_by_signal_names(topology, "60BS2", "60BS3") + asyncio.run(interlockinghelper.set_route(interlocking, route_bs2_bs3, True, "RB101")) + + signal_es1 = interlockinghelper.get_interlocking_signal_by_name(interlocking, "60ES1").yaramo_signal + signal_bs2 = interlockinghelper.get_interlocking_signal_by_name(interlocking, "60BS2").yaramo_signal + point_on_first_route_fd73d = interlockinghelper.get_interlocking_point_by_id(interlocking, "fd73d").yaramo_node + point_on_first_route_fa9ea = interlockinghelper.get_interlocking_point_by_id(interlocking, "fa9ea").yaramo_node + point_on_first_route_21b88 = interlockinghelper.get_interlocking_point_by_id(interlocking, "21b88").yaramo_node + point_on_second_route = interlockinghelper.get_interlocking_point_by_id(interlocking, "e641b").yaramo_node + assert not ip.was_signal_set_to_aspect(signal_es1, "go") + assert ip.was_signal_set_to_aspect(signal_bs2, "go") + assert not ip.was_point_turned_to(point_on_first_route_fd73d, "right") + assert ip.was_point_turned_to(point_on_first_route_fa9ea, "left") + assert ip.was_point_turned_to(point_on_first_route_21b88, "right") + assert ip.was_point_turned_to(point_on_second_route, "right") \ No newline at end of file diff --git a/test/interlocking_test.py b/test/interlocking_test.py index 867e362..8498a74 100644 --- a/test/interlocking_test.py +++ b/test/interlocking_test.py @@ -210,7 +210,6 @@ def test_failing_signal(): route_bs2_bs3 = topologyhelper.get_route_by_signal_names(topology, "60BS2", "60BS3") asyncio.run(interlockinghelper.set_route(interlocking, route_bs2_bs3, True, "RB101")) - # if __name__ == "__main__": # logging.basicConfig(level=logging.DEBUG) # test_driving() From 03b6bc0e45b06c3e1329f7e5c15ebf0dbcb19bb5 Mon Sep 17 00:00:00 2001 From: Arne Boockmeyer Date: Fri, 21 Jul 2023 11:47:58 +0200 Subject: [PATCH 09/16] Add logic for applying the limiters --- .../infrastructureprovider.py | 52 +++++++++++++++++-- .../interlockingcontroller/pointcontroller.py | 8 +-- .../signalcontroller.py | 4 +- 3 files changed, 54 insertions(+), 10 deletions(-) diff --git a/interlocking/infrastructureprovider/infrastructureprovider.py b/interlocking/infrastructureprovider/infrastructureprovider.py index 95ec6ce..062ff72 100644 --- a/interlocking/infrastructureprovider/infrastructureprovider.py +++ b/interlocking/infrastructureprovider/infrastructureprovider.py @@ -1,9 +1,34 @@ from abc import ABC, abstractmethod -import asyncio +from yaramo.model import Node, Signal + class InfrastructureProvider(ABC): - def __init__(self): + def __init__(self, + only_apply_for_signals: list[str] = None, + only_apply_for_points: list[str] = None, + apply_for_all_signals_except: list[str] = None, + apply_for_all_points_except: list[str] = None): + if only_apply_for_signals is None: + only_apply_for_signals = [] + self.only_apply_for_signals = only_apply_for_signals + if only_apply_for_points is None: + only_apply_for_points = [] + self.only_apply_for_points = only_apply_for_points + if apply_for_all_signals_except is None: + apply_for_all_signals_except = [] + self.apply_for_all_signals_except = apply_for_all_signals_except + if apply_for_all_points_except is None: + apply_for_all_points_except = [] + self.apply_for_all_points_except = apply_for_all_points_except + + if len(self.only_apply_for_signals) > 0 and len(self.apply_for_all_signals_except) > 0: + raise Exception(f"You can not limit the infrastructure provider with only_apply_for_signals and " + f"apply_for_all_signals_except at the same time.") + if len(self.only_apply_for_points) > 0 and len(self.apply_for_all_points_except) > 0: + raise Exception(f"You can not limit the infrastructure provider with only_apply_for_points and " + f"apply_for_all_points_except at the same time.") + self.tds_count_in_callback = None self.tds_count_out_callback = None @@ -11,8 +36,18 @@ def __init__(self): # Point Interaction # + async def call_turn_point(self, yaramo_point: Node, target_orientation: str): + point_id = yaramo_point.uuid[-5:] + if point_id in self.only_apply_for_points: + return await self.turn_point(yaramo_point, target_orientation) + if len(self.only_apply_for_points) == 0 and point_id not in self.apply_for_all_points_except: + return await self.turn_point(yaramo_point, target_orientation) + + # return True to skip this call and not prevent successfully turning of point. + return True + @abstractmethod - async def turn_point(self, yaramo_point, target_orientation: str): + async def turn_point(self, yaramo_point: Node, target_orientation: str): """This method will be called when the interlocking controller wants to set the point. The `yaramo_point` is the yaramo identifier of the point and `target_orientation` is one of `"left"` and `"right"` """ pass @@ -21,8 +56,17 @@ async def turn_point(self, yaramo_point, target_orientation: str): # Signal Interaction # + async def call_set_signal_state(self, yaramo_signal: Signal, target_state: str): + if yaramo_signal.name in self.only_apply_for_signals: + return await self.set_signal_state(yaramo_signal, target_state) + if len(self.only_apply_for_signals) == 0 and yaramo_signal.name not in self.apply_for_all_signals_except: + return await self.set_signal_state(yaramo_signal, target_state) + + # return True to skip this call and not prevent successfully turning of point. + return True + @abstractmethod - async def set_signal_state(self, yaramo_signal, target_state): + async def set_signal_state(self, yaramo_signal: Signal, target_state: str): """This method will be called when the interlocking controller wants to change the signal-state of a specific signal. `yaramo_signal` corresponds to the identifier of the signal in the yaramo model; `target_state` is one of `"halt"` and `"go"`. """ diff --git a/interlocking/interlockingcontroller/pointcontroller.py b/interlocking/interlockingcontroller/pointcontroller.py index e074f5d..2defc29 100644 --- a/interlocking/interlockingcontroller/pointcontroller.py +++ b/interlocking/interlockingcontroller/pointcontroller.py @@ -1,4 +1,4 @@ -from interlocking.model import OccupancyState +from interlocking.model import OccupancyState, Point import asyncio import logging @@ -6,7 +6,7 @@ class PointController(object): def __init__(self, infrastructure_providers, settings): - self.points = None + self.points: dict[str, Point] = {} self.infrastructure_providers = infrastructure_providers self.settings = settings @@ -54,11 +54,11 @@ async def turn_point(self, point, orientation): # tasks = [] results = [] for infrastructure_provider in self.infrastructure_providers: - results.append(await infrastructure_provider.turn_point(point.yaramo_node, orientation)) + results.append(await infrastructure_provider.call_turn_point(point.yaramo_node, orientation)) # async with asyncio.TaskGroup() as tg: # for infrastructure_provider in self.infrastructure_providers: - # tasks.append(tg.create_task(infrastructure_provider.turn_point(point.yaramo_node, orientation))) + # tasks.append(tg.create_task(infrastructure_provider.call_turn_point(point.yaramo_node, orientation))) # if all(list(map(lambda task: task.result(), tasks))): if all(results): point.orientation = orientation diff --git a/interlocking/interlockingcontroller/signalcontroller.py b/interlocking/interlockingcontroller/signalcontroller.py index 3cde9b2..dd8950c 100644 --- a/interlocking/interlockingcontroller/signalcontroller.py +++ b/interlocking/interlockingcontroller/signalcontroller.py @@ -31,12 +31,12 @@ async def set_signal_state(self, signal, state): results = [] for infrastructure_provider in self.infrastructure_providers: - results.append(await infrastructure_provider.set_signal_state(signal.yaramo_signal, state)) + results.append(await infrastructure_provider.call_set_signal_state(signal.yaramo_signal, state)) # tasks = [] # async with asyncio.TaskGroup() as tg: # for infrastructure_provider in self.infrastructure_providers: - # tasks.append(tg.create_task(infrastructure_provider.set_signal_state(signal.yaramo_signal, state))) + # tasks.append(tg.create_task(infrastructure_provider.call_set_signal_state(signal.yaramo_signal, state))) # if all(list(map(lambda task: task.result(), tasks))): if all(results): signal.state = state From 5ac193448ae7f143917de878c3dc5fb97f685d59 Mon Sep 17 00:00:00 2001 From: Arne Boockmeyer Date: Fri, 21 Jul 2023 11:49:14 +0200 Subject: [PATCH 10/16] Add limiters to other infrastructure providers --- .../infrastructureprovider/logginginfrastructureprovider.py | 4 ++-- .../randomwaitinfrastructureprovider.py | 4 ++-- .../infrastructureprovider/sumoinfrastructureprovider.py | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/interlocking/infrastructureprovider/logginginfrastructureprovider.py b/interlocking/infrastructureprovider/logginginfrastructureprovider.py index be1a555..e27e675 100644 --- a/interlocking/infrastructureprovider/logginginfrastructureprovider.py +++ b/interlocking/infrastructureprovider/logginginfrastructureprovider.py @@ -5,8 +5,8 @@ class LoggingInfrastructureProvider(InfrastructureProvider): - def __init__(self): - super().__init__() + def __init__(self, **kwargs): + super().__init__(**kwargs) async def set_signal_state(self, yaramo_signal, target_state): logging.info(f"{time.strftime('%X')} Set signal {yaramo_signal.name} to {target_state}") diff --git a/interlocking/infrastructureprovider/randomwaitinfrastructureprovider.py b/interlocking/infrastructureprovider/randomwaitinfrastructureprovider.py index 9dd4946..95443ce 100644 --- a/interlocking/infrastructureprovider/randomwaitinfrastructureprovider.py +++ b/interlocking/infrastructureprovider/randomwaitinfrastructureprovider.py @@ -15,8 +15,8 @@ class RandomWaitInfrastructureProvider(InfrastructureProvider): def __init__(self, fail_probability=0.0, signal_time_range: range = range(2, 5), point_turn_time_range: range = range(5, 8), always_succeed_for: list[str] = None, - always_fail_for: list[str] = None): - super().__init__() + always_fail_for: list[str] = None, **kwargs): + super().__init__(**kwargs) if always_succeed_for is None: always_succeed_for = [] if always_fail_for is None: diff --git a/interlocking/infrastructureprovider/sumoinfrastructureprovider.py b/interlocking/infrastructureprovider/sumoinfrastructureprovider.py index 0bb443d..c03d407 100644 --- a/interlocking/infrastructureprovider/sumoinfrastructureprovider.py +++ b/interlocking/infrastructureprovider/sumoinfrastructureprovider.py @@ -4,8 +4,8 @@ class SUMOInfrastructureProvider(InfrastructureProvider): - def __init__(self, traci_instance): - super().__init__() + def __init__(self, traci_instance, **kwargs): + super().__init__(**kwargs) self.traci_instance = traci_instance async def set_signal_state(self, yaramo_signal, target_state): From 7d6d3d6863b8826e93d7e1a47430183a671d159e Mon Sep 17 00:00:00 2001 From: Arne Boockmeyer Date: Fri, 21 Jul 2023 13:15:35 +0200 Subject: [PATCH 11/16] Add tests for denying non consecutive routes --- test/interlocking_test.py | 47 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/test/interlocking_test.py b/test/interlocking_test.py index 867e362..9eb586c 100644 --- a/test/interlocking_test.py +++ b/test/interlocking_test.py @@ -6,7 +6,6 @@ def test_reset(): - print("test") topology = topologyhelper.get_topology_from_planpro_file("./complex-example.ppxml") interlocking = interlockinghelper.get_interlocking(topology) route_1 = topologyhelper.get_route_by_signal_names(topology, "60BS1", "60BS2") @@ -24,6 +23,8 @@ def test_reset(): interlockinghelper.test_signal(interlocking, "60ES2", "go") interlockinghelper.test_point(interlocking, "fd73d", "RB102", "right", OccupancyState.RESERVED) + assert len(interlocking.active_routes) == 2 + asyncio.run(interlocking.reset()) interlockinghelper.test_track(interlocking, "94742-0", "RB101", OccupancyState.FREE) @@ -36,6 +37,8 @@ def test_reset(): interlockinghelper.test_signal(interlocking, "60ES2", "halt") interlockinghelper.test_point(interlocking, "fd73d", "RB102", "undefined", OccupancyState.FREE) + assert len(interlocking.active_routes) == 0 + def test_print_state(): # Tests if print state runs without error @@ -211,6 +214,48 @@ def test_failing_signal(): asyncio.run(interlockinghelper.set_route(interlocking, route_bs2_bs3, True, "RB101")) +def test_consecutive_routes(): + topology = topologyhelper.get_topology_from_planpro_file("./complex-example.ppxml") + interlocking = interlockinghelper.get_interlocking(topology) + + # Consecutive routes, everything fine. + route_1 = topologyhelper.get_route_by_signal_names(topology, "60BS1", "60BS2") + asyncio.run(interlockinghelper.set_route(interlocking, route_1, True, "RB101")) + route_2 = topologyhelper.get_route_by_signal_names(topology, "60BS2", "60BS3") + asyncio.run(interlockinghelper.set_route(interlocking, route_2, True, "RB101")) + + asyncio.run(interlocking.reset()) + + # Totally different routes, not allowed + route_1 = topologyhelper.get_route_by_signal_names(topology, "60BS1", "60BS2") + asyncio.run(interlockinghelper.set_route(interlocking, route_1, True, "RB101")) + route_2 = topologyhelper.get_route_by_signal_names(topology, "60ES1", "60AS1") + asyncio.run(interlockinghelper.set_route(interlocking, route_2, False, "RB101")) + + asyncio.run(interlocking.reset()) + + # Reduce speed to avoid overlap + for route in interlocking.routes: + route.yaramo_route.maximum_speed = 30 + + # Overlapping routes without overlap, not allowed + # (caused issue https://github.com/simulate-digital-rail/interlocking/issues/14) + route_1 = topologyhelper.get_route_by_signal_names(topology, "60BS1", "60BS2") + asyncio.run(interlockinghelper.set_route(interlocking, route_1, True, "RB101")) + route_2 = topologyhelper.get_route_by_signal_names(topology, "60BS6", "60BS7") + asyncio.run(interlockinghelper.set_route(interlocking, route_2, False, "RB101")) + + asyncio.run(interlocking.reset()) + + # Test three in a row consecutive routes, everything fine + route_1 = topologyhelper.get_route_by_signal_names(topology, "60BS1", "60ES1") + asyncio.run(interlockinghelper.set_route(interlocking, route_1, True, "RB101")) + route_2 = topologyhelper.get_route_by_signal_names(topology, "60ES1", "60AS1") + asyncio.run(interlockinghelper.set_route(interlocking, route_2, True, "RB101")) + route_3 = topologyhelper.get_route_by_signal_names(topology, "60AS1", "60BS3") + asyncio.run(interlockinghelper.set_route(interlocking, route_3, True, "RB101")) + + # if __name__ == "__main__": # logging.basicConfig(level=logging.DEBUG) # test_driving() From bb0abd10248762829921ae485acb1d868ee0813f Mon Sep 17 00:00:00 2001 From: Arne Boockmeyer Date: Fri, 21 Jul 2023 13:16:46 +0200 Subject: [PATCH 12/16] Add logic to deny non-consecutive routes --- interlocking/interlockinginterface.py | 44 +++++++++++++++++++++++---- interlocking/model/route.py | 1 + 2 files changed, 39 insertions(+), 6 deletions(-) diff --git a/interlocking/interlockinginterface.py b/interlocking/interlockinginterface.py index c064689..e554b33 100644 --- a/interlocking/interlockinginterface.py +++ b/interlocking/interlockinginterface.py @@ -1,6 +1,7 @@ from interlocking.interlockingcontroller import PointController, SignalController, TrackController, TrainDetectionController from interlocking.model import Point, Track, Signal, Route from interlocking.model.helper import SetRouteResult, Settings, InterlockingOperationType +from yaramo.model import Route as YaramoRoute import asyncio import time import logging @@ -18,8 +19,8 @@ def __init__(self, infrastructure_providers, settings=Settings()): self.signal_controller = SignalController(self.infrastructure_providers) self.track_controller = TrackController(self, self.point_controller, self.signal_controller) self.train_detection_controller = TrainDetectionController(self.track_controller, self.infrastructure_providers) - self.routes = [] - self.active_routes = [] + self.routes: list[Route] = [] + self.active_routes: list[Route] = [] def prepare(self, yaramo_topoloy): # Nodes @@ -116,13 +117,20 @@ def print_state(self): logging.debug(active_route.to_string()) logging.debug("##############") - async def set_route(self, yaramo_route, train_id: str): + async def set_route(self, yaramo_route: YaramoRoute, train_id: str): route_formation_time_start = time.time() set_route_result = SetRouteResult() + + # Test, if train is already on track and if yes, check for consecutive routes: + if not self._is_route_valid_consecutive_route(yaramo_route, train_id): + set_route_result.success = False + return set_route_result + if not self.can_route_be_set(yaramo_route, train_id): set_route_result.success = False return set_route_result - route = self.get_route_from_yaramo_route(yaramo_route) + route: Route = self.get_route_from_yaramo_route(yaramo_route) + route.used_by = train_id self.active_routes.append(route) async with asyncio.TaskGroup() as tg: @@ -138,6 +146,7 @@ async def set_route(self, yaramo_route, train_id: str): # Set route failed, so the route has to be reset await self.reset_route(yaramo_route, train_id) set_route_result.success = False + set_route_result.route_formation_time = time.time() - route_formation_time_start return set_route_result @@ -155,20 +164,43 @@ def do_two_routes_collide(self, yaramo_route_1, yaramo_route_2): return do_collide def free_route(self, yaramo_route, train_id: str): - route = self.get_route_from_yaramo_route(yaramo_route) + route: Route = self.get_route_from_yaramo_route(yaramo_route) self.track_controller.free_route(route, train_id) self.active_routes.remove(route) + route.used_by = None async def reset_route(self, yaramo_route, train_id: str): - route = self.get_route_from_yaramo_route(yaramo_route) + route: Route = self.get_route_from_yaramo_route(yaramo_route) self.point_controller.reset_route(route, train_id) self.track_controller.reset_route(route, train_id) self.train_detection_controller.reset_track_segments_of_route(route) await self.signal_controller.reset_route(route) self.active_routes.remove(route) + route.used_by = None def get_route_from_yaramo_route(self, yaramo_route): for route in self.routes: if route.yaramo_route.uuid == yaramo_route.uuid: return route return None + + def _is_route_valid_consecutive_route(self, new_route: YaramoRoute, train_id: str): + all_routes_of_train = list(filter(lambda active_route: active_route.used_by == train_id, self.active_routes)) + if len(all_routes_of_train) == 0: + # New train, per definition consecutive route + return True + + # Find route with no successor + last_route = None + for route in all_routes_of_train: + # All other routes + found_successor = False + for other_route in all_routes_of_train: + if route.id != other_route.id: + if route.end_signal.yaramo_signal.name == other_route.start_signal.yaramo_signal.name: + found_successor = True + if not found_successor: + if last_route is not None: + raise ValueError("Multiple last routes found") + last_route = route + return last_route.end_signal.yaramo_signal.name == new_route.start_signal.name diff --git a/interlocking/model/route.py b/interlocking/model/route.py index 4b83f0f..cf51555 100644 --- a/interlocking/model/route.py +++ b/interlocking/model/route.py @@ -12,6 +12,7 @@ def __init__(self, yaramo_route): self.end_signal = None self.tracks = [] self.overlap = None + self.used_by = None def contains_segment(self, segment): for track in self.tracks: From ed6746a9e43c0ae409424347b00ce33b7373d26b Mon Sep 17 00:00:00 2001 From: Arne Boockmeyer Date: Tue, 25 Jul 2023 16:24:16 +0200 Subject: [PATCH 13/16] Add test for detection of multiple invalid routes and prevention of setting opposite routes --- interlocking/interlockinginterface.py | 3 ++- test/interlocking_test.py | 24 ++++++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/interlocking/interlockinginterface.py b/interlocking/interlockinginterface.py index 79eca51..c9c5336 100644 --- a/interlocking/interlockinginterface.py +++ b/interlocking/interlockinginterface.py @@ -217,4 +217,5 @@ def _is_route_valid_consecutive_route(self, new_route: YaramoRoute, train_id: st if last_route is not None: raise ValueError("Multiple last routes found") last_route = route - return last_route.end_signal.yaramo_signal.name == new_route.start_signal.name + return last_route.end_signal.yaramo_signal.name == new_route.start_signal.name and \ + last_route.start_signal.yaramo_signal.name != new_route.end_signal.name diff --git a/test/interlocking_test.py b/test/interlocking_test.py index 88e0bf1..8e03d68 100644 --- a/test/interlocking_test.py +++ b/test/interlocking_test.py @@ -257,6 +257,30 @@ def test_consecutive_routes(): asyncio.run(interlockinghelper.set_route(interlocking, route_3, True, "RB101")) +class TestConsecutiveRouteDetectionWithTwoLastTracks(unittest.TestCase): + + def test_consecutive_route_detection_with_two_last_tracks(self): + topology = topologyhelper.get_topology_from_planpro_file("./complex-example.ppxml") + interlocking = interlockinghelper.get_interlocking(topology) + + route_1 = topologyhelper.get_route_by_signal_names(topology, "60BS1", "60BS2") + route_2 = topologyhelper.get_route_by_signal_names(topology, "60ES1", "60AS1") + route_3 = topologyhelper.get_route_by_signal_names(topology, "60AS1", "60BS3") + + ixl_route_1 = interlocking.get_route_from_yaramo_route(route_1) + ixl_route_2 = interlocking.get_route_from_yaramo_route(route_2) + + ixl_route_1.used_by = "RB101" + ixl_route_2.used_by = "RB101" + interlocking.active_routes.append(ixl_route_1) + interlocking.active_routes.append(ixl_route_2) + + with self.assertRaises(ValueError) as error: + asyncio.run(interlocking.set_route(route_3, "RB101")) + + self.assertEqual(str(error.exception), "Multiple last routes found") + + class TestFreeRouteExceptions(unittest.TestCase): def test_free_route_without_setting_route_before(self): From 05f05e017a89c41fb95e3ca1dc2a121720488095 Mon Sep 17 00:00:00 2001 From: Arne Boockmeyer Date: Wed, 26 Jul 2023 14:34:52 +0200 Subject: [PATCH 14/16] Add tests for the basic functions of the monitor --- test/interlocking-monitor_test.py | 57 +++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 test/interlocking-monitor_test.py diff --git a/test/interlocking-monitor_test.py b/test/interlocking-monitor_test.py new file mode 100644 index 0000000..375e22a --- /dev/null +++ b/test/interlocking-monitor_test.py @@ -0,0 +1,57 @@ +from .helper import topologyhelper +from interlockinglogicmonitor import InterlockingLogicMonitor +from interlocking.interlockinginterface import Interlocking +from interlocking.infrastructureprovider import LoggingInfrastructureProvider +import asyncio + + +def test_set_route(): + topology = topologyhelper.get_topology_from_planpro_file("./complex-example.ppxml") + ilm = InterlockingLogicMonitor(topology) + interlocking = Interlocking(LoggingInfrastructureProvider(), interlocking_logic_monitor=ilm) + interlocking.prepare(topology) + + some_route = list(topology.routes.values())[0] + assert not ilm.route_results[some_route.uuid].was_set + assert ilm.route_results[some_route.uuid].get_coverage() == 0.0 + asyncio.run(interlocking.set_route(some_route, "RB101")) + assert ilm.route_results[some_route.uuid].was_set + assert ilm.route_results[some_route.uuid].get_coverage() == 1/3 + + +def test_free_route(): + topology = topologyhelper.get_topology_from_planpro_file("./complex-example.ppxml") + ilm = InterlockingLogicMonitor(topology) + interlocking = Interlocking(LoggingInfrastructureProvider(), interlocking_logic_monitor=ilm) + interlocking.prepare(topology) + + some_route = list(topology.routes.values())[0] + assert not ilm.route_results[some_route.uuid].was_set + assert not ilm.route_results[some_route.uuid].was_freed + assert ilm.route_results[some_route.uuid].get_coverage() == 0.0 + + asyncio.run(interlocking.set_route(some_route, "RB101")) + assert ilm.route_results[some_route.uuid].was_set + assert ilm.route_results[some_route.uuid].get_coverage() == 1/3 + interlocking.free_route(some_route, "RB101") + assert ilm.route_results[some_route.uuid].was_freed + assert ilm.route_results[some_route.uuid].get_coverage() == 2/3 + + +def test_reset_route(): + topology = topologyhelper.get_topology_from_planpro_file("./complex-example.ppxml") + ilm = InterlockingLogicMonitor(topology) + interlocking = Interlocking(LoggingInfrastructureProvider(), interlocking_logic_monitor=ilm) + interlocking.prepare(topology) + + some_route = list(topology.routes.values())[0] + assert not ilm.route_results[some_route.uuid].was_set + assert not ilm.route_results[some_route.uuid].was_reset + assert ilm.route_results[some_route.uuid].get_coverage() == 0.0 + + asyncio.run(interlocking.set_route(some_route, "RB101")) + assert ilm.route_results[some_route.uuid].was_set + assert ilm.route_results[some_route.uuid].get_coverage() == 1/3 + asyncio.run(interlocking.reset_route(some_route, "RB101")) + assert ilm.route_results[some_route.uuid].was_reset + assert ilm.route_results[some_route.uuid].get_coverage() == 2/3 From 597562855fa5e46d1e183ce20aef56ec0aef78b9 Mon Sep 17 00:00:00 2001 From: Arne Boockmeyer Date: Wed, 26 Jul 2023 16:42:04 +0200 Subject: [PATCH 15/16] Add function to verify that all elements are covered by some infrastructure provider (and tests for that) --- .../infrastructureprovider.py | 69 +- interlocking/interlockinginterface.py | 7 + interlocking/model/helper/settings.py | 12 +- test/helper/topologyhelper.py | 16 +- test/infrastructureprovider_test.py | 80 +- test/simple-example.ppxml | 1622 +++++++++++++++++ 6 files changed, 1792 insertions(+), 14 deletions(-) create mode 100644 test/simple-example.ppxml diff --git a/interlocking/infrastructureprovider/infrastructureprovider.py b/interlocking/infrastructureprovider/infrastructureprovider.py index 062ff72..3eff1c3 100644 --- a/interlocking/infrastructureprovider/infrastructureprovider.py +++ b/interlocking/infrastructureprovider/infrastructureprovider.py @@ -1,14 +1,23 @@ +import logging from abc import ABC, abstractmethod -from yaramo.model import Node, Signal +from yaramo.model import Node, Signal, Topology class InfrastructureProvider(ABC): def __init__(self, + apply_for_signals: bool = True, + apply_for_points: bool = True, only_apply_for_signals: list[str] = None, only_apply_for_points: list[str] = None, apply_for_all_signals_except: list[str] = None, apply_for_all_points_except: list[str] = None): + self.apply_for_signals = apply_for_signals + self.apply_for_points = apply_for_points + + if not self.apply_for_points and not self.apply_for_signals: + raise Exception("The infrastructure provider has to apply for signals, points or both.") + if only_apply_for_signals is None: only_apply_for_signals = [] self.only_apply_for_signals = only_apply_for_signals @@ -36,13 +45,16 @@ def __init__(self, # Point Interaction # - async def call_turn_point(self, yaramo_point: Node, target_orientation: str): + def is_point_covered(self, yaramo_point: Node): + if not self.apply_for_points: + return False point_id = yaramo_point.uuid[-5:] - if point_id in self.only_apply_for_points: - return await self.turn_point(yaramo_point, target_orientation) - if len(self.only_apply_for_points) == 0 and point_id not in self.apply_for_all_points_except: - return await self.turn_point(yaramo_point, target_orientation) + return point_id in self.only_apply_for_points or \ + (len(self.only_apply_for_points) == 0 and point_id not in self.apply_for_all_points_except) + async def call_turn_point(self, yaramo_point: Node, target_orientation: str): + if self.is_point_covered(yaramo_point): + return await self.turn_point(yaramo_point, target_orientation) # return True to skip this call and not prevent successfully turning of point. return True @@ -56,13 +68,16 @@ async def turn_point(self, yaramo_point: Node, target_orientation: str): # Signal Interaction # + def is_signal_covered(self, yaramo_signal: Signal): + if not self.apply_for_signals: + return False + return yaramo_signal.name in self.only_apply_for_signals or \ + (len(self.only_apply_for_signals) == 0 and yaramo_signal.name not in self.apply_for_all_signals_except) + async def call_set_signal_state(self, yaramo_signal: Signal, target_state: str): - if yaramo_signal.name in self.only_apply_for_signals: - return await self.set_signal_state(yaramo_signal, target_state) - if len(self.only_apply_for_signals) == 0 and yaramo_signal.name not in self.apply_for_all_signals_except: + if self.is_signal_covered(yaramo_signal): return await self.set_signal_state(yaramo_signal, target_state) - - # return True to skip this call and not prevent successfully turning of point. + # return True to skip this call and not prevent successfully turning of signal. return True @abstractmethod @@ -89,3 +104,35 @@ def _set_tds_count_out_callback(self, tds_count_out_callback): async def tds_count_out(self, track_segment_id, train_id: str): """Removes a train to the segment identified by the `segment_id`""" self.tds_count_out_callback(track_segment_id, train_id) + + # + # Verify that all elements are covered by some infrastructure providers and add a default provider if not + # + + @staticmethod + def verify_all_elements_covered_by_infrastructure_provider(topology: Topology, infrastructure_providers, settings): + uncovered_signals = [] + uncovered_points = [] + + for signal in list(topology.signals.values()): + if not any(ip.is_signal_covered(signal) for ip in infrastructure_providers): + logging.warning(f"The signal {signal.name} is not covered by any infrastructure provider.") + uncovered_signals.append(signal.name) + + for point in list(topology.nodes.values()): + if len(point.connected_nodes) != 3: + continue # Skip all track ends + if not any(ip.is_point_covered(point) for ip in infrastructure_providers): + point_id = point.uuid[-5:] + logging.warning(f"The point {point_id} is not covered by any infrastructure provider.") + uncovered_points.append(point_id) + + if len(uncovered_signals) > 0 or len(uncovered_points) > 0: + if settings.default_interlocking_provider is not None: + return settings.default_interlocking_provider(apply_for_points=len(uncovered_points) > 0, + apply_for_signals=len(uncovered_signals) > 0, + only_apply_for_points=uncovered_points, + only_apply_for_signals=uncovered_signals) + return None + + diff --git a/interlocking/interlockinginterface.py b/interlocking/interlockinginterface.py index 8c8e691..73bb144 100644 --- a/interlocking/interlockinginterface.py +++ b/interlocking/interlockinginterface.py @@ -1,4 +1,5 @@ from interlocking.interlockingcontroller import PointController, SignalController, TrackController, TrainDetectionController +from interlocking.infrastructureprovider import InfrastructureProvider from interlocking.model import Point, Track, Signal, Route from interlocking.model.helper import SetRouteResult, Settings, InterlockingOperationType import asyncio @@ -38,6 +39,12 @@ def prepare(self, yaramo_topoloy): signals[yaramo_signal.uuid] = signal self.signal_controller.signals = signals + new_ip = InfrastructureProvider.verify_all_elements_covered_by_infrastructure_provider(yaramo_topoloy, + self.infrastructure_providers, + self.settings) + if new_ip is not None: + self.infrastructure_providers.append(new_ip) + # Tracks tracks = dict() for edge_uuid in yaramo_topoloy.edges: diff --git a/interlocking/model/helper/settings.py b/interlocking/model/helper/settings.py index 0c142f7..a325e32 100644 --- a/interlocking/model/helper/settings.py +++ b/interlocking/model/helper/settings.py @@ -1,4 +1,14 @@ +from interlocking.infrastructureprovider import LoggingInfrastructureProvider, InfrastructureProvider +from typing import Type + + class Settings(object): - def __init__(self, max_number_of_points_at_same_time=5): + def __init__(self, + max_number_of_points_at_same_time: int = 5, + default_interlocking_provider: Type[InfrastructureProvider] | None = LoggingInfrastructureProvider): self.max_number_of_points_at_same_time = max(max_number_of_points_at_same_time, 1) + + # For all elements that are not covered by an infrastructure provider, an instance of this default provider will + # be created. This default provider can be None. + self.default_interlocking_provider: Type[InfrastructureProvider] | None = default_interlocking_provider diff --git a/test/helper/topologyhelper.py b/test/helper/topologyhelper.py index 1da682f..0094043 100644 --- a/test/helper/topologyhelper.py +++ b/test/helper/topologyhelper.py @@ -1,6 +1,6 @@ from planpro_importer.reader import PlanProReader from railwayroutegenerator.routegenerator import RouteGenerator -from yaramo.model import Topology +from yaramo.model import Topology, Signal, Node def get_topology_from_planpro_file(file_name: str): @@ -28,3 +28,17 @@ def get_route_by_signal_names(topology: Topology, start_signal_name: str, end_si route = topology.routes[route_uuid] if route.start_signal.name == start_signal_name and route.end_signal.name == end_signal_name: return route + + +def get_signal_by_name(topology: Topology, signal_name: str) -> Signal | None: + for signal in list(topology.signals.values()): + if signal.name == signal_name: + return signal + return None + + +def get_point_by_name(topology: Topology, point_name: str) -> Node | None: + for point in list(topology.nodes.values()): + if point.uuid[-5:] == point_name: + return point + return None diff --git a/test/infrastructureprovider_test.py b/test/infrastructureprovider_test.py index 5b9fdc3..54b8e3e 100644 --- a/test/infrastructureprovider_test.py +++ b/test/infrastructureprovider_test.py @@ -1,4 +1,7 @@ from .helper import topologyhelper, interlockinghelper, trackoperationsinfrastructureprovider +from interlocking.interlockinginterface import Interlocking +from interlocking.infrastructureprovider import LoggingInfrastructureProvider +from interlocking.model.helper import Settings import asyncio import unittest @@ -28,6 +31,42 @@ def test_using_both_point_limiters(self): f"the same time.") +class TestApplyForNone(unittest.TestCase): + + def test_apply_for_none(self): + with self.assertRaises(Exception) as exception: + ip = LoggingInfrastructureProvider(apply_for_points=False, apply_for_signals=False) + + self.assertEqual(str(exception.exception), f"The infrastructure provider has to apply for " + f"signals, points or both.") + + +def test_apply_for_signals(): + topology = topologyhelper.get_topology_from_planpro_file("./complex-example.ppxml") + ip = trackoperationsinfrastructureprovider.TrackOperationsInfrastructureProvider(apply_for_signals=False) + interlocking = interlockinghelper.get_interlocking(topology, infrastructure_provider=[ip]) + route_es1_as1 = topologyhelper.get_route_by_signal_names(topology, "60ES1", "60AS1") + asyncio.run(interlockinghelper.set_route(interlocking, route_es1_as1, True, "RB101")) + + signal_es1 = interlockinghelper.get_interlocking_signal_by_name(interlocking, "60ES1").yaramo_signal + point_on_first_route = interlockinghelper.get_interlocking_point_by_id(interlocking, "fd73d").yaramo_node + assert not ip.was_signal_set_to_aspect(signal_es1, "go") + assert ip.was_point_turned_to(point_on_first_route, "right") + + +def test_apply_for_points(): + topology = topologyhelper.get_topology_from_planpro_file("./complex-example.ppxml") + ip = trackoperationsinfrastructureprovider.TrackOperationsInfrastructureProvider(apply_for_points=False) + interlocking = interlockinghelper.get_interlocking(topology, infrastructure_provider=[ip]) + route_es1_as1 = topologyhelper.get_route_by_signal_names(topology, "60ES1", "60AS1") + asyncio.run(interlockinghelper.set_route(interlocking, route_es1_as1, True, "RB101")) + + signal_es1 = interlockinghelper.get_interlocking_signal_by_name(interlocking, "60ES1").yaramo_signal + point_on_first_route = interlockinghelper.get_interlocking_point_by_id(interlocking, "fd73d").yaramo_node + assert ip.was_signal_set_to_aspect(signal_es1, "go") + assert not ip.was_point_turned_to(point_on_first_route, "right") + + def test_only_apply_for(): topology = topologyhelper.get_topology_from_planpro_file("./complex-example.ppxml") ip = trackoperationsinfrastructureprovider.TrackOperationsInfrastructureProvider(only_apply_for_signals=["60ES1"], @@ -71,4 +110,43 @@ def test_apply_for_all_except(): assert not ip.was_point_turned_to(point_on_first_route_fd73d, "right") assert ip.was_point_turned_to(point_on_first_route_fa9ea, "left") assert ip.was_point_turned_to(point_on_first_route_21b88, "right") - assert ip.was_point_turned_to(point_on_second_route, "right") \ No newline at end of file + assert ip.was_point_turned_to(point_on_second_route, "right") + + +def test_infrastructure_providers_cover_all_elements(): + topology = topologyhelper.get_topology_from_planpro_file("./simple-example.ppxml") + ip = LoggingInfrastructureProvider( + only_apply_for_signals=["A1", "B1"], + only_apply_for_points=["d2c77"] + ) + interlocking = Interlocking(ip) + assert len(interlocking.infrastructure_providers) == 1 + assert ip.is_point_covered(topologyhelper.get_point_by_name(topology, "d2c77")) + assert not ip.is_point_covered(topologyhelper.get_point_by_name(topology, "c92aa")) + assert ip.is_signal_covered(topologyhelper.get_signal_by_name(topology, "A1")) + assert ip.is_signal_covered(topologyhelper.get_signal_by_name(topology, "B1")) + assert not ip.is_signal_covered(topologyhelper.get_signal_by_name(topology, "A2")) + assert not ip.is_signal_covered(topologyhelper.get_signal_by_name(topology, "B2")) + + interlocking.prepare(topology) + assert len(interlocking.infrastructure_providers) == 2 + + new_ip = interlocking.infrastructure_providers[1] + assert not new_ip.is_point_covered(topologyhelper.get_point_by_name(topology, "d2c77")) + assert new_ip.is_point_covered(topologyhelper.get_point_by_name(topology, "c92aa")) + assert not new_ip.is_signal_covered(topologyhelper.get_signal_by_name(topology, "A1")) + assert not new_ip.is_signal_covered(topologyhelper.get_signal_by_name(topology, "B1")) + assert new_ip.is_signal_covered(topologyhelper.get_signal_by_name(topology, "A2")) + assert new_ip.is_signal_covered(topologyhelper.get_signal_by_name(topology, "B2")) + + +def test_no_default_infrastructure_provider(): + topology = topologyhelper.get_topology_from_planpro_file("./simple-example.ppxml") + ip = LoggingInfrastructureProvider( + only_apply_for_signals=["A1", "B1"], + only_apply_for_points=["d2c77"] + ) + interlocking = Interlocking(ip, Settings(default_interlocking_provider=None)) + assert len(interlocking.infrastructure_providers) == 1 + interlocking.prepare(topology) + assert len(interlocking.infrastructure_providers) == 1 diff --git a/test/simple-example.ppxml b/test/simple-example.ppxml new file mode 100644 index 0000000..f31e286 --- /dev/null +++ b/test/simple-example.ppxml @@ -0,0 +1,1622 @@ + + + + f15fc014-bdd7-4e55-b7e5-6cdc989f1af8 + + + + 2023-07-19T16:03:20.001228 + + + Werkzeugkoffer + + + 48.3.0 + + + + + + + 227dc14e-265d-4e20-a004-c2d68a98f1b2 + + + + 102ddaa1-0a2b-4f94-b093-589b8ea20e61 + + + + + + 71419fc6-11fc-4a8c-848c-c89ad2a311ea + + + + + C35FDB88-889E-539E-A68B-6079265F70D9 + + + + 2012-02-24 + + + + + + ESTW_A + + + + + Notstromaggregat_NEA_stationaer + + + C35FDB88-889E-539E-A68B-6079265F70D9 + + + + + Scheibenberg + + + + C35FDB88-889E-539E-A68B-6079265F70D9 + + + F4CE3AF8-13B1-4E12-BB4D-982BEA37466E + + + + + ac6d1828-8804-41e3-a3b9-858c309c28a6 + + + + 2012-02-24 + + + + + + 25.000 + + + 30.000 + + + 7ee494a9-c8e4-4dd2-a551-30e07d42cd3f + + + + + 0.000 + + + 40.000 + + + 18da7ec4-c5ac-40dd-9c5e-78d151c114d3 + + + + + 30.000 + + + 5.000 + + + 61c25937-de19-45fd-b2f6-79e9488a69d0 + + + + 30 + + + a3f4e81b-3f95-4cf8-bf97-80f74071c24f + + + c95982cb-227c-4742-bc0c-9b36c821d347 + + + + + 40147528-665c-4a2d-9186-1a13f16401ad + + + + 2012-02-24 + + + + + + 25.000 + + + 31.623 + + + c49e4857-ab24-4ea7-a8d3-8eebbd0f8860 + + + + + 0.000 + + + 40.000 + + + 18da7ec4-c5ac-40dd-9c5e-78d151c114d3 + + + + + 31.623 + + + 5.000 + + + 3ed7be31-4d7a-4fe6-8995-fa69329a5b76 + + + + 30 + + + 0a70c8a2-bebc-43df-9419-30ff4ed984fe + + + 31beef7e-e3b9-4362-bdac-77f60f4c8847 + + + + + 7c655899-ed8e-4b88-b933-597fd044c2ef + + + + 2012-02-24 + + + + + + Gerade + + + 31.62278 + + + Ivl + + + + 3ed7be31-4d7a-4fe6-8995-fa69329a5b76 + + + 57a7210f-2265-4cb4-8ba4-992cc1559121 + + + e72fdb3a-2d86-4971-8a5e-d149abd8eef9 + + + + + e471dc93-c14d-4158-8afa-cc123c4d7002 + + + + 2012-02-24 + + + + + + Gerade + + + 30.00000 + + + Ivl + + + + 7ee494a9-c8e4-4dd2-a551-30e07d42cd3f + + + 1f6a920a-68b1-4606-9455-27ed0e6ee861 + + + e72fdb3a-2d86-4971-8a5e-d149abd8eef9 + + + + + 39b7750f-55c8-455d-9dc2-d7aa6d3d75ab + + + + 2012-02-24 + + + + + + Gerade + + + 31.62278 + + + Ivl + + + + c49e4857-ab24-4ea7-a8d3-8eebbd0f8860 + + + 6ea4bf5c-7352-4a37-bf62-fc1ef601262d + + + ae44b397-db94-45e9-bd08-213a94f93518 + + + + + b240ee2b-7534-4592-ba4c-f75531546927 + + + + 2012-02-24 + + + + + + Gerade + + + 30.00000 + + + Ivl + + + + 61c25937-de19-45fd-b2f6-79e9488a69d0 + + + f53425d7-e9d0-409c-8b29-e58455416432 + + + ae44b397-db94-45e9-bd08-213a94f93518 + + + + + eb872e90-dd0a-47e9-b82a-2c35e35671f2 + + + + 2012-02-24 + + + + + + Gerade + + + 40.00000 + + + Ivl + + + + 18da7ec4-c5ac-40dd-9c5e-78d151c114d3 + + + e72fdb3a-2d86-4971-8a5e-d149abd8eef9 + + + ae44b397-db94-45e9-bd08-213a94f93518 + + + + + 57a7210f-2265-4cb4-8ba4-992cc1559121 + + + + 2012-02-24 + + + + + + + 1f6a920a-68b1-4606-9455-27ed0e6ee861 + + + + 2012-02-24 + + + + + + + 6ea4bf5c-7352-4a37-bf62-fc1ef601262d + + + + 2012-02-24 + + + + + + + f53425d7-e9d0-409c-8b29-e58455416432 + + + + 2012-02-24 + + + + + + + e72fdb3a-2d86-4971-8a5e-d149abd8eef9 + + + + 2012-02-24 + + + + + + + ae44b397-db94-45e9-bd08-213a94f93518 + + + + 2012-02-24 + + + + + + + 82936c80-38ef-4531-9864-b0614dcefae5 + + + + 2012-02-24 + + + + + + 0.00000 + + + 10.00000 + + + Ivl + + + EA0 + + + + 57a7210f-2265-4cb4-8ba4-992cc1559121 + + + + + 9b7fa162-31cd-4af4-96d4-a3cce09dc1ad + + + + 2012-02-24 + + + + + + 0.00000 + + + 0.00000 + + + Ivl + + + EA0 + + + + 1f6a920a-68b1-4606-9455-27ed0e6ee861 + + + + + 76227685-b013-49d4-a143-7c3694ed5521 + + + + 2012-02-24 + + + + + + 100.00000 + + + 10.00000 + + + Ivl + + + EA0 + + + + 6ea4bf5c-7352-4a37-bf62-fc1ef601262d + + + + + f9012d66-32b7-41cb-8ac5-f5bbb4cd6e56 + + + + 2012-02-24 + + + + + + 100.00000 + + + 0.00000 + + + Ivl + + + EA0 + + + + f53425d7-e9d0-409c-8b29-e58455416432 + + + + + 44a006c5-ed6f-4c17-b46d-5fc65d3db2d1 + + + + 2012-02-24 + + + + + + 30.00000 + + + 0.00000 + + + Ivl + + + EA0 + + + + e72fdb3a-2d86-4971-8a5e-d149abd8eef9 + + + + + 0d979871-829f-41c6-a4a8-433c5d28f4c2 + + + + 2012-02-24 + + + + + + 70.00000 + + + 0.00000 + + + Ivl + + + EA0 + + + + ae44b397-db94-45e9-bd08-213a94f93518 + + + + + 31beef7e-e3b9-4362-bdac-77f60f4c8847 + + + + 2012-02-24 + + + + + + f192a888-4ad4-4b80-aa6b-9a2d25affdcb + + + 1,632 + + + + + 5.000 + + + 3ed7be31-4d7a-4fe6-8995-fa69329a5b76 + + + gegen + + + -3.950 + + + + + A1 + + + A1 + + + A1 + + + A1 + + + 60 + + + A1 + + + + + Mast + + + + 31beef7e-e3b9-4362-bdac-77f60f4c8847 + + + Block_Signal + + + + + false + + + 150 + + + Hauptsignal + + + Ks + + + HG + + + HG4 + + + + + + + a3f4e81b-3f95-4cf8-bf97-80f74071c24f + + + + 2012-02-24 + + + + + + f192a888-4ad4-4b80-aa6b-9a2d25affdcb + + + 1,632 + + + + + 25.000 + + + 7ee494a9-c8e4-4dd2-a551-30e07d42cd3f + + + in + + + 3.950 + + + + + A2 + + + A2 + + + A2 + + + A2 + + + 60 + + + A2 + + + + + Mast + + + + a3f4e81b-3f95-4cf8-bf97-80f74071c24f + + + Block_Signal + + + + + false + + + 150 + + + Hauptsignal + + + Ks + + + HG + + + HG4 + + + + + + + 0a70c8a2-bebc-43df-9419-30ff4ed984fe + + + + 2012-02-24 + + + + + + f192a888-4ad4-4b80-aa6b-9a2d25affdcb + + + 1,632 + + + + + 25.000 + + + c49e4857-ab24-4ea7-a8d3-8eebbd0f8860 + + + in + + + 3.950 + + + + + B1 + + + B1 + + + B1 + + + B1 + + + 60 + + + B1 + + + + + Mast + + + + 0a70c8a2-bebc-43df-9419-30ff4ed984fe + + + Block_Signal + + + + + false + + + 150 + + + Hauptsignal + + + Ks + + + HG + + + HG4 + + + + + + + c95982cb-227c-4742-bc0c-9b36c821d347 + + + + 2012-02-24 + + + + + + f192a888-4ad4-4b80-aa6b-9a2d25affdcb + + + 1,632 + + + + + 5.000 + + + 61c25937-de19-45fd-b2f6-79e9488a69d0 + + + gegen + + + -3.950 + + + + + B2 + + + B2 + + + B2 + + + B2 + + + 60 + + + B2 + + + + + Mast + + + + c95982cb-227c-4742-bc0c-9b36c821d347 + + + Block_Signal + + + + + false + + + 150 + + + Hauptsignal + + + Ks + + + HG + + + HG4 + + + + + + + 31beef7e-e3b9-4362-bdac-77f60f4c8847 + + + + 2012-02-24 + + + + + C35FDB88-889E-539E-A68B-6079265F70D9 + + + C35FDB88-889E-539E-A68B-6079265F70D9 + + + + + a3f4e81b-3f95-4cf8-bf97-80f74071c24f + + + + 2012-02-24 + + + + + C35FDB88-889E-539E-A68B-6079265F70D9 + + + C35FDB88-889E-539E-A68B-6079265F70D9 + + + + + 0a70c8a2-bebc-43df-9419-30ff4ed984fe + + + + 2012-02-24 + + + + + C35FDB88-889E-539E-A68B-6079265F70D9 + + + C35FDB88-889E-539E-A68B-6079265F70D9 + + + + + c95982cb-227c-4742-bc0c-9b36c821d347 + + + + 2012-02-24 + + + + + C35FDB88-889E-539E-A68B-6079265F70D9 + + + C35FDB88-889E-539E-A68B-6079265F70D9 + + + + + f192a888-4ad4-4b80-aa6b-9a2d25affdcb + + + + 2012-02-24 + + + + + + 0.000 + + + 31.623 + + + 3ed7be31-4d7a-4fe6-8995-fa69329a5b76 + + + + + 0.000 + + + 30.000 + + + 7ee494a9-c8e4-4dd2-a551-30e07d42cd3f + + + + + 0.000 + + + 31.623 + + + c49e4857-ab24-4ea7-a8d3-8eebbd0f8860 + + + + + 0.000 + + + 30.000 + + + 61c25937-de19-45fd-b2f6-79e9488a69d0 + + + + + 0.000 + + + 40.000 + + + 18da7ec4-c5ac-40dd-9c5e-78d151c114d3 + + + + + 3791 + + + + + + 3ed7be31-4d7a-4fe6-8995-fa69329a5b76 + + + + 2012-02-24 + + + + + 4e6353c5-4d40-4d15-9c0e-706b306e745e + + + 5769f58d-b4ea-41f9-b9d1-2a9c458d2c77 + + + + Ende + + + Rechts + + + 31.623 + + + + + + 7ee494a9-c8e4-4dd2-a551-30e07d42cd3f + + + + 2012-02-24 + + + + + 38664d0d-7554-45d5-a348-d264e5cfa4ff + + + 5769f58d-b4ea-41f9-b9d1-2a9c458d2c77 + + + + Ende + + + Links + + + 30.000 + + + + + + c49e4857-ab24-4ea7-a8d3-8eebbd0f8860 + + + + 2012-02-24 + + + + + 18eddfbb-26a6-4acf-8fa2-fbcd9e3bcab9 + + + 70aa0450-61b0-48e3-bcab-a0b3d81c92aa + + + + Ende + + + Links + + + 31.623 + + + + + + 61c25937-de19-45fd-b2f6-79e9488a69d0 + + + + 2012-02-24 + + + + + de682e3b-d471-4865-a863-6f2ee2d1d9e6 + + + 70aa0450-61b0-48e3-bcab-a0b3d81c92aa + + + + Ende + + + Rechts + + + 30.000 + + + + + + 18da7ec4-c5ac-40dd-9c5e-78d151c114d3 + + + + 2012-02-24 + + + + + 5769f58d-b4ea-41f9-b9d1-2a9c458d2c77 + + + 70aa0450-61b0-48e3-bcab-a0b3d81c92aa + + + + Spitze + + + Spitze + + + 40.000 + + + + + + 4e6353c5-4d40-4d15-9c0e-706b306e745e + + + + 2012-02-24 + + + + + 57a7210f-2265-4cb4-8ba4-992cc1559121 + + + + + 38664d0d-7554-45d5-a348-d264e5cfa4ff + + + + 2012-02-24 + + + + + 1f6a920a-68b1-4606-9455-27ed0e6ee861 + + + + + 18eddfbb-26a6-4acf-8fa2-fbcd9e3bcab9 + + + + 2012-02-24 + + + + + 6ea4bf5c-7352-4a37-bf62-fc1ef601262d + + + + + de682e3b-d471-4865-a863-6f2ee2d1d9e6 + + + + 2012-02-24 + + + + + f53425d7-e9d0-409c-8b29-e58455416432 + + + + + 5769f58d-b4ea-41f9-b9d1-2a9c458d2c77 + + + + 2012-02-24 + + + + + e72fdb3a-2d86-4971-8a5e-d149abd8eef9 + + + + + 70aa0450-61b0-48e3-bcab-a0b3d81c92aa + + + + 2012-02-24 + + + + + ae44b397-db94-45e9-bd08-213a94f93518 + + + + + F4CE3AF8-13B1-4E12-BB4D-982BEA37466E + + + + 2012-02-24 + + + + + + Gebaeude + + + Fundament + + + + 11314F34-1A1C-4EBB-9AB2-133C5F2A9167 + + + + + + sonstige + + + + + + + F05DAC3B-4E33-43D3-9A03-6055566FC3B7 + + + + AFFE2101-3E72-4D64-88F1-FFE1A69396AA + + + ABC + + + + BE199B7D-A86C-4682-8E08-4B23DE2CF5A3 + + + 227dc14e-265d-4e20-a004-c2d68a98f1b2 + + + + 227dc14e-265d-4e20-a004-c2d68a98f1b2 + + + F4CE3AF8-13B1-4E12-BB4D-982BEA37466E + + + C35FDB88-889E-539E-A68B-6079265F70D9 + + + 4e6353c5-4d40-4d15-9c0e-706b306e745e + + + 57a7210f-2265-4cb4-8ba4-992cc1559121 + + + 38664d0d-7554-45d5-a348-d264e5cfa4ff + + + 1f6a920a-68b1-4606-9455-27ed0e6ee861 + + + 18eddfbb-26a6-4acf-8fa2-fbcd9e3bcab9 + + + 6ea4bf5c-7352-4a37-bf62-fc1ef601262d + + + de682e3b-d471-4865-a863-6f2ee2d1d9e6 + + + f53425d7-e9d0-409c-8b29-e58455416432 + + + 5769f58d-b4ea-41f9-b9d1-2a9c458d2c77 + + + e72fdb3a-2d86-4971-8a5e-d149abd8eef9 + + + 70aa0450-61b0-48e3-bcab-a0b3d81c92aa + + + ae44b397-db94-45e9-bd08-213a94f93518 + + + 3ed7be31-4d7a-4fe6-8995-fa69329a5b76 + + + 7c655899-ed8e-4b88-b933-597fd044c2ef + + + 7ee494a9-c8e4-4dd2-a551-30e07d42cd3f + + + e471dc93-c14d-4158-8afa-cc123c4d7002 + + + c49e4857-ab24-4ea7-a8d3-8eebbd0f8860 + + + 39b7750f-55c8-455d-9dc2-d7aa6d3d75ab + + + 61c25937-de19-45fd-b2f6-79e9488a69d0 + + + b240ee2b-7534-4592-ba4c-f75531546927 + + + 18da7ec4-c5ac-40dd-9c5e-78d151c114d3 + + + eb872e90-dd0a-47e9-b82a-2c35e35671f2 + + + 31beef7e-e3b9-4362-bdac-77f60f4c8847 + + + a3f4e81b-3f95-4cf8-bf97-80f74071c24f + + + 0a70c8a2-bebc-43df-9419-30ff4ed984fe + + + c95982cb-227c-4742-bc0c-9b36c821d347 + + + f192a888-4ad4-4b80-aa6b-9a2d25affdcb + + + ac6d1828-8804-41e3-a3b9-858c309c28a6 + + + 40147528-665c-4a2d-9186-1a13f16401ad + + + C35FDB88-889E-539E-A68B-6079265F70D9 + + + F4CE3AF8-13B1-4E12-BB4D-982BEA37466E + + + + + Neubau ESTW-A Scheibenberg + + + Ibn-Zustand + + + 2015-06-23 + + + 2015-06-23 + + + 01 + + + false + + + 04 + + + Bauzustand + + + PT_1 + + + + + 12345-67890 + + + 2019-07-31 + + + sonstige + + + + + + 1A3E6734-9E68-40FC-8C3F-D231E98146FF + + + + 1A3E6734-9E68-40FC-8C3F-D23DE98246FF + + + + Erlaeuterungsbericht + + + Erläuterungsbericht-Scheibenberg + + + pdf + + + + + + + + 2015-11-03 + + + + 60024045-7D4E-4C59-98AC-088D437D4B7A + + + + Boockmeyer + + + Boockmeyer + + + Boock + + + + + 55555555-5555-5555-5555-555555555555 + + + HPI-OSM + + + + + Planer + + + + + + + 2016-12-31 + + + 1.7.0.1 + + + sonstige + + + DB Netze + + + + + Annaberg-Buchholz - Schwarzenberg + + + 120,000 + + + 8980 + + + + + Annaberg-Buchholz - Schwarzenberg + + + ESTW-UZ N-Stadt + + + Neubau ESTW-A Scheibenberg + + + ESTW-A Scheibenberg + + + + 55555555-5555-5555-5555-555555555555 + + + HPI-OSM + + + + + + WGS84 + + + 4541324.55 5647860.25 4533627.31 5629542.69 4530892.30 5615315.52 4530896.25 5615314.81 4535700.32 5625130.79 4541007.74 5647040.74 + + + + + WGS84 + + + 4534953.69 5631722.21 4535060.27 5631655.77 4534117.81 5629091.17 4534190.47 5629031.56 4534190.47 5626410.63 4534541.42 5626126.37 4534451.48 5625924.01 4533883.87 5626227.30 4533883.87 5625744.61 4533648.18 5624927.37 4533444.84 5624987.20 4533963.42 5627558.05 4533627.31 5629542.69 4533935.67 5628728.88 4533950.34 5629188.21 + + + + + + Neubau ESTW Scheibenberg + + + 2017-12-31 + + + 1234ABCD5678EFGH + + + + 3C590A15-5293-4AF6-A81D-1C4E82297602 + + + + FleixDug + + + FleixDug + + + Fleix + + + + + 55555555-5555-5555-5555-555555555555 + + + HPI + + + + + + + + From fb83e2dcda211ff3286590a7a57ce27edbd48017 Mon Sep 17 00:00:00 2001 From: Arne Boockmeyer Date: Tue, 1 Aug 2023 15:18:13 +0200 Subject: [PATCH 16/16] Fix tests to new logic --- test/infrastructureprovider_test.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/infrastructureprovider_test.py b/test/infrastructureprovider_test.py index 54b8e3e..d172793 100644 --- a/test/infrastructureprovider_test.py +++ b/test/infrastructureprovider_test.py @@ -75,7 +75,7 @@ def test_only_apply_for(): route_es1_as1 = topologyhelper.get_route_by_signal_names(topology, "60ES1", "60AS1") asyncio.run(interlockinghelper.set_route(interlocking, route_es1_as1, True, "RB101")) route_bs2_bs3 = topologyhelper.get_route_by_signal_names(topology, "60BS2", "60BS3") - asyncio.run(interlockinghelper.set_route(interlocking, route_bs2_bs3, True, "RB101")) + asyncio.run(interlockinghelper.set_route(interlocking, route_bs2_bs3, True, "RB102")) signal_es1 = interlockinghelper.get_interlocking_signal_by_name(interlocking, "60ES1").yaramo_signal signal_bs2 = interlockinghelper.get_interlocking_signal_by_name(interlocking, "60BS2").yaramo_signal @@ -97,7 +97,7 @@ def test_apply_for_all_except(): route_es1_as1 = topologyhelper.get_route_by_signal_names(topology, "60ES1", "60AS1") asyncio.run(interlockinghelper.set_route(interlocking, route_es1_as1, True, "RB101")) route_bs2_bs3 = topologyhelper.get_route_by_signal_names(topology, "60BS2", "60BS3") - asyncio.run(interlockinghelper.set_route(interlocking, route_bs2_bs3, True, "RB101")) + asyncio.run(interlockinghelper.set_route(interlocking, route_bs2_bs3, True, "RB102")) signal_es1 = interlockinghelper.get_interlocking_signal_by_name(interlocking, "60ES1").yaramo_signal signal_bs2 = interlockinghelper.get_interlocking_signal_by_name(interlocking, "60BS2").yaramo_signal