Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 45 additions & 24 deletions custom_components/hon/appliance_list_patch.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,13 @@
``/commands/v1/appliance`` REST endpoint that pyhon-revived (0.18.3) calls in
:meth:`pyhon.connection.api.HonAPI.load_appliances`. For migrated accounts that
endpoint now answers ``200`` with an empty ``{"payload": {"appliances": []}}``
body, so every Haier device ends up ``unavailable`` in Home Assistant. The hOn
mobile app reads the list from ``{API_URL}/unified-api/v1/view/appliance-list``
instead, which returns the same appliance records.
body, so every Haier device ends up ``unavailable`` in Home Assistant.

The hOn app now reads the list from a ``POST`` to
``{API_URL}/unified-api/v1/view/appliance-list`` with a ``{"deviceId": ...}``
body, and returns the appliances nested under
``modules.applianceList.payload.appliances`` (shape cross-checked against
gvigroux/hon's ``async_authorize``).

This module monkey-patches ``load_appliances`` to query the new endpoint. It is
a stopgap until the fix lands upstream in pyhon-revived; delete this module and
Expand All @@ -17,6 +21,7 @@
Ref: https://github.com/mmalolepszy/hon-revived/issues/48
"""

import json
import logging
from typing import Any

Expand All @@ -29,45 +34,61 @@
# list for migrated accounts; this is the endpoint the current app uses.
APPLIANCE_LIST_URL = f"{const.API_URL}/unified-api/v1/view/appliance-list"

# Keys the unified-api response might nest the appliance list under. The exact
# envelope is undocumented, so probe the plausible shapes rather than assume one
# and silently return nothing.
# Fallback keys the appliance list might be nested under, in case the envelope
# differs from the primary ``modules.applianceList.payload.appliances`` path.
_LIST_KEYS = ("appliances", "applianceList", "appliancesList", "items", "data")


def _extract_appliances(result: Any) -> list[dict[str, Any]]:
"""Pull the appliance list out of the unified-api response body."""
if not result:
if isinstance(result, list):
return result
if not isinstance(result, dict):
return []
if isinstance(result, dict):
payload: Any = result.get("payload", result)
else:
payload = result
if isinstance(payload, list):
return payload
# Primary shape used by the current app (see module docstring).
try:
nested = result["modules"]["applianceList"]["payload"]["appliances"]
if isinstance(nested, list):
return nested
except (KeyError, TypeError):
pass
# Fallbacks: legacy ``payload.appliances`` or any known list-bearing key.
payload = result.get("payload", result)
if isinstance(payload, dict):
for key in _LIST_KEYS:
value = payload.get(key)
if isinstance(value, list):
return value
_LOGGER.warning(
"hon: unexpected appliance-list response shape (top-level=%s, payload=%s); "
"no appliances parsed",
list(result.keys()) if isinstance(result, dict) else type(result).__name__,
list(payload.keys()) if isinstance(payload, dict) else type(payload).__name__,
)
return []


async def _load_appliances(self: HonAPI) -> list[dict[str, Any]]:
"""Replacement for ``HonAPI.load_appliances`` hitting the new endpoint."""
# pylint: disable=protected-access
async with self._hon.get(APPLIANCE_LIST_URL) as response:
result = await response.json()
async with self._hon.post(
APPLIANCE_LIST_URL, json={"deviceId": "homeassistant"}
) as response:
status = response.status
raw = await response.text()
try:
result: Any = json.loads(raw) if raw else None
except ValueError:
result = None
appliances = _extract_appliances(result)
_LOGGER.debug(
"hon: loaded %d appliance(s) from %s", len(appliances), APPLIANCE_LIST_URL
)
if appliances:
_LOGGER.debug(
"hon: loaded %d appliance(s) from %s", len(appliances), APPLIANCE_LIST_URL
)
else:
# Surface the raw response so a shape/auth mismatch is debuggable from
# the HA log instead of silently yielding zero devices.
_LOGGER.warning(
"hon: appliance-list returned no appliances (HTTP %s) from %s; "
"body (truncated): %s",
status,
APPLIANCE_LIST_URL,
raw[:600],
)
return appliances


Expand Down
Loading