Subversion Repositories ESP8266_P1_Meter

Rev

Blame | Last modification | View Log | RSS feed

#ifndef __MQTT_REMOTE_H__
#define __MQTT_REMOTE_H__

#include "IMQTTRemote.h"
#include <MQTT.h>
#include <functional>
#include <map>
#include <string>
#ifdef ESP32
#include <WiFi.h>
#elif ESP8266
#include <ESP8266WiFi.h>
#elif defined(ARDUINO_SAMD_MKRWIFI1010)
#include <WiFi101.h>
#else
#error "Unsupported hardware. Sorry!"
#endif

/**
 * @brief MQTT wrapper for setting up MQTT connection (and will) and provide API for sending and subscribing to
 * messages.
 */
class MQTTRemote : public IMQTTRemote {
public:
  /**
   * Additional configuration where most user can go with defaults.
   */
  struct Configuration {
    /**
     * Maximum message size, in bytes, for incoming and outgoing messages. Messages larger than this will be truncated.
     * This will be allocated on the heap upon MQTTRemote object creation.
     */
    uint32_t buffer_size = 1024;

    /**
     * MQTT keep alive interval, in seconds. If the client fails to communicate with the broker within the specified
     * Keep Alive period, the LWT/Last Will message is sent (by the broker).
     */
    uint32_t keep_alive_s = 10;

    /**
     * if true, will print on Serial on message received. Publish verbosity is controlled by the
     * which publish method that is used. Connection information on setup will always be printed out.
     */
    bool receive_verbose = false;
  };

  /**
   * @brief Construct a new MQTTRemote object
   *
   * @param client_id Base ID for this device. This is used for the last will / status
   * topic. Example, if this is "esp_now_router", then the status/last will topic will be "esp_now_router/status". This
   * is also used as client ID for the MQTT connection. This has to be [a-zA-Z0-9_] only and unique among all MQTT
   * clients on the server. It should also be stable across connections.
   * @param host MQTT hostname or IP for MQTT server.
   * @param port MQTT port number.
   * @param username MQTT username.
   * @param password MQTT password.
   */
  MQTTRemote(std::string client_id, std::string host, int port, std::string username, std::string password)
      : MQTTRemote(std::move(client_id), std::move(host), port, std::move(username), std::move(password),
                   Configuration{}) {}

  /**
   * @brief Construct a new MQTTRemote object
   *
   * @param client_id Base ID for this device. This is used for the last will / status
   * topic. Example, if this is "esp_now_router", then the status/last will topic will be "esp_now_router/status". This
   * is also used as client ID for the MQTT connection. This has to be [a-zA-Z0-9_] only and unique among all MQTT
   * clients on the server. It should also be stable across connections.
   * @param host MQTT hostname or IP for MQTT server.
   * @param port MQTT port number.
   * @param username MQTT username.
   * @param password MQTT password.
   * @param configuration Additional configuration where most user can go with defaults.
   */
  MQTTRemote(std::string client_id, std::string host, int port, std::string username, std::string password,
             Configuration configuration);

  /**
   * Call from Arduino loop() function in main.
   */
  void handle();

  /**
   * @brief Set optional callback on connect state change. Will be called when the client is connected
   * to server (every time, so expect calls on reconnection), and on disconnect. The parameter will be true on new
   * connection and false on disconnection. Set to {} to clear callback.
   */
  void setOnConnectionChange(std::function<void(bool connected)> callback = {}) { _on_connection_change = callback; };

  /**
   * @brief Publish a message.
   *
   * @param topic the topic to publish to.
   * @param message The message to send. This cannot be larger than the value set for max_message_size in the
   * constructor.
   * @param retain True to set this message as retained.
   * @param qos quality of service for published message (0 (default), 1 or 2)
   * @returns true on success, or false on failure.
   */
  bool publishMessage(std::string topic, std::string message, bool retain = false, uint8_t qos = 0) override;

  /**
   * Same as publishMessage(), but will print the message and topic and the result on serial.
   */
  bool publishMessageVerbose(std::string topic, std::string message, bool retain = false, uint8_t qos = 0) override;

  /**
   * @brief returns if there is a connection to the MQTT server.
   */
  bool connected() override { return _mqtt_client.connected(); }

  /**
   * @brief Subscribe to a topic. The callback will be invoked on every new message.
   * There can only be one callback per topic. If trying to subscribe to an already subscribe topic, it will be ignored.
   * Don't do heavy operations in the callback or delays as this will block the MQTT callback.
   *
   * Can be called before being connected. All subscriptions will be (re-)subscribed to once a connection is
   * (re-)established.
   *
   * @param message_callback a message callback with the topic and the message. The topic is repeated for convinience,
   * but it will always be for the subscribed topic.
   * @return true if an subcription was successul. Will return false if there is no active MQTT connection. In this
   * case, the subscription will be performed once connected. Will retun false if this subscription is already
   * subscribed to.
   */
  bool subscribe(std::string topic, IMQTTRemote::SubscriptionCallback message_callback) override;

  /**
   * @brief Unsubscribe a topic.
   */
  bool unsubscribe(std::string topic) override;

  /**
   * @brief The client ID for this device. This is used for the last will / status
   * topic.Example, if this is "esp_now_router", then the status/last will topic will be "esp_now_router/status". This
   * has to be [a-zA-Z0-9_] only.
   */
  std::string &clientId() override { return _client_id; }

private:
  void onMessage(MQTTClient *client, char topic_cstr[], char message_cstr[], int message_size);
  void setupWill();

private:
  std::string _client_id;
  std::string _host;
  std::string _username;
  std::string _password;
  bool _receive_verbose;
  WiFiClient _wifi_client;
  MQTTClient _mqtt_client;
  bool _was_connected = false;
  std::function<void(bool)> _on_connection_change;
  std::map<std::string, SubscriptionCallback> _subscriptions;
  unsigned long _last_connection_attempt_timestamp_ms = 0;
};

#endif // __MQTT_REMOTE_H__