jmri_core.beans.id_tag

IdTag — one RFID / tag identity a reader has seen (or hasn't).

The JMRI mobile-throttle apps read ID-tag reads to attribute a locomotive to a physical block via an RFID reader; the tag bean stores the last reporter that saw it and the timestamp.

Wire shape (per jmri/server/json/idtag/idTag-server.json):

  • name — system name (typically IDxxx).
  • userName — friendly label.
  • comment — free-form.
  • state — 0 UNKNOWN, 2 SEEN, 3 UNSEEN.
  • reporter — system name of the last reporter to have read this tag, or null if never seen.
  • time — ISO-8601 timestamp of the last sighting, or null.
 1"""``IdTag`` — one RFID / tag identity a reader has seen (or hasn't).
 2
 3The JMRI mobile-throttle apps read ID-tag reads to attribute a
 4locomotive to a physical block via an RFID reader; the tag bean stores
 5the last reporter that saw it and the timestamp.
 6
 7Wire shape (per ``jmri/server/json/idtag/idTag-server.json``):
 8
 9* ``name`` — system name (typically ``IDxxx``).
10* ``userName`` — friendly label.
11* ``comment`` — free-form.
12* ``state`` — 0 UNKNOWN, 2 SEEN, 3 UNSEEN.
13* ``reporter`` — system name of the last reporter to have read this
14  tag, or ``null`` if never seen.
15* ``time`` — ISO-8601 timestamp of the last sighting, or ``null``.
16"""
17from __future__ import annotations
18
19from jmri_core.bean import AbstractNamedBean
20from jmri_core.manager import Manager
21
22# JMRI Java's IdTag.getState() values. UNKNOWN=0 matches every other
23# NamedBean's unknown; SEEN=2 mirrors sensor ACTIVE=2; UNSEEN=3 is
24# specific to IdTag (a tag that WAS seen but has since been reported
25# gone by the reader).
26ID_TAG_STATE_UNKNOWN = 0
27ID_TAG_STATE_SEEN = 2
28ID_TAG_STATE_UNSEEN = 3
29
30
31class IdTag(AbstractNamedBean):
32    """One RFID / detector tag. Read-only from the layout's perspective —
33    the app doesn't command tags, it observes reader reports."""
34
35    __slots__ = ("_reporter_name", "_state", "_time")
36
37    type_letter = "D"
38
39    def __init__(
40        self, system_name: str, user_name: str | None = None,
41    ) -> None:
42        super().__init__(system_name, user_name)
43        self._state: int = ID_TAG_STATE_UNKNOWN
44        # Set by a reporter when the tag comes into range; kept as a
45        # string so the JSON layer can hand it back without needing a
46        # live reference to the reporter bean.
47        self._reporter_name: str | None = None
48        self._time: str | None = None
49
50    @property
51    def state(self) -> int:
52        return self._state
53
54    @property
55    def reporter_name(self) -> str | None:
56        return self._reporter_name
57
58    @property
59    def time(self) -> str | None:
60        return self._time
61
62    async def report_seen(
63        self, reporter_name: str, when: str,
64    ) -> None:
65        """A reader saw this tag at time ``when``. ``when`` is an ISO
66        timestamp string — no timezone parsing at the bean level,
67        callers supply what they already have."""
68        self._state = ID_TAG_STATE_SEEN
69        self._reporter_name = reporter_name
70        self._time = when
71        await self.state_changed.emit(self._state)
72
73    async def report_unseen(self, when: str) -> None:
74        """The reader no longer sees this tag. Reporter attribution is
75        retained so clients can render "last seen at X, gone since Y"."""
76        self._state = ID_TAG_STATE_UNSEEN
77        self._time = when
78        await self.state_changed.emit(self._state)
79
80
81class IdTagManager(Manager[IdTag]):
82    type_letter = "D"
83
84    def _create(self, system_name: str) -> IdTag:
85        return IdTag(system_name)
ID_TAG_STATE_UNKNOWN = 0
ID_TAG_STATE_SEEN = 2
ID_TAG_STATE_UNSEEN = 3
class IdTag(jmri_core.bean.AbstractNamedBean):
32class IdTag(AbstractNamedBean):
33    """One RFID / detector tag. Read-only from the layout's perspective —
34    the app doesn't command tags, it observes reader reports."""
35
36    __slots__ = ("_reporter_name", "_state", "_time")
37
38    type_letter = "D"
39
40    def __init__(
41        self, system_name: str, user_name: str | None = None,
42    ) -> None:
43        super().__init__(system_name, user_name)
44        self._state: int = ID_TAG_STATE_UNKNOWN
45        # Set by a reporter when the tag comes into range; kept as a
46        # string so the JSON layer can hand it back without needing a
47        # live reference to the reporter bean.
48        self._reporter_name: str | None = None
49        self._time: str | None = None
50
51    @property
52    def state(self) -> int:
53        return self._state
54
55    @property
56    def reporter_name(self) -> str | None:
57        return self._reporter_name
58
59    @property
60    def time(self) -> str | None:
61        return self._time
62
63    async def report_seen(
64        self, reporter_name: str, when: str,
65    ) -> None:
66        """A reader saw this tag at time ``when``. ``when`` is an ISO
67        timestamp string — no timezone parsing at the bean level,
68        callers supply what they already have."""
69        self._state = ID_TAG_STATE_SEEN
70        self._reporter_name = reporter_name
71        self._time = when
72        await self.state_changed.emit(self._state)
73
74    async def report_unseen(self, when: str) -> None:
75        """The reader no longer sees this tag. Reporter attribution is
76        retained so clients can render "last seen at X, gone since Y"."""
77        self._state = ID_TAG_STATE_UNSEEN
78        self._time = when
79        await self.state_changed.emit(self._state)

One RFID / detector tag. Read-only from the layout's perspective — the app doesn't command tags, it observes reader reports.

IdTag(system_name: str, user_name: str | None = None)
40    def __init__(
41        self, system_name: str, user_name: str | None = None,
42    ) -> None:
43        super().__init__(system_name, user_name)
44        self._state: int = ID_TAG_STATE_UNKNOWN
45        # Set by a reporter when the tag comes into range; kept as a
46        # string so the JSON layer can hand it back without needing a
47        # live reference to the reporter bean.
48        self._reporter_name: str | None = None
49        self._time: str | None = None
type_letter = 'D'
state: int
51    @property
52    def state(self) -> int:
53        return self._state
reporter_name: str | None
55    @property
56    def reporter_name(self) -> str | None:
57        return self._reporter_name
time: str | None
59    @property
60    def time(self) -> str | None:
61        return self._time
async def report_seen(self, reporter_name: str, when: str) -> None:
63    async def report_seen(
64        self, reporter_name: str, when: str,
65    ) -> None:
66        """A reader saw this tag at time ``when``. ``when`` is an ISO
67        timestamp string — no timezone parsing at the bean level,
68        callers supply what they already have."""
69        self._state = ID_TAG_STATE_SEEN
70        self._reporter_name = reporter_name
71        self._time = when
72        await self.state_changed.emit(self._state)

A reader saw this tag at time when. when is an ISO timestamp string — no timezone parsing at the bean level, callers supply what they already have.

async def report_unseen(self, when: str) -> None:
74    async def report_unseen(self, when: str) -> None:
75        """The reader no longer sees this tag. Reporter attribution is
76        retained so clients can render "last seen at X, gone since Y"."""
77        self._state = ID_TAG_STATE_UNSEEN
78        self._time = when
79        await self.state_changed.emit(self._state)

The reader no longer sees this tag. Reporter attribution is retained so clients can render "last seen at X, gone since Y".

82class IdTagManager(Manager[IdTag]):
83    type_letter = "D"
84
85    def _create(self, system_name: str) -> IdTag:
86        return IdTag(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 = 'D'