From 557f882a8228a886e200eca9db183ab4c38cb936 Mon Sep 17 00:00:00 2001 From: coreGreenberet Date: Sun, 22 Dec 2019 21:16:04 +0100 Subject: [PATCH 1/4] added enum values --- homematicip/base/enums.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/homematicip/base/enums.py b/homematicip/base/enums.py index c818fb67..a9ef9016 100644 --- a/homematicip/base/enums.py +++ b/homematicip/base/enums.py @@ -200,6 +200,7 @@ class DeviceType(AutoNameEnum): DEVICE = auto() ACCELERATION_SENSOR = auto() ALARM_SIREN_INDOOR = auto() + ALARM_SIREN_OUTDOOR = auto() BRAND_BLIND = auto() BRAND_DIMMER = auto() BRAND_PUSH_BUTTON = auto() @@ -268,6 +269,7 @@ class GroupType(AutoNameEnum): HEATING_EXTERNAL_CLOCK = auto() HEATING_FAILURE_ALERT_RULE_GROUP = auto() HEATING = auto() + HOT_WATER = auto() HUMIDITY_WARNING_RULE_GROUP = auto() SECURITY_ZONE = auto() INBOX = auto() @@ -378,6 +380,7 @@ class FunctionalChannelType(AutoNameEnum): DEVICE_INCORRECT_POSITIONED = auto() DEVICE_OPERATIONLOCK = auto() DEVICE_PERMANENT_FULL_RX = auto() + DEVICE_RECHARGEABLE_WITH_SABOTAGE = auto() DEVICE_SABOTAGE = auto() DOOR_CHANNEL = auto() DIMMER_CHANNEL = auto() From 89c5b910fa29f077a690d8487d8bb903a4a96305 Mon Sep 17 00:00:00 2001 From: coreGreenberet Date: Sun, 22 Dec 2019 22:25:38 +0100 Subject: [PATCH 2/4] added support for HMIP-ASIR-O added HotWaterGroup --- CHANGELOG.md | 16 ++++-- README.rst | 2 +- homematicip/aio/class_maps.py | 2 + homematicip/aio/device.py | 2 + homematicip/aio/group.py | 7 +++ homematicip/base/enums.py | 4 ++ homematicip/base/functionalChannels.py | 12 ++++ homematicip/class_maps.py | 3 + homematicip/device.py | 24 ++++++-- homematicip/group.py | 41 ++++++++++++-- homematicip_demo/fake_cloud_server.py | 11 ++++ homematicip_demo/json_data/home.json | 78 ++++++++++++++++++++++++++ tests/aio_tests/test_async_groups.py | 17 ++++++ tests/test_groups.py | 13 +++++ 14 files changed, 214 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5423f06b..01a5d706 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,14 +6,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added -- Devices - - HMIP-MOD-TM (Garage Door Module for Novoferm and Tormatic door operators) -- API - - FunctionalChannels: DOOR_CHANNEL -- Python 3.8 support - API + - FunctionalChannels: + - DOOR_CHANNEL + - DEVICE_RECHARGEABLE_WITH_SABOTAGE - ExtendedLinkedShutterGroup.set_slats_level - added missing attributes +- Devices + - HMIP-MOD-TM (Garage Door Module for Novoferm and Tormatic door operators) + - HMIP-ASIR-O (Alarm Siren - outdoor) + +- Groups + - HOT_WATER +- Python 3.8 support + ### Changed - General - removed homematicip-testing package. Pip will automatically install the latest tagged release. For a "nightly" build you just have to run it with the "--pre" argument. diff --git a/README.rst b/README.rst index 246b9fd9..4587803e 100644 --- a/README.rst +++ b/README.rst @@ -78,7 +78,7 @@ Homematic IP Devices: - [X] HMIP-ASIR (Alarm Siren - indoor) - [X] HMIP-ASIR-B1 (Alarm Siren - indoor) *Silvercrest Edition* - [X] HMIP-ASIR-2 (Alarm Siren2 - indoor) New Version -- [ ] HMIP-ASIR-O (Alarm Siren - outdoor) +- [X] HMIP-ASIR-O (Alarm Siren - outdoor) - [X] HMIP-BBL (Blind Actuator for brand switches) - [X] HMIP-BDT (Dimming Actuator for brand switches) - [X] HMIP-BRC2 (Remote Control for brand switches – 2x channels) diff --git a/homematicip/aio/class_maps.py b/homematicip/aio/class_maps.py index 3bb75d17..6d1b67fc 100644 --- a/homematicip/aio/class_maps.py +++ b/homematicip/aio/class_maps.py @@ -8,6 +8,7 @@ DeviceType.DEVICE: AsyncDevice, DeviceType.ACCELERATION_SENSOR: AsyncAccelerationSensor, DeviceType.ALARM_SIREN_INDOOR: AsyncAlarmSirenIndoor, + DeviceType.ALARM_SIREN_OUTDOOR: AsyncAlarmSirenOutdoor, DeviceType.BRAND_BLIND: AsyncBrandBlind, DeviceType.BRAND_DIMMER: AsyncBrandDimmer, DeviceType.BRAND_SHUTTER: AsyncFullFlushShutter, @@ -81,6 +82,7 @@ GroupType.HEATING_COOLING_DEMAND_PUMP: AsyncHeatingCoolingDemandPumpGroup, GroupType.HEATING_FAILURE_ALERT_RULE_GROUP: AsyncHeatingFailureAlertRuleGroup, GroupType.HUMIDITY_WARNING_RULE_GROUP: AsyncHumidityWarningRuleGroup, + GroupType.HOT_WATER: AsyncHotWaterGroup, GroupType.SWITCHING_PROFILE: AsyncSwitchingProfileGroup, GroupType.OVER_HEAT_PROTECTION_RULE: AsyncOverHeatProtectionRule, GroupType.SMOKE_ALARM_DETECTION_RULE: AsyncSmokeAlarmDetectionRule, diff --git a/homematicip/aio/device.py b/homematicip/aio/device.py index fe62292f..1b262996 100644 --- a/homematicip/aio/device.py +++ b/homematicip/aio/device.py @@ -237,6 +237,8 @@ class AsyncRemoteControl8Module(RemoteControl8Module, AsyncRemoteControl8): class AsyncAlarmSirenIndoor(AlarmSirenIndoor, AsyncSabotageDevice): """ HMIP-ASIR (Alarm Siren) """ +class AsyncAlarmSirenOutdoor(AlarmSirenOutdoor, AsyncAlarmSirenIndoor): + """ HMIP-ASIR-O (Alarm Siren Outdoor) """ class AsyncMotionDetectorIndoor(MotionDetectorIndoor, AsyncSabotageDevice): """ HMIP-SMI (Motion Detector with Brightness Sensor - indoor) """ diff --git a/homematicip/aio/group.py b/homematicip/aio/group.py index 2932df05..ce313b2b 100644 --- a/homematicip/aio/group.py +++ b/homematicip/aio/group.py @@ -28,6 +28,7 @@ ShutterWindProtectionRule, LockOutProtectionRule, EnvironmentGroup, + HotWaterGroup, ) from homematicip.base.enums import * @@ -243,3 +244,9 @@ class AsyncLockOutProtectionRule(LockOutProtectionRule, AsyncGroup): class AsyncEnvironmentGroup(EnvironmentGroup, AsyncGroup): pass + +class AsyncHotWaterGroup(HotWaterGroup,AsyncGroup): + async def set_profile_mode(self, profileMode:ProfileMode): + return await self._connection.api_call( + *super().set_profile_mode(profileMode) + ) \ No newline at end of file diff --git a/homematicip/base/enums.py b/homematicip/base/enums.py index a9ef9016..8c767f3d 100644 --- a/homematicip/base/enums.py +++ b/homematicip/base/enums.py @@ -505,3 +505,7 @@ class GroupVisibility(AutoNameEnum): INVISIBLE_GROUP_AND_CONTROL = auto() INVISIBLE_CONTROL = auto() VISIBLE = auto() + +class ProfileMode(AutoNameEnum): + AUTOMATIC = auto() + MANUAL = auto() \ No newline at end of file diff --git a/homematicip/base/functionalChannels.py b/homematicip/base/functionalChannels.py index 9fccb172..8e363218 100644 --- a/homematicip/base/functionalChannels.py +++ b/homematicip/base/functionalChannels.py @@ -748,3 +748,15 @@ def from_json(self, js, groups: Iterable[Group]): self.set_attr_from_dict( "notificationSoundTypeLowToHigh", js, NotificationSoundType ) + +class DeviceRechargeableWithSabotage(DeviceSabotageChannel): + """ this is the representative of the DEVICE_RECHARGEABLE_WITH_SABOTAGE channel""" + + def __init__(self): + super().__init__() + #:bool:is the battery in a bad condition + self.badBatteryHealth = False + + def from_json(self, js, groups: Iterable[Group]): + super().from_json(js, groups) + self.set_attr_from_dict("badBatteryHealth",js) \ No newline at end of file diff --git a/homematicip/class_maps.py b/homematicip/class_maps.py index a4780b15..2f976dea 100644 --- a/homematicip/class_maps.py +++ b/homematicip/class_maps.py @@ -14,6 +14,7 @@ DeviceType.DEVICE: Device, DeviceType.ACCELERATION_SENSOR: AccelerationSensor, DeviceType.ALARM_SIREN_INDOOR: AlarmSirenIndoor, + DeviceType.ALARM_SIREN_OUTDOOR: AlarmSirenOutdoor, DeviceType.BRAND_BLIND: BrandBlind, DeviceType.BRAND_DIMMER: BrandDimmer, DeviceType.BRAND_PUSH_BUTTON: BrandPushButton, @@ -87,6 +88,7 @@ GroupType.HEATING_FAILURE_ALERT_RULE_GROUP: HeatingFailureAlertRuleGroup, GroupType.HEATING_COOLING_DEMAND_BOILER: HeatingCoolingDemandBoilerGroup, GroupType.HEATING_COOLING_DEMAND_PUMP: HeatingCoolingDemandPumpGroup, + GroupType.HOT_WATER: HotWaterGroup, GroupType.HUMIDITY_WARNING_RULE_GROUP: HumidityWarningRuleGroup, GroupType.SWITCHING_PROFILE: SwitchingProfileGroup, GroupType.OVER_HEAT_PROTECTION_RULE: OverHeatProtectionRule, @@ -136,6 +138,7 @@ FunctionalChannelType.DEVICE_INCORRECT_POSITIONED: DeviceIncorrectPositionedChannel, FunctionalChannelType.DEVICE_OPERATIONLOCK: DeviceOperationLockChannel, FunctionalChannelType.DEVICE_PERMANENT_FULL_RX: DevicePermanentFullRxChannel, + FunctionalChannelType.DEVICE_RECHARGEABLE_WITH_SABOTAGE:DeviceRechargeableWithSabotage, FunctionalChannelType.DEVICE_SABOTAGE: DeviceSabotageChannel, FunctionalChannelType.DOOR_CHANNEL: DoorChannel, FunctionalChannelType.DIMMER_CHANNEL: DimmerChannel, diff --git a/homematicip/device.py b/homematicip/device.py index f4ec0837..7780d790 100644 --- a/homematicip/device.py +++ b/homematicip/device.py @@ -30,8 +30,8 @@ def __init__(self, connection): 0 # firmwareVersion = A.B.C -> firmwareVersionInteger ((A<<16)|(B<<8)|C) ) self.availableFirmwareVersion = None - self.unreach = None - self.lowBat = None + self.unreach = False + self.lowBat = False self.routerModuleSupported = False self.routerModuleEnabled = False self.modelType = "" @@ -55,10 +55,10 @@ def __init__(self, connection): self._baseChannel = "DEVICE_BASE" - self.deviceOverheated = None - self.deviceOverloaded = None - self.deviceUndervoltage = None - self.temperatureOutOfRange = None + self.deviceOverheated = False + self.deviceOverloaded = False + self.deviceUndervoltage = False + self.temperatureOutOfRange = False def from_json(self, js): super().from_json(js) @@ -779,6 +779,18 @@ def from_json(self, js): # 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",js) class MotionDetectorIndoor(SabotageDevice): """ HMIP-SMI (Motion Detector with Brightness Sensor - indoor) """ diff --git a/homematicip/group.py b/homematicip/group.py index 1ae1b32e..21083545 100644 --- a/homematicip/group.py +++ b/homematicip/group.py @@ -919,14 +919,14 @@ def __init__(self, connection): self.profileId = ( None # Not sure why it is there. You can't use it to query something. ) - self.profileMode = None + self.profileMode = ProfileMode.MANUAL def from_json(self, js, devices): super().from_json(js, devices) - self.on = js["on"] - self.dimLevel = js["dimLevel"] - self.profileId = js["profileId"] - self.profileMode = js["profileMode"] + self.set_attr_from_dict("on",js) + self.set_attr_from_dict("dimLevel",js) + self.set_attr_from_dict("profileId",js) + self.set_attr_from_dict("profileMode",js,ProfileMode) def __str__(self): return "{} on({}) dimLevel({}) profileMode({})".format( @@ -949,7 +949,7 @@ def set_profile_mode(self, devices, automatic=True): data = { "groupId": self.id, "channels": channels, - "profileMode": "AUTOMATIC" if automatic else "MANUAL", + "profileMode": ProfileMode.AUTOMATIC if automatic else ProfileMode.MANUAL, } return self._restCall( "group/switching/profile/setProfileMode", body=json.dumps(data) @@ -1077,3 +1077,32 @@ def __str__(self): self.windSpeed, self.humidity, ) + +class HotWaterGroup(Group): + def __init__(self, connection): + super().__init__(connection) + self.on = None + self.onTime = 0.0 + self.profileId = ( + None # Not sure why it is there. You can't use it to query something. + ) + self.profileMode = ProfileMode.MANUAL + + def from_json(self, js, devices): + super().from_json(js, devices) + self.set_attr_from_dict("on",js) + self.set_attr_from_dict("onTime",js) + self.set_attr_from_dict("profileId",js) + self.set_attr_from_dict("profileMode",js,ProfileMode) + + def __str__(self): + return f"{super().__str__()} on({self.on}) onTime({self.onTime}) profileMode({self.profileMode})" + + def set_profile_mode(self, profileMode: ProfileMode): + data = { + "groupId": self.id, + "profileMode": profileMode + } + return self._restCall( + "group/heating/setProfileMode", body=json.dumps(data) + ) diff --git a/homematicip_demo/fake_cloud_server.py b/homematicip_demo/fake_cloud_server.py index 84ea41ad..554e012f 100644 --- a/homematicip_demo/fake_cloud_server.py +++ b/homematicip_demo/fake_cloud_server.py @@ -940,6 +940,17 @@ async def post_hmip_group_heating_setControlMode( response = self.errorCode("INVALID_GROUP", 404) return response + async def post_hmip_group_heating_setProfileMode( + self, request: web.Request + ) -> web.Response: + response = web.json_response(None) + + js = json.loads(request.data) + g = self.data["groups"][js["groupId"]] + g["profileMode"] = js["profileMode"] + + return response + @validate_authorization async def post_hmip_group_switching_alarm_testSignalAcoustic( self, request: web.Request diff --git a/homematicip_demo/json_data/home.json b/homematicip_demo/json_data/home.json index 681a286f..37b69301 100644 --- a/homematicip_demo/json_data/home.json +++ b/homematicip_demo/json_data/home.json @@ -14,6 +14,68 @@ } }, "devices": { + "3014F7110000ABCDABCD0033": { + "availableFirmwareVersion": "1.0.6", + "firmwareVersion": "1.0.6", + "firmwareVersionInteger": 65542, + "functionalChannels": { + "0": { + "badBatteryHealth": false, + "coProFaulty": false, + "coProRestartNeeded": false, + "coProUpdateFailure": false, + "configPending": false, + "deviceId": "3014F7110000ABCDABCD0033", + "deviceOverheated": false, + "deviceOverloaded": false, + "deviceUndervoltage": false, + "dutyCycle": false, + "functionalChannelType": "DEVICE_RECHARGEABLE_WITH_SABOTAGE", + "groupIndex": 0, + "groups": [], + "index": 0, + "label": "", + "lowBat": false, + "routerModuleEnabled": false, + "routerModuleSupported": false, + "rssiDeviceValue": -51, + "rssiPeerValue": null, + "sabotage": false, + "supportedOptionalFeatures": { + "IFeatureDeviceCoProError": false, + "IFeatureDeviceCoProRestart": false, + "IFeatureDeviceCoProUpdate": false, + "IFeatureDeviceOverheated": false, + "IFeatureDeviceOverloaded": false, + "IFeatureDeviceTemperatureOutOfRange": false, + "IFeatureDeviceUndervoltage": false + }, + "temperatureOutOfRange": false, + "unreach": false + }, + "1": { + "deviceId": "3014F7110000ABCDABCD0033", + "functionalChannelType": "ALARM_SIREN_CHANNEL", + "groupIndex": 1, + "groups": [], + "index": 1, + "label": "" + } + }, + "homeId": "00000000-0000-0000-0000-000000000001", + "id": "3014F7110000ABCDABCD0033", + "label": "Alarmsirene \u2013 au\u00dfen", + "lastStatusUpdate": 1573078567665, + "liveUpdateState": "LIVE_UPDATE_NOT_SUPPORTED", + "manufacturerCode": 1, + "modelId": 385, + "modelType": "HmIP-ASIR-O", + "oem": "eQ-3", + "permanentlyReachable": true, + "serializedGlobalTradeItemNumber": "3014F7110000000000000033", + "type": "ALARM_SIREN_OUTDOOR", + "updateState": "UP_TO_DATE" + }, "3014F7110000000000000031": { "availableFirmwareVersion": "1.2.1", "firmwareVersion": "1.2.1", @@ -5491,6 +5553,22 @@ "topSlatsLevel": 0.0, "type": "EXTENDED_LINKED_SHUTTER", "unreach": false + }, + "00000000-0000-0000-0000-000000000067": { + "channels": [], + "dutyCycle": null, + "homeId": "00000000-0000-0000-0000-000000000001", + "id": "00000000-0000-0000-0000-000000000067", + "label": "HOT_WATER", + "lastStatusUpdate": 0, + "lowBat": null, + "metaGroupId": null, + "on": null, + "onTime": 900.0, + "profileId": "00000000-0000-0000-0000-000000000068", + "profileMode": null, + "type": "HOT_WATER", + "unreach": null } }, "home": { diff --git a/tests/aio_tests/test_async_groups.py b/tests/aio_tests/test_async_groups.py index 43fcd9db..aeefb37c 100644 --- a/tests/aio_tests/test_async_groups.py +++ b/tests/aio_tests/test_async_groups.py @@ -242,3 +242,20 @@ async def test_extended_linked_shutter_group(no_ssl_fake_async_home: AsyncHome): "00000000-0000-0000-0000-000000000050" ) assert g.shutterLevel == 30 + +@pytest.mark.asyncio +async def test_hot_water(no_ssl_fake_async_home: AsyncHome): + g = no_ssl_fake_async_home.search_group_by_id( + "00000000-0000-0000-0000-000000000067" + ) + assert g.profileMode is None + + await g.set_profile_mode(ProfileMode.AUTOMATIC) + await no_ssl_fake_async_home.get_current_state() + g = no_ssl_fake_async_home.search_group_by_id( + "00000000-0000-0000-0000-000000000067" + ) + + assert g.profileMode == ProfileMode.AUTOMATIC + + assert str(g) == "HOT_WATER HOT_WATER on(None) onTime(900.0) profileMode(AUTOMATIC)" \ No newline at end of file diff --git a/tests/test_groups.py b/tests/test_groups.py index e4fb125f..647d5973 100644 --- a/tests/test_groups.py +++ b/tests/test_groups.py @@ -410,6 +410,19 @@ def test_extended_linked_shutter_group(fake_home: Home): g = fake_home.search_group_by_id("00000000-0000-0000-0000-000000000050") assert g.shutterLevel == 30 +def test_hot_water(fake_home: Home): + with no_ssl_verification(): + g = fake_home.search_group_by_id("00000000-0000-0000-0000-000000000067") + assert g.profileMode is None + + g.set_profile_mode(ProfileMode.AUTOMATIC) + fake_home.get_current_state() + g = fake_home.search_group_by_id("00000000-0000-0000-0000-000000000067") + assert g.profileMode == ProfileMode.AUTOMATIC + + assert str(g) == "HOT_WATER HOT_WATER on(None) onTime(900.0) profileMode(AUTOMATIC)" + + def test_all_groups_implemented(fake_home: Home): for g in fake_home.groups: From 78476a2079316a2b76f56963a4f9012b3d98f545 Mon Sep 17 00:00:00 2001 From: coreGreenberet Date: Sun, 22 Dec 2019 22:26:02 +0100 Subject: [PATCH 3/4] black --- homematicip/aio/device.py | 2 ++ homematicip/aio/group.py | 9 ++++----- homematicip/base/enums.py | 3 ++- homematicip/base/functionalChannels.py | 3 ++- homematicip/class_maps.py | 2 +- homematicip/device.py | 9 ++++++--- homematicip/group.py | 26 +++++++++++--------------- tests/aio_tests/test_async_groups.py | 3 ++- tests/test_groups.py | 7 +++++-- 9 files changed, 35 insertions(+), 29 deletions(-) diff --git a/homematicip/aio/device.py b/homematicip/aio/device.py index 1b262996..7fffe674 100644 --- a/homematicip/aio/device.py +++ b/homematicip/aio/device.py @@ -237,9 +237,11 @@ class AsyncRemoteControl8Module(RemoteControl8Module, AsyncRemoteControl8): class AsyncAlarmSirenIndoor(AlarmSirenIndoor, AsyncSabotageDevice): """ HMIP-ASIR (Alarm Siren) """ + class AsyncAlarmSirenOutdoor(AlarmSirenOutdoor, AsyncAlarmSirenIndoor): """ HMIP-ASIR-O (Alarm Siren Outdoor) """ + class AsyncMotionDetectorIndoor(MotionDetectorIndoor, AsyncSabotageDevice): """ HMIP-SMI (Motion Detector with Brightness Sensor - indoor) """ diff --git a/homematicip/aio/group.py b/homematicip/aio/group.py index ce313b2b..47cbbe26 100644 --- a/homematicip/aio/group.py +++ b/homematicip/aio/group.py @@ -245,8 +245,7 @@ class AsyncLockOutProtectionRule(LockOutProtectionRule, AsyncGroup): class AsyncEnvironmentGroup(EnvironmentGroup, AsyncGroup): pass -class AsyncHotWaterGroup(HotWaterGroup,AsyncGroup): - async def set_profile_mode(self, profileMode:ProfileMode): - return await self._connection.api_call( - *super().set_profile_mode(profileMode) - ) \ No newline at end of file + +class AsyncHotWaterGroup(HotWaterGroup, AsyncGroup): + async def set_profile_mode(self, profileMode: ProfileMode): + return await self._connection.api_call(*super().set_profile_mode(profileMode)) diff --git a/homematicip/base/enums.py b/homematicip/base/enums.py index 8c767f3d..71ecd442 100644 --- a/homematicip/base/enums.py +++ b/homematicip/base/enums.py @@ -506,6 +506,7 @@ class GroupVisibility(AutoNameEnum): INVISIBLE_CONTROL = auto() VISIBLE = auto() + class ProfileMode(AutoNameEnum): AUTOMATIC = auto() - MANUAL = auto() \ No newline at end of file + MANUAL = auto() diff --git a/homematicip/base/functionalChannels.py b/homematicip/base/functionalChannels.py index 8e363218..33667a75 100644 --- a/homematicip/base/functionalChannels.py +++ b/homematicip/base/functionalChannels.py @@ -749,6 +749,7 @@ def from_json(self, js, groups: Iterable[Group]): "notificationSoundTypeLowToHigh", js, NotificationSoundType ) + class DeviceRechargeableWithSabotage(DeviceSabotageChannel): """ this is the representative of the DEVICE_RECHARGEABLE_WITH_SABOTAGE channel""" @@ -759,4 +760,4 @@ def __init__(self): def from_json(self, js, groups: Iterable[Group]): super().from_json(js, groups) - self.set_attr_from_dict("badBatteryHealth",js) \ No newline at end of file + self.set_attr_from_dict("badBatteryHealth", js) diff --git a/homematicip/class_maps.py b/homematicip/class_maps.py index 2f976dea..1cad939d 100644 --- a/homematicip/class_maps.py +++ b/homematicip/class_maps.py @@ -138,7 +138,7 @@ FunctionalChannelType.DEVICE_INCORRECT_POSITIONED: DeviceIncorrectPositionedChannel, FunctionalChannelType.DEVICE_OPERATIONLOCK: DeviceOperationLockChannel, FunctionalChannelType.DEVICE_PERMANENT_FULL_RX: DevicePermanentFullRxChannel, - FunctionalChannelType.DEVICE_RECHARGEABLE_WITH_SABOTAGE:DeviceRechargeableWithSabotage, + FunctionalChannelType.DEVICE_RECHARGEABLE_WITH_SABOTAGE: DeviceRechargeableWithSabotage, FunctionalChannelType.DEVICE_SABOTAGE: DeviceSabotageChannel, FunctionalChannelType.DOOR_CHANNEL: DoorChannel, FunctionalChannelType.DIMMER_CHANNEL: DimmerChannel, diff --git a/homematicip/device.py b/homematicip/device.py index 7780d790..1de6e954 100644 --- a/homematicip/device.py +++ b/homematicip/device.py @@ -779,18 +779,21 @@ def from_json(self, js): # The ALARM_SIREN_CHANNEL doesn't have any values yet. pass + class AlarmSirenOutdoor(AlarmSirenIndoor): """ HMIP-ASIR-O (Alarm Siren Outdoor) """ - def __init__(self,connection): + + 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",js) + self.set_attr_from_dict("badBatteryHealth", js) + class MotionDetectorIndoor(SabotageDevice): """ HMIP-SMI (Motion Detector with Brightness Sensor - indoor) """ diff --git a/homematicip/group.py b/homematicip/group.py index 21083545..081ebb22 100644 --- a/homematicip/group.py +++ b/homematicip/group.py @@ -923,10 +923,10 @@ def __init__(self, connection): def from_json(self, js, devices): super().from_json(js, devices) - self.set_attr_from_dict("on",js) - self.set_attr_from_dict("dimLevel",js) - self.set_attr_from_dict("profileId",js) - self.set_attr_from_dict("profileMode",js,ProfileMode) + self.set_attr_from_dict("on", js) + self.set_attr_from_dict("dimLevel", js) + self.set_attr_from_dict("profileId", js) + self.set_attr_from_dict("profileMode", js, ProfileMode) def __str__(self): return "{} on({}) dimLevel({}) profileMode({})".format( @@ -1078,6 +1078,7 @@ def __str__(self): self.humidity, ) + class HotWaterGroup(Group): def __init__(self, connection): super().__init__(connection) @@ -1090,19 +1091,14 @@ def __init__(self, connection): def from_json(self, js, devices): super().from_json(js, devices) - self.set_attr_from_dict("on",js) - self.set_attr_from_dict("onTime",js) - self.set_attr_from_dict("profileId",js) - self.set_attr_from_dict("profileMode",js,ProfileMode) + self.set_attr_from_dict("on", js) + self.set_attr_from_dict("onTime", js) + self.set_attr_from_dict("profileId", js) + self.set_attr_from_dict("profileMode", js, ProfileMode) def __str__(self): return f"{super().__str__()} on({self.on}) onTime({self.onTime}) profileMode({self.profileMode})" def set_profile_mode(self, profileMode: ProfileMode): - data = { - "groupId": self.id, - "profileMode": profileMode - } - return self._restCall( - "group/heating/setProfileMode", body=json.dumps(data) - ) + data = {"groupId": self.id, "profileMode": profileMode} + return self._restCall("group/heating/setProfileMode", body=json.dumps(data)) diff --git a/tests/aio_tests/test_async_groups.py b/tests/aio_tests/test_async_groups.py index aeefb37c..5a7f0827 100644 --- a/tests/aio_tests/test_async_groups.py +++ b/tests/aio_tests/test_async_groups.py @@ -243,6 +243,7 @@ async def test_extended_linked_shutter_group(no_ssl_fake_async_home: AsyncHome): ) assert g.shutterLevel == 30 + @pytest.mark.asyncio async def test_hot_water(no_ssl_fake_async_home: AsyncHome): g = no_ssl_fake_async_home.search_group_by_id( @@ -258,4 +259,4 @@ async def test_hot_water(no_ssl_fake_async_home: AsyncHome): assert g.profileMode == ProfileMode.AUTOMATIC - assert str(g) == "HOT_WATER HOT_WATER on(None) onTime(900.0) profileMode(AUTOMATIC)" \ No newline at end of file + assert str(g) == "HOT_WATER HOT_WATER on(None) onTime(900.0) profileMode(AUTOMATIC)" diff --git a/tests/test_groups.py b/tests/test_groups.py index 647d5973..3923fa76 100644 --- a/tests/test_groups.py +++ b/tests/test_groups.py @@ -410,6 +410,7 @@ def test_extended_linked_shutter_group(fake_home: Home): g = fake_home.search_group_by_id("00000000-0000-0000-0000-000000000050") assert g.shutterLevel == 30 + def test_hot_water(fake_home: Home): with no_ssl_verification(): g = fake_home.search_group_by_id("00000000-0000-0000-0000-000000000067") @@ -420,8 +421,10 @@ def test_hot_water(fake_home: Home): g = fake_home.search_group_by_id("00000000-0000-0000-0000-000000000067") assert g.profileMode == ProfileMode.AUTOMATIC - assert str(g) == "HOT_WATER HOT_WATER on(None) onTime(900.0) profileMode(AUTOMATIC)" - + assert ( + str(g) + == "HOT_WATER HOT_WATER on(None) onTime(900.0) profileMode(AUTOMATIC)" + ) def test_all_groups_implemented(fake_home: Home): From 5f217ac6c9083128568be3f4e3f72f84ee57aee5 Mon Sep 17 00:00:00 2001 From: coreGreenberet Date: Sun, 22 Dec 2019 23:10:20 +0100 Subject: [PATCH 4/4] fixed Testcases --- homematicip/device.py | 30 +++++++++++++++++------- homematicip_demo/json_data/home.json | 28 +++++++++++----------- tests/test_devices.py | 35 +++++++++++++++++++++------- 3 files changed, 61 insertions(+), 32 deletions(-) diff --git a/homematicip/device.py b/homematicip/device.py index 1de6e954..d3d2da71 100644 --- a/homematicip/device.py +++ b/homematicip/device.py @@ -59,6 +59,9 @@ def __init__(self, connection): self.deviceOverloaded = False self.deviceUndervoltage = False self.temperatureOutOfRange = False + self.coProFaulty = False + self.coProRestartNeeded = False + self.coProUpdateFailure = False def from_json(self, js): super().from_json(js) @@ -94,14 +97,20 @@ def from_json(self, js): sof = c.get("supportedOptionalFeatures") if sof: - if sof["IFeatureDeviceOverheated"]: - self.deviceOverheated = c["deviceOverheated"] - if sof["IFeatureDeviceOverloaded"]: - self.deviceOverloaded = c["deviceOverloaded"] - if sof["IFeatureDeviceUndervoltage"]: - self.deviceUndervoltage = c["deviceUndervoltage"] - if sof["IFeatureDeviceTemperatureOutOfRange"]: - self.temperatureOutOfRange = c["temperatureOutOfRange"] + if sof.get("IFeatureDeviceOverheated", False): + self.set_attr_from_dict("deviceOverheated", c) + if sof.get("IFeatureDeviceOverloaded", False): + self.set_attr_from_dict("deviceOverloaded", c) + if sof.get("IFeatureDeviceUndervoltage", False): + self.set_attr_from_dict("deviceUndervoltage", c) + if sof.get("IFeatureDeviceTemperatureOutOfRange", False): + self.set_attr_from_dict("temperatureOutOfRange", c) + if sof.get("IFeatureDeviceCoProError", False): + self.set_attr_from_dict("coProFaulty", c) + if sof.get("IFeatureDeviceCoProRestart", False): + self.set_attr_from_dict("coProRestartNeeded", c) + if sof.get("IFeatureDeviceCoProUpdate", False): + self.set_attr_from_dict("coProUpdateFailure", c) def __str__(self): return "{} {} lowbat({}) unreach({}) rssiDeviceValue({}) rssiPeerValue({}) configPending({}) dutyCycle({})".format( @@ -792,7 +801,10 @@ 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", js) + self.set_attr_from_dict("badBatteryHealth", c) + + def __str__(self): + return f"{super().__str__()} badBatteryHealth({self.badBatteryHealth})" class MotionDetectorIndoor(SabotageDevice): diff --git a/homematicip_demo/json_data/home.json b/homematicip_demo/json_data/home.json index 37b69301..474bfdba 100644 --- a/homematicip_demo/json_data/home.json +++ b/homematicip_demo/json_data/home.json @@ -20,7 +20,7 @@ "firmwareVersionInteger": 65542, "functionalChannels": { "0": { - "badBatteryHealth": false, + "badBatteryHealth": true, "coProFaulty": false, "coProRestartNeeded": false, "coProUpdateFailure": false, @@ -72,7 +72,7 @@ "modelType": "HmIP-ASIR-O", "oem": "eQ-3", "permanentlyReachable": true, - "serializedGlobalTradeItemNumber": "3014F7110000000000000033", + "serializedGlobalTradeItemNumber": "3014F7110000ABCDABCD0033", "type": "ALARM_SIREN_OUTDOOR", "updateState": "UP_TO_DATE" }, @@ -747,14 +747,14 @@ "firmwareVersionInteger": 65542, "functionalChannels": { "0": { - "coProFaulty": false, - "coProRestartNeeded": false, - "coProUpdateFailure": false, + "coProFaulty": true, + "coProRestartNeeded": true, + "coProUpdateFailure": true, "configPending": false, "deviceId": "3014F7110000000000000064", "deviceOverheated": true, - "deviceOverloaded": false, - "deviceUndervoltage": false, + "deviceOverloaded": true, + "deviceUndervoltage": true, "dutyCycle": false, "functionalChannelType": "DEVICE_SABOTAGE", "groupIndex": 0, @@ -771,15 +771,15 @@ "rssiPeerValue": null, "sabotage": false, "supportedOptionalFeatures": { - "IFeatureDeviceCoProError": false, - "IFeatureDeviceCoProRestart": false, - "IFeatureDeviceCoProUpdate": false, + "IFeatureDeviceCoProError": true, + "IFeatureDeviceCoProRestart": true, + "IFeatureDeviceCoProUpdate": true, "IFeatureDeviceOverheated": true, - "IFeatureDeviceOverloaded": false, - "IFeatureDeviceTemperatureOutOfRange": false, - "IFeatureDeviceUndervoltage": false + "IFeatureDeviceOverloaded": true, + "IFeatureDeviceTemperatureOutOfRange": true, + "IFeatureDeviceUndervoltage": true }, - "temperatureOutOfRange": false, + "temperatureOutOfRange": true, "unreach": false }, "1": { diff --git a/tests/test_devices.py b/tests/test_devices.py index f567f0d5..b319561a 100644 --- a/tests/test_devices.py +++ b/tests/test_devices.py @@ -207,9 +207,12 @@ def test_contact_interface_device(fake_home: Home): assert d.dutyCycle is False assert d.configPending is False assert d.deviceOverheated is True - assert d.deviceOverloaded is None - assert d.deviceUndervoltage is None - assert d.temperatureOutOfRange is None + assert d.deviceOverloaded is True + assert d.deviceUndervoltage is True + assert d.temperatureOutOfRange is True + assert d.coProFaulty is True + assert d.coProRestartNeeded is True + assert d.coProUpdateFailure is True assert ( str(d) @@ -1039,9 +1042,20 @@ def test_alarm_siren_indoor(fake_home: Home): d = AlarmSirenIndoor(fake_home._connection) d = fake_home.search_device_by_id("3014F7110000000000BBBBB8") - assert ( - str(d) - == "HmIP-ASIR Alarmsirene lowbat(False) unreach(False) rssiDeviceValue(-59) rssiPeerValue(None) configPending(False) dutyCycle(False) sabotage(False)" + assert str(d) == ( + "HmIP-ASIR Alarmsirene lowbat(False) unreach(False) rssiDeviceValue(-59) " + "rssiPeerValue(None) configPending(False) dutyCycle(False) sabotage(False)" + ) + + +def test_alarm_siren_outdoor(fake_home: Home): + with no_ssl_verification(): + d = AlarmSirenIndoor(fake_home._connection) + d = fake_home.search_device_by_id("3014F7110000ABCDABCD0033") + + assert str(d) == ( + "HmIP-ASIR-O Alarmsirene – außen lowbat(False) unreach(False) rssiDeviceValue(-51) " + "rssiPeerValue(None) configPending(False) dutyCycle(False) sabotage(None) badBatteryHealth(True)" ) @@ -1065,9 +1079,12 @@ def test_floor_terminal_block(fake_home: Home): assert d.pumpProtectionSwitchingInterval == 14 assert str(d) == ( - "HmIP-FAL230-C6 Fußbodenheizungsaktor lowbat(None) unreach(False) rssiDeviceValue(-62) rssiPeerValue(None) configPending(False) dutyCycle(False) " - "globalPumpControl(True) heatingValveType(NORMALLY_CLOSE) heatingLoadType(LOAD_BALANCING) coolingEmergencyValue(0.0) frostProtectionTemperature(8.0) " - "heatingEmergencyValue(0.25) valveProtectionDuration(5) valveProtectionSwitchingInterval(14) pumpFollowUpTime(2) pumpLeadTime(2) " + "HmIP-FAL230-C6 Fußbodenheizungsaktor lowbat(None) unreach(False) " + "rssiDeviceValue(-62) rssiPeerValue(None) configPending(False) dutyCycle(False) " + "globalPumpControl(True) heatingValveType(NORMALLY_CLOSE) heatingLoadType(LOAD_BALANCING) " + "coolingEmergencyValue(0.0) frostProtectionTemperature(8.0) " + "heatingEmergencyValue(0.25) valveProtectionDuration(5) valveProtectionSwitchingInterval(14) " + "pumpFollowUpTime(2) pumpLeadTime(2) " "pumpProtectionDuration(1) pumpProtectionSwitchingInterval(14)" )