From b3a39d68429e17a1e4b217c0a9dc2cb46facc919 Mon Sep 17 00:00:00 2001 From: Mark Gascoyne Date: Fri, 19 Jun 2026 08:15:41 +0100 Subject: [PATCH 1/2] feat(lattice): gateway read-only producer (device map + sensors) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GatewayMQTT.lattice_fragment maps each battery inverter it sees (identity + type) on a local-Modbus access path and references the gateway sensor entities (soc/power/temperature) that already exist. Read-only — no control. Co-Authored-By: Claude Opus 4.8 (1M context) --- apps/predbat/gateway.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/apps/predbat/gateway.py b/apps/predbat/gateway.py index 3fbaefd67..3612f9cf5 100644 --- a/apps/predbat/gateway.py +++ b/apps/predbat/gateway.py @@ -648,6 +648,33 @@ def _inject_inverter_entities(self, inv, suffix): self.dashboard_item(f"sensor.{pfx}_battery_charge_today", round(energy.battery_charge_today_wh / 1000.0, 2), attributes=GATEWAY_ATTRIBUTE_TABLE.get("battery_charge_today", {}), app="gateway") self.dashboard_item(f"sensor.{pfx}_battery_discharge_today", round(energy.battery_discharge_today_wh / 1000.0, 2), attributes=GATEWAY_ATTRIBUTE_TABLE.get("battery_discharge_today", {}), app="gateway") + def lattice_fragment(self): + """Read-only Lattice fragment: the inverters this gateway sees, plus their sensors. + + Maps each battery inverter (identity + type) on a local-Modbus access path and references + the gateway sensor entities that already exist for it. Read-only — no control. + """ + from lattice import device_fragment + + sensor_suffixes = [("soc", "%", "soc"), ("battery_power", "W", "battery_power"), ("pv_power", "W", "pv_power"), ("grid_power", "W", "grid_power"), ("load_power", "W", "load_power"), ("temperature", "C", "battery_temperature")] + devices = [] + status = getattr(self, "_last_status", None) + for inv in status.inverters if status else []: + if not (inv.battery.ByteSize() > 0 or inv.battery.capacity_wh > 0): + continue + base = "{}_gateway_{}".format(self.prefix, inv.serial[-6:].lower()) + sensors = [{"capability": cap, "unit": unit, "entity": "sensor.{}_{}".format(base, suffix)} for cap, unit, suffix in sensor_suffixes] + devices.append({"serial": inv.serial, "device_type": self._lattice_device_type(inv.type), "sensors": sensors}) + return device_fragment(devices, provider="local-gateway", name="Local gateway", transport="modbus", preference=10, locality="local") + + @staticmethod + def _lattice_device_type(type_value): + """Best-effort readable device type from the gateway proto inverter-type enum (e.g. 'givenergy_aio').""" + try: + return pb.InverterType.Name(type_value).replace("INVERTER_TYPE_", "").lower() + except Exception: + return "inverter" + def _needs_reconfigure(self, status): """Whether automatic_config should (re-)run for this status. From 73fc554620bccae800afca4887d02a6ef0c37d66 Mon Sep 17 00:00:00 2001 From: Mark Gascoyne Date: Fri, 19 Jun 2026 08:15:42 +0100 Subject: [PATCH 2/2] feat(lattice): GE-Cloud read-only producer (device map + sensor inventory) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GECloudDirect.lattice_fragment publishes its cloud-reachable devices (topology + soc/power sensor inventory) on a low-preference cloud access path, so a device also seen by the gateway resolves to the gateway entity. Read-only — no control. Co-Authored-By: Claude Opus 4.8 (1M context) --- apps/predbat/gecloud.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/apps/predbat/gecloud.py b/apps/predbat/gecloud.py index b4ce3e640..61818ca8d 100644 --- a/apps/predbat/gecloud.py +++ b/apps/predbat/gecloud.py @@ -254,6 +254,13 @@ def initialize(self, ge_cloud_direct, api_key, automatic): self.requests_total = 0 self.failures_total = 0 + def lattice_fragment(self): + """Read-only Lattice fragment: GE-Cloud devices (topology + sensor inventory).""" + from lattice import device_fragment + + devices = [{"serial": str(s), "device_type": "ge-aio", "sensors": [{"capability": "soc", "unit": "%"}, {"capability": "battery_power", "unit": "W"}]} for s in getattr(self, "device_list", []) or []] + return device_fragment(devices, provider="ge-cloud", name="GivEnergy Cloud", transport="https", preference=1, locality="cloud") + async def switch_event(self, entity_id, service): """ Switch event