Skip to content

Add SplitFlap Gateway as a target for the app#50

Merged
csader merged 6 commits into
csader:mainfrom
avandeputte:main
Jun 24, 2026
Merged

Add SplitFlap Gateway as a target for the app#50
csader merged 6 commits into
csader:mainfrom
avandeputte:main

Conversation

@avandeputte

Copy link
Copy Markdown
Contributor

SplitFlap Gateway (MQTT) Integration — Change Summary

This adds an option to drive the display through a SplitFlap Gateway
(Waveshare ESP32-S3-RS485-CAN running SplitFlap Gateway firmware at https://github.com/avandeputte/SplitFlapGateway ) over MQTT, as an alternative to a
local serial port. The design goal was to minimize impact on the existing
splitflap-os codebase: the OS interacts with only two topics —
<prefix>/send and <prefix>/rx.

How it works

A new transport class presents the same interface the existing code already
used for the serial port (.write(), .flush(), .reset_input_buffer(),
.in_waiting, .read(), .close()). Because of this, none of the existing
read/write call sites had to change.

  • OS → Gateway on <prefix>/send: the exact RS485 frame bytes the old
    code wrote to the serial port (e.g. m05-A\n, m05c\n, m05d\n) are
    published verbatim. The gateway forwards them straight to the bus.
  • Gateway → OS on <prefix>/rx: the gateway publishes JSON
    {"ts":...,"wt":"...","command":"<ascii frame>"} for each frame received
    from a module. The transport extracts command, re-appends the newline the
    gateway stripped, and buffers it so the existing calibration / EEPROM-dump
    read loops parse it exactly as they did with serial.

The gateway MQTT connection is independent of the pre-existing
MQTT / Home Assistant integration (which publishes state and accepts control
commands). They use separate clients and settings.

Files changed

New: server/gateway_transport.py

GatewayTransport, a pyserial-compatible facade over MQTT. Touches only
<prefix>/send (publish) and <prefix>/rx (subscribe). Fails fast on an
unreachable broker so the app can fall back to simulation mode, mirroring how
serial failure is handled.

server/app.py

  • Import GatewayTransport (graceful fallback if unavailable).
  • New settings keys with defaults: connection_type (serial|gateway),
    gateway_broker, gateway_port, gateway_prefix, gateway_user,
    gateway_password.
  • _read_config_file(), _get_connection_type(), _get_gateway_config()
    helpers (readable at startup, before load_settings() runs).
  • _open_gateway() and _open_connection(); startup now calls
    _open_connection(), which dispatches on connection_type.
  • /serial_port now also sets connection_type='serial' when applied.
  • New /connection route (GET/POST) to view/switch/configure the connection.
    Password is preserved on blank resubmit and never echoed back in full.
  • No existing ser.write() / ser.read() call sites were modified.

server/templates/index.html

  • "Serial Port" settings panel replaced by a "Hardware Connection" panel with
    a Connection Type selector toggling between the serial panel and a new
    gateway panel (broker, port, topic prefix, optional username/password).
  • Added a clarifying note on the existing "MQTT / Home Assistant" section.

server/static/app.js

  • onConnectionTypeChange(), loadConnectionConfig(),
    applyGatewayConnection(); loadSettingsData() now also loads the
    connection config; applying a serial port syncs the type selector.

README.md

  • Feature bullet + a "SplitFlap Gateway (MQTT)" section documenting the topic
    contract and environment variables.

Environment variables (optional, override settings.json)

SPLITFLAP_CONNECTION_TYPE=gateway     # 'serial' (default) or 'gateway'
SPLITFLAP_GATEWAY_BROKER=192.168.1.50
SPLITFLAP_GATEWAY_PORT=1883
SPLITFLAP_GATEWAY_PREFIX=splitflap
SPLITFLAP_GATEWAY_USER=               # optional
SPLITFLAP_GATEWAY_PASSWORD=           # optional

Dependencies

paho-mqtt was already present in requirements.txt; no new dependency added.

…rt connect

"Serial Port" settings panel replaced by a "Hardware Connection" panel with
a Connection Type selector toggling between the serial panel and a new
gateway panel (broker, port, topic prefix, optional username/password).
Added a clarifying note on the existing "MQTT / Home Assistant" section.
…rt connect

onConnectionTypeChange(), loadConnectionConfig(),
applyGatewayConnection(); loadSettingsData() now also loads the
connection config; applying a serial port syncs the type selector.
…rt connect

Feature bullet + a "SplitFlap Gateway (MQTT)" section documenting the topic
contract and environment variables.

@csader csader left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work! Duck-typing the serial interface means zero downstream changes, the fail-fast behavior mirrors serial properly, and the password handling is done right.

A few things I'd like addressed before merging:

Silent publish on disconnect (important):
write() doesn't check self._connected before publishing. If the broker disconnects mid-session, paho silently queues messages and the user won't know commands aren't reaching the display. This is especially concerning during calibration/EEPROM writes. At minimum, write() should log a warning or raise when not connected, otherwise command loss is invisible.

Minor items (non-blocking):

  • client_id uses second-precision timestamps (int(time.time())). If the app restarts within the same second (crash loop on Pi), you get a duplicate client ID. Consider appending os.getpid() or a short random suffix.

  • _read_config_file() is called independently by _get_connection_type(), _get_gateway_config(), and _get_serial_port() at startup — reads settings.json 2-3 times. Not a real problem, just noting it.

  • /connection POST doesn't validate the type field. An unknown value silently falls through to the serial branch. A 400 for unrecognized types would be cleaner.

Once the disconnect issue is handled, this is good to go. Since the feature is opt-in (off by default, requires explicit config), I'm comfortable merging to main without a beta release.

@avandeputte

Copy link
Copy Markdown
Contributor Author

All 4 items resolved, i think. Tested locally no issues with the changes.

@csader csader left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All four items addressed cleanly. The warning-not-raise approach in write() is the right call for pyserial compatibility. Good to go.

@csader csader merged commit 4c11ab9 into csader:main Jun 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants