forked from peter/volvo2mqtt
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
25e90091ab | ||
|
|
c4c801e46a | ||
|
|
8326ed3c88 | ||
|
|
3166a1c6ab | ||
|
|
80646bbf51 | ||
|
|
7401ebfc53 | ||
|
|
bdbb6bd5f8 | ||
|
|
f03ff8953c | ||
|
|
2e95e71661 | ||
|
|
126cc37269 | ||
|
|
cb10fe07c3 | ||
|
|
f3244ccdc0 | ||
|
|
1223853208 | ||
|
|
1f0c52b5a7 | ||
|
|
161c02fc11 | ||
|
|
be4a1b6791 | ||
|
|
8803f9ff1d | ||
|
|
843e00acd1 | ||
|
|
3be0a4edbf | ||
|
|
22cf71f007 | ||
|
|
b7feb9713b | ||
|
|
1f53ef0198 | ||
|
|
d07e6dfe6e | ||
|
|
80473d5120 | ||
|
|
81b2771c80 | ||
|
|
f1953e1baa | ||
|
|
459c2dc1af | ||
|
|
be09c7953e | ||
|
|
fa68c4ba2c | ||
|
|
d7a79f84a4 | ||
|
|
2c8effd746 | ||
|
|
cef28f7d0b |
@@ -15,6 +15,8 @@ Maybe this component works also with other Volvo cars. Please try out the native
|
||||
|
||||
Home Assistant thread can be found [here](https://community.home-assistant.io/t/volvo2mqtt-connect-your-aaos-volvo/585699).
|
||||
|
||||
<b>Important note: The Volvo api currently ONLY works in these [countries](https://developer.volvocars.com/terms-and-conditions/apis-supported-locations/)</b>
|
||||
|
||||
If you like my work:<br>
|
||||
[](https://ko-fi.com/U7U8MFXCF)
|
||||
|
||||
@@ -32,7 +34,9 @@ If you like my work:<br>
|
||||
- XC60 PHEV (2022)
|
||||
- XC90 PHEV T8 (2023)
|
||||
- XC90 PHEV T8 (2024)
|
||||
- XC90 B5 Mildhybrid (2024)
|
||||
- V90 PHEV T8 (2019)*
|
||||
- V90 PHEV T6 (2024)
|
||||
|
||||
*only partly working
|
||||
|
||||
|
||||
+80
-1
@@ -1,3 +1,82 @@
|
||||
## v1.8.21
|
||||
### 🐛 Bug Fixes:
|
||||
|
||||
- Try to fix addon build #146
|
||||
|
||||
## v1.8.20
|
||||
### 🐛 Bug Fixes:
|
||||
|
||||
- Fix Addon crash if icon state is `UNKOWN`
|
||||
- Add `UNSPECIFIED` state for windows
|
||||
|
||||
|
||||
## 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:
|
||||
|
||||
@@ -48,7 +127,7 @@
|
||||
### 🚀 Features:
|
||||
|
||||
- <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:
|
||||
|
||||
Old:
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@ WORKDIR /volvoAAOS2mqtt
|
||||
RUN wget --no-cache https://raw.githubusercontent.com/Dielee/volvo2mqtt/main/requirements.txt
|
||||
|
||||
# install dependencies
|
||||
RUN pip install -r requirements.txt
|
||||
RUN pip install -r requirements.txt --break-system-packages
|
||||
|
||||
# copy the content of the local src directory to the working directory
|
||||
COPY / .
|
||||
|
||||
@@ -4,8 +4,10 @@ profile volvo2mqtt flags=(attach_disconnected,mediate_deleted) {
|
||||
#include <abstractions/base>
|
||||
|
||||
# Capabilities
|
||||
capability,
|
||||
file,
|
||||
signal (send) set=(kill,term,int,hup,cont),
|
||||
network,
|
||||
|
||||
# S6-Overlay
|
||||
/init ix,
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
name: "Volvo2Mqtt"
|
||||
description: "Volvo AAOS MQTT bridge"
|
||||
version: "1.8.7"
|
||||
version: "1.8.21"
|
||||
slug: "volvo2mqtt"
|
||||
init: false
|
||||
url: "https://github.com/Dielee/volvo2mqtt"
|
||||
|
||||
+17
-13
@@ -1,10 +1,10 @@
|
||||
from config import settings
|
||||
|
||||
VERSION = "v1.8.8"
|
||||
VERSION = "v1.8.21"
|
||||
|
||||
OAUTH_URL = "https://volvoid.eu.volvocars.com/as/token.oauth2"
|
||||
VEHICLES_URL = "https://api.volvocars.com/connected-vehicle/v1/vehicles"
|
||||
VEHICLE_DETAILS_URL = "https://api.volvocars.com/connected-vehicle/v1/vehicles/{0}"
|
||||
VEHICLES_URL = "https://api.volvocars.com/connected-vehicle/v2/vehicles"
|
||||
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"
|
||||
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"
|
||||
@@ -16,11 +16,11 @@ ODOMETER_STATE_URL = "https://api.volvocars.com/connected-vehicle/v2/vehicles/{0
|
||||
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"
|
||||
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_STATE_URL = "https://api.volvocars.com/connected-vehicle/v2/vehicles/{0}/fuel"
|
||||
FUEL_BATTERY_STATE_URL = "https://api.volvocars.com/connected-vehicle/v2/vehicles/{0}/fuel"
|
||||
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"
|
||||
VEHICLE_DIAGNOSTICS_URL = "https://api.volvocars.com/connected-vehicle/v2/vehicles/{0}/diagnostics"
|
||||
API_BACKEND_STATUS = "https://oip-dev-bff.euwest1.production.volvo.care/api/v1/backend-status"
|
||||
|
||||
units = {
|
||||
"en_GB": {
|
||||
@@ -50,8 +50,8 @@ charging_connection_states = {"CONNECTION_STATUS_DISCONNECTED": "Disconnected",
|
||||
"CONNECTION_STATUS_CONNECTED_DC": "Connected DC", "CONNECTION_STATUS_CONNECTED_AC": "Connected AC",
|
||||
"CONNECTION_STATUS_FAULT": "Fault"}
|
||||
|
||||
window_states = {"CLOSED": "OFF", "OPEN": "ON"}
|
||||
door_states = {"CLOSED": "OFF", "OPEN": "ON"}
|
||||
window_states = {"CLOSED": "OFF", "OPEN": "ON", "UNSPECIFIED": "UNKNOWN", "AJAR": "ON"}
|
||||
door_states = {"CLOSED": "OFF", "OPEN": "ON", "UNSPECIFIED": "UNKNOWN", "AJAR": "ON"}
|
||||
engine_states = {"RUNNING": "ON", "STOPPED": "OFF", "true": "ON", "false": "OFF"}
|
||||
|
||||
icon_states = {
|
||||
@@ -78,7 +78,7 @@ icon_states = {
|
||||
|
||||
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": 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": "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},
|
||||
@@ -108,14 +108,18 @@ supported_entities = [
|
||||
{"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": "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_BATTERY_STATE_URL, "state_class": "measurement"},
|
||||
{"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": "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": "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 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"]
|
||||
|
||||
+40
-24
@@ -10,8 +10,7 @@ from datetime import datetime
|
||||
from babel.dates import format_datetime
|
||||
from config import settings
|
||||
from const import CLIMATE_START_URL, CLIMATE_STOP_URL, CAR_LOCK_URL, \
|
||||
CAR_UNLOCK_URL, availability_topic, icon_states
|
||||
|
||||
CAR_UNLOCK_URL, availability_topic, icon_states, old_entity_ids
|
||||
|
||||
mqtt_client: mqtt.Client
|
||||
subscribed_topics = []
|
||||
@@ -25,7 +24,7 @@ active_schedules = {}
|
||||
|
||||
def connect():
|
||||
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)
|
||||
if settings["mqtt"]["username"] and settings["mqtt"]["password"]:
|
||||
@@ -71,14 +70,14 @@ def send_car_images(vin, data, device):
|
||||
if entity["id"] == "exterior_image":
|
||||
mqtt_client.publish(
|
||||
url_topic,
|
||||
data["images"]["exteriorDefaultUrl"],
|
||||
data["images"]["exteriorImageUrl"],
|
||||
retain=True
|
||||
)
|
||||
|
||||
if entity["id"] == "interior_image":
|
||||
mqtt_client.publish(
|
||||
url_topic,
|
||||
data["images"]["interiorDefaultUrl"],
|
||||
data["images"]["internalImageUrl"],
|
||||
retain=True
|
||||
)
|
||||
|
||||
@@ -90,7 +89,7 @@ def on_connect(client, userdata, flags, rc):
|
||||
mqtt_client.subscribe(topic)
|
||||
|
||||
|
||||
def on_disconnect(client, userdata, rc):
|
||||
def on_disconnect(client, userdata, rc):
|
||||
logging.warning("MQTT disconnected, reconnecting automatically")
|
||||
|
||||
|
||||
@@ -141,7 +140,7 @@ def start_climate_timer(d, vin):
|
||||
return None
|
||||
|
||||
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())
|
||||
logging.debug("Climate timer set to " + str(start_datetime))
|
||||
update_car_data()
|
||||
@@ -244,6 +243,11 @@ def update_car_data(force_update=False, overwrite={}):
|
||||
state = last_data_update
|
||||
elif entity["id"] == "active_schedules":
|
||||
state = active_schedules[vin]
|
||||
elif entity["id"] == "api_backend_status":
|
||||
if force_update:
|
||||
state = volvo.get_backend_status()
|
||||
else:
|
||||
state = volvo.backend_status
|
||||
else:
|
||||
if entity["id"] == ov_entity_id and vin == ov_vin:
|
||||
state = ov_state
|
||||
@@ -255,7 +259,7 @@ def update_car_data(force_update=False, overwrite={}):
|
||||
else:
|
||||
topic = f"homeassistant/{entity['domain']}/{vin}_{entity['id']}/state"
|
||||
|
||||
if state:
|
||||
if state or state == 0:
|
||||
mqtt_client.publish(
|
||||
topic,
|
||||
json.dumps(state) if isinstance(state, dict) or isinstance(state, list) else state
|
||||
@@ -265,13 +269,19 @@ def update_car_data(force_update=False, overwrite={}):
|
||||
|
||||
def update_ha_device(entity, vin, state):
|
||||
icon_config = icon_states.get(entity["id"])
|
||||
if icon_config and state:
|
||||
if state.replace(".", "").isnumeric():
|
||||
state = float(state)
|
||||
icon = util.get_icon_between(icon_config, state)
|
||||
try:
|
||||
if icon_config and state:
|
||||
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)
|
||||
icon = util.get_icon_between(icon_config, state)
|
||||
else:
|
||||
icon = icon_config[state]
|
||||
else:
|
||||
icon = icon_config[state]
|
||||
else:
|
||||
return None
|
||||
except:
|
||||
return None
|
||||
|
||||
logging.debug("Updating icon to " + icon + " for " + entity["id"])
|
||||
@@ -293,7 +303,7 @@ def update_ha_device(entity, vin, state):
|
||||
|
||||
if entity.get("state_class"):
|
||||
config["state_class"] = entity["state_class"]
|
||||
|
||||
|
||||
if entity.get("domain") == "device_tracker":
|
||||
config["json_attributes_topic"] = f"homeassistant/{entity['domain']}/{vin}_{entity['id']}/attributes"
|
||||
|
||||
@@ -317,15 +327,15 @@ def create_ha_devices():
|
||||
devices[vin] = device
|
||||
for entity in volvo.supported_endpoints[vin]:
|
||||
config = {
|
||||
"name": entity['name'],
|
||||
"object_id": f"volvo_{vin}_{entity['id']}",
|
||||
"schema": "state",
|
||||
"icon": f"mdi:{entity['icon']}",
|
||||
"state_topic": f"homeassistant/{entity['domain']}/{vin}_{entity['id']}/state",
|
||||
"device": device,
|
||||
"unique_id": f"volvoAAOS2mqtt_{vin}_{entity['id']}",
|
||||
"availability_topic": availability_topic
|
||||
}
|
||||
"name": entity['name'],
|
||||
"object_id": f"volvo_{vin}_{entity['id']}",
|
||||
"schema": "state",
|
||||
"icon": f"mdi:{entity['icon']}",
|
||||
"state_topic": f"homeassistant/{entity['domain']}/{vin}_{entity['id']}/state",
|
||||
"device": device,
|
||||
"unique_id": f"volvoAAOS2mqtt_{vin}_{entity['id']}",
|
||||
"availability_topic": availability_topic
|
||||
}
|
||||
if entity.get("device_class"):
|
||||
config["device_class"] = entity["device_class"]
|
||||
|
||||
@@ -361,3 +371,9 @@ def send_heartbeat():
|
||||
def send_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)
|
||||
|
||||
+80
-44
@@ -4,15 +4,15 @@ import mqtt
|
||||
import util
|
||||
import time
|
||||
import re
|
||||
from threading import currentThread
|
||||
from threading import current_thread, Thread
|
||||
from datetime import datetime, timedelta
|
||||
from config import settings
|
||||
from babel.dates import format_datetime
|
||||
from json import JSONDecodeError
|
||||
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, \
|
||||
WINDOWS_STATE_URL, LOCK_STATE_URL, TYRE_STATE_URL, supported_entities, BATTERY_CHARGE_STATE_URL, \
|
||||
STATISTICS_URL, ENGINE_DIAGNOSTICS_URL, engine_states
|
||||
WINDOWS_STATE_URL, LOCK_STATE_URL, TYRE_STATE_URL, supported_entities, FUEL_BATTERY_STATE_URL, \
|
||||
STATISTICS_URL, ENGINE_DIAGNOSTICS_URL, API_BACKEND_STATUS, engine_states
|
||||
|
||||
session = requests.Session()
|
||||
session.headers = {
|
||||
@@ -27,6 +27,7 @@ vins = []
|
||||
supported_endpoints = {}
|
||||
cached_requests = {}
|
||||
vcc_api_keys = []
|
||||
backend_status = ""
|
||||
|
||||
|
||||
def authorize():
|
||||
@@ -54,6 +55,7 @@ def authorize():
|
||||
get_vcc_api_keys()
|
||||
get_vehicles()
|
||||
check_supported_endpoints()
|
||||
Thread(target=backend_status_loop).start()
|
||||
else:
|
||||
message = auth.json()
|
||||
raise Exception(message["error_description"])
|
||||
@@ -116,6 +118,7 @@ def get_vehicles():
|
||||
else:
|
||||
initialize_climate(vins)
|
||||
initialize_scheduler(vins)
|
||||
mqtt.delete_old_entities()
|
||||
logging.info("Vin: " + str(vins) + " found!")
|
||||
|
||||
|
||||
@@ -240,16 +243,11 @@ def check_supported_endpoints():
|
||||
for vin in vins:
|
||||
supported_endpoints[vin] = []
|
||||
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]):
|
||||
# If battery charge level could be found in recharge-api, skip the second battery charge sensor
|
||||
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'):
|
||||
state = api_call(entity["url"], "GET", vin, entity["id"])
|
||||
else:
|
||||
@@ -308,7 +306,7 @@ def check_engine_status(vin):
|
||||
# Exit thread as engine state is unsupported by car
|
||||
return None
|
||||
|
||||
t = currentThread()
|
||||
t = current_thread()
|
||||
while getattr(t, "do_run", True):
|
||||
engine_state = api_call(endpoint_url, "GET", vin, "engine_state", True)
|
||||
if engine_state == "ON":
|
||||
@@ -318,12 +316,37 @@ def check_engine_status(vin):
|
||||
time.sleep(5)
|
||||
|
||||
|
||||
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, 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):
|
||||
if datetime.now(util.TZ) >= token_expires_at:
|
||||
refresh_auth()
|
||||
|
||||
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
|
||||
response = cached_request(url, method, vin, force_update, key_change)
|
||||
if response is None:
|
||||
@@ -369,7 +392,7 @@ def api_call(url, method, vin, sensor_id=None, force_update=False, key_change=Fa
|
||||
return None
|
||||
elif response.status_code == 403 and "message" in data:
|
||||
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()
|
||||
api_call(url, method, vin, sensor_id, force_update, True)
|
||||
else:
|
||||
@@ -415,7 +438,9 @@ def cached_request(url, method, vin, force_update=False, key_change=False):
|
||||
|
||||
|
||||
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":
|
||||
return data["batteryChargeLevel"]["value"] if util.keys_exists(data, "batteryChargeLevel") else None
|
||||
elif sensor_id == "electric_range":
|
||||
@@ -448,7 +473,7 @@ def parse_api_data(data, sensor_id=None):
|
||||
return ""
|
||||
return None
|
||||
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":
|
||||
multiplier = 1
|
||||
if util.keys_exists(settings["volvoData"], "odometerMultiplier"):
|
||||
@@ -460,44 +485,44 @@ def parse_api_data(data, sensor_id=None):
|
||||
return util.convert_metric_values(int(data["odometer"]["value"]) * multiplier) \
|
||||
if util.keys_exists(data, "odometer") else None
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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":
|
||||
return door_states[data["frontRightDoorOpen"]["value"]] \
|
||||
if util.keys_exists(data, "frontRightDoorOpen") else None
|
||||
return door_states[data["frontRightDoor"]["value"]] \
|
||||
if util.keys_exists(data, "frontRightDoor") else None
|
||||
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":
|
||||
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":
|
||||
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":
|
||||
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":
|
||||
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":
|
||||
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":
|
||||
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":
|
||||
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":
|
||||
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":
|
||||
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":
|
||||
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":
|
||||
if util.keys_exists(data, "fuelAmount"):
|
||||
fuel_amount = float(data["fuelAmount"]["value"])
|
||||
@@ -539,25 +564,36 @@ def parse_api_data(data, sensor_id=None):
|
||||
"latitude": raw_data["coordinates"][1],
|
||||
"gps_accuracy": 1}
|
||||
return coordinates
|
||||
elif sensor_id == "distance_to_empty":
|
||||
if util.keys_exists(data, "distanceToEmpty"):
|
||||
distance_to_empty = int(data["distanceToEmpty"]["value"])
|
||||
elif sensor_id == "distance_to_empty_tank":
|
||||
if util.keys_exists(data, "distanceToEmptyTank"):
|
||||
distance_to_empty = int(data["distanceToEmptyTank"]["value"])
|
||||
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
|
||||
elif sensor_id == "hours_to_service":
|
||||
return data["engineHoursToService"]["value"] if util.keys_exists(data, "engineHoursToService") else None
|
||||
elif sensor_id == "km_to_service":
|
||||
if util.keys_exists(data, "kmToService"):
|
||||
km_to_service = int(data["kmToService"]["value"])
|
||||
if util.keys_exists(data, "distanceToService"):
|
||||
km_to_service = int(data["distanceToService"]["value"])
|
||||
if km_to_service > 0:
|
||||
return util.convert_metric_values(data["kmToService"]["value"])
|
||||
return util.convert_metric_values(data["distanceToService"]["value"])
|
||||
return None
|
||||
elif sensor_id == "months_to_service":
|
||||
return data["monthsToService"]["value"] if util.keys_exists(data, "monthsToService") else None
|
||||
elif sensor_id == "time_to_service":
|
||||
if util.keys_exists(data, "timeToService"):
|
||||
return str(data["timeToService"]["value"]) + " " + data["timeToService"]["unit"]
|
||||
else:
|
||||
return None
|
||||
elif sensor_id == "service_warning_status":
|
||||
return data["serviceWarningStatus"]["value"] if util.keys_exists(data, "serviceWarningStatus") else None
|
||||
elif sensor_id == "service_warning_trigger":
|
||||
return data["serviceWarningTrigger"]["value"] if util.keys_exists(data, "serviceWarningTrigger") else None
|
||||
return data["serviceWarning"]["value"] if util.keys_exists(data, "serviceWarning") else None
|
||||
elif sensor_id == "average_energy_consumption":
|
||||
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:
|
||||
return None
|
||||
|
||||
Reference in New Issue
Block a user