Blackymas / NSPanel_HA_Blueprint

This allows you to configure your complete NSPanel via Blueprint with UI and without changing anything in the code
1.37k stars 253 forks source link

Random NSPanel EU rebooting after upgrade to 4.2.6 #1832

Closed electrocamuk closed 6 months ago

electrocamuk commented 6 months ago

TFT Version

4.2.6

ESPHome Version

4.2.6

Blueprint Version

4.2.6

Panel Model

EU

What is the bug?

Two NSPanel EU units reboot randomly

Steps to Reproduce

Running ESPhome 2023.12.9 on HA.

HA Versions: Core 2024.2.2 Supervisor 2024.02.0 Operating System 11.5 Frontend 20240207.1

Noticed spikes in the temperature graphs for both panels, sometimes around 3am where no heating was running or no large temperature changes expected, Diving deeper into things, both panels are now randomly rebooting, something which wasn't present on the previous, and fairly old (6-months?) version that I was running. The panel sitting infront of my computer has just rebooted before my eyes. Uptime duration: 29hours, 34hours, 6hours

Your Panel's YAML

alias: nspanel Configuration
description: ""
use_blueprint:
  path: Blackymas/nspanel_blueprint.yaml
  input:
    nspanel_name: 021af65a72b8f1f114db08583d8ef58e
    delay: 10
    weather_entity: weather.forecast_home_2
    home_custom_button01_icon: ""
    home_custom_button02_icon: ""
    qrcode_label: "Guest Wi-Fi Code: coffee99"
    button_page01_label: Kitchen
    entity01: scene.kitchen_bright
    entity01_name: Kitchen Bright
    entity02: scene.kitchen_dim
    entity02_name: Kitchen Dim
    qrcode_enabled: true
    entitypages_enabled: true
    entity_page01_label: Energy
    entities_entity01: sensor.growatt_sph_battery_state_of_charge
    entities_entity01_name: Battery Level
    entities_entity01_icon: mdi:battery-charging-high
    entities_entity02: sensor.growatt_sph_pv_power
    entities_entity02_name: Solar PV Power
    entities_entity02_icon: mdi:weather-sunny
    entities_entity03: sensor.growatt_sph_grid_power
    entities_entity03_name: Grid Import  -Export
    entities_entity03_icon: mdi:transmission-tower-import
    entities_entity04: sensor.growatt_sph_battery_power
    entities_entity04_name: Batt Charge -Discharge
    entities_entity04_icon: mdi:car-battery
    entities_entity05: sensor.growatt_sph_load_power
    entities_entity05_name: Inverter Load
    entities_entity05_icon: mdi:lightning-bolt
    entities_entity06: input_boolean.isgridcharging
    entities_entity06_name: Is Grid Charging
    entities_entity06_icon: mdi:router
    entities_entity07: >-
      sensor.octopus_energy_electricity_21l4450760_2200018829598_previous_accumulative_cost
    entities_entity07_name: Elec Cost Yesterday
    entities_entity07_icon: mdi:currency-gbp
    entities_entity08: sensor.agileratepence
    entities_entity08_name: Current Agile Rate
    entities_entity08_icon: mdi:currency-gbp
    qrcode_value: coffee99
    right_button_hold_custom_action: []
    right_button_hold_select: Default
    chip01: input_boolean.solardump
    chip01_icon: mdi:radiator-disabled
    chip01_icon_color:
      - 0
      - 255
      - 0
    chip02: switch.solar_heat_dump
    chip02_icon: mdi:radiator
    chip02_icon_color:
      - 255
      - 25
      - 0
    chip03: input_boolean.isgridcharging
    chip03_icon: mdi:transmission-tower-import
    chip03_icon_color:
      - 255
      - 200
      - 0
    date_format: "%A, %d/%m"
    time_format: "%-I:%M %p"
    home_indoor_temp_icon_color:
      - 255
      - 255
      - 255
    home_indoor_temp_icon: ""
    home_value01: sensor.growatt_sph_load_power
    home_value01_icon: mdi:sine-wave
    home_value02_icon: mdi:thermometer-chevron-up
    home_indoor_temp_label_color:
      - 255
      - 255
      - 255
    home_value01_icon_color:
      - 0
      - 255
      - 0
    entity01_icon: mdi:lightbulb-on-90
    entity01_icon_color:
      - 255
      - 234
      - 0
    entity02_icon: mdi:lightbulb-on-40
    entity02_icon_color:
      - 255
      - 247
      - 0
    entity03: scene.kitchen_off
    entity03_name: Kitchen Off
    entity03_icon: mdi:lightbulb-off-outline
    entity03_icon_color:
      - 162
      - 173
      - 0
    entity04_icon: ""
    entity04_icon_color:
      - 250
      - 242
      - 0
    entity05_icon: ""
    entity05_icon_color:
      - 179
      - 164
      - 0
    entity_page02_label: Temperatures
    entities_entity09: sensor.nspanel_temperature
    entities_entity09_name: NSPanel Temperature
    entities_entity09_icon: mdi:thermometer
    entities_entity10: sensor.nsupstairs_temperature
    entities_entity10_name: NSUpstairs Temperature
    entities_entity10_icon: mdi:thermometer
    entities_entity15: sensor.growatt_sph_temperature
    entities_entity15_name: Inverter Temperature
    entities_entity15_icon: mdi:thermometer
    entities_entity16: sensor.growatt_sph_battery_temperature
    entities_entity16_name: Battery Temperature
    entities_entity16_icon: mdi:home-thermometer
    entities_entity14_icon: ""
    button_page02_label: Living
    entity09: scene.livingroom_bright
    entity09_name: Living Bright
    entity09_icon: mdi:lightbulb-alert
    entity10: scene.livingroom_tv
    entity10_name: Living TV
    entity11: scene.livingroom_movie
    entity11_name: Living Movie
    entity12: scene.livingroom_off
    entity12_name: Living Off
    home_value01_label_color:
      - 255
      - 255
      - 0
    home_value03: sensor.growatt_sph_battery_state_of_charge
    home_value03_label_color:
      - 255
      - 255
      - 0
    home_value03_icon: mdi:battery-charging-100
    home_value03_icon_color:
      - 0
      - 255
      - 0
    home_outdoor_temp_font: "4"
    home_custom_button07_icon: ""
    home_custom_button04_icon: ""
    home_custom_button03: button.nspanel_restart
    home_custom_button03_icon: mdi:restart
    home_value02: sensor.nsupstairs_temperature
    home_value02_icon_color:
      - 255
      - 255
      - 255
    home_value02_label_color:
      - 255
      - 255
      - 0
    entity10_icon: mdi:lightbulb-on-10
    entity11_icon: mdi:lightbulb-night

ESPHome Logs

s6-rc: info: service legacy-cont-init successfully started s6-rc: info: service init-nginx: starting s6-rc: info: service esphome: starting s6-rc: info: service esphome successfully started s6-rc: info: service init-nginx successfully started s6-rc: info: service nginx: starting s6-rc: info: service nginx successfully started s6-rc: info: service discovery: starting [13:22:13] INFO: Waiting for ESPHome dashboard to come up... [13:22:13] INFO: Starting ESPHome dashboard... 2024-02-22 13:22:16,430 INFO Starting dashboard web server on unix socket /var/run/esphome.sock and configuration dir /config/esphome... [13:22:16] INFO: Starting NGINX... [13:22:17] INFO: Successfully send discovery information to Home Assistant. s6-rc: info: service discovery successfully started s6-rc: info: service legacy-services: starting s6-rc: info: service legacy-services successfully started 2024-02-22 13:25:07,302 INFO 200 GET /devices (0.0.0.0) 3.06ms 2024-02-22 13:30:08,854 INFO 200 GET /devices (0.0.0.0) 3.41ms 2024-02-22 13:35:08,856 INFO 200 GET /devices (0.0.0.0) 3.96ms 2024-02-22 13:40:08,853 INFO 200 GET /devices (0.0.0.0) 4.57ms 2024-02-22 13:45:08,848 INFO 200 GET /devices (0.0.0.0) 2.62ms 2024-02-22 13:50:08,925 INFO 200 GET /devices (0.0.0.0) 2.60ms 2024-02-22 13:55:08,853 INFO 200 GET /devices (0.0.0.0) 3.54ms 2024-02-22 14:00:08,854 INFO 200 GET /devices (0.0.0.0) 4.91ms 2024-02-22 14:05:09,266 INFO 200 GET /devices (0.0.0.0) 3.41ms 2024-02-22 14:10:08,852 INFO 200 GET /devices (0.0.0.0) 4.42ms 2024-02-22 14:15:08,857 INFO 200 GET /devices (0.0.0.0) 2.31ms 2024-02-22 14:20:08,853 INFO 200 GET /devices (0.0.0.0) 3.32ms 2024-02-22 14:25:08,852 INFO 200 GET /devices (0.0.0.0) 3.93ms 2024-02-22 14:30:08,856 INFO 200 GET /devices (0.0.0.0) 4.86ms 2024-02-22 14:35:08,853 INFO 200 GET /devices (0.0.0.0) 3.97ms 2024-02-22 14:40:09,230 INFO 200 GET /devices (0.0.0.0) 2.34ms 2024-02-22 14:45:08,852 INFO 200 GET /devices (0.0.0.0) 3.14ms 2024-02-22 14:50:08,851 INFO 200 GET /devices (0.0.0.0) 3.15ms 2024-02-22 14:55:08,860 INFO 200 GET /devices (0.0.0.0) 2.65ms 2024-02-22 15:00:08,849 INFO 200 GET /devices (0.0.0.0) 3.00ms 2024-02-22 15:05:08,849 INFO 200 GET /devices (0.0.0.0) 3.53ms 2024-02-22 15:10:09,082 INFO 200 GET /devices (0.0.0.0) 2.31ms 2024-02-22 15:15:09,233 INFO 200 GET /devices (0.0.0.0) 3.33ms 2024-02-22 15:20:08,853 INFO 200 GET /devices (0.0.0.0) 3.85ms 2024-02-22 15:25:08,852 INFO 200 GET /devices (0.0.0.0) 3.16ms 2024-02-22 15:30:08,900 INFO 200 GET /devices (0.0.0.0) 2.14ms 2024-02-22 15:35:08,852 INFO 200 GET /devices (0.0.0.0) 2.74ms 2024-02-22 15:40:08,852 INFO 200 GET /devices (0.0.0.0) 3.17ms 2024-02-22 15:45:08,853 INFO 200 GET /devices (0.0.0.0) 3.82ms 2024-02-22 15:50:08,852 INFO 200 GET /devices (0.0.0.0) 3.39ms 2024-02-22 15:55:08,855 INFO 200 GET /devices (0.0.0.0) 5.17ms 2024-02-22 16:00:08,849 INFO 200 GET /devices (0.0.0.0) 2.65ms 2024-02-22 16:05:08,853 INFO 200 GET /devices (0.0.0.0) 4.14ms 2024-02-22 16:10:08,856 INFO 200 GET /devices (0.0.0.0) 2.78ms 2024-02-22 16:15:08,853 INFO 200 GET /devices (0.0.0.0) 4.23ms 2024-02-22 16:20:08,854 INFO 200 GET /devices (0.0.0.0) 4.72ms 2024-02-22 16:25:08,855 INFO 200 GET /devices (0.0.0.0) 4.72ms 2024-02-22 16:30:08,849 INFO 200 GET /devices (0.0.0.0) 2.72ms 2024-02-22 16:35:08,854 INFO 200 GET /devices (0.0.0.0) 4.39ms 2024-02-22 16:40:08,853 INFO 200 GET /devices (0.0.0.0) 4.21ms 2024-02-22 16:45:08,850 INFO 200 GET /devices (0.0.0.0) 3.36ms 2024-02-22 16:50:08,851 INFO 200 GET /devices (0.0.0.0) 3.02ms 2024-02-22 16:55:08,852 INFO 200 GET /devices (0.0.0.0) 4.42ms 2024-02-22 17:00:08,853 INFO 200 GET /devices (0.0.0.0) 3.81ms 2024-02-22 17:05:08,852 INFO 200 GET /devices (0.0.0.0) 3.69ms 2024-02-22 17:10:09,032 INFO 200 GET /devices (0.0.0.0) 4.03ms 2024-02-22 17:15:08,853 INFO 200 GET /devices (0.0.0.0) 4.26ms 2024-02-22 17:20:08,851 INFO 200 GET /devices (0.0.0.0) 3.83ms 2024-02-22 17:25:09,170 INFO 200 GET /devices (0.0.0.0) 3.58ms 2024-02-22 17:30:09,415 INFO 200 GET /devices (0.0.0.0) 3.70ms 2024-02-22 17:35:08,855 INFO 200 GET /devices (0.0.0.0) 5.67ms 2024-02-22 17:40:08,850 INFO 200 GET /devices (0.0.0.0) 3.27ms 2024-02-22 17:45:08,850 INFO 200 GET /devices (0.0.0.0) 2.75ms 2024-02-22 17:50:08,853 INFO 200 GET /devices (0.0.0.0) 4.31ms 2024-02-22 17:55:08,995 INFO 200 GET /devices (0.0.0.0) 3.74ms 2024-02-22 18:00:09,253 INFO 200 GET /devices (0.0.0.0) 3.87ms 2024-02-22 18:05:08,850 INFO 200 GET /devices (0.0.0.0) 3.03ms 2024-02-22 18:10:08,854 INFO 200 GET /devices (0.0.0.0) 3.62ms 2024-02-22 18:15:08,853 INFO 200 GET /devices (0.0.0.0) 3.84ms 2024-02-22 18:20:08,850 INFO 200 GET /devices (0.0.0.0) 3.08ms 2024-02-22 18:25:08,852 INFO 200 GET /devices (0.0.0.0) 3.80ms 2024-02-22 18:30:08,892 INFO 200 GET /devices (0.0.0.0) 4.75ms 2024-02-22 18:35:09,410 INFO 200 GET /devices (0.0.0.0) 3.59ms 2024-02-22 18:40:08,857 INFO 200 GET /devices (0.0.0.0) 4.56ms 2024-02-22 18:45:09,335 INFO 200 GET /devices (0.0.0.0) 2.25ms 2024-02-22 18:50:08,850 INFO 200 GET /devices (0.0.0.0) 2.66ms 2024-02-22 18:55:08,854 INFO 200 GET /devices (0.0.0.0) 4.57ms 2024-02-22 19:00:08,849 INFO 200 GET /devices (0.0.0.0) 2.81ms 2024-02-22 19:05:08,850 INFO 200 GET /devices (0.0.0.0) 3.18ms 2024-02-22 19:10:09,315 INFO 200 GET /devices (0.0.0.0) 3.69ms 2024-02-22 19:15:08,849 INFO 200 GET /devices (0.0.0.0) 2.76ms 2024-02-22 19:20:08,848 INFO 200 GET /devices (0.0.0.0) 2.40ms 2024-02-22 19:25:08,852 INFO 200 GET /devices (0.0.0.0) 3.18ms 2024-02-22 19:30:08,852 INFO 200 GET /devices (0.0.0.0) 2.99ms 2024-02-22 19:35:08,849 INFO 200 GET /devices (0.0.0.0) 2.90ms 2024-02-22 19:40:09,009 INFO 200 GET /devices (0.0.0.0) 3.08ms 2024-02-22 19:45:09,189 INFO 200 GET /devices (0.0.0.0) 2.59ms 2024-02-22 19:50:08,852 INFO 200 GET /devices (0.0.0.0) 4.42ms 2024-02-22 19:55:08,849 INFO 200 GET /devices (0.0.0.0) 2.87ms 2024-02-22 20:00:08,854 INFO 200 GET /devices (0.0.0.0) 4.58ms 2024-02-22 20:05:08,854 INFO 200 GET /devices (0.0.0.0) 3.89ms 2024-02-22 20:10:08,854 INFO 200 GET /devices (0.0.0.0) 4.44ms 2024-02-22 20:15:08,852 INFO 200 GET /devices (0.0.0.0) 3.44ms 2024-02-22 20:20:08,851 INFO 200 GET /devices (0.0.0.0) 3.24ms

Home Assistant Logs

Logger: homeassistant.components.automation.nspanel_configuration Source: helpers/script.py:485 Integration: Automation (documentation, issues) First occurred: 20:12:58 (5 occurrences) Last logged: 20:12:58

nspanel Configuration: Main choices: Home page - Values: Repeat at step 2: If at step 1: If at step 3: Error executing script. Unexpected error for call_service at pos 2: Authenticated connection not ready yet for nspanel @ 192.168.0.222; current state is ConnectionState.HANDSHAKE_COMPLETE! nspanel Configuration: Main choices: Home page - Values: Repeat at step 2: If at step 1: Error executing script. Unexpected error for if at pos 3: Authenticated connection not ready yet for nspanel @ 192.168.0.222; current state is ConnectionState.HANDSHAKE_COMPLETE! nspanel Configuration: Main choices: Home page - Values: Repeat at step 2: Error executing script. Unexpected error for if at pos 1: Authenticated connection not ready yet for nspanel @ 192.168.0.222; current state is ConnectionState.HANDSHAKE_COMPLETE! nspanel Configuration: Main choices: Home page - Values: Error executing script. Unexpected error for repeat at pos 2: Authenticated connection not ready yet for nspanel @ 192.168.0.222; current state is ConnectionState.HANDSHAKE_COMPLETE! nspanel Configuration: Error executing script. Unexpected error for choose at pos 2: Authenticated connection not ready yet for nspanel @ 192.168.0.222; current state is ConnectionState.HANDSHAKE_COMPLETE! Traceback (most recent call last): File "/usr/src/homeassistant/homeassistant/helpers/script.py", line 485, in _async_step await getattr(self, handler)() File "/usr/src/homeassistant/homeassistant/helpers/script.py", line 723, in _async_call_service_step response_data = await self._async_run_long_action( ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/src/homeassistant/homeassistant/helpers/script.py", line 685, in _async_run_long_action return long_task.result() ^^^^^^^^^^^^^^^^^^ File "/usr/src/homeassistant/homeassistant/core.py", line 2279, in async_call response_data = await coro ^^^^^^^^^^ File "/usr/src/homeassistant/homeassistant/core.py", line 2316, in _execute_service return await target(service_call) ^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/src/homeassistant/homeassistant/components/esphome/manager.py", line 719, in execute_service await entry_data.client.execute_service(service, call.data) File "/usr/local/lib/python3.12/site-packages/aioesphomeapi/client.py", line 1193, in execute_service self._get_connection().send_message(req) ^^^^^^^^^^^^^^^^^^^^^^ File "/usr/local/lib/python3.12/site-packages/aioesphomeapi/client.py", line 372, in _get_connection raise APIConnectionError( aioesphomeapi.core.APIConnectionError: Authenticated connection not ready yet for nspanel @ 192.168.0.222; current state is ConnectionState.HANDSHAKE_COMPLETE!

Logger: homeassistant.components.automation.nspanel_configuration Source: components/automation/init.py:666 Integration: Automation (documentation, issues) First occurred: 20:12:58 (1 occurrences) Last logged: 20:12:58

While executing automation automation.nspanel_configuration Traceback (most recent call last): File "/usr/src/homeassistant/homeassistant/components/automation/init.py", line 666, in async_trigger return await self.action_script.async_run( ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/src/homeassistant/homeassistant/helpers/script.py", line 1600, in async_run return await asyncio.shield(run.async_run()) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/src/homeassistant/homeassistant/helpers/script.py", line 435, in async_run await self._async_step(log_exceptions=False) File "/usr/src/homeassistant/homeassistant/helpers/script.py", line 487, in _async_step self._handle_exception( File "/usr/src/homeassistant/homeassistant/helpers/script.py", line 512, in _handle_exception raise exception File "/usr/src/homeassistant/homeassistant/helpers/script.py", line 485, in _async_step await getattr(self, handler)() File "/usr/src/homeassistant/homeassistant/helpers/script.py", line 943, in _async_choose_step await self._async_run_script(script) File "/usr/src/homeassistant/homeassistant/helpers/script.py", line 1100, in _async_run_script result = await self._async_run_long_action( ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/src/homeassistant/homeassistant/helpers/script.py", line 685, in _async_run_long_action return long_task.result() ^^^^^^^^^^^^^^^^^^ File "/usr/src/homeassistant/homeassistant/helpers/script.py", line 1600, in async_run return await asyncio.shield(run.async_run()) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/src/homeassistant/homeassistant/helpers/script.py", line 435, in async_run await self._async_step(log_exceptions=False) File "/usr/src/homeassistant/homeassistant/helpers/script.py", line 487, in _async_step self._handle_exception( File "/usr/src/homeassistant/homeassistant/helpers/script.py", line 512, in _handle_exception raise exception File "/usr/src/homeassistant/homeassistant/helpers/script.py", line 485, in _async_step await getattr(self, handler)() File "/usr/src/homeassistant/homeassistant/helpers/trace.py", line 283, in async_wrapper await func(*args) File "/usr/src/homeassistant/homeassistant/helpers/script.py", line 891, in _async_repeat_step await async_run_sequence(iteration, extra_msg) File "/usr/src/homeassistant/homeassistant/helpers/script.py", line 842, in async_run_sequence await self._async_run_script(script) File "/usr/src/homeassistant/homeassistant/helpers/script.py", line 1100, in _async_run_script result = await self._async_run_long_action( ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/src/homeassistant/homeassistant/helpers/script.py", line 685, in _async_run_long_action return long_task.result() ^^^^^^^^^^^^^^^^^^ File "/usr/src/homeassistant/homeassistant/helpers/script.py", line 1600, in async_run return await asyncio.shield(run.async_run()) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/src/homeassistant/homeassistant/helpers/script.py", line 435, in async_run await self._async_step(log_exceptions=False) File "/usr/src/homeassistant/homeassistant/helpers/script.py", line 487, in _async_step self._handle_exception( File "/usr/src/homeassistant/homeassistant/helpers/script.py", line 512, in _handle_exception raise exception File "/usr/src/homeassistant/homeassistant/helpers/script.py", line 485, in _async_step await getattr(self, handler)() File "/usr/src/homeassistant/homeassistant/helpers/script.py", line 970, in _async_if_step await self._async_run_script(if_data["if_then"]) File "/usr/src/homeassistant/homeassistant/helpers/script.py", line 1100, in _async_run_script result = await self._async_run_long_action( ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/src/homeassistant/homeassistant/helpers/script.py", line 685, in _async_run_long_action return long_task.result() ^^^^^^^^^^^^^^^^^^ File "/usr/src/homeassistant/homeassistant/helpers/script.py", line 1600, in async_run return await asyncio.shield(run.async_run()) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/src/homeassistant/homeassistant/helpers/script.py", line 435, in async_run await self._async_step(log_exceptions=False) File "/usr/src/homeassistant/homeassistant/helpers/script.py", line 487, in _async_step self._handle_exception( File "/usr/src/homeassistant/homeassistant/helpers/script.py", line 512, in _handle_exception raise exception File "/usr/src/homeassistant/homeassistant/helpers/script.py", line 485, in _async_step await getattr(self, handler)() File "/usr/src/homeassistant/homeassistant/helpers/script.py", line 970, in _async_if_step await self._async_run_script(if_data["if_then"]) File "/usr/src/homeassistant/homeassistant/helpers/script.py", line 1100, in _async_run_script result = await self._async_run_long_action( ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/src/homeassistant/homeassistant/helpers/script.py", line 685, in _async_run_long_action return long_task.result() ^^^^^^^^^^^^^^^^^^ File "/usr/src/homeassistant/homeassistant/helpers/script.py", line 1600, in async_run return await asyncio.shield(run.async_run()) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/src/homeassistant/homeassistant/helpers/script.py", line 435, in async_run await self._async_step(log_exceptions=False) File "/usr/src/homeassistant/homeassistant/helpers/script.py", line 487, in _async_step self._handle_exception( File "/usr/src/homeassistant/homeassistant/helpers/script.py", line 535, in _handle_exception raise exception File "/usr/src/homeassistant/homeassistant/helpers/script.py", line 485, in _async_step await getattr(self, handler)() File "/usr/src/homeassistant/homeassistant/helpers/script.py", line 723, in _async_call_service_step response_data = await self._async_run_long_action( ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/src/homeassistant/homeassistant/helpers/script.py", line 685, in _async_run_long_action return long_task.result() ^^^^^^^^^^^^^^^^^^ File "/usr/src/homeassistant/homeassistant/core.py", line 2279, in async_call response_data = await coro ^^^^^^^^^^ File "/usr/src/homeassistant/homeassistant/core.py", line 2316, in _execute_service return await target(service_call) ^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/src/homeassistant/homeassistant/components/esphome/manager.py", line 719, in execute_service await entry_data.client.execute_service(service, call.data) File "/usr/local/lib/python3.12/site-packages/aioesphomeapi/client.py", line 1193, in execute_service self._get_connection().send_message(req) ^^^^^^^^^^^^^^^^^^^^^^ File "/usr/local/lib/python3.12/site-packages/aioesphomeapi/client.py", line 372, in _get_connection raise APIConnectionError( aioesphomeapi.core.APIConnectionError: Authenticated connection not ready yet for nspanel @ 192.168.0.222; current state is ConnectionState.HANDSHAKE_COMPLETE!

electrocamuk commented 6 months ago

To add, I realise my ESPhome is a little out of date, I've just updated one panel to latest and will test over the weekend, will report back :)

edwardtfn commented 6 months ago

It should work fine with ESPhome 2023.12.9.

Do you mind sharing your panel's yaml (from ESPHome Dashboard)? Please remove any sensitive info when sharing.

electrocamuk commented 6 months ago

Thanks, Just notes on the YAML below, a custom nextion_update_url was used as I had issues flashing and discovered flashing the blank FW before flashing the normal/updated FW yielded a successful TFT flash, I see that the newer implementation has a nice dropdown box to select the TFT firmware, this worked great, I still flashed to blank before flashing the new firmware to be on the safe side and both panels flashed perfectly, great feature! Thanks in advance,

My nspanel YAML--

substitutions:

###### CHANGE ME START ######

  device_name: "nspanel" 
  wifi_ssid: "SSID"
  wifi_password: "WPA2"

  nextion_update_url: "http://192.168.0.1/ha/nspanel_eu.tft" # URL to local tft File
  # nextion_update_url: "http://192.168.0.220:8123/local/nspanel_eu.tft" # URL to local tft File
  #  nextion_update_url: "https://raw.githubusercontent.com/Blackymas/NSPanel_HA_Blueprint/main/nspanel_us.tft" # URL to Github

##### CHANGE ME END #####

##### DO NOT CHANGE ANYTHING! #####
##### Original URL https://github.com/Blackymas/NSPanel_HA_Blueprint #####

packages:
  ##### download esphome code from Github
  remote_package:
    url: https://github.com/Blackymas/NSPanel_HA_Blueprint
    ref: main
    files: [nspanel_esphome.yaml]
    refresh: 300s

##### DO NOT CHANGE ANYTHING! #####
electrocamuk commented 6 months ago

The Nspanel I did upgrade the ESPhome version on last night, did crash/reboot this morning at 03:02am, thanks in advance :)

bkbartk commented 6 months ago

I noticed the same behaviar since i upgraded from v 4.2.4 to v4.2.6 the last one is on esphome Firmware: 2023.12.9 (Feb 19 2024, 18:51:29)

I don't really have logs as esphome doen not maintain logs after reboot.

what I noticed was some automations kicking in after a switch state change and a short blue boot screen.

Floppe commented 6 months ago

I noticed this with 4.2.5 also.

On Fri, 23 Feb 2024, 21.05 bkbartk, @.***> wrote:

I noticed the same behaviar since i upgraded from v 4.2.4 to v4.2.6 the last one is on esphome Firmware: 2023.12.9 (Feb 19 2024, 18:51:29)

I don't really have logs as esphome doen not maintain logs after reboot.

what I noticed was some automations kicking in after a switch state change and a short blue boot screen.

— Reply to this email directly, view it on GitHub https://github.com/Blackymas/NSPanel_HA_Blueprint/issues/1832#issuecomment-1961846386, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAB2JCNTWTHAXCW7M7IBHLLYVDR67AVCNFSM6AAAAABDVT2Q4WVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMYTSNRRHA2DMMZYGY . You are receiving this because you are subscribed to this thread.Message ID: @.***>

edwardtfn commented 6 months ago

The watchdog (in the logs) shows Free heap. How much is it showing? I know it changes all the time, but I just wanna have an idea.

edwardtfn commented 6 months ago

Could you please add this to your panel's yaml (in the customization area, or at the end of the file) so we can try to capture the reason for the restart?

debug:

text_sensor:
  - platform: debug
    device:
      name: "Device Info"
    reset_reason:
      name: "Reset Reason"

sensor:
  - platform: debug
    free:
      name: "Heap Free"
    block:
      name: "Heap Max Block"
    loop_time:
      name: "Loop Time"
electrocamuk commented 6 months ago

Thanks Edward I've added the debug code and I have the debug sensors show up now, I'll leave the panel to crash then report back the sensor status post crash - unless you want them immediately in which case I've attached! Screenshot 2024-02-24 001209 Thanks, -Dave

bkbartk commented 6 months ago

IMG_5500 Screenshot from my phone. The rest of the info I have to gather later

edwardtfn commented 6 months ago

60kb is half of what I was expecting. It probably can't sustain a TFT upload... Are you running any BLE?

bkbartk commented 6 months ago

60kb is half of what I was expecting. It probably can't sustain a TFT upload... Are you running any BLE?

Not BLE. But another external component for Udp syncing. I also have the tft add on still installed. That one I ofc can remove after uploading.

Edit: it’s true that I need to disable this component during the tft upload. Else it always fails.

electrocamuk commented 6 months ago

Screenshot 2024-02-24 093551 Memory leak?

edwardtfn commented 6 months ago

Looks like. 😩

Anyone else seeing a similar trend? Unfortunately I have all my panel off this weekend, so I cannot duplicate it.

bkbartk commented 6 months ago

image I don't think 40 minutes of measuring including one intentional reboot would be enough. but this morning the heap was 60KB,

I'm wondering if everyone is on arduino framework here

edwardtfn commented 6 months ago

I have one of my panels with Arduino, but cannot start the tests until Monday. 😩

bkbartk commented 6 months ago

after 3 hours of testing I think I can say the trend is the same image

edwardtfn commented 6 months ago

@bkbartk, do you mind sharing the yaml of this panel? Please remember to hide any private info before sharing.

bkbartk commented 6 months ago

of course

    # - priority: 800.0  # This is where all hardware initialization of vital components is executed. For example setting switches to their initial state.
    #   then:
    #     - wait_until:
    #         condition:
    #           - wifi.connected
    #         timeout: 60s
    #     - lambda: id(started) = 'true'; 

substitutions:
  device_name: "switch-kamer-achter" 
  nextion_update_url: "http://192.168.180.149/nspanel_eu.tft"

packages:
  base: !include .base.yaml
  device: !include .nspanel.yaml
  tftupload: !include .nspanel_esphome_addon_upload_tft.yaml

external_components:
    - source: github://Cossid/tasmotadevicegroupsforesphome@main
      components: [ device_groups ]
      refresh: 10 min

device_groups:
  - group_name: "bulbkamervoor"     # Tasmota device group name
    send_mask: 1
    receive_mask: 1
    switches:
      - relay_1
  - group_name: "bulbkamerachter"     # Tasmota device group name
    send_mask: 1
    receive_mask: 1
    switches:
      - relay_2

globals:
  - id: started
    type: bool
    initial_value: 'false'
    restore_value: no

switch:
  ##### PHYSICAL SWITCH 1 #####
  - name: ${device_name} Relay 1
    platform: template
    id: relay_1
    restore_mode: RESTORE_DEFAULT_OFF
    optimistic: true
    on_turn_on:
      then:
         - lambda: |-
            id(detailed_entity).publish_state("light.bulb_kamer_voor_bulb_kamer_voor");
            id(disp1).goto_page("light");
            id(disp1).set_component_text_printf("back_page", "home");
            id(disp1).set_component_text_printf("page_label", "Kamer voor");
            id(disp1).set_component_text_printf("icon_state", "\uF10F"); // mdi:lightbulb - https://htmlpreview.github.io/?https://github.com/jobr99/Generate-HASP-Fonts/blob/master/cheatsheet.html
    on_turn_off:
      then:
        - script.execute:
            id: refresh_relays
  ##### PHYSICAL SWITCH 2 ######
  - name: ${device_name} Relay 2
    platform: template
    id: relay_2
    optimistic: true
    restore_mode: RESTORE_DEFAULT_OFF
    on_turn_on:
      then:
        - if:
            condition:
              switch.is_off: Real_Right
            then:
              - switch.turn_on: Real_Right
        - lambda: |-
            id(detailed_entity).publish_state("light.bulb_kamer_achter_bulb_kamer_achter");
            id(disp1).goto_page("light");
            id(disp1).set_component_text_printf("back_page", "home");
            id(disp1).set_component_text_printf("page_label", "Kamer achter");
            id(disp1).set_component_text_printf("icon_state", "\uF060"); // mdi:lightbulb - https://htmlpreview.github.io/?https://github.com/jobr99/Generate-HASP-Fonts/blob/master/cheatsheet.html
    on_turn_off:
      then:
        - if:
            condition:
              or:
                - and:
                  - lambda: "return id(started);"
                  - not:
                      - wifi.connected
                - and:
                  - lambda: 'return !id(started);'
                  - lambda: 'return id(reset_reason).state == "Power On Reset";'
            then:
              - switch.turn_off: Real_Right
        - script.execute:
            id: refresh_relays
  ##### Real Buttons######  
  - name: ${device_name} Real Left
    id: Real_Left
    platform: gpio
    pin:
      number: 22
    restore_mode: ALWAYS_OFF
  - name: ${device_name} Real Right
    id: Real_Right
    platform: gpio
    pin:
      number: 19
    restore_mode: RESTORE_DEFAULT_ON

debug:

text_sensor:
  - platform: debug
    device:
      name: "Device Info"
    reset_reason:
      id: reset_reason
      name: "Reset Reason"

sensor:
  - platform: debug
    free:
      name: "Heap Free"
    block:
      name: "Heap Max Block"
    loop_time:
      name: "Loop Time"

then .base.yaml

wifi:
  id: wifiId
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  # power_save_mode: NONE
  power_save_mode: HIGH
  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "${device_name}"
    password: !secret wifi_password

captive_portal:

# Enable logging
logger:

api:
  encryption:
    key: !secret api_encryption_key
  reboot_timeout: 0s

ota:
  safe_mode: true
  reboot_timeout: 3min
  num_attempts: 3
  password: !secret ota_password

web_server:
  port: 80
  local: true
  auth:
    username: admin
    password: !secret web_server_password

button:
  - platform: restart
    name: "${device_name} switch Restart"
    icon: "mdi:restart"
  - platform: safe_mode
    name: "${device_name} Restart (Safe Mode)"
    icon: "mdi:restart-alert"

.nspanel.yaml (there are a few changes to not conflict with base or the original yaml, but nothing important.

#####################################################################################################
##### NSPANEL ESPHOME created by Blackymas - https://github.com/Blackymas/NSPanel_HA_Blueprint  #####
##### ESPHOME CORE                                                                              #####
##### PLEASE only make changes if it is necessary and also the required knowledge is available. #####
##### For normal use with the Blueprint, no changes are necessary.                              #####
#####################################################################################################
---
substitutions:
  ##############################
  ## Change only in your      ##
  ## local yaml substitutions ##
  temp_units: "°C"
  invalid_cooldown: "100ms"
  ota_password: ${wifi_password}
  ap_password: ${wifi_password}
  device_name: NSPanel
  name: ${device_name}
  friendly_name: ${device_name}
  wifi_timeout: '15'
  ##### DON'T CHANGE THIS ######
  version: "4.2.6"
  ##############################

##### External components #####
external_components:
  - source:
      type: git
      url: https://github.com/edwardtfn/esphome
      ref: nextion-v425
    components:
      - nextion  # Change this when that PR#6192 gets released (2024.3?)
    refresh: 300s

##### ESPHOME CONFIGURATION #####
esphome:
  name: ${name}
  friendly_name: ${friendly_name}
  min_version: 2023.12.0
  platformio_options:
    build_flags:
      - -Wno-missing-field-initializers
  on_boot:
    - priority: 600.0  # This is where most sensors are set up.
      then:
        - script.execute: restore_settings
        - wait_until:
            condition:
              - lambda: return disp1->is_setup();
            timeout: 60s
        - if:
            condition:
              - lambda: return (not disp1->is_detected());
            then:
              - switch.turn_off: screen_power
              - delay: 2s
              - switch.turn_on: screen_power
              - delay: 5s
    - priority: 800.0  # This is where all hardware initialization of vital components is executed. For example setting switches to their initial state.
      then:
        - wait_until:
            condition:
              - wifi.connected
            timeout: 60s
        - lambda: id(started) = "true"; 
  on_shutdown:
    - priority: 0
      then:
        - lambda: |-
            // Make it unavailable to blueprint calls
            nextion_init->publish_state(false);
            // Update Wi-Fi icon
            disp1->set_component_text_printf("home.wifi_icon", "\uE708");
            // Update Wi-Fi icon color
            disp1->set_component_font_color("home.wifi_icon", 63488);
    - priority: 600.0
      then:
        - switch.turn_off: screen_power

##### TYPE OF ESP BOARD #####
esp32:
  board: esp32dev

##### WIFI SETUP #####
wifi:
  id: wifi_component
  on_connect:
    then:
      - script.execute: watchdog
  on_disconnect:
    then:
      - script.execute: watchdog

##### JSON - Used to parse json and for Upload TFT #####
json:

##### LOGGER #####
logger:
  id: logger_std
  baud_rate: 0

##### ENABLE RINGTONE MUSIC SUPPORT #####
rtttl:
  id: buzzer
  output: buzzer_out

##### CONFIGURE INTERNAL BUZZER #####
output:
  ##### BUZZER FOR PLAYING RINGTONES #####
  - id: buzzer_out
    platform: ledc
    pin:
      number: 21

##### UART FOR NEXTION DISPLAY #####
uart:
  - id: tf_uart
    tx_pin: 16
    rx_pin: 17
    baud_rate: 115200

##### Keeps time display updated #####
time:
  - id: time_provider
    platform: homeassistant
    on_time:
      - seconds: 0
        then:
          - script.execute: refresh_datetime
      - seconds: 30
        then:
          - script.execute: watchdog

    on_time_sync:
      then:
        - logger.log: "System clock synchronized"
        - script.execute: refresh_datetime

##### START - API CONFIGURATION #####
api:
  id: api_server
  reboot_timeout: 0s
  on_client_connected:
    - script.execute: watchdog
  on_client_disconnected:
    - script.execute: watchdog

  services:
    ##### Service to send a command "printf" directly to the display #####
    - service: send_command_printf
      variables:
        cmd: string
      then:
        - lambda: |-
            if (!id(is_uploading_tft))
              disp1->send_command_printf("%s", cmd.c_str());

    ##### Service to send a command "text_printf" directly to the display #####
    - service: send_command_text_printf
      variables:
        component: string
        message: string
      then:
        - lambda: |-
            if (!id(is_uploading_tft))
              disp1->set_component_text_printf(component.c_str(), "%s", message.c_str());

    ##### Service to send a command "component_value (Dualstate Button)" directly to the display #####
    - service: send_command_value
      variables:
        component: string
        val: int
      then:
        - lambda: |-
            if (!id(is_uploading_tft))
              disp1->set_component_value(component.c_str(), val);

    ##### Service to send a command "hide componente" directly to the display #####
    - service: send_command_hide
      variables:
        component: string
      then:
        - lambda: |-
            if (!id(is_uploading_tft))
              disp1->hide_component(component.c_str());

    ##### Service to send a command "show componente" directly to the display #####
    - service: send_command_show
      variables:
        component: string
      then:
        - lambda: |-
            if (!id(is_uploading_tft))
              disp1->show_component(component.c_str());

    ##### Service to send a command "font color" directly to the display #####
    - service: set_component_color
      variables:
        component: string
        foreground: int[]
      then:
        - lambda: |-
            if (!id(is_uploading_tft))
              set_component_color->execute(component, foreground);

    ##### Service to play a rtttl tones #####
    # Example tones : https://codebender.cc/sketch:109888#RTTTL%20Songs.ino
    - service: play_rtttl
      variables:
        song_str: string
      then:
        - rtttl.play:
            rtttl: !lambda 'return song_str;'

    #### Service to populate the alarm settings page #####
    - service: alarm_settings
      variables:
        page_title: string
        state: string
        supported_features: int
        code_format: string
        code_arm_required: bool
        entity: string
        mui_alarm: string[]
      then:
        - lambda: |-
            // Is page Alarm visible?
            if (current_page->state == "alarm" and not id(is_uploading_tft))  // To do: This page constructor should be moved to Blueprint
              { // Update alarm page
                  detailed_entity->publish_state(entity);

                  // Alarm page - Header
                  update_alarm_icon->execute("icon_state", state.c_str());
                  if (page_title.find("\\r") != std::string::npos) {
                    page_title = page_title.replace(page_title.find("\\r"), 2, " ");
                  }
                  disp1->set_component_text_printf("page_label", "%s", page_title.c_str());
                  disp1->set_component_text_printf("code_format", "%s", code_format.c_str());
                  if (code_arm_required) disp1->set_component_text_printf("code_arm_req", "1");
                  else disp1->set_component_text_printf("code_arm_req", "0");

                  // Alarm page - Button's text
                  display_wrapped_text->execute("bt_home_text", mui_alarm[0].c_str(), 10);
                  display_wrapped_text->execute("bt_away_text", mui_alarm[1].c_str(), 10);
                  display_wrapped_text->execute("bt_night_text", mui_alarm[2].c_str(), 10);
                  display_wrapped_text->execute("bt_vacat_text", mui_alarm[3].c_str(), 10);
                  display_wrapped_text->execute("bt_bypass_text", mui_alarm[4].c_str(), 10);
                  display_wrapped_text->execute("bt_disarm_text", mui_alarm[5].c_str(), 10);

                  // Alarm page - Buttons
                  if (supported_features & 1 or state == "armed_home") // Alarm - Button - Home
                    {
                      disp1->send_command_printf("bt_home_pic.pic=%i", (state == "armed_home") ? 43 : 42);
                      disp1->set_component_background_color("bt_home_text", (state == "armed_home") ? 19818 : 52857);
                      disp1->set_component_background_color("bt_home_icon", (state == "armed_home") ? 19818 : 52857);
                      disp1->set_component_font_color("bt_home_text", (state == "armed_home") ? 65535 : 0);
                      disp1->set_component_font_color("bt_home_icon", (state == "armed_home") ? 65535 : 0);
                      if (state == "armed_home") disp1->hide_component("bt_home"); else disp1->show_component("bt_home");
                    }
                  if (supported_features & 2 or state == "armed_away") // Alarm - Button - Away
                    {
                      disp1->send_command_printf("bt_away_pic.pic=%i", (state == "armed_away") ? 43 : 42);
                      disp1->set_component_background_color("bt_away_text", (state == "armed_away") ? 19818 : 52857);
                      disp1->set_component_background_color("bt_away_icon", (state == "armed_away") ? 19818 : 52857);
                      disp1->set_component_font_color("bt_away_text", (state == "armed_away") ? 65535 : 0);
                      disp1->set_component_font_color("bt_away_icon", (state == "armed_away") ? 65535 : 0);
                      if (state == "armed_away") disp1->hide_component("bt_away"); else disp1->show_component("bt_away");
                    }
                  if (supported_features & 4 or state == "armed_night") // Alarm - Button - Night
                    {
                      disp1->send_command_printf("bt_night_pic.pic=%i", (state == "armed_night") ? 43 : 42);
                      disp1->set_component_background_color("bt_night_text", (state == "armed_night") ? 19818 : 52857);
                      disp1->set_component_background_color("bt_night_icon", (state == "armed_night") ? 19818 : 52857);
                      disp1->set_component_font_color("bt_night_text", (state == "armed_night") ? 65535 : 0);
                      disp1->set_component_font_color("bt_night_icon", (state == "armed_night") ? 65535 : 0);
                      if (state == "armed_night") disp1->hide_component("bt_night"); else disp1->show_component("bt_night");
                    }
                  if (supported_features & 32 or state == "armed_vacation") // Alarm - Button - Vacation
                    {
                      disp1->send_command_printf("bt_vacat_pic.pic=%i", (state == "armed_vacation") ? 43 : 42);
                      disp1->set_component_background_color("bt_vacat_text", (state == "armed_vacation") ? 19818 : 52857);
                      disp1->set_component_background_color("bt_vacat_icon", (state == "armed_vacation") ? 19818 : 52857);
                      disp1->set_component_font_color("bt_vacat_text", (state == "armed_vacation") ? 65535 : 0);
                      disp1->set_component_font_color("bt_vacat_icon", (state == "armed_vacation") ? 65535 : 0);
                      if (state == "armed_vacation") disp1->hide_component("bt_vacat"); else disp1->show_component("bt_vacat");
                    }
                  if (supported_features & 16 or state == "armed_bypass") // Alarm - Button - Custom bypass
                    {
                      disp1->send_command_printf("bt_bypass_pic.pic=%i", (state == "armed_bypass") ? 43 : 42);
                      disp1->set_component_background_color("bt_bypass_text", (state == "armed_bypass") ? 19818 : 52857);
                      disp1->set_component_background_color("bt_bypass_icon", (state == "armed_bypass") ? 19818 : 52857);
                      disp1->set_component_font_color("bt_bypass_text", (state == "armed_bypass") ? 65535 : 0);
                      disp1->set_component_font_color("bt_bypass_icon", (state == "armed_bypass") ? 65535 : 0);
                      if (state == "armed_bypass") disp1->hide_component("bt_bypass"); else disp1->show_component("bt_bypass");
                    }
                  if ( true ) // Alarm - Button - Disarm
                    {
                      disp1->send_command_printf("bt_disarm_pic.pic=%i", (state == "disarmed") ? 43 : 42);
                      disp1->set_component_background_color("bt_disarm_text", (state == "disarmed") ? 19818 : 52857);
                      disp1->set_component_background_color("bt_disarm_icon", (state == "disarmed") ? 19818 : 52857);
                      disp1->set_component_font_color("bt_disarm_text", (state == "disarmed") ? 65535 : 0);
                      disp1->set_component_font_color("bt_disarm_icon", (state == "disarmed") ? 65535 : 0);
                      if (state == "disarmed") disp1->hide_component("bt_disarm"); else disp1->show_component("bt_disarm");
                    }
              }

    ##### Service for transferring relay's settings from the blueprint to ESPHome #####
    - service: relay_settings
      variables:
        relay1_local_control: bool
        relay1_icon: string
        relay1_icon_color: int
        relay1_fallback: bool
        relay2_local_control: bool
        relay2_icon: string
        relay2_icon_color: int
        relay2_fallback: bool
      then:
        - script.execute:
            id: relay_settings
            relay1_local_control: !lambda "return relay1_local_control;"
            relay1_icon: !lambda "return relay1_icon;"
            relay1_icon_color: !lambda "return relay1_icon_color;"
            relay1_fallback: !lambda "return relay1_fallback;"
            relay2_local_control: !lambda "return relay2_local_control;"
            relay2_icon: !lambda "return relay2_icon;"
            relay2_icon_color: !lambda "return relay2_icon_color;"
            relay2_fallback: !lambda "return relay2_fallback;"
        - script.wait: relay_settings
        - lambda: |-
            blueprint_status->publish_state(int(blueprint_status->raw_state) | (1 << 4));

    ##### Service for transferring global settings from the blueprint to ESPHome #####
    - service: global_settings
      variables:
        blueprint_version: string
        embedded_climate: bool
        embedded_climate_friendly_name: string
        embedded_indoor_temperature: bool
        temperature_unit_is_fahrenheit: bool  # Deprecated
        mui_please_confirm: string
        mui_unavailable: string
        screensaver_time: bool
        screensaver_time_color: int[]
      then:
        - script.execute:
            id: global_settings
            blueprint_version: !lambda "return blueprint_version;"
            embedded_climate: !lambda "return embedded_climate;"
            embedded_climate_friendly_name: !lambda "return embedded_climate_friendly_name;"
            embedded_indoor_temperature: !lambda "return embedded_indoor_temperature;"
            # temperature_unit_is_fahrenheit: !lambda "return temperature_unit_is_fahrenheit;"  # Deprecated
            mui_please_confirm: !lambda "return mui_please_confirm;"
            mui_unavailable: !lambda "return mui_unavailable;"
            screensaver_time: !lambda "return screensaver_time;"
            screensaver_time_color: !lambda "return screensaver_time_color;"
        - script.wait: global_settings
        - lambda: |-
            blueprint_status->publish_state(int(blueprint_status->raw_state) | (1 << 5));

    ##### Service to show a notification-message on the screen #####
    - service: notification_show
      variables:
        label: string
        message: string
      then:
        - lambda: |-
            if (!id(is_uploading_tft)) {
              ESP_LOGV("service.notification_show", "Starting");

              disp1->goto_page("notification");
              disp1->set_component_text_printf("notification.notifi_label", "%s", label.c_str());

              display_wrapped_text->execute("notification.notifi_text01", message.c_str(), display_mode->state == 2 ? 23 : 32);

              notification_label->publish_state(label.c_str());
              notification_text->publish_state(message.c_str());
              timer_reset_all->execute(current_page->state.c_str());
              refresh_notification->execute();
              notification_unread->turn_on();
              if (notification_sound->state) buzzer->play("two short:d=4,o=5,b=100:16e6,16e6");
            }

    ##### Service to clear the notification #####
    - service: notification_clear
      then:
        - script.execute: notification_clear

    ##### Service to open information for settings-page(s)
    - service: open_entity_settings_page
      variables:
        page: string
        page_label: string
        page_icon: string
        page_icon_color: int[]
        entity: string
        back_page: string
      then:
        - script.execute:
            id: open_entity_settings_page
            page: !lambda "return page;"
            page_label: !lambda "return page_label;"
            page_icon: !lambda "return page_icon;"
            page_icon_color: !lambda "return page_icon_color;"
            entity: !lambda "return entity;"
            back_page: !lambda "return back_page;"

    # Service to show a QR code on the display (ex. for WiFi password)
    - service: qrcode
      variables:
        title: string
        qrcode: string
        show: bool
      then:
        - lambda: |-
            if (!id(is_uploading_tft)) {
              disp1->set_component_text_printf("qrcode.qrcode_label", "%s", title.c_str());
              disp1->set_component_text_printf("qrcode.qrcode_value", "%s", qrcode.c_str());
              if (show) disp1->goto_page("qrcode");
              blueprint_status->publish_state(int(blueprint_status->raw_state) | (1 << 2));
            }

    #### Service to set climate state ####
    - service: set_climate
      variables:
        current_temp: float
        supported_features: int
        target_temp: float
        target_temp_high: float
        target_temp_low: float
        temp_step: int
        total_steps: int
        temp_offset: int
        climate_icon: string
        embedded_climate: bool
        entity: string
      then:
        - if:
            condition:
              lambda: 'return not id(is_uploading_tft);'
            then:
              - lambda: |-
                  if (current_page->state == "climate") detailed_entity->publish_state(entity);
              - script.execute:
                  id: set_climate
                  current_temp: !lambda "return current_temp;"
                  supported_features: !lambda "return supported_features;"
                  target_temp: !lambda "return target_temp;"
                  target_temp_high: !lambda "return target_temp_high;"
                  target_temp_low: !lambda "return target_temp_low;"
                  temp_step: !lambda "return temp_step;"
                  total_steps: !lambda "return total_steps;"
                  temp_offset: !lambda "return temp_offset;"
                  climate_icon: !lambda "return climate_icon;"
                  embedded_climate: !lambda "return embedded_climate;"

    #### Service to set the buttons ####
    - service: set_button
      variables:
        page: string
        id: string
        state: bool
        icon: string
        icon_color: int[]
        icon_font: int
        bri: string
        label: string
      then:
        - lambda: |-
            if (page == current_page->state and not id(is_uploading_tft)) {
              std::string btnicon = id.c_str() + std::string("icon");
              std::string btntext = id.c_str() + std::string("text");
              std::string btnbri = id.c_str() + std::string("bri");
              std::string btnpic = id.c_str() + std::string("pic");
              uint8_t bg_pic = state ? 47 : 46;
              uint16_t txt_color = state ? 10597 : 65535;
              disp1->send_command_printf("%spic.picc=%u", id.c_str(), bg_pic);
              disp1->send_command_printf("%sbri.picc=%u", id.c_str(), bg_pic);
              disp1->send_command_printf("%stext.picc=%u", id.c_str(), bg_pic);
              disp1->send_command_printf("%sicon.picc=%u", id.c_str(), bg_pic);
              disp1->send_command_printf("%sicon.font=%" PRIu32, id.c_str(), icon_font);
              disp1->set_component_foreground_color(btnbri.c_str(), txt_color);
              disp1->set_component_foreground_color(btntext.c_str(), txt_color);
              set_component_color->execute(btnicon.c_str(), icon_color);
              disp1->set_component_text_printf(btnicon.c_str(), "%s", icon.c_str());
              display_wrapped_text->execute(btntext.c_str(), label.c_str(), 10);
              if (strcmp(bri.c_str(), "0") != 0)
                disp1->set_component_text_printf(btnbri.c_str(), "%s", bri.c_str());
              else
                disp1->set_component_text_printf(btnbri.c_str(), " ");
              disp1->show_component(btnpic.c_str());
              disp1->show_component(btnicon.c_str());
              disp1->show_component(btntext.c_str());
              disp1->show_component(btnbri.c_str());
              disp1->show_component(id.c_str());
            } else {
              ESP_LOGW("service.set_button", "Skipping button `%s.%s` update.", page.c_str(), id.c_str());
            }

    ##### SERVICE TO WAKE UP THE DISPLAY #####
    - service: wake_up
      variables:
        reset_timer: bool
      then:
        - lambda: |-
            if (not id(is_uploading_tft)) {
              if (current_page->state == "screensaver") disp1->goto_page(wakeup_page_name->state.c_str());
              if (reset_timer)
                timer_reset_all->execute(wakeup_page_name->state.c_str());
              else {
                timer_sleep->execute(wakeup_page_name->state.c_str(), int(timeout_sleep->state));
                timer_dim->execute(wakeup_page_name->state.c_str(), int(timeout_dim->state));
              }
            }

    #### Service to set the entities ####
    - service: set_entity
      variables:
        ent_id: string
        ent_icon: string
        ent_label: string
        ent_value: string
        ent_value_xcen: string
      then:
        - lambda: |-
            if (not id(is_uploading_tft)) {
              std::string enticon = ent_id.c_str() + std::string("_pic");
              std::string entlabel = ent_id.c_str() + std::string("_label");
              std::string entxcen = ent_id.c_str() + std::string(".xcen=") + ent_value_xcen.c_str();
              disp1->set_component_text_printf(enticon.c_str(), "%s", ent_icon.c_str());
              if (strcmp(ent_icon.c_str(), "0") != 0) disp1->set_component_text_printf(enticon.c_str(), "%s", ent_icon.c_str());
              disp1->set_component_text_printf(entlabel.c_str(), "%s", ent_label.c_str());
              disp1->set_component_text_printf(ent_id.c_str(), "%s", ent_value.c_str());
              if (strcmp(ent_value_xcen.c_str(), "0") != 0) disp1->send_command_printf("%s", entxcen.c_str());
            }

    #### Service to populate the page Home #####
    - service: page_home
      variables:
        date_color: int
        time_format: string
        time_color: int
        meridiem: string[]
        chip_font_size: int
        custom_buttons_font_size: int
        notification_icon: string
        notification_icon_color_normal: int[]
        notification_icon_color_unread: int[]
        qrcode: bool
        qrcode_icon: string
        qrcode_icon_color: int[]
        entities_pages: bool
        entities_pages_icon: string
        entities_pages_icon_color: int[]
      then:
        - lambda: |-
            if (not id(is_uploading_tft)) {
              static const char *const TAG = "service.page_home";
              ESP_LOGV(TAG, "date_color:                      %" PRIi32, date_color);
              ESP_LOGV(TAG, "time_format:                     %s", time_format.c_str());
              ESP_LOGV(TAG, "time_color:                      %" PRIi32, time_color);
              ESP_LOGV(TAG, "meridiem:                        %i", meridiem.size());
              ESP_LOGV(TAG, "chip_font_size:                  %" PRIi32, chip_font_size);
              ESP_LOGV(TAG, "custom_buttons_font_size:        %" PRIi32, custom_buttons_font_size);
              ESP_LOGV(TAG, "notification_icon:               %s", notification_icon.c_str());
              ESP_LOGV(TAG, "notification_icon_color_normal:  %i", notification_icon_color_normal.size());
              ESP_LOGV(TAG, "notification_icon_color_unread:  %i", notification_icon_color_unread.size());
              ESP_LOGV(TAG, "qrcode:                          %s", YESNO(qrcode));
              ESP_LOGV(TAG, "qrcode_icon:                     %s", qrcode_icon.c_str());
              ESP_LOGV(TAG, "qrcode_icon_color:               %i", qrcode_icon_color.size());
              ESP_LOGV(TAG, "entities_pages:                  %s", YESNO(entities_pages));
              ESP_LOGV(TAG, "entities_pages_icon:             %s", entities_pages_icon.c_str());
              ESP_LOGV(TAG, "entities_pages_icon_color:       %i", entities_pages_icon_color.size());

              // Localization
              ESP_LOGV(TAG, "Load localization");
              id(mui_time_format) = time_format;
              id(mui_meridiem) = meridiem;

              // Date/Time colors
              ESP_LOGV(TAG, "Load date/time colors");
              disp1->set_component_font_color("home.date", date_color);
              disp1->set_component_font_color("home.time", time_color);
              id(home_date_color) = date_color;
              id(home_time_color) = time_color;

              // Chips icon size
              ESP_LOGV(TAG, "Chips size");
              for (int i = 1; i <= 10; ++i) {
                disp1->send_command_printf("home.icon_top_%02d.font=%" PRIu32, i, chip_font_size);
              }
              disp1->send_command_printf("home.wifi_icon.font=%" PRIu32, chip_font_size);
              id(home_chip_font_size) = chip_font_size;

              // Custom buttons icon size
              ESP_LOGV(TAG, "Custom buttons sizes");
              id(home_custom_buttons_font_size) = custom_buttons_font_size;
              for (int i = 1; i <= 7; ++i) {
                disp1->send_command_printf("home.button%02d.font=%i", i, id(home_custom_buttons_font_size));
              }
              disp1->send_command_printf("home.bt_notific.font=%i", id(home_custom_buttons_font_size));
              disp1->send_command_printf("home.bt_qrcode.font=%i", id(home_custom_buttons_font_size));
              disp1->send_command_printf("home.bt_entities.font=%i", id(home_custom_buttons_font_size));

              // Notification button
              ESP_LOGV(TAG, "Set Notification button");
              disp1->send_command_printf("is_notification=%i", (notification_text->state.empty() and notification_label->state.empty()) ? 0 : 1);
              disp1->set_component_text_printf("home.bt_notific", "%s", notification_icon.c_str());
              set_component_color->execute("home.bt_notific", notification_unread->state ? notification_icon_color_unread : notification_icon_color_normal);
              id(home_notify_icon_color_normal) = notification_icon_color_normal;
              id(home_notify_icon_color_unread) = notification_icon_color_unread;

              // QRCode button
              ESP_LOGV(TAG, "Set QRCode button");
              disp1->send_command_printf("is_qrcode=%i", qrcode ? 1 : 0);
              disp1->set_component_text_printf("home.bt_qrcode", "%s", qrcode_icon.c_str());
              set_component_color->execute("home.bt_qrcode", qrcode_icon_color);

              // Entities pages button
              ESP_LOGV(TAG, "Set Entities button");
              disp1->send_command_printf("is_entities=%i", entities_pages ? 1 : 0);
              disp1->set_component_text_printf("home.bt_entities", "%s", entities_pages_icon.c_str());
              //set_component_color->execute("home.bt_entities", entities_pages_icon_color);
              set_component_color->execute("home.bt_entities", entities_pages_icon_color);

              blueprint_status->publish_state(int(blueprint_status->raw_state) | (1 << 1));
            }

    #### Service to populate the page Settings #####
    - service: page_settings
      variables:
        reboot: string
        brightness: string
        bright: string
        dim: string
      then:
        - lambda: |-
            if (not id(is_uploading_tft)) {
              if (not reboot.empty()) disp1->set_component_text_printf("settings.lbl_reboot", " %s", reboot.c_str());
              disp1->set_component_text_printf("settings.lbl_brightness", " %s", brightness.c_str());
              display_wrapped_text->execute("settings.lbl_bright", bright.c_str(), display_mode->state == 2 ? 25 : 10);
              display_wrapped_text->execute("settings.lbl_dim", dim.c_str(), display_mode->state == 2 ? 25 : 10);
              blueprint_status->publish_state(int(blueprint_status->raw_state) | (1 << 3));
            }

    #### Service to populate the media player page #####
    - service: media_player
      variables:
        entity: string
        state: string
        is_volume_muted: bool
        friendly_name: string
        volume_level: int
        media_title: string
        media_artist: string
        media_duration: float
        media_position: float
        media_position_delta: float
        supported_features: int
      then:
        - lambda: |-
            if (current_page->state == "media_player" and not id(is_uploading_tft)) {
              detailed_entity->publish_state(entity);
              disp1->set_component_text_printf("page_label", "%s", friendly_name.c_str());
              display_wrapped_text->execute("track", media_title.c_str(), display_mode->state == 2 ? 16 : 27);
              display_wrapped_text->execute("artist", media_artist.c_str(), display_mode->state == 2 ? 26 : 40);

              // on/off button
              if (supported_features & 128 and state == "off") {  //TURN_ON
                disp1->set_component_foreground_color("bt_on_off", 65535);
                disp1->show_component("bt_on_off");
              } else if (supported_features & 256 and state != "off") {  //TURN_OFF
                disp1->set_component_foreground_color("bt_on_off", 10597);
                disp1->show_component("bt_on_off");
              } else disp1->hide_component("bt_on_off");

              // play/pause button
              if ((supported_features & 512 or supported_features & 16384) and state != "playing" and state != "off") {  //PLAY_MEDIA+PLAY
                disp1->set_component_text_printf("bt_play_pause", "%s", "\uE409"); // mdi:play
                disp1->show_component("bt_play_pause");
              } else if (supported_features & 1 and state == "playing" ) {  //PAUSE
                disp1->set_component_text_printf("bt_play_pause", "%s", "\uE3E3"); // mdi:pause
                disp1->show_component("bt_play_pause");
              } else disp1->hide_component("bt_play_pause");

              // bt_prev button - PREVIOUS_TRACK
              if (supported_features & 16 and state != "off") disp1->show_component("bt_prev"); else disp1->hide_component("bt_prev");
              // bt_next button - NEXT_TRACK
              if (supported_features & 32 and state != "off") disp1->show_component("bt_next"); else disp1->hide_component("bt_next");

              // Stop button - STOP
              //if (supported_features & 4096 and (state == "playing" or state == "paused")) disp1->show_component("bt_stop"); else disp1->hide_component("bt_stop");

              // mute/unmute button - VOLUME_MUTE
              disp1->set_component_value("is_muted", is_volume_muted ? 1 : 0);
              if (supported_features & 8 and is_volume_muted) {  // unmute
                disp1->set_component_text_printf("bt_mute", "%s", "\uEE07"); // mdi:volume-variant-off
                disp1->show_component("bt_mute");
              } else if (supported_features & 8) {  // mute
                disp1->set_component_text_printf("bt_mute", "%s", "\uE57E"); // mdi:volume-low
                disp1->show_component("bt_mute");
              } else disp1->hide_component("bt_mute");

              // VOLUME_SET
              if (supported_features & 4) {
                if (volume_level != id(last_volume_level)) {
                  id(last_volume_level) = volume_level;
                  disp1->set_component_text_printf("vol_text", "%" PRIu32 "%%", volume_level);
                  disp1->set_component_value("vol_slider", volume_level);
                }
                disp1->show_component("vol_slider");
                disp1->show_component("bt_vol_down");
                disp1->show_component("bt_vol_up");
                disp1->show_component("vol_text");
              } else {
                disp1->hide_component("vol_slider");
                disp1->hide_component("bt_vol_down");
                disp1->hide_component("bt_vol_up");
                disp1->hide_component("vol_text");
              }

              if (media_duration > 0) {
                if (media_duration != id(last_media_duration) or media_position != id(last_media_position)) {
                  id(last_media_duration) = media_duration;
                  id(last_media_position) = media_position;
                  disp1->set_component_value("prg_current", int(round(min(media_position + media_position_delta, media_duration))));
                }
                disp1->set_component_value("prg_total", int(round(media_duration)));
                disp1->send_command_printf("prg_timer.en=%i", (state == "playing") ? 1 : 0);
                disp1->show_component("time_current");
                disp1->show_component("time_total");
                disp1->show_component("time_progress");
              } else {
                disp1->send_command_printf("prg_timer.en=0");
                disp1->hide_component("time_current");
                disp1->hide_component("time_total");
                disp1->hide_component("time_progress");
              }
            }

##### START - DISPLAY START CONFIGURATION #####
display:
  - id: disp1
    platform: nextion
    uart_id: tf_uart
    on_setup:
      - script.execute: setup_sequence
    on_page:
      lambda: |-
        static const char *const TAG = "display.disp1.on_page";
        if (id(is_uploading_tft)) {
          ESP_LOGD(TAG, "Page changed ignored as a TFT upload is in progress");
        } else if (x > id(page_names).size()) {
          ESP_LOGW(TAG, "Invalid page index: %i", int(x));
        } else {
          ESP_LOGD(TAG, "Nextion page changed");
          ESP_LOGD(TAG, "New page: %s (%i)" , id(page_names)[x].c_str(), x);
          page_id->update();
          if (current_page->state != id(page_names)[x].c_str() or x == 9) {
            current_page->publish_state(id(page_names)[x].c_str());
            page_changed->execute(id(page_names)[x].c_str());
          }
        }
    on_touch:
      lambda: |-
        static const char *const TAG = "display.disp1.on_touch";
        ESP_LOGV(TAG, "Nextion touch event detected!");
        ESP_LOGV(TAG, "Page:         %s", id(page_names)[page_id].c_str());
        ESP_LOGV(TAG, "Component Id: %i", component_id);
        ESP_LOGV(TAG, "Event type:   %s", touch_event ? "Press" : "Release");
        timer_reset_all->execute(id(page_names)[page_id].c_str());

##### START - GLOBALS CONFIGURATION #####
globals:
  ##### Wi-Fi timeout #####
  - id: wifi_timeout
    type: uint
    restore_value: false
    initial_value: ${wifi_timeout}

  ##### Is uploading TFT #####
  - id: is_uploading_tft
    type: bool
    restore_value: false
    initial_value: 'false'

  ##### Is boot sequence completed? #####
  - id: setup_sequence_completed
    type: bool
    restore_value: false
    initial_value: 'false'

  ###### Last volume level from Home Assistant ######
  - id: last_volume_level
    type: uint
    restore_value: false
    initial_value: '0'

  ###### Last duration from Home Assistant ######
  - id: last_media_duration
    type: uint
    restore_value: false
    initial_value: '0'

  ###### Last duration from Home Assistant ######
  - id: last_media_position
    type: uint
    restore_value: false
    initial_value: '0'

  ###### Relay fallback even when buttons have other entities? ######
  - id: relay_1_fallback
    type: bool
    restore_value: true
    initial_value: 'false'
  - id: relay_2_fallback
    type: bool
    restore_value: true
    initial_value: 'false'

  ##### Is embedded thermostat set as main climate entity? #####
  - id: is_embedded_thermostat
    type: bool
    restore_value: true
    initial_value: 'false'

  ##### Save Display Brightness for NSPanel reboot #####
  - id: display_brightness_global
    type: uint
    restore_value: true
    initial_value: '100'

  ##### Save Display DIM Brightness for NSPanel reboot
  - id: display_dim_brightness_global
    type: uint
    restore_value: true
    initial_value: '10'

  ##### Is embedded sensor used for indoor temperature? #####
  - id: embedded_indoor_temp
    type: bool
    restore_value: true
    initial_value: 'false'

  ##### Date/time formats #####
  - id: home_date_color
    type: uint
    restore_value: true
    initial_value: '65535'

  - id: mui_time_format
    type: std::string
    restore_value: true
    initial_value: '"%H:%M"'
  - id: home_time_color
    type: uint
    restore_value: true
    initial_value: '65535'
  - id: mui_meridiem
    type: std::vector<std::string>
    restore_value: false
    initial_value: '{"AM", "PM"}'

  #### MUI strings ####
  - id: mui_please_confirm_global
    type: std::string
    restore_value: true
    initial_value: '"Please confirm"'
  - id: mui_unavailable_global
    type: std::string
    restore_value: true
    initial_value: '"Unavailable"'

  ##### Chips #####
  - id: home_chip_font_size
    type: uint
    restore_value: true
    initial_value: '7'

  #### Custom buttons ####
  - id: home_custom_buttons_font_size
    type: uint
    restore_value: true
    initial_value: '8'

  ##### Relay icons #####
  - id: home_relay1_icon
    type: std::string
    restore_value: true
    initial_value: ''
  - id: home_relay1_icon_color
    type: uint16_t
    restore_value: true
    initial_value: '65535'

  - id: home_relay2_icon
    type: std::string
    restore_value: true
    initial_value: ''
  - id: home_relay2_icon_color
    type: uint16_t
    restore_value: true
    initial_value: '65535'

  - id: home_notify_icon_color_normal
    type: std::vector<int32_t>
    restore_value: false
  - id: home_notify_icon_color_unread
    type: std::vector<int32_t>
    restore_value: false

  ##### Screensaver #####
  - id: screensaver_display_time
    type: bool
    restore_value: false
    initial_value: 'false'
  - id: screensaver_display_time_color
    type: std::vector<int32_t>
    restore_value: false
    initial_value: '{64, 64, 64}'

  - id: page_names
    type: std::vector<std::string>
    restore_value: false
    initial_value:
      '{
        "home",
        "weather01",
        "weather02",
        "weather03",
        "weather04",
        "weather05",
        "climate",
        "settings",
        "boot",
        "screensaver",
        "light",
        "cover",
        "buttonpage01",
        "buttonpage02",
        "buttonpage03",
        "buttonpage04",
        "notification",
        "qrcode",
        "entitypage01",
        "entitypage02",
        "entitypage03",
        "entitypage04",
        "fan",
        "alarm",
        "keyb_num",
        "media_player",
        "confirm"
      }'

  - id: framework
    type: uint8_t
    restore_value: false
    initial_value: '0'  # 0 = unknown, 1 = Arduino, 2 = ESP-IDF

##### START - BINARY SENSOR CONFIGURATION #####
binary_sensor:

  ###### LEFT BUTTON BELOW DISPLAY TO TOGGLE RELAY#####
  - name: Left Button
    platform: gpio
    id: left_button
    pin:
      number: 14
      inverted: true
    on_multi_click:
      - timing: &long_click-timing
          - ON for at least 0.8s
        invalid_cooldown: ${invalid_cooldown}
        then:
          - logger.log: "Left button - Long click"
          - script.execute:
              id: ha_button
              page: !lambda return current_page->state.c_str();
              component: "hw_bt_left"
              command: "long_click"
      - timing: &short_click-timing
          - ON for at most 0.8s
        invalid_cooldown: ${invalid_cooldown}
        then:
          - logger.log: "Left button - Short click"
          - if:
              condition:
                or:
                  - switch.is_on: relay1_local
                  - and:
                      - lambda: !lambda return id(relay_1_fallback);
                      - or:
                          - not:
                              - api.connected:
                          - not:
                              - wifi.connected:
              then:
                - switch.toggle: relay_1
          - script.execute:
              id: ha_button
              page: !lambda return current_page->state.c_str();
              component: "hw_bt_left"
              command: "short_click"
      - timing: &hold_to_restart-timing
          - ON for at least 15.0s
        invalid_cooldown: ${invalid_cooldown}
        then:
          - switch.turn_off: screen_power
          - delay: 5s
          - switch.turn_on: screen_power
          - delay: 2s
          - lambda: disp1->soft_reset();
          - delay: 2s
          - script.execute: setup_sequence

  ##### RIGHT BUTTON BELOW DISPLAY TO TOGGLE RELAY #####
  - name: Right Button
    platform: gpio
    id: right_button
    pin:
      number: 27
      inverted: true
    on_multi_click:
      - timing: *long_click-timing
        invalid_cooldown: ${invalid_cooldown}
        then:
          - logger.log: "Right button - Long click"
          - script.execute:
              id: ha_button
              page: !lambda return current_page->state.c_str();
              component: "hw_bt_right"
              command: "long_click"
      - timing: *short_click-timing
        invalid_cooldown: ${invalid_cooldown}
        then:
          - logger.log: "Right button - Short click"
          - if:
              condition:
                or:
                  - switch.is_on: relay2_local
                  - and:
                      - lambda: !lambda return id(relay_2_fallback);
                      - or:
                          - not:
                              - api.connected:
                          - not:
                              - wifi.connected:
              then:
                - switch.toggle: relay_2
          - script.execute:
              id: ha_button
              page: !lambda return current_page->state.c_str();
              component: "hw_bt_right"
              command: "short_click"
      - timing: *hold_to_restart-timing
        invalid_cooldown: ${invalid_cooldown}
        then:  # Restart the panel
          - button.press: restart_nspanel

  ##### Restart NSPanel Button - Setting Page #####
  - name: Restart
    platform: nextion
    page_id: 7
    component_id: 9
    internal: true
    on_click:
      - button.press: restart_nspanel
  ##### Restart NSPanel Button - Boot Page #####
  - name: Restart
    platform: nextion
    page_id: 8
    component_id: 4
    internal: true
    on_click:
      - button.press: restart_nspanel

  ## Delays initial info from HA to the display #####
  - name: Nextion display
    id: nextion_init
    platform: template
    device_class: connectivity
    publish_initial_state: true
    entity_category: diagnostic
    icon: mdi:tablet-dashboard

  ##### API connection status
  - name: Status
    platform: status
    id: api_status
    on_state:
      then:
        - script.execute: watchdog

##### START - BUTTON CONFIGURATION #####
button:
  ###### Factory Reset button #####
  - name: Factory reset
    platform: factory_reset
    id: nspanel_factory_reset
    internal: false
    disabled_by_default: true
    icon: mdi:restart-alert

  ###### REBOOT BUTTON #####
  - name: Restart
    platform: restart
    id: restart_nspanel

  ###### Power cycle Nextion Display ######
  - name: Nextion display - Power cycle
    id: screen_power_cycle
    platform: template
    internal: false
    disabled_by_default: true
    icon: mdi:power-cycle
    entity_category: diagnostic
    on_press:
      - switch.turn_off: screen_power
      - delay: 1s
      - switch.turn_on: screen_power

##### START - NUMBER CONFIGURATION #####
number:
  ##### SCREEN BRIGHTNESS #####
  - name: Display Brightness
    id: display_brightness
    platform: template
    entity_category: config
    unit_of_measurement: '%'
    min_value: 1
    max_value: 100
    initial_value: 100
    step: 1
    restore_value: true
    optimistic: true
    set_action:
      then:
        - lambda: |-
            id(display_brightness_global) = int(x);
            disp1->send_command_printf("brightness=%i", int(x));
            disp1->send_command_printf("settings.brightslider.val=%i", int(x));
            if (current_page->state != "screensaver")
              {
                disp1->set_backlight_brightness(x/100);
                current_brightness->update();
                timer_dim->execute(current_page->state.c_str(), int(timeout_dim->state));
                timer_sleep->execute(current_page->state.c_str(), int(timeout_sleep->state));
                if (current_page->state == "settings") disp1->set_component_text_printf("bright_text", "%i%%", int(x));
              }

  ##### SCREEN BRIGHTNESS DIMMED DOWN #####
  - name: Display Brightness Dimdown
    id: display_dim_brightness
    platform: template
    entity_category: config
    unit_of_measurement: '%'
    min_value: 1
    max_value: 100
    initial_value: 25
    step: 1
    restore_value: true
    optimistic: true
    set_action:
      then:
        - lambda: |-
            id(display_dim_brightness_global) = int(x);
            disp1->send_command_printf("brightness_dim=%i", int(x));
            disp1->send_command_printf("settings.dimslider.val=%i", int(x));
            if (current_page->state != "screensaver" and (current_brightness->state <= id(display_dim_brightness_global)))
              {
                set_brightness->execute(x);
                timer_sleep->execute(current_page->state.c_str(), int(timeout_sleep->state));
                if (current_page->state == "settings") disp1->set_component_text_printf("dim_text", "%i%%", int(x));
              }

  ##### SCREEN BRIGHTNESS SLEEP #####
  - name: Display Brightness Sleep
    id: display_sleep_brightness
    platform: template
    entity_category: config
    unit_of_measurement: '%'
    min_value: 0
    max_value: 100
    initial_value: 0
    step: 1
    restore_value: true
    optimistic: true
    set_action:
      then:
        - lambda: |-
            id(display_dim_brightness_global) = int(x);
            disp1->send_command_printf("brightness_sleep=%i", int(x));
            page_screensaver->execute();

  ##### Temperature Correction #####
  - name: Temperature Correction
    platform: template
    id: temperature_correction
    entity_category: config
    unit_of_measurement: °C
    min_value: -10
    max_value: 10
    initial_value: 0
    step: 0.1
    mode: box
    restore_value: true
    internal: false
    optimistic: true
    set_action:
      - logger.log: Temperature correction changed.
      - delay: 1s
      - lambda: temp_nspanel->publish_state(temp_nspanel->raw_state);

  ##### Timers settings #####
  - name: Timeout Page
    platform: template
    id: timeout_page
    entity_category: config
    min_value: 0
    max_value: 86400
    initial_value: 15
    step: 1
    restore_value: true
    optimistic: true
    icon: mdi:timer
    unit_of_measurement: "s"
    set_action:
      - lambda: timer_page->execute(current_page->state.c_str(), int(x));
  - name: Timeout Dimming
    platform: template
    id: timeout_dim
    entity_category: config
    min_value: 0
    max_value: 86400
    initial_value: 30
    step: 1
    restore_value: true
    optimistic: true
    icon: mdi:timer
    unit_of_measurement: "s"
    set_action:
      - lambda: timer_dim->execute(current_page->state.c_str(), int(x));
  - name: Timeout Sleep
    platform: template
    id: timeout_sleep
    entity_category: config
    min_value: 0
    max_value: 86400
    initial_value: 60
    step: 1
    restore_value: true
    optimistic: true
    icon: mdi:timer
    unit_of_measurement: "s"
    set_action:
      - lambda: |-
          timer_dim->execute(current_page->state.c_str(), int(timeout_dim->state));
          timer_sleep->execute(current_page->state.c_str(), int(x));

##### START - SELECT CONFIGURATION #####
select:
  - id: baud_rate
    name: Baud rate
    platform: template
    options:
      - "2400"
      - "4800"
      - "9600"
      - "19200"
      - "31250"
      - "38400"
      - "57600"
      - "115200"
      - "230400"
      - "250000"
      - "256000"
      - "512000"
      - "921600"
    initial_option: "115200"
    optimistic: true
    restore_value: true
    internal: false
    entity_category: config
    disabled_by_default: true
    icon: mdi:swap-horizontal
    on_value:
      - lambda: set_baud_rate->execute(stoi(x), true);

  - id: wakeup_page_name
    name: Wake-up page
    platform: template
    options:
      - buttonpage01
      - buttonpage02
      - buttonpage03
      - buttonpage04
      - climate
      - entitypage01
      - entitypage02
      - entitypage03
      - entitypage04
      - home
      - qrcode
    initial_option: home
    optimistic: true
    restore_value: true
    internal: false
    entity_category: config
    icon: mdi:page-next-outline
    on_value:
      - lambda: |-
          static const char *const TAG = "select.wakeup_page_name";
          ESP_LOGD(TAG, "New wake-up page selected: %s", x.c_str());
          page_screensaver->execute();

##### START - SENSOR CONFIGURATION #####
sensor:
  ##### Blueprint status #####
  # Bit # Settings step        #
  #  0  # reserved             #
  #  1  # page_home            #
  #  2  # qrcode               #
  #  3  # page_settings        #
  #  4  # relay_settings       #
  #  5  # global_settings      #
  #  6  # reserved             #
  #  7  # reserved             #
  ##############################
  - name: Blueprint
    id: blueprint_status
    platform: template
    unit_of_measurement: "%"
    accuracy_decimals: 1
    entity_category: diagnostic
    icon: mdi:link-variant
    internal: false
    disabled_by_default: false
    filters:
      - lambda: return (x / 62) * 100.0f;
    on_value:
      then:
        - lambda: |-
            static const char *const TAG = "sensor.blueprint_status";
            ESP_LOGD(TAG, "Blueprint progress: %i%%", int(round(x)));
            // Update api value on Nextion
            disp1->send_command_printf("api=%i", (x > 99) ? 1 : 0);

  ##### INTERNAL TEMPERATURE SENSOR, ADC VALUE #####
  - id: ntc_source
    platform: adc
    pin: 38
    update_interval: 60s
    attenuation: 11db

  ##### INTERNAL TEMPERATURE SENSOR, adc reading converted to resistance (calculation)#####
  - id: resistance_sensor
    platform: resistance
    sensor: ntc_source
    configuration: DOWNSTREAM
    resistor: 11.2kOhm

  ##### INTERNAL TEMPERATURE SENSOR, resistance to temperature (calculation) #####
  - id: temp_nspanel
    name: Temperature
    platform: ntc
    sensor: resistance_sensor
    unit_of_measurement: °C
    internal: false
    calibration:
      b_constant: 3950
      reference_temperature: 25°C
      reference_resistance: 10kOhm
    filters:
      - lambda: |-
          return x + temperature_correction->state;
    on_value:
      then:
        # Show panel's temperature if API or Wi-Fi are out
        - lambda: display_embedded_temp->execute();

  ###### Display Brightness GET VALUE FROM NSPanel SLIDER #####
  - id: brightslider
    name: brightness Slider
    platform: nextion
    variable_name: brightslider
    internal: true
    on_value:
      then:
        - number.set:
            id: display_brightness
            value: !lambda 'return int(x);'
        - lambda: |-
            timer_reset_all->execute("settings");

  ###### Display DIM Brightness GET VALUE FROM NSPanel SLIDER #####
  - id: dimslider
    name: dim brightness slider
    platform: nextion
    variable_name: dimslider
    internal: true
    on_value:
      then:
        - number.set:
            id: display_dim_brightness
            value: !lambda 'return int(x);'
        - lambda: |-
            timer_reset_all->execute("settings");

  ###### Display Brightness - Current value (%) #####
  - id: current_brightness
    name: Display Current brightness
    platform: nextion
    variable_name: dim
    precision: 0
    accuracy_decimals: 0
    unit_of_measurement: "%"
    icon: mdi:brightness-percent
    internal: false
    disabled_by_default: false
    on_value:
      then:
        - lambda: |-
            static const char *const TAG = "sensor.current_brightness";
            ESP_LOGD(TAG, "Current brightness: %i%%", int(x));

  ###### Page Id - Current #####
  - id: page_id
    name: Page Id
    platform: nextion
    variable_name: dp
    precision: 0
    accuracy_decimals: 0
    internal: true
    entity_category: diagnostic
    on_value:
      then:
        - lambda: |-
            static const char *const TAG = "sensor.page_id";
            ESP_LOGD(TAG, "New page Id: %i", int(x));
            if (id(is_uploading_tft)) {
              ESP_LOGD(TAG, "Skipping actions as a TFT upload is in progress");
            } else if (x > id(page_names).size()) {
              ESP_LOGW(TAG, "Invalid page index: %i", int(x));
            } else if (current_page->state != id(page_names)[x].c_str()) {
              current_page->publish_state(id(page_names)[x].c_str());
              page_changed->execute(id(page_names)[x].c_str());
            }

  ##### Display mode (1 = EU, 2 = US, 3 = US Landscape)
  - id: display_mode
    name: Display mode
    platform: nextion
    variable_name: display_mode
    precision: 0
    accuracy_decimals: 0
    internal: false
    icon: mdi:phone-rotate-portrait
    entity_category: diagnostic

  ##### Charset (1 = International (original), 2 = CJK languages)
  - name: Display charset
    id: display_charset
    platform: nextion
    variable_name: charset
    precision: 0
    accuracy_decimals: 0
    internal: false
    icon: mdi:translate
    entity_category: diagnostic

  ##### Wi-Fi Signal stregth
  - name: RSSI
    id: wifi_rssi
    platform: wifi_signal
    internal: false
    disabled_by_default: false
    icon: mdi:wifi
    entity_category: diagnostic

##### START - SWITCH CONFIGURATION #####
switch:

  ##### Notification unread #####
  - name: Notification unread
    platform: template
    id: notification_unread
    entity_category: config
    optimistic: true
    restore_mode: ALWAYS_OFF
    on_turn_on:
      - wait_until:
          condition:
            - lambda: !lambda return (blueprint_status->state > 99);
      - lambda: set_component_color->execute("home.bt_notific", id(home_notify_icon_color_unread));
    on_turn_off:
      - wait_until:
          condition:
            - lambda: !lambda return (blueprint_status->state > 99);
      - lambda: set_component_color->execute("home.bt_notific", id(home_notify_icon_color_normal));

  ##### Notification sound #####
  - name: Notification sound
    platform: template
    id: notification_sound
    entity_category: config
    optimistic: true
    restore_mode: RESTORE_DEFAULT_OFF

  # ##### PHYSICAL SWITCH 1 #####
  # - name: Relay 1
  #   platform: gpio
  #   id: relay_1
  #   pin:
  #     number: 22
  #   restore_mode: RESTORE_DEFAULT_OFF
  #   on_turn_on:
  #     then:
  #       - script.execute: refresh_relays
  #   on_turn_off:
  #     then:
  #       - script.execute: refresh_relays
  # ##### PHYSICAL SWITCH 2 ######
  # - name: Relay 2
  #   platform: gpio
  #   id: relay_2
  #   pin:
  #     number: 19
  #   restore_mode: RESTORE_DEFAULT_OFF
  #   on_turn_on:
  #     then:
  #       - script.execute: refresh_relays
  #   on_turn_off:
  #     then:
  #       - script.execute: refresh_relays

  ##### DISPLAY ALWAYS ON #####
  - name: Nextion display - Power
    platform: gpio
    id: screen_power
    entity_category: diagnostic
    pin:
      number: 4
      inverted: true
    restore_mode: ALWAYS_ON
    internal: true
    disabled_by_default: false
    on_turn_on:
      - wait_until:
          condition:
            - lambda: !lambda return disp1->is_setup();
          timeout: 20s
      - lambda: |-
          if (id(setup_sequence_completed)) {
            nextion_init->publish_state(disp1->is_setup());
            disp1->goto_page(wakeup_page_name->state.c_str());
          }
    on_turn_off:
      - lambda: |-
          nextion_init->publish_state(false);

  ##### Relay Local control #####
  - name: Relay 1 Local
    platform: template
    id: relay1_local
    entity_category: config
    optimistic: true
    restore_mode: RESTORE_DEFAULT_OFF
    internal: true
    on_turn_on:
      - logger.log: "Relay 1 Local turned On!"
    on_turn_off:
      - logger.log: "Relay 1 Local turned Off!"
  - name: Relay 2 Local
    platform: template
    id: relay2_local
    entity_category: config
    optimistic: true
    restore_mode: RESTORE_DEFAULT_OFF
    internal: true
    on_turn_on:
      - logger.log: "Relay 2 Local turned On!"
    on_turn_off:
      - logger.log: "Relay 2 Local turned Off!"

##### START - TEXT SENSOR CONFIGURATION #####
text_sensor:
  ##### Entity Id of the entity displayed on the detailed pages
  - name: Detailed Entity
    id: detailed_entity
    platform: template
    icon: mdi:tablet-dashboard
    internal: false
    disabled_by_default: false

  ##### Current page name #####
  - name: Current page
    id: current_page
    platform: template
    icon: mdi:tablet-dashboard
    internal: false
    disabled_by_default: false

  - name: Notification Label
    platform: template
    id: notification_label

  - name: Notification Text
    platform: template
    id: notification_text

  ##### NSPanel event sensor, the main action sensor - push to HA #####
  - name: NSPanel event
    platform: nextion
    nextion_id: disp1
    id: disp1_nspanel_event
    component_name: nspanelevent
    internal: true
    filters:
      - lambda: |-
          x = x.c_str();
          x.shrink_to_fit();
          return x;
    on_value:
      then:
        - lambda: |-
            static const char *const TAG = "text_sensor.disp1_nspanel_event";
            ESP_LOGW(TAG, "Starting");
            DynamicJsonDocument doc(1024);
            deserializeJson(doc, x);
            std::string page = doc["page"];
            std::string component = doc["component"];
            if (not (component == "currentpage" and (page == "screensaver" or page == "home"))) timer_reset_all->execute(page.c_str());
            std::string value = doc["value"];
            std::string entity = detailed_entity->state.c_str();  // doc["entity"];
            ESP_LOGW(TAG, "page: %s", page.c_str());
            ESP_LOGW(TAG, "component: %s", component.c_str());
            ESP_LOGW(TAG, "value: %s", value.c_str());
            ESP_LOGW(TAG, "entity: %s", entity.c_str());
            auto ha_event = new esphome::api::CustomAPIDevice();
            ha_event->fire_homeassistant_event("esphome.nspanel_ha_blueprint",
              {
                {"type", "generic"},
                {"page", page},
                {"component", component},
                {"value", value},
                {"entity", entity}
              });

  ##### NSPanel event - Execute actions from ESPHome - NO push to HA #####
  - name: NSPanel local event
    platform: nextion
    nextion_id: disp1
    id: disp1_local_event
    component_name: localevent
    internal: true
    filters:
      - lambda: |-
          x = x.c_str();
          x.shrink_to_fit();
          return x;
    on_value:
      then:
        - lambda: |-
            static const char *const TAG = "text_sensor.localevent";
            DynamicJsonDocument doc(1024);
            deserializeJson(doc, x);
            std::string page = doc["page"];
            std::string event = doc["event"];
            std::string component = doc["component"];
            std::string key = doc["key"];
            std::string value = doc["value"];
            std::string entity = detailed_entity->state.c_str();  // doc["entity"];
            int embedded = doc["embedded"];
            std::string service = "";

            // Send event to Home Assistant
            if (event == "short_click" or event == "long_click") {
              ha_button->execute(page.c_str(), component.c_str(), event.c_str());
            } else if (event == "click" and page == "home" and component == "climate") {
              detailed_entity->publish_state((id(is_embedded_thermostat)) ? "embedded_climate" : "");
              disp1->set_component_value("climate.embedded", id(is_embedded_thermostat) ? 1 : 0);
              disp1->goto_page("climate");
            } else if (page == "light" or page == "climate" or page == "notification") {  // Generic event
                auto ha_event = new esphome::api::CustomAPIDevice();
                ha_event->fire_homeassistant_event("esphome.nspanel_ha_blueprint", {
                  {"type", "generic"},
                  {"page", page},
                  {"event", event},
                  {"value", value},
                  {"entity", entity}
                });
              }

            // page based actions
            if (page == "alarm")
              {
                std::string code_format = doc["code_format"];
                std::string code_arm_req = doc["code_arm_req"];
                std::string title = doc["mui"];
                if (code_format == "number" and (key == "disarm" or code_arm_req == "1"))
                  {
                    disp1->goto_page("keyb_num");
                    disp1->set_component_value("keyb_num.page_id", 23); //Calling from Alarm page
                    disp1->set_component_text_printf("keyb_num.domain", "%s", page.c_str());
                    disp1->set_component_text_printf("keyb_num.key", "%s", key.c_str());
                    disp1->set_component_text_printf("keyb_num.value", "%s", value.c_str());
                    disp1->set_component_text_printf("keyb_num.entity", "%s", entity.c_str());
                    disp1->set_component_text_printf("keyb_num.title", "%s", title.c_str());
                  }
                else service_call_alarm_control_panel->execute(entity.c_str(), key.c_str(), code_format.c_str(), "");
              }
            else if (page == "climate") {
              change_climate_state->execute((embedded==1), key.c_str(), value.c_str());
            }
            else if (page == "cover") {
              if (key == "position") ha_call_service->execute("cover.set_cover_position", key.c_str(), value.c_str(), entity.c_str());
              else ha_call_service->execute((std::string("cover.") + key.c_str()), "", "", entity.c_str());
            }
            else if (page == "fan") {
              if (key == "stop" or value == "0") ha_call_service->execute("fan.turn_off", "", "", entity.c_str());
              else ha_call_service->execute("fan.turn_on", key.c_str(), value.c_str(), entity.c_str());
            }
            else if (page == "keyb_num") {
              std::string base_domain = doc["base_domain"];
              if (base_domain == "alarm") {
                std::string code_format = doc["code_format"];
                std::string pin = doc["pin"];
                service_call_alarm_control_panel->execute(entity.c_str(), key.c_str(), code_format.c_str(), pin.c_str());
              }
              else if (base_domain == "" or base_domain.empty()) base_domain = "home";
              disp1->goto_page(base_domain.c_str());
            }
            else if (page == "light") ha_call_service->execute("light.turn_on", key.c_str(), value.c_str(), entity.c_str());
            else if (page == "media_player") {
              if (key == "volume_mute") ha_call_service->execute("media_player.volume_mute", "is_volume_muted", value.c_str(), entity.c_str());
              else if (key == "volume_set") ha_call_service->execute("media_player.volume_set", "volume_level", to_string(stof(value) / 100), entity.c_str());
              else if (not key.empty()) ha_call_service->execute((std::string("media_player.") + key.c_str()), "", "", entity.c_str());
            }

  ##### Versioning #####
  - id: version_blueprint
    name: Version Blueprint
    platform: template
    entity_category: diagnostic
    icon: mdi:tag-text-outline
    internal: false
    update_interval: never
    lambda: |-
      return {"unknown"};
    on_value:
      - lambda: |-
          static const char *const TAG = "text_sensor.version_blueprint";
          ESP_LOGD(TAG, "Blueprint version: %s", x.c_str());
          disp1->set_component_text_printf("boot.bluep_version", "%s", x.c_str());
          if (current_page->state == "boot") {
            disp1->send_command_printf("tm_esphome.en=0");
            page_boot->execute();
            timer_reset_all->execute("boot");
          }
          check_versions->execute();

  - id: version_esphome
    name: Version ESPHome
    platform: template
    entity_category: diagnostic
    icon: mdi:tag-text-outline
    internal: false
    lambda: |-
      return {"${version}"};
    on_value:
      - lambda: |-
          static const char *const TAG = "text_sensor.version_esphome";
          ESP_LOGD(TAG, "ESPHome version: %s", x.c_str());
          disp1->set_component_text_printf("boot.esph_version", x.c_str());
          if (current_page->state == "boot") {
            disp1->send_command_printf("tm_esphome.en=0");
            page_boot->execute();
            timer_reset_all->execute("boot");
          }
          check_versions->execute();

  - id: version_tft
    name: Version TFT
    platform: nextion
    component_name: boot.tft_version
    entity_category: diagnostic
    icon: mdi:tag-text-outline
    internal: false
    update_interval: never
    on_value:
      - lambda: |-
          static const char *const TAG = "text_sensor.version_tft";
          ESP_LOGD(TAG, "TFT version: %s", x.c_str());
          if (current_page->state == "boot") {
            disp1->send_command_printf("tm_esphome.en=0");
            page_boot->execute();
            timer_reset_all->execute("boot");
          }
          check_versions->execute();

### Scripts ######
script:
  - id: change_climate_state
    mode: restart
    parameters:
      embedded: bool
      key: string
      value: string
    then:
      - lambda: |-
          if (id(is_uploading_tft)) change_climate_state->stop();
          if (!embedded) {
            if (key == "temperature" or key == "target_temp_high" or key == "target_temp_low")
              ha_call_service->execute("climate.set_temperature", key.c_str(), to_string(stof(value) / 10), detailed_entity->state.c_str());
            else if (key == "hvac_mode")
              ha_call_service->execute("climate.set_hvac_mode", key.c_str(), value.c_str(), detailed_entity->state.c_str());
          }

  - id: check_versions
    mode: restart
    then:
      - wait_until:
          condition:
            - lambda: |-
                auto compareVersions = [](const char* version1, const char* version2) -> bool
                  {
                    int major1 = 0, minor1 = 0;
                    int major2 = 0, minor2 = 0;

                    sscanf(version1, "%d.%d", &major1, &minor1);
                    sscanf(version2, "%d.%d", &major2, &minor2);

                    return (major1 == major2) && (minor1 == minor2);
                  };
                return (compareVersions("${version}", version_tft->state.c_str()) and compareVersions("${version}", version_blueprint->state.c_str()));
          timeout: 60s
      - lambda: |-
          if (id(is_uploading_tft)) check_versions->stop();
          static const char *const TAG = "script.check_versions";
          auto compareVersions = [](const char* version1, const char* version2) -> bool
            {
                int major1 = 0, minor1 = 0;
                int major2 = 0, minor2 = 0;

                sscanf(version1, "%d.%d", &major1, &minor1);
                sscanf(version2, "%d.%d", &major2, &minor2);

                return (major1 == major2) && (minor1 == minor2);
            };
          ESP_LOGD(TAG, "Versions:");
          ESP_LOGD(TAG, "  ESPHome:   ${version}");
          ESP_LOGD(TAG, "  TFT:       %s", version_tft->state.c_str());
          if (not compareVersions("${version}", version_tft->state.c_str())) ESP_LOGE(TAG, "TFT version mismatch!");
          ESP_LOGD(TAG, "  Blueprint: %s", version_blueprint->state.c_str());
          if (not compareVersions("${version}", version_blueprint->state.c_str())) ESP_LOGE(TAG, "Blueprint version mismatch!");

          auto ha_event = new esphome::api::CustomAPIDevice();
          ha_event->fire_homeassistant_event("esphome.nspanel_ha_blueprint",
            {
              {"type", "version"},
              {"tft", version_tft->state.c_str()},
              {"esphome", "${version}"},
              {"blueprint", version_blueprint->state.c_str()}
            });

  - id: display_embedded_temp
    mode: restart
    then:
      - lambda: |-
          if (id(embedded_indoor_temp) or (!wifi_component->is_connected()) or (!api_server->is_connected())) {
            float unit_based_temperature = temp_nspanel->state;
            std::string temp_units = "${temp_units}";
            if (temp_units == "°F" || temp_units == "F" || temp_units == "°f" || temp_units == "f")
              unit_based_temperature = (unit_based_temperature * 9 / 5) + 32;
            disp1->set_component_text_printf("home.current_temp", "%.1f${temp_units}", unit_based_temperature);
          }

  - id: display_wrapped_text
    mode: queued
    parameters:
      component: string
      text_to_display: string
      line_length_limit: uint
    then:
      - lambda: |-
          int startPos = 0;
          int endPos = 0;
          std::string wrappedText = "";
          if (text_to_display.find("\\r") != std::string::npos) {
            wrappedText = text_to_display;
          } else {
            while (startPos < text_to_display.length()) {
              while (text_to_display[startPos] == ' ' and startPos < text_to_display.length()) { startPos++; }
              int endPos = startPos + line_length_limit;
              if (endPos >= text_to_display.length()) endPos = text_to_display.length();
              else
                {
                  while (endPos > startPos && text_to_display[endPos] != ' ') { endPos--; }
                  if (endPos == startPos) endPos = startPos + line_length_limit; // Handle case of long word
                }
              wrappedText += text_to_display.substr(startPos, endPos-startPos);
              if (endPos < text_to_display.length())
                {
                  while (text_to_display[endPos] == ' ') { endPos--; }
                  if (endPos >= startPos) wrappedText += "\\r";
                }
              startPos = endPos + 1; // Skip the space
              while (text_to_display[startPos] == ' ' and startPos < text_to_display.length()) { startPos++; }
            }
          }
          disp1->set_component_text_printf(component.c_str(), "%s", wrappedText.c_str());

  - id: global_settings
    mode: restart
    parameters:
      blueprint_version: string
      embedded_climate: bool
      embedded_climate_friendly_name: string
      embedded_indoor_temperature: bool
      # temperature_unit_is_fahrenheit: bool  # Deprecated
      mui_please_confirm: string
      mui_unavailable: string
      screensaver_time: bool
      screensaver_time_color: int32_t[]
    then:
      - lambda: |-
          if (id(is_uploading_tft)) global_settings->stop();
          static const char *const TAG = "script.global_settings";
          // Blueprint version
          ESP_LOGV(TAG, "Check Blueprint version");
          version_blueprint->publish_state(blueprint_version.c_str());
          check_versions->execute();

          // Embedded thermostat
          ESP_LOGV(TAG, "Load embedded thermostat");
          id(is_embedded_thermostat) = embedded_climate;

          // Indoor temperature
          ESP_LOGV(TAG, "Set indoor temperature");
          id(embedded_indoor_temp) = embedded_indoor_temperature;
          display_embedded_temp->execute();

          // MUI strings
          id(mui_please_confirm_global) = mui_please_confirm;
          id(mui_unavailable_global) = mui_unavailable;

          // Screen saver page (sleep)
          ESP_LOGV(TAG, "Setup screensaver page");
          id(screensaver_display_time) = screensaver_time;
          id(screensaver_display_time_color) = screensaver_time_color;
          page_screensaver->execute();

          if (current_page->state != "boot") {
            // Update current page
            ESP_LOGV(TAG, "Update current page");
            page_changed->execute(current_page->state.c_str());
          }
          ESP_LOGV(TAG, "Current page: %s", current_page->state.c_str());
          disp1->set_component_text_printf("boot.bluep_version", "%s", blueprint_version.c_str());

      - if:
          condition:
            - text_sensor.state:  # Is boot page visible?
                id: current_page
                state: boot
          then:
            - lambda: |-
                ESP_LOGV("script.global_settings", "Boot page is visible");
            - wait_until:
                condition:
                  - not:
                      - text_sensor.state:  # Is boot page visible?
                          id: current_page
                          state: 'boot'
                timeout: 2s
            - if:
                condition:
                  - text_sensor.state:  # Avoid this being called twice by multiple boot triggers
                      id: current_page
                      state: 'boot'
                then:
                  - lambda: |-
                      ESP_LOGV("script.global_settings", "Boot page still visible");
                  - if:
                      condition:
                        switch.is_on: notification_sound
                      then:
                        - rtttl.play:
                            rtttl: 'two short:d=4,o=5,b=100:16e6,16e6'
                  - lambda: |-
                      ESP_LOGD("script.global_settings", "Jump to wake-up page: %s", wakeup_page_name->state.c_str());
                      disp1->goto_page(wakeup_page_name->state.c_str());
                      timer_reset_all->execute(wakeup_page_name->state.c_str());

      - lambda: |-
          ESP_LOGV("script.global_settings", "Finished");

  - id: ha_button
    mode: parallel
    parameters:
      page: string
      component: string
      command: string
    then:
      - lambda: |-
          if (id(is_uploading_tft)) ha_button->stop();
          timer_reset_all->execute(page.c_str());
          auto ha_event = new esphome::api::CustomAPIDevice();
          ha_event->fire_homeassistant_event("esphome.nspanel_ha_blueprint",
            {
              {"type", "button_click"},
              {"page", page},
              {"component", component},
              {"command", command}
            });

  - id: ha_call_service
    mode: restart
    parameters:
      service: string
      key: string
      value: string
      entity: string
    then:
      - lambda: |-
          if (id(is_uploading_tft)) ha_call_service->stop();
          static const char *const TAG = "script.ha_call_service";
          ESP_LOGV(TAG, "Calling Home Assisant service");
          ESP_LOGV(TAG, "  Type: service_call");
          ESP_LOGV(TAG, "  Service: %s", service.c_str());
          ESP_LOGV(TAG, "  Entity:  %s", entity.c_str());
          ESP_LOGV(TAG, "  Key:     %s", key.c_str());
          ESP_LOGV(TAG, "  Value:   %s", value.c_str());
          if (service != "" and not service.empty())
            {
              auto ha_event = new esphome::api::CustomAPIDevice();
              ha_event->fire_homeassistant_event("esphome.nspanel_ha_blueprint",
                {
                  {"type", "service_call"},
                  {"service", service},
                  {"entity", entity},
                  {"key", key},
                  {"value", value}
                });
            }
          ESP_LOGV(TAG, "Finished");

  - id: nextion_status
    mode: restart
    then:
      - lambda: |-
          static const char *const TAG = "script.nextion_status";
          ESP_LOGD(TAG, "Nextion status:");
          ESP_LOGD(TAG, "  Is detected: %s", YESNO(disp1->is_detected()));
          ESP_LOGD(TAG, "  Is setup:    %s", YESNO(disp1->is_setup()));
          ESP_LOGD(TAG, "  Queue size:  %d", disp1->queue_size());

  - id: notification_clear
    mode: restart
    then:
      - lambda: |-
          if (not id(is_uploading_tft)) {
            notification_label->publish_state("");
            notification_text->publish_state("");
            notification_unread->turn_off();
            refresh_notification->execute();
            if (current_page->state == "notification") disp1->goto_page("home");
          }

  - id: open_entity_settings_page
    mode: restart
    parameters:
      page: string
      page_label: string
      page_icon: string
      page_icon_color: int32_t[]
      entity: string
      back_page: string
    then:
      - lambda: |-
          if (not id(is_uploading_tft)) {
            detailed_entity->publish_state(entity);
            if (page == "alarm_control_panel") page = "alarm";
            std::string cmd_page = std::string("page ") + page.c_str();
            disp1->send_command_printf(cmd_page.c_str());
            if (page_label.find("\\r") != std::string::npos)
              page_label = page_label.replace(page_label.find("\\r"), 2, " ");
            disp1->set_component_text_printf("page_label", "%s", page_label.c_str());
            set_page_id->execute("back_page_id", back_page.c_str());
            if (page == "climate")
                disp1->set_component_value("embedded", (entity == "embedded_climate") ? 1 : 0);
            else
              {
                if ((page_icon != std::string()) and (page_icon != ""))
                  disp1->set_component_text_printf("icon_state", "%s", page_icon.c_str());
                set_component_color->execute("icon_state", page_icon_color);
              }
          }

  - id: page_alarm
    mode: restart
    then:  # There's nothing here so far

  - id: page_blank
    mode: restart
    then:
      - lambda: |-
          static const char *const TAG = "script.page_blank";
          ESP_LOGV(TAG, "Construct blank page");
          disp1->set_component_text_printf("esp_version", "ESP: ${version}");  // ESPHome version
          disp1->set_component_text_printf("framework", "%s", id(framework) == 1 ? "Arduino" :
                                                        (id(framework) == 2 ? "ESP-IDF" : "Unknown"));  // ESPHome framework
          disp1->send_command_printf("tm_esphome.en=0");

  - id: page_boot
    mode: restart
    then:
      - lambda: |-
          static const char *const TAG = "script.page_boot";
          ESP_LOGV(TAG, "Construct boot page");
          set_brightness->execute(100);

          disp1->set_component_text_printf("boot.esph_version", "${version}");  // ESPHome version
          disp1->set_component_text_printf("framework", "%s", id(framework) == 1 ? "Arduino" :
                                                        (id(framework) == 2 ? "ESP-IDF" : "Unknown"));  // ESPHome framework
          disp1->send_command_printf("tm_esphome.en=0");
          // disp1->show_component("bt_reboot");

  - id: page_buttonpage
    mode: restart
    parameters:
      page_number: uint
    then:  # There's nothing here so far
  - id: page_buttonpage01
    mode: restart
    then:
      - script.execute:
          id: page_buttonpage
          page_number: 1
  - id: page_buttonpage02
    mode: restart
    then:
      - script.execute:
          id: page_buttonpage
          page_number: 2
  - id: page_buttonpage03
    mode: restart
    then:
      - script.execute:
          id: page_buttonpage
          page_number: 3
  - id: page_buttonpage04
    mode: restart
    then:
      - script.execute:
          id: page_buttonpage
          page_number: 4

  - id: page_changed
    mode: restart
    parameters:
      page: string
    then:
      - lambda: |-
          static const char *const TAG = "script.page_changed";

          // Go to boot page if not initiated
          if (page != "boot" and not nextion_init->state) disp1->goto_page("boot");
          // Reset globals
          if (page != "alarm" && //DEBOG
              page != "climate" &&
              page != "cover" &&
              page != "fan" &&
              page != "light" &&
              page != "media_player" &&
              page != "confirm" &&
              page != "keyb_num") {
              detailed_entity->publish_state("");
              disp1->send_command_printf("back_page_id=0");
          }
          if (page != "media_player") {
            id(last_volume_level) = 0;
            id(last_media_duration) = 0;
            id(last_media_position) = 0;
          }

          // Report new page to logs
          ESP_LOGD(TAG, "New page: %s", page.c_str());
          if (!detailed_entity->state.empty()) ESP_LOGD(TAG, "Entity shown: %s", detailed_entity->state.c_str());

          // Reset timers
          timer_reset_all->execute(page.c_str());

          // Report new page to Home Assistant
          ESP_LOGV(TAG, "Trigger HA event");
          auto ha_event = new esphome::api::CustomAPIDevice();
          ha_event->fire_homeassistant_event("esphome.nspanel_ha_blueprint",
            {
              {"type", "page_changed"},
              {"page", page.c_str()},
              {"entity", detailed_entity->state.c_str()}
            });

          // Call page constructor
          if (page == "alarm") page_alarm->execute();
          else if (page == "blank") page_blank->execute();
          else if (page == "boot") page_boot->execute();
          else if (page == "buttonpage01") page_buttonpage01->execute();
          else if (page == "buttonpage02") page_buttonpage02->execute();
          else if (page == "buttonpage03") page_buttonpage03->execute();
          else if (page == "buttonpage04") page_buttonpage04->execute();
          else if (page == "climate") page_climate->execute();
          else if (page == "confirm") page_confirm->execute();
          else if (page == "cover") page_cover->execute();
          else if (page == "entitypage01") page_entitypage01->execute();
          else if (page == "entitypage02") page_entitypage02->execute();
          else if (page == "entitypage03") page_entitypage03->execute();
          else if (page == "entitypage04") page_entitypage04->execute();
          else if (page == "fan") page_fan->execute();
          else if (page == "home") page_home->execute();
          else if (page == "keyb_num") page_keyb_num->execute();
          else if (page == "light") page_light->execute();
          else if (page == "media_player") page_media_player->execute();
          else if (page == "notification") page_notification->execute();
          else if (page == "qrcode") page_qrcode->execute();
          else if (page == "screensaver") page_screensaver->execute();
          else if (page == "settings") page_settings->execute();
          else if (page == "weather01") page_weather01->execute();
          else if (page == "weather02") page_weather02->execute();
          else if (page == "weather03") page_weather03->execute();
          else if (page == "weather04") page_weather04->execute();
          else if (page == "weather05") page_weather05->execute();

  - id: page_climate
    mode: restart
    then:  # There's nothing here so far

  - id: page_confirm
    mode: restart
    then:
      - lambda: |-
          if (not id(is_uploading_tft)) display_wrapped_text->execute("confirm.title", id(mui_please_confirm_global).c_str(), 15);

  - id: page_cover
    mode: restart
    then:  # There's nothing here so far

  - id: page_entitypage
    mode: restart
    parameters:
      page_number: uint
    then:  # There's nothing here so far
  - id: page_entitypage01
    mode: restart
    then:
      - script.execute:
          id: page_entitypage
          page_number: 1
  - id: page_entitypage02
    mode: restart
    then:
      - script.execute:
          id: page_entitypage
          page_number: 2
  - id: page_entitypage03
    mode: restart
    then:
      - script.execute:
          id: page_entitypage
          page_number: 3
  - id: page_entitypage04
    mode: restart
    then:
      - script.execute:
          id: page_entitypage
          page_number: 4

  - id: page_fan
    mode: restart
    then:  # There's nothing here so far

  - id: page_home
    mode: restart
    then:
      - script.execute: refresh_relays
      - script.execute: refresh_wifi_icon
      - script.execute: refresh_notification

  - id: page_keyb_num
    mode: restart
    then:  # There's nothing here so far

  - id: page_light
    mode: restart
    then:  # There's nothing here so far

  - id: page_media_player
    mode: restart
    then:  # There's nothing here so far

  - id: page_notification
    mode: restart
    then:
      - lambda: |-
          static const char *const TAG = "script.page_notification";
          ESP_LOGV(TAG, "Updating notification page");
          disp1->set_component_text_printf("notification.notifi_label", "%s", notification_label->state.c_str());
          display_wrapped_text->execute("notification.notifi_text01", notification_text->state.c_str(), display_mode->state == 2 ? 23 : 32);

  - id: page_qrcode
    mode: restart
    then:  # There's nothing here so far

  - id: page_screensaver
    mode: restart
    then:
      - lambda: |-
          if (current_page->state == "screensaver" and not id(is_uploading_tft)) {
            static const char *const TAG = "script.page_screensaver";
            ESP_LOGV(TAG, "Updating screensaver page");
            set_page_id->execute("back_page_id", wakeup_page_name->state.c_str());
            // disp1->send_command_printf("back_page_id=%i", id(wakeup_page_id));
            if (id(screensaver_display_time)) {
              disp1->show_component("text");
              set_component_color->execute("screensaver.text",id(screensaver_display_time_color));
              refresh_datetime->execute();
            } else {
              disp1->set_backlight_brightness(0.0f);
            }
            current_brightness->update();
          }

  - id: page_settings
    mode: restart
    then:
      - lambda: |-
          static const char *const TAG = "script.page_settings";
          ESP_LOGV(TAG, "Construct settings page");
          //disp1->set_component_text_printf("bt_sleep", "%s", (id(sleep_mode).state) ? "\uEA19" : "\uEA18"); //mdi:toggle-switch-outline or mdi:toggle-switch-off-outline
          disp1->hide_component("lbl_sleep");
          disp1->hide_component("bt_sleep");

  - id: page_weather
    mode: restart
    parameters:
      page_number: uint
    then:  # There's nothing here so far
  - id: page_weather01
    mode: restart
    then:
      - script.execute:
          id: page_weather
          page_number: 1
  - id: page_weather02
    mode: restart
    then:
      - script.execute:
          id: page_weather
          page_number: 2
  - id: page_weather03
    mode: restart
    then:
      - script.execute:
          id: page_weather
          page_number: 3
  - id: page_weather04
    mode: restart
    then:
      - script.execute:
          id: page_weather
          page_number: 4
  - id: page_weather05
    mode: restart
    then:
      - script.execute:
          id: page_weather
          page_number: 5

  - id: refresh_datetime
    mode: restart
    then:
      - lambda: |-
          static const char *const TAG = "script.refresh_datetime";
          ESP_LOGV(TAG, "Updating time display");
          std::string time_format_str = id(mui_time_format);
          if (time_format_str.find("%-H") != std::string::npos) {
            time_format_str = time_format_str.replace(time_format_str.find("%-H"), sizeof("%-H")-1,
                                                      to_string((int)(id(time_provider).now().hour)));
          }
          if (time_format_str.find("%-I") != std::string::npos) {
            if (id(time_provider).now().hour>12) {
              time_format_str = time_format_str.replace(time_format_str.find("%-I"), sizeof("%-I")-1,
                                                        to_string((int)(id(time_provider).now().hour-12)));
            } else if (id(time_provider).now().hour==0) {
              time_format_str = time_format_str.replace(time_format_str.find("%-I"), sizeof("%-I")-1, "12");
            } else {
              time_format_str = time_format_str.replace(time_format_str.find("%-I"), sizeof("%-I")-1,
                                                        to_string((int)(id(time_provider).now().hour)));
            }
          }
          std::string meridiem_text = (id(time_provider).now().hour<12) ? id(mui_meridiem)[0] : id(mui_meridiem)[1];
          if (current_page->state == "screensaver" and id(screensaver_display_time)) {
              ESP_LOGV(TAG, "Updating time on screensaver page");
              std::string time_format_str_sleep = time_format_str;
              if (time_format_str_sleep.find("%p") != std::string::npos)
                time_format_str_sleep.replace(time_format_str_sleep.find("%p"), sizeof("%p")-1, meridiem_text.c_str());
              disp1->set_component_text_printf("text", "%s", id(time_provider).now().strftime(time_format_str_sleep).c_str());
          }
          ESP_LOGV(TAG, "Updating home page meridiem");
          disp1->set_component_text_printf("home.meridiem", "%s", (time_format_str.find("%p") != std::string::npos) ? meridiem_text.c_str() : " ");
          ESP_LOGV(TAG, "Updating home page time");
          disp1->set_component_text_printf("home.time", "%s", id(time_provider).now().strftime(time_format_str).c_str());

  - id: refresh_notification
    mode: restart
    then:
      - wait_until:
          condition:
            - lambda: !lambda return id(setup_sequence_completed);
      - lambda: |-
          static const char *const TAG = "script.refresh_notification";
          bool is_notification = ((not notification_text->state.empty()) or (not notification_label->state.empty()));
          ESP_LOGV(TAG, "Notification: %s", YESNO(is_notification));
          disp1->send_command_printf("is_notification=%i", is_notification ? 0 : 1);
          if (current_page->state == "home") {
            if (is_notification) {
              disp1->show_component("bt_notific");
            } else {
              disp1->hide_component("bt_notific");
            }
          }
      - wait_until:
          condition:
            - lambda: return (blueprint_status->state > 99);
      - lambda: |-
          set_component_color->execute("home.bt_notific", notification_unread->state ? id(home_notify_icon_color_unread) : id(home_notify_icon_color_normal));

  - id: refresh_relays
    mode: restart
    then:
      - lambda: |-
          // Chips - Relays
          disp1->set_component_text_printf("home.icon_top_01", "%s", (relay_1->state) ? id(home_relay1_icon).c_str() : "\uFFFF");
          disp1->set_component_text_printf("home.icon_top_02", "%s", (relay_2->state) ? id(home_relay2_icon).c_str() : "\uFFFF");
          // Hardware buttons bars - Fallback mode
          if (relay1_local->state) disp1->send_command_printf("home.left_bt_pic.val=%i", (relay_1->state) ? 1 : 0);
          if (relay2_local->state) disp1->send_command_printf("home.right_bt_pic.val=%i", (relay_2->state) ? 1 : 0);

  - id: refresh_wifi_icon
    mode: restart
    then:
      - lambda: |-
          if (nextion_init->state) {
            // Update Wi-Fi icon color
            disp1->set_component_font_color("home.wifi_icon", (blueprint_status->state > 99) ? (wifi_rssi->state > -70 ? 33808 : 64992) : 63488);
            // Update Wi-Fi icon
            disp1->set_component_text_printf("home.wifi_icon", "%s",
                                              wifi_component->is_connected() ?
                                                (api_server->is_connected() ?
                                                  ((blueprint_status->state > 99) ? "\uE5A8" :  // mdi:wifi - All right!
                                                  "\uE7CF") :                                   // mdi:home-assistant - Blueprint is out
                                                "\uF256") :                                     // mdi:api-off
                                              "\uE5A9");                                        // mdi:wifi-off
          }

  - id: relay_settings
    mode: restart
    parameters:
      relay1_local_control: bool
      relay1_icon: string
      relay1_icon_color: int
      relay1_fallback: bool
      relay2_local_control: bool
      relay2_icon: string
      relay2_icon_color: int
      relay2_fallback: bool
    then:
      - if:
          condition:
            lambda: 'return id(is_uploading_tft);'
          then:
            - script.stop: relay_settings
      - lambda: |-
          static const char *const TAG = "script.relay_settings";
          // Relays
          ESP_LOGV(TAG, "Setup relays");
          relay1_local->publish_state(relay1_local_control);
          relay2_local->publish_state(relay2_local_control);
          id(relay_1_fallback) = relay1_fallback;
          id(relay_2_fallback) = relay2_fallback;
          disp1->set_component_font_color("home.icon_top_01", relay1_icon_color);
          disp1->set_component_font_color("home.icon_top_02", relay2_icon_color);
          disp1->set_component_text_printf("home.icon_top_01", "%s", relay1_icon.c_str());
          disp1->set_component_text_printf("home.icon_top_02", "%s", relay2_icon.c_str());
          id(home_relay1_icon) = relay1_icon.c_str();
          id(home_relay2_icon) = relay2_icon.c_str();
          id(home_relay1_icon_color) = relay1_icon_color;
          id(home_relay2_icon_color) = relay2_icon_color;
          ESP_LOGV(TAG, "Finished");

  - id: restore_settings
    mode: restart
    then:
      - lambda: |-
          ESP_LOGD("script.restore_settings", "Restoring settings");

          #ifdef ARDUINO
          id(framework) = 1;
          #elif defined(USE_ESP_IDF)
          id(framework) = 2;
          #endif

          // ESP_LOGV(TAG, "Restoring wake-up page selector");
          // auto wakeup_page_name_call = id(wakeup_page_name).make_call();
          // wakeup_page_name_call.set_option(id(page_names)[id(wakeup_page_id)]);
          // wakeup_page_name_call.perform();

          // id(is_restored_settings) = true;
      - wait_until:
          condition:
            - lambda: return (not isnan(stoi(baud_rate->state)));
      - lambda: |-
          ESP_LOGV("script.restore_settings", "Restoring baud rate");
          set_baud_rate->execute(stoi(baud_rate->state), true);
          ESP_LOGV("script.restore_settings", "Done!");

  - id: service_call_alarm_control_panel
    mode: restart
    parameters:
      entity: string
      key: string
      code_format: string
      pin: string
    then:
      - lambda: |-
          std::string service = "";
          if (key == "home") service = "alarm_control_panel.alarm_arm_home";
          else if (key == "away") service = "alarm_control_panel.alarm_arm_away";
          else if (key == "night") service = "alarm_control_panel.alarm_arm_night";
          else if (key == "vacation") service = "alarm_control_panel.alarm_arm_vacation";
          else if (key == "bypass") service = "alarm_control_panel.alarm_arm_custom_bypass";
          else if (key == "disarm") service = "alarm_control_panel.alarm_disarm";
          if (service != "" and not service.empty())
            {
              HomeassistantServiceResponse resp;
              HomeassistantServiceMap resp_kv;
              resp.service = service.c_str();
              resp_kv.key = "entity_id";
              resp_kv.value = entity.c_str();
              resp.data.push_back(resp_kv);
              if (pin != "" and not pin.empty())
                {
                  resp_kv.key = "code";
                  resp_kv.value = pin.c_str();
                  resp.data.push_back(resp_kv);
                }
              api_server->send_homeassistant_service_call(resp);
            }

  - id: set_baud_rate
    mode: restart
    parameters:
      baud_rate: uint32_t
      definitive: bool
    then:
      - if:
          condition:
            - lambda: !lambda return (tf_uart->get_baud_rate() != baud_rate);
          then:
            - lambda: |-
                static const char *const TAG = "script.set_baud_rate";
                ESP_LOGD(TAG, "Baud rate changing from %" PRIu32 " to %" PRIu32 " bps", tf_uart->get_baud_rate(), baud_rate);
                ESP_LOGD(TAG, "Flush UART");
            - wait_until:
                condition:
                  - lambda: !lambda return (tf_uart->available() < 1);
                timeout: 5s
            - lambda: |-
                static const char *const TAG = "script.set_baud_rate";
                ESP_LOGD(TAG, "Sending instruction '%s=%" PRIu32 "' to Nextion", definitive ? "bauds" : "baud", baud_rate);
                disp1->send_command_printf("%s=%" PRIu32, definitive ? "bauds" : "baud", baud_rate);
                ESP_LOGD(TAG, "Flush UART");
            - wait_until:
                condition:
                  - lambda: !lambda return (tf_uart->available() < 1);
                timeout: 5s
            - lambda: |-
                static const char *const TAG = "script.set_baud_rate";
                ESP_LOGD(TAG, "Set ESPHome new baud rate to %" PRIu32 " bps", baud_rate);
                tf_uart->set_baud_rate(baud_rate);
                tf_uart->load_settings();
                ESP_LOGD(TAG, "Current baud rate: %" PRIu32 " bps", tf_uart->get_baud_rate());

  - id: set_brightness
    mode: restart
    parameters:
      brightness: uint
    then:
      - lambda: |-
          static const char *const TAG = "script.set_brightness";
          ESP_LOGD(TAG, "brightness: %i%%", brightness);
          if (brightness == id(display_brightness_global) and current_page->state != "screensaver")
            disp1->send_command_printf("wakeup_timer.en=1");
          else
            disp1->set_backlight_brightness(static_cast<float>(brightness) / 100.0f);
          current_brightness->update();
      - delay: 5s
      - lambda: current_brightness->update();

  - id: set_climate
    mode: restart
    parameters:
      current_temp: float
      supported_features: int
      target_temp: float
      target_temp_high: float
      target_temp_low: float
      temp_step: uint
      total_steps: uint
      temp_offset: int
      climate_icon: string
      embedded_climate: bool
    then:
      - lambda: |-
          if (id(is_uploading_tft)) set_climate->stop();
          static const char *const TAG = "script.set_climate";
          ESP_LOGD(TAG, "Starting");
          ESP_LOGD(TAG, "  current_temp:         %f", current_temp);
          ESP_LOGD(TAG, "  supported_features:   %i", supported_features);
          ESP_LOGD(TAG, "  target_temp:          %f", target_temp);
          ESP_LOGD(TAG, "  target_temp_high:     %f", target_temp_high);
          ESP_LOGD(TAG, "  target_temp_low:      %f", target_temp_low);
          ESP_LOGD(TAG, "  temp_step:            %d", temp_step);
          ESP_LOGD(TAG, "  total_steps:          %d", total_steps);
          ESP_LOGD(TAG, "  temp_offset:          %i", temp_offset);
          ESP_LOGD(TAG, "  climate_icon:         %s", climate_icon.c_str());
          ESP_LOGD(TAG, "  embedded_climate:     %s", YESNO(embedded_climate));
          if (current_page->state == "climate") {
            ESP_LOGD(TAG, "Page climate is visible");
            disp1->send_command_printf("climateslider.maxval=%i", total_steps);
            disp1->send_command_printf("slider_high.maxval=%i", total_steps);
            disp1->send_command_printf("slider_low.maxval=%i", total_steps);
            disp1->set_component_value("temp_offset", temp_offset);
            disp1->set_component_value("temp_step", temp_step);
            disp1->show_component("current_temp");
            if (current_temp > -999)
              disp1->set_component_text_printf("current_temp", "%.1f°", current_temp);
            else
              disp1->set_component_text_printf("current_temp", id(mui_unavailable_global).c_str());

            if (target_temp > -999) {  // Target temp enabled
              disp1->set_component_value("active_slider", 0);
              disp1->hide_component("slider_high");
              disp1->hide_component("slider_low");
              disp1->hide_component("target_low");
              disp1->set_component_text_printf("target_high", "%.1f°", target_temp);
              disp1->show_component("target_high");
              disp1->set_component_value("climateslider", round(((10*target_temp) - temp_offset) / temp_step));
              disp1->show_component("climateslider");
            } else {
              disp1->hide_component("slider_high");
              if (target_temp_low > -999) {  // Target temp low enabled
                disp1->set_component_value("active_slider", 2);
                disp1->set_component_text_printf("target_low", "%.1f°", target_temp_low);
                disp1->show_component("target_low");
                disp1->set_component_value("slider_low", round(((10*target_temp_low) - temp_offset) / temp_step));
                disp1->show_component("slider_low");
              } else {
                disp1->hide_component("target_low");
                disp1->hide_component("slider_low");
              }
              if (target_temp_high > -999) {  // Target temp high enabled
                disp1->set_component_value("active_slider", 1);
                disp1->set_component_text_printf("target_high", "%.1f°", target_temp_high);
                disp1->show_component("target_high");
                disp1->set_component_value("slider_high", round(((10*target_temp_high) - temp_offset) / temp_step));
                disp1->show_component("slider_high");
              } else {
                disp1->hide_component("target_high");
                disp1->hide_component("slider_high");
              }
            }
            if (target_temp > -999 or target_temp_high > -999 or target_temp_low > -999) {
              disp1->set_component_text_printf("target_icon", "%s", climate_icon.c_str());
              disp1->show_component("target_icon");
              disp1->show_component("decrease_temp");
              disp1->show_component("increase_temp");
            } else {
              disp1->hide_component("target_icon");
              disp1->hide_component("decrease_temp");
              disp1->hide_component("increase_temp");
            }
            disp1->set_component_value("embedded", (embedded_climate) ? 1 : 0);
          }
          ESP_LOGD(TAG, "Finished");

  - id: set_component_color
    mode: queued
    parameters:
      component: string
      foreground: int32_t[]
    then:
      - lambda: |-
          if (id(is_uploading_tft)) set_component_color->stop();
          static const char *const TAG = "script.set_component_color";
          ESP_LOGVV(TAG, "Starting:");
          ESP_LOGVV(TAG, "  Component:  %s", component.c_str());
          int fg565 = -1;
          // Foreground
          if (foreground.size() == 3 and
              foreground[0] >= 0 and
              foreground[1] >= 0 and
              foreground[2] >= 0) {
            ESP_LOGVV(TAG, "  Foreground: {%i, %i, %i}", foreground[0], foreground[1], foreground[2]);
            fg565 = ((foreground[0] & 0b11111000) << 8) | ((foreground[1] & 0b11111100) << 3) | (foreground[2] >> 3);
          }
          else if (foreground.size() == 1) fg565 = foreground[0];
          else {
            ESP_LOGE(TAG, "  Component:       %s", component.c_str());
            ESP_LOGE(TAG, "  Foreground size: %i", foreground.size());
            fg565 = -1;
          }
          ESP_LOGVV(TAG, "  Foreground: %i", fg565);
          if (fg565 >= 0) disp1->set_component_font_color(component.c_str(), fg565);

  - id: set_page_id
    mode: queued
    parameters:
      variable: string
      page: string
    then:
      - lambda: |-
          static const char *const TAG = "script.set_page_id";
          ESP_LOGV(TAG, "Starting:");
          ESP_LOGV(TAG, "  Variable: %s", variable.c_str());
          ESP_LOGV(TAG, "  Page:     %s", page.c_str());

          auto pageIndex = [](const std::string& page_name) -> uint8_t {
              for (uint8_t i = 0; i < id(page_names).size(); ++i) {
                  if (id(page_names)[i] == page_name) {
                      return i;  // Return the index if found
                  }
              }
              return 0u;  // Return 0 (home page) if not found
          };

          uint detected_page_id = pageIndex(page.c_str());
          ESP_LOGV(TAG, "%s=%i", variable.c_str(), detected_page_id);
          disp1->send_command_printf("%s=%i", variable.c_str(), detected_page_id);

  - id: setup_sequence
    mode: restart
    then:
      - lambda: |-
          static const char *const TAG = "script.setup_sequence";
          ESP_LOGD(TAG, "Starting Nextion setup sequence");
          ESP_LOGD(TAG, "Fetching Page Id");
          page_id->update();
      - wait_until:
          condition:
            - lambda: !lambda return (not isnan(page_id->state));
          timeout: 15s
      - lambda: |-
          static const char *const TAG = "script.setup_sequence";
          ESP_LOGD(TAG, "Fetching charset");
          display_charset->update();
      - wait_until:
          condition:
            - lambda: !lambda return (not isnan(display_charset->state));
          timeout: 5s
      - lambda: |-
          static const char *const TAG = "script.setup_sequence";
          ESP_LOGD(TAG, "Fetching display mode");
          display_mode->update();
      - wait_until:
          condition:
            - lambda: !lambda return (not isnan(display_mode->state));
          timeout: 5s
      - if:
          condition:
            - lambda: !lambda return (not isnan(display_mode->state));
          then:  # Project's TFT detected
            - lambda: |-
                static const char *const TAG = "script.setup_sequence";
                ESP_LOGD(TAG, "Goto page Boot");
                disp1->goto_page("boot");
                ESP_LOGD(TAG, "Fetching TFT version");
                version_tft->update();
            - wait_until:
                condition:
                  - lambda: !lambda return (not version_tft->state.empty());
                timeout: 5s
            - lambda: |-
                static const char *const TAG = "script.setup_sequence";
                ESP_LOGD(TAG, "Wait for Wi-Fi");
            - wait_until:
                condition:
                  - lambda: !lambda return (wifi_component->is_connected());
                timeout: 10s
            - if:
                condition:
                  - lambda: !lambda return (wifi_component->is_connected());
                then:  # Wi-Fi connected
                  - lambda: |-
                      static const char *const TAG = "script.setup_sequence";
                      if (current_page->state == "boot") {
                        ESP_LOGD(TAG, "Publish IP address on screen");
                        disp1->set_component_text_printf("boot.ip_addr", "%s", network::get_ip_address().str().c_str());
                        set_brightness->execute(100);
                      }
                      ESP_LOGD(TAG, "Wait for API");
                  - wait_until:
                      condition:
                        - lambda: !lambda return (api_server->is_connected());
                      timeout: 10s
                  - if:
                      condition:
                        - lambda: !lambda return (api_server->is_connected());
                      then:  # API connected
                        - lambda: |-
                            static const char *const TAG = "script.setup_sequence";
                            ESP_LOGD(TAG, "Publish IP address on screen");
                            ESP_LOGD(TAG, "Report setup to Home Assistant");
                            auto ha_event = new esphome::api::CustomAPIDevice();
                            ha_event->fire_homeassistant_event("esphome.nspanel_ha_blueprint",
                              {
                                {"type", "boot"},
                                {"step", "start"}
                              });
                      else:  # API not connected
                        - lambda: |-
                            static const char *const TAG = "script.setup_sequence";
                            ESP_LOGE(TAG, "API not available");
                else:  # Wi-Fi not connected
                  - lambda: |-
                      static const char *const TAG = "script.setup_sequence";
                      ESP_LOGE(TAG, "Wi-Fi not available");
                  - lambda: |-
                      static const char *const TAG = "script.setup_sequence";
                      ESP_LOGE(TAG, "Wi-Fi not available");
            - wait_until:
                condition:
                  - lambda: !lambda return id(setup_sequence_completed);
                timeout: 1s
            - lambda: |-
                static const char *const TAG = "script.setup_sequence";
                ESP_LOGD(TAG, "Set dimming values");
                display_brightness->publish_state(id(display_brightness_global));
                display_dim_brightness->publish_state(id(display_dim_brightness_global));
                set_brightness->execute(id(display_brightness_global));
                ESP_LOGD(TAG, "Set page Settings");
                disp1->send_command_printf("brightness=%i", id(display_brightness_global));
                disp1->send_command_printf("settings.brightslider.val=%i", id(display_brightness_global));
                disp1->send_command_printf("brightness_dim=%i", id(display_dim_brightness_global));
                disp1->send_command_printf("settings.dimslider.val=%i", id(display_dim_brightness_global));
                disp1->send_command_printf("brightness_sleep=%i", int(display_sleep_brightness->state));
                ESP_LOGD(TAG, "Report to Home Assistant");
                nextion_init->publish_state(disp1->is_setup());
                if (api_server->is_connected() and disp1->is_setup()) {
                  auto ha_event = new esphome::api::CustomAPIDevice();
                  ha_event->fire_homeassistant_event("esphome.nspanel_ha_blueprint",
                    {
                      {"type", "boot"},
                      {"step", "nextion_init"}
                    });
                }
                // Chips icon size
                ESP_LOGV(TAG, "Adjusting icon's sizes");
                for (int i = 1; i <= 10; ++i) {
                  disp1->send_command_printf("home.icon_top_%02d.font=%i", i, id(home_chip_font_size));
                }
                // Custom buttons icon size
                ESP_LOGV(TAG, "Adjusting custom buttons sizes");
                for (int i = 1; i <= 7; ++i) {
                  disp1->send_command_printf("home.button%02d.font=%i", i, id(home_custom_buttons_font_size));
                }
                disp1->send_command_printf("home.bt_notific.font=%i", id(home_custom_buttons_font_size));
                disp1->send_command_printf("home.bt_qrcode.font=%i", id(home_custom_buttons_font_size));
                disp1->send_command_printf("home.bt_entities.font=%i", id(home_custom_buttons_font_size));
                disp1->send_command_printf("home.wifi_icon.font=%i", id(home_chip_font_size));
                ESP_LOGV(TAG, "Restoring relay's icons");
                disp1->set_component_text_printf("home.icon_top_01", "%s", id(home_relay1_icon).c_str());
                disp1->set_component_text_printf("home.icon_top_02", "%s", id(home_relay2_icon).c_str());
                timer_reset_all->execute("boot");
                notification_clear->execute();
                id(setup_sequence_completed) = true;
                ESP_LOGD(TAG, "Wait for leaving boot page");
            - wait_until:
                condition:
                  - not:
                      - text_sensor.state:  # Is boot page visible?
                          id: current_page
                          state: boot
                timeout: 10s
            - lambda: |-
                if (current_page->state == "boot") disp1->goto_page(wakeup_page_name->state.c_str());
          else:  # Unknown TFT
            - lambda: |-
                static const char *const TAG = "script.setup_sequence";
                ESP_LOGE(TAG, "No compatible TFT detected");
                ESP_LOGE(TAG, "Display mode: %f", display_mode->state);
      - lambda: |-
          static const char *const TAG = "script.setup_sequence";
          ESP_LOGD(TAG, "Nextion setup sequence finished!");

  - id: stop_all
    mode: restart
    then:
      - lambda: |-
          static const char *const TAG = "script.stop_all";
          ESP_LOGD(TAG, "Stopping scripts...");
          change_climate_state->stop();
          check_versions->stop();
          display_embedded_temp->stop();
          display_wrapped_text->stop();
          global_settings->stop();
          ha_button->stop();
          ha_call_service->stop();
          nextion_status->stop();
          notification_clear->stop();
          open_entity_settings_page->stop();
          page_alarm->stop();
          page_blank->stop();
          page_boot->stop();
          page_buttonpage01->stop();
          page_buttonpage02->stop();
          page_buttonpage03->stop();
          page_buttonpage04->stop();
          page_buttonpage->stop();
          page_climate->stop();
          page_changed->stop();
          page_confirm->stop();
          page_cover->stop();
          page_entitypage01->stop();
          page_entitypage02->stop();
          page_entitypage03->stop();
          page_entitypage04->stop();
          page_entitypage->stop();
          page_fan->stop();
          page_home->stop();
          page_keyb_num->stop();
          page_light->stop();
          page_media_player->stop();
          page_notification->stop();
          page_qrcode->stop();
          page_screensaver->stop();
          page_settings->stop();
          page_weather01->stop();
          page_weather02->stop();
          page_weather03->stop();
          page_weather04->stop();
          page_weather05->stop();
          page_weather->stop();
          refresh_datetime->stop();
          refresh_relays->stop();
          refresh_wifi_icon->stop();
          relay_settings->stop();
          service_call_alarm_control_panel->stop();
          set_baud_rate->stop();
          set_brightness->stop();
          set_climate->stop();
          set_component_color->stop();
          set_page_id->stop();
          setup_sequence->stop();
          timer_dim->stop();
          timer_page->stop();
          timer_reset_all->stop();
          timer_sleep->stop();
          update_alarm_icon->stop();
          update_climate_icon->stop();
          watchdog->stop();
          ESP_LOGD(TAG, "Finished");

  ###### Timers ######
  - id: timer_reset_all  # Global timer reset - Triggered with a touch on the screen
    mode: restart
    parameters:
      page: string
    then:
      - lambda: |-
          ESP_LOGV("script.timer_reset_all", "Reset timers");
          timer_page->execute(page.c_str(), int(timeout_page->state));
          timer_dim->execute(page.c_str(), int(timeout_dim->state));
          timer_sleep->execute(page.c_str(), int(timeout_sleep->state));
  - id: timer_page       # Handles the fallback to home page after a timeout
    mode: restart
    parameters:
      page: string
      timeout: uint
    then:
      - lambda: |-
          ESP_LOGV("script.timer_page", "Reset timer: %is", timeout);
      - if:
          condition:
            - lambda: |-
                return (timeout >= 1 and
                        page != "boot" and
                        page != "confirm" and
                        page != "home" and
                        page != "notification" and
                        page != "screensaver");
          then:
            - delay: !lambda return (timeout *1000);
            - lambda: |-
                ESP_LOGV("script.timer_page", "Timed out on page: %s", current_page->state.c_str());
                if (timeout >= 1 and
                    current_page->state != "boot" and
                    current_page->state != "confirm" and
                    current_page->state != "home" and
                    current_page->state != "notification" and
                    current_page->state != "screensaver")
                  {
                    ESP_LOGD("script.timer_page", "Fallback to page Home");
                    disp1->goto_page("home");
                  }
  - id: timer_dim        # Handles the brightness dimming after a timeout
    mode: restart
    parameters:
      page: string
      timeout: uint
    then:
      - lambda: |-
          ESP_LOGV("script.timer_dim", "Reset timer: %is", timeout);
          if (current_brightness->state <= id(display_dim_brightness_global)
              and page != "screensaver"
              and page != "boot"
              and page != "blank-screensaver") {
            ESP_LOGD("script.timer_dim", "Waking up on page: %s", page.c_str());
            set_brightness->execute(id(display_brightness_global));
          }
      - if:
          condition:
            - lambda: !lambda return (timeout >= 1);
          then:
            - delay: !lambda return (timeout *1000);
            - lambda: |-
                if (current_page->state != "screensaver" and
                    current_page->state != "blank-screensaver" and
                    current_page->state != "boot" and
                    timeout >= 1) {
                  set_brightness->execute(id(display_dim_brightness_global));
                }
  - id: timer_sleep      # Handles the sleep (go to screensaver page) after a timeout
    mode: restart
    parameters:
      page: string
      timeout: uint
    then:
      - lambda: |-
          ESP_LOGV("script.timer_sleep", "Reset timer: %is", timeout);
      - if:
          condition:
            - lambda: |-
                return (timeout >= 1 and current_page->state != "screensaver" and current_page->state != "boot");
          then:
            - delay: !lambda return (timeout *1000);
            - lambda: |-
                if (current_page->state != "screensaver" and
                    current_page->state != "boot" and
                    timeout >= 1) {
                  ESP_LOGD("script.timer_sleep", "Going to sleep from page %s", current_page->state.c_str());
                  disp1->goto_page("screensaver");
                  set_brightness->execute(display_sleep_brightness->state);
                }

  - id: update_alarm_icon  # To do: Move to blueprint
    mode: restart
    parameters:
      component: string
      state: string
    then:
      - lambda: |-
          std::string alarm_icon = "\uEECC"; //mdi:shield-alert-outline
          int alarm_color = 65535;
          if (state == "disarmed")
            {
              alarm_icon = "\uE99B"; //mdi:shield-off-outline
              alarm_color = 65535;
            }
          else if (state == "armed_home")
            {
              alarm_icon = "\uECCA"; //mdi:shield-home-outline
              alarm_color = 19818;
            }
          else if (state == "armed_away")
            {
              alarm_icon = "\uECCB"; //mdi:shield-lock-outline
              alarm_color = 19818;
            }
          else if (state == "armed_night")
            {
              alarm_icon = "\uF828"; //mdi:shield-moon-outline
              alarm_color = 19818;
            }
          else if (state == "armed_vacation")
            {
              alarm_icon = "\uECC6"; //mdi:shield-airplane-outline
              alarm_color = 19818;
            }
          else if (state == "armed_custom_bypass")
            {
              alarm_icon = "\uE77F"; //mdi:shield-half-full
              alarm_color = 19818;
            }
          else if (state == "pending" or state == "arming")
            {
              alarm_icon = "\uE498"; //mdi:shield-outline
              alarm_color = 65024;
            }
          else if (state == "disarming")
            {
              alarm_icon = "\uE99B"; //mdi:shield-off-outline
              alarm_color = 65024;
            }
          else if (state == "triggered")
            {
              alarm_icon = "\uEECC"; //mdi:shield-alert-outline
              alarm_color = 63488;
            }
          disp1->set_component_text_printf(component.c_str(), alarm_icon.c_str());
          disp1->set_component_font_color(component.c_str(), alarm_color);

  - id: update_climate_icon
    mode: restart
    parameters:
      component: string
      action: uint
      mode: uint
    then:
      - lambda: |-
          switch (action) // CLIMATE_ACTION_OFF = 0, CLIMATE_ACTION_COOLING = 2, CLIMATE_ACTION_HEATING = 3, CLIMATE_ACTION_IDLE = 4, CLIMATE_ACTION_DRYING = 5, CLIMATE_ACTION_FAN = 6
            {
              case 0: //CLIMATE_ACTION_OFF
                switch (mode) // CLIMATE_MODE_OFF = 0, CLIMATE_MODE_HEAT_COOL = 1, CLIMATE_MODE_COOL = 2, CLIMATE_MODE_HEAT = 3, CLIMATE_MODE_FAN_ONLY = 4, CLIMATE_MODE_DRY = 5, CLIMATE_MODE_AUTO = 6
                  {
                    case 0: //CLIMATE_MODE_OFF
                      disp1->set_component_text_printf(component.c_str(), "%s", "\uFFFF"); // (E424) Don't show icon when off
                      disp1->set_component_font_color(component.c_str(), 35921); // grey (off)
                      break;
                    case 1: //CLIMATE_MODE_HEAT_COOL
                      disp1->set_component_text_printf(component.c_str(), "%s", "\uE069"); // mdi:autorenew
                      disp1->set_component_font_color(component.c_str(), 35921); // grey (off)
                      break;
                    case 2: //CLIMATE_MODE_COOL
                      disp1->set_component_text_printf(component.c_str(), "%s", "\uE716"); // mdi:snowflake
                      disp1->set_component_font_color(component.c_str(), 35921); // grey (off)
                      break;
                    case 3: //CLIMATE_MODE_HEAT
                      disp1->set_component_text_printf(component.c_str(), "%s", "\uE237"); // mdi:fire
                      disp1->set_component_font_color(component.c_str(), 35921); // grey (off)
                      break;
                    case 4: //CLIMATE_MODE_FAN_ONLY
                      disp1->set_component_text_printf(component.c_str(), "%s", "\uE20F"); // mdi:fan
                      disp1->set_component_font_color(component.c_str(), 35921); // grey (off)
                      break;
                    case 5: //CLIMATE_MODE_DRY
                      disp1->set_component_text_printf(component.c_str(), "%s", "\uE58D"); // mdi:water-percent
                      disp1->set_component_font_color(component.c_str(), 35921); // grey (off)
                      break;
                    case 6: //CLIMATE_MODE_AUTO
                      disp1->set_component_text_printf(component.c_str(), "%s", "\uEE8D"); // mdi:calendar-sync
                      disp1->set_component_font_color(component.c_str(), 35921); // grey (off)
                      break;
                  }
                  break;
              case 2: //CLIMATE_ACTION_COOLING
                disp1->set_component_text_printf(component.c_str(), "%s", "\uE716"); // mdi:snowflake
                disp1->set_component_font_color(component.c_str(), 1055); // blue
                break;
              case 3: //CLIMATE_ACTION_HEATING
                disp1->set_component_text_printf(component.c_str(), "%s", "\uE237"); // mdi:fire
                disp1->set_component_font_color(component.c_str(), 64164); // deep-orange
                break;
              case 4: //CLIMATE_ACTION_IDLE
                disp1->set_component_text_printf(component.c_str(), "%s", "\uE50E"); // mdi:thermometer
                disp1->set_component_font_color(component.c_str(), 35921); // grey (off)
                break;
              case 5: //CLIMATE_ACTION_DRYING
                disp1->set_component_text_printf(component.c_str(), "%s", "\uE58D"); // mdi:water-percent
                disp1->set_component_font_color(component.c_str(), 64704); // orange
                break;
              case 6: //CLIMATE_ACTION_FAN
                disp1->set_component_text_printf(component.c_str(), "%s", "\uE20F"); // mdi:fan
                disp1->set_component_font_color(component.c_str(), 1530); // cyan
                break;
            }

  - id: watchdog
    mode: restart
    then:
      - script.execute: refresh_relays
      - lambda: |-
          static const char *const TAG = "script.watchdog";
          ESP_LOGV(TAG, "Starting");
          if (id(is_uploading_tft)) {
            ESP_LOGW(TAG, "TFT upload in progress");
          } else {
            // report Wi-Fi status
            bool wifi_connected = wifi_component->is_connected();
            if (wifi_connected) {
              id(wifi_timeout) = ${wifi_timeout};
              float rssi = wifi_rssi->state;
              std::string rssi_status = "Unknown";
              if (rssi > -50) rssi_status = "Excellent";
              else if (rssi > -60) rssi_status = "Good";
              else if (rssi > -70) rssi_status = "Fair";
              else if (rssi > -80) rssi_status = "Weak";
              else rssi_status = "Poor";
              if (rssi > -70) ESP_LOGI(TAG, "Wi-Fi:         %s (%.0f dBm)", rssi_status.c_str(), rssi);
              else if (rssi > -80) ESP_LOGW(TAG, "Wi-Fi:         %s (%.0f dBm)", rssi_status.c_str(), rssi);
              else ESP_LOGE(TAG, "Wi-Fi:         %s (%.0f dBm)", rssi_status.c_str(), rssi);
            }
            else {
              ESP_LOGW(TAG, "Wi-Fi:         DISCONNECTED");
              if (id(wifi_timeout) > 0) {
                id(wifi_timeout)--;
                ESP_LOGI(TAG, "Retrying Wi-Fi connection");
                wifi_component->retry_connect();
              } else {
                ESP_LOGE(TAG, "Restarting ESP due to a Wi-Fi timeout...");
                App.safe_reboot();
              }
            }

            // report API status
            bool api_connected = api_server->is_connected();
            if (api_connected) {
              ESP_LOGI(TAG, "API:           Connected");
            } else {
              ESP_LOGW(TAG, "API:           DISCONNECTED");
              blueprint_status->publish_state(0);
              if (current_page->state != "blank" and
                  current_page->state != "boot" and
                  current_page->state != "home" and
                  current_page->state != "screensaver" and
                  current_page->state != "settings" and
                  current_page->state != "qrcode") {
                ESP_LOGI(TAG, "Fallback to page Home");
                disp1->goto_page("home");
              }
            }

            if (!wifi_connected or !api_connected) blueprint_status->publish_state(0);

            // Report blueprint version
            ESP_LOGI(TAG, "Blueprint:");
            if (blueprint_status->state > 99) {
              ESP_LOGI(TAG, "  Version:     %s", version_blueprint->state.c_str());
              ESP_LOGI(TAG, "  Init steps:  %i (%0.1f%%)", int(blueprint_status->raw_state), blueprint_status->state);
            } else {
              ESP_LOGW(TAG, "  Init steps:  %i (%0.1f%%)", int(blueprint_status->raw_state), blueprint_status->state);
              ESP_LOGW(TAG, "  State:       %s", (wifi_connected and api_connected) ? "Pending" : "DISCONNECTED");
              ESP_LOGI(TAG, "Requesting blueprint settings");
              auto ha_event = new esphome::api::CustomAPIDevice();
              ha_event->fire_homeassistant_event("esphome.nspanel_ha_blueprint",
                {
                  {"type", "boot"},
                  {"step", "timeout"}
                });
            }

            // Report ESPHome
            ESP_LOGI(TAG, "ESPHome:");
            ESP_LOGI(TAG, "  Version:     ${version}");
            // Report framework
            #ifdef ARDUINO
            size_t total_heap_size = ESP.getHeapSize();
            size_t free_heap_size = ESP.getFreeHeap();
            #elif defined(USE_ESP_IDF)
            size_t total_heap_size = heap_caps_get_total_size(MALLOC_CAP_DEFAULT);
            size_t free_heap_size = esp_get_free_heap_size();
            #endif
            if (total_heap_size != 0)
              ESP_LOGI(TAG, "  Heap:        %zu bytes (%d%%)", free_heap_size,
                      int(round(((float)free_heap_size / total_heap_size) * 100.0f)));
            ESP_LOGI(TAG, "  Framework:   %s", id(framework) == 1 ? "Arduino" :
                                              (id(framework) == 2 ? "ESP-IDF" : "Unknown"));  // ESPHome framework

            // Report UART
            ESP_LOGI(TAG, "UART:");
            ESP_LOGI(TAG, "  Baud rate:   %" PRIu32 " bps", tf_uart->get_baud_rate());
            ESP_LOGI(TAG, "  Queue size:  %d", tf_uart->available());

            // Report Nextion status
            nextion_init->publish_state(nextion_init->state and disp1->is_setup());
            ESP_LOGI(TAG, "Nextion:");
            ESP_LOGI(TAG, "  Queue size:  %d", disp1->queue_size());
            if (disp1->is_setup())
              ESP_LOGI(TAG, "  Is setup:    True");
            else {
              ESP_LOGW(TAG, "  Is setup:    False");
              ESP_LOGW(TAG, "  Is detected: %s", YESNO(disp1->is_detected()));
              //exit_reparse->execute();
            }
            if (nextion_init->state) {
              ESP_LOGI(TAG, "  Init:        True");
            } else
              ESP_LOGW(TAG, "  Init:        False");
            if (version_tft->state.empty())
              ESP_LOGW(TAG, "  TFT:         UNKNOWN");
            else
              ESP_LOGI(TAG, "  TFT:         %s", version_tft->state.c_str());
          }
          refresh_wifi_icon->execute();
          ESP_LOGV(TAG, "Finished");
...

and no modifications at .nspanel_esphome_addon_upload_tft.yaml however remoring that one from the config doesn't help, I tried using another panel.

edwardtfn commented 6 months ago

I could duplicate this memory slope on both esp-idf and Arduino. Will look for a way to improve it.

bkbartk commented 6 months ago

in case you still want to know, a reboot seems to happen when the heapsize < ~60KB image image

but reset reason it "Software Reset CPU" which is the same as after a manual restart.

removing tft upload from config doesn't help.

the free size decreases exactly every 60 seconds, but that can also be because esphome only measures every minute.

the scripts with mode: queueddonot containt max_runs https://esphome.io/guides/automations.html#script-component which potentially can queue infinite runs.

not saying this is happening, but I don't see much other which can result in a memory leak. ofc the issue can also be in esphome core. in that case I expect other projects to have similar issue reported.

edwardtfn commented 6 months ago

Nice findings! I believe the memory leak was in the watchdog routine, so I've rebuilt that in a different way, but this point about the queued scripts is very valid. I've limited that also.

I will have to monitor now, but I think the leak was resolved, or at least significantly reduced.

edwardtfn commented 6 months ago

ESP-IDF:

image

Arduino:

image

edwardtfn commented 6 months ago

I will close this for now, to make easier managing my backlog, but please feel free to keep the discussion here and we can always reopen it if the problem persists after the v4.3 release.

bkbartk commented 6 months ago

did removing the watchdog https://github.com/Blackymas/NSPanel_HA_Blueprint/commit/692e145162e670a8b9c2694fc4feecfcd3034cf4#diff-45d6684fd336fc57bdd08e19302f4a3b120284bd43b34eea5b17e31f7f4af4c5 eventually solve the issue? and can I just go ahead and remove the watchdog for v 4.2.6. or is it better to wait for v4.3? also, are there any plans to reintroduce the watchdog again?

in my case I had the feeling that udp wasn't working if it was executed at the exact same moment the watchdog was running. my feeling was based on the log. but if I am able to just remove the watchdog without any consequences this would help me debug this issue.

sorry for spamming you with additional questions.

edwardtfn commented 6 months ago

For the memory leak, I've removed the watchdog and a bunch of logs that are not really adding anything. The watchdog was recently added and apart of logs, it was restarting the device if the wifi was out for more than some time. That was replaced by ESPHome native function.

It's safe to remove the watchdog, however you will have to remove all references to that, which could also be in the TFT upload and the climate base.

bkbartk commented 6 months ago

thnx, only removing watchdog doesn't do the trick image I tested on 2 devices, I will wait for 4.3 release or start using the dev branch for a while, but that takes time to plan, as I'm always in a stuggle uploading a new tft and need time to struggle with it.

electrocamuk commented 6 months ago

Screenshot 2024-02-27 221241 Screenshot 2024-02-27 221229 Just to add my results, uptime 3 days & 3 hours.

edwardtfn commented 6 months ago

ESP-IDF

image

Arduino

image

bkbartk commented 6 months ago

to confirm updating to 4.3dev4 fixes the issue for me image

djsomi commented 5 months ago

Still happening for me with 4.3.1 :(

edwardtfn commented 5 months ago

Still happening for me with 4.3.1 :(

Could you please report as a new bug and share your yaml there? Also, please give a bit more details... What still happening, the memory leak, the reboots or both?

edwardtfn commented 5 months ago

did removing the watchdog 692e145#diff-45d6684fd336fc57bdd08e19302f4a3b120284bd43b34eea5b17e31f7f4af4c5 eventually solve the issue? and can I just go ahead and remove the watchdog for v 4.2.6. or is it better to wait for v4.3? also, are there any plans to reintroduce the watchdog again?

in my case I had the feeling that udp wasn't working if it was executed at the exact same moment the watchdog was running. my feeling was based on the log. but if I am able to just remove the watchdog without any consequences this would help me debug this issue.

sorry for spamming you with additional questions.

@bkbartk, on v4.3.3 I'm trying to return with the watchdog, but this time with no memory leak. It would be nice if you can give it a try and let me know if you have issues with the UDP based component you use. By the way, we have introduced PSRAM on v4.3.3. This is also available for Arduino, but probably would require some tunning to be able to run more things on that framework. If you wanna give it a try, I will be happy to support.

bkbartk commented 5 months ago

@edwardtfn I was not able to give it a try before the release, I just installed v 4.3.4 on one of my panels, now updating the other once. so far nothing critical. UDP works as expected. tft upload works for 80 to 90% on arduino and the udp component, so for that I switch over to esp-idf. I noticed that the webserver stopped working on esp-idf (on all 3 of my devices) but on arduino it seems to work.

not sure what the benefit is of PSRAM at this moment. I know, more RAM but I don't think it's used at the moment? but it gives me a lot of yellow lines during arduino compiling. image I don't think this is an issue, so then it's just informative.

I will let you know if the Heap-size decreases, for now it's just to short to have reliable measurements.

edwardtfn commented 5 months ago

Specially with esp-idf we could move some things to psram without having to work in the code, but looks like arduino cannot take advantage of that (or might need some special settings). My plan is to start moving some of the variables to psram, which will enable caching some info and with this improve performance. I haven't started this direction yet as I wanna see the impact (for good and for bad) of psram alone, but that certainly will open space for future improvement.

edwardtfn commented 5 months ago

About the warnings, I was aware of that before the release. It happens only with Arduino and it is saying the psram pins are being overwritten (intentionality, as Sonoff chose non-standard pins for this). There's a way to hide those warnings, but that will hide any other warning, and we probably don't wanna that. 😉

bkbartk commented 5 months ago

Heap-free seems to be stable image on arduino, including UDP

edwardtfn commented 5 months ago

If you wanna give it a try with transfer buffer (it is just a small chunk, but it's probably better than nothing), add this to your panel's yaml:

external_components:
  - source:
      type: git
      url: https://github.com/edwardtfn/esphome
      ref: nextion-23
    components:
      - nextion
      - psram
    refresh: 1s

Please let me know your results.

bkbartk commented 5 months ago

cool, I just tested this and I'm able to upload the TFT even with the UDP component active

<html><body>
<!--StartFragment-->
19:33:15 | [D] | [nextion.upload.arduino:131] | Uploaded 99.70%, remaining 22404 bytes, free heap: 50564 (DRAM) + 2081303 (PSRAM) bytes
-- | -- | -- | --
19:33:15 | [D] | [nextion.upload.arduino:131] | Uploaded 99.76%, remaining 18308 bytes, free heap: 48736 (DRAM) + 2081303 (PSRAM) bytes
19:33:15 | [D] | [nextion.upload.arduino:131] | Uploaded 99.81%, remaining 14212 bytes, free heap: 50564 (DRAM) + 2081303 (PSRAM) bytes
19:33:15 | [D] | [nextion.upload.arduino:131] | Uploaded 99.87%, remaining 10116 bytes, free heap: 57740 (DRAM) + 2085951 (PSRAM) bytes
19:33:15 | [D] | [nextion.upload.arduino:131] | Uploaded 99.92%, remaining 6020 bytes, free heap: 57740 (DRAM) + 2085951 (PSRAM) bytes
19:33:15 | [D] | [nextion.upload.arduino:131] | Uploaded 99.97%, remaining 1924 bytes, free heap: 57740 (DRAM) + 2085951 (PSRAM) bytes
19:33:15 | [D] | [nextion.upload.arduino:131] | Uploaded 100.00%, remaining 0 bytes, free heap: 55908 (DRAM) + 2085951 (PSRAM) bytes
19:33:15 | [D] | [nextion.upload.arduino:357] | Successfully uploaded TFT to Nextion!
19:33:15 | [D] | [nextion.upload.arduino:359] | Close HTTP connection
19:33:15 | [D] | [nextion.upload.arduino:359] | Close HTTP connection
19:33:15 | [D] | [nextion.upload:104] | Nextion TFT upload finished: Upload successful
19:33:15 | [D] | [nextion.upload:115] | Restarting ESPHome

<!--EndFragment-->
</body>
</html>

This probably would also benefit people using BLE.

Still I hope one day I can use UDP with ESP-ESF But I checked other sync components and none of them support this yet.

edwardtfn commented 5 months ago

Still I hope one day I can use UDP with ESP-ESF

I'm not an expert on UDP, but that shouldn't be hard to implement/adapt.

edwardtfn commented 2 days ago

There is a new UDP component coming with ESPHome 2024.9.0 (which just started beta). Have you seen that?

bkbartk commented 2 days ago

yes I have, I haven't had the time to try it out and the configuration at the moment is not fully clear, I need to have a better look at how this config works, I think it will work in my case, still I have to check the configuration