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...")
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
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
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)
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
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")
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")