Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions custom_components/givenergy_local/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,37 @@ async def _missing_dashboard_cards(hass: HomeAssistant) -> list[str]:
return [card for card in _REQUIRED_HACS_CARDS if card not in urls]


# unique_id suffixes renamed in givenergy-modbus #174 (2.1.1). The old data is
# valid — IR35 was always AC charge, merely mislabelled "load" — so re-point the
# existing registry entry to the new unique_id, carrying its history, statistics
# and customisations across rather than orphaning it and starting fresh.
_RENAMED_UNIQUE_ID_SUFFIXES = {
"e_load_day": "e_ac_charge_today",
}


def _migrate_unique_ids(hass: HomeAssistant, entry: ConfigEntry) -> None:
"""Re-point entities registered under a renamed unique_id suffix in place."""
registry = er.async_get(hass)
for ent in er.async_entries_for_config_entry(registry, entry.entry_id):
for old, new in _RENAMED_UNIQUE_ID_SUFFIXES.items():
if not ent.unique_id.endswith(f"_{old}"):
continue
new_uid = ent.unique_id[: -len(old)] + new
if registry.async_get_entity_id(ent.domain, DOMAIN, new_uid):
# Target already exists (already migrated, or a genuine collision)
# — don't clobber it; leave the old entry for manual cleanup.
_LOGGER.debug(
"Skipping unique_id migration for %s: %s already exists",
ent.entity_id,
new_uid,
)
break
_LOGGER.info("Migrating unique_id %s -> %s", ent.unique_id, new_uid)
registry.async_update_entity(ent.entity_id, new_unique_id=new_uid)
break


async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
# Persisted topology lets the coordinator skip the cold-detect sweep on
# most reconnects/restarts. Client.detect(prior=...) accepts the cached
Expand Down Expand Up @@ -287,6 +318,10 @@ async def _on_topology_changed(actual: PlantCapabilities) -> None:

hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator

# Re-point any entities under renamed unique_ids before the platforms create
# them, so the existing entity (and its history) is reused rather than orphaned.
_migrate_unique_ids(hass, entry)

await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)

store = Store(hass, _DASHBOARD_STORAGE_VERSION, _DASHBOARD_STORAGE_KEY)
Expand Down
4 changes: 2 additions & 2 deletions custom_components/givenergy_local/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,9 @@
"e_grid_out_day",
"e_grid_in_total",
"e_grid_out_total",
# Load
# Load / Consumption
"p_load_demand",
"e_load_day",
"e_consumption_today",
# Inverter output
"e_inverter_out_total",
# Health — entity description's `key` is "status"; `inverter_status` is
Expand Down
6 changes: 3 additions & 3 deletions custom_components/givenergy_local/dashboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
# Increment whenever the generated YAML layout changes in a meaningful way.
# __init__.py compares this against the last-generated version stored in HA's
# persistent Store and raises a Repairs issue when they diverge.
DASHBOARD_VERSION = 5
DASHBOARD_VERSION = 6


class _NoAliasDumper(yaml.SafeDumper):
Expand Down Expand Up @@ -194,7 +194,7 @@ def _overview_view(inv: str, max_power_kw: int) -> str:
name: Imported
- entity: {_i(inv, "grid_export_today")}
name: Exported
- entity: {_i(inv, "load_energy_today")}
- entity: {_i(inv, "house_consumption_today")}
name: Consumed

- type: custom:apexcharts-card
Expand Down Expand Up @@ -253,7 +253,7 @@ def col_series(entity: str, name: str, color: str) -> str:
graph_span: 30d
series:
{col_series(_i(inv, "pv_energy_today"), "PV Generated", "#FFB300")}
{col_series(_i(inv, "load_energy_today"), "Consumed", "#EF5350")}
{col_series(_i(inv, "house_consumption_today"), "Consumed", "#EF5350")}

- type: custom:apexcharts-card
header:
Expand Down
2 changes: 1 addition & 1 deletion custom_components/givenergy_local/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"iot_class": "local_polling",
"issue_tracker": "https://github.com/dewet22/givenergy-hass/issues",
"requirements": [
"givenergy-modbus>=2.1.0,<3.0.0"
"givenergy-modbus>=2.1.1,<3.0.0"
],
"version": "1.1.0"
}
31 changes: 27 additions & 4 deletions custom_components/givenergy_local/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -490,20 +490,43 @@ class GivEnergyCoordinatorSensorDescription(SensorEntityDescription):
value_fn=lambda inv: inv.p_load_demand,
),
GivEnergyInverterSensorDescription(
key="e_load_day",
name="Load Energy Today",
# Derived house consumption (single-phase only) — matches the GE app's
# "Consumption today". Single-phase inverters expose no consumption
# register; givenergy-modbus computes it (PV gen + grid-in - grid-out -
# AC-charge). Three-phase has no such field, so skip_if_none drops it
# there (and the value_fn getattr keeps it None-safe).
Comment thread
coderabbitai[bot] marked this conversation as resolved.
key="e_consumption_today",
name="House Consumption Today",
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
device_class=SensorDeviceClass.ENERGY,
state_class=SensorStateClass.TOTAL_INCREASING,
value_fn=lambda inv: inv.e_load_day,
value_fn=lambda inv: getattr(inv, "e_consumption_today", None),
skip_if_none=True,
),
GivEnergyInverterSensorDescription(
# Renamed from "Load Energy Today" / e_load_day (givenergy-modbus #174):
# IR35 was a GivTCP-era mislabel — it has always been AC charge, not house
# load. A unique_id migration in __init__.py carries the existing history
# across so it lands under the correct name.
key="e_ac_charge_today",
name="AC Charge Today",
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
device_class=SensorDeviceClass.ENERGY,
state_class=SensorStateClass.TOTAL_INCREASING,
value_fn=lambda inv: inv.e_ac_charge_today,
),
GivEnergyInverterSensorDescription(
# NB: givenergy-modbus #174 found IR44 is PV generation, not inverter AC
# output, and renamed the field e_inverter_out_day -> e_pv_generation_today.
# We HOLD the entity rename (key/name) until the matching *total* (IR45/46)
# is verified and renamed too, so today+total move together — but read the
# new field name directly to avoid the deprecation warning on every poll.
key="e_inverter_out_day",
name="Inverter Output Today",
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
device_class=SensorDeviceClass.ENERGY,
state_class=SensorStateClass.TOTAL_INCREASING,
value_fn=lambda inv: inv.e_inverter_out_day,
value_fn=lambda inv: inv.e_pv_generation_today,
),
GivEnergyInverterSensorDescription(
key="e_inverter_out_total",
Expand Down
4 changes: 2 additions & 2 deletions dashboard/template.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ views:
name: Imported
- entity: sensor.givenergy_inverter_INVERTER_SERIAL_grid_export_today
name: Exported
- entity: sensor.givenergy_inverter_INVERTER_SERIAL_load_energy_today
- entity: sensor.givenergy_inverter_INVERTER_SERIAL_house_consumption_today
name: Consumed

- type: custom:apexcharts-card
Expand Down Expand Up @@ -106,7 +106,7 @@ views:
statistics:
type: max
period: day
- entity: sensor.givenergy_inverter_INVERTER_SERIAL_load_energy_today
- entity: sensor.givenergy_inverter_INVERTER_SERIAL_house_consumption_today
name: Consumed
color: "#EF5350"
type: column
Expand Down
3 changes: 1 addition & 2 deletions docs/migration-from-givtcp.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ Suffixes shown are after stripping the integration prefix and the inverter/batte
| ✅ | `import_energy_total_kwh` | `grid_import_total` | Grid import lifetime |
| ✅ | `invertor_energy_today_kwh` | `inverter_output_today` | Inverter AC output today |
| ✅ | `invertor_energy_total_kwh` | `inverter_output_total` | Inverter AC output lifetime |
| ✅ | `load_energy_today_kwh` | `load_energy_today` | House load today |
| ✅ | `load_energy_today_kwh` | `house_consumption_today` | House consumption today (the integration's derived consumption — givenergy-modbus #174; the old `load_energy_today` was a mislabel that read ~0) |
| ✅ | `pv_energy_today_kwh` | `pv_energy_today` | Solar generation today |
| ✅ | `pv_energy_total_kwh` | `pv_energy_total` | Solar generation lifetime |
| ⚠️ | `ac_charge_energy_total_kwh` | `charge_from_grid_total` | Live values disagree by ~36× (25.5 kWh vs 0.7 kWh). Likely reads a different register block, or has been reset more recently. |
Expand Down Expand Up @@ -413,4 +413,3 @@ Three viable paths:
- **(c) Document the limitation** and let users decide per-entity.

Option (b) is the cleanest long-term answer but depends on upstream work.

2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ version = "0.0.0"
description = "Home Assistant custom component for GivEnergy inverters via local Modbus TCP"
requires-python = ">=3.14.2"
dependencies = [
"givenergy-modbus>=2.1.0,<3.0.0",
"givenergy-modbus>=2.1.1,<3.0.0",
]

[dependency-groups]
Expand Down
Loading
Loading