feat: add parsed battery storage data with power integration#132
Conversation
Add a StorageData/BatteryStorageData model layer and a SolarEdge.get_parsed_storage_data() convenience method that parse the raw storageData response into typed objects. SolarEdge frequently reports lifeTimeEnergyCharged/lifeTimeEnergyDischarged as 0 in the storageData response even while the battery is actively cycling, so the charge and discharge energy are derived by trapezoidal integration of the reliably-populated power telemetry instead. This keeps the device-specific parsing in the library rather than in downstream consumers such as Home Assistant (see home-assistant/core#169964).
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
|
cc @bdraco |
|
Still have this on my list to get back to, but every day I wake up to a new CVE to solve so it keeps moving down the list. Hopefully that will stop soon |
|
Thank you! I can help you if you want if you tell me how you want to go forward. And can't we simply automerge some of these dependabot PRs? |
Dependabot automerge is doable. Add a workflow keyed on |
PR Review — feat: add parsed battery storage data with power integrationSolid, well-scoped additive feature. Merge-ready; one non-blocking accuracy note. Strengths:
Needs attention:
🟢 Suggestions
1. Per-interval sign classification misattributes energy across zero-crossings
|
There was a problem hiding this comment.
Pull request overview
Adds a typed parsing/model layer for the SolarEdge storageData endpoint and a convenience client wrapper to return parsed battery storage data, deriving charge/discharge energy by integrating power telemetry.
Changes:
- Introduces
BatteryStorageDataandStorageDatadataclasses plusintegrate_power(...)in a newmodels.py. - Adds
SolarEdge.get_parsed_storage_data(...)as a wrapper aroundget_storage_data(...). - Re-exports the new models/helpers from the package root and adds unit tests.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
src/aiosolaredge/models.py |
Adds dataclasses and power-integration logic to compute charge/discharge energy from telemetry. |
src/aiosolaredge/solaredge.py |
Adds get_parsed_storage_data(...) returning StorageData instead of raw JSON. |
src/aiosolaredge/__init__.py |
Re-exports the new models/helper at the package root. |
tests/test_models.py |
Adds unit tests for parsing and integrate_power(...). |
tests/test_init.py |
Extends integration-style client test to cover get_parsed_storage_data(...). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| charge_energy = 0.0 | ||
| discharge_energy = 0.0 | ||
| for previous, current in pairwise(telemetries): | ||
| previous_time = _parse_timestamp(previous.get("timeStamp")) | ||
| current_time = _parse_timestamp(current.get("timeStamp")) | ||
| if previous_time is None or current_time is None: | ||
| continue | ||
| interval = (current_time - previous_time).total_seconds() / 3600 | ||
| if interval <= 0: | ||
| continue | ||
| # Trapezoidal integration of power (W) over the interval (h) -> Wh. | ||
| average_power = ( | ||
| (previous.get("power") or 0.0) + (current.get("power") or 0.0) | ||
| ) / 2 | ||
| energy = average_power * interval | ||
| if energy >= 0: | ||
| charge_energy += energy | ||
| else: | ||
| discharge_energy -= energy | ||
| return charge_energy, discharge_energy |
| # Mixed charge then discharge. | ||
| ( | ||
| [ | ||
| _telemetry("2026-06-05 00:00:00", 1000), | ||
| _telemetry("2026-06-05 01:00:00", 1000), | ||
| _telemetry("2026-06-05 02:00:00", -1000), | ||
| _telemetry("2026-06-05 03:00:00", -1000), | ||
| ], | ||
| (1000.0, 1000.0), | ||
| ), | ||
| # Unparsable / missing timestamps are skipped. |
What
Adds a small model layer for the
storageDataendpoint:BatteryStorageData/StorageDatadataclasses in a newmodels.pySolarEdge.get_parsed_storage_data(...)— a convenience wrapper around theexisting
get_storage_data(...)that returns the parsed, typed objectsintegrate_power(...)helper, exported for reuse/testingAll three are re-exported from the package root.
Why
SolarEdge frequently reports
lifeTimeEnergyCharged/lifeTimeEnergyDischargedas
0in thestorageDataresponse, even while the battery is activelycharging or discharging. The
powertelemetry, however, is reliably populated.So instead of reading the (often-zero) lifetime counters, the charge/discharge
energy is derived by trapezoidal integration of the
powertelemetry overtime (positive power = charging, negative = discharging), returned in Wh.
This was verified against a live site: the power integral gave ~4.84 kWh charged
vs. ~4.47 kWh from SoC × capacity (~3% = charging losses), while the lifetime
counters stayed at 0 the whole time.
Keeping this device-specific parsing in the library (rather than in each
consumer) was requested during review of the Home Assistant integration —
see home-assistant/core#169964 and home-assistant/core#173096. A follow-up HA
PR will switch the integration to
get_parsed_storage_data()once this isreleased.
Notes
StorageData.from_response()raisesKeyErrorifstorageData/batteriesare missing, matching the existing "raise on missing key" expectations of
consumers.
ruff,ruff formatand theexisting test suite pass locally.