mirror of
https://github.com/Dielee/volvo2mqtt.git
synced 2026-07-22 02:13:01 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
39f457d01a | ||
|
|
21cb2999f7 | ||
|
|
298e8b503a | ||
|
|
2d50129ec4 | ||
|
|
5ef1aa528b | ||
|
|
3440dadeab | ||
|
|
1dc38e6925 | ||
|
|
81b0bb9b85 | ||
|
|
97fb95799a | ||
|
|
ab73f9afb2 | ||
|
|
285f9dc79b | ||
|
|
6dad197ed6 | ||
|
|
1ed6035a54 | ||
|
|
666eda99d0 | ||
|
|
02e64e5555 | ||
|
|
c298b8972a | ||
|
|
2c0169466c | ||
|
|
8565742752 | ||
|
|
e690f60433 | ||
|
|
9eba31804d | ||
|
|
5139d84602 | ||
|
|
2f3ca63b60 |
@@ -11,40 +11,51 @@ name: Create and publish a Docker image
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [created]
|
||||
|
||||
types: [created, edited]
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: ${{ github.repository }}
|
||||
|
||||
jobs:
|
||||
build-and-push-image:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
uses: actions/checkout@v3
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
- name: Log in to the Container registry
|
||||
uses: docker/login-action@65b78e6e13532edd9afa3aa52ac7964289d1a9c1
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Extract metadata (tags, labels) for Docker
|
||||
id: meta
|
||||
uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@f2a1d5e99d037542a71f64918e516c093c6f3fc4
|
||||
tags: |
|
||||
type=schedule
|
||||
type=ref,event=branch
|
||||
type=ref,event=pr
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=semver,pattern={{major}}
|
||||
type=sha
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
platforms: linux/amd64,linux/arm/v7,linux/arm/v6,linux/arm/v8
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
|
||||
+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" ]
|
||||
|
||||
@@ -1,2 +1,33 @@
|
||||
# volvo2mqtt
|
||||
Home Assistant addon for connecting AAOS Volvos
|
||||
# Volvo2Mqtt
|
||||
|
||||
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
|
||||
- Volvo XC40 BEV (2023)
|
||||
- Volvo V60 T8 PHEV (2023)
|
||||
- Volvo C40 (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 --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** | Update intervall in seconds. |
|
||||
| `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! |
|
||||
| `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 REQUIRED. Get your Vccapi key from [here](https://developer.volvocars.com/account/). |
|
||||
| `TZ` | `string` | | Container timezone eg "Europe/Berlin" |
|
||||
| `debug` | `string` | | Debug option (True/False) - optional! |
|
||||
|
||||
If you like my work:
|
||||
|
||||
[](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"
|
||||
]
|
||||
}
|
||||
@@ -1,211 +0,0 @@
|
||||
import time
|
||||
import paho.mqtt.client as mqtt
|
||||
import json
|
||||
import volvo
|
||||
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, \
|
||||
CAR_UNLOCK_URL, supported_sensors, supported_buttons, supported_switches, supported_locks
|
||||
|
||||
|
||||
mqtt_client: mqtt.Client
|
||||
subscribed_topics = []
|
||||
assumed_climate_state = "OFF"
|
||||
last_data_update = None
|
||||
|
||||
|
||||
def connect():
|
||||
client = mqtt.Client("volvoAAOS2mqtt")
|
||||
if settings["mqtt"]["username"] and settings["mqtt"]["password"]:
|
||||
client.username_pw_set(settings["mqtt"]["username"], settings["mqtt"]["password"])
|
||||
client.connect(settings["mqtt"]["broker"])
|
||||
client.loop_start()
|
||||
client.on_message = on_message
|
||||
client.on_disconnect = on_disconnect
|
||||
client.on_connect = on_connect
|
||||
|
||||
global mqtt_client
|
||||
mqtt_client = client
|
||||
|
||||
|
||||
def on_connect(client, userdata, flags, rc):
|
||||
if len(subscribed_topics) > 0:
|
||||
for topic in subscribed_topics:
|
||||
mqtt_client.subscribe(topic)
|
||||
|
||||
|
||||
def on_disconnect(client, userdata, rc):
|
||||
print("MQTT disconnected, reconnecting automatically")
|
||||
|
||||
|
||||
def on_message(client, userdata, msg):
|
||||
if msg.topic in subscribed_topics:
|
||||
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.start()
|
||||
assumed_climate_state = "ON"
|
||||
# Starting timer to disable climate after 30 mins
|
||||
threading.Timer(30 * 60, volvo.disable_climate).start()
|
||||
update_car_data()
|
||||
elif payload == "OFF":
|
||||
api_thread = threading.Thread(target=volvo.api_call, args=(CLIMATE_STOP_URL, "POST", ))
|
||||
api_thread.start()
|
||||
assumed_climate_state = "OFF"
|
||||
update_car_data()
|
||||
elif "lock_status" in msg.topic:
|
||||
if payload == "LOCK":
|
||||
volvo.api_call(CAR_LOCK_URL, "POST")
|
||||
update_car_data()
|
||||
elif payload == "UNLOCK":
|
||||
volvo.api_call(CAR_UNLOCK_URL, "POST")
|
||||
update_car_data()
|
||||
elif "update_data" in msg.topic:
|
||||
if payload == "PRESS":
|
||||
update_car_data()
|
||||
|
||||
|
||||
def update_loop():
|
||||
create_ha_devices()
|
||||
while True:
|
||||
print("Sending mqtt update...")
|
||||
update_car_data()
|
||||
time.sleep(settings["updateInterval"])
|
||||
|
||||
|
||||
def update_car_data():
|
||||
global last_data_update
|
||||
last_data_update = format_datetime(datetime.now(), format="medium", locale=settings["babelLocale"])
|
||||
for lock in supported_locks:
|
||||
state = volvo.api_call(lock["url"], "GET", lock["id"])
|
||||
mqtt_client.publish(
|
||||
f"homeassistant/lock/{volvo.vin}_{lock['id']}/state",
|
||||
state
|
||||
)
|
||||
|
||||
for switch in supported_switches:
|
||||
if switch["id"] == "climate_status":
|
||||
state = assumed_climate_state
|
||||
else:
|
||||
state = "OFF"
|
||||
|
||||
mqtt_client.publish(
|
||||
f"homeassistant/switch/{volvo.vin}_{switch['id']}/state",
|
||||
state
|
||||
)
|
||||
|
||||
for sensor in supported_sensors:
|
||||
if sensor["id"] == "last_data_update":
|
||||
state = last_data_update
|
||||
else:
|
||||
state = volvo.api_call(sensor["url"], "GET", sensor["id"])
|
||||
mqtt_client.publish(
|
||||
f"homeassistant/sensor/{volvo.vin}_{sensor['id']}/state",
|
||||
state
|
||||
)
|
||||
|
||||
|
||||
def create_ha_devices():
|
||||
car_details = volvo.api_call(VEHICLE_DETAILS_URL, "GET")
|
||||
|
||||
for button in supported_buttons:
|
||||
command_topic = f"homeassistant/button/{volvo.vin}_{button['id']}/command"
|
||||
config = {
|
||||
"name": button['name'],
|
||||
"object_id": button['id'],
|
||||
"schema": "state",
|
||||
"icon": f"mdi:{button['icon']}",
|
||||
"state_topic": f"homeassistant/button/{volvo.vin}_{button['id']}/state",
|
||||
"command_topic": command_topic,
|
||||
"device": {
|
||||
"identifiers": ["volvoAAOS2mqtt"],
|
||||
"manufacturer": "Volvo",
|
||||
"model": car_details['descriptions']['model'],
|
||||
"name": f"{car_details['descriptions']['model']} ({car_details['modelYear']}) - {volvo.vin}",
|
||||
},
|
||||
"unique_id": f"volvoAAOS2mqtt_{volvo.vin}_{button['id']}",
|
||||
}
|
||||
mqtt_client.publish(
|
||||
f"homeassistant/button/volvoAAOS2mqtt/{volvo.vin}_{button['id']}/config",
|
||||
json.dumps(config),
|
||||
)
|
||||
subscribed_topics.append(command_topic)
|
||||
mqtt_client.subscribe(command_topic)
|
||||
|
||||
for lock in supported_locks:
|
||||
command_topic = f"homeassistant/lock/{volvo.vin}_{lock['id']}/command"
|
||||
config = {
|
||||
"name": lock['name'],
|
||||
"object_id": lock['id'],
|
||||
"schema": "state",
|
||||
"icon": f"mdi:{lock['icon']}",
|
||||
"state_topic": f"homeassistant/lock/{volvo.vin}_{lock['id']}/state",
|
||||
"command_topic": command_topic,
|
||||
"optimistic": False,
|
||||
"device": {
|
||||
"identifiers": ["volvoAAOS2mqtt"],
|
||||
"manufacturer": "Volvo",
|
||||
"model": car_details['descriptions']['model'],
|
||||
"name": f"{car_details['descriptions']['model']} ({car_details['modelYear']}) - {volvo.vin}",
|
||||
},
|
||||
"unique_id": f"volvoAAOS2mqtt_{volvo.vin}_{lock['id']}",
|
||||
}
|
||||
mqtt_client.publish(
|
||||
f"homeassistant/lock/volvoAAOS2mqtt/{volvo.vin}_{lock['id']}/config",
|
||||
json.dumps(config),
|
||||
)
|
||||
subscribed_topics.append(command_topic)
|
||||
mqtt_client.subscribe(command_topic)
|
||||
|
||||
for switch in supported_switches:
|
||||
command_topic = f"homeassistant/switch/{volvo.vin}_{switch['id']}/command"
|
||||
config = {
|
||||
"name": switch['name'],
|
||||
"object_id": switch['id'],
|
||||
"schema": "state",
|
||||
"icon": f"mdi:{switch['icon']}",
|
||||
"state_topic": f"homeassistant/switch/{volvo.vin}_{switch['id']}/state",
|
||||
"command_topic": command_topic,
|
||||
"optimistic": False,
|
||||
"device": {
|
||||
"identifiers": ["volvoAAOS2mqtt"],
|
||||
"manufacturer": "Volvo",
|
||||
"model": car_details['descriptions']['model'],
|
||||
"name": f"{car_details['descriptions']['model']} ({car_details['modelYear']}) - {volvo.vin}",
|
||||
},
|
||||
"unique_id": f"volvoAAOS2mqtt_{volvo.vin}_{switch['id']}",
|
||||
}
|
||||
mqtt_client.publish(
|
||||
f"homeassistant/switch/volvoAAOS2mqtt/{volvo.vin}_{switch['id']}/config",
|
||||
json.dumps(config),
|
||||
)
|
||||
subscribed_topics.append(command_topic)
|
||||
mqtt_client.subscribe(command_topic)
|
||||
|
||||
for sensor in supported_sensors:
|
||||
config = {
|
||||
"name": sensor['name'],
|
||||
"object_id": sensor['id'],
|
||||
"schema": "state",
|
||||
"icon": f"mdi:{sensor['icon']}",
|
||||
"state_topic": f"homeassistant/sensor/{volvo.vin}_{sensor['id']}/state",
|
||||
"device": {
|
||||
"identifiers": ["volvoAAOS2mqtt"],
|
||||
"manufacturer": "Volvo",
|
||||
"model": car_details['descriptions']['model'],
|
||||
"name": f"{car_details['descriptions']['model']} ({car_details['modelYear']}) - {volvo.vin}",
|
||||
},
|
||||
"unique_id": f"volvoAAOS2mqtt_{volvo.vin}_{sensor['id']}",
|
||||
}
|
||||
if "unit" in sensor:
|
||||
config["unit_of_measurement"] = sensor["unit"]
|
||||
|
||||
mqtt_client.publish(
|
||||
f"homeassistant/sensor/volvoAAOS2mqtt/{volvo.vin}_{sensor['id']}/config",
|
||||
json.dumps(config),
|
||||
)
|
||||
time.sleep(2)
|
||||
@@ -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.9"
|
||||
|
||||
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}"
|
||||
@@ -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()
|
||||
+215
@@ -0,0 +1,215 @@
|
||||
import time
|
||||
import paho.mqtt.client as mqtt
|
||||
import json
|
||||
import volvo
|
||||
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, \
|
||||
CAR_UNLOCK_URL, supported_sensors, supported_buttons, supported_switches, supported_locks
|
||||
|
||||
|
||||
mqtt_client: mqtt.Client
|
||||
subscribed_topics = []
|
||||
assumed_climate_state = "OFF"
|
||||
last_data_update = None
|
||||
|
||||
|
||||
def connect():
|
||||
client = mqtt.Client("volvoAAOS2mqtt")
|
||||
if settings["mqtt"]["username"] and settings["mqtt"]["password"]:
|
||||
client.username_pw_set(settings["mqtt"]["username"], settings["mqtt"]["password"])
|
||||
client.connect(settings["mqtt"]["broker"])
|
||||
client.loop_start()
|
||||
client.on_message = on_message
|
||||
client.on_disconnect = on_disconnect
|
||||
client.on_connect = on_connect
|
||||
|
||||
global mqtt_client
|
||||
mqtt_client = client
|
||||
|
||||
|
||||
def on_connect(client, userdata, flags, rc):
|
||||
if len(subscribed_topics) > 0:
|
||||
for topic in subscribed_topics:
|
||||
mqtt_client.subscribe(topic)
|
||||
|
||||
|
||||
def on_disconnect(client, userdata, rc):
|
||||
print("MQTT disconnected, reconnecting automatically")
|
||||
|
||||
|
||||
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", vin))
|
||||
api_thread.start()
|
||||
assumed_climate_state = "ON"
|
||||
# Starting timer to disable climate after 30 mins
|
||||
threading.Timer(30 * 60, volvo.disable_climate).start()
|
||||
update_car_data()
|
||||
elif payload == "OFF":
|
||||
api_thread = threading.Thread(target=volvo.api_call, args=(CLIMATE_STOP_URL, "POST", vin))
|
||||
api_thread.start()
|
||||
assumed_climate_state = "OFF"
|
||||
update_car_data()
|
||||
elif "lock_status" in msg.topic:
|
||||
if payload == "LOCK":
|
||||
volvo.api_call(CAR_LOCK_URL, "POST", vin)
|
||||
update_car_data()
|
||||
elif payload == "UNLOCK":
|
||||
volvo.api_call(CAR_UNLOCK_URL, "POST", vin)
|
||||
update_car_data()
|
||||
elif "update_data" in msg.topic:
|
||||
if payload == "PRESS":
|
||||
update_car_data()
|
||||
|
||||
|
||||
def update_loop():
|
||||
create_ha_devices()
|
||||
while True:
|
||||
print("Sending mqtt update...")
|
||||
update_car_data()
|
||||
print("Mqtt update done. Next run in " + str(settings["updateInterval"]) + " seconds.")
|
||||
time.sleep(settings["updateInterval"])
|
||||
|
||||
|
||||
def update_car_data():
|
||||
global last_data_update
|
||||
last_data_update = format_datetime(datetime.now(), format="medium", locale=settings["babelLocale"])
|
||||
for vin in volvo.vins:
|
||||
for lock in supported_locks:
|
||||
state = volvo.api_call(lock["url"], "GET", vin, lock["id"])
|
||||
mqtt_client.publish(
|
||||
f"homeassistant/lock/{vin}_{lock['id']}/state",
|
||||
state
|
||||
)
|
||||
|
||||
for switch in supported_switches:
|
||||
if switch["id"] == "climate_status":
|
||||
state = assumed_climate_state
|
||||
else:
|
||||
state = "OFF"
|
||||
|
||||
mqtt_client.publish(
|
||||
f"homeassistant/switch/{vin}_{switch['id']}/state",
|
||||
state
|
||||
)
|
||||
|
||||
for sensor in supported_sensors:
|
||||
if sensor["id"] == "last_data_update":
|
||||
state = last_data_update
|
||||
else:
|
||||
state = volvo.api_call(sensor["url"], "GET", vin, sensor["id"])
|
||||
mqtt_client.publish(
|
||||
f"homeassistant/sensor/{vin}_{sensor['id']}/state",
|
||||
state
|
||||
)
|
||||
|
||||
|
||||
def create_ha_devices():
|
||||
for vin in volvo.vins:
|
||||
car_details = volvo.api_call(VEHICLE_DETAILS_URL, "GET", vin)
|
||||
|
||||
for button in supported_buttons:
|
||||
command_topic = f"homeassistant/button/{vin}_{button['id']}/command"
|
||||
config = {
|
||||
"name": button['name'],
|
||||
"object_id": button['id'],
|
||||
"schema": "state",
|
||||
"icon": f"mdi:{button['icon']}",
|
||||
"state_topic": f"homeassistant/button/{vin}_{button['id']}/state",
|
||||
"command_topic": command_topic,
|
||||
"device": {
|
||||
"identifiers": [f"volvoAAOS2mqtt_{vin}"],
|
||||
"manufacturer": "Volvo",
|
||||
"model": car_details['descriptions']['model'],
|
||||
"name": f"{car_details['descriptions']['model']} ({car_details['modelYear']}) - {vin}",
|
||||
},
|
||||
"unique_id": f"volvoAAOS2mqtt_{vin}_{button['id']}",
|
||||
}
|
||||
mqtt_client.publish(
|
||||
f"homeassistant/button/volvoAAOS2mqtt/{vin}_{button['id']}/config",
|
||||
json.dumps(config),
|
||||
)
|
||||
subscribed_topics.append(command_topic)
|
||||
mqtt_client.subscribe(command_topic)
|
||||
|
||||
for lock in supported_locks:
|
||||
command_topic = f"homeassistant/lock/{vin}_{lock['id']}/command"
|
||||
config = {
|
||||
"name": lock['name'],
|
||||
"object_id": lock['id'],
|
||||
"schema": "state",
|
||||
"icon": f"mdi:{lock['icon']}",
|
||||
"state_topic": f"homeassistant/lock/{vin}_{lock['id']}/state",
|
||||
"command_topic": command_topic,
|
||||
"optimistic": False,
|
||||
"device": {
|
||||
"identifiers": [f"volvoAAOS2mqtt_{vin}"],
|
||||
"manufacturer": "Volvo",
|
||||
"model": car_details['descriptions']['model'],
|
||||
"name": f"{car_details['descriptions']['model']} ({car_details['modelYear']}) - {vin}",
|
||||
},
|
||||
"unique_id": f"volvoAAOS2mqtt_{vin}_{lock['id']}",
|
||||
}
|
||||
mqtt_client.publish(
|
||||
f"homeassistant/lock/volvoAAOS2mqtt/{vin}_{lock['id']}/config",
|
||||
json.dumps(config),
|
||||
)
|
||||
subscribed_topics.append(command_topic)
|
||||
mqtt_client.subscribe(command_topic)
|
||||
|
||||
for switch in supported_switches:
|
||||
command_topic = f"homeassistant/switch/{vin}_{switch['id']}/command"
|
||||
config = {
|
||||
"name": switch['name'],
|
||||
"object_id": switch['id'],
|
||||
"schema": "state",
|
||||
"icon": f"mdi:{switch['icon']}",
|
||||
"state_topic": f"homeassistant/switch/{vin}_{switch['id']}/state",
|
||||
"command_topic": command_topic,
|
||||
"optimistic": False,
|
||||
"device": {
|
||||
"identifiers": [f"volvoAAOS2mqtt_{vin}"],
|
||||
"manufacturer": "Volvo",
|
||||
"model": car_details['descriptions']['model'],
|
||||
"name": f"{car_details['descriptions']['model']} ({car_details['modelYear']}) - {vin}",
|
||||
},
|
||||
"unique_id": f"volvoAAOS2mqtt_{vin}_{switch['id']}",
|
||||
}
|
||||
mqtt_client.publish(
|
||||
f"homeassistant/switch/volvoAAOS2mqtt/{vin}_{switch['id']}/config",
|
||||
json.dumps(config),
|
||||
)
|
||||
subscribed_topics.append(command_topic)
|
||||
mqtt_client.subscribe(command_topic)
|
||||
|
||||
for sensor in supported_sensors:
|
||||
config = {
|
||||
"name": sensor['name'],
|
||||
"object_id": sensor['id'],
|
||||
"schema": "state",
|
||||
"icon": f"mdi:{sensor['icon']}",
|
||||
"state_topic": f"homeassistant/sensor/{vin}_{sensor['id']}/state",
|
||||
"device": {
|
||||
"identifiers": [f"volvoAAOS2mqtt_{vin}"],
|
||||
"manufacturer": "Volvo",
|
||||
"model": car_details['descriptions']['model'],
|
||||
"name": f"{car_details['descriptions']['model']} ({car_details['modelYear']}) - {vin}",
|
||||
},
|
||||
"unique_id": f"volvoAAOS2mqtt_{vin}_{sensor['id']}",
|
||||
}
|
||||
if "unit" in sensor:
|
||||
config["unit_of_measurement"] = sensor["unit"]
|
||||
|
||||
mqtt_client.publish(
|
||||
f"homeassistant/sensor/volvoAAOS2mqtt/{vin}_{sensor['id']}/config",
|
||||
json.dumps(config),
|
||||
)
|
||||
time.sleep(2)
|
||||
@@ -9,6 +9,7 @@
|
||||
"volvoData": {
|
||||
"username": "",
|
||||
"password": "",
|
||||
"vin": ""
|
||||
"vin": "",
|
||||
"vccapikey": ""
|
||||
}
|
||||
}
|
||||
+66
-39
@@ -8,16 +8,16 @@ from const import charging_system_states, CLIMATE_START_URL, \
|
||||
|
||||
session = requests.Session()
|
||||
session.headers = {
|
||||
"vcc-api-key": "f0d0419bf51d420c8efb21cf9a127227",
|
||||
"vcc-api-key": settings["volvoData"]["vccapikey"],
|
||||
"content-type": "application/json",
|
||||
"accept": "*/*"
|
||||
}
|
||||
|
||||
token_expires_at = None
|
||||
token_expires_at: datetime
|
||||
refresh_token = None
|
||||
vin = ""
|
||||
vins = []
|
||||
recharge_response = {}
|
||||
recharge_last_update = None
|
||||
recharge_last_update = {}
|
||||
|
||||
|
||||
def authorize():
|
||||
@@ -42,7 +42,7 @@ def authorize():
|
||||
token_expires_at = datetime.now() + timedelta(seconds=(data["expires_in"] - 30))
|
||||
refresh_token = data["refresh_token"]
|
||||
|
||||
get_vehicle()
|
||||
get_vehicles()
|
||||
else:
|
||||
message = auth.json()
|
||||
raise Exception(message["error_description"])
|
||||
@@ -71,25 +71,31 @@ def refresh_auth():
|
||||
refresh_token = data["refresh_token"]
|
||||
|
||||
|
||||
def get_vehicle():
|
||||
global vin
|
||||
def get_vehicles():
|
||||
global vins
|
||||
if not settings.volvoData["vin"]:
|
||||
vehicles = session.get(VEHICLES_URL)
|
||||
if vehicles.status_code == 200:
|
||||
data = vehicles.json()
|
||||
if len(data["data"]) > 0:
|
||||
vin = data["data"][0]["vin"]
|
||||
for vehicle in data["data"]:
|
||||
vins.append(vehicle["vin"])
|
||||
else:
|
||||
print("No vehicle in account " + settings.volvoData["username"] + " found.")
|
||||
raise Exception("No vehicle in account " + settings.volvoData["username"] + " found.")
|
||||
else:
|
||||
print("Error getting Vehicles " + str(vehicles.status_code))
|
||||
raise Exception("Error getting vehicles: " + str(vehicles.status_code))
|
||||
else:
|
||||
vin = settings.volvoData["vin"]
|
||||
if isinstance(settings.volvoData["vin"], list):
|
||||
# If setting is a list, copy
|
||||
vins = settings.volvoData["vin"]
|
||||
else:
|
||||
# If setting is a string, append to list
|
||||
vins.append(settings.volvoData["vin"])
|
||||
|
||||
if not vin:
|
||||
if len(vins) == 0:
|
||||
raise Exception("No vehicle found, exiting application!")
|
||||
else:
|
||||
print("Vin: " + vin + " found!")
|
||||
print("Vin: " + str(vins) + " found!")
|
||||
|
||||
|
||||
def disable_climate():
|
||||
@@ -97,30 +103,30 @@ def disable_climate():
|
||||
mqtt.update_car_data()
|
||||
|
||||
|
||||
def api_call(url, method, sensor_id=None):
|
||||
def api_call(url, method, vin, sensor_id=None):
|
||||
global token_expires_at
|
||||
if datetime.now() >= token_expires_at:
|
||||
refresh_auth()
|
||||
|
||||
global vin, recharge_response, recharge_last_update
|
||||
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)
|
||||
@@ -133,6 +139,9 @@ def api_call(url, method, 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")
|
||||
@@ -141,27 +150,45 @@ def api_call(url, method, 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":
|
||||
return data["data"]["batteryChargeLevel"]["value"]
|
||||
elif sensor_id == "electric_range":
|
||||
return data["data"]["electricRange"]["value"]
|
||||
elif sensor_id == "charging_system_status":
|
||||
return charging_system_states[data["data"]["chargingSystemStatus"]["value"]]
|
||||
elif sensor_id == "estimated_charging_time":
|
||||
charging_system_state = charging_system_states[data["data"]["chargingSystemStatus"]["value"]]
|
||||
if charging_system_state == "Charging":
|
||||
return data["data"]["estimatedChargingTime"]["value"]
|
||||
if "batteryChargeLevel" in data["data"]:
|
||||
return data["data"]["batteryChargeLevel"]["value"]
|
||||
else:
|
||||
return 0
|
||||
return ""
|
||||
elif sensor_id == "electric_range":
|
||||
if "electricRange" in data["data"]:
|
||||
return data["data"]["electricRange"]["value"]
|
||||
else:
|
||||
return ""
|
||||
elif sensor_id == "charging_system_status":
|
||||
if "charging_system_status" in data["data"]:
|
||||
return charging_system_states[data["data"]["chargingSystemStatus"]["value"]]
|
||||
else:
|
||||
return ""
|
||||
elif sensor_id == "estimated_charging_time":
|
||||
if "chargingSystemStatus" in data["data"]:
|
||||
charging_system_state = charging_system_states[data["data"]["chargingSystemStatus"]["value"]]
|
||||
if charging_system_state == "Charging":
|
||||
return data["data"]["estimatedChargingTime"]["value"]
|
||||
else:
|
||||
return 0
|
||||
else:
|
||||
return ""
|
||||
elif sensor_id == "estimated_charging_finish_time":
|
||||
charging_system_state = charging_system_states[data["data"]["chargingSystemStatus"]["value"]]
|
||||
if charging_system_state == "Charging":
|
||||
charging_time = int(data["data"]["estimatedChargingTime"]["value"])
|
||||
charging_finished = datetime.now() + timedelta(minutes=charging_time)
|
||||
return format_datetime(charging_finished, format="medium", locale=settings["babelLocale"])
|
||||
if "chargingSystemStatus" in data["data"]:
|
||||
charging_system_state = charging_system_states[data["data"]["chargingSystemStatus"]["value"]]
|
||||
if charging_system_state == "Charging":
|
||||
charging_time = int(data["data"]["estimatedChargingTime"]["value"])
|
||||
charging_finished = datetime.now() + timedelta(minutes=charging_time)
|
||||
return format_datetime(charging_finished, format="medium", locale=settings["babelLocale"])
|
||||
else:
|
||||
return None
|
||||
else:
|
||||
return None
|
||||
elif sensor_id == "lock_status":
|
||||
Reference in New Issue
Block a user