Splitting addon climate
This commit is contained in:
@@ -1,4 +1,3 @@
|
||||
packages:
|
||||
core_package: !include nspanel_esphome_core.yaml
|
||||
upload_tft_package: !include nspanel_esphome_addon_upload_tft.yaml
|
||||
|
||||
|
||||
@@ -6,15 +6,10 @@
|
||||
substitutions:
|
||||
### Local thermostat defaults ###
|
||||
# https://esphome.io/components/climate/thermostat.html
|
||||
temp_units: "°C"
|
||||
cooler_relay: "0" # Select 1 for "Relay 1", 2 for "Relay 2" or "0" to a dummy switch/disabled
|
||||
min_off_time: "300"
|
||||
min_run_time: "300"
|
||||
min_idle_time: "30"
|
||||
# https://esphome.io/components/climate/index.html#base-climate-configuration
|
||||
temp_min: "15"
|
||||
temp_max: "45"
|
||||
temp_step: "0.5"
|
||||
|
||||
##### DO NOT CHANGE THIS #####
|
||||
addon_climate_cool: "true"
|
||||
@@ -52,102 +47,5 @@ climate:
|
||||
- script.execute: addon_climate_update_page_home
|
||||
- logger.log: Climate state changed - End
|
||||
|
||||
###### All the code bellow this point is shared between addon_climate_cool and addon_climate_heat #####
|
||||
|
||||
globals:
|
||||
##### Is embedded thermostat visible on climate page? #####
|
||||
- id: is_addon_climate_visible
|
||||
type: bool
|
||||
restore_value: false
|
||||
initial_value: 'false'
|
||||
##### Embeded climate friendly name #####
|
||||
- id: addon_climate_friendly_name
|
||||
type: std::string
|
||||
restore_value: false
|
||||
initial_value: '"${device_name} Thermostat"'
|
||||
|
||||
script:
|
||||
- id: !extend addon_climate_update_page_home
|
||||
mode: restart
|
||||
then:
|
||||
- lambda: |-
|
||||
// Update home.climate_entity variable
|
||||
id(entity_id) = (id(is_embedded_thermostat)) ? "embedded_climate" : "";
|
||||
id(disp1).set_component_value("climate.embedded", (id(is_embedded_thermostat)) ? 1 : 0);
|
||||
// Update chips
|
||||
if (id(is_embedded_thermostat))
|
||||
id(update_climate_icon).execute("home.icon_top_03", int(id(thermostat_embedded).action), int(id(thermostat_embedded).mode));
|
||||
|
||||
- id: !extend addon_climate_service_call
|
||||
then:
|
||||
- lambda: |-
|
||||
id(is_addon_climate_visible) = true;
|
||||
id(disp1).set_component_value("climate.embedded", 1);
|
||||
auto call = id(thermostat_embedded).make_call();
|
||||
if (key == "set_temperature")
|
||||
call.set_target_temperature(stof(value) / 10);
|
||||
else if (key == "hvac_mode")
|
||||
call.set_mode(value);
|
||||
call.perform();
|
||||
|
||||
- id: !extend addon_climate_set_climate
|
||||
then:
|
||||
- lambda: |-
|
||||
id(is_addon_climate_visible) = embedded_climate;
|
||||
|
||||
- id: !extend addon_climate_update_page_climate
|
||||
then:
|
||||
- lambda: |-
|
||||
if (id(current_page).state == "climate" and id(is_addon_climate_visible))
|
||||
{
|
||||
id(disp1).set_component_text_printf("page_label", id(addon_climate_friendly_name).c_str());
|
||||
float temp_step = ${temp_step};
|
||||
float temp_offset = ${temp_min};
|
||||
float temp_max = ${temp_max};
|
||||
float total_steps = (temp_max-temp_offset)/temp_step;
|
||||
id(set_climate)->execute
|
||||
(
|
||||
id(thermostat_embedded).current_temperature, // current_temp
|
||||
id(thermostat_embedded).target_temperature, // target_temp
|
||||
int(round(${temp_step}*10)), // temp_step
|
||||
int(round(total_steps)), // total_steps //int(round((10*id(thermostat_embedded).target_temperature-temp_offset)/temp_step)), // slider_val
|
||||
int(round(${temp_min}*10)), // temp_offset
|
||||
"", // climate_icon
|
||||
true // embedded_climate
|
||||
);
|
||||
|
||||
// Update target temp icon
|
||||
id(update_climate_icon).execute("climate.target_icon", int(id(thermostat_embedded).action), int(id(thermostat_embedded).mode));
|
||||
|
||||
// Update buttons bar
|
||||
// Hide not supported hotspots
|
||||
id(disp1).hide_component("button01");
|
||||
id(disp1).hide_component("button02");
|
||||
if (${addon_climate_heat}) id(disp1).show_component("button03"); else id(disp1).hide_component("button03"); //Heat
|
||||
if (${addon_climate_cool}) id(disp1).show_component("button04"); else id(disp1).hide_component("button04"); //Cool
|
||||
id(disp1).hide_component("button05");
|
||||
id(disp1).hide_component("button06");
|
||||
id(disp1).show_component("button07"); //Off
|
||||
// Set buttons colors
|
||||
id(disp1).set_component_font_color("climate.button01_icon", 6339);
|
||||
id(disp1).set_component_font_color("climate.button02_icon", 6339);
|
||||
id(disp1).set_component_font_color("climate.button03_icon", (id(thermostat_embedded).mode==climate::CLIMATE_MODE_HEAT) ? 64164 : ((${addon_climate_heat}) ? 48631 : 6339));
|
||||
id(disp1).set_component_font_color("climate.button04_icon", (id(thermostat_embedded).mode==climate::CLIMATE_MODE_COOL) ? 1055 : ((${addon_climate_cool}) ? 48631 : 6339));
|
||||
id(disp1).set_component_font_color("climate.button05_icon", 6339);
|
||||
id(disp1).set_component_font_color("climate.button06_icon", 6339);
|
||||
id(disp1).set_component_font_color("climate.button07_icon", (id(thermostat_embedded).mode==climate::CLIMATE_MODE_OFF) ? 10597 : 35921);
|
||||
}
|
||||
|
||||
- id: !extend addon_climate_set_climate_friendly_name
|
||||
then:
|
||||
- lambda: |-
|
||||
id(addon_climate_friendly_name) = friendly_name;
|
||||
|
||||
switch:
|
||||
##### PHYSICAL SWITCH 0 (Dummy) - Used when relay is not set #####
|
||||
- name: ${device_name} Relay 0 (dummy)
|
||||
platform: template
|
||||
id: relay_0
|
||||
lambda: !lambda return false;
|
||||
internal: true
|
||||
optimistic: true
|
||||
packages:
|
||||
core_package: !include nspanel_esphome_climate_shared.yaml
|
||||
|
||||
@@ -6,15 +6,10 @@
|
||||
substitutions:
|
||||
### Local thermostat defaults ###
|
||||
# https://esphome.io/components/climate/thermostat.html
|
||||
temp_units: "°C"
|
||||
heater_relay: "0" # Select 1 for "Relay 1", 2 for "Relay 2" or "0" to a dummy switch/disabled
|
||||
min_off_time: "300"
|
||||
min_run_time: "300"
|
||||
min_idle_time: "30"
|
||||
# https://esphome.io/components/climate/index.html#base-climate-configuration
|
||||
temp_min: "5"
|
||||
temp_max: "25"
|
||||
temp_step: "0.5"
|
||||
|
||||
##### DO NOT CHANGE THIS #####
|
||||
addon_climate_cool: "false"
|
||||
@@ -52,102 +47,5 @@ climate:
|
||||
- script.execute: addon_climate_update_page_home
|
||||
- logger.log: Climate state changed - End
|
||||
|
||||
###### All the code bellow this point is shared between addon_climate_cool and addon_climate_heat #####
|
||||
|
||||
globals:
|
||||
##### Is embedded thermostat visible on climate page? #####
|
||||
- id: is_addon_climate_visible
|
||||
type: bool
|
||||
restore_value: false
|
||||
initial_value: 'false'
|
||||
##### Embeded climate friendly name #####
|
||||
- id: addon_climate_friendly_name
|
||||
type: std::string
|
||||
restore_value: false
|
||||
initial_value: '"${device_name} Thermostat"'
|
||||
|
||||
script:
|
||||
- id: !extend addon_climate_update_page_home
|
||||
mode: restart
|
||||
then:
|
||||
- lambda: |-
|
||||
// Update home.climate_entity variable
|
||||
id(entity_id) = (id(is_embedded_thermostat)) ? "embedded_climate" : "";
|
||||
id(disp1).set_component_value("climate.embedded", (id(is_embedded_thermostat)) ? 1 : 0);
|
||||
// Update chips
|
||||
if (id(is_embedded_thermostat))
|
||||
id(update_climate_icon).execute("home.icon_top_03", int(id(thermostat_embedded).action), int(id(thermostat_embedded).mode));
|
||||
|
||||
- id: !extend addon_climate_service_call
|
||||
then:
|
||||
- lambda: |-
|
||||
id(is_addon_climate_visible) = true;
|
||||
id(disp1).set_component_value("climate.embedded", 1);
|
||||
auto call = id(thermostat_embedded).make_call();
|
||||
if (key == "set_temperature")
|
||||
call.set_target_temperature(stof(value) / 10);
|
||||
else if (key == "hvac_mode")
|
||||
call.set_mode(value);
|
||||
call.perform();
|
||||
|
||||
- id: !extend addon_climate_set_climate
|
||||
then:
|
||||
- lambda: |-
|
||||
id(is_addon_climate_visible) = embedded_climate;
|
||||
|
||||
- id: !extend addon_climate_update_page_climate
|
||||
then:
|
||||
- lambda: |-
|
||||
if (id(current_page).state == "climate" and id(is_addon_climate_visible))
|
||||
{
|
||||
id(disp1).set_component_text_printf("page_label", id(addon_climate_friendly_name).c_str());
|
||||
float temp_step = ${temp_step};
|
||||
float temp_offset = ${temp_min};
|
||||
float temp_max = ${temp_max};
|
||||
float total_steps = (temp_max-temp_offset)/temp_step;
|
||||
id(set_climate)->execute
|
||||
(
|
||||
id(thermostat_embedded).current_temperature, // current_temp
|
||||
id(thermostat_embedded).target_temperature, // target_temp
|
||||
int(round(${temp_step}*10)), // temp_step
|
||||
int(round(total_steps)), // total_steps //int(round((10*id(thermostat_embedded).target_temperature-temp_offset)/temp_step)), // slider_val
|
||||
int(round(${temp_min}*10)), // temp_offset
|
||||
"", // climate_icon
|
||||
true // embedded_climate
|
||||
);
|
||||
|
||||
// Update target temp icon
|
||||
id(update_climate_icon).execute("climate.target_icon", int(id(thermostat_embedded).action), int(id(thermostat_embedded).mode));
|
||||
|
||||
// Update buttons bar
|
||||
// Hide not supported hotspots
|
||||
id(disp1).hide_component("button01");
|
||||
id(disp1).hide_component("button02");
|
||||
if (${addon_climate_heat}) id(disp1).show_component("button03"); else id(disp1).hide_component("button03"); //Heat
|
||||
if (${addon_climate_cool}) id(disp1).show_component("button04"); else id(disp1).hide_component("button04"); //Cool
|
||||
id(disp1).hide_component("button05");
|
||||
id(disp1).hide_component("button06");
|
||||
id(disp1).show_component("button07"); //Off
|
||||
// Set buttons colors
|
||||
id(disp1).set_component_font_color("climate.button01_icon", 6339);
|
||||
id(disp1).set_component_font_color("climate.button02_icon", 6339);
|
||||
id(disp1).set_component_font_color("climate.button03_icon", (id(thermostat_embedded).mode==climate::CLIMATE_MODE_HEAT) ? 64164 : ((${addon_climate_heat}) ? 48631 : 6339));
|
||||
id(disp1).set_component_font_color("climate.button04_icon", (id(thermostat_embedded).mode==climate::CLIMATE_MODE_COOL) ? 1055 : ((${addon_climate_cool}) ? 48631 : 6339));
|
||||
id(disp1).set_component_font_color("climate.button05_icon", 6339);
|
||||
id(disp1).set_component_font_color("climate.button06_icon", 6339);
|
||||
id(disp1).set_component_font_color("climate.button07_icon", (id(thermostat_embedded).mode==climate::CLIMATE_MODE_OFF) ? 10597 : 35921);
|
||||
}
|
||||
|
||||
- id: !extend addon_climate_set_climate_friendly_name
|
||||
then:
|
||||
- lambda: |-
|
||||
id(addon_climate_friendly_name) = friendly_name;
|
||||
|
||||
switch:
|
||||
##### PHYSICAL SWITCH 0 (Dummy) - Used when relay is not set #####
|
||||
- name: ${device_name} Relay 0 (dummy)
|
||||
platform: template
|
||||
id: relay_0
|
||||
lambda: !lambda return false;
|
||||
internal: true
|
||||
optimistic: true
|
||||
packages:
|
||||
core_package: !include nspanel_esphome_climate_shared.yaml
|
||||
|
||||
121
nspanel_esphome_addon_climate_shared.yaml
Normal file
121
nspanel_esphome_addon_climate_shared.yaml
Normal file
@@ -0,0 +1,121 @@
|
||||
####################################################################################################
|
||||
##### NSPanel ESPHome Add-on for Climate control - Shared - This will be called by heat/cool #####
|
||||
##### Add-on for https://github.com/Blackymas/NSPanel_HA_Blueprint #####
|
||||
####################################################################################################
|
||||
|
||||
substitutions:
|
||||
### Local thermostat defaults ###
|
||||
# https://esphome.io/components/climate/thermostat.html
|
||||
temp_units: "°C"
|
||||
heater_relay: "0" # Select 1 for "Relay 1", 2 for "Relay 2" or "0" to a dummy switch/disabled
|
||||
cooler_relay: "0" # Select 1 for "Relay 1", 2 for "Relay 2" or "0" to a dummy switch/disabled
|
||||
min_off_time: "300"
|
||||
min_run_time: "300"
|
||||
min_idle_time: "30"
|
||||
# https://esphome.io/components/climate/index.html#base-climate-configuration
|
||||
temp_min: "5"
|
||||
temp_max: "45"
|
||||
temp_step: "0.5"
|
||||
|
||||
##### DO NOT CHANGE THIS #####
|
||||
addon_climate_cool: "false"
|
||||
addon_climate_heat: "false"
|
||||
##############################
|
||||
|
||||
globals:
|
||||
##### Is embedded thermostat visible on climate page? #####
|
||||
- id: is_addon_climate_visible
|
||||
type: bool
|
||||
restore_value: false
|
||||
initial_value: 'false'
|
||||
##### Embeded climate friendly name #####
|
||||
- id: addon_climate_friendly_name
|
||||
type: std::string
|
||||
restore_value: false
|
||||
initial_value: '"${device_name} Thermostat"'
|
||||
|
||||
switch:
|
||||
##### PHYSICAL SWITCH 0 (Dummy) - Used when relay is not set #####
|
||||
- name: ${device_name} Relay 0 (dummy)
|
||||
platform: template
|
||||
id: relay_0
|
||||
lambda: !lambda return false;
|
||||
internal: true
|
||||
optimistic: true
|
||||
|
||||
script:
|
||||
- id: !extend addon_climate_update_page_home
|
||||
mode: restart
|
||||
then:
|
||||
- lambda: |-
|
||||
// Update home.climate_entity variable
|
||||
id(entity_id) = (id(is_embedded_thermostat)) ? "embedded_climate" : "";
|
||||
id(disp1).set_component_value("climate.embedded", (id(is_embedded_thermostat)) ? 1 : 0);
|
||||
// Update chips
|
||||
if (id(is_embedded_thermostat))
|
||||
id(update_climate_icon).execute("home.icon_top_03", int(id(thermostat_embedded).action), int(id(thermostat_embedded).mode));
|
||||
|
||||
- id: !extend addon_climate_service_call
|
||||
then:
|
||||
- lambda: |-
|
||||
id(is_addon_climate_visible) = true;
|
||||
id(disp1).set_component_value("climate.embedded", 1);
|
||||
auto call = id(thermostat_embedded).make_call();
|
||||
if (key == "set_temperature")
|
||||
call.set_target_temperature(stof(value) / 10);
|
||||
else if (key == "hvac_mode")
|
||||
call.set_mode(value);
|
||||
call.perform();
|
||||
|
||||
- id: !extend addon_climate_set_climate
|
||||
then:
|
||||
- lambda: |-
|
||||
id(is_addon_climate_visible) = embedded_climate;
|
||||
|
||||
- id: !extend addon_climate_update_page_climate
|
||||
then:
|
||||
- lambda: |-
|
||||
if (id(current_page).state == "climate" and id(is_addon_climate_visible))
|
||||
{
|
||||
id(disp1).set_component_text_printf("page_label", id(addon_climate_friendly_name).c_str());
|
||||
float temp_step = ${temp_step};
|
||||
float temp_offset = ${temp_min};
|
||||
float temp_max = ${temp_max};
|
||||
float total_steps = (temp_max-temp_offset)/temp_step;
|
||||
id(set_climate)->execute
|
||||
(
|
||||
id(thermostat_embedded).current_temperature, // current_temp
|
||||
id(thermostat_embedded).target_temperature, // target_temp
|
||||
int(round(${temp_step}*10)), // temp_step
|
||||
int(round(total_steps)), // total_steps //int(round((10*id(thermostat_embedded).target_temperature-temp_offset)/temp_step)), // slider_val
|
||||
int(round(${temp_min}*10)), // temp_offset
|
||||
"", // climate_icon
|
||||
true // embedded_climate
|
||||
);
|
||||
|
||||
// Update target temp icon
|
||||
id(update_climate_icon).execute("climate.target_icon", int(id(thermostat_embedded).action), int(id(thermostat_embedded).mode));
|
||||
|
||||
// Update buttons bar
|
||||
// Hide not supported hotspots
|
||||
id(disp1).hide_component("button01");
|
||||
id(disp1).hide_component("button02");
|
||||
if (${addon_climate_heat}) id(disp1).show_component("button03"); else id(disp1).hide_component("button03"); //Heat
|
||||
if (${addon_climate_cool}) id(disp1).show_component("button04"); else id(disp1).hide_component("button04"); //Cool
|
||||
id(disp1).hide_component("button05");
|
||||
id(disp1).hide_component("button06");
|
||||
id(disp1).show_component("button07"); //Off
|
||||
// Set buttons colors
|
||||
id(disp1).set_component_font_color("climate.button01_icon", 6339);
|
||||
id(disp1).set_component_font_color("climate.button02_icon", 6339);
|
||||
id(disp1).set_component_font_color("climate.button03_icon", (id(thermostat_embedded).mode==climate::CLIMATE_MODE_HEAT) ? 64164 : ((${addon_climate_heat}) ? 48631 : 6339));
|
||||
id(disp1).set_component_font_color("climate.button04_icon", (id(thermostat_embedded).mode==climate::CLIMATE_MODE_COOL) ? 1055 : ((${addon_climate_cool}) ? 48631 : 6339));
|
||||
id(disp1).set_component_font_color("climate.button05_icon", 6339);
|
||||
id(disp1).set_component_font_color("climate.button06_icon", 6339);
|
||||
id(disp1).set_component_font_color("climate.button07_icon", (id(thermostat_embedded).mode==climate::CLIMATE_MODE_OFF) ? 10597 : 35921);
|
||||
}
|
||||
|
||||
- id: !extend addon_climate_set_climate_friendly_name
|
||||
then:
|
||||
- lambda: |-
|
||||
id(addon_climate_friendly_name) = friendly_name;
|
||||
@@ -16,6 +16,17 @@ time:
|
||||
- component.update: api_timestamp
|
||||
- component.update: device_timestamp
|
||||
|
||||
api:
|
||||
services:
|
||||
##### 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;'
|
||||
|
||||
button:
|
||||
##### EXIT REPARSE TFT DISPLAY #####
|
||||
- name: ${device_name} Exit reparse
|
||||
@@ -32,17 +43,6 @@ button:
|
||||
id: tf_uart
|
||||
data: [0xFF, 0xFF, 0xFF]
|
||||
|
||||
api:
|
||||
services:
|
||||
##### 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;'
|
||||
|
||||
sensor:
|
||||
##### Uptime Sensors #####
|
||||
- name: ${device_name} Uptime seconds
|
||||
|
||||
@@ -18,24 +18,6 @@ substitutions:
|
||||
upload_tft_chunk_size_max: "32768"
|
||||
#############################
|
||||
|
||||
##### WIFI SETUP #####
|
||||
wifi:
|
||||
networks:
|
||||
- id: wifi_default
|
||||
ssid: ${wifi_ssid}
|
||||
password: ${wifi_password}
|
||||
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}
|
||||
@@ -72,6 +54,24 @@ esp32:
|
||||
# framework:
|
||||
# type: esp-idf
|
||||
|
||||
##### WIFI SETUP #####
|
||||
wifi:
|
||||
networks:
|
||||
- id: wifi_default
|
||||
ssid: ${wifi_ssid}
|
||||
password: ${wifi_password}
|
||||
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}
|
||||
|
||||
##### OTA PASSWORD #####
|
||||
ota:
|
||||
id: ota_std
|
||||
@@ -119,13 +119,6 @@ time:
|
||||
- script.execute:
|
||||
id: refresh_datetime
|
||||
|
||||
##### START - BUTTON CONFIGURATION #####
|
||||
button:
|
||||
###### REBOOT BUTTON #####
|
||||
- name: ${device_name} Restart
|
||||
platform: restart
|
||||
id: restart_nspanel
|
||||
|
||||
##### START - API CONFIGURATION #####
|
||||
api:
|
||||
id: api_server
|
||||
@@ -717,6 +710,61 @@ api:
|
||||
}
|
||||
}
|
||||
|
||||
##### 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!"
|
||||
|
||||
##### START - GLOBALS CONFIGURATION #####
|
||||
globals:
|
||||
|
||||
@@ -809,7 +857,6 @@ globals:
|
||||
type: std::string
|
||||
restore_value: no
|
||||
initial_value: '"%H:%M"'
|
||||
|
||||
- id: home_time_color
|
||||
type: int
|
||||
restore_value: true
|
||||
@@ -820,7 +867,6 @@ globals:
|
||||
type: std::string
|
||||
restore_value: false
|
||||
initial_value: ''
|
||||
|
||||
- id: home_relay1_icon_color
|
||||
type: int
|
||||
restore_value: true
|
||||
@@ -830,7 +876,6 @@ globals:
|
||||
type: std::string
|
||||
restore_value: false
|
||||
initial_value: ''
|
||||
|
||||
- id: home_relay2_icon_color
|
||||
type: int
|
||||
restore_value: true
|
||||
@@ -839,7 +884,6 @@ globals:
|
||||
- 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
|
||||
@@ -943,7 +987,6 @@ binary_sensor:
|
||||
internal: true
|
||||
on_click:
|
||||
- button.press: restart_nspanel
|
||||
|
||||
##### Restart NSPanel Button - Boot Page #####
|
||||
- name: ${device_name} Restart
|
||||
platform: nextion
|
||||
@@ -971,6 +1014,157 @@ binary_sensor:
|
||||
- script.execute:
|
||||
id: refresh_wifi_icon
|
||||
|
||||
##### START - BUTTON CONFIGURATION #####
|
||||
button:
|
||||
###### REBOOT BUTTON #####
|
||||
- name: ${device_name} Restart
|
||||
platform: restart
|
||||
id: restart_nspanel
|
||||
|
||||
##### 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:
|
||||
id: page_screensaver
|
||||
construct_page: false
|
||||
|
||||
##### START - SENSOR CONFIGURATION #####
|
||||
sensor:
|
||||
|
||||
@@ -1043,6 +1237,95 @@ sensor:
|
||||
- lambda: |-
|
||||
id(timer_reset_all).execute("settings");
|
||||
|
||||
##### 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 - TEXT SENSOR CONFIGURATION #####
|
||||
text_sensor:
|
||||
|
||||
@@ -1247,294 +1530,6 @@ text_sensor:
|
||||
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:
|
||||
id: page_screensaver
|
||||
construct_page: false
|
||||
|
||||
##### 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 ######
|
||||
|
||||
Reference in New Issue
Block a user