various tidyups and esphome adds

This commit is contained in:
root
2026-02-16 22:13:42 +13:00
parent d7884770fe
commit 83e4040b84
16 changed files with 2251 additions and 113 deletions
+737
View File
@@ -0,0 +1,737 @@
##########################################################################################
##########################################################################################
# LOUNGE ENVIRONMENT SENSOR
#
# ESP32-C3 SuperMini + BME280 (I2C)
#
# V1.2 2026-02-16 Pressure tendency now 5-state over 3 hours + rate/hour, 1h removed
# V1.1 2026-02-16 Added derived min/max sensors and pressure trend text sensors
# V1.0 2026-02-16 Initial Version
##########################################################################################
##########################################################################################
substitutions:
# Device Naming
device_name: "esp-loungeenvironment"
friendly_name: "Lounge Environment"
description_comment: "Lounge Environment :: ESP32-C3 SuperMini + BME280"
device_area: "Lounge"
# Project Naming
project_name: "ESP32-C3 SuperMini.BME280"
project_version: "v1.2"
# Passwords
api_key: !secret esp-api_key
ota_pass: !secret esp-ota_pass
static_ip_address: !secret esp-loungeenvironment_ip
# General Settings
log_level: "WARN"
update_interval: "60s"
# I2C Settings
i2c_sda_pin: "GPIO1"
i2c_scl_pin: "GPIO3"
i2c_frequency: "100kHz"
bmp_address: "0x76"
# Pressure tendency thresholds (hPa over 3 hours)
# - falling rapidly: <= -6.0 hPa / 3h
# - falling: <= -1.0 hPa / 3h
# - steady: between -1.0 and +1.0 hPa / 3h
# - rising: >= +1.0 hPa / 3h
# - rising rapidly: >= +6.0 hPa / 3h
pressure_3h_steady_band: "1.0"
pressure_3h_rapid_threshold: "6.0"
##########################################################################################
# PACKAGES
##########################################################################################
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_mqtt: !include
file: common/mqtt_common.yaml
vars:
local_device_name: "${device_name}"
# Provides:
# - time id: sntp_time
# - global: current_mins (minutes since midnight, fallback supported)
# - text sensors: time_text, time_sync, device_last_restart
common_sntp: !include common/sntp_common.yaml
diag_basic: !include common/include_basic_diag_sensors.yaml
diag_more: !include common/include_more_diag_sensors.yaml
##########################################################################################
# ESPHome
##########################################################################################
esphome:
name: "${device_name}"
friendly_name: "${friendly_name}"
comment: "${description_comment}"
area: "${device_area}"
name_add_mac_suffix: false
min_version: 2026.1.0
project:
name: "${project_name}"
version: "${project_version}"
##########################################################################################
# PLATFORM
##########################################################################################
esp32:
board: esp32-c3-devkitm-1
variant: ESP32C3
framework:
type: esp-idf
cpu_frequency: 80MHz
preferences:
flash_write_interval: 5min
mdns:
disabled: false
##########################################################################################
# LOGGING
##########################################################################################
logger:
level: "${log_level}"
baud_rate: 0
##########################################################################################
# I2C
##########################################################################################
i2c:
sda: ${i2c_sda_pin}
scl: ${i2c_scl_pin}
scan: true
frequency: ${i2c_frequency}
##########################################################################################
# GLOBALS (tracking min/max across time windows)
##########################################################################################
globals:
# Last completed overnight window (23:00 -> 07:00) min temperature
- id: g_min_temp_today_overnight
type: float
restore_value: yes
initial_value: "NAN"
# Working overnight min temperature for the current 23:00 -> 07:00 window
- id: g_work_min_temp_overnight
type: float
restore_value: no
initial_value: "NAN"
# Last completed daytime window (07:00 -> 23:00) max temperature
- id: g_max_temp_today_daytime
type: float
restore_value: yes
initial_value: "NAN"
# Working daytime max temperature for the current 07:00 -> 23:00 window
- id: g_work_max_temp_daytime
type: float
restore_value: no
initial_value: "NAN"
# Last completed overnight window (23:00 -> 07:00) min humidity
- id: g_min_humidity_today_overnight
type: float
restore_value: yes
initial_value: "NAN"
# Working overnight min humidity for the current 23:00 -> 07:00 window
- id: g_work_min_humidity_overnight
type: float
restore_value: no
initial_value: "NAN"
# Last completed daytime window (07:00 -> 23:00) max humidity
- id: g_max_humidity_today_daytime
type: float
restore_value: yes
initial_value: "NAN"
# Working daytime max humidity for the current 07:00 -> 23:00 window
- id: g_work_max_humidity_daytime
type: float
restore_value: no
initial_value: "NAN"
# Current month max temperature
- id: g_max_temp_this_month
type: float
restore_value: yes
initial_value: "NAN"
# Last month max temperature
- id: g_max_temp_last_month
type: float
restore_value: yes
initial_value: "NAN"
# Current month min temperature
- id: g_min_temp_this_month
type: float
restore_value: yes
initial_value: "NAN"
# Last month min temperature
- id: g_min_temp_last_month
type: float
restore_value: yes
initial_value: "NAN"
# Current month max humidity
- id: g_max_humidity_this_month
type: float
restore_value: yes
initial_value: "NAN"
# Last month max humidity
- id: g_max_humidity_last_month
type: float
restore_value: yes
initial_value: "NAN"
# Current month min humidity
- id: g_min_humidity_this_month
type: float
restore_value: yes
initial_value: "NAN"
# Last month min humidity
- id: g_min_humidity_last_month
type: float
restore_value: yes
initial_value: "NAN"
# Track current calendar month/year so we can roll over month stats
- id: g_current_month
type: int
restore_value: yes
initial_value: "0"
- id: g_current_year
type: int
restore_value: yes
initial_value: "0"
# Hour-bucket rolling 24h min/max temperature
- id: g_temp_hour_min
type: std::array<float, 24>
restore_value: no
initial_value: "{NAN,NAN,NAN,NAN,NAN,NAN,NAN,NAN,NAN,NAN,NAN,NAN,NAN,NAN,NAN,NAN,NAN,NAN,NAN,NAN,NAN,NAN,NAN,NAN}"
- id: g_temp_hour_max
type: std::array<float, 24>
restore_value: no
initial_value: "{NAN,NAN,NAN,NAN,NAN,NAN,NAN,NAN,NAN,NAN,NAN,NAN,NAN,NAN,NAN,NAN,NAN,NAN,NAN,NAN,NAN,NAN,NAN,NAN}"
- id: g_temp_hour_ts
type: std::array<uint32_t, 24>
restore_value: no
initial_value: "{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}"
# Hour-bucket rolling 24h min/max humidity
- id: g_hum_hour_min
type: std::array<float, 24>
restore_value: no
initial_value: "{NAN,NAN,NAN,NAN,NAN,NAN,NAN,NAN,NAN,NAN,NAN,NAN,NAN,NAN,NAN,NAN,NAN,NAN,NAN,NAN,NAN,NAN,NAN,NAN}"
- id: g_hum_hour_max
type: std::array<float, 24>
restore_value: no
initial_value: "{NAN,NAN,NAN,NAN,NAN,NAN,NAN,NAN,NAN,NAN,NAN,NAN,NAN,NAN,NAN,NAN,NAN,NAN,NAN,NAN,NAN,NAN,NAN,NAN}"
- id: g_hum_hour_ts
type: std::array<uint32_t, 24>
restore_value: no
initial_value: "{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}"
# Pressure sampling (every 5 minutes) for trend calculation
- id: g_pressure_samples
type: std::array<float, 40>
restore_value: no
initial_value: "{NAN,NAN,NAN,NAN,NAN,NAN,NAN,NAN,NAN,NAN,NAN,NAN,NAN,NAN,NAN,NAN,NAN,NAN,NAN,NAN,NAN,NAN,NAN,NAN,NAN,NAN,NAN,NAN,NAN,NAN,NAN,NAN,NAN,NAN,NAN,NAN,NAN,NAN,NAN,NAN}"
- id: g_pressure_ts
type: std::array<uint32_t, 40>
restore_value: no
initial_value: "{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}"
- id: g_pressure_idx
type: int
restore_value: no
initial_value: "0"
##########################################################################################
# SENSORS
##########################################################################################
sensor:
- platform: bme280_i2c
address: ${bmp_address}
update_interval: 20s
temperature:
name: "${friendly_name} Temperature"
id: lounge_temperature
unit_of_measurement: "°C"
on_value:
then:
- lambda: |-
auto now = id(sntp_time).now();
// We only need hour buckets and month stats if we have valid SNTP.
if (!now.is_valid()) return;
const int hour = now.hour;
const uint32_t now_ts = now.timestamp;
// Update hourly bucket (rolling 24h)
{
int idx = hour;
if (idx < 0) idx = 0;
if (idx > 23) idx = 23;
id(g_temp_hour_ts)[idx] = now_ts;
float v = x;
if (isnan(id(g_temp_hour_min)[idx]) || v < id(g_temp_hour_min)[idx]) id(g_temp_hour_min)[idx] = v;
if (isnan(id(g_temp_hour_max)[idx]) || v > id(g_temp_hour_max)[idx]) id(g_temp_hour_max)[idx] = v;
}
// Daily window tracking uses current_mins so it still works during fallback.
// Overnight: 23:00 -> 07:00
// Daytime: 07:00 -> 23:00
const int mins = id(current_mins);
const bool in_overnight = (mins >= 1380) || (mins < 420);
if (in_overnight) {
if (isnan(id(g_work_min_temp_overnight)) || x < id(g_work_min_temp_overnight)) {
id(g_work_min_temp_overnight) = x;
}
} else {
if (isnan(id(g_work_max_temp_daytime)) || x > id(g_work_max_temp_daytime)) {
id(g_work_max_temp_daytime) = x;
}
}
// Month stats
if (isnan(id(g_min_temp_this_month)) || x < id(g_min_temp_this_month)) id(g_min_temp_this_month) = x;
if (isnan(id(g_max_temp_this_month)) || x > id(g_max_temp_this_month)) id(g_max_temp_this_month) = x;
pressure:
name: "${friendly_name} Pressure"
id: lounge_pressure
humidity:
name: "${friendly_name} Humidity"
id: lounge_humidity
on_value:
then:
- lambda: |-
auto now = id(sntp_time).now();
if (!now.is_valid()) return;
const int hour = now.hour;
const uint32_t now_ts = now.timestamp;
// Update hourly bucket (rolling 24h)
{
int idx = hour;
if (idx < 0) idx = 0;
if (idx > 23) idx = 23;
id(g_hum_hour_ts)[idx] = now_ts;
float v = x;
if (isnan(id(g_hum_hour_min)[idx]) || v < id(g_hum_hour_min)[idx]) id(g_hum_hour_min)[idx] = v;
if (isnan(id(g_hum_hour_max)[idx]) || v > id(g_hum_hour_max)[idx]) id(g_hum_hour_max)[idx] = v;
}
// Daily window tracking uses current_mins so it still works during fallback.
const int mins = id(current_mins);
const bool in_overnight = (mins >= 1380) || (mins < 420);
if (in_overnight) {
if (isnan(id(g_work_min_humidity_overnight)) || x < id(g_work_min_humidity_overnight)) {
id(g_work_min_humidity_overnight) = x;
}
} else {
if (isnan(id(g_work_max_humidity_daytime)) || x > id(g_work_max_humidity_daytime)) {
id(g_work_max_humidity_daytime) = x;
}
}
// Month stats
if (isnan(id(g_min_humidity_this_month)) || x < id(g_min_humidity_this_month)) id(g_min_humidity_this_month) = x;
if (isnan(id(g_max_humidity_this_month)) || x > id(g_max_humidity_this_month)) id(g_max_humidity_this_month) = x;
# min_temp_today_overnight (this is the min temp, from 11pm to 7am for the last daily period)
- platform: template
name: "Lowest Temp Overnight"
id: min_temp_today_overnight
unit_of_measurement: "°C"
update_interval: 60s
lambda: |-
return id(g_min_temp_today_overnight);
# max_temp_today_daytime (this is the max temp, from 7am to 11pm for the last daily period)
- platform: template
name: "Highest Temp Today (Daytime)"
id: max_temp_today_daytime
unit_of_measurement: "°C"
update_interval: 60s
lambda: |-
return id(g_max_temp_today_daytime);
# max_temp_this_month (max for this current calendar month)
- platform: template
name: "Highest Temp This Month"
id: max_temp_this_month
unit_of_measurement: "°C"
update_interval: 60s
lambda: |-
return id(g_max_temp_this_month);
# max_temp_last_month (max for this last calendar month)
- platform: template
name: "Highest Temp Last Month"
id: max_temp_last_month
unit_of_measurement: "°C"
update_interval: 60s
lambda: |-
return id(g_max_temp_last_month);
# min_temp_this_month (min for this current calendar month)
- platform: template
name: "Lowest Temp This Month"
id: min_temp_this_month
unit_of_measurement: "°C"
update_interval: 60s
lambda: |-
return id(g_min_temp_this_month);
# min_temp_last_month (min for this last calendar month)
- platform: template
name: "Lowest Temp Last Month"
id: min_temp_last_month
unit_of_measurement: "°C"
update_interval: 60s
lambda: |-
return id(g_min_temp_last_month);
# min_temp_today (min temp for last rolling 24 hours)
- platform: template
name: "Lowest Temp Last 24 Hours"
id: min_temp_today
unit_of_measurement: "°C"
update_interval: 60s
lambda: |-
auto now = id(sntp_time).now();
if (!now.is_valid()) return NAN;
const uint32_t now_ts = now.timestamp;
float m = NAN;
for (int i = 0; i < 24; i++) {
if (id(g_temp_hour_ts)[i] == 0) continue;
if ((now_ts - id(g_temp_hour_ts)[i]) > 86400) continue;
float v = id(g_temp_hour_min)[i];
if (isnan(v)) continue;
if (isnan(m) || v < m) m = v;
}
return m;
# max_temp_today (max temp for last rolling 24 hours)
- platform: template
name: "Highest Temp Last 24 Hours"
id: max_temp_today
unit_of_measurement: "°C"
update_interval: 60s
lambda: |-
auto now = id(sntp_time).now();
if (!now.is_valid()) return NAN;
const uint32_t now_ts = now.timestamp;
float m = NAN;
for (int i = 0; i < 24; i++) {
if (id(g_temp_hour_ts)[i] == 0) continue;
if ((now_ts - id(g_temp_hour_ts)[i]) > 86400) continue;
float v = id(g_temp_hour_max)[i];
if (isnan(v)) continue;
if (isnan(m) || v > m) m = v;
}
return m;
# min_humidity_today_overnight (this is the min humidity, from 11pm to 7am for the last daily period)
- platform: template
name: "Lowest Humidity Overnight"
id: min_humidity_today_overnight
unit_of_measurement: "%"
update_interval: 60s
lambda: |-
return id(g_min_humidity_today_overnight);
# max_humidity_today_daytime (this is the max humidity, from 7am to 11pm for the last daily period)
- platform: template
name: "Highest Humidity Today (Daytime)"
id: max_humidity_today_daytime
unit_of_measurement: "%"
update_interval: 60s
lambda: |-
return id(g_max_humidity_today_daytime);
# max_humidity_this_month (max for this current calendar month)
- platform: template
name: "Highest Humidity This Month"
id: max_humidity_this_month
unit_of_measurement: "%"
update_interval: 60s
lambda: |-
return id(g_max_humidity_this_month);
# max_humidity_last_month (max for this last calendar month)
- platform: template
name: "Highest Humidity Last Month"
id: max_humidity_last_month
unit_of_measurement: "%"
update_interval: 60s
lambda: |-
return id(g_max_humidity_last_month);
# min_humidity_this_month (min for this current calendar month)
- platform: template
name: "Lowest Humidity This Month"
id: min_humidity_this_month
unit_of_measurement: "%"
update_interval: 60s
lambda: |-
return id(g_min_humidity_this_month);
# min_humidity_last_month (min for this last calendar month)
- platform: template
name: "Lowest Humidity Last Month"
id: min_humidity_last_month
unit_of_measurement: "%"
update_interval: 60s
lambda: |-
return id(g_min_humidity_last_month);
# min_humidity_today (min humidity for last rolling 24 hours)
- platform: template
name: "Lowest Humidity Last 24 Hours"
id: min_humidity_today
unit_of_measurement: "%"
update_interval: 60s
lambda: |-
auto now = id(sntp_time).now();
if (!now.is_valid()) return NAN;
const uint32_t now_ts = now.timestamp;
float m = NAN;
for (int i = 0; i < 24; i++) {
if (id(g_hum_hour_ts)[i] == 0) continue;
if ((now_ts - id(g_hum_hour_ts)[i]) > 86400) continue;
float v = id(g_hum_hour_min)[i];
if (isnan(v)) continue;
if (isnan(m) || v < m) m = v;
}
return m;
# max_humidity_today (max humidity for last rolling 24 hours)
- platform: template
name: "Highest Humidity Last 24 Hours"
id: max_humidity_today
unit_of_measurement: "%"
update_interval: 60s
lambda: |-
auto now = id(sntp_time).now();
if (!now.is_valid()) return NAN;
const uint32_t now_ts = now.timestamp;
float m = NAN;
for (int i = 0; i < 24; i++) {
if (id(g_hum_hour_ts)[i] == 0) continue;
if ((now_ts - id(g_hum_hour_ts)[i]) > 86400) continue;
float v = id(g_hum_hour_max)[i];
if (isnan(v)) continue;
if (isnan(m) || v > m) m = v;
}
return m;
# Pressure Rate Per Hour (3h) (hPa/hour based on delta over the last ~3 hours)
- platform: template
name: "Pressure Rate Per Hour (3h)"
id: pressure_rate_per_hour_3h
unit_of_measurement: "hPa/h"
accuracy_decimals: 2
update_interval: 60s
lambda: |-
auto now = id(sntp_time).now();
if (!now.is_valid()) return NAN;
const uint32_t now_ts = now.timestamp;
const uint32_t target_ts = now_ts - 10800; // 3h
float past = NAN;
uint32_t best_dt = 0xFFFFFFFF;
for (int i = 0; i < 40; i++) {
uint32_t ts = id(g_pressure_ts)[i];
if (ts == 0) continue;
uint32_t dt = (ts > target_ts) ? (ts - target_ts) : (target_ts - ts);
if (dt < best_dt) {
best_dt = dt;
past = id(g_pressure_samples)[i];
}
}
float cur = id(lounge_pressure).state;
if (isnan(cur) || isnan(past)) return NAN;
float delta_3h = cur - past;
return delta_3h / 3.0f;
##########################################################################################
# TEXT SENSORS (pressure tendency)
##########################################################################################
text_sensor:
# Pressure Direction, Last 3 Hours (5-state tendency over the last ~3 hours)
# Outputs exactly one of:
# - rising rapidly
# - rising
# - steady
# - falling
# - falling rapidly
- platform: template
name: "Pressure Direction, Last 3 Hours"
id: pressure_rising_or_falling_3_hours
update_interval: 60s
lambda: |-
auto now = id(sntp_time).now();
if (!now.is_valid()) return std::string("unknown");
const uint32_t now_ts = now.timestamp;
const uint32_t target_ts = now_ts - 10800; // 3h
float past = NAN;
uint32_t best_dt = 0xFFFFFFFF;
for (int i = 0; i < 40; i++) {
uint32_t ts = id(g_pressure_ts)[i];
if (ts == 0) continue;
uint32_t dt = (ts > target_ts) ? (ts - target_ts) : (target_ts - ts);
if (dt < best_dt) {
best_dt = dt;
past = id(g_pressure_samples)[i];
}
}
float cur = id(lounge_pressure).state;
if (isnan(cur) || isnan(past)) return std::string("unknown");
float delta_3h = cur - past;
float steady_band = atof("${pressure_3h_steady_band}");
float rapid_thr = atof("${pressure_3h_rapid_threshold}");
if (delta_3h >= rapid_thr) return std::string("rising rapidly");
if (delta_3h >= steady_band) return std::string("rising");
if (delta_3h <= -rapid_thr) return std::string("falling rapidly");
if (delta_3h <= -steady_band) return std::string("falling");
return std::string("steady");
##########################################################################################
# INTERVALS (rollover overnight/daytime periods, month rollover, and pressure sampling)
##########################################################################################
interval:
# Sample pressure every 5 minutes for trend calculations
- interval: 5min
then:
- lambda: |-
auto now = id(sntp_time).now();
if (!now.is_valid()) return;
float p = id(lounge_pressure).state;
if (isnan(p)) return;
const uint32_t ts = now.timestamp;
int idx = id(g_pressure_idx);
if (idx < 0) idx = 0;
if (idx > 39) idx = 0;
id(g_pressure_samples)[idx] = p;
id(g_pressure_ts)[idx] = ts;
idx++;
if (idx >= 40) idx = 0;
id(g_pressure_idx) = idx;
# Check period boundaries once a minute (uses current_mins, works during fallback)
- interval: 60s
then:
- lambda: |-
const int mins = id(current_mins);
const bool at_0700 = (mins == 420);
const bool at_2300 = (mins == 1380);
// Roll overnight window at 07:00
if (at_0700) {
id(g_min_temp_today_overnight) = id(g_work_min_temp_overnight);
id(g_work_min_temp_overnight) = NAN;
id(g_min_humidity_today_overnight) = id(g_work_min_humidity_overnight);
id(g_work_min_humidity_overnight) = NAN;
}
// Roll daytime window at 23:00
if (at_2300) {
id(g_max_temp_today_daytime) = id(g_work_max_temp_daytime);
id(g_work_max_temp_daytime) = NAN;
id(g_max_humidity_today_daytime) = id(g_work_max_humidity_daytime);
id(g_work_max_humidity_daytime) = NAN;
}
// Month rollover requires real date/time (SNTP valid)
auto now = id(sntp_time).now();
if (!now.is_valid()) return;
const int mon = now.month;
const int yr = now.year;
if (id(g_current_month) == 0 || id(g_current_year) == 0) {
id(g_current_month) = mon;
id(g_current_year) = yr;
}
if (mon != id(g_current_month) || yr != id(g_current_year)) {
// Copy current month stats to last month
id(g_max_temp_last_month) = id(g_max_temp_this_month);
id(g_min_temp_last_month) = id(g_min_temp_this_month);
id(g_max_humidity_last_month) = id(g_max_humidity_this_month);
id(g_min_humidity_last_month) = id(g_min_humidity_this_month);
// Reset current month stats
id(g_max_temp_this_month) = NAN;
id(g_min_temp_this_month) = NAN;
id(g_max_humidity_this_month) = NAN;
id(g_min_humidity_this_month) = NAN;
id(g_current_month) = mon;
id(g_current_year) = yr;
}