mirror of
https://github.com/Dielee/volvo2mqtt.git
synced 2026-07-22 02:13:01 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
414ac503b1 | ||
|
|
18926b67e4 | ||
|
|
f650ea9247 | ||
|
|
a7a2e1182c | ||
|
|
3d3227de0d | ||
|
|
44509529ff | ||
|
|
0dd5852d81 | ||
|
|
c6ddd71021 | ||
|
|
1f0612ca4a | ||
|
|
6306c9a4b3 | ||
|
|
8bd0388690 | ||
|
|
245dd82c82 | ||
|
|
8e471d8eb0 | ||
|
|
cb1377005c | ||
|
|
89bd76e981 | ||
|
|
19aafa3b21 |
@@ -27,20 +27,20 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
uses: docker/setup-qemu-action@v3
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
uses: docker/setup-buildx-action@v3
|
||||
- name: Log in to the Container registry
|
||||
uses: docker/login-action@65b78e6e13532edd9afa3aa52ac7964289d1a9c1
|
||||
uses: docker/login-action@v3
|
||||
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
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
tags: |
|
||||
@@ -52,7 +52,7 @@ jobs:
|
||||
type=semver,pattern={{major}}
|
||||
type=sha
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v2
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm/v7,linux/arm/v6,linux/arm/v8
|
||||
|
||||
@@ -50,6 +50,7 @@ Please let me know if your car works with this addon so I can expand the list!<b
|
||||
- Lock/unlock car
|
||||
- Start/stop climate
|
||||
- Sensor "Battery Charge Level"
|
||||
- Sensor "Battery Capacity"
|
||||
- Sensor "Electric Range"
|
||||
- Sensor "Charging System Status"
|
||||
- Sensor "Charging Connection Status" (partly broken since 2.10 car update)
|
||||
@@ -73,6 +74,50 @@ Please let me know if your car works with this addon so I can expand the list!<b
|
||||
|
||||
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)
|
||||
|
||||
## OTP Authentication
|
||||
|
||||
As of version <b>v1.9.0</b>, this addon uses the same OTP authentication as the Volvo app.
|
||||
The following steps are required for authentication in exactly this order:
|
||||
|
||||
1. Setup volvo2Mqtt, either via Docker, or via HA addon (take a look at the "Setup" section below)
|
||||
2. Fill in your settings and start volvo2Mqtt
|
||||
3. Your log will show the following lines <br>
|
||||
```Waiting for otp code... Please check your mailbox and post your otp code to the following mqtt topic "volvoAAOS2mqtt/otp_code". Retry 0/15```<br>
|
||||
```Waiting for otp code... Please check your mailbox and post your otp code to the following mqtt topic "volvoAAOS2mqtt/otp_code". Retry 1/15```<br>
|
||||
```etc ...```
|
||||
4. Now, open your mailbox and copy your OTP Code
|
||||
5. Open HomeAssistant and search for the entity ID ```text.volvo_otp```
|
||||
6. Paste your OTP into the text entity and **<ins>press Enter</ins>**<br>
|
||||
6a. (optional) If your OTP cannot be safed, try to paste your OTP code via MQTT to the following topic: `volvoAAOS2mqtt/otp_code` <p>
|
||||
<details>
|
||||
<summary>OTP Post Script</summary>
|
||||
<div>
|
||||
<p>
|
||||
1. Safe the following script in your script section <br>
|
||||
2. Go to https://yourHAUrl/developer-tools/service<br>
|
||||
3. Search for "Post Volvo OTP"<br>
|
||||
4. Tick "OTP" and fill in your OTP<br>
|
||||
5. Run this service<br>
|
||||
</div>
|
||||
|
||||
alias: Post Volvo OTP
|
||||
sequence:
|
||||
- service: mqtt.publish
|
||||
metadata: {}
|
||||
data:
|
||||
topic: volvoAAOS2mqtt/otp_code
|
||||
payload: "{{ otp }}"
|
||||
mode: single
|
||||
fields:
|
||||
otp:
|
||||
selector:
|
||||
text: null
|
||||
name: OTP
|
||||
description: Volvo OTP
|
||||
|
||||
</details>
|
||||
8. If everything has worked, your addon is now authenticated. In the future, OTP authentication only needs to be done when updating, not when restarting the container.
|
||||
|
||||
## Setup
|
||||
<b>Docker:</b>
|
||||
|
||||
@@ -106,7 +151,6 @@ Here is what every option means:
|
||||
| `TZ` | `string` | | **required** | Container timezone eg "Europe/Berlin" from [here](https://docs.diladele.com/docker/timezones.html)|
|
||||
|
||||
|
||||
|
||||
[aarch64-shield]: https://img.shields.io/badge/aarch64-yes-green.svg
|
||||
[amd64-shield]: https://img.shields.io/badge/amd64-yes-green.svg
|
||||
[armhf-shield]: https://img.shields.io/badge/armhf-yes-green.svg
|
||||
|
||||
+3
-2
@@ -1,5 +1,6 @@
|
||||
requests~=2.29.0
|
||||
requests~=2.32.0
|
||||
dynaconf~=3.1.12
|
||||
paho-mqtt~=1.6.1
|
||||
Babel~=2.12.1
|
||||
pytz~=2023.3
|
||||
pytz~=2023.3
|
||||
google_play_scraper~=1.2.6
|
||||
@@ -1,3 +1,44 @@
|
||||
## v1.9.3
|
||||
### 🐛 Bug Fixes:
|
||||
|
||||
- Ignore retained OTP messages #193
|
||||
|
||||
## v1.9.2
|
||||
### 🐛 Bug Fixes:
|
||||
|
||||
- Try to fix OTP Input
|
||||
- Fix `SyntaxWarning: invalid escape sequence '\d'` #196
|
||||
|
||||
## v1.9.1
|
||||
### 🚀 Features:
|
||||
|
||||
- The token file now survives an HA addon update/restart
|
||||
|
||||
### 🐛 Bug Fixes:
|
||||
|
||||
- Extend OTP retry time to 120 seconds
|
||||
|
||||
## v1.9.0
|
||||
### ⚠️ Breaking change
|
||||
### 🚀 Features:
|
||||
|
||||
- Add "Battery Capacity" sensor. Thanks to @gurtjun!
|
||||
- OTP Auth
|
||||
This addon uses the same OTP authentication as the Volvo app, now.
|
||||
The following steps are required for authentication in exactly this order:
|
||||
|
||||
1. Setup volvo2Mqtt, either via Docker, or via HA addon (take a look at the "Setup" section)
|
||||
2. Fill in your settings and start volvo2Mqtt
|
||||
3. Your log will show the following lines
|
||||
Waiting for otp code... Please check your mailbox and post your otp code to the following mqtt topic "volvoAAOS2mqtt/otp_code". Retry 0/15
|
||||
Waiting for otp code... Please check your mailbox and post your otp code to the following mqtt topic "volvoAAOS2mqtt/otp_code". Retry 1/15
|
||||
etc ...
|
||||
4. Now, open your mailbox and copy your OTP Code
|
||||
5. Open HomeAssistant and search for the entity ID text.volvo_otp
|
||||
6. Paste your OTP into the text entity and press Enter
|
||||
7. If everything has worked, your addon is now authenticated. In the future, OTP authentication only needs to be done when updating, not when restarting the container.
|
||||
|
||||
|
||||
## v1.8.27
|
||||
### 🚀 Features:
|
||||
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
name: "Volvo2Mqtt"
|
||||
description: "Volvo AAOS MQTT bridge"
|
||||
version: "1.8.27"
|
||||
version: "1.9.3"
|
||||
slug: "volvo2mqtt"
|
||||
init: false
|
||||
url: "https://github.com/Dielee/volvo2mqtt"
|
||||
|
||||
+7
-3
@@ -1,8 +1,9 @@
|
||||
from config import settings
|
||||
|
||||
VERSION = "v1.8.27"
|
||||
VERSION = "v1.9.4"
|
||||
|
||||
OAUTH_URL = "https://volvoid.eu.volvocars.com/as/token.oauth2"
|
||||
OAUTH_TOKEN_URL = "https://volvoid.eu.volvocars.com/as/token.oauth2"
|
||||
OAUTH_AUTH_URL = "https://volvoid.eu.volvocars.com/as/authorization.oauth2"
|
||||
VEHICLES_URL = "https://api.volvocars.com/connected-vehicle/v2/vehicles"
|
||||
VEHICLE_DETAILS_URL = "https://api.volvocars.com/connected-vehicle/v2/vehicles/{0}"
|
||||
WINDOWS_STATE_URL = "https://api.volvocars.com/connected-vehicle/v2/vehicles/{0}/windows"
|
||||
@@ -21,7 +22,7 @@ STATISTICS_URL = "https://api.volvocars.com/connected-vehicle/v2/vehicles/{0}/st
|
||||
ENGINE_DIAGNOSTICS_URL = "https://api.volvocars.com/connected-vehicle/v2/vehicles/{0}/engine"
|
||||
VEHICLE_DIAGNOSTICS_URL = "https://api.volvocars.com/connected-vehicle/v2/vehicles/{0}/diagnostics"
|
||||
WARNINGS_URL = "https://api.volvocars.com/connected-vehicle/v2/vehicles/{0}/warnings"
|
||||
API_BACKEND_STATUS = "https://oip-dev-bff.euwest1.production.volvo.care/api/v1/backend-status"
|
||||
API_BACKEND_STATUS = "https://public-developer-portal-bff.weu-prod.ecpaz.volvocars.biz/api/v1/backend-status"
|
||||
|
||||
LENGTH_KILOMETERS = "km"
|
||||
SPEED_KILOMETERS_PER_HOUR = "km/h"
|
||||
@@ -91,6 +92,7 @@ icon_states = {
|
||||
supported_entities = [
|
||||
{"name": "Battery Charge Level", "domain": "sensor", "device_class": "battery", "id": "battery_charge_level", "unit": "%", "icon": "car-battery", "url": RECHARGE_STATE_URL, "state_class": "measurement"},
|
||||
{"name": "Battery Charge Level", "domain": "sensor", "device_class": "battery", "id": "battery_charge_level", "unit": "%", "icon": "car-battery", "url": FUEL_BATTERY_STATE_URL, "state_class": "measurement"},
|
||||
{"name": "Battery Capacity", "domain": "sensor", "device_class": "energy_storage", "id": "battery_capacity", "unit": ENERGY_KILO_WATT_HOUR, "icon": "car-battery", "url": VEHICLE_DETAILS_URL, "state_class": "measurement"},
|
||||
{"name": "Electric Range", "domain": "sensor", "id": "electric_range", "unit": LENGTH_KILOMETERS if not units.get(settings["babelLocale"]) else units[settings["babelLocale"]]["electric_range"]["unit"], "icon": "map-marker-distance", "url": RECHARGE_STATE_URL, "state_class": "measurement"},
|
||||
{"name": "Estimated Charging Time", "domain": "sensor", "id": "estimated_charging_time", "unit": TIME_MINUTES, "icon": "timer-sync-outline", "url": RECHARGE_STATE_URL, "state_class": "measurement"},
|
||||
{"name": "Charging System Status", "domain": "sensor", "id": "charging_system_status", "icon": "ev-station", "url": RECHARGE_STATE_URL},
|
||||
@@ -137,3 +139,5 @@ supported_entities = [
|
||||
]
|
||||
|
||||
old_entity_ids = ["months_to_service", "service_warning_trigger", "distance_to_empty"]
|
||||
otp_max_loops = 24
|
||||
otp_mqtt_topic = "volvoAAOS2mqtt/otp_code"
|
||||
+52
-8
@@ -10,7 +10,7 @@ from datetime import datetime
|
||||
from babel.dates import format_datetime
|
||||
from config import settings
|
||||
from const import CLIMATE_START_URL, CLIMATE_STOP_URL, CAR_LOCK_URL, \
|
||||
CAR_UNLOCK_URL, availability_topic, icon_states, old_entity_ids
|
||||
CAR_UNLOCK_URL, availability_topic, icon_states, old_entity_ids, otp_mqtt_topic
|
||||
|
||||
mqtt_client: mqtt.Client
|
||||
subscribed_topics = []
|
||||
@@ -20,7 +20,7 @@ climate_timer = {}
|
||||
engine_status = {}
|
||||
devices = {}
|
||||
active_schedules = {}
|
||||
|
||||
otp_code = None
|
||||
|
||||
def connect():
|
||||
client = mqtt.Client("volvoAAOS2mqtt") if os.environ.get("IS_HA_ADDON") \
|
||||
@@ -37,6 +37,7 @@ def connect():
|
||||
port = settings["mqtt"]["port"]
|
||||
client.connect(settings["mqtt"]["broker"], port)
|
||||
client.loop_start()
|
||||
client.subscribe("volvoAAOS2mqtt/otp_code")
|
||||
client.on_message = on_message
|
||||
client.on_disconnect = on_disconnect
|
||||
client.on_connect = on_connect
|
||||
@@ -45,6 +46,40 @@ def connect():
|
||||
mqtt_client = client
|
||||
|
||||
|
||||
def create_otp_input():
|
||||
state_topic = otp_mqtt_topic + "/state"
|
||||
config = {
|
||||
"name": "Volvo OTP",
|
||||
"object_id": f"volvo_otp",
|
||||
"schema": "state",
|
||||
"command_topic": otp_mqtt_topic,
|
||||
"state_topic": state_topic,
|
||||
"unique_id": "volvoAAOS2mqtt_otp",
|
||||
"pattern": r"\d{6}",
|
||||
"icon": "mdi:two-factor-authentication",
|
||||
"mode": "text"
|
||||
}
|
||||
|
||||
mqtt_client.publish(
|
||||
"homeassistant/text/volvoAAOS2mqtt/volvo_otp/config",
|
||||
json.dumps(config),
|
||||
retain=True
|
||||
)
|
||||
|
||||
mqtt_client.publish(
|
||||
state_topic,
|
||||
"000000",
|
||||
retain=True
|
||||
)
|
||||
|
||||
def set_otp_state():
|
||||
mqtt_client.publish(
|
||||
otp_mqtt_topic + "/state",
|
||||
otp_code,
|
||||
retain=True
|
||||
)
|
||||
|
||||
|
||||
def send_car_images(vin, data, device):
|
||||
if util.keys_exists(data, "images"):
|
||||
for entity in [{"name": "Exterior Image", "id": "exterior_image"},
|
||||
@@ -94,13 +129,22 @@ def on_disconnect(client, userdata, rc):
|
||||
|
||||
|
||||
def on_message(client, userdata, msg):
|
||||
try:
|
||||
vin = msg.topic.split('/')[2].split('_')[0]
|
||||
except IndexError:
|
||||
logging.error("Error - Cannot get vin from MQTT topic!")
|
||||
return None
|
||||
|
||||
payload = msg.payload.decode("UTF-8")
|
||||
if msg.topic == otp_mqtt_topic:
|
||||
if msg.retain == 0:
|
||||
global otp_code
|
||||
otp_code = payload
|
||||
set_otp_state()
|
||||
else:
|
||||
logging.warning("Found retained OTP, this can't work! Please clean retained messages!")
|
||||
return None
|
||||
else:
|
||||
try:
|
||||
vin = msg.topic.split('/')[2].split('_')[0]
|
||||
except IndexError:
|
||||
logging.error("Error - Cannot get vin from MQTT topic!")
|
||||
return None
|
||||
|
||||
if "climate_status" in msg.topic:
|
||||
if payload == "ON":
|
||||
start_climate(vin)
|
||||
|
||||
+28
-3
@@ -4,6 +4,8 @@ import os
|
||||
import re
|
||||
import sys
|
||||
import config
|
||||
import json
|
||||
from google_play_scraper import app
|
||||
from logging import handlers
|
||||
from datetime import datetime
|
||||
from const import units
|
||||
@@ -28,6 +30,29 @@ class SensitiveDataFilter(logging.Filter):
|
||||
record.msg = re.sub(pattern, "<REDACTED>", record.msg)
|
||||
return True
|
||||
|
||||
def get_volvo_app_version():
|
||||
result = app(
|
||||
'se.volvo.vcc',
|
||||
lang='en',
|
||||
country='us'
|
||||
)
|
||||
|
||||
if "version" in result:
|
||||
return result["version"]
|
||||
else:
|
||||
return "5.37.0"
|
||||
|
||||
def get_token_path():
|
||||
token_path = ".token"
|
||||
if os.environ.get("IS_HA_ADDON"):
|
||||
check_existing_folder("/addons/volvo2mqtt/token/")
|
||||
token_path = "/addons/volvo2mqtt/token/.token"
|
||||
|
||||
return token_path
|
||||
|
||||
def save_to_json(data, token_path):
|
||||
with open(token_path, 'w', encoding='utf-8') as f:
|
||||
json.dump(data, f, ensure_ascii=False, indent=4)
|
||||
|
||||
def get_icon_between(icon_list, state):
|
||||
icon = None
|
||||
@@ -40,7 +65,7 @@ def get_icon_between(icon_list, state):
|
||||
def setup_logging():
|
||||
log_location = "volvo2mqtt.log"
|
||||
if os.environ.get("IS_HA_ADDON"):
|
||||
check_existing_folder()
|
||||
check_existing_folder("/addons/volvo2mqtt/log/")
|
||||
log_location = "/addons/volvo2mqtt/log/volvo2mqtt.log"
|
||||
|
||||
logging.Formatter.converter = lambda *args: datetime.now(tz=TZ).timetuple()
|
||||
@@ -73,8 +98,8 @@ def setup_logging():
|
||||
logger.setLevel(logging.ERROR)
|
||||
|
||||
|
||||
def check_existing_folder():
|
||||
Path("/addons/volvo2mqtt/log/").mkdir(parents=True, exist_ok=True)
|
||||
def check_existing_folder(path):
|
||||
Path(path).mkdir(parents=True, exist_ok=True)
|
||||
|
||||
|
||||
def keys_exists(element, *keys):
|
||||
|
||||
+135
-26
@@ -1,18 +1,21 @@
|
||||
import json
|
||||
import logging
|
||||
import requests
|
||||
import mqtt
|
||||
import util
|
||||
import time
|
||||
import re
|
||||
import os.path
|
||||
from threading import current_thread, Thread
|
||||
from datetime import datetime, timedelta
|
||||
from config import settings
|
||||
from babel.dates import format_datetime
|
||||
from json import JSONDecodeError
|
||||
from const import charging_system_states, charging_connection_states, door_states, window_states, \
|
||||
OAUTH_URL, VEHICLES_URL, VEHICLE_DETAILS_URL, RECHARGE_STATE_URL, CLIMATE_START_URL, \
|
||||
OAUTH_AUTH_URL, OAUTH_TOKEN_URL, VEHICLES_URL, VEHICLE_DETAILS_URL, RECHARGE_STATE_URL, CLIMATE_START_URL, \
|
||||
WINDOWS_STATE_URL, LOCK_STATE_URL, TYRE_STATE_URL, supported_entities, FUEL_BATTERY_STATE_URL, \
|
||||
STATISTICS_URL, ENGINE_DIAGNOSTICS_URL, VEHICLE_DIAGNOSTICS_URL, API_BACKEND_STATUS, WARNINGS_URL, engine_states
|
||||
STATISTICS_URL, ENGINE_DIAGNOSTICS_URL, VEHICLE_DIAGNOSTICS_URL, API_BACKEND_STATUS, WARNINGS_URL, engine_states, \
|
||||
otp_max_loops
|
||||
|
||||
session = requests.Session()
|
||||
session.headers = {
|
||||
@@ -30,37 +33,137 @@ vcc_api_keys = []
|
||||
backend_status = ""
|
||||
|
||||
|
||||
def authorize():
|
||||
headers = {
|
||||
"authorization": "Basic aDRZZjBiOlU4WWtTYlZsNnh3c2c1WVFxWmZyZ1ZtSWFEcGhPc3kxUENhVXNpY1F0bzNUUjVrd2FKc2U0QVpkZ2ZJZmNMeXc=",
|
||||
"content-type": "application/x-www-form-urlencoded",
|
||||
"accept": "application/json"
|
||||
}
|
||||
def authorize(renew_tokenfile=False):
|
||||
global refresh_token
|
||||
|
||||
body = {
|
||||
"username": settings.volvoData["username"],
|
||||
"password": settings.volvoData["password"],
|
||||
"grant_type": "password",
|
||||
"scope": "openid email profile care_by_volvo:financial_information:invoice:read care_by_volvo:financial_information:payment_method care_by_volvo:subscription:read customer:attributes customer:attributes:write order:attributes vehicle:attributes tsp_customer_api:all conve:brake_status conve:climatization_start_stop conve:command_accessibility conve:commands conve:diagnostics_engine_status conve:diagnostics_workshop conve:doors_status conve:engine_status conve:environment conve:fuel_status conve:honk_flash conve:lock conve:lock_status conve:navigation conve:odometer_status conve:trip_statistics conve:tyre_status conve:unlock conve:vehicle_relation conve:warnings conve:windows_status energy:battery_charge_level energy:charging_connection_status energy:charging_system_status energy:electric_range energy:estimated_charging_time energy:recharge_status vehicle:attributes"
|
||||
}
|
||||
auth = requests.post(OAUTH_URL, data=body, headers=headers)
|
||||
if auth.status_code == 200:
|
||||
data = auth.json()
|
||||
session.headers.update({"authorization": "Bearer " + data["access_token"]})
|
||||
|
||||
global token_expires_at, refresh_token
|
||||
token_expires_at = datetime.now(util.TZ) + timedelta(seconds=(data["expires_in"] - 30))
|
||||
token_path = util.get_token_path()
|
||||
if os.path.isfile(token_path) and not renew_tokenfile:
|
||||
logging.info("Using login from token file")
|
||||
f = open(token_path)
|
||||
data = json.load(f)
|
||||
refresh_token = data["refresh_token"]
|
||||
refresh_auth()
|
||||
else:
|
||||
logging.info("Starting login with OTP")
|
||||
auth_session = requests.session()
|
||||
auth_session.headers = {
|
||||
"authorization": "Basic aDRZZjBiOlU4WWtTYlZsNnh3c2c1WVFxWmZyZ1ZtSWFEcGhPc3kxUENhVXNpY1F0bzNUUjVrd2FKc2U0QVpkZ2ZJZmNMeXc=",
|
||||
"User-Agent": "vca-android/" + util.get_volvo_app_version(),
|
||||
"Accept-Encoding": "gzip",
|
||||
"Content-Type": "application/json; charset=utf-8"
|
||||
}
|
||||
|
||||
get_vcc_api_keys()
|
||||
get_vehicles()
|
||||
check_supported_endpoints()
|
||||
Thread(target=backend_status_loop).start()
|
||||
url_params = ("?client_id=h4Yf0b"
|
||||
"&response_type=code"
|
||||
"&acr_values=urn:volvoid:aal:bronze:2sv"
|
||||
"&response_mode=pi.flow"
|
||||
"&scope=openid email profile care_by_volvo:financial_information:invoice:read care_by_volvo:financial_information:payment_method care_by_volvo:subscription:read customer:attributes customer:attributes:write order:attributes vehicle:attributes tsp_customer_api:all conve:brake_status conve:climatization_start_stop conve:command_accessibility conve:commands conve:diagnostics_engine_status conve:diagnostics_workshop conve:doors_status conve:engine_status conve:environment conve:fuel_status conve:honk_flash conve:lock conve:lock_status conve:navigation conve:odometer_status conve:trip_statistics conve:tyre_status conve:unlock conve:vehicle_relation conve:warnings conve:windows_status energy:battery_charge_level energy:charging_connection_status energy:charging_system_status energy:electric_range energy:estimated_charging_time energy:recharge_status vehicle:attributes")
|
||||
|
||||
auth = auth_session.get(OAUTH_AUTH_URL + url_params)
|
||||
if auth.status_code == 200:
|
||||
response = auth.json()
|
||||
auth_state = response["status"]
|
||||
|
||||
if auth_state == "USERNAME_PASSWORD_REQUIRED":
|
||||
auth_session.headers.update({"x-xsrf-header": "PingFederate"})
|
||||
response = check_username_password(auth_session, response)
|
||||
auth_state = response["status"]
|
||||
|
||||
if auth_state == "OTP_REQUIRED":
|
||||
response = send_otp(auth_session, response)
|
||||
response = continue_auth(auth_session, response)
|
||||
token_data = get_token(auth_session, response)
|
||||
else:
|
||||
raise Exception("Unkown auth state " + auth_state)
|
||||
elif auth_state == "OTP_REQUIRED":
|
||||
response = send_otp(auth_session, response)
|
||||
response = continue_auth(auth_session, response)
|
||||
token_data = get_token(auth_session, response)
|
||||
elif auth_state == "OTP_VERIFIED":
|
||||
response = continue_auth(auth_session, response)
|
||||
token_data = get_token(auth_session, response)
|
||||
elif auth_state == "COMPLETED":
|
||||
token_data = get_token(auth_session, response)
|
||||
else:
|
||||
raise Exception("Unkown auth state " + auth_state)
|
||||
|
||||
session.headers.update({"authorization": "Bearer " + token_data["access_token"]})
|
||||
|
||||
global token_expires_at
|
||||
token_expires_at = datetime.now(util.TZ) + timedelta(seconds=(token_data["expires_in"] - 30))
|
||||
refresh_token = token_data["refresh_token"]
|
||||
|
||||
util.save_to_json(token_data, token_path)
|
||||
else:
|
||||
message = auth.json()
|
||||
raise Exception(message["details"][0]["userMessage"])
|
||||
|
||||
get_vcc_api_keys()
|
||||
get_vehicles()
|
||||
check_supported_endpoints()
|
||||
Thread(target=backend_status_loop).start()
|
||||
|
||||
def continue_auth(auth_session, data):
|
||||
next_url = data["_links"]["continueAuthentication"]["href"] + "?action=continueAuthentication"
|
||||
auth = auth_session.get(next_url)
|
||||
|
||||
if auth.status_code == 200:
|
||||
return auth.json()
|
||||
else:
|
||||
message = auth.json()
|
||||
raise Exception(message["details"][0]["userMessage"])
|
||||
|
||||
|
||||
def get_token(auth_session, data):
|
||||
auth_session.headers.update({"content-type": "application/x-www-form-urlencoded"})
|
||||
body = {"code": data["authorizeResponse"]["code"], "grant_type": "authorization_code"}
|
||||
token_auth = auth_session.post(OAUTH_TOKEN_URL, data=body)
|
||||
|
||||
if token_auth.status_code == 200:
|
||||
return token_auth.json()
|
||||
else:
|
||||
message = token_auth.json()
|
||||
raise Exception(message["error_description"])
|
||||
|
||||
|
||||
def check_username_password(auth_session, data):
|
||||
next_url = data["_links"]["checkUsernamePassword"]["href"] + "?action=checkUsernamePassword"
|
||||
body = {"username": settings.volvoData["username"],
|
||||
"password": settings.volvoData["password"]}
|
||||
auth = auth_session.post(next_url, data=json.dumps(body))
|
||||
|
||||
if auth.status_code == 200:
|
||||
return auth.json()
|
||||
else:
|
||||
message = auth.json()
|
||||
raise Exception(message["details"][0]["userMessage"])
|
||||
|
||||
|
||||
def send_otp(auth_session, data):
|
||||
mqtt.create_otp_input()
|
||||
next_url = data["_links"]["checkOtp"]["href"] + "?action=checkOtp"
|
||||
body = {"otp": ""}
|
||||
|
||||
for i in range(otp_max_loops):
|
||||
if mqtt.otp_code:
|
||||
body["otp"] = mqtt.otp_code
|
||||
break
|
||||
|
||||
logging.info("Waiting for otp code... Please check your mailbox and post your otp code to the following "
|
||||
"mqtt topic \"volvoAAOS2mqtt/otp_code\". Retry " + str(i) + "/" + str(otp_max_loops))
|
||||
time.sleep(5)
|
||||
|
||||
if not mqtt.otp_code:
|
||||
raise Exception ("No OTP found, exting...")
|
||||
|
||||
auth = auth_session.post(next_url, data=json.dumps(body))
|
||||
if auth.status_code == 200:
|
||||
return auth.json()
|
||||
else:
|
||||
message = auth.json()
|
||||
raise Exception(message["details"][0]["userMessage"])
|
||||
|
||||
|
||||
def refresh_auth():
|
||||
logging.info("Refreshing credentials")
|
||||
global refresh_token
|
||||
@@ -76,18 +179,22 @@ def refresh_auth():
|
||||
}
|
||||
|
||||
try:
|
||||
auth = requests.post(OAUTH_URL, data=body, headers=headers)
|
||||
auth = requests.post(OAUTH_TOKEN_URL, data=body, headers=headers)
|
||||
except requests.exceptions.RequestException as e:
|
||||
logging.error("Error refreshing credentials data: " + str(e))
|
||||
return None
|
||||
|
||||
if auth.status_code == 200:
|
||||
token_path = util.get_token_path()
|
||||
data = auth.json()
|
||||
util.save_to_json(data, token_path)
|
||||
session.headers.update({"authorization": "Bearer " + data["access_token"]})
|
||||
|
||||
global token_expires_at
|
||||
token_expires_at = datetime.now(util.TZ) + timedelta(seconds=(data["expires_in"] - 30))
|
||||
refresh_token = data["refresh_token"]
|
||||
else:
|
||||
authorize(renew_tokenfile=True)
|
||||
|
||||
|
||||
def get_vehicles():
|
||||
@@ -443,6 +550,8 @@ def parse_api_data(data, sensor_id=None):
|
||||
|
||||
if sensor_id == "battery_charge_level":
|
||||
return data["batteryChargeLevel"]["value"] if util.keys_exists(data, "batteryChargeLevel") else None
|
||||
elif sensor_id == "battery_capacity":
|
||||
return data["batteryCapacityKWH"] if util.keys_exists(data, "batteryCapacityKWH") else None
|
||||
elif sensor_id == "electric_range":
|
||||
return util.convert_metric_values(data["electricRange"]["value"]) \
|
||||
if util.keys_exists(data, "electricRange") else None
|
||||
|
||||
Reference in New Issue
Block a user