From b6413081b045a247878afa59f99e62707a83cbd8 Mon Sep 17 00:00:00 2001 From: lboue Date: Wed, 24 Oct 2018 23:49:26 +0200 Subject: [PATCH 1/7] Fake WindowCovering accessory --- shutter.py | 202 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 202 insertions(+) create mode 100644 shutter.py diff --git a/shutter.py b/shutter.py new file mode 100644 index 00000000..7d711496 --- /dev/null +++ b/shutter.py @@ -0,0 +1,202 @@ +"""Starts a fake fan, lightbulb, garage door and a TemperatureSensor +""" +import logging +import signal +import random + +from pyhap.accessory import Accessory, Bridge +from pyhap.accessory_driver import AccessoryDriver +from pyhap.const import (CATEGORY_FAN, + CATEGORY_LIGHTBULB, + CATEGORY_GARAGE_DOOR_OPENER, + CATEGORY_WINDOW_COVERING, + CATEGORY_SENSOR) + +''' +# ### CATEGORY values ### +# Category is a hint to iOS clients about what "type" of Accessory this +# represents, for UI only. +CATEGORY_OTHER = 1 +CATEGORY_BRIDGE = 2 +CATEGORY_FAN = 3 +CATEGORY_GARAGE_DOOR_OPENER = 4 +CATEGORY_LIGHTBULB = 5 +CATEGORY_DOOR_LOCK = 6 +CATEGORY_OUTLET = 7 +CATEGORY_SWITCH = 8 +CATEGORY_THERMOSTAT = 9 +CATEGORY_SENSOR = 10 +CATEGORY_ALARM_SYSTEM = 11 +CATEGORY_DOOR = 12 +CATEGORY_WINDOW = 13 +CATEGORY_WINDOW_COVERING = 14 +CATEGORY_PROGRAMMABLE_SWITCH = 15 +CATEGORY_RANGE_EXTENDER = 16 +CATEGORY_CAMERA = 17 +CATEGORY_VIDEO_DOOR_BELL = 18 +CATEGORY_AIR_PURIFIER = 19 +CATEGORY_HEATER = 20 +CATEGORY_AIR_CONDITIONER = 21 +CATEGORY_HUMIDIFIER = 22 +CATEGORY_DEHUMIDIFIER = 23 +CATEGORY_SPEAKER = 26 +CATEGORY_SPRINKLER = 28 +CATEGORY_FAUCET = 29 +CATEGORY_SHOWER_HEAD = 30 +''' + +logging.basicConfig(level=logging.INFO, format="[%(module)s] %(message)s") + +class TemperatureSensor(Accessory): + """Fake Temperature sensor, measuring every 3 seconds.""" + + category = CATEGORY_SENSOR + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + serv_temp = self.add_preload_service('TemperatureSensor') + self.char_temp = serv_temp.configure_char('CurrentTemperature') + + @Accessory.run_at_interval(3) + async def run(self): + self.char_temp.set_value(random.randint(18, 26)) + + +class FakeFan(Accessory): + """Fake Fan, only logs whatever the client set.""" + + category = CATEGORY_FAN + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + # Add the fan service. Also add optional characteristics to it. + serv_fan = self.add_preload_service( + 'Fan', chars=['RotationSpeed', 'RotationDirection']) + + self.char_rotation_speed = serv_fan.configure_char( + 'RotationSpeed', setter_callback=self.set_rotation_speed) + self.char_rotation_direction = serv_fan.configure_char( + 'RotationDirection', setter_callback=self.set_rotation_direction) + + def set_rotation_speed(self, value): + logging.debug("Rotation speed changed: %s", value) + + def set_rotation_direction(self, value): + logging.debug("Rotation direction changed: %s", value) + +class LightBulb(Accessory): + """Fake lightbulb, logs what the client sets.""" + + category = CATEGORY_LIGHTBULB + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + serv_light = self.add_preload_service('Lightbulb') + self.char_on = serv_light.configure_char( + 'On', setter_callback=self.set_bulb) + + def set_bulb(self, value): + logging.info("Bulb value: %s", value) + +class GarageDoor(Accessory): + """Fake garage door.""" + + category = CATEGORY_GARAGE_DOOR_OPENER + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + self.add_preload_service('GarageDoorOpener')\ + .configure_char( + 'TargetDoorState', setter_callback=self.change_state) + + def change_state(self, value): + logging.info("GDC value: %s", value) + self.get_service('GarageDoorOpener')\ + .get_characteristic('CurrentDoorState')\ + .set_value(value) + + +class WindowCovering(Accessory): + """Fake WindowCovering.""" + + category = CATEGORY_WINDOW_COVERING + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + ''' + self.add_preload_service('WindowCovering')\ + .configure_char( + 'TargetPosition', setter_callback=self.change_state) + + self.char_cur = self.configure_char('CurrentPosition') + self.char_cur = self.configure_char('PositionState') + ''' + + # Add the fan service. Also add optional characteristics to it. + serv_cover = self.add_preload_service( + 'WindowCovering', chars=['CurrentPosition', 'TargetPosition', 'PositionState']) + + self.char_rotation_speed = serv_cover.configure_char( + 'TargetPosition', setter_callback=self.set_target_position) + + self.char_state = serv_cover.configure_char( + 'PositionState', setter_callback=self.set_position_state) + + self.char_rotation_direction = serv_cover.configure_char( + 'CurrentPosition', setter_callback=self.set_current_position) + + ''' + def change_state(self, value): + logging.info("WindowCovering CurrentPosition value: %s", value) + self.get_service('WindowCovering')\ + .get_characteristic('CurrentPosition')\ + .set_value(value) + ''' + def set_target_position(self, value): + logging.info("WindowCovering TargetPosition value: %s", value) + self.get_service('WindowCovering')\ + .get_characteristic('TargetPosition')\ + .set_value(value) + + # The value property of PositionState must be one of the following: + # Characteristic.PositionState.DECREASING = 0; + # Characteristic.PositionState.INCREASING = 1; + # Characteristic.PositionState.STOPPED = 2; + def set_position_state(self, value): + logging.info("WindowCovering PositionState value: %s", value) + self.get_service('PositionState')\ + .get_characteristic('PositionState')\ + .set_value(value) + + def set_current_position(self, value): + logging.info("WindowCovering CurrentPosition value: %s", value) + self.get_service('WindowCovering')\ + .get_characteristic('CurrentPosition')\ + .set_value(value) + + + +def get_bridge(driver): + bridge = Bridge(driver, 'Bridge') + + ''' + bridge.add_accessory(LightBulb(driver, 'Lightbulb')) + ''' + bridge.add_accessory(FakeFan(driver, 'Big Fan')) + bridge.add_accessory(GarageDoor(driver, 'Garage')) + bridge.add_accessory(WindowCovering(driver, 'Shutter')) + #bridge.add_accessory(TemperatureSensor(driver, 'Sensor')) + + + return bridge + + +driver = AccessoryDriver(port=51800, persist_file='shutter.state') +driver.add_accessory(accessory=get_bridge(driver)) +signal.signal(signal.SIGTERM, driver.signal_handler) +driver.start() From 531cbafaf0ed7b3cea7256bf226c22aba5bd0856 Mon Sep 17 00:00:00 2001 From: lboue Date: Thu, 25 Oct 2018 00:26:57 +0200 Subject: [PATCH 2/7] Update shutter.py Add 5sec delay for closing the fake shutter --- shutter.py | 45 +++++++++++++++------------------------------ 1 file changed, 15 insertions(+), 30 deletions(-) diff --git a/shutter.py b/shutter.py index 7d711496..46351b2e 100644 --- a/shutter.py +++ b/shutter.py @@ -3,6 +3,7 @@ import logging import signal import random +import time from pyhap.accessory import Accessory, Bridge from pyhap.accessory_driver import AccessoryDriver @@ -128,41 +129,32 @@ class WindowCovering(Accessory): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - ''' - self.add_preload_service('WindowCovering')\ - .configure_char( - 'TargetPosition', setter_callback=self.change_state) - - self.char_cur = self.configure_char('CurrentPosition') - self.char_cur = self.configure_char('PositionState') - ''' - # Add the fan service. Also add optional characteristics to it. serv_cover = self.add_preload_service( - 'WindowCovering', chars=['CurrentPosition', 'TargetPosition', 'PositionState']) + 'WindowCovering', chars=['CurrentPosition', 'TargetPosition', 'PositionState', 'ObstructionDetected', 'HoldPosition']) - self.char_rotation_speed = serv_cover.configure_char( + self.char_target_pos = serv_cover.configure_char( 'TargetPosition', setter_callback=self.set_target_position) self.char_state = serv_cover.configure_char( 'PositionState', setter_callback=self.set_position_state) - self.char_rotation_direction = serv_cover.configure_char( + self.char_cur_pos = serv_cover.configure_char( 'CurrentPosition', setter_callback=self.set_current_position) - ''' - def change_state(self, value): - logging.info("WindowCovering CurrentPosition value: %s", value) - self.get_service('WindowCovering')\ - .get_characteristic('CurrentPosition')\ - .set_value(value) - ''' def set_target_position(self, value): logging.info("WindowCovering TargetPosition value: %s", value) self.get_service('WindowCovering')\ .get_characteristic('TargetPosition')\ .set_value(value) + time.sleep(5) # Delays for 5 seconds closing the shutter. + + logging.info("WindowCovering CurrentPosition value: %s", value) + self.get_service('WindowCovering')\ + .get_characteristic('CurrentPosition')\ + .set_value(value) + # The value property of PositionState must be one of the following: # Characteristic.PositionState.DECREASING = 0; # Characteristic.PositionState.INCREASING = 1; @@ -178,24 +170,17 @@ def set_current_position(self, value): self.get_service('WindowCovering')\ .get_characteristic('CurrentPosition')\ .set_value(value) - - - + def get_bridge(driver): bridge = Bridge(driver, 'Bridge') - - ''' - bridge.add_accessory(LightBulb(driver, 'Lightbulb')) - ''' - bridge.add_accessory(FakeFan(driver, 'Big Fan')) - bridge.add_accessory(GarageDoor(driver, 'Garage')) + #bridge.add_accessory(LightBulb(driver, 'Lightbulb')) + #bridge.add_accessory(FakeFan(driver, 'Big Fan')) + #bridge.add_accessory(GarageDoor(driver, 'Garage')) bridge.add_accessory(WindowCovering(driver, 'Shutter')) #bridge.add_accessory(TemperatureSensor(driver, 'Sensor')) - return bridge - driver = AccessoryDriver(port=51800, persist_file='shutter.state') driver.add_accessory(accessory=get_bridge(driver)) signal.signal(signal.SIGTERM, driver.signal_handler) From 6027e35ec4963bbbc36f1459975180fc4e8523b0 Mon Sep 17 00:00:00 2001 From: lboue Date: Thu, 25 Oct 2018 00:27:35 +0200 Subject: [PATCH 3/7] Update shutter.py set_info_service for bridge --- shutter.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/shutter.py b/shutter.py index 46351b2e..de0ccb73 100644 --- a/shutter.py +++ b/shutter.py @@ -173,6 +173,8 @@ def set_current_position(self, value): def get_bridge(driver): bridge = Bridge(driver, 'Bridge') + bridge.set_info_service(firmware_revision=1, manufacturer="Brand", + model="model", serial_number="0123456789") #bridge.add_accessory(LightBulb(driver, 'Lightbulb')) #bridge.add_accessory(FakeFan(driver, 'Big Fan')) #bridge.add_accessory(GarageDoor(driver, 'Garage')) From 86368b0ffae2920464f59c7decc2d92da7c1f1d0 Mon Sep 17 00:00:00 2001 From: lboue Date: Thu, 25 Oct 2018 00:34:28 +0200 Subject: [PATCH 4/7] Update shutter.py Quick assign basic accessory information --- shutter.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/shutter.py b/shutter.py index de0ccb73..48e581c6 100644 --- a/shutter.py +++ b/shutter.py @@ -129,7 +129,11 @@ class WindowCovering(Accessory): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - # Add the fan service. Also add optional characteristics to it. + """Quick assign basic accessory information.""" + self.set_info_service(firmware_revision=2, manufacturer="Brand", + model="Shutter", serial_number="0123456789") + + # Add the WindowCovering service. Also add optional characteristics to it. serv_cover = self.add_preload_service( 'WindowCovering', chars=['CurrentPosition', 'TargetPosition', 'PositionState', 'ObstructionDetected', 'HoldPosition']) From 5c3c6a5429a6d8d102d618fbf769a24dbbfd5c35 Mon Sep 17 00:00:00 2001 From: lboue Date: Thu, 25 Oct 2018 00:52:19 +0200 Subject: [PATCH 5/7] Revert port to tcp/51826 --- shutter.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/shutter.py b/shutter.py index 48e581c6..345d757e 100644 --- a/shutter.py +++ b/shutter.py @@ -1,4 +1,4 @@ -"""Starts a fake fan, lightbulb, garage door and a TemperatureSensor +"""Starts a fake fan, lightbulb, garage door, shutter and a TemperatureSensor """ import logging import signal @@ -179,15 +179,15 @@ def get_bridge(driver): bridge = Bridge(driver, 'Bridge') bridge.set_info_service(firmware_revision=1, manufacturer="Brand", model="model", serial_number="0123456789") - #bridge.add_accessory(LightBulb(driver, 'Lightbulb')) - #bridge.add_accessory(FakeFan(driver, 'Big Fan')) - #bridge.add_accessory(GarageDoor(driver, 'Garage')) + bridge.add_accessory(LightBulb(driver, 'Lightbulb')) + bridge.add_accessory(FakeFan(driver, 'Big Fan')) + bridge.add_accessory(GarageDoor(driver, 'Garage')) bridge.add_accessory(WindowCovering(driver, 'Shutter')) - #bridge.add_accessory(TemperatureSensor(driver, 'Sensor')) + bridge.add_accessory(TemperatureSensor(driver, 'Sensor')) return bridge -driver = AccessoryDriver(port=51800, persist_file='shutter.state') +driver = AccessoryDriver(port=51826, persist_file='shutter.state') driver.add_accessory(accessory=get_bridge(driver)) signal.signal(signal.SIGTERM, driver.signal_handler) driver.start() From 17eed382b3c97712a770efaa3754a5730d1dbee7 Mon Sep 17 00:00:00 2001 From: lboue Date: Thu, 25 Oct 2018 00:57:55 +0200 Subject: [PATCH 6/7] Clean WindowCovering service characteristics --- shutter.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/shutter.py b/shutter.py index 345d757e..7efc7583 100644 --- a/shutter.py +++ b/shutter.py @@ -135,7 +135,7 @@ def __init__(self, *args, **kwargs): # Add the WindowCovering service. Also add optional characteristics to it. serv_cover = self.add_preload_service( - 'WindowCovering', chars=['CurrentPosition', 'TargetPosition', 'PositionState', 'ObstructionDetected', 'HoldPosition']) + 'WindowCovering', chars=['ObstructionDetected') self.char_target_pos = serv_cover.configure_char( 'TargetPosition', setter_callback=self.set_target_position) @@ -145,7 +145,10 @@ def __init__(self, *args, **kwargs): self.char_cur_pos = serv_cover.configure_char( 'CurrentPosition', setter_callback=self.set_current_position) - + + #serv_cover.configure_char('PositionState', value=2) # STOPPED + #serv_cover.configure_char('ObstructionDetected', value=1) + def set_target_position(self, value): logging.info("WindowCovering TargetPosition value: %s", value) self.get_service('WindowCovering')\ From ddcc069e0c9e06370f876f69c345b768aa8a7783 Mon Sep 17 00:00:00 2001 From: lboue Date: Thu, 25 Oct 2018 01:03:22 +0200 Subject: [PATCH 7/7] Fix chars --- shutter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shutter.py b/shutter.py index 7efc7583..5083c1b7 100644 --- a/shutter.py +++ b/shutter.py @@ -135,7 +135,7 @@ def __init__(self, *args, **kwargs): # Add the WindowCovering service. Also add optional characteristics to it. serv_cover = self.add_preload_service( - 'WindowCovering', chars=['ObstructionDetected') + 'WindowCovering', chars=['ObstructionDetected']) self.char_target_pos = serv_cover.configure_char( 'TargetPosition', setter_callback=self.set_target_position)