diff --git a/custom_components/toyota_na/__init__.py b/custom_components/toyota_na/__init__.py index 3948deb..bb3eec1 100644 --- a/custom_components/toyota_na/__init__.py +++ b/custom_components/toyota_na/__init__.py @@ -14,27 +14,17 @@ from homeassistant.helpers import device_registry as dr, service from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed -from .const import DOMAIN +from .const import ( + COMMAND_MAP, + DOMAIN, + DOOR_LOCK, + DOOR_UNLOCK, + ENGINE_START, + ENGINE_STOP, +) _LOGGER = logging.getLogger(__name__) -PLATFORMS = ["binary_sensor", "device_tracker", "sensor"] - -SERVICE_DOOR_LOCK = "door_lock" -SERVICE_DOOR_UNLOCK = "door_unlock" -SERVICE_ENGINE_START = "engine_start" -SERVICE_ENGINE_STOP = "engine_stop" - -commands = { - SERVICE_DOOR_LOCK: RemoteRequestCommand.DoorLock, - SERVICE_DOOR_UNLOCK: RemoteRequestCommand.DoorUnlock, - SERVICE_ENGINE_START: RemoteRequestCommand.EngineStart, - SERVICE_ENGINE_STOP: RemoteRequestCommand.EngineStop, -} - -SERVICE_DOOR_LOCK = "door_lock" -SERVICE_DOOR_UNLOCK = "door_unlock" -SERVICE_ENGINE_START = "engine_start" -SERVICE_ENGINE_STOP = "engine_stop" +PLATFORMS = ["binary_sensor", "device_tracker", "lock", "sensor"] async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): @@ -90,17 +80,17 @@ async def async_service_handle(service_call: ServiceCall) -> None: vin = identifier[1] for vehicle in coordinator.data: if vehicle.vin == vin: - await vehicle.send_command(commands[remote_action]) + await vehicle.send_command(COMMAND_MAP[remote_action]) break _LOGGER.info("Handling service call %s for %s ", remote_action, vin) return - hass.services.async_register(DOMAIN, SERVICE_DOOR_LOCK, async_service_handle) - hass.services.async_register(DOMAIN, SERVICE_DOOR_UNLOCK, async_service_handle) - hass.services.async_register(DOMAIN, SERVICE_ENGINE_START, async_service_handle) - hass.services.async_register(DOMAIN, SERVICE_ENGINE_STOP, async_service_handle) + hass.services.async_register(DOMAIN, DOOR_LOCK, async_service_handle) + hass.services.async_register(DOMAIN, DOOR_UNLOCK, async_service_handle) + hass.services.async_register(DOMAIN, ENGINE_START, async_service_handle) + hass.services.async_register(DOMAIN, ENGINE_STOP, async_service_handle) return True diff --git a/custom_components/toyota_na/const.py b/custom_components/toyota_na/const.py index 5ea4c00..22bcfbb 100644 --- a/custom_components/toyota_na/const.py +++ b/custom_components/toyota_na/const.py @@ -4,8 +4,23 @@ from homeassistant.components.sensor import SensorStateClass from homeassistant.const import LENGTH_MILES, PERCENTAGE, PRESSURE_PSI +from toyota_na.vehicle.base_vehicle import RemoteRequestCommand + + DOMAIN = "toyota_na" +DOOR_LOCK = "door_lock" +DOOR_UNLOCK = "door_unlock" +ENGINE_START = "engine_start" +ENGINE_STOP = "engine_stop" + +COMMAND_MAP = { + DOOR_LOCK: RemoteRequestCommand.DoorLock, + DOOR_UNLOCK: RemoteRequestCommand.DoorUnlock, + ENGINE_START: RemoteRequestCommand.EngineStart, + ENGINE_STOP: RemoteRequestCommand.EngineStop, +} + BINARY_SENSORS = [ { "device_class": BinarySensorDeviceClass.DOOR, diff --git a/custom_components/toyota_na/lock.py b/custom_components/toyota_na/lock.py new file mode 100644 index 0000000..e6c8477 --- /dev/null +++ b/custom_components/toyota_na/lock.py @@ -0,0 +1,114 @@ +import asyncio +from typing import Any + +from toyota_na.vehicle.base_vehicle import ToyotaVehicle, VehicleFeatures +from toyota_na.vehicle.entity_types.ToyotaLockableOpening import ToyotaLockableOpening +from toyota_na.vehicle.entity_types.ToyotaOpening import ToyotaOpening +from toyota_na.vehicle.entity_types.ToyotaRemoteStart import ToyotaRemoteStart + + +from homeassistant.components.lock import ( + LockEntity, +) + +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator + +from .base_entity import ToyotaNABaseEntity +from .const import COMMAND_MAP, DOMAIN, DOOR_LOCK, DOOR_UNLOCK + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_devices: AddEntitiesCallback, +): + """Set up the binary_sensor platform.""" + locks = [] + + coordinator: DataUpdateCoordinator[list[ToyotaVehicle]] = hass.data[DOMAIN][ + config_entry.entry_id + ]["coordinator"] + + for vehicle in coordinator.data: + locks.append( + ToyotaLock( + coordinator, + "", + vehicle.vin, + ) + ) + + async_add_devices(locks, True) + + +class ToyotaLock(ToyotaNABaseEntity, LockEntity): + + _state_changing = False + + def __init__( + self, + vin, + *args: Any, + ): + super().__init__(vin, *args) + + @property + def icon(self): + return "mdi:car-key" + + @property + def is_locked(self): + _is_locked = True + + if self.vehicle is not None: + + all_locks = [ + feature + for feature in self.vehicle.features.values() + if isinstance(feature, ToyotaLockableOpening) + ] + + for lock in all_locks: + if lock.locked is False: + _is_locked = False + + else: + _is_locked = False + + return _is_locked + + @property + def is_locking(self): + return self._state_changing is True and self.is_locked is False + + @property + def is_unlocking(self): + return self._state_changing is True and self.is_locked is True + + async def async_lock(self, **kwargs): + """Lock all or specified locks. A code to lock the lock with may optionally be specified.""" + await self.toggle_lock(DOOR_LOCK) + + async def async_unlock(self, **kwargs): + """Unlock all or specified locks. A code to unlock the lock with may optionally be specified.""" + await self.toggle_lock(DOOR_UNLOCK) + + async def toggle_lock(self, command: str): + """Set the lock state via the provided command string.""" + if self.vehicle is not None: + self._state_changing = True + await self.vehicle.send_command(COMMAND_MAP[command]) + + # TODO: This works great and prevents us from unnecessarily hitting Toyota. But we can and should + # probably do stuff like this in the library where we can better control which APIs we hit to refresh our in-memory data. + self.coordinator.async_set_updated_data(self.coordinator.data) + await asyncio.sleep(10) + self._state_changing = False + await self.coordinator.async_request_refresh() + + @property + def available(self): + return self.vehicle is not None