def accessory_setup(self): """Handle setup of a HomeKit accessory.""" # pylint: disable=import-error from homekit.model.services import ServicesTypes self.pairing.pairing_data['AccessoryIP'] = self.host self.pairing.pairing_data['AccessoryPort'] = self.port try: data = self.pairing.list_accessories_and_characteristics() except HomeKitConnectionError: call_later(self.hass, RETRY_INTERVAL, lambda _: self.accessory_setup()) return for accessory in data: serial = get_serial(accessory) if serial in self.hass.data[KNOWN_ACCESSORIES]: continue self.hass.data[KNOWN_ACCESSORIES][serial] = self aid = accessory['aid'] for service in accessory['services']: devtype = ServicesTypes.get_short(service['type']) _LOGGER.debug("Found %s", devtype) service_info = { 'serial': serial, 'aid': aid, 'iid': service['iid'], 'model': self.model, 'device-type': devtype } component = HOMEKIT_ACCESSORY_DISPATCH.get(devtype, None) if component is not None: discovery.load_platform(self.hass, component, DOMAIN, service_info, self.config)
def list_accessories_and_characteristics(self): """ This retrieves a current set of accessories and characteristics behind this pairing. :return: the accessory data as described in the spec on page 73 and following :raises AccessoryNotFoundError: if the device can not be found via zeroconf """ if not self.session: self.session = IpSession(self.pairing_data) try: response = self.session.get('/accessories') except (AccessoryDisconnectedError, EncryptionError): self.session.close() self.session = None raise tmp = response.read().decode() accessories = json.loads(tmp)['accessories'] for accessory in accessories: for service in accessory['services']: service['type'] = service['type'].upper() try: service['type'] = ServicesTypes.get_uuid(service['type']) except KeyError: pass for characteristic in service['characteristics']: characteristic['type'] = characteristic['type'].upper() try: characteristic['type'] = CharacteristicsTypes.get_uuid(characteristic['type']) except KeyError: pass self.pairing_data['accessories'] = accessories return accessories
def add_entities(self): """Process the entity map and create HA entities.""" # pylint: disable=import-error from homekit.model.services import ServicesTypes for accessory in self.accessories: aid = accessory['aid'] for service in accessory['services']: iid = service['iid'] if (aid, iid) in self.entities: # Don't add the same entity again continue devtype = ServicesTypes.get_short(service['type']) _LOGGER.debug("Found %s", devtype) service_info = { 'serial': self.hkid, 'aid': aid, 'iid': service['iid'], 'model': self.model, 'device-type': devtype } component = HOMEKIT_ACCESSORY_DISPATCH.get(devtype, None) if component is not None: discovery.load_platform(self.hass, component, DOMAIN, service_info, self.config) self.entities.append((aid, iid))
async def setup_test_component(hass, services): """Load a fake homekit accessory based on a homekit accessory model.""" domain = None for service in services: service_name = ServicesTypes.get_short(service.type) if service_name in HOMEKIT_ACCESSORY_DISPATCH: domain = HOMEKIT_ACCESSORY_DISPATCH[service_name] break assert domain, 'Cannot map test homekit services to homeassistant domain' config = {'discovery': {}} with mock.patch('homekit.Controller') as controller: fake_controller = controller.return_value = FakeController() await async_setup_component(hass, DOMAIN, config) accessory = Accessory('TestDevice', 'example.com', 'Test', '0001', '0.1') accessory.services.extend(services) pairing = fake_controller.add(accessory) discovery_info = { 'host': '127.0.0.1', 'port': 8080, 'properties': { 'md': 'TestDevice', 'id': '00:00:00:00:00:00', 'c#': 1, } } fire_service_discovered(hass, SERVICE_HOMEKIT, discovery_info) await hass.async_block_till_done() return Helper(hass, '.'.join((domain, 'testdevice')), pairing, accessory)
def accessory_setup(self): """Handle setup of a HomeKit accessory.""" # pylint: disable=import-error from homekit.model.services import ServicesTypes from homekit.exceptions import AccessoryDisconnectedError self.pairing.pairing_data['AccessoryIP'] = self.host self.pairing.pairing_data['AccessoryPort'] = self.port try: data = self.pairing.list_accessories_and_characteristics() except AccessoryDisconnectedError: call_later( self.hass, RETRY_INTERVAL, lambda _: self.accessory_setup()) return for accessory in data: aid = accessory['aid'] for service in accessory['services']: iid = service['iid'] if (aid, iid) in self.entities: # Don't add the same entity again continue devtype = ServicesTypes.get_short(service['type']) _LOGGER.debug("Found %s", devtype) service_info = {'serial': self.hkid, 'aid': aid, 'iid': service['iid'], 'model': self.model, 'device-type': devtype} component = HOMEKIT_ACCESSORY_DISPATCH.get(devtype, None) if component is not None: discovery.load_platform(self.hass, component, DOMAIN, service_info, self.config)
def add_entities(self): """Process the entity map and create HA entities.""" # pylint: disable=import-error from homekit.model.services import ServicesTypes for accessory in self.accessories: aid = accessory['aid'] for service in accessory['services']: iid = service['iid'] if (aid, iid) in self.entities: # Don't add the same entity again continue devtype = ServicesTypes.get_short(service['type']) _LOGGER.debug("Found %s", devtype) service_info = {'serial': self.hkid, 'aid': aid, 'iid': service['iid'], 'model': self.model, 'device-type': devtype} component = HOMEKIT_ACCESSORY_DISPATCH.get(devtype, None) if component is not None: discovery.load_platform(self.hass, component, DOMAIN, service_info, self.config) self.entities.append((aid, iid))
def __init__(self): AbstractService.__init__( self, ServicesTypes.get_uuid('public.hap.service.lightbulb'), get_id()) OnCharacteristicMixin.__init__(self, get_id()) BrightnessCharacteristicMixin.__init__(self, get_id()) HueCharacteristicMixin.__init__(self, get_id()) SaturationCharacteristicMixin.__init__(self, get_id())
def __init__(self, device): service = ServiceEntry( ServicesTypes.get_uuid('public.hap.service.pairing'), model_mixin.get_id(), ) super().__init__(device, service) self.characteristics.append(PairingSetupCharacteristicHandler(self)) self.characteristics.append(PairingVerifyCharacteristicHandler(self)) self.characteristics.append(PairingPairingsCharacteristicHandler(self))
def get_accessory_information(accessory): """Obtain the accessory information service of a HomeKit device.""" result = {} for service in accessory["services"]: stype = service["type"].upper() if ServicesTypes.get_short(stype) != "accessory-information": continue for characteristic in service["characteristics"]: ctype = CharacteristicsTypes.get_short(characteristic["type"]) if "value" in characteristic: result[ctype] = characteristic["value"] return result
def __init__(self, hass, entity_id, pairing, accessory): """Create a helper for a given accessory/entity.""" self.hass = hass self.entity_id = entity_id self.pairing = pairing self.accessory = accessory self.characteristics = {} for service in self.accessory.services: service_name = ServicesTypes.get_short(service.type) for char in service.characteristics: char_name = CharacteristicsTypes.get_short(char.type) self.characteristics[(service_name, char_name)] = char
def __init__(self, hass, entity_id, pairing, accessory): """Create a helper for a given accessory/entity.""" self.hass = hass self.entity_id = entity_id self.pairing = pairing self.accessory = accessory self.characteristics = {} for service in self.accessory.services: service_name = ServicesTypes.get_short(service.type) for char in service.characteristics: char_name = CharacteristicsTypes.get_short(char.type) self.characteristics[(service_name, char_name)] = char
def __init__(self, name, manufacturer, model, serialnumber, firmwarerevision): AbstractService.__init__( self, ServicesTypes.get_uuid('public.hap.service.accessory-information'), get_id()) self.characteristics.append(IdentifyCharacteristic(get_id())) self.characteristics.append( ManufacturerCharacteristic(get_id(), manufacturer)) self.characteristics.append(ModelCharacteristic(get_id(), model)) self.characteristics.append(NameCharacteristic(get_id(), name)) self.characteristics.append( SerialNumberCharacteristic(get_id(), serialnumber)) self.characteristics.append( FirmwareRevisionCharacteristic(get_id(), firmwarerevision))
def async_load_platforms(self): """Load any platforms needed by this HomeKit device.""" for accessory in self.accessories: for service in accessory["services"]: stype = ServicesTypes.get_short(service["type"].upper()) if stype not in HOMEKIT_ACCESSORY_DISPATCH: continue platform = HOMEKIT_ACCESSORY_DISPATCH[stype] if platform in self.platforms: continue self.hass.async_create_task( self.hass.config_entries.async_forward_entry_setup( self.config_entry, platform)) self.platforms.add(platform)
def get_accessory_information(accessory): """Obtain the accessory information service of a HomeKit device.""" # pylint: disable=import-error from homekit.model.services import ServicesTypes from homekit.model.characteristics import CharacteristicsTypes result = {} for service in accessory["services"]: stype = service["type"].upper() if ServicesTypes.get_short(stype) != "accessory-information": continue for characteristic in service["characteristics"]: ctype = CharacteristicsTypes.get_short(characteristic["type"]) if "value" in characteristic: result[ctype] = characteristic["value"] return result
def _add_new_entities(self, callbacks): for accessory in self.accessories: aid = accessory["aid"] for service in accessory["services"]: iid = service["iid"] stype = ServicesTypes.get_short(service["type"].upper()) service["stype"] = stype if (aid, iid) in self.entities: # Don't add the same entity again continue for listener in callbacks: if listener(aid, service): self.entities.append((aid, iid)) break
def get_serial(accessory): """Obtain the serial number of a HomeKit device.""" # pylint: disable=import-error from homekit.model.services import ServicesTypes from homekit.model.characteristics import CharacteristicsTypes for service in accessory['services']: if ServicesTypes.get_short(service['type']) != \ 'accessory-information': continue for characteristic in service['characteristics']: ctype = CharacteristicsTypes.get_short(characteristic['type']) if ctype != 'serial-number': continue return characteristic['value'] return None
def get_accessory_information(accessory): """Obtain the accessory information service of a HomeKit device.""" # pylint: disable=import-error from homekit.model.services import ServicesTypes from homekit.model.characteristics import CharacteristicsTypes result = {} for service in accessory['services']: stype = service['type'].upper() if ServicesTypes.get_short(stype) != 'accessory-information': continue for characteristic in service['characteristics']: ctype = CharacteristicsTypes.get_short(characteristic['type']) if 'value' in characteristic: result[ctype] = characteristic['value'] return result
def get_accessory_information(accessory): """Obtain the accessory information service of a HomeKit device.""" # pylint: disable=import-error from homekit.model.services import ServicesTypes from homekit.model.characteristics import CharacteristicsTypes result = {} for service in accessory['services']: stype = service['type'].upper() if ServicesTypes.get_short(stype) != 'accessory-information': continue for characteristic in service['characteristics']: ctype = CharacteristicsTypes.get_short(characteristic['type']) if 'value' in characteristic: result[ctype] = characteristic['value'] return result
def get_serial(accessory): """Obtain the serial number of a HomeKit device.""" # pylint: disable=import-error from homekit.model.services import ServicesTypes from homekit.model.characteristics import CharacteristicsTypes for service in accessory['services']: if ServicesTypes.get_short(service['type']) != \ 'accessory-information': continue for characteristic in service['characteristics']: ctype = CharacteristicsTypes.get_short( characteristic['type']) if ctype != 'serial-number': continue return characteristic['value'] return None
def _add_new_entities(self, callbacks): from homekit.model.services import ServicesTypes for accessory in self.accessories: aid = accessory['aid'] for service in accessory['services']: iid = service['iid'] stype = ServicesTypes.get_short(service['type'].upper()) service['stype'] = stype if (aid, iid) in self.entities: # Don't add the same entity again continue for listener in callbacks: if listener(aid, service): self.entities.append((aid, iid)) break
async def async_load_platforms(self): """Load any platforms needed by this HomeKit device.""" for accessory in self.accessories: for service in accessory["services"]: stype = ServicesTypes.get_short(service["type"].upper()) if stype not in HOMEKIT_ACCESSORY_DISPATCH: continue platform = HOMEKIT_ACCESSORY_DISPATCH[stype] if platform in self.platforms: continue self.platforms.add(platform) try: await self.opp.config_entries.async_forward_entry_setup( self.config_entry, platform) except Exception: self.platforms.remove(platform) raise
async def setup_test_component(hass, services, capitalize=False): """Load a fake homekit accessory based on a homekit accessory model. If capitalize is True, property names will be in upper case. """ domain = None for service in services: service_name = ServicesTypes.get_short(service.type) if service_name in HOMEKIT_ACCESSORY_DISPATCH: domain = HOMEKIT_ACCESSORY_DISPATCH[service_name] break assert domain, 'Cannot map test homekit services to homeassistant domain' config = { 'discovery': { } } with mock.patch('homekit.Controller') as controller: fake_controller = controller.return_value = FakeController() await async_setup_component(hass, DOMAIN, config) accessory = Accessory('TestDevice', 'example.com', 'Test', '0001', '0.1') accessory.services.extend(services) pairing = fake_controller.add(accessory) discovery_info = { 'host': '127.0.0.1', 'port': 8080, 'properties': { ('MD' if capitalize else 'md'): 'TestDevice', ('ID' if capitalize else 'id'): '00:00:00:00:00:00', ('C#' if capitalize else 'c#'): 1, } } fire_service_discovered(hass, SERVICE_HOMEKIT, discovery_info) await hass.async_block_till_done() return Helper(hass, '.'.join((domain, 'testdevice')), pairing, accessory)
async def setup_test_component(hass, services, capitalize=False, suffix=None): """Load a fake homekit accessory based on a homekit accessory model. If capitalize is True, property names will be in upper case. If suffix is set, entityId will include the suffix """ domain = None for service in services: service_name = ServicesTypes.get_short(service.type) if service_name in HOMEKIT_ACCESSORY_DISPATCH: domain = HOMEKIT_ACCESSORY_DISPATCH[service_name] break assert domain, 'Cannot map test homekit services to homeassistant domain' accessory = Accessory('TestDevice', 'example.com', 'Test', '0001', '0.1') accessory.services.extend(services) pairing = await setup_test_accessories(hass, [accessory]) entity = 'testdevice' if suffix is None else 'testdevice_{}'.format(suffix) return Helper(hass, '.'.join((domain, entity)), pairing, accessory)
async def setup_test_component(hass, services, capitalize=False, suffix=None): """Load a fake homekit accessory based on a homekit accessory model. If capitalize is True, property names will be in upper case. If suffix is set, entityId will include the suffix """ domain = None for service in services: service_name = ServicesTypes.get_short(service.type) if service_name in HOMEKIT_ACCESSORY_DISPATCH: domain = HOMEKIT_ACCESSORY_DISPATCH[service_name] break assert domain, 'Cannot map test homekit services to homeassistant domain' accessory = Accessory('TestDevice', 'example.com', 'Test', '0001', '0.1') accessory.services.extend(services) pairing = await setup_test_accessories(hass, [accessory], capitalize) entity = 'testdevice' if suffix is None else 'testdevice_{}'.format(suffix) return Helper(hass, '.'.join((domain, entity)), pairing, accessory)
async def setup_test_component(hass, services, capitalize=False, suffix=None): """Load a fake homekit accessory based on a homekit accessory model. If capitalize is True, property names will be in upper case. If suffix is set, entityId will include the suffix """ domain = None for service in services: service_name = ServicesTypes.get_short(service.type) if service_name in HOMEKIT_ACCESSORY_DISPATCH: domain = HOMEKIT_ACCESSORY_DISPATCH[service_name] break assert domain, "Cannot map test homekit services to Home Assistant domain" accessory = Accessory("TestDevice", "example.com", "Test", "0001", "0.1") accessory.services.extend(services) config_entry, pairing = await setup_test_accessories(hass, [accessory]) entity = "testdevice" if suffix is None else "testdevice_{}".format(suffix) return Helper(hass, ".".join((domain, entity)), pairing, accessory, config_entry)
async def setup_test_component(hass, services, capitalize=False, suffix=None): """Load a fake homekit accessory based on a homekit accessory model. If capitalize is True, property names will be in upper case. If suffix is set, entityId will include the suffix """ domain = None for service in services: service_name = ServicesTypes.get_short(service.type) if service_name in HOMEKIT_ACCESSORY_DISPATCH: domain = HOMEKIT_ACCESSORY_DISPATCH[service_name] break assert domain, 'Cannot map test homekit services to homeassistant domain' fake_controller = await setup_platform(hass) accessory = Accessory('TestDevice', 'example.com', 'Test', '0001', '0.1') accessory.services.extend(services) pairing = fake_controller.add([accessory]) discovery_info = { 'host': '127.0.0.1', 'port': 8080, 'properties': { ('MD' if capitalize else 'md'): 'TestDevice', ('ID' if capitalize else 'id'): '00:00:00:00:00:00', ('C#' if capitalize else 'c#'): 1, } } fire_service_discovered(hass, SERVICE_HOMEKIT, discovery_info) await hass.async_block_till_done() entity = 'testdevice' if suffix is None else 'testdevice_{}'.format(suffix) return Helper(hass, '.'.join((domain, entity)), pairing, accessory)
def test_get_short_no_baseid(self): self.assertEqual( ServicesTypes.get_short('00000023-0000-1000-8000-NOTBASEID'), 'Unknown Service: 00000023-0000-1000-8000-NOTBASEID')
data = json.load(sys.stdin) data = {"accessories": data} else: parser.print_help() assertions = [] success = True for accessory in data["accessories"]: aid = accessory['aid'] for service in accessory['services']: s_type = service['type'] s_iid = service['iid'] if args.print: print('{aid}.{iid}: >{stype}<'.format( aid=aid, iid=s_iid, stype=ServicesTypes.get_short(s_type))) for characteristic in service['characteristics']: c_iid = characteristic['iid'] value = characteristic.get('value', '') assertion = {} c_type = characteristic['type'] perms = ','.join(characteristic['perms']) desc = characteristic.get('description', '') c_type = CharacteristicsTypes.get_short(c_type) if 'maxLen' in characteristic: maxLen = characteristic.get('maxLen') if value is not None and len(value) <= maxLen:
def test_get_short_no_service(self): self.assertEqual( ServicesTypes.get_short('00000023-0000-1000-8000-0026BB765291'), 'Unknown Service: 00000023-0000-1000-8000-0026BB765291')
sys.exit(-1) if args.decode: data = decode_values(data) # prepare output if args.output == 'json': print(json.dumps(data, indent=4, cls=tlv8.JsonEncoder)) if args.output == 'compact': for accessory in data: aid = accessory['aid'] for service in accessory['services']: s_type = service['type'] s_iid = service['iid'] print('{aid}.{iid}: >{stype}<'.format(aid=aid, iid=s_iid, stype=ServicesTypes.get_short(s_type))) for characteristic in service['characteristics']: c_iid = characteristic['iid'] value = characteristic.get('value', '') c_type = characteristic['type'] c_format = characteristic['format'] # we need to get the entry list from the decoder into a string and reformat it for better # readability. if args.decode and c_format in [CharacteristicFormats.tlv8] and isinstance(value, tlv8.EntryList): value = tlv8.format_string(value) value = ' '.join(value.splitlines(keepends=True)) value = '\n ' + value perms = ','.join(characteristic['perms']) desc = characteristic.get('description', '') c_type = CharacteristicsTypes.get_short(c_type)
def test_get_short_lowercase(self): self.assertEqual( ServicesTypes.get_short('00000086-0000-1000-8000-0026bb765291'), 'occupancy')
def __init__(self, service_name): """Create a fake service by its short form HAP spec name.""" char_type = ServicesTypes.get_uuid(service_name) super().__init__(char_type, get_id())
def __init__(self, service_name): """Create a fake service by its short form HAP spec name.""" char_type = ServicesTypes.get_uuid(service_name) super().__init__(char_type, get_id())
def test_get_uuid(self): self.assertEqual(ServicesTypes.get_uuid('public.hap.service.doorbell'), '00000121-0000-1000-8000-0026BB765291')
def create_proxy(accessories_and_characteristics): """ Create a proxy in front of a set of accessories, services and characteristics. This allows to follow the communication of a controller and an accessory (e.g. an iPhone and some HomeKit IP camera). :param accessories_and_characteristics: the accessory data as described in the spec on page 73 and following. This contains all the data that will be used to create the proxy. :return: a list of `Accessory` instances whose services and characteristics are replaced by proxied versions. That means characteristic's callback functions for getting and setting values relay those calls to the proxied characteristics. """ accessories = [] logging.info('%<------ creating proxy ------') for accessory in accessories_and_characteristics: proxy_accessory = Accessory( '', '', '', '', '', ) aid = accessory['aid'] proxy_accessory.aid = aid logging.info('accessory with aid=%s', aid) proxy_accessory.services = [] accessories.append(proxy_accessory) for service in accessory['services']: service_iid = service['iid'] service_type = service['type'] short_type = ServicesTypes.get_short(service_type) logging.info(' %i.%i: >%s< (%s)', aid, service_iid, short_type, service_type) proxy_service = ProxyService(service_iid, service_type) proxy_accessory.add_service(proxy_service) for characteristic in service['characteristics']: characteristic_iid = characteristic['iid'] characteristic_type = characteristic['type'] short_type = CharacteristicsTypes.get_short( characteristic_type) characteristic_format = characteristic['format'] characteristic_value = characteristic.get('value') characteristic_perms = characteristic['perms'] logging.info(' %i.%i: %s >%s< (%s) [%s] %s', aid, characteristic_iid, characteristic_value, short_type, characteristic_type, ','.join(characteristic_perms), characteristic_format) proxy_characteristic = ProxyCharacteristic( characteristic_iid, characteristic_type, characteristic_format) proxy_service.append_characteristic(proxy_characteristic) if characteristic_value: proxy_characteristic.value = characteristic_value proxy_characteristic.perms = characteristic_perms proxy_characteristic.set_set_value_callback( generate_set_value_callback(accessory['aid'], proxy_characteristic)) proxy_characteristic.set_get_value_callback( generate_get_value_callback(accessory['aid'], proxy_characteristic)) logging.info('%<------ finished creating proxy ------') return accessories
def __init__(self): AbstractService.__init__( self, ServicesTypes.get_uuid('public.hap.service.fan'), get_id()) OnCharacteristicMixin.__init__(self, get_id())