diff --git a/CHANGELOG.md b/CHANGELOG.md index e2be790a..59a20c85 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,12 @@ Sections ### Developers --> +## [2.8.2] - 2020-04-10 + +### Added +- Add an option to select the zeroconf broadcast interface. [#239](https://github.com/ikalchev/HAP-python/pull/239) +- Allow service callbacks to handle multiple AIDs. [#237](https://github.com/ikalchev/HAP-python/pull/237) + ## [2.8.1] - 2020-04-06 ### Fixed diff --git a/pyhap/accessory_driver.py b/pyhap/accessory_driver.py index 6a4161e8..76033d4f 100644 --- a/pyhap/accessory_driver.py +++ b/pyhap/accessory_driver.py @@ -133,7 +133,7 @@ class AccessoryDriver: def __init__(self, *, address=None, port=51234, persist_file='accessory.state', pincode=None, encoder=None, loader=None, loop=None, mac=None, - listen_address=None, advertised_address=None): + listen_address=None, advertised_address=None, interface_choice=None): """ Initialize a new AccessoryDriver object. @@ -172,6 +172,9 @@ def __init__(self, *, address=None, port=51234, This can be used to announce an external address from behind a NAT. If not given, the value of the address parameter will be used. :type advertised_address: str + + :param interface_choice: The zeroconf interfaces to listen on. + :type InterfacesType: [InterfaceChoice.Default, InterfaceChoice.All] """ if sys.platform == 'win32': self.loop = loop or asyncio.ProactorEventLoop() @@ -187,7 +190,10 @@ def __init__(self, *, address=None, port=51234, self.accessory = None self.http_server_thread = None - self.advertiser = Zeroconf() + if interface_choice is not None: + self.advertiser = Zeroconf(interfaces=interface_choice) + else: + self.advertiser = Zeroconf() self.persist_file = os.path.expanduser(persist_file) self.encoder = encoder or AccessoryEncoder() self.topics = {} # topic: set of (address, port) of subscribed clients @@ -642,10 +648,12 @@ def set_characteristics(self, chars_query, client_addr): if HAP_PERMISSION_NOTIFY in cq: char_topic = get_topic(aid, iid) - logger.debug('Subscribed client %s to topic %s', - client_addr, char_topic) + logger.debug( + "Subscribed client %s to topic %s", client_addr, char_topic + ) self.subscribe_client_topic( - client_addr, char_topic, cq[HAP_PERMISSION_NOTIFY]) + client_addr, char_topic, cq[HAP_PERMISSION_NOTIFY] + ) if HAP_REPR_VALUE in cq: # TODO: status needs to be based on success of set_value @@ -658,18 +666,20 @@ def set_characteristics(self, chars_query, client_addr): service = char.service if service and service.setter_callback: - service_callbacks.setdefault( - service.display_name, - [service.setter_callback, {}] + service_name = service.display_name + service_callbacks.setdefault(aid, {}) + service_callbacks[aid].setdefault( + service_name, [service.setter_callback, {}] ) - service_callbacks[service.display_name][ - SERVICE_CALLBACK_DATA - ][char.display_name] = cq[HAP_REPR_VALUE] - - for service_name in service_callbacks: - service_callbacks[service_name][SERVICE_CALLBACK]( - service_callbacks[service_name][SERVICE_CALLBACK_DATA] - ) + service_callbacks[aid][service_name][SERVICE_CALLBACK_DATA][ + char.display_name + ] = cq[HAP_REPR_VALUE] + + for aid in service_callbacks: + for service_name in service_callbacks[aid]: + service_callbacks[aid][service_name][SERVICE_CALLBACK]( + service_callbacks[aid][service_name][SERVICE_CALLBACK_DATA] + ) def signal_handler(self, _signal, _frame): """Stops the AccessoryDriver for a given signal. diff --git a/pyhap/const.py b/pyhap/const.py index 1a20855f..8f92f011 100644 --- a/pyhap/const.py +++ b/pyhap/const.py @@ -1,7 +1,7 @@ """This module contains constants used by other modules.""" MAJOR_VERSION = 2 MINOR_VERSION = 8 -PATCH_VERSION = 1 +PATCH_VERSION = 2 __short_version__ = '{}.{}'.format(MAJOR_VERSION, MINOR_VERSION) __version__ = '{}.{}'.format(__short_version__, PATCH_VERSION) REQUIRED_PYTHON_VER = (3, 5) diff --git a/tests/test_accessory_driver.py b/tests/test_accessory_driver.py index 00849662..7dfbc8ff 100644 --- a/tests/test_accessory_driver.py +++ b/tests/test_accessory_driver.py @@ -5,7 +5,7 @@ import pytest -from pyhap.accessory import STANDALONE_AID, Accessory +from pyhap.accessory import STANDALONE_AID, Accessory, Bridge from pyhap.accessory_driver import AccessoryDriver from pyhap.characteristic import (HAP_FORMAT_INT, HAP_PERMISSION_READ, PROP_FORMAT, PROP_PERMISSIONS, @@ -55,7 +55,9 @@ def test_persist_load(): def test_service_callbacks(driver): - acc = Accessory(driver, 'TestAcc') + bridge = Bridge(driver,"mybridge") + acc = Accessory(driver, 'TestAcc', aid=2) + acc2 = Accessory(driver, 'TestAcc2', aid=3) service = Service(uuid1(), 'Lightbulb') char_on = Characteristic('On', uuid1(), CHAR_PROPS) @@ -68,10 +70,27 @@ def test_service_callbacks(driver): service.setter_callback = mock_callback acc.add_service(service) - driver.add_accessory(acc) + bridge.add_accessory(acc) + + service2 = Service(uuid1(), 'Lightbulb') + char_on2 = Characteristic('On', uuid1(), CHAR_PROPS) + char_brightness2 = Characteristic('Brightness', uuid1(), CHAR_PROPS) + + service2.add_characteristic(char_on2) + service2.add_characteristic(char_brightness2) + + mock_callback2 = MagicMock() + service2.setter_callback = mock_callback2 + + acc2.add_service(service2) + bridge.add_accessory(acc2) char_on_iid = char_on.to_HAP()[HAP_REPR_IID] char_brightness_iid = char_brightness.to_HAP()[HAP_REPR_IID] + char_on2_iid = char_on2.to_HAP()[HAP_REPR_IID] + char_brightness2_iid = char_brightness2.to_HAP()[HAP_REPR_IID] + + driver.add_accessory(bridge) driver.set_characteristics({ HAP_REPR_CHARS: [{ @@ -82,9 +101,18 @@ def test_service_callbacks(driver): HAP_REPR_AID: acc.aid, HAP_REPR_IID: char_brightness_iid, HAP_REPR_VALUE: 88 + }, { + HAP_REPR_AID: acc2.aid, + HAP_REPR_IID: char_on2_iid, + HAP_REPR_VALUE: True + }, { + HAP_REPR_AID: acc2.aid, + HAP_REPR_IID: char_brightness2_iid, + HAP_REPR_VALUE: 12 }] }, "mock_addr") + mock_callback2.assert_called_with({'On': True, 'Brightness': 12}) mock_callback.assert_called_with({'On': True, 'Brightness': 88})