1 """Reads vehicle status from BMW MyBMW portal."""
3 from __future__
import annotations
5 from collections.abc
import Callable
6 from dataclasses
import dataclass
10 from bimmer_connected.vehicle
import MyBMWVehicle
11 from bimmer_connected.vehicle.doors_windows
import LockState
12 from bimmer_connected.vehicle.fuel_and_battery
import ChargingState
13 from bimmer_connected.vehicle.reports
import ConditionBasedService
16 BinarySensorDeviceClass,
18 BinarySensorEntityDescription,
24 from .
import BMWConfigEntry
25 from .const
import UNIT_MAP
26 from .coordinator
import BMWDataUpdateCoordinator
27 from .entity
import BMWBaseEntity
29 _LOGGER = logging.getLogger(__name__)
32 ALLOWED_CONDITION_BASED_SERVICE_KEYS = {
44 LOGGED_CONDITION_BASED_SERVICE_WARNINGS: set[str] = set()
46 ALLOWED_CHECK_CONTROL_MESSAGE_KEYS = {
51 LOGGED_CHECK_CONTROL_MESSAGE_WARNINGS: set[str] = set()
55 vehicle: MyBMWVehicle, unit_system: UnitSystem
58 for report
in vehicle.condition_based_services.messages:
60 report.service_type
not in ALLOWED_CONDITION_BASED_SERVICE_KEYS
61 and report.service_type
not in LOGGED_CONDITION_BASED_SERVICE_WARNINGS
64 "'%s' not an allowed condition based service (%s)",
68 LOGGED_CONDITION_BASED_SERVICE_WARNINGS.add(report.service_type)
72 return extra_attributes
76 extra_attributes: dict[str, Any] = {}
77 for message
in vehicle.check_control_messages.messages:
79 message.description_short
not in ALLOWED_CHECK_CONTROL_MESSAGE_KEYS
80 and message.description_short
not in LOGGED_CHECK_CONTROL_MESSAGE_WARNINGS
83 "'%s' not an allowed check control message (%s)",
84 message.description_short,
87 LOGGED_CHECK_CONTROL_MESSAGE_WARNINGS.add(message.description_short)
90 extra_attributes[message.description_short.lower()] = message.state.value
91 return extra_attributes
95 report: ConditionBasedService, unit_system: UnitSystem
97 result: dict[str, Any] = {}
98 service_type = report.service_type.lower()
99 result[service_type] = report.state.value
100 if report.due_date
is not None:
101 result[f
"{service_type}_date"] = report.due_date.strftime(
"%Y-%m-%d")
102 if report.due_distance.value
and report.due_distance.unit:
105 report.due_distance.value,
106 UNIT_MAP.get(report.due_distance.unit, report.due_distance.unit),
109 result[f
"{service_type}_distance"] = f
"{distance} {unit_system.length_unit}"
113 @dataclass(frozen=True, kw_only=True)
115 """Describes BMW binary_sensor entity."""
117 value_fn: Callable[[MyBMWVehicle], bool]
118 attr_fn: Callable[[MyBMWVehicle, UnitSystem], dict[str, Any]] |
None =
None
119 is_available: Callable[[MyBMWVehicle], bool] =
lambda v: v.is_lsc_enabled
122 SENSOR_TYPES: tuple[BMWBinarySensorEntityDescription, ...] = (
125 translation_key=
"lids",
126 device_class=BinarySensorDeviceClass.OPENING,
128 value_fn=
lambda v:
not v.doors_and_windows.all_lids_closed,
129 attr_fn=
lambda v, u: {
130 lid.name: lid.state.value
for lid
in v.doors_and_windows.lids
135 translation_key=
"windows",
136 device_class=BinarySensorDeviceClass.OPENING,
138 value_fn=
lambda v:
not v.doors_and_windows.all_windows_closed,
139 attr_fn=
lambda v, u: {
140 window.name: window.state.value
for window
in v.doors_and_windows.windows
144 key=
"door_lock_state",
145 translation_key=
"door_lock_state",
146 device_class=BinarySensorDeviceClass.LOCK,
149 value_fn=
lambda v: v.doors_and_windows.door_lock_state
150 not in {LockState.LOCKED, LockState.SECURED},
151 attr_fn=
lambda v, u: {
152 "door_lock_state": v.doors_and_windows.door_lock_state.value
156 key=
"condition_based_services",
157 translation_key=
"condition_based_services",
158 device_class=BinarySensorDeviceClass.PROBLEM,
160 value_fn=
lambda v: v.condition_based_services.is_service_required,
161 attr_fn=_condition_based_services,
164 key=
"check_control_messages",
165 translation_key=
"check_control_messages",
166 device_class=BinarySensorDeviceClass.PROBLEM,
168 value_fn=
lambda v: v.check_control_messages.has_check_control_messages,
173 key=
"charging_status",
174 translation_key=
"charging_status",
175 device_class=BinarySensorDeviceClass.BATTERY_CHARGING,
177 value_fn=
lambda v: v.fuel_and_battery.charging_status == ChargingState.CHARGING,
178 is_available=
lambda v: v.has_electric_drivetrain,
181 key=
"connection_status",
182 translation_key=
"connection_status",
183 device_class=BinarySensorDeviceClass.PLUG,
184 value_fn=
lambda v: v.fuel_and_battery.is_charger_connected,
185 is_available=
lambda v: v.has_electric_drivetrain,
188 key=
"is_pre_entry_climatization_enabled",
189 translation_key=
"is_pre_entry_climatization_enabled",
190 value_fn=
lambda v: v.charging_profile.is_pre_entry_climatization_enabled
191 if v.charging_profile
193 is_available=
lambda v: v.has_electric_drivetrain,
200 config_entry: BMWConfigEntry,
201 async_add_entities: AddEntitiesCallback,
203 """Set up the BMW binary sensors from config entry."""
204 coordinator = config_entry.runtime_data.coordinator
208 for vehicle
in coordinator.account.vehicles
209 for description
in SENSOR_TYPES
210 if description.is_available(vehicle)
216 """Representation of a BMW vehicle binary sensor."""
218 entity_description: BMWBinarySensorEntityDescription
222 coordinator: BMWDataUpdateCoordinator,
223 vehicle: MyBMWVehicle,
224 description: BMWBinarySensorEntityDescription,
225 unit_system: UnitSystem,
227 """Initialize sensor."""
228 super().
__init__(coordinator, vehicle)
235 """Handle updated data from the coordinator."""
237 "Updating binary sensor '%s' of %s",
_attr_extra_state_attributes
None _handle_coordinator_update(self)
None __init__(self, BMWDataUpdateCoordinator coordinator, MyBMWVehicle vehicle, BMWBinarySensorEntityDescription description, UnitSystem unit_system)
dict[str, Any] _format_cbs_report(ConditionBasedService report, UnitSystem unit_system)
dict[str, Any] _condition_based_services(MyBMWVehicle vehicle, UnitSystem unit_system)
None async_setup_entry(HomeAssistant hass, BMWConfigEntry config_entry, AddEntitiesCallback async_add_entities)
dict[str, Any] _check_control_messages(MyBMWVehicle vehicle)