Exemple #1
0
def test_clamp_enum_valid_vals():
    a = Accessory()
    tv_service = a.add_service(service_type=ServicesTypes.TELEVISION)
    char = tv_service.add_char(
        CharacteristicsTypes.REMOTE_KEY,
        valid_values=[RemoteKeyValues.PLAY_PAUSE],
        min_value=None,
        max_value=None,
    )

    valid_vals = clamp_enum_to_char(RemoteKeyValues, char)
    assert valid_vals == {RemoteKeyValues.PLAY_PAUSE}
Exemple #2
0
def test_clamp_enum_min_max_single_press():
    a = Accessory()
    tv_service = a.add_service(service_type=ServicesTypes.STATELESS_PROGRAMMABLE_SWITCH)
    char = tv_service.add_char(
        CharacteristicsTypes.INPUT_EVENT,
        valid_values=None,
        min_value=InputEventValues.SINGLE_PRESS,
        max_value=InputEventValues.SINGLE_PRESS,
    )

    valid_vals = clamp_enum_to_char(InputEventValues, char)

    assert valid_vals == {InputEventValues.SINGLE_PRESS}
Exemple #3
0
def test_clamp_enum_min_max_unclamped_button_press():
    a = Accessory()
    tv_service = a.add_service(service_type=ServicesTypes.STATELESS_PROGRAMMABLE_SWITCH)
    char = tv_service.add_char(
        CharacteristicsTypes.INPUT_EVENT,
    )

    valid_vals = clamp_enum_to_char(InputEventValues, char)

    assert valid_vals == {
        InputEventValues.SINGLE_PRESS,
        InputEventValues.DOUBLE_PRESS,
        InputEventValues.LONG_PRESS,
    }
Exemple #4
0
async def setup_test_component(hass,
                               setup_accessory,
                               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
    """
    accessory = Accessory.create_with_info("TestDevice", "example.com", "Test",
                                           "0001", "0.1")
    setup_accessory(accessory)

    domain = None
    for service in accessory.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"

    config_entry, pairing = await setup_test_accessories(hass, [accessory])
    entity = "testdevice" if suffix is None else f"testdevice_{suffix}"
    return Helper(hass, ".".join((domain, entity)), pairing, accessory,
                  config_entry)
Exemple #5
0
async def test_update_named_service_events_manual_accessory_auto_requires():
    accessories = Accessories()
    accessory = Accessory.create_with_info(
        name="TestLight",
        manufacturer="Test Mfr",
        model="Test Bulb",
        serial_number="1234",
        firmware_revision="1.1",
    )
    service = accessory.add_service(ServicesTypes.LIGHTBULB,
                                    name="Light Strip",
                                    add_required=True)
    on_char = service[CharacteristicsTypes.ON]
    accessories.add_accessory(accessory)

    controller = FakeController()
    pairing = await controller.add_paired_device(accessories, "alias")

    callback = mock.Mock()
    await pairing.subscribe([(accessory.aid, on_char.iid)])
    pairing.dispatcher_connect(callback)

    # Simulate that the state was changed on the device itself.
    pairing.testing.update_named_service("Light Strip",
                                         {CharacteristicsTypes.ON: True})

    assert callback.call_args_list == [
        mock.call({(accessory.aid, on_char.iid): {
                       "value": 1
                   }})
    ]
async def test_parse_old_homekit_json(hass):
    """Test migrating original .homekit/hk-00:00:00:00:00:00 files."""
    accessory = Accessory.create_with_info("TestDevice", "example.com", "Test",
                                           "0001", "0.1")
    service = accessory.add_service(ServicesTypes.LIGHTBULB)
    on_char = service.add_char(CharacteristicsTypes.ON)
    on_char.value = 0

    accessories = Accessories()
    accessories.add_accessory(accessory)

    fake_controller = await setup_platform(hass)
    pairing = await fake_controller.add_paired_device(accessories,
                                                      "00:00:00:00:00:00")
    pairing.pairing_data = {"AccessoryPairingID": "00:00:00:00:00:00"}

    mock_path = mock.Mock()
    mock_path.exists.side_effect = [False, True]

    mock_listdir = mock.Mock()
    mock_listdir.return_value = ["hk-00:00:00:00:00:00", "pairings.json"]

    read_data = {"AccessoryPairingID": "00:00:00:00:00:00"}
    mock_open = mock.mock_open(read_data=json.dumps(read_data))

    discovery_info = {
        "name": "TestDevice",
        "host": "127.0.0.1",
        "port": 8080,
        "properties": {
            "md": "TestDevice",
            "id": "00:00:00:00:00:00",
            "c#": 1,
            "sf": 0
        },
    }

    flow = _setup_flow_handler(hass)

    pairing_cls_imp = (
        "homeassistant.components.homekit_controller.config_flow.IpPairing")

    with mock.patch(pairing_cls_imp) as pairing_cls:
        pairing_cls.return_value = pairing
        with mock.patch("builtins.open", mock_open):
            with mock.patch("os.path", mock_path):
                with mock.patch("os.listdir", mock_listdir):
                    result = await flow.async_step_zeroconf(discovery_info)

    assert result["type"] == "create_entry"
    assert result["title"] == "TestDevice"
    assert result["data"]["AccessoryPairingID"] == "00:00:00:00:00:00"
    assert flow.context == {
        "hkid": "00:00:00:00:00:00",
        "title_placeholders": {
            "name": "TestDevice"
        },
        "unique_id": "00:00:00:00:00:00",
    }
Exemple #7
0
async def test_ble_device_only_checks_is_available(hass, controller):
    """Test a BLE device only checks is_available."""

    is_available = False

    class FakeBLEPairing(FakePairing):
        """Fake BLE pairing that can flip is_available."""
        @property
        def transport(self):
            return Transport.BLE

        @property
        def is_connected(self):
            return False

        @property
        def is_available(self):
            nonlocal is_available
            return is_available

        async def async_populate_accessories_state(self, *args, **kwargs):
            nonlocal is_available
            if not is_available:
                raise AccessoryNotFoundError("any")
            await super().async_populate_accessories_state(*args, **kwargs)

        async def get_characteristics(self, chars, *args, **kwargs):
            nonlocal is_available
            if not is_available:
                raise AccessoryNotFoundError("any")
            return {}

    accessory = Accessory.create_with_info("TestDevice", "example.com", "Test",
                                           "0001", "0.1")
    create_alive_service(accessory)

    with patch("aiohomekit.testing.FakePairing", FakeBLEPairing):
        await async_setup_component(hass, DOMAIN, {})
        config_entry, _ = await setup_test_accessories_with_controller(
            hass, [accessory], controller)
        await hass.async_block_till_done()

    assert config_entry.state == ConfigEntryState.SETUP_RETRY

    is_available = True

    async_fire_time_changed(hass, utcnow() + timedelta(seconds=10))
    await hass.async_block_till_done()
    assert config_entry.state == ConfigEntryState.LOADED
    assert hass.states.get("light.testdevice").state == STATE_OFF

    is_available = False
    async_fire_time_changed(hass, utcnow() + timedelta(hours=1))
    assert hass.states.get("light.testdevice").state == STATE_UNAVAILABLE

    is_available = True
    async_fire_time_changed(hass, utcnow() + timedelta(hours=1))
    assert hass.states.get("light.testdevice").state == STATE_OFF
Exemple #8
0
async def controller_and_unpaired_accessory(request, loop):
    config_file = tempfile.NamedTemporaryFile()
    config_file.write("""{
        "accessory_ltpk": "7986cf939de8986f428744e36ed72d86189bea46b4dcdc8d9d79a3e4fceb92b9",
        "accessory_ltsk": "3d99f3e959a1f93af4056966f858074b2a1fdec1c5fd84a51ea96f9fa004156a",
        "accessory_pairing_id": "12:34:56:00:01:0A",
        "accessory_pin": "031-45-154",
        "c#": 1,
        "category": "Lightbulb",
        "host_ip": "127.0.0.1",
        "host_port": 51842,
        "name": "unittestLight",
        "unsuccessful_tries": 0
    }""".encode())
    config_file.flush()

    # Make sure get_id() numbers are stable between tests
    model_mixin.id_counter = 0

    httpd = AccessoryServer(config_file.name, None)
    accessory = Accessory.create_with_info("Testlicht", "lusiardi.de",
                                           "Demoserver", "0001", "0.1")
    lightBulbService = accessory.add_service(ServicesTypes.LIGHTBULB)
    lightBulbService.add_char(CharacteristicsTypes.ON, value=False)
    httpd.add_accessory(accessory)

    t = threading.Thread(target=httpd.serve_forever)
    t.start()

    controller = Controller()

    for i in range(10):
        if port_ready(51842):
            break
        time.sleep(1)

    with mock.patch("aiohomekit.zeroconf._find_data_for_device_id") as find:
        find.return_value = {
            "address": "127.0.0.1",
            "port": 51842,
            "id": "12:34:56:00:01:0A",
        }
        with mock.patch.object(controller, "load_data", lambda x: None):
            with mock.patch("aiohomekit.__main__.Controller") as c:
                c.return_value = controller
                yield controller

    try:
        await asyncio.shield(controller.shutdown())
    except asyncio.CancelledError:
        pass

    httpd.shutdown()
    t.join()
Exemple #9
0
def create_service_with_ecobee_mode(accessory: Accessory):
    """Define a thermostat with ecobee mode characteristics."""
    service = accessory.add_service(ServicesTypes.THERMOSTAT,
                                    add_required=True)

    current_mode = service.add_char(
        CharacteristicsTypes.VENDOR_ECOBEE_CURRENT_MODE)
    current_mode.value = 0
    current_mode.perms.append("ev")

    service.add_char(CharacteristicsTypes.VENDOR_ECOBEE_SET_HOLD_SCHEDULE)

    return service
Exemple #10
0
async def test_offline_device_raises(hass, controller):
    """Test an offline device raises ConfigEntryNotReady."""

    is_connected = False

    class OfflineFakePairing(FakePairing):
        """Fake pairing that can flip is_connected."""
        @property
        def is_connected(self):
            nonlocal is_connected
            return is_connected

        @property
        def is_available(self):
            return self.is_connected

        async def async_populate_accessories_state(self, *args, **kwargs):
            nonlocal is_connected
            if not is_connected:
                raise AccessoryNotFoundError("any")
            await super().async_populate_accessories_state(*args, **kwargs)

        async def get_characteristics(self, chars, *args, **kwargs):
            nonlocal is_connected
            if not is_connected:
                raise AccessoryNotFoundError("any")
            return {}

    accessory = Accessory.create_with_info("TestDevice", "example.com", "Test",
                                           "0001", "0.1")
    create_alive_service(accessory)

    with patch("aiohomekit.testing.FakePairing", OfflineFakePairing):
        await async_setup_component(hass, DOMAIN, {})
        config_entry, _ = await setup_test_accessories_with_controller(
            hass, [accessory], controller)
        await hass.async_block_till_done()

    assert config_entry.state == ConfigEntryState.SETUP_RETRY

    is_connected = True

    async_fire_time_changed(hass, utcnow() + timedelta(seconds=10))
    await hass.async_block_till_done()
    assert config_entry.state == ConfigEntryState.LOADED
    assert hass.states.get("light.testdevice").state == STATE_OFF
Exemple #11
0
def setup_mock_accessory(controller):
    """Add a bridge accessory to a test controller."""
    bridge = Accessories()

    accessory = Accessory.create_with_info(
        name="Koogeek-LS1-20833F",
        manufacturer="Koogeek",
        model="LS1",
        serial_number="12345",
        firmware_revision="1.1",
    )

    service = accessory.add_service(ServicesTypes.LIGHTBULB)
    on_char = service.add_char(CharacteristicsTypes.ON)
    on_char.value = 0

    bridge.add_accessory(accessory)

    return controller.add_device(bridge)
Exemple #12
0
    def __init__(
        self,
        accessory: Accessory,
        service_type: str,
        name: str | None = None,
        add_required: bool = False,
    ):
        self.type = normalize_uuid(service_type)

        self.accessory = accessory
        self.iid = accessory.get_next_id()
        self.characteristics = Characteristics()
        self.characteristics_by_type = {}
        self.linked = []

        if name:
            char = self.add_char(CharacteristicsTypes.NAME)
            char.set_value(name)

        if add_required:
            for required in services[self.type]["required"]:
                if required not in self.characteristics_by_type:
                    self.add_char(required)
Exemple #13
0
async def controller_and_paired_accessory(request, loop):
    config_file = tempfile.NamedTemporaryFile()
    config_file.write("""{
        "accessory_ltpk": "7986cf939de8986f428744e36ed72d86189bea46b4dcdc8d9d79a3e4fceb92b9",
        "accessory_ltsk": "3d99f3e959a1f93af4056966f858074b2a1fdec1c5fd84a51ea96f9fa004156a",
        "accessory_pairing_id": "12:34:56:00:01:0A",
        "accessory_pin": "031-45-154",
        "c#": 1,
        "category": "Lightbulb",
        "host_ip": "127.0.0.1",
        "host_port": 51842,
        "name": "unittestLight",
        "peers": {
            "decc6fa3-de3e-41c9-adba-ef7409821bfc": {
                "admin": true,
                "key": "d708df2fbf4a8779669f0ccd43f4962d6d49e4274f88b1292f822edc3bcf8ed8"
            }
        },
        "unsuccessful_tries": 0
    }""".encode())
    config_file.flush()

    # Make sure get_id() numbers are stable between tests
    model_mixin.id_counter = 0

    httpd = AccessoryServer(config_file.name, None)
    accessory = Accessory.create_with_info("Testlicht", "lusiardi.de",
                                           "Demoserver", "0001", "0.1")
    lightBulbService = accessory.add_service(ServicesTypes.LIGHTBULB)
    lightBulbService.add_char(CharacteristicsTypes.ON, value=False)
    httpd.add_accessory(accessory)

    t = threading.Thread(target=httpd.serve_forever)
    t.start()

    controller_file = tempfile.NamedTemporaryFile()
    controller_file.write("""{
        "alias": {
            "Connection": "IP",
            "iOSDeviceLTPK": "d708df2fbf4a8779669f0ccd43f4962d6d49e4274f88b1292f822edc3bcf8ed8",
            "iOSPairingId": "decc6fa3-de3e-41c9-adba-ef7409821bfc",
            "AccessoryLTPK": "7986cf939de8986f428744e36ed72d86189bea46b4dcdc8d9d79a3e4fceb92b9",
            "AccessoryPairingID": "12:34:56:00:01:0A",
            "AccessoryPort": 51842,
            "AccessoryIP": "127.0.0.1",
            "iOSDeviceLTSK": "fa45f082ef87efc6c8c8d043d74084a3ea923a2253e323a7eb9917b4090c2fcc"
        }
    }""".encode())
    controller_file.flush()

    controller = Controller()
    controller.load_data(controller_file.name)
    config_file.close()

    for i in range(10):
        if port_ready(51842):
            break
        time.sleep(1)

    with mock.patch("aiohomekit.zeroconf._find_data_for_device_id") as find:
        find.return_value = {
            "address": "127.0.0.1",
            "port": 51842,
            "id": "12:34:56:00:01:0A",
        }
        with mock.patch.object(controller, "load_data", lambda x: None):
            with mock.patch("aiohomekit.__main__.Controller") as c:
                c.return_value = controller
                yield controller

    try:
        await asyncio.shield(controller.shutdown())
    except asyncio.CancelledError:
        pass

    httpd.shutdown()
    t.join()