Compare commits

..
13 Commits
8 changed files with 101 additions and 105 deletions
+9 -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)
## Confirmed working with
- XC40 BEV (2024)
- XC40 BEV (2023)
- XC40 BEV (2022)
- XC40 PHEV (2021)
@@ -26,6 +27,7 @@ If you like my work:<br>
- C40 BEV (2023)
- C40 BEV (2022)
- XC90 T8 PHEV (2023)
- XC60 PHEV (2024)
- XC60 PHEV (2023)
- XC60 PHEV (2022)
- XC90 PHEV T8 (2023)
@@ -53,6 +55,12 @@ Please let me know if your car works with this addon so I can expand the list!<b
- Sensor "Fuel Status"
- Sensor "Average Fuel Consumption"
- 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
- Multiple cars
@@ -64,7 +72,7 @@ NOTE: Energy status currently available only for cars in the Europe / Middle Eas
Just install this addon with the following command.
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>
@@ -90,74 +98,6 @@ Here is what every option means:
| `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)|
## 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
+32
View File
@@ -1,3 +1,35 @@
## 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
### 🚀 Features:
+7 -6
View File
@@ -1,6 +1,6 @@
name: "Volvo2Mqtt"
description: "Volvo AAOS MQTT bridge"
version: "1.8.0"
version: "1.8.6"
slug: "volvo2mqtt"
init: false
url: "https://github.com/Dielee/volvo2mqtt"
@@ -24,14 +24,15 @@ options:
username: null
password: null
vin: ""
vccapikey: null
vccapikey:
- null
odometerMultiplier: 1
averageSpeedDivider: 1
averageFuelConsumptionMultiplier: 1
schema:
updateInterval: int(10,)
updateInterval: int(60,)
babelLocale: str
TZ: str
TZ: match(^.+/.+$)
debug: bool
mqtt:
broker: str
@@ -39,11 +40,11 @@ schema:
username: str?
password: str?
volvoData:
username: str
username: match(^([\w+\.]+@([\w-]+\.)+[\w-]{2,4})|(\+\d{5,20})$)
password: str
vin: str?
vccapikey:
- str
- match(^\b\w{32}\b$)
odometerMultiplier: int(1,)
averageSpeedDivider: int(1,)
averageFuelConsumptionMultiplier: int(1,)
+3 -3
View File
@@ -1,6 +1,6 @@
from config import settings
VERSION = "v1.8.0"
VERSION = "v1.8.6"
OAUTH_URL = "https://volvoid.eu.volvocars.com/as/token.oauth2"
VEHICLES_URL = "https://api.volvocars.com/connected-vehicle/v1/vehicles"
@@ -77,8 +77,8 @@ icon_states = {
}
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", "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": 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": "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},
+4 -3
View File
@@ -2,14 +2,15 @@ import logging
from volvo import authorize
from mqtt import update_loop, connect
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__':
set_tz()
set_mqtt_settings()
setup_logging()
logging.info("Starting volvo2mqtt version " + VERSION)
validate_settings()
set_tz()
set_mqtt_settings()
connect()
authorize()
update_loop()
+4 -1
View File
@@ -4,6 +4,7 @@ import paho.mqtt.client as mqtt
import json
import volvo
import util
import os
from threading import Thread, Timer
from datetime import datetime
from babel.dates import format_datetime
@@ -23,7 +24,9 @@ active_schedules = {}
def connect():
client = mqtt.Client("volvoAAOS2mqtt")
client = mqtt.Client("volvoAAOS2mqtt") if os.environ.get("IS_HA_ADDON") \
else mqtt.Client("volvoAAOS2mqtt_" + settings.volvoData["username"])
client.will_set(availability_topic, "offline", 0, False)
if settings["mqtt"]["username"] and settings["mqtt"]["password"]:
client.username_pw_set(settings["mqtt"]["username"], settings["mqtt"]["password"])
+16 -3
View File
@@ -50,9 +50,10 @@ def setup_logging():
'%b %d %H:%M:%S')
file_log_handler.setFormatter(formatter)
if settings["debug"]:
sensitive_data_filter = SensitiveDataFilter(SENSITIVE_PATTERNS)
file_log_handler.addFilter(sensitive_data_filter)
if "debug" in settings:
if settings["debug"]:
sensitive_data_filter = SensitiveDataFilter(SENSITIVE_PATTERNS)
file_log_handler.addFilter(sensitive_data_filter)
console_log_handler = logging.StreamHandler(sys.stdout)
console_log_handler.setFormatter(formatter)
@@ -135,3 +136,15 @@ def set_mqtt_settings():
config.settings["mqtt"]["port"] = broker_port
config.settings["mqtt"]["username"] = broker_user
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!")
+26 -20
View File
@@ -119,28 +119,32 @@ def get_vehicles():
def get_vcc_api_keys(used_key=None):
setting_keys = settings.volvoData["vccapikey"]
if isinstance(setting_keys, str):
set_key_state(setting_keys)
elif isinstance(setting_keys, list):
for key in setting_keys:
set_key_state(key)
working_keys = None
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]
if len(working_keys) < 1:
logging.warning("No working VCCAPIKEY found, waiting 10 minutes. Then trying again!")
mqtt.send_offline()
time.sleep(600)
get_vcc_api_keys(used_key=None)
return None
while not working_keys:
setting_keys = settings.volvoData["vccapikey"]
if isinstance(setting_keys, str):
set_key_state(setting_keys)
elif isinstance(setting_keys, list):
for key in setting_keys:
set_key_state(key)
mqtt.send_heartbeat()
session.headers.update({"vcc-api-key": working_keys[0]})
logging.info("Using VCCAPIKEY: " + working_keys[0])
for key_dict in vcc_api_keys:
if key_dict["key"] == working_keys[0]:
key_dict["in_use"] = True
logging.debug(str(vcc_api_keys))
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:
used_key = None
logging.warning("No working VCCAPIKEY found, waiting 10 minutes. Then trying again!")
mqtt.send_offline()
time.sleep(600)
else:
mqtt.send_heartbeat()
session.headers.update({"vcc-api-key": working_keys[0]})
logging.info("Using VCCAPIKEY: " + working_keys[0])
for key_dict in vcc_api_keys:
if key_dict["key"] == working_keys[0]:
key_dict["in_use"] = True
logging.debug(str(vcc_api_keys))
def set_key_state(key):
@@ -160,6 +164,8 @@ def set_key_state(key):
def check_vcc_api_key(test_key, extended_until=None):
if extended_until:
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
if datetime.now(util.TZ) >= token_expires_at: