From cc04c6ddebf5cca7d310c8c7f6eb9c7164cb3b49 Mon Sep 17 00:00:00 2001 From: Vincent Wolsink Date: Mon, 25 Nov 2024 14:03:45 +0100 Subject: [PATCH] Fully retire devstatus endpoint --- .../enphase_envoy/binary_sensor.py | 157 +++++++----------- custom_components/enphase_envoy/const.py | 152 ++++------------- .../enphase_envoy/envoy_endpoints.py | 12 +- .../enphase_envoy/envoy_reader.py | 95 +---------- custom_components/enphase_envoy/sensor.py | 147 +++++----------- 5 files changed, 141 insertions(+), 422 deletions(-) diff --git a/custom_components/enphase_envoy/binary_sensor.py b/custom_components/enphase_envoy/binary_sensor.py index 89d06a1..7ec062f 100644 --- a/custom_components/enphase_envoy/binary_sensor.py +++ b/custom_components/enphase_envoy/binary_sensor.py @@ -1,3 +1,5 @@ +import datetime + from homeassistant.core import HomeAssistant from homeassistant.config_entries import ConfigEntry from homeassistant.helpers.entity_platform import AddEntitiesCallback @@ -44,9 +46,9 @@ async def async_setup_entry( ) ) - elif sensor_description.key.startswith("inverters_"): - if coordinator.data.get("inverters_status"): - for inverter in coordinator.data["inverters_status"].keys(): + elif sensor_description.key.startswith("inverter_info_"): + if coordinator.data.get("inverter_info"): + for inverter in coordinator.data["inverter_info"].keys(): device_name = f"Inverter {inverter}" entity_name = f"{device_name} {sensor_description.name}" entities.append( @@ -60,46 +62,36 @@ async def async_setup_entry( ) ) - elif sensor_description.key == "relays": - if coordinator.data.get("relays"): - for relay in coordinator.data["relays"]: - device_name = f"Relay {relay}" - entity_name = f"{device_name} {sensor_description.name}" - - serial_number = relay - entities.append( - EnvoyRelayContactEntity( - sensor_description, - entity_name, - device_name, - serial_number, - serial_number, - coordinator, - config_entry.unique_id, - ) - ) - - elif sensor_description.key.startswith("relays_"): - sensor_key = sensor_description.key.split("_", 1)[-1] - if coordinator.data.get("relays") != None: - for serial_number, data in coordinator.data["relays"].items(): - if data.get(sensor_key, None) == None: - continue - + elif sensor_description.key.startswith("relay_info_"): + if coordinator.data.get("relay_info") != None: + for serial_number, data in coordinator.data["relay_info"].items(): device_name = f"Relay {serial_number}" entity_name = f"{device_name} {sensor_description.name}" - entities.append( - EnvoyRelayGenericEntity( - sensor_description, - entity_name, - device_name, - serial_number, - None, - coordinator, - config_entry.unique_id, + if sensor_description.key == "relay_info_relay": + entities.append( + EnvoyRelayContactEntity( + sensor_description, + entity_name, + device_name, + serial_number, + serial_number, + coordinator, + config_entry.unique_id, + ) + ) + else: + entities.append( + EnvoyRelayEntity( + sensor_description, + entity_name, + device_name, + serial_number, + None, + coordinator, + config_entry.unique_id, + ) ) - ) elif sensor_description.key.startswith("batteries_"): if coordinator.data.get("batteries"): @@ -173,13 +165,17 @@ def unique_id(self): @property def extra_state_attributes(self): """Return the state attributes.""" - if self.coordinator.data.get("inverters_status"): + if self.coordinator.data.get("inverter_info"): value = ( - self.coordinator.data.get("inverters_status") + self.coordinator.data.get("inverter_info") .get(self._device_serial_number) - .get("report_date") + .get("last_rpt_date") ) - return {"last_reported": value} + return { + "last_reported": datetime.datetime.fromtimestamp( + int(value), tz=datetime.timezone.utc + ) + } return None @@ -190,7 +186,7 @@ def device_info(self) -> DeviceInfo or None: return None hw_version = ( - self.coordinator.data.get("inverters_info", {}) + self.coordinator.data.get("inverter_info", {}) .get(self._device_serial_number, {}) .get("part_num", None) ) @@ -210,11 +206,11 @@ def is_on(self) -> bool: .get(self._device_serial_number) .get(self.entity_description.key[14:]) ) - if self.coordinator.data.get("inverters_status"): + if self.entity_description.key.startswith("inverter_info_"): return ( - self.coordinator.data.get("inverters_status") + self.coordinator.data.get("inverter_info") .get(self._device_serial_number) - .get(self.entity_description.key[10:]) + .get(self.entity_description.key[14:]) ) @@ -328,49 +324,27 @@ def is_on(self) -> bool | None: class EnvoyRelayEntity(EnvoyBinaryEntity): """Envoy relay entity.""" - MODEL = "Relay" - - @property - def relay(self): - return self.coordinator.data.get("relays", {}).get( - self._device_serial_number, {} - ) - - @property - def value_key(self): - if "_" in self.entity_description.key: - return self.entity_description.key.split("_", 1)[-1] - return self.entity_description.key - - @property - def available(self) -> bool: - """Return if entity is available.""" - if not self.coordinator.last_update_success: - return False - - if self.value_key != "communicating": - return self.relay.get("communicating") - - return True - @property def is_on(self) -> bool | None: - """Return true if the binary sensor is on.""" - if self.relay is None: - return None - - return self.relay.get("relay") == "closed" + if self.coordinator.data.get("relay_info"): + return ( + self.coordinator.data.get("relay_info") + .get(self._device_serial_number) + .get(self.entity_description.key[11:]) + ) @property def extra_state_attributes(self) -> dict | None: """Return the state attributes.""" - if self.relay is None: - return None - + relay_info = self.coordinator.data.get("relay_info").get( + self._device_serial_number + ) return { - "last_reported": self.relay.get("report_date"), - "reason_code": self.relay.get("reason_code"), - "reason": self.relay.get("reason"), + "last_reported": datetime.datetime.fromtimestamp( + int(relay_info.get("last_rpt_date")), tz=datetime.timezone.utc + ), + "reason_code": relay_info.get("reason_code"), + "reason": relay_info.get("reason"), } @@ -379,19 +353,14 @@ class EnvoyRelayContactEntity(EnvoyRelayEntity): def icon(self): return "mdi:electric-switch-closed" if self.is_on else "mdi:electric-switch" - -class EnvoyRelayGenericEntity(EnvoyRelayEntity): @property def is_on(self) -> bool | None: - """Return true if the binary sensor is on.""" - if self.relay is None: - return None - - return self.relay.get(self.value_key) - - @property - def extra_state_attributes(self) -> dict | None: - return None + return ( + self.coordinator.data.get("relay_info") + .get(self._device_serial_number) + .get("relay") + == "closed" + ) class EnvoyBatteryEntity(CoordinatorEntity, BinarySensorEntity): diff --git a/custom_components/enphase_envoy/const.py b/custom_components/enphase_envoy/const.py index b0dba38..83aa41e 100644 --- a/custom_components/enphase_envoy/const.py +++ b/custom_components/enphase_envoy/const.py @@ -143,47 +143,6 @@ def get_model_name(model, hardware_id): device_class=SensorDeviceClass.ENERGY, suggested_display_precision=0, ), - SensorEntityDescription( - key="inverters", - name="Production", - native_unit_of_measurement=UnitOfPower.WATT, - state_class=SensorStateClass.MEASUREMENT, - device_class=SensorDeviceClass.POWER, - suggested_display_precision=0, - ), - # I think this data requires installer perms where inverter_data does not - # SensorEntityDescription( - # key="inverters_ac_voltage", - # name="AC Voltage", - # native_unit_of_measurement=UnitOfElectricPotential.VOLT, - # state_class=SensorStateClass.MEASUREMENT, - # device_class=SensorDeviceClass.VOLTAGE, - # ), - # I think this data requires installer perms where inverter_data does not - # SensorEntityDescription( - # key="inverters_dc_voltage", - # name="DC Voltage", - # native_unit_of_measurement=UnitOfElectricPotential.VOLT, - # state_class=SensorStateClass.MEASUREMENT, - # device_class=SensorDeviceClass.VOLTAGE, - # ), - # I think this data requires installer perms where inverter_data does not - # SensorEntityDescription( - # key="inverters_dc_current", - # name="DC Current", - # icon="mdi:current-dc", - # native_unit_of_measurement=UnitOfElectricCurrent.AMPERE, - # state_class=SensorStateClass.MEASUREMENT, - # device_class=SensorDeviceClass.CURRENT, - # ), - # I think this data requires installer perms where inverter_data does not - # SensorEntityDescription( - # key="inverters_temperature", - # name="Temperature", - # native_unit_of_measurement=UnitOfTemperature.CELSIUS, - # state_class=SensorStateClass.MEASUREMENT, - # device_class=SensorDeviceClass.TEMPERATURE, - # ), SensorEntityDescription( key="inverter_data_dc_current", name="DC Current", @@ -201,18 +160,9 @@ def get_model_name(model, hardware_id): device_class=SensorDeviceClass.FREQUENCY, suggested_display_precision=3, ), - # This is a dupe of other data - # SensorEntityDescription( - # key="inverter_data_watts", - # name="Data Watts", - # native_unit_of_measurement=UnitOfPower.WATT, - # state_class=SensorStateClass.MEASUREMENT, - # device_class=SensorDeviceClass.POWER, - # suggested_display_precision=0, - # ), - SensorEntityDescription( - key="inverter_data_watts_max", - name="Production Max", + SensorEntityDescription( + key="inverter_data_watts", + name="Production", native_unit_of_measurement=UnitOfPower.WATT, state_class=SensorStateClass.MEASUREMENT, device_class=SensorDeviceClass.POWER, @@ -220,28 +170,27 @@ def get_model_name(model, hardware_id): ), SensorEntityDescription( key="inverter_data_ac_voltage", - name="Voltage", + name="AC Voltage", native_unit_of_measurement=UnitOfElectricPotential.VOLT, state_class=SensorStateClass.MEASUREMENT, device_class=SensorDeviceClass.VOLTAGE, - suggested_display_precision=1, - ), - # This never got data in my system - # SensorEntityDescription( - # key="inverter_data_ac_current", - # name="Data Current", - # native_unit_of_measurement=UnitOfElectricCurrent.AMPERE, - # state_class=SensorStateClass.MEASUREMENT, - # device_class=SensorDeviceClass.CURRENT, - # suggested_display_precision=1, - # ), + suggested_display_precision=3, + ), + SensorEntityDescription( + key="inverter_data_ac_current", + name="AC Current", + native_unit_of_measurement=UnitOfElectricCurrent.AMPERE, + state_class=SensorStateClass.MEASUREMENT, + device_class=SensorDeviceClass.CURRENT, + suggested_display_precision=3, + ), SensorEntityDescription( key="inverter_data_dc_voltage", name="DC Voltage", native_unit_of_measurement=UnitOfElectricPotential.VOLT, state_class=SensorStateClass.MEASUREMENT, device_class=SensorDeviceClass.VOLTAGE, - suggested_display_precision=1, + suggested_display_precision=3, ), SensorEntityDescription( key="inverter_data_temperature", @@ -249,28 +198,8 @@ def get_model_name(model, hardware_id): native_unit_of_measurement=UnitOfTemperature.CELSIUS, state_class=SensorStateClass.MEASUREMENT, device_class=SensorDeviceClass.TEMPERATURE, - suggested_display_precision=1, - ), - # I was hoping for interesting data here, never saw anything useful, just a documentaiton of it existing - # SensorEntityDescription( - # key="inverter_data_rssi", - # name="Data RSSI", - # native_unit_of_measurement=SIGNAL_STRENGTH_DECIBELS, - # state_class=SensorStateClass.MEASUREMENT, - # device_class=SensorDeviceClass.SIGNAL_STRENGTH, - # suggested_display_precision=1, - # entity_category=EntityCategory.DIAGNOSTIC, - # ), - # I was hoping for interesting data here, never saw anything useful, just a documentaiton of it existing - # SensorEntityDescription( - # key="inverter_data_issi", - # name="Data ISSI", - # native_unit_of_measurement=SIGNAL_STRENGTH_DECIBELS, - # state_class=SensorStateClass.MEASUREMENT, - # device_class=SensorDeviceClass.SIGNAL_STRENGTH, - # suggested_display_precision=1, - # entity_category=EntityCategory.DIAGNOSTIC, - # ), + suggested_display_precision=0, + ), SensorEntityDescription( key="inverter_data_lifetime_power", name="Lifetime Energy Production", @@ -285,7 +214,7 @@ def get_model_name(model, hardware_id): native_unit_of_measurement=UnitOfEnergy.WATT_HOUR, state_class=SensorStateClass.TOTAL, device_class=SensorDeviceClass.ENERGY, - suggested_display_precision=1, + suggested_display_precision=0, ), SensorEntityDescription( key="inverter_data_watt_hours_yesterday", @@ -293,7 +222,7 @@ def get_model_name(model, hardware_id): native_unit_of_measurement=UnitOfEnergy.WATT_HOUR, state_class=SensorStateClass.TOTAL, device_class=SensorDeviceClass.ENERGY, - suggested_display_precision=1, + suggested_display_precision=0, ), SensorEntityDescription( key="inverter_data_watt_hours_week", @@ -301,7 +230,7 @@ def get_model_name(model, hardware_id): native_unit_of_measurement=UnitOfEnergy.WATT_HOUR, state_class=SensorStateClass.TOTAL, device_class=SensorDeviceClass.ENERGY, - suggested_display_precision=1, + suggested_display_precision=0, ), # This data is in attributes too, but seemed helpful to be in diagnostics SensorEntityDescription( @@ -428,7 +357,7 @@ def get_model_name(model, hardware_id): entity_category=EntityCategory.DIAGNOSTIC, ), SensorEntityDescription( - key="inverters_communication_level", + key="inverter_pcu_communication_level", name="Communication Level", device_class=SensorDeviceClass.POWER_FACTOR, state_class=SensorStateClass.MEASUREMENT, @@ -436,7 +365,7 @@ def get_model_name(model, hardware_id): entity_category=EntityCategory.DIAGNOSTIC, ), SensorEntityDescription( - key="relays_communication_level", + key="relay_pcu_communication_level", name="Communication Level", device_class=SensorDeviceClass.POWER_FACTOR, state_class=SensorStateClass.MEASUREMENT, @@ -450,13 +379,13 @@ def get_model_name(model, hardware_id): entity_category=EntityCategory.DIAGNOSTIC, ), SensorEntityDescription( - key="inverters_software", + key="inverter_info_img_pnum_running", name="Firmware Version", icon="mdi:memory", entity_category=EntityCategory.DIAGNOSTIC, ), SensorEntityDescription( - key="relays_software", + key="relay_info_img_pnum_running", name="Firmware Version", icon="mdi:memory", entity_category=EntityCategory.DIAGNOSTIC, @@ -587,28 +516,14 @@ def get_model_name(model, hardware_id): ) BINARY_SENSORS = ( - # This sensor appears to mirror the 'communicating' sensor - # BinarySensorEntityDescription( - # key="inverter_data_gone", - # name="Data Gone", - # device_class=BinarySensorDeviceClass.POWER, - # entity_category=EntityCategory.DIAGNOSTIC, - # ), - # This sensor is always on, maybe this is for removed devices or something, documenting, but leaving off - # BinarySensorEntityDescription( - # key="inverter_data_active", - # name="Data Active", - # device_class=BinarySensorDeviceClass.POWER, - # entity_category=EntityCategory.DIAGNOSTIC, - # ), BinarySensorEntityDescription( - key="inverters_producing", + key="inverter_info_producing", name="Producing", device_class=BinarySensorDeviceClass.POWER, entity_category=EntityCategory.DIAGNOSTIC, ), BinarySensorEntityDescription( - key="inverters_communicating", + key="inverter_info_communicating", name="Communicating", device_class=BinarySensorDeviceClass.CONNECTIVITY, entity_category=EntityCategory.DIAGNOSTIC, @@ -619,27 +534,16 @@ def get_model_name(model, hardware_id): device_class=BinarySensorDeviceClass.CONNECTIVITY, ), BinarySensorEntityDescription( - key="relays", + key="relay_info_relay", name="Contact", device_class=BinarySensorDeviceClass.POWER, ), BinarySensorEntityDescription( - key="relays_communicating", + key="relay_info_communicating", name="Communicating", device_class=BinarySensorDeviceClass.CONNECTIVITY, entity_category=EntityCategory.DIAGNOSTIC, ), - BinarySensorEntityDescription( - key="relays_forced", - name="Forced", - device_class=BinarySensorDeviceClass.TAMPER, - entity_category=EntityCategory.DIAGNOSTIC, - ), - BinarySensorEntityDescription( - key="firmware", - name="Firmware", - device_class=BinarySensorDeviceClass.UPDATE, - ), BinarySensorEntityDescription( key="batteries_operating", name="Operating", diff --git a/custom_components/enphase_envoy/envoy_endpoints.py b/custom_components/enphase_envoy/envoy_endpoints.py index 31df7ce..cfc3d3d 100644 --- a/custom_components/enphase_envoy/envoy_endpoints.py +++ b/custom_components/enphase_envoy/envoy_endpoints.py @@ -73,14 +73,14 @@ "url": "https://{}/ivp/pdm/device_data", "cache": 0, "installer_required": False, - "optional": True, - }, - "devstatus": { - "url": "https://{}/ivp/peb/devstatus", - "cache": 0, - "installer_required": True, "optional": False, }, + # "devstatus": { + # "url": "https://{}/ivp/peb/devstatus", + # "cache": 0, + # "installer_required": True, + # "optional": True, + # }, "pcu_comm_check": { "url": "https://{}/installer/pcu_comm_check", "cache": 3600, diff --git a/custom_components/enphase_envoy/envoy_reader.py b/custom_components/enphase_envoy/envoy_reader.py index bd0e538..f3744ca 100644 --- a/custom_components/enphase_envoy/envoy_reader.py +++ b/custom_components/enphase_envoy/envoy_reader.py @@ -97,41 +97,6 @@ def parse_devicedata(data): return idd -def parse_devstatus(data): - def convert_dev(dev): - def iter(): - for key, value in dev.items(): - if key == "reportDate": - yield "report_date", ( - time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(value)) - if value - else None - ) - elif key == "dcVoltageINmV": - yield "dc_voltage", int(value) / 1000 - elif key == "dcCurrentINmA": - yield "dc_current", int(value) / 1000 - elif key == "acVoltageINmV": - yield "ac_voltage", int(value) / 1000 - elif key == "acPowerINmW": - yield "ac_power", int(value) / 1000 - else: - yield key, value - - return dict(iter()) - - new_data = {} - for key, val in data.items(): - if val.get("fields", None) == None or val.get("values", None) == None: - new_data[key] = val - continue - - new_data[key] = [ - convert_dev(dict(zip(val["fields"], entry))) for entry in val["values"] - ] - return new_data - - class EnvoyReaderError(Exception): pass @@ -280,10 +245,7 @@ def set_endpoint_data(self, endpoint, response): return content_type = response.headers.get("content-type", "application/json") - if endpoint == "endpoint_devstatus": - # Do extra parsing, to zip the fields and values and make it a proper dict - self.data[endpoint] = parse_devstatus(response.json()) - elif endpoint == "endpoint_device_data": + if endpoint == "endpoint_device_data": # Do extra parsing, to zip the fields and values and make it a proper dict self.data[endpoint] = parse_devicedata(response.json()) elif content_type == "application/json": @@ -440,42 +402,10 @@ def grid_status(self): if grid_status != None: return grid_status == "closed" - inverters_data_value = path_by_token( - owner="endpoint_production_inverters.[?(@.devType==1)]", - installer="endpoint_devstatus.pcu[?(@.devType==1)]", - ) - pcu_availability_value = "endpoint_pcu_comm_check" - @envoy_property - def inverters_production(self): - # We will use the endpoint based on the token_type, which is automatically resolved by the inverters_data property - data = self.get("inverters_data") - - def iter(): - if ( - self.reader.token_type == "installer" - and not self.reader.disable_installer_account_use - ): - for item in data: - yield item["serialNumber"], { - "watt": item["ac_power"], - "report_date": item["report_date"], - } - else: - # endpoint_production_inverters endpoint - for item in data: - yield item["serialNumber"], { - "watt": item["lastReportWatts"], - "report_date": time.strftime( - "%Y-%m-%d %H:%M:%S", time.localtime(item["lastReportDate"]) - ), - } - - return dict(iter()) - @envoy_property(required_endpoint="endpoint_inventory") - def inverters_info(self): + def inverter_info(self): return self._path_to_dict( "endpoint_inventory.[?(@.type=='PCU')].devices[?(@.dev_type==1)]", "serial_num", @@ -488,27 +418,6 @@ def relay_info(self): "serial_num", ) - @envoy_property(required_endpoint="endpoint_devstatus") - def inverters_status(self): - return self._path_to_dict( - "endpoint_devstatus.pcu[?(@.devType==1)]", - "serialNumber", - ) - - @envoy_property(required_endpoint="endpoint_devstatus") - def relays(self): - status = self._path_to_dict( - [ - "endpoint_devstatus.pcu[?(@.devType==12)]", - "endpoint_devstatus.nsrb", - ], - "serialNumber", - ) - if not status: - # fallback to the information which is available with owner token. - status = self.get("relay_info") - return status - @envoy_property(required_endpoint="endpoint_ensemble_inventory") def batteries(self): battery_data = self._resolve_path("endpoint_ensemble_inventory[0].devices") diff --git a/custom_components/enphase_envoy/sensor.py b/custom_components/enphase_envoy/sensor.py index 44d3e36..e06f968 100644 --- a/custom_components/enphase_envoy/sensor.py +++ b/custom_components/enphase_envoy/sensor.py @@ -50,58 +50,9 @@ async def async_setup_entry( if sensor_description.key in ADDITIONAL_METRICS: continue _LOGGER.debug(f"Picking how to handle Sensor {sensor_description}") - if sensor_description.key == "inverters": - if coordinator.data.get("inverters_production"): - for inverter in coordinator.data["inverters_production"]: - device_name = f"Inverter {inverter}" - serial_number = inverter - entities.append( - EnvoyInverterEntity( - description=sensor_description, - name=f"{device_name} {sensor_description.name}", - device_name=device_name, - device_serial_number=serial_number, - serial_number=serial_number, - coordinator=coordinator, - parent_device=config_entry.unique_id, - ) - ) - - elif sensor_description.key == "inverters_software": - if coordinator.data.get("inverters_info"): - for serial_number in coordinator.data["inverters_production"]: - device_name = f"Inverter {serial_number}" - entities.append( - EnvoyInverterFirmwareEntity( - description=sensor_description, - name=f"{device_name} {sensor_description.name}", - device_name=device_name, - device_serial_number=serial_number, - serial_number=None, - coordinator=coordinator, - parent_device=config_entry.unique_id, - ) - ) - - elif sensor_description.key == "relays_software": - if coordinator.data.get("relays"): - for serial_number in coordinator.data["relays"].keys(): - device_name = f"Relay {serial_number}" - entities.append( - EnvoyRelayFirmwareEntity( - description=sensor_description, - name=f"{device_name} {sensor_description.name}", - device_name=device_name, - device_serial_number=serial_number, - serial_number=None, - coordinator=coordinator, - parent_device=config_entry.unique_id, - ) - ) - - elif sensor_description.key == "inverters_communication_level": + if sensor_description.key == "inverter_pcu_communication_level": if coordinator.data.get("pcu_availability"): - for serial_number in coordinator.data["inverters_production"]: + for serial_number in coordinator.data["inverter_device_data"]: device_name = f"Inverter {serial_number}" entities.append( EnvoyInverterSignalEntity( @@ -115,11 +66,11 @@ async def async_setup_entry( ) ) - elif sensor_description.key == "relays_communication_level": - if coordinator.data.get("relays") and coordinator.data.get( + elif sensor_description.key == "relay_pcu_communication_level": + if coordinator.data.get("relay_device_data") and coordinator.data.get( "pcu_availability" ): - for serial_number in coordinator.data["relays"].keys(): + for serial_number in coordinator.data["relay_device_data"].keys(): device_name = f"Relay {serial_number}" entities.append( EnvoyRelaySignalEntity( @@ -153,9 +104,9 @@ async def async_setup_entry( ) ) - elif sensor_description.key.startswith("inverters_"): - if coordinator.data.get("inverters_status"): - for inverter in coordinator.data["inverters_status"].keys(): + elif sensor_description.key.startswith("inverter_info_"): + if coordinator.data.get("inverter_info"): + for inverter in coordinator.data["inverter_info"].keys(): device_name = f"Inverter {inverter}" serial_number = inverter entities.append( @@ -170,6 +121,23 @@ async def async_setup_entry( ) ) + elif sensor_description.key.startswith("relay_info_"): + if coordinator.data.get("relay_info"): + for relay in coordinator.data["relay_info"].keys(): + device_name = f"Relay {inverter}" + serial_number = relay + entities.append( + EnvoyRelayEntity( + description=sensor_description, + name=f"{device_name} {sensor_description.name}", + device_name=device_name, + device_serial_number=serial_number, + serial_number=None, + coordinator=coordinator, + parent_device=config_entry.unique_id, + ) + ) + elif sensor_description.key == "batteries_software": if coordinator.data.get("batteries"): for battery in coordinator.data["batteries"].keys(): @@ -396,33 +364,31 @@ def native_value(self): if serial.get("gone", True): return None return value - elif self.entity_description.key.startswith("inverters_"): - if self.coordinator.data.get("inverters_status"): + elif self.entity_description.key.startswith("inverter_info_"): + if self.coordinator.data.get("inverter_info"): return ( - self.coordinator.data.get("inverters_status") + self.coordinator.data.get("inverter_info") .get(self._device_serial_number) - .get(self.entity_description.key[10:]) + .get(self.entity_description.key[14:]) ) - elif self.coordinator.data.get("inverters_production"): - return ( - self.coordinator.data.get("inverters_production") - .get(self._device_serial_number) - .get("watt") - ) return None @property def extra_state_attributes(self): """Return the state attributes.""" - if self.entity_description.key.startswith("inverters_"): - if self.coordinator.data.get("inverters_status"): + if self.entity_description.key.startswith("inverter_info_"): + if self.coordinator.data.get("inverter_info"): value = ( - self.coordinator.data.get("inverters_status") + self.coordinator.data.get("inverter_info") .get(self._device_serial_number) - .get("report_date") + .get("last_rpt_date") ) - return {"last_reported": value} + return { + "last_reported": datetime.datetime.fromtimestamp( + int(value), tz=datetime.timezone.utc + ) + } elif self.entity_description.key.startswith("inverter_data_"): if self.coordinator.data.get("inverter_device_data"): device_data = self.coordinator.data.get("inverter_device_data") @@ -433,13 +399,6 @@ def extra_state_attributes(self): value, tz=datetime.timezone.utc ) } - elif self.coordinator.data.get("inverters_production"): - value = ( - self.coordinator.data.get("inverters_production") - .get(self._serial_number) - .get("report_date") - ) - return {"last_reported": value} return None @@ -452,16 +411,16 @@ def device_info(self) -> DeviceInfo | None: if self._parent_device: device_info_kw["via_device"] = (DOMAIN, self._parent_device) - if self.coordinator.data.get("inverters_info") and self.coordinator.data.get( - "inverters_info" + if self.coordinator.data.get("inverter_info") and self.coordinator.data.get( + "inverter_info" ).get(self._device_serial_number): device_info_kw["sw_version"] = ( - self.coordinator.data.get("inverters_info") + self.coordinator.data.get("inverter_info") .get(self._device_serial_number) .get("img_pnum_running") ) device_info_kw["hw_version"] = ( - self.coordinator.data.get("inverters_info") + self.coordinator.data.get("inverter_info") .get(self._device_serial_number) .get("part_num") ) @@ -529,28 +488,6 @@ def native_value(self) -> int: return int(data.get(self._device_serial_number, 0)) -class EnvoyInverterFirmwareEntity(EnvoyInverterEntity): - - @property - def native_value(self) -> str: - return ( - self.coordinator.data.get("inverters_info") - .get(self._device_serial_number) - .get("img_pnum_running") - ) - - -class EnvoyRelayFirmwareEntity(EnvoyRelayEntity): - - @property - def native_value(self) -> str: - return ( - self.coordinator.data.get("relay_info", {}) - .get(self._device_serial_number, {}) - .get("img_pnum_running", None) - ) - - class EnvoyInverterSignalEntity(EnvoySignalEntity, EnvoyInverterEntity): pass