Skip to content

Commit

Permalink
Add client side ping to address latest MB-server changes
Browse files Browse the repository at this point in the history
  • Loading branch information
ReneNulschDE committed Apr 4, 2024
1 parent f5daf4b commit c943d28
Show file tree
Hide file tree
Showing 4 changed files with 72 additions and 19 deletions.
7 changes: 5 additions & 2 deletions custom_components/mbapi2020/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,14 +86,17 @@ def __init__(
self.config_entry = config_entry
self._locale: str = DEFAULT_LOCALE
self._country_code: str = DEFAULT_COUNTRY_CODE
self.session_id = str(uuid.uuid4()).upper()

self.oauth: Oauth = Oauth(
self._hass,
session=session,
region=self._region,
config_entry=config_entry,
)
self.oauth.session_id = self.session_id
self.webapi: WebApi = WebApi(self._hass, session=session, oauth=self.oauth, region=self._region)
self.webapi.session_id = self.session_id
self.websocket: Websocket = Websocket(self._hass, self.oauth, region=self._region)
self.cars: dict[str, Car] = {}

Expand Down Expand Up @@ -235,7 +238,7 @@ def on_data(data):
LOGGER.error(
"Error with the websocket connection (retry counter: %s): %s", ws_connect_retry_counter, err
)
ws_connect_retry_counter += 1
ws_connect_retry_counter = ws_connect_retry_counter + 1
except Exception as err:
if self.websocket._is_stopping:
stop_retry_loop = True
Expand All @@ -246,7 +249,7 @@ def on_data(data):
ws_connect_retry_counter,
err,
)
ws_connect_retry_counter += 1
ws_connect_retry_counter = ws_connect_retry_counter + 1
if ws_connect_retry_counter > 10:
LOGGER.error(
"Retry counter: %s - Giving up and initiate component reload.", ws_connect_retry_counter
Expand Down
11 changes: 6 additions & 5 deletions custom_components/mbapi2020/const.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Constants for the MercedesME 2020 integration."""

from __future__ import annotations

from datetime import timedelta
Expand Down Expand Up @@ -81,10 +82,10 @@
RIS_APPLICATION_VERSION_NA = "3.40.0"
RIS_APPLICATION_VERSION_CN = "1.39.0"
RIS_APPLICATION_VERSION_PA = "1.40.0"
RIS_APPLICATION_VERSION = "1.41.0"
RIS_SDK_VERSION = "2.113.0"
RIS_APPLICATION_VERSION = "1.42.0 (2168)"
RIS_SDK_VERSION = "2.114.0"
RIS_SDK_VERSION_CN = "2.109.2"
RIS_OS_VERSION = "17.3"
RIS_OS_VERSION = "17.4.1"
RIS_OS_NAME = "ios"
X_APPLICATIONNAME = "mycar-store-ece"
X_APPLICATIONNAME_ECE = "mycar-store-ece"
Expand All @@ -94,7 +95,7 @@

USE_PROXY = False
VERIFY_SSL = True
SYSTEM_PROXY: str | None = None if not USE_PROXY else "http://0.0.0.0:8080"
SYSTEM_PROXY: str | None = None if not USE_PROXY else "http://0.0.0.0:20000"


LOGIN_APP_ID = "01398c1c-dc45-4b42-882b-9f5ba9f175f1"
Expand All @@ -116,7 +117,7 @@
WEBSOCKET_API_BASE_NA = "wss://websocket.amap-prod.mobilesdk.mercedes-benz.com/ws"
WEBSOCKET_API_BASE_PA = "wss://websocket.amap-prod.mobilesdk.mercedes-benz.com/ws"
WEBSOCKET_API_BASE_CN = "wss://websocket.cn-prod.mobilesdk.mercedes-benz.com/ws"
WEBSOCKET_USER_AGENT = f"MyCar/{RIS_APPLICATION_VERSION} (com.daimler.ris.mercedesme.ece.ios; {RIS_OS_NAME} {RIS_OS_VERSION}) Alamofire/5.4.0"
WEBSOCKET_USER_AGENT = "MyCar/2168 CFNetwork/1494.0.7 Darwin/23.4.0"
WEBSOCKET_USER_AGENT_CN = "MyStarCN/1.39.0 (com.daimler.ris.mercedesme.cn.ios; build:1758; iOS 16.3.1) Alamofire/5.4.0"
WEBSOCKET_USER_AGENT_PA = (
f"mycar-store-ap v{RIS_APPLICATION_VERSION}, {RIS_OS_NAME} {RIS_OS_VERSION}, SDK {RIS_SDK_VERSION}"
Expand Down
6 changes: 4 additions & 2 deletions custom_components/mbapi2020/webapi.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Define an object to interact with the REST API."""

from __future__ import annotations

import json
Expand Down Expand Up @@ -47,6 +48,7 @@ def __init__(
self._oauth: Oauth = oauth
self._region = region
self.hass = hass
self.session_id = str(uuid.uuid4()).upper()

async def _request(
self,
Expand All @@ -67,8 +69,8 @@ async def _request(
if not rcp_headers:
kwargs["headers"] = {
"Authorization": f"Bearer {token['access_token']}",
"X-SessionId": str(uuid.uuid4()),
"X-TrackingId": str(uuid.uuid4()),
"X-SessionId": self.session_id,
"X-TrackingId": str(uuid.uuid4()).upper(),
"X-ApplicationName": X_APPLICATIONNAME,
"ris-application-version": RIS_APPLICATION_VERSION,
"ris-os-name": "ios",
Expand Down
67 changes: 57 additions & 10 deletions custom_components/mbapi2020/websocket.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,39 @@ async def trigger(self):
self._timer_task = self._loop.call_later(self._timeout, lambda: asyncio.create_task(self.on_expire()))


class WebsocketPingWatcher:
"""Define a watchdog to ping the websocket connection at intervals."""

def __init__(
self,
action: Callable[..., Awaitable],
*,
timeout_seconds: int = DEFAULT_WATCHDOG_TIMEOUT,
):
"""Initialize."""
self._action: Callable[..., Awaitable] = action
self._loop = asyncio.get_event_loop()
self._timer_task: Optional[asyncio.TimerHandle] = None
self._timeout: int = 15

def cancel(self):
"""Cancel the watchdog."""
if self._timer_task:
self._timer_task.cancel()
self._timer_task = None

async def on_expire(self):
"""Log and act when the watchdog expires."""
await self._action()

async def trigger(self):
"""Trigger the watchdog."""
if self._timer_task:
self._timer_task.cancel()

self._timer_task = self._loop.call_later(self._timeout, lambda: asyncio.create_task(self.on_expire()))


class Websocket:
"""Define the websocket."""

Expand All @@ -89,7 +122,9 @@ def __init__(self, hass, oauth, region) -> None:
self.connection_state = "unknown"
self.is_connecting = False
self._watchdog: WebsocketWatchdog = WebsocketWatchdog(self.initiatiate_connection_reset)
self._pingwatchdog: WebsocketPingWatcher = WebsocketPingWatcher(self.ping)
self._queue = asyncio.Queue()
self.session_id = str(uuid.uuid4()).upper()

async def async_connect(self, on_data) -> None:
"""Connect to the socket."""
Expand Down Expand Up @@ -118,6 +153,7 @@ async def async_stop(self):
"""Close connection."""
self._is_stopping = True
self._watchdog.cancel()
self._pingwatchdog.cancel()
if self._connection is not None:
await self._connection.close()

Expand All @@ -126,6 +162,14 @@ async def initiatiate_connection_reset(self):
if self._connection is not None:
await self._connection.close()

async def ping(self):
"""Send a ping to the MB websocket servers."""
try:
await self._connection.ping()
await self._pingwatchdog.trigger()
except client_exceptions.ClientError as err:
LOGGER.error("remote websocket connection closed: %s", err)

async def call(self, message):
"""Send a message to the MB websocket servers."""
try:
Expand Down Expand Up @@ -199,27 +243,30 @@ async def _websocket_handler(self, session: ClientSession):

self.connection_state = STATE_CONNECTED

if msg.type in (
WSMsgType.CLOSED,
WSMsgType.ERROR,
):
if msg.type == WSMsgType.CLOSED:
LOGGER.info("websocket connection is closing")
break
elif msg.type == WSMsgType.ERROR:
LOGGER.info("websocket connection is closing - message type error.")
break
elif msg.type == WSMsgType.BINARY:
self._queue.put_nowait(msg.data)
await self._watchdog.trigger()
await self._pingwatchdog.trigger()

async def _websocket_connection_headers(self):
token = await self.oauth.async_get_cached_token()
header = {
"Authorization": token["access_token"],
"X-SessionId": str(uuid.uuid4()),
"X-TrackingId": str(uuid.uuid4()),
"ris-os-name": RIS_OS_NAME,
"ris-os-version": RIS_OS_VERSION,
"ris-sdk-version": RIS_SDK_VERSION,
"X-Locale": "en-US",
"X-SessionId": self.session_id,
"X-TrackingId": str(uuid.uuid4()).upper(),
"RIS-OS-Name": RIS_OS_NAME,
"RIS-OS-Version": RIS_OS_VERSION,
"ris-websocket-type": "ios-native",
"RIS-SDK-Version": RIS_SDK_VERSION,
"X-Locale": "de-DE",
"User-Agent": WEBSOCKET_USER_AGENT,
"Accept-Language": " de-DE,de;q=0.9",
}

header = self._get_region_header(header)
Expand Down

0 comments on commit c943d28

Please sign in to comment.