From 27854ab94c8d86da797191363e5207e20d6ba823 Mon Sep 17 00:00:00 2001 From: Arie Catsman Date: Thu, 10 Aug 2023 21:50:56 +0200 Subject: [PATCH] Show FW on device and in diagnostics Obtain FW version from /info page and display in device info Add envoy_info to collect data and also use for diagnostics purpose --- .../enphase_envoy_custom/__init__.py | 1 + .../enphase_envoy_custom/envoy_reader.py | 54 ++++++++++++++++++- .../enphase_envoy_custom/sensor.py | 10 ++++ 3 files changed, 64 insertions(+), 1 deletion(-) diff --git a/custom_components/enphase_envoy_custom/__init__.py b/custom_components/enphase_envoy_custom/__init__.py index c328f2d..f6b3f12 100644 --- a/custom_components/enphase_envoy_custom/__init__.py +++ b/custom_components/enphase_envoy_custom/__init__.py @@ -100,6 +100,7 @@ async def async_update_data(): ] = await envoy_reader.lifetime_consumption_phase(description.key) data["grid_status"] = await envoy_reader.grid_status() + data["envoy_info"] = await envoy_reader.envoy_info() _LOGGER.debug("Retrieved data from API: %s", data) diff --git a/custom_components/enphase_envoy_custom/envoy_reader.py b/custom_components/enphase_envoy_custom/envoy_reader.py index bf1b797..48d4406 100644 --- a/custom_components/enphase_envoy_custom/envoy_reader.py +++ b/custom_components/enphase_envoy_custom/envoy_reader.py @@ -12,6 +12,7 @@ from bs4 import BeautifulSoup from envoy_utils.envoy_utils import EnvoyUtils from homeassistant.util.network import is_ipv6_address +import xmltodict # # Legacy parser is only used on ancient firmwares @@ -33,6 +34,7 @@ ENDPOINT_URL_CHECK_JWT = "https://{}/auth/check_jwt" ENDPOINT_URL_ENSEMBLE_INVENTORY = "http{}://{}/ivp/ensemble/inventory" ENDPOINT_URL_HOME_JSON = "http{}://{}/home.json" +ENDPOINT_URL_INFO_XML = "http{}://{}/info" # pylint: disable=pointless-string-statement @@ -97,7 +99,8 @@ def __init__( # pylint: disable=too-many-arguments enlighten_serial_num=None, https_flag="", use_enlighten_owner_token=False, - token_refresh_buffer_seconds=0 + token_refresh_buffer_seconds=0, + info_refresh_buffer_seconds=3600, ): """Init the EnvoyReader.""" self.host = host.lower() @@ -129,6 +132,9 @@ def __init__( # pylint: disable=too-many-arguments self._token = "" self.use_enlighten_owner_token = use_enlighten_owner_token self.token_refresh_buffer_seconds = token_refresh_buffer_seconds + self.endpoint_info_results = None + self.info_refresh_buffer_seconds = info_refresh_buffer_seconds + self.info_next_refresh_time = datetime.datetime.now() @property def async_client(self): @@ -147,6 +153,8 @@ async def _update(self): await self._update_from_p_endpoint() if self.endpoint_type == ENVOY_MODEL_LEGACY: await self._update_from_p0_endpoint() + + await self._update_info_endpoint() async def _update_from_pc_endpoint(self): """Update from PC endpoint.""" @@ -173,6 +181,25 @@ async def _update_from_p0_endpoint(self): "endpoint_production_results", ENDPOINT_URL_PRODUCTION ) + async def _update_info_endpoint(self): + """Update from info endpoint if next time expried.""" + if self.info_next_refresh_time <= datetime.datetime.now(): + await self._update_endpoint("endpoint_info_results", ENDPOINT_URL_INFO_XML) + self.info_next_refresh_time = datetime.datetime.now() + datetime.timedelta( + seconds=self.info_refresh_buffer_seconds + ) + _LOGGER.debug( + "Info endpoint updated, set next update time: %s using interval: %s", + self.info_next_refresh_time, + self.info_refresh_buffer_seconds, + ) + else: + _LOGGER.debug( + "Info endpoint next update time is: %s using interval: %s", + self.info_next_refresh_time, + self.info_refresh_buffer_seconds, + ) + async def _update_endpoint(self, attr, url): """Update a property from an endpoint.""" formatted_url = url.format(self.https_flag, self.host) @@ -452,6 +479,8 @@ async def detect_model(self): + "Please enter in the needed Enlighten credentials during setup." ) + await self._update_info_endpoint() + if ( self.endpoint_production_json_results and self.endpoint_production_json_results.status_code == 200 @@ -882,6 +911,29 @@ async def grid_status(self): self.has_grid_status = False return None + async def envoy_info(self): + """Return information reported by Envoy info.xml.""" + device_data = {} + + if self.endpoint_info_results: + try: + data = xmltodict.parse(self.endpoint_info_results.text) + device_data["software"] = data["envoy_info"]["device"]["software"] + device_data["pn"] = data["envoy_info"]["device"]["pn"] + device_data["metered"] = data["envoy_info"]["device"]["imeter"] + except Exception: # pylint: disable=broad-except + pass + # add internal key information for envoy class + device_data["Using-model"] = self.endpoint_type + device_data["Using-httpsflag"] = self.https_flag + device_data["Using-MeteringEnabled"] = self.isMeteringEnabled + device_data["Using-GetInverters"] = self.get_inverters + device_data["Using-UseEnligthen"] = self.use_enlighten_owner_token + device_data["Using-InfoUpdateInterval"] = self.info_refresh_buffer_seconds + device_data["Using-hasgridstatus"] = self.has_grid_status + + return device_data + def run_in_console(self): """If running this module directly, print all the values in the console.""" print("Reading...") diff --git a/custom_components/enphase_envoy_custom/sensor.py b/custom_components/enphase_envoy_custom/sensor.py index e20bc0e..ca90fea 100644 --- a/custom_components/enphase_envoy_custom/sensor.py +++ b/custom_components/enphase_envoy_custom/sensor.py @@ -189,11 +189,21 @@ def device_info(self) -> DeviceInfo | None: """Return the device_info of the device.""" if not self._device_serial_number: return None + + sw_version = None + hw_version = None + + if self.coordinator.data.get("envoy_info"): + sw_version = self.coordinator.data.get("envoy_info").get("software", None) + hw_version = self.coordinator.data.get("envoy_info").get("pn", None) + return DeviceInfo( identifiers={(DOMAIN, str(self._device_serial_number))}, manufacturer="Enphase", model="Envoy", name=self._device_name, + sw_version=sw_version, + hw_version=hw_version, ) class CoordinatedEnvoyEntity(EnvoyEntity, CoordinatorEntity):