bimmerconnected / bimmer_connected

🚘 Library to query the status of your BMW or Mini from the ConnectedDrive portal
Apache License 2.0
365 stars 79 forks source link

Add remote services for EVs #520

Closed rikroe closed 1 year ago

rikroe commented 1 year ago

Breaking change

The RemoteServices.trigger_charge_now() service is renamed to RemoteServices.trigger_charge_start().

Proposed change

Add remote services for electric vehicles:

All remote services have been refactored to include checks against the vehicle capabilities (provided in the state API). If your vehicle does not support a service, a ValueError will be raised. For example, not all EVs will support charging start/stop - it seems that you need an ID8 head unit for this (e.g. i4, i7, iX, but not iX3).

Type of change

Additional information

Checklist

codecov[bot] commented 1 year ago

Codecov Report

Base: 100.00% // Head: 100.00% // No change to project coverage :thumbsup:

Coverage data is based on head (04e2b82) compared to base (3e1bb9e). Patch coverage: 100.00% of modified lines in pull request are covered.

Additional details and impacted files ```diff @@ Coverage Diff @@ ## master #520 +/- ## ========================================== Coverage 100.00% 100.00% ========================================== Files 16 16 Lines 1258 1361 +103 ========================================== + Hits 1258 1361 +103 ``` | Flag | Coverage Δ | | |---|---|---| | 3.10 | `100.00% <100.00%> (ø)` | | | 3.11 | `100.00% <100.00%> (ø)` | | | 3.7 | `100.00% <100.00%> (ø)` | | | 3.8 | `100.00% <100.00%> (ø)` | | | 3.9 | `100.00% <100.00%> (ø)` | | Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=bimmerconnected#carryforward-flags-in-the-pull-request-comment) to find out more. | [Impacted Files](https://codecov.io/gh/bimmerconnected/bimmer_connected/pull/520?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=bimmerconnected) | Coverage Δ | | |---|---|---| | [bimmer\_connected/account.py](https://codecov.io/gh/bimmerconnected/bimmer_connected/pull/520?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=bimmerconnected#diff-YmltbWVyX2Nvbm5lY3RlZC9hY2NvdW50LnB5) | `100.00% <100.00%> (ø)` | | | [bimmer\_connected/api/utils.py](https://codecov.io/gh/bimmerconnected/bimmer_connected/pull/520?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=bimmerconnected#diff-YmltbWVyX2Nvbm5lY3RlZC9hcGkvdXRpbHMucHk=) | `100.00% <100.00%> (ø)` | | | [bimmer\_connected/models.py](https://codecov.io/gh/bimmerconnected/bimmer_connected/pull/520?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=bimmerconnected#diff-YmltbWVyX2Nvbm5lY3RlZC9tb2RlbHMucHk=) | `100.00% <100.00%> (ø)` | | | [bimmer\_connected/vehicle/charging\_profile.py](https://codecov.io/gh/bimmerconnected/bimmer_connected/pull/520?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=bimmerconnected#diff-YmltbWVyX2Nvbm5lY3RlZC92ZWhpY2xlL2NoYXJnaW5nX3Byb2ZpbGUucHk=) | `100.00% <100.00%> (ø)` | | | [bimmer\_connected/vehicle/remote\_services.py](https://codecov.io/gh/bimmerconnected/bimmer_connected/pull/520?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=bimmerconnected#diff-YmltbWVyX2Nvbm5lY3RlZC92ZWhpY2xlL3JlbW90ZV9zZXJ2aWNlcy5weQ==) | `100.00% <100.00%> (ø)` | | | [bimmer\_connected/vehicle/vehicle.py](https://codecov.io/gh/bimmerconnected/bimmer_connected/pull/520?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=bimmerconnected#diff-YmltbWVyX2Nvbm5lY3RlZC92ZWhpY2xlL3ZlaGljbGUucHk=) | `100.00% <100.00%> (ø)` | | Help us with your feedback. Take ten seconds to tell us [how you rate us](https://about.codecov.io/nps?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=bimmerconnected). Have a feature suggestion? [Share it here.](https://app.codecov.io/gh/feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=bimmerconnected)

:umbrella: View full report at Codecov.
:loudspeaker: Do you have feedback about the report comment? Let us know in this issue.

martinarva commented 1 year ago

Probably doesn't work with i3?

rikroe commented 1 year ago

Probably doesn't work with i3?

trigger_charging_profile_update(charging_mode, precondition_climate) should work with all (PH)EVs. From what I understand from #440, you should be able to start/stop charging by working with charing windows and immediate/delayed charging.

kriscremers commented 1 year ago

I've tested all commands (iX driver), but only the trigger_charge_start and trigger_charge_stop succeeded. The start command failed once with the following error, but succeeded after a retry. I'm guessing this is caused by the fact that I triggered a stop command not soon before, IIRC the app gives the user a message then saying it can't process new remote commands yet.

Remote service failed with state 'ERROR'. Response: {'eventStatus': 'ERROR', 'errorDetails': {'title': None, 'description': None, 'presentationType': 'DIALOG', 'iconId': None, 'isRetriable': False, 'errorDetails': 'CHARGING_NOT_STARTED'}}

On the other two I get bad requests using the following commands: result = asyncio.run(vehicle.remote_services.trigger_charging_settings_update(80, 16)) and result = asyncio.run(vehicle.remote_services.trigger_charging_profile_update("DELAYED_CHARGING", "false"))

Is there more information I can provide to help?

kriscremers commented 1 year ago

@rikroe I added some change requests to your PR. The trigger_charging_profile_update was serializing already serialized (json) data. As a result I was getting bad request errors in my tests. After these changes, the trigger succeeds. I'm checking the charging_settings now.

jollyjinx commented 1 year ago

I'm delighted that this pr exists. But as I'm not a proficient in python I tried it out with my BMW i3 no rex and using the commandline: bimmerconnected chargingprofile --charging-mode DELAYED_CHARGING user password region vin

it always responded in a http400 response like this:

DEBUG:asyncio:Using selector: KqueueSelector
DEBUG:bimmer_connected.account:Getting vehicle list
DEBUG:bimmer_connected.account:Getting vehicle list
DEBUG:bimmer_connected.api.authentication:Authenticating with MyBMW flow for North America & Rest of World.
DEBUG:httpx._client:HTTP Request: GET https://cocoapi.bmwgroup.com/eadrax-ucs/v1/presentation/oauth/config "HTTP/1.1 200 OK"
DEBUG:httpx._client:HTTP Request: POST https://customer.bmwgroup.com/gcdm/oauth/authenticate "HTTP/1.1 200 OK"
DEBUG:httpx._client:HTTP Request: POST https://customer.bmwgroup.com/gcdm/oauth/authenticate "HTTP/1.1 302 Found"
DEBUG:httpx._client:HTTP Request: POST https://customer.bmwgroup.com/gcdm/oauth/token "HTTP/1.1 200 OK"
DEBUG:httpx._client:HTTP Request: GET https://cocoapi.bmwgroup.com/eadrax-vcs/v4/vehicles "HTTP/1.1 200 OK"
DEBUG:httpx._client:HTTP Request: GET https://cocoapi.bmwgroup.com/eadrax-vcs/v4/vehicles "HTTP/1.1 200 OK"
DEBUG:httpx._client:HTTP Request: GET https://cocoapi.bmwgroup.com/eadrax-vcs/v4/vehicles/state "HTTP/1.1 200 OK"
DEBUG:httpx._client:HTTP Request: GET https://cocoapi.bmwgroup.com/eadrax-crccs/v2/vehicles?fields=charging-profile&has_charging_settings_capabilities=false "HTTP/1.1 200 OK"
DEBUG:bimmer_connected.vehicle.remote_services:Triggering remote service <Services.CHARGING_PROFILE: 'CHARGING_PROFILE'> with params None and data {"chargingMode": {"chargingPreference": "CHARGING_WINDOW", "endTimeSlot": "0001-01-01T12:00:00.000", "startTimeSlot": "0001-01-01T11:59:00.000", "type": "TIME_SLOT", "timerChange": "NO_CHANGE"}, "departureTimer": {"type": "WEEKLY_DEPARTURE_TIMER", "weeklyTimers": [{"daysOfTheWeek": ["MONDAY"], "id": 1, "time": "0001-01-01T12:00:00.000", "timerAction": "ACTIVATE"}, {"daysOfTheWeek": [], "id": 2, "time": "0001-01-01T18:00:00.000", "timerAction": "DEACTIVATE"}, {"daysOfTheWeek": [], "id": 3, "time": "0001-01-01T15:00:00.000", "timerAction": "DEACTIVATE"}, {"daysOfTheWeek": ["MONDAY"], "id": 4, "time": "0001-01-01T12:00:00.000", "timerAction": "DEACTIVATE"}]}, "isPreconditionForDepartureActive": false, "servicePack": "ATM"}
DEBUG:httpx._client:HTTP Request: POST https://cocoapi.bmwgroup.com/eadrax-crccs/v1/vehicles/WBYXXXXXXXX/charging-profile "HTTP/1.1 400 Bad Request"
Traceback (most recent call last):
  File "/Users/jolly/Desktop/bimmer_connected/./bin/bimmerconnected", line 10, in <module>
    sys.exit(main())
  File "/Users/jolly/Desktop/bimmer_connected/lib/python3.10/site-packages/bimmer_connected/cli.py", line 356, in main
    loop.run_until_complete(args.func(args))
  File "/opt/homebrew/Cellar/python@3.10/3.10.9/Frameworks/Python.framework/Versions/3.10/lib/python3.10/asyncio/base_events.py", line 649, in run_until_complete
    return future.result()
  File "/Users/jolly/Desktop/bimmer_connected/lib/python3.10/site-packages/bimmer_connected/cli.py", line 240, in chargingprofile
    status = await vehicle.remote_services.trigger_charging_profile_update(
  File "/Users/jolly/Desktop/bimmer_connected/lib/python3.10/site-packages/bimmer_connected/vehicle/remote_services.py", line 281, in trigger_charging_profile_update
    return await self.trigger_remote_service(
  File "/Users/jolly/Desktop/bimmer_connected/lib/python3.10/site-packages/bimmer_connected/vehicle/remote_services.py", line 124, in trigger_remote_service
    response = await client.post(
  File "/Users/jolly/Desktop/bimmer_connected/lib/python3.10/site-packages/httpx/_client.py", line 1848, in post
    return await self.request(
  File "/Users/jolly/Desktop/bimmer_connected/lib/python3.10/site-packages/httpx/_client.py", line 1533, in request
    return await self.send(request, auth=auth, follow_redirects=follow_redirects)
  File "/Users/jolly/Desktop/bimmer_connected/lib/python3.10/site-packages/httpx/_client.py", line 1620, in send
    response = await self._send_handling_auth(
  File "/Users/jolly/Desktop/bimmer_connected/lib/python3.10/site-packages/httpx/_client.py", line 1648, in _send_handling_auth
    response = await self._send_handling_redirects(
  File "/Users/jolly/Desktop/bimmer_connected/lib/python3.10/site-packages/httpx/_client.py", line 1706, in _send_handling_redirects
    raise exc
  File "/Users/jolly/Desktop/bimmer_connected/lib/python3.10/site-packages/httpx/_client.py", line 1688, in _send_handling_redirects
    await hook(response)
  File "/Users/jolly/Desktop/bimmer_connected/lib/python3.10/site-packages/bimmer_connected/api/client.py", line 69, in raise_for_status_event_handler
    response.raise_for_status()
  File "/Users/jolly/Desktop/bimmer_connected/lib/python3.10/site-packages/httpx/_models.py", line 749, in raise_for_status
    raise HTTPStatusError(message, request=request, response=self)
httpx.HTTPStatusError: Client error '400 Bad Request' for url 'https://cocoapi.bmwgroup.com/eadrax-crccs/v1/vehicles/WBYXXXXXXXX/charging-profile'
For more information check: https://httpstatuses.com/400

I've tried manually changing the servicePack to TBC1 ( like in the example responses ) in the source code but that did not help. Am I doing something wrong ? Or does it only work in the library not on the command line ?

kriscremers commented 1 year ago

I've tried manually changing the servicePack to TBC1 ( like in the example responses ) in the source code but that did not help. Am I doing something wrong ? Or does it only work in the library not on the command line ?

@jollyjinx that's the same problem I experienced. You need to make the changes I listed above locally in your files. Then when you run the cmdline, it works. (Not sure if I'm allowed or should commit the changes to the codebase myself, hence why I added several change requests.)

I've attached my changes in this zip file: bimmer_connected.zip. Unpack it in the bimmer_connected/bimmer_connected dir, so it can replace the 2 files.

rikroe commented 1 year ago

Thanks @kriscremers for your debugging, really missed something there and pretty sure you found it. I'll review it this evening.

So if you're a little patient, you'll also get an new beta release making it easier to try out.

jollyjinx commented 1 year ago

I've attached my changes in this zip file: bimmer_connected.zip. Unpack it in the bimmer_connected/bimmer_connected dir, so it can replace the 2 files.

@kriscremers I've replaced the files with the ones you provided, but it still has the same error. Neither --charging-mode DELAYED_CHARGING nor --charging-mode IMMEDIATE_CHARGING work.

rikroe commented 1 year ago

Hey @kriscremers thanks again for pointing me to the issue regarding JSON formatting! Implemented it a little bit differently, the magic basically is in 452d541.

I thought this shouldn't be able to occur as I thought I tested the parseability of the requests (and their completeness). However my mocked functions for the new request types were never called, so no parsing done...

This has been fixed as well now.

EDIT: Just released 0.13.0b2, so please try again :)

kriscremers commented 1 year ago

@rikroe aha, I didn't consider that, despite that fact that I often enough run against the same problems in dotnet where json is serialized in the http request call itself as well. This solution looks a lot cleaner than my quick hack, though. 😁

I also just finished testing the entire set of functionality, after having to reset the car's iDrive as it wasn't syncing again. Everything appears to be working straight out of the box as far as my tests show. 👍

rikroe commented 1 year ago

Amazing and thanks for testing! Looks like we're finally close 😁

I'd like to see another test of an EV/PHEV without the full functionality, but then we should be good!

jollyjinx commented 1 year ago

Success! - 👍 with commit 35a15b0 it now works switching between intermediate and delayed mode on my BMW i3 (no rex) Great work , Thanx a lot. I now can prevent charging more than I want on my underground parking garage.

martinarva commented 1 year ago

Great work guys! Will this feature be available in Home Assitant integration as well?

rikroe commented 1 year ago

Great work guys! Will this feature be available in Home Assitant integration as well?

If course! Although it will take at least a month (or more) to get it reviewed and merged. But I do have the basics ready.

StSimmons commented 1 year ago

Confirmed this worked on a Mini Electric too. Thanks! (Notably the charging immediate/delayed)

kriscremers commented 1 year ago

@gerard33 any chance we can see the start/stop charging functionality in the ha_custom_component soon?

rikroe commented 1 year ago

The start/stop charging functionality is available in HA (without custom component) since 2023.5.0 and also in the latest version of the custom component. Check for the select entity.

kriscremers commented 1 year ago

@rikroe I'm running v2023.5.3 but I don't see it available. The only available entities are for the charging limit and mode:

image

I mean the commands trigger_charge_start() and trigger_charge_stop().

gerard33 commented 1 year ago

Looks like your car doesn't support this. My car neither but I can use delayed and immediate charging, which works fine in my case.

kriscremers commented 1 year ago

@gerard33 it does, I've tested and used these commands through python scripts. (See my comments above in this topic.) I can even drive this functionality over the BMW app.

I just did a quick search in the ha component and I can't find the start/stop trigger at all. Which is my point, it seems to be missing?

ha_custom_component: https://github.com/search?q=repo%3Abimmerconnected%2Fha_custom_component%20trigger_charge_start&type=code

bimmer_connected: https://github.com/search?q=repo%3Abimmerconnected%2Fbimmer_connected+trigger_charge_start&type=code

rikroe commented 1 year ago

I think we never added it because we received feedback somewhere that it is not working properly. But that was before we understood more on the capabilities.

As soon as https://github.com/home-assistant/core/pull/92962 gets merged, we probably can also provide a switch entity to HA for those triggering charging start/stop.

kriscremers commented 1 year ago

@rikroe that would be great. The start/stop would make it easier to work with dynamic pricing tariffs along with solar energy production.

rikroe commented 1 year ago

For now, you should be able to adjust that based on the charging mode select: DELAYED_CHARGING means don't charge if you don't have a charging plan for this moment.

kriscremers commented 1 year ago

@rikroe that isn't something I plan on using, since I don't only charge at home. If I start using delayed charging I'll need to switch every time I charge on the go, plus I'd still be unable to set the window to charge in.

Edit: just remembered I can call the start/stop functionality through python as well. Will give that a whirl for now.

rikroe commented 1 year ago

The switch entity for charging on/off is included in 2023.6. If there are any problems, please open a new issue.