Repository: home-assistant/core (Python)

Issue:
Peblar charge limit can't be set to 0A
### The problem

On the Peblar API it is possible to set the charge limit to less than 6A (e.g. 0A) to disable charging.
Unfortunately this doesn't seem possible via the current integration.

I configured an automation to automatically turn off charging (limit to 0A) when my own car is disconnected (and on when I detect my own car.. the idea being that I no longer need the card for authentication).
In the history of the charge limit I don't see any changes though.

In the UI the lowest number shown is 6A as well.
Alternatively there could also be a binary switch to change between allowing charging or pausing it.

### What version of Home Assistant Core has the issue?

core-2025.1.1

### What was the last working version of Home Assistant Core?

_No response_

### What type of installation are you running?

Home Assistant Container

### Integration causing the issue

Peblar

### Link to integration documentation on our website

https://www.home-assistant.io/integrations/peblar

### Diagnostics information

_No response_

### Example YAML snippet

_No response_

### Anything in the logs that might be useful for us?

_No response_

### Additional information

Do note that Peblar previously indicated via email that they do not have any protection in place for quickly switching the relays (and wearing them) if the limit is set below/above 6A or if the force single phase is changed. It would be good if the integration would have some mechanism to prevent frequent toggles (e.g. less than 1 minute or so between a change?).


Discussion: 
Hey there @frenck, mind taking a look at this issue as it has been labeled with an integration (`peblar`) you are listed as a [code owner](https://github.com/home-assistant/core/blob/dev/CODEOWNERS#L1125) for? Thanks!

<details>
<summary>Code owner commands</summary>


Code owners of `peblar` can trigger bot actions by commenting:

- `@home-assistant close` Closes the issue.
- `@home-assistant rename Awesome new title` Renames the issue.
- `@home-assistant reopen` Reopen the issue.
- `@home-assistant unassign peblar` Removes the current integration label and assignees on the issue, add the integration domain after the command.
- `@home-assistant add-label needs-more-information` Add a label (needs-more-information, problem in dependency, problem in custom component) to the issue.
- `@home-assistant remove-label needs-more-information` Remove a label (needs-more-information, problem in dependency, problem in custom component) on the issue.

</details>


<sub><sup>(message by CodeOwnersMention)</sup></sub>

---

[peblar documentation](https://www.home-assistant.io/integrations/peblar)
[peblar source](https://github.com/home-assistant/core/tree/dev/homeassistant/components/peblar)
<sub><sup>(message by IssueLinks)</sup></sub>
Can you set it to 0 in your Peblar web interface?
> Can you set it to 0 in your Peblar web interface?

In the web interface I can't set it 0.

I've contacted Peblar before about my use cases and that's when they told me that via the REST API (and presumably Modbus) it is possible to set it to less than 6A to turn off the relays.
I've also verified this a few months ago.

It is working as intended and documented in that case: not a bug.

I am in contact with them on this as well, as setting it to 4A will also disable charging. This is highly confusing and unexpected behavior from and end-user perspective.

Essentially, they have put two separate functionalities in this single endpoint, which IMHO isn’t right. I think instead it would have been better to have a separate in its own switch value.

I have tried to extract it into a separate switch, which will work for turning it off (and setting it to 0A), but when it is turned on again? What should the restored/previous value be? We can’t know because of the current behavior they have implemented. I think their web interface has the same limits for that reason?

For these reasons, the current integration mimics their own software.

../Frenck
During the winter I can still work around it by selecting the Pure Solar charge strategy, so no rush from my side.

Alternatively, it could also be something where you select 0 or a value between 6 and 16.
Not sure what kind of entity would be best for that and still give a logical representation in the UI.
> Alternatively, it could also be something where you select 0 or a value between 6 and 16.

We don't have that concept in Home Assistant. Numeric values cannot skip steps.
Is there already a solution to stop charging from HA?
If I set charge limit via an automation to 0. Charging still continues.
> Is there already a solution to stop charging from HA?
> If I set charge limit via an automation to 0. Charging still continues.

As mentioned, I'm currently changing the Charge strategy to Pure Solar via an automation to "disable" charging (at least with the current weather).
> If I set charge limit via an automation to 0. Charging still continues.

You can set the charge limit to 0 via an automation. The automation would fail to run.

I do have a draft solution that I'm testing out.

../Frenck
> > Is there already a solution to stop charging from HA?
> > If I set charge limit via an automation to 0. Charging still continues.
> 
> As mentioned, I'm currently changing the Charge strategy to Pure Solar via an automation to "disable" charging (at least with the current weather).

Thnx, that is a solution for now!
> > If I set charge limit via an automation to 0. Charging still continues.
> 
> You can set the charge limit to 0 via an automation. The automation would fail to run.
> 
> I do have a draft solution that I'm testing out.
> 
> ../Frenck

Oke, would be great if you have something that works. 
I can't set a max battery SoC in my car. But with HA I can read the SoC. So it would be great to set the Peblar to stop charging when the car battery is at a set level!
![Image](https://github.com/user-attachments/assets/be4d2cee-3abb-4a59-8212-4c6366ae9ef9)

Added a switch to enable/disable charging in pull request: [#137853](https://github.com/home-assistant/core/pull/137853)
Great Frenck!
Relatively new to HA. So sorry for a n00b question. 
Is the Peblar integration updated automatically so the on/off switch will become visible?
Or do I have to download the code files from github and put them in HA myself?
@wereldreiziger This is a new feature. We only release new features with the major releases, which we ship each first Wednesday of the month. This is thus pending for the 2025.3 release on the first Wednesday of March.

../Frenck



Fix (patch):
diff --git a/homeassistant/components/peblar/coordinator.py b/homeassistant/components/peblar/coordinator.py
index 058f2aefb3b0f..36708b207c566 100644
--- a/homeassistant/components/peblar/coordinator.py
+++ b/homeassistant/components/peblar/coordinator.py
@@ -34,6 +34,7 @@ class PeblarRuntimeData:
     """Class to hold runtime data."""
 
     data_coordinator: PeblarDataUpdateCoordinator
+    last_known_charging_limit = 6
     system_information: PeblarSystemInformation
     user_configuration_coordinator: PeblarUserConfigurationDataUpdateCoordinator
     version_coordinator: PeblarVersionDataUpdateCoordinator
@@ -137,6 +138,8 @@ async def _async_update_data(self) -> PeblarVersionInformation:
 class PeblarDataUpdateCoordinator(DataUpdateCoordinator[PeblarData]):
     """Class to manage fetching Peblar active data."""
 
+    config_entry: PeblarConfigEntry
+
     def __init__(
         self, hass: HomeAssistant, entry: PeblarConfigEntry, api: PeblarApi
     ) -> None:
diff --git a/homeassistant/components/peblar/icons.json b/homeassistant/components/peblar/icons.json
index 6244945077baf..a954d112c4a12 100644
--- a/homeassistant/components/peblar/icons.json
+++ b/homeassistant/components/peblar/icons.json
@@ -36,6 +36,9 @@
       }
     },
     "switch": {
+      "charge": {
+        "default": "mdi:ev-plug-type2"
+      },
       "force_single_phase": {
         "default": "mdi:power-cycle"
       }
diff --git a/homeassistant/components/peblar/number.py b/homeassistant/components/peblar/number.py
index 1a7cec43295aa..0e929a635235d 100644
--- a/homeassistant/components/peblar/number.py
+++ b/homeassistant/components/peblar/number.py
@@ -2,58 +2,27 @@
 
 from __future__ import annotations
 
-from collections.abc import Awaitable, Callable
-from dataclasses import dataclass
-from typing import Any
-
-from peblar import PeblarApi
-
 from homeassistant.components.number import (
     NumberDeviceClass,
-    NumberEntity,
     NumberEntityDescription,
+    RestoreNumber,
 )
-from homeassistant.const import EntityCategory, UnitOfElectricCurrent
-from homeassistant.core import HomeAssistant
+from homeassistant.const import (
+    STATE_UNAVAILABLE,
+    STATE_UNKNOWN,
+    EntityCategory,
+    UnitOfElectricCurrent,
+)
+from homeassistant.core import HomeAssistant, callback
 from homeassistant.helpers.entity_platform import AddEntitiesCallback
 
-from .coordinator import (
-    PeblarConfigEntry,
-    PeblarData,
-    PeblarDataUpdateCoordinator,
-    PeblarRuntimeData,
-)
+from .coordinator import PeblarConfigEntry, PeblarDataUpdateCoordinator
 from .entity import PeblarEntity
 from .helpers import peblar_exception_handler
 
 PARALLEL_UPDATES = 1
 
 
-@dataclass(frozen=True, kw_only=True)
-class PeblarNumberEntityDescription(NumberEntityDescription):
-    """Describe a Peblar number."""
-
-    native_max_value_fn: Callable[[PeblarRuntimeData], int]
-    set_value_fn: Callable[[PeblarApi, float], Awaitable[Any]]
-    value_fn: Callable[[PeblarData], int | None]
-
-
-DESCRIPTIONS = [
-    PeblarNumberEntityDescription(
-        key="charge_current_limit",
-        translation_key="charge_current_limit",
-        device_class=NumberDeviceClass.CURRENT,
-        entity_category=EntityCategory.CONFIG,
-        native_step=1,
-        native_min_value=6,
-        native_max_value_fn=lambda x: x.user_configuration_coordinator.data.user_defined_charge_limit_current,
-        native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,
-        set_value_fn=lambda x, v: x.ev_interface(charge_current_limit=int(v) * 1000),
-        value_fn=lambda x: round(x.ev.charge_current_limit / 1000),
-    ),
-]
-
-
 async def async_setup_entry(
     hass: HomeAssistant,
     entry: PeblarConfigEntry,
@@ -61,42 +30,101 @@ async def async_setup_entry(
 ) -> None:
     """Set up Peblar number based on a config entry."""
     async_add_entities(
-        PeblarNumberEntity(
-            entry=entry,
-            coordinator=entry.runtime_data.data_coordinator,
-            description=description,
-        )
-        for description in DESCRIPTIONS
+        [
+            PeblarChargeCurrentLimitNumberEntity(
+                entry=entry,
+                coordinator=entry.runtime_data.data_coordinator,
+            )
+        ]
     )
 
 
-class PeblarNumberEntity(
+class PeblarChargeCurrentLimitNumberEntity(
     PeblarEntity[PeblarDataUpdateCoordinator],
-    NumberEntity,
+    RestoreNumber,
 ):
-    """Defines a Peblar number."""
-
-    entity_description: PeblarNumberEntityDescription
+    """Defines a Peblar charge current limit number.
+
+    This entity is a little bit different from the other entities, any value
+    below 6 amps is ignored. It means the Peblar is not charging.
+    Peblar has assigned a dual functionality to the charge current limit
+    number, it is used to set the current charging value and to start/stop/pauze
+    the charging process.
+    """
+
+    _attr_device_class = NumberDeviceClass.CURRENT
+    _attr_entity_category = EntityCategory.CONFIG
+    _attr_native_min_value = 6
+    _attr_native_step = 1
+    _attr_native_unit_of_measurement = UnitOfElectricCurrent.AMPERE
+    _attr_translation_key = "charge_current_limit"
 
     def __init__(
         self,
         entry: PeblarConfigEntry,
         coordinator: PeblarDataUpdateCoordinator,
-        description: PeblarNumberEntityDescription,
     ) -> None:
-        """Initialize the Peblar entity."""
-        super().__init__(entry=entry, coordinator=coordinator, description=description)
-        self._attr_native_max_value = description.native_max_value_fn(
-            entry.runtime_data
+        """Initialize the Peblar charge current limit entity."""
+        super().__init__(
+            entry=entry,
+            coordinator=coordinator,
+            description=NumberEntityDescription(key="charge_current_limit"),
         )
-
-    @property
-    def native_value(self) -> int | None:
-        """Return the number value."""
-        return self.entity_description.value_fn(self.coordinator.data)
+        configuration = entry.runtime_data.user_configuration_coordinator.data
+        self._attr_native_max_value = configuration.user_defined_charge_limit_current
+
+    async def async_added_to_hass(self) -> None:
+        """Load the last known state when adding this entity."""
+        if (
+            (last_state := await self.async_get_last_state())
+            and (last_number_data := await self.async_get_last_number_data())
+            and last_state.state not in (STATE_UNKNOWN, STATE_UNAVAILABLE)
+            and last_number_data.native_value
+        ):
+            self._attr_native_value = last_number_data.native_value
+            # Set the last known charging limit in the runtime data the
+            # start/stop/pauze functionality needs it in order to restore
+            # the last known charging limits when charging is resumed.
+            self.coordinator.config_entry.runtime_data.last_known_charging_limit = int(
+                last_number_data.native_value
+            )
+        await super().async_added_to_hass()
+        self._handle_coordinator_update()
+
+    @callback
+    def _handle_coordinator_update(self) -> None:
+        """Handle coordinator update.
+
+        Ignore any update that provides a ampere value that is below the
+        minimum value (6 amps). It means the Peblar is currently not charging.
+        """
+        if (
+            current_charge_limit := round(
+                self.coordinator.data.ev.charge_current_limit / 1000
+            )
+        ) < 6:
+            return
+        self._attr_native_value = current_charge_limit
+        # Update the last known charging limit in the runtime data the
+        # start/stop/pauze functionality needs it in order to restore
+        # the last known charging limits when charging is resumed.
+        self.coordinator.config_entry.runtime_data.last_known_charging_limit = (
+            current_charge_limit
+        )
+        super()._handle_coordinator_update()
 
     @peblar_exception_handler
     async def async_set_native_value(self, value: float) -> None:
-        """Change to new number value."""
-        await self.entity_description.set_value_fn(self.coordinator.api, value)
+        """Change the current charging value."""
+        # If charging is currently disabled (below 6 amps), just set the value
+        # as the native value and the last known charging limit in the runtime
+        # data. So we can pick it up once charging gets enabled again.
+        if self.coordinator.data.ev.charge_current_limit < 6000:
+            self._attr_native_value = int(value)
+            self.coordinator.config_entry.runtime_data.last_known_charging_limit = int(
+                value
+            )
+            self.async_write_ha_state()
+            return
+        await self.coordinator.api.ev_interface(charge_current_limit=int(value) * 1000)
         await self.coordinator.async_request_refresh()
diff --git a/homeassistant/components/peblar/strings.json b/homeassistant/components/peblar/strings.json
index a33667fa5330f..4a1500e54c548 100644
--- a/homeassistant/components/peblar/strings.json
+++ b/homeassistant/components/peblar/strings.json
@@ -153,6 +153,9 @@
       }
     },
     "switch": {
+      "charge": {
+        "name": "Charge"
+      },
       "force_single_phase": {
         "name": "Force single phase"
       }
diff --git a/homeassistant/components/peblar/switch.py b/homeassistant/components/peblar/switch.py
index e56c2fcdaecca..74a42ddc47de4 100644
--- a/homeassistant/components/peblar/switch.py
+++ b/homeassistant/components/peblar/switch.py
@@ -6,7 +6,7 @@
 from dataclasses import dataclass
 from typing import Any
 
-from peblar import PeblarApi
+from peblar import PeblarEVInterface
 
 from homeassistant.components.switch import SwitchEntity, SwitchEntityDescription
 from homeassistant.const import EntityCategory
@@ -31,7 +31,19 @@ class PeblarSwitchEntityDescription(SwitchEntityDescription):
 
     has_fn: Callable[[PeblarRuntimeData], bool] = lambda x: True
     is_on_fn: Callable[[PeblarData], bool]
-    set_fn: Callable[[PeblarApi, bool], Awaitable[Any]]
+    set_fn: Callable[[PeblarDataUpdateCoordinator, bool], Awaitable[Any]]
+
+
+def _async_peblar_charge(
+    coordinator: PeblarDataUpdateCoordinator, on: bool
+) -> Awaitable[PeblarEVInterface]:
+    """Set the charge state."""
+    charge_current_limit = 0
+    if on:
+        charge_current_limit = (
+            coordinator.config_entry.runtime_data.last_known_charging_limit * 1000
+        )
+    return coordinator.api.ev_interface(charge_current_limit=charge_current_limit)
 
 
 DESCRIPTIONS = [
@@ -44,7 +56,14 @@ class PeblarSwitchEntityDescription(SwitchEntityDescription):
             and x.user_configuration_coordinator.data.connected_phases > 1
         ),
         is_on_fn=lambda x: x.ev.force_single_phase,
-        set_fn=lambda x, on: x.ev_interface(force_single_phase=on),
+        set_fn=lambda x, on: x.api.ev_interface(force_single_phase=on),
+    ),
+    PeblarSwitchEntityDescription(
+        key="charge",
+        translation_key="charge",
+        entity_category=EntityCategory.CONFIG,
+        is_on_fn=lambda x: (x.ev.charge_current_limit >= 6000),
+        set_fn=_async_peblar_charge,
     ),
 ]
 
@@ -82,11 +101,11 @@ def is_on(self) -> bool:
     @peblar_exception_handler
     async def async_turn_on(self, **kwargs: Any) -> None:
         """Turn the entity on."""
-        await self.entity_description.set_fn(self.coordinator.api, True)
+        await self.entity_description.set_fn(self.coordinator, True)
         await self.coordinator.async_request_refresh()
 
     @peblar_exception_handler
     async def async_turn_off(self, **kwargs: Any) -> None:
         """Turn the entity off."""
-        await self.entity_description.set_fn(self.coordinator.api, False)
+        await self.entity_description.set_fn(self.coordinator, False)
         await self.coordinator.async_request_refresh()