From e6431807e0840334556913be523b7ae8f951acef Mon Sep 17 00:00:00 2001 From: BlueAndi Date: Sun, 5 Jan 2025 15:53:15 +0100 Subject: [PATCH] Allow topic registration with empty entity-id. HA discovery topic and id's generation is like the following patterns: DISCOVERY-TOPIC = DISCOVERY-PREFIX/COMPONENT/NODE-ID/OBJECT-ID/config NODE-ID = DEVICE-ID with "/" and "." replaced by "_" OBJECT-ID = [ENTITY-ID/]TOPIC with "/" and "." replaced by "_" UNIQUE-ID = DEVICE-ID/[ENTITY-ID/]TOPIC --- data/js/rest.js | 2 +- lib/CountdownPlugin/src/CountdownPlugin.cpp | 2 +- lib/CountdownPlugin/src/CountdownPlugin.h | 8 +- lib/DateTimePlugin/src/DateTimePlugin.cpp | 2 +- lib/DateTimePlugin/src/DateTimePlugin.h | 8 +- lib/FileMgrService/src/FileMgrService.cpp | 4 +- .../src/GrabViaMqttPlugin.cpp | 2 +- lib/GrabViaMqttPlugin/src/GrabViaMqttPlugin.h | 8 +- .../src/GrabViaRestPlugin.cpp | 2 +- lib/GrabViaRestPlugin/src/GrabViaRestPlugin.h | 8 +- lib/GruenbeckPlugin/src/GruenbeckPlugin.cpp | 2 +- lib/GruenbeckPlugin/src/GruenbeckPlugin.h | 8 +- .../src/IconTextLampPlugin.cpp | 6 +- .../src/IconTextLampPlugin.h | 8 +- lib/IconTextPlugin/src/IconTextPlugin.cpp | 2 +- lib/IconTextPlugin/src/IconTextPlugin.h | 8 +- lib/JustTextPlugin/src/JustTextPlugin.cpp | 2 +- lib/JustTextPlugin/src/JustTextPlugin.h | 8 +- .../src/HomeAssistantMqtt.cpp | 40 ++++- .../src/HomeAssistantMqtt.h | 27 ++- .../src/MqttApiTopicHandler.cpp | 163 +++++++++--------- .../src/MqttApiTopicHandler.h | 15 ++ lib/MultiIconPlugin/src/MultiIconPlugin.cpp | 4 +- lib/MultiIconPlugin/src/MultiIconPlugin.h | 8 +- .../src/OpenWeatherPlugin.cpp | 2 +- lib/OpenWeatherPlugin/src/OpenWeatherPlugin.h | 8 +- lib/Plugin/src/IPluginMaintenance.hpp | 8 +- lib/Plugin/src/Plugin.hpp | 8 +- .../src/RestApiTopicHandler.cpp | 121 ++++++------- .../src/RestApiTopicHandler.h | 52 +++--- lib/SensorPlugin/src/SensorPlugin.cpp | 2 +- lib/SensorPlugin/src/SensorPlugin.h | 8 +- .../src/SignalDetectorPlugin.cpp | 2 +- .../src/SignalDetectorPlugin.h | 8 +- .../src/SoundReactivePlugin.cpp | 2 +- .../src/SoundReactivePlugin.h | 8 +- lib/SunrisePlugin/src/SunrisePlugin.cpp | 2 +- lib/SunrisePlugin/src/SunrisePlugin.h | 8 +- lib/TimerService/src/TimerService.cpp | 2 +- .../src/TopicHandlerService.cpp | 3 - .../src/TopicHandlerService.h | 2 +- lib/VolumioPlugin/src/VolumioPlugin.cpp | 2 +- lib/VolumioPlugin/src/VolumioPlugin.h | 8 +- src/Hal/SensorDataProvider.cpp | 4 +- src/Topics/Topics.cpp | 16 +- 45 files changed, 351 insertions(+), 272 deletions(-) diff --git a/data/js/rest.js b/data/js/rest.js index ea04e82b..0594f0e9 100644 --- a/data/js/rest.js +++ b/data/js/rest.js @@ -232,7 +232,7 @@ pixelix.rest.Client.prototype.setSetting = function(key, value) { pixelix.rest.Client.prototype.restart = function() { return utils.makeRequest({ method: "POST", - url: "/rest/api/v1/device/restart", + url: "/rest/api/v1/restart", isJsonResponse: true }); }; diff --git a/lib/CountdownPlugin/src/CountdownPlugin.cpp b/lib/CountdownPlugin/src/CountdownPlugin.cpp index 4b762ff7..97b7fd17 100644 --- a/lib/CountdownPlugin/src/CountdownPlugin.cpp +++ b/lib/CountdownPlugin/src/CountdownPlugin.cpp @@ -59,7 +59,7 @@ *****************************************************************************/ /* Initialize plugin topic. */ -const char* CountdownPlugin::TOPIC_CONFIG = "/countdown"; +const char* CountdownPlugin::TOPIC_CONFIG = "countdown"; /****************************************************************************** * Public Methods diff --git a/lib/CountdownPlugin/src/CountdownPlugin.h b/lib/CountdownPlugin/src/CountdownPlugin.h index 181555bd..e9199455 100644 --- a/lib/CountdownPlugin/src/CountdownPlugin.h +++ b/lib/CountdownPlugin/src/CountdownPlugin.h @@ -251,7 +251,7 @@ class CountdownPlugin : public PluginWithConfig * {.json} * { * "topics": [ - * "/text" + * "text" * ] * } * @@ -267,7 +267,7 @@ class CountdownPlugin : public PluginWithConfig * {.json} * { * "topics": [{ - * "name": "/text", + * "name": "text", * "access": "r" * }] * } @@ -278,7 +278,7 @@ class CountdownPlugin : public PluginWithConfig * {.json} * { * "topics": [{ - * "name": "/text", + * "name": "text", * "extra": { * "ha": { * ... everything here will be used for MQTT discovery ... @@ -293,7 +293,7 @@ class CountdownPlugin : public PluginWithConfig * {.json} * { * "topics": [{ - * "name": "/text", + * "name": "text", * "extra": "extra.json" * }] * } diff --git a/lib/DateTimePlugin/src/DateTimePlugin.cpp b/lib/DateTimePlugin/src/DateTimePlugin.cpp index caca06ee..7848ec59 100644 --- a/lib/DateTimePlugin/src/DateTimePlugin.cpp +++ b/lib/DateTimePlugin/src/DateTimePlugin.cpp @@ -59,7 +59,7 @@ *****************************************************************************/ /* Initialize plugin topic. */ -const char DateTimePlugin::TOPIC_CONFIG[] = "/dateTime"; +const char DateTimePlugin::TOPIC_CONFIG[] = "dateTime"; /* Initialize default time format. */ const char DateTimePlugin::TIME_FORMAT_DEFAULT[] = "%I:%M %p"; diff --git a/lib/DateTimePlugin/src/DateTimePlugin.h b/lib/DateTimePlugin/src/DateTimePlugin.h index 727ad84d..edb53d8c 100644 --- a/lib/DateTimePlugin/src/DateTimePlugin.h +++ b/lib/DateTimePlugin/src/DateTimePlugin.h @@ -144,7 +144,7 @@ class DateTimePlugin : public PluginWithConfig * {.json} * { * "topics": [ - * "/text" + * "text" * ] * } * @@ -160,7 +160,7 @@ class DateTimePlugin : public PluginWithConfig * {.json} * { * "topics": [{ - * "name": "/text", + * "name": "text", * "access": "r" * }] * } @@ -171,7 +171,7 @@ class DateTimePlugin : public PluginWithConfig * {.json} * { * "topics": [{ - * "name": "/text", + * "name": "text", * "extra": { * "ha": { * ... everything here will be used for MQTT discovery ... @@ -186,7 +186,7 @@ class DateTimePlugin : public PluginWithConfig * {.json} * { * "topics": [{ - * "name": "/text", + * "name": "text", * "extra": "extra.json" * }] * } diff --git a/lib/FileMgrService/src/FileMgrService.cpp b/lib/FileMgrService/src/FileMgrService.cpp index 5bec8e67..6fab4217 100644 --- a/lib/FileMgrService/src/FileMgrService.cpp +++ b/lib/FileMgrService/src/FileMgrService.cpp @@ -65,8 +65,8 @@ const char* FileMgrService::WORKING_DIRECTORY = "/configuration"; const char* FileMgrService::CONFIG_FILE_NAME = "fileMgr.json"; const char* FileMgrService::ENTITY_ID = "files"; -const char* FileMgrService::TOPIC_UPLOAD = "/upload"; -const char* FileMgrService::TOPIC_REMOVE = "/remove"; +const char* FileMgrService::TOPIC_UPLOAD = "upload"; +const char* FileMgrService::TOPIC_REMOVE = "remove"; const char* FileMgrService::FILE_EXTENSIONS[] = { ".bmp", diff --git a/lib/GrabViaMqttPlugin/src/GrabViaMqttPlugin.cpp b/lib/GrabViaMqttPlugin/src/GrabViaMqttPlugin.cpp index b4f341eb..db0559fa 100644 --- a/lib/GrabViaMqttPlugin/src/GrabViaMqttPlugin.cpp +++ b/lib/GrabViaMqttPlugin/src/GrabViaMqttPlugin.cpp @@ -59,7 +59,7 @@ *****************************************************************************/ /* Initialize plugin topic. */ -const char* GrabViaMqttPlugin::TOPIC_CONFIG = "/grabConfig"; +const char* GrabViaMqttPlugin::TOPIC_CONFIG = "grabConfig"; /****************************************************************************** * Public Methods diff --git a/lib/GrabViaMqttPlugin/src/GrabViaMqttPlugin.h b/lib/GrabViaMqttPlugin/src/GrabViaMqttPlugin.h index 4ddf73fd..e635a7e6 100644 --- a/lib/GrabViaMqttPlugin/src/GrabViaMqttPlugin.h +++ b/lib/GrabViaMqttPlugin/src/GrabViaMqttPlugin.h @@ -140,7 +140,7 @@ class GrabViaMqttPlugin : public PluginWithConfig * {.json} * { * "topics": [ - * "/text" + * "text" * ] * } * @@ -156,7 +156,7 @@ class GrabViaMqttPlugin : public PluginWithConfig * {.json} * { * "topics": [{ - * "name": "/text", + * "name": "text", * "access": "r" * }] * } @@ -167,7 +167,7 @@ class GrabViaMqttPlugin : public PluginWithConfig * {.json} * { * "topics": [{ - * "name": "/text", + * "name": "text", * "extra": { * "ha": { * ... everything here will be used for MQTT discovery ... @@ -182,7 +182,7 @@ class GrabViaMqttPlugin : public PluginWithConfig * {.json} * { * "topics": [{ - * "name": "/text", + * "name": "text", * "extra": "extra.json" * }] * } diff --git a/lib/GrabViaRestPlugin/src/GrabViaRestPlugin.cpp b/lib/GrabViaRestPlugin/src/GrabViaRestPlugin.cpp index 94583d48..fac22208 100644 --- a/lib/GrabViaRestPlugin/src/GrabViaRestPlugin.cpp +++ b/lib/GrabViaRestPlugin/src/GrabViaRestPlugin.cpp @@ -59,7 +59,7 @@ *****************************************************************************/ /* Initialize plugin topic. */ -const char* GrabViaRestPlugin::TOPIC_CONFIG = "/grabConfig"; +const char* GrabViaRestPlugin::TOPIC_CONFIG = "grabConfig"; /****************************************************************************** * Public Methods diff --git a/lib/GrabViaRestPlugin/src/GrabViaRestPlugin.h b/lib/GrabViaRestPlugin/src/GrabViaRestPlugin.h index ada88dfd..f7127974 100644 --- a/lib/GrabViaRestPlugin/src/GrabViaRestPlugin.h +++ b/lib/GrabViaRestPlugin/src/GrabViaRestPlugin.h @@ -158,7 +158,7 @@ class GrabViaRestPlugin : public PluginWithConfig * {.json} * { * "topics": [ - * "/text" + * "text" * ] * } * @@ -174,7 +174,7 @@ class GrabViaRestPlugin : public PluginWithConfig * {.json} * { * "topics": [{ - * "name": "/text", + * "name": "text", * "access": "r" * }] * } @@ -185,7 +185,7 @@ class GrabViaRestPlugin : public PluginWithConfig * {.json} * { * "topics": [{ - * "name": "/text", + * "name": "text", * "extra": { * "ha": { * ... everything here will be used for MQTT discovery ... @@ -200,7 +200,7 @@ class GrabViaRestPlugin : public PluginWithConfig * {.json} * { * "topics": [{ - * "name": "/text", + * "name": "text", * "extra": "extra.json" * }] * } diff --git a/lib/GruenbeckPlugin/src/GruenbeckPlugin.cpp b/lib/GruenbeckPlugin/src/GruenbeckPlugin.cpp index 3e73b42a..acc92db0 100644 --- a/lib/GruenbeckPlugin/src/GruenbeckPlugin.cpp +++ b/lib/GruenbeckPlugin/src/GruenbeckPlugin.cpp @@ -60,7 +60,7 @@ *****************************************************************************/ /* Initialize plugin topic. */ -const char* GruenbeckPlugin::TOPIC_CONFIG = "/ipAddress"; +const char* GruenbeckPlugin::TOPIC_CONFIG = "ipAddress"; /****************************************************************************** * Public Methods diff --git a/lib/GruenbeckPlugin/src/GruenbeckPlugin.h b/lib/GruenbeckPlugin/src/GruenbeckPlugin.h index ab0d9723..21ca637f 100644 --- a/lib/GruenbeckPlugin/src/GruenbeckPlugin.h +++ b/lib/GruenbeckPlugin/src/GruenbeckPlugin.h @@ -153,7 +153,7 @@ class GruenbeckPlugin : public PluginWithConfig * {.json} * { * "topics": [ - * "/text" + * "text" * ] * } * @@ -169,7 +169,7 @@ class GruenbeckPlugin : public PluginWithConfig * {.json} * { * "topics": [{ - * "name": "/text", + * "name": "text", * "access": "r" * }] * } @@ -180,7 +180,7 @@ class GruenbeckPlugin : public PluginWithConfig * {.json} * { * "topics": [{ - * "name": "/text", + * "name": "text", * "extra": { * "ha": { * ... everything here will be used for MQTT discovery ... @@ -195,7 +195,7 @@ class GruenbeckPlugin : public PluginWithConfig * {.json} * { * "topics": [{ - * "name": "/text", + * "name": "text", * "extra": "extra.json" * }] * } diff --git a/lib/IconTextLampPlugin/src/IconTextLampPlugin.cpp b/lib/IconTextLampPlugin/src/IconTextLampPlugin.cpp index 228bdb54..92748430 100644 --- a/lib/IconTextLampPlugin/src/IconTextLampPlugin.cpp +++ b/lib/IconTextLampPlugin/src/IconTextLampPlugin.cpp @@ -59,14 +59,14 @@ *****************************************************************************/ /* Initialize plugin topic. */ -const char* IconTextLampPlugin::TOPIC_TEXT = "/iconText"; +const char* IconTextLampPlugin::TOPIC_TEXT = "iconText"; const char* IconTextLampPlugin::TOPIC_TEXT_EXTRA_FILE_NAME = "/extra/iconTextLampPlugin.json"; /* Initialize plugin topic. */ -const char* IconTextLampPlugin::TOPIC_LAMPS = "/lamps"; +const char* IconTextLampPlugin::TOPIC_LAMPS = "lamps"; /* Initialize plugin topic. */ -const char* IconTextLampPlugin::TOPIC_LAMP = "/lamp"; +const char* IconTextLampPlugin::TOPIC_LAMP = "lamp"; /****************************************************************************** * Public Methods diff --git a/lib/IconTextLampPlugin/src/IconTextLampPlugin.h b/lib/IconTextLampPlugin/src/IconTextLampPlugin.h index f2e31dd0..2472f329 100644 --- a/lib/IconTextLampPlugin/src/IconTextLampPlugin.h +++ b/lib/IconTextLampPlugin/src/IconTextLampPlugin.h @@ -117,7 +117,7 @@ class IconTextLampPlugin : public PluginWithConfig * {.json} * { * "topics": [ - * "/text" + * "text" * ] * } * @@ -133,7 +133,7 @@ class IconTextLampPlugin : public PluginWithConfig * {.json} * { * "topics": [{ - * "name": "/text", + * "name": "text", * "access": "r" * }] * } @@ -144,7 +144,7 @@ class IconTextLampPlugin : public PluginWithConfig * {.json} * { * "topics": [{ - * "name": "/text", + * "name": "text", * "extra": { * "ha": { * ... everything here will be used for MQTT discovery ... @@ -159,7 +159,7 @@ class IconTextLampPlugin : public PluginWithConfig * {.json} * { * "topics": [{ - * "name": "/text", + * "name": "text", * "extra": "extra.json" * }] * } diff --git a/lib/IconTextPlugin/src/IconTextPlugin.cpp b/lib/IconTextPlugin/src/IconTextPlugin.cpp index 1f0e40b4..046ba0eb 100644 --- a/lib/IconTextPlugin/src/IconTextPlugin.cpp +++ b/lib/IconTextPlugin/src/IconTextPlugin.cpp @@ -59,7 +59,7 @@ *****************************************************************************/ /* Initialize plugin topic. */ -const char* IconTextPlugin::TOPIC_TEXT = "/iconText"; +const char* IconTextPlugin::TOPIC_TEXT = "iconText"; const char* IconTextPlugin::TOPIC_TEXT_EXTRA_FILE_NAME = "/extra/iconTextPlugin.json"; /****************************************************************************** diff --git a/lib/IconTextPlugin/src/IconTextPlugin.h b/lib/IconTextPlugin/src/IconTextPlugin.h index d386701f..e97dbdd0 100644 --- a/lib/IconTextPlugin/src/IconTextPlugin.h +++ b/lib/IconTextPlugin/src/IconTextPlugin.h @@ -145,7 +145,7 @@ class IconTextPlugin : public PluginWithConfig * {.json} * { * "topics": [ - * "/text" + * "text" * ] * } * @@ -161,7 +161,7 @@ class IconTextPlugin : public PluginWithConfig * {.json} * { * "topics": [{ - * "name": "/text", + * "name": "text", * "access": "r" * }] * } @@ -172,7 +172,7 @@ class IconTextPlugin : public PluginWithConfig * {.json} * { * "topics": [{ - * "name": "/text", + * "name": "text", * "extra": { * "ha": { * ... everything here will be used for MQTT discovery ... @@ -187,7 +187,7 @@ class IconTextPlugin : public PluginWithConfig * {.json} * { * "topics": [{ - * "name": "/text", + * "name": "text", * "extra": "extra.json" * }] * } diff --git a/lib/JustTextPlugin/src/JustTextPlugin.cpp b/lib/JustTextPlugin/src/JustTextPlugin.cpp index a90e553b..16c9f52e 100644 --- a/lib/JustTextPlugin/src/JustTextPlugin.cpp +++ b/lib/JustTextPlugin/src/JustTextPlugin.cpp @@ -58,7 +58,7 @@ *****************************************************************************/ /* Initialize plugin topic. */ -const char* JustTextPlugin::TOPIC_TEXT = "/text"; +const char* JustTextPlugin::TOPIC_TEXT = "text"; const char* JustTextPlugin::TOPIC_TEXT_EXTRA_FILE_NAME = "/extra/justTextPlugin.json"; /****************************************************************************** diff --git a/lib/JustTextPlugin/src/JustTextPlugin.h b/lib/JustTextPlugin/src/JustTextPlugin.h index f4edd7e3..b9eceab5 100644 --- a/lib/JustTextPlugin/src/JustTextPlugin.h +++ b/lib/JustTextPlugin/src/JustTextPlugin.h @@ -142,7 +142,7 @@ class JustTextPlugin : public PluginWithConfig * {.json} * { * "topics": [ - * "/text" + * "text" * ] * } * @@ -158,7 +158,7 @@ class JustTextPlugin : public PluginWithConfig * {.json} * { * "topics": [{ - * "name": "/text", + * "name": "text", * "access": "r" * }] * } @@ -169,7 +169,7 @@ class JustTextPlugin : public PluginWithConfig * {.json} * { * "topics": [{ - * "name": "/text", + * "name": "text", * "extra": { * "ha": { * ... everything here will be used for MQTT discovery ... @@ -184,7 +184,7 @@ class JustTextPlugin : public PluginWithConfig * {.json} * { * "topics": [{ - * "name": "/text", + * "name": "text", * "extra": "extra.json" * }] * } diff --git a/lib/MqttApiTopicHandler/src/HomeAssistantMqtt.cpp b/lib/MqttApiTopicHandler/src/HomeAssistantMqtt.cpp index 087fe4d9..29fa9b56 100644 --- a/lib/MqttApiTopicHandler/src/HomeAssistantMqtt.cpp +++ b/lib/MqttApiTopicHandler/src/HomeAssistantMqtt.cpp @@ -129,7 +129,7 @@ void HomeAssistantMqtt::process(bool isConnected) m_isConnected = isConnected; } -void HomeAssistantMqtt::registerMqttDiscovery(const String& deviceId, const String& entityId, const String& stateTopic, const String& cmdTopic, const String& availabilityTopic, JsonObjectConst& extra) +void HomeAssistantMqtt::registerMqttDiscovery(const String& deviceId, const String& entityId, const String& topic, const String& stateTopic, const String& cmdTopic, const String& availabilityTopic, JsonObjectConst& extra) { /* The Home Assistant discovery must be enabled and the prefix must be available, otherwise this * feature is disabled. @@ -157,8 +157,8 @@ void HomeAssistantMqtt::registerMqttDiscovery(const String& deviceId, const Stri if (nullptr != mqttDiscoveryInfo) { mqttDiscoveryInfo->component = jsonComponent.as(); - mqttDiscoveryInfo->nodeId = deviceId; - mqttDiscoveryInfo->objectId = getObjectId(entityId); + mqttDiscoveryInfo->nodeId = getNodeId(deviceId); + mqttDiscoveryInfo->objectId = getObjectId(entityId, topic); mqttDiscoveryInfo->discoveryDetails.set(jsonDiscovery); /* Deep copy, because discovery details are handled in different context. */ /* Readable topic? */ @@ -186,7 +186,7 @@ void HomeAssistantMqtt::registerMqttDiscovery(const String& deviceId, const Stri } } -void HomeAssistantMqtt::unregisterMqttDiscovery(const String& deviceId, const String& entityId, const String& stateTopic, const String& cmdTopic) +void HomeAssistantMqtt::unregisterMqttDiscovery(const String& deviceId, const String& entityId, const String& topic, const String& stateTopic, const String& cmdTopic) { /* The Home Assistant discovery must be enabled and the prefix must be available, otherwise this * feature is disabled. @@ -195,7 +195,7 @@ void HomeAssistantMqtt::unregisterMqttDiscovery(const String& deviceId, const St (false == m_haDiscoveryPrefix.isEmpty())) { ListOfMqttDiscoveryInfo::iterator listOfMqttDiscoveryInfoIt = m_mqttDiscoveryInfoList.begin(); - String objectId = getObjectId(entityId); + String objectId = getObjectId(entityId, topic); while (m_mqttDiscoveryInfoList.end() != listOfMqttDiscoveryInfoIt) { @@ -282,11 +282,35 @@ void HomeAssistantMqtt::unregisterMqttDiscovery(const String& deviceId, const St * Private Methods *****************************************************************************/ -String HomeAssistantMqtt::getObjectId(const String& entityId) +String HomeAssistantMqtt::getNodeId(const String& deviceId) { - String objectId = entityId; + String nodeId = deviceId; - /* Home Assistant MQTT discovery doesn't allow '/' and '.' in the object id. */ + /* Home Assistant MQTT discovery doesn't allow '/' and '.' in the node id. + * See https://www.home-assistant.io/integrations/mqtt#discovery-messages + */ + nodeId.replace('/', '_'); + nodeId.replace('.', '_'); + + return nodeId; +} + +String HomeAssistantMqtt::getObjectId(const String& entityId, const String& topic) +{ + String objectId; + + if (false == entityId.isEmpty()) + { + objectId = entityId + "/" + topic; + } + else + { + objectId = topic; + } + + /* Home Assistant MQTT discovery doesn't allow '/' and '.' in the object id. + * See https://www.home-assistant.io/integrations/mqtt#discovery-messages + */ objectId.replace('/', '_'); objectId.replace('.', '_'); diff --git a/lib/MqttApiTopicHandler/src/HomeAssistantMqtt.h b/lib/MqttApiTopicHandler/src/HomeAssistantMqtt.h index 3377b43c..2e3ab682 100644 --- a/lib/MqttApiTopicHandler/src/HomeAssistantMqtt.h +++ b/lib/MqttApiTopicHandler/src/HomeAssistantMqtt.h @@ -107,15 +107,23 @@ class HomeAssistantMqtt * Register Home Assistant MQTT discovery. * It will not publish, just prepare the MQTT discovery information * and hold it internally. + * + * The MQTT discovery topic is generated by the following pattern: + * DISCOVERY-TOPIC = DISCOVERY-PREFIX/COMPONENT/NODE-ID/OBJECT-ID/config * + * NODE-ID = DEVICE-ID with "/" and "." replaced by "_" + * OBJECT-ID = [ENTITY-ID/]TOPIC with "/" and "." replaced by "_" + * UNIQUE-ID = DEVICE-ID/[ENTITY-ID/]TOPIC + * * @param[in] deviceId Device id. * @param[in] entityId Entity id. + * @param[in] topic The topic. * @param[in] stateTopic The MQTT status topic. * @param[in] cmdTopic The MQTT command topic. * @param[in] availabilityTopic The MQTT availability topic. * @param[in] extra Extra parameters used by this extension. */ - void registerMqttDiscovery(const String& deviceId, const String& entityId, const String& stateTopic, const String& cmdTopic, const String& availabilityTopic, JsonObjectConst& extra); + void registerMqttDiscovery(const String& deviceId, const String& entityId, const String& topic, const String& stateTopic, const String& cmdTopic, const String& availabilityTopic, JsonObjectConst& extra); /** * Unregister Home Assistant MQTT discovery. @@ -124,10 +132,11 @@ class HomeAssistantMqtt * * @param[in] deviceId Device id. * @param[in] entityId Entity id. + * @param[in] topic The topic. * @param[in] stateTopic The MQTT status topic. * @param[in] cmdTopic The MQTT command topic. */ - void unregisterMqttDiscovery(const String& deviceId, const String& entityId, const String& stateTopic, const String& cmdTopic); + void unregisterMqttDiscovery(const String& deviceId, const String& entityId, const String& topic, const String& stateTopic, const String& cmdTopic); private: @@ -191,13 +200,23 @@ class HomeAssistantMqtt HomeAssistantMqtt& operator=(const HomeAssistantMqtt& ext); /** - * Get the object id from the entity id. + * Get the node id. + * + * @param[in] deviceId The device id. + * + * @return Object id + */ + String getNodeId(const String& deviceId); + + /** + * Get the object id. * * @param[in] entityId The entity id. + * @param[in] topic The topic. * * @return Object id */ - String getObjectId(const String& entityId); + String getObjectId(const String& entityId, const String& topic); /** * Clear MQTT discovery info list. diff --git a/lib/MqttApiTopicHandler/src/MqttApiTopicHandler.cpp b/lib/MqttApiTopicHandler/src/MqttApiTopicHandler.cpp index 5550c50e..ceb0955a 100644 --- a/lib/MqttApiTopicHandler/src/MqttApiTopicHandler.cpp +++ b/lib/MqttApiTopicHandler/src/MqttApiTopicHandler.cpp @@ -86,31 +86,30 @@ void MqttApiTopicHandler::stop() void MqttApiTopicHandler::registerTopic(const String& deviceId, const String& entityId, const String& topic, JsonObjectConst& extra, GetTopicFunc getTopicFunc, SetTopicFunc setTopicFunc, UploadReqFunc uploadReqFunc) { if ((false == deviceId.isEmpty()) && - (false == entityId.isEmpty()) && (false == topic.isEmpty())) { - String mqttTopicNameBase = deviceId + "/" + entityId + topic; - TopicState* topicState = new(std::nothrow) TopicState(); + String mqttTopicBase = getMqttBaseTopic(deviceId, entityId, topic); + TopicState* topicState = new (std::nothrow) TopicState(); - LOG_INFO("Register: %s", mqttTopicNameBase.c_str()); + LOG_INFO("Register: %s", mqttTopicBase.c_str()); if (nullptr != topicState) { - String topicUriReadable; - String topicUriWriteable; + String mqttTopicReadable; + String mqttTopicWriteable; - topicState->deviceId = deviceId; - topicState->entityId = entityId; - topicState->topic = topic; - topicState->getTopicFunc = getTopicFunc; - topicState->setTopicFunc = setTopicFunc; - topicState->uploadReqFunc = uploadReqFunc; - topicState->isPublishReq = false; + topicState->deviceId = deviceId; + topicState->entityId = entityId; + topicState->topic = topic; + topicState->getTopicFunc = getTopicFunc; + topicState->setTopicFunc = setTopicFunc; + topicState->uploadReqFunc = uploadReqFunc; + topicState->isPublishReq = false; /* Is the topic readable? */ if (nullptr != getTopicFunc) { - topicUriReadable = mqttTopicNameBase + MQTT_ENDPOINT_READ_ACCESS; + mqttTopicReadable = mqttTopicBase + MQTT_ENDPOINT_READ_ACCESS; /* Publish initially. */ topicState->isPublishReq = true; @@ -119,30 +118,29 @@ void MqttApiTopicHandler::registerTopic(const String& deviceId, const String& en /* Is the topic writeable? */ if (nullptr != setTopicFunc) { - MqttService& mqttService = MqttService::getInstance(); - MqttService::TopicCallback setCallback = - [this, topicState](const String& mqttTopic, const uint8_t* payload, size_t size) - { + MqttService& mqttService = MqttService::getInstance(); + MqttService::TopicCallback setCallback = + [this, topicState](const String& mqttTopic, const uint8_t* payload, size_t size) { if (0U != mqttTopic.endsWith(topicState->topic + MQTT_ENDPOINT_WRITE_ACCESS)) { this->write(topicState->deviceId, topicState->entityId, topicState->topic, payload, size, topicState->setTopicFunc, topicState->uploadReqFunc); } }; - topicUriWriteable = mqttTopicNameBase + MQTT_ENDPOINT_WRITE_ACCESS; + mqttTopicWriteable = mqttTopicBase + MQTT_ENDPOINT_WRITE_ACCESS; - LOG_INFO("Subscribe: %s", topicUriWriteable.c_str()); - if (false == mqttService.subscribe(topicUriWriteable, setCallback)) + LOG_INFO("Subscribe: %s", mqttTopicWriteable.c_str()); + if (false == mqttService.subscribe(mqttTopicWriteable, setCallback)) { - LOG_WARNING("Couldn't subscribe %s.", topicUriWriteable.c_str()); + LOG_WARNING("Couldn't subscribe %s.", mqttTopicWriteable.c_str()); } } /* Handle Home Assistant extension */ { - String willTopic = deviceId + "/status"; + String willTopic = deviceId + "/status"; /* See MqttService how the last will shall look like. */ - m_haExtension.registerMqttDiscovery(deviceId, entityId, topicUriReadable, topicUriWriteable, willTopic, extra); + m_haExtension.registerMqttDiscovery(deviceId, entityId, topic, mqttTopicReadable, mqttTopicWriteable, willTopic, extra); } m_listOfTopicStates.push_back(topicState); @@ -153,16 +151,15 @@ void MqttApiTopicHandler::registerTopic(const String& deviceId, const String& en void MqttApiTopicHandler::unregisterTopic(const String& deviceId, const String& entityId, const String& topic, bool purge) { if ((false == deviceId.isEmpty()) && - (false == entityId.isEmpty()) && (false == topic.isEmpty())) { - String mqttTopicNameBase = deviceId + "/" + entityId + topic; - MqttService& mqttService = MqttService::getInstance(); - ListOfTopicStates::iterator topicStateIt = m_listOfTopicStates.begin(); + String mqttTopicBase = getMqttBaseTopic(deviceId, entityId, topic); + MqttService& mqttService = MqttService::getInstance(); + ListOfTopicStates::iterator topicStateIt = m_listOfTopicStates.begin(); - LOG_INFO("Unregister: %s", mqttTopicNameBase.c_str()); + LOG_INFO("Unregister: %s", mqttTopicBase.c_str()); - while(m_listOfTopicStates.end() != topicStateIt) + while (m_listOfTopicStates.end() != topicStateIt) { TopicState* topicState = *topicStateIt; @@ -171,41 +168,41 @@ void MqttApiTopicHandler::unregisterTopic(const String& deviceId, const String& (entityId == topicState->entityId) && (topic == topicState->topic)) { - String topicUriReadable; - String topicUriWriteable; + String mqttTopicReadable; + String mqttTopicWriteable; if ((nullptr != topicState->getTopicFunc) && (true == m_isMqttConnected)) { - topicUriReadable = mqttTopicNameBase + MQTT_ENDPOINT_READ_ACCESS; + mqttTopicReadable = mqttTopicBase + MQTT_ENDPOINT_READ_ACCESS; /* Purge topic? */ if (true == purge) { - if (false == mqttService.publish(topicUriReadable, "")) + if (false == mqttService.publish(mqttTopicReadable, "")) { - LOG_WARNING("Failed to purge: %s", topicUriReadable.c_str()); + LOG_WARNING("Failed to purge: %s", mqttTopicReadable.c_str()); } else { - LOG_INFO("Purged: %s", topicUriReadable.c_str()); + LOG_INFO("Purged: %s", mqttTopicReadable.c_str()); } } } - + if (nullptr != topicState->setTopicFunc) { - topicUriWriteable = mqttTopicNameBase + MQTT_ENDPOINT_WRITE_ACCESS; + mqttTopicWriteable = mqttTopicBase + MQTT_ENDPOINT_WRITE_ACCESS; - LOG_INFO("Unsubscribe: %s", topicUriWriteable.c_str()); + LOG_INFO("Unsubscribe: %s", mqttTopicWriteable.c_str()); - mqttService.unsubscribe(topicUriWriteable); + mqttService.unsubscribe(mqttTopicWriteable); } /* Handle Home Assistant extension. */ if (true == purge) { - m_haExtension.unregisterMqttDiscovery(deviceId, entityId, topicUriReadable, topicUriWriteable); + m_haExtension.unregisterMqttDiscovery(deviceId, entityId, topic, mqttTopicReadable, mqttTopicWriteable); } topicStateIt = m_listOfTopicStates.erase(topicStateIt); @@ -226,21 +223,21 @@ void MqttApiTopicHandler::process() if (true == m_isStarted) { MqttService& mqttService = MqttService::getInstance(); - + /* If connection to MQTT broker is the first time established or reconnected, - * all topics will be published to be up-to-date. - */ + * all topics will be published to be up-to-date. + */ if ((false == m_isMqttConnected) && (MqttService::STATE_CONNECTED == mqttService.getState())) { m_isMqttConnected = true; - + /* Publish after connection establishment. */ requestToPublishAllTopicStates(); } else if ((true == m_isMqttConnected) && - (MqttService::STATE_CONNECTED != mqttService.getState())) + (MqttService::STATE_CONNECTED != mqttService.getState())) { m_isMqttConnected = false; } @@ -252,12 +249,12 @@ void MqttApiTopicHandler::process() if (true == m_isMqttConnected) { /* If necessary, a topic state will be published. - * - * Don't publish all of them at once, only one per process cycle. - * This has the advantage to detect lost MQTT connection, because remember - * its cooperative! As long as the MQTT service is not called, no update - * about the connection status will appear. - */ + * + * Don't publish all of them at once, only one per process cycle. + * This has the advantage to detect lost MQTT connection, because remember + * its cooperative! As long as the MQTT service is not called, no update + * about the connection status will appear. + */ publishTopicStatesOnDemand(); } @@ -269,12 +266,11 @@ void MqttApiTopicHandler::process() void MqttApiTopicHandler::notify(const String& deviceId, const String& entityId, const String& topic) { if ((false == deviceId.isEmpty()) && - (false == entityId.isEmpty()) && (false == topic.isEmpty())) { ListOfTopicStates::iterator topicStateIt = m_listOfTopicStates.begin(); - while(m_listOfTopicStates.end() != topicStateIt) + while (m_listOfTopicStates.end() != topicStateIt) { TopicState* topicState = *topicStateIt; @@ -299,18 +295,32 @@ void MqttApiTopicHandler::notify(const String& deviceId, const String& entityId, * Private Methods *****************************************************************************/ +String MqttApiTopicHandler::getMqttBaseTopic(const String& deviceId, const String& entityId, const String& topic) const +{ + String mqttBaseTopic = deviceId; + + if (false == entityId.isEmpty()) + { + mqttBaseTopic += "/" + entityId; + } + + mqttBaseTopic += "/"; + mqttBaseTopic += topic; + + return mqttBaseTopic; +} + void MqttApiTopicHandler::requestToPublishAllTopicStates() { ListOfTopicStates::iterator topicStateIt = m_listOfTopicStates.begin(); /* Set the publish request flag for all topic states. */ - while(m_listOfTopicStates.end() != topicStateIt) + while (m_listOfTopicStates.end() != topicStateIt) { TopicState* topicState = *topicStateIt; if ((nullptr != topicState) && (false == topicState->deviceId.isEmpty()) && - (false == topicState->entityId.isEmpty()) && (nullptr != topicState->getTopicFunc)) { topicState->isPublishReq = true; @@ -324,13 +334,12 @@ void MqttApiTopicHandler::publishTopicStatesOnDemand() { ListOfTopicStates::iterator topicStateIt = m_listOfTopicStates.begin(); - while(m_listOfTopicStates.end() != topicStateIt) + while (m_listOfTopicStates.end() != topicStateIt) { TopicState* topicState = *topicStateIt; if ((nullptr != topicState) && (false == topicState->deviceId.isEmpty()) && - (false == topicState->entityId.isEmpty()) && (nullptr != topicState->getTopicFunc)) { if (true == topicState->isPublishReq) @@ -350,9 +359,9 @@ void MqttApiTopicHandler::publishTopicStatesOnDemand() void MqttApiTopicHandler::write(const String& deviceId, const String& entityId, const String& topic, const uint8_t* payload, size_t size, SetTopicFunc setTopicFunc, UploadReqFunc uploadReqFunc) { - const size_t JSON_DOC_SIZE = 4096U; - DynamicJsonDocument jsonDoc(JSON_DOC_SIZE); - DeserializationError error = deserializeJson(jsonDoc, payload, size); + const size_t JSON_DOC_SIZE = 4096U; + DynamicJsonDocument jsonDoc(JSON_DOC_SIZE); + DeserializationError error = deserializeJson(jsonDoc, payload, size); if (DeserializationError::Ok != error) { @@ -360,8 +369,8 @@ void MqttApiTopicHandler::write(const String& deviceId, const String& entityId, } else { - JsonVariantConst jsonFileName = jsonDoc["fileName"]; - JsonVariantConst jsonFileBase64 = jsonDoc["file"]; + JsonVariantConst jsonFileName = jsonDoc["fileName"]; + JsonVariantConst jsonFileBase64 = jsonDoc["file"]; /* File transfer? */ if ((true == jsonFileName.is()) && @@ -377,9 +386,9 @@ void MqttApiTopicHandler::write(const String& deviceId, const String& entityId, } else { - String fileBase64 = jsonFileBase64.as(); - size_t fileSize = 0U; - int32_t decodeRet = mbedtls_base64_decode(nullptr, 0U, &fileSize, reinterpret_cast(fileBase64.c_str()), fileBase64.length()); + String fileBase64 = jsonFileBase64.as(); + size_t fileSize = 0U; + int32_t decodeRet = mbedtls_base64_decode(nullptr, 0U, &fileSize, reinterpret_cast(fileBase64.c_str()), fileBase64.length()); if (MBEDTLS_ERR_BASE64_INVALID_CHARACTER == decodeRet) { @@ -392,7 +401,7 @@ void MqttApiTopicHandler::write(const String& deviceId, const String& entityId, } else { - uint8_t* buffer = new(std::nothrow) uint8_t[fileSize]; + uint8_t* buffer = new (std::nothrow) uint8_t[fileSize]; if (nullptr != buffer) { @@ -442,10 +451,10 @@ void MqttApiTopicHandler::publish(const String& deviceId, const String& entityId { if (nullptr != getTopicFunc) { - const size_t JSON_DOC_SIZE = 4096U; + const size_t JSON_DOC_SIZE = 4096U; DynamicJsonDocument jsonDoc(JSON_DOC_SIZE); - JsonObject jsonObj = jsonDoc.createNestedObject("data"); - String mqttTopicNameBase = deviceId + "/" + entityId + topic; + JsonObject jsonObj = jsonDoc.createNestedObject("data"); + String mqttTopicBase = getMqttBaseTopic(deviceId, entityId, topic); if (true == getTopicFunc(topic, jsonObj)) { @@ -453,8 +462,8 @@ void MqttApiTopicHandler::publish(const String& deviceId, const String& entityId if (0U < serializeJson(jsonDoc["data"], topicContent)) { - MqttService& mqttService = MqttService::getInstance(); - String topicStateUri = mqttTopicNameBase + MQTT_ENDPOINT_READ_ACCESS; + MqttService& mqttService = MqttService::getInstance(); + String topicStateUri = mqttTopicBase + MQTT_ENDPOINT_READ_ACCESS; if (false == mqttService.publish(topicStateUri, topicContent)) { @@ -471,10 +480,10 @@ void MqttApiTopicHandler::publish(const String& deviceId, const String& entityId void MqttApiTopicHandler::clearTopicStates() { - MqttService& mqttService = MqttService::getInstance(); - ListOfTopicStates::iterator topicStateIt = m_listOfTopicStates.begin(); + MqttService& mqttService = MqttService::getInstance(); + ListOfTopicStates::iterator topicStateIt = m_listOfTopicStates.begin(); - while(m_listOfTopicStates.end() != topicStateIt) + while (m_listOfTopicStates.end() != topicStateIt) { TopicState* topicState = *topicStateIt; @@ -482,8 +491,8 @@ void MqttApiTopicHandler::clearTopicStates() { if (nullptr != topicState->setTopicFunc) { - String mqttTopicNameBase = topicState->deviceId + "/" + topicState->entityId + topicState->topic; - String topicStateUri = mqttTopicNameBase + MQTT_ENDPOINT_WRITE_ACCESS; + String mqttTopicBase = topicState->deviceId + "/" + topicState->entityId + topicState->topic; + String topicStateUri = mqttTopicBase + MQTT_ENDPOINT_WRITE_ACCESS; mqttService.unsubscribe(topicStateUri); } diff --git a/lib/MqttApiTopicHandler/src/MqttApiTopicHandler.h b/lib/MqttApiTopicHandler/src/MqttApiTopicHandler.h index 006acdfd..30a254d3 100644 --- a/lib/MqttApiTopicHandler/src/MqttApiTopicHandler.h +++ b/lib/MqttApiTopicHandler/src/MqttApiTopicHandler.h @@ -180,6 +180,21 @@ class MqttApiTopicHandler : public ITopicHandler MqttApiTopicHandler(const MqttApiTopicHandler& adapter); MqttApiTopicHandler& operator=(const MqttApiTopicHandler& adapter); + /** + * Get MQTT base topic. + * + * Generates the base topic for the MQTT API according to following pattern: + * - writeable: DEVICE-ID/[ENTITY-ID/]TOPIC/set + * - readable: DEVICE-ID/[ENTITY-ID/]TOPIC/state + * + * @param[in] deviceId The device id which represents the physical device. + * @param[in] entityId The entity id which represents the entity of a plugin. May be empty. + * @param[in] topic The topic. + * + * @return REST API base URL + */ + String getMqttBaseTopic(const String& deviceId, const String& entityId, const String& topic) const; + /** * Request to publish all topic states. */ diff --git a/lib/MultiIconPlugin/src/MultiIconPlugin.cpp b/lib/MultiIconPlugin/src/MultiIconPlugin.cpp index b4b466e4..9d3d7b7e 100644 --- a/lib/MultiIconPlugin/src/MultiIconPlugin.cpp +++ b/lib/MultiIconPlugin/src/MultiIconPlugin.cpp @@ -59,10 +59,10 @@ *****************************************************************************/ /* Initialize slot control topic. */ -const char* MultiIconPlugin::TOPIC_SLOT = "/slot"; +const char* MultiIconPlugin::TOPIC_SLOT = "slot"; /* Initialize slots control topic. */ -const char* MultiIconPlugin::TOPIC_SLOTS = "/slots"; +const char* MultiIconPlugin::TOPIC_SLOTS = "slots"; /****************************************************************************** * Public Methods diff --git a/lib/MultiIconPlugin/src/MultiIconPlugin.h b/lib/MultiIconPlugin/src/MultiIconPlugin.h index 71f8b743..63723e82 100644 --- a/lib/MultiIconPlugin/src/MultiIconPlugin.h +++ b/lib/MultiIconPlugin/src/MultiIconPlugin.h @@ -111,7 +111,7 @@ class MultiIconPlugin : public PluginWithConfig * {.json} * { * "topics": [ - * "/text" + * "text" * ] * } * @@ -127,7 +127,7 @@ class MultiIconPlugin : public PluginWithConfig * {.json} * { * "topics": [{ - * "name": "/text", + * "name": "text", * "access": "r" * }] * } @@ -138,7 +138,7 @@ class MultiIconPlugin : public PluginWithConfig * {.json} * { * "topics": [{ - * "name": "/text", + * "name": "text", * "extra": { * "ha": { * ... everything here will be used for MQTT discovery ... @@ -153,7 +153,7 @@ class MultiIconPlugin : public PluginWithConfig * {.json} * { * "topics": [{ - * "name": "/text", + * "name": "text", * "extra": "extra.json" * }] * } diff --git a/lib/OpenWeatherPlugin/src/OpenWeatherPlugin.cpp b/lib/OpenWeatherPlugin/src/OpenWeatherPlugin.cpp index b35ca19f..295259bf 100644 --- a/lib/OpenWeatherPlugin/src/OpenWeatherPlugin.cpp +++ b/lib/OpenWeatherPlugin/src/OpenWeatherPlugin.cpp @@ -70,7 +70,7 @@ const char* OpenWeatherPlugin::OPEN_WEATHER_BASE_URI = "http://api.openweathermap.org"; /* Initialize plugin topic. */ -const char* OpenWeatherPlugin::TOPIC_CONFIG = "/weather"; +const char* OpenWeatherPlugin::TOPIC_CONFIG = "weather"; /****************************************************************************** * Public Methods diff --git a/lib/OpenWeatherPlugin/src/OpenWeatherPlugin.h b/lib/OpenWeatherPlugin/src/OpenWeatherPlugin.h index 606dc535..bd4a550c 100644 --- a/lib/OpenWeatherPlugin/src/OpenWeatherPlugin.h +++ b/lib/OpenWeatherPlugin/src/OpenWeatherPlugin.h @@ -180,7 +180,7 @@ class OpenWeatherPlugin : public PluginWithConfig * {.json} * { * "topics": [ - * "/text" + * "text" * ] * } * @@ -196,7 +196,7 @@ class OpenWeatherPlugin : public PluginWithConfig * {.json} * { * "topics": [{ - * "name": "/text", + * "name": "text", * "access": "r" * }] * } @@ -207,7 +207,7 @@ class OpenWeatherPlugin : public PluginWithConfig * {.json} * { * "topics": [{ - * "name": "/text", + * "name": "text", * "extra": { * "ha": { * ... everything here will be used for MQTT discovery ... @@ -222,7 +222,7 @@ class OpenWeatherPlugin : public PluginWithConfig * {.json} * { * "topics": [{ - * "name": "/text", + * "name": "text", * "extra": "extra.json" * }] * } diff --git a/lib/Plugin/src/IPluginMaintenance.hpp b/lib/Plugin/src/IPluginMaintenance.hpp index 0996bd05..c14f79e4 100644 --- a/lib/Plugin/src/IPluginMaintenance.hpp +++ b/lib/Plugin/src/IPluginMaintenance.hpp @@ -131,7 +131,7 @@ class IPluginMaintenance * {.json} * { * "topics": [ - * "/text" + * "text" * ] * } * @@ -147,7 +147,7 @@ class IPluginMaintenance * {.json} * { * "topics": [{ - * "name": "/text", + * "name": "text", * "access": "r" * }] * } @@ -158,7 +158,7 @@ class IPluginMaintenance * {.json} * { * "topics": [{ - * "name": "/text", + * "name": "text", * "extra": { * "ha": { * ... everything here will be used for MQTT discovery ... @@ -173,7 +173,7 @@ class IPluginMaintenance * {.json} * { * "topics": [{ - * "name": "/text", + * "name": "text", * "extra": "extra.json" * }] * } diff --git a/lib/Plugin/src/Plugin.hpp b/lib/Plugin/src/Plugin.hpp index b913ea29..bc14cc59 100644 --- a/lib/Plugin/src/Plugin.hpp +++ b/lib/Plugin/src/Plugin.hpp @@ -147,7 +147,7 @@ class Plugin : public IPluginMaintenance * {.json} * { * "topics": [ - * "/text" + * "text" * ] * } * @@ -163,7 +163,7 @@ class Plugin : public IPluginMaintenance * {.json} * { * "topics": [{ - * "name": "/text", + * "name": "text", * "access": "r" * }] * } @@ -174,7 +174,7 @@ class Plugin : public IPluginMaintenance * {.json} * { * "topics": [{ - * "name": "/text", + * "name": "text", * "extra": { * "ha": { * ... everything here will be used for MQTT discovery ... @@ -189,7 +189,7 @@ class Plugin : public IPluginMaintenance * {.json} * { * "topics": [{ - * "name": "/text", + * "name": "text", * "extra": "extra.json" * }] * } diff --git a/lib/RestApiTopicHandler/src/RestApiTopicHandler.cpp b/lib/RestApiTopicHandler/src/RestApiTopicHandler.cpp index 5bc0e284..44def7b6 100644 --- a/lib/RestApiTopicHandler/src/RestApiTopicHandler.cpp +++ b/lib/RestApiTopicHandler/src/RestApiTopicHandler.cpp @@ -68,36 +68,34 @@ void RestApiTopicHandler::registerTopic(const String& deviceId, const String& entityId, const String& topic, JsonObjectConst& extra, GetTopicFunc getTopicFunc, SetTopicFunc setTopicFunc, UploadReqFunc uploadReqFunc) { - if ((false == deviceId.isEmpty()) && - (false == entityId.isEmpty()) && - (false == topic.isEmpty())) + /* A REST API URI is unique by its entity id and topic. */ + UTIL_NOT_USED(deviceId); + + /* Extra info not supported. */ + UTIL_NOT_USED(extra); + + if (false == topic.isEmpty()) { - TopicMetaData* topicMetaData = new(std::nothrow) TopicMetaData(); + TopicMetaData* topicMetaData = new (std::nothrow) TopicMetaData(); if (nullptr != topicMetaData) { - String baseUri = getBaseUri(entityId); - ArRequestHandlerFunction onRequest = - [this, topicMetaData](AsyncWebServerRequest *request) - { - this->webReqHandler(request, topicMetaData); - }; - ArUploadHandlerFunction onUpload = - [this, topicMetaData](AsyncWebServerRequest *request, const String& filename, size_t index, uint8_t *data, size_t len, bool final) - { - this->uploadHandler(request, filename, index, data, len, final, topicMetaData); - }; - - topicMetaData->deviceId = deviceId; - topicMetaData->entityId = entityId; - topicMetaData->topic = topic; - topicMetaData->getTopicFunc = getTopicFunc; - topicMetaData->setTopicFunc = setTopicFunc; - topicMetaData->uploadReqFunc = uploadReqFunc; - topicMetaData->uri = baseUri + topic; - topicMetaData->webHandler = &MyWebServer::getInstance().on(topicMetaData->uri.c_str(), HTTP_ANY, onRequest, onUpload); - - UTIL_NOT_USED(extra); + ArRequestHandlerFunction onRequest = + [this, topicMetaData](AsyncWebServerRequest* request) { + this->webReqHandler(request, topicMetaData); + }; + ArUploadHandlerFunction onUpload = + [this, topicMetaData](AsyncWebServerRequest* request, const String& filename, size_t index, uint8_t* data, size_t len, bool final) { + this->uploadHandler(request, filename, index, data, len, final, topicMetaData); + }; + + topicMetaData->entityId = entityId; + topicMetaData->topic = topic; + topicMetaData->getTopicFunc = getTopicFunc; + topicMetaData->setTopicFunc = setTopicFunc; + topicMetaData->uploadReqFunc = uploadReqFunc; + topicMetaData->uri = getUri(entityId, topic); + topicMetaData->webHandler = &MyWebServer::getInstance().on(topicMetaData->uri.c_str(), HTTP_ANY, onRequest, onUpload); LOG_INFO("Register: %s", topicMetaData->uri.c_str()); @@ -108,20 +106,18 @@ void RestApiTopicHandler::registerTopic(const String& deviceId, const String& en void RestApiTopicHandler::unregisterTopic(const String& deviceId, const String& entityId, const String& topic, bool purge) { + UTIL_NOT_USED(deviceId); UTIL_NOT_USED(purge); - if ((false == deviceId.isEmpty()) && - (false == entityId.isEmpty()) && - (false == topic.isEmpty())) + if (false == topic.isEmpty()) { ListOfTopicMetaData::iterator topicMetaDataIt = m_listOfTopicMetaData.begin(); - while(m_listOfTopicMetaData.end() != topicMetaDataIt) + while (m_listOfTopicMetaData.end() != topicMetaDataIt) { TopicMetaData* topicMetaData = *topicMetaDataIt; if ((nullptr != topicMetaData) && - (deviceId == topicMetaData->deviceId) && (entityId == topicMetaData->entityId) && (topic == topicMetaData->topic)) { @@ -135,7 +131,7 @@ void RestApiTopicHandler::unregisterTopic(const String& deviceId, const String& } topicMetaDataIt = m_listOfTopicMetaData.erase(topicMetaDataIt); - + delete topicMetaData; topicMetaData = nullptr; } @@ -155,22 +151,29 @@ void RestApiTopicHandler::unregisterTopic(const String& deviceId, const String& * Private Methods *****************************************************************************/ -String RestApiTopicHandler::getBaseUri(const String& entityId) +String RestApiTopicHandler::getUri(const String& entityId, const String& topic) const { - String baseUri = RestApi::BASE_URI; - baseUri += "/"; - baseUri += entityId; + String restUri = RestApi::BASE_URI; - return baseUri; + if (false == entityId.isEmpty()) + { + restUri += "/"; + restUri += entityId; + } + + restUri += "/"; + restUri += topic; + + return restUri; } -void RestApiTopicHandler::webReqHandler(AsyncWebServerRequest *request, TopicMetaData* topicMetaData) +void RestApiTopicHandler::webReqHandler(AsyncWebServerRequest* request, TopicMetaData* topicMetaData) { String content; - const size_t JSON_DOC_SIZE = 4096U; + const size_t JSON_DOC_SIZE = 4096U; DynamicJsonDocument jsonDoc(JSON_DOC_SIZE); - JsonObject dataObj = jsonDoc.createNestedObject("data"); - uint32_t httpStatusCode = HttpStatus::STATUS_CODE_OK; + JsonObject dataObj = jsonDoc.createNestedObject("data"); + uint32_t httpStatusCode = HttpStatus::STATUS_CODE_OK; if ((nullptr == request) || (nullptr == topicMetaData)) @@ -202,7 +205,7 @@ void RestApiTopicHandler::webReqHandler(AsyncWebServerRequest *request, TopicMet { DynamicJsonDocument jsonDocPar(JSON_DOC_SIZE); JsonObjectConst jsonValue; - + /* Topic data is in the HTTP parameters and needs to be converted to JSON. */ par2Json(jsonDocPar, request); @@ -232,8 +235,8 @@ void RestApiTopicHandler::webReqHandler(AsyncWebServerRequest *request, TopicMet } else { - jsonDoc["status"] = "ok"; - httpStatusCode = HttpStatus::STATUS_CODE_OK; + jsonDoc["status"] = "ok"; + httpStatusCode = HttpStatus::STATUS_CODE_OK; } } else @@ -248,7 +251,7 @@ void RestApiTopicHandler::webReqHandler(AsyncWebServerRequest *request, TopicMet RestUtil::sendJsonRsp(request, jsonDoc, httpStatusCode); } -void RestApiTopicHandler::uploadHandler(AsyncWebServerRequest *request, const String& filename, size_t index, uint8_t *data, size_t len, bool final, TopicMetaData* topicMetaData) +void RestApiTopicHandler::uploadHandler(AsyncWebServerRequest* request, const String& filename, size_t index, uint8_t* data, size_t len, bool final, TopicMetaData* topicMetaData) { /* Begin of upload? */ if (0 == index) @@ -327,7 +330,7 @@ void RestApiTopicHandler::uploadHandler(AsyncWebServerRequest *request, const St } } -void RestApiTopicHandler::par2Json(JsonDocument& jsonDocPar, AsyncWebServerRequest *request) +void RestApiTopicHandler::par2Json(JsonDocument& jsonDocPar, AsyncWebServerRequest* request) { size_t idx = 0U; @@ -336,15 +339,15 @@ void RestApiTopicHandler::par2Json(JsonDocument& jsonDocPar, AsyncWebServerReque * - key.subKey=value --> { "key": { "subKey": "value "} } * - key._0_=value --> { "key": [ "value" ] } * - key._0_.subKey=value --> { "key": [ "subKey": "value" ] } - * + * * Note: Only the patterns above are supported, but not a higher * nesting level. */ - for(idx = 0U; idx < request->args(); ++idx) + for (idx = 0U; idx < request->args(); ++idx) { - const String& keyPattern = request->argName(idx); - const String& value = request->arg(idx); - int dotIdx = keyPattern.indexOf("."); + const String& keyPattern = request->argName(idx); + const String& value = request->arg(idx); + int dotIdx = keyPattern.indexOf("."); /* No "." in the key pattern means: key=value */ if (0 > dotIdx) @@ -354,21 +357,21 @@ void RestApiTopicHandler::par2Json(JsonDocument& jsonDocPar, AsyncWebServerReque /* No "_" after the "." means: key.subKey=value */ else if ('_' != keyPattern[dotIdx + 1U]) { - String key = keyPattern.substring(0, dotIdx); - String subKey = keyPattern.substring(dotIdx + 1U); + String key = keyPattern.substring(0, dotIdx); + String subKey = keyPattern.substring(dotIdx + 1U); jsonDocPar[key][subKey] = value; } /* Its an array. */ else { - String key = keyPattern.substring(0, dotIdx); - int dot2Idx = keyPattern.lastIndexOf("."); + String key = keyPattern.substring(0, dotIdx); + int dot2Idx = keyPattern.lastIndexOf("."); /* No additional "." means: key._0_=value */ if (dotIdx == dot2Idx) { - String strArrayIdx = keyPattern.substring(dotIdx + 1U); + String strArrayIdx = keyPattern.substring(dotIdx + 1U); /* Remove "_" at the front and the end. */ strArrayIdx.remove(0U, 1U); @@ -379,8 +382,8 @@ void RestApiTopicHandler::par2Json(JsonDocument& jsonDocPar, AsyncWebServerReque /* Additional "." means: key._0_.subKey=value */ else { - String strArrayIdx = keyPattern.substring(dotIdx + 1U); - String subKey = keyPattern.substring(dot2Idx + 1U); + String strArrayIdx = keyPattern.substring(dotIdx + 1U); + String subKey = keyPattern.substring(dot2Idx + 1U); /* Remove "_" at the front and the end. */ strArrayIdx.remove(0U, 1U); @@ -396,7 +399,7 @@ void RestApiTopicHandler::clearPluginTopics() { ListOfTopicMetaData::iterator topicMetaDataIt = m_listOfTopicMetaData.begin(); - while(m_listOfTopicMetaData.end() != topicMetaDataIt) + while (m_listOfTopicMetaData.end() != topicMetaDataIt) { TopicMetaData* topicMetaData = *topicMetaDataIt; diff --git a/lib/RestApiTopicHandler/src/RestApiTopicHandler.h b/lib/RestApiTopicHandler/src/RestApiTopicHandler.h index 1d26a54e..ceced5d2 100644 --- a/lib/RestApiTopicHandler/src/RestApiTopicHandler.h +++ b/lib/RestApiTopicHandler/src/RestApiTopicHandler.h @@ -99,9 +99,10 @@ class RestApiTopicHandler : public ITopicHandler /** * Register the topic. - * + * Generates the REST API URI according to the following pattern: BASE-URL/[ENTITY-ID/]TOPIC + * * @param[in] deviceId The device id which represents the physical device. - * @param[in] entityId The entity id which represents the entity of the device. + * @param[in] entityId The entity id which represents the entity of the device. May be empty in case of a general REST API. * @param[in] topic The topic name. * @param[in] extra Extra parameters, which depend on the topic handler. * @param[in] getTopicFunc Function to get the topic content. @@ -112,7 +113,7 @@ class RestApiTopicHandler : public ITopicHandler /** * Unregister the topic. - * + * * @param[in] deviceId The device id which represents the physical device. * @param[in] entityId The entity id which represents the entity of the device. * @param[in] topic The topic name. @@ -130,7 +131,7 @@ class RestApiTopicHandler : public ITopicHandler /** * Notify that the topic has changed. - * + * * @param[in] deviceId The device id which represents the physical device. * @param[in] entityId The entity id which represents the entity of the device. * @param[in] topic The topic name. @@ -150,22 +151,20 @@ class RestApiTopicHandler : public ITopicHandler */ struct TopicMetaData { - String deviceId; /**< The device id. */ - String entityId; /**< The entity id. */ - String topic; /**< The plugin topic. */ - GetTopicFunc getTopicFunc; /**< Function used to get topic content. */ - SetTopicFunc setTopicFunc; /**< Function used to set topic content. */ - UploadReqFunc uploadReqFunc; /**< Function used to check whether a file upload is allowed. */ - AsyncCallbackWebHandler* webHandler; /**< Webhandler callback, necessary to remove it later again. */ - String uri; /**< URI where the handler is registered. */ - bool isUploadError; /**< If upload error happened, it will be true otherwise false. */ - String fullPath; /**< Full path of uploaded file. If empty, there is no file available. */ + String entityId; /**< The entity id. */ + String topic; /**< The plugin topic. */ + GetTopicFunc getTopicFunc; /**< Function used to get topic content. */ + SetTopicFunc setTopicFunc; /**< Function used to set topic content. */ + UploadReqFunc uploadReqFunc; /**< Function used to check whether a file upload is allowed. */ + AsyncCallbackWebHandler* webHandler; /**< Webhandler callback, necessary to remove it later again. */ + String uri; /**< URI where the handler is registered. */ + bool isUploadError; /**< If upload error happened, it will be true otherwise false. */ + String fullPath; /**< Full path of uploaded file. If empty, there is no file available. */ /** * Initialize topic meta data. */ TopicMetaData() : - deviceId(), entityId(), topic(), getTopicFunc(nullptr), @@ -184,27 +183,28 @@ class RestApiTopicHandler : public ITopicHandler */ typedef std::vector ListOfTopicMetaData; - ListOfTopicMetaData m_listOfTopicMetaData; /**< List of topic meta data. */ + ListOfTopicMetaData m_listOfTopicMetaData; /**< List of topic meta data. */ RestApiTopicHandler(const RestApiTopicHandler& adapter); RestApiTopicHandler& operator=(const RestApiTopicHandler& adapter); /** - * Get plugin REST base URI to identify plugin. + * Get plugin REST URI * - * @param[in] entityId The entity id which represents the entity of the device. + * @param[in] entityId The entity id which represents the entity of a plugin. May be empty in case of a general REST API. + * @param[in] topic The topic. * - * @return Plugin REST API base URI + * @return REST API base URL */ - String getBaseUri(const String& entityId); + String getUri(const String& entityId, const String& topic) const; /** * The web request handler handles all incoming HTTP requests for every plugin topic. - * + * * @param[in] request The web request information from the client. * @param[in] topicMetaData The related topic meta data. */ - void webReqHandler(AsyncWebServerRequest *request, TopicMetaData* topicMetaData); + void webReqHandler(AsyncWebServerRequest* request, TopicMetaData* topicMetaData); /** * File upload handler. @@ -217,15 +217,15 @@ class RestApiTopicHandler : public ITopicHandler * @param[in] final Is final packet or not. * @param[in] topicMetaData The related topic meta data. */ - void uploadHandler(AsyncWebServerRequest *request, const String& filename, size_t index, uint8_t *data, size_t len, bool final, TopicMetaData* topicMetaData); + void uploadHandler(AsyncWebServerRequest* request, const String& filename, size_t index, uint8_t* data, size_t len, bool final, TopicMetaData* topicMetaData); /** * Convert HTTP parameters to JSON format. - * + * * @param[in, out] jsonDocPar JSON document which the parameters will be transfered to. * @param[in] request HTTP request with parameters */ - void par2Json(JsonDocument& jsonDocPar, AsyncWebServerRequest *request); + void par2Json(JsonDocument& jsonDocPar, AsyncWebServerRequest* request); /** * Clear plugin topics. @@ -237,6 +237,6 @@ class RestApiTopicHandler : public ITopicHandler * Functions *****************************************************************************/ -#endif /* REST_API_TOPIC_HANDLER_H */ +#endif /* REST_API_TOPIC_HANDLER_H */ /** @} */ \ No newline at end of file diff --git a/lib/SensorPlugin/src/SensorPlugin.cpp b/lib/SensorPlugin/src/SensorPlugin.cpp index 6a409e61..950fd316 100644 --- a/lib/SensorPlugin/src/SensorPlugin.cpp +++ b/lib/SensorPlugin/src/SensorPlugin.cpp @@ -60,7 +60,7 @@ *****************************************************************************/ /* Initialize plugin topic. */ -const char* SensorPlugin::TOPIC_CONFIG = "/channel"; +const char* SensorPlugin::TOPIC_CONFIG = "channel"; /****************************************************************************** * Public Methods diff --git a/lib/SensorPlugin/src/SensorPlugin.h b/lib/SensorPlugin/src/SensorPlugin.h index 955715b5..c6d790ca 100644 --- a/lib/SensorPlugin/src/SensorPlugin.h +++ b/lib/SensorPlugin/src/SensorPlugin.h @@ -138,7 +138,7 @@ class SensorPlugin : public PluginWithConfig * {.json} * { * "topics": [ - * "/text" + * "text" * ] * } * @@ -154,7 +154,7 @@ class SensorPlugin : public PluginWithConfig * {.json} * { * "topics": [{ - * "name": "/text", + * "name": "text", * "access": "r" * }] * } @@ -165,7 +165,7 @@ class SensorPlugin : public PluginWithConfig * {.json} * { * "topics": [{ - * "name": "/text", + * "name": "text", * "extra": { * "ha": { * ... everything here will be used for MQTT discovery ... @@ -180,7 +180,7 @@ class SensorPlugin : public PluginWithConfig * {.json} * { * "topics": [{ - * "name": "/text", + * "name": "text", * "extra": "extra.json" * }] * } diff --git a/lib/SignalDetectorPlugin/src/SignalDetectorPlugin.cpp b/lib/SignalDetectorPlugin/src/SignalDetectorPlugin.cpp index 0f3ca928..5a09ca03 100644 --- a/lib/SignalDetectorPlugin/src/SignalDetectorPlugin.cpp +++ b/lib/SignalDetectorPlugin/src/SignalDetectorPlugin.cpp @@ -59,7 +59,7 @@ *****************************************************************************/ /* Initialize plugin topic. */ -const char* SignalDetectorPlugin::TOPIC_CONFIG = "/signalDetector"; +const char* SignalDetectorPlugin::TOPIC_CONFIG = "signalDetector"; /* Initialize the default text which will be shown if signal is detected. */ const char* SignalDetectorPlugin::DEFAULT_TEXT = "{hc}Signal!"; diff --git a/lib/SignalDetectorPlugin/src/SignalDetectorPlugin.h b/lib/SignalDetectorPlugin/src/SignalDetectorPlugin.h index 96738141..236aad72 100644 --- a/lib/SignalDetectorPlugin/src/SignalDetectorPlugin.h +++ b/lib/SignalDetectorPlugin/src/SignalDetectorPlugin.h @@ -148,7 +148,7 @@ class SignalDetectorPlugin : public PluginWithConfig * {.json} * { * "topics": [ - * "/text" + * "text" * ] * } * @@ -164,7 +164,7 @@ class SignalDetectorPlugin : public PluginWithConfig * {.json} * { * "topics": [{ - * "name": "/text", + * "name": "text", * "access": "r" * }] * } @@ -175,7 +175,7 @@ class SignalDetectorPlugin : public PluginWithConfig * {.json} * { * "topics": [{ - * "name": "/text", + * "name": "text", * "extra": { * "ha": { * ... everything here will be used for MQTT discovery ... @@ -190,7 +190,7 @@ class SignalDetectorPlugin : public PluginWithConfig * {.json} * { * "topics": [{ - * "name": "/text", + * "name": "text", * "extra": "extra.json" * }] * } diff --git a/lib/SoundReactivePlugin/src/SoundReactivePlugin.cpp b/lib/SoundReactivePlugin/src/SoundReactivePlugin.cpp index 779b9f3c..53b221e4 100644 --- a/lib/SoundReactivePlugin/src/SoundReactivePlugin.cpp +++ b/lib/SoundReactivePlugin/src/SoundReactivePlugin.cpp @@ -58,7 +58,7 @@ *****************************************************************************/ /* Initialize plugin topic. */ -const char* SoundReactivePlugin::TOPIC_CONFIG = "/config"; +const char* SoundReactivePlugin::TOPIC_CONFIG = "config"; /* Initialize the list with the high edge frequency bin of the center band frequency. */ const uint16_t SoundReactivePlugin::LIST_16_BAND_HIGH_EDGE_FREQ_BIN[] = diff --git a/lib/SoundReactivePlugin/src/SoundReactivePlugin.h b/lib/SoundReactivePlugin/src/SoundReactivePlugin.h index f5a35884..715c790e 100644 --- a/lib/SoundReactivePlugin/src/SoundReactivePlugin.h +++ b/lib/SoundReactivePlugin/src/SoundReactivePlugin.h @@ -129,7 +129,7 @@ class SoundReactivePlugin : public PluginWithConfig * {.json} * { * "topics": [ - * "/text" + * "text" * ] * } * @@ -145,7 +145,7 @@ class SoundReactivePlugin : public PluginWithConfig * {.json} * { * "topics": [{ - * "name": "/text", + * "name": "text", * "access": "r" * }] * } @@ -156,7 +156,7 @@ class SoundReactivePlugin : public PluginWithConfig * {.json} * { * "topics": [{ - * "name": "/text", + * "name": "text", * "extra": { * "ha": { * ... everything here will be used for MQTT discovery ... @@ -171,7 +171,7 @@ class SoundReactivePlugin : public PluginWithConfig * {.json} * { * "topics": [{ - * "name": "/text", + * "name": "text", * "extra": "extra.json" * }] * } diff --git a/lib/SunrisePlugin/src/SunrisePlugin.cpp b/lib/SunrisePlugin/src/SunrisePlugin.cpp index 7630e4c3..dee8a1d8 100644 --- a/lib/SunrisePlugin/src/SunrisePlugin.cpp +++ b/lib/SunrisePlugin/src/SunrisePlugin.cpp @@ -61,7 +61,7 @@ *****************************************************************************/ /* Initialize plugin topic. */ -const char* SunrisePlugin::TOPIC_CONFIG = "/location"; +const char* SunrisePlugin::TOPIC_CONFIG = "location"; /* Initialize time format. */ const char* SunrisePlugin::TIME_FORMAT_DEFAULT = "%I:%M %p"; diff --git a/lib/SunrisePlugin/src/SunrisePlugin.h b/lib/SunrisePlugin/src/SunrisePlugin.h index e1794249..5177e7be 100644 --- a/lib/SunrisePlugin/src/SunrisePlugin.h +++ b/lib/SunrisePlugin/src/SunrisePlugin.h @@ -160,7 +160,7 @@ class SunrisePlugin : public PluginWithConfig * {.json} * { * "topics": [ - * "/text" + * "text" * ] * } * @@ -176,7 +176,7 @@ class SunrisePlugin : public PluginWithConfig * {.json} * { * "topics": [{ - * "name": "/text", + * "name": "text", * "access": "r" * }] * } @@ -187,7 +187,7 @@ class SunrisePlugin : public PluginWithConfig * {.json} * { * "topics": [{ - * "name": "/text", + * "name": "text", * "extra": { * "ha": { * ... everything here will be used for MQTT discovery ... @@ -202,7 +202,7 @@ class SunrisePlugin : public PluginWithConfig * {.json} * { * "topics": [{ - * "name": "/text", + * "name": "text", * "extra": "extra.json" * }] * } diff --git a/lib/TimerService/src/TimerService.cpp b/lib/TimerService/src/TimerService.cpp index 8d76b575..35d79def 100644 --- a/lib/TimerService/src/TimerService.cpp +++ b/lib/TimerService/src/TimerService.cpp @@ -65,7 +65,7 @@ /* Initialize constant values. */ const char* TimerService::FILE_NAME = "/configuration/timerService.json"; -const char* TimerService::TOPIC = "/timer"; +const char* TimerService::TOPIC = "timer"; const char* TimerService::ENTITY = "timerService"; /****************************************************************************** diff --git a/lib/TopicHandlerService/src/TopicHandlerService.cpp b/lib/TopicHandlerService/src/TopicHandlerService.cpp index 418ee40b..67e965c6 100644 --- a/lib/TopicHandlerService/src/TopicHandlerService.cpp +++ b/lib/TopicHandlerService/src/TopicHandlerService.cpp @@ -247,7 +247,6 @@ void TopicHandlerService::unregisterTopics(const String& deviceId, const String& void TopicHandlerService::registerTopic(const String& deviceId, const String& entityId, const String& topic, JsonObjectConst& jsonExtra, ITopicHandler::GetTopicFunc getTopicFunc, HasChangedFunc hasChangedFunc, ITopicHandler::SetTopicFunc setTopicFunc, ITopicHandler::UploadReqFunc uploadReqFunc) { if ((false == deviceId.isEmpty()) && - (false == entityId.isEmpty()) && (false == topic.isEmpty())) { bool isReadAccess = false; @@ -325,7 +324,6 @@ void TopicHandlerService::registerTopic(const String& deviceId, const String& en void TopicHandlerService::unregisterTopic(const String& deviceId, const String& entityId, const String& topic, bool purge) { if ((false == deviceId.isEmpty()) && - (false == entityId.isEmpty()) && (false == topic.isEmpty())) { uint8_t idx = 0U; @@ -407,7 +405,6 @@ void TopicHandlerService::strToAccess(IPluginMaintenance* plugin, const String& void TopicHandlerService::addToTopicMetaDataList(const String& deviceId, const String& entityId, IPluginMaintenance* plugin, const String& topic, HasChangedFunc hasChangedFunc) { if ((false == deviceId.isEmpty()) && - (false == entityId.isEmpty()) && (false == topic.isEmpty())) { TopicMetaData* topicMetaData = new (std::nothrow) TopicMetaData(); diff --git a/lib/TopicHandlerService/src/TopicHandlerService.h b/lib/TopicHandlerService/src/TopicHandlerService.h index 64fd2c13..b7b2b222 100644 --- a/lib/TopicHandlerService/src/TopicHandlerService.h +++ b/lib/TopicHandlerService/src/TopicHandlerService.h @@ -104,7 +104,7 @@ class TopicHandlerService : public IService * Register all topics of the given plugin. * * @param[in] deviceId The device id which represents the physical device. - * @param[in] entityId The entity id which represents the entity of the device. + * @param[in] entityId The entity id which represents the entity of the device. May be empty. * @param[in] plugin The plugin, which topics shall be registered. */ void registerTopics(const String& deviceId, const String& entityId, IPluginMaintenance* plugin); diff --git a/lib/VolumioPlugin/src/VolumioPlugin.cpp b/lib/VolumioPlugin/src/VolumioPlugin.cpp index 61f23668..fc5e4892 100644 --- a/lib/VolumioPlugin/src/VolumioPlugin.cpp +++ b/lib/VolumioPlugin/src/VolumioPlugin.cpp @@ -59,7 +59,7 @@ *****************************************************************************/ /* Initialize plugin topic. */ -const char* VolumioPlugin::TOPIC_CONFIG = "/host"; +const char* VolumioPlugin::TOPIC_CONFIG = "host"; /****************************************************************************** * Public Methods diff --git a/lib/VolumioPlugin/src/VolumioPlugin.h b/lib/VolumioPlugin/src/VolumioPlugin.h index a4efd982..9bfebf6c 100644 --- a/lib/VolumioPlugin/src/VolumioPlugin.h +++ b/lib/VolumioPlugin/src/VolumioPlugin.h @@ -135,7 +135,7 @@ class VolumioPlugin : public PluginWithConfig * {.json} * { * "topics": [ - * "/text" + * "text" * ] * } * @@ -151,7 +151,7 @@ class VolumioPlugin : public PluginWithConfig * {.json} * { * "topics": [{ - * "name": "/text", + * "name": "text", * "access": "r" * }] * } @@ -162,7 +162,7 @@ class VolumioPlugin : public PluginWithConfig * {.json} * { * "topics": [{ - * "name": "/text", + * "name": "text", * "extra": { * "ha": { * ... everything here will be used for MQTT discovery ... @@ -177,7 +177,7 @@ class VolumioPlugin : public PluginWithConfig * {.json} * { * "topics": [{ - * "name": "/text", + * "name": "text", * "extra": "extra.json" * }] * } diff --git a/src/Hal/SensorDataProvider.cpp b/src/Hal/SensorDataProvider.cpp index c2bcbc53..61d782ed 100644 --- a/src/Hal/SensorDataProvider.cpp +++ b/src/Hal/SensorDataProvider.cpp @@ -521,7 +521,7 @@ void SensorDataProvider::registerSensorTopics() const uint32_t VALUE_PRECISION = 2U; /* 2 digits after the . */ ISensor* sensor = this->getSensor(sensorIndex); ISensorChannel* sensorChannel = sensor->getChannel(channelIndex); - String channelName = "/" + ISensorChannel::channelTypeToName(sensorTopic->sensorChannelType); + String channelName = ISensorChannel::channelTypeToName(sensorTopic->sensorChannelType); String entityId = "sensors/"; ITopicHandler::GetTopicFunc getTopicFunc = [sensorTopic, sensorChannel, VALUE_PRECISION](const String& topic, JsonObject& jsonValue) -> bool { @@ -583,7 +583,7 @@ void SensorDataProvider::unregisterSensorTopics() for (index = 0U; index < UTIL_ARRAY_NUM(gSensorTopics); ++index) { const SensorTopic* sensorTopic = &gSensorTopics[index]; - String channelName = "/" + ISensorChannel::channelTypeToName(sensorTopic->sensorChannelType); + String channelName = ISensorChannel::channelTypeToName(sensorTopic->sensorChannelType); String entityId = "sensors/"; entityId += index; diff --git a/src/Topics/Topics.cpp b/src/Topics/Topics.cpp index 0eec3c3e..2e0ea7be 100644 --- a/src/Topics/Topics.cpp +++ b/src/Topics/Topics.cpp @@ -83,10 +83,22 @@ static String gDeviceId; /** * List of topics. + * + * ENTITY-ID : display/uid/PLUGIN-UID | display/alias/PLUGIN-ALIAS | empty + * + * REST API : BASE-URL/[ENTITY-ID/]TOPIC + * + * MQTT : DEVICE-ID/[ENTITY-ID/]TOPIC/set (writeable) + * DEVICE-ID/[ENTITY-ID/]TOPIC/state (readable) + * + * HomeAssistant : NODE-ID = DEVICE-ID with "/" and "." replaced by "_" + * OBJECT-ID = [ENTITY-ID/]TOPIC with "/" and "." replaced by "_" + * UNIQUE-ID = DEVICE-ID/[ENTITY-ID/]TOPIC + * DISCOVERY-TOPIC = DISCOVERY-PREFIX/COMPONENT/NODE-ID/OBJECT-ID/config */ static TopicElem gTopicList[] = { - { "display", "/power", getDisplayState, hasDisplayStateChanged, setDisplayState, "/extra/display.json" }, - { "device", "/restart", nullptr, nullptr, restart, "/extra/restart.json" } + { "display", "power", getDisplayState, hasDisplayStateChanged, setDisplayState, "/extra/display.json" }, + { "", "restart", nullptr, nullptr, restart, "/extra/restart.json" } }; /**