Share My Creation HubController – ESP32 Command Hub

HubController – ESP32 Command Hub for Embedded Devices

HubController
is a lightweight command-based firmware, written in B4R, for the ESP32 designed to control motors, signals, sensors, and LEDs in embedded systems such as model railways, automation hubs, and robotics.

It acts as a central hub: clients send small binary commands over USB serial or BLE, and HubController executes them on connected hardware.
The design focuses on predictable behavior, clean modular architecture, and low-latency command execution resulting in a simple and reliable embedded control node that can be controlled from different platforms.



Overview with Demo Application

1774267822770.png




HubController Key Features
  • deterministic command execution
  • extremely small binary protocol
  • transport-agnostic design
  • modular device and peripheral architecture
  • cooperative event loop (no blocking)
  • designed for embedded reliability


HubController Binary Protocol (HubBin)
The binary frame is intentionally tiny as 5-bytes only:

B4X:
[HDR][ADR][CMD][VAL][FTR]
 1B   1B   1B   1B   1B

Example Run motor with address (10), command set speed (5) and speed (70).
B4X:
[0x19] [10] [5] [70] [0x58]
Each device & peripheral has an unique address.



Client applications currently exist in:
  • B4J - RailYardGUI (B4XPages, HMITiles, ASyncCanvas), RailYardCommander (B4XPages, HMITiles), RailYardDCCBridge (Console)
  • B4A- RailYardCommander (B4XPages, HMITiles)
  • Python - RailYardGUI ((PySide6), RailYardDCCBridge (Console)


Demo Project – RailYard
The current demonstration is RailYard, a LEGO® train and layout control system for schools and fairs:
  • Controls 1 loco via BLE - Lego City Hub or BuWizz2. Planned Power Functions Motor
  • Switches and detectors controlled via USB serial
  • Supports simple automated schedules
  • Designed to run without Wi-Fi, making it portable and flexible
Short Demo Video



Source Code
Source code may be published later when the project structure stabilizes.



Next Steps
  • Create a custom BLE device: Loco with Power Functions Motor
  • Create HarbourYard with crane driven by GeekServo motors


Personal Note on Development
Creating HubController has been a learning-rich journey, tackling challenges such as:
  • Designing a strong modular setup with many classes and code modules
  • Balancing USB serial vs BLE/Wi-Fi client control
  • Handling quirks of ESP32 NimBLE (created rNimBLEClient & rNimBLEServer) vs Arduino BLE libraries for stability
These challenges have shaped a robust system that is flexible enough for demos, teaching, and further experimentation.

Disclaimer
LEGO® is a trademark of the LEGO Group of companies which does not sponsor or endorse this project.
 
Last edited:

rwblinn

Well-Known Member
Licensed User
Longtime User
Good approach for modular design and versatile features... eager to see it soon in action. 👍
Thanks for feedback = Appreciated.

Look at the short demo video (post #1) to see the HubController in action using the B4J Client RailYard.
The loco, named Lokky, follows an automated schedule. The track end points are reed switches, the track turnout and the 2 signal-masts are set accordingly.
Schedules are defined in external file with JSON format.
More information about the RailYard Application:

RailYard is a standalone LEGO® train and layout control system built for learning purposes and demonstrations.
It uses the ESP32-based HubController communicating over a hardware serial interface for track-side device control (signals, switches, sensors) and BLE for Loco control with BLE Bricks. It is a hobby project to demonstrate:
  • How to develop a microcontroller (ESP32) to control a LEGO simple railroad layout with 1 loco (BuWizz2 BLE Motor), 1 switch (TrixBrix motor), 3 detectors (REED).
  • How to control the loco (via BLE) and the peripherals via client applications (B4A, B4J, Python PySide6) using Windows 11, Ubuntu & Raspberry Pi OS Trixie.
  • How to communicate using the serial line and BLE (for Loco only).
  • How to create simple automated schedules (this has been the ultimate goal and the real fun part).


Hardware
  • HubController: ESP32-WROOM-32
  • IO: MCP23017 Port Expander, PCA9685 PWM Servo driver, 3x REED switches
  • TrixBrix: 2x Signal Masts (2-aspect), 1x Servo Switch, 2x Boom Barrier LEDs
  • Bricks:
    • LEGO® PoweredUp 88009 Hub to power and control PoweredUp sensors and motors.
    • BuWizz 2.0 "Ludicrous": a remote control & battery in one brick, compatible with all LEGO® Power Functions motors and lights.
    • Custom BLE-based ESP32 bricks (planned).
Software
  • B4R: 4.00 (64 bit)
  • B4R Additional external libraries: Adafruit MCP23017 Arduino 2.3.2 (B4R Lib rAdafruitMCP23017_I2C), Adafruit PWM Servo Driver PCA9685 3.0.3 (B4R Lib rAdafruitPWMServoDriver), NimBLE (B4R Lib: rNimBLEClient), rConvert
  • B4J: 10.50 (64 bit), B4A: 13.4 (64 bit), Python: 3.10.11, Qt: 6.10.2, PySide6: 6.10.2
Wiring
B4X:
IO                            ID        Addr    ORANGE    RED        BROWN    Notes
----------------------------------------------------------------------------------------------------------------------------------------------
SIGNAL MAST
Extension cable                                WHITE    RED        BLACK
                                            Bottom            Top
Signal Mast North             SMN        20        PB1        3V3        PB0        MCP23017, False ON
Signal Mast South            SMN        21        PB3        3V3        PB2        MCP23017, False ON

TRAIN DETECTOR (2 options REED or TCRT5000)
Extension cable                                WHITE    RED        BLACK
REED SWITCH (INPUT_PULLUP)
Train Detector West            TDW        40        PA0        n/a        GND        MCP23017
Train Detector East            TDE        41        PA1        n/a        GND        MCP23017
Train Detector North        TDN        42        PA2        n/a        GND        MCP23017
TrixBrix TCRT5000 (INPUT)
Train Detector West            TDW        40        PA0        3V3        GND        MCP23017
Train Detector East            TDE        41        PA1        3V3        GND        MCP23017
Train Detector North        TDN        42        PA2        3V3        GND        MCP23017

BOOM BARRIER LED
Extension cable                                WHITE    RED        BLACK
                                            LEFT            RIGHT
BoomBarrier LED MCU            BLM        30        PB7        3V3        PB6        MCP23017 PB7 = Left LED (False ON); PB6 = Right LED (False ON)
BoomBarrier Tower            BLM        31        PB5        3V3        PB4        MCP23017

TRACK SWITCH
Extension cable                                ORANGE    RED        BROWN
Track Switch South            SWS        50        0        VCC        GND        PCA9685 Channel 0

Note:
Each peripheral has extension cable (white,red,black) which is wired accordingly.

## MCP23017 Overview
PA0 = Trans Detector West
PA1 = Trans Detector East
PA2 = Trans Detector North
PA3 =
PA4 =
PA5 =
PA6 =
PA7 =
PB0 = Signal Mast North Top LED
PB1 = Signal Mast North Bottom LED
PB2 = Signal Mast South Top LED
PB3 = Signal Mast South Bottom LED
PB4 =
PB5 =
PB6 = BoomBarrier LED Left MCU
PB7 = BoomBarrier LED Right MCU

## PWM Servo Driver
PCA9685 = MCU
GND    = GND (Black)
OE    = n/a
SCL    = IO22 (Blue)
SDA    = IO21 (Green)
VCC    = 3V3 (Red)
V+    = 5V 3A (recommend 1A per servo switch)
I2C Address = 0x40

External Power = PCA9685
V+ = VCC (orange)
GND = GND (black)

## PCA9685 Overview
Channel 0 = Track Switch South
Channel 1 =
Channel 2 =
Channel 3 =
Channel 4 =
Channel 5 =
Channel 6 =
Channel 7 =
Channel 8 =
Channel 9 =
Channel 10 =
Channel 11 =
Channel 12 =
Channel 13 =
Channel 14 =
Channel 15 =

## Multi IO Expander
MCP23017 = MCU
VCC = 3V3 (Red)
GND = GND (Black)
SCL    = IO22 (Blue)
SDA    = IO21 (Green)
I2C Address = 0x27

## Multiple I2C
Connect multiple I2C devices.
Each device must have unique I2C address. Check out with an I2C Scanner.

AdafruitMCP23017 = 0x27
AdafruitPWMServoDriver PCA9685 = 0x40

Power Setup
ESP32 (bare module or development board with 3.3V regulator). Powered by an external 3.3V supply.
PCA9685: Powered by external 5V supply with 3A (servo switch requires 1A).



Command Data Format
Commands are sent from clients via the serial line to the HubController using the HubBin frame format (see post #1).
Examples
B4X:
signal_south_off: HDR, SIGNALMAST1_ADDRESS, SIGNAL_CMD_ASPECT, SIGNAL_ASPECT_OFF, FTR
signal_south_go: HDR, SIGNALMAST1_ADDRESS, SIGNAL_CMD_ASPECT, SIGNAL_ASPECT_GO, FTR

HubBin Frame 5-Bytes
HDR = 0x19
SIGNALMAST1_ADDRESS = 20
SIGNAL_CMD_ASPECT = 0
SIGNAL_ASPECT_OFF = 0
SIGNAL_ASPECT_GO = 2
FTR = 0x58

Modules
The application has a modular setup used by the HubController firmware and client applications.

ModulePurposeExamples B4R,B4A,B4J (.bas), Python (.py)
Device AddressesStatic device and peripheral addressesHubAddresses.bas
addresses.py
ModelSystem model definitions (types, commands, states)HubModel.bas
model.py
ProtocolDefinition of the communication frame + helpers at the protocol level (HubBin)HubProtocol.bas
protocol.py
CommandsHigh-level named commands built on hub_model constants
CmdMap.bas
commands.py



Clients

Note that any client which can send binary data over a serial line can be used.

B4J
- B4J/RailYardGUI: Control the yard using GUI with a graphical track layout, loco controller, scheduler

- RailYardHMI: For development - HMI Tiles based GUI to test the yard
- RailYardScheduler: For development - run defined schedules

- RailYardBlockly: Experimental - Control using Blockly to send commands
- RailYardDCCBridge: Experimental - Bridge between DCC and HubController (tested with with RocRail)
- SerialTest: Tool - Test sending commands over the serial line

B4A
- RailYardCommander: For development - control the yard using direct commands

Python
- RailYardGUI: Control the yard using GUI with a graphical track layout, loco controller, scheduler
- RailYardDCCBridge: Experimental - Bridge between DCC and HubController (tested with RocRail)
- SerialTest: Tool - Test sending commands over the serial line

RocRail (Experimental)
- RocView (and RocRail server) to manually control or via predefined schedules



Schedule Example (JSON Format)
Lokky the loco starts at south track west position, runs to the south track east position and then back with low speed 25.

JSON:
{
  "route": "WEST_to_EAST_vv",
  "steps": [
    {"type": "COMMAND", "value": "signal_south_stop"},
    {"type": "WAIT_TIME", "value": 2},
    {"type": "COMMAND", "value": "signal_south_go"},
    {"type": "COMMAND", "value": "switch_south_straight"},
    {"type": "COMMAND", "value": "motor_set_direction_bck"},
    {"type": "COMMAND", "value": "motor_set_speed_25"},
    {"type": "WAIT_TIME", "value": 2},
    {"type": "WAIT_DETECTOR", "value": "DETECTOR2"},
    {"type": "COMMAND", "value": "motor_stop"},
    {"type": "WAIT_TIME", "value": 2},
    {"type": "COMMAND", "value": "motor_set_direction_fwd"},
    {"type": "COMMAND", "value": "motor_set_speed_25"},
    {"type": "WAIT_TIME", "value": 1},
    {"type": "WAIT_DETECTOR", "value": "DETECTOR1"},
    {"type": "WAIT_TIME", "value": 1},
    {"type": "COMMAND", "value": "motor_stop"},
    {"type": "COMMAND", "value": "signal_south_stop"}
    ]
   },
 
Top