jmri_core.beans.signal_head

SignalHead — a single signal-light unit with a fixed appearance set.

 1"""``SignalHead`` — a single signal-light unit with a fixed appearance set."""
 2from __future__ import annotations
 3
 4from dataclasses import dataclass
 5
 6from jmri_core.bean import AbstractNamedBean
 7from jmri_core.manager import Manager
 8from jmri_core.state import SignalAppearance
 9
10
11@dataclass(frozen=True, slots=True)
12class SignalHeadChange:
13    previous: SignalAppearance
14    current: SignalAppearance
15
16
17class SignalHead(AbstractNamedBean):
18    """A single signal head (one physical signal, one face).
19
20    The ``held`` flag forces the head to RED regardless of its commanded
21    appearance — used by the dispatcher to override aspect logic.
22    """
23
24    __slots__ = ("_appearance", "_held")
25
26    type_letter = "H"
27
28    def __init__(self, system_name: str, user_name: str | None = None) -> None:
29        super().__init__(system_name, user_name)
30        self._appearance: SignalAppearance = SignalAppearance.DARK
31        self._held: bool = False
32
33    @property
34    def appearance(self) -> SignalAppearance:
35        return SignalAppearance.RED if self._held else self._appearance
36
37    @property
38    def commanded_appearance(self) -> SignalAppearance:
39        """The appearance ignoring ``held`` — what was last set explicitly."""
40        return self._appearance
41
42    @property
43    def held(self) -> bool:
44        return self._held
45
46    async def set_appearance(self, appearance: SignalAppearance) -> None:
47        prev_effective = self.appearance
48        self._appearance = appearance
49        new_effective = self.appearance
50        if prev_effective != new_effective:
51            await self.state_changed.emit(SignalHeadChange(prev_effective, new_effective))
52
53    async def set_held(self, held: bool) -> None:
54        if held == self._held:
55            return
56        prev_effective = self.appearance
57        self._held = held
58        new_effective = self.appearance
59        if prev_effective != new_effective:
60            await self.state_changed.emit(SignalHeadChange(prev_effective, new_effective))
61
62
63class SignalHeadManager(Manager[SignalHead]):
64    type_letter = "H"
65
66    def _create(self, system_name: str) -> SignalHead:
67        return SignalHead(system_name)
@dataclass(frozen=True, slots=True)
class SignalHeadChange:
12@dataclass(frozen=True, slots=True)
13class SignalHeadChange:
14    previous: SignalAppearance
15    current: SignalAppearance
class SignalHead(jmri_core.bean.AbstractNamedBean):
18class SignalHead(AbstractNamedBean):
19    """A single signal head (one physical signal, one face).
20
21    The ``held`` flag forces the head to RED regardless of its commanded
22    appearance — used by the dispatcher to override aspect logic.
23    """
24
25    __slots__ = ("_appearance", "_held")
26
27    type_letter = "H"
28
29    def __init__(self, system_name: str, user_name: str | None = None) -> None:
30        super().__init__(system_name, user_name)
31        self._appearance: SignalAppearance = SignalAppearance.DARK
32        self._held: bool = False
33
34    @property
35    def appearance(self) -> SignalAppearance:
36        return SignalAppearance.RED if self._held else self._appearance
37
38    @property
39    def commanded_appearance(self) -> SignalAppearance:
40        """The appearance ignoring ``held`` — what was last set explicitly."""
41        return self._appearance
42
43    @property
44    def held(self) -> bool:
45        return self._held
46
47    async def set_appearance(self, appearance: SignalAppearance) -> None:
48        prev_effective = self.appearance
49        self._appearance = appearance
50        new_effective = self.appearance
51        if prev_effective != new_effective:
52            await self.state_changed.emit(SignalHeadChange(prev_effective, new_effective))
53
54    async def set_held(self, held: bool) -> None:
55        if held == self._held:
56            return
57        prev_effective = self.appearance
58        self._held = held
59        new_effective = self.appearance
60        if prev_effective != new_effective:
61            await self.state_changed.emit(SignalHeadChange(prev_effective, new_effective))

A single signal head (one physical signal, one face).

The held flag forces the head to RED regardless of its commanded appearance — used by the dispatcher to override aspect logic.

SignalHead(system_name: str, user_name: str | None = None)
29    def __init__(self, system_name: str, user_name: str | None = None) -> None:
30        super().__init__(system_name, user_name)
31        self._appearance: SignalAppearance = SignalAppearance.DARK
32        self._held: bool = False
type_letter = 'H'
appearance: jmri_core.state.SignalAppearance
34    @property
35    def appearance(self) -> SignalAppearance:
36        return SignalAppearance.RED if self._held else self._appearance
commanded_appearance: jmri_core.state.SignalAppearance
38    @property
39    def commanded_appearance(self) -> SignalAppearance:
40        """The appearance ignoring ``held`` — what was last set explicitly."""
41        return self._appearance

The appearance ignoring held — what was last set explicitly.

held: bool
43    @property
44    def held(self) -> bool:
45        return self._held
async def set_appearance(self, appearance: jmri_core.state.SignalAppearance) -> None:
47    async def set_appearance(self, appearance: SignalAppearance) -> None:
48        prev_effective = self.appearance
49        self._appearance = appearance
50        new_effective = self.appearance
51        if prev_effective != new_effective:
52            await self.state_changed.emit(SignalHeadChange(prev_effective, new_effective))
async def set_held(self, held: bool) -> None:
54    async def set_held(self, held: bool) -> None:
55        if held == self._held:
56            return
57        prev_effective = self.appearance
58        self._held = held
59        new_effective = self.appearance
60        if prev_effective != new_effective:
61            await self.state_changed.emit(SignalHeadChange(prev_effective, new_effective))
64class SignalHeadManager(Manager[SignalHead]):
65    type_letter = "H"
66
67    def _create(self, system_name: str) -> SignalHead:
68        return SignalHead(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 = 'H'