diff --git a/src/const.py b/src/const.py index 67a8e68..362a78f 100644 --- a/src/const.py +++ b/src/const.py @@ -20,7 +20,8 @@ FUEL_BATTERY_STATE_URL = "https://api.volvocars.com/connected-vehicle/v2/vehicle 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" VEHICLE_DIAGNOSTICS_URL = "https://api.volvocars.com/connected-vehicle/v2/vehicles/{0}/diagnostics" -API_BACKEND_STATUS = "https://oip-dev-bff.euwest1.production.volvo.care/api/v1/backend-status" +API_BACKEND_STATUS_URL = "https://oip-dev-bff.euwest1.production.volvo.care/api/v1/backend-status" +SUPPORTED_COMMANDS_URL = "https://api.volvocars.com/connected-vehicle/v2/vehicles/{0}/commands" units = { "en_GB": { @@ -99,8 +100,8 @@ supported_entities = [ {"name": "Engine Hood", "domain": "binary_sensor", "device_class": "door", "id": "engine_hood", "icon": "car-door-lock", "url": LOCK_STATE_URL}, {"name": "Tank Lid", "domain": "binary_sensor", "device_class": "door", "id": "tank_lid", "icon": "car-door-lock", "url": LOCK_STATE_URL}, {"name": "Sunroof", "domain": "binary_sensor", "device_class": "door", "id": "sunroof", "icon": "car-door-lock", "url": WINDOWS_STATE_URL}, - {"name": "Air Conditioning", "domain": "switch", "id": "climate_status", "icon": "air-conditioner"}, - {"name": "Lock state", "domain": "lock", "id": "lock_status", "icon": "lock", "url": LOCK_STATE_URL}, + {"name": "Air Conditioning", "domain": "switch", "id": "climate_status", "icon": "air-conditioner", "commands": ["CLIMATIZATION_START", "CLIMATIZATION_STOP"]}, + {"name": "Lock state", "domain": "lock", "id": "lock_status", "icon": "lock", "url": LOCK_STATE_URL, "commands": ["LOCK", "UNLOCK"]}, {"name": "Force Update Data", "domain": "button", "id": "update_data", "icon": "update", "url": ""}, {"name": "Location", "domain": "device_tracker", "id": "location", "icon": "map-marker-radius", "url": LOCATION_STATE_URL}, {"name": "Tire Front Left", "domain": "sensor", "id": "tyre_front_left", "icon": "car-tire-alert", "url": TYRE_STATE_URL}, diff --git a/src/volvo.py b/src/volvo.py index af947c8..dde6ecf 100644 --- a/src/volvo.py +++ b/src/volvo.py @@ -12,7 +12,7 @@ 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, \ WINDOWS_STATE_URL, LOCK_STATE_URL, TYRE_STATE_URL, supported_entities, FUEL_BATTERY_STATE_URL, \ - STATISTICS_URL, ENGINE_DIAGNOSTICS_URL, API_BACKEND_STATUS, engine_states + STATISTICS_URL, ENGINE_DIAGNOSTICS_URL, API_BACKEND_STATUS_URL, SUPPORTED_COMMANDS_URL, engine_states session = requests.Session() session.headers = { @@ -27,6 +27,7 @@ vins = [] supported_endpoints = {} cached_requests = {} vcc_api_keys = [] +supported_commands = [] backend_status = "" @@ -54,6 +55,7 @@ def authorize(): get_vcc_api_keys() get_vehicles() + get_supported_commands() check_supported_endpoints() Thread(target=backend_status_loop).start() else: @@ -90,6 +92,18 @@ def refresh_auth(): refresh_token = data["refresh_token"] +def get_supported_commands(): + global supported_commands + for vin in vins: + commands = session.get(SUPPORTED_COMMANDS_URL.format(vin)) + data = commands.json() + if commands.status_code == 200: + for command in data["data"]: + supported_commands.append(command["command"]) + else: + logging.error("Error getting supported commands: " + str(commands.status_code) + ". " + str(data["error"].get("message"))) + + def get_vehicles(): global vins if not settings.volvoData["vin"]: @@ -102,9 +116,8 @@ def get_vehicles(): else: raise Exception("No vehicle in account " + settings.volvoData["username"] + " found.") else: - error = vehicles.json() raise Exception( - "Error getting vehicles: " + str(vehicles.status_code) + ". " + str(error["error"].get("message"))) + "Error getting vehicles: " + str(vehicles.status_code) + ". " + str(data["error"].get("message"))) else: if isinstance(settings.volvoData["vin"], list): # If setting is a list, copy @@ -248,10 +261,13 @@ def check_supported_endpoints(): # If battery charge level could be found in recharge-api, skip the second battery charge sensor continue - if entity.get('url'): - state = api_call(entity["url"], "GET", vin, entity["id"]) + if entity["domain"] in ["switch", "lock"]: + state = check_supported_command(entity["commands"]) else: - state = "" + if entity.get('url'): + state = api_call(entity["url"], "GET", vin, entity["id"]) + else: + state = "" if state is not None: logging.info("Success! " + entity["name"] + " is supported by your vehicle.") @@ -260,6 +276,18 @@ def check_supported_endpoints(): logging.info("Failed, " + entity["name"] + " is unfortunately not supported by your vehicle.") +def check_supported_command(entity_commands): + commands_supported = True + for entity_command in entity_commands: + if entity_command not in supported_commands: + commands_supported = False + + if commands_supported: + return "" + else: + return None + + def initialize_scheduler(vins): for vin in vins: topic = f"homeassistant/schedule/{vin}/command" @@ -325,7 +353,7 @@ def backend_status_loop(): def get_backend_status(): global backend_status - response = session.get(API_BACKEND_STATUS, timeout=15) + response = session.get(API_BACKEND_STATUS_URL, timeout=15) try: data = response.json() if util.keys_exists(data, "message"):