From 8a76e869cb8fae79a995b21f3078aef62eb4fe7f Mon Sep 17 00:00:00 2001 From: Tianyu Liu Date: Fri, 29 Aug 2025 19:17:09 +0200 Subject: [PATCH] wip --- include/config.h | 4 ++-- lib/communication/mqtt.cpp | 38 +++++++++++++++++++++++++++----------- lib/communication/mqtt.h | 1 + lib/light/light.cpp | 19 ++++++++++++++----- lib/light/light.h | 27 ++------------------------- lib/light/lightinfo.h | 0 lib/network/network.cpp | 11 +++++++++++ lib/network/network.h | 1 + platformio.ini | 2 +- src/states.hpp | 11 +++++++++++ 10 files changed, 70 insertions(+), 44 deletions(-) delete mode 100644 lib/light/lightinfo.h diff --git a/include/config.h b/include/config.h index e2e2e65..b5c5ccb 100644 --- a/include/config.h +++ b/include/config.h @@ -8,8 +8,8 @@ inline constexpr uint8_t ledPinB = 18; inline constexpr uint8_t ledPinCW = 19; inline constexpr uint8_t ledPinWW = 21; -inline constexpr std::string_view hostName = "smart-rgb-dev"; -inline constexpr std::string_view friendlyName = "Smart RGB Dev"; +inline constexpr std::string_view hostName = "smart-rgb"; +inline constexpr std::string_view friendlyName = "Smart RGB"; inline constexpr uint32_t maxNumberOfStates = 10; inline constexpr std::string_view mqttBroker = "10.238.75.81"; \ No newline at end of file diff --git a/lib/communication/mqtt.cpp b/lib/communication/mqtt.cpp index a22929e..f4d1ce0 100644 --- a/lib/communication/mqtt.cpp +++ b/lib/communication/mqtt.cpp @@ -1,5 +1,6 @@ #include #include +#include "debugutil.hpp" #include "mqtt.h" constexpr uint16_t BUFFER_SIZE = 2048; @@ -26,12 +27,27 @@ void Mqtt::subscribe(const std::string& topic, MqttCallback callback) { if (mqttClient.connected()) { if (mqttClient.subscribe(topic.c_str())) { callbacks[topic] = callback; - Serial.printf("Subscribed to topic: %s\n", topic.c_str()); + Debug::printf("Subscribed to topic: %s\n", topic.c_str()); } else { - Serial.printf("Failed to subscribe to topic: %s\n", topic.c_str()); + Debug::printf("Failed to subscribe to topic: %s\n", topic.c_str()); } } else { - Serial.println("MQTT client is not connected. Cannot subscribe."); + Debug::println("MQTT client is not connected. Cannot subscribe."); + } +} + +void Mqtt::unsubscribe(const std::string& topic) { + if (mqttClient.connected()) { + if (mqttClient.unsubscribe(topic.c_str())) { + Debug::printf("Unsubscribed from topic: %s\n", topic.c_str()); + } else { + Debug::printf("Failed to unsubscribe from topic: %s\n", topic.c_str()); + } + } else { + Debug::println("MQTT client is not connected. Unsubscribe skipped."); + } + if (callbacks.find(topic) != callbacks.end()) { + callbacks.erase(topic); } } @@ -39,10 +55,10 @@ void Mqtt::publish(const std::string& topic, const std::string& payload, bool re if (mqttClient.connected()) { if (mqttClient.publish(topic.c_str(), payload.c_str(), retain)) { } else { - Serial.printf("Failed to publish to topic: %s\n", topic.c_str(), payload.c_str()); + Debug::printf("Failed to publish to topic: %s\n", topic.c_str(), payload.c_str()); } } else { - Serial.println("MQTT client is not connected. Cannot publish."); + Debug::println("MQTT client is not connected. Cannot publish."); } } @@ -50,21 +66,21 @@ void Mqtt::poll() { if (mqttClient.connected()) { mqttClient.loop(); // Process incoming messages } else { - Serial.println("MQTT client is not connected. Polling skipped."); + Debug::println("MQTT client is not connected. Polling skipped."); } } void Mqtt::checkConnection() { if (!mqttClient.connected()) { - Serial.println("MQTT client is not connected. Attempting to reconnect..."); + Debug::println("MQTT client is not connected. Attempting to reconnect..."); if (mqttClient.connect(Mqtt::clientId.c_str(), Mqtt::username.c_str(), Mqtt::password.c_str())) { - Serial.println("Reconnected to MQTT broker successfully."); + Debug::println("Reconnected to MQTT broker successfully."); for (const auto& callback : Mqtt::callbacks) { mqttClient.subscribe(callback.first.c_str()); } Mqtt::isConnected = true; } else { - Serial.printf("Failed to reconnect to MQTT broker, rc=%d\n", mqttClient.state()); + Debug::printf("Failed to reconnect to MQTT broker, rc=%d\n", mqttClient.state()); Mqtt::isConnected = false; } } @@ -82,10 +98,10 @@ void Mqtt::connect(std::string brokerIp, uint16_t brokerPort, std::string client mqttClient.setBufferSize(BUFFER_SIZE); if (mqttClient.connect(Mqtt::clientId.c_str(), Mqtt::username.c_str(), Mqtt::password.c_str())) { - Serial.println("Connected to MQTT broker"); + Debug::println("Connected to MQTT broker"); Mqtt::initialized = true; Mqtt::isConnected = true; } else { - Serial.printf("Failed to connect to MQTT broker, rc=%d\n", mqttClient.state()); + Debug::printf("Failed to connect to MQTT broker, rc=%d\n", mqttClient.state()); } } diff --git a/lib/communication/mqtt.h b/lib/communication/mqtt.h index 5861f50..c8c0a06 100644 --- a/lib/communication/mqtt.h +++ b/lib/communication/mqtt.h @@ -12,6 +12,7 @@ public: static void checkConnection(); static void publish(const std::string& topic, const std::string& payload, bool retain = false); static void subscribe(const std::string& topic, MqttCallback callback); + static void unsubscribe(const std::string& topic); static void mqttCb(char* topic, uint8_t* payload, unsigned int length); private: diff --git a/lib/light/light.cpp b/lib/light/light.cpp index 10ae1ff..4434844 100644 --- a/lib/light/light.cpp +++ b/lib/light/light.cpp @@ -21,8 +21,7 @@ Light::Light(Pin* pinR, Pin* pinG, Pin* pinB, std::string uniqueId) lightType = LightType::rgb; uint8_t bits = pinR->getLedResolutionBits(); maxPwm = (bits >= 1 && bits <= 31) ? ((1u << bits) - 1u) : 255u; - publishInitialState(); - subscribeToMqttTopics(); + notifyOnline(); } Light::Light(Pin* pinR, Pin* pinG, Pin* pinB, Pin* pinCW, Pin* pinWW, std::string uniqueId) @@ -32,7 +31,7 @@ Light::Light(Pin* pinR, Pin* pinG, Pin* pinB, Pin* pinCW, Pin* pinWW, std::strin uint8_t bits = pinR->getLedResolutionBits(); maxPwm = (bits >= 1 && bits <= 31) ? ((1u << bits) - 1u) : 255u; publishInitialState(); - subscribeToMqttTopics(); + notifyOnline(); } void Light::publishInitialState() { @@ -42,7 +41,7 @@ void Light::publishInitialState() { deviceInfo["name"] = this->deviceInfo.name; deviceInfo["model"] = this->deviceInfo.model; JsonArray identifiers = deviceInfo["identifiers"].to(); - identifiers.add(this->deviceInfo.identifier + this->lightInfo.uniqueId); + identifiers.add(this->lightInfo.uniqueId); deviceInfo["sw_version"] = this->deviceInfo.swVersion; deviceInfo["manufacturer"] = this->deviceInfo.manufacturer; @@ -159,11 +158,21 @@ void Light::operatePin() { if (pinWW) pinWW->setLedLevel(wwSetpoint); } -void Light::subscribeToMqttTopics() { +void Light::notifyOnline() { Mqtt::subscribe(lightInfo.commandTopic, [this](uint8_t* payload, int length) { std::string command(reinterpret_cast(payload), length); handleCommand(command); }); + publishInitialState(); +} + +void Light::notifyOffline() { + Mqtt::unsubscribe(lightInfo.commandTopic); + JsonDocument availabilityDoc; + availabilityDoc["availability"] = Availability.notAvailable; + std::string availabilityJson; + serializeJson(availabilityDoc, availabilityJson); + Mqtt::publish(lightInfo.availabilityTopic, availabilityJson); } void Light::handleCommand(const std::string& command) { diff --git a/lib/light/light.h b/lib/light/light.h index f0015a1..96ce2b6 100644 --- a/lib/light/light.h +++ b/lib/light/light.h @@ -7,35 +7,11 @@ struct LightInfo { std::string uniqueId; const std::string name = "Smart RGB Light"; const std::string discoveryTopic = "homeassistant/light/smart_rgb_light/light/config"; - const std::string baseTopic = "studiotj/smart-rgb/light"; const std::string availabilityTopic = "studiotj/smart-rgb/light/status"; const std::string stateTopic = "studiotj/smart-rgb/light/state"; const std::string jsonAttributesTopic = "studiotj/smart-rgb/light/attributes"; const std::string stateValueTemplate = "{{ value_json.state }}"; const std::string commandTopic = "studiotj/smart-rgb/light/state/set"; - const std::string brightnessCommandTopic = "studiotj/smart-rgb/light/brightness/set"; - const std::string brightnessValueTemplate = "{{ value_json.brightness }}"; - const std::string colorTempCommandTopic = "studiotj/smart-rgb/light/color_temp/set"; - const std::string colorTempKelvinTopic = "studiotj/smart-rgb/light/color_temp_kelvin/set"; - const std::string colorTempStateTopic = "studiotj/smart-rgb/light/color_temp/state"; - const std::string colorTempValueTemplate = "{{ value_json.color_temp }}"; - const std::string hsCommandTopic = "studiotj/smart-rgb/light/hs/set"; - const std::string hsCommandTemplate = "{{ value_json.hs_cmd }}"; - const std::string hsStateTopic = "studiotj/smart-rgb/light/hs/state"; - const std::string hsValueTemplate = " {{ value_json.hs_value }}"; - const std::string rgbCommandTopic = "studiotj/smart-rgb/light/rgb/set"; - const std::string rgbCommandTemplate = "{{ {'rgb': [red, green, blue]} | to_json }}"; - const std::string rgbStateTopic = "studiotj/smart-rgb/light/rgb/state"; - const std::string rgbValueTemplate = "{{ value_json.rgb | join(',') }}"; - const std::string rgbwCommandTopic = "studiotj/smart-rgb/light/rgbw/set"; - const std::string rgbwCommandTemplate = "{{ value_json.rgbw_cmd }}"; - const std::string rgbwStateTopic = "studiotj/smart-rgb/light/rgbw/state"; - const std::string rgbwValueTemplate = "{{ value_json.rgbw_value }}"; - const std::string rgbwwCommandTopic = "studiotj/smart-rgb/light/rgbww/set"; - const std::string rgbwwCommandTemplate = "{{ value_json.rgbww_cmd }}"; - const std::string rgbwwStateTopic = "studiotj/smart-rgb/light/rgbww/state"; - const std::string rgbwwValueTemplate = "{{ value_json.rgbww_value }}"; - const std::string supportedColorModesTopic = "studiotj/smart-rgb/light/supported_color_modes"; const std::string supportedColorModesValue = "['rgb', 'brightness']"; const std::string availabilityTemplate = "{{ value_json.availability }}"; }; @@ -67,7 +43,8 @@ public: Light(Pin* pinR, Pin* pinG, Pin* pinB, std::string uniqueId); Light(Pin* pinR, Pin* pinG, Pin* pinB, Pin* pinCW, std::string uniqueId); Light(Pin* pinR, Pin* pinG, Pin* pinB, Pin* pinCW, Pin* pinWW, std::string uniqueId); - void subscribeToMqttTopics(); + void notifyOnline(); + void notifyOffline(); void publishInitialState(); void publishCurrentState(); void setHsl(uint8_t h, uint8_t s, uint8_t l); diff --git a/lib/light/lightinfo.h b/lib/light/lightinfo.h deleted file mode 100644 index e69de29..0000000 diff --git a/lib/network/network.cpp b/lib/network/network.cpp index cd5932d..a9e8a94 100644 --- a/lib/network/network.cpp +++ b/lib/network/network.cpp @@ -38,6 +38,17 @@ std::string Network::getHostname() const { return WiFi.getHostname(); } +const std::string Network::getMacAddress() const { + std::string mac = WiFi.macAddress().c_str(); // format: "AA:BB:CC:DD:EE:FF" + std::string hexMac = "0x"; + for (size_t i = 0; i < mac.size(); ++i) { + if (mac[i] != ':') { + hexMac += mac[i]; + } + } + return hexMac; +} + bool Network::setHostname(const std::string_view &hostname) { this->hostname = std::string(hostname); return WiFi.setHostname(this->hostname.c_str()); diff --git a/lib/network/network.h b/lib/network/network.h index 997fb4d..5905003 100644 --- a/lib/network/network.h +++ b/lib/network/network.h @@ -9,6 +9,7 @@ public: void reconnect(); bool isConnected() const; std::string getHostname() const; + const std::string getMacAddress() const; bool setHostname(const std::string_view &hostname); void registerMDNS(); private: diff --git a/platformio.ini b/platformio.ini index 804ea98..7d21431 100644 --- a/platformio.ini +++ b/platformio.ini @@ -31,4 +31,4 @@ build_flags = [env:esp32dev-ota] upload_protocol = espota -upload_port = smart-rgb-dev.local +upload_port = smart-rgb.local diff --git a/src/states.hpp b/src/states.hpp index 32397a1..19c757c 100644 --- a/src/states.hpp +++ b/src/states.hpp @@ -85,6 +85,14 @@ class RunningState : public State public: RunningState(AppContext *appContext) : State("RunningState", StateId::RunningState), appContext(appContext) {} void onEnter(StateMachineBase &stateMachine) override { + Debug::println("Entering RunningState"); + if (appContext) { + if (!appContext->light) { + appContext->light = new Light(appContext->pinR, appContext->pinG, appContext->pinB, appContext->pinCW, appContext->pinWW, appContext->network->getMacAddress()); + } else { + appContext->light->notifyOnline(); + } + } lastOtaPollMs = millis(); lastMqttPollMs = millis(); lastMqttCheckConnectionPollSecond = millis() / 1000; @@ -93,6 +101,9 @@ public: void onExit(StateMachineBase &stateMachine) override { Debug::println("Exiting RunningState"); + if (appContext && appContext->light) { + appContext->light->notifyOffline(); + } } void onUpdate(StateMachineBase &stateMachine) override {