diff --git a/components/nspanel_ha_blueprint/nextion_components.h b/components/nspanel_ha_blueprint/nextion_components.h index 56403ed..f85b728 100644 --- a/components/nspanel_ha_blueprint/nextion_components.h +++ b/components/nspanel_ha_blueprint/nextion_components.h @@ -1,51 +1,86 @@ // nextion_components.h #pragma once +#include +#include #include +#include namespace nspanel_ha_blueprint { struct NextionComponent { - std::string page; - std::string component_id; + char page[15]; // 14 characters + null terminator + char component_id[15]; // 14 characters + null terminator bool is_current_page; }; /** * Extracts the page name and component ID from a given input string. - * Handles a special case where "alarm_control_panel" should be shortened to "alarm". + * If the input string omits the page, uses defaultPage. + * Special case: "alarm_control_panel" is shortened to "alarm". * - * @param input The input string containing either the combined page and component ID or just the component ID. - * @param defaultPage The default page name to use if the input string does not specify a page. - * @return A NextionComponent struct containing the extracted or default page name, the component ID, and a flag indicating if it's the current page. + * @param input The input string containing the component ID, optionally prefixed by the page name and a dot. + * @param defaultPage The default page name to use if the input string does not specify a page, limited to 14 characters. + * @return A NextionComponent struct with the extracted or default page name, the component ID, and a flag for current page status. */ NextionComponent extractNextionComponent(const std::string& input, const std::string& defaultPage) { + NextionComponent result{}; size_t dotPos = input.find("."); - NextionComponent result; - - if (dotPos != std::string::npos) { - // Extract page and component_id from the input string - result.page = input.substr(0, dotPos); - result.component_id = input.substr(dotPos + 1); - result.is_current_page = false; // Since there's a dot, it's assumed not to be the current page - // Check for the special case of "alarm_control_panel" - if (result.page == "alarm_control_panel") { - result.page = "alarm"; + if (dotPos != std::string::npos) { + if (input.compare(0, 20, "alarm_control_panel.") == 0) { + strncpy(result.page, "alarm", sizeof(result.page) - 1); + result.page[sizeof(result.page) - 1] = '\0'; + } else { + // Copy up to the dot or 14 characters, whichever is smaller + size_t lengthToCopy = std::min(dotPos, static_cast(14)); + strncpy(result.page, input.c_str(), lengthToCopy); + result.page[lengthToCopy] = '\0'; // Ensure null termination } + + // Extract and copy component_id + const char* componentStart = input.c_str() + dotPos + 1; + strncpy(result.component_id, componentStart, std::min(input.length() - dotPos - 1, static_cast(14))); + result.component_id[14] = '\0'; // Ensure null termination + result.is_current_page = false; } else { - // No dot found, the entire input is considered as component_id - result.page = defaultPage; - result.component_id = input; - result.is_current_page = true; // No specific page mentioned, so it's the current page + // No dot found, use defaultPage and assume it's the current page + strncpy(result.page, defaultPage.c_str(), 14); + result.page[14] = '\0'; // Ensure null termination + + // Input is the component_id + strncpy(result.component_id, input.c_str(), std::min(input.length(), static_cast(14))); + result.component_id[14] = '\0'; // Ensure null termination + result.is_current_page = true; } - // Check if the resolved page matches the defaultPage indicating it's the current page - if (result.page == defaultPage) { + // Additional check to see if the current page matches defaultPage + if (strncmp(result.page, defaultPage.c_str(), 14) == 0) { result.is_current_page = true; } return result; } + /** + * Converts an RGB color represented as a vector of integers to the 16-bit 5-6-5 format supported by Nextion displays. + * + * This function takes a vector containing three integer values representing + * the red, green, and blue components of an RGB color, each in the range 0-255. + * It then converts these values into a single uint16_t value in 5-6-5 format, + * commonly used for color displays. The conversion process masks and shifts + * the components to fit into the 5 bits for red, 6 bits for green, and 5 bits for blue. + * + * @param rgb A vector of integers with exactly three elements: [red, green, blue]. + * @return The color encoded in 16-bit 5-6-5 format, or UINT16_MAX if the input vector + * does not contain at least three elements. + */ + template + inline uint16_t rgbTo565(const Container& rgb) { + if (rgb.size() != 3) { + return UINT16_MAX; // Use UINT16_MAX as an error indicator + } + return ((rgb[0] & 0xF8) << 8) | ((rgb[1] & 0xFC) << 3) | (rgb[2] >> 3); + } + } // namespace nspanel_ha_blueprint diff --git a/esphome/nspanel_esphome_addon_upload_tft.yaml b/esphome/nspanel_esphome_addon_upload_tft.yaml index 1b32151..ebe4ec4 100644 --- a/esphome/nspanel_esphome_addon_upload_tft.yaml +++ b/esphome/nspanel_esphome_addon_upload_tft.yaml @@ -19,7 +19,7 @@ external_components: - source: type: git url: https://github.com/Blackymas/NSPanel_HA_Blueprint - ref: main + # ref: main components: - nspanel_ha_blueprint_upload_tft refresh: 300s diff --git a/esphome/nspanel_esphome_core.yaml b/esphome/nspanel_esphome_core.yaml index 813832a..4bbafbb 100644 --- a/esphome/nspanel_esphome_core.yaml +++ b/esphome/nspanel_esphome_core.yaml @@ -27,7 +27,7 @@ external_components: # path: packages/Blackymas/components type: git url: https://github.com/Blackymas/NSPanel_HA_Blueprint - ref: dev # To do: change to main for release + ref: dev # To do: use tag for release components: - nspanel_ha_blueprint refresh: 3s # To do: change to 300s for release @@ -191,7 +191,7 @@ api: disp1->set_component_font((id + "icon").c_str(), icon_font); disp1->set_component_foreground_color((id + "bri").c_str(), state ? 10597 : 65535); disp1->set_component_foreground_color((id + "text").c_str(), state ? 10597 : 65535); - disp1->set_component_font_color((id + "icon").c_str(), esphome::display::ColorUtil::color_to_565(esphome::Color(icon_color[0], icon_color[1], icon_color[2]))); + disp1->set_component_font_color((id + "icon").c_str(), rgbTo565(icon_color)); disp1->set_component_text((id + "icon").c_str(), icon.c_str()); display_wrapped_text->execute((id + "text").c_str(), label.c_str(), 10); disp1->set_component_text((id + "bri").c_str(), (strcmp(bri.c_str(), "0") == 0) ? " " : bri.c_str()); @@ -219,7 +219,7 @@ api: then: - lambda: |- if (!id(is_uploading_tft)) - disp1->set_component_font_color(id.c_str(), esphome::display::ColorUtil::color_to_565(esphome::Color(color[0], color[1], color[2]))); + disp1->set_component_font_color(id.c_str(), rgbTo565(color)); # Updates the text of a specified component on the display. - service: component_text @@ -305,7 +305,7 @@ api: if (!id(is_uploading_tft) and !id.empty()) { disp1->set_component_text(id.c_str(), visible ? icon.c_str() : ""); if (icon_color.size() == 3) - disp1->set_component_font_color(id.c_str(), esphome::display::ColorUtil::color_to_565(esphome::Color(icon_color[0], icon_color[1], icon_color[2]))); + disp1->set_component_font_color(id.c_str(), rgbTo565(icon_color)); set_component_visibility->execute(id.c_str(), visible); } @@ -383,15 +383,11 @@ api: // Relay icon's colors if (relay1_icon_color.size() == 3) { - id(home_relay1_icon_color) = ColorUtil::color_to_565(esphome::Color(relay1_icon_color[0], - relay1_icon_color[1], - relay1_icon_color[2])); + id(home_relay1_icon_color) = rgbTo565(relay1_icon_color); disp1->set_component_font_color("home.chip_relay1", id(home_relay1_icon_color)); } if (relay2_icon_color.size() == 3) { - id(home_relay2_icon_color) = ColorUtil::color_to_565(esphome::Color(relay2_icon_color[0], - relay2_icon_color[1], - relay2_icon_color[2])); + id(home_relay2_icon_color) = rgbTo565(relay2_icon_color); disp1->set_component_font_color("home.chip_relay2", id(home_relay2_icon_color)); } @@ -401,13 +397,9 @@ api: update_bitwise_setting(id(buttons_settings), button_right, ButtonSettings::ButtonRight_Enabled); if (button_bar_color_on.size() == 3) - id(buttons_color_on) = ColorUtil::color_to_565(esphome::Color(button_bar_color_on[0], - button_bar_color_on[1], - button_bar_color_on[2])); + id(buttons_color_on) = rgbTo565(button_bar_color_on); if (button_bar_color_off.size() == 3) - id(buttons_color_off) = ColorUtil::color_to_565(esphome::Color(button_bar_color_off[0], - button_bar_color_off[1], - button_bar_color_off[2])); + id(buttons_color_off) = rgbTo565(button_bar_color_off); // Refresh relays display refresh_relays->execute(3); @@ -445,8 +437,8 @@ api: } // Date/Time colors - id(home_date_color) = ColorUtil::color_to_565(esphome::Color(date_color[0], date_color[1], date_color[2])); - id(home_time_color) = ColorUtil::color_to_565(esphome::Color(time_color[0], time_color[1], time_color[2])); + id(home_date_color) = rgbTo565(date_color); + id(home_time_color) = rgbTo565(time_color); disp1->set_component_font_color("home.date", id(home_date_color)); disp1->set_component_font_color("home.time", id(home_time_color)); @@ -475,22 +467,16 @@ api: // QRCode button set_component_visibility->execute("home.bt_qrcode", qrcode); disp1->set_component_text("home.bt_qrcode", qrcode_icon.c_str()); - disp1->set_component_font_color("home.bt_qrcode", ColorUtil::color_to_565(esphome::Color(qrcode_icon_color[0], - qrcode_icon_color[1], - qrcode_icon_color[2]))); + disp1->set_component_font_color("home.bt_qrcode", rgbTo565(qrcode_icon_color)); // Entities pages button disp1->set_component_text("home.bt_entities", entities_pages_icon.c_str()); - disp1->set_component_font_color("home.bt_entities", ColorUtil::color_to_565(esphome::Color(entities_pages_icon_color[0], - entities_pages_icon_color[1], - entities_pages_icon_color[2]))); + disp1->set_component_font_color("home.bt_entities", rgbTo565(entities_pages_icon_color)); // Utilities button disp1->send_command_printf("is_utilities=%i", utilities ? 1 : 0); disp1->set_component_text("home.bt_utilities", utilities_icon.c_str()); - disp1->set_component_font_color("home.bt_utilities", ColorUtil::color_to_565(esphome::Color(utilities_icon_color[0], - utilities_icon_color[1], - utilities_icon_color[2]))); + disp1->set_component_font_color("home.bt_utilities", rgbTo565(utilities_icon_color)); blueprint_status->publish_state(int(blueprint_status->raw_state) | (1 << 1)); } @@ -804,16 +790,12 @@ api: using namespace esphome::display; if (!(icon.empty())) disp1->set_component_text((id + "_icon").c_str(), icon.c_str()); if (icon_color.size() == 3) - disp1->set_component_font_color((id + "_icon").c_str(), ColorUtil::color_to_565(esphome::Color(icon_color[0], - icon_color[1], - icon_color[2]))); + disp1->set_component_font_color((id + "_icon").c_str(), rgbTo565(icon_color)); if (!(name.empty())) disp1->set_component_text((id + "_label").c_str(), name.c_str()); if (!(value.empty())) disp1->set_component_text(id.c_str(), adjustDecimalSeparator(value, id(mui_decimal_separator)).c_str()); if (value_color.size() == 3) - disp1->set_component_font_color(id.c_str(), ColorUtil::color_to_565(esphome::Color(value_color[0], - value_color[1], - value_color[2]))); + disp1->set_component_font_color(id.c_str(), rgbTo565(value_color)); if (current_page->state.find("entitypage") == 0 and !(value.empty())) { // Adjust value's font on entities pages // Adjusted length starts at 0 float adjusted_length = 0.0; @@ -1960,9 +1942,7 @@ script: // Screen saver page (sleep) id(screensaver_display_time) = screensaver_time; id(screensaver_display_time_font) = screensaver_time_font; - id(screensaver_display_time_color) = esphome::display::ColorUtil::color_to_565(esphome::Color(screensaver_time_color[0], - screensaver_time_color[1], - screensaver_time_color[2])); + id(screensaver_display_time_color) = rgbTo565(screensaver_time_color); page_screensaver->execute(); // Entities pages alignment @@ -2613,14 +2593,18 @@ script: parameters: brightness: float then: - - lambda: |- - if (brightness == display_brightness->state and current_page->state != "boot" and current_page->state != "screensaver") - disp1->send_command_printf("wakeup_timer.en=1"); - else - disp1->set_backlight_brightness(brightness / 100.0f); - current_brightness->update(); - - delay: 5s - - lambda: current_brightness->update(); + - if: + condition: + - lambda: return (!id(is_uploading_tft)); + then: + - lambda: |- + if (brightness == display_brightness->state and current_page->state != "boot" and current_page->state != "screensaver") + disp1->send_command_printf("wakeup_timer.en=1"); + else + disp1->set_backlight_brightness(brightness / 100.0f); + current_brightness->update(); + - delay: 5s + - lambda: current_brightness->update(); - id: set_climate mode: restart @@ -2637,8 +2621,7 @@ script: embedded_climate: bool then: - lambda: |- - if (id(is_uploading_tft)) set_climate->stop(); - if (current_page->state == "climate") { + if (!id(is_uploading_tft) and current_page->state == "climate") { bool useDecimal = (temp_step % 10 != 0); char buffer[15]; disp1->send_command_printf("climateslider.maxval=%i", total_steps); @@ -2713,7 +2696,8 @@ script: then: - lambda: |- NextionComponent component = extractNextionComponent(component_id, current_page->state); - if (component.is_current_page) disp1->send_command_printf("vis %s,%i", component.component_id.c_str(), show ? 1 : 0); + if (component.is_current_page) disp1->send_command_printf("vis %s,%i", component.component_id, show ? 1 : 0); + - id: stop_all mode: restart diff --git a/nspanel_blueprint.yaml b/nspanel_blueprint.yaml index 7b0ccc9..e76eb09 100644 --- a/nspanel_blueprint.yaml +++ b/nspanel_blueprint.yaml @@ -9989,6 +9989,7 @@ action: entity: '{{ "embedded_climate" if entity_id == thermostat_embedded else entity_id }}' back_page: '{{ back_page }}' continue_on_error: true + - alias: Custom action - Left conditions: '{{ last_click_button.hold_select == "Custom Action" and nspanel_event.component == "hw_bt_left" }}' sequence: !input left_button_hold_custom_action