Repository navigation
FRED Throttles and the Distributed Master
Note: two parts of this page are superseded. §6.6 describes layout-wide power: power is per district (RocNet on the Mesh §6, decision R10). The fast clock is set by Hive-Rail, not Rocrail (RocNet on the Mesh §7, decision R11).
Status: started (2026-10-29). The Hive-Rail Node Board removes the slot master conflict: its LocoNet is driven only by a LocoNet buffer, so Hive-Rail is the only slot master on each module's LocoNet. The loco register with layout-wide throttle numbers, and the translation of Rocrail's or a central's own slots, are implemented (§14). The rest of this page is the design for what follows; dispatch is the next step.
Superseded parts: loco commands, power, clock and device routing now travel as RocNet frames (see RocNet on the Mesh); the Zenoh keys in §5.1 for those are replaced by the
rn/...keys there. LocoNet framing and opcodes are in the LocoNet Adapter. What remains specific to this page: the throttle bus cabling rule, throttle slots, dispatch, purge and reconnecting a FRED.Protocol behaviour is based on the Digitrax LocoNet Personal Use Edition 1.0 (PE), which is licensed for personal use only and not stored in this repository.
Contents
- Use case
- Cluster topology and cabling rule
- The distributed master
- Two kinds of slots
- Routing: only what applies locally
- Message flows
- Throttle slot allocation without a central server
- Command station backends
- Conflict detection
- Test setups
- Design decisions
- LocoNet message reference
- Open questions
At a FREMO meet many participants join their modules into one layout. Today that needs one central LocoNet command station for the whole layout. Operators walk with their train: they plug a FRED or Fredi into the LocoNet socket on any module, unplug it, walk on, and plug it in again further down the line. The loco keeps running in the meantime.
Hive-Rail replaces the single central with one command station per cluster, coordinated over the Zenoh mesh with no central server. For the operator nothing changes:
- A FRED or Fredi plugged into any module socket drives its loco, wherever that loco is.
- Dispatching works as before.
- Points, buttons, signals and module actions (windmill, fire engine lights) work as before.
- The whole meet shares one fast clock.
In practice most clusters use DCC-EX as their local command station. LocoNet command stations (Intellibox, Digitrax, Z21) are supported by design through the same LocoNet link.
A FRED answers to whichever slot master is on its LocoNet wire, and a command station cannot switch its slot master off. Hive-Rail therefore separates the two by moving one cable: the module LocoNet plugs into the Hive-Rail node instead of into the command station.
flowchart LR
subgraph today["Today: one central"]
direction LR
F1["FRED / Fredi"] --- ML1["Module LocoNet<br/>detectors, decoders"]
ML1 --- CS1["Central command station<br/>slot master"]
CS1 ==>|DCC| T1["Track"]
end
subgraph hive["Hive-Rail cluster"]
direction LR
F2["FRED / Fredi"] --- ML2["Module LocoNet<br/>detectors, decoders"]
ML2 ---|"throttle bus<br/>node is the only slot master"| N["Hive-Rail node"]
N ---|"private link<br/>USB serial, TCP or DCC-EX serial"| CS2["Local command station<br/>DCC-EX or LocoNet central"]
CS2 ==>|DCC| T2["Track"]
end
Cabling rule: the module LocoNet plugs into the Hive-Rail node. Nothing else plugs into the command station's LocoNet sockets. The node's LocoNet interface must be able to run a LocoNet without a command station on it (for example the standalone mode of a Digitrax PR3/PR4).
| Bus | Who is on it | Slot master |
|---|---|---|
| Throttle bus (module LocoNet) | FREDs, Fredis, detectors, accessory decoders, the node | The node, acting for the distributed master |
| Command station link | The node and the local command station only | The local command station; its slots are private to the node |
The distributed master is the one virtual LocoNet command station that every throttle sees. It does not run on one machine: every node runs a part of it, and the parts stay consistent over the mesh.
flowchart TB
subgraph mesh["Zenoh mesh: distributed master"]
TS["Throttle slots<br/>slot number to loco address"]
DS["Dispatch slot"]
FC["Fast clock"]
RT["Routing table<br/>loco address to node<br/>device name to node"]
end
subgraph A["Cluster A"]
NA["Node A"] --- CSA["DCC-EX"]
FA["FREDs"] --- NA
end
subgraph B["Cluster B"]
NB["Node B"] --- CSB["Intellibox<br/>via LocoNet USB"]
FB["FREDs"] --- NB
end
subgraph C["Cluster C"]
NC["Node C"] --- CSC["DCC-EX"]
FC2["FREDs"] --- NC
end
NA <--> mesh
NB <--> mesh
NC <--> mesh
| The distributed master owns | The local node owns |
|---|---|
| Throttle slot numbers that FREDs and Fredis see | Its command station and that station's own slots |
| The dispatch slot | Its module devices: points, signals, buttons, actions |
| The fast clock | The programming track |
| The routing table | Applying commands to its own track |
pie showData
title LocoNet slot numbers 0 to 127
"Dispatch slot 0" : 1
"Loco slots 1 to 119" : 119
"System slots 120 to 127" : 8
| Throttle slots | Command station slots | |
|---|---|---|
| Owned by | The distributed master | Each local command station |
| Seen by | FREDs and Fredis on any throttle bus | Only the node attached to that command station |
| Range | 0 to 127 (7-bit). No expanded slots | Whatever the command station supports. DCC-EX has none |
| Content on the mesh | Slot number, loco address, status, holding node | Only the loco addresses in the table. Never slot numbers or speed state |
| Lifetime | Until released or purged | Managed by the node: acquired when a loco enters the district, released when it leaves |
System slots:
| Slot | Use | Handling |
|---|---|---|
| 0 | Dispatch | Owned by the distributed master |
| 123 (0x7B) | Fast clock | Calculated by every node from the shared clock (6.5) |
| 124 (0x7C) | Programming track | Always local. Goes only to the node's own command station. Never on the mesh |
| 127 (0x7F) | Command station options | Always local |
| 120 to 122, 125, 126 | Reserved | Neither stored nor routed |
Because throttle slot numbers come from the distributed master, slot 5 means the same loco on every throttle bus. A FRED that is unplugged and plugged in on another cluster keeps working.
DCC addresses are the operators' responsibility. Every loco decoder is programmed with its address before the meet, and Hive-Rail does not check that addresses are unique. It only guarantees one throttle slot per address.
The mesh carries everything, but each node only puts on its own LocoNet and track what concerns its own modules. LocoNet runs at 16.6 kbit/s, so copying the whole meet's traffic onto every throttle bus would flood it.
| Traffic | Where it goes |
|---|---|
| Loco commands (speed, direction, functions) | To the node whose command station has the loco (the district owner), plus the destination node during a handoff |
| Loco state feedback (for example a stop from elsewhere) | Back to every node where a throttle holds that slot |
| Points, signals | To the node that owns the module, by device name. Raw accessory addresses never leave their cluster, because every owner numbers from 1 |
| Buttons, sensors | Local. Published by name only when another node needs them, for example boundary sensors for a handoff |
| Module actions (windmill, fire engine lights) | To the owning node by device name |
| Power on / off / emergency stop | Every node, Priority 0 |
| Short circuit | Reported to every node. Only the affected district loses power |
| Fast clock | Every node calculates it locally from the shared clock settings |
| Programming track | Never leaves the node |
flowchart TD
IN["LocoNet message received<br/>on the throttle bus"] --> K{"Message type"}
K -->|"slot request, slot read or write,<br/>dispatch"| TSL["Answer from the<br/>distributed master"]
K -->|"speed, direction, functions"| ADR["Throttle slot to loco address"]
ADR --> OWN{"Is the loco in<br/>this node's district?"}
OWN -->|yes| LCS["Apply to the local<br/>command station"]
OWN -->|no| MESH["Publish on loco/addr/cmd<br/>the district owner applies it"]
K -->|"point or action request"| DEV{"Device owned by<br/>this node?"}
DEV -->|yes| LDEV["Apply locally"]
DEV -->|"no, mapped to a remote device"| RDEV["Publish on dev/node/name/cmd"]
K -->|"power off, emergency stop"| ALL["Apply locally and<br/>broadcast Priority 0"]
K -->|"programming track"| PRG["Local command station only"]
K -->|"sensor report"| SEN["Local sensor model<br/>publish only if needed"]
These extend the table in Architecture §2.3.
| Key expression | Published by | Content | Priority |
|---|---|---|---|
hive/{session}/tslot/{n} |
The node holding the block that contains slot n | Throttle slot entry: address, status, holding node | 1 |
hive/{session}/tslot-block/{b} |
The node claiming block b | Block claim (7) | 1 |
hive/{session}/dispatch |
The node that put a loco into dispatch | Dispatched address, or empty | 1 |
hive/{session}/loco/{address}/district |
District owner | Which node currently powers the loco | 1 |
hive/{session}/loco/{address}/cmd |
Node of the throttle holding the slot | Speed, direction, functions | 2 |
hive/{session}/loco/{address}/state |
District owner, also as a Zenoh queryable | Speed, direction and functions actually applied. Used for throttle feedback and to answer slot reads after a FRED is plugged in again | 2 |
hive/{session}/node/{id}/locos |
Every node | Loco addresses in its command station's slot table | 1 |
hive/{session}/node/{id}/devices |
Every node | Named points, signals, buttons, actions from its profile | 1 |
hive/{session}/dev/{node}/{device}/cmd |
Any node | Command for a named device | 1 |
hive/{session}/dev/{node}/{device}/state |
Owning node | Device state and button presses | 1 |
hive/{session}/clock |
The node where the clock was last set | Fast clock settings (6.5) | 1 |
hive/{session}/power |
Any node | Requested layout power: on, off or emergency stop, with sequence number, source and reason (6.6) | 0 |
hive/{session}/node/{id}/power |
Every node | Actual power of its district, including Short (6.6) |
0 |
Most of the routing is done by Zenoh itself. A node subscribes to loco/{address}/cmd only for the locos in its district or being handed to it, and to dev/{its id}/** for its own devices. When a loco is handed over, the destination subscribes and the source unsubscribes. The throttle's node never needs to know where the loco is.
sequenceDiagram
autonumber
participant D as Dispatcher<br/>throttle or PC on node A
participant NA as Node A
participant M as Mesh
participant NC as Node C<br/>district owner
participant CS as DCC-EX on C
participant F as FRED on node B
participant NB as Node B
D->>NA: put address 1234 into dispatch
NA->>M: tslot 5 = 1234, dispatch = 1234
F->>NB: dispatch get
NB->>M: take dispatch, tslot 5 in use by node B
NB-->>F: slot 5 data
F->>NB: speed 40 on slot 5
NB->>M: loco/1234/cmd speed 40
M->>NC: loco/1234/cmd
NC->>CS: set speed for 1234
NC->>M: loco/1234/state speed 40
The PE defines what a throttle does after reconnecting: if it kept its slot state, it requests the full status of its old slot. It continues using the slot only if the returned status, speed and functions exactly match what it remembers. Otherwise it assumes the slot was purged and logs on again.
So the node where the FRED is plugged in must answer with the loco's current state, which lives with the district owner. While it asks the mesh, the node holds the bus with OPC_BUSY, as the PE allows a master to do.
sequenceDiagram
autonumber
participant F as FRED holding slot 5
participant NB as Node B
participant ND as Node D
participant M as Mesh
participant NC as Node C<br/>district owner
F->>NB: speed 40 on slot 5
NB->>M: loco/1234/cmd
Note over F,NB: operator unplugs the FRED<br/>and walks on
Note over NC: loco keeps running at speed 40
F->>ND: request slot data, slot 5
ND-->>F: OPC_BUSY while it asks the mesh
ND->>M: tslot 5 = 1234, get loco/1234/state
M->>NC: query
NC-->>ND: speed 40, forward, F0 on
ND-->>F: slot data: in use, 1234, speed 40
Note over F: matches its memory, keeps the slot
ND->>M: tslot 5 holder = node D
F->>ND: speed 60 on slot 5
ND->>M: loco/1234/cmd speed 60
M->>NC: loco/1234/cmd
If the loco's state changed while the FRED was unplugged, for example by an emergency stop, the data does not match and the FRED logs on again. That is the behaviour the PE intends.
sequenceDiagram
autonumber
participant NB as Node B<br/>throttle node
participant M as Mesh
participant NC as Node C<br/>source district
participant ND as Node D<br/>destination district
NB->>M: loco/1234/cmd
M->>NC: delivered, C applies it
NC->>ND: propose 1234 over the seam
ND->>M: subscribe loco/1234/cmd
ND->>ND: acquire 1234 on its command station<br/>pre-load speed and functions
Note over NC,ND: transition zone takeover
ND->>M: loco/1234/district = D
NC->>M: unsubscribe loco/1234/cmd
NC->>NC: release 1234 on its command station
NB->>M: loco/1234/cmd
M->>ND: delivered, D applies it
The handoff itself is defined in the Transition Zone Specification.
sequenceDiagram
autonumber
participant T as Throttle or panel<br/>on node A
participant NA as Node A
participant M as Mesh
participant NC as Node C
participant LN as Module LocoNet<br/>or DCC on C
T->>NA: request for device mapped to C/windmill
NA->>M: dev/C/windmill/cmd on
M->>NC: dev/C/windmill/cmd on
NC->>LN: local address from C's profile
NC->>M: dev/C/windmill/state on
M->>NA: state on
Requests for a node's own devices, which is the normal case, never leave that node. A switch request on the throttle bus for a decoder powered by DCC is turned by the node into an accessory command on its command station, because there is no LocoNet command station on the throttle bus to do it.
No node sends clock ticks over the mesh. The clock is stored as settings:
{ "rate": 4, "running": true, "fast_ref": "06:00:00", "real_ref": "2026-10-28T10:00:00Z" }Every node calculates the current fast time as fast_ref + rate × (now − real_ref), answers slot 123 reads on its throttle bus, and writes it to a LocoNet command station on its private link. Setting the clock from any throttle (a slot write to slot 123) updates the settings for the whole mesh. Node clocks must agree, either through NTP on the meet network or Zenoh's hybrid logical clock timestamps.
The PE says display devices keep their own clock calculation and only use slot 123 to re-synchronise. One device should send a sync read every 70 to 100 seconds; on each node that device is the node itself, on its own throttle bus.
| Slot 123 byte | Meaning |
|---|---|
CLK_RATE |
0 = clock stopped, 1 = real time, up to 127 |
FRAC_MINSL, FRAC_MINSH
|
Sub-minute counter, internal to the clock generator |
MINS |
Fast minutes, stored as 256 minus minutes |
TRK |
Track status byte, as in every slot read |
HRS |
Fast hours, stored as 256 minus hours |
DAYS |
Number of 24 h roll-overs |
CLK_CNTRL |
Bit 6 = valid clock data |
ID1, ID2
|
Device that last set the clock |
flowchart LR
SET["Throttle writes slot 123<br/>on any throttle bus"] --> NODE["That node publishes<br/>hive/session/clock"]
NODE --> ALL["Every node stores<br/>rate and reference"]
ALL --> CALC["Each node calculates<br/>fast time locally"]
CALC --> SYNC["Sync read on its throttle bus<br/>every 70 to 100 s"]
CALC --> CSW["Write to a LocoNet central<br/>on its private link"]
This is the first feature to implement. It needs no slots, works with any command station, and gives every operator the same view of the layout's power.
Global power. OPC_GPON, OPC_GPOFF and OPC_IDLE (emergency stop) from any throttle bus, from Rocrail or from a node's own controls set the requested layout power. It is published on hive/{session}/power at Priority 0 with a sequence number, the source node and the reason. Every node applies it to its own command station and echoes it on its own throttle bus, so throttle displays and Rocrail stay correct.
Short circuits are reported, not spread. A short trips only the affected district, as with boosters today. The node that detects it publishes the actual state of its district on hive/{session}/node/{id}/power and in its heartbeat. Every node shows it, and the neighbours set their boundary signals to Stop (Architecture §4.2). Switching the whole layout off on a short is an optional setting, off by default.
| State | Meaning |
|---|---|
On |
Track power on |
Off |
Switched off on request |
EmergencyStop |
Power on, all locos stopped (OPC_IDLE) |
Short |
Power lost without a request: short circuit suspected |
Unknown |
No contact with the command station |
How a node detects a short depends on its command station:
| Command station | Detection |
|---|---|
| DCC-EX | Power-off report that the node did not request (Architecture §7.2) |
| LocoNet central |
OPC_GPOFF from the central, or the power bit in the TRK byte dropping, without a request from the node. To confirm with the Intellibox Basic in stage 2 |
sequenceDiagram
autonumber
participant T as Throttle or Rocrail<br/>on node A
participant NA as Node A
participant M as Mesh
participant NB as Node B
participant CSB as Command station B
T->>NA: OPC_GPOFF
NA->>M: power = Off, seq 17, from A
M->>NB: power = Off
NB->>CSB: power off
NB->>NB: echo OPC_GPOFF on its throttle bus
NB->>M: node B power = Off
Note over NA,CSB: later, a short on B
CSB-->>NB: power lost, not requested
NB->>M: node B power = Short, Priority 0
M->>NA: node B power = Short
Note over NA: shows SHORT for node B,<br/>boundary signal towards B at Stop
stateDiagram-v2
[*] --> Unknown
Unknown --> Off: command station connected
Off --> On: power on requested
On --> Off: power off requested
On --> EmergencyStop: emergency stop requested
EmergencyStop --> On: power on requested
EmergencyStop --> Off: power off requested
On --> Short: power lost without request
Short --> Off: short cleared
Short --> On: power on requested after clearing
On --> Unknown: command station lost
Off --> Unknown: command station lost
The terminal dashboard shows the whole layout at a glance and keeps raw LocoNet traffic in its own pane. Second priority, after power, is the fast clock in the header.
┌ HIVE-RAIL ─ node mac ─ session bench ──────────────── fast clock 06:42 ×4 ┐
│ LAYOUT POWER ON (set by windows, 10:02:13) │
├ Nodes ────────────────────────────────────────────────────────────────────┤
│ Node Heartbeat Power Command station Locos Last event │
│ mac local ON DCC-EX (sim) 2 - │
│ windows ONLINE 80ms SHORT DCC-EX (sim) 1 short 10:04:51 │
├ Events ───────────────────────────────────────────────────────────────────┤
│ 10:04:51 windows SHORT district power lost without request │
│ 10:02:13 windows POWER layout ON (OPC_GPON from Rocrail) │
├ LocoNet ──────────────────────────────────────────────────────────────────┤
│ [tbus] 83 7C GPON power on │
│ [tbus] A0 05 28 .. LOCO_SPD slot 5 (address 12) speed 40 │
└ q quit Tab switch pane Up/Down scroll End follow ─────────────────────┘
- Header: node, session, fast clock with rate, and the layout power state with who set it and when.
-
Nodes: one row per node, including its power state.
SHORTandUnknownare shown in red. - Events: human-readable events only: power, shorts, nodes joining and leaving, conflicts.
-
LocoNet: raw bytes and the decoded line (12.4), with
--plainprinting the same lines as a log.
Implementation note (2026-10-29): the first implementation uses one allocator (the lowest online node) instead of slot blocks; see §14. The block scheme below remains the target, because it stays correct during a network split.
The distributed master needs slot numbers to be unique across the mesh without a central server and without waiting for the whole mesh on every request.
- Slots 1 to 119 are split into blocks, for example 15 blocks of 8.
- A node claims a free block on
tslot-block/{b}, using the settle rule from Architecture §6.4: a short settle window, lowest node ID wins. - A throttle on the node's bus asks for an address. The node first checks whether a throttle slot for that address already exists anywhere on the mesh. If not, it takes a free slot from its own blocks and answers at once.
- A node claims another block when it runs out and gives blocks back when they are empty.
- If a node disappears, its slot entries stay on the mesh (Zenoh storage), locos keep running and FREDs keep working. The node with the lowest ID adopts the orphaned blocks.
- During a network partition each half allocates only from blocks it already holds, so slot numbers cannot collide. Blocks claimed on both sides during the split go to the lowest node ID on merge; the losing side's new slots are moved and its throttles are told the slot is no longer valid.
The PE defines four slot states in bits 5 and 4 of the status byte. Hive-Rail uses them unchanged:
| State | Bits | Loco address in slot | Refreshed on the track |
|---|---|---|---|
| FREE | 00 | No | No |
| COMMON | 01 | Yes | Yes, the loco keeps running |
| IDLE | 10 | Yes | No |
| IN_USE | 11 | Yes | Yes |
stateDiagram-v2
[*] --> Free
Free --> Idle: throttle requests an address<br/>new slot, speed 0, forward
Idle --> InUse: null move by the throttle
Common --> InUse: null move by the throttle
InUse --> Dispatched: holder moves the slot to slot 0
Dispatched --> InUse: dispatch get by any throttle
InUse --> Common: purge time without access<br/>loco keeps running
InUse --> Common: released by the throttle
Common --> Idle: stopped and released
Idle --> Free: loco removed from the layout
Free --> [*]
- A throttle activates a slot with a null move (
OPC_MOVE_SLOTSwith source and destination equal). - The dispatch slot is a one-deep stack: one slot in the whole system is marked as dispatched. Hive-Rail keeps exactly one for the whole mesh.
- Purging: a slot not accessed within the purge time (about 200 s on a Digitrax master) becomes COMMON. It is still refreshed, so the loco keeps running. Throttles are expected to resend their speed about every 100 s to keep their slot; JMRI's throttles do it every 50 s. An Intellibox I purges sooner: an untouched slot becomes IDLE after about 2 minutes (measured on 30 October 2026, see the LocoNet adapter §4.2), so Hive-Rail keeps the slots it holds on a central alive every 50 s. Hive-Rail as the slot master uses a configurable purge time, 200 s by default.
A throttle slot in use is what the Architecture calls the loco lease. The 2 s dead-man timeout from §6.3 does not apply to LocoNet throttles, because an unplugged FRED is normal operation. A loco driven by a LocoNet throttle stops only on an explicit command, an emergency stop, or a fault. Purging never stops a loco.
The node drives its local command station through one driver trait (Architecture §7.1). The command station only ever holds the locos of its own district plus incoming handoffs.
| Backend | Link | Slots | Status |
|---|---|---|---|
| DCC-EX | USB serial, text commands | None, addressed by loco address | Primary. Most clusters use this |
| LocoNet central (Intellibox, Digitrax, Z21) | LocoNet over USB interface or TCP | The central's own, mapped privately by the node | Supported by design |
DCC-EX mapping
| Hive-Rail action | DCC-EX command |
|---|---|
| Speed and direction |
<t address speed direction>. LocoNet speed 0 is stop, 1 is emergency stop (-1), 2 to 127 become 1 to 126 |
| Function | <F address function state> |
| Loco leaves the district |
<- address> frees its entry |
| Accessory | <a address subaddress state> |
| Track power |
<1> / <0>
|
| Capacity check |
<#> reports how many locos it can hold |
LocoNet central mapping: the node requests the address on its private link and receives the central's own slot number, for example 17. It keeps a private map, throttle slot 5 to address 1234 to central slot 17, and keeps that slot alive so the central does not purge it.
Known capacities (simultaneous locos, to be confirmed per firmware):
| Command station | Locos |
|---|---|
| DCC-EX | 50 by default (30 on small boards), set at compile time |
| Intellibox Basic | 32 |
| Z21 (black) | 100 |
| Digitrax DCS240 | 120 standard slots |
| Digitrax DCS52 | about 20 |
A cluster rarely has more than a dozen locos on its track. If the command station is full, the node holds the incoming handoff at Stop and raises an alarm.
| Situation | Detected by | Reaction |
|---|---|---|
| Slot replies on the throttle bus that the node did not send | Node | A second slot master is on the module LocoNet. Alarm; the node stops answering throttle requests on that bus until the operator fixes it |
| Traffic on the command station link for slots not in the node's map | Node | Something else drives the central (built-in knobs, extra handheld). Alarm |
| One loco address reported by two nodes outside a handoff | Every node | Duplicate presence (Architecture §6.2). Both hold the loco at speed 0 and raise an alarm |
| Throttle slot claimed by two nodes after a partition | Every node | Lowest node ID keeps it; the other throttle is told its slot is invalid |
| Command station full | Destination node | Handoff held at Stop, alarm |
Degraded mode (not recommended). If a cluster cannot separate the throttle bus from its LocoNet central, the node can only listen. FREDs then get locos through the central's own dispatch and the node maps the central's slot numbers. Moving a FRED to another cluster does not work in this mode.
flowchart LR
subgraph mac["MacBook"]
HM["Hive-Rail node<br/>simulated DCC-EX"]
end
subgraph win["Windows PC"]
HW["Hive-Rail node<br/>simulated DCC-EX<br/>LocoNet over TCP server, port 1234"]
RR["Rocrail<br/>virtual command station"]
RR <-->|"LocoNet over TCP"| HW
end
HM <-->|"Zenoh mesh on the LAN"| HW
- The Windows node offers a LocoNet over TCP server in the LbServer text format (
SEND/RECEIVElines). Rocrail connects to it as a LocoNet client and acts as throttle and dispatcher on that node's throttle bus. - Both nodes run a simulated DCC-EX, so loco commands can be followed end to end: Rocrail on Windows dispatches and drives a loco whose district is on the MacBook.
- Stage 1 is built in this order:
-
Global power and short reporting (6.6): Rocrail switches power on and off on both nodes; a simulated short on one node shows as
SHORTon both dashboards. - Fast clock (6.5) in the dashboard header on both nodes, set from Rocrail.
-
Clear CLI view (6.7): decoded LocoNet log, event pane,
--plainoutput. - Throttle slots, dispatch, routing to the district owner and state feedback.
- Node restart and FRED-style reconnection (6.2).
-
Global power and short reporting (6.6): Rocrail switches power on and off on both nodes; a simulated short on one node shows as
flowchart LR
subgraph mac["MacBook"]
HM["Hive-Rail node"]
HM ---|"LocoNet USB interface"| IB["Intellibox Basic<br/>LocoNet central on the private link"]
IB --- IS["IntelliSwitch<br/>LocoNet accessory hardware"]
end
subgraph win["Windows PC"]
HW["Hive-Rail node"] <--> RR["Rocrail"]
end
HM <-->|"Zenoh mesh"| HW
- The Intellibox Basic is the first real LocoNet central behind a node, which tests the private slot mapping.
- IntelliSwitch tests points and module actions routed by device name.
- The Intellibox Basic is listed with 32 locos, which is enough for this setup.
| # | Decision |
|---|---|
| D1 | Each local command station carries only the locos routed to it: its own district plus incoming handoffs |
| D2 | Throttle slot numbers are limited to 0 to 127 (7-bit). Digitrax expanded slots are not supported |
| D3 | The fast clock is part of the distributed master and shared by the whole mesh |
| D4 | The programming track (slot 124) and command station options (slot 127) are always local |
| D5 | Unique DCC addresses are the operators' responsibility. Hive-Rail guarantees one throttle slot per address |
| D6 | Only the distributed master assigns the slot numbers throttles see. Local command station slots stay private to their node |
| D7 | Throttles connect only to the node's throttle bus. A second slot master there, or unknown slots on a command station link, raises an alarm and is never resolved silently |
| D8 | The mesh holds a routing table, not a copy of every slot table. Nodes share only loco addresses and named devices |
| D9 | Each node only puts on its LocoNet and track what concerns its own modules |
| D10 | DCC-EX is the primary local command station. LocoNet centrals are supported through the same driver trait |
Moved to the LocoNet Adapter §5.
Answered by the PE:
| # | Question | Answer |
|---|---|---|
| L1 | What does a throttle send after being plugged in again? | It requests the status of its old slot and keeps it only if status, speed and functions match exactly (6.2) |
| L2 | How long may the master take to answer? | No fixed timeout in the PE. The master may hold the bus with OPC_BUSY while it prepares the reply |
| L3 | What happens to a moving loco when its slot is purged? | The slot becomes COMMON and is still refreshed, so the loco keeps running (7.1) |
| L4 | One dispatch slot or one per cluster? | The PE defines one dispatch slot for the whole system. Hive-Rail keeps one for the whole mesh |
Still open:
| # | Question | How it will be answered |
|---|---|---|
| L5 | Which DCC-EX loco limit and track packet rate are needed per cluster? | Check <#> and measure in simulation |
| L6 | How exactly does Rocrail connect to LocoNet over TCP (client of an LbServer-style server)? | Stage 1 test |
| L7 | Can a Z21 in LocoNet slave mode still drive its own track? If so, a Z21 could sit directly on the throttle bus | Test with a real Z21 |
| L8 | Do any clusters feed boosters over LocoNet RailSync? Such boosters must stay on the command station side | Ask participants |
| L9 | Do real devices encode the fast clock minutes and hours exactly as the PE describes? | Record slot 123 traffic from the Intellibox Basic in stage 2 |
| L10 | Functions above F8 and long-address details are later LocoNet extensions not in the PE. Which do FREDs, Fredis and Rocrail use? | Record traffic in stage 1 and 2 |
| L11 | How long can a FRED stay unplugged before its slot is purged and it must log on again? Is 200 s enough for walk-around operation? | Ask operators, make the purge time configurable |
Why the node board solves the slot master conflict. A FRED answers to whichever slot master is on its LocoNet wire, and a LocoNet central cannot switch its slot master off (§2). On the node board, DCC-EX does not speak LocoNet at all: it is the command station over its own serial or WiFi link. The board's LocoNet-T is driven only by the LocoNet buffer, so Hive-Rail is the only slot master on that wire. The board's LocoNet-T is the "throttle bus" of §2, without extra hardware. Modules with a LocoNet central keep the original problem; for them §2 still applies.
What a FRED does (LocoNet PE §3.2.1 and the FREMO FRED manual):
- A FRED cannot select a loco. A loco is selected and dispatched on another throttle (on a DT100: SEL/SET, then MODE/DISP: a slot move to slot 0), and the FRED takes it with EMERGENCY STOP plus F0, F1 or F2 (a slot move from slot 0). It remembers that slot number, not the address.
- Plugged in again, it requests the status of its old slot and keeps it only if status, speed and functions exactly match its memory; otherwise it assumes the slot was purged. LEDs: flashing yellow = a loco is allocated after plugging in, green = reconnect successful, red = no loco assigned (dispatch again). A FRED with an analog knob also compares the knob with the current speed and flashes green ("turn to zero") until they match; a newer FREDI with a rotary encoder needs no such step.
- A central purges a slot after about 200 s without activity, or turns it COMMON (an Intellibox I after about 2 minutes, to IDLE).
So on a throttle bus the slot numbers must be layout-wide and answered with the loco's current state. Connections with their own slot master (Rocrail, a central) ask by address (OPC_LOCO_ADR), so their slots stay theirs and are translated (§4: throttle slots versus command station slots).
A FREMO session with node boards:
- Dispatch. The dispatcher (Rocrail, a DT100 or another throttle) dispatches loco 1234; at module A the FRED takes it and gets throttle number 7, which belongs to the whole layout.
- Driving. The FRED's speed for throttle 7 goes into A's LocoNet. Hive-Rail looks up throttle 7, finds loco 1234, and sends the speed to the district that holds the train (README 8.4).
- District change. The transition zone hands the loco from A's command station to B's. The FRED does not notice.
- Unplug and walk. The train keeps running at its last speed; LocoNet throttles are exempt from the dead-man timeout.
- Plug in at module C. The FRED asks for slot 7 again; Hive-Rail at C finds throttle 7 in the register and answers with loco 1234 and its current state, so the FRED keeps it.
Implemented (2026-10-29), src/locos.rs:
-
Loco register (the mesh): every loco by DCC address, with status, speed, direction, F0 to F8, a version, the node whose LocoNet drives it, and its throttle number if it is on a throttle bus. Every node keeps a copy; changes go out on
hive/{session}/locos/update, the higher version wins, ties go to the higher node id. Only addresses and state travel on the mesh, never a local slot number. -
Throttle numbers are handed out by one allocator, the online node with the lowest id (the fast clock master too). A node that gets
OPC_LOCO_ADRon a throttle bus for a loco without a number asks onhive/{session}/locos/requestand answers its throttle when the number arrives; it gives up after 2 s and the throttle asks again. A loco with a number gets the same number on every node. -
Slots per LocoNet connection. Each connection has a slot owner:
node(the node answers slot requests with throttle numbers: the board's LocoNet with FREDs, simple throttles, the simulation) orexternal(a slot master on the connection owns the slots: Rocrail's slot server, an Intellibox, a DR5000, a Digitrax station). On an external connection the node answers no slot request; it learns local slot → loco address from the slot master'sOPC_SL_RD_DATAand follows speed, direction and functions by address. Defaults: LocoNet TCP clientsexternal(--loconet-tcp-slots), the node board's busnodeand a serial interface on a centralexternal(--loconet-connect-slots). - Translation between connections. Slot messages are never passed on as they are, because their numbers belong to one connection. A change on one connection (or from another node) is sent to the other connection in its own numbering: Rocrail's slot 7 and a FRED's throttle 1 for loco 1234 hear each other's speed and functions. Power, switches and sensors are passed on as before.
- On a
nodeconnection every real change is answered with the new slot data, and repeated messages publish nothing. - A node that comes online gets the whole register from the allocator.
- The dashboard lists each loco under the node whose LocoNet drives it.
-
Rocrail and slot servers: tested on 2026-10-29. With its slot server off, Rocrail sends every function message with only the key just pressed and resends its own state a few seconds later, so functions go off again. It builds function messages from its own slot copy, which only its slot server keeps up to date; replies from Hive-Rail do not change that. Hence the
externaldefault: Rocrail's slot server on, and Hive-Rail translating.
Seeing the register:
-
Dispatcher screen for the Zugleiter,
--mode dispatcher: every district, the fast clock, the allocator (★) and the whole loco register, with a sync check per node. It only watches: it says hello onhive/{session}/observer/hello; every node answers with its copy of the register (so a late dispatcher gets it even if the allocator cannot answer) and the clock master with the clock. It sends no heartbeat, so it is never allocator or clock master. It can set the fast clock as a node's dashboard does (crun or pause,+-rate,[]hour): every node takes the set, and the clock master keeps it. Each district'sLocoscolumn counts the locos in use in that district, from the loco register (not from the node's heartbeat, which lists only the locos driven through its own LocoNet). The loco register shows each loco's district beside the node that drove it last;dshows every detail event. It sends the global emergency stop (E) and the global go (G), and its header has aGLOBALline:normal, or who stopped the layout and when. Lite nodes appear as199 → 101(carried by node 101) with the registerlite: none, and never become allocator or clock master. -
Sync check: every node puts an FNV-1a checksum of its copy of the register in its heartbeat. A copy that stays different for more than 2 s is shown as
LOCO REGISTER DIFFERSon the dispatcher and in the node's status line. This also catches the split-brain case below as soon as the mesh joins again. -
--show-locosprints the register once, for scripts and remote checks. -
JMRI's slot monitor works against a
nodeconnection: free throttle numbers are answered too.
Not yet done (README roadmap 8.2 to 8.6):
| Item | Notes |
|---|---|
| Dispatch across connections: a loco dispatched on Rocrail, a DT100 or another throttle, taken by a FRED on any throttle bus (a FRED cannot select a loco) | §6, §7.1. Without it a FRED cannot get a loco on a Hive-Rail throttle bus |
| Purging idle throttle numbers | Centrals purge after about 200 s or make the slot COMMON; a re-plugged FRED then logs on again |
| Slot updates for throttles with a display | When a loco changes elsewhere, send OPC_SL_RD_DATA on the LocoNet ports where it is driven |
| Throttle numbers that are safe during a network split | Today one allocator per connected part of the mesh: during a split both halves can hand out the same number. The blocks of §7 prevent that |
| Routing speed and functions to the district that holds the loco | Needs loco commands (README 4.6) and loco ownership (5.1) |
| Response time with a real FRED through the node board and the network | The PE sets no fixed timeout (L2); measure anyway |
| Walk-around test with real FREDs and Fredis | Stage 2 hardware |