Battery state of charge (SoC)

This page covers what services/battery_soc/ publishes and how you configure it. Device services shows where the service sits among the others. battery-soc-how-it-works.md explains the coulomb counting itself: the calibration math, the AC/DC efficiency handling and the reasons behind them.

At a glance

services/battery_soc/ + libs/battery_soc_core/ · battery-soc.service · battery_soc_devices.json

This is the only service that polls nothing. It subscribes to power and voltage topics the other bridges already publish and computes the state of charge of one or two LiFePO4 banks by coulomb counting. It recalibrates from the voltage at both ends of the curve, applies an efficiency per converter and compensates the measured cell voltage for the load.

The result is an estimate for monitoring. It does not replace a BMS. Optional DC side topics take over from the AC measurements while they are fresh. The service publishes the combined SoC and the SoC per bank, the net battery power, problem sensors for stale inputs, time to full and to empty, and a number entity to set the SoC by hand after an outage.

Configuration

System type. system_type is ac_coupled (default) or dc_only. It only changes which fields the dashboard form shows. dc_only hides the AC inputs, their efficiencies and dc_max_age_s, and the service rejects a dc_only entry that still has AC inputs.

Inputs. Charger power, inverter power and one voltage topic per bank, each as a topic plus an optional JSON key, because a Trucki stick publishes a bare number and a Shelly publishes an object. Optional DC side power topics take over from the AC measurements while they are fresh (dc_max_age_s). When they go stale, the service falls back to AC. A side with only a DC input uses it as its only source and has no AC fallback.

In the configuration editor, the fields empty_v_per_cell and full_v_per_cell of each bank show the cell voltage measured right now, once raw and once corrected for the load. The service compares the corrected value. A click on either value copies it into the field, and Refresh reads the values again. The raw value is divided by the cell count that is in the form, not by the saved one. Without live data for the bank, the block only says so.

Every power input has an *_invert switch. The same topic can be used in several power inputs, typically one signed sensor for charging and the same sensor inverted for discharging. Negative values count as zero on each side. The DC inputs take *_unit: "W" or "A". A current is converted to watts with the pack voltage.

Validity. Each side needs a source (AC or DC), and bank A needs a voltage. The service rejects any other configuration with an error code and keeps running with the previous one. The dashboard’s configuration page shows the reason. A file without any charge or discharge source does not load.

Topology. parallel (both banks on one DC bus with one voltage) or series. Set bank_b_enabled: false for a single bank. The entity list follows the topology. A series pack also gets the SoC per bank, the voltage difference between the banks and an imbalance warning. On a series pack, bank_a_voltage_measures says whether the bank A sensor measures bank A alone (bank_a, default) or the whole stack (stack). With stack, bank A is the stack minus bank B.

Calibration and efficiency. Cell count and capacity per bank, the open circuit volts per cell that count as empty and full, how far those thresholds may relax at rest (calibration_tolerance_v_per_cell), how long a voltage has to hold before calibration applies, the charger’s AC→DC and the inverter’s DC→AC efficiency, and the charge efficiency of the cells.

Entities

All sensor and binary sensor entities read from one JSON state topic, outstation/<id>/state, through a value_template. The “Field” column is the JSON key in that payload. Most of them have entity_category: diagnostic, as noted in the tables. Unlike the diagnostic sensors of the other services, none of them are disabled by default.

Always published:

Entity Field Unit Notes
SoC soc_combined_pct % Combined. On a series pack this is the weaker bank
Net battery power net_power_w W Positive means net charge
Inputs stale inputs_stale — binary_sensor, problem, diagnostic
Time to full time_to_full_h h diagnostic
Time to empty time_to_empty_h h diagnostic

AC fallback active (ac_fallback_active, binary_sensor, problem, diagnostic) is only announced when a side has both an AC and a DC input.

Per unit, once for pack or once each for bank_a and bank_b in a series topology (<unit> stands for the unit’s name):

Entity Field Unit Notes
Voltage <unit>_voltage_v V diagnostic
Current (estimated) <unit>_current_a A diagnostic
Remaining capacity <unit>_remaining_ah Ah diagnostic
Load-corrected cell voltage <unit>_corrected_v_per_cell V diagnostic
Last calibration last_calibration_<unit> timestamp diagnostic
Calibration threshold, empty <unit>_calibration_empty_v_per_cell V diagnostic
Calibration threshold, full <unit>_calibration_full_v_per_cell V diagnostic
Voltage-based SoC (uncertain) <unit>_voltage_soc_pct % diagnostic
Voltage/coulomb mismatch <unit>_voltage_soc_mismatch — binary_sensor, problem, diagnostic
Calibration jump <unit>_last_calibration_residual_ah Ah diagnostic
Current at calibration <unit>_last_calibration_current_a A diagnostic

Series topology only:

Entity Field Unit Notes
SoC Bank A soc_a_pct %  
SoC Bank B soc_b_pct %  
Voltage difference A/B voltage_delta_v V diagnostic
Banks unbalanced imbalance_warning — binary_sensor, problem, diagnostic

Control:

Entity Topic Range
Set SoC by hand (number) outstation/<id>/cmd/manual_soc for parallel or single bank, .../cmd/manual_soc/bank_a and .../bank_b on a series pack 0–100 %

Use it after an outage that lost the coulomb count.

When the topology changes, the service cleans up discovery. Object IDs that do not belong to the current configuration are cleared with an empty retained payload, so no orphaned entities stay behind.

Also available as a native Home Assistant integration

The same engine is available as a native Home Assistant integration that you can install through HACS. See ha/battery-soc.md.

Written for Energy Node v0.10.0 (Battery SoC service v0.4.7).

Edit on GitHub