SAIC-iSmart-API / saic-python-mqtt-gateway

A service that queries the data from an MG iSMART account and publishes the data over MQTT and to other sources
MIT License
71 stars 21 forks source link

Feature: Expose find my car functionality #264

Open jkrenzer opened 1 week ago

jkrenzer commented 1 week ago

Hi!

It would be really helpful if the find my car feature of the app would also be available through MQTT. As a quick search turned up, it seems this functionality is already implemented in the python client lib.

Thanks!

Jörn

tosate commented 1 week ago

Hi @jkrenzer

the location information of your car is available through MQTT. You find everything with the topics saic/<username>/vehicles/<car ID>/location/[speed, heading, longitude, latitude, elevation, position]

Have a nive day! Thomas

jkrenzer commented 1 week ago

That is not what I meant. Sorry I did not make it clearer. I thought about the light flashing/horn honking feature to locate the vehicle in a parking lot. See:

https://youtu.be/D9YePbYduMM?si=P29gCFYq4NGEd5bn&t=32

There is also code referencing this feature obviously in the python client:

https://github.com/SAIC-iSmart-API/saic-python-client-ng/blob/a6b62feecf06451207825288749637e60e355da7/src/saic_ismart_client_ng/api/vehicle/__init__.py#L35

tosate commented 1 week ago

Hi @jkrenzer

you are right! This functionality is not yet exposed on MQTT. Sounds like a nice little task for the upcoming holidays. I will give it a try to implement it

nanomad commented 1 week ago

@tosate on the HA side you can expose it as a button:

https://www.home-assistant.io/integrations/button.mqtt/

tosate commented 1 week ago

Hi @jkrenzer

maybe you can help me a bit to design this feature. How would you expect the MQTT topic?

The default would be to activate the horn and the lights. From the API, it might be possible to activate only the horn or only the lights. So would you prefer to be able to configure the feature and just use it or would you want to pass values on demand to a setter (hornAndLights, hornOnly, lightsOnly, stop)?

jkrenzer commented 1 week ago

Hi @tosate

II would second putting it in saic/<username>/vehicles/<car ID>/location/findMyCar as it would reflect the original user-flow of the app quite well. And regarding the different modes I also think it would be best to default to horn + lights for the default (like the app) but make it possible to choose differently if required.

Thanks for the effort!

Jörn

tosate commented 1 week ago

Thanks for your feedback @jkrenzer I have implemented it like this, but I couldn’t test it yet because I didn’t want to wake someone up yesterday evening.

tosate commented 1 week ago

My first test was successful, but the MG ZS activates the lights only. However, it shows the same behavior if I use the app.

Unfortunately, the command leads to a httpcore.ReadTimeout

@nanomad Maybe the delay for the send_vehicle_control_command is a bit short?

2024-10-07 14:44:35,117 [ ERROR  ] Not retrying ()  - saic_ismart_client_ng.api.base
2024-10-07 14:44:35,118 [ ERROR  ] Error updating charge status - handlers.vehicle
Traceback (most recent call last):
  File "/usr/local/lib/python3.12/site-packages/httpx/_transports/default.py", line 72, in map_httpcore_exceptions
    yield
  File "/usr/local/lib/python3.12/site-packages/httpx/_transports/default.py", line 377, in handle_async_request
    resp = await self._pool.handle_async_request(req)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/httpcore/_async/connection_pool.py", line 216, in handle_async_request
    raise exc from None
  File "/usr/local/lib/python3.12/site-packages/httpcore/_async/connection_pool.py", line 196, in handle_async_request
    response = await connection.handle_async_request(
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/httpcore/_async/connection.py", line 101, in handle_async_request
    return await self._connection.handle_async_request(request)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/httpcore/_async/http11.py", line 143, in handle_async_request
    raise exc
  File "/usr/local/lib/python3.12/site-packages/httpcore/_async/http11.py", line 113, in handle_async_request
    ) = await self._receive_response_headers(**kwargs)
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/httpcore/_async/http11.py", line 186, in _receive_response_headers
    event = await self._receive_event(timeout=timeout)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/httpcore/_async/http11.py", line 224, in _receive_event
    data = await self._network_stream.read(
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/httpcore/_backends/anyio.py", line 32, in read
    with map_exceptions(exc_map):
         ^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/contextlib.py", line 158, in __exit__
    self.gen.throw(value)
  File "/usr/local/lib/python3.12/site-packages/httpcore/_exceptions.py", line 14, in map_exceptions
    raise to_exc(exc) from exc
httpcore.ReadTimeout

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/usr/src/app/handlers/vehicle.py", line 123, in __polling
    charge_status = await self.update_charge_status()
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/src/app/handlers/vehicle.py", line 180, in update_charge_status
    charge_mgmt_data = await self.saic_api.get_vehicle_charging_management_data(self.vin_info.vin)
                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/saic_ismart_client_ng/api/vehicle_charging/__init__.py", line 24, in get_vehicle_charging_management_data
    return await self.execute_api_call_with_event_id(
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/saic_ismart_client_ng/api/base.py", line 123, in execute_api_call_with_event_id
    return await execute_api_call_with_event_id_inner(event_id='0')
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/tenacity/asyncio/__init__.py", line 189, in async_wrapped
    return await copy(fn, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/tenacity/asyncio/__init__.py", line 111, in __call__
    do = await self.iter(retry_state=retry_state)
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/tenacity/asyncio/__init__.py", line 153, in iter
    result = await action(retry_state)
             ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/tenacity/_utils.py", line 99, in inner
    return call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/tenacity/__init__.py", line 398, in <lambda>
    self._add_action_func(lambda rs: rs.outcome.result())
                                     ^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/concurrent/futures/_base.py", line 449, in result
    return self.__get_result()
           ^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/concurrent/futures/_base.py", line 401, in __get_result
    raise self._exception
  File "/usr/local/lib/python3.12/site-packages/tenacity/asyncio/__init__.py", line 114, in __call__
    result = await fn(*args, **kwargs)
             ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/saic_ismart_client_ng/api/base.py", line 114, in execute_api_call_with_event_id_inner
    return await self.execute_api_call(
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/saic_ismart_client_ng/api/base.py", line 90, in execute_api_call
    response = await self.api_client.client.send(req)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/httpx/_client.py", line 1674, in send
    response = await self._send_handling_auth(
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/httpx/_client.py", line 1702, in _send_handling_auth
    response = await self._send_handling_redirects(
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/httpx/_client.py", line 1739, in _send_handling_redirects
    response = await self._send_single_request(request)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/httpx/_client.py", line 1776, in _send_single_request
    response = await transport.handle_async_request(request)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/httpx/_transports/default.py", line 376, in handle_async_request
    with map_httpcore_exceptions():
         ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/contextlib.py", line 158, in __exit__
    self.gen.throw(value)
  File "/usr/local/lib/python3.12/site-packages/httpx/_transports/default.py", line 89, in map_httpcore_exceptions
    raise mapped_exc(message) from exc
httpx.ReadTimeout
jkrenzer commented 1 week ago

My first test was successful, but the MG ZS activates the lights only. However, it shows the same behavior if I use the app.

This is probably car and/or region-specific. But anyways really nice, thanks!

nanomad commented 1 week ago

@nanomad Maybe the delay for the send_vehicle_control_command is a bit short?

The timeout raised there it's the time to first byte. Default is 5s. I'll add a retry policy to that error as well

nanomad commented 1 week ago

@jkrenzer @tosate next RC will contain this

jkrenzer commented 1 week ago

My first test was successful, but the MG ZS activates the lights only. However, it shows the same behavior if I use the app.

FYI: I found a setting in Car settings > Comfort on our MG ZS EV where you can choose the reaction to find-my-vehicle to either be lights or horn+lights. If this sets the option persistently or just regulates horn-access I do not know yet.

@jkrenzer @tosate next RC will contain this

Thanks!