A .NET Blazor Server application for controlling model train yard points (turnouts) via either the LocoNet protocol over a serial interface or a Roco/Fleischmann Z21 command station over UDP. It provides a graphical web UI with interactive signal-based train route setting, as well as numeric keypad input for hands-free operation.
The application supports individual point control, train routes between signals with automatic route derivation from topology, point locking to prevent conflicting movements, signal control, and real-time position feedback from the command station.
- Main and shunting routes — Set main train routes or shunting routes between signals. Main routes are highlighted in green; shunting routes in yellow (EBICOS palette). Shunting routes use dwarf signals, skip destination signal go-aspects, and release locks immediately — matching real railway shunting operations.
- Automatic route derivation — Just specify the from and to signals; the application finds the shortest path through the topology and determines the required point positions automatically.
- Train route queueing — When a route conflicts with an active route, it is automatically queued and executed as soon as the blocking route is cleared. Queued routes are displayed in the UI and can be cancelled before they execute.
- Clear vs cancel semantics — Clearing a route (
/) advances the train number from the origin to the destination signal and releases locks after a safety delay; cancelling (ESC) removes train numbers and releases locks immediately. This distinction mirrors real dispatch operations. - Signal control — Signals are set to go/stop automatically when routes are set or cleared, with hardware integration via LocoNet accessory addresses. Five signal types are supported: OutboundMain, InboundMain, MainDwarf, ShuntingDwarf, and Hidden.
- Train number labels — Assign train numbers to signals and see them displayed live on the yard diagram. A train number stays at the origin signal until the operator confirms the train has advanced (via clear, move, or arrival commands). Blue labels next to each signal provide at-a-glance train identification.
- Single file configuration — Define your entire station (topology, points, signals, routes, translations) in a single text file with a human-readable format.
- Multiple station support — Configure several stations and switch between them at runtime from the UI.
- Live configuration reload — Edit data files while the application is running; changes are detected and applied automatically.
- Localisation — UI and track labels available in English, Swedish, Danish, Norwegian, and German.
- Choice of command station — Drive accessories over LocoNet (serial) or via a Roco/Fleischmann Z21 over UDP. Point and signal feedback work identically on both.
Download the archive for your platform from the GitHub Releases page:
| Platform | Archive |
|---|---|
| Windows x64 | YardControlApplication-vX.X.X-win-x64.zip |
| Windows ARM64 | YardControlApplication-vX.X.X-win-arm64.zip |
| Linux x64 | YardControlApplication-vX.X.X-linux-x64.tar.gz |
| Linux ARM 32-bit | YardControlApplication-vX.X.X-linux-arm.tar.gz |
| Linux ARM 64-bit | YardControlApplication-vX.X.X-linux-arm64.tar.gz |
The releases are self-contained — no .NET runtime installation is required.
Windows:
- Extract the zip archive to a folder of your choice (e.g.,
C:\YardControl). - Connect your command station (LocoNet interface via USB, or Z21 via Ethernet).
- Edit
appsettings.jsonto configure your stations and command station — see the Command station section for details. A minimal LocoNet-over-serial configuration:{ "Stations": [ { "Name": "Munkeröd", "DataFolder": "Data\\Munkeröd.txt" } ], "CommandStation": { "Type": "Serial", "SerialPort": { "PortName": "COM5", "BaudRate": 57600 } } } - Run
YardController.Web.exe. - Open the displayed URL (typically
http://localhost:5000) in a browser.
Linux:
- Extract the archive:
tar -xzf YardControlApplication-vX.X.X-linux-x64.tar.gz -C /opt/yardcontrol
- Make the executable runnable:
chmod +x /opt/yardcontrol/YardController.Web
- Connect your command station (LocoNet interface via USB, or Z21 via Ethernet).
- Edit
appsettings.jsonto configure your stations and command station — see the Command station section for details. A minimal LocoNet-over-serial configuration:The serial port is typically{ "Stations": [ { "Name": "Munkeröd", "DataFolder": "Data/Munkeröd.txt" } ], "CommandStation": { "Type": "Serial", "SerialPort": { "PortName": "/dev/ttyUSB0", "BaudRate": 57600 } } }/dev/ttyUSB0for USB-to-serial adapters or/dev/ttyACM0for devices with built-in USB. Runls /dev/tty*to find the correct device name. - Run the application:
/opt/yardcontrol/YardController.Web
- Open the displayed URL (typically
http://localhost:5000) in a browser.
Copy your station .txt files into the Data folder next to the executable and reference them in appsettings.json. See the Configuration section for the file format. The application watches the data files for changes, so you can edit them while the application is running.
The browser-based GUI displays the full yard topology as an interactive SVG diagram:
- Signals are shown as red (stop) or green (go) indicators with direction arrows.
- Points display their current position with colour coding (straight/diverging/unknown).
- Active main routes are highlighted in green along the track path; shunting routes in yellow; inactive track is light gray; routes being cancelled are shown in blue (locks still held).
- Train number labels appear as blue boxes next to signals, showing which train is at each location.
- Labels identify tracks, signals, and points.
To set a train route, click a signal to select the from signal, then click a second signal to select the to signal. The route is set, the involved points are moved and locked, and the from signal turns green (go). CTRL+click on a green signal to cancel its route. Shift+click on any signal to toggle it between stop and go manually.
Above the yard diagram is an All Signals Stop emergency button that immediately sets all green signals to red.
The footer provides:
- Show Grid checkbox to toggle coordinate grid overlay (useful for editing topology files).
- Query Point States button to request current positions from hardware (or simulate random positions in development mode).
- Reset All Points button to set all unlocked points to straight position (locked points are skipped).
- Language selector to switch the UI language.
A numeric keypad is required for full operation — it enables hands-free control of all yard functions. Either a wired USB keypad or a wireless keypad works; a wireless keypad is notably easier to move around with during operations.
| Command | Description |
|---|---|
[number]+ |
Set point to straight (e.g., 1+) |
[number]- |
Set point to diverging (e.g., 1-) |
A point number can represent a single point or multiple coupled points (e.g., opposing crossover points) that move together.
| Command | Description |
|---|---|
[from][to]⏎ |
Set main train route (e.g., 2131⏎ sets path from signal 21 to 31) |
[from][to]* |
Set shunting route |
[from].[to]⏎ |
When signal numbers have different digit counts, use . as divider (e.g., 121.33⏎) |
[from].[via].[to]⏎ |
Multi-signal route (e.g., 21.31.35⏎) |
[signal]/ |
Clear all routes up to a signal (e.g., 31/) |
// |
Clear all train routes and release all locks. Train numbers remain visible until you advance them with the train-number commands below |
ESC ESC |
Cancel all train routes, release all locks, and remove all train numbers |
** |
Set all signals to stop |
== |
Set all unlocked points to straight |
Note: Clearing up to a signal can be used to manually confirm that a train has reached its destination signal, releasing the locks on points used in the route. This is useful when occupancy detection is not available.
Train numbers stay where the train physically is. When a route is set with =[trainnumber], the number appears at the origin (from) signal. As the train moves through the yard, advance the number with one of the arrival commands; once the train leaves via an outbound signal and the next station confirms arrival, the number is removed.
| Command | Description |
|---|---|
[from][to]=[trainnumber]⏎ |
Set route and assign train number to the origin signal (e.g., 2131=1234⏎) |
[from].[to]=[trainnumber]⏎ |
Same with . divider (e.g., 121.33=1234⏎) |
[signal]=[trainnumber]⏎ |
Manually assign a train number directly to a signal (e.g., 21=1234⏎) |
[signal]/ |
Clear the route up to that signal and move the train number from the origin to that signal — confirms arrival |
[signal]=⏎ |
Move the train number from the origin to that signal without clearing the route |
=[trainnumber]⏎ |
Advance that train number one signal forward along its current route. If no further signal exists (e.g. it sits at an outbound signal with no extending route), the train number is removed — used when the next station confirms arrival |
Cancelling a route with ESC removes the train number.
The ⏎ symbol throughout these tables is the Enter key on the numpad.
| Command | Description |
|---|---|
+[track number]⏎ |
Move turntable to the specified track position |
| Command | Description |
|---|---|
⌫ |
Clear current input buffer |
+- |
Reload configuration |
Configuration files are located in YardController.Web/Data/ (paths can be changed in appsettings.json). Files are watched for changes and automatically reloaded.
Multiple stations can be configured in appsettings.json:
{
"Stations": [
{ "Name": "Munkeröd", "DataFolder": "Data\\Munkeröd.txt" },
{ "Name": "Steinsnes", "DataFolder": "Data\\Steinsnes.txt" }
]
}Each station is configured as a single text file with named sections. This keeps all station data together and enables automatic route derivation.
Munkeröd
[Settings]
LockOffset:1000
LockReleaseDelay:30
[Tracks]
1.1-1.2-1.3-1.4 ' track segments (left-to-right)
[Points]
2.3(1a>)-2.4 @840a,843b ' forward point with LocoNet address
2.5(<2)-2.6 @842 ' backward point
4.7(8a>)-7.10(<8b) @-809a,812b &+:10+ ' single-slip half: setting 8 to + also forces 10 to +
7.10(10a>)-9.12(<10b) @-813a,822b &-:8- ' the other half: setting 10 to - also forces 8 to -
[Signals]
1.1:21>:u @900 ' outbound main signal driving right
1.4:<31:i @901;951 ' inbound main signal with feedback address
[Routes]
21-31 ' auto-derived from topology
21-31:x25+ ' auto-derived + explicit flank protection
21-35:21.31.35 ' composite route via intermediate signal
[Labels]
1.1[Goods track]1.4
[Gaps]
2.3|2.4 ' occupancy gap on a link
[Translations]
en;sv;da;nb;de
Track;Spår;Spor;Spor;Gleis
Goods track;Godsspår;Godsspor;Godsspor;Güterspur
[Turntable]
Tracks:1-17
Offset:196
Comments start with a single quote ('). Section names are case-insensitive.
Route auto-derivation: When a route line contains only signal numbers (e.g., 21-31 without a colon), the application uses the topology graph to find the shortest path between the signals and automatically determines which points need to be set to straight or diverging. You can also specify only flank protection points (prefixed with x) and let the on-route points be auto-derived.
Slave clauses (point coupling): Single-slip switches (and similar arrangements where two configured points are mechanically coupled) can declare that setting one position automatically sets another point. Append one or more whitespace-separated &<masterPos>:<slave><slavePos>[,<slave><slavePos>...] clauses after the address spec. For example, &-:8- on point 10 means "when 10 is set to diverging, also set 8 to diverging". The cascade applies both to direct keypad commands and to routes that include the master point. If a route or a keypad command would force a single point to two different positions (its own master plus a contradictory slave), it is rejected at load or input time.
Signal types: u=OutboundMain, i=InboundMain, h=MainDwarf, d=ShuntingDwarf, x=Hidden.
The application UI is currently localised in English, Swedish, Danish, Norwegian, and German.
Track labels in the yard diagram (e.g., "Goods track", "Headshunt") can be translated per language. Translations are defined in the [Translations] section of the station file.
When a train route is set, the from signal and any intermediate signals are set to go (green). When a route is cleared, those signals are set back to stop (red), unless they are still needed by another active route.
The // command (cancel all routes) also sets all route signals to stop before releasing locks. The ** command sets all signals to stop regardless of route state.
Detailed signal aspects are typically implemented in the yard's internal control system and are not part of this application.
The intention is that when a station gets occupation feedback, this will also be reflected in the UI, so that green train routes become red when occupied. The train route will also automatically reset when the train reaches the final signal.
The application talks to either a LocoNet command station over a serial interface or a Roco/Fleischmann Z21 (or Z21-compatible, e.g. TAMS mc², YaMoRC YD7010/YD7001) over UDP. Switching is done by the CommandStation:Type setting in appsettings.json:
"CommandStation": {
"Type": "Serial",
"SerialPort": { "PortName": "COM5", "BaudRate": 57600 },
"Z21": { "Address": "192.168.0.111", "CommandPort": 21105, "FeedbackPort": 21106 }
}Type |
Transport | Settings used |
|---|---|---|
Serial |
LocoNet over a USB/serial interface (LocoBuffer-USB, PR3, PR4, etc.) | SerialPort |
Z21 |
Z21 LAN UDP protocol | Z21 |
Only the settings block matching the selected Type is used; the other is ignored but kept as a template.
When running against a Z21, the application sends accessory commands as native XpressNet (LAN_X_SET_TURNOUT) and subscribes to RunningAndSwitching broadcasts to receive turnout state changes. This makes the yard app fully interoperable with the Z21 App, WLANMaus, and other XpressNet clients — point changes from any client are visible in all others. The Z21 bridges XpressNet accessory commands to its internal LocoNet bus, so LocoNet-connected accessory decoders still receive commands.
UDP port 21106 (or whichever port is configured as FeedbackPort) must be allowed through Windows Firewall for feedback to work.
If you run multiple yards from separate Z21 units, each instance of the application subscribes independently to its own Z21.
Point commands are sent as protocol-agnostic accessory commands through the Tellurian communications adapter layer. Each point number maps to one or more hardware accessory addresses configured in the station file. A negative address inverts the direction: Closed becomes Thrown and vice versa.
When a train route is set, the application sends commands for all points in the route in sequence.
When LockOffset is configured, hardware point locking is supported. Setting a train route:
- Sends turnout commands to move points to the correct positions.
- Sends lock commands (
Closed) to each point's lock address (address + offset).
When a train route is cleared:
- Signals are immediately set to stop.
- The route is shown in blue on the UI, indicating that locks are still held.
- After a configurable delay, unlock commands (
Thrown) are sent to the lock addresses and the route is fully cleared.
This two-phase cancellation mirrors real railway operations where point locks are held for a safety period after signal clearance. The delay is configured in the station file (LockReleaseDelay under [Settings]). In development mode, the delay is always 5 seconds.
This feature is designed for Möllehem switch decoders that support individual point locks via a parallel address range. When a point is locked, it cannot be altered via LocoNet, XpressNet, or buttons connected to the decoder.
Logical locking is always active regardless of hardware support: the application prevents conflicting train routes from being set when the same point would need different positions.
The application listens for accessory state notifications from the command station to track the actual position of each point. When a notification is received, the hardware address is mapped back to the corresponding point number and the position is updated in real-time on the UI.
Point changes made from other sources — the Z21 App, WLANMaus, handsets on the LocoNet bus, or other throttles — are reflected in the yard display in real time.
For paired points with sub-point suffixes (e.g., 1a, 1b), each sub-point tracks its position independently from the same or different hardware addresses.
