예제 #1
0
async def main(loop):
    def parse_recursive(msg):
        if 'type' in msg and msg['type'] == 'refEnum':
            for reference in msg['references']:
                client.get(reference['id'])
        else:
            pprint(msg)

    def parse_message(msg):
        pprint(msg)

    client = NefitCore(serial_number=SERIAL_NUMBER,
                       access_key=ACCESS_KEY,
                       password=PASSWORD,
                       message_callback=parse_message)
    client.connect()
    loop.nefitclient = client

    await client.xmppclient.connected_event.wait()

    client.get('/ecus/rrc/uiStatus')
예제 #2
0
class nefit(generic.FhemModule):

    URL_RRC_UISTATUS = "/ecus/rrc/uiStatus"
    URL_REC_GASUSAGEPOINTER = "/ecus/rrc/recordings/gasusagePointer"
    URL_REC_GASUSAGE = "/ecus/rrc/recordings/gasusage"
    URL_REC_YEARTOTAL = "/ecus/rrc/recordings/yearTotal"
    URL_OUTDOOR_TEMP = "/system/sensors/temperatures/outdoor_t1"
    URL_DAY_STARTSWITH = "/ecus/rrc/dayassunday/day"
    URL_DAY_ACTIVE = "/ecus/rrc/dayassunday/day%DAY%/active"
    URL_DAY_MODE = "/ecus/rrc/dayassunday/day%DAY%/mode"
    URL_DAY_DATE = "/ecus/rrc/dayassunday/day%DAY%/date"
    URL_SYSTEM_PRESSURE = "/system/appliance/systemPressure"

    def __init__(self, logger):
        super().__init__(logger)

        self.first_run = True
        self.module_shutdown = False

        attr_config = {
            "interval": {
                "default": 900,
                "format": "int",
                "help": "Change interval, default is 900.",
            },
            "password": {
                "default": ""
            },
            "access_key": {
                "default": ""
            },
        }
        self.set_attr_config(attr_config)

        set_config = {
            "mode": {
                "args": ["mode"],
                "argsh": ["mode"],
                "params": {
                    "mode": {
                        "default": "clock",
                        "optional": False
                    }
                },
                "options": "clock,manual",
            },
            "desiredTemp": {
                "args": ["temperature"],
                "params": {
                    "temperature": {
                        "format": "float"
                    }
                },
                "options": "slider,10,0.5,30,1",
            },
            "dayAsSunday_00_active": {
                "args": ["onoff"],
                "options": "on,off",
                "function": "set_dayassunday_activate",
                "function_param": "0",
            },
            "dayAsSunday_01_active": {
                "args": ["onoff"],
                "options": "on,off",
                "function": "set_dayassunday_activate",
                "function_param": "1",
            },
            "dayAsSunday_02_active": {
                "args": ["onoff"],
                "options": "on,off",
                "function": "set_dayassunday_activate",
                "function_param": "2",
            },
            "dayAsSunday_03_active": {
                "args": ["onoff"],
                "options": "on,off",
                "function": "set_dayassunday_activate",
                "function_param": "3",
            },
            "dayAsSunday_04_active": {
                "args": ["onoff"],
                "options": "on,off",
                "function": "set_dayassunday_activate",
                "function_param": "4",
            },
            "dayAsSunday_05_active": {
                "args": ["onoff"],
                "options": "on,off",
                "function": "set_dayassunday_activate",
                "function_param": "5",
            },
            "dayAsSunday_06_active": {
                "args": ["onoff"],
                "options": "on,off",
                "function": "set_dayassunday_activate",
                "function_param": "6",
            },
            "dayAsSunday_12_active": {
                "args": ["onoff"],
                "options": "on,off",
                "function": "set_dayassunday_activate",
                "function_param": "12",
            },
            "dayAsSunday_07_active": {
                "args": ["onoff"],
                "options": "on,off",
                "function": "set_dayassunday_activate",
                "function_param": "7",
            },
            "dayAsSunday_08_active": {
                "args": ["onoff"],
                "options": "on,off",
                "function": "set_dayassunday_activate",
                "function_param": "8",
            },
            "dayAsSunday_09_active": {
                "args": ["onoff"],
                "options": "on,off",
                "function": "set_dayassunday_activate",
                "function_param": "9",
            },
            "dayAsSunday_10_active": {
                "args": ["onoff"],
                "options": "on,off",
                "function": "set_dayassunday_activate",
                "function_param": "10",
            },
            "dayAsSunday_11_active": {
                "args": ["onoff"],
                "options": "on,off",
                "function": "set_dayassunday_activate",
                "function_param": "11",
            },
            "dayAsSunday_07_date": {
                "args": ["dateval"],
                "function": "set_dayassunday_date",
                "function_param": "7",
            },
            "dayAsSunday_08_date": {
                "args": ["dateval"],
                "function": "set_dayassunday_date",
                "function_param": "8",
            },
            "dayAsSunday_09_date": {
                "args": ["dateval"],
                "function": "set_dayassunday_date",
                "function_param": "9",
            },
            "dayAsSunday_10_date": {
                "args": ["dateval"],
                "function": "set_dayassunday_date",
                "function_param": "10",
            },
            "dayAsSunday_11_date": {
                "args": ["dateval"],
                "function": "set_dayassunday_date",
                "function_param": "11",
            },
            "todayAsSunday": {
                "args": ["onoff"],
                "options": "on,off"
            },
            "tomorrowAsSunday": {
                "args": ["onoff"],
                "options": "on,off"
            },
        }
        self.set_set_config(set_config)

    # FHEM FUNCTION
    async def Define(self, hash, args, argsh):
        await super().Define(hash, args, argsh)
        if len(args) != 4:
            return "Usage: define netfit_thermostat fhempy nefit <SERIAL_NUMBER>"

        self._serial_number = args[3]
        hash["SERIAL_NUMBER"] = self._serial_number

        if self._attr_access_key == "" or self._attr_password == "":
            await fhem.readingsBeginUpdate(hash)
            await fhem.readingsBulkUpdateIfChanged(
                hash, "state", "set access_key and password attribute")
            await fhem.readingsEndUpdate(hash, 1)
        else:
            await fhem.readingsSingleUpdateIfChanged(hash, "state",
                                                     "connecting", 1)
            self.create_async_task(self.nefit_connect())

    async def set_attr_password(self, hash):
        if self._attr_password != "" and self._attr_access_key != "":
            self.create_async_task(self.nefit_connect())

    async def set_attr_access_key(self, hash):
        if self._attr_password != "" and self._attr_access_key != "":
            self.create_async_task(self.nefit_connect())

    async def set_dayassunday_activate(self, hash, params):
        day = params["function_param"]
        onoff = params["onoff"]
        self._nefit_client.put_value(
            nefit.URL_DAY_ACTIVE.replace("%DAY%", day), onoff)
        await self.update_dayassunday(day)

    async def set_dayassunday_date(self, hash, params):
        day = params["function_param"]
        dateval = params["dateval"]
        self._nefit_client.put_value(nefit.URL_DAY_DATE.replace("%DAY%", day),
                                     dateval)
        await self.update_dayassunday(day)

    async def set_desiredTemp(self, hash, params):
        self._nefit_client.set_temperature(params["temperature"])
        self._nefit_client.get(nefit.URL_RRC_UISTATUS)

    async def set_mode(self, hash, params):
        self._nefit_client.set_usermode(params["mode"])
        self._nefit_client.get(nefit.URL_RRC_UISTATUS)

    async def set_todayAsSunday(self, hash, params):
        # call set_dayassunday_activate
        await self.set_dayassunday_activate(hash, {
            "onoff": params["onoff"],
            "function_param": "11"
        })
        if params["onoff"] == "on":
            # call set_dayssunday_date
            today = datetime.date.today()
            await self.set_dayassunday_date(
                hash,
                {
                    "dateval": f"{today.month:02d}-{today.day:02d}",
                    "function_param": "11",
                },
            )

    async def set_tomorrowAsSunday(self, hash, params):
        # call set_dayassunday_activate
        await self.set_dayassunday_activate(hash, {
            "onoff": params["onoff"],
            "function_param": "10"
        })
        if params["onoff"] == "on":
            # call set_dayssunday_date
            tomorrow = datetime.date.today() + datetime.timedelta(days=1)
            await self.set_dayassunday_date(
                hash,
                {
                    "dateval": f"{tomorrow.month:02d}-{tomorrow.day:02d}",
                    "function_param": "10",
                },
            )

    async def received_message(self, msg):
        try:
            if msg["id"] == nefit.URL_RRC_UISTATUS:
                await self.handle_uistatus(msg)
            elif msg["id"] == nefit.URL_REC_GASUSAGEPOINTER:
                await self.handle_gasusagepointer(msg)
            elif msg["id"] == nefit.URL_REC_GASUSAGE:
                await self.handle_gasusage(msg)
            elif msg["id"] == nefit.URL_REC_YEARTOTAL:
                await self.handle_yeartotal(msg)
            elif msg["id"] == nefit.URL_OUTDOOR_TEMP:
                await self.handle_outdoortemp(msg)
            elif msg["id"] == nefit.URL_SYSTEM_PRESSURE:
                await self.handle_systempressure(msg)
            elif msg["id"].startswith(nefit.URL_DAY_STARTSWITH):
                await self.handle_dayassunday(msg)
        except Exception:
            self.logger.exception(f"Failed to handle msg: {msg}")

    async def handle_systempressure(self, msg):
        if msg["value"] <= msg["maxValue"] and msg["value"] >= msg["minValue"]:
            await fhem.readingsSingleUpdateIfChanged(self.hash,
                                                     "system_pressure",
                                                     float(msg["value"]) / 10,
                                                     1)

    async def handle_dayassunday(self, msg):
        day = int(re.findall(r"\d+", msg["id"])[0])
        val_type = re.findall(r"/(\w+)$", msg["id"])[0]
        await fhem.readingsSingleUpdateIfChanged(
            self.hash, f"dayassunday_{day:02d}_{val_type}", msg["value"], 1)

    async def handle_outdoortemp(self, msg):
        await fhem.readingsSingleUpdateIfChanged(self.hash,
                                                 "outdoor_temperature",
                                                 msg["value"], 1)

    async def handle_yeartotal(self, msg):
        await fhem.readingsSingleUpdateIfChanged(self.hash, "year_total_kwh",
                                                 msg["value"], 1)

    async def handle_gasusage(self, msg):
        entry = msg["value"][self._gasusage_page_entry]
        yesterday = datetime.date.today() - datetime.timedelta(days=1)
        if entry[
                "d"] == f"{yesterday.day:02d}-{yesterday.month:02d}-{yesterday.year}":
            await fhem.readingsSingleUpdateIfChanged(
                self.hash, "yesterday_consumption_ch", entry["ch"], 1)
            await fhem.readingsSingleUpdateIfChanged(
                self.hash, "yesterday_consumption_hw", entry["hw"], 1)
            await fhem.readingsSingleUpdateIfChanged(self.hash,
                                                     "yesterday_temperature",
                                                     entry["T"] / 10, 1)

    async def handle_gasusagepointer(self, msg):
        if msg["value"] < 2:
            # no entry in history yet
            return

        self._gasusage_page = math.ceil((msg["value"] - 2) / 32)
        self._gasusage_page_entry = (msg["value"] - 2) % 32
        self._nefit_client.get(nefit.URL_REC_GASUSAGE + "?page=" +
                               str(self._gasusage_page))

    async def handle_uistatus(self, msg):
        await fhem.readingsBeginUpdate(self.hash)
        try:
            await fhem.readingsBulkUpdateIfChanged(self.hash, "ars",
                                                   msg["value"]["ARS"])
            await fhem.readingsBulkUpdateIfChanged(self.hash, "boiler_heating",
                                                   msg["value"]["BAI"])
            await fhem.readingsBulkUpdateIfChanged(self.hash, "boiler_block",
                                                   msg["value"]["BBE"])
            await fhem.readingsBulkUpdateIfChanged(self.hash, "boiler_lock",
                                                   msg["value"]["BLE"])
            await fhem.readingsBulkUpdateIfChanged(self.hash,
                                                   "boiler_maintainance",
                                                   msg["value"]["BMR"])
            await fhem.readingsBulkUpdateIfChanged(self.hash, "clock_program",
                                                   msg["value"]["CPM"])
            await fhem.readingsBulkUpdateIfChanged(self.hash,
                                                   "current_switchpoint",
                                                   msg["value"]["CSP"])
            await fhem.readingsBulkUpdateIfChanged(self.hash,
                                                   "current_time_date",
                                                   msg["value"]["CTD"])
            await fhem.readingsBulkUpdateIfChanged(self.hash, "control",
                                                   msg["value"]["CTR"])
            await fhem.readingsBulkUpdateIfChanged(self.hash, "todayAsSunday",
                                                   msg["value"]["DAS"])
            await fhem.readingsBulkUpdateIfChanged(self.hash, "hot_water",
                                                   msg["value"]["DHW"])
            await fhem.readingsBulkUpdateIfChanged(self.hash, "dot",
                                                   msg["value"]["DOT"])
            await fhem.readingsBulkUpdateIfChanged(self.hash, "esi",
                                                   msg["value"]["ESI"])
            await fhem.readingsBulkUpdateIfChanged(self.hash,
                                                   "temp_in_fahrenheit",
                                                   msg["value"]["FAH"])
            await fhem.readingsBulkUpdateIfChanged(self.hash, "fireplace_mode",
                                                   msg["value"]["FPA"])
            await fhem.readingsBulkUpdateIfChanged(
                self.hash, "presence_detection_status_device",
                msg["value"]["HED_DB"])
            await fhem.readingsBulkUpdateIfChanged(
                self.hash, "presence_detection_device",
                msg["value"]["HED_DEV"])
            await fhem.readingsBulkUpdateIfChanged(self.hash,
                                                   "presence_detection",
                                                   msg["value"]["HED_EN"])
            await fhem.readingsBulkUpdateIfChanged(self.hash, "holiday_mode",
                                                   msg["value"]["HMD"])
            await fhem.readingsBulkUpdateIfChanged(self.hash, "inhouse_status",
                                                   msg["value"]["IHS"])
            await fhem.readingsBulkUpdateIfChanged(self.hash, "temperature",
                                                   msg["value"]["IHT"])
            await fhem.readingsBulkUpdateIfChanged(self.hash,
                                                   "manual_mode_temperature",
                                                   msg["value"]["MMT"])
            await fhem.readingsBulkUpdateIfChanged(self.hash, "pmr",
                                                   msg["value"]["PMR"])
            await fhem.readingsBulkUpdateIfChanged(self.hash, "rs",
                                                   msg["value"]["RS"])
            await fhem.readingsBulkUpdateIfChanged(self.hash,
                                                   "tomorrowAsSunday",
                                                   msg["value"]["TAS"])
            await fhem.readingsBulkUpdateIfChanged(
                self.hash, "temperature_override_duration",
                msg["value"]["TOD"])
            await fhem.readingsBulkUpdateIfChanged(self.hash,
                                                   "temperature_override",
                                                   msg["value"]["TOR"])
            await fhem.readingsBulkUpdateIfChanged(self.hash,
                                                   "temperature_override",
                                                   msg["value"]["TOT"])
            await fhem.readingsBulkUpdateIfChanged(self.hash, "desiredTemp",
                                                   msg["value"]["TSP"])
            await fhem.readingsBulkUpdateIfChanged(self.hash, "mode",
                                                   msg["value"]["UMD"])
        except Exception:
            self.logger.exception("Failed to handle uiStatus")

        await fhem.readingsEndUpdate(self.hash, 1)

    async def nefit_connect(self):
        if self.first_run:
            self._nefit_client = NefitCore(
                self._serial_number,
                self._attr_access_key,
                self._attr_password,
                message_callback=self.received_message,
            )

            self._nefit_client.failed_auth_handler = self.failed_auth_handler
            self._nefit_client.no_content_callback = self.no_content_callback
            self._nefit_client.session_end_callback = self.session_end_callback

        await self._nefit_client.connect()
        await self._nefit_client.xmppclient.connected_event.wait()

        await fhem.readingsSingleUpdateIfChanged(self.hash, "state",
                                                 "connected", 1)

        if self.first_run:
            self.create_async_task(self.update_loop())
            self.first_run = False

    async def failed_auth_handler(self, event):
        pass

    async def no_content_callback(self, data):
        pass

    async def session_end_callback(self):
        await fhem.readingsSingleUpdateIfChanged(self.hash, "state",
                                                 "disconnected", 1)
        if not self.module_shutdown:
            await asyncio.sleep(10)
            await self.nefit_connect()

    async def update_loop(self):
        while True:
            try:
                self._nefit_client.get(nefit.URL_RRC_UISTATUS)
                self._nefit_client.get(nefit.URL_REC_YEARTOTAL)
                self._nefit_client.get(nefit.URL_OUTDOOR_TEMP)
                self._nefit_client.get(nefit.URL_SYSTEM_PRESSURE)
                await self.update_dayassunday()

                await self.update_gasusage()
            except Exception:
                self.logger.exception("Failed to update readings")
            await asyncio.sleep(self._attr_interval)

    async def update_dayassunday(self, day=None):
        if day is None:
            for day in range(13):
                self._nefit_client.get(
                    nefit.URL_DAY_ACTIVE.replace("%DAY%", str(day)))
                self._nefit_client.get(
                    nefit.URL_DAY_DATE.replace("%DAY%", str(day)))
                self._nefit_client.get(
                    nefit.URL_DAY_MODE.replace("%DAY%", str(day)))
        else:
            self._nefit_client.get(
                nefit.URL_DAY_ACTIVE.replace("%DAY%", str(day)))
            self._nefit_client.get(
                nefit.URL_DAY_DATE.replace("%DAY%", str(day)))
            self._nefit_client.get(
                nefit.URL_DAY_MODE.replace("%DAY%", str(day)))

    async def update_gasusage(self):
        self._nefit_client.get(nefit.URL_REC_GASUSAGEPOINTER)

    async def Undefine(self, hash):
        await super().Undefine(hash)
        self.module_shutdown = True
예제 #3
0
class NefitThermostat(ClimateDevice):
    """Representation of a NefitThermostat device."""

    def __init__(self, hass, config):
        from aionefit import NefitCore
        """Initialize the thermostat."""
        name = config.get(CONF_NAME)
        serial = config.get(CONF_SERIAL)
        accesskey = config.get(CONF_ACCESSKEY)
        password = config.get(CONF_PASSWORD)

        self.config = config
        self.hass = hass
        self._name = name

        self.error_state = "initializing"
        self._online = False
        self._unit_of_measurement = TEMP_CELSIUS
        self._uistatus = None
        self._attributes = {}
        self._stateattr = {}
        self._data = {}
        self._hvac_modes = [HVAC_MODE_HEAT]
        self._url_events = {
            '/ecus/rrc/uiStatus': asyncio.Event(),
            '/heatingCircuits/hc1/actualSupplyTemperature': asyncio.Event(),
            '/system/sensors/temperatures/outdoor_t1': asyncio.Event(),
            '/system/appliance/systemPressure': asyncio.Event(),
            '/ecus/rrc/recordings/yearTotal': asyncio.Event()
        }

        self._client = NefitCore(serial_number=serial,
                       access_key=accesskey,
                       password=password,
                       message_callback=self.parse_message)
        
        self._client.failed_auth_handler = self.failed_auth_handler
        self._client.no_content_callback = self.no_content_callback

    async def connect(self):
        self._client.connect()
        _LOGGER.debug("Waiting for connected event")        
        try:
            # await self._client.xmppclient.connected_event.wait()
            await asyncio.wait_for(self._client.xmppclient.connected_event.wait(), timeout=10.0)
            _LOGGER.debug("adding stop listener")
            self.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP,
                                            self._shutdown)
        except concurrent.futures._base.TimeoutError:
            _LOGGER.debug("TimeoutError on waiting for connected event")
            self.hass.components.persistent_notification.create( 
                'Timeout while connecting to Bosch cloud. Retrying in the background',
                title='Nefit error',
                notification_id='nefit_logon_error')
            raise PlatformNotReady
        
        if self.error_state == "authentication_failed":
            self.hass.components.persistent_notification.create( 
                'Invalid credentials while connecting to Bosch cloud.',
                title='Nefit error',
                notification_id='nefit_logon_error')
            raise PlatformNotReady

    def no_content_callback(self, data):
        _LOGGER.debug("no_content_callback: %s", data)

    def failed_auth_handler(self, event):
        self.error_state = "authentication_failed"
        self._client.xmppclient.connected_event.set()

    @property
    def supported_features(self):
        """Return the list of supported features.
        """
        return SUPPORT_FLAGS

    @property
    def target_temperature_step(self):
        return 0.5

    def parse_message(self, data):
        """Message received callback function for the XMPP client.
        """
        _LOGGER.debug("parse_message callback called with data %s", data)
        if not 'id' in data:
            _LOGGER.error("Unknown response received: %s", data)
            return

        if data['id'] == '/ecus/rrc/uiStatus':
            self._uistatus = data['value']
            self._data['temp_setpoint'] = float(data['value']['TSP'])
            self._data['inhouse_temperature'] = float(data['value']['IHT'])
            self._data['user_mode'] = data['value']['UMD']
            self._stateattr['boiler_indicator'] = data['value']['BAI']
            self._stateattr['current_time'] = data['value']['CTD']        
        elif data['id'] == '/heatingCircuits/hc1/actualSupplyTemperature':
            self._stateattr['supply_temperature'] = data['value']
        elif data['id'] == '/system/sensors/temperatures/outdoor_t1':
            self._stateattr['outdoor_temperature'] = data['value']
        elif data['id'] == '/system/appliance/systemPressure':
            self._stateattr['system_pressure'] = data['value']            
        elif data['id'] == '/ecus/rrc/recordings/yearTotal':
            self._stateattr['year_total'] = data['value']

        if data['id'] in self._url_events:
            self._url_events[data['id']].set()
            
    async def async_update(self):
        """Get latest data
        """
        _LOGGER.debug("async_update called")
        tasks = []
        for url in self._url_events:
            event = self._url_events[url]
            event.clear()
            self._client.get(url)
            tasks.append(asyncio.wait_for(event.wait(), timeout=10))
        
        await asyncio.gather(*tasks)
    
        _LOGGER.debug("async_update finished")

    @property
    def name(self):
        """Return the name of the ClimateDevice.
        """
        return self._name

    @property
    def temperature_unit(self):
        """Return the unit of measurement.
        """
        return self._unit_of_measurement

    @property
    def current_temperature(self):
        """Return the current temperature.
        """
        return self._data.get('inhouse_temperature')

    @property
    def target_temperature(self):
        return self._data.get('temp_setpoint')

    @property
    def hvac_modes (self):
        """List of available modes."""
        return self._hvac_modes

    @property
    def hvac_mode(self):
        return HVAC_MODE_HEAT
    
    @property
    def hvac_action(self):
        """Return the current running hvac operation if supported."""
        if self._stateattr.get('boiler_indicator') == 'CH': #HW (hot water) is not for climate
            return CURRENT_HVAC_HEAT
        
        return CURRENT_HVAC_IDLE

    @property
    def preset_modes(self):
        """Return available preset modes."""
        return [
            OPERATION_CLOCK
        ]


    @property
    def preset_mode(self):
        """Return the current preset mode."""
        if self._data.get('user_mode') == 'manual':
            return None
        elif self._data.get('user_mode') == 'clock':
            return OPERATION_CLOCK
        else:
            return None

    @property
    def device_state_attributes(self):
        """Return the device specific state attributes."""

        return self._stateattr

    @property
    def min_temp(self):
        """Return the minimum temperature."""
        return self.config.get(CONF_MIN_TEMP)

    @property
    def max_temp(self):
        """Return the maximum temperature."""
        return self.config.get(CONF_MAX_TEMP)

    async def async_set_preset_mode(self, preset_mode):
        """Set new target operation mode."""
        _LOGGER.debug("set_preset_mode called mode={}.".format(preset_mode))
        if preset_mode == OPERATION_CLOCK:
            new_mode = "clock"
        else:
            new_mode = "manual"

        self._client.set_usermode(new_mode)
        await asyncio.wait_for(self._client.xmppclient.message_event.wait(), timeout=10.0)
        self._client.xmppclient.message_event.clear()
        self._data['user_mode'] = new_mode

    async def async_set_temperature(self, **kwargs):
        """Set new target temperature."""
        temperature = kwargs.get(ATTR_TEMPERATURE)
        self._data['target_temperature'] = temperature
        _LOGGER.debug("set_temperature called (temperature={}).".format(temperature))
        self._client.set_temperature(temperature)
        await asyncio.wait_for(self._client.xmppclient.message_event.wait(), timeout=10.0)
        self._client.xmppclient.message_event.clear()
        self._data['target_temperature'] = temperature
        

    def _shutdown(self, event):
        _LOGGER.debug("shutdown")
        self._client.disconnect()
예제 #4
0
class NefitEasy:
    def __init__(self, hass, credentials):
        from aionefit import NefitCore
        _LOGGER.debug("Initialize Nefit class")

        self.data = {}  #stores device states and values
        self.keys = {}  #unique name for entity
        self.events = {}
        self.uiStatusVars = {}  #variables to monitor for sensors
        self.hass = hass
        self.connected_state = STATE_INIT
        self.expected_end = False
        self.is_connecting = False
        self.serial = credentials.get(CONF_SERIAL)

        self.nefit = NefitCore(serial_number=credentials.get(CONF_SERIAL),
                               access_key=credentials.get(CONF_ACCESSKEY),
                               password=credentials.get(CONF_PASSWORD),
                               message_callback=self.parse_message)

        self.nefit.failed_auth_handler = self.failed_auth_handler
        self.nefit.no_content_callback = self.no_content_callback
        self.nefit.session_end_callback = self.session_end_callback

        #self.hass.services.register(DOMAIN, "test_disconnect", self.nefit.disconnect)

    async def connect(self):
        _LOGGER.debug("Starting connecting..")
        if not self.is_connecting:
            self.is_connecting = True
            retries_connection = 0

            while self.connected_state != STATE_CONNECTED and retries_connection < 3:
                await self.nefit.connect()
                _LOGGER.debug("Waiting for connected event")
                try:
                    await asyncio.wait_for(
                        self.nefit.xmppclient.connected_event.wait(),
                        timeout=29.0)
                    self.connected_state = STATE_CONNECTED
                    _LOGGER.debug("adding stop listener")
                    self.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP,
                                                    self.shutdown)
                except concurrent.futures._base.TimeoutError:
                    _LOGGER.debug(
                        "TimeoutError on waiting for connected event (connection retries=%d)",
                        retries_connection)
                    retries_connection = retries_connection + 1
                except:
                    _LOGGER.debug("Unknown error")

            #test password for decrypting messages if connected
            if self.connected_state == STATE_CONNECTED:
                _LOGGER.info("Testing connection (connect retries=%d)",
                             retries_connection)
                retries_validation = 0
                while self.connected_state != STATE_CONNECTION_VERIFIED and retries_validation < 3:
                    try:
                        self.nefit.get('/gateway/brandID')
                        await asyncio.wait_for(
                            self.nefit.xmppclient.message_event.wait(),
                            timeout=29.0)
                        self.nefit.xmppclient.message_event.clear()

                        if self.connected_state == STATE_ERROR_AUTH:
                            self.is_connecting = False
                            return

                        self.connected_state = STATE_CONNECTION_VERIFIED
                        _LOGGER.info(
                            "Connected %s with %d retries and %d test retries.",
                            self.serial, retries_connection,
                            retries_validation)
                    except concurrent.futures._base.TimeoutError:
                        _LOGGER.error(
                            "Did not get a response in time for testing connection (validation retries=%d).",
                            retries_validation)
                        retries_validation = retries_validation + 1
                    except:
                        _LOGGER.error(
                            "No connection while testing connection.")
                        break

            if self.connected_state != STATE_CONNECTION_VERIFIED:
                self.hass.components.persistent_notification.create(
                    'Did not succeed in connecting {} to Bosch cloud after retrying 3 times. Retry in 30 seconds.'
                    .format(self.serial),
                    title='Nefit connect error',
                    notification_id='nefit_connect_error')
                self.is_connecting = False

                #wait 30 seconds to retry
                await asyncio.sleep(30)
                await self.connect()

            self.is_connecting = False
        else:
            _LOGGER.debug("Connection procedure was already running..")

    async def shutdown(self, event):
        _LOGGER.debug("Shutdown connection to Bosch cloud")
        self.expected_end = True
        await self.nefit.disconnect()

    async def no_content_callback(self, data):
        _LOGGER.debug("no_content_callback: %s", data)

    async def failed_auth_handler(self, event):
        self.connected_state = STATE_ERROR_AUTH
        self.nefit.xmppclient.connected_event.set()

        #disconnect, since nothing will work from now.
        await self.shutdown('auth_failed')

        if event == 'auth_error_password':
            self.hass.components.persistent_notification.create(
                'Invalid password for connecting {} to Bosch cloud.'.format(
                    self.serial),
                title='Nefit password error',
                notification_id='nefit_password_error')
        else:
            self.hass.components.persistent_notification.create(
                'Invalid credentials (serial or accesskey) for connecting {} to Bosch cloud.'
                .format(self.serial),
                title='Nefit authentication error',
                notification_id='nefit_logon_error')

    async def session_end_callback(self):
        """If connection is closed unexpectedly, try to reconnect"""
        if not self.expected_end:
            self.hass.components.persistent_notification.create(
                'Unexpected disconnect of {} with Bosch server. Try to reconnect..'
                .format(self.serial),
                title='Nefit disconnect',
                notification_id='nefit_disconnect')

            _LOGGER.info("Starting reconnect procedure.")
            # Reset values
            self.connected_state = STATE_INIT
            self.expected_end = False

            # Retry connection
            await self.connect()

    async def parse_message(self, data):
        """Message received callback function for the XMPP client."""
        _LOGGER.debug("parse_message data %s", data)
        if not 'id' in data:
            _LOGGER.error("Unknown response received: %s", data)
            return

        if data['id'] in self.keys:
            key = self.keys[data['id']]
            _LOGGER.debug("Got update for %s.", key)

            if data['id'] == '/ecus/rrc/uiStatus' and self.connected_state == STATE_CONNECTION_VERIFIED:
                self.data['temp_setpoint'] = float(
                    data['value']['TSP'])  #for climate
                self.data['inhouse_temperature'] = float(
                    data['value']['IHT'])  #for climate
                self.data['user_mode'] = data['value']['UMD']  #for climate
                self.data['boiler_indicator'] = data['value'][
                    'BAI']  #for climate
                self.data['last_update'] = data['value']['CTD']

                # Update all sensors/switches when there is new data form uiStatus
                for uikey in self.uiStatusVars:
                    self.updateDeviceValue(
                        uikey, data['value'].get(self.uiStatusVars[uikey]))

            self.updateDeviceValue(key, data['value'])

            # Mark event as finished if it was part of an update action
            if key in self.events:
                self.events[key].set()

    def updateDeviceValue(self, key, value):
        """Store new device value and send to dispatcher to be picked up by device"""
        self.data[key] = value

        #send update signal to dispatcher to pick up new state
        signal = DISPATCHER_ON_DEVICE_UPDATE.format(key=key)
        async_dispatcher_send(self.hass, signal)

    async def get_value(self, key, url):
        isNewKey = not url in self.keys
        if isNewKey:
            self.events[key] = asyncio.Event()
            self.keys[url] = key
        event = self.events[key]
        event.clear()  #clear old event
        self.nefit.get(url)
        await asyncio.wait_for(event.wait(), timeout=9)
        if isNewKey:
            del self.events[key]
            del self.keys[url]
        return self.data[key]
예제 #5
0
class NefitEasy:
    """Supporting class for nefit easy."""
    def __init__(self, hass, credentials):
        """Initialize nefit easy component."""
        _LOGGER.debug("Initialize Nefit class")

        self.data = {}  # stores device states and values
        self.keys = {}  # unique name for entity
        self.events = {}
        self.ui_status_vars = {}  # variables to monitor for sensors
        self.hass = hass
        self.connected_state = STATE_INIT
        self.expected_end = False
        self.is_connecting = False
        self.serial = credentials.get(CONF_SERIAL)

        self.nefit = NefitCore(
            serial_number=credentials.get(CONF_SERIAL),
            access_key=credentials.get(CONF_ACCESSKEY),
            password=credentials.get(CONF_PASSWORD),
            message_callback=self.parse_message,
        )

        self.nefit.failed_auth_handler = self.failed_auth_handler
        self.nefit.no_content_callback = self.no_content_callback
        self.nefit.session_end_callback = self.session_end_callback

    async def connect(self):
        """Connect to nefit easy."""
        _LOGGER.debug("Starting connecting..")
        if not self.is_connecting:
            self.is_connecting = True
            retries_connection = 0

            while self.connected_state != STATE_CONNECTED and retries_connection < 3:
                await self.nefit.connect()
                _LOGGER.debug("Waiting for connected event")
                try:
                    await asyncio.wait_for(
                        self.nefit.xmppclient.connected_event.wait(),
                        timeout=29.0)
                    self.connected_state = STATE_CONNECTED
                    _LOGGER.debug("adding stop listener")
                    self.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP,
                                                    self.shutdown)
                except asyncio.TimeoutError:
                    _LOGGER.debug(
                        "TimeoutError on waiting for connected event (connection retries=%d)",
                        retries_connection,
                    )
                    retries_connection = retries_connection + 1
                except:  # noqa: E722 pylint: disable=bare-except
                    _LOGGER.debug("Unknown error")

            # test password for decrypting messages if connected
            if self.connected_state == STATE_CONNECTED:
                _LOGGER.info("Testing connection (connect retries=%d)",
                             retries_connection)
                retries_validation = 0
                while (self.connected_state != STATE_CONNECTION_VERIFIED
                       and retries_validation < 3):
                    try:
                        self.nefit.get("/gateway/brandID")
                        await asyncio.wait_for(
                            self.nefit.xmppclient.message_event.wait(),
                            timeout=29.0)
                        self.nefit.xmppclient.message_event.clear()

                        if self.connected_state == STATE_ERROR_AUTH:
                            self.is_connecting = False
                            return

                        self.connected_state = STATE_CONNECTION_VERIFIED
                        _LOGGER.info(
                            "Connected %s with %d retries and %d test retries.",
                            self.serial,
                            retries_connection,
                            retries_validation,
                        )
                    except asyncio.TimeoutError:
                        _LOGGER.error(
                            "Did not get a response in time for testing connection (validation retries=%d).",
                            retries_validation,
                        )
                        retries_validation = retries_validation + 1
                    except:  # noqa: E722 pylint: disable=bare-except
                        _LOGGER.error(
                            "No connection while testing connection.")
                        break

            if self.connected_state != STATE_CONNECTION_VERIFIED:
                self.hass.components.persistent_notification.create(
                    f"Did not succeed in connecting {self.serial} to Bosch cloud after retrying 3 times. Retry in 30 seconds.",
                    title="Nefit connect error",
                    notification_id="nefit_connect_error",
                )
                self.is_connecting = False

                # wait 30 seconds to retry
                await asyncio.sleep(30)
                await self.connect()

            self.is_connecting = False
        else:
            _LOGGER.debug("Connection procedure was already running..")

    async def shutdown(self, event):
        """Shutdown."""
        _LOGGER.debug("Shutdown connection to Bosch cloud")
        self.expected_end = True
        await self.nefit.disconnect()

    async def no_content_callback(self, data):
        """Log no content."""
        _LOGGER.debug("no_content_callback: %s", data)

    async def failed_auth_handler(self, event):
        """Handle failed auth."""
        self.connected_state = STATE_ERROR_AUTH
        self.nefit.xmppclient.connected_event.set()

        # disconnect, since nothing will work from now.
        await self.shutdown("auth_failed")

        if event == "auth_error_password":
            self.hass.components.persistent_notification.create(
                f"Invalid password for connecting {self.serial} to Bosch cloud.",
                title="Nefit password error",
                notification_id="nefit_password_error",
            )
        else:
            self.hass.components.persistent_notification.create(
                f"Invalid credentials (serial or accesskey) for connecting {self.serial} to Bosch cloud.",
                title="Nefit authentication error",
                notification_id="nefit_logon_error",
            )

    async def session_end_callback(self):
        """If connection is closed unexpectedly, try to reconnect."""
        if not self.expected_end:
            self.hass.components.persistent_notification.create(
                f"Unexpected disconnect of {self.serial} with Bosch server. Try to reconnect..",
                title="Nefit disconnect",
                notification_id="nefit_disconnect",
            )

            _LOGGER.info("Starting reconnect procedure.")
            # Reset values
            self.connected_state = STATE_INIT
            self.expected_end = False

            # Retry connection
            await self.connect()

    async def parse_message(self, data):
        """Message received callback function for the XMPP client."""
        _LOGGER.debug("parse_message data %s", data)
        if "id" not in data:
            _LOGGER.error("Unknown response received: %s", data)
            return

        if data["id"] in self.keys:
            key = self.keys[data["id"]]
            _LOGGER.debug("Got update for %s.", key)

            if (data["id"] == "/ecus/rrc/uiStatus"
                    and self.connected_state == STATE_CONNECTION_VERIFIED):
                self.data["temp_setpoint"] = float(
                    data["value"]["TSP"])  # for climate
                self.data["inhouse_temperature"] = float(
                    data["value"]["IHT"])  # for climate
                self.data["user_mode"] = data["value"]["UMD"]  # for climate
                self.data["boiler_indicator"] = data["value"][
                    "BAI"]  # for climate
                self.data["last_update"] = data["value"]["CTD"]

                # Update all sensors/switches when there is new data form uiStatus
                for uikey in self.ui_status_vars:
                    self.update_device_value(
                        uikey, data["value"].get(self.ui_status_vars[uikey]))

            self.update_device_value(key, data["value"])

            # Mark event as finished if it was part of an update action
            if key in self.events:
                self.events[key].set()

    def update_device_value(self, key, value):
        """Store new device value and send to dispatcher to be picked up by device."""
        self.data[key] = value

        # send update signal to dispatcher to pick up new state
        signal = DISPATCHER_ON_DEVICE_UPDATE.format(key=key)
        async_dispatcher_send(self.hass, signal)

    async def get_value(self, key, url):
        """Get value."""
        is_new_key = url not in self.keys
        if is_new_key:
            self.events[key] = asyncio.Event()
            self.keys[url] = key
        event = self.events[key]
        event.clear()  # clear old event
        self.nefit.get(url)
        await asyncio.wait_for(event.wait(), timeout=9)
        if is_new_key:
            del self.events[key]
            del self.keys[url]
        return self.data[key]
예제 #6
0
class NefitEasy(DataUpdateCoordinator):
    """Supporting class for nefit easy."""
    def __init__(self, hass, config):
        """Initialize nefit easy component."""
        _LOGGER.debug("Initialize Nefit class")

        self._data = {}  # stores device states and values
        self._event = asyncio.Event()
        self._lock = asyncio.Lock()
        self.hass = hass
        self.connected_state = STATE_INIT
        self.expected_end = False
        self.is_connecting = False
        self.serial = config[CONF_SERIAL]
        self._config = config
        self._request = None

        self.nefit = NefitCore(
            serial_number=config[CONF_SERIAL],
            access_key=config[CONF_ACCESSKEY],
            password=config[CONF_PASSWORD],
            message_callback=self.parse_message,
        )

        self.nefit.failed_auth_handler = self.failed_auth_handler
        self.nefit.no_content_callback = self.no_content_callback
        self.nefit.session_end_callback = self.session_end_callback

        self._urls = {}
        self._status_keys = {}

        update_interval = timedelta(seconds=60)

        super().__init__(
            hass,
            _LOGGER,
            name=DOMAIN,
            update_interval=update_interval,
        )

    async def add_key(self, key, typeconf):
        """Add key to list of endpoints."""
        async with self._lock:
            if url in typeconf:
                self._urls[typeconf[url]] = {
                    "key": key,
                    short: typeconf.get(short)
                }
            elif short in typeconf:
                self._status_keys[typeconf[short]] = key

    async def connect(self):
        """Connect to nefit easy."""
        _LOGGER.debug("Starting connecting..")
        if not self.is_connecting:
            self.is_connecting = True
            retries_connection = 0

            while self.connected_state != STATE_CONNECTED and retries_connection < 3:
                await self.nefit.connect()
                _LOGGER.debug("Waiting for connected event")
                try:
                    await asyncio.wait_for(
                        self.nefit.xmppclient.connected_event.wait(),
                        timeout=29.0)
                    self.connected_state = STATE_CONNECTED
                    _LOGGER.debug("adding stop listener")
                    self.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP,
                                                    self.shutdown)
                except asyncio.TimeoutError:
                    _LOGGER.debug(
                        "TimeoutError on waiting for connected event (connection retries=%d)",
                        retries_connection,
                    )
                    retries_connection = retries_connection + 1
                except:  # noqa: E722 pylint: disable=bare-except
                    _LOGGER.debug("Unknown error")

            # test password for decrypting messages if connected
            if self.connected_state == STATE_CONNECTED:
                _LOGGER.info("Testing connection (connect retries=%d)",
                             retries_connection)
                retries_validation = 0
                while (self.connected_state != STATE_CONNECTION_VERIFIED
                       and retries_validation < 3):
                    try:
                        self.nefit.get("/gateway/brandID")
                        await asyncio.wait_for(
                            self.nefit.xmppclient.message_event.wait(),
                            timeout=29.0)
                        self.nefit.xmppclient.message_event.clear()

                        if self.connected_state == STATE_ERROR_AUTH:
                            self.is_connecting = False
                            return

                        self.connected_state = STATE_CONNECTION_VERIFIED
                        _LOGGER.info(
                            "Connected %s with %d retries and %d test retries.",
                            self.serial,
                            retries_connection,
                            retries_validation,
                        )
                    except asyncio.TimeoutError:
                        _LOGGER.error(
                            "Did not get a response in time for testing connection (validation retries=%d).",
                            retries_validation,
                        )
                        retries_validation = retries_validation + 1
                    except:  # noqa: E722 pylint: disable=bare-except
                        _LOGGER.error(
                            "No connection while testing connection.")
                        break

            if self.connected_state != STATE_CONNECTION_VERIFIED:
                self.hass.components.persistent_notification.create(
                    f"Did not succeed in connecting {self.serial} to Bosch cloud after retrying 3 times. Retry in 30 seconds.",
                    title="Nefit connect error",
                    notification_id="nefit_connect_error",
                )
                self.is_connecting = False

                # wait 30 seconds to retry
                await asyncio.sleep(30)
                await self.connect()

            self.is_connecting = False
        else:
            _LOGGER.debug("Connection procedure was already running..")

    async def shutdown(self, event):
        """Shutdown."""
        _LOGGER.debug("Shutdown connection to Bosch cloud")
        self.expected_end = True
        await self.nefit.disconnect()

    async def no_content_callback(self, data):
        """Log no content."""
        _LOGGER.debug("no_content_callback: %s", data)

    async def failed_auth_handler(self, event):
        """Handle failed auth."""
        self.connected_state = STATE_ERROR_AUTH
        self.nefit.xmppclient.connected_event.set()

        # disconnect, since nothing will work from now.
        await self.shutdown("auth_failed")

        if event == "auth_error_password":
            self.hass.components.persistent_notification.create(
                f"Invalid password for connecting {self.serial} to Bosch cloud.",
                title="Nefit password error",
                notification_id="nefit_password_error",
            )
        else:
            self.hass.components.persistent_notification.create(
                f"Invalid credentials (serial or accesskey) for connecting {self.serial} to Bosch cloud.",
                title="Nefit authentication error",
                notification_id="nefit_logon_error",
            )

    async def session_end_callback(self):
        """If connection is closed unexpectedly, try to reconnect."""
        if not self.expected_end:
            self.hass.components.persistent_notification.create(
                f"Unexpected disconnect of {self.serial} with Bosch server. Try to reconnect..",
                title="Nefit disconnect",
                notification_id="nefit_disconnect",
            )

            _LOGGER.info("Starting reconnect procedure.")
            # Reset values
            self.connected_state = STATE_INIT
            self.expected_end = False

            # Retry connection
            await self.connect()

    async def parse_message(self, data):
        """Message received callback function for the XMPP client."""
        if (data["id"] == "/ecus/rrc/uiStatus"
                and self.connected_state == STATE_CONNECTION_VERIFIED):
            self._data["temp_setpoint"] = float(
                data["value"]["TSP"])  # for climate
            self._data["inhouse_temperature"] = float(
                data["value"]["IHT"])  # for climate
            self._data["user_mode"] = data["value"]["UMD"]  # for climate
            self._data["boiler_indicator"] = data["value"][
                "BAI"]  # for climate
            self._data["last_update"] = data["value"]["CTD"]

            for val, key in self._status_keys.items():
                self._data[key] = data["value"].get(val)
        elif (data["id"].startswith(
                "/ecus/rrc/homeentrancedetection/userprofile")
              and self.connected_state == STATE_CONNECTION_VERIFIED):
            m = re.search(r"(?<=userprofile)\w+", data["id"])
            id = m.group(0)

            val = data["id"].rsplit("/", 1)[-1]

            self._data[f"presence{id}_{val}"] = data["value"]
        elif (data["id"] in self._urls
              and self.connected_state == STATE_CONNECTION_VERIFIED):
            self._data[self._urls[data["id"]]["key"]] = data["value"]
        else:
            return

        if self._request == data["id"]:
            self._event.set()
        else:
            self.async_set_updated_data(self._data)

    async def _async_update_data(self):
        """Update data via library."""
        if self.connected_state != STATE_CONNECTION_VERIFIED:
            raise UpdateFailed("Nefit easy not connected!")

        async with self._lock:
            url = "/ecus/rrc/uiStatus"
            await self._async_get_url(url)

            for url in self._urls:
                await self._async_get_url(url)

        return self._data

    async def async_init_presence(self, endpoint, index):
        """Init presence detection."""
        async with self._lock:
            url = f"{endpoint}/userprofile{index}/active"
            await self._async_get_url(url)

            if self._data[f"presence{index}_active"] == "on":
                url = f"{endpoint}/userprofile{index}/name"
                await self._async_get_url(url)

                return self._data.get(f"presence{index}_name")

            return None

    async def update_ui_status_later(self, delay):
        """Force update of uiStatus after delay."""
        self.hass.loop.call_later(delay, self.nefit.get, "/ecus/rrc/uiStatus")

    async def _async_get_url(self, url):
        self._event.clear()
        self._request = url
        self.nefit.get(url)
        await asyncio.wait_for(self._event.wait(), timeout=9)
        self._request = None