From 93050ae34b8c8d899a2bbc01c2f70ab5869efaf8 Mon Sep 17 00:00:00 2001 From: Edward Firmo <94725493+edwardtfn@users.noreply.github.com> Date: Mon, 27 Nov 2023 11:15:36 +0100 Subject: [PATCH 01/13] Validate beta only on demand To reduce number of unnecessary runs (when no beta is available) --- .github/workflows/validate_esphome_beta.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/validate_esphome_beta.yml b/.github/workflows/validate_esphome_beta.yml index 81e7f91..ab5a45f 100644 --- a/.github/workflows/validate_esphome_beta.yml +++ b/.github/workflows/validate_esphome_beta.yml @@ -2,9 +2,9 @@ name: Validate ESPHome (beta) on: workflow_dispatch: - pull_request: - paths: - - "nspanel_esphome*.yaml" + #pull_request: + # paths: + # - "nspanel_esphome*.yaml" #schedule: #- cron: "0 0 * * *" From 239406053f38dac4798e6aaaf1bdf84c61d45a6d Mon Sep 17 00:00:00 2001 From: Edward Firmo <94725493+edwardtfn@users.noreply.github.com> Date: Mon, 27 Nov 2023 16:21:39 +0100 Subject: [PATCH 02/13] New entity anchor --- .gitignore | 4 +- custom_configuration/nspanel_blank.HMI | Bin 7368710 -> 7368710 bytes custom_configuration/nspanel_blank.tft | Bin 275288 -> 275288 bytes nspanel_blueprint.yaml | 245 +++++++++++++------------ 4 files changed, 133 insertions(+), 116 deletions(-) diff --git a/.gitignore b/.gitignore index d0af3ba..db0c583 100644 --- a/.gitignore +++ b/.gitignore @@ -10,4 +10,6 @@ Nextion2Text.* .idea # Ignore dev folder -dev \ No newline at end of file +dev + +nspanel_esphome_prebuilt.yaml diff --git a/custom_configuration/nspanel_blank.HMI b/custom_configuration/nspanel_blank.HMI index 5fc756c09fad769129736a186c651d64c7dac1ad..7fb4a377035cfe42fedc6c89163fc33f1cb00528 100644 GIT binary patch delta 739 zcmd6hOHWf#6ov1;x3naNL_)6?O_4zZgK8-gBei1XA>~ybTEQn=zy~6@{0A6T+7Ss2 zgA-@WnKS`%CWJ>qq6rBDW=xC|r!aC#Je0yuFrMVg*~wme?=Saj*U1^eS1ik#^`_@y z>Hcn0mLIJ7v#KC9g?Ob%p=C83kHITJrEF>kL zlrwCDWAGU&41S%Iz>m|~e|>WAbODlwae{Gzae{Gzae{Gz1eX4bz#1eT;61i5!TU_| z0bBWyDL!Hw+u6ZRK4urw>}C&p*~flnIKV*;ahOl|lvzIG2uJyxFZhzL_?mAx#&LR_ z;3TK`mhbqUA2`jAoZ%!ORIEzQ~sVf3OS zd3E&$Tp3imE?o690lzv(pg|I9=`LK`5#>ejO^0q`-zSbX+q6^ODk(J-HA4gSxTeAp z+?LJKkRFTRw|yR((ZxorIOoxki@;X&GLV%&B?D!0ZT6<koa{tT;g2~?(zve8Pa z9J=+Z09(J;@8!{1UG+$*{;7-#Q75U2kGfs2m%ZVFNhOn-I0UEQ5?TbeZIbrV@cs46 zdZWNpVsT<|VsT<|VsT<|V$!bvMd5+T&=5ojVf4d91OpgE6hjzB3?mrD7{(Dt0uz|T z6q1<63}!Khc`RTNDWtK446<0p3Rba(b!=c07PhdB9qeKc`#3-jhd9D9PLRjxBYSk$ nnf`WRx9c3EDn~h8ee$x~u6eoUus?!)Pj)uNH(mB!A7>(8(c7c; diff --git a/custom_configuration/nspanel_blank.tft b/custom_configuration/nspanel_blank.tft index 3b45242edd36760fb5e39a500fff403a7b0b1dc1..e3ef4114b0ba939f603a75ac03dca0801a454ce3 100644 GIT binary patch delta 316 zcmXwwKS;w+5XRrVd}$Sh1cLYnDIKy{+L%@grBen$aIxJwh=Oz76kYr?L;`hHZgJ_L zyC6K3EN(8M;F49rcI(!~MPEw2p!37uF6m!?r zFV`+Uq5n^o(07bekya!c;$C;13{po0+(-@NO6c2TARtQ|i=2pg#zUgRfrr6lK&A2Z Xdl|_;I}{GRq^x@N5lNhXwch>!?gmn= delta 312 zcmXwxze~eV5XaxWeA6ll2^6UoDIL04+L%(Kjt&9A4xyWaC^*+qbn(X!6m%*mx42}` zT_`?va}$CfxMX#(b?P7BV0|g|j^lgZ`+m5jXD2=TG^9|5#ua|dC9k^TT@T?*kG5)k z)z!l{0W@tuJ-8eX-|&n7l`%|w>>fsZuXFwsu+(ZsG?c6mO7J+108gLjat zwx-~NIviQZQ`<&|4nbDiR%vAKOE@qjh%04mQ_N8r zIdxOUCrI$|0{S^|BGT%Jrs(SSN&h^o;8tp&SVP|)0|8m$NEAiPGY%3R4jc?71FDU$ XUyDfoSx}B%Q&zpXIG1?neh>curKMAZ diff --git a/nspanel_blueprint.yaml b/nspanel_blueprint.yaml index 9dd5eef..9c4dc03 100644 --- a/nspanel_blueprint.yaml +++ b/nspanel_blueprint.yaml @@ -6462,7 +6462,7 @@ condition: conditions: - '{{ trigger.event is defined and trigger.event.data is defined }}' - '{{ trigger.event.data.entity_id is defined }}' - #- '{{ trigger.event.data.entity_id is match "light." }}' + - '{{ trigger.event.data.entity_id is match "light." }}' - condition: and conditions: - '{{ trigger.event is defined and trigger.event.data is defined }}' @@ -6543,6 +6543,78 @@ condition: ##### START - Action ##### ############################################################# action: + - if: '{{ false }}' # Global anchor repository + then: + - variables: + entity_id: '{{ None }}' + overlap: + icon: '{{ None }}' + icon_color: '{{ None }}' + frendly_name: '{{ None }}' + - &variable_entity + variables: + entity_id_is_valid: '{{ entity_id is defined and entity_id is string and entity_id.split(".") | count == 2 }}' + entity_state: '{{ states(entity_id) | default(None) if entity_id_is_valid else None }}' + entity_domain: '{{ entity_id.split(".")[0] if entity_id_is_valid else None }}' + entity_rgb_color: '{{ state_attr(entity_id, "rgb_color") | default(None) if entity_id_is_valid else None }}' + entity_brightness: '{{ state_attr(entity_id, "brightness") | default(None) if entity_id_is_valid else None }}' + entity_brightness_index: '{{ (entity_brightness | int(255))/255 if entity_brightness else 1 }}' + entity_icon: '{{ state_attr(entity_id, "icon") | default(None) if entity_id_is_valid else None }}' + entity_icon_color: > + {{ + entity_rgb_color | list + if entity_rgb_color is sequence + else + ( + nextion.color.on + if entity_state in enum.states.on + else nextion.color.off + ) + }} + entity_icon_color_adjusted: > + {{ + [ + min(255,entity_icon_color[0] * entity_brightness_index) | round(0), + min(255,entity_icon_color[1] * entity_brightness_index) | round(0), + min(255,entity_icon_color[2] * entity_brightness_index) | round(0) + ] + if entity_icon_color is sequence and entity_icon_color | count == 3 + else entity_icon_color + }} + entity: + id: '{{ entity_id }}' + valid: '{{ entity_id_is_valid }}' + state: '{{ entity_state }}' + state_is_number: '{{ is_number(entity_state) }}' + domain: '{{ entity_domain }}' + unit_of_measurement: '{{ state_attr(entity_id, "unit_of_measurement") | default(None) if entity_id_is_valid else None }}' + friendly_name: > + {{ + overlap.frendly_name + if overlap is defined and overlap.frendly_name is defined and overlap.frendly_name is string + else + ( + state_attr(entity_id, "friendly_name") | default(None) + if entity_id_is_valid + else None + ) + }} + supported_features: '{{ state_attr(entity_id, "supported_features") | default(None) if entity_id_is_valid else None }}' + icon: > + {% set icon_domain = nextion.icon.domain[entity_domain] | default(all_icons.blank) %} + {{ + all_icons[overlap.icon[1]] | default(icon_domain) + if overlap is defined and overlap.icon is defined and overlap.icon is string and overlap.icon.split(":") | count == 2 + else + ( + all_icons[entity_icon[1]] | default(icon_domain) + if entity_id_is_valid and entity_icon is string and entity_icon.split(":") | count == 2 + else icon_domain + ) + }} + icon_color: '{{ entity_icon_color_adjusted }}' + rgb_color: '{{ entity_rgb_color }}' + - alias: Main choices choose: ##### DATE ##### @@ -7031,42 +7103,21 @@ action: if: '{{ repeat.item.entity is defined and repeat.item.entity is string and repeat.item.entity | length > 0 }}' then: - variables: - entity_domain: '{{ repeat.item.entity.split(".")[0] if repeat.item.entity and repeat.item.entity.split(".") | count > 1 else "unknown" }}' - entity_state: '{{ states(repeat.item.entity) }}' - rgb_color: '{{ state_attr(repeat.item.entity, "rgb_color") | default(None) }}' - brightness: '{{ (state_attr(repeat.item.entity, "brightness") | int(255))/255 }}' - icon_color: > - {{ - rgb_color | list - if rgb_color is sequence - else - ( - nextion.color.on - if entity_state in enum.states.on - else nextion.color.off - ) - }} - icon_color_adjusted: > - {{ - [ - min(255,icon_color[0] * brightness) | round(0), - min(255,icon_color[1] * brightness) | round(0), - min(255,icon_color[2] * brightness) | round(0) - ] - if icon_color is sequence and icon_color | count == 3 - else icon_color - }} + entity_id: '{{ repeat.item.entity }}' + overlap: + icon: '{{ repeat.item.icon }}' + - *variable_entity - service: '{{ nextion.command.set_component_color }}' data: component: 'home.{{ repeat.item.component }}' - foreground: '{{ icon_color_adjusted }}' + foreground: '{{ entity.icon_color }}' background: [] continue_on_error: true - *delay-default - service: '{{ nextion.command.text_printf }}' data: component: 'home.{{ repeat.item.component }}' - message: > + message: '{{ entity.icon }}' {% if repeat.item.icon | length > 0 %} {{ all_icons[repeat.item.icon.split(":")[1]] | default(all_icons.unknown) @@ -8907,37 +8958,34 @@ action: - *variables_hardware - variables: last_click_button: '{{ hardware.buttons.left if nspanel_event.component == "hw_bt_left" else hardware.buttons.right }}' - entity_domain: > - {{ - last_click_button.entity.split(".")[0] | default("unknown") - if - last_click_button.entity is string and - last_click_button.entity | length > 0 and - last_click_button.entity.split(".") | count > 0 - else "unknown" - }} + entity_id: '{{ last_click_button.entity }}' + - *variable_entity + - condition: '{{ entity.valid }}' - choose: - alias: Long click conditions: - - '{{ nspanel_event.command == "long_click" or entity_domain in ["climate", "media_player"] }}' + - '{{ nspanel_event.command == "long_click" or entity.domain in ["climate", "media_player"] }}' sequence: - choose: - alias: Default conditions: - - '{{ last_click_button.hold_select == "Default" and last_click_button.entity | length > 0 }}' - - '{{ entity_domain in ["climate", "cover", "light", "fan", "media_player"] }}' - #- '{{ entity_domain != "cover" or state_attr(last_click_button.entity, "supported_features") | int(0) | bitwise_and(4) > 0 }}' - #- '{{ entity_domain != "fan" or state_attr(last_click_button.entity, "supported_features") | int(0) | bitwise_and(1) > 0 }}' - #- '{{ entity_domain != "light" or state_attr(last_click_button.entity, "supported_color_modes") | default("unknown") | string not in ["unknown", "onoff", enum.color_mode.unknown, enum.color_mode.onoff, "", none] }}' + - '{{ last_click_button.hold_select == "Default" }}' + - '{{ entity.domain in ["climate", "cover", "light", "fan", "media_player"] }}' + #- '{{ entity.domain != "cover" or entity.supported_features | bitwise_and(4) > 0 }}' + #- '{{ entity.domain != "fan" or entity.supported_features | bitwise_and(1) > 0 }}' + #- '{{ entity.domain != "light" or state_attr(entity.id, "supported_color_modes") | default("unknown") | string not in ["unknown", "onoff", enum.color_mode.unknown, enum.color_mode.onoff, "", none] }}' sequence: - - service: '{{ nextion.command.open_entity_settings_page }}' - data: - page: '{{ entity_domain }}' - page_label: '{{ last_click_button.name if last_click_button.name is string and last_click_button.name | length > 0 else state_attr(last_click_button.entity, "friendly_name") }}' - page_icon: '{{ nextion.icon.domain[entity_domain] }}' - page_icon_color: [-1 ] #No color set - entity: '{{ "embedded_climate" if last_click_button.entity == thermostat_embedded else last_click_button.entity }}' + - variables: back_page: '{{ page.home }}' + - &open_entity_settings_page + service: '{{ nextion.command.open_entity_settings_page }}' + data: + page: '{{ entity.domain }}' + page_label: '{{ entity.friendly_name }}' + page_icon: '{{ entity.icon }}' + page_icon_color: '{{ entity.icon_color }}' + 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" }}' @@ -8993,43 +9041,28 @@ action: - condition: '{{ last_click_button | count >= 0 }}' - variables: last_click_button: '{{ last_click_button[0] }}' - entity_domain: > - {{ - last_click_button.entity.split(".")[0] | default("unknown") - if - last_click_button.entity is string and - last_click_button.entity | length > 0 and - last_click_button.entity.split(".") | count > 0 - else "unknown" - }} + entity_id: '{{ last_click_button.entity }}' + overlap: + icon: '{{ last_click_button.icon }}' + friendly_name: '{{ last_click_button.name }}' + - *variable_entity + - condition: '{{ entity.valid }}' - choose: - alias: Long click conditions: - - '{{ nspanel_event.command == "long_click" or entity_domain in ["climate", "media_player"] }}' - - '{{ last_click_button.entity | length > 0 }}' - - '{{ entity_domain in ["climate", "cover", "light", "fan", "media_player"] }}' - #- '{{ entity_domain != "cover" or state_attr(last_click_button.entity, "supported_features") | int(0) | bitwise_and(4) > 0 }}' - #- '{{ entity_domain != "fan" or state_attr(last_click_button.entity, "supported_features") | int(0) | bitwise_and(1) > 0 }}' - #- '{{ entity_domain != "light" or state_attr(last_click_button.entity, "supported_color_modes") | default("unknown") | string not in ["unknown", "onoff", enum.color_mode.unknown, enum.color_mode.onoff, "", none] }}' + - '{{ nspanel_event.command == "long_click" or entity.domain in ["climate", "media_player"] }}' + - '{{ entity.domain in ["climate", "cover", "light", "fan", "media_player"] }}' + #- '{{ entity.domain != "cover" or entity.supported_features | bitwise_and(4) > 0 }}' + #- '{{ entity.domain != "fan" or entity.supported_features | bitwise_and(1) > 0 }}' + #- '{{ entity.domain != "light" or state_attr(entity_id, "supported_color_modes") | default("unknown") | string not in ["unknown", "onoff", enum.color_mode.unknown, enum.color_mode.onoff, "", none] }}' sequence: - - service: '{{ nextion.command.open_entity_settings_page }}' - data: - page: '{{ entity_domain }}' - page_label: '{{ last_click_button.name if last_click_button.name is string and last_click_button.name | length > 0 else state_attr(last_click_button.entity, "friendly_name") }}' - page_icon: > - {{ - all_icons[last_click_button.icon.split(":")[1]] | default(last_click_button.icon if last_click_button.icon is defined and last_click_button.icon is string else nextion.icon.domain[entity_domain]) - if last_click_button.icon not in ["unavailable", "unknown", "", None] and last_click_button.icon | length > 0 - else nextion.icon.domain[entity_domain] - }} - page_icon_color: '{{ [ last_click_button.icon_color_rgb ] if is_number(last_click_button.icon_color_rgb) else last_click_button.icon_color_rgb }}' - entity: '{{ "embedded_climate" if last_click_button.entity == thermostat_embedded else last_click_button.entity }}' + - variables: back_page: '{{ nspanel_event.page }}' - continue_on_error: true + - *open_entity_settings_page - alias: Short click conditions: - - '{{ nspanel_event.command == "short_click" and entity_domain not in ["climate", "media_player"] }}' - - '{{ entity_domain not in ["unknown", "person", "binary_sensor", "sensor"] }}' + - '{{ nspanel_event.command == "short_click" and entity.domain not in ["climate", "media_player"] }}' + - '{{ entity.domain not in ["unknown", "person", "binary_sensor", "sensor"] }}' sequence: - *short_press-service_call @@ -9078,36 +9111,21 @@ action: - condition: '{{ last_click_button | count >= 0 }}' - variables: last_click_button: '{{ last_click_button[0] }}' - entity_domain: > - {{ - last_click_button.entity.split(".")[0] | default("unknown") - if - last_click_button.entity is string and - last_click_button.entity | length > 0 and - last_click_button.entity.split(".") | count > 0 - else "unknown" - }} + entity_id: '{{ last_click_button.entity }}' + overlap: + icon: '{{ last_click_button.icon }}' + friendly_name: '{{ last_click_button.name }}' + - *variable_entity + - condition: '{{ entity.valid }}' - if: - - '{{ entity_domain in ["climate", "cover", "fan", "light", "media_player"] }}' - - '{{ last_click_button.entity | length > 0 }}' - #- '{{ entity_domain != "cover" or state_attr(last_click_button.entity, "supported_features") | int(0) | bitwise_and(4) > 0 }}' - #- '{{ entity_domain != "fan" or state_attr(last_click_button.entity, "supported_features") | int(0) | bitwise_and(1) > 0 }}' - #- '{{ entity_domain != "light" or state_attr(last_click_button.entity, "supported_color_modes") | default("unknown") | string not in ["unknown", "onoff", enum.color_mode.unknown, enum.color_mode.onoff, "", none] }}' + - '{{ entity.domain in ["climate", "cover", "fan", "light", "media_player"] }}' + #- '{{ entity.domain != "cover" or entity.supported_features | bitwise_and(4) > 0 }}' + #- '{{ entity.domain != "fan" or entity.supported_features | bitwise_and(1) > 0 }}' + #- '{{ entity.domain != "light" or state_attr(entity.id, "supported_color_modes") | default("unknown") | string not in ["unknown", "onoff", enum.color_mode.unknown, enum.color_mode.onoff, "", none] }}' then: - - service: '{{ nextion.command.open_entity_settings_page }}' - data: - page: '{{ entity_domain }}' - page_label: '{{ last_click_button.name if last_click_button.name is string and last_click_button.name | length > 0 else state_attr(last_click_button.entity, "friendly_name") }}' - page_icon: > - {{ - all_icons[last_click_button.icon.split(":")[1]] | default(last_click_button.icon if last_click_button.icon is defined and last_click_button.icon is string else nextion.icon.domain[entity_domain]) - if last_click_button.icon not in ["unavailable", "unknown", "", None] and last_click_button.icon | length > 0 - else nextion.icon.domain[entity_domain] - }} - page_icon_color: [-1 ] #No color set - entity: '{{ "embedded_climate" if last_click_button.entity == thermostat_embedded else last_click_button.entity }}' + - variables: back_page: '{{ page.home }}' - continue_on_error: true + - *open_entity_settings_page else: - *short_press-service_call @@ -9188,15 +9206,12 @@ action: - '{{ nspanel_event.value == "release" }}' - '{{ climate | length > 0 }} ' sequence: - - service: '{{ nextion.command.open_entity_settings_page }}' - data: - page: 'climate' - page_label: '{{ state_attr(climate, "friendly_name") }}' - page_icon: '' - page_icon_color: [ -1 ] #No color set - entity: '{{ "embedded_climate" if climate == thermostat_embedded else climate }}' + - variables: + entity_id: '{{ climate }}' back_page: '{{ page.home }}' - continue_on_error: true + - *variable_entity + - condition: '{{ entity.valid }}' + - *open_entity_settings_page - alias: Show button - Notification clear conditions: From 657512a470b01a3263ab74924b438bf6ea73decc Mon Sep 17 00:00:00 2001 From: Edward Firmo <94725493+edwardtfn@users.noreply.github.com> Date: Mon, 27 Nov 2023 16:23:23 +0100 Subject: [PATCH 03/13] Bump version to 4.1.3dev --- custom_configuration/nspanel_blank_code/blank.txt | 14 +++++++------- nspanel_blueprint.yaml | 4 ++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/custom_configuration/nspanel_blank_code/blank.txt b/custom_configuration/nspanel_blank_code/blank.txt index 1526e0d..186766a 100644 --- a/custom_configuration/nspanel_blank_code/blank.txt +++ b/custom_configuration/nspanel_blank_code/blank.txt @@ -3,7 +3,7 @@ Page blank ID : 0 Scope : local Dragging : 0 - Send Component ID : disabled + Send Component ID : on press and release Locked : no Swide up page ID : disabled Swide down page ID : disabled @@ -22,7 +22,7 @@ Page blank printh 92 prints "nspanelevent",0 printh 00 - prints "{\"page\": \"blank\", \"component\": \"currentpage\", \"value\": \"pagechange\", \"version\": \"2023.11.0\"}",0 + prints "{\"page\": \"blank\", \"component\": \"currentpage\", \"value\": \"pagechange\", \"version\": \"2023.11.1\"}",0 printh 00 printh FF FF FF covx baud,baud_rate.txt,0,0 @@ -48,7 +48,7 @@ Text file_name ID : 4 Scope : local Dragging : 0 - Send Component ID : disabled + Send Component ID : on press and release Associated Keyboard: none Text : nspanel_blank.tft Max. Text Size : 20 @@ -67,7 +67,7 @@ Text baud_rate ID : 5 Scope : local Dragging : 0 - Send Component ID : disabled + Send Component ID : on press and release Associated Keyboard: none Text : Max. Text Size : 20 @@ -86,7 +86,7 @@ Text tft_version ID : 6 Scope : local Dragging : 0 - Send Component ID : disabled + Send Component ID : on press and release Associated Keyboard: none Text : TFT: v2023.11.0 Max. Text Size : 20 @@ -124,7 +124,7 @@ Text esp_version ID : 8 Scope : local Dragging : 0 - Send Component ID : disabled + Send Component ID : on press and release Associated Keyboard: none Text : ESP: Waiting... Max. Text Size : 20 @@ -143,7 +143,7 @@ QR Code qr0 ID : 1 Scope : local Dragging : 0 - Send Component ID: disabled + Send Component ID: on press and release Text : https://github.com/Blackymas/NSPanel_HA_Blueprint/blob/main/docs/en/nspanel_blank.md#what-to-do-after-installing-nspanel_blanktft Max. Text Size : 150 diff --git a/nspanel_blueprint.yaml b/nspanel_blueprint.yaml index 9c4dc03..3a3559b 100644 --- a/nspanel_blueprint.yaml +++ b/nspanel_blueprint.yaml @@ -33,7 +33,7 @@ blueprint: 🎉 Roadmap can be found here: [Roadmap](https://github.com/Blackymas/NSPanel_HA_Blueprint/labels/roadmap) - ℹ️ Version: v4.1.2 + ℹ️ Version: v4.1.3dev source_url: https://github.com/Blackymas/NSPanel_HA_Blueprint/blob/main/nspanel_blueprint.yaml domain: automation @@ -3603,7 +3603,7 @@ trigger_variables: variables: ##### GENERAL ##### - blueprint_version: '4.1.2' + blueprint_version: '4.1.3dev' date_format_temp: !input 'date_format' #Avoid breaking change for existing users with legacy type format date_format: > From 66c80a490b4a8502507d7c22436eb516fd57ba7b Mon Sep 17 00:00:00 2001 From: Edward Firmo <94725493+edwardtfn@users.noreply.github.com> Date: Mon, 27 Nov 2023 17:14:30 +0100 Subject: [PATCH 04/13] Icons standardization --- ReleaseNotes.md | 139 ++++---------------------------------- nspanel_blueprint.yaml | 121 ++++++++++++++------------------- nspanel_esphome_core.yaml | 5 +- 3 files changed, 66 insertions(+), 199 deletions(-) diff --git a/ReleaseNotes.md b/ReleaseNotes.md index 3ddd6ce..7609539 100644 --- a/ReleaseNotes.md +++ b/ReleaseNotes.md @@ -1,4 +1,4 @@ -# v4.1.1 - Easier TFT transfer +# v4.2 <= pending update ## Support this project @@ -7,7 +7,7 @@ [![Paypal](https://user-images.githubusercontent.com/41958506/212499642-b2fd097a-0938-4bfc-b37b-74df64592c58.png)](https://www.paypal.com/donate/?hosted_button_id=S974SWQMB8PB2)   -## General +## General <= pending update The focus this time was on making easier to transfer the TFT files. As this project grows with features, it becomes more hungry of resources from all sides (ESPHome, your Home Assistant server and even the Nextion display) and with that the chances of something going wrong when updating increased. @@ -16,12 +16,12 @@ So at this release we concentrated on cleaning up de code everywhere and shave s   ## Updating -Since in this update lots of input to the blueprint changed, we highly recommend you review your settings and make sure all fields have the correct selection. We did our best to support your legacy settings and avoid breaking your system, but please double check your settings if you see something not working as expected. +Updates may come with changes on the blueprint inputs and we highly recommend you review your settings and make sure all fields have the correct selection. We did our best to support your legacy settings and avoid breaking your system, but please double check your settings if you see something not working as expected. ### Files that need to be reloaded: -1. nspanel_eu.tft, nspanel_us.tft or nspanel_us_land.tft - v4.1 -2. nspanel_esphome.yaml - v4.1 -3. nspanel_blueprint.yaml - v4.1 +1. nspanel_eu.tft, nspanel_us.tft or nspanel_us_land.tft - v4.2 +2. nspanel_esphome.yaml - v4.2 +3. nspanel_blueprint.yaml - v4.2 #### @@ -33,144 +33,33 @@ Since in this update lots of input to the blueprint changed, we highly recommend   ## Breaking changes -1. Home Assistant 2023.9.0 or later is now required -
This is necessary in order to support the new [Weather Forecast Service](https://www.home-assistant.io/blog/2023/09/06/release-20239/#weather-forecast-service). -
The legacy forecast based on attributes will be deprecated in Home Assistant on the begining of 2024. +1. Service `esphome.xxxxx_set_component_color` parameter `background` is deprecated. -2. The following components are now deprecated: - - Buttons: - - Exit reparse (`button.xxxxx_exit_reparse`) - It shouldn't be necessary with the new TFT upload engine - - ~~Services:~~ - - ~~Play rtttl (`esphome.xxxxx_play_rtttl`)~~ - - Sensors: - - API uptime - - Device uptime - - RSSI - - ESPhome Version - - IP - - SSID - - BSSID - - ESPHome components: - - Web server - - You can still add these components (except by "Exit reparse") by adding the file `nspanel_esphome_advanced.yaml` in your ESPHome settings as in the example bellow, but please have in mind that these are not used by the blueprint and will be using some memory of your ESPHome. -```yaml -... -packages: - remote_package: - url: https://github.com/Blackymas/NSPanel_HA_Blueprint - ref: main - files: - - nspanel_esphome.yaml # Core package - - nspanel_esphome_advanced.yaml # activate advanced (legacy) elements - can be useful for troubleshooting -... -``` - -3. Very long press on hardware buttons -
If you have a custom automation using very long hold (more than 15s) of hardware buttons it may fail as now the panel will restart with button hold for 15s. - -4. Logger via UART is disable -
You can add it back as a [customization](https://github.com/Blackymas/NSPanel_HA_Blueprint/wiki/%28EN%29-Customization/_edit#logger-via-uart). -5. Service `notification_show` parameter `text` was renamed to `message` to support ESPHome 2023.11.0. - -6. Buttons now will run an automation -
On the previous versions, a button with an automation will enable or disable the automation. Now the button will trigger the automation. -
If you want the legacy behavior, please create a script to enable/disable the automation and assign this to your button. 😉   ## Overview of noteworthy changes -1. New Upload TFT engine -2. Hardware restarts with button hold for 15s -3. Support to `esp-idf` framework -4. Support to 921600 bps -5. Font size for chips -6. Short click to open Climate and Media Player - -- 4.1.1 patch: - - Service `esphome.xxxxx_play_rtttl` is back to core package - - Stop the fallback to Home page when Notification or Confirm pages are shown - - Display current temperature on buttons even when climate is off - - Buttons on both the Home and Buttons pages shows entity's icon when available -- 4.1.2 patch: - - Fix light icon color not being used for buttons - - Add option to wake-up to Climate page - - New switch "Nextion display - Power" which can turn the screen on/off - - Fix for button image update when local fallback is enabled. - - Don't show thermostat chip when state is `auto` and no action is available. - - Fallback to legacy forecast (attributes based) when new method (service based) fails. - - Fix custom buttons icons not updating on Home page +1. Standardized entity's icons   ## Details of noteworthy changes -### 1. New Upload TFT engine -We rebuilt the Upload TFT engine and now it will be using less resources from ESPHome during the transfer and, in addition, will provide more logs for troubleshooting. - -The original engine from Nextion component still as a fallback in case the new engine fails, but it might be removed in a future version. +### 1. Standardized entity's icons +The engine for defining the icons to be shown and it's color was standardize between all the pages, so now you may see a more consistent User's Interface.   -### 2. Hardware restarts with button hold for 15s -Now if you press the hardware buttons for more than 15s, the panel will act as the following: -| Button | Action | Details | -| :--: | :--: | :- | -| Left | Power cycle the screen | It can remove the need to power cycle the panel when the screen can't stablish connection to ESPHome (`Nextion is not connected!` on logs). | -| Right | Restarts the panel | It is equivalent to press the "Restart" button on the Settings page or from Home Assistant, but is available even when the Wi-Fi isn't connected or Home Assistant is out. | - -  -### 3. Support to `esp-idf` framework -Although this project still using ESPHome default framework (currently `arduino`), we started supporting the framework `esp-idf` as this is a recomendation from ESPHome team since an year ago or so. -The `arduino` protocol still more popular and therefore more components are available, but there are some advantages with the ESP-IDF framework: -- It is updated more frequently by EspressIF, which means it is more secure and stable. -- It reduces a layer, as Arduino is developed in top of ESP-IDF, so basically we are changing from ESPHome -> Arduino -> ESP-IDF -> Hardware to ESPHome -> ESP-IDF -> Hardware. -- By reducing a layer, more memory is available for future features and for the custom components you might want to add to your panel. -- The memory management is more efficient, which makes critical tasks, like uploading a TFT file, more reliable. -
In the future we will probably make this as the default framework, so if you are a new user or if for some reason you have to flash your panel via serial/usb, it's a good idea to change to ESP-IDF now. -
Please look at [customizations docs in the Wiki](https://github.com/Blackymas/NSPanel_HA_Blueprint/wiki/(EN)-Customization#framework-esp-idf) for more details on how to change the framework. -
-> The ESP-IDF framework will be used when you enable the advanced mode. - -  -### 4. Support to 921600 bps -Until this point, the communication between ESPHome and the Nextion display is at 115200 bps. This is not changing for now, but we are preparing to change this in the near future to a higer rate of 921600 bps, which is 8 times higher than the one used currently. -After installing the latest TFT v4.1, you will be able to [change the speed as a customization](https://github.com/Blackymas/NSPanel_HA_Blueprint/wiki/(EN)-Customization#change-uarts-baud-rate) and your system will switch to the higher transfer rate after the next boot. -
In most of the cases, you are not going to see any diference related tho this change, but it will reduce significantly the time to upload a TFT file. - -  -### 5. Font size for chips -We ran a poll, the maiority select one option, but we respect the minority and... we supported both options. :smiley: -
Now you can have your chips a bit bigger, making it easier to see from the distance: - -| 24px | 32px | -| :--: | :--: | -| As before | New option | -| ![EU font 8](https://github.com/Blackymas/NSPanel_HA_Blueprint/assets/94725493/dfb79856-8456-443c-b4de-a955be8e4561) | ![EU font 9](https://github.com/Blackymas/NSPanel_HA_Blueprint/assets/94725493/268ec945-94dc-4f17-be94-8abd691ef2ed) | -| ![US font 8](https://github.com/Blackymas/NSPanel_HA_Blueprint/assets/94725493/a9eb3578-901a-444f-9d52-3909f2aa4f34) | ![US font 9](https://github.com/Blackymas/NSPanel_HA_Blueprint/assets/94725493/e46bac5e-8a84-4cfe-a01d-159042828350) | - -If you wanna try the new option, just go to your blueprint setting and select the font size: -![image](https://github.com/Blackymas/NSPanel_HA_Blueprint/assets/94725493/83e64dfa-b2cf-4186-af2a-6f89c96e9044) - -  -### 6. Short click to open Climate and Media Player -Now on any button page, buttons assigned to Climate or Media Player entities will always open the detailed page. As it is hard to define an adequate toggle action for all the different cases, it makes more sense to just open the page and let the control to the users. +### 2.   ## Next topics we are currently working on See here: https://github.com/Blackymas/NSPanel_HA_Blueprint/labels/roadmap +  ## Special thanks to: -- @misery - Fixing a typo in the docs (#1176) -- @Floppe - Adding space for value fields on Home page (#1180) -- @bkbartk: - - Display current temperature on buttons even when climate is off (#1268) - - Button pages shows entity's icon when available (#1269) - - Home page buttons shows entity's icon when available (#1276) - - New switch "Nextion display - Power" which can turn the screen on/off (#1327) - - Fix for button image update when local fallback is enabled (#1333) ## New Contributors -@misery -@Floppe -@bkbartk ## Previous releases +- [v4.1.2 - Bug fixes and minor enhancements](https://github.com/Blackymas/NSPanel_HA_Blueprint/releases/tag/v4.1.2) +- [v4.1.1 - UI Enhancements and Reintroduction of `play_rtttl` Service](https://github.com/Blackymas/NSPanel_HA_Blueprint/releases/tag/v4.1.1) - [v4.1 - Easier TFT transfer](https://github.com/Blackymas/NSPanel_HA_Blueprint/releases/tag/v4.1) - [v4.0.2 - Bug fixes](https://github.com/Blackymas/NSPanel_HA_Blueprint/releases/tag/v4.0.2) - [v4.0.1 - Bug fixes](https://github.com/Blackymas/NSPanel_HA_Blueprint/releases/tag/v4.0.1) diff --git a/nspanel_blueprint.yaml b/nspanel_blueprint.yaml index 3a3559b..8a64076 100644 --- a/nspanel_blueprint.yaml +++ b/nspanel_blueprint.yaml @@ -33,7 +33,7 @@ blueprint: 🎉 Roadmap can be found here: [Roadmap](https://github.com/Blackymas/NSPanel_HA_Blueprint/labels/roadmap) - ℹ️ Version: v4.1.3dev + ℹ️ Version: v4.2dev source_url: https://github.com/Blackymas/NSPanel_HA_Blueprint/blob/main/nspanel_blueprint.yaml domain: automation @@ -3603,7 +3603,7 @@ trigger_variables: variables: ##### GENERAL ##### - blueprint_version: '4.1.3dev' + blueprint_version: '4.2dev' date_format_temp: !input 'date_format' #Avoid breaking change for existing users with legacy type format date_format: > @@ -6551,11 +6551,13 @@ action: icon: '{{ None }}' icon_color: '{{ None }}' frendly_name: '{{ None }}' - - &variable_entity + - &variable_entity #DEBUG variables: entity_id_is_valid: '{{ entity_id is defined and entity_id is string and entity_id.split(".") | count == 2 }}' entity_state: '{{ states(entity_id) | default(None) if entity_id_is_valid else None }}' entity_domain: '{{ entity_id.split(".")[0] if entity_id_is_valid else None }}' + entity_hvac_action: '{{ state_attr(entity_id, "hvac_action") | default(None) if entity_id_is_valid else None }}' + entity_climate_action: '{{ (entity_hvac_action if entity_hvac_action not in ["unavailable", "unknown", "", None] else entity_state) if entity_domain == "climate" else None }}' entity_rgb_color: '{{ state_attr(entity_id, "rgb_color") | default(None) if entity_id_is_valid else None }}' entity_brightness: '{{ state_attr(entity_id, "brightness") | default(None) if entity_id_is_valid else None }}' entity_brightness_index: '{{ (entity_brightness | int(255))/255 if entity_brightness else 1 }}' @@ -6584,6 +6586,7 @@ action: entity: id: '{{ entity_id }}' valid: '{{ entity_id_is_valid }}' + has_value: '{{ has_value(entity_id) if entity_id_is_valid else false }}' state: '{{ entity_state }}' state_is_number: '{{ is_number(entity_state) }}' domain: '{{ entity_domain }}' @@ -6602,18 +6605,43 @@ action: supported_features: '{{ state_attr(entity_id, "supported_features") | default(None) if entity_id_is_valid else None }}' icon: > {% set icon_domain = nextion.icon.domain[entity_domain] | default(all_icons.blank) %} - {{ - all_icons[overlap.icon[1]] | default(icon_domain) - if overlap is defined and overlap.icon is defined and overlap.icon is string and overlap.icon.split(":") | count == 2 - else - ( - all_icons[entity_icon[1]] | default(icon_domain) - if entity_id_is_valid and entity_icon is string and entity_icon.split(":") | count == 2 - else icon_domain - ) - }} - icon_color: '{{ entity_icon_color_adjusted }}' + {% if overlap is defined and overlap.icon is defined and overlap.icon is string and overlap.icon.split(":") | count == 2 %} + {{ all_icons[overlap.icon[1]] | default(icon_domain) }} + {% elif entity_domain == "climate" %} + {% if "off" in entity_climate_action %}{{ all_icons.blank }} + {% elif "heating" in entity_climate_action or "heat" in entity_climate_action %}{{ all_icons["thermometer-lines"] }} + {% elif "cooling" in entity_climate_action or "cool" in entity_climate_action %}{{ all_icons.snowflake }} + {% elif "drying" in entity_climate_action or "dry" in entity_climate_action %}{{ all_icons["water-percent"] }} + {% elif "fan" in entity_climate_action or "fan_only" in entity_climate_action %}{{ all_icons.fan }} + {% elif "heat_cool" in entity_climate_action %}{{ all_icons.autorenew }} + {% elif "auto" in entity_climate_action %}{{ all_icons.blank }} + {% elif "idle" in entity_climate_action %}{{ all_icons.thermometer }} + {% else %}{{ icon_domain }} + {% endif %} + {% else %} + {{ + all_icons[entity_icon[1]] | default(icon_domain) + if entity_id_is_valid and entity_icon is string and entity_icon.split(":") | count == 2 + else icon_domain + }} + {% endif %} + icon_color: > + {% if entity_domain == "climate" %} + {% if "off" in entity_climate_action %}{{ nextion.color["off"] }} + {% elif "heating" in entity_climate_action or "heat" in entity_climate_action %}{{ nextion.color["deep-orange"]}} + {% elif "cooling" in entity_climate_action or "cool" in entity_climate_action %}{{ nextion.color["blue"] }} + {% elif "drying" in entity_climate_action or "dry" in entity_climate_action %}{{ nextion.color["orange"] }} + {% elif "fan" in entity_climate_action or "fan_only" in entity_climate_action %}{{ nextion.color["cyan"] }} + {% elif "heat_cool" in entity_climate_action %}{{ nextion.color["amber"] }} + {% elif "auto" in entity_climate_action %}{{ nextion.color["off"] }} + {% elif "idle" in entity_climate_action %}{{ nextion.color["off"] }} + {% else %}{{ nextion.color["off"] }} + {% endif %} + {% else %}{{ entity_icon_color_adjusted }} + {% endif %} rgb_color: '{{ entity_rgb_color }}' + hvac_action: '{{ entity_hvac_action }}' + climate_action: '{{ entity_climate_action }}' - alias: Main choices choose: @@ -6921,7 +6949,6 @@ action: data: component: home.outdoor_temp foreground: '{{ [ outdoor_temp_color_rgb ] if is_number(outdoor_temp_color_rgb) else outdoor_temp_color_rgb }}' - background: [] continue_on_error: true ### LABEL Outdoor Temp Font ### - *delay-default @@ -6979,7 +7006,6 @@ action: data: component: home.indoortempicon foreground: '{{ [ indoor_temp.icon.color_rgb ] if is_number(indoor_temp.icon.color_rgb) else indoor_temp.icon.color_rgb }}' - background: [] continue_on_error: true ### ICON Indoor Temp Font ### - *delay-default @@ -7004,7 +7030,6 @@ action: data: component: home.current_temp foreground: '{{ [ indoor_temp.label.color_rgb ] if is_number(indoor_temp.label.color_rgb) else indoor_temp.label.color_rgb }}' - background: [] continue_on_error: true - if: '{{ not embedded_indoor_temperature }}' then: @@ -7031,7 +7056,6 @@ action: data: component: home.left_bt_text foreground: '{{ [ hardware.buttons.left.color_rgb ] if is_number(hardware.buttons.left.color_rgb) else hardware.buttons.left.color_rgb }}' - background: [] continue_on_error: true ### LABEL Font ### - *delay-default @@ -7060,7 +7084,6 @@ action: data: component: home.right_bt_text foreground: '{{ [ hardware.buttons.right.color_rgb ] if is_number(hardware.buttons.right.color_rgb) else hardware.buttons.right.color_rgb }}' - background: [] continue_on_error: true ### LABEL Font ### - *delay-default @@ -7111,25 +7134,12 @@ action: data: component: 'home.{{ repeat.item.component }}' foreground: '{{ entity.icon_color }}' - background: [] continue_on_error: true - *delay-default - service: '{{ nextion.command.text_printf }}' data: component: 'home.{{ repeat.item.component }}' message: '{{ entity.icon }}' - {% if repeat.item.icon | length > 0 %} - {{ - all_icons[repeat.item.icon.split(":")[1]] | default(all_icons.unknown) - if repeat.item.icon.split(":") | count > 0 - else repeat.item.icon - }} - {% elif state_attr(repeat.item.entity, "icon") | default("") not in ["unavailable", "unknown", "", None] and all_icons[state_attr(repeat.item.entity, "icon").split(":")[1]] != null %} - {{ all_icons[state_attr(repeat.item.entity, "icon").split(":")[1]] | default(all_icons.unknown) }} - {% elif repeat.item.entity and repeat.item.entity.split(".") | count > 1 %} - {{ nextion.icon.domain[entity_domain] }} - {% else %}{{ nextion.icon.domain.unknown }} - {% endif %} continue_on_error: true - if: '{{ page.current == page.home }}' then: @@ -7149,49 +7159,27 @@ action: ###### Climate chip ###### - &update-home_page-climate_chip if: '{{ (not embedded_climate) and climate is string and climate is match "climate." }}' - then: - - if: '{{ has_value(climate) }}' + then: # DEBUG + - variables: + entity_id: '{{ climate }}' + - *variable_entity + - if: '{{ entity.has_value }}' then: - - variables: - climate_state: '{{ states(climate) | default("unavailable") if climate is string else "unavailable" }}' - hvac_action: '{{ state_attr(climate, "hvac_action") | default("unavailable") if climate is string else "unavailable" }}' - climate_action: '{{ hvac_action if hvac_action not in ["unavailable", "unknown", "", None] else climate_state }}' - *delay-default - - if: '{{ climate_action in ["off", "heating", "heat", "cooling", "cool", "dry", "fan", "fan_only", "heat_cool", "auto", "idle"] }}' + - if: '{{ entity.climate_action in ["off", "heating", "heat", "cooling", "cool", "dry", "fan", "fan_only", "heat_cool", "auto", "idle"] }}' then: ### ICON Font Color ### - service: '{{ nextion.command.set_component_color }}' data: component: home.icon_top_03 - foreground: > - {% if "off" in climate_action %}{{ nextion.color["off"] }} - {% elif "heating" in climate_action or "heat" in climate_action %}{{ nextion.color["deep-orange"]}} - {% elif "cooling" in climate_action or "cool" in climate_action %}{{ nextion.color["blue"] }} - {% elif "drying" in climate_action or "dry" in climate_action %}{{ nextion.color["orange"] }} - {% elif "fan" in climate_action or "fan_only" in climate_action %}{{ nextion.color["cyan"] }} - {% elif "heat_cool" in climate_action %}{{ nextion.color["amber"] }} - {% elif "auto" in climate_action %}{{ nextion.color["off"] }} - {% elif "idle" in climate_action %}{{ nextion.color["off"] }} - {% else %}{{ nextion.color["off"] }} - {% endif %} - background: [] + foreground: '{{ entity.icon_color }}' continue_on_error: true ### ICON Font ### - *delay-default - service: '{{ nextion.command.text_printf }}' data: component: home.icon_top_03 - message: > - {% if "off" in climate_action %}{{ all_icons.blank }} - {% elif "heating" in climate_action or "heat" in climate_action %}{{ all_icons["thermometer-lines"] }} - {% elif "cooling" in climate_action or "cool" in climate_action %}{{ all_icons.snowflake }} - {% elif "drying" in climate_action or "dry" in climate_action %}{{ all_icons["water-percent"] }} - {% elif "fan" in climate_action or "fan_only" in climate_action %}{{ all_icons.fan }} - {% elif "heat_cool" in climate_action %}{{ all_icons.autorenew }} - {% elif "auto" in climate_action %}{{ all_icons.blank }} - {% elif "idle" in climate_action %}{{ all_icons.thermometer }} - {% else %}{{ all_icons.blank }} - {% endif %} + message: '{{ entity.icon }}' continue_on_error: true else: - &hide-home_page-climate_chip @@ -7264,7 +7252,6 @@ action: data: component: 'home.{{ repeat.item.component }}' foreground: '{{ [ repeat.item.icon_color_rgb ] if is_number(repeat.item.icon_color_rgb) else repeat.item.icon_color_rgb }}' - background: [] continue_on_error: true ### ICON Font ### - *delay-default @@ -7320,7 +7307,6 @@ action: data: component: '{{ repeat.item.page }}.{{ repeat.item.component }}_icon' foreground: '{{ [ repeat.item.icon_color_rgb ] if is_number(repeat.item.icon_color_rgb) else repeat.item.icon_color_rgb }}' - background: [] continue_on_error: true ### ICON Font ### - *delay-default @@ -7349,7 +7335,6 @@ action: data: component: '{{ repeat.item.page }}.{{ repeat.item.component }}{{ "_state" if repeat.item.page == page.home }}' foreground: '{{ [ repeat.item.label_color_rgb ] if is_number(repeat.item.label_color_rgb) else repeat.item.label_color_rgb }}' - background: [] continue_on_error: true ### LABEL Font ### - *delay-default @@ -7998,7 +7983,6 @@ action: data: component: cover.battery_icon foreground: '{{ nextion.color.grey_super_light }}' - background: [] continue_on_error: true ### ICON Battery Font ### - *delay-default @@ -8051,21 +8035,18 @@ action: data: component: fan.button_up foreground: '{{ nextion.color.grey_white if fan.percentage < 100 else nextion.color.grey_dark }}' - background: [] continue_on_error: true - *delay-default - service: '{{ nextion.command.set_component_color }}' data: component: fan.button_down foreground: '{{ nextion.color.grey_white if fan.percentage > 0 else nextion.color.grey_dark }}' - background: [] continue_on_error: true - *delay-default - service: '{{ nextion.command.set_component_color }}' data: component: fan.button_off foreground: '{{ nextion.color.grey_white if fan.percentage > 0 else nextion.color.grey_dark }}' - background: [] continue_on_error: true ## PAGE MEDIA PLAYER ## @@ -8281,7 +8262,6 @@ action: if states(climate_entity) == repeat.item.mode else nextion.color.disabled }} - background: [] continue_on_error: true ### ICON Font ### - *delay-default @@ -8339,7 +8319,6 @@ action: if states(repeat.item.entity) in ["on", "true", true, "open", "opening"] else nextion.color.disabled }} - background: [] continue_on_error: true ### ICON Font ### - *delay-default diff --git a/nspanel_esphome_core.yaml b/nspanel_esphome_core.yaml index 162a8ad..1cdbb7d 100644 --- a/nspanel_esphome_core.yaml +++ b/nspanel_esphome_core.yaml @@ -7,7 +7,7 @@ substitutions: ##### DON'T CHANGE THIS ##### - version: "4.1.2" + version: "4.2dev" ############################# ##### ESPHOME CONFIGURATION ##### @@ -185,9 +185,8 @@ api: variables: component: string foreground: int[] - background: int[] then: - - lambda: set_component_color->execute(component, foreground, background); + - lambda: set_component_color->execute(component, foreground, {}); ##### Service to play a rtttl tones ##### # Example tones : https://codebender.cc/sketch:109888#RTTTL%20Songs.ino From a60ad3abd300e3db8c845c58bab8015d2fa8f45a Mon Sep 17 00:00:00 2001 From: Edward Firmo <94725493+edwardtfn@users.noreply.github.com> Date: Mon, 27 Nov 2023 19:05:36 +0100 Subject: [PATCH 05/13] Fix display as a light As per #1348 --- ..._advanced_climate_heat_customizations.yaml | 49 ++++++++++--------- 1 file changed, 27 insertions(+), 22 deletions(-) diff --git a/.test/esphome_advanced_climate_heat_customizations.yaml b/.test/esphome_advanced_climate_heat_customizations.yaml index d42d430..a9f881c 100644 --- a/.test/esphome_advanced_climate_heat_customizations.yaml +++ b/.test/esphome_advanced_climate_heat_customizations.yaml @@ -120,14 +120,14 @@ light: on_turn_on: then: - lambda: |- - ESP_LOGV("light.display_light", "Turn-on"); - if (id(current_page).state == "screensaver") id(disp1).goto_page(id(wakeup_page_name).state.c_str()); - id(timer_reset_all).execute(id(wakeup_page_name).state.c_str()); + ESP_LOGD("light.display_light", "Turn-on"); + if (current_page->state == "screensaver") disp1->goto_page(wakeup_page_name->state.c_str()); + timer_reset_all->execute(wakeup_page_name->state.c_str()); on_turn_off: then: - lambda: |- - ESP_LOGV("light.display_light", "Turn-off"); - id(disp1).goto_page("screensaver"); + ESP_LOGD("light.display_light", "Turn-off"); + disp1->goto_page("screensaver"); logger: # Enable hardware UART serial logging @@ -146,22 +146,22 @@ output: write_action: - lambda: |- ESP_LOGV("output.display_output", "state: %f", state); - uint current_brightness = int(round(id(display_light).current_values.is_on() ? (id(display_light).current_values.get_brightness() * 100.0f) : 0.0)); + uint8_t current_brightness = int(round(display_light->current_values.is_on() ? (display_light->current_values.get_brightness() * 100.0f) : 0.0)); ESP_LOGV("output.display_output", "current_brightness: %i%%", current_brightness); - id(set_brightness).execute(current_brightness); - + set_brightness->execute(current_brightness); + script: # Updates the existing `page_changed` script to update the `display_light` status when a page changes - id: !extend page_changed then: - lambda: |- - ESP_LOGV("script.page_changed(custom)", "page: %s", page.c_str()); - ESP_LOGV("script.page_changed(custom)", "is_on(): %i", id(display_light).current_values.is_on() ? 1 : 0); - if (page == "screensaver" and id(display_light).current_values.is_on()) { - auto call = id(display_light).turn_off(); + ESP_LOGD("script.page_changed(custom)", "page: %s", page.c_str()); + ESP_LOGV("script.page_changed(custom)", "is_on(): %s", display_light->current_values.is_on() ? "True" : "False"); + if (page == "screensaver" and display_light->current_values.is_on()) { + auto call = display_light->turn_off(); call.perform(); - } else if (page != "screensaver" and (not id(display_light).current_values.is_on())) { - auto call = id(display_light).turn_on(); + } else if (page != "screensaver" and (not display_light->current_values.is_on())) { + auto call = display_light->turn_on(); call.perform(); } @@ -169,14 +169,19 @@ script: - id: !extend set_brightness then: - lambda: |- - ESP_LOGD("script.set_brightness(custom)", "brightness: %i", brightness); - if (id(current_page).state != "screensaver" and brightness > 0) { - auto call = id(display_light).turn_on(); - call.set_brightness(static_cast(id(display_last_brightness)) / 100.0f); - call.perform(); - } else { - auto call = id(display_light).turn_off(); - call.perform(); + ESP_LOGD("script.set_brightness(custom)", "brightness: %i%%", brightness); + uint8_t current_brightness = int(round(display_light->current_values.is_on() ? (display_light->current_values.get_brightness() * 100.0f) : 0.0)); + ESP_LOGV("script.set_brightness(custom)", "current_brightness: %i%%", current_brightness); + if (brightness != current_brightness) { + if (current_page->state != "screensaver" and brightness > 0) { + auto call = display_light->turn_on(); + call.set_brightness(static_cast(id(display_last_brightness)) / 100.0f); + call.perform(); + } else if (display_light->current_values.is_on()) { + auto call = display_light->turn_off(); + call.set_brightness(0); + call.perform(); + } } time: From 629bf0aad149b93aaae68eb6ede5a47a72bd5213 Mon Sep 17 00:00:00 2001 From: Edward Firmo <94725493+edwardtfn@users.noreply.github.com> Date: Mon, 27 Nov 2023 20:34:40 +0100 Subject: [PATCH 06/13] Replace page announcement Using "sendme" in replace to text sensor "currentpage". --- nspanel_esphome_core.yaml | 37 ++++++++++++++------------ nspanel_eu.HMI | Bin 14899044 -> 14899044 bytes nspanel_eu.tft | Bin 7247932 -> 7245860 bytes nspanel_eu_code/alarm.txt | 7 +---- nspanel_eu_code/boot.txt | 9 ++----- nspanel_eu_code/buttonpage01.txt | 7 +---- nspanel_eu_code/buttonpage02.txt | 7 +---- nspanel_eu_code/buttonpage03.txt | 7 +---- nspanel_eu_code/buttonpage04.txt | 7 +---- nspanel_eu_code/climate.txt | 7 +---- nspanel_eu_code/confirm.txt | 7 +---- nspanel_eu_code/cover.txt | 7 +---- nspanel_eu_code/entitypage01.txt | 7 +---- nspanel_eu_code/entitypage02.txt | 7 +---- nspanel_eu_code/entitypage03.txt | 7 +---- nspanel_eu_code/entitypage04.txt | 7 +---- nspanel_eu_code/fan.txt | 7 +---- nspanel_eu_code/home.txt | 7 +---- nspanel_eu_code/keyb_num.txt | 7 +---- nspanel_eu_code/light.txt | 7 +---- nspanel_eu_code/media_player.txt | 7 +---- nspanel_eu_code/notification.txt | 7 +---- nspanel_eu_code/qrcode.txt | 7 +---- nspanel_eu_code/screensaver.txt | 7 +---- nspanel_eu_code/settings.txt | 7 +---- nspanel_eu_code/weather01.txt | 7 +---- nspanel_eu_code/weather02.txt | 7 +---- nspanel_eu_code/weather03.txt | 7 +---- nspanel_eu_code/weather04.txt | 7 +---- nspanel_eu_code/weather05.txt | 7 +---- nspanel_us.HMI | Bin 14731244 -> 14731244 bytes nspanel_us.tft | Bin 7255544 -> 7253468 bytes nspanel_us_code/alarm.txt | 7 +---- nspanel_us_code/boot.txt | 9 ++----- nspanel_us_code/buttonpage01.txt | 7 +---- nspanel_us_code/buttonpage02.txt | 7 +---- nspanel_us_code/buttonpage03.txt | 7 +---- nspanel_us_code/buttonpage04.txt | 7 +---- nspanel_us_code/climate.txt | 7 +---- nspanel_us_code/confirm.txt | 7 +---- nspanel_us_code/cover.txt | 7 +---- nspanel_us_code/entitypage01.txt | 7 +---- nspanel_us_code/entitypage02.txt | 7 +---- nspanel_us_code/entitypage03.txt | 7 +---- nspanel_us_code/entitypage04.txt | 7 +---- nspanel_us_code/fan.txt | 7 +---- nspanel_us_code/home.txt | 7 +---- nspanel_us_code/keyb_num.txt | 7 +---- nspanel_us_code/light.txt | 7 +---- nspanel_us_code/media_player.txt | 7 +---- nspanel_us_code/notification.txt | 7 +---- nspanel_us_code/qrcode.txt | 7 +---- nspanel_us_code/screensaver.txt | 7 +---- nspanel_us_code/settings.txt | 7 +---- nspanel_us_code/weather01.txt | 7 +---- nspanel_us_code/weather02.txt | 7 +---- nspanel_us_code/weather03.txt | 7 +---- nspanel_us_code/weather04.txt | 7 +---- nspanel_us_code/weather05.txt | 7 +---- nspanel_us_land.HMI | Bin 14515341 -> 14636579 bytes nspanel_us_land.tft | Bin 7247932 -> 7245860 bytes nspanel_us_land_code/alarm.txt | 7 +---- nspanel_us_land_code/boot.txt | 9 ++----- nspanel_us_land_code/buttonpage01.txt | 7 +---- nspanel_us_land_code/buttonpage02.txt | 7 +---- nspanel_us_land_code/buttonpage03.txt | 7 +---- nspanel_us_land_code/buttonpage04.txt | 7 +---- nspanel_us_land_code/climate.txt | 7 +---- nspanel_us_land_code/confirm.txt | 7 +---- nspanel_us_land_code/cover.txt | 7 +---- nspanel_us_land_code/entitypage01.txt | 7 +---- nspanel_us_land_code/entitypage02.txt | 7 +---- nspanel_us_land_code/entitypage03.txt | 7 +---- nspanel_us_land_code/entitypage04.txt | 7 +---- nspanel_us_land_code/fan.txt | 7 +---- nspanel_us_land_code/home.txt | 7 +---- nspanel_us_land_code/keyb_num.txt | 7 +---- nspanel_us_land_code/light.txt | 7 +---- nspanel_us_land_code/media_player.txt | 7 +---- nspanel_us_land_code/notification.txt | 7 +---- nspanel_us_land_code/qrcode.txt | 7 +---- nspanel_us_land_code/screensaver.txt | 7 +---- nspanel_us_land_code/settings.txt | 7 +---- nspanel_us_land_code/weather01.txt | 7 +---- nspanel_us_land_code/weather02.txt | 7 +---- nspanel_us_land_code/weather03.txt | 7 +---- nspanel_us_land_code/weather04.txt | 7 +---- nspanel_us_land_code/weather05.txt | 7 +---- 88 files changed, 104 insertions(+), 506 deletions(-) diff --git a/nspanel_esphome_core.yaml b/nspanel_esphome_core.yaml index 1cdbb7d..8a25c18 100644 --- a/nspanel_esphome_core.yaml +++ b/nspanel_esphome_core.yaml @@ -741,11 +741,14 @@ display: platform: nextion uart_id: tf_uart start_up_page: 8 - on_page: # This requires `sendme` to be executed on Nextion side + on_page: lambda: |- static const char *const TAG = "display.disp1.on_page"; ESP_LOGD(TAG, "Nextion page changed"); ESP_LOGD(TAG, "New page: %s (%i)" , id(page_names)[x].c_str(), x); + current_page->publish_state(id(page_names)[x].c_str()); + page_changed->execute(id(page_names)[x].c_str()); + on_setup: - script.execute: boot_sequence @@ -1392,25 +1395,25 @@ text_sensor: ##### Current page name ##### - name: ${device_name} Current page id: current_page - #platform: template - platform: nextion - nextion_id: disp1 - component_name: currentpage + platform: template + #platform: nextion + #nextion_id: disp1 + #component_name: currentpage icon: mdi:tablet-dashboard internal: false disabled_by_default: false - filters: - - lambda: |- - x = x.c_str(); - x.shrink_to_fit(); - return x; - on_value: - then: - - lambda: |- - static const char *const TAG = "text_sensor.current_page"; - // Construct new page - ESP_LOGV(TAG, "Construct new page"); - page_changed->execute(x.c_str()); + #filters: + # - lambda: |- + # x = x.c_str(); + # x.shrink_to_fit(); + # return x; + #on_value: + # then: + # - lambda: |- + # static const char *const TAG = "text_sensor.current_page"; + # // Construct new page + # ESP_LOGV(TAG, "Construct new page"); + # page_changed->execute(x.c_str()); - name: ${device_name} Notification Label platform: template diff --git a/nspanel_eu.HMI b/nspanel_eu.HMI index c6bb6ce3633597eac782fb307f1b02a00f3a31ee..201e11adccfd19d3fa961d451cae009da3e2c960 100644 GIT binary patch delta 15267 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 A&;S4c diff --git a/nspanel_eu.tft b/nspanel_eu.tft index bae77b617fdafdba12364952cbf74780aa48cd20..c2f65ccf6df0747ea9d39e83d97338c7a9c05a13 100644 GIT binary patch delta 21098 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 Qf752#Ie<-5Z!C2G^>UbB6KOlqW~+_ltMgO zLke+FO;iXljSitpA?B;4Qe-VD#C&*>q|sp_u#&Z<5TQ#U_O7kcnc7l_z41CpqeJUD zm>23uAwrizj05OeR|+w%juc|&x+)z5rqLmEDa0=T4gOMyU+PLBZuZBlQcnsIx)frU zdiX_KJt@R4^`sEh`XICVQi#x{5Vrz!YaoTVwZ0T$j|M8G0@LUay3nCbLzON-K0v%1 z2_4!r6gq6yNTuD4gbtw#9hw136~4r8n>0FH)Lg1?7q)5(&hRyQ>43=>a@|^>$1V6s z6&w`0Ebz?$!vnOUbI)6ua#MN5oq8&z2)N~Qx&MASwLUYGYh}fi1NS=1WM)cAQfi zGG&Z@S8fYrnPp_6x-SeDCoXc1{R1TE3$DP|x_2JB{x`76hyGIhgY|Hu|Uwg4w^ZF*A3?5Ur62;)bKN z&#GLxPZM0!Dk1F96wOqJwRH%mHbq4B5PqX!%Yo$z<%DLymWOb9Ghm%UQ7)mptU0hw zp?sn_u>HWi`*KtZVEaQkz6G#|zA8oc;0AT6BT7}`q5x~;>a%4+j%feLJ z9>%p=0b3Tv{aOLT%8U&CxOi(|)Q^ z9timm?#l1l85h|c0$6Sk$FxV#p@E##9zml9sq|nFkLiFQO~*4ob*{>NZsBW*lN}J` zI~Z9S%pn~SAupZ&sCBltZC!@2Y2AItk)w}p;)E$6 zVmF0zP-o+QyTN+Q0FYODF5U$LtUq+^qr@Bo(pZy!?qUqHdul*OjVg^a@{#UF7yE|z z3Nws~DQgM8HLB1Rtr&)=v=#Dx2NJaMyk{c7953J?{&L51cw?2~ngMrF9D*TjHb%f4m1fN0Vf_(&XcjN- z58DS|_H(gnh8&yAzT*)*WB`Ic%;CrZaHuv9OY8aQ5#-B!Za5IZ)#h=(fv^pYQEAEo zm7Er;RC^&S6A)Z~5Q2x|$1;Q95C=o@BCIhMs}!-A^AARF+(Pyn4BKO1AD5^!msQ%q z{6VulUd(Rcuz4=UWm=|E3gpQ$&IpIib17TvVe7SAr6E6KR$8G_=@p!+hpiW$=W8~P zSS+l6!BPeC=@(8gz~&LlR}GjETvu|E5qWf9snVR4+Byi|{|qrY+iQT?{8)XD5yVG6 zg}ZS0EMoz_F~qn-t+0xthoaVeS7|wi}pNJZ@1T zaq(K=IB}nG$nWkrHjINqhpo6HhJ1mv-lkQLmz{>;wTa@Z@XCvzvOKTC3-HYG#>tMa zw_y#u9YY%O`*y9A@T-DIH1~QN`$wX=(+Mh-*@2-8S+hf{Coc`eX+?Ohhw9cHyQ1vKyk?&1n;1E3%8J*db% zyl)b0t#)(zB-joCE1!rPZOEHMUL6J7p*?&e3O4_}U=m2>eJV}Z$1^9x=D(NYC&Sid zze>j;0S7RDAK+nAVC%A<7f*rB^B^i5QZNbUp2Xcvuz4QjNE2*NfOS2Dp#||y=7v*Y zdy>TcrlMLmBy-?2tQqzuOWhp)hgp6L?A-8*_H!e99y8rI*{bVdZ7tW!SWInsnB8We zcn9Sx||ImChi#-pj9-pAjHm*_Kk8{#&wCQ!+ zl4V^fn7sJH9OI%~OHX0-eoAH69fsoSj8np-byHP3kEhj++I%$^(`a2hdk!}4D1fQH zM!wT%1XA@3O8ks=SA);z=Aj_+o#vo<7+VuzFq=mcXGG{r@{sw)IgV2RcE#gu!N zZDVlJrkvplF~-bX2Y{K)aZ5()i)sV0?E>R?$7EnT&Y@?J!1H`#0sNEC^5+FefY$|l zw0aSVgKWRZM;5~7b%7r)gzW~f7HKMthn!C1O^aZ=agon0!pRr7q#egksb%@j_r+pd tP^U{AyTq7cSH@y8n>*QXg#!590CqWfqxK2852vzm2tJ8hw!KsQ{{u66mhJ!m delta 23823 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 H;En$R6=M}V delta 15176 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 JzS-&KUjP}QOUD2J diff --git a/nspanel_us.tft b/nspanel_us.tft index 06863872af24dfc43ce81832151a1d06968d9b6f..060e87939d0a932ae6491e72dddf8e02cb1528ca 100644 GIT binary patch delta 20663 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