jhansche / ha-teslafi

Home Assistant integration for TeslaFi-monitored vehicles
MIT License
13 stars 0 forks source link

Climate fails to turn on when vehicle is asleep #39

Closed nicolasbuch closed 2 months ago

nicolasbuch commented 5 months ago

I'm having issues with turning on the climate when the vehicle is asleep. HA shows an error and usually I can just wait a few seconds (until the vehicle wakes up) and try it again successfully.

Expected behaviour should be to wake up the vehicle when it is asleep and then send the auto_conditioning_start command. It seems like we get a 500 error back with the message: "vehicle is offline or asleep".

Have you encountered this problem before?

Se attached log below:

2024-05-16 23:58:08.636 WARNING (MainThread) [custom_components.teslafi.client] Error reading as json: FleetApi::auto_conditioning_start - 500 From Tesla:: {"response":null,"error":"vehicle unavailable: vehicle is offline or asleep","error_description":""} Traceback (most recent call last): File "/config/custom_components/teslafi/client.py", line 67, in _request data = response.json() ^^^^^^^^^^^^^^^ File "/usr/local/lib/python3.12/site-packages/httpx/_models.py", line 764, in json return jsonlib.loads(self.content, kwargs) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/local/lib/python3.12/json/init.py", line 346, in loads return _default_decoder.decode(s) ^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/local/lib/python3.12/json/decoder.py", line 337, in decode obj, end = self.raw_decode(s, idx=_w(s, 0).end()) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/local/lib/python3.12/json/decoder.py", line 355, in raw_decode raise JSONDecodeError("Expecting value", s, err.value) from None json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0) 2024-05-16 23:58:08.643 ERROR (MainThread) [homeassistant.components.websocket_api.http.connection] [140523722869312] Unexpected exception Traceback (most recent call last): File "/usr/src/homeassistant/homeassistant/components/websocket_api/commands.py", line 239, in handle_call_service response = await hass.services.async_call( ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/src/homeassistant/homeassistant/core.py", line 2738, in async_call response_data = await coro ^^^^^^^^^^ File "/usr/src/homeassistant/homeassistant/core.py", line 2779, in _execute_service return await target(service_call) ^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/src/homeassistant/homeassistant/helpers/service.py", line 975, in entity_service_call single_response = await _handle_entity_call( ^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/src/homeassistant/homeassistant/helpers/service.py", line 1047, in _handle_entity_call result = await task ^^^^^^^^^^ File "/config/custom_components/teslafi/climate.py", line 153, in async_set_hvac_mode await self.coordinator.execute_command(cmd) File "/config/custom_components/teslafi/coordinator.py", line 51, in execute_command return await self._client.command(cmd, kwargs) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/config/custom_components/teslafi/client.py", line 44, in command return await self._request(cmd, kwargs) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/config/custom_components/teslafi/client.py", line 74, in _request raise exc File "/config/custom_components/teslafi/client.py", line 67, in _request data = response.json() ^^^^^^^^^^^^^^^ File "/usr/local/lib/python3.12/site-packages/httpx/_models.py", line 764, in json return jsonlib.loads(self.content, kwargs) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/local/lib/python3.12/json/init.py", line 346, in loads return _default_decoder.decode(s) ^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/local/lib/python3.12/json/decoder.py", line 337, in decode obj, end = self.raw_decode(s, idx=_w(s, 0).end()) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/local/lib/python3.12/json/decoder.py", line 355, in raw_decode raise JSONDecodeError("Expecting value", s, err.value) from None json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)

jhansche commented 3 months ago

Sorry for the delay..

We have been trying to account for that here, and handling it by sending the wake command: https://github.com/jhansche/ha-teslafi/blob/647972209af37bfb99e1c17cec84b830a6e4d9bd/custom_components/teslafi/client.py#L68

But it looks like the TeslaFi response has changed:

Error reading as json: FleetApi::auto_conditioning_start - 500 From Tesla:: {"response":null,"error":"vehicle unavailable: vehicle is offline or asleep","error_description":""}

This means the json data is not returned properly, and instead TeslaFi is reporting the error with plain text prepended, and that plaintext string has changed. Looks like we can fix this by changing the startwith check to a contains/substring check.

Ultimately TeslaFi is wrong to return non-json here 🤦‍♂️ they should instead be encapsulating the upstream error and embedding that into the json error response.

avhm commented 2 months ago

Just adding a +1 to this, I'm seeing the same error when attempting to start a charge while the car is asleep.

Traceback (most recent call last):
  File "/usr/src/homeassistant/homeassistant/components/automation/__init__.py", line 755, in async_trigger
    return await self.action_script.async_run(
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/src/homeassistant/homeassistant/helpers/script.py", line 1799, in async_run
    return await asyncio.shield(create_eager_task(run.async_run()))
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/src/homeassistant/homeassistant/helpers/script.py", line 463, in async_run
    await self._async_step(log_exceptions=False)
  File "/usr/src/homeassistant/homeassistant/helpers/script.py", line 527, in _async_step
    self._handle_exception(
  File "/usr/src/homeassistant/homeassistant/helpers/script.py", line 557, in _handle_exception
    raise exception
  File "/usr/src/homeassistant/homeassistant/helpers/script.py", line 525, in _async_step
    await getattr(self, handler)()
  File "/usr/src/homeassistant/homeassistant/helpers/script.py", line 1075, in _async_if_step
    await self._async_run_script(if_data["if_then"])
  File "/usr/src/homeassistant/homeassistant/helpers/script.py", line 1269, in _async_run_script
    result = await self._async_run_long_action(
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/src/homeassistant/homeassistant/helpers/script.py", line 727, in _async_run_long_action
    return await long_task
           ^^^^^^^^^^^^^^^
  File "/usr/src/homeassistant/homeassistant/helpers/script.py", line 1799, in async_run
    return await asyncio.shield(create_eager_task(run.async_run()))
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/src/homeassistant/homeassistant/helpers/script.py", line 463, in async_run
    await self._async_step(log_exceptions=False)
  File "/usr/src/homeassistant/homeassistant/helpers/script.py", line 527, in _async_step
    self._handle_exception(
  File "/usr/src/homeassistant/homeassistant/helpers/script.py", line 557, in _handle_exception
    raise exception
  File "/usr/src/homeassistant/homeassistant/helpers/script.py", line 525, 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(
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  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 2763, in async_call
    response_data = await coro
                    ^^^^^^^^^^
  File "/usr/src/homeassistant/homeassistant/core.py", line 2806, in _execute_service
    return await target(service_call)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/src/homeassistant/homeassistant/helpers/service.py", line 999, in entity_service_call
    single_response = await _handle_entity_call(
                      ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/src/homeassistant/homeassistant/helpers/service.py", line 1071, in _handle_entity_call
    result = await task
             ^^^^^^^^^^
  File "/config/custom_components/teslafi/switch.py", line 51, in async_turn_on
    await self.entity_description.cmd(self.coordinator, True)
  File "/config/custom_components/teslafi/coordinator.py", line 51, in execute_command
    return await self._client.command(cmd, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/config/custom_components/teslafi/client.py", line 44, in command
    return await self._request(cmd, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/config/custom_components/teslafi/client.py", line 74, in _request
    raise exc
  File "/config/custom_components/teslafi/client.py", line 67, in _request
    data = response.json()
           ^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/httpx/_models.py", line 764, in json
    return jsonlib.loads(self.content, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/json/__init__.py", line 346, in loads
    return _default_decoder.decode(s)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/json/decoder.py", line 337, in decode
    obj, end = self.raw_decode(s, idx=_w(s, 0).end())
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/json/decoder.py", line 355, in raw_decode
    raise JSONDecodeError("Expecting value", s, err.value) from None
json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)
avhm commented 2 months ago

I have a workaround that's functional: The automation checks for the sleep state, wakes the car if needed, then begin the charge once it's confirmed as awake

...
action:
  - if:
      - condition: state
        entity_id: binary_sensor.octopus_energy_target_car_charging_cheap
        state: "on"
    then:
      - if:
          - condition: state
            entity_id: sensor.tesla_car_state
            state: sleeping
        then:
          - device_id: 0ed14f92e3ab3e1ddd296384ba04857a // button.tesla_wake_up
            domain: button
            entity_id: 5fb1417e4027b186d67ca8e783622d05
            type: press
          - wait_for_trigger:
              - platform: state
                entity_id:
                  - sensor.tesla_car_state
                from: sleeping
                to: null
          - action: switch.turn_on
            metadata: {}
            data: {}
            target:
              entity_id: switch.tesla_charging
        else:
          - action: switch.turn_on
            metadata: {}
            data: {}
            target:
              entity_id: switch.tesla_charging

Unfortunately, wait_for_trigger seems to fire around 10 minutes after the wake press, so it misses the start window by 10 mins every time, but at least it works.

It seems that polling takes quite a while to update the state. Is there a way to increase the polling frequency? Or perhaps trigger a poll manually within the automation?

nicolasbuch commented 2 months ago

Sorry for the delay..

We have been trying to account for that here, and handling it by sending the wake command:

https://github.com/jhansche/ha-teslafi/blob/647972209af37bfb99e1c17cec84b830a6e4d9bd/custom_components/teslafi/client.py#L68

But it looks like the TeslaFi response has changed:

Error reading as json: FleetApi::auto_conditioning_start - 500 From Tesla:: {"response":null,"error":"vehicle unavailable: vehicle is offline or asleep","error_description":""}

This means the json data is not returned properly, and instead TeslaFi is reporting the error with plain text prepended, and that plaintext string has changed. Looks like we can fix this by changing the startwith check to a contains/substring check.

Ultimately TeslaFi is wrong to return non-json here 🤦‍♂️ they should instead be encapsulating the upstream error and embedding that into the json error response.

@jhansche I spend some time looking into your suggestion and changed the startswith to contains, but it still does not work.

I'm not a python developer, but as far as I can tell this check just throws a VehicleNotReadyError which is not caught anywhere. I might be wrong, but this does not seem like the right place to fix it.

Do you have the time to take a look or point me in the right direction here? I might find some time to look into it again if you help me understand the problem.

jhansche commented 2 months ago

You're right - that would only surface the error in a more user-friendly way.

In coordinator.execute_command(), we are automatically adding wake=10 to the command parameters, if the car is currently sleeping. That is a TeslaFi API feature, and it should be enough to cause the car to wake up:

Adding &wake=X after a command will send a wake command and then pause for X seconds before sending the command if the vehicle is sleeping. Usually 10-15 seconds is enough time for the vehicle to wake up. Maximum time is 60 seconds.

Can you tell if the error is raised immediately, or does it hang for 10 seconds before the error?

If you don't do anything -- that is, you try the command, get the "offline or asleep" error -- does the car eventually perform the requested action? Like, maybe after a minute or two? If not, does it at least wake up on its own?

If the car eventually responds to the command despite the TeslaFi API error, then we might just need to suppress the error while we wait for the car to wake up.

If the car does not respond, and the request waits for those ~10 seconds before responding with an error, then maybe the DELAY_CMD_WAKE delay is too conservative and we might need to increase that delay.

avhm commented 2 months ago

Can you tell if the error is raised immediately, or does it hang for 10 seconds before the error?

When toggling charge / setting charge target with the car asleep, the error is returned immediately.

The car does wake from sleep, but the intended action doesn't ever run.

I've noticed that the wake time can vary quite a bit - everything from a couple of seconds to well beyond 1 minute. Setting a timeout feels a bit fragile with that in mind, might be better to wake the car, wait for confirmation, then set the desired actions.

jhansche commented 2 months ago

Right, that should be the intent behind TeslaFi's wake= parameter. But clearly that is not working correctly currently.

We could work around it in the integration, but I wonder if this should be a bug report to TeslaFi instead... Possibly even a feature request to replace the wake-delay parameter with a simple "wake if needed and send the command whenever it wakes up", so that we don't have to guess how long a typical wake should take. Even if the TeslaFi wake parameter worked correctly, it sounds like it would impose the requested delay regardless of how long it actually takes for it to wake up

nicolasbuch commented 2 months ago

@jhansche I looked into the "api documentation" over at teslafi.com and it does indeed seem like the wake command is currently broken.

Sending a &command=auto_conditioning_start&wake=10 returns the same 500 error as we see here in the logs after just a few seconds. It does wake the vehicle as @avhm pointed out - but the original command is never executed.

I think that this is a TeslaFi issue. Will try to contact them and see what they say. I'll keep you posted here.

nicolasbuch commented 2 months ago

It was indeed a problem over at teslafi and it seems like they fixed it now. It's working as intended for me at least.

Response from James:

Hi,

That should be corrected now. The only exception is if a users firmware reports the car as offline instead of asleep it won't trigger the time to wait. These users will need to enable show offline as asleep in sleet settings for it to work as expected. Sleep Sessions No Longer Showing

As he points out there might be a case where some firmware versions report the vehicle as offline instead of asleep. In this case the command will not be able to wake the car. Might be something we should handle somewhere or at the very least document.

What's your view on this @jhansche?

jhansche commented 2 months ago

Awesome, thanks for checking on that.

My view is we shouldn't go out of our way to work around issues with TeslaFi or Fleet API or specific car models and versions.

If there is some intricacy with how Home Assistant works with certain scenarios, then I can make a case for handling that in the integration -- example being how we need to wait for TeslaFi to reflect the new status for an unlock command or climate command. In those cases, I schedule a refresh after a short delay, and try to suppress the state flapping in the meantime. Without that, you would send the unlock command, it would show unlocked, and then a few seconds later switch back to locked again because TeslaFi hasn't updated its status yet, and then a minute or two later switch to unlocked once it is reflected.

So, with that in mind, I don't think we should work around the exception that James described.

nicolasbuch commented 2 months ago

That makes sense. Well, seems like we can close this one.

Thanks for the assistance 😄

avhm commented 2 weeks ago

Unfortunately, this issue seems to have returned, and the API is responding with vehicle unavailable: vehicle is offline or asleep again.

nicolasbuch commented 2 weeks ago

Still works fine on my end. Could be your vehicle reporting as offline rather than asleep depending on model/firmware. Try to enable "show offline as asleep" in the teslafi sleep settings and see if it fixes the problem.

avhm commented 2 weeks ago

Interesting that it's still working for you, I already had the offline / sleep toggle enabled. It's been failing for roughly 5 days (looking at the logs when the automations began failing)

I haven't changed anything in the setup during this time.

This seems to be the case for every change requested (set charge level etc) while the car is asleep.

nicolasbuch commented 2 weeks ago

Try to call the API manually to see what response you get.

This error caused the API to return a 500 response when we tried to wake the vehicle with the request &command=auto_conditioning_start&wake=10. The problem was faulty endpoint behaviour from teslafi.

Since I can still wake my vehicle with this command i guess the problem you are experiencing is different.

avhm commented 1 week ago

Thought I'd update here - this started working again not long after my last post, and hasn't happened since then, so I can only assume it was an issue on TeslaFi's side, or perhaps a timeout issue (e.g 10s wasn't long enough) - As the car is on cellular, this might be more variable in my situation.

If it does happen again, I'll try the manual API call again with a longer timeout and see if this fixes the issue.