diff --git a/custom_components/bosch_shc/__init__.py b/custom_components/bosch_shc/__init__.py index c3660e8..0f3194c 100644 --- a/custom_components/bosch_shc/__init__.py +++ b/custom_components/bosch_shc/__init__.py @@ -6,7 +6,7 @@ from boschshcpy import SHCSession, SHCUniversalSwitch from boschshcpy.exceptions import SHCAuthenticationError, SHCConnectionError from homeassistant.components.zeroconf import async_get_instance -from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntry +from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( ATTR_DEVICE_ID, ATTR_ID, @@ -60,16 +60,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: False, zeroconf, ) - except SHCAuthenticationError: - _LOGGER.warning("Unable to authenticate on Bosch Smart Home Controller API") - hass.async_create_task( - hass.config_entries.flow.async_init( - DOMAIN, - context={"source": SOURCE_REAUTH}, - data=entry.data, - ) - ) - return False + except SHCAuthenticationError as err: + raise ConfigEntryAuthFailed from err except SHCConnectionError as err: raise ConfigEntryNotReady from err @@ -86,7 +78,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: device_entry = device_registry.async_get_or_create( config_entry_id=entry.entry_id, connections={(dr.CONNECTION_NETWORK_MAC, dr.format_mac(shc_info.unique_id))}, - identifiers={(DOMAIN, shc_info.name)}, + identifiers={(DOMAIN, shc_info.unique_id)}, manufacturer="Bosch", name=entry.title, model="SmartHomeController", @@ -94,10 +86,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: ) device_id = device_entry.id - for platform in PLATFORMS: - hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, platform) - ) + hass.config_entries.async_setup_platforms(entry, PLATFORMS) async def stop_polling(event): """Stop polling service.""" @@ -137,19 +126,11 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: session: SHCSession = hass.data[DOMAIN][entry.entry_id][DATA_SESSION] session.unsubscribe_scenario_callback() - if hass.data[DOMAIN][entry.entry_id][DATA_POLLING_HANDLER]: - hass.data[DOMAIN][entry.entry_id][DATA_POLLING_HANDLER]() - hass.data[DOMAIN][entry.entry_id].pop(DATA_POLLING_HANDLER) - await hass.async_add_executor_job(session.stop_polling) + hass.data[DOMAIN][entry.entry_id][DATA_POLLING_HANDLER]() + hass.data[DOMAIN][entry.entry_id].pop(DATA_POLLING_HANDLER) + await hass.async_add_executor_job(session.stop_polling) - unload_ok = all( - await asyncio.gather( - *[ - hass.config_entries.async_forward_entry_unload(entry, platform) - for platform in PLATFORMS - ] - ) - ) + unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS) if unload_ok: hass.data[DOMAIN].pop(entry.entry_id) diff --git a/custom_components/bosch_shc/alarm_control_panel.py b/custom_components/bosch_shc/alarm_control_panel.py index 47664e6..b43b66e 100644 --- a/custom_components/bosch_shc/alarm_control_panel.py +++ b/custom_components/bosch_shc/alarm_control_panel.py @@ -32,7 +32,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): intrusion_system = session.intrusion_system alarm_control_panel = IntrusionSystemAlarmControlPanel( device=intrusion_system, - parent_id=session.information.name, + parent_id=session.information.unique_id, entry_id=config_entry.entry_id, ) devices.append(alarm_control_panel) diff --git a/custom_components/bosch_shc/binary_sensor.py b/custom_components/bosch_shc/binary_sensor.py index d7f4ca8..29f6b00 100644 --- a/custom_components/bosch_shc/binary_sensor.py +++ b/custom_components/bosch_shc/binary_sensor.py @@ -54,7 +54,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): entities.append( ShutterContactSensor( device=binarysensor, - parent_id=session.information.name, + parent_id=session.information.unique_id, entry_id=config_entry.entry_id, ) ) @@ -64,7 +64,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): MotionDetectionSensor( hass=hass, device=binarysensor, - parent_id=session.information.name, + parent_id=session.information.unique_id, entry_id=config_entry.entry_id, ) ) @@ -73,7 +73,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): entities.append( SmokeDetectorSensor( device=binarysensor, - parent_id=session.information.name, + parent_id=session.information.unique_id, hass=hass, entry_id=config_entry.entry_id, ) @@ -83,7 +83,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): entities.append( SmokeDetectionSystemSensor( device=binarysensor, - parent_id=session.information.name, + parent_id=session.information.unique_id, hass=hass, entry_id=config_entry.entry_id, ) @@ -93,7 +93,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): entities.append( WaterLeakageDetectorSensor( device=binarysensor, - parent_id=session.information.name, + parent_id=session.information.unique_id, hass=hass, entry_id=config_entry.entry_id, ) diff --git a/custom_components/bosch_shc/climate.py b/custom_components/bosch_shc/climate.py index 914e531..f06d431 100644 --- a/custom_components/bosch_shc/climate.py +++ b/custom_components/bosch_shc/climate.py @@ -33,7 +33,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): entities.append( ClimateControl( device=climate, - parent_id=session.information.name, + parent_id=session.information.unique_id, entry_id=config_entry.entry_id, name=f"Room Climate {session.room(room_id).name}", ) diff --git a/custom_components/bosch_shc/config_flow.py b/custom_components/bosch_shc/config_flow.py index c89e3ca..03f12ff 100644 --- a/custom_components/bosch_shc/config_flow.py +++ b/custom_components/bosch_shc/config_flow.py @@ -2,7 +2,6 @@ import logging from os import makedirs -import voluptuous as vol from boschshcpy import SHCRegisterClient, SHCSession from boschshcpy.exceptions import ( SHCAuthenticationError, @@ -10,6 +9,8 @@ SHCRegistrationError, SHCSessionError, ) +import voluptuous as vol + from homeassistant import config_entries, core from homeassistant.components.zeroconf import async_get_instance from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_TOKEN @@ -77,7 +78,6 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): """Handle a config flow for Bosch SHC.""" VERSION = 1 - CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_POLL info = None host = None hostname = None @@ -123,8 +123,8 @@ async def async_step_credentials(self, user_input=None): """Handle the credentials step.""" errors = {} if user_input is not None: + zeroconf = await async_get_instance(self.hass) try: - zeroconf = await async_get_instance(self.hass) result = await self.hass.async_add_executor_job( create_credentials_and_validate, self.hass, @@ -132,29 +132,27 @@ async def async_step_credentials(self, user_input=None): user_input, zeroconf, ) - entry_data = { - CONF_SSL_CERTIFICATE: self.hass.config.path(DOMAIN, CONF_SHC_CERT), - CONF_SSL_KEY: self.hass.config.path(DOMAIN, CONF_SHC_KEY), - CONF_HOST: self.host, - CONF_TOKEN: result["token"], - CONF_HOSTNAME: result["token"].split(":", 1)[1], - } except SHCAuthenticationError: errors["base"] = "invalid_auth" except SHCConnectionError: errors["base"] = "cannot_connect" - except SHCSessionError: - _LOGGER.warning("API call returned non-OK result. Wrong password?") + except SHCSessionError as err: + _LOGGER.warning("Session error: %s", err.message) errors["base"] = "unknown" - except SHCRegistrationError: - _LOGGER.warning( - "SHC not in pairing mode! Please press the Bosch Smart Home Controller button until LED starts blinking" - ) + except SHCRegistrationError as err: + _LOGGER.warning("Registration error: %s", err.message) errors["base"] = "pairing_failed" except Exception: # pylint: disable=broad-except _LOGGER.exception("Unexpected exception") errors["base"] = "unknown" else: + entry_data = { + CONF_SSL_CERTIFICATE: self.hass.config.path(DOMAIN, CONF_SHC_CERT), + CONF_SSL_KEY: self.hass.config.path(DOMAIN, CONF_SHC_KEY), + CONF_HOST: self.host, + CONF_TOKEN: result["token"], + CONF_HOSTNAME: result["token"].split(":", 1)[1], + } existing_entry = await self.async_set_unique_id(self.info["unique_id"]) if existing_entry: self.hass.config_entries.async_update_entry( @@ -173,7 +171,9 @@ async def async_step_credentials(self, user_input=None): schema = vol.Schema( { - vol.Required(CONF_PASSWORD, default=user_input.get(CONF_PASSWORD)): str, + vol.Required( + CONF_PASSWORD, default=user_input.get(CONF_PASSWORD, "") + ): str, } ) diff --git a/custom_components/bosch_shc/cover.py b/custom_components/bosch_shc/cover.py index 7c8e553..df97410 100644 --- a/custom_components/bosch_shc/cover.py +++ b/custom_components/bosch_shc/cover.py @@ -28,7 +28,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): entities.append( ShutterControlCover( device=cover, - parent_id=session.information.name, + parent_id=session.information.unique_id, entry_id=config_entry.entry_id, ) ) diff --git a/custom_components/bosch_shc/device_trigger.py b/custom_components/bosch_shc/device_trigger.py index adf9909..7a58e77 100644 --- a/custom_components/bosch_shc/device_trigger.py +++ b/custom_components/bosch_shc/device_trigger.py @@ -66,7 +66,7 @@ async def get_device_from_id(hass, device_id) -> Tuple[SHCDevice, str]: return ids, "IDS" device = dev_registry.async_get_device( - identifiers={(DOMAIN, session.information.name)}, connections=set() + identifiers={(DOMAIN, session.information.unique_id)}, connections=set() ) if device.id == device_id: return session, "SHC" diff --git a/custom_components/bosch_shc/entity.py b/custom_components/bosch_shc/entity.py index e0abd2e..dc362eb 100644 --- a/custom_components/bosch_shc/entity.py +++ b/custom_components/bosch_shc/entity.py @@ -1,8 +1,8 @@ """Bosch Smart Home Controller base entity.""" from boschshcpy.device import SHCDevice + from homeassistant.helpers.device_registry import async_get as get_dev_reg from homeassistant.helpers.entity import Entity -from homeassistant.helpers.entity_registry import async_get as get_ent_reg from .const import DOMAIN @@ -13,21 +13,16 @@ async def async_get_device_id(hass, device_id): device = dev_registry.async_get_device( identifiers={(DOMAIN, device_id)}, connections=set() ) - if device is None: - return None - return device.id - + return device.id if device is not None else None async def async_remove_devices(hass, entity, entry_id): """Get item that is removed from session.""" - ent_registry = get_ent_reg(hass) - if entity.entity_id in ent_registry.entities: - ent_registry.async_remove(entity.entity_id) - dev_registry = get_dev_reg(hass) - device_id = async_get_device_id(hass, entity.device_id) - if device_id is not None: - dev_registry.async_update_device(device_id, remove_config_entry_id=entry_id) + device = dev_registry.async_get_device( + identifiers={(DOMAIN, entity.device_id)}, connections=set() + ) + if device is not None: + dev_registry.async_update_device(device.id, remove_config_entry_id=entry_id) class SHCEntity(Entity): @@ -49,7 +44,6 @@ def on_state_changed(): def update_entity_information(): if self._device.deleted: self.hass.add_job(async_remove_devices(self.hass, self, self._entry_id)) - else: self.schedule_update_ha_state() diff --git a/custom_components/bosch_shc/light.py b/custom_components/bosch_shc/light.py index 160cdfd..981c1c2 100644 --- a/custom_components/bosch_shc/light.py +++ b/custom_components/bosch_shc/light.py @@ -33,7 +33,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): entities.append( LightSwitch( device=light, - parent_id=session.information.name, + parent_id=session.information.unique_id, entry_id=config_entry.entry_id, ) ) diff --git a/custom_components/bosch_shc/manifest.json b/custom_components/bosch_shc/manifest.json index bfc7590..63b71f0 100644 --- a/custom_components/bosch_shc/manifest.json +++ b/custom_components/bosch_shc/manifest.json @@ -3,8 +3,8 @@ "name": "Bosch SHC", "config_flow": true, "documentation": "https://github.com/tschamm/boschshc-hass/blob/master/README.md", - "requirements": ["boschshcpy==0.2.16.dev1"], - "version": "0.4.2.dev0", + "requirements": ["boschshcpy==0.2.17"], + "version": "0.4.2", "zeroconf": [ {"type": "_http._tcp.local.", "name": "bosch shc*"} ], diff --git a/custom_components/bosch_shc/sensor.py b/custom_components/bosch_shc/sensor.py index bfe8e35..cab94d3 100644 --- a/custom_components/bosch_shc/sensor.py +++ b/custom_components/bosch_shc/sensor.py @@ -27,14 +27,14 @@ async def async_setup_entry(hass, config_entry, async_add_entities): entities.append( TemperatureSensor( device=sensor, - parent_id=session.information.name, + parent_id=session.information.unique_id, entry_id=config_entry.entry_id, ) ) entities.append( ValveTappetSensor( device=sensor, - parent_id=session.information.name, + parent_id=session.information.unique_id, entry_id=config_entry.entry_id, ) ) @@ -43,14 +43,14 @@ async def async_setup_entry(hass, config_entry, async_add_entities): entities.append( TemperatureSensor( device=sensor, - parent_id=session.information.name, + parent_id=session.information.unique_id, entry_id=config_entry.entry_id, ) ) entities.append( HumiditySensor( device=sensor, - parent_id=session.information.name, + parent_id=session.information.unique_id, entry_id=config_entry.entry_id, ) ) @@ -59,28 +59,28 @@ async def async_setup_entry(hass, config_entry, async_add_entities): entities.append( TemperatureSensor( device=sensor, - parent_id=session.information.name, + parent_id=session.information.unique_id, entry_id=config_entry.entry_id, ) ) entities.append( HumiditySensor( device=sensor, - parent_id=session.information.name, + parent_id=session.information.unique_id, entry_id=config_entry.entry_id, ) ) entities.append( PuritySensor( device=sensor, - parent_id=session.information.name, + parent_id=session.information.unique_id, entry_id=config_entry.entry_id, ) ) entities.append( AirQualitySensor( device=sensor, - parent_id=session.information.name, + parent_id=session.information.unique_id, entry_id=config_entry.entry_id, ) ) @@ -89,14 +89,14 @@ async def async_setup_entry(hass, config_entry, async_add_entities): entities.append( PowerSensor( device=sensor, - parent_id=session.information.name, + parent_id=session.information.unique_id, entry_id=config_entry.entry_id, ) ) entities.append( EnergySensor( device=sensor, - parent_id=session.information.name, + parent_id=session.information.unique_id, entry_id=config_entry.entry_id, ) ) @@ -105,14 +105,14 @@ async def async_setup_entry(hass, config_entry, async_add_entities): entities.append( PowerSensor( device=sensor, - parent_id=session.information.name, + parent_id=session.information.unique_id, entry_id=config_entry.entry_id, ) ) entities.append( EnergySensor( device=sensor, - parent_id=session.information.name, + parent_id=session.information.unique_id, entry_id=config_entry.entry_id, ) ) @@ -129,7 +129,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): entities.append( BatterySensor( device=sensor, - parent_id=session.information.name, + parent_id=session.information.unique_id, entry_id=config_entry.entry_id, ) ) diff --git a/custom_components/bosch_shc/strings.json b/custom_components/bosch_shc/strings.json index a7c8211..063a2dd 100644 --- a/custom_components/bosch_shc/strings.json +++ b/custom_components/bosch_shc/strings.json @@ -25,7 +25,7 @@ "error": { "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]", - "pairing_failed": "Pairing failed", + "pairing_failed": "Pairing failed... Please press the Bosch Smart Home Controller button until LED starts blinking and check your password.", "unknown": "[%key:common::config_flow::error::unknown%]" }, "abort": { diff --git a/custom_components/bosch_shc/switch.py b/custom_components/bosch_shc/switch.py index 8df97e5..4a03d92 100644 --- a/custom_components/bosch_shc/switch.py +++ b/custom_components/bosch_shc/switch.py @@ -31,7 +31,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): entities.append( SmartPlugSwitch( device=switch, - parent_id=session.information.name, + parent_id=session.information.unique_id, entry_id=config_entry.entry_id, ) ) @@ -41,7 +41,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): entities.append( SmartPlugCompactSwitch( device=switch, - parent_id=session.information.name, + parent_id=session.information.unique_id, entry_id=config_entry.entry_id, ) ) @@ -51,7 +51,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): entities.append( CameraEyesSwitch( device=switch, - parent_id=session.information.name, + parent_id=session.information.unique_id, entry_id=config_entry.entry_id, ) ) @@ -61,7 +61,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): entities.append( Camera360Switch( device=switch, - parent_id=session.information.name, + parent_id=session.information.unique_id, entry_id=config_entry.entry_id, ) ) diff --git a/custom_components/bosch_shc/translations/en.json b/custom_components/bosch_shc/translations/en.json index e4619c1..4ff2861 100644 --- a/custom_components/bosch_shc/translations/en.json +++ b/custom_components/bosch_shc/translations/en.json @@ -29,7 +29,7 @@ "error": { "cannot_connect": "Failed to connect", "invalid_auth": "Invalid authentication", - "pairing_failed": "Pairing failed", + "pairing_failed": "Pairing failed... Please press the Bosch Smart Home Controller button until LED starts blinking and check your password.", "unknown": "Unexpected error" }, "abort": {