Address some Wallbox quality scale issues (#148200)

This commit is contained in:
Hessel 2025-07-07 10:09:07 +02:00 committed by GitHub
parent 6351c3302e
commit 0bce01da0b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 321 additions and 273 deletions

View File

@ -9,7 +9,7 @@ from homeassistant.const import CONF_PASSWORD, CONF_USERNAME, Platform
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryAuthFailed
from .const import DOMAIN, UPDATE_INTERVAL
from .const import UPDATE_INTERVAL
from .coordinator import InvalidAuth, WallboxCoordinator, async_validate_input
PLATFORMS = [
@ -20,8 +20,10 @@ PLATFORMS = [
Platform.SWITCH,
]
type WallboxConfigEntry = ConfigEntry[WallboxCoordinator]
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_setup_entry(hass: HomeAssistant, entry: WallboxConfigEntry) -> bool:
"""Set up Wallbox from a config entry."""
wallbox = Wallbox(
entry.data[CONF_USERNAME],
@ -36,7 +38,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
wallbox_coordinator = WallboxCoordinator(hass, entry, wallbox)
await wallbox_coordinator.async_config_entry_first_refresh()
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = wallbox_coordinator
entry.runtime_data = wallbox_coordinator
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
@ -45,8 +47,4 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
if unload_ok:
hass.data[DOMAIN].pop(entry.entry_id)
return unload_ok
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)

View File

@ -222,7 +222,9 @@ class WallboxCoordinator(DataUpdateCoordinator[dict[str, Any]]):
return data # noqa: TRY300
except requests.exceptions.HTTPError as wallbox_connection_error:
if wallbox_connection_error.response.status_code == 403:
raise InvalidAuth from wallbox_connection_error
raise InvalidAuth(
translation_domain=DOMAIN, translation_key="invalid_auth"
) from wallbox_connection_error
if wallbox_connection_error.response.status_code == 429:
raise HomeAssistantError(
translation_domain=DOMAIN, translation_key="too_many_requests"
@ -248,7 +250,9 @@ class WallboxCoordinator(DataUpdateCoordinator[dict[str, Any]]):
return data # noqa: TRY300
except requests.exceptions.HTTPError as wallbox_connection_error:
if wallbox_connection_error.response.status_code == 403:
raise InvalidAuth from wallbox_connection_error
raise InvalidAuth(
translation_domain=DOMAIN, translation_key="invalid_auth"
) from wallbox_connection_error
if wallbox_connection_error.response.status_code == 429:
raise HomeAssistantError(
translation_domain=DOMAIN, translation_key="too_many_requests"
@ -303,7 +307,9 @@ class WallboxCoordinator(DataUpdateCoordinator[dict[str, Any]]):
return data # noqa: TRY300
except requests.exceptions.HTTPError as wallbox_connection_error:
if wallbox_connection_error.response.status_code == 403:
raise InvalidAuth from wallbox_connection_error
raise InvalidAuth(
translation_domain=DOMAIN, translation_key="invalid_auth"
) from wallbox_connection_error
if wallbox_connection_error.response.status_code == 429:
raise HomeAssistantError(
translation_domain=DOMAIN, translation_key="too_many_requests"

View File

@ -13,7 +13,6 @@ from .const import (
CHARGER_DATA_KEY,
CHARGER_LOCKED_UNLOCKED_KEY,
CHARGER_SERIAL_NUMBER_KEY,
DOMAIN,
)
from .coordinator import WallboxCoordinator
from .entity import WallboxEntity
@ -32,7 +31,7 @@ async def async_setup_entry(
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Create wallbox lock entities in HASS."""
coordinator: WallboxCoordinator = hass.data[DOMAIN][entry.entry_id]
coordinator: WallboxCoordinator = entry.runtime_data
async_add_entities(
WallboxLock(coordinator, description)
for ent in coordinator.data
@ -40,6 +39,10 @@ async def async_setup_entry(
)
# Coordinator is used to centralize the data updates
PARALLEL_UPDATES = 0
class WallboxLock(WallboxEntity, LockEntity):
"""Representation of a wallbox lock."""

View File

@ -23,7 +23,6 @@ from .const import (
CHARGER_MAX_ICP_CURRENT_KEY,
CHARGER_PART_NUMBER_KEY,
CHARGER_SERIAL_NUMBER_KEY,
DOMAIN,
)
from .coordinator import WallboxCoordinator
from .entity import WallboxEntity
@ -84,7 +83,7 @@ async def async_setup_entry(
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Create wallbox number entities in HASS."""
coordinator: WallboxCoordinator = hass.data[DOMAIN][entry.entry_id]
coordinator: WallboxCoordinator = entry.runtime_data
async_add_entities(
WallboxNumber(coordinator, entry, description)
for ent in coordinator.data
@ -92,6 +91,10 @@ async def async_setup_entry(
)
# Coordinator is used to centralize the data updates
PARALLEL_UPDATES = 0
class WallboxNumber(WallboxEntity, NumberEntity):
"""Representation of the Wallbox portal."""

View File

@ -62,7 +62,7 @@ async def async_setup_entry(
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Create wallbox select entities in HASS."""
coordinator: WallboxCoordinator = hass.data[DOMAIN][entry.entry_id]
coordinator: WallboxCoordinator = entry.runtime_data
if coordinator.data[CHARGER_ECO_SMART_KEY] != EcoSmartMode.DISABLED:
async_add_entities(
WallboxSelect(coordinator, description)
@ -74,6 +74,10 @@ async def async_setup_entry(
)
# Coordinator is used to centralize the data updates
PARALLEL_UPDATES = 0
class WallboxSelect(WallboxEntity, SelectEntity):
"""Representation of the Wallbox portal."""

View File

@ -43,7 +43,6 @@ from .const import (
CHARGER_SERIAL_NUMBER_KEY,
CHARGER_STATE_OF_CHARGE_KEY,
CHARGER_STATUS_DESCRIPTION_KEY,
DOMAIN,
)
from .coordinator import WallboxCoordinator
from .entity import WallboxEntity
@ -174,7 +173,7 @@ async def async_setup_entry(
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Create wallbox sensor entities in HASS."""
coordinator: WallboxCoordinator = hass.data[DOMAIN][entry.entry_id]
coordinator: WallboxCoordinator = entry.runtime_data
async_add_entities(
WallboxSensor(coordinator, description)
@ -183,6 +182,10 @@ async def async_setup_entry(
)
# Coordinator is used to centralize the data updates
PARALLEL_UPDATES = 0
class WallboxSensor(WallboxEntity, SensorEntity):
"""Representation of the Wallbox portal."""

View File

@ -6,6 +6,11 @@
"station": "Station Serial Number",
"username": "[%key:common::config_flow::data::username%]",
"password": "[%key:common::config_flow::data::password%]"
},
"data_description": {
"station": "Serial number of the charger, this value can be found in the Wallbox App or in the Wallbox Portal.",
"username": "Username for your Wallbox Account.",
"password": "Password for your Wallbox Account."
}
},
"reauth_confirm": {
@ -115,6 +120,9 @@
},
"too_many_requests": {
"message": "Error communicating with Wallbox API, too many requests"
},
"invalid_auth": {
"message": "Invalid authentication"
}
}
}

View File

@ -14,7 +14,6 @@ from .const import (
CHARGER_PAUSE_RESUME_KEY,
CHARGER_SERIAL_NUMBER_KEY,
CHARGER_STATUS_DESCRIPTION_KEY,
DOMAIN,
ChargerStatus,
)
from .coordinator import WallboxCoordinator
@ -34,12 +33,16 @@ async def async_setup_entry(
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Create wallbox sensor entities in HASS."""
coordinator: WallboxCoordinator = hass.data[DOMAIN][entry.entry_id]
coordinator: WallboxCoordinator = entry.runtime_data
async_add_entities(
[WallboxSwitch(coordinator, SWITCH_TYPES[CHARGER_PAUSE_RESUME_KEY])]
)
# Coordinator is used to centralize the data updates
PARALLEL_UPDATES = 0
class WallboxSwitch(WallboxEntity, SwitchEntity):
"""Representation of the Wallbox portal."""

View File

@ -7,165 +7,22 @@ import pytest
import requests
from homeassistant.components.wallbox.const import (
CHARGER_ADDED_ENERGY_KEY,
CHARGER_ADDED_RANGE_KEY,
CHARGER_CHARGING_POWER_KEY,
CHARGER_CHARGING_SPEED_KEY,
CHARGER_CURRENCY_KEY,
CHARGER_CURRENT_VERSION_KEY,
CHARGER_DATA_KEY,
CHARGER_DATA_POST_L1_KEY,
CHARGER_DATA_POST_L2_KEY,
CHARGER_ECO_SMART_KEY,
CHARGER_ECO_SMART_MODE_KEY,
CHARGER_ECO_SMART_STATUS_KEY,
CHARGER_ENERGY_PRICE_KEY,
CHARGER_FEATURES_KEY,
CHARGER_LOCKED_UNLOCKED_KEY,
CHARGER_MAX_AVAILABLE_POWER_KEY,
CHARGER_MAX_CHARGING_CURRENT_KEY,
CHARGER_MAX_CHARGING_CURRENT_POST_KEY,
CHARGER_MAX_ICP_CURRENT_KEY,
CHARGER_NAME_KEY,
CHARGER_PART_NUMBER_KEY,
CHARGER_PLAN_KEY,
CHARGER_POWER_BOOST_KEY,
CHARGER_SERIAL_NUMBER_KEY,
CHARGER_SOFTWARE_KEY,
CHARGER_STATUS_ID_KEY,
CONF_STATION,
DOMAIN,
)
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
from homeassistant.core import HomeAssistant
from .const import ERROR, REFRESH_TOKEN_TTL, STATUS, TTL, USER_ID
from .const import WALLBOX_AUTHORISATION_RESPONSE, WALLBOX_STATUS_RESPONSE
from tests.common import MockConfigEntry
test_response = {
CHARGER_CHARGING_POWER_KEY: 0,
CHARGER_STATUS_ID_KEY: 193,
CHARGER_MAX_AVAILABLE_POWER_KEY: 25.0,
CHARGER_CHARGING_SPEED_KEY: 0,
CHARGER_ADDED_RANGE_KEY: 150,
CHARGER_ADDED_ENERGY_KEY: 44.697,
CHARGER_NAME_KEY: "WallboxName",
CHARGER_DATA_KEY: {
CHARGER_MAX_CHARGING_CURRENT_KEY: 24,
CHARGER_ENERGY_PRICE_KEY: 0.4,
CHARGER_LOCKED_UNLOCKED_KEY: False,
CHARGER_SERIAL_NUMBER_KEY: "20000",
CHARGER_PART_NUMBER_KEY: "PLP1-0-2-4-9-002-E",
CHARGER_SOFTWARE_KEY: {CHARGER_CURRENT_VERSION_KEY: "5.5.10"},
CHARGER_CURRENCY_KEY: {"code": "EUR/kWh"},
CHARGER_MAX_ICP_CURRENT_KEY: 20,
CHARGER_PLAN_KEY: {CHARGER_FEATURES_KEY: [CHARGER_POWER_BOOST_KEY]},
CHARGER_ECO_SMART_KEY: {
CHARGER_ECO_SMART_STATUS_KEY: False,
CHARGER_ECO_SMART_MODE_KEY: 0,
},
},
}
test_response_bidir = {
CHARGER_CHARGING_POWER_KEY: 0,
CHARGER_STATUS_ID_KEY: 193,
CHARGER_MAX_AVAILABLE_POWER_KEY: 25.0,
CHARGER_CHARGING_SPEED_KEY: 0,
CHARGER_ADDED_RANGE_KEY: 150,
CHARGER_ADDED_ENERGY_KEY: 44.697,
CHARGER_NAME_KEY: "WallboxName",
CHARGER_DATA_KEY: {
CHARGER_MAX_CHARGING_CURRENT_KEY: 24,
CHARGER_ENERGY_PRICE_KEY: 0.4,
CHARGER_LOCKED_UNLOCKED_KEY: False,
CHARGER_SERIAL_NUMBER_KEY: "20000",
CHARGER_PART_NUMBER_KEY: "QSP1-0-2-4-9-002-E",
CHARGER_SOFTWARE_KEY: {CHARGER_CURRENT_VERSION_KEY: "5.5.10"},
CHARGER_CURRENCY_KEY: {"code": "EUR/kWh"},
CHARGER_MAX_ICP_CURRENT_KEY: 20,
CHARGER_PLAN_KEY: {CHARGER_FEATURES_KEY: [CHARGER_POWER_BOOST_KEY]},
CHARGER_ECO_SMART_KEY: {
CHARGER_ECO_SMART_STATUS_KEY: False,
CHARGER_ECO_SMART_MODE_KEY: 0,
},
},
}
test_response_eco_mode = {
CHARGER_CHARGING_POWER_KEY: 0,
CHARGER_STATUS_ID_KEY: 193,
CHARGER_MAX_AVAILABLE_POWER_KEY: 25.0,
CHARGER_CHARGING_SPEED_KEY: 0,
CHARGER_ADDED_RANGE_KEY: 150,
CHARGER_ADDED_ENERGY_KEY: 44.697,
CHARGER_NAME_KEY: "WallboxName",
CHARGER_DATA_KEY: {
CHARGER_MAX_CHARGING_CURRENT_KEY: 24,
CHARGER_ENERGY_PRICE_KEY: 0.4,
CHARGER_LOCKED_UNLOCKED_KEY: False,
CHARGER_SERIAL_NUMBER_KEY: "20000",
CHARGER_PART_NUMBER_KEY: "PLP1-0-2-4-9-002-E",
CHARGER_SOFTWARE_KEY: {CHARGER_CURRENT_VERSION_KEY: "5.5.10"},
CHARGER_CURRENCY_KEY: {"code": "EUR/kWh"},
CHARGER_MAX_ICP_CURRENT_KEY: 20,
CHARGER_PLAN_KEY: {CHARGER_FEATURES_KEY: [CHARGER_POWER_BOOST_KEY]},
CHARGER_ECO_SMART_KEY: {
CHARGER_ECO_SMART_STATUS_KEY: True,
CHARGER_ECO_SMART_MODE_KEY: 0,
},
},
}
test_response_full_solar = {
CHARGER_CHARGING_POWER_KEY: 0,
CHARGER_STATUS_ID_KEY: 193,
CHARGER_MAX_AVAILABLE_POWER_KEY: 25.0,
CHARGER_CHARGING_SPEED_KEY: 0,
CHARGER_ADDED_RANGE_KEY: 150,
CHARGER_ADDED_ENERGY_KEY: 44.697,
CHARGER_NAME_KEY: "WallboxName",
CHARGER_DATA_KEY: {
CHARGER_MAX_CHARGING_CURRENT_KEY: 24,
CHARGER_ENERGY_PRICE_KEY: 0.4,
CHARGER_LOCKED_UNLOCKED_KEY: False,
CHARGER_SERIAL_NUMBER_KEY: "20000",
CHARGER_PART_NUMBER_KEY: "PLP1-0-2-4-9-002-E",
CHARGER_SOFTWARE_KEY: {CHARGER_CURRENT_VERSION_KEY: "5.5.10"},
CHARGER_CURRENCY_KEY: {"code": "EUR/kWh"},
CHARGER_MAX_ICP_CURRENT_KEY: 20,
CHARGER_PLAN_KEY: {CHARGER_FEATURES_KEY: [CHARGER_POWER_BOOST_KEY]},
CHARGER_ECO_SMART_KEY: {
CHARGER_ECO_SMART_STATUS_KEY: True,
CHARGER_ECO_SMART_MODE_KEY: 1,
},
},
}
test_response_no_power_boost = {
CHARGER_CHARGING_POWER_KEY: 0,
CHARGER_STATUS_ID_KEY: 193,
CHARGER_MAX_AVAILABLE_POWER_KEY: 25.0,
CHARGER_CHARGING_SPEED_KEY: 0,
CHARGER_ADDED_RANGE_KEY: 150,
CHARGER_ADDED_ENERGY_KEY: 44.697,
CHARGER_NAME_KEY: "WallboxName",
CHARGER_DATA_KEY: {
CHARGER_MAX_CHARGING_CURRENT_KEY: 24,
CHARGER_ENERGY_PRICE_KEY: 0.4,
CHARGER_LOCKED_UNLOCKED_KEY: False,
CHARGER_SERIAL_NUMBER_KEY: "20000",
CHARGER_PART_NUMBER_KEY: "PLP1-0-2-4-9-002-E",
CHARGER_SOFTWARE_KEY: {CHARGER_CURRENT_VERSION_KEY: "5.5.10"},
CHARGER_CURRENCY_KEY: {"code": "EUR/kWh"},
CHARGER_MAX_ICP_CURRENT_KEY: 20,
CHARGER_PLAN_KEY: {CHARGER_FEATURES_KEY: []},
},
}
http_403_error = requests.exceptions.HTTPError()
http_403_error.response = requests.Response()
http_403_error.response.status_code = HTTPStatus.FORBIDDEN
@ -176,45 +33,6 @@ http_429_error = requests.exceptions.HTTPError()
http_429_error.response = requests.Response()
http_429_error.response.status_code = HTTPStatus.TOO_MANY_REQUESTS
authorisation_response = {
"data": {
"attributes": {
"token": "fakekeyhere",
"refresh_token": "refresh_fakekeyhere",
USER_ID: 12345,
TTL: 145656758,
REFRESH_TOKEN_TTL: 145756758,
ERROR: "false",
STATUS: 200,
}
}
}
authorisation_response_unauthorised = {
"data": {
"attributes": {
"token": "fakekeyhere",
"refresh_token": "refresh_fakekeyhere",
USER_ID: 12345,
TTL: 145656758,
REFRESH_TOKEN_TTL: 145756758,
ERROR: "false",
STATUS: 404,
}
}
}
invalid_reauth_response = {
"jwt": "fakekeyhere",
"refresh_token": "refresh_fakekeyhere",
"user_id": 12345,
"ttl": 145656758,
"refresh_token_ttl": 145756758,
"error": False,
"status": 200,
}
@pytest.fixture
def entry(hass: HomeAssistant) -> MockConfigEntry:
@ -237,7 +55,7 @@ def mock_wallbox():
"""Patch Wallbox class for tests."""
with patch("homeassistant.components.wallbox.Wallbox") as mock:
wallbox = MagicMock()
wallbox.authenticate = Mock(return_value=authorisation_response)
wallbox.authenticate = Mock(return_value=WALLBOX_AUTHORISATION_RESPONSE)
wallbox.lockCharger = Mock(
return_value={
CHARGER_DATA_POST_L1_KEY: {
@ -263,7 +81,7 @@ def mock_wallbox():
}
)
wallbox.setIcpMaxCurrent = Mock(return_value={CHARGER_MAX_ICP_CURRENT_KEY: 25})
wallbox.getChargerStatus = Mock(return_value=test_response)
wallbox.getChargerStatus = Mock(return_value=WALLBOX_STATUS_RESPONSE)
mock.return_value = wallbox
yield wallbox

View File

@ -1,5 +1,31 @@
"""Provides constants for Wallbox component tests."""
from homeassistant.components.wallbox.const import (
CHARGER_ADDED_ENERGY_KEY,
CHARGER_ADDED_RANGE_KEY,
CHARGER_CHARGING_POWER_KEY,
CHARGER_CHARGING_SPEED_KEY,
CHARGER_CURRENCY_KEY,
CHARGER_CURRENT_VERSION_KEY,
CHARGER_DATA_KEY,
CHARGER_ECO_SMART_KEY,
CHARGER_ECO_SMART_MODE_KEY,
CHARGER_ECO_SMART_STATUS_KEY,
CHARGER_ENERGY_PRICE_KEY,
CHARGER_FEATURES_KEY,
CHARGER_LOCKED_UNLOCKED_KEY,
CHARGER_MAX_AVAILABLE_POWER_KEY,
CHARGER_MAX_CHARGING_CURRENT_KEY,
CHARGER_MAX_ICP_CURRENT_KEY,
CHARGER_NAME_KEY,
CHARGER_PART_NUMBER_KEY,
CHARGER_PLAN_KEY,
CHARGER_POWER_BOOST_KEY,
CHARGER_SERIAL_NUMBER_KEY,
CHARGER_SOFTWARE_KEY,
CHARGER_STATUS_ID_KEY,
)
JWT = "jwt"
USER_ID = "user_id"
TTL = "ttl"
@ -7,6 +33,169 @@ REFRESH_TOKEN_TTL = "refresh_token_ttl"
ERROR = "error"
STATUS = "status"
WALLBOX_STATUS_RESPONSE = {
CHARGER_CHARGING_POWER_KEY: 0,
CHARGER_STATUS_ID_KEY: 193,
CHARGER_MAX_AVAILABLE_POWER_KEY: 25.0,
CHARGER_CHARGING_SPEED_KEY: 0,
CHARGER_ADDED_RANGE_KEY: 150,
CHARGER_ADDED_ENERGY_KEY: 44.697,
CHARGER_NAME_KEY: "WallboxName",
CHARGER_DATA_KEY: {
CHARGER_MAX_CHARGING_CURRENT_KEY: 24,
CHARGER_ENERGY_PRICE_KEY: 0.4,
CHARGER_LOCKED_UNLOCKED_KEY: False,
CHARGER_SERIAL_NUMBER_KEY: "20000",
CHARGER_PART_NUMBER_KEY: "PLP1-0-2-4-9-002-E",
CHARGER_SOFTWARE_KEY: {CHARGER_CURRENT_VERSION_KEY: "5.5.10"},
CHARGER_CURRENCY_KEY: {"code": "EUR/kWh"},
CHARGER_MAX_ICP_CURRENT_KEY: 20,
CHARGER_PLAN_KEY: {CHARGER_FEATURES_KEY: [CHARGER_POWER_BOOST_KEY]},
CHARGER_ECO_SMART_KEY: {
CHARGER_ECO_SMART_STATUS_KEY: False,
CHARGER_ECO_SMART_MODE_KEY: 0,
},
},
}
WALLBOX_STATUS_RESPONSE_BIDIR = {
CHARGER_CHARGING_POWER_KEY: 0,
CHARGER_STATUS_ID_KEY: 193,
CHARGER_MAX_AVAILABLE_POWER_KEY: 25.0,
CHARGER_CHARGING_SPEED_KEY: 0,
CHARGER_ADDED_RANGE_KEY: 150,
CHARGER_ADDED_ENERGY_KEY: 44.697,
CHARGER_NAME_KEY: "WallboxName",
CHARGER_DATA_KEY: {
CHARGER_MAX_CHARGING_CURRENT_KEY: 24,
CHARGER_ENERGY_PRICE_KEY: 0.4,
CHARGER_LOCKED_UNLOCKED_KEY: False,
CHARGER_SERIAL_NUMBER_KEY: "20000",
CHARGER_PART_NUMBER_KEY: "QSP1-0-2-4-9-002-E",
CHARGER_SOFTWARE_KEY: {CHARGER_CURRENT_VERSION_KEY: "5.5.10"},
CHARGER_CURRENCY_KEY: {"code": "EUR/kWh"},
CHARGER_MAX_ICP_CURRENT_KEY: 20,
CHARGER_PLAN_KEY: {CHARGER_FEATURES_KEY: [CHARGER_POWER_BOOST_KEY]},
CHARGER_ECO_SMART_KEY: {
CHARGER_ECO_SMART_STATUS_KEY: False,
CHARGER_ECO_SMART_MODE_KEY: 0,
},
},
}
WALLBOX_STATUS_RESPONSE_ECO_MODE = {
CHARGER_CHARGING_POWER_KEY: 0,
CHARGER_STATUS_ID_KEY: 193,
CHARGER_MAX_AVAILABLE_POWER_KEY: 25.0,
CHARGER_CHARGING_SPEED_KEY: 0,
CHARGER_ADDED_RANGE_KEY: 150,
CHARGER_ADDED_ENERGY_KEY: 44.697,
CHARGER_NAME_KEY: "WallboxName",
CHARGER_DATA_KEY: {
CHARGER_MAX_CHARGING_CURRENT_KEY: 24,
CHARGER_ENERGY_PRICE_KEY: 0.4,
CHARGER_LOCKED_UNLOCKED_KEY: False,
CHARGER_SERIAL_NUMBER_KEY: "20000",
CHARGER_PART_NUMBER_KEY: "PLP1-0-2-4-9-002-E",
CHARGER_SOFTWARE_KEY: {CHARGER_CURRENT_VERSION_KEY: "5.5.10"},
CHARGER_CURRENCY_KEY: {"code": "EUR/kWh"},
CHARGER_MAX_ICP_CURRENT_KEY: 20,
CHARGER_PLAN_KEY: {CHARGER_FEATURES_KEY: [CHARGER_POWER_BOOST_KEY]},
CHARGER_ECO_SMART_KEY: {
CHARGER_ECO_SMART_STATUS_KEY: True,
CHARGER_ECO_SMART_MODE_KEY: 0,
},
},
}
WALLBOX_STATUS_RESPONSE_FULL_SOLAR = {
CHARGER_CHARGING_POWER_KEY: 0,
CHARGER_STATUS_ID_KEY: 193,
CHARGER_MAX_AVAILABLE_POWER_KEY: 25.0,
CHARGER_CHARGING_SPEED_KEY: 0,
CHARGER_ADDED_RANGE_KEY: 150,
CHARGER_ADDED_ENERGY_KEY: 44.697,
CHARGER_NAME_KEY: "WallboxName",
CHARGER_DATA_KEY: {
CHARGER_MAX_CHARGING_CURRENT_KEY: 24,
CHARGER_ENERGY_PRICE_KEY: 0.4,
CHARGER_LOCKED_UNLOCKED_KEY: False,
CHARGER_SERIAL_NUMBER_KEY: "20000",
CHARGER_PART_NUMBER_KEY: "PLP1-0-2-4-9-002-E",
CHARGER_SOFTWARE_KEY: {CHARGER_CURRENT_VERSION_KEY: "5.5.10"},
CHARGER_CURRENCY_KEY: {"code": "EUR/kWh"},
CHARGER_MAX_ICP_CURRENT_KEY: 20,
CHARGER_PLAN_KEY: {CHARGER_FEATURES_KEY: [CHARGER_POWER_BOOST_KEY]},
CHARGER_ECO_SMART_KEY: {
CHARGER_ECO_SMART_STATUS_KEY: True,
CHARGER_ECO_SMART_MODE_KEY: 1,
},
},
}
WALLBOX_STATUS_RESPONSE_NO_POWER_BOOST = {
CHARGER_CHARGING_POWER_KEY: 0,
CHARGER_STATUS_ID_KEY: 193,
CHARGER_MAX_AVAILABLE_POWER_KEY: 25.0,
CHARGER_CHARGING_SPEED_KEY: 0,
CHARGER_ADDED_RANGE_KEY: 150,
CHARGER_ADDED_ENERGY_KEY: 44.697,
CHARGER_NAME_KEY: "WallboxName",
CHARGER_DATA_KEY: {
CHARGER_MAX_CHARGING_CURRENT_KEY: 24,
CHARGER_ENERGY_PRICE_KEY: 0.4,
CHARGER_LOCKED_UNLOCKED_KEY: False,
CHARGER_SERIAL_NUMBER_KEY: "20000",
CHARGER_PART_NUMBER_KEY: "PLP1-0-2-4-9-002-E",
CHARGER_SOFTWARE_KEY: {CHARGER_CURRENT_VERSION_KEY: "5.5.10"},
CHARGER_CURRENCY_KEY: {"code": "EUR/kWh"},
CHARGER_MAX_ICP_CURRENT_KEY: 20,
CHARGER_PLAN_KEY: {CHARGER_FEATURES_KEY: []},
},
}
WALLBOX_AUTHORISATION_RESPONSE = {
"data": {
"attributes": {
"token": "fakekeyhere",
"refresh_token": "refresh_fakekeyhere",
USER_ID: 12345,
TTL: 145656758,
REFRESH_TOKEN_TTL: 145756758,
ERROR: "false",
STATUS: 200,
}
}
}
WALLBOX_AUTHORISATION_RESPONSE_UNAUTHORISED = {
"data": {
"attributes": {
"token": "fakekeyhere",
"refresh_token": "refresh_fakekeyhere",
USER_ID: 12345,
TTL: 145656758,
REFRESH_TOKEN_TTL: 145756758,
ERROR: "false",
STATUS: 404,
}
}
}
WALLBOX_INVALID_REAUTH_RESPONSE = {
"jwt": "fakekeyhere",
"refresh_token": "refresh_fakekeyhere",
"user_id": 12345,
"ttl": 145656758,
"refresh_token_ttl": 145756758,
"error": False,
"status": 200,
}
MOCK_NUMBER_ENTITY_ID = "number.wallbox_wallboxname_maximum_charging_current"
MOCK_NUMBER_ENTITY_ENERGY_PRICE_ID = "number.wallbox_wallboxname_energy_price"
MOCK_NUMBER_ENTITY_ICP_CURRENT_ID = "number.wallbox_wallboxname_maximum_icp_current"

View File

@ -3,7 +3,6 @@
from unittest.mock import Mock, patch
from homeassistant import config_entries
from homeassistant.components.wallbox import config_flow
from homeassistant.components.wallbox.const import (
CHARGER_ADDED_ENERGY_KEY,
CHARGER_ADDED_RANGE_KEY,
@ -18,12 +17,10 @@ from homeassistant.config_entries import ConfigEntryState
from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResultType
from .conftest import (
authorisation_response,
authorisation_response_unauthorised,
http_403_error,
http_404_error,
setup_integration,
from .conftest import http_403_error, http_404_error, setup_integration
from .const import (
WALLBOX_AUTHORISATION_RESPONSE,
WALLBOX_AUTHORISATION_RESPONSE_UNAUTHORISED,
)
from tests.common import MockConfigEntry
@ -40,10 +37,9 @@ test_response = {
async def test_show_set_form(hass: HomeAssistant, mock_wallbox) -> None:
"""Test that the setup form is served."""
flow = config_flow.WallboxConfigFlow()
flow.hass = hass
result = await flow.async_step_user(user_input=None)
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "user"
@ -112,7 +108,7 @@ async def test_form_validate_input(hass: HomeAssistant) -> None:
with (
patch(
"homeassistant.components.wallbox.Wallbox.authenticate",
return_value=authorisation_response,
return_value=WALLBOX_AUTHORISATION_RESPONSE,
),
patch(
"homeassistant.components.wallbox.Wallbox.pauseChargingSession",
@ -143,7 +139,7 @@ async def test_form_reauth(
patch.object(
mock_wallbox,
"authenticate",
return_value=authorisation_response_unauthorised,
return_value=WALLBOX_AUTHORISATION_RESPONSE_UNAUTHORISED,
),
patch.object(mock_wallbox, "getChargerStatus", return_value=test_response),
):
@ -176,7 +172,7 @@ async def test_form_reauth_invalid(
patch.object(
mock_wallbox,
"authenticate",
return_value=authorisation_response_unauthorised,
return_value=WALLBOX_AUTHORISATION_RESPONSE_UNAUTHORISED,
),
patch.object(mock_wallbox, "getChargerStatus", return_value=test_response),
):

View File

@ -1,19 +1,23 @@
"""Test Wallbox Init Component."""
from datetime import datetime, timedelta
from unittest.mock import patch
from homeassistant.components.wallbox.const import DOMAIN
from homeassistant.config_entries import ConfigEntryState
from homeassistant.core import HomeAssistant
import pytest
from .conftest import (
http_403_error,
http_429_error,
setup_integration,
test_response_no_power_boost,
from homeassistant.components.input_number import ATTR_VALUE, SERVICE_SET_VALUE
from homeassistant.config_entries import ConfigEntryState
from homeassistant.const import ATTR_ENTITY_ID
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from .conftest import http_403_error, http_429_error, setup_integration
from .const import (
MOCK_NUMBER_ENTITY_ENERGY_PRICE_ID,
WALLBOX_STATUS_RESPONSE_NO_POWER_BOOST,
)
from tests.common import MockConfigEntry
from tests.common import MockConfigEntry, async_fire_time_changed
async def test_wallbox_setup_unload_entry(
@ -40,24 +44,25 @@ async def test_wallbox_unload_entry_connection_error(
assert entry.state is ConfigEntryState.NOT_LOADED
async def test_wallbox_refresh_failed_connection_error_auth(
async def test_wallbox_refresh_failed_connection_error_too_many_requests(
hass: HomeAssistant, entry: MockConfigEntry, mock_wallbox
) -> None:
"""Test Wallbox setup with connection error."""
await setup_integration(hass, entry)
assert entry.state is ConfigEntryState.LOADED
with patch.object(mock_wallbox, "getChargerStatus", side_effect=http_429_error):
await setup_integration(hass, entry)
assert entry.state is ConfigEntryState.SETUP_RETRY
with patch.object(mock_wallbox, "authenticate", side_effect=http_429_error):
wallbox = hass.data[DOMAIN][entry.entry_id]
await wallbox.async_refresh()
await hass.async_block_till_done()
assert await hass.config_entries.async_unload(entry.entry_id)
assert entry.state is ConfigEntryState.NOT_LOADED
async def test_wallbox_refresh_failed_invalid_auth(
hass: HomeAssistant, entry: MockConfigEntry, mock_wallbox
async def test_wallbox_refresh_failed_error_auth(
hass: HomeAssistant,
entry: MockConfigEntry,
mock_wallbox,
) -> None:
"""Test Wallbox setup with authentication error."""
@ -66,11 +71,31 @@ async def test_wallbox_refresh_failed_invalid_auth(
with (
patch.object(mock_wallbox, "authenticate", side_effect=http_403_error),
patch.object(mock_wallbox, "pauseChargingSession", side_effect=http_403_error),
pytest.raises(HomeAssistantError),
):
wallbox = hass.data[DOMAIN][entry.entry_id]
await hass.services.async_call(
"number",
SERVICE_SET_VALUE,
{
ATTR_ENTITY_ID: MOCK_NUMBER_ENTITY_ENERGY_PRICE_ID,
ATTR_VALUE: 1.1,
},
blocking=True,
)
await wallbox.async_refresh()
with (
patch.object(mock_wallbox, "authenticate", side_effect=http_429_error),
pytest.raises(HomeAssistantError),
):
await hass.services.async_call(
"number",
SERVICE_SET_VALUE,
{
ATTR_ENTITY_ID: MOCK_NUMBER_ENTITY_ENERGY_PRICE_ID,
ATTR_VALUE: 1.1,
},
blocking=True,
)
assert await hass.config_entries.async_unload(entry.entry_id)
assert entry.state is ConfigEntryState.NOT_LOADED
@ -81,13 +106,10 @@ async def test_wallbox_refresh_failed_http_error(
) -> None:
"""Test Wallbox setup with authentication error."""
await setup_integration(hass, entry)
assert entry.state is ConfigEntryState.LOADED
with patch.object(mock_wallbox, "getChargerStatus", side_effect=http_403_error):
wallbox = hass.data[DOMAIN][entry.entry_id]
await wallbox.async_refresh()
await setup_integration(hass, entry)
assert entry.state is ConfigEntryState.SETUP_RETRY
await hass.async_block_till_done()
assert await hass.config_entries.async_unload(entry.entry_id)
assert entry.state is ConfigEntryState.NOT_LOADED
@ -102,9 +124,8 @@ async def test_wallbox_refresh_failed_too_many_requests(
assert entry.state is ConfigEntryState.LOADED
with patch.object(mock_wallbox, "getChargerStatus", side_effect=http_429_error):
wallbox = hass.data[DOMAIN][entry.entry_id]
await wallbox.async_refresh()
async_fire_time_changed(hass, datetime.now() + timedelta(seconds=120), True)
await hass.async_block_till_done()
assert await hass.config_entries.async_unload(entry.entry_id)
assert entry.state is ConfigEntryState.NOT_LOADED
@ -119,9 +140,8 @@ async def test_wallbox_refresh_failed_connection_error(
assert entry.state is ConfigEntryState.LOADED
with patch.object(mock_wallbox, "pauseChargingSession", side_effect=http_403_error):
wallbox = hass.data[DOMAIN][entry.entry_id]
await wallbox.async_refresh()
async_fire_time_changed(hass, datetime.now() + timedelta(seconds=120), True)
await hass.async_block_till_done()
assert await hass.config_entries.async_unload(entry.entry_id)
assert entry.state is ConfigEntryState.NOT_LOADED
@ -132,7 +152,9 @@ async def test_wallbox_setup_load_entry_no_eco_mode(
) -> None:
"""Test Wallbox Unload."""
with patch.object(
mock_wallbox, "getChargerStatus", return_value=test_response_no_power_boost
mock_wallbox,
"getChargerStatus",
return_value=WALLBOX_STATUS_RESPONSE_NO_POWER_BOOST,
):
await setup_integration(hass, entry)
assert entry.state is ConfigEntryState.LOADED

View File

@ -11,17 +11,12 @@ from homeassistant.const import ATTR_ENTITY_ID
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from .conftest import (
http_403_error,
http_404_error,
http_429_error,
setup_integration,
test_response_bidir,
)
from .conftest import http_403_error, http_404_error, http_429_error, setup_integration
from .const import (
MOCK_NUMBER_ENTITY_ENERGY_PRICE_ID,
MOCK_NUMBER_ENTITY_ICP_CURRENT_ID,
MOCK_NUMBER_ENTITY_ID,
WALLBOX_STATUS_RESPONSE_BIDIR,
)
from tests.common import MockConfigEntry
@ -53,7 +48,7 @@ async def test_wallbox_number_power_class_bidir(
) -> None:
"""Test wallbox sensor class."""
with patch.object(
mock_wallbox, "getChargerStatus", return_value=test_response_bidir
mock_wallbox, "getChargerStatus", return_value=WALLBOX_STATUS_RESPONSE_BIDIR
):
await setup_integration(hass, entry)

View File

@ -13,23 +13,21 @@ from homeassistant.components.wallbox.const import EcoSmartMode
from homeassistant.const import ATTR_ENTITY_ID
from homeassistant.core import HomeAssistant, HomeAssistantError
from .conftest import (
http_404_error,
http_429_error,
setup_integration,
test_response,
test_response_eco_mode,
test_response_full_solar,
test_response_no_power_boost,
from .conftest import http_404_error, http_429_error, setup_integration
from .const import (
MOCK_SELECT_ENTITY_ID,
WALLBOX_STATUS_RESPONSE,
WALLBOX_STATUS_RESPONSE_ECO_MODE,
WALLBOX_STATUS_RESPONSE_FULL_SOLAR,
WALLBOX_STATUS_RESPONSE_NO_POWER_BOOST,
)
from .const import MOCK_SELECT_ENTITY_ID
from tests.common import MockConfigEntry
TEST_OPTIONS = [
(EcoSmartMode.OFF, test_response),
(EcoSmartMode.ECO_MODE, test_response_eco_mode),
(EcoSmartMode.FULL_SOLAR, test_response_full_solar),
(EcoSmartMode.OFF, WALLBOX_STATUS_RESPONSE),
(EcoSmartMode.ECO_MODE, WALLBOX_STATUS_RESPONSE_ECO_MODE),
(EcoSmartMode.FULL_SOLAR, WALLBOX_STATUS_RESPONSE_FULL_SOLAR),
]
@ -61,7 +59,9 @@ async def test_wallbox_select_no_power_boost_class(
"""Test wallbox select class."""
with patch.object(
mock_wallbox, "getChargerStatus", return_value=test_response_no_power_boost
mock_wallbox,
"getChargerStatus",
return_value=WALLBOX_STATUS_RESPONSE_NO_POWER_BOOST,
):
await setup_integration(hass, entry)