Compare commits

...
11 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
9 changed files with 100 additions and 40 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
+39
View File
@@ -1,3 +1,42 @@
## 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 ## v1.8.3
### 🚀 Features: ### 🚀 Features:
+8 -5
View File
@@ -1,6 +1,6 @@
name: "Volvo2Mqtt" name: "Volvo2Mqtt"
description: "Volvo AAOS MQTT bridge" description: "Volvo AAOS MQTT bridge"
version: "1.8.3" 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(60,) 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,)
+2 -3
View File
@@ -1,6 +1,6 @@
from config import settings from config import settings
VERSION = "v1.8.3" 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 = {
@@ -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"},
+10 -6
View File
@@ -4,6 +4,7 @@ import paho.mqtt.client as mqtt
import json import json
import volvo import volvo
import util import util
import os
from threading import Thread, Timer from threading import Thread, Timer
from datetime import datetime from datetime import datetime
from babel.dates import format_datetime from babel.dates import format_datetime
@@ -23,7 +24,9 @@ active_schedules = {}
def connect(): def connect():
client = mqtt.Client("volvoAAOS2mqtt") client = mqtt.Client("volvoAAOS2mqtt") if os.environ.get("IS_HA_ADDON") \
else mqtt.Client("volvoAAOS2mqtt_" + settings.volvoData["username"].replace("+", ""))
client.will_set(availability_topic, "offline", 0, False) client.will_set(availability_topic, "offline", 0, False)
if settings["mqtt"]["username"] and settings["mqtt"]["password"]: if settings["mqtt"]["username"] and settings["mqtt"]["password"]:
client.username_pw_set(settings["mqtt"]["username"], settings["mqtt"]["password"]) client.username_pw_set(settings["mqtt"]["username"], settings["mqtt"]["password"])
@@ -252,11 +255,12 @@ def update_car_data(force_update=False, overwrite={}):
else: else:
topic = f"homeassistant/{entity['domain']}/{vin}_{entity['id']}/state" topic = f"homeassistant/{entity['domain']}/{vin}_{entity['id']}/state"
mqtt_client.publish( if state:
topic, mqtt_client.publish(
json.dumps(state) if isinstance(state, dict) or isinstance(state, list) else state topic,
) json.dumps(state) if isinstance(state, dict) or isinstance(state, list) else state
update_ha_device(entity, vin, state) )
update_ha_device(entity, vin, state)
def update_ha_device(entity, vin, state): def update_ha_device(entity, vin, state):
+2
View File
@@ -2,6 +2,8 @@
"updateInterval": 300, "updateInterval": 300,
"babelLocale": "de", "babelLocale": "de",
"TZ": "Europe/Berlin", "TZ": "Europe/Berlin",
"debug": false,
"disable_logging": false,
"mqtt": { "mqtt": {
"broker": "", "broker": "",
"port": 1883, "port": 1883,
+3
View File
@@ -11,6 +11,9 @@ configuration:
debug: debug:
name: API debug mode name: API debug mode
description: Enable Volvo API debug, normaly this can stay off. If enabled, the complete log file can be found under \\<Your HA Host IP>\addons\volvo2mqtt\log\volvo2mqtt.log. description: Enable Volvo API debug, normaly this can stay off. If enabled, the complete log file can be found under \\<Your HA Host IP>\addons\volvo2mqtt\log\volvo2mqtt.log.
disable_logging:
name: Disable logging
description: Disable logging completely to reduce IO and SD card access.
mqtt: mqtt:
name: MQTT Broker settings name: MQTT Broker settings
description: Leave the settings as they are if you are using the MQTT Mosquitto Addon. If not, take a look at the readme from volvo2mqtt. description: Leave the settings as they are if you are using the MQTT Mosquitto Addon. If not, take a look at the readme from volvo2mqtt.
+4
View File
@@ -68,6 +68,10 @@ def setup_logging():
if settings["debug"]: if settings["debug"]:
logger.setLevel(logging.DEBUG) logger.setLevel(logging.DEBUG)
if "disable_logging" in settings:
if settings["disable_logging"]:
logger.setLevel(logging.ERROR)
def check_existing_folder(): def check_existing_folder():
Path("/addons/volvo2mqtt/log/").mkdir(parents=True, exist_ok=True) Path("/addons/volvo2mqtt/log/").mkdir(parents=True, exist_ok=True)
+31 -26
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, \
@@ -347,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))
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)
@@ -443,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"):
@@ -455,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"])
@@ -543,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