diff --git a/.vscode/settings.json b/.vscode/settings.json index 025eb3b..854982c 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -55,5 +55,6 @@ "thread": "cpp", "cinttypes": "cpp", "typeinfo": "cpp" - } + }, + "idf.portWin": "COM4" } \ No newline at end of file diff --git a/lib/communication/mqtt.cpp b/lib/communication/mqtt.cpp new file mode 100644 index 0000000..a22929e --- /dev/null +++ b/lib/communication/mqtt.cpp @@ -0,0 +1,91 @@ +#include +#include +#include "mqtt.h" + +constexpr uint16_t BUFFER_SIZE = 2048; + +static WiFiClient wifiClient = WiFiClient(); +static PubSubClient mqttClient = PubSubClient(wifiClient); +std::string Mqtt::brokerIp; +uint16_t Mqtt::brokerPort; +std::string Mqtt::clientId; +std::string Mqtt::username; +std::string Mqtt::password; +std::map Mqtt::callbacks; +bool Mqtt::initialized = false; +bool Mqtt::isConnected = false; + +void Mqtt::mqttCb(char* topic, uint8_t* payload, unsigned int length) { + std::string topicStr(topic); + if (callbacks.find(topicStr) != callbacks.end()) { + callbacks[topicStr](payload, length); + } +} + +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()); + } else { + Serial.printf("Failed to subscribe to topic: %s\n", topic.c_str()); + } + } else { + Serial.println("MQTT client is not connected. Cannot subscribe."); + } +} + +void Mqtt::publish(const std::string& topic, const std::string& payload, bool retain) { + 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()); + } + } else { + Serial.println("MQTT client is not connected. Cannot publish."); + } +} + +void Mqtt::poll() { + if (mqttClient.connected()) { + mqttClient.loop(); // Process incoming messages + } else { + Serial.println("MQTT client is not connected. Polling skipped."); + } +} + +void Mqtt::checkConnection() { + if (!mqttClient.connected()) { + Serial.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."); + 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()); + Mqtt::isConnected = false; + } + } +} + +void Mqtt::connect(std::string brokerIp, uint16_t brokerPort, std::string clientId, std::string username, std::string password) { + Mqtt::brokerIp = brokerIp; + Mqtt::brokerPort = brokerPort; + Mqtt::clientId = clientId; + Mqtt::username = username; + Mqtt::password = password; + mqttClient.setServer(Mqtt::brokerIp.c_str(), Mqtt::brokerPort); + mqttClient.setKeepAlive(60); + mqttClient.setCallback(mqttCb); + 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"); + Mqtt::initialized = true; + Mqtt::isConnected = true; + } else { + Serial.printf("Failed to connect to MQTT broker, rc=%d\n", mqttClient.state()); + } +} diff --git a/lib/communication/mqtt.h b/lib/communication/mqtt.h new file mode 100644 index 0000000..5861f50 --- /dev/null +++ b/lib/communication/mqtt.h @@ -0,0 +1,26 @@ +#pragma once +#include +#include +#include + +typedef std::function MqttCallback; + +class Mqtt { +public: + static void connect(std::string brokerIp, uint16_t brokerPort, std::string clientId, std::string username="mqtt", std::string password="mqtt"); + static void poll(); + 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 mqttCb(char* topic, uint8_t* payload, unsigned int length); + +private: + static std::string brokerIp; + static uint16_t brokerPort; + static std::string clientId; + static std::string username; + static std::string password; + static bool initialized; + static bool isConnected; + static std::map callbacks; +}; \ No newline at end of file diff --git a/lib/light/light.cpp b/lib/light/light.cpp new file mode 100644 index 0000000..9736a4f --- /dev/null +++ b/lib/light/light.cpp @@ -0,0 +1,270 @@ +#include +#include "light.h" +#include "mqtt.h" + +constexpr uint16_t configMsgSize = 1024; +constexpr uint16_t statusMsgSize = 128; +constexpr uint8_t minPwmValue = 0; +constexpr double gammaCorrection = 2.4; +constexpr uint32_t maxPwmUi = 255; + +const struct { + String available = "online"; + String notAvailable = "offline"; +} Availability; + + +Light::Light(Pin* pinR, Pin* pinG, Pin* pinB, Mqtt* mqttClient, std::string uniqueId) + : pinR(pinR), pinG(pinG), pinB(pinB), pinCW(nullptr), pinWW(nullptr), mqttClient(mqttClient) { + lightInfo.uniqueId = uniqueId; + lightType = LightType::rgb; + uint8_t bits = pinR->getLedResolutionBits(); + maxPwm = (bits >= 1 && bits <= 31) ? ((1u << bits) - 1u) : 255u; + publishInitialState(); + subscribeToMqttTopics(); +} + +Light::Light(Pin* pinR, Pin* pinG, Pin* pinB, Pin* pinCW, Pin* pinWW, Mqtt* mqttClient, std::string uniqueId) + : pinR(pinR), pinG(pinG), pinB(pinB), pinCW(pinCW), pinWW(pinWW), mqttClient(mqttClient) { + lightInfo.uniqueId = uniqueId; + lightType = LightType::rgbww; + uint8_t bits = pinR->getLedResolutionBits(); + maxPwm = (bits >= 1 && bits <= 31) ? ((1u << bits) - 1u) : 255u; + publishInitialState(); + subscribeToMqttTopics(); +} + +void Light::publishInitialState() { + // Publish the initial state of the light + JsonDocument configInfo; + JsonObject deviceInfo = configInfo["device"].to(); + deviceInfo["name"] = this->deviceInfo.name; + deviceInfo["model"] = this->deviceInfo.model; + JsonArray identifiers = deviceInfo["identifiers"].to(); + identifiers.add(this->deviceInfo.identifier + this->lightInfo.uniqueId); + deviceInfo["sw_version"] = this->deviceInfo.swVersion; + deviceInfo["manufacturer"] = this->deviceInfo.manufacturer; + + configInfo["unique_id"] = this->lightInfo.uniqueId; + configInfo["name"] = this->lightInfo.name; + configInfo["schema"] = "json"; + configInfo["json_attributes_topic"] = this->lightInfo.jsonAttributesTopic; + configInfo["command_topic"] = this->lightInfo.commandTopic; + configInfo["color_temp_kelvin"] = true; + configInfo["max_kelvin"] = cwTempKelvin; + configInfo["min_kelvin"] = wwTempKelvin; + JsonArray availabilityInfo = configInfo["availability"].to(); + JsonObject availabilityItem = availabilityInfo.add(); + availabilityItem["topic"] = this->lightInfo.availabilityTopic; + availabilityItem["value_template"] = this->lightInfo.availabilityTemplate; + JsonArray supportedColorModes = configInfo["supported_color_modes"].to(); + if (lightType == LightType::rgb) { + supportedColorModes.add("rgb"); + } else if (lightType == LightType::rgbw) { + supportedColorModes.add("rgbw"); + } else if (lightType == LightType::rgbww) { + supportedColorModes.add("rgb"); + supportedColorModes.add("color_temp"); + } else if (lightType == LightType::colorTemperature) { + supportedColorModes.add("color_temp"); + } else if (lightType == LightType::brightness) { + supportedColorModes.add("brightness"); + } else { + supportedColorModes.add("onoff"); + } + configInfo["state_topic"] = this->lightInfo.stateTopic; + + std::string configJson; + serializeJson(configInfo, configJson); + mqttClient->publish(lightInfo.discoveryTopic, configJson); + + std::string stateJson; + JsonDocument stateInfo; + stateInfo["state"] = "OFF"; + stateInfo["brightness"] = maxPwmUi; + brightness = maxPwmUi; + JsonObject color = stateInfo["color"].to(); + color["r"] = 0; + r = 0; + color["g"] = 0; + g = 0; + color["b"] = 0; + b = 0; + serializeJson(stateInfo, stateJson); + std::string availabilityJson; + JsonDocument availabilityInfoDoc; + availabilityInfoDoc["availability"] = Availability.available; + serializeJson(availabilityInfoDoc, availabilityJson); + mqttClient->publish(lightInfo.stateTopic, stateJson); + mqttClient->publish(lightInfo.availabilityTopic, availabilityJson); +} + +void Light::operatePin() { + auto clamp8 = [](int v)->uint8_t { return v < 0 ? 0 : (v > 255 ? 255 : v); }; + if (!isOn) { + turnOff(); + return; + } + uint8_t r8 = clamp8(r); + uint8_t g8 = clamp8(g); + uint8_t b8 = clamp8(b); + uint8_t cw8 = clamp8(cw); + uint8_t ww8 = clamp8(ww); + uint8_t br8 = clamp8(brightness); + + uint32_t rGamma = correctGamma(r8); + uint32_t gGamma = correctGamma(g8); + uint32_t bGamma = correctGamma(b8); + uint32_t cwGamma = correctGamma(cw8); + uint32_t wwGamma = correctGamma(ww8); + uint32_t brGamma = correctGamma(br8); + + auto mixHw = [this](uint32_t cG, uint32_t bG) -> uint32_t { + return static_cast((static_cast(cG) * bG + (maxPwm / 2)) / maxPwm); + }; + + uint32_t rSetpoint = mixHw(rGamma, brGamma); + uint32_t gSetpoint = mixHw(gGamma, brGamma); + uint32_t bSetpoint = mixHw(bGamma, brGamma); + uint32_t cwSetpoint = 0; + uint32_t wwSetpoint = 0; + + if (activeMode == ActiveMode::modeCct) { + uint32_t peak = cwGamma > wwGamma ? cwGamma : wwGamma; + if (peak > 0) { + uint32_t cwGammaUp = static_cast( + (static_cast(cwGamma) * maxPwm + (peak / 2)) / peak + ); + uint32_t wwGammaUp = static_cast( + (static_cast(wwGamma) * maxPwm + (peak / 2)) / peak + ); + if (cwGammaUp > maxPwm) cwGammaUp = maxPwm; + if (wwGammaUp > maxPwm) wwGammaUp = maxPwm; + + cwSetpoint = mixHw(cwGammaUp, brGamma); + wwSetpoint = mixHw(wwGammaUp, brGamma); + } else { + cwSetpoint = wwSetpoint = 0; + } + } else { + cwSetpoint = mixHw(cwGamma, brGamma); + wwSetpoint = mixHw(wwGamma, brGamma); + } + + if (pinR) pinR->setLedLevel(rSetpoint); + if (pinG) pinG->setLedLevel(gSetpoint); + if (pinB) pinB->setLedLevel(bSetpoint); + if (pinCW) pinCW->setLedLevel(cwSetpoint); + if (pinWW) pinWW->setLedLevel(wwSetpoint); +} + +void Light::subscribeToMqttTopics() { + mqttClient->subscribe(lightInfo.commandTopic, [this](uint8_t* payload, int length) { + std::string command(reinterpret_cast(payload), length); + handleCommand(command); + }); +} + +void Light::handleCommand(const std::string& command) { + Serial.println("Received command: " + String(command.c_str())); + JsonDocument commandJson; + deserializeJson(commandJson, command); + if (commandJson.isNull()) { + Serial.println("Invalid command JSON"); + return; + } + if (commandJson["state"].is()) { + std::string state = commandJson["state"].as(); + if (state == "ON") { + isOn = true; + } else if (state == "OFF") { + isOn = false; + } + } + if (commandJson["brightness"].is()) { + brightness = commandJson["brightness"].as(); + } + if (commandJson["color"].is()) { + JsonObject color = commandJson["color"]; + r = color["r"] | maxPwmUi; + g = color["g"] | maxPwmUi; + b = color["b"] | maxPwmUi; + cw = 0; + ww = 0; + activeMode = ActiveMode::modeRgb; + } + if (commandJson["color_temp"].is()) { + colorTemperature = commandJson["color_temp"].as(); + applyKelvin(colorTemperature); + } + if (lightType == LightType::rgb || lightType == LightType::rgbw || lightType == LightType::rgbww) + { + operatePin(); + publishCurrentState(); + } +} + +void Light::turnOff() { + isOn = false; + if (pinR != nullptr) pinR->setLedLevel(0); + if (pinG != nullptr) pinG->setLedLevel(0); + if (pinB != nullptr) pinB->setLedLevel(0); + if (pinCW != nullptr) pinCW->setLedLevel(0); + if (pinWW != nullptr) pinWW->setLedLevel(0); +} + +void Light::publishCurrentState() { + // Publish the current state of the light + JsonDocument stateInfo; + JsonDocument attributeInfo; + stateInfo["state"] = isOn ? "ON" : "OFF"; + stateInfo["availability"] = Availability.available; // Current availability + stateInfo["brightness"] = brightness; + if (activeMode == ActiveMode::modeRgb) { + JsonObject color = stateInfo["color"].to(); + color["r"] = r; + color["g"] = g; + color["b"] = b; + stateInfo["color_mode"] = "rgb"; + } else if (activeMode == ActiveMode::modeCct) { + stateInfo["color_temp"] = colorTemperature; + stateInfo["color_mode"] = "color_temp"; + } + attributeInfo["pwmR"] = pinR->getLedLevel(); + attributeInfo["pwmG"] = pinG->getLedLevel(); + attributeInfo["pwmB"] = pinB->getLedLevel(); + if (pinCW != nullptr) + attributeInfo["pwmCW"] = pinCW->getLedLevel(); + if (pinWW != nullptr) + attributeInfo["pwmWW"] = pinWW->getLedLevel(); + + std::string stateJson; + serializeJson(stateInfo, stateJson); + Serial.println("Publishing current state: " + String(stateJson.c_str())); + mqttClient->publish(lightInfo.stateTopic, stateJson); + std::string attributeJson; + serializeJson(attributeInfo, attributeJson); + Serial.println("Publishing current attributes: " + String(attributeJson.c_str())); + mqttClient->publish(lightInfo.jsonAttributesTopic, attributeJson); +} + +uint32_t Light::correctGamma(uint32_t originalPwm) { + double normalized = static_cast(originalPwm) / maxPwmUi; + if (normalized <= 0.04045) { + return static_cast((normalized / 12.92) * maxPwm); + } else { + return static_cast(pow((normalized + 0.055) / 1.055, gammaCorrection) * maxPwm); + } +} + +void Light::applyKelvin(uint32_t kelvin) { + if (kelvin > cwTempKelvin) kelvin = cwTempKelvin; + if (kelvin < wwTempKelvin) kelvin = wwTempKelvin; + double tLin = static_cast(kelvin - wwTempKelvin) / static_cast(cwTempKelvin - wwTempKelvin); + r = 0; + g = 0; + b = 0; + cw = static_cast(tLin * maxPwmUi); + ww = static_cast((1.0 - tLin) * maxPwmUi); + activeMode = ActiveMode::modeCct; +} diff --git a/lib/light/light.h b/lib/light/light.h new file mode 100644 index 0000000..793b0d1 --- /dev/null +++ b/lib/light/light.h @@ -0,0 +1,105 @@ +#pragma once + +#include +#include "pin.h" +#include "mqtt.h" + +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 }}"; +}; + +struct DeviceInfo { + std::string name = "Smart RGB Light"; + std::string model = "smart_rgb_light"; + std::string identifier = "smart_rgb_light_"; + std::string swVersion = "1.0"; // TODO: version will be generated. + std::string manufacturer = "Studio TJ"; +}; + +enum LightType { + onOff, + brightness, + colorTemperature, + rgb, + rgbw, + rgbww, +}; + +enum ActiveMode { + modeRgb, + modeCct +}; + +class Light { +public: + Light(Pin* pinR, Pin* pinG, Pin* pinB, Mqtt* mqttClient, std::string uniqueId); + Light(Pin* pinR, Pin* pinG, Pin* pinB, Pin* pinCW, Mqtt* mqttClient, std::string uniqueId); + Light(Pin* pinR, Pin* pinG, Pin* pinB, Pin* pinCW, Pin* pinWW, Mqtt* mqttClient, std::string uniqueId); + void subscribeToMqttTopics(); + void publishInitialState(); + void publishCurrentState(); + void setHsl(uint8_t h, uint8_t s, uint8_t l); + void setColorTemperature(uint16_t temperature); + void setBrightness(uint8_t brightness); + void turnOff(); + +private: + void handleCommand(const std::string& command); + void operatePin(); + uint32_t correctGamma(uint32_t originalPwm); + void applyKelvin(uint32_t kelvin); + uint8_t r = 0; // Default to white + uint8_t g = 0; // Default to white + uint8_t b = 0; // Default to white + uint8_t cw = 255; // Default to white + uint8_t ww = 255; // Default to white + const uint32_t cwTempKelvin = 6000; + const uint32_t wwTempKelvin = 3000; + uint16_t colorTemperature; + uint8_t brightness; + uint32_t maxPwm; + bool isOn = false; + Pin* pinR; + Pin* pinG; + Pin* pinB; + Pin* pinCW; + Pin* pinWW; + Mqtt* mqttClient; + LightInfo lightInfo; + DeviceInfo deviceInfo; + LightType lightType = onOff; // Default light type + ActiveMode activeMode = modeRgb; +}; \ No newline at end of file diff --git a/lib/light/lightinfo.h b/lib/light/lightinfo.h new file mode 100644 index 0000000..e69de29 diff --git a/lib/pin/pin.cpp b/lib/pin/pin.cpp new file mode 100644 index 0000000..4f4827c --- /dev/null +++ b/lib/pin/pin.cpp @@ -0,0 +1,53 @@ +#include +#include "pin.h" + +Pin::Pin(int pinNumber, bool isOutput, bool isLed, uint32_t ledFrequency, uint8_t ledChannel) + : pinNumber(pinNumber), output(isOutput), isLed(isLed), ledChannel(ledChannel) { + pinMode(pinNumber, isOutput ? OUTPUT : INPUT); + if (isLed) { + ledcSetup(ledChannel, ledFrequency, ledResolutionBits); // Setup LEDC for PWM with 8-bit resolution + ledcAttachPin(pinNumber, ledChannel); // Attach the pin to the LEDC channel + } +} + +const uint8_t Pin::getLedResolutionBits() const { + return ledResolutionBits; +} + +void Pin::setHigh() { + if (output) { + digitalWrite(pinNumber, HIGH); + } +} + +void Pin::setLow() { + if (output) { + digitalWrite(pinNumber, LOW); + } +} + +void Pin::setLedLevel(uint32_t level) { + if (output && isLed) { + ledcWrite(ledChannel, level); + ledLevel = level; + } +} + +uint32_t Pin::getLedLevel() const { + return ledLevel; +} + +bool Pin::read() { + if (!output) { + return digitalRead(pinNumber); + } + return false; +} + +int Pin::getPinNumber() const { + return pinNumber; +} + +bool Pin::isOutput() const { + return output; +} diff --git a/lib/pin/pin.h b/lib/pin/pin.h new file mode 100644 index 0000000..ea1a695 --- /dev/null +++ b/lib/pin/pin.h @@ -0,0 +1,22 @@ +#pragma once + +class Pin { +public: + Pin(int pinNumber, bool isOutput = true, bool isLed = false, uint32_t ledFrequency = 5000, uint8_t ledChannel = 0); + void setHigh(); + void setLow(); + void setLedLevel(uint32_t level); + uint32_t getLedLevel() const; + const uint8_t getLedResolutionBits() const; + bool read(); + int getPinNumber() const; + bool isOutput() const; + +private: + uint8_t ledChannel = 0; // LED channel for PWM + uint8_t pinNumber; + uint32_t ledLevel = 0; + const uint8_t ledResolutionBits = 12; + bool output; + bool isLed = false; // Flag to indicate if this pin is used for LED control +}; \ No newline at end of file diff --git a/platformio.ini b/platformio.ini index 482e731..6987dad 100644 --- a/platformio.ini +++ b/platformio.ini @@ -14,13 +14,14 @@ board = esp32dev framework = arduino monitor_speed = 115200 lib_deps = - martinverges/ESP32 Wifi Manager@^1.5.0 - esp32async/ESPAsyncWebServer@^3.7.10 - bblanchon/ArduinoJson@^7.4.2 + martinverges/ESP32 Wifi Manager@^1.5.0 + esp32async/ESPAsyncWebServer@^3.7.10 + bblanchon/ArduinoJson@^7.4.2 + knolleary/PubSubClient@^2.8 + arkhipenko/TaskScheduler@^3.8.5 [env:esp32dev-serial] - [env:esp32dev-ota] upload_protocol = espota upload_port = smart-rgb.local diff --git a/src/main.cpp b/src/main.cpp index 9de6d7a..d5bc921 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,22 +1,64 @@ #include +#include "light.h" +#include "mqtt.h" #include "network.h" #include "ota.h" +#include "pin.h" +#include "TaskScheduler.h" #include "wifimanager.h" Network* network = nullptr; OTAHandler* otaHandler = nullptr; +Mqtt* mqttClient = nullptr; +Light *light = nullptr; +Task *updateTask = nullptr; +Task *mqttTickTask = nullptr; +Task *mqttCheckConnectionTask = nullptr; + +Pin *pinR = new Pin(16, true, true, 5000, 0); // Example pin numbers, adjust as needed +Pin *pinG = new Pin(17, true, true, 5000, 1); +Pin *pinB = new Pin(18, true, true, 5000, 2); +Pin *pinCW = new Pin(19, true, true, 5000, 3); +Pin *pinWW = new Pin(21, true, true, 5000, 4); + +Scheduler *scheduler; + +void initializeScheduler(); void setup() { // put your setup code here, to run once: Serial.begin(115200); Serial.println("Starting Smart RGB ESP32..."); + pinR->setLedLevel(0); + pinG->setLedLevel(0); + pinB->setLedLevel(0); + pinCW->setLedLevel(0); + pinWW->setLedLevel(0); network = new Network("smart-rgb"); otaHandler = new OTAHandler("smart-rgb-ota"); network->registerMDNS(); + Mqtt::connect("10.238.75.81", 1883, "smart_rgb_client", "mqtt", "mqtt"); + delay(1000); // Wait for MQTT connection to stabilize + light = new Light(pinR, pinG, pinB, pinCW, pinWW, mqttClient, "smart_rgb_light"); + initializeScheduler(); } void loop() { - otaHandler->poll(); // Handle OTA updates - delay(500); + scheduler->execute(); // Execute the scheduler to run tasks + yield(); // Yield to allow other tasks to run +} + +void initializeScheduler() { + scheduler = new Scheduler(); + updateTask = new Task(TASK_SECOND, TASK_FOREVER, []() { + otaHandler->poll(); // Poll for OTA updates + + }, scheduler, true, nullptr, nullptr); + mqttTickTask = new Task(TASK_MILLISECOND * 100, TASK_FOREVER, []() { + Mqtt::poll(); // Poll MQTT client for messages + }, scheduler, true, nullptr, nullptr); + mqttCheckConnectionTask = new Task(TASK_SECOND * 30, TASK_FOREVER, []() { + Mqtt::checkConnection(); // Check MQTT connection status + }, scheduler, true, nullptr, nullptr); } \ No newline at end of file