jmri_core.beans.programmer

Programmer ABC + ProgrammingMode enum + ProgrammerManager.

A programmer reads and writes Configuration Variables (CVs) on a DCC decoder. Two operational modes:

  • Service mode (programming track) — the decoder is electrically isolated from the layout. Slow but works on any decoder; reads always succeed when a decoder is on the track. Three sub-modes: SERVICE_DIRECT (modern), SERVICE_PAGED (legacy), SERVICE_REGISTER (oldest). Most drivers expose only DIRECT.
  • Ops mode (also called "ops mode programming" or "POM") — write to a specific loco while it's running. Reads only work with RailCom; on a non-RailCom layout, ops-mode reads time out.

A driver implements Programmer once for service mode and once per ops-mode address. ProgrammerManager is the registry that hands these out — same shape as ThrottleManager.

 1"""``Programmer`` ABC + ``ProgrammingMode`` enum + ``ProgrammerManager``.
 2
 3A *programmer* reads and writes Configuration Variables (CVs) on a DCC
 4decoder. Two operational modes:
 5
 6* **Service mode** (programming track) — the decoder is electrically
 7  isolated from the layout. Slow but works on any decoder; reads always
 8  succeed when a decoder is on the track. Three sub-modes:
 9  ``SERVICE_DIRECT`` (modern), ``SERVICE_PAGED`` (legacy), ``SERVICE_REGISTER``
10  (oldest). Most drivers expose only DIRECT.
11* **Ops mode** (also called "ops mode programming" or "POM") — write to
12  a specific loco *while it's running*. Reads only work with RailCom; on
13  a non-RailCom layout, ops-mode reads time out.
14
15A driver implements :class:`Programmer` once for service mode and once
16per ops-mode address. :class:`ProgrammerManager` is the registry that
17hands these out — same shape as ``ThrottleManager``.
18"""
19from __future__ import annotations
20
21from abc import ABC, abstractmethod
22from enum import StrEnum
23
24
25class ProgrammingMode(StrEnum):
26    """How the programmer talks to the decoder."""
27
28    SERVICE_DIRECT = "service_direct"
29    SERVICE_PAGED  = "service_paged"
30    SERVICE_REGISTER = "service_register"
31    OPS_MODE = "ops_mode"
32
33
34class Programmer(ABC):
35    """Reads and writes one DCC decoder's CVs.
36
37    Subclasses implement the two abstract methods. They may raise
38    :class:`asyncio.TimeoutError` if the CS doesn't respond in time, or
39    any driver-specific exception on programming errors (no acknowledge
40    pulse, decoder refused write, etc.).
41    """
42
43    @property
44    @abstractmethod
45    def mode(self) -> ProgrammingMode:
46        """Which mode this programmer instance uses."""
47
48    @abstractmethod
49    async def read_cv(self, cv: int) -> int:
50        """Read CV *cv*, returns 0..255."""
51
52    @abstractmethod
53    async def write_cv(self, cv: int, value: int) -> None:
54        """Write *value* to CV *cv*."""
55
56
57class ProgrammerManager:
58    """Hands out programmer instances.
59
60    * :meth:`global_programmer` returns the service-mode programmer (one
61      per connection — the programming track is a singleton).
62    * :meth:`ops_programmer(address, long_address)` returns an ops-mode
63      programmer scoped to the given DCC address.
64
65    The base class returns ``None`` from both; drivers override.
66    """
67
68    def global_programmer(self) -> Programmer | None:
69        return None
70
71    def ops_programmer(
72        self, address: int, *, long_address: bool = False
73    ) -> Programmer | None:
74        return None
class ProgrammingMode(enum.StrEnum):
26class ProgrammingMode(StrEnum):
27    """How the programmer talks to the decoder."""
28
29    SERVICE_DIRECT = "service_direct"
30    SERVICE_PAGED  = "service_paged"
31    SERVICE_REGISTER = "service_register"
32    OPS_MODE = "ops_mode"

How the programmer talks to the decoder.

SERVICE_DIRECT = <ProgrammingMode.SERVICE_DIRECT: 'service_direct'>
SERVICE_PAGED = <ProgrammingMode.SERVICE_PAGED: 'service_paged'>
SERVICE_REGISTER = <ProgrammingMode.SERVICE_REGISTER: 'service_register'>
OPS_MODE = <ProgrammingMode.OPS_MODE: 'ops_mode'>
class Programmer(abc.ABC):
35class Programmer(ABC):
36    """Reads and writes one DCC decoder's CVs.
37
38    Subclasses implement the two abstract methods. They may raise
39    :class:`asyncio.TimeoutError` if the CS doesn't respond in time, or
40    any driver-specific exception on programming errors (no acknowledge
41    pulse, decoder refused write, etc.).
42    """
43
44    @property
45    @abstractmethod
46    def mode(self) -> ProgrammingMode:
47        """Which mode this programmer instance uses."""
48
49    @abstractmethod
50    async def read_cv(self, cv: int) -> int:
51        """Read CV *cv*, returns 0..255."""
52
53    @abstractmethod
54    async def write_cv(self, cv: int, value: int) -> None:
55        """Write *value* to CV *cv*."""

Reads and writes one DCC decoder's CVs.

Subclasses implement the two abstract methods. They may raise asyncio.TimeoutError if the CS doesn't respond in time, or any driver-specific exception on programming errors (no acknowledge pulse, decoder refused write, etc.).

mode: ProgrammingMode
44    @property
45    @abstractmethod
46    def mode(self) -> ProgrammingMode:
47        """Which mode this programmer instance uses."""

Which mode this programmer instance uses.

@abstractmethod
async def read_cv(self, cv: int) -> int:
49    @abstractmethod
50    async def read_cv(self, cv: int) -> int:
51        """Read CV *cv*, returns 0..255."""

Read CV cv, returns 0..255.

@abstractmethod
async def write_cv(self, cv: int, value: int) -> None:
53    @abstractmethod
54    async def write_cv(self, cv: int, value: int) -> None:
55        """Write *value* to CV *cv*."""

Write value to CV cv.

class ProgrammerManager:
58class ProgrammerManager:
59    """Hands out programmer instances.
60
61    * :meth:`global_programmer` returns the service-mode programmer (one
62      per connection — the programming track is a singleton).
63    * :meth:`ops_programmer(address, long_address)` returns an ops-mode
64      programmer scoped to the given DCC address.
65
66    The base class returns ``None`` from both; drivers override.
67    """
68
69    def global_programmer(self) -> Programmer | None:
70        return None
71
72    def ops_programmer(
73        self, address: int, *, long_address: bool = False
74    ) -> Programmer | None:
75        return None

Hands out programmer instances.

  • global_programmer() returns the service-mode programmer (one per connection — the programming track is a singleton).
  • ops_programmer(address, long_address)() returns an ops-mode programmer scoped to the given DCC address.

The base class returns None from both; drivers override.

def global_programmer(self) -> Programmer | None:
69    def global_programmer(self) -> Programmer | None:
70        return None
def ops_programmer( self, address: int, *, long_address: bool = False) -> Programmer | None:
72    def ops_programmer(
73        self, address: int, *, long_address: bool = False
74    ) -> Programmer | None:
75        return None