From 18d18e219ab10e79929fc060d17d6a09c450cdd8 Mon Sep 17 00:00:00 2001 From: Vincent Wolsink Date: Mon, 25 Nov 2024 16:02:11 +0100 Subject: [PATCH] Add extra sensors for relays --- custom_components/enphase_envoy/const.py | 51 +++++++++++- .../enphase_envoy/envoy_reader.py | 32 +++++++- custom_components/enphase_envoy/sensor.py | 77 ++++++++++++++++++- 3 files changed, 151 insertions(+), 9 deletions(-) diff --git a/custom_components/enphase_envoy/const.py b/custom_components/enphase_envoy/const.py index 83aa41e..bed1116 100644 --- a/custom_components/enphase_envoy/const.py +++ b/custom_components/enphase_envoy/const.py @@ -154,7 +154,7 @@ def get_model_name(model, hardware_id): ), SensorEntityDescription( key="inverter_data_ac_frequency", - name="Frequency", + name="AC Frequency", native_unit_of_measurement=UnitOfFrequency.HERTZ, state_class=SensorStateClass.MEASUREMENT, device_class=SensorDeviceClass.FREQUENCY, @@ -232,7 +232,6 @@ def get_model_name(model, hardware_id): device_class=SensorDeviceClass.ENERGY, suggested_display_precision=0, ), - # This data is in attributes too, but seemed helpful to be in diagnostics SensorEntityDescription( key="inverter_data_last_reading", name="Last Reading", @@ -249,6 +248,7 @@ def get_model_name(model, hardware_id): device_class=None, suggested_display_precision=0, entity_category=EntityCategory.DIAGNOSTIC, + icon="mdi:counter", ), SensorEntityDescription( key="inverter_data_conversion_error", @@ -390,12 +390,59 @@ def get_model_name(model, hardware_id): icon="mdi:memory", entity_category=EntityCategory.DIAGNOSTIC, ), + SensorEntityDescription( + key="relay_data_state_change_count", + name="State Change Count", + entity_category=EntityCategory.DIAGNOSTIC, + suggested_display_precision=0, + icon="mdi:counter", + ), + SensorEntityDescription( + key="relay_data_temperature", + name="Temperature", + native_unit_of_measurement=UnitOfTemperature.CELSIUS, + state_class=SensorStateClass.MEASUREMENT, + device_class=SensorDeviceClass.TEMPERATURE, + suggested_display_precision=0, + ), SensorEntityDescription( key="batteries_software", name="Firmware Version", icon="mdi:memory", entity_category=EntityCategory.DIAGNOSTIC, ), + SensorEntityDescription( + key=f"relay_data_voltage_l1", + name=f"Voltage L1", + native_unit_of_measurement=UnitOfElectricPotential.VOLT, + state_class=SensorStateClass.MEASUREMENT, + device_class=SensorDeviceClass.VOLTAGE, + suggested_display_precision=3, + ), + SensorEntityDescription( + key=f"relay_data_voltage_l2", + name=f"Voltage L2", + native_unit_of_measurement=UnitOfElectricPotential.VOLT, + state_class=SensorStateClass.MEASUREMENT, + device_class=SensorDeviceClass.VOLTAGE, + suggested_display_precision=3, + ), + SensorEntityDescription( + key=f"relay_data_voltage_l3", + name=f"Voltage L3", + native_unit_of_measurement=UnitOfElectricPotential.VOLT, + state_class=SensorStateClass.MEASUREMENT, + device_class=SensorDeviceClass.VOLTAGE, + suggested_display_precision=3, + ), + SensorEntityDescription( + key="relay_data_last_reading", + name="Last Reading", + native_unit_of_measurement=None, + state_class=None, + device_class=SensorDeviceClass.TIMESTAMP, + entity_category=EntityCategory.DIAGNOSTIC, + ), ) ADDITIONAL_METRICS.extend( [ diff --git a/custom_components/enphase_envoy/envoy_reader.py b/custom_components/enphase_envoy/envoy_reader.py index f3744ca..f5c0ec8 100644 --- a/custom_components/enphase_envoy/envoy_reader.py +++ b/custom_components/enphase_envoy/envoy_reader.py @@ -73,6 +73,7 @@ def parse_devicedata(data): lifetime = channel["lifetime"] last_reading = channel["lastReading"] idd[value["sn"]] = { + "type": value["devName"], "sn": value["sn"], "active": value["active"], "watts": channel["watts"]["now"], @@ -94,6 +95,25 @@ def parse_devicedata(data): "gone": value["modGone"], "last_reading": last_reading["endDate"], } + elif ( + isinstance(value, dict) + and "devName" in value + and value["devName"] == "nsrb" + ): + channel = value["channels"][0] # unclear when there might be a channel > 0 + last_reading = channel["lastReading"] + idd[value["sn"]] = { + "type": value["devName"], + "sn": value["sn"], + "active": value["active"], + "temperature": last_reading["temperature"], + "voltage_l1": last_reading["VrmsL1N"] / 1000, + "voltage_l2": last_reading["VrmsL2N"] / 1000, + "voltage_l3": last_reading["VrmsL3N"] / 1000, + "state_change_count": last_reading["stateChngCnt"], + "gone": value["modGone"], + "last_reading": last_reading["endDate"], + } return idd @@ -418,6 +438,14 @@ def relay_info(self): "serial_num", ) + @envoy_property(required_endpoint="endpoint_device_data") + def inverter_device_data(self): + return self._path_to_dict("endpoint_device_data.[?(@.type=='pcu')]", "sn") + + @envoy_property(required_endpoint="endpoint_device_data") + def relay_device_data(self): + return self._path_to_dict("endpoint_device_data.[?(@.type=='nsrb')]", "sn") + @envoy_property(required_endpoint="endpoint_ensemble_inventory") def batteries(self): battery_data = self._resolve_path("endpoint_ensemble_inventory[0].devices") @@ -441,10 +469,6 @@ def batteries(self): def batteries_power(self): return self._path_to_dict("endpoint_ensemble_power.devices:", "serial_num") - @envoy_property(required_endpoint="endpoint_device_data") - def inverter_device_data(self): - return self._resolve_path("endpoint_device_data") - @envoy_property(required_endpoint="endpoint_ensemble_power") def agg_batteries_power(self): batteries_data = self._resolve_path("endpoint_ensemble_power.devices:") diff --git a/custom_components/enphase_envoy/sensor.py b/custom_components/enphase_envoy/sensor.py index e06f968..c7b389a 100644 --- a/custom_components/enphase_envoy/sensor.py +++ b/custom_components/enphase_envoy/sensor.py @@ -121,10 +121,30 @@ async def async_setup_entry( ) ) + elif sensor_description.key.startswith("relay_data_"): + _LOGGER.debug(f"Relay Data Sensor {sensor_description}") + if coordinator.data.get("relay_device_data"): + _LOGGER.debug(f"Relay Data Sensor DATA {sensor_description}") + for relay in coordinator.data["relay_device_data"].keys(): + _LOGGER.debug(f"Relay Data Sensor DATA {relay}") + device_name = f"Relay {relay}" + 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.startswith("relay_info_"): if coordinator.data.get("relay_info"): for relay in coordinator.data["relay_info"].keys(): - device_name = f"Relay {inverter}" + device_name = f"Relay {relay}" serial_number = relay entities.append( EnvoyRelayEntity( @@ -359,7 +379,7 @@ def native_value(self): value = serial.get(self.entity_description.key[14:]) if self.entity_description.key.endswith("last_reading"): return datetime.datetime.fromtimestamp( - value, tz=datetime.timezone.utc + int(value), tz=datetime.timezone.utc ) if serial.get("gone", True): return None @@ -396,7 +416,7 @@ def extra_state_attributes(self): value = serial.get("last_reading") return { "last_reported": datetime.datetime.fromtimestamp( - value, tz=datetime.timezone.utc + int(value), tz=datetime.timezone.utc ) } @@ -437,6 +457,57 @@ def device_info(self) -> DeviceInfo | None: class EnvoyRelayEntity(EnvoyDeviceEntity): + @property + def native_value(self): + if self.entity_description.key.startswith("relay_data_"): + if self.coordinator.data.get("relay_device_data"): + serial = self.coordinator.data.get("relay_device_data").get( + self._device_serial_number + ) + value = serial.get(self.entity_description.key[11:]) + if self.entity_description.key.endswith("last_reading"): + return datetime.datetime.fromtimestamp( + int(value), tz=datetime.timezone.utc + ) + if serial.get("gone", True): + return None + return value + elif self.entity_description.key.startswith("relay_info_"): + 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): + """Return the state attributes.""" + if self.entity_description.key.startswith("relay_data_"): + if self.coordinator.data.get("relay_device_data"): + value = ( + self.coordinator.data.get("relay_device_data") + .get(self._device_serial_number) + .get("last_reading") + ) + return { + "last_reported": datetime.datetime.fromtimestamp( + int(value), tz=datetime.timezone.utc + ) + } + elif self.entity_description.key.startswith("relay_info_"): + if self.coordinator.data.get("relay_info"): + value = ( + self.coordinator.data.get("relay_info") + .get(self._device_serial_number) + .get("last_rpt_date") + ) + return { + "last_reported": datetime.datetime.fromtimestamp( + int(value), tz=datetime.timezone.utc + ) + } + @property def device_info(self) -> DeviceInfo | None: """Return the device_info of the device."""