From 070a4372846473182ed7ad7c81955758479bf8d8 Mon Sep 17 00:00:00 2001 From: Dawid Chyrzynski Date: Wed, 2 Dec 2020 21:55:32 +0100 Subject: [PATCH] migrate to PubSubClient --- library.properties | 2 +- src/ArduinoHA.h | 1 + src/HADevice.cpp | 88 ++++++++++++++++++-------- src/HADevice.h | 9 +-- src/HAMqtt.cpp | 154 ++++++++++++++++++++++++++------------------- src/HAMqtt.h | 79 +++++++++++++++++++---- 6 files changed, 224 insertions(+), 109 deletions(-) diff --git a/library.properties b/library.properties index 7893f09..2891225 100644 --- a/library.properties +++ b/library.properties @@ -7,4 +7,4 @@ paragraph=This library provides easy to use API for integrating your Arduin-base category=Communication url=https://github.com/dawidchyrzynski/arduino-home-assistant architectures=avr -depends=MQTT +depends=PubSubClient diff --git a/src/ArduinoHA.h b/src/ArduinoHA.h index 673848d..0c4e1c1 100644 --- a/src/ArduinoHA.h +++ b/src/ArduinoHA.h @@ -1,2 +1,3 @@ #include "HADevice.h" #include "HAMqtt.h" +#include "device-types/HATriggers.h" diff --git a/src/HADevice.cpp b/src/HADevice.cpp index 8327c5e..a24b808 100644 --- a/src/HADevice.cpp +++ b/src/HADevice.cpp @@ -1,3 +1,5 @@ +#include + #include "HADevice.h" HADevice::HADevice(const char* uniqueId) : @@ -5,50 +7,84 @@ HADevice::HADevice(const char* uniqueId) : _manufacturer(nullptr), _model(nullptr), _name(nullptr), - _softwareVersion(nullptr), - _viaDevice(nullptr) + _softwareVersion(nullptr) { } -String HADevice::serialize() const +uint16_t HADevice::calculateSerializedLength() const { - String output; - output.concat("{"); - output.concat("{\"identifiers\":\""); - output.concat(_uniqueId); - output.concat("\"}"); + uint16_t size = + 3 + // opening and closing bracket + null terminator + strlen(_uniqueId) + 8; // 8 - length of the JSON data for this field if (_manufacturer != nullptr) { - output.concat(",{\"manufacturer\":\""); - output.concat(_manufacturer); - output.concat("\"}"); + size += strlen(_manufacturer) + 8; // 8 - length of the JSON data for this field } if (_model != nullptr) { - output.concat(",{\"model\":\""); - output.concat(_model); - output.concat("\"}"); + size += strlen(_model) + 9; // 9 - length of the JSON data for this field } if (_name != nullptr) { - output.concat(",{\"name\":\""); - output.concat(_name); - output.concat("\"}"); + size += strlen(_name) + 10; // 10 - length of the JSON data for this field } if (_softwareVersion != nullptr) { - output.concat(",{\"sw_version\":\""); - output.concat(_softwareVersion); - output.concat("\"}"); + size += strlen(_softwareVersion) + 8; // 8 - length of the JSON data for this field } - if (_viaDevice != nullptr) { - output.concat(",{\"via_device\":\""); - output.concat(_viaDevice); - output.concat("\"}"); + return size; +} + +uint16_t HADevice::serialize(char* output) const +{ + static const char QuotationSign[] PROGMEM = {"\""}; + + { + static const char DataBefore[] PROGMEM = {"{\"ids\":\""}; + + strcpy_P(output, DataBefore); + strcat(output, _uniqueId); + strcat_P(output, QuotationSign); + } + + if (_manufacturer != nullptr) { + static const char DataBefore[] PROGMEM = {",\"mf\":\""}; + + strcat_P(output, DataBefore); + strcat(output, _manufacturer); + strcat_P(output, QuotationSign); + } + + if (_model != nullptr) { + static const char DataBefore[] PROGMEM = {",\"mdl\":\""}; + + strcat_P(output, DataBefore); + strcat(output, _model); + strcat_P(output, QuotationSign); + } + + if (_name != nullptr) { + static const char DataBefore[] PROGMEM = {",\"name\":\""}; + + strcat_P(output, DataBefore); + strcat(output, _name); + strcat_P(output, QuotationSign); + } + + if (_softwareVersion != nullptr) { + static const char DataBefore[] PROGMEM = {",\"sw\":\""}; + + strcat_P(output, DataBefore); + strcat(output, _softwareVersion); + strcat_P(output, QuotationSign); + } + + { + static const char Data[] PROGMEM = {"}"}; + strcat_P(output, Data); } - output += "}"; - return output; + return strlen(output) + 1; // size with null terminator } diff --git a/src/HADevice.h b/src/HADevice.h index a4c7fd1..3d04e16 100644 --- a/src/HADevice.h +++ b/src/HADevice.h @@ -1,7 +1,7 @@ #ifndef HADEVICE_H #define HADEVICE_H -#include +#include class HADevice { @@ -23,10 +23,8 @@ public: inline void setSoftwareVersion(const char* softwareVersion) { _softwareVersion = softwareVersion; } - inline void setViaDevice(const char* viaDevice) - { _viaDevice = viaDevice; } - - String serialize() const; + uint16_t calculateSerializedLength() const; + uint16_t serialize(char* output) const; private: const char* _uniqueId; @@ -34,7 +32,6 @@ private: const char* _model; const char* _name; const char* _softwareVersion; - const char* _viaDevice; }; #endif diff --git a/src/HAMqtt.cpp b/src/HAMqtt.cpp index 51ce980..99354f2 100644 --- a/src/HAMqtt.cpp +++ b/src/HAMqtt.cpp @@ -1,53 +1,27 @@ #include -#include +#include #include "HAMqtt.h" #include "HADevice.h" #include "ArduinoHADefines.h" +#include "device-types/BaseDeviceType.h" #define HAMQTT_INIT \ _initialized(false), \ _discoveryPrefix(DefaultDiscoveryPrefix), \ - _mqtt(new MQTTClient()), \ + _mqtt(new PubSubClient(netClient)), \ _serverIp(new IPAddress()), \ _serverPort(0), \ _username(nullptr), \ _password(nullptr), \ - _connecting(false), \ - _randomNumber(0), \ - _lastConnectionAttemptAt(0) - -// Author: https://rheingoldheavy.com/better-arduino-random-values/ -uint32_t generateRandomSeed() -{ - uint8_t seedBitValue = 0; - uint8_t seedByteValue = 0; - uint32_t seedWordValue = 0; - - for (uint8_t wordShift = 0; wordShift < 4; wordShift++) - { - for (uint8_t byteShift = 0; byteShift < 8; byteShift++) - { - for (uint8_t bitSum = 0; bitSum <= 8; bitSum++) - { - seedBitValue = seedBitValue + (analogRead(A0) & 0x01); - } - - delay(1); - seedByteValue = seedByteValue | ((seedBitValue & 0x01) << byteShift); - seedBitValue = 0; - } - - seedWordValue = seedWordValue | (uint32_t)seedByteValue << (8 * wordShift); - seedByteValue = 0; - } - - return (seedWordValue); -} + _lastConnectionAttemptAt(0), \ + _devicesTypesNb(0), \ + _devicesTypes(nullptr) const char* HAMqtt::DefaultDiscoveryPrefix = "homeassistant"; -HAMqtt::HAMqtt(Client& netClient) : +HAMqtt::HAMqtt(const char* clientId, Client& netClient) : + _clientId(clientId), _netClient(netClient), _hasDevice(false), HAMQTT_INIT @@ -55,7 +29,8 @@ HAMqtt::HAMqtt(Client& netClient) : } -HAMqtt::HAMqtt(Client& netClient, HADevice& device) : +HAMqtt::HAMqtt(const char* clientId, Client& netClient, HADevice& device) : + _clientId(clientId), _netClient(netClient), _device(device), _hasDevice(true), @@ -71,7 +46,7 @@ bool HAMqtt::begin( const char* password ) { -#ifdef ARDUINOHA_DEBUG +#if defined(ARDUINOHA_DEBUG) Serial.println(F("Initializing ArduinoHA")); Serial.print(F("Server address: ")); Serial.print(serverIp); @@ -81,23 +56,22 @@ bool HAMqtt::begin( #endif if (_initialized) { -#ifdef ARDUINOHA_DEBUG +#if defined(ARDUINOHA_DEBUG) Serial.println(F("ArduinoHA is already initialized")); #endif return false; } - _mqtt->begin(serverIp, serverPort, _netClient); - *_serverIp = serverIp; _serverPort = serverPort; _username = username; _password = password; - _randomNumber = generateRandomSeed(); _initialized = true; - connectToServer(); + _mqtt->setServer(*_serverIp, _serverPort); + // _mqtt->setCallback(...); + return true; } @@ -107,9 +81,7 @@ void HAMqtt::loop() return; } - _mqtt->loop(); - - if (!_mqtt->connected()) { + if (!_mqtt->loop()) { connectToServer(); } } @@ -119,48 +91,104 @@ bool HAMqtt::isConnected() return _mqtt->connected(); } +void HAMqtt::addDeviceType(BaseDeviceType* deviceType) +{ + BaseDeviceType** data = realloc(_devicesTypes, sizeof(BaseDeviceType*) * (_devicesTypesNb + 1)); + if (data != nullptr) { + _devicesTypes = data; + _devicesTypes[_devicesTypesNb] = deviceType; + _devicesTypesNb++; + } +} + +void HAMqtt::removeDeviceType(BaseDeviceType* deviceType) +{ + // to do +} + +bool HAMqtt::publish(const char* topic, const char* payload, bool retained) +{ + if (!isConnected()) { + return false; + } + + #if defined(ARDUINOHA_DEBUG) + Serial.print(F("Publishing message with topic: ")); + Serial.print(topic); + Serial.print(F(", payload length: ")); + Serial.print(strlen(payload)); + Serial.println(); + #endif + + _mqtt->beginPublish(topic, strlen(payload), retained); + _mqtt->write(payload, strlen(payload)); + return _mqtt->endPublish(); +} + +bool HAMqtt::beginPublish( + const char* topic, + uint16_t payloadLength, + bool retained +) +{ + #if defined(ARDUINOHA_DEBUG) + Serial.print(F("Publishing message with topic: ")); + Serial.print(topic); + Serial.print(F(", payload length: ")); + Serial.print(payloadLength); + Serial.println(); + #endif + + return _mqtt->beginPublish(topic, payloadLength, retained); +} + +bool HAMqtt::writePayload(const char* data, uint16_t length) +{ + return (_mqtt->write(data, length) > 0); +} + +bool HAMqtt::endPublish() +{ + return _mqtt->endPublish(); +} + void HAMqtt::connectToServer() { if (_lastConnectionAttemptAt > 0 && - abs(millis() - _lastConnectionAttemptAt) < ReconnectInterval) { + (millis() - _lastConnectionAttemptAt) < ReconnectInterval) { return; } - bool result; - String clientId = generateClientId(); + _lastConnectionAttemptAt = millis(); - #ifdef ARDUINOHA_DEBUG - Serial.print("Connecting to the MQTT broker... Client ID: "); - Serial.print(clientId); + #if defined(ARDUINOHA_DEBUG) + Serial.print(F("Connecting to the MQTT broker... Client ID: ")); + Serial.print(_clientId); Serial.println(); #endif if (_username == nullptr || _password == nullptr) { - result = _mqtt->connect(clientId.c_str()); + _mqtt->connect(_clientId); } else { - result = _mqtt->connect(clientId.c_str(), _username, _password); + _mqtt->connect(_clientId, _username, _password); } - if (result) { -#ifdef ARDUINOHA_DEBUG + if (isConnected()) { +#if defined(ARDUINOHA_DEBUG) Serial.println(F("Connected to the broker")); #endif - // to do: discovery + onConnected(); } else { -#ifdef ARDUINOHA_DEBUG +#if defined(ARDUINOHA_DEBUG) Serial.println(F("Failed to connect to the broker")); #endif } - - _lastConnectionAttemptAt = millis(); } -String HAMqtt::generateClientId() const +void HAMqtt::onConnected() { - if (_hasDevice) { - //return _device.getUniqueId(); + for (uint8_t i = 0; i < _devicesTypesNb; i++) { + _devicesTypes[i]->onMqttConnected(); } - - return "aha-" + String(_randomNumber, DEC); } diff --git a/src/HAMqtt.h b/src/HAMqtt.h index d2830ca..8eb2bb3 100644 --- a/src/HAMqtt.h +++ b/src/HAMqtt.h @@ -1,10 +1,13 @@ #ifndef HAMQTT_H #define HAMQTT_H +#include + class IPAddress; class Client; -class MQTTClient; +class PubSubClient; class HADevice; +class BaseDeviceType; class HAMqtt { @@ -12,8 +15,28 @@ public: static const char* DefaultDiscoveryPrefix; static const uint16_t ReconnectInterval = 3000; // ms - HAMqtt(Client& netClient); - HAMqtt(Client& netClient, HADevice& device); + HAMqtt(const char* clientId, Client& netClient); + HAMqtt(const char* clientId, Client& netClient, HADevice& device); + + /** + * Sets prefix for Home Assistant discovery. + * It needs to match prefix set in the HA admin panel. + * The default prefix is "homeassistant". + */ + inline void setDiscoveryPrefix(const char* prefix) + { _discoveryPrefix = prefix; } + + /** + * Returns discovery prefix. + */ + inline const char* getDiscoveryPrefix() const + { return _discoveryPrefix; } + + /** + * Returns instance of the device assigned to the HAMqtt class. + */ + inline HADevice const* getDevice() const + { return (_hasDevice ? &_device : nullptr); } /** * Sets parameters of the connection to the MQTT broker. @@ -27,7 +50,7 @@ public: */ bool begin( const IPAddress& serverIp, - const uint16_t& serverPort, + const uint16_t& serverPort = 1883, const char* username = nullptr, const char* password = nullptr ); @@ -43,30 +66,60 @@ public: bool isConnected(); /** - * Sets prefix for Home Assistant discovery. - * It needs to match prefix set in the HA admin panel. - * The default prefix is "homeassistant". + * 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. + * + * @param deviceType Instance of the device's type (eg. HATriggers). */ - inline void setDiscoveryPrefix(const char* prefix) - { _discoveryPrefix = prefix; } + void addDeviceType(BaseDeviceType* deviceType); + + /** + * Removes device's type from the MQTT. + */ + void removeDeviceType(BaseDeviceType* deviceType); + + /** + * Publishes MQTT message with given topic and payload. + * Message won't be published if connection with MQTT broker is not established. + * In this case method returns false. + * + * @param topic Topic to publish. + * @param payload Payload to publish (it may be empty const char). + * @param retained Determines whether message should be retained. + */ + bool publish(const char* topic, const char* payload, bool retained = false); + + bool beginPublish(const char* topic, uint16_t payloadLength, bool retained = false); + bool writePayload(const char* data, uint16_t length); + bool endPublish(); private: + /** + * Attempts to connect to the MQTT broker. + * The method uses properties passed to the "begin" method. + */ void connectToServer(); - String generateClientId() const; + /** + * This method is called each time the connection with MQTT broker is acquired. + */ + void onConnected(); + + const char* _clientId; Client& _netClient; HADevice& _device; bool _hasDevice; bool _initialized; const char* _discoveryPrefix; - MQTTClient* _mqtt; + PubSubClient* _mqtt; IPAddress* _serverIp; uint16_t _serverPort; const char* _username; const char* _password; - bool _connecting; - uint32_t _randomNumber; uint32_t _lastConnectionAttemptAt; + uint8_t _devicesTypesNb; + BaseDeviceType** _devicesTypes; }; #endif