diff --git a/interlocking/infrastructureprovider/infrastructureprovider.py b/interlocking/infrastructureprovider/infrastructureprovider.py index 34ba42e..a6d66ef 100644 --- a/interlocking/infrastructureprovider/infrastructureprovider.py +++ b/interlocking/infrastructureprovider/infrastructureprovider.py @@ -1,9 +1,43 @@ +import logging from abc import ABC, abstractmethod -import asyncio +from yaramo.model import Node, Signal, Topology + class InfrastructureProvider(ABC): - def __init__(self): + 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 + 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 +45,21 @@ def __init__(self): # Point Interaction # + def is_point_covered(self, yaramo_point: Node): + if not self.apply_for_points: + return False + point_id = yaramo_point.uuid[-5:] + 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 + @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,10 +68,22 @@ async def turn_point(self, yaramo_point, 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_aspect(self, yaramo_signal: Signal, target_state: str): + if self.is_signal_covered(yaramo_signal): + return await self.set_signal_aspect(yaramo_signal, target_state) + # return True to skip this call and not prevent successfully turning of signal. + return True + @abstractmethod - async def set_signal_aspect(self, yaramo_signal, target_aspect): - """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"`. + async def set_signal_aspect(self, yaramo_signal: Signal, target_aspect: str): + """This method will be called when the interlocking controller wants to change the signal-aspect of a specific signal. + `yaramo_signal` corresponds to the identifier of the signal in the yaramo model; `target_aspect` is one of `"halt"` and `"go"`. """ pass @@ -45,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/infrastructureprovider/logginginfrastructureprovider.py b/interlocking/infrastructureprovider/logginginfrastructureprovider.py index 2427423..077ff63 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_aspect(self, yaramo_signal, target_aspect): logging.info(f"{time.strftime('%X')} Set signal {yaramo_signal.name} to {target_aspect}") diff --git a/interlocking/infrastructureprovider/randomwaitinfrastructureprovider.py b/interlocking/infrastructureprovider/randomwaitinfrastructureprovider.py index 1436c16..6fd638e 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 1265baa..0e98548 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_aspect(self, yaramo_signal, target_aspect): diff --git a/interlocking/interlockingcontroller/pointcontroller.py b/interlocking/interlockingcontroller/pointcontroller.py index 89b1990..3386147 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 from interlocking.model.helper import Settings from interlocking.infrastructureprovider import InfrastructureProvider from .flankprotectioncontroller import FlankProtectionController @@ -11,7 +11,7 @@ class PointController(object): def __init__(self, signal_controller: SignalController, infrastructure_providers: list[InfrastructureProvider], settings: Settings): - self.points = None + self.points: dict[str, Point] = {} self.infrastructure_providers = infrastructure_providers self.settings = settings self.flank_protection_controller = FlankProtectionController(self, signal_controller) @@ -61,11 +61,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 70ae8a5..69e97ee 100644 --- a/interlocking/interlockingcontroller/signalcontroller.py +++ b/interlocking/interlockingcontroller/signalcontroller.py @@ -35,12 +35,12 @@ async def set_signal_aspect(self, signal, signal_aspect): results = [] for infrastructure_provider in self.infrastructure_providers: - results.append(await infrastructure_provider.set_signal_aspect(signal.yaramo_signal, signal_aspect)) + results.append(await infrastructure_provider.call_set_signal_aspect(signal.yaramo_signal, signal_aspect)) # 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_aspect(signal.yaramo_signal, state))) # if all(list(map(lambda task: task.result(), tasks))): if all(results): signal.signal_aspect = signal_aspect diff --git a/interlocking/interlockinginterface.py b/interlocking/interlockinginterface.py index 673aa13..2e5eea0 100644 --- a/interlocking/interlockinginterface.py +++ b/interlocking/interlockinginterface.py @@ -1,6 +1,9 @@ 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 +from interlockinglogicmonitor import InterlockingLogicMonitor +from yaramo.model import Route as YaramoRoute import asyncio import time import logging @@ -8,18 +11,22 @@ 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.infrastructure_providers: list[InfrastructureProvider] = infrastructure_providers self.settings = settings + self.interlocking_logic_monitor = interlocking_logic_monitor self.signal_controller = SignalController(self.infrastructure_providers) self.point_controller = PointController(self.signal_controller, self.infrastructure_providers, self.settings) 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 @@ -38,6 +45,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: @@ -116,13 +129,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,7 +158,10 @@ 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 + 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): @@ -155,21 +178,63 @@ 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.signal_controller.free_route(route, train_id) self.active_routes.remove(route) + route.used_by = None + 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) + 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, train_id) self.active_routes.remove(route) + route.used_by = None + 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: 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 and \ + last_route.start_signal.yaramo_signal.name != new_route.end_signal.name 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/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: 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" } diff --git a/test/flank-protection_test.py b/test/flank-protection_test.py index 42248a3..1f219a0 100644 --- a/test/flank-protection_test.py +++ b/test/flank-protection_test.py @@ -51,7 +51,7 @@ def test_example_1(): assert point.orientation == "left" assert point.state == OccupancyState.RESERVED - flank_protection_signal = topologyhelper.get_interlocking_signal_by_name(interlocking, "99N3") + flank_protection_signal = interlockinghelper.get_interlocking_signal_by_name(interlocking, "99N3") assert flank_protection_signal.signal_aspect == "halt" assert track_operations_ip.was_signal_set_to_aspect(flank_protection_signal.yaramo_signal, "halt") assert flank_protection_signal.state == OccupancyState.FLANK_PROTECTION @@ -112,7 +112,7 @@ def test_example_3(): assert flank_protection_point.state == OccupancyState.FLANK_PROTECTION assert "RB101" in flank_protection_point.used_by - flank_protection_signal = topologyhelper.get_interlocking_signal_by_name(interlocking, "60E2") + flank_protection_signal = interlockinghelper.get_interlocking_signal_by_name(interlocking, "60E2") assert flank_protection_signal.signal_aspect == "halt" assert track_operations_ip.was_signal_set_to_aspect(flank_protection_signal.yaramo_signal, "halt") assert flank_protection_signal.state == OccupancyState.FLANK_PROTECTION @@ -133,6 +133,7 @@ def test_example_4(): asyncio.run(interlockinghelper.set_route(interlocking, route, True, "RB102")) interlocking.print_state() + if __name__ == "__main__": logging.basicConfig(level=logging.DEBUG) test_example_1() 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 62bd744..2869da0 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/topologyhelper.py b/test/helper/topologyhelper.py index 6c2b065..18f1bfd 100644 --- a/test/helper/topologyhelper.py +++ b/test/helper/topologyhelper.py @@ -1,7 +1,6 @@ from planpro_importer.reader import PlanProReader from railwayroutegenerator.routegenerator import RouteGenerator -from interlocking.interlockinginterface import Interlocking -from yaramo.model import Topology, Route +from yaramo.model import Topology, Route, Signal, Node def get_topology_from_planpro_file(file_name: str): @@ -31,14 +30,15 @@ def get_route_by_signal_names(topology: Topology, start_signal_name: str, end_si return route -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: +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 -async def set_route(interlocking: Interlocking, route: Route, should_be_able_to_set: bool, train_id: str): - set_route_result = await interlocking.set_route(route, train_id) - assert (set_route_result.success == should_be_able_to_set) - return set_route_result +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 \ No newline at end of file diff --git a/test/helper/trackoperationsinfrastructureprovider.py b/test/helper/trackoperationsinfrastructureprovider.py new file mode 100644 index 0000000..e6b6270 --- /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_aspect(self, yaramo_signal: Signal, target_aspect: str): + await super().set_signal_aspect(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..d172793 --- /dev/null +++ b/test/infrastructureprovider_test.py @@ -0,0 +1,152 @@ +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 + + +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.") + + +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"], + 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, "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 + 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, "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 + 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") + + +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/interlocking-monitor_test.py b/test/interlocking-monitor_test.py new file mode 100644 index 0000000..e7e3279 --- /dev/null +++ b/test/interlocking-monitor_test.py @@ -0,0 +1,63 @@ +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) + + route_bs5_bs6 = topologyhelper.get_route_by_signal_names(topology, "60BS5", "60BS6") + assert not ilm.route_results[route_bs5_bs6.uuid].was_set + assert not ilm.route_results[route_bs5_bs6.uuid].was_freed + assert ilm.route_results[route_bs5_bs6.uuid].get_coverage() == 0.0 + + asyncio.run(interlocking.set_route(route_bs5_bs6, "RB101")) + assert ilm.route_results[route_bs5_bs6.uuid].was_set + assert ilm.route_results[route_bs5_bs6.uuid].get_coverage() == 1/3 + + # Drive Route + ip = interlocking.infrastructure_providers[0] + asyncio.run(ip.tds_count_in("b8e69-2", "RB101")) + asyncio.run(ip.tds_count_out("b8e69-2", "RB101")) + + interlocking.free_route(route_bs5_bs6, "RB101") + assert ilm.route_results[route_bs5_bs6.uuid].was_freed + assert ilm.route_results[route_bs5_bs6.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 diff --git a/test/interlocking_test.py b/test/interlocking_test.py index bbe379b..72e96bf 100644 --- a/test/interlocking_test.py +++ b/test/interlocking_test.py @@ -1,43 +1,46 @@ -#from .helper import topologyhelper, interlockinghelper -import helper +from .helper import topologyhelper, interlockinghelper from interlocking.model import OccupancyState from interlocking.infrastructureprovider import RandomWaitInfrastructureProvider from yaramo.model import Route as YaramoRoute, Signal as YaramoSignal, Edge as YaramoEdge, Node as YaramoNode import asyncio -import logging +import unittest def test_reset(): - topology = helper.get_topology_from_planpro_file("./complex-example.ppxml") - interlocking = helper.get_interlocking(topology) - route_1 = helper.get_route_by_signal_names(topology, "60BS1", "60BS2") - asyncio.run(helper.set_route(interlocking, route_1, True, "RB101")) - interlocking.print_state() - route_2 = helper.get_route_by_signal_names(topology, "60ES2", "60AS3") - asyncio.run(helper.set_route(interlocking, route_2, True, "RB102")) + 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") + asyncio.run(interlockinghelper.set_route(interlocking, route_1, True, "RB101")) + interlocking.print_state() + route_2 = topologyhelper.get_route_by_signal_names(topology, "60ES2", "60AS3") + asyncio.run(interlockinghelper.set_route(interlocking, route_2, True, "RB102")) + + interlockinghelper.test_track(interlocking, "94742-0", "RB101", OccupancyState.RESERVED) + interlockinghelper.test_track(interlocking, "b8e69-3", "RB101", OccupancyState.RESERVED_OVERLAP) + interlockinghelper.test_track(interlocking, "a8f44-0", "RB101", OccupancyState.FREE) + interlockinghelper.test_signal(interlocking, "60BS1", "RB101", "go", OccupancyState.RESERVED) + interlockinghelper.test_point(interlocking, "d43f9", "RB101", "left", OccupancyState.RESERVED) - helper.test_track(interlocking, "94742-0", "RB101", OccupancyState.RESERVED) - helper.test_track(interlocking, "b8e69-3", "RB101", OccupancyState.RESERVED_OVERLAP) - helper.test_track(interlocking, "a8f44-0", "RB101", OccupancyState.FREE) - helper.test_signal(interlocking, "60BS1", "RB101", "go", OccupancyState.RESERVED) - helper.test_point(interlocking, "d43f9", "RB101", "left", OccupancyState.RESERVED) + interlockinghelper.test_track(interlocking, "3a70a-0", "RB102", OccupancyState.RESERVED) + interlockinghelper.test_signal(interlocking, "60ES2", "RB102", "go", OccupancyState.RESERVED) + interlockinghelper.test_point(interlocking, "fd73d", "RB102", "right", OccupancyState.RESERVED) - helper.test_track(interlocking, "3a70a-0", "RB102", OccupancyState.RESERVED) - helper.test_signal(interlocking, "60ES2", "RB101", "go", OccupancyState.RESERVED) - helper.test_point(interlocking, "fd73d", "RB102", "right", OccupancyState.RESERVED) + assert len(interlocking.active_routes) == 2 asyncio.run(interlocking.reset()) - helper.test_track(interlocking, "94742-0", "RB101", OccupancyState.FREE) - helper.test_track(interlocking, "b8e69-3", "RB101", OccupancyState.FREE) - helper.test_track(interlocking, "a8f44-0", "RB101", OccupancyState.FREE) - helper.test_signal(interlocking, "60BS1", "RB101", "halt", OccupancyState.FREE) - helper.test_point(interlocking, "d43f9", "RB101", "undefined", OccupancyState.FREE) + interlockinghelper.test_track(interlocking, "94742-0", "RB101", OccupancyState.FREE) + interlockinghelper.test_track(interlocking, "b8e69-3", "RB101", OccupancyState.FREE) + interlockinghelper.test_track(interlocking, "a8f44-0", "RB101", OccupancyState.FREE) + interlockinghelper.test_signal(interlocking, "60BS1", "RB101", "halt", OccupancyState.FREE) + interlockinghelper.test_point(interlocking, "d43f9", "RB101", "undefined", OccupancyState.FREE) + + interlockinghelper.test_track(interlocking, "3a70a-0", "RB102", OccupancyState.FREE) + interlockinghelper.test_signal(interlocking, "60ES2", "RB102", "halt", OccupancyState.FREE) + interlockinghelper.test_point(interlocking, "fd73d", "RB102", "undefined", OccupancyState.FREE) - helper.test_track(interlocking, "3a70a-0", "RB102", OccupancyState.FREE) - helper.test_signal(interlocking, "60ES2", "RB101", "halt", OccupancyState.FREE) - helper.test_point(interlocking, "fd73d", "RB102", "undefined", OccupancyState.FREE) + assert len(interlocking.active_routes) == 0 def test_print_state(): @@ -214,7 +217,132 @@ def test_failing_signal(): asyncio.run(interlockinghelper.set_route(interlocking, route_bs2_bs3, True, "RB101")) -if __name__ == "__main__": - logging.basicConfig(level=logging.DEBUG) - test_reset() +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")) + + +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): + 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} was not set.") + + 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 TestResetRouteExceptions(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} was not set.") + + 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() 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 + + + + + + + + diff --git a/test_interlocking.py b/test_interlocking.py index 5c1d838..6fd24f6 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 @@ -114,15 +118,24 @@ async def drive_some_route_forwards(): 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 set_route("60ES2", "60AS4", True, "RB101") - await set_route("60ES2", "60AS3", False, "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, "RB101") + await set_route("60ES2", "60AS3", 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 +148,7 @@ async def drive_some_route_forwards(): if __name__ == "__main__": - logging.basicConfig(level=logging.INFO) + logging.basicConfig(level=logging.DEBUG) asyncio.run(test_01())