Skip to content

Commit

Permalink
Merge pull request #38 from bentekkie/adddiag
Browse files Browse the repository at this point in the history
Add diagnostics
  • Loading branch information
bentekkie authored Aug 13, 2023
2 parents 0a7da63 + ec91022 commit 49973cf
Show file tree
Hide file tree
Showing 6 changed files with 151 additions and 55 deletions.
9 changes: 6 additions & 3 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@
"*.yaml": "home-assistant"
},
"[python]": {
"editor.formatOnSave": true
"editor.formatOnSave": true,
"editor.defaultFormatter": "ms-python.black-formatter"
},
"python.formatting.provider": "black",
"python.formatting.provider": "none",
"python.linting.flake8Enabled": true,
"[json]": {
"editor.quickSuggestions": {
Expand All @@ -16,5 +17,7 @@
"editor.suggest.insertMode": "replace",
"editor.formatOnSave": false
},
"json.format.enable": false
"json.format.enable": false,

"python.analysis.typeCheckingMode": "basic"
}
4 changes: 2 additions & 2 deletions custom_components/generac/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
hass.data.setdefault(DOMAIN, {})
_LOGGER.info(STARTUP_MESSAGE)

username = entry.data.get(CONF_USERNAME)
password = entry.data.get(CONF_PASSWORD)
username = entry.data.get(CONF_USERNAME, "")
password = entry.data.get(CONF_PASSWORD, "")

session = async_get_clientsession(hass)
client = GeneracApiClient(username, password, session)
Expand Down
84 changes: 84 additions & 0 deletions custom_components/generac/diagnostics.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
"""Diagnostics support for Generac."""
from __future__ import annotations

import ipaddress
from dataclasses import asdict
from email.utils import parseaddr
from typing import Any

from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant

from .const import DOMAIN
from .coordinator import GeneracDataUpdateCoordinator


async def async_get_config_entry_diagnostics(
hass: HomeAssistant, entry: ConfigEntry
) -> dict[str, Any]:
"""Return diagnostics for a config entry."""
coordinator: GeneracDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]

diagnostics_data = {
"data": redact(
{gen_id: asdict(item) for gen_id, item in coordinator.data.items()}, False
),
}

return diagnostics_data


DataDict = dict[str, "str | DataDict"]


def redact(data: Any, redact_all: bool):
if isinstance(data, dict):
return redact_dict(data, redact_all)
if isinstance(data, list):
return redact_array(data, redact_all)
if isinstance(data, str):
if parseaddr(data) == ("", ""):
return "REDACTED_VALID_EMAIL"
if is_ipv4(data):
return "REDACTED_IPV4"
if is_ipv6(data):
return "REDACTED_IPV4"
if redact_all:
return "REDACTED"
return data


def is_ipv4(s: str):
try:
ipaddress.IPv4Network(s)
return True
except ValueError:
return False


def is_ipv6(s: str):
try:
ipaddress.IPv4Network(s)
return True
except ValueError:
return False


_REDACTED_KEYS = {
"deviceSsid",
"serialNumber",
"apparatusId",
"address",
"localizedAddress",
}


def redact_dict(data: dict, redact_all: bool) -> dict:
return {
k: redact(v, True) if k in _REDACTED_KEYS else redact(v, redact_all)
for k, v in data.items()
}


def redact_array(data: list, redact_all: bool) -> list:
return [redact(it, redact_all) for it in data]
7 changes: 5 additions & 2 deletions custom_components/generac/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@
_LOGGER: logging.Logger = logging.getLogger(__package__)


_EMPTY_ITEM = Item(apparatus=Apparatus(), apparatusDetail=ApparatusDetail(), empty=True)


class GeneracEntity(CoordinatorEntity[GeneracDataUpdateCoordinator]):
def __init__(
self,
Expand Down Expand Up @@ -55,7 +58,7 @@ def device_state_attributes(self):
@property
def available(self):
"""Return True if entity is available."""
return self.coordinator.is_online
return self.coordinator.is_online and not self.item.empty

async def async_added_to_hass(self) -> None:
"""Connect to dispatcher listening for entity data notifications."""
Expand All @@ -75,6 +78,6 @@ def aparatus_detail(self) -> ApparatusDetail:
@callback
def _handle_coordinator_update(self) -> None:
"""Handle updated data from the coordinator."""
self.item = self.coordinator.data.get(self.generator_id)
self.item = self.coordinator.data.get(self.generator_id, _EMPTY_ITEM)
_LOGGER.debug(f"Updated data for {self.unique_id}: {self.item}")
self.async_write_ha_state()
5 changes: 5 additions & 0 deletions custom_components/generac/image.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,8 @@ def name(self):
@property
def image_url(self):
return self.aparatus_detail.heroImageUrl

@property
def available(self):
"""Return True if entity is available."""
return super().available and self.aparatus_detail.heroImageUrl is not None
97 changes: 49 additions & 48 deletions custom_components/generac/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,25 +80,25 @@ class Temperature:

@dataclass()
class Apparatus:
apparatusId: Optional[int]
serialNumber: Optional[str]
name: Optional[str]
type: Optional[int]
localizedAddress: Optional[str]
materialDescription: Optional[str]
heroImageUrl: Optional[str]
apparatusStatus: Optional[int]
isConnected: Optional[bool]
isConnecting: Optional[bool]
showWarning: Optional[bool]
weather: Optional[Weather]
preferredDealerName: Optional[str]
preferredDealerPhone: Optional[str]
preferredDealerEmail: Optional[str]
isDealerManaged: Optional[bool]
isDealerUnmonitored: Optional[bool]
modelNumber: Optional[str]
panelId: Optional[str]
apparatusId: Optional[int] = None
serialNumber: Optional[str] = None
name: Optional[str] = None
type: Optional[int] = None
localizedAddress: Optional[str] = None
materialDescription: Optional[str] = None
heroImageUrl: Optional[str] = None
apparatusStatus: Optional[int] = None
isConnected: Optional[bool] = None
isConnecting: Optional[bool] = None
showWarning: Optional[bool] = None
weather: Optional[Weather] = None
preferredDealerName: Optional[str] = None
preferredDealerPhone: Optional[str] = None
preferredDealerEmail: Optional[str] = None
isDealerManaged: Optional[bool] = None
isDealerUnmonitored: Optional[bool] = None
modelNumber: Optional[str] = None
panelId: Optional[str] = None

@dataclass
class Property:
Expand All @@ -118,7 +118,7 @@ class Value:
value: Optional[Value | list]
type: Optional[int]

properties: Optional[list[Property]]
properties: Optional[list[Property]] = None


@dataclass
Expand Down Expand Up @@ -157,37 +157,38 @@ class ProductInfo:
value: Optional[str]
type: Optional[int]

apparatusId: Optional[int]
name: Optional[str]
serialNumber: Optional[str]
apparatusClassification: Optional[int]
panelId: Optional[str]
activationDate: Optional[str]
deviceType: Optional[str]
deviceSsid: Optional[str]
shortDeviceId: Optional[str]
apparatusStatus: Optional[int]
heroImageUrl: Optional[str]
statusLabel: Optional[str]
statusText: Optional[str]
eCodeLabel: Optional[str]
weather: Optional[Weather]
isConnected: Optional[bool]
isConnecting: Optional[bool]
showWarning: Optional[bool]
hasMaintenanceAlert: Optional[bool]
lastSeen: Optional[str]
connectionTimestamp: Optional[str]
address: Optional[Address]
properties: Optional[list[Property]]
subscription: Optional[Subscription]
enrolledInVpp: Optional[bool]
hasActiveVppEvent: Optional[bool]
productInfo: Optional[list[Property]]
hasDisconnectedNotificationsOn: Optional[bool]
apparatusId: Optional[int] = None
name: Optional[str] = None
serialNumber: Optional[str] = None
apparatusClassification: Optional[int] = None
panelId: Optional[str] = None
activationDate: Optional[str] = None
deviceType: Optional[str] = None
deviceSsid: Optional[str] = None
shortDeviceId: Optional[str] = None
apparatusStatus: Optional[int] = None
heroImageUrl: Optional[str] = None
statusLabel: Optional[str] = None
statusText: Optional[str] = None
eCodeLabel: Optional[str] = None
weather: Optional[Weather] = None
isConnected: Optional[bool] = None
isConnecting: Optional[bool] = None
showWarning: Optional[bool] = None
hasMaintenanceAlert: Optional[bool] = None
lastSeen: Optional[str] = None
connectionTimestamp: Optional[str] = None
address: Optional[Address] = None
properties: Optional[list[Property]] = None
subscription: Optional[Subscription] = None
enrolledInVpp: Optional[bool] = None
hasActiveVppEvent: Optional[bool] = None
productInfo: Optional[list[Property]] = None
hasDisconnectedNotificationsOn: Optional[bool] = None


@dataclass
class Item:
apparatus: Apparatus
apparatusDetail: ApparatusDetail
empty: bool = False

0 comments on commit 49973cf

Please sign in to comment.