esphome device updates

This commit is contained in:
root
2025-09-20 15:18:46 +12:00
parent a27b79fbd9
commit 1e1d0571d9
32 changed files with 3756 additions and 1188 deletions
+2 -2
View File
@@ -1,7 +1,7 @@
#############################################
##########################################################################################
# Enable the Home Assistant API
# https://esphome.io/components/api.html
#############################################
##########################################################################################
api:
encryption:
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 youd 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; theyre 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.
+1
View File
@@ -22,3 +22,4 @@ mqtt:
id: mqtt_client
reboot_timeout: 0s # same for MQTT
log_topic: null # <— stops MQTT log streaming (big JSON payloads)
keepalive: 60s
+39 -39
View File
@@ -1,8 +1,8 @@
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
@@ -30,7 +30,7 @@ substitutions:
base_interval_hours: "23" # Base interval in hours
random_offset_max_minutes: "59" # Max random offset in minutes
#############################################
###########################################################################################
# Common Wifi Settings
# https://esphome.io/components/wifi.html
#
@@ -38,13 +38,13 @@ substitutions:
# 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
power_save_mode: none # 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}
@@ -63,60 +63,60 @@ wifi:
#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
#- platform: web_server # Uncomment if you want to be able to do OTA with the web interface
#############################################
###########################################################################################
# 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
# This ensure that the device is connected to the best AP
#############################################
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
##########################################################################################
#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
-126
View File
@@ -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
View File
@@ -1 +0,0 @@
<<: !include ../secrets.yaml
+73 -86
View File
@@ -1,26 +1,58 @@
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:
- platform: uptime # Uptime for this device in seconds
name: "Uptime (s):"
update_interval: ${local_update_interval}
id: uptime_sensor
entity_category: "diagnostic"
- platform: wifi_signal # Wifi Strength
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: ""
# - platform: uptime # Uptime for this device in seconds
# name: "Uptime (s):"
# update_interval: ${local_update_interval}
# id: uptime_sensor
# entity_category: "diagnostic"
# - platform: wifi_signal # Wifi Strength
# 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: ""
# 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 youd 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
@@ -29,11 +61,11 @@ sensor:
# id: vcc_voltage_sensor
# entity_category: diagnostic
binary_sensor:
- platform: status
name: "Network Status"
icon: mdi:check-network-outline
entity_category: diagnostic
#binary_sensor:
# - platform: status
# name: "Network Status"
# icon: mdi:check-network-outline
# entity_category: diagnostic
##########################################################################################
# Text Sensors
@@ -46,6 +78,7 @@ text_sensor:
- platform: version
name: "Version:"
entity_category: "diagnostic"
- platform: wifi_info
ip_address:
icon: mdi:ip-network
@@ -59,37 +92,12 @@ text_sensor:
name: "MAC Address:"
icon: mdi:network-pos
entity_category: diagnostic
- platform: uptime # Uptime for this device human readable
name: "Uptime:"
icon: mdi:clock-start
update_interval: ${local_update_interval}
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
@@ -118,44 +126,23 @@ text_sensor:
# }
# 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
# 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
#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
+12
View File
@@ -1,3 +1,6 @@
substitutions:
local_update_interval: 30s
##########################################################################################
# GENERAL COMMON SENSORS
# https://esphome.io/components/sensor/
@@ -16,6 +19,15 @@ sensor:
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"
+7 -7
View File
@@ -1,8 +1,8 @@
##############################################
############################################################################################
# SPECIFIC DEVICE VARIABLE SUBSTITUTIONS
# If NOT using a secrets file, just replace
# these with the values (in quotes)
#############################################
###########################################################################################
substitutions:
timezone: "Pacific/Auckland"
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_3: !secret ntp_server_3
#############################################
###########################################################################################
# Real time clock time source for ESPHome
# If it's invalid, we fall back to an internal clock
# https://esphome.io/components/time/index.html
# https://esphome.io/components/time/sntp
#############################################
###########################################################################################
time:
- platform: sntp
id: sntp_time
@@ -53,14 +53,14 @@ time:
text_sensor:
# Creates a sensor showing when the device was last restarted
- platform: template
name: "Last Restart"
name: "Time: Last Restart"
id: device_last_restart
icon: mdi:clock
entity_category: diagnostic
#device_class: timestamp
- platform: template
name: "Internal Time"
name: "Time: Internal Time"
id: time_text
update_interval: "1min"
entity_category: diagnostic
@@ -69,7 +69,7 @@ text_sensor:
return { time_text };
- platform: template
name: "Time Sync Status"
name: "Time: Sync Status"
id: time_sync
update_interval: "1min"
entity_category: diagnostic
+89 -23
View File
@@ -17,47 +17,69 @@
# If NOT using a secrets file, just replace these with the passwords etc (in quotes)
#############################################
substitutions:
device_name: "esp-3dprinterpow" # Reference name for the device in the system.
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
entity_prefix: "Load" # Simple device name where we want to prefix a sensor or switch, eg "Load" Current.
# Device Naming
device_name: "esp-3dprinterpow"
friendly_name: "3D Printer Power"
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.
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-3dprinterpow_ip
device_area: "Bedroom 3" # Allows ESP device to be automatically linked to an 'Area' in Home Assistant.
# Project Naming
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
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
# https://esphome.io/components/packages.html
##########################################################################################
packages:
#### WIFI, Network (Static/DHCP/IPV6 etc), Fallback AP, Safemode ####
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}"
#### HOME ASSISTANT API (choose encryption or no encryption options) ####
common_api: !include
file: common/api_common.yaml
#file: common/api_common_noencryption.yaml
vars:
local_api_key: "${api_key}"
#common_webportal: !include
# file: common/webportal_common.yaml
#### MQTT ####
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}"
#### WEB PORTAL ####
#common_webportal: !include common/webportal_common.yaml
#### SNTP (Only use if you want/need accurate timeclocks) ####
common_sntp: !include common/sntp_common.yaml
#### 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
@@ -71,10 +93,10 @@ esphome:
project:
name: "${project_name}"
version: "${project_version}"
#on_boot:
#priority: 200
#then:
# - switch.turn_on: "virtual_button"
on_boot:
priority: -10
then:
- script.execute: publish_state_burst
#############################################
# ESP Platform and Framework
@@ -95,6 +117,24 @@ logger:
#esp8266_store_log_strings_in_flash: false
#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
# https://esphome.io/components/status_led.html
@@ -164,10 +204,22 @@ switch:
turn_off_action:
- switch.turn_off: relay
- light.turn_off: led
- platform: gpio
id: relay
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:
- platform: esp8266_pwm
@@ -180,4 +232,18 @@ light:
- platform: monochromatic
name: Status 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:
- platform: esphome
password: ${ota_pass}
- platform: web_server # Uncomment if you want to be able to do OTA with the web interface
#############################################
# Safe Mode
+180
View File
@@ -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
+56 -2
View File
@@ -153,6 +153,60 @@ logger:
#esp8266_store_log_strings_in_flash: false
#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
# This adds device-specific MQTT command triggers to the common MQTT configuration.
@@ -371,11 +425,11 @@ fan:
# whenever the fan goes off→on (e.g. via HAs Fan switch):
on_turn_on:
- lambda: |-
// bump 01 if we were off
// bump 0->1 if we were off
if (id(speed_value) == 0) id(speed_value) = 1;
- fan.turn_on:
id: ifan02_fan
speed: !lambda 'return id(speed_value);'
- mqtt.publish:
topic: "${mqtt_local_status_topic}/speed/state"
payload: !lambda 'return to_string(id(speed_value));'
payload: !lambda 'return to_string(id(speed_value));'
+321 -133
View File
@@ -1,103 +1,132 @@
##########################################################################################
##########################################################################################
# Title: BREAKFAST BAR LEDS (Lounge)
# Hardware: Magichome inline single channel dimmer (ESP8266)
# Title: BREAKFAST BAR LEDS (Lounge)
# 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)
#
# ------------------------------------------
# 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)
# ------------------------------------------
# GPIO02 Blue LED (used for ESPHome status) - I think... can't see the board
# 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
# If NOT using a secrets file, just replace these with the passwords etc (in quotes)
##########################################################################################
substitutions:
# Device Naming
device_name: "esp-breakfastbarleds"
device_name: "esp-breakfastbarleds" # yaml file should be device_name.yaml
friendly_name: "Breakfast Bar LEDs"
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.
# Project Naming
project_name: "Magichome ESP8266" # Project details
project_version: "v1.0" # Project version denotes release of the YAML file, allowing checking of deployed vs latest version
# Passwords & Secrets
project_name: "Magichome Single Colour V1.1 LED Controller.Generic ESP8266" # Project details. Manufacturer before the dot, device after the dot.
project_version: "v2.0" # Project version denotes release of the YAML file, allowing checking of deployed vs latest version
# Passwords & Secrets (Unfortunately, you can't use substitutions inside secret names)
api_key: !secret esp-api_key
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_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_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_OFF: "OFF"
# Device Specific Settings
log_level: "NONE" # Define logging level: NONE, ERROR, WARN, INFO, DEBUG (default), VERBOSE, VERY_VERBOSE
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
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
max_on_default_hours: "12" # The maximum time the LEDs will be on, in case they get left on. 0 = no automatic turn-off
log_level: "NONE" # Define logging level: NONE, ERROR, WARN, INFO, DEBUG (default), VERBOSE, VERY_VERBOSE
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
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
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
# https://esphome.io/components/packages.html
##########################################################################################
packages:
#### WIFI, Network (Static/DHCP/IPV6 etc), Fallback AP, Safemode ####
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}"
#### HOME ASSISTANT API (choose encryption or no encryption options) ####
common_api: !include
file: common/api_common.yaml
#file: common/api_common_noencryption.yaml
vars:
local_api_key: "${api_key}"
#common_webportal: !include
# file: common/webportal_common.yaml
#### MQTT ####
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
#file: common/sensors_common_lite.yaml
vars:
local_friendly_name: "${friendly_name}"
local_update_interval: "${update_interval}"
#### WEB PORTAL ####
#common_webportal: !include common/webportal_common.yaml
#### SNTP (Only use if you want/need accurate timeclocks) ####
#common_sntp: !include common/sntp_common.yaml
#### 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 CORE CONFIGURATION
@@ -109,7 +138,8 @@ esphome:
comment: "${description_comment}"
area: "${device_area}"
on_boot:
priority: 600
# Run later so everything is initialised before we touch states.
priority: 200
then:
# Keep the HA dropdown in sync with the stored mode
- lambda: |-
@@ -127,31 +157,12 @@ esphome:
- lambda: 'id(ramp_switch_target_on) = true;'
- 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:
condition:
lambda: 'return id(restart_mode) == 1;'
then:
- lambda: |-
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
- script.execute: deferred_restore_brightness
# Mode 2: Remain Off (no blip; stays off)
- if:
@@ -165,15 +176,19 @@ esphome:
id: mosfet_leds
transition_length: 0s
platformio_options:
build_unflags:
- -flto
build_flags:
- -fno-lto
- -Wl,--gc-sections
- -ffunction-sections
- -fdata-sections
- -DNDEBUG
# Boot complete: allow OFF handlers again
- lambda: 'id(booting) = false;'
# Only if you want to play with build flags...
# platformio_options:
# build_unflags:
# - -flto
# build_flags:
# - -fno-lto
# - -Wl,--gc-sections
# - -ffunction-sections
# - -fdata-sections
# - -DNDEBUG
##########################################################################################
# ESP PLATFORM AND FRAMEWORK
@@ -185,10 +200,10 @@ esp8266:
restore_from_flash: true # restore some values on reboot
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:
flash_write_interval: 5min
flash_write_interval: 5sec # enough time to update values for reboots, but not enough to wear flash
##########################################################################################
# GLOBAL VARIABLES
@@ -209,7 +224,8 @@ globals:
- id: max_on_hours
type: int
restore_value: true
initial_value: '${max_on_default_hours}'
initial_value: "${max_on_default_hours}"
# Default Fading Up Time (Selectable and will be retained)
- id: ramp_up_ms # fade-in when turned ON
type: int
@@ -273,6 +289,16 @@ globals:
type: float
restore_value: false
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
@@ -283,6 +309,15 @@ 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, Serial control)
#########################################################################################
# STATUS LED
# https://esphome.io/components/status_led.html
#########################################################################################
status_led:
pin:
number: ${device_status_led}
inverted: true
##########################################################################################
# MQTT COMMANDS
# This adds device-specific MQTT command triggers to the common MQTT configuration.
@@ -300,15 +335,6 @@ mqtt:
then:
- switch.turn_off: mosfet_ramp_switch
#########################################################################################
# STATUS LED
# https://esphome.io/components/status_led.html
#########################################################################################
status_led:
pin:
number: GPIO2
inverted: true
##########################################################################################
# SWITCH COMPONENT
# https://esphome.io/components/switch/
@@ -367,12 +393,18 @@ button:
name: "${friendly_name} Fade Stop"
icon: mdi:pause
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_off_script
# Cancel the light's transition by commanding the current level with 0 ms,
# but DO NOT change the ramp switch state/flag.
- script.stop: led_flash_up
- script.stop: led_flash_down
- output.turn_off: green_led_out
- 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;
if (cv.is_on()) {
auto call = id(mosfet_leds).make_call();
@@ -382,6 +414,14 @@ button:
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
# https://esphome.io/components/select/index.html
@@ -411,7 +451,37 @@ select:
# BINARY SENSORS
# 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
@@ -431,16 +501,22 @@ sensor:
on_value:
then:
- lambda: |-
// Remember latest actual output (0..100) for "Restore Brightness"
id(last_brightness_pct) = x;
if (x > 0.5f) {
// Only persist when not ramping; always track a volatile copy
if (!id(is_ramping)) id(last_nonzero_brightness_pct) = x;
id(last_nonzero_tmp) = x;
const auto &cv_now = id(mosfet_leds).current_values;
// Only persist exact last brightness while actually ON,
// and not during the OFF ramp's brief floor-kick.
if (cv_now.is_on()) {
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 (!id(suppress_slider_sync)) {
float actual = x; // actual %
float actual = x;
float minp = (float) id(min_brightness_pct);
float maxp = (float) id(max_brightness_pct);
if (maxp <= minp) maxp = minp + 1.0f;
@@ -464,7 +540,7 @@ sensor:
lambda: |-
const auto &cv = id(mosfet_leds).current_values;
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
float pwm = powf(lin, gamma); // approx PWM duty after gamma
if (pwm < 0.0f) pwm = 0.0f;
@@ -481,8 +557,13 @@ sensor:
output:
- platform: esp8266_pwm
id: mosfet_pwm
pin: GPIO12
frequency: 1000 Hz # higher frequency helps reduce camera banding while filming
pin: ${device_mosfet_out}
frequency: ${pwm_frequency}
- platform: gpio
id: green_led_out # Green LED
pin:
number: ${device_fading_led}
inverted: false
##########################################################################################
# LIGHT COMPONENT
@@ -493,29 +574,48 @@ light:
id: mosfet_leds
name: "${friendly_name}"
output: mosfet_pwm
restore_mode: ALWAYS_OFF
restore_mode: RESTORE_DEFAULT_OFF
default_transition_length: 2s
icon: mdi:led-strip-variant
gamma_correct: "${led_gamma}"
# ON: publish state, track intent, arm watchdog if configured
on_turn_on:
- mqtt.publish:
topic: "${mqtt_local_status_topic}/light/state"
payload: "${mqtt_local_device_command_ON}"
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
- if:
condition:
lambda: 'return id(max_on_hours) > 0;'
then:
- 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:
- mqtt.publish:
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
- if:
condition:
lambda: 'return !id(booting);'
then:
- mqtt.publish:
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:
- lambda: |-
const float cap = id(max_brightness_pct) / 100.0f;
@@ -608,6 +708,8 @@ number:
- lambda: |-
id(ramp_switch_target_on) = false;
id(led_output_set_pct).publish_state(0);
id(last_brightness_pct) = 0.0f; // persist OFF immediately
id(last_set_pos) = 0;
- delay: 400ms
- lambda: 'id(suppress_slider_sync) = false;'
else:
@@ -631,7 +733,19 @@ number:
if (out_pct > maxp) out_pct = maxp;
return out_pct / 100.0f;
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
- 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.
##########################################################################################
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.
- id: ramp_on_script
mode: restart
then:
- lambda: 'id(is_ramping) = true;' # mark ramping
- lambda: 'id(is_ramping) = true;'
- 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".
- if:
condition:
@@ -697,18 +842,14 @@ script:
const auto &cv = id(mosfet_leds).current_values;
const float floor = id(min_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;
// 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) {
id(last_ramp_ms) = id(ramp_up_ms);
return (uint32_t) id(last_ramp_ms);
}
if (curr > cap) curr = cap;
if (curr < floor) curr = floor;
float frac = (cap - curr) / (cap - floor);
@@ -717,20 +858,25 @@ script:
id(last_ramp_ms) = (int) (id(ramp_up_ms) * frac);
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: |-
id(is_ramping) = false; // ramp complete
id(is_ramping) = false;
const auto &cv = id(mosfet_leds).current_values;
if (cv.is_on()) {
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)
- id: ramp_to_target_script
mode: restart
then:
- lambda: 'id(is_ramping) = true;' # mark ramping
- lambda: 'id(is_ramping) = true;'
- script.stop: ramp_off_script
- script.stop: led_flash_down
- script.execute: led_flash_up
# Ensure we start at the floor cleanly.
- if:
condition:
@@ -757,12 +903,9 @@ script:
float cap = id(restore_target_pct) / 100.0f;
const float maxp = id(max_brightness_pct) / 100.0f;
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;
// Coming from OFF/below floor? We just did the "floor kick" above — use full ramp_up_ms.
if (curr + 0.0005f < floor) {
id(last_ramp_ms) = id(ramp_up_ms);
return (uint32_t) id(last_ramp_ms);
@@ -776,20 +919,27 @@ script:
id(last_ramp_ms) = (int) (id(ramp_up_ms) * frac);
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: |-
id(is_ramping) = false; // ramp complete
id(is_ramping) = false;
const auto &cv = id(mosfet_leds).current_values;
if (cv.is_on()) {
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
- id: ramp_off_script
mode: restart
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: led_flash_up
- script.execute: led_flash_down
- light.turn_on:
id: mosfet_leds
brightness: !lambda 'return id(min_brightness_pct) / 100.0f;'
@@ -799,7 +949,7 @@ script:
float curr = cv.get_brightness();
if (curr < floor) curr = 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;
if (frac < 0.0f) frac = 0.0f;
if (frac > 1.0f) frac = 1.0f;
@@ -810,12 +960,15 @@ script:
id: mosfet_leds
transition_length: 150ms
- delay: 150ms
- script.stop: led_flash_down
- output.turn_off: green_led_out
- lambda: |-
id(is_ramping) = false; // ramp complete
auto call = id(mosfet_leds).make_call();
call.set_state(false);
call.set_brightness(id(max_brightness_pct) / 100.0f);
call.perform();
id(is_ramping) = false;
id(ramping_for_off) = false;
id(last_brightness_pct) = 0.0f; // persist OFF for restore path
id(last_set_pos) = 0;
id(led_output_set_pct).publish_state(0);
- id: max_on_watchdog
mode: restart
then:
@@ -833,3 +986,38 @@ script:
id(mosfet_ramp_switch).publish_state(false);
- script.stop: ramp_on_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
+237 -150
View File
@@ -1,10 +1,9 @@
##########################################################################################
##########################################################################################
# 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
#
# 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),
# 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
@@ -14,115 +13,131 @@
# v1.0 - 2025-08-17 First setup (and replacement of Tasmota)
#
# ------------------------------------------
# DEVICE GPIO (Sinilink XY-WFMS)
# ------------------------------------------
# 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)
# 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).
# 3. Global setting for MAX % output to extend LED life.
# 4. 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
# resets on this 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.
# 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 behaviour:
# 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 (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.
# 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).
# 11. Useful 3D-printed case: https://cults3d.com/en/3d-model/tool/snapfit-enclosure-for-esp8266-sinilink-xy-wfms-5v-36v-mosfet-switch-module
# 12. Exposed in Home Assistant/MQTT:
# - Startup action
# 11. Exposed in Home Assistant/MQTT:
# - Startup action (On,Off,Restore)
# - Fade Up / Fade Down / Fade Stop buttons
# - Fade Up/Down switch
# - Fade Up/Down toggle 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)
# - 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: 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
# If NOT using a secrets file, just replace these with the passwords etc (in quotes)
##########################################################################################
substitutions:
# Device Naming
device_name: "esp-downstairskitchleds"
device_name: "esp-downstairskitchleds" # yaml file should be device_name.yaml
friendly_name: "Downstairs Kitchen LEDs"
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.
# Project Naming
project_name: "Sinilink.XY-WFMS" # Project details
project_version: "v1.5" # Project version denotes release of the YAML file, allowing checking of deployed vs latest version
# Passwords & Secrets
project_name: "Sinilink.XY-WFMS" # Project details. Manufacturer before the dot, device after the dot.
project_version: "v2.0" # Project version denotes release of the YAML file, allowing checking of deployed vs latest version
# Passwords & Secrets (Unfortunately, you can't use substitutions inside secret names)
api_key: !secret esp-api_key
ota_pass: !secret esp-ota_pass
static_ip_address: !secret esp-downstairskitchleds_ip # Unfortunately, you can't use substitutions inside secret names
mqtt_local_command_main_topic: !secret mqtt_local_command_main_topic
mqtt_local_status_main_topic: !secret mqtt_local_status_main_topic
static_ip_address: !secret esp-downstairskitchleds_ip # CHANGE THIS
# MQTT LOCAL Controls
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_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_OFF: "OFF"
# Device Specific Settings
log_level: "NONE" # Define logging level: NONE, ERROR, WARN, INFO, DEBUG (default), VERBOSE, VERY_VERBOSE
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
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
max_on_default_hours: "6" # The maximum time the LEDs will be on, in case they get left on. 0 = no automatic turn-off
log_level: "NONE" # Define logging level: NONE, ERROR, WARN, INFO, DEBUG (default), VERBOSE, VERY_VERBOSE
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
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
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
# https://esphome.io/components/packages.html
##########################################################################################
packages:
#### WIFI, Network (Static/DHCP/IPV6 etc), Fallback AP, Safemode ####
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}"
#### HOME ASSISTANT API (choose encryption or no encryption options) ####
common_api: !include
file: common/api_common.yaml
#file: common/api_common_noencryption.yaml
vars:
local_api_key: "${api_key}"
#common_webportal: !include
# file: common/webportal_common.yaml
#### MQTT ####
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
#file: common/sensors_common_lite.yaml
vars:
local_friendly_name: "${friendly_name}"
local_update_interval: "${update_interval}"
#### WEB PORTAL ####
#common_webportal: !include common/webportal_common.yaml
#### SNTP (Only use if you want/need accurate timeclocks) ####
#common_sntp: !include common/sntp_common.yaml
#### 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 CORE CONFIGURATION
@@ -134,7 +149,8 @@ esphome:
comment: "${description_comment}"
area: "${device_area}"
on_boot:
priority: 600
# Run later so everything is initialised before we touch states.
priority: 200
then:
# Keep the HA dropdown in sync with the stored mode
- lambda: |-
@@ -152,34 +168,12 @@ esphome:
- lambda: 'id(ramp_switch_target_on) = true;'
- 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:
condition:
lambda: 'return id(restart_mode) == 1;'
then:
- lambda: |-
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
- script.execute: deferred_restore_brightness
# Mode 2: Remain Off (no blip; stays off)
- if:
@@ -193,15 +187,19 @@ esphome:
id: mosfet_leds
transition_length: 0s
platformio_options:
build_unflags:
- -flto
build_flags:
- -fno-lto
- -Wl,--gc-sections
- -ffunction-sections
- -fdata-sections
- -DNDEBUG
# Boot complete: allow OFF handlers again
- lambda: 'id(booting) = false;'
# Only if you want to play with build flags...
# platformio_options:
# build_unflags:
# - -flto
# build_flags:
# - -fno-lto
# - -Wl,--gc-sections
# - -ffunction-sections
# - -fdata-sections
# - -DNDEBUG
##########################################################################################
# ESP PLATFORM AND FRAMEWORK
@@ -212,14 +210,11 @@ esp8266:
board: esp01_1m
restore_from_flash: true # restore some values on reboot
#preferences:
# flash_write_interval: 5min
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:
flash_write_interval: 5min
flash_write_interval: 5sec # enough time to update values for reboots, but not enough to wear flash
##########################################################################################
# GLOBAL VARIABLES
@@ -240,7 +235,8 @@ globals:
- id: max_on_hours
type: int
restore_value: true
initial_value: '${max_on_default_hours}'
initial_value: "${max_on_default_hours}"
# Default Fading Up Time (Selectable and will be retained)
- id: ramp_up_ms # fade-in when turned ON
type: int
@@ -304,6 +300,16 @@ globals:
type: float
restore_value: false
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
@@ -314,6 +320,15 @@ 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, Serial control)
#########################################################################################
# STATUS LED
# https://esphome.io/components/status_led.html
#########################################################################################
status_led:
pin:
number: ${device_status_led}
inverted: true
##########################################################################################
# MQTT COMMANDS
# This adds device-specific MQTT command triggers to the common MQTT configuration.
@@ -331,17 +346,6 @@ mqtt:
then:
- 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
# https://esphome.io/components/switch/
@@ -400,15 +404,18 @@ button:
name: "${friendly_name} Fade Stop"
icon: mdi:pause
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_off_script
- script.stop: led_flash_up
- script.stop: led_flash_down
- 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: |-
// 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;
if (cv.is_on()) {
auto call = id(mosfet_leds).make_call();
@@ -418,6 +425,14 @@ button:
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
# https://esphome.io/components/select/index.html
@@ -452,7 +467,7 @@ binary_sensor:
id: btn_gpio12
name: "${friendly_name} Button"
pin:
number: GPIO12
number: ${device_usr_button}
mode:
input: true
pullup: true
@@ -465,14 +480,14 @@ binary_sensor:
condition:
lambda: 'return id(ramp_switch_target_on);'
then:
# Target is currently ON press should go OFF (start ramp-down)
# 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)
# 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
@@ -497,17 +512,22 @@ sensor:
on_value:
then:
- lambda: |-
// Remember latest actual output (0..100) for "Restore Brightness"
id(last_brightness_pct) = x;
if (x > 0.5f) {
// Only persist when not ramping; always track a volatile copy
if (!id(is_ramping)) id(last_nonzero_brightness_pct) = x;
id(last_nonzero_tmp) = x;
const auto &cv_now = id(mosfet_leds).current_values;
// Only persist exact last brightness while actually ON,
// and not during the OFF ramp's brief floor-kick.
if (cv_now.is_on()) {
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 (!id(suppress_slider_sync)) {
float actual = x; // actual %
float actual = x;
float minp = (float) id(min_brightness_pct);
float maxp = (float) id(max_brightness_pct);
if (maxp <= minp) maxp = minp + 1.0f;
@@ -531,7 +551,7 @@ sensor:
lambda: |-
const auto &cv = id(mosfet_leds).current_values;
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
float pwm = powf(lin, gamma); // approx PWM duty after gamma
if (pwm < 0.0f) pwm = 0.0f;
@@ -548,12 +568,12 @@ sensor:
output:
- platform: esp8266_pwm
id: mosfet_pwm
pin: GPIO4
frequency: 1000 Hz # high frequency to avoid audible/visible artifacts
pin: ${device_mosfet_out}
frequency: ${pwm_frequency}
- platform: gpio
id: green_led_out # Green LED
pin:
number: GPIO13
number: ${device_fading_led}
inverted: false
##########################################################################################
@@ -565,29 +585,48 @@ light:
id: mosfet_leds
name: "${friendly_name}"
output: mosfet_pwm
restore_mode: ALWAYS_OFF
restore_mode: RESTORE_DEFAULT_OFF
default_transition_length: 2s
icon: mdi:led-strip-variant
gamma_correct: "${led_gamma}"
# ON: publish state, track intent, arm watchdog if configured
on_turn_on:
- mqtt.publish:
topic: "${mqtt_local_status_topic}/light/state"
payload: "${mqtt_local_device_command_ON}"
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
- if:
condition:
lambda: 'return id(max_on_hours) > 0;'
then:
- 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:
- mqtt.publish:
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
- if:
condition:
lambda: 'return !id(booting);'
then:
- mqtt.publish:
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:
- lambda: |-
const float cap = id(max_brightness_pct) / 100.0f;
@@ -680,6 +719,8 @@ number:
- lambda: |-
id(ramp_switch_target_on) = false;
id(led_output_set_pct).publish_state(0);
id(last_brightness_pct) = 0.0f; // persist OFF immediately
id(last_set_pos) = 0;
- delay: 400ms
- lambda: 'id(suppress_slider_sync) = false;'
else:
@@ -703,7 +744,19 @@ number:
if (out_pct > maxp) out_pct = maxp;
return out_pct / 100.0f;
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
- lambda: 'id(suppress_slider_sync) = false;'
@@ -776,7 +829,7 @@ script:
- id: ramp_on_script
mode: restart
then:
- lambda: 'id(is_ramping) = true;' # mark ramping
- lambda: 'id(is_ramping) = true;'
- script.stop: ramp_off_script
- script.stop: led_flash_down
- script.execute: led_flash_up
@@ -800,13 +853,10 @@ script:
const auto &cv = id(mosfet_leds).current_values;
const float floor = id(min_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;
// 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) {
id(last_ramp_ms) = id(ramp_up_ms);
return (uint32_t) id(last_ramp_ms);
@@ -822,18 +872,19 @@ script:
- script.stop: led_flash_up
- output.turn_off: green_led_out
- lambda: |-
id(is_ramping) = false; // ramp complete
id(is_ramping) = false;
const auto &cv = id(mosfet_leds).current_values;
if (cv.is_on()) {
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)
- id: ramp_to_target_script
mode: restart
then:
- lambda: 'id(is_ramping) = true;' # mark ramping
- lambda: 'id(is_ramping) = true;'
- script.stop: ramp_off_script
- script.stop: led_flash_down
- script.execute: led_flash_up
@@ -863,12 +914,9 @@ script:
float cap = id(restore_target_pct) / 100.0f;
const float maxp = id(max_brightness_pct) / 100.0f;
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;
// Coming from OFF/below floor? We just did the "floor kick" above — use full ramp_up_ms.
if (curr + 0.0005f < floor) {
id(last_ramp_ms) = id(ramp_up_ms);
return (uint32_t) id(last_ramp_ms);
@@ -885,18 +933,21 @@ script:
- script.stop: led_flash_up
- output.turn_off: green_led_out
- lambda: |-
id(is_ramping) = false; // ramp complete
id(is_ramping) = false;
const auto &cv = id(mosfet_leds).current_values;
if (cv.is_on()) {
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
- id: ramp_off_script
mode: restart
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: led_flash_up
- script.execute: led_flash_down
@@ -909,7 +960,7 @@ script:
float curr = cv.get_brightness();
if (curr < floor) curr = 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;
if (frac < 0.0f) frac = 0.0f;
if (frac > 1.0f) frac = 1.0f;
@@ -923,11 +974,12 @@ script:
- script.stop: led_flash_down
- output.turn_off: green_led_out
- lambda: |-
id(is_ramping) = false; // ramp complete
auto call = id(mosfet_leds).make_call();
call.set_state(false);
call.set_brightness(id(max_brightness_pct) / 100.0f);
call.perform();
id(is_ramping) = false;
id(ramping_for_off) = false;
id(last_brightness_pct) = 0.0f; // persist OFF for restore path
id(last_set_pos) = 0;
id(led_output_set_pct).publish_state(0);
- id: max_on_watchdog
mode: restart
then:
@@ -945,3 +997,38 @@ script:
id(mosfet_ramp_switch).publish_state(false);
- script.stop: ramp_on_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
+33 -5
View File
@@ -49,29 +49,57 @@ substitutions:
# https://esphome.io/components/packages.html
##########################################################################################
packages:
##################################################
# MANDATORY packages (won't compile without them!)
##################################################
#### WIFI, Network (Static/DHCP/IPV6 etc), Fallback AP, Safemode ####
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}"
##################################################
# OPTIONAL packages (comment if you don't want them)
##################################################
#### HOME ASSISTANT API (choose encryption or no encryption options) ####
common_api: !include
file: common/api_common.yaml
#file: common/api_common_noencryption.yaml
vars:
local_api_key: "${api_key}"
#### WEB PORTAL ####
#common_webportal: !include
# file: common/webportal_common.yaml
#### MQTT ####
common_mqtt: !include
file: common/mqtt_common.yaml
vars:
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
file: common/sensors_common.yaml
vars:
local_friendly_name: "${friendly_name}"
local_update_interval: "${update_interval}"
#### DEBUGGING Sensors ####
# A count of various resets
#common_resetcount_debugging: !include
# file: common/resetcount_common.yaml
#############################################
# ESPHome
+181
View File
@@ -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
View File
@@ -34,18 +34,18 @@ substitutions:
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-laundrylights_ip
#mqtt_local_command_main_topic: !secret mqtt_local_command_main_topic
#mqtt_local_status_main_topic: !secret mqtt_local_status_main_topic
mqtt_command_main_topic: !secret mqtt_command_main_topic
mqtt_status_main_topic: !secret mqtt_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 Controls
#mqtt_device_name: "bedroom2-ceilingfan"
#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
# MQTT REMOTE Controls
mqtt_remote1_device_name: "3dprinter-power"
mqtt_remote1_command_topic: "${mqtt_command_main_topic}/${mqtt_remote1_device_name}" # Topic we will use to command this 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_1_name: "Laundry Lights"
@@ -58,29 +58,45 @@ substitutions:
# https://esphome.io/components/packages.html
##########################################################################################
packages:
##################################################
# MANDATORY packages (won't compile without them!)
##################################################
#### WIFI, Network (Static/DHCP/IPV6 etc), Fallback AP, Safemode ####
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}"
##################################################
# OPTIONAL packages (comment if you don't want them)
##################################################
#### HOME ASSISTANT API (choose encryption or no encryption options) ####
common_api: !include
file: common/api_common.yaml
#file: common/api_common_noencryption.yaml
vars:
local_api_key: "${api_key}"
#common_webportal: !include
# file: common/webportal_common.yaml
#### MQTT ####
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}"
#### WEB PORTAL ####
common_webportal: !include common/webportal_common.yaml
#### SNTP (Only use if you want/need accurate timeclocks) ####
common_sntp: !include common/sntp_common.yaml
#### 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
@@ -127,6 +143,20 @@ status_led:
number: GPIO2
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
# https://esphome.io/components/binary_sensor/
@@ -145,20 +175,76 @@ binary_sensor:
pin:
number: GPIO05
mode: INPUT
inverted: True
inverted: true
name: "Button 2: ${switch_2_name}"
on_press:
- switch.toggle: Relay_2
on_multi_click:
# 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
# pin:
# number: GPIO4
# mode: INPUT
# inverted: True
# name: "Button 3: ${switch_3_name}"
# on_press:
# - switch.toggle: Relay_3
#########################################################################################
# TEXT SENSORS
#
#########################################################################################
text_sensor:
- platform: mqtt_subscribe
id: remote1_status
internal: true
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
@@ -175,10 +261,5 @@ switch:
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
@@ -1,12 +1,13 @@
##########################################################################################
##########################################################################################
# 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)
#
# Contoller: https://www.aliexpress.com/item/32838426377.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)
##########################################################################################
#
@@ -56,25 +57,41 @@
##########################################################################################
substitutions:
# Device Naming
device_name: "esp-4chdmxdimmer1"
friendly_name: "4 Channel DMX Dimmer 1"
description_comment: "Lounge 4 Channel Mains Lighting Dimmer using DMX512 protocol"
device_name: "esp-lounge6chdimmer"
friendly_name: "6 Channel Lounge Dimmer"
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.
# 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
project_version: "v1.1" # 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-4chdmxdimmer1_ip
static_ip_address: !secret esp-lounge6chdimmer_ip
# Device Settings
#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
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
# 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
##########################################################################################
packages:
#### WIFI, Network (Static/DHCP/IPV6 etc), Fallback AP, Safemode ####
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}"
#### HOME ASSISTANT API (choose encryption or no encryption options) ####
common_api: !include
file: common/api_common.yaml
#file: common/api_common_noencryption.yaml
vars:
local_api_key: "${api_key}"
#common_webportal: !include
# file: common/webportal_common.yaml
#### MQTT ####
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}"
#### WEB PORTAL ####
#common_webportal: !include common/webportal_common.yaml
#### SNTP (Only use if you want/need accurate timeclocks) ####
common_sntp: !include common/sntp_common.yaml
#### 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
@@ -126,13 +151,15 @@ esphome:
id(dmx_ch2_sensor).publish_state(0);
id(dmx_ch3_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
# https://esphome.io/components/esp32.html
#########################################################################################
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.
board_flash_mode: dout # Default is dout
restore_from_flash: true # restore some values on reboot
@@ -140,8 +167,8 @@ esp8266:
mdns:
disabled: false # Disabling will make the build file smaller (and it is still available via static IP)
preferences:
flash_write_interval: 0s
#preferences:
# flash_write_interval: 0s
#########################################################################################
# ESPHome Logging Enable
@@ -183,33 +210,47 @@ uart:
##########################################################################################
light:
- platform: monochromatic
name: "Test Lights Chan 1"
name: ${output_1_name}
output: dmx_1
id: light_test_1
default_transition_length: 5s
gamma_correct: 1
default_transition_length: 10s
gamma_correct: 2.2
restore_mode: ALWAYS_ON
- platform: monochromatic
name: "Test Lights Chan 2"
name: ${output_2_name}
output: dmx_2
id: light_test_2
default_transition_length: 5s
gamma_correct: 1
default_transition_length: 10s
gamma_correct: 2.2
restore_mode: ALWAYS_ON
- platform: monochromatic
name: "Test Lights Chan 3"
name: ${output_3_name}
output: dmx_3
id: light_test_3
default_transition_length: 5s
gamma_correct: 1
default_transition_length: 10s
gamma_correct: 2.2
restore_mode: ALWAYS_ON
- platform: monochromatic
name: "Test Lights Chan 4"
name: ${output_4_name}
output: dmx_4
id: light_test_4
default_transition_length: 5s
default_transition_length: 10s
gamma_correct: 1
restore_mode: ALWAYS_ON
restore_mode: ALWAYS_ON
- platform: monochromatic
name: ${output_5_name}
output: dmx_5
id: light_test_5
default_transition_length: 10s
gamma_correct: 1
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
@@ -219,12 +260,14 @@ sensor:
# Expose each DMX channel's current output level as 0..255 integers (post-gamma)
- platform: template
id: dmx_ch1_sensor
name: "DMX Ch 1 Output (0-255)"
name: ${output_1_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
@@ -232,12 +275,14 @@ sensor:
- platform: template
id: dmx_ch2_sensor
name: "DMX Ch 2 Output (0-255)"
name: ${output_2_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
@@ -245,12 +290,14 @@ sensor:
- platform: template
id: dmx_ch3_sensor
name: "DMX Ch 3 Output (0-255)"
name: ${output_3_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
@@ -258,12 +305,44 @@ sensor:
- platform: template
id: dmx_ch4_sensor
name: "DMX Ch 4 Output (0-255)"
name: ${output_4_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_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
- clamp:
min_value: 0
@@ -284,7 +363,7 @@ dmx512:
number: GPIO2
allow_other_uses: true # For ESPHome >= 2023.12.0
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
#custom_break_len: 92 # optional. Break Length. Default is 92uS
#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
universe: dmx # The ID of the DMX512 Component
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
- platform: template
@@ -319,31 +406,77 @@ output:
type: float
write_action:
- lambda: |-
id(dmx_ch1_sensor).publish_state(state * 255.0f);
id(dmx_1_raw).set_level(state);
const float MIN = ${min_output_1};
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
id: dmx_2
type: float
write_action:
- lambda: |-
id(dmx_ch2_sensor).publish_state(state * 255.0f);
id(dmx_2_raw).set_level(state);
const float MIN = ${min_output_2};
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
id: dmx_3
type: float
write_action:
- lambda: |-
id(dmx_ch3_sensor).publish_state(state * 255.0f);
id(dmx_3_raw).set_level(state);
const float MIN = ${min_output_3};
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
id: dmx_4
type: float
write_action:
- lambda: |-
id(dmx_ch4_sensor).publish_state(state * 255.0f);
id(dmx_4_raw).set_level(state);
const float MIN = ${min_output_4};
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)
# Hardware: Magichome inline single channel dimmer (ESP8266)
#
# Repo:
# Title: LOUNGE CABINET LEDS
# Repo: https://home.fox.co.nz/gitea/zorruno/zorruno-homeassistant/src/branch/master/esphome/esp-loungecabinetleds.yaml
#
# v1.0 - 2025-08-17 First setup (and replacement of Tasmota)
# 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)
#
# ------------------------------------------
# DEVICE GPIO (Magichome ESP8266)
# OPERATION (as of v2.0)
# ------------------------------------------
# GPIO02 Blue LED (used for ESPHome status) - I think... can't see the board
# GPIO12 MOSFET output (0 V when switched)
#
# ------------------------------------------
# OPERATION
# ------------------------------------------
# - Startup/power restore action
# 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 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/Down switch
# - Normal On/Off switch (quick ramp up/down)
# - 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)
# - Device diagnostics (from the included package)
# - 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: 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
# If NOT using a secrets file, just replace these with the passwords etc (in quotes)
##########################################################################################
substitutions:
# Device Naming
device_name: "esp-loungecabinetleds"
device_name: "esp-loungecabinetleds2" # yaml file should be device_name.yaml
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.
# Project Naming
project_name: "Magichome ESP8266" # Project details
project_version: "v1.0" # Project version denotes release of the YAML file, allowing checking of deployed vs latest version
project_name: "Sinilink.XY-WFMS" # Project details. Manufacturer before the dot, device after the dot.
project_version: "v2.0" # Project version denotes release of the 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-loungecabinetleds_ip # Unfortunately, you can't use substitutions inside secret names
# Passwords & Secrets (Unfortunately, you can't use substitutions inside secret names)
api_key: !secret esp-api_key # If using HA API
ota_pass: !secret esp-ota_pass # If passwording OTA updates
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_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_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_OFF: "OFF"
# Device Specific Settings
log_level: "NONE" # Define logging level: NONE, ERROR, WARN, INFO, DEBUG (default), VERBOSE, VERY_VERBOSE
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
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
max_on_default_hours: "12" # The maximum time the LEDs will be on, in case they get left on. 0 = no automatic turn-off
log_level: "NONE" # Define logging level: NONE, ERROR, WARN, INFO, DEBUG (default), VERBOSE, VERY_VERBOSE
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
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
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
# https://esphome.io/components/packages.html
##########################################################################################
packages:
#### WIFI, Network (Static/DHCP/IPV6 etc), Fallback AP, Safemode ####
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}"
#### HOME ASSISTANT API (choose encryption or no encryption options) ####
common_api: !include
file: common/api_common.yaml
#file: common/api_common_noencryption.yaml
vars:
local_api_key: "${api_key}"
#common_webportal: !include
# file: common/webportal_common.yaml
#### MQTT ####
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
#file: common/sensors_common_lite.yaml
vars:
local_friendly_name: "${friendly_name}"
local_update_interval: "${update_interval}"
#### WEB PORTAL ####
#common_webportal: !include common/webportal_common.yaml
#### SNTP (Only use if you want/need accurate timeclocks) ####
#common_sntp: !include common/sntp_common.yaml
#### 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 CORE CONFIGURATION
@@ -108,7 +143,8 @@ esphome:
comment: "${description_comment}"
area: "${device_area}"
on_boot:
priority: 600
# Run later so everything is initialised before we touch states.
priority: 200
then:
# Keep the HA dropdown in sync with the stored mode
- lambda: |-
@@ -126,31 +162,12 @@ esphome:
- lambda: 'id(ramp_switch_target_on) = true;'
- 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:
condition:
lambda: 'return id(restart_mode) == 1;'
then:
- lambda: |-
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
- script.execute: deferred_restore_brightness
# Mode 2: Remain Off (no blip; stays off)
- if:
@@ -164,15 +181,19 @@ esphome:
id: mosfet_leds
transition_length: 0s
platformio_options:
build_unflags:
- -flto
build_flags:
- -fno-lto
- -Wl,--gc-sections
- -ffunction-sections
- -fdata-sections
- -DNDEBUG
# Boot complete: allow OFF handlers again
- lambda: 'id(booting) = false;'
# Only if you want to play with build flags...
# platformio_options:
# build_unflags:
# - -flto
# build_flags:
# - -fno-lto
# - -Wl,--gc-sections
# - -ffunction-sections
# - -fdata-sections
# - -DNDEBUG
##########################################################################################
# ESP PLATFORM AND FRAMEWORK
@@ -184,10 +205,10 @@ esp8266:
restore_from_flash: true # restore some values on reboot
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:
flash_write_interval: 5min
flash_write_interval: 5sec # enough time to update values for reboots, but not enough to wear flash
##########################################################################################
# GLOBAL VARIABLES
@@ -208,7 +229,8 @@ globals:
- id: max_on_hours
type: int
restore_value: true
initial_value: '${max_on_default_hours}'
initial_value: "${max_on_default_hours}"
# Default Fading Up Time (Selectable and will be retained)
- id: ramp_up_ms # fade-in when turned ON
type: int
@@ -272,6 +294,16 @@ globals:
type: float
restore_value: false
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
@@ -282,6 +314,15 @@ 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, Serial control)
#########################################################################################
# STATUS LED
# https://esphome.io/components/status_led.html
#########################################################################################
status_led:
pin:
number: ${device_status_led}
inverted: true
##########################################################################################
# MQTT COMMANDS
# This adds device-specific MQTT command triggers to the common MQTT configuration.
@@ -299,15 +340,6 @@ mqtt:
then:
- switch.turn_off: mosfet_ramp_switch
#########################################################################################
# STATUS LED
# https://esphome.io/components/status_led.html
#########################################################################################
status_led:
pin:
number: GPIO2
inverted: true
##########################################################################################
# SWITCH COMPONENT
# https://esphome.io/components/switch/
@@ -366,12 +398,18 @@ button:
name: "${friendly_name} Fade Stop"
icon: mdi:pause
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_off_script
# Cancel the light's transition by commanding the current level with 0 ms,
# but DO NOT change the ramp switch state/flag.
- script.stop: led_flash_up
- script.stop: led_flash_down
- output.turn_off: green_led_out
- 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;
if (cv.is_on()) {
auto call = id(mosfet_leds).make_call();
@@ -381,6 +419,14 @@ button:
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
# https://esphome.io/components/select/index.html
@@ -410,7 +456,37 @@ select:
# BINARY SENSORS
# 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
@@ -430,16 +506,22 @@ sensor:
on_value:
then:
- lambda: |-
// Remember latest actual output (0..100) for "Restore Brightness"
id(last_brightness_pct) = x;
if (x > 0.5f) {
// Only persist when not ramping; always track a volatile copy
if (!id(is_ramping)) id(last_nonzero_brightness_pct) = x;
id(last_nonzero_tmp) = x;
const auto &cv_now = id(mosfet_leds).current_values;
// Only persist exact last brightness while actually ON,
// and not during the OFF ramp's brief floor-kick.
if (cv_now.is_on()) {
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 (!id(suppress_slider_sync)) {
float actual = x; // actual %
float actual = x;
float minp = (float) id(min_brightness_pct);
float maxp = (float) id(max_brightness_pct);
if (maxp <= minp) maxp = minp + 1.0f;
@@ -463,7 +545,7 @@ sensor:
lambda: |-
const auto &cv = id(mosfet_leds).current_values;
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
float pwm = powf(lin, gamma); // approx PWM duty after gamma
if (pwm < 0.0f) pwm = 0.0f;
@@ -480,8 +562,13 @@ sensor:
output:
- platform: esp8266_pwm
id: mosfet_pwm
pin: GPIO12
frequency: 1000 Hz # higher frequency helps reduce camera banding while filming
pin: ${device_mosfet_out}
frequency: ${pwm_frequency}
- platform: gpio
id: green_led_out # Green LED
pin:
number: ${device_fading_led}
inverted: false
##########################################################################################
# LIGHT COMPONENT
@@ -492,29 +579,48 @@ light:
id: mosfet_leds
name: "${friendly_name}"
output: mosfet_pwm
restore_mode: ALWAYS_OFF
restore_mode: RESTORE_DEFAULT_OFF
default_transition_length: 2s
icon: mdi:led-strip-variant
gamma_correct: "${led_gamma}"
# ON: publish state, track intent, arm watchdog if configured
on_turn_on:
- mqtt.publish:
topic: "${mqtt_local_status_topic}/light/state"
payload: "${mqtt_local_device_command_ON}"
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
- if:
condition:
lambda: 'return id(max_on_hours) > 0;'
then:
- 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:
- mqtt.publish:
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
- if:
condition:
lambda: 'return !id(booting);'
then:
- mqtt.publish:
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:
- lambda: |-
const float cap = id(max_brightness_pct) / 100.0f;
@@ -607,6 +713,8 @@ number:
- lambda: |-
id(ramp_switch_target_on) = false;
id(led_output_set_pct).publish_state(0);
id(last_brightness_pct) = 0.0f; // persist OFF immediately
id(last_set_pos) = 0;
- delay: 400ms
- lambda: 'id(suppress_slider_sync) = false;'
else:
@@ -630,7 +738,19 @@ number:
if (out_pct > maxp) out_pct = maxp;
return out_pct / 100.0f;
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
- 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.
##########################################################################################
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.
- id: ramp_on_script
mode: restart
then:
- lambda: 'id(is_ramping) = true;' # mark ramping
- lambda: 'id(is_ramping) = true;'
- 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".
- if:
condition:
@@ -696,18 +847,14 @@ script:
const auto &cv = id(mosfet_leds).current_values;
const float floor = id(min_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;
// 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) {
id(last_ramp_ms) = id(ramp_up_ms);
return (uint32_t) id(last_ramp_ms);
}
if (curr > cap) curr = cap;
if (curr < floor) curr = floor;
float frac = (cap - curr) / (cap - floor);
@@ -716,20 +863,25 @@ script:
id(last_ramp_ms) = (int) (id(ramp_up_ms) * frac);
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: |-
id(is_ramping) = false; // ramp complete
id(is_ramping) = false;
const auto &cv = id(mosfet_leds).current_values;
if (cv.is_on()) {
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)
- id: ramp_to_target_script
mode: restart
then:
- lambda: 'id(is_ramping) = true;' # mark ramping
- lambda: 'id(is_ramping) = true;'
- script.stop: ramp_off_script
- script.stop: led_flash_down
- script.execute: led_flash_up
# Ensure we start at the floor cleanly.
- if:
condition:
@@ -756,12 +908,9 @@ script:
float cap = id(restore_target_pct) / 100.0f;
const float maxp = id(max_brightness_pct) / 100.0f;
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;
// Coming from OFF/below floor? We just did the "floor kick" above — use full ramp_up_ms.
if (curr + 0.0005f < floor) {
id(last_ramp_ms) = id(ramp_up_ms);
return (uint32_t) id(last_ramp_ms);
@@ -775,20 +924,27 @@ script:
id(last_ramp_ms) = (int) (id(ramp_up_ms) * frac);
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: |-
id(is_ramping) = false; // ramp complete
id(is_ramping) = false;
const auto &cv = id(mosfet_leds).current_values;
if (cv.is_on()) {
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
- id: ramp_off_script
mode: restart
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: led_flash_up
- script.execute: led_flash_down
- light.turn_on:
id: mosfet_leds
brightness: !lambda 'return id(min_brightness_pct) / 100.0f;'
@@ -798,7 +954,7 @@ script:
float curr = cv.get_brightness();
if (curr < floor) curr = 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;
if (frac < 0.0f) frac = 0.0f;
if (frac > 1.0f) frac = 1.0f;
@@ -809,12 +965,15 @@ script:
id: mosfet_leds
transition_length: 150ms
- delay: 150ms
- script.stop: led_flash_down
- output.turn_off: green_led_out
- lambda: |-
id(is_ramping) = false; // ramp complete
auto call = id(mosfet_leds).make_call();
call.set_state(false);
call.set_brightness(id(max_brightness_pct) / 100.0f);
call.perform();
id(is_ramping) = false;
id(ramping_for_off) = false;
id(last_brightness_pct) = 0.0f; // persist OFF for restore path
id(last_set_pos) = 0;
id(led_output_set_pct).publish_state(0);
- id: max_on_watchdog
mode: restart
then:
@@ -832,3 +991,38 @@ script:
id(mosfet_ramp_switch).publish_state(false);
- script.stop: ramp_on_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
+181
View File
@@ -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
+10 -10
View File
@@ -262,7 +262,7 @@ interval:
#############################################
number:
- 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
icon: "mdi:cogs"
optimistic: true
@@ -279,12 +279,12 @@ number:
std::string th1st = "mth1=" + str_sprintf("%.0f",x) +"\r\n";
return std::vector<uint8_t>(th1st.begin(), th1st.end());
- 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
icon: "mdi:cogs"
optimistic: true
restore_value: true #If you don't want to store the setting at ESP, set it to false.
initial_value: "30" #Default mth2 Setting
restore_value: true # If you don't want to store the setting at ESP, set it to false.
initial_value: "30" # Default mth2 Setting
min_value: 5
max_value: 300
step: 5
@@ -296,12 +296,12 @@ number:
std::string th2st = "mth2=" + str_sprintf("%.0f",x) +"\r\n";
return std::vector<uint8_t>(th2st.begin(), th2st.end());
- platform: template
name: ${friendly_name} LD1125H mth3 #mth3 is above 8m Sensitivity.
name: "8m+ Sensitivity" # mth3 is above 8m Sensitivity.
id: LD1125H_mth3
icon: "mdi:cogs"
optimistic: true
restore_value: true #If you don't want to store the setting at ESP, set it to false.
initial_value: "20" #Default mth3 Setting
restore_value: true # If you don't want to store the setting at ESP, set it to false.
initial_value: "20" # Default mth3 Setting
min_value: 5
max_value: 200
step: 5
@@ -313,7 +313,7 @@ number:
std::string th3st = "mth3=" + str_sprintf("%.0f",x) +"\r\n";
return std::vector<uint8_t>(th3st.begin(), th3st.end());
- platform: template
name: ${friendly_name} LD1125H rmax #rmax is max detection distance.
name: "Max Detection (m)" # rmax is max detection distance.
id: LD1125H_rmax
icon: "mdi:cogs"
optimistic: true
@@ -330,7 +330,7 @@ number:
std::string rmaxst = "rmax=" + str_sprintf("%.1f",x) +"\r\n";
return std::vector<uint8_t>(rmaxst.begin(), rmaxst.end());
- platform: template
name: ${friendly_name} LD1125H Clearence Time
name: "Clearence Time (s)"
id: LD1125H_Clear_Time
icon: "mdi:cogs"
optimistic: true
@@ -340,7 +340,7 @@ number:
max_value: 20
step: 0.5
- platform: template
name: ${friendly_name} LD1125H Movement Time
name: "Movement Time (s)"
id: LD1125H_Mov_Time
icon: "mdi:cogs"
optimistic: true
+29 -3
View File
@@ -102,29 +102,55 @@ substitutions:
# https://esphome.io/components/packages.html
##########################################################################################
packages:
##################################################
# MANDATORY packages (won't compile without them!)
##################################################
#### WIFI, Network (Static/DHCP/IPV6 etc), Fallback AP, Safemode ####
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}"
##################################################
# OPTIONAL packages (comment if you don't want them)
##################################################
#### HOME ASSISTANT API (choose encryption or no encryption options) ####
common_api: !include
file: common/api_common.yaml
#file: common/api_common_noencryption.yaml
vars:
local_api_key: "${api_key}"
#### WEB PORTAL ####
#common_webportal: !include
# file: common/webportal_common.yaml
#### MQTT ####
common_mqtt: !include
file: common/mqtt_common.yaml
vars:
local_device_name: "${device_name}"
#### SNTP (Only use if you want/need accurate timeclocks) ####
common_sntp: !include
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
file: common/sensors_common.yaml
vars:
local_friendly_name: "${friendly_name}"
local_update_interval: "${update_interval}"
#### DEBUGGING Sensors ####
# A count of various resets
#common_resetcount_debugging: !include
# file: common/resetcount_common.yaml
# Device Specific included packages
common_athompowermonV1: !include
+31 -3
View File
@@ -86,29 +86,57 @@ substitutions:
# https://esphome.io/components/packages.html
##########################################################################################
packages:
##################################################
# MANDATORY packages (won't compile without them!)
##################################################
#### WIFI, Network (Static/DHCP/IPV6 etc), Fallback AP, Safemode ####
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}"
##################################################
# OPTIONAL packages (comment if you don't want them)
##################################################
#### HOME ASSISTANT API (choose encryption or no encryption options) ####
common_api: !include
file: common/api_common.yaml
#file: common/api_common_noencryption.yaml
vars:
local_api_key: "${api_key}"
#### WEB PORTAL ####
#common_webportal: !include
# file: common/webportal_common.yaml
#### MQTT ####
common_mqtt: !include
file: common/mqtt_common.yaml
vars:
local_device_name: "${device_name}"
#### 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
file: common/sensors_common.yaml
vars:
local_friendly_name: "${friendly_name}"
local_update_interval: "${update_interval}"
#### DEBUGGING Sensors ####
# A count of various resets
#common_resetcount_debugging: !include
# file: common/resetcount_common.yaml
# Device Specific included packages
common_athompowermonV3: !include
+217 -304
View File
@@ -1,18 +1,19 @@
#############################################
#############################################
#######################################################################################################
#######################################################################################################
# Opengreen energy weather station board, with ESP8266 D1 Mini
# https://www.opengreenenergy.com/solar-powered-wifi-weather-station-v2-0/
# zorruno 2024-05-04 V0.0 Untested
# 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 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-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
#
# TESTED WORKING
@@ -55,43 +56,43 @@
# soil temp
# soil moisture
#
#############################################
#######################################################################################################
#############################################
#######################################################################################################
# WEMOS D1 Mini GPIO, and Weather station Use
#############################################
#######################################################################################################
# PIN HEADERS on Weatherstation board:
# D1, GPIO5 Used as SCL (I2C)
# D2, GPIO4 Used as SDA (I2C)
# D3, GPIO0 connected to FLASH button, boot fails if pulled LOW
# D4, GPIO2 connected to on-board LED, boot fails if pulled LOW
# D5, GPIO14 Used as Wind Speed Count
# D6, GPIO12 Used as Rainfall count
# D7, GPIO13 Used for DHT
# D8, GPIO15 (Boot fails if pulled HIGH)
# D1, GPIO5 Used as SCL (I2C)
# D2, GPIO4 Used as SDA (I2C)
# D3, GPIO0 connected to FLASH button, boot fails if pulled LOW
# D4, GPIO2 connected to on-board LED, boot fails if pulled LOW
# D5, GPIO14 Used as Wind Speed Count
# D6, GPIO12 Used as Rainfall count
# D7, GPIO13 Used for DHT
# D8, GPIO15 (Boot fails if pulled HIGH)
#
# NO HEADERS on Weatherstation board:
# D0, GPIO16, (used to wake up from deep sleep)
# RX, GPIO3 (HIGH at boot)
# TX, GPIO1 (HIGH at boot, boot fails if pulled LOW)
# A0, ADC0 Used here to measure battery V
# D0, GPIO16 (used to wake up from deep sleep)
# RX, GPIO3 (HIGH at boot)
# TX, GPIO1 (HIGH at boot, boot fails if pulled LOW)
# A0, ADC0 Used here to measure battery V
#
#############################################
#######################################################################################################
#############################################
#######################################################################################################
# Pulse Meter Issues 202405
# https://github.com/esphome/issues/issues/4807
# https://github.com/esphome/issues/issues/3143
#############################################
#######################################################################################################
external_components:
- source: github://TrentHouliston/esphome@loop_pulse_isr
components: [ pulse_meter ]
#############################################
#######################################################################################################
# Variable Substitutions
# Give this a useful name & description here
# and change values accordingly
#############################################
#######################################################################################################
substitutions:
devicename: "esp-weatherstation"
friendly_name: "Weather Station"
@@ -109,22 +110,22 @@ substitutions:
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
#############################################
#######################################################################################################
# ESPHome
# https://esphome.io/components/esphome.html
#############################################
#######################################################################################################
esphome:
name: ${devicename}
comment: ${description_comment} #appears on the esphome page in HA
#on_boot: #Initial Setting stuff
#priority: -200
#then:
# priority: -200
# then:
#############################################
#######################################################################################################
# ESP Platform and Framework
# https://esphome.io/components/esp32.html OR
# https://esphome.io/components/esp8266.html
#############################################
#######################################################################################################
#esp32:
# board:
# framework:
@@ -135,12 +136,12 @@ esphome:
esp8266:
board: d1_mini
#############################################
#######################################################################################################
# i2c bus
# https://esphome.io/components/i2c.html
# Put the relevant pins here, 4/5 are default for ESP8266
# and 21/22 for esp32 (although esp32 can have two buses)
#############################################
#######################################################################################################
i2c:
sda: GPIO4
scl: GPIO5
@@ -194,11 +195,11 @@ wifi:
password: ${wifi_pass}
#power_save_mode: LIGHT #https://esphome.io/components/wifi.html#wifi-power-save-mode
#manual_ip: #optional static IP address
#static_ip: 192.168.x.x
#gateway: 192.168.X.x
#subnet: 255.255.255.0
# static_ip: 192.168.x.x
# gateway: 192.168.X.x
# subnet: 255.255.255.0
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
ap_timeout: 30min #default is 1min
@@ -214,14 +215,14 @@ wifi:
#web_server:
# port: 80
# version: 1 #V1 occasionally works better, V2 The nicer page
# username: !secret web_server_username #probably a good idea to secure it
# password: !secret web_server_password
# username: !secret web_server_username #probably a good idea to secure it
# password: !secret web_server_password
#############################################
#######################################################################################################
# MQTT Monitoring
# https://esphome.io/components/mqtt.html?highlight=mqtt
# MUST also have api enabled if you enable MQTT
#############################################
#######################################################################################################
mqtt:
broker: ${mqtt_server}
topic_prefix: ${mqtt_topic}/${devicename}
@@ -230,14 +231,14 @@ mqtt:
#Method to prevent deep sleep using MQTT command
#on_message:
# - topic: ${mqtt_topic}/${devicename}/deepsleep
# payload: 'ON'
# then:
# - deep_sleep.prevent: deep_sleep_1
# - topic: ${mqtt_topic}/${devicename}/deepsleep
# payload: 'ON'
# then:
# - deep_sleep.enter: deep_sleep_1
# - topic: ${mqtt_topic}/${devicename}/deepsleep
# payload: 'ON'
# then:
# - deep_sleep.prevent: deep_sleep_1
# - topic: ${mqtt_topic}/${devicename}/deepsleep
# payload: 'ON'
# then:
# - deep_sleep.enter: deep_sleep_1
########################################
# Deep Sleep
@@ -248,29 +249,30 @@ mqtt:
# sleep_duration: 10min
# id: deep_sleep_1
#############################################
#######################################################################################################
# General espHome status LED
# Not needed, but can be useful to see issues
# https://esphome.io/components/status_led.html
#############################################
# If the LED appears inverted (on when it should be off), set 'inverted: true'.
#######################################################################################################
status_led:
pin:
number: GPIO2 #Wemos ESP32 and ESP8266 Onboard LEDs use GPIO2
inverted: false
#ignore_strapping_warning: True #https://esphome.io/guides/faq.html#why-am-i-getting-a-warning-about-strapping-pins
################################
##########################################################################################
# 4 Input A->D sensor
# Analog sensor for voltage reading
# https://esphome.io/components/sensor/ads1115.html
################################
##########################################################################################
ads1115:
- address: 0x48 #On the ADS1115 pull address pin to VCC for 0x49. Default is 0x48
#############################################
#######################################################################################################
# Text Sensors
# https://esphome.io/components/text_sensor/index.html
#############################################
#######################################################################################################
text_sensor:
- platform: template
name: ${friendly_name} Beaufort Wind Scale
@@ -283,17 +285,17 @@ text_sensor:
id: wind_dir_heading
update_interval: never
#############################################
#############################################
#######################################################################################################
#######################################################################################################
# General Sensors
# https://esphome.io/components/sensor/index.html
#############################################
#############################################
#######################################################################################################
#######################################################################################################
sensor:
################################
# UPTIME
# Device uptime info
# Device uptime info
# https://esphome.io/components/sensor/uptime.html
################################
- platform: uptime
@@ -316,7 +318,7 @@ sensor:
################################
- platform: bmp280
address: 0x76
iir_filter: 16X
iir_filter: 16x
update_interval: ${update_time}
temperature:
name: ${friendly_name} BMP280 Temperature
@@ -327,7 +329,7 @@ sensor:
pressure:
name: ${friendly_name} BMP280 Pressure
unit_of_measurement: "hPa"
oversampling: 16X
oversampling: 16x
#retain: true #MQTT retain useful if sleeping
################################
@@ -356,11 +358,14 @@ sensor:
icon: "mdi:thermometer"
unit_of_measurement: "°C"
lambda: |-
return (
id(bmp280_temp).state
+
id(dht_temp).state
) / 2;
const float a = id(bmp280_temp).state;
const float b = id(dht_temp).state;
const bool va = !isnan(a);
const bool vb = !isnan(b);
if (va && vb) return (a + b) / 2.0f;
if (va) return a;
if (vb) return b;
return NAN;
################################
# BATTERY VOLTAGE
@@ -384,9 +389,9 @@ sensor:
send_every: 1
send_first_at: 1
- calibrate_linear:
- 3.321 -> 3.125
- 3.89 -> 3.90
- 4.09 -> 4.14
- 3.321 -> 3.125
- 3.89 -> 3.90
- 4.09 -> 4.14
################################
# A Calculation of recent Av V
@@ -398,30 +403,30 @@ sensor:
unit_of_measurement: "V"
accuracy_decimals: 6
update_interval: 30s #updates every 30s
lambda: >
lambda: |-
return id(battery_voltage).state; //grabs values from the battery_voltage
filters:
- 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_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
################################
##########################################################################################
- platform: template
name: ${friendly_name} Max Battery Voltage over recent period
id: battery_voltage_recentmax
unit_of_measurement: "V"
accuracy_decimals: 6
update_interval: 30s #updates every 10s
lambda: >
update_interval: 30s #updates every 30s
lambda: |-
return id(battery_voltage).state; //grabs values from the battery_voltage
filters:
- heartbeat: 30s
- 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_first_at: 1
@@ -431,8 +436,8 @@ sensor:
unit_of_measurement: "V"
accuracy_decimals: 5
update_interval: 30s #updates every 30s
lambda: >
return {id(battery_voltage_recentmax).state - id(battery_voltage).state}; //recent av voltage - now
lambda: |-
return id(battery_voltage_recentmax).state - id(battery_voltage).state; //recent max voltage - now
- platform: duty_time
id: battery_discharge_time
@@ -444,17 +449,17 @@ sensor:
filters:
- multiply: 0.01666666
- round: 0
lambda: >
return { id(battery_voltage).state <= id(battery_voltage_recentav).state };
lambda: |-
return id(battery_voltage).state <= id(battery_voltage_recentav).state;
################################
##########################################################################################
# RAIN SENSOR
# Pulse Meter for measuring rainfall
# https://esphome.io/components/sensor/pulse_meter.html
################################
##########################################################################################
# CALIBRATION HINTS
# https://forum.mysensors.org/topic/9594/misol-rain-gauge-tipping-bucket-rain-amount/2
################################
##########################################################################################
- platform: pulse_meter
id: rainfall_meter
pin:
@@ -483,20 +488,20 @@ sensor:
# max_value: 250 #if anything over that, we have debounce issues or are going to die
# ignore_out_of_range: true
################################
##########################################################################################
# Pulse Meter for measuring wind speed
# Analog sensor for voltage reading
# 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
# 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.
# 1rps = 60rpm = 2.4kmh
# 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.
################################
##########################################################################################
# USEFUL LINKS
# https://community.home-assistant.io/t/measuring-wind-speed/395693/20
# 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
ignore_out_of_range: true
################################
# WIND 10 MINUTE MAX
# Template for wind gust measurements
# To measure GUST properly, the World Meteorological Organization
# recommends gust measurement should be wind speed averages over
# 3 second periods and report the maximum of these averages
# within a sliding window of the last 10 minutes.
# This needs a wind speed history buffer of 200 samples
# (200 x 3 second average = 10 minutes)
################################
# Useful links
# https://community.home-assistant.io/t/adding-an-old-marine-anemometer-to-ha/337842
################################
##########################################################################################
# WIND GUSTS WMO-style 10-minute gust
# Per WMO guidance, "gust" ≈ maximum of 3-second mean wind over the last 10 minutes.
# Implementation:
# 1) Compute a 3 s sliding average (updated every 1 s) from wind_meter.
# 2) Take the rolling maximum of that 3 s average over the last 10 minutes.
##########################################################################################
- platform: template
id: wind_speed_3s_avg
name: ${friendly_name} Wind speed (3s avg)
unit_of_measurement: "km/h"
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
name: ${friendly_name} Windspeed Gust (Max last 10m)
unit_of_measurement: "km/h"
update_interval: 3s #updates every 2s
lambda: >
return id(wind_meter).state; //grabs values from the wind_sensor
icon: "mdi:weather-windy"
update_interval: 1s
lambda: |-
return id(wind_speed_3s_avg).state; # use the 3 s average stream
filters:
- filter_out: nan #in case there is no value
- filter_out: nan
- max:
window_size: 200 #10 Min window as 3s update x 200 = 10min
send_every: 1 #updates on the 1st 2s check
send_first_at: 1 #on restart, send after first 10s
window_size: 600 # 600 x 1s = 10 minutes rolling window
send_every: 1
send_first_at: 3 # after first 3 s average becomes meaningful
################################
##########################################################################################
# BEAUFORT SCALE
# Template for Beaufort for measuring wind speed
# The output updates the Text Sensor ID wind_scale
# https://windy.app/blog/wind-speed-beaufort-scale.html
################################
##########################################################################################
- platform: template
#name: ${friendly_name} Windspeed Scale
internal: true # no need to show this externally as it is just for calculating Beaufort
#icon: "mdi:weather-windy"
id: wind_meter_scale
lambda: return id(wind_meter).state;
lambda: |-
return id(wind_meter).state;
#unit_of_measurement: "m/s"
update_interval: 5s
filters:
@@ -602,7 +618,7 @@ sensor:
} else if (id(wind_meter_scale).state >= 33) {
id(wind_scale).publish_state("Hurricane Force");
} else {
id(wind_scale).publish_state({""});
id(wind_scale).publish_state("");
}
#############################################
@@ -614,69 +630,114 @@ sensor:
# Wind Direction, Analogue on ADS115
# Analog sensor for voltage reading
# https://esphome.io/components/sensor/ads1115.html
################################
##########################################################################################
# Using a analog wind direction sensor that has an output
# of 0 - 5 V for 360° representing the position of the arrow
# from the north pole. This is 5V divided by 8 resistor
# values giving the output voltage value for each azimuth.
# We could also report a compass direction.
################################
##########################################################################################
# Useful Links
# https://community.home-assistant.io/t/davis-wind-direction-equipment-and-esp-home/508764
################################
##########################################################################################
- platform: ads1115
multiplexer: 'A0_GND'
gain: 4.096
name: ${friendly_name} Wind Direction Voltage
id: wind_direction_voltage
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:
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");
} 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");
} 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");
} 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");
} 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");
} 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");
} 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");
} 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");
} 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:
# lambda: |-
@@ -797,88 +858,6 @@ sensor:
# 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
# pin:
# number: GPIO13 #This is Pin D7 on the D1 Mini ESP8266
@@ -979,69 +958,3 @@ sensor:
# type: float
# restore_value: no
# 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
+4 -4
View File
@@ -2,13 +2,13 @@ group:
simulation_lights_all:
name: Simulation Lights (All)
entities:
- group.downstairs_flat_lights # ← now valid here
- switch.esp_centralstairs_top_relay_2_stair_footer_lights
- switch.esp_centralstairs_bottom_relay_1_main_stair_lights_lower
- switch.esp_entrancebathrmlights_relay_1_main_lights
- group.downstairs_flat_lights # ← now valid here
- switch.tasmo_ks811d_1242_entrance_a
- switch.tasmo_ks811d_6110_kitchen_a
- switch.tasmo_ks811d_6110_kitchen_b
- switch.esp_entrancelightswitch_relay_1_entrance_lights
- switch.esp_mainkitchenlights_relay_1_main_lights
- switch.esp_mainkitchenlights_relay_2_benchtop_lights
- switch.main_hallway_lightswitch_tasmo_ks811s_2940_hallway_1a
- switch.tasmo_ks811t_0702_lounge_3a
- switch.tasmo_ks811t_0702_lounge_3b