def __init__(self, connection=None):
        if connection is None:
            connection = Connection()
        super().__init__(connection)

        self.weather = None
        self.location = None
        self.connected = None
        self.currentAPVersion = None
        self.availableAPVersion = None
        self.timeZoneId = None
        self.pinAssigned = None
        self.dutyCycle = None
        self.updateState = None
        self.powerMeterUnitPrice = None
        self.powerMeterCurrency = None
        self.deviceUpdateStrategy = None
        self.lastReadyForUpdateTimestamp = None
        self.apExchangeClientId = None
        self.apExchangeState = None
        self.id = None
        self.carrierSense = None

        self.__webSocket = None
        self.__webSocketThread = None
        self.onEvent = EventHook()

        self.devices = []
        self.clients = []
        self.groups = []
        self.rules = []
Beispiel #2
0
    def __init__(self, connection=None):
        if connection is None:
            connection = Connection()
        super().__init__(connection)

        # List with create handlers.
        self._on_create = []

        self.apExchangeClientId = None
        self.apExchangeState = ApExchangeState.NONE
        self.availableAPVersion = None
        self.carrierSense = None
        #:bool:displays if the access point is connected to the hmip cloud or
        # not
        self.connected = None
        #:str:the current version of the access point
        self.currentAPVersion = None
        self.deviceUpdateStrategy = DeviceUpdateStrategy.MANUALLY
        self.dutyCycle = None
        #:str:the SGTIN of the access point
        self.id = None
        self.lastReadyForUpdateTimestamp = None
        #:Location:the location of the AP
        self.location = None
        #:bool:determines if a pin is set on this access point
        self.pinAssigned = None
        self.powerMeterCurrency = None
        self.powerMeterUnitPrice = None
        self.timeZoneId = None
        self.updateState = HomeUpdateState.UP_TO_DATE
        #:Weather:the current weather
        self.weather = None

        self.__webSocket = None
        self.__webSocketThread = None
        self.onEvent = EventHook()
        self.onWsError = EventHook()
        #:bool:switch to enable/disable automatic reconnection of the websocket (default=True)
        self.websocket_reconnect_on_error = True

        #:List[Device]: a collection of all devices in home
        self.devices = []
        #:List[Client]: a collection of all clients in home
        self.clients = []
        #:List[Group]: a collection of all groups in the home
        self.groups = []
        #:List[Rule]: a collection of all rules in the home
        self.rules = []
        #: a collection of all functionalHomes in the home
        self.functionalHomes = []
        #:Map: a map of all access points and their updateStates
        self.accessPointUpdateStates = {}
class Home(HomeMaticIPObject.HomeMaticIPObject):
    """this class represents the 'Home' of the homematic ip"""
    _typeClassMap = TYPE_CLASS_MAP
    _typeGroupMap = TYPE_GROUP_MAP
    _typeSecurityEventMap = TYPE_SECURITY_EVENT_MAP
    _typeRuleMap = TYPE_RULE_MAP

    def __init__(self, connection=None):
        if connection is None:
            connection = Connection()
        super().__init__(connection)

        self.weather = None
        self.location = None
        self.connected = None
        self.currentAPVersion = None
        self.availableAPVersion = None
        self.timeZoneId = None
        self.pinAssigned = None
        self.dutyCycle = None
        self.updateState = None
        self.powerMeterUnitPrice = None
        self.powerMeterCurrency = None
        self.deviceUpdateStrategy = None
        self.lastReadyForUpdateTimestamp = None
        self.apExchangeClientId = None
        self.apExchangeState = None
        self.id = None
        self.carrierSense = None

        self.__webSocket = None
        self.__webSocketThread = None
        self.onEvent = EventHook()

        self.devices = []
        self.clients = []
        self.groups = []
        self.rules = []

    def init(self, access_point_id, lookup=True):
        self._connection.init(access_point_id, lookup)

    def set_auth_token(self, auth_token):
        self._connection.set_auth_token(auth_token)

    def from_json(self, js_home):
        super().from_json(js_home)

        self.weather = Weather(self._connection)
        self.weather.from_json(js_home["weather"])
        self.location = Location(self._connection)
        self.location.from_json(js_home["location"])

        self.connected = js_home["connected"]
        self.currentAPVersion = js_home["currentAPVersion"]
        self.availableAPVersion = js_home["availableAPVersion"]
        self.timeZoneId = js_home["timeZoneId"]
        self.pinAssigned = js_home["pinAssigned"]
        self.dutyCycle = js_home["dutyCycle"]
        self.updateState = js_home["updateState"]
        self.powerMeterUnitPrice = js_home["powerMeterUnitPrice"]
        self.powerMeterCurrency = js_home["powerMeterCurrency"]
        self.deviceUpdateStrategy = js_home["deviceUpdateStrategy"]
        self.lastReadyForUpdateTimestamp = js_home["lastReadyForUpdateTimestamp"]
        self.apExchangeClientId = js_home["apExchangeClientId"]
        self.apExchangeState = js_home["apExchangeState"]
        self.id = js_home["id"]
        self.carrierSense = js_home["carrierSense"]

        
        self._get_rules(js_home)

    def download_configuration(self):
        return self._restCall('home/getCurrentState',
                              json.dumps(self._connection.clientCharacteristics))

    def get_current_state(self):
        json_state = self.download_configuration()

        if "errorCode" in json_state:
            LOGGER.error("Could not get the current configuration. Error: %s",
                         json_state["errorCode"])
            return False

        js_home = json_state["home"]

        self.from_json(js_home)

        self._get_devices(json_state)
        self._get_clients(json_state)
        self._get_groups(json_state)

        return True

    def _get_devices(self, json_state):
        self.devices = [x for x in self.devices if x.id in json_state["devices"].keys()]
        for id_, raw in json_state["devices"].items():
            _device = self.search_device_by_id(id_)
            if _device:
                _device.from_json(raw)
            else:
                self.devices.append(self._parse_device(raw))

    def _parse_device(self, json_state):
        deviceType = json_state["type"]
        if deviceType in self._typeClassMap:
            d = self._typeClassMap[deviceType](self._connection)
            d.from_json(json_state)
            return d
        else:
            d = self._typeClassMap[DEVICE](self._connection)
            d.from_json(json_state)
            LOGGER.warning("There is no class for %s yet", deviceType)
            return d

    def _get_rules(self, json_state):
        self.rules = [x for x in self.rules if x.id in json_state["ruleMetaDatas"].keys()]
        for id_, raw in json_state["ruleMetaDatas"].items():
            _rule = self.search_rule_by_id(id_)
            if _rule:
                _rule.from_json(raw)
            else:
                self.rules.append(self._parse_rule(raw))

    def _parse_rule(self, json_state):
        ruleType = json_state["type"]
        if ruleType in self._typeRuleMap:
            r = self._typeRuleMap[ruleType](self._connection)
            r.from_json(json_state)
        else:
            r = Rule(self._connection)
            r.from_json(json_state)
            LOGGER.warning("There is no class for %s yet", ruleType)
        return r

    def _get_clients(self, json_state):
        self.clients = [x for x in self.clients if x.id in json_state["clients"].keys()]
        for id_, raw in json_state["clients"].items():
            _client = self.search_client_by_id(id_)
            if _client:
                _client.from_json(raw)
            else:
                c = Client(self._connection)
                c.from_json(raw)
                self.clients.append(c)

    def _parse_group(self, json_state):
        groupType = json_state["type"]
        if groupType in self._typeGroupMap:
            g = self._typeGroupMap[groupType](self._connection)
            g.from_json(json_state, self.devices)
        elif groupType == "META":
            g = MetaGroup(self._connection)
            g.from_json(json_state, self.devices, self.groups)
        else:
            g = Group(self._connection)
            g.from_json(json_state, self.devices)
            LOGGER.warning("There is no class for %s yet", groupType)
        return g

    def _get_groups(self, json_state):
        self.groups = [x for x in self.groups if x.id in json_state["groups"].keys()]
        metaGroups = []
        for id_, raw in json_state["groups"].items():
            _group = self.search_group_by_id(id_)
            if _group:
                if isinstance(_group, MetaGroup):
                    _group.from_json(raw, self.devices, self.groups)
                else:
                    _group.from_json(raw, self.devices)
            else:
                group_type = raw["type"]
                if group_type == "META":
                    metaGroups.append(raw)
                else:
                    self.groups.append(self._parse_group(raw))
        for mg in metaGroups:
            self.groups.append(self._parse_group(mg))

    def search_device_by_id(self, deviceID) -> Device:
        """ searches a device by given id
        :param deviceID the device to search for
        :return the Device object or None if it couldn't find a device
        """
        for d in self.devices:
            if d.id == deviceID:
                return d
        return None

    def search_group_by_id(self, groupID) -> Group:
        """ searches a group by given id
        :param groupID the device to search for
        :return the group object or None if it couldn't find a group
        """
        for g in self.groups:
            if g.id == groupID:
                return g
        return None

    def search_client_by_id(self, clientID) -> Client:
        """ searches a client by given id
        :param clientID the device to search for
        :return the client object or None if it couldn't find a client
        """
        for c in self.clients:
            if c.id == clientID:
                return c
        return None

    def search_rule_by_id(self, ruleID) -> Rule:
        """ searches a rule by given id
        :param ruleID the device to search for
        :return the rule object or None if it couldn't find a rule
        """
        for r in self.rules:
            if r.id == ruleID:
                return r
        return None

    def get_security_zones_activation(self):
        internal_active = False
        external_active = False
        for g in self.groups:
            if isinstance(g, SecurityZoneGroup):
                if g.label == 'EXTERNAL':
                    external_active = g.active
                elif g.label == 'INTERNAL':
                    internal_active = g.active
        return internal_active, external_active

    def set_security_zones_activation(self, internal=True, external=True):
        data = {"zonesActivation": {"EXTERNAL": external, "INTERNAL": internal}}
        return self._restCall("home/security/setZonesActivation", json.dumps(data))

    def set_location(self, city, latitude, longitude):
        data = {"city": city, "latitude": latitude, "longitude": longitude}
        return self._restCall("home/setLocation", json.dumps(data))

    def set_intrusion_alert_through_smoke_detectors(self, activate=True):
        data = {"intrusionAlertThroughSmokeDetectors": activate}
        return self._restCall("home/security/setIntrusionAlertThroughSmokeDetectors",
                              json.dumps(data))

    def activate_absence_with_period(self, endtime):
        data = {"endTime": endtime.strftime("%Y_%m_%d %H:%M")}
        return self._restCall("home/heating/activateAbsenceWithPeriod", json.dumps(data))

    def activate_absence_with_duration(self, duration):
        data = {"duration": duration}
        return self._restCall("home/heating/activateAbsenceWithDuration", json.dumps(data))

    def deactivate_absence(self):
        return self._restCall("home/heating/deactivateAbsence")

    def activate_vacation(self, endtime, temperature):
        data = {"endtime": endtime.strftime("%Y_%m_%d %H:%M"),
                "temperature": temperature}
        return self._restCall("home/heating/activateVacation",
                              json.dumps(data))

    def deactivate_vacation(self):
        return self._restCall("home/heating/deactivateVacation")

    def set_pin(self, newPin, oldPin=None):
        if newPin == None:
            newPin = ""
        data = {"pin": newPin}
        if oldPin:
            self._connection.headers["PIN"] = str(oldPin)
        result = self._restCall('home/setPin', body=json.dumps(data))
        if oldPin:
            del self._connection.headers["PIN"]
        return result

    def set_zone_activation_delay(self, delay):
        data = {"zoneActivationDelay": delay}
        return self._restCall("home/security/setZoneActivationDelay",
                              body=json.dumps(data))

    def get_security_journal(self):
        journal = self._restCall("home/security/getSecurityJournal")
        if "errorCode" in journal:
            LOGGER.error(
                "Could not get the security journal. Error: %s", journal["errorCode"])
            return None
        ret = []
        for entry in journal["entries"]:
            eventType = entry["eventType"]
            if eventType in self._typeSecurityEventMap:
                j = self._typeSecurityEventMap[eventType](self._connection)
                j.from_json(entry)
                ret.append(j)
            else:
                j = SecurityEvent(self._connection)
                j.from_json(entry)
                ret.append(j)
                LOGGER.warning("There is no class for %s yet", eventType)
        return ret

    def delete_group(self, group):
        data = {"groupId": group.id}
        return self._restCall("home/group/deleteGroup", body=json.dumps(data))

    def get_OAuth_OTK(self):
        token = OAuthOTK(self._connection)
        token.from_json(self._restCall("home/getOAuthOTK"))
        return token

    def set_timezone(self, timezone):
        """ sets the timezone for the AP. e.g. "Europe/Berlin" """
        data = {"timezoneId": timezone}
        return self._restCall("home/setTimezone", body=json.dumps(data))

    def set_powermeter_unit_price(self, price):
        data = {"powerMeterUnitPrice": price}
        return self._restCall("home/setPowerMeterUnitPrice",
                              body=json.dumps(data))

    def set_zones_device_assignment(self, internal_devices, external_devices):
        """ sets the devices for the security zones
        :param internal_devices the devices which should be used for the internal zone
        :param external_devices the devices which should be used for the external(hull) zone
        :return the result of _restCall
        """
        internal = [x.id for x in internal_devices]
        external = [x.id for x in external_devices]
        data = {"zonesDeviceAssignment": {"INTERNAL": internal,
                                          "EXTERNAL": external}}
        return self._restCall("home/security/setZonesDeviceAssignment",
                              body=json.dumps(data))

    def enable_events(self):
        websocket.enableTrace(True)
        self.__webSocket = websocket.WebSocketApp(
            self._connection.urlWebSocket, header=[
                'AUTHTOKEN: {}'.format(self._connection.auth_token),
                'CLIENTAUTH: {}'.format(self._connection.clientauth_token)
            ],
            on_message=self._ws_on_message,
            on_error=self._ws_on_error)
        self.__webSocketThread = threading.Thread(
            target=self.__webSocket.run_forever)
        self.__webSocketThread.daemon = True
        self.__webSocketThread.start()

    def disable_events(self):
        self.__webSocket.close()

    def _ws_on_error(self, ws, message):
        LOGGER.error("Websocket error: %s", bytes2str(message))

    def _ws_on_message(self, ws, message):
        #json.loads doesn't support bytes as parameter before python 3.6
        js = json.loads(bytes2str(message))
        # LOGGER.debug(js)
        eventList = []
        try:
            for event in js["events"].values():
                pushEventType = event["pushEventType"]
                LOGGER.debug(pushEventType)
                obj = None
                if pushEventType == EVENT_GROUP_CHANGED:
                    data = event["group"]
                    obj = self.search_group_by_id(data["id"])
                    if type(obj) is MetaGroup:
                        obj.from_json(data, self.devices, self.groups)
                    else:
                        obj.from_json(data, self.devices)
                    obj.fire_update_event(data, event_type=pushEventType, obj=obj)
                elif pushEventType == EVENT_HOME_CHANGED:
                    data = event["home"]
                    obj = self
                    obj.from_json(data)
                    obj.fire_update_event(data, event_type=pushEventType, obj=obj)
                elif pushEventType == EVENT_CLIENT_ADDED:
                    data = event["client"]
                    obj = Client(self._connection)
                    obj.from_json(data)
                    self.clients.append(obj)
                elif pushEventType == EVENT_CLIENT_CHANGED:
                    data = event["client"]
                    obj = self.search_client_by_id(data["id"])
                    obj.from_json(data)
                elif pushEventType == EVENT_CLIENT_REMOVED:
                    obj = self.search_client_by_id(event["id"])
                    self.clients.remove(obj)
                elif pushEventType == EVENT_DEVICE_ADDED:
                    data = event["device"]
                    obj = self._parse_device(data)
                    self.devices.append(obj)
                elif pushEventType == EVENT_DEVICE_CHANGED:
                    data = event["device"]
                    obj = self.search_device_by_id(data["id"])
                    if obj is None:  # no DEVICE_ADDED Event?
                        obj = self._parse_device(data)
                        self.devices.append(obj)
                    else:
                        obj.from_json(data)
                    obj.fire_update_event(data, event_type=pushEventType, obj=obj)
                elif pushEventType == EVENT_DEVICE_REMOVED:
                    obj = self.search_device_by_id(event["id"])
                    self.devices.remove(obj)
                elif pushEventType == EVENT_GROUP_REMOVED:
                    obj = self.search_group_by_id(event["id"])
                    self.groups.remove(obj)
                elif pushEventType == EVENT_GROUP_ADDED:
                    group = event["group"]
                    obj = self._parse_group(group)
                    self.groups.append(obj)
                elif pushEventType == EVENT_SECURITY_JOURNAL_CHANGED:
                    pass  # data is just none so nothing to do here

                # TODO: implement INCLUSION_REQUESTED, NONE
                else:
                    LOGGER.warning("Uknown EventType '%s' Data: %s", pushEventType, event)
                eventList.append({"eventType": pushEventType, "data": obj})
        except Exception as err:
            LOGGER.exception(err)
        self.onEvent.fire(eventList)
Beispiel #4
0
class Home(HomeMaticIPObject.HomeMaticIPObject):
    """this class represents the 'Home' of the homematic ip"""

    _typeClassMap = TYPE_CLASS_MAP
    _typeGroupMap = TYPE_GROUP_MAP
    _typeSecurityEventMap = TYPE_SECURITY_EVENT_MAP
    _typeRuleMap = TYPE_RULE_MAP
    _typeFunctionalHomeMap = TYPE_FUNCTIONALHOME_MAP

    def __init__(self, connection=None):
        if connection is None:
            connection = Connection()
        super().__init__(connection)

        self.apExchangeClientId = None
        self.apExchangeState = ApExchangeState.NONE
        self.availableAPVersion = None
        self.carrierSense = None
        #:bool:displays if the access point is connected to the hmip cloud or not
        self.connected = None
        #:str:the current version of the access point
        self.currentAPVersion = None
        self.deviceUpdateStrategy = DeviceUpdateStrategy.MANUALLY
        self.dutyCycle = None
        #:str:the SGTIN of the access point
        self.id = None
        self.lastReadyForUpdateTimestamp = None
        #:Location:the location of the AP
        self.location = None
        #:bool:determines if a pin is set on this access point
        self.pinAssigned = None
        self.powerMeterCurrency = None
        self.powerMeterUnitPrice = None
        self.timeZoneId = None
        self.updateState = HomeUpdateState.UP_TO_DATE
        #:Weather:the current weather
        self.weather = None

        self.__webSocket = None
        self.__webSocketThread = None
        self.onEvent = EventHook()
        self.onWsError = EventHook()

        #:List[Device]: a collection of all devices in home
        self.devices = []
        #:List[Client]: a collection of all clients in home
        self.clients = []
        #:List[Group]: a collection of all groups in the home
        self.groups = []
        #:List[Rule]: a collection of all rules in the home
        self.rules = []
        #: a collection of all functionalHomes in the home
        self.functionalHomes = []

    def init(self, access_point_id, lookup=True):
        self._connection.init(access_point_id, lookup)

    def set_auth_token(self, auth_token):
        self._connection.set_auth_token(auth_token)

    def from_json(self, js_home):
        super().from_json(js_home)

        self.weather = Weather(self._connection)
        self.weather.from_json(js_home["weather"])
        if js_home["location"] != None:
            self.location = Location(self._connection)
            self.location.from_json(js_home["location"])

        self.connected = js_home["connected"]
        self.currentAPVersion = js_home["currentAPVersion"]
        self.availableAPVersion = js_home["availableAPVersion"]
        self.timeZoneId = js_home["timeZoneId"]
        self.pinAssigned = js_home["pinAssigned"]
        self.dutyCycle = js_home["dutyCycle"]
        self.updateState = HomeUpdateState.from_str(js_home["updateState"])
        self.powerMeterUnitPrice = js_home["powerMeterUnitPrice"]
        self.powerMeterCurrency = js_home["powerMeterCurrency"]
        self.deviceUpdateStrategy = DeviceUpdateStrategy.from_str(
            js_home["deviceUpdateStrategy"])
        self.lastReadyForUpdateTimestamp = js_home[
            "lastReadyForUpdateTimestamp"]
        self.apExchangeClientId = js_home["apExchangeClientId"]
        self.apExchangeState = ApExchangeState.from_str(
            js_home["apExchangeState"])
        self.id = js_home["id"]
        self.carrierSense = js_home["carrierSense"]

        self._get_rules(js_home)

    def download_configuration(self) -> str:
        """downloads the current configuration from the cloud

        Returns
            the downloaded configuration or an errorCode
        """
        return self._restCall(
            "home/getCurrentState",
            json.dumps(self._connection.clientCharacteristics))

    def get_current_state(self, clearConfig: bool = False):
        """downloads the current configuration and parses it into self
           
        Args:
            clearConfig(bool): if set to true, this function will remove all old objects 
            from self.devices, self.client, ... to have a fresh config instead of reparsing them
        """
        json_state = self.download_configuration()

        if "errorCode" in json_state:
            LOGGER.error(
                "Could not get the current configuration. Error: %s",
                json_state["errorCode"],
            )
            return False

        if clearConfig:
            self.devices = []
            self.clients = []
            self.groups = []
            self.rules = []
            self.functionalHomes = []

        js_home = json_state["home"]

        self.from_json(js_home)

        self._get_devices(json_state)
        self._get_clients(json_state)
        self._get_groups(json_state)

        self._get_functionalHomes(js_home)
        self._load_functionalChannels()

        return True

    def _get_devices(self, json_state):
        self.devices = [
            x for x in self.devices if x.id in json_state["devices"].keys()
        ]
        for id_, raw in json_state["devices"].items():
            _device = self.search_device_by_id(id_)
            if _device:
                _device.from_json(raw)
            else:
                self.devices.append(self._parse_device(raw))

    def _parse_device(self, json_state):
        try:
            deviceType = DeviceType.from_str(json_state["type"])
            d = self._typeClassMap[deviceType](self._connection)
            d.from_json(json_state)
            return d
        except:
            d = self._typeClassMap[DeviceType.DEVICE](self._connection)
            d.from_json(json_state)
            LOGGER.warning("There is no class for device '%s' yet",
                           json_state["type"])
            return d

    def _get_rules(self, json_state):
        self.rules = [
            x for x in self.rules
            if x.id in json_state["ruleMetaDatas"].keys()
        ]
        for id_, raw in json_state["ruleMetaDatas"].items():
            _rule = self.search_rule_by_id(id_)
            if _rule:
                _rule.from_json(raw)
            else:
                self.rules.append(self._parse_rule(raw))

    def _parse_rule(self, json_state):
        try:
            ruleType = AutomationRuleType.from_str(json_state["type"])
            r = self._typeRuleMap[ruleType](self._connection)
            r.from_json(json_state)
            return r
        except:
            r = Rule(self._connection)
            r.from_json(json_state)
            LOGGER.warning("There is no class for rule  '%s' yet",
                           json_state["type"])
            return r

    def _get_clients(self, json_state):
        self.clients = [
            x for x in self.clients if x.id in json_state["clients"].keys()
        ]
        for id_, raw in json_state["clients"].items():
            _client = self.search_client_by_id(id_)
            if _client:
                _client.from_json(raw)
            else:
                c = Client(self._connection)
                c.from_json(raw)
                self.clients.append(c)

    def _parse_group(self, json_state):
        g = None
        if json_state["type"] == "META":
            g = MetaGroup(self._connection)
            g.from_json(json_state, self.devices, self.groups)
        else:
            try:
                groupType = GroupType.from_str(json_state["type"])
                g = self._typeGroupMap[groupType](self._connection)
                g.from_json(json_state, self.devices)
            except:
                g = self._typeGroupMap[GroupType.GROUP](self._connection)
                g.from_json(json_state, self.devices)
                LOGGER.warning("There is no class for group '%s' yet",
                               json_state["type"])
        return g

    def _get_groups(self, json_state):
        self.groups = [
            x for x in self.groups if x.id in json_state["groups"].keys()
        ]
        metaGroups = []
        for id_, raw in json_state["groups"].items():
            _group = self.search_group_by_id(id_)
            if _group:
                if isinstance(_group, MetaGroup):
                    _group.from_json(raw, self.devices, self.groups)
                else:
                    _group.from_json(raw, self.devices)
            else:
                group_type = raw["type"]
                if group_type == "META":
                    metaGroups.append(raw)
                else:
                    self.groups.append(self._parse_group(raw))
        for mg in metaGroups:
            self.groups.append(self._parse_group(mg))

    def _get_functionalHomes(self, json_state):
        for solution, functionalHome in json_state["functionalHomes"].items():
            try:
                solutionType = FunctionalHomeType.from_str(solution)
                h = None
                for fh in self.functionalHomes:
                    if fh.solution == solution:
                        h = fh
                        break
                if h is None:
                    h = self._typeFunctionalHomeMap[solutionType](
                        self._connection)
                    self.functionalHomes.append(h)
                h.from_json(functionalHome, self.groups)
            except:
                h = FunctionalHome(self._connection)
                h.from_json(functionalHome, self.groups)
                LOGGER.warning("There is no class for functionalHome '%s' yet",
                               solution)
                self.functionalHomes.append(h)

    def _load_functionalChannels(self):
        for d in self.devices:
            d.load_functionalChannels(self.groups)

    def get_functionalHome(self, functionalHomeType: type) -> FunctionalHome:
        """ gets the specified functionalHome
        
        Args:
            functionalHome(type): the type of the functionalHome which should be returned

        Returns:
            the FunctionalHome or None if it couldn't be found
        """
        for x in self.functionalHomes:
            if isinstance(x, functionalHomeType):
                return x

        return None

    def search_device_by_id(self, deviceID) -> Device:
        """ searches a device by given id
        
        Args:
          deviceID(str): the device to search for
          
        Returns
          the Device object or None if it couldn't find a device
        """
        for d in self.devices:
            if d.id == deviceID:
                return d
        return None

    def search_group_by_id(self, groupID) -> Group:
        """ searches a group by given id
        
        Args:
          groupID(str): groupID the group to search for
          
        Returns
          the group object or None if it couldn't find a group
        """
        for g in self.groups:
            if g.id == groupID:
                return g
        return None

    def search_client_by_id(self, clientID) -> Client:
        """ searches a client by given id
        
        Args:
          clientID(str): the client to search for
        
        Returns
          the client object or None if it couldn't find a client
        """
        for c in self.clients:
            if c.id == clientID:
                return c
        return None

    def search_rule_by_id(self, ruleID) -> Rule:
        """ searches a rule by given id
        
        Args:
          ruleID(str): the rule to search for
        
        Returns
          the rule object or None if it couldn't find a rule
        """
        for r in self.rules:
            if r.id == ruleID:
                return r
        return None

    def get_security_zones_activation(self) -> (bool, bool):
        """ returns the value of the security zones if they are armed or not
        
        Returns
            internal
              True if the internal zone is armed
            external
              True if the external zone is armed
        """
        internal_active = False
        external_active = False
        for g in self.groups:
            if isinstance(g, SecurityZoneGroup):
                if g.label == "EXTERNAL":
                    external_active = g.active
                elif g.label == "INTERNAL":
                    internal_active = g.active
        return internal_active, external_active

    def set_security_zones_activation(self, internal=True, external=True):
        """ this function will set the alarm system to armed or disable it
        
        Args:
          internal(bool): activates/deactivates the internal zone
          external(bool): activates/deactivates the external zone
        
        Examples:
          arming while being at home
          
          >>> home.set_security_zones_activation(False,True)
          
          arming without being at home
          
          >>> home.set_security_zones_activation(True,True)
          
          disarming the alarm system
          
          >>> home.set_security_zones_activation(False,False)
        """
        data = {
            "zonesActivation": {
                "EXTERNAL": external,
                "INTERNAL": internal
            }
        }
        return self._restCall("home/security/setZonesActivation",
                              json.dumps(data))

    def set_location(self, city, latitude, longitude):
        data = {"city": city, "latitude": latitude, "longitude": longitude}
        return self._restCall("home/setLocation", json.dumps(data))

    def set_intrusion_alert_through_smoke_detectors(self,
                                                    activate: bool = True):
        """ activate or deactivate if smoke detectors should "ring" during an alarm

        Args:
            activate(bool): True will let the smoke detectors "ring" during an alarm
        """
        data = {"intrusionAlertThroughSmokeDetectors": activate}
        return self._restCall(
            "home/security/setIntrusionAlertThroughSmokeDetectors",
            json.dumps(data))

    def activate_absence_with_period(self, endtime: datetime):
        """ activates the absence mode until the given time

        Args:
            endtime(datetime): the time when the absence should automatically be disabled
        """
        data = {"endTime": endtime.strftime("%Y_%m_%d %H:%M")}
        return self._restCall("home/heating/activateAbsenceWithPeriod",
                              json.dumps(data))

    def activate_absence_with_duration(self, duration: int):
        """ activates the absence mode for a given time

        Args:
            duration(int): the absence duration in minutes
        """
        data = {"duration": duration}
        return self._restCall("home/heating/activateAbsenceWithDuration",
                              json.dumps(data))

    def deactivate_absence(self):
        """ deactivates the absence mode immediately"""
        return self._restCall("home/heating/deactivateAbsence")

    def activate_vacation(self, endtime: datetime, temperature: float):
        """ activates the vatation mode until the given time

        Args:
            endtime(datetime): the time when the vatation mode should automatically be disabled
            temperature(float): the settemperature during the vacation mode
        """
        data = {
            "endtime": endtime.strftime("%Y_%m_%d %H:%M"),
            "temperature": temperature,
        }
        return self._restCall("home/heating/activateVacation",
                              json.dumps(data))

    def deactivate_vacation(self):
        """ deactivates the vacation mode immediately"""
        return self._restCall("home/heating/deactivateVacation")

    def set_pin(self, newPin: str, oldPin: str = None) -> dict:
        """ sets a new pin for the home

        Args:
            newPin(str): the new pin
            oldPin(str): optional, if there is currently a pin active it must be given here.
                        Otherwise it will not be possible to set the new pin

        Returns:
            the result of the call
        """
        if newPin == None:
            newPin = ""
        data = {"pin": newPin}
        if oldPin:
            self._connection.headers["PIN"] = str(oldPin)
        result = self._restCall("home/setPin", body=json.dumps(data))
        if oldPin:
            del self._connection.headers["PIN"]
        return result

    def set_zone_activation_delay(self, delay):
        data = {"zoneActivationDelay": delay}
        return self._restCall("home/security/setZoneActivationDelay",
                              body=json.dumps(data))

    def get_security_journal(self):
        journal = self._restCall("home/security/getSecurityJournal")
        if "errorCode" in journal:
            LOGGER.error("Could not get the security journal. Error: %s",
                         journal["errorCode"])
            return None
        ret = []
        for entry in journal["entries"]:
            try:
                eventType = SecurityEventType(entry["eventType"])
                if eventType in self._typeSecurityEventMap:
                    j = self._typeSecurityEventMap[eventType](self._connection)
            except:
                j = SecurityEvent(self._connection)
                LOGGER.warning("There is no class for %s yet",
                               entry["eventType"])

            j.from_json(entry)
            ret.append(j)

        return ret

    def delete_group(self, group: Group):
        """deletes the given group from the cloud
        
        Args:
            group(Group):the group to delete
        """
        return group.delete()

    def get_OAuth_OTK(self):
        token = OAuthOTK(self._connection)
        token.from_json(self._restCall("home/getOAuthOTK"))
        return token

    def set_timezone(self, timezone: str):
        """ sets the timezone for the AP. e.g. "Europe/Berlin" 
        Args:
            timezone(str): the new timezone
        """
        data = {"timezoneId": timezone}
        return self._restCall("home/setTimezone", body=json.dumps(data))

    def set_powermeter_unit_price(self, price):
        data = {"powerMeterUnitPrice": price}
        return self._restCall("home/setPowerMeterUnitPrice",
                              body=json.dumps(data))

    def set_zones_device_assignment(self, internal_devices,
                                    external_devices) -> dict:
        """ sets the devices for the security zones
        Args:
            internal_devices(List[Device]): the devices which should be used for the internal zone
            external_devices(List[Device]):  the devices which should be used for the external(hull) zone
        
        Returns:
            the result of _restCall
        """
        internal = [x.id for x in internal_devices]
        external = [x.id for x in external_devices]
        data = {
            "zonesDeviceAssignment": {
                "INTERNAL": internal,
                "EXTERNAL": external
            }
        }
        return self._restCall("home/security/setZonesDeviceAssignment",
                              body=json.dumps(data))

    def enable_events(self):
        websocket.enableTrace(True)
        self.__webSocket = websocket.WebSocketApp(
            self._connection.urlWebSocket,
            header=[
                "AUTHTOKEN: {}".format(self._connection.auth_token),
                "CLIENTAUTH: {}".format(self._connection.clientauth_token),
            ],
            on_message=self._ws_on_message,
            on_error=self._ws_on_error,
        )
        self.__webSocketThread = threading.Thread(
            target=self.__webSocket.run_forever)
        self.__webSocketThread.daemon = True
        self.__webSocketThread.start()

    def disable_events(self):
        self.__webSocket.close()

    def _ws_on_error(self, err):
        LOGGER.exception(err)
        self.onWsError.fire(err)

    def _ws_on_message(self, message):
        # json.loads doesn't support bytes as parameter before python 3.6
        js = json.loads(bytes2str(message))
        # LOGGER.debug(js)
        eventList = []
        for event in js["events"].values():
            try:
                pushEventType = EventType(event["pushEventType"])
                LOGGER.debug(pushEventType)
                obj = None
                if pushEventType == EventType.GROUP_CHANGED:
                    data = event["group"]
                    obj = self.search_group_by_id(data["id"])
                    if type(obj) is MetaGroup:
                        obj.from_json(data, self.devices, self.groups)
                    else:
                        obj.from_json(data, self.devices)
                    obj.fire_update_event(data,
                                          event_type=pushEventType,
                                          obj=obj)
                elif pushEventType == EventType.HOME_CHANGED:
                    data = event["home"]
                    obj = self
                    obj.from_json(data)
                    obj.fire_update_event(data,
                                          event_type=pushEventType,
                                          obj=obj)
                elif pushEventType == EventType.CLIENT_ADDED:
                    data = event["client"]
                    obj = Client(self._connection)
                    obj.from_json(data)
                    self.clients.append(obj)
                elif pushEventType == EventType.CLIENT_CHANGED:
                    data = event["client"]
                    obj = self.search_client_by_id(data["id"])
                    obj.from_json(data)
                elif pushEventType == EventType.CLIENT_REMOVED:
                    obj = self.search_client_by_id(event["id"])
                    self.clients.remove(obj)
                elif pushEventType == EventType.DEVICE_ADDED:
                    data = event["device"]
                    obj = self._parse_device(data)
                    obj.load_functionalChannels(self.groups)
                    self.devices.append(obj)
                elif pushEventType == EventType.DEVICE_CHANGED:
                    data = event["device"]
                    obj = self.search_device_by_id(data["id"])
                    if obj is None:  # no DEVICE_ADDED Event?
                        obj = self._parse_device(data)
                        self.devices.append(obj)
                    else:
                        obj.from_json(data)
                    obj.load_functionalChannels(self.groups)
                    obj.fire_update_event(data,
                                          event_type=pushEventType,
                                          obj=obj)
                elif pushEventType == EventType.DEVICE_REMOVED:
                    obj = self.search_device_by_id(event["id"])
                    self.devices.remove(obj)
                elif pushEventType == EventType.GROUP_REMOVED:
                    obj = self.search_group_by_id(event["id"])
                    self.groups.remove(obj)
                elif pushEventType == EventType.GROUP_ADDED:
                    group = event["group"]
                    obj = self._parse_group(group)
                    self.groups.append(obj)
                elif pushEventType == EventType.SECURITY_JOURNAL_CHANGED:
                    pass  # data is just none so nothing to do here

                # TODO: implement INCLUSION_REQUESTED, NONE
                eventList.append({"eventType": pushEventType, "data": obj})
            except ValueError as valerr:
                LOGGER.warning("Uknown EventType '%s' Data: %s",
                               event["pushEventType"], event)

            except Exception as err:
                LOGGER.exception(err)
        self.onEvent.fire(eventList)