jmri_core.beans.signal_mast

SignalMast — a logical signal that exposes aspect names defined by a signal system (e.g. "Clear", "Approach Medium").

Where SignalHead uses a fixed enum of colours, a SignalMast's set of valid aspects is open-ended and loaded from XML at runtime. The mast is composed of one or more underlying signal heads; here we only carry the logical aspect — the head-driving logic lives in a Stage 1.5 module.

 1"""``SignalMast`` — a logical signal that exposes aspect *names* defined by
 2a signal system (e.g. ``"Clear"``, ``"Approach Medium"``).
 3
 4Where :class:`SignalHead` uses a fixed enum of colours, a SignalMast's set of
 5valid aspects is open-ended and loaded from XML at runtime. The mast is
 6composed of one or more underlying signal heads; here we only carry the
 7logical aspect — the head-driving logic lives in a Stage 1.5 module.
 8"""
 9from __future__ import annotations
10
11from dataclasses import dataclass
12
13from jmri_core.bean import AbstractNamedBean
14from jmri_core.manager import Manager
15
16
17@dataclass(frozen=True, slots=True)
18class SignalMastChange:
19    previous: str
20    current: str
21
22
23class SignalMast(AbstractNamedBean):
24    """Logical signal exposing named aspects from a signal system.
25
26    Attributes:
27
28    * ``aspect`` — current displayed aspect name; empty string means unknown.
29    * ``signal_system`` — the name of the signal-system XML this mast follows
30      (e.g. ``"basic"``, ``"AAR-1946"``). Resolved against
31      ``xml/signals/<system>/`` at load time by persistence code.
32    * ``held`` — forces ``"Stop"`` (or the system's most-restrictive aspect).
33    """
34
35    __slots__ = ("_aspect", "_held", "signal_system")
36
37    type_letter = "F"  # JMRI uses "F" for SignalMast system-type letter
38
39    def __init__(
40        self,
41        system_name: str,
42        signal_system: str = "",
43        user_name: str | None = None,
44    ) -> None:
45        super().__init__(system_name, user_name)
46        self.signal_system = signal_system
47        self._aspect: str = ""
48        self._held: bool = False
49
50    @property
51    def aspect(self) -> str:
52        return self._aspect
53
54    @property
55    def held(self) -> bool:
56        return self._held
57
58    async def set_aspect(self, aspect: str) -> None:
59        prev = self._aspect
60        if prev == aspect:
61            return
62        self._aspect = aspect
63        await self.state_changed.emit(SignalMastChange(prev, aspect))
64
65    async def set_held(self, held: bool) -> None:
66        # Held flips a flag; aspect display is computed elsewhere by the
67        # signal-mast logic. We emit a state-changed so listeners know
68        # something changed even if `aspect` didn't.
69        if held == self._held:
70            return
71        self._held = held
72        await self.state_changed.emit(SignalMastChange(self._aspect, self._aspect))
73
74
75class SignalMastManager(Manager[SignalMast]):
76    type_letter = "F"
77
78    def _create(self, system_name: str) -> SignalMast:
79        return SignalMast(system_name)
@dataclass(frozen=True, slots=True)
class SignalMastChange:
18@dataclass(frozen=True, slots=True)
19class SignalMastChange:
20    previous: str
21    current: str
SignalMastChange(previous: str, current: str)
previous: str
current: str
class SignalMast(jmri_core.bean.AbstractNamedBean):
24class SignalMast(AbstractNamedBean):
25    """Logical signal exposing named aspects from a signal system.
26
27    Attributes:
28
29    * ``aspect`` — current displayed aspect name; empty string means unknown.
30    * ``signal_system`` — the name of the signal-system XML this mast follows
31      (e.g. ``"basic"``, ``"AAR-1946"``). Resolved against
32      ``xml/signals/<system>/`` at load time by persistence code.
33    * ``held`` — forces ``"Stop"`` (or the system's most-restrictive aspect).
34    """
35
36    __slots__ = ("_aspect", "_held", "signal_system")
37
38    type_letter = "F"  # JMRI uses "F" for SignalMast system-type letter
39
40    def __init__(
41        self,
42        system_name: str,
43        signal_system: str = "",
44        user_name: str | None = None,
45    ) -> None:
46        super().__init__(system_name, user_name)
47        self.signal_system = signal_system
48        self._aspect: str = ""
49        self._held: bool = False
50
51    @property
52    def aspect(self) -> str:
53        return self._aspect
54
55    @property
56    def held(self) -> bool:
57        return self._held
58
59    async def set_aspect(self, aspect: str) -> None:
60        prev = self._aspect
61        if prev == aspect:
62            return
63        self._aspect = aspect
64        await self.state_changed.emit(SignalMastChange(prev, aspect))
65
66    async def set_held(self, held: bool) -> None:
67        # Held flips a flag; aspect display is computed elsewhere by the
68        # signal-mast logic. We emit a state-changed so listeners know
69        # something changed even if `aspect` didn't.
70        if held == self._held:
71            return
72        self._held = held
73        await self.state_changed.emit(SignalMastChange(self._aspect, self._aspect))

Logical signal exposing named aspects from a signal system.

Attributes:

  • aspect — current displayed aspect name; empty string means unknown.
  • signal_system — the name of the signal-system XML this mast follows (e.g. "basic", "AAR-1946"). Resolved against xml/signals/<system>/ at load time by persistence code.
  • held — forces "Stop" (or the system's most-restrictive aspect).
SignalMast( system_name: str, signal_system: str = '', user_name: str | None = None)
40    def __init__(
41        self,
42        system_name: str,
43        signal_system: str = "",
44        user_name: str | None = None,
45    ) -> None:
46        super().__init__(system_name, user_name)
47        self.signal_system = signal_system
48        self._aspect: str = ""
49        self._held: bool = False
type_letter = 'F'
signal_system
aspect: str
51    @property
52    def aspect(self) -> str:
53        return self._aspect
held: bool
55    @property
56    def held(self) -> bool:
57        return self._held
async def set_aspect(self, aspect: str) -> None:
59    async def set_aspect(self, aspect: str) -> None:
60        prev = self._aspect
61        if prev == aspect:
62            return
63        self._aspect = aspect
64        await self.state_changed.emit(SignalMastChange(prev, aspect))
async def set_held(self, held: bool) -> None:
66    async def set_held(self, held: bool) -> None:
67        # Held flips a flag; aspect display is computed elsewhere by the
68        # signal-mast logic. We emit a state-changed so listeners know
69        # something changed even if `aspect` didn't.
70        if held == self._held:
71            return
72        self._held = held
73        await self.state_changed.emit(SignalMastChange(self._aspect, self._aspect))
76class SignalMastManager(Manager[SignalMast]):
77    type_letter = "F"
78
79    def _create(self, system_name: str) -> SignalMast:
80        return SignalMast(system_name)

Per-type bean registry. Subclass and override _create().

Attributes set on the class describe the bean type:

  • type_letter — single uppercase letter the bean class owns (e.g. "T" for Turnout). Must be set by subclass.
  • system_prefix — connection prefix (e.g. "L"). Set per-instance because one bean class can have many connection-specific managers.
type_letter = 'F'