1 """Reads vehicle status from MyBMW portal."""
3 from __future__
import annotations
5 from dataclasses
import dataclass
8 import voluptuous
as vol
14 device_registry
as dr,
16 entity_registry
as er,
20 from .const
import ATTR_VIN, CONF_READ_ONLY, DOMAIN
21 from .coordinator
import BMWDataUpdateCoordinator
23 _LOGGER = logging.getLogger(__name__)
26 SERVICE_SCHEMA = vol.Schema(
28 {vol.Required(ATTR_VIN): cv.string},
29 {vol.Required(CONF_DEVICE_ID): cv.string},
34 CONF_READ_ONLY:
False,
38 Platform.BINARY_SENSOR,
40 Platform.DEVICE_TRACKER,
49 SERVICE_UPDATE_STATE =
"update_state"
52 type BMWConfigEntry = ConfigEntry[BMWData]
57 """Class to store BMW runtime data."""
59 coordinator: BMWDataUpdateCoordinator
64 hass: HomeAssistant, entry: ConfigEntry
66 data =
dict(entry.data)
67 options =
dict(entry.options)
69 if CONF_READ_ONLY
in data
or list(options) !=
list(DEFAULT_OPTIONS):
72 **{k: v
for k, v
in options.items()
if k
in DEFAULT_OPTIONS},
74 options[CONF_READ_ONLY] = data.pop(CONF_READ_ONLY,
False)
76 hass.config_entries.async_update_entry(entry, data=data, options=options)
80 hass: HomeAssistant, config_entry: BMWConfigEntry
82 """Migrate old entry."""
83 entity_registry = er.async_get(hass)
88 "charging_level_hv":
"fuel_and_battery.remaining_battery_percent",
89 "fuel_percent":
"fuel_and_battery.remaining_fuel_percent",
90 "ac_current_limit":
"charging_profile.ac_current_limit",
91 "charging_start_time":
"fuel_and_battery.charging_start_time",
92 "charging_end_time":
"fuel_and_battery.charging_end_time",
93 "charging_status":
"fuel_and_battery.charging_status",
94 "charging_target":
"fuel_and_battery.charging_target",
95 "remaining_battery_percent":
"fuel_and_battery.remaining_battery_percent",
96 "remaining_range_total":
"fuel_and_battery.remaining_range_total",
97 "remaining_range_electric":
"fuel_and_battery.remaining_range_electric",
98 "remaining_range_fuel":
"fuel_and_battery.remaining_range_fuel",
99 "remaining_fuel":
"fuel_and_battery.remaining_fuel",
100 "remaining_fuel_percent":
"fuel_and_battery.remaining_fuel_percent",
101 "activity":
"climate.activity",
103 if (key := entry.unique_id.split(
"-")[-1])
in replacements:
104 new_unique_id = entry.unique_id.replace(key, replacements[key])
106 "Migrating entity '%s' unique_id from '%s' to '%s'",
111 if existing_entity_id := entity_registry.async_get_entity_id(
112 entry.domain, entry.platform, new_unique_id
115 "Cannot migrate to unique_id '%s', already exists for '%s'",
121 "new_unique_id": new_unique_id,
125 await er.async_migrate_entries(hass, config_entry.entry_id, update_unique_id)
131 """Set up BMW Connected Drive from a config entry."""
142 await coordinator.async_config_entry_first_refresh()
144 entry.runtime_data =
BMWData(coordinator)
147 await hass.config_entries.async_forward_entry_setups(
148 entry, [platform
for platform
in PLATFORMS
if platform != Platform.NOTIFY]
153 hass.async_create_task(
154 discovery.async_load_platform(
158 {CONF_NAME: DOMAIN, CONF_ENTITY_ID: entry.entry_id},
164 account_vehicles = {(DOMAIN, v.vin)
for v
in coordinator.account.vehicles}
165 device_registry = dr.async_get(hass)
166 device_entries = dr.async_entries_for_config_entry(
167 device_registry, config_entry_id=entry.entry_id
169 for device
in device_entries:
170 if not device.identifiers.intersection(account_vehicles):
171 device_registry.async_update_device(
172 device.id, remove_config_entry_id=entry.entry_id
179 """Unload a config entry."""
181 return await hass.config_entries.async_unload_platforms(
182 entry, [platform
for platform
in PLATFORMS
if platform != Platform.NOTIFY]
None _async_migrate_options_from_data_if_missing(HomeAssistant hass, ConfigEntry entry)
bool async_setup_entry(HomeAssistant hass, ConfigEntry entry)
bool _async_migrate_entries(HomeAssistant hass, BMWConfigEntry config_entry)
bool async_unload_entry(HomeAssistant hass, ConfigEntry entry)
dict[str, str]|None update_unique_id(er.RegistryEntry entity_entry, str unique_id)