Skip to content

FRED Throttles and the Distributed Master

Timo Sariwating edited this page Oct 31, 2026 · 11 revisions

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

  1. Use case
  2. Cluster topology and cabling rule
  3. The distributed master
  4. Two kinds of slots
  5. Routing: only what applies locally
  6. Message flows
  7. Throttle slot allocation without a central server
  8. Command station backends
  9. Conflict detection
  10. Test setups
  11. Design decisions
  12. LocoNet message reference
  13. Open questions

1. Use case

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.

2. Cluster topology and cabling rule

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
Loading

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

3. The distributed master

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

4. Two kinds of slots

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

5. Routing: only what applies locally

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

5.1 Key expressions

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.

6. Message flows

6.1 Dispatch and drive a loco in another cluster

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
Loading

6.2 FRED plugged in again on another cluster

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
Loading

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.

6.3 Handoff moves the route, not the throttle

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
Loading

The handoff itself is defined in the Transition Zone Specification.

6.4 Points, signals and module actions

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
Loading

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.

6.5 Fast clock

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

6.6 Global power on/off and short circuits

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

6.7 Node dashboard

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. SHORT and Unknown are 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 --plain printing the same lines as a log.

7. Throttle slot allocation without a central server

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.

7.1 Slot status

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 --> [*]
Loading
  • A throttle activates a slot with a null move (OPC_MOVE_SLOTS with 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.

8. Command station backends

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.

9. Conflict detection

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.

10. Test setups

10.1 Stage 1: software only

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
Loading
  • The Windows node offers a LocoNet over TCP server in the LbServer text format (SEND / RECEIVE lines). 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:
    1. Global power and short reporting (6.6): Rocrail switches power on and off on both nodes; a simulated short on one node shows as SHORT on both dashboards.
    2. Fast clock (6.5) in the dashboard header on both nodes, set from Rocrail.
    3. Clear CLI view (6.7): decoded LocoNet log, event pane, --plain output.
    4. Throttle slots, dispatch, routing to the district owner and state feedback.
    5. Node restart and FRED-style reconnection (6.2).

10.2 Stage 2: first LocoNet hardware

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
Loading
  • 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.

11. Design decisions

# 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

12. LocoNet message reference

Moved to the LocoNet Adapter §5.

13. Open questions

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

14. The node board solution and the loco register

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:

  1. 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.
  2. 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).
  3. District change. The transition zone hands the loco from A's command station to B's. The FRED does not notice.
  4. Unplug and walk. The train keeps running at its last speed; LocoNet throttles are exempt from the dead-man timeout.
  5. 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_ADR on a throttle bus for a loco without a number asks on hive/{session}/locos/request and 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) or external (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's OPC_SL_RD_DATA and follows speed, direction and functions by address. Defaults: LocoNet TCP clients external (--loconet-tcp-slots), the node board's bus node and a serial interface on a central external (--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 node connection 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 external default: 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 on hive/{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 (c run or pause, + - rate, [ ] hour): every node takes the set, and the clock master keeps it. Each district's Locos column 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; d shows every detail event. It sends the global emergency stop (E) and the global go (G), and its header has a GLOBAL line: normal, or who stopped the layout and when. Lite nodes appear as 199 → 101 (carried by node 101) with the register lite: 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 DIFFERS on 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-locos prints the register once, for scripts and remote checks.
  • JMRI's slot monitor works against a node connection: 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

Clone this wiki locally