Compare commits

...
21 Commits
Author SHA1 Message Date
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
Linus Dietz 47bd726ebb Add changelog info 2023-08-17 12:22:45 +02:00
Linus Dietz 0397d05522 Fix app crash if debug mode is not set #86 2023-08-17 12:19:29 +02:00
Linus DietzandGitHub f0628e6a26 Add missing sensors 2023-08-17 11:29:05 +02:00
Linus DietzandGitHub 0f89258718 Add missing cars 2023-08-17 11:25:20 +02:00
10 changed files with 175 additions and 138 deletions
+10 -69
View File
@@ -19,6 +19,7 @@ If you like my work:<br>
[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/U7U8MFXCF) [![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/U7U8MFXCF)
## Confirmed working with ## Confirmed working with
- XC40 BEV (2024)
- XC40 BEV (2023) - XC40 BEV (2023)
- XC40 BEV (2022) - XC40 BEV (2022)
- XC40 PHEV (2021) - XC40 PHEV (2021)
@@ -26,10 +27,12 @@ If you like my work:<br>
- C40 BEV (2023) - C40 BEV (2023)
- C40 BEV (2022) - C40 BEV (2022)
- XC90 T8 PHEV (2023) - XC90 T8 PHEV (2023)
- XC60 PHEV (2024)
- XC60 PHEV (2023) - XC60 PHEV (2023)
- 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
@@ -53,6 +56,12 @@ Please let me know if your car works with this addon so I can expand the list!<b
- Sensor "Fuel Status" - Sensor "Fuel Status"
- Sensor "Average Fuel Consumption" - Sensor "Average Fuel Consumption"
- Sensor "Average Speed" - Sensor "Average Speed"
- Sensor "Distance to Empty"
- Sensor "Hours to Service"
- Sensor "Distance to Service"
- Sensor "Months to Service"
- Sensor "Service warning status"
- Sensor "Service warning trigger"
- Car Device Tracker - Car Device Tracker
- Multiple cars - Multiple cars
@@ -64,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>
@@ -90,74 +99,6 @@ Here is what every option means:
| `CONF_debug` | `string` | | optional | Debug option (true/false). Normally you don't need this. | | `CONF_debug` | `string` | | optional | Debug option (true/false). Normally you don't need this. |
| `TZ` | `string` | | **required** | Container timezone eg "Europe/Berlin" from [here](https://docs.diladele.com/docker/timezones.html)| | `TZ` | `string` | | **required** | Container timezone eg "Europe/Berlin" from [here](https://docs.diladele.com/docker/timezones.html)|
## Lovelace sample card
<details>
<summary>Show me more!</summary><blockquote>
<br>
![alt text](https://raw.githubusercontent.com/Dielee/volvo2mqtt/main/img/lovelace_sample.png)<br>
<details>
<summary>Lovelace card sample</summary>
```
type: vertical-stack
title: Autostatus
cards:
- type: custom:vertical-stack-in-card
cards:
- type: custom:mushroom-lock-card
entity: lock.volvo_<your vin>_lock_status
name: Verrigelungsstatus
- type: horizontal-stack
cards:
- type: custom:mushroom-entity-card
entity: sensor.volvo_<your vin>_electric_range
name: Reichweite
layout: vertical
- type: custom:mushroom-entity-card
entity: sensor.volvo_<your vin>_battery_charge_level
name: Batteriestatus
layout: vertical
- type: custom:mushroom-entity-card
entity: sensor.volvo_<your vin>_estimated_charging_time
layout: vertical
name: Ladezeit
- type: horizontal-stack
cards:
- type: custom:mushroom-entity-card
entity: switch.volvo_<your vin>_climate_status
tap_action:
action: toggle
layout: vertical
name: Klimatisieren/Heizen
- type: custom:mushroom-template-card
primary: Daten aktualisieren
secondary: '{{ states(''sensor.volvo_<your vin>_last_data_update'')}}'
icon: mdi:update
layout: vertical
entity: button.volvo_<your vin>_update_data
- type: conditional
conditions:
- entity: sensor.volvo_<your vin>_estimated_charging_time
state_not: '0'
card:
type: custom:mushroom-entity-card
entity: sensor.volvo_<your vin>_estimated_charging_finish_time
name: Ladung vorraussichtlich abgeschlossen
show_name: true
- type: map
entities:
- entity: device_tracker.volvo_<your vin>_location
default_zoom: 16
dark_mode: false
hours_to_show: 0
auto_fit: true
aspect_ratio: '16:9'
```
</details>
</blockquote></details>
[aarch64-shield]: https://img.shields.io/badge/aarch64-yes-green.svg [aarch64-shield]: https://img.shields.io/badge/aarch64-yes-green.svg
+56
View File
@@ -1,3 +1,59 @@
## 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
### 🐛 Bug Fixes:
- Fix app crash if `debug` is not set #86
## v1.8.0 ## v1.8.0
### 🚀 Features: ### 🚀 Features:
+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.0" version: "1.8.10"
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,)
+4 -5
View File
@@ -1,6 +1,6 @@
from config import settings from config import settings
VERSION = "v1.8.0" VERSION = "v1.8.10"
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"
@@ -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()
+5 -1
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,6 +255,7 @@ 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"
if state:
mqtt_client.publish( mqtt_client.publish(
topic, topic,
json.dumps(state) if isinstance(state, dict) or isinstance(state, list) else state json.dumps(state) if isinstance(state, dict) or isinstance(state, list) else 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.
+17
View File
@@ -50,6 +50,7 @@ def setup_logging():
'%b %d %H:%M:%S') '%b %d %H:%M:%S')
file_log_handler.setFormatter(formatter) file_log_handler.setFormatter(formatter)
if "debug" in settings:
if settings["debug"]: if settings["debug"]:
sensitive_data_filter = SensitiveDataFilter(SENSITIVE_PATTERNS) sensitive_data_filter = SensitiveDataFilter(SENSITIVE_PATTERNS)
file_log_handler.addFilter(sensitive_data_filter) file_log_handler.addFilter(sensitive_data_filter)
@@ -67,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)
@@ -135,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!")
+39 -28
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,6 +120,9 @@ def get_vehicles():
def get_vcc_api_keys(used_key=None): def get_vcc_api_keys(used_key=None):
working_keys = None
while not working_keys:
setting_keys = settings.volvoData["vccapikey"] setting_keys = settings.volvoData["vccapikey"]
if isinstance(setting_keys, str): if isinstance(setting_keys, str):
set_key_state(setting_keys) set_key_state(setting_keys)
@@ -129,12 +133,11 @@ def get_vcc_api_keys(used_key=None):
logging.debug(str(vcc_api_keys)) logging.debug(str(vcc_api_keys))
working_keys = [key["key"] for key in vcc_api_keys if not key.get("extended") and key.get('key') != used_key] working_keys = [key["key"] for key in vcc_api_keys if not key.get("extended") and key.get('key') != used_key]
if len(working_keys) < 1: if len(working_keys) < 1:
used_key = None
logging.warning("No working VCCAPIKEY found, waiting 10 minutes. Then trying again!") logging.warning("No working VCCAPIKEY found, waiting 10 minutes. Then trying again!")
mqtt.send_offline() mqtt.send_offline()
time.sleep(600) time.sleep(600)
get_vcc_api_keys(used_key=None) else:
return None
mqtt.send_heartbeat() mqtt.send_heartbeat()
session.headers.update({"vcc-api-key": working_keys[0]}) session.headers.update({"vcc-api-key": working_keys[0]})
logging.info("Using VCCAPIKEY: " + working_keys[0]) logging.info("Using VCCAPIKEY: " + working_keys[0])
@@ -142,6 +145,8 @@ def get_vcc_api_keys(used_key=None):
if key_dict["key"] == working_keys[0]: if key_dict["key"] == working_keys[0]:
key_dict["in_use"] = True key_dict["in_use"] = True
logging.debug(str(vcc_api_keys))
def set_key_state(key): def set_key_state(key):
global vcc_api_keys global vcc_api_keys
@@ -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:
@@ -341,7 +348,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))
try:
data = response.json() 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 +448,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 +460,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 +548,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