esphome device updates
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
#############################################
|
##########################################################################################
|
||||||
# Enable the Home Assistant API
|
# Enable the Home Assistant API
|
||||||
# https://esphome.io/components/api.html
|
# https://esphome.io/components/api.html
|
||||||
#############################################
|
##########################################################################################
|
||||||
api:
|
api:
|
||||||
encryption:
|
encryption:
|
||||||
key: ${local_api_key}
|
key: ${local_api_key}
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
substitutions:
|
||||||
|
local_update_interval: 30s
|
||||||
|
|
||||||
|
##########################################################################################
|
||||||
|
# GENERAL COMMON SENSORS
|
||||||
|
# https://esphome.io/components/sensor/
|
||||||
|
# LITE (1MB-friendly)
|
||||||
|
##########################################################################################
|
||||||
|
sensor:
|
||||||
|
- platform: uptime
|
||||||
|
name: "Uptime (s):"
|
||||||
|
update_interval: ${local_update_interval}
|
||||||
|
id: uptime_sensor
|
||||||
|
entity_category: diagnostic
|
||||||
|
|
||||||
|
- platform: wifi_signal
|
||||||
|
name: "Wifi (dB):"
|
||||||
|
id: wifi_signal_db
|
||||||
|
update_interval: ${local_update_interval}
|
||||||
|
entity_category: diagnostic
|
||||||
|
|
||||||
|
- platform: copy # Reports the WiFi signal strength in %
|
||||||
|
source_id: wifi_signal_db
|
||||||
|
name: "WiFi (%):"
|
||||||
|
filters:
|
||||||
|
- lambda: return min(max(2 * (x + 100.0), 0.0), 100.0);
|
||||||
|
unit_of_measurement: "% Max"
|
||||||
|
entity_category: "diagnostic"
|
||||||
|
device_class: ""
|
||||||
|
|
||||||
|
binary_sensor:
|
||||||
|
- platform: status
|
||||||
|
name: "Network Status"
|
||||||
|
icon: mdi:check-network-outline
|
||||||
|
entity_category: diagnostic
|
||||||
|
|
||||||
|
##########################################################################################
|
||||||
|
# Text Sensors
|
||||||
|
# https://esphome.io/components/text_sensor/index.html
|
||||||
|
##########################################################################################
|
||||||
|
text_sensor:
|
||||||
|
- platform: uptime # Uptime for this device human readable
|
||||||
|
name: "Uptime:"
|
||||||
|
icon: mdi:clock-start
|
||||||
|
update_interval: ${local_update_interval}
|
||||||
|
entity_category: "diagnostic"
|
||||||
|
|
||||||
|
##########################################################################################
|
||||||
|
# BUTTONS
|
||||||
|
# Diagnostic buttons, activated if needed in HA
|
||||||
|
##########################################################################################
|
||||||
|
button:
|
||||||
|
- platform: safe_mode
|
||||||
|
name: "Safe Mode Restart:"
|
||||||
|
entity_category: "diagnostic"
|
||||||
|
disabled_by_default: true
|
||||||
|
- platform: restart
|
||||||
|
name: "Restart:"
|
||||||
|
entity_category: "diagnostic"
|
||||||
|
disabled_by_default: true
|
||||||
|
- platform: factory_reset
|
||||||
|
name: "FACTORY RESET:"
|
||||||
|
entity_category: "diagnostic"
|
||||||
|
disabled_by_default: true
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
substitutions:
|
||||||
|
local_update_interval: 30s
|
||||||
|
|
||||||
|
##########################################################################################
|
||||||
|
# GENERAL COMMON SENSORS
|
||||||
|
# https://esphome.io/components/sensor/
|
||||||
|
# LITE (1MB-friendly)
|
||||||
|
##########################################################################################
|
||||||
|
sensor:
|
||||||
|
- platform: uptime
|
||||||
|
name: "Uptime (s):"
|
||||||
|
update_interval: ${local_update_interval}
|
||||||
|
id: uptime_sensor
|
||||||
|
entity_category: diagnostic
|
||||||
|
|
||||||
|
- platform: wifi_signal
|
||||||
|
name: "Wifi (dB):"
|
||||||
|
id: wifi_signal_db
|
||||||
|
update_interval: ${local_update_interval}
|
||||||
|
entity_category: diagnostic
|
||||||
|
|
||||||
|
- platform: copy # Reports the WiFi signal strength in %
|
||||||
|
source_id: wifi_signal_db
|
||||||
|
name: "WiFi (%):"
|
||||||
|
filters:
|
||||||
|
- lambda: return min(max(2 * (x + 100.0), 0.0), 100.0);
|
||||||
|
unit_of_measurement: "% Max"
|
||||||
|
entity_category: "diagnostic"
|
||||||
|
device_class: ""
|
||||||
|
|
||||||
|
binary_sensor:
|
||||||
|
- platform: status
|
||||||
|
name: "Network Status"
|
||||||
|
icon: mdi:check-network-outline
|
||||||
|
entity_category: diagnostic
|
||||||
|
|
||||||
|
##########################################################################################
|
||||||
|
# Text Sensors
|
||||||
|
# https://esphome.io/components/text_sensor/index.html
|
||||||
|
##########################################################################################
|
||||||
|
text_sensor:
|
||||||
|
- platform: uptime # Uptime for this device human readable
|
||||||
|
name: "Uptime:"
|
||||||
|
icon: mdi:clock-start
|
||||||
|
update_interval: ${local_update_interval}
|
||||||
|
entity_category: "diagnostic"
|
||||||
|
|
||||||
|
##########################################################################################
|
||||||
|
# BUTTONS
|
||||||
|
# Diagnostic buttons, activated if needed in HA
|
||||||
|
##########################################################################################
|
||||||
|
button:
|
||||||
|
- platform: safe_mode
|
||||||
|
name: "Safe Mode Restart:"
|
||||||
|
entity_category: "diagnostic"
|
||||||
|
disabled_by_default: true
|
||||||
|
- platform: restart
|
||||||
|
name: "Restart:"
|
||||||
|
entity_category: "diagnostic"
|
||||||
|
disabled_by_default: true
|
||||||
|
- platform: factory_reset
|
||||||
|
name: "FACTORY RESET:"
|
||||||
|
entity_category: "diagnostic"
|
||||||
|
disabled_by_default: true
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
substitutions:
|
||||||
|
local_update_interval: 30s
|
||||||
|
|
||||||
|
##########################################################################################
|
||||||
|
# GENERAL COMMON SENSORS
|
||||||
|
# https://esphome.io/components/sensor/
|
||||||
|
##########################################################################################
|
||||||
|
|
||||||
|
# Debug helpers (loop time / heap / reset info)
|
||||||
|
debug:
|
||||||
|
update_interval: ${local_update_interval}
|
||||||
|
|
||||||
|
sensor:
|
||||||
|
# Free heap via debug (replaces template-based ESP.getFreeHeap if you prefer)
|
||||||
|
- platform: debug
|
||||||
|
free:
|
||||||
|
name: "Free Heap"
|
||||||
|
entity_category: "diagnostic"
|
||||||
|
|
||||||
|
# Loop time via debug (replaces broken template loop time)
|
||||||
|
- platform: debug
|
||||||
|
loop_time:
|
||||||
|
name: "Loop Time"
|
||||||
|
entity_category: "diagnostic"
|
||||||
|
|
||||||
|
## If you’d rather keep the legacy heap sensor, uncomment below and
|
||||||
|
## delete the debug "free" block above to avoid duplicates:
|
||||||
|
# - platform: template
|
||||||
|
# name: "${friendly_name} Free Heap"
|
||||||
|
# unit_of_measurement: "B"
|
||||||
|
# update_interval: 30s
|
||||||
|
# entity_category: "diagnostic"
|
||||||
|
# lambda: |-
|
||||||
|
# return (float) ESP.getFreeHeap();
|
||||||
|
|
||||||
|
## # Only works with esp8266
|
||||||
|
#- platform: adc
|
||||||
|
# pin: VCC
|
||||||
|
# name: "VCC Voltage"
|
||||||
|
# id: vcc_voltage_sensor
|
||||||
|
# entity_category: diagnostic
|
||||||
|
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
substitutions:
|
||||||
|
local_update_interval: 30s
|
||||||
|
|
||||||
|
##########################################################################################
|
||||||
|
# Text Sensors
|
||||||
|
# https://esphome.io/components/text_sensor/index.html
|
||||||
|
##########################################################################################
|
||||||
|
text_sensor:
|
||||||
|
- platform: version
|
||||||
|
name: "Version:"
|
||||||
|
entity_category: "diagnostic"
|
||||||
|
|
||||||
|
- platform: wifi_info
|
||||||
|
ip_address:
|
||||||
|
icon: mdi:ip-network
|
||||||
|
entity_category: diagnostic
|
||||||
|
name: "IP Address:"
|
||||||
|
ssid:
|
||||||
|
name: "Connected SSID"
|
||||||
|
icon: mdi:wifi-strength-2
|
||||||
|
entity_category: diagnostic
|
||||||
|
mac_address:
|
||||||
|
name: "MAC Address:"
|
||||||
|
icon: mdi:network-pos
|
||||||
|
entity_category: diagnostic
|
||||||
|
|
||||||
|
#####################################################################################################
|
||||||
|
# Creates a sensor of the uptime of the device, in formatted days, hours, minutes and seconds
|
||||||
|
###################################################################################################
|
||||||
|
# - platform: template
|
||||||
|
# name: "Uptime (Days)"
|
||||||
|
# entity_category: diagnostic
|
||||||
|
# lambda: |-
|
||||||
|
# int seconds = (id(uptime_sensor).state);
|
||||||
|
# int days = seconds / (24 * 3600);
|
||||||
|
# seconds = seconds % (24 * 3600);
|
||||||
|
# int hours = seconds / 3600;
|
||||||
|
# seconds = seconds % 3600;
|
||||||
|
# int minutes = seconds / 60;
|
||||||
|
# seconds = seconds % 60;
|
||||||
|
# if ( days > 3650 ) {
|
||||||
|
# return "Starting up" ;
|
||||||
|
# } else if ( days ) {
|
||||||
|
# return (String(days) +"d " + String(hours) +"h " + String(minutes) +"m "+ String(seconds) +"s").c_str() ;
|
||||||
|
# } else if ( hours ) {
|
||||||
|
# return (String(hours) +"h " + String(minutes) +"m "+ String(seconds) +"s").c_str() ;
|
||||||
|
# } else if ( minutes ) {
|
||||||
|
# return (String(minutes) +"m "+ String(seconds) +"s").c_str() ;
|
||||||
|
# } else {
|
||||||
|
# return (String(seconds) +"s").c_str() ;
|
||||||
|
# }
|
||||||
|
# icon: mdi:clock-start
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,183 @@
|
|||||||
|
##########################################################################################
|
||||||
|
##########################################################################################
|
||||||
|
# Title: Reset Classification & Counters (Interval-based, no on_boot work)
|
||||||
|
# Target: ESP8266/ESP32 (optimized for ESP8266 heap stability)
|
||||||
|
#
|
||||||
|
# Exposes four entities:
|
||||||
|
# - <friendly> Restarts: Power (sensor)
|
||||||
|
# - <friendly> Restarts: Software (sensor)
|
||||||
|
# - <friendly> Restarts: Crashes (sensor)
|
||||||
|
# - <friendly> Restarts: Last Reason (text_sensor)
|
||||||
|
#
|
||||||
|
# Also exposes a button:
|
||||||
|
# - <friendly> Restarts: Clear Counters
|
||||||
|
#
|
||||||
|
# Behavior:
|
||||||
|
# - Does NOT run on_boot. Instead, an interval waits a short time after power-up, then:
|
||||||
|
# * Reads ESP.getResetReason()
|
||||||
|
# * Categorizes into one of: power, software, crash (exactly one bucket)
|
||||||
|
# * Increments that counter and publishes all three sensors + the last reason.
|
||||||
|
# - A 'run-once per boot' guard prevents re-incrementing on subsequent intervals.
|
||||||
|
#
|
||||||
|
# Notes:
|
||||||
|
# - Uses Arduino String (not std::string) in lambdas to reduce heap churn.
|
||||||
|
# - Sensors use update_interval: never; they’re pushed when we publish.
|
||||||
|
# - If you prefer a different delay, change the interval: value below.
|
||||||
|
##########################################################################################
|
||||||
|
##########################################################################################
|
||||||
|
|
||||||
|
##########################################################################################
|
||||||
|
# GLOBALS
|
||||||
|
##########################################################################################
|
||||||
|
globals:
|
||||||
|
# Cumulative counters (persist across reboots)
|
||||||
|
- id: power_reset_count
|
||||||
|
type: int
|
||||||
|
restore_value: true
|
||||||
|
initial_value: '0'
|
||||||
|
- id: software_reset_count
|
||||||
|
type: int
|
||||||
|
restore_value: true
|
||||||
|
initial_value: '0'
|
||||||
|
- id: crash_reset_count
|
||||||
|
type: int
|
||||||
|
restore_value: true
|
||||||
|
initial_value: '0'
|
||||||
|
|
||||||
|
# Per-boot guard so the interval only classifies once each boot
|
||||||
|
- id: reset_classified_this_boot
|
||||||
|
type: bool
|
||||||
|
restore_value: false # always false at cold boot
|
||||||
|
initial_value: 'false'
|
||||||
|
|
||||||
|
##########################################################################################
|
||||||
|
# SENSORS (push-only; never self-update)
|
||||||
|
##########################################################################################
|
||||||
|
sensor:
|
||||||
|
- platform: template
|
||||||
|
id: power_reset_count_sensor
|
||||||
|
name: "${friendly_name} Restarts: Power"
|
||||||
|
icon: mdi:power-plug
|
||||||
|
unit_of_measurement: "restarts"
|
||||||
|
accuracy_decimals: 0
|
||||||
|
entity_category: diagnostic
|
||||||
|
update_interval: never
|
||||||
|
|
||||||
|
- platform: template
|
||||||
|
id: software_reset_count_sensor
|
||||||
|
name: "${friendly_name} Restarts: Software"
|
||||||
|
icon: mdi:restart
|
||||||
|
unit_of_measurement: "restarts"
|
||||||
|
accuracy_decimals: 0
|
||||||
|
entity_category: diagnostic
|
||||||
|
update_interval: never
|
||||||
|
|
||||||
|
- platform: template
|
||||||
|
id: crash_reset_count_sensor
|
||||||
|
name: "${friendly_name} Restarts: Crashes"
|
||||||
|
icon: mdi:alert-decagram
|
||||||
|
unit_of_measurement: "restarts"
|
||||||
|
accuracy_decimals: 0
|
||||||
|
entity_category: diagnostic
|
||||||
|
update_interval: never
|
||||||
|
|
||||||
|
##########################################################################################
|
||||||
|
# TEXT SENSORS (push-only; never self-update)
|
||||||
|
##########################################################################################
|
||||||
|
text_sensor:
|
||||||
|
- platform: template
|
||||||
|
id: last_reset_reason_txt
|
||||||
|
name: "${friendly_name} Restarts: Last Reason"
|
||||||
|
icon: mdi:restart-alert
|
||||||
|
entity_category: diagnostic
|
||||||
|
update_interval: never
|
||||||
|
|
||||||
|
##########################################################################################
|
||||||
|
# BUTTONS
|
||||||
|
##########################################################################################
|
||||||
|
button:
|
||||||
|
- platform: template
|
||||||
|
id: clear_reset_counters_btn
|
||||||
|
name: "${friendly_name} Restarts: Clear Counters"
|
||||||
|
icon: mdi:backup-restore
|
||||||
|
entity_category: diagnostic
|
||||||
|
on_press:
|
||||||
|
- lambda: |-
|
||||||
|
id(power_reset_count) = 0;
|
||||||
|
id(software_reset_count) = 0;
|
||||||
|
id(crash_reset_count) = 0;
|
||||||
|
id(power_reset_count_sensor).publish_state(0);
|
||||||
|
id(software_reset_count_sensor).publish_state(0);
|
||||||
|
id(crash_reset_count_sensor).publish_state(0);
|
||||||
|
// Keep the last reason as-is; it describes the most recent boot.
|
||||||
|
|
||||||
|
##########################################################################################
|
||||||
|
# SCRIPTS
|
||||||
|
##########################################################################################
|
||||||
|
script:
|
||||||
|
# Classify last reset exactly once per boot
|
||||||
|
- id: classify_reset_once
|
||||||
|
mode: restart
|
||||||
|
then:
|
||||||
|
- lambda: |-
|
||||||
|
// Guard: only run once per boot
|
||||||
|
if (id(reset_classified_this_boot)) return;
|
||||||
|
|
||||||
|
// Read raw reason string (examples: "Power on", "External System",
|
||||||
|
// "Software/System restart", "Fatal exception:29", "WDT reset")
|
||||||
|
String reason = ESP.getResetReason();
|
||||||
|
|
||||||
|
// Category: 1=power, 2=software, 3=crash
|
||||||
|
int cat = 0;
|
||||||
|
|
||||||
|
// Crash has priority if phrases appear (rare but be explicit)
|
||||||
|
if (reason.indexOf("Watchdog") >= 0 || reason.indexOf("WDT") >= 0 ||
|
||||||
|
reason.indexOf("Exception") >= 0 || reason.indexOf("exception") >= 0) {
|
||||||
|
cat = 3; // crash
|
||||||
|
}
|
||||||
|
// Software/System restart (HA/API/OTA initiated)
|
||||||
|
else if (reason.indexOf("Software") >= 0 || reason.indexOf("System") >= 0) {
|
||||||
|
cat = 2; // software
|
||||||
|
}
|
||||||
|
// Power-like causes (power-on, external reset pin/button, deep sleep wake)
|
||||||
|
else if (reason.indexOf("Power") >= 0 || reason.indexOf("External") >= 0 ||
|
||||||
|
reason.indexOf("Deep") >= 0) {
|
||||||
|
cat = 1; // power
|
||||||
|
}
|
||||||
|
// Anything else -> crash bucket
|
||||||
|
else {
|
||||||
|
cat = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cat == 1) id(power_reset_count) += 1;
|
||||||
|
if (cat == 2) id(software_reset_count) += 1;
|
||||||
|
if (cat == 3) id(crash_reset_count) += 1;
|
||||||
|
|
||||||
|
// Publish reason text now
|
||||||
|
id(last_reset_reason_txt).publish_state(reason.c_str());
|
||||||
|
|
||||||
|
// Mark done for this boot
|
||||||
|
id(reset_classified_this_boot) = true;
|
||||||
|
|
||||||
|
# Publish the three counters (kept separate so we can reuse)
|
||||||
|
- id: publish_reset_status
|
||||||
|
mode: restart
|
||||||
|
then:
|
||||||
|
- lambda: |-
|
||||||
|
id(power_reset_count_sensor).publish_state(id(power_reset_count));
|
||||||
|
id(software_reset_count_sensor).publish_state(id(software_reset_count));
|
||||||
|
id(crash_reset_count_sensor).publish_state(id(crash_reset_count));
|
||||||
|
|
||||||
|
##########################################################################################
|
||||||
|
# INTERVAL: run once shortly after boot, then no-op
|
||||||
|
##########################################################################################
|
||||||
|
interval:
|
||||||
|
- interval: 10s
|
||||||
|
then:
|
||||||
|
- if:
|
||||||
|
condition:
|
||||||
|
lambda: 'return !id(reset_classified_this_boot);'
|
||||||
|
then:
|
||||||
|
- script.execute: classify_reset_once
|
||||||
|
- script.execute: publish_reset_status
|
||||||
|
# On later passes, the guard prevents re-counting and this block does nothing.
|
||||||
@@ -22,3 +22,4 @@ mqtt:
|
|||||||
id: mqtt_client
|
id: mqtt_client
|
||||||
reboot_timeout: 0s # same for MQTT
|
reboot_timeout: 0s # same for MQTT
|
||||||
log_topic: null # <— stops MQTT log streaming (big JSON payloads)
|
log_topic: null # <— stops MQTT log streaming (big JSON payloads)
|
||||||
|
keepalive: 60s
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
substitutions:
|
substitutions:
|
||||||
##############################################
|
############################################################################################
|
||||||
# SPECIFIC DEVICE VARIABLE SUBSTITUTIONS
|
# SPECIFIC DEVICE VARIABLE SUBSTITUTIONS
|
||||||
# If NOT using a secrets file, just replace these with the passwords etc (in quotes)
|
# If NOT using a secrets file, just replace these with the passwords etc (in quotes)
|
||||||
#############################################
|
###########################################################################################
|
||||||
wifi_ssid: !secret ha_wifi_ssid
|
wifi_ssid: !secret ha_wifi_ssid
|
||||||
wifi_password: !secret ha_wifi_password
|
wifi_password: !secret ha_wifi_password
|
||||||
fallback_ap_password: !secret fallback_ap_password
|
fallback_ap_password: !secret fallback_ap_password
|
||||||
@@ -30,7 +30,7 @@ substitutions:
|
|||||||
base_interval_hours: "23" # Base interval in hours
|
base_interval_hours: "23" # Base interval in hours
|
||||||
random_offset_max_minutes: "59" # Max random offset in minutes
|
random_offset_max_minutes: "59" # Max random offset in minutes
|
||||||
|
|
||||||
#############################################
|
###########################################################################################
|
||||||
# Common Wifi Settings
|
# Common Wifi Settings
|
||||||
# https://esphome.io/components/wifi.html
|
# https://esphome.io/components/wifi.html
|
||||||
#
|
#
|
||||||
@@ -38,13 +38,13 @@ substitutions:
|
|||||||
# NONE (least power saving, Default for ESP8266)
|
# NONE (least power saving, Default for ESP8266)
|
||||||
# LIGHT (Default for ESP32)
|
# LIGHT (Default for ESP32)
|
||||||
# HIGH (most power saving)
|
# HIGH (most power saving)
|
||||||
#############################################
|
###########################################################################################
|
||||||
wifi:
|
wifi:
|
||||||
ssid: ${wifi_ssid}
|
ssid: ${wifi_ssid}
|
||||||
password: ${wifi_password}
|
password: ${wifi_password}
|
||||||
#enable_rrm: true # (ESP32 only) enable 802.11k Radio Resource Management
|
#enable_rrm: true # (ESP32 only) enable 802.11k Radio Resource Management
|
||||||
#enable_btm: true # (ESP32 only) enable 802.11v BSS Transition Management
|
#enable_btm: true # (ESP32 only) enable 802.11v BSS Transition Management
|
||||||
#power_save_mode: LIGHT # https://esphome.io/components/wifi.html#wifi-power-save-mode
|
power_save_mode: none # https://esphome.io/components/wifi.html#wifi-power-save-mode
|
||||||
manual_ip: # optional static IP address
|
manual_ip: # optional static IP address
|
||||||
static_ip: ${local_static_ip_address}
|
static_ip: ${local_static_ip_address}
|
||||||
gateway: ${static_ip_gateway}
|
gateway: ${static_ip_gateway}
|
||||||
@@ -63,60 +63,60 @@ wifi:
|
|||||||
|
|
||||||
#captive_portal: # extra fallback mechanism for when connecting if the configured WiFi fails
|
#captive_portal: # extra fallback mechanism for when connecting if the configured WiFi fails
|
||||||
|
|
||||||
#############################################
|
###########################################################################################
|
||||||
# Enable Over the Air Update Capability
|
# Enable Over the Air Update Capability
|
||||||
# https://esphome.io/components/ota.html?highlight=ota
|
# https://esphome.io/components/ota.html?highlight=ota
|
||||||
#############################################
|
###########################################################################################
|
||||||
ota:
|
ota:
|
||||||
- platform: esphome
|
- platform: esphome
|
||||||
password: ${local_ota_pass}
|
password: ${local_ota_pass}
|
||||||
version: 2
|
version: 2
|
||||||
#- platform: web_server # Uncomment if you want to be able to do OTA with the web interface
|
#- platform: web_server # Uncomment if you want to be able to do OTA with the web interface
|
||||||
|
|
||||||
#############################################
|
###########################################################################################
|
||||||
# Safe Mode
|
# Safe Mode
|
||||||
# Safe mode will detect boot loops
|
# Safe mode will detect boot loops
|
||||||
# https://esphome.io/components/safe_mode
|
# https://esphome.io/components/safe_mode
|
||||||
#############################################
|
###########################################################################################
|
||||||
safe_mode:
|
safe_mode:
|
||||||
|
|
||||||
#############################################
|
###########################################################################################
|
||||||
# Network
|
# Network
|
||||||
# global configuration for all types of networks
|
# global configuration for all types of networks
|
||||||
# https://esphome.io/components/network.html
|
# https://esphome.io/components/network.html
|
||||||
#############################################
|
###########################################################################################
|
||||||
network:
|
network:
|
||||||
enable_ipv6: ${ipv6_enable}
|
enable_ipv6: ${ipv6_enable}
|
||||||
|
|
||||||
#############################################
|
##########################################################################################
|
||||||
# Interval
|
# Interval
|
||||||
# Restart Networking every x hours + rand mins
|
# Restart Networking every x hours + rand mins
|
||||||
# This ensure that the device is connected to the best AP
|
# This ensure that the device is connected to the best AP
|
||||||
#############################################
|
##########################################################################################
|
||||||
script:
|
#script:
|
||||||
- id: random_reconnect
|
# - id: random_reconnect
|
||||||
mode: restart
|
# mode: restart
|
||||||
then:
|
# then:
|
||||||
- lambda: |-
|
# - lambda: |-
|
||||||
// Compute total delay: base hours + random offset minutes
|
# // Compute total delay: base hours + random offset minutes
|
||||||
uint32_t extra;
|
# uint32_t extra;
|
||||||
#if defined(ESP32)
|
# #if defined(ESP32)
|
||||||
// ESP32 (both Arduino & IDF builds) uses esp_random()
|
# // ESP32 (both Arduino & IDF builds) uses esp_random()
|
||||||
extra = esp_random() % (${random_offset_max_minutes} + 1);
|
# extra = esp_random() % (${random_offset_max_minutes} + 1);
|
||||||
#elif defined(ESP8266)
|
# #elif defined(ESP8266)
|
||||||
// ESP8266 Arduino core
|
# // ESP8266 Arduino core
|
||||||
extra = os_random() % (${random_offset_max_minutes} + 1);
|
# extra = os_random() % (${random_offset_max_minutes} + 1);
|
||||||
#else
|
# #else
|
||||||
// Fallback to esp_random() on other platforms
|
# // Fallback to esp_random() on other platforms
|
||||||
extra = esp_random() % (${random_offset_max_minutes} + 1);
|
# extra = esp_random() % (${random_offset_max_minutes} + 1);
|
||||||
#endif
|
# #endif
|
||||||
uint32_t total_s = ${base_interval_hours} * 3600 + extra * 60;
|
# uint32_t total_s = ${base_interval_hours} * 3600 + extra * 60;
|
||||||
ESP_LOGI("random_reconnect", "Next reconnect in %u seconds", total_s);
|
# ESP_LOGI("random_reconnect", "Next reconnect in %u seconds", total_s);
|
||||||
// Delay inside lambda (blocks script execution but OK for reconnect timing)
|
# // Delay inside lambda (blocks script execution but OK for reconnect timing)
|
||||||
delay(total_s * 1000);
|
# delay(total_s * 1000);
|
||||||
- logger.log: "network_check: performing reconnect"
|
# - logger.log: "network_check: performing reconnect"
|
||||||
- wifi.disable: {}
|
# - wifi.disable: {}
|
||||||
- delay: 1s
|
# - delay: 1s
|
||||||
- wifi.enable: {}
|
# - wifi.enable: {}
|
||||||
- script.execute: random_reconnect
|
# - script.execute: random_reconnect
|
||||||
|
|
||||||
|
|||||||
@@ -1,126 +0,0 @@
|
|||||||
substitutions:
|
|
||||||
##############################################
|
|
||||||
# SPECIFIC DEVICE VARIABLE SUBSTITUTIONS
|
|
||||||
# If NOT using a secrets file, just replace these with the passwords etc (in quotes)
|
|
||||||
#############################################
|
|
||||||
wifi_ssid: !secret ha_wifi_ssid
|
|
||||||
wifi_password: !secret ha_wifi_password
|
|
||||||
fallback_ap_password: !secret fallback_ap_password
|
|
||||||
|
|
||||||
# Enables faster network connections, with last connected SSID being connected to and no full scan for SSID being undertaken
|
|
||||||
wifi_fast_connect: "false"
|
|
||||||
|
|
||||||
# Define a domain for this device to use. i.e. iot.home.lan (so device will appear as athom-smart-plug-v2.iot.home.lan in DNS/DHCP logs)
|
|
||||||
dns_domain: ".local"
|
|
||||||
|
|
||||||
# Automatically add the mac address to the name
|
|
||||||
# eg so you can use a single firmware for all devices
|
|
||||||
add_mac_suffix: "false"
|
|
||||||
|
|
||||||
# Enable or disable the use of IPv6 networking on the device
|
|
||||||
ipv6_enable: "false"
|
|
||||||
|
|
||||||
# Add these if we are giving it a static ip, or remove them in the Wifi section
|
|
||||||
static_ip_subnet: !secret ha_wifi_subnet
|
|
||||||
static_ip_gateway: !secret ha_wifi_gateway
|
|
||||||
static_ip_dns1: !secret ha_wifi_dns1
|
|
||||||
static_ip_dns2: !secret ha_wifi_dns2
|
|
||||||
|
|
||||||
# Network reconnect every x hours to ensure best access point
|
|
||||||
base_interval_hours: "6" # Base interval in hours
|
|
||||||
random_offset_max_minutes: "59" # Max random offset in minutes
|
|
||||||
|
|
||||||
#############################################
|
|
||||||
# Common Wifi Settings
|
|
||||||
# https://esphome.io/components/wifi.html
|
|
||||||
#
|
|
||||||
# Power Save mode (can reduce wifi reliability)
|
|
||||||
# NONE (least power saving, Default for ESP8266)
|
|
||||||
# LIGHT (Default for ESP32)
|
|
||||||
# HIGH (most power saving)
|
|
||||||
#############################################
|
|
||||||
wifi:
|
|
||||||
networks:
|
|
||||||
- ssid: "vega_control_1"
|
|
||||||
password: "3Kd#vQBP8RDc"
|
|
||||||
priority: 1.0
|
|
||||||
- ssid: ${wifi_ssid}
|
|
||||||
password: ${wifi_password}
|
|
||||||
#enable_rrm: true # (ESP32 only) enable 802.11k Radio Resource Management
|
|
||||||
#enable_btm: true # (ESP32 only) enable 802.11v BSS Transition Management
|
|
||||||
#power_save_mode: LIGHT # https://esphome.io/components/wifi.html#wifi-power-save-mode
|
|
||||||
priority: 0.5
|
|
||||||
manual_ip: # optional static IP address
|
|
||||||
static_ip: ${local_static_ip_address}
|
|
||||||
gateway: ${static_ip_gateway}
|
|
||||||
subnet: ${static_ip_subnet}
|
|
||||||
dns1: ${static_ip_dns1}
|
|
||||||
dns2: ${static_ip_dns2}
|
|
||||||
|
|
||||||
use_address: ${local_static_ip_address} # required when any network uses manual_ip
|
|
||||||
ap: # Details for fallback hotspot in case wifi connection fails https://esphome.io/components/wifi.html#access-point-mode
|
|
||||||
ssid: ${device_name} AP
|
|
||||||
password: ${fallback_ap_password}
|
|
||||||
ap_timeout: 10min # Time until it brings up fallback AP. default is 1min
|
|
||||||
# Allow rapid re-connection to previously connect WiFi SSID, skipping scan of all SSID
|
|
||||||
fast_connect: "${wifi_fast_connect}"
|
|
||||||
# Define dns domain / suffix to add to hostname
|
|
||||||
domain: "${dns_domain}"
|
|
||||||
|
|
||||||
#captive_portal: # extra fallback mechanism for when connecting if the configured WiFi fails
|
|
||||||
|
|
||||||
#############################################
|
|
||||||
# Enable Over the Air Update Capability
|
|
||||||
# https://esphome.io/components/ota.html?highlight=ota
|
|
||||||
#############################################
|
|
||||||
ota:
|
|
||||||
- platform: esphome
|
|
||||||
password: ${local_ota_pass}
|
|
||||||
version: 2
|
|
||||||
|
|
||||||
#############################################
|
|
||||||
# Safe Mode
|
|
||||||
# Safe mode will detect boot loops
|
|
||||||
# https://esphome.io/components/safe_mode
|
|
||||||
#############################################
|
|
||||||
safe_mode:
|
|
||||||
|
|
||||||
#############################################
|
|
||||||
# Network
|
|
||||||
# global configuration for all types of networks
|
|
||||||
# https://esphome.io/components/network.html
|
|
||||||
#############################################
|
|
||||||
network:
|
|
||||||
enable_ipv6: ${ipv6_enable}
|
|
||||||
|
|
||||||
#############################################
|
|
||||||
# Interval
|
|
||||||
# Restart Networking every x hours + rand mins
|
|
||||||
#############################################
|
|
||||||
script:
|
|
||||||
- id: random_reconnect
|
|
||||||
mode: restart
|
|
||||||
then:
|
|
||||||
- lambda: |-
|
|
||||||
// Compute total delay: base hours + random offset minutes
|
|
||||||
uint32_t extra;
|
|
||||||
#if defined(ESP32)
|
|
||||||
// ESP32 (both Arduino & IDF builds) uses esp_random()
|
|
||||||
extra = esp_random() % (${random_offset_max_minutes} + 1);
|
|
||||||
#elif defined(ESP8266)
|
|
||||||
// ESP8266 Arduino core
|
|
||||||
extra = os_random() % (${random_offset_max_minutes} + 1);
|
|
||||||
#else
|
|
||||||
// Fallback to esp_random() on other platforms
|
|
||||||
extra = esp_random() % (${random_offset_max_minutes} + 1);
|
|
||||||
#endif
|
|
||||||
uint32_t total_s = ${base_interval_hours} * 3600 + extra * 60;
|
|
||||||
ESP_LOGI("random_reconnect", "Next reconnect in %u seconds", total_s);
|
|
||||||
// Delay inside lambda (blocks script execution but OK for reconnect timing)
|
|
||||||
delay(total_s * 1000);
|
|
||||||
- logger.log: "network_check: performing reconnect"
|
|
||||||
- wifi.disable: {}
|
|
||||||
- delay: 1s
|
|
||||||
- wifi.enable: {}
|
|
||||||
- script.execute: random_reconnect
|
|
||||||
|
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
substitutions:
|
|
||||||
##############################################
|
|
||||||
# SPECIFIC DEVICE VARIABLE SUBSTITUTIONS
|
|
||||||
# If NOT using a secrets file, just replace these with the passwords etc (in quotes)
|
|
||||||
#############################################
|
|
||||||
wifi_ssid: !secret ha_wifi_ssid
|
|
||||||
wifi_password: !secret ha_wifi_password
|
|
||||||
fallback_ap_password: !secret fallback_ap_password
|
|
||||||
|
|
||||||
# Enables faster network connections, with last connected SSID being connected to and no full scan for SSID being undertaken
|
|
||||||
wifi_fast_connect: "false"
|
|
||||||
|
|
||||||
# Define a domain for this device to use. i.e. iot.home.lan (so device will appear as athom-smart-plug-v2.iot.home.lan in DNS/DHCP logs)
|
|
||||||
dns_domain: ".local"
|
|
||||||
|
|
||||||
# Automatically add the mac address to the name
|
|
||||||
# eg so you can use a single firmware for all devices
|
|
||||||
add_mac_suffix: "false"
|
|
||||||
|
|
||||||
# Enable or disable the use of IPv6 networking on the device
|
|
||||||
ipv6_enable: "false"
|
|
||||||
|
|
||||||
# Add these if we are giving it a static ip, or remove them in the Wifi section
|
|
||||||
static_ip_subnet: !secret ha_wifi_subnet
|
|
||||||
static_ip_gateway: !secret ha_wifi_gateway
|
|
||||||
static_ip_dns1: !secret ha_wifi_gateway
|
|
||||||
|
|
||||||
#############################################
|
|
||||||
# Common Wifi Settings
|
|
||||||
# https://esphome.io/components/wifi.html
|
|
||||||
#
|
|
||||||
# Power Save mode (can reduce wifi reliability)
|
|
||||||
# NONE (least power saving, Default for ESP8266)
|
|
||||||
# LIGHT (Default for ESP32)
|
|
||||||
# HIGH (most power saving)
|
|
||||||
#############################################
|
|
||||||
wifi:
|
|
||||||
ssid: ${wifi_ssid}
|
|
||||||
password: ${wifi_password}
|
|
||||||
#enable_rrm: true # (ESP32 only) enable 802.11k Radio Resource Management
|
|
||||||
#enable_btm: true # (ESP32 only) enable 802.11v BSS Transition Management
|
|
||||||
#power_save_mode: LIGHT # https://esphome.io/components/wifi.html#wifi-power-save-mode
|
|
||||||
manual_ip: # optional static IP address
|
|
||||||
static_ip: ${local_static_ip_address}
|
|
||||||
gateway: ${static_ip_gateway}
|
|
||||||
subnet: ${static_ip_subnet}
|
|
||||||
dns1: ${static_ip_dns1}
|
|
||||||
ap: # Details for fallback hotspot in case wifi connection fails https://esphome.io/components/wifi.html#access-point-mode
|
|
||||||
ssid: ${device_name} AP
|
|
||||||
password: ${fallback_ap_password}
|
|
||||||
ap_timeout: 10min # Time until it brings up fallback AP. default is 1min
|
|
||||||
# Allow rapid re-connection to previously connect WiFi SSID, skipping scan of all SSID
|
|
||||||
fast_connect: "${wifi_fast_connect}"
|
|
||||||
# Define dns domain / suffix to add to hostname
|
|
||||||
domain: "${dns_domain}"
|
|
||||||
|
|
||||||
#captive_portal: # extra fallback mechanism for when connecting if the configured WiFi fails
|
|
||||||
|
|
||||||
#############################################
|
|
||||||
# Enable Over the Air Update Capability
|
|
||||||
# https://esphome.io/components/ota.html?highlight=ota
|
|
||||||
#############################################
|
|
||||||
ota:
|
|
||||||
- platform: esphome
|
|
||||||
password: ${local_ota_pass}
|
|
||||||
version: 2
|
|
||||||
|
|
||||||
#############################################
|
|
||||||
# Safe Mode
|
|
||||||
# Safe mode will detect boot loops
|
|
||||||
# https://esphome.io/components/safe_mode
|
|
||||||
#############################################
|
|
||||||
safe_mode:
|
|
||||||
|
|
||||||
#############################################
|
|
||||||
# Network
|
|
||||||
# global configuration for all types of networks
|
|
||||||
# https://esphome.io/components/network.html
|
|
||||||
#############################################
|
|
||||||
network:
|
|
||||||
enable_ipv6: ${ipv6_enable}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
<<: !include ../secrets.yaml
|
|
||||||
@@ -1,26 +1,58 @@
|
|||||||
|
substitutions:
|
||||||
|
local_update_interval: 30s
|
||||||
|
|
||||||
##########################################################################################
|
##########################################################################################
|
||||||
# GENERAL COMMON SENSORS
|
# GENERAL COMMON SENSORS
|
||||||
# https://esphome.io/components/sensor/
|
# https://esphome.io/components/sensor/
|
||||||
##########################################################################################
|
##########################################################################################
|
||||||
|
|
||||||
|
# Debug helpers (loop time / heap / reset info)
|
||||||
|
debug:
|
||||||
|
update_interval: ${local_update_interval}
|
||||||
|
|
||||||
sensor:
|
sensor:
|
||||||
- platform: uptime # Uptime for this device in seconds
|
# - platform: uptime # Uptime for this device in seconds
|
||||||
name: "Uptime (s):"
|
# name: "Uptime (s):"
|
||||||
update_interval: ${local_update_interval}
|
# update_interval: ${local_update_interval}
|
||||||
id: uptime_sensor
|
# id: uptime_sensor
|
||||||
entity_category: "diagnostic"
|
# entity_category: "diagnostic"
|
||||||
- platform: wifi_signal # Wifi Strength
|
|
||||||
name: "Wifi (dB):"
|
# - platform: wifi_signal # Wifi Strength
|
||||||
id: wifi_signal_db
|
# name: "Wifi (dB):"
|
||||||
update_interval: ${local_update_interval}
|
# id: wifi_signal_db
|
||||||
entity_category: "diagnostic"
|
# update_interval: ${local_update_interval}
|
||||||
- platform: copy # Reports the WiFi signal strength in %
|
# entity_category: "diagnostic"
|
||||||
source_id: wifi_signal_db
|
|
||||||
name: "WiFi (%):"
|
# - platform: copy # Reports the WiFi signal strength in %
|
||||||
filters:
|
# source_id: wifi_signal_db
|
||||||
- lambda: return min(max(2 * (x + 100.0), 0.0), 100.0);
|
# name: "WiFi (%):"
|
||||||
unit_of_measurement: "% Max"
|
# filters:
|
||||||
entity_category: "diagnostic"
|
# - lambda: return min(max(2 * (x + 100.0), 0.0), 100.0);
|
||||||
device_class: ""
|
# unit_of_measurement: "% Max"
|
||||||
|
# entity_category: "diagnostic"
|
||||||
|
# device_class: ""
|
||||||
|
|
||||||
|
# Free heap via debug (replaces template-based ESP.getFreeHeap if you prefer)
|
||||||
|
- platform: debug
|
||||||
|
free:
|
||||||
|
name: "Free Heap"
|
||||||
|
entity_category: "diagnostic"
|
||||||
|
|
||||||
|
# Loop time via debug (replaces broken template loop time)
|
||||||
|
- platform: debug
|
||||||
|
loop_time:
|
||||||
|
name: "Loop Time"
|
||||||
|
entity_category: "diagnostic"
|
||||||
|
|
||||||
|
# If you’d rather keep the legacy heap sensor, uncomment below and
|
||||||
|
# delete the debug "free" block above to avoid duplicates:
|
||||||
|
# - platform: template
|
||||||
|
# name: "${friendly_name} Free Heap"
|
||||||
|
# unit_of_measurement: "B"
|
||||||
|
# update_interval: 30s
|
||||||
|
# entity_category: "diagnostic"
|
||||||
|
# lambda: |-
|
||||||
|
# return (float) ESP.getFreeHeap();
|
||||||
|
|
||||||
# Only works with esp8266
|
# Only works with esp8266
|
||||||
#- platform: adc
|
#- platform: adc
|
||||||
@@ -29,11 +61,11 @@ sensor:
|
|||||||
# id: vcc_voltage_sensor
|
# id: vcc_voltage_sensor
|
||||||
# entity_category: diagnostic
|
# entity_category: diagnostic
|
||||||
|
|
||||||
binary_sensor:
|
#binary_sensor:
|
||||||
- platform: status
|
# - platform: status
|
||||||
name: "Network Status"
|
# name: "Network Status"
|
||||||
icon: mdi:check-network-outline
|
# icon: mdi:check-network-outline
|
||||||
entity_category: diagnostic
|
# entity_category: diagnostic
|
||||||
|
|
||||||
##########################################################################################
|
##########################################################################################
|
||||||
# Text Sensors
|
# Text Sensors
|
||||||
@@ -46,6 +78,7 @@ text_sensor:
|
|||||||
- platform: version
|
- platform: version
|
||||||
name: "Version:"
|
name: "Version:"
|
||||||
entity_category: "diagnostic"
|
entity_category: "diagnostic"
|
||||||
|
|
||||||
- platform: wifi_info
|
- platform: wifi_info
|
||||||
ip_address:
|
ip_address:
|
||||||
icon: mdi:ip-network
|
icon: mdi:ip-network
|
||||||
@@ -59,37 +92,12 @@ text_sensor:
|
|||||||
name: "MAC Address:"
|
name: "MAC Address:"
|
||||||
icon: mdi:network-pos
|
icon: mdi:network-pos
|
||||||
entity_category: diagnostic
|
entity_category: diagnostic
|
||||||
|
|
||||||
- platform: uptime # Uptime for this device human readable
|
- platform: uptime # Uptime for this device human readable
|
||||||
name: "Uptime:"
|
name: "Uptime:"
|
||||||
icon: mdi:clock-start
|
icon: mdi:clock-start
|
||||||
update_interval: ${local_update_interval}
|
update_interval: ${local_update_interval}
|
||||||
entity_category: "diagnostic"
|
entity_category: "diagnostic"
|
||||||
- platform: template
|
|
||||||
name: "Last Reset Reason"
|
|
||||||
update_interval: 30s
|
|
||||||
entity_category: diagnostic
|
|
||||||
lambda: |-
|
|
||||||
#if defined(USE_ESP8266)
|
|
||||||
return { ESP.getResetReason().c_str() };
|
|
||||||
#elif defined(USE_ESP32)
|
|
||||||
auto r = esp_reset_reason();
|
|
||||||
switch (r) {
|
|
||||||
case ESP_RST_POWERON: return { "Power-on" };
|
|
||||||
case ESP_RST_EXT: return { "External pin" };
|
|
||||||
case ESP_RST_SW: return { "Software reset" };
|
|
||||||
case ESP_RST_PANIC: return { "Exception/Panic" };
|
|
||||||
case ESP_RST_INT_WDT: return { "Interrupt WDT" };
|
|
||||||
case ESP_RST_TASK_WDT: return { "Task WDT" };
|
|
||||||
case ESP_RST_WDT: return { "Other WDT" };
|
|
||||||
case ESP_RST_DEEPSLEEP: return { "Deep sleep wake" };
|
|
||||||
case ESP_RST_BROWNOUT: return { "Brownout" };
|
|
||||||
case ESP_RST_SDIO: return { "SDIO" };
|
|
||||||
default: return { "Unknown" };
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
return { "Unknown" };
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
|
||||||
###################################################################################################
|
###################################################################################################
|
||||||
# Creates a sensor of the uptime of the device, in formatted days, hours, minutes and seconds
|
# Creates a sensor of the uptime of the device, in formatted days, hours, minutes and seconds
|
||||||
@@ -118,44 +126,23 @@ text_sensor:
|
|||||||
# }
|
# }
|
||||||
# icon: mdi:clock-start
|
# icon: mdi:clock-start
|
||||||
|
|
||||||
# - platform: template
|
|
||||||
# name: "Uptime (Days)"
|
|
||||||
# entity_category: diagnostic
|
|
||||||
# lambda: |-
|
|
||||||
# int seconds = (id(uptime_sensor).state);
|
|
||||||
# int days = seconds / (24 * 3600);
|
|
||||||
# seconds = seconds % (24 * 3600);
|
|
||||||
# int hours = seconds / 3600;
|
|
||||||
# seconds = seconds % 3600;
|
|
||||||
# int minutes = seconds / 60;
|
|
||||||
# seconds = seconds % 60;
|
|
||||||
# if ( days > 3650 ) {
|
|
||||||
# return { "Starting up" };
|
|
||||||
# } else if ( days ) {
|
|
||||||
# return { (String(days) +"d " + String(hours) +"h " + String(minutes) +"m "+ String(seconds) +"s").c_str() };
|
|
||||||
# } else if ( hours ) {
|
|
||||||
# return { (String(hours) +"h " + String(minutes) +"m "+ String(seconds) +"s").c_str() };
|
|
||||||
# } else if ( minutes ) {
|
|
||||||
# return { (String(minutes) +"m "+ String(seconds) +"s").c_str() };
|
|
||||||
# } else {
|
|
||||||
# return { (String(seconds) +"s").c_str() };
|
|
||||||
# }
|
|
||||||
# icon: mdi:clock-start
|
|
||||||
|
|
||||||
##########################################################################################
|
##########################################################################################
|
||||||
# BUTTONS
|
# BUTTONS
|
||||||
# Diagnostic buttons, activated if needed in HA
|
# Diagnostic buttons, activated if needed in HA
|
||||||
##########################################################################################
|
##########################################################################################
|
||||||
button:
|
#button:
|
||||||
- platform: safe_mode
|
# - platform: safe_mode
|
||||||
name: "Safe Mode Restart:"
|
# name: "Safe Mode Restart:"
|
||||||
entity_category: "diagnostic"
|
# entity_category: "diagnostic"
|
||||||
disabled_by_default: true
|
# disabled_by_default: true
|
||||||
- platform: restart
|
#
|
||||||
name: "Restart:"
|
# - platform: restart
|
||||||
entity_category: "diagnostic"
|
# name: "Restart:"
|
||||||
disabled_by_default: true
|
# entity_category: "diagnostic"
|
||||||
- platform: factory_reset
|
# disabled_by_default: true
|
||||||
name: "FACTORY RESET:"
|
#
|
||||||
entity_category: "diagnostic"
|
# - platform: factory_reset
|
||||||
disabled_by_default: true
|
# name: "FACTORY RESET:"
|
||||||
|
# entity_category: "diagnostic"
|
||||||
|
# disabled_by_default: true
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
substitutions:
|
||||||
|
local_update_interval: 30s
|
||||||
|
|
||||||
##########################################################################################
|
##########################################################################################
|
||||||
# GENERAL COMMON SENSORS
|
# GENERAL COMMON SENSORS
|
||||||
# https://esphome.io/components/sensor/
|
# https://esphome.io/components/sensor/
|
||||||
@@ -16,6 +19,15 @@ sensor:
|
|||||||
update_interval: ${local_update_interval}
|
update_interval: ${local_update_interval}
|
||||||
entity_category: diagnostic
|
entity_category: diagnostic
|
||||||
|
|
||||||
|
- platform: copy # Reports the WiFi signal strength in %
|
||||||
|
source_id: wifi_signal_db
|
||||||
|
name: "WiFi (%):"
|
||||||
|
filters:
|
||||||
|
- lambda: return min(max(2 * (x + 100.0), 0.0), 100.0);
|
||||||
|
unit_of_measurement: "% Max"
|
||||||
|
entity_category: "diagnostic"
|
||||||
|
device_class: ""
|
||||||
|
|
||||||
binary_sensor:
|
binary_sensor:
|
||||||
- platform: status
|
- platform: status
|
||||||
name: "Network Status"
|
name: "Network Status"
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
##############################################
|
############################################################################################
|
||||||
# SPECIFIC DEVICE VARIABLE SUBSTITUTIONS
|
# SPECIFIC DEVICE VARIABLE SUBSTITUTIONS
|
||||||
# If NOT using a secrets file, just replace
|
# If NOT using a secrets file, just replace
|
||||||
# these with the values (in quotes)
|
# these with the values (in quotes)
|
||||||
#############################################
|
###########################################################################################
|
||||||
substitutions:
|
substitutions:
|
||||||
timezone: "Pacific/Auckland"
|
timezone: "Pacific/Auckland"
|
||||||
sntp_update_interval: 6h # Set the duration between the sntp service polling
|
sntp_update_interval: 6h # Set the duration between the sntp service polling
|
||||||
@@ -11,12 +11,12 @@ substitutions:
|
|||||||
sntp_server_2: !secret ntp_server_2
|
sntp_server_2: !secret ntp_server_2
|
||||||
sntp_server_3: !secret ntp_server_3
|
sntp_server_3: !secret ntp_server_3
|
||||||
|
|
||||||
#############################################
|
###########################################################################################
|
||||||
# Real time clock time source for ESPHome
|
# Real time clock time source for ESPHome
|
||||||
# If it's invalid, we fall back to an internal clock
|
# If it's invalid, we fall back to an internal clock
|
||||||
# https://esphome.io/components/time/index.html
|
# https://esphome.io/components/time/index.html
|
||||||
# https://esphome.io/components/time/sntp
|
# https://esphome.io/components/time/sntp
|
||||||
#############################################
|
###########################################################################################
|
||||||
time:
|
time:
|
||||||
- platform: sntp
|
- platform: sntp
|
||||||
id: sntp_time
|
id: sntp_time
|
||||||
@@ -53,14 +53,14 @@ time:
|
|||||||
text_sensor:
|
text_sensor:
|
||||||
# Creates a sensor showing when the device was last restarted
|
# Creates a sensor showing when the device was last restarted
|
||||||
- platform: template
|
- platform: template
|
||||||
name: "Last Restart"
|
name: "Time: Last Restart"
|
||||||
id: device_last_restart
|
id: device_last_restart
|
||||||
icon: mdi:clock
|
icon: mdi:clock
|
||||||
entity_category: diagnostic
|
entity_category: diagnostic
|
||||||
#device_class: timestamp
|
#device_class: timestamp
|
||||||
|
|
||||||
- platform: template
|
- platform: template
|
||||||
name: "Internal Time"
|
name: "Time: Internal Time"
|
||||||
id: time_text
|
id: time_text
|
||||||
update_interval: "1min"
|
update_interval: "1min"
|
||||||
entity_category: diagnostic
|
entity_category: diagnostic
|
||||||
@@ -69,7 +69,7 @@ text_sensor:
|
|||||||
return { time_text };
|
return { time_text };
|
||||||
|
|
||||||
- platform: template
|
- platform: template
|
||||||
name: "Time Sync Status"
|
name: "Time: Sync Status"
|
||||||
id: time_sync
|
id: time_sync
|
||||||
update_interval: "1min"
|
update_interval: "1min"
|
||||||
entity_category: diagnostic
|
entity_category: diagnostic
|
||||||
@@ -17,47 +17,69 @@
|
|||||||
# If NOT using a secrets file, just replace these with the passwords etc (in quotes)
|
# If NOT using a secrets file, just replace these with the passwords etc (in quotes)
|
||||||
#############################################
|
#############################################
|
||||||
substitutions:
|
substitutions:
|
||||||
device_name: "esp-3dprinterpow" # Reference name for the device in the system.
|
# Device Naming
|
||||||
project_name: "Sonoff Technologies.POW R1" # Project Details
|
device_name: "esp-3dprinterpow"
|
||||||
project_version: "v1" # Project V denotes release of yaml file, allowing checking of deployed vs latest version
|
|
||||||
entity_prefix: "Load" # Simple device name where we want to prefix a sensor or switch, eg "Load" Current.
|
|
||||||
friendly_name: "3D Printer Power"
|
friendly_name: "3D Printer Power"
|
||||||
description_comment: "3D Printer inline monitor and switch override using a Sonoff Pow R1."
|
description_comment: "3D Printer inline monitor and switch override using a Sonoff Pow R1."
|
||||||
device_area: "Laundry" # Allows ESP device to be automatically linked to an 'Area' in Home Assistant.
|
device_area: "Bedroom 3" # Allows ESP device to be automatically linked to an 'Area' in Home Assistant.
|
||||||
api_key: !secret esp-api_key # unfortunately you can't use substitutions inside secrets names
|
|
||||||
ota_pass: !secret esp-ota_pass # unfortunately you can't use substitutions inside secrets names
|
# Project Naming
|
||||||
static_ip_address: !secret esp-3dprinterpow_ip
|
project_name: "Sonoff Technologies.POW R1" # Project Details
|
||||||
|
project_version: "v1" # Project V denotes release of yaml file, allowing checking of deployed vs latest version
|
||||||
|
|
||||||
|
# Passwords & Secrets
|
||||||
|
api_key: !secret esp-api_key
|
||||||
|
ota_pass: !secret esp-ota_pass
|
||||||
|
static_ip_address: !secret esp-3dprinterpow_ip # unfortunately you can't use substitutions inside secrets names
|
||||||
|
mqtt_command_main_topic: !secret mqtt_command_main_topic
|
||||||
|
mqtt_status_main_topic: !secret mqtt_status_main_topic
|
||||||
|
|
||||||
|
# MQTT LOCAL Controls
|
||||||
|
mqtt_local_device_name: "3dprinter-power"
|
||||||
|
mqtt_local_command_topic: "${mqtt_command_main_topic}/${mqtt_local_device_name}" # Topic we will use to command this locally without HA
|
||||||
|
mqtt_local_status_topic: "${mqtt_status_main_topic}/${mqtt_local_device_name}" # Topic we will use to view status locally without HA
|
||||||
|
|
||||||
|
# Device Settings
|
||||||
log_level: "INFO" # Define logging level: NONE, ERROR, WARN, INFO, DEBUG (Default), VERBOSE, VERY_VERBOSE
|
log_level: "INFO" # Define logging level: NONE, ERROR, WARN, INFO, DEBUG (Default), VERBOSE, VERY_VERBOSE
|
||||||
update_interval: "60s" # update time for for general sensors etc
|
update_interval: "60s" # update time for for general sensors etc
|
||||||
|
entity_prefix: "Load" # Simple device name where we want to prefix a sensor or switch, eg "Load" Current.
|
||||||
|
|
||||||
##########################################################################################
|
##########################################################################################
|
||||||
# PACKAGES: Included Common Packages
|
# PACKAGES: Included Common Packages
|
||||||
# https://esphome.io/components/packages.html
|
# https://esphome.io/components/packages.html
|
||||||
##########################################################################################
|
##########################################################################################
|
||||||
packages:
|
packages:
|
||||||
|
#### WIFI, Network (Static/DHCP/IPV6 etc), Fallback AP, Safemode ####
|
||||||
common_wifi: !include
|
common_wifi: !include
|
||||||
file: common/network_common.yaml
|
file: common/network_common.yaml
|
||||||
vars:
|
vars:
|
||||||
local_device_name: "${device_name}"
|
local_device_name: "${device_name}"
|
||||||
local_static_ip_address: "${static_ip_address}"
|
local_static_ip_address: "${static_ip_address}"
|
||||||
local_ota_pass: "${ota_pass}"
|
local_ota_pass: "${ota_pass}"
|
||||||
|
|
||||||
|
#### HOME ASSISTANT API (choose encryption or no encryption options) ####
|
||||||
common_api: !include
|
common_api: !include
|
||||||
file: common/api_common.yaml
|
file: common/api_common.yaml
|
||||||
|
#file: common/api_common_noencryption.yaml
|
||||||
vars:
|
vars:
|
||||||
local_api_key: "${api_key}"
|
local_api_key: "${api_key}"
|
||||||
#common_webportal: !include
|
|
||||||
# file: common/webportal_common.yaml
|
#### MQTT ####
|
||||||
common_mqtt: !include
|
common_mqtt: !include
|
||||||
file: common/mqtt_common.yaml
|
file: common/mqtt_common.yaml
|
||||||
vars:
|
vars:
|
||||||
local_device_name: "${device_name}"
|
local_device_name: "${device_name}"
|
||||||
common_sntp: !include
|
|
||||||
file: common/sntp_common.yaml
|
#### WEB PORTAL ####
|
||||||
common_general_sensors: !include
|
#common_webportal: !include common/webportal_common.yaml
|
||||||
file: common/sensors_common.yaml
|
#### SNTP (Only use if you want/need accurate timeclocks) ####
|
||||||
vars:
|
common_sntp: !include common/sntp_common.yaml
|
||||||
local_friendly_name: "${friendly_name}"
|
|
||||||
local_update_interval: "${update_interval}"
|
#### DIAGNOSTICS Sensors ####
|
||||||
|
diag_basic: !include common/include_basic_diag_sensors.yaml
|
||||||
|
diag_more: !include common/include_more_diag_sensors.yaml
|
||||||
|
diag_debug: !include common/include_debug_diag_sensors.yaml
|
||||||
|
#diag_resetcount: !include common/include_resetcount_diag_sensors.yaml
|
||||||
|
|
||||||
#############################################
|
#############################################
|
||||||
# ESPHome
|
# ESPHome
|
||||||
@@ -71,10 +93,10 @@ esphome:
|
|||||||
project:
|
project:
|
||||||
name: "${project_name}"
|
name: "${project_name}"
|
||||||
version: "${project_version}"
|
version: "${project_version}"
|
||||||
#on_boot:
|
on_boot:
|
||||||
#priority: 200
|
priority: -10
|
||||||
#then:
|
then:
|
||||||
# - switch.turn_on: "virtual_button"
|
- script.execute: publish_state_burst
|
||||||
|
|
||||||
#############################################
|
#############################################
|
||||||
# ESP Platform and Framework
|
# ESP Platform and Framework
|
||||||
@@ -95,6 +117,24 @@ logger:
|
|||||||
#esp8266_store_log_strings_in_flash: false
|
#esp8266_store_log_strings_in_flash: false
|
||||||
#tx_buffer_size: 64
|
#tx_buffer_size: 64
|
||||||
|
|
||||||
|
##########################################################################################
|
||||||
|
# MQTT COMMANDS
|
||||||
|
# This adds device-specific MQTT command triggers to the common MQTT configuration.
|
||||||
|
##########################################################################################
|
||||||
|
mqtt:
|
||||||
|
on_message:
|
||||||
|
# Switch control from MQTT
|
||||||
|
- topic: "${mqtt_local_command_topic}"
|
||||||
|
payload: "ON"
|
||||||
|
then:
|
||||||
|
- switch.turn_on: relay
|
||||||
|
- light.turn_on: led
|
||||||
|
- topic: "${mqtt_local_command_topic}"
|
||||||
|
payload: "OFF"
|
||||||
|
then:
|
||||||
|
- switch.turn_off: relay
|
||||||
|
- light.turn_off: led
|
||||||
|
|
||||||
#############################################
|
#############################################
|
||||||
# STATUS LED
|
# STATUS LED
|
||||||
# https://esphome.io/components/status_led.html
|
# https://esphome.io/components/status_led.html
|
||||||
@@ -164,10 +204,22 @@ switch:
|
|||||||
turn_off_action:
|
turn_off_action:
|
||||||
- switch.turn_off: relay
|
- switch.turn_off: relay
|
||||||
- light.turn_off: led
|
- light.turn_off: led
|
||||||
|
|
||||||
- platform: gpio
|
- platform: gpio
|
||||||
id: relay
|
id: relay
|
||||||
pin: GPIO12
|
pin: GPIO12
|
||||||
#restore_mode: ALWAYS_ON # Or RESTORE_DEFAULT_ON/RESTORE_DEFAULT_OFF
|
restore_mode: RESTORE_DEFAULT_ON
|
||||||
|
# restore_mode: ALWAYS_ON # Or RESTORE_DEFAULT_ON/RESTORE_DEFAULT_OFF
|
||||||
|
on_turn_on:
|
||||||
|
- mqtt.publish:
|
||||||
|
topic: "${mqtt_local_status_topic}"
|
||||||
|
payload: "ON"
|
||||||
|
retain: false
|
||||||
|
on_turn_off:
|
||||||
|
- mqtt.publish:
|
||||||
|
topic: "${mqtt_local_status_topic}"
|
||||||
|
payload: "OFF"
|
||||||
|
retain: false
|
||||||
|
|
||||||
output:
|
output:
|
||||||
- platform: esp8266_pwm
|
- platform: esp8266_pwm
|
||||||
@@ -181,3 +233,17 @@ light:
|
|||||||
name: Status LED
|
name: Status LED
|
||||||
output: pow_blue_led
|
output: pow_blue_led
|
||||||
id: led
|
id: led
|
||||||
|
|
||||||
|
script:
|
||||||
|
- id: publish_state_burst
|
||||||
|
mode: restart
|
||||||
|
then:
|
||||||
|
- repeat:
|
||||||
|
count: 5
|
||||||
|
then:
|
||||||
|
- mqtt.publish:
|
||||||
|
topic: "${mqtt_local_status_topic}"
|
||||||
|
payload: !lambda 'return id(relay).state ? "ON" : "OFF";'
|
||||||
|
retain: false
|
||||||
|
qos: 1
|
||||||
|
- delay: 5s
|
||||||
@@ -85,6 +85,7 @@ api:
|
|||||||
ota:
|
ota:
|
||||||
- platform: esphome
|
- platform: esphome
|
||||||
password: ${ota_pass}
|
password: ${ota_pass}
|
||||||
|
- platform: web_server # Uncomment if you want to be able to do OTA with the web interface
|
||||||
|
|
||||||
#############################################
|
#############################################
|
||||||
# Safe Mode
|
# Safe Mode
|
||||||
@@ -0,0 +1,180 @@
|
|||||||
|
##########################################################################################
|
||||||
|
##########################################################################################
|
||||||
|
# BYD ATTO3 12V Battery Monitor
|
||||||
|
# Monitoring the status of a vehicle 12V battery with
|
||||||
|
# an esp8266 (D1 Mini). It will obviously only
|
||||||
|
# transmit when the vehicle is within wifi range.
|
||||||
|
# Voltage is measured with a resistor voltage divider
|
||||||
|
# into the analogue GPIO on the esp8266.
|
||||||
|
# https://zorruno.com/2022/vehicle-12v-battery-monitoring/
|
||||||
|
##########################################################################################
|
||||||
|
##########################################################################################
|
||||||
|
|
||||||
|
##########################################################################################
|
||||||
|
# SPECIFIC DEVICE VARIABLE SUBSTITUTIONS
|
||||||
|
# If NOT using a secrets file, just replace these with the passwords etc (in quotes)
|
||||||
|
##########################################################################################
|
||||||
|
substitutions:
|
||||||
|
# Device Naming
|
||||||
|
device_name: "esp-attobattery"
|
||||||
|
friendly_name: "Atto3 12V Battery Monitor"
|
||||||
|
description_comment: "Atto3 12V Battery Monitor (when home)"
|
||||||
|
device_area: "Garage" # Allows ESP device to be automatically linked to an 'Area' in Home Assistant.
|
||||||
|
|
||||||
|
# Project Naming
|
||||||
|
project_name: "Generic ESP8266.D1 Mini" # Project Details
|
||||||
|
project_version: "v1.0" # Project V denotes release of yaml file, allowing checking of deployed vs latest version
|
||||||
|
|
||||||
|
# Passwords & Secrets
|
||||||
|
api_key: !secret esp-api_key # unfortunately you can't use substitutions inside secrets names
|
||||||
|
ota_pass: !secret esp-ota_pass # unfortunately you can't use substitutions inside secrets names
|
||||||
|
static_ip_address: !secret esp-attobattery_ip
|
||||||
|
mqtt_command_main_topic: !secret mqtt_command_main_topic
|
||||||
|
mqtt_status_main_topic: !secret mqtt_status_main_topic
|
||||||
|
|
||||||
|
# MQTT LOCAL Controls
|
||||||
|
mqtt_device_name: "atto-battery"
|
||||||
|
mqtt_local_command_topic: "${mqtt_command_main_topic}/${mqtt_device_name}" # Topic we will use to command this locally without HA
|
||||||
|
mqtt_local_status_topic: "${mqtt_status_main_topic}/${mqtt_device_name}" # Topic we will use to view status locally without HA
|
||||||
|
|
||||||
|
# Device Settings
|
||||||
|
#relay_icon: "mdi:lightbulb-group"
|
||||||
|
log_level: "DEBUG" # Define logging level: NONE, ERROR, WARN, INFO, DEBUG (Default), VERBOSE, VERY_VERBOSE
|
||||||
|
update_interval: "60s" # update time for for general sensors etc
|
||||||
|
|
||||||
|
##########################################################################################
|
||||||
|
# ESPHome
|
||||||
|
# https://esphome.io/components/esphome.html
|
||||||
|
##########################################################################################
|
||||||
|
esphome:
|
||||||
|
name: ${device_name}
|
||||||
|
friendly_name: ${friendly_name}
|
||||||
|
comment: ${description_comment} #appears on the esphome page in HA
|
||||||
|
min_version: 2024.6.0
|
||||||
|
|
||||||
|
##########################################################################################
|
||||||
|
# ESP Platform and Framework
|
||||||
|
# https://esphome.io/components/esp8266.html
|
||||||
|
# https://esphome.io/components/esp32.html
|
||||||
|
##########################################################################################
|
||||||
|
esp8266:
|
||||||
|
board: d1_mini
|
||||||
|
framework:
|
||||||
|
version: latest #recommended, latest or dev
|
||||||
|
|
||||||
|
##########################################################################################
|
||||||
|
# ESPHome Logging Enable
|
||||||
|
# https://esphome.io/components/logger.html
|
||||||
|
##########################################################################################
|
||||||
|
logger:
|
||||||
|
level: "${log_level}" #INFO Level suggested, or DEBUG for testing
|
||||||
|
#baud_rate: 0 #set to 0 for no logging via UART, needed if you are using it for other serial things (eg PZEM)
|
||||||
|
#esp8266_store_log_strings_in_flash: false
|
||||||
|
#tx_buffer_size: 64
|
||||||
|
|
||||||
|
#############################################
|
||||||
|
# Enable Over the Air Update Capability
|
||||||
|
# https://esphome.io/components/ota.html?highlight=ota
|
||||||
|
#############################################
|
||||||
|
ota:
|
||||||
|
- platform: web_server # Uncomment if you want to be able to do OTA with the web interface
|
||||||
|
|
||||||
|
##########################################################################################
|
||||||
|
# PACKAGES: Included Common Packages
|
||||||
|
# https://esphome.io/components/packages.html
|
||||||
|
##########################################################################################
|
||||||
|
packages:
|
||||||
|
common_wifi: !include
|
||||||
|
file: common/network_common.yaml
|
||||||
|
vars:
|
||||||
|
local_device_name: "${device_name}"
|
||||||
|
local_static_ip_address: "${static_ip_address}"
|
||||||
|
local_ota_pass: "${ota_pass}"
|
||||||
|
common_api: !include
|
||||||
|
#file: common/api_common_noencryption.yaml
|
||||||
|
file: common/api_common.yaml
|
||||||
|
vars:
|
||||||
|
local_api_key: "${api_key}"
|
||||||
|
common_webportal: !include
|
||||||
|
file: common/webportal_common.yaml
|
||||||
|
common_mqtt: !include
|
||||||
|
file: common/mqtt_common.yaml
|
||||||
|
vars:
|
||||||
|
local_device_name: "${device_name}"
|
||||||
|
#common_sntp: !include
|
||||||
|
# file: common/sntp_common.yaml
|
||||||
|
common_general_sensors: !include
|
||||||
|
file: common/sensors_common_lite.yaml
|
||||||
|
#file: common/sensors_common.yaml
|
||||||
|
vars:
|
||||||
|
local_friendly_name: "${friendly_name}"
|
||||||
|
local_update_interval: "${update_interval}"
|
||||||
|
|
||||||
|
##########################################################################################
|
||||||
|
# MQTT COMMANDS
|
||||||
|
# This adds device-specific MQTT command triggers to the common MQTT configuration.
|
||||||
|
##########################################################################################
|
||||||
|
mqtt:
|
||||||
|
|
||||||
|
# Availability Topic
|
||||||
|
birth_message:
|
||||||
|
topic: ${mqtt_local_status_topic}/availability
|
||||||
|
payload: online
|
||||||
|
will_message:
|
||||||
|
topic: ${mqtt_local_status_topic}/availability
|
||||||
|
payload: offline
|
||||||
|
|
||||||
|
# A way to prevent deep sleep using MQTT command
|
||||||
|
on_message:
|
||||||
|
- topic: ${mqtt_local_command_topic}/deepsleep
|
||||||
|
payload: "OFF"
|
||||||
|
then:
|
||||||
|
- deep_sleep.prevent: deep_sleep_1
|
||||||
|
- topic: ${mqtt_local_command_topic}/deepsleep
|
||||||
|
payload: "ON"
|
||||||
|
then:
|
||||||
|
- deep_sleep.enter: deep_sleep_1
|
||||||
|
|
||||||
|
##########################################################################################
|
||||||
|
# Deep Sleep
|
||||||
|
# https://esphome.io/components/deep_sleep.html
|
||||||
|
##########################################################################################
|
||||||
|
deep_sleep:
|
||||||
|
run_duration: 20s
|
||||||
|
sleep_duration: 5min
|
||||||
|
id: deep_sleep_1
|
||||||
|
|
||||||
|
##########################################################################################
|
||||||
|
# SENSOR COMPONENT
|
||||||
|
# https://esphome.io/components/sensor/
|
||||||
|
##########################################################################################
|
||||||
|
sensor:
|
||||||
|
#Quality of Wifi in dBm
|
||||||
|
- platform: wifi_signal
|
||||||
|
name: "WiFi Signal"
|
||||||
|
update_interval: ${update_interval}
|
||||||
|
retain: true #Retain this as you'll have no value between sleeps otherwise
|
||||||
|
#Analog sensor for voltage reading (A0)
|
||||||
|
- platform: uptime
|
||||||
|
name: "Uptime"
|
||||||
|
update_interval: ${update_interval}
|
||||||
|
retain: true #Retain this as you'll have no value between sleeps otherwise
|
||||||
|
- platform: adc
|
||||||
|
pin: A0
|
||||||
|
name: "Battery Voltage"
|
||||||
|
update_interval: ${update_interval}
|
||||||
|
retain: true #Retain this as you'll have no value between sleeps otherwise
|
||||||
|
filters:
|
||||||
|
- multiply: 3.3 #D1 mini V divider, 3.3V -> 1V on esp8266
|
||||||
|
- calibrate_linear: #Read values with voltmeter and bench supply
|
||||||
|
- 3.11 -> 14.00
|
||||||
|
- 3.00 -> 13.50
|
||||||
|
- 2.89 -> 13.00
|
||||||
|
- 2.77 -> 12.50
|
||||||
|
- 2.67 -> 12.00
|
||||||
|
- 2.55 -> 11.50
|
||||||
|
- 2.45 -> 11.00
|
||||||
|
- 2.34 -> 10.50
|
||||||
|
- 2.22 -> 10.00
|
||||||
|
- 2.11 -> 09.50
|
||||||
|
- 2.00 -> 09.00
|
||||||
@@ -153,6 +153,60 @@ logger:
|
|||||||
#esp8266_store_log_strings_in_flash: false
|
#esp8266_store_log_strings_in_flash: false
|
||||||
#tx_buffer_size: 64
|
#tx_buffer_size: 64
|
||||||
|
|
||||||
|
##########################################################################################
|
||||||
|
# UART: Fan MCU / RF Remote Sniff (iFan02 -> ESP over UART0)
|
||||||
|
# - Uses GPIO3 (RX0) to listen to the fan microcontroller frames (433 MHz remote is decoded there)
|
||||||
|
# - 9600 8N1 is typical; if no frames appear, try 19200 (uncomment below)
|
||||||
|
# - If still nothing, some revisions route MCU TX to another pin; try rx_pin: GPIO13
|
||||||
|
##########################################################################################
|
||||||
|
uart:
|
||||||
|
id: ifan_uart
|
||||||
|
tx_pin: GPIO1 # ESP TX0 -> Fan MCU RX (not required for sniffing, but harmless)
|
||||||
|
rx_pin: GPIO3 # ESP RX0 <- Fan MCU TX (primary source for RF button frames)
|
||||||
|
baud_rate: 9600
|
||||||
|
#baud_rate: 19200 # <-- try this if you see no frames in logs
|
||||||
|
rx_buffer_size: 256
|
||||||
|
# NOTE: If no frames appear at either 9600 or 19200, try moving rx_pin to GPIO13 and reflash:
|
||||||
|
# rx_pin: GPIO13
|
||||||
|
|
||||||
|
##########################################################################################
|
||||||
|
# INTERVAL: Accumulate UART bytes and publish last frame to text sensor
|
||||||
|
# - Publishes after ~60ms of silence to group a single button press into one frame
|
||||||
|
##########################################################################################
|
||||||
|
interval:
|
||||||
|
- interval: 50ms
|
||||||
|
then:
|
||||||
|
- lambda: |-
|
||||||
|
static std::string frame;
|
||||||
|
static uint32_t last = 0;
|
||||||
|
uint8_t b;
|
||||||
|
while (id(ifan_uart).available()) {
|
||||||
|
if (id(ifan_uart).read_byte(&b)) {
|
||||||
|
char buf[4];
|
||||||
|
sprintf(buf, "%02X", b);
|
||||||
|
frame += buf;
|
||||||
|
frame += " ";
|
||||||
|
last = millis();
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!frame.empty() && (millis() - last > 60)) {
|
||||||
|
ESP_LOGI("ifan02", "MCU frame: %s", frame.c_str());
|
||||||
|
id(ifan_last_rf).publish_state(frame.c_str());
|
||||||
|
frame.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
##########################################################################################
|
||||||
|
# TEXT SENSORS: RF/MCU debug output (last frame seen)
|
||||||
|
# - Shows the last raw hex bytes received from the fan MCU when you press the RF remote
|
||||||
|
##########################################################################################
|
||||||
|
text_sensor:
|
||||||
|
- platform: template
|
||||||
|
id: ifan_last_rf
|
||||||
|
name: "iFan02 Remote Last Frame"
|
||||||
|
update_interval: never
|
||||||
|
|
||||||
##########################################################################################
|
##########################################################################################
|
||||||
# MQTT COMMANDS
|
# MQTT COMMANDS
|
||||||
# This adds device-specific MQTT command triggers to the common MQTT configuration.
|
# This adds device-specific MQTT command triggers to the common MQTT configuration.
|
||||||
@@ -371,7 +425,7 @@ fan:
|
|||||||
# whenever the fan goes off→on (e.g. via HA’s Fan switch):
|
# whenever the fan goes off→on (e.g. via HA’s Fan switch):
|
||||||
on_turn_on:
|
on_turn_on:
|
||||||
- lambda: |-
|
- lambda: |-
|
||||||
// bump 0→1 if we were off
|
// bump 0->1 if we were off
|
||||||
if (id(speed_value) == 0) id(speed_value) = 1;
|
if (id(speed_value) == 0) id(speed_value) = 1;
|
||||||
- fan.turn_on:
|
- fan.turn_on:
|
||||||
id: ifan02_fan
|
id: ifan02_fan
|
||||||
|
|||||||
+320
-132
@@ -1,103 +1,132 @@
|
|||||||
##########################################################################################
|
##########################################################################################
|
||||||
##########################################################################################
|
##########################################################################################
|
||||||
# Title: BREAKFAST BAR LEDS (Lounge)
|
# Title: BREAKFAST BAR LEDS (Lounge)
|
||||||
# Hardware: Magichome inline single channel dimmer (ESP8266)
|
# Repo: https://home.fox.co.nz/gitea/zorruno/zorruno-homeassistant/src/branch/master/esphome/esp-loungebookshelfleds.yaml
|
||||||
#
|
|
||||||
# Repo:
|
|
||||||
#
|
#
|
||||||
|
# v2.0 - 2025-09-15 A bunch of fixes and made it more standard across my devices
|
||||||
# v1.0 - 2025-08-17 First setup (and replacement of Tasmota)
|
# v1.0 - 2025-08-17 First setup (and replacement of Tasmota)
|
||||||
#
|
#
|
||||||
# ------------------------------------------
|
# ------------------------------------------
|
||||||
|
# OPERATION (as of v2.0)
|
||||||
|
# ------------------------------------------
|
||||||
|
# 1. General-purpose LED controller.
|
||||||
|
# 2. Designed for the Sinilink XY-WFMS board with a MOSFET output (claimed 5A, 5-36 V DC), but will suit similar mosfet boards.
|
||||||
|
# 3. (In SUBSTITUTIONS) setting for MAX % output to extend LED life (ie can ensure it is only ever 95%)
|
||||||
|
# 4. (In SUBSTITUTIONS) Minimum output setting; switches fully OFF at/below the minimum to avoid low-PWM flicker.
|
||||||
|
# 5. (In SUBSTITUTIONS) PWM frequency is set to 1kHz by default. You can increase it, but higher values caused
|
||||||
|
# resets on Sinilink device. On ESP32 you can run much higher (~40 kHz).
|
||||||
|
# 6. Min/Max output settings are not exposed in Home Assistant/MQTT by default, but could be.
|
||||||
|
# With a 1 MB flash, space is tight and only minimal optimisation has been done so far.
|
||||||
|
# 7. (PACKAGES) include common items: network settings, diagnostic entities, MQTT, and SNTP (optional).
|
||||||
|
# 8. Default behaviours, for recovery from lost power or reset:
|
||||||
|
# - "Fade up to full": fade from floor to max on boot.
|
||||||
|
# - "Restore Brightness": fade from floor to the last brightness on boot (should be no on/off blip/flash).
|
||||||
|
# - "Remain Off": stays off on boot.
|
||||||
|
# 9. The green LED flashes while fading (different patterns for up/down). The red LED follows the
|
||||||
|
# output (it shares the MOSFET GPIO). (Obviously this depends on hardware)
|
||||||
|
# 10. Fade timing scales with the configured values (proportionally when starting mid-brightness).
|
||||||
|
# 11. Exposed in Home Assistant/MQTT:
|
||||||
|
# - Startup action (On,Off,Restore)
|
||||||
|
# - Fade Up / Fade Down / Fade Stop buttons
|
||||||
|
# - Fade Up/Down toggle switch (keeps fading up to full, or down to off unless stopped)
|
||||||
|
# - Normal On/Off switch (quick ramp up/down, up to the level of previous brightness)
|
||||||
|
# - Fade up/down times (0-60s)
|
||||||
|
# - Output % (pre-gamma) and PWM % (post-gamma)
|
||||||
|
# - Output Set (1-100, respects min/max). This also changes with other output so reflects value.
|
||||||
|
# - Many device diagnostics (from the included 'diagnostics' package)
|
||||||
|
# - Maximum 'on' time before automatic fade-down (1-48 h, 0 = no limit)
|
||||||
|
##########################################################################################
|
||||||
|
##########################################################################################
|
||||||
|
# Hardware: Magishome LED controller single channel (ESP8266, NOT the later ones with BL602)
|
||||||
|
# https://devices.esphome.io/devices/MagicHome-ZJ-WFMN-C-Single-Color
|
||||||
|
# ------------------------------------------
|
||||||
# DEVICE GPIO (Magichome ESP8266)
|
# DEVICE GPIO (Magichome ESP8266)
|
||||||
# ------------------------------------------
|
# ------------------------------------------
|
||||||
# GPIO02 Blue LED (used for ESPHome status) - I think... can't see the board
|
# GPIO02 Blue LED (used for ESPHome status) - I think... can't see the board
|
||||||
# GPIO12 MOSFET output (0 V when switched)
|
# GPIO12 MOSFET output (0 V when switched)
|
||||||
# GPIO13 Green LED (used to display fading status)
|
|
||||||
#
|
|
||||||
# ------------------------------------------
|
|
||||||
# OPERATION
|
|
||||||
# ------------------------------------------
|
|
||||||
# - Startup/power restore action
|
|
||||||
# - Fade Up / Fade Down / Fade Stop buttons
|
|
||||||
# - Fade Up/Down switch
|
|
||||||
# - Normal On/Off switch (quick ramp up/down)
|
|
||||||
# - Fade up/down times (0-60s)
|
|
||||||
# - Output % (pre-gamma) and PWM % (post-gamma)
|
|
||||||
# - Output Set (1-100, respects min/max)
|
|
||||||
# - Device diagnostics (from the included package)
|
|
||||||
# - Maximum 'on' time before automatic fade-down (1-48 h, 0 = no limit)
|
|
||||||
#
|
|
||||||
##########################################################################################
|
##########################################################################################
|
||||||
##########################################################################################
|
##########################################################################################
|
||||||
|
|
||||||
|
|
||||||
##########################################################################################
|
##########################################################################################
|
||||||
# SPECIFIC DEVICE VARIABLE SUBSTITUTIONS
|
# SPECIFIC DEVICE VARIABLE SUBSTITUTIONS
|
||||||
# If NOT using a secrets file, just replace these with the passwords etc (in quotes)
|
# If NOT using a secrets file, just replace these with the passwords etc (in quotes)
|
||||||
##########################################################################################
|
##########################################################################################
|
||||||
substitutions:
|
substitutions:
|
||||||
# Device Naming
|
# Device Naming
|
||||||
device_name: "esp-breakfastbarleds"
|
device_name: "esp-breakfastbarleds" # yaml file should be device_name.yaml
|
||||||
friendly_name: "Breakfast Bar LEDs"
|
friendly_name: "Breakfast Bar LEDs"
|
||||||
description_comment: "LED Strip under the breakfast bar, upstairs lounge :: Magichome ESP8266"
|
description_comment: "LED Strip under the breakfast bar, upstairs lounge :: Magichome ESP8266"
|
||||||
device_area: "Lounge" # Allows the ESP device to be automatically linked to an 'Area' in Home Assistant.
|
device_area: "Lounge" # Allows the ESP device to be automatically linked to an 'Area' in Home Assistant.
|
||||||
|
|
||||||
# Project Naming
|
# Project Naming
|
||||||
project_name: "Magichome ESP8266" # Project details
|
project_name: "Magichome Single Colour V1.1 LED Controller.Generic ESP8266" # Project details. Manufacturer before the dot, device after the dot.
|
||||||
project_version: "v1.0" # Project version denotes release of the YAML file, allowing checking of deployed vs latest version
|
project_version: "v2.0" # Project version denotes release of the YAML file, allowing checking of deployed vs latest version
|
||||||
|
|
||||||
# Passwords & Secrets
|
# Passwords & Secrets (Unfortunately, you can't use substitutions inside secret names)
|
||||||
api_key: !secret esp-api_key
|
api_key: !secret esp-api_key
|
||||||
ota_pass: !secret esp-ota_pass
|
ota_pass: !secret esp-ota_pass
|
||||||
static_ip_address: !secret esp-breakfastbarleds_ip # Unfortunately, you can't use substitutions inside secret names
|
static_ip_address: !secret esp-breakfastbarleds_ip # CHANGE THIS
|
||||||
|
|
||||||
|
# MQTT LOCAL Controls (This is just my local method of controlling via MQTT, I also use the built in method)
|
||||||
|
mqtt_local_device_name: "lounge-breakfastbarleds"
|
||||||
mqtt_local_command_main_topic: !secret mqtt_local_command_main_topic
|
mqtt_local_command_main_topic: !secret mqtt_local_command_main_topic
|
||||||
mqtt_local_status_main_topic: !secret mqtt_local_status_main_topic
|
mqtt_local_status_main_topic: !secret mqtt_local_status_main_topic
|
||||||
|
|
||||||
# MQTT LOCAL Controls
|
|
||||||
mqtt_local_device_name: "lounge-breakfastbarleds"
|
|
||||||
mqtt_local_command_topic: "${mqtt_local_command_main_topic}/${mqtt_local_device_name}" # Topic we will use to command this locally without HA
|
mqtt_local_command_topic: "${mqtt_local_command_main_topic}/${mqtt_local_device_name}" # Topic we will use to command this locally without HA
|
||||||
mqtt_local_status_topic: "${mqtt_local_status_main_topic}/${mqtt_local_device_name}" # Topic we will use to view status locally without HA
|
mqtt_local_status_topic: "${mqtt_local_status_main_topic}/${mqtt_local_device_name}" # Topic we will use to view status locally without HA
|
||||||
mqtt_local_device_command_ON: "ON"
|
mqtt_local_device_command_ON: "ON"
|
||||||
mqtt_local_device_command_OFF: "OFF"
|
mqtt_local_device_command_OFF: "OFF"
|
||||||
|
|
||||||
# Device Specific Settings
|
# Device Specific Settings
|
||||||
log_level: "NONE" # Define logging level: NONE, ERROR, WARN, INFO, DEBUG (default), VERBOSE, VERY_VERBOSE
|
log_level: "NONE" # Define logging level: NONE, ERROR, WARN, INFO, DEBUG (default), VERBOSE, VERY_VERBOSE
|
||||||
update_interval: "20s" # Update time for general sensors, etc.
|
update_interval: "20s" # Update time for general sensors, etc.
|
||||||
led_gamma: "1.2" # Gamma from 1.2-3 is sensible to normalise the LED fading vs PWM
|
led_gamma: "1.2" # Gamma from 1.2-3 is sensible to normalise the LED fading vs PWM
|
||||||
minimum_led_output: "1" # % If at this value or below, we'll switch it completely off
|
minimum_led_output: "1" # % If at this value or below, we'll switch it completely off
|
||||||
maximum_led_output: "90" # % Maximum output; it is sometimes nice to limit the output for longevity or aesthetics
|
maximum_led_output: "90" # % Maximum output; it is sometimes nice to limit the output for longevity or aesthetics
|
||||||
max_on_default_hours: "12" # The maximum time the LEDs will be on, in case they get left on. 0 = no automatic turn-off
|
max_on_default_hours: "24" # The maximum time the LEDs will be on, in case they get left on. 0 = no automatic turn-off
|
||||||
|
pwm_frequency: "1000hz" # PWM output Frequency. High enough to avoid audible/visible artifacts
|
||||||
|
|
||||||
|
# Device Specific GPIO (so we can easily update for other devices)
|
||||||
|
device_status_led: GPIO02 # Can't really see if there is an LED
|
||||||
|
device_mosfet_out: GPIO12
|
||||||
|
device_usr_button: GPIO04 # this is just a fake/safeish choice so it compiles with my generic yaml
|
||||||
|
device_fading_led: GPIO13 # this is just a fake/safeish choice so it compiles with my generic yaml
|
||||||
|
|
||||||
##########################################################################################
|
##########################################################################################
|
||||||
# PACKAGES: Included Common Packages
|
# PACKAGES: Included Common Packages
|
||||||
# https://esphome.io/components/packages.html
|
# https://esphome.io/components/packages.html
|
||||||
##########################################################################################
|
##########################################################################################
|
||||||
packages:
|
packages:
|
||||||
|
#### WIFI, Network (Static/DHCP/IPV6 etc), Fallback AP, Safemode ####
|
||||||
common_wifi: !include
|
common_wifi: !include
|
||||||
file: common/network_common.yaml
|
file: common/network_common.yaml
|
||||||
vars:
|
vars:
|
||||||
local_device_name: "${device_name}"
|
local_device_name: "${device_name}"
|
||||||
local_static_ip_address: "${static_ip_address}"
|
local_static_ip_address: "${static_ip_address}"
|
||||||
local_ota_pass: "${ota_pass}"
|
local_ota_pass: "${ota_pass}"
|
||||||
|
|
||||||
|
#### HOME ASSISTANT API (choose encryption or no encryption options) ####
|
||||||
common_api: !include
|
common_api: !include
|
||||||
file: common/api_common.yaml
|
file: common/api_common.yaml
|
||||||
#file: common/api_common_noencryption.yaml
|
#file: common/api_common_noencryption.yaml
|
||||||
vars:
|
vars:
|
||||||
local_api_key: "${api_key}"
|
local_api_key: "${api_key}"
|
||||||
#common_webportal: !include
|
|
||||||
# file: common/webportal_common.yaml
|
#### MQTT ####
|
||||||
common_mqtt: !include
|
common_mqtt: !include
|
||||||
file: common/mqtt_common.yaml
|
file: common/mqtt_common.yaml
|
||||||
vars:
|
vars:
|
||||||
local_device_name: "${device_name}"
|
local_device_name: "${device_name}"
|
||||||
#common_sntp: !include
|
|
||||||
# file: common/sntp_common.yaml
|
#### WEB PORTAL ####
|
||||||
common_general_sensors: !include
|
#common_webportal: !include common/webportal_common.yaml
|
||||||
file: common/sensors_common.yaml
|
#### SNTP (Only use if you want/need accurate timeclocks) ####
|
||||||
#file: common/sensors_common_lite.yaml
|
#common_sntp: !include common/sntp_common.yaml
|
||||||
vars:
|
|
||||||
local_friendly_name: "${friendly_name}"
|
#### DIAGNOSTICS Sensors ####
|
||||||
local_update_interval: "${update_interval}"
|
diag_basic: !include common/include_basic_diag_sensors.yaml
|
||||||
|
diag_more: !include common/include_more_diag_sensors.yaml
|
||||||
|
diag_debug: !include common/include_debug_diag_sensors.yaml
|
||||||
|
diag_resetcount: !include common/include_resetcount_diag_sensors.yaml
|
||||||
|
|
||||||
##########################################################################################
|
##########################################################################################
|
||||||
# ESPHome CORE CONFIGURATION
|
# ESPHome CORE CONFIGURATION
|
||||||
@@ -109,7 +138,8 @@ esphome:
|
|||||||
comment: "${description_comment}"
|
comment: "${description_comment}"
|
||||||
area: "${device_area}"
|
area: "${device_area}"
|
||||||
on_boot:
|
on_boot:
|
||||||
priority: 600
|
# Run later so everything is initialised before we touch states.
|
||||||
|
priority: 200
|
||||||
then:
|
then:
|
||||||
# Keep the HA dropdown in sync with the stored mode
|
# Keep the HA dropdown in sync with the stored mode
|
||||||
- lambda: |-
|
- lambda: |-
|
||||||
@@ -127,31 +157,12 @@ esphome:
|
|||||||
- lambda: 'id(ramp_switch_target_on) = true;'
|
- lambda: 'id(ramp_switch_target_on) = true;'
|
||||||
- script.execute: ramp_on_script
|
- script.execute: ramp_on_script
|
||||||
|
|
||||||
# Mode 1: Restore Brightness using a scripted ramp 0 -> last non-zero brightness
|
# Mode 1: Restore Brightness (deferred to avoid races)
|
||||||
- if:
|
- if:
|
||||||
condition:
|
condition:
|
||||||
lambda: 'return id(restart_mode) == 1;'
|
lambda: 'return id(restart_mode) == 1;'
|
||||||
then:
|
then:
|
||||||
- lambda: |-
|
- script.execute: deferred_restore_brightness
|
||||||
float target = id(last_nonzero_brightness_pct);
|
|
||||||
if (target < 0.0f) target = 0.0f;
|
|
||||||
if (target > 100.0f) target = 100.0f;
|
|
||||||
const float minp = (float) id(min_brightness_pct);
|
|
||||||
const float maxp = (float) id(max_brightness_pct);
|
|
||||||
if (target > 0.0f && target < minp) target = minp;
|
|
||||||
if (target > maxp) target = maxp;
|
|
||||||
id(restore_target_pct) = target; // set scripted target
|
|
||||||
id(ramp_switch_target_on) = (target > 0.0f);
|
|
||||||
- if:
|
|
||||||
condition:
|
|
||||||
lambda: 'return id(restore_target_pct) > 0.0f;'
|
|
||||||
then:
|
|
||||||
- script.stop: ramp_off_script
|
|
||||||
- script.execute: ramp_to_target_script
|
|
||||||
else:
|
|
||||||
- light.turn_off:
|
|
||||||
id: mosfet_leds
|
|
||||||
transition_length: 0s
|
|
||||||
|
|
||||||
# Mode 2: Remain Off (no blip; stays off)
|
# Mode 2: Remain Off (no blip; stays off)
|
||||||
- if:
|
- if:
|
||||||
@@ -165,15 +176,19 @@ esphome:
|
|||||||
id: mosfet_leds
|
id: mosfet_leds
|
||||||
transition_length: 0s
|
transition_length: 0s
|
||||||
|
|
||||||
platformio_options:
|
# Boot complete: allow OFF handlers again
|
||||||
build_unflags:
|
- lambda: 'id(booting) = false;'
|
||||||
- -flto
|
|
||||||
build_flags:
|
# Only if you want to play with build flags...
|
||||||
- -fno-lto
|
# platformio_options:
|
||||||
- -Wl,--gc-sections
|
# build_unflags:
|
||||||
- -ffunction-sections
|
# - -flto
|
||||||
- -fdata-sections
|
# build_flags:
|
||||||
- -DNDEBUG
|
# - -fno-lto
|
||||||
|
# - -Wl,--gc-sections
|
||||||
|
# - -ffunction-sections
|
||||||
|
# - -fdata-sections
|
||||||
|
# - -DNDEBUG
|
||||||
|
|
||||||
##########################################################################################
|
##########################################################################################
|
||||||
# ESP PLATFORM AND FRAMEWORK
|
# ESP PLATFORM AND FRAMEWORK
|
||||||
@@ -185,10 +200,10 @@ esp8266:
|
|||||||
restore_from_flash: true # restore some values on reboot
|
restore_from_flash: true # restore some values on reboot
|
||||||
|
|
||||||
mdns:
|
mdns:
|
||||||
disabled: false # Disabling will make the build file smaller (and it is still available via static IP)
|
disabled: false # Disabling will make the build file smaller (and device is still available via static IP)
|
||||||
|
|
||||||
preferences:
|
preferences:
|
||||||
flash_write_interval: 5min
|
flash_write_interval: 5sec # enough time to update values for reboots, but not enough to wear flash
|
||||||
|
|
||||||
##########################################################################################
|
##########################################################################################
|
||||||
# GLOBAL VARIABLES
|
# GLOBAL VARIABLES
|
||||||
@@ -209,7 +224,8 @@ globals:
|
|||||||
- id: max_on_hours
|
- id: max_on_hours
|
||||||
type: int
|
type: int
|
||||||
restore_value: true
|
restore_value: true
|
||||||
initial_value: '${max_on_default_hours}'
|
initial_value: "${max_on_default_hours}"
|
||||||
|
|
||||||
# Default Fading Up Time (Selectable and will be retained)
|
# Default Fading Up Time (Selectable and will be retained)
|
||||||
- id: ramp_up_ms # fade-in when turned ON
|
- id: ramp_up_ms # fade-in when turned ON
|
||||||
type: int
|
type: int
|
||||||
@@ -273,6 +289,16 @@ globals:
|
|||||||
type: float
|
type: float
|
||||||
restore_value: false
|
restore_value: false
|
||||||
initial_value: '0.0'
|
initial_value: '0.0'
|
||||||
|
# guards OFF persistence during startup caused by restore_mode: ALWAYS_OFF
|
||||||
|
- id: booting
|
||||||
|
type: bool
|
||||||
|
restore_value: false
|
||||||
|
initial_value: 'true'
|
||||||
|
# true only while we are performing a ramp-down that briefly turns the light ON to hit the floor
|
||||||
|
- id: ramping_for_off
|
||||||
|
type: bool
|
||||||
|
restore_value: false
|
||||||
|
initial_value: 'false'
|
||||||
|
|
||||||
##########################################################################################
|
##########################################################################################
|
||||||
# LOGGER COMPONENT
|
# LOGGER COMPONENT
|
||||||
@@ -283,6 +309,15 @@ logger:
|
|||||||
level: "${log_level}" # INFO Level suggested, or DEBUG for testing
|
level: "${log_level}" # INFO Level suggested, or DEBUG for testing
|
||||||
baud_rate: 0 # set to 0 for no logging via UART, needed if you are using it for other serial things (eg PZEM, Serial control)
|
baud_rate: 0 # set to 0 for no logging via UART, needed if you are using it for other serial things (eg PZEM, Serial control)
|
||||||
|
|
||||||
|
#########################################################################################
|
||||||
|
# STATUS LED
|
||||||
|
# https://esphome.io/components/status_led.html
|
||||||
|
#########################################################################################
|
||||||
|
status_led:
|
||||||
|
pin:
|
||||||
|
number: ${device_status_led}
|
||||||
|
inverted: true
|
||||||
|
|
||||||
##########################################################################################
|
##########################################################################################
|
||||||
# MQTT COMMANDS
|
# MQTT COMMANDS
|
||||||
# This adds device-specific MQTT command triggers to the common MQTT configuration.
|
# This adds device-specific MQTT command triggers to the common MQTT configuration.
|
||||||
@@ -300,15 +335,6 @@ mqtt:
|
|||||||
then:
|
then:
|
||||||
- switch.turn_off: mosfet_ramp_switch
|
- switch.turn_off: mosfet_ramp_switch
|
||||||
|
|
||||||
#########################################################################################
|
|
||||||
# STATUS LED
|
|
||||||
# https://esphome.io/components/status_led.html
|
|
||||||
#########################################################################################
|
|
||||||
status_led:
|
|
||||||
pin:
|
|
||||||
number: GPIO2
|
|
||||||
inverted: true
|
|
||||||
|
|
||||||
##########################################################################################
|
##########################################################################################
|
||||||
# SWITCH COMPONENT
|
# SWITCH COMPONENT
|
||||||
# https://esphome.io/components/switch/
|
# https://esphome.io/components/switch/
|
||||||
@@ -367,12 +393,18 @@ button:
|
|||||||
name: "${friendly_name} Fade Stop"
|
name: "${friendly_name} Fade Stop"
|
||||||
icon: mdi:pause
|
icon: mdi:pause
|
||||||
on_press:
|
on_press:
|
||||||
# Stop any pending scripts (and their delayed turn_off)
|
# Stop any pending scripts (and their delayed actions)
|
||||||
- script.stop: ramp_on_script
|
- script.stop: ramp_on_script
|
||||||
- script.stop: ramp_off_script
|
- script.stop: ramp_off_script
|
||||||
# Cancel the light's transition by commanding the current level with 0 ms,
|
- script.stop: led_flash_up
|
||||||
# but DO NOT change the ramp switch state/flag.
|
- script.stop: led_flash_down
|
||||||
|
- output.turn_off: green_led_out
|
||||||
- lambda: |-
|
- lambda: |-
|
||||||
|
// We are no longer ramping (up or down)
|
||||||
|
id(ramping_for_off) = false;
|
||||||
|
id(is_ramping) = false;
|
||||||
|
|
||||||
|
// Freeze the light at its *current* level by overwriting the transition
|
||||||
const auto &cv = id(mosfet_leds).current_values;
|
const auto &cv = id(mosfet_leds).current_values;
|
||||||
if (cv.is_on()) {
|
if (cv.is_on()) {
|
||||||
auto call = id(mosfet_leds).make_call();
|
auto call = id(mosfet_leds).make_call();
|
||||||
@@ -382,6 +414,14 @@ button:
|
|||||||
call.perform();
|
call.perform();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Persist the exact frozen level so "Restore Brightness" survives a reboot
|
||||||
|
if (cv.is_on()) {
|
||||||
|
float pct = cv.get_brightness() * 100.0f;
|
||||||
|
id(last_brightness_pct) = pct;
|
||||||
|
if (pct > 0.5f) id(last_nonzero_brightness_pct) = pct;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
#########################################################################################
|
#########################################################################################
|
||||||
# SELECT COMPONENT
|
# SELECT COMPONENT
|
||||||
# https://esphome.io/components/select/index.html
|
# https://esphome.io/components/select/index.html
|
||||||
@@ -411,7 +451,37 @@ select:
|
|||||||
# BINARY SENSORS
|
# BINARY SENSORS
|
||||||
# https://esphome.io/components/binary_sensor/
|
# https://esphome.io/components/binary_sensor/
|
||||||
#########################################################################################
|
#########################################################################################
|
||||||
#binary_sensor:
|
binary_sensor:
|
||||||
|
- platform: gpio
|
||||||
|
id: btn_gpio12
|
||||||
|
name: "${friendly_name} Button"
|
||||||
|
pin:
|
||||||
|
number: ${device_usr_button}
|
||||||
|
mode:
|
||||||
|
input: true
|
||||||
|
pullup: true
|
||||||
|
inverted: true
|
||||||
|
filters:
|
||||||
|
- delayed_on: 20ms
|
||||||
|
- delayed_off: 20ms
|
||||||
|
on_press:
|
||||||
|
- if:
|
||||||
|
condition:
|
||||||
|
lambda: 'return id(ramp_switch_target_on);'
|
||||||
|
then:
|
||||||
|
# Target is currently ON -> press should go OFF (start ramp-down)
|
||||||
|
- lambda: |-
|
||||||
|
id(ramp_switch_target_on) = false;
|
||||||
|
id(mosfet_ramp_switch).publish_state(false); // reflect in HA immediately
|
||||||
|
- script.stop: ramp_on_script
|
||||||
|
- script.execute: ramp_off_script
|
||||||
|
else:
|
||||||
|
# Target is currently OFF -> press should go ON (start ramp-up)
|
||||||
|
- lambda: |-
|
||||||
|
id(ramp_switch_target_on) = true;
|
||||||
|
id(mosfet_ramp_switch).publish_state(true); // reflect in HA immediately
|
||||||
|
- script.stop: ramp_off_script
|
||||||
|
- script.execute: ramp_on_script
|
||||||
|
|
||||||
##########################################################################################
|
##########################################################################################
|
||||||
# SENSOR COMPONENT
|
# SENSOR COMPONENT
|
||||||
@@ -431,16 +501,22 @@ sensor:
|
|||||||
on_value:
|
on_value:
|
||||||
then:
|
then:
|
||||||
- lambda: |-
|
- lambda: |-
|
||||||
// Remember latest actual output (0..100) for "Restore Brightness"
|
const auto &cv_now = id(mosfet_leds).current_values;
|
||||||
id(last_brightness_pct) = x;
|
|
||||||
if (x > 0.5f) {
|
// Only persist exact last brightness while actually ON,
|
||||||
// Only persist when not ramping; always track a volatile copy
|
// and not during the OFF ramp's brief floor-kick.
|
||||||
if (!id(is_ramping)) id(last_nonzero_brightness_pct) = x;
|
if (cv_now.is_on()) {
|
||||||
id(last_nonzero_tmp) = x;
|
if (x > 0.5f) {
|
||||||
|
if (!id(is_ramping)) id(last_nonzero_brightness_pct) = x;
|
||||||
|
id(last_nonzero_tmp) = x;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (x > 0.5f) id(last_nonzero_tmp) = x;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If not suppressing sync, update the 0..100 slider only when its INT changes
|
// If not suppressing sync, update the 0..100 slider only when its INT changes
|
||||||
if (!id(suppress_slider_sync)) {
|
if (!id(suppress_slider_sync)) {
|
||||||
float actual = x; // actual %
|
float actual = x;
|
||||||
float minp = (float) id(min_brightness_pct);
|
float minp = (float) id(min_brightness_pct);
|
||||||
float maxp = (float) id(max_brightness_pct);
|
float maxp = (float) id(max_brightness_pct);
|
||||||
if (maxp <= minp) maxp = minp + 1.0f;
|
if (maxp <= minp) maxp = minp + 1.0f;
|
||||||
@@ -464,7 +540,7 @@ sensor:
|
|||||||
lambda: |-
|
lambda: |-
|
||||||
const auto &cv = id(mosfet_leds).current_values;
|
const auto &cv = id(mosfet_leds).current_values;
|
||||||
if (!cv.is_on()) return 0.0f;
|
if (!cv.is_on()) return 0.0f;
|
||||||
const float lin = cv.get_brightness(); // 0..1 (linear brightness)
|
const float lin = cv.get_brightness(); // 0..1 linear brightness
|
||||||
const float gamma = atof("${led_gamma}"); // parse substitution string -> float
|
const float gamma = atof("${led_gamma}"); // parse substitution string -> float
|
||||||
float pwm = powf(lin, gamma); // approx PWM duty after gamma
|
float pwm = powf(lin, gamma); // approx PWM duty after gamma
|
||||||
if (pwm < 0.0f) pwm = 0.0f;
|
if (pwm < 0.0f) pwm = 0.0f;
|
||||||
@@ -481,8 +557,13 @@ sensor:
|
|||||||
output:
|
output:
|
||||||
- platform: esp8266_pwm
|
- platform: esp8266_pwm
|
||||||
id: mosfet_pwm
|
id: mosfet_pwm
|
||||||
pin: GPIO12
|
pin: ${device_mosfet_out}
|
||||||
frequency: 1000 Hz # higher frequency helps reduce camera banding while filming
|
frequency: ${pwm_frequency}
|
||||||
|
- platform: gpio
|
||||||
|
id: green_led_out # Green LED
|
||||||
|
pin:
|
||||||
|
number: ${device_fading_led}
|
||||||
|
inverted: false
|
||||||
|
|
||||||
##########################################################################################
|
##########################################################################################
|
||||||
# LIGHT COMPONENT
|
# LIGHT COMPONENT
|
||||||
@@ -493,29 +574,48 @@ light:
|
|||||||
id: mosfet_leds
|
id: mosfet_leds
|
||||||
name: "${friendly_name}"
|
name: "${friendly_name}"
|
||||||
output: mosfet_pwm
|
output: mosfet_pwm
|
||||||
restore_mode: ALWAYS_OFF
|
restore_mode: RESTORE_DEFAULT_OFF
|
||||||
default_transition_length: 2s
|
default_transition_length: 2s
|
||||||
icon: mdi:led-strip-variant
|
icon: mdi:led-strip-variant
|
||||||
gamma_correct: "${led_gamma}"
|
gamma_correct: "${led_gamma}"
|
||||||
|
|
||||||
|
# ON: publish state, track intent, arm watchdog if configured
|
||||||
on_turn_on:
|
on_turn_on:
|
||||||
- mqtt.publish:
|
- mqtt.publish:
|
||||||
topic: "${mqtt_local_status_topic}/light/state"
|
topic: "${mqtt_local_status_topic}/light/state"
|
||||||
payload: "${mqtt_local_device_command_ON}"
|
payload: "${mqtt_local_device_command_ON}"
|
||||||
retain: true
|
retain: true
|
||||||
- lambda: 'id(ramp_switch_target_on) = true;'
|
- lambda: |-
|
||||||
|
// Do not flip "intent to ON" when we're inside an OFF ramp's floor-kick
|
||||||
|
if (!id(ramping_for_off)) {
|
||||||
|
id(ramp_switch_target_on) = true;
|
||||||
|
}
|
||||||
- script.stop: max_on_watchdog
|
- script.stop: max_on_watchdog
|
||||||
- if:
|
- if:
|
||||||
condition:
|
condition:
|
||||||
lambda: 'return id(max_on_hours) > 0;'
|
lambda: 'return id(max_on_hours) > 0;'
|
||||||
then:
|
then:
|
||||||
- script.execute: max_on_watchdog
|
- script.execute: max_on_watchdog
|
||||||
|
|
||||||
|
# OFF: publish state, track intent, clear watchdog, and persist OFF so Mode 1 will not restore
|
||||||
|
# Guarded by booting flag so the automatic OFF at boot from ALWAYS_OFF does not zero persistence.
|
||||||
on_turn_off:
|
on_turn_off:
|
||||||
- mqtt.publish:
|
- if:
|
||||||
topic: "${mqtt_local_status_topic}/light/state"
|
condition:
|
||||||
payload: "${mqtt_local_device_command_OFF}"
|
lambda: 'return !id(booting);'
|
||||||
retain: true
|
then:
|
||||||
- lambda: 'id(ramp_switch_target_on) = false;'
|
- mqtt.publish:
|
||||||
- script.stop: max_on_watchdog
|
topic: "${mqtt_local_status_topic}/light/state"
|
||||||
|
payload: "${mqtt_local_device_command_OFF}"
|
||||||
|
retain: true
|
||||||
|
- lambda: 'id(ramp_switch_target_on) = false;'
|
||||||
|
- script.stop: max_on_watchdog
|
||||||
|
- lambda: |-
|
||||||
|
id(last_brightness_pct) = 0.0f;
|
||||||
|
id(last_set_pos) = 0;
|
||||||
|
id(led_output_set_pct).publish_state(0);
|
||||||
|
|
||||||
|
# Cap brightness to max_brightness_pct without a visible step
|
||||||
on_state:
|
on_state:
|
||||||
- lambda: |-
|
- lambda: |-
|
||||||
const float cap = id(max_brightness_pct) / 100.0f;
|
const float cap = id(max_brightness_pct) / 100.0f;
|
||||||
@@ -608,6 +708,8 @@ number:
|
|||||||
- lambda: |-
|
- lambda: |-
|
||||||
id(ramp_switch_target_on) = false;
|
id(ramp_switch_target_on) = false;
|
||||||
id(led_output_set_pct).publish_state(0);
|
id(led_output_set_pct).publish_state(0);
|
||||||
|
id(last_brightness_pct) = 0.0f; // persist OFF immediately
|
||||||
|
id(last_set_pos) = 0;
|
||||||
- delay: 400ms
|
- delay: 400ms
|
||||||
- lambda: 'id(suppress_slider_sync) = false;'
|
- lambda: 'id(suppress_slider_sync) = false;'
|
||||||
else:
|
else:
|
||||||
@@ -631,7 +733,19 @@ number:
|
|||||||
if (out_pct > maxp) out_pct = maxp;
|
if (out_pct > maxp) out_pct = maxp;
|
||||||
return out_pct / 100.0f;
|
return out_pct / 100.0f;
|
||||||
transition_length: 250ms
|
transition_length: 250ms
|
||||||
- lambda: 'id(ramp_switch_target_on) = true;'
|
- lambda: |-
|
||||||
|
id(ramp_switch_target_on) = true;
|
||||||
|
float pos = id(led_output_set_pct).state; // 1..100
|
||||||
|
float minp = (float) id(min_brightness_pct);
|
||||||
|
float maxp = (float) id(max_brightness_pct);
|
||||||
|
if (maxp <= minp) maxp = minp + 1.0f;
|
||||||
|
float out_pct = minp + (pos * (maxp - minp) / 100.0f);
|
||||||
|
if (out_pct > maxp) out_pct = maxp;
|
||||||
|
id(last_brightness_pct) = out_pct; // persist exact target now
|
||||||
|
if (out_pct > 0.5f) {
|
||||||
|
id(last_nonzero_brightness_pct) = out_pct;
|
||||||
|
id(last_nonzero_tmp) = out_pct;
|
||||||
|
}
|
||||||
- delay: 400ms
|
- delay: 400ms
|
||||||
- lambda: 'id(suppress_slider_sync) = false;'
|
- lambda: 'id(suppress_slider_sync) = false;'
|
||||||
|
|
||||||
@@ -671,12 +785,43 @@ number:
|
|||||||
# Scripts can be executed nearly anywhere in your device configuration with a single call.
|
# Scripts can be executed nearly anywhere in your device configuration with a single call.
|
||||||
##########################################################################################
|
##########################################################################################
|
||||||
script:
|
script:
|
||||||
|
# Blink pattern while ramping UP: quick double-blink, pause, repeat
|
||||||
|
- id: led_flash_up
|
||||||
|
mode: restart
|
||||||
|
then:
|
||||||
|
- while:
|
||||||
|
condition:
|
||||||
|
lambda: 'return true;'
|
||||||
|
then:
|
||||||
|
- output.turn_on: green_led_out
|
||||||
|
- delay: 100ms
|
||||||
|
- output.turn_off: green_led_out
|
||||||
|
- delay: 100ms
|
||||||
|
- output.turn_on: green_led_out
|
||||||
|
- delay: 100ms
|
||||||
|
- output.turn_off: green_led_out
|
||||||
|
- delay: 400ms
|
||||||
|
# Blink pattern while ramping DOWN: steady slow blink
|
||||||
|
- id: led_flash_down
|
||||||
|
mode: restart
|
||||||
|
then:
|
||||||
|
- while:
|
||||||
|
condition:
|
||||||
|
lambda: 'return true;'
|
||||||
|
then:
|
||||||
|
- output.turn_on: green_led_out
|
||||||
|
- delay: 250ms
|
||||||
|
- output.turn_off: green_led_out
|
||||||
|
- delay: 250ms
|
||||||
|
|
||||||
# Script: ramp up from current level. Obey global max.
|
# Script: ramp up from current level. Obey global max.
|
||||||
- id: ramp_on_script
|
- id: ramp_on_script
|
||||||
mode: restart
|
mode: restart
|
||||||
then:
|
then:
|
||||||
- lambda: 'id(is_ramping) = true;' # mark ramping
|
- lambda: 'id(is_ramping) = true;'
|
||||||
- script.stop: ramp_off_script
|
- script.stop: ramp_off_script
|
||||||
|
- script.stop: led_flash_down
|
||||||
|
- script.execute: led_flash_up
|
||||||
# Ensure we start at at least the floor without a visible "pop".
|
# Ensure we start at at least the floor without a visible "pop".
|
||||||
- if:
|
- if:
|
||||||
condition:
|
condition:
|
||||||
@@ -697,18 +842,14 @@ script:
|
|||||||
const auto &cv = id(mosfet_leds).current_values;
|
const auto &cv = id(mosfet_leds).current_values;
|
||||||
const float floor = id(min_brightness_pct) / 100.0f;
|
const float floor = id(min_brightness_pct) / 100.0f;
|
||||||
const float cap = id(max_brightness_pct) / 100.0f;
|
const float cap = id(max_brightness_pct) / 100.0f;
|
||||||
if (cap <= floor + 0.0005f) { id(last_ramp_ms) = 0; return (uint32_t) 0; } // guard degenerate case
|
if (cap <= floor + 0.0005f) { id(last_ramp_ms) = 0; return (uint32_t) 0; }
|
||||||
|
|
||||||
// IMPORTANT: when the light was OFF, treat current as 0 so we don't compute a 0ms jump to cap.
|
|
||||||
float curr = cv.is_on() ? cv.get_brightness() : 0.0f;
|
float curr = cv.is_on() ? cv.get_brightness() : 0.0f;
|
||||||
|
|
||||||
// If we're below the floor (i.e. coming from OFF), we just did the "floor kick" above.
|
|
||||||
// Use full ramp_up_ms for a smooth 0 -> cap ramp.
|
|
||||||
if (curr + 0.0005f < floor) {
|
if (curr + 0.0005f < floor) {
|
||||||
id(last_ramp_ms) = id(ramp_up_ms);
|
id(last_ramp_ms) = id(ramp_up_ms);
|
||||||
return (uint32_t) id(last_ramp_ms);
|
return (uint32_t) id(last_ramp_ms);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (curr > cap) curr = cap;
|
if (curr > cap) curr = cap;
|
||||||
if (curr < floor) curr = floor;
|
if (curr < floor) curr = floor;
|
||||||
float frac = (cap - curr) / (cap - floor);
|
float frac = (cap - curr) / (cap - floor);
|
||||||
@@ -717,20 +858,25 @@ script:
|
|||||||
id(last_ramp_ms) = (int) (id(ramp_up_ms) * frac);
|
id(last_ramp_ms) = (int) (id(ramp_up_ms) * frac);
|
||||||
return (uint32_t) id(last_ramp_ms);
|
return (uint32_t) id(last_ramp_ms);
|
||||||
- delay: !lambda 'return (uint32_t) id(last_ramp_ms);'
|
- delay: !lambda 'return (uint32_t) id(last_ramp_ms);'
|
||||||
|
- script.stop: led_flash_up
|
||||||
|
- output.turn_off: green_led_out
|
||||||
- lambda: |-
|
- lambda: |-
|
||||||
id(is_ramping) = false; // ramp complete
|
id(is_ramping) = false;
|
||||||
const auto &cv = id(mosfet_leds).current_values;
|
const auto &cv = id(mosfet_leds).current_values;
|
||||||
if (cv.is_on()) {
|
if (cv.is_on()) {
|
||||||
float pct = cv.get_brightness() * 100.0f;
|
float pct = cv.get_brightness() * 100.0f;
|
||||||
if (pct > 0.5f) id(last_nonzero_brightness_pct) = pct; // commit once
|
id(last_brightness_pct) = pct; // persist final level
|
||||||
|
if (pct > 0.5f) id(last_nonzero_brightness_pct) = pct;
|
||||||
}
|
}
|
||||||
|
|
||||||
# Script: ramp to a specific target (Restore Brightness path)
|
# Script: ramp to a specific target (Restore Brightness path)
|
||||||
- id: ramp_to_target_script
|
- id: ramp_to_target_script
|
||||||
mode: restart
|
mode: restart
|
||||||
then:
|
then:
|
||||||
- lambda: 'id(is_ramping) = true;' # mark ramping
|
- lambda: 'id(is_ramping) = true;'
|
||||||
- script.stop: ramp_off_script
|
- script.stop: ramp_off_script
|
||||||
|
- script.stop: led_flash_down
|
||||||
|
- script.execute: led_flash_up
|
||||||
# Ensure we start at the floor cleanly.
|
# Ensure we start at the floor cleanly.
|
||||||
- if:
|
- if:
|
||||||
condition:
|
condition:
|
||||||
@@ -757,12 +903,9 @@ script:
|
|||||||
float cap = id(restore_target_pct) / 100.0f;
|
float cap = id(restore_target_pct) / 100.0f;
|
||||||
const float maxp = id(max_brightness_pct) / 100.0f;
|
const float maxp = id(max_brightness_pct) / 100.0f;
|
||||||
if (cap > maxp) cap = maxp;
|
if (cap > maxp) cap = maxp;
|
||||||
if (cap <= floor + 0.0005f) { id(last_ramp_ms) = 0; return (uint32_t) 0; } // guard
|
if (cap <= floor + 0.0005f) { id(last_ramp_ms) = 0; return (uint32_t) 0; }
|
||||||
|
|
||||||
// Same OFF case handling: if off, treat curr as 0 so we don't collapse the ramp.
|
|
||||||
float curr = cv.is_on() ? cv.get_brightness() : 0.0f;
|
float curr = cv.is_on() ? cv.get_brightness() : 0.0f;
|
||||||
|
|
||||||
// Coming from OFF/below floor? We just did the "floor kick" above — use full ramp_up_ms.
|
|
||||||
if (curr + 0.0005f < floor) {
|
if (curr + 0.0005f < floor) {
|
||||||
id(last_ramp_ms) = id(ramp_up_ms);
|
id(last_ramp_ms) = id(ramp_up_ms);
|
||||||
return (uint32_t) id(last_ramp_ms);
|
return (uint32_t) id(last_ramp_ms);
|
||||||
@@ -776,20 +919,27 @@ script:
|
|||||||
id(last_ramp_ms) = (int) (id(ramp_up_ms) * frac);
|
id(last_ramp_ms) = (int) (id(ramp_up_ms) * frac);
|
||||||
return (uint32_t) id(last_ramp_ms);
|
return (uint32_t) id(last_ramp_ms);
|
||||||
- delay: !lambda 'return (uint32_t) id(last_ramp_ms);'
|
- delay: !lambda 'return (uint32_t) id(last_ramp_ms);'
|
||||||
|
- script.stop: led_flash_up
|
||||||
|
- output.turn_off: green_led_out
|
||||||
- lambda: |-
|
- lambda: |-
|
||||||
id(is_ramping) = false; // ramp complete
|
id(is_ramping) = false;
|
||||||
const auto &cv = id(mosfet_leds).current_values;
|
const auto &cv = id(mosfet_leds).current_values;
|
||||||
if (cv.is_on()) {
|
if (cv.is_on()) {
|
||||||
float pct = cv.get_brightness() * 100.0f;
|
float pct = cv.get_brightness() * 100.0f;
|
||||||
if (pct > 0.5f) id(last_nonzero_brightness_pct) = pct; // commit once
|
id(last_brightness_pct) = pct; // persist final level
|
||||||
|
if (pct > 0.5f) id(last_nonzero_brightness_pct) = pct;
|
||||||
}
|
}
|
||||||
|
|
||||||
# Script: ramp down from current level to floor, then cleanly cut to OFF
|
# Script: ramp down from current level to floor, then cleanly cut to OFF
|
||||||
- id: ramp_off_script
|
- id: ramp_off_script
|
||||||
mode: restart
|
mode: restart
|
||||||
then:
|
then:
|
||||||
- lambda: 'id(is_ramping) = true;' # mark ramping
|
- lambda: |-
|
||||||
|
id(is_ramping) = true;
|
||||||
|
id(ramping_for_off) = true;
|
||||||
- script.stop: ramp_on_script
|
- script.stop: ramp_on_script
|
||||||
|
- script.stop: led_flash_up
|
||||||
|
- script.execute: led_flash_down
|
||||||
- light.turn_on:
|
- light.turn_on:
|
||||||
id: mosfet_leds
|
id: mosfet_leds
|
||||||
brightness: !lambda 'return id(min_brightness_pct) / 100.0f;'
|
brightness: !lambda 'return id(min_brightness_pct) / 100.0f;'
|
||||||
@@ -799,7 +949,7 @@ script:
|
|||||||
float curr = cv.get_brightness();
|
float curr = cv.get_brightness();
|
||||||
if (curr < floor) curr = floor;
|
if (curr < floor) curr = floor;
|
||||||
const float denom = (1.0f - floor);
|
const float denom = (1.0f - floor);
|
||||||
if (denom <= 0.0005f) { id(last_ramp_ms) = 0; return (uint32_t) 0; } // guard near-1.0 floor
|
if (denom <= 0.0005f) { id(last_ramp_ms) = 0; return (uint32_t) 0; }
|
||||||
float frac = (curr - floor) / denom;
|
float frac = (curr - floor) / denom;
|
||||||
if (frac < 0.0f) frac = 0.0f;
|
if (frac < 0.0f) frac = 0.0f;
|
||||||
if (frac > 1.0f) frac = 1.0f;
|
if (frac > 1.0f) frac = 1.0f;
|
||||||
@@ -810,12 +960,15 @@ script:
|
|||||||
id: mosfet_leds
|
id: mosfet_leds
|
||||||
transition_length: 150ms
|
transition_length: 150ms
|
||||||
- delay: 150ms
|
- delay: 150ms
|
||||||
|
- script.stop: led_flash_down
|
||||||
|
- output.turn_off: green_led_out
|
||||||
- lambda: |-
|
- lambda: |-
|
||||||
id(is_ramping) = false; // ramp complete
|
id(is_ramping) = false;
|
||||||
auto call = id(mosfet_leds).make_call();
|
id(ramping_for_off) = false;
|
||||||
call.set_state(false);
|
id(last_brightness_pct) = 0.0f; // persist OFF for restore path
|
||||||
call.set_brightness(id(max_brightness_pct) / 100.0f);
|
id(last_set_pos) = 0;
|
||||||
call.perform();
|
id(led_output_set_pct).publish_state(0);
|
||||||
|
|
||||||
- id: max_on_watchdog
|
- id: max_on_watchdog
|
||||||
mode: restart
|
mode: restart
|
||||||
then:
|
then:
|
||||||
@@ -833,3 +986,38 @@ script:
|
|||||||
id(mosfet_ramp_switch).publish_state(false);
|
id(mosfet_ramp_switch).publish_state(false);
|
||||||
- script.stop: ramp_on_script
|
- script.stop: ramp_on_script
|
||||||
- script.execute: ramp_off_script
|
- script.execute: ramp_off_script
|
||||||
|
|
||||||
|
# Deferred restore to avoid early-boot races and ensure we apply the last saved level
|
||||||
|
- id: deferred_restore_brightness
|
||||||
|
mode: restart
|
||||||
|
then:
|
||||||
|
- delay: 5s # let preferences/globals load and light settle
|
||||||
|
- lambda: |-
|
||||||
|
// Use ONLY the exact last saved brightness.
|
||||||
|
float target = id(last_brightness_pct);
|
||||||
|
|
||||||
|
// Clamp into [min..max] only if we are restoring a non-zero target
|
||||||
|
float minp = (float) id(min_brightness_pct);
|
||||||
|
float maxp = (float) id(max_brightness_pct);
|
||||||
|
if (target > 0.0f && target < minp) target = minp;
|
||||||
|
if (target > maxp) target = maxp;
|
||||||
|
|
||||||
|
id(restore_target_pct) = target;
|
||||||
|
|
||||||
|
- if:
|
||||||
|
condition:
|
||||||
|
lambda: 'return id(restore_target_pct) > 0.5f;'
|
||||||
|
then:
|
||||||
|
- lambda: |-
|
||||||
|
id(ramp_switch_target_on) = true; // reflect our intent
|
||||||
|
id(suppress_slider_sync) = true;
|
||||||
|
- script.stop: ramp_off_script
|
||||||
|
- script.execute: ramp_to_target_script
|
||||||
|
- delay: !lambda 'return (uint32_t) id(last_ramp_ms);'
|
||||||
|
- lambda: 'id(suppress_slider_sync) = false;'
|
||||||
|
else:
|
||||||
|
# Exact last was 0 -> remain OFF
|
||||||
|
- lambda: 'id(ramp_switch_target_on) = false;'
|
||||||
|
- light.turn_off:
|
||||||
|
id: mosfet_leds
|
||||||
|
transition_length: 0s
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
##########################################################################################
|
##########################################################################################
|
||||||
##########################################################################################
|
##########################################################################################
|
||||||
# Title: DOWNSTAIRS KITCHEN - OVER PANTRY LEDS
|
# Title: DOWNSTAIRS KITCHEN - OVER PANTRY LEDS
|
||||||
# Hardware: Sinilink MOSFET Board XY-WFMS (ESP8266) — sometimes listed as “XY-VFMS”
|
|
||||||
# https://devices.esphome.io/devices/Sinilink-XY-VFMS
|
|
||||||
# Repo: https://home.fox.co.nz/gitea/zorruno/zorruno-homeassistant/src/branch/master/esphome/esp-downstairskitchleds.yaml
|
# Repo: https://home.fox.co.nz/gitea/zorruno/zorruno-homeassistant/src/branch/master/esphome/esp-downstairskitchleds.yaml
|
||||||
#
|
#
|
||||||
|
# v2.0 - 2025-09-15 A bunch of fixes.
|
||||||
# v1.5 - 2025-09-04 Adopt ramped "Restore Brightness" (0→last non-zero), avoid boot blip (ALWAYS_OFF + high-priority on_boot),
|
# v1.5 - 2025-09-04 Adopt ramped "Restore Brightness" (0→last non-zero), avoid boot blip (ALWAYS_OFF + high-priority on_boot),
|
||||||
# add flash-write coalescing (persist once at end of ramps), guards for edge cases, and flash_write_interval=5min
|
# add flash-write coalescing (persist once at end of ramps), guards for edge cases, and flash_write_interval=5min
|
||||||
# v1.4 - 2025-08-22 Improved power loss/on actions
|
# v1.4 - 2025-08-22 Improved power loss/on actions
|
||||||
@@ -14,115 +13,131 @@
|
|||||||
# v1.0 - 2025-08-17 First setup (and replacement of Tasmota)
|
# v1.0 - 2025-08-17 First setup (and replacement of Tasmota)
|
||||||
#
|
#
|
||||||
# ------------------------------------------
|
# ------------------------------------------
|
||||||
# DEVICE GPIO (Sinilink XY-WFMS)
|
# OPERATION (as of v2.0)
|
||||||
# ------------------------------------------
|
|
||||||
# GPIO02 Blue LED (used for ESPHome status)
|
|
||||||
# GPIO04 MOSFET output (0 V when switched) and Red LED
|
|
||||||
# GPIO12 Toggle button
|
|
||||||
# GPIO13 Green LED (used to display fading status)
|
|
||||||
#
|
|
||||||
# ------------------------------------------
|
|
||||||
# OPERATION (as of v1.5)
|
|
||||||
# ------------------------------------------
|
# ------------------------------------------
|
||||||
# 1. General-purpose LED controller.
|
# 1. General-purpose LED controller.
|
||||||
# 2. Designed for the Sinilink XY-WFMS board with a MOSFET output (claimed 5A, 5-36 V DC).
|
# 2. Designed for the Sinilink XY-WFMS board with a MOSFET output (claimed 5A, 5-36 V DC), but will suit similar mosfet boards.
|
||||||
# 3. Global setting for MAX % output to extend LED life.
|
# 3. (In SUBSTITUTIONS) setting for MAX % output to extend LED life (ie can ensure it is only ever 95%)
|
||||||
# 4. Minimum output setting; switches fully OFF at/below the minimum to avoid low-PWM flicker.
|
# 4. (In SUBSTITUTIONS) Minimum output setting; switches fully OFF at/below the minimum to avoid low-PWM flicker.
|
||||||
# 5. PWM frequency is set to 500 Hz by default. You can increase it, but higher values caused
|
# 5. (In SUBSTITUTIONS) PWM frequency is set to 1kHz by default. You can increase it, but higher values caused
|
||||||
# resets on this device. On ESP32 you can run much higher (~40 kHz).
|
# resets on Sinilink device. On ESP32 you can run much higher (~40 kHz).
|
||||||
# 6. Min/Max output settings are not exposed in Home Assistant/MQTT by default, but can be.
|
# 6. Min/Max output settings are not exposed in Home Assistant/MQTT by default, but could be.
|
||||||
# With a 1 MB flash, space is tight and only minimal optimisation has been done so far.
|
# With a 1 MB flash, space is tight and only minimal optimisation has been done so far.
|
||||||
# 7. PACKAGES include common items: network settings, diagnostic entities, MQTT, and SNTP (optional).
|
# 7. (PACKAGES) include common items: network settings, diagnostic entities, MQTT, and SNTP (optional).
|
||||||
# 8. Default behaviour:
|
# 8. Default behaviours, for recovery from lost power or reset:
|
||||||
# - "Fade up to full": fade from floor to max on boot.
|
# - "Fade up to full": fade from floor to max on boot.
|
||||||
# - "Restore Brightness": fade from floor to the last non-zero brightness on boot (no on/off blip).
|
# - "Restore Brightness": fade from floor to the last non-zero brightness on boot (should be no on/off blip/flash).
|
||||||
# - "Remain Off": stays off on boot.
|
# - "Remain Off": stays off on boot.
|
||||||
# 9. The green LED flashes while fading (different patterns for up/down). The red LED follows the
|
# 9. The green LED flashes while fading (different patterns for up/down). The red LED follows the
|
||||||
# output (it shares the MOSFET GPIO).
|
# output (it shares the MOSFET GPIO). (Obviously this depends on hardware)
|
||||||
# 10. Fade timing scales with the configured values (proportionally when starting mid-brightness).
|
# 10. Fade timing scales with the configured values (proportionally when starting mid-brightness).
|
||||||
# 11. Useful 3D-printed case: https://cults3d.com/en/3d-model/tool/snapfit-enclosure-for-esp8266-sinilink-xy-wfms-5v-36v-mosfet-switch-module
|
# 11. Exposed in Home Assistant/MQTT:
|
||||||
# 12. Exposed in Home Assistant/MQTT:
|
# - Startup action (On,Off,Restore)
|
||||||
# - Startup action
|
|
||||||
# - Fade Up / Fade Down / Fade Stop buttons
|
# - Fade Up / Fade Down / Fade Stop buttons
|
||||||
# - Fade Up/Down switch
|
# - Fade Up/Down toggle switch
|
||||||
# - Normal On/Off switch (quick ramp up/down)
|
# - Normal On/Off switch (quick ramp up/down)
|
||||||
# - Fade up/down times (0-60s)
|
# - Fade up/down times (0-60s)
|
||||||
# - Output % (pre-gamma) and PWM % (post-gamma)
|
# - Output % (pre-gamma) and PWM % (post-gamma)
|
||||||
# - Output Set (1-100, respects min/max)
|
# - Output Set (1-100, respects min/max). This also changes with other output so reflects value.
|
||||||
# - Device diagnostics (from the included package)
|
# - Many device diagnostics (from the included 'diagnostics' package)
|
||||||
# - Maximum 'on' time before automatic fade-down (1-48 h, 0 = no limit)
|
# - Maximum 'on' time before automatic fade-down (1-48 h, 0 = no limit)
|
||||||
|
###########################################################################################
|
||||||
|
# Hardware: Sinilink MOSFET Board XY-WFMS (ESP8266) - sometimes listed as "XY-VFMS"
|
||||||
|
# https://devices.esphome.io/devices/Sinilink-XY-WFMS
|
||||||
|
# ------------------------------------------
|
||||||
|
# DEVICE GPIO (Sinilink XY-WFMS)
|
||||||
|
# ------------------------------------------
|
||||||
|
# GPIO02 Blue LED, active-low (used for ESPHome status)
|
||||||
|
# GPIO04 MOSFET output (0V when switched) and Red LED
|
||||||
|
# GPIO12 Toggle button
|
||||||
|
# GPIO13 Green LED (used to display fading status)
|
||||||
|
#
|
||||||
|
# 3D-printed case for Sinilink XY-WFMS
|
||||||
|
# https://cults3d.com/en/3d-model/tool/snapfit-enclosure-for-esp8266-sinilink-xy-wfms-5v-36v-mosfet-switch-module
|
||||||
#
|
#
|
||||||
##########################################################################################
|
##########################################################################################
|
||||||
##########################################################################################
|
##########################################################################################
|
||||||
|
|
||||||
|
|
||||||
##########################################################################################
|
##########################################################################################
|
||||||
# SPECIFIC DEVICE VARIABLE SUBSTITUTIONS
|
# SPECIFIC DEVICE VARIABLE SUBSTITUTIONS
|
||||||
# If NOT using a secrets file, just replace these with the passwords etc (in quotes)
|
# If NOT using a secrets file, just replace these with the passwords etc (in quotes)
|
||||||
##########################################################################################
|
##########################################################################################
|
||||||
substitutions:
|
substitutions:
|
||||||
# Device Naming
|
# Device Naming
|
||||||
device_name: "esp-downstairskitchleds"
|
device_name: "esp-downstairskitchleds" # yaml file should be device_name.yaml
|
||||||
friendly_name: "Downstairs Kitchen LEDs"
|
friendly_name: "Downstairs Kitchen LEDs"
|
||||||
description_comment: "Downstairs Kitchen Over Pantry LEDs :: Sinilink XY-WFMS"
|
description_comment: "Downstairs Kitchen Over Pantry LEDs :: Sinilink XY-WFMS"
|
||||||
device_area: "Downstairs Kitchen" # Allows the ESP device to be automatically linked to an 'Area' in Home Assistant.
|
device_area: "Downstairs Kitchen" # Allows the ESP device to be automatically linked to an 'Area' in Home Assistant.
|
||||||
|
|
||||||
# Project Naming
|
# Project Naming
|
||||||
project_name: "Sinilink.XY-WFMS" # Project details
|
project_name: "Sinilink.XY-WFMS" # Project details. Manufacturer before the dot, device after the dot.
|
||||||
project_version: "v1.5" # Project version denotes release of the YAML file, allowing checking of deployed vs latest version
|
project_version: "v2.0" # Project version denotes release of the YAML file, allowing checking of deployed vs latest version
|
||||||
|
|
||||||
# Passwords & Secrets
|
# Passwords & Secrets (Unfortunately, you can't use substitutions inside secret names)
|
||||||
api_key: !secret esp-api_key
|
api_key: !secret esp-api_key
|
||||||
ota_pass: !secret esp-ota_pass
|
ota_pass: !secret esp-ota_pass
|
||||||
static_ip_address: !secret esp-downstairskitchleds_ip # Unfortunately, you can't use substitutions inside secret names
|
static_ip_address: !secret esp-downstairskitchleds_ip # CHANGE THIS
|
||||||
mqtt_local_command_main_topic: !secret mqtt_local_command_main_topic
|
|
||||||
mqtt_local_status_main_topic: !secret mqtt_local_status_main_topic
|
|
||||||
|
|
||||||
# MQTT LOCAL Controls
|
# MQTT LOCAL Controls
|
||||||
mqtt_local_device_name: "downstairskitchen-pantryleds"
|
mqtt_local_device_name: "downstairskitchen-pantryleds"
|
||||||
|
mqtt_local_command_main_topic: !secret mqtt_local_command_main_topic
|
||||||
|
mqtt_local_status_main_topic: !secret mqtt_local_status_main_topic
|
||||||
mqtt_local_command_topic: "${mqtt_local_command_main_topic}/${mqtt_local_device_name}" # Topic we will use to command this locally without HA
|
mqtt_local_command_topic: "${mqtt_local_command_main_topic}/${mqtt_local_device_name}" # Topic we will use to command this locally without HA
|
||||||
mqtt_local_status_topic: "${mqtt_local_status_main_topic}/${mqtt_local_device_name}" # Topic we will use to view status locally without HA
|
mqtt_local_status_topic: "${mqtt_local_status_main_topic}/${mqtt_local_device_name}" # Topic we will use to view status locally without HA
|
||||||
mqtt_local_device_command_ON: "ON"
|
mqtt_local_device_command_ON: "ON"
|
||||||
mqtt_local_device_command_OFF: "OFF"
|
mqtt_local_device_command_OFF: "OFF"
|
||||||
|
|
||||||
# Device Specific Settings
|
# Device Specific Settings
|
||||||
log_level: "NONE" # Define logging level: NONE, ERROR, WARN, INFO, DEBUG (default), VERBOSE, VERY_VERBOSE
|
log_level: "NONE" # Define logging level: NONE, ERROR, WARN, INFO, DEBUG (default), VERBOSE, VERY_VERBOSE
|
||||||
update_interval: "20s" # Update time for general sensors, etc.
|
update_interval: "20s" # Update time for general sensors, etc.
|
||||||
led_gamma: "1.2" # Gamma from 1.2-3 is sensible to normalise the LED fading vs PWM
|
led_gamma: "1.2" # Gamma from 1.2-3 is sensible to normalise the LED fading vs PWM
|
||||||
minimum_led_output: "1" # % If at this value or below, we'll switch it completely off
|
minimum_led_output: "1" # % If at this value or below, we'll switch it completely off
|
||||||
maximum_led_output: "90" # % Maximum output; it is sometimes nice to limit the output for longevity or aesthetics
|
maximum_led_output: "90" # % Maximum output; it is sometimes nice to limit the output for longevity or aesthetics
|
||||||
max_on_default_hours: "6" # The maximum time the LEDs will be on, in case they get left on. 0 = no automatic turn-off
|
max_on_default_hours: "24" # The maximum time the LEDs will be on, in case they get left on. 0 = no automatic turn-off
|
||||||
|
pwm_frequency: "1000hz" # PWM output Frequency. High enough to avoid audible/visible artifacts
|
||||||
|
|
||||||
|
# Device Specific GPIO (so we can easily update for other devices)
|
||||||
|
device_status_led: GPIO02
|
||||||
|
device_mosfet_out: GPIO04
|
||||||
|
device_usr_button: GPIO12
|
||||||
|
device_fading_led: GPIO13
|
||||||
|
|
||||||
##########################################################################################
|
##########################################################################################
|
||||||
# PACKAGES: Included Common Packages
|
# PACKAGES: Included Common Packages
|
||||||
# https://esphome.io/components/packages.html
|
# https://esphome.io/components/packages.html
|
||||||
##########################################################################################
|
##########################################################################################
|
||||||
packages:
|
packages:
|
||||||
|
#### WIFI, Network (Static/DHCP/IPV6 etc), Fallback AP, Safemode ####
|
||||||
common_wifi: !include
|
common_wifi: !include
|
||||||
file: common/network_common.yaml
|
file: common/network_common.yaml
|
||||||
vars:
|
vars:
|
||||||
local_device_name: "${device_name}"
|
local_device_name: "${device_name}"
|
||||||
local_static_ip_address: "${static_ip_address}"
|
local_static_ip_address: "${static_ip_address}"
|
||||||
local_ota_pass: "${ota_pass}"
|
local_ota_pass: "${ota_pass}"
|
||||||
|
|
||||||
|
#### HOME ASSISTANT API (choose encryption or no encryption options) ####
|
||||||
common_api: !include
|
common_api: !include
|
||||||
file: common/api_common.yaml
|
file: common/api_common.yaml
|
||||||
#file: common/api_common_noencryption.yaml
|
#file: common/api_common_noencryption.yaml
|
||||||
vars:
|
vars:
|
||||||
local_api_key: "${api_key}"
|
local_api_key: "${api_key}"
|
||||||
#common_webportal: !include
|
|
||||||
# file: common/webportal_common.yaml
|
#### MQTT ####
|
||||||
common_mqtt: !include
|
common_mqtt: !include
|
||||||
file: common/mqtt_common.yaml
|
file: common/mqtt_common.yaml
|
||||||
vars:
|
vars:
|
||||||
local_device_name: "${device_name}"
|
local_device_name: "${device_name}"
|
||||||
#common_sntp: !include
|
|
||||||
# file: common/sntp_common.yaml
|
#### WEB PORTAL ####
|
||||||
common_general_sensors: !include
|
#common_webportal: !include common/webportal_common.yaml
|
||||||
file: common/sensors_common.yaml
|
#### SNTP (Only use if you want/need accurate timeclocks) ####
|
||||||
#file: common/sensors_common_lite.yaml
|
#common_sntp: !include common/sntp_common.yaml
|
||||||
vars:
|
|
||||||
local_friendly_name: "${friendly_name}"
|
#### DIAGNOSTICS Sensors ####
|
||||||
local_update_interval: "${update_interval}"
|
diag_basic: !include common/include_basic_diag_sensors.yaml
|
||||||
|
diag_more: !include common/include_more_diag_sensors.yaml
|
||||||
|
diag_debug: !include common/include_debug_diag_sensors.yaml
|
||||||
|
diag_resetcount: !include common/include_resetcount_diag_sensors.yaml
|
||||||
|
|
||||||
##########################################################################################
|
##########################################################################################
|
||||||
# ESPHome CORE CONFIGURATION
|
# ESPHome CORE CONFIGURATION
|
||||||
@@ -134,7 +149,8 @@ esphome:
|
|||||||
comment: "${description_comment}"
|
comment: "${description_comment}"
|
||||||
area: "${device_area}"
|
area: "${device_area}"
|
||||||
on_boot:
|
on_boot:
|
||||||
priority: 600
|
# Run later so everything is initialised before we touch states.
|
||||||
|
priority: 200
|
||||||
then:
|
then:
|
||||||
# Keep the HA dropdown in sync with the stored mode
|
# Keep the HA dropdown in sync with the stored mode
|
||||||
- lambda: |-
|
- lambda: |-
|
||||||
@@ -152,34 +168,12 @@ esphome:
|
|||||||
- lambda: 'id(ramp_switch_target_on) = true;'
|
- lambda: 'id(ramp_switch_target_on) = true;'
|
||||||
- script.execute: ramp_on_script
|
- script.execute: ramp_on_script
|
||||||
|
|
||||||
# Mode 1: Restore Brightness using a scripted ramp 0 -> last non-zero brightness
|
# Mode 1: Restore Brightness (deferred to avoid races)
|
||||||
- if:
|
- if:
|
||||||
condition:
|
condition:
|
||||||
lambda: 'return id(restart_mode) == 1;'
|
lambda: 'return id(restart_mode) == 1;'
|
||||||
then:
|
then:
|
||||||
- lambda: |-
|
- script.execute: deferred_restore_brightness
|
||||||
float target = id(last_nonzero_brightness_pct);
|
|
||||||
if (target < 0.0f) target = 0.0f;
|
|
||||||
if (target > 100.0f) target = 100.0f;
|
|
||||||
|
|
||||||
// Gently clamp to min/max caps to avoid an immediate post-on_state correction.
|
|
||||||
const float minp = (float) id(min_brightness_pct);
|
|
||||||
const float maxp = (float) id(max_brightness_pct);
|
|
||||||
if (target > 0.0f && target < minp) target = minp;
|
|
||||||
if (target > maxp) target = maxp;
|
|
||||||
|
|
||||||
id(restore_target_pct) = target; // set scripted target
|
|
||||||
id(ramp_switch_target_on) = (target > 0.0f);
|
|
||||||
- if:
|
|
||||||
condition:
|
|
||||||
lambda: 'return id(restore_target_pct) > 0.0f;'
|
|
||||||
then:
|
|
||||||
- script.stop: ramp_off_script
|
|
||||||
- script.execute: ramp_to_target_script
|
|
||||||
else:
|
|
||||||
- light.turn_off:
|
|
||||||
id: mosfet_leds
|
|
||||||
transition_length: 0s
|
|
||||||
|
|
||||||
# Mode 2: Remain Off (no blip; stays off)
|
# Mode 2: Remain Off (no blip; stays off)
|
||||||
- if:
|
- if:
|
||||||
@@ -193,15 +187,19 @@ esphome:
|
|||||||
id: mosfet_leds
|
id: mosfet_leds
|
||||||
transition_length: 0s
|
transition_length: 0s
|
||||||
|
|
||||||
platformio_options:
|
# Boot complete: allow OFF handlers again
|
||||||
build_unflags:
|
- lambda: 'id(booting) = false;'
|
||||||
- -flto
|
|
||||||
build_flags:
|
# Only if you want to play with build flags...
|
||||||
- -fno-lto
|
# platformio_options:
|
||||||
- -Wl,--gc-sections
|
# build_unflags:
|
||||||
- -ffunction-sections
|
# - -flto
|
||||||
- -fdata-sections
|
# build_flags:
|
||||||
- -DNDEBUG
|
# - -fno-lto
|
||||||
|
# - -Wl,--gc-sections
|
||||||
|
# - -ffunction-sections
|
||||||
|
# - -fdata-sections
|
||||||
|
# - -DNDEBUG
|
||||||
|
|
||||||
##########################################################################################
|
##########################################################################################
|
||||||
# ESP PLATFORM AND FRAMEWORK
|
# ESP PLATFORM AND FRAMEWORK
|
||||||
@@ -212,14 +210,11 @@ esp8266:
|
|||||||
board: esp01_1m
|
board: esp01_1m
|
||||||
restore_from_flash: true # restore some values on reboot
|
restore_from_flash: true # restore some values on reboot
|
||||||
|
|
||||||
#preferences:
|
|
||||||
# flash_write_interval: 5min
|
|
||||||
|
|
||||||
mdns:
|
mdns:
|
||||||
disabled: false # Disabling will make the build file smaller (and it is still available via static IP)
|
disabled: false # Disabling will make the build file smaller (and device is still available via static IP)
|
||||||
|
|
||||||
preferences:
|
preferences:
|
||||||
flash_write_interval: 5min
|
flash_write_interval: 5sec # enough time to update values for reboots, but not enough to wear flash
|
||||||
|
|
||||||
##########################################################################################
|
##########################################################################################
|
||||||
# GLOBAL VARIABLES
|
# GLOBAL VARIABLES
|
||||||
@@ -240,7 +235,8 @@ globals:
|
|||||||
- id: max_on_hours
|
- id: max_on_hours
|
||||||
type: int
|
type: int
|
||||||
restore_value: true
|
restore_value: true
|
||||||
initial_value: '${max_on_default_hours}'
|
initial_value: "${max_on_default_hours}"
|
||||||
|
|
||||||
# Default Fading Up Time (Selectable and will be retained)
|
# Default Fading Up Time (Selectable and will be retained)
|
||||||
- id: ramp_up_ms # fade-in when turned ON
|
- id: ramp_up_ms # fade-in when turned ON
|
||||||
type: int
|
type: int
|
||||||
@@ -304,6 +300,16 @@ globals:
|
|||||||
type: float
|
type: float
|
||||||
restore_value: false
|
restore_value: false
|
||||||
initial_value: '0.0'
|
initial_value: '0.0'
|
||||||
|
# guards OFF persistence during startup caused by restore_mode: ALWAYS_OFF
|
||||||
|
- id: booting
|
||||||
|
type: bool
|
||||||
|
restore_value: false
|
||||||
|
initial_value: 'true'
|
||||||
|
# true only while we are performing a ramp-down that briefly turns the light ON to hit the floor
|
||||||
|
- id: ramping_for_off
|
||||||
|
type: bool
|
||||||
|
restore_value: false
|
||||||
|
initial_value: 'false'
|
||||||
|
|
||||||
##########################################################################################
|
##########################################################################################
|
||||||
# LOGGER COMPONENT
|
# LOGGER COMPONENT
|
||||||
@@ -314,6 +320,15 @@ logger:
|
|||||||
level: "${log_level}" # INFO Level suggested, or DEBUG for testing
|
level: "${log_level}" # INFO Level suggested, or DEBUG for testing
|
||||||
baud_rate: 0 # set to 0 for no logging via UART, needed if you are using it for other serial things (eg PZEM, Serial control)
|
baud_rate: 0 # set to 0 for no logging via UART, needed if you are using it for other serial things (eg PZEM, Serial control)
|
||||||
|
|
||||||
|
#########################################################################################
|
||||||
|
# STATUS LED
|
||||||
|
# https://esphome.io/components/status_led.html
|
||||||
|
#########################################################################################
|
||||||
|
status_led:
|
||||||
|
pin:
|
||||||
|
number: ${device_status_led}
|
||||||
|
inverted: true
|
||||||
|
|
||||||
##########################################################################################
|
##########################################################################################
|
||||||
# MQTT COMMANDS
|
# MQTT COMMANDS
|
||||||
# This adds device-specific MQTT command triggers to the common MQTT configuration.
|
# This adds device-specific MQTT command triggers to the common MQTT configuration.
|
||||||
@@ -331,17 +346,6 @@ mqtt:
|
|||||||
then:
|
then:
|
||||||
- switch.turn_off: mosfet_ramp_switch
|
- switch.turn_off: mosfet_ramp_switch
|
||||||
|
|
||||||
#########################################################################################
|
|
||||||
# STATUS LED
|
|
||||||
# https://esphome.io/components/status_led.html
|
|
||||||
#########################################################################################
|
|
||||||
# SINILINK: Status LED Blue LED on GPIO2, active-low
|
|
||||||
#########################################################################################
|
|
||||||
status_led:
|
|
||||||
pin:
|
|
||||||
number: GPIO2
|
|
||||||
inverted: true
|
|
||||||
|
|
||||||
##########################################################################################
|
##########################################################################################
|
||||||
# SWITCH COMPONENT
|
# SWITCH COMPONENT
|
||||||
# https://esphome.io/components/switch/
|
# https://esphome.io/components/switch/
|
||||||
@@ -400,15 +404,18 @@ button:
|
|||||||
name: "${friendly_name} Fade Stop"
|
name: "${friendly_name} Fade Stop"
|
||||||
icon: mdi:pause
|
icon: mdi:pause
|
||||||
on_press:
|
on_press:
|
||||||
# Stop any pending scripts (and their delayed turn_off)
|
# Stop any pending scripts (and their delayed actions)
|
||||||
- script.stop: ramp_on_script
|
- script.stop: ramp_on_script
|
||||||
- script.stop: ramp_off_script
|
- script.stop: ramp_off_script
|
||||||
- script.stop: led_flash_up
|
- script.stop: led_flash_up
|
||||||
- script.stop: led_flash_down
|
- script.stop: led_flash_down
|
||||||
- output.turn_off: green_led_out
|
- output.turn_off: green_led_out
|
||||||
# Cancel the light's transition by commanding the current level with 0 ms,
|
|
||||||
# but DO NOT change the ramp switch state/flag.
|
|
||||||
- lambda: |-
|
- lambda: |-
|
||||||
|
// We are no longer ramping (up or down)
|
||||||
|
id(ramping_for_off) = false;
|
||||||
|
id(is_ramping) = false;
|
||||||
|
|
||||||
|
// Freeze the light at its *current* level by overwriting the transition
|
||||||
const auto &cv = id(mosfet_leds).current_values;
|
const auto &cv = id(mosfet_leds).current_values;
|
||||||
if (cv.is_on()) {
|
if (cv.is_on()) {
|
||||||
auto call = id(mosfet_leds).make_call();
|
auto call = id(mosfet_leds).make_call();
|
||||||
@@ -418,6 +425,14 @@ button:
|
|||||||
call.perform();
|
call.perform();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Persist the exact frozen level so "Restore Brightness" survives a reboot
|
||||||
|
if (cv.is_on()) {
|
||||||
|
float pct = cv.get_brightness() * 100.0f;
|
||||||
|
id(last_brightness_pct) = pct;
|
||||||
|
if (pct > 0.5f) id(last_nonzero_brightness_pct) = pct;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
#########################################################################################
|
#########################################################################################
|
||||||
# SELECT COMPONENT
|
# SELECT COMPONENT
|
||||||
# https://esphome.io/components/select/index.html
|
# https://esphome.io/components/select/index.html
|
||||||
@@ -452,7 +467,7 @@ binary_sensor:
|
|||||||
id: btn_gpio12
|
id: btn_gpio12
|
||||||
name: "${friendly_name} Button"
|
name: "${friendly_name} Button"
|
||||||
pin:
|
pin:
|
||||||
number: GPIO12
|
number: ${device_usr_button}
|
||||||
mode:
|
mode:
|
||||||
input: true
|
input: true
|
||||||
pullup: true
|
pullup: true
|
||||||
@@ -465,14 +480,14 @@ binary_sensor:
|
|||||||
condition:
|
condition:
|
||||||
lambda: 'return id(ramp_switch_target_on);'
|
lambda: 'return id(ramp_switch_target_on);'
|
||||||
then:
|
then:
|
||||||
# Target is currently ON → press should go OFF (start ramp-down)
|
# Target is currently ON -> press should go OFF (start ramp-down)
|
||||||
- lambda: |-
|
- lambda: |-
|
||||||
id(ramp_switch_target_on) = false;
|
id(ramp_switch_target_on) = false;
|
||||||
id(mosfet_ramp_switch).publish_state(false); // reflect in HA immediately
|
id(mosfet_ramp_switch).publish_state(false); // reflect in HA immediately
|
||||||
- script.stop: ramp_on_script
|
- script.stop: ramp_on_script
|
||||||
- script.execute: ramp_off_script
|
- script.execute: ramp_off_script
|
||||||
else:
|
else:
|
||||||
# Target is currently OFF → press should go ON (start ramp-up)
|
# Target is currently OFF -> press should go ON (start ramp-up)
|
||||||
- lambda: |-
|
- lambda: |-
|
||||||
id(ramp_switch_target_on) = true;
|
id(ramp_switch_target_on) = true;
|
||||||
id(mosfet_ramp_switch).publish_state(true); // reflect in HA immediately
|
id(mosfet_ramp_switch).publish_state(true); // reflect in HA immediately
|
||||||
@@ -497,17 +512,22 @@ sensor:
|
|||||||
on_value:
|
on_value:
|
||||||
then:
|
then:
|
||||||
- lambda: |-
|
- lambda: |-
|
||||||
// Remember latest actual output (0..100) for "Restore Brightness"
|
const auto &cv_now = id(mosfet_leds).current_values;
|
||||||
id(last_brightness_pct) = x;
|
|
||||||
if (x > 0.5f) {
|
// Only persist exact last brightness while actually ON,
|
||||||
// Only persist when not ramping; always track a volatile copy
|
// and not during the OFF ramp's brief floor-kick.
|
||||||
if (!id(is_ramping)) id(last_nonzero_brightness_pct) = x;
|
if (cv_now.is_on()) {
|
||||||
id(last_nonzero_tmp) = x;
|
if (x > 0.5f) {
|
||||||
|
if (!id(is_ramping)) id(last_nonzero_brightness_pct) = x;
|
||||||
|
id(last_nonzero_tmp) = x;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (x > 0.5f) id(last_nonzero_tmp) = x;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If not suppressing sync, update the 0..100 slider only when its INT changes
|
// If not suppressing sync, update the 0..100 slider only when its INT changes
|
||||||
if (!id(suppress_slider_sync)) {
|
if (!id(suppress_slider_sync)) {
|
||||||
float actual = x; // actual %
|
float actual = x;
|
||||||
float minp = (float) id(min_brightness_pct);
|
float minp = (float) id(min_brightness_pct);
|
||||||
float maxp = (float) id(max_brightness_pct);
|
float maxp = (float) id(max_brightness_pct);
|
||||||
if (maxp <= minp) maxp = minp + 1.0f;
|
if (maxp <= minp) maxp = minp + 1.0f;
|
||||||
@@ -531,7 +551,7 @@ sensor:
|
|||||||
lambda: |-
|
lambda: |-
|
||||||
const auto &cv = id(mosfet_leds).current_values;
|
const auto &cv = id(mosfet_leds).current_values;
|
||||||
if (!cv.is_on()) return 0.0f;
|
if (!cv.is_on()) return 0.0f;
|
||||||
const float lin = cv.get_brightness(); // 0..1 (linear brightness)
|
const float lin = cv.get_brightness(); // 0..1 linear brightness
|
||||||
const float gamma = atof("${led_gamma}"); // parse substitution string -> float
|
const float gamma = atof("${led_gamma}"); // parse substitution string -> float
|
||||||
float pwm = powf(lin, gamma); // approx PWM duty after gamma
|
float pwm = powf(lin, gamma); // approx PWM duty after gamma
|
||||||
if (pwm < 0.0f) pwm = 0.0f;
|
if (pwm < 0.0f) pwm = 0.0f;
|
||||||
@@ -548,12 +568,12 @@ sensor:
|
|||||||
output:
|
output:
|
||||||
- platform: esp8266_pwm
|
- platform: esp8266_pwm
|
||||||
id: mosfet_pwm
|
id: mosfet_pwm
|
||||||
pin: GPIO4
|
pin: ${device_mosfet_out}
|
||||||
frequency: 1000 Hz # high frequency to avoid audible/visible artifacts
|
frequency: ${pwm_frequency}
|
||||||
- platform: gpio
|
- platform: gpio
|
||||||
id: green_led_out # Green LED
|
id: green_led_out # Green LED
|
||||||
pin:
|
pin:
|
||||||
number: GPIO13
|
number: ${device_fading_led}
|
||||||
inverted: false
|
inverted: false
|
||||||
|
|
||||||
##########################################################################################
|
##########################################################################################
|
||||||
@@ -565,29 +585,48 @@ light:
|
|||||||
id: mosfet_leds
|
id: mosfet_leds
|
||||||
name: "${friendly_name}"
|
name: "${friendly_name}"
|
||||||
output: mosfet_pwm
|
output: mosfet_pwm
|
||||||
restore_mode: ALWAYS_OFF
|
restore_mode: RESTORE_DEFAULT_OFF
|
||||||
default_transition_length: 2s
|
default_transition_length: 2s
|
||||||
icon: mdi:led-strip-variant
|
icon: mdi:led-strip-variant
|
||||||
gamma_correct: "${led_gamma}"
|
gamma_correct: "${led_gamma}"
|
||||||
|
|
||||||
|
# ON: publish state, track intent, arm watchdog if configured
|
||||||
on_turn_on:
|
on_turn_on:
|
||||||
- mqtt.publish:
|
- mqtt.publish:
|
||||||
topic: "${mqtt_local_status_topic}/light/state"
|
topic: "${mqtt_local_status_topic}/light/state"
|
||||||
payload: "${mqtt_local_device_command_ON}"
|
payload: "${mqtt_local_device_command_ON}"
|
||||||
retain: true
|
retain: true
|
||||||
- lambda: 'id(ramp_switch_target_on) = true;'
|
- lambda: |-
|
||||||
|
// Do not flip "intent to ON" when we're inside an OFF ramp's floor-kick
|
||||||
|
if (!id(ramping_for_off)) {
|
||||||
|
id(ramp_switch_target_on) = true;
|
||||||
|
}
|
||||||
- script.stop: max_on_watchdog
|
- script.stop: max_on_watchdog
|
||||||
- if:
|
- if:
|
||||||
condition:
|
condition:
|
||||||
lambda: 'return id(max_on_hours) > 0;'
|
lambda: 'return id(max_on_hours) > 0;'
|
||||||
then:
|
then:
|
||||||
- script.execute: max_on_watchdog
|
- script.execute: max_on_watchdog
|
||||||
|
|
||||||
|
# OFF: publish state, track intent, clear watchdog, and persist OFF so Mode 1 will not restore
|
||||||
|
# Guarded by booting flag so the automatic OFF at boot from ALWAYS_OFF does not zero persistence.
|
||||||
on_turn_off:
|
on_turn_off:
|
||||||
- mqtt.publish:
|
- if:
|
||||||
topic: "${mqtt_local_status_topic}/light/state"
|
condition:
|
||||||
payload: "${mqtt_local_device_command_OFF}"
|
lambda: 'return !id(booting);'
|
||||||
retain: true
|
then:
|
||||||
- lambda: 'id(ramp_switch_target_on) = false;'
|
- mqtt.publish:
|
||||||
- script.stop: max_on_watchdog
|
topic: "${mqtt_local_status_topic}/light/state"
|
||||||
|
payload: "${mqtt_local_device_command_OFF}"
|
||||||
|
retain: true
|
||||||
|
- lambda: 'id(ramp_switch_target_on) = false;'
|
||||||
|
- script.stop: max_on_watchdog
|
||||||
|
- lambda: |-
|
||||||
|
id(last_brightness_pct) = 0.0f;
|
||||||
|
id(last_set_pos) = 0;
|
||||||
|
id(led_output_set_pct).publish_state(0);
|
||||||
|
|
||||||
|
# Cap brightness to max_brightness_pct without a visible step
|
||||||
on_state:
|
on_state:
|
||||||
- lambda: |-
|
- lambda: |-
|
||||||
const float cap = id(max_brightness_pct) / 100.0f;
|
const float cap = id(max_brightness_pct) / 100.0f;
|
||||||
@@ -680,6 +719,8 @@ number:
|
|||||||
- lambda: |-
|
- lambda: |-
|
||||||
id(ramp_switch_target_on) = false;
|
id(ramp_switch_target_on) = false;
|
||||||
id(led_output_set_pct).publish_state(0);
|
id(led_output_set_pct).publish_state(0);
|
||||||
|
id(last_brightness_pct) = 0.0f; // persist OFF immediately
|
||||||
|
id(last_set_pos) = 0;
|
||||||
- delay: 400ms
|
- delay: 400ms
|
||||||
- lambda: 'id(suppress_slider_sync) = false;'
|
- lambda: 'id(suppress_slider_sync) = false;'
|
||||||
else:
|
else:
|
||||||
@@ -703,7 +744,19 @@ number:
|
|||||||
if (out_pct > maxp) out_pct = maxp;
|
if (out_pct > maxp) out_pct = maxp;
|
||||||
return out_pct / 100.0f;
|
return out_pct / 100.0f;
|
||||||
transition_length: 250ms
|
transition_length: 250ms
|
||||||
- lambda: 'id(ramp_switch_target_on) = true;'
|
- lambda: |-
|
||||||
|
id(ramp_switch_target_on) = true;
|
||||||
|
float pos = id(led_output_set_pct).state; // 1..100
|
||||||
|
float minp = (float) id(min_brightness_pct);
|
||||||
|
float maxp = (float) id(max_brightness_pct);
|
||||||
|
if (maxp <= minp) maxp = minp + 1.0f;
|
||||||
|
float out_pct = minp + (pos * (maxp - minp) / 100.0f);
|
||||||
|
if (out_pct > maxp) out_pct = maxp;
|
||||||
|
id(last_brightness_pct) = out_pct; // persist exact target now
|
||||||
|
if (out_pct > 0.5f) {
|
||||||
|
id(last_nonzero_brightness_pct) = out_pct;
|
||||||
|
id(last_nonzero_tmp) = out_pct;
|
||||||
|
}
|
||||||
- delay: 400ms
|
- delay: 400ms
|
||||||
- lambda: 'id(suppress_slider_sync) = false;'
|
- lambda: 'id(suppress_slider_sync) = false;'
|
||||||
|
|
||||||
@@ -776,7 +829,7 @@ script:
|
|||||||
- id: ramp_on_script
|
- id: ramp_on_script
|
||||||
mode: restart
|
mode: restart
|
||||||
then:
|
then:
|
||||||
- lambda: 'id(is_ramping) = true;' # mark ramping
|
- lambda: 'id(is_ramping) = true;'
|
||||||
- script.stop: ramp_off_script
|
- script.stop: ramp_off_script
|
||||||
- script.stop: led_flash_down
|
- script.stop: led_flash_down
|
||||||
- script.execute: led_flash_up
|
- script.execute: led_flash_up
|
||||||
@@ -800,13 +853,10 @@ script:
|
|||||||
const auto &cv = id(mosfet_leds).current_values;
|
const auto &cv = id(mosfet_leds).current_values;
|
||||||
const float floor = id(min_brightness_pct) / 100.0f;
|
const float floor = id(min_brightness_pct) / 100.0f;
|
||||||
const float cap = id(max_brightness_pct) / 100.0f;
|
const float cap = id(max_brightness_pct) / 100.0f;
|
||||||
if (cap <= floor + 0.0005f) { id(last_ramp_ms) = 0; return (uint32_t) 0; } // guard degenerate case
|
if (cap <= floor + 0.0005f) { id(last_ramp_ms) = 0; return (uint32_t) 0; }
|
||||||
|
|
||||||
// IMPORTANT: when the light was OFF, treat current as 0 so we don't compute a 0ms jump to cap.
|
|
||||||
float curr = cv.is_on() ? cv.get_brightness() : 0.0f;
|
float curr = cv.is_on() ? cv.get_brightness() : 0.0f;
|
||||||
|
|
||||||
// If we're below the floor (i.e. coming from OFF), we just did the "floor kick" above.
|
|
||||||
// Use full ramp_up_ms for a smooth 0 -> cap ramp.
|
|
||||||
if (curr + 0.0005f < floor) {
|
if (curr + 0.0005f < floor) {
|
||||||
id(last_ramp_ms) = id(ramp_up_ms);
|
id(last_ramp_ms) = id(ramp_up_ms);
|
||||||
return (uint32_t) id(last_ramp_ms);
|
return (uint32_t) id(last_ramp_ms);
|
||||||
@@ -822,18 +872,19 @@ script:
|
|||||||
- script.stop: led_flash_up
|
- script.stop: led_flash_up
|
||||||
- output.turn_off: green_led_out
|
- output.turn_off: green_led_out
|
||||||
- lambda: |-
|
- lambda: |-
|
||||||
id(is_ramping) = false; // ramp complete
|
id(is_ramping) = false;
|
||||||
const auto &cv = id(mosfet_leds).current_values;
|
const auto &cv = id(mosfet_leds).current_values;
|
||||||
if (cv.is_on()) {
|
if (cv.is_on()) {
|
||||||
float pct = cv.get_brightness() * 100.0f;
|
float pct = cv.get_brightness() * 100.0f;
|
||||||
if (pct > 0.5f) id(last_nonzero_brightness_pct) = pct; // commit once
|
id(last_brightness_pct) = pct; // persist final level
|
||||||
|
if (pct > 0.5f) id(last_nonzero_brightness_pct) = pct;
|
||||||
}
|
}
|
||||||
|
|
||||||
# Script: ramp to a specific target (Restore Brightness path)
|
# Script: ramp to a specific target (Restore Brightness path)
|
||||||
- id: ramp_to_target_script
|
- id: ramp_to_target_script
|
||||||
mode: restart
|
mode: restart
|
||||||
then:
|
then:
|
||||||
- lambda: 'id(is_ramping) = true;' # mark ramping
|
- lambda: 'id(is_ramping) = true;'
|
||||||
- script.stop: ramp_off_script
|
- script.stop: ramp_off_script
|
||||||
- script.stop: led_flash_down
|
- script.stop: led_flash_down
|
||||||
- script.execute: led_flash_up
|
- script.execute: led_flash_up
|
||||||
@@ -863,12 +914,9 @@ script:
|
|||||||
float cap = id(restore_target_pct) / 100.0f;
|
float cap = id(restore_target_pct) / 100.0f;
|
||||||
const float maxp = id(max_brightness_pct) / 100.0f;
|
const float maxp = id(max_brightness_pct) / 100.0f;
|
||||||
if (cap > maxp) cap = maxp;
|
if (cap > maxp) cap = maxp;
|
||||||
if (cap <= floor + 0.0005f) { id(last_ramp_ms) = 0; return (uint32_t) 0; } // guard
|
if (cap <= floor + 0.0005f) { id(last_ramp_ms) = 0; return (uint32_t) 0; }
|
||||||
|
|
||||||
// Same OFF case handling: if off, treat curr as 0 so we don't collapse the ramp.
|
|
||||||
float curr = cv.is_on() ? cv.get_brightness() : 0.0f;
|
float curr = cv.is_on() ? cv.get_brightness() : 0.0f;
|
||||||
|
|
||||||
// Coming from OFF/below floor? We just did the "floor kick" above — use full ramp_up_ms.
|
|
||||||
if (curr + 0.0005f < floor) {
|
if (curr + 0.0005f < floor) {
|
||||||
id(last_ramp_ms) = id(ramp_up_ms);
|
id(last_ramp_ms) = id(ramp_up_ms);
|
||||||
return (uint32_t) id(last_ramp_ms);
|
return (uint32_t) id(last_ramp_ms);
|
||||||
@@ -885,18 +933,21 @@ script:
|
|||||||
- script.stop: led_flash_up
|
- script.stop: led_flash_up
|
||||||
- output.turn_off: green_led_out
|
- output.turn_off: green_led_out
|
||||||
- lambda: |-
|
- lambda: |-
|
||||||
id(is_ramping) = false; // ramp complete
|
id(is_ramping) = false;
|
||||||
const auto &cv = id(mosfet_leds).current_values;
|
const auto &cv = id(mosfet_leds).current_values;
|
||||||
if (cv.is_on()) {
|
if (cv.is_on()) {
|
||||||
float pct = cv.get_brightness() * 100.0f;
|
float pct = cv.get_brightness() * 100.0f;
|
||||||
if (pct > 0.5f) id(last_nonzero_brightness_pct) = pct; // commit once
|
id(last_brightness_pct) = pct; // persist final level
|
||||||
|
if (pct > 0.5f) id(last_nonzero_brightness_pct) = pct;
|
||||||
}
|
}
|
||||||
|
|
||||||
# Script: ramp down from current level to floor, then cleanly cut to OFF
|
# Script: ramp down from current level to floor, then cleanly cut to OFF
|
||||||
- id: ramp_off_script
|
- id: ramp_off_script
|
||||||
mode: restart
|
mode: restart
|
||||||
then:
|
then:
|
||||||
- lambda: 'id(is_ramping) = true;' # mark ramping
|
- lambda: |-
|
||||||
|
id(is_ramping) = true;
|
||||||
|
id(ramping_for_off) = true;
|
||||||
- script.stop: ramp_on_script
|
- script.stop: ramp_on_script
|
||||||
- script.stop: led_flash_up
|
- script.stop: led_flash_up
|
||||||
- script.execute: led_flash_down
|
- script.execute: led_flash_down
|
||||||
@@ -909,7 +960,7 @@ script:
|
|||||||
float curr = cv.get_brightness();
|
float curr = cv.get_brightness();
|
||||||
if (curr < floor) curr = floor;
|
if (curr < floor) curr = floor;
|
||||||
const float denom = (1.0f - floor);
|
const float denom = (1.0f - floor);
|
||||||
if (denom <= 0.0005f) { id(last_ramp_ms) = 0; return (uint32_t) 0; } // guard near-1.0 floor
|
if (denom <= 0.0005f) { id(last_ramp_ms) = 0; return (uint32_t) 0; }
|
||||||
float frac = (curr - floor) / denom;
|
float frac = (curr - floor) / denom;
|
||||||
if (frac < 0.0f) frac = 0.0f;
|
if (frac < 0.0f) frac = 0.0f;
|
||||||
if (frac > 1.0f) frac = 1.0f;
|
if (frac > 1.0f) frac = 1.0f;
|
||||||
@@ -923,11 +974,12 @@ script:
|
|||||||
- script.stop: led_flash_down
|
- script.stop: led_flash_down
|
||||||
- output.turn_off: green_led_out
|
- output.turn_off: green_led_out
|
||||||
- lambda: |-
|
- lambda: |-
|
||||||
id(is_ramping) = false; // ramp complete
|
id(is_ramping) = false;
|
||||||
auto call = id(mosfet_leds).make_call();
|
id(ramping_for_off) = false;
|
||||||
call.set_state(false);
|
id(last_brightness_pct) = 0.0f; // persist OFF for restore path
|
||||||
call.set_brightness(id(max_brightness_pct) / 100.0f);
|
id(last_set_pos) = 0;
|
||||||
call.perform();
|
id(led_output_set_pct).publish_state(0);
|
||||||
|
|
||||||
- id: max_on_watchdog
|
- id: max_on_watchdog
|
||||||
mode: restart
|
mode: restart
|
||||||
then:
|
then:
|
||||||
@@ -945,3 +997,38 @@ script:
|
|||||||
id(mosfet_ramp_switch).publish_state(false);
|
id(mosfet_ramp_switch).publish_state(false);
|
||||||
- script.stop: ramp_on_script
|
- script.stop: ramp_on_script
|
||||||
- script.execute: ramp_off_script
|
- script.execute: ramp_off_script
|
||||||
|
|
||||||
|
# Deferred restore to avoid early-boot races and ensure we apply the last saved level
|
||||||
|
- id: deferred_restore_brightness
|
||||||
|
mode: restart
|
||||||
|
then:
|
||||||
|
- delay: 5s # let preferences/globals load and light settle
|
||||||
|
- lambda: |-
|
||||||
|
// Use ONLY the exact last saved brightness.
|
||||||
|
float target = id(last_brightness_pct);
|
||||||
|
|
||||||
|
// Clamp into [min..max] only if we are restoring a non-zero target
|
||||||
|
float minp = (float) id(min_brightness_pct);
|
||||||
|
float maxp = (float) id(max_brightness_pct);
|
||||||
|
if (target > 0.0f && target < minp) target = minp;
|
||||||
|
if (target > maxp) target = maxp;
|
||||||
|
|
||||||
|
id(restore_target_pct) = target;
|
||||||
|
|
||||||
|
- if:
|
||||||
|
condition:
|
||||||
|
lambda: 'return id(restore_target_pct) > 0.5f;'
|
||||||
|
then:
|
||||||
|
- lambda: |-
|
||||||
|
id(ramp_switch_target_on) = true; // reflect our intent
|
||||||
|
id(suppress_slider_sync) = true;
|
||||||
|
- script.stop: ramp_off_script
|
||||||
|
- script.execute: ramp_to_target_script
|
||||||
|
- delay: !lambda 'return (uint32_t) id(last_ramp_ms);'
|
||||||
|
- lambda: 'id(suppress_slider_sync) = false;'
|
||||||
|
else:
|
||||||
|
# Exact last was 0 -> remain OFF
|
||||||
|
- lambda: 'id(ramp_switch_target_on) = false;'
|
||||||
|
- light.turn_off:
|
||||||
|
id: mosfet_leds
|
||||||
|
transition_length: 0s
|
||||||
|
|||||||
@@ -49,29 +49,57 @@ substitutions:
|
|||||||
# https://esphome.io/components/packages.html
|
# https://esphome.io/components/packages.html
|
||||||
##########################################################################################
|
##########################################################################################
|
||||||
packages:
|
packages:
|
||||||
|
##################################################
|
||||||
|
# MANDATORY packages (won't compile without them!)
|
||||||
|
##################################################
|
||||||
|
|
||||||
|
#### WIFI, Network (Static/DHCP/IPV6 etc), Fallback AP, Safemode ####
|
||||||
common_wifi: !include
|
common_wifi: !include
|
||||||
file: common/network_common.yaml
|
file: common/network_common.yaml
|
||||||
vars:
|
vars:
|
||||||
local_device_name: "${device_name}"
|
local_device_name: "${device_name}"
|
||||||
local_static_ip_address: "${static_ip_address}"
|
local_static_ip_address: "${static_ip_address}"
|
||||||
local_ota_pass: "${ota_pass}"
|
local_ota_pass: "${ota_pass}"
|
||||||
|
|
||||||
|
##################################################
|
||||||
|
# OPTIONAL packages (comment if you don't want them)
|
||||||
|
##################################################
|
||||||
|
|
||||||
|
#### HOME ASSISTANT API (choose encryption or no encryption options) ####
|
||||||
common_api: !include
|
common_api: !include
|
||||||
file: common/api_common.yaml
|
file: common/api_common.yaml
|
||||||
|
#file: common/api_common_noencryption.yaml
|
||||||
vars:
|
vars:
|
||||||
local_api_key: "${api_key}"
|
local_api_key: "${api_key}"
|
||||||
|
|
||||||
|
#### WEB PORTAL ####
|
||||||
#common_webportal: !include
|
#common_webportal: !include
|
||||||
# file: common/webportal_common.yaml
|
# file: common/webportal_common.yaml
|
||||||
|
|
||||||
|
#### MQTT ####
|
||||||
common_mqtt: !include
|
common_mqtt: !include
|
||||||
file: common/mqtt_common.yaml
|
file: common/mqtt_common.yaml
|
||||||
vars:
|
vars:
|
||||||
local_device_name: "${device_name}"
|
local_device_name: "${device_name}"
|
||||||
common_sntp: !include
|
|
||||||
file: common/sntp_common.yaml
|
#### SNTP (Only use if you want/need accurate timeclocks) ####
|
||||||
|
#common_sntp: !include
|
||||||
|
# file: common/sntp_common.yaml
|
||||||
|
|
||||||
|
#### DIAGNOSTICS Lite Sensors ####
|
||||||
|
# Option of everything, or a lite version
|
||||||
|
common_lite_sensors: !include
|
||||||
|
file: common/sensors_common_lite.yaml
|
||||||
|
|
||||||
|
#### DIAGNOSTICS General Sensors ####
|
||||||
|
# Option of everything, or a lite version
|
||||||
common_general_sensors: !include
|
common_general_sensors: !include
|
||||||
file: common/sensors_common.yaml
|
file: common/sensors_common.yaml
|
||||||
vars:
|
|
||||||
local_friendly_name: "${friendly_name}"
|
#### DEBUGGING Sensors ####
|
||||||
local_update_interval: "${update_interval}"
|
# A count of various resets
|
||||||
|
#common_resetcount_debugging: !include
|
||||||
|
# file: common/resetcount_common.yaml
|
||||||
|
|
||||||
#############################################
|
#############################################
|
||||||
# ESPHome
|
# ESPHome
|
||||||
|
|||||||
@@ -0,0 +1,181 @@
|
|||||||
|
##########################################################################################
|
||||||
|
##########################################################################################
|
||||||
|
# Main Entrance LIGHTSWITCH
|
||||||
|
# V3.7 2025-09-15 upload to this lightswitch from copy
|
||||||
|
#
|
||||||
|
##########################################################################################
|
||||||
|
# Zemismart KS-811 Double push button
|
||||||
|
# pinout/schematic https://community.home-assistant.io/t/zemismart-ks-811-working-with-esphome/
|
||||||
|
#
|
||||||
|
# NOTES
|
||||||
|
# - Light switch with 2 sets of lights
|
||||||
|
#
|
||||||
|
##########################################################################################
|
||||||
|
##########################################################################################
|
||||||
|
|
||||||
|
##########################################################################################
|
||||||
|
# SPECIFIC DEVICE VARIABLE SUBSTITUTIONS
|
||||||
|
# If NOT using a secrets file, just replace these with the passwords etc (in quotes)
|
||||||
|
##########################################################################################
|
||||||
|
substitutions:
|
||||||
|
# Device Naming
|
||||||
|
device_name: "esp-entrancelightswitch"
|
||||||
|
friendly_name: "Main Entrance Lightswitch (2)"
|
||||||
|
description_comment: "Main Entrance lightswitch using a Zemismart KS-811 Double Push Button. Main Lights (1), Bench Lights (2)"
|
||||||
|
device_area: "Kitchen" # Allows ESP device to be automatically linked to an 'Area' in Home Assistant.
|
||||||
|
|
||||||
|
# Project Naming
|
||||||
|
project_name: "Zemismart Technologies.KS-811 Double" # Project Details
|
||||||
|
project_version: "v3.7" # Project V denotes release of yaml file, allowing checking of deployed vs latest version
|
||||||
|
|
||||||
|
# Passwords & Secrets
|
||||||
|
api_key: !secret esp-api_key # unfortunately you can't use substitutions inside secrets names
|
||||||
|
ota_pass: !secret esp-ota_pass # unfortunately you can't use substitutions inside secrets names
|
||||||
|
static_ip_address: !secret esp-entrancelightswitch_ip
|
||||||
|
#mqtt_local_command_main_topic: !secret mqtt_local_command_main_topic
|
||||||
|
#mqtt_local_status_main_topic: !secret mqtt_local_status_main_topic
|
||||||
|
|
||||||
|
# Device Settings
|
||||||
|
#relay_icon: "mdi:lightbulb-group"
|
||||||
|
log_level: "INFO" # Define logging level: NONE, ERROR, WARN, INFO, DEBUG (Default), VERBOSE, VERY_VERBOSE
|
||||||
|
update_interval: "60s" # update time for for general sensors etc
|
||||||
|
|
||||||
|
# MQTT LOCAL Controls
|
||||||
|
#mqtt_device_name: "bedroom2-lights"
|
||||||
|
#mqtt_local_command_topic: "${mqtt_local_command_main_topic}/${mqtt_device_name}" # Topic we will use to command this locally without HA
|
||||||
|
#mqtt_local_status_topic: "${mqtt_local_status_main_topic}/${mqtt_device_name}" # Topic we will use to view status locally without HA
|
||||||
|
|
||||||
|
# Switch Naming
|
||||||
|
switch_1_name: "Entrance Lights"
|
||||||
|
switch_2_name: "External Light Controller" #permanently on, and pushbutton sends MQTT
|
||||||
|
#switch_3_name: "Nil"
|
||||||
|
|
||||||
|
#########################################################################################
|
||||||
|
# PACKAGES: Included Common Packages
|
||||||
|
# https://esphome.io/components/packages.html
|
||||||
|
##########################################################################################
|
||||||
|
packages:
|
||||||
|
common_wifi: !include
|
||||||
|
file: common/network_common.yaml
|
||||||
|
vars:
|
||||||
|
local_device_name: "${device_name}"
|
||||||
|
local_static_ip_address: "${static_ip_address}"
|
||||||
|
local_ota_pass: "${ota_pass}"
|
||||||
|
common_api: !include
|
||||||
|
file: common/api_common.yaml
|
||||||
|
vars:
|
||||||
|
local_api_key: "${api_key}"
|
||||||
|
#common_webportal: !include
|
||||||
|
# file: common/webportal_common.yaml
|
||||||
|
common_mqtt: !include
|
||||||
|
file: common/mqtt_common.yaml
|
||||||
|
vars:
|
||||||
|
local_device_name: "${device_name}"
|
||||||
|
common_sntp: !include
|
||||||
|
file: common/sntp_common.yaml
|
||||||
|
common_general_sensors: !include
|
||||||
|
file: common/sensors_common.yaml
|
||||||
|
vars:
|
||||||
|
local_friendly_name: "${friendly_name}"
|
||||||
|
local_update_interval: "${update_interval}"
|
||||||
|
|
||||||
|
#########################################################################################
|
||||||
|
# ESPHome
|
||||||
|
# https://esphome.io/components/esphome.html
|
||||||
|
#########################################################################################
|
||||||
|
esphome:
|
||||||
|
name: "${device_name}"
|
||||||
|
friendly_name: "${friendly_name}"
|
||||||
|
comment: "${description_comment}" # Appears on the esphome page in HA
|
||||||
|
area: "${device_area}"
|
||||||
|
project:
|
||||||
|
name: "${project_name}"
|
||||||
|
version: "${project_version}"
|
||||||
|
on_boot:
|
||||||
|
priority: 200
|
||||||
|
then:
|
||||||
|
- switch.turn_on: Relay_2 # Permanently on
|
||||||
|
|
||||||
|
#########################################################################################
|
||||||
|
# ESP Platform and Framework
|
||||||
|
# https://esphome.io/components/esp32.html
|
||||||
|
#########################################################################################
|
||||||
|
esp8266:
|
||||||
|
board: esp01_1m
|
||||||
|
early_pin_init: False # Initialise pins early to known values. Recommended false where switches are involved. Defaults to True.
|
||||||
|
board_flash_mode: dout # Default is dout
|
||||||
|
|
||||||
|
#########################################################################################
|
||||||
|
# ESPHome Logging Enable
|
||||||
|
# https://esphome.io/components/logger.html
|
||||||
|
#############################################
|
||||||
|
logger:
|
||||||
|
level: "${log_level}" #INFO Level suggested, or DEBUG for testing
|
||||||
|
#baud_rate: 0 #set to 0 for no logging via UART, needed if you are using it for other serial things (eg PZEM)
|
||||||
|
#esp8266_store_log_strings_in_flash: false
|
||||||
|
#tx_buffer_size: 64
|
||||||
|
|
||||||
|
#########################################################################################
|
||||||
|
# STATUS LED
|
||||||
|
# https://esphome.io/components/status_led.html
|
||||||
|
#########################################################################################
|
||||||
|
status_led:
|
||||||
|
pin:
|
||||||
|
number: GPIO2
|
||||||
|
inverted: yes
|
||||||
|
|
||||||
|
#########################################################################################
|
||||||
|
# BINARY SENSORS
|
||||||
|
# https://esphome.io/components/binary_sensor/
|
||||||
|
#########################################################################################
|
||||||
|
binary_sensor:
|
||||||
|
- platform: gpio
|
||||||
|
pin:
|
||||||
|
number: GPIO16
|
||||||
|
mode: INPUT
|
||||||
|
inverted: True
|
||||||
|
name: "Button 1: ${switch_1_name}"
|
||||||
|
on_press:
|
||||||
|
- switch.toggle: Relay_1
|
||||||
|
|
||||||
|
- platform: gpio
|
||||||
|
pin:
|
||||||
|
number: GPIO05
|
||||||
|
mode: INPUT
|
||||||
|
inverted: True
|
||||||
|
name: "Button 2: ${switch_2_name}"
|
||||||
|
# on_press:
|
||||||
|
# - switch.toggle: Relay_2
|
||||||
|
|
||||||
|
# KS-811-2 is a double only
|
||||||
|
# - platform: gpio
|
||||||
|
# pin:
|
||||||
|
# number: GPIO4
|
||||||
|
# mode: INPUT
|
||||||
|
# inverted: True
|
||||||
|
# name: "Button 3: ${switch_3_name}"
|
||||||
|
# on_press:
|
||||||
|
# - switch.toggle: Relay_3
|
||||||
|
|
||||||
|
#########################################################################################
|
||||||
|
# SWITCH COMPONENT
|
||||||
|
# https://esphome.io/components/switch/
|
||||||
|
#########################################################################################
|
||||||
|
switch:
|
||||||
|
- platform: gpio
|
||||||
|
name: "Relay 1: ${switch_1_name}"
|
||||||
|
pin: GPIO13
|
||||||
|
id: Relay_1
|
||||||
|
|
||||||
|
- platform: gpio
|
||||||
|
name: "Relay 2: ${switch_2_name}"
|
||||||
|
pin: GPIO12
|
||||||
|
id: Relay_2
|
||||||
|
|
||||||
|
# KS-811-2 is a double only
|
||||||
|
# - platform: gpio
|
||||||
|
# name: "Relay 3: ${switch_3_name}"
|
||||||
|
# pin: GPIO14
|
||||||
|
# id: Relay_3
|
||||||
|
|
||||||
|
|
||||||
+113
-32
@@ -34,18 +34,18 @@ substitutions:
|
|||||||
api_key: !secret esp-api_key # unfortunately you can't use substitutions inside secrets names
|
api_key: !secret esp-api_key # unfortunately you can't use substitutions inside secrets names
|
||||||
ota_pass: !secret esp-ota_pass # unfortunately you can't use substitutions inside secrets names
|
ota_pass: !secret esp-ota_pass # unfortunately you can't use substitutions inside secrets names
|
||||||
static_ip_address: !secret esp-laundrylights_ip
|
static_ip_address: !secret esp-laundrylights_ip
|
||||||
#mqtt_local_command_main_topic: !secret mqtt_local_command_main_topic
|
mqtt_command_main_topic: !secret mqtt_command_main_topic
|
||||||
#mqtt_local_status_main_topic: !secret mqtt_local_status_main_topic
|
mqtt_status_main_topic: !secret mqtt_status_main_topic
|
||||||
|
|
||||||
# Device Settings
|
# Device Settings
|
||||||
#relay_icon: "mdi:lightbulb-group"
|
#relay_icon: "mdi:lightbulb-group"
|
||||||
log_level: "INFO" # Define logging level: NONE, ERROR, WARN, INFO, DEBUG (Default), VERBOSE, VERY_VERBOSE
|
log_level: "INFO" # Define logging level: NONE, ERROR, WARN, INFO, DEBUG (Default), VERBOSE, VERY_VERBOSE
|
||||||
update_interval: "60s" # update time for for general sensors etc
|
update_interval: "60s" # update time for for general sensors etc
|
||||||
|
|
||||||
# MQTT Controls
|
# MQTT REMOTE Controls
|
||||||
#mqtt_device_name: "bedroom2-ceilingfan"
|
mqtt_remote1_device_name: "3dprinter-power"
|
||||||
#mqtt_local_command_topic: "${mqtt_local_command_main_topic}/${mqtt_device_name}" # Topic we will use to command this locally without HA
|
mqtt_remote1_command_topic: "${mqtt_command_main_topic}/${mqtt_remote1_device_name}" # Topic we will use to command this locally without HA
|
||||||
#mqtt_local_status_topic: "${mqtt_local_status_main_topic}/${mqtt_device_name}" # Topic we will use to view status locally without HA
|
mqtt_remote1_status_topic: "${mqtt_status_main_topic}/${mqtt_remote1_device_name}" # Topic we will use to view status locally without HA
|
||||||
|
|
||||||
# Switch Naming
|
# Switch Naming
|
||||||
switch_1_name: "Laundry Lights"
|
switch_1_name: "Laundry Lights"
|
||||||
@@ -58,29 +58,45 @@ substitutions:
|
|||||||
# https://esphome.io/components/packages.html
|
# https://esphome.io/components/packages.html
|
||||||
##########################################################################################
|
##########################################################################################
|
||||||
packages:
|
packages:
|
||||||
|
##################################################
|
||||||
|
# MANDATORY packages (won't compile without them!)
|
||||||
|
##################################################
|
||||||
|
|
||||||
|
#### WIFI, Network (Static/DHCP/IPV6 etc), Fallback AP, Safemode ####
|
||||||
common_wifi: !include
|
common_wifi: !include
|
||||||
file: common/network_common.yaml
|
file: common/network_common.yaml
|
||||||
vars:
|
vars:
|
||||||
local_device_name: "${device_name}"
|
local_device_name: "${device_name}"
|
||||||
local_static_ip_address: "${static_ip_address}"
|
local_static_ip_address: "${static_ip_address}"
|
||||||
local_ota_pass: "${ota_pass}"
|
local_ota_pass: "${ota_pass}"
|
||||||
|
|
||||||
|
##################################################
|
||||||
|
# OPTIONAL packages (comment if you don't want them)
|
||||||
|
##################################################
|
||||||
|
|
||||||
|
#### HOME ASSISTANT API (choose encryption or no encryption options) ####
|
||||||
common_api: !include
|
common_api: !include
|
||||||
file: common/api_common.yaml
|
file: common/api_common.yaml
|
||||||
|
#file: common/api_common_noencryption.yaml
|
||||||
vars:
|
vars:
|
||||||
local_api_key: "${api_key}"
|
local_api_key: "${api_key}"
|
||||||
#common_webportal: !include
|
|
||||||
# file: common/webportal_common.yaml
|
#### MQTT ####
|
||||||
common_mqtt: !include
|
common_mqtt: !include
|
||||||
file: common/mqtt_common.yaml
|
file: common/mqtt_common.yaml
|
||||||
vars:
|
vars:
|
||||||
local_device_name: "${device_name}"
|
local_device_name: "${device_name}"
|
||||||
common_sntp: !include
|
|
||||||
file: common/sntp_common.yaml
|
#### WEB PORTAL ####
|
||||||
common_general_sensors: !include
|
common_webportal: !include common/webportal_common.yaml
|
||||||
file: common/sensors_common.yaml
|
#### SNTP (Only use if you want/need accurate timeclocks) ####
|
||||||
vars:
|
common_sntp: !include common/sntp_common.yaml
|
||||||
local_friendly_name: "${friendly_name}"
|
|
||||||
local_update_interval: "${update_interval}"
|
#### DIAGNOSTICS Sensors ####
|
||||||
|
diag_basic: !include common/include_basic_diag_sensors.yaml
|
||||||
|
diag_more: !include common/include_more_diag_sensors.yaml
|
||||||
|
diag_debug: !include common/include_debug_diag_sensors.yaml
|
||||||
|
#diag_resetcount: !include common/include_resetcount_diag_sensors.yaml
|
||||||
|
|
||||||
#########################################################################################
|
#########################################################################################
|
||||||
# ESPHome
|
# ESPHome
|
||||||
@@ -127,6 +143,20 @@ status_led:
|
|||||||
number: GPIO2
|
number: GPIO2
|
||||||
inverted: yes
|
inverted: yes
|
||||||
|
|
||||||
|
#########################################################################################
|
||||||
|
# GLOBAL VARIABLES
|
||||||
|
#
|
||||||
|
#########################################################################################
|
||||||
|
globals:
|
||||||
|
- id: remote1_state
|
||||||
|
type: bool
|
||||||
|
restore_value: no
|
||||||
|
initial_value: 'false'
|
||||||
|
- id: remote1_known
|
||||||
|
type: bool
|
||||||
|
restore_value: no
|
||||||
|
initial_value: 'false'
|
||||||
|
|
||||||
#########################################################################################
|
#########################################################################################
|
||||||
# BINARY SENSORS
|
# BINARY SENSORS
|
||||||
# https://esphome.io/components/binary_sensor/
|
# https://esphome.io/components/binary_sensor/
|
||||||
@@ -145,20 +175,76 @@ binary_sensor:
|
|||||||
pin:
|
pin:
|
||||||
number: GPIO05
|
number: GPIO05
|
||||||
mode: INPUT
|
mode: INPUT
|
||||||
inverted: True
|
inverted: true
|
||||||
name: "Button 2: ${switch_2_name}"
|
name: "Button 2: ${switch_2_name}"
|
||||||
on_press:
|
on_multi_click:
|
||||||
- switch.toggle: Relay_2
|
# SINGLE CLICK: quick tap -> toggle Relay_2
|
||||||
|
- timing:
|
||||||
|
- ON for at most 0.5s
|
||||||
|
- OFF for at least 0.5s
|
||||||
|
then:
|
||||||
|
- if:
|
||||||
|
condition:
|
||||||
|
lambda: 'return id(Relay_2).state;'
|
||||||
|
then:
|
||||||
|
- switch.turn_off: Relay_2
|
||||||
|
else:
|
||||||
|
- switch.turn_on: Relay_2
|
||||||
|
# HOLD: pressed for 2s or more -> toggle remote; default to ON if unknown
|
||||||
|
- timing:
|
||||||
|
- ON for at least 2s
|
||||||
|
then:
|
||||||
|
- if:
|
||||||
|
condition:
|
||||||
|
lambda: 'return id(remote1_known) && id(remote1_state);' # known AND currently ON
|
||||||
|
then:
|
||||||
|
- mqtt.publish:
|
||||||
|
topic: "${mqtt_remote1_command_topic}"
|
||||||
|
payload: "OFF"
|
||||||
|
retain: false
|
||||||
|
else:
|
||||||
|
- mqtt.publish:
|
||||||
|
topic: "${mqtt_remote1_command_topic}"
|
||||||
|
payload: "ON"
|
||||||
|
retain: false
|
||||||
|
|
||||||
# KS-811-2 is a double only
|
#########################################################################################
|
||||||
# - platform: gpio
|
# TEXT SENSORS
|
||||||
# pin:
|
#
|
||||||
# number: GPIO4
|
#########################################################################################
|
||||||
# mode: INPUT
|
text_sensor:
|
||||||
# inverted: True
|
- platform: mqtt_subscribe
|
||||||
# name: "Button 3: ${switch_3_name}"
|
id: remote1_status
|
||||||
# on_press:
|
internal: true
|
||||||
# - switch.toggle: Relay_3
|
topic: "${mqtt_remote1_status_topic}"
|
||||||
|
on_value:
|
||||||
|
then:
|
||||||
|
- lambda: |-
|
||||||
|
// copy, trim whitespace, uppercase
|
||||||
|
std::string s = x;
|
||||||
|
// trim leading/trailing whitespace
|
||||||
|
auto notspace = [](int ch){ return !std::isspace(ch); };
|
||||||
|
s.erase(s.begin(), std::find_if(s.begin(), s.end(), notspace));
|
||||||
|
s.erase(std::find_if(s.rbegin(), s.rend(), notspace).base(), s.end());
|
||||||
|
for (auto &c : s) c = (char)toupper(c);
|
||||||
|
|
||||||
|
bool recognized = false;
|
||||||
|
if (s == "ON" || s == "1" || s == "TRUE") {
|
||||||
|
id(remote1_state) = true;
|
||||||
|
id(remote1_known) = true;
|
||||||
|
recognized = true;
|
||||||
|
} else if (s == "OFF" || s == "0" || s == "FALSE") {
|
||||||
|
id(remote1_state) = false;
|
||||||
|
id(remote1_known) = true;
|
||||||
|
recognized = true;
|
||||||
|
} else {
|
||||||
|
// unknown payload (e.g. "ONLINE", "OFFLINE", JSON, etc.)
|
||||||
|
id(remote1_known) = false;
|
||||||
|
}
|
||||||
|
ESP_LOGI("remote1_status", "Parsed status='%s' -> known=%s state=%s",
|
||||||
|
s.c_str(),
|
||||||
|
id(remote1_known) ? "true" : "false",
|
||||||
|
id(remote1_state) ? "ON" : "OFF");
|
||||||
|
|
||||||
#########################################################################################
|
#########################################################################################
|
||||||
# SWITCH COMPONENT
|
# SWITCH COMPONENT
|
||||||
@@ -175,10 +261,5 @@ switch:
|
|||||||
pin: GPIO12
|
pin: GPIO12
|
||||||
id: Relay_2
|
id: Relay_2
|
||||||
|
|
||||||
# KS-811-2 is a double only
|
|
||||||
# - platform: gpio
|
|
||||||
# name: "Relay 3: ${switch_3_name}"
|
|
||||||
# pin: GPIO14
|
|
||||||
# id: Relay_3
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
##########################################################################################
|
##########################################################################################
|
||||||
##########################################################################################
|
##########################################################################################
|
||||||
# 4 Channel DMX Mains Dimmer
|
# 4 Channel DMX Mains Dimmer
|
||||||
# This is using an esp8366 (D1 Mini) sending DMX512 Serial to a pair of 2 channel
|
# This is using an esp8366 (D1 Mini) sending DMX512 Serial to a trio of 2 channel
|
||||||
# mains lighting contollers (1.2A/Channel)
|
# mains lighting contollers (1.2A/Channel)
|
||||||
#
|
#
|
||||||
# Contoller: https://www.aliexpress.com/item/32838426377.html
|
# Contoller: https://www.aliexpress.com/item/32838426377.html
|
||||||
# RS-485 TTL converter: https://www.aliexpress.com/item/1005005737922222.html
|
# RS-485 TTL converter: https://www.aliexpress.com/item/1005005737922222.html
|
||||||
#
|
#
|
||||||
|
# V1.1 2025-09-13 Extended to 3 modules and installed
|
||||||
# V1.0 2025-09-06 First Rev (only tested one module, 2 channels at this stage, and incandescent)
|
# V1.0 2025-09-06 First Rev (only tested one module, 2 channels at this stage, and incandescent)
|
||||||
##########################################################################################
|
##########################################################################################
|
||||||
#
|
#
|
||||||
@@ -56,25 +57,41 @@
|
|||||||
##########################################################################################
|
##########################################################################################
|
||||||
substitutions:
|
substitutions:
|
||||||
# Device Naming
|
# Device Naming
|
||||||
device_name: "esp-4chdmxdimmer1"
|
device_name: "esp-lounge6chdimmer"
|
||||||
friendly_name: "4 Channel DMX Dimmer 1"
|
friendly_name: "6 Channel Lounge Dimmer"
|
||||||
description_comment: "Lounge 4 Channel Mains Lighting Dimmer using DMX512 protocol"
|
description_comment: "Lounge 6 Channel Mains Lighting Dimmer (3 x 2 channel modules) controlled using DMX512 protocol with a D1 Mini Pro"
|
||||||
device_area: "Lounge" # Allows ESP device to be automatically linked to an 'Area' in Home Assistant.
|
device_area: "Lounge" # Allows ESP device to be automatically linked to an 'Area' in Home Assistant.
|
||||||
|
|
||||||
# Project Naming
|
# Project Naming
|
||||||
project_name: "Generic ESP8266.D1 Mini" # Project Details
|
project_name: "Generic ESP8266.D1 Mini" # Project Details
|
||||||
project_version: "v1.0" # Project V denotes release of yaml file, allowing checking of deployed vs latest version
|
project_version: "v1.1" # Project V denotes release of yaml file, allowing checking of deployed vs latest version
|
||||||
|
|
||||||
# Passwords & Secrets
|
# Passwords & Secrets
|
||||||
api_key: !secret esp-api_key # unfortunately you can't use substitutions inside secrets names
|
api_key: !secret esp-api_key # unfortunately you can't use substitutions inside secrets names
|
||||||
ota_pass: !secret esp-ota_pass # unfortunately you can't use substitutions inside secrets names
|
ota_pass: !secret esp-ota_pass # unfortunately you can't use substitutions inside secrets names
|
||||||
static_ip_address: !secret esp-4chdmxdimmer1_ip
|
static_ip_address: !secret esp-lounge6chdimmer_ip
|
||||||
|
|
||||||
# Device Settings
|
# Device Settings
|
||||||
#relay_icon: "mdi:lightbulb-group"
|
#relay_icon: "mdi:lightbulb-group"
|
||||||
log_level: "DEBUG" # Define logging level: NONE, ERROR, WARN, INFO, DEBUG (Default), VERBOSE, VERY_VERBOSE
|
log_level: "INFO" # Define logging level: NONE, ERROR, WARN, INFO, DEBUG (Default), VERBOSE, VERY_VERBOSE
|
||||||
update_interval: "60s" # update time for for general sensors etc
|
update_interval: "60s" # update time for for general sensors etc
|
||||||
|
|
||||||
|
output_1_name: "Lounge Downlights South"
|
||||||
|
output_2_name: "Lounge Rafter Buttons"
|
||||||
|
output_3_name: "Lounge Downlights Centre"
|
||||||
|
output_4_name: "Dining Table Light"
|
||||||
|
output_5_name: "Couch Spots Right"
|
||||||
|
output_6_name: "Couch Spots Left"
|
||||||
|
|
||||||
|
# Per-channel minimum output when ON (0.00f..1.00f). OFF remains 0%.
|
||||||
|
# Adjust per channel to avoid flicker at very low levels.
|
||||||
|
min_output_1: "0.20f"
|
||||||
|
min_output_2: "0.20f"
|
||||||
|
min_output_3: "0.20f"
|
||||||
|
min_output_4: "0.20f"
|
||||||
|
min_output_5: "0.20f"
|
||||||
|
min_output_6: "0.20f"
|
||||||
|
|
||||||
#########################################################################################
|
#########################################################################################
|
||||||
# PACKAGES: Included Common Packages
|
# PACKAGES: Included Common Packages
|
||||||
# https://esphome.io/components/packages.html
|
# https://esphome.io/components/packages.html
|
||||||
@@ -82,29 +99,37 @@ substitutions:
|
|||||||
# Comment some of these out where not needed, eg MQTT, Web portal, SNTP, General Sensors
|
# Comment some of these out where not needed, eg MQTT, Web portal, SNTP, General Sensors
|
||||||
##########################################################################################
|
##########################################################################################
|
||||||
packages:
|
packages:
|
||||||
|
#### WIFI, Network (Static/DHCP/IPV6 etc), Fallback AP, Safemode ####
|
||||||
common_wifi: !include
|
common_wifi: !include
|
||||||
file: common/network_common.yaml
|
file: common/network_common.yaml
|
||||||
vars:
|
vars:
|
||||||
local_device_name: "${device_name}"
|
local_device_name: "${device_name}"
|
||||||
local_static_ip_address: "${static_ip_address}"
|
local_static_ip_address: "${static_ip_address}"
|
||||||
local_ota_pass: "${ota_pass}"
|
local_ota_pass: "${ota_pass}"
|
||||||
|
|
||||||
|
#### HOME ASSISTANT API (choose encryption or no encryption options) ####
|
||||||
common_api: !include
|
common_api: !include
|
||||||
file: common/api_common.yaml
|
file: common/api_common.yaml
|
||||||
|
#file: common/api_common_noencryption.yaml
|
||||||
vars:
|
vars:
|
||||||
local_api_key: "${api_key}"
|
local_api_key: "${api_key}"
|
||||||
#common_webportal: !include
|
|
||||||
# file: common/webportal_common.yaml
|
#### MQTT ####
|
||||||
common_mqtt: !include
|
common_mqtt: !include
|
||||||
file: common/mqtt_common.yaml
|
file: common/mqtt_common.yaml
|
||||||
vars:
|
vars:
|
||||||
local_device_name: "${device_name}"
|
local_device_name: "${device_name}"
|
||||||
common_sntp: !include
|
|
||||||
file: common/sntp_common.yaml
|
#### WEB PORTAL ####
|
||||||
common_general_sensors: !include
|
#common_webportal: !include common/webportal_common.yaml
|
||||||
file: common/sensors_common.yaml
|
#### SNTP (Only use if you want/need accurate timeclocks) ####
|
||||||
vars:
|
common_sntp: !include common/sntp_common.yaml
|
||||||
local_friendly_name: "${friendly_name}"
|
|
||||||
local_update_interval: "${update_interval}"
|
#### DIAGNOSTICS Sensors ####
|
||||||
|
diag_basic: !include common/include_basic_diag_sensors.yaml
|
||||||
|
diag_more: !include common/include_more_diag_sensors.yaml
|
||||||
|
diag_debug: !include common/include_debug_diag_sensors.yaml
|
||||||
|
#diag_resetcount: !include common/include_resetcount_diag_sensors.yaml
|
||||||
|
|
||||||
#########################################################################################
|
#########################################################################################
|
||||||
# ESPHome
|
# ESPHome
|
||||||
@@ -126,13 +151,15 @@ esphome:
|
|||||||
id(dmx_ch2_sensor).publish_state(0);
|
id(dmx_ch2_sensor).publish_state(0);
|
||||||
id(dmx_ch3_sensor).publish_state(0);
|
id(dmx_ch3_sensor).publish_state(0);
|
||||||
id(dmx_ch4_sensor).publish_state(0);
|
id(dmx_ch4_sensor).publish_state(0);
|
||||||
|
id(dmx_ch5_sensor).publish_state(0);
|
||||||
|
id(dmx_ch6_sensor).publish_state(0);
|
||||||
|
|
||||||
#########################################################################################
|
#########################################################################################
|
||||||
# ESP Platform and Framework
|
# ESP Platform and Framework
|
||||||
# https://esphome.io/components/esp32.html
|
# https://esphome.io/components/esp32.html
|
||||||
#########################################################################################
|
#########################################################################################
|
||||||
esp8266:
|
esp8266:
|
||||||
board: esp01_1m
|
board: d1_mini_pro
|
||||||
#early_pin_init: False # Initialise pins early to known values. Recommended false where switches are involved. Defaults to True.
|
#early_pin_init: False # Initialise pins early to known values. Recommended false where switches are involved. Defaults to True.
|
||||||
board_flash_mode: dout # Default is dout
|
board_flash_mode: dout # Default is dout
|
||||||
restore_from_flash: true # restore some values on reboot
|
restore_from_flash: true # restore some values on reboot
|
||||||
@@ -140,8 +167,8 @@ esp8266:
|
|||||||
mdns:
|
mdns:
|
||||||
disabled: false # Disabling will make the build file smaller (and it is still available via static IP)
|
disabled: false # Disabling will make the build file smaller (and it is still available via static IP)
|
||||||
|
|
||||||
preferences:
|
#preferences:
|
||||||
flash_write_interval: 0s
|
# flash_write_interval: 0s
|
||||||
|
|
||||||
#########################################################################################
|
#########################################################################################
|
||||||
# ESPHome Logging Enable
|
# ESPHome Logging Enable
|
||||||
@@ -183,33 +210,47 @@ uart:
|
|||||||
##########################################################################################
|
##########################################################################################
|
||||||
light:
|
light:
|
||||||
- platform: monochromatic
|
- platform: monochromatic
|
||||||
name: "Test Lights Chan 1"
|
name: ${output_1_name}
|
||||||
output: dmx_1
|
output: dmx_1
|
||||||
id: light_test_1
|
id: light_test_1
|
||||||
default_transition_length: 5s
|
default_transition_length: 10s
|
||||||
gamma_correct: 1
|
gamma_correct: 2.2
|
||||||
restore_mode: ALWAYS_ON
|
restore_mode: ALWAYS_ON
|
||||||
- platform: monochromatic
|
- platform: monochromatic
|
||||||
name: "Test Lights Chan 2"
|
name: ${output_2_name}
|
||||||
output: dmx_2
|
output: dmx_2
|
||||||
id: light_test_2
|
id: light_test_2
|
||||||
default_transition_length: 5s
|
default_transition_length: 10s
|
||||||
gamma_correct: 1
|
gamma_correct: 2.2
|
||||||
restore_mode: ALWAYS_ON
|
restore_mode: ALWAYS_ON
|
||||||
- platform: monochromatic
|
- platform: monochromatic
|
||||||
name: "Test Lights Chan 3"
|
name: ${output_3_name}
|
||||||
output: dmx_3
|
output: dmx_3
|
||||||
id: light_test_3
|
id: light_test_3
|
||||||
default_transition_length: 5s
|
default_transition_length: 10s
|
||||||
|
gamma_correct: 2.2
|
||||||
|
restore_mode: ALWAYS_ON
|
||||||
|
- platform: monochromatic
|
||||||
|
name: ${output_4_name}
|
||||||
|
output: dmx_4
|
||||||
|
id: light_test_4
|
||||||
|
default_transition_length: 10s
|
||||||
gamma_correct: 1
|
gamma_correct: 1
|
||||||
restore_mode: ALWAYS_ON
|
restore_mode: ALWAYS_ON
|
||||||
- platform: monochromatic
|
- platform: monochromatic
|
||||||
name: "Test Lights Chan 4"
|
name: ${output_5_name}
|
||||||
output: dmx_4
|
output: dmx_5
|
||||||
id: light_test_4
|
id: light_test_5
|
||||||
default_transition_length: 5s
|
default_transition_length: 10s
|
||||||
gamma_correct: 1
|
gamma_correct: 1
|
||||||
restore_mode: ALWAYS_ON
|
restore_mode: RESTORE_DEFAULT_OFF
|
||||||
|
- platform: monochromatic
|
||||||
|
name: ${output_6_name}
|
||||||
|
output: dmx_6
|
||||||
|
id: light_test_6
|
||||||
|
default_transition_length: 10s
|
||||||
|
gamma_correct: 1
|
||||||
|
restore_mode: RESTORE_DEFAULT_OFF
|
||||||
|
|
||||||
##########################################################################################
|
##########################################################################################
|
||||||
# SENSOR COMPONENT
|
# SENSOR COMPONENT
|
||||||
@@ -219,12 +260,14 @@ sensor:
|
|||||||
# Expose each DMX channel's current output level as 0..255 integers (post-gamma)
|
# Expose each DMX channel's current output level as 0..255 integers (post-gamma)
|
||||||
- platform: template
|
- platform: template
|
||||||
id: dmx_ch1_sensor
|
id: dmx_ch1_sensor
|
||||||
name: "DMX Ch 1 Output (0-255)"
|
name: ${output_1_name} Output
|
||||||
icon: mdi:counter
|
icon: mdi:counter
|
||||||
#entity_category: diagnostic
|
#entity_category: diagnostic
|
||||||
accuracy_decimals: 0
|
accuracy_decimals: 0
|
||||||
update_interval: never
|
update_interval: never
|
||||||
filters:
|
filters:
|
||||||
|
- throttle: 200ms
|
||||||
|
- delta: 1
|
||||||
- round: 0
|
- round: 0
|
||||||
- clamp:
|
- clamp:
|
||||||
min_value: 0
|
min_value: 0
|
||||||
@@ -232,12 +275,14 @@ sensor:
|
|||||||
|
|
||||||
- platform: template
|
- platform: template
|
||||||
id: dmx_ch2_sensor
|
id: dmx_ch2_sensor
|
||||||
name: "DMX Ch 2 Output (0-255)"
|
name: ${output_2_name} Output
|
||||||
icon: mdi:counter
|
icon: mdi:counter
|
||||||
#entity_category: diagnostic
|
#entity_category: diagnostic
|
||||||
accuracy_decimals: 0
|
accuracy_decimals: 0
|
||||||
update_interval: never
|
update_interval: never
|
||||||
filters:
|
filters:
|
||||||
|
- throttle: 200ms
|
||||||
|
- delta: 1
|
||||||
- round: 0
|
- round: 0
|
||||||
- clamp:
|
- clamp:
|
||||||
min_value: 0
|
min_value: 0
|
||||||
@@ -245,12 +290,14 @@ sensor:
|
|||||||
|
|
||||||
- platform: template
|
- platform: template
|
||||||
id: dmx_ch3_sensor
|
id: dmx_ch3_sensor
|
||||||
name: "DMX Ch 3 Output (0-255)"
|
name: ${output_3_name} Output
|
||||||
icon: mdi:counter
|
icon: mdi:counter
|
||||||
#entity_category: diagnostic
|
#entity_category: diagnostic
|
||||||
accuracy_decimals: 0
|
accuracy_decimals: 0
|
||||||
update_interval: never
|
update_interval: never
|
||||||
filters:
|
filters:
|
||||||
|
- throttle: 200ms
|
||||||
|
- delta: 1
|
||||||
- round: 0
|
- round: 0
|
||||||
- clamp:
|
- clamp:
|
||||||
min_value: 0
|
min_value: 0
|
||||||
@@ -258,12 +305,44 @@ sensor:
|
|||||||
|
|
||||||
- platform: template
|
- platform: template
|
||||||
id: dmx_ch4_sensor
|
id: dmx_ch4_sensor
|
||||||
name: "DMX Ch 4 Output (0-255)"
|
name: ${output_4_name} Output
|
||||||
icon: mdi:counter
|
icon: mdi:counter
|
||||||
#entity_category: diagnostic
|
#entity_category: diagnostic
|
||||||
accuracy_decimals: 0
|
accuracy_decimals: 0
|
||||||
update_interval: never
|
update_interval: never
|
||||||
filters:
|
filters:
|
||||||
|
- throttle: 200ms
|
||||||
|
- delta: 1
|
||||||
|
- round: 0
|
||||||
|
- clamp:
|
||||||
|
min_value: 0
|
||||||
|
max_value: 255
|
||||||
|
|
||||||
|
- platform: template
|
||||||
|
id: dmx_ch5_sensor
|
||||||
|
name: ${output_5_name} Output
|
||||||
|
icon: mdi:counter
|
||||||
|
#entity_category: diagnostic
|
||||||
|
accuracy_decimals: 0
|
||||||
|
update_interval: never
|
||||||
|
filters:
|
||||||
|
- throttle: 200ms
|
||||||
|
- delta: 1
|
||||||
|
- round: 0
|
||||||
|
- clamp:
|
||||||
|
min_value: 0
|
||||||
|
max_value: 255
|
||||||
|
|
||||||
|
- platform: template
|
||||||
|
id: dmx_ch6_sensor
|
||||||
|
name: ${output_6_name} Output
|
||||||
|
icon: mdi:counter
|
||||||
|
#entity_category: diagnostic
|
||||||
|
accuracy_decimals: 0
|
||||||
|
update_interval: never
|
||||||
|
filters:
|
||||||
|
- throttle: 200ms
|
||||||
|
- delta: 1
|
||||||
- round: 0
|
- round: 0
|
||||||
- clamp:
|
- clamp:
|
||||||
min_value: 0
|
min_value: 0
|
||||||
@@ -284,7 +363,7 @@ dmx512:
|
|||||||
number: GPIO2
|
number: GPIO2
|
||||||
allow_other_uses: true # For ESPHome >= 2023.12.0
|
allow_other_uses: true # For ESPHome >= 2023.12.0
|
||||||
uart_num: 1
|
uart_num: 1
|
||||||
#periodic_update: true # optional. Some dimmers may turn off without regular updates.
|
periodic_update: true # optional. Some dimmers may turn off without regular updates.
|
||||||
#force_full_frames: false # optional. If true the 513 byte is always sent
|
#force_full_frames: false # optional. If true the 513 byte is always sent
|
||||||
#custom_break_len: 92 # optional. Break Length. Default is 92uS
|
#custom_break_len: 92 # optional. Break Length. Default is 92uS
|
||||||
#custom_mab_len: 12 # optional. Mark after Break Length. Default is 12mS
|
#custom_mab_len: 12 # optional. Mark after Break Length. Default is 12mS
|
||||||
@@ -312,6 +391,14 @@ output:
|
|||||||
channel: 4 # The ID we set the device to
|
channel: 4 # The ID we set the device to
|
||||||
universe: dmx # The ID of the DMX512 Component
|
universe: dmx # The ID of the DMX512 Component
|
||||||
id: dmx_4_raw
|
id: dmx_4_raw
|
||||||
|
- platform: dmx512
|
||||||
|
channel: 5 # The ID we set the device to
|
||||||
|
universe: dmx # The ID of the DMX512 Component
|
||||||
|
id: dmx_5_raw
|
||||||
|
- platform: dmx512
|
||||||
|
channel: 6 # The ID we set the device to
|
||||||
|
universe: dmx # The ID of the DMX512 Component
|
||||||
|
id: dmx_6_raw
|
||||||
|
|
||||||
# Wrapped outputs: publish exact 0..255 to sensors, then forward float to raw DMX
|
# Wrapped outputs: publish exact 0..255 to sensors, then forward float to raw DMX
|
||||||
- platform: template
|
- platform: template
|
||||||
@@ -319,31 +406,77 @@ output:
|
|||||||
type: float
|
type: float
|
||||||
write_action:
|
write_action:
|
||||||
- lambda: |-
|
- lambda: |-
|
||||||
id(dmx_ch1_sensor).publish_state(state * 255.0f);
|
const float MIN = ${min_output_1};
|
||||||
id(dmx_1_raw).set_level(state);
|
float scaled = state;
|
||||||
|
if (state > 0.0f) {
|
||||||
|
// Remap 0..1 -> MIN..1.0 (keep OFF at 0)
|
||||||
|
scaled = state * (1.0f - MIN) + MIN;
|
||||||
|
}
|
||||||
|
id(dmx_ch1_sensor).publish_state(scaled * 255.0f);
|
||||||
|
id(dmx_1_raw).set_level(scaled);
|
||||||
|
|
||||||
- platform: template
|
- platform: template
|
||||||
id: dmx_2
|
id: dmx_2
|
||||||
type: float
|
type: float
|
||||||
write_action:
|
write_action:
|
||||||
- lambda: |-
|
- lambda: |-
|
||||||
id(dmx_ch2_sensor).publish_state(state * 255.0f);
|
const float MIN = ${min_output_2};
|
||||||
id(dmx_2_raw).set_level(state);
|
float scaled = state;
|
||||||
|
if (state > 0.0f) {
|
||||||
|
scaled = state * (1.0f - MIN) + MIN;
|
||||||
|
}
|
||||||
|
id(dmx_ch2_sensor).publish_state(scaled * 255.0f);
|
||||||
|
id(dmx_2_raw).set_level(scaled);
|
||||||
|
|
||||||
- platform: template
|
- platform: template
|
||||||
id: dmx_3
|
id: dmx_3
|
||||||
type: float
|
type: float
|
||||||
write_action:
|
write_action:
|
||||||
- lambda: |-
|
- lambda: |-
|
||||||
id(dmx_ch3_sensor).publish_state(state * 255.0f);
|
const float MIN = ${min_output_3};
|
||||||
id(dmx_3_raw).set_level(state);
|
float scaled = state;
|
||||||
|
if (state > 0.0f) {
|
||||||
|
scaled = state * (1.0f - MIN) + MIN;
|
||||||
|
}
|
||||||
|
id(dmx_ch3_sensor).publish_state(scaled * 255.0f);
|
||||||
|
id(dmx_3_raw).set_level(scaled);
|
||||||
|
|
||||||
- platform: template
|
- platform: template
|
||||||
id: dmx_4
|
id: dmx_4
|
||||||
type: float
|
type: float
|
||||||
write_action:
|
write_action:
|
||||||
- lambda: |-
|
- lambda: |-
|
||||||
id(dmx_ch4_sensor).publish_state(state * 255.0f);
|
const float MIN = ${min_output_4};
|
||||||
id(dmx_4_raw).set_level(state);
|
float scaled = state;
|
||||||
|
if (state > 0.0f) {
|
||||||
|
scaled = state * (1.0f - MIN) + MIN;
|
||||||
|
}
|
||||||
|
id(dmx_ch4_sensor).publish_state(scaled * 255.0f);
|
||||||
|
id(dmx_4_raw).set_level(scaled);
|
||||||
|
|
||||||
|
- platform: template
|
||||||
|
id: dmx_5
|
||||||
|
type: float
|
||||||
|
write_action:
|
||||||
|
- lambda: |-
|
||||||
|
const float MIN = ${min_output_5};
|
||||||
|
float scaled = state;
|
||||||
|
if (state > 0.0f) {
|
||||||
|
scaled = state * (1.0f - MIN) + MIN;
|
||||||
|
}
|
||||||
|
id(dmx_ch5_sensor).publish_state(scaled * 255.0f);
|
||||||
|
id(dmx_5_raw).set_level(scaled);
|
||||||
|
|
||||||
|
- platform: template
|
||||||
|
id: dmx_6
|
||||||
|
type: float
|
||||||
|
write_action:
|
||||||
|
- lambda: |-
|
||||||
|
const float MIN = ${min_output_6};
|
||||||
|
float scaled = state;
|
||||||
|
if (state > 0.0f) {
|
||||||
|
scaled = state * (1.0f - MIN) + MIN;
|
||||||
|
}
|
||||||
|
id(dmx_ch6_sensor).publish_state(scaled * 255.0f);
|
||||||
|
id(dmx_6_raw).set_level(scaled);
|
||||||
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,102 +1,137 @@
|
|||||||
##########################################################################################
|
##########################################################################################
|
||||||
##########################################################################################
|
##########################################################################################
|
||||||
# Title: Lounge cabinet LEDS (Lounge)
|
# Title: LOUNGE CABINET LEDS
|
||||||
# Hardware: Magichome inline single channel dimmer (ESP8266)
|
# Repo: https://home.fox.co.nz/gitea/zorruno/zorruno-homeassistant/src/branch/master/esphome/esp-loungecabinetleds.yaml
|
||||||
#
|
#
|
||||||
# Repo:
|
# v2.0 - 2025-09-15 A bunch of fixes, this is now the master for other similar mosfet controllers of mine
|
||||||
#
|
# v1.5 - 2025-09-11 First setup (copied from another template)
|
||||||
# v1.0 - 2025-08-17 First setup (and replacement of Tasmota)
|
|
||||||
#
|
#
|
||||||
# ------------------------------------------
|
# ------------------------------------------
|
||||||
# DEVICE GPIO (Magichome ESP8266)
|
# OPERATION (as of v2.0)
|
||||||
# ------------------------------------------
|
# ------------------------------------------
|
||||||
# GPIO02 Blue LED (used for ESPHome status) - I think... can't see the board
|
# 1. General-purpose LED controller.
|
||||||
# GPIO12 MOSFET output (0 V when switched)
|
# 2. Designed for the Sinilink XY-WFMS board with a MOSFET output (claimed 5A, 5-36 V DC), but will suit similar mosfet boards.
|
||||||
#
|
# 3. (In SUBSTITUTIONS) setting for MAX % output to extend LED life (ie can ensure it is only ever 95%)
|
||||||
# ------------------------------------------
|
# 4. (In SUBSTITUTIONS) Minimum output setting; switches fully OFF at/below the minimum to avoid low-PWM flicker.
|
||||||
# OPERATION
|
# 5. (In SUBSTITUTIONS) PWM frequency is set to 1kHz by default. You can increase it, but higher values caused
|
||||||
# ------------------------------------------
|
# resets on Sinilink device. On ESP32 you can run much higher (~40 kHz).
|
||||||
# - Startup/power restore action
|
# 6. Min/Max output settings are not exposed in Home Assistant/MQTT by default, but could be.
|
||||||
|
# With a 1 MB flash, space is tight and only minimal optimisation has been done so far.
|
||||||
|
# 7. (PACKAGES) include common items: network settings, diagnostic entities, MQTT, and SNTP (optional).
|
||||||
|
# 8. Default behaviours, for recovery from lost power or reset:
|
||||||
|
# - "Fade up to full": fade from floor to max on boot.
|
||||||
|
# - "Restore Brightness": fade from floor to the last non-zero brightness on boot (should be no on/off blip/flash).
|
||||||
|
# - "Remain Off": stays off on boot.
|
||||||
|
# 9. The green LED flashes while fading (different patterns for up/down). The red LED follows the
|
||||||
|
# output (it shares the MOSFET GPIO). (Obviously this depends on hardware)
|
||||||
|
# 10. Fade timing scales with the configured values (proportionally when starting mid-brightness).
|
||||||
|
# 11. Exposed in Home Assistant/MQTT:
|
||||||
|
# - Startup action (On,Off,Restore)
|
||||||
# - Fade Up / Fade Down / Fade Stop buttons
|
# - Fade Up / Fade Down / Fade Stop buttons
|
||||||
# - Fade Up/Down switch
|
# - Fade Up/Down toggle switch (keeps fading up to full, or down to off unless stopped)
|
||||||
# - Normal On/Off switch (quick ramp up/down)
|
# - Normal On/Off switch (quick ramp up/down, up to the level of previous brightness)
|
||||||
# - Fade up/down times (0-60s)
|
# - Fade up/down times (0-60s)
|
||||||
# - Output % (pre-gamma) and PWM % (post-gamma)
|
# - Output % (pre-gamma) and PWM % (post-gamma)
|
||||||
# - Output Set (1-100, respects min/max)
|
# - Output Set (1-100, respects min/max). This also changes with other output so reflects value.
|
||||||
# - Device diagnostics (from the included package)
|
# - Many device diagnostics (from the included 'diagnostics' package)
|
||||||
# - Maximum 'on' time before automatic fade-down (1-48 h, 0 = no limit)
|
# - Maximum 'on' time before automatic fade-down (1-48 h, 0 = no limit)
|
||||||
|
###########################################################################################
|
||||||
|
# Hardware: Sinilink MOSFET Board XY-WFMS (ESP8266) - sometimes listed as "XY-VFMS"
|
||||||
|
# https://devices.esphome.io/devices/Sinilink-XY-WFMS
|
||||||
|
# ------------------------------------------
|
||||||
|
# DEVICE GPIO (Sinilink XY-WFMS)
|
||||||
|
# ------------------------------------------
|
||||||
|
# GPIO02 Blue LED, active-low (used for ESPHome status)
|
||||||
|
# GPIO04 MOSFET output (0V when switched) and Red LED
|
||||||
|
# GPIO12 Toggle button
|
||||||
|
# GPIO13 Green LED (used to display fading status)
|
||||||
|
#
|
||||||
|
# 3D-printed case for Sinilink XY-WFMS
|
||||||
|
# https://cults3d.com/en/3d-model/tool/snapfit-enclosure-for-esp8266-sinilink-xy-wfms-5v-36v-mosfet-switch-module
|
||||||
#
|
#
|
||||||
##########################################################################################
|
##########################################################################################
|
||||||
##########################################################################################
|
##########################################################################################
|
||||||
|
|
||||||
|
|
||||||
##########################################################################################
|
##########################################################################################
|
||||||
# SPECIFIC DEVICE VARIABLE SUBSTITUTIONS
|
# SPECIFIC DEVICE VARIABLE SUBSTITUTIONS
|
||||||
# If NOT using a secrets file, just replace these with the passwords etc (in quotes)
|
# If NOT using a secrets file, just replace these with the passwords etc (in quotes)
|
||||||
##########################################################################################
|
##########################################################################################
|
||||||
substitutions:
|
substitutions:
|
||||||
# Device Naming
|
# Device Naming
|
||||||
device_name: "esp-loungecabinetleds"
|
device_name: "esp-loungecabinetleds2" # yaml file should be device_name.yaml
|
||||||
friendly_name: "Lounge Cabinet LEDs"
|
friendly_name: "Lounge Cabinet LEDs"
|
||||||
description_comment: "LED Strip inside the display cabinet upstairs lounge :: Magichome ESP8266"
|
description_comment: "LED Strip inside the display cabinet upstairs lounge :: Sinilink XY-WFMS"
|
||||||
device_area: "Lounge" # Allows the ESP device to be automatically linked to an 'Area' in Home Assistant.
|
device_area: "Lounge" # Allows the ESP device to be automatically linked to an 'Area' in Home Assistant.
|
||||||
|
|
||||||
# Project Naming
|
# Project Naming
|
||||||
project_name: "Magichome ESP8266" # Project details
|
project_name: "Sinilink.XY-WFMS" # Project details. Manufacturer before the dot, device after the dot.
|
||||||
project_version: "v1.0" # Project version denotes release of the YAML file, allowing checking of deployed vs latest version
|
project_version: "v2.0" # Project version denotes release of the YAML file, allowing checking of deployed vs latest version
|
||||||
|
|
||||||
# Passwords & Secrets
|
# Passwords & Secrets (Unfortunately, you can't use substitutions inside secret names)
|
||||||
api_key: !secret esp-api_key
|
api_key: !secret esp-api_key # If using HA API
|
||||||
ota_pass: !secret esp-ota_pass
|
ota_pass: !secret esp-ota_pass # If passwording OTA updates
|
||||||
static_ip_address: !secret esp-loungecabinetleds_ip # Unfortunately, you can't use substitutions inside secret names
|
static_ip_address: !secret esp-loungecabinetleds2_ip # CHANGE THIS
|
||||||
|
|
||||||
|
# MQTT LOCAL Controls (This is just my local method of controlling via MQTT, I also use the built in method)
|
||||||
|
mqtt_local_device_name: "lounge-cabinetleds"
|
||||||
mqtt_local_command_main_topic: !secret mqtt_local_command_main_topic
|
mqtt_local_command_main_topic: !secret mqtt_local_command_main_topic
|
||||||
mqtt_local_status_main_topic: !secret mqtt_local_status_main_topic
|
mqtt_local_status_main_topic: !secret mqtt_local_status_main_topic
|
||||||
|
|
||||||
# MQTT LOCAL Controls
|
|
||||||
mqtt_local_device_name: "lounge-cabinetleds"
|
|
||||||
mqtt_local_command_topic: "${mqtt_local_command_main_topic}/${mqtt_local_device_name}" # Topic we will use to command this locally without HA
|
mqtt_local_command_topic: "${mqtt_local_command_main_topic}/${mqtt_local_device_name}" # Topic we will use to command this locally without HA
|
||||||
mqtt_local_status_topic: "${mqtt_local_status_main_topic}/${mqtt_local_device_name}" # Topic we will use to view status locally without HA
|
mqtt_local_status_topic: "${mqtt_local_status_main_topic}/${mqtt_local_device_name}" # Topic we will use to view status locally without HA
|
||||||
mqtt_local_device_command_ON: "ON"
|
mqtt_local_device_command_ON: "ON"
|
||||||
mqtt_local_device_command_OFF: "OFF"
|
mqtt_local_device_command_OFF: "OFF"
|
||||||
|
|
||||||
# Device Specific Settings
|
# Device Specific Settings
|
||||||
log_level: "NONE" # Define logging level: NONE, ERROR, WARN, INFO, DEBUG (default), VERBOSE, VERY_VERBOSE
|
log_level: "NONE" # Define logging level: NONE, ERROR, WARN, INFO, DEBUG (default), VERBOSE, VERY_VERBOSE
|
||||||
update_interval: "20s" # Update time for general sensors, etc.
|
update_interval: "20s" # Update time for general sensors, etc.
|
||||||
led_gamma: "1.2" # Gamma from 1.2-3 is sensible to normalise the LED fading vs PWM
|
led_gamma: "1.2" # Gamma from 1.2-3 is sensible to normalise the LED fading vs PWM
|
||||||
minimum_led_output: "1" # % If at this value or below, we'll switch it completely off
|
minimum_led_output: "1" # % If at this value or below, we'll switch it completely off
|
||||||
maximum_led_output: "90" # % Maximum output; it is sometimes nice to limit the output for longevity or aesthetics
|
maximum_led_output: "90" # % Maximum output; it is sometimes nice to limit the output for longevity or aesthetics
|
||||||
max_on_default_hours: "12" # The maximum time the LEDs will be on, in case they get left on. 0 = no automatic turn-off
|
max_on_default_hours: "24" # The maximum time the LEDs will be on, in case they get left on. 0 = no automatic turn-off
|
||||||
|
pwm_frequency: "1000hz" # PWM output Frequency. High enough to avoid audible/visible artifacts
|
||||||
|
|
||||||
|
# Device Specific GPIO (so we can easily update for other devices)
|
||||||
|
device_status_led: GPIO02
|
||||||
|
device_mosfet_out: GPIO04
|
||||||
|
device_usr_button: GPIO12
|
||||||
|
device_fading_led: GPIO13
|
||||||
|
|
||||||
##########################################################################################
|
##########################################################################################
|
||||||
# PACKAGES: Included Common Packages
|
# PACKAGES: Included Common Packages
|
||||||
# https://esphome.io/components/packages.html
|
# https://esphome.io/components/packages.html
|
||||||
##########################################################################################
|
##########################################################################################
|
||||||
packages:
|
packages:
|
||||||
|
#### WIFI, Network (Static/DHCP/IPV6 etc), Fallback AP, Safemode ####
|
||||||
common_wifi: !include
|
common_wifi: !include
|
||||||
file: common/network_common.yaml
|
file: common/network_common.yaml
|
||||||
vars:
|
vars:
|
||||||
local_device_name: "${device_name}"
|
local_device_name: "${device_name}"
|
||||||
local_static_ip_address: "${static_ip_address}"
|
local_static_ip_address: "${static_ip_address}"
|
||||||
local_ota_pass: "${ota_pass}"
|
local_ota_pass: "${ota_pass}"
|
||||||
|
|
||||||
|
#### HOME ASSISTANT API (choose encryption or no encryption options) ####
|
||||||
common_api: !include
|
common_api: !include
|
||||||
file: common/api_common.yaml
|
file: common/api_common.yaml
|
||||||
#file: common/api_common_noencryption.yaml
|
#file: common/api_common_noencryption.yaml
|
||||||
vars:
|
vars:
|
||||||
local_api_key: "${api_key}"
|
local_api_key: "${api_key}"
|
||||||
#common_webportal: !include
|
|
||||||
# file: common/webportal_common.yaml
|
#### MQTT ####
|
||||||
common_mqtt: !include
|
common_mqtt: !include
|
||||||
file: common/mqtt_common.yaml
|
file: common/mqtt_common.yaml
|
||||||
vars:
|
vars:
|
||||||
local_device_name: "${device_name}"
|
local_device_name: "${device_name}"
|
||||||
#common_sntp: !include
|
|
||||||
# file: common/sntp_common.yaml
|
#### WEB PORTAL ####
|
||||||
common_general_sensors: !include
|
#common_webportal: !include common/webportal_common.yaml
|
||||||
file: common/sensors_common.yaml
|
#### SNTP (Only use if you want/need accurate timeclocks) ####
|
||||||
#file: common/sensors_common_lite.yaml
|
#common_sntp: !include common/sntp_common.yaml
|
||||||
vars:
|
|
||||||
local_friendly_name: "${friendly_name}"
|
#### DIAGNOSTICS Sensors ####
|
||||||
local_update_interval: "${update_interval}"
|
diag_basic: !include common/include_basic_diag_sensors.yaml
|
||||||
|
diag_more: !include common/include_more_diag_sensors.yaml
|
||||||
|
diag_debug: !include common/include_debug_diag_sensors.yaml
|
||||||
|
diag_resetcount: !include common/include_resetcount_diag_sensors.yaml
|
||||||
|
|
||||||
##########################################################################################
|
##########################################################################################
|
||||||
# ESPHome CORE CONFIGURATION
|
# ESPHome CORE CONFIGURATION
|
||||||
@@ -108,7 +143,8 @@ esphome:
|
|||||||
comment: "${description_comment}"
|
comment: "${description_comment}"
|
||||||
area: "${device_area}"
|
area: "${device_area}"
|
||||||
on_boot:
|
on_boot:
|
||||||
priority: 600
|
# Run later so everything is initialised before we touch states.
|
||||||
|
priority: 200
|
||||||
then:
|
then:
|
||||||
# Keep the HA dropdown in sync with the stored mode
|
# Keep the HA dropdown in sync with the stored mode
|
||||||
- lambda: |-
|
- lambda: |-
|
||||||
@@ -126,31 +162,12 @@ esphome:
|
|||||||
- lambda: 'id(ramp_switch_target_on) = true;'
|
- lambda: 'id(ramp_switch_target_on) = true;'
|
||||||
- script.execute: ramp_on_script
|
- script.execute: ramp_on_script
|
||||||
|
|
||||||
# Mode 1: Restore Brightness using a scripted ramp 0 -> last non-zero brightness
|
# Mode 1: Restore Brightness (deferred to avoid races)
|
||||||
- if:
|
- if:
|
||||||
condition:
|
condition:
|
||||||
lambda: 'return id(restart_mode) == 1;'
|
lambda: 'return id(restart_mode) == 1;'
|
||||||
then:
|
then:
|
||||||
- lambda: |-
|
- script.execute: deferred_restore_brightness
|
||||||
float target = id(last_nonzero_brightness_pct);
|
|
||||||
if (target < 0.0f) target = 0.0f;
|
|
||||||
if (target > 100.0f) target = 100.0f;
|
|
||||||
const float minp = (float) id(min_brightness_pct);
|
|
||||||
const float maxp = (float) id(max_brightness_pct);
|
|
||||||
if (target > 0.0f && target < minp) target = minp;
|
|
||||||
if (target > maxp) target = maxp;
|
|
||||||
id(restore_target_pct) = target; // set scripted target
|
|
||||||
id(ramp_switch_target_on) = (target > 0.0f);
|
|
||||||
- if:
|
|
||||||
condition:
|
|
||||||
lambda: 'return id(restore_target_pct) > 0.0f;'
|
|
||||||
then:
|
|
||||||
- script.stop: ramp_off_script
|
|
||||||
- script.execute: ramp_to_target_script
|
|
||||||
else:
|
|
||||||
- light.turn_off:
|
|
||||||
id: mosfet_leds
|
|
||||||
transition_length: 0s
|
|
||||||
|
|
||||||
# Mode 2: Remain Off (no blip; stays off)
|
# Mode 2: Remain Off (no blip; stays off)
|
||||||
- if:
|
- if:
|
||||||
@@ -164,15 +181,19 @@ esphome:
|
|||||||
id: mosfet_leds
|
id: mosfet_leds
|
||||||
transition_length: 0s
|
transition_length: 0s
|
||||||
|
|
||||||
platformio_options:
|
# Boot complete: allow OFF handlers again
|
||||||
build_unflags:
|
- lambda: 'id(booting) = false;'
|
||||||
- -flto
|
|
||||||
build_flags:
|
# Only if you want to play with build flags...
|
||||||
- -fno-lto
|
# platformio_options:
|
||||||
- -Wl,--gc-sections
|
# build_unflags:
|
||||||
- -ffunction-sections
|
# - -flto
|
||||||
- -fdata-sections
|
# build_flags:
|
||||||
- -DNDEBUG
|
# - -fno-lto
|
||||||
|
# - -Wl,--gc-sections
|
||||||
|
# - -ffunction-sections
|
||||||
|
# - -fdata-sections
|
||||||
|
# - -DNDEBUG
|
||||||
|
|
||||||
##########################################################################################
|
##########################################################################################
|
||||||
# ESP PLATFORM AND FRAMEWORK
|
# ESP PLATFORM AND FRAMEWORK
|
||||||
@@ -184,10 +205,10 @@ esp8266:
|
|||||||
restore_from_flash: true # restore some values on reboot
|
restore_from_flash: true # restore some values on reboot
|
||||||
|
|
||||||
mdns:
|
mdns:
|
||||||
disabled: false # Disabling will make the build file smaller (and it is still available via static IP)
|
disabled: false # Disabling will make the build file smaller (and device is still available via static IP)
|
||||||
|
|
||||||
preferences:
|
preferences:
|
||||||
flash_write_interval: 5min
|
flash_write_interval: 5sec # enough time to update values for reboots, but not enough to wear flash
|
||||||
|
|
||||||
##########################################################################################
|
##########################################################################################
|
||||||
# GLOBAL VARIABLES
|
# GLOBAL VARIABLES
|
||||||
@@ -208,7 +229,8 @@ globals:
|
|||||||
- id: max_on_hours
|
- id: max_on_hours
|
||||||
type: int
|
type: int
|
||||||
restore_value: true
|
restore_value: true
|
||||||
initial_value: '${max_on_default_hours}'
|
initial_value: "${max_on_default_hours}"
|
||||||
|
|
||||||
# Default Fading Up Time (Selectable and will be retained)
|
# Default Fading Up Time (Selectable and will be retained)
|
||||||
- id: ramp_up_ms # fade-in when turned ON
|
- id: ramp_up_ms # fade-in when turned ON
|
||||||
type: int
|
type: int
|
||||||
@@ -272,6 +294,16 @@ globals:
|
|||||||
type: float
|
type: float
|
||||||
restore_value: false
|
restore_value: false
|
||||||
initial_value: '0.0'
|
initial_value: '0.0'
|
||||||
|
# guards OFF persistence during startup caused by restore_mode: ALWAYS_OFF
|
||||||
|
- id: booting
|
||||||
|
type: bool
|
||||||
|
restore_value: false
|
||||||
|
initial_value: 'true'
|
||||||
|
# true only while we are performing a ramp-down that briefly turns the light ON to hit the floor
|
||||||
|
- id: ramping_for_off
|
||||||
|
type: bool
|
||||||
|
restore_value: false
|
||||||
|
initial_value: 'false'
|
||||||
|
|
||||||
##########################################################################################
|
##########################################################################################
|
||||||
# LOGGER COMPONENT
|
# LOGGER COMPONENT
|
||||||
@@ -282,6 +314,15 @@ logger:
|
|||||||
level: "${log_level}" # INFO Level suggested, or DEBUG for testing
|
level: "${log_level}" # INFO Level suggested, or DEBUG for testing
|
||||||
baud_rate: 0 # set to 0 for no logging via UART, needed if you are using it for other serial things (eg PZEM, Serial control)
|
baud_rate: 0 # set to 0 for no logging via UART, needed if you are using it for other serial things (eg PZEM, Serial control)
|
||||||
|
|
||||||
|
#########################################################################################
|
||||||
|
# STATUS LED
|
||||||
|
# https://esphome.io/components/status_led.html
|
||||||
|
#########################################################################################
|
||||||
|
status_led:
|
||||||
|
pin:
|
||||||
|
number: ${device_status_led}
|
||||||
|
inverted: true
|
||||||
|
|
||||||
##########################################################################################
|
##########################################################################################
|
||||||
# MQTT COMMANDS
|
# MQTT COMMANDS
|
||||||
# This adds device-specific MQTT command triggers to the common MQTT configuration.
|
# This adds device-specific MQTT command triggers to the common MQTT configuration.
|
||||||
@@ -299,15 +340,6 @@ mqtt:
|
|||||||
then:
|
then:
|
||||||
- switch.turn_off: mosfet_ramp_switch
|
- switch.turn_off: mosfet_ramp_switch
|
||||||
|
|
||||||
#########################################################################################
|
|
||||||
# STATUS LED
|
|
||||||
# https://esphome.io/components/status_led.html
|
|
||||||
#########################################################################################
|
|
||||||
status_led:
|
|
||||||
pin:
|
|
||||||
number: GPIO2
|
|
||||||
inverted: true
|
|
||||||
|
|
||||||
##########################################################################################
|
##########################################################################################
|
||||||
# SWITCH COMPONENT
|
# SWITCH COMPONENT
|
||||||
# https://esphome.io/components/switch/
|
# https://esphome.io/components/switch/
|
||||||
@@ -366,12 +398,18 @@ button:
|
|||||||
name: "${friendly_name} Fade Stop"
|
name: "${friendly_name} Fade Stop"
|
||||||
icon: mdi:pause
|
icon: mdi:pause
|
||||||
on_press:
|
on_press:
|
||||||
# Stop any pending scripts (and their delayed turn_off)
|
# Stop any pending scripts (and their delayed actions)
|
||||||
- script.stop: ramp_on_script
|
- script.stop: ramp_on_script
|
||||||
- script.stop: ramp_off_script
|
- script.stop: ramp_off_script
|
||||||
# Cancel the light's transition by commanding the current level with 0 ms,
|
- script.stop: led_flash_up
|
||||||
# but DO NOT change the ramp switch state/flag.
|
- script.stop: led_flash_down
|
||||||
|
- output.turn_off: green_led_out
|
||||||
- lambda: |-
|
- lambda: |-
|
||||||
|
// We are no longer ramping (up or down)
|
||||||
|
id(ramping_for_off) = false;
|
||||||
|
id(is_ramping) = false;
|
||||||
|
|
||||||
|
// Freeze the light at its *current* level by overwriting the transition
|
||||||
const auto &cv = id(mosfet_leds).current_values;
|
const auto &cv = id(mosfet_leds).current_values;
|
||||||
if (cv.is_on()) {
|
if (cv.is_on()) {
|
||||||
auto call = id(mosfet_leds).make_call();
|
auto call = id(mosfet_leds).make_call();
|
||||||
@@ -381,6 +419,14 @@ button:
|
|||||||
call.perform();
|
call.perform();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Persist the exact frozen level so "Restore Brightness" survives a reboot
|
||||||
|
if (cv.is_on()) {
|
||||||
|
float pct = cv.get_brightness() * 100.0f;
|
||||||
|
id(last_brightness_pct) = pct;
|
||||||
|
if (pct > 0.5f) id(last_nonzero_brightness_pct) = pct;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
#########################################################################################
|
#########################################################################################
|
||||||
# SELECT COMPONENT
|
# SELECT COMPONENT
|
||||||
# https://esphome.io/components/select/index.html
|
# https://esphome.io/components/select/index.html
|
||||||
@@ -410,7 +456,37 @@ select:
|
|||||||
# BINARY SENSORS
|
# BINARY SENSORS
|
||||||
# https://esphome.io/components/binary_sensor/
|
# https://esphome.io/components/binary_sensor/
|
||||||
#########################################################################################
|
#########################################################################################
|
||||||
#binary_sensor:
|
binary_sensor:
|
||||||
|
- platform: gpio
|
||||||
|
id: btn_gpio12
|
||||||
|
name: "${friendly_name} Button"
|
||||||
|
pin:
|
||||||
|
number: ${device_usr_button}
|
||||||
|
mode:
|
||||||
|
input: true
|
||||||
|
pullup: true
|
||||||
|
inverted: true
|
||||||
|
filters:
|
||||||
|
- delayed_on: 20ms
|
||||||
|
- delayed_off: 20ms
|
||||||
|
on_press:
|
||||||
|
- if:
|
||||||
|
condition:
|
||||||
|
lambda: 'return id(ramp_switch_target_on);'
|
||||||
|
then:
|
||||||
|
# Target is currently ON -> press should go OFF (start ramp-down)
|
||||||
|
- lambda: |-
|
||||||
|
id(ramp_switch_target_on) = false;
|
||||||
|
id(mosfet_ramp_switch).publish_state(false); // reflect in HA immediately
|
||||||
|
- script.stop: ramp_on_script
|
||||||
|
- script.execute: ramp_off_script
|
||||||
|
else:
|
||||||
|
# Target is currently OFF -> press should go ON (start ramp-up)
|
||||||
|
- lambda: |-
|
||||||
|
id(ramp_switch_target_on) = true;
|
||||||
|
id(mosfet_ramp_switch).publish_state(true); // reflect in HA immediately
|
||||||
|
- script.stop: ramp_off_script
|
||||||
|
- script.execute: ramp_on_script
|
||||||
|
|
||||||
##########################################################################################
|
##########################################################################################
|
||||||
# SENSOR COMPONENT
|
# SENSOR COMPONENT
|
||||||
@@ -430,16 +506,22 @@ sensor:
|
|||||||
on_value:
|
on_value:
|
||||||
then:
|
then:
|
||||||
- lambda: |-
|
- lambda: |-
|
||||||
// Remember latest actual output (0..100) for "Restore Brightness"
|
const auto &cv_now = id(mosfet_leds).current_values;
|
||||||
id(last_brightness_pct) = x;
|
|
||||||
if (x > 0.5f) {
|
// Only persist exact last brightness while actually ON,
|
||||||
// Only persist when not ramping; always track a volatile copy
|
// and not during the OFF ramp's brief floor-kick.
|
||||||
if (!id(is_ramping)) id(last_nonzero_brightness_pct) = x;
|
if (cv_now.is_on()) {
|
||||||
id(last_nonzero_tmp) = x;
|
if (x > 0.5f) {
|
||||||
|
if (!id(is_ramping)) id(last_nonzero_brightness_pct) = x;
|
||||||
|
id(last_nonzero_tmp) = x;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (x > 0.5f) id(last_nonzero_tmp) = x;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If not suppressing sync, update the 0..100 slider only when its INT changes
|
// If not suppressing sync, update the 0..100 slider only when its INT changes
|
||||||
if (!id(suppress_slider_sync)) {
|
if (!id(suppress_slider_sync)) {
|
||||||
float actual = x; // actual %
|
float actual = x;
|
||||||
float minp = (float) id(min_brightness_pct);
|
float minp = (float) id(min_brightness_pct);
|
||||||
float maxp = (float) id(max_brightness_pct);
|
float maxp = (float) id(max_brightness_pct);
|
||||||
if (maxp <= minp) maxp = minp + 1.0f;
|
if (maxp <= minp) maxp = minp + 1.0f;
|
||||||
@@ -463,7 +545,7 @@ sensor:
|
|||||||
lambda: |-
|
lambda: |-
|
||||||
const auto &cv = id(mosfet_leds).current_values;
|
const auto &cv = id(mosfet_leds).current_values;
|
||||||
if (!cv.is_on()) return 0.0f;
|
if (!cv.is_on()) return 0.0f;
|
||||||
const float lin = cv.get_brightness(); // 0..1 (linear brightness)
|
const float lin = cv.get_brightness(); // 0..1 linear brightness
|
||||||
const float gamma = atof("${led_gamma}"); // parse substitution string -> float
|
const float gamma = atof("${led_gamma}"); // parse substitution string -> float
|
||||||
float pwm = powf(lin, gamma); // approx PWM duty after gamma
|
float pwm = powf(lin, gamma); // approx PWM duty after gamma
|
||||||
if (pwm < 0.0f) pwm = 0.0f;
|
if (pwm < 0.0f) pwm = 0.0f;
|
||||||
@@ -480,8 +562,13 @@ sensor:
|
|||||||
output:
|
output:
|
||||||
- platform: esp8266_pwm
|
- platform: esp8266_pwm
|
||||||
id: mosfet_pwm
|
id: mosfet_pwm
|
||||||
pin: GPIO12
|
pin: ${device_mosfet_out}
|
||||||
frequency: 1000 Hz # higher frequency helps reduce camera banding while filming
|
frequency: ${pwm_frequency}
|
||||||
|
- platform: gpio
|
||||||
|
id: green_led_out # Green LED
|
||||||
|
pin:
|
||||||
|
number: ${device_fading_led}
|
||||||
|
inverted: false
|
||||||
|
|
||||||
##########################################################################################
|
##########################################################################################
|
||||||
# LIGHT COMPONENT
|
# LIGHT COMPONENT
|
||||||
@@ -492,29 +579,48 @@ light:
|
|||||||
id: mosfet_leds
|
id: mosfet_leds
|
||||||
name: "${friendly_name}"
|
name: "${friendly_name}"
|
||||||
output: mosfet_pwm
|
output: mosfet_pwm
|
||||||
restore_mode: ALWAYS_OFF
|
restore_mode: RESTORE_DEFAULT_OFF
|
||||||
default_transition_length: 2s
|
default_transition_length: 2s
|
||||||
icon: mdi:led-strip-variant
|
icon: mdi:led-strip-variant
|
||||||
gamma_correct: "${led_gamma}"
|
gamma_correct: "${led_gamma}"
|
||||||
|
|
||||||
|
# ON: publish state, track intent, arm watchdog if configured
|
||||||
on_turn_on:
|
on_turn_on:
|
||||||
- mqtt.publish:
|
- mqtt.publish:
|
||||||
topic: "${mqtt_local_status_topic}/light/state"
|
topic: "${mqtt_local_status_topic}/light/state"
|
||||||
payload: "${mqtt_local_device_command_ON}"
|
payload: "${mqtt_local_device_command_ON}"
|
||||||
retain: true
|
retain: true
|
||||||
- lambda: 'id(ramp_switch_target_on) = true;'
|
- lambda: |-
|
||||||
|
// Do not flip "intent to ON" when we're inside an OFF ramp's floor-kick
|
||||||
|
if (!id(ramping_for_off)) {
|
||||||
|
id(ramp_switch_target_on) = true;
|
||||||
|
}
|
||||||
- script.stop: max_on_watchdog
|
- script.stop: max_on_watchdog
|
||||||
- if:
|
- if:
|
||||||
condition:
|
condition:
|
||||||
lambda: 'return id(max_on_hours) > 0;'
|
lambda: 'return id(max_on_hours) > 0;'
|
||||||
then:
|
then:
|
||||||
- script.execute: max_on_watchdog
|
- script.execute: max_on_watchdog
|
||||||
|
|
||||||
|
# OFF: publish state, track intent, clear watchdog, and persist OFF so Mode 1 will not restore
|
||||||
|
# Guarded by booting flag so the automatic OFF at boot from ALWAYS_OFF does not zero persistence.
|
||||||
on_turn_off:
|
on_turn_off:
|
||||||
- mqtt.publish:
|
- if:
|
||||||
topic: "${mqtt_local_status_topic}/light/state"
|
condition:
|
||||||
payload: "${mqtt_local_device_command_OFF}"
|
lambda: 'return !id(booting);'
|
||||||
retain: true
|
then:
|
||||||
- lambda: 'id(ramp_switch_target_on) = false;'
|
- mqtt.publish:
|
||||||
- script.stop: max_on_watchdog
|
topic: "${mqtt_local_status_topic}/light/state"
|
||||||
|
payload: "${mqtt_local_device_command_OFF}"
|
||||||
|
retain: true
|
||||||
|
- lambda: 'id(ramp_switch_target_on) = false;'
|
||||||
|
- script.stop: max_on_watchdog
|
||||||
|
- lambda: |-
|
||||||
|
id(last_brightness_pct) = 0.0f;
|
||||||
|
id(last_set_pos) = 0;
|
||||||
|
id(led_output_set_pct).publish_state(0);
|
||||||
|
|
||||||
|
# Cap brightness to max_brightness_pct without a visible step
|
||||||
on_state:
|
on_state:
|
||||||
- lambda: |-
|
- lambda: |-
|
||||||
const float cap = id(max_brightness_pct) / 100.0f;
|
const float cap = id(max_brightness_pct) / 100.0f;
|
||||||
@@ -607,6 +713,8 @@ number:
|
|||||||
- lambda: |-
|
- lambda: |-
|
||||||
id(ramp_switch_target_on) = false;
|
id(ramp_switch_target_on) = false;
|
||||||
id(led_output_set_pct).publish_state(0);
|
id(led_output_set_pct).publish_state(0);
|
||||||
|
id(last_brightness_pct) = 0.0f; // persist OFF immediately
|
||||||
|
id(last_set_pos) = 0;
|
||||||
- delay: 400ms
|
- delay: 400ms
|
||||||
- lambda: 'id(suppress_slider_sync) = false;'
|
- lambda: 'id(suppress_slider_sync) = false;'
|
||||||
else:
|
else:
|
||||||
@@ -630,7 +738,19 @@ number:
|
|||||||
if (out_pct > maxp) out_pct = maxp;
|
if (out_pct > maxp) out_pct = maxp;
|
||||||
return out_pct / 100.0f;
|
return out_pct / 100.0f;
|
||||||
transition_length: 250ms
|
transition_length: 250ms
|
||||||
- lambda: 'id(ramp_switch_target_on) = true;'
|
- lambda: |-
|
||||||
|
id(ramp_switch_target_on) = true;
|
||||||
|
float pos = id(led_output_set_pct).state; // 1..100
|
||||||
|
float minp = (float) id(min_brightness_pct);
|
||||||
|
float maxp = (float) id(max_brightness_pct);
|
||||||
|
if (maxp <= minp) maxp = minp + 1.0f;
|
||||||
|
float out_pct = minp + (pos * (maxp - minp) / 100.0f);
|
||||||
|
if (out_pct > maxp) out_pct = maxp;
|
||||||
|
id(last_brightness_pct) = out_pct; // persist exact target now
|
||||||
|
if (out_pct > 0.5f) {
|
||||||
|
id(last_nonzero_brightness_pct) = out_pct;
|
||||||
|
id(last_nonzero_tmp) = out_pct;
|
||||||
|
}
|
||||||
- delay: 400ms
|
- delay: 400ms
|
||||||
- lambda: 'id(suppress_slider_sync) = false;'
|
- lambda: 'id(suppress_slider_sync) = false;'
|
||||||
|
|
||||||
@@ -670,12 +790,43 @@ number:
|
|||||||
# Scripts can be executed nearly anywhere in your device configuration with a single call.
|
# Scripts can be executed nearly anywhere in your device configuration with a single call.
|
||||||
##########################################################################################
|
##########################################################################################
|
||||||
script:
|
script:
|
||||||
|
# Blink pattern while ramping UP: quick double-blink, pause, repeat
|
||||||
|
- id: led_flash_up
|
||||||
|
mode: restart
|
||||||
|
then:
|
||||||
|
- while:
|
||||||
|
condition:
|
||||||
|
lambda: 'return true;'
|
||||||
|
then:
|
||||||
|
- output.turn_on: green_led_out
|
||||||
|
- delay: 100ms
|
||||||
|
- output.turn_off: green_led_out
|
||||||
|
- delay: 100ms
|
||||||
|
- output.turn_on: green_led_out
|
||||||
|
- delay: 100ms
|
||||||
|
- output.turn_off: green_led_out
|
||||||
|
- delay: 400ms
|
||||||
|
# Blink pattern while ramping DOWN: steady slow blink
|
||||||
|
- id: led_flash_down
|
||||||
|
mode: restart
|
||||||
|
then:
|
||||||
|
- while:
|
||||||
|
condition:
|
||||||
|
lambda: 'return true;'
|
||||||
|
then:
|
||||||
|
- output.turn_on: green_led_out
|
||||||
|
- delay: 250ms
|
||||||
|
- output.turn_off: green_led_out
|
||||||
|
- delay: 250ms
|
||||||
|
|
||||||
# Script: ramp up from current level. Obey global max.
|
# Script: ramp up from current level. Obey global max.
|
||||||
- id: ramp_on_script
|
- id: ramp_on_script
|
||||||
mode: restart
|
mode: restart
|
||||||
then:
|
then:
|
||||||
- lambda: 'id(is_ramping) = true;' # mark ramping
|
- lambda: 'id(is_ramping) = true;'
|
||||||
- script.stop: ramp_off_script
|
- script.stop: ramp_off_script
|
||||||
|
- script.stop: led_flash_down
|
||||||
|
- script.execute: led_flash_up
|
||||||
# Ensure we start at at least the floor without a visible "pop".
|
# Ensure we start at at least the floor without a visible "pop".
|
||||||
- if:
|
- if:
|
||||||
condition:
|
condition:
|
||||||
@@ -696,18 +847,14 @@ script:
|
|||||||
const auto &cv = id(mosfet_leds).current_values;
|
const auto &cv = id(mosfet_leds).current_values;
|
||||||
const float floor = id(min_brightness_pct) / 100.0f;
|
const float floor = id(min_brightness_pct) / 100.0f;
|
||||||
const float cap = id(max_brightness_pct) / 100.0f;
|
const float cap = id(max_brightness_pct) / 100.0f;
|
||||||
if (cap <= floor + 0.0005f) { id(last_ramp_ms) = 0; return (uint32_t) 0; } // guard degenerate case
|
if (cap <= floor + 0.0005f) { id(last_ramp_ms) = 0; return (uint32_t) 0; }
|
||||||
|
|
||||||
// IMPORTANT: when the light was OFF, treat current as 0 so we don't compute a 0ms jump to cap.
|
|
||||||
float curr = cv.is_on() ? cv.get_brightness() : 0.0f;
|
float curr = cv.is_on() ? cv.get_brightness() : 0.0f;
|
||||||
|
|
||||||
// If we're below the floor (i.e. coming from OFF), we just did the "floor kick" above.
|
|
||||||
// Use full ramp_up_ms for a smooth 0 -> cap ramp.
|
|
||||||
if (curr + 0.0005f < floor) {
|
if (curr + 0.0005f < floor) {
|
||||||
id(last_ramp_ms) = id(ramp_up_ms);
|
id(last_ramp_ms) = id(ramp_up_ms);
|
||||||
return (uint32_t) id(last_ramp_ms);
|
return (uint32_t) id(last_ramp_ms);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (curr > cap) curr = cap;
|
if (curr > cap) curr = cap;
|
||||||
if (curr < floor) curr = floor;
|
if (curr < floor) curr = floor;
|
||||||
float frac = (cap - curr) / (cap - floor);
|
float frac = (cap - curr) / (cap - floor);
|
||||||
@@ -716,20 +863,25 @@ script:
|
|||||||
id(last_ramp_ms) = (int) (id(ramp_up_ms) * frac);
|
id(last_ramp_ms) = (int) (id(ramp_up_ms) * frac);
|
||||||
return (uint32_t) id(last_ramp_ms);
|
return (uint32_t) id(last_ramp_ms);
|
||||||
- delay: !lambda 'return (uint32_t) id(last_ramp_ms);'
|
- delay: !lambda 'return (uint32_t) id(last_ramp_ms);'
|
||||||
|
- script.stop: led_flash_up
|
||||||
|
- output.turn_off: green_led_out
|
||||||
- lambda: |-
|
- lambda: |-
|
||||||
id(is_ramping) = false; // ramp complete
|
id(is_ramping) = false;
|
||||||
const auto &cv = id(mosfet_leds).current_values;
|
const auto &cv = id(mosfet_leds).current_values;
|
||||||
if (cv.is_on()) {
|
if (cv.is_on()) {
|
||||||
float pct = cv.get_brightness() * 100.0f;
|
float pct = cv.get_brightness() * 100.0f;
|
||||||
if (pct > 0.5f) id(last_nonzero_brightness_pct) = pct; // commit once
|
id(last_brightness_pct) = pct; // persist final level
|
||||||
|
if (pct > 0.5f) id(last_nonzero_brightness_pct) = pct;
|
||||||
}
|
}
|
||||||
|
|
||||||
# Script: ramp to a specific target (Restore Brightness path)
|
# Script: ramp to a specific target (Restore Brightness path)
|
||||||
- id: ramp_to_target_script
|
- id: ramp_to_target_script
|
||||||
mode: restart
|
mode: restart
|
||||||
then:
|
then:
|
||||||
- lambda: 'id(is_ramping) = true;' # mark ramping
|
- lambda: 'id(is_ramping) = true;'
|
||||||
- script.stop: ramp_off_script
|
- script.stop: ramp_off_script
|
||||||
|
- script.stop: led_flash_down
|
||||||
|
- script.execute: led_flash_up
|
||||||
# Ensure we start at the floor cleanly.
|
# Ensure we start at the floor cleanly.
|
||||||
- if:
|
- if:
|
||||||
condition:
|
condition:
|
||||||
@@ -756,12 +908,9 @@ script:
|
|||||||
float cap = id(restore_target_pct) / 100.0f;
|
float cap = id(restore_target_pct) / 100.0f;
|
||||||
const float maxp = id(max_brightness_pct) / 100.0f;
|
const float maxp = id(max_brightness_pct) / 100.0f;
|
||||||
if (cap > maxp) cap = maxp;
|
if (cap > maxp) cap = maxp;
|
||||||
if (cap <= floor + 0.0005f) { id(last_ramp_ms) = 0; return (uint32_t) 0; } // guard
|
if (cap <= floor + 0.0005f) { id(last_ramp_ms) = 0; return (uint32_t) 0; }
|
||||||
|
|
||||||
// Same OFF case handling: if off, treat curr as 0 so we don't collapse the ramp.
|
|
||||||
float curr = cv.is_on() ? cv.get_brightness() : 0.0f;
|
float curr = cv.is_on() ? cv.get_brightness() : 0.0f;
|
||||||
|
|
||||||
// Coming from OFF/below floor? We just did the "floor kick" above — use full ramp_up_ms.
|
|
||||||
if (curr + 0.0005f < floor) {
|
if (curr + 0.0005f < floor) {
|
||||||
id(last_ramp_ms) = id(ramp_up_ms);
|
id(last_ramp_ms) = id(ramp_up_ms);
|
||||||
return (uint32_t) id(last_ramp_ms);
|
return (uint32_t) id(last_ramp_ms);
|
||||||
@@ -775,20 +924,27 @@ script:
|
|||||||
id(last_ramp_ms) = (int) (id(ramp_up_ms) * frac);
|
id(last_ramp_ms) = (int) (id(ramp_up_ms) * frac);
|
||||||
return (uint32_t) id(last_ramp_ms);
|
return (uint32_t) id(last_ramp_ms);
|
||||||
- delay: !lambda 'return (uint32_t) id(last_ramp_ms);'
|
- delay: !lambda 'return (uint32_t) id(last_ramp_ms);'
|
||||||
|
- script.stop: led_flash_up
|
||||||
|
- output.turn_off: green_led_out
|
||||||
- lambda: |-
|
- lambda: |-
|
||||||
id(is_ramping) = false; // ramp complete
|
id(is_ramping) = false;
|
||||||
const auto &cv = id(mosfet_leds).current_values;
|
const auto &cv = id(mosfet_leds).current_values;
|
||||||
if (cv.is_on()) {
|
if (cv.is_on()) {
|
||||||
float pct = cv.get_brightness() * 100.0f;
|
float pct = cv.get_brightness() * 100.0f;
|
||||||
if (pct > 0.5f) id(last_nonzero_brightness_pct) = pct; // commit once
|
id(last_brightness_pct) = pct; // persist final level
|
||||||
|
if (pct > 0.5f) id(last_nonzero_brightness_pct) = pct;
|
||||||
}
|
}
|
||||||
|
|
||||||
# Script: ramp down from current level to floor, then cleanly cut to OFF
|
# Script: ramp down from current level to floor, then cleanly cut to OFF
|
||||||
- id: ramp_off_script
|
- id: ramp_off_script
|
||||||
mode: restart
|
mode: restart
|
||||||
then:
|
then:
|
||||||
- lambda: 'id(is_ramping) = true;' # mark ramping
|
- lambda: |-
|
||||||
|
id(is_ramping) = true;
|
||||||
|
id(ramping_for_off) = true;
|
||||||
- script.stop: ramp_on_script
|
- script.stop: ramp_on_script
|
||||||
|
- script.stop: led_flash_up
|
||||||
|
- script.execute: led_flash_down
|
||||||
- light.turn_on:
|
- light.turn_on:
|
||||||
id: mosfet_leds
|
id: mosfet_leds
|
||||||
brightness: !lambda 'return id(min_brightness_pct) / 100.0f;'
|
brightness: !lambda 'return id(min_brightness_pct) / 100.0f;'
|
||||||
@@ -798,7 +954,7 @@ script:
|
|||||||
float curr = cv.get_brightness();
|
float curr = cv.get_brightness();
|
||||||
if (curr < floor) curr = floor;
|
if (curr < floor) curr = floor;
|
||||||
const float denom = (1.0f - floor);
|
const float denom = (1.0f - floor);
|
||||||
if (denom <= 0.0005f) { id(last_ramp_ms) = 0; return (uint32_t) 0; } // guard near-1.0 floor
|
if (denom <= 0.0005f) { id(last_ramp_ms) = 0; return (uint32_t) 0; }
|
||||||
float frac = (curr - floor) / denom;
|
float frac = (curr - floor) / denom;
|
||||||
if (frac < 0.0f) frac = 0.0f;
|
if (frac < 0.0f) frac = 0.0f;
|
||||||
if (frac > 1.0f) frac = 1.0f;
|
if (frac > 1.0f) frac = 1.0f;
|
||||||
@@ -809,12 +965,15 @@ script:
|
|||||||
id: mosfet_leds
|
id: mosfet_leds
|
||||||
transition_length: 150ms
|
transition_length: 150ms
|
||||||
- delay: 150ms
|
- delay: 150ms
|
||||||
|
- script.stop: led_flash_down
|
||||||
|
- output.turn_off: green_led_out
|
||||||
- lambda: |-
|
- lambda: |-
|
||||||
id(is_ramping) = false; // ramp complete
|
id(is_ramping) = false;
|
||||||
auto call = id(mosfet_leds).make_call();
|
id(ramping_for_off) = false;
|
||||||
call.set_state(false);
|
id(last_brightness_pct) = 0.0f; // persist OFF for restore path
|
||||||
call.set_brightness(id(max_brightness_pct) / 100.0f);
|
id(last_set_pos) = 0;
|
||||||
call.perform();
|
id(led_output_set_pct).publish_state(0);
|
||||||
|
|
||||||
- id: max_on_watchdog
|
- id: max_on_watchdog
|
||||||
mode: restart
|
mode: restart
|
||||||
then:
|
then:
|
||||||
@@ -832,3 +991,38 @@ script:
|
|||||||
id(mosfet_ramp_switch).publish_state(false);
|
id(mosfet_ramp_switch).publish_state(false);
|
||||||
- script.stop: ramp_on_script
|
- script.stop: ramp_on_script
|
||||||
- script.execute: ramp_off_script
|
- script.execute: ramp_off_script
|
||||||
|
|
||||||
|
# Deferred restore to avoid early-boot races and ensure we apply the last saved level
|
||||||
|
- id: deferred_restore_brightness
|
||||||
|
mode: restart
|
||||||
|
then:
|
||||||
|
- delay: 5s # let preferences/globals load and light settle
|
||||||
|
- lambda: |-
|
||||||
|
// Use ONLY the exact last saved brightness.
|
||||||
|
float target = id(last_brightness_pct);
|
||||||
|
|
||||||
|
// Clamp into [min..max] only if we are restoring a non-zero target
|
||||||
|
float minp = (float) id(min_brightness_pct);
|
||||||
|
float maxp = (float) id(max_brightness_pct);
|
||||||
|
if (target > 0.0f && target < minp) target = minp;
|
||||||
|
if (target > maxp) target = maxp;
|
||||||
|
|
||||||
|
id(restore_target_pct) = target;
|
||||||
|
|
||||||
|
- if:
|
||||||
|
condition:
|
||||||
|
lambda: 'return id(restore_target_pct) > 0.5f;'
|
||||||
|
then:
|
||||||
|
- lambda: |-
|
||||||
|
id(ramp_switch_target_on) = true; // reflect our intent
|
||||||
|
id(suppress_slider_sync) = true;
|
||||||
|
- script.stop: ramp_off_script
|
||||||
|
- script.execute: ramp_to_target_script
|
||||||
|
- delay: !lambda 'return (uint32_t) id(last_ramp_ms);'
|
||||||
|
- lambda: 'id(suppress_slider_sync) = false;'
|
||||||
|
else:
|
||||||
|
# Exact last was 0 -> remain OFF
|
||||||
|
- lambda: 'id(ramp_switch_target_on) = false;'
|
||||||
|
- light.turn_off:
|
||||||
|
id: mosfet_leds
|
||||||
|
transition_length: 0s
|
||||||
@@ -0,0 +1,181 @@
|
|||||||
|
##########################################################################################
|
||||||
|
##########################################################################################
|
||||||
|
# Main Kitchen LIGHTSWITCH
|
||||||
|
# V3.7 2025-09-14 upload to this lightswitch from copy
|
||||||
|
#
|
||||||
|
##########################################################################################
|
||||||
|
# Zemismart KS-811 Double push button
|
||||||
|
# pinout/schematic https://community.home-assistant.io/t/zemismart-ks-811-working-with-esphome/
|
||||||
|
#
|
||||||
|
# NOTES
|
||||||
|
# - Light switch with 2 sets of lights
|
||||||
|
#
|
||||||
|
##########################################################################################
|
||||||
|
##########################################################################################
|
||||||
|
|
||||||
|
##########################################################################################
|
||||||
|
# SPECIFIC DEVICE VARIABLE SUBSTITUTIONS
|
||||||
|
# If NOT using a secrets file, just replace these with the passwords etc (in quotes)
|
||||||
|
##########################################################################################
|
||||||
|
substitutions:
|
||||||
|
# Device Naming
|
||||||
|
device_name: "esp-mainkitchenlights"
|
||||||
|
friendly_name: "Main Kitchen Lightswitch (2)"
|
||||||
|
description_comment: "Main Kitchen lightswitch using a Zemismart KS-811 Double Push Button. Main Lights (1), Bench Lights (2)"
|
||||||
|
device_area: "Kitchen" # Allows ESP device to be automatically linked to an 'Area' in Home Assistant.
|
||||||
|
|
||||||
|
# Project Naming
|
||||||
|
project_name: "Zemismart Technologies.KS-811 Double" # Project Details
|
||||||
|
project_version: "v3.7" # Project V denotes release of yaml file, allowing checking of deployed vs latest version
|
||||||
|
|
||||||
|
# Passwords & Secrets
|
||||||
|
api_key: !secret esp-api_key # unfortunately you can't use substitutions inside secrets names
|
||||||
|
ota_pass: !secret esp-ota_pass # unfortunately you can't use substitutions inside secrets names
|
||||||
|
static_ip_address: !secret esp-mainkitchenlights_ip
|
||||||
|
#mqtt_local_command_main_topic: !secret mqtt_local_command_main_topic
|
||||||
|
#mqtt_local_status_main_topic: !secret mqtt_local_status_main_topic
|
||||||
|
|
||||||
|
# Device Settings
|
||||||
|
#relay_icon: "mdi:lightbulb-group"
|
||||||
|
log_level: "INFO" # Define logging level: NONE, ERROR, WARN, INFO, DEBUG (Default), VERBOSE, VERY_VERBOSE
|
||||||
|
update_interval: "60s" # update time for for general sensors etc
|
||||||
|
|
||||||
|
# MQTT LOCAL Controls
|
||||||
|
#mqtt_device_name: "bedroom2-lights"
|
||||||
|
#mqtt_local_command_topic: "${mqtt_local_command_main_topic}/${mqtt_device_name}" # Topic we will use to command this locally without HA
|
||||||
|
#mqtt_local_status_topic: "${mqtt_local_status_main_topic}/${mqtt_device_name}" # Topic we will use to view status locally without HA
|
||||||
|
|
||||||
|
# Switch Naming
|
||||||
|
switch_1_name: "Main Lights"
|
||||||
|
switch_2_name: "Benchtop Lights"
|
||||||
|
#switch_3_name: "Nil"
|
||||||
|
|
||||||
|
#########################################################################################
|
||||||
|
# PACKAGES: Included Common Packages
|
||||||
|
# https://esphome.io/components/packages.html
|
||||||
|
##########################################################################################
|
||||||
|
packages:
|
||||||
|
common_wifi: !include
|
||||||
|
file: common/network_common.yaml
|
||||||
|
vars:
|
||||||
|
local_device_name: "${device_name}"
|
||||||
|
local_static_ip_address: "${static_ip_address}"
|
||||||
|
local_ota_pass: "${ota_pass}"
|
||||||
|
common_api: !include
|
||||||
|
file: common/api_common.yaml
|
||||||
|
vars:
|
||||||
|
local_api_key: "${api_key}"
|
||||||
|
#common_webportal: !include
|
||||||
|
# file: common/webportal_common.yaml
|
||||||
|
common_mqtt: !include
|
||||||
|
file: common/mqtt_common.yaml
|
||||||
|
vars:
|
||||||
|
local_device_name: "${device_name}"
|
||||||
|
common_sntp: !include
|
||||||
|
file: common/sntp_common.yaml
|
||||||
|
common_general_sensors: !include
|
||||||
|
file: common/sensors_common.yaml
|
||||||
|
vars:
|
||||||
|
local_friendly_name: "${friendly_name}"
|
||||||
|
local_update_interval: "${update_interval}"
|
||||||
|
|
||||||
|
#########################################################################################
|
||||||
|
# ESPHome
|
||||||
|
# https://esphome.io/components/esphome.html
|
||||||
|
#########################################################################################
|
||||||
|
esphome:
|
||||||
|
name: "${device_name}"
|
||||||
|
friendly_name: "${friendly_name}"
|
||||||
|
comment: "${description_comment}" # Appears on the esphome page in HA
|
||||||
|
area: "${device_area}"
|
||||||
|
project:
|
||||||
|
name: "${project_name}"
|
||||||
|
version: "${project_version}"
|
||||||
|
# on_boot:
|
||||||
|
# priority: 200
|
||||||
|
# then:
|
||||||
|
# - switch.turn_on: Relay_2 # Stays on permanently
|
||||||
|
|
||||||
|
#########################################################################################
|
||||||
|
# ESP Platform and Framework
|
||||||
|
# https://esphome.io/components/esp32.html
|
||||||
|
#########################################################################################
|
||||||
|
esp8266:
|
||||||
|
board: esp01_1m
|
||||||
|
early_pin_init: False # Initialise pins early to known values. Recommended false where switches are involved. Defaults to True.
|
||||||
|
board_flash_mode: dout # Default is dout
|
||||||
|
|
||||||
|
#########################################################################################
|
||||||
|
# ESPHome Logging Enable
|
||||||
|
# https://esphome.io/components/logger.html
|
||||||
|
#############################################
|
||||||
|
logger:
|
||||||
|
level: "${log_level}" #INFO Level suggested, or DEBUG for testing
|
||||||
|
#baud_rate: 0 #set to 0 for no logging via UART, needed if you are using it for other serial things (eg PZEM)
|
||||||
|
#esp8266_store_log_strings_in_flash: false
|
||||||
|
#tx_buffer_size: 64
|
||||||
|
|
||||||
|
#########################################################################################
|
||||||
|
# STATUS LED
|
||||||
|
# https://esphome.io/components/status_led.html
|
||||||
|
#########################################################################################
|
||||||
|
status_led:
|
||||||
|
pin:
|
||||||
|
number: GPIO2
|
||||||
|
inverted: yes
|
||||||
|
|
||||||
|
#########################################################################################
|
||||||
|
# BINARY SENSORS
|
||||||
|
# https://esphome.io/components/binary_sensor/
|
||||||
|
#########################################################################################
|
||||||
|
binary_sensor:
|
||||||
|
- platform: gpio
|
||||||
|
pin:
|
||||||
|
number: GPIO16
|
||||||
|
mode: INPUT
|
||||||
|
inverted: True
|
||||||
|
name: "Button 1: ${switch_1_name}"
|
||||||
|
on_press:
|
||||||
|
- switch.toggle: Relay_1
|
||||||
|
|
||||||
|
- platform: gpio
|
||||||
|
pin:
|
||||||
|
number: GPIO05
|
||||||
|
mode: INPUT
|
||||||
|
inverted: True
|
||||||
|
name: "Button 2: ${switch_2_name}"
|
||||||
|
on_press:
|
||||||
|
- switch.toggle: Relay_2
|
||||||
|
|
||||||
|
# KS-811-2 is a double only
|
||||||
|
# - platform: gpio
|
||||||
|
# pin:
|
||||||
|
# number: GPIO4
|
||||||
|
# mode: INPUT
|
||||||
|
# inverted: True
|
||||||
|
# name: "Button 3: ${switch_3_name}"
|
||||||
|
# on_press:
|
||||||
|
# - switch.toggle: Relay_3
|
||||||
|
|
||||||
|
#########################################################################################
|
||||||
|
# SWITCH COMPONENT
|
||||||
|
# https://esphome.io/components/switch/
|
||||||
|
#########################################################################################
|
||||||
|
switch:
|
||||||
|
- platform: gpio
|
||||||
|
name: "Relay 1: ${switch_1_name}"
|
||||||
|
pin: GPIO13
|
||||||
|
id: Relay_1
|
||||||
|
|
||||||
|
- platform: gpio
|
||||||
|
name: "Relay 2: ${switch_2_name}"
|
||||||
|
pin: GPIO12
|
||||||
|
id: Relay_2
|
||||||
|
|
||||||
|
# KS-811-2 is a double only
|
||||||
|
# - platform: gpio
|
||||||
|
# name: "Relay 3: ${switch_3_name}"
|
||||||
|
# pin: GPIO14
|
||||||
|
# id: Relay_3
|
||||||
|
|
||||||
|
|
||||||
@@ -262,7 +262,7 @@ interval:
|
|||||||
#############################################
|
#############################################
|
||||||
number:
|
number:
|
||||||
- platform: template
|
- platform: template
|
||||||
name: ${friendly_name} LD1125H mth1 #mth1 is 0~2.8m Sensitivity.
|
name: "0-2.8m Sensitivity" # mth1 is 0~2.8m Sensitivity.
|
||||||
id: LD1125H_mth1
|
id: LD1125H_mth1
|
||||||
icon: "mdi:cogs"
|
icon: "mdi:cogs"
|
||||||
optimistic: true
|
optimistic: true
|
||||||
@@ -279,12 +279,12 @@ number:
|
|||||||
std::string th1st = "mth1=" + str_sprintf("%.0f",x) +"\r\n";
|
std::string th1st = "mth1=" + str_sprintf("%.0f",x) +"\r\n";
|
||||||
return std::vector<uint8_t>(th1st.begin(), th1st.end());
|
return std::vector<uint8_t>(th1st.begin(), th1st.end());
|
||||||
- platform: template
|
- platform: template
|
||||||
name: ${friendly_name} LD1125H mth2 #mth2 is 2.8~8m Sensitivity.
|
name: "2.8-8m Sensitivity" # mth2 is 2.8~8m Sensitivity.
|
||||||
id: LD1125H_mth2
|
id: LD1125H_mth2
|
||||||
icon: "mdi:cogs"
|
icon: "mdi:cogs"
|
||||||
optimistic: true
|
optimistic: true
|
||||||
restore_value: true #If you don't want to store the setting at ESP, set it to false.
|
restore_value: true # If you don't want to store the setting at ESP, set it to false.
|
||||||
initial_value: "30" #Default mth2 Setting
|
initial_value: "30" # Default mth2 Setting
|
||||||
min_value: 5
|
min_value: 5
|
||||||
max_value: 300
|
max_value: 300
|
||||||
step: 5
|
step: 5
|
||||||
@@ -296,12 +296,12 @@ number:
|
|||||||
std::string th2st = "mth2=" + str_sprintf("%.0f",x) +"\r\n";
|
std::string th2st = "mth2=" + str_sprintf("%.0f",x) +"\r\n";
|
||||||
return std::vector<uint8_t>(th2st.begin(), th2st.end());
|
return std::vector<uint8_t>(th2st.begin(), th2st.end());
|
||||||
- platform: template
|
- platform: template
|
||||||
name: ${friendly_name} LD1125H mth3 #mth3 is above 8m Sensitivity.
|
name: "8m+ Sensitivity" # mth3 is above 8m Sensitivity.
|
||||||
id: LD1125H_mth3
|
id: LD1125H_mth3
|
||||||
icon: "mdi:cogs"
|
icon: "mdi:cogs"
|
||||||
optimistic: true
|
optimistic: true
|
||||||
restore_value: true #If you don't want to store the setting at ESP, set it to false.
|
restore_value: true # If you don't want to store the setting at ESP, set it to false.
|
||||||
initial_value: "20" #Default mth3 Setting
|
initial_value: "20" # Default mth3 Setting
|
||||||
min_value: 5
|
min_value: 5
|
||||||
max_value: 200
|
max_value: 200
|
||||||
step: 5
|
step: 5
|
||||||
@@ -313,7 +313,7 @@ number:
|
|||||||
std::string th3st = "mth3=" + str_sprintf("%.0f",x) +"\r\n";
|
std::string th3st = "mth3=" + str_sprintf("%.0f",x) +"\r\n";
|
||||||
return std::vector<uint8_t>(th3st.begin(), th3st.end());
|
return std::vector<uint8_t>(th3st.begin(), th3st.end());
|
||||||
- platform: template
|
- platform: template
|
||||||
name: ${friendly_name} LD1125H rmax #rmax is max detection distance.
|
name: "Max Detection (m)" # rmax is max detection distance.
|
||||||
id: LD1125H_rmax
|
id: LD1125H_rmax
|
||||||
icon: "mdi:cogs"
|
icon: "mdi:cogs"
|
||||||
optimistic: true
|
optimistic: true
|
||||||
@@ -330,7 +330,7 @@ number:
|
|||||||
std::string rmaxst = "rmax=" + str_sprintf("%.1f",x) +"\r\n";
|
std::string rmaxst = "rmax=" + str_sprintf("%.1f",x) +"\r\n";
|
||||||
return std::vector<uint8_t>(rmaxst.begin(), rmaxst.end());
|
return std::vector<uint8_t>(rmaxst.begin(), rmaxst.end());
|
||||||
- platform: template
|
- platform: template
|
||||||
name: ${friendly_name} LD1125H Clearence Time
|
name: "Clearence Time (s)"
|
||||||
id: LD1125H_Clear_Time
|
id: LD1125H_Clear_Time
|
||||||
icon: "mdi:cogs"
|
icon: "mdi:cogs"
|
||||||
optimistic: true
|
optimistic: true
|
||||||
@@ -340,7 +340,7 @@ number:
|
|||||||
max_value: 20
|
max_value: 20
|
||||||
step: 0.5
|
step: 0.5
|
||||||
- platform: template
|
- platform: template
|
||||||
name: ${friendly_name} LD1125H Movement Time
|
name: "Movement Time (s)"
|
||||||
id: LD1125H_Mov_Time
|
id: LD1125H_Mov_Time
|
||||||
icon: "mdi:cogs"
|
icon: "mdi:cogs"
|
||||||
optimistic: true
|
optimistic: true
|
||||||
|
|||||||
@@ -102,29 +102,55 @@ substitutions:
|
|||||||
# https://esphome.io/components/packages.html
|
# https://esphome.io/components/packages.html
|
||||||
##########################################################################################
|
##########################################################################################
|
||||||
packages:
|
packages:
|
||||||
|
##################################################
|
||||||
|
# MANDATORY packages (won't compile without them!)
|
||||||
|
##################################################
|
||||||
|
|
||||||
|
#### WIFI, Network (Static/DHCP/IPV6 etc), Fallback AP, Safemode ####
|
||||||
common_wifi: !include
|
common_wifi: !include
|
||||||
file: common/network_common.yaml
|
file: common/network_common.yaml
|
||||||
vars:
|
vars:
|
||||||
local_device_name: "${device_name}"
|
local_device_name: "${device_name}"
|
||||||
local_static_ip_address: "${static_ip_address}"
|
local_static_ip_address: "${static_ip_address}"
|
||||||
local_ota_pass: "${ota_pass}"
|
local_ota_pass: "${ota_pass}"
|
||||||
|
|
||||||
|
##################################################
|
||||||
|
# OPTIONAL packages (comment if you don't want them)
|
||||||
|
##################################################
|
||||||
|
|
||||||
|
#### HOME ASSISTANT API (choose encryption or no encryption options) ####
|
||||||
common_api: !include
|
common_api: !include
|
||||||
file: common/api_common.yaml
|
file: common/api_common.yaml
|
||||||
|
#file: common/api_common_noencryption.yaml
|
||||||
vars:
|
vars:
|
||||||
local_api_key: "${api_key}"
|
local_api_key: "${api_key}"
|
||||||
|
|
||||||
|
#### WEB PORTAL ####
|
||||||
#common_webportal: !include
|
#common_webportal: !include
|
||||||
# file: common/webportal_common.yaml
|
# file: common/webportal_common.yaml
|
||||||
|
|
||||||
|
#### MQTT ####
|
||||||
common_mqtt: !include
|
common_mqtt: !include
|
||||||
file: common/mqtt_common.yaml
|
file: common/mqtt_common.yaml
|
||||||
vars:
|
vars:
|
||||||
local_device_name: "${device_name}"
|
local_device_name: "${device_name}"
|
||||||
|
|
||||||
|
#### SNTP (Only use if you want/need accurate timeclocks) ####
|
||||||
common_sntp: !include
|
common_sntp: !include
|
||||||
file: common/sntp_common.yaml
|
file: common/sntp_common.yaml
|
||||||
|
|
||||||
|
#### DIAGNOSTICS Lite Sensors ####
|
||||||
|
common_lite_sensors: !include
|
||||||
|
file: common/sensors_common_lite.yaml
|
||||||
|
|
||||||
|
#### DIAGNOSTICS General Sensors ####
|
||||||
common_general_sensors: !include
|
common_general_sensors: !include
|
||||||
file: common/sensors_common.yaml
|
file: common/sensors_common.yaml
|
||||||
vars:
|
|
||||||
local_friendly_name: "${friendly_name}"
|
#### DEBUGGING Sensors ####
|
||||||
local_update_interval: "${update_interval}"
|
# A count of various resets
|
||||||
|
#common_resetcount_debugging: !include
|
||||||
|
# file: common/resetcount_common.yaml
|
||||||
|
|
||||||
# Device Specific included packages
|
# Device Specific included packages
|
||||||
common_athompowermonV1: !include
|
common_athompowermonV1: !include
|
||||||
|
|||||||
@@ -86,29 +86,57 @@ substitutions:
|
|||||||
# https://esphome.io/components/packages.html
|
# https://esphome.io/components/packages.html
|
||||||
##########################################################################################
|
##########################################################################################
|
||||||
packages:
|
packages:
|
||||||
|
##################################################
|
||||||
|
# MANDATORY packages (won't compile without them!)
|
||||||
|
##################################################
|
||||||
|
|
||||||
|
#### WIFI, Network (Static/DHCP/IPV6 etc), Fallback AP, Safemode ####
|
||||||
common_wifi: !include
|
common_wifi: !include
|
||||||
file: common/network_common.yaml
|
file: common/network_common.yaml
|
||||||
vars:
|
vars:
|
||||||
local_device_name: "${device_name}"
|
local_device_name: "${device_name}"
|
||||||
local_static_ip_address: "${static_ip_address}"
|
local_static_ip_address: "${static_ip_address}"
|
||||||
local_ota_pass: "${ota_pass}"
|
local_ota_pass: "${ota_pass}"
|
||||||
|
|
||||||
|
##################################################
|
||||||
|
# OPTIONAL packages (comment if you don't want them)
|
||||||
|
##################################################
|
||||||
|
|
||||||
|
#### HOME ASSISTANT API (choose encryption or no encryption options) ####
|
||||||
common_api: !include
|
common_api: !include
|
||||||
file: common/api_common.yaml
|
file: common/api_common.yaml
|
||||||
|
#file: common/api_common_noencryption.yaml
|
||||||
vars:
|
vars:
|
||||||
local_api_key: "${api_key}"
|
local_api_key: "${api_key}"
|
||||||
|
|
||||||
|
#### WEB PORTAL ####
|
||||||
#common_webportal: !include
|
#common_webportal: !include
|
||||||
# file: common/webportal_common.yaml
|
# file: common/webportal_common.yaml
|
||||||
|
|
||||||
|
#### MQTT ####
|
||||||
common_mqtt: !include
|
common_mqtt: !include
|
||||||
file: common/mqtt_common.yaml
|
file: common/mqtt_common.yaml
|
||||||
vars:
|
vars:
|
||||||
local_device_name: "${device_name}"
|
local_device_name: "${device_name}"
|
||||||
|
|
||||||
|
#### SNTP (Only use if you want/need accurate timeclocks) ####
|
||||||
common_sntp: !include
|
common_sntp: !include
|
||||||
file: common/sntp_common.yaml
|
file: common/sntp_common.yaml
|
||||||
|
|
||||||
|
#### DIAGNOSTICS Lite Sensors ####
|
||||||
|
# Option of everything, or a lite version
|
||||||
|
common_lite_sensors: !include
|
||||||
|
file: common/sensors_common_lite.yaml
|
||||||
|
|
||||||
|
#### DIAGNOSTICS General Sensors ####
|
||||||
|
# Option of everything, or a lite version
|
||||||
common_general_sensors: !include
|
common_general_sensors: !include
|
||||||
file: common/sensors_common.yaml
|
file: common/sensors_common.yaml
|
||||||
vars:
|
|
||||||
local_friendly_name: "${friendly_name}"
|
#### DEBUGGING Sensors ####
|
||||||
local_update_interval: "${update_interval}"
|
# A count of various resets
|
||||||
|
#common_resetcount_debugging: !include
|
||||||
|
# file: common/resetcount_common.yaml
|
||||||
|
|
||||||
# Device Specific included packages
|
# Device Specific included packages
|
||||||
common_athompowermonV3: !include
|
common_athompowermonV3: !include
|
||||||
|
|||||||
+216
-303
@@ -1,18 +1,19 @@
|
|||||||
#############################################
|
#######################################################################################################
|
||||||
#############################################
|
#######################################################################################################
|
||||||
# Opengreen energy weather station board, with ESP8266 D1 Mini
|
# Opengreen energy weather station board, with ESP8266 D1 Mini
|
||||||
# https://www.opengreenenergy.com/solar-powered-wifi-weather-station-v2-0/
|
# https://www.opengreenenergy.com/solar-powered-wifi-weather-station-v2-0/
|
||||||
# zorruno 2024-05-04 V0.0 Untested
|
# zorruno 2025-09-12 V6 fixes: AP SSID sub; ADC calibrate indent; NaN-safe temp avg; empty-string publishes; improved 10min gust (3s avg -> 10m max); comment tidy-ups
|
||||||
# zorruno 2024-05-23 V1.0 Tidied up sensor info and added BMP280
|
|
||||||
# zorruno 2024-05-24 V1.1 Tested ok and added some V calibration for mine
|
|
||||||
# zorruno 2024-05-25 V2 A bunch more sensors added (wind direction not yet sorted)
|
|
||||||
# zorruno 2024-05-25 V3 Shuffled some sensors, rain count added
|
|
||||||
# zorruno 2024-06-03 V4 tweaks to battery and wind
|
|
||||||
# zorruno 2024-06-14 V5 added wind heading, and sorted breaking a change for OTA
|
# zorruno 2024-06-14 V5 added wind heading, and sorted breaking a change for OTA
|
||||||
#############################################
|
# zorruno 2024-06-03 V4 tweaks to battery and wind
|
||||||
#############################################
|
# zorruno 2024-05-25 V3 Shuffled some sensors, rain count added
|
||||||
|
# zorruno 2024-05-25 V2 A bunch more sensors added (wind direction not yet sorted)
|
||||||
|
# zorruno 2024-05-24 V1.1 Tested ok and added some V calibration for mine
|
||||||
|
# zorruno 2024-05-23 V1.0 Tidied up sensor info and added BMP280
|
||||||
|
# zorruno 2024-05-04 V0.0 Untested
|
||||||
|
#######################################################################################################
|
||||||
|
#######################################################################################################
|
||||||
|
|
||||||
#############################################
|
#######################################################################################################
|
||||||
# SENSORS INCLUDED
|
# SENSORS INCLUDED
|
||||||
#
|
#
|
||||||
# TESTED WORKING
|
# TESTED WORKING
|
||||||
@@ -55,43 +56,43 @@
|
|||||||
# soil temp
|
# soil temp
|
||||||
# soil moisture
|
# soil moisture
|
||||||
#
|
#
|
||||||
#############################################
|
#######################################################################################################
|
||||||
|
|
||||||
#############################################
|
#######################################################################################################
|
||||||
# WEMOS D1 Mini GPIO, and Weather station Use
|
# WEMOS D1 Mini GPIO, and Weather station Use
|
||||||
#############################################
|
#######################################################################################################
|
||||||
# PIN HEADERS on Weatherstation board:
|
# PIN HEADERS on Weatherstation board:
|
||||||
# D1, GPIO5 Used as SCL (I2C)
|
# D1, GPIO5 Used as SCL (I2C)
|
||||||
# D2, GPIO4 Used as SDA (I2C)
|
# D2, GPIO4 Used as SDA (I2C)
|
||||||
# D3, GPIO0 connected to FLASH button, boot fails if pulled LOW
|
# D3, GPIO0 connected to FLASH button, boot fails if pulled LOW
|
||||||
# D4, GPIO2 connected to on-board LED, boot fails if pulled LOW
|
# D4, GPIO2 connected to on-board LED, boot fails if pulled LOW
|
||||||
# D5, GPIO14 Used as Wind Speed Count
|
# D5, GPIO14 Used as Wind Speed Count
|
||||||
# D6, GPIO12 Used as Rainfall count
|
# D6, GPIO12 Used as Rainfall count
|
||||||
# D7, GPIO13 Used for DHT
|
# D7, GPIO13 Used for DHT
|
||||||
# D8, GPIO15 (Boot fails if pulled HIGH)
|
# D8, GPIO15 (Boot fails if pulled HIGH)
|
||||||
#
|
#
|
||||||
# NO HEADERS on Weatherstation board:
|
# NO HEADERS on Weatherstation board:
|
||||||
# D0, GPIO16, (used to wake up from deep sleep)
|
# D0, GPIO16 (used to wake up from deep sleep)
|
||||||
# RX, GPIO3 (HIGH at boot)
|
# RX, GPIO3 (HIGH at boot)
|
||||||
# TX, GPIO1 (HIGH at boot, boot fails if pulled LOW)
|
# TX, GPIO1 (HIGH at boot, boot fails if pulled LOW)
|
||||||
# A0, ADC0 Used here to measure battery V
|
# A0, ADC0 Used here to measure battery V
|
||||||
#
|
#
|
||||||
#############################################
|
#######################################################################################################
|
||||||
|
|
||||||
#############################################
|
#######################################################################################################
|
||||||
# Pulse Meter Issues 202405
|
# Pulse Meter Issues 202405
|
||||||
# https://github.com/esphome/issues/issues/4807
|
# https://github.com/esphome/issues/issues/4807
|
||||||
# https://github.com/esphome/issues/issues/3143
|
# https://github.com/esphome/issues/issues/3143
|
||||||
#############################################
|
#######################################################################################################
|
||||||
external_components:
|
external_components:
|
||||||
- source: github://TrentHouliston/esphome@loop_pulse_isr
|
- source: github://TrentHouliston/esphome@loop_pulse_isr
|
||||||
components: [ pulse_meter ]
|
components: [ pulse_meter ]
|
||||||
|
|
||||||
#############################################
|
#######################################################################################################
|
||||||
# Variable Substitutions
|
# Variable Substitutions
|
||||||
# Give this a useful name & description here
|
# Give this a useful name & description here
|
||||||
# and change values accordingly
|
# and change values accordingly
|
||||||
#############################################
|
#######################################################################################################
|
||||||
substitutions:
|
substitutions:
|
||||||
devicename: "esp-weatherstation"
|
devicename: "esp-weatherstation"
|
||||||
friendly_name: "Weather Station"
|
friendly_name: "Weather Station"
|
||||||
@@ -109,22 +110,22 @@ substitutions:
|
|||||||
mqtt_topic: "esphome" #main topic for the mqtt server, call it what you like
|
mqtt_topic: "esphome" #main topic for the mqtt server, call it what you like
|
||||||
update_time: 30s #update time for for various temp sensors etc
|
update_time: 30s #update time for for various temp sensors etc
|
||||||
|
|
||||||
#############################################
|
#######################################################################################################
|
||||||
# ESPHome
|
# ESPHome
|
||||||
# https://esphome.io/components/esphome.html
|
# https://esphome.io/components/esphome.html
|
||||||
#############################################
|
#######################################################################################################
|
||||||
esphome:
|
esphome:
|
||||||
name: ${devicename}
|
name: ${devicename}
|
||||||
comment: ${description_comment} #appears on the esphome page in HA
|
comment: ${description_comment} #appears on the esphome page in HA
|
||||||
#on_boot: #Initial Setting stuff
|
#on_boot: #Initial Setting stuff
|
||||||
#priority: -200
|
# priority: -200
|
||||||
#then:
|
# then:
|
||||||
|
|
||||||
#############################################
|
#######################################################################################################
|
||||||
# ESP Platform and Framework
|
# ESP Platform and Framework
|
||||||
# https://esphome.io/components/esp32.html OR
|
# https://esphome.io/components/esp32.html OR
|
||||||
# https://esphome.io/components/esp8266.html
|
# https://esphome.io/components/esp8266.html
|
||||||
#############################################
|
#######################################################################################################
|
||||||
#esp32:
|
#esp32:
|
||||||
# board:
|
# board:
|
||||||
# framework:
|
# framework:
|
||||||
@@ -135,12 +136,12 @@ esphome:
|
|||||||
esp8266:
|
esp8266:
|
||||||
board: d1_mini
|
board: d1_mini
|
||||||
|
|
||||||
#############################################
|
#######################################################################################################
|
||||||
# i2c bus
|
# i2c bus
|
||||||
# https://esphome.io/components/i2c.html
|
# https://esphome.io/components/i2c.html
|
||||||
# Put the relevant pins here, 4/5 are default for ESP8266
|
# Put the relevant pins here, 4/5 are default for ESP8266
|
||||||
# and 21/22 for esp32 (although esp32 can have two buses)
|
# and 21/22 for esp32 (although esp32 can have two buses)
|
||||||
#############################################
|
#######################################################################################################
|
||||||
i2c:
|
i2c:
|
||||||
sda: GPIO4
|
sda: GPIO4
|
||||||
scl: GPIO5
|
scl: GPIO5
|
||||||
@@ -194,11 +195,11 @@ wifi:
|
|||||||
password: ${wifi_pass}
|
password: ${wifi_pass}
|
||||||
#power_save_mode: LIGHT #https://esphome.io/components/wifi.html#wifi-power-save-mode
|
#power_save_mode: LIGHT #https://esphome.io/components/wifi.html#wifi-power-save-mode
|
||||||
#manual_ip: #optional static IP address
|
#manual_ip: #optional static IP address
|
||||||
#static_ip: 192.168.x.x
|
# static_ip: 192.168.x.x
|
||||||
#gateway: 192.168.X.x
|
# gateway: 192.168.X.x
|
||||||
#subnet: 255.255.255.0
|
# subnet: 255.255.255.0
|
||||||
ap: #Details for fallback hotspot in case wifi connection fails https://esphome.io/components/wifi.html#access-point-mode
|
ap: #Details for fallback hotspot in case wifi connection fails https://esphome.io/components/wifi.html#access-point-mode
|
||||||
ssid: $devicename fallback AP
|
ssid: "${devicename} fallback AP"
|
||||||
password: !secret fallback_ap_password
|
password: !secret fallback_ap_password
|
||||||
ap_timeout: 30min #default is 1min
|
ap_timeout: 30min #default is 1min
|
||||||
|
|
||||||
@@ -214,14 +215,14 @@ wifi:
|
|||||||
#web_server:
|
#web_server:
|
||||||
# port: 80
|
# port: 80
|
||||||
# version: 1 #V1 occasionally works better, V2 The nicer page
|
# version: 1 #V1 occasionally works better, V2 The nicer page
|
||||||
# username: !secret web_server_username #probably a good idea to secure it
|
# username: !secret web_server_username #probably a good idea to secure it
|
||||||
# password: !secret web_server_password
|
# password: !secret web_server_password
|
||||||
|
|
||||||
#############################################
|
#######################################################################################################
|
||||||
# MQTT Monitoring
|
# MQTT Monitoring
|
||||||
# https://esphome.io/components/mqtt.html?highlight=mqtt
|
# https://esphome.io/components/mqtt.html?highlight=mqtt
|
||||||
# MUST also have api enabled if you enable MQTT
|
# MUST also have api enabled if you enable MQTT
|
||||||
#############################################
|
#######################################################################################################
|
||||||
mqtt:
|
mqtt:
|
||||||
broker: ${mqtt_server}
|
broker: ${mqtt_server}
|
||||||
topic_prefix: ${mqtt_topic}/${devicename}
|
topic_prefix: ${mqtt_topic}/${devicename}
|
||||||
@@ -230,14 +231,14 @@ mqtt:
|
|||||||
|
|
||||||
#Method to prevent deep sleep using MQTT command
|
#Method to prevent deep sleep using MQTT command
|
||||||
#on_message:
|
#on_message:
|
||||||
# - topic: ${mqtt_topic}/${devicename}/deepsleep
|
# - topic: ${mqtt_topic}/${devicename}/deepsleep
|
||||||
# payload: 'ON'
|
# payload: 'ON'
|
||||||
# then:
|
# then:
|
||||||
# - deep_sleep.prevent: deep_sleep_1
|
# - deep_sleep.prevent: deep_sleep_1
|
||||||
# - topic: ${mqtt_topic}/${devicename}/deepsleep
|
# - topic: ${mqtt_topic}/${devicename}/deepsleep
|
||||||
# payload: 'ON'
|
# payload: 'ON'
|
||||||
# then:
|
# then:
|
||||||
# - deep_sleep.enter: deep_sleep_1
|
# - deep_sleep.enter: deep_sleep_1
|
||||||
|
|
||||||
########################################
|
########################################
|
||||||
# Deep Sleep
|
# Deep Sleep
|
||||||
@@ -248,29 +249,30 @@ mqtt:
|
|||||||
# sleep_duration: 10min
|
# sleep_duration: 10min
|
||||||
# id: deep_sleep_1
|
# id: deep_sleep_1
|
||||||
|
|
||||||
#############################################
|
#######################################################################################################
|
||||||
# General espHome status LED
|
# General espHome status LED
|
||||||
# Not needed, but can be useful to see issues
|
# Not needed, but can be useful to see issues
|
||||||
# https://esphome.io/components/status_led.html
|
# https://esphome.io/components/status_led.html
|
||||||
#############################################
|
# If the LED appears inverted (on when it should be off), set 'inverted: true'.
|
||||||
|
#######################################################################################################
|
||||||
status_led:
|
status_led:
|
||||||
pin:
|
pin:
|
||||||
number: GPIO2 #Wemos ESP32 and ESP8266 Onboard LEDs use GPIO2
|
number: GPIO2 #Wemos ESP32 and ESP8266 Onboard LEDs use GPIO2
|
||||||
inverted: false
|
inverted: false
|
||||||
#ignore_strapping_warning: True #https://esphome.io/guides/faq.html#why-am-i-getting-a-warning-about-strapping-pins
|
#ignore_strapping_warning: True #https://esphome.io/guides/faq.html#why-am-i-getting-a-warning-about-strapping-pins
|
||||||
|
|
||||||
################################
|
##########################################################################################
|
||||||
# 4 Input A->D sensor
|
# 4 Input A->D sensor
|
||||||
# Analog sensor for voltage reading
|
# Analog sensor for voltage reading
|
||||||
# https://esphome.io/components/sensor/ads1115.html
|
# https://esphome.io/components/sensor/ads1115.html
|
||||||
################################
|
##########################################################################################
|
||||||
ads1115:
|
ads1115:
|
||||||
- address: 0x48 #On the ADS1115 pull address pin to VCC for 0x49. Default is 0x48
|
- address: 0x48 #On the ADS1115 pull address pin to VCC for 0x49. Default is 0x48
|
||||||
|
|
||||||
#############################################
|
#######################################################################################################
|
||||||
# Text Sensors
|
# Text Sensors
|
||||||
# https://esphome.io/components/text_sensor/index.html
|
# https://esphome.io/components/text_sensor/index.html
|
||||||
#############################################
|
#######################################################################################################
|
||||||
text_sensor:
|
text_sensor:
|
||||||
- platform: template
|
- platform: template
|
||||||
name: ${friendly_name} Beaufort Wind Scale
|
name: ${friendly_name} Beaufort Wind Scale
|
||||||
@@ -283,12 +285,12 @@ text_sensor:
|
|||||||
id: wind_dir_heading
|
id: wind_dir_heading
|
||||||
update_interval: never
|
update_interval: never
|
||||||
|
|
||||||
#############################################
|
#######################################################################################################
|
||||||
#############################################
|
#######################################################################################################
|
||||||
# General Sensors
|
# General Sensors
|
||||||
# https://esphome.io/components/sensor/index.html
|
# https://esphome.io/components/sensor/index.html
|
||||||
#############################################
|
#######################################################################################################
|
||||||
#############################################
|
#######################################################################################################
|
||||||
sensor:
|
sensor:
|
||||||
|
|
||||||
################################
|
################################
|
||||||
@@ -316,7 +318,7 @@ sensor:
|
|||||||
################################
|
################################
|
||||||
- platform: bmp280
|
- platform: bmp280
|
||||||
address: 0x76
|
address: 0x76
|
||||||
iir_filter: 16X
|
iir_filter: 16x
|
||||||
update_interval: ${update_time}
|
update_interval: ${update_time}
|
||||||
temperature:
|
temperature:
|
||||||
name: ${friendly_name} BMP280 Temperature
|
name: ${friendly_name} BMP280 Temperature
|
||||||
@@ -327,7 +329,7 @@ sensor:
|
|||||||
pressure:
|
pressure:
|
||||||
name: ${friendly_name} BMP280 Pressure
|
name: ${friendly_name} BMP280 Pressure
|
||||||
unit_of_measurement: "hPa"
|
unit_of_measurement: "hPa"
|
||||||
oversampling: 16X
|
oversampling: 16x
|
||||||
#retain: true #MQTT retain useful if sleeping
|
#retain: true #MQTT retain useful if sleeping
|
||||||
|
|
||||||
################################
|
################################
|
||||||
@@ -356,11 +358,14 @@ sensor:
|
|||||||
icon: "mdi:thermometer"
|
icon: "mdi:thermometer"
|
||||||
unit_of_measurement: "°C"
|
unit_of_measurement: "°C"
|
||||||
lambda: |-
|
lambda: |-
|
||||||
return (
|
const float a = id(bmp280_temp).state;
|
||||||
id(bmp280_temp).state
|
const float b = id(dht_temp).state;
|
||||||
+
|
const bool va = !isnan(a);
|
||||||
id(dht_temp).state
|
const bool vb = !isnan(b);
|
||||||
) / 2;
|
if (va && vb) return (a + b) / 2.0f;
|
||||||
|
if (va) return a;
|
||||||
|
if (vb) return b;
|
||||||
|
return NAN;
|
||||||
|
|
||||||
################################
|
################################
|
||||||
# BATTERY VOLTAGE
|
# BATTERY VOLTAGE
|
||||||
@@ -384,9 +389,9 @@ sensor:
|
|||||||
send_every: 1
|
send_every: 1
|
||||||
send_first_at: 1
|
send_first_at: 1
|
||||||
- calibrate_linear:
|
- calibrate_linear:
|
||||||
- 3.321 -> 3.125
|
- 3.321 -> 3.125
|
||||||
- 3.89 -> 3.90
|
- 3.89 -> 3.90
|
||||||
- 4.09 -> 4.14
|
- 4.09 -> 4.14
|
||||||
|
|
||||||
################################
|
################################
|
||||||
# A Calculation of recent Av V
|
# A Calculation of recent Av V
|
||||||
@@ -398,30 +403,30 @@ sensor:
|
|||||||
unit_of_measurement: "V"
|
unit_of_measurement: "V"
|
||||||
accuracy_decimals: 6
|
accuracy_decimals: 6
|
||||||
update_interval: 30s #updates every 30s
|
update_interval: 30s #updates every 30s
|
||||||
lambda: >
|
lambda: |-
|
||||||
return id(battery_voltage).state; //grabs values from the battery_voltage
|
return id(battery_voltage).state; //grabs values from the battery_voltage
|
||||||
filters:
|
filters:
|
||||||
- sliding_window_moving_average:
|
- sliding_window_moving_average:
|
||||||
window_size: 60 #40x 30s = 20 min average period
|
window_size: 60 #60x 30s = 30 min average period
|
||||||
send_every: 1
|
send_every: 1
|
||||||
send_first_at: 1
|
send_first_at: 1
|
||||||
|
|
||||||
################################
|
##########################################################################################
|
||||||
# A Calculation of recent Max V
|
# A Calculation of 10 min Max V
|
||||||
# So we can see if battery is charging/discharging
|
# So we can see if battery is charging/discharging
|
||||||
################################
|
##########################################################################################
|
||||||
- platform: template
|
- platform: template
|
||||||
name: ${friendly_name} Max Battery Voltage over recent period
|
name: ${friendly_name} Max Battery Voltage over recent period
|
||||||
id: battery_voltage_recentmax
|
id: battery_voltage_recentmax
|
||||||
unit_of_measurement: "V"
|
unit_of_measurement: "V"
|
||||||
accuracy_decimals: 6
|
accuracy_decimals: 6
|
||||||
update_interval: 30s #updates every 10s
|
update_interval: 30s #updates every 30s
|
||||||
lambda: >
|
lambda: |-
|
||||||
return id(battery_voltage).state; //grabs values from the battery_voltage
|
return id(battery_voltage).state; //grabs values from the battery_voltage
|
||||||
filters:
|
filters:
|
||||||
- heartbeat: 30s
|
- heartbeat: 30s
|
||||||
- max:
|
- max:
|
||||||
window_size: 60 #60x 30s = 30 min max value
|
window_size: 20 # 20 x 30s = 10 min max value (was 60 = 30 min)
|
||||||
send_every: 1
|
send_every: 1
|
||||||
send_first_at: 1
|
send_first_at: 1
|
||||||
|
|
||||||
@@ -431,8 +436,8 @@ sensor:
|
|||||||
unit_of_measurement: "V"
|
unit_of_measurement: "V"
|
||||||
accuracy_decimals: 5
|
accuracy_decimals: 5
|
||||||
update_interval: 30s #updates every 30s
|
update_interval: 30s #updates every 30s
|
||||||
lambda: >
|
lambda: |-
|
||||||
return {id(battery_voltage_recentmax).state - id(battery_voltage).state}; //recent av voltage - now
|
return id(battery_voltage_recentmax).state - id(battery_voltage).state; //recent max voltage - now
|
||||||
|
|
||||||
- platform: duty_time
|
- platform: duty_time
|
||||||
id: battery_discharge_time
|
id: battery_discharge_time
|
||||||
@@ -444,17 +449,17 @@ sensor:
|
|||||||
filters:
|
filters:
|
||||||
- multiply: 0.01666666
|
- multiply: 0.01666666
|
||||||
- round: 0
|
- round: 0
|
||||||
lambda: >
|
lambda: |-
|
||||||
return { id(battery_voltage).state <= id(battery_voltage_recentav).state };
|
return id(battery_voltage).state <= id(battery_voltage_recentav).state;
|
||||||
|
|
||||||
################################
|
##########################################################################################
|
||||||
# RAIN SENSOR
|
# RAIN SENSOR
|
||||||
# Pulse Meter for measuring rainfall
|
# Pulse Meter for measuring rainfall
|
||||||
# https://esphome.io/components/sensor/pulse_meter.html
|
# https://esphome.io/components/sensor/pulse_meter.html
|
||||||
################################
|
##########################################################################################
|
||||||
# CALIBRATION HINTS
|
# CALIBRATION HINTS
|
||||||
# https://forum.mysensors.org/topic/9594/misol-rain-gauge-tipping-bucket-rain-amount/2
|
# https://forum.mysensors.org/topic/9594/misol-rain-gauge-tipping-bucket-rain-amount/2
|
||||||
################################
|
##########################################################################################
|
||||||
- platform: pulse_meter
|
- platform: pulse_meter
|
||||||
id: rainfall_meter
|
id: rainfall_meter
|
||||||
pin:
|
pin:
|
||||||
@@ -483,20 +488,20 @@ sensor:
|
|||||||
# max_value: 250 #if anything over that, we have debounce issues or are going to die
|
# max_value: 250 #if anything over that, we have debounce issues or are going to die
|
||||||
# ignore_out_of_range: true
|
# ignore_out_of_range: true
|
||||||
|
|
||||||
################################
|
##########################################################################################
|
||||||
# Pulse Meter for measuring wind speed
|
# Pulse Meter for measuring wind speed
|
||||||
# Analog sensor for voltage reading
|
# Analog sensor for voltage reading
|
||||||
# https://esphome.io/components/sensor/pulse_meter.html
|
# https://esphome.io/components/sensor/pulse_meter.html
|
||||||
#
|
#
|
||||||
# This Anenometer https://www.sparkfun.com/datasheets/Sensors/Weather/Weather%20Sensor%20Assembly..pdf
|
# This Anemometer https://www.sparkfun.com/datasheets/Sensors/Weather/Weather%20Sensor%20Assembly..pdf
|
||||||
# The cup-type anemometer measures wind speed by closing a contact
|
# The cup-type anemometer measures wind speed by closing a contact
|
||||||
# as a magnet moves past a reed switch. A wind speed of 1.492mph (2.4km/h) (0.667m/s)
|
# as a magnet moves past a reed switch. A wind speed of 1.492mph (2.4km/h) (0.667m/s)
|
||||||
# causes the switch to close once per second. The reed switch closes twice per revolution.
|
# causes the switch to close once per second. The reed switch closes twice per revolution.
|
||||||
# 1rps = 60rpm = 2.4kmh
|
# 1rps = 60rpm = 2.4kmh
|
||||||
# 1pulse/min = 2.4kmh/60 = 0.04kmh
|
# 1pulse/min = 2.4kmh/60 = 0.04kmh
|
||||||
# Pulse meter component measures the time between rising edges on a pin,
|
# Pulse meter component measures the time between rising edges on a pin,
|
||||||
# for each pulse it outputs the frequency in pulses/min.
|
# for each pulse it outputs the frequency in pulses/min.
|
||||||
################################
|
##########################################################################################
|
||||||
# USEFUL LINKS
|
# USEFUL LINKS
|
||||||
# https://community.home-assistant.io/t/measuring-wind-speed/395693/20
|
# https://community.home-assistant.io/t/measuring-wind-speed/395693/20
|
||||||
# https://community.home-assistant.io/t/measuring-wind-speed/395693/21
|
# https://community.home-assistant.io/t/measuring-wind-speed/395693/21
|
||||||
@@ -530,44 +535,55 @@ sensor:
|
|||||||
max_value: 250 #if anything over that, we have debounce issues or are going to die
|
max_value: 250 #if anything over that, we have debounce issues or are going to die
|
||||||
ignore_out_of_range: true
|
ignore_out_of_range: true
|
||||||
|
|
||||||
################################
|
##########################################################################################
|
||||||
# WIND 10 MINUTE MAX
|
# WIND GUSTS – WMO-style 10-minute gust
|
||||||
# Template for wind gust measurements
|
# Per WMO guidance, "gust" ≈ maximum of 3-second mean wind over the last 10 minutes.
|
||||||
# To measure GUST properly, the World Meteorological Organization
|
# Implementation:
|
||||||
# recommends gust measurement should be wind speed averages over
|
# 1) Compute a 3 s sliding average (updated every 1 s) from wind_meter.
|
||||||
# 3 second periods and report the maximum of these averages
|
# 2) Take the rolling maximum of that 3 s average over the last 10 minutes.
|
||||||
# within a sliding window of the last 10 minutes.
|
##########################################################################################
|
||||||
# This needs a wind speed history buffer of 200 samples
|
- platform: template
|
||||||
# (200 x 3 second average = 10 minutes)
|
id: wind_speed_3s_avg
|
||||||
################################
|
name: ${friendly_name} Wind speed (3s avg)
|
||||||
# Useful links
|
unit_of_measurement: "km/h"
|
||||||
# https://community.home-assistant.io/t/adding-an-old-marine-anemometer-to-ha/337842
|
icon: "mdi:weather-windy"
|
||||||
################################
|
update_interval: 1s
|
||||||
|
lambda: |-
|
||||||
|
return id(wind_meter).state; // sample latest wind speed
|
||||||
|
filters:
|
||||||
|
- filter_out: nan
|
||||||
|
- sliding_window_moving_average:
|
||||||
|
window_size: 3 # 3 samples x 1s = 3 s average
|
||||||
|
send_every: 1
|
||||||
|
send_first_at: 1
|
||||||
|
|
||||||
- platform: template
|
- platform: template
|
||||||
name: ${friendly_name} Windspeed Gust (Max last 10m)
|
name: ${friendly_name} Windspeed Gust (Max last 10m)
|
||||||
unit_of_measurement: "km/h"
|
unit_of_measurement: "km/h"
|
||||||
update_interval: 3s #updates every 2s
|
icon: "mdi:weather-windy"
|
||||||
lambda: >
|
update_interval: 1s
|
||||||
return id(wind_meter).state; //grabs values from the wind_sensor
|
lambda: |-
|
||||||
|
return id(wind_speed_3s_avg).state; # use the 3 s average stream
|
||||||
filters:
|
filters:
|
||||||
- filter_out: nan #in case there is no value
|
- filter_out: nan
|
||||||
- max:
|
- max:
|
||||||
window_size: 200 #10 Min window as 3s update x 200 = 10min
|
window_size: 600 # 600 x 1s = 10 minutes rolling window
|
||||||
send_every: 1 #updates on the 1st 2s check
|
send_every: 1
|
||||||
send_first_at: 1 #on restart, send after first 10s
|
send_first_at: 3 # after first 3 s average becomes meaningful
|
||||||
|
|
||||||
################################
|
##########################################################################################
|
||||||
# BEAUFORT SCALE
|
# BEAUFORT SCALE
|
||||||
# Template for Beaufort for measuring wind speed
|
# Template for Beaufort for measuring wind speed
|
||||||
# The output updates the Text Sensor ID wind_scale
|
# The output updates the Text Sensor ID wind_scale
|
||||||
# https://windy.app/blog/wind-speed-beaufort-scale.html
|
# https://windy.app/blog/wind-speed-beaufort-scale.html
|
||||||
################################
|
##########################################################################################
|
||||||
- platform: template
|
- platform: template
|
||||||
#name: ${friendly_name} Windspeed Scale
|
#name: ${friendly_name} Windspeed Scale
|
||||||
internal: true # no need to show this externally as it is just for calculating Beaufort
|
internal: true # no need to show this externally as it is just for calculating Beaufort
|
||||||
#icon: "mdi:weather-windy"
|
#icon: "mdi:weather-windy"
|
||||||
id: wind_meter_scale
|
id: wind_meter_scale
|
||||||
lambda: return id(wind_meter).state;
|
lambda: |-
|
||||||
|
return id(wind_meter).state;
|
||||||
#unit_of_measurement: "m/s"
|
#unit_of_measurement: "m/s"
|
||||||
update_interval: 5s
|
update_interval: 5s
|
||||||
filters:
|
filters:
|
||||||
@@ -602,7 +618,7 @@ sensor:
|
|||||||
} else if (id(wind_meter_scale).state >= 33) {
|
} else if (id(wind_meter_scale).state >= 33) {
|
||||||
id(wind_scale).publish_state("Hurricane Force");
|
id(wind_scale).publish_state("Hurricane Force");
|
||||||
} else {
|
} else {
|
||||||
id(wind_scale).publish_state({""});
|
id(wind_scale).publish_state("");
|
||||||
}
|
}
|
||||||
|
|
||||||
#############################################
|
#############################################
|
||||||
@@ -614,69 +630,114 @@ sensor:
|
|||||||
# Wind Direction, Analogue on ADS115
|
# Wind Direction, Analogue on ADS115
|
||||||
# Analog sensor for voltage reading
|
# Analog sensor for voltage reading
|
||||||
# https://esphome.io/components/sensor/ads1115.html
|
# https://esphome.io/components/sensor/ads1115.html
|
||||||
################################
|
##########################################################################################
|
||||||
# Using a analog wind direction sensor that has an output
|
# Using a analog wind direction sensor that has an output
|
||||||
# of 0 - 5 V for 360° representing the position of the arrow
|
# of 0 - 5 V for 360° representing the position of the arrow
|
||||||
# from the north pole. This is 5V divided by 8 resistor
|
# from the north pole. This is 5V divided by 8 resistor
|
||||||
# values giving the output voltage value for each azimuth.
|
# values giving the output voltage value for each azimuth.
|
||||||
# We could also report a compass direction.
|
# We could also report a compass direction.
|
||||||
################################
|
##########################################################################################
|
||||||
# Useful Links
|
# Useful Links
|
||||||
# https://community.home-assistant.io/t/davis-wind-direction-equipment-and-esp-home/508764
|
# https://community.home-assistant.io/t/davis-wind-direction-equipment-and-esp-home/508764
|
||||||
################################
|
##########################################################################################
|
||||||
- platform: ads1115
|
- platform: ads1115
|
||||||
multiplexer: 'A0_GND'
|
multiplexer: 'A0_GND'
|
||||||
gain: 4.096
|
gain: 4.096
|
||||||
name: ${friendly_name} Wind Direction Voltage
|
name: ${friendly_name} Wind Direction Voltage
|
||||||
id: wind_direction_voltage
|
id: wind_direction_voltage
|
||||||
update_interval: 2s
|
update_interval: 2s
|
||||||
|
|
||||||
################################################################
|
|
||||||
# CONVERT VOLTAGE TO HEADING
|
|
||||||
# Version used here only has 8 heading points, some have 16.
|
|
||||||
#
|
|
||||||
# Bear R Head Meas'd Max Min
|
|
||||||
# (deg) (Ohms) Volts V+1% V-1%
|
|
||||||
# ---------------------------------------------
|
|
||||||
# 0 33000 N 2.900 2.9290 2.8710
|
|
||||||
# 22.5 6570 NNE
|
|
||||||
# 45 8200 NE 2.108 2.1290 2.0869
|
|
||||||
# 67.5 891 ENE
|
|
||||||
# 90 1000 E 0.584 0.5898 0.5782
|
|
||||||
# 112.5 688 ESE
|
|
||||||
# 135 2200 SE 1.061 1.0716 1.0504
|
|
||||||
# 157.5 1410 SSE
|
|
||||||
# 180 3900 S 1.506 1.5211 1.4910
|
|
||||||
# 202.5 3140 SSW
|
|
||||||
# 225 16000 SW 2.560 2.5856 2.5344
|
|
||||||
# 247.5 14120 WSW
|
|
||||||
# 270 120000 W 3.186 3.2178 3.1541
|
|
||||||
# 292.5 42120 WNW
|
|
||||||
# 315 64900 NW 3.088 3.1188 3.0571
|
|
||||||
# 337.5 21880 NNW
|
|
||||||
################################################################
|
|
||||||
on_value:
|
on_value:
|
||||||
lambda: |-
|
lambda: |-
|
||||||
if (id(wind_direction_voltage).state >= 0.57816 && id(wind_direction_voltage).state <= 0.58984) {
|
const float v = x; // use current reading from on_value
|
||||||
|
if (v >= 0.57816 && v <= 0.58984) {
|
||||||
id(wind_dir_heading).publish_state("E");
|
id(wind_dir_heading).publish_state("E");
|
||||||
} else if (id(wind_direction_voltage).state >= 1.05039 && id(wind_direction_voltage).state <= 1.07161) {
|
} else if (v >= 1.05039 && v <= 1.07161) {
|
||||||
id(wind_dir_heading).publish_state("SE");
|
id(wind_dir_heading).publish_state("SE");
|
||||||
} else if (id(wind_direction_voltage).state >= 1.49094 && id(wind_direction_voltage).state <= 1.52106) {
|
} else if (v >= 1.49094 && v <= 1.52106) {
|
||||||
id(wind_dir_heading).publish_state("S");
|
id(wind_dir_heading).publish_state("S");
|
||||||
} else if (id(wind_direction_voltage).state >= 2.08692 && id(wind_direction_voltage).state <= 2.12908) {
|
} else if (v >= 2.08692 && v <= 2.12908) {
|
||||||
id(wind_dir_heading).publish_state("NE");
|
id(wind_dir_heading).publish_state("NE");
|
||||||
} else if (id(wind_direction_voltage).state >= 2.5344 && id(wind_direction_voltage).state <= 2.5856) {
|
} else if (v >= 2.53440 && v <= 2.58560) {
|
||||||
id(wind_dir_heading).publish_state("SW");
|
id(wind_dir_heading).publish_state("SW");
|
||||||
} else if (id(wind_direction_voltage).state >= 2.871 && id(wind_direction_voltage).state <= 2.929) {
|
} else if (v >= 2.87100 && v <= 2.92900) {
|
||||||
id(wind_dir_heading).publish_state("N");
|
id(wind_dir_heading).publish_state("N");
|
||||||
} else if (id(wind_direction_voltage).state >= 3.05712 && id(wind_direction_voltage).state <= 3.11888) {
|
} else if (v >= 3.05712 && v <= 3.11888) {
|
||||||
id(wind_dir_heading).publish_state("NW");
|
id(wind_dir_heading).publish_state("NW");
|
||||||
} else if (id(wind_direction_voltage).state >= 3.15414 && id(wind_direction_voltage).state <= 3.21786) {
|
} else if (v >= 3.15414 && v <= 3.21786) {
|
||||||
id(wind_dir_heading).publish_state("W");
|
id(wind_dir_heading).publish_state("W");
|
||||||
} else {
|
} else {
|
||||||
id(wind_dir_heading).publish_state({});
|
id(wind_dir_heading).publish_state("");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
##########################################################################################
|
||||||
|
# BME280 temp/humidity/pressure (Note, BME and BMP Are different...)
|
||||||
|
# https://esphome.io/cookbook/bme280_environment.html
|
||||||
|
##########################################################################################
|
||||||
|
#- platform: bme280_i2c
|
||||||
|
# address: 0x76
|
||||||
|
# update_interval: ${update_time}
|
||||||
|
# temperature:
|
||||||
|
# name: ${friendly_name} BME280 Temp
|
||||||
|
# accuracy_decimals: 1
|
||||||
|
# oversampling: 2x
|
||||||
|
# unit_of_measurement: "°C"
|
||||||
|
# #retain: true #MQTT retain useful if sleeping
|
||||||
|
# pressure:
|
||||||
|
# name: ${friendly_name} BME280 Pressure
|
||||||
|
# oversampling: 2x
|
||||||
|
# unit_of_measurement: "hPa"
|
||||||
|
# #retain: true #MQTT retain useful if sleeping
|
||||||
|
# humidity:
|
||||||
|
# name: ${friendly_name} BME280 Humidity
|
||||||
|
# accuracy_decimals: 1
|
||||||
|
# oversampling: 2x
|
||||||
|
# unit_of_measurement: "%"
|
||||||
|
# #retain: true #MQTT retain useful if sleeping
|
||||||
|
|
||||||
|
|
||||||
|
##########################################################################################
|
||||||
|
# TSL2561 ambient light sensor
|
||||||
|
# https://esphome.io/components/sensor/tsl2561.html
|
||||||
|
##########################################################################################
|
||||||
|
#- platform: tsl2561
|
||||||
|
# name: ${friendly_name} Ambient Light
|
||||||
|
# address: 0x39
|
||||||
|
# update_interval: 60s
|
||||||
|
|
||||||
|
##########################################################################################
|
||||||
|
# LTR390 Ambient Light and UV Sensor
|
||||||
|
# https://esphome.io/components/sensor/ltr390.html
|
||||||
|
##########################################################################################
|
||||||
|
#- platform: ltr390
|
||||||
|
# address: 0x35
|
||||||
|
# uv_index:
|
||||||
|
# name: ${friendly_name} 390 UV Index
|
||||||
|
# uv:
|
||||||
|
# name: ${friendly_name} 390 UV Sensor Counts
|
||||||
|
# light:
|
||||||
|
# name: ${friendly_name} 390 Light
|
||||||
|
# ambient_light:
|
||||||
|
# name: ${friendly_name} 390 UV Sensor Counts
|
||||||
|
# update_interval: 60s
|
||||||
|
|
||||||
|
##########################################################################################
|
||||||
|
# PMSX003 Particulate sensor
|
||||||
|
# NEEDS UART SET FIRST
|
||||||
|
# https://esphome.io/components/sensor/pmsx003.html
|
||||||
|
##########################################################################################
|
||||||
|
#- platform: pmsx003
|
||||||
|
# type: PMSX003
|
||||||
|
# pm_1_0:
|
||||||
|
# name: ${friendly_name} Particulate Matter <1.0µm
|
||||||
|
# pm_2_5:
|
||||||
|
# name: ${friendly_name} <2.5µm
|
||||||
|
# pm_10_0:
|
||||||
|
# name: ${friendly_name} <10.0µm Concentration
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# filters:
|
# filters:
|
||||||
# lambda: |-
|
# lambda: |-
|
||||||
@@ -797,88 +858,6 @@ sensor:
|
|||||||
# return {};
|
# return {};
|
||||||
|
|
||||||
|
|
||||||
# - platform: template
|
|
||||||
# name: "Roof Wind Run daily"
|
|
||||||
# icon: "mdi:weather-windy"
|
|
||||||
# unit_of_measurement: "km"
|
|
||||||
# accuracy_decimals: 3
|
|
||||||
# update_interval: 30s
|
|
||||||
# internal: true
|
|
||||||
# lambda: |-
|
|
||||||
# return id(g_wind_run_count)*0.000666982;
|
|
||||||
# id: id_wind_run_daily_g
|
|
||||||
|
|
||||||
# - platform: template
|
|
||||||
# name: "Roof Wind gust 10s"
|
|
||||||
# unit_of_measurement: 'km/h'
|
|
||||||
# update_interval: 10s
|
|
||||||
# id: id_wind_gust_10s
|
|
||||||
# lambda: return id(g_WindGust_10s);
|
|
||||||
#
|
|
||||||
# - platform: template
|
|
||||||
# name: "Roof Wind gust 60s"
|
|
||||||
# unit_of_measurement: 'km/h'
|
|
||||||
# state_class: "measurement"
|
|
||||||
# update_interval: 60s
|
|
||||||
# id: id_wind_gust_60s
|
|
||||||
# lambda: return id(g_WindGust_60s);
|
|
||||||
|
|
||||||
# - platform: template
|
|
||||||
# name: "Roof Wind gust 10min"
|
|
||||||
# unit_of_measurement: 'km/h'
|
|
||||||
# state_class: "measurement"
|
|
||||||
# update_interval: 10min
|
|
||||||
# id: id_wind_gust_10min
|
|
||||||
# lambda: return id(g_WindGust_10min);
|
|
||||||
|
|
||||||
# - platform: template
|
|
||||||
# name: "Roof Wind speed avg 1s"
|
|
||||||
# unit_of_measurement: 'km/h'
|
|
||||||
# update_interval: 333ms
|
|
||||||
# lambda: |-
|
|
||||||
# return id(id_wind_speed).state;
|
|
||||||
# filters:
|
|
||||||
# throttle_average: 1s
|
|
||||||
# id: id_wind_speed_avg_1s
|
|
||||||
#
|
|
||||||
# - platform: template
|
|
||||||
# name: "Roof Wind speed avg 10s"
|
|
||||||
# unit_of_measurement: 'km/h'
|
|
||||||
# update_interval: 333ms
|
|
||||||
# lambda: |-
|
|
||||||
# return id(id_wind_speed).state;
|
|
||||||
# filters:
|
|
||||||
# throttle_average: 10s
|
|
||||||
# id: id_wind_speed_avg_10s
|
|
||||||
#
|
|
||||||
# - platform: template
|
|
||||||
# name: "Roof Wind Speed Min 10s"
|
|
||||||
# unit_of_measurement: 'km/h'
|
|
||||||
# id: id_wind_speed_min_10s
|
|
||||||
#
|
|
||||||
# - platform: template
|
|
||||||
# name: "Roof Wind speed avg 1min"
|
|
||||||
# unit_of_measurement: 'km/h'
|
|
||||||
# state_class: "measurement"
|
|
||||||
# update_interval: '1s'
|
|
||||||
# lambda: |-
|
|
||||||
# return id(id_wind_speed).state;
|
|
||||||
# filters:
|
|
||||||
# - throttle_average: 1min
|
|
||||||
# id: id_wind_speed_avg_1min
|
|
||||||
#
|
|
||||||
# - platform: template
|
|
||||||
# name: "Roof Wind speed avg 10min"
|
|
||||||
# unit_of_measurement: 'km/h'
|
|
||||||
# state_class: "measurement"
|
|
||||||
# update_interval: 1s
|
|
||||||
# lambda: |-
|
|
||||||
# return id(id_wind_speed).state;
|
|
||||||
# filters:
|
|
||||||
# - throttle_average: 10min
|
|
||||||
# id: id_wind_speed_avg_10min
|
|
||||||
|
|
||||||
|
|
||||||
# - platform: pulse_meter
|
# - platform: pulse_meter
|
||||||
# pin:
|
# pin:
|
||||||
# number: GPIO13 #This is Pin D7 on the D1 Mini ESP8266
|
# number: GPIO13 #This is Pin D7 on the D1 Mini ESP8266
|
||||||
@@ -979,69 +958,3 @@ sensor:
|
|||||||
# type: float
|
# type: float
|
||||||
# restore_value: no
|
# restore_value: no
|
||||||
# initial_value: '0.0'
|
# initial_value: '0.0'
|
||||||
|
|
||||||
|
|
||||||
################################
|
|
||||||
# BME280 temp/humidity/pressure (Note, BME and BMP Are different...)
|
|
||||||
# https://esphome.io/cookbook/bme280_environment.html
|
|
||||||
################################
|
|
||||||
#- platform: bme280_i2c
|
|
||||||
# address: 0x76
|
|
||||||
# update_interval: ${update_time}
|
|
||||||
# temperature:
|
|
||||||
# name: ${friendly_name} BME280 Temp
|
|
||||||
# accuracy_decimals: 1
|
|
||||||
# oversampling: 2x
|
|
||||||
# unit_of_measurement: "°C"
|
|
||||||
# #retain: true #MQTT retain useful if sleeping
|
|
||||||
# pressure:
|
|
||||||
# name: ${friendly_name} BME280 Pressure
|
|
||||||
# oversampling: 2x
|
|
||||||
# unit_of_measurement: "hPa"
|
|
||||||
# #retain: true #MQTT retain useful if sleeping
|
|
||||||
# humidity:
|
|
||||||
# name: ${friendly_name} BME280 Humidity
|
|
||||||
# accuracy_decimals: 1
|
|
||||||
# oversampling: 2x
|
|
||||||
# unit_of_measurement: "%"
|
|
||||||
# #retain: true #MQTT retain useful if sleeping
|
|
||||||
|
|
||||||
|
|
||||||
################################
|
|
||||||
# TSL2561 ambient light sensor
|
|
||||||
# https://esphome.io/components/sensor/tsl2561.html
|
|
||||||
################################
|
|
||||||
#- platform: tsl2561
|
|
||||||
# name: ${friendly_name} Ambient Light
|
|
||||||
# address: 0x39
|
|
||||||
# update_interval: 60s
|
|
||||||
|
|
||||||
################################
|
|
||||||
# LTR390 Ambient Light and UV Sensor
|
|
||||||
# https://esphome.io/components/sensor/ltr390.html
|
|
||||||
################################
|
|
||||||
#- platform: ltr390
|
|
||||||
# address: 0x35
|
|
||||||
# uv_index:
|
|
||||||
# name: ${friendly_name} 390 UV Index
|
|
||||||
# uv:
|
|
||||||
# name: ${friendly_name} 390 UV Sensor Counts
|
|
||||||
# light:
|
|
||||||
# name: ${friendly_name} 390 Light
|
|
||||||
# ambient_light:
|
|
||||||
# name: ${friendly_name} 390 UV Sensor Counts
|
|
||||||
# update_interval: 60s
|
|
||||||
|
|
||||||
################################
|
|
||||||
# PMSX003 Particulate sensor
|
|
||||||
# NEEDS UART SET FIRST
|
|
||||||
# https://esphome.io/components/sensor/pmsx003.html
|
|
||||||
################################
|
|
||||||
#- platform: pmsx003
|
|
||||||
# type: PMSX003
|
|
||||||
# pm_1_0:
|
|
||||||
# name: ${friendly_name} Particulate Matter <1.0µm
|
|
||||||
# pm_2_5:
|
|
||||||
# name: ${friendly_name} <2.5µm
|
|
||||||
# pm_10_0:
|
|
||||||
# name: ${friendly_name} <10.0µm Concentration
|
|
||||||
@@ -2,13 +2,13 @@ group:
|
|||||||
simulation_lights_all:
|
simulation_lights_all:
|
||||||
name: Simulation Lights (All)
|
name: Simulation Lights (All)
|
||||||
entities:
|
entities:
|
||||||
|
- group.downstairs_flat_lights # ← now valid here
|
||||||
- switch.esp_centralstairs_top_relay_2_stair_footer_lights
|
- switch.esp_centralstairs_top_relay_2_stair_footer_lights
|
||||||
- switch.esp_centralstairs_bottom_relay_1_main_stair_lights_lower
|
- switch.esp_centralstairs_bottom_relay_1_main_stair_lights_lower
|
||||||
- switch.esp_entrancebathrmlights_relay_1_main_lights
|
- switch.esp_entrancebathrmlights_relay_1_main_lights
|
||||||
- group.downstairs_flat_lights # ← now valid here
|
- switch.esp_entrancelightswitch_relay_1_entrance_lights
|
||||||
- switch.tasmo_ks811d_1242_entrance_a
|
- switch.esp_mainkitchenlights_relay_1_main_lights
|
||||||
- switch.tasmo_ks811d_6110_kitchen_a
|
- switch.esp_mainkitchenlights_relay_2_benchtop_lights
|
||||||
- switch.tasmo_ks811d_6110_kitchen_b
|
|
||||||
- switch.main_hallway_lightswitch_tasmo_ks811s_2940_hallway_1a
|
- switch.main_hallway_lightswitch_tasmo_ks811s_2940_hallway_1a
|
||||||
- switch.tasmo_ks811t_0702_lounge_3a
|
- switch.tasmo_ks811t_0702_lounge_3a
|
||||||
- switch.tasmo_ks811t_0702_lounge_3b
|
- switch.tasmo_ks811t_0702_lounge_3b
|
||||||
|
|||||||
Reference in New Issue
Block a user