From c4b0a367ac726df1dc9ca00f9593da06e9867b70 Mon Sep 17 00:00:00 2001 From: MartinRinas Date: Thu, 17 Aug 2023 20:50:58 +0000 Subject: [PATCH 1/8] tronity soc --- packages/modules/vehicles/tronity/__init__.py | 0 packages/modules/vehicles/tronity/api.py | 58 +++++++++++++++++++ packages/modules/vehicles/tronity/config.py | 21 +++++++ packages/modules/vehicles/tronity/soc.py | 20 +++++++ 4 files changed, 99 insertions(+) create mode 100644 packages/modules/vehicles/tronity/__init__.py create mode 100644 packages/modules/vehicles/tronity/api.py create mode 100644 packages/modules/vehicles/tronity/config.py create mode 100644 packages/modules/vehicles/tronity/soc.py diff --git a/packages/modules/vehicles/tronity/__init__.py b/packages/modules/vehicles/tronity/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/modules/vehicles/tronity/api.py b/packages/modules/vehicles/tronity/api.py new file mode 100644 index 0000000000..7f9f63f52a --- /dev/null +++ b/packages/modules/vehicles/tronity/api.py @@ -0,0 +1,58 @@ +#!/usr/bin/env python3 +import logging + +from modules.common import req +from modules.vehicles.tronity.config import TronityVehicleSocConfiguration +from modules.common.abstract_soc import SocUpdateData +from modules.common.component_state import CarState + +log = logging.getLogger(__name__) + + +def fetch_soc(config: TronityVehicleSocConfiguration, soc_update_data: SocUpdateData) -> CarState: + log.debug("Fetching Tronity SOC") + session = create_session(str(config.client_id), str(config.client_secret)) + vehicle_id = str(config.vehicle_id) + tronity_data = fetch_soc_for_vehicle(vehicle_id, session) + return CarState(soc=tronity_data['level'], range=tronity_data['range'], soc_timestamp=tronity_data['lastUpdate']) + + +def create_session(client_id: str, client_secret: str) -> req.Session: + session = req.Session() + data = {'grant_type': 'app', + 'client_id': str(client_id), + 'client_secret': str(client_secret)} + + response = session.post( + "https://api.tronity.tech/authentication", + data=data, + ) + + if response.status_code != 201: + raise Exception("Error requesting Tronity access token, please check client_id and client_secret: %s" % response.status_code) + + access_token = response.json()['access_token'] + session.headers = { + 'Accept': 'application/hal+json', 'Authorization': 'Bearer %s' % access_token + } + log.debug("Retrieved Tronity Access Token.") + return session + + +def fetch_vehicles(session: req.Session) -> dict: + url = 'https://api.tronity.tech/tronity/vehicles' + vehicles = session.get(url).json() + if vehicles.status_code != 200: + raise Exception("Error requesting vehicles from Tronity: %s" % vehicles.status_code) + + log.debug("Retrieved Tronity vehicles: %s", vehicles["data"]) + return vehicles["data"] + + +def fetch_soc_for_vehicle(vehicle_id: str, session: req.Session) -> dict: + url = 'https://api.tronity.tech/tronity/vehicles/' + str(vehicle_id) + '/last_record' + response = session.get(url).json() + if response.status_code != 200: + raise Exception("Error requesting vehicle from Tronity: %s" % response.status_code) + log.debug("Retrieved Tronity data: %s", response) + return response diff --git a/packages/modules/vehicles/tronity/config.py b/packages/modules/vehicles/tronity/config.py new file mode 100644 index 0000000000..f3a4428e1b --- /dev/null +++ b/packages/modules/vehicles/tronity/config.py @@ -0,0 +1,21 @@ +from typing import Optional + + +class TronityVehicleSocConfiguration: + def __init__(self, + vehicle_id: Optional[str] = None, + client_id: Optional[str] = None, + client_secret: Optional[str] = None) -> None: + self.vehicle_id = vehicle_id + self.client_id = client_id + self.client_secret = client_secret + + +class TronityVehicleSoc: + def __init__(self, + name: str = "Tronity", + type: str = "tronity", + configuration: TronityVehicleSocConfiguration = None) -> None: + self.name = name + self.type = type + self.configuration = configuration or TronityVehicleSocConfiguration() diff --git a/packages/modules/vehicles/tronity/soc.py b/packages/modules/vehicles/tronity/soc.py new file mode 100644 index 0000000000..89431bd453 --- /dev/null +++ b/packages/modules/vehicles/tronity/soc.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python3 +import logging + +from modules.common.abstract_device import DeviceDescriptor +from modules.common.abstract_soc import SocUpdateData +from modules.common.component_state import CarState +from modules.common.configurable_vehicle import ConfigurableVehicle +from modules.vehicles.tronity.config import TronityVehicleSoc +from modules.vehicles.tronity.api import fetch_soc + +log = logging.getLogger(__name__) + + +def create_vehicle(vehicle_config: TronityVehicleSoc, vehicle: int): + def updater(soc_update_data: SocUpdateData) -> CarState: + return fetch_soc(vehicle_config.configuration, soc_update_data) + return ConfigurableVehicle(vehicle_config=vehicle_config, component_updater=updater, vehicle=vehicle) + + +device_descriptor = DeviceDescriptor(configuration_factory=TronityVehicleSoc) From da38b5f2d2998cc46ca73072ee5b5339c51f68a5 Mon Sep 17 00:00:00 2001 From: MartinRinas Date: Thu, 17 Aug 2023 21:16:55 +0000 Subject: [PATCH 2/8] flake8 --- packages/modules/vehicles/tronity/api.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/modules/vehicles/tronity/api.py b/packages/modules/vehicles/tronity/api.py index 7f9f63f52a..5b989d98da 100644 --- a/packages/modules/vehicles/tronity/api.py +++ b/packages/modules/vehicles/tronity/api.py @@ -29,7 +29,8 @@ def create_session(client_id: str, client_secret: str) -> req.Session: ) if response.status_code != 201: - raise Exception("Error requesting Tronity access token, please check client_id and client_secret: %s" % response.status_code) + raise Exception("Error requesting Tronity access token, please check client_id and client_secret: %s" + % response.status_code) access_token = response.json()['access_token'] session.headers = { From 6b0e81588e3546db159f5a4be3e2e705cd067975 Mon Sep 17 00:00:00 2001 From: MartinRinas Date: Fri, 18 Aug 2023 11:19:20 +0000 Subject: [PATCH 3/8] fix status check --- packages/modules/vehicles/tronity/api.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/packages/modules/vehicles/tronity/api.py b/packages/modules/vehicles/tronity/api.py index 5b989d98da..28215d70d2 100644 --- a/packages/modules/vehicles/tronity/api.py +++ b/packages/modules/vehicles/tronity/api.py @@ -42,18 +42,19 @@ def create_session(client_id: str, client_secret: str) -> req.Session: def fetch_vehicles(session: req.Session) -> dict: url = 'https://api.tronity.tech/tronity/vehicles' - vehicles = session.get(url).json() - if vehicles.status_code != 200: - raise Exception("Error requesting vehicles from Tronity: %s" % vehicles.status_code) - + response = session.get(url) + if response.status_code != 200: + raise Exception("Error requesting vehicles from Tronity: %s" % response.status_code) + vehicles = response.json() log.debug("Retrieved Tronity vehicles: %s", vehicles["data"]) return vehicles["data"] def fetch_soc_for_vehicle(vehicle_id: str, session: req.Session) -> dict: url = 'https://api.tronity.tech/tronity/vehicles/' + str(vehicle_id) + '/last_record' - response = session.get(url).json() + response = session.get(url) if response.status_code != 200: raise Exception("Error requesting vehicle from Tronity: %s" % response.status_code) - log.debug("Retrieved Tronity data: %s", response) - return response + data = response.json() + log.debug("Retrieved Tronity data: %s", data) + return data From af3014b9247e677b42b8b7728552e70231e3d1ea Mon Sep 17 00:00:00 2001 From: MartinRinas Date: Fri, 18 Aug 2023 16:03:23 +0000 Subject: [PATCH 4/8] token cache --- packages/modules/vehicles/tronity/api.py | 70 ++++++++++++++++----- packages/modules/vehicles/tronity/config.py | 4 +- packages/modules/vehicles/tronity/soc.py | 2 +- 3 files changed, 58 insertions(+), 18 deletions(-) diff --git a/packages/modules/vehicles/tronity/api.py b/packages/modules/vehicles/tronity/api.py index 28215d70d2..ced9e22714 100644 --- a/packages/modules/vehicles/tronity/api.py +++ b/packages/modules/vehicles/tronity/api.py @@ -1,38 +1,76 @@ #!/usr/bin/env python3 import logging +import jwt +import paho.mqtt.publish as publish +import json from modules.common import req -from modules.vehicles.tronity.config import TronityVehicleSocConfiguration +from modules.vehicles.tronity.config import TronityVehicleSocConfiguration, TronityVehicleSoc from modules.common.abstract_soc import SocUpdateData from modules.common.component_state import CarState +from datetime import datetime log = logging.getLogger(__name__) -def fetch_soc(config: TronityVehicleSocConfiguration, soc_update_data: SocUpdateData) -> CarState: +def fetch_soc(config: TronityVehicleSocConfiguration, soc_update_data: SocUpdateData, vehicle: int) -> CarState: log.debug("Fetching Tronity SOC") - session = create_session(str(config.client_id), str(config.client_secret)) - vehicle_id = str(config.vehicle_id) - tronity_data = fetch_soc_for_vehicle(vehicle_id, session) + session = create_session(config, vehicle) + tronity_vehicle_id = str(config.vehicle_id) + tronity_data = fetch_soc_for_vehicle(tronity_vehicle_id, session) return CarState(soc=tronity_data['level'], range=tronity_data['range'], soc_timestamp=tronity_data['lastUpdate']) -def create_session(client_id: str, client_secret: str) -> req.Session: +def is_token_valid(access_token: str) -> bool: + if not access_token: + log.debug("No token found") + return False + + decoded_data = jwt.decode(jwt=access_token, verify=False, algorithms=['HS256'], options={"verify_signature": False}) + if datetime.utcfromtimestamp(decoded_data['exp']) < datetime.utcnow(): + log.debug("Token expired: %s", decoded_data) + return False + else: + log.debug("Token still valid: %s", decoded_data) + return True + + +def write_token_mqtt(topic: str, token: str, config: TronityVehicleSocConfiguration): + try: + config.access_token = token + value: TronityVehicleSoc = TronityVehicleSoc(configuration=config) + value_to_mqtt = json.dumps(value.__dict__, default=lambda o: o.__dict__) + publish.single(topic, value_to_mqtt, hostname="localhost", port=1883, retain=True) + except Exception as e: + log.exception('Token mqtt write exception ' + str(e)) + + +def create_session(config: TronityVehicleSocConfiguration, vehicle: int) -> req.Session: session = req.Session() data = {'grant_type': 'app', - 'client_id': str(client_id), - 'client_secret': str(client_secret)} + 'client_id': str(config.client_id), + 'client_secret': str(config.client_secret)} + + if not is_token_valid(str(config.access_token)): + log.debug("Requesting new Tronity Access Token") + response = session.post( + "https://api.tronity.tech/authentication", + data=data, + ) - response = session.post( - "https://api.tronity.tech/authentication", - data=data, - ) + if response.status_code != 201: + raise Exception("Error requesting Tronity access token, please check client_id and client_secret: %s" + % response.status_code) - if response.status_code != 201: - raise Exception("Error requesting Tronity access token, please check client_id and client_secret: %s" - % response.status_code) + access_token = response.json()['access_token'] + log.debug("Retrieved Tronity Access Token: %s", access_token) + write_token_mqtt( + "openWB/set/vehicle/" + str(vehicle) + "/soc_module/config", + access_token, + config) + else: + access_token = config.access_token - access_token = response.json()['access_token'] session.headers = { 'Accept': 'application/hal+json', 'Authorization': 'Bearer %s' % access_token } diff --git a/packages/modules/vehicles/tronity/config.py b/packages/modules/vehicles/tronity/config.py index f3a4428e1b..9106a960c2 100644 --- a/packages/modules/vehicles/tronity/config.py +++ b/packages/modules/vehicles/tronity/config.py @@ -5,10 +5,12 @@ class TronityVehicleSocConfiguration: def __init__(self, vehicle_id: Optional[str] = None, client_id: Optional[str] = None, - client_secret: Optional[str] = None) -> None: + client_secret: Optional[str] = None, + access_token: Optional[str] = None) -> None: self.vehicle_id = vehicle_id self.client_id = client_id self.client_secret = client_secret + self.access_token = access_token class TronityVehicleSoc: diff --git a/packages/modules/vehicles/tronity/soc.py b/packages/modules/vehicles/tronity/soc.py index 89431bd453..280d5c50b7 100644 --- a/packages/modules/vehicles/tronity/soc.py +++ b/packages/modules/vehicles/tronity/soc.py @@ -13,7 +13,7 @@ def create_vehicle(vehicle_config: TronityVehicleSoc, vehicle: int): def updater(soc_update_data: SocUpdateData) -> CarState: - return fetch_soc(vehicle_config.configuration, soc_update_data) + return fetch_soc(vehicle_config.configuration, soc_update_data, vehicle) return ConfigurableVehicle(vehicle_config=vehicle_config, component_updater=updater, vehicle=vehicle) From 0790da58117973ad2a8f8baa0fb7586d9d7be88a Mon Sep 17 00:00:00 2001 From: MartinRinas Date: Wed, 23 Aug 2023 16:56:35 +0000 Subject: [PATCH 5/8] fix TypeError when publishing --- packages/helpermodules/pub.py | 9 ++++++++- packages/modules/vehicles/tronity/api.py | 9 +++------ 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/packages/helpermodules/pub.py b/packages/helpermodules/pub.py index 1f5f15c2b2..da10072d37 100644 --- a/packages/helpermodules/pub.py +++ b/packages/helpermodules/pub.py @@ -17,7 +17,14 @@ def pub(self, topic: str, payload, qos: int = 0, retain: bool = True) -> None: if payload == "": self.publisher.client.publish(topic, payload, qos=qos, retain=retain) else: - self.publisher.client.publish(topic, payload=json.dumps(payload), qos=qos, retain=retain) + try: + json_payload = json.dumps(payload) + except TypeError: + # Couldn't serialize payload to json, try to serialize it manually. Most likely a class instance. + json_payload = json.dumps(payload.__dict__, default=lambda o: o.__dict__) + except Exception as e: + raise e + self.publisher.client.publish(topic, payload=json_payload, qos=qos, retain=retain) class Pub: diff --git a/packages/modules/vehicles/tronity/api.py b/packages/modules/vehicles/tronity/api.py index ced9e22714..81143593b9 100644 --- a/packages/modules/vehicles/tronity/api.py +++ b/packages/modules/vehicles/tronity/api.py @@ -1,8 +1,7 @@ #!/usr/bin/env python3 import logging import jwt -import paho.mqtt.publish as publish -import json +from helpermodules.pub import Pub from modules.common import req from modules.vehicles.tronity.config import TronityVehicleSocConfiguration, TronityVehicleSoc @@ -22,7 +21,7 @@ def fetch_soc(config: TronityVehicleSocConfiguration, soc_update_data: SocUpdate def is_token_valid(access_token: str) -> bool: - if not access_token: + if not access_token or access_token == "None": log.debug("No token found") return False @@ -38,9 +37,7 @@ def is_token_valid(access_token: str) -> bool: def write_token_mqtt(topic: str, token: str, config: TronityVehicleSocConfiguration): try: config.access_token = token - value: TronityVehicleSoc = TronityVehicleSoc(configuration=config) - value_to_mqtt = json.dumps(value.__dict__, default=lambda o: o.__dict__) - publish.single(topic, value_to_mqtt, hostname="localhost", port=1883, retain=True) + Pub().pub(topic, TronityVehicleSoc(configuration=config)) except Exception as e: log.exception('Token mqtt write exception ' + str(e)) From 2f734e8ff2ffe8491409af186cf205a577eac629 Mon Sep 17 00:00:00 2001 From: MartinRinas Date: Thu, 24 Aug 2023 21:24:24 +0000 Subject: [PATCH 6/8] Revert "fix TypeError when publishing" This reverts commit 0790da58117973ad2a8f8baa0fb7586d9d7be88a. --- packages/helpermodules/pub.py | 9 +-------- packages/modules/vehicles/tronity/api.py | 9 ++++++--- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/packages/helpermodules/pub.py b/packages/helpermodules/pub.py index da10072d37..1f5f15c2b2 100644 --- a/packages/helpermodules/pub.py +++ b/packages/helpermodules/pub.py @@ -17,14 +17,7 @@ def pub(self, topic: str, payload, qos: int = 0, retain: bool = True) -> None: if payload == "": self.publisher.client.publish(topic, payload, qos=qos, retain=retain) else: - try: - json_payload = json.dumps(payload) - except TypeError: - # Couldn't serialize payload to json, try to serialize it manually. Most likely a class instance. - json_payload = json.dumps(payload.__dict__, default=lambda o: o.__dict__) - except Exception as e: - raise e - self.publisher.client.publish(topic, payload=json_payload, qos=qos, retain=retain) + self.publisher.client.publish(topic, payload=json.dumps(payload), qos=qos, retain=retain) class Pub: diff --git a/packages/modules/vehicles/tronity/api.py b/packages/modules/vehicles/tronity/api.py index 81143593b9..ced9e22714 100644 --- a/packages/modules/vehicles/tronity/api.py +++ b/packages/modules/vehicles/tronity/api.py @@ -1,7 +1,8 @@ #!/usr/bin/env python3 import logging import jwt -from helpermodules.pub import Pub +import paho.mqtt.publish as publish +import json from modules.common import req from modules.vehicles.tronity.config import TronityVehicleSocConfiguration, TronityVehicleSoc @@ -21,7 +22,7 @@ def fetch_soc(config: TronityVehicleSocConfiguration, soc_update_data: SocUpdate def is_token_valid(access_token: str) -> bool: - if not access_token or access_token == "None": + if not access_token: log.debug("No token found") return False @@ -37,7 +38,9 @@ def is_token_valid(access_token: str) -> bool: def write_token_mqtt(topic: str, token: str, config: TronityVehicleSocConfiguration): try: config.access_token = token - Pub().pub(topic, TronityVehicleSoc(configuration=config)) + value: TronityVehicleSoc = TronityVehicleSoc(configuration=config) + value_to_mqtt = json.dumps(value.__dict__, default=lambda o: o.__dict__) + publish.single(topic, value_to_mqtt, hostname="localhost", port=1883, retain=True) except Exception as e: log.exception('Token mqtt write exception ' + str(e)) From 45f5b399b2a14e1fa37120dba1a794a32706ff9d Mon Sep 17 00:00:00 2001 From: MartinRinas Date: Thu, 24 Aug 2023 21:35:47 +0000 Subject: [PATCH 7/8] Revert "fix TypeError when publishing" This reverts commit 0790da58117973ad2a8f8baa0fb7586d9d7be88a. --- packages/modules/vehicles/tronity/api.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/packages/modules/vehicles/tronity/api.py b/packages/modules/vehicles/tronity/api.py index ced9e22714..1031179ab0 100644 --- a/packages/modules/vehicles/tronity/api.py +++ b/packages/modules/vehicles/tronity/api.py @@ -1,9 +1,9 @@ #!/usr/bin/env python3 import logging import jwt -import paho.mqtt.publish as publish -import json +from dataclass_utils import asdict +from helpermodules.pub import Pub from modules.common import req from modules.vehicles.tronity.config import TronityVehicleSocConfiguration, TronityVehicleSoc from modules.common.abstract_soc import SocUpdateData @@ -39,8 +39,8 @@ def write_token_mqtt(topic: str, token: str, config: TronityVehicleSocConfigurat try: config.access_token = token value: TronityVehicleSoc = TronityVehicleSoc(configuration=config) - value_to_mqtt = json.dumps(value.__dict__, default=lambda o: o.__dict__) - publish.single(topic, value_to_mqtt, hostname="localhost", port=1883, retain=True) + log.debug("saving new access token") + Pub().pub(topic, asdict(value)) except Exception as e: log.exception('Token mqtt write exception ' + str(e)) @@ -63,18 +63,19 @@ def create_session(config: TronityVehicleSocConfiguration, vehicle: int) -> req. % response.status_code) access_token = response.json()['access_token'] - log.debug("Retrieved Tronity Access Token: %s", access_token) + log.debug("Retrieved new Tronity Access Token: %s", access_token) write_token_mqtt( "openWB/set/vehicle/" + str(vehicle) + "/soc_module/config", access_token, config) else: + log.debug("Using existing Tronity Access Token") access_token = config.access_token session.headers = { 'Accept': 'application/hal+json', 'Authorization': 'Bearer %s' % access_token } - log.debug("Retrieved Tronity Access Token.") + return session From 56ef37e4af165b384a3ec1cc32eee57d20cf5b76 Mon Sep 17 00:00:00 2001 From: MartinRinas Date: Thu, 24 Aug 2023 21:37:37 +0000 Subject: [PATCH 8/8] flake8 --- packages/modules/vehicles/tronity/api.py | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/modules/vehicles/tronity/api.py b/packages/modules/vehicles/tronity/api.py index 1031179ab0..4d89b42541 100644 --- a/packages/modules/vehicles/tronity/api.py +++ b/packages/modules/vehicles/tronity/api.py @@ -75,7 +75,6 @@ def create_session(config: TronityVehicleSocConfiguration, vehicle: int) -> req. session.headers = { 'Accept': 'application/hal+json', 'Authorization': 'Bearer %s' % access_token } - return session