Files
Home-Assistant/custom_components/ha_washdata/diagnostics.py
T
2026-07-20 22:52:35 -04:00

112 lines
3.9 KiB
Python

# WashData - Home Assistant integration for appliance cycle monitoring via smart plugs.
# Copyright (C) 2026 Lukas Bandura
# SPDX-License-Identifier: AGPL-3.0-or-later
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
"""Diagnostics support for WashData."""
from __future__ import annotations
from typing import Any
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from .const import DOMAIN
from .manager import WashDataManager
# Keys that can identify the user or their home network - redacted in all contexts.
_SENSITIVE_KEYS = {
"auth",
"entry_id",
"flow_id",
"flow_title",
"handler",
"name",
"source",
"title",
"unique_id",
"user_id",
# Community-store account credentials / identifiers.
"refresh_token",
"id_token",
"uid",
# HA entity / service references that reveal home topology.
"notify_service",
"notify_start_services",
"notify_finish_services",
"notify_live_services",
"notify_people",
"notify_actions",
"power_sensor",
"external_end_trigger",
"door_sensor_entity",
"switch_entity",
"energy_price_entity",
"energy_sensor",
}
def _redact(obj: Any) -> Any:
if isinstance(obj, dict):
return {
k: "**REDACTED**" if k in _SENSITIVE_KEYS else _redact(v)
for k, v in obj.items()
}
if isinstance(obj, list):
return [_redact(v) for v in obj]
return obj
async def async_get_config_entry_diagnostics(
hass: HomeAssistant, entry: ConfigEntry
) -> dict[str, Any]:
"""Return diagnostics for a config entry."""
manager: WashDataManager = hass.data[DOMAIN][entry.entry_id]
# Full store export - same payload as the export_config service, but the
# entry_data / entry_options pass through the redactor to strip personal keys.
exported: dict[str, Any] = manager.profile_store.export_data(
entry_data=dict(entry.data),
entry_options=dict(entry.options),
)
return {
"entry": _redact(entry.as_dict()),
"manager_state": {
"current_state": manager.check_state(),
"current_program": manager.current_program,
"time_remaining": manager.time_remaining,
"cycle_progress": manager.cycle_progress,
"sample_interval_stats": (
dict(manager.sample_interval_stats)
if isinstance(manager.sample_interval_stats, dict)
else {}
),
"profile_sample_repair_stats": manager.profile_sample_repair_stats,
"suggestions": manager.profile_store.get_suggestions(),
"feature_flags": {
"auto_maintenance": bool(getattr(manager, "_auto_maintenance", False)),
"save_debug_traces": bool(getattr(manager, "_save_debug_traces", False)),
"notify_fire_events": bool(getattr(manager, "_notify_fire_events", False)),
},
},
"store_export": _redact(exported),
# Rolling 24-hour in-memory buffers (redacted: msg fields stripped from logs).
# power_trace: [[iso_ts, watts], ...] - every raw sensor reading
# state_history: [{ts, from, to, program}, ...] - detector state changes
# logs: [{ts, lvl}, ...] - log timestamps and levels (msg removed)
"live_diagnostics": manager.diag_buffer.redacted_snapshot(),
}