コード例 #1
0
ファイル: evecc.py プロジェクト: BlueAndi/evecc
    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
コード例 #2
0
ファイル: test_client.py プロジェクト: astrandb/pyeasee
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()
コード例 #3
0
ファイル: controller.py プロジェクト: theneedyguy/easee_hass
    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()
コード例 #4
0
ファイル: config_flow.py プロジェクト: fondberg/easee_hass
    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,
        )
コード例 #5
0
ファイル: test_client.py プロジェクト: astrandb/pyeasee
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()
コード例 #6
0
    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,
        )
コード例 #7
0
ファイル: evecc.py プロジェクト: BlueAndi/evecc
    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
コード例 #8
0
ファイル: test_client.py プロジェクト: astrandb/pyeasee
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()
コード例 #9
0
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,
                )