Files
NSPanel_HA_Blueprint/nspanel_esphome.yaml
Edward Firmo 93194d0f8f Prepares for esp-idf
I haven't decided for `esp-idf` framework yet, but wanna play a bit as it looks to be saving some memory, so I want to prepare the system to support both frameworks (together with `arduino`).
2023-10-05 09:32:36 +02:00

2682 lines
110 KiB
YAML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#####################################################################################################
##### NSPANEL ESPHOME created by Blackymas - https://github.com/Blackymas/NSPanel_HA_Blueprint #####
##### ADVANCED CONFIG + FULL ESPHOME CODE! #####
##### 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:
################## Defaults ##################
# Just in case user forgets to set something #
nextion_update_url: "https://github.com/Blackymas/NSPanel_HA_Blueprint/raw/main/nspanel_eu.tft"
# nextion_update_blank_url: "https://github.com/Blackymas/NSPanel_HA_Blueprint/raw/main/custom_configuration/nspanel_blank.tft"
##############################################
##### DON'T CHANGE THIS #####
version: "4.1dev3"
upload_tft_chunk_size_max: "32768"
#############################
##### WIFI SETUP #####
wifi:
networks:
- id: wifi_default
ssid: ${wifi_ssid}
password: ${wifi_password}
power_save_mode: none
ap:
ssid: "${device_name}"
password: ${wifi_password}
##### WEB SERVER SETUP - Required for json parsing and tft upload #####
web_server:
id: web_server_std
port: 80
auth:
username: admin
password: ${wifi_password}
##### ESPHOME CONFIGURATION #####
esphome:
name: ${device_name}
min_version: 2023.5.0
on_boot:
priority: 200.0
then:
- logger.log: After boot check-up
- wait_until:
condition:
- api.connected:
timeout: 60s
- wait_until:
condition:
- lambda: !lambda return id(disp1).is_setup();
timeout: 20s
- lambda: |-
if (not id(tf_uart).available() and not id(disp1).is_setup())
{
ESP_LOGE("on_boot", "No response from Nextion display");
ESP_LOGD("on_boot", "Turn off Nextion");
id(screen_power).turn_off();
delay(1500);
ESP_LOGD("script.power_cycle_display", "Turn on Nextion");
id(screen_power).turn_on();
}
##### TYPE OF ESP BOARD #####
esp32:
board: esp32dev
##### OTA PASSWORD #####
ota:
id: ota_std
password: ${wifi_password}
safe_mode: true
reboot_timeout: 3min
num_attempts: 3
##### LOGGER #####
logger:
id: logger_std
##### ENABLE RINGTONE MUSIC SUPPORT #####
rtttl:
id: buzzer
output: buzzer_out
##### CONFIGURE INTERNAL BUZZER #####
output:
##### BUZZER FOR PLAYING RINGTONES #####
- platform: ledc
id: buzzer_out
pin:
number: 21
##### UART FOR NEXTION DISPLAY #####
uart:
- id: tf_uart
tx_pin: 16
rx_pin: 17
baud_rate: 115200
##### HTTP REQUEST #####
# Enables http client #
# for upload_tft. #
# pr#3256 adds esp-idf #
########################
external_components:
- source: github://pr#3256
components:
- http_request
http_request:
id: httpclient
##### Keeps time display updated #####
time:
- id: time_provider
platform: homeassistant
on_time:
- seconds: 0
then:
- script.execute:
id: refresh_datetime
on_time_sync:
then:
- logger.log: "System clock synchronized"
- script.execute:
id: refresh_datetime
##### START - BUTTON CONFIGURATION #####
button:
###### REBOOT BUTTON #####
- name: ${device_name} Restart
platform: restart
id: restart_nspanel
##### UPDATE TFT DISPLAY #####
- name: ${device_name} Update TFT display
platform: template
icon: mdi:file-sync
id: tft_update
entity_category: config
on_press:
- logger.log: "Button pressed: Update TFT display"
- binary_sensor.template.publish:
id: nextion_init
state: false
- delay: 16ms
- lambda: id(upload_tft).execute("${nextion_update_url}");
##### START - API CONFIGURATION #####
api:
id: api_server
reboot_timeout: 0s
services:
#### 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[] #std::vector<std::string> #std::map
then:
- lambda: |-
// set alarm icon on home page
id(disp1).send_command_printf("is_alarm=%i", (state == "" or state.empty()) ? 0 : 1);
id(update_alarm_icon).execute("home.bt_alarm", state.c_str());
// Is page Alarm visible?
if (id(current_page).state == "alarm")
{ // Update alarm page
id(entity_id) = entity;
// Alarm page - Header
id(update_alarm_icon).execute("icon_state", state.c_str());
id(disp1).set_component_text_printf("page_label", "%s", page_title.c_str());
id(disp1).set_component_text_printf("code_format", "%s", code_format.c_str());
if (code_arm_required) id(disp1).set_component_text_printf("code_arm_req", "1"); else id(disp1).set_component_text_printf("code_arm_req", "0");
// Alarm page - Button's icons
id(disp1).set_component_text_printf("bt_home_icon", "\uE689"); //mdi:shield-home
id(disp1).set_component_text_printf("bt_away_icon", "\uE99C"); //mdi:shield-lock
id(disp1).set_component_text_printf("bt_night_icon", "\uF827"); //mdi:shield-moon
id(disp1).set_component_text_printf("bt_vacat_icon", "\uE6BA"); //mdi:shield-airplane
id(disp1).set_component_text_printf("bt_bypass_icon", "\uE77F"); //mdi:shield-half-full
id(disp1).set_component_text_printf("bt_disarm_icon", "\uE99D"); //mdi:shield-off
// Alarm page - Button's text
id(display_wrapped_text).execute("bt_home_text", mui_alarm[0].c_str(), 10);
id(display_wrapped_text).execute("bt_away_text", mui_alarm[1].c_str(), 10);
id(display_wrapped_text).execute("bt_night_text", mui_alarm[2].c_str(), 10);
id(display_wrapped_text).execute("bt_vacat_text", mui_alarm[3].c_str(), 10);
id(display_wrapped_text).execute("bt_bypass_text", mui_alarm[4].c_str(), 10);
id(display_wrapped_text).execute("bt_disarm_text", mui_alarm[5].c_str(), 10);
// Alarm page - Buttons
if (supported_features & 1) // Alarm - Button - Home
{
id(disp1).send_command_printf("bt_home_pic.pic=%i", (state == "armed_home") ? 43 : 42);
id(disp1).set_component_background_color("bt_home_text", (state == "armed_home") ? 19818 : 52857);
id(disp1).set_component_background_color("bt_home_icon", (state == "armed_home") ? 19818 : 52857);
id(disp1).set_component_font_color("bt_home_text", (state == "armed_home") ? 65535 : 0);
id(disp1).set_component_font_color("bt_home_icon", (state == "armed_home") ? 65535 : 0);
if (state == "armed_home") id(disp1).hide_component("bt_home"); else id(disp1).show_component("bt_home");
}
if (supported_features & 2) // Alarm - Button - Away
{
id(disp1).send_command_printf("bt_away_pic.pic=%i", (state == "armed_away") ? 43 : 42);
id(disp1).set_component_background_color("bt_away_text", (state == "armed_away") ? 19818 : 52857);
id(disp1).set_component_background_color("bt_away_icon", (state == "armed_away") ? 19818 : 52857);
id(disp1).set_component_font_color("bt_away_text", (state == "armed_away") ? 65535 : 0);
id(disp1).set_component_font_color("bt_away_icon", (state == "armed_away") ? 65535 : 0);
if (state == "armed_away") id(disp1).hide_component("bt_away"); else id(disp1).show_component("bt_away");
}
if (supported_features & 4) // Alarm - Button - Night
{
id(disp1).send_command_printf("bt_night_pic.pic=%i", (state == "armed_night") ? 43 : 42);
id(disp1).set_component_background_color("bt_night_text", (state == "armed_night") ? 19818 : 52857);
id(disp1).set_component_background_color("bt_night_icon", (state == "armed_night") ? 19818 : 52857);
id(disp1).set_component_font_color("bt_night_text", (state == "armed_night") ? 65535 : 0);
id(disp1).set_component_font_color("bt_night_icon", (state == "armed_night") ? 65535 : 0);
if (state == "armed_night") id(disp1).hide_component("bt_night"); else id(disp1).show_component("bt_night");
}
if (supported_features & 32) // Alarm - Button - Vacation
{
id(disp1).send_command_printf("bt_vacat_pic.pic=%i", (state == "armed_vacation") ? 43 : 42);
id(disp1).set_component_background_color("bt_vacat_text", (state == "armed_vacation") ? 19818 : 52857);
id(disp1).set_component_background_color("bt_vacat_icon", (state == "armed_vacation") ? 19818 : 52857);
id(disp1).set_component_font_color("bt_vacat_text", (state == "armed_vacation") ? 65535 : 0);
id(disp1).set_component_font_color("bt_vacat_icon", (state == "armed_vacation") ? 65535 : 0);
if (state == "armed_vacation") id(disp1).hide_component("bt_vacat"); else id(disp1).show_component("bt_vacat");
}
if (supported_features & 16) // Alarm - Button - Custom bypass
{
id(disp1).send_command_printf("bt_bypass_pic.pic=%i", (state == "armed_bypass") ? 43 : 42);
id(disp1).set_component_background_color("bt_bypass_text", (state == "armed_bypass") ? 19818 : 52857);
id(disp1).set_component_background_color("bt_bypass_icon", (state == "armed_bypass") ? 19818 : 52857);
id(disp1).set_component_font_color("bt_bypass_text", (state == "armed_bypass") ? 65535 : 0);
id(disp1).set_component_font_color("bt_bypass_icon", (state == "armed_bypass") ? 65535 : 0);
if (state == "armed_bypass") id(disp1).hide_component("bt_bypass"); else id(disp1).show_component("bt_bypass");
}
if ( true ) // Alarm - Button - Disarm
{
id(disp1).send_command_printf("bt_disarm_pic.pic=%i", (state == "disarmed") ? 43 : 42);
id(disp1).set_component_background_color("bt_disarm_text", (state == "disarmed") ? 19818 : 52857);
id(disp1).set_component_background_color("bt_disarm_icon", (state == "disarmed") ? 19818 : 52857);
id(disp1).set_component_font_color("bt_disarm_text", (state == "disarmed") ? 65535 : 0);
id(disp1).set_component_font_color("bt_disarm_icon", (state == "disarmed") ? 65535 : 0);
if (state == "disarmed") id(disp1).hide_component("bt_disarm"); else id(disp1).show_component("bt_disarm");
}
}
##### Service for transferring global settings from the blueprint to ESPHome #####
- service: global_settings
variables:
blueprint_version: string
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
date_color: int
time_format: string
time_color: int
embedded_climate: bool
embedded_indoor_temperature: bool
temperature_unit_is_fahrenheit: bool
mui_please_confirm: string
then:
- lambda: |-
// Blueprint version
ESP_LOGV("service.global_settings", "Check Blueprint version");
id(version_blueprint) = blueprint_version;
id(check_versions).execute();
// Relays
ESP_LOGV("service.global_settings", "Setup relays");
id(relay1_local).publish_state(relay1_local_control);
id(relay2_local).publish_state(relay2_local_control);
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;
id(relay_1_fallback) = relay1_fallback;
id(relay_2_fallback) = relay2_fallback;
// Localization
ESP_LOGV("service.global_settings", "Load localization");
id(mui_time_format) = time_format;
// Date/Time colors
ESP_LOGV("service.global_settings", "Load date/time colors");
id(home_date_color) = date_color;
id(home_time_color) = time_color;
// Embedded thermostat
ESP_LOGV("service.global_settings", "Load embedded thermostat");
id(is_embedded_thermostat) = embedded_climate;
// Indoor temperature
ESP_LOGV("service.global_settings", "Set indoor temperature");
id(embedded_indoor_temp) = embedded_indoor_temperature;
id(temp_unit_fahrenheit) = temperature_unit_is_fahrenheit;
id(display_embedded_temp).execute();
// Confirm page
ESP_LOGV("service.global_settings", "Setup confirm page");
id(display_wrapped_text).execute("confirm.title", mui_please_confirm.c_str(), 15);
// Refresh colors of global components
ESP_LOGV("service.global_settings", "Refresh color of global components");
id(disp1).set_component_font_color("home.date", id(home_date_color));
id(disp1).set_component_font_color("home.time", id(home_time_color));
id(disp1).set_component_font_color("home.icon_top_01", id(home_relay1_icon_color));
id(disp1).set_component_font_color("home.icon_top_02", id(home_relay2_icon_color));
// Update home page
ESP_LOGV("service.global_settings", "Update home page");
id(update_page_home).execute();
ESP_LOGV("service.global_settings", "Current page: %s", id(current_page).state.c_str());
- if:
condition:
- text_sensor.state: # Is boot page visible?
id: current_page
state: boot
then:
- lambda: |-
ESP_LOGV("service.global_settings", "Boot page is visible");
id(disp1).set_component_text_printf("boot.bluep_version", "%s", blueprint_version.c_str());
- 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("service.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("service.global_settings", "Jump to wake-up page: %s", id(wakeup_page_name).state.c_str());
id(disp1).goto_page(id(wakeup_page_name).state.c_str());
id(timer_reset_all).execute(id(wakeup_page_name).state.c_str());
##### SERVICE TO UPDATE THE HMI FILE ##############
- service: upload_tft
then:
- logger.log: "Service: upload_tft"
- binary_sensor.template.publish:
id: nextion_init
state: false
- lambda: 'id(upload_tft).execute("${nextion_update_url}");'
##### SERVICE TO UPDATE THE TFT FILE from URL #####
- service: upload_tft_url
variables:
url: string
then:
- logger.log: "Service: upload_tft_url"
- binary_sensor.template.publish:
id: nextion_init
state: false
- lambda: 'id(upload_tft).execute(url.c_str());'
##### Service to send a command "printf" directly to the display #####
- service: send_command_printf
variables:
cmd: string
then:
- lambda: 'id(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: 'id(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
message: int
then:
- lambda: 'id(disp1).set_component_value(component.c_str(), message);'
##### Service to send a command "hide componente" directly to the display #####
- service: send_command_hide ### unused ###
variables:
component: string
then:
- lambda: 'id(disp1).hide_component(component.c_str());'
##### Service to send a command "show componente" directly to the display #####
- service: send_command_show ### unused ###
variables:
component: string
then:
- lambda: 'id(disp1).show_component(component.c_str());'
##### Service to send a command "show ALL componente" directly to the display #####
- service: send_command_show_all ### unused ###
then:
- lambda: id(disp1).show_component("255");
##### Service to send a command "font color" directly to the display #####
- service: set_component_color
variables:
component: string
foreground: int[]
background: int[]
then:
- lambda: id(set_component_color).execute(component, foreground, background);
##### Service to show a notification-message on the screen #####
- service: notification_show
variables:
label: string
text: string
then:
- lambda: |-
ESP_LOGV("service.notification_show", "Starting");
id(disp1).send_command_printf("is_notification=1");
id(disp1).goto_page("notification");
id(disp1).set_component_text_printf("notification.notifi_label", "%s", label.c_str());
id(display_wrapped_text).execute("notification.notifi_text01", text.c_str(), id(display_mode) == 2 ? 23 : 32);
id(notification_label).publish_state(label.c_str());
id(notification_text).publish_state(text.c_str());
id(timer_reset_all).execute(id(current_page).state.c_str());
id(notification_unread).turn_on();
if (id(notification_sound).state) id(buzzer).play("two short:d=4,o=5,b=100:16e6,16e6");
##### Service to clear the notification #####
- service: notification_clear
then:
- logger.log: "Service: notification_clear"
- &notification_clear
lambda: |-
id(disp1).send_command_printf("is_notification=0");
if (id(current_page).state == "home") id(disp1).hide_component("bt_notific");
id(notification_label).publish_state("");
id(notification_text).publish_state("");
id(notification_unread).turn_off();
##### 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:
- lambda: |-
id(entity_id) = entity;
std::string cmd_page = std::string("page ") + page.c_str();
id(disp1).send_command_printf(cmd_page.c_str());
id(disp1).set_component_text_printf("page_label", "%s", page_label.c_str());
id(disp1).set_component_text_printf("back_page", "%s", back_page.c_str());
if (page == "climate")
{
if (entity == "embedded_climate") id(addon_climate_set_climate_friendly_name).execute(page_label.c_str());
id(disp1).set_component_value("embedded", (entity == "embedded_climate") ? 1 : 0);
}
else
{
if ((page_icon != std::string()) and (page_icon != ""))
id(disp1).set_component_text_printf("icon_state", "%s", page_icon.c_str());
id(set_component_color).execute("icon_state", page_icon_color, {});
}
# Service to show a QR code on the display (ex. for WiFi password)
- service: qrcode
variables:
title: string
qrcode: string
show: bool
then:
- lambda: |-
id(disp1).set_component_text_printf("qrcode.qrcode_label", "%s", title.c_str());
id(disp1).set_component_text_printf("qrcode.qrcode_value", "%s", qrcode.c_str());
if (show) id(disp1).goto_page("qrcode");
#### Service to set climate state ####
- service: set_climate
variables:
current_temp: float
target_temp: float
temp_step: int
total_steps: int
temp_offset: int
climate_icon: string
embedded_climate: bool
entity: string
then:
- lambda: |-
if (id(current_page).state == "climate") id(entity_id) = entity;
- script.execute:
id: set_climate
current_temp: !lambda "return current_temp;"
target_temp: !lambda "return target_temp;"
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:
btn_id: string
btn_pic: int
btn_bg: int[]
btn_icon_font: int[]
btn_txt_font: int[]
btn_bri_font: int[]
btn_icon: string
btn_label: string
btn_bri_txt: string
then:
- lambda: |-
std::string btnicon = btn_id.c_str() + std::string("icon");
std::string btntext = btn_id.c_str() + std::string("text");
std::string btnbri = btn_id.c_str() + std::string("bri");
id(disp1).send_command_printf("%spic.pic=%i", btn_id.c_str(), btn_pic);
id(set_component_color).execute(btnicon.c_str(), btn_icon_font, btn_bg);
id(set_component_color).execute(btntext.c_str(), btn_txt_font, btn_bg);
id(set_component_color).execute(btnbri.c_str(), btn_bri_font, btn_bg);
id(disp1).set_component_text_printf(btnicon.c_str(), "%s", btn_icon.c_str());
id(display_wrapped_text).execute(btntext.c_str(), btn_label.c_str(), 10);
if (strcmp(btn_bri_txt.c_str(), "0") != 0)
id(disp1).set_component_text_printf(btnbri.c_str(), "%s", btn_bri_txt.c_str());
else
id(disp1).set_component_text_printf(btnbri.c_str(), " ");
##### SERVICE TO WAKE UP THE DISPLAY #####
- service: wake_up
variables:
reset_timer: bool
then:
- lambda: |-
if (id(current_page).state == "screensaver") id(disp1).goto_page(id(wakeup_page_name).state.c_str());
if (reset_timer)
id(timer_reset_all).execute(id(wakeup_page_name).state.c_str());
else
{
id(timer_sleep).execute(id(wakeup_page_name).state.c_str(), int(id(timeout_sleep).state));
id(timer_dim).execute(id(wakeup_page_name).state.c_str(), int(id(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: |-
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();
id(disp1).set_component_text_printf(enticon.c_str(), "%s", ent_icon.c_str());
if (strcmp(ent_icon.c_str(), "0") != 0) id(disp1).set_component_text_printf(enticon.c_str(), "%s", ent_icon.c_str());
id(disp1).set_component_text_printf(entlabel.c_str(), "%s", ent_label.c_str());
id(disp1).set_component_text_printf(ent_id.c_str(), "%s", ent_value.c_str());
if (strcmp(ent_value_xcen.c_str(), "0") != 0) id(disp1).send_command_printf("%s", entxcen.c_str());
#### Service to populate the page Home #####
- service: page_home
variables:
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[]
alarm_state: string
then:
- lambda: |-
// Notification button
id(disp1).send_command_printf("is_notification=%i", (id(notification_text).state.empty() and id(notification_label).state.empty()) ? 0 : 1);
id(disp1).set_component_text_printf("home.bt_notific", "%s", notification_icon.c_str());
id(set_component_color).execute("home.bt_notific", id(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
id(disp1).send_command_printf("is_qrcode=%i", (qrcode) ? 1 : 0);
id(disp1).set_component_text_printf("home.bt_qrcode", "%s", qrcode_icon.c_str());
id(set_component_color).execute("home.bt_qrcode", qrcode_icon_color, {});
// Entities pages button
id(disp1).send_command_printf("is_entities=%i", (entities_pages) ? 1 : 0);
id(disp1).set_component_text_printf("home.bt_entities", "%s", entities_pages_icon.c_str());
id(set_component_color).execute("home.bt_entities", entities_pages_icon_color, {});
// Alarm button
id(disp1).send_command_printf("is_alarm=%i", (alarm_state == "" or alarm_state.empty()) ? 0 : 1);
id(update_alarm_icon).execute("home.bt_alarm", alarm_state.c_str());
#### Service to populate the page Settings #####
- service: page_settings
variables:
reboot: string
#sleep_mode: string
brightness: string
bright: string
dim: string
then:
- lambda: |-
if (not reboot.empty()) id(disp1).set_component_text_printf("settings.lbl_reboot", " %s", reboot.c_str());
id(disp1).set_component_text_printf("settings.lbl_brightness", " %s", brightness.c_str());
id(display_wrapped_text).execute("settings.lbl_bright", bright.c_str(), id(display_mode) == 2 ? 25 : 10);
id(display_wrapped_text).execute("settings.lbl_dim", dim.c_str(), id(display_mode) == 2 ? 25 : 10);
#### 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 (id(current_page).state == "media_player")
{
id(entity_id) = entity;
id(disp1).set_component_text_printf("page_label", "%s", friendly_name.c_str());
id(display_wrapped_text).execute("track", media_title.c_str(), id(display_mode) == 2 ? 16 : 27);
id(display_wrapped_text).execute("artist", media_artist.c_str(), id(display_mode) == 2 ? 26 : 40);
// on/off button
if (supported_features & 128 and state == "off") //TURN_ON
{
id(set_component_color).execute("bt_on_off", { 65535 }, {} );
id(disp1).show_component("bt_on_off");
}
else if (supported_features & 256 and state != "off") //TURN_OFF
{
id(set_component_color).execute("bt_on_off", { 10597 }, {} );
id(disp1).show_component("bt_on_off");
}
else id(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
{
id(disp1).set_component_text_printf("bt_play_pause", "%s", "\uE409"); // mdi:play
id(disp1).show_component("bt_play_pause");
}
else if (supported_features & 1 and state == "playing" ) //PAUSE
{
id(disp1).set_component_text_printf("bt_play_pause", "%s", "\uE3E3"); // mdi:pause
id(disp1).show_component("bt_play_pause");
}
else id(disp1).hide_component("bt_play_pause");
// bt_prev button - PREVIOUS_TRACK
if (supported_features & 16 and state != "off") id(disp1).show_component("bt_prev"); else id(disp1).hide_component("bt_prev");
// bt_next button - NEXT_TRACK
if (supported_features & 32 and state != "off") id(disp1).show_component("bt_next"); else id(disp1).hide_component("bt_next");
// Stop button - STOP
//if (supported_features & 4096 and (state == "playing" or state == "paused")) id(disp1).show_component("bt_stop"); else id(disp1).hide_component("bt_stop");
// mute/unmute button - VOLUME_MUTE
id(disp1).set_component_value("is_muted", (is_volume_muted) ? 1 : 0);
if (supported_features & 8 and is_volume_muted) // unmute
{
id(disp1).set_component_text_printf("bt_mute", "%s", "\uEE07"); // mdi:volume-variant-off
id(disp1).show_component("bt_mute");
}
else if (supported_features & 8) // mute
{
id(disp1).set_component_text_printf("bt_mute", "%s", "\uE57E"); // mdi:volume-low
id(disp1).show_component("bt_mute");
}
else id(disp1).hide_component("bt_mute");
// VOLUME_SET
if (supported_features & 4)
{
if (volume_level != id(last_volume_level))
{
id(last_volume_level) = volume_level;
id(disp1).set_component_text_printf("vol_text", "%i%%", volume_level);
id(disp1).set_component_value("vol_slider", volume_level);
}
id(disp1).show_component("vol_slider");
id(disp1).show_component("bt_vol_down");
id(disp1).show_component("bt_vol_up");
id(disp1).show_component("vol_text");
}
else
{
id(disp1).hide_component("vol_slider");
id(disp1).hide_component("bt_vol_down");
id(disp1).hide_component("bt_vol_up");
id(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;
id(disp1).set_component_value("prg_current", int(round(min(media_position + media_position_delta, media_duration))));
}
id(disp1).set_component_value("prg_total", int(round(media_duration)));
id(disp1).send_command_printf("prg_timer.en=%i", (state == "playing") ? 1 : 0);
id(disp1).show_component("time_current");
id(disp1).show_component("time_total");
id(disp1).show_component("time_progress");
}
else
{
id(disp1).send_command_printf("prg_timer.en=0");
id(disp1).hide_component("time_current");
id(disp1).hide_component("time_total");
id(disp1).hide_component("time_progress");
}
}
##### START - GLOBALS CONFIGURATION #####
globals:
###### Last volume level from Home Assistant ######
- id: last_volume_level
type: int
restore_value: false
initial_value: '-1'
###### Last duration from Home Assistant ######
- id: last_media_duration
type: int
restore_value: false
initial_value: '-1'
###### Last duration from Home Assistant ######
- id: last_media_position
type: int
restore_value: false
initial_value: '-1'
###### 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'
##### Display mode (1 = EU, 2 = US, 3 = US Landscape)
- id: display_mode
type: int
restore_value: true
initial_value: '0'
##### Is dimmed #####
- id: is_dim_brightness
type: bool
restore_value: false
initial_value: 'false'
##### Entity Id of the entity displayed on the detailed pages
- id: entity_id
type: std::string
restore_value: no
initial_value: ''
##### 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: int
restore_value: true
initial_value: '100'
##### Save Display DIM Brightness for NSPanel reboot
- id: display_dim_brightness_global
type: int
restore_value: true
initial_value: '10'
##### Temperature unit #####
##### Is embedded sensor used for indoor temperature? #####
- id: embedded_indoor_temp
type: bool
restore_value: true
initial_value: 'false'
- id: temp_unit_fahrenheit
type: bool
restore_value: true
initial_value: 'false'
##### Date/time formats #####
#- id: mui_date_format
# type: std::string
# restore_value: no
# initial_value: '"%A, %d.%m"'
- id: home_date_color
type: int
restore_value: true
initial_value: '65535'
- id: mui_time_format
type: std::string
restore_value: no
initial_value: '"%H:%M"'
- id: home_time_color
type: int
restore_value: true
initial_value: '65535'
##### Relay icons #####
- id: home_relay1_icon
type: std::string
restore_value: false
initial_value: ''
- id: home_relay1_icon_color
type: int
restore_value: true
initial_value: '65535'
- id: home_relay2_icon
type: std::string
restore_value: false
initial_value: ''
- id: home_relay2_icon_color
type: int
restore_value: true
initial_value: '65535'
- id: home_notify_icon_color_normal
type: std::vector<int>
restore_value: false
- id: home_notify_icon_color_unread
type: std::vector<int>
restore_value: false
##### Versions #####
- id: version_blueprint
type: std::string
restore_value: false
initial_value: ''
- id: version_tft
type: std::string
restore_value: false
initial_value: ''
##### START - BINARY SENSOR CONFIGURATION #####
binary_sensor:
###### LEFT BUTTON BELOW DISPLAY TO TOGGLE RELAY#####
- name: ${device_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
then:
- logger.log: "Left button - Long click"
- script.execute:
id: ha_button
page: !lambda return id(current_page).state.c_str();
component: "hw_bt_left"
command: "long_click"
- timing: &short_click-timing
- ON for at most 0.8s
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 id(current_page).state.c_str();
component: "hw_bt_left"
command: "short_click"
##### RIGHT BUTTON BELOW DISPLAY TO TOGGLE RELAY #####
- name: ${device_name} Right Button
platform: gpio
id: right_button
pin:
number: 27
inverted: true
on_multi_click:
- timing: *long_click-timing
then:
- logger.log: "Right button - Long click"
- script.execute:
id: ha_button
page: !lambda return id(current_page).state.c_str();
component: "hw_bt_right"
command: "long_click"
- timing: *short_click-timing
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 id(current_page).state.c_str();
component: "hw_bt_right"
command: "short_click"
##### Restart NSPanel Button - Setting Page #####
- name: ${device_name} Restart
platform: nextion
page_id: 7
component_id: 9
internal: true
on_click:
- button.press: restart_nspanel
##### Restart NSPanel Button - Boot Page #####
- name: ${device_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: ${device_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: ${device_name} Status
platform: status
id: api_status
on_state:
then:
- script.execute:
id: refresh_wifi_icon
##### START - SENSOR CONFIGURATION #####
sensor:
##### touchevent sensor, Reset the page timeout #####
- id: touchevent
platform: nextion
nextion_id: disp1
component_name: touchevent
internal: true
on_value:
then:
- lambda: |-
id(timer_reset_all).execute(id(current_page).state.c_str());
##### 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) #####
- name: ${device_name} Temperature
platform: ntc
id: temp_nspanel
sensor: resistance_sensor
calibration:
b_constant: 3950
reference_temperature: 25°C
reference_resistance: 10kOhm
filters:
- lambda: return x + id(temperature_correction).state;
on_value:
then:
# Show panel's temperature if API or Wi-Fi are out
- lambda: id(display_embedded_temp).execute();
###### Display Brightness GET VALUE FROM NSPanel SLIDER #####
- name: ${device_name} brightness Slider
platform: nextion
id: brightslider
variable_name: brightslider
internal: true
on_value:
then:
- number.set:
id: display_brightness
value: !lambda 'return int(x);'
- lambda: |-
id(timer_reset_all).execute("settings");
###### Display DIM Brightness GET VALUE FROM NSPanel SLIDER #####
- name: ${device_name} dim brightness slider
platform: nextion
id: dimslider
variable_name: dimslider
internal: true
on_value:
then:
- number.set:
id: display_dim_brightness
value: !lambda 'return int(x);'
- lambda: |-
id(timer_reset_all).execute("settings");
##### START - TEXT SENSOR CONFIGURATION #####
text_sensor:
##### Current page name #####
- name: ${device_name} Current page
id: current_page
#platform: template
platform: nextion
nextion_id: disp1
component_name: currentpage
icon: mdi:tablet-dashboard
internal: false
disabled_by_default: false
filters:
- lambda: |-
x = x.c_str();
x.shrink_to_fit();
return x;
on_value:
then:
- lambda: |-
// Reset globals
if (x != "climate" and x != "cover" and x != "fan" and x != "light" and x != "media_player" and x != "confirm" and x != "keyb_num") id(entity_id) = "";
if (x != "media_player")
{
id(last_volume_level) = -1;
id(last_media_duration) = -1;
id(last_media_position) = -1;
}
// Report new page to logs
ESP_LOGD("text_sensor.current_page", "New page: %s", x.c_str());
if (!id(entity_id).empty()) ESP_LOGD("text_sensor.current_page", "Entity shown: %s", id(entity_id).c_str());
// Reset timers
id(timer_reset_all).execute(x.c_str());
// Report new page to Home Assistant
ESP_LOGV("text_sensor.localevent", "Trigger HA event");
auto ha_event = new esphome::api::CustomAPIDevice();
ha_event->fire_homeassistant_event("esphome.nspanel_ha_blueprint",
{
{"type", "page_changed"},
{"page", x.c_str()},
{"entity", id(entity_id)}
});
// Report new page to add-ons
ESP_LOGV("text_sensor.localevent", "Call add-ons scripts for new page");
id(addon_climate_set_climate).execute(x == "climate" and id(entity_id) == "embedded_climate");
// Construct new page
ESP_LOGV("text_sensor.localevent", "Construct new page");
if (x == "home")
{
ESP_LOGV("text_sensor.nspanelevent", "Construct home page");
id(update_page_home).execute();
}
else if (x == "screensaver")
{
ESP_LOGV("text_sensor.nspanelevent", "Construct screensaver page");
id(update_page_screensaver).execute();
}
else if (x == "climate")
{
ESP_LOGV("text_sensor.nspanelevent", "Construct climate page");
id(disp1).set_component_text_printf("climate.button01_icon", "%s", "\uEE8D"); //mdi:calendar-sync
id(disp1).set_component_text_printf("climate.button02_icon", "%s", "\uE069"); //mdi:autorenew
id(disp1).set_component_text_printf("climate.button03_icon", "%s", "\uE237"); //mdi:fire
id(disp1).set_component_text_printf("climate.button04_icon", "%s", "\uE716"); //mdi:snowflake
id(disp1).set_component_text_printf("climate.button05_icon", "%s", "\uE58D"); //mdi:water-percent
id(disp1).set_component_text_printf("climate.button06_icon", "%s", "\uE20F"); //mdi:fan
id(disp1).set_component_text_printf("climate.button07_icon", "%s", "\uE424"); //mdi:power
id(addon_climate_update_page_climate).execute();
}
else if (x == "cover") // To do: Should be moved to Blueprint
{
ESP_LOGV("text_sensor.nspanelevent", "Construct cover page");
id(disp1).set_component_text_printf("cover.cover_stop", "%s", "\uE666"); //mdi:stop-circle-outline
// In the future this will be dynamically contructed based on the device_class
id(disp1).set_component_text_printf("cover.cover_open", "%s", "\uF11D"); //mdi:window-shutter-open
id(disp1).set_component_text_printf("cover.cover_close", "%s", "\uF11B"); //mdi:window-shutter
}
else if (x == "fan") // To do: Should be moved to Blueprint
{
ESP_LOGV("text_sensor.nspanelevent", "Construct fan page");
id(disp1).set_component_text_printf("fan.button_on", "%s", "\uE20F"); //mdi:fan
id(disp1).set_component_text_printf("fan.button_off", "%s", "\uE81C"); //mdi:fan-off
id(disp1).set_component_text_printf("fan.button_up", "%s", "\uF46D"); //mdi:fan-chevron-up
id(disp1).set_component_text_printf("fan.button_down", "%s", "\uF46C"); //mdi:fan-chevron-down
}
else if (x == "keyb_num")
{
ESP_LOGV("text_sensor.nspanelevent", "Construct keyb_num page");
id(disp1).set_component_text_printf("keyb_num.bview", "%s", "\uE207"); //mdi:eye
id(disp1).set_component_text_printf("keyb_num.bclose", "%s", "\uE158"); //mdi:close-circle
id(disp1).set_component_text_printf("keyb_num.bclear", "%s", "\uE641"); //mdi:eraser-variant
id(disp1).set_component_text_printf("keyb_num.benter", "%s", "\uE12B"); //mdi:check
}
else if (x == "weather01") id(disp1).set_component_text_printf("page_index", "%s", "●○○○○"); // 1/5 // To do: Should be moved to Blueprint
else if (x == "weather02") id(disp1).set_component_text_printf("page_index", "%s", "○●○○○"); // 2/5 // To do: Should be moved to Blueprint
else if (x == "weather03") id(disp1).set_component_text_printf("page_index", "%s", "○○●○○"); // 3/5 // To do: Should be moved to Blueprint
else if (x == "weather04") id(disp1).set_component_text_printf("page_index", "%s", "○○○●○"); // 4/5 // To do: Should be moved to Blueprint
else if (x == "weather05") id(disp1).set_component_text_printf("page_index", "%s", "○○○○●"); // 5/5 // To do: Should be moved to Blueprint
else if (x == "buttonpage01" or x == "entitypage01") id(disp1).set_component_text_printf("page_index", "%s", "●○○○"); // 1/4 // To do: Should be moved to Blueprint
else if (x == "buttonpage02" or x == "entitypage02") id(disp1).set_component_text_printf("page_index", "%s", "○●○○"); // 2/4 // To do: Should be moved to Blueprint
else if (x == "buttonpage03" or x == "entitypage03") id(disp1).set_component_text_printf("page_index", "%s", "○○●○"); // 3/4 // To do: Should be moved to Blueprint
else if (x == "buttonpage04" or x == "entitypage04") id(disp1).set_component_text_printf("page_index", "%s", "○○○●"); // 4/4 // To do: Should be moved to Blueprint
else if (x == "settings") // To do: Add timers on TFT?
{
//id(disp1).set_component_text_printf("bt_sleep", "%s", (id(sleep_mode).state) ? "\uEA19" : "\uEA18"); //mdi:toggle-switch-outline or mdi:toggle-switch-off-outline
id(disp1).hide_component("lbl_sleep");
id(disp1).hide_component("bt_sleep");
}
else if (x == "notification")
{
id(disp1).set_component_text_printf("notification.notifi_label", "%s", id(notification_label).state.c_str());
id(display_wrapped_text).execute("notification.notifi_text01", id(notification_text).state.c_str(), id(display_mode) == 2 ? 23 : 32);
}
else if (x == "media_player") // To do: Should be moved to Blueprint
{
ESP_LOGV("text_sensor.nspanelevent", "Construct media_player page");
id(disp1).set_component_text_printf("bt_on_off", "%s", "\uE424"); //mdi:power
id(disp1).set_component_text_printf("bt_prev", "%s", "\uE4AD"); //mdi:skip-previous
id(disp1).set_component_text_printf("bt_next", "%s", "\uE4AC"); //mdi:skip-next
id(disp1).set_component_text_printf("bt_play_pause", "%s", "\uE40D"); //mdi:play-pause
//id(disp1).set_component_text_printf("bt_stop", "%s", "\uE4DA"); //mdi:stop
id(disp1).set_component_text_printf("bt_mute", "%s", "\uE75E"); //mdi:volume-mute
id(disp1).set_component_text_printf("bt_vol_down", "%s", "\uE75D"); //mdi:volume-minus
id(disp1).set_component_text_printf("bt_vol_up", "%s", "\uE75C"); //mdi:volume-plus
}
- name: ${device_name} Notification Label
platform: template
id: notification_label
- name: ${device_name} Notification Text
platform: template
id: notification_text
##### NSPanel event sensor, the main action sensor - push to HA #####
- name: ${device_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: |-
ESP_LOGE("text_sensor.nspanelevent", "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"))) id(timer_reset_all).execute(page.c_str());
std::string value = doc["value"];
std::string entity = id(entity_id); //doc["entity"];
ESP_LOGE("text_sensor.nspanelevent", "page: %s", page.c_str());
ESP_LOGE("text_sensor.nspanelevent", "component: %s", component.c_str());
ESP_LOGE("text_sensor.nspanelevent", "value: %s", value.c_str());
ESP_LOGE("text_sensor.nspanelevent", "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: ${device_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: |-
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 = id(entity_id); //doc["entity"];
int embedded = doc["embedded"];
std::string service = "";
// send event to Home Assistant
auto ha_event = new esphome::api::CustomAPIDevice();
if (event == "short_click" or event == "long_click") id(ha_button).execute(page.c_str(), component.c_str(), event.c_str());
else if (event == "click")
{
if (page == "home" and component == "climate")
{
id(entity_id) = (id(is_embedded_thermostat)) ? "embedded_climate" : "";
id(disp1).set_component_value("climate.embedded", (id(is_embedded_thermostat)) ? 1 : 0);
}
id(disp1).goto_page("climate");
}
else if (page == "light" or page == "climate" or page == "notification")// Generic event
{
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"))
{
id(disp1).goto_page("keyb_num");
id(disp1).set_component_value("keyb_num.page_id", 23); //Calling from Alarm page
id(disp1).set_component_text_printf("keyb_num.domain", "%s", page.c_str());
id(disp1).set_component_text_printf("keyb_num.key", "%s", key.c_str());
id(disp1).set_component_text_printf("keyb_num.value", "%s", value.c_str());
id(disp1).set_component_text_printf("keyb_num.entity", "%s", entity.c_str());
id(disp1).set_component_text_printf("keyb_num.title", "%s", title.c_str());
}
else id(service_call_alarm_control_panel).execute(entity.c_str(), key.c_str(), code_format.c_str(), "");
}
else if (page == "boot")
{
// Detect display mode
if (doc.containsKey("display_mode"))
{
std::string display_mode_str = doc["display_mode"];
ESP_LOGV("text_sensor.localevent", "display_mode: %s", display_mode_str.c_str());
float display_mode_float = stof(display_mode_str);
if (display_mode_float > 0) id(display_mode) = int(display_mode_float);
}
// Detect TFT version
if (doc.containsKey("version"))
{
std::string version_tmp = doc["version"];
id(version_tft) = version_tmp;
}
id(check_versions).execute();
// Detect timeout
if (event == "timeout")
{
ha_event->fire_homeassistant_event("esphome.nspanel_ha_blueprint",
{
{"type", "boot"},
{"step", "timeout"},
{"value", value}
});
if (stof(value) >= 5)
id(disp1).goto_page(id(wakeup_page_name).state.c_str());
}
}
else if (page == "climate") id(service_call_climate).execute(entity.c_str(), key.c_str(), value.c_str(), (embedded==1));
else if (page == "cover")
{
if (key == "position") id(ha_call_service).execute("cover.set_cover_position", key.c_str(), value.c_str(), entity.c_str());
else id(ha_call_service).execute((std::string("cover.") + key.c_str()), "", "", entity.c_str());
}
else if (page == "fan")
{
if (key == "stop" or value == "0") id(ha_call_service).execute("fan.turn_off", "", "", entity.c_str());
else id(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"];
id(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";
id(disp1).goto_page(base_domain.c_str());
}
else if (page == "light") id(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") id(ha_call_service).execute("media_player.volume_mute", "is_volume_muted", value.c_str(), entity.c_str());
else if (key == "volume_set") id(ha_call_service).execute("media_player.volume_set", "volume_level", to_string(stof(value) / 100), entity.c_str());
else if (not key.empty()) id(ha_call_service).execute((std::string("media_player.") + key.c_str()), "", "", entity.c_str());
}
##### START - SWITCH CONFIGURATION #####
switch:
##### Notification unread #####
- name: ${device_name} Notification unread
platform: template
id: notification_unread
entity_category: config
optimistic: true
restore_mode: ALWAYS_OFF
on_turn_on:
- lambda: id(set_component_color).execute("home.bt_notific", id(home_notify_icon_color_unread), {});
on_turn_off:
- lambda: id(set_component_color).execute("home.bt_notific", id(home_notify_icon_color_normal), {});
##### Notification sound #####
- name: ${device_name} Notification sound
platform: template
id: notification_sound
entity_category: config
optimistic: true
restore_mode: RESTORE_DEFAULT_OFF
##### PHYSICAL SWITCH 1 #####
- name: ${device_name} Relay 1
platform: gpio
id: relay_1
pin:
number: 22
restore_mode: RESTORE_DEFAULT_OFF
on_turn_on:
then:
- script.execute:
id: refresh_relays
on_turn_off:
then:
- script.execute:
id: refresh_relays
##### PHYSICAL SWITCH 2 ######
- name: ${device_name} Relay 2
platform: gpio
id: relay_2
pin:
number: 19
restore_mode: RESTORE_DEFAULT_OFF
on_turn_on:
then:
- script.execute:
id: refresh_relays
on_turn_off:
then:
- script.execute:
id: refresh_relays
##### DISPLAY ALWAYS ON #####
- name: ${device_name} Screen Power
platform: gpio
id: screen_power
entity_category: config
pin:
number: 4
inverted: true
restore_mode: ALWAYS_ON
internal: true
##### Relay Local control #####
- name: ${device_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: ${device_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 - NUMBER CONFIGURATION #####
number:
##### SCREEN BRIGHTNESS #####
- name: ${device_name} Display Brightness
id: display_brightness
platform: template
entity_category: config
unit_of_measurement: '%'
min_value: 1
max_value: 100
step: 1
restore_value: true
optimistic: true
set_action:
then:
- lambda: |-
id(display_brightness_global) = int(x);
id(disp1).send_command_printf("brightness=%i", int(x));
id(disp1).send_command_printf("settings.brightslider.val=%i", int(x));
if (id(current_page).state != "screensaver")
{
id(disp1).set_backlight_brightness(x/100);
id(timer_dim).execute(id(current_page).state.c_str(), int(id(timeout_dim).state));
id(timer_sleep).execute(id(current_page).state.c_str(), int(id(timeout_sleep).state));
if (id(current_page).state == "settings") id(disp1).set_component_text_printf("bright_text", "%i%%", int(x));
}
##### SCREEN BRIGHTNESS DIMMED DOWN #####
- name: ${device_name} Display Brightness Dimdown
id: display_dim_brightness
platform: template
entity_category: config
unit_of_measurement: '%'
min_value: 1
max_value: 100
step: 1
restore_value: true
optimistic: true
set_action:
then:
- lambda: |-
id(display_dim_brightness_global) = int(x);
id(disp1).send_command_printf("brightness_dim=%i", int(x));
id(disp1).send_command_printf("settings.dimslider.val=%i", int(x));
if (id(current_page).state != "screensaver" and id(is_dim_brightness))
{
id(disp1).set_backlight_brightness(x/100);
id(timer_sleep).execute(id(current_page).state.c_str(), int(id(timeout_sleep).state));
if (id(current_page).state == "settings") id(disp1).set_component_text_printf("dim_text", "%i%%", int(x));
}
##### Temperature Correction #####
- name: ${device_name} Temperature Correction
platform: template
id: temperature_correction
entity_category: config
unit_of_measurement: '°C'
initial_value: 0
min_value: -10
max_value: 10
step: 0.1
restore_value: true
internal: false
optimistic: true
set_action:
- logger.log: Temperature correction changed.
- delay: 1s
- lambda: id(temp_nspanel).publish_state(id(temp_nspanel).raw_state);
##### Timers settings #####
- name: ${device_name} Timeout Page
platform: template
id: timeout_page
entity_category: config
min_value: 0
max_value: 300
initial_value: 15
step: 1
restore_value: true
optimistic: true
icon: mdi:timer
unit_of_measurement: "s"
set_action:
- lambda: id(timer_page).execute(id(current_page).state.c_str(), int(x));
- name: ${device_name} Timeout Dimming
platform: template
id: timeout_dim
entity_category: config
min_value: 0
max_value: 300
initial_value: 30
step: 1
restore_value: true
optimistic: true
icon: mdi:timer
unit_of_measurement: "s"
set_action:
- lambda: id(timer_dim).execute(id(current_page).state.c_str(), int(x));
- name: ${device_name} Timeout Sleep
platform: template
id: timeout_sleep
entity_category: config
min_value: 0
max_value: 300
initial_value: 60
step: 1
restore_value: true
optimistic: true
icon: mdi:timer
unit_of_measurement: "s"
set_action:
- lambda: |-
id(timer_dim).execute(id(current_page).state.c_str(), int(id(timeout_dim).state));
id(timer_sleep).execute(id(current_page).state.c_str(), int(x));
##### START - SELECT CONFIGURATION #####
select:
- name: ${device_name} Wake-up page
id: wakeup_page_name
platform: template
options:
- home
- buttonpage01
- buttonpage02
- buttonpage03
- buttonpage04
- entitypage01
- entitypage02
- entitypage03
- entitypage04
- qrcode
- alarm
initial_option: home
optimistic: true
restore_value: true
internal: false
entity_category: config
icon: mdi:page-next-outline
set_action:
- script.execute: update_page_screensaver
##### START - DISPLAY START CONFIGURATION #####
display:
- id: disp1
platform: nextion
uart_id: tf_uart
#tft_url: ${nextion_update_url}
on_page: # I couldn't make this trigger to work, so used text_sensor nspanelevent and localevent instead
then:
- lambda: ESP_LOGW("display.disp1.on_page", "NEXTION PAGE CHANGED");
on_setup:
then:
- lambda: |-
ESP_LOGV("display.disp1.on_setup", "Nextion starting");
id(disp1).goto_page("boot");
id(disp1).send_command_printf("bkcmd=3");
id(disp1).set_component_text_printf("boot.esph_version", "%s", "${version}"); // ### esphome-version ###
id(disp1).show_component("bt_reboot");
id(timer_reset_all).execute("boot");
ESP_LOGV("display.disp1.on_setup", "Wait for API");
- wait_until:
api.connected
- lambda: |-
ESP_LOGV("display.disp1.on_setup", "Publish IP address");
auto ip = network::get_ip_address();
id(disp1).set_component_text_printf("boot.ip_addr", "%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]);
ESP_LOGV("display.disp1.on_setup", "Report to Home Assistant");
auto ha_event = new esphome::api::CustomAPIDevice();
ha_event->fire_homeassistant_event("esphome.nspanel_ha_blueprint",
{
{"type", "boot"},
{"step", "start"}
});
- delay: 1s
- lambda: |-
// Set dimming values
id(display_brightness).publish_state(id(display_brightness_global));
id(display_dim_brightness).publish_state(id(display_dim_brightness_global));
id(disp1).send_command_printf("brightness=%i", id(display_brightness_global));
id(disp1).send_command_printf("settings.brightslider.val=%i", id(display_brightness_global));
id(disp1).send_command_printf("brightness_dim=%i", id(display_dim_brightness_global));
id(disp1).send_command_printf("settings.dimslider.val=%i", id(display_dim_brightness_global));
id(nextion_init).publish_state(true);
ESP_LOGV("display.disp1.on_setup", "Report to Home Assistant");
auto ha_event = new esphome::api::CustomAPIDevice();
ha_event->fire_homeassistant_event("esphome.nspanel_ha_blueprint",
{
{"type", "boot"},
{"step", "nextion_init"}
});
id(home_relay1_icon) = "\uE3A5";
id(home_relay1_icon) = "\uE3A8";
id(timer_reset_all).execute("boot");
- *notification_clear
- logger.log: "Nextion start - Done!"
### Scripts ######
script:
###### Timers ######
## Global timer reset - Triggered with a touch on the screen
- id: timer_reset_all
mode: restart
parameters:
page: string
then:
- lambda: |-
ESP_LOGV("script.timer_reset_all", "Reset timers");
id(timer_page).execute(page.c_str(), int(id(timeout_page).state));
id(timer_dim).execute(page.c_str(), int(id(timeout_dim).state));
id(timer_sleep).execute(page.c_str(), int(id(timeout_sleep).state));
- id: timer_page # Handle the fallback to home page after a timeout
mode: restart
parameters:
page: string
timeout: int
then:
- lambda: |-
ESP_LOGV("script.timer_page", "Reset timer: %is", timeout);
- if:
condition:
- lambda: |-
return (page != "screensaver" and page != "boot" and page != "home" and timeout >= 1);
then:
- delay: !lambda return (timeout *1000);
- lambda: |-
ESP_LOGV("script.timer_page", "Timed out on page: %s", id(current_page).state.c_str());
if (id(current_page).state != "screensaver" and id(current_page).state != "boot" and id(current_page).state != "home" and timeout >= 1)
{
ESP_LOGD("script.timer_page", "Fallback to page Home");
id(disp1).goto_page("home");
}
- id: timer_dim # Handle the brightness dimming after a timeout
mode: restart
parameters:
page: string
timeout: int
then:
- lambda: |-
ESP_LOGV("script.timer_dim", "Reset timer: %is", timeout);
if (id(is_dim_brightness))
{
if (page != "screensaver" and page != "boot" and page != "blank-screensaver")
{
ESP_LOGD("script.timer_dim", "Waking up on page: %s", page.c_str());
id(disp1).send_command_printf("wakeup_timer.en=1");
}
id(is_dim_brightness) = false;
}
- if:
condition:
- lambda: !lambda return (timeout >= 1);
then:
- delay: !lambda return (timeout *1000);
- lambda: |-
if (id(current_page).state != "screensaver" and id(current_page).state != "blank-screensaver" and id(current_page).state != "boot" and timeout >= 1)
{
ESP_LOGD("script.timer_dim", "Dimming the display to %i%%", id(display_dim_brightness_global));
id(disp1).send_command_printf("dim=%i", id(display_dim_brightness_global));
id(is_dim_brightness) = true;
}
- id: timer_sleep # Handle the sleep (go to screensaver page) after a timeout
mode: restart
parameters:
page: string
timeout: int
then:
- lambda: |-
ESP_LOGV("script.timer_sleep", "Reset timer: %is", timeout);
- if:
condition:
- lambda: |-
return (timeout >= 1 and id(current_page).state != "screensaver" and id(current_page).state != "boot");
then:
- delay: !lambda return (timeout *1000);
- lambda: |-
if (id(current_page).state != "screensaver" and id(current_page).state != "boot" and timeout >= 1)
{
ESP_LOGD("script.timer_sleep", "Going to sleep from page %s", id(current_page).state.c_str());
id(disp1).goto_page("screensaver");
id(is_dim_brightness) = true;
}
- id: set_climate
mode: restart
parameters:
current_temp: float
target_temp: float
temp_step: int
total_steps: int
temp_offset: int
climate_icon: string
embedded_climate: bool
then:
- if:
condition:
- text_sensor.state: # Is climate page visible?
id: current_page
state: 'climate'
then:
- lambda: |-
id(addon_climate_set_climate).execute(embedded_climate);
id(disp1).send_command_printf("climateslider.maxval=%i", total_steps);
id(disp1).set_component_value("temp_offset", temp_offset);
id(disp1).set_component_value("temp_step", temp_step);
id(disp1).set_component_text_printf("current_temp", "%.1f°", current_temp);
id(disp1).show_component("current_temp");
id(disp1).show_component("current_icon");
if (target_temp > -999)
{
float slider_val = round(((10*target_temp) - temp_offset) / temp_step);
id(disp1).set_component_value("climateslider", slider_val);
id(disp1).set_component_text_printf("target_temp", "%.1f°", target_temp);
id(disp1).set_component_text_printf("target_icon", "%s", climate_icon.c_str());
id(disp1).show_component("target_icon");
id(disp1).show_component("target_temp");
id(disp1).show_component("climateslider");
id(disp1).show_component("decrease_temp");
id(disp1).show_component("increase_temp");
}
else
{
id(disp1).hide_component("target_icon");
id(disp1).hide_component("target_temp");
id(disp1).hide_component("climateslider");
id(disp1).hide_component("decrease_temp");
id(disp1).hide_component("increase_temp");
}
id(disp1).set_component_value("embedded", (embedded_climate) ? 1 : 0);
- id: refresh_datetime
mode: restart
then:
- lambda: |-
std::string time_format_str = id(mui_time_format);
if (time_format_str.find("%p") != std::string::npos)
{
std::string meridiem_text = id(time_provider).now().strftime("%p");
id(disp1).set_component_text_printf("home.meridiem", "%s", meridiem_text.c_str());
}
else { id(disp1).set_component_text_printf("home.meridiem", " "); }
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 time_text = id(time_provider).now().strftime(time_format_str);
id(disp1).set_component_text_printf("home.time", "%s", time_text.c_str());
- id: refresh_relays
mode: restart
then:
- lambda: |-
// Chips - Relays
if (id(relay_1).state) id(disp1).set_component_text_printf("home.icon_top_01", "%s", id(home_relay1_icon).c_str());
else id(disp1).set_component_text_printf("icon_top_01", "\uFFFF");
if (id(relay_2).state) id(disp1).set_component_text_printf("home.icon_top_02", "%s", id(home_relay2_icon).c_str());
else id(disp1).set_component_text_printf("home.icon_top_02", "\uFFFF");
// Hardware buttons - Fallback mode
if (id(relay_1).state and id(relay1_local).state) id(disp1).send_command_printf("home.left_bt_pic.val=%i", (id(relay_1).state) ? 1 : 0);
if (id(relay_2).state and id(relay2_local).state) id(disp1).send_command_printf("home.right_bt_pic.val=%i", (id(relay_2).state) ? 1 : 0);
- id: refresh_wifi_icon
mode: restart
then:
- if:
condition:
- binary_sensor.is_on: nextion_init
then:
# Update Wi-Fi icon
- if:
condition:
wifi.connected:
then:
- if:
condition:
api.connected:
then:
- lambda: id(disp1).send_command_printf("api=1");
- lambda: id(disp1).set_component_text_printf("home.wifi_icon", "%s", "\uE5A8");
- lambda: id(disp1).set_component_font_color("home.wifi_icon", 33808);
else:
- lambda: id(disp1).send_command_printf("api=0");
- lambda: id(disp1).set_component_text_printf("home.wifi_icon", "%s", "\uF256");
- lambda: id(disp1).set_component_font_color("home.wifi_icon", 63488);
else:
- lambda: id(disp1).send_command_printf("api=0");
- lambda: id(disp1).set_component_text_printf("home.wifi_icon", "%s", "\uE5A9");
- lambda: id(disp1).set_component_font_color("home.wifi_icon", 63488);
- id: update_page_home
mode: restart
then:
- if:
condition:
- text_sensor.state: # Is home page visible?
id: current_page
state: 'home'
then:
- script.execute: refresh_relays
- script.execute: refresh_wifi_icon
- lambda: |-
id(disp1).send_command_printf("is_notification=%i", (id(notification_text).state.empty() and id(notification_label).state.empty()) ? 0 : 1);
id(set_component_color).execute("home.bt_notific", id(notification_unread).state ? id(home_notify_icon_color_unread) : id(home_notify_icon_color_normal), {});
- script.execute: refresh_datetime
- script.execute: addon_climate_update_page_home
- 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);
}
id(api_server).send_homeassistant_service_call(resp);
}
- id: service_call_climate
mode: restart
parameters:
entity: string
key: string
value: string
embedded: bool
then:
- lambda: |-
if (embedded)
id(addon_climate_service_call).execute(key.c_str(), value.c_str());
else if (key == "set_temperature")
id(ha_call_service).execute("climate.set_temperature", "temperature", to_string(stof(value) / 10), entity.c_str());
else if (key == "hvac_mode")
id(ha_call_service).execute("climate.set_hvac_mode", key.c_str(), value.c_str(), entity.c_str());
- id: ha_call_service
mode: restart
parameters:
service: string
key: string
value: string
entity: string
then:
- lambda: |-
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}
});
}
- id: ha_button
mode: parallel
parameters:
page: string
component: string
command: string
then:
- lambda: |-
id(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: update_alarm_icon
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;
}
id(disp1).set_component_text_printf(component.c_str(), alarm_icon.c_str());
id(disp1).set_component_font_color(component.c_str(), alarm_color);
- id: update_climate_icon
mode: restart
parameters:
component: string
action: int
mode: int
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
id(disp1).set_component_text_printf(component.c_str(), "%s", "\uFFFF"); // (E424) Don't show icon when off
id(disp1).set_component_font_color(component.c_str(), 35921); // grey (off)
break;
case 1: //CLIMATE_MODE_HEAT_COOL
id(disp1).set_component_text_printf(component.c_str(), "%s", "\uE069"); // mdi:autorenew
id(disp1).set_component_font_color(component.c_str(), 35921); // grey (off)
break;
case 2: //CLIMATE_MODE_COOL
id(disp1).set_component_text_printf(component.c_str(), "%s", "\uE716"); // mdi:snowflake
id(disp1).set_component_font_color(component.c_str(), 35921); // grey (off)
break;
case 3: //CLIMATE_MODE_HEAT
id(disp1).set_component_text_printf(component.c_str(), "%s", "\uE237"); // mdi:fire
id(disp1).set_component_font_color(component.c_str(), 35921); // grey (off)
break;
case 4: //CLIMATE_MODE_FAN_ONLY
id(disp1).set_component_text_printf(component.c_str(), "%s", "\uE20F"); // mdi:fan
id(disp1).set_component_font_color(component.c_str(), 35921); // grey (off)
break;
case 5: //CLIMATE_MODE_DRY
id(disp1).set_component_text_printf(component.c_str(), "%s", "\uE58D"); // mdi:water-percent
id(disp1).set_component_font_color(component.c_str(), 35921); // grey (off)
break;
case 6: //CLIMATE_MODE_AUTO
id(disp1).set_component_text_printf(component.c_str(), "%s", "\uEE8D"); // mdi:calendar-sync
id(disp1).set_component_font_color(component.c_str(), 35921); // grey (off)
break;
}
break;
case 2: //CLIMATE_ACTION_COOLING
id(disp1).set_component_text_printf(component.c_str(), "%s", "\uE716"); // mdi:snowflake
id(disp1).set_component_font_color(component.c_str(), 1055); // blue
break;
case 3: //CLIMATE_ACTION_HEATING
id(disp1).set_component_text_printf(component.c_str(), "%s", "\uE237"); // mdi:fire
id(disp1).set_component_font_color(component.c_str(), 64164); // deep-orange
break;
case 4: //CLIMATE_ACTION_IDLE
id(disp1).set_component_text_printf(component.c_str(), "%s", "\uE50E"); // mdi:thermometer
id(disp1).set_component_font_color(component.c_str(), 35921); // grey (off)
break;
case 5: //CLIMATE_ACTION_DRYING
id(disp1).set_component_text_printf(component.c_str(), "%s", "\uE58D"); // mdi:water-percent
id(disp1).set_component_font_color(component.c_str(), 64704); // orange
break;
case 6: //CLIMATE_ACTION_FAN
id(disp1).set_component_text_printf(component.c_str(), "%s", "\uE20F"); // mdi:fan
id(disp1).set_component_font_color(component.c_str(), 1530); // cyan
break;
}
- id: set_component_color
mode: queued
parameters:
component: string
foreground: int[]
background: int[]
then:
- lambda: |-
int fg565 = -1;
int bg565 = -1;
// Foreground
if (foreground.size() == 3) fg565 = ((foreground[0] & 0b11111000) << 8) | ((foreground[1] & 0b11111100) << 3) | (foreground[2] >> 3);
else if (foreground.size() == 1) fg565 = foreground[0];
if (fg565 >= 0) id(disp1).set_component_font_color(component.c_str(), fg565);
// Background
if (background.size() == 3) bg565 = ((background[0] & 0b11111000) << 8) | ((background[1] & 0b11111100) << 3) | (background[2] >> 3);
else if (background.size() == 1) bg565 = background[0];
if (bg565 >= 0) id(disp1).set_component_background_color(component.c_str(), bg565);
- id: display_wrapped_text
mode: queued
parameters:
component: string
text: string
line_length_limit: int
then:
- lambda: |-
int startPos = 0;
int endPos = 0;
std::string wrappedText = "";
while (startPos < text.length()) {
while (text[startPos] == ' ' and startPos < text.length()) { startPos++; }
int endPos = startPos + line_length_limit;
if (endPos >= text.length()) endPos = text.length();
else
{
while (endPos > startPos && text[endPos] != ' ') { endPos--; }
if (endPos == startPos) endPos = startPos + line_length_limit; // Handle case of long word
}
wrappedText += text.substr(startPos, endPos-startPos);
if (endPos < text.length())
{
while (text[endPos] == ' ') { endPos--; }
if (endPos >= startPos) wrappedText += "\\r";
}
startPos = endPos + 1; // Skip the space
while (text[startPos] == ' ' and startPos < text.length()) { startPos++; }
}
id(disp1).set_component_text_printf(component.c_str(), "%s", wrappedText.c_str());
- id: display_embedded_temp
mode: restart
then:
- if:
condition:
- or:
- lambda: return id(embedded_indoor_temp);
- not:
- api.connected:
- not:
- wifi.connected:
then:
- lambda: |-
if (id(temp_unit_fahrenheit)) id(disp1).set_component_text_printf("home.current_temp", "%.0f°F", ((id(temp_nspanel).state * 9.0 / 5.0) + 32.0)); // °F = (°C × 9/5) + 32
else id(disp1).set_component_text_printf("home.current_temp", "%.1f°C", id(temp_nspanel).state);
- 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}", id(version_tft).c_str()) and compareVersions("${version}", id(version_blueprint).c_str()));
#- lambda: !lambda 'return (id(version_tft) == "${version}");'
#- lambda: !lambda 'return (id(version_blueprint) == "${version}");'
timeout: 60s
- 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);
};
ESP_LOGD("script.check_versions", "ESPHome version: ${version}");
ESP_LOGD("script.check_versions", "TFT version: %s", id(version_tft).c_str());
if (not compareVersions("${version}", id(version_tft).c_str())) ESP_LOGE("script.check_versions", "TFT version mismatch!");
ESP_LOGD("script.check_versions", "Blueprint version: %s", id(version_blueprint).c_str());
if (not compareVersions("${version}", id(version_blueprint).c_str())) ESP_LOGE("script.check_versions", "Blueprint version mismatch!");
auto ha_event = new esphome::api::CustomAPIDevice();
ha_event->fire_homeassistant_event("esphome.nspanel_ha_blueprint",
{
{"type", "version"},
{"tft", id(version_tft).c_str()},
{"esphome", "${version}"},
{"blueprint", id(version_blueprint).c_str()}
});
- id: update_page_screensaver
mode: restart
then:
- if:
condition:
text_sensor.state:
id: current_page
state: screensaver
then:
- lambda: |-
int wakeup_page_id = 0;
if (id(wakeup_page_name).state == "buttonpage01") wakeup_page_id = 12;
else if (id(wakeup_page_name).state == "buttonpage02") wakeup_page_id = 13;
else if (id(wakeup_page_name).state == "buttonpage03") wakeup_page_id = 14;
else if (id(wakeup_page_name).state == "buttonpage04") wakeup_page_id = 15;
else if (id(wakeup_page_name).state == "entitypage01") wakeup_page_id = 18;
else if (id(wakeup_page_name).state == "entitypage02") wakeup_page_id = 19;
else if (id(wakeup_page_name).state == "entitypage03") wakeup_page_id = 20;
else if (id(wakeup_page_name).state == "entitypage04") wakeup_page_id = 21;
else if (id(wakeup_page_name).state == "qrcode") wakeup_page_id = 17;
else if (id(wakeup_page_name).state == "alarm") wakeup_page_id = 23;
id(disp1).set_component_value("orign", wakeup_page_id);
- id: upload_tft
mode: single
parameters:
url: string
then:
- lambda: |-
ESP_LOGD("script.upload_tft", "Starting...");
#ifdef ARDUINO
std::vector<uint8_t> buffer_;
bool is_updating_ = false;
uint8_t *transfer_buffer_{nullptr};
size_t transfer_buffer_size_;
bool upload_first_chunk_sent_ = false;
int content_length_ = 0;
int tft_size_ = 0;
auto send_nextion_command = [](const std::string &command) -> bool
{
ESP_LOGD("script.upload_tft.send_nextion_command", "Sending: %s", command.c_str());
id(tf_uart).write_str(command.c_str());
const uint8_t to_send[3] = {0xFF, 0xFF, 0xFF};
id(tf_uart).write_array(to_send, sizeof(to_send));
return true;
};
auto recv_ret_string_ = [](std::string &response, uint32_t timeout, bool recv_flag) -> uint16_t
{
uint16_t ret;
uint8_t c = 0;
uint8_t nr_of_ff_bytes = 0;
uint64_t start;
bool exit_flag = false;
bool ff_flag = false;
start = millis();
while ((timeout == 0 && id(tf_uart).available()) || millis() - start <= timeout)
{
if (!id(tf_uart).available())
{
App.feed_wdt();
continue;
}
id(tf_uart).read_byte(&c);
if (c == 0xFF)
{
nr_of_ff_bytes++;
}
else
{
nr_of_ff_bytes = 0;
ff_flag = false;
}
if (nr_of_ff_bytes >= 3)
ff_flag = true;
response += (char) c;
if (recv_flag)
{
if (response.find(0x05) != std::string::npos)
{
exit_flag = true;
}
}
App.feed_wdt();
delay(2);
if (exit_flag || ff_flag)
{
break;
}
}
if (ff_flag)
response = response.substr(0, response.length() - 3); // Remove last 3 0xFF
ret = response.length();
return ret;
};
auto upload_end_ = [&](bool retry) -> bool
{
ESP_LOGD("script.upload_tft.upload_end_", "Restarting Nextion");
send_nextion_command("rest");
if (is_updating_) is_updating_ = not retry;
if (retry) ESP_LOGD("script.upload_tft.upload_end_", "Nextion TFT upload will try again");
return not retry;
};
auto upload_by_chunks_ = [&](HTTPClient *http, const std::string &url, int range_start) -> int
{
int range_end;
if (range_start == 0 && transfer_buffer_size_ > 16384) { // Start small at the first run in case of a big skip
range_end = 16384 - 1;
} else {
range_end = range_start + transfer_buffer_size_ - 1;
}
if (range_end > tft_size_)
range_end = tft_size_;
char range_header[64];
sprintf(range_header, "bytes=%d-%d", range_start, range_end);
ESP_LOGD("script.upload_tft.upload_by_chunks", "Requesting range: %s", range_header);
int tries = 1;
int code;
bool begin_status;
while (tries <= 10) {
begin_status = http->begin(url.c_str());
++tries;
if (!begin_status) {
ESP_LOGD("script.upload_tft.upload_by_chunks", "Connection failed");
delay(1000);
continue;
};
http->addHeader("Range", range_header);
code = http->GET();
if (code == 200 || code == 206) {
break;
}
ESP_LOGW("script.upload_tft.upload_by_chunks", "HTTP Request failed; URL: %s; Error: %s, retries(%d/10)", url.c_str(),
HTTPClient::errorToString(code).c_str(), tries);
http->end();
delay(1000);
}
if (tries > 10) {
return -1;
}
std::string recv_string;
size_t size;
int fetched = 0;
int range = range_end - range_start;
int write_len;
// fetch next segment from HTTP stream
while (fetched < range) {
size = http->getStreamPtr()->available();
if (!size) {
App.feed_wdt();
delay(2);
continue;
}
int c = http->getStreamPtr()->readBytes(
&transfer_buffer_[fetched], ((size > transfer_buffer_size_) ? transfer_buffer_size_ : size));
fetched += c;
}
http->end();
ESP_LOGD("script.upload_tft.upload_by_chunks", "Fetched %d bytes", fetched);
// upload fetched segments to the display in 4KB chunks
for (int i = 0; i < range; i += 4096) {
App.feed_wdt();
write_len = content_length_ < 4096 ? content_length_ : 4096;
id(tf_uart).write_array(&transfer_buffer_[i], write_len);
content_length_ -= write_len;
ESP_LOGD("script.upload_tft.upload_by_chunks", "Uploaded %0.1f %%, remaining %d bytes",
100.0 * (tft_size_ - content_length_) / tft_size_,
content_length_);
if (!upload_first_chunk_sent_) {
upload_first_chunk_sent_ = true;
delay(500);
}
recv_ret_string_(recv_string, 5000, true);
if (recv_string[0] != 0x05) { // 0x05 == "ok"
ESP_LOGD("script.upload_tft.upload_by_chunks", "recv_string [%s]",
format_hex_pretty(reinterpret_cast<const uint8_t *>(recv_string.data()), recv_string.size()).c_str());
}
// handle partial upload request
if (recv_string[0] == 0x08 && recv_string.size() == 5) {
uint32_t result = 0;
for (int j = 0; j < 4; ++j) {
result += static_cast<uint8_t>(recv_string[j + 1]) << (8 * j);
}
if (result > 0) {
ESP_LOGD("script.upload_tft.upload_by_chunks", "Nextion reported new range %d", result);
content_length_ = tft_size_ - result;
return result;
}
}
recv_string.clear();
}
return range_end + 1;
};
auto upload_tft = [&](const std::string &url, unsigned int update_baud_rate_) -> bool
{
ESP_LOGD("script.upload_tft.upload_tft", "Nextion TFT upload requested");
ESP_LOGD("script.upload_tft.upload_tft", "url: %s", url.c_str());
ESP_LOGD("script.upload_tft.upload_tft", "baud_rate: %i", update_baud_rate_);
if (is_updating_) {
ESP_LOGD("script.upload_tft.upload_tft", "Currently updating");
return upload_end_(false);
}
if (!network::is_connected()) {
ESP_LOGD("script.upload_tft.upload_tft", "Network is not connected");
return upload_end_(false);
}
send_nextion_command("DRAKJHSUYDGBNCJHGJKSHBDN");
send_nextion_command("recmod=0");
send_nextion_command("recmod=0");
send_nextion_command("connect");
is_updating_ = true;
HTTPClient http;
http.setTimeout(15000); // Yes 15 seconds.... Helps 8266s along
http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
bool begin_status = http.begin(url.c_str());
if (!begin_status) {
is_updating_ = false;
ESP_LOGD("script.upload_tft.upload_tft", "Connection failed");
ExternalRAMAllocator<uint8_t> allocator(ExternalRAMAllocator<uint8_t>::ALLOW_FAILURE);
allocator.deallocate(transfer_buffer_, transfer_buffer_size_);
return upload_end_(true);
} else {
ESP_LOGD("script.upload_tft.upload_tft", "Connected");
}
http.addHeader("Range", "bytes=0-255");
const char *header_names[] = {"Content-Range"};
http.collectHeaders(header_names, 1);
ESP_LOGD("script.upload_tft.upload_tft", "Requesting URL: %s", url.c_str());
http.setReuse(true);
// try up to 5 times. DNS sometimes needs a second try or so
int tries = 1;
int code = http.GET();
delay(100);
while (code != 200 && code != 206 && tries <= 5) {
ESP_LOGW("script.upload_tft.upload_tft", "HTTP Request failed; URL: %s; Error: %s, retrying (%d/5)", url.c_str(),
HTTPClient::errorToString(code).c_str(), tries);
delay(250);
code = http.GET();
++tries;
}
if ((code != 200 && code != 206) || tries > 5) {
return upload_end_(true);
}
String content_range_string = http.header("Content-Range");
content_range_string.remove(0, 12);
content_length_ = content_range_string.toInt();
tft_size_ = content_length_;
http.end();
if (content_length_ < 4096) {
ESP_LOGE("script.upload_tft.upload_tft", "Failed to get file size");
return upload_end_(true);
}
ESP_LOGD("script.upload_tft.upload_tft", "Updating Nextion");
// The Nextion will ignore the update command if it is sleeping
char command[128];
// Tells the Nextion the content length of the tft file and baud rate it will be sent at
// Once the Nextion accepts the command it will wait until the file is successfully uploaded
// If it fails for any reason a power cycle of the display will be needed
sprintf(command, "whmi-wris %d,%d,1", content_length_, update_baud_rate_);
// Clear serial receive buffer
uint8_t d;
while (id(tf_uart).available()) {
id(tf_uart).read_byte(&d);
};
send_nextion_command(command);
if (update_baud_rate_ != id(tf_uart).get_baud_rate())
{
id(tf_uart).set_baud_rate(update_baud_rate_);
id(tf_uart).setup();
}
std::string response;
ESP_LOGD("script.upload_tft.upload_tft", "Waiting for upgrade response");
recv_ret_string_(response, 2000, true); // This can take some time to return
// The Nextion display will, if it's ready to accept data, send a 0x05 byte.
ESP_LOGD("script.upload_tft.upload_tft", "Upgrade response is [%s]",
format_hex_pretty(reinterpret_cast<const uint8_t *>(response.data()), response.size()).c_str());
if (response.find(0x05) != std::string::npos) {
ESP_LOGD("script.upload_tft.upload_tft", "Preparation for tft update done");
} else {
ESP_LOGD("script.upload_tft.upload_tft", "Preparation for tft update failed %d \"%s\"", response[0], response.c_str());
return upload_end_(true);
}
// Nextion wants 4096 bytes at a time. Make chunk_size a multiple of 4096
uint32_t chunk_size = 8192;
if (ESP.getFreeHeap() > 81920) { // Ensure some FreeHeap to other things and limit chunk size
chunk_size = ESP.getFreeHeap() - 65536;
chunk_size = int(chunk_size / 4096) * 4096;
chunk_size = chunk_size > ${upload_tft_chunk_size_max} ? ${upload_tft_chunk_size_max} : chunk_size;
} else if (ESP.getFreeHeap() < 32768) {
chunk_size = 4096;
}
if (transfer_buffer_ == nullptr) {
ExternalRAMAllocator<uint8_t> allocator(ExternalRAMAllocator<uint8_t>::ALLOW_FAILURE);
ESP_LOGD("script.upload_tft.upload_tft", "Allocating buffer size %d, Heap size is %u", chunk_size, ESP.getFreeHeap());
transfer_buffer_ = allocator.allocate(chunk_size);
if (transfer_buffer_ == nullptr) { // Try a smaller size
ESP_LOGD("script.upload_tft.upload_tft", "Could not allocate buffer size: %d trying 4096 instead", chunk_size);
chunk_size = 4096;
ESP_LOGD("script.upload_tft.upload_tft", "Allocating %d buffer", chunk_size);
transfer_buffer_ = allocator.allocate(chunk_size);
if (!transfer_buffer_)
{
return upload_end_(true);
}
}
transfer_buffer_size_ = chunk_size;
}
ESP_LOGD("script.upload_tft.upload_tft", "Updating tft from \"%s\" with a file size of %d using %zu chunksize, Heap Size %d",
url.c_str(), content_length_, transfer_buffer_size_, ESP.getFreeHeap());
int result = 0;
while (content_length_ > 0) {
result = upload_by_chunks_(&http, url, result);
if (result < 0) {
ESP_LOGD("script.upload_tft.upload_tft", "Error updating Nextion!");
return upload_end_(true);
}
App.feed_wdt();
ESP_LOGD("script.upload_tft.upload_tft", "Heap Size %d, Bytes left %d", ESP.getFreeHeap(), content_length_);
}
is_updating_ = false;
ESP_LOGD("script.upload_tft.upload_tft", "Successfully updated Nextion!");
return upload_end_(false);
};
if (upload_tft(url, id(tf_uart).get_baud_rate())) id(restart_nspanel).press();
ESP_LOGD("script.upload_tft", "Turn off Nextion");
id(screen_power).turn_off();
delay(1500);
ESP_LOGD("script.upload_tft", "Turn on Nextion");
id(screen_power).turn_on();
delay(1500);
unsigned int new_baud_rate;
if (id(tf_uart).get_baud_rate() == 115200) new_baud_rate = 921600; else new_baud_rate = 115200;
ESP_LOGD("script.upload_tft", "Trying again at %i bps", new_baud_rate);
if (upload_tft(url, new_baud_rate)) id(restart_nspanel).press();
#elif defined(ESP_PLATFORM) // esp-idf
// ESP-IDF-specific code
#endif
ESP_LOGE("script.upload_tft", "TFT upload failed.");
ESP_LOGD("script.upload_tft", "Turn off Nextion");
id(screen_power).turn_off();
delay(1500);
//ESP_LOGD("script.upload_tft", "Turn on Nextion");
//id(screen_power).turn_on();
//delay(1500);
//ESP_LOGW("script.upload_tft", "Trying Nextion standard upload");
//id(disp1)->set_tft_url(url.c_str());
//id(disp1)->upload_tft();
ESP_LOGD("script.upload_tft", "Restarting esphome");
delay(1500);
id(restart_nspanel).press();
ESP_LOGD("script.upload_tft", "Finished!");
##### ADD-ONS ############################################################
##### Add-on - Climate #####
- id: addon_climate_service_call
mode: restart
parameters:
key: string
value: string
then:
# Reserved for Add-on Climate
- lambda: |-
ESP_LOGV("script.addon_climate_service_call", "Check for addon_climate");
- id: addon_climate_update_page_home
mode: restart
then:
# Reserved for Add-on Climate
- lambda: |-
ESP_LOGV("script.addon_climate_update_page_home", "Check for addon_climate");
- id: addon_climate_set_climate
mode: restart
parameters:
embedded_climate: bool
then:
# Reserved for Add-on Climate
- lambda: |-
ESP_LOGV("script.addon_climate_set_climate", "Check for addon_climate");
ESP_LOGV("script.addon_climate_set_climate", "embedded_climate: %i", (embedded_climate) ? 1 : 0);
- id: addon_climate_update_page_climate
mode: restart
then:
# Reserved for Add-on Climate
- lambda: |-
ESP_LOGV("script.addon_climate_update_page_climate", "Check for addon_climate");
- id: addon_climate_set_climate_friendly_name
mode: restart
parameters:
friendly_name: string
then:
# Reserved for Add-on Climate
- lambda: |-
ESP_LOGV("script.addon_climate_set_climate_friendly_name", "Check for addon_climate");
ESP_LOGV("script.addon_climate_set_climate_friendly_name", "friendly_name: %s", friendly_name.c_str());