jmri_core.beans.consist

Consist — an ordered group of locomotives operating together.

Not a NamedBean: a consist is a configuration object that owns a set of addresses + per-loco direction flags. The hardware-level consisting (whether the loconet/NCE/etc. command station tracks the group, or whether the software simply broadcasts identical commands to each address) is the driver's concern; here we only carry the data.

 1"""``Consist`` — an ordered group of locomotives operating together.
 2
 3Not a NamedBean: a consist is a configuration object that owns a set of
 4addresses + per-loco direction flags. The hardware-level consisting (whether
 5the loconet/NCE/etc. command station tracks the group, or whether the
 6software simply broadcasts identical commands to each address) is the
 7driver's concern; here we only carry the data.
 8"""
 9from __future__ import annotations
10
11from typing import Literal
12
13from pydantic import BaseModel, Field
14
15ConsistKind = Literal["advanced", "command_station", "software"]
16
17# Consist addresses are constrained to NMRA CV19's 7-bit range across
18# every kind — advanced consists literally encode the group into CV19
19# on each decoder, and we hold command-station / software consists to
20# the same rule so the address space is uniform and the UI doesn't
21# have to fork on kind. Bit 7 of CV19 carries direction, so only
22# 1..127 is available for the group identifier itself.
23CONSIST_ADDRESS_MAX = 127
24
25
26class ConsistLoco(BaseModel):
27    """One member of a consist. Individual member locomotives may still
28    have long addresses — only the *consist*'s own address is
29    restricted to short 1..127."""
30
31    address: int = Field(ge=1, le=10239)
32    long_address: bool = False
33    direction_normal: bool = True
34    # Position is the index within the consist; first added = 0.
35    position: int = Field(ge=0)
36
37
38class Consist(BaseModel):
39    """An ordered group of locomotives.
40
41    The "consist address" identifies the group on the bus. For advanced
42    (CV19) consists this is the active short address shared across locos;
43    for command-station consists it's a slot/handle the command station
44    assigns; for software consists it's the bookkeeping key. All three
45    variants are held to the 1..127 short-address range for consistency
46    with the CV19 wire limit.
47    """
48
49    address: int = Field(ge=1, le=CONSIST_ADDRESS_MAX)
50    kind: ConsistKind = "advanced"
51    locos: list[ConsistLoco] = Field(default_factory=list)
52    comment: str = ""
53
54    def add(self, loco: ConsistLoco) -> None:
55        self.locos.append(loco)
56
57    def remove(self, address: int) -> bool:
58        """Drop the loco at *address*. Returns True if found."""
59        for i, loco in enumerate(self.locos):
60            if loco.address == address:
61                del self.locos[i]
62                return True
63        return False
ConsistKind = typing.Literal['advanced', 'command_station', 'software']
CONSIST_ADDRESS_MAX = 127
class ConsistLoco(pydantic.main.BaseModel):
27class ConsistLoco(BaseModel):
28    """One member of a consist. Individual member locomotives may still
29    have long addresses — only the *consist*'s own address is
30    restricted to short 1..127."""
31
32    address: int = Field(ge=1, le=10239)
33    long_address: bool = False
34    direction_normal: bool = True
35    # Position is the index within the consist; first added = 0.
36    position: int = Field(ge=0)

One member of a consist. Individual member locomotives may still have long addresses — only the consist's own address is restricted to short 1..127.

address: int = PydanticUndefined
long_address: bool = False
direction_normal: bool = True
position: int = PydanticUndefined
class Consist(pydantic.main.BaseModel):
39class Consist(BaseModel):
40    """An ordered group of locomotives.
41
42    The "consist address" identifies the group on the bus. For advanced
43    (CV19) consists this is the active short address shared across locos;
44    for command-station consists it's a slot/handle the command station
45    assigns; for software consists it's the bookkeeping key. All three
46    variants are held to the 1..127 short-address range for consistency
47    with the CV19 wire limit.
48    """
49
50    address: int = Field(ge=1, le=CONSIST_ADDRESS_MAX)
51    kind: ConsistKind = "advanced"
52    locos: list[ConsistLoco] = Field(default_factory=list)
53    comment: str = ""
54
55    def add(self, loco: ConsistLoco) -> None:
56        self.locos.append(loco)
57
58    def remove(self, address: int) -> bool:
59        """Drop the loco at *address*. Returns True if found."""
60        for i, loco in enumerate(self.locos):
61            if loco.address == address:
62                del self.locos[i]
63                return True
64        return False

An ordered group of locomotives.

The "consist address" identifies the group on the bus. For advanced (CV19) consists this is the active short address shared across locos; for command-station consists it's a slot/handle the command station assigns; for software consists it's the bookkeeping key. All three variants are held to the 1..127 short-address range for consistency with the CV19 wire limit.

address: int = PydanticUndefined
kind: Literal['advanced', 'command_station', 'software'] = 'advanced'
locos: list[ConsistLoco] = PydanticUndefined
comment: str = ''
def add(self, loco: ConsistLoco) -> None:
55    def add(self, loco: ConsistLoco) -> None:
56        self.locos.append(loco)
def remove(self, address: int) -> bool:
58    def remove(self, address: int) -> bool:
59        """Drop the loco at *address*. Returns True if found."""
60        for i, loco in enumerate(self.locos):
61            if loco.address == address:
62                del self.locos[i]
63                return True
64        return False

Drop the loco at address. Returns True if found.