From 6a326f6affac274944662e27bd82f7dd9dab35ff Mon Sep 17 00:00:00 2001 From: Fredrik Ljunggren Date: Wed, 25 Dec 2024 23:09:48 +0100 Subject: [PATCH] service to control climitisation --- custom_components/porscheconnect/__init__.py | 4 + custom_components/porscheconnect/services.py | 122 ++++++++++++++++++ .../porscheconnect/services.yaml | 40 ++++++ .../porscheconnect/translations/en.json | 32 +++++ .../porscheconnect/translations/sv.json | 34 ++++- pyproject.toml | 2 +- 6 files changed, 232 insertions(+), 2 deletions(-) create mode 100644 custom_components/porscheconnect/services.py create mode 100644 custom_components/porscheconnect/services.yaml diff --git a/custom_components/porscheconnect/__init__.py b/custom_components/porscheconnect/__init__.py index 87be3ed..6e60b02 100644 --- a/custom_components/porscheconnect/__init__.py +++ b/custom_components/porscheconnect/__init__.py @@ -89,6 +89,10 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): _async_save_token(hass, entry, controller.token) + from .services import setup_services + + setup_services(hass, entry) + return True diff --git a/custom_components/porscheconnect/services.py b/custom_components/porscheconnect/services.py new file mode 100644 index 0000000..4f29d03 --- /dev/null +++ b/custom_components/porscheconnect/services.py @@ -0,0 +1,122 @@ +"""Porsche Connect services.""" + +from __future__ import annotations + +import logging +from collections.abc import Mapping + +import voluptuous as vol +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant, ServiceCall +from homeassistant.exceptions import HomeAssistantError, ServiceValidationError +from homeassistant.helpers import config_validation as cv +from homeassistant.helpers import device_registry as dr +from pyporscheconnectapi.exceptions import PorscheExceptionError +from pyporscheconnectapi.vehicle import PorscheVehicle + +from . import ( + PorscheConnectDataUpdateCoordinator, +) +from .const import DOMAIN + +LOGGER = logging.getLogger(__name__) + +ATTR_VEHICLE = "vehicle" + +ATTR_TEMPERATURE = "temperature" +ATTR_FRONT_LEFT = "front_left" +ATTR_FRONT_RIGHT = "front_right" +ATTR_REAR_LEFT = "rear_left" +ATTR_REAR_RIGHT = "rear_right" + +SERVICE_VEHICLE_SCHEMA = vol.Schema( + { + vol.Required("vehicle"): cv.string, + } +) + +SERVICE_CLIMATISATION_START_SCHEMA = SERVICE_VEHICLE_SCHEMA.extend( + { + vol.Optional(ATTR_TEMPERATURE): cv.positive_float, + vol.Optional(ATTR_FRONT_LEFT): cv.boolean, + vol.Optional(ATTR_FRONT_RIGHT): cv.boolean, + vol.Optional(ATTR_REAR_LEFT): cv.boolean, + vol.Optional(ATTR_REAR_RIGHT): cv.boolean, + } +) + +SERVICE_CLIMATISATION_START = "climatisation_start" + +SERVICES = [ + SERVICE_CLIMATISATION_START, +] + + +def setup_services( + hass: HomeAssistant, + config_entry: ConfigEntry, +) -> None: + """Register the Porsche Connect service actions.""" + coordinator: PorscheConnectDataUpdateCoordinator = hass.data[DOMAIN][ + config_entry.entry_id + ] + + async def climatisation_start(service_call: ServiceCall) -> None: + """Start climatisation.""" + temperature: float = service_call.data.get(ATTR_TEMPERATURE) + front_left: bool = service_call.data.get(ATTR_FRONT_LEFT) + front_right: bool = service_call.data.get(ATTR_FRONT_RIGHT) + rear_left: bool = service_call.data.get(ATTR_REAR_LEFT) + rear_right: bool = service_call.data.get(ATTR_REAR_RIGHT) + + LOGGER.debug( + "Starting climatisation: %s, %s, %s, %s, %s", + temperature, + front_left, + front_right, + rear_left, + rear_right, + ) + vehicle = get_vehicle(service_call.data) + try: + await vehicle.remote_services.climatise_on( + target_temperature=293.15 + if temperature is None + else temperature + 273.15, + front_left=front_left or False, + front_right=front_right or False, + rear_left=rear_left or False, + rear_right=rear_right or False, + ) + except PorscheExceptionError as ex: + raise HomeAssistantError(ex) from ex + + hass.services.async_register( + DOMAIN, + SERVICE_CLIMATISATION_START, + climatisation_start, + schema=SERVICE_CLIMATISATION_START_SCHEMA, + ) + + def get_vehicle(service_call_data: Mapping) -> PorscheVehicle: + """Get vehicle from service_call data.""" + device_registry = dr.async_get(hass) + device_id = service_call_data[ATTR_VEHICLE] + device_entry = device_registry.async_get(device_id) + + if device_entry is None: + raise ServiceValidationError( + translation_domain=DOMAIN, + translation_key="invalid_device_id", + translation_placeholders={"device_id": device_id}, + ) + + for vehicle in coordinator.vehicles: + if (DOMAIN, vehicle.vin) in device_entry.identifiers: + return vehicle + + raise ServiceValidationError( + translation_domain=DOMAIN, + translation_key="no_config_entry_for_device", + translation_placeholders={"device_id": device_entry.name or device_id}, + ) diff --git a/custom_components/porscheconnect/services.yaml b/custom_components/porscheconnect/services.yaml new file mode 100644 index 0000000..4574b74 --- /dev/null +++ b/custom_components/porscheconnect/services.yaml @@ -0,0 +1,40 @@ +climatisation_start: + fields: + vehicle: + required: true + selector: + device: + integration: porscheconnect + temperature: + example: "21" + required: false + selector: + number: + min: 15 + max: 25 + step: 0.5 + unit_of_measurement: °C + front_left: + example: true + required: false + default: false + selector: + boolean: + front_right: + example: false + required: false + default: false + selector: + boolean: + rear_left: + example: false + required: false + default: false + selector: + boolean: + rear_right: + example: false + required: false + default: false + selector: + boolean: diff --git a/custom_components/porscheconnect/translations/en.json b/custom_components/porscheconnect/translations/en.json index 0bcea68..a3ac8b5 100644 --- a/custom_components/porscheconnect/translations/en.json +++ b/custom_components/porscheconnect/translations/en.json @@ -103,6 +103,38 @@ "climatise": {"name": "Remote climatisation"}, "direct_charging": {"name": "Direct charging"} } + }, + "services": { + "climatisation_start": { + "name": "Start remote climatisation", + "description": "Starts remote climatisation of the passenger compartment with specified parameters", + "fields": { + "vehicle": { + "name": "Fordon", + "description": "The vehicle to be climatised." + }, + "temperature": { + "name": "Temperatur", + "description": "Target temperature for climatisation (default 20 degrees C)" + }, + "front_left": { + "name": "Seat heating front left", + "description": "If front left seat heater should be activated (default off)" + }, + "front_right": { + "name": "SSeat heating front right", + "description": "If front right seat heater should be activated (default off)" + }, + "rear_left": { + "name": "Seat heating rear left", + "description": "If rear left seat heater should be activated (default off)" + }, + "rear_right": { + "name": "Seat heating rear right", + "description": "If rear right seat heater should be activated (default off)" + } + } + } } } diff --git a/custom_components/porscheconnect/translations/sv.json b/custom_components/porscheconnect/translations/sv.json index f3225df..efe130c 100644 --- a/custom_components/porscheconnect/translations/sv.json +++ b/custom_components/porscheconnect/translations/sv.json @@ -133,5 +133,37 @@ "climatise": {"name": "Fjärrklimatisering"}, "direct_charging": {"name": "Direktladdning"} } + }, + "services": { + "climatisation_start": { + "name": "Starta klimatisering", + "description": "Startar klimatisering av kupén med angivna parametrar.", + "fields": { + "vehicle": { + "name": "Fordon", + "description": "Det fordon som ska klimatiseras." + }, + "temperature": { + "name": "Temperatur", + "description": "Måltemperatur för klimatisering (standardvärde 20 grader)" + }, + "front_left": { + "name": "Sätesvärme vänster fram", + "description": "Om sätesvärmde vänster fram ska aktiveras (standardvärde av)" + }, + "front_right": { + "name": "Sätesvärme höger fram", + "description": "Om sätesvärmde höger fram ska aktiveras (standardvärde av)" + }, + "rear_left": { + "name": "Sätesvärme vänster bak", + "description": "Om sätesvärmde vänster bak ska aktiveras (standardvärde av)" + }, + "rear_right": { + "name": "Sätesvärme höger bak", + "description": "Om sätesvärmde höger bak ska aktiveras (standardvärde av)" + } + } + } } -} +} \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 714ae1e..ae9858d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,4 +3,4 @@ exclude = [ "tests/*.py" ] [tool.ruff.lint] select = [ "ALL" ] -ignore = [ "ANN001", "ANN201", "ANN202", "ANN204", "ARG001", "TC002", "TC003", "COM812", "ISC001" ] +ignore = [ "ANN001", "ANN201", "ANN202", "ANN204", "ARG001", "TC001", "TC002", "TC003", "COM812", "ISC001" ]