diff --git a/qthing/include/qthing.hpp b/qthing/include/qthing.hpp index 63d670b6502ceca8e9b5fd312eba665080f98239..96975a0d98444a63daef2730975c65acf7ae0c1a 100644 --- a/qthing/include/qthing.hpp +++ b/qthing/include/qthing.hpp @@ -11,7 +11,7 @@ #include "qthing/ota.hpp" #include "qthing/power.hpp" #include "qthing/qthing_legacy.hpp" -#include "qthing/synchronized_variable.hpp" +#include "qthing/property.hpp" #include "qthing/time.hpp" #include "qthing/util.hpp" diff --git a/qthing/include/qthing/description.hpp b/qthing/include/qthing/description.hpp index 5b1ff79d6435056cfcc6c07c0d7689de38a24771..026446752b5055f34225b8360bc41d74195d2546 100644 --- a/qthing/include/qthing/description.hpp +++ b/qthing/include/qthing/description.hpp @@ -15,7 +15,13 @@ class DeviceDescription { void addLeds(int numLeds) { j["leds"] += numLeds; } - void addSynchronizedVariable(std::string typeName, std::string name) { j["properties"][name] = {{"type", typeName}}; } + void addProperty(json typeDescription, std::string name, bool subscribe, json defaultValue) { + j["properties"][name] = {{"type", typeDescription}, {"subscribe", subscribe}, {"default", defaultValue}}; + } + + void addProperty(json typeDescription, std::string name, bool subscribe) { + j["properties"][name] = {{"type", typeDescription}, {"subscribe", subscribe}}; + } const json& to_json() { return j; } std::string encode() { return j.dump(); } diff --git a/qthing/include/qthing/mqtt_common.hpp b/qthing/include/qthing/mqtt_common.hpp index 5dcc37a3fda33fe979dc4b7400f94762a671e02f..42191c9e7d189a765cf66cb501d2aef82615b942 100644 --- a/qthing/include/qthing/mqtt_common.hpp +++ b/qthing/include/qthing/mqtt_common.hpp @@ -11,9 +11,10 @@ const std::string DEVICE_NAMESPACE(ENVIRONMENT_NAMESPACE + "device/" + static_ca const std::string DEVICE_NAMESPACE("device/" + static_cast<std::string>(DEVICE_NAME) + "/"); #endif -const std::string STATUS_TOPIC(DEVICE_NAMESPACE + "status"); -const std::string STATUS_MESSAGE_ONLINE("online"); -const std::string STATUS_MESSAGE_OFFLINE("offline"); +const std::string DEVICE_CONFIGURATION_TOPIC(DEVICE_NAMESPACE + "description"); + +const std::string BEACON_TOPIC(DEVICE_NAMESPACE + "beacon"); +const std::string BEACON_MESSAGE_CONNECTED("mqtt_connected"); const std::string FIRMWARE_ERROR_TOPIC(DEVICE_NAMESPACE + "error"); const std::string FIRMWARE_COMMAND_TOPIC(DEVICE_NAMESPACE + "command"); diff --git a/qthing/include/qthing/property.hpp b/qthing/include/qthing/property.hpp new file mode 100644 index 0000000000000000000000000000000000000000..601b575c446aa0998482c28a3f8785f2e760f58a --- /dev/null +++ b/qthing/include/qthing/property.hpp @@ -0,0 +1,134 @@ +#pragma once + +#include <nlohmann/json.hpp> +#include <optional> +#include <string> + +#include "esp_log.h" +#include "qthing/core.hpp" +#include "qthing/mqtt_common.hpp" +#include "qthing/qthing_legacy.hpp" + +using json = nlohmann::json; + +namespace qthing { + +template <typename T> +class Serializer { + static_assert(T::is_serializable(), "Serializer can only work on classes, that support it."); + Serializer() = delete; // delete ctor (abstract class) + + public: + static json typeDescription() { return T::typeDescription(); } + static json toJSON(const T& val) { return val.toJSON(); } + static std::optional<T> fromJSON(std::string s) { return T(s); } +}; + +template <> +class Serializer<double> { + Serializer() = delete; // delete ctor (static class) + public: + static json typeDescription() { return {{"tag", "Float"}}; } + static json toJSON(double val) { return val; } + static std::optional<double> fromJSON(std::string s) { + try { + return std::stod(s); + } catch (const std::invalid_argument&) { + } catch (const std::out_of_range&) { + } + return {}; + } +}; + +template <> +class Serializer<float> { + Serializer() = delete; // delete ctor (static class) + public: + static json typeDescription() { return {{"tag", "Float"}}; } + static json toJSON(float val) { return val; } + static std::optional<float> fromJSON(std::string s) { + try { + return std::stof(s); + } catch (const std::invalid_argument&) { + } catch (const std::out_of_range&) { + } + return {}; + } +}; + +template <> +class Serializer<int> { + Serializer() = delete; // delete ctor (static class) + public: + static json typeDescription() { return {{"tag", "Int"}}; } + static json toJSON(int val) { return val; } + static std::optional<int> fromJSON(std::string s) { + try { + return std::stoi(s); + } catch (const std::invalid_argument&) { + } catch (const std::out_of_range&) { + } + return {}; + } +}; + +template <typename T> +class WriteProperty : public qthing::Component { + WriteProperty(const WriteProperty&) = delete; // delete cctor + WriteProperty& operator=(const WriteProperty&) = delete; // delete cassop + WriteProperty(WriteProperty&&) = delete; // delete mctor + WriteProperty& operator=(WriteProperty&&) = delete; // delete massop + + protected: + std::string name; + + public: + WriteProperty(std::string name) : name(name) {} + void set(T value) { + qthing::publish_message(DEVICE_NAMESPACE + "set/" + name, Serializer<T>::toJSON(value).dump()); + } + void init() override {} + void deinit() override {} + void describe(qthing::DeviceDescription& description) override { + description.addProperty(Serializer<T>::typeDescription(), name, false); + } +}; + +template <typename T> +class Property : public WriteProperty<T> { + Property(const Property&) = delete; // delete cctor + Property& operator=(const Property&) = delete; // delete cassop + Property(Property&&) = delete; // delete mctor + Property& operator=(Property&&) = delete; // delete massop + + private: + T defaultValue; + T value; + void mqtt_message_received(std::string arg) { + auto var = Serializer<T>::fromJSON(arg); + if (var) { + value = var.value(); + } else { + value = defaultValue; + } + onUpdate(value); + } + + public: + Property(std::string name, T default_value) + : WriteProperty<T>(name), defaultValue(default_value), value(default_value) {} + T get() { return value; } + void init() override { + qthing::add_message_callback(DEVICE_NAMESPACE + "update/" + this->name, + [&](std::string arg) { this->mqtt_message_received(arg); }); + } + void deinit() override { + // TODO + } + void describe(qthing::DeviceDescription& description) override { + description.addProperty(Serializer<T>::typeDescription(), this->name, true, Serializer<T>::toJSON(defaultValue)); + } + const Callback<void(const T&), Property<T>> onUpdate; +}; + +} // namespace qthing diff --git a/qthing/include/qthing/synchronized_variable.hpp b/qthing/include/qthing/synchronized_variable.hpp deleted file mode 100644 index fe5d37fec089b905185f454da6a0adf77f52cfd3..0000000000000000000000000000000000000000 --- a/qthing/include/qthing/synchronized_variable.hpp +++ /dev/null @@ -1,84 +0,0 @@ -#pragma once - -#include <optional> -#include <string> - -#include "qthing/core.hpp" -#include "qthing/qthing_legacy.hpp" - -namespace qthing { - -template <typename T> -class Serializer { - static_assert(T::is_serializable(), "Serializer can only work on classes, that support it."); - Serializer() = delete; // delete ctor (abstract class) - - public: - static std::string typeName() { return T::typeName(); } - static std::string serialize(const T& val) { return val.serialize(); } - static std::optional<T> deserialize(std::string s) { return T(s); } -}; - -template <> -class Serializer<double> { - Serializer() = delete; // delete ctor (static class) - public: - static std::string typeName() { return "double"; } - static std::string serialize(const double& val) { return std::to_string(val); } - static std::optional<double> deserialize(std::string s) { - try { - return std::stod(s); - } catch (const std::invalid_argument&) { - } catch (const std::out_of_range&) { - } - return {}; - } -}; - -template <> -class Serializer<float> { - Serializer() = delete; // delete ctor (static class) - public: - static std::string typeName() { return "float"; } - static std::string serialize(const float& val) { return std::to_string(val); } - static std::optional<float> deserialize(std::string s) { - try { - return std::stof(s); - } catch (const std::invalid_argument&) { - } catch (const std::out_of_range&) { - } - return {}; - } -}; - -template <typename T> -class SynchronizedVariable : public qthing::Component { - SynchronizedVariable(const SynchronizedVariable&) = delete; // delete cctor - SynchronizedVariable& operator=(const SynchronizedVariable&) = delete; // delete cassop - SynchronizedVariable(SynchronizedVariable&&) = delete; // delete mctor - SynchronizedVariable& operator=(SynchronizedVariable&&) = delete; // delete massop - - private: - std::string name; - T value; - void mqtt_message_received(std::string arg) { - auto var = Serializer<T>::deserialize(arg); - if (var) { - value = var.value(); - } - } - - public: - SynchronizedVariable(std::string name, T default_value) : name(name), value(default_value) {} - T get() { return value; } - void init() override { - qthing::add_message_callback("device/mausMotel/syncedVars/" + name, - [&](std::string arg) { this->mqtt_message_received(arg); }); - } - void deinit() override { throw std::runtime_error("Synced Variables cannot be removed."); } - void describe(qthing::DeviceDescription& description) override { - description.addSynchronizedVariable(Serializer<T>::typeName(), name); - } -}; - -} // namespace qthing diff --git a/qthing/mqtt.cpp b/qthing/mqtt.cpp index 278058331809cfd6fbb9331ba643af64286c3dca..d4a33988b7b0f07f00bb6636f6d4b30d737fe0aa 100644 --- a/qthing/mqtt.cpp +++ b/qthing/mqtt.cpp @@ -41,11 +41,11 @@ esp_mqtt_client_handle_t mqtt_client_start_c(mqtt_event_callback_t mqtt_event_ha #else .host = MQTT_SERVER, #endif - .lwt_topic = STATUS_TOPIC.c_str(), - .lwt_msg = STATUS_MESSAGE_OFFLINE.c_str(), + .lwt_topic = DEVICE_CONFIGURATION_TOPIC.c_str(), + .lwt_msg = "", .lwt_qos = 0, .lwt_retain = 1, - .lwt_msg_len = static_cast<int>(STATUS_MESSAGE_OFFLINE.length()), + .lwt_msg_len = 0, .keepalive = 60, #ifdef MQTT_USE_GLOBAL_CA_STORE .use_global_ca_store = true, @@ -72,7 +72,7 @@ mqtt_connected_callback_t connected_callback = []() {}; std::string multipart_topic; void publishDeviceDescription(const std::string& deviceDescription = getDriver<DescriptionDriver>()->get()) { - publish_message(DEVICE_NAMESPACE + "description", deviceDescription, true); + publish_message(DEVICE_CONFIGURATION_TOPIC, deviceDescription, true); } void on_message(const std::string& topic, const std::string& message) { @@ -101,6 +101,10 @@ esp_err_t mqtt_event_handler(esp_mqtt_event_handle_t event) { case MQTT_EVENT_CONNECTED: ESP_LOGI(TAG, "MQTT_EVENT_CONNECTED"); + result = esp_mqtt_client_publish(client, BEACON_TOPIC.c_str(), BEACON_MESSAGE_CONNECTED.c_str(), + BEACON_MESSAGE_CONNECTED.length(), 0, false); + ESP_LOGI(TAG, "Beacon sent (result %i)", result); + if (!callback_map.empty()) { for (std::map<std::string, message_callback_t>::iterator it = callback_map.begin(); it != callback_map.end(); ++it) { @@ -116,9 +120,6 @@ esp_err_t mqtt_event_handler(esp_mqtt_event_handle_t event) { } } } - result = esp_mqtt_client_publish(client, STATUS_TOPIC.c_str(), STATUS_MESSAGE_ONLINE.c_str(), - STATUS_MESSAGE_ONLINE.length(), 0, 1); - ESP_LOGI(TAG, "Online status published (result %i)", result); publishDeviceDescription(); ESP_LOGI(TAG, "Device description published");