diff --git a/requirements.txt b/requirements.txt index 66db402..fc8e167 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,5 @@ requests~=2.29.0 dynaconf~=3.1.12 paho-mqtt~=1.6.1 -Babel~=2.12.1 \ No newline at end of file +Babel~=2.12.1 +pytz~=2023.3 \ No newline at end of file diff --git a/src/config.yaml b/src/config.yaml index 1afc395..7ecfe23 100644 --- a/src/config.yaml +++ b/src/config.yaml @@ -8,6 +8,7 @@ apparmor: true options: updateInterval: 300 babelLocale: null + TZ: null debug: false mqtt: broker: null @@ -23,6 +24,7 @@ options: schema: updateInterval: int(10,) babelLocale: str + TZ: str debug: bool mqtt: broker: str diff --git a/src/main.py b/src/main.py index 9dd6f7b..15bee43 100644 --- a/src/main.py +++ b/src/main.py @@ -1,10 +1,12 @@ from volvo import authorize from mqtt import update_loop, connect from const import VERSION +from util import set_tz if __name__ == '__main__': print("Starting volvo2mqtt version " + VERSION) + set_tz() authorize() connect() update_loop() diff --git a/src/mqtt.py b/src/mqtt.py index 3340b26..d0b3b5b 100644 --- a/src/mqtt.py +++ b/src/mqtt.py @@ -2,7 +2,7 @@ import time import paho.mqtt.client as mqtt import json import volvo -from util import keys_exists +import util from threading import Thread, Timer from datetime import datetime from babel.dates import format_datetime @@ -24,7 +24,7 @@ def connect(): if settings["mqtt"]["username"] and settings["mqtt"]["password"]: client.username_pw_set(settings["mqtt"]["username"], settings["mqtt"]["password"]) port = 1883 - if keys_exists(settings["mqtt"], "port"): + if util.keys_exists(settings["mqtt"], "port"): conf_port = settings["mqtt"]["port"] if isinstance(conf_port, int): if conf_port > 0: @@ -99,7 +99,7 @@ def update_loop(): def update_car_data(force_update=False, overwrite={}): global last_data_update - last_data_update = format_datetime(datetime.now(), format="medium", locale=settings["babelLocale"]) + last_data_update = format_datetime(datetime.now(util.TZ), format="medium", locale=settings["babelLocale"]) for vin in volvo.vins: for entity in volvo.supported_endpoints[vin]: if entity["domain"] == "button": diff --git a/src/settings.json b/src/settings.json index 248945e..ac77f98 100644 --- a/src/settings.json +++ b/src/settings.json @@ -1,6 +1,7 @@ { "updateInterval": 300, "babelLocale": "de", + "TZ": "Europe/Berlin", "mqtt": { "broker": "", "port": 1883, diff --git a/src/translations/en.yaml b/src/translations/en.yaml index e7b192a..af5c41f 100644 --- a/src/translations/en.yaml +++ b/src/translations/en.yaml @@ -5,6 +5,9 @@ configuration: babelLocale: name: Babel locale description: Select your country code or take a look at the GitHub repo for more infos! + TZ: + name: Timezone + description: Set your local timezone eg. "Europe/Berlin" or "America/New_York" debug: name: API debug mode description: Enable Volvo API debug, normaly this can stay off diff --git a/src/util.py b/src/util.py index 10bef3c..26a3d5a 100644 --- a/src/util.py +++ b/src/util.py @@ -1,3 +1,10 @@ +import pytz +import os +from config import settings + +TZ = None + + def keys_exists(element, *keys): """" Check if *keys (nested) exists in `element` (dict). @@ -15,3 +22,15 @@ def keys_exists(element, *keys): except KeyError: return False return True + + +def set_tz(): + global TZ + env_tz = os.environ.get("TZ") + settings_tz = settings.get("TZ") + if env_tz: + TZ = pytz.timezone(env_tz) + elif settings_tz: + TZ = pytz.timezone(settings_tz) + else: + raise Exception("No timezone setting found! Please read the README!") diff --git a/src/volvo.py b/src/volvo.py index a300410..f2519f3 100644 --- a/src/volvo.py +++ b/src/volvo.py @@ -1,9 +1,9 @@ import requests -from datetime import datetime, timedelta import mqtt +import util +from datetime import datetime, timedelta from config import settings from babel.dates import format_datetime -from util import keys_exists from const import charging_system_states, charging_connection_states, CLIMATE_START_URL, \ OAUTH_URL, VEHICLES_URL, VEHICLE_DETAILS_URL, RECHARGE_STATE_URL, \ WINDOWS_STATE_URL, LOCK_STATE_URL, TYRE_STATE_URL, supported_entities, BATTERY_CHARGE_STATE_URL @@ -41,7 +41,7 @@ def authorize(): session.headers.update({'authorization': "Bearer " + data["access_token"]}) global token_expires_at, refresh_token - token_expires_at = datetime.now() + timedelta(seconds=(data["expires_in"] - 30)) + token_expires_at = datetime.now(util.TZ) + timedelta(seconds=(data["expires_in"] - 30)) refresh_token = data["refresh_token"] get_vehicles() @@ -70,7 +70,7 @@ def refresh_auth(): session.headers.update({'authorization': "Bearer " + data["access_token"]}) global token_expires_at - token_expires_at = datetime.now() + timedelta(seconds=(data["expires_in"] - 30)) + token_expires_at = datetime.now(util.TZ) + timedelta(seconds=(data["expires_in"] - 30)) refresh_token = data["refresh_token"] @@ -165,7 +165,7 @@ def disable_climate(vin): def api_call(url, method, vin, sensor_id=None, force_update=False): global token_expires_at - if datetime.now() >= token_expires_at: + if datetime.now(util.TZ) >= token_expires_at: refresh_auth() if url == RECHARGE_STATE_URL: @@ -228,7 +228,7 @@ def api_call(url, method, vin, sensor_id=None, force_update=False): def cached_request(url, method, vin, force_update=False): global cached_requests - if not keys_exists(cached_requests, vin + "_" + url): + if not util.keys_exists(cached_requests, vin + "_" + url): # No API Data cached, get fresh data from API print("Starting " + method + " call against " + url) try: @@ -237,11 +237,11 @@ def cached_request(url, method, vin, force_update=False): print("Error getting data: " + str(e)) return None - data = {"response": response, "last_update": datetime.now()} + data = {"response": response, "last_update": datetime.now(util.TZ)} cached_requests[vin + "_" + url] = data else: - if (datetime.now() - cached_requests[vin + "_" + url]["last_update"]).total_seconds() >= settings["updateInterval"] \ - or (force_update and (datetime.now() - cached_requests[vin + "_" + url]["last_update"]).total_seconds() >= 2): + if (datetime.now(util.TZ) - cached_requests[vin + "_" + url]["last_update"]).total_seconds() >= settings["updateInterval"] \ + or (force_update and (datetime.now(util.TZ) - cached_requests[vin + "_" + url]["last_update"]).total_seconds() >= 2): # Old Data in Cache, or force mode active, updating print("Starting " + method + " call against " + url) try: @@ -249,7 +249,7 @@ def cached_request(url, method, vin, force_update=False): except requests.exceptions.RequestException as e: print("Error getting data: " + str(e)) return None - data = {"response": response, "last_update": datetime.now()} + data = {"response": response, "last_update": datetime.now(util.TZ)} cached_requests[vin + "_" + url] = data else: # Data is up do date, returning cached data @@ -260,86 +260,86 @@ def cached_request(url, method, vin, force_update=False): def parse_api_data(data, sensor_id=None): data = data["data"] if sensor_id == "battery_charge_level": - return data["batteryChargeLevel"]["value"] if keys_exists(data, "batteryChargeLevel") else None + return data["batteryChargeLevel"]["value"] if util.keys_exists(data, "batteryChargeLevel") else None elif sensor_id == "electric_range": - return data["electricRange"]["value"] if keys_exists(data, "electricRange") else None + return data["electricRange"]["value"] if util.keys_exists(data, "electricRange") else None elif sensor_id == "charging_system_status": - return charging_system_states[data["chargingSystemStatus"]["value"]] if keys_exists(data, + return charging_system_states[data["chargingSystemStatus"]["value"]] if util.keys_exists(data, "chargingSystemStatus") else None elif sensor_id == "charging_connection_status": - return charging_connection_states[data["chargingConnectionStatus"]["value"]] if keys_exists(data, + return charging_connection_states[data["chargingConnectionStatus"]["value"]] if util.keys_exists(data, "chargingConnectionStatus") else None elif sensor_id == "estimated_charging_time": - if keys_exists(data, "chargingSystemStatus"): + if util.keys_exists(data, "chargingSystemStatus"): charging_system_state = charging_system_states[data["chargingSystemStatus"]["value"]] if charging_system_state == "Charging": - return data["estimatedChargingTime"]["value"] if keys_exists(data, "estimatedChargingTime") else "" + return data["estimatedChargingTime"]["value"] if util.keys_exists(data, "estimatedChargingTime") else "" else: return 0 else: return None elif sensor_id == "estimated_charging_finish_time": - if keys_exists(data, "chargingSystemStatus"): + if util.keys_exists(data, "chargingSystemStatus"): charging_system_state = charging_system_states[data["chargingSystemStatus"]["value"]] if charging_system_state == "Charging": - charging_time = int(data["estimatedChargingTime"]["value"] if keys_exists(data, "estimatedChargingTime") + charging_time = int(data["estimatedChargingTime"]["value"] if util.keys_exists(data, "estimatedChargingTime") else 0) - charging_finished = datetime.now() + timedelta(minutes=charging_time) + charging_finished = datetime.now(util.TZ) + timedelta(minutes=charging_time) return format_datetime(charging_finished, format="medium", locale=settings["babelLocale"]) else: return "" else: return None elif sensor_id == "lock_status": - return data["carLocked"]["value"] if keys_exists(data, "carLocked") else None + return data["carLocked"]["value"] if util.keys_exists(data, "carLocked") else None elif sensor_id == "odometer": multiplier = 1 - if keys_exists(settings["volvoData"], "odometerMultiplier"): + if util.keys_exists(settings["volvoData"], "odometerMultiplier"): multiplier = settings["volvoData"]["odometerMultiplier"] if isinstance(multiplier, str): multiplier = 1 elif multiplier < 1: multiplier = 1 - return int(data["odometer"]["value"]) * multiplier if keys_exists(data, "odometer") else None + return int(data["odometer"]["value"]) * multiplier if util.keys_exists(data, "odometer") else None elif sensor_id == "window_front_left": - return data["frontLeftWindowOpen"]["value"] if keys_exists(data, "frontLeftWindowOpen") else None + return data["frontLeftWindowOpen"]["value"] if util.keys_exists(data, "frontLeftWindowOpen") else None elif sensor_id == "window_front_right": - return data["frontRightWindowOpen"]["value"] if keys_exists(data, "frontRightWindowOpen") else None + return data["frontRightWindowOpen"]["value"] if util.keys_exists(data, "frontRightWindowOpen") else None elif sensor_id == "window_rear_left": - return data["rearLeftWindowOpen"]["value"] if keys_exists(data, "rearLeftWindowOpen") else None + return data["rearLeftWindowOpen"]["value"] if util.keys_exists(data, "rearLeftWindowOpen") else None elif sensor_id == "window_rear_right": - return data["rearRightWindowOpen"]["value"] if keys_exists(data, "rearRightWindowOpen") else None + return data["rearRightWindowOpen"]["value"] if util.keys_exists(data, "rearRightWindowOpen") else None elif sensor_id == "door_front_left": - return data["frontLeftDoorOpen"]["value"] if keys_exists(data, "frontLeftDoorOpen") else None + return data["frontLeftDoorOpen"]["value"] if util.keys_exists(data, "frontLeftDoorOpen") else None elif sensor_id == "door_front_right": - return data["frontRightDoorOpen"]["value"] if keys_exists(data, "frontRightDoorOpen") else None + return data["frontRightDoorOpen"]["value"] if util.keys_exists(data, "frontRightDoorOpen") else None elif sensor_id == "door_rear_left": - return data["rearLeftDoorOpen"]["value"] if keys_exists(data, "rearLeftDoorOpen") else None + return data["rearLeftDoorOpen"]["value"] if util.keys_exists(data, "rearLeftDoorOpen") else None elif sensor_id == "door_rear_right": - return data["rearRightDoorOpen"]["value"] if keys_exists(data, "rearRightDoorOpen") else None + return data["rearRightDoorOpen"]["value"] if util.keys_exists(data, "rearRightDoorOpen") else None elif sensor_id == "tailgate": - return data["tailGateOpen"]["value"] if keys_exists(data, "tailGateOpen") else None + return data["tailGateOpen"]["value"] if util.keys_exists(data, "tailGateOpen") else None elif sensor_id == "engine_hood": - return data["hoodOpen"]["value"] if keys_exists(data, "hoodOpen") else None + return data["hoodOpen"]["value"] if util.keys_exists(data, "hoodOpen") else None elif sensor_id == "tank_lid": - return data["tankLidOpen"]["value"] if keys_exists(data, "tankLidOpen") else None + return data["tankLidOpen"]["value"] if util.keys_exists(data, "tankLidOpen") else None elif sensor_id == "tyre_front_left": - return data["frontLeftTyrePressure"]["value"] if keys_exists(data, "frontLeftTyrePressure") else None + return data["frontLeftTyrePressure"]["value"] if util.keys_exists(data, "frontLeftTyrePressure") else None elif sensor_id == "tyre_front_right": - return data["frontRightTyrePressure"]["value"] if keys_exists(data, "frontRightTyrePressure") else None + return data["frontRightTyrePressure"]["value"] if util.keys_exists(data, "frontRightTyrePressure") else None elif sensor_id == "tyre_rear_left": - return data["rearLeftTyrePressure"]["value"] if keys_exists(data, "rearLeftTyrePressure") else None + return data["rearLeftTyrePressure"]["value"] if util.keys_exists(data, "rearLeftTyrePressure") else None elif sensor_id == "tyre_rear_right": - return data["rearRightTyrePressure"]["value"] if keys_exists(data, "rearRightTyrePressure") else None + return data["rearRightTyrePressure"]["value"] if util.keys_exists(data, "rearRightTyrePressure") else None elif sensor_id == "engine_state": - return data["engineRunning"]["value"] if keys_exists(data, "engineRunning") else None + return data["engineRunning"]["value"] if util.keys_exists(data, "engineRunning") else None elif sensor_id == "fuel_level": - return data["fuelAmount"]["value"] if keys_exists(data, "fuelAmount") else None + return data["fuelAmount"]["value"] if util.keys_exists(data, "fuelAmount") else None elif sensor_id == "location": coordinates = {} - if keys_exists(data, "geometry"): + if util.keys_exists(data, "geometry"): raw_data = data["geometry"] - if keys_exists(raw_data, "coordinates"): + if util.keys_exists(raw_data, "coordinates"): coordinates = {"longitude": raw_data["coordinates"][0], "latitude": raw_data["coordinates"][1], "gps_accuracy": 1}