Files
nixie-clock/firmware/uniclock/lib/arduino-home-assistant/src/HAMqtt.h
T

465 lines
16 KiB
C++
Raw Normal View History

2026-10-25 21:37:25 +01:00
#ifndef AHA_HAMQTT_H
#define AHA_HAMQTT_H
#include <Arduino.h>
#include <Client.h>
#include <IPAddress.h>
#include "ArduinoHADefines.h"
#define HAMQTT_CALLBACK(name) void (*name)()
#define HAMQTT_STATE_CALLBACK(name) void (*name)(ConnectionState state)
#define HAMQTT_MESSAGE_CALLBACK(name) void (*name)(const char* topic, const uint8_t* payload, uint16_t length)
#define HAMQTT_DEFAULT_PORT 1883
#ifdef ARDUINOHA_TEST
class PubSubClientMock;
#else
class PubSubClient;
#endif
#if defined(__AVR_ATmega328P__) || defined(__AVR_ATmega168__)
#define HAMQTT_DEFAULT_DEVICES_LIMIT 6
#else
#define HAMQTT_DEFAULT_DEVICES_LIMIT 24
#endif
class HADevice;
class HABaseDeviceType;
#if defined(ARDUINO_API_VERSION)
using namespace arduino;
#endif
/**
* This class is a wrapper for the PubSub API.
* It's a central point of the library where instances of all device types are stored.
*/
class HAMqtt
{
public:
enum ConnectionState {
StateConnecting = -5,
StateConnectionTimeout = -4,
StateConnectionLost = -3,
StateConnectionFailed = -2,
StateDisconnected = -1,
StateConnected = 0,
StateBadProtocol = 1,
StateBadClientId = 2,
StateUnavailable = 3,
StateBadCredentials = 4,
StateUnauthorized = 5
};
/**
* Returns existing instance (singleton) of the HAMqtt class.
* It may be a null pointer if the HAMqtt object was never constructed or it was destroyed.
*/
inline static HAMqtt* instance()
{ return _instance; }
#ifdef ARDUINOHA_TEST
explicit HAMqtt(
PubSubClientMock* pubSub,
HADevice& device,
const uint8_t maxDevicesTypesNb = HAMQTT_DEFAULT_DEVICES_LIMIT
);
#else
/**
* Creates a new instance of the HAMqtt class.
* Please note that only one instance of the class can be initialized at the same time.
*
* @param netClient The EthernetClient or WiFiClient that's going to be used for the network communication.
* @param device An instance of the HADevice class representing your device.
* @param maxDevicesTypesNb The maximum number of device types (sensors, switches, etc.) that you're going to implement.
*/
explicit HAMqtt(
Client& netClient,
HADevice& device,
const uint8_t maxDevicesTypesNb = HAMQTT_DEFAULT_DEVICES_LIMIT
);
#endif
/**
* Removes singleton of the HAMqtt class.
*/
~HAMqtt();
/**
* Sets the prefix of the Home Assistant discovery topics.
* It needs to match the prefix set in the HA admin panel.
* The default prefix is "homeassistant".
*
* @param prefix The discovery topics' prefix.
*/
inline void setDiscoveryPrefix(const char* prefix)
{ _discoveryPrefix = prefix; }
/**
* Returns the discovery topics' prefix.
*/
inline const char* getDiscoveryPrefix() const
{ return _discoveryPrefix; }
/**
* Sets prefix of the data topics.
* It may be useful if you want to pass MQTT traffic through a bridge.
* The default prefix is "aha".
*
* @param prefix The data topics' prefix.
*/
inline void setDataPrefix(const char* prefix)
{ _dataPrefix = prefix; }
/**
* Returns the data topics' prefix.
*/
inline const char* getDataPrefix() const
{ return _dataPrefix; }
/**
* Returns instance of the device assigned to the HAMqtt class.
* It's the same object (pointer) that was passed to the HAMqtt constructor.
*/
inline HADevice const* getDevice() const
{ return &_device; }
/**
* Registers a new callback method that will be called when the device receives an MQTT message.
* Please note that the callback is also fired by internal MQTT messages used by the library.
* You should always verify the topic of the received message.
*
* @param callback Callback method.
*/
inline void onMessage(HAMQTT_MESSAGE_CALLBACK(callback))
{ _messageCallback = callback; }
/**
* Registers a new callback method that will be called each time a connection to the MQTT broker is acquired.
* The callback is also fired after reconnecting to the broker.
* You can use this method to register topics' subscriptions.
*
* @param callback Callback method.
*/
inline void onConnected(HAMQTT_CALLBACK(callback))
{ _connectedCallback = callback; }
/**
* Registers a new callback method that will be called each time the connection to the MQTT broker is lost.
*
* @param callback Callback method.
*/
inline void onDisconnected(HAMQTT_CALLBACK(callback))
{ _disconnectedCallback = callback; }
/**
* Registers a new callback method that will be called each time the connection state changes.
*
* @param callback Callback method.
*/
inline void onStateChanged(HAMQTT_STATE_CALLBACK(callback))
{ _stateChangedCallback = callback; }
/**
* Returns the current state of the MQTT connection.
*/
inline ConnectionState getState() const
{ return _currentState; }
/**
* Sets parameters of the MQTT connection using the IP address and port.
* The library will try to connect to the broker in first loop cycle.
* Please note that the library automatically reconnects to the broker if connection is lost.
*
* @param serverIp IP address of the MQTT broker.
* @param serverPort Port of the MQTT broker.
* @param username Username for authentication. It can be nullptr if the anonymous connection needs to be performed.
* @param password Password for authentication. It can be nullptr if the anonymous connection needs to be performed.
*/
bool begin(
const IPAddress serverIp,
const uint16_t serverPort = HAMQTT_DEFAULT_PORT,
const char* username = nullptr,
const char* password = nullptr
);
/**
* Sets parameters of the MQTT connection using the IP address and the default port (1883).
* The library will try to connect to the broker in first loop cycle.
* Please note that the library automatically reconnects to the broker if connection is lost.
*
* @param serverIp IP address of the MQTT broker.
* @param username Username for authentication. It can be nullptr if the anonymous connection needs to be performed.
* @param password Password for authentication. It can be nullptr if the anonymous connection needs to be performed.
*/
bool begin(
const IPAddress serverIp,
const char* username,
const char* password
);
/**
* Sets parameters of the MQTT connection using the hostname and port.
* The library will try to connect to the broker in first loop cycle.
* Please note that the library automatically reconnects to the broker if connection is lost.
*
* @param serverHostname Hostname of the MQTT broker.
* @param serverPort Port of the MQTT broker.
* @param username Username for authentication. It can be nullptr if the anonymous connection needs to be performed.
* @param password Password for authentication. It can be nullptr if the anonymous connection needs to be performed.
*/
bool begin(
const char* serverHostname,
const uint16_t serverPort = HAMQTT_DEFAULT_PORT,
const char* username = nullptr,
const char* password = nullptr
);
/**
* Sets parameters of the MQTT connection using the hostname and the default port (1883).
* The library will try to connect to the broker in first loop cycle.
* Please note that the library automatically reconnects to the broker if connection is lost.
*
* @param serverHostname Hostname of the MQTT broker.
* @param username Username for authentication. It can be nullptr if the anonymous connection needs to be performed.
* @param password Password for authentication. It can be nullptr if the anonymous connection needs to be performed.
*/
bool begin(
const char* serverHostname,
const char* username,
const char* password
);
/**
* Closes the MQTT connection.
*/
bool disconnect();
/**
* This method should be called periodically inside the main loop of the firmware.
* It's safe to call this method in some interval (like 5ms).
*/
void loop();
/**
* Returns true if connection to the MQTT broker is established.
*/
bool isConnected() const;
/**
* Sets keep alive for the MQTT connection.
* By default it's 15 seconds.
*
* @param keepAlive Number of seconds to keep connection alive.
*/
void setKeepAlive(uint16_t keepAlive);
/**
* Sets the buffer size for the MQTT connection.
* By default it's 256 bytes.
*
* @param size Size of the buffer.
*/
bool setBufferSize(uint16_t size);
/**
* Adds a new device's type to the MQTT.
* Each time the connection with MQTT broker is acquired, the HAMqtt class
* calls "onMqttConnected" method in all devices' types instances.
*
* @note The HAMqtt class doesn't take ownership of the given pointer.
* @param deviceType Instance of the device's type (HASwitch, HABinarySensor, etc.).
*/
void addDeviceType(HABaseDeviceType* deviceType);
/**
* Publishes the MQTT message with given topic and payload.
* Message won't be published if the connection with the MQTT broker is not established.
* In this case method returns false.
*
* @param topic The topic to publish.
* @param payload The payload to publish (it may be empty const char).
* @param retained Specifies whether message should be retained.
*/
bool publish(const char* topic, const char* payload, bool retained = false);
/**
* Begins publishing of a message with the given properties.
* When this method returns true the payload can be written using HAMqtt::writePayload method.
*
* @param topic Topic of the published message.
* @param payloadLength Length of the payload (bytes) that's going to be published.
* @param retained Specifies whether the published message should be retained.
*/
bool beginPublish(const char* topic, uint16_t payloadLength, bool retained = false);
/**
* Writes given string to the TCP stream.
* Please note that before writing any data the HAMqtt::beginPublish method
* needs to be called.
*
* @param data The string to publish.
* @param length Length of the data (bytes).
*/
void writePayload(const char* data, const uint16_t length);
/**
* Writes given data to the TCP stream.
* Please note that before writing any data the HAMqtt::beginPublish method
* needs to be called.
*
* @param data The data to publish.
* @param length Length of the data (bytes).
*/
void writePayload(const uint8_t* data, const uint16_t length);
/**
* Writes given progmem data to the TCP stream.
* Please note that before writing any data the HAMqtt::beginPublish method
* needs to be called.
*
* @param data Progmem data to publish.
*/
void writePayload(const __FlashStringHelper* data);
/**
* Finishes publishing of a message.
* After calling this method the message will be processed by the broker.
*/
bool endPublish();
/**
* Subscribes to the given topic.
* Whenever a new message is received the onMqttMessage callback in all
* devices types is called.
*
* Please note that you need to subscribe topic each time the connection
* with the broker is acquired.
*
* @param topic Topic to subscribe.
*/
bool subscribe(const char* topic);
/**
* Enables the last will message that will be produced when the device disconnects from the broker.
* If you want to change availability of the device in Home Assistant panel
* please use enableLastWill() method from the HADevice class instead.
*
* @param lastWillTopic The topic to publish.
* @param lastWillMessage The message (payload) to publish.
* @param lastWillRetain Specifies whether the published message should be retained.
*/
inline void setLastWill(
const char* lastWillTopic,
const char* lastWillMessage,
bool lastWillRetain
) {
_lastWillTopic = lastWillTopic;
_lastWillMessage = lastWillMessage;
_lastWillRetain = lastWillRetain;
}
/**
* Processes MQTT message received from the broker (subscription).
*
* @note Do not use this method on your own. It's only for the internal purpose.
* @param topic Topic of the message.
* @param payload Content of the message.
* @param length Length of the message.
*/
void processMessage(const char* topic, const uint8_t* payload, uint16_t length);
#ifdef ARDUINOHA_TEST
inline uint8_t getDevicesTypesNb() const
{ return _devicesTypesNb; }
inline HABaseDeviceType** getDevicesTypes() const
{ return _devicesTypes; }
#endif
private:
/// Interval between MQTT reconnects (milliseconds).
static const uint16_t ReconnectInterval = 10000;
/// Living instance of the HAMqtt class. It can be nullptr.
static HAMqtt* _instance;
/**
* Attempts to connect to the MQTT broker.
* The method uses properties passed to the "begin" method.
*/
void connectToServer();
/**
* This method is called each time the connection with MQTT broker is acquired.
*/
void onConnectedLogic();
/**
* Sets the state of the MQTT connection.
*/
void setState(ConnectionState state);
#ifdef ARDUINOHA_TEST
PubSubClientMock* _mqtt;
#else
/// Instance of the PubSubClient class. It's initialized in the constructor.
PubSubClient* _mqtt;
#endif
/// Instance of the HADevice passed to the constructor.
const HADevice& _device;
/// The callback method that will be called when an MQTT message is received.
HAMQTT_MESSAGE_CALLBACK(_messageCallback);
/// The callback method that will be called when the MQTT connection is acquired.
HAMQTT_CALLBACK(_connectedCallback);
/// The callback method that will be called when the MQTT connection is lost.
HAMQTT_CALLBACK(_disconnectedCallback);
/// The callback method that will be called when the MQTT connection state changes.
HAMQTT_STATE_CALLBACK(_stateChangedCallback);
/// Specifies whether the HAMqtt::begin method was ever called.
bool _initialized;
/// Teh discovery prefix that's used for the configuration messages.
const char* _discoveryPrefix;
/// The data prefix that's used for publishing data messages.
const char* _dataPrefix;
/// The username used for the authentication. It's set in the HAMqtt::begin method.
const char* _username;
/// The password used for the authentication. It's set in the HAMqtt::begin method.
const char* _password;
/// Time of the last connection attemps (milliseconds since boot).
uint32_t _lastConnectionAttemptAt;
/// The amount of registered devices types.
uint8_t _devicesTypesNb;
/// The maximum amount of devices types that can be registered.
uint8_t _maxDevicesTypesNb;
/// Pointers of all registered devices types (array of pointers).
HABaseDeviceType** _devicesTypes;
/// The last will topic set by HAMqtt::setLastWill
const char* _lastWillTopic;
/// The last will message set by HAMqtt::setLastWill
const char* _lastWillMessage;
/// The last will retain set by HAMqtt::setLastWill
bool _lastWillRetain;
/// The last known state of the MQTT connection.
ConnectionState _currentState;
};
#endif