Compare commits

...
Author SHA1 Message Date
Linus Dietz 016a0193e2 Rename engine runtime sensor 2023-11-22 10:07:49 +01:00
Linus Dietz c3a798e397 Bump version 2023-11-22 08:51:31 +01:00
Linus Dietz 89417696bd Add engine start and stop logic 2023-11-22 08:49:36 +01:00
Linus Dietz d55dc25647 Add engine runtime number and engine start switch entities 2023-11-22 08:14:20 +01:00
Linus Dietz 1d193270a4 Add available commands check 2023-11-22 07:37:43 +01:00
Linus Dietz f03ff8953c Fix #133 2023-11-22 07:15:20 +01:00
Linus Dietz 2e95e71661 Fix Backend State Sensor returning null 2023-11-21 18:50:55 +01:00
Linus Dietz 126cc37269 Move vehicles details endpoint to v2 // Try to fix #131 2023-11-21 13:59:47 +01:00
Linus Dietz cb10fe07c3 Move vehicles details endpoint to v2 2023-11-21 09:46:16 +01:00
Linus Dietz f3244ccdc0 Move vehicles endpoint to v2 2023-11-21 09:42:56 +01:00
Linus Dietz 1223853208 Bump version 2023-11-21 08:59:25 +01:00
Linus Dietz 1f0c52b5a7 Add average energy consumption, distance to empty battery and washer fluid level sensor 2023-11-21 08:55:27 +01:00
Linus Dietz 161c02fc11 Support force update for backend state 2023-11-21 08:25:33 +01:00
Linus Dietz be4a1b6791 Use cached requests for fuel endpoints 2023-11-21 08:19:26 +01:00
Linus Dietz 8803f9ff1d Add api backend state sensor 2023-11-20 19:33:27 +01:00
Linus Dietz 843e00acd1 Move battery charge to v2 endpoint 2023-11-20 18:34:57 +01:00
Linus Dietz 3be0a4edbf Bump version 2023-11-20 07:49:34 +01:00
Linus Dietz 22cf71f007 Fix distance to empty sensor 2023-11-20 07:46:37 +01:00
Linus Dietz b7feb9713b Remove unused service warning trigger 2023-11-20 07:43:42 +01:00
Linus DietzandGitHub 1f53ef0198 Update CHANGELOG.md 2023-11-17 17:58:31 +01:00
Linus Dietz d07e6dfe6e Add "Time to Service" sensor 2023-11-17 17:57:30 +01:00
Linus Dietz 80473d5120 Fix apparmor for Debian 12 2023-11-11 14:56:41 +01:00
Linus Dietz 81b2771c80 Fix AJAR state for windows 2023-11-11 14:51:52 +01:00
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
7 changed files with 310 additions and 94 deletions
+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
+79 -1
View File
@@ -1,3 +1,81 @@
## v1.9.0
### 🚀 Features:
- Add check for supported commands
- Add Engine start/stop and Engine runtime minutes for supported cars
## v1.8.19
### 🐛 Bug Fixes:
- Fix Estimated Charging Time not changing to zero #133
## v1.8.18
### 🐛 Bug Fixes:
- Fix Backend State Sensor returning `null`
## v1.8.17
### 🚀 Features:
- Move vehicle and vehicle details to v2 endpoint
### 🐛 Bug Fixes:
- Fix Battery Charge Level icon update
## v1.8.16
### 🚀 Features:
- Add `Average energy consumption`, `Distance to empty battery` and `Washer fluid level` sensors
- Support force update for `Backend state` Sensor
- Use cached requests for fuel endpoint
## v1.8.15
### 🚀 Features:
- Move `batteryChargeLevel` to the new v2 fuel endpoint
- Add api backend status sensor
## v1.8.14
### 🐛 Bug Fixes:
- Fix distance to empty sensor #127
- Remove unused service warning trigger sensor
## v1.8.13
### 🐛 Bug Fixes:
- Add "Time to Service" sensor #125<p>
<b>Breaking change: The "Months to Service" sensor will be automatically deleted.<br>
"Time to Service" contains this information now.
## v1.8.12
### 🐛 Bug Fixes:
- Fix `AJAR` state for windows
- Fix deprecation from `currentThread` to `current_thread`
## 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 ## v1.8.7
### 🚀 Features: ### 🚀 Features:
@@ -43,7 +121,7 @@
### 🚀 Features: ### 🚀 Features:
- <b>Breaking change: The `vccapikey` setting is now a list. It is possible to - <b>Breaking change: The `vccapikey` setting is now a list. It is possible to
add multiple VCCAPIKEYs. This will be helpful if you are extending your 10.000 call limit some times. add multiple VCCAPIKEYs. This will be helpful if you are extending your 10.000 call limit sometimes.
Change your vccapikey config like this: Change your vccapikey config like this:
Old: Old:
+2
View File
@@ -4,8 +4,10 @@ profile volvo2mqtt flags=(attach_disconnected,mediate_deleted) {
#include <abstractions/base> #include <abstractions/base>
# Capabilities # Capabilities
capability,
file, file,
signal (send) set=(kill,term,int,hup,cont), signal (send) set=(kill,term,int,hup,cont),
network,
# S6-Overlay # S6-Overlay
/init ix, /init ix,
+1 -1
View File
@@ -1,6 +1,6 @@
name: "Volvo2Mqtt" name: "Volvo2Mqtt"
description: "Volvo AAOS MQTT bridge" description: "Volvo AAOS MQTT bridge"
version: "1.8.7" version: "1.9.0"
slug: "volvo2mqtt" slug: "volvo2mqtt"
init: false init: false
url: "https://github.com/Dielee/volvo2mqtt" url: "https://github.com/Dielee/volvo2mqtt"
+25 -16
View File
@@ -1,10 +1,10 @@
from config import settings from config import settings
VERSION = "v1.8.7" VERSION = "v1.9.0"
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/v2/vehicles"
VEHICLE_DETAILS_URL = "https://api.volvocars.com/connected-vehicle/v1/vehicles/{0}" VEHICLE_DETAILS_URL = "https://api.volvocars.com/connected-vehicle/v2/vehicles/{0}"
WINDOWS_STATE_URL = "https://api.volvocars.com/connected-vehicle/v2/vehicles/{0}/windows" WINDOWS_STATE_URL = "https://api.volvocars.com/connected-vehicle/v2/vehicles/{0}/windows"
CLIMATE_START_URL = "https://api.volvocars.com/connected-vehicle/v2/vehicles/{0}/commands/climatization-start" CLIMATE_START_URL = "https://api.volvocars.com/connected-vehicle/v2/vehicles/{0}/commands/climatization-start"
CLIMATE_STOP_URL = "https://api.volvocars.com/connected-vehicle/v2/vehicles/{0}/commands/climatization-stop" CLIMATE_STOP_URL = "https://api.volvocars.com/connected-vehicle/v2/vehicles/{0}/commands/climatization-stop"
@@ -16,11 +16,14 @@ 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" FUEL_BATTERY_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"
VEHICLE_DIAGNOSTICS_URL = "https://api.volvocars.com/connected-vehicle/v2/vehicles/{0}/diagnostics" VEHICLE_DIAGNOSTICS_URL = "https://api.volvocars.com/connected-vehicle/v2/vehicles/{0}/diagnostics"
API_BACKEND_STATUS_URL = "https://oip-dev-bff.euwest1.production.volvo.care/api/v1/backend-status"
SUPPORTED_COMMANDS_URL = "https://api.volvocars.com/connected-vehicle/v2/vehicles/{0}/commands"
ENGINE_START_URL = "https://api.volvocars.com/connected-vehicle/v2/vehicles/{0}/commands/engine-start"
ENGINE_STOP_URL = "https://api.volvocars.com/connected-vehicle/v2/vehicles/{0}/commands/engine-stop"
units = { units = {
"en_GB": { "en_GB": {
@@ -50,8 +53,8 @@ charging_connection_states = {"CONNECTION_STATUS_DISCONNECTED": "Disconnected",
"CONNECTION_STATUS_CONNECTED_DC": "Connected DC", "CONNECTION_STATUS_CONNECTED_AC": "Connected AC", "CONNECTION_STATUS_CONNECTED_DC": "Connected DC", "CONNECTION_STATUS_CONNECTED_AC": "Connected AC",
"CONNECTION_STATUS_FAULT": "Fault"} "CONNECTION_STATUS_FAULT": "Fault"}
window_states = {"CLOSED": "OFF", "OPEN": "ON"} window_states = {"CLOSED": "OFF", "OPEN": "ON", "AJAR": "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 = {
@@ -78,7 +81,7 @@ icon_states = {
supported_entities = [ supported_entities = [
{"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", "device_class": "battery", "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": 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": FUEL_BATTERY_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},
@@ -99,23 +102,29 @@ supported_entities = [
{"name": "Engine Hood", "domain": "binary_sensor", "device_class": "door", "id": "engine_hood", "icon": "car-door-lock", "url": LOCK_STATE_URL}, {"name": "Engine Hood", "domain": "binary_sensor", "device_class": "door", "id": "engine_hood", "icon": "car-door-lock", "url": LOCK_STATE_URL},
{"name": "Tank Lid", "domain": "binary_sensor", "device_class": "door", "id": "tank_lid", "icon": "car-door-lock", "url": LOCK_STATE_URL}, {"name": "Tank Lid", "domain": "binary_sensor", "device_class": "door", "id": "tank_lid", "icon": "car-door-lock", "url": LOCK_STATE_URL},
{"name": "Sunroof", "domain": "binary_sensor", "device_class": "door", "id": "sunroof", "icon": "car-door-lock", "url": WINDOWS_STATE_URL}, {"name": "Sunroof", "domain": "binary_sensor", "device_class": "door", "id": "sunroof", "icon": "car-door-lock", "url": WINDOWS_STATE_URL},
{"name": "Air Conditioning", "domain": "switch", "id": "climate_status", "icon": "air-conditioner"}, {"name": "Air Conditioning", "domain": "switch", "id": "climate_status", "icon": "air-conditioner", "commands": ["CLIMATIZATION_START", "CLIMATIZATION_STOP"]},
{"name": "Lock state", "domain": "lock", "id": "lock_status", "icon": "lock", "url": LOCK_STATE_URL}, {"name": "Engine State", "domain": "switch", "id": "engine_state", "icon": "engine", "url": ENGINE_STATE_URL ,"commands": ["ENGINE_START", "ENGINE_STOP"]},
{"name": "Leave in", "domain": "number", "id": "engine_runtime", "unit": "minutes", "icon": "timer-sand", "commands": ["ENGINE_START", "ENGINE_STOP"], "min": 1, "max": 15, "mode": "box"},
{"name": "Engine State", "domain": "binary_sensor", "device_class": "running", "id": "engine_state", "icon": "engine", "url": ENGINE_STATE_URL},
{"name": "Lock state", "domain": "lock", "id": "lock_status", "icon": "lock", "url": LOCK_STATE_URL, "commands": ["LOCK", "UNLOCK"]},
{"name": "Force Update Data", "domain": "button", "id": "update_data", "icon": "update", "url": ""}, {"name": "Force Update Data", "domain": "button", "id": "update_data", "icon": "update", "url": ""},
{"name": "Location", "domain": "device_tracker", "id": "location", "icon": "map-marker-radius", "url": LOCATION_STATE_URL}, {"name": "Location", "domain": "device_tracker", "id": "location", "icon": "map-marker-radius", "url": LOCATION_STATE_URL},
{"name": "Tire Front Left", "domain": "sensor", "id": "tyre_front_left", "icon": "car-tire-alert", "url": TYRE_STATE_URL}, {"name": "Tire Front Left", "domain": "sensor", "id": "tyre_front_left", "icon": "car-tire-alert", "url": TYRE_STATE_URL},
{"name": "Tire Front Right", "domain": "sensor", "id": "tyre_front_right", "icon": "car-tire-alert", "url": TYRE_STATE_URL}, {"name": "Tire Front Right", "domain": "sensor", "id": "tyre_front_right", "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 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": "Fuel Level", "domain": "sensor", "id": "fuel_level", "unit": "liters", "icon": "fuel", "url": FUEL_BATTERY_STATE_URL, "state_class": "measurement"},
{"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": "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": "Average Energy Consumption", "domain": "sensor", "id": "average_energy_consumption", "unit": "kwh", "icon": "car-electric", "url": STATISTICS_URL},
{"name": "Distance to Empty Tank", "domain": "sensor", "id": "distance_to_empty_tank", "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 Battery", "domain": "sensor", "id": "distance_to_empty_battery", "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": "Average Speed", "domain": "sensor", "id": "average_speed", "unit": "km/h" if not units.get(settings["babelLocale"]) else units[settings["babelLocale"]]["average_speed"]["unit"], "icon": "speedometer", "url": STATISTICS_URL, "state_class": "measurement"}, {"name": "Average Speed", "domain": "sensor", "id": "average_speed", "unit": "km/h" if not units.get(settings["babelLocale"]) else units[settings["babelLocale"]]["average_speed"]["unit"], "icon": "speedometer", "url": STATISTICS_URL, "state_class": "measurement"},
{"name": "Hours to Service", "domain": "sensor", "id": "hours_to_service", "unit": "hour", "icon": "wrench-clock", "url": VEHICLE_DIAGNOSTICS_URL}, {"name": "Hours to Service", "domain": "sensor", "id": "hours_to_service", "unit": "hour", "icon": "wrench-clock", "url": VEHICLE_DIAGNOSTICS_URL},
{"name": "Distance to Service", "domain": "sensor", "id": "km_to_service", "unit": "km" if not units.get(settings["babelLocale"]) else units[settings["babelLocale"]]["distance_to_service"]["unit"], "icon": "wrench-clock", "url": VEHICLE_DIAGNOSTICS_URL}, {"name": "Distance to Service", "domain": "sensor", "id": "km_to_service", "unit": "km" if not units.get(settings["babelLocale"]) else units[settings["babelLocale"]]["distance_to_service"]["unit"], "icon": "wrench-clock", "url": VEHICLE_DIAGNOSTICS_URL},
{"name": "Months to Service", "domain": "sensor", "id": "months_to_service", "unit": "month", "icon": "wrench-clock", "url": VEHICLE_DIAGNOSTICS_URL}, {"name": "Time to Service", "domain": "sensor", "id": "time_to_service", "icon": "wrench-clock", "url": VEHICLE_DIAGNOSTICS_URL},
{"name": "Service warning status", "domain": "sensor", "id": "service_warning_status", "icon": "alert-outline", "url": VEHICLE_DIAGNOSTICS_URL}, {"name": "Service warning status", "domain": "sensor", "id": "service_warning_status", "icon": "alert-outline", "url": VEHICLE_DIAGNOSTICS_URL},
{"name": "Service warning trigger", "domain": "sensor", "id": "service_warning_trigger", "icon": "alert-outline", "url": VEHICLE_DIAGNOSTICS_URL} {"name": "Washer Fluid Level warning", "domain": "sensor", "id": "washer_fluid_warning", "icon": "alert-outline", "url": VEHICLE_DIAGNOSTICS_URL},
{"name": "API Backend status", "domain": "sensor", "id": "api_backend_status", "icon": "alert"}
] ]
old_entity_ids = ["months_to_service", "service_warning_trigger", "distance_to_empty"]
+72 -25
View File
@@ -10,8 +10,7 @@ from datetime import datetime
from babel.dates import format_datetime from babel.dates import format_datetime
from config import settings from config import settings
from const import CLIMATE_START_URL, CLIMATE_STOP_URL, CAR_LOCK_URL, \ from const import CLIMATE_START_URL, CLIMATE_STOP_URL, CAR_LOCK_URL, \
CAR_UNLOCK_URL, availability_topic, icon_states CAR_UNLOCK_URL, ENGINE_START_URL, ENGINE_STOP_URL, availability_topic, icon_states, old_entity_ids
mqtt_client: mqtt.Client mqtt_client: mqtt.Client
subscribed_topics = [] subscribed_topics = []
@@ -21,11 +20,12 @@ climate_timer = {}
engine_status = {} engine_status = {}
devices = {} devices = {}
active_schedules = {} active_schedules = {}
engine_runtime = 1
def connect(): def connect():
client = mqtt.Client("volvoAAOS2mqtt") if os.environ.get("IS_HA_ADDON") \ client = mqtt.Client("volvoAAOS2mqtt") if os.environ.get("IS_HA_ADDON") \
else mqtt.Client("volvoAAOS2mqtt_" + settings.volvoData["username"]) 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"]:
@@ -71,14 +71,14 @@ def send_car_images(vin, data, device):
if entity["id"] == "exterior_image": if entity["id"] == "exterior_image":
mqtt_client.publish( mqtt_client.publish(
url_topic, url_topic,
data["images"]["exteriorDefaultUrl"], data["images"]["exteriorImageUrl"],
retain=True retain=True
) )
if entity["id"] == "interior_image": if entity["id"] == "interior_image":
mqtt_client.publish( mqtt_client.publish(
url_topic, url_topic,
data["images"]["interiorDefaultUrl"], data["images"]["internalImageUrl"],
retain=True retain=True
) )
@@ -90,7 +90,7 @@ def on_connect(client, userdata, flags, rc):
mqtt_client.subscribe(topic) mqtt_client.subscribe(topic)
def on_disconnect(client, userdata, rc): def on_disconnect(client, userdata, rc):
logging.warning("MQTT disconnected, reconnecting automatically") logging.warning("MQTT disconnected, reconnecting automatically")
@@ -115,6 +115,15 @@ def on_message(client, userdata, msg):
elif "update_data" in msg.topic: elif "update_data" in msg.topic:
if payload == "PRESS": if payload == "PRESS":
update_car_data(True) update_car_data(True)
elif "engine_runtime" in msg.topic:
global engine_runtime
engine_runtime = int(payload)
logging.debug("Updated engine runtime to " + str(engine_runtime) + " minutes!")
elif "engine_state" in msg.topic:
if payload == "ON":
start_engine(vin)
elif payload == "OFF":
stop_engine(vin)
elif "schedule" in msg.topic: elif "schedule" in msg.topic:
try: try:
d = json.loads(payload) d = json.loads(payload)
@@ -141,7 +150,7 @@ def start_climate_timer(d, vin):
return None return None
if timer_seconds > 0: if timer_seconds > 0:
Timer(timer_seconds, activate_climate_timer, (vin, start_datetime.isoformat(), )).start() Timer(timer_seconds, activate_climate_timer, (vin, start_datetime.isoformat(),)).start()
active_schedules[vin]["timers"].append(start_datetime.isoformat()) active_schedules[vin]["timers"].append(start_datetime.isoformat())
logging.debug("Climate timer set to " + str(start_datetime)) logging.debug("Climate timer set to " + str(start_datetime))
update_car_data() update_car_data()
@@ -212,6 +221,23 @@ def start_climate(vin):
update_car_data() update_car_data()
def start_engine(vin):
body = {"runtimeMinutes": engine_runtime}
# Start the api call in another thread for HA performance
Thread(target=volvo.api_call, args=(ENGINE_START_URL, "POST", vin, None, None, None, body)).start()
# Set and update switch status
update_car_data(False, {"entity_id": "engine_state", "vin": vin, "state": "ON"})
def stop_engine(vin):
# Start the api call in another thread for HA performance
Thread(target=volvo.api_call, args=(ENGINE_STOP_URL, "POST", vin)).start()
# Set and update switch status
update_car_data(False, {"entity_id": "engine_state", "vin": vin, "state": "OFF"})
def update_loop(): def update_loop():
create_ha_devices() create_ha_devices()
while True: while True:
@@ -227,7 +253,7 @@ def update_car_data(force_update=False, overwrite={}):
last_data_update = format_datetime(datetime.now(util.TZ), 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"] in ["button", "number"]:
continue continue
ov_entity_id = "" ov_entity_id = ""
@@ -244,6 +270,11 @@ def update_car_data(force_update=False, overwrite={}):
state = last_data_update state = last_data_update
elif entity["id"] == "active_schedules": elif entity["id"] == "active_schedules":
state = active_schedules[vin] state = active_schedules[vin]
elif entity["id"] == "api_backend_status":
if force_update:
state = volvo.get_backend_status()
else:
state = volvo.backend_status
else: else:
if entity["id"] == ov_entity_id and vin == ov_vin: if entity["id"] == ov_entity_id and vin == ov_vin:
state = ov_state state = ov_state
@@ -255,17 +286,21 @@ 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 or state == 0:
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):
icon_config = icon_states.get(entity["id"]) icon_config = icon_states.get(entity["id"])
if icon_config and state: if icon_config and state:
if state.replace(".", "").isnumeric(): logging.debug("Entity " + entity["id"] + " state " + str(state))
if isinstance(state, float):
icon = util.get_icon_between(icon_config, state)
elif state.replace(".", "").isnumeric():
state = float(state) state = float(state)
icon = util.get_icon_between(icon_config, state) icon = util.get_icon_between(icon_config, state)
else: else:
@@ -292,7 +327,7 @@ def update_ha_device(entity, vin, state):
if entity.get("state_class"): if entity.get("state_class"):
config["state_class"] = entity["state_class"] config["state_class"] = entity["state_class"]
if entity.get("domain") == "device_tracker": if entity.get("domain") == "device_tracker":
config["json_attributes_topic"] = f"homeassistant/{entity['domain']}/{vin}_{entity['id']}/attributes" config["json_attributes_topic"] = f"homeassistant/{entity['domain']}/{vin}_{entity['id']}/attributes"
@@ -316,15 +351,15 @@ def create_ha_devices():
devices[vin] = device devices[vin] = device
for entity in volvo.supported_endpoints[vin]: for entity in volvo.supported_endpoints[vin]:
config = { config = {
"name": entity['name'], "name": entity['name'],
"object_id": f"volvo_{vin}_{entity['id']}", "object_id": f"volvo_{vin}_{entity['id']}",
"schema": "state", "schema": "state",
"icon": f"mdi:{entity['icon']}", "icon": f"mdi:{entity['icon']}",
"state_topic": f"homeassistant/{entity['domain']}/{vin}_{entity['id']}/state", "state_topic": f"homeassistant/{entity['domain']}/{vin}_{entity['id']}/state",
"device": device, "device": device,
"unique_id": f"volvoAAOS2mqtt_{vin}_{entity['id']}", "unique_id": f"volvoAAOS2mqtt_{vin}_{entity['id']}",
"availability_topic": availability_topic "availability_topic": availability_topic
} }
if entity.get("device_class"): if entity.get("device_class"):
config["device_class"] = entity["device_class"] config["device_class"] = entity["device_class"]
@@ -336,11 +371,17 @@ def create_ha_devices():
if entity.get("domain") == "device_tracker" or entity.get("id") == "active_schedules": if entity.get("domain") == "device_tracker" or entity.get("id") == "active_schedules":
config["json_attributes_topic"] = f"homeassistant/{entity['domain']}/{vin}_{entity['id']}/attributes" config["json_attributes_topic"] = f"homeassistant/{entity['domain']}/{vin}_{entity['id']}/attributes"
elif entity.get("domain") in ["switch", "lock", "button"]: elif entity.get("domain") in ["switch", "lock", "button", "number"]:
command_topic = f"homeassistant/{entity['domain']}/{vin}_{entity['id']}/command" command_topic = f"homeassistant/{entity['domain']}/{vin}_{entity['id']}/command"
config["command_topic"] = command_topic config["command_topic"] = command_topic
subscribed_topics.append(command_topic) subscribed_topics.append(command_topic)
mqtt_client.subscribe(command_topic) mqtt_client.subscribe(command_topic)
if entity["domain"] == "number":
config["min"] = entity["min"]
config["max"] = entity["max"]
config["mode"] = entity["mode"]
elif entity.get("domain") == "image": elif entity.get("domain") == "image":
config["url_topic"] = f"homeassistant/{entity['domain']}/{vin}_{entity['id']}/image_url" config["url_topic"] = f"homeassistant/{entity['domain']}/{vin}_{entity['id']}/image_url"
@@ -360,3 +401,9 @@ def send_heartbeat():
def send_offline(): def send_offline():
mqtt_client.publish(availability_topic, "offline") mqtt_client.publish(availability_topic, "offline")
def delete_old_entities():
for vin in volvo.vins:
for entity in old_entity_ids:
topic = f"homeassistant/sensor/volvoAAOS2mqtt/{vin}_{entity}/config"
mqtt_client.publish(topic, payload="", retain=True)
+130 -51
View File
@@ -1,17 +1,20 @@
import json
import logging import logging
import requests import requests
import mqtt import mqtt
import util import util
import time import time
import re import re
from threading import currentThread from threading import current_thread, Thread
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, FUEL_BATTERY_STATE_URL, \
STATISTICS_URL, ENGINE_DIAGNOSTICS_URL, engine_states STATISTICS_URL, ENGINE_DIAGNOSTICS_URL, API_BACKEND_STATUS_URL, SUPPORTED_COMMANDS_URL, \
ENGINE_STATE_URL, engine_states
session = requests.Session() session = requests.Session()
session.headers = { session.headers = {
@@ -26,6 +29,8 @@ vins = []
supported_endpoints = {} supported_endpoints = {}
cached_requests = {} cached_requests = {}
vcc_api_keys = [] vcc_api_keys = []
supported_commands = []
backend_status = ""
def authorize(): def authorize():
@@ -52,7 +57,9 @@ def authorize():
get_vcc_api_keys() get_vcc_api_keys()
get_vehicles() get_vehicles()
get_supported_commands()
check_supported_endpoints() check_supported_endpoints()
Thread(target=backend_status_loop).start()
else: else:
message = auth.json() message = auth.json()
raise Exception(message["error_description"]) raise Exception(message["error_description"])
@@ -87,6 +94,19 @@ def refresh_auth():
refresh_token = data["refresh_token"] refresh_token = data["refresh_token"]
def get_supported_commands():
global supported_commands
for vin in vins:
commands = session.get(SUPPORTED_COMMANDS_URL.format(vin))
data = commands.json()
if commands.status_code == 200:
for command in data["data"]:
supported_commands.append(command["command"])
else:
logging.error("Error getting supported commands: " + str(commands.status_code) + ". " + str(data["error"].get("message")))
logging.debug("Supported commands: " + str(supported_commands))
def get_vehicles(): def get_vehicles():
global vins global vins
if not settings.volvoData["vin"]: if not settings.volvoData["vin"]:
@@ -99,9 +119,8 @@ def get_vehicles():
else: else:
raise Exception("No vehicle in account " + settings.volvoData["username"] + " found.") raise Exception("No vehicle in account " + settings.volvoData["username"] + " found.")
else: else:
error = vehicles.json()
raise Exception( raise Exception(
"Error getting vehicles: " + str(vehicles.status_code) + ". " + str(error["error"].get("message"))) "Error getting vehicles: " + str(vehicles.status_code) + ". " + str(data["error"].get("message")))
else: else:
if isinstance(settings.volvoData["vin"], list): if isinstance(settings.volvoData["vin"], list):
# If setting is a list, copy # If setting is a list, copy
@@ -115,6 +134,7 @@ def get_vehicles():
else: else:
initialize_climate(vins) initialize_climate(vins)
initialize_scheduler(vins) initialize_scheduler(vins)
mqtt.delete_old_entities()
logging.info("Vin: " + str(vins) + " found!") logging.info("Vin: " + str(vins) + " found!")
@@ -239,26 +259,43 @@ def check_supported_endpoints():
for vin in vins: for vin in vins:
supported_endpoints[vin] = [] supported_endpoints[vin] = []
for entity in supported_entities: for entity in supported_entities:
if entity["id"] == "battery_charge_level" and entity["url"] == BATTERY_CHARGE_STATE_URL \ if entity["id"] == "battery_charge_level" and entity["url"] == FUEL_BATTERY_STATE_URL \
and any("battery_charge_level" in d["id"] for d in supported_endpoints[vin]): and any("battery_charge_level" in d["id"] for d in supported_endpoints[vin]):
# 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 \ if entity["id"] == "engine_state" and entity["url"] == ENGINE_STATE_URL \
and any("engine_state" in d["id"] for d in supported_endpoints[vin]): 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 # If engine state is supported with commands, skip engine state sensor, as switch
# represents the actual engine state
continue continue
if entity.get('url'): if entity["domain"] in ["switch", "lock", "number"]:
state = api_call(entity["url"], "GET", vin, entity["id"]) state = check_supported_command(entity["commands"])
else: else:
state = "" if entity.get('url'):
state = api_call(entity["url"], "GET", vin, entity["id"])
else:
state = ""
if state is not None: if state is not None:
logging.info("Success! " + entity["name"] + " is supported by your vehicle.") logging.info("Success! " + entity["name"] + " (" + entity["domain"] + ") is supported by your vehicle.")
supported_endpoints[vin].append(entity) supported_endpoints[vin].append(entity)
else: else:
logging.info("Failed, " + entity["name"] + " is unfortunately not supported by your vehicle.") logging.info("Failed, " + entity["name"] + " (" + entity["domain"] + ") is unfortunately not supported by your vehicle.")
def check_supported_command(entity_commands):
commands_supported = True
for entity_command in entity_commands:
if entity_command not in supported_commands:
commands_supported = False
break
if commands_supported:
return ""
else:
return None
def initialize_scheduler(vins): def initialize_scheduler(vins):
@@ -307,7 +344,7 @@ def check_engine_status(vin):
# Exit thread as engine state is unsupported by car # Exit thread as engine state is unsupported by car
return None return None
t = currentThread() t = current_thread()
while getattr(t, "do_run", True): while getattr(t, "do_run", True):
engine_state = api_call(endpoint_url, "GET", vin, "engine_state", True) engine_state = api_call(endpoint_url, "GET", vin, "engine_state", True)
if engine_state == "ON": if engine_state == "ON":
@@ -317,12 +354,37 @@ def check_engine_status(vin):
time.sleep(5) time.sleep(5)
def api_call(url, method, vin, sensor_id=None, force_update=False, key_change=False): def backend_status_loop():
while True:
get_backend_status()
# Update every hour
time.sleep(3600)
def get_backend_status():
global backend_status
response = session.get(API_BACKEND_STATUS_URL, timeout=15)
try:
data = response.json()
if util.keys_exists(data, "message"):
if data["message"]:
backend_status = data["message"]
else:
backend_status = "NO_WARNING"
else:
backend_status = "NO_WARNING"
except JSONDecodeError as e:
backend_status = "NO_WARNING"
return backend_status
def api_call(url, method, vin, sensor_id=None, force_update=False, key_change=False, body=None):
if datetime.now(util.TZ) >= token_expires_at: if datetime.now(util.TZ) >= token_expires_at:
refresh_auth() refresh_auth()
if url in [RECHARGE_STATE_URL, WINDOWS_STATE_URL, LOCK_STATE_URL, TYRE_STATE_URL, if url in [RECHARGE_STATE_URL, WINDOWS_STATE_URL, LOCK_STATE_URL, TYRE_STATE_URL,
STATISTICS_URL, ENGINE_DIAGNOSTICS_URL]: STATISTICS_URL, ENGINE_DIAGNOSTICS_URL, FUEL_BATTERY_STATE_URL]:
# Minimize API calls for endpoints with multiple values # Minimize API calls for endpoints with multiple values
response = cached_request(url, method, vin, force_update, key_change) response = cached_request(url, method, vin, force_update, key_change)
if response is None: if response is None:
@@ -338,7 +400,7 @@ def api_call(url, method, vin, sensor_id=None, force_update=False, key_change=Fa
elif method == "POST": elif method == "POST":
logging.debug("Starting " + method + " call against " + url) logging.debug("Starting " + method + " call against " + url)
try: try:
response = session.post(url.format(vin), timeout=20) response = session.post(url.format(vin), data=json.dumps(body), timeout=20)
except requests.exceptions.RequestException as e: except requests.exceptions.RequestException as e:
logging.error("Error getting data: " + str(e)) logging.error("Error getting data: " + str(e))
return None return None
@@ -347,7 +409,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)
@@ -364,7 +430,7 @@ def api_call(url, method, vin, sensor_id=None, force_update=False, key_change=Fa
return None return None
elif response.status_code == 403 and "message" in data: elif response.status_code == 403 and "message" in data:
if "Out of call volume quota" in data["message"]: if "Out of call volume quota" in data["message"]:
logging.warn("Quota extended. Try to change VCCAPIKEY!") logging.warning("Quota extended. Try to change VCCAPIKEY!")
change_vcc_api_key() change_vcc_api_key()
api_call(url, method, vin, sensor_id, force_update, True) api_call(url, method, vin, sensor_id, force_update, True)
else: else:
@@ -410,7 +476,9 @@ def cached_request(url, method, vin, force_update=False, key_change=False):
def parse_api_data(data, sensor_id=None): def parse_api_data(data, sensor_id=None):
data = data["data"] if sensor_id != "api_backend_status":
data = data["data"]
if sensor_id == "battery_charge_level": if sensor_id == "battery_charge_level":
return data["batteryChargeLevel"]["value"] if util.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":
@@ -443,7 +511,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"):
@@ -455,44 +523,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"])
@@ -534,25 +602,36 @@ def parse_api_data(data, sensor_id=None):
"latitude": raw_data["coordinates"][1], "latitude": raw_data["coordinates"][1],
"gps_accuracy": 1} "gps_accuracy": 1}
return coordinates return coordinates
elif sensor_id == "distance_to_empty": elif sensor_id == "distance_to_empty_tank":
if util.keys_exists(data, "distanceToEmpty"): if util.keys_exists(data, "distanceToEmptyTank"):
distance_to_empty = int(data["distanceToEmpty"]["value"]) distance_to_empty = int(data["distanceToEmptyTank"]["value"])
if distance_to_empty > 0: if distance_to_empty > 0:
return util.convert_metric_values(data["distanceToEmpty"]["value"]) return util.convert_metric_values(data["distanceToEmptyTank"]["value"])
return None
elif sensor_id == "distance_to_empty_battery":
if util.keys_exists(data, "distanceToEmptyBattery"):
distance_to_empty = int(data["distanceToEmptyBattery"]["value"])
if distance_to_empty > 0:
return util.convert_metric_values(data["distanceToEmptyBattery"]["value"])
return None return 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 == "time_to_service":
return data["monthsToService"]["value"] if util.keys_exists(data, "monthsToService") else None if util.keys_exists(data, "timeToService"):
return str(data["timeToService"]["value"]) + " " + data["timeToService"]["unit"]
else:
return 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 == "average_energy_consumption":
return data["serviceWarningTrigger"]["value"] if util.keys_exists(data, "serviceWarningTrigger") else None return data["averageEnergyConsumption"]["value"] if util.keys_exists(data, "averageEnergyConsumption") else None
elif sensor_id == "washer_fluid_warning":
return data["washerFluidLevelWarning"]["value"] if util.keys_exists(data, "washerFluidLevelWarning") else None
else: else:
return None return None