Compare commits

...
18 Commits
Author SHA1 Message Date
Linus Dietz f1953e1baa Quality optimizations 2023-11-10 09:21:00 +01:00
Linus Dietz 459c2dc1af Add AJAR state for sunroof #123 2023-11-10 09:05:03 +01:00
Linus Dietz be09c7953e Merge remote-tracking branch 'origin/main' into main 2023-11-09 18:27:51 +01:00
Linus Dietz fa68c4ba2c Fix api changes from volvo 2023-11-09 18:27:31 +01:00
Linus DietzandGitHub d7a79f84a4 add XC90 mild 2023-10-23 20:16:01 +02:00
Linus Dietz 2c8effd746 Fix version for HA addon 2023-10-09 10:29:12 +02:00
Linus Dietz cef28f7d0b Fix 'ACL denying access to client with dangerous client id' 2023-09-21 15:22:18 +02:00
Linus DietzandGitHub 781e1da8e8 Fix #104 (#107)
* Catch Volvo API not returning a valid json string #104

* Update changelog
2023-09-21 14:31:21 +02:00
6b419cfe3a Fix regex for some emails #98 // Add option to disable log completely #96 (#99)
* Update volovData.username regex (#98)

* Add option to disable log completely #96

---------

Co-authored-by: Gert-jan Theunissen <gurtjun@users.noreply.github.com>
2023-09-08 11:40:21 +02:00
Linus Dietz e5ece2116b Add option to use multiple docker containers (with different logins) #93 2023-08-28 15:09:01 +02:00
Linus Dietz 23246a464f Allow phone number as username #91 2023-08-25 15:08:25 +02:00
Linus Dietz 1a5cc3db7a Optimize addon configuration 2023-08-25 09:08:58 +02:00
Linus Dietz 974ad0e454 Add "device_class: battery" for battery state sensors from BEV and PHEV 2023-08-18 15:44:13 +02:00
Linus Dietz 7db02b5dc6 Update changelog 2023-08-18 09:43:07 +02:00
Linus Dietz 7c87c71ac8 Optimize vcc api key change 2023-08-18 09:40:48 +02:00
Linus Dietz 3482a5f893 Change addon settings to prevent abuse 2023-08-18 07:54:24 +02:00
Linus Dietz 3f1f793c1b Add settings validation to prevent abuse 2023-08-18 07:53:58 +02:00
Linus DietzandGitHub 1039eab649 Update readme for 1.8.0 2023-08-17 14:35:53 +02:00
10 changed files with 165 additions and 73 deletions
+2 -1
View File
@@ -32,6 +32,7 @@ If you like my work:<br>
- XC60 PHEV (2022) - XC60 PHEV (2022)
- XC90 PHEV T8 (2023) - XC90 PHEV T8 (2023)
- XC90 PHEV T8 (2024) - XC90 PHEV T8 (2024)
- XC90 B5 Mildhybrid (2024)
- V90 PHEV T8 (2019)* - V90 PHEV T8 (2019)*
*only partly working *only partly working
@@ -72,7 +73,7 @@ NOTE: Energy status currently available only for cars in the Europe / Middle Eas
Just install this addon with the following command. Just install this addon with the following command.
Please note to fill in your settings inside the environment variables. Please note to fill in your settings inside the environment variables.
`docker run -d --pull=always -e CONF_updateInterval=300 -e CONF_babelLocale='de' -e CONF_mqtt='@json {"broker": "", "username": "", "password": "", "port": 1883}' -e CONF_volvoData='@json {"username": "", "password": "", "vin": "", "vccapikey": "", "backupvccapikey": "", "odometerMultiplier": 1, "averageSpeedDivider": 1, "averageFuelConsumptionMultiplier": 1}' -e TZ='Europe/Berlin' --name volvo2mqtt ghcr.io/dielee/volvo2mqtt:latest` `docker run -d --pull=always -e CONF_updateInterval=300 -e CONF_babelLocale='de' -e CONF_mqtt='@json {"broker": "", "username": "", "password": "", "port": 1883}' -e CONF_volvoData='@json {"username": "", "password": "", "vin": "", "vccapikey": ["key1", "key2"], "odometerMultiplier": 1, "averageSpeedDivider": 1, "averageFuelConsumptionMultiplier": 1}' -e TZ='Europe/Berlin' --name volvo2mqtt ghcr.io/dielee/volvo2mqtt:latest`
<b>HA Add-On:</b><br> <b>HA Add-On:</b><br>
+57
View File
@@ -1,3 +1,60 @@
## v1.8.11
### 🐛 Bug Fixes:
- Remove old code from checks for running engine sensor
- Move battery charge level sensor from v2 to v1, as v2 returns 404
## v1.8.10
### 🐛 Bug Fixes:
- Add `AJAR` state for sunroof #123
## v1.8.9
### 🐛 Bug Fixes:
- Fix api changes from volvo #121
## v1.8.8
### 🐛 Bug Fixes:
- Fix json decode error, if volvo API returns a simple string #104
## v1.8.7
### 🚀 Features:
- Add option to disable log completely #96
### 🐛 Bug Fixes:
- Fix regex error for some mailaddresses #97
## v1.8.6
### 🚀 Features:
- Add option to use multiple docker containers (with different logins) #93
## v1.8.5
### 🚀 Features:
- Allow phone number as username #91
## v1.8.4
### 🚀 Features:
- Optimize Addon configuration #90
## v1.8.3
### 🚀 Features:
- Add `device_class: battery` for battery state sensors from BEV and PHEV
## v1.8.2
### 🚀 Features:
- Add `updateInterval` limit to prevent abuse (60 Seconds)
- Add `vccapikey` limit to prevent abuse (3 Keys)
- Optimize vcc api key change behaviour
## v1.8.1 ## v1.8.1
### 🐛 Bug Fixes: ### 🐛 Bug Fixes:
+9 -6
View File
@@ -1,6 +1,6 @@
name: "Volvo2Mqtt" name: "Volvo2Mqtt"
description: "Volvo AAOS MQTT bridge" description: "Volvo AAOS MQTT bridge"
version: "1.8.1" version: "1.8.11"
slug: "volvo2mqtt" slug: "volvo2mqtt"
init: false init: false
url: "https://github.com/Dielee/volvo2mqtt" url: "https://github.com/Dielee/volvo2mqtt"
@@ -15,6 +15,7 @@ options:
babelLocale: null babelLocale: null
TZ: null TZ: null
debug: false debug: false
disable_logging: false
mqtt: mqtt:
broker: "auto_broker" broker: "auto_broker"
port: "auto_port" port: "auto_port"
@@ -24,26 +25,28 @@ options:
username: null username: null
password: null password: null
vin: "" vin: ""
vccapikey: null vccapikey:
- null
odometerMultiplier: 1 odometerMultiplier: 1
averageSpeedDivider: 1 averageSpeedDivider: 1
averageFuelConsumptionMultiplier: 1 averageFuelConsumptionMultiplier: 1
schema: schema:
updateInterval: int(10,) updateInterval: int(60,)
babelLocale: str babelLocale: str
TZ: str TZ: match(^.+/.+$)
debug: bool debug: bool
disable_logging: bool
mqtt: mqtt:
broker: str broker: str
port: str port: str
username: str? username: str?
password: str? password: str?
volvoData: volvoData:
username: str username: match(^([a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+)|(\+\d{5,20})$)
password: str password: str
vin: str? vin: str?
vccapikey: vccapikey:
- str - match(^\b\w{32}\b$)
odometerMultiplier: int(1,) odometerMultiplier: int(1,)
averageSpeedDivider: int(1,) averageSpeedDivider: int(1,)
averageFuelConsumptionMultiplier: int(1,) averageFuelConsumptionMultiplier: int(1,)
+5 -6
View File
@@ -1,6 +1,6 @@
from config import settings from config import settings
VERSION = "v1.8.1" VERSION = "v1.8.11"
OAUTH_URL = "https://volvoid.eu.volvocars.com/as/token.oauth2" OAUTH_URL = "https://volvoid.eu.volvocars.com/as/token.oauth2"
VEHICLES_URL = "https://api.volvocars.com/connected-vehicle/v1/vehicles" VEHICLES_URL = "https://api.volvocars.com/connected-vehicle/v1/vehicles"
@@ -16,7 +16,7 @@ ODOMETER_STATE_URL = "https://api.volvocars.com/connected-vehicle/v2/vehicles/{0
LOCATION_STATE_URL = "https://api.volvocars.com/location/v1/vehicles/{0}/location" LOCATION_STATE_URL = "https://api.volvocars.com/location/v1/vehicles/{0}/location"
TYRE_STATE_URL = "https://api.volvocars.com/connected-vehicle/v2/vehicles/{0}/tyres" TYRE_STATE_URL = "https://api.volvocars.com/connected-vehicle/v2/vehicles/{0}/tyres"
ENGINE_STATE_URL = "https://api.volvocars.com/connected-vehicle/v2/vehicles/{0}/engine-status" ENGINE_STATE_URL = "https://api.volvocars.com/connected-vehicle/v2/vehicles/{0}/engine-status"
BATTERY_CHARGE_STATE_URL = "https://api.volvocars.com/connected-vehicle/v2/vehicles/{0}/battery-charge-level" BATTERY_CHARGE_STATE_URL = "https://api.volvocars.com/connected-vehicle/v1/vehicles/{0}/battery-charge-level"
FUEL_STATE_URL = "https://api.volvocars.com/connected-vehicle/v2/vehicles/{0}/fuel" FUEL_STATE_URL = "https://api.volvocars.com/connected-vehicle/v2/vehicles/{0}/fuel"
STATISTICS_URL = "https://api.volvocars.com/connected-vehicle/v2/vehicles/{0}/statistics" STATISTICS_URL = "https://api.volvocars.com/connected-vehicle/v2/vehicles/{0}/statistics"
ENGINE_DIAGNOSTICS_URL = "https://api.volvocars.com/connected-vehicle/v2/vehicles/{0}/engine" ENGINE_DIAGNOSTICS_URL = "https://api.volvocars.com/connected-vehicle/v2/vehicles/{0}/engine"
@@ -51,7 +51,7 @@ charging_connection_states = {"CONNECTION_STATUS_DISCONNECTED": "Disconnected",
"CONNECTION_STATUS_FAULT": "Fault"} "CONNECTION_STATUS_FAULT": "Fault"}
window_states = {"CLOSED": "OFF", "OPEN": "ON"} window_states = {"CLOSED": "OFF", "OPEN": "ON"}
door_states = {"CLOSED": "OFF", "OPEN": "ON"} door_states = {"CLOSED": "OFF", "OPEN": "ON", "UNSPECIFIED": "UNKNOWN", "AJAR": "ON"}
engine_states = {"RUNNING": "ON", "STOPPED": "OFF", "true": "ON", "false": "OFF"} engine_states = {"RUNNING": "ON", "STOPPED": "OFF", "true": "ON", "false": "OFF"}
icon_states = { icon_states = {
@@ -77,8 +77,8 @@ icon_states = {
} }
supported_entities = [ supported_entities = [
{"name": "Battery Charge Level", "domain": "sensor", "id": "battery_charge_level", "unit": "%", "icon": "car-battery", "url": RECHARGE_STATE_URL, "state_class": "measurement"}, {"name": "Battery Charge Level", "domain": "sensor", "device_class": "battery", "id": "battery_charge_level", "unit": "%", "icon": "car-battery", "url": RECHARGE_STATE_URL, "state_class": "measurement"},
{"name": "Battery Charge Level", "domain": "sensor", "id": "battery_charge_level", "unit": "%", "icon": "car-battery", "url": BATTERY_CHARGE_STATE_URL, "state_class": "measurement"}, {"name": "Battery Charge Level", "domain": "sensor", "device_class": "battery", "id": "battery_charge_level", "unit": "%", "icon": "car-battery", "url": BATTERY_CHARGE_STATE_URL, "state_class": "measurement"},
{"name": "Electric Range", "domain": "sensor", "id": "electric_range", "unit": "km" if not units.get(settings["babelLocale"]) else units[settings["babelLocale"]]["electric_range"]["unit"], "icon": "map-marker-distance", "url": RECHARGE_STATE_URL, "state_class": "measurement"}, {"name": "Electric Range", "domain": "sensor", "id": "electric_range", "unit": "km" if not units.get(settings["babelLocale"]) else units[settings["babelLocale"]]["electric_range"]["unit"], "icon": "map-marker-distance", "url": RECHARGE_STATE_URL, "state_class": "measurement"},
{"name": "Estimated Charging Time", "domain": "sensor", "id": "estimated_charging_time", "unit": "minutes", "icon": "timer-sync-outline", "url": RECHARGE_STATE_URL, "state_class": "measurement"}, {"name": "Estimated Charging Time", "domain": "sensor", "id": "estimated_charging_time", "unit": "minutes", "icon": "timer-sync-outline", "url": RECHARGE_STATE_URL, "state_class": "measurement"},
{"name": "Charging System Status", "domain": "sensor", "id": "charging_system_status", "icon": "ev-station", "url": RECHARGE_STATE_URL}, {"name": "Charging System Status", "domain": "sensor", "id": "charging_system_status", "icon": "ev-station", "url": RECHARGE_STATE_URL},
@@ -108,7 +108,6 @@ supported_entities = [
{"name": "Tire Rear Left", "domain": "sensor", "id": "tyre_rear_left", "icon": "car-tire-alert", "url": TYRE_STATE_URL}, {"name": "Tire Rear Left", "domain": "sensor", "id": "tyre_rear_left", "icon": "car-tire-alert", "url": TYRE_STATE_URL},
{"name": "Tire Rear Right", "domain": "sensor", "id": "tyre_rear_right", "icon": "car-tire-alert", "url": TYRE_STATE_URL}, {"name": "Tire Rear Right", "domain": "sensor", "id": "tyre_rear_right", "icon": "car-tire-alert", "url": TYRE_STATE_URL},
{"name": "Engine State", "domain": "binary_sensor", "device_class": "running", "id": "engine_state", "icon": "engine", "url": ENGINE_STATE_URL}, {"name": "Engine State", "domain": "binary_sensor", "device_class": "running", "id": "engine_state", "icon": "engine", "url": ENGINE_STATE_URL},
{"name": "Engine State", "domain": "binary_sensor", "device_class": "running", "id": "engine_state", "icon": "engine", "url": ENGINE_DIAGNOSTICS_URL},
{"name": "Fuel Level", "domain": "sensor", "id": "fuel_level", "unit": "liters", "icon": "fuel", "url": FUEL_STATE_URL, "state_class": "measurement"}, {"name": "Fuel Level", "domain": "sensor", "id": "fuel_level", "unit": "liters", "icon": "fuel", "url": FUEL_STATE_URL, "state_class": "measurement"},
{"name": "Average Fuel Consumption", "domain": "sensor", "id": "average_fuel_consumption", "unit": "liters", "icon": "fuel", "url": STATISTICS_URL}, {"name": "Average Fuel Consumption", "domain": "sensor", "id": "average_fuel_consumption", "unit": "liters", "icon": "fuel", "url": STATISTICS_URL},
{"name": "Distance to Empty", "domain": "sensor", "id": "distance_to_empty", "unit": "km" if not units.get(settings["babelLocale"]) else units[settings["babelLocale"]]["distance_to_empty"]["unit"], "icon": "map-marker-distance", "url": STATISTICS_URL, "state_class": "measurement"}, {"name": "Distance to Empty", "domain": "sensor", "id": "distance_to_empty", "unit": "km" if not units.get(settings["babelLocale"]) else units[settings["babelLocale"]]["distance_to_empty"]["unit"], "icon": "map-marker-distance", "url": STATISTICS_URL, "state_class": "measurement"},
+4 -3
View File
@@ -2,14 +2,15 @@ import logging
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, setup_logging, set_mqtt_settings from util import set_tz, setup_logging, set_mqtt_settings, validate_settings
if __name__ == '__main__': if __name__ == '__main__':
set_tz()
set_mqtt_settings()
setup_logging() setup_logging()
logging.info("Starting volvo2mqtt version " + VERSION) logging.info("Starting volvo2mqtt version " + VERSION)
validate_settings()
set_tz()
set_mqtt_settings()
connect() connect()
authorize() authorize()
update_loop() update_loop()
+10 -6
View File
@@ -4,6 +4,7 @@ import paho.mqtt.client as mqtt
import json import json
import volvo import volvo
import util import util
import os
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
@@ -23,7 +24,9 @@ active_schedules = {}
def connect(): def connect():
client = mqtt.Client("volvoAAOS2mqtt") client = mqtt.Client("volvoAAOS2mqtt") if os.environ.get("IS_HA_ADDON") \
else mqtt.Client("volvoAAOS2mqtt_" + settings.volvoData["username"].replace("+", ""))
client.will_set(availability_topic, "offline", 0, False) client.will_set(availability_topic, "offline", 0, False)
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"])
@@ -252,11 +255,12 @@ def update_car_data(force_update=False, overwrite={}):
else: else:
topic = f"homeassistant/{entity['domain']}/{vin}_{entity['id']}/state" topic = f"homeassistant/{entity['domain']}/{vin}_{entity['id']}/state"
mqtt_client.publish( if state:
topic, mqtt_client.publish(
json.dumps(state) if isinstance(state, dict) or isinstance(state, list) else state topic,
) json.dumps(state) if isinstance(state, dict) or isinstance(state, list) else state
update_ha_device(entity, vin, state) )
update_ha_device(entity, vin, state)
def update_ha_device(entity, vin, state): def update_ha_device(entity, vin, state):
+2
View File
@@ -2,6 +2,8 @@
"updateInterval": 300, "updateInterval": 300,
"babelLocale": "de", "babelLocale": "de",
"TZ": "Europe/Berlin", "TZ": "Europe/Berlin",
"debug": false,
"disable_logging": false,
"mqtt": { "mqtt": {
"broker": "", "broker": "",
"port": 1883, "port": 1883,
+3
View File
@@ -11,6 +11,9 @@ configuration:
debug: debug:
name: API debug mode name: API debug mode
description: Enable Volvo API debug, normaly this can stay off. If enabled, the complete log file can be found under \\<Your HA Host IP>\addons\volvo2mqtt\log\volvo2mqtt.log. description: Enable Volvo API debug, normaly this can stay off. If enabled, the complete log file can be found under \\<Your HA Host IP>\addons\volvo2mqtt\log\volvo2mqtt.log.
disable_logging:
name: Disable logging
description: Disable logging completely to reduce IO and SD card access.
mqtt: mqtt:
name: MQTT Broker settings name: MQTT Broker settings
description: Leave the settings as they are if you are using the MQTT Mosquitto Addon. If not, take a look at the readme from volvo2mqtt. description: Leave the settings as they are if you are using the MQTT Mosquitto Addon. If not, take a look at the readme from volvo2mqtt.
+16
View File
@@ -68,6 +68,10 @@ def setup_logging():
if settings["debug"]: if settings["debug"]:
logger.setLevel(logging.DEBUG) logger.setLevel(logging.DEBUG)
if "disable_logging" in settings:
if settings["disable_logging"]:
logger.setLevel(logging.ERROR)
def check_existing_folder(): def check_existing_folder():
Path("/addons/volvo2mqtt/log/").mkdir(parents=True, exist_ok=True) Path("/addons/volvo2mqtt/log/").mkdir(parents=True, exist_ok=True)
@@ -136,3 +140,15 @@ def set_mqtt_settings():
config.settings["mqtt"]["port"] = broker_port config.settings["mqtt"]["port"] = broker_port
config.settings["mqtt"]["username"] = broker_user config.settings["mqtt"]["username"] = broker_user
config.settings["mqtt"]["password"] = broker_pass config.settings["mqtt"]["password"] = broker_pass
def validate_settings():
if not os.environ.get("DEV_MODE"):
setting_keys = settings.volvoData["vccapikey"]
if isinstance(setting_keys, list):
if len(setting_keys) > 3:
raise Exception("Settings invalid! Maximum allowed vccapikeys are three!")
update_interval = settings["updateInterval"]
if update_interval < 60:
raise Exception("Settings invalid! Minimum allowed update interval is 60 seconds!")
+57 -51
View File
@@ -8,6 +8,7 @@ from threading import currentThread
from datetime import datetime, timedelta 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 json import JSONDecodeError
from const import charging_system_states, charging_connection_states, door_states, window_states, \ from const import charging_system_states, charging_connection_states, door_states, window_states, \
OAUTH_URL, VEHICLES_URL, VEHICLE_DETAILS_URL, RECHARGE_STATE_URL, CLIMATE_START_URL, \ OAUTH_URL, VEHICLES_URL, VEHICLE_DETAILS_URL, RECHARGE_STATE_URL, CLIMATE_START_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, \
@@ -119,28 +120,32 @@ def get_vehicles():
def get_vcc_api_keys(used_key=None): def get_vcc_api_keys(used_key=None):
setting_keys = settings.volvoData["vccapikey"] working_keys = None
if isinstance(setting_keys, str):
set_key_state(setting_keys)
elif isinstance(setting_keys, list):
for key in setting_keys:
set_key_state(key)
logging.debug(str(vcc_api_keys)) while not working_keys:
working_keys = [key["key"] for key in vcc_api_keys if not key.get("extended") and key.get('key') != used_key] setting_keys = settings.volvoData["vccapikey"]
if len(working_keys) < 1: if isinstance(setting_keys, str):
logging.warning("No working VCCAPIKEY found, waiting 10 minutes. Then trying again!") set_key_state(setting_keys)
mqtt.send_offline() elif isinstance(setting_keys, list):
time.sleep(600) for key in setting_keys:
get_vcc_api_keys(used_key=None) set_key_state(key)
return None
mqtt.send_heartbeat() logging.debug(str(vcc_api_keys))
session.headers.update({"vcc-api-key": working_keys[0]}) working_keys = [key["key"] for key in vcc_api_keys if not key.get("extended") and key.get('key') != used_key]
logging.info("Using VCCAPIKEY: " + working_keys[0]) if len(working_keys) < 1:
for key_dict in vcc_api_keys: used_key = None
if key_dict["key"] == working_keys[0]: logging.warning("No working VCCAPIKEY found, waiting 10 minutes. Then trying again!")
key_dict["in_use"] = True mqtt.send_offline()
time.sleep(600)
else:
mqtt.send_heartbeat()
session.headers.update({"vcc-api-key": working_keys[0]})
logging.info("Using VCCAPIKEY: " + working_keys[0])
for key_dict in vcc_api_keys:
if key_dict["key"] == working_keys[0]:
key_dict["in_use"] = True
logging.debug(str(vcc_api_keys))
def set_key_state(key): def set_key_state(key):
@@ -160,6 +165,8 @@ def set_key_state(key):
def check_vcc_api_key(test_key, extended_until=None): def check_vcc_api_key(test_key, extended_until=None):
if extended_until: if extended_until:
if extended_until >= datetime.now(): if extended_until >= datetime.now():
logging.warning("VCCAPIKEY " + test_key + " is extended and will be reusable at: "
+ format_datetime(extended_until, format="medium", locale=settings["babelLocale"]))
return True, extended_until return True, extended_until
if datetime.now(util.TZ) >= token_expires_at: if datetime.now(util.TZ) >= token_expires_at:
@@ -238,11 +245,6 @@ def check_supported_endpoints():
# If battery charge level could be found in recharge-api, skip the second battery charge sensor # If battery charge level could be found in recharge-api, skip the second battery charge sensor
continue continue
if entity["id"] == "engine_state" and entity["url"] == ENGINE_DIAGNOSTICS_URL \
and any("engine_state" in d["id"] for d in supported_endpoints[vin]):
# If engine state could be found in engine state endpoint, skip the second engine running sensor
continue
if entity.get('url'): if entity.get('url'):
state = api_call(entity["url"], "GET", vin, entity["id"]) state = api_call(entity["url"], "GET", vin, entity["id"])
else: else:
@@ -341,7 +343,11 @@ def api_call(url, method, vin, sensor_id=None, force_update=False, key_change=Fa
return None return None
logging.debug("Response status code: " + str(response.status_code)) logging.debug("Response status code: " + str(response.status_code))
data = response.json() try:
data = response.json()
except JSONDecodeError as e:
logging.error("Fetched json decode error, Volvo API seems to return garbage. Skipping update. Error: " + str(e))
return None
if response.status_code == 200: if response.status_code == 200:
logging.debug(response.text) logging.debug(response.text)
@@ -437,7 +443,7 @@ def parse_api_data(data, sensor_id=None):
return "" return ""
return None return None
elif sensor_id == "lock_status": elif sensor_id == "lock_status":
return data["carLocked"]["value"] if util.keys_exists(data, "carLocked") else None return data["centralLock"]["value"] if util.keys_exists(data, "centralLock") else None
elif sensor_id == "odometer": elif sensor_id == "odometer":
multiplier = 1 multiplier = 1
if util.keys_exists(settings["volvoData"], "odometerMultiplier"): if util.keys_exists(settings["volvoData"], "odometerMultiplier"):
@@ -449,44 +455,44 @@ def parse_api_data(data, sensor_id=None):
return util.convert_metric_values(int(data["odometer"]["value"]) * multiplier) \ return util.convert_metric_values(int(data["odometer"]["value"]) * multiplier) \
if util.keys_exists(data, "odometer") else None if util.keys_exists(data, "odometer") else None
elif sensor_id == "window_front_left": elif sensor_id == "window_front_left":
return window_states[data["frontLeftWindowOpen"]["value"]] if util.keys_exists(data, "frontLeftWindowOpen") \ return window_states[data["frontLeftWindow"]["value"]] if util.keys_exists(data, "frontLeftWindow") \
else None else None
elif sensor_id == "window_front_right": elif sensor_id == "window_front_right":
return window_states[data["frontRightWindowOpen"]["value"]] if util.keys_exists(data, "frontRightWindowOpen") \ return window_states[data["frontRightWindow"]["value"]] if util.keys_exists(data, "frontRightWindow") \
else None else None
elif sensor_id == "window_rear_left": elif sensor_id == "window_rear_left":
return window_states[data["rearLeftWindowOpen"]["value"]] if util.keys_exists(data, "rearLeftWindowOpen") \ return window_states[data["rearLeftWindow"]["value"]] if util.keys_exists(data, "rearLeftWindow") \
else None else None
elif sensor_id == "window_rear_right": elif sensor_id == "window_rear_right":
return window_states[data["rearRightWindowOpen"]["value"]] if util.keys_exists(data, "rearRightWindowOpen") \ return window_states[data["rearRightWindow"]["value"]] if util.keys_exists(data, "rearRightWindow") \
else None else None
elif sensor_id == "door_front_left": elif sensor_id == "door_front_left":
return door_states[data["frontLeftDoorOpen"]["value"]] if util.keys_exists(data, "frontLeftDoorOpen") else None return door_states[data["frontLeftDoor"]["value"]] if util.keys_exists(data, "frontLeftDoor") else None
elif sensor_id == "door_front_right": elif sensor_id == "door_front_right":
return door_states[data["frontRightDoorOpen"]["value"]] \ return door_states[data["frontRightDoor"]["value"]] \
if util.keys_exists(data, "frontRightDoorOpen") else None if util.keys_exists(data, "frontRightDoor") else None
elif sensor_id == "door_rear_left": elif sensor_id == "door_rear_left":
return door_states[data["rearLeftDoorOpen"]["value"]] if util.keys_exists(data, "rearLeftDoorOpen") else None return door_states[data["rearLeftDoor"]["value"]] if util.keys_exists(data, "rearLeftDoor") else None
elif sensor_id == "door_rear_right": elif sensor_id == "door_rear_right":
return door_states[data["rearRightDoorOpen"]["value"]] if util.keys_exists(data, "rearRightDoorOpen") else None return door_states[data["rearRightDoor"]["value"]] if util.keys_exists(data, "rearRightDoor") else None
elif sensor_id == "tailgate": elif sensor_id == "tailgate":
return door_states[data["tailGateOpen"]["value"]] if util.keys_exists(data, "tailGateOpen") else None return door_states[data["tailgate"]["value"]] if util.keys_exists(data, "tailgate") else None
elif sensor_id == "sunroof": elif sensor_id == "sunroof":
return door_states[data["sunRoofOpen"]["value"]] if util.keys_exists(data, "sunRoofOpen") else None return door_states[data["sunroof"]["value"]] if util.keys_exists(data, "sunroof") else None
elif sensor_id == "engine_hood": elif sensor_id == "engine_hood":
return door_states[data["hoodOpen"]["value"]] if util.keys_exists(data, "hoodOpen") else None return door_states[data["hood"]["value"]] if util.keys_exists(data, "hood") else None
elif sensor_id == "tank_lid": elif sensor_id == "tank_lid":
return door_states[data["tankLidOpen"]["value"]] if util.keys_exists(data, "tankLidOpen") else None return door_states[data["tankLid"]["value"]] if util.keys_exists(data, "tankLid") else None
elif sensor_id == "tyre_front_left": elif sensor_id == "tyre_front_left":
return data["frontLeftTyrePressure"]["value"] if util.keys_exists(data, "frontLeftTyrePressure") else None return data["frontLeft"]["value"] if util.keys_exists(data, "frontLeft") else None
elif sensor_id == "tyre_front_right": elif sensor_id == "tyre_front_right":
return data["frontRightTyrePressure"]["value"] if util.keys_exists(data, "frontRightTyrePressure") else None return data["frontRight"]["value"] if util.keys_exists(data, "frontRight") else None
elif sensor_id == "tyre_rear_left": elif sensor_id == "tyre_rear_left":
return data["rearLeftTyrePressure"]["value"] if util.keys_exists(data, "rearLeftTyrePressure") else None return data["rearLeft"]["value"] if util.keys_exists(data, "rearLeft") else None
elif sensor_id == "tyre_rear_right": elif sensor_id == "tyre_rear_right":
return data["rearRightTyrePressure"]["value"] if util.keys_exists(data, "rearRightTyrePressure") else None return data["rearRight"]["value"] if util.keys_exists(data, "rearRight") else None
elif sensor_id == "engine_state": elif sensor_id == "engine_state":
return engine_states[data["engineRunning"]["value"]] if util.keys_exists(data, "engineRunning") else None return engine_states[data["engineStatus"]["value"]] if util.keys_exists(data, "engineStatus") else None
elif sensor_id == "fuel_level": elif sensor_id == "fuel_level":
if util.keys_exists(data, "fuelAmount"): if util.keys_exists(data, "fuelAmount"):
fuel_amount = float(data["fuelAmount"]["value"]) fuel_amount = float(data["fuelAmount"]["value"])
@@ -537,16 +543,16 @@ def parse_api_data(data, sensor_id=None):
elif sensor_id == "hours_to_service": elif sensor_id == "hours_to_service":
return data["engineHoursToService"]["value"] if util.keys_exists(data, "engineHoursToService") else None return data["engineHoursToService"]["value"] if util.keys_exists(data, "engineHoursToService") else None
elif sensor_id == "km_to_service": elif sensor_id == "km_to_service":
if util.keys_exists(data, "kmToService"): if util.keys_exists(data, "distanceToService"):
km_to_service = int(data["kmToService"]["value"]) km_to_service = int(data["distanceToService"]["value"])
if km_to_service > 0: if km_to_service > 0:
return util.convert_metric_values(data["kmToService"]["value"]) return util.convert_metric_values(data["distanceToService"]["value"])
return None return None
elif sensor_id == "months_to_service": elif sensor_id == "months_to_service":
return data["monthsToService"]["value"] if util.keys_exists(data, "monthsToService") else None return data["timeToService"]["value"] if util.keys_exists(data, "timeToService") else None
elif sensor_id == "service_warning_status": elif sensor_id == "service_warning_status":
return data["serviceWarningStatus"]["value"] if util.keys_exists(data, "serviceWarningStatus") else None return data["serviceWarning"]["value"] if util.keys_exists(data, "serviceWarning") else None
elif sensor_id == "service_warning_trigger": elif sensor_id == "service_warning_trigger":
return data["serviceWarningTrigger"]["value"] if util.keys_exists(data, "serviceWarningTrigger") else None return data["serviceTrigger"]["value"] if util.keys_exists(data, "serviceTrigger") else None
else: else:
return None return None