Compare commits

...
32 Commits
Author SHA1 Message Date
Linus DietzandGitHub 2f0e2352a6 Add HA Add-On 2023-06-21 13:46:04 +02:00
Linus Dietz 43c16e7c65 Add HomeAssistant add-on 2023-06-21 12:59:24 +02:00
Linus DietzandGitHub 407989952e Update README.md 2023-06-21 10:40:03 +02:00
Linus DietzandGitHub d358241801 Merge pull request #15 from elyara/patch-1
Update Energy availability in README.md
2023-06-21 10:39:18 +02:00
Elya SheininandGitHub 3148aa82c6 Update Energy availability in README.md 2023-06-20 23:21:41 -07:00
Linus DietzandGitHub 281533e2ec Add supported features to readme 2023-06-20 15:17:48 +02:00
Linus DietzandGitHub 9359896a68 Update README.md 2023-06-20 15:08:04 +02:00
Linus DietzandGitHub 2ebd1a30e3 Update README.md 2023-06-20 15:03:46 +02:00
Linus DietzandGitHub 0a13fd3bba Update README.md 2023-06-20 15:03:29 +02:00
Linus Dietz fcf8f968ab Fix #12 - Climate for multiple cars 2023-06-20 14:56:26 +02:00
Linus DietzandGitHub 07b144cc38 Update README.md 2023-06-20 13:36:51 +02:00
Linus Dietz 1eb635592d Fix charging system status 2023-06-20 13:31:18 +02:00
Linus DietzandGitHub c60ef48591 Update README.md 2023-06-20 13:16:18 +02:00
Linus Dietz 0544257126 Bump version 2023-06-20 13:14:11 +02:00
Linus DietzandGitHub 4770846312 Update Dockerfile 2023-06-20 13:06:17 +02:00
Linus DietzandGitHub 4896f8d00a Merge pull request #11 from hapu2000/patch-1
Update README.md
2023-06-20 13:02:36 +02:00
Linus DietzandGitHub 00f9e30002 Update README.md 2023-06-20 13:00:45 +02:00
Linus Dietz 372370786a Add debug option 2023-06-20 12:58:08 +02:00
Ronny FischerandGitHub 0ece0ba04f Update README.md
tested on Volvo C40 (2023)
2023-06-20 12:53:27 +02:00
Linus DietzandGitHub 13ac9a9c2c Optimize locale README 2023-06-20 12:48:55 +02:00
Linus DietzandGitHub 3cd82f83a6 Add V60 T8 to working cars 2023-06-20 12:44:52 +02:00
Linus Dietz 05e4539062 Fix dockerfile 2023-06-20 12:27:28 +02:00
Linus Dietz 7e5d09681e cleanup repo 2023-06-20 12:18:46 +02:00
Linus DietzandGitHub 73ac466992 Move to Ko-Fi 2023-06-20 11:44:38 +02:00
Linus DietzandGitHub 38f747ffc3 Create repository.yaml 2023-06-20 11:23:20 +02:00
Linus Dietz d01174e213 Fix climate and lock commands for multiple cars 2023-06-20 11:20:38 +02:00
Linus Dietz c87a3cb2d7 Fix #9 2023-06-20 10:58:09 +02:00
Linus DietzandGitHub f6a26e5a41 Update README.md 2023-06-20 10:01:54 +02:00
Linus DietzandGitHub 42d07f1a4c Update README.md 2023-06-20 10:01:39 +02:00
Linus Dietz 71f03e505b Fix recharge caching while using multiple cars 2023-06-20 08:56:45 +02:00
Linus DietzandGitHub 2b6a2a4f4c Update README.md 2023-06-20 08:35:29 +02:00
Linus DietzandGitHub 38cca04473 Add working with section 2023-06-20 08:32:35 +02:00
15 changed files with 199 additions and 41 deletions
+2 -2
View File
@@ -11,7 +11,7 @@ COPY requirements.txt .
RUN pip install -r requirements.txt RUN pip install -r requirements.txt
# copy the content of the local src directory to the working directory # copy the content of the local src directory to the working directory
COPY / . COPY /src .
# command to run on container start # command to run on container start
CMD [ "python", "-u", "./main.py" ] CMD [ "python", "-u", "./main.py" ]
+31 -6
View File
@@ -3,23 +3,48 @@
This component establishes a connection between the newer AAOS Volvo cars and Home Assistant via MQTT.<br> 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. 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!
## Supported features
- Lock/unlock car
- Start/stop climate
- Sensor "Battery Charge Level"
- Sensor "Electric Range"
- Sensor "Charging System Status"
- Sensor "Estimated Charging Finish Time"
- Multiple cars
NOTE: Energy status currently available only for cars in the Europe / Middle East / Africa regions. [source](https://developer.volvocars.com/apis/energy/v1/overview/#availability)
## Setup ## Setup
Docker:
Just install this addon with the following command. Just install this addon with the following command.
Please note to fill in your settings inside the environment variables. 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` `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`
HA Add-On<br>
[![Open your Home Assistant instance and show the add add-on repository dialog with a specific repository URL pre-filled.](https://my.home-assistant.io/badges/supervisor_add_addon_repository.svg)](https://my.home-assistant.io/redirect/supervisor_add_addon_repository/?repository_url=https%3A%2F%2Fgithub.com%2FDielee%2Fvolvo2mqtt)
Here is what every option means: Here is what every option means:
| Name | Type | Default | Description | | Name | Type | Default | Description |
| -------------------- | :-------: | :----------: | --------------------------------------------------------------- | | -------------------- | :-------: | :----------: | --------------------------------------------------------------- |
| `updateInterval` | `int` | **required** | Update intervall in seconds. | | `CONF_updateInterval` | `int` | **required** | Update intervall in seconds. |
| `babelLocale` | `string` | **required** | Locale for date Format | | `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! |
| `mqtt` | `json` | **required** | Broker = Mqtt Broker IP / Username and Passwort are optional! | | `CONF_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_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" | | `TZ` | `string` | | Container timezone eg "Europe/Berlin" |
If you like my work: 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> [![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/U7U8MFXCF)
-8
View File
@@ -1,8 +0,0 @@
{
"name": "volvoaaos2mqtt",
"ports": {},
"env": {"TZ": "Europe/Amsterdam"},
"volumes": [
"/home/dockerhost/volvoAAOS2mqtt/settings.json:/volvoAAOS2mqtt/settings.json"
]
}
+3
View File
@@ -0,0 +1,3 @@
name: volvo2mqtt
url: https://github.com/Dielee/volvo2mqtt
maintainer: Linus Dietz (Dielee)
+20
View File
@@ -0,0 +1,20 @@
ARG BUILD_FROM
FROM $BUILD_FROM
# Install requirements for add-on
RUN apk add --no-cache python3 bash py3-pip
# set the working directory in the container
WORKDIR /volvoAAOS2mqtt
# download requirements.txt
RUN wget https://raw.githubusercontent.com/Dielee/volvo2mqtt/main/requirements.txt
# install dependencies
RUN pip install -r requirements.txt
# copy the content of the local src directory to the working directory
COPY / .
# command to run on container start
CMD [ "python", "-u", "./main.py" ]
+64
View File
@@ -0,0 +1,64 @@
#include <tunables/global>
profile volvo2mqtt flags=(attach_disconnected,mediate_deleted) {
#include <abstractions/base>
# Capabilities
file,
signal (send) set=(kill,term,int,hup,cont),
# S6-Overlay
/init ix,
/bin/** ix,
/usr/bin/** ix,
/run/{s6,s6-rc*,service}/** ix,
/package/** ix,
/command/** ix,
/etc/services.d/** rwix,
/etc/cont-init.d/** rwix,
/etc/cont-finish.d/** rwix,
/run/{,**} rwk,
/dev/tty rw,
# Bashio
/usr/lib/bashio/** ix,
/tmp/** rwk,
# Access to options.json and other files within your addon
/data/** rw,
# Start new profile for service
/usr/bin/myprogram cx -> myprogram,
profile myprogram flags=(attach_disconnected,mediate_deleted) {
#include <abstractions/base>
# Receive signals from S6-Overlay
signal (receive) peer=*_ADDON_SLUG,
# Access to options.json and other files within your addon
/data/** rw,
# Access to mapped volumes specified in config.json
/share/** rw,
# Access required for service functionality
/usr/bin/myprogram r,
/bin/bash rix,
/bin/echo ix,
/etc/passwd r,
/dev/tty rw,
# Allow outbound MQTT communication
network tcp,
network inet stream,
network inet dgram,
network inet6 stream,
network inet6 dgram,
# Add any additional MQTT-related rules if needed
# For example, if you're using the paho-mqtt library
/usr/bin/python3 cx -> paho.mqtt,
/usr/lib/python3.11/site-packages/paho/mqtt/** r,
}
}
+1 -1
View File
@@ -2,5 +2,5 @@ from dynaconf import Dynaconf
settings = Dynaconf( settings = Dynaconf(
envvar_prefix="CONF", envvar_prefix="CONF",
settings_files=["settings.json"], settings_files=["settings.json", "/data/options.json"],
) )
+39
View File
@@ -0,0 +1,39 @@
name: "Volvo2Mqtt"
description: "Volvo AAOS MQTT bridge"
version: "1.1.0"
slug: "volvo2mqtt"
init: false
url: "https://github.com/Dielee/volvo2mqtt"
apparmor: true
options:
updateInterval: 300
babelLocale: null
debug: false
mqtt:
broker: null
username: ""
password: ""
volvoData:
username: null
password: null
vin: ""
vccapikey: null
schema:
updateInterval: int(10,)
babelLocale: str
debug: bool
mqtt:
broker: str
username: str?
password: str?
volvoData:
username: str
password: str
vin: str?
vccapikey: str
arch:
- aarch64
- amd64
- armhf
- armv7
- i386
+2 -2
View File
@@ -1,4 +1,4 @@
VERSION = "v1.0.4" VERSION = "v1.1.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"
@@ -12,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" 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_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 = [ supported_sensors = [
{"name": "Battery Charge Level", "id": "battery_charge_level", "unit": "%", "icon": "car-battery", "url": RECHARGE_STATUS_URL}, {"name": "Battery Charge Level", "id": "battery_charge_level", "unit": "%", "icon": "car-battery", "url": RECHARGE_STATUS_URL},
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

View File
+11 -9
View File
@@ -12,7 +12,7 @@ from const import VEHICLE_DETAILS_URL, CLIMATE_START_URL, CLIMATE_STOP_URL, CAR_
mqtt_client: mqtt.Client mqtt_client: mqtt.Client
subscribed_topics = [] subscribed_topics = []
assumed_climate_state = "OFF" assumed_climate_state = {}
last_data_update = None last_data_update = None
@@ -42,27 +42,28 @@ def on_disconnect(client, userdata, rc):
def on_message(client, userdata, msg): def on_message(client, userdata, msg):
if msg.topic in subscribed_topics: if msg.topic in subscribed_topics:
vin = msg.topic.split('/')[2].split('_')[0]
payload = msg.payload.decode("UTF-8") payload = msg.payload.decode("UTF-8")
if "climate_status" in msg.topic: if "climate_status" in msg.topic:
global assumed_climate_state global assumed_climate_state
if payload == "ON": 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() api_thread.start()
assumed_climate_state = "ON" assumed_climate_state[vin] = "ON"
# Starting timer to disable climate after 30 mins # 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() update_car_data()
elif payload == "OFF": 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() api_thread.start()
assumed_climate_state = "OFF" assumed_climate_state[vin] = "OFF"
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") volvo.api_call(CAR_LOCK_URL, "POST", vin)
update_car_data() update_car_data()
elif payload == "UNLOCK": elif payload == "UNLOCK":
volvo.api_call(CAR_UNLOCK_URL, "POST") volvo.api_call(CAR_UNLOCK_URL, "POST", vin)
update_car_data() update_car_data()
elif "update_data" in msg.topic: elif "update_data" in msg.topic:
if payload == "PRESS": if payload == "PRESS":
@@ -74,6 +75,7 @@ def update_loop():
while True: while True:
print("Sending mqtt update...") print("Sending mqtt update...")
update_car_data() update_car_data()
print("Mqtt update done. Next run in " + str(settings["updateInterval"]) + " seconds.")
time.sleep(settings["updateInterval"]) time.sleep(settings["updateInterval"])
@@ -90,7 +92,7 @@ def update_car_data():
for switch in supported_switches: for switch in supported_switches:
if switch["id"] == "climate_status": if switch["id"] == "climate_status":
state = assumed_climate_state state = assumed_climate_state[vin]
else: else:
state = "OFF" state = "OFF"
View File
+26 -13
View File
@@ -17,7 +17,7 @@ token_expires_at: datetime
refresh_token = None refresh_token = None
vins = [] vins = []
recharge_response = {} recharge_response = {}
recharge_last_update = None recharge_last_update = {}
def authorize(): def authorize():
@@ -95,11 +95,18 @@ def get_vehicles():
if len(vins) == 0: if len(vins) == 0:
raise Exception("No vehicle found, exiting application!") raise Exception("No vehicle found, exiting application!")
else: else:
initialize_climate(vins)
print("Vin: " + str(vins) + " found!") print("Vin: " + str(vins) + " found!")
def disable_climate(): def initialize_climate(vins):
mqtt.assumed_climate_state = "OFF" 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() mqtt.update_car_data()
@@ -111,22 +118,22 @@ def api_call(url, method, vin, sensor_id=None):
global recharge_response, recharge_last_update global recharge_response, recharge_last_update
if url == RECHARGE_STATUS_URL: if url == RECHARGE_STATUS_URL:
# Minimize API calls for recharge API # Minimize API calls for recharge API
if not bool(recharge_response): if not vin in recharge_response:
# No API Data cached, get fresh data from API # No API Data for vin 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) response = session.get(url.format(vin), timeout=15)
recharge_response = response recharge_response[vin] = response
recharge_last_update = datetime.now() recharge_last_update[vin] = datetime.now()
else: else:
if (datetime.now() - recharge_last_update).total_seconds() >= settings["updateInterval"]: if (datetime.now() - recharge_last_update[vin]).total_seconds() >= settings["updateInterval"]:
# Old Data in Cache, updateing # Old Data in Cache, updating
print("Starting " + method + " call against " + url) print("Starting " + method + " call against " + url)
response = session.get(url.format(vin), timeout=15) response = session.get(url.format(vin), timeout=15)
recharge_response = response recharge_response[vin] = response
recharge_last_update = datetime.now() recharge_last_update[vin] = datetime.now()
else: else:
# Data is up do date, returning cached data # Data is up do date, returning cached data
response = recharge_response response = recharge_response[vin]
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) 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: if response.status_code == 200:
data = response.json() data = response.json()
if "debug" in settings:
if settings["debug"]:
print(response.text)
else: else:
if url == CLIMATE_START_URL and response.status_code == 503: if url == CLIMATE_START_URL and response.status_code == 503:
print("Car in use, cannot start pre climatization") print("Car in use, cannot start pre climatization")
@@ -147,7 +157,10 @@ def api_call(url, method, vin, sensor_id=None):
else: else:
print("API Call failed. Status Code: " + str(response.status_code) + ". Error: " + response.text) print("API Call failed. Status Code: " + str(response.status_code) + ". Error: " + response.text)
return "" return ""
return parse_api_data(url, data, sensor_id)
def parse_api_data(url, data, sensor_id=None):
if url == VEHICLE_DETAILS_URL: if url == VEHICLE_DETAILS_URL:
return data["data"] return data["data"]
elif sensor_id == "battery_charge_level": elif sensor_id == "battery_charge_level":
@@ -161,7 +174,7 @@ def api_call(url, method, vin, sensor_id=None):
else: else:
return "" return ""
elif sensor_id == "charging_system_status": 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"]] return charging_system_states[data["data"]["chargingSystemStatus"]["value"]]
else: else:
return "" return ""