Ejemplo n.º 1
0
async def main():
    """Run Main execution."""
    if args.debug:
        logging.basicConfig(
            level=logging.DEBUG,
            format="%(asctime)-15s %(levelname)-5s %(name)s -- %(message)s",
        )
        asyncio.get_running_loop().set_debug(True)

    async with HueBridgeV2(args.host, args.appkey) as bridge:

        print("Connected to bridge: ", bridge.bridge_id)
        print(bridge.config.bridge_device)

        # pick a random light
        light = bridge.lights.items[0]
        print(f"Sending 100 requests to bridge for {light.name}...")

        async def toggle_light():
            await bridge.lights.turn_on(light.id)
            await bridge.lights.turn_off(light.id)

        before = time.time()
        await asyncio.gather(*[toggle_light() for i in range(0, 50)])
        after = time.time()
        print(f"Completed in {after-before} seconds...")
Ejemplo n.º 2
0
def light_entities_for_group(group_name):
    """Find light entity IDs for the Philips Hue zone/room name.

    All configured Hue bridges are queried for the group. Pyscript must be configured to expose the
    "hass" global variable and allow all imports so that we can access the bridge configs and entity
    registry.

    :param group_name: The Hue zone/room name exactly as it appears in the Hue app (e.g. "Living room").
    :return: Set of light entity IDs for the group name or empty set if no matching group or entities are found.
    """
    entity_ids = set()

    # Load entity registry.
    entity_registry = er.async_get_registry(hass)
    # Find Hue bridge config(s).
    for config_entry in hass.config_entries.async_entries(domain="hue"):
        host, api_key = config_entry.data["host"], config_entry.data["api_key"]
        async with HueBridgeV2(host, api_key) as bridge:
            # Query Hue bridge for light services in the matching group(s), if any.
            for group in bridge.groups:
                if not hasattr(group, "metadata") or group.metadata.name != group_name:
                    continue

                lights_resolver = bridge.groups.room if group.type == ResourceTypes.ROOM else bridge.groups.zone
                light_ids = {light.id for light in lights_resolver.get_lights(group.id)}
                group_entity_ids = {
                    id
                    for id, entity in entity_registry.entities.items()
                    if entity.unique_id in light_ids and entity.platform == "hue"
                }
                log.debug(f"Found Hue group '{group_name}' on {host}; lights: {group_entity_ids}")
                entity_ids |= group_entity_ids
    return entity_ids
Ejemplo n.º 3
0
async def test_bridge_init(v2_resources):
    """Test v2 bridge."""
    bridge = HueBridgeV2("192.168.1.123", "mock-key")
    assert bridge.host == "192.168.1.123"

    with patch.object(bridge, "request", return_value=v2_resources):
        await bridge.fetch_full_state()

    assert bridge.config is not None
    assert bridge.config.bridge_id == "aabbccddeeffggh"

    assert bridge.devices is not None
    assert len(
        bridge.devices.get_lights("0b216218-d811-4c95-8c55-bbcda50f9d50")) == 1
    assert len(
        bridge.devices.get_sensors(
            "342daec9-391b-480b-abdd-87f1aa04ce3b")) == 6

    assert bridge.lights is not None
    assert bridge.scenes is not None
    assert bridge.sensors is not None
    assert bridge.groups is not None

    # test required version check
    assert bridge.config.check_version("1.50.1950111030") is False
    assert bridge.config.check_version("1.48.1948086000") is True
    assert bridge.config.check_version("1.48.1948085000") is True
Ejemplo n.º 4
0
async def main():
    """Run Main execution."""
    if args.debug:
        logging.basicConfig(
            level=logging.DEBUG,
            format="%(asctime)-15s %(levelname)-5s %(name)s -- %(message)s",
        )

    async with HueBridgeV2(args.host, args.appkey) as bridge:

        print("Connected to bridge: ", bridge.bridge_id)
        print(bridge.config.bridge_device)

        print()
        print("found lights:")
        for item in bridge.lights:
            print(item.metadata.name)

        print()
        print("found devices:")
        for item in bridge.devices:
            print(item.metadata.name)

        # turn on a light
        light = next(x for x in bridge.lights.items if x.supports_color)
        print("Turning on light", light.name)
        await bridge.lights.turn_on(light.id)
        await asyncio.sleep(1)
        print("Set brightness 100 to light", light.name)
        await bridge.lights.set_brightness(light.id, 100, 2000)
        await asyncio.sleep(2)
        print("Set color to light", light.name)
        await bridge.lights.set_color(light.id, 0.141, 0.123, 2000)
        await asyncio.sleep(1)
        print("Turning off light", light.name)
        await bridge.lights.turn_off(light.id, 2000)

        print()
        print("Subscribing to events...")

        def print_event(event_type, item):
            print()
            print("received event", event_type.value, item)
            print()

        bridge.subscribe(print_event)

        await asyncio.sleep(3600)
Ejemplo n.º 5
0
Archivo: bridge.py Proyecto: 2Fake/core
 def __init__(self, hass: core.HomeAssistant,
              config_entry: ConfigEntry) -> None:
     """Initialize the system."""
     self.config_entry = config_entry
     self.hass = hass
     self.authorized = False
     # Jobs to be executed when API is reset.
     self.reset_jobs: list[core.CALLBACK_TYPE] = []
     self.sensor_manager: SensorManager | None = None
     self.logger = logging.getLogger(__name__)
     # store actual api connection to bridge as api
     app_key: str = self.config_entry.data[CONF_API_KEY]
     websession = aiohttp_client.async_get_clientsession(hass)
     if self.api_version == 1:
         self.api = HueBridgeV1(self.host, app_key, websession)
     else:
         self.api = HueBridgeV2(self.host, app_key, websession)
     # store (this) bridge object in hass data
     hass.data.setdefault(DOMAIN, {})[self.config_entry.entry_id] = self
Ejemplo n.º 6
0
async def handle_v2_migration(hass: core.HomeAssistant,
                              entry: ConfigEntry) -> None:
    """Perform migration of devices and entities to V2 Id's."""
    host = entry.data[CONF_HOST]
    api_key = entry.data[CONF_API_KEY]
    websession = aiohttp_client.async_get_clientsession(hass)
    dev_reg = async_get_device_registry(hass)
    ent_reg = async_get_entity_registry(hass)
    LOGGER.info(
        "Start of migration of devices and entities to support API schema 2")
    # initialize bridge connection just for the migration
    async with HueBridgeV2(host, api_key, websession) as api:

        sensor_class_mapping = {
            DEVICE_CLASS_BATTERY: ResourceTypes.DEVICE_POWER,
            DEVICE_CLASS_MOTION: ResourceTypes.MOTION,
            DEVICE_CLASS_ILLUMINANCE: ResourceTypes.LIGHT_LEVEL,
            DEVICE_CLASS_TEMPERATURE: ResourceTypes.TEMPERATURE,
        }

        # handle entities attached to device
        for hue_dev in api.devices:
            zigbee = api.devices.get_zigbee_connectivity(hue_dev.id)
            if not zigbee or not zigbee.mac_address:
                # not a zigbee device or invalid mac
                continue
            # get/update existing device by V1 identifier (mac address)
            # the device will now have both the old and the new identifier
            identifiers = {(DOMAIN, hue_dev.id), (DOMAIN, zigbee.mac_address)}
            hass_dev = dev_reg.async_get_or_create(
                config_entry_id=entry.entry_id, identifiers=identifiers)
            LOGGER.info("Migrated device %s (%s)", hass_dev.name, hass_dev.id)
            # loop through al entities for device and find match
            for ent in async_entries_for_device(ent_reg, hass_dev.id, True):
                # migrate light
                if ent.entity_id.startswith("light"):
                    # should always return one lightid here
                    new_unique_id = next(iter(hue_dev.lights))
                    if ent.unique_id == new_unique_id:
                        continue  # just in case
                    LOGGER.info(
                        "Migrating %s from unique id %s to %s",
                        ent.entity_id,
                        ent.unique_id,
                        new_unique_id,
                    )
                    ent_reg.async_update_entity(ent.entity_id,
                                                new_unique_id=new_unique_id)
                    continue
                # migrate sensors
                matched_dev_class = sensor_class_mapping.get(
                    ent.original_device_class or "unknown")
                if matched_dev_class is None:
                    # this may happen if we're looking at orphaned or unsupported entity
                    LOGGER.warning(
                        "Skip migration of %s because it no longer exists on the bridge",
                        ent.entity_id,
                    )
                    continue
                for sensor in api.devices.get_sensors(hue_dev.id):
                    if sensor.type != matched_dev_class:
                        continue
                    new_unique_id = sensor.id
                    if ent.unique_id == new_unique_id:
                        break  # just in case
                    LOGGER.info(
                        "Migrating %s from unique id %s to %s",
                        ent.entity_id,
                        ent.unique_id,
                        new_unique_id,
                    )
                    try:
                        ent_reg.async_update_entity(ent.entity_id,
                                                    new_unique_id=sensor.id)
                    except ValueError:
                        # assume edge case where the entity was already migrated in a previous run
                        # which got aborted somehow and we do not want
                        # to crash the entire integration init
                        LOGGER.warning(
                            "Skip migration of %s because it already exists",
                            ent.entity_id,
                        )
                    break

        # migrate entities that are not connected to a device (groups)
        for ent in entities_for_config_entry(ent_reg, entry.entry_id):
            if ent.device_id is not None:
                continue
            v1_id = f"/groups/{ent.unique_id}"
            hue_group = api.groups.room.get_by_v1_id(v1_id)
            if hue_group is None or hue_group.grouped_light is None:
                # try again with zone
                hue_group = api.groups.zone.get_by_v1_id(v1_id)
            if hue_group is None or hue_group.grouped_light is None:
                # this may happen if we're looking at some orphaned entity
                LOGGER.warning(
                    "Skip migration of %s because it no longer exist on the bridge",
                    ent.entity_id,
                )
                continue
            new_unique_id = hue_group.grouped_light
            LOGGER.info(
                "Migrating %s from unique id %s to %s ",
                ent.entity_id,
                ent.unique_id,
                new_unique_id,
            )
            try:
                ent_reg.async_update_entity(ent.entity_id,
                                            new_unique_id=new_unique_id)
            except ValueError:
                # assume edge case where the entity was already migrated in a previous run
                # which got aborted somehow and we do not want
                # to crash the entire integration init
                LOGGER.warning(
                    "Skip migration of %s because it already exists",
                    ent.entity_id,
                )
    LOGGER.info(
        "Migration of devices and entities to support API schema 2 finished")
Ejemplo n.º 7
0
async def handle_v2_migration(hass: core.HomeAssistant,
                              entry: ConfigEntry) -> None:
    """Perform migration of devices and entities to V2 Id's."""
    host = entry.data[CONF_HOST]
    api_key = entry.data[CONF_API_KEY]
    websession = aiohttp_client.async_get_clientsession(hass)
    dev_reg = async_get_device_registry(hass)
    ent_reg = async_get_entity_registry(hass)
    LOGGER.info(
        "Start of migration of devices and entities to support API schema 2")

    # Create mapping of mac address to HA device id's.
    # Identifier in dev reg should be mac-address,
    # but in some cases it has a postfix like `-0b` or `-01`.
    dev_ids = {}
    for hass_dev in devices_for_config_entries(dev_reg, entry.entry_id):
        for domain, mac in hass_dev.identifiers:
            if domain != DOMAIN:
                continue
            normalized_mac = mac.split("-")[0]
            dev_ids[normalized_mac] = hass_dev.id

    # initialize bridge connection just for the migration
    async with HueBridgeV2(host, api_key, websession) as api:

        sensor_class_mapping = {
            SensorDeviceClass.BATTERY.value: ResourceTypes.DEVICE_POWER,
            BinarySensorDeviceClass.MOTION.value: ResourceTypes.MOTION,
            SensorDeviceClass.ILLUMINANCE.value: ResourceTypes.LIGHT_LEVEL,
            SensorDeviceClass.TEMPERATURE.value: ResourceTypes.TEMPERATURE,
        }

        # migrate entities attached to a device
        for hue_dev in api.devices:
            zigbee = api.devices.get_zigbee_connectivity(hue_dev.id)
            if not zigbee or not zigbee.mac_address:
                # not a zigbee device or invalid mac
                continue

            # get existing device by V1 identifier (mac address)
            if hue_dev.product_data.product_archetype == DeviceArchetypes.BRIDGE_V2:
                hass_dev_id = dev_ids.get(api.config.bridge_id.upper())
            else:
                hass_dev_id = dev_ids.get(zigbee.mac_address)
            if hass_dev_id is None:
                # can be safely ignored, this device does not exist in current config
                LOGGER.debug(
                    "Ignoring device %s (%s) as it does not (yet) exist in the device registry",
                    hue_dev.metadata.name,
                    hue_dev.id,
                )
                continue
            dev_reg.async_update_device(hass_dev_id,
                                        new_identifiers={(DOMAIN, hue_dev.id)})
            LOGGER.info("Migrated device %s (%s)", hue_dev.metadata.name,
                        hass_dev_id)

            # loop through all entities for device and find match
            for ent in async_entries_for_device(ent_reg, hass_dev_id, True):

                if ent.entity_id.startswith("light"):
                    # migrate light
                    # should always return one lightid here
                    new_unique_id = next(iter(hue_dev.lights), None)
                else:
                    # migrate sensors
                    matched_dev_class = sensor_class_mapping.get(
                        ent.original_device_class or "unknown")
                    new_unique_id = next(
                        (sensor.id
                         for sensor in api.devices.get_sensors(hue_dev.id)
                         if sensor.type == matched_dev_class),
                        None,
                    )

                if new_unique_id is None:
                    # this may happen if we're looking at orphaned or unsupported entity
                    LOGGER.warning(
                        "Skip migration of %s because it no longer exists on the bridge",
                        ent.entity_id,
                    )
                    continue

                try:
                    ent_reg.async_update_entity(ent.entity_id,
                                                new_unique_id=new_unique_id)
                except ValueError:
                    # assume edge case where the entity was already migrated in a previous run
                    # which got aborted somehow and we do not want
                    # to crash the entire integration init
                    LOGGER.warning(
                        "Skip migration of %s because it already exists",
                        ent.entity_id,
                    )
                else:
                    LOGGER.info(
                        "Migrated entity %s from unique id %s to %s",
                        ent.entity_id,
                        ent.unique_id,
                        new_unique_id,
                    )

        # migrate entities that are not connected to a device (groups)
        for ent in entities_for_config_entry(ent_reg, entry.entry_id):
            if ent.device_id is not None:
                continue
            if "-" in ent.unique_id:
                # handle case where unique id is v2-id of group/zone
                hue_group = api.groups.get(ent.unique_id)
            else:
                # handle case where the unique id is just the v1 id
                v1_id = f"/groups/{ent.unique_id}"
                hue_group = api.groups.room.get_by_v1_id(
                    v1_id) or api.groups.zone.get_by_v1_id(v1_id)
            if hue_group is None or hue_group.grouped_light is None:
                # this may happen if we're looking at some orphaned entity
                LOGGER.warning(
                    "Skip migration of %s because it no longer exist on the bridge",
                    ent.entity_id,
                )
                continue
            new_unique_id = hue_group.grouped_light
            LOGGER.info(
                "Migrating %s from unique id %s to %s ",
                ent.entity_id,
                ent.unique_id,
                new_unique_id,
            )
            try:
                ent_reg.async_update_entity(ent.entity_id,
                                            new_unique_id=new_unique_id)
            except ValueError:
                # assume edge case where the entity was already migrated in a previous run
                # which got aborted somehow and we do not want
                # to crash the entire integration init
                LOGGER.warning(
                    "Skip migration of %s because it already exists",
                    ent.entity_id,
                )
    LOGGER.info(
        "Migration of devices and entities to support API schema 2 finished")