Open Source · Windows · Roco/Fleischmann Z21 Open Source · Windows · Roco/Fleischmann Z21

Measure your model train
with precision
Ihre Modellbahn
präzise vermessen

OpenSpeed is a free, open-source system for measuring the scale speed and physical length of model trains — using an IR sensor module and a Windows desktop app. OpenSpeed ist ein kostenloses Open-Source-System zur Messung der Maßstabsgeschwindigkeit und Länge von Modellzügen – mit einem IR-Sensor-Modul und einer Windows-Desktop-App.

⚠️ Requires a Roco / Fleischmann Z21 command station.Erfordert eine Roco / Fleischmann Z21-Zentrale. Other DCC systems are not supported. Andere DCC-Systeme werden nicht unterstützt.

Speed from time, length from blockage

Two IR sensors on a straight section of track — at a distance you choose and configure in the firmware — measure both speed and train length without any modifications to the locomotive.

Geschwindigkeit aus Zeit, Länge aus Sperrzeit

Zwei IR-Sensoren auf einem geraden Gleisabschnitt – in einem frei wählbaren, in der Firmware konfigurierten Abstand – messen Geschwindigkeit und Zuglänge ohne Eingriff in die Lokomotive.

Step 1Schritt 1

Train enters sensor fieldZug tritt ins Sensorfeld ein

Whichever sensor fires first becomes the start sensor. Direction is detected automatically — no manual configuration needed.Der Sensor, der zuerst auslöst, wird zum Startsensor. Die Richtung wird automatisch erkannt.

Step 2Schritt 2

Train reaches the second sensorZug erreicht den zweiten Sensor

Transit time between sensors is measured in milliseconds. speed = SENSOR_DISTANCE_M ÷ time × 3.6 gives km/h.Die Durchfahrtszeit wird in ms gemessen. Geschwindigkeit = SENSOR_DISTANCE_M ÷ Zeit × 3,6 ergibt km/h.

Step 3Schritt 3

Train clears the second sensorZug verlässt den zweiten Sensor

Blockage duration × speed = physical length in cm. Result is published via REST after a 1-second debounce.Sperrzeit × Geschwindigkeit = physische Länge in cm. Ergebnis nach 1 s Entprellzeit per REST verfügbar.

ℹ️
The desktop app multiplies the raw speed_kmh value by the configured scale factor (e.g. ×87 for H0) to produce the prototype speed. Scale conversion happens entirely in software. Die Desktop-App multipliziert den rohen speed_kmh-Wert mit dem Maßstabsfaktor (z. B. ×87 für H0). Die Umrechnung erfolgt ausschließlich in der Software.

Everything you need to calibrate your fleet

Two measurement modes, real-time results, and full DCC integration — all in one application.

Alles, um Ihre Flotte zu kalibrieren

Zwei Messmodi, Echtzeitergebnisse und vollständige DCC-Integration – in einer Anwendung.

🚄

Speed MeasurementGeschwindigkeitsmessung

Automatically sweeps every DCC speed step from a configurable start to the maximum, measuring a forward and backward pass for each step. Results appear on a live chart as they arrive.Misst automatisch jeden DCC-Fahrstufenschritt vorwärts und rückwärts. Ergebnisse erscheinen sofort live im Diagramm.

📏

Length MeasurementLängenmessung

Measures the physical length of any train in centimetres. Forward and backward passes are averaged for accuracy.Misst die physische Länge eines Zuges in Zentimetern. Vorwärts- und Rückwärtsfahrt werden gemittelt.

📊

Live Speed PlotEchtzeit-Diagramm

Forward (blue) and backward (orange) passes shown with a legend. X axis fixed to 0–128 speed steps; Y auto-expands if speeds exceed 200 km/h.Vorwärts (blau) und rückwärts (orange) mit Legende. X-Achse auf 0–128 Fahrstufen, Y expandiert automatisch.

📁

Excel ExportExcel-Export

Export all measured speed steps to a formatted .xlsx spreadsheet with one click. Pre-named with decoder address and timestamp.Alle Fahrstufen mit einem Klick in eine .xlsx-Tabelle exportieren, vorbenannt mit Decoderadresse und Zeitstempel.

🌙

Dark & Light ModeHell- & Dunkelmodus

Switch themes via the sun/moon button. The chart adapts automatically. The chosen theme persists across restarts.Wechsel per Sonne/Mond-Schaltfläche. Das Diagramm passt sich an. Die Einstellung bleibt nach Neustart erhalten.

🌍

English & German UIEnglische & deutsche Oberfläche

Switch language at runtime from the header dropdown — no restart required.Umschalten im laufenden Betrieb – kein Neustart erforderlich.

Hardware Requirements

The sensor module is built from off-the-shelf components costing under €20. The Z21 is your existing DCC command station.

Hardware-Anforderungen

Das Sensormodul besteht aus Standard-Komponenten für unter 20 €. Die Z21 ist Ihre vorhandene DCC-Zentrale.

Sensor Module — Bill of Materials Sensor-Modul – Stückliste
ComponentBauteil QtyAnz. NotesHinweise
ESP32 DevKit v1 Any 38-pin variant with GPIO 6 & 7 availableBeliebige 38-Pin-Variante mit GPIO 6 & 7
IR Sensor (TCRT5000) Reflection module with digital OUT, active LOWReflexionsmodul mit digitalem OUT, aktiv LOW
Mounting rail / crossbarMontageschienenhalter Fix sensors at your chosen distance and set SENSOR_DISTANCE_M in firmwareSensoren im selbst gewählten Abstand fixieren und SENSOR_DISTANCE_M setzen
5 V USB power supply5-V-USB-Netzteil Powers the ESP32; sensors fed from its 3.3 V pinVersorgt den ESP32; Sensoren am 3,3-V-Pin
Wi-Fi router / AP Same LAN segment as the Windows PC and Z21Gleiches LAN-Segment wie Windows-PC und Z21
PC & DCC Requirements PC- & DCC-Anforderungen
Z21 command station required — no other system is supported Z21-Zentrale erforderlich – kein anderes System wird unterstützt

CompatibleKompatibel

  • Roco Z21 (all variants)
  • Fleischmann z21 (all variants)
  • z21 start

Not supportedNicht unterstützt

  • Märklin Central Station 2 / 3
  • ESU ECoS
  • Digitrax / NCE / Lenz
  • JMRI, iTrain, other softwareandere Software

OpenSpeed uses the Z21's proprietary LAN protocol, which is specific to Roco/Fleischmann hardware. OpenSpeed kommuniziert über das proprietäre LAN-Protokoll der Z21, das Roco/Fleischmann-spezifisch ist.

Operating SystemBetriebssystem Windows 10 / 11 (64-bit)
RuntimeLaufzeit .NET 8 Desktop Runtime
NetworkNetzwerk PC, ESP32, and Z21 on the same LAN subnet PC, ESP32 und Z21 im selben LAN-Subnetz
CV3 / CV4 Must be 0 — no ramp Muss 0 sein – keine Rampe
Track sectionGleisabschnitt Straight, min. 1.5 m run-up before sensor 1 Gerade, mind. 1,5 m Anlaufstrecke vor Sensor 1

Connecting the IR Sensors to the ESP32

The sensors use the ESP32's internal pull-up resistors. The sensor output pulls the GPIO pin LOW when a train is detected. No external resistors are needed.

IR-Sensoren am ESP32 anschließen

Die Sensoren nutzen die internen Pull-up-Widerstände des ESP32. Der Sensor-Ausgang zieht den GPIO-Pin auf LOW, wenn ein Zug erkannt wird. Externe Widerstände sind nicht erforderlich.

            SENSOR 1 (GPIO 6)                   SENSOR 2 (GPIO 7)
             Entry / Einfahrt                     Exit / Ausfahrt

           ┌──────────────┐                    ┌──────────────┐
           │  TCRT5000    │                    │  TCRT5000    │
           │  Module      │                    │  Module      │
           │              │                    │              │
VCC (3.3V) ┤ VCC      OUT ├─────────────────┤ OUT      VCC ├─── VCC (3.3V)
GND       ┤ GND          │                    │          GND ├─── GND
           └──────┬───────┘                    └───────┬──────┘
                  │ OUT (active LOW)    (active LOW) OUT │
                  │                                      │
      ────────────┼──────────────────────────────────────┼────────────
                  ↓                                      ↓
      ┌───────────────────────────────────────────────────────────────┐
      │                    ESP32 DevKit                              │
      │                                                               │
      │   GPIO 6  ◄──── Sensor 1 OUT    Sensor 2 OUT ────►  GPIO 7   │
      │   3V3     ──── to both sensor VCC pins                       │
      │   GND     ──── to both sensor GND pins                       │
      │                                                               │
      │   USB Micro/C ──────────────────────────────► 5V USB Charger │
      │   Wi-Fi ────────────────────────────────────► LAN Router/AP  │
      └───────────────────────────────────────────────────────────────┘

      ◄──────────── SENSOR_DISTANCE_M (e.g. 0.49 m) ────────────►
      (set this constant in the firmware to match your actual installation)

Sensors face downward from a crossbar — detecting reflections from the
locomotive roof / wagon tops as the train passes underneath.
📐 Sensor distance — choose any value, then set it in the firmware Sensorabstand – frei wählbar, dann in der Firmware einstellen

The firmware constant SENSOR_DISTANCE_M tells the ESP32 how far apart the sensors are. You decide the distance based on your layout — then measure it precisely and enter the exact value before flashing.

  • A longer base distance (e.g. 1.0 m) improves speed accuracy at low speeds.
  • A shorter distance (e.g. 0.3 m) is easier to fit on tight layouts.
  • The default in the sketch is 0.49 m (49 cm) — change it to match your actual installation.
  • After mounting, measure the distance between the detector centres with a ruler to the nearest millimetre, then update SENSOR_DISTANCE_M and re-flash.
  • If the distance shifts after installation, you must re-measure and re-flash — the firmware does not auto-calibrate.

Die Firmware-Konstante SENSOR_DISTANCE_M teilt dem ESP32 den Abstand zwischen den Sensoren mit. Sie wählen den Abstand passend zu Ihrer Anlage – dann genau messen und den exakten Wert vor dem Flashen eintragen.

  • Ein größerer Abstand (z. B. 1,0 m) verbessert die Genauigkeit bei niedrigen Geschwindigkeiten.
  • Ein kleinerer Abstand (z. B. 0,3 m) lässt sich auf beengten Anlagen leichter unterbringen.
  • Der Standardwert im Sketch ist 0,49 m (49 cm) – auf den tatsächlichen Einbauwert ändern.
  • Nach der Montage den Abstand zwischen den Sensormittelpunkten auf den Millimeter genau nachmessen und SENSOR_DISTANCE_M aktualisieren und neu flashen.
  • Verändert sich der Abstand nach der Installation, muss neu gemessen und neu geflasht werden.
ESP32 Pin Assignment ESP32-Pin-Belegung
ESP32 Pin Connects toVerbindet mit Wire colorKabelfarbe
3V3 Both sensor VCC pinsBeide Sensor-VCC-Pins RedRot
GND Both sensor GND pinsBeide Sensor-GND-Pins BlackSchwarz
GPIO 6 Sensor 1 OUT — entry sensor Sensor 1 OUT – Einfahrtsensor GreenGrün
GPIO 7 Sensor 2 OUT — exit sensor Sensor 2 OUT – Ausfahrtsensor Yellow / OrangeGelb / Orange
USB 5 V USB power supply5-V-USB-Netzteil
ℹ️
The firmware uses INPUT_PULLUP on both GPIO pins. The sensor's OUT pin should be open-collector / open-drain and pull to GND when a train is detected — as TCRT5000 modules do. Die Firmware setzt beide GPIO-Pins auf INPUT_PULLUP. Der OUT-Pin muss Open-Collector/Open-Drain sein und bei Erkennung auf GND ziehen – wie TCRT5000-Module es tun.
↔️
Direction is auto-detected.Richtung wird automatisch erkannt. The firmware treats whichever sensor fires first as the start sensor. Die Firmware behandelt den zuerst auslösenden Sensor als Startsensor.
Track Mounting Streckenmontage

Mount the sensors on a crossbar over a straight section of track so they point downward toward passing rolling stock.

  Crossbar / arch above the track
  ┌─────────────────────────────────────────┐
  │  [Sensor 1 / GPIO 6]  [Sensor 2 / GPIO 7]│
  │          ↓                    ↓          │
══╪══════════╪════════════════════╪══════════╪══  ← Track
  │    Detection zone        Detection zone  │
  └─────────────────────────────────────────┘
        ◄── SENSOR_DISTANCE_M (your value) ──►
  • Sensor height: approx. 5–15 mm above the tallest vehicle in your fleet.
  • Test with the shortest and tallest rolling stock — all must trigger reliably.
  • Avoid direct sunlight on the sensors (causes false triggers).
  • Provide at least 1.5 m of straight track before Sensor 1 for constant-speed run-up.
  • Leave at least 1 m of track after Sensor 2 for the loco to decelerate and stop.

Sensoren an einem Querbalken über einem geraden Gleisabschnitt montieren, sodass sie nach unten auf das Rollmaterial zeigen.

  Querbalken / Bogen über dem Gleis
  ┌─────────────────────────────────────────┐
  │  [Sensor 1 / GPIO 6]  [Sensor 2 / GPIO 7]│
  │          ↓                    ↓          │
══╪══════════╪════════════════════╪══════════╪══  ← Gleis
  │  Detektionszone       Detektionszone     │
  └─────────────────────────────────────────┘
        ◄── SENSOR_DISTANCE_M (your value) ──►
  • Sensorhöhe: ca. 5–15 mm über dem höchsten Fahrzeug im Fuhrpark.
  • Mit kleinstem und größtem Rollmaterial testen – alle müssen zuverlässig auslösen.
  • Direktes Sonnenlicht auf die Sensoren vermeiden.
  • Mindestens 1,5 m geraden Anlaufweg vor Sensor 1 einplanen.
  • Mindestens 1 m Bremsweg nach Sensor 2 vorsehen.

Flashing the ESP32 Firmware

The sketch lives in Arduino Sketch/OpenSpeed/OpenSpeed.ino. You only need to edit two lines before flashing.

ESP32-Firmware flashen

Der Sketch befindet sich in Arduino Sketch/OpenSpeed/OpenSpeed.ino. Vor dem Flashen müssen nur zwei Zeilen angepasst werden.

Prerequisites

1

Install Arduino IDE 2

Download from arduino.cc. In Board Manager install esp32 by Espressif Systems (version 2.x or 3.x).

2

Install libraries

In Library Manager install:
ESPAsyncWebServer by me-no-dev
AsyncTCP by me-no-dev

3

Open the sketch

Open Arduino Sketch/OpenSpeed/OpenSpeed.ino from the repository root.

Voraussetzungen

1

Arduino IDE 2 installieren

Von arduino.cc herunterladen. Im Board-Manager esp32 von Espressif Systems installieren (Version 2.x oder 3.x).

2

Bibliotheken installieren

Im Bibliotheks-Manager installieren:
ESPAsyncWebServer von me-no-dev
AsyncTCP von me-no-dev

3

Sketch öffnen

Arduino Sketch/OpenSpeed/OpenSpeed.ino aus dem Repository-Stammverzeichnis öffnen.

Edit & Flash

4

Set your Wi-Fi credentials

Edit the two Wi-Fi constants at the top of the sketch:

const char* WIFI_SSID = "YourNetworkName"; const char* WIFI_PASS = "YourPassword";
5

⚠️ Set your sensor distance

This is the most important configuration step. Measure the distance between your two sensor centres, then set:

const float SENSOR_DISTANCE_M = 0.49f;

If the value is wrong, all speed and length measurements will be systematically incorrect with no error message.

You can also change GPIO pins here if your wiring differs:

const int SENSOR1_PIN = 6; const int SENSOR2_PIN = 7;
6

Select board & upload

Tools → Board → ESP32 Dev Module. Select the COM port. Click Upload ▶.

7

Note the IP address

Open Serial Monitor at 115200 baud. After "Connected!" the assigned IP prints on one line, e.g. 192.168.1.42. Enter this as Speed Sensor Address in OpenSpeed.

Anpassen & Flashen

4

WLAN-Zugangsdaten eintragen

Die zwei WLAN-Konstanten am Anfang des Sketches anpassen:

const char* WIFI_SSID = "IhrNetzwerkname"; const char* WIFI_PASS = "IhrPasswort";
5

⚠️ Sensorabstand einstellen

Dies ist der wichtigste Konfigurationsschritt. Den Abstand zwischen den Sensormittelpunkten messen und setzen:

const float SENSOR_DISTANCE_M = 0.49f;

Ist der Wert falsch, sind alle Messungen systematisch falsch – ohne Fehlermeldung.

GPIO-Pins können hier ebenfalls angepasst werden:

const int SENSOR1_PIN = 6; const int SENSOR2_PIN = 7;
6

Board auswählen & hochladen

Werkzeuge → Board → ESP32 Dev Module. COM-Port auswählen. Upload ▶ klicken.

7

IP-Adresse notieren

Serial Monitor auf 115200 Baud öffnen. Nach "Connected!" wird die IP angezeigt, z. B. 192.168.1.42. Diese als Speed Sensor Address in OpenSpeed eintragen.

Setting Up the Desktop App

OpenSpeed runs on Windows 10/11. No installer needed — unzip and run.

Einrichtung der Desktop-App

OpenSpeed läuft auf Windows 10/11. Kein Installer – entzippen und starten.

Installation

1

Install the runtime

Download and install the Windows Desktop Runtime 8.x from dotnet.microsoft.com if not already installed.

2

Download OpenSpeed

Download the latest release from GitHub Releases and extract the ZIP to any folder.

3

Run OpenSpeed.UI.exe

Double-click OpenSpeed.UI.exe. Settings are saved to %USERPROFILE%\OpenSpeed\Settings.json.

Installation

1

Laufzeitumgebung installieren

Die Windows Desktop Runtime 8.x von dotnet.microsoft.com herunterladen und installieren.

2

OpenSpeed herunterladen

Aktuelle Version von GitHub Releases herunterladen und die ZIP-Datei entpacken.

3

OpenSpeed.UI.exe starten

Doppelklick auf OpenSpeed.UI.exe. Einstellungen werden unter %USERPROFILE%\OpenSpeed\Settings.json gespeichert.

First-Run Configuration

1

Enter Z21 address

Type the IP address of your Z21 in the Z21 Address field. The status dot turns green when the connection is established.

2

Enter sensor address

Enter the IP address of the ESP32 from the Serial Monitor output.

3

Configure the locomotive

Set Decoder Address, DCC Speed Mode (14 / 28 / 128), and Scale (e.g. 87 for H0). CV3 and CV4 on the decoder must be 0.

4

Position the loco & start

Place the locomotive before Sensor 1 in the forward direction. Click Start Speed Measurement. The app drives it through all configured speed steps automatically.

Ersteinrichtung

1

Z21-Adresse eingeben

IP-Adresse der Z21 in das Feld Z21 Address eingeben. Der Status-Punkt wird grün, sobald die Verbindung steht.

2

Sensor-Adresse eingeben

IP-Adresse des ESP32 aus dem Serial-Monitor-Output eingeben.

3

Lokomotive konfigurieren

Decoder-Adresse, DCC-Fahrstufenmodus (14 / 28 / 128) und Maßstab (z. B. 87 für H0) einstellen. CV3 und CV4 müssen 0 sein.

4

Lok positionieren & starten

Lokomotive vor Sensor 1 in Vorwärtsrichtung aufstellen. Geschwindigkeitsmessung starten klicken. Die App fährt die Lok automatisch durch alle Fahrstufen.

⚠️
CV3 / CV4 = 0: Acceleration and deceleration ramps must be disabled before measuring. If either CV is non-zero, the timing logic will fail and all measurements will be incorrect. Beschleunigungs- und Bremsrampen müssen vor der Messung deaktiviert werden. Sind diese CVs ungleich 0, schlägt die Zeitmesslogik fehl.

ESP32 HTTP Endpoints

The firmware serves three endpoints on port 80 via ESPAsyncWebServer. All return JSON — you can call them in a browser for debugging.

ESP32-HTTP-Endpunkte

Die Firmware stellt drei Endpunkte auf Port 80 via ESPAsyncWebServer bereit. Alle antworten mit JSON und können zum Debuggen im Browser aufgerufen werden.

GET /result Latest measurement resultLetztes Messergebnis
{ "id": 3178432491, "train_length_cm": 31.247, "speed_kmh": 2.940 }

The desktop app polls this every 250 ms and detects a new measurement by comparing the id field. The id is a random 32-bit number (not a counter).

Die Desktop-App ruft diesen Endpunkt alle 250 ms ab. Eine neue Messung wird durch Vergleich des id-Felds erkannt. Die id ist eine zufällige 32-Bit-Zahl.

GET /reset Reset all state, clear result, set id to 0Zustand zurücksetzen, Ergebnis löschen, id auf 0
{ "status": "reset" }

Called before every measurement pass. Resets the state machine to WAITING_FOR_MEASUREMENT and clears all timing variables.

Wird vor jedem Messdurchlauf aufgerufen. Setzt den Zustandsautomaten auf WAITING_FOR_MEASUREMENT und löscht alle Zeitvariablen.

GET /status Current sensor stateAktueller Sensorzustand
{ "status": 0 }

Polled once per second by the ReachabilityMonitor to drive the connection status dot. A successful HTTP response means the sensor is reachable regardless of the status value.

Wird vom ReachabilityMonitor einmal pro Sekunde abgerufen, um die Verbindungsanzeige zu steuern.

Quick connectivity test:Schneller Verbindungstest: Open http://<sensor-ip>/status in a browser. If you see {"status":0} the firmware is running and reachable. Im Browser http://<sensor-ip>/status öffnen. Erscheint {"status":0}, läuft die Firmware und ist erreichbar.