from collections import Counter from collections.abc import Iterable from typing import ClassVar from homematicip.base.enums import * from homematicip.base.functionalChannels import FunctionalChannel from homematicip.base.helpers import get_functional_channel, get_functional_channels from homematicip.base.homematicip_object import HomeMaticIPObject from homematicip.group import Group LOGGER = logging.getLogger(__name__) class BaseDevice(HomeMaticIPObject): """Base device class. This is the foundation for homematicip and external (hue) devices""" _supportedFeatureAttributeMap: ClassVar[dict[str, list[str]]] = {} def __init__(self, connection): super().__init__(connection) self.id = None self.homeId = None self.label = None self.connectionType = ConnectionType.HMIP_LAN self.deviceArchetype = DeviceArchetype.HMIP self.lastStatusUpdate = None self.firmwareVersion = None self.modelType = "" self.permanentlyReachable = False self.functionalChannels = [] self.functionalChannelCount = Counter() self.deviceType = None # must be imported in init. otherwise we have cross import issues from homematicip.class_maps import TYPE_FUNCTIONALCHANNEL_MAP self._typeFunctionalChannelMap = TYPE_FUNCTIONALCHANNEL_MAP def from_json(self, js): super().from_json(js) self.id = js["id"] self.homeId = js["homeId"] self.label = js["label"] self.lastStatusUpdate = self.fromtimestamp(js["lastStatusUpdate"]) self.firmwareVersion = js["firmwareVersion"] self.modelType = js["modelType"] self.permanentlyReachable = js["permanentlyReachable"] self.deviceType = js["type"] self.connectionType = ConnectionType.from_str(js["connectionType"]) if "deviceArchetype" in js: self.deviceArchetype = DeviceArchetype.from_str(js["deviceArchetype"]) def load_functionalChannels( self, groups: Iterable[Group], channels: Iterable[FunctionalChannel] ): """this function will load the functionalChannels into the device""" for channel in self._rawJSONData["functionalChannels"].values(): items = [ ch for ch in self.functionalChannels if ch.index == channel["index"] ] fc = items[0] if items else None if fc is not None: fc.from_json(channel, groups) else: fc = self._parse_functionalChannel(channel, groups) channels.append(fc) self.functionalChannels.append(fc) self.functionalChannelCount = Counter( x.functionalChannelType for x in self.functionalChannels ) def _parse_functionalChannel(self, json_state, groups: Iterable[Group]) -> FunctionalChannel: fc = None channel_type = FunctionalChannelType.from_str(json_state["functionalChannelType"]) if not channel_type in self._typeFunctionalChannelMap: LOGGER.warning( "There is no class for functionalChannel '%s' yet", json_state["functionalChannelType"], ) return self._parse_default_functional_channel(json_state, groups) try: fc = self._typeFunctionalChannelMap[channel_type](self, self._connection) fc.from_json(json_state, groups) except Exception: # pragma: no cover LOGGER.exception("Error while parsing functionalChannel\nJS: %s", json_state) return fc def _parse_default_functional_channel(self, json_state, groups: Iterable[Group]) -> FunctionalChannel: fc = self._typeFunctionalChannelMap[FunctionalChannelType.FUNCTIONAL_CHANNEL](self, self._connection) fc.from_json(json_state, groups) fc.device = self return fc def get_functional_channel( self, channel_type: FunctionalChannelType | str, index: int | None = None ) -> FunctionalChannel | None: """Return the loaded functional channel matching the given type and optional index.""" if isinstance(channel_type, str): channel_type = FunctionalChannelType.from_str(channel_type, channel_type) for channel in self.functionalChannels: if channel.functionalChannelType != channel_type: continue if index is not None and channel.index != index: continue return channel return None class Device(BaseDevice): """this class represents a generic homematic ip device""" _supportedFeatureAttributeMap: ClassVar[dict[str, list[str]]] = { "IFeatureBusConfigMismatch": ["busConfigMismatch"], "IFeatureDeviceCoProError": ["coProFaulty"], "IFeatureDeviceCoProRestart": ["coProRestartNeeded"], "IFeatureDeviceCoProUpdate": ["coProUpdateFailure"], "IFeatureDeviceIdentify": [], "IFeatureDeviceMountingModuleError": ["mountingModuleError"], "IFeatureDeviceOverheated": ["deviceOverheated"], "IFeatureDeviceOverloaded": ["deviceOverloaded"], "IFeatureDeviceParticulateMatterSensorCommunicationError": ["particulateMatterSensorCommunicationError"], "IFeatureDeviceParticulateMatterSensorError": ["particulateMatterSensorError"], "IFeatureDevicePowerFailure": ["devicePowerFailureDetected"], "IFeatureDeviceSensorCommunicationError": ["sensorCommunicationError"], "IFeatureDeviceSensorError": ["sensorError"], "IFeatureDeviceTemperatureHumiditySensorCommunicationError": ["temperatureHumiditySensorCommunicationError"], "IFeatureDeviceTemperatureHumiditySensorError": ["temperatureHumiditySensorError"], "IFeatureDeviceTemperatureOutOfRange": ["temperatureOutOfRange"], "IFeatureDeviceUndervoltage": ["deviceUndervoltage"], "IFeatureMinimumFloorHeatingValvePosition": ["minimumFloorHeatingValvePosition"], "IFeatureMulticastRouter": ["multicastRoutingEnabled"], "IFeaturePowerShortCircuit": ["powerShortCircuit"], "IFeatureProfilePeriodLimit": [], "IFeaturePulseWidthModulationAtLowFloorHeatingValvePosition": [ "pulseWidthModulationAtLowFloorHeatingValvePositionEnabled"], "IFeatureRssiValue": ["rssiDeviceValue"], "IFeatureShortCircuitDataLine": ["shortCircuitDataLine"], "IOptionalFeatureColorTemperature": ["colorTemperature"], # "IOptionalFeatureColorTemperatureDim2Warm": false, # "IOptionalFeatureColorTemperatureDynamicDaylight": false, "IOptionalFeatureControlsMountingOrientation": ["controlsMountingOrientation"], "IOptionalFeatureDeviceErrorLockJammed": ["lockJammed"], "IOptionalFeatureDisplayContrast": [], "IOptionalFeatureDutyCycle": ["dutyCycle"], "IOptionalFeatureFilteredMulticastRouter": ["filteredMulticastRoutingEnabled"], # "IOptionalFeatureHardwareColorTemperature": false, # "IOptionalFeatureHueSaturationValue": false, # "IOptionalFeatureLightScene": false, # "IOptionalFeatureLightSceneWithShortTimes": false, "IOptionalFeatureLowBat": ["lowBat"], "IOptionalFeatureMountingOrientation": ["mountingOrientation"], "IOptionalFeatureOperationDays": ["operationDays"], "IOptionalFeatureDeviceFrostProtectionError": ["frostProtectionError"], "IOptionalFeatureDeviceValveError": ["valveFlowError"], "IOptionalFeatureDeviceWaterError": ["valveWaterError"], "IOptionalFeatureDeviceOperationMode": ["deviceOperationMode"], "IOptionalFeatureDeviceAliveSignalEnabled": ["deviceAliveSignalEnabled"], "IOptionalFeatureDeviceSwitchChannelMode": ["switchChannelMode"] } def __init__(self, connection): super().__init__(connection) self.liveUpdateState = None self.updateState = DeviceUpdateState.UP_TO_DATE self.availableFirmwareVersion = None self.firmwareVersionInteger = ( 0 # firmwareVersion = A.B.C -> firmwareVersionInteger ((A<<16)|(B<<8)|C) ) self.unreach = False self.lowBat = False self.routerModuleSupported = False self.routerModuleEnabled = False self.modelId = 0 self.oem = "" self.manufacturerCode = 0 self.serializedGlobalTradeItemNumber = "" self.rssiDeviceValue = 0 self.rssiPeerValue = 0 self.dutyCycle = False self.configPending = False self._baseChannel = "DEVICE_BASE" self.deviceOverheated = False self.deviceOverloaded = False self.deviceUndervoltage = False self.temperatureOutOfRange = False self.coProFaulty = False self.coProRestartNeeded = False self.coProUpdateFailure = False self.busConfigMismatch = False self.shortCircuitDataLine = False self.powerShortCircuit = False self.mountingModuleError = None self.switchChannelMode = None self.deviceUndervoltage = False self.devicePowerFailureDetected = False self.deviceAliveSignalEnabled = None self.deviceIdentifySupported = ( False # just placeholder at the moment the feature doesn't set any values ) def from_json(self, js): super().from_json(js) self.updateState = DeviceUpdateState.from_str(js["updateState"]) self.firmwareVersionInteger = js["firmwareVersionInteger"] self.availableFirmwareVersion = js["availableFirmwareVersion"] self.modelId = js["modelId"] self.oem = js["oem"] self.manufacturerCode = js["manufacturerCode"] self.serializedGlobalTradeItemNumber = js["serializedGlobalTradeItemNumber"] self.liveUpdateState = LiveUpdateState.from_str(js["liveUpdateState"]) c = get_functional_channel(self._baseChannel, js) if c: self.set_attr_from_dict("lowBat", c) self.set_attr_from_dict("unreach", c) self.set_attr_from_dict("rssiDeviceValue", c) self.set_attr_from_dict("rssiPeerValue", c) self.set_attr_from_dict("configPending", c) self.set_attr_from_dict("dutyCycle", c) self.routerModuleSupported = c["routerModuleSupported"] self.routerModuleEnabled = c["routerModuleEnabled"] sof = c.get("supportedOptionalFeatures") if sof: for k, v in sof.items(): if v: if k in Device._supportedFeatureAttributeMap: for attribute in Device._supportedFeatureAttributeMap[k]: self.set_attr_from_dict(attribute, c) else: # pragma: no cover LOGGER.warning( "Optional Device Feature '%s' is not yet supported", k, ) def __str__(self): return f"{self.modelType} {self.label} {self.str_from_attr_map()}" def set_label(self, label): return self._run_non_async(lambda: self.set_label_async(label)) async def set_label_async(self, label): data = {"deviceId": self.id, "label": label} return await self._rest_call_async("device/setDeviceLabel", data) def is_update_applicable(self): return self._run_non_async(self.is_update_applicable_async) async def is_update_applicable_async(self): data = {"deviceId": self.id} return await self._rest_call_async("device/isUpdateApplicable", data) def authorizeUpdate(self): return self._run_non_async(self.authorizeUpdate_async) async def authorizeUpdate_async(self): data = {"deviceId": self.id} return await self._rest_call_async("device/authorizeUpdate", data) def delete(self): return self._run_non_async(self.delete_async) async def delete_async(self): data = {"deviceId": self.id} return await self._rest_call_async("device/deleteDevice", data) def set_router_module_enabled(self, enabled=True): return self._run_non_async(lambda: self.set_router_module_enabled_async(enabled)) async def set_router_module_enabled_async(self, enabled=True): if not self.routerModuleSupported: return False data = {"deviceId": self.id, "channelIndex": 0, "routerModuleEnabled": enabled} return await self._rest_call_async( "device/configuration/setRouterModuleEnabled", data ) class ExternalDevice(BaseDevice): """Represents devices with archtetype EXTERNAL""" def __init__(self, connection): super().__init__(connection) self.hasCustomLabel = None self.externalService = "" self.supported = None self._baseChannel = "EXTERNAL_BASE_CHANNEL" def from_json(self, js): super().from_json(js) self.hasCustomLabel = js["hasCustomLabel"] self.externalService = js["externalService"] self.supported = js["supported"] class HomeControlUnit(Device): def __init__(self, connection): super().__init__(connection) self.dutyCycleLevel = 0.0 self.accessPointPriority = 0 self.signalBrightness = 0 self._baseChannel = "ACCESS_CONTROLLER_CHANNEL" def from_json(self, js): super().from_json(js) c = get_functional_channel(self._baseChannel, js) if c: self.set_attr_from_dict("dutyCycleLevel", c) self.set_attr_from_dict("accessPointPriority", c) self.set_attr_from_dict("signalBrightness", c) class HomeControlAccessPoint(Device): def __init__(self, connection): super().__init__(connection) self.dutyCycleLevel = 0.0 self.accessPointPriority = 0 self.signalBrightness = 0 self._baseChannel = "ACCESS_CONTROLLER_CHANNEL" def from_json(self, js): super().from_json(js) c = get_functional_channel(self._baseChannel, js) if c: self.set_attr_from_dict("dutyCycleLevel", c) self.set_attr_from_dict("accessPointPriority", c) self.set_attr_from_dict("signalBrightness", c) class WiredDinRailAccessPoint(Device): def __init__(self, connection): super().__init__(connection) self.accessPointPriority = 0 self.signalBrightness = 0 self._baseChannel = "ACCESS_CONTROLLER_WIRED_CHANNEL" def from_json(self, js): super().from_json(js) c = get_functional_channel(self._baseChannel, js) if c: self.set_attr_from_dict("accessPointPriority", c) self.set_attr_from_dict("signalBrightness", c) class SabotageDevice(Device): def __init__(self, connection): super().__init__(connection) self.sabotage = None self._baseChannel = "DEVICE_SABOTAGE" def from_json(self, js): super().from_json(js) c = get_functional_channel(self._baseChannel, js) if c: self.set_attr_from_dict("sabotage", c) class OperationLockableDevice(Device): def __init__(self, connection): super().__init__(connection) self.operationLockActive = None self._baseChannel = "DEVICE_OPERATIONLOCK" def from_json(self, js): super().from_json(js) c = get_functional_channel(self._baseChannel, js) if c: self.set_attr_from_dict("operationLockActive", c) def set_operation_lock(self, operationLock=True): return self._run_non_async(lambda: self.set_operation_lock_async(operationLock)) async def set_operation_lock_async(self, operationLock=True): data = {"channelIndex": 0, "deviceId": self.id, "operationLock": operationLock} return await self._rest_call_async("device/configuration/setOperationLock", data) class HeatingThermostat(OperationLockableDevice): """HMIP-eTRV (Radiator Thermostat)""" def __init__(self, connection): super().__init__(connection) #:float: the offset temperature for the thermostat (+/- 3.5) self.temperatureOffset = 0.0 #:float: the current position of the valve 0.0 = closed, 1.0 max opened self.valvePosition = 0.0 #:ValveState: the current state of the valve self.valveState = ValveState.ERROR_POSITION #:float: the current temperature which should be reached in the room self.setPointTemperature = 0.0 #:float: the current measured temperature at the valve self.valveActualTemperature = 0.0 #:bool: must the adaption re-run? self.automaticValveAdaptionNeeded = False def from_json(self, js): super().from_json(js) c = get_functional_channel("HEATING_THERMOSTAT_CHANNEL", js) if c: self.temperatureOffset = c["temperatureOffset"] self.valvePosition = c["valvePosition"] self.valveState = ValveState.from_str(c["valveState"]) self.setPointTemperature = c["setPointTemperature"] self.valveActualTemperature = c["valveActualTemperature"] def __str__(self): return f"{super().__str__()} valvePosition({self.valvePosition}) valveState({self.valveState}) temperatureOffset({self.temperatureOffset}) setPointTemperature({self.setPointTemperature}) valveActualTemperature({self.valveActualTemperature})" class HeatingThermostatCompact(SabotageDevice): """HMIP-eTRV-C (Heating-thermostat compact without display)""" def __init__(self, connection): super().__init__(connection) #:float: the offset temperature for the thermostat (+/- 3.5) self.temperatureOffset = 0.0 #:float: the current position of the valve 0.0 = closed, 1.0 max opened self.valvePosition = 0.0 #:ValveState: the current state of the valve self.valveState = ValveState.ERROR_POSITION #:float: the current temperature which should be reached in the room self.setPointTemperature = 0.0 #:float: the current measured temperature at the valve self.valveActualTemperature = 0.0 #:bool: must the adaption re-run? self.automaticValveAdaptionNeeded = False def from_json(self, js): super().from_json(js) c = get_functional_channel("HEATING_THERMOSTAT_CHANNEL", js) if c: self.temperatureOffset = c["temperatureOffset"] self.valvePosition = c["valvePosition"] self.valveState = ValveState.from_str(c["valveState"]) self.setPointTemperature = c["setPointTemperature"] self.valveActualTemperature = c["valveActualTemperature"] def __str__(self): return f"{super().__str__()} valvePosition({self.valvePosition}) valveState({self.valveState}) temperatureOffset({self.temperatureOffset}) setPointTemperature({self.setPointTemperature}) valveActualTemperature({self.valveActualTemperature})" class HeatingThermostatEvo(OperationLockableDevice): """HMIP-eTRV-E (Heating-thermostat new evo version)""" def __init__(self, connection): super().__init__(connection) #:float: the offset temperature for the thermostat (+/- 3.5) self.temperatureOffset = 0.0 #:float: the current position of the valve 0.0 = closed, 1.0 max opened self.valvePosition = 0.0 #:ValveState: the current state of the valve self.valveState = ValveState.ERROR_POSITION #:float: the current temperature which should be reached in the room self.setPointTemperature = 0.0 #:float: the current measured temperature at the valve self.valveActualTemperature = 0.0 #:bool: must the adaption re-run? self.automaticValveAdaptionNeeded = False def from_json(self, js): super().from_json(js) c = get_functional_channel("HEATING_THERMOSTAT_CHANNEL", js) if c: self.temperatureOffset = c["temperatureOffset"] self.valvePosition = c["valvePosition"] self.valveState = ValveState.from_str(c["valveState"]) self.setPointTemperature = c["setPointTemperature"] self.valveActualTemperature = c["valveActualTemperature"] def __str__(self): return f"{super().__str__()} valvePosition({self.valvePosition}) valveState({self.valveState}) temperatureOffset({self.temperatureOffset}) setPointTemperature({self.setPointTemperature}) valveActualTemperature({self.valveActualTemperature})" class ShutterContact(SabotageDevice): """HMIP-SWDO (Door / Window Contact - optical) / HMIP-SWDO-I (Door / Window Contact Invisible - optical)""" def __init__(self, connection): super().__init__(connection) self.windowState = WindowState.CLOSED self.eventDelay = None def from_json(self, js): super().from_json(js) c = get_functional_channel("SHUTTER_CONTACT_CHANNEL", js) if c: self.windowState = WindowState.from_str(c["windowState"]) self.eventDelay = c["eventDelay"] def __str__(self): return f"{super().__str__()} windowState({self.windowState})" class ShutterContactMagnetic(Device): """HMIP-SWDM / HMIP-SWDM-B2 (Door / Window Contact - magnetic )""" def __init__(self, connection): super().__init__(connection) self.windowState = WindowState.CLOSED self.eventDelay = None def from_json(self, js): super().from_json(js) c = get_functional_channel("SHUTTER_CONTACT_CHANNEL", js) if c: self.windowState = WindowState.from_str(c["windowState"]) self.eventDelay = c["eventDelay"] def __str__(self): return f"{super().__str__()} windowState({self.windowState})" class ShutterContactOpticalPlus(ShutterContact): """HmIP-SWDO-PL ( Window / Door Contact – optical, plus )""" class ContactInterface(SabotageDevice): """HMIP-SCI (Contact Interface Sensor)""" def __init__(self, connection): super().__init__(connection) self.windowState = WindowState.CLOSED self.eventDelay = None def from_json(self, js): super().from_json(js) c = get_functional_channel("CONTACT_INTERFACE_CHANNEL", js) if c: self.windowState = WindowState.from_str(c["windowState"]) self.eventDelay = c["eventDelay"] def __str__(self): return f"{super().__str__()} windowState({self.windowState})" class RotaryHandleSensor(SabotageDevice): """HMIP-SRH""" def __init__(self, connection): super().__init__(connection) self.windowState = WindowState.CLOSED self.eventDelay = None def from_json(self, js): super().from_json(js) c = get_functional_channel("ROTARY_HANDLE_CHANNEL", js) if c: self.windowState = WindowState.from_str(c["windowState"]) self.eventDelay = c["eventDelay"] def __str__(self): return f"{super().__str__()} windowState({self.windowState})" class TemperatureHumiditySensorOutdoor(Device): """HMIP-STHO (Temperature and Humidity Sensor outdoor)""" def __init__(self, connection): super().__init__(connection) self.actualTemperature = 0 self.humidity = 0 self.vaporAmount = 0.0 def from_json(self, js): super().from_json(js) c = get_functional_channel("CLIMATE_SENSOR_CHANNEL", js) if c: self.actualTemperature = c["actualTemperature"] self.humidity = c["humidity"] self.vaporAmount = c["vaporAmount"] def __str__(self): return f"{super().__str__()} actualTemperature({self.actualTemperature}) humidity({self.humidity}) vaporAmount({self.vaporAmount})" class TemperatureHumiditySensorWithoutDisplay(Device): """HMIP-STH (Temperature and Humidity Sensor without display - indoor)""" def __init__(self, connection): super().__init__(connection) self.temperatureOffset = 0 self.actualTemperature = 0 self.humidity = 0 self.vaporAmount = 0.0 def from_json(self, js): super().from_json(js) c = get_functional_channel( "WALL_MOUNTED_THERMOSTAT_WITHOUT_DISPLAY_CHANNEL", js ) if c: self.temperatureOffset = c["temperatureOffset"] self.actualTemperature = c["actualTemperature"] self.humidity = c["humidity"] self.vaporAmount = c["vaporAmount"] def __str__(self): return f"{super().__str__()} actualTemperature({self.actualTemperature}) humidity({self.humidity}) vaporAmount({self.vaporAmount})" class TemperatureHumiditySensorDisplay(Device): """HMIP-STHD (Temperature and Humidity Sensor with display - indoor)""" def __init__(self, connection): super().__init__(connection) self.temperatureOffset = 0 self.display = ClimateControlDisplay.ACTUAL self.actualTemperature = 0 self.humidity = 0 self.setPointTemperature = 0 self.vaporAmount = 0.0 def from_json(self, js): super().from_json(js) c = get_functional_channel("WALL_MOUNTED_THERMOSTAT_PRO_CHANNEL", js) if c: self.temperatureOffset = c["temperatureOffset"] self.display = ClimateControlDisplay.from_str(c["display"]) self.actualTemperature = c["actualTemperature"] self.humidity = c["humidity"] self.setPointTemperature = c["setPointTemperature"] self.vaporAmount = c["vaporAmount"] def set_display(self, display: ClimateControlDisplay = ClimateControlDisplay.ACTUAL): return self._run_non_async(lambda: self.set_display_async(display)) async def set_display_async(self, display: ClimateControlDisplay = ClimateControlDisplay.ACTUAL): data = {"channelIndex": 1, "deviceId": self.id, "display": str(display)} return await self._rest_call_async( "device/configuration/setClimateControlDisplay", data ) def __str__(self): return f"{super().__str__()} actualTemperature({self.actualTemperature}) humidity({self.humidity}) vaporAmount({self.vaporAmount}) setPointTemperature({self.setPointTemperature})" class WallMountedThermostatPro( TemperatureHumiditySensorDisplay, OperationLockableDevice ): """HMIP-WTH, HMIP-WTH-2 (Wall Thermostat with Humidity Sensor) / HMIP-BWTH (Brand Wall Thermostat with Humidity Sensor)""" def from_json(self, js): super().from_json(js) c = get_functional_channel("WALL_MOUNTED_THERMOSTAT_PRO_CHANNEL", js) if c: self.temperatureOffset = c["temperatureOffset"] self.display = ClimateControlDisplay.from_str(c["display"]) self.actualTemperature = c["actualTemperature"] self.humidity = c["humidity"] self.setPointTemperature = c["setPointTemperature"] class WiredCarbonTemperatureHumiditySensorDisplay(Device): """HMIP-STHD-C (Wired Carbon Dioxide, Temperature and Humidity Sensor with display)""" def __init__(self, connection): super().__init__(connection) def from_json(self, js): super().from_json(js) class RoomControlDevice(WallMountedThermostatPro): """ALPHA-IP-RBG (Alpha IP Wall Thermostat Display)""" class RoomControlDeviceAnalog(Device): """ALPHA-IP-RBGa (ALpha IP Wall Thermostat Display analog)""" def __init__(self, connection): super().__init__(connection) self.actualTemperature = 0.0 self.setPointTemperature = 0.0 self.temperatureOffset = 0.0 def from_json(self, js): super().from_json(js) c = get_functional_channel("ANALOG_ROOM_CONTROL_CHANNEL", js) if c: self.set_attr_from_dict("actualTemperature", c) self.set_attr_from_dict("setPointTemperature", c) self.set_attr_from_dict("temperatureOffset", c) class WallMountedThermostatBasicHumidity(WallMountedThermostatPro): """HMIP-WTH-B (Wall Thermostat – basic)""" class SmokeDetector(Device): """HMIP-SWSD, HmIP-SWSD-2 (Smoke Alarm with Q label)""" def __init__(self, connection): super().__init__(connection) self.smokeDetectorAlarmType = SmokeDetectorAlarmType.IDLE_OFF self.chamberDegraded = False self.dirtLevel = 0.0 self.smokeDetectorGroupAssignment = [] self.smokeEventRepeatingActive = False self.lastSmokeAlarmTimestamp = None self.lastSmokeTestTimestamp = None self.smokeAlarmCounter = None self.smokeTestCounter = None def from_json(self, js): super().from_json(js) c = get_functional_channel("SMOKE_DETECTOR_CHANNEL", js) if c: self.set_attr_from_dict("smokeDetectorAlarmType", c, enum_type=SmokeDetectorAlarmType) self.set_attr_from_dict("chamberDegraded", c) self.set_attr_from_dict("dirtLevel", c) self.set_attr_from_dict("smokeDetectorGroupAssignment", c) self.set_attr_from_dict("smokeEventRepeatingActive", c) self.set_attr_from_dict("lastSmokeAlarmTimestamp", c) self.set_attr_from_dict("lastSmokeTestTimestamp", c) self.set_attr_from_dict("smokeAlarmCounter", c) self.set_attr_from_dict("smokeTestCounter", c) class FloorTerminalBlock6(Device): """HMIP-FAL230-C6 (Floor Heating Actuator - 6 channels, 230 V)""" def __init__(self, connection): super().__init__(connection) self.globalPumpControl = False self.heatingValveType = HeatingValveType.NORMALLY_CLOSE self.heatingLoadType = HeatingLoadType.LOAD_BALANCING self.frostProtectionTemperature = 0.0 self.heatingEmergencyValue = 0.0 self.valveProtectionDuration = 0 self.valveProtectionSwitchingInterval = 20 self.coolingEmergencyValue = 0 self.pumpFollowUpTime = 0 self.pumpLeadTime = 0 self.pumpProtectionDuration = 0 self.pumpProtectionSwitchingInterval = 20 self._baseChannel = "DEVICE_GLOBAL_PUMP_CONTROL" def from_json(self, js): super().from_json(js) c = get_functional_channel("DEVICE_GLOBAL_PUMP_CONTROL", js) if c: self.globalPumpControl = c["globalPumpControl"] self.heatingValveType = HeatingValveType.from_str(c["heatingValveType"]) self.heatingLoadType = HeatingLoadType.from_str(c["heatingLoadType"]) self.coolingEmergencyValue = c["coolingEmergencyValue"] self.frostProtectionTemperature = c["frostProtectionTemperature"] self.heatingEmergencyValue = c["heatingEmergencyValue"] self.valveProtectionDuration = c["valveProtectionDuration"] self.valveProtectionSwitchingInterval = c[ "valveProtectionSwitchingInterval" ] c = get_functional_channel("FLOOR_TERMINAL_BLOCK_LOCAL_PUMP_CHANNEL", js) if c: self.pumpFollowUpTime = c["pumpFollowUpTime"] self.pumpLeadTime = c["pumpLeadTime"] self.pumpProtectionDuration = c["pumpProtectionDuration"] self.pumpProtectionSwitchingInterval = c["pumpProtectionSwitchingInterval"] def __str__(self): return ( f"{super().__str__()} globalPumpControl({self.globalPumpControl}) heatingValveType({self.heatingValveType}) heatingLoadType({self.heatingLoadType}) coolingEmergencyValue({self.coolingEmergencyValue}) frostProtectionTemperature({self.frostProtectionTemperature}) heatingEmergencyValue({self.heatingEmergencyValue}) " f"valveProtectionDuration({self.valveProtectionDuration}) valveProtectionSwitchingInterval({self.valveProtectionSwitchingInterval}) pumpFollowUpTime({self.pumpFollowUpTime}) pumpLeadTime({self.pumpLeadTime}) pumpProtectionDuration({self.pumpProtectionDuration}) " f"pumpProtectionSwitchingInterval({self.pumpProtectionSwitchingInterval})" ) class FloorTerminalBlock10(FloorTerminalBlock6): """HMIP-FAL24-C10 (Floor Heating Actuator – 10x channels, 24V)""" class FloorTerminalBlock12(Device): """HMIP-FALMOT-C12 (Floor Heating Actuator – 12x channels, motorised)""" def __init__(self, connection): super().__init__(connection) self.frostProtectionTemperature = 0.0 self.heatingEmergencyValue = 0.0 self.valveProtectionDuration = 0 self.valveProtectionSwitchingInterval = 20 self.coolingEmergencyValue = 0 self.minimumFloorHeatingValvePosition = 0.0 self.pulseWidthModulationAtLowFloorHeatingValvePositionEnabled = False self._baseChannel = "DEVICE_BASE_FLOOR_HEATING" def from_json(self, js): super().from_json(js) c = get_functional_channel("DEVICE_BASE_FLOOR_HEATING", js) if c: self.set_attr_from_dict("coolingEmergencyValue", c) self.set_attr_from_dict("frostProtectionTemperature", c) self.set_attr_from_dict("heatingEmergencyValue", c) self.set_attr_from_dict("minimumFloorHeatingValvePosition", c) self.set_attr_from_dict("pulseWidthModulationAtLowFloorHeatingValvePositionEnabled", c) self.set_attr_from_dict("valveProtectionDuration", c) self.set_attr_from_dict("valveProtectionSwitchingInterval", c) def set_minimum_floor_heating_valve_position(self, minimumFloorHeatingValvePosition: float): """sets the minimum floot heating valve position Args: minimumFloorHeatingValvePosition(float): the minimum valve position. must be between 0.0 and 1.0 Returns: the result of the _restCall """ return self._run_non_async(lambda: self.set_minimum_floor_heating_valve_position_async( minimumFloorHeatingValvePosition ) ) async def set_minimum_floor_heating_valve_position_async(self, minimumFloorHeatingValvePosition: float): """sets the minimum floot heating valve position Args: minimumFloorHeatingValvePosition(float): the minimum valve position. must be between 0.0 and 1.0 Returns: the result of the _restCall """ data = { "channelIndex": 0, "deviceId": self.id, "minimumFloorHeatingValvePosition": minimumFloorHeatingValvePosition, } return await self._rest_call_async( "device/configuration/setMinimumFloorHeatingValvePosition", body=data, ) class WiredFloorTerminalBlock12(FloorTerminalBlock12): """Implementation of HmIPW-FALMOT-C12""" class Switch(Device): """Generic Switch class""" def __init__(self, connection): super().__init__(connection) self.on = None self.profileMode = None self.userDesiredProfileMode = None def from_json(self, js): super().from_json(js) c = get_functional_channel("SWITCH_CHANNEL", js) if c: self.on = c["on"] self.profileMode = c["profileMode"] self.userDesiredProfileMode = c["userDesiredProfileMode"] def __str__(self): return f"{super().__str__()} on({self.on}) profileMode({self.profileMode}) userDesiredProfileMode({self.userDesiredProfileMode})" def set_switch_state(self, on=True, channelIndex=1): return self._run_non_async(self.set_switch_state_async, on, channelIndex) async def set_switch_state_async(self, on=True, channelIndex=1): data = {"channelIndex": channelIndex, "deviceId": self.id, "on": on} return await self._rest_call_async("device/control/setSwitchState", body=data) def turn_on(self, channelIndex=1): return self._run_non_async(self.turn_on_async, channelIndex) async def turn_on_async(self, channelIndex=1): return await self.set_switch_state_async(True, channelIndex) def turn_off(self, channelIndex=1): return self._run_non_async(self.turn_off_async, channelIndex) async def turn_off_async(self, channelIndex=1): return await self.set_switch_state_async(False, channelIndex) class CarbonDioxideSensor(Switch): """HmIP-SCTH230""" class PlugableSwitch(Switch): """HMIP-PS (Pluggable Switch), HMIP-PCBS (Switch Circuit Board - 1 channel)""" class PrintedCircuitBoardSwitchBattery(Switch): """HMIP-PCBS-BAT (Printed Circuit Board Switch Battery)""" class PrintedCircuitBoardSwitch2(Switch): """HMIP-PCBS2 (Switch Circuit Board - 2x channels)""" class OpenCollector8Module(Switch): """HMIP-MOD-OC8 ( Open Collector Module )""" class StatusBoard8(Switch): """ELV-SH-SB8 (Status Board with 8 LEDs)""" class HeatingSwitch2(Switch): """HMIP-WHS2 (Switch Actuator for heating systems – 2x channels)""" class WiredInputSwitch6(Switch): """HmIPW-FIO6""" class WiredSwitch8(Switch): """HMIPW-DRS8 (Homematic IP Wired Switch Actuator – 8x channels)""" class WiredSwitch4(Switch): """HMIPW-DRS4 (Homematic IP Wired Switch Actuator – 4x channels)""" class DinRailSwitch4(Switch): """HMIP-DRSI4 (Homematic IP Switch Actuator for DIN rail mount – 4x channels)""" class BrandSwitch2(Switch): """ELV-SH-BS2 (ELV Smart Home ARR-Bausatz Schaltaktor für Markenschalter – 2-fach powered by Homematic IP)""" class SoilMoistureSensorInterface(Device): """ELV-SH-SMSI (Soil Moisture Sensor Interface)""" class SwitchMeasuring(Switch): """Generic class for Switch and Meter""" def __init__(self, connection): super().__init__(connection) self.energyCounter = 0 self.currentPowerConsumption = 0 self.measuredAttributes: dict[str, dict[str, bool]] = {} def reset_energy_counter(self): return self._run_non_async(self.reset_energy_counter_async) async def reset_energy_counter_async(self): data = {"channelIndex": 1, "deviceId": self.id} return await self._rest_call_async("device/control/resetEnergyCounter", body=data) def from_json(self, js): super().from_json(js) self.set_attr_from_dict("measuredAttributes", js) c = get_functional_channel("SWITCH_MEASURING_CHANNEL", js) if c: self.on = c["on"] self.energyCounter = c["energyCounter"] self.currentPowerConsumption = c["currentPowerConsumption"] self.profileMode = c["profileMode"] self.userDesiredProfileMode = c["userDesiredProfileMode"] def __str__(self): return f"{super().__str__()} energyCounter({self.energyCounter}) currentPowerConsumption({self.currentPowerConsumption}W)" class MultiIOBox(Switch): """HMIP-MIOB (Multi IO Box for floor heating & cooling)""" def __init__(self, connection): super().__init__(connection) self.analogOutputLevel = 0.0 def from_json(self, js): super().from_json(js) c = get_functional_channel("ANALOG_OUTPUT_CHANNEL", js) if c: self.analogOutputLevel = c["analogOutputLevel"] def __str__(self): return f"{super().__str__()} analogOutputLevel({self.analogOutputLevel})" class DoorBellContactInterface(Device): """HMIP-DSD-PCB (Door Bell Contact Interface)""" class BrandSwitchNotificationLight(Switch): """HMIP-BSL (Switch Actuator for brand switches – with signal lamp)""" def __init__(self, connection): super().__init__(connection) #:int:the channel number for the top light self.topLightChannelIndex = 2 #:int:the channel number for the bottom light self.bottomLightChannelIndex = 3 def __str__(self): top = self.functionalChannels[self.topLightChannelIndex] bottom = self.functionalChannels[self.bottomLightChannelIndex] return ( f"{super().__str__()} topDimLevel({top.dimLevel}) topColor({top.simpleRGBColorState}) bottomDimLevel({bottom.dimLevel}) bottomColor({bottom.simpleRGBColorState})" ) def set_rgb_dim_level(self, channelIndex: int, rgb: RGBColorState, dimLevel: float): """sets the color and dimlevel of the lamp Args: channelIndex(int): the channelIndex of the lamp. Use self.topLightChannelIndex or self.bottomLightChannelIndex rgb(RGBColorState): the color of the lamp dimLevel(float): the dimLevel of the lamp. 0.0 = off, 1.0 = MAX Returns: the result of the _restCall """ return self._run_non_async(lambda: self.set_rgb_dim_level_async(channelIndex, rgb, dimLevel)) async def set_rgb_dim_level_async(self, channelIndex: int, rgb: RGBColorState, dimLevel: float): """sets the color and dimlevel of the lamp Args: channelIndex(int): the channelIndex of the lamp. Use self.topLightChannelIndex or self.bottomLightChannelIndex rgb(RGBColorState): the color of the lamp dimLevel(float): the dimLevel of the lamp. 0.0 = off, 1.0 = MAX Returns: the result of the _restCall """ data = { "channelIndex": channelIndex, "deviceId": self.id, "simpleRGBColorState": rgb, "dimLevel": dimLevel, } return await self._rest_call_async( "device/control/setSimpleRGBColorDimLevel", body=data ) def set_rgb_dim_level_with_time( self, channelIndex: int, rgb: RGBColorState, dimLevel: float, onTime: float, rampTime: float, ): """sets the color and dimlevel of the lamp Args: channelIndex(int): the channelIndex of the lamp. Use self.topLightChannelIndex or self.bottomLightChannelIndex rgb(RGBColorState): the color of the lamp dimLevel(float): the dimLevel of the lamp. 0.0 = off, 1.0 = MAX onTime(float): rampTime(float): Returns: the result of the _restCall """ return self._run_non_async(lambda: self.set_rgb_dim_level_with_time_async( channelIndex, rgb, dimLevel, onTime, rampTime )) async def set_rgb_dim_level_with_time_async( self, channelIndex: int, rgb: RGBColorState, dimLevel: float, onTime: float, rampTime: float, ): """sets the color and dimlevel of the lamp Args: channelIndex(int): the channelIndex of the lamp. Use self.topLightChannelIndex or self.bottomLightChannelIndex rgb(RGBColorState): the color of the lamp dimLevel(float): the dimLevel of the lamp. 0.0 = off, 1.0 = MAX onTime(float): rampTime(float): Returns: the result of the _restCall """ data = { "channelIndex": channelIndex, "deviceId": self.id, "simpleRGBColorState": rgb, "dimLevel": dimLevel, "onTime": onTime, "rampTime": rampTime, } return await self._rest_call_async( "device/control/setSimpleRGBColorDimLevelWithTime", body=data ) class PushButton(Device): """HMIP-WRC2 (Wall-mount Remote Control - 2-button)""" class DoorBellButton(PushButton): """HmIP-DBB""" class PushButton6(PushButton): """HMIP-WRC6 (Wall-mount Remote Control - 6-button)""" class PushButtonFlat(PushButton): """HmIP-WRCC2 (Wall-mount Remote Control – flat)""" class BrandPushButton(PushButton): """HMIP-BRC2 (Remote Control for brand switches – 2x channels)""" class KeyRemoteControl4(PushButton): """HMIP-KRC4 (Key Ring Remote Control - 4 buttons)""" class RemoteControl8(PushButton): """HMIP-RC8 (Remote Control - 8 buttons)""" class RemoteControl8Module(RemoteControl8): """HMIP-MOD-RC8 (Open Collector Module Sender - 8x)""" class RgbwDimmer(Device): """HmIP-RGBW""" fastColorChangeSupported: bool = False def from_json(self, js): super().from_json(js) self.set_attr_from_dict("fastColorChangeSupported", js) class AlarmSirenIndoor(SabotageDevice): """HMIP-ASIR (Alarm Siren)""" def from_json(self, js): super().from_json(js) c = get_functional_channel("ALARM_SIREN_CHANNEL", js) if c: # The ALARM_SIREN_CHANNEL doesn't have any values yet. pass class AlarmSirenOutdoor(AlarmSirenIndoor): """HMIP-ASIR-O (Alarm Siren Outdoor)""" def __init__(self, connection): super().__init__(connection) self.badBatteryHealth = False self._baseChannel = "DEVICE_RECHARGEABLE_WITH_SABOTAGE" def from_json(self, js): super().from_json(js) c = get_functional_channel("DEVICE_RECHARGEABLE_WITH_SABOTAGE", js) if c: self.set_attr_from_dict("badBatteryHealth", c) class CombinationSignallingDevice(Device): """HmIP-MP3P (Combination Signalling Device) A device that can play MP3 sounds and show optical signals for various alarm and notification conditions. """ def __init__(self, connection): super().__init__(connection) self._baseChannel = "DEVICE_PERMANENT_FULL_RX" self.permanentFullRx = False # NOTIFICATION_MP3_SOUND_CHANNEL properties self.dimLevel = 0.0 self.lightSoundNotificationSettings = {} self.mp3ErrorState = "NO_ERROR" self.noSoundLowBat = False self.on = False self.opticalSignalBehaviour = None self.playingFileActive = False self.profileMode = "AUTOMATIC" self.simpleRGBColorState = "BLACK" self.soundFile = None self.volumeLevel = None def from_json(self, js): super().from_json(js) c = get_functional_channel("DEVICE_PERMANENT_FULL_RX", js) if c: self.set_attr_from_dict("permanentFullRx", c) c = get_functional_channel("NOTIFICATION_MP3_SOUND_CHANNEL", js) if c: self.set_attr_from_dict("dimLevel", c) self.set_attr_from_dict("lightSoundNotificationSettings", c) self.set_attr_from_dict("mp3ErrorState", c) self.set_attr_from_dict("noSoundLowBat", c) self.set_attr_from_dict("on", c) self.set_attr_from_dict("opticalSignalBehaviour", c) self.set_attr_from_dict("playingFileActive", c) self.set_attr_from_dict("profileMode", c) self.set_attr_from_dict("simpleRGBColorState", c) self.set_attr_from_dict("soundFile", c) self.set_attr_from_dict("volumeLevel", c) class MotionDetectorIndoor(SabotageDevice): """HMIP-SMI (Motion Detector with Brightness Sensor - indoor)""" def __init__(self, connection): super().__init__(connection) self.currentIllumination = None self.motionDetected = None self.illumination = None self.motionBufferActive = False self.motionDetectionSendInterval = MotionDetectionSendInterval.SECONDS_30 self.numberOfBrightnessMeasurements = 0 def from_json(self, js): super().from_json(js) c = get_functional_channel("MOTION_DETECTION_CHANNEL", js) if c: self.motionDetected = c["motionDetected"] self.illumination = c["illumination"] self.motionBufferActive = c["motionBufferActive"] self.motionDetectionSendInterval = MotionDetectionSendInterval.from_str( c["motionDetectionSendInterval"] ) self.numberOfBrightnessMeasurements = c["numberOfBrightnessMeasurements"] self.currentIllumination = c["currentIllumination"] def __str__(self): return f"{super().__str__()} motionDetected({self.motionDetected}) illumination({self.illumination}) motionBufferActive({self.motionBufferActive}) motionDetectionSendInterval({self.motionDetectionSendInterval}) numberOfBrightnessMeasurements({self.numberOfBrightnessMeasurements})" class MotionDetectorOutdoor(Device): """HMIP-SMO-A (Motion Detector with Brightness Sensor - outdoor)""" def __init__(self, connection): super().__init__(connection) self.currentIllumination = None self.motionDetected = None self.illumination = None self.motionBufferActive = False self.motionDetectionSendInterval = MotionDetectionSendInterval.SECONDS_30 self.numberOfBrightnessMeasurements = 0 def from_json(self, js): super().from_json(js) c = get_functional_channel("MOTION_DETECTION_CHANNEL", js) if c: self.set_attr_from_dict("motionDetected", c) self.set_attr_from_dict("illumination", c) self.set_attr_from_dict("motionBufferActive", c) self.set_attr_from_dict("motionDetectionSendInterval", c) self.set_attr_from_dict("numberOfBrightnessMeasurements", c) self.set_attr_from_dict("currentIllumination", c) class MotionDetectorPushButton(MotionDetectorOutdoor): """HMIP-SMI55 (Motion Detector with Brightness Sensor and Remote Control - 2-button)""" def __init__(self, connection): super().__init__(connection) self._baseChannel = "DEVICE_PERMANENT_FULL_RX" self.permanentFullRx = False def from_json(self, js): super().from_json(js) c = get_functional_channel("DEVICE_PERMANENT_FULL_RX", js) if c: self.set_attr_from_dict("permanentFullRx", c) class WiredMotionDetectorPushButton(MotionDetectorOutdoor): """HmIPW-SMI55""" class PresenceDetectorIndoor(SabotageDevice): """HMIP-SPI (Presence Sensor - indoor)""" def __init__(self, connection): super().__init__(connection) self.presenceDetected = False self.currentIllumination = None self.illumination = 0 self.motionBufferActive = False self.motionDetectionSendInterval = MotionDetectionSendInterval.SECONDS_30 self.numberOfBrightnessMeasurements = 0 def from_json(self, js): super().from_json(js) c = get_functional_channel("PRESENCE_DETECTION_CHANNEL", js) if c: self.presenceDetected = c["presenceDetected"] self.currentIllumination = c["currentIllumination"] self.illumination = c["illumination"] self.motionBufferActive = c["motionBufferActive"] self.motionDetectionSendInterval = MotionDetectionSendInterval.from_str( c["motionDetectionSendInterval"] ) self.numberOfBrightnessMeasurements = c["numberOfBrightnessMeasurements"] def __str__(self): return f"{super().__str__()} presenceDetected({self.presenceDetected}) illumination({self.illumination}) motionBufferActive({self.motionBufferActive}) motionDetectionSendInterval({self.motionDetectionSendInterval}) numberOfBrightnessMeasurements({self.numberOfBrightnessMeasurements})" class PassageDetector(SabotageDevice): """HMIP-SPDR (Passage Detector)""" def __init__(self, connection): super().__init__(connection) self.leftCounter = 0 self.leftRightCounterDelta = 0 self.passageBlindtime = 0.0 self.passageDirection = PassageDirection.RIGHT self.passageSensorSensitivity = 0.0 self.passageTimeout = 0.0 self.rightCounter = 0 def from_json(self, js): super().from_json(js) c = get_functional_channel("PASSAGE_DETECTOR_CHANNEL", js) if c: self.leftCounter = c["leftCounter"] self.leftRightCounterDelta = c["leftRightCounterDelta"] self.passageBlindtime = c["passageBlindtime"] self.passageDirection = PassageDirection.from_str(c["passageDirection"]) self.passageSensorSensitivity = c["passageSensorSensitivity"] self.passageTimeout = c["passageTimeout"] self.rightCounter = c["rightCounter"] def __str__(self): return f"{super().__str__()} leftCounter({self.leftCounter}) leftRightCounterDelta({self.leftRightCounterDelta}) passageBlindtime({self.passageBlindtime}) passageDirection({self.passageDirection}) passageSensorSensitivity({self.passageSensorSensitivity}) passageTimeout({self.passageTimeout}) rightCounter({self.rightCounter})" class KeyRemoteControlAlarm(Device): """HMIP-KRCA (Key Ring Remote Control - alarm)""" class Shutter(Device): """Base class for shutter devices""" def set_shutter_level(self, level=0.0, channelIndex=1): """sets the shutter level Args: level(float): the new level of the shutter. 0.0 = open, 1.0 = closed channelIndex(int): the channel to control Returns: the result of the _restCall """ return self._run_non_async(lambda: self.set_shutter_level_async(level, channelIndex)) async def set_shutter_level_async(self, level=0.0, channelIndex=1): """sets the shutter level Args: level(float): the new level of the shutter. 0.0 = open, 1.0 = closed channelIndex(int): the channel to control Returns: the result of the _restCall """ data = { "channelIndex": channelIndex, "deviceId": self.id, "shutterLevel": level, } return await self._rest_call_async("device/control/setShutterLevel", body=data) def set_shutter_stop(self, channelIndex=1): """stops the current shutter operation Args: channelIndex(int): the channel to control Returns: the result of the _restCall """ return self._run_non_async(lambda: self.set_shutter_stop_async(channelIndex)) async def set_shutter_stop_async(self, channelIndex=1): """stops the current shutter operation Args: channelIndex(int): the channel to control Returns: the result of the _restCall """ data = {"channelIndex": channelIndex, "deviceId": self.id} return await self._rest_call_async("device/control/stop", body=data) class Blind(Shutter): """Base class for blind devices""" def set_slats_level(self, slatsLevel=0.0, shutterLevel=None, channelIndex=1): """sets the slats and shutter level Args: slatsLevel(float): the new level of the slats. 0.0 = open, 1.0 = closed shutterLevel(float): the new level of the shutter. 0.0 = open, 1.0 = closed, None = use the current value channelIndex(int): the channel to control Returns: the result of the _restCall """ return self._run_non_async(lambda: self.set_slats_level_async(slatsLevel, shutterLevel, channelIndex) ) async def set_slats_level_async(self, slatsLevel=0.0, shutterLevel=None, channelIndex=1): """sets the slats and shutter level Args: slatsLevel(float): the new level of the slats. 0.0 = open, 1.0 = closed, shutterLevel(float): the new level of the shutter. 0.0 = open, 1.0 = closed, None = use the current value channelIndex(int): the channel to control Returns: the result of the _restCall """ if shutterLevel is None: shutterLevel = self.functionalChannels[channelIndex].shutterLevel data = { "channelIndex": channelIndex, "deviceId": self.id, "slatsLevel": slatsLevel, "shutterLevel": shutterLevel, } return await self._rest_call_async("device/control/setSlatsLevel", data) class FullFlushShutter(Shutter): """HMIP-FROLL (Shutter Actuator - flush-mount) / HMIP-BROLL (Shutter Actuator - Brand-mount)""" def __init__(self, connection): super().__init__(connection) self.shutterLevel = 0 self.changeOverDelay = 0.0 self.bottomToTopReferenceTime = 0.0 self.topToBottomReferenceTime = 0.0 self.delayCompensationValue = 0 self.endpositionAutoDetectionEnabled = False self.previousShutterLevel = None self.processing = False self.profileMode = "AUTOMATIC" self.selfCalibrationInProgress = None self.supportingDelayCompensation = False self.supportingEndpositionAutoDetection = False self.supportingSelfCalibration = False self.userDesiredProfileMode = "AUTOMATIC" def from_json(self, js): super().from_json(js) c = get_functional_channel("SHUTTER_CHANNEL", js) if c: self.shutterLevel = c["shutterLevel"] self.changeOverDelay = c["changeOverDelay"] self.delayCompensationValue = c["delayCompensationValue"] self.bottomToTopReferenceTime = c["bottomToTopReferenceTime"] self.topToBottomReferenceTime = c["topToBottomReferenceTime"] self.endpositionAutoDetectionEnabled = c["endpositionAutoDetectionEnabled"] self.previousShutterLevel = c["previousShutterLevel"] self.processing = c["processing"] self.profileMode = c["profileMode"] self.selfCalibrationInProgress = c["selfCalibrationInProgress"] self.supportingDelayCompensation = c["supportingDelayCompensation"] self.supportingEndpositionAutoDetection = c[ "supportingEndpositionAutoDetection" ] self.supportingSelfCalibration = c["supportingSelfCalibration"] self.userDesiredProfileMode = c["userDesiredProfileMode"] def __str__(self): return f"{super().__str__()} shutterLevel({self.shutterLevel}) topToBottom({self.topToBottomReferenceTime}) bottomToTop({self.bottomToTopReferenceTime})" class FullFlushBlind(FullFlushShutter, Blind): """HMIP-FBL (Blind Actuator - flush-mount)""" def __init__(self, connection): super().__init__(connection) self.slatsLevel = 0 self.slatsReferenceTime = 0.0 self.previousSlatsLevel = 0 self.blindModeActive = False def from_json(self, js): super().from_json(js) c = get_functional_channel("BLIND_CHANNEL", js) if c: self.shutterLevel = c["shutterLevel"] self.changeOverDelay = c["changeOverDelay"] self.delayCompensationValue = c["delayCompensationValue"] self.bottomToTopReferenceTime = c["bottomToTopReferenceTime"] self.topToBottomReferenceTime = c["topToBottomReferenceTime"] self.endpositionAutoDetectionEnabled = c["endpositionAutoDetectionEnabled"] self.previousShutterLevel = c["previousShutterLevel"] self.processing = c["processing"] self.profileMode = c["profileMode"] self.selfCalibrationInProgress = c["selfCalibrationInProgress"] self.supportingDelayCompensation = c["supportingDelayCompensation"] self.supportingEndpositionAutoDetection = c[ "supportingEndpositionAutoDetection" ] self.supportingSelfCalibration = c["supportingSelfCalibration"] self.userDesiredProfileMode = c["userDesiredProfileMode"] self.slatsLevel = c["slatsLevel"] self.slatsReferenceTime = c["slatsReferenceTime"] self.previousSlatsLevel = c["previousSlatsLevel"] self.blindModeActive = c["blindModeActive"] def __str__(self): return f"{super().__str__()} slatsLevel({self.slatsLevel}) blindModeActive({self.blindModeActive})" class BrandBlind(FullFlushBlind): """HMIP-BBL (Blind Actuator for brand switches)""" class DinRailBlind4(Blind): """HmIP-DRBLI4 (Blind Actuator for DIN rail mount – 4 channels)""" class WiredDinRailBlind4(Blind): """HmIPW-DRBL4""" class WiredPushButton(PushButton): """HmIPW-WRC6 and HmIPW-WRC2""" def set_optical_signal( self, channelIndex, opticalSignalBehaviour: OpticalSignalBehaviour, rgb: RGBColorState, dimLevel=1.01, ): """sets the signal type for the leds Args: channelIndex(int): Channel which is affected opticalSignalBehaviour(OpticalSignalBehaviour): LED signal behaviour rgb(RGBColorState): Color dimLevel(float): usally 1.01. Use set_dim_level instead Returns: Result of the _restCall """ return self._run_non_async( self.set_optical_signal_async, channelIndex, opticalSignalBehaviour, rgb, dimLevel, ) async def set_optical_signal_async( self, channelIndex, opticalSignalBehaviour: OpticalSignalBehaviour, rgb: RGBColorState, dimLevel=1.01, ): """sets the signal type for the leds Args: channelIndex(int): Channel which is affected opticalSignalBehaviour(OpticalSignalBehaviour): LED signal behaviour rgb(RGBColorState): Color dimLevel(float): usally 1.01. Use set_dim_level instead Returns: Result of the _restCall """ data = { "channelIndex": channelIndex, "deviceId": self.id, "dimLevel": dimLevel, "opticalSignalBehaviour": opticalSignalBehaviour, "simpleRGBColorState": rgb, } return await self._rest_call_async("device/control/setOpticalSignal", body=data) def set_dim_level(self, channelIndex, dimLevel): """sets the signal type for the leds Args: channelIndex(int): Channel which is affected dimLevel(float): usally 1.01. Use set_dim_level instead Returns: Result of the _restCall """ return self._run_non_async(lambda: self.set_dim_level_async(channelIndex, dimLevel)) async def set_dim_level_async(self, channelIndex, dimLevel): """sets the signal type for the leds Args: channelIndex(int): Channel which is affected dimLevel(float): usally 1.01. Use set_dim_level instead Returns: Result of the _restCall """ data = {"channelIndex": channelIndex, "deviceId": self.id, "dimLevel": dimLevel} return await self._rest_call_async("device/control/setDimLevel", body=data) def set_switch_state(self, on, channelIndex): return self._run_non_async(lambda: self.set_switch_state_async(on, channelIndex)) async def set_switch_state_async(self, on, channelIndex): data = {"channelIndex": channelIndex, "deviceId": self.id, "on": on} return await self._rest_call_async("device/control/setSwitchState", body=data) def turn_on(self, channelIndex): return self.set_switch_state(True, channelIndex) async def turn_on_async(self, channelIndex): return await self.set_switch_state_async(True, channelIndex) def turn_off(self, channelIndex): return self.set_switch_state(False, channelIndex) async def turn_off_async(self, channelIndex): return await self.set_switch_state_async(False, channelIndex) class PushButton6LedSwitch(WiredPushButton): """HmIP-WRC6-230 (Wall-mount Remote Control - 6-button, 230V, with LED)""" class BlindModule(Device): """HMIP-HDM1 (Hunter Douglas & erfal window blinds)""" def __init__(self, connection): super().__init__(connection) self.automationDriveSpeed = DriveSpeed.CREEP_SPEED self.manualDriveSpeed = DriveSpeed.CREEP_SPEED self.favoritePrimaryShadingPosition = 0.0 self.favoriteSecondaryShadingPosition = 0.0 self.primaryShadingLevel = 0.0 self.secondaryShadingLevel = 0.0 self.previousPrimaryShadingLevel = 0.0 self.previousSecondaryShadingLevel = 0.0 self.identifyOemSupported = False self.productId = 0 self.primaryCloseAdjustable = False self.primaryOpenAdjustable = False self.primaryShadingStateType = ShadingStateType.NOT_EXISTENT self.primaryCloseAdjustable = False self.primaryOpenAdjustable = False self.primaryShadingStateType = ShadingStateType.NOT_EXISTENT self.profileMode = ProfileMode.MANUAL self.userDesiredProfileMode = ProfileMode.MANUAL self.processing = False self.shadingDriveVersion = None self.shadingPackagePosition = ShadingPackagePosition.NOT_USED self.shadingPositionAdjustmentActive = None self.shadingPositionAdjustmentClientId = None def from_json(self, js): super().from_json(js) c = get_functional_channel("SHADING_CHANNEL", js) if c: self.set_attr_from_dict("automationDriveSpeed", c, DriveSpeed) self.set_attr_from_dict("manualDriveSpeed", c, DriveSpeed) self.set_attr_from_dict("favoritePrimaryShadingPosition", c) self.set_attr_from_dict("favoriteSecondaryShadingPosition", c) self.set_attr_from_dict("primaryCloseAdjustable", c) self.set_attr_from_dict("primaryOpenAdjustable", c) self.set_attr_from_dict("primaryShadingStateType", c, ShadingStateType) self.set_attr_from_dict("secondaryCloseAdjustable", c) self.set_attr_from_dict("secondaryOpenAdjustable", c) self.set_attr_from_dict("secondaryShadingStateType", c, ShadingStateType) self.set_attr_from_dict("primaryShadingLevel", c) self.set_attr_from_dict("secondaryShadingLevel", c) self.set_attr_from_dict("previousPrimaryShadingLevel", c) self.set_attr_from_dict("previousSecondaryShadingLevel", c) self.set_attr_from_dict("identifyOemSupported", c) self.set_attr_from_dict("productId", c) self.set_attr_from_dict("profileMode", c, ProfileMode) self.set_attr_from_dict("userDesiredProfileMode", c, ProfileMode) self.set_attr_from_dict("shadingDriveVersion", c) self.set_attr_from_dict("shadingPackagePosition", c, ShadingPackagePosition) self.set_attr_from_dict("shadingPositionAdjustmentActive", c) self.set_attr_from_dict("shadingPositionAdjustmentClientId", c) def set_primary_shading_level(self, primaryShadingLevel: float): return self._run_non_async(lambda: self.set_primary_shading_level_async(primaryShadingLevel)) async def set_primary_shading_level_async(self, primaryShadingLevel: float): data = { "channelIndex": 1, "deviceId": self.id, "primaryShadingLevel": primaryShadingLevel, } return await self._rest_call_async("device/control/setPrimaryShadingLevel", data) def set_secondary_shading_level( self, primaryShadingLevel: float, secondaryShadingLevel: float ): return self._run_non_async(lambda: self.set_secondary_shading_level_async(primaryShadingLevel, secondaryShadingLevel) ) async def set_secondary_shading_level_async( self, primaryShadingLevel: float, secondaryShadingLevel: float): data = { "channelIndex": 1, "deviceId": self.id, "primaryShadingLevel": primaryShadingLevel, "secondaryShadingLevel": secondaryShadingLevel, } return await self._rest_call_async( "device/control/setSecondaryShadingLevel", data ) async def stop(self): """stops the current operation Returns: the result of the _restCall """ return self._run_non_async(self.stop_async) async def stop_async(self): """stops the current operation Returns: the result of the _restCall """ data = {"channelIndex": 1, "deviceId": self.id} return await self._rest_call_async("device/control/stop", body=data) class LightSensor(Device): """HMIP-SLO (Light Sensor outdoor)""" def __init__(self, connection): super().__init__(connection) #:float:the average illumination value self.averageIllumination = 0.0 #:float:the current illumination value self.currentIllumination = 0.0 #:float:the highest illumination value self.highestIllumination = 0.0 #:float:the lowest illumination value self.lowestIllumination = 0.0 def from_json(self, js): super().from_json(js) c = get_functional_channel("LIGHT_SENSOR_CHANNEL", js) if c: self.averageIllumination = c["averageIllumination"] self.currentIllumination = c["currentIllumination"] self.highestIllumination = c["highestIllumination"] self.lowestIllumination = c["lowestIllumination"] def __str__(self): return f"{super().__str__()} averageIllumination({self.averageIllumination}) currentIllumination({self.currentIllumination}) highestIllumination({self.highestIllumination}) lowestIllumination({self.lowestIllumination})" class Dimmer(Device): """Base dimmer device class""" def __init__(self, connection): super().__init__(connection) self.dimLevel = 0.0 self.profileMode = "" self.userDesiredProfileMode = "" def from_json(self, js): super().from_json(js) c = get_functional_channel("DIMMER_CHANNEL", js) if c: self.dimLevel = c["dimLevel"] self.profileMode = c["profileMode"] self.userDesiredProfileMode = c["userDesiredProfileMode"] def __str__(self): return f"{super().__str__()} dimLevel({self.dimLevel}) profileMode({self.profileMode}) userDesiredProfileMode({self.userDesiredProfileMode})" def set_dim_level(self, dimLevel=0.0, channelIndex=1): return self._run_non_async(lambda: self.set_dim_level_async(dimLevel, channelIndex)) async def set_dim_level_async(self, dimLevel=0.0, channelIndex=1): data = {"channelIndex": channelIndex, "deviceId": self.id, "dimLevel": dimLevel} return await self._rest_call_async("device/control/setDimLevel", data) class PluggableDimmer(Dimmer): """HMIP-PDT Pluggable Dimmer""" class BrandDimmer(Dimmer): """HMIP-BDT Brand Dimmer""" class FullFlushDimmer(Dimmer): """HMIP-FDT Dimming Actuator flush-mount""" class WiredDimmer3(Dimmer): """HMIPW-DRD3 (Homematic IP Wired Dimming Actuator – 3x channels)""" class DinRailDimmer3(Dimmer): """HMIP-DRDI3 (Dimming Actuator Inbound 230V – 3x channels, 200W per channel) electrical DIN rail""" def __init__(self, connection): super().__init__(connection) self.c1dimLevel = 0.0 self.c2dimLevel = 0.0 self.c3dimLevel = 0.0 def from_json(self, js): super().from_json(js) channels = get_functional_channels("MULTI_MODE_INPUT_DIMMER_CHANNEL", js) if channels: self.c1dimLevel = channels[0]["dimLevel"] self.dimLevel = self.c1dimLevel self.c2dimLevel = channels[1]["dimLevel"] self.c3dimLevel = channels[2]["dimLevel"] def __str__(self): return f"{super().__str__()} c1DimLevel({self.c1dimLevel}) c2DimLevel({self.c2dimLevel}) c3DimLevel({self.c3dimLevel})" class WeatherSensor(Device): """HMIP-SWO-B""" def __init__(self, connection): super().__init__(connection) self.actualTemperature = 0 self.humidity = 0 self.illumination = 0 self.illuminationThresholdSunshine = 0 self.storm = False self.sunshine = False self.todaySunshineDuration = 0 self.totalSunshineDuration = 0 self.windSpeed = 0 self.windValueType = WindValueType.AVERAGE_VALUE self.yesterdaySunshineDuration = 0 self.vaporAmount = 0.0 def from_json(self, js): super().from_json(js) c = get_functional_channel("WEATHER_SENSOR_CHANNEL", js) if c: self.actualTemperature = c["actualTemperature"] self.humidity = c["humidity"] self.illumination = c["illumination"] self.illuminationThresholdSunshine = c["illuminationThresholdSunshine"] self.storm = c["storm"] self.sunshine = c["sunshine"] self.todaySunshineDuration = c["todaySunshineDuration"] self.totalSunshineDuration = c["totalSunshineDuration"] self.windSpeed = c["windSpeed"] self.windValueType = WindValueType.from_str(c["windValueType"]) self.yesterdaySunshineDuration = c["yesterdaySunshineDuration"] self.vaporAmount = c["vaporAmount"] def __str__(self): return ( f"{super().__str__()} actualTemperature({self.actualTemperature}) humidity({self.humidity}) vaporAmount({self.vaporAmount}) illumination({self.illumination}) illuminationThresholdSunshine({self.illuminationThresholdSunshine}) storm({self.storm}) sunshine({self.sunshine}) " f"todaySunshineDuration({self.todaySunshineDuration}) totalSunshineDuration({self.totalSunshineDuration}) " f"windSpeed({self.windSpeed}) windValueType({self.windValueType}) " f"yesterdaySunshineDuration({self.yesterdaySunshineDuration})" ) class WeatherSensorPlus(Device): """HMIP-SWO-PL""" def __init__(self, connection): super().__init__(connection) self.actualTemperature = 0 self.humidity = 0 self.illumination = 0 self.illuminationThresholdSunshine = 0 self.raining = False self.storm = False self.sunshine = False self.todayRainCounter = 0 self.todaySunshineDuration = 0 self.totalRainCounter = 0 self.totalSunshineDuration = 0 self.windSpeed = 0 self.windValueType = WindValueType.AVERAGE_VALUE self.yesterdayRainCounter = 0 self.yesterdaySunshineDuration = 0 self.vaporAmount = 0.0 def from_json(self, js): super().from_json(js) c = get_functional_channel("WEATHER_SENSOR_PLUS_CHANNEL", js) if c: self.actualTemperature = c["actualTemperature"] self.humidity = c["humidity"] self.illumination = c["illumination"] self.illuminationThresholdSunshine = c["illuminationThresholdSunshine"] self.raining = c["raining"] self.storm = c["storm"] self.sunshine = c["sunshine"] self.todayRainCounter = c["todayRainCounter"] self.todaySunshineDuration = c["todaySunshineDuration"] self.totalRainCounter = c["totalRainCounter"] self.totalSunshineDuration = c["totalSunshineDuration"] self.windSpeed = c["windSpeed"] self.windValueType = WindValueType.from_str(c["windValueType"]) self.yesterdayRainCounter = c["yesterdayRainCounter"] self.yesterdaySunshineDuration = c["yesterdaySunshineDuration"] self.vaporAmount = c["vaporAmount"] def __str__(self): return ( f"{super().__str__()} actualTemperature({self.actualTemperature}) humidity({self.humidity}) vaporAmount({self.vaporAmount}) illumination({self.illumination}) illuminationThresholdSunshine({self.illuminationThresholdSunshine}) raining({self.raining}) storm({self.storm}) sunshine({self.sunshine}) " f"todayRainCounter({self.todayRainCounter}) todaySunshineDuration({self.todaySunshineDuration}) totalRainCounter({self.totalRainCounter}) totalSunshineDuration({self.totalSunshineDuration}) " f"windSpeed({self.windSpeed}) windValueType({self.windValueType}) yesterdayRainCounter({self.yesterdayRainCounter}) yesterdaySunshineDuration({self.yesterdaySunshineDuration})" ) class WeatherSensorPro(Device): """HMIP-SWO-PR""" def __init__(self, connection): super().__init__(connection) self.actualTemperature = 0 self.humidity = 0 self.illumination = 0 self.illuminationThresholdSunshine = 0 self.raining = False self.storm = False self.sunshine = False self.todayRainCounter = 0 self.todaySunshineDuration = 0 self.totalRainCounter = 0 self.totalSunshineDuration = 0 self.weathervaneAlignmentNeeded = False self.windDirection = 0 self.windDirectionVariation = 0 self.windSpeed = 0 self.windValueType = WindValueType.AVERAGE_VALUE self.yesterdayRainCounter = 0 self.yesterdaySunshineDuration = 0 self.vaporAmount = 0.0 def from_json(self, js): super().from_json(js) c = get_functional_channel("WEATHER_SENSOR_PRO_CHANNEL", js) if c: self.actualTemperature = c["actualTemperature"] self.humidity = c["humidity"] self.illumination = c["illumination"] self.illuminationThresholdSunshine = c["illuminationThresholdSunshine"] self.raining = c["raining"] self.storm = c["storm"] self.sunshine = c["sunshine"] self.todayRainCounter = c["todayRainCounter"] self.todaySunshineDuration = c["todaySunshineDuration"] self.totalRainCounter = c["totalRainCounter"] self.totalSunshineDuration = c["totalSunshineDuration"] self.weathervaneAlignmentNeeded = c["weathervaneAlignmentNeeded"] self.windDirection = c["windDirection"] self.windDirectionVariation = c["windDirectionVariation"] self.windSpeed = c["windSpeed"] self.windValueType = WindValueType.from_str(c["windValueType"]) self.yesterdayRainCounter = c["yesterdayRainCounter"] self.yesterdaySunshineDuration = c["yesterdaySunshineDuration"] self.vaporAmount = c["vaporAmount"] def __str__(self): return ( f"{super().__str__()} actualTemperature({self.actualTemperature}) humidity({self.humidity}) vaporAmount({self.vaporAmount}) illumination({self.illumination}) illuminationThresholdSunshine({self.illuminationThresholdSunshine}) raining({self.raining}) storm({self.storm}) sunshine({self.sunshine}) " f"todayRainCounter({self.todayRainCounter}) todaySunshineDuration({self.todaySunshineDuration}) totalRainCounter({self.totalRainCounter}) totalSunshineDuration({self.totalSunshineDuration}) " f"weathervaneAlignmentNeeded({self.weathervaneAlignmentNeeded}) windDirection({self.windDirection}) windDirectionVariation({self.windDirectionVariation}) windSpeed({self.windSpeed}) windValueType({self.windValueType}) " f"yesterdayRainCounter({self.yesterdayRainCounter}) yesterdaySunshineDuration({self.yesterdaySunshineDuration})" ) # Any set/calibration functions? class WaterSensor(Device): """HMIP-SWD ( Water Sensor )""" def __init__(self, connection): super().__init__(connection) self.incorrectPositioned = False self.acousticAlarmSignal = AcousticAlarmSignal.DISABLE_ACOUSTIC_SIGNAL self.acousticAlarmTiming = AcousticAlarmTiming.PERMANENT self.acousticWaterAlarmTrigger = WaterAlarmTrigger.NO_ALARM self.inAppWaterAlarmTrigger = WaterAlarmTrigger.NO_ALARM self.moistureDetected = False self.sirenWaterAlarmTrigger = WaterAlarmTrigger.NO_ALARM self.waterlevelDetected = False self._baseChannel = "DEVICE_INCORRECT_POSITIONED" def from_json(self, js): super().from_json(js) c = get_functional_channel("DEVICE_INCORRECT_POSITIONED", js) if c: self.incorrectPositioned = c["incorrectPositioned"] c = get_functional_channel("WATER_SENSOR_CHANNEL", js) if c: self.acousticAlarmSignal = AcousticAlarmSignal.from_str( c["acousticAlarmSignal"] ) self.acousticAlarmTiming = AcousticAlarmTiming.from_str( c["acousticAlarmTiming"] ) self.acousticWaterAlarmTrigger = WaterAlarmTrigger.from_str( c["acousticWaterAlarmTrigger"] ) self.inAppWaterAlarmTrigger = WaterAlarmTrigger.from_str( c["inAppWaterAlarmTrigger"] ) self.moistureDetected = c["moistureDetected"] self.sirenWaterAlarmTrigger = WaterAlarmTrigger.from_str( c["sirenWaterAlarmTrigger"] ) self.waterlevelDetected = c["waterlevelDetected"] def __str__(self): return ( f"{super().__str__()} incorrectPositioned({self.incorrectPositioned}) acousticAlarmSignal({self.acousticAlarmSignal}) acousticAlarmTiming({self.acousticAlarmTiming}) acousticWaterAlarmTrigger({self.acousticWaterAlarmTrigger})" f" inAppWaterAlarmTrigger({self.inAppWaterAlarmTrigger}) moistureDetected({self.moistureDetected}) sirenWaterAlarmTrigger({self.sirenWaterAlarmTrigger}) waterlevelDetected({self.waterlevelDetected})" ) def set_acoustic_alarm_signal(self, acousticAlarmSignal: AcousticAlarmSignal): return self._run_non_async(lambda: self.set_acoustic_alarm_signal_async(acousticAlarmSignal) ) async def set_acoustic_alarm_signal_async(self, acousticAlarmSignal: AcousticAlarmSignal): data = { "channelIndex": 1, "deviceId": self.id, "acousticAlarmSignal": str(acousticAlarmSignal), } return await self._rest_call_async( "device/configuration/setAcousticAlarmSignal", data ) def set_acoustic_alarm_timing(self, acousticAlarmTiming: AcousticAlarmTiming): return self._run_non_async(lambda: self.set_acoustic_alarm_timing_async(acousticAlarmTiming) ) async def set_acoustic_alarm_timing_async(self, acousticAlarmTiming: AcousticAlarmTiming): data = { "channelIndex": 1, "deviceId": self.id, "acousticAlarmTiming": str(acousticAlarmTiming), } return await self._rest_call_async( "device/configuration/setAcousticAlarmTiming", data ) def set_acoustic_water_alarm_trigger( self, acousticWaterAlarmTrigger: WaterAlarmTrigger ): return self._run_non_async(lambda: self.set_acoustic_water_alarm_trigger_async(acousticWaterAlarmTrigger) ) async def set_acoustic_water_alarm_trigger_async( self, acousticWaterAlarmTrigger: WaterAlarmTrigger ): data = { "channelIndex": 1, "deviceId": self.id, "acousticWaterAlarmTrigger": str(acousticWaterAlarmTrigger), } return await self._rest_call_async("device/configuration/setAcousticWaterAlarmTrigger", data) def set_inapp_water_alarm_trigger(self, inAppWaterAlarmTrigger: WaterAlarmTrigger): return self._run_non_async(lambda: self.set_inapp_water_alarm_trigger_async(inAppWaterAlarmTrigger) ) async def set_inapp_water_alarm_trigger_async(self, inAppWaterAlarmTrigger: WaterAlarmTrigger): data = { "channelIndex": 1, "deviceId": self.id, "inAppWaterAlarmTrigger": str(inAppWaterAlarmTrigger), } return await self._rest_call_async( "device/configuration/setInAppWaterAlarmTrigger", data ) def set_siren_water_alarm_trigger(self, sirenWaterAlarmTrigger: WaterAlarmTrigger): return self._run_non_async(lambda: self.set_siren_water_alarm_trigger_async(sirenWaterAlarmTrigger) ) async def set_siren_water_alarm_trigger_async(self, sirenWaterAlarmTrigger: WaterAlarmTrigger): LOGGER.warning( "set_siren_water_alarm_trigger is currently not available in the HMIP App. It might not be available in the cloud yet" ) data = { "channelIndex": 1, "deviceId": self.id, "sirenWaterAlarmTrigger": str(sirenWaterAlarmTrigger), } return await self._rest_call_async( "device/configuration/setSirenWaterAlarmTrigger", data ) class FullFlushContactInterface(Device): """HMIP-FCI1 (Contact Interface flush-mount – 1 channel)""" def __init__(self, connection): super().__init__(connection) self.binaryBehaviorType = BinaryBehaviorType.NORMALLY_OPEN self.multiModeInputMode = MultiModeInputMode.BINARY_BEHAVIOR self.windowState = WindowState.OPEN def from_json(self, js): super().from_json(js) c = get_functional_channel("MULTI_MODE_INPUT_CHANNEL", js) if c: self.binaryBehaviorType = BinaryBehaviorType.from_str( c["binaryBehaviorType"] ) self.multiModeInputMode = MultiModeInputMode.from_str( c["multiModeInputMode"] ) self.windowState = WindowState.from_str(c["windowState"]) def __str__(self): return ( f"{super().__str__()} binaryBehaviorType({self.binaryBehaviorType}) multiModeInputMode({self.multiModeInputMode}) windowState({self.windowState})" ) class FullFlushContactInterface6(Device): """HMIP-FCI6 (Contact Interface flush-mount – 6 channels)""" class WiredInput32(FullFlushContactInterface): """HMIPW-DRI32 (Homematic IP Wired Inbound module – 32x channels)""" class FullFlushInputSwitch(Switch): """HMIP-FSI16 (Switch Actuator with Push-button Input 230V, 16A)""" def __init__(self, connection): super().__init__(connection) self.binaryBehaviorType = BinaryBehaviorType.NORMALLY_OPEN self.multiModeInputMode = MultiModeInputMode.SWITCH_BEHAVIOR self.on = False self.profileMode = ProfileMode.MANUAL self.userDesiredProfileMode = ProfileMode.MANUAL def from_json(self, js): super().from_json(js) c = get_functional_channel("MULTI_MODE_INPUT_SWITCH_CHANNEL", js) if c: self.set_attr_from_dict("binaryBehaviorType", c, BinaryBehaviorType) self.set_attr_from_dict("multiModeInputMode", c, MultiModeInputMode) self.set_attr_from_dict("on", c) self.set_attr_from_dict("profileMode", c, ProfileMode) self.set_attr_from_dict("userDesiredProfileMode", c, ProfileMode) class DinRailSwitch(FullFlushInputSwitch): """HMIP-DRSI1 (Switch Actuator for DIN rail mount – 1x channel)""" class AccelerationSensor(Device): """HMIP-SAM (Contact Interface flush-mount – 1 channel)""" def __init__(self, connection): super().__init__(connection) #:float: self.accelerationSensorEventFilterPeriod = 100.0 #:AccelerationSensorMode: self.accelerationSensorMode = AccelerationSensorMode.ANY_MOTION #:AccelerationSensorNeutralPosition: self.accelerationSensorNeutralPosition = ( AccelerationSensorNeutralPosition.HORIZONTAL ) #:AccelerationSensorSensitivity: self.accelerationSensorSensitivity = ( AccelerationSensorSensitivity.SENSOR_RANGE_2G ) #:int: self.accelerationSensorTriggerAngle = 0 #:bool: self.accelerationSensorTriggered = False #:NotificationSoundType: self.notificationSoundTypeHighToLow = NotificationSoundType.SOUND_NO_SOUND #:NotificationSoundType: self.notificationSoundTypeLowToHigh = NotificationSoundType.SOUND_NO_SOUND def from_json(self, js): super().from_json(js) c = get_functional_channel("ACCELERATION_SENSOR_CHANNEL", js) if c: self.set_attr_from_dict("accelerationSensorEventFilterPeriod", c) self.set_attr_from_dict("accelerationSensorMode", c, AccelerationSensorMode) self.set_attr_from_dict( "accelerationSensorNeutralPosition", c, AccelerationSensorNeutralPosition, ) self.set_attr_from_dict( "accelerationSensorSensitivity", c, AccelerationSensorSensitivity ) self.set_attr_from_dict("accelerationSensorTriggerAngle", c) self.set_attr_from_dict("accelerationSensorTriggered", c) self.set_attr_from_dict( "notificationSoundTypeHighToLow", c, NotificationSoundType ) self.set_attr_from_dict( "notificationSoundTypeLowToHigh", c, NotificationSoundType ) def set_acceleration_sensor_mode( self, mode: AccelerationSensorMode, channelIndex=1 ): return self._run_non_async(lambda: self.set_acceleration_sensor_mode_async(mode, channelIndex) ) async def set_acceleration_sensor_mode_async( self, mode: AccelerationSensorMode, channelIndex=1 ): data = { "channelIndex": channelIndex, "deviceId": self.id, "accelerationSensorMode": str(mode), } return await self._rest_call_async( "device/configuration/setAccelerationSensorMode", data ) def set_acceleration_sensor_neutral_position( self, neutralPosition: AccelerationSensorNeutralPosition, channelIndex=1 ): return self._run_non_async(lambda: self.set_acceleration_sensor_neutral_position_async(neutralPosition, channelIndex) ) async def set_acceleration_sensor_neutral_position_async( self, neutralPosition: AccelerationSensorNeutralPosition, channelIndex=1 ): data = { "channelIndex": channelIndex, "deviceId": self.id, "accelerationSensorNeutralPosition": str(neutralPosition), } return await self._rest_call_async( "device/configuration/setAccelerationSensorNeutralPosition", data, ) def set_acceleration_sensor_sensitivity( self, sensitivity: AccelerationSensorSensitivity, channelIndex=1 ): return self._run_non_async(lambda: self.set_acceleration_sensor_sensitivity_async(sensitivity, channelIndex) ) async def set_acceleration_sensor_sensitivity_async( self, sensitivity: AccelerationSensorSensitivity, channelIndex=1 ): data = { "channelIndex": channelIndex, "deviceId": self.id, "accelerationSensorSensitivity": str(sensitivity), } return await self._rest_call_async( "device/configuration/setAccelerationSensorSensitivity", data ) def set_acceleration_sensor_trigger_angle(self, angle: int, channelIndex=1): return self._run_non_async(lambda: self.set_acceleration_sensor_trigger_angle_async(angle, channelIndex) ) async def set_acceleration_sensor_trigger_angle_async(self, angle: int, channelIndex=1): data = { "channelIndex": channelIndex, "deviceId": self.id, "accelerationSensorTriggerAngle": angle, } return await self._rest_call_async( "device/configuration/setAccelerationSensorTriggerAngle", data ) def set_acceleration_sensor_event_filter_period( self, period: float, channelIndex=1 ): return self._run_non_async(lambda: self.set_acceleration_sensor_event_filter_period_async(period, channelIndex) ) async def set_acceleration_sensor_event_filter_period_async( self, period: float, channelIndex=1 ): data = { "channelIndex": channelIndex, "deviceId": self.id, "accelerationSensorEventFilterPeriod": period, } return await self._rest_call_async( "device/configuration/setAccelerationSensorEventFilterPeriod", data, ) def set_notification_sound_type( self, soundType: NotificationSoundType, isHighToLow: bool, channelIndex=1 ): return self._run_non_async(lambda: self.set_notification_sound_type_async(soundType, isHighToLow, channelIndex) ) async def set_notification_sound_type_async( self, soundType: NotificationSoundType, isHighToLow: bool, channelIndex=1 ): data = { "channelIndex": channelIndex, "deviceId": self.id, "notificationSoundType": str(soundType), "isHighToLow": isHighToLow, } return await self._rest_call_async( "device/configuration/setNotificationSoundType", data ) class DoorModule(Device): """Generic class for a door module""" def __init__(self, connection): super().__init__(connection) self.doorState = DoorState.POSITION_UNKNOWN self.on = False self.processing = False self.ventilationPositionSupported = False def from_json(self, js): super().from_json(js) c = get_functional_channel("DOOR_CHANNEL", js) if c: self.set_attr_from_dict("doorState", c, DoorState) self.set_attr_from_dict("on", c) self.set_attr_from_dict("processing", c) self.set_attr_from_dict("ventilationPositionSupported", c) def send_door_command(self, doorCommand=DoorCommand.STOP): logging.warning("function is deprecated, use send_door_command_async instead") return self._run_non_async(lambda: self.send_door_command_async(doorCommand)) async def send_door_command_async(self, doorCommand=DoorCommand.STOP): logging.warning("function is deprecated, use send_door_command_async instead") data = {"channelIndex": 1, "deviceId": self.id, "doorCommand": str(doorCommand)} return await self._rest_call_async("device/control/sendDoorCommand", data) class GarageDoorModuleTormatic(DoorModule): """HMIP-MOD-TM (Garage Door Module Tormatic)""" class HoermannDrivesModule(DoorModule): """HMIP-MOD-HO (Garage Door Module for Hörmann)""" class PluggableMainsFailureSurveillance(Device): """HMIP-PMFS (Plugable Power Supply Monitoring)""" def __init__(self, connection): super().__init__(connection) self.powerMainsFailure = False self.genericAlarmSignal = AlarmSignalType.NO_ALARM def from_json(self, js): super().from_json(js) c = get_functional_channel("MAINS_FAILURE_CHANNEL", js) if c: self.set_attr_from_dict("powerMainsFailure", c) self.set_attr_from_dict("genericAlarmSignal", c, AlarmSignalType) class WallMountedGarageDoorController(Device): """HmIP-WGC Wall mounted Garage Door Controller""" def __init__(self, connection): super().__init__(connection) self.impulseDuration = 0 self.processing = False def from_json(self, js): super().from_json(js) c = get_functional_channel("IMPULSE_OUTPUT_CHANNEL", js) if c: self.set_attr_from_dict("impulseDuration", c) self.set_attr_from_dict("processing", c) def send_start_impulse(self, channelIndex=2): """Toggle Wall mounted Garage Door Controller.""" return self._run_non_async(lambda: self.send_start_impulse_async(channelIndex)) async def send_start_impulse_async(self, channelIndex=2): """Toggle Wall mounted Garage Door Controller.""" data = {"channelIndex": channelIndex, "deviceId": self.id} return await self._rest_call_async("device/control/startImpulse", body=data) class FullFlushLockController(Device): """HmIP-FLC Universal lock controller.""" def _get_channel_by_role( self, channel_type: FunctionalChannelType | str, channel_role: str ) -> FunctionalChannel | None: for channel in self.functionalChannels: if isinstance(channel_type, str): expected_type = FunctionalChannelType.from_str(channel_type, channel_type) else: expected_type = channel_type if channel.functionalChannelType != expected_type: continue if channel.channelRole != channel_role: continue return channel return None @property def lockState(self): channel = self._get_channel_by_role( FunctionalChannelType.MULTI_MODE_LOCK_INPUT_CHANNEL, "DOOR_LOCK_SENSOR" ) return channel.lockState if channel else None @property def glassBroken(self): channel = self._get_channel_by_role( FunctionalChannelType.MULTI_MODE_LOCK_INPUT_CHANNEL, "DOOR_LOCK_SENSOR" ) return channel.glassBroken if channel else None @property def doorLockActive(self): channel = self._get_channel_by_role( FunctionalChannelType.DOOR_SWITCH_CHANNEL, "DOOR_LOCK_ACTUATOR" ) return channel.doorLockActive if channel else None @property def impulseDuration(self): channel = self._get_channel_by_role( FunctionalChannelType.DOOR_SWITCH_CHANNEL, "DOOR_OPENER_ACTUATOR" ) return channel.impulseDuration if channel else None @property def processing(self): channel = self._get_channel_by_role( FunctionalChannelType.DOOR_SWITCH_CHANNEL, "DOOR_OPENER_ACTUATOR" ) return channel.processing if channel else None def send_start_impulse(self): channel = self._get_channel_by_role( FunctionalChannelType.DOOR_SWITCH_CHANNEL, "DOOR_OPENER_ACTUATOR" ) if channel is None: raise AttributeError("DOOR_SWITCH_CHANNEL with DOOR_OPENER_ACTUATOR not loaded for device") return channel.send_start_impulse() async def send_start_impulse_async(self): channel = self._get_channel_by_role( FunctionalChannelType.DOOR_SWITCH_CHANNEL, "DOOR_OPENER_ACTUATOR" ) if channel is None: raise AttributeError("DOOR_SWITCH_CHANNEL with DOOR_OPENER_ACTUATOR not loaded for device") return await channel.async_send_start_impulse() class FullFlushDoorController(Device): """HmIP-FDC Full Flush Door Controller (door opener). Provides an impulse-driven door opener output via DOOR_SWITCH_CHANNEL (role DOOR_OPENER_ACTUATOR). Two MULTI_MODE_LOCK_INPUT_CHANNEL inputs and a set of ACCESS_AUTHORIZATION_CHANNEL channels for keypad/code access are exposed via the standard `functionalChannels` collection. """ def _get_channel_by_role( self, channel_type: FunctionalChannelType | str, channel_role: str ) -> FunctionalChannel | None: for channel in self.functionalChannels: if isinstance(channel_type, str): expected_type = FunctionalChannelType.from_str(channel_type, channel_type) else: expected_type = channel_type if channel.functionalChannelType != expected_type: continue if channel.channelRole != channel_role: continue return channel return None @property def impulseDuration(self): channel = self._get_channel_by_role( FunctionalChannelType.DOOR_SWITCH_CHANNEL, "DOOR_OPENER_ACTUATOR" ) return channel.impulseDuration if channel else None @property def processing(self): channel = self._get_channel_by_role( FunctionalChannelType.DOOR_SWITCH_CHANNEL, "DOOR_OPENER_ACTUATOR" ) return channel.processing if channel else None def send_start_impulse(self): channel = self._get_channel_by_role( FunctionalChannelType.DOOR_SWITCH_CHANNEL, "DOOR_OPENER_ACTUATOR" ) if channel is None: raise AttributeError("DOOR_SWITCH_CHANNEL with DOOR_OPENER_ACTUATOR not loaded for device") return channel.send_start_impulse() async def send_start_impulse_async(self): channel = self._get_channel_by_role( FunctionalChannelType.DOOR_SWITCH_CHANNEL, "DOOR_OPENER_ACTUATOR" ) if channel is None: raise AttributeError("DOOR_SWITCH_CHANNEL with DOOR_OPENER_ACTUATOR not loaded for device") return await channel.async_send_start_impulse() class TiltVibrationSensor(Device): """HMIP-STV (Inclination and vibration Sensor)""" def __init__(self, connection): super().__init__(connection) #:float: self.accelerationSensorEventFilterPeriod = 100.0 #:AccelerationSensorMode: self.accelerationSensorMode = AccelerationSensorMode.ANY_MOTION #:AccelerationSensorSensitivity: self.accelerationSensorSensitivity = ( AccelerationSensorSensitivity.SENSOR_RANGE_2G ) #:int: self.accelerationSensorTriggerAngle = 0 #:bool: self.accelerationSensorTriggered = False def from_json(self, js): super().from_json(js) c = get_functional_channel("TILT_VIBRATION_SENSOR_CHANNEL", js) if c: self.set_attr_from_dict("accelerationSensorEventFilterPeriod", c) self.set_attr_from_dict("accelerationSensorMode", c, AccelerationSensorMode) self.set_attr_from_dict( "accelerationSensorSensitivity", c, AccelerationSensorSensitivity ) self.set_attr_from_dict("accelerationSensorTriggerAngle", c) self.set_attr_from_dict("accelerationSensorTriggered", c) def set_acceleration_sensor_mode( self, mode: AccelerationSensorMode, channelIndex=1 ): return self._run_non_async( self.set_acceleration_sensor_mode_async, mode, channelIndex ) async def set_acceleration_sensor_mode_async( self, mode: AccelerationSensorMode, channelIndex=1 ): data = { "channelIndex": channelIndex, "deviceId": self.id, "accelerationSensorMode": str(mode), } return await self._rest_call_async( "device/configuration/setAccelerationSensorMode", data ) def set_acceleration_sensor_sensitivity( self, sensitivity: AccelerationSensorSensitivity, channelIndex=1 ): return self._run_non_async( self.set_acceleration_sensor_sensitivity_async, sensitivity, channelIndex ) async def set_acceleration_sensor_sensitivity_async( self, sensitivity: AccelerationSensorSensitivity, channelIndex=1 ): data = { "channelIndex": channelIndex, "deviceId": self.id, "accelerationSensorSensitivity": str(sensitivity), } return await self._rest_call_async( "device/configuration/setAccelerationSensorSensitivity", data ) def set_acceleration_sensor_trigger_angle(self, angle: int, channelIndex=1): return self._run_non_async( self.set_acceleration_sensor_trigger_angle_async, angle, channelIndex ) async def set_acceleration_sensor_trigger_angle_async(self, angle: int, channelIndex=1): data = { "channelIndex": channelIndex, "deviceId": self.id, "accelerationSensorTriggerAngle": angle, } return await self._rest_call_async( "device/configuration/setAccelerationSensorTriggerAngle", data ) def set_acceleration_sensor_event_filter_period( self, period: float, channelIndex=1 ): return self._run_non_async( self.set_acceleration_sensor_event_filter_period_async, period, channelIndex ) async def set_acceleration_sensor_event_filter_period_async( self, period: float, channelIndex=1 ): data = { "channelIndex": channelIndex, "deviceId": self.id, "accelerationSensorEventFilterPeriod": period, } return await self._rest_call_async( "device/configuration/setAccelerationSensorEventFilterPeriod", data, ) class RainSensor(Device): """HMIP-SRD (Rain Sensor)""" def __init__(self, connection): super().__init__(connection) #:bool: self.raining = False #:float: self.rainSensorSensitivity = 0.0 def from_json(self, js): super().from_json(js) c = get_functional_channel("RAIN_DETECTION_CHANNEL", js) if c: self.set_attr_from_dict("rainSensorSensitivity", c) self.set_attr_from_dict("raining", c) class TemperatureDifferenceSensor2(Device): """HmIP-STE2-PCB (Temperature Difference Sensors - 2x sensors)""" def __init__(self, connection): super().__init__(connection) #:float: self.temperatureExternalDelta = 0.0 #:float: self.temperatureExternalOne = 0.0 #:float: self.temperatureExternalTwo = 0.0 def from_json(self, js): super().from_json(js) c = get_functional_channel("TEMPERATURE_SENSOR_2_EXTERNAL_DELTA_CHANNEL", js) if c: self.set_attr_from_dict("temperatureExternalDelta", c) self.set_attr_from_dict("temperatureExternalOne", c) self.set_attr_from_dict("temperatureExternalTwo", c) class ParticulateMatterSensor(Device): """HmIP-SFD (Fine Dust Sensor)""" def __init__(self, connection): super().__init__(connection) self.actualTemperature = None self.humidity = None self.particulateMassConcentrationTen = None self.particulateMassConcentrationTwoPointFive = None self.particulateMassConcentrationOne = None def from_json(self, js): super().from_json(js) c = get_functional_channel("PARTICULATE_MATTER_SENSOR_CHANNEL", js) if c: self.set_attr_from_dict("actualTemperature", c) self.set_attr_from_dict("humidity", c) self.set_attr_from_dict("particulateMassConcentrationTen", c) self.set_attr_from_dict("particulateMassConcentrationTwoPointFive", c) self.set_attr_from_dict("particulateMassConcentrationOne", c) class DoorLockDrive(OperationLockableDevice): """HmIP-DLD""" def __init__(self, connection): super().__init__(connection) self.autoRelockDelay = False self.doorHandleType = "UNKNOWN" self.doorLockDirection = False self.doorLockNeutralPosition = False self.doorLockTurns = False self.lockState = LockState.UNLOCKED self.motorState = MotorState.STOPPED self.door_lock_channel = 1 def from_json(self, js): super().from_json(js) c = get_functional_channel("DOOR_LOCK_CHANNEL", js) if c: self.set_attr_from_dict("autoRelockDelay", c) self.set_attr_from_dict("doorHandleType", c) self.set_attr_from_dict("doorLockDirection", c) self.set_attr_from_dict("doorLockNeutralPosition", c) self.set_attr_from_dict("doorLockTurns", c) self.set_attr_from_dict("lockState", c, LockState) self.set_attr_from_dict("motorState", c, MotorState) self.door_lock_channel = c["index"] def set_lock_state(self, doorLockState: LockState, pin="", channelIndex=1): """sets the door lock state Args: doorLockState(float): the state of the door. See LockState from base/enums.py pin(string): Pin, if specified. channelIndex(int): the channel to control. Normally the channel from DOOR_LOCK_CHANNEL is used. Returns: the result of the _restCall """ return self._run_non_async(self.set_lock_state_async, doorLockState, pin, channelIndex) async def set_lock_state_async(self, doorLockState: LockState, pin="", channelIndex=1): """sets the door lock state Args: doorLockState(float): the state of the door. See LockState from base/enums.py pin(string): Pin, if specified. channelIndex(int): the channel to control. Normally the channel from DOOR_LOCK_CHANNEL is used. Returns: the result of the _restCall """ if channelIndex == 1: channelIndex = self.door_lock_channel data = { "deviceId": self.id, "channelIndex": channelIndex, "authorizationPin": pin, "targetLockState": doorLockState, } return await self._rest_call_async("device/control/setLockState", data) class DoorLockDrivePro(DoorLockDrive): """HmIP-DLP""" def __init__(self, connection): super().__init__(connection) self.autoRelockEnabled = False self.doorOpeningDirection = "" self.lockSilenceMode = "" self.lockStateChangeReason = "" def from_json(self, js): # Skip DoorLockDrive.from_json (looks for DOOR_LOCK_CHANNEL) # and call its parent directly OperationLockableDevice.from_json(self, js) c = get_functional_channel("DOOR_LOCK_PRO_CHANNEL", js) if c: self.set_attr_from_dict("autoRelockDelay", c) self.set_attr_from_dict("doorHandleType", c) self.set_attr_from_dict("doorLockDirection", c) self.set_attr_from_dict("doorLockNeutralPosition", c) self.set_attr_from_dict("doorLockTurns", c) self.set_attr_from_dict("lockState", c, LockState) self.set_attr_from_dict("motorState", c, MotorState) self.door_lock_channel = c["index"] self.set_attr_from_dict("autoRelockEnabled", c) self.set_attr_from_dict("doorOpeningDirection", c) self.set_attr_from_dict("lockSilenceMode", c) self.set_attr_from_dict("lockStateChangeReason", c) class DoorLockSensor(Device): """HmIP-DLS""" def __init__(self, connection): super().__init__(connection) self.door_lock_channel = None self.doorLockDirection = "" self.doorLockNeutralPosition = "" self.doorLockTurns = 0 self.lockState = LockState.OPEN def from_json(self, js): super().from_json(js) c = get_functional_channel("DOOR_LOCK_SENSOR_CHANNEL", js) if c: self.set_attr_from_dict("doorLockDirection", c) self.set_attr_from_dict("doorLockNeutralPosition", c) self.set_attr_from_dict("doorLockTurns", c) self.set_attr_from_dict("lockState", c, LockState) self.door_lock_channel = c["index"] class EnergySensorsInterface(Device): """HmIP-ESI""" def __init__(self, connection): super().__init__(connection) class DaliGateway(Device): """HmIP-DRG-DALI Dali Gateway device.""" class MotionDetectorSwitchOutdoor(SabotageDevice): """HmIP-SMO230 (Motion Detector with Brightness Sensor and Switch - outdoor)""" def __init__(self, connection): super().__init__(connection) def _get_motion_detection_channel(self): return self.get_functional_channel(FunctionalChannelType.MOTION_DETECTION_CHANNEL) def _get_switch_channel(self): return self.get_functional_channel(FunctionalChannelType.SWITCH_CHANNEL) @property def currentIllumination(self): channel = self._get_motion_detection_channel() return channel.currentIllumination if channel else None @property def motionDetected(self): channel = self._get_motion_detection_channel() return channel.motionDetected if channel else None @property def illumination(self): channel = self._get_motion_detection_channel() return channel.illumination if channel else None @property def motionBufferActive(self): channel = self._get_motion_detection_channel() return channel.motionBufferActive if channel else False @property def motionDetectionSendInterval(self): channel = self._get_motion_detection_channel() return channel.motionDetectionSendInterval if channel else MotionDetectionSendInterval.SECONDS_30 @property def numberOfBrightnessMeasurements(self): channel = self._get_motion_detection_channel() return channel.numberOfBrightnessMeasurements if channel else 0 @property def on(self): channel = self._get_switch_channel() return channel.on if channel else None @property def profileMode(self): channel = self._get_switch_channel() return channel.profileMode if channel else None @property def userDesiredProfileMode(self): channel = self._get_switch_channel() return channel.userDesiredProfileMode if channel else None def __str__(self): return f"{super().__str__()} motionDetected({self.motionDetected}) illumination({self.illumination}) on({self.on})" def set_switch_state(self, on=True): channel = self._get_switch_channel() if channel is None: raise AttributeError("SWITCH_CHANNEL not loaded for device") return channel.set_switch_state(on) async def set_switch_state_async(self, on=True): channel = self._get_switch_channel() if channel is None: raise AttributeError("SWITCH_CHANNEL not loaded for device") return await channel.async_set_switch_state(on) def turn_on(self): return self.set_switch_state(True) async def turn_on_async(self): return await self.set_switch_state_async(True) def turn_off(self): return self.set_switch_state(False) async def turn_off_async(self): return await self.set_switch_state_async(False) class FullFlushWiegandInterface(Device): """HmIP-FWI (Wiegand Interface)""" def __init__(self, connection): super().__init__(connection) self.doorBellLabel = None self.sabotage = None self.blockedSabotage = None self.blockedWrongCodePermanently = None self.blockedWrongCodeTemporarily = None def from_json(self, js): super().from_json(js) c = get_functional_channel( "DEVICE_BLOCKING_WITH_TEACHABLE_CODE", js ) if c: self.set_attr_from_dict("doorBellLabel", c) self.set_attr_from_dict("sabotage", c) self.set_attr_from_dict("blockedSabotage", c) self.set_attr_from_dict("blockedWrongCodePermanently", c) self.set_attr_from_dict("blockedWrongCodeTemporarily", c) class WallMountedKeyPad(Device): pass class WateringActuator(Device): pass