def test_handle_get_characteristics_encrypted(driver): """Verify an encrypted get_characteristics.""" acc = Accessory(driver, "TestAcc", aid=1) assert acc.aid == 1 service = acc.driver.loader.get_service("GarageDoorOpener") acc.add_service(service) driver.add_accessory(acc) handler = hap_handler.HAPServerHandler(driver, "peername") handler.is_encrypted = True response = hap_handler.HAPResponse() handler.response = response handler.path = "/characteristics?id=1.9" handler.handle_get_characteristics() assert response.status_code == 207 assert b'"value": 0' in response.body with patch.object(acc.iid_manager, "get_obj", side_effect=CharacteristicError): response = hap_handler.HAPResponse() handler.response = response handler.path = "/characteristics?id=1.9" handler.handle_get_characteristics() assert response.status_code == 207 assert b"-70402" in response.body
def test_get_characteristics_with_crypto(driver): """Verify an encrypt characteristics request.""" loop = MagicMock() transport = MagicMock() connections = {} acc = Accessory(driver, "TestAcc", aid=1) assert acc.aid == 1 service = acc.driver.loader.get_service("TemperatureSensor") acc.add_service(service) driver.add_accessory(acc) hap_proto = hap_protocol.HAPServerProtocol(loop, connections, driver) hap_proto.connection_made(transport) hap_proto.hap_crypto = MockHAPCrypto() hap_proto.handler.is_encrypted = True with patch.object(hap_proto.transport, "write") as writer: hap_proto.data_received( b"GET /characteristics?id=3762173001.7 HTTP/1.1\r\nHost: HASS\\032Bridge\\032YPHW\\032B223AD._hap._tcp.local\r\n\r\n" # pylint: disable=line-too-long ) hap_proto.data_received( b"GET /characteristics?id=1.5 HTTP/1.1\r\nHost: HASS\\032Bridge\\032YPHW\\032B223AD._hap._tcp.local\r\n\r\n" # pylint: disable=line-too-long ) hap_proto.close() assert b"Content-Length:" in writer.call_args_list[0][0][0] assert b"Transfer-Encoding: chunked\r\n\r\n" not in writer.call_args_list[0][0][0] assert b"-70402" in writer.call_args_list[0][0][0] assert b"Content-Length:" in writer.call_args_list[1][0][0] assert b"Transfer-Encoding: chunked\r\n\r\n" not in writer.call_args_list[1][0][0] assert b"TestAcc" in writer.call_args_list[1][0][0]
def test_set_characteristics_with_crypto(driver): """Verify an encrypt characteristics request.""" loop = MagicMock() transport = MagicMock() connections = {} acc = Accessory(driver, "TestAcc", aid=1) assert acc.aid == 1 service = acc.driver.loader.get_service("GarageDoorOpener") acc.add_service(service) driver.add_accessory(acc) hap_proto = hap_protocol.HAPServerProtocol(loop, connections, driver) hap_proto.connection_made(transport) hap_proto.hap_crypto = MockHAPCrypto() hap_proto.handler.is_encrypted = True with patch.object(hap_proto.transport, "write") as writer: hap_proto.data_received( b'PUT /characteristics HTTP/1.1\r\nHost: HASS12\\032AD1C22._hap._tcp.local\r\nContent-Length: 49\r\nContent-Type: application/hap+json\r\n\r\n{"characteristics":[{"aid":1,"iid":9,"ev":true}]}' # pylint: disable=line-too-long ) hap_proto.close() assert writer.call_args_list[0][0][0] == b"HTTP/1.1 204 No Content\r\n\r\n"
def test_acc_set_primary_service(mock_driver): """Test method set_primary_service.""" acc = Accessory(mock_driver, "Test Accessory") service = acc.driver.loader.get_service("Television") acc.add_service(service) linked_service = acc.driver.loader.get_service("TelevisionSpeaker") acc.add_service(linked_service) assert acc.get_service("Television").is_primary_service is None assert acc.get_service("TelevisionSpeaker").is_primary_service is None acc.set_primary_service(service) assert acc.get_service("Television").is_primary_service is True assert acc.get_service("TelevisionSpeaker").is_primary_service is False
def test_handle_set_handle_set_characteristics_unencrypted(driver): """Verify an unencrypted set_characteristics.""" acc = Accessory(driver, "TestAcc", aid=1) assert acc.aid == 1 service = acc.driver.loader.get_service("GarageDoorOpener") acc.add_service(service) driver.add_accessory(acc) handler = hap_handler.HAPServerHandler(driver, "peername") handler.is_encrypted = False response = hap_handler.HAPResponse() handler.response = response handler.request_body = b'{"characteristics":[{"aid":1,"iid":9,"ev":true}]}' handler.handle_set_characteristics() assert response.status_code == 401
def main(): logging.basicConfig(level=logging.INFO) lightbulb1 = Accessory(name='Acme LED Light Bulb', model='LEDBulb1,1', manufacturer='Acme') lightbulb1_lightbulb = LightbulbService() lightbulb1_lightbulb.add_characteristic(On(True, bulb_on)) lightbulb1_lightbulb.add_characteristic(Brightness(50, bulb_brightness)) lightbulb1.add_service(lightbulb1_lightbulb) accessories = Accessories() accessories.add(lightbulb1) config = JsonConfig(IP_ADDRESS) start(config, accessories)
def test_handle_set_handle_set_characteristics_encrypted_with_exception( driver): """Verify an encrypted set_characteristics.""" acc = Accessory(driver, "TestAcc", aid=1) assert acc.aid == 1 def _mock_failure(*_): raise ValueError service = acc.driver.loader.get_service("GarageDoorOpener") service.setter_callback = _mock_failure acc.add_service(service) driver.add_accessory(acc) handler = hap_handler.HAPServerHandler(driver, "peername") handler.is_encrypted = True response = hap_handler.HAPResponse() handler.response = response handler.request_body = b'{"characteristics":[{"aid":1,"iid":9,"value":1}]}' handler.handle_set_characteristics() assert response.status_code == 207 assert b"-70402" in response.body
def test_mixing_service_char_callbacks_partial_failure(driver): bridge = Bridge(driver, "mybridge") acc = Accessory(driver, "TestAcc", aid=2) acc2 = UnavailableAccessory(driver, "TestAcc2", aid=3) service = Service(uuid1(), "Lightbulb") char_on = Characteristic("On", uuid1(), CHAR_PROPS) char_brightness = Characteristic("Brightness", uuid1(), CHAR_PROPS) service.add_characteristic(char_on) service.add_characteristic(char_brightness) def fail_callback(*_): raise ValueError service.setter_callback = fail_callback acc.add_service(service) 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) char_on2.setter_callback = fail_callback 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) response = driver.set_characteristics( { HAP_REPR_CHARS: [ { HAP_REPR_AID: acc.aid, HAP_REPR_IID: char_on_iid, HAP_REPR_VALUE: True, }, { 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", ) assert response == { HAP_REPR_CHARS: [ { HAP_REPR_AID: acc.aid, HAP_REPR_IID: char_on_iid, HAP_REPR_STATUS: HAP_SERVER_STATUS.SERVICE_COMMUNICATION_FAILURE, }, { HAP_REPR_AID: acc.aid, HAP_REPR_IID: char_brightness_iid, HAP_REPR_STATUS: HAP_SERVER_STATUS.SERVICE_COMMUNICATION_FAILURE, }, { HAP_REPR_AID: acc2.aid, HAP_REPR_IID: char_on2_iid, HAP_REPR_STATUS: HAP_SERVER_STATUS.SERVICE_COMMUNICATION_FAILURE, }, { HAP_REPR_AID: acc2.aid, HAP_REPR_IID: char_brightness2_iid, HAP_REPR_STATUS: 0, }, ] }
def test_service_callbacks(driver): bridge = Bridge(driver, "mybridge") acc = Accessory(driver, "TestAcc", aid=2) acc2 = UnavailableAccessory(driver, "TestAcc2", aid=3) service = Service(uuid1(), "Lightbulb") char_on = Characteristic("On", uuid1(), CHAR_PROPS) char_brightness = Characteristic("Brightness", uuid1(), CHAR_PROPS) service.add_characteristic(char_on) service.add_characteristic(char_brightness) mock_callback = MagicMock() service.setter_callback = mock_callback acc.add_service(service) 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) response = driver.set_characteristics( { HAP_REPR_CHARS: [ { HAP_REPR_AID: acc.aid, HAP_REPR_IID: char_on_iid, HAP_REPR_VALUE: True, }, { 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", ) assert response is None mock_callback2.assert_called_with({"On": True, "Brightness": 12}) mock_callback.assert_called_with({"On": True, "Brightness": 88}) get_chars = driver.get_characteristics([ "{}.{}".format(acc.aid, char_on_iid), "{}.{}".format(acc2.aid, char_on2_iid) ]) assert get_chars == { "characteristics": [ { "aid": acc.aid, "iid": char_on_iid, "status": 0, "value": True }, { "aid": acc2.aid, "iid": char_on2_iid, "status": -70402 }, ] } def _fail_func(): raise ValueError char_brightness.getter_callback = _fail_func get_chars = driver.get_characteristics([ "{}.{}".format(acc.aid, char_on_iid), "{}.{}".format(acc2.aid, char_on2_iid), "{}.{}".format(acc2.aid, char_brightness_iid), "{}.{}".format(acc.aid, char_brightness2_iid), ]) assert get_chars == { "characteristics": [ { "aid": acc.aid, "iid": char_on_iid, "status": 0, "value": True }, { "aid": acc2.aid, "iid": char_on2_iid, "status": -70402 }, { "aid": acc2.aid, "iid": char_brightness2_iid, "status": -70402 }, { "aid": acc.aid, "iid": char_brightness_iid, "status": -70402 }, ] }
def test_service_callbacks(driver): 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) char_brightness = Characteristic('Brightness', uuid1(), CHAR_PROPS) service.add_characteristic(char_on) service.add_characteristic(char_brightness) mock_callback = MagicMock() service.setter_callback = mock_callback acc.add_service(service) 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: [{ HAP_REPR_AID: acc.aid, HAP_REPR_IID: char_on_iid, HAP_REPR_VALUE: True }, { 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})
def test_acc_publish_no_broker(mock_driver): acc = Accessory(mock_driver, "Test Accessory") service = acc.driver.loader.get_service("TemperatureSensor") char = service.get_characteristic("CurrentTemperature") acc.add_service(service) char.set_value(25, should_notify=True)
logging.basicConfig(level=logging.INFO, format="[%(asctime)s] [%(module)-16s] [%(levelname)-8s] %(message)s", datefmt="%I:%M:%S %p") # Start the accessory on port 51826 driver = AccessoryDriver(port=51827, persist_file=accessory_state) mqtt_bridge = HapMqtt(MQTTSERVER, driver, "mqtt_bridge") # get loaders service_loader = loader.get_loader() char_loader = loader.get_loader() test = Accessory(driver, "Switch", aid=999196) test.add_service(service_loader.get_service("Switch")) accessories = list() accessories.append(BasicLight("outside", MQTTSERVER, driver, "flood_1", aid=1111)) accessories.append(BasicLight("outside", MQTTSERVER, driver, "flood_2", aid=2222)) accessories.append(BasicLight("outside", MQTTSERVER, driver, "flood_3", aid=3333)) accessories.append(BasicLight("outside", MQTTSERVER, driver, "flood_4", aid=4444)) accessories.append(TemperatureSensor(driver, "fake_temp")) # accessories.append(test) # MqttAccessories(TemperatureSensor(MQTTSERVER, driver, "Battery_1", aid=2323)) # MqttAccessories.accessories[2].add_service(service_loader.get_service("BatteryService")) # Add the accessories and the topics to the mqtt bridge
def test_write_characteristic(self): accessory = Accessory(name='PyHAP', model='PyHAP1,1', manufacturer='PyHAP', hardware_revision='0') service = LightbulbService() bool_characteristic = On(False) int_characteristic = Brightness(8) float_characteristic = Hue(5.0) service.add_characteristic(bool_characteristic) service.add_characteristic(int_characteristic) service.add_characteristic(float_characteristic) accessories = Accessories() accessory.add_service(service) accessories.add(accessory) # bool characteristic callback = AsyncMock() bool_characteristic.callback = callback self.assertEqual(bool_characteristic.value, False) result = asyncio.get_event_loop().run_until_complete( accessories.write_characteristic([{ 'aid': 2, 'iid': 10, 'value': True }])) callback.assert_called_once_with(True) self.assertEqual(result, []) self.assertEqual(bool_characteristic.value, True) # int characteristic write callback = AsyncMock() int_characteristic.callback = callback self.assertEqual(int_characteristic.value, 8) result = asyncio.get_event_loop().run_until_complete( accessories.write_characteristic([{ 'aid': 2, 'iid': 11, 'value': 12 }])) callback.assert_called_once_with(12) self.assertEqual(result, []) self.assertEqual(int_characteristic.value, 12) # float characteristic write callback = AsyncMock() float_characteristic.callback = callback self.assertEqual(float_characteristic.value, 5.0) result = asyncio.get_event_loop().run_until_complete( accessories.write_characteristic([{ 'aid': 2, 'iid': 12, 'value': 7.0 }])) callback.assert_called_once_with(7.0) self.assertEqual(result, []) self.assertEqual(float_characteristic.value, 7.0) # None value during write, leave previous value previous_value = bool_characteristic.value result = asyncio.get_event_loop().run_until_complete( accessories.write_characteristic([{ 'aid': 2, 'iid': 10 }])) self.assertEqual(result, []) self.assertEqual(bool_characteristic.value, previous_value) bool_characteristic.value = previous_value # None callback bool_characteristic.callback = None result = asyncio.get_event_loop().run_until_complete( accessories.write_characteristic([{ 'aid': 2, 'iid': 10, 'value': True }])) self.assertEqual(result, []) # Exception in callback bool_characteristic.callback = exception_callback result = asyncio.get_event_loop().run_until_complete( accessories.write_characteristic([{ 'aid': 2, 'iid': 10, 'value': True }])) self.assertEqual(result, [{ 'aid': 2, 'iid': 10, 'status': -70402, }])
def load_accessories(self, override_ids=False): """ Return a list of initialized accessories specified by the config files. The characteristics of those accessories should contain three additional values: topic_in, topic_out and adapter used by the MqttBridge class. :param override_ids: override accessory ids for a new bridge config :type override_ids: bool """ # find all cfg files, but skip the bridge.cfg fnames = [] for r, _, fs in os.walk(self.cfg_path): fnames += [ os.path.join(r, f) for f in fs if f != 'bridge.cfg' and f != 'accessory.state' ] for fname in fnames: cfg = configparser.ConfigParser() cfg.optionxform = str cfg.fname = fname try: cfg.read(fname) self.cfgs.append(cfg) except Exception as e: logger.warn('Skipping "{}" because of Exception: {}'.format( fname, str(e))) # sort by aid max_aid = 2**63 - 1 self.cfgs = sorted( self.cfgs, key=lambda cfg: int(cfg['Accessory'].get('AID', max_aid))) self.accs = [] for cfg in self.cfgs: try: # init accessory acc_def = cfg['Accessory'] if acc_def['Category'] not in categories: logger.warn('Unknown category: "{}"'.format( acc_def['category'])) continue if 'DisplayName' not in acc_def.keys(): # use filename as display name acc_def['DisplayName'] = os.path.basename(fname).split( '.')[0] aid = None if not override_ids: aid = acc_def.get('AID', None) if aid is not None: aid = int(aid) acc = Accessory(self.driver, acc_def['DisplayName'], aid=aid) acc.category = categories[acc_def.get('Category', 'Other')] acc.set_info_service(acc_def.get('FirmwareRevision', None), acc_def.get('Manufacturer', None), acc_def.get('Model', None), acc_def.get('SerialNumber', None)) # init services serv_types = cfg.sections() serv_types.remove('Accessory') for serv_type in serv_types: serv_def = cfg[serv_type] serv = loader.get_loader().get_service(serv_type) char_types = serv_def.keys() for char_type in char_types: char_def = serv_def[char_type] char_def = char_def.split() # init characteristic try: char = loader.get_loader().get_char(char_type) if len(char_def) != 3: logger.warn( 'Skipping caracteristic "{}" because of invalid format' .format(char_type)) continue # add topics and adapter char.properties['topic_in'] = None char.properties['topic_out'] = None char.properties['adapter'] = None if char_def[0] != '_': char.properties['topic_in'] = char_def[0] if char_def[1] != '_': char.properties['topic_out'] = char_def[1] if char_def[2] != '_': char.properties['adapter'] = char_def[2] # add characteristic added = False for i, old_char in enumerate(serv.characteristics): if old_char.type_id == char.type_id: serv.characteristics[i] = char added = True break if not added: serv.add_characteristic(char) except KeyError as e: continue acc.add_service(serv) self.accs.append(acc) logger.info('Added accessory "{}"'.format(acc.display_name)) except Exception as e: logger.warn( 'Skipping "{}" because of Exception: {}: {}'.format( type(e), cfg.fname, str(e))) return self.accs