jmri_core.beans.turnout

Turnout — a layout switch (point) with CLOSED/THROWN states.

 1"""``Turnout`` — a layout switch (point) with CLOSED/THROWN states."""
 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 TurnoutFeedback, TurnoutState
 9
10
11@dataclass(frozen=True, slots=True)
12class TurnoutStateChange:
13    """Payload emitted on :attr:`Turnout.state_changed`."""
14
15    kind: str          # "commanded" or "known"
16    previous: TurnoutState
17    current: TurnoutState
18
19
20class Turnout(AbstractNamedBean):
21    """Layout turnout (point switch).
22
23    A turnout has two state values:
24
25    * ``commanded_state`` — what we last *asked* the layout to do.
26    * ``known_state``     — what the layout reports back.
27
28    Drivers update ``known_state`` when their feedback channel reports a
29    change; this may differ from ``commanded_state`` until the move completes
30    or if motorised feedback says the switch ended up in an INCONSISTENT pos.
31    """
32
33    __slots__ = (
34        "_commanded_state",
35        "_known_state",
36        "feedback_mode",
37        "inverted",
38        "locked",
39    )
40
41    type_letter = "T"
42
43    def __init__(self, system_name: str, user_name: str | None = None) -> None:
44        super().__init__(system_name, user_name)
45        self._commanded_state: TurnoutState = TurnoutState.UNKNOWN
46        self._known_state: TurnoutState = TurnoutState.UNKNOWN
47        self.feedback_mode: TurnoutFeedback = TurnoutFeedback.DIRECT
48        self.inverted: bool = False
49        self.locked: bool = False
50
51    @property
52    def commanded_state(self) -> TurnoutState:
53        return self._commanded_state
54
55    @property
56    def known_state(self) -> TurnoutState:
57        return self._known_state
58
59    async def set_commanded_state(self, state: TurnoutState) -> None:
60        """Ask the layout to throw or close the switch.
61
62        The base implementation only updates the in-memory commanded state
63        and emits the signal. Driver subclasses override this to push a
64        hardware message *and then* call :meth:`super().set_commanded_state`.
65        """
66        prev = self._commanded_state
67        if prev == state:
68            return
69        self._commanded_state = state
70        await self.state_changed.emit(TurnoutStateChange("commanded", prev, state))
71        # For DIRECT feedback, known state mirrors commanded immediately.
72        if self.feedback_mode == TurnoutFeedback.DIRECT:
73            await self._set_known_state(state)
74
75    async def _set_known_state(self, state: TurnoutState) -> None:
76        """Driver hook: layout reported this state."""
77        prev = self._known_state
78        if prev == state:
79            return
80        self._known_state = state
81        await self.state_changed.emit(TurnoutStateChange("known", prev, state))
82
83
84class TurnoutManager(Manager[Turnout]):
85    type_letter = "T"
86
87    def _create(self, system_name: str) -> Turnout:
88        return Turnout(system_name)
@dataclass(frozen=True, slots=True)
class TurnoutStateChange:
12@dataclass(frozen=True, slots=True)
13class TurnoutStateChange:
14    """Payload emitted on :attr:`Turnout.state_changed`."""
15
16    kind: str          # "commanded" or "known"
17    previous: TurnoutState
18    current: TurnoutState

Payload emitted on Turnout.state_changed.

TurnoutStateChange( kind: str, previous: jmri_core.state.TurnoutState, current: jmri_core.state.TurnoutState)
kind: str
class Turnout(jmri_core.bean.AbstractNamedBean):
21class Turnout(AbstractNamedBean):
22    """Layout turnout (point switch).
23
24    A turnout has two state values:
25
26    * ``commanded_state`` — what we last *asked* the layout to do.
27    * ``known_state``     — what the layout reports back.
28
29    Drivers update ``known_state`` when their feedback channel reports a
30    change; this may differ from ``commanded_state`` until the move completes
31    or if motorised feedback says the switch ended up in an INCONSISTENT pos.
32    """
33
34    __slots__ = (
35        "_commanded_state",
36        "_known_state",
37        "feedback_mode",
38        "inverted",
39        "locked",
40    )
41
42    type_letter = "T"
43
44    def __init__(self, system_name: str, user_name: str | None = None) -> None:
45        super().__init__(system_name, user_name)
46        self._commanded_state: TurnoutState = TurnoutState.UNKNOWN
47        self._known_state: TurnoutState = TurnoutState.UNKNOWN
48        self.feedback_mode: TurnoutFeedback = TurnoutFeedback.DIRECT
49        self.inverted: bool = False
50        self.locked: bool = False
51
52    @property
53    def commanded_state(self) -> TurnoutState:
54        return self._commanded_state
55
56    @property
57    def known_state(self) -> TurnoutState:
58        return self._known_state
59
60    async def set_commanded_state(self, state: TurnoutState) -> None:
61        """Ask the layout to throw or close the switch.
62
63        The base implementation only updates the in-memory commanded state
64        and emits the signal. Driver subclasses override this to push a
65        hardware message *and then* call :meth:`super().set_commanded_state`.
66        """
67        prev = self._commanded_state
68        if prev == state:
69            return
70        self._commanded_state = state
71        await self.state_changed.emit(TurnoutStateChange("commanded", prev, state))
72        # For DIRECT feedback, known state mirrors commanded immediately.
73        if self.feedback_mode == TurnoutFeedback.DIRECT:
74            await self._set_known_state(state)
75
76    async def _set_known_state(self, state: TurnoutState) -> None:
77        """Driver hook: layout reported this state."""
78        prev = self._known_state
79        if prev == state:
80            return
81        self._known_state = state
82        await self.state_changed.emit(TurnoutStateChange("known", prev, state))

Layout turnout (point switch).

A turnout has two state values:

Drivers update known_state when their feedback channel reports a change; this may differ from commanded_state until the move completes or if motorised feedback says the switch ended up in an INCONSISTENT pos.

Turnout(system_name: str, user_name: str | None = None)
44    def __init__(self, system_name: str, user_name: str | None = None) -> None:
45        super().__init__(system_name, user_name)
46        self._commanded_state: TurnoutState = TurnoutState.UNKNOWN
47        self._known_state: TurnoutState = TurnoutState.UNKNOWN
48        self.feedback_mode: TurnoutFeedback = TurnoutFeedback.DIRECT
49        self.inverted: bool = False
50        self.locked: bool = False
type_letter = 'T'
inverted: bool
locked: bool
commanded_state: jmri_core.state.TurnoutState
52    @property
53    def commanded_state(self) -> TurnoutState:
54        return self._commanded_state
known_state: jmri_core.state.TurnoutState
56    @property
57    def known_state(self) -> TurnoutState:
58        return self._known_state
async def set_commanded_state(self, state: jmri_core.state.TurnoutState) -> None:
60    async def set_commanded_state(self, state: TurnoutState) -> None:
61        """Ask the layout to throw or close the switch.
62
63        The base implementation only updates the in-memory commanded state
64        and emits the signal. Driver subclasses override this to push a
65        hardware message *and then* call :meth:`super().set_commanded_state`.
66        """
67        prev = self._commanded_state
68        if prev == state:
69            return
70        self._commanded_state = state
71        await self.state_changed.emit(TurnoutStateChange("commanded", prev, state))
72        # For DIRECT feedback, known state mirrors commanded immediately.
73        if self.feedback_mode == TurnoutFeedback.DIRECT:
74            await self._set_known_state(state)

Ask the layout to throw or close the switch.

The base implementation only updates the in-memory commanded state and emits the signal. Driver subclasses override this to push a hardware message and then call super().set_commanded_state().

85class TurnoutManager(Manager[Turnout]):
86    type_letter = "T"
87
88    def _create(self, system_name: str) -> Turnout:
89        return Turnout(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 = 'T'