Repository navigation
LocoNet Adapter
Status: largely implemented (see the notes below); the translation of switches and loco commands to RocNet on the mesh is still design. The LocoNet adapter connects a Hive-Rail node to LocoNet equipment and translates to and from RocNet on the mesh. Protocol facts come from the Digitrax LocoNet Personal Use Edition 1.0 (PE). The PE is licensed for personal use only, so it is not stored in this repository; this page only summarises what is needed for interoperability.
Implemented (2026-10-28): the LocoNet codec (validation, stream decoding, decoded log lines), LocoNet over TCP on port 1234 with text (LbServer) and binary clients detected per client, power (GPON, GPOFF, IDLE) for the node's district, and the fast clock written to slot 123. Tested with Rocrail. Each LocoNet connection has a slot owner:
external(its own slot master, such as Rocrail's slot server, which stays on, or a central; the node follows its slots by loco address and answers no slot requests) ornode(the node answersLOCO_ADR,MOVE_SLOTSandRQ_SL_DATAwith the layout's throttle numbers). A loco entering the district is taken on an external slot master (§4).
Added (2026-10-30): LocoNet over UDP through a GCA101, stage 2 on an Intellibox I (§4.2), taking a loco only after the null move is confirmed and marking its slot with the node's throttle ID, and turnout answers when the node is the command station (§4.3).
Added (2026-10-31 and 11-01): a panel as an occupancy display (
--show-sensors, §4.3), andOPC_INPUT_REPfrom the bus or the layout simulator reported on the mesh as a RocNet sensor report (group 8 onrn/node/{id}/sr). Switch commands are answered and learned on the node and sent to DCC-EX, but not yet published on the mesh (rn/node/{id}/dev); locos go through the loco register, not yet as RocNet loco frames (§2).Implemented (2026-10-29), not yet tested with a real central: a serial LocoNet interface as the node's bus (
--loconet-serial <port>): the RS232 port of an Intellibox I (65000) in LocoNet mode, or a LocoBuffer. Defaults 19200 baud with RTS/CTS and an external slot master.--loconet-fast-clock offkeeps the fast clock off a bus whose central has none.
Contents
- Role
- Translation to RocNet
- Power and short detection
- Loco commands on a LocoNet central
- LocoNet message reference
- Stage 2 test setup
- Open questions
flowchart LR
subgraph cluster["Cluster with a LocoNet central"]
DEV["Module LocoNet<br/>detectors, IntelliSwitch,<br/>throttles of the central"] --- IB["LocoNet central<br/>Intellibox, Digitrax, Z21"]
IB ---|"LocoNet USB interface<br/>or LocoNet over TCP"| N["Hive-Rail node<br/>LocoNet adapter"]
end
N <-->|"RocNet frames"| MESH["Zenoh mesh"]
- The central stays the slot master of its own LocoNet, as today. The node is one more LocoNet client: it listens to everything and sends requests like any PC interface.
- Without FREDs on the mesh, the module LocoNet can stay connected to the central. The cabling rule for FREDs applies only when FRED support is added.
- Transports: a serial LocoNet interface (
--loconet-serial), LocoNet over UDP multicast through a GCA101 LocoBuffer-UDP (--loconet-udp, group 224.0.0.1 port 1235 as Rocrail's LNUDP; one GCA101 per network), a node board's LocoNet buffer (--loconet-connect, an LbServer) and LocoNet over TCP for Rocrail and JMRI (--loconet-tcp, the LbServer text format withSEND/RECEIVElines, or raw binary, port 1234 by default).
| Interface | Settings | Echo |
|---|---|---|
| Intellibox I (65000) RS232 port | On the Intellibox: SO 2 = 4 (LocoNet on the interface instead of P50X), SO 5 = 1 (one stop bit), SO 6 = 255 (the stop key does not drop CTS), interface at 19200 baud. On the node: 19200 baud, RTS/CTS (the defaults). A Mac needs a USB-RS232 adapter and a cable that carries RTS and CTS | None |
| LocoBuffer-USB |
--loconet-serial-baud 57600, RTS/CTS |
Each message once it is on the bus |
| Intellibox II, IB-Com, Intellibox Basic (65060) USB port | 115200 baud, --loconet-serial-flow none (as JMRI) |
Not yet known |
The node drops the echo of its own messages when one comes back within 300 ms, so it only sees what other bus devices sent, on every interface. JMRI warns that the Intellibox I's handshake can lose messages under heavy traffic and recommends a LocoBuffer for a busy layout. In P50X mode (the default) the RS232 port speaks Uhlenbrock's own command protocol, by loco address and without slots or bus traffic; Hive-Rail does not use it. The settings are from JMRI's Intellibox page.
Recorded on 2026-10-28 with Rocrail connected over LocoNet TCP, slot server on, clock at 1:1:
BB 7B 00 3F RQ_SL_DATA slot 123
E7 0E 7B 01 7F 7F 5A 04 76 00 70 7F 70 3B SL_RD_DATA 14:23 x1 (answered by Rocrail's slot server)
EF 0E 7B 01 7F 7F 5B 04 76 00 70 7F 70 32 WR_SL_DATA 14:24 x1, set by 7F 70 (Rocrail)
B4 6F 7F 5B LONG_ACK write accepted
| Byte | Field | Encoding |
|---|---|---|
| 3 | CLK_RATE |
0 frozen, 1 real time, up to 127 |
| 4, 5 | FRAC_MINS |
sub-minute counter; Rocrail always sends 7F 7F
|
| 6 | minutes |
(255 - (60 - m)) & 0x7F, so 14:23 is 5A (as JMRI) |
| 7 | TRK |
bit 0 power on, bit 1 not idle, bit 2 LocoNet 1.1 |
| 8 | hours |
(256 - (24 - h)) & 0x7F, so 14 is 76
|
| 9 | DAYS |
day count |
| 10 | CLK_CNTRL |
bit 6: valid clock data |
| 11, 12 | setter ID |
7F 7x is reserved for PCs: Rocrail 7F 70, JMRI 7F 71, Hive-Rail 7F 7E
|
Rocrail reads slot 123 and writes the new time once per fast minute. Every slot-server answer and LONG_ACK arrives twice, because the interface echo reaches Rocrail's slot server as well; a reader treats identical repeats as one. JMRI's clock master writes slot 123 once per fast minute in the same way.
| LocoNet | Direction | RocNet on the mesh |
|---|---|---|
OPC_GPON / OPC_GPOFF
|
both | Track power on / off for this node's district only; the result goes out as a status event on rn/cs/node/{id}
|
OPC_IDLE |
LocoNet to node | Emergency stop for this node's district only |
OPC_IDLE |
mesh to LocoNet | Sent to local clients for a district stop and for the global emergency stop (halt on rn/cs/all) |
TRK power bit, unrequested power off |
LocoNet to mesh | Status event on rn/cs/node/{id}, node/{id}/power = Short
|
| Slot 123 fast clock | mesh to LocoNet |
OPC_WR_SL_DATA to slot 123 with setter ID 7F 7E, on each fast minute and on every change of the rn/ck clock. Clock writes from LocoNet are ignored: Hive-Rail commands the clock |
OPC_LOCO_SPD, OPC_LOCO_DIRF, OPC_LOCO_SND from the central's own throttles |
LocoNet to mesh | Group 1 velocity / function on rn/loco/{address}, with the address from the central's slot (today through the loco register, not as RocNet frames) |
| RocNet velocity / function for a loco in this district | mesh to LocoNet | Node acquires the address on the central (OPC_LOCO_ADR, null move) and writes speed and functions to that slot (today through the loco register, not as RocNet frames) |
OPC_SW_REQ, OPC_SW_REP
|
both | Group 1 switch on rn/node/{id}/dev, events for feedback (not yet built) |
OPC_INPUT_REP |
LocoNet to mesh | Group 8 sensor report on rn/node/{id}/sr (built 2026-11-01) |
| Slot 124 programming | — | Local only |
The central's slot numbers never leave the node.
A short on a LocoNet central is detected when the central reports power off (OPC_GPOFF, or the power bit in the TRK byte of a slot read dropping) without a power-off request from the node or the mesh in the moment before. The node then reports Short for its district (see RocNet on the Mesh §6). How the Intellibox I signals a short on LocoNet is still to be recorded (L-Q1).
When a loco enters this node's district, the node acquires its address on the central as a normal LocoNet client:
sequenceDiagram
autonumber
participant M as Mesh
participant N as Node
participant IB as Intellibox
M->>N: rn/loco/1234 velocity 40
N->>IB: OPC_LOCO_ADR 1234
IB-->>N: SL_RD_DATA slot 17 (IDLE or COMMON)
N->>IB: OPC_MOVE_SLOTS 17 17, null move
IB-->>N: SL_RD_DATA slot 17 IN_USE (or LONG_ACK 3A 00: refused)
N->>IB: OPC_WR_SL_DATA slot 17 with the node's throttle ID
N->>IB: OPC_LOCO_SPD slot 17, speed
N->>M: rn/loco/1234/event velocity 40
Note over N,IB: node resends speed every 50 s<br/>so the central does not purge the slot
When the loco leaves the district, the node releases the slot with OPC_SLOT_STAT1 set to COMMON: only the busy bits 4 and 5 change to 01, and the other bits stay as the slot master last reported them, so its speed steps for the loco are kept (B5 slot 13 for 128 steps, B5 slot 10 for 28 steps). A COMMON slot is still refreshed, so a train straddling the district border keeps running. Until 2026-10-29 the node sent B5 slot 03, which is FREE and forced 128 steps.
The node takes the loco only on a connection whose slots belong to an external slot master (--loconet-tcp-slots external, the default). The register leads for a held loco: the slot master's answer to the null move (speed 0) does not overwrite it.
The details, from JMRI's LocoNet code and an Intellibox I (§4.2):
-
Speed only after the confirmation. The node sends speed and functions once the slot master has confirmed the null move (the slot IN_USE). A refused null move (
LONG_ACK 3A 00) means another throttle has the slot: a conflict, and nothing is sent to that slot. A slot master that does not answer the null move at all is taken as having accepted it after 2 s. Several open null moves are answered in order, so a refusal, which does not name its slot, belongs to the oldest. -
Throttle ID. After the confirmation the node writes the slot (
OPC_WR_SL_DATA) with the loco's state and its throttle ID,Hand its RocNet address (48 65for 101), as JMRI marks its slots with its own ID. A slot found IN_USE with the node's own ID, for example after a restart, is carried on without a null move; one with another throttle's ID, or one the node saw a throttle drive, is a conflict. -
No answer to
OPC_LOCO_ADR: asked again every second, at most 20 times, as JMRI does. - Keep-alive every 50 s, as JMRI's throttles.
The test tool --transfer-loco <address> --to <node> [--speed 0-126] [--lights] puts a loco into a district, as a handoff will. Findings with Rocrail's slot server standing in for a central:
-
Rocrail answers a new
OPC_LOCO_ADRwith status IN_USE (0x30), where the PE gives a free slot. The node therefore refuses a loco only when a throttle on the same connection already drives that slot; it follows the connection's slot traffic, so it knows those slots. - Rocrail drives only locos in its loco table. For an address it does not know, it sends speed 0 to the slot about two seconds after the node took it, and the loco does not appear in Rocrail. With the loco in the table and assigned to the LocoNet interface, it runs at the speed the node set.
This is a limit of the bench setup, not of the design: in a district the node talks LocoNet to the central itself (Intellibox, Digitrax, Digikeijs, DCC-EX on the node board), and those let any address be called up, as their FREDs need. Rocrail is then a throttle or automation client next to them. To test transfers with Rocrail's slot server, put the locos in Rocrail's loco table first.
On 30 October 2026, through a GCA101, with an IB-Switch on the Intellibox's LocoNet:
| Test | The Intellibox |
|---|---|
OPC_LOCO_ADR 4444, a loco it had never seen |
Often 81 7E (BUSY) first, then a slot: IDLE (0x20), 28 steps |
| Null move on an IDLE or COMMON slot | The slot IN_USE |
| Null move on an IN_USE slot, also one with the node's own ID |
Refused, LONG_ACK 3A 00; protects its own throttle's loco (loco 3) |
OPC_WR_SL_DATA with a throttle ID |
LONG_ACK 6F 7F; the ID is kept, also after a release |
Release B5 slot 10
|
COMMON, speed steps and ID kept |
| Purge | A COMMON or IN_USE slot nobody touches becomes IDLE after about 2 minutes (the PE says 200 s). One that only had a null move stayed IN_USE for over 11 minutes |
| Keep-alive every 50 s | The held slot stays IN_USE (checked for 6.5 minutes) |
| District power | Follows GPON/GPOFF from the node (TRK 07/06); answers a GPON with its own |
| Slot 0 | ID bytes 49 42, "IB" |
| Its own throttle | Sends LOCO_SPD for its slot about every 50 s |
What the IB-Switch and the Intellibox send for turnouts is written up in How an Uhlenbrock IB-Switch works on LocoNet.
On a side the node is the slot master of (node), it answers for turnouts as the Intellibox does, so an IB-Switch or a Keyboard 6040 on Marklin-6040-LocoNet lights up on a node board with DCC-EX:
| Message | The node answers |
|---|---|
OPC_SW_STATE (BC) |
LONG_ACK 3C 30 closed or 3C 50 thrown, from the last switch command; never switched: thrown |
OPC_SW_REQ (B0) |
Nothing when taken; LONG_ACK 30 00 without track power; the position is remembered either way |
OPC_SW_ACK (BD) |
LONG_ACK 3D 7F, or 3D 00 without track power |
OPC_SW_REP (B1) output report |
Remembered as the position |
A panel as an occupancy display. With --show-sensors turnout=sensor,... the node sets a panel's turnout LEDs from sensors: green (closed) while the sensor is free, red (thrown) while it is occupied. It sends OPC_SW_REQ on and off for the turnout on both LocoNet sides whenever the sensor changes, and remembers the position, so a panel asking at start-up gets it too (a shown turnout starts green). Tested on 2026-10-31 with an IB-Switch on a GCA101 (--loconet-udp --loconet-connect-slots node, the node as the bus's only command station) and the layout simulator's occupancy: its key pairs 1 to 4, 11, 12, 18 and 19 (turnouts 111, 112, 137, 131, 121, 122, 126, 127) showed Adorf's three platform tracks, a border block and the four tracks of the Cdorf passing loop. The turnouts it shows must not be real turnouts or signals: their decoders would switch too.
The layout simulator (--web-sim --session <name>) sends each block's occupancy to its district's node (hive/<session>/sim/<district>/sensor, {"address": 204, "occupied": true}), which reports it on its LocoNet as OPC_INPUT_REP, as a real occupancy detector would.
With --dccex, a switch command taken with track power on also goes to DCC-EX as <a turnout 1|0> (closed 1). A Digitrax command station's start-up interrogate (turnouts 1017 to 1020) is ignored, as JMRI does. Only one command station may answer on a bus: the Intellibox or Hive-Rail, never both.
DCC long addresses run to 10239; addresses above that are reserved. The LocoNet slot data holds 14 bits, so it is not the limit. Four-digit throttles, FREDs among them, and many centrals stop at 9999, so 1 to 9999 is the safe range for a FREMO meeting. Address 0 is the analogue loco and is not used.
Summary of the PE message format, used by the loconet codec and to decode messages in the CLI log.
- The first byte is the opcode and is the only byte with bit 7 set.
- Bits 6 and 5 of the opcode give the length:
00= 2 bytes,01= 4 bytes,10= 6 bytes,11= the next byte is the byte count. - Bit 3 of the opcode set means a reply follows.
- The last byte is the checksum: the XOR of all bytes including the checksum is
0xFF. -
0x81(busy) is a filler a master sends to hold the bus; receivers ignore it.
flowchart LR
OPC["Opcode<br/>bit 7 = 1"] --> LEN{"bits 6 and 5"}
LEN -->|00| L2["2 bytes"]
LEN -->|01| L4["4 bytes"]
LEN -->|10| L6["6 bytes"]
LEN -->|11| LN["count byte follows"]
L2 & L4 & L6 & LN --> CHK["XOR of all bytes = 0xFF"]
| Opcode | Name | Arguments | Reply | Hive-Rail handling |
|---|---|---|---|---|
0x81 |
BUSY | none | none | Ignored: the Intellibox sends it before its answer to LOCO_ADR or RQ_SL_DATA, which follows |
0x82 |
GPOFF | none | none | Both ways: RocNet track power off. Unrequested from the central: short suspected |
0x83 |
GPON | none | none | Both ways: RocNet track power on |
0x85 |
IDLE | none | none | Both ways: RocNet halt |
0xA0 |
LOCO_SPD | slot, speed | none | From the central's throttles: RocNet velocity. Sent by the node for locos in its district |
0xA1 |
LOCO_DIRF | slot, direction and F0 to F4 | none | As LOCO_SPD: RocNet velocity direction and function F0 to F4 |
0xA2 |
LOCO_SND | slot, F5 to F8 | none | As LOCO_SPD: RocNet function F5 to F8 |
0xB0 |
SW_REQ | switch address, direction, on |
B4 only on failure |
Both ways: RocNet group 1 switch. Position remembered; as the command station: B4 30 00 without track power, and <a> to DCC-EX |
0xB1 |
SW_REP | turnout feedback | none | RocNet switch event; an output report is remembered as the position |
0xB2 |
INPUT_REP | sensor address, level | none | RocNet group 8 sensor report |
0xB4 |
LONG_ACK | opcode answered, code | none | Read: result of the node's own requests (3A 00: null move refused). Sent as the command station for BC, BD and a refused B0
|
0xB5 |
SLOT_STAT1 | slot, status | none | Read: keeps the node's view of the central's slots |
0xB6 |
CONSIST_FUNC | slot, functions | none | Not used in V1 |
0xB8 |
UNLINK_SLOTS | two slots | E7 |
Not used in V1 |
0xB9 |
LINK_SLOTS | two slots | E7 |
Not used in V1 |
0xBA |
MOVE_SLOTS | source, destination |
E7 or B4
|
Sent by the node: null move to take a slot it acquired |
0xBB |
RQ_SL_DATA | slot | E7 |
Sent by the node: read a slot, the clock (123) or track status |
0xBC |
SW_STATE | switch address | B4 |
Answered as the command station: 3C 30 closed, 3C 50 thrown |
0xBD |
SW_ACK | switch address, direction, on | B4 |
As SW_REQ, with acknowledge: 3D 7F, or 3D 00 without track power |
0xBF |
LOCO_ADR | address high, address low |
E7 or B4 fail |
Sent by the node to acquire a loco on its central |
0xE5 |
PEER_XFER | source, destination, 8 data bytes | none | Local only, logged |
0xE7 |
SL_RD_DATA | 10 slot bytes | none | Read: slot data from the central, including TRK power status and the clock |
0xED |
IMM_PACKET | raw DCC packet | B4 |
Local only |
0xEF |
WR_SL_DATA | 10 slot bytes | B4 |
Slot 123 clock from the mesh, slot 124 local programming; a taken loco's slot with the node's throttle ID |
Slot read (E7) and slot write (EF) carry the same 10 data bytes:
| Byte | Content |
|---|---|
| 0 | Slot number |
| 1 | Status 1: consist bits 6 and 3, slot state bits 5 and 4, speed step mode bits 2 to 0 |
| 2 | Loco address, low 7 bits |
| 3 | Speed: 0 = stop, 1 = emergency stop, 2 to 127 = speed |
| 4 | DIRF: bit 5 = forward, bit 4 = F0, bits 3 to 0 = F4 to F1 |
| 5 | TRK: bit 0 = track power, bit 1 = not paused, bit 2 = LocoNet 1.1 master, bit 3 = programming track busy |
| 6 | Status 2 |
| 7 | Loco address, high 7 bits (0 = short address) |
| 8 | SND: F5 to F8 |
| 9, 10 | ID1, ID2: ID of the throttle or PC using the slot |
Long acknowledge fail codes the central may send: no free slot for LOCO_ADR (B4 3F 00), illegal move (B4 3A 00), switch request failed (B4 30 00).
Every LocoNet message in the CLI log shows the bus, the raw bytes and a decoded line:
[ln ] BF 00 0C 4C LOCO_ADR address 12
[ln ] E7 0E 05 23 0C 00 20 07 00 00 00 00 00 .. SL_RD_DATA slot 5 IDLE address 12 speed 0 fwd
[ln ] BA 05 05 .. MOVE_SLOTS null move slot 5 -> IN_USE
[ln ] A0 05 28 .. LOCO_SPD slot 5 (address 12) speed 40 -> node-c
[ln ] B2 21 50 .. INPUT_REP sensor 67 high
flowchart LR
subgraph mac["MacBook"]
HM["Hive-Rail node<br/>LocoNet adapter"]
HM ---|"RS232 in LocoNet mode<br/>(USB-RS232 adapter)"| IB["Intellibox I (65000)"]
IB --- IS["IntelliSwitch"]
end
subgraph win["Windows PC"]
HW["Hive-Rail node"] <-->|"LocoNet over TCP<br/>(RocNet UDP later)"| RR["Rocrail"]
end
HM <-->|"Zenoh mesh"| HW
- Power on/off from Rocrail switches the Intellibox's district only; the other node shows the change. The Intellibox's stop key stops its own district; the global emergency stop from any node stops it too.
- A short on the Intellibox shows as
SHORTon both dashboards. - The node runs from
examples/config/intellibox.toml(cargo run -- --config examples/config/intellibox.toml), with the serial port set to the adapter's. - Hive-Rail's fast clock appears in Rocrail. The Intellibox I has no model clock as far as known (the Intellibox II and later do), so the config turns the fast clock off on its bus (L-Q9).
- IntelliSwitch points and outputs are controlled from Rocrail through the mesh.
The first stage 2 run (30 October 2026) used a GCA101 instead of the serial port: the node joined the Intellibox's LocoNet with --loconet-udp (examples/config/gca101.toml), a simulated second node took part, and --transfer-loco moved loco 4444 between them; §4.2 has the results.
First bench steps, with a loco on a short test track and the log kept (| tee ib-test.log):
- The node connects and sees the Intellibox's traffic and power.
- A loco driven from the Intellibox's own controls appears in the loco register (L-Q8).
-
--transfer-loco <address> --to <node> --speed 0takes it on the Intellibox (L-Q7). - Releasing it makes the slot COMMON: the loco keeps running at its speed, and the Intellibox's format for the loco is unchanged (§4).
| # | Question | How it will be answered |
|---|---|---|
| L-Q1 | How does the Intellibox signal a short on LocoNet: OPC_GPOFF, the TRK byte, or something else? |
Record in stage 2 |
| L-Q2 | Do real devices encode the fast clock minutes and hours exactly as the PE describes? | Partly answered: Rocrail and JMRI store minutes as (255 - (60 - m)) & 0x7F and hours as (256 - (24 - h)) & 0x7F, which is not what the PE text says. Check the Intellibox in stage 2 |
| L-Q3 | Functions above F8 are later LocoNet extensions not in the PE. Which does the Intellibox use? | Partly answered: Uhlenbrock sends F9 to F12 with 0xA3 and higher functions with 0xD4 (as JMRI decodes them); Hive-Rail ignores both so far. Record in stage 2 |
| L-Q4 | Which LocoNet USB interface is used, and does it need a specific baud rate or flow control? | Answered: the Intellibox I's own RS232 port in LocoNet mode, 19200 baud with RTS/CTS (§1, Serial interfaces) |
| L-Q5 | Can a Z21 in LocoNet slave mode still drive its own track? | Test with a real Z21 |
| L-Q6 | Do any clusters feed boosters over LocoNet RailSync? | Ask participants |
| L-Q7 | Does an Intellibox or Digitrax central answer a new OPC_LOCO_ADR with a free slot, as the PE says, and take a loco it has never seen with the right track format? |
Answered for the Intellibox I: an IDLE slot (BUSY first), 28 steps, and a null move takes it (§4.2). Digitrax still to test |
| L-Q8 | Does the Intellibox put the speed and functions set on its own controls on LocoNet? If not, the loco register cannot follow locos driven from the Intellibox | Partly answered: it sends LOCO_SPD for its throttle's slot about every 50 s, and the node reads the slot then (loco 3 appeared in the register). Turning its knob is still to be watched |
| L-Q9 | Does the Intellibox I (65000) keep a LocoNet fast clock? Uhlenbrock lists the model clock for the Intellibox II and later only | Answered: no. Slot 123 is an empty slot, and a written valid time is not kept (§4.2) |
| L-Q10 | Does an Intellibox with the IB-Control firmware work as a throttle on Hive-Rail's slot server? | Test with Hive-Rail as the command station |
| L-Q11 | Does DCC-EX's <a turnout 1> switch a real accessory decoder the right way round for LocoNet's closed? |
Test on the node board |