Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@

DOMAIN = 'bmw_connected_drive'
CONF_REGION = 'region'

ATTR_VIN = 'vin'

ACCOUNT_SCHEMA = vol.Schema({
vol.Required(CONF_USERNAME): cv.string,
Expand All @@ -35,42 +35,89 @@
},
}, extra=vol.ALLOW_EXTRA)

SERVICE_SCHEMA = vol.Schema({
vol.Required(ATTR_VIN): cv.string,
})


BMW_COMPONENTS = ['binary_sensor', 'device_tracker', 'lock', 'sensor']
UPDATE_INTERVAL = 5 # in minutes

SERVICE_UPDATE_STATE = 'update_state'

def setup(hass, config):
_SERVICE_MAP = {
'light_flash': 'trigger_remote_light_flash',
'sound_horn': 'trigger_remote_horn',
'activate_air_conditioning': 'trigger_remote_air_conditioning',
}


def setup(hass, config: dict):
"""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]
region = account_config[CONF_REGION]
_LOGGER.debug('Adding new account %s', name)
bimmer = BMWConnectedDriveAccount(username, password, region, 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)
accounts.append(setup_account(account_config, hass, name))

hass.data[DOMAIN] = accounts

for account in accounts:
account.update()
def _update_all(call) -> None:
"""Update all BMW accounts."""
for cd_account in hass.data[DOMAIN]:
cd_account.update()

# Service to manually trigger updates for all accounts.
hass.services.register(DOMAIN, SERVICE_UPDATE_STATE, _update_all)

_update_all(None)

for component in BMW_COMPONENTS:
discovery.load_platform(hass, component, DOMAIN, {}, config)

return True


def setup_account(account_config: dict, hass, name: str) \
-> 'BMWConnectedDriveAccount':
"""Set up a new BMWConnectedDriveAccount based on the config."""
username = account_config[CONF_USERNAME]
password = account_config[CONF_PASSWORD]
region = account_config[CONF_REGION]
_LOGGER.debug('Adding new account %s', name)
cd_account = BMWConnectedDriveAccount(username, password, region, name)

def execute_service(call):
"""Execute a service for a vehicle.

This must be a member function as we need access to the cd_account
object here.
"""
vin = call.data[ATTR_VIN]
vehicle = cd_account.account.get_vehicle(vin)
if not vehicle:
_LOGGER.error('Could not find a vehicle for VIN "%s"!', vin)
return
function_name = _SERVICE_MAP[call.service]
function_call = getattr(vehicle.remote_services, function_name)
function_call()

# register the remote services
for service in _SERVICE_MAP:
hass.services.register(
DOMAIN, service,
execute_service,
schema=SERVICE_SCHEMA)

# 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, cd_account.update,
minute=range(now.minute % UPDATE_INTERVAL, 60, UPDATE_INTERVAL),
second=now.second)

return cd_account


class BMWConnectedDriveAccount(object):
"""Representation of a BMW vehicle."""

Expand Down
42 changes: 42 additions & 0 deletions homeassistant/components/bmw_connected_drive/services.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Describes the format for available services for bmw_connected_drive
#
# The services related to locking/unlocking are implemented in the lock
# component to avoid redundancy.

light_flash:
description: >
Flash the lights of the vehicle. The vehicle is identified via the vin
(see below).
fields:
vin:
description: >
The vehicle identification number (VIN) of the vehicle, 17 characters
example: WBANXXXXXX1234567

sound_horn:
description: >
Sound the horn of the vehicle. The vehicle is identified via the vin
(see below).
fields:
vin:
description: >
The vehicle identification number (VIN) of the vehicle, 17 characters
example: WBANXXXXXX1234567

activate_air_conditioning:
description: >
Start the air conditioning of the vehicle. What exactly is started here
depends on the type of vehicle. It might range from just ventilation over
auxilary heating to real air conditioning. The vehicle is identified via
the vin (see below).
fields:
vin:
description: >
The vehicle identification number (VIN) of the vehicle, 17 characters
example: WBANXXXXXX1234567

update_state:
description: >
Fetch the last state of the vehicles of all your accounts from the BMW
server. This does *not* trigger an update from the vehicle, it just gets
the data from the BMW servers. This service does not require any attributes.
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,11 @@ def update(self) -> None:
return

_LOGGER.debug('Updating %s', dev_id)

attrs = {
'vin': self.vehicle.vin,
}
self._see(
dev_id=dev_id, host_name=self.vehicle.name,
gps=self.vehicle.state.gps_position, icon='mdi:car'
gps=self.vehicle.state.gps_position, attributes=attrs,
icon='mdi:car'
)