forked from peter/volvo2mqtt
Add timezone option for HA Addon
This commit is contained in:
+2
-1
@@ -1,4 +1,5 @@
|
|||||||
requests~=2.29.0
|
requests~=2.29.0
|
||||||
dynaconf~=3.1.12
|
dynaconf~=3.1.12
|
||||||
paho-mqtt~=1.6.1
|
paho-mqtt~=1.6.1
|
||||||
Babel~=2.12.1
|
Babel~=2.12.1
|
||||||
|
pytz~=2023.3
|
||||||
@@ -8,6 +8,7 @@ apparmor: true
|
|||||||
options:
|
options:
|
||||||
updateInterval: 300
|
updateInterval: 300
|
||||||
babelLocale: null
|
babelLocale: null
|
||||||
|
TZ: null
|
||||||
debug: false
|
debug: false
|
||||||
mqtt:
|
mqtt:
|
||||||
broker: null
|
broker: null
|
||||||
@@ -23,6 +24,7 @@ options:
|
|||||||
schema:
|
schema:
|
||||||
updateInterval: int(10,)
|
updateInterval: int(10,)
|
||||||
babelLocale: str
|
babelLocale: str
|
||||||
|
TZ: str
|
||||||
debug: bool
|
debug: bool
|
||||||
mqtt:
|
mqtt:
|
||||||
broker: str
|
broker: str
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
from volvo import authorize
|
from volvo import authorize
|
||||||
from mqtt import update_loop, connect
|
from mqtt import update_loop, connect
|
||||||
from const import VERSION
|
from const import VERSION
|
||||||
|
from util import set_tz
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
print("Starting volvo2mqtt version " + VERSION)
|
print("Starting volvo2mqtt version " + VERSION)
|
||||||
|
set_tz()
|
||||||
authorize()
|
authorize()
|
||||||
connect()
|
connect()
|
||||||
update_loop()
|
update_loop()
|
||||||
|
|||||||
+3
-3
@@ -2,7 +2,7 @@ import time
|
|||||||
import paho.mqtt.client as mqtt
|
import paho.mqtt.client as mqtt
|
||||||
import json
|
import json
|
||||||
import volvo
|
import volvo
|
||||||
from util import keys_exists
|
import util
|
||||||
from threading import Thread, Timer
|
from threading import Thread, Timer
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from babel.dates import format_datetime
|
from babel.dates import format_datetime
|
||||||
@@ -24,7 +24,7 @@ def connect():
|
|||||||
if settings["mqtt"]["username"] and settings["mqtt"]["password"]:
|
if settings["mqtt"]["username"] and settings["mqtt"]["password"]:
|
||||||
client.username_pw_set(settings["mqtt"]["username"], settings["mqtt"]["password"])
|
client.username_pw_set(settings["mqtt"]["username"], settings["mqtt"]["password"])
|
||||||
port = 1883
|
port = 1883
|
||||||
if keys_exists(settings["mqtt"], "port"):
|
if util.keys_exists(settings["mqtt"], "port"):
|
||||||
conf_port = settings["mqtt"]["port"]
|
conf_port = settings["mqtt"]["port"]
|
||||||
if isinstance(conf_port, int):
|
if isinstance(conf_port, int):
|
||||||
if conf_port > 0:
|
if conf_port > 0:
|
||||||
@@ -99,7 +99,7 @@ def update_loop():
|
|||||||
|
|
||||||
def update_car_data(force_update=False, overwrite={}):
|
def update_car_data(force_update=False, overwrite={}):
|
||||||
global last_data_update
|
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 vin in volvo.vins:
|
||||||
for entity in volvo.supported_endpoints[vin]:
|
for entity in volvo.supported_endpoints[vin]:
|
||||||
if entity["domain"] == "button":
|
if entity["domain"] == "button":
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"updateInterval": 300,
|
"updateInterval": 300,
|
||||||
"babelLocale": "de",
|
"babelLocale": "de",
|
||||||
|
"TZ": "Europe/Berlin",
|
||||||
"mqtt": {
|
"mqtt": {
|
||||||
"broker": "",
|
"broker": "",
|
||||||
"port": 1883,
|
"port": 1883,
|
||||||
|
|||||||
@@ -5,6 +5,9 @@ configuration:
|
|||||||
babelLocale:
|
babelLocale:
|
||||||
name: Babel locale
|
name: Babel locale
|
||||||
description: Select your country code or take a look at the GitHub repo for more infos!
|
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:
|
debug:
|
||||||
name: API debug mode
|
name: API debug mode
|
||||||
description: Enable Volvo API debug, normaly this can stay off
|
description: Enable Volvo API debug, normaly this can stay off
|
||||||
|
|||||||
+19
@@ -1,3 +1,10 @@
|
|||||||
|
import pytz
|
||||||
|
import os
|
||||||
|
from config import settings
|
||||||
|
|
||||||
|
TZ = None
|
||||||
|
|
||||||
|
|
||||||
def keys_exists(element, *keys):
|
def keys_exists(element, *keys):
|
||||||
""""
|
""""
|
||||||
Check if *keys (nested) exists in `element` (dict).
|
Check if *keys (nested) exists in `element` (dict).
|
||||||
@@ -15,3 +22,15 @@ def keys_exists(element, *keys):
|
|||||||
except KeyError:
|
except KeyError:
|
||||||
return False
|
return False
|
||||||
return True
|
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!")
|
||||||
|
|||||||
+41
-41
@@ -1,9 +1,9 @@
|
|||||||
import requests
|
import requests
|
||||||
from datetime import datetime, timedelta
|
|
||||||
import mqtt
|
import mqtt
|
||||||
|
import util
|
||||||
|
from datetime import datetime, timedelta
|
||||||
from config import settings
|
from config import settings
|
||||||
from babel.dates import format_datetime
|
from babel.dates import format_datetime
|
||||||
from util import keys_exists
|
|
||||||
from const import charging_system_states, charging_connection_states, CLIMATE_START_URL, \
|
from const import charging_system_states, charging_connection_states, CLIMATE_START_URL, \
|
||||||
OAUTH_URL, VEHICLES_URL, VEHICLE_DETAILS_URL, RECHARGE_STATE_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
|
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"]})
|
session.headers.update({'authorization': "Bearer " + data["access_token"]})
|
||||||
|
|
||||||
global token_expires_at, refresh_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"]
|
refresh_token = data["refresh_token"]
|
||||||
|
|
||||||
get_vehicles()
|
get_vehicles()
|
||||||
@@ -70,7 +70,7 @@ def refresh_auth():
|
|||||||
session.headers.update({'authorization': "Bearer " + data["access_token"]})
|
session.headers.update({'authorization': "Bearer " + data["access_token"]})
|
||||||
|
|
||||||
global token_expires_at
|
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"]
|
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):
|
def api_call(url, method, vin, sensor_id=None, force_update=False):
|
||||||
global token_expires_at
|
global token_expires_at
|
||||||
if datetime.now() >= token_expires_at:
|
if datetime.now(util.TZ) >= token_expires_at:
|
||||||
refresh_auth()
|
refresh_auth()
|
||||||
|
|
||||||
if url == RECHARGE_STATE_URL:
|
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):
|
def cached_request(url, method, vin, force_update=False):
|
||||||
global cached_requests
|
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
|
# No API Data cached, get fresh data from API
|
||||||
print("Starting " + method + " call against " + url)
|
print("Starting " + method + " call against " + url)
|
||||||
try:
|
try:
|
||||||
@@ -237,11 +237,11 @@ def cached_request(url, method, vin, force_update=False):
|
|||||||
print("Error getting data: " + str(e))
|
print("Error getting data: " + str(e))
|
||||||
return None
|
return None
|
||||||
|
|
||||||
data = {"response": response, "last_update": datetime.now()}
|
data = {"response": response, "last_update": datetime.now(util.TZ)}
|
||||||
cached_requests[vin + "_" + url] = data
|
cached_requests[vin + "_" + url] = data
|
||||||
else:
|
else:
|
||||||
if (datetime.now() - cached_requests[vin + "_" + url]["last_update"]).total_seconds() >= settings["updateInterval"] \
|
if (datetime.now(util.TZ) - 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):
|
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
|
# Old Data in Cache, or force mode active, updating
|
||||||
print("Starting " + method + " call against " + url)
|
print("Starting " + method + " call against " + url)
|
||||||
try:
|
try:
|
||||||
@@ -249,7 +249,7 @@ def cached_request(url, method, vin, force_update=False):
|
|||||||
except requests.exceptions.RequestException as e:
|
except requests.exceptions.RequestException as e:
|
||||||
print("Error getting data: " + str(e))
|
print("Error getting data: " + str(e))
|
||||||
return None
|
return None
|
||||||
data = {"response": response, "last_update": datetime.now()}
|
data = {"response": response, "last_update": datetime.now(util.TZ)}
|
||||||
cached_requests[vin + "_" + url] = data
|
cached_requests[vin + "_" + url] = data
|
||||||
else:
|
else:
|
||||||
# Data is up do date, returning cached data
|
# 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):
|
def parse_api_data(data, sensor_id=None):
|
||||||
data = data["data"]
|
data = data["data"]
|
||||||
if sensor_id == "battery_charge_level":
|
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":
|
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":
|
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
|
"chargingSystemStatus") else None
|
||||||
elif sensor_id == "charging_connection_status":
|
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
|
"chargingConnectionStatus") else None
|
||||||
elif sensor_id == "estimated_charging_time":
|
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"]]
|
charging_system_state = charging_system_states[data["chargingSystemStatus"]["value"]]
|
||||||
if charging_system_state == "Charging":
|
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:
|
else:
|
||||||
return 0
|
return 0
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
elif sensor_id == "estimated_charging_finish_time":
|
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"]]
|
charging_system_state = charging_system_states[data["chargingSystemStatus"]["value"]]
|
||||||
if charging_system_state == "Charging":
|
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)
|
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"])
|
return format_datetime(charging_finished, format="medium", locale=settings["babelLocale"])
|
||||||
else:
|
else:
|
||||||
return ""
|
return ""
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
elif sensor_id == "lock_status":
|
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":
|
elif sensor_id == "odometer":
|
||||||
multiplier = 1
|
multiplier = 1
|
||||||
if keys_exists(settings["volvoData"], "odometerMultiplier"):
|
if util.keys_exists(settings["volvoData"], "odometerMultiplier"):
|
||||||
multiplier = settings["volvoData"]["odometerMultiplier"]
|
multiplier = settings["volvoData"]["odometerMultiplier"]
|
||||||
if isinstance(multiplier, str):
|
if isinstance(multiplier, str):
|
||||||
multiplier = 1
|
multiplier = 1
|
||||||
elif multiplier < 1:
|
elif multiplier < 1:
|
||||||
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":
|
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":
|
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":
|
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":
|
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":
|
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":
|
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":
|
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":
|
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":
|
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":
|
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":
|
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":
|
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":
|
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":
|
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":
|
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":
|
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":
|
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":
|
elif sensor_id == "location":
|
||||||
coordinates = {}
|
coordinates = {}
|
||||||
if keys_exists(data, "geometry"):
|
if util.keys_exists(data, "geometry"):
|
||||||
raw_data = 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],
|
coordinates = {"longitude": raw_data["coordinates"][0],
|
||||||
"latitude": raw_data["coordinates"][1],
|
"latitude": raw_data["coordinates"][1],
|
||||||
"gps_accuracy": 1}
|
"gps_accuracy": 1}
|
||||||
|
|||||||
Reference in New Issue
Block a user