jmri_loconet.opcodes
LocoNet opcode constants and helpers.
LocoNet message bytes have the high bit set on opcodes (0x80–0xFF) and
clear on data bytes (0x00–0x7F). Bits 5–6 of the opcode encode the
message length class:
0b00 → 2 bytes (opcode + checksum)
0b01 → 4 bytes
0b10 → 6 bytes
0b11 → variable: the byte *after* the opcode is the total length
Checksum = 0xFF XOR of every preceding byte. This module exposes the
opcodes JMRI's LnConstants declares, plus length_class_of() and
expected_length().
1"""LocoNet opcode constants and helpers. 2 3LocoNet message bytes have the high bit set on opcodes (``0x80–0xFF``) and 4clear on data bytes (``0x00–0x7F``). Bits 5–6 of the opcode encode the 5message length class: 6 7 0b00 → 2 bytes (opcode + checksum) 8 0b01 → 4 bytes 9 0b10 → 6 bytes 10 0b11 → variable: the byte *after* the opcode is the total length 11 12Checksum = ``0xFF XOR`` of every preceding byte. This module exposes the 13opcodes JMRI's ``LnConstants`` declares, plus :func:`length_class_of` and 14:func:`expected_length`. 15""" 16from __future__ import annotations 17 18# 2-byte messages ----------------------------------------------------------- 19OPC_BUSY = 0x81 # master is busy 20OPC_GPOFF = 0x82 # global power off 21OPC_GPON = 0x83 # global power on 22OPC_IDLE = 0x85 # force idle 23 24# 4-byte messages ----------------------------------------------------------- 25OPC_LOCO_SPD = 0xA0 # set loco speed 26OPC_LOCO_DIRF = 0xA1 # set loco direction + F0..F4 27OPC_LOCO_SND = 0xA2 # set loco F5..F8 28OPC_SW_REQ = 0xB0 # switch request (turnout) 29OPC_SW_REP = 0xB1 # switch report (turnout feedback) 30OPC_INPUT_REP = 0xB2 # input report (sensor) 31OPC_LONG_ACK = 0xB4 # long-form acknowledgment 32OPC_SLOT_STAT1 = 0xB5 # write slot status 33OPC_CONSIST_FUNC = 0xB6 34OPC_UNLINK_SLOTS = 0xB8 35OPC_LINK_SLOTS = 0xB9 36OPC_MOVE_SLOTS = 0xBA # move slots (used to dispatch / release) 37OPC_RQ_SL_DATA = 0xBB # request slot data 38OPC_SW_STATE = 0xBC 39OPC_SW_ACK = 0xBD 40OPC_LOCO_ADR = 0xBF # request slot for a DCC address 41 42# Variable-length messages -------------------------------------------------- 43OPC_PEER_XFER = 0xE5 # peer transfer (programmer / utility) 44OPC_SL_RD_DATA = 0xE7 # slot read data (14-byte response to OPC_LOCO_ADR) 45OPC_IMM_PACKET = 0xED # immediate DCC packet 46OPC_WR_SL_DATA = 0xEF # write slot data (14-byte) 47 48 49# Slot status constants (high nibble of slot stat byte) --------------------- 50LOCO_FREE = 0x00 51LOCO_COMMON = 0x10 52LOCO_IDLE = 0x20 53LOCO_IN_USE = 0x30 54LOCO_STAT_MASK = 0x30 55 56DEC_MODE_128 = 0x07 # 128 step decoder 57DEC_MODE_28 = 0x02 # 28 step decoder 58DEC_MODE_14 = 0x01 # 14 step decoder 59DEC_MODE_128A = 0x06 # 128 step + advanced consist 60DEC_MODE_28A = 0x03 # 28 step + advanced consist 61 62 63# Length helpers ----------------------------------------------------------- 64 65 66def length_class_of(opcode: int) -> int: 67 """Return 0, 1, 2, or 3 for the length-class bits of *opcode*.""" 68 return (opcode >> 5) & 0b11 69 70 71def expected_length(opcode: int) -> int | None: 72 """Bytes including the opcode and checksum for a fixed-length frame. 73 74 Returns ``None`` for variable-length opcodes (length class 3) where the 75 length lives in the byte after the opcode. 76 """ 77 cls = length_class_of(opcode) 78 if cls == 0b00: 79 return 2 80 if cls == 0b01: 81 return 4 82 if cls == 0b10: 83 return 6 84 return None 85 86 87def is_valid_opcode_byte(b: int) -> bool: 88 """True if *b* could be the first byte of a LocoNet message.""" 89 return 0x80 <= b <= 0xFF
OPC_BUSY =
129
OPC_GPOFF =
130
OPC_GPON =
131
OPC_IDLE =
133
OPC_LOCO_SPD =
160
OPC_LOCO_DIRF =
161
OPC_LOCO_SND =
162
OPC_SW_REQ =
176
OPC_SW_REP =
177
OPC_INPUT_REP =
178
OPC_LONG_ACK =
180
OPC_SLOT_STAT1 =
181
OPC_CONSIST_FUNC =
182
OPC_UNLINK_SLOTS =
184
OPC_LINK_SLOTS =
185
OPC_MOVE_SLOTS =
186
OPC_RQ_SL_DATA =
187
OPC_SW_STATE =
188
OPC_SW_ACK =
189
OPC_LOCO_ADR =
191
OPC_PEER_XFER =
229
OPC_SL_RD_DATA =
231
OPC_IMM_PACKET =
237
OPC_WR_SL_DATA =
239
LOCO_FREE =
0
LOCO_COMMON =
16
LOCO_IDLE =
32
LOCO_IN_USE =
48
LOCO_STAT_MASK =
48
DEC_MODE_128 =
7
DEC_MODE_28 =
2
DEC_MODE_14 =
1
DEC_MODE_128A =
6
DEC_MODE_28A =
3
def
length_class_of(opcode: int) -> int:
67def length_class_of(opcode: int) -> int: 68 """Return 0, 1, 2, or 3 for the length-class bits of *opcode*.""" 69 return (opcode >> 5) & 0b11
Return 0, 1, 2, or 3 for the length-class bits of opcode.
def
expected_length(opcode: int) -> int | None:
72def expected_length(opcode: int) -> int | None: 73 """Bytes including the opcode and checksum for a fixed-length frame. 74 75 Returns ``None`` for variable-length opcodes (length class 3) where the 76 length lives in the byte after the opcode. 77 """ 78 cls = length_class_of(opcode) 79 if cls == 0b00: 80 return 2 81 if cls == 0b01: 82 return 4 83 if cls == 0b10: 84 return 6 85 return None
Bytes including the opcode and checksum for a fixed-length frame.
Returns None for variable-length opcodes (length class 3) where the
length lives in the byte after the opcode.
def
is_valid_opcode_byte(b: int) -> bool:
88def is_valid_opcode_byte(b: int) -> bool: 89 """True if *b* could be the first byte of a LocoNet message.""" 90 return 0x80 <= b <= 0xFF
True if b could be the first byte of a LocoNet message.