From 8ed4e4bc49aff92230e043a8b3589929dd150d97 Mon Sep 17 00:00:00 2001
From: Jens Nolte <git@queezle.net>
Date: Thu, 29 Oct 2020 18:05:32 +0100
Subject: [PATCH] Update mqtt interaction to match current qd version

---
 qthing/include/qthing.hpp                     |   2 +-
 qthing/include/qthing/description.hpp         |   8 +-
 qthing/include/qthing/mqtt_common.hpp         |   7 +-
 qthing/include/qthing/property.hpp            | 134 ++++++++++++++++++
 .../include/qthing/synchronized_variable.hpp  |  84 -----------
 qthing/mqtt.cpp                               |  15 +-
 6 files changed, 154 insertions(+), 96 deletions(-)
 create mode 100644 qthing/include/qthing/property.hpp
 delete mode 100644 qthing/include/qthing/synchronized_variable.hpp

diff --git a/qthing/include/qthing.hpp b/qthing/include/qthing.hpp
index 63d670b..96975a0 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 5b1ff79..0264467 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 5dcc37a..42191c9 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 0000000..601b575
--- /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 fe5d37f..0000000
--- 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 2780583..d4a3398 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");
-- 
GitLab