mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 13:17:32 +00:00
Add sensors for BMW electric cars (#14293)
* Add sensors for electric cars * Updates based on review of @MartinHjelmare * Fix Travis error * Another fix for Travis
This commit is contained in:
parent
48b13cc865
commit
ba7333e804
@ -17,9 +17,19 @@ _LOGGER = logging.getLogger(__name__)
|
|||||||
SENSOR_TYPES = {
|
SENSOR_TYPES = {
|
||||||
'lids': ['Doors', 'opening'],
|
'lids': ['Doors', 'opening'],
|
||||||
'windows': ['Windows', 'opening'],
|
'windows': ['Windows', 'opening'],
|
||||||
'door_lock_state': ['Door lock state', 'safety']
|
'door_lock_state': ['Door lock state', 'safety'],
|
||||||
|
'lights_parking': ['Parking lights', 'light'],
|
||||||
|
'condition_based_services': ['Condition based services', 'problem'],
|
||||||
|
'check_control_messages': ['Control messages', 'problem']
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SENSOR_TYPES_ELEC = {
|
||||||
|
'charging_status': ['Charging status', 'power'],
|
||||||
|
'connection_status': ['Connection status', 'plug']
|
||||||
|
}
|
||||||
|
|
||||||
|
SENSOR_TYPES_ELEC.update(SENSOR_TYPES)
|
||||||
|
|
||||||
|
|
||||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
"""Set up the BMW sensors."""
|
"""Set up the BMW sensors."""
|
||||||
@ -29,10 +39,18 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||||||
devices = []
|
devices = []
|
||||||
for account in accounts:
|
for account in accounts:
|
||||||
for vehicle in account.account.vehicles:
|
for vehicle in account.account.vehicles:
|
||||||
for key, value in sorted(SENSOR_TYPES.items()):
|
if vehicle.has_hv_battery:
|
||||||
device = BMWConnectedDriveSensor(account, vehicle, key,
|
_LOGGER.debug('BMW with a high voltage battery')
|
||||||
value[0], value[1])
|
for key, value in sorted(SENSOR_TYPES_ELEC.items()):
|
||||||
devices.append(device)
|
device = BMWConnectedDriveSensor(account, vehicle, key,
|
||||||
|
value[0], value[1])
|
||||||
|
devices.append(device)
|
||||||
|
elif vehicle.has_internal_combustion_engine:
|
||||||
|
_LOGGER.debug('BMW with an internal combustion engine')
|
||||||
|
for key, value in sorted(SENSOR_TYPES.items()):
|
||||||
|
device = BMWConnectedDriveSensor(account, vehicle, key,
|
||||||
|
value[0], value[1])
|
||||||
|
devices.append(device)
|
||||||
add_devices(devices, True)
|
add_devices(devices, True)
|
||||||
|
|
||||||
|
|
||||||
@ -92,12 +110,41 @@ class BMWConnectedDriveSensor(BinarySensorDevice):
|
|||||||
result[window.name] = window.state.value
|
result[window.name] = window.state.value
|
||||||
elif self._attribute == 'door_lock_state':
|
elif self._attribute == 'door_lock_state':
|
||||||
result['door_lock_state'] = vehicle_state.door_lock_state.value
|
result['door_lock_state'] = vehicle_state.door_lock_state.value
|
||||||
|
result['last_update_reason'] = vehicle_state.last_update_reason
|
||||||
|
elif self._attribute == 'lights_parking':
|
||||||
|
result['lights_parking'] = vehicle_state.parking_lights.value
|
||||||
|
elif self._attribute == 'condition_based_services':
|
||||||
|
for report in vehicle_state.condition_based_services:
|
||||||
|
service_type = report.service_type.lower().replace('_', ' ')
|
||||||
|
result['{} status'.format(service_type)] = report.state.value
|
||||||
|
if report.due_date is not None:
|
||||||
|
result['{} date'.format(service_type)] = \
|
||||||
|
report.due_date.strftime('%Y-%m-%d')
|
||||||
|
if report.due_distance is not None:
|
||||||
|
result['{} distance'.format(service_type)] = \
|
||||||
|
'{} km'.format(report.due_distance)
|
||||||
|
elif self._attribute == 'check_control_messages':
|
||||||
|
check_control_messages = vehicle_state.check_control_messages
|
||||||
|
if not check_control_messages:
|
||||||
|
result['check_control_messages'] = 'OK'
|
||||||
|
else:
|
||||||
|
result['check_control_messages'] = check_control_messages
|
||||||
|
elif self._attribute == 'charging_status':
|
||||||
|
result['charging_status'] = vehicle_state.charging_status.value
|
||||||
|
# pylint: disable=W0212
|
||||||
|
result['last_charging_end_result'] = \
|
||||||
|
vehicle_state._attributes['lastChargingEndResult']
|
||||||
|
if self._attribute == 'connection_status':
|
||||||
|
# pylint: disable=W0212
|
||||||
|
result['connection_status'] = \
|
||||||
|
vehicle_state._attributes['connectionStatus']
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def update(self):
|
def update(self):
|
||||||
"""Read new state data from the library."""
|
"""Read new state data from the library."""
|
||||||
from bimmer_connected.state import LockState
|
from bimmer_connected.state import LockState
|
||||||
|
from bimmer_connected.state import ChargingState
|
||||||
vehicle_state = self._vehicle.state
|
vehicle_state = self._vehicle.state
|
||||||
|
|
||||||
# device class opening: On means open, Off means closed
|
# device class opening: On means open, Off means closed
|
||||||
@ -111,6 +158,24 @@ class BMWConnectedDriveSensor(BinarySensorDevice):
|
|||||||
# Possible values: LOCKED, SECURED, SELECTIVE_LOCKED, UNLOCKED
|
# Possible values: LOCKED, SECURED, SELECTIVE_LOCKED, UNLOCKED
|
||||||
self._state = vehicle_state.door_lock_state not in \
|
self._state = vehicle_state.door_lock_state not in \
|
||||||
[LockState.LOCKED, LockState.SECURED]
|
[LockState.LOCKED, LockState.SECURED]
|
||||||
|
# device class light: On means light detected, Off means no light
|
||||||
|
if self._attribute == 'lights_parking':
|
||||||
|
self._state = vehicle_state.are_parking_lights_on
|
||||||
|
# device class problem: On means problem detected, Off means no problem
|
||||||
|
if self._attribute == 'condition_based_services':
|
||||||
|
self._state = not vehicle_state.are_all_cbs_ok
|
||||||
|
if self._attribute == 'check_control_messages':
|
||||||
|
self._state = vehicle_state.has_check_control_messages
|
||||||
|
# device class power: On means power detected, Off means no power
|
||||||
|
if self._attribute == 'charging_status':
|
||||||
|
self._state = vehicle_state.charging_status in \
|
||||||
|
[ChargingState.CHARGING]
|
||||||
|
# device class plug: On means device is plugged in,
|
||||||
|
# Off means device is unplugged
|
||||||
|
if self._attribute == 'connection_status':
|
||||||
|
# pylint: disable=W0212
|
||||||
|
self._state = (vehicle_state._attributes['connectionStatus'] ==
|
||||||
|
'CONNECTED')
|
||||||
|
|
||||||
def update_callback(self):
|
def update_callback(self):
|
||||||
"""Schedule a state update."""
|
"""Schedule a state update."""
|
||||||
|
@ -14,7 +14,7 @@ from homeassistant.helpers import discovery
|
|||||||
from homeassistant.helpers.event import track_utc_time_change
|
from homeassistant.helpers.event import track_utc_time_change
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
|
||||||
REQUIREMENTS = ['bimmer_connected==0.5.0']
|
REQUIREMENTS = ['bimmer_connected==0.5.1']
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -9,22 +9,12 @@ import logging
|
|||||||
|
|
||||||
from homeassistant.components.bmw_connected_drive import DOMAIN as BMW_DOMAIN
|
from homeassistant.components.bmw_connected_drive import DOMAIN as BMW_DOMAIN
|
||||||
from homeassistant.helpers.entity import Entity
|
from homeassistant.helpers.entity import Entity
|
||||||
|
from homeassistant.helpers.icon import icon_for_battery_level
|
||||||
|
|
||||||
DEPENDENCIES = ['bmw_connected_drive']
|
DEPENDENCIES = ['bmw_connected_drive']
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
LENGTH_ATTRIBUTES = {
|
|
||||||
'remaining_range_fuel': ['Range (fuel)', 'mdi:ruler'],
|
|
||||||
'mileage': ['Mileage', 'mdi:speedometer']
|
|
||||||
}
|
|
||||||
|
|
||||||
VALID_ATTRIBUTES = {
|
|
||||||
'remaining_fuel': ['Remaining Fuel', 'mdi:gas-station']
|
|
||||||
}
|
|
||||||
|
|
||||||
VALID_ATTRIBUTES.update(LENGTH_ATTRIBUTES)
|
|
||||||
|
|
||||||
|
|
||||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
"""Set up the BMW sensors."""
|
"""Set up the BMW sensors."""
|
||||||
@ -34,27 +24,26 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||||||
devices = []
|
devices = []
|
||||||
for account in accounts:
|
for account in accounts:
|
||||||
for vehicle in account.account.vehicles:
|
for vehicle in account.account.vehicles:
|
||||||
for key, value in sorted(VALID_ATTRIBUTES.items()):
|
for attribute_name in vehicle.drive_train_attributes:
|
||||||
device = BMWConnectedDriveSensor(account, vehicle, key,
|
device = BMWConnectedDriveSensor(account, vehicle,
|
||||||
value[0], value[1])
|
attribute_name)
|
||||||
devices.append(device)
|
devices.append(device)
|
||||||
|
device = BMWConnectedDriveSensor(account, vehicle, 'mileage')
|
||||||
|
devices.append(device)
|
||||||
add_devices(devices, True)
|
add_devices(devices, True)
|
||||||
|
|
||||||
|
|
||||||
class BMWConnectedDriveSensor(Entity):
|
class BMWConnectedDriveSensor(Entity):
|
||||||
"""Representation of a BMW vehicle sensor."""
|
"""Representation of a BMW vehicle sensor."""
|
||||||
|
|
||||||
def __init__(self, account, vehicle, attribute: str, sensor_name, icon):
|
def __init__(self, account, vehicle, attribute: str):
|
||||||
"""Constructor."""
|
"""Constructor."""
|
||||||
self._vehicle = vehicle
|
self._vehicle = vehicle
|
||||||
self._account = account
|
self._account = account
|
||||||
self._attribute = attribute
|
self._attribute = attribute
|
||||||
self._state = None
|
self._state = None
|
||||||
self._unit_of_measurement = None
|
|
||||||
self._name = '{} {}'.format(self._vehicle.name, self._attribute)
|
self._name = '{} {}'.format(self._vehicle.name, self._attribute)
|
||||||
self._unique_id = '{}-{}'.format(self._vehicle.vin, self._attribute)
|
self._unique_id = '{}-{}'.format(self._vehicle.vin, self._attribute)
|
||||||
self._sensor_name = sensor_name
|
|
||||||
self._icon = icon
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def should_poll(self) -> bool:
|
def should_poll(self) -> bool:
|
||||||
@ -74,7 +63,27 @@ class BMWConnectedDriveSensor(Entity):
|
|||||||
@property
|
@property
|
||||||
def icon(self):
|
def icon(self):
|
||||||
"""Icon to use in the frontend, if any."""
|
"""Icon to use in the frontend, if any."""
|
||||||
return self._icon
|
from bimmer_connected.state import ChargingState
|
||||||
|
vehicle_state = self._vehicle.state
|
||||||
|
charging_state = vehicle_state.charging_status in \
|
||||||
|
[ChargingState.CHARGING]
|
||||||
|
|
||||||
|
if self._attribute == 'mileage':
|
||||||
|
return 'mdi:speedometer'
|
||||||
|
elif self._attribute in (
|
||||||
|
'remaining_range_total', 'remaining_range_electric',
|
||||||
|
'remaining_range_fuel', 'max_range_electric'):
|
||||||
|
return 'mdi:ruler'
|
||||||
|
elif self._attribute == 'remaining_fuel':
|
||||||
|
return 'mdi:gas-station'
|
||||||
|
elif self._attribute == 'charging_time_remaining':
|
||||||
|
return 'mdi:update'
|
||||||
|
elif self._attribute == 'charging_status':
|
||||||
|
return 'mdi:battery-charging'
|
||||||
|
elif self._attribute == 'charging_level_hv':
|
||||||
|
return icon_for_battery_level(
|
||||||
|
battery_level=vehicle_state.charging_level_hv,
|
||||||
|
charging=charging_state)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def state(self):
|
def state(self):
|
||||||
@ -88,7 +97,17 @@ class BMWConnectedDriveSensor(Entity):
|
|||||||
@property
|
@property
|
||||||
def unit_of_measurement(self) -> str:
|
def unit_of_measurement(self) -> str:
|
||||||
"""Get the unit of measurement."""
|
"""Get the unit of measurement."""
|
||||||
return self._unit_of_measurement
|
if self._attribute in (
|
||||||
|
'mileage', 'remaining_range_total', 'remaining_range_electric',
|
||||||
|
'remaining_range_fuel', 'max_range_electric'):
|
||||||
|
return 'km'
|
||||||
|
elif self._attribute == 'remaining_fuel':
|
||||||
|
return 'l'
|
||||||
|
elif self._attribute == 'charging_time_remaining':
|
||||||
|
return 'h'
|
||||||
|
elif self._attribute == 'charging_level_hv':
|
||||||
|
return '%'
|
||||||
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def device_state_attributes(self):
|
def device_state_attributes(self):
|
||||||
@ -101,14 +120,10 @@ class BMWConnectedDriveSensor(Entity):
|
|||||||
"""Read new state data from the library."""
|
"""Read new state data from the library."""
|
||||||
_LOGGER.debug('Updating %s', self._vehicle.name)
|
_LOGGER.debug('Updating %s', self._vehicle.name)
|
||||||
vehicle_state = self._vehicle.state
|
vehicle_state = self._vehicle.state
|
||||||
self._state = getattr(vehicle_state, self._attribute)
|
if self._attribute == 'charging_status':
|
||||||
|
self._state = getattr(vehicle_state, self._attribute).value
|
||||||
if self._attribute in LENGTH_ATTRIBUTES:
|
|
||||||
self._unit_of_measurement = 'km'
|
|
||||||
elif self._attribute == 'remaining_fuel':
|
|
||||||
self._unit_of_measurement = 'l'
|
|
||||||
else:
|
else:
|
||||||
self._unit_of_measurement = None
|
self._state = getattr(vehicle_state, self._attribute)
|
||||||
|
|
||||||
def update_callback(self):
|
def update_callback(self):
|
||||||
"""Schedule a state update."""
|
"""Schedule a state update."""
|
||||||
|
@ -149,7 +149,7 @@ beautifulsoup4==4.6.0
|
|||||||
bellows==0.6.0
|
bellows==0.6.0
|
||||||
|
|
||||||
# homeassistant.components.bmw_connected_drive
|
# homeassistant.components.bmw_connected_drive
|
||||||
bimmer_connected==0.5.0
|
bimmer_connected==0.5.1
|
||||||
|
|
||||||
# homeassistant.components.blink
|
# homeassistant.components.blink
|
||||||
blinkpy==0.6.0
|
blinkpy==0.6.0
|
||||||
|
Loading…
x
Reference in New Issue
Block a user