Home Assistant Unofficial Reference 2024.12.1
diagnostics.py
Go to the documentation of this file.
1 """Diagnostics support for Enphase Envoy."""
2 
3 from __future__ import annotations
4 
5 import copy
6 from typing import TYPE_CHECKING, Any
7 
8 from attr import asdict
9 from pyenphase.envoy import Envoy
10 from pyenphase.exceptions import EnvoyError
11 
12 from homeassistant.components.diagnostics import async_redact_data
13 from homeassistant.const import (
14  CONF_NAME,
15  CONF_PASSWORD,
16  CONF_TOKEN,
17  CONF_UNIQUE_ID,
18  CONF_USERNAME,
19 )
20 from homeassistant.core import HomeAssistant
21 from homeassistant.helpers import device_registry as dr, entity_registry as er
22 from homeassistant.helpers.json import json_dumps
23 from homeassistant.util.json import json_loads
24 
25 from .const import OPTION_DIAGNOSTICS_INCLUDE_FIXTURES
26 from .coordinator import EnphaseConfigEntry
27 
28 CONF_TITLE = "title"
29 CLEAN_TEXT = "<<envoyserial>>"
30 
31 TO_REDACT = {
32  CONF_NAME,
33  CONF_PASSWORD,
34  # Config entry title and unique ID may contain sensitive data:
35  CONF_TITLE,
36  CONF_UNIQUE_ID,
37  CONF_USERNAME,
38  CONF_TOKEN,
39 }
40 
41 
42 async def _get_fixture_collection(envoy: Envoy, serial: str) -> dict[str, Any]:
43  """Collect Envoy endpoints to use for test fixture set."""
44  fixture_data: dict[str, Any] = {}
45  end_points = [
46  "/info",
47  "/api/v1/production",
48  "/api/v1/production/inverters",
49  "/production.json",
50  "/production.json?details=1",
51  "/production",
52  "/ivp/ensemble/power",
53  "/ivp/ensemble/inventory",
54  "/ivp/ensemble/dry_contacts",
55  "/ivp/ensemble/status",
56  "/ivp/ensemble/secctrl",
57  "/ivp/ss/dry_contact_settings",
58  "/admin/lib/tariff",
59  "/ivp/ss/gen_config",
60  "/ivp/ss/gen_schedule",
61  "/ivp/sc/pvlimit",
62  "/ivp/ss/pel_settings",
63  "/ivp/ensemble/generator",
64  "/ivp/meters",
65  "/ivp/meters/readings",
66  ]
67 
68  for end_point in end_points:
69  response = await envoy.request(end_point)
70  fixture_data[end_point] = response.text.replace("\n", "").replace(
71  serial, CLEAN_TEXT
72  )
73  fixture_data[f"{end_point}_log"] = json_dumps(
74  {
75  "headers": dict(response.headers.items()),
76  "code": response.status_code,
77  }
78  )
79  return fixture_data
80 
81 
83  hass: HomeAssistant, entry: EnphaseConfigEntry
84 ) -> dict[str, Any]:
85  """Return diagnostics for a config entry."""
86  coordinator = entry.runtime_data
87 
88  if TYPE_CHECKING:
89  assert coordinator.envoy.data
90  envoy_data = coordinator.envoy.data
91  envoy = coordinator.envoy
92 
93  device_registry = dr.async_get(hass)
94  entity_registry = er.async_get(hass)
95 
96  device_entities = []
97  # for each device associated with the envoy get entity and state information
98  for device in dr.async_entries_for_config_entry(device_registry, entry.entry_id):
99  entities = []
100  for entity in er.async_entries_for_device(
101  entity_registry, device_id=device.id, include_disabled_entities=True
102  ):
103  state_dict = None
104  if state := hass.states.get(entity.entity_id):
105  state_dict = dict(state.as_dict())
106  state_dict.pop("context", None)
107  entity_dict = asdict(entity)
108  entity_dict.pop("_cache", None)
109  entities.append({"entity": entity_dict, "state": state_dict})
110  device_dict = asdict(device)
111  device_dict.pop("_cache", None)
112  device_entities.append({"device": device_dict, "entities": entities})
113 
114  # remove envoy serial
115  old_serial = coordinator.envoy_serial_number
116 
117  coordinator_data = copy.deepcopy(coordinator.data)
118  coordinator_data_cleaned = json_dumps(coordinator_data).replace(
119  old_serial, CLEAN_TEXT
120  )
121 
122  device_entities_cleaned = json_dumps(device_entities).replace(
123  old_serial, CLEAN_TEXT
124  )
125 
126  envoy_model: dict[str, Any] = {
127  "encharge_inventory": envoy_data.encharge_inventory,
128  "encharge_power": envoy_data.encharge_power,
129  "encharge_aggregate": envoy_data.encharge_aggregate,
130  "enpower": envoy_data.enpower,
131  "system_consumption": envoy_data.system_consumption,
132  "system_production": envoy_data.system_production,
133  "system_consumption_phases": envoy_data.system_consumption_phases,
134  "system_production_phases": envoy_data.system_production_phases,
135  "ctmeter_production": envoy_data.ctmeter_production,
136  "ctmeter_consumption": envoy_data.ctmeter_consumption,
137  "ctmeter_storage": envoy_data.ctmeter_storage,
138  "ctmeter_production_phases": envoy_data.ctmeter_production_phases,
139  "ctmeter_consumption_phases": envoy_data.ctmeter_consumption_phases,
140  "ctmeter_storage_phases": envoy_data.ctmeter_storage_phases,
141  "dry_contact_status": envoy_data.dry_contact_status,
142  "dry_contact_settings": envoy_data.dry_contact_settings,
143  "inverters": envoy_data.inverters,
144  "tariff": envoy_data.tariff,
145  }
146 
147  envoy_properties: dict[str, Any] = {
148  "envoy_firmware": envoy.firmware,
149  "part_number": envoy.part_number,
150  "envoy_model": envoy.envoy_model,
151  "supported_features": [feature.name for feature in envoy.supported_features],
152  "phase_mode": envoy.phase_mode,
153  "phase_count": envoy.phase_count,
154  "active_phasecount": envoy.active_phase_count,
155  "ct_count": envoy.ct_meter_count,
156  "ct_consumption_meter": envoy.consumption_meter_type,
157  "ct_production_meter": envoy.production_meter_type,
158  "ct_storage_meter": envoy.storage_meter_type,
159  }
160 
161  fixture_data: dict[str, Any] = {}
162  if entry.options.get(OPTION_DIAGNOSTICS_INCLUDE_FIXTURES, False):
163  try:
164  fixture_data = await _get_fixture_collection(envoy=envoy, serial=old_serial)
165  except EnvoyError as err:
166  fixture_data["Error"] = repr(err)
167 
168  diagnostic_data: dict[str, Any] = {
169  "config_entry": async_redact_data(entry.as_dict(), TO_REDACT),
170  "envoy_properties": envoy_properties,
171  "raw_data": json_loads(coordinator_data_cleaned),
172  "envoy_model_data": envoy_model,
173  "envoy_entities_by_device": json_loads(device_entities_cleaned),
174  "fixtures": fixture_data,
175  }
176 
177  return diagnostic_data
dict async_redact_data(Mapping data, Iterable[Any] to_redact)
Definition: util.py:14
dict[str, Any] async_get_config_entry_diagnostics(HomeAssistant hass, EnphaseConfigEntry entry)
Definition: diagnostics.py:84
dict[str, Any] _get_fixture_collection(Envoy envoy, str serial)
Definition: diagnostics.py:42
str json_dumps(Any data)
Definition: json.py:149