diff --git a/README.md b/README.md
index a53b859..4b38d83 100644
--- a/README.md
+++ b/README.md
@@ -14,6 +14,7 @@ Component for tracking devices and managing routers based on [MiWiFi](http://miw
- [Supported routers](#supported-routers)
- [API check list](#api-check-list)
- [Summary](#summary)
+- [Diagnostics](#diagnostics)
## FAQ
**Q. Do I need to get telnet or ssh?**
@@ -163,3 +164,26 @@ Many more Xiaomi and Redmi routers supported by MiWiFi (OpenWRT - Luci API)
|  | **Mi Router Mini** | R1CM | 🟢🟢🟢🟢 | 🟢🟢🟢🟢🟢🟢🟢🟢🔴 | 🟢🟢🟢🟢 |
|  | **Mi Router R2D** | R2D | 🟢🟢🟢🟢 | 🟢🟢🟢🟢🟢🟢🟢🟢🔴 | 🟢🟢🟢🟢 |
|  | **Mi Router R1D** | R1D | 🟢🟢🟢🟢 | 🟢🟢🟢🟢🟢🟢🟢🟢🔴 | 🟢🟢🟢🟢 |
+
+## Diagnostics
+You will need to obtain diagnostic data to search for a problem or before creating an issue.
+
+### Via GUI:
+
+How to obtain diagnostic data can be found on the website HASS: [Diagnostic Documentation](https://www.home-assistant.io/integrations/diagnostics/)
+
+### Via Debug:
+
+❗ Check the data that you publish in the issue, they may contain secret data.
+
+Set component to debug mode and reload HASS:
+
+```yaml
+logger:
+ default: error
+ logs:
+ ...
+ custom_components.miwifi: debug
+```
+
+Then wait a bit and you can watch the logs, they will need information
\ No newline at end of file
diff --git a/custom_components/miwifi/const.py b/custom_components/miwifi/const.py
index 3475b45..a2c33cf 100644
--- a/custom_components/miwifi/const.py
+++ b/custom_components/miwifi/const.py
@@ -25,6 +25,11 @@
DISCOVERY: Final = "discovery"
DISCOVERY_INTERVAL: Final = timedelta(minutes=60)
+"""Diagnostic const"""
+DIAGNOSTIC_DATE_TIME: Final = "date_time"
+DIAGNOSTIC_MESSAGE: Final = "message"
+DIAGNOSTIC_CONTENT: Final = "content"
+
"""Helper const"""
UPDATER: Final = "updater"
UPDATE_LISTENER: Final = "update_listener"
diff --git a/custom_components/miwifi/diagnostics.py b/custom_components/miwifi/diagnostics.py
new file mode 100644
index 0000000..9387ccc
--- /dev/null
+++ b/custom_components/miwifi/diagnostics.py
@@ -0,0 +1,57 @@
+"""MiWifi diagnostic."""
+
+from __future__ import annotations
+
+from typing import Final
+
+from homeassistant.components.diagnostics import async_redact_data
+from homeassistant.config_entries import ConfigEntry
+from homeassistant.const import (
+ CONF_PASSWORD,
+ CONF_USERNAME,
+ CONF_URL,
+ CONF_TOKEN,
+ CONF_ID,
+)
+from homeassistant.core import HomeAssistant
+
+from .const import (
+ DOMAIN,
+ UPDATER,
+ ATTR_CAMERA_IMAGE,
+)
+
+TO_REDACT: Final = {
+ CONF_PASSWORD,
+ CONF_USERNAME,
+ CONF_URL,
+ CONF_TOKEN,
+ CONF_ID,
+ ATTR_CAMERA_IMAGE,
+ "routerId",
+ "gateWay",
+ "hostname",
+ "ipv4",
+ "ssid",
+ "pwd",
+}
+
+
+async def async_get_config_entry_diagnostics(
+ hass: HomeAssistant, config_entry: ConfigEntry
+) -> dict:
+ """Return diagnostics for a config entry."""
+
+ _data: dict = {"config_entry": async_redact_data(config_entry.as_dict(), TO_REDACT)}
+
+ if _updater := hass.data[DOMAIN][config_entry.entry_id].get(UPDATER):
+ if hasattr(_updater, "data"):
+ _data["data"] = async_redact_data(_updater.data, TO_REDACT)
+
+ if hasattr(_updater, "devices"):
+ _data["devices"] = _updater.devices
+
+ if len(_updater.luci.diagnostics) > 0:
+ _data["requests"] = async_redact_data(_updater.luci.diagnostics, TO_REDACT)
+
+ return _data
diff --git a/custom_components/miwifi/luci.py b/custom_components/miwifi/luci.py
index 223ead3..2140f94 100644
--- a/custom_components/miwifi/luci.py
+++ b/custom_components/miwifi/luci.py
@@ -11,6 +11,8 @@
import uuid
import urllib.parse
+from datetime import datetime
+from typing import Any
from httpx import AsyncClient, Response, HTTPError
from homeassistant.util import slugify
@@ -23,6 +25,9 @@
CLIENT_LOGIN_TYPE,
CLIENT_NONCE_TYPE,
CLIENT_PUBLIC_KEY,
+ DIAGNOSTIC_DATE_TIME,
+ DIAGNOSTIC_MESSAGE,
+ DIAGNOSTIC_CONTENT,
)
from .exceptions import LuciConnectionException, LuciTokenException
@@ -67,14 +72,17 @@ def __init__(
self._url = CLIENT_URL.format(ip=ip)
+ self.diagnostics: dict[str, Any] = {}
+
async def login(self) -> dict:
"""Login method
:return dict: dict with login data.
"""
+ _method: str = "xqsystem/login"
_nonce: str = self.generate_nonce()
- _url: str = f"{self._url}/api/xqsystem/login"
+ _url: str = f"{self._url}/api/{_method}"
try:
async with self._client as client:
@@ -91,15 +99,17 @@ async def login(self) -> dict:
timeout=self._timeout,
)
- _LOGGER.debug("Successful request %s: %s", _url, response.content)
+ self._debug("Successful request", _url, response.content, _method)
_data: dict = json.loads(response.content)
except (HTTPError, ValueError, TypeError) as _e:
- _LOGGER.debug("Connection error %r", _e)
+ self._debug("Connection error", _url, _e, _method)
raise LuciConnectionException("Connection error") from _e
if response.status_code != 200 or "token" not in _data:
+ self._debug("Failed to get token", _url, _data, _method)
+
raise LuciTokenException("Failed to get token")
self._token = _data["token"]
@@ -112,15 +122,16 @@ async def logout(self) -> None:
if self._token is None:
return
- _url: str = f"{self._url}/;stok={self._token}/web/logout"
+ _method: str = "logout"
+ _url: str = f"{self._url}/;stok={self._token}/web/{_method}"
try:
async with self._client as client:
response: Response = await client.get(_url, timeout=self._timeout)
- _LOGGER.debug("Successful request %s: %s", _url, response.content)
+ self._debug("Successful request", _url, response.content, _method)
except (HTTPError, ValueError, TypeError) as _e:
- _LOGGER.debug("Logout error: %r", _e)
+ self._debug("Logout error", _url, _e, _method)
async def get(
self, path: str, query_params: dict | None = None, use_stok: bool = True
@@ -143,15 +154,17 @@ async def get(
async with self._client as client:
response: Response = await client.get(_url, timeout=self._timeout)
- _LOGGER.debug("Successful request %s: %s", _url, response.content)
+ self._debug("Successful request", _url, response.content, path)
_data: dict = json.loads(response.content)
except (HTTPError, ValueError, TypeError) as _e:
- _LOGGER.debug("Connection error %r", _e)
+ self._debug("Connection error", _url, _e, path)
raise LuciConnectionException("Connection error") from _e
if "code" not in _data or _data["code"] > 0:
+ self._debug("Invalid error code received", _url, _data, path)
+
raise LuciTokenException("Invalid error code received")
return _data
@@ -308,20 +321,19 @@ async def image(self, hardware: str) -> bytes | None:
"""
hardware = slugify(hardware.lower())
- # fmt: off
- url: str = f"http://{self.ip}/xiaoqiang/web/img/icons/router_{hardware}_100_on.png"
- # fmt: on
+ _path: str = f"icons/router_{hardware}_100_on.png"
+ _url: str = f"http://{self.ip}/xiaoqiang/web/img/{_path}"
try:
async with self._client as client:
- response: Response = await client.get(url, timeout=self._timeout)
+ response: Response = await client.get(_url, timeout=self._timeout)
- _LOGGER.debug("Successful request image %s", url)
+ self._debug("Successful request image", _url, response.status_code, _path)
if len(response.content) > 0:
return base64.b64encode(response.content)
- except HTTPError:
- return None
+ except HTTPError as _e:
+ self._debug("Error request image", _url, _e, _path)
return None
@@ -365,3 +377,27 @@ def generate_password_hash(self, nonce: str, password: str) -> str:
"""
return self.sha1(nonce + self.sha1(password + CLIENT_PUBLIC_KEY))
+
+ def _debug(self, message: str, url: str, content: Any, path: str) -> None:
+ """Debug log
+
+ :param message: str: Message
+ :param url: str: URL
+ :param content: Any: Content
+ :param path: str: Path
+ """
+
+ _LOGGER.debug("%s (%s): %s", message, url, str(content))
+
+ _content: dict | str = {}
+
+ try:
+ _content = json.loads(content)
+ except (ValueError, TypeError):
+ _content = str(content)
+
+ self.diagnostics[path] = {
+ DIAGNOSTIC_DATE_TIME: datetime.now().replace(microsecond=0).isoformat(),
+ DIAGNOSTIC_MESSAGE: message,
+ DIAGNOSTIC_CONTENT: _content,
+ }
diff --git a/custom_components/miwifi/manifest.json b/custom_components/miwifi/manifest.json
index dca5bde..55cd609 100644
--- a/custom_components/miwifi/manifest.json
+++ b/custom_components/miwifi/manifest.json
@@ -1,7 +1,7 @@
{
"domain": "miwifi",
"name": "MiWiFi",
- "version": "2.3.1",
+ "version": "2.4.0",
"documentation": "https://github.com/dmamontov/hass-miwifi/blob/main/README.md",
"issue_tracker": "https://github.com/dmamontov/hass-miwifi/issues",
"config_flow": true,