diff --git a/homeassistant/components/binary_sensor/bmw_connected_drive.py b/homeassistant/components/binary_sensor/bmw_connected_drive.py new file mode 100644 index 0000000000000000000000000000000000000000..0c848a57fbf0832819ba8ea14dd837d56f6a1e8f --- /dev/null +++ b/homeassistant/components/binary_sensor/bmw_connected_drive.py @@ -0,0 +1,117 @@ +""" +Reads vehicle status from BMW connected drive portal. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/binary_sensor.bmw_connected_drive/ +""" +import asyncio +import logging + +from homeassistant.components.bmw_connected_drive import DOMAIN as BMW_DOMAIN +from homeassistant.components.binary_sensor import BinarySensorDevice + +DEPENDENCIES = ['bmw_connected_drive'] + +_LOGGER = logging.getLogger(__name__) + +SENSOR_TYPES = { + 'all_lids_closed': ['Doors', 'opening'], + 'all_windows_closed': ['Windows', 'opening'], + 'door_lock_state': ['Door lock state', 'safety'] +} + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """Set up the BMW sensors.""" + 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(SENSOR_TYPES.items()): + device = BMWConnectedDriveSensor(account, vehicle, key, + value[0], value[1]) + devices.append(device) + add_devices(devices, True) + + +class BMWConnectedDriveSensor(BinarySensorDevice): + """Representation of a BMW vehicle binary sensor.""" + + def __init__(self, account, vehicle, attribute: str, sensor_name, + device_class): + """Constructor.""" + self._account = account + self._vehicle = vehicle + self._attribute = attribute + self._name = sensor_name + self._device_class = device_class + self._state = None + + @property + def should_poll(self) -> bool: + """Data update is triggered from BMWConnectedDriveEntity.""" + return False + + @property + def name(self): + """Return the name of the binary sensor.""" + return self._name + + @property + def device_class(self): + """Return the class of the binary sensor.""" + return self._device_class + + @property + def is_on(self): + """Return the state of the binary sensor.""" + return self._state + + @property + def device_state_attributes(self): + """Return the state attributes of the binary sensor.""" + vehicle_state = self._vehicle.state + result = { + 'car': self._vehicle.modelName + } + + if self._attribute == 'all_lids_closed': + for lid in vehicle_state.lids: + result[lid.name] = lid.state.value + elif self._attribute == 'all_windows_closed': + for window in vehicle_state.windows: + result[window.name] = window.state.value + elif self._attribute == 'door_lock_state': + result['door_lock_state'] = vehicle_state.door_lock_state.value + + return result + + def update(self): + """Read new state data from the library.""" + vehicle_state = self._vehicle.state + + # device class opening: On means open, Off means closed + if self._attribute == 'all_lids_closed': + _LOGGER.debug("Status of lid: %s", vehicle_state.all_lids_closed) + self._state = not vehicle_state.all_lids_closed + if self._attribute == 'all_windows_closed': + self._state = not vehicle_state.all_windows_closed + # device class safety: On means unsafe, Off means safe + if self._attribute == 'door_lock_state': + # Possible values: LOCKED, SECURED, SELECTIVELOCKED, UNLOCKED + self._state = bool(vehicle_state.door_lock_state.value + in ('SELECTIVELOCKED', 'UNLOCKED')) + + def update_callback(self): + """Schedule a state update.""" + self.schedule_update_ha_state(True) + + @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_callback) diff --git a/homeassistant/components/bmw_connected_drive.py b/homeassistant/components/bmw_connected_drive.py index 86048a56e2239de6d6fb35ec5a1163d3a4dcc02c..9e9e2bafac5c036f676e1940e1e2bbec4976fc2e 100644 --- a/homeassistant/components/bmw_connected_drive.py +++ b/homeassistant/components/bmw_connected_drive.py @@ -37,7 +37,7 @@ CONFIG_SCHEMA = vol.Schema({ }, extra=vol.ALLOW_EXTRA) -BMW_COMPONENTS = ['device_tracker', 'sensor'] +BMW_COMPONENTS = ['binary_sensor', 'device_tracker', 'lock', 'sensor'] UPDATE_INTERVAL = 5 # in minutes diff --git a/homeassistant/components/lock/bmw_connected_drive.py b/homeassistant/components/lock/bmw_connected_drive.py new file mode 100644 index 0000000000000000000000000000000000000000..4592fd7cae9220bba2251d2341b76a538ef4bd8e --- /dev/null +++ b/homeassistant/components/lock/bmw_connected_drive.py @@ -0,0 +1,108 @@ +""" +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 asyncio +import logging + +from homeassistant.components.bmw_connected_drive import DOMAIN as BMW_DOMAIN +from homeassistant.components.lock import LockDevice +from homeassistant.const import STATE_LOCKED, STATE_UNLOCKED + +DEPENDENCIES = ['bmw_connected_drive'] + +_LOGGER = logging.getLogger(__name__) + + +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: + device = BMWLock(account, vehicle, 'lock', 'BMW lock') + devices.append(device) + add_devices(devices, True) + + +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 should_poll(self): + """Do not poll this class. + + Updates are triggered from BMWConnectedDriveAccount. + """ + return False + + @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 { + 'car': self._vehicle.modelName, + 'door_lock_state': vehicle_state.door_lock_state.value + } + + @property + def is_locked(self): + """Return true if lock is locked.""" + return self._state == STATE_LOCKED + + def lock(self, **kwargs): + """Lock the car.""" + _LOGGER.debug("%s: locking doors", self._vehicle.modelName) + # Optimistic state set here because it takes some time before the + # update callback response + self._state = STATE_LOCKED + self.schedule_update_ha_state() + self._vehicle.remote_services.trigger_remote_door_lock() + + def unlock(self, **kwargs): + """Unlock the car.""" + _LOGGER.debug("%s: unlocking doors", self._vehicle.modelName) + # Optimistic state set here because it takes some time before the + # update callback response + self._state = STATE_UNLOCKED + self.schedule_update_ha_state() + self._vehicle.remote_services.trigger_remote_door_unlock() + + def update(self): + """Update state of the lock.""" + _LOGGER.debug("%s: updating data for %s", self._vehicle.modelName, + self._attribute) + vehicle_state = self._vehicle.state + + # Possible values: LOCKED, SECURED, SELECTIVELOCKED, UNLOCKED + self._state = (STATE_LOCKED if vehicle_state.door_lock_state.value + in ('LOCKED', 'SECURED') else STATE_UNLOCKED) + + def update_callback(self): + """Schedule a state update.""" + self.schedule_update_ha_state(True) + + @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_callback) diff --git a/homeassistant/components/sensor/bmw_connected_drive.py b/homeassistant/components/sensor/bmw_connected_drive.py index 26bfd19e6fc6772bbf8168648e5d6acc3c3a64b1..76719763931fe84278f87508ea9671be01f15424 100644 --- a/homeassistant/components/sensor/bmw_connected_drive.py +++ b/homeassistant/components/sensor/bmw_connected_drive.py @@ -14,14 +14,16 @@ DEPENDENCIES = ['bmw_connected_drive'] _LOGGER = logging.getLogger(__name__) -LENGTH_ATTRIBUTES = [ - 'remaining_range_fuel', - 'mileage', - ] +LENGTH_ATTRIBUTES = { + 'remaining_range_fuel': ['Range (fuel)', 'mdi:ruler'], + 'mileage': ['Mileage', 'mdi:speedometer'] +} -VALID_ATTRIBUTES = LENGTH_ATTRIBUTES + [ - 'remaining_fuel', -] +VALID_ATTRIBUTES = { + 'remaining_fuel': ['Remaining Fuel', 'mdi:gas-station'] +} + +VALID_ATTRIBUTES.update(LENGTH_ATTRIBUTES) def setup_platform(hass, config, add_devices, discovery_info=None): @@ -32,23 +34,25 @@ def setup_platform(hass, config, add_devices, discovery_info=None): devices = [] for account in accounts: for vehicle in account.account.vehicles: - for sensor in VALID_ATTRIBUTES: - device = BMWConnectedDriveSensor(account, vehicle, sensor) + for key, value in sorted(VALID_ATTRIBUTES.items()): + device = BMWConnectedDriveSensor(account, vehicle, key, + value[0], value[1]) devices.append(device) - add_devices(devices) + add_devices(devices, True) class BMWConnectedDriveSensor(Entity): """Representation of a BMW vehicle sensor.""" - def __init__(self, account, vehicle, attribute: str): + def __init__(self, account, vehicle, attribute: str, sensor_name, icon): """Constructor.""" self._vehicle = vehicle self._account = account self._attribute = attribute self._state = None self._unit_of_measurement = None - self._name = '{} {}'.format(self._vehicle.modelName, self._attribute) + self._name = sensor_name + self._icon = icon @property def should_poll(self) -> bool: @@ -60,6 +64,11 @@ class BMWConnectedDriveSensor(Entity): """Return the name of the sensor.""" return self._name + @property + def icon(self): + """Icon to use in the frontend, if any.""" + return self._icon + @property def state(self): """Return the state of the sensor. @@ -74,9 +83,16 @@ class BMWConnectedDriveSensor(Entity): """Get the unit of measurement.""" return self._unit_of_measurement + @property + def device_state_attributes(self): + """Return the state attributes of the binary sensor.""" + return { + 'car': self._vehicle.modelName + } + def update(self) -> None: """Read new state data from the library.""" - _LOGGER.debug('Updating %s', self.entity_id) + _LOGGER.debug('Updating %s', self._vehicle.modelName) vehicle_state = self._vehicle.state self._state = getattr(vehicle_state, self._attribute) @@ -87,7 +103,9 @@ class BMWConnectedDriveSensor(Entity): else: self._unit_of_measurement = None - self.schedule_update_ha_state() + def update_callback(self): + """Schedule a state update.""" + self.schedule_update_ha_state(True) @asyncio.coroutine def async_added_to_hass(self): @@ -95,5 +113,4 @@ class BMWConnectedDriveSensor(Entity): Show latest data after startup. """ - self._account.add_update_listener(self.update) - yield from self.hass.async_add_job(self.update) + self._account.add_update_listener(self.update_callback)