Skip to content

Module and Layout Files

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

Status: design draft for review. This page proposes how module owners describe their modules and how a FREMO meeting's layout is built from those descriptions, including the routes for a route planner. It replaces the single module_profile.json of Architecture §3.1 with three files. Implemented (2026-10-29): the first part of the node file, as a TOML config file (§4). Implemented (2026-10-30): the module and layout files (schema version 2), their loader and the checks of §8, with hive-rail --check-layout <layout.json>, and an example meeting. Implemented (2026-11-01): modules drawn from SpDrS60 tiles in the module editor, schema version 3 (§11); the example meeting in the repository's examples/layout/ is drawn that way (two termini, a passing loop over two modules, line modules, a 90° curve, three districts). Schema version 2 below is still read. The workflow of §9 (library, district file, publishing and locking) is design.

Contents

  1. Why
  2. Three files, three owners
  3. The module file
  4. The node file
  5. The layout file
  6. What Hive-Rail derives
  7. Plan and reality
  8. Checks
  9. The meeting, step by step
  10. Example
  11. Module editor and schema version 3
  12. Roadmap
  13. Open questions

1. Why

At a FREMO meeting every participant brings modules, and the layout planning team decides how they are put together. Today that plan lives on paper and in CAD drawings; the layout does not know it. Hive-Rail needs the same knowledge to:

  • give sensors, points and signals a meaning ("entry sensor of Adorf, west") instead of a bare LocoNet or DCC-EX address;
  • find the district borders and with them the transition zones;
  • build the topology graph of the whole layout, and from it the routes for the route planner and the timetable;
  • show the Zugleiter on the dispatcher screen where trains are.

If every owner describes their modules once, in a file they keep and bring to every meeting, the planning team can load them and the layout is built from them.

2. Three files, three owners

File Written by Describes Changes
Module file The module owner One module: its ends, its track, its stations, its devices When the module is rebuilt
Node file Whoever brings the node (Pi, PC or node board) The Hive-Rail node: its identity and command station Rarely
Layout file The layout planning team One meeting: which modules, which end meets which, which modules form a district under which node Per meeting

The module file holds no neighbours and no session, like the profile of the Architecture today: it describes only what the module is. Everything about one meeting is in the layout file.

flowchart LR
    M1["Module files<br/>(owners)"] --> P["Planning team<br/>loads and checks"]
    N1["Node files<br/>(node owners)"] --> P
    P --> L["Layout file<br/>(one meeting)"]
    L --> NA["Every node:<br/>graph, districts,<br/>devices, routes"]
    L --> D["Dispatcher:<br/>map and routes"]
Loading

3. The module file

One file per module, named by the module's id, for example timo-adorf-west.json.

{
  "schema_version": 2,
  "module_id": "timo-adorf-west",
  "friendly_name": "Adorf, west end",
  "owner": "Timo",
  "scale": "H0",
  "length_mm": 1200,
  "ends": [
    { "id": "A", "profile": "H0-RE single", "tracks": ["main"] },
    { "id": "B", "profile": "H0-RE single", "tracks": ["main"] }
  ],
  "track": [
    { "id": "t1", "from": "A.main", "to": "W1.trunk", "length_mm": 300 },
    { "id": "t2", "from": "W1.straight", "to": "B.main", "length_mm": 850 },
    { "id": "t3", "from": "W1.diverging", "to": "end", "length_mm": 700 }
  ],
  "turnouts": [
    { "id": "W1", "device": "point_w1" }
  ],
  "stations": [
    {
      "name": "Adorf",
      "code": "Ad",
      "tracks": [
        { "name": "1", "track": "t2", "usable_mm": 800, "platform": true },
        { "name": "Ladegleis", "track": "t3", "usable_mm": 600, "platform": false }
      ]
    }
  ],
  "devices": [
    { "id": "point_w1", "type": "turnout", "bus": "loconet", "address": 12 },
    { "id": "occ_t2", "type": "occupancy", "bus": "loconet", "address": 104, "track": "t2" },
    { "id": "sig_a", "type": "signal", "bus": "dccex", "address": 21, "at": "A.main" },
    { "id": "windmill", "type": "relay", "bus": "board", "address": 40 }
  ]
}

3.1 Ends

  • profile is the FREMO end profile from the norm of the module system, so two ends fit only when their profiles fit. The H0 Europe end boards, for example, come as single-track and double-track variants, and names such as B96, E96 and F96 are used for H0-RE profiles. Hive-Rail only compares names; it does not need the dimensions. The list of accepted names and which of them fit each other is a table in Hive-Rail, kept with the FREMO norms (M-Q1).
  • tracks names the tracks at that end, from left to right as seen standing on the module and looking out of that end. When two ends meet face to face, the left track of one meets the right track of the other. This is what makes a turned double-track module come out right without anyone thinking about it.

3.2 Track

The track is a small graph, not a drawing. Each piece of track runs between two points:

  • an end track, A.main;
  • a turnout leg, W1.trunk, W1.straight or W1.diverging (a three-way or slip turnout has more legs, M-Q3);
  • end, a buffer stop.

length_mm is the track length, which the route planner needs for train lengths and the timetable. On double track, direction gives a track's regular running direction under right-hand running, for example A>B. On the open line (a module without points) it binds: routes run there only in that direction. In a station a train may run against it, to reach a platform or to turn back. A crossing or a more complicated track plan is drawn with the same pieces.

3.3 Stations

A station (Betriebsstelle) has the name and short code used in the timetable, and its tracks with their usable length and whether they have a platform. A station is one module (decided 2026-11-02, M-Q4): a station, a passing loop, a long bridge, a transition at an unusual place, anything always used in one composition is designed and built as one and is one module file, even when it is split into boxes to transport it (boxes, §11.3). --check-layout reports a station found in two modules. A station track can be a passing siding ("passing": true): a slower train waits there to let a faster one by.

Passing loops. A passing loop on the open line (Überholstelle) is not a station: it has no platforms, and trains do not stop there by timetable. It goes under passing_loops instead of stations, in the same form: a name, and per direction the through track and the passing siding ("passing": true). Trains run straight on along the through track. They take the siding only when the through track is taken, or when a faster train follows close behind; then they wait at the siding's exit signal until it has passed. One entry signal per direction guards both tracks. Like a station, a passing loop may span two modules (as Cdorf in the bench example), but not a district border. The web view shows its name in small italics, with no station label and no station board. A track marked platform in a passing loop is an error.

"passing_loops": [
  { "name": "Cdorf", "code": "Cd", "tracks": [
    { "name": "3", "track": "1", "usable_mm": 800 },
    { "name": "4", "track": "3", "usable_mm": 800, "passing": true }
  ] }
]

3.4 Devices

Every sensor, point, signal, relay and button the module has, each with:

Field Meaning
id Name inside the module, used by the track and turnouts
type turnout, signal, sensor, occupancy, relay, button, transponder, light, animation
bus Where the address lives: dcc (an accessory decoder on the track, programmed to that DCC accessory address), loconet (OPC_INPUT_REP, OPC_SW_REQ, OPC_SW_REP), dccex (DCC-EX's own <Q> sensors and pins) or board (a pin of the node board)
address The address on that bus, as the device is set up or programmed
block, track + at_mm, at Where it is: an occupancy detector covers a block; a sensor or signal stands on a track at a distance from the track's from end, or at a point such as an end
role, for For sensors, signals and relays: entry, stop, edge, boundary, transfer_entry or transfer_relay, and the running direction it serves (A>B); a signal stands on the right of the track in its direction
aspects, functions Optional: a signal's aspects and the DCC addresses or outputs that show them; a light's or animation's outputs

The bus matters because sensor 104 on LocoNet and sensor 104 in DCC-EX are different things. Accessory decoders on the track (dcc) are addressed through the district's command station, so their addresses share one space per district, whichever throttle or panel sends the command.

3.5 Blocks

A block is a named stretch of track for occupancy and train positions. Electrically it is bounded by rail cuts in its detected rail; the other rail is common:

"blocks": [
  { "id": "adorf_1", "track": "t2", "cuts_mm": [30, 830], "detected_rail": "right", "occupancy": "occ_t2" }
]

cuts_mm are the positions of the two cuts along the track, from its from end; the block's length follows from them. detected_rail is left, right (seen from the track's from end), outer on double track, or both for a transfer block. A block that spans several track pieces or modules lists its pieces and the cut at each end (M-Q8). The route planner and the dispatcher work in blocks.

3.6 Geometry

Optional, for drawing the module and the layout map; a module file without it still works for everything else.

"geometry": {
  "outline": [[0, 0], [1200, 0], [1200, 500], [0, 500]],
  "ends": { "A": { "at": [0, 250], "facing": 180 }, "B": { "at": [1200, 250], "facing": 0 } },
  "points": { "W1": [300, 250] },
  "track": { "t3": [[300, 250], [520, 360], [1000, 360]] },
  "platforms": { "Adorf 1": [[500, 180], [1100, 225]] },
  "devices": { "sig_a": [40, 210], "windmill": [1080, 440] }
}

In schema version 3 there is no geometry: the module is drawn from its SpDrS60 tiles (§11), and the map takes its drawing from them. This is the example station as it is today, Adorf (timo-adorf in examples/layout/), as the module editor shows it:

Adorf, a terminus, drawn from its SpDrS60 tiles as in the module editor

Coordinates are in millimetres on the module, seen from above: x runs from end A towards end B, y from the front edge (the operator's side) towards the back. facing is the direction out of an end, in degrees, 0 along x. Track between two known points is drawn straight unless track gives it a polyline, as for the loading track here. Corner and curved modules are an outline with a different shape and ends that do not face opposite ways. The layout map is built by joining ends: each module is placed so that its end meets the end it is connected to.

3.7 Transition zone

A module built as a transition zone declares it at the end where the seam is, with the hardware from Transition Zone §9. The zone is only used when the layout puts a district border at that end.

3.8 Example: a standard double-track module

A plain straight FREMO H0 double-track module with 2E96 end profiles, 900 × 546 mm, as sold as a kit by Pikart Models. A line module like this has no stations and no turnouts; its file is short:

{
  "schema_version": 2,
  "module_id": "fremo-2e96-straight",
  "friendly_name": "Double-track line, straight",
  "scale": "H0",
  "length_mm": 900,
  "ends": [
    { "id": "A", "profile": "2E96", "tracks": ["1", "2"] },
    { "id": "B", "profile": "2E96", "tracks": ["2", "1"] }
  ],
  "track": [
    { "id": "1", "from": "A.1", "to": "B.1", "length_mm": 900, "direction": "A>B" },
    { "id": "2", "from": "A.2", "to": "B.2", "length_mm": 900, "direction": "B>A" }
  ],
  "blocks": [
    { "id": "line_1", "track": "1", "cuts_mm": [20, 880], "detected_rail": "outer", "occupancy": "occ_1" },
    { "id": "line_2", "track": "2", "cuts_mm": [20, 880], "detected_rail": "outer", "occupancy": "occ_2" }
  ],
  "devices": [
    { "id": "occ_1", "type": "occupancy", "bus": "loconet", "address": 201, "block": "line_1" },
    { "id": "occ_2", "type": "occupancy", "bus": "loconet", "address": 202, "block": "line_2" },
    { "id": "in_1", "type": "sensor", "role": "entry", "bus": "loconet", "address": 211, "track": "1", "at_mm": 100, "for": "A>B" },
    { "id": "stop_1", "type": "sensor", "role": "stop", "bus": "loconet", "address": 212, "track": "1", "at_mm": 800, "for": "A>B" },
    { "id": "in_2", "type": "sensor", "role": "entry", "bus": "loconet", "address": 221, "track": "2", "at_mm": 800, "for": "B>A" },
    { "id": "stop_2", "type": "sensor", "role": "stop", "bus": "loconet", "address": 222, "track": "2", "at_mm": 100, "for": "B>A" }
  ],
  "geometry": {
    "outline": [[0, 0], [900, 0], [900, 546], [0, 546]],
    "ends": { "A": { "at": [0, 273], "facing": 180 }, "B": { "at": [900, 273], "facing": 0 } },
    "track": { "1": [[0, 250], [900, 250]], "2": [[0, 296], [900, 296]] }
  }
}

A standard FREMO 2E96 double-track module, drawn from SpDrS60 tiles as in the module editor: track 2 above track 1, the entry and stop sensors as green dots

  • Running direction. With right-hand running, a train going from A to B (+x) uses the track on its right, the front track 1; a train from B to A uses the back track 2. In the module's own coordinates this does not depend on how the module is turned, so the module states it per track (direction). A layout with left-hand running reverses it (M-Q7).
  • Blocks and rail cuts. Each block is bounded by two cuts in its detected rail, here the outer rail of each track, 20 mm from the module ends (cuts_mm, measured along the track from its from end). The other rail is common. The block length follows from the cuts: 860 mm.
  • Sensors by direction. The entry sensor is where a train arrives, the stop sensor before it leaves the block, so on track 2 they are the other way round from track 1. at_mm gives the position along the track; the drawing takes x and y from the track's geometry, so a sensor needs no coordinates of its own.
  • Track order at the ends. The two ends list the same tracks in opposite order: looking out of end A, track 1 is on the left; looking out of end B, track 2 is. Joined to the next module, each track continues on the same side, however the module is turned.

The track spacing of 46 mm is assumed (the prototype's 4.0 m in H0); the FREMO norm gives the real value (M-Q1).

3.9 Example: a district border on double track

Two such 2E96 modules at the border between the MacBook and Windows districts, with the sensors placed as the takeover specification needs them:

District border on double track: timo-2e96-border (MacBook) and anna-2e96-border (Windows) as drawn in the module editor, each with its district border tile and its signal with distant

With right-hand running each track has one direction, so each district hosts the transfer block for its incoming track: track 1 (towards Windows) has its transfer block T1 on Windows' first module, track 2 (towards MacBook) has T2 on MacBook's last module. Per direction:

Item Where role
Source edge block the last block before the border, on the source side; a declared loco in it starts the handoff edge
Boundary signal before the border on the source side, on the right of the track boundary (signal)
Transfer block T the first block of the receiving district, cut in both rails at both ends, longer than the longest train transfer (block)
IR beam across the track at T's entrance, on the receiving node's board transfer_entry
Latching relay feeds T from the source (RESET) or the receiving district (SET), on the receiving node transfer_relay
Destination entry block the first block after T; occupied with T clear means committed entry

The receiving node switches T to its own feed when the beam at T's entrance has been broken and is restored again for beam_clear_ms (the last car has passed it) and the source edge block is clear. The beam sees the whole train, including cars without resistor wheelsets, and no delay has to be tuned to train length or speed.

In the module file a transfer module marks its block and devices with these roles:

"blocks": [
  { "id": "t1", "track": "1", "cuts_mm": [20, 880], "detected_rail": "both", "role": "transfer", "for": "A>B", "occupancy": "occ_t1" }
],
"devices": [
  { "id": "ir_t1", "type": "sensor", "role": "transfer_entry", "bus": "board", "address": 17, "track": "1", "at_mm": 50, "for": "A>B" },
  { "id": "relay_t1", "type": "relay", "role": "transfer_relay", "bus": "board", "address": 27, "block": "t1" }
]

The layout decides which modules are transfer modules in use: a module is only a transfer zone where the layout puts a district border at its end, and the border check (§8) warns when a border has no transfer block for one of its tracks.

3.10 Buildings and level crossings

Buildings with lights and level crossings make a module come alive, and the dispatcher and the web view show them. Both refer to devices of §3.4; on the example modules everything is switched over LocoNet:

"buildings": [
  { "id": "station", "name": "Bahnhof Adorf", "lights": ["light_station_hall", "light_station_platform"] }
],
"crossings": [
  {
    "id": "lc", "name": "Feldweg",
    "tracks": [ { "track": "1", "at_mm": 450 }, { "track": "2", "at_mm": 450 } ],
    "lights": "lc_lights", "barriers": "lc_barriers",
    "close": ["lc_close_1", "lc_close_2"], "open": ["lc_open_1", "lc_open_2"]
  }
],
"devices": [
  { "id": "light_station_hall", "type": "light", "bus": "loconet", "address": 101 },
  { "id": "lc_lights", "type": "light", "bus": "loconet", "address": 111 },
  { "id": "lc_barriers", "type": "animation", "bus": "loconet", "address": 112 },
  { "id": "lc_close_1", "type": "sensor", "bus": "loconet", "address": 211, "track": "1", "at_mm": 100, "for": "A>B" },
  { "id": "lc_open_1", "type": "sensor", "bus": "loconet", "address": 212, "track": "1", "at_mm": 600, "for": "A>B" }
]
  • A building lists the lights in it (devices of type light).
  • A level crossing gives where the road crosses each track (at_mm from the track's from end), its warning lights (a light) and barriers (an animation), and the sensors that close it before a train arrives and open it after the train has passed, one pair per track in its running direction. A crossing without a closing sensor gets a warning.
  • The geometry (§3.6) can draw them: buildings gives each building's outline by id, roads a polyline per road, for example straight across the module at the crossing.
  • Lights and barriers are LocoNet switch addresses (OPC_SW_REQ), the crossing sensors LocoNet sensor addresses (OPC_INPUT_REP), so they never clash with each other (§8).

3.11 Signals and routes

A signal is a device of type signal on a track, facing the trains of one running direction. It guards the exit of its track piece: beyond it begins the next section, up to the next signal.

"devices": [
  { "id": "sig_a1", "type": "signal", "bus": "loconet", "address": 121, "track": "1",  "at_mm": 1080, "for": "A>B", "aspects": ["red", "green"] },
  { "id": "sig_ae", "type": "signal", "bus": "loconet", "address": 125, "track": "2b", "at_mm": 70,   "for": "B>A", "aspects": ["red", "yellow", "green"] }
]
  • Aspects. In schema version 3 a signal's aspects are named as in the DB rulebook Ril 301 and come with its tile: ["hp0", "hp1", "hp2", "vr0", "vr1", "vr2"] for an H/V block signal with its distant, ks1, ks2, sh1 and so on (see Signal Faces and Choosing and Placing Signals). A signal that can announce a stop (a distant on its mast, or Ks 2) takes two LocoNet switch addresses, its address and the next one, as signal decoders pair them; any other signal takes one. The address check counts both (§8). In schema version 2 the aspects were colours: a two-aspect signal (["red", "green"], thrown is red, closed is green) or a three-aspect signal (["red", "yellow", "green"]); without aspects, a signal has two.
  • Where. at_mm is measured from the track's from end, like a sensor. The signal stands on the right of the track in its direction, on the platform between two station tracks. Put it on a straight part of the track, before the points it guards.
  • Routes. A train reserves its way to the next destination one section at a time, all or nothing. A section is reserved only when no other train stands on it or holds it, and its points are set as it is reserved. A train holds at most two signals cleared ahead of it, so it never blocks a station throat it is still far from. Pieces behind the train are released once it has left them.
  • Aspect. A signal shows green while a train holds the section beyond it, and red otherwise. It turns red again once the train has passed. A three-aspect signal shows yellow when the next signal on the way is red or the way ends at the destination.
  • Stop. A train whose next section is taken halts just before the red signal and waits until the section is free.

The web view draws reserved track in yellow and each signal as its SpDrS60 tile, showing its aspect: stop as Hp 0, proceed as Hp 1 with the distant (or Ks 1) showing what the next signal shows. The simulator does not yet show Hp 2 and Vr 2 for a route over a turnout's curved side. Its header shows the layout's fast clock as the nodes keep it (on the mesh), the real-world time and the time since the simulation started. The simulator (--simulate-layout, --web-sim) works this way. On a layout, the district nodes will set the signals they own over LocoNet.

The transfer block at a district border (§3.7) shows which district feeds it. The sending district feeds it while a train comes in and still stands on the sending side. The receiving district takes over once the train is fully inside and the sending side is clear. The block goes back to the sending district when it is free. The web view gives each district a colour and draws the transfer block in the colour of the district feeding it, named beside it.

4. The node file

Implemented (2026-10-29) as a TOML config file, read with --config <file.toml>. TOML rather than JSON, because the node owner edits this file by hand and needs comments (which Intellibox settings, why the fast clock is off), and TOML has no commas or indentation to get wrong:

mode = "node"
session = "bench"
id = "MacBook"
rocnet-address = 101
dccex = "/dev/cu.usbserial-0001"

[loconet]
tcp = 1234
tcp-slots = "external"

Each key is a command line flag without its dashes, and a table is a flag prefix: tcp = 1234 under [loconet] is --loconet-tcp 1234. true and false switch an option on or off. So every option of the node and the dispatcher works in the file without a schema of its own, and options given on the command line win over the file: one file can serve several nodes (--config bench.toml --id MacBook-2). The repository's examples/config/ holds a reference with every option and its default, and one file each for an Intellibox I, DCC-EX over USB serial, DCC-EX over WiFi and the dispatcher screen.

Still to come, with the module and layout files: a schema version, the session coming from the layout file instead of the node file, and the checksum the layout file keeps of each node file (§5).

5. The layout file

The planning team's file for one meeting. It names the module files and node files it was made from, with a checksum each, so a node can tell when an owner brings a changed module.

{
  "schema_version": 2,
  "meeting": "FREMO Bench 2026-11",
  "modules": [
    { "module_id": "timo-adorf-west", "checksum": "8d1f…" },
    { "module_id": "anna-line-1200", "checksum": "02ab…" },
    { "module_id": "bert-bdorf", "checksum": "77c0…" }
  ],
  "connections": [
    { "a": "timo-adorf-west.B", "b": "anna-line-1200.A" },
    { "a": "anna-line-1200.B", "b": "bert-bdorf.A" }
  ],
  "districts": [
    { "node": "MacBook", "modules": ["timo-adorf-west", "anna-line-1200"] },
    { "node": "Windows", "modules": ["bert-bdorf"] }
  ]
}
  • connections pairs module ends. The tracks at the two ends are matched by position (§3.1).
  • districts puts every module under exactly one node and its command station. A district border is every connection between modules of two districts.

6. What Hive-Rail derives

From the layout file and the files it names, every node and the dispatcher build the same things:

Derived Used for
Topology graph of the whole layout: every track piece, turnout and end, joined across modules Everything below
District borders, and on each the transition zone module, if there is one The handoff (Transition Zone)
Routes from every station track to every other: the track pieces, the turnout positions, the total length, the districts crossed The route planner, the timetable, setting points
Device map: bus and address to module, device and meaning, per district Sensor and point events on the mesh with names, the dispatcher's occupancy view
Station list with tracks and usable lengths The timetable and the dispatcher

Mesh events then carry the meaning, for example "occupancy timo-adorf-west/occ_t2 occupied" instead of "LocoNet sensor 104 high". Addresses that no module lists still pass between the local LocoNet devices and clients as today, so Rocrail and control panels keep working.

7. Plan and reality

The layout file is a plan. It does not replace Architecture §3.3: a link between two nodes is still only Active when both sides report it. The plan changes how that report is made: instead of picking a neighbour by hand, the operator confirms the link the plan expects, and the node warns when what it sees does not match the plan (a different neighbour, a module that is missing, a module file whose checksum changed). The system never treats the plan as proof that two modules are really joined.

8. Checks

Hive-Rail checks the files when the planning team loads them, so problems are found before the meeting, not on the floor:

Check Error or warning
Two connected ends have profiles that do not fit Error
Two connected ends have a different number of tracks Error
A module end is in two connections Error
A module is in no district, or in two Error
Two devices in one district use the same bus and address Error: the devices would answer for each other, for example two signal decoders on the same DCC accessory address. Re-address one, or move a module to another district
A district border has no transfer block for one of its tracks (on double track, one on each side) Warning: a train cannot be handed over there
A track piece ends nowhere, or a turnout leg is not connected Error in the module file
A station track is shorter than the trains planned for it Warning, once the timetable is known
A module file changed since the layout was made Warning on the node

The address check is the one FREMO meetings feel most: every owner numbers from 1, and LocoNet devices report the address they are set to, so Hive-Rail cannot shift them. Within one district the planning team has to resolve clashes; across districts the same address is fine, because each district has its own bus.

Additions from the implementation:

Check Error or warning
A district border carries more than single or double track Error: only standard single or double track may cross between districts. Points, sidings and stations stay inside a district
A three-aspect signal Takes its address and the next one (§3.11); both count in the address check
Two LocoNet devices share an address Only within one address space: LocoNet sensors (OPC_INPUT_REP: occupancy, sensors, buttons, transponders) and LocoNet switches and outputs (OPC_SW_REQ) are separate, so sensor 21 and turnout 21 do not clash
A module file changed since the layout was made The checksum is a 64-bit FNV-1a hash of the file, 16 hex digits; --check-layout prints it for each module. A change check, not a security measure

9. The meeting, step by step

9.1 Who does what

Step Who Where Result
1. Module library Every owner Their own Hive-Rail, at home A module file for each of their modules, for example ten
2. Sharing Owners of one district Email, USB stick, SSH, NFS or SMB (a cloud service later) Each owner's module files reach the person who brings the district's node
3. District file Whoever brings the node Hive-Rail's web view on that node The modules brought to this meeting, from all owners of the district, in order and orientation
4. To the planning team The district Exported as a file and mailed in advance, or published live on the mesh at the meeting The planning team has every district
5. Layout The planning team Their tool: the web view on any node or the dispatcher, or the command line The districts linked at their borders, checked (§8): the layout file
6. Lock The planning team Published on the mesh with a version Every node loads the same locked layout
7. At the meeting Every node — It builds the graph and announces its district; operators confirm the links the plan expects (§7)

The planning team works as today: module data comes in before the meeting, and changes on the day are the exception.

9.2 Module library and sharing

Every Hive-Rail keeps a module library: a folder of module files. An owner keeps their own modules there, and imports the files other owners share with them. A module file is complete in itself and its id carries the owner's name (timo-adorf, anna-2e96-border), so files from different owners do not collide. For now files are shared by hand, by email, USB stick or a shared folder over SSH, NFS or SMB; a cloud service may come later.

9.3 The district file

Several owners usually bring modules into one district, under one node and one command station. The person who brings the node puts the district together from the library, in Hive-Rail's web view:

  • choose the modules brought to this meeting, from every owner of the district;
  • put them in order and orientation: which end meets which (timo-adorf.B to timo-2e96-border.A, or a turned module joined at its other end);
  • see the checks of §8 that one district can answer already: track graph, addresses within the district, ends that fit.

The result is a district file: the node, the module ids with their checksums, the connections inside the district, and the ends left open towards the neighbours. It is exported as a file to mail before the meeting, or published live on the mesh at the meeting; both carry the same content. The module files go with it, so the planning team has everything.

9.4 The layout and the lock

The planning team imports the district files, links the open ends of neighbouring districts and runs every check. The result is the layout file of §5: its districts and connections are the union of the district files plus the borders.

The planning team locks the layout: it is published on the mesh with a version, and every node loads it. A locked layout is what train tracking and handoffs work on, so it does not change unnoticed during operation. Starting rule (M-Q9): a district may still propose a change, for example a module swapped on the day; the change becomes active only when the planning team approves it and publishes a new locked version.

The lock is a plan, not proof: a link between two districts is still only Active when both nodes report it (§7).

10. Example

Three modules: the west end of Adorf with its loading track, a plain line module, and Bdorf. Adorf and the line module are in the MacBook district, Bdorf in the Windows district, so the border is between anna-line-1200.B and bert-bdorf.A.

flowchart LR
    subgraph mac["District MacBook"]
        subgraph ad["timo-adorf-west"]
            AA(("A")) --- W1{"W1"}
            W1 -- straight --- AD1["Adorf 1<br/>800 mm"]
            W1 -- diverging --- LG["Ladegleis<br/>600 mm"]
            AD1 --- AB(("B"))
        end
        subgraph ln["anna-line-1200"]
            LA(("A")) --- LT["line<br/>1200 mm"] --- LB(("B"))
        end
    end
    subgraph win["District Windows"]
        subgraph bd["bert-bdorf"]
            BA(("A")) --- BD1["Bdorf 1"]
        end
    end
    AB --- LA
    LB ===|"district border"| BA
Loading

Derived routes include "Adorf 1 to Bdorf 1": W1 straight, line, across the MacBook–Windows border; and "Ladegleis to Bdorf 1": W1 diverging, the same way. The border gets a warning unless one of the two modules at it is a transition zone module.

11. Module editor and schema version 3

A module is drawn in the module editor of the web view (/panel), as in Rocrail's editor, from square tiles in the style of the Siemens SpDrS60 signal box. The editor opens and saves the module file; that file is the one source of everything about the module, and Hive-Rail derives the rest from its tiles. Owners do not write JSON.

11.1 The grid

  • A module's grid is its real size in cells of 50 mm, rounded to the nearest cell: a 900 × 546 mm 2E96 module is 18 × 11 cells. 50 mm is about the 46 mm between the tracks of H0 double track (NEM 112), so parallel tracks lie one cell apart, and FREMO lengths divide into whole cells.
  • The real size counts for planning and for the simulator: a track's length is its share of the module's real length and depth. The screen shows the grid.
  • All turns are 45 degrees, as on the real panel: a curve tile turns the track by a quarter, curve tiles in a row make a diagonal.
  • Joining rule: track meets a tile edge only at the middle of the edge, always over the same span, so any two tiles placed side by side join flush. Track that meets neither another tile nor the edge is an open end, marked red in the editor and refused by the loader.

11.2 The tiles

Track, curve, left- and right-hand turnout, crossing and double slip (two cells wide), buffer stop, occupancy sensor (a dot in the middle of the track: green free, red occupied), platform track (platform above, below or both, so tracks a row apart make an island platform), level crossing, district border, train number, name plate, lamp, windmill and action tiles, and a tile for every DB light signal sold for H0 with LEDs (Viessmann): H/V block, entry and departure signal, each also with its distant signal below on the same mast, H/V distant, Ks main, Ks multi-section, Ks distant and the light shunting signal. A signal tile shows the signal small, lying along the track on the right of it, with its lamps where the model has them; its aspects are named as in Ril 301 (Hp 0, Hp 1, Hp 2, Vr 0–2, Ks 1, Ks 2, Sh 0, Sh 1); Sh 1 on Ks signals, the upper white and Hp 0 with one red are options. See Signal Faces. The tiles are Hive-Rail's own drawings (assets/spdrs60/, assets/signals/).

11.3 The file (schema version 3)

{
  "schema_version": 3,
  "module_id": "timo-adorf",
  "friendly_name": "Adorf",
  "owner": "Timo",
  "scale": "H0",
  "size_mm": [1800, 546],
  "ends": [ { "id": "B", "side": "right", "profile": "2E96", "tracks": ["in", "out"] } ],
  "tracks": [ { "name": "in", "direction": "B>A" } ],
  "tiles": [
    { "tile": "turnout-left", "x": 26, "y": 5, "turn": 2, "id": "3", "block": "throat", "device": "point_3" },
    { "tile": "signal-hv-departure", "x": 24, "y": 5, "id": "N1", "track": "1", "block": "adorf_1_e",
      "device": "signal_n1" },
    { "tile": "sensor", "x": 21, "y": 5, "id": "1 H-B", "track": "1", "block": "adorf_1_e", "device": "sensor_1_h_b" }
  ],
  "stations": [ { "name": "Adorf", "code": "Ad", "tracks": [ { "name": "1", "track": "1", "platform": true } ] } ],
  "blocks": [ { "id": "adorf_1_e", "track": "1", "detected_rail": "outer",
                "sections": [ { "device": "sensor_1_h_b", "role": "stop", "for": "A>B" } ] } ],
  "devices": [ { "id": "signal_n1", "type": "signal", "bus": "loconet", "address": 121,
                 "decoder": "Viessmann 5229", "aspects": ["hp0", "hp1", "hp2"] } ]
}
  • Tiles: place (x, y), turn (0 to 3 quarters clockwise), mirror (flipped across the track, before turning), and what the tile stands for: id (a turnout's number, a signal's or sensor's name), track (the track it belongs to), block (its occupancy section), device, text, options (a signal's lamps, a track's platforms), distant (the signal a distant signal announces).
  • Ends are found where track leaves the grid; each gets a name and an end profile, and its tracks follow from the tiles. A terminus whose platform tracks end at buffer stops has one end; its back side is named for directions ("B>A": towards the buffer stops).
  • Devices keep their bus, address and decoder; a tile only names its device.
  • Blocks: each occupancy section is a block, drawn on its tiles, with its sensor; role is stop, brake, throat, or at a district border transfer, edge or entry.

Boxes. A module that is one composition but too long to carry (a station, a passing loop, a long bridge) lists the boxes it is built of, from end A, so that layout planning knows the real pieces and the editor and the map show where they meet (a dashed line; no turnout or signal on a joint):

"boxes": [
  { "id": "west", "length_mm": 1200 },
  { "id": "east", "length_mm": 1200 }
]

Together they must be the module's length (--check-layout). The example's Cdorf is one module of two boxes of 1200 mm (examples/layout/timo-cdorf.json).

11.4 What Hive-Rail derives from the tiles

The track pieces between turnouts, buffer stops and module ends, with their lengths; turnouts (a double slip as two halves, 4a and 4b); each signal's and sensor's track, place and direction (a signal faces the way its tile faces); each block's extent; stations' usable lengths; the map, drawn from the tiles and placed by joining the module ends, its lamps following the layout.

11.5 Names (DB / FREMO practice)

  • Signals: entry signals A (in the direction of rising kilometres) and F; exit signals N and P with the track number (N1, P2); block signals numbered, odd and even by direction; a distant signal named after the signal it announces, in lowercase (a, f, n3).
  • Turnouts: numbered per station from its entry in kilometre direction; a crossover is a pair (1/2), a double slip's halves a and b (4a/4b).
  • Occupancy sections: by track and kind: H a stop section with the end it lies at (1 H-A), B a braking section, S the open line, T a transfer section and R an edge section at a district border; a station throat W with its turnouts (W 1-4).
  • Level crossings: BÜ with their name; their switch-on and switch-off contacts ESK and ASK.

A station track has three occupancy sections: a short stop section at each platform end (up to the exit signal or the buffer stop) and the braking section between them; a station throat has a section of its own; a passing loop track (run one way) a braking and a stop section; the open line one section per track and module.

12. Roadmap

Step Roadmap
Node file as a TOML config file (--config) 1.7, done
Module, node and layout file schemas (version 2), loader and checks, with the current version 1 profile still read 1.1 to 1.3: module and layout files, loader and checks done (--check-layout)
Module library, district file (export and import), publishing and locking on the mesh (§9) new
Binding confirmation against the plan 1.4
Topology graph and routes 1.5
Device map: LocoNet and DCC-EX sensors and points on the mesh with names new
Dispatcher map and occupancy view 7.6: the web view, on the layout simulator so far
Graphical module editor done (2026-11-01): /panel, modules drawn from SpDrS60 tiles, schema version 3 (§11)
Transition zones at the derived district borders 5.x

13. Open questions

# Question How it will be answered
M-Q1 Which FREMO end profile names exist per module system, and which fit each other? From the FREMO norms per module system; a table in Hive-Rail
M-Q2 Do FREMO groups already keep module data in a format Hive-Rail could import? FREMO:87, for example, keeps a module database and plans arrangements from checked CAD drawings Ask the planning teams
M-Q3 How are three-way turnouts, double slips and crossings described: more legs, or a small graph of their own? First real modules
M-Q4 Is joining a station across modules by name enough, or does a station need its own file? Answered (2026-11-02): a station is one module, one file, however many boxes it is built of (§3.3, boxes in §11.3)
M-Q5 How does the layout file reach the nodes at the meeting: copied by hand, or shared on the mesh with a version? Answered (§9): district files by file before the meeting or live on the mesh; the locked layout published on the mesh with a version
M-Q9 Locked layout: no edits at all, or proposals that become active once the planning team approves them? Starting rule: proposals with approval (§9.4); decide with the first meetings
M-Q6 Besides block cuts, does the module file describe other electrical sections, for example a separately fed station or a reverse loop? Booster and occupancy tests
M-Q7 Do FREMO meetings ever run left-hand (some prototypes, such as France, run left)? Then the layout file sets the running side and the module's direction is reversed Ask the planning teams
M-Q8 Should a block always be cut at module ends, or may it span modules with the cut on a neighbouring module? First multi-module blocks

Clone this wiki locally