mirror of
https://github.com/Dielee/volvo2mqtt.git
synced 2026-07-22 02:13:01 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
71b6cd269a | ||
|
|
4cb729beb5 | ||
|
|
0a12587276 | ||
|
|
91c50dba05 | ||
|
|
b93f50ae0b | ||
|
|
9a6d128cdf | ||
|
|
1998dfc231 | ||
|
|
389be776d0 |
@@ -0,0 +1,33 @@
|
||||
# This workflow warns and then closes issues and PRs that have had no activity for a specified amount of time.
|
||||
#
|
||||
# You can adjust the behavior by modifying this file.
|
||||
# For more information, see:
|
||||
# https://github.com/actions/stale
|
||||
name: Mark stale issues and pull requests
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 0 * * *'
|
||||
|
||||
jobs:
|
||||
stale:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
|
||||
steps:
|
||||
- uses: actions/stale@v5
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
days-before-issue-stale: 15
|
||||
days-before-pr-stale: 45
|
||||
days-before-issue-close: 5
|
||||
days-before-pr-close: 10
|
||||
stale-issue-message: 'This issue is stale because it has been open 15 days with no activity. Remove stale label or comment or this will be closed in 5 days.'
|
||||
stale-pr-message: 'This PR is stale because it has been open 20 days with no activity. Remove stale label or comment or this will be closed in 10 days.'
|
||||
close-issue-message: 'This issue was closed because it has been stalled for 5 days with no activity.'
|
||||
close-pr-message: 'This PR was closed because it has been stalled for 10 days with no activity.'
|
||||
stale-issue-label: 'no-issue-activity'
|
||||
stale-pr-label: 'no-pr-activity'
|
||||
+9
-6
@@ -1,10 +1,13 @@
|
||||
name: "Volvo2Mqtt"
|
||||
description: "Volvo AAOS MQTT bridge"
|
||||
version: "1.6.5"
|
||||
version: "1.7.1"
|
||||
slug: "volvo2mqtt"
|
||||
init: false
|
||||
url: "https://github.com/Dielee/volvo2mqtt"
|
||||
apparmor: true
|
||||
codenotary: notary@home-assistant.io
|
||||
services:
|
||||
- 'mqtt:need'
|
||||
map:
|
||||
- addons:rw
|
||||
options:
|
||||
@@ -13,10 +16,10 @@ options:
|
||||
TZ: null
|
||||
debug: false
|
||||
mqtt:
|
||||
broker: null
|
||||
port: 1883
|
||||
username: ""
|
||||
password: ""
|
||||
broker: "auto_broker"
|
||||
port: "auto_port"
|
||||
username: "auto_user"
|
||||
password: "auto_password"
|
||||
volvoData:
|
||||
username: null
|
||||
password: null
|
||||
@@ -32,7 +35,7 @@ schema:
|
||||
debug: bool
|
||||
mqtt:
|
||||
broker: str
|
||||
port: int(1,)
|
||||
port: str
|
||||
username: str?
|
||||
password: str?
|
||||
volvoData:
|
||||
|
||||
+6
-2
@@ -1,6 +1,6 @@
|
||||
from config import settings
|
||||
|
||||
VERSION = "v1.6.5"
|
||||
VERSION = "v1.7.1"
|
||||
|
||||
OAUTH_URL = "https://volvoid.eu.volvocars.com/as/token.oauth2"
|
||||
VEHICLES_URL = "https://api.volvocars.com/connected-vehicle/v1/vehicles"
|
||||
@@ -19,6 +19,7 @@ ENGINE_STATE_URL = "https://api.volvocars.com/connected-vehicle/v2/vehicles/{0}/
|
||||
BATTERY_CHARGE_STATE_URL = "https://api.volvocars.com/connected-vehicle/v2/vehicles/{0}/battery-charge-level"
|
||||
FUEL_STATE_URL = "https://api.volvocars.com/connected-vehicle/v2/vehicles/{0}/fuel"
|
||||
STATISTICS_URL = "https://api.volvocars.com/connected-vehicle/v2/vehicles/{0}/statistics"
|
||||
ENGINE_DIAGNOSTICS_URL = "https://api.volvocars.com/connected-vehicle/v2/vehicles/{0}/engine"
|
||||
|
||||
units = {
|
||||
"en_GB": {
|
||||
@@ -47,6 +48,7 @@ charging_connection_states = {"CONNECTION_STATUS_DISCONNECTED": "Disconnected",
|
||||
|
||||
window_states = {"CLOSED": "OFF", "OPEN": "ON"}
|
||||
door_states = {"CLOSED": "OFF", "OPEN": "ON"}
|
||||
engine_states = {"RUNNING": "ON", "STOPPED": "OFF"}
|
||||
|
||||
icon_states = {
|
||||
"lock_status": {"UNLOCKED": "lock-open-alert", "LOCKED": "lock"},
|
||||
@@ -54,6 +56,7 @@ icon_states = {
|
||||
"door_front_right": {"ON": "car-door", "OFF": "car-door-lock"},
|
||||
"door_rear_left": {"ON": "car-door", "OFF": "car-door-lock"},
|
||||
"door_rear_right": {"ON": "car-door", "OFF": "car-door-lock"},
|
||||
"engine_state": {"ON": "engine-outline", "OFF": "engine-off-outline"},
|
||||
"battery_charge_level": [
|
||||
{"from": 100, "to": 100, "icon": "battery"},
|
||||
{"from": 99, "to": 90, "icon": "battery-90"},
|
||||
@@ -99,7 +102,8 @@ supported_entities = [
|
||||
{"name": "Tire Front Right", "domain": "sensor", "id": "tyre_front_right", "icon": "car-tire-alert", "url": TYRE_STATE_URL},
|
||||
{"name": "Tire Rear Left", "domain": "sensor", "id": "tyre_rear_left", "icon": "car-tire-alert", "url": TYRE_STATE_URL},
|
||||
{"name": "Tire Rear Right", "domain": "sensor", "id": "tyre_rear_right", "icon": "car-tire-alert", "url": TYRE_STATE_URL},
|
||||
{"name": "Engine State", "domain": "sensor", "id": "engine_state", "icon": "engine", "url": ENGINE_STATE_URL},
|
||||
{"name": "Engine State", "domain": "binary_sensor", "device_class": "running", "id": "engine_state", "icon": "engine", "url": ENGINE_STATE_URL},
|
||||
{"name": "Engine State", "domain": "binary_sensor", "device_class": "running", "id": "engine_state", "icon": "engine", "url": ENGINE_DIAGNOSTICS_URL},
|
||||
{"name": "Fuel Level", "domain": "sensor", "id": "fuel_level", "unit": "liters", "icon": "fuel", "url": FUEL_STATE_URL},
|
||||
{"name": "Average Fuel Consumption", "domain": "sensor", "id": "average_fuel_consumption", "unit": "liters", "icon": "fuel", "url": STATISTICS_URL},
|
||||
{"name": "Distance to Empty", "domain": "sensor", "id": "distance_to_empty", "unit": "km" if not units.get(settings["babelLocale"]) else units[settings["babelLocale"]]["distance_to_empty"]["unit"], "icon": "map-marker-distance", "url": STATISTICS_URL},
|
||||
|
||||
+2
-1
@@ -2,11 +2,12 @@ import logging
|
||||
from volvo import authorize
|
||||
from mqtt import update_loop, connect
|
||||
from const import VERSION
|
||||
from util import set_tz, setup_logging
|
||||
from util import set_tz, setup_logging, set_mqtt_settings
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
set_tz()
|
||||
set_mqtt_settings()
|
||||
setup_logging()
|
||||
logging.info("Starting volvo2mqtt version " + VERSION)
|
||||
authorize()
|
||||
|
||||
+9
-9
@@ -17,7 +17,7 @@ subscribed_topics = []
|
||||
assumed_climate_state = {}
|
||||
last_data_update = None
|
||||
climate_timer = {}
|
||||
door_status = {}
|
||||
engine_status = {}
|
||||
devices = {}
|
||||
|
||||
|
||||
@@ -62,21 +62,21 @@ def on_message(client, userdata, msg):
|
||||
|
||||
payload = msg.payload.decode("UTF-8")
|
||||
if "climate_status" in msg.topic:
|
||||
global assumed_climate_state, climate_timer, door_status
|
||||
global assumed_climate_state, climate_timer, engine_status
|
||||
if payload == "ON":
|
||||
# Start the api call in another thread for HA performance
|
||||
Thread(target=volvo.api_call, args=(CLIMATE_START_URL, "POST", vin)).start()
|
||||
|
||||
# Start door check thread to turn off climate if driver door is opened
|
||||
door_thread = Thread(target=volvo.check_door_status, args=(vin, ))
|
||||
door_thread.start()
|
||||
door_status[vin] = door_thread
|
||||
# Starting timer to disable climate after 30 mins
|
||||
check_engine_thread = Thread(target=volvo.check_engine_status, args=(vin, ))
|
||||
check_engine_thread.start()
|
||||
engine_status[vin] = check_engine_thread
|
||||
|
||||
# Starting timer to disable climate after 30 mins
|
||||
climate_timer[vin] = Timer(30 * 60, volvo.disable_climate, (vin, ))
|
||||
climate_timer[vin].start()
|
||||
# Set and update switch status
|
||||
|
||||
# Set and update switch status
|
||||
assumed_climate_state[vin] = "ON"
|
||||
update_car_data()
|
||||
elif payload == "OFF":
|
||||
@@ -84,8 +84,8 @@ def on_message(client, userdata, msg):
|
||||
Thread(target=volvo.api_call, args=(CLIMATE_STOP_URL, "POST", vin)).start()
|
||||
|
||||
# Stop door check thread if running
|
||||
if door_status[vin].is_alive():
|
||||
door_status[vin].do_run = False
|
||||
if engine_status[vin].is_alive():
|
||||
engine_status[vin].do_run = False
|
||||
|
||||
# Stop climate timer if active
|
||||
if climate_timer[vin].is_alive():
|
||||
|
||||
+6
-1
@@ -1,4 +1,9 @@
|
||||
#!/bin/bash
|
||||
#!/command/with-contenv bashio
|
||||
export IS_HA_ADDON="true"
|
||||
|
||||
export MQTTHOST=$(bashio::services mqtt "host")
|
||||
export MQTTPORT=$(bashio::services mqtt "port")
|
||||
export MQTTUSER=$(bashio::services mqtt "username")
|
||||
export MQTTPASS=$(bashio::services mqtt "password")
|
||||
|
||||
python -u main.py
|
||||
@@ -10,10 +10,10 @@ configuration:
|
||||
description: Set your local timezone eg. "Europe/Berlin" or "America/New_York"
|
||||
debug:
|
||||
name: API debug mode
|
||||
description: Enable Volvo API debug, normaly this can stay off
|
||||
description: Enable Volvo API debug, normaly this can stay off. If enabled, the complete log file can be found under \\<Your HA Host IP>\addons\volvo2mqtt\log\volvo2mqtt.log.
|
||||
mqtt:
|
||||
name: MQTT Broker settings
|
||||
description: Broker is your mqtt broker IP eg "192.168.0.1". Mqtt username and password are optional. Broker port can be changed. If no value is given, port 1883 will be used.
|
||||
description: Leave the settings as they are if you are using the MQTT Mosquitto Addon. If not, take a look at the readme from volvo2mqtt.
|
||||
volvoData:
|
||||
name: Volvo configuration options
|
||||
description: You have to enter your Volvo app credentials. The username is normally your email, and the password is the password you use for your Volvo app. The vin can stay empty. The Add-On will use any vin inside your Volvo account. VCCAPI key is required and comes from your Volvo developer account. Odometer multiplier is sometimes 10, sometimes 1. The same is applicable for average speed divider and average fuel consumption. Leave it as it is if you don't know what's right. Take a look at the GitHub repo for more information!
|
||||
+26
@@ -2,6 +2,7 @@ import logging
|
||||
import pytz
|
||||
import os
|
||||
import sys
|
||||
import config
|
||||
from logging import handlers
|
||||
from datetime import datetime
|
||||
from const import units
|
||||
@@ -87,3 +88,28 @@ def convert_metric_values(value):
|
||||
else:
|
||||
return value
|
||||
|
||||
|
||||
def set_mqtt_settings():
|
||||
if os.environ.get("IS_HA_ADDON"):
|
||||
if config.settings["mqtt"]["broker"] != "auto_broker" \
|
||||
or config.settings["mqtt"]["port"] != "auto_port" \
|
||||
or config.settings["mqtt"]["username"] != "auto_user" \
|
||||
or config.settings["mqtt"]["password"] != "auto_password":
|
||||
# If settings were manually set, use the manually set settings
|
||||
return None
|
||||
|
||||
broker_host = os.getenv("MQTTHOST", None)
|
||||
broker_port = os.getenv("MQTTPORT", None)
|
||||
broker_user = os.getenv("MQTTUSER", None)
|
||||
broker_pass = os.getenv("MQTTPASS", None)
|
||||
|
||||
if not broker_host or not broker_port:
|
||||
raise Exception("MQTT connection could not be established. Please check if your MQTT Add-On is running!")
|
||||
|
||||
logging.debug("MQTT Credentials - Host " + broker_host + " Port: " + str(broker_port) +
|
||||
" User: " + str(broker_user) + " Pass: " + str(broker_pass))
|
||||
|
||||
config.settings["mqtt"]["broker"] = broker_host
|
||||
config.settings["mqtt"]["port"] = broker_port
|
||||
config.settings["mqtt"]["username"] = broker_user
|
||||
config.settings["mqtt"]["password"] = broker_pass
|
||||
|
||||
+24
-10
@@ -3,14 +3,14 @@ import requests
|
||||
import mqtt
|
||||
import util
|
||||
import time
|
||||
from threading import Thread, currentThread
|
||||
from threading import currentThread
|
||||
from datetime import datetime, timedelta
|
||||
from config import settings
|
||||
from babel.dates import format_datetime
|
||||
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, \
|
||||
WINDOWS_STATE_URL, LOCK_STATE_URL, TYRE_STATE_URL, supported_entities, BATTERY_CHARGE_STATE_URL, \
|
||||
STATISTICS_URL, CLIMATE_STOP_URL
|
||||
STATISTICS_URL, ENGINE_DIAGNOSTICS_URL, engine_states
|
||||
|
||||
session = requests.Session()
|
||||
session.headers = {
|
||||
@@ -150,6 +150,11 @@ def check_supported_endpoints():
|
||||
# If battery charge level could be found in recharge-api, skip the second battery charge sensor
|
||||
continue
|
||||
|
||||
if entity["id"] == "engine_state" and entity["url"] == ENGINE_DIAGNOSTICS_URL \
|
||||
and any("engine_state" in d["id"] for d in supported_endpoints[vin]):
|
||||
# If engine state could be found in engine state endpoint, skip the second engine running sensor
|
||||
continue
|
||||
|
||||
if entity.get('url'):
|
||||
state = api_call(entity["url"], "GET", vin, entity["id"])
|
||||
else:
|
||||
@@ -169,18 +174,27 @@ def initialize_climate(vins):
|
||||
|
||||
def disable_climate(vin):
|
||||
logging.info("Turning climate off by timer!")
|
||||
mqtt.door_status[vin].do_run = False
|
||||
mqtt.engine_status[vin].do_run = False
|
||||
mqtt.assumed_climate_state[vin] = "OFF"
|
||||
mqtt.update_car_data()
|
||||
|
||||
|
||||
def check_door_status(vin):
|
||||
def check_engine_status(vin):
|
||||
endpoint_url = ""
|
||||
engine_state_supported = False
|
||||
for endpoint in supported_endpoints[vin]:
|
||||
if "engine_state" == endpoint["id"]:
|
||||
engine_state_supported = True
|
||||
endpoint_url = endpoint["url"]
|
||||
|
||||
if not engine_state_supported:
|
||||
# Exit thread as engine state is unsupported by car
|
||||
return None
|
||||
|
||||
t = currentThread()
|
||||
while getattr(t, "do_run", True):
|
||||
lock_door_left = api_call(LOCK_STATE_URL, "GET", vin, "door_front_left", True)
|
||||
lock_door_right = api_call(LOCK_STATE_URL, "GET", vin, "door_front_right", True)
|
||||
if lock_door_left == "ON" or lock_door_right == "ON":
|
||||
Thread(target=api_call, args=(CLIMATE_STOP_URL, "POST", vin)).start()
|
||||
engine_state = api_call(endpoint_url, "GET", vin, "engine_state", True)
|
||||
if engine_state == "ON":
|
||||
mqtt.assumed_climate_state[vin] = "OFF"
|
||||
mqtt.update_car_data()
|
||||
break
|
||||
@@ -253,7 +267,7 @@ def cached_request(url, method, vin, force_update=False):
|
||||
if (datetime.now(util.TZ) - cached_requests[vin + "_" + url]["last_update"]).total_seconds() \
|
||||
>= settings["updateInterval"] or (force_update and
|
||||
(datetime.now(util.TZ) - cached_requests[vin + "_" + url]
|
||||
["last_update"]).total_seconds() >= 2):
|
||||
["last_update"]).total_seconds() >= 2):
|
||||
# Old Data in Cache, or force mode active, updating
|
||||
logging.debug("Starting " + method + " call against " + url)
|
||||
try:
|
||||
@@ -352,7 +366,7 @@ def parse_api_data(data, sensor_id=None):
|
||||
elif sensor_id == "tyre_rear_right":
|
||||
return data["rearRightTyrePressure"]["value"] if util.keys_exists(data, "rearRightTyrePressure") else None
|
||||
elif sensor_id == "engine_state":
|
||||
return data["engineRunning"]["value"] if util.keys_exists(data, "engineRunning") else None
|
||||
return engine_states[data["engineRunning"]["value"]] if util.keys_exists(data, "engineRunning") else None
|
||||
elif sensor_id == "fuel_level":
|
||||
if util.keys_exists(data, "fuelAmount"):
|
||||
fuel_amount = int(data["fuelAmount"]["value"])
|
||||
|
||||
Reference in New Issue
Block a user