From d20f78ddfe950d604d10c238919f3b73fe9007c7 Mon Sep 17 00:00:00 2001 From: Dennis George Date: Mon, 10 Feb 2025 09:04:00 -0600 Subject: [PATCH] Brightness control fixes (#1) * refactor light value controls method * fix brightness debug output * partial fixes for brightness control * try to fix white brightness * fix payload debug logging * fix debug output logging * fix light_off message * fix brightness --- components/fastcon/fastcon_controller.cpp | 105 +++++++++++++++------- components/fastcon/fastcon_controller.h | 5 +- components/fastcon/fastcon_light.cpp | 50 +++++------ components/fastcon/utils.cpp | 13 +++ components/fastcon/utils.h | 2 + 5 files changed, 112 insertions(+), 63 deletions(-) diff --git a/components/fastcon/fastcon_controller.cpp b/components/fastcon/fastcon_controller.cpp index c5fcefa..993c079 100644 --- a/components/fastcon/fastcon_controller.cpp +++ b/components/fastcon/fastcon_controller.cpp @@ -1,5 +1,6 @@ #include "esphome/core/component_iterator.h" #include "esphome/core/log.h" +#include "esphome/components/light/color_mode.h" #include "fastcon_controller.h" #include "protocol.h" @@ -130,51 +131,91 @@ namespace esphome } } - std::vector FastconController::get_advertisement(uint32_t light_id_, bool is_on, float brightness, float red, float green, float blue) + std::vector FastconController::get_light_data(light::LightState *state) { - std::vector light_data; + std::vector light_data = { + 0, // 0 - On/Off Bit + 7-bit Brightness + 0, // 1 - Blue byte + 0, // 2 - Red byte + 0, // 3 - Green byte + 0, // 4 - Warm byte + 0 // 5 - Cold byte + }; - // Convert brightness to 0-127 range - uint8_t bright = static_cast(std::min(brightness * 127.0f, 127.0f)); + // TODO: need to figure out when esphome is changing to white vs setting brightness + auto values = state->current_values; + + bool is_on = values.is_on(); if (!is_on) { - // Off state - light_data = {static_cast(0)}; // Just the off command - } - else if (red == 0 && green == 0 && blue == 0) - { - // Warm white mode - light_data = std::vector{ - static_cast(128 + bright), // On bit (128) + brightness - 0, 0, 0, // RGB values - 127, 127 // Warm/cold values - }; - } - else - { - // RGB mode - uint8_t r = static_cast(red * 255.0f); - uint8_t g = static_cast(green * 255.0f); - uint8_t b = static_cast(blue * 255.0f); - - light_data = std::vector{ - static_cast(128 + bright), // On bit (128) + brightness - b, r, g, // RGB values (in BRG order per protocol) - 0, 0 // No warm/cold values in RGB mode - }; + return std::vector({0x00}); } - return this->single_control(light_id_, light_data); + auto color_mode = values.get_color_mode(); + bool has_white = (static_cast(color_mode) & static_cast(light::ColorCapability::WHITE)) != 0; + float brightness = std::min(values.get_brightness() * 127.0f, 127.0f); // clamp the value to at most 127 + light_data[0] = 0x80 + static_cast(brightness); + + if (has_white) + { + return std::vector({static_cast(brightness)}); + // DEBUG: when changing to white mode, this should be the payload: + // ff0000007f7f + } + + bool has_rgb = (static_cast(color_mode) & static_cast(light::ColorCapability::RGB)) != 0; + if (has_rgb) + { + light_data[1] = static_cast(values.get_blue() * 255.0f); + light_data[2] = static_cast(values.get_red() * 255.0f); + light_data[3] = static_cast(values.get_green() * 255.0f); + } + + bool has_cold_warm = (static_cast(color_mode) & static_cast(light::ColorCapability::COLD_WARM_WHITE)) != 0; + if (has_cold_warm) + { + light_data[4] = static_cast(values.get_warm_white() * 255.0f); + light_data[5] = static_cast(values.get_cold_white() * 255.0f); + } + + // TODO figure out if we can use these, and how + bool has_temp = (static_cast(color_mode) & static_cast(light::ColorCapability::COLOR_TEMPERATURE)) != 0; + if (has_temp) + { + float temperature = values.get_color_temperature(); + if (temperature < 153) + { + light_data[4] = 0xff; + light_data[5] = 0x00; + } + else if (temperature > 500) + { + light_data[4] = 0x00; + light_data[5] = 0xff; + } + else + { + // Linear interpolation between (153, 0xff) and (500, 0x00) + light_data[4] = (uint8_t)(((500 - temperature) * 255.0f + (temperature - 153) * 0x00) / (500 - 153)); + light_data[5] = (uint8_t)(((temperature - 153) * 255.0f + (500 - temperature) * 0x00) / (500 - 153)); + } + } + + return light_data; } - std::vector FastconController::single_control(uint32_t light_id_, const std::vector &data) + std::vector FastconController::single_control(uint32_t light_id_, const std::vector &light_data) { std::vector result_data(12); - result_data[0] = 2 | (((0xfffffff & (data.size() + 1)) << 4)); + result_data[0] = 2 | (((0xfffffff & (light_data.size() + 1)) << 4)); result_data[1] = light_id_; - std::copy(data.begin(), data.end(), result_data.begin() + 2); + std::copy(light_data.begin(), light_data.end(), result_data.begin() + 2); + + // Debug output - print payload as hex + auto hex_str = vector_to_hex_string(result_data).data(); + ESP_LOGD(TAG, "Inner Payload (%d bytes): %s", result_data.size(), hex_str); return this->generate_command(5, light_id_, result_data, true); } diff --git a/components/fastcon/fastcon_controller.h b/components/fastcon/fastcon_controller.h index 420c1c8..cdc7808 100644 --- a/components/fastcon/fastcon_controller.h +++ b/components/fastcon/fastcon_controller.h @@ -19,8 +19,10 @@ namespace esphome void setup() override; void loop() override; + std::vector get_light_data(light::LightState *state); + std::vector single_control(uint32_t addr, const std::vector &light_data); + void queueCommand(uint32_t light_id_, const std::vector &data); - std::vector get_advertisement(uint32_t light_id_, bool is_on, float brightness, float red, float green, float blue); void clear_queue(); bool is_queue_empty() const @@ -73,7 +75,6 @@ namespace esphome // Protocol implementation std::vector generate_command(uint8_t n, uint32_t light_id_, const std::vector &data, bool forward = true); - std::vector single_control(uint32_t addr, const std::vector &data); std::array mesh_key_{}; diff --git a/components/fastcon/fastcon_light.cpp b/components/fastcon/fastcon_light.cpp index b2e68ec..af7a983 100644 --- a/components/fastcon/fastcon_light.cpp +++ b/components/fastcon/fastcon_light.cpp @@ -2,6 +2,7 @@ #include "esphome/core/log.h" #include "fastcon_light.h" #include "fastcon_controller.h" +#include "utils.h" namespace esphome { @@ -28,7 +29,7 @@ namespace esphome light::LightTraits FastconLight::get_traits() { auto traits = light::LightTraits(); - traits.set_supported_color_modes({light::ColorMode::RGB, light::ColorMode::WHITE, light::ColorMode::BRIGHTNESS}); + traits.set_supported_color_modes({light::ColorMode::RGB, light::ColorMode::WHITE, light::ColorMode::BRIGHTNESS, light::ColorMode::COLD_WARM_WHITE}); traits.set_min_mireds(153); traits.set_max_mireds(500); return traits; @@ -36,40 +37,31 @@ namespace esphome void FastconLight::write_state(light::LightState *state) { - float red = 0.0f, green = 0.0f, blue = 0.0f; + // Get the light data bits from the state + auto light_data = this->controller_->get_light_data(state); - // Get the light state values - float brightness = state->current_values.get_brightness() * 127.0f; // Scale to 0-127 - bool is_on = state->current_values.is_on(); - auto color_mode = state->current_values.get_color_mode(); - - if (!is_on) + // Debug output - print the light state values + bool is_on = (light_data[0] & 0x80) != 0; + float brightness = ((light_data[0] & 0x7F) / 127.0f) * 100.0f; + if (light_data.size() == 1) { - brightness = 0.0f; + ESP_LOGD(TAG, "Writing state: light_id=%d, on=%d, brightness=%.1f%%", light_id_, is_on, brightness); + } + else + { + auto r = light_data[2]; + auto g = light_data[3]; + auto b = light_data[1]; + auto warm = light_data[4]; + auto cold = light_data[5]; + ESP_LOGD(TAG, "Writing state: light_id=%d, on=%d, brightness=%.1f%%, rgb=(%d,%d,%d), warm=%d, cold=%d", light_id_, is_on, brightness, r, g, b, warm, cold); } - if (color_mode == light::ColorMode::RGB) - { - state->current_values_as_rgb(&red, &green, &blue); - } - - // Convert to protocol values - auto r = static_cast(red * 255.0f); - auto g = static_cast(green * 255.0f); - auto b = static_cast(blue * 255.0f); - - ESP_LOGD(TAG, "Writing state: light_id=%d, on=%d, brightness=%.1f%%, rgb=(%d,%d,%d)", light_id_, is_on, (brightness / 127.0f) * 100.0f, r, g, b); - - // Get the advertisement data - auto adv_data = this->controller_->get_advertisement(this->light_id_, is_on, brightness, red, green, blue); + // Generate the advertisement payload + auto adv_data = this->controller_->single_control(this->light_id_, light_data); // Debug output - print payload as hex - char hex_str[adv_data.size() * 2 + 1]; // Each byte needs 2 chars + null terminator - for (size_t i = 0; i < adv_data.size(); i++) - { - sprintf(hex_str + (i * 2), "%02X", adv_data[i]); - } - hex_str[adv_data.size() * 2] = '\0'; // Ensure null termination + auto hex_str = vector_to_hex_string(adv_data).data(); ESP_LOGD(TAG, "Advertisement Payload (%d bytes): %s", adv_data.size(), hex_str); // Send the advertisement diff --git a/components/fastcon/utils.cpp b/components/fastcon/utils.cpp index b261a6f..371ae09 100644 --- a/components/fastcon/utils.cpp +++ b/components/fastcon/utils.cpp @@ -1,3 +1,5 @@ +#include +#include #include "esphome/core/log.h" #include "utils.h" @@ -109,5 +111,16 @@ namespace esphome ctx.f_0x4 = var0; } } + + std::vector vector_to_hex_string(std::vector &data) + { + std::vector hex_str(data.size() * 2 + 1); // Allocate the vector with the required size + for (size_t i = 0; i < data.size(); i++) + { + sprintf(hex_str.data() + (i * 2), "%02X", data[i]); + } + hex_str[data.size() * 2] = '\0'; // Ensure null termination + return hex_str; + } } // namespace fastcon } // namespace esphome \ No newline at end of file diff --git a/components/fastcon/utils.h b/components/fastcon/utils.h index 25a9e42..c243d9c 100644 --- a/components/fastcon/utils.h +++ b/components/fastcon/utils.h @@ -30,5 +30,7 @@ namespace esphome void whitening_init(uint32_t val, WhiteningContext &ctx); void whitening_encode(std::vector &data, WhiteningContext &ctx); + + std::vector vector_to_hex_string(std::vector &data); } // namespace fastcon } // namespace esphome \ No newline at end of file