Tellurian.Trains.Protocols.XpressNet
1.8.0
Prefix Reserved
dotnet add package Tellurian.Trains.Protocols.XpressNet --version 1.8.0
NuGet\Install-Package Tellurian.Trains.Protocols.XpressNet -Version 1.8.0
<PackageReference Include="Tellurian.Trains.Protocols.XpressNet" Version="1.8.0" />
<PackageVersion Include="Tellurian.Trains.Protocols.XpressNet" Version="1.8.0" />
<PackageReference Include="Tellurian.Trains.Protocols.XpressNet" />
paket add Tellurian.Trains.Protocols.XpressNet --version 1.8.0
#r "nuget: Tellurian.Trains.Protocols.XpressNet, 1.8.0"
#:package Tellurian.Trains.Protocols.XpressNet@1.8.0
#addin nuget:?package=Tellurian.Trains.Protocols.XpressNet&version=1.8.0
#tool nuget:?package=Tellurian.Trains.Protocols.XpressNet&version=1.8.0
Tellurian.Trains.Protocols.XpressNet
A complete C# implementation of the XpressNet protocol for digital model railroad control.
Overview
This library provides a comprehensive, type-safe implementation of the XpressNet protocol, enabling full control of DCC model railroad systems through XpressNet-compatible command stations (Lenz LZ100/LZV100, Roco Z21, etc.).
XpressNet is a serial bus protocol developed by Lenz Elektronik GmbH that provides a standardized interface between throttles, computer interfaces, and DCC command stations.
Features
Fully Implemented
Locomotive Control
- Speed control with 14, 27, 28, and 126 speed steps
- Direction control (forward/backward)
- Function control (F0-F28)
- Emergency stop (per-locomotive and global)
- Short address support (1-127)
- Long address support (128-9999)
- Locomotive state queries
Track Power Management
- Track power on/off
- Emergency stop (all locomotives)
- Short circuit detection and notification
- Power-up mode configuration (manual/automatic)
Accessory Control
- Turnout/accessory control (addresses 1-1024)
- Accessory decoder information requests
- Feedback broadcast notifications
- Turnout status queries
Programming - Service Mode
- Register Mode read/write (registers 1-8)
- Direct CV Mode read/write (CVs 1-256)
- Paged Mode read/write (CVs 1-256)
- Service mode result polling
- Programming status notifications (ready, busy, short circuit, timeout)
Programming - Operations Mode (POM)
- Byte mode write (CVs 1-1024)
- Bit mode write (CVs 1-1024)
- Full locomotive address range (1-9999)
Double Header Operations
- Establish Double Header (join two locomotives)
- Dissolve Double Header
- Full address range support (1-9999)
Multi-Unit (Consist) Operations
- Add locomotive to Multi-Unit (with direction control)
- Remove locomotive from Multi-Unit
- Multi-Unit address range (1-99)
Address Search & Stack
- Multi-Unit member inquiry (forward/backward search)
- Multi-Unit address inquiry
- Command station stack inquiry
- Delete locomotive from stack
Function Status
- Query function status (momentary vs on/off)
- Set function state for F0-F12
- Per-function momentary/on-off configuration
System Monitoring
- Hardware and firmware version detection
- Command station status queries
- Broadcast subject configuration
- Transfer error detection
- Command station busy notification
Installation
dotnet add package Tellurian.Trains.Protocols.XpressNet
Or add to your .csproj:
<PackageReference Include="Tellurian.Trains.Protocols.XpressNet" />
Quick Start
Basic Locomotive Control
using Tellurian.Trains.Protocols.XpressNet;
using Tellurian.Trains.Protocols.XpressNet.Commands;
using Tellurian.Trains.Protocols.XpressNet.Notifications;
// Create locomotive address
var address = new LocoAddress(3);
// Create speed/direction command (126 speed steps, speed 64, forward)
var driveCmd = new LocoDriveCommand(address, new LocoSpeed(64, SpeedSteps.S126), LocoDirection.Forward);
byte[] message = driveCmd.GetBytesWithChecksum();
// Send message to command station...
// Emergency stop a specific locomotive
var stopCmd = new LocoEmergencyStopCommand(address);
// Parse incoming notifications
var notification = NotificationFactory.Create(receivedBytes);
if (notification is LocoInfoNotification locoInfo)
{
Console.WriteLine($"Loco {locoInfo.Address}: Speed={locoInfo.Speed}, Direction={locoInfo.Direction}");
}
Track Power Control
// Turn track power on
var powerOn = new TrackPowerOnCommand();
SendCommand(powerOn);
// Turn track power off
var powerOff = new TrackPowerOffCommand();
SendCommand(powerOff);
// Emergency stop all locomotives
var emergencyStop = new EmergencyStopCommand();
SendCommand(emergencyStop);
Function Control
var address = new LocoAddress(3);
// Set functions F0-F4 (lights and sound)
var funcGroup1 = new LocoFunctionCommand(address, FunctionGroup.Group1,
f0: true, // Headlight
f1: true, // Bell
f2: false, f3: false, f4: false);
SendCommand(funcGroup1);
// Set functions F5-F8
var funcGroup2 = new LocoFunctionCommand(address, FunctionGroup.Group2,
f5: true, // Horn
f6: false, f7: false, f8: false);
SendCommand(funcGroup2);
Accessory/Turnout Control
// Switch turnout 100 to diverging
var turnout = new AccessoryFunctionCommand(AccessoryAddress.From(100), AccessoryOutput.Output1, true);
SendCommand(turnout);
// Query turnout status
var query = new AccessoryInfoRequestCommand(AccessoryAddress.From(100));
SendCommand(query);
// Handle response
if (notification is AccessoryDecoderInfoNotification info)
{
Console.WriteLine($"Turnout status: {info.FirstTurnoutStatus}");
}
CV Programming (Service Mode)
// Read CV29 using Direct Mode
var readCmd = new ServiceModeReadDirectCommand(29);
SendCommand(readCmd);
// Poll for results
var resultsCmd = new ServiceModeResultsCommand();
SendCommand(resultsCmd);
// Handle response
if (notification is ServiceModeDirectCVNotification cvResponse)
{
Console.WriteLine($"{cvResponse.CV}"); // "CV29=value"
}
// Write CV3 = 10 using Direct Mode
var writeCmd = new ServiceModeWriteDirectCommand(3, 10);
SendCommand(writeCmd);
CV Programming (Operations Mode - ProgramOnMain)
var address = new LocoAddress(1234);
// Write CV29 = 6 while locomotive is on main track
var writeByte = new ProgramOnMainWriteByteCommand(address, new CV(29, 6));
SendCommand(writeByte);
// Write single bit (CV29 bit 5 = 1)
var writeBit = new ProgramOnMainWriteBitCommand(address, 29, 5, true);
SendCommand(writeBit);
Double Header Operations
// Create a double header with locomotives 100 and 200
var establish = new EstablishDoubleHeaderCommand(
new LocoAddress(100),
new LocoAddress(200));
SendCommand(establish);
// Dissolve the double header
var dissolve = new DissolveDoubleHeaderCommand(new LocoAddress(100));
SendCommand(dissolve);
Multi-Unit (Consist) Operations
// Add locomotive 100 to Multi-Unit 10 (same direction)
var addLoco = new AddLocoToMultiUnitCommand(
new LocoAddress(100),
multiUnitAddress: 10,
reversed: false);
SendCommand(addLoco);
// Add locomotive 200 reversed
var addReversed = new AddLocoToMultiUnitCommand(
new LocoAddress(200),
multiUnitAddress: 10,
reversed: true);
SendCommand(addReversed);
// Remove locomotive from Multi-Unit
var removeLoco = new RemoveLocoFromMultiUnitCommand(
new LocoAddress(100),
multiUnitAddress: 10);
SendCommand(removeLoco);
Core Concepts
Message Structure
All XpressNet messages follow this format:
[Header] [Data Byte 1] [Data Byte 2] ... [XOR Checksum]
- Header: Encodes message type and data length (upper nibble = 0x0-0xF for length)
- Data Bytes: Message-specific payload
- Checksum: XOR of all preceding bytes
Checksum Calculation
// Automatic checksum via GetBytesWithChecksum()
byte[] message = command.GetBytesWithChecksum();
// Manual validation via Packet class
var packet = new Packet(receivedBytes);
if (packet.IsValid)
{
// Process message
}
Address Encoding
Locomotive Addresses:
- Short (1-127): Single byte, AH=0x00
- Long (128-9999): Two bytes with 0xC0 flag in high byte
var shortAddr = new LocoAddress(3); // AH=0x00, AL=0x03
var longAddr = new LocoAddress(1234); // AH=0xC4, AL=0xD2
byte[] bytes = longAddr.GetBytesAccordingToXpressNet();
Accessory Addresses:
- Range: 1-1024
- Encoded as group address (address/4) and subaddress (address%4)
Speed Steps
Four speed step modes are supported:
| Mode | Steps | Encoding |
|---|---|---|
| 14 | 0-14 + E-Stop | 4 bits |
| 27 | 0-27 + E-Stop | 5 bits |
| 28 | 0-28 + E-Stop | 5 bits |
| 126 | 0-126 + E-Stop | 7 bits |
var speed14 = new LocoSpeed(10, SpeedSteps.S14);
var speed126 = new LocoSpeed(64, SpeedSteps.S126);
Architecture
Layer Structure
┌─────────────────────────────────┐
│ Application Layer │
│ (Your Model Railroad Control) │
└────────────┬────────────────────┘
│
┌────────────▼────────────────────┐
│ XpressNet Protocol Layer │
│ • Commands (outgoing) │
│ • Notifications (incoming) │
│ • NotificationFactory │
└────────────┬────────────────────┘
│
┌────────────▼────────────────────┐
│ Transport Layer │
│ (UDP via Z21, Serial via LI) │
│ Tellurian.Communications.* │
└─────────────────────────────────┘
Key Classes
Commands
- Locomotive:
LocoDriveCommand,LocoFunctionCommand,LocoEmergencyStopCommand,GetLocoInfoCommand - Track Power:
TrackPowerOnCommand,TrackPowerOffCommand,EmergencyStopCommand - Accessories:
AccessoryFunctionCommand,AccessoryInfoRequestCommand - Service Mode:
ServiceModeReadDirectCommand,ServiceModeWriteDirectCommand,ServiceModeResultsCommand - Operations Mode:
ProgramOnMainWriteByteCommand,ProgramOnMainWriteBitCommand - Double Header:
EstablishDoubleHeaderCommand,DissolveDoubleHeaderCommand - Multi-Unit:
AddLocoToMultiUnitCommand,RemoveLocoFromMultiUnitCommand - Address Search:
AddressInquiryStackCommand,AddressInquiryMultiUnitCommand,DeleteLocoFromStackCommand - System:
GetVersionCommand,GetStatusCommand,SetPowerUpModeCommand
Notifications
- Locomotive:
LocoInfoNotification,LocoOperatedByAnotherDeviceNotification - Track Power:
TrackPowerOnBroadcast,TrackPowerOffBroadcast,EmergencyStopBroadcast - Accessories:
AccessoryDecoderInfoNotification,FeedbackBroadcast - Programming:
ServiceModeDirectCVNotification,ServiceModeRegisterPagedNotification - System:
VersionNotification,StatusChangedNotification,TransferErrorNotification,CommandStationBusyNotification - Multi-Unit/DH:
MUDHErrorNotification,AddressRetrievalNotification - Function Status:
FunctionStatusNotification
Data Types
LocoAddress- Locomotive address (1-9999)AccessoryAddress- Accessory/turnout address (1-1024)LocoSpeed- Speed with step modeLocoDirection- Forward/Backward/UnchangedLocoFunctionStates- F0-F28 statesCV- Configuration variable with number (1-1024) and value (0-255)
Error Handling
Programming Errors
var notification = NotificationFactory.Create(buffer);
switch (notification)
{
case WriteCVShortCircuitResponse:
Console.WriteLine("Short circuit on programming track!");
break;
case WriteCVTimeoutResponse:
Console.WriteLine("No decoder acknowledgment received");
break;
case ProgrammingStationBusyBroadcast:
Console.WriteLine("Programming station busy, retry later");
break;
}
Multi-Unit/Double Header Errors
if (notification is MUDHErrorNotification error)
{
Console.WriteLine($"MU/DH Error: {error.ErrorMessage}");
// Possible errors:
// - NotOperatedByDevice
// - OperatedByAnotherDevice
// - AlreadyInConsist
// - SpeedNotZero
// - NotInMultiUnit
// - StackFull
}
System Errors
switch (notification)
{
case TransferErrorNotification:
Console.WriteLine("Checksum error - resend command");
break;
case CommandStationBusyNotification:
Console.WriteLine("Command station busy - retry");
break;
case UnknownCommandNotification:
Console.WriteLine("Command not supported by this station");
break;
}
Testing
Run tests:
dotnet test Tellurian.Trains.Protocols.XpressNet.Tests
The test suite (156 tests) covers:
- Command byte generation
- Notification parsing
- Checksum validation
- Address encoding
- Speed step conversions
- Error handling
Hardware Compatibility
This library is compatible with:
Command Stations:
- Lenz LZ100, LZV100, LZV200
- Roco Z21, z21 (via XpressNet tunneling)
- Other XpressNet 3.0+ compatible stations
Interfaces:
- Lenz LI100, LI100F, LI101F
- Roco Z21 (UDP/LAN)
Protocol Specification
Based on:
- XpressNet Protocol Specification by Lenz Elektronik GmbH
- Covers XpressNet versions up to 3.6
Contributing
Contributions welcome! Please:
- Follow existing code style
- Add XML documentation to public APIs
- Include unit tests for new features
- Update documentation
License
This library is part of the Tellurian.Trains project.
Support
For issues, questions, or contributions:
- GitHub Issues: Tellurian.Trains.Control
See Also
- API-SPECIFICATION.md - Detailed API reference
- CLAUDE.md - Development guidelines
- Specifications/ - Protocol specification documents
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Tellurian.Trains.Communications.Interfaces (>= 1.8.0)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on Tellurian.Trains.Protocols.XpressNet:
| Package | Downloads |
|---|---|
|
Tellurian.Trains.Adapters.Z21
A .NET adapter for the Roco/Fleischmann Z21 digital command station. |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.8.0 | 168 | 4/17/2026 |
| 1.7.15 | 147 | 4/16/2026 |
| 1.7.14 | 135 | 4/16/2026 |
| 1.7.13 | 134 | 4/16/2026 |
| 1.7.12 | 133 | 4/16/2026 |
| 1.7.11 | 124 | 4/15/2026 |
| 1.7.10 | 132 | 4/15/2026 |
| 1.7.9 | 132 | 4/15/2026 |
| 1.7.8 | 134 | 4/15/2026 |
| 1.7.7 | 133 | 4/15/2026 |
| 1.7.6 | 136 | 4/15/2026 |
| 1.7.5 | 131 | 4/15/2026 |
| 1.7.4 | 129 | 4/15/2026 |
| 1.7.3 | 255 | 3/28/2026 |
| 1.7.2 | 139 | 3/28/2026 |
| 1.7.1 | 134 | 3/27/2026 |
| 1.7.0 | 157 | 3/16/2026 |
| 1.6.1 | 150 | 2/22/2026 |
| 1.6.0 | 132 | 2/19/2026 |
| 1.5.0 | 139 | 2/11/2026 |