Home Assistant Project Guide

A Fully Local Battery Backup Dashboard

Integrating a Dabbsson 2000L power station into Home Assistant over local Tuya/LocalTuya — no cloud dependency — with real-time ComEd electricity-price automations to run a server rack off battery instead of the grid.

Why Local Tuya Instead of the Manufacturer App

The Dabbsson app/cloud integration is limited — slow polling, rate limits, and only a handful of exposed sensors. Pairing the power station to the generic Tuya Smart app instead gives access to the raw Tuya “data points” (DPs), which can be pulled locally over LAN and turned into much richer sensors via LocalTuya.

This setup deliberately avoids the Dabbsson cloud entirely — everything runs local-only, so it keeps working even if the manufacturer's servers go down, and none of the battery telemetry ever leaves the house.

Setup Steps

  1. Factory reset the unit. Not documented anywhere by Dabbsson — do it from inside the Dabbsson app: reset settings, then press “unlink.” That's enough.
  2. Re-pair the unit to the Tuya Smart app (not the Dabbsson app).
  3. Create a Tuya IoT Platform project: sign up at the Tuya Developer Platform → Project Management → create a Cloud Project → under that project, go to Devices → link your Tuya Smart app account to the project so the device shows up there.
  4. Get the device ID + local key from the Tuya IoT Platform itself — no terminal or local install needed. Go to Cloud → API Explorer → Device Management → Query Device Details (us.platform.tuya.com/cloud/explorer), pick your project + device, and run the query — the response includes the local_key and device ID right there in the browser. (TinyTuya's python -m tinytuya wizard still works too, but it's an extra local install you don't need if you're already signed into the Tuya Cloud project.)
  5. Install the LocalTuya fork that supports the 3.5 LAN protocol via HACS (the mainline localtuya integration doesn't support protocol 3.5, which this unit needs). Add it as a custom repository in HACS, then install and restart Home Assistant.
  6. Give the power station a static IP (DHCP reservation on the router). LocalTuya did not auto-discover this device — it had to be added manually with the static IP, device ID, and local key from step 4.
  7. In LocalTuya, add a sensor entity for DP 158 — the raw status string. This one entity is the foundation for almost every derived sensor below. It's expected to be named sensor.2000l_power_string (this entity is created by LocalTuya's UI config flow, not by YAML).

IP address, device ID, and local key are intentionally not shown anywhere on this page — they live in LocalTuya's own config entry, never in YAML.

How the Raw Data Actually Works

DP 158 doesn't return one clean value — it's a blob of Chinese-labeled sub-strings, e.g.:

AC入{0.00V,0.00A,0W,35HZ,使能:1}
AC输出{119.90V,0.46A,15W,并机:0}
PV{0.00V,33.30V,1.30A,45W}
INV电池端{53.2V,-1.4A,45W,设置:700W}
BMS主机{57,53.15V,-0.67A,采样:53.03V,放电:120.00A,充电:20.00A,容量:{23.67,40.99},电芯{3.325,3.320},soh:100}
点烟器{12.5V,0.0A,0W}
温度{转速:0,INV:41℃,BMS:26℃,MPPT:0℃,PD:43℃}

The device actually pushes this in two back-to-back packets, and the BMS portion gets overwritten almost instantly by the second packet. To not lose it, sensor.2000l_bms_string is a “latching” template sensor: it only updates its state when the incoming string contains the BMS marker, and otherwise holds its previous value. Every battery-related sensor is parsed from that latched string, not directly from the raw power string.

All of the AC / PV / BMS / temperature / fan sensors are regex-parsed out of these two raw strings inside the template: block of configuration.yaml (full source below).

Full DP (Data Point) Reference

Built from three official Tuya sources pulled straight from the device's own schema: a live DP snapshot, the official status schema, and the official writable-instruction schema. This is the complete DP list for this device model — no more guessing needed for “what DPs exist.”

DPTuya CodeTypeRange / OptionsWritableHA Entity
1battery_percentageInteger0–100 %Nosensor.2000l_hardware_battery_state
10temp_currentInteger-20–85 °CNosensor.2000l_system_temperature
25beepBooleantrue/falseYesswitch.2000l_beep
39power_dataRawmaxlen 128Nosensor.2000l_dp_39_raw
40run_stateRawmaxlen 128Yessensor.2000l_dp_40_raw
101charge_stateBooleantrue/falseNobinary_sensor.2000l_ac_charging_status
102led_statusEnumoff / dim / bright / sosYesselect.2000l_led_light + button.2000l_cycle_led_light
109ac_switchBooleantrue/falseYesswitch.2000l_ac_output
110display_12v_outpowerInteger0–1000 WNosensor.2000l_dp_110_power
111dc12v_output_stateBooleantrue/falseYesswitch.2000l_dc_12v_output
112usb_output_stateBooleantrue/falseYesswitch.2000l_usb_output
119device_stateRawmaxlen 128Nosensor.2000l_dp_119_raw
120device_standby_time_setEnum30_minutes … close_timingYesselect.2000l_device_standby_time
121lcd_off_time_setEnum10_seconds … close_timingYesselect.2000l_lcd_off_time
122ac_standby_time_setInteger0–2147483647 minYesnumber.2000l_ac_standby_time
123custom_charge_powerInteger0–2147483647 WYesnumber.2000l_ac_charge_power_limit
124fast_or_slow_chargeBooleantrue/falseYesswitch.2000l_fast_charge_mode
127device_modeEnumstandby_mode / eco_modeYesselect.2000l_ac_mode
130pd_verinfoStringmaxlen 255Nosensor.2000l_pd_subsystem_firmware
132bms_verinfoStringmaxlen 255Nosensor.2000l_bms_firmware
133inv_verinfoStringmaxlen 255Nosensor.2000l_inverter_firmware
134inv_errorcodeBitmap19 flags (inverter errors)Nosensor.2000l_dp_134_unknown_counter
135ems_errorcodeBitmap18 flags (EMS/PD errors)Nosensor.2000l_dp_135_unknown_counter
136bms_errorcodeBitmap15 flags (BMS/pack errors)Nosensor.2000l_dp_136_unknown_counter
137mppt_errorcodeBitmap14 flags (MPPT/solar errors)Nosensor.2000l_dp_137_unknown_counter
140serial_numberStringmaxlen 255Nosensor.2000l_device_serial_number
141daily_logRawmaxlen 128Yessensor.2000l_dp_141_raw
142factory_data_resetBooleantrue/falseYes — dangerousNot exposed in HA
143ota_bms_inv_timeInteger0–2147483647 sNosensor.2000l_dp_143_unknown_counter
145output_vol_levelInteger0–2147483647 VYesnumber.2000l_output_voltage_level
146timer_poweronRawmaxlen 128 (base64 schedule)Yessensor.2000l_dp_146_raw
147timer_poweroffRawmaxlen 128 (base64 schedule)Yessensor.2000l_dp_147_raw
148timer_ac_dischargeRawmaxlen 128 (base64 schedule)Yessensor.2000l_dp_148_raw
149timer_dc_dischargeRawmaxlen 128 (base64 schedule)Yessensor.2000l_dp_149_raw
150timer_discharge_stateBooleantrue/falseYesswitch.2000l_discharge_timer_enabled
151timer_charging_stateBooleantrue/falseYesswitch.2000l_charging_timer_enabled
152timer_ac_chargingRawmaxlen 128 (base64 schedule)Yessensor.2000l_dp_152_raw
153timer_usb_dischargingRawmaxlen 128 (base64 schedule)Yessensor.2000l_dp_153_raw
156marge_value_dpidRawmaxlen 128Nosensor.2000l_dp_156_raw
157merge_dp_setRawmaxlen 128Yessensor.2000l_dp_157_raw
158sale_logStringmaxlen 255 (multi-field AC/PV/INV string)Nosensor.2000l_power_string
159energy_inIntegerkWh, scale 2 (÷ 100)Nosensor.2000l_energy_in
160energy_outIntegerkWh, scale 2 (÷ 100)Nosensor.2000l_energy_out

Automations: Battery Cycling & ComEd Price Arbitrage

No solar yet. The AC output switch (DP 109) turned out to just be the physical AC-outlet on/off button — the Dabbsson manual's EPS section confirms it can't stop grid charging or force a battery discharge while grid stays connected. The real charging lever is the AC charge power limit (DP 123): setting it to 0 stops AC charging entirely without ever touching the output relay, so loads drain the battery naturally. That lets the battery actually cycle between a floor and a ceiling instead of sitting pinned at 100%, and it's safe to enable since it can never cut power to the load.

  1. Force Always-On UPS Mode — keeps the unit from ever sleeping, since it's a 24/7 UPS for a server rack.
  2. Battery Floor Protection — forces AC/grid ON and restores the charge power limit whenever SOC drops below 21%, no matter what. Always enabled.
  3. Resume Charging Below 80% or on Cheap Pricing — restores the charge power limit when SOC drops below 80% (recharging after being drained by loads) or when the ComEd real-time price drops below a configurable “cheap” threshold, as long as SOC is still below 100%.
  4. Stop Charging When Full or Pricing Is Expensive — zeroes the charge power limit once SOC is above 99% or price rises above a configurable “expensive” threshold, guarded by the 20% floor and no storm forecast. Enabled by default — unlike the old switch-based design, this can never cut power to the load.
  5. Severe Weather Override — forces a full charge and disables eco/standby mode ahead of severe weather, ignoring the normal 80%-100% cycle — outage resilience beats longevity here.
Why the redesign: earlier automations toggled the AC output switch to try to shift load to battery when grid power was expensive. The manual's Emergency Power Supply (EPS) section confirmed that's impossible — that switch is just the AC-output on/off button, not a battery/grid source selector. The real lever turned out to be the AC charge power limit (DP 123 custom_charge_power): zeroing it out stops grid charging without ever risking power to the load.

Dashboard Layout

Dabbsson 2000L Home Assistant dashboard

Overview

Headline bar (SOC, charge/discharge mode), a live ComEd price tile with a 6-hour graph, live power gauges, lifetime energy totals (device counters plus a since-setup solar/grid/battery-usage breakdown), hardware controls (AC/DC/USB outputs, LED light, fast charge, eco/standby mode), settings & limits, and a 12-hour price-vs-power history graph.

Diagnostics

Full BMS health block (temps, fan, pack voltage/current, remaining Ah, state of health, error bitmaps), firmware versions, serial number, raw parsed strings, and every still-undecoded raw DP.

Automations & Activity

Each automation's on/off state and last-triggered time, a scoped logbook of automation/switch activity, and a history graph of price vs. battery SOC.

Full Source Code

The complete, real files this dashboard runs on. IP address, device ID, and local key are not present here — LocalTuya stores those in its own config entry, never in YAML.

configuration.yaml


# Loads default set of integrations. Do not remove.
default_config:

# Load frontend themes from the themes folder
frontend:
  themes: !include_dir_merge_named themes

automation: !include automations.yaml
script: !include scripts.yaml
scene: !include scenes.yaml

# ==============================================================================
# REST SENSOR: COMED REAL-TIME ELECTRICITY PRICE
# ==============================================================================
sensor:
  - platform: rest
    name: ComEd 5-Minute Price
    resource: https://hourlypricing.comed.com/api?type=5minutefeed&format=json
    value_template: "{{ value_json[0].price | float(0) }}"
    unit_of_measurement: "¢/kWh"
    scan_interval: 300

  # ==============================================================================
  # DERIVED LIFETIME ENERGY TOTALS (Riemann-sum integration of the real-time W
  # sensors below). The device's own DP159/160 counters only report a combined
  # energy in/out, not broken down by source -- these three track since-setup,
  # not device lifetime, but do split out solar vs. grid vs. battery usage.
  # ==============================================================================
  - platform: integration
    source: sensor.2000l_solar_watts_in
    name: "2000L Solar Energy In (Since Setup)"
    unique_id: dabbsson_2000l_solar_energy_integral
    unit_prefix: k
    round: 2
    method: left

  - platform: integration
    source: sensor.2000l_ac_power_in
    name: "2000L Grid Charge Energy In (Since Setup)"
    unique_id: dabbsson_2000l_grid_energy_integral
    unit_prefix: k
    round: 2
    method: left

  - platform: integration
    source: sensor.2000l_battery_watts_out
    name: "2000L Battery Usage (Since Setup)"
    unique_id: dabbsson_2000l_battery_usage_integral
    unit_prefix: k
    round: 2
    method: left

# ==============================================================================
# TEMPLATE SENSORS & SWITCHES: MASTER DABBSSON 2000L INTEGRATION
# ==============================================================================
template:
  - sensor:
      # --- FIRMWARE & SYSTEM IDENTIFICATION ---
      - name: "2000L BMS Firmware"
        unique_id: dabbsson_2000l_bms_ver
        state: "{{ states('sensor.dabbsson_2000l_dp_132') }}"

      - name: "2000L Inverter Firmware"
        unique_id: dabbsson_2000l_inv_ver
        state: "{{ states('sensor.dabbsson_2000l_dp_133') }}"

      - name: "2000L PD Subsystem Firmware"
        unique_id: dabbsson_2000l_pd_ver
        state: "{{ states('sensor.dabbsson_2000l_dp_130') }}"

      # --- HARDWARE & HARDWARE DP STATES ---
      - name: "2000L System Temperature"
        unique_id: dabbsson_2000l_system_temp
        unit_of_measurement: "°C"
        device_class: temperature
        state: "{{ states('sensor.dabbsson_2000l_dp_10') | float(0) }}"

      - name: "2000L Hardware Battery State"
        unique_id: dabbsson_2000l_battery_percent_dp
        unit_of_measurement: "%"
        device_class: battery
        state: >
          {% set pct = states('sensor.2000l_bms_string') | regex_findall('BMS主机\{(\d+),') %}
          {% set dp1 = states('sensor.dabbsson_2000l_dp_1') %}
          {% if pct %}
            {{ pct[0] }}
          {% elif dp1 not in ['unknown', 'unavailable', ''] %}
            {{ dp1 | int(0) }}
          {% else %}
            {{ this.state }}
          {% endif %}

      # DP 145 (output_vol_level), 121 (lcd_off_time_set), 122 (ac_standby_time_set),
      # and 120 (device_standby_time_set) are all writable per standard_instruction.txt --
      # promoted to the number:/select: blocks below instead of read-only sensors.

      # --- BMS LATCH FILTER & DP 158 POWER STRING PARSING ---
      - name: "2000L BMS String"
        unique_id: dabbsson_2000l_bms_string
        state: >
          {% set BMS = states('sensor.2000l_power_string') %}
          {% if BMS is search("BMS主机") %}
            {{ states('sensor.2000l_power_string') }}
          {% else %}
            {{ this.state }}
          {% endif %}

      - name: "2000L AC Volts In"
        unique_id: dabbsson_2000l_ac_volts_in
        unit_of_measurement: "V"
        device_class: voltage
        state: >
          {% set ac_data = states('sensor.2000l_power_string') | regex_findall('AC输入\{([-\d.]+)V') %}
          {{ ac_data[0] if ac_data else none }}

      - name: "2000L AC Power In"
        unique_id: dabbsson_2000l_ac_power_in
        unit_of_measurement: "W"
        device_class: power
        state: >
          {% set ac_data = states('sensor.2000l_power_string') | regex_findall('AC输入\{[^}]*,\s*([\d.]+)W') %}
          {{ ac_data[0] if ac_data else none }}

      - name: "2000L AC Power Out"
        unique_id: dabbsson_2000l_ac_power_out
        unit_of_measurement: "W"
        device_class: power
        state: >
          {% set ac_out = states('sensor.2000l_power_string') | regex_findall('AC输出\{[^}]*,\s*([\d.]+)W') %}
          {{ ac_out[0] if ac_out else none }}

      - name: "2000L Solar Volts In"
        unique_id: dabbsson_2000l_solar_volts_in
        unit_of_measurement: "V"
        device_class: voltage
        state: >
          {% set pv_data = states('sensor.2000l_power_string') | regex_findall('PV\{[^,]+,\s*([\d.]+)V') %}
          {{ pv_data[0] if pv_data else none }}

      - name: "2000L Solar Amps In"
        unique_id: dabbsson_2000l_solar_amps_in
        unit_of_measurement: "A"
        device_class: current
        state: >
          {% set pv_data = states('sensor.2000l_power_string') | regex_findall('PV\{[^}]*,\s*([\d.]+)A') %}
          {{ pv_data[0] if pv_data else none }}

      - name: "2000L Solar Watts In"
        unique_id: dabbsson_2000l_solar_watts_in
        unit_of_measurement: "W"
        device_class: power
        state: >
          {% set pv_data = (states('sensor.2000l_power_string') | regex_findall('PV\{[^}]*,\s*([\d.]+)W\}')) %}
          {{ pv_data[0] if pv_data else none }}

      - name: "2000L Battery Status"
        unique_id: dabbsson_2000l_battery_status
        state: >
          {% set bat_power_string = states('sensor.2000l_bms_string') | regex_findall('BMS主机\{\d+,([-\d.]+)V,\s*([-\d.]+)A') %}
          {% if bat_power_string %}  
            {% set bat_volts = bat_power_string[0][0] | float(0) %} 
            {% set bat_amps = bat_power_string[0][1] | float(0) %} 
            {% set bat_watts = bat_volts * bat_amps %} 
            {% if bat_watts < -5 %}
             discharging
            {% elif bat_watts > 5 %}
             charging
            {% else %}
             standby
            {% endif %}
          {% endif %}

      - name: "2000L Battery Volts"
        unique_id: dabbsson_2000l_battery_volts
        unit_of_measurement: "V"
        device_class: voltage
        state: >
          {% set bat_power_string = states('sensor.2000l_bms_string') | regex_findall('BMS主机\{\d+,([-\d.]+)V,\s*([-\d.]+)A') %}
          {% if bat_power_string %}  
            {% set bat_volts = bat_power_string[0][0] | float(0) %} 
            {{ bat_volts | round(2) if bat_volts else none }}
          {% else %}
            {{ none }}
          {% endif %}

      - name: "2000L Battery Current"
        unique_id: dabbsson_2000l_battery_current
        unit_of_measurement: "A"
        device_class: current
        state: >
          {% set bat_power_string = states('sensor.2000l_bms_string') | regex_findall('BMS主机\{\d+,([-\d.]+)V,\s*([-\d.]+)A') %}
          {% if bat_power_string %}  
            {% set bat_amps = bat_power_string[0][1] | float(0) %} 
            {{ bat_amps | round(2) if bat_amps else none }}
          {% else %}
            {{ none }}
          {% endif %}

      - name: "2000L Battery Watts In"
        unique_id: dabbsson_2000l_battery_watts_in
        unit_of_measurement: "W"
        device_class: power
        state: >
          {% set bat_power_string = states('sensor.2000l_bms_string') | regex_findall('BMS主机\{\d+,([-\d.]+)V,\s*([-\d.]+)A') %}
          {% if bat_power_string %}  
            {% set bat_volts = bat_power_string[0][0] | float(0) %} 
            {% set bat_amps = bat_power_string[0][1] | float(0) %} 
            {% set bat_watts = bat_volts * bat_amps %} 
            {{ bat_watts | round(2) if bat_watts >= 0 else 0 }}
          {% else %}
            {{ none }}
          {% endif %}

      - name: "2000L Battery Watts Out"
        unique_id: dabbsson_2000l_battery_watts_out
        unit_of_measurement: "W"
        device_class: power
        state: >
          {% set bat_power_string = states('sensor.2000l_bms_string') | regex_findall('BMS主机\{\d+,([-\d.]+)V,\s*([-\d.]+)A') %}
          {% if bat_power_string %}  
            {% set bat_volts = bat_power_string[0][0] | float(0) %} 
            {% set bat_amps = bat_power_string[0][1] | float(0) %} 
            {% set bat_watts = bat_volts * bat_amps %} 
            {{ bat_watts | abs | round(2) if bat_watts <= 0 else 0 }}
          {% else %}
            {{ none }}
          {% endif %}

      - name: "2000L BMS Temperature"
        unique_id: dabbsson_2000l_bms_temp
        unit_of_measurement: "°C"
        device_class: temperature
        state: >
          {% set temp = states('sensor.2000l_bms_string') | regex_findall('BMS:(\d+)℃') %}
          {{ temp[0] if temp else none }}

      - name: "2000L Inverter Temperature"
        unique_id: dabbsson_2000l_inv_temp
        unit_of_measurement: "°C"
        device_class: temperature
        state: >
          {% set temp = states('sensor.2000l_bms_string') | regex_findall('INV:(\d+)℃') %}
          {{ temp[0] if temp else none }}

      - name: "2000L Fan Speed"
        unique_id: dabbsson_2000l_fan_speed
        state: >
          {% set speed = states('sensor.2000l_bms_string') | regex_findall('转速:(\d+)') %}
          {{ speed[0] if speed else none }}

      - name: "2000L Battery Remaining Capacity Ah"
        unique_id: dabbsson_2000l_battery_ah
        unit_of_measurement: "Ah"
        state: >
          {% set ah = states('sensor.2000l_bms_string') | regex_findall('容量:\{([-\d.]+),') %}
          {{ ah[0] if ah else none }}

      - name: "2000L State of Health"
        unique_id: dabbsson_2000l_soh
        unit_of_measurement: "%"
        state: >
          {% set soh = states('sensor.2000l_bms_string') | regex_findall('soh:(\d+)') %}
          {{ soh[0] if soh else none }}

      # --- ADDITIONAL DPs (bounced against official standard_status.txt / standard_instruction.txt schema, see README DP table) ---
      # DP 123 (custom_charge_power) is writable -- see the number: block below

      - name: "2000L Device Serial Number"
        unique_id: dabbsson_2000l_serial_number
        state: "{{ states('sensor.dabbsson_2000l_dp_140') }}"

      # DP 110 (display_12v_outpower) is the only metered DC output in the schema --
      # it's the 12V/DC5521 port only, USB-A/USB-C output isn't separately reported.
      - name: "2000L DC Power Out"
        unique_id: dabbsson_2000l_dp_110_power_v2
        unit_of_measurement: "W"
        device_class: power
        state: "{{ states('sensor.dabbsson_2000l_dp_110') | int(0) }}"

      - name: "2000L Energy In"
        unique_id: dabbsson_2000l_energy_in_v2
        unit_of_measurement: "kWh"
        device_class: energy
        state_class: total_increasing
        state: "{{ (states('sensor.dabbsson_2000l_dp_159') | float(0)) / 100 }}"

      - name: "2000L Energy Out"
        unique_id: dabbsson_2000l_energy_out_v2
        unit_of_measurement: "kWh"
        device_class: energy
        state_class: total_increasing
        state: "{{ (states('sensor.dabbsson_2000l_dp_160') | float(0)) / 100 }}"

      - name: "2000L DP 134 Unknown Counter"
        unique_id: dabbsson_2000l_dp_134_counter_v2
        state: "{{ states('sensor.dabbsson_2000l_dp_134') | int(0) }}"

      - name: "2000L DP 135 Unknown Counter"
        unique_id: dabbsson_2000l_dp_135_counter_v2
        state: "{{ states('sensor.dabbsson_2000l_dp_135') | int(0) }}"

      - name: "2000L DP 136 Unknown Counter"
        unique_id: dabbsson_2000l_dp_136_counter_v2
        state: "{{ states('sensor.dabbsson_2000l_dp_136') | int(0) }}"

      - name: "2000L DP 137 Unknown Counter"
        unique_id: dabbsson_2000l_dp_137_counter_v2
        state: "{{ states('sensor.dabbsson_2000l_dp_137') | int(0) }}"

      - name: "2000L DP 143 Unknown Counter"
        unique_id: dabbsson_2000l_dp_143_counter_v2
        unit_of_measurement: "s"
        state: "{{ states('sensor.dabbsson_2000l_dp_143') | int(0) }}"

      # --- RAW/UNDECODED DPs (visible for completeness -- encoding not reverse-engineered yet) ---
      - name: "2000L DP 39 Raw"
        unique_id: dabbsson_2000l_dp_39_raw_v2
        state: "{{ states('sensor.dabbsson_2000l_dp_39') }}"

      - name: "2000L DP 40 Raw"
        unique_id: dabbsson_2000l_dp_40_raw_v2
        state: "{{ states('sensor.dabbsson_2000l_dp_40') }}"

      - name: "2000L DP 119 Raw"
        unique_id: dabbsson_2000l_dp_119_raw_v2
        state: "{{ states('sensor.dabbsson_2000l_dp_119') }}"

      - name: "2000L DP 141 Raw"
        unique_id: dabbsson_2000l_dp_141_raw_v2
        state: "{{ states('sensor.dabbsson_2000l_dp_141') }}"

      - name: "2000L DP 146 Raw"
        unique_id: dabbsson_2000l_dp_146_raw_v2
        state: "{{ states('sensor.dabbsson_2000l_dp_146') }}"

      - name: "2000L DP 147 Raw"
        unique_id: dabbsson_2000l_dp_147_raw_v2
        state: "{{ states('sensor.dabbsson_2000l_dp_147') }}"

      - name: "2000L DP 148 Raw"
        unique_id: dabbsson_2000l_dp_148_raw_v2
        state: "{{ states('sensor.dabbsson_2000l_dp_148') }}"

      - name: "2000L DP 149 Raw"
        unique_id: dabbsson_2000l_dp_149_raw_v2
        state: "{{ states('sensor.dabbsson_2000l_dp_149') }}"

      - name: "2000L DP 152 Raw"
        unique_id: dabbsson_2000l_dp_152_raw_v2
        state: "{{ states('sensor.dabbsson_2000l_dp_152') }}"

      - name: "2000L DP 153 Raw"
        unique_id: dabbsson_2000l_dp_153_raw_v2
        state: "{{ states('sensor.dabbsson_2000l_dp_153') }}"

      - name: "2000L DP 156 Raw"
        unique_id: dabbsson_2000l_dp_156_raw_v2
        state: "{{ states('sensor.dabbsson_2000l_dp_156') }}"

      - name: "2000L DP 157 Raw"
        unique_id: dabbsson_2000l_dp_157_raw_v2
        state: "{{ states('sensor.dabbsson_2000l_dp_157') }}"

  - binary_sensor:
      # DP 25 (beep) is writable -- see the switch: block below

      # --- ADDITIONAL DPs (bounced against official schema, see README DP table) ---
      - name: "2000L AC Charging Status"
        unique_id: dabbsson_2000l_dp_101_state
        availability: "{{ states('sensor.dabbsson_2000l_dp_101') not in ['unknown', 'unavailable'] }}"
        state: "{{ states('sensor.dabbsson_2000l_dp_101') | lower in ['true', 'on', '1'] }}"

      # DP 109 (ac_switch), 111 (dc12v_output_state), 112 (usb_output_state),
      # 124 (fast_or_slow_charge), 150 (timer_discharge_state), and
      # 151 (timer_charging_state) are all writable -- see the switch: block below

      - name: "2000L Eco Mode Active"
        unique_id: dabbsson_2000l_eco_mode_active
        state: "{{ is_state('select.dabbsson_2000l_device_mode', 'dp_device_mode_eco_mode') }}"

  - select:
      # Friendly wrapper for DP 127's raw enum strings -- writes through to the real
      # LocalTuya select entity, which must be configured for DP 127 (see README).
      - name: "2000L AC Mode"
        unique_id: dabbsson_2000l_ac_mode_friendly
        state: >
          {% set raw = states('select.dabbsson_2000l_device_mode') %}
          {% if raw == 'dp_device_mode_eco_mode' %}
            Eco Mode
          {% elif raw == 'dp_device_mode_standby_mode' %}
            Standby Mode
          {% else %}
            {{ raw }}
          {% endif %}
        options: "{{ ['Eco Mode', 'Standby Mode'] }}"
        select_option:
          - service: select.select_option
            target:
              entity_id: select.dabbsson_2000l_device_mode
            data:
              option: >
                {% if option == "Eco Mode" %}
                  dp_device_mode_eco_mode
                {% else %}
                  dp_device_mode_standby_mode
                {% endif %}

      # DP 102 is the physical LED camping light on the front of the unit (Enum:
      # off/dim/bright/sos) -- NOT an AC-standby-timeout flag as originally assumed.
      # Requires DP 102 re-added in LocalTuya as a Select entity (not Switch/Sensor).
      - name: "2000L LED Light"
        unique_id: dabbsson_2000l_led_light_friendly
        state: >
          {% set raw = states('select.dabbsson_2000l_dp_102') %}
          {% if raw == 'off' %}
            Off
          {% elif raw == 'dim' %}
            Dim
          {% elif raw == 'bright' %}
            Bright
          {% elif raw == 'sos' %}
            SOS Flash
          {% else %}
            {{ raw }}
          {% endif %}
        options: "{{ ['Off', 'Dim', 'Bright', 'SOS Flash'] }}"
        select_option:
          - service: select.select_option
            target:
              entity_id: select.dabbsson_2000l_dp_102
            data:
              option: >
                {% if option == "Off" %}
                  off
                {% elif option == "Dim" %}
                  dim
                {% elif option == "Bright" %}
                  bright
                {% else %}
                  sos
                {% endif %}

      # DP 120 (device_standby_time_set) -- requires re-adding in LocalTuya as a Select
      - name: "2000L Device Standby Time"
        unique_id: dabbsson_2000l_device_standby_select
        state: >
          {% set raw = states('select.dabbsson_2000l_dp_120') %}
          {{ 'Never (Disabled)' if raw == 'close_timing' else raw | replace('_', ' ') | title }}
        options: "{{ ['30 Minutes', '1 Hour', '2 Hours', '6 Hours', '12 Hours', 'Never (Disabled)'] }}"
        select_option:
          - service: select.select_option
            target:
              entity_id: select.dabbsson_2000l_dp_120
            data:
              option: >
                {{ 'close_timing' if option == 'Never (Disabled)' else option | lower | replace(' ', '_') }}

      # DP 121 (lcd_off_time_set) -- requires re-adding in LocalTuya as a Select
      - name: "2000L LCD Off Time"
        unique_id: dabbsson_2000l_lcd_off_select
        state: >
          {% set raw = states('select.dabbsson_2000l_dp_121') %}
          {{ 'Never (Disabled)' if raw == 'close_timing' else raw | replace('_', ' ') | title }}
        options: "{{ ['10 Seconds', '30 Seconds', '1 Minute', '5 Minutes', '30 Minutes', 'Never (Disabled)'] }}"
        select_option:
          - service: select.select_option
            target:
              entity_id: select.dabbsson_2000l_dp_121
            data:
              option: >
                {{ 'close_timing' if option == 'Never (Disabled)' else option | lower | replace(' ', '_') }}

  - switch:
      # Writable booleans confirmed via standard_instruction.txt. Each requires its
      # raw DP re-added in LocalTuya as a Switch entity (not a generic Sensor).
      - name: "2000L Beep"
        unique_id: dabbsson_2000l_beep_switch
        availability: "{{ states('switch.dabbsson_2000l_dp_25') not in ['unknown', 'unavailable'] }}"
        state: "{{ is_state('switch.dabbsson_2000l_dp_25', 'on') }}"
        turn_on:
          - service: switch.turn_on
            target:
              entity_id: switch.dabbsson_2000l_dp_25
        turn_off:
          - service: switch.turn_off
            target:
              entity_id: switch.dabbsson_2000l_dp_25

      - name: "2000L AC Output"
        unique_id: dabbsson_2000l_ac_output_switch
        availability: "{{ states('switch.dabbsson_2000l_dp_109') not in ['unknown', 'unavailable'] }}"
        state: "{{ is_state('switch.dabbsson_2000l_dp_109', 'on') }}"
        turn_on:
          - service: switch.turn_on
            target:
              entity_id: switch.dabbsson_2000l_dp_109
        turn_off:
          - service: switch.turn_off
            target:
              entity_id: switch.dabbsson_2000l_dp_109

      - name: "2000L DC 12V Output"
        unique_id: dabbsson_2000l_dc12v_output_switch
        availability: "{{ states('switch.dabbsson_2000l_dp_111') not in ['unknown', 'unavailable'] }}"
        state: "{{ is_state('switch.dabbsson_2000l_dp_111', 'on') }}"
        turn_on:
          - service: switch.turn_on
            target:
              entity_id: switch.dabbsson_2000l_dp_111
        turn_off:
          - service: switch.turn_off
            target:
              entity_id: switch.dabbsson_2000l_dp_111

      - name: "2000L USB Output"
        unique_id: dabbsson_2000l_usb_output_switch
        availability: "{{ states('switch.dabbsson_2000l_dp_112') not in ['unknown', 'unavailable'] }}"
        state: "{{ is_state('switch.dabbsson_2000l_dp_112', 'on') }}"
        turn_on:
          - service: switch.turn_on
            target:
              entity_id: switch.dabbsson_2000l_dp_112
        turn_off:
          - service: switch.turn_off
            target:
              entity_id: switch.dabbsson_2000l_dp_112

      - name: "2000L Fast Charge Mode"
        unique_id: dabbsson_2000l_fast_charge_switch
        availability: "{{ states('switch.dabbsson_2000l_dp_124') not in ['unknown', 'unavailable'] }}"
        state: "{{ is_state('switch.dabbsson_2000l_dp_124', 'on') }}"
        turn_on:
          - service: switch.turn_on
            target:
              entity_id: switch.dabbsson_2000l_dp_124
        turn_off:
          - service: switch.turn_off
            target:
              entity_id: switch.dabbsson_2000l_dp_124

      - name: "2000L Discharge Timer Enabled"
        unique_id: dabbsson_2000l_discharge_timer_switch
        availability: "{{ states('switch.dabbsson_2000l_dp_150') not in ['unknown', 'unavailable'] }}"
        state: "{{ is_state('switch.dabbsson_2000l_dp_150', 'on') }}"
        turn_on:
          - service: switch.turn_on
            target:
              entity_id: switch.dabbsson_2000l_dp_150
        turn_off:
          - service: switch.turn_off
            target:
              entity_id: switch.dabbsson_2000l_dp_150

      - name: "2000L Charging Timer Enabled"
        unique_id: dabbsson_2000l_charging_timer_switch
        availability: "{{ states('switch.dabbsson_2000l_dp_151') not in ['unknown', 'unavailable'] }}"
        state: "{{ is_state('switch.dabbsson_2000l_dp_151', 'on') }}"
        turn_on:
          - service: switch.turn_on
            target:
              entity_id: switch.dabbsson_2000l_dp_151
        turn_off:
          - service: switch.turn_off
            target:
              entity_id: switch.dabbsson_2000l_dp_151

  - number:
      # Writable integers confirmed via standard_instruction.txt. Each requires its
      # raw DP re-added in LocalTuya as a Number entity (not a generic Sensor).
      # Min/max below are practical UI limits, not the raw schema's unbounded max.
      - name: "2000L AC Standby Time"
        unique_id: dabbsson_2000l_ac_standby_time_number
        unit_of_measurement: "min"
        min: 0
        max: 1440
        step: 1
        state: "{{ states('number.dabbsson_2000l_dp_122') | int(0) }}"
        set_value:
          - service: number.set_value
            target:
              entity_id: number.dabbsson_2000l_dp_122
            data:
              value: "{{ value }}"

      - name: "2000L AC Charge Power Limit"
        unique_id: dabbsson_2000l_charge_power_limit_number
        unit_of_measurement: "W"
        device_class: power
        min: 0
        max: 2000
        step: 10
        state: "{{ states('number.dabbsson_2000l_dp_123') | int(0) }}"
        set_value:
          - service: number.set_value
            target:
              entity_id: number.dabbsson_2000l_dp_123
            data:
              value: "{{ value }}"

      - name: "2000L Output Voltage Level"
        unique_id: dabbsson_2000l_output_vol_level_number
        unit_of_measurement: "V"
        min: 100
        max: 130
        step: 1
        state: "{{ states('number.dabbsson_2000l_dp_145') | int(0) }}"
        set_value:
          - service: number.set_value
            target:
              entity_id: number.dabbsson_2000l_dp_145
            data:
              value: "{{ value }}"

  - button:
      # Cycles the LED light through off -> dim -> bright -> sos -> off, so it can
      # be operated like the unit's physical button from a single dashboard tile.
      - name: "2000L Cycle LED Light"
        unique_id: dabbsson_2000l_cycle_led_button
        press:
          - service: select.select_option
            target:
              entity_id: select.dabbsson_2000l_dp_102
            data:
              option: >
                {% set order = ['off', 'dim', 'bright', 'sos'] %}
                {% set current = states('select.dabbsson_2000l_dp_102') %}
                {% set idx = order.index(current) if current in order else -1 %}
                {{ order[(idx + 1) % 4] }}

# ==============================================================================
# AUTOMATION RULES: UPS PROTECTION, 80%-90% LIFECYCLE, STORM PREP
# ==============================================================================
# NOTE: named "automation manual" (not "automation") because configuration.yaml
# already has `automation: !include automations.yaml` above -- YAML forbids a
# duplicate top-level "automation" key (it was silently dropping the UI-managed
# automations.yaml). HA merges any "automation <suffix>" key into the same domain.
automation manual:
  # No solar yet. switch.2000l_ac_output (DP109) only gates the AC OUTPUT relay -- per the
  # manual's EPS section it can't stop grid charging or force a battery discharge, so it's never
  # used below to control charging. The real charging lever is number.2000l_ac_charge_power_limit
  # (DP123 custom_charge_power): setting it to 0 stops AC charging entirely without ever touching
  # AC output, so DC/AC loads naturally drain the battery -- this is what lets the battery actually
  # cycle between a floor and a ceiling instead of sitting pinned at 100%.

  # 1. UPS PROTECTION: FORCE ALWAYS-ON (DISABLE ECO / TIMEOUTS)
  # NOTE: this used to also toggle switch.dabbsson_2000l_dp_102 off, based on the
  # wrong assumption that DP 102 was an AC-standby-timeout flag. DP 102 is actually
  # the physical LED camping light -- removed from this automation. Control it via
  # select.2000l_led_light / button.2000l_cycle_led_light on the dashboard instead.
  - alias: "Dabbsson: Force Always-On UPS Mode"
    id: dabbsson_force_always_on_ups
    trigger:
      - platform: state
        entity_id: select.2000l_device_standby_time
    condition:
      - condition: not
        conditions:
          - condition: state
            entity_id: select.2000l_device_standby_time
            state: "Never (Disabled)"
    action:
      - service: select.select_option
        target:
          entity_id: select.2000l_device_standby_time
        data:
          option: "Never (Disabled)"

  # 2. HARD SAFETY FLOOR: never let the battery drop below 20%. Restores real charging (charge
  # power back to 700W) in case #4 below had zeroed it out -- switch.turn_on is kept too as a
  # harmless belt-and-suspenders, but charge_power_limit is what actually matters here.
  - alias: "Dabbsson: Battery Floor Protection (Force Grid Below 20%)"
    id: dabbsson_battery_floor_protection
    trigger:
      - platform: numeric_state
        entity_id: sensor.2000l_hardware_battery_state
        below: 21
    action:
      - service: switch.turn_on
        target:
          entity_id: switch.2000l_ac_output
      - service: number.set_value
        target:
          entity_id: number.2000l_ac_charge_power_limit
        data:
          value: 700

  # 3. RESUME AC CHARGING once SOC drops to 80% (lets DC/AC loads drain the battery from 100%
  # down to 80% without grid immediately topping it back off) OR whenever ComEd is cheap.
  # "4" cents/kWh is a starting guess -- check sensor.comed_5_minute_price's history to calibrate.
  - alias: "Dabbsson: Resume Charging Below 80% or on Cheap ComEd Pricing"
    id: dabbsson_resume_charge_80
    trigger:
      - platform: numeric_state
        entity_id: sensor.2000l_hardware_battery_state
        below: 80
      - platform: numeric_state
        entity_id: sensor.comed_5_minute_price
        below: 4
    condition:
      - condition: numeric_state
        entity_id: sensor.2000l_hardware_battery_state
        below: 100
    action:
      - service: number.set_value
        target:
          entity_id: number.2000l_ac_charge_power_limit
        data:
          value: 700

  # 4. STOP AC CHARGING once the battery is full (100%) or ComEd is expensive, so it doesn't just
  # sit pinned at 100% -- DC/AC loads then drain it naturally down toward the 80% floor (#3).
  # Safe to enable (unlike the old DP109-based version): this only throttles charge input via
  # DP123, it never touches the AC output relay, so it can't cut power to the rack.
  - alias: "Dabbsson: Stop Charging When Full or ComEd Is Expensive"
    id: dabbsson_stop_charge_90
    trigger:
      - platform: numeric_state
        entity_id: sensor.2000l_hardware_battery_state
        above: 99
      - platform: numeric_state
        entity_id: sensor.comed_5_minute_price
        above: 8
    condition:
      - condition: numeric_state
        entity_id: sensor.2000l_hardware_battery_state
        above: 20
      - condition: not
        conditions:
          - condition: state
            entity_id: weather.home
            state: "lightning-rainy"
          - condition: state
            entity_id: weather.home
            state: "exceptional"
    action:
      - service: number.set_value
        target:
          entity_id: number.2000l_ac_charge_power_limit
        data:
          value: 0

  # 5. WEATHER OVERRIDE: SEVERE WEATHER STORM PREP -- charge to full and force standby mode,
  # ignoring the normal 80%-100% cycle (outage resilience beats longevity here). Restores charge
  # power in case #4 above had zeroed it out.
  - alias: "Dabbsson: Severe Weather Override (Charge to 100%, Standby Mode)"
    id: dabbsson_severe_weather_override
    trigger:
      - platform: state
        entity_id: weather.home
        to: "lightning-rainy"
      - platform: state
        entity_id: weather.home
        to: "exceptional"
    action:
      - service: switch.turn_on
        target:
          entity_id: switch.2000l_ac_output
      - service: number.set_value
        target:
          entity_id: number.2000l_ac_charge_power_limit
        data:
          value: 700
      - service: select.select_option
        target:
          entity_id: select.2000l_ac_mode
        data:
          option: "Standby Mode"

lovelace:
  resource_mode: yaml
  dashboards:
    lovelace-yaml:
      mode: yaml
      filename: ui-lovelace.yaml
      title: Dabbsson Control
      icon: mdi:lightning-bolt-circle
      show_in_sidebar: true
      
panel_custom:
  - name: panel_develop
    sidebar_title: Developer Tools
    sidebar_icon: mdi:hammer
    url_path: "config/developer-tools/yaml"
    module_url: /api/hassio/app/entrypoint.js
    embed_iframe: true
    require_admin: true

ui-lovelace.yaml

title: Dabbsson 2000L UPS Master Station
views:
  - title: Overview
    path: overview
    icon: mdi:lightning-bolt-circle
    cards:
      # Top Header Overview Bar
      - type: horizontal-stack
        cards:
          - type: entity
            entity: sensor.2000l_hardware_battery_state
            name: Battery SOC
            icon: mdi:battery-high
          - type: entity
            entity: sensor.2000l_battery_status
            name: Operating Mode
            icon: mdi:power-cycle

      # Real-Time Power Gauges
      - type: horizontal-stack
        cards:
          - type: gauge
            entity: sensor.2000l_ac_power_in
            name: Grid Charge In
            max: 2000
            severity:
              green: 0
              yellow: 1000
              red: 1800
          - type: gauge
            entity: sensor.2000l_ac_power_out
            name: AC Load Out
            max: 2200
            severity:
              green: 0
              yellow: 1200
              red: 2000
          - type: gauge
            entity: sensor.2000l_solar_watts_in
            name: Solar PV Input
            max: 1000
            severity:
              green: 300
              yellow: 100
              red: 0
          - type: gauge
            entity: sensor.2000l_dp_110_power
            name: DC Load Out
            max: 1000
            severity:
              green: 0
              yellow: 600
              red: 900

      # Lifetime Energy Totals (DP 159/160 = device's own combined counters; the
      # Solar/Grid/Battery breakdown below is derived since HA setup, not device lifetime,
      # since the device doesn't meter those sources separately)
      - type: entities
        title: Lifetime Energy Totals
        icon: mdi:counter
        entities:
          - entity: sensor.2000l_energy_in
            name: Total Energy In (Device Lifetime)
          - entity: sensor.2000l_energy_out
            name: Total Energy Out (Device Lifetime)
          - entity: sensor.2000l_solar_energy_in_since_setup
            name: Solar Energy In (Since Setup)
          - entity: sensor.2000l_grid_charge_energy_in_since_setup
            name: Grid Charge Energy In (Since Setup)
          - entity: sensor.2000l_battery_usage_since_setup
            name: Battery Usage (Since Setup)

      # Interactive Hardware Control Station
      # NOTE: every entity below is confirmed against the official Tuya
      # standard_status.txt / standard_instruction.txt schema (see README "Full DP
      # ID reference"). DP 102 is the physical LED camping light (Enum), NOT an
      # AC-standby-timeout flag as originally assumed -- controlled via the LED
      # select + cycle button below. DP 127 (Eco/Standby power mode) is a separate
      # writable Enumeration, controlled via select.2000l_ac_mode.
      - type: entities
        title: Hardware Controls & Outputs
        icon: mdi:toggle-switch
        show_header_toggle: false
        entities:
          - entity: switch.2000l_ac_output # DP 109 - requires Switch entity in LocalTuya
            name: AC Output
            icon: mdi:power-socket-us
          - entity: switch.2000l_dc_12v_output # DP 111 - requires Switch entity in LocalTuya
            name: DC 12V Output
            icon: mdi:current-dc
          - entity: switch.2000l_usb_output # DP 112 - requires Switch entity in LocalTuya
            name: USB Output
            icon: mdi:usb-port
          - entity: switch.2000l_beep # DP 25 - requires Switch entity in LocalTuya
            name: Beep / Buzzer
            icon: mdi:volume-high
          - entity: binary_sensor.2000l_ac_charging_status # DP 101 - read-only status
            name: AC Charging Status (read-only)
            icon: mdi:power-plug
          - entity: select.2000l_led_light # DP 102 - requires Select entity in LocalTuya
            name: LED Camping Light
            icon: mdi:flashlight
          - entity: button.2000l_cycle_led_light
            name: Cycle LED Light
            icon: mdi:rotate-right
          - entity: switch.2000l_fast_charge_mode # DP 124 - requires Switch entity in LocalTuya
            name: Fast Charge Mode
            icon: mdi:lightning-bolt-outline
          - entity: select.2000l_ac_mode # DP 127 - requires select.dabbsson_2000l_device_mode in LocalTuya
            name: AC Mode - Eco / Standby
            icon: mdi:leaf
          - entity: binary_sensor.2000l_eco_mode_active # DP 127 derived
            name: Eco Mode Active
            icon: mdi:leaf-circle-outline

      # Settings & Limits (all writable per standard_instruction.txt -- each
      # requires the raw DP re-added in LocalTuya as the noted entity type)
      - type: entities
        title: Settings & Limits
        icon: mdi:tune
        show_header_toggle: false
        entities:
          - entity: number.2000l_ac_standby_time # DP 122 - requires Number entity in LocalTuya
            name: AC Standby Time (minutes, raw)
            icon: mdi:timer-cog-outline
          - entity: select.2000l_device_standby_time # DP 120 - requires Select entity in LocalTuya
            name: Device Standby Time
            icon: mdi:sleep
          - entity: select.2000l_lcd_off_time # DP 121 - requires Select entity in LocalTuya
            name: LCD Off Time
            icon: mdi:monitor-off
          - entity: number.2000l_ac_charge_power_limit # DP 123 - requires Number entity in LocalTuya
            name: Max AC Charge Limit (Watts)
            icon: mdi:gauge-full
          - entity: number.2000l_output_voltage_level # DP 145 - requires Number entity in LocalTuya
            name: AC Output Voltage Target
            icon: mdi:sine-wave
          - entity: switch.2000l_discharge_timer_enabled # DP 150 - requires Switch entity in LocalTuya
            name: Discharge Timer Enabled
            icon: mdi:timer-check-outline
          - entity: switch.2000l_charging_timer_enabled # DP 151 - requires Switch entity in LocalTuya
            name: Charging Timer Enabled
            icon: mdi:timer-check-outline

      # Live Graphing: side by side so price and load trends are easy to eyeball together
      - type: horizontal-stack
        cards:
          - type: sensor
            entity: sensor.comed_5_minute_price
            name: Live ComEd Price
            icon: mdi:currency-usd
            graph: line
            hours_to_show: 6
            detail: 2
          - type: history-graph
            title: Solar & Battery Power
            hours_to_show: 12
            entities:
              - entity: sensor.2000l_solar_watts_in
              - entity: sensor.2000l_battery_watts_in
              - entity: sensor.2000l_battery_watts_out

  - title: Diagnostics
    path: diagnostics
    icon: mdi:microchip
    cards:
      # Comprehensive Health & BMS Diagnostics
      - type: entities
        title: BMS Health & Deep Diagnostics
        icon: mdi:microchip
        entities:
          - entity: sensor.2000l_system_temperature
            name: Internal Main Temp
          - entity: sensor.2000l_bms_temperature
            name: BMS Pack Temp
          - entity: sensor.2000l_inverter_temperature
            name: Inverter Temp
          - entity: sensor.2000l_fan_speed
            name: Fan RPM Status
          - entity: sensor.2000l_battery_volts
            name: Pack Voltage
          - entity: sensor.2000l_battery_current
            name: Pack Amperage
          - entity: sensor.2000l_battery_remaining_capacity_ah
            name: Remaining Capacity (Ah)
          - entity: sensor.2000l_state_of_health
            name: Battery State of Health (SOH)
          - entity: sensor.2000l_dp_110_power
            name: DC Power Out (12V Port)
          - entity: sensor.2000l_dp_134_unknown_counter
            name: Inverter Error Bitmap (0 = no faults)
          - entity: sensor.2000l_dp_135_unknown_counter
            name: EMS Error Bitmap (0 = no faults)
          - entity: sensor.2000l_dp_136_unknown_counter
            name: BMS Error Bitmap (0 = no faults)
          - entity: sensor.2000l_dp_137_unknown_counter
            name: MPPT Error Bitmap (0 = no faults)

      # System Info & Software Timers
      - type: entities
        title: System Info & Software Timers
        icon: mdi:information
        entities:
          - entity: sensor.2000l_bms_firmware
            name: BMS Firmware Version
          - entity: sensor.2000l_inverter_firmware
            name: Inverter Firmware Version
          - entity: sensor.2000l_pd_subsystem_firmware
            name: PD Firmware Version
          - entity: sensor.2000l_device_serial_number
            name: Device Serial Number
          - entity: sensor.2000l_dp_143_unknown_counter
            name: OTA BMS/INV Time (s)

      # Raw parsed strings, useful when a regex-derived sensor looks wrong
      - type: entities
        title: Raw Data Strings
        icon: mdi:code-braces
        entities:
          - entity: sensor.2000l_power_string
            name: Raw DP 158 Power String
          - entity: sensor.2000l_bms_string
            name: Latched BMS String
          - entity: switch.2000l_beep
            name: Beep/Buzzer Enabled (DP 25)

      # Truly raw/undecoded DPs -- every DP in the official schema is now mapped to
      # SOME entity (see README "Full DP ID reference"), but these have never been
      # reverse-engineered beyond "here's the raw bytes/string." Timer_* DPs are
      # base64-encoded schedule blobs; power_data/run_state/device_state/daily_log
      # have not reported any value yet on this device.
      - type: entities
        title: Raw / Undecoded DPs
        icon: mdi:help-box
        entities:
          - entity: sensor.2000l_dp_39_raw
            name: DP 39 - Power Data (raw)
          - entity: sensor.2000l_dp_40_raw
            name: DP 40 - Run State (raw)
          - entity: sensor.2000l_dp_119_raw
            name: DP 119 - Device State (raw)
          - entity: sensor.2000l_dp_141_raw
            name: DP 141 - Daily Log (raw)
          - entity: sensor.2000l_dp_146_raw
            name: DP 146 - Timer Power-On Schedule (raw)
          - entity: sensor.2000l_dp_147_raw
            name: DP 147 - Timer Power-Off Schedule (raw)
          - entity: sensor.2000l_dp_148_raw
            name: DP 148 - Timer AC Discharge Schedule (raw)
          - entity: sensor.2000l_dp_149_raw
            name: DP 149 - Timer DC Discharge Schedule (raw)
          - entity: sensor.2000l_dp_152_raw
            name: DP 152 - Timer AC Charging Schedule (raw)
          - entity: sensor.2000l_dp_153_raw
            name: DP 153 - Timer USB Discharging Schedule (raw)
          - entity: sensor.2000l_dp_156_raw
            name: DP 156 - Merged DP Value Blob (raw)
          - entity: sensor.2000l_dp_157_raw
            name: DP 157 - Merge DP Set (raw)

  - title: Automations & Activity
    path: automations-activity
    icon: mdi:robot
    cards:
      # NOTE: entity IDs here are HA's slugified guess from each automation's
      # alias text. Confirm under Developer Tools > States (filter "automation.")
      # and fix any that differ -- see README "Seeing what automations are running".
      - type: entities
        title: Automation Status (on/off + last triggered)
        show_header_toggle: false
        entities:
          - entity: automation.dabbsson_force_always_on_ups_mode
            name: Force Always-On UPS Mode
            secondary_info: last-triggered
          - entity: automation.dabbsson_battery_floor_protection_force_grid_below_20
            name: Battery Floor Protection (Never Below 20%)
            secondary_info: last-triggered
          - entity: automation.dabbsson_resume_charging_at_80_soc
            name: Resume Charging on Cheap ComEd Pricing
            secondary_info: last-triggered
          - entity: automation.dabbsson_stop_charging_at_90_soc
            name: Stop Charging When Full or Pricing Is Expensive
            secondary_info: last-triggered
          - entity: automation.dabbsson_severe_weather_override_charge_to_100_standby_mode
            name: Severe Weather Override
            secondary_info: last-triggered

      - type: logbook
        title: Recent Automation & Switch Activity
        hours_to_show: 48
        entities:
          - automation.dabbsson_force_always_on_ups_mode
          - automation.dabbsson_battery_floor_protection_force_grid_below_20
          - automation.dabbsson_resume_charging_at_80_soc
          - automation.dabbsson_stop_charging_at_90_soc
          - automation.dabbsson_severe_weather_override_charge_to_100_standby_mode
          - switch.2000l_ac_output
          - select.2000l_device_standby_time
          - select.2000l_ac_mode
          - select.2000l_led_light

      - type: history-graph
        title: Charging Switch State Over Time
        hours_to_show: 48
        entities:
          - entity: switch.2000l_ac_output
          - entity: sensor.comed_5_minute_price
          - entity: sensor.2000l_hardware_battery_state

      - type: markdown
        title: Full Step-by-Step Traces
        content: >
          For a detailed trace of a specific run (which trigger fired, which
          conditions passed/failed, what each action did), go to **Settings →
          Automations & Scenes → pick the automation → ⋮ menu → Traces** in the
          Home Assistant UI. That view isn't embeddable as a Lovelace card —
          the Logbook and history graph above cover *when* things ran; Traces
          cover *why*.

Need Help With Your Own Smart Home Project?

I install whole-home WiFi, security cameras, and smart home automation for homeowners in Chicagoland.

View Smart Home Services →