From 6d6cb03848f45117e4a8e3b6112a51a94f0cfe3e Mon Sep 17 00:00:00 2001
From: hesselonline <hesselonline@users.noreply.github.com>
Date: Wed, 27 Oct 2021 19:53:14 +0200
Subject: [PATCH] Add Number platform to Wallbox (#52786)

Co-authored-by: jan iversen <jancasacondor@gmail.com>
---
 homeassistant/components/wallbox/__init__.py  |  96 +++++++++-----
 .../components/wallbox/config_flow.py         |   8 +-
 homeassistant/components/wallbox/const.py     |  78 +++++++----
 homeassistant/components/wallbox/number.py    |  56 ++++++++
 homeassistant/components/wallbox/sensor.py    |  30 ++---
 .../components/wallbox/translations/en.json   |   3 +-
 .../components/wallbox/translations/nl.json   |  10 +-
 tests/components/wallbox/__init__.py          | 124 +++++++++++++++--
 tests/components/wallbox/const.py             |  10 ++
 tests/components/wallbox/test_config_flow.py  |  73 ++++++++--
 tests/components/wallbox/test_init.py         | 125 +++++++++++++++---
 tests/components/wallbox/test_number.py       |  91 +++++++++++++
 tests/components/wallbox/test_sensor.py       |  30 ++---
 13 files changed, 584 insertions(+), 150 deletions(-)
 create mode 100644 homeassistant/components/wallbox/number.py
 create mode 100644 tests/components/wallbox/const.py
 create mode 100644 tests/components/wallbox/test_number.py

diff --git a/homeassistant/components/wallbox/__init__.py b/homeassistant/components/wallbox/__init__.py
index 96ae5210d4c..e5c8b7719a3 100644
--- a/homeassistant/components/wallbox/__init__.py
+++ b/homeassistant/components/wallbox/__init__.py
@@ -6,37 +6,40 @@ import logging
 import requests
 from wallbox import Wallbox
 
-from homeassistant import exceptions
 from homeassistant.config_entries import ConfigEntry
 from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
 from homeassistant.core import HomeAssistant
+from homeassistant.exceptions import HomeAssistantError
 from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
 
-from .const import CONF_CONNECTIONS, CONF_ROUND, CONF_SENSOR_TYPES, CONF_STATION, DOMAIN
+from .const import (
+    CONF_CONNECTIONS,
+    CONF_DATA_KEY,
+    CONF_MAX_CHARGING_CURRENT_KEY,
+    CONF_ROUND,
+    CONF_SENSOR_TYPES,
+    CONF_STATION,
+    DOMAIN,
+)
 
 _LOGGER = logging.getLogger(__name__)
 
-PLATFORMS = ["sensor"]
+PLATFORMS = ["sensor", "number"]
 UPDATE_INTERVAL = 30
 
 
-class WallboxHub:
-    """Wallbox Hub class."""
+class WallboxCoordinator(DataUpdateCoordinator):
+    """Wallbox Coordinator class."""
 
-    def __init__(self, station, username, password, hass):
+    def __init__(self, station, wallbox, hass):
         """Initialize."""
         self._station = station
-        self._username = username
-        self._password = password
-        self._wallbox = Wallbox(self._username, self._password)
-        self._hass = hass
-        self._coordinator = DataUpdateCoordinator(
+        self._wallbox = wallbox
+
+        super().__init__(
             hass,
             _LOGGER,
-            # Name of the data. For logging purposes.
-            name="wallbox",
-            update_method=self.async_get_data,
-            # Polling interval. Will only be polled if there are subscribers.
+            name=DOMAIN,
             update_interval=timedelta(seconds=UPDATE_INTERVAL),
         )
 
@@ -50,11 +53,24 @@ class WallboxHub:
                 raise InvalidAuth from wallbox_connection_error
             raise ConnectionError from wallbox_connection_error
 
+    def _validate(self):
+        """Authenticate using Wallbox API."""
+        try:
+            self._wallbox.authenticate()
+            return True
+        except requests.exceptions.HTTPError as wallbox_connection_error:
+            if wallbox_connection_error.response.status_code == 403:
+                raise InvalidAuth from wallbox_connection_error
+            raise ConnectionError from wallbox_connection_error
+
     def _get_data(self):
         """Get new sensor data for Wallbox component."""
         try:
             self._authenticate()
             data = self._wallbox.getChargerStatus(self._station)
+            data[CONF_MAX_CHARGING_CURRENT_KEY] = data[CONF_DATA_KEY][
+                CONF_MAX_CHARGING_CURRENT_KEY
+            ]
 
             filtered_data = {k: data[k] for k in CONF_SENSOR_TYPES if k in data}
 
@@ -69,42 +85,52 @@ class WallboxHub:
         except requests.exceptions.HTTPError as wallbox_connection_error:
             raise ConnectionError from wallbox_connection_error
 
-    async def async_coordinator_first_refresh(self):
-        """Refresh coordinator for the first time."""
-        await self._coordinator.async_config_entry_first_refresh()
+    def _set_charging_current(self, charging_current):
+        """Set maximum charging current for Wallbox."""
+        try:
+            self._authenticate()
+            self._wallbox.setMaxChargingCurrent(self._station, charging_current)
+        except requests.exceptions.HTTPError as wallbox_connection_error:
+            if wallbox_connection_error.response.status_code == 403:
+                raise InvalidAuth from wallbox_connection_error
+            raise ConnectionError from wallbox_connection_error
 
-    async def async_authenticate(self) -> bool:
-        """Authenticate using Wallbox API."""
-        return await self._hass.async_add_executor_job(self._authenticate)
+    async def async_set_charging_current(self, charging_current):
+        """Set maximum charging current for Wallbox."""
+        await self.hass.async_add_executor_job(
+            self._set_charging_current, charging_current
+        )
+        await self.async_request_refresh()
 
-    async def async_get_data(self) -> bool:
+    async def _async_update_data(self) -> bool:
         """Get new sensor data for Wallbox component."""
-        data = await self._hass.async_add_executor_job(self._get_data)
+        data = await self.hass.async_add_executor_job(self._get_data)
         return data
 
-    @property
-    def coordinator(self):
-        """Return the coordinator."""
-        return self._coordinator
+    async def async_validate_input(self) -> bool:
+        """Get new sensor data for Wallbox component."""
+        data = await self.hass.async_add_executor_job(self._validate)
+        return data
 
 
 async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
     """Set up Wallbox from a config entry."""
-    wallbox = WallboxHub(
+    wallbox = Wallbox(entry.data[CONF_USERNAME], entry.data[CONF_PASSWORD])
+    wallbox_coordinator = WallboxCoordinator(
         entry.data[CONF_STATION],
-        entry.data[CONF_USERNAME],
-        entry.data[CONF_PASSWORD],
+        wallbox,
         hass,
     )
 
-    await wallbox.async_authenticate()
+    await wallbox_coordinator.async_validate_input()
 
-    await wallbox.async_coordinator_first_refresh()
+    await wallbox_coordinator.async_config_entry_first_refresh()
 
     hass.data.setdefault(DOMAIN, {CONF_CONNECTIONS: {}})
-    hass.data[DOMAIN][CONF_CONNECTIONS][entry.entry_id] = wallbox
+    hass.data[DOMAIN][CONF_CONNECTIONS][entry.entry_id] = wallbox_coordinator
 
     for platform in PLATFORMS:
+
         hass.async_create_task(
             hass.config_entries.async_forward_entry_setup(entry, platform)
         )
@@ -116,10 +142,10 @@ 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]["connections"].pop(entry.entry_id)
+        hass.data[DOMAIN][CONF_CONNECTIONS].pop(entry.entry_id)
 
     return unload_ok
 
 
-class InvalidAuth(exceptions.HomeAssistantError):
+class InvalidAuth(HomeAssistantError):
     """Error to indicate there is invalid auth."""
diff --git a/homeassistant/components/wallbox/config_flow.py b/homeassistant/components/wallbox/config_flow.py
index f9fdef3c5af..f123ad0cd2d 100644
--- a/homeassistant/components/wallbox/config_flow.py
+++ b/homeassistant/components/wallbox/config_flow.py
@@ -1,10 +1,11 @@
 """Config flow for Wallbox integration."""
 import voluptuous as vol
+from wallbox import Wallbox
 
 from homeassistant import config_entries, core
 from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
 
-from . import InvalidAuth, WallboxHub
+from . import InvalidAuth, WallboxCoordinator
 from .const import CONF_STATION, DOMAIN
 
 COMPONENT_DOMAIN = DOMAIN
@@ -23,9 +24,10 @@ async def validate_input(hass: core.HomeAssistant, data):
 
     Data has the keys from STEP_USER_DATA_SCHEMA with values provided by the user.
     """
-    hub = WallboxHub(data["station"], data["username"], data["password"], hass)
+    wallbox = Wallbox(data["username"], data["password"])
+    wallbox_coordinator = WallboxCoordinator(data["station"], wallbox, hass)
 
-    await hub.async_get_data()
+    await wallbox_coordinator.async_validate_input()
 
     # Return info that you want to store in the config entry.
     return {"title": "Wallbox Portal"}
diff --git a/homeassistant/components/wallbox/const.py b/homeassistant/components/wallbox/const.py
index c8044f6990a..62c9b2f6efd 100644
--- a/homeassistant/components/wallbox/const.py
+++ b/homeassistant/components/wallbox/const.py
@@ -1,99 +1,123 @@
 """Constants for the Wallbox integration."""
 from homeassistant.const import (
+    CONF_DEVICE_CLASS,
     CONF_ICON,
     CONF_NAME,
     CONF_UNIT_OF_MEASUREMENT,
+    DEVICE_CLASS_BATTERY,
+    DEVICE_CLASS_CURRENT,
+    DEVICE_CLASS_ENERGY,
+    DEVICE_CLASS_POWER,
     ELECTRIC_CURRENT_AMPERE,
     ENERGY_KILO_WATT_HOUR,
     LENGTH_KILOMETERS,
     PERCENTAGE,
     POWER_KILO_WATT,
-    STATE_UNAVAILABLE,
 )
 
 DOMAIN = "wallbox"
 
 CONF_STATION = "station"
+CONF_ADDED_ENERGY_KEY = "added_energy"
+CONF_ADDED_RANGE_KEY = "added_range"
+CONF_CHARGING_POWER_KEY = "charging_power"
+CONF_CHARGING_SPEED_KEY = "charging_speed"
+CONF_CHARGING_TIME_KEY = "charging_time"
+CONF_COST_KEY = "cost"
+CONF_CURRENT_MODE_KEY = "current_mode"
+CONF_DATA_KEY = "config_data"
+CONF_DEPOT_PRICE_KEY = "depot_price"
+CONF_MAX_AVAILABLE_POWER_KEY = "max_available_power"
+CONF_MAX_CHARGING_CURRENT_KEY = "max_charging_current"
+CONF_STATE_OF_CHARGE_KEY = "state_of_charge"
+CONF_STATUS_DESCRIPTION_KEY = "status_description"
 
 CONF_CONNECTIONS = "connections"
 CONF_ROUND = "round"
 
 CONF_SENSOR_TYPES = {
-    "charging_power": {
-        CONF_ICON: "mdi:ev-station",
+    CONF_CHARGING_POWER_KEY: {
+        CONF_ICON: None,
         CONF_NAME: "Charging Power",
         CONF_ROUND: 2,
         CONF_UNIT_OF_MEASUREMENT: POWER_KILO_WATT,
-        STATE_UNAVAILABLE: False,
+        CONF_DEVICE_CLASS: DEVICE_CLASS_POWER,
     },
-    "max_available_power": {
-        CONF_ICON: "mdi:ev-station",
+    CONF_MAX_AVAILABLE_POWER_KEY: {
+        CONF_ICON: None,
         CONF_NAME: "Max Available Power",
         CONF_ROUND: 0,
         CONF_UNIT_OF_MEASUREMENT: ELECTRIC_CURRENT_AMPERE,
-        STATE_UNAVAILABLE: False,
+        CONF_DEVICE_CLASS: DEVICE_CLASS_CURRENT,
     },
-    "charging_speed": {
+    CONF_CHARGING_SPEED_KEY: {
         CONF_ICON: "mdi:speedometer",
         CONF_NAME: "Charging Speed",
         CONF_ROUND: 0,
         CONF_UNIT_OF_MEASUREMENT: None,
-        STATE_UNAVAILABLE: False,
+        CONF_DEVICE_CLASS: None,
     },
-    "added_range": {
+    CONF_ADDED_RANGE_KEY: {
         CONF_ICON: "mdi:map-marker-distance",
         CONF_NAME: "Added Range",
         CONF_ROUND: 0,
         CONF_UNIT_OF_MEASUREMENT: LENGTH_KILOMETERS,
-        STATE_UNAVAILABLE: False,
+        CONF_DEVICE_CLASS: None,
     },
-    "added_energy": {
-        CONF_ICON: "mdi:battery-positive",
+    CONF_ADDED_ENERGY_KEY: {
+        CONF_ICON: None,
         CONF_NAME: "Added Energy",
         CONF_ROUND: 2,
         CONF_UNIT_OF_MEASUREMENT: ENERGY_KILO_WATT_HOUR,
-        STATE_UNAVAILABLE: False,
+        CONF_DEVICE_CLASS: DEVICE_CLASS_ENERGY,
     },
-    "charging_time": {
+    CONF_CHARGING_TIME_KEY: {
         CONF_ICON: "mdi:timer",
         CONF_NAME: "Charging Time",
         CONF_ROUND: None,
         CONF_UNIT_OF_MEASUREMENT: None,
-        STATE_UNAVAILABLE: False,
+        CONF_DEVICE_CLASS: None,
     },
-    "cost": {
+    CONF_COST_KEY: {
         CONF_ICON: "mdi:ev-station",
         CONF_NAME: "Cost",
         CONF_ROUND: None,
         CONF_UNIT_OF_MEASUREMENT: None,
-        STATE_UNAVAILABLE: False,
+        CONF_DEVICE_CLASS: None,
     },
-    "state_of_charge": {
-        CONF_ICON: "mdi:battery-charging-80",
+    CONF_STATE_OF_CHARGE_KEY: {
+        CONF_ICON: None,
         CONF_NAME: "State of Charge",
         CONF_ROUND: None,
         CONF_UNIT_OF_MEASUREMENT: PERCENTAGE,
-        STATE_UNAVAILABLE: False,
+        CONF_DEVICE_CLASS: DEVICE_CLASS_BATTERY,
     },
-    "current_mode": {
+    CONF_CURRENT_MODE_KEY: {
         CONF_ICON: "mdi:ev-station",
         CONF_NAME: "Current Mode",
         CONF_ROUND: None,
         CONF_UNIT_OF_MEASUREMENT: None,
-        STATE_UNAVAILABLE: False,
+        CONF_DEVICE_CLASS: None,
     },
-    "depot_price": {
+    CONF_DEPOT_PRICE_KEY: {
         CONF_ICON: "mdi:ev-station",
         CONF_NAME: "Depot Price",
         CONF_ROUND: 2,
         CONF_UNIT_OF_MEASUREMENT: None,
-        STATE_UNAVAILABLE: False,
+        CONF_DEVICE_CLASS: None,
     },
-    "status_description": {
+    CONF_STATUS_DESCRIPTION_KEY: {
         CONF_ICON: "mdi:ev-station",
         CONF_NAME: "Status Description",
         CONF_ROUND: None,
         CONF_UNIT_OF_MEASUREMENT: None,
-        STATE_UNAVAILABLE: False,
+        CONF_DEVICE_CLASS: None,
+    },
+    CONF_MAX_CHARGING_CURRENT_KEY: {
+        CONF_ICON: None,
+        CONF_NAME: "Max. Charging Current",
+        CONF_ROUND: None,
+        CONF_UNIT_OF_MEASUREMENT: ELECTRIC_CURRENT_AMPERE,
+        CONF_DEVICE_CLASS: DEVICE_CLASS_CURRENT,
     },
 }
diff --git a/homeassistant/components/wallbox/number.py b/homeassistant/components/wallbox/number.py
new file mode 100644
index 00000000000..d99d6511822
--- /dev/null
+++ b/homeassistant/components/wallbox/number.py
@@ -0,0 +1,56 @@
+"""Home Assistant component for accessing the Wallbox Portal API. The sensor component creates multiple sensors regarding wallbox performance."""
+
+from homeassistant.components.number import NumberEntity
+from homeassistant.const import CONF_DEVICE_CLASS
+from homeassistant.helpers.update_coordinator import CoordinatorEntity
+
+from . import InvalidAuth
+from .const import (
+    CONF_CONNECTIONS,
+    CONF_MAX_AVAILABLE_POWER_KEY,
+    CONF_MAX_CHARGING_CURRENT_KEY,
+    CONF_NAME,
+    CONF_SENSOR_TYPES,
+    DOMAIN,
+)
+
+
+async def async_setup_entry(hass, config, async_add_entities):
+    """Create wallbox sensor entities in HASS."""
+    coordinator = hass.data[DOMAIN][CONF_CONNECTIONS][config.entry_id]
+    # Check if the user is authorized to change current, if so, add number component:
+    try:
+        await coordinator.async_set_charging_current(
+            coordinator.data[CONF_MAX_CHARGING_CURRENT_KEY]
+        )
+    except InvalidAuth:
+        pass
+    else:
+        async_add_entities([WallboxNumber(coordinator, config)])
+
+
+class WallboxNumber(CoordinatorEntity, NumberEntity):
+    """Representation of the Wallbox portal."""
+
+    def __init__(self, coordinator, config):
+        """Initialize a Wallbox sensor."""
+        super().__init__(coordinator)
+        _properties = CONF_SENSOR_TYPES[CONF_MAX_CHARGING_CURRENT_KEY]
+        self._coordinator = coordinator
+        self._attr_name = f"{config.title} {_properties[CONF_NAME]}"
+        self._attr_min_value = 6
+        self._attr_device_class = _properties[CONF_DEVICE_CLASS]
+
+    @property
+    def max_value(self):
+        """Return the maximum available current."""
+        return self._coordinator.data[CONF_MAX_AVAILABLE_POWER_KEY]
+
+    @property
+    def value(self):
+        """Return the state of the sensor."""
+        return self._coordinator.data[CONF_MAX_CHARGING_CURRENT_KEY]
+
+    async def async_set_value(self, value: float):
+        """Set the value of the entity."""
+        await self._coordinator.async_set_charging_current(value)
diff --git a/homeassistant/components/wallbox/sensor.py b/homeassistant/components/wallbox/sensor.py
index 0691a39ff48..37450a5ea79 100644
--- a/homeassistant/components/wallbox/sensor.py
+++ b/homeassistant/components/wallbox/sensor.py
@@ -1,6 +1,7 @@
 """Home Assistant component for accessing the Wallbox Portal API. The sensor component creates multiple sensors regarding wallbox performance."""
 
 from homeassistant.components.sensor import SensorEntity
+from homeassistant.const import CONF_DEVICE_CLASS
 from homeassistant.helpers.update_coordinator import CoordinatorEntity
 
 from .const import (
@@ -18,9 +19,7 @@ UPDATE_INTERVAL = 30
 
 async def async_setup_entry(hass, config, async_add_entities):
     """Create wallbox sensor entities in HASS."""
-    wallbox = hass.data[DOMAIN][CONF_CONNECTIONS][config.entry_id]
-
-    coordinator = wallbox.coordinator
+    coordinator = hass.data[DOMAIN][CONF_CONNECTIONS][config.entry_id]
 
     async_add_entities(
         WallboxSensor(coordinator, idx, ent, config)
@@ -34,28 +33,15 @@ class WallboxSensor(CoordinatorEntity, SensorEntity):
     def __init__(self, coordinator, idx, ent, config):
         """Initialize a Wallbox sensor."""
         super().__init__(coordinator)
-        self._properties = CONF_SENSOR_TYPES[ent]
-        self._name = f"{config.title} {self._properties[CONF_NAME]}"
-        self._icon = self._properties[CONF_ICON]
-        self._unit = self._properties[CONF_UNIT_OF_MEASUREMENT]
+        self._attr_name = f"{config.title} {CONF_SENSOR_TYPES[ent][CONF_NAME]}"
+        self._attr_icon = CONF_SENSOR_TYPES[ent][CONF_ICON]
+        self._attr_native_unit_of_measurement = CONF_SENSOR_TYPES[ent][
+            CONF_UNIT_OF_MEASUREMENT
+        ]
+        self._attr_device_class = CONF_SENSOR_TYPES[ent][CONF_DEVICE_CLASS]
         self._ent = ent
 
-    @property
-    def name(self):
-        """Return the name of the sensor."""
-        return self._name
-
     @property
     def native_value(self):
         """Return the state of the sensor."""
         return self.coordinator.data[self._ent]
-
-    @property
-    def native_unit_of_measurement(self):
-        """Return the unit of the sensor."""
-        return self._unit
-
-    @property
-    def icon(self):
-        """Return the icon of the sensor."""
-        return self._icon
diff --git a/homeassistant/components/wallbox/translations/en.json b/homeassistant/components/wallbox/translations/en.json
index 52dcf8530d4..3d75e0bc276 100644
--- a/homeassistant/components/wallbox/translations/en.json
+++ b/homeassistant/components/wallbox/translations/en.json
@@ -17,6 +17,5 @@
                 }
             }
         }
-    },
-    "title": "Wallbox"
+    }
 }
\ No newline at end of file
diff --git a/homeassistant/components/wallbox/translations/nl.json b/homeassistant/components/wallbox/translations/nl.json
index 6ba03e7ee99..dd406ea3b90 100644
--- a/homeassistant/components/wallbox/translations/nl.json
+++ b/homeassistant/components/wallbox/translations/nl.json
@@ -1,7 +1,8 @@
 {
     "config": {
         "abort": {
-            "already_configured": "Apparaat is al geconfigureerd"
+            "already_configured": "Apparaat is al geconfigureerd",
+            "reauth_successful": "Herauthenticatie was succesvol"
         },
         "error": {
             "cannot_connect": "Kan geen verbinding maken",
@@ -15,6 +16,13 @@
                     "station": "Station Serienummer",
                     "username": "Gebruikersnaam"
                 }
+            },
+            "reauth_confirm": {
+                "data": {
+                    "password": "Wachtwoord",
+                    "station": "Station Serienummer",
+                    "username": "Gebruikersnaam"
+                }
             }
         }
     },
diff --git a/tests/components/wallbox/__init__.py b/tests/components/wallbox/__init__.py
index c7d83665d94..4a403d0afc8 100644
--- a/tests/components/wallbox/__init__.py
+++ b/tests/components/wallbox/__init__.py
@@ -5,35 +5,73 @@ import json
 
 import requests_mock
 
-from homeassistant.components.wallbox.const import CONF_STATION, DOMAIN
+from homeassistant.components.wallbox.const import (
+    CONF_ADDED_ENERGY_KEY,
+    CONF_ADDED_RANGE_KEY,
+    CONF_CHARGING_POWER_KEY,
+    CONF_CHARGING_SPEED_KEY,
+    CONF_DATA_KEY,
+    CONF_MAX_AVAILABLE_POWER_KEY,
+    CONF_MAX_CHARGING_CURRENT_KEY,
+    CONF_STATION,
+    DOMAIN,
+)
 from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
 
 from tests.common import MockConfigEntry
+from tests.components.wallbox.const import (
+    CONF_ERROR,
+    CONF_JWT,
+    CONF_STATUS,
+    CONF_TTL,
+    CONF_USER_ID,
+)
 
 test_response = json.loads(
-    '{"charging_power": 0,"max_available_power": "xx","charging_speed": 0,"added_range": "xx","added_energy": "44.697"}'
+    json.dumps(
+        {
+            CONF_CHARGING_POWER_KEY: 0,
+            CONF_MAX_AVAILABLE_POWER_KEY: 25,
+            CONF_CHARGING_SPEED_KEY: 0,
+            CONF_ADDED_RANGE_KEY: "xx",
+            CONF_ADDED_ENERGY_KEY: "44.697",
+            CONF_DATA_KEY: {CONF_MAX_CHARGING_CURRENT_KEY: 24},
+        }
+    )
+)
+
+authorisation_response = json.loads(
+    json.dumps(
+        {
+            CONF_JWT: "fakekeyhere",
+            CONF_USER_ID: 12345,
+            CONF_TTL: 145656758,
+            CONF_ERROR: "false",
+            CONF_STATUS: 200,
+        }
+    )
+)
+
+entry = MockConfigEntry(
+    domain=DOMAIN,
+    data={
+        CONF_USERNAME: "test_username",
+        CONF_PASSWORD: "test_password",
+        CONF_STATION: "12345",
+    },
+    entry_id="testEntry",
 )
 
 
 async def setup_integration(hass):
     """Test wallbox sensor class setup."""
 
-    entry = MockConfigEntry(
-        domain=DOMAIN,
-        data={
-            CONF_USERNAME: "test_username",
-            CONF_PASSWORD: "test_password",
-            CONF_STATION: "12345",
-        },
-        entry_id="testEntry",
-    )
-
     entry.add_to_hass(hass)
 
     with requests_mock.Mocker() as mock_request:
         mock_request.get(
             "https://api.wall-box.com/auth/token/user",
-            text='{"jwt":"fakekeyhere","user_id":12345,"ttl":145656758,"error":false,"status":200}',
+            json=authorisation_response,
             status_code=HTTPStatus.OK,
         )
         mock_request.get(
@@ -41,5 +79,65 @@ async def setup_integration(hass):
             json=test_response,
             status_code=HTTPStatus.OK,
         )
+        mock_request.put(
+            "https://api.wall-box.com/v2/charger/12345",
+            json=json.loads(json.dumps({CONF_MAX_CHARGING_CURRENT_KEY: 20})),
+            status_code=HTTPStatus.OK,
+        )
+
+        entry.add_to_hass(hass)
+
+        await hass.config_entries.async_setup(entry.entry_id)
+        await hass.async_block_till_done()
+
+
+async def setup_integration_connection_error(hass):
+    """Test wallbox sensor class setup with a connection error."""
+
+    with requests_mock.Mocker() as mock_request:
+        mock_request.get(
+            "https://api.wall-box.com/auth/token/user",
+            json=authorisation_response,
+            status_code=HTTPStatus.FORBIDDEN,
+        )
+        mock_request.get(
+            "https://api.wall-box.com/chargers/status/12345",
+            json=test_response,
+            status_code=HTTPStatus.FORBIDDEN,
+        )
+        mock_request.put(
+            "https://api.wall-box.com/v2/charger/12345",
+            json=json.loads(json.dumps({CONF_MAX_CHARGING_CURRENT_KEY: 20})),
+            status_code=HTTPStatus.FORBIDDEN,
+        )
+
+        entry.add_to_hass(hass)
+
+        await hass.config_entries.async_setup(entry.entry_id)
+        await hass.async_block_till_done()
+
+
+async def setup_integration_read_only(hass):
+    """Test wallbox sensor class setup for read only."""
+
+    with requests_mock.Mocker() as mock_request:
+        mock_request.get(
+            "https://api.wall-box.com/auth/token/user",
+            json=authorisation_response,
+            status_code=HTTPStatus.OK,
+        )
+        mock_request.get(
+            "https://api.wall-box.com/chargers/status/12345",
+            json=test_response,
+            status_code=HTTPStatus.OK,
+        )
+        mock_request.put(
+            "https://api.wall-box.com/v2/charger/12345",
+            json=test_response,
+            status_code=HTTPStatus.FORBIDDEN,
+        )
+
+        entry.add_to_hass(hass)
+
         await hass.config_entries.async_setup(entry.entry_id)
         await hass.async_block_till_done()
diff --git a/tests/components/wallbox/const.py b/tests/components/wallbox/const.py
new file mode 100644
index 00000000000..3aa2dde38f0
--- /dev/null
+++ b/tests/components/wallbox/const.py
@@ -0,0 +1,10 @@
+"""Provides constants for Wallbox component tests."""
+CONF_JWT = "jwt"
+CONF_USER_ID = "user_id"
+CONF_TTL = "ttl"
+CONF_ERROR = "error"
+CONF_STATUS = "status"
+
+CONF_MOCK_NUMBER_ENTITY_ID = "number.mock_title_max_charging_current"
+CONF_MOCK_SENSOR_CHARGING_SPEED_ID = "sensor.mock_title_charging_speed"
+CONF_MOCK_SENSOR_CHARGING_POWER_ID = "sensor.mock_title_charging_power"
diff --git a/tests/components/wallbox/test_config_flow.py b/tests/components/wallbox/test_config_flow.py
index acf62ee3fef..ca55c076fea 100644
--- a/tests/components/wallbox/test_config_flow.py
+++ b/tests/components/wallbox/test_config_flow.py
@@ -6,11 +6,61 @@ import requests_mock
 
 from homeassistant import config_entries, data_entry_flow
 from homeassistant.components.wallbox import config_flow
-from homeassistant.components.wallbox.const import DOMAIN
+from homeassistant.components.wallbox.const import (
+    CONF_ADDED_ENERGY_KEY,
+    CONF_ADDED_RANGE_KEY,
+    CONF_CHARGING_POWER_KEY,
+    CONF_CHARGING_SPEED_KEY,
+    CONF_DATA_KEY,
+    CONF_MAX_AVAILABLE_POWER_KEY,
+    CONF_MAX_CHARGING_CURRENT_KEY,
+    DOMAIN,
+)
 from homeassistant.core import HomeAssistant
 
+from tests.components.wallbox.const import (
+    CONF_ERROR,
+    CONF_JWT,
+    CONF_STATUS,
+    CONF_TTL,
+    CONF_USER_ID,
+)
+
 test_response = json.loads(
-    '{"charging_power": 0,"max_available_power": 25,"charging_speed": 0,"added_range": 372,"added_energy": 44.697}'
+    json.dumps(
+        {
+            CONF_CHARGING_POWER_KEY: 0,
+            CONF_MAX_AVAILABLE_POWER_KEY: "xx",
+            CONF_CHARGING_SPEED_KEY: 0,
+            CONF_ADDED_RANGE_KEY: "xx",
+            CONF_ADDED_ENERGY_KEY: "44.697",
+            CONF_DATA_KEY: {CONF_MAX_CHARGING_CURRENT_KEY: 24},
+        }
+    )
+)
+
+authorisation_response = json.loads(
+    json.dumps(
+        {
+            CONF_JWT: "fakekeyhere",
+            CONF_USER_ID: 12345,
+            CONF_TTL: 145656758,
+            CONF_ERROR: "false",
+            CONF_STATUS: 200,
+        }
+    )
+)
+
+authorisation_response_unauthorised = json.loads(
+    json.dumps(
+        {
+            CONF_JWT: "fakekeyhere",
+            CONF_USER_ID: 12345,
+            CONF_TTL: 145656758,
+            CONF_ERROR: "false",
+            CONF_STATUS: 404,
+        }
+    )
 )
 
 
@@ -33,7 +83,12 @@ async def test_form_cannot_authenticate(hass):
     with requests_mock.Mocker() as mock_request:
         mock_request.get(
             "https://api.wall-box.com/auth/token/user",
-            text='{"jwt":"fakekeyhere","user_id":12345,"ttl":145656758,"error":false,"status":200}',
+            json=authorisation_response,
+            status_code=HTTPStatus.FORBIDDEN,
+        )
+        mock_request.get(
+            "https://api.wall-box.com/chargers/status/12345",
+            json=test_response,
             status_code=HTTPStatus.FORBIDDEN,
         )
         result2 = await hass.config_entries.flow.async_configure(
@@ -58,12 +113,12 @@ async def test_form_cannot_connect(hass):
     with requests_mock.Mocker() as mock_request:
         mock_request.get(
             "https://api.wall-box.com/auth/token/user",
-            text='{"jwt":"fakekeyhere","user_id":12345,"ttl":145656758,"error":false,"status":200}',
-            status_code=HTTPStatus.OK,
+            json=authorisation_response_unauthorised,
+            status_code=HTTPStatus.NOT_FOUND,
         )
         mock_request.get(
             "https://api.wall-box.com/chargers/status/12345",
-            text='{"Temperature": 100, "Location": "Toronto", "Datetime": "2020-07-23", "Units": "Celsius"}',
+            json=test_response,
             status_code=HTTPStatus.NOT_FOUND,
         )
         result2 = await hass.config_entries.flow.async_configure(
@@ -80,7 +135,7 @@ async def test_form_cannot_connect(hass):
 
 
 async def test_form_validate_input(hass):
-    """Test we handle cannot connect error."""
+    """Test we can validate input."""
     result = await hass.config_entries.flow.async_init(
         DOMAIN, context={"source": config_entries.SOURCE_USER}
     )
@@ -88,12 +143,12 @@ async def test_form_validate_input(hass):
     with requests_mock.Mocker() as mock_request:
         mock_request.get(
             "https://api.wall-box.com/auth/token/user",
-            text='{"jwt":"fakekeyhere","user_id":12345,"ttl":145656758,"error":false,"status":200}',
+            json=authorisation_response,
             status_code=HTTPStatus.OK,
         )
         mock_request.get(
             "https://api.wall-box.com/chargers/status/12345",
-            text='{"Temperature": 100, "Location": "Toronto", "Datetime": "2020-07-23", "Units": "Celsius"}',
+            json=test_response,
             status_code=HTTPStatus.OK,
         )
         result2 = await hass.config_entries.flow.async_configure(
diff --git a/tests/components/wallbox/test_init.py b/tests/components/wallbox/test_init.py
index 874629bac3e..10e6cab99fc 100644
--- a/tests/components/wallbox/test_init.py
+++ b/tests/components/wallbox/test_init.py
@@ -1,35 +1,122 @@
 """Test Wallbox Init Component."""
 import json
 
-from homeassistant.components.wallbox.const import CONF_STATION, DOMAIN
-from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
-from homeassistant.core import HomeAssistant
+import requests_mock
 
-from tests.common import MockConfigEntry
-from tests.components.wallbox import setup_integration
-
-entry = MockConfigEntry(
-    domain=DOMAIN,
-    data={
-        CONF_USERNAME: "test_username",
-        CONF_PASSWORD: "test_password",
-        CONF_STATION: "12345",
-    },
-    entry_id="testEntry",
+from homeassistant.components.wallbox import (
+    CONF_CONNECTIONS,
+    CONF_MAX_CHARGING_CURRENT_KEY,
 )
+from homeassistant.config_entries import ConfigEntryState
+from homeassistant.core import HomeAssistant
 
-test_response = json.loads(
-    '{"charging_power": 0,"max_available_power": 25,"charging_speed": 0,"added_range": 372,"added_energy": 44.697}'
+from . import test_response
+
+from tests.components.wallbox import (
+    DOMAIN,
+    entry,
+    setup_integration,
+    setup_integration_connection_error,
+    setup_integration_read_only,
+)
+from tests.components.wallbox.const import (
+    CONF_ERROR,
+    CONF_JWT,
+    CONF_STATUS,
+    CONF_TTL,
+    CONF_USER_ID,
 )
 
-test_response_rounding_error = json.loads(
-    '{"charging_power": "XX","max_available_power": "xx","charging_speed": 0,"added_range": "xx","added_energy": "XX"}'
+authorisation_response = json.loads(
+    json.dumps(
+        {
+            CONF_JWT: "fakekeyhere",
+            CONF_USER_ID: 12345,
+            CONF_TTL: 145656758,
+            CONF_ERROR: "false",
+            CONF_STATUS: 200,
+        }
+    )
 )
 
 
-async def test_wallbox_unload_entry(hass: HomeAssistant):
+async def test_wallbox_setup_unload_entry(hass: HomeAssistant):
     """Test Wallbox Unload."""
 
     await setup_integration(hass)
+    assert entry.state == ConfigEntryState.LOADED
+
+    assert await hass.config_entries.async_unload(entry.entry_id)
+    assert entry.state == ConfigEntryState.NOT_LOADED
+
+
+async def test_wallbox_unload_entry_connection_error(hass: HomeAssistant):
+    """Test Wallbox Unload Connection Error."""
+
+    await setup_integration_connection_error(hass)
+    assert entry.state == ConfigEntryState.SETUP_ERROR
+
+    assert await hass.config_entries.async_unload(entry.entry_id)
+    assert entry.state == ConfigEntryState.NOT_LOADED
+
+
+async def test_wallbox_refresh_failed_invalid_auth(hass: HomeAssistant):
+    """Test Wallbox setup with authentication error."""
+
+    await setup_integration(hass)
+    assert entry.state == ConfigEntryState.LOADED
+
+    with requests_mock.Mocker() as mock_request:
+        mock_request.get(
+            "https://api.wall-box.com/auth/token/user",
+            json=authorisation_response,
+            status_code=403,
+        )
+        mock_request.put(
+            "https://api.wall-box.com/v2/charger/12345",
+            json=json.loads(json.dumps({CONF_MAX_CHARGING_CURRENT_KEY: 20})),
+            status_code=403,
+        )
+
+        wallbox = hass.data[DOMAIN][CONF_CONNECTIONS][entry.entry_id]
+
+        await wallbox.async_refresh()
+
+    assert await hass.config_entries.async_unload(entry.entry_id)
+    assert entry.state == ConfigEntryState.NOT_LOADED
+
+
+async def test_wallbox_refresh_failed_connection_error(hass: HomeAssistant):
+    """Test Wallbox setup with connection error."""
+
+    await setup_integration(hass)
+    assert entry.state == ConfigEntryState.LOADED
+
+    with requests_mock.Mocker() as mock_request:
+        mock_request.get(
+            "https://api.wall-box.com/auth/token/user",
+            json=authorisation_response,
+            status_code=200,
+        )
+        mock_request.get(
+            "https://api.wall-box.com/chargers/status/12345",
+            json=test_response,
+            status_code=403,
+        )
+
+        wallbox = hass.data[DOMAIN][CONF_CONNECTIONS][entry.entry_id]
+
+        await wallbox.async_refresh()
+
+    assert await hass.config_entries.async_unload(entry.entry_id)
+    assert entry.state == ConfigEntryState.NOT_LOADED
+
+
+async def test_wallbox_refresh_failed_read_only(hass: HomeAssistant):
+    """Test Wallbox setup for read-only user."""
+
+    await setup_integration_read_only(hass)
+    assert entry.state == ConfigEntryState.LOADED
 
     assert await hass.config_entries.async_unload(entry.entry_id)
+    assert entry.state == ConfigEntryState.NOT_LOADED
diff --git a/tests/components/wallbox/test_number.py b/tests/components/wallbox/test_number.py
new file mode 100644
index 00000000000..989cb1b3c31
--- /dev/null
+++ b/tests/components/wallbox/test_number.py
@@ -0,0 +1,91 @@
+"""Test Wallbox Switch component."""
+import json
+
+import pytest
+import requests_mock
+
+from homeassistant.components.input_number import ATTR_VALUE, SERVICE_SET_VALUE
+from homeassistant.components.wallbox import CONF_MAX_CHARGING_CURRENT_KEY
+from homeassistant.const import ATTR_ENTITY_ID
+
+from tests.components.wallbox import entry, setup_integration
+from tests.components.wallbox.const import (
+    CONF_ERROR,
+    CONF_JWT,
+    CONF_MOCK_NUMBER_ENTITY_ID,
+    CONF_STATUS,
+    CONF_TTL,
+    CONF_USER_ID,
+)
+
+authorisation_response = json.loads(
+    json.dumps(
+        {
+            CONF_JWT: "fakekeyhere",
+            CONF_USER_ID: 12345,
+            CONF_TTL: 145656758,
+            CONF_ERROR: "false",
+            CONF_STATUS: 200,
+        }
+    )
+)
+
+
+async def test_wallbox_number_class(hass):
+    """Test wallbox sensor class."""
+
+    await setup_integration(hass)
+
+    with requests_mock.Mocker() as mock_request:
+        mock_request.get(
+            "https://api.wall-box.com/auth/token/user",
+            json=authorisation_response,
+            status_code=200,
+        )
+        mock_request.put(
+            "https://api.wall-box.com/v2/charger/12345",
+            json=json.loads(json.dumps({CONF_MAX_CHARGING_CURRENT_KEY: 20})),
+            status_code=200,
+        )
+
+        await hass.services.async_call(
+            "number",
+            SERVICE_SET_VALUE,
+            {
+                ATTR_ENTITY_ID: CONF_MOCK_NUMBER_ENTITY_ID,
+                ATTR_VALUE: 20,
+            },
+            blocking=True,
+        )
+    await hass.config_entries.async_unload(entry.entry_id)
+
+
+async def test_wallbox_number_class_connection_error(hass):
+    """Test wallbox sensor class."""
+
+    await setup_integration(hass)
+
+    with requests_mock.Mocker() as mock_request:
+        mock_request.get(
+            "https://api.wall-box.com/auth/token/user",
+            json=authorisation_response,
+            status_code=200,
+        )
+        mock_request.put(
+            "https://api.wall-box.com/v2/charger/12345",
+            json=json.loads(json.dumps({CONF_MAX_CHARGING_CURRENT_KEY: 20})),
+            status_code=404,
+        )
+
+        with pytest.raises(ConnectionError):
+
+            await hass.services.async_call(
+                "number",
+                SERVICE_SET_VALUE,
+                {
+                    ATTR_ENTITY_ID: CONF_MOCK_NUMBER_ENTITY_ID,
+                    ATTR_VALUE: 20,
+                },
+                blocking=True,
+            )
+    await hass.config_entries.async_unload(entry.entry_id)
diff --git a/tests/components/wallbox/test_sensor.py b/tests/components/wallbox/test_sensor.py
index b88ed094fda..41dcd0e6ee0 100644
--- a/tests/components/wallbox/test_sensor.py
+++ b/tests/components/wallbox/test_sensor.py
@@ -1,19 +1,10 @@
 """Test Wallbox Switch component."""
+from homeassistant.const import CONF_ICON, CONF_UNIT_OF_MEASUREMENT, POWER_KILO_WATT
 
-from homeassistant.components.wallbox.const import CONF_STATION, DOMAIN
-from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
-
-from tests.common import MockConfigEntry
-from tests.components.wallbox import setup_integration
-
-entry = MockConfigEntry(
-    domain=DOMAIN,
-    data={
-        CONF_USERNAME: "test_username",
-        CONF_PASSWORD: "test_password",
-        CONF_STATION: "12345",
-    },
-    entry_id="testEntry",
+from tests.components.wallbox import entry, setup_integration
+from tests.components.wallbox.const import (
+    CONF_MOCK_SENSOR_CHARGING_POWER_ID,
+    CONF_MOCK_SENSOR_CHARGING_SPEED_ID,
 )
 
 
@@ -22,11 +13,12 @@ async def test_wallbox_sensor_class(hass):
 
     await setup_integration(hass)
 
-    state = hass.states.get("sensor.mock_title_charging_power")
-    assert state.attributes["unit_of_measurement"] == "kW"
-    assert state.attributes["icon"] == "mdi:ev-station"
+    state = hass.states.get(CONF_MOCK_SENSOR_CHARGING_POWER_ID)
+    assert state.attributes[CONF_UNIT_OF_MEASUREMENT] == POWER_KILO_WATT
     assert state.name == "Mock Title Charging Power"
 
-    state = hass.states.get("sensor.mock_title_charging_speed")
-    assert state.attributes["icon"] == "mdi:speedometer"
+    state = hass.states.get(CONF_MOCK_SENSOR_CHARGING_SPEED_ID)
+    assert state.attributes[CONF_ICON] == "mdi:speedometer"
     assert state.name == "Mock Title Charging Speed"
+
+    await hass.config_entries.async_unload(entry.entry_id)
-- 
GitLab