Skip to content

Commit

Permalink
Add new number component for setting the wallbox ICP current (#125209)
Browse files Browse the repository at this point in the history
* Add new number component for setting the wallbox ICP current

* feat: Add number component for wallbox ICP current control
  • Loading branch information
hesselonline authored Sep 4, 2024
1 parent 4b11100 commit b5d7eba
Show file tree
Hide file tree
Showing 8 changed files with 171 additions and 1 deletion.
4 changes: 4 additions & 0 deletions homeassistant/components/wallbox/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,15 @@
CHARGER_DATA_KEY = "config_data"
CHARGER_DEPOT_PRICE_KEY = "depot_price"
CHARGER_ENERGY_PRICE_KEY = "energy_price"
CHARGER_FEATURES_KEY = "features"
CHARGER_SERIAL_NUMBER_KEY = "serial_number"
CHARGER_PART_NUMBER_KEY = "part_number"
CHARGER_PLAN_KEY = "plan"
CHARGER_POWER_BOOST_KEY = "POWER_BOOST"
CHARGER_SOFTWARE_KEY = "software"
CHARGER_MAX_AVAILABLE_POWER_KEY = "max_available_power"
CHARGER_MAX_CHARGING_CURRENT_KEY = "max_charging_current"
CHARGER_MAX_ICP_CURRENT_KEY = "icp_max_current"
CHARGER_PAUSE_RESUME_KEY = "paused"
CHARGER_LOCKED_UNLOCKED_KEY = "locked"
CHARGER_NAME_KEY = "name"
Expand Down
29 changes: 29 additions & 0 deletions homeassistant/components/wallbox/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,12 @@
CHARGER_CURRENCY_KEY,
CHARGER_DATA_KEY,
CHARGER_ENERGY_PRICE_KEY,
CHARGER_FEATURES_KEY,
CHARGER_LOCKED_UNLOCKED_KEY,
CHARGER_MAX_CHARGING_CURRENT_KEY,
CHARGER_MAX_ICP_CURRENT_KEY,
CHARGER_PLAN_KEY,
CHARGER_POWER_BOOST_KEY,
CHARGER_STATUS_DESCRIPTION_KEY,
CHARGER_STATUS_ID_KEY,
CODE_KEY,
Expand Down Expand Up @@ -130,6 +134,16 @@ def _get_data(self) -> dict[str, Any]:
data[CHARGER_ENERGY_PRICE_KEY] = data[CHARGER_DATA_KEY][
CHARGER_ENERGY_PRICE_KEY
]
# Only show max_icp_current if power_boost is available in the wallbox unit:
if (
data[CHARGER_DATA_KEY].get(CHARGER_MAX_ICP_CURRENT_KEY, 0) > 0
and CHARGER_POWER_BOOST_KEY
in data[CHARGER_DATA_KEY][CHARGER_PLAN_KEY][CHARGER_FEATURES_KEY]
):
data[CHARGER_MAX_ICP_CURRENT_KEY] = data[CHARGER_DATA_KEY][
CHARGER_MAX_ICP_CURRENT_KEY
]

data[CHARGER_CURRENCY_KEY] = (
f"{data[CHARGER_DATA_KEY][CHARGER_CURRENCY_KEY][CODE_KEY]}/kWh"
)
Expand Down Expand Up @@ -160,6 +174,21 @@ async def async_set_charging_current(self, charging_current: float) -> None:
)
await self.async_request_refresh()

@_require_authentication
def _set_icp_current(self, icp_current: float) -> None:
"""Set maximum icp current for Wallbox."""
try:
self._wallbox.setIcpMaxCurrent(self._station, icp_current)
except requests.exceptions.HTTPError as wallbox_connection_error:
if wallbox_connection_error.response.status_code == 403:
raise InvalidAuth from wallbox_connection_error
raise

async def async_set_icp_current(self, icp_current: float) -> None:
"""Set maximum icp current for Wallbox."""
await self.hass.async_add_executor_job(self._set_icp_current, icp_current)
await self.async_request_refresh()

@_require_authentication
def _set_energy_cost(self, energy_cost: float) -> None:
"""Set energy cost for Wallbox."""
Expand Down
11 changes: 11 additions & 0 deletions homeassistant/components/wallbox/number.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
CHARGER_ENERGY_PRICE_KEY,
CHARGER_MAX_AVAILABLE_POWER_KEY,
CHARGER_MAX_CHARGING_CURRENT_KEY,
CHARGER_MAX_ICP_CURRENT_KEY,
CHARGER_PART_NUMBER_KEY,
CHARGER_SERIAL_NUMBER_KEY,
DOMAIN,
Expand Down Expand Up @@ -67,6 +68,16 @@ class WallboxNumberEntityDescription(NumberEntityDescription):
set_value_fn=lambda coordinator: coordinator.async_set_energy_cost,
native_step=0.01,
),
CHARGER_MAX_ICP_CURRENT_KEY: WallboxNumberEntityDescription(
key=CHARGER_MAX_ICP_CURRENT_KEY,
translation_key="maximum_icp_current",
max_value_fn=lambda coordinator: cast(
float, coordinator.data[CHARGER_MAX_AVAILABLE_POWER_KEY]
),
min_value_fn=lambda _: 6,
set_value_fn=lambda coordinator: coordinator.async_set_icp_current,
native_step=1,
),
}


Expand Down
8 changes: 8 additions & 0 deletions homeassistant/components/wallbox/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
CHARGER_ENERGY_PRICE_KEY,
CHARGER_MAX_AVAILABLE_POWER_KEY,
CHARGER_MAX_CHARGING_CURRENT_KEY,
CHARGER_MAX_ICP_CURRENT_KEY,
CHARGER_SERIAL_NUMBER_KEY,
CHARGER_STATE_OF_CHARGE_KEY,
CHARGER_STATUS_DESCRIPTION_KEY,
Expand Down Expand Up @@ -145,6 +146,13 @@ class WallboxSensorEntityDescription(SensorEntityDescription):
device_class=SensorDeviceClass.CURRENT,
state_class=SensorStateClass.MEASUREMENT,
),
CHARGER_MAX_ICP_CURRENT_KEY: WallboxSensorEntityDescription(
key=CHARGER_MAX_ICP_CURRENT_KEY,
translation_key=CHARGER_MAX_ICP_CURRENT_KEY,
native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,
device_class=SensorDeviceClass.CURRENT,
state_class=SensorStateClass.MEASUREMENT,
),
}


Expand Down
6 changes: 6 additions & 0 deletions homeassistant/components/wallbox/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@
},
"energy_price": {
"name": "Energy price"
},
"maximum_icp_current": {
"name": "Maximum ICP current"
}
},
"sensor": {
Expand Down Expand Up @@ -79,6 +82,9 @@
},
"max_charging_current": {
"name": "Max charging current"
},
"icp_max_current": {
"name": "Max ICP current"
}
},
"switch": {
Expand Down
8 changes: 8 additions & 0 deletions tests/components/wallbox/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,15 @@
CHARGER_CURRENT_VERSION_KEY,
CHARGER_DATA_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,
Expand All @@ -45,6 +49,8 @@
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]},
},
}

Expand All @@ -64,6 +70,8 @@
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]},
},
}

Expand Down
1 change: 1 addition & 0 deletions tests/components/wallbox/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

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"
MOCK_LOCK_ENTITY_ID = "lock.wallbox_wallboxname_lock"
MOCK_SENSOR_CHARGING_SPEED_ID = "sensor.wallbox_wallboxname_charging_speed"
MOCK_SENSOR_CHARGING_POWER_ID = "sensor.wallbox_wallboxname_charging_power"
Expand Down
105 changes: 104 additions & 1 deletion tests/components/wallbox/test_number.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,12 @@
import requests_mock

from homeassistant.components.input_number import ATTR_VALUE, SERVICE_SET_VALUE
from homeassistant.components.number import DOMAIN as NUMBER_DOMAIN
from homeassistant.components.wallbox import InvalidAuth
from homeassistant.components.wallbox.const import (
CHARGER_ENERGY_PRICE_KEY,
CHARGER_MAX_CHARGING_CURRENT_KEY,
CHARGER_MAX_ICP_CURRENT_KEY,
)
from homeassistant.const import ATTR_ENTITY_ID
from homeassistant.core import HomeAssistant
Expand All @@ -20,7 +23,11 @@
setup_integration_bidir,
setup_integration_platform_not_ready,
)
from .const import MOCK_NUMBER_ENTITY_ENERGY_PRICE_ID, MOCK_NUMBER_ENTITY_ID
from .const import (
MOCK_NUMBER_ENTITY_ENERGY_PRICE_ID,
MOCK_NUMBER_ENTITY_ICP_CURRENT_ID,
MOCK_NUMBER_ENTITY_ID,
)

from tests.common import MockConfigEntry

Expand Down Expand Up @@ -212,3 +219,99 @@ async def test_wallbox_number_class_platform_not_ready(
assert state is None

await hass.config_entries.async_unload(entry.entry_id)


async def test_wallbox_number_class_icp_energy(
hass: HomeAssistant, entry: MockConfigEntry
) -> None:
"""Test wallbox sensor class."""

await setup_integration(hass, entry)

with requests_mock.Mocker() as mock_request:
mock_request.get(
"https://user-api.wall-box.com/users/signin",
json=authorisation_response,
status_code=200,
)

mock_request.post(
"https://api.wall-box.com/chargers/config/12345",
json={CHARGER_MAX_ICP_CURRENT_KEY: 10},
status_code=200,
)

await hass.services.async_call(
NUMBER_DOMAIN,
SERVICE_SET_VALUE,
{
ATTR_ENTITY_ID: MOCK_NUMBER_ENTITY_ICP_CURRENT_ID,
ATTR_VALUE: 10,
},
blocking=True,
)
await hass.config_entries.async_unload(entry.entry_id)


async def test_wallbox_number_class_icp_energy_auth_error(
hass: HomeAssistant, entry: MockConfigEntry
) -> None:
"""Test wallbox sensor class."""

await setup_integration(hass, entry)

with requests_mock.Mocker() as mock_request:
mock_request.get(
"https://user-api.wall-box.com/users/signin",
json=authorisation_response,
status_code=200,
)
mock_request.post(
"https://api.wall-box.com/chargers/config/12345",
json={CHARGER_MAX_ICP_CURRENT_KEY: 10},
status_code=403,
)

with pytest.raises(InvalidAuth):
await hass.services.async_call(
NUMBER_DOMAIN,
SERVICE_SET_VALUE,
{
ATTR_ENTITY_ID: MOCK_NUMBER_ENTITY_ICP_CURRENT_ID,
ATTR_VALUE: 10,
},
blocking=True,
)
await hass.config_entries.async_unload(entry.entry_id)


async def test_wallbox_number_class_icp_energy_connection_error(
hass: HomeAssistant, entry: MockConfigEntry
) -> None:
"""Test wallbox sensor class."""

await setup_integration(hass, entry)

with requests_mock.Mocker() as mock_request:
mock_request.get(
"https://user-api.wall-box.com/users/signin",
json=authorisation_response,
status_code=200,
)
mock_request.post(
"https://api.wall-box.com/chargers/config/12345",
json={CHARGER_MAX_ICP_CURRENT_KEY: 10},
status_code=404,
)

with pytest.raises(ConnectionError):
await hass.services.async_call(
NUMBER_DOMAIN,
SERVICE_SET_VALUE,
{
ATTR_ENTITY_ID: MOCK_NUMBER_ENTITY_ICP_CURRENT_ID,
ATTR_VALUE: 10,
},
blocking=True,
)
await hass.config_entries.async_unload(entry.entry_id)

0 comments on commit b5d7eba

Please sign in to comment.