mlesniew / PicoMQTT

ESP MQTT client and broker library
GNU Lesser General Public License v3.0
219 stars 25 forks source link

publish + subscribe on same device #32

Closed mhaberler closed 2 months ago

mhaberler commented 3 months ago

you write:

is there a way to override this behavior - either on a publish or subscribe basis?

to explain the use case (whacky design alert):

I guess it could work with a local client, not the server publish API?

mlesniew commented 3 months ago

The reason behind this limitation of the server is that I wanted to allow publishing messages in chunks.

Writing in chunks can be done by using the begin_publish() method (see https://github.com/mlesniew/PicoMQTT?tab=readme-ov-file#arbitrary-sized-messages). The method returns an object that has a Print interface, so it can be written to using the usual methods (like write, print, printf, println). The object will allow using these methods multiple times, until the full declared size of the payload is reached.

This way the library can send the MQTT header right away (when begin_publish() is called) and then send the payload bit by bit with every write/print call. There is some optional buffering done to not submit chunks that are very small, but in general this reduces the memory use significantly.

With this approach we can send MQTT messages even bigger than the RAM size. It's useful when the message content can be generated on the fly like in the ArduinoJson examples -- it saves a lot of memory.

The consequence of this is that we may never have the full payload of the message we're sending in any buffer. This means we can't fire a subscription callback, because we can't provide it with a complete payload argument.

To make things even more complicated, the subscription API also supports reading messages in a streaming fashion...

Now regarding your specific use case. You can probably make things work using the following approach:

PicoMQTT::Server mqtt;
LocalSensor local_sensor;

void on_sensor_update(const char * topic, const char * payload) {
    // update UI
    ...
}

void publish_wrapper(const String & topic, const String & payload) {
        mqtt.publish(topic, payload);
        on_sensor_update(topic.c_str(), payload.c_str());
}

void setup() {
    ...
    mqtt.subscribe("sensor/#", on_sensor_update);
    mqtt.begin();
}

void loop() {
    mqtt.loop();

    if (local_sensor.updated()) {
        // sensor has a new reading
        const String topic = String("sensor/") + local_sensor.get_name();
        const String payload = String(local.sensor.get_reading());

        publish_wrapper(topic, payload);
    }

    ...
}

You could also subclass PicoMQTT::Server and override the publish methods to do something similar (but you'll have to avoid using begin_publish).

mhaberler commented 3 months ago

the first method (bypass the broker via on_sensor_update) works around the issue

however, compared to a subscription it has the disadvantage of send-always instead of send-on-payload-change

mlesniew commented 2 months ago

I've added some changes to support local subscribe -- they're currently on the server_local_subscribe branch.

Give it a try, probably all you'll have to change in your code is use PicoMQTT::ServerLocalSubscribe instead of PicoMQTT::Server, but have a look at this section of the readme and the example.

mhaberler commented 2 months ago

really appreciated!

will try it out and report.

mlesniew commented 2 months ago

The changes from the branch are now merged into master and included in release 1.1.0

mhaberler commented 2 months ago

did try it out - found no issues

mlesniew commented 2 months ago

Great, in that case, I'm closing the issue