Compare commits

...
28 Commits
Author SHA1 Message Date
Linus Dietz fbb98481f4 Undo changes // Bump version 2023-06-20 07:52:08 +02:00
Linus Dietz dad5f9aaaa Remove unused requirements // Bump version 2023-06-20 07:35:30 +02:00
Linus DietzandGitHub c404356ee0 Run workflow on release update 2023-06-20 07:28:37 +02:00
Linus Dietz 0516c29563 Fix type error #3 // Add addon version 2023-06-20 07:12:40 +02:00
Linus DietzandGitHub 9b3f9b4c0b Add pull always to docker run 2023-06-19 19:58:55 +02:00
Linus DietzandGitHub 65d0214664 Fix typo 2023-06-19 19:53:29 +02:00
Linus DietzandGitHub 39f457d01a Add info for multiple vins 2023-06-19 19:49:17 +02:00
Linus Dietz 21cb2999f7 Add support for multiple vehicles 2023-06-19 19:46:06 +02:00
Linus DietzandGitHub 298e8b503a Update README.md 2023-06-19 12:56:52 +02:00
Linus DietzandGitHub 2d50129ec4 Update README.md 2023-06-19 12:49:30 +02:00
Linus DietzandGitHub 5ef1aa528b Update README.md 2023-06-19 12:41:30 +02:00
Linus Dietz 3440dadeab Move vcc api key to settings 2023-06-19 12:39:20 +02:00
Linus DietzandGitHub 1dc38e6925 change image tagging 2023-06-19 12:33:53 +02:00
Linus DietzandGitHub 81b0bb9b85 remove unused step 2023-06-19 12:25:06 +02:00
Linus DietzandGitHub 97fb95799a add armv6 and v8 2023-06-19 12:21:51 +02:00
Linus DietzandGitHub ab73f9afb2 Update build_image.yml 2023-06-19 12:17:48 +02:00
Linus DietzandGitHub 285f9dc79b Update build_image.yml 2023-06-19 12:00:00 +02:00
Linus DietzandGitHub 6dad197ed6 Update build_image.yml 2023-06-19 11:53:53 +02:00
Linus DietzandGitHub 1ed6035a54 Update build_image.yml 2023-06-19 11:52:25 +02:00
Linus DietzandGitHub 666eda99d0 Update build_image.yml 2023-06-19 11:50:01 +02:00
Linus DietzandGitHub 02e64e5555 Add manual action dispatch 2023-06-19 11:48:18 +02:00
Linus DietzandGitHub c298b8972a Change build behaviour 2023-06-19 11:41:46 +02:00
Linus Dietz 2c0169466c Merge branch 'main' of https://github.com/Dielee/volvo2mqtt into main 2023-06-19 10:02:02 +02:00
Linus Dietz 8565742752 Check for recharge keys before returning them 2023-06-19 10:01:54 +02:00
Linus DietzandGitHub e690f60433 Try to add arm and arm64 build support 2023-06-19 09:35:03 +02:00
Linus DietzandGitHub 9eba31804d Add TZ to default sample 2023-06-18 21:34:12 +02:00
Linus DietzandGitHub 5139d84602 Add install instructions 2023-06-18 21:33:23 +02:00
Linus DietzandGitHub 2f3ca63b60 Update build_image.yml 2023-06-18 15:30:45 +02:00
8 changed files with 222 additions and 161 deletions
+18 -7
View File
@@ -11,7 +11,8 @@ name: Create and publish a Docker image
on:
release:
types: [created]
types: [created, edited]
workflow_dispatch:
env:
REGISTRY: ghcr.io
@@ -27,24 +28,34 @@ jobs:
steps:
- name: Checkout repository
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 }}
+25 -2
View File
@@ -1,2 +1,25 @@
# 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.
## 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** | 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/). |
| `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>
+2
View File
@@ -1,3 +1,5 @@
VERSION = "v1.0.4"
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}"
+2
View File
@@ -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()
+32 -30
View File
@@ -6,7 +6,7 @@ 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
@@ -80,10 +80,11 @@ def update_loop():
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", lock["id"])
state = volvo.api_call(lock["url"], "GET", vin, lock["id"])
mqtt_client.publish(
f"homeassistant/lock/{volvo.vin}_{lock['id']}/state",
f"homeassistant/lock/{vin}_{lock['id']}/state",
state
)
@@ -94,7 +95,7 @@ def update_car_data():
state = "OFF"
mqtt_client.publish(
f"homeassistant/switch/{volvo.vin}_{switch['id']}/state",
f"homeassistant/switch/{vin}_{switch['id']}/state",
state
)
@@ -102,85 +103,86 @@ def update_car_data():
if sensor["id"] == "last_data_update":
state = last_data_update
else:
state = volvo.api_call(sensor["url"], "GET", sensor["id"])
state = volvo.api_call(sensor["url"], "GET", vin, sensor["id"])
mqtt_client.publish(
f"homeassistant/sensor/{volvo.vin}_{sensor['id']}/state",
f"homeassistant/sensor/{vin}_{sensor['id']}/state",
state
)
def create_ha_devices():
car_details = volvo.api_call(VEHICLE_DETAILS_URL, "GET")
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/{volvo.vin}_{button['id']}/command"
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/{volvo.vin}_{button['id']}/state",
"state_topic": f"homeassistant/button/{vin}_{button['id']}/state",
"command_topic": command_topic,
"device": {
"identifiers": ["volvoAAOS2mqtt"],
"identifiers": [f"volvoAAOS2mqtt_{vin}"],
"manufacturer": "Volvo",
"model": car_details['descriptions']['model'],
"name": f"{car_details['descriptions']['model']} ({car_details['modelYear']}) - {volvo.vin}",
"name": f"{car_details['descriptions']['model']} ({car_details['modelYear']}) - {vin}",
},
"unique_id": f"volvoAAOS2mqtt_{volvo.vin}_{button['id']}",
"unique_id": f"volvoAAOS2mqtt_{vin}_{button['id']}",
}
mqtt_client.publish(
f"homeassistant/button/volvoAAOS2mqtt/{volvo.vin}_{button['id']}/config",
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/{volvo.vin}_{lock['id']}/command"
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/{volvo.vin}_{lock['id']}/state",
"state_topic": f"homeassistant/lock/{vin}_{lock['id']}/state",
"command_topic": command_topic,
"optimistic": False,
"device": {
"identifiers": ["volvoAAOS2mqtt"],
"identifiers": [f"volvoAAOS2mqtt_{vin}"],
"manufacturer": "Volvo",
"model": car_details['descriptions']['model'],
"name": f"{car_details['descriptions']['model']} ({car_details['modelYear']}) - {volvo.vin}",
"name": f"{car_details['descriptions']['model']} ({car_details['modelYear']}) - {vin}",
},
"unique_id": f"volvoAAOS2mqtt_{volvo.vin}_{lock['id']}",
"unique_id": f"volvoAAOS2mqtt_{vin}_{lock['id']}",
}
mqtt_client.publish(
f"homeassistant/lock/volvoAAOS2mqtt/{volvo.vin}_{lock['id']}/config",
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/{volvo.vin}_{switch['id']}/command"
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/{volvo.vin}_{switch['id']}/state",
"state_topic": f"homeassistant/switch/{vin}_{switch['id']}/state",
"command_topic": command_topic,
"optimistic": False,
"device": {
"identifiers": ["volvoAAOS2mqtt"],
"identifiers": [f"volvoAAOS2mqtt_{vin}"],
"manufacturer": "Volvo",
"model": car_details['descriptions']['model'],
"name": f"{car_details['descriptions']['model']} ({car_details['modelYear']}) - {volvo.vin}",
"name": f"{car_details['descriptions']['model']} ({car_details['modelYear']}) - {vin}",
},
"unique_id": f"volvoAAOS2mqtt_{volvo.vin}_{switch['id']}",
"unique_id": f"volvoAAOS2mqtt_{vin}_{switch['id']}",
}
mqtt_client.publish(
f"homeassistant/switch/volvoAAOS2mqtt/{volvo.vin}_{switch['id']}/config",
f"homeassistant/switch/volvoAAOS2mqtt/{vin}_{switch['id']}/config",
json.dumps(config),
)
subscribed_topics.append(command_topic)
@@ -192,20 +194,20 @@ def create_ha_devices():
"object_id": sensor['id'],
"schema": "state",
"icon": f"mdi:{sensor['icon']}",
"state_topic": f"homeassistant/sensor/{volvo.vin}_{sensor['id']}/state",
"state_topic": f"homeassistant/sensor/{vin}_{sensor['id']}/state",
"device": {
"identifiers": ["volvoAAOS2mqtt"],
"identifiers": [f"volvoAAOS2mqtt_{vin}"],
"manufacturer": "Volvo",
"model": car_details['descriptions']['model'],
"name": f"{car_details['descriptions']['model']} ({car_details['modelYear']}) - {volvo.vin}",
"name": f"{car_details['descriptions']['model']} ({car_details['modelYear']}) - {vin}",
},
"unique_id": f"volvoAAOS2mqtt_{volvo.vin}_{sensor['id']}",
"unique_id": f"volvoAAOS2mqtt_{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",
f"homeassistant/sensor/volvoAAOS2mqtt/{vin}_{sensor['id']}/config",
json.dumps(config),
)
time.sleep(2)
-1
View File
@@ -1,5 +1,4 @@
requests~=2.29.0
dynaconf~=3.1.12
paho-mqtt~=1.6.1
pytz~=2023.3
Babel~=2.12.1
+2 -1
View File
@@ -9,6 +9,7 @@
"volvoData": {
"username": "",
"password": "",
"vin": ""
"vin": "",
"vccapikey": ""
}
}
+35 -14
View File
@@ -8,14 +8,14 @@ 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
@@ -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,12 +103,12 @@ 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):
@@ -145,18 +151,31 @@ def api_call(url, method, sensor_id=None):
if url == VEHICLE_DETAILS_URL:
return data["data"]
elif sensor_id == "battery_charge_level":
if "batteryChargeLevel" in data["data"]:
return data["data"]["batteryChargeLevel"]["value"]
else:
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":
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"])
@@ -164,6 +183,8 @@ def api_call(url, method, sensor_id=None):
return format_datetime(charging_finished, format="medium", locale=settings["babelLocale"])
else:
return None
else:
return None
elif sensor_id == "lock_status":
return data["data"]["carLocked"]["value"]
else: