async def getCircuitPowerLimit(self): status = SysExitStatus.SUCCESS settings = None circuitPowerLimit = 0 self._easee = Easee(self._username, self._password) site = await self._getSite(self._siteKey) if (None == site): print("No site with key %s found." % self._siteKey) status = SysExitStatus.FAILED else: _LOGGER.info("Site: %s", site["createdOn"]) circuit = self._getCircuit(site, self._circuitPanelId) if (None == circuit): print("No circuit with panel id %s found." % self._circuitPanelId) status = SysExitStatus.FAILED else: settings = await (await self._easee.get( f"/api/sites/{site.id}/circuits/{circuit.id}/settings") ).json() circuitPowerLimit = self._calcCircuitPowerLimit(settings) await self._easee.close() return status, circuitPowerLimit
async def test_get_sites(aiosession, aioresponse): token_data = load_json_fixture("token.json") aioresponse.post(f"{BASE_URL}/api/accounts/token", payload=token_data) sites_data = load_json_fixture("sites.json") aioresponse.get(f"{BASE_URL}/api/sites", payload=sites_data) site_data = load_json_fixture("site.json") aioresponse.get(f"{BASE_URL}/api/sites/55555?detailed=true", payload=site_data) easee = Easee("+46070123456", "password", aiosession) sites = await easee.get_sites() assert sites[0].id == 55555 circuits = sites[0].get_circuits() assert circuits[0].id == 12345 chargers_data = load_json_fixture("chargers.json") aioresponse.get(f"{BASE_URL}/api/chargers", payload=chargers_data) chargers = circuits[0].get_chargers() assert chargers[0].id == "ES12345" await easee.close() await aiosession.close()
async def initialize(self): """ initialize the session and get initial data """ client_session = aiohttp_client.async_get_clientsession(self.hass) self.easee = Easee(self.username, self.password, client_session) try: with timeout(TIMEOUT): await self.easee.connect() except asyncio.TimeoutError as err: _LOGGER.debug("Connection to easee login timed out") raise ConfigEntryNotReady from err except ServerFailureException as err: _LOGGER.debug("Easee server failure") raise ConfigEntryNotReady from err except TooManyRequestsException as err: _LOGGER.debug("Easee server too many requests") raise ConfigEntryNotReady from err except AuthorizationFailedException as err: _LOGGER.error("Authorization failed to easee") raise Unauthorized from err except Exception: # pylint: disable=broad-except _LOGGER.error("Unexpected error creating device") return None self.sites: List[Site] = await self.easee.get_sites() self.monitored_sites = self.config.options.get( CONF_MONITORED_SITES, [site["name"] for site in self.sites]) for site in self.sites: if not site["name"] in self.monitored_sites: _LOGGER.debug("Found site (unmonitored): %s %s", site.id, site["name"]) else: _LOGGER.debug("Found site (monitored): %s %s", site.id, site["name"]) for equalizer in site.get_equalizers(): _LOGGER.debug("Found equalizer: %s %s", equalizer.id, equalizer["name"]) self.equalizers.append(equalizer) equalizer_data = EqualizerData(equalizer, site) self.equalizers_data.append(equalizer_data) for circuit in site.get_circuits(): _LOGGER.debug("Found circuit: %s %s", circuit.id, circuit["panelName"]) self.circuits.append(circuit) for charger in circuit.get_chargers(): _LOGGER.debug("Found charger: %s %s", charger.id, charger.name) self.chargers.append(charger) charger_data = ChargerData(charger, circuit, site) self.chargers_data.append(charger_data) self._create_entitites()
async def async_step_user(self, user_input: Optional[ConfigType] = None): """Handle a flow start.""" # Supporting a single account. entries = self.hass.config_entries.async_entries(DOMAIN) if entries: return self.async_abort(reason="already_setup") errors = {} if user_input is not None: username = user_input[CONF_USERNAME] password = user_input[CONF_PASSWORD] self.data = user_input try: client_session = aiohttp_client.async_get_clientsession( self.hass) easee = Easee(username, password, client_session) # Check that login is possible await easee.connect() the_sites: List[Site] = await easee.get_sites() self.sites = [site.name for site in the_sites] if len(self.sites) == 1: return self.async_create_entry( title=self.data[CONF_USERNAME], data=self.data, options={CONF_MONITORED_SITES: self.sites}, ) # Account has more than one site, select sites to add return await self.async_step_sites() except AuthorizationFailedException: errors["base"] = "auth_failure" _LOGGER.debug("AuthorizationFailed") except (ConnectionRefusedError): errors["base"] = "refused_failure" _LOGGER.debug("ConnectionRefusedError") except (ClientConnectionError): errors["base"] = "connection_failure" _LOGGER.debug("ClientConnectionError") return self.async_show_form( step_id="user", data_schema=vol.Schema({ vol.Required(CONF_USERNAME): str, vol.Required(CONF_PASSWORD): str }), errors=errors, )
async def test_get_chargers(aiosession, aioresponse): token_data = load_json_fixture("token.json") aioresponse.post(f"{BASE_URL}/api/accounts/token", payload=token_data) chargers_data = load_json_fixture("chargers.json") aioresponse.get(f"{BASE_URL}/api/chargers", payload=chargers_data) easee = Easee("+46070123456", "password", aiosession) chargers = await easee.get_chargers() assert chargers[0].id == "EH12345" chargers_state_data = load_json_fixture("charger-state.json") aioresponse.get(f"{BASE_URL}/api/chargers/EH12345/state", payload=chargers_state_data) state = await chargers[0].get_state() assert state["chargerOpMode"] == "PAUSED" await easee.close() await aiosession.close()
async def async_step_user(self, user_input: Optional[ConfigType] = None): """Handle a flow start.""" # Supporting a single account. entries = self.hass.config_entries.async_entries(DOMAIN) if entries: return self.async_abort(reason="already_setup") errors = {} if user_input is not None: username = user_input[CONF_USERNAME] password = user_input[CONF_PASSWORD] try: client_session = aiohttp_client.async_get_clientsession( self.hass) easee = Easee(username, password, client_session) # Check that login is possible await easee.connect() return self.async_create_entry(title=username, data=user_input) except AuthorizationFailedException: errors["base"] = "auth_failure" _LOGGER.debug("AuthorizationFailed") except (ConnectionRefusedError): errors["base"] = "refused_failure" _LOGGER.debug("ConnectionRefusedError") except (ClientConnectionError): errors["base"] = "connection_failure" _LOGGER.debug("ClientConnectionError") return self.async_show_form( step_id="user", data_schema=vol.Schema({ vol.Required(CONF_USERNAME): str, vol.Required(CONF_PASSWORD): str }), errors=errors, )
async def setCircuitPowerLimit(self, powerLimit): status = SysExitStatus.SUCCESS self._easee = Easee(self._username, self._password) site = await self._getSite(self._siteKey) if (None == site): print("No site with key %s found." % self._siteKey) status = SysExitStatus.FAILED else: _LOGGER.info("Site: %s", site["createdOn"]) circuit = self._getCircuit(site, self._circuitPanelId) if (None == circuit): print("No circuit with panel id %s found." % self._circuitPanelId) status = SysExitStatus.FAILED else: await self._setCircuitPowerLimit(circuit, powerLimit) await self._easee.close() return status
async def test_get_site_state(aiosession, aioresponse): token_data = load_json_fixture("token.json") aioresponse.post(f"{BASE_URL}/api/accounts/token", payload=token_data) site_state_data = load_json_fixture("site-state.json") aioresponse.get(f"{BASE_URL}/api/sites/54321/state", payload=site_state_data) easee = Easee("+46070123456", "password", aiosession) site_state = await easee.get_site_state("54321") charger_config = site_state.get_charger_config("EH123497") assert charger_config["localNodeType"] == "Master" charger_state = site_state.get_charger_state("EH123497") assert charger_state["chargerOpMode"] == "STANDBY" charger_state = site_state.get_charger_state("NOTEXIST") assert charger_state == None await easee.close() await aiosession.close()
class Controller: """Controller class orchestrating the data fetching and entitities""" def __init__(self, username: str, password: str, hass: HomeAssistant, entry: ConfigEntry): self.username = username self.password = password self.hass = hass self.config = entry self.easee: Easee = None self.sites: List[Site] = [] self.circuits: List[Circuit] = [] self.chargers: List[Charger] = [] self.chargers_data: List[ProductData] = [] self.equalizers: List[Equalizer] = [] self.equalizers_data: List[ProductData] = [] self.switch_entities = [] self.sensor_entities = [] self.equalizer_sensor_entities = [] self.equalizer_binary_sensor_entities = [] def __del__(self): _LOGGER.debug("Controller deleted") async def cleanup(self): if self.easee is not None: for equalizer in self.equalizers: await self.easee.sr_unsubscribe(equalizer) for charger in self.chargers: await self.easee.sr_unsubscribe(charger) await self.easee.close() for tracker in self.trackers: tracker() self.trackers = [] collect() _LOGGER.debug("Controller refcount after cleanup %d", getrefcount(self)) async def initialize(self): """initialize the session and get initial data""" client_session = aiohttp_client.async_get_clientsession(self.hass) self.easee = Easee(self.username, self.password, client_session) self.running_loop = asyncio.get_running_loop() self.event_loop = asyncio.get_event_loop() try: with timeout(TIMEOUT): await self.easee.connect() except asyncio.TimeoutError as err: _LOGGER.debug("Connection to easee login timed out") raise ConfigEntryNotReady from err except ServerFailureException as err: _LOGGER.debug("Easee server failure") raise ConfigEntryNotReady from err except TooManyRequestsException as err: _LOGGER.debug("Easee server too many requests") raise ConfigEntryNotReady from err except AuthorizationFailedException as err: _LOGGER.error("Authorization failed to easee") raise Unauthorized from err except Exception: # pylint: disable=broad-except _LOGGER.error("Unexpected error creating device") return None self.sites: List[Site] = await self.easee.get_sites() self.monitored_sites = self.config.options.get( CONF_MONITORED_SITES, [site.name for site in self.sites]) for site in self.sites: if site.name not in self.monitored_sites: _LOGGER.debug("Found site (unmonitored): %s %s", site.id, site.name) else: _LOGGER.debug("Found site (monitored): %s %s", site.id, site.name) for equalizer in site.get_equalizers(): _LOGGER.debug("Found equalizer: %s %s", equalizer.id, equalizer.name) self.equalizers.append(equalizer) equalizer_data = ProductData(self.event_loop, equalizer, site, EqualizerStreamData) self.equalizers_data.append(equalizer_data) for circuit in site.get_circuits(): _LOGGER.debug("Found circuit: %s %s", circuit.id, circuit["panelName"]) self.circuits.append(circuit) for charger in circuit.get_chargers(): _LOGGER.debug("Found charger: %s %s", charger.id, charger.name) self.chargers.append(charger) charger_data = ProductData(self.event_loop, charger, site, ChargerStreamData, circuit) self.chargers_data.append(charger_data) self._init_count = 0 self.trackers = [] self._create_entitites() async def stream_callback(self, id, data_type, data_id, value): all_data = self.chargers_data + self.equalizers_data for data in all_data: if data.product.id == id: if data.update_stream_data(data_type, data_id, value): _LOGGER.debug("Scheduling update") self.update_ha_state() return def setup_done(self, name): _LOGGER.debug(f"Entities {name} setup done") self._init_count = self._init_count + 1 if self._init_count >= len(PLATFORMS) and self.event_loop is not None: asyncio.run_coroutine_threadsafe(self.add_schedulers(), self.event_loop) def update_ha_state(self): # Schedule an update for all other included entities all_entities = (self.switch_entities + self.sensor_entities + self.binary_sensor_entities + self.equalizer_sensor_entities + self.equalizer_binary_sensor_entities) for entity in all_entities: if entity.enabled and entity.data.is_dirty(): entity.async_schedule_update_ha_state(True) for entity in all_entities: entity.data.mark_clean() async def add_schedulers(self): """Add schedules to udpate data""" # first update tasks = [ charger.schedules_async_refresh() for charger in self.chargers_data ] if tasks: await asyncio.wait(tasks) self.hass.async_add_job(self.refresh_sites_state) self.hass.async_add_job(self.refresh_equalizers_state) # Add interval refresh for site state interval self.trackers.append( async_track_time_interval( self.hass, self.refresh_sites_state, timedelta(seconds=SCAN_INTERVAL_STATE_SECONDS), )) # Add interval refresh for equalizer state interval self.trackers.append( async_track_time_interval( self.hass, self.refresh_equalizers_state, timedelta(seconds=SCAN_INTERVAL_EQUALIZERS_SECONDS), )) # Add interval refresh for schedules self.trackers.append( async_track_time_interval( self.hass, self.refresh_schedules, timedelta(seconds=SCAN_INTERVAL_SCHEDULES_SECONDS), )) # Let other tasks run await asyncio.sleep(0) for equalizer in self.equalizers: await self.easee.sr_subscribe(equalizer, self.stream_callback) for charger in self.chargers: await self.easee.sr_subscribe(charger, self.stream_callback) async def refresh_schedules(self, now=None): """Refreshes the charging schedules data""" for charger in self.chargers_data: if charger.is_schedule_polled() and self.easee.sr_is_connected(): continue await charger.schedules_async_refresh() self.update_ha_state() async def refresh_sites_state(self, now=None): """gets site state for all sites and updates the chargers state and config""" for charger_data in self.chargers_data: charger_data.set_signalr_state(self.easee.sr_is_connected()) charger_data.check_latest_pulse() if charger_data.is_state_polled() and self.easee.sr_is_connected(): continue site_state = await self.easee.get_site_state(charger_data.site.id) charger_id = charger_data.product.id if site_state is not None: charger_data.state = site_state.get_charger_state(charger_id, raw=True) _LOGGER.debug("Charger state: %s ", charger_id) charger_data.config = site_state.get_charger_config(charger_id, raw=True) charger_data.set_signalr_state(self.easee.sr_is_connected()) charger_data.mark_dirty() self.update_ha_state() async def refresh_equalizers_state(self, now=None): """gets equalizer state for all equalizers""" for equalizer_data in self.equalizers_data: equalizer_data.set_signalr_state(self.easee.sr_is_connected()) equalizer_data.check_latest_pulse() if equalizer_data.is_state_polled() and self.easee.sr_is_connected( ): continue try: equalizer_data.state = await equalizer_data.product.get_state() equalizer_data.config = await equalizer_data.product.get_config( ) except Exception: _LOGGER.error("Got server error while polling equalizer state") equalizer_data.set_signalr_state(self.easee.sr_is_connected()) equalizer_data.mark_dirty() self.update_ha_state() def get_sites(self): return self.sites def get_chargers(self): return self.chargers def check_circuit_current( self, circuit_id, currentP1, currentP2, currentP3, compareP1, compareP2, compareP3, ): if currentP2 is None: currentP2 = currentP1 if currentP3 is None: currentP3 = currentP1 for charger_data in self.chargers_data: if charger_data.circuit.id == circuit_id: try: if (charger_data.state[compareP1] != currentP1 or charger_data.state[compareP2] != currentP2 or charger_data.state[compareP3] != currentP3): return charger_data.circuit except KeyError: if (charger_data.config[compareP1] != currentP1 or charger_data.config[compareP2] != currentP2 or charger_data.config[compareP3] != currentP3): return charger_data.circuit return False return None def check_charger_current( self, charger_id, currentP1, currentP2, currentP3, compareP1, compareP2, compareP3, ): if currentP2 is None: currentP2 = currentP1 if currentP3 is None: currentP3 = currentP1 for charger_data in self.chargers_data: if charger_data.product.id == charger_id: try: if (charger_data.state[compareP1] != currentP1 or charger_data.state[compareP2] != currentP2 or charger_data.state[compareP3] != currentP3): return charger_data.product except KeyError: if (charger_data.config[compareP1] != currentP1 or charger_data.config[compareP2] != currentP2 or charger_data.config[compareP3] != currentP3): return charger_data.product return False return None def get_circuits(self): return self.circuits def get_binary_sensor_entities(self): return self.binary_sensor_entities + self.equalizer_binary_sensor_entities def get_sensor_entities(self): return self.sensor_entities + self.equalizer_sensor_entities def get_switch_entities(self): return self.switch_entities def _create_entity( self, object_type, controller, product_data, name, data, ): custom_units = self.config.options.get(CUSTOM_UNITS, {}) if data["units"] in custom_units: data["units"] = CUSTOM_UNITS_TABLE[data["units"]] entity_type_name = ENTITY_TYPES[object_type] entity = entity_type_name( controller=controller, data=product_data, name=name, state_key=data["key"], units=data["units"], convert_units_func=convert_units_funcs.get( data["convert_units_func"], None), attrs_keys=data["attrs"], device_class=data["device_class"], state_class=data.get("state_class", None), icon=data["icon"], state_func=data.get("state_func", None), switch_func=data.get("switch_func", None), enabled_default=data.get("enabled_default", True), entity_category=data.get("entity_category", None), ) _LOGGER.debug( "Adding entity: %s (%s) for product %s", name, object_type, product_data.product.name, ) if object_type == "sensor": self.sensor_entities.append(entity) elif object_type == "switch": self.switch_entities.append(entity) elif object_type == "binary_sensor": self.binary_sensor_entities.append(entity) elif object_type == "eq_sensor": self.equalizer_sensor_entities.append(entity) elif object_type == "eq_binary_sensor": self.equalizer_binary_sensor_entities.append(entity) return entity def _create_entitites(self): self.sensor_entities = [] self.switch_entities = [] self.binary_sensor_entities = [] self.equalizer_sensor_entities = [] self.equalizer_binary_sensor_entities = [] all_easee_entities = { **MANDATORY_EASEE_ENTITIES, **OPTIONAL_EASEE_ENTITIES } for charger_data in self.chargers_data: for key in all_easee_entities: data = all_easee_entities[key] entity_type = data.get("type", "sensor") self._create_entity( entity_type, controller=self, product_data=charger_data, name=key, data=data, ) for equalizer_data in self.equalizers_data: for key in EASEE_EQ_ENTITIES: data = EASEE_EQ_ENTITIES[key] entity_type = data.get("type", "eq_sensor") self._create_entity( entity_type, controller=self, product_data=equalizer_data, name=key, data=data, )