"""Rainforest data.""" from __future__ import annotations import asyncio from datetime import timedelta import logging import aioeagle import aiohttp from eagle100 import Eagle as Eagle100Reader from requests.exceptions import ConnectionError as ConnectError, HTTPError, Timeout from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_HOST, CONF_TYPE from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import aiohttp_client from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed from .const import ( CONF_CLOUD_ID, CONF_HARDWARE_ADDRESS, CONF_INSTALL_CODE, TYPE_EAGLE_100, TYPE_EAGLE_200, ) _LOGGER = logging.getLogger(__name__) UPDATE_100_ERRORS = (ConnectError, HTTPError, Timeout) class RainforestError(HomeAssistantError): """Base error.""" class CannotConnect(RainforestError): """Error to indicate a request failed.""" class InvalidAuth(RainforestError): """Error to indicate bad auth.""" async def async_get_type(hass, cloud_id, install_code, host): """Try API call 'get_network_info' to see if target device is Eagle-100 or Eagle-200.""" # For EAGLE-200, fetch the hardware address of the meter too. hub = aioeagle.EagleHub( aiohttp_client.async_get_clientsession(hass), cloud_id, install_code, host=host ) try: async with asyncio.timeout(30): meters = await hub.get_device_list() except aioeagle.BadAuth as err: raise InvalidAuth from err except (KeyError, aiohttp.ClientError): # This can happen if it's an eagle-100 meters = None if meters is not None: if meters: hardware_address = meters[0].hardware_address else: hardware_address = None return TYPE_EAGLE_200, hardware_address reader = Eagle100Reader(cloud_id, install_code, host) try: response = await hass.async_add_executor_job(reader.get_network_info) except ValueError as err: # This could be invalid auth because it doesn't check 401 and tries to read JSON. raise InvalidAuth from err except UPDATE_100_ERRORS as error: _LOGGER.error("Failed to connect during setup: %s", error) raise CannotConnect from error # Branch to test if target is Legacy Model if ( "NetworkInfo" in response and response["NetworkInfo"].get("ModelId") == "Z109-EAGLE" ): return TYPE_EAGLE_100, None return None, None class EagleDataCoordinator(DataUpdateCoordinator): """Get the latest data from the Eagle device.""" eagle100_reader: Eagle100Reader | None = None eagle200_meter: aioeagle.ElectricMeter | None = None def __init__(self, hass: HomeAssistant, entry: ConfigEntry) -> None: """Initialize the data object.""" self.entry = entry if self.type == TYPE_EAGLE_100: self.model = "EAGLE-100" update_method = self._async_update_data_100 else: self.model = "EAGLE-200" update_method = self._async_update_data_200 super().__init__( hass, _LOGGER, name=entry.data[CONF_CLOUD_ID], update_interval=timedelta(seconds=30), update_method=update_method, ) @property def cloud_id(self): """Return the cloud ID.""" return self.entry.data[CONF_CLOUD_ID] @property def type(self): """Return entry type.""" return self.entry.data[CONF_TYPE] @property def hardware_address(self): """Return hardware address of meter.""" return self.entry.data[CONF_HARDWARE_ADDRESS] @property def is_connected(self): """Return if the hub is connected to the electric meter.""" if self.eagle200_meter: return self.eagle200_meter.is_connected return True async def _async_update_data_200(self): """Get the latest data from the Eagle-200 device.""" if (eagle200_meter := self.eagle200_meter) is None: hub = aioeagle.EagleHub( aiohttp_client.async_get_clientsession(self.hass), self.cloud_id, self.entry.data[CONF_INSTALL_CODE], host=self.entry.data[CONF_HOST], ) eagle200_meter = aioeagle.ElectricMeter.create_instance( hub, self.hardware_address ) is_connected = True else: is_connected = eagle200_meter.is_connected async with asyncio.timeout(30): data = await eagle200_meter.get_device_query() if self.eagle200_meter is None: self.eagle200_meter = eagle200_meter elif is_connected and not eagle200_meter.is_connected: _LOGGER.warning("Lost connection with electricity meter") _LOGGER.debug("API data: %s", data) return {var["Name"]: var["Value"] for var in data.values()} async def _async_update_data_100(self): """Get the latest data from the Eagle-100 device.""" try: data = await self.hass.async_add_executor_job(self._fetch_data_100) except UPDATE_100_ERRORS as error: raise UpdateFailed from error _LOGGER.debug("API data: %s", data) return data def _fetch_data_100(self): """Fetch and return the four sensor values in a dict.""" if self.eagle100_reader is None: self.eagle100_reader = Eagle100Reader( self.cloud_id, self.entry.data[CONF_INSTALL_CODE], self.entry.data[CONF_HOST], ) out = {} resp = self.eagle100_reader.get_instantaneous_demand()["InstantaneousDemand"] out["zigbee:InstantaneousDemand"] = resp["Demand"] resp = self.eagle100_reader.get_current_summation()["CurrentSummation"] out["zigbee:CurrentSummationDelivered"] = resp["SummationDelivered"] out["zigbee:CurrentSummationReceived"] = resp["SummationReceived"] return out