diff --git a/custom_components/volkswagencarnet/__init__.py b/custom_components/volkswagencarnet/__init__.py index b6cf352..61659ec 100755 --- a/custom_components/volkswagencarnet/__init__.py +++ b/custom_components/volkswagencarnet/__init__.py @@ -20,7 +20,11 @@ from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.icon import icon_for_battery_level from homeassistant.helpers.restore_state import RestoreEntity -from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed, CoordinatorEntity +from homeassistant.helpers.update_coordinator import ( + DataUpdateCoordinator, + UpdateFailed, + CoordinatorEntity, +) from volkswagencarnet.vw_connection import Connection from volkswagencarnet.vw_dashboard import ( Instrument, @@ -147,15 +151,21 @@ def is_new(attr): return attr not in entry.options.get(CONF_AVAILABLE_RESOURCES, [attr]) components = set() - for instrument in (instrument for instrument in instruments if instrument.component in COMPONENTS): + for instrument in ( + instrument for instrument in instruments if instrument.component in COMPONENTS + ): # Add resource if it's enabled or new - if is_enabled(instrument.slug_attr) or (is_new(instrument.slug_attr) and not entry.pref_disable_new_entities): + if is_enabled(instrument.slug_attr) or ( + is_new(instrument.slug_attr) and not entry.pref_disable_new_entities + ): data.instruments.add(instrument) components.add(COMPONENTS[instrument.component]) for component in components: coordinator.platforms.append(component) - hass.async_create_task(hass.config_entries.async_forward_entry_setup(entry, component)) + hass.async_create_task( + hass.config_entries.async_forward_entry_setup(entry, component) + ) hass.data[DOMAIN][entry.entry_id] = { UPDATE_CALLBACK: update_callback, @@ -249,7 +259,9 @@ async def _async_update_listener(hass: HomeAssistant, entry: ConfigEntry) -> boo class VolkswagenData: """Hold component state.""" - def __init__(self, config: dict, coordinator: Optional[DataUpdateCoordinator] = None): + def __init__( + self, config: dict, coordinator: Optional[DataUpdateCoordinator] = None + ): """Initialize the component state.""" self.vehicles: set[Vehicle] = set() self.instruments: set[Instrument] = set() @@ -262,13 +274,21 @@ def instrument(self, vin: str, component: str, attr: str) -> Instrument: ret = next( ( instrument - for instrument in (self.coordinator.data if self.coordinator is not None else self.instruments) - if instrument.vehicle.vin == vin and instrument.component == component and instrument.attr == attr + for instrument in ( + self.coordinator.data + if self.coordinator is not None + else self.instruments + ) + if instrument.vehicle.vin == vin + and instrument.component == component + and instrument.attr == attr ), None, ) if ret is None: - raise ValueError(f"Instrument not found; component: {component}, attribute: {attr}") + raise ValueError( + f"Instrument not found; component: {component}, attribute: {attr}" + ) return ret def vehicle_name(self, vehicle: Vehicle) -> str: @@ -320,7 +340,11 @@ def async_write_ha_state(self) -> None: prev: Optional[State] = self.hass.states.get(self.entity_id) # This is not the best place to handle this, but... :shrug:.. - if self.attribute == "requests_remaining" and self.state in [-1, STATE_UNAVAILABLE, STATE_UNKNOWN]: + if self.attribute == "requests_remaining" and self.state in [ + -1, + STATE_UNAVAILABLE, + STATE_UNKNOWN, + ]: restored = prev or self.restored_state if restored is not None: try: @@ -340,12 +364,15 @@ def async_write_ha_state(self) -> None: # but the stored one is a string... sigh) if ( prev is None - or str(prev.attributes.get("last_updated", None)) != str(backend_refresh_time) + or str(prev.attributes.get("last_updated", None)) + != str(backend_refresh_time) or str(self.state or STATE_UNKNOWN) != str(prev.state) ): super().async_write_ha_state() else: - _LOGGER.debug(f"{self.name}: state not changed ('{prev.state}' == '{self.state}'), skipping update.") + _LOGGER.debug( + f"{self.name}: state not changed ('{prev.state}' == '{self.state}'), skipping update." + ) @callback def _handle_coordinator_update(self) -> None: @@ -369,14 +396,22 @@ async def async_added_to_hass(self) -> None: """Register update dispatcher.""" self.restored_state = await self.async_get_last_state() if self.coordinator is not None: - self.async_on_remove(self.coordinator.async_add_listener(self.async_write_ha_state)) + self.async_on_remove( + self.coordinator.async_add_listener(self.async_write_ha_state) + ) else: - self.async_on_remove(async_dispatcher_connect(self.hass, SIGNAL_STATE_UPDATED, self.async_write_ha_state)) + self.async_on_remove( + async_dispatcher_connect( + self.hass, SIGNAL_STATE_UPDATED, self.async_write_ha_state + ) + ) @property def instrument( self, - ) -> Union[BinarySensor, Climate, DoorLock, Position, Sensor, Switch, TrunkLock, Instrument]: + ) -> Union[ + BinarySensor, Climate, DoorLock, Position, Sensor, Switch, TrunkLock, Instrument + ]: """Return corresponding instrument.""" return self.data.instrument(self.vin, self.component, self.attribute) @@ -384,7 +419,9 @@ def instrument( def icon(self) -> Optional[str]: """Return the icon.""" if self.instrument.attr in ["battery_level", "charging"]: - return icon_for_battery_level(battery_level=self.instrument.state, charging=self.vehicle.charging) + return icon_for_battery_level( + battery_level=self.instrument.state, charging=self.vehicle.charging + ) else: return self.instrument.icon @@ -464,7 +501,9 @@ def notify_updated(self): class VolkswagenCoordinator(DataUpdateCoordinator): """Class to manage fetching data from the API.""" - def __init__(self, hass: HomeAssistant, entry: ConfigEntry, update_interval: timedelta): + def __init__( + self, hass: HomeAssistant, entry: ConfigEntry, update_interval: timedelta + ): """Initialize the coordinator.""" self.vin = entry.data[CONF_VEHICLE].upper() self.entry = entry @@ -474,7 +513,9 @@ def __init__(self, hass: HomeAssistant, entry: ConfigEntry, update_interval: tim session=async_get_clientsession(hass), username=self.entry.data[CONF_USERNAME], password=self.entry.data[CONF_PASSWORD], - fulldebug=self.entry.options.get(CONF_DEBUG, self.entry.data.get(CONF_DEBUG, DEFAULT_DEBUG)), + fulldebug=self.entry.options.get( + CONF_DEBUG, self.entry.data.get(CONF_DEBUG, DEFAULT_DEBUG) + ), country=self.entry.options.get(CONF_REGION, self.entry.data[CONF_REGION]), ) @@ -482,7 +523,9 @@ def __init__(self, hass: HomeAssistant, entry: ConfigEntry, update_interval: tim def async_update_listener(self) -> None: """Listen for update events.""" - _LOGGER.debug(f"Async update finished for {self.vin} ({self.name}). Next update in {self.update_interval}.") + _LOGGER.debug( + f"Async update finished for {self.vin} ({self.name}). Next update in {self.update_interval}." + ) async def _async_update_data(self) -> list[Instrument]: """Update data via library.""" @@ -494,10 +537,14 @@ async def _async_update_data(self) -> list[Instrument]: "Try logging in to the portal: https://www.portal.volkswagen-we.com/" ) - if self.entry.options.get(CONF_REPORT_REQUEST, self.entry.data.get(CONF_REPORT_REQUEST, False)): + if self.entry.options.get( + CONF_REPORT_REQUEST, self.entry.data.get(CONF_REPORT_REQUEST, False) + ): await self.report_request(vehicle) - convert_conf = self.entry.options.get(CONF_CONVERT, self.entry.data.get(CONF_CONVERT, CONF_NO_CONVERSION)) + convert_conf = self.entry.options.get( + CONF_CONVERT, self.entry.data.get(CONF_CONVERT, CONF_NO_CONVERSION) + ) dashboard = vehicle.dashboard( mutable=self.entry.data.get(CONF_MUTABLE), @@ -552,7 +599,10 @@ async def update(self) -> Optional[Vehicle]: async def report_request(self, vehicle: Vehicle) -> None: """Request car to report itself an update to Volkswagen WeConnect.""" report_interval = self.entry.options.get( - CONF_REPORT_SCAN_INTERVAL, self.entry.data.get(CONF_REPORT_SCAN_INTERVAL, DEFAULT_REPORT_UPDATE_INTERVAL) + CONF_REPORT_SCAN_INTERVAL, + self.entry.data.get( + CONF_REPORT_SCAN_INTERVAL, DEFAULT_REPORT_UPDATE_INTERVAL + ), ) if not self.report_last_updated: diff --git a/custom_components/volkswagencarnet/binary_sensor.py b/custom_components/volkswagencarnet/binary_sensor.py index 91912f6..9b90697 100644 --- a/custom_components/volkswagencarnet/binary_sensor.py +++ b/custom_components/volkswagencarnet/binary_sensor.py @@ -2,7 +2,11 @@ import logging from typing import Union -from homeassistant.components.binary_sensor import DEVICE_CLASSES, BinarySensorEntity, BinarySensorDeviceClass +from homeassistant.components.binary_sensor import ( + DEVICE_CLASSES, + BinarySensorEntity, + BinarySensorDeviceClass, +) from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.helpers.entity import EntityCategory @@ -13,7 +17,9 @@ _LOGGER = logging.getLogger(__name__) -async def async_setup_platform(hass: HomeAssistant, config: ConfigEntry, async_add_entities, discovery_info=None): +async def async_setup_platform( + hass: HomeAssistant, config: ConfigEntry, async_add_entities, discovery_info=None +): """Set up the Volkswagen binary sensors platform.""" if discovery_info is None: return @@ -33,7 +39,11 @@ async def async_setup_entry(hass, entry, async_add_devices): attribute=instrument.attr, callback=hass.data[DOMAIN][entry.entry_id][UPDATE_CALLBACK], ) - for instrument in (instrument for instrument in data.instruments if instrument.component == "binary_sensor") + for instrument in ( + instrument + for instrument in data.instruments + if instrument.component == "binary_sensor" + ) ) return True diff --git a/custom_components/volkswagencarnet/climate.py b/custom_components/volkswagencarnet/climate.py index d7e92b4..c78278b 100644 --- a/custom_components/volkswagencarnet/climate.py +++ b/custom_components/volkswagencarnet/climate.py @@ -34,8 +34,17 @@ async def async_setup_entry(hass, entry, async_add_devices): coordinator = data.coordinator if coordinator.data is not None: async_add_devices( - VolkswagenClimate(data=data, vin=coordinator.vin, component=instrument.component, attribute=instrument.attr) - for instrument in (instrument for instrument in data.instruments if instrument.component == "climate") + VolkswagenClimate( + data=data, + vin=coordinator.vin, + component=instrument.component, + attribute=instrument.attr, + ) + for instrument in ( + instrument + for instrument in data.instruments + if instrument.component == "climate" + ) ) return True diff --git a/custom_components/volkswagencarnet/config_flow.py b/custom_components/volkswagencarnet/config_flow.py index c29d3b4..6a84b94 100644 --- a/custom_components/volkswagencarnet/config_flow.py +++ b/custom_components/volkswagencarnet/config_flow.py @@ -85,7 +85,9 @@ async def async_step_user(self, user_input=None): return await self.async_step_login() - return self.async_show_form(step_id="user", data_schema=vol.Schema(DATA_SCHEMA), errors=self._errors) + return self.async_show_form( + step_id="user", data_schema=vol.Schema(DATA_SCHEMA), errors=self._errors + ) # noinspection PyBroadException async def _async_task_login(self): @@ -98,7 +100,9 @@ async def _async_task_login(self): if not self._connection.logged_in: self._errors["base"] = "cannot_connect" - self.hass.async_create_task(self.hass.config_entries.flow.async_configure(flow_id=self.flow_id)) + self.hass.async_create_task( + self.hass.config_entries.flow.async_configure(flow_id=self.flow_id) + ) async def async_step_select_vehicle(self, user_input=None): if user_input is not None: @@ -115,7 +119,9 @@ async def async_step_select_vehicle(self, user_input=None): async def async_step_select_instruments(self, user_input=None): instruments = self._init_info["CONF_VEHICLES"][self._init_info[CONF_VEHICLE]] - instruments_dict = {instrument.attr: instrument.name for instrument in instruments} + instruments_dict = { + instrument.attr: instrument.name for instrument in instruments + } if user_input is not None: # self._init_info[CONF_RESOURCES] = user_input[CONF_RESOURCES] @@ -130,14 +136,21 @@ async def async_step_select_instruments(self, user_input=None): return self.async_create_entry( title=self._init_info[CONF_NAME], data=self._init_info, - options={CONF_RESOURCES: user_input[CONF_RESOURCES], CONF_AVAILABLE_RESOURCES: instruments_dict}, + options={ + CONF_RESOURCES: user_input[CONF_RESOURCES], + CONF_AVAILABLE_RESOURCES: instruments_dict, + }, ) return self.async_show_form( step_id="select_instruments", errors=self._errors, data_schema=vol.Schema( - {vol.Optional(CONF_RESOURCES, default=list(instruments_dict.keys())): cv.multi_select(instruments_dict)} + { + vol.Optional( + CONF_RESOURCES, default=list(instruments_dict.keys()) + ): cv.multi_select(instruments_dict) + } ), last_step=True, ) @@ -164,7 +177,8 @@ async def async_step_login(self, user_input=None): _LOGGER.info(f"Found data for VIN: {vehicle.vin} from WeConnect") self._init_info["CONF_VEHICLES"] = { - vehicle.vin: vehicle.dashboard().instruments for vehicle in self._connection.vehicles + vehicle.vin: vehicle.dashboard().instruments + for vehicle in self._connection.vehicles } return self.async_show_progress_done(next_step_id="select_vehicle") @@ -184,8 +198,12 @@ async def async_step_reauth_confirm(self, user_input: dict = None) -> dict: session=async_get_clientsession(self.hass), username=user_input[CONF_USERNAME], password=user_input[CONF_PASSWORD], - fulldebug=self.entry.options.get(CONF_DEBUG, self.entry.data.get(CONF_DEBUG, DEFAULT_DEBUG)), - country=self.entry.options.get(CONF_REGION, self.entry.data[CONF_REGION]), + fulldebug=self.entry.options.get( + CONF_DEBUG, self.entry.data.get(CONF_DEBUG, DEFAULT_DEBUG) + ), + country=self.entry.options.get( + CONF_REGION, self.entry.data[CONF_REGION] + ), ) # noinspection PyBroadException @@ -207,7 +225,9 @@ async def async_step_reauth_confirm(self, user_input: dict = None) -> dict: CONF_PASSWORD: user_input[CONF_PASSWORD], }, ) - self.hass.async_create_task(self.hass.config_entries.async_reload(self.entry.entry_id)) + self.hass.async_create_task( + self.hass.config_entries.async_reload(self.entry.entry_id) + ) return self.async_abort(reason="reauth_successful") except Exception as e: @@ -218,7 +238,9 @@ async def async_step_reauth_confirm(self, user_input: dict = None) -> dict: step_id="reauth_confirm", data_schema=vol.Schema( { - vol.Required(CONF_USERNAME, default=self.entry.data[CONF_USERNAME]): str, + vol.Required( + CONF_USERNAME, default=self.entry.data[CONF_USERNAME] + ): str, vol.Required(CONF_PASSWORD): str, } ), @@ -261,34 +283,47 @@ async def async_step_user(self, user_input=None): vol.Optional( CONF_REPORT_REQUEST, default=self._config_entry.options.get( - CONF_REPORT_REQUEST, self._config_entry.data.get(CONF_REPORT_REQUEST, False) + CONF_REPORT_REQUEST, + self._config_entry.data.get(CONF_REPORT_REQUEST, False), ), ): cv.boolean, vol.Optional( CONF_DEBUG, default=self._config_entry.options.get( - CONF_DEBUG, self._config_entry.data.get(CONF_DEBUG, DEFAULT_DEBUG) + CONF_DEBUG, + self._config_entry.data.get(CONF_DEBUG, DEFAULT_DEBUG), ), ): cv.boolean, vol.Optional( - CONF_CONVERT, default=self._config_entry.data.get(CONF_CONVERT, CONF_NO_CONVERSION) + CONF_CONVERT, + default=self._config_entry.data.get( + CONF_CONVERT, CONF_NO_CONVERSION + ), ): vol.In(CONVERT_DICT), vol.Optional( CONF_REPORT_SCAN_INTERVAL, default=self._config_entry.options.get( CONF_REPORT_SCAN_INTERVAL, - self._config_entry.data.get(CONF_REPORT_SCAN_INTERVAL, DEFAULT_REPORT_UPDATE_INTERVAL), + self._config_entry.data.get( + CONF_REPORT_SCAN_INTERVAL, + DEFAULT_REPORT_UPDATE_INTERVAL, + ), ), ): cv.positive_int, vol.Optional( CONF_SCAN_INTERVAL, default=self._config_entry.options.get( - CONF_SCAN_INTERVAL, self._config_entry.data.get(CONF_SCAN_INTERVAL, DEFAULT_UPDATE_INTERVAL) + CONF_SCAN_INTERVAL, + self._config_entry.data.get( + CONF_SCAN_INTERVAL, DEFAULT_UPDATE_INTERVAL + ), ), ): cv.positive_int, vol.Optional( CONF_REGION, - default=self._config_entry.options.get(CONF_REGION, self._config_entry.data[CONF_REGION]), + default=self._config_entry.options.get( + CONF_REGION, self._config_entry.data[CONF_REGION] + ), ): str, } ), @@ -301,7 +336,9 @@ async def async_step_select_instruments(self, user_input=None): v: Vehicle = get_vehicle(coordinator=coordinator) d = v.dashboard() - instruments_dict = {instrument.attr: instrument.name for instrument in d.instruments} + instruments_dict = { + instrument.attr: instrument.name for instrument in d.instruments + } if user_input is not None: options = { @@ -310,18 +347,24 @@ async def async_step_select_instruments(self, user_input=None): CONF_AVAILABLE_RESOURCES: instruments_dict, } - removed_resources = set(data.get("options", {}).get("resources", {})) - set(options[CONF_RESOURCES]) - added_resources = set(options[CONF_RESOURCES]) - set(data.get("options", {}).get("resources", {})) + removed_resources = set(data.get("options", {}).get("resources", {})) - set( + options[CONF_RESOURCES] + ) + added_resources = set(options[CONF_RESOURCES]) - set( + data.get("options", {}).get("resources", {}) + ) - _LOGGER.info(f"Adding resources: {added_resources}, removing resources: {removed_resources}") + _LOGGER.info( + f"Adding resources: {added_resources}, removing resources: {removed_resources}" + ) # Need to recreate entitiesin some cases # Some resource was removed recreate_entities = len(removed_resources) > 0 # distance conversion was changed - recreate_entities = recreate_entities or self._data[CONF_CONVERT] != data.get("data", {}).get( - CONF_CONVERT, "" - ) + recreate_entities = recreate_entities or self._data[ + CONF_CONVERT + ] != data.get("data", {}).get(CONF_CONVERT, "") if recreate_entities: entity_registry = async_get(self.hass) @@ -346,7 +389,11 @@ async def async_step_select_instruments(self, user_input=None): step_id="select_instruments", errors=self._errors, data_schema=vol.Schema( - {vol.Optional(CONF_RESOURCES, default=list(selected)): cv.multi_select(instruments_dict)} + { + vol.Optional( + CONF_RESOURCES, default=list(selected) + ): cv.multi_select(instruments_dict) + } ), last_step=True, ) diff --git a/custom_components/volkswagencarnet/device_tracker.py b/custom_components/volkswagencarnet/device_tracker.py index 2a512de..b48bfb3 100644 --- a/custom_components/volkswagencarnet/device_tracker.py +++ b/custom_components/volkswagencarnet/device_tracker.py @@ -21,10 +21,15 @@ async def async_setup_entry(hass: HomeAssistant, entry, async_add_devices): if coordinator.data is not None: async_add_devices( VolkswagenDeviceTracker( - data=data, vin=coordinator.vin, component=instrument.component, attribute=instrument.attr + data=data, + vin=coordinator.vin, + component=instrument.component, + attribute=instrument.attr, ) for instrument in ( - instrument for instrument in data.instruments if instrument.component == "device_tracker" + instrument + for instrument in data.instruments + if instrument.component == "device_tracker" ) ) diff --git a/custom_components/volkswagencarnet/lock.py b/custom_components/volkswagencarnet/lock.py index 0e4a54a..d3d3170 100644 --- a/custom_components/volkswagencarnet/lock.py +++ b/custom_components/volkswagencarnet/lock.py @@ -24,8 +24,17 @@ async def async_setup_entry(hass, entry, async_add_devices): coordinator = data.coordinator if coordinator.data is not None: async_add_devices( - VolkswagenLock(data=data, vin=coordinator.vin, component=instrument.component, attribute=instrument.attr) - for instrument in (instrument for instrument in data.instruments if instrument.component == "lock") + VolkswagenLock( + data=data, + vin=coordinator.vin, + component=instrument.component, + attribute=instrument.attr, + ) + for instrument in ( + instrument + for instrument in data.instruments + if instrument.component == "lock" + ) ) return True diff --git a/custom_components/volkswagencarnet/sensor.py b/custom_components/volkswagencarnet/sensor.py index e2c86be..63ff620 100644 --- a/custom_components/volkswagencarnet/sensor.py +++ b/custom_components/volkswagencarnet/sensor.py @@ -19,7 +19,9 @@ _LOGGER = logging.getLogger(__name__) -async def async_setup_platform(hass: HomeAssistant, config: ConfigEntry, async_add_entities, discovery_info=None): +async def async_setup_platform( + hass: HomeAssistant, config: ConfigEntry, async_add_entities, discovery_info=None +): """Set up the Volkswagen sensors.""" if discovery_info is None: return @@ -39,7 +41,11 @@ async def async_setup_entry(hass, entry, async_add_devices): attribute=instrument.attr, callback=hass.data[DOMAIN][entry.entry_id][UPDATE_CALLBACK], ) - for instrument in (instrument for instrument in data.instruments if instrument.component == "sensor") + for instrument in ( + instrument + for instrument in data.instruments + if instrument.component == "sensor" + ) ) return True @@ -68,7 +74,10 @@ def native_unit_of_measurement(self): @property def device_class(self) -> Union[SensorDeviceClass, None]: """Return the device class.""" - if self.instrument.device_class is None or self.instrument.device_class in DEVICE_CLASSES: + if ( + self.instrument.device_class is None + or self.instrument.device_class in DEVICE_CLASSES + ): return self.instrument.device_class _LOGGER.warning(f"Unknown device class {self.instrument.device_class}") return None @@ -76,7 +85,10 @@ def device_class(self) -> Union[SensorDeviceClass, None]: @property def state_class(self) -> Union[SensorStateClass, None]: """Return the state class.""" - if self.instrument.state_class is None or self.instrument.state_class in STATE_CLASSES: + if ( + self.instrument.state_class is None + or self.instrument.state_class in STATE_CLASSES + ): return self.instrument.state_class _LOGGER.warning(f"Unknown state class {self.instrument.state_class}") return None diff --git a/custom_components/volkswagencarnet/services.py b/custom_components/volkswagencarnet/services.py index 7559949..bb59fa7 100644 --- a/custom_components/volkswagencarnet/services.py +++ b/custom_components/volkswagencarnet/services.py @@ -15,7 +15,9 @@ SERVICE_SET_CHARGER_MAX_CURRENT_SCHEMA = vol.Schema( { vol.Optional("device_id"): vol.All(cv.string, vol.Length(min=32, max=32)), - vol.Optional("max_current"): vol.In([5, 10, 13, 16, 32, "5", "10", "13", "16", "32", "reduced", "max"]), + vol.Optional("max_current"): vol.In( + [5, 10, 13, 16, 32, "5", "10", "13", "16", "32", "reduced", "max"] + ), }, extra=vol.ALLOW_EXTRA, # FIXME, should not be needed ) @@ -24,7 +26,9 @@ { vol.Required("device_id"): vol.All(cv.string, vol.Length(min=32, max=32)), vol.Required("timer_id"): vol.In([1, 2, 3]), - vol.Optional("charging_profile"): vol.All(cv.positive_int, vol.Range(min_included=1, max_included=10)), + vol.Optional("charging_profile"): vol.All( + cv.positive_int, vol.Range(min_included=1, max_included=10) + ), vol.Optional("enabled"): vol.All(cv.boolean), vol.Optional("frequency"): vol.In(["cyclic", "single"]), vol.Optional("departure_time"): vol.All(cv.string), @@ -39,7 +43,9 @@ vol.Optional("device_id"): vol.All(cv.string, vol.Length(min=32, max=32)), vol.Optional("min_level"): vol.In([0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100]), vol.Optional("target_temperature_celsius"): vol.Any(cv.string, cv.positive_int), - vol.Optional("target_temperature_fahrenheit"): vol.Any(cv.string, cv.positive_int), + vol.Optional("target_temperature_fahrenheit"): vol.Any( + cv.string, cv.positive_int + ), }, extra=vol.ALLOW_EXTRA, # FIXME, should not be needed ) @@ -47,7 +53,9 @@ SERVICE_UPDATE_PROFILE_SCHEMA = vol.Schema( { vol.Required("device_id"): vol.All(cv.string, vol.Length(min=32, max=32)), - vol.Required("profile_id"): vol.All(cv.positive_int, vol.Range(min_included=1, max_included=10)), + vol.Required("profile_id"): vol.All( + cv.positive_int, vol.Range(min_included=1, max_included=10) + ), vol.Optional("profile_name"): vol.All(cv.string), vol.Optional("charging"): vol.All(cv.boolean), vol.Optional("climatisation"): vol.All(cv.boolean), @@ -77,7 +85,9 @@ "100", ] ), - vol.Optional("charge_max_current"): vol.In([5, 10, 13, 16, 32, "5", "10", "13", "16", "32", "reduced", "max"]), + vol.Optional("charge_max_current"): vol.In( + [5, 10, 13, 16, 32, "5", "10", "13", "16", "32", "reduced", "max"] + ), vol.Optional("night_rate"): vol.All(cv.boolean), vol.Optional("night_rate_start"): vol.All(cv.string), vol.Optional("night_rate_end"): vol.All(cv.string), @@ -95,7 +105,9 @@ def __init__(self, hass: HomeAssistant): async def set_timer_basic_settings(self, service_call: ServiceCall) -> bool: """Service for configuring basic settings.""" - c = await get_coordinator_by_device_id(self.hass, service_call.data.get("device_id")) + c = await get_coordinator_by_device_id( + self.hass, service_call.data.get("device_id") + ) v = get_vehicle(c) # parse service call @@ -114,15 +126,23 @@ async def set_timer_basic_settings(self, service_call: ServiceCall) -> bool: # # res = await v.set_departure_timer_heater_source(hs) # _LOGGER.debug(f"set heater source returned {res}") if tt is not None: - _LOGGER.debug(f"Setting target temperature to {tt} {self.hass.config.units.temperature_unit}") + _LOGGER.debug( + f"Setting target temperature to {tt} {self.hass.config.units.temperature_unit}" + ) # get timers t = await c.connection.getTimers(c.vin) if self.hass.config.units.is_metric: - t.timersAndProfiles.timerBasicSetting.set_target_temperature_celsius(float(tt)) + t.timersAndProfiles.timerBasicSetting.set_target_temperature_celsius( + float(tt) + ) else: - t.timersAndProfiles.timerBasicSetting.set_target_temperature_fahrenheit(int(tt)) + t.timersAndProfiles.timerBasicSetting.set_target_temperature_fahrenheit( + int(tt) + ) # send command to volkswagencarnet - res = res and await v.set_climatisation_temp(t.timersAndProfiles.timerBasicSetting.targetTemperature) + res = res and await v.set_climatisation_temp( + t.timersAndProfiles.timerBasicSetting.targetTemperature + ) if ml is not None: _LOGGER.debug(f"Setting minimum charge level to {ml}%") # send charge limit command to volkswagencarnet @@ -134,7 +154,9 @@ async def set_timer_basic_settings(self, service_call: ServiceCall) -> bool: async def update_schedule(self, service_call: ServiceCall) -> bool: """Service for updating departure schedules.""" - c = await get_coordinator_by_device_id(self.hass, service_call.data.get("device_id")) + c = await get_coordinator_by_device_id( + self.hass, service_call.data.get("device_id") + ) v = get_vehicle(c) data: TimerData = await c.connection.getTimers(c.vin) @@ -149,7 +171,11 @@ async def update_schedule(self, service_call: ServiceCall) -> bool: departure_datetime = service_call.data.get("departure_datetime", None) weekday_mask = service_call.data.get("weekday_mask", None) - timers: dict[int, Timer] = {1: data.get_schedule(1), 2: data.get_schedule(2), 3: data.get_schedule(3)} + timers: dict[int, Timer] = { + 1: data.get_schedule(1), + 2: data.get_schedule(2), + 3: data.get_schedule(3), + } if frequency is not None: timers[timer_id].timerFrequency = frequency if frequency == "single": @@ -175,7 +201,9 @@ async def update_schedule(self, service_call: ServiceCall) -> bool: timers[timer_id].profileID = charging_profile if enabled is not None: - timers[timer_id].timerProgrammedStatus = "programmed" if enabled else "notProgrammed" + timers[timer_id].timerProgrammedStatus = ( + "programmed" if enabled else "notProgrammed" + ) _LOGGER.debug(f"Updating timer {timers[timer_id].json_updated['timer']}") data.timersAndProfiles.timerList.timer = [timers[1], timers[2], timers[3]] @@ -185,7 +213,9 @@ async def update_schedule(self, service_call: ServiceCall) -> bool: async def update_profile(self, service_call: ServiceCall) -> bool: """Service for updating charging profiles (locations).""" - c = await get_coordinator_by_device_id(self.hass, service_call.data.get("device_id")) + c = await get_coordinator_by_device_id( + self.hass, service_call.data.get("device_id") + ) v = get_vehicle(coordinator=c) data: TimerData = await c.connection.getTimers(c.vin) @@ -207,12 +237,28 @@ async def update_profile(self, service_call: ServiceCall) -> bool: charge_max_current = validate_charge_max_current(charge_max_current) profile = data.get_profile(profile_id) - profile.profileName = profile_name if profile_name is not None else profile.profileName - profile.operationCharging = charging if charging is not None else profile.operationCharging - profile.chargeMaxCurrent = charge_max_current if charge_max_current is not None else profile.chargeMaxCurrent - profile.operationClimatisation = climatisation if climatisation is not None else profile.operationClimatisation - profile.targetChargeLevel = target_level if target_level is not None else profile.targetChargeLevel - profile.nightRateActive = night_rate if night_rate is not None else profile.nightRateActive + profile.profileName = ( + profile_name if profile_name is not None else profile.profileName + ) + profile.operationCharging = ( + charging if charging is not None else profile.operationCharging + ) + profile.chargeMaxCurrent = ( + charge_max_current + if charge_max_current is not None + else profile.chargeMaxCurrent + ) + profile.operationClimatisation = ( + climatisation + if climatisation is not None + else profile.operationClimatisation + ) + profile.targetChargeLevel = ( + target_level if target_level is not None else profile.targetChargeLevel + ) + profile.nightRateActive = ( + night_rate if night_rate is not None else profile.nightRateActive + ) if night_rate_start is not None: profile.nightRateTimeStart = self.time_to_utc(night_rate_start) @@ -245,7 +291,9 @@ def __init__(self, hass: HomeAssistant): async def set_charger_max_current(self, service_call: ServiceCall) -> bool: """Service for setting max charging current.""" - c = await get_coordinator_by_device_id(self.hass, service_call.data.get("device_id")) + c = await get_coordinator_by_device_id( + self.hass, service_call.data.get("device_id") + ) v = get_vehicle(c) # parse service call diff --git a/custom_components/volkswagencarnet/switch.py b/custom_components/volkswagencarnet/switch.py index fe6fb48..092e949 100644 --- a/custom_components/volkswagencarnet/switch.py +++ b/custom_components/volkswagencarnet/switch.py @@ -15,7 +15,9 @@ _LOGGER = logging.getLogger(__name__) -async def async_setup_platform(hass: HomeAssistant, config, async_add_entities, discovery_info=None): +async def async_setup_platform( + hass: HomeAssistant, config, async_add_entities, discovery_info=None +): """Set up the volkswagen switch platform.""" if discovery_info is None: return @@ -54,7 +56,11 @@ async def async_setup_entry(hass: HomeAssistant, entry, async_add_devices): instrument=instrument, callback=hass.data[DOMAIN][entry.entry_id][UPDATE_CALLBACK], ) - for instrument in (instrument for instrument in data.instruments if instrument.component == "switch") + for instrument in ( + instrument + for instrument in data.instruments + if instrument.component == "switch" + ) ) return True @@ -62,7 +68,14 @@ async def async_setup_entry(hass: HomeAssistant, entry, async_add_devices): class VolkswagenSwitch(VolkswagenEntity, ToggleEntity): """Representation of a Volkswagen WeConnect Switch.""" - def __init__(self, data: VolkswagenData, vin: str, component: str, attribute: str, callback=None): + def __init__( + self, + data: VolkswagenData, + vin: str, + component: str, + attribute: str, + callback=None, + ): """Initialize switch.""" super().__init__(data, vin, component, attribute, callback) @@ -108,7 +121,10 @@ def assumed_state(self): @property def extra_state_attributes(self) -> dict[str, Any]: """Return extra state attributes.""" - return {**super().extra_state_attributes, **(self.instrument.attributes if self.instrument is not None else {})} + return { + **super().extra_state_attributes, + **(self.instrument.attributes if self.instrument is not None else {}), + } class VolkswagenDepartureTimer(VolkswagenSwitch): @@ -122,7 +138,14 @@ def turn_off(self, **kwargs: Any) -> None: """Disable timer.""" super().turn_off() - def __init__(self, data: VolkswagenData, vin: str, component: str, attribute: str, callback=None): + def __init__( + self, + data: VolkswagenData, + vin: str, + component: str, + attribute: str, + callback=None, + ): """Initialize class.""" super().__init__(data, vin, component, attribute, callback) _LOGGER.debug("Departure Timer initialized") @@ -153,8 +176,8 @@ def extra_state_attributes(self) -> dict[str, Any]: ).astimezone(pytz.timezone(self.hass.config.time_zone)) attribs["departure_time"] = d.strftime("%H:%M") else: - d = datetime.strptime(attribs["departure_time"], "%Y-%m-%dT%H:%M").replace( - tzinfo=timezone.utc, second=0, microsecond=0 - ) + d = datetime.strptime( + attribs["departure_time"], "%Y-%m-%dT%H:%M" + ).replace(tzinfo=timezone.utc, second=0, microsecond=0) attribs["departure_time"] = d return attribs diff --git a/custom_components/volkswagencarnet/util.py b/custom_components/volkswagencarnet/util.py index 4cbef78..d81c28e 100644 --- a/custom_components/volkswagencarnet/util.py +++ b/custom_components/volkswagencarnet/util.py @@ -21,7 +21,9 @@ def get_convert_conf(entry: ConfigEntry) -> Optional[str]: """ return ( CONF_SCANDINAVIAN_MILES - if entry.options.get(CONF_SCANDINAVIAN_MILES, entry.data.get(CONF_SCANDINAVIAN_MILES, False)) + if entry.options.get( + CONF_SCANDINAVIAN_MILES, entry.data.get(CONF_SCANDINAVIAN_MILES, False) + ) else CONF_NO_CONVERSION ) @@ -31,7 +33,9 @@ async def get_coordinator_by_device_id(hass: HomeAssistant, device_id: str): registry: DeviceRegistry = device_registry.async_get(hass) dev_entry: DeviceEntry = registry.async_get(device_id) - config_entry = hass.config_entries.async_get_entry(list(dev_entry.config_entries)[0]) + config_entry = hass.config_entries.async_get_entry( + list(dev_entry.config_entries)[0] + ) return await get_coordinator(hass, config_entry) @@ -65,7 +69,9 @@ def get_vehicle(coordinator) -> Vehicle: return v -def validate_charge_max_current(charge_max_current: Optional[Union[int, str]]) -> Optional[int]: +def validate_charge_max_current( + charge_max_current: Optional[Union[int, str]] +) -> Optional[int]: """ Validate value against known valid ones and return numeric value. diff --git a/manage/update_manifest.py b/manage/update_manifest.py index 1691fec..26c31ed 100644 --- a/manage/update_manifest.py +++ b/manage/update_manifest.py @@ -12,7 +12,9 @@ def update_manifest(): version = sys.argv[index + 1] # read manifest - with open(f"{os.getcwd()}/custom_components/volkswagencarnet/manifest.json") as manifestfile: + with open( + f"{os.getcwd()}/custom_components/volkswagencarnet/manifest.json" + ) as manifestfile: manifest = json.load(manifestfile) # set version in manifest @@ -26,7 +28,9 @@ def update_manifest(): manifest["requirements"] = requirements # save manifest - with open(f"{os.getcwd()}/custom_components/volkswagencarnet/manifest.json", "w") as manifestfile: + with open( + f"{os.getcwd()}/custom_components/volkswagencarnet/manifest.json", "w" + ) as manifestfile: manifestfile.write(json.dumps(manifest, indent=4, sort_keys=False)) # print output diff --git a/tests/config_flow_test.py b/tests/config_flow_test.py index 39226e2..2caacf4 100644 --- a/tests/config_flow_test.py +++ b/tests/config_flow_test.py @@ -40,7 +40,12 @@ CONF_REGION: "XX", CONF_CONVERT: CONF_NO_CONVERSION, } -DUMMY_OPTIONS = {CONF_CONVERT: CONF_NO_CONVERSION, CONF_DEBUG: True, CONF_REGION: "XY", CONF_REPORT_REQUEST: False} +DUMMY_OPTIONS = { + CONF_CONVERT: CONF_NO_CONVERSION, + CONF_DEBUG: True, + CONF_REGION: "XY", + CONF_REPORT_REQUEST: False, +} @patch("custom_components.volkswagencarnet.config_flow.Connection") @@ -59,7 +64,9 @@ async def test_flow_user_init_auth_fails(m_connection, hass: HomeAssistant): user_input=DUMMY_CONFIG, ) - flow: VolkswagenCarnetConfigFlow = hass.config_entries.flow._progress[result["flow_id"]] + flow: VolkswagenCarnetConfigFlow = hass.config_entries.flow._progress[ + result["flow_id"] + ] with patch.object(flow._connection, "doLogin") as aaaaa: aaaaa.side_effect = Exception @@ -72,7 +79,12 @@ async def test_flow_user_init_auth_fails(m_connection, hass: HomeAssistant): @patch("custom_components.volkswagencarnet.config_flow.get_coordinator") @patch("custom_components.volkswagencarnet.config_flow.get_vehicle") -async def test_options_flow(get_vehicle: MagicMock, get_coordinator: MagicMock, hass: HomeAssistant, m_connection): +async def test_options_flow( + get_vehicle: MagicMock, + get_coordinator: MagicMock, + hass: HomeAssistant, + m_connection, +): """Test options flow.""" m_dashboard = MagicMock(spec=Dashboard) m_dashboard.instruments = [ diff --git a/tests/timer_services_test.py b/tests/timer_services_test.py index ff437d9..7c8b4c7 100644 --- a/tests/timer_services_test.py +++ b/tests/timer_services_test.py @@ -41,10 +41,18 @@ async def test_call_service(conn: MagicMock, hass: HomeAssistant): """Test service call.""" e = MockConfigEntry( - data={CONF_VEHICLE: "xyz", CONF_USERNAME: "", CONF_PASSWORD: "", CONF_DEBUG: True, CONF_REGION: "ZZ"} + data={ + CONF_VEHICLE: "xyz", + CONF_USERNAME: "", + CONF_PASSWORD: "", + CONF_DEBUG: True, + CONF_REGION: "ZZ", + } ) - c: VolkswagenCoordinator = VolkswagenCoordinator(hass=hass, entry=e, update_interval=timedelta(minutes=10)) + c: VolkswagenCoordinator = VolkswagenCoordinator( + hass=hass, entry=e, update_interval=timedelta(minutes=10) + ) c.connection.vehicles = [MagicMock(Vehicle)] c.connection.vehicles[0].vin = "XYZ" @@ -67,9 +75,9 @@ async def test_call_service(conn: MagicMock, hass: HomeAssistant): target_temp = 24.5 data = {"device_id": e.entry_id, "target_temperature": target_temp} - with patch("custom_components.volkswagencarnet.services.get_coordinator_by_device_id") as m, patch.object( - c.connection, "getTimers" - ) as get_timers: + with patch( + "custom_components.volkswagencarnet.services.get_coordinator_by_device_id" + ) as m, patch.object(c.connection, "getTimers") as get_timers: m.return_value = c timer_profiles = [ TimerProfile( @@ -97,20 +105,32 @@ async def test_call_service(conn: MagicMock, hass: HomeAssistant): departureTimeOfDay="07:33", ) ] - basic_settings = BasicSettings(timestamp="2022-02-22T20:22:00Z", targetTemperature=2965, chargeMinLimit=20) + basic_settings = BasicSettings( + timestamp="2022-02-22T20:22:00Z", targetTemperature=2965, chargeMinLimit=20 + ) tpl = TimerProfileList(timer_profiles) - tp = TimersAndProfiles(timerProfileList=tpl, timerList=TimerList(timer_list), timerBasicSetting=basic_settings) + tp = TimersAndProfiles( + timerProfileList=tpl, + timerList=TimerList(timer_list), + timerBasicSetting=basic_settings, + ) future: Future = asyncio.Future() future.set_result(TimerData(timersAndProfiles=tp, status={})) get_timers.return_value = future res = await hass.services.async_call( - domain=DOMAIN, service=SERVICE_SET_TIMER_BASIC_SETTINGS, service_data=data, blocking=True, limit=15 + domain=DOMAIN, + service=SERVICE_SET_TIMER_BASIC_SETTINGS, + service_data=data, + blocking=True, + limit=15, ) c.connection.vehicles[0].set_climatisation_temp.assert_called_once() - used_args = c.connection.vehicles[0].set_climatisation_temp.call_args_list[0].args[0] + used_args = ( + c.connection.vehicles[0].set_climatisation_temp.call_args_list[0].args[0] + ) # check that the correct VW temperature unit was set assert 2975 == used_args diff --git a/tests/util_test.py b/tests/util_test.py index b565f5b..a08d332 100644 --- a/tests/util_test.py +++ b/tests/util_test.py @@ -12,7 +12,11 @@ from homeassistant.core import HomeAssistant from homeassistant.helpers import device_registry -from custom_components.volkswagencarnet import VolkswagenCoordinator, SchedulerService, util +from custom_components.volkswagencarnet import ( + VolkswagenCoordinator, + SchedulerService, + util, +) from custom_components.volkswagencarnet.const import ( DOMAIN, CONF_VEHICLE, @@ -37,7 +41,9 @@ async def test_get_coordinator(hass: HomeAssistant): } # We want to skip the actual setup flow here - with patch.object(homeassistant.config_entries.ConfigEntries, "async_setup") as flow, patch.object( + with patch.object( + homeassistant.config_entries.ConfigEntries, "async_setup" + ) as flow, patch.object( homeassistant.config_entries.ConfigEntries, "_async_schedule_save" ): f: Future = Future() @@ -51,7 +57,9 @@ async def test_get_coordinator(hass: HomeAssistant): identifiers: set[tuple[str, str]] = {tuple(["volkswagencarnet", dev_id])} # type: ignore - dev_entry = registry.async_get_or_create(config_entry_id=config_entry.entry_id, identifiers=identifiers) + dev_entry = registry.async_get_or_create( + config_entry_id=config_entry.entry_id, identifiers=identifiers + ) res = await util.get_coordinator(hass=hass, config_entry=config_entry) assert m_coord == res