Use growattServer library error code constants in growatt_server (#172771)

This commit is contained in:
johanzander
2026-06-02 04:54:27 +02:00
committed by GitHub
parent a21212ab7e
commit c22823ff8d
6 changed files with 20 additions and 30 deletions
@@ -10,8 +10,8 @@ Classic API (username/password):
Open API V1 (API token):
- Stateless — no login call, token is sent as a Bearer header on every request.
- Auth failure is signalled by raising GrowattV1ApiError with error_code=10011
(V1_API_ERROR_NO_PRIVILEGE). The library NEVER returns a failure silently;
- Auth failure is signalled by raising GrowattV1ApiError with
error_code=GrowattV1ApiErrorCode.NO_PRIVILEGE. The library NEVER returns a failure silently;
any non-zero error_code raises an exception via _process_response().
- Because the library always raises on error, return-value validation after a
successful V1 API call is unnecessary — if it returned, the token was valid.
@@ -19,7 +19,7 @@ Open API V1 (API token):
Error handling pattern for reauth:
- Classic API: check NOT login_response["success"] and msg == LOGIN_INVALID_AUTH_CODE
→ raise ConfigEntryAuthFailed
- V1 API: catch GrowattV1ApiError with error_code == V1_API_ERROR_NO_PRIVILEGE
- V1 API: catch GrowattV1ApiError with error_code == GrowattV1ApiErrorCode.NO_PRIVILEGE
→ raise ConfigEntryAuthFailed
- All other errors → ConfigEntryError (setup) or UpdateFailed (coordinator)
"""
@@ -30,6 +30,7 @@ from json import JSONDecodeError
import logging
import growattServer
from growattServer import GrowattV1ApiErrorCode
from requests import RequestException
from homeassistant.const import CONF_PASSWORD, CONF_TOKEN, CONF_URL, CONF_USERNAME
@@ -58,8 +59,6 @@ from .const import (
LOGIN_INVALID_AUTH_CODE,
PLATFORMS,
SUPPORTED_DEVICE_TYPES,
V1_API_ERROR_NO_PRIVILEGE,
V1_API_ERROR_RATE_LIMITED,
V1_DEVICE_TYPES,
)
from .coordinator import GrowattConfigEntry, GrowattCoordinator
@@ -265,11 +264,11 @@ def get_device_list_v1(
try:
devices_dict = api.device_list(plant_id)
except growattServer.GrowattV1ApiError as e:
if e.error_code == V1_API_ERROR_NO_PRIVILEGE:
if e.error_code == GrowattV1ApiErrorCode.NO_PRIVILEGE:
raise ConfigEntryAuthFailed(
f"Authentication failed for Growatt API: {e.error_msg or str(e)}"
) from e
if e.error_code == V1_API_ERROR_RATE_LIMITED:
if e.error_code == GrowattV1ApiErrorCode.RATE_LIMITED:
raise ConfigEntryNotReady(
f"Growatt API rate limited, will retry: {e.error_msg or str(e)}"
) from e
@@ -5,6 +5,7 @@ import logging
from typing import Any
import growattServer
from growattServer import GrowattV1ApiErrorCode
import requests
import voluptuous as vol
@@ -32,7 +33,6 @@ from .const import (
ERROR_INVALID_AUTH,
LOGIN_INVALID_AUTH_CODE,
SERVER_URLS_NAMES,
V1_API_ERROR_NO_PRIVILEGE,
)
_URL_TO_REGION = {v: k for k, v in SERVER_URLS_NAMES.items()}
@@ -148,7 +148,7 @@ class GrowattServerConfigFlow(ConfigFlow, domain=DOMAIN):
_LOGGER.debug("Network error during credential update: %s", ex)
errors["base"] = ERROR_CANNOT_CONNECT
except growattServer.GrowattV1ApiError as err:
if err.error_code == V1_API_ERROR_NO_PRIVILEGE:
if err.error_code == GrowattV1ApiErrorCode.NO_PRIVILEGE:
errors["base"] = ERROR_INVALID_AUTH
else:
_LOGGER.debug(
@@ -301,7 +301,7 @@ class GrowattServerConfigFlow(ConfigFlow, domain=DOMAIN):
e.error_msg or str(e),
e.error_code,
)
if e.error_code == V1_API_ERROR_NO_PRIVILEGE:
if e.error_code == GrowattV1ApiErrorCode.NO_PRIVILEGE:
return self._async_show_token_form({"base": ERROR_INVALID_AUTH})
return self._async_show_token_form({"base": ERROR_CANNOT_CONNECT})
except (ValueError, KeyError, TypeError, AttributeError) as ex:
@@ -42,13 +42,6 @@ PLATFORMS = [Platform.NUMBER, Platform.SENSOR, Platform.SWITCH]
# Growatt Classic API error codes
LOGIN_INVALID_AUTH_CODE = "502"
# Growatt Open API V1 error codes
# Reference: https://www.showdoc.com.cn/262556420217021/1494055648380019
V1_API_ERROR_WRONG_DOMAIN = -1 # Use correct regional domain
V1_API_ERROR_NO_PRIVILEGE = 10011 # No privilege access — invalid or expired token
V1_API_ERROR_RATE_LIMITED = 10012 # Access frequency limit (5 minutes per call)
V1_API_ERROR_PAGE_SIZE = 10013 # Page size cannot exceed 100
V1_API_ERROR_PAGE_COUNT = 10014 # Page count cannot exceed 250
# Config flow error types (also used as abort reasons)
ERROR_CANNOT_CONNECT = "cannot_connect" # Used for both form errors and aborts
@@ -6,6 +6,7 @@ import logging
from typing import TYPE_CHECKING, Any
import growattServer
from growattServer import GrowattV1ApiErrorCode
from requests import RequestException
from homeassistant.components.sensor import SensorStateClass
@@ -27,7 +28,6 @@ from .const import (
DEFAULT_URL,
DOMAIN,
LOGIN_INVALID_AUTH_CODE,
V1_API_ERROR_NO_PRIVILEGE,
V1_DEVICE_TYPES,
)
from .models import GrowattRuntimeData
@@ -113,7 +113,7 @@ class GrowattCoordinator(DataUpdateCoordinator[dict[str, Any]]):
if device.get("type") in V1_DEVICE_TYPES
]
except growattServer.GrowattV1ApiError as err:
if err.error_code == V1_API_ERROR_NO_PRIVILEGE:
if err.error_code == GrowattV1ApiErrorCode.NO_PRIVILEGE:
raise ConfigEntryAuthFailed(
f"Authentication failed for Growatt API: {err.error_msg or str(err)}"
) from err
@@ -179,7 +179,7 @@ class GrowattCoordinator(DataUpdateCoordinator[dict[str, Any]]):
try:
total_info = self.api.plant_energy_overview(self.plant_id)
except growattServer.GrowattV1ApiError as err:
if err.error_code == V1_API_ERROR_NO_PRIVILEGE:
if err.error_code == GrowattV1ApiErrorCode.NO_PRIVILEGE:
raise ConfigEntryAuthFailed(
"Authentication failed for Growatt API:"
f" {err.error_msg or str(err)}"
@@ -212,7 +212,7 @@ class GrowattCoordinator(DataUpdateCoordinator[dict[str, Any]]):
min_settings = self.api.min_settings(self.device_id)
min_energy = self.api.min_energy(self.device_id)
except growattServer.GrowattV1ApiError as err:
if err.error_code == V1_API_ERROR_NO_PRIVILEGE:
if err.error_code == GrowattV1ApiErrorCode.NO_PRIVILEGE:
raise ConfigEntryAuthFailed(
"Authentication failed for Growatt API:"
f" {err.error_msg or str(err)}"
@@ -240,7 +240,7 @@ class GrowattCoordinator(DataUpdateCoordinator[dict[str, Any]]):
sph_detail = self.api.sph_detail(self.device_id)
sph_energy = self.api.sph_energy(self.device_id)
except growattServer.GrowattV1ApiError as err:
if err.error_code == V1_API_ERROR_NO_PRIVILEGE:
if err.error_code == GrowattV1ApiErrorCode.NO_PRIVILEGE:
raise ConfigEntryAuthFailed(
"Authentication failed for Growatt API:"
f" {err.error_msg or str(err)}"
@@ -6,6 +6,7 @@ from typing import Any
from unittest.mock import MagicMock
import growattServer
from growattServer import GrowattV1ApiErrorCode
import pytest
import requests
import voluptuous as vol
@@ -23,8 +24,6 @@ from homeassistant.components.growatt_server.const import (
ERROR_INVALID_AUTH,
LOGIN_INVALID_AUTH_CODE,
SERVER_URLS_NAMES,
V1_API_ERROR_NO_PRIVILEGE,
V1_API_ERROR_RATE_LIMITED,
)
from homeassistant.const import (
CONF_NAME,
@@ -355,8 +354,8 @@ async def test_password_auth_multiple_plants(
@pytest.mark.parametrize(
("error_code", "expected_error"),
[
(V1_API_ERROR_NO_PRIVILEGE, ERROR_INVALID_AUTH),
(V1_API_ERROR_RATE_LIMITED, ERROR_CANNOT_CONNECT),
(GrowattV1ApiErrorCode.NO_PRIVILEGE, ERROR_INVALID_AUTH),
(GrowattV1ApiErrorCode.RATE_LIMITED, ERROR_CANNOT_CONNECT),
],
)
@pytest.mark.usefixtures("mock_setup_entry")
+3 -4
View File
@@ -5,6 +5,7 @@ import json
from freezegun.api import FrozenDateTimeFactory
import growattServer
from growattServer import GrowattV1ApiErrorCode
import pytest
import requests
from syrupy.assertion import SnapshotAssertion
@@ -19,8 +20,6 @@ from homeassistant.components.growatt_server.const import (
DEVICE_SCAN_INTERVAL,
DOMAIN,
LOGIN_INVALID_AUTH_CODE,
V1_API_ERROR_RATE_LIMITED,
V1_API_ERROR_WRONG_DOMAIN,
)
from homeassistant.config_entries import ConfigEntryState
from homeassistant.const import (
@@ -70,7 +69,7 @@ async def test_device_info(
(
growattServer.GrowattV1ApiError(
message="API Error",
error_code=V1_API_ERROR_WRONG_DOMAIN,
error_code=GrowattV1ApiErrorCode.WRONG_DOMAIN,
error_msg="Invalid JSON",
),
ConfigEntryState.SETUP_ERROR,
@@ -82,7 +81,7 @@ async def test_device_info(
(
growattServer.GrowattV1ApiError(
message="Rate limited",
error_code=V1_API_ERROR_RATE_LIMITED,
error_code=GrowattV1ApiErrorCode.RATE_LIMITED,
error_msg="Access frequency limit",
),
ConfigEntryState.SETUP_RETRY,