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'-'