Clarify accumulated diagnostics values

This commit is contained in:
Bas Nijholt 2026-09-06 09:43:11 -07:00
commit 07a25aefce
4 changed files with 142 additions and 11 deletions

View file

@ -585,9 +585,14 @@ logger:
After the issue occurs, create a new issue report with the log (`/config/home-assistant.log`).
For support, use Home Assistant's **Download diagnostics** action on the
Adaptive Lighting config entry. The download is an on-demand snapshot of that
profile's current switch and effective-light facts. It does not create live
sensors; existing switch attributes remain the interface for automations.
Adaptive Lighting config entry. The download is an on-demand snapshot of the
profile's current switches and currently tracked light targets. It does not
refresh group membership or predict targets a disabled profile would use after
being enabled. It does not create live sensors; existing switch attributes
remain the interface for automations.
The reported last adaptation values are the shared manager's latest retained
value for each attribute. They can come from different commands and do not
represent one sent command or the current desired state.
<!-- SECTION:troubleshooting-intro:END -->
<!-- SECTION:common-problems:START -->

View file

@ -37,11 +37,15 @@ _TARGET_ATTRIBUTES = (
)
def _last_sent_target(
def _last_adaptation_values(
manager: AdaptiveLightingManager,
light: str,
) -> dict[str, Any] | None:
"""Return allowlisted target attributes from the last adaptation command."""
"""Return latest retained value for each allowlisted adaptation attribute.
Values may come from different commands because the manager merges partial
service data per attribute.
"""
service_data = manager.last_service_data.get(light)
if service_data is None:
return None
@ -108,7 +112,10 @@ async def async_get_config_entry_diagnostics(
"color": bool(manual_control & LightControlAttributes.COLOR),
},
"global_manager_autoreset_seconds": _autoreset_seconds(manager, light),
"global_manager_last_sent_target": _last_sent_target(manager, light),
"global_manager_last_adaptation_values": _last_adaptation_values(
manager,
light,
),
}
return {

View file

@ -26,9 +26,14 @@ logger:
After the issue occurs, create a new issue report with the log (`/config/home-assistant.log`).
For support, use Home Assistant's **Download diagnostics** action on the
Adaptive Lighting config entry. The download is an on-demand snapshot of that
profile's current switch and effective-light facts. It does not create live
sensors; existing switch attributes remain the interface for automations.
Adaptive Lighting config entry. The download is an on-demand snapshot of the
profile's current switches and currently tracked light targets. It does not
refresh group membership or predict targets a disabled profile would use after
being enabled. It does not create live sensors; existing switch attributes
remain the interface for automations.
The reported last adaptation values are the shared manager's latest retained
value for each attribute. They can come from different commands and do not
represent one sent command or the current desired state.
<!-- OUTPUT:END -->

View file

@ -2,10 +2,13 @@
import json
from copy import deepcopy
from unittest.mock import patch
import pytest
from homeassistant.components.adaptive_lighting.adaptation_utils import (
AdaptationData,
LightControlAttributes,
_create_service_call_data_iterator,
)
from homeassistant.components.adaptive_lighting.const import (
ATTR_ADAPTIVE_LIGHTING_MANAGER,
@ -23,11 +26,14 @@ from homeassistant.components.light import (
ATTR_COLOR_TEMP_KELVIN,
ATTR_RGB_COLOR,
ATTR_TRANSITION,
SERVICE_TURN_ON,
)
from homeassistant.components.light import DOMAIN as LIGHT_DOMAIN
from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN
from homeassistant.config_entries import ConfigEntryState
from homeassistant.const import (
ATTR_ENTITY_ID,
ATTR_SERVICE_DATA,
CONF_LIGHTS,
CONF_NAME,
EVENT_CALL_SERVICE,
@ -35,6 +41,7 @@ from homeassistant.const import (
STATE_OFF,
STATE_UNAVAILABLE,
)
from homeassistant.core import State
from tests.common import MockConfigEntry
from tests.components.diagnostics import get_diagnostics_for_config_entry
@ -162,7 +169,7 @@ async def test_config_entry_diagnostics_reports_allowlisted_current_facts(
assert result["lights"]["light_1"][
"global_manager_autoreset_seconds"
] == pytest.approx(60, abs=2)
assert result["lights"]["light_1"]["global_manager_last_sent_target"] == {
assert result["lights"]["light_1"]["global_manager_last_adaptation_values"] == {
ATTR_BRIGHTNESS: 123,
ATTR_COLOR_TEMP_KELVIN: 3456,
ATTR_RGB_COLOR: [12, 34, 56],
@ -175,7 +182,7 @@ async def test_config_entry_diagnostics_reports_allowlisted_current_facts(
"color": True,
},
"global_manager_autoreset_seconds": pytest.approx(60, abs=2),
"global_manager_last_sent_target": None,
"global_manager_last_adaptation_values": None,
}
serialized = json.dumps(result, sort_keys=True)
@ -194,6 +201,113 @@ async def test_config_entry_diagnostics_reports_allowlisted_current_facts(
assert sensitive_value not in serialized
async def test_diagnostics_labels_accumulated_partial_adaptation_values(
hass,
cleanup_diagnostics,
):
"""Diagnostics do not describe merged per-attribute history as one command."""
await setup_lights(hass)
entry, switch = await _setup_entry(
hass,
"Private Profile",
[ENTITY_LIGHT_1],
)
commands = [
{
ATTR_ENTITY_ID: ENTITY_LIGHT_1,
ATTR_BRIGHTNESS: 100,
ATTR_RGB_COLOR: (12, 34, 56),
ATTR_TRANSITION: 2,
},
{ATTR_ENTITY_ID: ENTITY_LIGHT_1, ATTR_BRIGHTNESS: 180},
{ATTR_ENTITY_ID: ENTITY_LIGHT_1, ATTR_COLOR_TEMP_KELVIN: 3500},
]
call_events = []
remove_listener = hass.bus.async_listen(EVENT_CALL_SERVICE, call_events.append)
await switch._execute_adaptation_calls(
AdaptationData(
entity_id=ENTITY_LIGHT_1,
context=switch.create_context("diagnostics_test"),
sleep_time=0,
service_call_datas=_create_service_call_data_iterator(
hass,
commands,
filter_by_state=False,
),
force=True,
max_length=len(commands),
attributes=LightControlAttributes.ALL,
),
)
await hass.async_block_till_done()
remove_listener()
actual_commands = [
event.data[ATTR_SERVICE_DATA]
for event in call_events
if event.data["domain"] == LIGHT_DOMAIN
and event.data["service"] == SERVICE_TURN_ON
]
assert actual_commands == commands
result = await async_get_config_entry_diagnostics(hass, entry)
light = result["lights"]["light_1"]
assert "global_manager_last_sent_target" not in light
assert light["global_manager_last_adaptation_values"] == {
ATTR_BRIGHTNESS: 180,
ATTR_COLOR_TEMP_KELVIN: 3500,
ATTR_RGB_COLOR: [12, 34, 56],
ATTR_TRANSITION: 2,
}
async def test_diagnostics_preserves_restored_off_profile_tracked_group(hass):
"""Diagnostics report tracked targets without refreshing late groups."""
await setup_lights(hass)
group = "light.private_late_group"
members = [ENTITY_LIGHT_1, ENTITY_LIGHT_2]
with patch(
"homeassistant.helpers.restore_state.RestoreEntity.async_get_last_state",
return_value=State("switch.restored", STATE_OFF),
):
entry, switch = await _setup_entry(
hass,
"Private Restored Profile",
[group],
)
assert not switch.is_on
assert switch.lights == [group]
hass.states.async_set(
group,
STATE_UNAVAILABLE,
{ATTR_ENTITY_ID: members, "friendly_name": "Private Late Group"},
)
await hass.async_block_till_done()
manager = hass.data[DOMAIN][ATTR_ADAPTIVE_LIGHTING_MANAGER]
manager_lights_before = set(manager.lights)
reset_times_before = dict(manager.auto_reset_manual_control_times)
result = await async_get_config_entry_diagnostics(hass, entry)
assert result["lights"] == {
"light_1": {
"state": STATE_UNAVAILABLE,
"global_manager_manual_control": {
"brightness": False,
"color": False,
},
"global_manager_autoreset_seconds": None,
"global_manager_last_adaptation_values": None,
},
}
assert switch.lights == [group]
assert manager.lights == manager_lights_before
assert manager.auto_reset_manual_control_times == reset_times_before
assert group not in json.dumps(result)
async def test_diagnostics_handles_missing_states_and_unload_without_side_effects(
hass,
):