mirror of
https://github.com/Dielee/volvo2mqtt.git
synced 2026-08-12 18:59:53 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ebc3ba4e16 | ||
|
|
a1881a87d8 | ||
|
|
8c0ce8976e |
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
name: "Volvo2Mqtt"
|
name: "Volvo2Mqtt"
|
||||||
description: "Volvo AAOS MQTT bridge"
|
description: "Volvo AAOS MQTT bridge"
|
||||||
version: "1.6.2"
|
version: "1.6.5"
|
||||||
slug: "volvo2mqtt"
|
slug: "volvo2mqtt"
|
||||||
init: false
|
init: false
|
||||||
url: "https://github.com/Dielee/volvo2mqtt"
|
url: "https://github.com/Dielee/volvo2mqtt"
|
||||||
|
|||||||
+28
-8
@@ -1,6 +1,6 @@
|
|||||||
from config import settings
|
from config import settings
|
||||||
|
|
||||||
VERSION = "v1.6.2"
|
VERSION = "v1.6.5"
|
||||||
|
|
||||||
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"
|
||||||
@@ -19,21 +19,21 @@ ENGINE_STATE_URL = "https://api.volvocars.com/connected-vehicle/v2/vehicles/{0}/
|
|||||||
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/v2/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"
|
||||||
DISTANCE_TO_EMPTY_URL = "https://api.volvocars.com/extended-vehicle/v1/vehicles/{0}/resources/distanceToEmpty"
|
|
||||||
WASHER_FLUID_LEVEL_URL = "https://api.volvocars.com/extended-vehicle/v1/vehicles/{0}/resources/washerFluidLevel"
|
|
||||||
|
|
||||||
units = {
|
units = {
|
||||||
"en_GB": {
|
"en_GB": {
|
||||||
"divider": 1.60934,
|
"divider": 1.60934,
|
||||||
"electric_range": {"unit": "mi"},
|
"electric_range": {"unit": "mi"},
|
||||||
"odometer": {"unit": "mi"},
|
"odometer": {"unit": "mi"},
|
||||||
"average_speed": {"unit": "mph"}
|
"average_speed": {"unit": "mph"},
|
||||||
|
"distance_to_empty": {"unit": "mi"}
|
||||||
},
|
},
|
||||||
"en_US": {
|
"en_US": {
|
||||||
"divider": 1.60934,
|
"divider": 1.60934,
|
||||||
"electric_range": {"unit": "mi"},
|
"electric_range": {"unit": "mi"},
|
||||||
"odometer": {"unit": "mi"},
|
"odometer": {"unit": "mi"},
|
||||||
"average_speed": {"unit": "mph"}
|
"average_speed": {"unit": "mph"},
|
||||||
|
"distance_to_empty": {"unit": "mi"}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,6 +48,27 @@ charging_connection_states = {"CONNECTION_STATUS_DISCONNECTED": "Disconnected",
|
|||||||
window_states = {"CLOSED": "OFF", "OPEN": "ON"}
|
window_states = {"CLOSED": "OFF", "OPEN": "ON"}
|
||||||
door_states = {"CLOSED": "OFF", "OPEN": "ON"}
|
door_states = {"CLOSED": "OFF", "OPEN": "ON"}
|
||||||
|
|
||||||
|
icon_states = {
|
||||||
|
"lock_status": {"UNLOCKED": "lock-open-alert", "LOCKED": "lock"},
|
||||||
|
"door_front_left": {"ON": "car-door", "OFF": "car-door-lock"},
|
||||||
|
"door_front_right": {"ON": "car-door", "OFF": "car-door-lock"},
|
||||||
|
"door_rear_left": {"ON": "car-door", "OFF": "car-door-lock"},
|
||||||
|
"door_rear_right": {"ON": "car-door", "OFF": "car-door-lock"},
|
||||||
|
"battery_charge_level": [
|
||||||
|
{"from": 100, "to": 100, "icon": "battery"},
|
||||||
|
{"from": 99, "to": 90, "icon": "battery-90"},
|
||||||
|
{"from": 89, "to": 80, "icon": "battery-80"},
|
||||||
|
{"from": 79, "to": 70, "icon": "battery-70"},
|
||||||
|
{"from": 69, "to": 60, "icon": "battery-60"},
|
||||||
|
{"from": 59, "to": 50, "icon": "battery-50"},
|
||||||
|
{"from": 49, "to": 40, "icon": "battery-40"},
|
||||||
|
{"from": 39, "to": 30, "icon": "battery-30"},
|
||||||
|
{"from": 29, "to": 20, "icon": "battery-20"},
|
||||||
|
{"from": 19, "to": 10, "icon": "battery-10"},
|
||||||
|
{"from": 9, "to": 0, "icon": "battery-alert-variant-outline"},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
supported_entities = [
|
supported_entities = [
|
||||||
{"name": "Battery Charge Level", "domain": "sensor", "id": "battery_charge_level", "unit": "%", "icon": "car-battery", "url": RECHARGE_STATE_URL},
|
{"name": "Battery Charge Level", "domain": "sensor", "id": "battery_charge_level", "unit": "%", "icon": "car-battery", "url": RECHARGE_STATE_URL},
|
||||||
{"name": "Battery Charge Level", "domain": "sensor", "id": "battery_charge_level", "unit": "%", "icon": "car-battery", "url": BATTERY_CHARGE_STATE_URL},
|
{"name": "Battery Charge Level", "domain": "sensor", "id": "battery_charge_level", "unit": "%", "icon": "car-battery", "url": BATTERY_CHARGE_STATE_URL},
|
||||||
@@ -81,7 +102,6 @@ supported_entities = [
|
|||||||
{"name": "Engine State", "domain": "sensor", "id": "engine_state", "icon": "engine", "url": ENGINE_STATE_URL},
|
{"name": "Engine State", "domain": "sensor", "id": "engine_state", "icon": "engine", "url": ENGINE_STATE_URL},
|
||||||
{"name": "Fuel Level", "domain": "sensor", "id": "fuel_level", "unit": "liters", "icon": "fuel", "url": FUEL_STATE_URL},
|
{"name": "Fuel Level", "domain": "sensor", "id": "fuel_level", "unit": "liters", "icon": "fuel", "url": FUEL_STATE_URL},
|
||||||
{"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": "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},
|
{"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},
|
||||||
{"name": "Distance to Empty", "domain": "sensor", "id": "distance_to_empty", "unit": "km" if not units.get(settings["babelLocale"]) else units[settings["babelLocale"]]["odometer"]["unit"], "icon": "map-marker-distance", "url": DISTANCE_TO_EMPTY_URL},
|
{"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}
|
||||||
{"name": "Washer Fluid Level", "domain": "sensor", "id": "washer_fluid_level", "icon": "wiper-wash", "url": WASHER_FLUID_LEVEL_URL},
|
|
||||||
]
|
]
|
||||||
|
|||||||
+49
-2
@@ -9,7 +9,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
|
CAR_UNLOCK_URL, availability_topic, icon_states
|
||||||
|
|
||||||
|
|
||||||
mqtt_client: mqtt.Client
|
mqtt_client: mqtt.Client
|
||||||
@@ -18,6 +18,7 @@ assumed_climate_state = {}
|
|||||||
last_data_update = None
|
last_data_update = None
|
||||||
climate_timer = {}
|
climate_timer = {}
|
||||||
door_status = {}
|
door_status = {}
|
||||||
|
devices = {}
|
||||||
|
|
||||||
|
|
||||||
def connect():
|
def connect():
|
||||||
@@ -154,12 +155,58 @@ def update_car_data(force_update=False, overwrite={}):
|
|||||||
topic,
|
topic,
|
||||||
json.dumps(state) if isinstance(state, dict) else state
|
json.dumps(state) if isinstance(state, dict) else state
|
||||||
)
|
)
|
||||||
|
update_ha_device(entity, vin, state)
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
||||||
|
else:
|
||||||
|
icon = icon_config[state]
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
logging.debug("Updating icon to " + icon + " for " + entity["id"])
|
||||||
|
config = {
|
||||||
|
"name": entity['name'],
|
||||||
|
"object_id": f"volvo_{vin}_{entity['id']}",
|
||||||
|
"schema": "state",
|
||||||
|
"icon": f"mdi:{icon}" if icon else f"mdi:{entity['icon']}",
|
||||||
|
"state_topic": f"homeassistant/{entity['domain']}/{vin}_{entity['id']}/state",
|
||||||
|
"device": devices[vin],
|
||||||
|
"unique_id": f"volvoAAOS2mqtt_{vin}_{entity['id']}",
|
||||||
|
"availability_topic": availability_topic
|
||||||
|
}
|
||||||
|
if entity.get("device_class"):
|
||||||
|
config["device_class"] = entity["device_class"]
|
||||||
|
|
||||||
|
if entity.get("unit"):
|
||||||
|
config["unit_of_measurement"] = entity["unit"]
|
||||||
|
|
||||||
|
if entity.get("domain") == "device_tracker":
|
||||||
|
config["json_attributes_topic"] = f"homeassistant/{entity['domain']}/{vin}_{entity['id']}/attributes"
|
||||||
|
|
||||||
|
if entity.get("domain") in ["switch", "lock", "button"]:
|
||||||
|
command_topic = f"homeassistant/{entity['domain']}/{vin}_{entity['id']}/command"
|
||||||
|
config["command_topic"] = command_topic
|
||||||
|
subscribed_topics.append(command_topic)
|
||||||
|
mqtt_client.subscribe(command_topic)
|
||||||
|
|
||||||
|
mqtt_client.publish(
|
||||||
|
f"homeassistant/{entity['domain']}/volvoAAOS2mqtt/{vin}_{entity['id']}/config",
|
||||||
|
json.dumps(config),
|
||||||
|
retain=True
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def create_ha_devices():
|
def create_ha_devices():
|
||||||
global subscribed_topics
|
global subscribed_topics, devices
|
||||||
for vin in volvo.vins:
|
for vin in volvo.vins:
|
||||||
device = volvo.get_vehicle_details(vin)
|
device = volvo.get_vehicle_details(vin)
|
||||||
|
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'],
|
||||||
|
|||||||
@@ -11,6 +11,14 @@ from pathlib import Path
|
|||||||
TZ = None
|
TZ = None
|
||||||
|
|
||||||
|
|
||||||
|
def get_icon_between(icon_list, state):
|
||||||
|
icon = None
|
||||||
|
for s in icon_list:
|
||||||
|
if s["to"] <= state <= s["from"]:
|
||||||
|
icon = s["icon"]
|
||||||
|
return icon
|
||||||
|
|
||||||
|
|
||||||
def setup_logging():
|
def setup_logging():
|
||||||
log_location = "volvo2mqtt.log"
|
log_location = "volvo2mqtt.log"
|
||||||
if os.environ.get("IS_HA_ADDON"):
|
if os.environ.get("IS_HA_ADDON"):
|
||||||
|
|||||||
+10
-21
@@ -1,5 +1,4 @@
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
import mqtt
|
import mqtt
|
||||||
import util
|
import util
|
||||||
@@ -290,8 +289,7 @@ def parse_api_data(data, sensor_id=None):
|
|||||||
return data["estimatedChargingTime"]["value"] if util.keys_exists(data, "estimatedChargingTime") else ""
|
return data["estimatedChargingTime"]["value"] if util.keys_exists(data, "estimatedChargingTime") else ""
|
||||||
else:
|
else:
|
||||||
return 0
|
return 0
|
||||||
else:
|
return None
|
||||||
return None
|
|
||||||
elif sensor_id == "estimated_charging_finish_time":
|
elif sensor_id == "estimated_charging_finish_time":
|
||||||
if util.keys_exists(data, "chargingSystemStatus"):
|
if util.keys_exists(data, "chargingSystemStatus"):
|
||||||
charging_system_state = charging_system_states[data["chargingSystemStatus"]["value"]]
|
charging_system_state = charging_system_states[data["chargingSystemStatus"]["value"]]
|
||||||
@@ -303,8 +301,7 @@ def parse_api_data(data, sensor_id=None):
|
|||||||
return format_datetime(charging_finished, format="medium", locale=settings["babelLocale"])
|
return format_datetime(charging_finished, format="medium", locale=settings["babelLocale"])
|
||||||
else:
|
else:
|
||||||
return ""
|
return ""
|
||||||
else:
|
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["carLocked"]["value"] if util.keys_exists(data, "carLocked") else None
|
||||||
elif sensor_id == "odometer":
|
elif sensor_id == "odometer":
|
||||||
@@ -361,10 +358,7 @@ def parse_api_data(data, sensor_id=None):
|
|||||||
fuel_amount = int(data["fuelAmount"]["value"])
|
fuel_amount = int(data["fuelAmount"]["value"])
|
||||||
if fuel_amount > 0:
|
if fuel_amount > 0:
|
||||||
return fuel_amount
|
return fuel_amount
|
||||||
else:
|
return None
|
||||||
return None
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
elif sensor_id == "average_fuel_consumption":
|
elif sensor_id == "average_fuel_consumption":
|
||||||
if util.keys_exists(data, "averageFuelConsumption"):
|
if util.keys_exists(data, "averageFuelConsumption"):
|
||||||
average_fuel_con = float(data["averageFuelConsumption"]["value"])
|
average_fuel_con = float(data["averageFuelConsumption"]["value"])
|
||||||
@@ -377,10 +371,7 @@ def parse_api_data(data, sensor_id=None):
|
|||||||
elif multiplier < 1:
|
elif multiplier < 1:
|
||||||
multiplier = 1
|
multiplier = 1
|
||||||
return average_fuel_con * multiplier
|
return average_fuel_con * multiplier
|
||||||
else:
|
return None
|
||||||
return None
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
elif sensor_id == "average_speed":
|
elif sensor_id == "average_speed":
|
||||||
if util.keys_exists(data, "averageSpeed"):
|
if util.keys_exists(data, "averageSpeed"):
|
||||||
average_speed = int(data["averageSpeed"]["value"])
|
average_speed = int(data["averageSpeed"]["value"])
|
||||||
@@ -393,10 +384,7 @@ def parse_api_data(data, sensor_id=None):
|
|||||||
elif divider < 1:
|
elif divider < 1:
|
||||||
divider = 1
|
divider = 1
|
||||||
return util.convert_metric_values(average_speed / divider)
|
return util.convert_metric_values(average_speed / divider)
|
||||||
else:
|
return None
|
||||||
return None
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
elif sensor_id == "location":
|
elif sensor_id == "location":
|
||||||
coordinates = {}
|
coordinates = {}
|
||||||
if util.keys_exists(data, "geometry"):
|
if util.keys_exists(data, "geometry"):
|
||||||
@@ -407,9 +395,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":
|
||||||
return util.convert_metric_values(data["distanceToEmpty"]["value"]) \
|
if util.keys_exists(data, "distanceToEmpty"):
|
||||||
if util.keys_exists(data, "distanceToEmpty") else None
|
distance_to_empty = int(data["distanceToEmpty"]["value"])
|
||||||
elif sensor_id == "washer_fluid_level":
|
if distance_to_empty > 0:
|
||||||
return data["washerFluidLevel"]["value"] if util.keys_exists(data, "washerFluidLevel") else None
|
return util.convert_metric_values(data["distanceToEmpty"]["value"])
|
||||||
|
return None
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|||||||
Reference in New Issue
Block a user