async def test_get_supported_features_prioritize_state(hass): """Test get_supported_features gives priority to state.""" entity_reg = mock_registry(hass) entity_id = entity_reg.async_get_or_create( "hello", "world", "5678", supported_features=456).entity_id assert entity.get_supported_features(hass, entity_id) == 456 hass.states.async_set(entity_id, None, {"supported_features": 123}) assert entity.get_supported_features(hass, entity_id) == 123
async def async_get_conditions(hass: HomeAssistant, device_id: str) -> list[dict[str, str]]: """List device conditions for Climate devices.""" registry = await entity_registry.async_get_registry(hass) conditions = [] # Get all the integrations entities for this device for entry in entity_registry.async_entries_for_device(registry, device_id): if entry.domain != DOMAIN: continue supported_features = get_supported_features(hass, entry.entity_id) base_condition = { CONF_CONDITION: "device", CONF_DEVICE_ID: device_id, CONF_DOMAIN: DOMAIN, CONF_ENTITY_ID: entry.entity_id, } conditions.append({**base_condition, CONF_TYPE: "is_hvac_mode"}) if supported_features & const.SUPPORT_PRESET_MODE: conditions.append({**base_condition, CONF_TYPE: "is_preset_mode"}) return conditions
async def test_get_supported_features_entity_registry(hass): """Test get_supported_features falls back to entity registry.""" entity_reg = mock_registry(hass) entity_id = entity_reg.async_get_or_create( "hello", "world", "5678", supported_features=456 ).entity_id assert entity.get_supported_features(hass, entity_id) == 456
async def async_get_actions(hass: HomeAssistant, device_id: str) -> list[dict[str, str]]: """List device actions.""" actions = await toggle_entity.async_get_actions(hass, device_id, DOMAIN) entity_registry = er.async_get(hass) for entry in er.async_entries_for_device(entity_registry, device_id): if entry.domain != DOMAIN: continue supported_color_modes = get_supported_color_modes( hass, entry.entity_id) supported_features = get_supported_features(hass, entry.entity_id) base_action = { CONF_DEVICE_ID: device_id, CONF_DOMAIN: DOMAIN, CONF_ENTITY_ID: entry.entity_id, } if brightness_supported(supported_color_modes): actions.extend(( { **base_action, CONF_TYPE: TYPE_BRIGHTNESS_INCREASE }, { **base_action, CONF_TYPE: TYPE_BRIGHTNESS_DECREASE }, )) if supported_features & LightEntityFeature.FLASH: actions.append({**base_action, CONF_TYPE: TYPE_FLASH}) return actions
async def async_get_action_capabilities( hass: HomeAssistant, config: ConfigType) -> dict[str, vol.Schema]: """List action capabilities.""" if config[CONF_TYPE] != toggle_entity.CONF_TURN_ON: return {} try: supported_color_modes = get_supported_color_modes( hass, config[ATTR_ENTITY_ID]) except HomeAssistantError: supported_color_modes = None try: supported_features = get_supported_features(hass, config[ATTR_ENTITY_ID]) except HomeAssistantError: supported_features = 0 extra_fields = {} if brightness_supported(supported_color_modes): extra_fields[vol.Optional(ATTR_BRIGHTNESS_PCT)] = VALID_BRIGHTNESS_PCT if supported_features & LightEntityFeature.FLASH: extra_fields[vol.Optional(ATTR_FLASH)] = VALID_FLASH return {"extra_fields": vol.Schema(extra_fields)} if extra_fields else {}
async def async_get_actions(hass: HomeAssistant, device_id: str) -> list[dict[str, str]]: """List device actions for Alarm control panel devices.""" registry = await entity_registry.async_get_registry(hass) actions = [] # Get all the integrations entities for this device for entry in entity_registry.async_entries_for_device(registry, device_id): if entry.domain != DOMAIN: continue supported_features = get_supported_features(hass, entry.entity_id) base_action: dict = { CONF_DEVICE_ID: device_id, CONF_DOMAIN: DOMAIN, CONF_ENTITY_ID: entry.entity_id, } # Add actions for each entity that belongs to this integration if supported_features & SUPPORT_ALARM_ARM_AWAY: actions.append({**base_action, CONF_TYPE: "arm_away"}) if supported_features & SUPPORT_ALARM_ARM_HOME: actions.append({**base_action, CONF_TYPE: "arm_home"}) if supported_features & SUPPORT_ALARM_ARM_NIGHT: actions.append({**base_action, CONF_TYPE: "arm_night"}) if supported_features & SUPPORT_ALARM_ARM_VACATION: actions.append({**base_action, CONF_TYPE: "arm_vacation"}) actions.append({**base_action, CONF_TYPE: "disarm"}) if supported_features & SUPPORT_ALARM_TRIGGER: actions.append({**base_action, CONF_TYPE: "trigger"}) return actions
async def async_get_conditions( hass: HomeAssistant, device_id: str ) -> list[dict[str, str]]: """List device conditions for Humidifier devices.""" registry = await entity_registry.async_get_registry(hass) conditions = await toggle_entity.async_get_conditions(hass, device_id, DOMAIN) # Get all the integrations entities for this device for entry in entity_registry.async_entries_for_device(registry, device_id): if entry.domain != DOMAIN: continue supported_features = get_supported_features(hass, entry.entity_id) if supported_features & const.SUPPORT_MODES: conditions.append( { CONF_CONDITION: "device", CONF_DEVICE_ID: device_id, CONF_DOMAIN: DOMAIN, CONF_ENTITY_ID: entry.entity_id, CONF_TYPE: "is_mode", } ) return conditions
async def async_get_actions( hass: HomeAssistant, device_id: str ) -> list[dict[str, str]]: """List device actions for Lock devices.""" registry = await entity_registry.async_get_registry(hass) actions = [] # Get all the integrations entities for this device for entry in entity_registry.async_entries_for_device(registry, device_id): if entry.domain != DOMAIN: continue supported_features = get_supported_features(hass, entry.entity_id) # Add actions for each entity that belongs to this integration base_action = { CONF_DEVICE_ID: device_id, CONF_DOMAIN: DOMAIN, CONF_ENTITY_ID: entry.entity_id, } actions.append({**base_action, CONF_TYPE: "lock"}) actions.append({**base_action, CONF_TYPE: "unlock"}) if supported_features & (LockEntityFeature.OPEN): actions.append({**base_action, CONF_TYPE: "open"}) return actions
async def async_get_conditions(hass: HomeAssistant, device_id: str) -> list[dict[str, str]]: """List device conditions for Cover devices.""" registry = await entity_registry.async_get_registry(hass) conditions: list[dict[str, str]] = [] # Get all the integrations entities for this device for entry in entity_registry.async_entries_for_device(registry, device_id): if entry.domain != DOMAIN: continue supported_features = get_supported_features(hass, entry.entity_id) supports_open_close = supported_features & (SUPPORT_OPEN | SUPPORT_CLOSE) # Add conditions for each entity that belongs to this integration base_condition = { CONF_CONDITION: "device", CONF_DEVICE_ID: device_id, CONF_DOMAIN: DOMAIN, CONF_ENTITY_ID: entry.entity_id, } if supports_open_close: conditions += [{ **base_condition, CONF_TYPE: cond } for cond in STATE_CONDITION_TYPES] if supported_features & SUPPORT_SET_POSITION: conditions.append({**base_condition, CONF_TYPE: "is_position"}) if supported_features & SUPPORT_SET_TILT_POSITION: conditions.append({ **base_condition, CONF_TYPE: "is_tilt_position" }) return conditions
async def async_get_conditions(hass: HomeAssistant, device_id: str) -> list[dict[str, str]]: """List device conditions for Alarm control panel devices.""" registry = await entity_registry.async_get_registry(hass) conditions = [] # Get all the integrations entities for this device for entry in entity_registry.async_entries_for_device(registry, device_id): if entry.domain != DOMAIN: continue supported_features = get_supported_features(hass, entry.entity_id) # Add conditions for each entity that belongs to this integration base_condition = { CONF_CONDITION: "device", CONF_DEVICE_ID: device_id, CONF_DOMAIN: DOMAIN, CONF_ENTITY_ID: entry.entity_id, } conditions += [ { **base_condition, CONF_TYPE: CONDITION_DISARMED }, { **base_condition, CONF_TYPE: CONDITION_TRIGGERED }, ] if supported_features & SUPPORT_ALARM_ARM_HOME: conditions.append({ **base_condition, CONF_TYPE: CONDITION_ARMED_HOME }) if supported_features & SUPPORT_ALARM_ARM_AWAY: conditions.append({ **base_condition, CONF_TYPE: CONDITION_ARMED_AWAY }) if supported_features & SUPPORT_ALARM_ARM_NIGHT: conditions.append({ **base_condition, CONF_TYPE: CONDITION_ARMED_NIGHT }) if supported_features & SUPPORT_ALARM_ARM_VACATION: conditions.append({ **base_condition, CONF_TYPE: CONDITION_ARMED_VACATION }) if supported_features & SUPPORT_ALARM_ARM_CUSTOM_BYPASS: conditions.append({ **base_condition, CONF_TYPE: CONDITION_ARMED_CUSTOM_BYPASS }) return conditions
async def async_get_triggers( hass: HomeAssistant, device_id: str ) -> list[dict[str, str]]: """List device triggers for Cover devices.""" registry = entity_registry.async_get(hass) triggers = [] # Get all the integrations entities for this device for entry in entity_registry.async_entries_for_device(registry, device_id): if entry.domain != DOMAIN: continue supported_features = get_supported_features(hass, entry.entity_id) supports_open_close = supported_features & (SUPPORT_OPEN | SUPPORT_CLOSE) # Add triggers for each entity that belongs to this integration base_trigger = { CONF_PLATFORM: "device", CONF_DEVICE_ID: device_id, CONF_DOMAIN: DOMAIN, CONF_ENTITY_ID: entry.entity_id, } if supports_open_close: triggers += [ { **base_trigger, CONF_TYPE: trigger, } for trigger in STATE_TRIGGER_TYPES ] if supported_features & SUPPORT_SET_POSITION: triggers.append( { **base_trigger, CONF_TYPE: "position", } ) if supported_features & SUPPORT_SET_TILT_POSITION: triggers.append( { **base_trigger, CONF_TYPE: "tilt_position", } ) return triggers
async def async_get_triggers(hass: HomeAssistant, device_id: str) -> list[dict[str, Any]]: """List device triggers for Alarm control panel devices.""" registry = entity_registry.async_get(hass) triggers: list[dict[str, str]] = [] # Get all the integrations entities for this device for entry in entity_registry.async_entries_for_device(registry, device_id): if entry.domain != DOMAIN: continue supported_features = get_supported_features(hass, entry.entity_id) # Add triggers for each entity that belongs to this integration base_trigger = { CONF_PLATFORM: "device", CONF_DEVICE_ID: device_id, CONF_DOMAIN: DOMAIN, CONF_ENTITY_ID: entry.entity_id, } triggers += [{ **base_trigger, CONF_TYPE: trigger, } for trigger in BASIC_TRIGGER_TYPES] if supported_features & SUPPORT_ALARM_ARM_HOME: triggers.append({ **base_trigger, CONF_TYPE: "armed_home", }) if supported_features & SUPPORT_ALARM_ARM_AWAY: triggers.append({ **base_trigger, CONF_TYPE: "armed_away", }) if supported_features & SUPPORT_ALARM_ARM_NIGHT: triggers.append({ **base_trigger, CONF_TYPE: "armed_night", }) if supported_features & SUPPORT_ALARM_ARM_VACATION: triggers.append({ **base_trigger, CONF_TYPE: "armed_vacation", }) return triggers
async def async_get_actions(hass: HomeAssistant, device_id: str) -> list[dict]: """List device actions.""" actions = await toggle_entity.async_get_actions(hass, device_id, DOMAIN) entity_registry = er.async_get(hass) for entry in er.async_entries_for_device(entity_registry, device_id): if entry.domain != DOMAIN: continue supported_color_modes = get_supported_color_modes(hass, entry.entity_id) supported_features = get_supported_features(hass, entry.entity_id) if brightness_supported(supported_color_modes): actions.extend( ( { CONF_TYPE: TYPE_BRIGHTNESS_INCREASE, "device_id": device_id, "entity_id": entry.entity_id, "domain": DOMAIN, }, { CONF_TYPE: TYPE_BRIGHTNESS_DECREASE, "device_id": device_id, "entity_id": entry.entity_id, "domain": DOMAIN, }, ) ) if supported_features & SUPPORT_FLASH: actions.extend( ( { CONF_TYPE: TYPE_FLASH, "device_id": device_id, "entity_id": entry.entity_id, "domain": DOMAIN, }, ) ) return actions
async def async_get_action_capabilities(hass: HomeAssistant, config: dict) -> dict: """List action capabilities.""" if config[CONF_TYPE] != toggle_entity.CONF_TURN_ON: return {} try: supported_features = get_supported_features(hass, config[ATTR_ENTITY_ID]) except HomeAssistantError: supported_features = 0 extra_fields = {} if supported_features & SUPPORT_BRIGHTNESS: extra_fields[vol.Optional(ATTR_BRIGHTNESS_PCT)] = VALID_BRIGHTNESS_PCT if supported_features & SUPPORT_FLASH: extra_fields[vol.Optional(ATTR_FLASH)] = VALID_FLASH return {"extra_fields": vol.Schema(extra_fields)} if extra_fields else {}
async def async_get_actions( hass: HomeAssistant, device_id: str ) -> list[dict[str, str]]: """List device actions for Cover devices.""" registry = entity_registry.async_get(hass) actions = [] # Get all the integrations entities for this device for entry in entity_registry.async_entries_for_device(registry, device_id): if entry.domain != DOMAIN: continue supported_features = get_supported_features(hass, entry.entity_id) # Add actions for each entity that belongs to this integration base_action = { CONF_DEVICE_ID: device_id, CONF_DOMAIN: DOMAIN, CONF_ENTITY_ID: entry.entity_id, } if supported_features & SUPPORT_SET_POSITION: actions.append({**base_action, CONF_TYPE: "set_position"}) else: if supported_features & SUPPORT_OPEN: actions.append({**base_action, CONF_TYPE: "open"}) if supported_features & SUPPORT_CLOSE: actions.append({**base_action, CONF_TYPE: "close"}) if supported_features & SUPPORT_STOP: actions.append({**base_action, CONF_TYPE: "stop"}) if supported_features & SUPPORT_SET_TILT_POSITION: actions.append({**base_action, CONF_TYPE: "set_tilt_position"}) else: if supported_features & SUPPORT_OPEN_TILT: actions.append({**base_action, CONF_TYPE: "open_tilt"}) if supported_features & SUPPORT_CLOSE_TILT: actions.append({**base_action, CONF_TYPE: "close_tilt"}) return actions
async def async_get_actions(hass: HomeAssistant, device_id: str) -> list[dict]: """List device actions for Humidifier devices.""" registry = await entity_registry.async_get_registry(hass) actions = await toggle_entity.async_get_actions(hass, device_id, DOMAIN) # Get all the integrations entities for this device for entry in entity_registry.async_entries_for_device(registry, device_id): if entry.domain != DOMAIN: continue supported_features = get_supported_features(hass, entry.entity_id) base_action = { CONF_DEVICE_ID: device_id, CONF_DOMAIN: DOMAIN, CONF_ENTITY_ID: entry.entity_id, } actions.append({**base_action, CONF_TYPE: "set_humidity"}) if supported_features & const.SUPPORT_MODES: actions.append({**base_action, CONF_TYPE: "set_mode"}) return actions
async def test_get_supported_features_raises_on_unknown(hass): """Test get_supported_features raises on unknown entity_id.""" with pytest.raises(HomeAssistantError): entity.get_supported_features(hass, "hello.world")
async def _state_publisher(entity_id, old_state, new_state): if new_state is None: return if not publish_filter(entity_id): return mybase = f"{base_topic}{entity_id.replace('.', '/')}/" if publish_timestamps: if new_state.last_updated: await mqtt_publish(f"{mybase}last_updated", new_state.last_updated.isoformat(), 1, True) if new_state.last_changed: await mqtt_publish(f"{mybase}last_changed", new_state.last_changed.isoformat(), 1, True) if publish_attributes: for key, val in new_state.attributes.items(): encoded_val = json.dumps(val, cls=JSONEncoder) await mqtt_publish(mybase + key, encoded_val, 1, True) ent_parts = entity_id.split(".") ent_domain = ent_parts[0] ent_id = ent_parts[1] if publish_discovery and not entity_id in hass.data[DOMAIN][ base_topic]["conf_published"]: config = { "uniq_id": f"mqtt_{entity_id}", "name": ent_id.replace("_", " ").title(), "stat_t": f"{mybase}state", "json_attr_t": f"{mybase}attributes", "avty_t": f"{mybase}availability" } if ("device_class" in new_state.attributes): config["dev_cla"] = new_state.attributes["device_class"] if ("unit_of_measurement" in new_state.attributes): config["unit_of_meas"] = new_state.attributes[ "unit_of_measurement"] if ("state_class" in new_state.attributes): config["stat_cla"] = new_state.attributes["state_class"] publish_config = False if ent_domain == "sensor" and (has_includes or "device_class" in new_state.attributes): publish_config = True elif ent_domain == "binary_sensor" and ( has_includes or "device_class" in new_state.attributes): config["pl_off"] = STATE_OFF config["pl_on"] = STATE_ON publish_config = True elif ent_domain == "switch": config["pl_off"] = STATE_OFF config["pl_on"] = STATE_ON config["cmd_t"] = f"{mybase}set" publish_config = True elif ent_domain == "device_tracker": publish_config = True elif ent_domain == "light": del config["json_attr_t"] config["cmd_t"] = f"{mybase}set_light" config["schema"] = "json" supported_features = get_supported_features(hass, entity_id) if supported_features & SUPPORT_BRIGHTNESS: config["brightness"] = True if supported_features & SUPPORT_EFFECT: config["effect"] = True if "supported_color_modes" in new_state.attributes: config["color_mode"] = True config["supported_color_modes"] = new_state.attributes[ "supported_color_modes"] publish_config = True if publish_config: for entry in ent_reg.entities.values(): if entry.entity_id != entity_id: continue for device in dev_reg.devices.values(): if device.id != entry.device_id: continue config["dev"] = {} if device.manufacturer: config["dev"]["mf"] = device.manufacturer if device.model: config["dev"]["mdl"] = device.model if device.name: config["dev"]["name"] = device.name if device.sw_version: config["dev"]["sw"] = device.sw_version if device.identifiers: config["dev"]["ids"] = [ id[1] for id in device.identifiers ] if device.connections: config["dev"]["cns"] = device.connections encoded = json.dumps(config, cls=JSONEncoder) await mqtt_publish(f"{mybase}config", encoded, 1, True) hass.data[DOMAIN][base_topic]["conf_published"].append( entity_id) if publish_discovery: if ent_domain == "light": payload = { "state": "ON" if new_state.state == STATE_ON else "OFF", } if ("brightness" in new_state.attributes): payload["brightness"] = new_state.attributes["brightness"] if ("color_mode" in new_state.attributes): payload["color_mode"] = new_state.attributes["color_mode"] if ("color_temp" in new_state.attributes): payload["color_temp"] = new_state.attributes["color_temp"] if ("effect" in new_state.attributes): payload["effect"] = new_state.attributes["effect"] color = {} if ("hs_color" in new_state.attributes): color["h"] = new_state.attributes["hs_color"][0] color["s"] = new_state.attributes["hs_color"][1] if ("xy_color" in new_state.attributes): color["x"] = new_state.attributes["xy_color"][0] color["x"] = new_state.attributes["xy_color"][1] if ("rgb_color" in new_state.attributes): color["r"] = new_state.attributes["rgb_color"][0] color["g"] = new_state.attributes["rgb_color"][1] color["b"] = new_state.attributes["rgb_color"][2] if color: payload["color"] = color await mqtt_publish(f"{mybase}state", json.dumps(payload, cls=JSONEncoder), 1, True) payload = "offline" if new_state.state in (STATE_UNAVAILABLE, STATE_UNKNOWN, None) else "online" await mqtt_publish(f"{mybase}availability", payload, 1, True) else: payload = new_state.state await mqtt_publish(f"{mybase}state", payload, 1, True) payload = "offline" if new_state.state in (STATE_UNAVAILABLE, STATE_UNKNOWN, None) else "online" await mqtt_publish(f"{mybase}availability", payload, 1, True) attributes = {} for key, val in new_state.attributes.items(): attributes[key] = val encoded = json.dumps(attributes, cls=JSONEncoder) await mqtt_publish(f"{mybase}attributes", encoded, 1, True) else: payload = new_state.state await mqtt_publish(f"{mybase}state", payload, 1, True)