jmri_core.beans.light

Light — a controllable layout light (yard light, building, etc.).

A light may be plain on/off or dimmable. intensity is in the range [0.0, 1.0]; when set to a non-extreme value, state becomes INTERMEDIATE.

 1"""``Light`` — a controllable layout light (yard light, building, etc.).
 2
 3A light may be plain on/off or dimmable. ``intensity`` is in the range
 4``[0.0, 1.0]``; when set to a non-extreme value, ``state`` becomes
 5``INTERMEDIATE``.
 6"""
 7from __future__ import annotations
 8
 9from dataclasses import dataclass
10
11from jmri_core.bean import AbstractNamedBean
12from jmri_core.manager import Manager
13from jmri_core.state import LightState
14
15
16@dataclass(frozen=True, slots=True)
17class LightChange:
18    previous_state: LightState
19    current_state: LightState
20    intensity: float
21
22
23class Light(AbstractNamedBean):
24    """Layout light. Supports binary or dimmable control."""
25
26    __slots__ = ("_intensity", "_state", "supports_dimming")
27
28    type_letter = "L"
29
30    def __init__(self, system_name: str, user_name: str | None = None) -> None:
31        super().__init__(system_name, user_name)
32        self._state: LightState = LightState.UNKNOWN
33        self._intensity: float = 0.0
34        self.supports_dimming: bool = False
35
36    @property
37    def state(self) -> LightState:
38        return self._state
39
40    @property
41    def intensity(self) -> float:
42        return self._intensity
43
44    async def set_state(self, state: LightState) -> None:
45        prev = self._state
46        if state == LightState.ON:
47            self._intensity = 1.0
48        elif state == LightState.OFF:
49            self._intensity = 0.0
50        self._state = state
51        if prev != state:
52            await self.state_changed.emit(LightChange(prev, state, self._intensity))
53
54    async def set_intensity(self, intensity: float) -> None:
55        if not 0.0 <= intensity <= 1.0:
56            raise ValueError(f"intensity must be in [0,1], got {intensity}")
57        if not self.supports_dimming and intensity not in (0.0, 1.0):
58            raise ValueError("this light does not support dimming")
59        prev = self._state
60        self._intensity = intensity
61        if intensity == 0.0:
62            self._state = LightState.OFF
63        elif intensity == 1.0:
64            self._state = LightState.ON
65        else:
66            self._state = LightState.INTERMEDIATE
67        await self.state_changed.emit(LightChange(prev, self._state, intensity))
68
69
70class LightManager(Manager[Light]):
71    type_letter = "L"
72
73    def _create(self, system_name: str) -> Light:
74        return Light(system_name)
@dataclass(frozen=True, slots=True)
class LightChange:
17@dataclass(frozen=True, slots=True)
18class LightChange:
19    previous_state: LightState
20    current_state: LightState
21    intensity: float
LightChange( previous_state: jmri_core.state.LightState, current_state: jmri_core.state.LightState, intensity: float)
previous_state: jmri_core.state.LightState
intensity: float
class Light(jmri_core.bean.AbstractNamedBean):
24class Light(AbstractNamedBean):
25    """Layout light. Supports binary or dimmable control."""
26
27    __slots__ = ("_intensity", "_state", "supports_dimming")
28
29    type_letter = "L"
30
31    def __init__(self, system_name: str, user_name: str | None = None) -> None:
32        super().__init__(system_name, user_name)
33        self._state: LightState = LightState.UNKNOWN
34        self._intensity: float = 0.0
35        self.supports_dimming: bool = False
36
37    @property
38    def state(self) -> LightState:
39        return self._state
40
41    @property
42    def intensity(self) -> float:
43        return self._intensity
44
45    async def set_state(self, state: LightState) -> None:
46        prev = self._state
47        if state == LightState.ON:
48            self._intensity = 1.0
49        elif state == LightState.OFF:
50            self._intensity = 0.0
51        self._state = state
52        if prev != state:
53            await self.state_changed.emit(LightChange(prev, state, self._intensity))
54
55    async def set_intensity(self, intensity: float) -> None:
56        if not 0.0 <= intensity <= 1.0:
57            raise ValueError(f"intensity must be in [0,1], got {intensity}")
58        if not self.supports_dimming and intensity not in (0.0, 1.0):
59            raise ValueError("this light does not support dimming")
60        prev = self._state
61        self._intensity = intensity
62        if intensity == 0.0:
63            self._state = LightState.OFF
64        elif intensity == 1.0:
65            self._state = LightState.ON
66        else:
67            self._state = LightState.INTERMEDIATE
68        await self.state_changed.emit(LightChange(prev, self._state, intensity))

Layout light. Supports binary or dimmable control.

Light(system_name: str, user_name: str | None = None)
31    def __init__(self, system_name: str, user_name: str | None = None) -> None:
32        super().__init__(system_name, user_name)
33        self._state: LightState = LightState.UNKNOWN
34        self._intensity: float = 0.0
35        self.supports_dimming: bool = False
type_letter = 'L'
supports_dimming: bool
state: jmri_core.state.LightState
37    @property
38    def state(self) -> LightState:
39        return self._state
intensity: float
41    @property
42    def intensity(self) -> float:
43        return self._intensity
async def set_state(self, state: jmri_core.state.LightState) -> None:
45    async def set_state(self, state: LightState) -> None:
46        prev = self._state
47        if state == LightState.ON:
48            self._intensity = 1.0
49        elif state == LightState.OFF:
50            self._intensity = 0.0
51        self._state = state
52        if prev != state:
53            await self.state_changed.emit(LightChange(prev, state, self._intensity))
async def set_intensity(self, intensity: float) -> None:
55    async def set_intensity(self, intensity: float) -> None:
56        if not 0.0 <= intensity <= 1.0:
57            raise ValueError(f"intensity must be in [0,1], got {intensity}")
58        if not self.supports_dimming and intensity not in (0.0, 1.0):
59            raise ValueError("this light does not support dimming")
60        prev = self._state
61        self._intensity = intensity
62        if intensity == 0.0:
63            self._state = LightState.OFF
64        elif intensity == 1.0:
65            self._state = LightState.ON
66        else:
67            self._state = LightState.INTERMEDIATE
68        await self.state_changed.emit(LightChange(prev, self._state, intensity))
71class LightManager(Manager[Light]):
72    type_letter = "L"
73
74    def _create(self, system_name: str) -> Light:
75        return Light(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 = 'L'