#:########################################################################################:#
# TITLE: GARAGE DB CONTROLS
# zorruno.com layout v1.1 2026
#:########################################################################################:#
# REPO:
# https://home.fox.co.nz/gitea/zorruno/zorruno-homeassistant/src/branch/master/esphome/esp-garagedbcontrols.yaml
#:########################################################################################:#
# VERSIONS:
# v1.2 2026-03-03 EV monitoring updated to match Device A:
#      - Rolling average EV power (default 5 min @ 10s)
#      - EV charging binary sensor (threshold configurable)
#      - EV totals and energy summaries (today/yesterday/month/year)
# v1.1 2026-02-25 Updated yaml to zorruno layout V1.1
# v1.0 2025-10-23 Initial Version
#:########################################################################################:#
# HARDWARE:
# - Sonoff 4CH R2 (ESP8266 / ESP8285)
# - Reference YAML: https://github.com/jvyoralek/homeassistant-config/blob/master/esphome/sonoff-4ch.yaml
# - Relays (4CH R2): GPIO12, GPIO5, GPIO4, GPIO15
# - Buttons (4CH R2): GPIO0, GPIO9, GPIO10, GPIO14
# - Status LED: GPIO13 (active-low)
# - Extras (as used here):
#   - EV pulse input: GPIO2 (must be HIGH at boot; INPUT_PULLUP used)
#   - DHT sensor: GPIO3 (note: UART RX pin on ESP8266; ensure logger baud_rate=0)
#:########################################################################################:#
# OPERATION NOTES:
# - Provides 4 relay outputs with matching local buttons.
# - Relay restore modes are set per-output (some default ON, some default OFF).
# - EV charger monitoring (2 Wh per pulse on GPIO2):
#   - Lifetime total energy (kWh) from persisted absolute pulse total.
#   - Rolling average power (default 5 min) to reduce spiky readings at low load.
#   - Charging binary sensor based on averaged power threshold.
#   - Energy summaries: today, yesterday, this month, last month, this year, last year.
#:########################################################################################:#
# OFFLINE NOTES:
# a) Home Assistant OFFLINE, WiFi OK
#    - Device continues relay/button operation and continues pulse counting locally.
#    - API/MQTT publishing will fail until HA (and/or broker) is back.
# b) MQTT OFFLINE (or broker unreachable)
#    - Relays/buttons still operate locally and via HA API (if HA is available).
#    - MQTT state publishing depends on broker.
# c) Entire WiFi/Network OFFLINE
#    - Accurate time IS needed for daily/monthly/year rollovers (SNTP required for correct period boundaries).
#    - Loss of network will drift the timeclocks; time of day can be reset by powering off then on again at 12pm (midday)
#:########################################################################################:#


#:########################################################################################:#
# SUBSTITUTIONS: Specific device variable substitutions
#:########################################################################################:#
substitutions:
  # Device Naming
  device_name: "esp-garagedbcontrols"
  friendly_name: "Garage DB Control"
  description_comment: "Garage DB Power, EV Power & Measure, Lighting :: Sonoff 4ch R2 (Layout V1.1)"
  device_area: "Garage"

  # Project Naming
  project_name: "Sonoff Technologies.Sonoff 4Ch R2"
  project_version: "v1.2"

  # Passwords & Secrets
  api_key: !secret esp-api_key
  ota_pass: !secret esp-ota_pass
  static_ip_address: !secret esp-garagedbcontrols_ip

  # 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}

  # General Settings
  log_level: "ERROR"
  update_interval: "60s"

  # Globals persistence tuning (gentler on flash)
  globals_save_interval_slow: "10min"
  globals_save_interval_pulses: "2min"

  # I/O Naming
  switch_1_name: "16A Wallcharger Operation"
  button_1_name: "Button 1"
  relay_1_icon: "mdi:power-socket-au"

  switch_2_name: "Garage West Power"
  button_2_name: "Button 2"
  relay_2_icon: "mdi:power-socket-au"

  switch_3_name: "Spare 3"
  button_3_name: "Button 3"
  relay_3_icon: "mdi:toggle-switch"

  switch_4_name: "Spare 4"
  button_4_name: "Button 4"
  relay_4_icon: "mdi:toggle-switch"

  ########################################################################################
  # EV Pulse Settings (Device A logic)
  ########################################################################################
  ev_pulse_wh: "2.0"         # Each pulse is 2Wh
  ev_pulse_filter_ms: "10ms" # Debounce / filter
  ev_pulse_pin_mode: "INPUT_PULLUP"

  # EV Charging Detection (Averaged power + binary sensor)
  # Minimum EV charging power threshold (W) for binary sensor
  ev_charging_min_power_w: "200"

  # Rolling average window - configuration notes:
  # - Substitutions are string replacements, so window samples must be updated manually if you change times.
  # - window_samples = (avg_minutes * 60) / sample_interval_seconds
  ev_power_avg_window_minutes: "5"             # informational
  ev_power_avg_sample_interval: "10s"
  ev_power_avg_sample_interval_seconds: "10"   # must match the value above
  ev_power_avg_window_samples: "15"            # 5 min @ 10s = 30 samples
  ev_power_avg_buffer_size: "60"              # ring buffer >= window_samples (180 @ 10s = 30 minutes)

  # Rollover check interval (time-valid day/month/year updates)
  rollover_check_interval: "30s"

  # Names - EV Wallcharger (Device B)
  ev_charger_name: "16A Wallcharger"
  ev_power_name: "${ev_charger_name} Power"
  ev_charging_name: "${ev_charger_name} Charging"
  ev_total_energy_name: "${ev_charger_name} Total Energy"
  ev_today_name: "${ev_charger_name} Energy Today"
  ev_yesterday_name: "${ev_charger_name} Energy Yesterday"
  ev_month_name: "${ev_charger_name} Energy This Month"
  ev_last_month_name: "${ev_charger_name} Energy Last Month"
  ev_year_name: "${ev_charger_name} Energy This Year"
  ev_last_year_name: "${ev_charger_name} Energy Last Year"

#:########################################################################################:#
# 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_ev_history_arrays
      - delay: 3s
      - script.execute: ev_rollover_check

#:########################################################################################:#
# ESP PLATFORM AND FRAMEWORK
#:########################################################################################:#
esp8266:
  board: esp8285
  restore_from_flash: true

preferences:
  flash_write_interval: 5min

mdns:
  disabled: false

#:########################################################################################:#
# LOGGING
#:########################################################################################:#
logger:
  level: "${log_level}"
  baud_rate: 0

#:########################################################################################:#
# STATUS LED
#:########################################################################################:#
status_led:
  pin:
    number: GPIO13
    inverted: yes

#:########################################################################################:#
# GLOBALS (EV totals + rollovers + rolling average history)
#:########################################################################################:#
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"

  # 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"

  # EV rolling average power (RAM only)
  - id: g_ev_power_avg_w
    type: float
    restore_value: no
    initial_value: "0.0"

  # EV pulse history ring buffer for rolling average (RAM only)
  - id: g_ev_pulses_hist
    type: uint32_t[${ev_power_avg_buffer_size}]
    restore_value: no
  - id: g_ev_pulses_hist_index
    type: int
    restore_value: no
    initial_value: "0"
  - id: g_ev_pulses_hist_count
    type: int
    restore_value: no
    initial_value: "0"

#:########################################################################################:#
# SWITCHES
#:########################################################################################:#
switch:
  # Sonoff 4Ch R2 Relays are GPIO12, GPIO5, GPIO4, GPIO15
  - platform: gpio
    id: relay1
    name: "${switch_1_name}"
    restore_mode: RESTORE_DEFAULT_OFF
    pin: GPIO12
    icon: "${relay_1_icon}"

  - platform: gpio
    id: relay2
    name: "${switch_2_name}"
    restore_mode: RESTORE_DEFAULT_ON
    pin: GPIO5
    icon: "${relay_2_icon}"

  - platform: gpio
    id: relay3
    name: "${switch_3_name}"
    restore_mode: RESTORE_DEFAULT_ON
    pin: GPIO4
    icon: "${relay_3_icon}"

  - platform: gpio
    id: relay4
    name: "${switch_4_name}"
    restore_mode: RESTORE_DEFAULT_OFF
    pin: GPIO15
    icon: "${relay_4_icon}"

#:########################################################################################:#
# BINARY SENSORS
#:########################################################################################:#
binary_sensor:
  # Sonoff 4Ch R2 Buttons are GPIO0, GPIO9, GPIO10, GPIO14
  - platform: gpio
    id: button1
    name: "${button_1_name}"
    pin:
      number: GPIO0
      mode: INPUT_PULLUP
      inverted: true
    on_press:
      - switch.toggle: relay1

  - platform: gpio
    id: button2
    name: "${button_2_name}"
    pin:
      number: GPIO9
      mode: INPUT_PULLUP
      inverted: true
    on_press:
      - switch.toggle: relay2

  - platform: gpio
    id: button3
    name: "${button_3_name}"
    pin:
      number: GPIO10
      mode: INPUT_PULLUP
      inverted: true
    on_press:
      - switch.toggle: relay3

  - platform: gpio
    id: button4
    name: "${button_4_name}"
    pin:
      number: GPIO14
      mode: INPUT_PULLUP
      inverted: true
    on_press:
      - switch.toggle: relay4

  # EV charging detection (Device A logic: averaged power threshold)
  - platform: template
    name: "${ev_charging_name}"
    id: ev_wallcharger_charging
    device_class: power
    icon: "mdi:ev-station"
    lambda: |-
      const float p = id(ev_power_avg_w).state;
      if (isnan(p)) return false;
      return (p >= ${ev_charging_min_power_w});

#:########################################################################################:#
# SENSORS
#:########################################################################################:#
sensor:
  ########################################################################################
  # EV WALLCHARGER PULSE ENERGY (GPIO2)
  ########################################################################################
  - platform: pulse_counter
    id: ev_pulse_rate_ppm
    internal: true
    pin:
      number: GPIO2
      mode: ${ev_pulse_pin_mode}     # GPIO2 must be HIGH at boot
      inverted: false
    update_interval: ${ev_power_avg_sample_interval}
    internal_filter: ${ev_pulse_filter_ms}
    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_power_name}"
    id: ev_power_avg_w
    unit_of_measurement: "W"
    device_class: power
    state_class: measurement
    accuracy_decimals: 0
    update_interval: "${ev_power_avg_sample_interval}"
    lambda: |-
      return id(g_ev_power_avg_w);

  - platform: template
    name: "${ev_total_energy_name}"
    id: ev_total_energy_kwh
    unit_of_measurement: "kWh"
    device_class: energy
    state_class: total_increasing
    accuracy_decimals: 3
    update_interval: ${ev_power_avg_sample_interval}
    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"
    device_class: energy
    state_class: total
    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"
    device_class: energy
    state_class: total
    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"
    device_class: energy
    state_class: total
    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"
    device_class: energy
    state_class: total
    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"
    device_class: energy
    state_class: total
    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"
    device_class: energy
    state_class: total
    accuracy_decimals: 3
    update_interval: 60min
    lambda: |-
      return id(g_ev_last_year_kwh);

  ########################################################################################
  # DHT SENSOR (GPIO3)
  ########################################################################################
  - platform: dht
    pin: GPIO3
    update_interval: ${update_interval}
    temperature:
      name: "${friendly_name} Temperature"
      unit_of_measurement: "°C"
      accuracy_decimals: 1
    humidity:
      name: "${friendly_name} Humidity"
      unit_of_measurement: "%"
      accuracy_decimals: 0

#:########################################################################################:#
# INTERVALS (EV rolling average + rollover checks)
#:########################################################################################:#
interval:
  - interval: "${rollover_check_interval}"
    then:
      - script.execute: ev_rollover_check

  - interval: "${ev_power_avg_sample_interval}"
    then:
      - script.execute: ev_power_avg_update

#:########################################################################################:#
# SCRIPTS (EV)
#:########################################################################################:#
script:
  # Initialize EV pulse history buffer so rolling average starts stable after boot
  - id: init_ev_history_arrays
    mode: queued
    then:
      - lambda: |-
          const int ev_buf = ${ev_power_avg_buffer_size};
          const uint32_t p0 = id(g_ev_pulses_total_abs);

          for (int i = 0; i < ev_buf; i++) {
            id(g_ev_pulses_hist)[i] = p0;
          }
          id(g_ev_pulses_hist_index) = 0;
          id(g_ev_pulses_hist_count) = ev_buf;
          id(g_ev_power_avg_w) = 0.0f;

  # EV rolling average update (based on absolute pulse total)
  - id: ev_power_avg_update
    mode: queued
    then:
      - lambda: |-
          const int buf = ${ev_power_avg_buffer_size};
          int idx = id(g_ev_pulses_hist_index);
          if (idx < 0 || idx >= buf) idx = 0;

          const uint32_t p_now = id(g_ev_pulses_total_abs);
          id(g_ev_pulses_hist)[idx] = p_now;

          idx = (idx + 1) % buf;
          id(g_ev_pulses_hist_index) = idx;

          if (id(g_ev_pulses_hist_count) < buf) id(g_ev_pulses_hist_count)++;

          int win = ${ev_power_avg_window_samples};
          if (win < 1) win = 1;
          if (win > buf) win = buf;

          if (id(g_ev_pulses_hist_count) < win) {
            return;
          }

          int old_idx = idx - win;
          if (old_idx < 0) old_idx += buf;

          const uint32_t p_old = id(g_ev_pulses_hist)[old_idx];
          const uint32_t dp = (p_now >= p_old) ? (p_now - p_old) : 0;

          const float wh = ((float) dp) * (${ev_pulse_wh});
          const float seconds = ((float) win) * (${ev_power_avg_sample_interval_seconds});

          if (seconds <= 0.0f) {
            id(g_ev_power_avg_w) = 0.0f;
            return;
          }

          // W = Wh * 3600 / seconds
          id(g_ev_power_avg_w) = wh * 3600.0f / seconds;

  # Period rollover check (day/month/year) for EV totals
  - id: ev_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 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(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;
          }

          // Protect against meter reset / wrap (total < baseline)
          if (!isnan(ev_total)) {
            if (id(g_ev_day_start_kwh) < 0.0f) id(g_ev_day_start_kwh) = ev_total;
            if (id(g_ev_month_start_kwh) < 0.0f) id(g_ev_month_start_kwh) = ev_total;
            if (id(g_ev_year_start_kwh) < 0.0f) id(g_ev_year_start_kwh) = ev_total;

            if (ev_total < id(g_ev_day_start_kwh)) id(g_ev_day_start_kwh) = ev_total;
            if (ev_total < id(g_ev_month_start_kwh)) id(g_ev_month_start_kwh) = ev_total;
            if (ev_total < id(g_ev_year_start_kwh)) id(g_ev_year_start_kwh) = ev_total;
          }

          // YEAR rollover
          if (y != id(g_last_year)) {
            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(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;
            }
            id(g_last_month) = m;
          }

          // DAY rollover
          if (d != id(g_last_day)) {
            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;
            }
            id(g_last_day) = d;
          }

#:########################################################################################:#
# ROLLBACK COPY (v1.1) - PREVIOUS EV SECTION (pulse_meter + daily snapshot + mqtt window)
# NOTE: Commented out. Kept for quick revert if needed.
#:########################################################################################:#
#globals:
#  - id: ev_last_total_kwh
#    type: float
#    restore_value: true
#    initial_value: "0.0"
#
#  - id: ev_energy_yesterday_kwh
#    type: float
#    restore_value: true
#    initial_value: "0.0"
#
#  - id: ev_last_snapshot_doy
#    type: int
#    restore_value: no
#    initial_value: "-1"
#
#sensor:
#  - platform: pulse_meter
#    id: ev_pulse_pm
#    pin:
#      number: GPIO2
#      mode: INPUT_PULLUP
#      inverted: false
#    name: "${switch_1_name} Power Now"
#    unit_of_measurement: "W"
#    accuracy_decimals: 0
#    internal_filter: ${ev_pulse_filter}
#    timeout: ${ev_pulse_timeout}
#    filters:
#      - multiply: 120.0
#    total:
#      id: ev_energy_total_kwh
#      name: "${switch_1_name} Energy Cumulative Total"
#      unit_of_measurement: "kWh"
#      accuracy_decimals: 1
#      filters:
#        - multiply: 0.002
#
#interval:
#  - interval: 60s
#    then:
#      - mqtt.publish:
#          topic: "${ev_tasmota_tele_topic}"
#          qos: 0
#          retain: false
#          payload: !lambda |-
#            float current = id(ev_energy_total_kwh).state;
#            if (!isfinite(current)) current = id(ev_last_total_kwh);
#            float delta_kwh = current - id(ev_last_total_kwh);
#            if (delta_kwh < 0.0f || !isfinite(delta_kwh)) delta_kwh = 0.0f;
#            float delta_wh = delta_kwh * 1000.0f;
#            id(ev_last_total_kwh) = current;
#            char buf[64];
#            snprintf(buf, sizeof(buf), "{\"EVChargerWhCount\":%.3f}", delta_wh);
#            return std::string(buf);
#
#  - interval: 60s
#    then:
#      - lambda: |-
#          auto now = id(sntp_time).now();
#          if (!now.is_valid()) return;
#          if (now.hour != 23 || now.minute != 59) return;
#          if (id(ev_last_snapshot_doy) == now.day_of_year) return;
#          if (isfinite(id(ev_energy_today_kwh).state)) {
#            id(ev_energy_yesterday_kwh) = id(ev_energy_today_kwh).state;
#          }
#          id(ev_last_snapshot_doy) = now.day_of_year;