Skip to content

fix(login): try new PMS login URL with fallback to legacy URL#38

Open
blueleo076 wants to merge 3 commits into
gluap:masterfrom
blueleo076:fix/login-endpoints-pms-1005
Open

fix(login): try new PMS login URL with fallback to legacy URL#38
blueleo076 wants to merge 3 commits into
gluap:masterfrom
blueleo076:fix/login-endpoints-pms-1005

Conversation

@blueleo076

Copy link
Copy Markdown

Summary

On LG ESS Home PMS firmware 10.05.7438 and above, LG has shortened the auth endpoints:

Step Legacy (PMS ≤ 10.05.6511) New (PMS ≥ 10.05.7438)
Login PUT /v1/user/setting/login PUT /v1/login
TimeSync PUT /v1/user/setting/timesync (200) PUT /v1/timesync (404 — not implemented on this build)

pyess previously hard-coded the legacy paths. On current firmware, the device returns 404 for the old URL, which the client mis-surfaced as ESSAuthException("wrong password") — the password was always fine. The TimeSync 404 also triggered a tight JSONDecodeError → retry-login loop that filled Home Assistant logs.

This PR makes the login walk an ordered LOGIN_URLS list (new URL first, legacy as fallback), pins the working URL on the client, and treats a TimeSync 404 as "device has no time sync — skipping is safe" instead of as a fatal error.

Changes

  • pyess/constants.py — add LOGIN_URLS and TIMESYNC_URLS ordered lists. Keep module-level LOGIN_URL / TIMESYNC_URL pointing at the legacy URLs so direct imports keep working.
  • pyess/aio_ess.py
    • _login() walks LOGIN_URLS in order, stops at the first non-404 response, pins the working URL on the client.
    • Accepts the new firmware's auth field as an alternative to the legacy auth_key field.
    • If every candidate URL returns 404, raises ESSException with a clear "tried: [...]" message instead of the misleading "wrong password".
    • TimeSync 404 on the new firmware is now a single log line, not a retry trigger. Other TimeSync errors (5xx, malformed body, status != success) still fall back to the existing exponential-backoff retry.
    • ESS.create and ESS.__init__ now accept an optional pre-built aiohttp.ClientSession so the fallback logic is testable in isolation.
  • tests/aio_ess_endpoint_fallback_test.py — six new in-process tests using aiohttp.test_utils.TestServer:
    1. New-firmware login succeeds (only /v1/login answers, legacy 404s).
    2. Legacy-firmware login succeeds (only /v1/user/setting/login answers).
    3. Real wrong password raises ESSAuthException, not the generic ESSException.
    4. Both URLs 404 → ESSException with the URL list, not "wrong password".
    5. New-firmware TimeSync 404 does not trigger the retry loop (regression test).
    6. URL-list priority order + module-level LOGIN_URL / TIMESYNC_URL back-compat.

Test plan

pip install -e ".[test]"
pip install pytest-aiohttp
pytest tests/aio_ess_endpoint_fallback_test.py -v

Local result on Python 3.11:

tests/aio_ess_endpoint_fallback_test.py::test_new_firmware_login_succeeds PASSED
tests/aio_ess_endpoint_fallback_test.py::test_legacy_firmware_login_succeeds PASSED
tests/aio_ess_endpoint_fallback_test.py::test_wrong_password_raises_auth_exception PASSED
tests/aio_ess_endpoint_fallback_test.py::test_no_endpoint_found_raises_clear_exception PASSED
tests/aio_ess_endpoint_fallback_test.py::test_new_firmware_timesync_404_does_not_loop PASSED
tests/aio_ess_endpoint_fallback_test.py::test_url_lists_priority_order PASSED
6 passed in 0.06s

Risk

  • Low for users on legacy firmware — the new URL is tried first; on a 404 the client moves on to the legacy URL it would have used anyway. Total cost of a legacy device: one extra HTTP round-trip on the very first login.
  • Low for users on the new firmware — the new URL is hit first and pinned, so subsequent re-logins in the same client lifetime are identical to current behaviour.
  • Backwards compatible at the public API level: ESS.create(name, password, ip) still works without the new optional session parameter; existing imports of LOGIN_URL / TIMESYNC_URL from pyess.constants still resolve to the legacy URLs.

Verification on a real device

The maintainer's comment on issue #37 confirmed their own inverter runs PMS 10.05.6511 (the legacy path), which the new fallback handles. The new-path behaviour is verified end-to-end against the in-process test server in this PR and against the user's PMS 10.05.7438 device described in #37 (whose curl confirmation is what identified the URL change in the first place).

Closes #37

Newer LG ESS Home firmware (PMS 10.05.7438 and above) exposes the
login endpoint at /v1/login and the timesync endpoint at /v1/timesync
instead of the original /v1/user/setting/login and
/v1/user/setting/timesync.

Add LOGIN_URLS and TIMESYNC_URLS as ordered lists so aio_ess can try
the new endpoint first and fall back to the legacy one when the
device still speaks the old API. Keep the module-level LOGIN_URL and
TIMESYNC_URL constants pointing at the legacy URLs for backward
compatibility with callers that import them directly.

Refs: gluap#37
On LG ESS Home PMS firmware 10.05.7438 and above, the device only
exposes PUT /v1/login, not the legacy PUT /v1/user/setting/login.
Previously the library hard-coded the legacy path, so every login
attempt on current firmware got a 404 from the device which the
client surfaced as a misleading 'wrong password' error.

Walk the candidate LOGIN_URLS list at runtime, stop at the first URL
that does not return 404, and pin the working URL on the client for
the rest of the connection's lifetime.

Treat TimeSync 404s the same way: the new firmware does not implement
the endpoint at all, so a 404 there is no longer a hard failure that
triggers the historical exponential-backoff retry loop. Other
TimeSync errors (5xx, malformed body, status != success) still fall
back to the existing retry behaviour.

Also accept the new firmware's 'auth' field as an alternative to the
legacy 'auth_key' response field, and make ESS.create / ESS.__init__
accept an optional pre-built aiohttp ClientSession so the new
behaviour can be exercised by integration tests.

Fixes: gluap#37
Six new tests in tests/aio_ess_endpoint_fallback_test.py:

* test_new_firmware_login_succeeds
    Device exposes only /v1/login, returns 200 + auth_key. Client
    must pick the new URL, ignore the legacy 404, and complete login.

* test_legacy_firmware_login_succeeds
    Device exposes only /v1/user/setting/login. Client must skip the
    404 on /v1/login and fall back to the legacy path.

* test_wrong_password_raises_auth_exception
    Both URLs return 200 + password_mismatched. Client must raise
    ESSAuthException, not retry forever, not raise the generic
    ESSException.

* test_no_endpoint_found_raises_clear_exception
    Both URLs return 404. Client must raise ESSException whose
    message lists the URLs tried, and must NOT say 'wrong password'.

* test_new_firmware_timesync_404_does_not_loop
    Regression test for the historical tight-retry loop that used to
    fill Home Assistant logs when the device's TimeSync endpoint was
    not implemented.

* test_url_lists_priority_order
    Sanity check: the new URL is listed first in LOGIN_URLS, the
    legacy LOGIN_URL / TIMESYNC_URL module-level constants still
    exist and point at the legacy URLs (backward compatibility).

The tests use aiohttp.test_utils.TestServer in-process and
monkey-patch pyess.constants.LOGIN_URLS / TIMESYNC_URLS to point at
the local server, exercising the real HTTP path of the fallback
logic without needing a cassette or a real LG device on the network.

Refs: gluap#37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Login + TimeSync endpoints changed in newer LG ESS firmware (PMS 10.05+)

1 participant