SEM-METER/Fusion Energy Emporia Vue 2 clone ESPHome Conversion

I managed to reverse engineer the guts of the S3 based clone of Emporia Vue, called SEM-METER (Fusion Energy). I calibrated both the 50a and the 200a clips using a kill-o-watt and then checked with various loads, they seem to all be within 1% or less.

#==============================================================================
# ESP32-S3 "Emporia Vue 2 clone" (W338 / Artery AT32F421) - ESPHome
#==============================================================================
# Single-file config for the Chinese W338 clone: ESP32-S3-WROOM-1U + AT32F421
# metering MCU. The AT32 streams a measurement frame over UART (ESP RX=GPIO39,
# 115200 8N1); the uart debug parser decodes it into a globals array, and
# template sensors cir1..cir16 / phase_a/b/c read that array. 16 branch circuits
# + 3 mains phases, per-circuit 240V + enable toggles, daily energy, and a
# connection-aware status LED (blink/solid/glow).
#
# Frame map (record = byte after each 0xFF tag):
#   0x00..0x0F = branch circuits 1..16,  0x10/0x11/0x12 = mains phase A/B/C
#   per active (status 0x07) record, offsets after "FF NN":
#     r[0] status (07 active, 01 idle)
#     r[4..5]  voltage  BE16 (/10.30 = V)   [mains]
#     r[12..15] real pwr BE32 (/95 branch, /102 mains = W)
#     r[20]    line frequency (Hz, direct)
# Calibrated against a Kill-A-Watt near 240 W - trim the /95 and /102 divisors
# per channel if your CTs read high/low.
#
# Onboard pins (found by pin-walk): Status LED GPIO2, buzzer GPIO21.
# >>> Replace the WiFi / API key / OTA placeholders below before flashing. <<<
#==============================================================================

substitutions:
  device_name: "emporia-clone"
  friendly_name: "Emporia Clone Monitor"
  device_description: "W338 AT32 clone - 16-channel branch circuit energy monitor"

  leg_1: "Phase A"
  leg_2: "Phase B"
  leg_3: "Phase C"
  cir_1:  "Circuit 1"
  cir_2:  "Circuit 2"
  cir_3:  "Circuit 3"
  cir_4:  "Circuit 4"
  cir_5:  "Circuit 5"
  cir_6:  "Circuit 6"
  cir_7:  "Circuit 7"
  cir_8:  "Circuit 8"
  cir_9:  "Circuit 9"
  cir_10: "Circuit 10"
  cir_11: "Circuit 11"
  cir_12: "Circuit 12"
  cir_13: "Circuit 13"
  cir_14: "Circuit 14"
  cir_15: "Circuit 15"
  cir_16: "Circuit 16"
  xtra:   "Balance"   # leftover/unmonitored power (total minus circuits 1..16)

esp32:
  board: esp32-s3-devkitc-1
  framework:
    type: esp-idf

esphome:
  name: ${device_name}
  comment: ${device_description}
  friendly_name: ${friendly_name}
  on_boot:
    priority: -100
    then:
      - script.execute: update_status_led

logger:
  level: DEBUG
  logs:
    sensor: INFO   # silence per-sensor publish spam (16 circuits + 3 phases)

# ---- Network ----------------------------------------------------------------
wifi:
  ssid: "YOUR_WIFI_SSID"            # <-- edit
  password: "YOUR_WIFI_PASSWORD"    # <-- edit
  min_auth_mode: WPA2
  # AP fallback + captive portal: if it can't join your WiFi, it broadcasts
  # "<name> Setup" so you can re-provision from a phone.
  ap:
    ssid: "${device_name} Setup"
  on_connect:
    - script.execute: update_status_led
  on_disconnect:
    - script.execute: update_status_led

captive_portal:

api:
  reboot_timeout: 0s
  encryption:
    key: "REPLACE_WITH_44_CHAR_BASE64_KEY="   # <-- esphome wizard generates this
  on_client_connected:
    - script.execute: update_status_led
  on_client_disconnected:
    - script.execute: update_status_led

ota:
  platform: esphome
  password: "REPLACE_WITH_OTA_PASSWORD"   # <-- edit

web_server:
  port: 80
  version: 2

mdns:
  disabled: false

time:
  - platform: homeassistant
    id: device_time

preferences:
  # Daily-energy totals use restore:true; batch NVS writes to bound flash wear.
  flash_write_interval: "1h"

# ---- UART data source: the AT32 measurement frame ---------------------------
globals:
  - id: raw_w
    type: float[16]
    restore_value: no
  - id: raw_pa
    type: float
    restore_value: no
  - id: raw_pb
    type: float
    restore_value: no
  - id: raw_pc
    type: float
    restore_value: no
  - id: line_v_a
    type: float
    restore_value: no
  - id: line_v_b
    type: float
    restore_value: no
  - id: line_hz
    type: float
    restore_value: no

uart:
  id: meter_uart
  rx_pin: GPIO39
  baud_rate: 115200
  rx_buffer_size: 1024
  debug:
    direction: RX
    dummy_receiver: true
    after:
      delimiter: "\n"
      timeout: 250ms
      bytes: 400
    sequence:
      - lambda: |-
          const auto &b = bytes;
          int n = b.size();
          for (int i = 0; i + 2 < n; i++) {
            if (b[i] != 0xFF) continue;
            uint8_t rec = b[i+1];
            if (rec > 0x12) continue;
            uint8_t status = b[i+2];
            if (status != 0x01 && status != 0x07) continue;
            const uint8_t *r = &b[i+2];

            // Idle record: zero the channel so a load turning OFF reads 0.
            if (status == 0x01) {
              if (rec <= 0x0F) id(raw_w)[rec] = 0.0f;
              else if (rec == 0x10) id(raw_pa) = 0.0f;
              else if (rec == 0x11) id(raw_pb) = 0.0f;
              else if (rec == 0x12) id(raw_pc) = 0.0f;
              continue;
            }

            // Active record: need the full field span in this chunk.
            if (i + 22 >= n) continue;

            uint32_t wraw = ((uint32_t)r[12] << 24) | ((uint32_t)r[13] << 16)
                          | ((uint32_t)r[14] << 8)  |  (uint32_t)r[15];
            uint16_t vraw = (r[4] << 8) | r[5];

            if (rec <= 0x0F) {
              id(raw_w)[rec] = wraw / 95.0f;          // 50 A branch CT scale
            } else if (rec == 0x10) {
              id(raw_pa) = wraw / 102.0f;             // 200 A mains CT scale
              if (vraw > 900) { id(line_v_a) = vraw / 10.30f; id(line_hz) = (float) r[20]; }
            } else if (rec == 0x11) {
              id(raw_pb) = wraw / 102.0f;
              if (vraw > 900) id(line_v_b) = vraw / 10.30f;
            } else if (rec == 0x12) {
              id(raw_pc) = wraw / 102.0f;
            }
          }

# ---- Buzzer + connection-aware status LED -----------------------------------
output:
  - platform: ledc
    pin: GPIO41
    id: buzzer
  - platform: ledc          # status LED (PWM so it can glow)
    id: status_led_output
    pin: GPIO2              # add `inverted: true` here if the LED is active-low
    frequency: 1000Hz

rtttl:
  output: buzzer
  on_finished_playback:
    - logger.log: "Song ended!"

light:
  - platform: monochromatic
    id: status_led
    output: status_led_output
    internal: true
    default_transition_length: 0ms
    effects:
      - strobe:
          name: "Fast Blink"
          colors:
            - { state: true,  brightness: 100%, duration: 500ms }
            - { state: false, duration: 500ms }
      - lambda:
          name: "Slow Glow"
          update_interval: 40ms
          lambda: |-
            static float phase = 0;
            phase += 0.05;  // ~2s full cycle
            if (phase > 3.14159) phase = -3.14159;
            float brightness = (sin(phase) + 1.0) / 2.0;
            auto call = id(status_led).turn_on();
            call.set_brightness(brightness);
            call.set_transition_length(0);
            call.perform();

# LED: fast blink = no WiFi, solid = WiFi but no API, slow glow = API connected.
script:
  - id: update_status_led
    then:
      - if:
          condition: { not: { wifi.connected: } }
          then:
            - light.turn_on: { id: status_led, brightness: 100%, effect: "Fast Blink" }
          else:
            - if:
                condition: { api.connected: }
                then:
                  - light.turn_on: { id: status_led, brightness: 100%, effect: "Slow Glow" }
                else:
                  - light.turn_on: { id: status_led, brightness: 100%, effect: none }

interval:
  - interval: 30s   # backup refresh in case an event is missed
    then:
      - script.execute: update_status_led

button:
  - platform: template
    name: "Two Beeps"
    on_press:
      - rtttl.play: "two short:d=4,o=5,b=100:16e6,16e6"
  - platform: template
    name: "Funky Town"
    icon: mdi:music
    on_press:
      - rtttl.play: "funkytown:d=8,o=4,b=125:c6,c6,a#5,c6,p,g5,p,g5,c6,f6,e6,c6,p,c6,c6,a#5,c6,p,g5,p,g5,c6,f6,e6,c6"

# ---- Per-circuit 240V mode + enable toggles (runtime, no reflash) -----------
switch:
  - platform: restart
    name: "Restart"
    entity_category: config

  # 240V mode ON = single-leg CT on a double-pole breaker; firmware doubles W.
  - { platform: template, name:  "${cir_1} 240V Mode", id:  cir1_is_240v, optimistic: true, restore_mode: RESTORE_DEFAULT_ON,  entity_category: config }
  - { platform: template, name:  "${cir_2} 240V Mode", id:  cir2_is_240v, optimistic: true, restore_mode: RESTORE_DEFAULT_ON,  entity_category: config }
  - { platform: template, name:  "${cir_3} 240V Mode", id:  cir3_is_240v, optimistic: true, restore_mode: RESTORE_DEFAULT_OFF, entity_category: config }
  - { platform: template, name:  "${cir_4} 240V Mode", id:  cir4_is_240v, optimistic: true, restore_mode: RESTORE_DEFAULT_OFF, entity_category: config }
  - { platform: template, name:  "${cir_5} 240V Mode", id:  cir5_is_240v, optimistic: true, restore_mode: RESTORE_DEFAULT_OFF, entity_category: config }
  - { platform: template, name:  "${cir_6} 240V Mode", id:  cir6_is_240v, optimistic: true, restore_mode: RESTORE_DEFAULT_OFF, entity_category: config }
  - { platform: template, name:  "${cir_7} 240V Mode", id:  cir7_is_240v, optimistic: true, restore_mode: RESTORE_DEFAULT_OFF, entity_category: config }
  - { platform: template, name:  "${cir_8} 240V Mode", id:  cir8_is_240v, optimistic: true, restore_mode: RESTORE_DEFAULT_OFF, entity_category: config }
  - { platform: template, name:  "${cir_9} 240V Mode", id:  cir9_is_240v, optimistic: true, restore_mode: RESTORE_DEFAULT_OFF, entity_category: config }
  - { platform: template, name: "${cir_10} 240V Mode", id: cir10_is_240v, optimistic: true, restore_mode: RESTORE_DEFAULT_OFF, entity_category: config }
  - { platform: template, name: "${cir_11} 240V Mode", id: cir11_is_240v, optimistic: true, restore_mode: RESTORE_DEFAULT_OFF, entity_category: config }
  - { platform: template, name: "${cir_12} 240V Mode", id: cir12_is_240v, optimistic: true, restore_mode: RESTORE_DEFAULT_OFF, entity_category: config }
  - { platform: template, name: "${cir_13} 240V Mode", id: cir13_is_240v, optimistic: true, restore_mode: RESTORE_DEFAULT_OFF, entity_category: config }
  - { platform: template, name: "${cir_14} 240V Mode", id: cir14_is_240v, optimistic: true, restore_mode: RESTORE_DEFAULT_OFF, entity_category: config }
  - { platform: template, name: "${cir_15} 240V Mode", id: cir15_is_240v, optimistic: true, restore_mode: RESTORE_DEFAULT_OFF, entity_category: config }
  - { platform: template, name: "${cir_16} 240V Mode", id: cir16_is_240v, optimistic: true, restore_mode: RESTORE_DEFAULT_OFF, entity_category: config }

  # Enable OFF = report 0 W and freeze that circuit's daily energy.
  - { platform: template, name:  "${cir_1} Enabled", id:  cir1_enabled, optimistic: true, restore_mode: RESTORE_DEFAULT_ON, entity_category: config }
  - { platform: template, name:  "${cir_2} Enabled", id:  cir2_enabled, optimistic: true, restore_mode: RESTORE_DEFAULT_ON, entity_category: config }
  - { platform: template, name:  "${cir_3} Enabled", id:  cir3_enabled, optimistic: true, restore_mode: RESTORE_DEFAULT_ON, entity_category: config }
  - { platform: template, name:  "${cir_4} Enabled", id:  cir4_enabled, optimistic: true, restore_mode: RESTORE_DEFAULT_ON, entity_category: config }
  - { platform: template, name:  "${cir_5} Enabled", id:  cir5_enabled, optimistic: true, restore_mode: RESTORE_DEFAULT_ON, entity_category: config }
  - { platform: template, name:  "${cir_6} Enabled", id:  cir6_enabled, optimistic: true, restore_mode: RESTORE_DEFAULT_ON, entity_category: config }
  - { platform: template, name:  "${cir_7} Enabled", id:  cir7_enabled, optimistic: true, restore_mode: RESTORE_DEFAULT_ON, entity_category: config }
  - { platform: template, name:  "${cir_8} Enabled", id:  cir8_enabled, optimistic: true, restore_mode: RESTORE_DEFAULT_ON, entity_category: config }
  - { platform: template, name:  "${cir_9} Enabled", id:  cir9_enabled, optimistic: true, restore_mode: RESTORE_DEFAULT_ON, entity_category: config }
  - { platform: template, name: "${cir_10} Enabled", id: cir10_enabled, optimistic: true, restore_mode: RESTORE_DEFAULT_ON, entity_category: config }
  - { platform: template, name: "${cir_11} Enabled", id: cir11_enabled, optimistic: true, restore_mode: RESTORE_DEFAULT_ON, entity_category: config }
  - { platform: template, name: "${cir_12} Enabled", id: cir12_enabled, optimistic: true, restore_mode: RESTORE_DEFAULT_ON, entity_category: config }
  - { platform: template, name: "${cir_13} Enabled", id: cir13_enabled, optimistic: true, restore_mode: RESTORE_DEFAULT_ON, entity_category: config }
  - { platform: template, name: "${cir_14} Enabled", id: cir14_enabled, optimistic: true, restore_mode: RESTORE_DEFAULT_ON, entity_category: config }
  - { platform: template, name: "${cir_15} Enabled", id: cir15_enabled, optimistic: true, restore_mode: RESTORE_DEFAULT_ON, entity_category: config }
  - { platform: template, name: "${cir_16} Enabled", id: cir16_enabled, optimistic: true, restore_mode: RESTORE_DEFAULT_ON, entity_category: config }

binary_sensor:
  - platform: status
    name: "Status"
    entity_category: diagnostic

text_sensor:
  - platform: template
    name: "Uptime"
    id: uptime_human
    icon: mdi:clock-start
    entity_category: diagnostic
  - platform: version
    name: "ESPHome Version"
    entity_category: diagnostic
  - platform: wifi_info
    ip_address: { name: "IP", icon: mdi:ip-outline, entity_category: diagnostic }
    ssid:       { name: "SSID", icon: mdi:wifi-settings, entity_category: diagnostic }
    bssid:      { name: "BSSID", icon: mdi:wifi-settings, entity_category: diagnostic }
    mac_address: { name: "MAC", icon: mdi:network-outline, entity_category: diagnostic }

# YAML anchors reused below.
.filters:
  - &throttle_avg
    throttle_average: 5s
  - &throttle_time
    throttle: 60s

sensor:
  # ---- Diagnostics ----------------------------------------------------------
  - platform: wifi_signal
    name: "WiFi Signal Strength"
    update_interval: 60s
    entity_category: diagnostic
  - platform: uptime
    name: "Uptime Seconds"
    id: uptime_sensor
    update_interval: 60s
    internal: true
    on_raw_value:
      then:
        - text_sensor.template.publish:
            id: uptime_human
            state: !lambda |-
              int seconds = round(id(uptime_sensor).get_raw_state());
              int days = seconds / (24 * 3600);  seconds = seconds % (24 * 3600);
              int hours = seconds / 3600;         seconds = seconds % 3600;
              int minutes = seconds / 60;         seconds = seconds % 60;
              return ((days ? to_string(days) + "d " : "") +
                      (hours ? to_string(hours) + "h " : "") +
                      (minutes ? to_string(minutes) + "m " : "") +
                      (to_string(seconds) + "s")).c_str();

  # ---- RAW per-circuit power (internal); enable + 240V logic applied here ----
  - { platform: template, id:  cir1, internal: true, update_interval: 2s, device_class: power, unit_of_measurement: "W", lambda: 'float x = id(raw_w)[0];  return id(cir1_enabled).state  ? (id(cir1_is_240v).state  ? x*2.0f : x) : 0.0f;' }
  - { platform: template, id:  cir2, internal: true, update_interval: 2s, device_class: power, unit_of_measurement: "W", lambda: 'float x = id(raw_w)[1];  return id(cir2_enabled).state  ? (id(cir2_is_240v).state  ? x*2.0f : x) : 0.0f;' }
  - { platform: template, id:  cir3, internal: true, update_interval: 2s, device_class: power, unit_of_measurement: "W", lambda: 'float x = id(raw_w)[2];  return id(cir3_enabled).state  ? (id(cir3_is_240v).state  ? x*2.0f : x) : 0.0f;' }
  - { platform: template, id:  cir4, internal: true, update_interval: 2s, device_class: power, unit_of_measurement: "W", lambda: 'float x = id(raw_w)[3];  return id(cir4_enabled).state  ? (id(cir4_is_240v).state  ? x*2.0f : x) : 0.0f;' }
  - { platform: template, id:  cir5, internal: true, update_interval: 2s, device_class: power, unit_of_measurement: "W", lambda: 'float x = id(raw_w)[4];  return id(cir5_enabled).state  ? (id(cir5_is_240v).state  ? x*2.0f : x) : 0.0f;' }
  - { platform: template, id:  cir6, internal: true, update_interval: 2s, device_class: power, unit_of_measurement: "W", lambda: 'float x = id(raw_w)[5];  return id(cir6_enabled).state  ? (id(cir6_is_240v).state  ? x*2.0f : x) : 0.0f;' }
  - { platform: template, id:  cir7, internal: true, update_interval: 2s, device_class: power, unit_of_measurement: "W", lambda: 'float x = id(raw_w)[6];  return id(cir7_enabled).state  ? (id(cir7_is_240v).state  ? x*2.0f : x) : 0.0f;' }
  - { platform: template, id:  cir8, internal: true, update_interval: 2s, device_class: power, unit_of_measurement: "W", lambda: 'float x = id(raw_w)[7];  return id(cir8_enabled).state  ? (id(cir8_is_240v).state  ? x*2.0f : x) : 0.0f;' }
  - { platform: template, id:  cir9, internal: true, update_interval: 2s, device_class: power, unit_of_measurement: "W", lambda: 'float x = id(raw_w)[8];  return id(cir9_enabled).state  ? (id(cir9_is_240v).state  ? x*2.0f : x) : 0.0f;' }
  - { platform: template, id: cir10, internal: true, update_interval: 2s, device_class: power, unit_of_measurement: "W", lambda: 'float x = id(raw_w)[9];  return id(cir10_enabled).state ? (id(cir10_is_240v).state ? x*2.0f : x) : 0.0f;' }
  - { platform: template, id: cir11, internal: true, update_interval: 2s, device_class: power, unit_of_measurement: "W", lambda: 'float x = id(raw_w)[10]; return id(cir11_enabled).state ? (id(cir11_is_240v).state ? x*2.0f : x) : 0.0f;' }
  - { platform: template, id: cir12, internal: true, update_interval: 2s, device_class: power, unit_of_measurement: "W", lambda: 'float x = id(raw_w)[11]; return id(cir12_enabled).state ? (id(cir12_is_240v).state ? x*2.0f : x) : 0.0f;' }
  - { platform: template, id: cir13, internal: true, update_interval: 2s, device_class: power, unit_of_measurement: "W", lambda: 'float x = id(raw_w)[12]; return id(cir13_enabled).state ? (id(cir13_is_240v).state ? x*2.0f : x) : 0.0f;' }
  - { platform: template, id: cir14, internal: true, update_interval: 2s, device_class: power, unit_of_measurement: "W", lambda: 'float x = id(raw_w)[13]; return id(cir14_enabled).state ? (id(cir14_is_240v).state ? x*2.0f : x) : 0.0f;' }
  - { platform: template, id: cir15, internal: true, update_interval: 2s, device_class: power, unit_of_measurement: "W", lambda: 'float x = id(raw_w)[14]; return id(cir15_enabled).state ? (id(cir15_is_240v).state ? x*2.0f : x) : 0.0f;' }
  - { platform: template, id: cir16, internal: true, update_interval: 2s, device_class: power, unit_of_measurement: "W", lambda: 'float x = id(raw_w)[15]; return id(cir16_enabled).state ? (id(cir16_is_240v).state ? x*2.0f : x) : 0.0f;' }

  # RAW mains power (internal): A / B / C.
  - { platform: template, id: phase_a_power, internal: true, update_interval: 2s, device_class: power, unit_of_measurement: "W", lambda: 'return id(raw_pa);' }
  - { platform: template, id: phase_b_power, internal: true, update_interval: 2s, device_class: power, unit_of_measurement: "W", lambda: 'return id(raw_pb);' }
  - { platform: template, id: phase_c_power, internal: true, update_interval: 2s, device_class: power, unit_of_measurement: "W", lambda: 'return id(raw_pc);' }

  # ---- Phase voltage + frequency --------------------------------------------
  - { platform: template, name: "${leg_1} Voltage",   update_interval: 5s, unit_of_measurement: "V",  accuracy_decimals: 1, device_class: voltage,   state_class: measurement, lambda: 'return id(line_v_a);' }
  - { platform: template, name: "${leg_1} Frequency", update_interval: 5s, unit_of_measurement: "Hz", accuracy_decimals: 0, device_class: frequency, state_class: measurement, lambda: 'return id(line_hz);' }
  - { platform: template, name: "${leg_2} Voltage",   update_interval: 5s, unit_of_measurement: "V",  accuracy_decimals: 1, device_class: voltage,   state_class: measurement, lambda: 'return id(line_v_b);' }

  # ---- Published power (copy sensors, throttled) ----------------------------
  - { platform: copy, name: "${leg_1} Power", source_id: phase_a_power, filters: *throttle_avg }
  - { platform: copy, name: "${leg_2} Power", source_id: phase_b_power, filters: *throttle_avg }
  - { platform: copy, name: "${leg_3} Power", source_id: phase_c_power, filters: *throttle_avg }
  - { platform: copy, name: "Total Power",    source_id: total_power,   filters: *throttle_avg }
  - { platform: copy, name: "${xtra} Power",  source_id: balance_power, filters: *throttle_avg }
  - { platform: copy, name:  "${cir_1} Power", source_id:  cir1, filters: *throttle_avg }
  - { platform: copy, name:  "${cir_2} Power", source_id:  cir2, filters: *throttle_avg }
  - { platform: copy, name:  "${cir_3} Power", source_id:  cir3, filters: *throttle_avg }
  - { platform: copy, name:  "${cir_4} Power", source_id:  cir4, filters: *throttle_avg }
  - { platform: copy, name:  "${cir_5} Power", source_id:  cir5, filters: *throttle_avg }
  - { platform: copy, name:  "${cir_6} Power", source_id:  cir6, filters: *throttle_avg }
  - { platform: copy, name:  "${cir_7} Power", source_id:  cir7, filters: *throttle_avg }
  - { platform: copy, name:  "${cir_8} Power", source_id:  cir8, filters: *throttle_avg }
  - { platform: copy, name:  "${cir_9} Power", source_id:  cir9, filters: *throttle_avg }
  - { platform: copy, name: "${cir_10} Power", source_id: cir10, filters: *throttle_avg }
  - { platform: copy, name: "${cir_11} Power", source_id: cir11, filters: *throttle_avg }
  - { platform: copy, name: "${cir_12} Power", source_id: cir12, filters: *throttle_avg }
  - { platform: copy, name: "${cir_13} Power", source_id: cir13, filters: *throttle_avg }
  - { platform: copy, name: "${cir_14} Power", source_id: cir14, filters: *throttle_avg }
  - { platform: copy, name: "${cir_15} Power", source_id: cir15, filters: *throttle_avg }
  - { platform: copy, name: "${cir_16} Power", source_id: cir16, filters: *throttle_avg }

  # ---- Totals + balance (Total includes Phase C for 3-phase service) --------
  - platform: template
    id: total_power
    lambda: 'return id(phase_a_power).state + id(phase_b_power).state + id(phase_c_power).state;'
    update_interval: 5s
    device_class: power
    state_class: measurement
    unit_of_measurement: "W"
  - platform: total_daily_energy
    name: "Total Daily Energy"
    power_id: total_power
    accuracy_decimals: 0
    restore: true
    filters: *throttle_time

  - platform: template
    id: balance_power
    update_interval: 5s
    device_class: power
    state_class: measurement
    unit_of_measurement: "W"
    lambda: !lambda |-
      return max(0.0f, id(total_power).state -
        id( cir1).state - id( cir2).state - id( cir3).state - id( cir4).state -
        id( cir5).state - id( cir6).state - id( cir7).state - id( cir8).state -
        id( cir9).state - id(cir10).state - id(cir11).state - id(cir12).state -
        id(cir13).state - id(cir14).state - id(cir15).state - id(cir16).state);
  - platform: total_daily_energy
    name: "${xtra} Daily Energy"
    power_id: balance_power
    accuracy_decimals: 0
    restore: true
    filters: *throttle_time

  # ---- Per-circuit daily energy ---------------------------------------------
  - { power_id:  cir1, platform: total_daily_energy, accuracy_decimals: 0, restore: true, name:  "${cir_1} Daily Energy", filters: *throttle_time }
  - { power_id:  cir2, platform: total_daily_energy, accuracy_decimals: 0, restore: true, name:  "${cir_2} Daily Energy", filters: *throttle_time }
  - { power_id:  cir3, platform: total_daily_energy, accuracy_decimals: 0, restore: true, name:  "${cir_3} Daily Energy", filters: *throttle_time }
  - { power_id:  cir4, platform: total_daily_energy, accuracy_decimals: 0, restore: true, name:  "${cir_4} Daily Energy", filters: *throttle_time }
  - { power_id:  cir5, platform: total_daily_energy, accuracy_decimals: 0, restore: true, name:  "${cir_5} Daily Energy", filters: *throttle_time }
  - { power_id:  cir6, platform: total_daily_energy, accuracy_decimals: 0, restore: true, name:  "${cir_6} Daily Energy", filters: *throttle_time }
  - { power_id:  cir7, platform: total_daily_energy, accuracy_decimals: 0, restore: true, name:  "${cir_7} Daily Energy", filters: *throttle_time }
  - { power_id:  cir8, platform: total_daily_energy, accuracy_decimals: 0, restore: true, name:  "${cir_8} Daily Energy", filters: *throttle_time }
  - { power_id:  cir9, platform: total_daily_energy, accuracy_decimals: 0, restore: true, name:  "${cir_9} Daily Energy", filters: *throttle_time }
  - { power_id: cir10, platform: total_daily_energy, accuracy_decimals: 0, restore: true, name: "${cir_10} Daily Energy", filters: *throttle_time }
  - { power_id: cir11, platform: total_daily_energy, accuracy_decimals: 0, restore: true, name: "${cir_11} Daily Energy", filters: *throttle_time }
  - { power_id: cir12, platform: total_daily_energy, accuracy_decimals: 0, restore: true, name: "${cir_12} Daily Energy", filters: *throttle_time }
  - { power_id: cir13, platform: total_daily_energy, accuracy_decimals: 0, restore: true, name: "${cir_13} Daily Energy", filters: *throttle_time }
  - { power_id: cir14, platform: total_daily_energy, accuracy_decimals: 0, restore: true, name: "${cir_14} Daily Energy", filters: *throttle_time }
  - { power_id: cir15, platform: total_daily_energy, accuracy_decimals: 0, restore: true, name: "${cir_15} Daily Energy", filters: *throttle_time }
  - { power_id: cir16, platform: total_daily_energy, accuracy_decimals: 0, restore: true, name: "${cir_16} Daily Energy", filters: *throttle_time }

1 Like