From a72ca7b2d755d3d4ed6f2378b05ad34878adce09 Mon Sep 17 00:00:00 2001 From: jdavidbush Date: Sat, 19 Sep 2026 02:57:05 -0600 Subject: [PATCH] Add brightness_pct beside brightness in the LLM live context (#182571) --- homeassistant/components/homeassistant/llm.py | 9 ++++ tests/components/homeassistant/test_llm.py | 42 +++++++++++++++++++ 2 files changed, 51 insertions(+) diff --git a/homeassistant/components/homeassistant/llm.py b/homeassistant/components/homeassistant/llm.py index e533580f3071..9272f2d79bd4 100644 --- a/homeassistant/components/homeassistant/llm.py +++ b/homeassistant/components/homeassistant/llm.py @@ -7,6 +7,7 @@ from typing import Any, override import probatio +from homeassistant.components.light import DOMAIN as LIGHT_DOMAIN from homeassistant.components.llm import LLMTools from homeassistant.components.sensor import ( DOMAIN as SENSOR_DOMAIN, @@ -172,6 +173,14 @@ def async_get_exposed_entities( if attr_name in interesting_attributes } ): + # Tools take brightness as a 0-100 percentage; the attribute is 0-255. + if state.domain == LIGHT_DOMAIN and isinstance( + brightness := state.attributes.get("brightness"), int + ): + pct = round(brightness / 255 * 100) + attributes["brightness_pct"] = str( + max(pct, 1) if brightness > 0 else pct + ) info["attributes"] = attributes entities[state.entity_id] = info diff --git a/tests/components/homeassistant/test_llm.py b/tests/components/homeassistant/test_llm.py index a568d79d34d5..0d7fd73ed74f 100644 --- a/tests/components/homeassistant/test_llm.py +++ b/tests/components/homeassistant/test_llm.py @@ -412,3 +412,45 @@ async def test_get_live_context_schema( schema = to_openapi(tool.parameters, custom_serializer=api.custom_serializer) assert schema == snapshot + + +async def test_get_exposed_entities_brightness_percentage(hass: HomeAssistant) -> None: + """Test that a light's brightness is also rendered as a percentage.""" + hass.states.async_set( + ENTITY_ID, "on", {"friendly_name": "Kitchen Light", "brightness": 128} + ) + async_expose_entity(hass, "conversation", ENTITY_ID, True) + + exposed = async_get_exposed_entities(hass, "conversation", include_state=True) + attributes = exposed[ENTITY_ID]["attributes"] + + # The raw attribute is unchanged, so anything reading it keeps working. + assert attributes["brightness"] == "128" + # The percentage is the inverse of the percentage-to-brightness conversion. + assert attributes["brightness_pct"] == "50" + + hass.states.async_set( + ENTITY_ID, "on", {"friendly_name": "Kitchen Light", "brightness": 255} + ) + exposed = async_get_exposed_entities(hass, "conversation", include_state=True) + assert exposed[ENTITY_ID]["attributes"]["brightness_pct"] == "100" + + # A lit light never rounds down to nothing. + hass.states.async_set( + ENTITY_ID, "on", {"friendly_name": "Kitchen Light", "brightness": 1} + ) + exposed = async_get_exposed_entities(hass, "conversation", include_state=True) + assert exposed[ENTITY_ID]["attributes"]["brightness_pct"] == "1" + + hass.states.async_set(ENTITY_ID, "off", {"friendly_name": "Kitchen Light"}) + exposed = async_get_exposed_entities(hass, "conversation", include_state=True) + assert "brightness_pct" not in exposed[ENTITY_ID].get("attributes", {}) + + # Only lights get the percentage, whatever attribute another domain carries. + hass.states.async_set( + "fan.kitchen", "on", {"friendly_name": "Kitchen Fan", "brightness": 128} + ) + async_expose_entity(hass, "conversation", "fan.kitchen", True) + exposed = async_get_exposed_entities(hass, "conversation", include_state=True) + assert exposed["fan.kitchen"]["attributes"]["brightness"] == "128" + assert "brightness_pct" not in exposed["fan.kitchen"]["attributes"]