mirror of
https://github.com/Dielee/volvo2mqtt.git
synced 2026-08-12 10:49:52 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aeea34b438 | ||
|
|
1ccd140400 | ||
|
|
f4aef07e55 | ||
|
|
8f6d664264 | ||
|
|
2b510d4e1b | ||
|
|
5c0216465e | ||
|
|
d9ad484c69 | ||
|
|
28721384f7 | ||
|
|
c7a883d781 | ||
|
|
023d8d2096 | ||
|
|
64d0640282 | ||
|
|
39c748fe1a |
@@ -0,0 +1,5 @@
|
|||||||
|
CONF_updateInterval=300
|
||||||
|
CONF_babelLocale='de'
|
||||||
|
CONF_mqtt='@json {"broker": "mqtt", "username": "", "password": ""}'
|
||||||
|
CONF_volvoData='@json {"username": "", "password": "", "vin": "", "vccapikey": ""}'
|
||||||
|
TZ='Europe/Berlin'
|
||||||
+2
-1
@@ -1,3 +1,4 @@
|
|||||||
.idea
|
.idea
|
||||||
venv
|
venv
|
||||||
src/settings.dev.json
|
src/settings.dev.json
|
||||||
|
.env
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ Maybe this component works also with other Volvo cars. Please try out the native
|
|||||||
|
|
||||||
## Confirmed working with
|
## Confirmed working with
|
||||||
- XC40 BEV (2023)
|
- XC40 BEV (2023)
|
||||||
|
- XC40 BEV (2022)
|
||||||
- V60 T8 PHEV (2023)
|
- V60 T8 PHEV (2023)
|
||||||
- C40 PHEV (2023)
|
- C40 PHEV (2023)
|
||||||
- C40 PHEV (2022)
|
- C40 PHEV (2022)
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
version: '3'
|
||||||
|
services:
|
||||||
|
app:
|
||||||
|
build: .
|
||||||
|
env_file:
|
||||||
|
- .env
|
||||||
|
# environment:
|
||||||
|
# CONF_updateInterval: 300
|
||||||
|
# CONF_babelLocale: 'de'
|
||||||
|
# CONF_mqtt: '@json {"broker": "mqtt", "username": "", "password": ""}'
|
||||||
|
# CONF_volvoData: '@json {"username": "", "password": "", "vin": "", "vccapikey": ""}'
|
||||||
|
# TZ: 'Europe/Berlin'
|
||||||
|
links:
|
||||||
|
- mqtt
|
||||||
|
depends_on:
|
||||||
|
- mqtt
|
||||||
|
|
||||||
|
mqtt:
|
||||||
|
container_name: mqtt
|
||||||
|
image: eclipse-mosquitto
|
||||||
|
expose:
|
||||||
|
- 1883
|
||||||
|
command: [ "/usr/sbin/mosquitto", "-c", "/mosquitto-no-auth.conf" ]
|
||||||
|
|
||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
name: "Volvo2Mqtt"
|
name: "Volvo2Mqtt"
|
||||||
description: "Volvo AAOS MQTT bridge"
|
description: "Volvo AAOS MQTT bridge"
|
||||||
version: "1.3.2"
|
version: "1.4.0"
|
||||||
slug: "volvo2mqtt"
|
slug: "volvo2mqtt"
|
||||||
init: false
|
init: false
|
||||||
url: "https://github.com/Dielee/volvo2mqtt"
|
url: "https://github.com/Dielee/volvo2mqtt"
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
from config import settings
|
from config import settings
|
||||||
|
|
||||||
VERSION = "v1.3.2"
|
VERSION = "v1.4.0"
|
||||||
|
|
||||||
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"
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ from volvo import authorize
|
|||||||
from mqtt import update_loop, connect
|
from mqtt import update_loop, connect
|
||||||
from const import VERSION
|
from const import VERSION
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
print("Starting volvo2mqtt version " + VERSION)
|
print("Starting volvo2mqtt version " + VERSION)
|
||||||
authorize()
|
authorize()
|
||||||
|
|||||||
+8
-7
@@ -14,7 +14,7 @@ mqtt_client: mqtt.Client
|
|||||||
subscribed_topics = []
|
subscribed_topics = []
|
||||||
assumed_climate_state = {}
|
assumed_climate_state = {}
|
||||||
last_data_update = None
|
last_data_update = None
|
||||||
climate_timer: Timer
|
climate_timer = {}
|
||||||
|
|
||||||
|
|
||||||
def connect():
|
def connect():
|
||||||
@@ -56,24 +56,24 @@ def on_message(client, userdata, msg):
|
|||||||
api_thread.start()
|
api_thread.start()
|
||||||
assumed_climate_state[vin] = "ON"
|
assumed_climate_state[vin] = "ON"
|
||||||
# Starting timer to disable climate after 30 mins
|
# Starting timer to disable climate after 30 mins
|
||||||
climate_timer = Timer(1 * 60, volvo.disable_climate, (vin, ))
|
climate_timer[vin] = Timer(30 * 60, volvo.disable_climate, (vin, ))
|
||||||
climate_timer.start()
|
climate_timer[vin].start()
|
||||||
update_car_data()
|
update_car_data()
|
||||||
elif payload == "OFF":
|
elif payload == "OFF":
|
||||||
api_thread = Thread(target=volvo.api_call, args=(CLIMATE_STOP_URL, "POST", vin))
|
api_thread = Thread(target=volvo.api_call, args=(CLIMATE_STOP_URL, "POST", vin))
|
||||||
api_thread.start()
|
api_thread.start()
|
||||||
assumed_climate_state[vin] = "OFF"
|
assumed_climate_state[vin] = "OFF"
|
||||||
# Stop timer if active
|
# Stop timer if active
|
||||||
if climate_timer.is_alive():
|
if climate_timer[vin].is_alive():
|
||||||
climate_timer.cancel()
|
climate_timer[vin].cancel()
|
||||||
update_car_data()
|
update_car_data()
|
||||||
elif "lock_status" in msg.topic:
|
elif "lock_status" in msg.topic:
|
||||||
if payload == "LOCK":
|
if payload == "LOCK":
|
||||||
volvo.api_call(CAR_LOCK_URL, "POST", vin)
|
volvo.api_call(CAR_LOCK_URL, "POST", vin)
|
||||||
update_car_data()
|
update_car_data(True)
|
||||||
elif payload == "UNLOCK":
|
elif payload == "UNLOCK":
|
||||||
volvo.api_call(CAR_UNLOCK_URL, "POST", vin)
|
volvo.api_call(CAR_UNLOCK_URL, "POST", vin)
|
||||||
update_car_data()
|
update_car_data(True)
|
||||||
elif "update_data" in msg.topic:
|
elif "update_data" in msg.topic:
|
||||||
if payload == "PRESS":
|
if payload == "PRESS":
|
||||||
update_car_data(True)
|
update_car_data(True)
|
||||||
@@ -143,5 +143,6 @@ def create_ha_devices():
|
|||||||
mqtt_client.publish(
|
mqtt_client.publish(
|
||||||
f"homeassistant/{entity['domain']}/volvoAAOS2mqtt/{vin}_{entity['id']}/config",
|
f"homeassistant/{entity['domain']}/volvoAAOS2mqtt/{vin}_{entity['id']}/config",
|
||||||
json.dumps(config),
|
json.dumps(config),
|
||||||
|
retain=True
|
||||||
)
|
)
|
||||||
time.sleep(2)
|
time.sleep(2)
|
||||||
|
|||||||
+55
-93
@@ -18,15 +18,8 @@ session.headers = {
|
|||||||
token_expires_at: datetime
|
token_expires_at: datetime
|
||||||
refresh_token = None
|
refresh_token = None
|
||||||
vins = []
|
vins = []
|
||||||
recharge_cached_api_response = {}
|
|
||||||
recharge_api_last_update = {}
|
|
||||||
window_cached_api_response = {}
|
|
||||||
window_api_last_update = {}
|
|
||||||
door_cached_api_response = {}
|
|
||||||
door_api_last_update = {}
|
|
||||||
tyre_cached_api_response = {}
|
|
||||||
tyre_api_last_update = {}
|
|
||||||
supported_endpoints = {}
|
supported_endpoints = {}
|
||||||
|
cached_requests = {}
|
||||||
|
|
||||||
|
|
||||||
def authorize():
|
def authorize():
|
||||||
@@ -177,22 +170,42 @@ def api_call(url, method, vin, sensor_id=None, force_update=False):
|
|||||||
|
|
||||||
if url == RECHARGE_STATE_URL:
|
if url == RECHARGE_STATE_URL:
|
||||||
# Minimize API calls for recharge state
|
# Minimize API calls for recharge state
|
||||||
response = pull_recharge_api(url, method, vin, force_update)
|
response = cached_request(url, method, vin, force_update)
|
||||||
|
if response is None:
|
||||||
|
# Exception caught while getting data from volvo api, doing nothing
|
||||||
|
return None
|
||||||
elif url == WINDOWS_STATE_URL:
|
elif url == WINDOWS_STATE_URL:
|
||||||
# Minimize API calls for window state
|
# Minimize API calls for window state
|
||||||
response = pull_window_api(url, method, vin, force_update)
|
response = cached_request(url, method, vin, force_update)
|
||||||
|
if response is None:
|
||||||
|
# Exception caught while getting data from volvo api, doing nothing
|
||||||
|
return ""
|
||||||
elif url == LOCK_STATE_URL:
|
elif url == LOCK_STATE_URL:
|
||||||
# Minimize API calls for door state
|
# Minimize API calls for door state
|
||||||
response = pull_door_api(url, method, vin, force_update)
|
response = cached_request(url, method, vin, force_update)
|
||||||
|
if response is None:
|
||||||
|
# Exception caught while getting data from volvo api, doing nothing
|
||||||
|
return ""
|
||||||
elif url == TYRE_STATE_URL:
|
elif url == TYRE_STATE_URL:
|
||||||
# Minimize API calls for tyre state
|
# Minimize API calls for tyre state
|
||||||
response = pull_tyre_api(url, method, vin, force_update)
|
response = cached_request(url, method, vin, force_update)
|
||||||
|
if response is None:
|
||||||
|
# Exception caught while getting data from volvo api, doing nothing
|
||||||
|
return ""
|
||||||
elif method == "GET":
|
elif method == "GET":
|
||||||
print("Starting " + method + " call against " + url)
|
print("Starting " + method + " call against " + url)
|
||||||
response = session.get(url.format(vin), timeout=15)
|
try:
|
||||||
|
response = session.get(url.format(vin), timeout=15)
|
||||||
|
except requests.exceptions.RequestException as e:
|
||||||
|
print("Error getting data: " + str(e))
|
||||||
|
return ""
|
||||||
elif method == "POST":
|
elif method == "POST":
|
||||||
print("Starting " + method + " call against " + url)
|
print("Starting " + method + " call against " + url)
|
||||||
response = session.post(url.format(vin), timeout=20)
|
try:
|
||||||
|
response = session.post(url.format(vin), timeout=20)
|
||||||
|
except requests.exceptions.RequestException as e:
|
||||||
|
print("Error getting data: " + str(e))
|
||||||
|
return ""
|
||||||
else:
|
else:
|
||||||
print("Unkown method posted: " + method + ". Returning nothing")
|
print("Unkown method posted: " + method + ". Returning nothing")
|
||||||
return ""
|
return ""
|
||||||
@@ -213,87 +226,34 @@ def api_call(url, method, vin, sensor_id=None, force_update=False):
|
|||||||
return parse_api_data(data, sensor_id)
|
return parse_api_data(data, sensor_id)
|
||||||
|
|
||||||
|
|
||||||
def pull_tyre_api(url, method, vin, force_update=False):
|
def cached_request(url, method, vin, force_update=False):
|
||||||
global tyre_cached_api_response, tyre_api_last_update
|
global cached_requests
|
||||||
if not vin in tyre_cached_api_response or force_update:
|
if not keys_exists(cached_requests, vin + "_" + url):
|
||||||
# No API Data for vin cached, get fresh data from API
|
# No API Data cached, get fresh data from API
|
||||||
print("Starting " + method + " call against " + url)
|
print("Starting " + method + " call against " + url)
|
||||||
response = session.get(url.format(vin), timeout=15)
|
try:
|
||||||
tyre_cached_api_response[vin] = response
|
|
||||||
tyre_api_last_update[vin] = datetime.now()
|
|
||||||
else:
|
|
||||||
if (datetime.now() - tyre_api_last_update[vin]).total_seconds() >= settings["updateInterval"]:
|
|
||||||
# Old Data in Cache, updating
|
|
||||||
print("Starting " + method + " call against " + url)
|
|
||||||
response = session.get(url.format(vin), timeout=15)
|
response = session.get(url.format(vin), timeout=15)
|
||||||
tyre_cached_api_response[vin] = response
|
except requests.exceptions.RequestException as e:
|
||||||
tyre_api_last_update[vin] = datetime.now()
|
print("Error getting data: " + str(e))
|
||||||
|
return None
|
||||||
|
|
||||||
|
data = {"response": response, "last_update": datetime.now()}
|
||||||
|
cached_requests[vin + "_" + url] = data
|
||||||
|
else:
|
||||||
|
if (datetime.now() - cached_requests[vin + "_" + url]["last_update"]).total_seconds() >= settings["updateInterval"] \
|
||||||
|
or (force_update and (datetime.now() - cached_requests[vin + "_" + url]["last_update"]).total_seconds() >= 2):
|
||||||
|
# Old Data in Cache, or force mode active, updating
|
||||||
|
print("Starting " + method + " call against " + url)
|
||||||
|
try:
|
||||||
|
response = session.get(url.format(vin), timeout=15)
|
||||||
|
except requests.exceptions.RequestException as e:
|
||||||
|
print("Error getting data: " + str(e))
|
||||||
|
return None
|
||||||
|
data = {"response": response, "last_update": datetime.now()}
|
||||||
|
cached_requests[vin + "_" + url] = data
|
||||||
else:
|
else:
|
||||||
# Data is up do date, returning cached data
|
# Data is up do date, returning cached data
|
||||||
response = tyre_cached_api_response[vin]
|
response = cached_requests[vin + "_" + url]["response"]
|
||||||
return response
|
|
||||||
|
|
||||||
|
|
||||||
def pull_door_api(url, method, vin, force_update=False):
|
|
||||||
global door_cached_api_response, door_api_last_update
|
|
||||||
if not vin in door_cached_api_response or force_update:
|
|
||||||
# No API Data for vin cached, get fresh data from API
|
|
||||||
print("Starting " + method + " call against " + url)
|
|
||||||
response = session.get(url.format(vin), timeout=15)
|
|
||||||
door_cached_api_response[vin] = response
|
|
||||||
door_api_last_update[vin] = datetime.now()
|
|
||||||
else:
|
|
||||||
if (datetime.now() - door_api_last_update[vin]).total_seconds() >= settings["updateInterval"]:
|
|
||||||
# Old Data in Cache, updating
|
|
||||||
print("Starting " + method + " call against " + url)
|
|
||||||
response = session.get(url.format(vin), timeout=15)
|
|
||||||
door_cached_api_response[vin] = response
|
|
||||||
door_api_last_update[vin] = datetime.now()
|
|
||||||
else:
|
|
||||||
# Data is up do date, returning cached data
|
|
||||||
response = door_cached_api_response[vin]
|
|
||||||
return response
|
|
||||||
|
|
||||||
|
|
||||||
def pull_window_api(url, method, vin, force_update=False):
|
|
||||||
global window_cached_api_response, window_api_last_update
|
|
||||||
if not vin in window_cached_api_response or force_update:
|
|
||||||
# No API Data for vin cached, get fresh data from API
|
|
||||||
print("Starting " + method + " call against " + url)
|
|
||||||
response = session.get(url.format(vin), timeout=15)
|
|
||||||
window_cached_api_response[vin] = response
|
|
||||||
window_api_last_update[vin] = datetime.now()
|
|
||||||
else:
|
|
||||||
if (datetime.now() - window_api_last_update[vin]).total_seconds() >= settings["updateInterval"]:
|
|
||||||
# Old Data in Cache, updating
|
|
||||||
print("Starting " + method + " call against " + url)
|
|
||||||
response = session.get(url.format(vin), timeout=15)
|
|
||||||
window_cached_api_response[vin] = response
|
|
||||||
window_api_last_update[vin] = datetime.now()
|
|
||||||
else:
|
|
||||||
# Data is up do date, returning cached data
|
|
||||||
response = window_cached_api_response[vin]
|
|
||||||
return response
|
|
||||||
|
|
||||||
|
|
||||||
def pull_recharge_api(url, method, vin, force_update=False):
|
|
||||||
global recharge_cached_api_response, recharge_api_last_update
|
|
||||||
if not vin in recharge_cached_api_response or force_update:
|
|
||||||
# No API Data for vin cached, get fresh data from API
|
|
||||||
print("Starting " + method + " call against " + url)
|
|
||||||
response = session.get(url.format(vin), timeout=15)
|
|
||||||
recharge_cached_api_response[vin] = response
|
|
||||||
recharge_api_last_update[vin] = datetime.now()
|
|
||||||
else:
|
|
||||||
if (datetime.now() - recharge_api_last_update[vin]).total_seconds() >= settings["updateInterval"]:
|
|
||||||
# Old Data in Cache, updating
|
|
||||||
print("Starting " + method + " call against " + url)
|
|
||||||
response = session.get(url.format(vin), timeout=15)
|
|
||||||
recharge_cached_api_response[vin] = response
|
|
||||||
recharge_api_last_update[vin] = datetime.now()
|
|
||||||
else:
|
|
||||||
# Data is up do date, returning cached data
|
|
||||||
response = recharge_cached_api_response[vin]
|
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
@@ -304,9 +264,11 @@ def parse_api_data(data, sensor_id=None):
|
|||||||
elif sensor_id == "electric_range":
|
elif sensor_id == "electric_range":
|
||||||
return data["electricRange"]["value"] if keys_exists(data, "electricRange") else None
|
return data["electricRange"]["value"] if keys_exists(data, "electricRange") else None
|
||||||
elif sensor_id == "charging_system_status":
|
elif sensor_id == "charging_system_status":
|
||||||
return charging_system_states[data["chargingSystemStatus"]["value"]] if keys_exists(data, "chargingSystemStatus") else None
|
return charging_system_states[data["chargingSystemStatus"]["value"]] if keys_exists(data,
|
||||||
|
"chargingSystemStatus") else None
|
||||||
elif sensor_id == "charging_connection_status":
|
elif sensor_id == "charging_connection_status":
|
||||||
return charging_connection_states[data["chargingConnectionStatus"]["value"]] if keys_exists(data, "chargingConnectionStatus") else None
|
return charging_connection_states[data["chargingConnectionStatus"]["value"]] if keys_exists(data,
|
||||||
|
"chargingConnectionStatus") else None
|
||||||
elif sensor_id == "estimated_charging_time":
|
elif sensor_id == "estimated_charging_time":
|
||||||
if keys_exists(data, "chargingSystemStatus"):
|
if keys_exists(data, "chargingSystemStatus"):
|
||||||
charging_system_state = charging_system_states[data["chargingSystemStatus"]["value"]]
|
charging_system_state = charging_system_states[data["chargingSystemStatus"]["value"]]
|
||||||
|
|||||||
Reference in New Issue
Block a user