FXRoute can optionally talk to a small RP2040/ESP32-class MCU over USB CDC serial. The MCU remains the autonomous owner of the physical amplifier/input-selector logic. FXRoute only reads the current state and can send override/control commands.
Current target use case: an MCU controlling an Aiyima A70 input selector.
- Completely optional: FXRoute must run normally when no MCU is connected.
- No startup blocking: serial detection happens lazily through the hardware status API/UI polling.
- Safe serial behavior: explicit opt-in device path, short read/write timeouts, reconnect by retrying that device, and throttled logs.
- Small protocol surface: line-based text protocol, version 1.
- Cached state: the last valid MCU status line is kept and returned where useful.
hardware_controller.py- Implements
HardwareController. - Uses
pyserial. - Opens only the explicit
HARDWARE_CONTROLLER_DEVICEpath when configured. - Detects a compatible MCU with
PING→PONG. - Parses semicolon-separated status lines.
- Serial errors close the connection and are returned as status notes instead of crashing FXRoute.
- Reconnect retries are throttled to avoid tight loops.
- Repeated log messages are throttled.
- Implements
requirements.txt- Adds
pyserial==3.5.
- Adds
config.py- Adds optional
HARDWARE_CONTROLLER_DEVICE.
- Adds optional
main.py- Creates a global optional
HardwareControllerinstance during app startup. - Closes it on shutdown.
- Adds
/api/hardware/...routes. - Runs serial work via
asyncio.to_thread(...)so FastAPI is not blocked by serial I/O.
- Creates a global optional
static/index.htmlandstatic/app.js- Adds a compact
Amplifier Controllercard in Technical settings. - Polls status while Technical settings is open.
- Disables controls when no controller is connected.
- Adds a compact
By default FXRoute does not open any serial device. This avoids touching unrelated USB serial hardware.
To enable hardware control, set an explicit serial device in .env:
HARDWARE_CONTROLLER_DEVICE=/dev/ttyACM0If unset or wrong, FXRoute continues to run. When unset, the UI shows that the controller is disabled until HARDWARE_CONTROLLER_DEVICE is configured.
All messages are line-based and newline-terminated.
PING
GET
SET INPUT RCA
SET INPUT XLR
PRESS INPUT
AUTO ON
AUTO OFF
PONG
OK
ERR UNKNOWN_CMD
POWER=1;TRIGGER=1;INPUT=RCA;RCA=1;XLR=0;AUTO=1
Expected behavior:
PINGshould returnPONG.GETshould return a status line.- Control commands may return
OK; FXRoute then asksGETto refresh state. ERR ...is treated as command failure but must not crash FXRoute.
FXRoute currently recognizes these keys from the status line:
POWER→ booleanTRIGGER→ booleanINPUT→ string, usuallyRCAorXLRRCA→ booleanXLR→ booleanAUTO→ boolean
0 and 1 are converted to booleans. Other values remain strings.
Example parsed payload from /api/hardware/status:
{
"available": true,
"connected": true,
"device": "/dev/ttyACM0",
"status": {
"POWER": true,
"TRIGGER": true,
"INPUT": "RCA",
"RCA": true,
"XLR": false,
"AUTO": true
},
"raw": "POWER=1;TRIGGER=1;INPUT=RCA;RCA=1;XLR=0;AUTO=1",
"power": true,
"trigger": true,
"input": "RCA",
"rca": true,
"xlr": false,
"auto": true,
"notes": []
}GET /api/hardware/status- Scans/reconnects if needed.
- Sends
GETwhen connected. - Returns current/cached status and notes.
POST /api/hardware/input/rca- Sends
SET INPUT RCA, thenGET.
- Sends
POST /api/hardware/input/xlr- Sends
SET INPUT XLR, thenGET.
- Sends
POST /api/hardware/input/press- Sends
PRESS INPUT, thenGET.
- Sends
POST /api/hardware/auto/on- Sends
AUTO ON, thenGET.
- Sends
POST /api/hardware/auto/off- Sends
AUTO OFF, thenGET.
- Sends
The card lives in:
FXRoute logo → Technical settings → Amplifier Controller
It shows:
- connection state and serial device path
- current input
- trigger state
- power state
- auto mode
Buttons:
RCAXLRPress InputAuto OnAuto Off
Buttons are disabled when connected=false or while a command is pending.
The implementation was checked without real MCU hardware:
python3 -m py_compile main.py config.py hardware_controller.py measurement.py easyeffects.py
node --check static/app.js
git diff --checkA fake-serial smoke test verified:
PING→PONGdetectionGETstatus parsingSET INPUT XLRcommand path- no crash on the simulated serial path
With a real RP2040/ESP32 attached:
-
Install/update dependencies so
pyserialexists in the FXRoute venv. -
Start/restart FXRoute.
-
Check:
curl http://localhost:8000/api/hardware/status
-
Confirm that
device,connected, and parsed status fields are correct. -
Press the UI buttons and verify the MCU receives exactly:
SET INPUT RCA SET INPUT XLR PRESS INPUT AUTO ON AUTO OFF -
Unplug/replug the MCU and confirm reconnect works without restarting FXRoute.
- Add an explicit protocol/version field if the MCU firmware grows.
- Add a small MCU firmware example once pinout/logic is final.
- Consider hiding the card until a controller is detected if the settings page feels too busy.
- Add a UI timestamp for last valid status once real hardware behavior is known.