Files
zorruno-homeassistant/esphome/esp-mainpowermonitor.yaml
T
2026-02-23 21:01:07 +13:00

1125 lines
37 KiB
YAML

##########################################################################################
##########################################################################################
# MAIN HOUSE 3 PHASE POWER MONITOR
# Power Monitor using 3x PZEM004 for 3 phase monitoring plus energy monitoring for 32A EV Charger
#
# Hardware: Wemos D1 mini (ESP8266) + 3x PZEM-004T V3 (Modbus) + DHT11 + EV Pulse Input
#
# V1.1 2026-02-21 Fix globals array init compile error, reduce flash writes (globals update_interval)
# V1.0 2026-02-21 Initial Version
#
# NOTES
# - PZEM-004T V3 uses Modbus over UART at 9600 baud.
# - UART is on GPIO1 (TX) and GPIO3 (RX). Logger UART is disabled (baud_rate: 0).
# - You will usually need to disconnect PZEM UART wires while flashing via USB.
# - DHT11 is on GPIO2 (boot-strap pin and D1 mini onboard LED pin). Usually OK, but if you ever
# get boot issues or flaky DHT readings, move DHT to a different GPIO.
# - PZEM "energy" from ESPHome pzemac is in Wh, converted to kWh in this YAML.
# - Phase mapping requested: Red=0, Yellow=1, Blue=2 (Tasmota-style). In ESPHome pzemac Modbus
# addresses must be 0x01..0xF7, so this YAML maps to Red=0x01, Yellow=0x02, Blue=0x03.
# - 365-day history array is RAM-only (not restored) to avoid ESP8266 restore limits and flash wear.
##########################################################################################
##########################################################################################
##########################################################################################
# SPECIFIC DEVICE VARIABLE SUBSTITUTIONS
##########################################################################################
substitutions:
# Device Naming
device_name: "esp-mainpowermonitor"
friendly_name: "Main House 3 Phase Power Monitor"
description_comment: "Power Monitor using 3x PZEM004 for 3 phase monitoring plus energy monitoring for 32A EV Charger"
device_area: "Data Cupboard"
# Project Naming
project_name: "Wemos.D1Mini PZEM-004T V3 3-Phase Power Monitor"
project_version: "v1.1"
# Passwords
api_key: !secret esp-api_key
ota_pass: !secret esp-ota_pass
static_ip_address: !secret esp-mainpowermonitor_ip
mqtt_command_main_topic: !secret mqtt_command_main_topic
# If we are changing IP addresses, you must update the current IP address here, otherwise it remains
# Don't forget to switch it back when changed.
current_ip_address: ${static_ip_address}
# MQTT Controls
#mqtt_device_name: "main-house-3phase-power"
#mqtt_main_topic: "${mqtt_command_main_topic}/${mqtt_device_name}"
# Device Settings
log_level: "ERROR"
pzem_update_interval: "10s"
env_update_interval: "60s"
rollover_check_interval: "30s"
# Globals persistence tuning (gentler on flash)
globals_save_interval_slow: "10min"
globals_save_interval_pulses: "2min"
# PZEM Modbus Addresses (valid 0x01..0xF7)
# Requested phase IDs: Red=0, Yellow=1, Blue=2 -> map to 0x01, 0x02, 0x03
pzem_addr_red: "0x01"
pzem_addr_yellow: "0x02"
pzem_addr_blue: "0x03"
# EV Pulse Settings
ev_pulse_wh: "2.0" # Each pulse is 2Wh
ev_pulse_filter_ms: "50ms" # Debounce / filter
ev_pulse_pin_mode: "INPUT_PULLUP"
# Names - PZEM (per phase)
pzem_red_v_name: "Main Power Red Voltage"
pzem_red_i_name: "Main Power Red Current"
pzem_red_p_name: "Main Power Red Active Power"
pzem_red_e_name: "Main Power Red Active Energy"
pzem_red_f_name: "Main Power Red Frequency"
pzem_red_pf_name: "Main Power Red Power Factor"
pzem_yellow_v_name: "Main Power Yellow Voltage"
pzem_yellow_i_name: "Main Power Yellow Current"
pzem_yellow_p_name: "Main Power Yellow Active Power"
pzem_yellow_e_name: "Main Power Yellow Active Energy"
pzem_yellow_f_name: "Main Power Yellow Frequency"
pzem_yellow_pf_name: "Main Power Yellow Power Factor"
pzem_blue_v_name: "Main Power Blue Voltage"
pzem_blue_i_name: "Main Power Blue Current"
pzem_blue_p_name: "Main Power Blue Active Power"
pzem_blue_e_name: "Main Power Blue Active Energy"
pzem_blue_f_name: "Main Power Blue Frequency"
pzem_blue_pf_name: "Main Power Blue Power Factor"
# Names - Totals
mains_total_p_name: "Main Power Total Active Power"
mains_total_s_name: "Main Power Total Apparent Power"
mains_total_pf_name: "Main Power Total Power Factor"
mains_total_e_name: "Main Power Total Active Energy"
mains_today_name: "Main Power Energy Today"
mains_yesterday_name: "Main Power Energy Yesterday"
mains_month_name: "Main Power Energy This Month"
mains_last_month_name: "Main Power Energy Last Month"
mains_year_name: "Main Power Energy This Year"
mains_last_year_name: "Main Power Energy Last Year"
mains_daily_max_365_name: "Main Power Daily Energy Max (365d)"
mains_daily_min_365_name: "Main Power Daily Energy Min (365d)"
# Names - EV Wallcharger
ev_power_name: "EV Wallcharger Power"
ev_total_energy_name: "EV Wallcharger Total Energy"
ev_today_name: "EV Wallcharger Energy Today"
ev_yesterday_name: "EV Wallcharger Energy Yesterday"
ev_month_name: "EV Wallcharger Energy This Month"
ev_last_month_name: "EV Wallcharger Energy Last Month"
ev_year_name: "EV Wallcharger Energy This Year"
ev_last_year_name: "EV Wallcharger Energy Last Year"
# Names - Environment
temp_name: "Main Power Monitor Temperature"
hum_name: "Main Power Monitor Humidity"
dew_name: "Main Power Monitor Dew Point"
##########################################################################################
# PACKAGES: Included Common Packages
##########################################################################################
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}"
local_current_ip_address: "${current_ip_address}"
#### HOME ASSISTANT API ####
common_api: !include
file: common/api_common.yaml
vars:
local_api_key: "${api_key}"
#### MQTT ####
common_mqtt: !include
file: common/mqtt_common.yaml
vars:
local_device_name: "${device_name}"
#### SNTP ####
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
##########################################################################################
# ESPHome
##########################################################################################
esphome:
name: "${device_name}"
friendly_name: "${friendly_name}"
comment: "${description_comment}"
area: "${device_area}"
project:
name: "${project_name}"
version: "${project_version}"
on_boot:
priority: -100
then:
- script.execute: init_history_arrays
- delay: 3s
- script.execute: rollover_check
##########################################################################################
# ESP Platform and Framework
##########################################################################################
esp8266:
board: d1_mini
restore_from_flash: true
preferences:
flash_write_interval: 5min
mdns:
disabled: false
##########################################################################################
# LOGGING
##########################################################################################
logger:
level: "${log_level}"
baud_rate: 0
##########################################################################################
# UART + MODBUS (PZEM-004T V3)
##########################################################################################
uart:
id: uart_pzem_bus
tx_pin: GPIO1
rx_pin: GPIO3
baud_rate: 9600
modbus:
id: modbus_pzem_bus
send_wait_time: 200ms
##########################################################################################
# GLOBALS (Energy rollovers + 365-day history + temp min/max)
##########################################################################################
globals:
# Date tracking for rollovers (restored)
- id: g_last_day
type: int
restore_value: yes
update_interval: ${globals_save_interval_slow}
initial_value: "0"
- id: g_last_month
type: int
restore_value: yes
update_interval: ${globals_save_interval_slow}
initial_value: "0"
- id: g_last_year
type: int
restore_value: yes
update_interval: ${globals_save_interval_slow}
initial_value: "0"
# Main (3-phase) baseline totals for period calculations (restored)
- id: g_mains_day_start_kwh
type: float
restore_value: yes
update_interval: ${globals_save_interval_slow}
initial_value: "-1.0"
- id: g_mains_month_start_kwh
type: float
restore_value: yes
update_interval: ${globals_save_interval_slow}
initial_value: "-1.0"
- id: g_mains_year_start_kwh
type: float
restore_value: yes
update_interval: ${globals_save_interval_slow}
initial_value: "-1.0"
# Stored prior periods (restored)
- id: g_mains_yesterday_kwh
type: float
restore_value: yes
update_interval: ${globals_save_interval_slow}
initial_value: "0.0"
- id: g_mains_last_month_kwh
type: float
restore_value: yes
update_interval: ${globals_save_interval_slow}
initial_value: "0.0"
- id: g_mains_last_year_kwh
type: float
restore_value: yes
update_interval: ${globals_save_interval_slow}
initial_value: "0.0"
# Daily history ring buffer (last 365 days) - RAM ONLY (NOT restored)
# NOTE: Array globals MUST NOT use initial_value: "{}" (causes compile error).
- id: g_mains_daily_hist_kwh
type: float[365]
restore_value: no
- id: g_mains_daily_hist_index
type: int
restore_value: no
initial_value: "0"
- id: g_mains_daily_hist_count
type: int
restore_value: no
initial_value: "0"
# EV baseline totals for period calculations (restored)
- id: g_ev_day_start_kwh
type: float
restore_value: yes
update_interval: ${globals_save_interval_slow}
initial_value: "-1.0"
- id: g_ev_month_start_kwh
type: float
restore_value: yes
update_interval: ${globals_save_interval_slow}
initial_value: "-1.0"
- id: g_ev_year_start_kwh
type: float
restore_value: yes
update_interval: ${globals_save_interval_slow}
initial_value: "-1.0"
# EV stored prior periods (restored)
- id: g_ev_yesterday_kwh
type: float
restore_value: yes
update_interval: ${globals_save_interval_slow}
initial_value: "0.0"
- id: g_ev_last_month_kwh
type: float
restore_value: yes
update_interval: ${globals_save_interval_slow}
initial_value: "0.0"
- id: g_ev_last_year_kwh
type: float
restore_value: yes
update_interval: ${globals_save_interval_slow}
initial_value: "0.0"
# EV pulse accumulation (persisted across reboots) (restored)
- id: g_ev_pulses_total_abs
type: uint32_t
restore_value: yes
update_interval: ${globals_save_interval_pulses}
initial_value: "0"
- id: g_ev_pulses_raw_last
type: uint32_t
restore_value: yes
update_interval: ${globals_save_interval_pulses}
initial_value: "0"
# Temperature min/max tracking (RAM ONLY - not restored, gentler on flash)
- id: g_temp_today_min
type: float
restore_value: no
initial_value: "9999.0"
- id: g_temp_today_max
type: float
restore_value: no
initial_value: "-9999.0"
- id: g_temp_yesterday_min
type: float
restore_value: no
initial_value: "9999.0"
- id: g_temp_yesterday_max
type: float
restore_value: no
initial_value: "-9999.0"
- id: g_temp_month_min
type: float
restore_value: no
initial_value: "9999.0"
- id: g_temp_month_max
type: float
restore_value: no
initial_value: "-9999.0"
- id: g_temp_last_month_min
type: float
restore_value: no
initial_value: "9999.0"
- id: g_temp_last_month_max
type: float
restore_value: no
initial_value: "-9999.0"
##########################################################################################
# INTERVAL (periodic rollover checks)
# NOTE: common_sntp may define its own interval. That is OK.
##########################################################################################
interval:
- interval: "${rollover_check_interval}"
then:
- script.execute: rollover_check
##########################################################################################
# SENSORS
##########################################################################################
sensor:
########################################################################################
# PZEM-004T V3 - RED PHASE
########################################################################################
- platform: pzemac
id: pzem_red
modbus_id: modbus_pzem_bus
address: ${pzem_addr_red}
update_interval: ${pzem_update_interval}
voltage:
name: "${pzem_red_v_name}"
id: pzem_red_voltage
accuracy_decimals: 1
current:
name: "${pzem_red_i_name}"
id: pzem_red_current
accuracy_decimals: 3
power:
name: "${pzem_red_p_name}"
id: pzem_red_power_w
accuracy_decimals: 0
energy:
name: "${pzem_red_e_name}"
id: pzem_red_energy_kwh
unit_of_measurement: "kWh"
accuracy_decimals: 3
filters:
- multiply: 0.001
frequency:
name: "${pzem_red_f_name}"
id: pzem_red_freq_hz
accuracy_decimals: 1
power_factor:
name: "${pzem_red_pf_name}"
id: pzem_red_pf
accuracy_decimals: 2
- platform: template
name: "Main Power Red Apparent Power"
id: pzem_red_apparent_power_va
unit_of_measurement: "VA"
accuracy_decimals: 0
update_interval: ${pzem_update_interval}
lambda: |-
if (isnan(id(pzem_red_voltage).state) || isnan(id(pzem_red_current).state)) return NAN;
return id(pzem_red_voltage).state * id(pzem_red_current).state;
- platform: template
name: "Main Power Red Reactive Power"
id: pzem_red_reactive_power_var
unit_of_measurement: "var"
accuracy_decimals: 0
update_interval: ${pzem_update_interval}
lambda: |-
const float p = id(pzem_red_power_w).state;
const float s = id(pzem_red_apparent_power_va).state;
if (isnan(p) || isnan(s)) return NAN;
const float d = (s * s) - (p * p);
if (d <= 0.0f) return 0.0f;
return sqrtf(d);
- platform: integration
name: "Main Power Red Apparent Energy"
id: pzem_red_apparent_energy_kvah
sensor: pzem_red_apparent_power_va
time_unit: h
unit_of_measurement: "kVAh"
accuracy_decimals: 3
filters:
- multiply: 0.001
########################################################################################
# PZEM-004T V3 - YELLOW PHASE
########################################################################################
- platform: pzemac
id: pzem_yellow
modbus_id: modbus_pzem_bus
address: ${pzem_addr_yellow}
update_interval: ${pzem_update_interval}
voltage:
name: "${pzem_yellow_v_name}"
id: pzem_yellow_voltage
accuracy_decimals: 1
current:
name: "${pzem_yellow_i_name}"
id: pzem_yellow_current
accuracy_decimals: 3
power:
name: "${pzem_yellow_p_name}"
id: pzem_yellow_power_w
accuracy_decimals: 0
energy:
name: "${pzem_yellow_e_name}"
id: pzem_yellow_energy_kwh
unit_of_measurement: "kWh"
accuracy_decimals: 3
filters:
- multiply: 0.001
frequency:
name: "${pzem_yellow_f_name}"
id: pzem_yellow_freq_hz
accuracy_decimals: 1
power_factor:
name: "${pzem_yellow_pf_name}"
id: pzem_yellow_pf
accuracy_decimals: 2
- platform: template
name: "Main Power Yellow Apparent Power"
id: pzem_yellow_apparent_power_va
unit_of_measurement: "VA"
accuracy_decimals: 0
update_interval: ${pzem_update_interval}
lambda: |-
if (isnan(id(pzem_yellow_voltage).state) || isnan(id(pzem_yellow_current).state)) return NAN;
return id(pzem_yellow_voltage).state * id(pzem_yellow_current).state;
- platform: template
name: "Main Power Yellow Reactive Power"
id: pzem_yellow_reactive_power_var
unit_of_measurement: "var"
accuracy_decimals: 0
update_interval: ${pzem_update_interval}
lambda: |-
const float p = id(pzem_yellow_power_w).state;
const float s = id(pzem_yellow_apparent_power_va).state;
if (isnan(p) || isnan(s)) return NAN;
const float d = (s * s) - (p * p);
if (d <= 0.0f) return 0.0f;
return sqrtf(d);
- platform: integration
name: "Main Power Yellow Apparent Energy"
id: pzem_yellow_apparent_energy_kvah
sensor: pzem_yellow_apparent_power_va
time_unit: h
unit_of_measurement: "kVAh"
accuracy_decimals: 3
filters:
- multiply: 0.001
########################################################################################
# PZEM-004T V3 - BLUE PHASE
########################################################################################
- platform: pzemac
id: pzem_blue
modbus_id: modbus_pzem_bus
address: ${pzem_addr_blue}
update_interval: ${pzem_update_interval}
voltage:
name: "${pzem_blue_v_name}"
id: pzem_blue_voltage
accuracy_decimals: 1
current:
name: "${pzem_blue_i_name}"
id: pzem_blue_current
accuracy_decimals: 3
power:
name: "${pzem_blue_p_name}"
id: pzem_blue_power_w
accuracy_decimals: 0
energy:
name: "${pzem_blue_e_name}"
id: pzem_blue_energy_kwh
unit_of_measurement: "kWh"
accuracy_decimals: 3
filters:
- multiply: 0.001
frequency:
name: "${pzem_blue_f_name}"
id: pzem_blue_freq_hz
accuracy_decimals: 1
power_factor:
name: "${pzem_blue_pf_name}"
id: pzem_blue_pf
accuracy_decimals: 2
- platform: template
name: "Main Power Blue Apparent Power"
id: pzem_blue_apparent_power_va
unit_of_measurement: "VA"
accuracy_decimals: 0
update_interval: ${pzem_update_interval}
lambda: |-
if (isnan(id(pzem_blue_voltage).state) || isnan(id(pzem_blue_current).state)) return NAN;
return id(pzem_blue_voltage).state * id(pzem_blue_current).state;
- platform: template
name: "Main Power Blue Reactive Power"
id: pzem_blue_reactive_power_var
unit_of_measurement: "var"
accuracy_decimals: 0
update_interval: ${pzem_update_interval}
lambda: |-
const float p = id(pzem_blue_power_w).state;
const float s = id(pzem_blue_apparent_power_va).state;
if (isnan(p) || isnan(s)) return NAN;
const float d = (s * s) - (p * p);
if (d <= 0.0f) return 0.0f;
return sqrtf(d);
- platform: integration
name: "Main Power Blue Apparent Energy"
id: pzem_blue_apparent_energy_kvah
sensor: pzem_blue_apparent_power_va
time_unit: h
unit_of_measurement: "kVAh"
accuracy_decimals: 3
filters:
- multiply: 0.001
########################################################################################
# 3-PHASE TOTALS (Active/Real + Apparent + Reactive + PF)
########################################################################################
- platform: template
name: "${mains_total_p_name}"
id: mains_total_active_power_w
unit_of_measurement: "W"
accuracy_decimals: 0
update_interval: ${pzem_update_interval}
lambda: |-
if (isnan(id(pzem_red_power_w).state) || isnan(id(pzem_yellow_power_w).state) || isnan(id(pzem_blue_power_w).state)) return NAN;
return id(pzem_red_power_w).state + id(pzem_yellow_power_w).state + id(pzem_blue_power_w).state;
- platform: template
name: "${mains_total_s_name}"
id: mains_total_apparent_power_va
unit_of_measurement: "VA"
accuracy_decimals: 0
update_interval: ${pzem_update_interval}
lambda: |-
if (isnan(id(pzem_red_apparent_power_va).state) || isnan(id(pzem_yellow_apparent_power_va).state) || isnan(id(pzem_blue_apparent_power_va).state)) return NAN;
return id(pzem_red_apparent_power_va).state + id(pzem_yellow_apparent_power_va).state + id(pzem_blue_apparent_power_va).state;
- platform: template
name: "Main Power Total Reactive Power"
id: mains_total_reactive_power_var
unit_of_measurement: "var"
accuracy_decimals: 0
update_interval: ${pzem_update_interval}
lambda: |-
if (isnan(id(pzem_red_reactive_power_var).state) || isnan(id(pzem_yellow_reactive_power_var).state) || isnan(id(pzem_blue_reactive_power_var).state)) return NAN;
return id(pzem_red_reactive_power_var).state + id(pzem_yellow_reactive_power_var).state + id(pzem_blue_reactive_power_var).state;
- platform: template
name: "${mains_total_pf_name}"
id: mains_total_pf
accuracy_decimals: 2
update_interval: ${pzem_update_interval}
lambda: |-
const float p = id(mains_total_active_power_w).state;
const float s = id(mains_total_apparent_power_va).state;
if (isnan(p) || isnan(s) || s <= 0.0f) return NAN;
float pf = p / s;
if (pf < 0.0f) pf = 0.0f;
if (pf > 1.0f) pf = 1.0f;
return pf;
filters:
- round: 2
- platform: template
name: "${mains_total_e_name}"
id: mains_total_active_energy_kwh
unit_of_measurement: "kWh"
accuracy_decimals: 3
update_interval: ${pzem_update_interval}
lambda: |-
if (isnan(id(pzem_red_energy_kwh).state) || isnan(id(pzem_yellow_energy_kwh).state) || isnan(id(pzem_blue_energy_kwh).state)) return NAN;
return id(pzem_red_energy_kwh).state + id(pzem_yellow_energy_kwh).state + id(pzem_blue_energy_kwh).state;
########################################################################################
# MAIN ENERGY PERIOD TOTALS (Today / Yesterday / Month / Year)
########################################################################################
- platform: template
name: "${mains_today_name}"
id: mains_energy_today_kwh
unit_of_measurement: "kWh"
accuracy_decimals: 3
update_interval: 60s
lambda: |-
const float total = id(mains_total_active_energy_kwh).state;
if (isnan(total) || id(g_mains_day_start_kwh) < 0.0f) return NAN;
float v = total - id(g_mains_day_start_kwh);
if (v < 0.0f) v = 0.0f;
return v;
- platform: template
name: "${mains_yesterday_name}"
id: mains_energy_yesterday_kwh
unit_of_measurement: "kWh"
accuracy_decimals: 3
update_interval: 5min
lambda: |-
return id(g_mains_yesterday_kwh);
- platform: template
name: "${mains_month_name}"
id: mains_energy_this_month_kwh
unit_of_measurement: "kWh"
accuracy_decimals: 3
update_interval: 5min
lambda: |-
const float total = id(mains_total_active_energy_kwh).state;
if (isnan(total) || id(g_mains_month_start_kwh) < 0.0f) return NAN;
float v = total - id(g_mains_month_start_kwh);
if (v < 0.0f) v = 0.0f;
return v;
- platform: template
name: "${mains_last_month_name}"
id: mains_energy_last_month_kwh
unit_of_measurement: "kWh"
accuracy_decimals: 3
update_interval: 10min
lambda: |-
return id(g_mains_last_month_kwh);
- platform: template
name: "${mains_year_name}"
id: mains_energy_this_year_kwh
unit_of_measurement: "kWh"
accuracy_decimals: 3
update_interval: 10min
lambda: |-
const float total = id(mains_total_active_energy_kwh).state;
if (isnan(total) || id(g_mains_year_start_kwh) < 0.0f) return NAN;
float v = total - id(g_mains_year_start_kwh);
if (v < 0.0f) v = 0.0f;
return v;
- platform: template
name: "${mains_last_year_name}"
id: mains_energy_last_year_kwh
unit_of_measurement: "kWh"
accuracy_decimals: 3
update_interval: 10min
lambda: |-
return id(g_mains_last_year_kwh);
- platform: template
name: "${mains_daily_max_365_name}"
id: mains_daily_energy_max_365d_kwh
unit_of_measurement: "kWh"
accuracy_decimals: 3
update_interval: 10min
lambda: |-
if (id(g_mains_daily_hist_count) <= 0) return NAN;
float maxv = NAN;
for (int i = 0; i < 365; i++) {
const float v = id(g_mains_daily_hist_kwh)[i];
if (isnan(v)) continue;
if (isnan(maxv) || v > maxv) maxv = v;
}
return maxv;
- platform: template
name: "${mains_daily_min_365_name}"
id: mains_daily_energy_min_365d_kwh
unit_of_measurement: "kWh"
accuracy_decimals: 3
update_interval: 10min
lambda: |-
if (id(g_mains_daily_hist_count) <= 0) return NAN;
float minv = NAN;
for (int i = 0; i < 365; i++) {
const float v = id(g_mains_daily_hist_kwh)[i];
if (isnan(v)) continue;
if (isnan(minv) || v < minv) minv = v;
}
return minv;
########################################################################################
# DHT11 (Temp / Humidity / Dewpoint) + TEMP MIN/MAX (Today / Yesterday / Month / Last Month)
########################################################################################
- platform: dht
pin: GPIO2
model: DHT11
update_interval: ${env_update_interval}
temperature:
name: "${temp_name}"
id: env_temp
unit_of_measurement: "°C"
accuracy_decimals: 1
on_value:
then:
- script.execute: update_temp_minmax
humidity:
name: "${hum_name}"
id: env_humidity
unit_of_measurement: "%"
accuracy_decimals: 0
- platform: template
name: "${dew_name}"
id: env_dewpoint
unit_of_measurement: "°C"
accuracy_decimals: 1
update_interval: ${env_update_interval}
lambda: |-
const float t = id(env_temp).state;
const float rh = id(env_humidity).state;
if (isnan(t) || isnan(rh) || rh <= 0.0f) return NAN;
const float h = rh / 100.0f;
const float a = 17.67f;
const float b = 243.5f;
const float gamma = (a * t) / (b + t) + logf(h);
return (b * gamma) / (a - gamma);
- platform: template
name: "Main Power Temp Min Today"
id: temp_min_today
unit_of_measurement: "°C"
accuracy_decimals: 1
update_interval: 2min
lambda: |-
return (id(g_temp_today_min) > 9000.0f) ? NAN : id(g_temp_today_min);
- platform: template
name: "Main Power Temp Max Today"
id: temp_max_today
unit_of_measurement: "°C"
accuracy_decimals: 1
update_interval: 2min
lambda: |-
return (id(g_temp_today_max) < -9000.0f) ? NAN : id(g_temp_today_max);
- platform: template
name: "Main Power Temp Min Yesterday"
id: temp_min_yesterday
unit_of_measurement: "°C"
accuracy_decimals: 1
update_interval: 10min
lambda: |-
return (id(g_temp_yesterday_min) > 9000.0f) ? NAN : id(g_temp_yesterday_min);
- platform: template
name: "Main Power Temp Max Yesterday"
id: temp_max_yesterday
unit_of_measurement: "°C"
accuracy_decimals: 1
update_interval: 10min
lambda: |-
return (id(g_temp_yesterday_max) < -9000.0f) ? NAN : id(g_temp_yesterday_max);
- platform: template
name: "Main Power Temp Min This Month"
id: temp_min_month
unit_of_measurement: "°C"
accuracy_decimals: 1
update_interval: 30min
lambda: |-
return (id(g_temp_month_min) > 9000.0f) ? NAN : id(g_temp_month_min);
- platform: template
name: "Main Power Temp Max This Month"
id: temp_max_month
unit_of_measurement: "°C"
accuracy_decimals: 1
update_interval: 30min
lambda: |-
return (id(g_temp_month_max) < -9000.0f) ? NAN : id(g_temp_month_max);
- platform: template
name: "Main Power Temp Min Last Month"
id: temp_min_last_month
unit_of_measurement: "°C"
accuracy_decimals: 1
update_interval: 60min
lambda: |-
return (id(g_temp_last_month_min) > 9000.0f) ? NAN : id(g_temp_last_month_min);
- platform: template
name: "Main Power Temp Max Last Month"
id: temp_max_last_month
unit_of_measurement: "°C"
accuracy_decimals: 1
update_interval: 60min
lambda: |-
return (id(g_temp_last_month_max) < -9000.0f) ? NAN : id(g_temp_last_month_max);
########################################################################################
# EV WALLCHARGER PULSE ENERGY (GPIO14)
# - pulse_counter reports pulses/minute
# - Power (W) = pulses_per_min * (Wh_per_pulse) * 60
########################################################################################
- platform: pulse_counter
id: ev_pulse_rate_ppm
pin:
number: GPIO14
mode: ${ev_pulse_pin_mode}
inverted: false
update_interval: 10s
internal_filter: ${ev_pulse_filter_ms}
name: "${ev_power_name}"
unit_of_measurement: "W"
accuracy_decimals: 0
filters:
- lambda: |-
return x * (${ev_pulse_wh} * 60.0f);
total:
id: ev_pulses_raw_total
internal: true
on_value:
then:
- lambda: |-
uint32_t raw = (uint32_t) x;
uint32_t last = id(g_ev_pulses_raw_last);
if (raw < last) {
last = 0;
}
id(g_ev_pulses_total_abs) += (raw - last);
id(g_ev_pulses_raw_last) = raw;
- platform: template
name: "${ev_total_energy_name}"
id: ev_total_energy_kwh
unit_of_measurement: "kWh"
accuracy_decimals: 3
update_interval: 10s
lambda: |-
return ((float) id(g_ev_pulses_total_abs)) * (${ev_pulse_wh} / 1000.0f);
- platform: template
name: "${ev_today_name}"
id: ev_energy_today_kwh
unit_of_measurement: "kWh"
accuracy_decimals: 3
update_interval: 60s
lambda: |-
const float total = id(ev_total_energy_kwh).state;
if (isnan(total) || id(g_ev_day_start_kwh) < 0.0f) return NAN;
float v = total - id(g_ev_day_start_kwh);
if (v < 0.0f) v = 0.0f;
return v;
- platform: template
name: "${ev_yesterday_name}"
id: ev_energy_yesterday_kwh
unit_of_measurement: "kWh"
accuracy_decimals: 3
update_interval: 10min
lambda: |-
return id(g_ev_yesterday_kwh);
- platform: template
name: "${ev_month_name}"
id: ev_energy_this_month_kwh
unit_of_measurement: "kWh"
accuracy_decimals: 3
update_interval: 10min
lambda: |-
const float total = id(ev_total_energy_kwh).state;
if (isnan(total) || id(g_ev_month_start_kwh) < 0.0f) return NAN;
float v = total - id(g_ev_month_start_kwh);
if (v < 0.0f) v = 0.0f;
return v;
- platform: template
name: "${ev_last_month_name}"
id: ev_energy_last_month_kwh
unit_of_measurement: "kWh"
accuracy_decimals: 3
update_interval: 30min
lambda: |-
return id(g_ev_last_month_kwh);
- platform: template
name: "${ev_year_name}"
id: ev_energy_this_year_kwh
unit_of_measurement: "kWh"
accuracy_decimals: 3
update_interval: 30min
lambda: |-
const float total = id(ev_total_energy_kwh).state;
if (isnan(total) || id(g_ev_year_start_kwh) < 0.0f) return NAN;
float v = total - id(g_ev_year_start_kwh);
if (v < 0.0f) v = 0.0f;
return v;
- platform: template
name: "${ev_last_year_name}"
id: ev_energy_last_year_kwh
unit_of_measurement: "kWh"
accuracy_decimals: 3
update_interval: 60min
lambda: |-
return id(g_ev_last_year_kwh);
##########################################################################################
# SCRIPTS
##########################################################################################
script:
# Initialize RAM-only history arrays and counters
- id: init_history_arrays
mode: queued
then:
- lambda: |-
for (int i = 0; i < 365; i++) {
id(g_mains_daily_hist_kwh)[i] = NAN;
}
id(g_mains_daily_hist_index) = 0;
id(g_mains_daily_hist_count) = 0;
# Period rollover check (day/month/year) for:
# - Main total energy (sum of 3 PZEM channels)
# - EV total energy (pulse derived)
# - Temperature min/max rollovers (today/yesterday, month/last_month)
- id: rollover_check
mode: restart
then:
- lambda: |-
auto now = id(sntp_time).now();
if (!now.is_valid()) {
return;
}
const int d = now.day_of_month;
const int m = now.month;
const int y = now.year;
const float mains_total = id(mains_total_active_energy_kwh).state;
const float ev_total = id(ev_total_energy_kwh).state;
// Initialize date markers on first valid time
if (id(g_last_year) == 0) {
id(g_last_day) = d;
id(g_last_month) = m;
id(g_last_year) = y;
if (!isnan(mains_total)) {
id(g_mains_day_start_kwh) = mains_total;
id(g_mains_month_start_kwh) = mains_total;
id(g_mains_year_start_kwh) = mains_total;
}
if (!isnan(ev_total)) {
id(g_ev_day_start_kwh) = ev_total;
id(g_ev_month_start_kwh) = ev_total;
id(g_ev_year_start_kwh) = ev_total;
}
return;
}
// Helper: protect against meter reset / wrap (total < baseline)
auto clamp_baseline = [&](float total, float &baseline) {
if (baseline < 0.0f) baseline = total;
if (total < baseline) baseline = total;
};
if (!isnan(mains_total)) {
clamp_baseline(mains_total, id(g_mains_day_start_kwh));
clamp_baseline(mains_total, id(g_mains_month_start_kwh));
clamp_baseline(mains_total, id(g_mains_year_start_kwh));
}
if (!isnan(ev_total)) {
clamp_baseline(ev_total, id(g_ev_day_start_kwh));
clamp_baseline(ev_total, id(g_ev_month_start_kwh));
clamp_baseline(ev_total, id(g_ev_year_start_kwh));
}
// --- YEAR ROLLOVER ---
if (y != id(g_last_year)) {
if (!isnan(mains_total) && id(g_mains_year_start_kwh) >= 0.0f) {
float ykwh = mains_total - id(g_mains_year_start_kwh);
if (ykwh < 0.0f) ykwh = 0.0f;
id(g_mains_last_year_kwh) = ykwh;
id(g_mains_year_start_kwh) = mains_total;
}
if (!isnan(ev_total) && id(g_ev_year_start_kwh) >= 0.0f) {
float ykwh = ev_total - id(g_ev_year_start_kwh);
if (ykwh < 0.0f) ykwh = 0.0f;
id(g_ev_last_year_kwh) = ykwh;
id(g_ev_year_start_kwh) = ev_total;
}
id(g_last_year) = y;
}
// --- MONTH ROLLOVER ---
if (m != id(g_last_month)) {
if (!isnan(mains_total) && id(g_mains_month_start_kwh) >= 0.0f) {
float mkwh = mains_total - id(g_mains_month_start_kwh);
if (mkwh < 0.0f) mkwh = 0.0f;
id(g_mains_last_month_kwh) = mkwh;
id(g_mains_month_start_kwh) = mains_total;
}
if (!isnan(ev_total) && id(g_ev_month_start_kwh) >= 0.0f) {
float mkwh = ev_total - id(g_ev_month_start_kwh);
if (mkwh < 0.0f) mkwh = 0.0f;
id(g_ev_last_month_kwh) = mkwh;
id(g_ev_month_start_kwh) = ev_total;
}
// Temperature month rollover
id(g_temp_last_month_min) = id(g_temp_month_min);
id(g_temp_last_month_max) = id(g_temp_month_max);
id(g_temp_month_min) = 9999.0f;
id(g_temp_month_max) = -9999.0f;
id(g_last_month) = m;
}
// --- DAY ROLLOVER ---
if (d != id(g_last_day)) {
if (!isnan(mains_total) && id(g_mains_day_start_kwh) >= 0.0f) {
float dkwh = mains_total - id(g_mains_day_start_kwh);
if (dkwh < 0.0f) dkwh = 0.0f;
id(g_mains_yesterday_kwh) = dkwh;
// Push into RAM ring buffer
int idx = id(g_mains_daily_hist_index);
if (idx < 0) idx = 0;
if (idx > 364) idx = 0;
id(g_mains_daily_hist_kwh)[idx] = dkwh;
idx = (idx + 1) % 365;
id(g_mains_daily_hist_index) = idx;
if (id(g_mains_daily_hist_count) < 365) id(g_mains_daily_hist_count)++;
id(g_mains_day_start_kwh) = mains_total;
}
if (!isnan(ev_total) && id(g_ev_day_start_kwh) >= 0.0f) {
float dkwh = ev_total - id(g_ev_day_start_kwh);
if (dkwh < 0.0f) dkwh = 0.0f;
id(g_ev_yesterday_kwh) = dkwh;
id(g_ev_day_start_kwh) = ev_total;
}
// Temperature day rollover
id(g_temp_yesterday_min) = id(g_temp_today_min);
id(g_temp_yesterday_max) = id(g_temp_today_max);
id(g_temp_today_min) = 9999.0f;
id(g_temp_today_max) = -9999.0f;
id(g_last_day) = d;
}
# Update temperature min/max (today + month)
- id: update_temp_minmax
mode: queued
then:
- lambda: |-
const float t = id(env_temp).state;
if (isnan(t)) return;
if (t < id(g_temp_today_min)) id(g_temp_today_min) = t;
if (t > id(g_temp_today_max)) id(g_temp_today_max) = t;
if (t < id(g_temp_month_min)) id(g_temp_month_min) = t;
if (t > id(g_temp_month_max)) id(g_temp_month_max) = t;
##########################################################################################
# SWITCHES
##########################################################################################
switch:
- platform: restart
name: "Main Power Monitor Restart"