Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Actions! #73

Merged
merged 16 commits into from
Jan 1, 2025
Merged

Actions! #73

merged 16 commits into from
Jan 1, 2025

Conversation

RustyDust
Copy link
Collaborator

Actions!

Warning

This version of the integration needs python_sonnenbatterie >= 0.5.0 (see this PR)

This is the first version of the integration to provide user facing
actions. Currently supported are:

  • set_operating_mode(mode=<mode>)
  • charge_battery(power=<power>)
  • discharge_battery(power=<power>)
  • set_battery_reserve(value=<value>)
  • set_config_item(item=<item>, value=<value>)
  • set_tou_schedule(schedule=<schedule_array>)
  • get_tou_schedule()

Signed-off-by: stefan <stefan@whocares.de>
Signed-off-by: stefan <stefan@whocares.de>
actions. Currently supported are:

- `set_operating_mode(mode=<mode>)`
- `charge_battery(power=<power>)`
- `discharge_battery(power=<power>)`
- `set_battery_reserve(value=<value>)`
- `set_config_item(item=<item>, value=<value>)`
- `set_tou_schedule(schedule=<schedule_array>)`
- `get_tou_schedule()`

Signed-off-by: stefan <stefan@whocares.de>
Signed-off-by: stefan <stefan@whocares.de>
@RustyDust RustyDust requested a review from weltmeyer December 29, 2024 23:15
@RustyDust RustyDust self-assigned this Dec 29, 2024
Signed-off-by: stefan <stefan@whocares.de>
Signed-off-by: stefan <stefan@whocares.de>
Signed-off-by: stefan <stefan@whocares.de>
@RustyDust RustyDust marked this pull request as draft December 29, 2024 23:38
@weltmeyer
Copy link
Owner

I published https://pypi.org/manage/project/sonnenbatterie/release/0.5.0/ :)
That looks awesome!

@weltmeyer weltmeyer marked this pull request as ready for review December 30, 2024 00:39
@weltmeyer weltmeyer marked this pull request as draft December 30, 2024 00:40
This was linked to issues Dec 30, 2024
Signed-off-by: stefan <stefan@whocares.de>
@RustyDust RustyDust marked this pull request as ready for review December 30, 2024 01:47
@RustyDust
Copy link
Collaborator Author

@weltmeyer
Thanks for linking the issues. Totally forgot that I intended to do that ;)

Signed-off-by: stefan <stefan@whocares.de>
@RustyDust
Copy link
Collaborator Author

Oh, and please take your time testing this. I might have broken things when switching to "fully asynchronous".
I still have a suspicion that the sensor values stop updating after a while.

@weltmeyer
Copy link
Owner

weltmeyer commented Dec 30, 2024

Fresh HAOS install, errors right after setup

2024-12-30 10:22:38.925 ERROR (MainThread) [homeassistant.config_entries] Error setting up entry 10.0.20.114 (<coroutine object SonnenbatterieFlowHandler._internal_setup at 0x7fec5d49d120>) for sonnenbatterie
Traceback (most recent call last):
  File "/usr/src/homeassistant/homeassistant/config_entries.py", line 640, in __async_setup_with_context
    result = await component.async_setup_entry(hass, self)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/config/custom_components/sonnenbatterie/__init__.py", line 68, in async_setup_entry
    LOGGER.info(f"setup_entry: {json.dumps(dict(config_entry.data))}\n{json.dumps(dict(config_entry.options))}")
                                ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.13/json/__init__.py", line 231, in dumps
    return _default_encoder.encode(obj)
           ~~~~~~~~~~~~~~~~~~~~~~~^^^^^
  File "/usr/local/lib/python3.13/json/encoder.py", line 200, in encode
    chunks = self.iterencode(o, _one_shot=True)
  File "/usr/local/lib/python3.13/json/encoder.py", line 261, in iterencode
    return _iterencode(o, 0)
  File "/usr/local/lib/python3.13/json/encoder.py", line 180, in default
    raise TypeError(f'Object of type {o.__class__.__name__} '
                    f'is not JSON serializable')
TypeError: Object of type coroutine is not JSON serializable
2024-12-30 10:22:39.937 ERROR (MainThread) [homeassistant] Error doing job: Task exception was never retrieved (None)
Traceback (most recent call last):
  File "/usr/src/homeassistant/homeassistant/helpers/json.py", line 87, in json_encoder_default
    raise TypeError
TypeError
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
  File "/usr/src/homeassistant/homeassistant/helpers/storage.py", line 516, in _async_callback_delayed_write
    await self._async_handle_write_data()
  File "/usr/src/homeassistant/homeassistant/helpers/storage.py", line 541, in _async_handle_write_data
    await self._async_write_data(self.path, data)
  File "/usr/src/homeassistant/homeassistant/helpers/storage.py", line 546, in _async_write_data
    await self.hass.async_add_executor_job(self._write_data, self.path, data)
  File "/usr/local/lib/python3.13/concurrent/futures/thread.py", line 58, in run
    result = self.fn(*self.args, **self.kwargs)
  File "/usr/src/homeassistant/homeassistant/helpers/storage.py", line 553, in _write_data
    data["data"] = data.pop("data_func")()
                   ~~~~~~~~~~~~~~~~~~~~~^^
  File "/usr/src/homeassistant/homeassistant/config_entries.py", line 2432, in _data_to_save
    "entries": [entry.as_storage_fragment for entry in self._entries.values()]  # type: ignore[misc]
                ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "src/propcache/_helpers_c.pyx", line 80, in propcache._helpers_c.cached_property.__get__
  File "/usr/src/homeassistant/homeassistant/config_entries.py", line 537, in as_storage_fragment
    return json_fragment(json_bytes_sorted(self.as_dict()))
                         ~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^
TypeError: Type is not JSON serializable: coroutine

Signed-off-by: stefan <stefan@whocares.de>
@RustyDust
Copy link
Collaborator Author

Fresh HAOS install, errors right after setup

2024-12-30 10:22:38.925 ERROR (MainThread) [homeassistant.config_entries] Error setting up entry 10.0.20.114 (<coroutine object SonnenbatterieFlowHandler._internal_setup at 0x7fec5d49d120>) for sonnenbatterie
Traceback (most recent call last):

Should be fixed now, together with some other minor stuff.

@weltmeyer
Copy link
Owner

weltmeyer commented Dec 30, 2024

Should be fixed now, together with some other minor stuff.
Yes. just config'd it and enabled all sensors. watching it now.

@RustyDust
Copy link
Collaborator Author

RustyDust commented Dec 30, 2024

Yes. just config'd it and enabled all sensors. watching it now.

Also try some re-configuration, like for example changing the scan interval. Works fine for me but you never know ...

@weltmeyer
Copy link
Owner

image
Doesnt update anymore...
did not do anything and got no errors in log
this is the case for all values
the prod install is still update(so its not a problem with the battery or the network)

@RustyDust
Copy link
Collaborator Author

Doesnt update anymore...

Uhoh, I think that's more a problem with python_sonnenbatterie there. Testing a fix right now. Thanks for confirming.

@RustyDust
Copy link
Collaborator Author

Yup, at least for me it was a problem with python_sonnenbatterie. I just sent a new PR and incremented the version to 0.5.1.

Signed-off-by: stefan <stefan@whocares.de>
@weltmeyer
Copy link
Owner

updated and restarted and in testing :)

@weltmeyer
Copy link
Owner

Do you know how to get a device selection in the actions UI?
image

@weltmeyer
Copy link
Owner

Ahh i see its not meant to select a device. (What if i have multiple sonnenbatteries?)

So the service returns an error for me:

Logger: homeassistant.helpers.script.websocket_api_script
Source: helpers/script.py:526
First occurred: 10:14:41 PM (1 occurrences)
Last logged: 10:14:41 PM

websocket_api script: Error executing script. Unexpected error for call_service at pos 1: Session is closed
Traceback (most recent call last):
  File "/usr/src/homeassistant/homeassistant/helpers/script.py", line 526, in _async_step
    await getattr(self, handler)()
  File "/usr/src/homeassistant/homeassistant/helpers/script.py", line 764, in _async_call_service_step
    response_data = await self._async_run_long_action(
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    ...<9 lines>...
    )
    ^
  File "/usr/src/homeassistant/homeassistant/helpers/script.py", line 727, in _async_run_long_action
    return await long_task
           ^^^^^^^^^^^^^^^
  File "/usr/src/homeassistant/homeassistant/core.py", line 2802, in async_call
    response_data = await coro
                    ^^^^^^^^^^
  File "/usr/src/homeassistant/homeassistant/core.py", line 2845, in _execute_service
    return await target(service_call)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/config/custom_components/sonnenbatterie/__init__.py", line 158, in get_tou_schedule
    await sb.login()
  File "/usr/local/lib/python3.13/site-packages/sonnenbatterie/sonnenbatterie.py", line 236, in login
    req_challenge = await self._session.get(
                    ^^^^^^^^^^^^^^^^^^^^^^^^
    ...<2 lines>...
    )
    ^
  File "/usr/local/lib/python3.13/site-packages/aiohttp/client.py", line 512, in _request
    raise RuntimeError("Session is closed")
RuntimeError: Session is closed

@RustyDust
Copy link
Collaborator Author

RustyDust commented Dec 30, 2024

Unfortunately I can't deduce from the snippet what action you tried to run. I'll look into the device id issue.
Ah, ok. Another token expiry issue. Aargh 🤪

@RustyDust RustyDust marked this pull request as draft December 30, 2024 22:19
@weltmeyer
Copy link
Owner

Unfortunately I can't deduce from the snippet what action you tried to run

it was "Sonnenbatterie: Get charging schedule"

- Properly support device selection on actions (device_id is now mandatory!)
- Support multiple SonnenBatterie instances (I even tested this ;))

Signed-off-by: stefan <stefan@whocares.de>
@RustyDust
Copy link
Collaborator Author

Ahh i see its not meant to select a device. (What if i have multiple sonnenbatteries?)

Multiple SonnenBatteries are now supported

So the service returns an error for me:

RuntimeError: Session is closed

Fixed - unfortunately with yet another PR to python_sonnenbatterie

Signed-off-by: stefan <stefan@whocares.de>
@weltmeyer
Copy link
Owner

Get charging schedule is working now.
i tried Discharge:

action: sonnenbatterie.discharge_battery
data:
  device_id: 6a7786f85afe8ced361b0dfb9fd591a1
  power: 5

this resulted with a 401:

Logger: homeassistant.helpers.script.websocket_api_script
Source: helpers/script.py:526
First occurred: 1:11:17 PM (1 occurrences)
Last logged: 1:11:17 PM

websocket_api script: Error executing script. Unexpected error for call_service at pos 1: 401, message='Unauthorized', url='http://10.0.20.114/api/v2/setpoint/discharge/5'
Traceback (most recent call last):
  File "/usr/src/homeassistant/homeassistant/helpers/script.py", line 526, in _async_step
    await getattr(self, handler)()
  File "/usr/src/homeassistant/homeassistant/helpers/script.py", line 764, in _async_call_service_step
    response_data = await self._async_run_long_action(
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    ...<9 lines>...
    )
    ^
  File "/usr/src/homeassistant/homeassistant/helpers/script.py", line 727, in _async_run_long_action
    return await long_task
           ^^^^^^^^^^^^^^^
  File "/usr/src/homeassistant/homeassistant/core.py", line 2802, in async_call
    response_data = await coro
                    ^^^^^^^^^^
  File "/usr/src/homeassistant/homeassistant/core.py", line 2845, in _execute_service
    return await target(service_call)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/config/custom_components/sonnenbatterie/__init__.py", line 139, in discharge_battery
    response = await sb_conn.sb2.discharge_battery(power)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.13/site-packages/sonnenbatterie2/sonnenbatterie2.py", line 332, in discharge_battery
    return await self.set_manual_flow("discharge", watts)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.13/site-packages/sonnenbatterie2/sonnenbatterie2.py", line 326, in set_manual_flow
    return await self._post(f"setpoint/{direction}/{watts}")
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.13/site-packages/sonnenbatterie2/sonnenbatterie2.py", line 287, in _post
    return await self._post(what, isretry=True)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.13/site-packages/sonnenbatterie2/sonnenbatterie2.py", line 289, in _post
    response.raise_for_status()
    ~~~~~~~~~~~~~~~~~~~~~~~~~^^
  File "/usr/local/lib/python3.13/site-packages/aiohttp/client_reqrep.py", line 1161, in raise_for_status
    raise ClientResponseError(
    ...<5 lines>...
    )
aiohttp.client_exceptions.ClientResponseError: 401, message='Unauthorized', url='http://10.0.20.114/api/v2/setpoint/discharge/5'

i have NEVER used these functions on my battery, so i am not sure if it should work or not. Also the bat is at 0% currently so this could be just some generic API error

@weltmeyer
Copy link
Owner

Nevermind, the 401 is resolved by enabling/allowing write-operations in the batterie.

Maybe we should inform the user about this :)

@weltmeyer
Copy link
Owner

/api/json_api/json_api_configuration contains the api configuration state:

{
    "IN_LocalAPIReadActive": "1",
    "IN_LocalAPIWriteActive": "1",
    "IN_LocalAPIToken": "REDACTED",
    "VPP_Priority": null
}

/api/json_api/local_write allows setting the values (disable IN_LocalAPIWriteActive) :

curl 'http://10.0.20.114/api/json_api/local_write' \
  -X 'PUT' \
  -H 'Accept: application/json, text/javascript, */*; q=0.01' \
  -H 'Accept-Language: en-US,en' \
  -H 'Auth-Token: REDACTED' \
  -H 'Cache-Control: no-cache' \
  -H 'Connection: keep-alive' \
  -H 'Content-Type: application/x-www-form-urlencoded; charset=UTF-8' \
  -H 'Cookie: authenticationToken=REDACTED' \
  -H 'Origin: http://10.0.20.114' \
  -H 'Pragma: no-cache' \
  -H 'Referer: http://10.0.20.114/dash/software-integration/json-api' \
  -H 'Sec-GPC: 1' \
  -H 'User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36' \
  -H 'X-Requested-With: XMLHttpRequest' \
  --data-raw 'IN_LocalAPIWriteActive=0' \
  --insecure

@weltmeyer
Copy link
Owner

I'll let this Version of the PR run until tomorrow and if it doesn't stall on the value-updates we can merge it,imho.

Signed-off-by: stefan <stefan@whocares.de>
@RustyDust
Copy link
Collaborator Author

I'll let this Version of the PR run until tomorrow and if it doesn't stall on the value-updates we can merge it,imho.

I'd like to add two more actions: get_backup_reserve() and get_operating_mode() for convenience when scripting automations.
Also, since you so kindly provided an endpoint for checking the availability of the write API, I'm thinking about incorporating that one into the integration so that we can properly inform the user about the problem.

@weltmeyer
Copy link
Owner

I'll wait these actions then.

Maybe we can just if API is enabled in case of 401 to show a more describing error message.
We could also give the user an action to enabled it. Text may be something like:

(READ|WRITE) Api is not enabled. you can enable it on the sonnenbatterie itself or via action
sonnenbatterie.enableAllApiCalls
you can check the current status with sonnenbatterie.checkAllApiCallsEnabled

I first need to understand how actions work from your code to be able to write this by myself.. you may be faster at implementing this :)

@RustyDust
Copy link
Collaborator Author

Maybe we can just if API is enabled in case of 401 to show a more describing error message.

I so love the logic of Sonnen. The Write API is accessible only from the v2 API. But to check whether the Write API is enabled you have to use the API v1 - and of course the respective credentials. Jeez. I'll have to think about this.

We could also give the user an action to enable it.

I'd rather not provide that. Enabling the API should be a decision the user has to make and set it on the device. (imho)

I first need to understand how actions work

Actually, that's fairly easy:

  • create the functions you want within the async_setup_entry(...) function
    • every function uses the call object it gets passed. In call.data are all parameters passed to the function.
  • also within the async_setup_entry(...) function add the hass.services.async_register(...) call for your function
  • add it to services.yaml and don't forget the translations.

=> You're done ;)
If you have any specific questions feel free to use the annotations option in the files review tab here.

- `get_battery_reserve`
- `get_operating_mode`

> [!WARNING]
> Untested since I'm on the road

Signed-off-by: Stefan Rubner <stefan@whocares.de>
@weltmeyer
Copy link
Owner

Happy New Year :)

For me it looks like everything is working
(I tried all the get services...)
I am using only the senors.

If you don't have anything you want to change you can undraft this PR .

@RustyDust RustyDust marked this pull request as ready for review January 1, 2025 13:40
@RustyDust
Copy link
Collaborator Author

Happy New Year :)

To you too!

If you don't have anything you want to change you can undraft this PR .

It's undrafted ;) Keeping my fingers crossed.

@RustyDust RustyDust merged commit aa7e236 into weltmeyer:master Jan 1, 2025
2 checks passed
@RustyDust RustyDust deleted the sru_work branch January 1, 2025 15:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Button to start Manual Loading Process Time of Use functionality?
2 participants