mirror of
https://github.com/Dielee/volvo2mqtt.git
synced 2026-07-22 02:13:01 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
515e1a8d27 | ||
|
|
4961f7bfad | ||
|
|
a68216bdd5 | ||
|
|
98d8dd7e65 | ||
|
|
7acfdea1d8 | ||
|
|
f95348769b | ||
|
|
4673f14a1c | ||
|
|
1a2f35f1b5 | ||
|
|
33ee258f3f | ||
|
|
54db72df94 | ||
|
|
70b027fb70 | ||
|
|
1c07ec1b46 | ||
|
|
2fa154e023 | ||
|
|
ae5e3074ed | ||
|
|
f5a1e48e26 | ||
|
|
ee9d3cdef9 |
@@ -129,7 +129,17 @@ The following steps are required for authentication in exactly this order:
|
|||||||
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 -d --pull=always -e CONF_updateInterval=300 -e CONF_babelLocale='de' -e CONF_mqtt='@json {"broker": "", "username": "", "password": "", "port": 1883}' -e CONF_volvoData='@json {"username": "", "password": "", "vin": "", "vccapikey": ["key1", "key2"]}' -e TZ='Europe/Berlin' --name volvo2mqtt ghcr.io/dielee/volvo2mqtt:latest`
|
<pre>
|
||||||
|
<code>docker run -d --pull=always \
|
||||||
|
-e CONF_updateInterval=300 \
|
||||||
|
-e CONF_babelLocale='de' \
|
||||||
|
-e CONF_mqtt='@json {"broker": "", "username": "", "password": "", "port": 1883}' \
|
||||||
|
-e CONF_volvoData='@json {"username": "", "password": "", "vin": "", "vccapikey": ["key1", "key2"]}' \
|
||||||
|
-e TZ='Europe/Berlin' \
|
||||||
|
--name volvo2mqtt \
|
||||||
|
ghcr.io/dielee/volvo2mqtt:latest
|
||||||
|
</code>
|
||||||
|
</pre>
|
||||||
|
|
||||||
<b>HA Add-On:</b><br>
|
<b>HA Add-On:</b><br>
|
||||||
|
|
||||||
@@ -141,14 +151,15 @@ Here is what every option means:
|
|||||||
| ------------------------- | :-------: | :-----------------------------------: | :----------: | --------------------------------------------------------------- |
|
| ------------------------- | :-------: | :-----------------------------------: | :----------: | --------------------------------------------------------------- |
|
||||||
| `CONF_updateInterval` | `int` | | **required** | Update intervall in seconds. |
|
| `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_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` | `broker` | **required** | Your MQTT Broker IP. Eg. 192.168.0.5.
|
| `CONF_mqtt` | `json` | `broker` | **required** | Your MQTT Broker IP. Eg. 192.168.0.5. |
|
||||||
| `CONF_mqtt` | `json` | `port` | 1883 | Your MQTT Broker Port. If no value is given, port 1883 will be used. |
|
| `CONF_mqtt` | `json` | `port` | 1883 | Your MQTT Broker Port. If no value is given, port 1883 will be used. |
|
||||||
| `CONF_mqtt` | `json` | `username` | optional | MQTT Username for your broker.
|
| `CONF_mqtt` | `json` | `username` | optional | MQTT Username for your broker. |
|
||||||
| `CONF_mqtt` | `json` | `password` | optional | MQTT Password for your broker.
|
| `CONF_mqtt` | `json` | `password` | optional | MQTT Password for your broker. |
|
||||||
| `CONF_volvoData` | `json` | `username` | **required** | Normally your email address to login into the Volvo App.
|
| `CONF_mqtt` | `json` | `logging` | optional | Enable MQTT logging (true/false). Default don't enable MQTT log. |
|
||||||
| `CONF_volvoData` | `json` | `password` | **required** | Your password to login into the Volvo App.
|
| `CONF_volvoData` | `json` | `username` | **required** | Normally your email address to login into the Volvo App. |
|
||||||
| `CONF_volvoData` | `json` | `vin` | optional | A single VIN like "VIN1" or a list of VINs like "["VIN1", "VIN2"]". Leave this empty if you don't know your VIN. The addon will use every car that is tied to your account.
|
| `CONF_volvoData` | `json` | `password` | **required** | Your password to login into the Volvo App. |
|
||||||
| `CONF_volvoData` | `json` | `vccapikey` | **required** | VCCAPIKEY linked with your volvo developer account. Get your Vccapi key from [here](https://developer.volvocars.com/account/). <b>Starting version 1.8.0, it is possible to define multiple keys, like this: `["vccapikey1", "vccapikey2", "vccapikey3", "etc..."]`</b>
|
| `CONF_volvoData` | `json` | `vin` | optional | A single VIN like "VIN1" or a list of VINs like "["VIN1", "VIN2"]". Leave this empty if you don't know your VIN. The addon will use every car that is tied to your account. |
|
||||||
|
| `CONF_volvoData` | `json` | `vccapikey` | **required** | VCCAPIKEY linked with your volvo developer account. Get your Vccapi key from [here](https://developer.volvocars.com/account/). <b>Starting version 1.8.0, it is possible to define multiple keys, like this: `["vccapikey1", "vccapikey2", "vccapikey3", "etc..."]`</b> |
|
||||||
| `CONF_debug` | `string` | | optional | Debug option (true/false). Normally you don't need this. |
|
| `CONF_debug` | `string` | | optional | Debug option (true/false). Normally you don't need this. |
|
||||||
| `TZ` | `string` | | **required** | Container timezone eg "Europe/Berlin" from [here](https://docs.diladele.com/docker/timezones.html)|
|
| `TZ` | `string` | | **required** | Container timezone eg "Europe/Berlin" from [here](https://docs.diladele.com/docker/timezones.html)|
|
||||||
|
|
||||||
@@ -160,6 +171,6 @@ Here is what every option means:
|
|||||||
[i386-shield]: https://img.shields.io/badge/i386-yes-green.svg
|
[i386-shield]: https://img.shields.io/badge/i386-yes-green.svg
|
||||||
[releases]: https://github.com/Dielee/volvo2mqtt/releases
|
[releases]: https://github.com/Dielee/volvo2mqtt/releases
|
||||||
[releases-shield]: https://img.shields.io/github/release/Dielee/volvo2mqtt.svg
|
[releases-shield]: https://img.shields.io/github/release/Dielee/volvo2mqtt.svg
|
||||||
[maintenance-shield]: https://img.shields.io/maintenance/yes/2025.svg
|
[maintenance-shield]: https://img.shields.io/maintenance/yes/2026.svg
|
||||||
[commits-shield]: https://img.shields.io/github/commit-activity/y/Dielee/volvo2mqtt.svg
|
[commits-shield]: https://img.shields.io/github/commit-activity/y/Dielee/volvo2mqtt.svg
|
||||||
[commits]: https://github.com/Dielee/volvo2mqtt/commits/main
|
[commits]: https://github.com/Dielee/volvo2mqtt/commits/main
|
||||||
|
|||||||
+39
-1
@@ -1,10 +1,48 @@
|
|||||||
|
## v1.13.1
|
||||||
|
|
||||||
|
### 🐛 Bug Fixes:
|
||||||
|
|
||||||
|
- Fix missing charging system state `error` #314
|
||||||
|
- Move `object_id` to `default_entity_id` for mqtt entities according to #312
|
||||||
|
|
||||||
|
## v1.13.0
|
||||||
|
|
||||||
|
### 🚀 Features:
|
||||||
|
|
||||||
|
- Move energy api to v2 (https://developer.volvocars.com/apis/energy/v2/release-notes/)
|
||||||
|
|
||||||
|
## v1.12.2
|
||||||
|
|
||||||
|
### 🚀 Features:
|
||||||
|
|
||||||
|
- Add more logging for refresh process
|
||||||
|
- Update refresh token, if volvo API returns a new one
|
||||||
|
|
||||||
|
## v1.12.1
|
||||||
|
|
||||||
|
### 🐛 Bug Fixes:
|
||||||
|
|
||||||
|
- Fix `trip_speed` and `trip_distance` unit for `en_US` and `en_GB` #301
|
||||||
|
|
||||||
|
## v1.12.0
|
||||||
|
|
||||||
|
### 🚀 Features:
|
||||||
|
|
||||||
|
- Some minor corrections and more checking for exceptions #267
|
||||||
|
- Better logging, indicate URL used on API errors, check exceptions in more places to dont kill threads, add option for mqtt logging
|
||||||
|
- Dont force token renew on 5xx errors
|
||||||
|
- Indicate payload on error message to understand better issue #270
|
||||||
|
- Better checking on received update_interval #270
|
||||||
|
- Add TA trip statistics #283
|
||||||
|
|
||||||
|
Thanks to @luka6000 and @ivanfmartinez for your PRs!
|
||||||
|
|
||||||
## v1.10.6
|
## v1.10.6
|
||||||
|
|
||||||
### 🐛 Bug Fixes:
|
### 🐛 Bug Fixes:
|
||||||
|
|
||||||
- Fix missing refresh_token and API changes by volvo. Thanks to @esusxunil.
|
- Fix missing refresh_token and API changes by volvo. Thanks to @esusxunil.
|
||||||
|
|
||||||
|
|
||||||
## v1.10.5
|
## v1.10.5
|
||||||
|
|
||||||
### 🐛 Bug Fixes:
|
### 🐛 Bug Fixes:
|
||||||
|
|||||||
+3
-1
@@ -1,6 +1,6 @@
|
|||||||
name: "Volvo2Mqtt"
|
name: "Volvo2Mqtt"
|
||||||
description: "Volvo AAOS MQTT bridge"
|
description: "Volvo AAOS MQTT bridge"
|
||||||
version: "1.10.6"
|
version: "1.13.1"
|
||||||
slug: "volvo2mqtt"
|
slug: "volvo2mqtt"
|
||||||
init: false
|
init: false
|
||||||
url: "https://github.com/Dielee/volvo2mqtt"
|
url: "https://github.com/Dielee/volvo2mqtt"
|
||||||
@@ -21,6 +21,7 @@ options:
|
|||||||
port: "auto_port"
|
port: "auto_port"
|
||||||
username: "auto_user"
|
username: "auto_user"
|
||||||
password: "auto_password"
|
password: "auto_password"
|
||||||
|
logging: false
|
||||||
volvoData:
|
volvoData:
|
||||||
username: null
|
username: null
|
||||||
password: null
|
password: null
|
||||||
@@ -38,6 +39,7 @@ schema:
|
|||||||
port: str
|
port: str
|
||||||
username: str?
|
username: str?
|
||||||
password: str?
|
password: str?
|
||||||
|
logging: bool
|
||||||
volvoData:
|
volvoData:
|
||||||
username: match(^([a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+)|(\+\d{5,20})$)
|
username: match(^([a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+)|(\+\d{5,20})$)
|
||||||
password: str
|
password: str
|
||||||
|
|||||||
+18
-10
@@ -1,6 +1,6 @@
|
|||||||
from config import settings
|
from config import settings
|
||||||
|
|
||||||
VERSION = "v1.10.6"
|
VERSION = "v1.13.1"
|
||||||
|
|
||||||
OAUTH_TOKEN_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"
|
OAUTH_AUTH_URL = "https://volvoid.eu.volvocars.com/as/authorization.oauth2"
|
||||||
@@ -12,7 +12,7 @@ CLIMATE_STOP_URL = "https://api.volvocars.com/connected-vehicle/v2/vehicles/{0}/
|
|||||||
LOCK_STATE_URL = "https://api.volvocars.com/connected-vehicle/v2/vehicles/{0}/doors"
|
LOCK_STATE_URL = "https://api.volvocars.com/connected-vehicle/v2/vehicles/{0}/doors"
|
||||||
CAR_LOCK_URL = "https://api.volvocars.com/connected-vehicle/v2/vehicles/{0}/commands/lock"
|
CAR_LOCK_URL = "https://api.volvocars.com/connected-vehicle/v2/vehicles/{0}/commands/lock"
|
||||||
CAR_UNLOCK_URL = "https://api.volvocars.com/connected-vehicle/v2/vehicles/{0}/commands/unlock"
|
CAR_UNLOCK_URL = "https://api.volvocars.com/connected-vehicle/v2/vehicles/{0}/commands/unlock"
|
||||||
RECHARGE_STATE_URL = "https://api.volvocars.com/energy/v1/vehicles/{0}/recharge-status"
|
RECHARGE_STATE_URL = "https://api.volvocars.com/energy/v2/vehicles/{0}/state"
|
||||||
ODOMETER_STATE_URL = "https://api.volvocars.com/connected-vehicle/v2/vehicles/{0}/odometer"
|
ODOMETER_STATE_URL = "https://api.volvocars.com/connected-vehicle/v2/vehicles/{0}/odometer"
|
||||||
LOCATION_STATE_URL = "https://api.volvocars.com/location/v1/vehicles/{0}/location"
|
LOCATION_STATE_URL = "https://api.volvocars.com/location/v1/vehicles/{0}/location"
|
||||||
TYRE_STATE_URL = "https://api.volvocars.com/connected-vehicle/v2/vehicles/{0}/tyres"
|
TYRE_STATE_URL = "https://api.volvocars.com/connected-vehicle/v2/vehicles/{0}/tyres"
|
||||||
@@ -41,7 +41,9 @@ units = {
|
|||||||
"odometer": {"unit": LENGTH_MILES},
|
"odometer": {"unit": LENGTH_MILES},
|
||||||
"average_speed": {"unit": SPEED_MILES_PER_HOUR},
|
"average_speed": {"unit": SPEED_MILES_PER_HOUR},
|
||||||
"distance_to_empty": {"unit": LENGTH_MILES},
|
"distance_to_empty": {"unit": LENGTH_MILES},
|
||||||
"distance_to_service": {"unit": LENGTH_MILES}
|
"distance_to_service": {"unit": LENGTH_MILES},
|
||||||
|
"trip_distance": {"unit": LENGTH_MILES},
|
||||||
|
"trip_speed": {"unit": SPEED_MILES_PER_HOUR}
|
||||||
},
|
},
|
||||||
"en_US": {
|
"en_US": {
|
||||||
"divider": 1.60934,
|
"divider": 1.60934,
|
||||||
@@ -49,19 +51,21 @@ units = {
|
|||||||
"odometer": {"unit": LENGTH_MILES},
|
"odometer": {"unit": LENGTH_MILES},
|
||||||
"average_speed": {"unit": SPEED_MILES_PER_HOUR},
|
"average_speed": {"unit": SPEED_MILES_PER_HOUR},
|
||||||
"distance_to_empty": {"unit": LENGTH_MILES},
|
"distance_to_empty": {"unit": LENGTH_MILES},
|
||||||
"distance_to_service": {"unit": LENGTH_MILES}
|
"distance_to_service": {"unit": LENGTH_MILES},
|
||||||
|
"trip_distance": {"unit": LENGTH_MILES},
|
||||||
|
"trip_speed": {"unit": SPEED_MILES_PER_HOUR}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
availability_topic = "volvoAAOS2mqtt/availability"
|
availability_topic = "volvoAAOS2mqtt/availability"
|
||||||
|
|
||||||
charging_system_states = {"CHARGING_SYSTEM_CHARGING": "Charging", "CHARGING_SYSTEM_IDLE": "Idle",
|
charging_system_states = {"CHARGING": "Charging", "IDLE": "Idle",
|
||||||
"CHARGING_SYSTEM_FAULT": "Fault", "CHARGING_SYSTEM_UNSPECIFIED": "Unspecified",
|
"FAULT": "Fault", "UNSPECIFIED": "Unspecified",
|
||||||
"CHARGING_SYSTEM_DONE": "Done", "CHARGING_SYSTEM_SCHEDULED": "Scheduled"}
|
"DONE": "Done", "SCHEDULED": "Scheduled", "ERROR": "Error",
|
||||||
|
"DISCHARGING": "Discharging"}
|
||||||
|
|
||||||
charging_connection_states = {"CONNECTION_STATUS_DISCONNECTED": "Disconnected", "CONNECTION_STATUS_UNSPECIFIED": "Unspecified",
|
charging_connection_states = {"DISCONNECTED": "Disconnected", "UNSPECIFIED": "Unspecified",
|
||||||
"CONNECTION_STATUS_CONNECTED_DC": "Connected DC", "CONNECTION_STATUS_CONNECTED_AC": "Connected AC",
|
"FAULT": "Fault", "CONNECTED": "Connected"}
|
||||||
"CONNECTION_STATUS_FAULT": "Fault"}
|
|
||||||
|
|
||||||
window_states = {"CLOSED": "OFF", "OPEN": "ON", "UNSPECIFIED": "UNKNOWN", "AJAR": "ON"}
|
window_states = {"CLOSED": "OFF", "OPEN": "ON", "UNSPECIFIED": "UNKNOWN", "AJAR": "ON"}
|
||||||
door_states = {"CLOSED": "OFF", "OPEN": "ON", "UNSPECIFIED": "UNKNOWN", "AJAR": "ON"}
|
door_states = {"CLOSED": "OFF", "OPEN": "ON", "UNSPECIFIED": "UNKNOWN", "AJAR": "ON"}
|
||||||
@@ -136,6 +140,10 @@ supported_entities = [
|
|||||||
{"name": "API Backend status", "domain": "sensor", "id": "api_backend_status", "icon": "alert"},
|
{"name": "API Backend status", "domain": "sensor", "id": "api_backend_status", "icon": "alert"},
|
||||||
{"name": "Update Interval", "domain": "number", "id": "update_interval", "unit": "seconds", "icon": "timer", "min": -1, "max": 600, "mode": "box"},
|
{"name": "Update Interval", "domain": "number", "id": "update_interval", "unit": "seconds", "icon": "timer", "min": -1, "max": 600, "mode": "box"},
|
||||||
{"name": "Warnings", "domain": "sensor", "id": "warnings", "icon": "alert", "url": WARNINGS_URL}
|
{"name": "Warnings", "domain": "sensor", "id": "warnings", "icon": "alert", "url": WARNINGS_URL}
|
||||||
|
,{"name": "Trip Fuel Consumption", "domain": "sensor", "id": "trip_fuel_consumption", "unit": VOLUME_LITERS, "icon": "fuel", "url": STATISTICS_URL}
|
||||||
|
,{"name": "Trip distance", "domain": "sensor", "id": "trip_distance", "unit": LENGTH_KILOMETERS if not units.get(settings["babelLocale"]) else units[settings["babelLocale"]]["trip_distance"]["unit"], "icon": "counter", "url": STATISTICS_URL, "state_class":"measurement"}
|
||||||
|
,{"name": "Trip Energy Consumption", "domain": "sensor", "id": "trip_energy_consumption", "unit": ENERGY_KILO_WATT_HOUR, "icon": "car-electric", "url": STATISTICS_URL}
|
||||||
|
,{"name": "Trip Speed", "domain": "sensor", "id": "trip_speed", "unit": SPEED_KILOMETERS_PER_HOUR if not units.get(settings["babelLocale"]) else units[settings["babelLocale"]]["trip_speed"]["unit"], "icon": "speedometer", "url": STATISTICS_URL, "state_class": "measurement"}
|
||||||
]
|
]
|
||||||
|
|
||||||
old_entity_ids = ["months_to_service", "service_warning_trigger", "distance_to_empty"]
|
old_entity_ids = ["months_to_service", "service_warning_trigger", "distance_to_empty"]
|
||||||
|
|||||||
+66
-28
@@ -6,6 +6,7 @@ import volvo
|
|||||||
import util
|
import util
|
||||||
import os
|
import os
|
||||||
import requests
|
import requests
|
||||||
|
import threading
|
||||||
from threading import Thread, Timer
|
from threading import Thread, Timer
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from babel.dates import format_datetime
|
from babel.dates import format_datetime
|
||||||
@@ -27,6 +28,14 @@ def connect():
|
|||||||
client = mqtt.Client("volvoAAOS2mqtt") if os.environ.get("IS_HA_ADDON") \
|
client = mqtt.Client("volvoAAOS2mqtt") if os.environ.get("IS_HA_ADDON") \
|
||||||
else mqtt.Client("volvoAAOS2mqtt_" + settings.volvoData["username"].replace("+", ""))
|
else mqtt.Client("volvoAAOS2mqtt_" + settings.volvoData["username"].replace("+", ""))
|
||||||
|
|
||||||
|
if "logging" in settings["mqtt"] and settings["mqtt"]["logging"]:
|
||||||
|
mqtt_logger = logging.getLogger("mqtt")
|
||||||
|
client.enable_logger(mqtt_logger)
|
||||||
|
|
||||||
|
client.on_message = safe_on_message
|
||||||
|
client.on_disconnect = on_disconnect
|
||||||
|
client.on_connect = on_connect
|
||||||
|
|
||||||
client.will_set(availability_topic, "offline", 0, False)
|
client.will_set(availability_topic, "offline", 0, False)
|
||||||
if settings["mqtt"]["username"] and settings["mqtt"]["password"]:
|
if settings["mqtt"]["username"] and settings["mqtt"]["password"]:
|
||||||
client.username_pw_set(settings["mqtt"]["username"], settings["mqtt"]["password"])
|
client.username_pw_set(settings["mqtt"]["username"], settings["mqtt"]["password"])
|
||||||
@@ -37,11 +46,9 @@ def connect():
|
|||||||
if conf_port > 0:
|
if conf_port > 0:
|
||||||
port = settings["mqtt"]["port"]
|
port = settings["mqtt"]["port"]
|
||||||
client.connect(settings["mqtt"]["broker"], port)
|
client.connect(settings["mqtt"]["broker"], port)
|
||||||
|
|
||||||
client.loop_start()
|
client.loop_start()
|
||||||
client.subscribe("volvoAAOS2mqtt/otp_code")
|
client.subscribe("volvoAAOS2mqtt/otp_code")
|
||||||
client.on_message = on_message
|
|
||||||
client.on_disconnect = on_disconnect
|
|
||||||
client.on_connect = on_connect
|
|
||||||
|
|
||||||
global mqtt_client
|
global mqtt_client
|
||||||
mqtt_client = client
|
mqtt_client = client
|
||||||
@@ -51,7 +58,7 @@ def create_otp_input():
|
|||||||
state_topic = otp_mqtt_topic + "/state"
|
state_topic = otp_mqtt_topic + "/state"
|
||||||
config = {
|
config = {
|
||||||
"name": "Volvo OTP",
|
"name": "Volvo OTP",
|
||||||
"object_id": f"volvo_otp",
|
"default_entity_id": "volvo_otp",
|
||||||
"schema": "state",
|
"schema": "state",
|
||||||
"command_topic": otp_mqtt_topic,
|
"command_topic": otp_mqtt_topic,
|
||||||
"state_topic": state_topic,
|
"state_topic": state_topic,
|
||||||
@@ -88,7 +95,7 @@ def send_car_images(vin, data, device):
|
|||||||
image_topic = f"homeassistant/image/{vin}_{entity['id']}/image_topic"
|
image_topic = f"homeassistant/image/{vin}_{entity['id']}/image_topic"
|
||||||
config = {
|
config = {
|
||||||
"name": entity["name"],
|
"name": entity["name"],
|
||||||
"object_id": f"volvo_{vin}_{entity['id']}",
|
"default_entity_id": f"volvo_{vin}_{entity['id']}",
|
||||||
"schema": "state",
|
"schema": "state",
|
||||||
"icon": "mdi:image-area",
|
"icon": "mdi:image-area",
|
||||||
"content_type": "image/png",
|
"content_type": "image/png",
|
||||||
@@ -128,6 +135,10 @@ def send_car_images(vin, data, device):
|
|||||||
|
|
||||||
|
|
||||||
def on_connect(client, userdata, flags, rc):
|
def on_connect(client, userdata, flags, rc):
|
||||||
|
# set a better name for the mqtt_loop Thread
|
||||||
|
threading.current_thread().name = 'mqtt_thread'
|
||||||
|
logging.info("MQTT connected")
|
||||||
|
|
||||||
send_heartbeat()
|
send_heartbeat()
|
||||||
if len(subscribed_topics) > 0:
|
if len(subscribed_topics) > 0:
|
||||||
for topic in subscribed_topics:
|
for topic in subscribed_topics:
|
||||||
@@ -135,9 +146,16 @@ def on_connect(client, userdata, flags, rc):
|
|||||||
|
|
||||||
|
|
||||||
def on_disconnect(client, userdata, rc):
|
def on_disconnect(client, userdata, rc):
|
||||||
logging.warning("MQTT disconnected, reconnecting automatically")
|
logging.warning(f"MQTT disconnected, reconnecting automatically rc={rc}")
|
||||||
|
|
||||||
|
|
||||||
|
def safe_on_message(client, userdata, msg):
|
||||||
|
try:
|
||||||
|
on_message(client, userdata, msg)
|
||||||
|
except Exception as error:
|
||||||
|
logging.error(f"Exception {error} processing received message on topic {msg.topic}")
|
||||||
|
return None
|
||||||
|
|
||||||
def on_message(client, userdata, msg):
|
def on_message(client, userdata, msg):
|
||||||
payload = msg.payload.decode("UTF-8")
|
payload = msg.payload.decode("UTF-8")
|
||||||
if msg.topic == otp_mqtt_topic:
|
if msg.topic == otp_mqtt_topic:
|
||||||
@@ -152,40 +170,56 @@ def on_message(client, userdata, msg):
|
|||||||
try:
|
try:
|
||||||
vin = msg.topic.split('/')[2].split('_')[0]
|
vin = msg.topic.split('/')[2].split('_')[0]
|
||||||
except IndexError:
|
except IndexError:
|
||||||
logging.error("Error - Cannot get vin from MQTT topic!")
|
logging.error(f"Error - Cannot get vin from MQTT topic : {msg.topic}!")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
if "climate_status" in msg.topic:
|
if "climate_status" in msg.topic:
|
||||||
if payload == "ON":
|
if payload == "ON":
|
||||||
start_climate(vin)
|
start_climate(vin)
|
||||||
|
return None
|
||||||
elif payload == "OFF":
|
elif payload == "OFF":
|
||||||
stop_climate(vin)
|
stop_climate(vin)
|
||||||
|
return None
|
||||||
|
return None
|
||||||
elif "lock_status" in msg.topic:
|
elif "lock_status" in msg.topic:
|
||||||
if payload == "LOCK":
|
if payload == "LOCK":
|
||||||
lock_car(vin)
|
lock_car(vin)
|
||||||
|
return None
|
||||||
elif payload == "UNLOCK":
|
elif payload == "UNLOCK":
|
||||||
unlock_car(vin)
|
unlock_car(vin)
|
||||||
|
return None
|
||||||
|
return None
|
||||||
elif "update_data" in msg.topic:
|
elif "update_data" in msg.topic:
|
||||||
if payload == "PRESS":
|
if payload == "PRESS":
|
||||||
update_car_data(True)
|
update_car_data(True)
|
||||||
|
return None
|
||||||
|
return None
|
||||||
elif "update_interval" in msg.topic:
|
elif "update_interval" in msg.topic:
|
||||||
update_interval = int(payload)
|
try:
|
||||||
if update_interval >= 60 or update_interval == -1:
|
update_interval = int(payload)
|
||||||
settings.update({"updateInterval": int(update_interval)})
|
if update_interval >= 60 or update_interval == -1:
|
||||||
else:
|
settings.update({"updateInterval": int(update_interval)})
|
||||||
logging.warning("Interval " + str(update_interval) + " seconds is to low. Doing nothing!")
|
else:
|
||||||
update_car_data()
|
logging.warning(f"Interval {update_interval} seconds is to low. Doing nothing!")
|
||||||
|
update_car_data()
|
||||||
|
return None
|
||||||
|
except ValueError as error:
|
||||||
|
logging.error(f"Unable to change update_interval {error} payload={payload}")
|
||||||
|
return None
|
||||||
elif "schedule" in msg.topic:
|
elif "schedule" in msg.topic:
|
||||||
try:
|
try:
|
||||||
d = json.loads(payload)
|
d = json.loads(payload)
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
logging.error("Can't set timer. Error: " + str(e))
|
logging.error(f"Can't set timer. Error: {e}, payload={payload}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
if d["mode"] == "timer":
|
if d["mode"] == "timer":
|
||||||
start_climate_timer(d, vin)
|
start_climate_timer(d, vin)
|
||||||
|
return None
|
||||||
else:
|
else:
|
||||||
logging.warning("No schedule mode found, doing nothing")
|
logging.warning("No schedule mode found, doing nothing")
|
||||||
|
return None
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def start_climate_timer(d, vin):
|
def start_climate_timer(d, vin):
|
||||||
@@ -197,13 +231,13 @@ def start_climate_timer(d, vin):
|
|||||||
start_datetime = local_datetime.replace(hour=hour, minute=minute, second=0)
|
start_datetime = local_datetime.replace(hour=hour, minute=minute, second=0)
|
||||||
timer_seconds = (start_datetime - local_datetime).total_seconds()
|
timer_seconds = (start_datetime - local_datetime).total_seconds()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error("Error creating climate timer: " + str(e))
|
logging.error(f"Error creating climate timer: {e}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
if timer_seconds > 0:
|
if timer_seconds > 0:
|
||||||
Timer(timer_seconds, activate_climate_timer, (vin, start_datetime.isoformat(),)).start()
|
Timer(timer_seconds, activate_climate_timer, (vin, start_datetime.isoformat(),)).start()
|
||||||
active_schedules[vin]["timers"].append(start_datetime.isoformat())
|
active_schedules[vin]["timers"].append(start_datetime.isoformat())
|
||||||
logging.debug("Climate timer set to " + str(start_datetime))
|
logging.debug(f"Climate timer set to {start_datetime}")
|
||||||
update_car_data()
|
update_car_data()
|
||||||
else:
|
else:
|
||||||
logging.warning("Timer can not be set. Unusable start time entered")
|
logging.warning("Timer can not be set. Unusable start time entered")
|
||||||
@@ -211,28 +245,28 @@ def start_climate_timer(d, vin):
|
|||||||
|
|
||||||
def unlock_car(vin):
|
def unlock_car(vin):
|
||||||
# Start the api call in another thread for HA performance
|
# Start the api call in another thread for HA performance
|
||||||
Thread(target=volvo.api_call, args=(CAR_UNLOCK_URL, "POST", vin)).start()
|
Thread(target=volvo.api_call, args=(CAR_UNLOCK_URL, "POST", vin), name="unlock_car_thread").start()
|
||||||
|
|
||||||
# Force set unlocking state
|
# Force set unlocking state
|
||||||
update_car_data(False, {"entity_id": "lock_status", "vin": vin, "state": "UNLOCKING"})
|
update_car_data(False, {"entity_id": "lock_status", "vin": vin, "state": "UNLOCKING"})
|
||||||
# Fetch API lock state until unlocking finished
|
# Fetch API lock state until unlocking finished
|
||||||
Thread(target=volvo.check_lock_status, args=(vin, "LOCKED")).start()
|
Thread(target=volvo.check_lock_status, args=(vin, "LOCKED"), name="check_lock_status_thread").start()
|
||||||
|
|
||||||
|
|
||||||
def lock_car(vin):
|
def lock_car(vin):
|
||||||
# Start the api call in another thread for HA performance
|
# Start the api call in another thread for HA performance
|
||||||
Thread(target=volvo.api_call, args=(CAR_LOCK_URL, "POST", vin)).start()
|
Thread(target=volvo.api_call, args=(CAR_LOCK_URL, "POST", vin), name="lock_car_thread").start()
|
||||||
|
|
||||||
# Force set locking state
|
# Force set locking state
|
||||||
update_car_data(False, {"entity_id": "lock_status", "vin": vin, "state": "LOCKING"})
|
update_car_data(False, {"entity_id": "lock_status", "vin": vin, "state": "LOCKING"})
|
||||||
# Fetch API lock state until locking finished
|
# Fetch API lock state until locking finished
|
||||||
Thread(target=volvo.check_lock_status, args=(vin, "UNLOCKED")).start()
|
Thread(target=volvo.check_lock_status, args=(vin, "UNLOCKED"), name="check_lock_status_thread").start()
|
||||||
|
|
||||||
|
|
||||||
def stop_climate(vin):
|
def stop_climate(vin):
|
||||||
global assumed_climate_state, climate_timer, engine_status
|
global assumed_climate_state, climate_timer, engine_status
|
||||||
# Start the api call in another thread for HA performance
|
# Start the api call in another thread for HA performance
|
||||||
Thread(target=volvo.api_call, args=(CLIMATE_STOP_URL, "POST", vin)).start()
|
Thread(target=volvo.api_call, args=(CLIMATE_STOP_URL, "POST", vin), name="stop_climate_thread").start()
|
||||||
|
|
||||||
# Stop door check thread if running
|
# Stop door check thread if running
|
||||||
if engine_status[vin].is_alive():
|
if engine_status[vin].is_alive():
|
||||||
@@ -256,10 +290,10 @@ def activate_climate_timer(vin, start_time):
|
|||||||
def start_climate(vin):
|
def start_climate(vin):
|
||||||
global assumed_climate_state, climate_timer, engine_status
|
global assumed_climate_state, climate_timer, engine_status
|
||||||
# Start the api call in another thread for HA performance
|
# Start the api call in another thread for HA performance
|
||||||
Thread(target=volvo.api_call, args=(CLIMATE_START_URL, "POST", vin)).start()
|
Thread(target=volvo.api_call, args=(CLIMATE_START_URL, "POST", vin), name="start_climate_thread").start()
|
||||||
|
|
||||||
# Start door check thread to turn off climate if driver door is opened
|
# Start door check thread to turn off climate if driver door is opened
|
||||||
check_engine_thread = Thread(target=volvo.check_engine_status, args=(vin,))
|
check_engine_thread = Thread(target=volvo.check_engine_status, args=(vin,), name="check_engine_status_thread")
|
||||||
check_engine_thread.start()
|
check_engine_thread.start()
|
||||||
engine_status[vin] = check_engine_thread
|
engine_status[vin] = check_engine_thread
|
||||||
|
|
||||||
@@ -277,9 +311,12 @@ def update_loop():
|
|||||||
while True:
|
while True:
|
||||||
if settings["updateInterval"] > 0:
|
if settings["updateInterval"] > 0:
|
||||||
logging.info("Sending mqtt update...")
|
logging.info("Sending mqtt update...")
|
||||||
send_heartbeat()
|
try:
|
||||||
update_car_data()
|
send_heartbeat()
|
||||||
logging.info("Mqtt update done. Next run in " + str(settings["updateInterval"]) + " seconds.")
|
update_car_data()
|
||||||
|
logging.info(f"Mqtt update done. Next run in {settings['updateInterval']} seconds.")
|
||||||
|
except Exception as error:
|
||||||
|
logging.info(f"Exception {error} in Mqtt update. Next run in {settings['updateInterval']} seconds.")
|
||||||
time.sleep(settings["updateInterval"])
|
time.sleep(settings["updateInterval"])
|
||||||
else:
|
else:
|
||||||
logging.info("Data update is disabled, doing nothing for 30 seconds")
|
logging.info("Data update is disabled, doing nothing for 30 seconds")
|
||||||
@@ -338,6 +375,7 @@ def update_car_data(force_update=False, overwrite={}):
|
|||||||
else:
|
else:
|
||||||
topic = f"homeassistant/{entity['domain']}/{vin}_{entity['id']}/state"
|
topic = f"homeassistant/{entity['domain']}/{vin}_{entity['id']}/state"
|
||||||
|
|
||||||
|
logging.info(f"update_car_data {topic} {state}")
|
||||||
if state or state == 0:
|
if state or state == 0:
|
||||||
mqtt_client.publish(
|
mqtt_client.publish(
|
||||||
topic,
|
topic,
|
||||||
@@ -367,7 +405,7 @@ def update_ha_device(entity, vin, state):
|
|||||||
logging.debug("Updating icon to " + icon + " for " + entity["id"])
|
logging.debug("Updating icon to " + icon + " for " + entity["id"])
|
||||||
config = {
|
config = {
|
||||||
"name": entity['name'],
|
"name": entity['name'],
|
||||||
"object_id": f"volvo_{vin}_{entity['id']}",
|
"default_entity_id": f"volvo_{vin}_{entity['id']}",
|
||||||
"schema": "state",
|
"schema": "state",
|
||||||
"icon": f"mdi:{icon}" if icon else f"mdi:{entity['icon']}",
|
"icon": f"mdi:{icon}" if icon else f"mdi:{entity['icon']}",
|
||||||
"state_topic": f"homeassistant/{entity['domain']}/{vin}_{entity['id']}/state",
|
"state_topic": f"homeassistant/{entity['domain']}/{vin}_{entity['id']}/state",
|
||||||
@@ -408,7 +446,7 @@ def create_ha_devices():
|
|||||||
for entity in volvo.supported_endpoints[vin]:
|
for entity in volvo.supported_endpoints[vin]:
|
||||||
config = {
|
config = {
|
||||||
"name": entity['name'],
|
"name": entity['name'],
|
||||||
"object_id": f"volvo_{vin}_{entity['id']}",
|
"default_entity_id": f"volvo_{vin}_{entity['id']}",
|
||||||
"schema": "state",
|
"schema": "state",
|
||||||
"icon": f"mdi:{entity['icon']}",
|
"icon": f"mdi:{entity['icon']}",
|
||||||
"state_topic": f"homeassistant/{entity['domain']}/{vin}_{entity['id']}/state",
|
"state_topic": f"homeassistant/{entity['domain']}/{vin}_{entity['id']}/state",
|
||||||
|
|||||||
+2
-1
@@ -8,7 +8,8 @@
|
|||||||
"broker": "",
|
"broker": "",
|
||||||
"port": 1883,
|
"port": 1883,
|
||||||
"username": "",
|
"username": "",
|
||||||
"password": ""
|
"password": "",
|
||||||
|
"logging": false
|
||||||
},
|
},
|
||||||
"volvoData": {
|
"volvoData": {
|
||||||
"username": "",
|
"username": "",
|
||||||
|
|||||||
+1
-1
@@ -71,7 +71,7 @@ def setup_logging():
|
|||||||
logging.Formatter.converter = lambda *args: datetime.now(tz=TZ).timetuple()
|
logging.Formatter.converter = lambda *args: datetime.now(tz=TZ).timetuple()
|
||||||
file_log_handler = logging.handlers.RotatingFileHandler(log_location, maxBytes=1000000, backupCount=1)
|
file_log_handler = logging.handlers.RotatingFileHandler(log_location, maxBytes=1000000, backupCount=1)
|
||||||
formatter = logging.Formatter(
|
formatter = logging.Formatter(
|
||||||
'%(asctime)s volvo2mqtt [%(process)d] - %(levelname)s: %(message)s',
|
'%(asctime)s volvo2mqtt [%(threadName)s] [%(process)d] - %(levelname)s: %(message)s',
|
||||||
'%b %d %H:%M:%S')
|
'%b %d %H:%M:%S')
|
||||||
file_log_handler.setFormatter(formatter)
|
file_log_handler.setFormatter(formatter)
|
||||||
|
|
||||||
|
|||||||
+258
-197
@@ -47,6 +47,11 @@ def authorize(renew_tokenfile=False):
|
|||||||
except ValueError:
|
except ValueError:
|
||||||
logging.warning("Detected corrupted token file, restarting auth process")
|
logging.warning("Detected corrupted token file, restarting auth process")
|
||||||
authorize(True)
|
authorize(True)
|
||||||
|
return
|
||||||
|
except KeyError:
|
||||||
|
logging.warning("Token file missing refresh_token, restarting auth process")
|
||||||
|
authorize(True)
|
||||||
|
return
|
||||||
else:
|
else:
|
||||||
logging.info("Starting login with OTP")
|
logging.info("Starting login with OTP")
|
||||||
auth_session = requests.session()
|
auth_session = requests.session()
|
||||||
@@ -105,7 +110,7 @@ def authorize(renew_tokenfile=False):
|
|||||||
get_vcc_api_keys()
|
get_vcc_api_keys()
|
||||||
get_vehicles()
|
get_vehicles()
|
||||||
check_supported_endpoints()
|
check_supported_endpoints()
|
||||||
Thread(target=backend_status_loop).start()
|
Thread(target=backend_status_loop,name="backend_status_thread").start()
|
||||||
|
|
||||||
def continue_auth(auth_session, data):
|
def continue_auth(auth_session, data):
|
||||||
next_url = data["_links"]["continueAuthentication"]["href"].replace("http://", "https://") + "?action=continueAuthentication"
|
next_url = data["_links"]["continueAuthentication"]["href"].replace("http://", "https://") + "?action=continueAuthentication"
|
||||||
@@ -170,8 +175,9 @@ def send_otp(auth_session, data):
|
|||||||
|
|
||||||
|
|
||||||
def refresh_auth():
|
def refresh_auth():
|
||||||
logging.info("Refreshing credentials")
|
|
||||||
global refresh_token
|
global refresh_token
|
||||||
|
logging.info("Refreshing credentials with token " + refresh_token)
|
||||||
|
|
||||||
headers = {
|
headers = {
|
||||||
"authorization": "Basic aDRZZjBiOlU4WWtTYlZsNnh3c2c1WVFxWmZyZ1ZtSWFEcGhPc3kxUENhVXNpY1F0bzNUUjVrd2FKc2U0QVpkZ2ZJZmNMeXc=",
|
"authorization": "Basic aDRZZjBiOlU4WWtTYlZsNnh3c2c1WVFxWmZyZ1ZtSWFEcGhPc3kxUENhVXNpY1F0bzNUUjVrd2FKc2U0QVpkZ2ZJZmNMeXc=",
|
||||||
"content-type": "application/x-www-form-urlencoded",
|
"content-type": "application/x-www-form-urlencoded",
|
||||||
@@ -191,24 +197,35 @@ def refresh_auth():
|
|||||||
|
|
||||||
if auth.status_code == 200:
|
if auth.status_code == 200:
|
||||||
token_path = util.get_token_path()
|
token_path = util.get_token_path()
|
||||||
|
if os.path.exists(token_path):
|
||||||
|
with open(token_path) as f:
|
||||||
|
data = json.load(f)
|
||||||
|
|
||||||
refresh_data = auth.json()
|
refresh_data = auth.json()
|
||||||
|
|
||||||
f = open(token_path)
|
if not "refresh_token" in refresh_data:
|
||||||
try:
|
f = open(token_path)
|
||||||
data = json.load(f)
|
try:
|
||||||
refresh_token = data["refresh_token"]
|
data = json.load(f)
|
||||||
except ValueError:
|
refresh_data["refresh_token"] = data["refresh_token"]
|
||||||
raise Exception("Cannot refresh token!")
|
except ValueError:
|
||||||
|
raise Exception("Cannot refresh token!")
|
||||||
|
else:
|
||||||
|
logging.info("New refresh token found, updating!")
|
||||||
|
|
||||||
refresh_data["refresh_token"] = refresh_token
|
|
||||||
util.save_to_json(refresh_data, token_path)
|
util.save_to_json(refresh_data, token_path)
|
||||||
session.headers.update({"authorization": "Bearer " + refresh_data["access_token"]})
|
session.headers.update({"authorization": "Bearer " + refresh_data["access_token"]})
|
||||||
|
|
||||||
global token_expires_at
|
global token_expires_at
|
||||||
token_expires_at = datetime.now(util.TZ) + timedelta(seconds=(refresh_data["expires_in"] - 30))
|
token_expires_at = datetime.now(util.TZ) + timedelta(seconds=(data["expires_in"] - 30))
|
||||||
refresh_token = refresh_data["refresh_token"]
|
refresh_token = data["refresh_token"]
|
||||||
|
return None
|
||||||
|
elif int(auth.status_code / 100) == 5:
|
||||||
|
# Server error try again later
|
||||||
|
logging.warning("Refreshing credentials failed!: " + str(auth.status_code) + " Message: " + auth.text)
|
||||||
return None
|
return None
|
||||||
else:
|
else:
|
||||||
|
# TODO check other codes that dont have to force the renew_tokenfile
|
||||||
logging.warning("Refreshing credentials failed!: " + str(auth.status_code) + " Message: " + auth.text)
|
logging.warning("Refreshing credentials failed!: " + str(auth.status_code) + " Message: " + auth.text)
|
||||||
authorize(renew_tokenfile=True)
|
authorize(renew_tokenfile=True)
|
||||||
return None
|
return None
|
||||||
@@ -445,31 +462,44 @@ def check_engine_status(vin):
|
|||||||
mqtt.assumed_climate_state[vin] = "OFF"
|
mqtt.assumed_climate_state[vin] = "OFF"
|
||||||
mqtt.update_car_data()
|
mqtt.update_car_data()
|
||||||
break
|
break
|
||||||
|
|
||||||
time.sleep(5)
|
time.sleep(5)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def backend_status_loop():
|
def backend_status_loop():
|
||||||
|
logging.debug("backend_status_loop started")
|
||||||
while True:
|
while True:
|
||||||
get_backend_status()
|
last_status = get_backend_status()
|
||||||
# Update every hour
|
# Update every hour when not error, 10 minutes when got server error
|
||||||
time.sleep(3600)
|
time.sleep(3600 if not last_status.startswith("APIERR_") else 600)
|
||||||
|
|
||||||
|
|
||||||
def get_backend_status():
|
def get_backend_status():
|
||||||
global backend_status
|
global backend_status
|
||||||
response = session.get(API_BACKEND_STATUS, timeout=15)
|
|
||||||
try:
|
try:
|
||||||
data = response.json()
|
response = session.get(API_BACKEND_STATUS, timeout=15)
|
||||||
if util.keys_exists(data, "message"):
|
try:
|
||||||
if data["message"]:
|
data = response.json()
|
||||||
backend_status = data["message"]
|
if util.keys_exists(data, "message"):
|
||||||
|
if data["message"]:
|
||||||
|
backend_status = data["message"]
|
||||||
|
else:
|
||||||
|
backend_status = "NO_WARNING"
|
||||||
else:
|
else:
|
||||||
backend_status = "NO_WARNING"
|
backend_status = "NO_WARNING"
|
||||||
else:
|
|
||||||
backend_status = "NO_WARNING"
|
|
||||||
|
|
||||||
except JSONDecodeError as e:
|
except JSONDecodeError as e:
|
||||||
backend_status = "NO_WARNING"
|
logging.error(f"Invalid json response from API Backend Status - {response} - {e}")
|
||||||
|
backend_status = "APIERR_INVALID_JSON_RESPONSE"
|
||||||
|
|
||||||
|
except requests.exceptions.ReadTimeout as e:
|
||||||
|
logging.error(f"API Backend Status - timeout - {e}")
|
||||||
|
backend_status = "APIERR_TIMEOUT"
|
||||||
|
except requests.exceptions.RequestException as e:
|
||||||
|
logging.error(f"API Backend Status - Error - {e}")
|
||||||
|
backend_status = "APIERR_REQUEST_ERROR"
|
||||||
|
|
||||||
return backend_status
|
return backend_status
|
||||||
|
|
||||||
|
|
||||||
@@ -485,28 +515,28 @@ def api_call(url, method, vin, sensor_id=None, force_update=False, key_change=Fa
|
|||||||
# Exception caught while getting data from volvo api, doing nothing
|
# Exception caught while getting data from volvo api, doing nothing
|
||||||
return None
|
return None
|
||||||
elif method == "GET":
|
elif method == "GET":
|
||||||
logging.debug("Starting " + method + " call against " + url)
|
logging.debug(f"Starting {method} call against {url}")
|
||||||
try:
|
try:
|
||||||
response = session.get(url.format(vin), timeout=15)
|
response = session.get(url.format(vin), timeout=15)
|
||||||
except requests.exceptions.RequestException as e:
|
except requests.exceptions.RequestException as e:
|
||||||
logging.error("Error getting data: " + str(e))
|
logging.error(f"Error getting data: {e} from {url}")
|
||||||
return None
|
return None
|
||||||
elif method == "POST":
|
elif method == "POST":
|
||||||
logging.debug("Starting " + method + " call against " + url)
|
logging.debug(f"Starting {method} call against {url}")
|
||||||
try:
|
try:
|
||||||
response = session.post(url.format(vin), timeout=20)
|
response = session.post(url.format(vin), timeout=20)
|
||||||
except requests.exceptions.RequestException as e:
|
except requests.exceptions.RequestException as e:
|
||||||
logging.error("Error getting data: " + str(e))
|
logging.error(f"Error getting data from {url} : {e}")
|
||||||
return None
|
return None
|
||||||
else:
|
else:
|
||||||
logging.error("Unkown method posted: " + method + ". Returning nothing")
|
logging.error(f"Unkown method posted: {method}. Returning nothing")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
logging.debug("Response status code: " + str(response.status_code))
|
logging.debug(f"Response status code: {response.status_code}")
|
||||||
try:
|
try:
|
||||||
data = response.json()
|
data = response.json()
|
||||||
except JSONDecodeError as e:
|
except JSONDecodeError as e:
|
||||||
logging.error("Fetched json decode error, Volvo API seems to return garbage. Skipping update. Error: " + str(e))
|
logging.error(f"Fetched json decode error, Volvo API seems to return garbage. Skipping update. Error: {e}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
@@ -529,9 +559,9 @@ def api_call(url, method, vin, sensor_id=None, force_update=False, key_change=Fa
|
|||||||
api_call(url, method, vin, sensor_id, force_update, True)
|
api_call(url, method, vin, sensor_id, force_update, True)
|
||||||
else:
|
else:
|
||||||
logging.error(
|
logging.error(
|
||||||
"API Call failed. Status Code: " + str(response.status_code) + ". Error: " + response.text)
|
f"API Call failed to {url}. Status Code: {response.status_code}. Error: {response.text}")
|
||||||
else:
|
else:
|
||||||
logging.error("API Call failed. Status Code: " + str(response.status_code) + ". Error: " + response.text)
|
logging.error(f"API Call failed to {url}. Status Code: {response.status_code}. Error: {response.text}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
@@ -539,11 +569,11 @@ def cached_request(url, method, vin, force_update=False, key_change=False):
|
|||||||
global cached_requests
|
global cached_requests
|
||||||
if not util.keys_exists(cached_requests, vin + "_" + url):
|
if not util.keys_exists(cached_requests, vin + "_" + url):
|
||||||
# No API Data cached, get fresh data from API
|
# No API Data cached, get fresh data from API
|
||||||
logging.debug("Starting " + method + " call against " + url)
|
logging.debug(f"Starting {method} call against {url}")
|
||||||
try:
|
try:
|
||||||
response = session.get(url.format(vin), timeout=15)
|
response = session.get(url.format(vin), timeout=15)
|
||||||
except requests.exceptions.RequestException as e:
|
except requests.exceptions.RequestException as e:
|
||||||
logging.error("Error getting data: " + str(e))
|
logging.error(f"Error getting data from {url}: {e}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
data = {"response": response, "last_update": datetime.now(util.TZ)}
|
data = {"response": response, "last_update": datetime.now(util.TZ)}
|
||||||
@@ -555,11 +585,11 @@ def cached_request(url, method, vin, force_update=False, key_change=False):
|
|||||||
"last_update"]).total_seconds() >= 2) \
|
"last_update"]).total_seconds() >= 2) \
|
||||||
or key_change:
|
or key_change:
|
||||||
# Old Data in Cache, or force mode active, updating
|
# Old Data in Cache, or force mode active, updating
|
||||||
logging.debug("Starting " + method + " call against " + url)
|
logging.debug(f"Starting {method} call against {url}")
|
||||||
try:
|
try:
|
||||||
response = session.get(url.format(vin), timeout=15)
|
response = session.get(url.format(vin), timeout=15)
|
||||||
except requests.exceptions.RequestException as e:
|
except requests.exceptions.RequestException as e:
|
||||||
logging.error("Error getting data: " + str(e))
|
logging.error(f"Error getting data from {url} : {e}")
|
||||||
return None
|
return None
|
||||||
data = {"response": response, "last_update": datetime.now(util.TZ)}
|
data = {"response": response, "last_update": datetime.now(util.TZ)}
|
||||||
cached_requests[vin + "_" + url] = data
|
cached_requests[vin + "_" + url] = data
|
||||||
@@ -570,171 +600,202 @@ def cached_request(url, method, vin, force_update=False, key_change=False):
|
|||||||
|
|
||||||
|
|
||||||
def parse_api_data(data, sensor_id=None):
|
def parse_api_data(data, sensor_id=None):
|
||||||
if sensor_id != "api_backend_status":
|
if sensor_id in ["battery_charge_level", "electric_range", "charging_system_status",
|
||||||
data = data["data"]
|
"charging_connection_status", "estimated_charging_time", "estimated_charging_finish_time"]:
|
||||||
if sensor_id == "battery_charge_level":
|
|
||||||
return data["batteryChargeLevel"]["value"] if util.keys_exists(data, "batteryChargeLevel") else None
|
if sensor_id == "battery_charge_level":
|
||||||
elif sensor_id == "battery_capacity":
|
return data["batteryChargeLevel"]["value"] if util.keys_exists(data, "batteryChargeLevel") else None
|
||||||
return data["batteryCapacityKWH"] if util.keys_exists(data, "batteryCapacityKWH") else None
|
elif sensor_id == "electric_range":
|
||||||
elif sensor_id == "electric_range":
|
return util.convert_metric_values(data["electricRange"]["value"]) \
|
||||||
return util.convert_metric_values(data["electricRange"]["value"]) \
|
if util.keys_exists(data, "electricRange") else None
|
||||||
if util.keys_exists(data, "electricRange") else None
|
elif sensor_id == "charging_system_status":
|
||||||
elif sensor_id == "charging_system_status":
|
return charging_system_states[data["chargingStatus"]["value"]] \
|
||||||
return charging_system_states[data["chargingSystemStatus"]["value"]] \
|
if util.keys_exists(data, "chargingStatus") else None
|
||||||
if util.keys_exists(data, "chargingSystemStatus") else None
|
elif sensor_id == "charging_connection_status":
|
||||||
elif sensor_id == "charging_connection_status":
|
return charging_connection_states[data["chargerConnectionStatus"]["value"]] \
|
||||||
return charging_connection_states[data["chargingConnectionStatus"]["value"]] \
|
if util.keys_exists(data, "chargerConnectionStatus") else None
|
||||||
if util.keys_exists(data, "chargingConnectionStatus") else None
|
elif sensor_id == "estimated_charging_time":
|
||||||
elif sensor_id == "estimated_charging_time":
|
if util.keys_exists(data, "chargingStatus"):
|
||||||
if util.keys_exists(data, "chargingSystemStatus"):
|
charging_system_state = charging_system_states[data["chargingStatus"]["value"]]
|
||||||
charging_system_state = charging_system_states[data["chargingSystemStatus"]["value"]]
|
if charging_system_state == "Charging":
|
||||||
if charging_system_state == "Charging":
|
return data["estimatedChargingTimeToTargetBatteryChargeLevel"]["value"] if util.keys_exists(data,
|
||||||
return data["estimatedChargingTime"]["value"] if util.keys_exists(data, "estimatedChargingTime") else ""
|
"estimatedChargingTimeToTargetBatteryChargeLevel") else ""
|
||||||
|
else:
|
||||||
|
return 0
|
||||||
|
return None
|
||||||
|
elif sensor_id == "estimated_charging_finish_time":
|
||||||
|
if util.keys_exists(data, "estimatedChargingTimeToTargetBatteryChargeLevel"):
|
||||||
|
charging_system_state = charging_system_states[data["chargingStatus"]["value"]]
|
||||||
|
if charging_system_state == "Charging":
|
||||||
|
charging_time = int(
|
||||||
|
data["estimatedChargingTimeToTargetBatteryChargeLevel"]["value"] if util.keys_exists(data, "estimatedChargingTimeToTargetBatteryChargeLevel")
|
||||||
|
else 0)
|
||||||
|
charging_finished = datetime.now(util.TZ) + timedelta(minutes=charging_time)
|
||||||
|
return format_datetime(charging_finished, format="medium", locale=settings["babelLocale"])
|
||||||
|
else:
|
||||||
|
return ""
|
||||||
|
return None
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
if sensor_id != "api_backend_status":
|
||||||
|
data = data["data"]
|
||||||
|
|
||||||
|
if sensor_id == "lock_status":
|
||||||
|
return data["centralLock"]["value"] if util.keys_exists(data, "centralLock") else None
|
||||||
|
elif sensor_id == "battery_capacity":
|
||||||
|
return data["batteryCapacityKWH"] if util.keys_exists(data, "batteryCapacityKWH") else None
|
||||||
|
elif sensor_id == "odometer":
|
||||||
|
return util.convert_metric_values(int(data["odometer"]["value"])) \
|
||||||
|
if util.keys_exists(data, "odometer") else None
|
||||||
|
elif sensor_id == "window_front_left":
|
||||||
|
return window_states[data["frontLeftWindow"]["value"]] if util.keys_exists(data, "frontLeftWindow") \
|
||||||
|
else None
|
||||||
|
elif sensor_id == "window_front_right":
|
||||||
|
return window_states[data["frontRightWindow"]["value"]] if util.keys_exists(data, "frontRightWindow") \
|
||||||
|
else None
|
||||||
|
elif sensor_id == "window_rear_left":
|
||||||
|
return window_states[data["rearLeftWindow"]["value"]] if util.keys_exists(data, "rearLeftWindow") \
|
||||||
|
else None
|
||||||
|
elif sensor_id == "window_rear_right":
|
||||||
|
return window_states[data["rearRightWindow"]["value"]] if util.keys_exists(data, "rearRightWindow") \
|
||||||
|
else None
|
||||||
|
elif sensor_id == "door_front_left":
|
||||||
|
return door_states[data["frontLeftDoor"]["value"]] if util.keys_exists(data, "frontLeftDoor") else None
|
||||||
|
elif sensor_id == "door_front_right":
|
||||||
|
return door_states[data["frontRightDoor"]["value"]] \
|
||||||
|
if util.keys_exists(data, "frontRightDoor") else None
|
||||||
|
elif sensor_id == "door_rear_left":
|
||||||
|
return door_states[data["rearLeftDoor"]["value"]] if util.keys_exists(data, "rearLeftDoor") else None
|
||||||
|
elif sensor_id == "door_rear_right":
|
||||||
|
return door_states[data["rearRightDoor"]["value"]] if util.keys_exists(data, "rearRightDoor") else None
|
||||||
|
elif sensor_id == "tailgate":
|
||||||
|
return door_states[data["tailgate"]["value"]] if util.keys_exists(data, "tailgate") else None
|
||||||
|
elif sensor_id == "sunroof":
|
||||||
|
return door_states[data["sunroof"]["value"]] if util.keys_exists(data, "sunroof") else None
|
||||||
|
elif sensor_id == "engine_hood":
|
||||||
|
return door_states[data["hood"]["value"]] if util.keys_exists(data, "hood") else None
|
||||||
|
elif sensor_id == "tank_lid":
|
||||||
|
return door_states[data["tankLid"]["value"]] if util.keys_exists(data, "tankLid") else None
|
||||||
|
elif sensor_id == "tyre_front_left":
|
||||||
|
return data["frontLeft"]["value"] if util.keys_exists(data, "frontLeft") else None
|
||||||
|
elif sensor_id == "tyre_front_right":
|
||||||
|
return data["frontRight"]["value"] if util.keys_exists(data, "frontRight") else None
|
||||||
|
elif sensor_id == "tyre_rear_left":
|
||||||
|
return data["rearLeft"]["value"] if util.keys_exists(data, "rearLeft") else None
|
||||||
|
elif sensor_id == "tyre_rear_right":
|
||||||
|
return data["rearRight"]["value"] if util.keys_exists(data, "rearRight") else None
|
||||||
|
elif sensor_id == "engine_state":
|
||||||
|
return engine_states[data["engineStatus"]["value"]] if util.keys_exists(data, "engineStatus") else None
|
||||||
|
elif sensor_id == "fuel_level":
|
||||||
|
if util.keys_exists(data, "fuelAmount"):
|
||||||
|
fuel_amount = float(data["fuelAmount"]["value"])
|
||||||
|
if fuel_amount > 0:
|
||||||
|
return fuel_amount
|
||||||
|
return None
|
||||||
|
elif sensor_id == "average_fuel_consumption":
|
||||||
|
average_fuel_con = 0
|
||||||
|
if util.keys_exists(data, "averageFuelConsumption"):
|
||||||
|
average_fuel_con = float(data["averageFuelConsumption"]["value"])
|
||||||
|
elif util.keys_exists(data, "averageFuelConsumptionAutomatic"):
|
||||||
|
average_fuel_con = float(data["averageFuelConsumptionAutomatic"]["value"])
|
||||||
|
|
||||||
|
if average_fuel_con > 0:
|
||||||
|
return average_fuel_con
|
||||||
|
return None
|
||||||
|
elif sensor_id == "average_speed":
|
||||||
|
average_speed = 0
|
||||||
|
if util.keys_exists(data, "averageSpeed"):
|
||||||
|
average_speed = float(data["averageSpeed"]["value"])
|
||||||
|
elif util.keys_exists(data, "averageSpeedAutomatic"):
|
||||||
|
average_speed = float(data["averageSpeedAutomatic"]["value"])
|
||||||
|
|
||||||
|
if average_speed != 0:
|
||||||
|
return util.convert_metric_values(average_speed)
|
||||||
|
|
||||||
|
return None
|
||||||
|
elif sensor_id == "location":
|
||||||
|
coordinates = {}
|
||||||
|
if util.keys_exists(data, "geometry"):
|
||||||
|
raw_data = data["geometry"]
|
||||||
|
if util.keys_exists(raw_data, "coordinates"):
|
||||||
|
coordinates = {"longitude": raw_data["coordinates"][0],
|
||||||
|
"latitude": raw_data["coordinates"][1],
|
||||||
|
"gps_accuracy": 1}
|
||||||
|
return coordinates
|
||||||
|
elif sensor_id == "distance_to_empty_tank":
|
||||||
|
if util.keys_exists(data, "distanceToEmptyTank"):
|
||||||
|
distance_to_empty = int(data["distanceToEmptyTank"]["value"])
|
||||||
|
if distance_to_empty > 0:
|
||||||
|
return util.convert_metric_values(data["distanceToEmptyTank"]["value"])
|
||||||
|
return None
|
||||||
|
elif sensor_id == "distance_to_empty_battery":
|
||||||
|
if util.keys_exists(data, "distanceToEmptyBattery"):
|
||||||
|
distance_to_empty = int(data["distanceToEmptyBattery"]["value"])
|
||||||
|
if distance_to_empty > 0:
|
||||||
|
return util.convert_metric_values(data["distanceToEmptyBattery"]["value"])
|
||||||
|
return None
|
||||||
|
elif sensor_id == "hours_to_service":
|
||||||
|
return data["engineHoursToService"]["value"] if util.keys_exists(data, "engineHoursToService") else None
|
||||||
|
elif sensor_id == "km_to_service":
|
||||||
|
if util.keys_exists(data, "distanceToService"):
|
||||||
|
km_to_service = int(data["distanceToService"]["value"])
|
||||||
|
if km_to_service > 0:
|
||||||
|
return util.convert_metric_values(data["distanceToService"]["value"])
|
||||||
|
return None
|
||||||
|
elif sensor_id == "time_to_service":
|
||||||
|
if util.keys_exists(data, "timeToService"):
|
||||||
|
return str(data["timeToService"]["value"]) + " " + data["timeToService"]["unit"]
|
||||||
else:
|
else:
|
||||||
return 0
|
return None
|
||||||
return None
|
elif sensor_id == "service_warning_status":
|
||||||
elif sensor_id == "estimated_charging_finish_time":
|
return data["serviceWarning"]["value"] if util.keys_exists(data, "serviceWarning") else None
|
||||||
if util.keys_exists(data, "chargingSystemStatus"):
|
elif sensor_id == "average_energy_consumption":
|
||||||
charging_system_state = charging_system_states[data["chargingSystemStatus"]["value"]]
|
average_energy_con = 0
|
||||||
if charging_system_state == "Charging":
|
if util.keys_exists(data, "averageEnergyConsumption"):
|
||||||
charging_time = int(
|
average_energy_con = data["averageEnergyConsumption"]["value"]
|
||||||
data["estimatedChargingTime"]["value"] if util.keys_exists(data, "estimatedChargingTime")
|
elif util.keys_exists(data, "averageEnergyConsumptionAutomatic"):
|
||||||
else 0)
|
average_energy_con = data["averageEnergyConsumptionAutomatic"]["value"]
|
||||||
charging_finished = datetime.now(util.TZ) + timedelta(minutes=charging_time)
|
|
||||||
return format_datetime(charging_finished, format="medium", locale=settings["babelLocale"])
|
|
||||||
else:
|
|
||||||
return ""
|
|
||||||
return None
|
|
||||||
elif sensor_id == "lock_status":
|
|
||||||
return data["centralLock"]["value"] if util.keys_exists(data, "centralLock") else None
|
|
||||||
elif sensor_id == "odometer":
|
|
||||||
return util.convert_metric_values(int(data["odometer"]["value"])) \
|
|
||||||
if util.keys_exists(data, "odometer") else None
|
|
||||||
elif sensor_id == "window_front_left":
|
|
||||||
return window_states[data["frontLeftWindow"]["value"]] if util.keys_exists(data, "frontLeftWindow") \
|
|
||||||
else None
|
|
||||||
elif sensor_id == "window_front_right":
|
|
||||||
return window_states[data["frontRightWindow"]["value"]] if util.keys_exists(data, "frontRightWindow") \
|
|
||||||
else None
|
|
||||||
elif sensor_id == "window_rear_left":
|
|
||||||
return window_states[data["rearLeftWindow"]["value"]] if util.keys_exists(data, "rearLeftWindow") \
|
|
||||||
else None
|
|
||||||
elif sensor_id == "window_rear_right":
|
|
||||||
return window_states[data["rearRightWindow"]["value"]] if util.keys_exists(data, "rearRightWindow") \
|
|
||||||
else None
|
|
||||||
elif sensor_id == "door_front_left":
|
|
||||||
return door_states[data["frontLeftDoor"]["value"]] if util.keys_exists(data, "frontLeftDoor") else None
|
|
||||||
elif sensor_id == "door_front_right":
|
|
||||||
return door_states[data["frontRightDoor"]["value"]] \
|
|
||||||
if util.keys_exists(data, "frontRightDoor") else None
|
|
||||||
elif sensor_id == "door_rear_left":
|
|
||||||
return door_states[data["rearLeftDoor"]["value"]] if util.keys_exists(data, "rearLeftDoor") else None
|
|
||||||
elif sensor_id == "door_rear_right":
|
|
||||||
return door_states[data["rearRightDoor"]["value"]] if util.keys_exists(data, "rearRightDoor") else None
|
|
||||||
elif sensor_id == "tailgate":
|
|
||||||
return door_states[data["tailgate"]["value"]] if util.keys_exists(data, "tailgate") else None
|
|
||||||
elif sensor_id == "sunroof":
|
|
||||||
return door_states[data["sunroof"]["value"]] if util.keys_exists(data, "sunroof") else None
|
|
||||||
elif sensor_id == "engine_hood":
|
|
||||||
return door_states[data["hood"]["value"]] if util.keys_exists(data, "hood") else None
|
|
||||||
elif sensor_id == "tank_lid":
|
|
||||||
return door_states[data["tankLid"]["value"]] if util.keys_exists(data, "tankLid") else None
|
|
||||||
elif sensor_id == "tyre_front_left":
|
|
||||||
return data["frontLeft"]["value"] if util.keys_exists(data, "frontLeft") else None
|
|
||||||
elif sensor_id == "tyre_front_right":
|
|
||||||
return data["frontRight"]["value"] if util.keys_exists(data, "frontRight") else None
|
|
||||||
elif sensor_id == "tyre_rear_left":
|
|
||||||
return data["rearLeft"]["value"] if util.keys_exists(data, "rearLeft") else None
|
|
||||||
elif sensor_id == "tyre_rear_right":
|
|
||||||
return data["rearRight"]["value"] if util.keys_exists(data, "rearRight") else None
|
|
||||||
elif sensor_id == "engine_state":
|
|
||||||
return engine_states[data["engineStatus"]["value"]] if util.keys_exists(data, "engineStatus") else None
|
|
||||||
elif sensor_id == "fuel_level":
|
|
||||||
if util.keys_exists(data, "fuelAmount"):
|
|
||||||
fuel_amount = float(data["fuelAmount"]["value"])
|
|
||||||
if fuel_amount > 0:
|
|
||||||
return fuel_amount
|
|
||||||
return None
|
|
||||||
elif sensor_id == "average_fuel_consumption":
|
|
||||||
average_fuel_con = 0
|
|
||||||
if util.keys_exists(data, "averageFuelConsumption"):
|
|
||||||
average_fuel_con = float(data["averageFuelConsumption"]["value"])
|
|
||||||
elif util.keys_exists(data, "averageFuelConsumptionAutomatic"):
|
|
||||||
average_fuel_con = float(data["averageFuelConsumptionAutomatic"]["value"])
|
|
||||||
|
|
||||||
if average_fuel_con > 0:
|
if average_energy_con != 0:
|
||||||
return average_fuel_con
|
return average_energy_con
|
||||||
return None
|
return None
|
||||||
elif sensor_id == "average_speed":
|
elif sensor_id == "washer_fluid_warning":
|
||||||
average_speed = 0
|
return data["washerFluidLevelWarning"]["value"] if util.keys_exists(data, "washerFluidLevelWarning") else None
|
||||||
if util.keys_exists(data, "averageSpeed"):
|
elif sensor_id == "warnings":
|
||||||
average_speed = float(data["averageSpeed"]["value"])
|
warnings = 0
|
||||||
elif util.keys_exists(data, "averageSpeedAutomatic"):
|
cleaned_data = {}
|
||||||
average_speed = float(data["averageSpeedAutomatic"]["value"])
|
for key, dicts in data.items():
|
||||||
|
contains_data = sum(value == "NO_WARNING" or value == "FAILURE" for value in dicts.values())
|
||||||
|
if contains_data:
|
||||||
|
warnings = warnings + 1
|
||||||
|
cleaned_data[key] = dicts["value"]
|
||||||
|
|
||||||
if average_speed != 0:
|
return cleaned_data if warnings > 0 else None
|
||||||
return util.convert_metric_values(average_speed)
|
elif sensor_id == "trip_fuel_consumption":
|
||||||
|
trip_fuel_con = 0
|
||||||
return None
|
if util.keys_exists(data, "averageFuelConsumptionAutomatic"):
|
||||||
elif sensor_id == "location":
|
trip_fuel_con = float(data["averageFuelConsumptionAutomatic"]["value"])
|
||||||
coordinates = {}
|
if trip_fuel_con > 0:
|
||||||
if util.keys_exists(data, "geometry"):
|
return trip_fuel_con
|
||||||
raw_data = data["geometry"]
|
return None
|
||||||
if util.keys_exists(raw_data, "coordinates"):
|
elif sensor_id == "trip_distance":
|
||||||
coordinates = {"longitude": raw_data["coordinates"][0],
|
return util.convert_metric_values(int(data["tripMeterAutomatic"]["value"])) \
|
||||||
"latitude": raw_data["coordinates"][1],
|
if util.keys_exists(data, "tripMeterAutomatic") else None
|
||||||
"gps_accuracy": 1}
|
elif sensor_id == "trip_energy_consumption":
|
||||||
return coordinates
|
trip_energy_con = 0
|
||||||
elif sensor_id == "distance_to_empty_tank":
|
if util.keys_exists(data, "averageEnergyConsumptionAutomatic"):
|
||||||
if util.keys_exists(data, "distanceToEmptyTank"):
|
trip_energy_con = data["averageEnergyConsumptionAutomatic"]["value"]
|
||||||
distance_to_empty = int(data["distanceToEmptyTank"]["value"])
|
if trip_energy_con != 0:
|
||||||
if distance_to_empty > 0:
|
return trip_energy_con
|
||||||
return util.convert_metric_values(data["distanceToEmptyTank"]["value"])
|
return None
|
||||||
return None
|
elif sensor_id == "trip_speed":
|
||||||
elif sensor_id == "distance_to_empty_battery":
|
trip_speed = 0
|
||||||
if util.keys_exists(data, "distanceToEmptyBattery"):
|
if util.keys_exists(data, "averageSpeedAutomatic"):
|
||||||
distance_to_empty = int(data["distanceToEmptyBattery"]["value"])
|
trip_speed = float(data["averageSpeedAutomatic"]["value"])
|
||||||
if distance_to_empty > 0:
|
if trip_speed != 0:
|
||||||
return util.convert_metric_values(data["distanceToEmptyBattery"]["value"])
|
return util.convert_metric_values(trip_speed)
|
||||||
return None
|
return None
|
||||||
elif sensor_id == "hours_to_service":
|
|
||||||
return data["engineHoursToService"]["value"] if util.keys_exists(data, "engineHoursToService") else None
|
|
||||||
elif sensor_id == "km_to_service":
|
|
||||||
if util.keys_exists(data, "distanceToService"):
|
|
||||||
km_to_service = int(data["distanceToService"]["value"])
|
|
||||||
if km_to_service > 0:
|
|
||||||
return util.convert_metric_values(data["distanceToService"]["value"])
|
|
||||||
return None
|
|
||||||
elif sensor_id == "time_to_service":
|
|
||||||
if util.keys_exists(data, "timeToService"):
|
|
||||||
return str(data["timeToService"]["value"]) + " " + data["timeToService"]["unit"]
|
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
elif sensor_id == "service_warning_status":
|
|
||||||
return data["serviceWarning"]["value"] if util.keys_exists(data, "serviceWarning") else None
|
|
||||||
elif sensor_id == "average_energy_consumption":
|
|
||||||
average_energy_con = 0
|
|
||||||
if util.keys_exists(data, "averageEnergyConsumption"):
|
|
||||||
average_energy_con = data["averageEnergyConsumption"]["value"]
|
|
||||||
elif util.keys_exists(data, "averageEnergyConsumptionAutomatic"):
|
|
||||||
average_energy_con = data["averageEnergyConsumptionAutomatic"]["value"]
|
|
||||||
|
|
||||||
if average_energy_con != 0:
|
|
||||||
return average_energy_con
|
|
||||||
return None
|
|
||||||
elif sensor_id == "washer_fluid_warning":
|
|
||||||
return data["washerFluidLevelWarning"]["value"] if util.keys_exists(data, "washerFluidLevelWarning") else None
|
|
||||||
elif sensor_id == "warnings":
|
|
||||||
warnings = 0
|
|
||||||
cleaned_data = {}
|
|
||||||
for key, dicts in data.items():
|
|
||||||
contains_data = sum(value == "NO_WARNING" or value == "FAILURE" for value in dicts.values())
|
|
||||||
if contains_data:
|
|
||||||
warnings = warnings + 1
|
|
||||||
cleaned_data[key] = dicts["value"]
|
|
||||||
|
|
||||||
return cleaned_data if warnings > 0 else None
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user