bimmerconnected / bimmer_connected

🚘 Library to query the status of your BMW or Mini from the ConnectedDrive portal
Apache License 2.0
372 stars 81 forks source link

Add lock #19

Closed gerard33 closed 6 years ago

gerard33 commented 6 years ago

A bit tricky, but it's possible to add a lock to HA which can be used to lock/unlock the car.

The most logical use case would be that you can remotely lock the car if it's not locked.

This one can also be assigned to me, as I already wrote a lock component.

gerard33 commented 6 years ago

@ChristianKuehnel Allright, here is the first code for the lock. Can you please check? Most important part is howto import the remote_service script.

And I can't test how to import the remote_service part and try locking/unlocking at the moment, because my car as at the dealer for maintenance ;).

Show code

```py """ Support for BMW cars with BMW ConnectedDrive. For more details about this component, please refer to the documentation at https://home-assistant.io/components/lock.bmw_connected_drive/ """ import logging import asyncio import time from custom_components.bmw_connected_drive import DOMAIN as BMW_DOMAIN # from homeassistant.components.bmw_connected_drive import DOMAIN as BMW_DOMAIN # from custom_components.bmw_connected_drive.remote_services import( # RemoteServiceStatus, ExecutionState) # from homeassistant.components.bmw_connected_drive.remote_services import( # RemoteServiceStatus, ExecutionState) from homeassistant.components.lock import ENTITY_ID_FORMAT, LockDevice from homeassistant.const import STATE_LOCKED, STATE_UNLOCKED DEPENDENCIES = ['bmw_connected_drive'] _LOGGER = logging.getLogger(__name__) LOCKS = { 'lock': 'BMW Lock' } def setup_platform(hass, config, add_devices, discovery_info=None): """Setup the BMW Connected Drive lock.""" accounts = hass.data[BMW_DOMAIN] _LOGGER.debug('Found BMW accounts: %s', ', '.join([a.name for a in accounts])) devices = [] for account in accounts: for vehicle in account.account.vehicles: for key, value in sorted(LOCKS.items()): device = BMWLock(account, vehicle, key, value) devices.append(device) add_devices(devices) class BMWLock(LockDevice): """Representation of a BMW vehicle lock.""" def __init__(self, account, vehicle, attribute: str, sensor_name): """Initialize the lock.""" self._account = account self._vehicle = vehicle self._attribute = attribute self._name = sensor_name self._state = None @property def name(self): """Return the name of the lock.""" return self._name @property def device_state_attributes(self): """Return the state attributes of the lock.""" vehicle_state = self._vehicle.state return { 'last_update': vehicle_state.timestamp, 'car': self._vehicle.modelName, 'door_lock_state': vehicle_state.door_lock_state.value } @property def is_locked(self): """Return true if lock is locked.""" vehicle_state = self._vehicle.state return bool(vehicle_state.door_lock_state.value in ('LOCKED', 'SECURED')) def lock(self, **kwargs): """Lock the car.""" _LOGGER.warning("%s: locking doors", self._vehicle.modelName) ###info status = self._vehicle.remote_services.trigger_remote_door_lock() while status.state != ExecutionState.EXECUTED: status = self._vehicle.remote_services.get_remote_service_status() time.sleep(1) # use time.sleep() or something else? def unlock(self, **kwargs): """Unlock the car.""" _LOGGER.warning("%s: unlocking doors", self._vehicle.modelName) ###info status = self._vehicle.remote_services.trigger_remote_door_unlock() while status.state != ExecutionState.EXECUTED: status = self._vehicle.remote_services.get_remote_service_status() time.sleep(1) # use time.sleep() or something else? def update(self): """Update state of the lock.""" _LOGGER.info("%s: updating data for %s", self._vehicle.modelName, self._attribute) vehicle_state = self._vehicle.state # LOCKED, SECURED, SELECTIVELOCKED, UNLOCKED self._state = (STATE_LOCKED if vehicle_state.door_lock_state.value in ('LOCKED', 'SECURED') else STATE_UNLOCKED) self.schedule_update_ha_state() @asyncio.coroutine def async_added_to_hass(self): """Add callback after being added to hass. Show latest data after startup. """ self._account.add_update_listener(self.update) yield from self.hass.async_add_job(self.update) ```

ChristianKuehnel commented 6 years ago

Hi @gerard33 , you were quite busy yesterday. I again downloaded you code and added it to the same branch as the last change: https://github.com/ChristianKuehnel/home-assistant/commit/eec6d7bf1d41919baea7665ad14fdef6b80c5c93

The current solution seems to be working, but I'm not 100% happy with the solution:

I guess we can start with the current solution and improve as we go along...

gerard33 commented 6 years ago

When locking/unlocking the car from HA, it would be nice if the binary sensors also gets updated, so that the lock state is also directly visible in the Door lock state binary sensor. What are your thoughts on this?

ChristianKuehnel commented 6 years ago

My understanding of the flow is:

  1. trigger remote service for locking/unlocking
  2. poll the service status until we get "EXECUTED". This takes around 10-30 seconds
  3. Then wait a while (e.g. 10 seconds) until the vehicle has sent a new state update to the server.
  4. Fetch the new state from the server.

My problem is, step 3: It's not predictable how long we need to wait until we get new data from the server.

I already added a _trigger_state_update() to all remote service calls that change the vehicle state. But as I said, this takes around 20-40 seconds between the customer triggering the lock/unlock until we have a proper feedback from the vehicle. Right now I have no idea how to speed this up.

So my proposal would be: The user of the library (in our case the HASS sensor) has to handle that problem, as there is no good generic solution for this.

gerard33 commented 6 years ago

I agree with your proposal.

Closing this as the locked is part of the next HA release https://github.com/home-assistant/home-assistant/pull/12591.

lock[bot] commented 4 years ago

This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.