diff --git a/platformio.ini b/platformio.ini index 493b24d..dfc100f 100644 --- a/platformio.ini +++ b/platformio.ini @@ -24,6 +24,7 @@ lib_deps = fastled/FastLED@^3.6.0 me-no-dev/ESPAsyncTCP@^1.2.2 #me-no-dev/ESP Async WebServer@^1.2.4 TODO: doesn't work well https://github.com/yubox-node-org/ESPAsyncWebServer.git + marvinroger/AsyncMqttClient@^0.9.0 arduino-libraries/NTPClient@^3.2.1 bblanchon/ArduinoJson@^7.1.0 diff --git a/src/application.cpp b/src/application.cpp index 4831b2e..fab3419 100644 --- a/src/application.cpp +++ b/src/application.cpp @@ -19,7 +19,20 @@ Application::Application(Storage &config_storage, Storage & config(config_storage.get()), preset_names(preset_names_storage.get()), preset_configs(preset_configs_storage.get()), custom_palette_config(custom_palette_storage.get()), night_mode_manager(night_mode_manager), - wave_provider(wave_provider), spectrum_provider(spectrum_provider), parametric_provider(parametric_provider) {} + wave_provider(wave_provider), spectrum_provider(spectrum_provider), parametric_provider(parametric_provider) { + + event_property_changed.subscribe(this, [this](auto sender, auto type, auto arg) { + if (sender == this) return; + + if (type == NotificationProperty::COLOR) { + this->change_color(*(uint32_t *) arg); + } else if (type == NotificationProperty::NIGHT_MODE_ENABLED) { + this->night_mode_manager.reset(); + } + + this->load(); + }); +} void Application::load() { const auto &preset = this->preset(); @@ -135,6 +148,37 @@ void Application::brightness_decrease() { D_PRINT("Decrease brightness"); } +void Application::change_color(uint32_t color) { + auto preset_index = preset_configs.count - 1; + auto &preset = preset_configs.presets[preset_index]; + + auto preset_name = "API Color"; + memcpy(preset_names.names[preset_index], preset_name, std::min(preset_names.length, strlen(preset_name))); + + preset.color_effect = ColorEffectEnum::SOLID; + config.preset_id = preset_index; + + auto hsv = rgb2hsv_approximate(color); + preset.speed = hsv.hue; + preset.scale = hsv.sat; + + load(); +} + +uint32_t Application::current_color() { + auto preset_index = preset_configs.count - 1; + auto &preset = preset_configs.presets[preset_index]; + + if (preset.color_effect == ColorEffectEnum::SOLID) { + CRGB color{}; + hsv2rgb_rainbow(CHSV(preset.speed, preset.scale, 255), color); + + return static_cast(color) & 0xffffff; + } + + return 0xffffff; +} + SignalProvider *Application::signal_provider() const { if (config.audio_config.is_spectrum()) { return spectrum_provider; diff --git a/src/application.h b/src/application.h index 074a907..526237e 100644 --- a/src/application.h +++ b/src/application.h @@ -1,7 +1,9 @@ #pragma once #include "config.h" +#include "metadata.h" #include "misc/storage.h" +#include "misc/event_topic.h" #include "night_mode.h" class Application { @@ -29,6 +31,8 @@ class Application { CRGBPalette16 current_palette = CRGBPalette16(); + EventTopic event_property_changed; + Application(Storage &config_storage, Storage &preset_names_storage, Storage &preset_configs_storage, Storage &custom_palette_storage, NightModeManager &night_mode_manager, @@ -44,10 +48,13 @@ class Application { void brightness_increase(); void brightness_decrease(); + void change_color(uint32_t color); + uint32_t current_color(); + void restart(); inline PresetConfig &preset() { return preset_configs.presets[config.preset_id]; } [[nodiscard]] BrightnessSettings get_brightness_settings() const; - SignalProvider* signal_provider() const; + SignalProvider *signal_provider() const; }; \ No newline at end of file diff --git a/src/config.h b/src/config.h index 975e3b0..101930d 100644 --- a/src/config.h +++ b/src/config.h @@ -7,7 +7,7 @@ class NtpTime; -enum class AppState { +enum class AppState: uint8_t { INITIALIZATION, NORMAL, CALIBRATION, diff --git a/src/constants.h b/src/constants.h index fa07fa2..e431a6c 100644 --- a/src/constants.h +++ b/src/constants.h @@ -47,3 +47,21 @@ #define AUDIO_GAIN (1u) #define AUDIO_GATE (1u) #define AUDIO_WINDOW_DURATION (5000u) + + +#define MQTT (0u) // MQTT protocol Enabled + +#define MQTT_CONNECTION_TIMEOUT (15000u) // Connection attempt timeout to MQTT server +#define MQTT_RECONNECT_TIMEOUT (5000u) // Time before new reconnection attempt to MQTT server + +#define MQTT_PREFIX MDNS_NAME +#define MQTT_TOPIC_BRIGHTNESS MQTT_PREFIX "/brightness" +#define MQTT_TOPIC_POWER MQTT_PREFIX "/power" +#define MQTT_TOPIC_COLOR MQTT_PREFIX "/color" +#define MQTT_TOPIC_NIGHT_MODE MQTT_PREFIX "/night_mode" + +#define MQTT_OUT_PREFIX MQTT_PREFIX "/out" +#define MQTT_OUT_TOPIC_BRIGHTNESS MQTT_OUT_PREFIX "/brightness" +#define MQTT_OUT_TOPIC_POWER MQTT_OUT_PREFIX "/power" +#define MQTT_OUT_TOPIC_COLOR MQTT_OUT_PREFIX "/color" +#define MQTT_OUT_TOPIC_NIGHT_MODE MQTT_OUT_PREFIX "/night_mode" \ No newline at end of file diff --git a/src/credentials.h b/src/credentials.h index 28f9a79..3eeb588 100644 --- a/src/credentials.h +++ b/src/credentials.h @@ -5,3 +5,8 @@ #define CREDENTIAL_AUTH_USER "esp_lamp" #define CREDENTIAL_AUTH_PASSWORD "password" + +#define MQTT_HOST "example.com" +#define MQTT_PORT (1234u) +#define MQTT_USER "esp_user" +#define MQTT_PASSWORD "esp_pass" \ No newline at end of file diff --git a/src/debug.h b/src/debug.h index b7ce9f5..1c77a6c 100644 --- a/src/debug.h +++ b/src/debug.h @@ -3,20 +3,40 @@ #define __DEBUG_LEVEL_VERBOSE 0 #ifdef DEBUG +#include + #define D_PRINT(x) Serial.println(x) #define D_PRINTF(...) Serial.printf(__VA_ARGS__) #define D_WRITE(x) Serial.print(x) +#define D_PRINT_HEX(ptr, length) \ + D_WRITE("HEX: "); \ + for (unsigned int i = 0; i < length; ++i) { \ + D_PRINTF("%02X ", (ptr)[i]); \ + } \ + D_PRINT() + +#define D_TIME_STRING(unix_time) ([](time_t time) { \ + static char buffer[20]; \ + struct tm* time_info; \ + time_info = localtime(&time); \ + strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", time_info); \ + return buffer; \ +}(unix_time)) + #if DEBUG_LEVEL == __DEBUG_LEVEL_VERBOSE #define VERBOSE(ARG) ARG #else #define VERBOSE(ARG) #endif - #else #define D_PRINT(x) #define D_PRINTF(...) #define D_WRITE(x) +#define D_PRINT_HEX(ptr, length) #define VERBOSE(ARG) -#endif \ No newline at end of file +#endif + +template +const char *__debug_enum_str(T) { return "Unsupported"; } \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index e0c97b5..1869308 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -21,6 +21,7 @@ #include "network/wifi.h" #include "network/web.h" #include "network/protocol/server/api.h" +#include "network/protocol/server/mqtt.h" #include "network/protocol/server/udp.h" #include "network/protocol/server/ws.h" @@ -82,6 +83,7 @@ WifiManager wifi_manager; WebServer web_server(WEB_PORT); ApiWebServer api_server(app); +MqttServer mqtt_server(app); UdpServer udp_server(app); WebSocketServer ws_server(app); @@ -312,6 +314,7 @@ void service_loop(void *) { udp_server.begin(UDP_PORT); ws_server.begin(web_server); + if constexpr (MQTT) mqtt_server.begin(); web_server.on("/debug", HTTP_GET, [](AsyncWebServerRequest *request) { char result[320] = {}; @@ -344,6 +347,8 @@ void service_loop(void *) { udp_server.handle_incoming_data(); ws_server.handle_incoming_data(); + + if constexpr (MQTT) mqtt_server.handle_connection(); break; } diff --git a/src/metadata.cpp b/src/metadata.cpp new file mode 100644 index 0000000..446017d --- /dev/null +++ b/src/metadata.cpp @@ -0,0 +1,71 @@ +#include "metadata.h" + +#include + +#include "constants.h" + +std::map PacketTypeMetadataMap = { + { + PacketType::POWER_ON, + { + NotificationProperty::POWER, PacketType::POWER_ON, + offsetof(Config, power), sizeof(Config::power), + MQTT_TOPIC_POWER, MQTT_OUT_TOPIC_POWER, + } + }, + { + PacketType::POWER_OFF, + { + NotificationProperty::POWER, PacketType::POWER_OFF, + offsetof(Config, power), sizeof(Config::power), + MQTT_TOPIC_POWER, MQTT_OUT_TOPIC_POWER, + } + }, + { + PacketType::MAX_BRIGHTNESS, + { + NotificationProperty::BRIGHTNESS, PacketType::MAX_BRIGHTNESS, + offsetof(Config, max_brightness), sizeof(Config::max_brightness), + MQTT_TOPIC_BRIGHTNESS, MQTT_OUT_TOPIC_BRIGHTNESS, + } + }, + { + PacketType::NIGHT_MODE_ENABLED, + { + NotificationProperty::NIGHT_MODE_ENABLED, PacketType::NIGHT_MODE_ENABLED, + offsetof(Config, night_mode.enabled), sizeof(Config::night_mode.enabled), + MQTT_TOPIC_NIGHT_MODE, MQTT_OUT_TOPIC_NIGHT_MODE, + } + }, +}; + +std::map> PropertyMetadataMap = + _build_property_metadata_map(PacketTypeMetadataMap); + +std::map TopicPropertyMetadata = + _build_topic_property_metadata_map(PacketTypeMetadataMap); + +std::map> _build_property_metadata_map( + std::map &packetMapping) { + std::map> result; + + for (auto &[packetType, metadata]: packetMapping) { + std::vector &prop = result[metadata.property]; + prop.push_back(metadata); + } + + return result; +} + +std::map _build_topic_property_metadata_map( + std::map &packetMapping) { + std::map result; + + for (auto &[packetType, metadata]: packetMapping) { + if (metadata.mqtt_in_topic == nullptr) continue; + + result[metadata.mqtt_in_topic] = metadata; + } + + return result; +} \ No newline at end of file diff --git a/src/metadata.h b/src/metadata.h new file mode 100644 index 0000000..a318d3e --- /dev/null +++ b/src/metadata.h @@ -0,0 +1,42 @@ +#pragma once + +#include +#include +#include +#include + +#include + +#include "config.h" +#include "network/enum.h" +#include "utils/enum.h" + +class Application; + +MAKE_ENUM(NotificationProperty, uint8_t, + POWER, 0, + BRIGHTNESS, 1, + COLOR, 2, + NIGHT_MODE_ENABLED, 3, +) + +struct PropertyMetadata { + NotificationProperty property; + PacketType packet_type; + + uint8_t value_offset{}; + uint8_t value_size{}; + + const char *mqtt_in_topic = nullptr; + const char *mqtt_out_topic = nullptr; +}; + +extern std::map PacketTypeMetadataMap; +extern std::map> PropertyMetadataMap; +extern std::map TopicPropertyMetadata; + +std::map> _build_property_metadata_map( + std::map &packetMapping); + +std::map _build_topic_property_metadata_map( + std::map &packetMapping); \ No newline at end of file diff --git a/src/misc/event_topic.h b/src/misc/event_topic.h new file mode 100644 index 0000000..abf2150 --- /dev/null +++ b/src/misc/event_topic.h @@ -0,0 +1,72 @@ +#pragma once + +#include +#include +#include +#include +#include + +template>> +class Subscription { +public: + typedef std::function SubscriptionCallback; + + Subscription(void *subscriber, SubscriptionCallback callback) : _subscriber(subscriber), _callback(callback) {} + + inline void call(void *sender, T type, void *arg) const { _callback(sender, type, arg); } + + bool operator==(const Subscription &other) const { + return _subscriber == other._subscriber; + } + + struct HashFunction { + size_t operator()(const Subscription &sub) const { + return std::hash()(sub._subscriber); + } + }; + +private: + void *_subscriber; + SubscriptionCallback _callback; +}; + +template>> +class EventTopic { + typedef std::unordered_set, typename Subscription::HashFunction> SubscriptionsSetT; + + std::unordered_map _subscribers{}; + SubscriptionsSetT _broadcast_subscribers{}; + +public: + typedef typename Subscription::SubscriptionCallback SubscriptionCallback; + + void subscribe(void *target, T type, SubscriptionCallback callback); + void subscribe(void *target, SubscriptionCallback callback); + + void publish(void *sender, T type, void *arg = nullptr); +}; + +template +void EventTopic::subscribe(void *target, EventTopic::SubscriptionCallback callback) { + _broadcast_subscribers.emplace(target, callback); +} + +template +void EventTopic::subscribe(void *target, T type, EventTopic::SubscriptionCallback callback) { + auto &type_set = _subscribers[type]; + type_set.emplace(Subscription(target, callback)); +} + +template +void EventTopic::publish(void *sender, T type, void *arg) { + for (auto &sub: _broadcast_subscribers) { + sub.call(sender, type, arg); + } + + auto type_set = _subscribers.find(type); + if (type_set == _subscribers.end()) return; + + for (auto &sub: type_set->second) { + sub.call(sender, type, arg); + } +} diff --git a/src/network/protocol/server/api.cpp b/src/network/protocol/server/api.cpp index 38cd565..6b0642c 100644 --- a/src/network/protocol/server/api.cpp +++ b/src/network/protocol/server/api.cpp @@ -43,53 +43,16 @@ void ApiWebServer::begin(WebServer &server) { server.on((_path + String("/color")).c_str(), HTTP_GET, [this](AsyncWebServerRequest *request) { if (!request->hasArg("value")) { D_PRINT("Request color status"); - auto hsv = _app.preset().color_effect == ColorEffectEnum::SOLID - ? CHSV(_app.preset().speed, _app.preset().scale, 255) - : CHSV(255, 255, 225); - - CRGB color{}; - hsv2rgb_rainbow(hsv, color); + auto color = _app.current_color(); return response_with_json(request, JsonPropListT{ {"status", "ok"}, - {"value", static_cast(color) & 0xffffff}, - - {"hue", hsv.hue}, - {"sat", hsv.sat}, - {"bri", hsv.val}, + {"value", color} }); } - PresetConfig *preset = nullptr; - if (_app.preset().color_effect == ColorEffectEnum::SOLID) { - preset = &_app.preset(); - } else { - for (int i = 0; i < _app.preset_configs.count; ++i) { - if (_app.preset_configs.presets[i].color_effect == ColorEffectEnum::SOLID) { - _app.change_preset(i); - preset = &_app.preset_configs.presets[i]; - break; - } - } - - if (preset == nullptr) { - return response_with_json_status(request, "error"); - } - } - - auto new_color = CRGB(request->arg("value").toInt()); - auto hsv = rgb2hsv_approximate(new_color); - preset->speed = hsv.hue; - preset->scale = hsv.sat; - - _app.load(); - - response_with_json(request, { - {"status", "ok"}, - {"hue", hsv.hue}, - {"sat", hsv.sat}, - {"bri", hsv.val}, - }); + _app.change_color(request->arg("value").toInt()); + response_with_json_status(request, "ok"); }); } diff --git a/src/network/protocol/server/base.cpp b/src/network/protocol/server/base.cpp index 4b25380..858bcd9 100644 --- a/src/network/protocol/server/base.cpp +++ b/src/network/protocol/server/base.cpp @@ -1,6 +1,7 @@ #include "base.h" #include "application.h" +#include "metadata.h" #include "fx/fx.h" #include "fx/palette.h" @@ -12,27 +13,22 @@ Response ServerBase::handle_packet_data(const uint8_t *buffer, uint16_t length) const auto [header, data] = parseResponse.packet; + Response response; if (header->type >= PacketType::DISCOVERY) { - auto response = process_command(*header); + response = process_command(*header); if (response.is_ok() && header->type >= PacketType::POWER_OFF) app().update(); - - return response; } else if (header->type >= PacketType::PALETTE_LIST) { - auto response = process_data_request(*header); + response = process_data_request(*header); if (response.is_ok()) D_PRINTF("Data request %u, size: %u \n", (uint8_t) header->type, response.body.buffer.size); - - return response; } else if (header->type >= PacketType::CALIBRATION_R) { - auto response = calibrate(*header, data); + response = calibrate(*header, data); if (response.is_ok()) { D_PRINTF("Color correction: %Xu\n", app().config.color_correction); app().config_storage.save(); } - - return response; } else { - auto response = update_parameter(*header, data); + response = update_parameter(*header, data); if (response.is_ok()) { if (header->type >= PacketType::NIGHT_MODE_ENABLED && header->type <= PacketType::NIGHT_MODE_INTERVAL) { app().night_mode_manager.reset(); @@ -44,9 +40,16 @@ Response ServerBase::handle_packet_data(const uint8_t *buffer, uint16_t length) app().update(); } + } - return response; + if (response.is_ok()) { + auto iter = PacketTypeMetadataMap.find(header->type); + if (iter != PacketTypeMetadataMap.end()) { + _app.event_property_changed.publish(this, iter->second.property); + } } + + return response; } Response ServerBase::update_parameter(const PacketHeader &header, const void *data) { diff --git a/src/network/protocol/server/mqtt.h b/src/network/protocol/server/mqtt.h new file mode 100644 index 0000000..9fb75b2 --- /dev/null +++ b/src/network/protocol/server/mqtt.h @@ -0,0 +1,268 @@ +#include + +#include "application.h" +#include "metadata.h" +#include "constants.h" +#include "credentials.h" +#include "debug.h" + +enum class MqttServerState : uint8_t { + UNINITIALIZED, + CONNECTING, + CONNECTED, + DISCONNECTED +}; + +class MqttServer { + AsyncMqttClient _mqttClient; + + Application &_app; + + MqttServerState _state = MqttServerState::UNINITIALIZED; + unsigned long _state_change_time = 0; + unsigned long _last_connection_attempt_time = 0; + +public: + explicit MqttServer(Application &app); + + void begin(); + + void handle_connection(); + +protected: + inline Application &app() { return _app; } + + void _on_connect(bool sessionPresent); + void _on_disconnect(AsyncMqttClientDisconnectReason reason); + void _on_message(char *topic, char *payload, AsyncMqttClientMessageProperties properties, size_t len, size_t index, size_t total); + + void _subscribe(const char *topic, uint8_t qos); + void _publish(const char *topic, uint8_t qos, const char *payload, size_t length); + + void _process_message(const String &topic, const String &payload); + void _process_notification(NotificationProperty prop); + + void _change_state(MqttServerState state); + void _connect(); + + template + void _set_value(const PropertyMetadata &meta, const T &value); + + template + void _notify_value_changed(const PropertyMetadata &meta); + + + virtual void _transform_topic_payload(const String &topic, String &payload) {}; + virtual void _transform_topic_notification(const PropertyMetadata &meta, String &payload) {}; + + virtual void _after_message_process(const PropertyMetadata &meta); +}; + +MqttServer::MqttServer(Application &app) : _app(app) {} + +void MqttServer::begin() { + if (_state != MqttServerState::UNINITIALIZED) return; + + using namespace std::placeholders; + + _mqttClient.onConnect(std::bind(&MqttServer::_on_connect, this, _1)); + _mqttClient.onDisconnect(std::bind(&MqttServer::_on_disconnect, this, _1)); + _mqttClient.onMessage(std::bind(&MqttServer::_on_message, this, _1, _2, _3, _4, _5, _6)); + + _mqttClient.setServer(MQTT_HOST, MQTT_PORT); + _mqttClient.setCredentials(MQTT_USER, MQTT_PASSWORD); + + _app.event_property_changed.subscribe(this, [this](auto, auto prop, auto) { + _process_notification(prop); + }); + + _connect(); +} + + +void MqttServer::_connect() { + _change_state(MqttServerState::CONNECTING); + + _last_connection_attempt_time = millis(); + _mqttClient.connect(); +} + + +void MqttServer::_change_state(MqttServerState state) { + _state = state; + _state_change_time = millis(); +} + + +void MqttServer::handle_connection() { + if (_state == MqttServerState::DISCONNECTED && (millis() - _last_connection_attempt_time) > MQTT_RECONNECT_TIMEOUT) { + D_PRINT("MQTT Reconnecting..."); + + _connect(); + } + + if (_state == MqttServerState::CONNECTING && !_mqttClient.connected() && (millis() - _state_change_time) > MQTT_CONNECTION_TIMEOUT) { + D_PRINT("MQTT Connection timeout"); + + _change_state(MqttServerState::DISCONNECTED); + _mqttClient.disconnect(true); + } +} + + +void MqttServer::_on_connect(bool) { + D_PRINT("MQTT Connected"); + + for (const auto &[topic, _]: TopicPropertyMetadata) { + _subscribe(topic.c_str(), 1); + } + + _subscribe(MQTT_TOPIC_COLOR, 1); + + _last_connection_attempt_time = millis(); + _change_state(MqttServerState::CONNECTED); +} + + +void MqttServer::_on_disconnect(AsyncMqttClientDisconnectReason reason) { + D_PRINTF("MQTT Disconnected. Reason %u\n", (uint8_t) reason); + + _change_state(MqttServerState::DISCONNECTED); +} + + +void MqttServer::_on_message(char *topic, char *payload, AsyncMqttClientMessageProperties properties, + size_t len, size_t index, size_t total) { + D_PRINTF("MQTT Received: %s: \"%.*s\"\n", topic, len, payload); + + String topic_str(topic); + String payload_str{}; + payload_str.concat(payload, len); + + _transform_topic_payload(topic_str, payload_str); + + if (topic_str == MQTT_TOPIC_COLOR) { + uint32_t color = payload_str.toInt(); + _app.event_property_changed.publish(this, NotificationProperty::COLOR, &color); + } else { + _process_message(topic_str, payload_str); + } +} + + +void MqttServer::_subscribe(const char *topic, uint8_t qos) { + _mqttClient.subscribe(topic, qos); + D_PRINTF("MQTT Subscribe: \"%s\"\n", topic); +} + + +void MqttServer::_publish(const char *topic, uint8_t qos, const char *payload, size_t length) { + if (_state != MqttServerState::CONNECTED) { + D_PRINTF("MQTT Not connected. Skip message to %s\n", topic); + return; + }; + + _mqttClient.publish(topic, qos, true, payload, length); + + D_PRINTF("MQTT Publish: %s: \"%.*s\"\n", topic, length, payload); +} + + +void MqttServer::_process_message(const String &topic, const String &payload) { + auto iter_meta = TopicPropertyMetadata.find(topic); + if (iter_meta == TopicPropertyMetadata.end()) { + D_PRINTF("MQTT: Message in unsupported topic: %s\n", topic.c_str()); + return; + } + + const auto meta = iter_meta->second; + switch (meta.value_size) { + case 8: + _set_value(meta, (uint64_t) payload.toInt()); + break; + + case 4: + _set_value(meta, (uint32_t) payload.toInt()); + break; + + case 2: + _set_value(meta, (uint16_t) payload.toInt()); + break; + + case 1: + _set_value(meta, (uint8_t) payload.toInt()); + break; + + default: + D_PRINTF("MQTT: Unsupported value size: %u. topic: %s\n", meta.value_size, topic.c_str()); + return; + } + + _after_message_process(meta); +} + + +void MqttServer::_after_message_process(const PropertyMetadata &meta) { + _app.event_property_changed.publish(this, meta.property); +} + + +void MqttServer::_process_notification(NotificationProperty prop) { + if (prop == NotificationProperty::COLOR) { + String str(_app.current_color()); + _publish(MQTT_OUT_TOPIC_COLOR, 1, str.c_str(), str.length()); + return; + } + + auto iter_meta = PropertyMetadataMap.find(prop); + if (iter_meta == PropertyMetadataMap.end()) return; + + const auto meta = iter_meta->second[0]; + if (meta.mqtt_out_topic == nullptr) return; + + D_PRINTF("MQTT: Processing notification for %s\n", __debug_enum_str(prop)); + + switch (meta.value_size) { + case 8: + _notify_value_changed(meta); + break; + + case 4: + _notify_value_changed(meta); + break; + + case 2: + _notify_value_changed(meta); + break; + + case 1: + _notify_value_changed(meta); + break; + + default: + D_PRINTF("MQTT: Unsupported value size: %u\n", meta.value_size); + return; + } +} + + +template +void MqttServer::_notify_value_changed(const PropertyMetadata &meta) { + T value; + memcpy(&value, (uint8_t *) &_app.config + meta.value_offset, sizeof(value)); + + auto value_str = String(value); + _transform_topic_notification(meta, value_str); + + _publish(meta.mqtt_out_topic, 1, value_str.c_str(), value_str.length()); +} + + +template +void MqttServer::_set_value(const PropertyMetadata &meta, const T &value) { + memcpy((uint8_t *) &_app.config + meta.value_offset, &value, sizeof(value)); + + D_PRINTF("Set %s = ", __debug_enum_str(meta.property)); + D_PRINT_HEX(((uint8_t *) (&value)), sizeof(value)); +} + diff --git a/src/network/protocol/type.h b/src/network/protocol/type.h index 1ef29ea..ac567ba 100644 --- a/src/network/protocol/type.h +++ b/src/network/protocol/type.h @@ -29,9 +29,9 @@ struct Response { ResponseCode code; const char *str; - const struct { + struct { uint16_t size; - uint8_t *data; + const uint8_t *data; } buffer; } body; diff --git a/src/utils/enum.h b/src/utils/enum.h new file mode 100644 index 0000000..e01040f --- /dev/null +++ b/src/utils/enum.h @@ -0,0 +1,34 @@ +#define __PARENS () + +#define __EXPAND(...) __EXPAND4(__EXPAND4(__EXPAND4(__EXPAND4(__VA_ARGS__)))) +#define __EXPAND4(...) __EXPAND3(__EXPAND3(__EXPAND3(__EXPAND3(__VA_ARGS__)))) +#define __EXPAND3(...) __EXPAND2(__EXPAND2(__EXPAND2(__EXPAND2(__VA_ARGS__)))) +#define __EXPAND2(...) __EXPAND1(__EXPAND1(__EXPAND1(__EXPAND1(__VA_ARGS__)))) +#define __EXPAND1(...) __VA_ARGS__ + +#define __MAKE_ENUM_OPTS(MACRO, Name, ...) __EXPAND(__MAKE_ENUM_IMPL(MACRO, Name, __VA_ARGS__)) +#define __MAKE_ENUM_IMPL(MACRO, Name, _1, _2, ...) MACRO(Name, _1, _2) \ + __VA_OPT__(__MAKE_ENUM_IMPL_AGAIN __PARENS (MACRO, Name, __VA_ARGS__)) +#define __MAKE_ENUM_IMPL_AGAIN() __MAKE_ENUM_IMPL + +#define __ENUM_VALUE(Name, _1, _2) _1 = _2, +#define __ENUM_CASE(Name, _1, _2) case Name::_1: return #_1; + + +#ifdef DEBUG +#define MAKE_ENUM(Name, Type, ...) \ + enum class Name: Type { __MAKE_ENUM_OPTS(__ENUM_VALUE, Name, __VA_ARGS__) }; \ + constexpr const char * __debug_enum_str(Name _e) { \ + switch (_e) { \ + __MAKE_ENUM_OPTS(__ENUM_CASE, Name, __VA_ARGS__) \ + default: \ + return "unknown"; \ + } \ + } +#else +#define MAKE_ENUM(Name, Type, ...) \ + enum class Name: Type { __MAKE_ENUM_OPTS(__ENUM_VALUE, Name, __VA_ARGS__) }; \ + constexpr const char * __debug_enum_str(Name _e) { \ + return "*** Debug info deleted ***"; \ + } +#endif \ No newline at end of file diff --git a/upload_fs.sh b/upload_fs.sh index 01f634a..62aea1e 100755 --- a/upload_fs.sh +++ b/upload_fs.sh @@ -1,6 +1,6 @@ #!/bin/bash -OTA=${OTA=0} +OTA=${OTA:0} set +v @@ -11,7 +11,7 @@ npm run build || (echo "Failed" && exit 2) cd .. echo "Compress..." -gzip -9 ./data/* +gzip -9 -r ./data/* echo "Uploading..." @@ -20,5 +20,5 @@ if (("$OTA" == 1)); then pio run -t uploadfs -e ota "$@" else echo "*** WIRE mode selected ***" - pio run -t uploadfs "$@" + pio run -t uploadfs -e release "$@" fi \ No newline at end of file