Skip to content
 
 

Latest commit

 

History

464 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

NextCab

NextCab is a modular, software-first fork of WiTcontroller — a DIY WiFi throttle for model trains.

It began as a personal exploration: what happens if a DCC throttle is treated as evolving software rather than fixed firmware? This project is not commercial and has no fixed roadmap. It exists to explore what a modern, extensible, software-defined throttle can become.


Simulator Mode

NextCab's defining feature is Simulator Mode: a natural, physical control plane for operating a train as a train, rather than as a collection of decoder CVs and function numbers.

The large rotary encoder is the center of that interface:

  • Rotate to set the driver's requested speed or power.
  • Double-click to cycle momentum through Off, Low, Medium, and High.
  • Press and hold to brake. At zero throttle this is an accelerated stop; above zero it is a hold-to-slow dynamic brake.
  • Release to let the train return smoothly toward its requested speed.

Simulator Mode coordinates the things an operator naturally thinks about in one interaction plane:

  • Momentum and acceleration are simulated in the throttle, without changing decoder CVs.
  • Braking behavior follows the selected locomotive type and consist size.
  • Sound functions respond automatically to throttle effort, braking, and dynamic braking.
  • The display presents requested speed, actual speed, power, momentum level, and brake/ramp state together.

It is more than a momentum setting. It is a software driving model that turns one encoder into a throttle, brake handle, and sound-control interface while preserving ordinary WiThrottle compatibility underneath.

Simulator Mode Display

When Simulator Mode is active, the main display shows power as a percentage and the simulated speed as a secondary reading. The screen also indicates momentum level and whether the train is braking or ramping toward its requested speed.

NextCab Simulator Mode UI


Design Philosophy

Drive Your Trains, Not Your Decoders

NextCab's design philosophy can be summarized in one sentence: drive your trains, not your decoders.

The throttle owns intent: acceleration, coasting, braking, sound cues, and locomotive-specific behavior. The decoder still receives ordinary DCC speed and function commands, but the operator works through a consistent physical model instead of manually reprogramming momentum for every locomotive.

Physical Design

NextCab was developed with a specific physical form factor in mind:

https://www.thingiverse.com/thing:7029069

This case design places a large rotary encoder at the center of the interaction model. NextCab will run on other hardware configurations, but the Simulator Mode experience is designed around this encoder-led layout.


Origins

NextCab is forked from WiTcontroller by Peter Akers (flash62au).

WiTcontroller remains an excellent DIY WiThrottle-compatible controller. NextCab builds on that work with a modular architecture and a software driving model.


What Is New in NextCab

  • Simulator Mode: momentum, locomotive-aware braking, sound control, and display feedback from the encoder.
  • Physics-based acceleration with locomotive-type and consist effects.
  • Per-loco speed caps and sound-function configuration.
  • Direction-change safety that brakes a moving train before reversing.
  • Double-click, hold, and canonical press/release input handling.
  • Modular input and rendering components under src/core.

Momentum can be enabled, adjusted, or disabled dynamically from the throttle itself (via double-click on the encoder). This allows:

  • Experienced operators to enjoy realistic acceleration and braking
  • Younger or casual operators to run trains without delay
  • Mixed skill levels to coexist on the same layout

Momentum is treated as an interaction mode, not a decoder constraint.

This keeps realism optional — and reversible — without requiring CV changes.

Braking

Hold the encoder to brake. What happens depends on the current throttle position:

Throttle Gesture Behavior
Above zero Hold encoder Dynamic brake — the traction motors switch to generators, retarding the train while you hold. The braking effect fades as speed decreases (just like the real thing — dynamic brakes can't bring you to a full stop). Release the encoder and the train accelerates back to the set speed.
At zero Hold encoder Air brake — accelerates deceleration to bring the train to a complete stop quickly.

Dynamic braking is loco-type-aware. Each locomotive type has a distinct brake profile that controls how aggressively speed is shed and the minimum speed before dynamic brakes disengage:

Loco Type Deceleration Min Speed Character
Diesel Moderate (4.0) 20 Rheostatic — resistor grid dynamic brakes
Steam Gentle (3.0) 20 Air brakes only — no dynamic braking available
Electric Strong (5.0) 10 Regenerative — feeds energy back to catenary

While braking is active, the speed value is drawn inverted on OLED displays or changes color on TFT displays.

If a dynamic brake sound function is configured for the locomotive (funcDynamicBrake in the loco config), it will be activated automatically when braking engages and deactivated on release.

Consist Power Scaling

When multiple locomotives are consisted together, each additional unit contributes roughly 15% more tractive effort during acceleration. This happens automatically — no configuration required.

A two-unit consist accelerates slightly faster than a single locomotive. A four-unit consist pulls noticeably harder. Just like on real railroads, adding power to the head end makes a difference you can feel through the throttle.

This only affects acceleration when momentum is enabled. Top speed and braking behavior remain unchanged.


Compatibility

NextCab retains compatibility with WiThrottle-compatible servers (JMRI, DCC-EX, etc.) and supports the same core hardware configurations as WiTcontroller unless otherwise noted.

Most existing WiTcontroller build instructions and hardware layouts remain valid.

However, internal architecture and input handling have diverged significantly.


Versioning

NextCab uses independent semantic versioning. The current development release is:

v0.2.0

NextCab remains pre-1.0 and should be considered a work in progress. The version indicates project maturity and feature scope, not a promise of stable hardware support across every configuration.

Prerequisites

Recommended Simulator Mode Setup

NextCab manages momentum and braking in the controller. For predictable operation, disable decoder momentum on every locomotive that will use Simulator Mode. Leaving decoder momentum enabled makes the controller's simulated acceleration and braking fight the decoder's own delay.

Calibrate locomotives to a common speed reference before using them together. By default, NextCab interprets DCC speed step 126 as 100 scale mph. When metric units are configured, NextCab automatically displays the equivalent value, approximately 161 kph. A useful target is to adjust both passenger and freight locomotives so full throttle reaches that same scale speed. This gives the controller a consistent reference for the Simulator Mode speed display and makes locomotives behave more predictably when operated together.

The loco configuration wizard lets you enter each locomotive's actual top speed in the selected scale units. NextCab converts that value to a DCC speed-step cap. In a consist, it applies the lowest cap among the selected locomotives, so a faster locomotive does not overrun a slower one. Rather than flat-lining once that cap is reached, the full throttle range is rescaled onto it, so 100% throttle lines up with the slowest locomotive's actual top speed. This is a protective cap, not automatic speed matching: physical calibration remains the best way to produce smooth consists.

For sound-equipped locomotives, also:

  1. Disable automatic notching.
  2. Configure throttle-up, throttle-down, brake squeal, brake release, dynamic-brake, and prime-mover sound functions in the loco configuration wizard when those sounds are available.

Sound-function numbers are stored per locomotive. Leave a function unset when a decoder does not provide that sound. Brake squeal is pulsed once while a braked train slows through the low-speed range. Brake release is pulsed when power is next requested after a braked stop. Prime mover is sent as a normal function press when a locomotive is acquired and released, so a decoder's configured toggle function can start and stop the sound. Steam locomotives skip only the throttle-up/down sound functions.

  1. Some basic soldering skills. Soldered wiring is recommended for a compact handheld controller.

  2. A WiThrottle-compatible server to connect to, such as JMRI, DCC-EX EX-CommandStation, MRC WiFi, Digitrax LnWi, or NCE WiFiTrax.

Building

Original LOLIN32 Components

The original hardware build uses a WeMos LOLIN32 Lite with LiPo charger, a 3x4 keypad, KY-040 or EC11 rotary encoder, 128x64 I2C OLED, LiPo battery, case, knob, and hookup wire. A 4x4 keypad, additional buttons, potentiometer, battery monitor, and larger 128x64 OLED are optional. See the pinout diagrams below.

The current firmware also supports ESP32-S3/TFT builds. The supported PlatformIO environments and the Feather S3 wiring notes are documented in the Loading the code section and docs/HOW_TO_BUILD.md.

Optional: You can use a 4x4 keypad instead of the 3x4 keypad.
Note: You will need to make a small configuration change in config_buttons.h for this to work correctly.

Optional: Up to eleven (11) additional push buttons can be added directly to the ESP32, each with their own independent commands. (Example)

Optional: A 1.3" or 2.4" OLED Display (128x64) can be used instead of the 0.96" OLED Display 128x64 (Example) Note: You will need to make a minor change in the config file for this to work correctly.

Optional: It is possible to use a potentiometer instead of the rotary encoder for throttle control. The code supports it if you make the appropriate configuration changes in config_buttons.h. This has had only limited testing; see config_buttons_example.h.


Pinouts

Standard Configuration Pinouts
This is the simplest form of the WiTcontroller Assembly diagram

Pinouts for Optional Additional Buttons Assembly diagram - Optional Additional Buttons

Pinouts for Optional Additional Buttons - With Pullups Assembly diagram - Optional Additional Buttons - With Pullups

Pinouts for Optional 4x4 keypad and Additional Buttons - without pullup resistors Assembly diagram - 4x4 keypad and Optional Additional Buttons

Pinouts for Optional Battery Monitor and Additional Buttons Assembly diagram - Optional Battery Monitor and Additional Buttons

Warning: Different keypad manufacturers may arrange the pins on the base of the keypad differently. See notes in the Default Pins for the keypads section below.


Default Pins

Default ESP32 and WiTcontroller pins

Click to expand to see a table of the default pinouts
         3x4    4x4    OLED Encoder  Additional   Battery
    Pin Keypad Keypad                  Buttons     Test
     0    C1     C1                             
N/A  1                                       
     2    C2     C2                             
N/A  3                                     
     4    C0     C0                             
     5                                  AB0     
N/A  6
N/A  7
N/A  8
N/A  9
N/A 10
N/A 11
    12                        DT
    13                        SW
    14                        CLK
    15                                  AB1
    16     R3     R3
    17     R2     R2
    18     R1     R1
    19     R0     R0
N/A 20
N/A 21
    22                  SCL
    23                  SDA
N/A 24
    25                                  AB2
    26                                  AB3
    27                                  AB4
N/A 28
N/A 29
N/A 30
N/A 31
    32                                  AB5
    33          C3                      AB6
    34                                  AB7* BT
    35                                  AB8*
VP  36                                  AB9*
N/A 37
N/A 38
VN  39                                  AB10*

* Requires an external pullup resistor

Default Pins for the keypads

Click to expand to see a table of the Default Pins for the keypads
3x4 Keypad - Left to Right    4x4 keypad - Left to Right
 C1 PIN 0                      C0 PIN 4
 R0 PIN 19                     C1 PIN 0
 C0 PIN 4                      C2 PIN 2
 R3 PIN 16                     C3 PIN 33
 C2 PIN 2                      R0 PIN 19
 R2 PIN 17                     R1 PIN 18
 R1 PIN 18                     R2 PIN 17
                               R3 PIN 16

Cx = Column x      Rx = Row x

Different keypad manufacturers may arrange pins differently. Verify the keypad pin order before wiring it.

Loading the code

The project is built with PlatformIO, either through the PlatformIO extension in Visual Studio Code or the PlatformIO command line. The repository's platformio.ini file manages the ESP32 platform, framework, libraries, display options, and board-specific build flags.

  1. Install Visual Studio Code and the PlatformIO extension, or install PlatformIO Core.
  2. Open this repository as a folder.
  3. Choose a PlatformIO environment from platformio.ini:
  • lolin32_lite for the original LOLIN32 Lite build.
  • esp32s3_ili9341 for the GPIO-input ESP32-S3/TFT build.
  • feather_s3_ili9341 for the Feather S3/Qwiic/TFT build.
  1. Build and upload the selected environment. From a terminal:
pio run -e lolin32_lite
pio run -e lolin32_lite -t upload
pio device monitor -e lolin32_lite

Replace lolin32_lite with the environment you are using. The default serial monitor speed is 115200. 5. Configure keypad mappings and optional GPIO inputs in config_buttons.h. The checked-in file is an example of a local configuration; adjust it for your hardware before uploading. 6. On first boot, NextCab creates its LittleFS configuration storage. Wi-Fi credentials, server settings, and per-loco settings are saved there, so a separate config_network.h file is not required.

For the current Feather S3 assembly notes, see docs/HOW_TO_BUILD.md. To run the desktop unit tests without hardware:

pio test -e native_test
**

-->

Version Notes

Note: The version history below refers to the original WiTcontroller lineage. NextCab uses independent version numbers; its current development release is v0.2.0.

Click to expand
  • WiTcontroller version 1.83 includes a new way to add additional buttons. (see below) The old way will continue to work, but conversion to the new format is recommended.
    This change allows up to a total of 11 additional buttons, but some of the extra pins may be in use for the 4x4 keypad, the battery test, etc.
  • WiTcontroller version 1.82 or later requires WiThrottleProtocol version 1.1.26 or later.
  • WiTcontroller version 1.73 or later is recommended to use WiThrottleProtocol version 1.1.25 or later to address a bug in heartbeat send, but only seen with JMRI WiThrottle .
  • WiTcontroller version 1.66 or later requires WiThrottleProtocol version 1.1.24 or later.
  • WiTcontroller version 1.65 or later requires WiThrottleProtocol version 1.1.23 or later.
  • WiTcontroller version 1.57 or later requires WiThrottleProtocol version 1.1.20 or later for the E_STOP and E_STOP_CURRENT_LOCO to work correctly.
  • WiTcontroller version 1.45 or later requires WiThrottleProtocol version 1.1.14 or later.
  • WiTcontroller version 1.41 or later requires WiThrottleProtocol version 1.1.12 or later.
  • WiTcontroller version 1.39 or later requires WiThrottleProtocol version 1.1.11 or later.
  • WiTcontroller version 1.38 or later requires WiThrottleProtocol version 1.1.10 or later.
  • WiTcontroller version 1.35 or later requires WiThrottleProtocol version 1.1.7 or later.
  • WiTcontroller version 1.33 or later requires WiThrottleProtocol version 1.1.6 or later.
  • WiTcontroller version 1.19 - 1.32 requires WiThrottleProtocol version 1.1.3 - 1.1.5 or later.
  • WiTcontroller version 1.06 or later requires WiThrottleProtocol version 1.1.2 or later.
  • WiTcontroller version 1.01 or later requires WiThrottleProtocol version 1.1.0 or later.
  • WiTcontroller version 0.10 or later requires WiThrottleProtocol version 1.0.3 or later.
  • The WiFi.h and ESPmDNS.h libraries were automatically installed for me when I installed the esp32 boards, however it is possible that you may need to install them manually, but this is seriously not recommended.
  • Later versions of the ESP board support are available and do appear to work, but if you have difficulties version 2.0.11 and 1.0.6 appear to be stable.
  • Later versions of the libraries generally should work, but if you have difficulties use the versions listed above.
  • To get the WiTcontroller sketch I recommend using either the git command line, or the far more friendly 'GitHub Desktop' app. See instructions above.
  • If you receive and error related to Python, and you are on MacOS 12 and above please edit the platform file, change from python to python3 as follows; preferences->user/path/arduino/packages/hardware/esp32/version/platform.txt and edit the line that looks as follows:tools.gen_esp32part.cmd=python3 "{runtime.platform.path}/tools/gen_esp32part.py"


Using NextCab

Architecture Summary

The input layer was recently refactored to unify all physical controls behind a small interface (IInputDevice). Each device implements:

class IInputDevice {
  public:
    using DispatchFn = std::function<void(const InputEvent&)>;
    virtual void begin();   // hardware setup
    virtual void poll();    // called each loop to emit events
    virtual const char* name() const;
};

Registered devices (keypad, rotary encoder, throttle potentiometer, additional buttons) push InputEvent objects to a central InputManager which routes them to the current mode handler:

Flow: Hardware -> Device (poll) -> InputEvent -> InputManager -> Mode Handler (Operation / Password) -> System / Throttle logic.

Key additions:

  • AdditionalButtonsInput now generates canonical press ('P') / release ('R') events with internal debouncing & toggle logic.
  • Keypad events provide both press and release (enabling momentary function semantics) and integrate password entry mode.
  • Rotary encoder produces incremental speed (SpeedDelta) and an optional click (EncoderClick).
  • Potentiometer (optional) produces absolute speed (SpeedAbsolute).

Relevant event types (excerpt; see src/core/input/InputEvents.h):

  • SpeedDelta, SpeedAbsolute
  • EncoderClick, EncoderDoubleClick, EncoderHold, EncoderHoldRelease, EncoderLongPress
  • KeypadChar, KeypadSpecial, KeypadCharRelease, KeypadSpecialRelease
  • AdditionalButton (function or action; press/release encoded via cvalue = 'P'/'R')
  • Action (generic higher level actions / system controls)
  • PasswordCommit

Removed component: ThrottleInputManager (legacy intermediary) – rotary & pot now implement IInputDevice directly.

Benefits:

  • Consistent polling & dispatch path (single loop call: inputManager.pollAllDevices()).
  • Clear separation of hardware concerns vs. application logic.
  • Easier to add new input hardware (implement interface, register device).

For deeper contributor details see ARCHITECTURE.md (section: Producers & Input Events).

WiFi limitations

The ESP32 cannot use the 5gHz frequencies. It is limited to the 2.4gHz frequencies.

Using 2.4gHz Wi-Fi channels beyond 10 (11-13) may be problematic depending on the regional settings in the ESP32 Arduino platform. The current project does not use a config_network.h file; platform and board settings are managed through platformio.ini.

Definitions and Explanations

NextCab (like WiTcontroller before it) is deliberately described as a "controller" rather than a "throttle".

A "Throttle" can control only one train, which may be one loco, or more than one loco in consist/MU.

NextCab, as a "controller", contains up to six (6) "throttles". Each of which can control any number of locos in consist/MU. You can swap between throttles at will (keypad 5) to select which locos/consists/MUs you you are manipulating at a given time. While you can only manipulate one loco/consist/MU at at time, the others continue running at the setting you last gave it.

Features

Currently functioning: NextCab:

  • Provides a list of discovered SSIDs with the ability to choose one. When you select one:
    • If it is one in your specified list (in the sketch), it will use that specified password
    • If it is a DCC-EX EX-CommandStation in Access Point (AP) mode, it will guess the password
    • Otherwise it will ask to enter the password (Use the rotary encoder to choose each character and the encoder button to select it. * = backspace. # = enter the password.)
  • Optionally provides a list of SSIDs with the specified passwords (in the sketch) to choose from
  • Auto-connects to the first found WiThrottle Protocol Server if only one found, otherwise
    • Asks which to connect to
    • If none found will ask to enter the IP Address and Port
    • Guesses the WiThrottle IP address and Port for DCC-EX EX-CommandStations in Access Point (AP) mode
    • Optionally you can add a #define (a preference) to disable this auto connect feature
  • Allows On-the-fly consists/MUs Allows assigning commands directly to the 1-9 buttons (in the sketch) (see list below)
    • This is done in config_button.h
    • Latching / non-latching for the function is provided by the roster entry of the WiThrottle server
  • Optionally uses a potentiometer (pot) instead of the rotary encoder
  • Optional ability to assign commands directly to the 1-11 additional buttons (in the sketch) (see list below)
    • These are defined config_button.h
  • Provides a command Menu (see below for full list) including:
    • Able to select and deselect locos:
      • by their DCC address, via the keypad
        • On NCE systems, a leading zero (0) will force a long address
      • from the first 50 locos in the roster (can be increased)
    • Able to select multiple locos to create a consist
      • Able to change the facing of the additional locos in the consists/MUs (via the 'extra' menu after selection)
    • Able to activate any function (0-31)
      • Showing of the roster function labels (from the WiThrottle server if provided)
      • Quick access to the functions by pressing #. Temporarily enabled via the Extras menu (or permanently enabled in config_button.h)
      • Limited ability to configure which functions are sent to the first or all locos in a consist (defined in config_button.h)
    • Able to toggle turnouts/points between thrown and closed:
      • from the address
      • from the first 50 turnouts/points in the server list
      • tracks the current state of each turnout, including changes made by other throttles
    • Able to activate routes:
      • from their address
      • from the first 50 routes in the server list
    • Set/unset a multiplier for the rotary encoder
    • Power Track On/Off
    • Disconnect / Reconnect
    • Put ESP32 in deep sleep and restart it
    • Option to switch between Single Loco and Consist/MU (Drop before Acquire)
    • Option to save the currently select locos (on multiple throttles) and have them automatically re-acquired on next connection.
    • Option to disable the heartbeat check
  • Option to have up to 4 command sequences executed on connection
  • Option to automatically acquire a loco if there is only one loco in the roster
  • Have up to 6 throttles, each with an unlimited number of locos in consist.
    The default is 2 throttles, which can be increased or decreased temporarily via the Extras menu (or permanently enabled in config_button.h)
  • Limited dealing with unexpected disconnects. It will throw you back to the WiThrottle Server selection screen.
  • The boundary between short and long DCC addresses can be configured in config_buttons.h.
    The default is that 127 and below are Short Addresses.
  • The default speed step (per encoder click) can be configured in config_buttons.h
  • The controller will automatically shut down if no SSID is selected or entered in 4 minutes (to conserve the battery)
  • Relatively easy to add/use translation files.
  • Translations files for German and Italian included.
  • Stores Wi-Fi credentials, server settings, and per-loco settings in LittleFS.
  • Provides a loco configuration wizard for locomotive type, maximum speed, and sound-function assignments.
  • Applies a per-loco speed cap when locomotives are used in consists.
  • Uses software momentum levels (Off, Low, Medium, High), with physics-based acceleration and braking.
  • Safely queues a direction change while moving and brakes to a stop before applying it.

ToDo:

  • Speed button repeat (i.e. hold the button down)
  • Deal with unexpected disconnects better
    • automatic attempt to reconnect
  • Keep a list of IP addresses and ports if mDNS doesn't provide any

Command menu:

  • 0-9 keys = pressing these directly will do whatever has been configured in your config_buttons.h for them to do, or whatever is the default for that key (see # below)
  • * = Menu: The button press following the * is the actual command:
    • 1 = Add loco.
      • Followed by the loco number, followed by # to complete. e.g. to select loco 99 you would press '*199#'
      • or # alone to show the roster # again will show the next page
    • 2 = release loco:
      • Followed by the loco number, followed by # to release an individual loco. e.g. to deselect the loco 99 you would press '*299#'
      • Otherwise followed directly by # to release all e.g. '*2#'
    • 3 = Toggle direction.
    • 4 = Set / Unset a 2 times multiplier for the rotary encoder dial.
    • 5 = Toggle turnout/point.
      • Followed by the turnout/point number, followed by the # to complete. e.g. Toggle turnout XX12 '*512#' (where XX is a prefix defined in the sketch)
      • or # alone to show the list from the server # again will show the next page
      • The turnout is switched to the opposite of its last known state. If that state is not known, it is thrown.
      • Note the two different numbers. When you type a number directly you are entering the turnout's ID on the server (its system name, minus any prefix). The numbers shown down the left of the turnout list are simply the row positions on the current page, and they restart at 1 on every page. They are not turnout IDs and will only coincide by chance.
    • 7 = Set Route.
      • Followed by the Route number, followed by # to complete. e.g. to Set route XX:XX:0012 '*60012#' (where 'XX:XX:' is a prefix defined in the sketch)
      • or # alone to show the list from the server # again will show the next page
    • 0 = Function button. Followed by...
      • the function number, Followed by # to complete. e.g. to set function 17 you would press '*017#'
      • # alone, to show the list of functions.
    • 8 = Track Power On/Off.
    • 9 = Extras. Followed by...
      • 0 then # to toggle the action the the # key does as a direct action, either to show the direct action key definitions, or the Function labels.
      • 1 to change the facing of locos in a consist.
      • 3 to cycle momentum level.
      • 4 to configure device settings: heartbeat, throttle count, acquire mode, and remembered locos.
      • 5 to configure turnout and route prefixes for the connected server.
      • 6 to configure the current locomotive.
      • 7 to Disconnect/Reconnect.
      • 8 to put into deep sleep Pressing '*' again before the '#' will terminate the current command (but not start a new command)
  • # = Pressing # alone will show the function the the numbered keys (0-9) perform, outside the menu. Optionally, you can configure it so that the the Function labels from the roster show

Pressing the Encoder button while the ESP32 is in Deep Sleep will revive it.



Screens

WiFI/SSID Connection Screens

Start-up - browsing for SSIDs
Start-up - browsing for SSIDs

SSID (WiFi networks) list
SSID list

Trying to to connect to SSID
Trying to to connect to SSID

WiThrottle Server Connection Screens

WiThrottle Server List
WiThrottle Server List

Throttle Screen

Throttle Screen with no loco acquired
Throttle Screen with no loco acquired

Throttle Screen - with locos selected on throttle 1 and throttle 2
Throttle Screen

TT LLLL LLLL LLLL               BBBBB
TT ----------------------------------
f f f f f f f f f f f f f f f f f f f
              SSSSS  DDDD
              SSSSS  DDDD
              SSSSS            
M             SSSSS              llll
P  h                          d sss d
-------------------------------------
mmmmmmmmm
  • TT = Current Throttle Number
  • LLLL = Currently acquire loco(s) on this throttle
  • SSSS = Speed of current throttle (0-126)
  • DDDD = Direction of current throttle (Fwd,Rev)
  • f = State of each of the 32 functions (number displays if the function is active)

  • llll = First Loco on next available throttle
  • d = Direction of next next available throttle (<,>)
  • sss = Speed of next available throttle (0-126)

  • h = heartbeat (only displays if heartbeat is disabled)
  • M = Speed step multiplier
  • P = Track Power
  • BBBB = Optional battery state
  • mmmm = menu, other instructions or broadcast messages

* Menu

Menu

# Direct Commands List

Direct Commands List


Default number key assignments (0-9) (outside the menu)

  • 0 = FUNCTION_0 (DCC Lights)
  • 1 = FUNCTION_1 (DCC Bell)
  • 2 = FUNCTION_2 (DCC Horn/Whistle)
  • 3 = FUNCTION_3
  • 4 = FUNCTION_4
  • 5 = NEXT_THROTTLE
  • 6 = SPEED_MULTIPLIER
  • 7 = DIRECTION_REVERSE
  • 8 = E_STOP
  • 9 = DIRECTION_FORWARD

Allowed assignments for the 0-9 keys and/or Additional Buttons:

Note: you need to edit config_buttons.h to alter these assignments (copy config_buttons_example.h)

  • FUNCTION_NULL - don't do anything
  • FUNCTION_0 - FUNCTION_31
  • SPEED_STOP
  • SPEED_UP
  • SPEED_DOWN
  • SPEED_UP_FAST
  • SPEED_DOWN_FAST
  • SPEED_MULTIPLIER
  • E_STOP - E Stop all locos on all throttles
  • E_STOP_CURRENT_LOCO - E Stop locos on current throttle only
  • POWER_TOGGLE
  • POWER_ON
  • POWER_OFF
  • SHOW_HIDE_BATTERY
  • DIRECTION_TOGGLE
  • DIRECTION_FORWARD
  • DIRECTION_REVERSE
  • NEXT_THROTTLE
  • SPEED_STOP_THEN_TOGGLE_DIRECTION - stops the loco if moving. Toggles the direction if stationary.
  • MAX_THROTTLE_INCREASE - change the number of available throttles on-the-fly
  • MAX_THROTTLE_DECREASE - change the number of available throttles on-the-fly
  • POWER_TOGGLE - track power toggle
  • POWER_ON - track power on
  • POWER_OFF - track power off
  • THROTTLE_1 - change to a specific throttle
  • THROTTLE_2 - change to a specific throttle
  • THROTTLE_3 - change to a specific throttle
  • THROTTLE_4 - change to a specific throttle
  • THROTTLE_5 - change to a specific throttle
  • THROTTLE_6 - change to a specific throttle
  • SLEEP - put device to sleep
  • CUSTOM_1 - There must be already be a command defined as #CUSTOM_COMMAND_1 ...
  • CUSTOM_2 - There must be already be a command defined as #CUSTOM_COMMAND_2 ...
  • CUSTOM_3 - There must be already be a command defined as #CUSTOM_COMMAND_3 ...
  • CUSTOM_4 - There must be already be a command defined as #CUSTOM_COMMAND_4 ...
  • CUSTOM_5 - There must be already be a command defined as #CUSTOM_COMMAND_5 ...
  • CUSTOM_6 - There must be already be a command defined as #CUSTOM_COMMAND_6 ...
  • CUSTOM_7 - There must be already be a command defined as #CUSTOM_COMMAND_7 ...


Options and Extras

Rotary Encoder 'bounce'

If you find that moving the encoder a single click sends more than one speed command, try adjusting this define by uncommenting (removing the //) following line in your config_buttons.h and changing the value. (Usually you need to increase the value.)

#define ROTARY_ENCODER_STEPS 2

Depending on your encoder - try 1,2,3 or 4 till you get expected behaviour. The default is 2


Direction of rotation of the encoder

The default configuration is to rotate the encoder counter-clockwise to increase speed. This made sense for the original simple case, but is not for many of the new cases that people have designed.

To change the configuration so that rotating the encoder clockwise increases speed, uncomment (remove the //) following line in your config_buttons.h and change the value to true.

#define ENCODER_ROTATION_CLOCKWISE_IS_INCREASE_SPEED true


Optional Additional Buttons

The way to add additional buttons changed in version 1.83. The old way will continue to work but only the new way is described here.

To use the new format:

  • USE_NEW_ADDITIONAL_BUTTONS_FORMAT must be set to true
  • NEW_MAX_ADDITIONAL_BUTTONS must be set to the number of buttons you want to use. This must be 1 or more. never 0.

Then the following lists MUST have the same number of elements as NEW_MAX_ADDITIONAL_BUTTONS.

  • NEW_ADDITIONAL_BUTTON_ACTIONS
  • NEW_ADDITIONAL_BUTTON_LATCHING
  • NEW_ADDITIONAL_BUTTON_PIN
  • NEW_ADDITIONAL_BUTTON_TYPE

Note

It is theoretically possible to add up to 11 additional buttons directly to the ESP32.
Pins 5,15,25,26,27,32,33 are the 7 normally used for the buttons.
However one of these becomes unavailable if you have an optional 4x4 keypad.
Also one of these becomes unavailable if you use the battery test.
Pins 34,35,36,39 can also be used, but:
a) they don't have an internal pullup, so need and external resister, and they need to configured (in config_buttons.h) as INPUT rather than INPUT_PULLUP
b) one of these pins may used by the optional 4x4 keypad
c) one of these pins may used by the optional battery monitor

For NEW_ADDITIONAL_BUTTON_ACTIONS

This array lists the functions or actions assigned to the buttons. See the list of 'Allowed assignments' above (or in actions.h).

This has the general form NEW_ADDITIONAL_BUTTON_ACTIONS{val0, val1, .. val10, up-to-val11}

For NEW_ADDITIONAL_BUTTON_LATCHING

This array lists if the functions assigned to the buttons should be latching or not. This is only relevant if the assigned function is for FUNCTION_0 to FUNCTION_31.

This has the general form NEW_ADDITIONAL_BUTTON_LATCHING{val0, val1, .. val10, up-to-val11}

For NEW_ADDITIONAL_BUTTON_PIN

This array lists the pins that the buttons will be attached to.

  • For pins on the ESP32 use the number shown on the board/diagram
  • If you are using pins on a I2C GPIO Expansion board, they will be numbered: 0-15
  • If don't want to use pin, but retain the entry set it to -1.

This has the general form NEW_ADDITIONAL_BUTTON_PIN{val0, val1, .. val10, up-to-val11}

For NEW_ADDITIONAL_BUTTON_TYPE

This array lists the type of the pin.

  • Set to either INPUT_PULLUP or INPUT. If INPUT, the pin will need an external pullup resister (e.g. 10k)
  • Pins 34,35,36,39 can be used but don't have an internal pullup, so use INPUT for these

This has the general form NEW_ADDITIONAL_BUTTON_TYPE{val0, val1, .. val10, up-to-val11}

See additional information in config_buttons_example.h.


Optional use of a EC11 rotary encoder (with no physical resistor pullups) in place of the KY040 encoder module

Internal GPIO pullups required if the hardware build utilises a bare EC11 rotary encoder in place of a KY040 encoder module. (The encoder module has physical pullups fitted)

#define EC11_PULLUPS_REQUIRED true

If the EC11_PULLUPS_REQUIRED is set to false a KY040 module used in hardware build OR bare EC11 used but with physical pullup resistors

If the EC11_PULLUPS_REQUIRED is set to true an EC11 used for hardware build WITHOUT any physical pullups, GPIO pullups will ne enabled in main

The default is false.


Optional use of different OLED displays

WitController will support any OLED display with a resolution of 128x64 that is supported by the U8g2 library. This include displays up to 2.4 inch.

The complete list is available here: https://github.com/olikraus/u8g2/wiki/u8g2setupcpp

The OLED_TYPE define will need to be updated to whatever display you have

This is one of the common .9 inch OLED displays (and also for a common 2.4 inch) and is included by default

#define OLED_TYPE U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE, /* clock=*/ 22, /* data=*/ 23);

This is one of the common 1.3 inch OLED displays

#define OLED_TYPE U8G2_SH1106_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE, /* clock=*/ 22, /* data=*/ 23);

See config_buttons_example.h for more information.


Optional use of a potentiometer (pot) instead of the encoder for the throttle

config_buttons.h can include the following optional defines:

  • #define USE_ROTARY_ENCODER_FOR_THROTTLE false
  • #define THROTTLE_POT_PIN 39
  • #define THROTTLE_POT_USE_NOTCHES true #define THROTTLE_POT_NOTCH_VALUES {1,585,1170,1755,2340,2925,3510,4094}
  • #define THROTTLE_POT_NOTCH_SPEEDS {0,18,36,54,72,90,108,127}

If USE_ROTARY_ENCODER_FOR_THROTTLE is set to false the rotary encoder is ignored and a pot on the pin defined with THROTTLE_POT_PIN will be used instead.

You must specify the PIN to be used. Currently PINs 34, 35 and 39 are the only ones that cannot be used by the app for other purposes, so these are the safest to use. This should be connected to the centre pin of the pot. The 3v and GND should be connected to the outer pins of the pot.

The pot can be set to have 8 defined 'notches' (the default) or just a linear value.

If you want to have the 8 notches:

a) You must define the values the pot will send at each of 8 points - THROTTLE_POT_NOTCH_VALUES. Note that you should avoid the value zero (0) for notch zero. Use at least 1 instead.

The example values above are useble for a 10k ohm pot but any value pot can be used. Just adjust the numbers.

b) You must define what speed should be sent for each notch - THROTTLE_POT_NOTCH_SPEEDS

If you want a linear speed instead of notches:

a) You must define the values the pot will send at at zero throw and full throw in the first and last of the 8 values in THROTTLE_POT_NOTCH_VALUES. The other values will be ignored but you still need to include 8 values. (They can be zero.) Note that you should avoid the value zero (0) for notch zero. Use at least 1 instead.

Sumner Patterson has developed an app to help find the appropriate pot values for the THROTTLE_POT_NOTCH_VALUES.


Optional use of a voltage divider to show the battery charge level

See diagram above for how to wire in the volage divider. 47k resistors are used, but this can be varied as long as the output to the pin is limited to below 3.3v. See additional information related to the Pangodream Library that WiTcontroller uses here... https://www.pangodream.es/tag/18650-ion-li

The diagram has the voltage divider spliced into battery leads, but you can solder the positive in to the back of the battery connector (on the ESP32) if you prefer. The ground can be taken from any ground pin.

To enable the battery monitor, set the following to true. The default is false.

#define USE_BATTERY_TEST true

To set which pin to use. The default is 34. In theory 34, 35, 36, or 39 should be able to be used, but only 34 and 36 have been tested. (36 is marked 'VP' on the board. 39 is marked 'VN' on the board.)

#define BATTERY_TEST_PIN 34

If the battery does not show 100% when plugged into the charger, you may need to adjust this value. The default is 1.7.

#define BATTERY_CONVERSION_FACTOR 1.7

To help work out the correct BATTERY_CONVERSION_FACTOR, 
you can enable so serial monitor message that will assist.

In your ``config_buttons.h`` add (or uncomment -remove the ``//``) these defines:

  #define WITCONTROLLER_DEBUG    0

a) Make sure your battery is fully charged.
b) Upload the code and open the serial monitor. 
c) Wait. Don't connect.
You will see lines like...

  BATTERY TestValue: 100 (10003)
  BATTERY lastAnalogReadValue: 2491 (10003)
  BATTERY If Battery full, BATTERY_CONVERSION_FACTOR should be: 1.69 (10014)

Let it run for a while.
d) Note one of the recommended values (it will vary a bit) and enter 
   it into the define in your config_buttons.h
e) Re-upload code and connect to a server
f) Confirm that the battery reads 100% (repeat if not)
g) Run the WiTcontroller on battery for few hours and confirm the 
   battery level is droping at an expected rate. 
   (adjust the conversion factor if not.)

To show the calculated percentage, set the following to true The default is false.

#define USE_BATTERY_PERCENT_AS_WELL_AS_ICON true

To force the WiThrottle to go to sleep at a specific level, set this value. (e.g. to 3 or 5.) A value of less than zero (e.g. -1) will disable the feature. By default it is disabled (-1).

#define USE_BATTERY_SLEEP_AT_PERCENT 3

The display of the battery can be temporarily toggled by setting a key or button to SHOW_HIDE_BATTERY. The display will cycle between none, icon only and icon plus percent value. Note that USE_BATTERY_TEST must be set to true for this to have any effect.

Note: I recommend adding a physical power switch to disconnect the battery as this feature will, slowly, continually drain the battery, even when not being used.


YaMoRC Command Stations / Heartbeat Period

#define DEFAULT_HEARTBEAT_PERIOD 10

The heartbeat period defaults to 10 seconds. The server should send the period that it is expecting on connection and overide the default, so generally you should never need to adjust this.

It is believed that the YaMoRC Command Stations are not sending this as the should (according to the WiThrottle specification). For those, and any other WiThrottle that do not send it, uncomment or add the define above and adjust the number of seconds to match the setting on the server.


Startup Commands

#define STARTUP_COMMAND_1 "" .. #define STARTUP_COMMAND_4 ""

Optional. If defined, up to four commands will be executed, in order, after connection to the WiThrottle Server. Each must be ONLY ONE single valid command. Either a direct action or a menu action. Any can be blank or not defined, the others will still be executed.

Note: Selecting from the roster/turnouts etc. is not possible as the commands will execute before the roster loads.


Optionally automatically acquire the only loco in the roster

#define ACQUIRE_ROSTER_ENTRY_IF_ONLY_ONE true

Enabling this option will automatically acquire the only roster entry after connection to the WiThrottle Server, but only if there is only one roster entry.


Instructions for German Translations Anleitung für deutsche Übersetzungen

The file language_deutsch.h contains German translations (by Bastian Zechendorf). Uncomment (or add) the #include "language_deutsch.h" line in config_buttons.h to see menus and messages in German.

You can override any of these translations individually by adding an appropriate #define statement in the config_buttons.h, but it must be before the #include... statement.

Die Datei language_deutsch.h enthält deutsche Übersetzungen (von Bastian Zechendorf). Kommentieren Sie die Zeile #include "language_deutsch.h" in config_buttons.h aus (oder fügen Sie sie hinzu), um Menüs und Meldungen auf Deutsch anzuzeigen.

Sie können jede dieser Übersetzungen einzeln überschreiben, indem Sie eine entsprechende #define -Anweisung in config_buttons.h hinzufügen, diese muss jedoch vor der #include... -anweisung stehen.

Ich würde mich über jede Hilfe zur Verbesserung dieser Übersetzungen freuen. Bei Interesse kontaktieren Sie mich bitte direkt für eine Anleitung.


Istruzioni per la traduzione in Italiano

Il file language_italiano.h contiene la traduzione italiana (by Roberto B). Rimuovere il commento o aggiungere la linea "#include language_italiano.h" (senza le " ") nel file "config_buttons.h" per avere menù e messaggi in Italiano.

Si può sovrascrivere una qualunque di queste traduzioni individualmente aggiungendo la relativa "#define" nel file "config_buttons.h" ma deve essere fatto assolutamente prima dell' "#include" della traduzione.

Qualunque contributo è gradito. Per ogni questione, il contatto ufficiale è il canale Discord di DCC-EX nella sezione wifi-throttles.


Instructions for Other Translations

If you create a copy of the file language_deutsch.h (with a new name) and change the German text it contains to any language you wish (using the English text on the right as a guide to what is needed) you can add that file as an include in config_buttons.h.

I would welcome it if you then sent me the file you created for inclusion in this repository.

I would appreciate any assistance to improve or add to the translations. If you are interested, please contact me directly for instructions.



Modifying the code

Refactored modular core (October 2025)

The project has begun an incremental refactor to separate newly introduced classes from the original monolithic sketch. All new code that encapsulates specific responsibilities now lives under src/core:

  • Renderer.* – centralised all display rendering logic (menus, speed screen, lists, heartbeat).
  • UIState.* – holds transient UI line buffers and flags (replaces many former global arrays / booleans with a struct).
  • ThrottleManager.* – consolidates speed, direction and multi‑throttle selection logic.
  • BatteryMonitor.* – encapsulates battery percentage / icon handling and low‑battery checks.
  • ConfigStore.* – persists Wi-Fi credentials, server settings, and per-loco settings as JSON in LittleFS.
  • RenderModel.h & HeartbeatPresenter.h – first presenter example (MVP style) building a model for the heartbeat check screen.

Legacy global variables are still defined in WiTcontroller.ino, but many are now accessed via lightweight macros in WiTcontroller.h that map to the UIState instance (e.g. oledText, menuIsShowing). This keeps the public API stable for existing code while enabling progressive isolation and testability.

Transitional stubs

Previously duplicated root *.cpp files (e.g. ThrottleManager.cpp at the project root) have been removed; authoritative implementations exist only in src/core. If you still have local untracked stub files, you can delete them safely once you confirm builds are clean.

Why this structure?

  • Avoids touching the original large sketch all at once (lower merge / regression risk).
  • Eases future unit testing by giving coherent, single‑purpose classes.
  • Provides a pattern (Presenter + RenderModel) for future screens reducing direct OLED side‑effects sprinkled across the sketch.

Next refactor steps (not yet done)

  • Migrate remaining rendering helpers into Renderer or additional presenters.
  • Reduce macro bridging in WiTcontroller.h by moving more state directly into UIState.
  • Make renderAllLocos private again once external legacy calls are redirected.
  • Add unit tests (where practical) for ThrottleManager and BatteryMonitor behavior.

If you contribute new modules following this pattern, prefer placing them in src/core (or a subfolder) and include them from the sketch using a relative path (#include "src/core/YourClass.h").

Additional Buttons State Machine (October 2025)

Optional hardware buttons (defined via NEW_ADDITIONAL_BUTTON_* arrays in config_buttons.h) now use a per-button state machine that produces clean, canonical events. This replaces the older debounce + pulseEmitted + implicit latching logic.

Highlights:

  • Deterministic states (Idle → PressNoise → Active → ReleaseNoise → Idle) ensure one logical press per physical press.
  • 40ms debounce window filters chatter while still permitting quick taps.
  • Ultra-short taps (≥5ms) are accepted as valid fast toggles (useful for horn / bell bursts) even if shorter than the full debounce window.
  • Emergency actions (E_STOP, E_STOP_CURRENT_LOCO) bypass the debounce and emit immediately on the raw edge for maximum responsiveness.
  • Non-function actions (speed, power, direction, etc.) emit only a single press Action event; releases are suppressed to simplify downstream handling.
  • Function buttons (FUNCTION_0FUNCTION_31) emit press and, for momentary functions, release events (AdditionalButton type with cvalue = 'P'|'R'). Toggle/latching functions emit press only.

Benefits:

  • Eliminates duplicate or missed events seen with the prior mixed approach.
  • Creates a "pure" event stream so consumers (InputManager, renderers, protocol layer) need no corrective heuristics.
  • Guarantees immediate emergency stop regardless of other button activity.

Configuration:

  • No new defines required; state machine is always active.
  • Existing arrays (NEW_ADDITIONAL_BUTTON_ACTIONS, pins, types, latching flags) remain unchanged.
  • Fast toggle minimum (5ms) is internal; adjust only if you fork the class.

Invariant for contributors: Do not introduce release events for non-function actions—downstream code assumes press-only semantics.

If you plan to modify the code to make you own version, it is recommended that you create your own GitHub fork of my repository and post your mods there.

Instructions on how to do so are here.


Change Log

See full change log/ version history


About

A software-first fork of WiTcontroller with extended control behaviors and experimental features.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages