Add diagnostics platform, config-flow gap warning, issue template
Completes the device-capability-diagnostics spec: - diagnostics.py: the standard HA diagnostics hook, returning device type, one_ui_version, unbound hrefs, and the redacted raw resource tree, plus integration and smartthings-local version numbers. This is what the Repairs issue (added in the previous commit) points users at, and what they attach to a device-support issue. - config_flow.py: _probe_and_validate now also reports oneUiVersion and whether the device type is recognized. Recognized types are unaffected; an unrecognized type shows a new confirm_unknown_type step explaining that only common capabilities will be available before creating the entry, so expectations are set at setup time rather than only after the fact via Repairs. - .github/ISSUE_TEMPLATE/device-support.yml: structured template for filing a capability gap, linked from both the Repairs issue and the config-flow confirmation step. - README: short section pointing at this whole mechanism.
This commit is contained in:
1 parent
ca754cbb01
commit
d2699f0f5b
9 files changed
+221
-12
No files matched your search
@@ -125,6 +125,7 @@ def _probe_and_validate(host: str, ca_cert_pem: str, ca_key_pem: str) -> dict:
|
||||
import cbor2
|
||||
from smartthings_local.protocol.dtls_session import DtlsCoapSession
|
||||
from .registry.batch import parse_device0_batch
|
||||
from .registry.by_type import for_device
|
||||
|
||||
_LOGGER.debug("Fetching Samsung cloud UUID from %s", _SAMSUNG_CLOUD_HOST)
|
||||
try:
|
||||
@@ -170,11 +171,21 @@ def _probe_and_validate(host: str, ca_cert_pem: str, ca_key_pem: str) -> dict:
|
||||
)
|
||||
if not serial:
|
||||
serial = f"{host}:{port}"
|
||||
one_ui_version = (
|
||||
resources
|
||||
.get('/otninformation/vs/0', {})
|
||||
.get('swVersionInfo', {})
|
||||
.get('oneUiVersion', '')
|
||||
)
|
||||
return {
|
||||
"port": port,
|
||||
"serial": serial,
|
||||
"leaf_cert_pem": fullchain_pem,
|
||||
"leaf_key_pem": leaf_key_pem,
|
||||
"one_ui_version": one_ui_version,
|
||||
"device_type_recognized": bool(
|
||||
one_ui_version and for_device(one_ui_version) is not None
|
||||
),
|
||||
}
|
||||
except CannotConnect:
|
||||
raise
|
||||
@@ -197,6 +208,20 @@ class LocalThingsConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
self._host: str = ""
|
||||
self._ca_cert_pem: str = ""
|
||||
self._ca_key_pem: str = ""
|
||||
self._pending_info: dict | None = None
|
||||
|
||||
def _create_entry(self, info: dict) -> FlowResult:
|
||||
return self.async_create_entry(
|
||||
title=f"Samsung Appliance ({self._host})",
|
||||
data={
|
||||
CONF_HOST: self._host,
|
||||
CONF_PORT: info["port"],
|
||||
CONF_CA_CERT_PEM: self._ca_cert_pem,
|
||||
CONF_CA_KEY_PEM: self._ca_key_pem,
|
||||
CONF_LEAF_CERT_PEM: info["leaf_cert_pem"],
|
||||
CONF_LEAF_KEY_PEM: info["leaf_key_pem"],
|
||||
},
|
||||
)
|
||||
|
||||
async def async_step_user(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
@@ -232,17 +257,10 @@ class LocalThingsConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
else:
|
||||
await self.async_set_unique_id(f"localthings_{info['serial']}")
|
||||
self._abort_if_unique_id_configured()
|
||||
return self.async_create_entry(
|
||||
title=f"Samsung Appliance ({self._host})",
|
||||
data={
|
||||
CONF_HOST: self._host,
|
||||
CONF_PORT: info["port"],
|
||||
CONF_CA_CERT_PEM: self._ca_cert_pem,
|
||||
CONF_CA_KEY_PEM: self._ca_key_pem,
|
||||
CONF_LEAF_CERT_PEM: info["leaf_cert_pem"],
|
||||
CONF_LEAF_KEY_PEM: info["leaf_key_pem"],
|
||||
},
|
||||
)
|
||||
if info["device_type_recognized"]:
|
||||
return self._create_entry(info)
|
||||
self._pending_info = info
|
||||
return await self.async_step_confirm_unknown_type()
|
||||
|
||||
if has_creds:
|
||||
schema = vol.Schema({vol.Required(CONF_HOST): _TEXT})
|
||||
@@ -261,3 +279,18 @@ class LocalThingsConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
errors=errors,
|
||||
description_placeholders={"reuse_note": reuse_note},
|
||||
)
|
||||
|
||||
async def async_step_confirm_unknown_type(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> FlowResult:
|
||||
"""Shown only when the probe already knows the device type is unrecognized."""
|
||||
if user_input is not None:
|
||||
return self._create_entry(self._pending_info)
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="confirm_unknown_type",
|
||||
data_schema=vol.Schema({}),
|
||||
description_placeholders={
|
||||
"one_ui_version": self._pending_info["one_ui_version"] or "(none reported)",
|
||||
},
|
||||
)
|
||||
@@ -0,0 +1,36 @@
|
||||
"""Diagnostics support for Local Things.
|
||||
|
||||
Downloadable from Settings > Devices & Services > this integration's
|
||||
device > the menu > Download diagnostics. This is what the Repairs issue
|
||||
(raised in coordinator.py when capability coverage is incomplete) points
|
||||
users at: a redacted snapshot of the device's raw /device/0 state, plus
|
||||
enough version/coverage metadata to reproduce and diagnose the gap.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from importlib.metadata import version as pkg_version
|
||||
from typing import Any
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.loader import async_get_integration
|
||||
|
||||
from .const import DOMAIN
|
||||
from .coordinator import LocalThingsCoordinator
|
||||
from .registry.redact import redact_resources
|
||||
|
||||
|
||||
async def async_get_config_entry_diagnostics(
|
||||
hass: HomeAssistant, entry: ConfigEntry
|
||||
) -> dict[str, Any]:
|
||||
coordinator: LocalThingsCoordinator = hass.data[DOMAIN][entry.entry_id]
|
||||
integration = await async_get_integration(hass, DOMAIN)
|
||||
|
||||
return {
|
||||
"device_type": coordinator.device_type_name or "unknown",
|
||||
"one_ui_version": coordinator.one_ui_version,
|
||||
"unbound_hrefs": sorted(coordinator._unbound_hrefs),
|
||||
"resources": redact_resources(coordinator.last_resources),
|
||||
"integration_version": integration.version,
|
||||
"smartthings_local_version": pkg_version("smartthings-local"),
|
||||
}
|
||||
@@ -63,6 +63,10 @@
|
||||
"ca_cert_pem": "The PEM-encoded AC14K_M CA certificate chain. Paste the full contents of your fullchain PEM including all BEGIN/END CERTIFICATE blocks.",
|
||||
"ca_key_pem": "The PEM-encoded AC14K_M private key. Paste the full contents including the BEGIN/END PRIVATE KEY header and footer."
|
||||
}
|
||||
},
|
||||
"confirm_unknown_type": {
|
||||
"title": "Appliance type not recognized",
|
||||
"description": "This appliance reported oneUiVersion \"{one_ui_version}\", which isn't a recognized type. It'll still be added, but only with common capabilities (power, alarms, etc. where present) rather than the full set for its family. You can help add full support afterward by downloading diagnostics for this device (Settings > Devices & Services > this device > the menu > Download diagnostics) and filing them in a new issue. Submit to add it anyway."
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
|
||||
@@ -63,6 +63,10 @@
|
||||
"ca_cert_pem": "The PEM-encoded AC14K_M CA certificate chain. Paste the full contents of your fullchain PEM including all BEGIN/END CERTIFICATE blocks.",
|
||||
"ca_key_pem": "The PEM-encoded AC14K_M private key. Paste the full contents including the BEGIN/END PRIVATE KEY header and footer."
|
||||
}
|
||||
},
|
||||
"confirm_unknown_type": {
|
||||
"title": "Appliance type not recognized",
|
||||
"description": "This appliance reported oneUiVersion \"{one_ui_version}\", which isn't a recognized type. It'll still be added, but only with common capabilities (power, alarms, etc. where present) rather than the full set for its family. You can help add full support afterward by downloading diagnostics for this device (Settings > Devices & Services > this device > the menu > Download diagnostics) and filing them in a new issue. Submit to add it anyway."
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
@@ -73,5 +77,11 @@
|
||||
"abort": {
|
||||
"already_configured": "This device is already configured."
|
||||
}
|
||||
},
|
||||
"issues": {
|
||||
"device_gap": {
|
||||
"title": "Incomplete capability coverage for {device_name}",
|
||||
"description": "This device is missing full capability coverage. Either its appliance type wasn't recognized, or some of the resources it exposes aren't modeled yet. It'll keep working with whatever is already supported. You can help expand support by going to Settings > Devices & Services > {device_name} > the menu (top right) > Download diagnostics, then filing it with the linked issue template."
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user