#include #include "light.h" #include "mqtt.h" constexpr uint16_t configMsgSize = 1024; constexpr uint16_t statusMsgSize = 128; constexpr uint8_t minPwmValue = 0; constexpr float gammaCorrection = 2.2f; constexpr uint8_t maxPwm = 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; 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; 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.statusTopic; configInfo["command_topic"] = this->lightInfo.commandTopic; 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("rgbww"); 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; // configInfo["state_value_template"] = this->lightInfo.stateValueTemplate; std::string configJson; serializeJson(configInfo, configJson); mqttClient->publish(lightInfo.discoveryTopic, configJson); std::string stateJson; JsonDocument stateInfo; stateInfo["state"] = "OFF"; // Initial state is OFF // stateInfo["availability"] = Availability.available; // Initial availability stateInfo["brightness"] = 0; // Initial brightness JsonObject rgbValue = stateInfo["rgb_value"].to(); rgbValue["r"] = 255; rgbValue["g"] = 255; rgbValue["b"] = 255; serializeJson(stateInfo, stateJson); std::string availabilityJson; JsonDocument availabilityInfoDoc; availabilityInfoDoc["availability"] = Availability.available; // Initial availability serializeJson(availabilityInfoDoc, availabilityJson); mqttClient->publish(lightInfo.stateTopic, stateJson); mqttClient->publish(lightInfo.availabilityTopic, availabilityJson); } void Light::operatePin() { uint32_t rSetpoint = r; uint32_t gSetpoint = g; uint32_t bSetpoint = b; if (!isOn) { turnOff(); return; } float brightnessFactor = brightness / 255.0f; rSetpoint = static_cast(r * brightnessFactor); rSetpoint = correctGamma(rSetpoint); // if (rSetpoint < minPwmValue && rSetpoint > 0) { // rSetpoint = minPwmValue; // } gSetpoint = static_cast(g * brightnessFactor); gSetpoint = correctGamma(gSetpoint); // if (gSetpoint < minPwmValue && gSetpoint > 0) { // gSetpoint = minPwmValue; // } bSetpoint = static_cast(b * brightnessFactor); bSetpoint = correctGamma(bSetpoint); // if (bSetpoint < minPwmValue && bSetpoint > 0) { // bSetpoint = minPwmValue; // } Serial.printf("Setting RGB: R=%d, G=%d, B=%d with brightness factor: %.2f\n", rSetpoint, gSetpoint, bSetpoint, brightnessFactor); pinR->setLedLevel(rSetpoint); pinG->setLedLevel(gSetpoint); pinB->setLedLevel(bSetpoint); if (pinCW != nullptr) { pinCW->setLedLevel(cw); } if (pinWW != nullptr) { pinWW->setLedLevel(ww); } Serial.printf("Set RGB: R=%d, G=%d, B=%d\n", r, g, b); } 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"] | 255; // Default to 255 if not provided g = color["g"] | 255; // Default to 255 if not provided b = color["b"] | 255; // Default to 255 if not provided if (lightType == LightType::rgbw || lightType == LightType::rgbww) { ww = color["w"] | 255; // Default to 255 if not provided } if (lightType == LightType::rgbww) { cw = color["cw"] | 255; // Default to 255 if not provided } } if (lightType == LightType::rgb || lightType == LightType::rgbw || lightType == LightType::rgbww) { operatePin(); } publishCurrentState(); } void Light::turnOn() { isOn = true; if (pinR != nullptr) pinR->setLedLevel(r); if (pinG != nullptr) pinG->setLedLevel(g); if (pinB != nullptr) pinB->setLedLevel(b); if (pinCW != nullptr) pinCW->setLedLevel(cw); if (pinWW != nullptr) pinWW->setLedLevel(ww); } 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; stateInfo["state"] = isOn ? "ON" : "OFF"; stateInfo["availability"] = Availability.available; // Current availability stateInfo["brightness"] = brightness; if (lightType == LightType::rgb || lightType == LightType::rgbw || lightType == LightType::rgbww) { JsonObject rgbValue = stateInfo["color"].to(); rgbValue["r"] = r; rgbValue["g"] = g; rgbValue["b"] = b; if (lightType == LightType::rgb) { stateInfo["color_mode"] = "rgb"; } if (lightType == LightType::rgbw) { stateInfo["color_mode"] = "rgbw"; rgbValue["w"] = ww; } else if (lightType == LightType::rgbww) { stateInfo["color_mode"] = "rgbww"; rgbValue["cw"] = cw; rgbValue["ww"] = ww; } } std::string stateJson; serializeJson(stateInfo, stateJson); Serial.println("Publishing current state: " + String(stateJson.c_str())); mqttClient->publish(lightInfo.stateTopic, stateJson); } uint32_t Light::correctGamma(uint32_t originalPwm) { // Apply gamma correction to the PWM value float pwmPercentage = originalPwm / 255.0f; return static_cast(pow(pwmPercentage, 1 / gammaCorrection) * 255); }