Skip to content

Add the ebus_mqtt target: transport interface, PublishHold, reconnect order - #6

Merged
dcj merged 5 commits into
mainfrom
feat/ebus-mqtt-target
Oct 9, 2026
Merged

dcj merged 5 commits into
mainfrom
feat/ebus-mqtt-target

Conversation

@dcj

@dcj dcj commented Oct 9, 2026

Copy link
Copy Markdown
Contributor

Adds ebus_mqtt, the first component of the split in #3: the Homie-agnostic MQTT layer, with no dependency and only its own include directory. ebus_core links it, and PlatformIO still builds one library.

What moved

From To
Transport interface in homie/homie_transport.h MqttTransport in mqtt/include/ebus/mqtt/transport.h; HomieTransport stays in homie/homie_transport.h as a subclass with the Homie adapter
ports/posix/include/ebus_posix/publish_hold.h, ports/posix/src/publish_hold.cpp mqtt/include/ebus/mqtt/publish_hold.h, mqtt/src/publish_hold.cpp
ports/posix/test/test_publish_hold.cpp (hand-rolled checks) test/test_mqtt_publish_hold/ (Unity under CTest)
Reconnect order inline in PahoTransport::try_connect() mqtt_after_connect() in mqtt/include/ebus/mqtt/reconnect.h, mqtt/src/reconnect.cpp, with test/test_mqtt_reconnect/

Layout: each component gets <name>/include/ebus/<name>/ and <name>/src/ with its own CMakeLists.txt, so discovery/ and homie/ can follow the same shape. Naming stays namespace-free like the rest of the core, with Mqtt/mqtt_ prefixes and EBUS_MQTT_ macros.

Interface change

Before:

virtual bool queue_publish(const char* topic, const char* payload, int length,
                           bool retained, Property* source) = 0;   // HomieTransport

After:

typedef void (*mqtt_publish_done_fn)(void* ctx, const char* payload, int length, bool sent);

// MqttTransport (ebus_mqtt)
virtual bool queue_publish(const char* topic, const char* payload, int length, bool retained,
                           int qos, mqtt_publish_done_fn done, void* ctx) = 0;

// HomieTransport : MqttTransport (homie/homie_transport.h)
bool queue_publish(..., int qos, mqtt_publish_done_fn done, void* ctx) override;  // default: refuse, log, done(false)
virtual bool queue_publish(..., bool retained, Property* source);  // default: forward at homie_qos(retained)

Property still calls the Property* overload. Its default forwards to the generic one with a callback that calls Property::queued_publish_done(), so the publish-on-change memo works either way. A port overrides one of the two.

PublishHold no longer logs; hold() returns EVICTED_OLDEST / DROPPED_TOO_LARGE and the port logs. Sizes are EBUS_MQTT_HOLD_ENTRIES (64), EBUS_MQTT_HOLD_TOPIC_MAX (127), EBUS_MQTT_HOLD_PAYLOAD_MAX (1024).

esp32-sdk compatibility

MqttClientTransport overrides the Property* overload, which is still virtual, so esp32-sdk builds with no source change. Checked in a scratch worktree of esp32-sdk main with lib/ebus_core pointed at this branch (nothing committed there):

  • ./ebus-esp32 build (esp32-poe-iso): builds with no warnings. firmware.bin 1,436,352 bytes on main (cpp-sdk v0.1.0), 1,436,480 with this branch, so +128 bytes (.flash.text +32, .flash.rodata +80, RAM sections unchanged).
  • ./ebus-esp32 test: 11/11 suites pass, including the two new test_mqtt_* suites under PlatformIO's native env. That confirms library.json's -I mqtt/include reaches the library's dependents.

Tests

  • Core (cmake -S . -B build, warnings as errors): 10/10 CTest suites pass with Apple Clang and GCC 16.
  • POSIX port (-DEBUS_POSIX_WERROR=ON): 6/6 e2e tests pass against a local mosquitto on 127.0.0.1. The reconnect test checks the flush, re-subscribe, notify order from the log, and those log lines are unchanged.
  • ebus_mqtt alone: every source and header compiled with -I mqtt/include only (Clang and GCC), plus add_subdirectory(cpp-sdk/mqtt) from a scratch project.
  • New CI step "ebus_mqtt alone" in core-host-build.

Part of #3. Closes #4.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Npk6WmkpnBRzWkuhK6uCuz

dcj and others added 5 commits October 9, 2026 07:14
ebus_mqtt is the first component of the split: mqtt/include/ebus/mqtt/
and mqtt/src/, built by mqtt/CMakeLists.txt with only its own include
directory and no dependency. ebus_core links it, so consumers keep one
target.

PublishHold moves from ports/posix/ unchanged in behavior. Its sizes are
now EBUS_MQTT_HOLD_ENTRIES, EBUS_MQTT_HOLD_TOPIC_MAX and
EBUS_MQTT_HOLD_PAYLOAD_MAX, and it no longer logs: hold() reports
EVICTED_OLDEST and DROPPED_TOO_LARGE and the POSIX port logs them. Its
test becomes the Unity suite test_mqtt_publish_hold, which links
ebus_mqtt alone.

The header check compiles each component's headers against that
component's target. library.json compiles src/ and mqtt/src/ and adds
mqtt/include to the include path, which PlatformIO passes on to the
library's dependents.

Part of #3

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Npk6WmkpnBRzWkuhK6uCuz
…X port

After every connect a port flushes the hold, re-subscribes, then notifies
the application, as ebus-mqtt-client does. ebus/mqtt/reconnect.h documents
that contract and mqtt_after_connect() runs it from three hooks, stopping
before the later steps when the flush is interrupted or the link drops
during the re-subscribe. PahoTransport::try_connect() now calls it; its
log lines, which the e2e reconnect test checks, are unchanged.

Part of #3

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Npk6WmkpnBRzWkuhK6uCuz
ebus/mqtt/transport.h holds the Homie-agnostic interface: publish(),
subscribe(), connected(), last_error(), and a queue_publish() that takes
an explicit QoS and a completion callback (mqtt_publish_done_fn plus a
void* context) in place of Homie's Property*.

HomieTransport, in homie/homie_transport.h as before, derives from it and
keeps the Property overload as the Homie adapter: by default it forwards
to the generic overload with a callback that calls
Property::queued_publish_done(), so the publish-on-change memo works over
either. A port overrides one of the two; a port written against 0.1.0
(esp32-sdk's MqttClientTransport overrides the Property overload) builds
and behaves as before. MAX_DATA_LEN, mqtt_qos and homie_qos() stay on the
Homie side.

The POSIX port and the test FakeTransport now implement the generic
overload. New tests cover a transport that overrides only the Property
overload and one that overrides neither.

Part of #3

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Npk6WmkpnBRzWkuhK6uCuz
Each mqtt/src/ source and mqtt/include/ header is compiled with
-I mqtt/include as the only include path, with GCC and Clang, so an
include of a Homie or ArduinoJson header in ebus_mqtt fails the build.

Part of #3

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Npk6WmkpnBRzWkuhK6uCuz
…rder

doc/mqtt.md covers MqttTransport and the HomieTransport adapter (which
overload a port overrides), PublishHold's rules and sizes, and
mqtt_after_connect(). The README gains a table of the CMake targets and
the PlatformIO layout; doc/core.md, the POSIX port README and
CONTRIBUTING point to the new pieces; CHANGELOG has an Unreleased entry.

Closes #4

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Npk6WmkpnBRzWkuhK6uCuz
@dcj
dcj merged commit 2b9a4fc into main Oct 9, 2026
8 checks passed
@dcj
dcj deleted the feat/ebus-mqtt-target branch October 9, 2026 14:24
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.

Add an ebus_mqtt target: transport interface and disconnected-link rules

1 participant