Skip to content

LocoNet Adapter

Timo Sariwating edited this page Nov 2, 2026 · 10 revisions

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) or node (the node answers LOCO_ADR, MOVE_SLOTS and RQ_SL_DATA with 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), and OPC_INPUT_REP from the bus or the layout simulator reported on the mesh as a RocNet sensor report (group 8 on rn/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 off keeps the fast clock off a bus whose central has none.

Contents

  1. Role
  2. Translation to RocNet
  3. Power and short detection
  4. Loco commands on a LocoNet central
  5. LocoNet message reference
  6. Stage 2 test setup
  7. Open questions

1. Role

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"]
Loading
  • 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 with SEND / RECEIVE lines, or raw binary, port 1234 by default).

Serial interfaces

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.

Fast clock in slot 123, as recorded from Rocrail

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.

2. Translation to RocNet

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.

3. Power and short detection

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).

4. Loco commands on a LocoNet central

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
Loading

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, H and its RocNet address (48 65 for 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.

4.1 Tested with Rocrail's slot server

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_ADR with 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.

4.2 Tested with an Intellibox I

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.

4.3 Turnouts, with the node as the command station

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.

4.4 Loco addresses

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.

5. LocoNet message reference

Summary of the PE message format, used by the loconet codec and to decode messages in the CLI log.

5.1 Framing

  • 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"]
Loading

5.2 Opcodes

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

5.3 Slot data

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).

5.4 CLI log format

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

6. Stage 2 test setup

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
Loading
  • 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 SHORT on 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):

  1. The node connects and sees the Intellibox's traffic and power.
  2. A loco driven from the Intellibox's own controls appears in the loco register (L-Q8).
  3. --transfer-loco <address> --to <node> --speed 0 takes it on the Intellibox (L-Q7).
  4. Releasing it makes the slot COMMON: the loco keeps running at its speed, and the Intellibox's format for the loco is unchanged (§4).

7. Open questions

# 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

Clone this wiki locally