From eb46020bc990f8b691abc2d8baf2c4a7f1222518 Mon Sep 17 00:00:00 2001
From: Stefan Rubner <stefan@whocares.de>
Date: Sun, 19 Jan 2025 17:31:05 +0100
Subject: [PATCH 1/6] Updated README with more detailed installation
 instructions

Signed-off-by: Stefan Rubner <stefan@whocares.de>
---
 README.md | 60 +++++++++++++++++++++++++++++++++++++++++++++++++++----
 1 file changed, 56 insertions(+), 4 deletions(-)

diff --git a/README.md b/README.md
index 4840ff3..829ea4e 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,4 @@
+[![hacs_badge][hacsbadge]][hacs] [![hainstall][hainstallbadge]][hainstall]
 # ha_sonnenbatterie
 Homeassistant integration to show many stats of Sonnenbatterie
 that should work with current versions of Sonnenbatterie.
@@ -5,10 +6,7 @@ that should work with current versions of Sonnenbatterie.
 [![Validate with hassfest](https://github.com/weltmeyer/ha_sonnenbatterie/actions/workflows/hassfest.yaml/badge.svg)](https://github.com/weltmeyer/ha_sonnenbatterie/actions/workflows/hassfest.yaml)
 [![Validate with HACS](https://github.com/weltmeyer/ha_sonnenbatterie/actions/workflows/validate.yaml/badge.svg)](https://github.com/weltmeyer/ha_sonnenbatterie/actions/workflows/validate.yaml)
 
-## Installation
-Easiest way to install is to add this repository via [HACS](https://hacs.xyz).
-
-## Tested working with
+### Tested working with
 * eco 8.03 9010 ND
 * eco 8.0 DE 9010 ND
 * sonnenBatterie 10 performance
@@ -16,6 +14,53 @@ Easiest way to install is to add this repository via [HACS](https://hacs.xyz).
 ### Won't work with older Batteries
 * ex. model 9.2 eco from 2014 not working
 
+## Installation
+
+### 1) via HACS
+1. Add a custom **integration** repository to HACS using this link:
+   [https://github.com/weltmeyer/hasonnenbatterie](https://github.com/weltmeyer/hasonnenbatterie)
+   > [!IMPORTANT] 
+   > This is a **HACS _integration_**, not a **HASS-IO _AddOn_**, so you <ins>need to have [HACS](https://hacs.xyz) installed</ins>,
+   > and you need to add this repository as a custom **integration repository** to HACS.
+2. Once the repository is added, use the search bar and type `sonnenbatterie`
+3. Use the 3-dot menu to the right of the list entry (not the one at the top bar!) to download/install the integration.  
+   The latest release is automatically selected. Only select a different version if you've been told to do so
+   by one of the maintainers.
+4. After you press download and the process has completed, you have to __Restart Home Assistant__ to install the
+   dependencies required by the integration
+5. Setup the `sonnenbatterie` custom integration
+
+### 2) Manual installation
+
+1. Using your tool of choice open the directory (folder) where your HA configuration resides, e.g. where the
+   `configuration.yaml` is
+2. If you don't have a `custom_components` directory (folder) there, create it
+3. In the `custom_components` directory (folder) create a new folder called `sonnenbatterie`
+4. Download _all_ the files from the `custom_components/sonnenbatterie/` directory (folder) from this repository
+5. Place the files you downloaded in the new directory (folder) `sonnenbatterie` you created
+6. Restart Home Assistant
+7. Setup the sonnenbatterie custom integration as described below (see [Adding or enabling the integration](#adding_or_enabling_the_integration))
+
+## Adding or enabling the integration
+
+> [!IMPORTANT]
+> The integration must be [installed](#installation) before you can start to add or enable it!
+
+### 1) My Home Assistant
+
+Just click the following Button to start the configuration automatically:
+
+[![Open your Home Assistant instance and start setting up a new integration.](https://my.home-assistant.io/badges/config_flow_start.svg)][hainstall]
+
+### 2) Manual
+
+- Open the Home Assistant we interface
+- Go to `Configuration -> Integrations` and click the "Add Integration" button in the lower right corner
+- Search for "sonnenbatterie", select the correct entry and click on it
+- This starts the configuration of a new Sonnenbatterie instance. Make sure to
+  - provide the correct IP address of your Sonnenbatterie within your network
+  - set the update interval to a reasonable value
+
 ## Sensors
 The main focus of the integration is to provide a comprehensive set of sensors
 for your SonnenBatterie. Right after installation the most relevant sensors 
@@ -233,3 +278,10 @@ Please put those logs along with the setting you want monitored into
 
 ## Screenshots :)
 ![image](https://user-images.githubusercontent.com/1668465/78452159-ed2d7d80-7689-11ea-9e30-3a66ecc2372a.png)
+
+---
+[hacs]: https://hacs.xyz
+[hacsbadge]: https://img.shields.io/badge/HACS-Custom-orange.svg?style=for-the-badge&logo=homeassistantcommunitystore&logoColor=ccc
+
+[hainstall]: https://my.home-assistant.io/redirect/config_flow_start/?domain=sonnenbatterie
+[hainstallbadge]: https://img.shields.io/badge/dynamic/json?style=for-the-badge&logo=home-assistant&logoColor=ccc&label=usage&suffix=%20installs&cacheSeconds=15600&url=https://analytics.home-assistant.io/custom_integrations.json&query=$.sonnenbatterie.total
\ No newline at end of file

From 78728e685cf433a4a3028dcc7ded54166cb0e4a7 Mon Sep 17 00:00:00 2001
From: Stefan Rubner <stefan@whocares.de>
Date: Sun, 19 Jan 2025 17:38:00 +0100
Subject: [PATCH 2/6] Updates description for actions, added explanation for
 device_id

Signed-off-by: Stefan Rubner <stefan@whocares.de>
---
 README.md | 16 +++++++++++++++-
 1 file changed, 15 insertions(+), 1 deletion(-)

diff --git a/README.md b/README.md
index 829ea4e..5c8755f 100644
--- a/README.md
+++ b/README.md
@@ -75,6 +75,13 @@ are already activated.
 Since version 2025.01.01 this integration also supports actions you can use to
 set some variables that influence the behaviour of your SonnenBatterie.
 
+> [!NOTE]
+> All actions require you ro provide a `device_id` to correctly identify the
+> Sonnenbatterie you want to talk to. To find the device id for your Sonnenbatterie
+> use the developer tools provided by Home Assistant. Just open the "Actions" tab
+> and select an action an a device. Then switch to YAML mode where instead of the
+> user-friendly name the device id will be displayed.
+
 Currently supported actions are:
 
 ### <a name="set_operatingmode"></a>`set_operating_mode(mode=<mode>)`
@@ -88,6 +95,7 @@ Currently supported actions are:
 ``` yaml
 action: sonnenbatterie.set_operating_mode
 data:
+  device_id: "<your sb instance's device id>"
   mode: "automatic"
 ```
 
@@ -118,6 +126,7 @@ An `int` representing the mode that has been set:
 ``` yaml
 action: sonnenbatterie.charge_battery
 data:
+  device_id: "<your sb instance's device id>"
   power: 0
 ```
 
@@ -148,6 +157,7 @@ otherwise.
 ``` yaml
 action: sonnenbatterie.discharge_battery
 data:
+  device_id: "<your sb instance's device id>"
   power: 0
 ```
 
@@ -164,6 +174,7 @@ otherwise.
 ``` yaml
 action: sonnenbatterie.set_battery_reserve
 data:
+  device_id: "<your sb instance's device id>"
   value: 10
 ```
 
@@ -203,6 +214,7 @@ An integer representing the current value of "battery reserve"
 ``` yaml
 action: sonnenbatterie.set_config_item
 data:
+  device_id: "<your sb instance's device id>"
   item: "EM_USOC"
   value: "10"
 ```
@@ -236,6 +248,7 @@ data:
 ``` yaml
 action: sonnenbatterie.set_tou_schedule_string
 data:
+  device_id: "<your sb instance's device id>"
   schedule: '[{"start":"10:00", "stop":"10:00", "threshold_p_max": 20000}]'
 ```
 
@@ -252,7 +265,8 @@ data:
 ##### Code snippet
 ``` yaml 
 action: sonnenbatterie.get_tou_schedule
-data: {}
+data:
+  deviceid: "<your sb instance's device id>"
 ```
 
 ##### Result

From 4b789f0d60b0911631f4976728c0f02f61cb3965 Mon Sep 17 00:00:00 2001
From: Stefan Rubner <stefan@whocares.de>
Date: Sun, 19 Jan 2025 17:43:31 +0100
Subject: [PATCH 3/6] Fix some minor formatting errors in the actions section

Signed-off-by: Stefan Rubner <stefan@whocares.de>
---
 README.md | 10 ++++++----
 1 file changed, 6 insertions(+), 4 deletions(-)

diff --git a/README.md b/README.md
index 5c8755f..a058de1 100644
--- a/README.md
+++ b/README.md
@@ -220,7 +220,7 @@ data:
 ```
 ##### Response
 ``` json
-{'EM_USOC': '10'}
+{"EM_USOC": "10"}
 ```
 
 ### <a name="set_tou_schedule"></a>`set_tou_schedule(schedule=<schedule_array>)`
@@ -255,7 +255,7 @@ data:
 ##### Result
 ``` json
 {
-  "schedule": '[{"start": "10:00", "stop": "10:00", "threshold_p_max": 20000}]'
+  "schedule": [{"start": "10:00", "stop": "10:00", "threshold_p_max": 20000}]
 }
 ```
 
@@ -270,8 +270,10 @@ data:
 ```
 
 ##### Result
-``` yaml
-schedule: "[{\"start\":\"10:00\", \"stop\":\"10:00\", \"threshold_p_max\": 20000}]"
+``` json
+{
+  "schedule": [{"start": "10:00", "stop": "10:00", "threshold_p_max": 20000}]
+}
 ```
 
 ## Problems and/or unused/unavailable sensors

From 383819a64760bd95d44a6eda4abed012b87a201c Mon Sep 17 00:00:00 2001
From: stefan <stefan@whocares.de>
Date: Fri, 24 Jan 2025 16:50:52 +0100
Subject: [PATCH 4/6] Change input type for charge/discharge actions to str,
 closes #78

Signed-off-by: stefan <stefan@whocares.de>
---
 custom_components/sonnenbatterie/__init__.py   | 4 +++-
 custom_components/sonnenbatterie/manifest.json | 2 +-
 custom_components/sonnenbatterie/service.py    | 2 ++
 custom_components/sonnenbatterie/services.yaml | 6 ++++--
 4 files changed, 10 insertions(+), 4 deletions(-)

diff --git a/custom_components/sonnenbatterie/__init__.py b/custom_components/sonnenbatterie/__init__.py
index 20f83df..b57667d 100644
--- a/custom_components/sonnenbatterie/__init__.py
+++ b/custom_components/sonnenbatterie/__init__.py
@@ -93,12 +93,14 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b
     hass.data[DOMAIN][config_entry.entry_id][CONF_COORDINATOR] = sb_coordinator
 
     inverter_power = sb_coordinator.latestData['battery_system']['battery_system']['system']['inverter_capacity']
+    LOGGER.debug(f"inverter_power: {inverter_power}")
 
     # noinspection PyPep8Naming
     SCHEMA_CHARGE_BATTERY = vol.Schema(
         {
             **cv.ENTITY_SERVICE_FIELDS,
-            vol.Required(CONF_CHARGE_WATT): vol.Range(min=0, max=inverter_power),
+            # vol.Required(CONF_CHARGE_WATT): vol.Range(min=0, max=inverter_power),
+            vol.Required(CONF_CHARGE_WATT): str,
         }
     )
 
diff --git a/custom_components/sonnenbatterie/manifest.json b/custom_components/sonnenbatterie/manifest.json
index d17e04d..455ad70 100644
--- a/custom_components/sonnenbatterie/manifest.json
+++ b/custom_components/sonnenbatterie/manifest.json
@@ -8,5 +8,5 @@
     "iot_class": "local_polling",
     "issue_tracker": "https://github.com/weltmeyer/ha_sonnenbatterie/issues",
     "requirements": ["requests","sonnenbatterie>=0.5.2"],
-    "version": "2025.01.02"
+    "version": "2025.01.03"
 }
diff --git a/custom_components/sonnenbatterie/service.py b/custom_components/sonnenbatterie/service.py
index dc2931d..8076e28 100644
--- a/custom_components/sonnenbatterie/service.py
+++ b/custom_components/sonnenbatterie/service.py
@@ -49,6 +49,7 @@ def _get_sb_connection(self, call_data: ReadOnlyDict) -> AsyncSonnenBatterie:
 
     # service definitions
     async def charge_battery(self, call: ServiceCall) -> ServiceResponse:
+        LOGGER.debug(f"_charge_battery: {call.data}")
         power = int(call.data.get(CONF_CHARGE_WATT))
         # Make sure we have an sb2 object
         sb_conn = self._get_sb_connection(call.data)
@@ -60,6 +61,7 @@ async def charge_battery(self, call: ServiceCall) -> ServiceResponse:
         }
 
     async def discharge_battery(self, call: ServiceCall) -> ServiceResponse:
+        LOGGER.debug(f"_discharge_battery: {call.data}")
         power = int(call.data.get(CONF_CHARGE_WATT))
         sb_conn = self._get_sb_connection(call.data)
         # await sb_conn.login()
diff --git a/custom_components/sonnenbatterie/services.yaml b/custom_components/sonnenbatterie/services.yaml
index 1ff6d88..b656298 100644
--- a/custom_components/sonnenbatterie/services.yaml
+++ b/custom_components/sonnenbatterie/services.yaml
@@ -22,7 +22,8 @@ charge_battery:
       required: true
       example: "1000"
       selector:
-        number:
+        text:
+          suffix: "W"
 discharge_battery:
   fields:
     device_id:
@@ -34,7 +35,8 @@ discharge_battery:
       required: true
       example: "1000"
       selector:
-        number:
+        text:
+          suffix: "W"
 set_battery_reserve:
   fields:
     device_id:

From eb988546ce8da480812ceabbab94e1f2e313a6f8 Mon Sep 17 00:00:00 2001
From: stefan <stefan@whocares.de>
Date: Fri, 24 Jan 2025 16:57:42 +0100
Subject: [PATCH 5/6] Add trivial check for negative values to dis-/charge
 actions

Signed-off-by: stefan <stefan@whocares.de>
---
 custom_components/sonnenbatterie/service.py | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/custom_components/sonnenbatterie/service.py b/custom_components/sonnenbatterie/service.py
index 8076e28..2ee52a7 100644
--- a/custom_components/sonnenbatterie/service.py
+++ b/custom_components/sonnenbatterie/service.py
@@ -51,6 +51,8 @@ def _get_sb_connection(self, call_data: ReadOnlyDict) -> AsyncSonnenBatterie:
     async def charge_battery(self, call: ServiceCall) -> ServiceResponse:
         LOGGER.debug(f"_charge_battery: {call.data}")
         power = int(call.data.get(CONF_CHARGE_WATT))
+        if power < 0:
+            power = 0
         # Make sure we have an sb2 object
         sb_conn = self._get_sb_connection(call.data)
         # await sb_conn.login()
@@ -63,6 +65,8 @@ async def charge_battery(self, call: ServiceCall) -> ServiceResponse:
     async def discharge_battery(self, call: ServiceCall) -> ServiceResponse:
         LOGGER.debug(f"_discharge_battery: {call.data}")
         power = int(call.data.get(CONF_CHARGE_WATT))
+        if power < 0:
+            power = 0
         sb_conn = self._get_sb_connection(call.data)
         # await sb_conn.login()
         response = await sb_conn.sb2.discharge_battery(power)

From 9e0c78b71ac7dddd5378e8840b4b2aa418e7f85b Mon Sep 17 00:00:00 2001
From: stefan <stefan@whocares.de>
Date: Fri, 24 Jan 2025 17:48:29 +0100
Subject: [PATCH 6/6] Add number service to set `battery reserve`, closes #80

Signed-off-by: stefan <stefan@whocares.de>
---
 custom_components/sonnenbatterie/entities.py  | 21 ++++++++++++++++++-
 custom_components/sonnenbatterie/number.py    |  8 ++++++-
 .../sonnenbatterie/translations/de.json       |  3 +++
 .../sonnenbatterie/translations/en.json       |  4 ++++
 4 files changed, 34 insertions(+), 2 deletions(-)

diff --git a/custom_components/sonnenbatterie/entities.py b/custom_components/sonnenbatterie/entities.py
index 9c6c4f8..f8a9fbb 100644
--- a/custom_components/sonnenbatterie/entities.py
+++ b/custom_components/sonnenbatterie/entities.py
@@ -56,6 +56,14 @@ def __str__(self) -> str:
         writable=True,
     )
 
+    BATTERY_RESERVE = SelectEntry(
+        key="battery_reserve",
+        type=Platform.NUMBER,
+        section="status",
+        property="USOC",
+        writable=True,
+    )
+
     BTN_RESET_CHARGE = SelectEntry(
         key="button_reset_charge",
         type=Platform.BUTTON,
@@ -131,7 +139,7 @@ def unique_id(self) -> str:
 class SonnenbatterieNumberEntityDescription(NumberEntityDescription):
     tag: Tag = None
     native_min_value = 0
-    native_step = 100
+    native_step = 1
 
 
 class SonnenNumberEntity(CoordinatorEntity[SonnenbatterieCoordinator], Entity):
@@ -204,6 +212,7 @@ def unique_id(self) -> str:
         tag=Tag.CHARGE_POWER,
         device_class=NumberDeviceClass.POWER,
         mode=NumberMode.SLIDER,
+        native_step=100,
     ),
     SonnenbatterieNumberEntityDescription(
         key=Tag.DISCHARGE_POWER.key,
@@ -212,6 +221,16 @@ def unique_id(self) -> str:
         tag=Tag.DISCHARGE_POWER,
         device_class=NumberDeviceClass.POWER,
         mode=NumberMode.SLIDER,
+        native_step=100,
+    ),
+    SonnenbatterieNumberEntityDescription(
+        key=Tag.BATTERY_RESERVE.key,
+        icon="mdi:battery-unknown",
+        entity_category=EntityCategory.CONFIG,
+        tag=Tag.BATTERY_RESERVE,
+        device_class=NumberDeviceClass.BATTERY,
+        mode=NumberMode.SLIDER,
+        native_step=1,
     )
 ]
 
diff --git a/custom_components/sonnenbatterie/number.py b/custom_components/sonnenbatterie/number.py
index 3b123a0..8033b04 100644
--- a/custom_components/sonnenbatterie/number.py
+++ b/custom_components/sonnenbatterie/number.py
@@ -27,7 +27,11 @@ class SonnenbatterieNumber(SonnenNumberEntity, NumberEntity):
 
     def __init__(self, coordinator: SonnenbatterieCoordinator, description: SonnenbatterieNumberEntityDescription, max_power: int) -> None:
         super().__init__(coordinator, description)
-        self._max_power = max_power
+        LOGGER.debug(f"SonnenbatterieNumberEntity: {description}")
+        if description.key == "battery_reserve":
+            self._max_power = 100
+        else:
+            self._max_power = max_power
 
     @property
     def native_max_value(self) -> int:
@@ -42,5 +46,7 @@ async def async_set_native_value(self, value):
                     await self.coordinator.sbconn.sb2.charge_battery(int(value))
                 case "number_discharge":
                     await self.coordinator.sbconn.sb2.discharge_battery(int(value))
+                case "battery_reserve":
+                    await self.coordinator.sbconn.sb2.set_battery_reserve(int(value))
             await self.coordinator.async_request_refresh()
         return None
diff --git a/custom_components/sonnenbatterie/translations/de.json b/custom_components/sonnenbatterie/translations/de.json
index d128b76..8a4479b 100644
--- a/custom_components/sonnenbatterie/translations/de.json
+++ b/custom_components/sonnenbatterie/translations/de.json
@@ -51,6 +51,9 @@
             },
             "number_discharge": {
                 "name": "Entladen erzwingen (W)"
+            },
+            "battery_reserve": {
+                "name": "Batterie-Reserve einstellen (%)"
             }
         },
         "select": {
diff --git a/custom_components/sonnenbatterie/translations/en.json b/custom_components/sonnenbatterie/translations/en.json
index 7d176d1..7730236 100644
--- a/custom_components/sonnenbatterie/translations/en.json
+++ b/custom_components/sonnenbatterie/translations/en.json
@@ -51,7 +51,11 @@
             },
             "number_discharge": {
                 "name": "Force discharge (W)"
+            },
+            "battery_reserve": {
+                "name": "Set battery reserve (%)"
             }
+
         },
         "select": {
             "select_operating_mode": {