jmri_nce.opcodes

NCE binary command opcodes — audited against JMRI Java's NceMessage.java constants and NceMonBinaryBundle.properties (the wire-decoder bundle).

Every constant in this file must trace to a specific JMRI setOpCode call site or NceBinaryCommand factory. If it doesn't, it is NOT SAFE to send — the NCE binary protocol is not orthogonal (adjacent opcodes have very different effects; e.g. 0x97 is Write-1-byte-to-memory, not Read-CV) and a wrong opcode can corrupt CS EEPROM.

Every unused / speculative opcode has been removed from this module.

Reference: NceMessage.java decimal → hex conversion table used to build this file:

NOP_CMD              128 = 0x80
READ_CLOCK_CMD       130 = 0x82
STOP_CLOCK_CMD       131 = 0x83
START_CLOCK_CMD      132 = 0x84
SET_CLOCK_CMD        133 = 0x85   (set clock hr/min — NOT memory write)
CLOCK_1224_CMD       134 = 0x86
CLOCK_RATIO_CMD      135 = 0x87   (set fast-clock ratio — NOT version query)
DEQUEUE_CMD          136 = 0x88   (dequeue byte — NOT power off)
ENABLE_MAIN_CMD      137 = 0x89   (power on)
READ_AUI4_CMD        138 = 0x8A
KILL_MAIN_CMD        139 = 0x8B   (power off)
DUMMY_CMD            140 = 0x8C
WRITE_N_CMD          142 = 0x8E   (write N bytes at address)
READ16_CMD           143 = 0x8F   (read 16 bytes at address)
DISPLAY3_CMD         144 = 0x90   (write 3 chars to CS LCD — NOT CV read)
DISPLAY4_CMD         145 = 0x91   (write 4 chars to CS LCD — NOT CV write)
DISPLAY2_CMD         146 = 0x92
QUEUE3..6_TMP_CMD    147..150 = 0x93..0x96  (queue raw DCC packet)
WRITE1_CMD           151 = 0x97   (write 1 byte at address — NOT CV read)
WRITE2/4/8_CMD       152..154 = 0x98..0x9A
READ_AUI2_CMD        155 = 0x9B
MACRO_CMD            156 = 0x9C
READ1_CMD            157 = 0x9D
ENTER_PROG_CMD       158 = 0x9E
EXIT_PROG_CMD        159 = 0x9F
WRITE_PAGED_CV_CMD   160 = 0xA0   (paged-mode CV write — NOT memory read)
READ_PAGED_CV_CMD    161 = 0xA1
LOCO_CMD             162 = 0xA2   (throttle)
WRITE_REG_CMD        166 = 0xA6
READ_REG_CMD         167 = 0xA7
WRITE_DIR_CV_CMD     168 = 0xA8   (direct-mode CV write on prog track)
READ_DIR_CV_CMD      169 = 0xA9   (direct-mode CV read on prog track)
SW_REV_CMD           170 = 0xAA   (firmware version — this is the safe one)
ACC_CMD              173 = 0xAD   (accessory decoder / signal / macro)
OPS_PROG_LOCO_CMD    174 = 0xAE
OPS_PROG_ACCY_CMD    175 = 0xAF
  1"""NCE binary command opcodes — audited against JMRI Java's
  2``NceMessage.java`` constants and ``NceMonBinaryBundle.properties``
  3(the wire-decoder bundle).
  4
  5**Every constant in this file must trace to a specific JMRI ``setOpCode``
  6call site or ``NceBinaryCommand`` factory.** If it doesn't, it is
  7NOT SAFE to send — the NCE binary protocol is not orthogonal (adjacent
  8opcodes have very different effects; e.g. 0x97 is Write-1-byte-to-memory,
  9not Read-CV) and a wrong opcode can corrupt CS EEPROM.
 10
 11Every unused / speculative opcode has been removed from this module.
 12
 13Reference: ``NceMessage.java`` decimal → hex conversion table used to
 14build this file:
 15
 16    NOP_CMD              128 = 0x80
 17    READ_CLOCK_CMD       130 = 0x82
 18    STOP_CLOCK_CMD       131 = 0x83
 19    START_CLOCK_CMD      132 = 0x84
 20    SET_CLOCK_CMD        133 = 0x85   (set clock hr/min — NOT memory write)
 21    CLOCK_1224_CMD       134 = 0x86
 22    CLOCK_RATIO_CMD      135 = 0x87   (set fast-clock ratio — NOT version query)
 23    DEQUEUE_CMD          136 = 0x88   (dequeue byte — NOT power off)
 24    ENABLE_MAIN_CMD      137 = 0x89   (power on)
 25    READ_AUI4_CMD        138 = 0x8A
 26    KILL_MAIN_CMD        139 = 0x8B   (power off)
 27    DUMMY_CMD            140 = 0x8C
 28    WRITE_N_CMD          142 = 0x8E   (write N bytes at address)
 29    READ16_CMD           143 = 0x8F   (read 16 bytes at address)
 30    DISPLAY3_CMD         144 = 0x90   (write 3 chars to CS LCD — NOT CV read)
 31    DISPLAY4_CMD         145 = 0x91   (write 4 chars to CS LCD — NOT CV write)
 32    DISPLAY2_CMD         146 = 0x92
 33    QUEUE3..6_TMP_CMD    147..150 = 0x93..0x96  (queue raw DCC packet)
 34    WRITE1_CMD           151 = 0x97   (write 1 byte at address — NOT CV read)
 35    WRITE2/4/8_CMD       152..154 = 0x98..0x9A
 36    READ_AUI2_CMD        155 = 0x9B
 37    MACRO_CMD            156 = 0x9C
 38    READ1_CMD            157 = 0x9D
 39    ENTER_PROG_CMD       158 = 0x9E
 40    EXIT_PROG_CMD        159 = 0x9F
 41    WRITE_PAGED_CV_CMD   160 = 0xA0   (paged-mode CV write — NOT memory read)
 42    READ_PAGED_CV_CMD    161 = 0xA1
 43    LOCO_CMD             162 = 0xA2   (throttle)
 44    WRITE_REG_CMD        166 = 0xA6
 45    READ_REG_CMD         167 = 0xA7
 46    WRITE_DIR_CV_CMD     168 = 0xA8   (direct-mode CV write on prog track)
 47    READ_DIR_CV_CMD      169 = 0xA9   (direct-mode CV read on prog track)
 48    SW_REV_CMD           170 = 0xAA   (firmware version — this is the safe one)
 49    ACC_CMD              173 = 0xAD   (accessory decoder / signal / macro)
 50    OPS_PROG_LOCO_CMD    174 = 0xAE
 51    OPS_PROG_ACCY_CMD    175 = 0xAF
 52"""
 53from __future__ import annotations
 54
 55# --- power (mains-track relay) -------------------------------------------
 56
 57# JMRI: NceMessage.getEnableMain / getKillMain (setOpCode 137 / 139).
 58OP_POWER_ON             = 0x89   # ENABLE_MAIN_CMD
 59OP_POWER_OFF            = 0x8B   # KILL_MAIN_CMD
 60
 61# --- CS memory read/write -------------------------------------------------
 62
 63# JMRI: NceBinaryCommand.accMemoryWriteN / accMemoryRead / accMemoryRead1.
 64OP_WRITE_N_MEM          = 0x8E   # WRITE_N_CMD    — write N bytes at addr
 65OP_READ_16_MEM          = 0x8F   # READ16_CMD     — read exactly 16 bytes at addr
 66OP_READ_1_MEM           = 0x9D   # READ1_CMD      — read 1 byte at addr
 67
 68# NCE has no "read N bytes" opcode — only fixed 16-byte or 1-byte reads.
 69
 70# --- fast clock -----------------------------------------------------------
 71#
 72# JMRI: NceBinaryCommand.accStopClock / accStartClock / accSetClock1224 /
 73# accSetClockRatio, plus accMemoryReadN(getClockAddr()) for reads and
 74# accMemoryWriteN(getClockAddr()+2, [ss, mm, hh]) for time writes.
 75
 76OP_CLOCK_STOP           = 0x83   # STOP_CLOCK_CMD
 77OP_CLOCK_START          = 0x84   # START_CLOCK_CMD
 78OP_CLOCK_SET_1224       = 0x86   # CLOCK_1224_CMD  — arg: 1=24h, 0=12h
 79OP_CLOCK_SET_RATIO      = 0x87   # CLOCK_RATIO_CMD — arg: integer ratio 1..15
 80
 81# PowerPro clock lives at 0xDC00; PowerCab (Ph5) at 0x3E00.
 82CS_CLOCK_ADDR           = 0xDC00
 83
 84# --- AIU (occupancy detector) --------------------------------------------
 85
 86# JMRI: NceBinaryCommand.accAiu2Read.
 87OP_AIU_BASE_QUERY       = 0x9B   # READ_AUI2_CMD  — read AIU 16-bit input word
 88
 89# --- accessory decoders ---------------------------------------------------
 90
 91# JMRI: NceBinaryCommand.accDecoder.
 92OP_ACCY_DECODER_OPS     = 0xAD   # ACC_CMD
 93                                  # wire form: [0xAD, addr_hi, addr_lo, dir, 0x00]
 94                                  # dir = 3 (closed) / 4 (thrown), per NMRA
 95
 96# --- throttle (modern firmware, commandOptions >= 10) --------------------
 97
 98# JMRI: NceBinaryCommand.nceLocoCmd.
 99OP_NCE_LOCO_CMD         = 0xA2   # LOCO_CMD
100                                  # wire form: [0xA2, addr_hi, addr_lo, sub_cmd, data]
101
102LOCO_SUB_28_REV         = 1
103LOCO_SUB_28_FWD         = 2
104LOCO_SUB_128_REV        = 3
105LOCO_SUB_128_FWD        = 4
106LOCO_SUB_ESTOP_REV      = 5
107LOCO_SUB_ESTOP_FWD      = 6
108LOCO_SUB_FUNC_GROUP_1   = 7    # F0..F4
109LOCO_SUB_FUNC_GROUP_2   = 8    # F5..F8
110LOCO_SUB_FUNC_GROUP_3   = 9    # F9..F12
111LOCO_SUB_FUNC_GROUP_4   = 21   # F13..F20
112LOCO_SUB_FUNC_GROUP_5   = 22   # F21..F28
113
114# --- CS-managed consist sub-commands -----------------------------------
115#
116# NCE PowerPro tracks command-station consists internally (1..127, six
117# members max: one LEAD + one REAR + up to four MIDs). The loco command
118# above adds / removes locos to that consist by *sub-command byte*, with
119# the target consist number in the data byte. Cross-referenced against
120# JMRI Java ``jmri.jmrix.nce.NceConsist.add()`` — see the values passed
121# to ``addLocoToConsist(...)`` on the first / second / third+ add path,
122# and the constants used by ``removeLocoFromConsist`` (16) and
123# ``killConsist`` (17).
124#
125# Direction: FWD = loco oriented forward in the consist,
126#            REV = loco oriented backwards. The CS translates that into
127#            per-loco direction inversion when it fans out speed cmds.
128#
129# Position rules (enforced by ``NceConsistManager``):
130# * First member added becomes LEAD (position 0).
131# * Second member becomes REAR (position 255 in JMRI convention).
132# * Third through sixth become MID (positions 1..4).
133# * LEAD and REAR can NOT be removed with 0x10 — dispose the whole
134#   consist with 0x11 instead.
135
136LOCO_SUB_CS_ADD_LEAD_REV = 0x0A  # Add LEAD, reversed orientation
137LOCO_SUB_CS_ADD_LEAD_FWD = 0x0B  # Add LEAD, forward orientation
138LOCO_SUB_CS_ADD_REAR_REV = 0x0C  # Add REAR, reversed
139LOCO_SUB_CS_ADD_REAR_FWD = 0x0D  # Add REAR, forward
140LOCO_SUB_CS_ADD_MID_REV  = 0x0E  # Add MID, reversed
141LOCO_SUB_CS_ADD_MID_FWD  = 0x0F  # Add MID, forward
142LOCO_SUB_CS_REMOVE       = 0x10  # Remove this loco from its consist (data=0)
143LOCO_SUB_CS_KILL         = 0x11  # Kill the consist this loco leads (data=0)
144
145# Long addresses are pre-encoded as ``addr + 0xC000`` so the CS can
146# distinguish them from short addresses with the same numeric value.
147LONG_ADDRESS_OFFSET     = 0xC000
148
149# --- CV programming (programming track / ops mode) -----------------------
150#
151# JMRI: NceMessage.getWriteDirectCV / getReadDirectCV (setOpCode 168/169).
152# Ops-mode CV writes use sendPacketMessage which wraps a raw NMRA DCC
153# packet in a QUEUE_TMP command (opcodes 0x93+packet_len).
154
155OP_ENTER_PROG_MODE      = 0x9E   # ENTER_PROG_CMD    — put CS in programming mode
156OP_EXIT_PROG_MODE       = 0x9F   # EXIT_PROG_CMD     — leave programming mode
157OP_WR_CV_DIRECT_MODE    = 0xA8   # WRITE_DIR_CV_CMD  — direct-mode write, prog track
158OP_RD_CV_DIRECT_MODE    = 0xA9   # READ_DIR_CV_CMD   — direct-mode read, prog track
159
160# Base opcode for the QUEUEn_TMP_CMD family: 0x93 = queue 3-byte packet,
161# 0x94 = 4-byte, 0x95 = 5-byte, 0x96 = 6-byte. Ops-mode CV writes on the
162# main track use this by building a raw DCC packet and shipping it via
163# the appropriate variant. There is no NCE ops-mode CV *read* — NCE
164# PowerPro doesn't ship RailCom decoding; ops reads are not supported.
165OP_QUEUE_PACKET_BASE    = 0x93
166
167# --- firmware version query -----------------------------------------------
168
169# JMRI: NceBinaryCommand.getNceEpromRev (opcode 0xAA).
170OP_QUERY_SW_REV         = 0xAA   # SW_REV_CMD — safe: read-only, no side effects
171
172# --- ACK / reply byte values ---------------------------------------------
173
174ACK_OK              = b"!"   # success
175ACK_ERROR           = b"0"   # explicit NACK — raises NceCommandError
176ACK_OTHER           = b"$"   # other status
177ACK_FRAMING_ERROR   = b"-"
178
179# NCE's reply byte semantics vary by command. Many action commands return
180# either ``!`` (ACK_OK) or an echo of the request's opcode byte — JMRI
181# treats both as success (its listener is null for these). The traffic
182# controller validates ``ACK_ERROR`` only; any other byte is accepted.
183
184
185# --- deprecated aliases (kept only long enough to update callers) --------
186#
187# Old names that were bound to WRONG opcodes. Importing any of these
188# raises at import-time so a caller can't accidentally send the
189# pre-audit byte.
190
191def __getattr__(name: str) -> int:
192    _WITHDRAWN = {
193        # name → (previous_value, what_it_actually_did, correct_symbol)
194        "OP_READ_N_MEM":       (0xA0, "WRITE_PAGED_CV_CMD (paged-mode CV write)",
195                                "OP_READ_16_MEM (fixed 16-byte read) or OP_READ_1_MEM"),
196        "OP_READ_ACCY_MEM":    (0x8F, "correct opcode, misleading name",
197                                "OP_READ_16_MEM"),
198        "OP_READ_ACCY_MEM_1":  (0x9D, "correct opcode, misleading name",
199                                "OP_READ_1_MEM"),
200        "OP_ACCY_WRITE_N":     (0x8E, "correct opcode, misleading name",
201                                "OP_WRITE_N_MEM"),
202        "OP_QUERY_COMMAND_VER": (0x87, "CLOCK_RATIO_CMD (sets CS fast-clock rate)",
203                                 "OP_QUERY_SW_REV"),
204        "OP_RD_CV_OPS_MODE":   (0x90, "DISPLAY3_CMD (writes 3 chars to CS LCD)",
205                                "not supported on NCE — no ops-mode CV read"),
206        "OP_WR_CV_OPS_MODE":   (0x91, "DISPLAY4_CMD (writes 4 chars to CS LCD)",
207                                "OP_QUEUE_PACKET_BASE + raw DCC packet builder"),
208    }
209    if name in _WITHDRAWN:
210        prev, was, correct = _WITHDRAWN[name]
211        raise ImportError(
212            f"{name} (={prev:#04x}) was withdrawn by the 2026-07-07 opcode "
213            f"audit — that byte is actually {was}. Use {correct}."
214        )
215    raise AttributeError(name)
OP_POWER_ON = 137
OP_POWER_OFF = 139
OP_WRITE_N_MEM = 142
OP_READ_16_MEM = 143
OP_READ_1_MEM = 157
OP_CLOCK_STOP = 131
OP_CLOCK_START = 132
OP_CLOCK_SET_1224 = 134
OP_CLOCK_SET_RATIO = 135
CS_CLOCK_ADDR = 56320
OP_AIU_BASE_QUERY = 155
OP_ACCY_DECODER_OPS = 173
OP_NCE_LOCO_CMD = 162
LOCO_SUB_28_REV = 1
LOCO_SUB_28_FWD = 2
LOCO_SUB_128_REV = 3
LOCO_SUB_128_FWD = 4
LOCO_SUB_ESTOP_REV = 5
LOCO_SUB_ESTOP_FWD = 6
LOCO_SUB_FUNC_GROUP_1 = 7
LOCO_SUB_FUNC_GROUP_2 = 8
LOCO_SUB_FUNC_GROUP_3 = 9
LOCO_SUB_FUNC_GROUP_4 = 21
LOCO_SUB_FUNC_GROUP_5 = 22
LOCO_SUB_CS_ADD_LEAD_REV = 10
LOCO_SUB_CS_ADD_LEAD_FWD = 11
LOCO_SUB_CS_ADD_REAR_REV = 12
LOCO_SUB_CS_ADD_REAR_FWD = 13
LOCO_SUB_CS_ADD_MID_REV = 14
LOCO_SUB_CS_ADD_MID_FWD = 15
LOCO_SUB_CS_REMOVE = 16
LOCO_SUB_CS_KILL = 17
LONG_ADDRESS_OFFSET = 49152
OP_ENTER_PROG_MODE = 158
OP_EXIT_PROG_MODE = 159
OP_WR_CV_DIRECT_MODE = 168
OP_RD_CV_DIRECT_MODE = 169
OP_QUEUE_PACKET_BASE = 147
OP_QUERY_SW_REV = 170
ACK_OK = b'!'
ACK_ERROR = b'0'
ACK_OTHER = b'$'
ACK_FRAMING_ERROR = b'-'