Skip to content

Implement MQTT as a regular API ("api": "mqtt") instead of hardcoding it into the core (#550)#709

Open
OKDaG wants to merge 2 commits into
volkszaehler:masterfrom
OKDaG:feature/mqtt-as-api-550
Open

Implement MQTT as a regular API ("api": "mqtt") instead of hardcoding it into the core (#550)#709
OKDaG wants to merge 2 commits into
volkszaehler:masterfrom
OKDaG:feature/mqtt-as-api-550

Conversation

@OKDaG

@OKDaG OKDaG commented Jun 3, 2026

Copy link
Copy Markdown

Summary

Fixes #550. MQTT was added in #357 directly inside the vzlogger core
(reading_thread), publishing on a separate path that bypassed aggregation and
other channel logic (see #538).

This PR turns MQTT into a regular per-channel API, selected via "api": "mqtt",
exactly like volkszaehler, influxdb, etc. Values now flow through the channel
buffer and the regular logging_threadapi->send() path, so aggregation
(aggmode/aggtime) and duplicate handling now apply to MQTT
as well.

"channels": [{
    "api": "mqtt",
    ...
}]

Changes

  • New vz::api::MQTT (include/api/MQTT.hpp, src/api/MQTT.cpp): consumes the
    (already aggregated) channel buffer in send() like InfluxDB/Null, honoring
    duplicates, and publishes via the shared broker connection. register_device()
    announces uuid/id.
  • MqttClient reduced to a transport/connection layer: new thread-safe
    publish(topic, payload) plus getters for the global defaults; the old
    channel-based publish()/ChannelEntry/_chMap logic is removed.
  • Removed the hardcoded mqttClient->publish() calls from reading_thread
    (the bypass) and registered "mqtt" in the API factory in threads.cpp and
    MeterMap.cpp.
  • Added MQTT.cpp to the api library and the metermap mock build.
  • Documented the change in etc/vzlogger.conf + added an "api": "mqtt" channel
    example.

The global "mqtt" config block is kept for the shared broker connection
(host/port/tls/credentials) and as defaults for topic/qos/retain/timestamp,
which a channel may override.

⚠️ Breaking change

Configs that relied on the global mqtt block to auto-publish all channels must
now set "api": "mqtt" per channel. A channel can only have one API.

Testing

  • Build with ENABLE_MQTT=ON (Docker builder stage) compiles & links cleanly.
  • Test suite (build_test=on): 5/5 pass.
  • End-to-end against a real Mosquitto broker: "api": "mqtt" publishes
    aggregated values to …/agg (one per aggtime) plus the id/uuid
    announce topics; with a non-mqtt api nothing is published (bypass gone).

🤖 Generated with Claude Code

MQTT was added in volkszaehler#357 directly inside the vzlogger core (reading_thread),
publishing readings on a separate path that bypassed aggregation and other
channel logic (see volkszaehler#538).

This makes MQTT a per-channel API selected via "api": "mqtt", so values flow
through the channel buffer and the regular logging_thread -> api->send() path.
As a result aggregation (aggmode/aggtime) and duplicate handling now apply to
MQTT just like for the other APIs.

- Add vz::api::MQTT (include/api/MQTT.hpp, src/api/MQTT.cpp): consumes the
  (already aggregated) channel buffer in send() like InfluxDB/Null and
  publishes via the shared broker connection; register_device() announces
  uuid/id.
- Reduce MqttClient to a transport/connection layer: new thread-safe
  publish(topic, payload) plus getters for the global defaults; remove the old
  channel-based publish()/ChannelEntry/_chMap logic.
- Remove the hardcoded mqttClient->publish() calls from reading_thread (the
  bypass) and register "mqtt" in the api factory in threads.cpp and
  MeterMap.cpp.
- Add MQTT.cpp to the api library and the metermap mock build.
- Document the change in etc/vzlogger.conf and add an "api": "mqtt" channel
  example.

The global "mqtt" config block is kept for the shared broker connection
(host/port/tls/credentials) and as defaults for topic/qos/retain/timestamp,
which a channel may override.

Note: this is a breaking change - configs that relied on the global mqtt block
to auto-publish all channels must now set "api": "mqtt" per channel.

Closes volkszaehler#550

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@r00t-

r00t- commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

thanks for picking up that ticket!

i'm a bit wary of fully(?) generated code,
can you share some details about how this was done? just pointed claude at the ticket?

two things i wonder:

  • do we want to keep the broker address in the "mqtt" section? no other API does this. (but it does get repettitive.)
  • should backward compatibility be provided for users? does it make sense?

also note that i can't test this as i'm not a user of mqtt.

@OKDaG

OKDaG commented Jun 3, 2026

Copy link
Copy Markdown
Author

I used an AI assistant to do the heavy lifting not fully coding since my coding skills in c++ are sometimes "outdated", I reviewed every change and verified it end-to-end against a local Mosquitto broker
Since i'm german, i use claude to create the text part of the PRs

About the Broker address in the mqtt section: I kept it there on purpose, because MQTT is a bit different from the HTTP-based APIs: it's a single persistent connection (one TCP socket + a background mosquitto_loop thread) that's naturally shared by all channels, whereas volkszaehler/influxdb are stateless per-request. Putting host/port/TLS/credentials per channel would mean either repeating them on every channel or opening one broker connection per channel. So the mqtt block stays as the shared connection, and per channel you only set "api": "mqtt" plus optional topic/qos/retain/timestamp overrides. If you'd prefer consistency with the other APIs, an alternative is to allow an optional per-channel host/port that falls back to the global block (pooling connections by host).

Right now this is intentionally a clean break. The global block no longer auto-publishes every channel, you opt in per channel via "api": "mqtt". The one real regression is that a channel can only have one api, so the "send to volkszaehler and mirror to mqtt" use case isn't possible in a single channel anymore (you'd need a second channel with the same identifier/uuid).

Options I see:

  1. keep it as a clean break (current PR) and document it
  2. additionally keep the old global auto-publish path behind a flag for compatibility (two code paths to maintain)
  3. longer-term: allow multiple apis per channel (bigger refactor, but the "proper" fix for secondary sinks)

What's your preference?

@Zugschlus

Copy link
Copy Markdown
Contributor

I applaud this change. Thank you for moving forward here. The situation was more than unsatisfying before.

I'd probably want broker and topic with the channel though, there might be use cases where the readings from one channel to one broker and from another channel to another broker. In the program, the startup code would have to sift through the channel definitions, make a list of connections to build, bring up the connections and then take care that the correct channel publlishes to the correct connection.
But that's probably gold plating things, the use cases I thought about are probably exotic.

@r00t-

r00t- commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

@OKDaG:
thanks for detailing!

all sounds good to me,
i personally don't mind the breaking change, in part because i'm simply not a user of mqtt...
(for the "broker defined in global config", we might want to create some shared mechanism, like "channel templates" or such - but fine to leave as-is for now!)
regarding 2), i was wondering if there's a simple way to internally create the channels equivalent to the old behaviour (instead of maintaining the old code path), but simply making the breaking change is also fine with me.

a channel can only have one api

as you noted, a meter can have any number of channels, and those can attach to the same identifiers,
( https://github.com/volkszaehler/vzlogger/blob/master/src/threads.cpp#L119 )
so it should be perfectly possible to send the same readings to multiple APIs,
one just has to declare multiple channels with different APIs - that was always the design,
until mqtt bypassed it by direct integration.

the only thing i think we should have here is some testing by actual users of mqtt.
i was wondering if we could ask for volunteers the mailing list(s)?

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.

mqtt should be implemented as an API, not hardcoded into vzlogger core

3 participants