-
-
Notifications
You must be signed in to change notification settings - Fork 31.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add new component: BMW connected drive (#12277)
* first working version of BMW connected drive sensor * extended coveragerc * fixed blank line * fixed pylint * major refactoring after major refactoring in bimmer_connected * Update are now triggered from BMWConnectedDriveVehicle. * removed polling from sensor and device_tracker * backend URL is not detected automatically based on current country * vehicles are discovered automatically * updates are async now resolves: * bimmerconnected/bimmer_connected#3 * bimmerconnected/bimmer_connected#5 * improved exception handing * fixed static analysis findings * fixed review comments from @MartinHjelmare * improved startup, data is updated right after sensors were created. * fixed pylint issue * updated to latest release of the bimmer_connected library * updated requirements-all.txt * fixed comments from @MartinHjelmare * calling self.update from async_add_job * removed unused attribute "account"
- Loading branch information
1 parent
5d29d88
commit 316eb59
Showing
6 changed files
with
263 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Validating CODEOWNERS rules …
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
""" | ||
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/bmw_connected_drive/ | ||
""" | ||
import logging | ||
import datetime | ||
|
||
import voluptuous as vol | ||
from homeassistant.helpers import discovery | ||
from homeassistant.helpers.event import track_utc_time_change | ||
|
||
import homeassistant.helpers.config_validation as cv | ||
from homeassistant.const import ( | ||
CONF_USERNAME, CONF_PASSWORD | ||
) | ||
|
||
REQUIREMENTS = ['bimmer_connected==0.3.0'] | ||
|
||
_LOGGER = logging.getLogger(__name__) | ||
|
||
DOMAIN = 'bmw_connected_drive' | ||
CONF_VALUES = 'values' | ||
CONF_COUNTRY = 'country' | ||
|
||
ACCOUNT_SCHEMA = vol.Schema({ | ||
vol.Required(CONF_USERNAME): cv.string, | ||
vol.Required(CONF_PASSWORD): cv.string, | ||
vol.Required(CONF_COUNTRY): cv.string, | ||
}) | ||
|
||
CONFIG_SCHEMA = vol.Schema({ | ||
DOMAIN: { | ||
cv.string: ACCOUNT_SCHEMA | ||
}, | ||
}, extra=vol.ALLOW_EXTRA) | ||
|
||
|
||
BMW_COMPONENTS = ['device_tracker', 'sensor'] | ||
UPDATE_INTERVAL = 5 # in minutes | ||
|
||
|
||
def setup(hass, config): | ||
"""Set up the BMW connected drive components.""" | ||
accounts = [] | ||
for name, account_config in config[DOMAIN].items(): | ||
username = account_config[CONF_USERNAME] | ||
password = account_config[CONF_PASSWORD] | ||
country = account_config[CONF_COUNTRY] | ||
_LOGGER.debug('Adding new account %s', name) | ||
bimmer = BMWConnectedDriveAccount(username, password, country, name) | ||
accounts.append(bimmer) | ||
|
||
# update every UPDATE_INTERVAL minutes, starting now | ||
# this should even out the load on the servers | ||
|
||
now = datetime.datetime.now() | ||
track_utc_time_change( | ||
hass, bimmer.update, | ||
minute=range(now.minute % UPDATE_INTERVAL, 60, UPDATE_INTERVAL), | ||
second=now.second) | ||
|
||
hass.data[DOMAIN] = accounts | ||
|
||
for account in accounts: | ||
account.update() | ||
|
||
for component in BMW_COMPONENTS: | ||
discovery.load_platform(hass, component, DOMAIN, {}, config) | ||
|
||
return True | ||
|
||
|
||
class BMWConnectedDriveAccount(object): | ||
"""Representation of a BMW vehicle.""" | ||
|
||
def __init__(self, username: str, password: str, country: str, | ||
name: str) -> None: | ||
"""Constructor.""" | ||
from bimmer_connected.account import ConnectedDriveAccount | ||
|
||
self.account = ConnectedDriveAccount(username, password, country) | ||
self.name = name | ||
self._update_listeners = [] | ||
|
||
def update(self, *_): | ||
"""Update the state of all vehicles. | ||
Notify all listeners about the update. | ||
""" | ||
_LOGGER.debug('Updating vehicle state for account %s, ' | ||
'notifying %d listeners', | ||
self.name, len(self._update_listeners)) | ||
try: | ||
self.account.update_vehicle_states() | ||
for listener in self._update_listeners: | ||
listener() | ||
except IOError as exception: | ||
_LOGGER.error('Error updating the vehicle state.') | ||
_LOGGER.exception(exception) | ||
|
||
def add_update_listener(self, listener): | ||
"""Add a listener for update notifications.""" | ||
self._update_listeners.append(listener) |
51 changes: 51 additions & 0 deletions
51
homeassistant/components/device_tracker/bmw_connected_drive.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
"""Device tracker for BMW Connected Drive vehicles. | ||
For more details about this platform, please refer to the documentation at | ||
https://home-assistant.io/components/device_tracker.bmw_connected_drive/ | ||
""" | ||
import logging | ||
|
||
from homeassistant.components.bmw_connected_drive import DOMAIN \ | ||
as BMW_DOMAIN | ||
from homeassistant.util import slugify | ||
|
||
DEPENDENCIES = ['bmw_connected_drive'] | ||
|
||
_LOGGER = logging.getLogger(__name__) | ||
|
||
|
||
def setup_scanner(hass, config, see, discovery_info=None): | ||
"""Set up the BMW tracker.""" | ||
accounts = hass.data[BMW_DOMAIN] | ||
_LOGGER.debug('Found BMW accounts: %s', | ||
', '.join([a.name for a in accounts])) | ||
for account in accounts: | ||
for vehicle in account.account.vehicles: | ||
tracker = BMWDeviceTracker(see, vehicle) | ||
account.add_update_listener(tracker.update) | ||
tracker.update() | ||
return True | ||
|
||
|
||
class BMWDeviceTracker(object): | ||
"""BMW Connected Drive device tracker.""" | ||
|
||
def __init__(self, see, vehicle): | ||
"""Initialize the Tracker.""" | ||
self._see = see | ||
self.vehicle = vehicle | ||
|
||
def update(self) -> None: | ||
"""Update the device info.""" | ||
dev_id = slugify(self.vehicle.modelName) | ||
_LOGGER.debug('Updating %s', dev_id) | ||
attrs = { | ||
'trackr_id': dev_id, | ||
'id': dev_id, | ||
'name': self.vehicle.modelName | ||
} | ||
self._see( | ||
dev_id=dev_id, host_name=self.vehicle.modelName, | ||
gps=self.vehicle.state.gps_position, attributes=attrs, | ||
icon='mdi:car' | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
""" | ||
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/sensor.bmw_connected_drive/ | ||
""" | ||
import logging | ||
import asyncio | ||
|
||
from homeassistant.components.bmw_connected_drive import DOMAIN as BMW_DOMAIN | ||
from homeassistant.helpers.entity import Entity | ||
|
||
DEPENDENCIES = ['bmw_connected_drive'] | ||
|
||
_LOGGER = logging.getLogger(__name__) | ||
|
||
LENGTH_ATTRIBUTES = [ | ||
'remaining_range_fuel', | ||
'mileage', | ||
] | ||
|
||
VALID_ATTRIBUTES = LENGTH_ATTRIBUTES + [ | ||
'remaining_fuel', | ||
] | ||
|
||
|
||
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 sensor in VALID_ATTRIBUTES: | ||
device = BMWConnectedDriveSensor(account, vehicle, sensor) | ||
devices.append(device) | ||
add_devices(devices) | ||
|
||
|
||
class BMWConnectedDriveSensor(Entity): | ||
"""Representation of a BMW vehicle sensor.""" | ||
|
||
def __init__(self, account, vehicle, attribute: str): | ||
"""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) | ||
|
||
@property | ||
def should_poll(self) -> bool: | ||
"""Data update is triggered from BMWConnectedDriveEntity.""" | ||
return False | ||
|
||
@property | ||
def name(self) -> str: | ||
"""Return the name of the sensor.""" | ||
return self._name | ||
|
||
@property | ||
def state(self): | ||
"""Return the state of the sensor. | ||
The return type of this call depends on the attribute that | ||
is configured. | ||
""" | ||
return self._state | ||
|
||
@property | ||
def unit_of_measurement(self) -> str: | ||
"""Get the unit of measurement.""" | ||
return self._unit_of_measurement | ||
|
||
def update(self) -> None: | ||
"""Read new state data from the library.""" | ||
_LOGGER.debug('Updating %s', self.entity_id) | ||
vehicle_state = self._vehicle.state | ||
self._state = getattr(vehicle_state, self._attribute) | ||
|
||
if self._attribute in LENGTH_ATTRIBUTES: | ||
self._unit_of_measurement = vehicle_state.unit_of_length | ||
elif self._attribute == 'remaining_fuel': | ||
self._unit_of_measurement = vehicle_state.unit_of_volume | ||
else: | ||
self._unit_of_measurement = None | ||
|
||
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters