mirror of
https://github.com/Dielee/volvo2mqtt.git
synced 2026-08-13 03:09:52 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3be0a4edbf | ||
|
|
22cf71f007 | ||
|
|
b7feb9713b | ||
|
|
1f53ef0198 | ||
|
|
d07e6dfe6e | ||
|
|
80473d5120 | ||
|
|
81b2771c80 | ||
|
|
f1953e1baa |
+26
-1
@@ -1,3 +1,28 @@
|
|||||||
|
## 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
|
## v1.8.10
|
||||||
### 🐛 Bug Fixes:
|
### 🐛 Bug Fixes:
|
||||||
|
|
||||||
@@ -58,7 +83,7 @@
|
|||||||
### 🚀 Features:
|
### 🚀 Features:
|
||||||
|
|
||||||
- <b>Breaking change: The `vccapikey` setting is now a list. It is possible to
|
- <b>Breaking change: The `vccapikey` setting is now a list. It is possible to
|
||||||
add multiple VCCAPIKEYs. This will be helpful if you are extending your 10.000 call limit some times.
|
add multiple VCCAPIKEYs. This will be helpful if you are extending your 10.000 call limit sometimes.
|
||||||
Change your vccapikey config like this:
|
Change your vccapikey config like this:
|
||||||
|
|
||||||
Old:
|
Old:
|
||||||
|
|||||||
@@ -4,8 +4,10 @@ profile volvo2mqtt flags=(attach_disconnected,mediate_deleted) {
|
|||||||
#include <abstractions/base>
|
#include <abstractions/base>
|
||||||
|
|
||||||
# Capabilities
|
# Capabilities
|
||||||
|
capability,
|
||||||
file,
|
file,
|
||||||
signal (send) set=(kill,term,int,hup,cont),
|
signal (send) set=(kill,term,int,hup,cont),
|
||||||
|
network,
|
||||||
|
|
||||||
# S6-Overlay
|
# S6-Overlay
|
||||||
/init ix,
|
/init ix,
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
name: "Volvo2Mqtt"
|
name: "Volvo2Mqtt"
|
||||||
description: "Volvo AAOS MQTT bridge"
|
description: "Volvo AAOS MQTT bridge"
|
||||||
version: "1.8.10"
|
version: "1.8.14"
|
||||||
slug: "volvo2mqtt"
|
slug: "volvo2mqtt"
|
||||||
init: false
|
init: false
|
||||||
url: "https://github.com/Dielee/volvo2mqtt"
|
url: "https://github.com/Dielee/volvo2mqtt"
|
||||||
|
|||||||
+7
-6
@@ -1,6 +1,6 @@
|
|||||||
from config import settings
|
from config import settings
|
||||||
|
|
||||||
VERSION = "v1.8.10"
|
VERSION = "v1.8.14"
|
||||||
|
|
||||||
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"
|
||||||
@@ -16,7 +16,7 @@ ODOMETER_STATE_URL = "https://api.volvocars.com/connected-vehicle/v2/vehicles/{0
|
|||||||
LOCATION_STATE_URL = "https://api.volvocars.com/location/v1/vehicles/{0}/location"
|
LOCATION_STATE_URL = "https://api.volvocars.com/location/v1/vehicles/{0}/location"
|
||||||
TYRE_STATE_URL = "https://api.volvocars.com/connected-vehicle/v2/vehicles/{0}/tyres"
|
TYRE_STATE_URL = "https://api.volvocars.com/connected-vehicle/v2/vehicles/{0}/tyres"
|
||||||
ENGINE_STATE_URL = "https://api.volvocars.com/connected-vehicle/v2/vehicles/{0}/engine-status"
|
ENGINE_STATE_URL = "https://api.volvocars.com/connected-vehicle/v2/vehicles/{0}/engine-status"
|
||||||
BATTERY_CHARGE_STATE_URL = "https://api.volvocars.com/connected-vehicle/v2/vehicles/{0}/battery-charge-level"
|
BATTERY_CHARGE_STATE_URL = "https://api.volvocars.com/connected-vehicle/v1/vehicles/{0}/battery-charge-level"
|
||||||
FUEL_STATE_URL = "https://api.volvocars.com/connected-vehicle/v2/vehicles/{0}/fuel"
|
FUEL_STATE_URL = "https://api.volvocars.com/connected-vehicle/v2/vehicles/{0}/fuel"
|
||||||
STATISTICS_URL = "https://api.volvocars.com/connected-vehicle/v2/vehicles/{0}/statistics"
|
STATISTICS_URL = "https://api.volvocars.com/connected-vehicle/v2/vehicles/{0}/statistics"
|
||||||
ENGINE_DIAGNOSTICS_URL = "https://api.volvocars.com/connected-vehicle/v2/vehicles/{0}/engine"
|
ENGINE_DIAGNOSTICS_URL = "https://api.volvocars.com/connected-vehicle/v2/vehicles/{0}/engine"
|
||||||
@@ -50,7 +50,7 @@ charging_connection_states = {"CONNECTION_STATUS_DISCONNECTED": "Disconnected",
|
|||||||
"CONNECTION_STATUS_CONNECTED_DC": "Connected DC", "CONNECTION_STATUS_CONNECTED_AC": "Connected AC",
|
"CONNECTION_STATUS_CONNECTED_DC": "Connected DC", "CONNECTION_STATUS_CONNECTED_AC": "Connected AC",
|
||||||
"CONNECTION_STATUS_FAULT": "Fault"}
|
"CONNECTION_STATUS_FAULT": "Fault"}
|
||||||
|
|
||||||
window_states = {"CLOSED": "OFF", "OPEN": "ON"}
|
window_states = {"CLOSED": "OFF", "OPEN": "ON", "AJAR": "ON"}
|
||||||
door_states = {"CLOSED": "OFF", "OPEN": "ON", "UNSPECIFIED": "UNKNOWN", "AJAR": "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"}
|
||||||
|
|
||||||
@@ -114,7 +114,8 @@ supported_entities = [
|
|||||||
{"name": "Average Speed", "domain": "sensor", "id": "average_speed", "unit": "km/h" if not units.get(settings["babelLocale"]) else units[settings["babelLocale"]]["average_speed"]["unit"], "icon": "speedometer", "url": STATISTICS_URL, "state_class": "measurement"},
|
{"name": "Average Speed", "domain": "sensor", "id": "average_speed", "unit": "km/h" if not units.get(settings["babelLocale"]) else units[settings["babelLocale"]]["average_speed"]["unit"], "icon": "speedometer", "url": STATISTICS_URL, "state_class": "measurement"},
|
||||||
{"name": "Hours to Service", "domain": "sensor", "id": "hours_to_service", "unit": "hour", "icon": "wrench-clock", "url": VEHICLE_DIAGNOSTICS_URL},
|
{"name": "Hours to Service", "domain": "sensor", "id": "hours_to_service", "unit": "hour", "icon": "wrench-clock", "url": VEHICLE_DIAGNOSTICS_URL},
|
||||||
{"name": "Distance to Service", "domain": "sensor", "id": "km_to_service", "unit": "km" if not units.get(settings["babelLocale"]) else units[settings["babelLocale"]]["distance_to_service"]["unit"], "icon": "wrench-clock", "url": VEHICLE_DIAGNOSTICS_URL},
|
{"name": "Distance to Service", "domain": "sensor", "id": "km_to_service", "unit": "km" if not units.get(settings["babelLocale"]) else units[settings["babelLocale"]]["distance_to_service"]["unit"], "icon": "wrench-clock", "url": VEHICLE_DIAGNOSTICS_URL},
|
||||||
{"name": "Months to Service", "domain": "sensor", "id": "months_to_service", "unit": "month", "icon": "wrench-clock", "url": VEHICLE_DIAGNOSTICS_URL},
|
{"name": "Time to Service", "domain": "sensor", "id": "time_to_service", "icon": "wrench-clock", "url": VEHICLE_DIAGNOSTICS_URL},
|
||||||
{"name": "Service warning status", "domain": "sensor", "id": "service_warning_status", "icon": "alert-outline", "url": VEHICLE_DIAGNOSTICS_URL},
|
{"name": "Service warning status", "domain": "sensor", "id": "service_warning_status", "icon": "alert-outline", "url": VEHICLE_DIAGNOSTICS_URL}
|
||||||
{"name": "Service warning trigger", "domain": "sensor", "id": "service_warning_trigger", "icon": "alert-outline", "url": VEHICLE_DIAGNOSTICS_URL}
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
old_entity_ids = ["months_to_service", "service_warning_trigger"]
|
||||||
|
|||||||
+19
-14
@@ -10,8 +10,7 @@ from datetime import datetime
|
|||||||
from babel.dates import format_datetime
|
from babel.dates import format_datetime
|
||||||
from config import settings
|
from config import settings
|
||||||
from const import CLIMATE_START_URL, CLIMATE_STOP_URL, CAR_LOCK_URL, \
|
from const import CLIMATE_START_URL, CLIMATE_STOP_URL, CAR_LOCK_URL, \
|
||||||
CAR_UNLOCK_URL, availability_topic, icon_states
|
CAR_UNLOCK_URL, availability_topic, icon_states, old_entity_ids
|
||||||
|
|
||||||
|
|
||||||
mqtt_client: mqtt.Client
|
mqtt_client: mqtt.Client
|
||||||
subscribed_topics = []
|
subscribed_topics = []
|
||||||
@@ -90,7 +89,7 @@ def on_connect(client, userdata, flags, rc):
|
|||||||
mqtt_client.subscribe(topic)
|
mqtt_client.subscribe(topic)
|
||||||
|
|
||||||
|
|
||||||
def on_disconnect(client, userdata, rc):
|
def on_disconnect(client, userdata, rc):
|
||||||
logging.warning("MQTT disconnected, reconnecting automatically")
|
logging.warning("MQTT disconnected, reconnecting automatically")
|
||||||
|
|
||||||
|
|
||||||
@@ -141,7 +140,7 @@ def start_climate_timer(d, vin):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
if timer_seconds > 0:
|
if timer_seconds > 0:
|
||||||
Timer(timer_seconds, activate_climate_timer, (vin, start_datetime.isoformat(), )).start()
|
Timer(timer_seconds, activate_climate_timer, (vin, start_datetime.isoformat(),)).start()
|
||||||
active_schedules[vin]["timers"].append(start_datetime.isoformat())
|
active_schedules[vin]["timers"].append(start_datetime.isoformat())
|
||||||
logging.debug("Climate timer set to " + str(start_datetime))
|
logging.debug("Climate timer set to " + str(start_datetime))
|
||||||
update_car_data()
|
update_car_data()
|
||||||
@@ -293,7 +292,7 @@ def update_ha_device(entity, vin, state):
|
|||||||
|
|
||||||
if entity.get("state_class"):
|
if entity.get("state_class"):
|
||||||
config["state_class"] = entity["state_class"]
|
config["state_class"] = entity["state_class"]
|
||||||
|
|
||||||
if entity.get("domain") == "device_tracker":
|
if entity.get("domain") == "device_tracker":
|
||||||
config["json_attributes_topic"] = f"homeassistant/{entity['domain']}/{vin}_{entity['id']}/attributes"
|
config["json_attributes_topic"] = f"homeassistant/{entity['domain']}/{vin}_{entity['id']}/attributes"
|
||||||
|
|
||||||
@@ -317,15 +316,15 @@ def create_ha_devices():
|
|||||||
devices[vin] = device
|
devices[vin] = device
|
||||||
for entity in volvo.supported_endpoints[vin]:
|
for entity in volvo.supported_endpoints[vin]:
|
||||||
config = {
|
config = {
|
||||||
"name": entity['name'],
|
"name": entity['name'],
|
||||||
"object_id": f"volvo_{vin}_{entity['id']}",
|
"object_id": f"volvo_{vin}_{entity['id']}",
|
||||||
"schema": "state",
|
"schema": "state",
|
||||||
"icon": f"mdi:{entity['icon']}",
|
"icon": f"mdi:{entity['icon']}",
|
||||||
"state_topic": f"homeassistant/{entity['domain']}/{vin}_{entity['id']}/state",
|
"state_topic": f"homeassistant/{entity['domain']}/{vin}_{entity['id']}/state",
|
||||||
"device": device,
|
"device": device,
|
||||||
"unique_id": f"volvoAAOS2mqtt_{vin}_{entity['id']}",
|
"unique_id": f"volvoAAOS2mqtt_{vin}_{entity['id']}",
|
||||||
"availability_topic": availability_topic
|
"availability_topic": availability_topic
|
||||||
}
|
}
|
||||||
if entity.get("device_class"):
|
if entity.get("device_class"):
|
||||||
config["device_class"] = entity["device_class"]
|
config["device_class"] = entity["device_class"]
|
||||||
|
|
||||||
@@ -361,3 +360,9 @@ def send_heartbeat():
|
|||||||
def send_offline():
|
def send_offline():
|
||||||
mqtt_client.publish(availability_topic, "offline")
|
mqtt_client.publish(availability_topic, "offline")
|
||||||
|
|
||||||
|
|
||||||
|
def delete_old_entities():
|
||||||
|
for vin in volvo.vins:
|
||||||
|
for entity in old_entity_ids:
|
||||||
|
topic = f"homeassistant/sensor/volvoAAOS2mqtt/{vin}_{entity}/config"
|
||||||
|
mqtt_client.publish(topic, payload="", retain=True)
|
||||||
|
|||||||
+12
-15
@@ -4,7 +4,7 @@ import mqtt
|
|||||||
import util
|
import util
|
||||||
import time
|
import time
|
||||||
import re
|
import re
|
||||||
from threading import currentThread
|
from threading import current_thread
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from config import settings
|
from config import settings
|
||||||
from babel.dates import format_datetime
|
from babel.dates import format_datetime
|
||||||
@@ -116,6 +116,7 @@ def get_vehicles():
|
|||||||
else:
|
else:
|
||||||
initialize_climate(vins)
|
initialize_climate(vins)
|
||||||
initialize_scheduler(vins)
|
initialize_scheduler(vins)
|
||||||
|
mqtt.delete_old_entities()
|
||||||
logging.info("Vin: " + str(vins) + " found!")
|
logging.info("Vin: " + str(vins) + " found!")
|
||||||
|
|
||||||
|
|
||||||
@@ -245,11 +246,6 @@ def check_supported_endpoints():
|
|||||||
# If battery charge level could be found in recharge-api, skip the second battery charge sensor
|
# If battery charge level could be found in recharge-api, skip the second battery charge sensor
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if entity["id"] == "engine_state" and entity["url"] == ENGINE_DIAGNOSTICS_URL \
|
|
||||||
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'):
|
if entity.get('url'):
|
||||||
state = api_call(entity["url"], "GET", vin, entity["id"])
|
state = api_call(entity["url"], "GET", vin, entity["id"])
|
||||||
else:
|
else:
|
||||||
@@ -308,7 +304,7 @@ def check_engine_status(vin):
|
|||||||
# Exit thread as engine state is unsupported by car
|
# Exit thread as engine state is unsupported by car
|
||||||
return None
|
return None
|
||||||
|
|
||||||
t = currentThread()
|
t = current_thread()
|
||||||
while getattr(t, "do_run", True):
|
while getattr(t, "do_run", True):
|
||||||
engine_state = api_call(endpoint_url, "GET", vin, "engine_state", True)
|
engine_state = api_call(endpoint_url, "GET", vin, "engine_state", True)
|
||||||
if engine_state == "ON":
|
if engine_state == "ON":
|
||||||
@@ -369,7 +365,7 @@ def api_call(url, method, vin, sensor_id=None, force_update=False, key_change=Fa
|
|||||||
return None
|
return None
|
||||||
elif response.status_code == 403 and "message" in data:
|
elif response.status_code == 403 and "message" in data:
|
||||||
if "Out of call volume quota" in data["message"]:
|
if "Out of call volume quota" in data["message"]:
|
||||||
logging.warn("Quota extended. Try to change VCCAPIKEY!")
|
logging.warning("Quota extended. Try to change VCCAPIKEY!")
|
||||||
change_vcc_api_key()
|
change_vcc_api_key()
|
||||||
api_call(url, method, vin, sensor_id, force_update, True)
|
api_call(url, method, vin, sensor_id, force_update, True)
|
||||||
else:
|
else:
|
||||||
@@ -540,10 +536,10 @@ def parse_api_data(data, sensor_id=None):
|
|||||||
"gps_accuracy": 1}
|
"gps_accuracy": 1}
|
||||||
return coordinates
|
return coordinates
|
||||||
elif sensor_id == "distance_to_empty":
|
elif sensor_id == "distance_to_empty":
|
||||||
if util.keys_exists(data, "distanceToEmpty"):
|
if util.keys_exists(data, "distanceToEmptyTank"):
|
||||||
distance_to_empty = int(data["distanceToEmpty"]["value"])
|
distance_to_empty = int(data["distanceToEmptyTank"]["value"])
|
||||||
if distance_to_empty > 0:
|
if distance_to_empty > 0:
|
||||||
return util.convert_metric_values(data["distanceToEmpty"]["value"])
|
return util.convert_metric_values(data["distanceToEmptyTank"]["value"])
|
||||||
return None
|
return None
|
||||||
elif sensor_id == "hours_to_service":
|
elif sensor_id == "hours_to_service":
|
||||||
return data["engineHoursToService"]["value"] if util.keys_exists(data, "engineHoursToService") else None
|
return data["engineHoursToService"]["value"] if util.keys_exists(data, "engineHoursToService") else None
|
||||||
@@ -553,11 +549,12 @@ def parse_api_data(data, sensor_id=None):
|
|||||||
if km_to_service > 0:
|
if km_to_service > 0:
|
||||||
return util.convert_metric_values(data["distanceToService"]["value"])
|
return util.convert_metric_values(data["distanceToService"]["value"])
|
||||||
return None
|
return None
|
||||||
elif sensor_id == "months_to_service":
|
elif sensor_id == "time_to_service":
|
||||||
return data["timeToService"]["value"] if util.keys_exists(data, "timeToService") else None
|
if util.keys_exists(data, "timeToService"):
|
||||||
|
return str(data["timeToService"]["value"]) + " " + data["timeToService"]["unit"]
|
||||||
|
else:
|
||||||
|
return None
|
||||||
elif sensor_id == "service_warning_status":
|
elif sensor_id == "service_warning_status":
|
||||||
return data["serviceWarning"]["value"] if util.keys_exists(data, "serviceWarning") else None
|
return data["serviceWarning"]["value"] if util.keys_exists(data, "serviceWarning") else None
|
||||||
elif sensor_id == "service_warning_trigger":
|
|
||||||
return data["serviceTrigger"]["value"] if util.keys_exists(data, "serviceTrigger") else None
|
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|||||||
Reference in New Issue
Block a user