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)
previous: jmri_core.state.TurnoutState
current: jmri_core.state.TurnoutState
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:
commanded_state— what we last asked the layout to do.known_state— what the layout reports back.
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
feedback_mode: jmri_core.state.TurnoutFeedback
commanded_state: jmri_core.state.TurnoutState
known_state: jmri_core.state.TurnoutState
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().
Inherited Members
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.