From aa0ad7c8ab453e0e0d13734e0fcfd2023a8b1446 Mon Sep 17 00:00:00 2001 From: Tom Scholten Date: Tue, 19 Apr 2022 21:47:20 +0200 Subject: [PATCH 1/3] Add v4 --- custom_components/stromer/config_flow.py | 2 +- custom_components/stromer/strings.json | 2 +- custom_components/stromer/stromer.py | 30 ++++++++++++++----- .../stromer/translations/en.json | 2 +- .../stromer/translations/nl.json | 2 +- 5 files changed, 27 insertions(+), 11 deletions(-) diff --git a/custom_components/stromer/config_flow.py b/custom_components/stromer/config_flow.py index efa243c..53d030b 100644 --- a/custom_components/stromer/config_flow.py +++ b/custom_components/stromer/config_flow.py @@ -19,7 +19,7 @@ vol.Required(CONF_USERNAME): str, vol.Required(CONF_PASSWORD): str, vol.Required(CONF_CLIENT_ID): str, - vol.Required(CONF_CLIENT_SECRET): str, + vol.Optional(CONF_CLIENT_SECRET): str = None, } ) diff --git a/custom_components/stromer/strings.json b/custom_components/stromer/strings.json index 7c6d988..baccae9 100644 --- a/custom_components/stromer/strings.json +++ b/custom_components/stromer/strings.json @@ -8,7 +8,7 @@ "password": "Password", "username" : "Username", "client_id": "Client ID", - "client_secret": "Client Secret" + "client_secret": "Client Secret (optional, not needed if you client id starts with 4P" } } }, diff --git a/custom_components/stromer/stromer.py b/custom_components/stromer/stromer.py index bf95e9a..573822c 100644 --- a/custom_components/stromer/stromer.py +++ b/custom_components/stromer/stromer.py @@ -1,6 +1,6 @@ """Stromer module for Home Assistant Core.""" -__version__ = "0.0.6" +__version__ = "0.0.7" import json import logging @@ -19,6 +19,10 @@ def __init__(self, username, password, client_id, client_secret, timeout=60): self.bike = {} self.status = {} self.position = {} + + self._api_version = 'v4' + if client_secret: + self._api_version = 'v3' self.base_url = "https://api3.stromer-portal.ch" self._timeout = timeout @@ -85,7 +89,9 @@ async def stromer_update(self): LOGGER.debug("Stromer retry: {}/5".format(attempts)) async def stromer_get_code(self): - url = f"{self.base_url}/users/login/" + url = f"{self.base_url}/mobile/v4/login/" + if self._api_version == 'v3': + url = f"{self.base_url}/users/login/" res = await self._websession.get(url) cookie = res.headers.get("Set-Cookie") pattern = "=(.*?);" @@ -104,9 +110,12 @@ async def stromer_get_code(self): "password": self._password, "username": self._username, "csrfmiddlewaretoken": csrftoken, - "next": "/o/authorize/?" + qs, + "next": "/mobile/v4/o/authorize/?" + qs } + if self._api_version == 'v3': + data["next"]= "/o/authorize/?" + qs + res = await self._websession.post( url, data=data, headers=dict(Referer=url), allow_redirects=False ) @@ -117,21 +126,28 @@ async def stromer_get_code(self): self._code = self._code.split("=")[1] async def stromer_get_access_token(self): - url = f"{self.base_url}/o/token/" + url = f"{self.base_url}/mobile/v4/o/token/" data = { "grant_type": "authorization_code", "client_id": self._client_id, - "client_secret": self._client_secret, "code": self._code, - "redirect_uri": "stromerauth://auth", + "redirect_uri": "stromer://auth", } + if self._api_version == 'v3': + url = f"{self.base_url}/o/token/" + data["client_secret"] = self._client_secret, + data["redirect_uri"] = "stromerauth://auth" + res = await self._websession.post(url, data=data) token = json.loads(await res.text()) self._token = token["access_token"] async def stromer_call_api(self, endpoint, data={}): - url = f"{self.base_url}/rapi/mobile/v2/{endpoint}" + url = f"{self.base_url}/rapi/mobile/v4.1/{endpoint}" + if self._api_version == 'v3': + url = f"{self.base_url}/rapi/mobile/v2/{endpoint}" + headers = {"Authorization": f"Bearer {self._token}"} # LOGGER.debug("token %s" % self._token) res = await self._websession.get(url, headers=headers, data={}) diff --git a/custom_components/stromer/translations/en.json b/custom_components/stromer/translations/en.json index 22e0fd4..8fc3aa2 100644 --- a/custom_components/stromer/translations/en.json +++ b/custom_components/stromer/translations/en.json @@ -8,7 +8,7 @@ "password": "Password", "username" : "Username", "client_id": "Client ID", - "client_secret": "Client Secret" + "client_secret": "Client Secret (optional, not needed if your Client ID starts with 4P" } } }, diff --git a/custom_components/stromer/translations/nl.json b/custom_components/stromer/translations/nl.json index 6e51ee3..7e0bc24 100644 --- a/custom_components/stromer/translations/nl.json +++ b/custom_components/stromer/translations/nl.json @@ -8,7 +8,7 @@ "password": "Wachtwoord", "username" : "Gebruikersnaam", "client_id": "Client ID", - "client_secret": "Client Secret" + "client_secret": "Client Secret (optioneel, niet nodig als je client id met 4P begint" } } }, From 1850d267c3686bfef28d617cc1cf98b316c3ec0f Mon Sep 17 00:00:00 2001 From: Tom Scholten Date: Tue, 19 Apr 2022 22:09:27 +0200 Subject: [PATCH 2/3] Make secret more optional, fix coordinator data and reconnection handling --- custom_components/stromer/__init__.py | 2 +- custom_components/stromer/binary_sensor.py | 2 +- custom_components/stromer/config_flow.py | 4 ++-- custom_components/stromer/sensor.py | 2 +- custom_components/stromer/stromer.py | 10 ++++++---- 5 files changed, 11 insertions(+), 9 deletions(-) diff --git a/custom_components/stromer/__init__.py b/custom_components/stromer/__init__.py index b45be54..1ecc807 100644 --- a/custom_components/stromer/__init__.py +++ b/custom_components/stromer/__init__.py @@ -24,7 +24,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: username = entry.data[CONF_USERNAME] password = entry.data[CONF_PASSWORD] client_id = entry.data[CONF_CLIENT_ID] - client_secret = entry.data[CONF_CLIENT_SECRET] + client_secret = entry.data.get(CONF_CLIENT_SECRET, None) # Initialize connection to stromer stromer = Stromer(username, password, client_id, client_secret) diff --git a/custom_components/stromer/binary_sensor.py b/custom_components/stromer/binary_sensor.py index eb2fdb3..3461ef9 100644 --- a/custom_components/stromer/binary_sensor.py +++ b/custom_components/stromer/binary_sensor.py @@ -86,4 +86,4 @@ def __init__( @property def is_on(self) -> bool | None: """Return true if the binary sensor is on.""" - return self._coordinator.bike.bikedata.get(self._ent) + return self._coordinator.data.bikedata.get(self._ent) diff --git a/custom_components/stromer/config_flow.py b/custom_components/stromer/config_flow.py index 53d030b..3d90fd2 100644 --- a/custom_components/stromer/config_flow.py +++ b/custom_components/stromer/config_flow.py @@ -19,7 +19,7 @@ vol.Required(CONF_USERNAME): str, vol.Required(CONF_PASSWORD): str, vol.Required(CONF_CLIENT_ID): str, - vol.Optional(CONF_CLIENT_SECRET): str = None, + vol.Optional(CONF_CLIENT_SECRET): str, } ) @@ -29,7 +29,7 @@ async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str, username = data[CONF_USERNAME] password = data[CONF_PASSWORD] client_id = data[CONF_CLIENT_ID] - client_secret = data[CONF_CLIENT_SECRET] + client_secret = data.get(CONF_CLIENT_SECRET, None) # Initialize connection to stromer stromer = Stromer(username, password, client_id, client_secret) diff --git a/custom_components/stromer/sensor.py b/custom_components/stromer/sensor.py index 39181c1..a15f04a 100644 --- a/custom_components/stromer/sensor.py +++ b/custom_components/stromer/sensor.py @@ -175,4 +175,4 @@ def __init__( @property def state(self): """Return the state of the sensor.""" - return self._coordinator.bike.bikedata.get(self._ent) + return self._coordinator.data.bikedata.get(self._ent) diff --git a/custom_components/stromer/stromer.py b/custom_components/stromer/stromer.py index 573822c..d39a87b 100644 --- a/custom_components/stromer/stromer.py +++ b/custom_components/stromer/stromer.py @@ -61,11 +61,13 @@ async def stromer_connect(self): async def stromer_update(self): attempts = 0 - while attempts < 5: + while attempts < 10: + if attempts == 5: + LOGGER.info("Reconnecting to Stromer API") + await self.stromer_connect() attempts += 1 - await self.stromer_get_access_token() try: - LOGGER.debug("Stromer attempt: {}/5".format(attempts)) + LOGGER.debug("Stromer attempt: {}/10".format(attempts)) self.bike = await self.stromer_call_api(endpoint="bike/") LOGGER.debug("Stromer bike: {}".format(self.bike)) @@ -86,7 +88,7 @@ async def stromer_update(self): except Exception as e: LOGGER.error("Stromer error: api call failed: {}".format(e)) - LOGGER.debug("Stromer retry: {}/5".format(attempts)) + LOGGER.debug("Stromer retry: {}/10".format(attempts)) async def stromer_get_code(self): url = f"{self.base_url}/mobile/v4/login/" From 240feca5ffc04e63f392f3c17c09264dc90fc363 Mon Sep 17 00:00:00 2001 From: Tom Scholten Date: Tue, 19 Apr 2022 22:10:11 +0200 Subject: [PATCH 3/3] Typo fix --- custom_components/stromer/stromer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/stromer/stromer.py b/custom_components/stromer/stromer.py index d39a87b..62e9b8d 100644 --- a/custom_components/stromer/stromer.py +++ b/custom_components/stromer/stromer.py @@ -138,7 +138,7 @@ async def stromer_get_access_token(self): if self._api_version == 'v3': url = f"{self.base_url}/o/token/" - data["client_secret"] = self._client_secret, + data["client_secret"] = self._client_secret data["redirect_uri"] = "stromerauth://auth" res = await self._websession.post(url, data=data)