mirror of
https://github.com/Dielee/volvo2mqtt.git
synced 2026-07-22 02:13:01 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9359896a68 | ||
|
|
2ebd1a30e3 | ||
|
|
0a13fd3bba | ||
|
|
fcf8f968ab | ||
|
|
07b144cc38 | ||
|
|
1eb635592d | ||
|
|
c60ef48591 | ||
|
|
0544257126 | ||
|
|
4770846312 | ||
|
|
4896f8d00a | ||
|
|
00f9e30002 | ||
|
|
372370786a | ||
|
|
0ece0ba04f | ||
|
|
13ac9a9c2c | ||
|
|
3cd82f83a6 | ||
|
|
05e4539062 | ||
|
|
7e5d09681e | ||
|
|
73ac466992 | ||
|
|
38f747ffc3 | ||
|
|
d01174e213 | ||
|
|
c87a3cb2d7 | ||
|
|
f6a26e5a41 | ||
|
|
42d07f1a4c | ||
|
|
71f03e505b | ||
|
|
2b6a2a4f4c | ||
|
|
38cca04473 | ||
|
|
fbb98481f4 | ||
|
|
dad5f9aaaa | ||
|
|
c404356ee0 | ||
|
|
0516c29563 | ||
|
|
9b3f9b4c0b | ||
|
|
65d0214664 |
@@ -11,7 +11,7 @@ name: Create and publish a Docker image
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [created]
|
||||
types: [created, edited]
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
|
||||
+2
-2
@@ -11,7 +11,7 @@ COPY requirements.txt .
|
||||
RUN pip install -r requirements.txt
|
||||
|
||||
# copy the content of the local src directory to the working directory
|
||||
COPY / .
|
||||
COPY /src .
|
||||
|
||||
# command to run on container start
|
||||
CMD [ "python", "-u", "./main.py" ]
|
||||
CMD [ "python", "-u", "./main.py" ]
|
||||
|
||||
@@ -3,23 +3,32 @@
|
||||
This component establishes a connection between the newer AAOS Volvo cars and Home Assistant via MQTT.<br>
|
||||
Maybe this component works also with other Volvo cars. Please try out the native Volvo [integration](https://www.home-assistant.io/integrations/volvooncall/) before using this component! If the native component doesn't work for your car, try this mqtt bridge.
|
||||
|
||||
## Confirmed working with
|
||||
- XC40 BEV (2023)
|
||||
- V60 T8 PHEV (2023)
|
||||
- C40 PHEV (2023)
|
||||
- XC90 T8 PHEV (2023)
|
||||
|
||||
Please let me know if your car works with this addon so I can expand the list!
|
||||
|
||||
## Setup
|
||||
|
||||
Just install this addon with the following command.
|
||||
Please note to fill in your settings inside the environment variables.
|
||||
|
||||
`docker run -e CONF_updateInterval=300 -e CONF_babelLocale='de' -e CONF_mqtt='@json {"broker": "", "username": "", "password": ""}' -e CONF_volvoData='@json {"username": "", "password": "", "vin": "", "vccapikey": ""}' -e TZ='Europe/Berlin' --name volvo2mqtt ghcr.io/dielee/volvo2mqtt:latest`
|
||||
`docker run --pull=always -e CONF_updateInterval=300 -e CONF_babelLocale='de' -e CONF_mqtt='@json {"broker": "", "username": "", "password": ""}' -e CONF_volvoData='@json {"username": "", "password": "", "vin": "", "vccapikey": ""}' -e TZ='Europe/Berlin' --name volvo2mqtt ghcr.io/dielee/volvo2mqtt:latest`
|
||||
|
||||
Here is what every option means:
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
| -------------------- | :-------: | :----------: | --------------------------------------------------------------- |
|
||||
| `updateInterval` | `int` | **required** | Updateintervall in seconds. |
|
||||
| `babelLocale` | `string` | **required** | Locale for date Format |
|
||||
| `mqtt` | `json` | **required** | Broker = Mqtt Broker IP / Username and Passwort are optional! |
|
||||
| `volvoData` | `json` | **required** | Username and password are required. Car vin can be a single vin or a list of multiple vins like `["vin1", "vin2"]`. If no vin is provided, <b>ALL</b> of your vehicles will be used. Vccapi key is your api key from [here](https://developer.volvocars.com/account/). |
|
||||
| `CONF_updateInterval` | `int` | **required** | Update intervall in seconds. |
|
||||
| `CONF_babelLocale` | `string` | **required** | Select your country from this [list](https://www.ibm.com/docs/en/radfws/9.7?topic=overview-locales-code-pages-supported). "Locale name" is the column you need! |
|
||||
| `CONF_mqtt` | `json` | **required** | Broker = Mqtt Broker IP / Username and Passwort are optional! |
|
||||
| `CONF_volvoData` | `json` | **required** | Username and password are REQUIRED. Car vin can be a single vin or a list of multiple vins like `["vin1", "vin2"]`. If no vin is provided, <b>ALL</b> of your vehicles will be used. Vccapi key is REQUIRED. Get your Vccapi key from [here](https://developer.volvocars.com/account/). |
|
||||
| `CONF_debug` | `string` | | Debug option (true/false) - optional! |
|
||||
| `TZ` | `string` | | Container timezone eg "Europe/Berlin" |
|
||||
|
||||
If you like my work:
|
||||
|
||||
<a href="https://www.buymeacoffee.com/dielee" target="_blank"><img src="https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png" alt="Buy Me A Coffee" style="height: 41px !important;width: 174px !important;box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;-webkit-box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;" ></a>
|
||||
[](https://ko-fi.com/U7U8MFXCF)
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"name": "volvoaaos2mqtt",
|
||||
"ports": {},
|
||||
"env": {"TZ": "Europe/Amsterdam"},
|
||||
"volumes": [
|
||||
"/home/dockerhost/volvoAAOS2mqtt/settings.json:/volvoAAOS2mqtt/settings.json"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
name: volvo2mqtt
|
||||
url: https://github.com/Dielee/volvo2mqtt
|
||||
maintainer: Linus Dietz (Dielee)
|
||||
@@ -1,5 +1,4 @@
|
||||
requests~=2.29.0
|
||||
dynaconf~=3.1.12
|
||||
paho-mqtt~=1.6.1
|
||||
pytz~=2023.3
|
||||
Babel~=2.12.1
|
||||
@@ -1,3 +1,5 @@
|
||||
VERSION = "v1.0.11"
|
||||
|
||||
OAUTH_URL = "https://volvoid.eu.volvocars.com/as/token.oauth2"
|
||||
VEHICLES_URL = "https://api.volvocars.com/connected-vehicle/v1/vehicles"
|
||||
VEHICLE_DETAILS_URL = "https://api.volvocars.com/connected-vehicle/v1/vehicles/{0}"
|
||||
@@ -10,7 +12,7 @@ CAR_UNLOCK_URL = "https://api.volvocars.com/connected-vehicle/v2/vehicles/{0}/co
|
||||
RECHARGE_STATUS_URL = "https://api.volvocars.com/energy/v1/vehicles/{0}/recharge-status"
|
||||
|
||||
charging_system_states = {"CHARGING_SYSTEM_CHARGING": "Charging", "CHARGING_SYSTEM_IDLE": "Idle",
|
||||
"CHARGING_SYSTEM_FAULT": "Fault", "CHARGING_SYSTEM_UNSPECIFIED": "UNSPECIFIED"}
|
||||
"CHARGING_SYSTEM_FAULT": "Fault", "CHARGING_SYSTEM_UNSPECIFIED": "Unspecified"}
|
||||
|
||||
supported_sensors = [
|
||||
{"name": "Battery Charge Level", "id": "battery_charge_level", "unit": "%", "icon": "car-battery", "url": RECHARGE_STATUS_URL},
|
||||
@@ -1,7 +1,9 @@
|
||||
from volvo import authorize
|
||||
from mqtt import update_loop, connect
|
||||
from const import VERSION
|
||||
|
||||
if __name__ == '__main__':
|
||||
print("Starting volvo2mqtt version " + VERSION)
|
||||
authorize()
|
||||
connect()
|
||||
update_loop()
|
||||
+12
-10
@@ -6,13 +6,13 @@ import threading
|
||||
from datetime import datetime
|
||||
from babel.dates import format_datetime
|
||||
from config import settings
|
||||
from const import VEHICLE_DETAILS_URL,CLIMATE_START_URL, CLIMATE_STOP_URL, CAR_LOCK_URL, \
|
||||
from const import VEHICLE_DETAILS_URL, CLIMATE_START_URL, CLIMATE_STOP_URL, CAR_LOCK_URL, \
|
||||
CAR_UNLOCK_URL, supported_sensors, supported_buttons, supported_switches, supported_locks
|
||||
|
||||
|
||||
mqtt_client: mqtt.Client
|
||||
subscribed_topics = []
|
||||
assumed_climate_state = "OFF"
|
||||
assumed_climate_state = {}
|
||||
last_data_update = None
|
||||
|
||||
|
||||
@@ -42,27 +42,28 @@ def on_disconnect(client, userdata, rc):
|
||||
|
||||
def on_message(client, userdata, msg):
|
||||
if msg.topic in subscribed_topics:
|
||||
vin = msg.topic.split('/')[2].split('_')[0]
|
||||
payload = msg.payload.decode("UTF-8")
|
||||
if "climate_status" in msg.topic:
|
||||
global assumed_climate_state
|
||||
if payload == "ON":
|
||||
api_thread = threading.Thread(target=volvo.api_call, args=(CLIMATE_START_URL, "POST", ))
|
||||
api_thread = threading.Thread(target=volvo.api_call, args=(CLIMATE_START_URL, "POST", vin))
|
||||
api_thread.start()
|
||||
assumed_climate_state = "ON"
|
||||
assumed_climate_state[vin] = "ON"
|
||||
# Starting timer to disable climate after 30 mins
|
||||
threading.Timer(30 * 60, volvo.disable_climate).start()
|
||||
threading.Timer(30 * 60, volvo.disable_climate, (vin, )).start()
|
||||
update_car_data()
|
||||
elif payload == "OFF":
|
||||
api_thread = threading.Thread(target=volvo.api_call, args=(CLIMATE_STOP_URL, "POST", ))
|
||||
api_thread = threading.Thread(target=volvo.api_call, args=(CLIMATE_STOP_URL, "POST", vin))
|
||||
api_thread.start()
|
||||
assumed_climate_state = "OFF"
|
||||
assumed_climate_state[vin] = "OFF"
|
||||
update_car_data()
|
||||
elif "lock_status" in msg.topic:
|
||||
if payload == "LOCK":
|
||||
volvo.api_call(CAR_LOCK_URL, "POST")
|
||||
volvo.api_call(CAR_LOCK_URL, "POST", vin)
|
||||
update_car_data()
|
||||
elif payload == "UNLOCK":
|
||||
volvo.api_call(CAR_UNLOCK_URL, "POST")
|
||||
volvo.api_call(CAR_UNLOCK_URL, "POST", vin)
|
||||
update_car_data()
|
||||
elif "update_data" in msg.topic:
|
||||
if payload == "PRESS":
|
||||
@@ -74,6 +75,7 @@ def update_loop():
|
||||
while True:
|
||||
print("Sending mqtt update...")
|
||||
update_car_data()
|
||||
print("Mqtt update done. Next run in " + str(settings["updateInterval"]) + " seconds.")
|
||||
time.sleep(settings["updateInterval"])
|
||||
|
||||
|
||||
@@ -90,7 +92,7 @@ def update_car_data():
|
||||
|
||||
for switch in supported_switches:
|
||||
if switch["id"] == "climate_status":
|
||||
state = assumed_climate_state
|
||||
state = assumed_climate_state[vin]
|
||||
else:
|
||||
state = "OFF"
|
||||
|
||||
+26
-13
@@ -17,7 +17,7 @@ token_expires_at: datetime
|
||||
refresh_token = None
|
||||
vins = []
|
||||
recharge_response = {}
|
||||
recharge_last_update = None
|
||||
recharge_last_update = {}
|
||||
|
||||
|
||||
def authorize():
|
||||
@@ -95,11 +95,18 @@ def get_vehicles():
|
||||
if len(vins) == 0:
|
||||
raise Exception("No vehicle found, exiting application!")
|
||||
else:
|
||||
initialize_climate(vins)
|
||||
print("Vin: " + str(vins) + " found!")
|
||||
|
||||
|
||||
def disable_climate():
|
||||
mqtt.assumed_climate_state = "OFF"
|
||||
def initialize_climate(vins):
|
||||
for vin in vins:
|
||||
mqtt.assumed_climate_state[vin] = "OFF"
|
||||
|
||||
|
||||
def disable_climate(vin):
|
||||
print("Turning climate off by timer!")
|
||||
mqtt.assumed_climate_state[vin] = "OFF"
|
||||
mqtt.update_car_data()
|
||||
|
||||
|
||||
@@ -111,22 +118,22 @@ def api_call(url, method, vin, sensor_id=None):
|
||||
global recharge_response, recharge_last_update
|
||||
if url == RECHARGE_STATUS_URL:
|
||||
# Minimize API calls for recharge API
|
||||
if not bool(recharge_response):
|
||||
# No API Data cached, get fresh data from API
|
||||
if not vin in recharge_response:
|
||||
# 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_response = response
|
||||
recharge_last_update = datetime.now()
|
||||
recharge_response[vin] = response
|
||||
recharge_last_update[vin] = datetime.now()
|
||||
else:
|
||||
if (datetime.now() - recharge_last_update).total_seconds() >= settings["updateInterval"]:
|
||||
# Old Data in Cache, updateing
|
||||
if (datetime.now() - recharge_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_response = response
|
||||
recharge_last_update = datetime.now()
|
||||
recharge_response[vin] = response
|
||||
recharge_last_update[vin] = datetime.now()
|
||||
else:
|
||||
# Data is up do date, returning cached data
|
||||
response = recharge_response
|
||||
response = recharge_response[vin]
|
||||
elif method == "GET":
|
||||
print("Starting " + method + " call against " + url)
|
||||
response = session.get(url.format(vin), timeout=15)
|
||||
@@ -139,6 +146,9 @@ def api_call(url, method, vin, sensor_id=None):
|
||||
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
if "debug" in settings:
|
||||
if settings["debug"]:
|
||||
print(response.text)
|
||||
else:
|
||||
if url == CLIMATE_START_URL and response.status_code == 503:
|
||||
print("Car in use, cannot start pre climatization")
|
||||
@@ -147,7 +157,10 @@ def api_call(url, method, vin, sensor_id=None):
|
||||
else:
|
||||
print("API Call failed. Status Code: " + str(response.status_code) + ". Error: " + response.text)
|
||||
return ""
|
||||
return parse_api_data(url, data, sensor_id)
|
||||
|
||||
|
||||
def parse_api_data(url, data, sensor_id=None):
|
||||
if url == VEHICLE_DETAILS_URL:
|
||||
return data["data"]
|
||||
elif sensor_id == "battery_charge_level":
|
||||
@@ -161,7 +174,7 @@ def api_call(url, method, vin, sensor_id=None):
|
||||
else:
|
||||
return ""
|
||||
elif sensor_id == "charging_system_status":
|
||||
if "charging_system_status" in data["data"]:
|
||||
if "chargingSystemStatus" in data["data"]:
|
||||
return charging_system_states[data["data"]["chargingSystemStatus"]["value"]]
|
||||
else:
|
||||
return ""
|
||||
Reference in New Issue
Block a user