jmri_openlcb.events

EventId — the 64-bit OpenLCB event identifier.

OpenLCB events are 64-bit values uniquely identifying something happened on the network. A sensor turning on produces one event ID; the same sensor turning off produces a different event ID. Beans that should react bind specific event IDs to specific state transitions.

The canonical text form is dotted hex, big-endian, e.g. "05.01.01.01.4C.43.00.01" — eight bytes joined by dots. We parse both that form and a plain hex string "05010101 4C430001" (with or without spaces) since both appear in JMRI configs.

 1"""``EventId`` — the 64-bit OpenLCB event identifier.
 2
 3OpenLCB events are 64-bit values uniquely identifying *something happened*
 4on the network. A sensor turning on produces one event ID; the same
 5sensor turning off produces a different event ID. Beans that should
 6react bind specific event IDs to specific state transitions.
 7
 8The canonical text form is dotted hex, big-endian, e.g.
 9``"05.01.01.01.4C.43.00.01"`` — eight bytes joined by dots. We parse
10both that form and a plain hex string ``"05010101 4C430001"`` (with or
11without spaces) since both appear in JMRI configs.
12"""
13from __future__ import annotations
14
15from dataclasses import dataclass
16
17
18@dataclass(frozen=True, slots=True)
19class EventId:
20    """64-bit OpenLCB event identifier."""
21
22    value: int  # 0..2**64-1
23
24    def __post_init__(self) -> None:
25        if not 0 <= self.value < (1 << 64):
26            raise ValueError(f"EventId must be 0..2**64-1, got {self.value}")
27
28    @classmethod
29    def parse(cls, text: str) -> EventId:
30        """Parse the dotted-hex or plain-hex text form into an :class:`EventId`."""
31        cleaned = text.replace(".", "").replace(" ", "").strip()
32        if not cleaned:
33            raise ValueError(f"empty EventId text {text!r}")
34        try:
35            return cls(int(cleaned, 16))
36        except ValueError as e:
37            raise ValueError(f"invalid EventId text {text!r}: {e}") from None
38
39    @property
40    def bytes(self) -> bytes:
41        return self.value.to_bytes(8, "big")
42
43    def __str__(self) -> str:
44        b = self.bytes
45        return ".".join(f"{x:02X}" for x in b)
46
47
48@dataclass(frozen=True, slots=True)
49class EventReport:
50    """Produced/Consumer Event Report — the workhorse message.
51
52    Sent by producers to announce "this event happened"; observed by
53    consumers to drive their state. The 64-bit ``event`` is enough; the
54    16-bit source ``alias`` identifies *which* node produced it (useful
55    for diagnostics; we don't validate aliases here).
56    """
57
58    event: EventId
59    source_alias: int  # 12-bit node alias, observed in the CAN header
@dataclass(frozen=True, slots=True)
class EventId:
19@dataclass(frozen=True, slots=True)
20class EventId:
21    """64-bit OpenLCB event identifier."""
22
23    value: int  # 0..2**64-1
24
25    def __post_init__(self) -> None:
26        if not 0 <= self.value < (1 << 64):
27            raise ValueError(f"EventId must be 0..2**64-1, got {self.value}")
28
29    @classmethod
30    def parse(cls, text: str) -> EventId:
31        """Parse the dotted-hex or plain-hex text form into an :class:`EventId`."""
32        cleaned = text.replace(".", "").replace(" ", "").strip()
33        if not cleaned:
34            raise ValueError(f"empty EventId text {text!r}")
35        try:
36            return cls(int(cleaned, 16))
37        except ValueError as e:
38            raise ValueError(f"invalid EventId text {text!r}: {e}") from None
39
40    @property
41    def bytes(self) -> bytes:
42        return self.value.to_bytes(8, "big")
43
44    def __str__(self) -> str:
45        b = self.bytes
46        return ".".join(f"{x:02X}" for x in b)

64-bit OpenLCB event identifier.

EventId(value: int)
value: int
@classmethod
def parse(cls, text: str) -> EventId:
29    @classmethod
30    def parse(cls, text: str) -> EventId:
31        """Parse the dotted-hex or plain-hex text form into an :class:`EventId`."""
32        cleaned = text.replace(".", "").replace(" ", "").strip()
33        if not cleaned:
34            raise ValueError(f"empty EventId text {text!r}")
35        try:
36            return cls(int(cleaned, 16))
37        except ValueError as e:
38            raise ValueError(f"invalid EventId text {text!r}: {e}") from None

Parse the dotted-hex or plain-hex text form into an EventId.

bytes: <property object at 0x000002466E8652B0>
40    @property
41    def bytes(self) -> bytes:
42        return self.value.to_bytes(8, "big")
@dataclass(frozen=True, slots=True)
class EventReport:
49@dataclass(frozen=True, slots=True)
50class EventReport:
51    """Produced/Consumer Event Report — the workhorse message.
52
53    Sent by producers to announce "this event happened"; observed by
54    consumers to drive their state. The 64-bit ``event`` is enough; the
55    16-bit source ``alias`` identifies *which* node produced it (useful
56    for diagnostics; we don't validate aliases here).
57    """
58
59    event: EventId
60    source_alias: int  # 12-bit node alias, observed in the CAN header

Produced/Consumer Event Report — the workhorse message.

Sent by producers to announce "this event happened"; observed by consumers to drive their state. The 64-bit event is enough; the 16-bit source alias identifies which node produced it (useful for diagnostics; we don't validate aliases here).

EventReport(event: EventId, source_alias: int)
event: EventId
source_alias: int