diff --git a/custom_components/siedle/__init__.py b/custom_components/siedle/__init__.py index 7680e6e..19efbfc 100644 --- a/custom_components/siedle/__init__.py +++ b/custom_components/siedle/__init__.py @@ -83,6 +83,8 @@ PLATFORMS = [Platform.SENSOR, Platform.BINARY_SENSOR, Platform.BUTTON, Platform.CAMERA] +_reloading_entries: set = set() + # This integration is config entry only CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN) @@ -417,16 +419,28 @@ async def _start_fcm_handler(): # Register services await async_setup_services(hass, siedle) - # Reload integration when options change (e.g. external SIP enabled/disabled) + # Reload integration when options change (e.g. SIP/FCM settings) entry.async_on_unload(entry.add_update_listener(_async_reload_on_options_change)) return True async def _async_reload_on_options_change(hass: HomeAssistant, entry: ConfigEntry) -> None: - """Reload the config entry when options are updated via the UI.""" - _LOGGER.info("Options changed, reloading Siedle integration...") - await hass.config_entries.async_reload(entry.entry_id) + """Reload the config entry when options are updated via the UI. + + Guard against double-reload: HA 2026.5 may trigger an additional reload after + OptionsFlow completes. If a reload for this entry is already in progress we skip + the second trigger instead of racing. + """ + if entry.entry_id in _reloading_entries: + _LOGGER.debug("Siedle reload already in progress for %s, skipping duplicate", entry.entry_id) + return + _reloading_entries.add(entry.entry_id) + try: + _LOGGER.info("Options changed, reloading Siedle integration...") + await hass.config_entries.async_reload(entry.entry_id) + finally: + _reloading_entries.discard(entry.entry_id) async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): diff --git a/custom_components/siedle/sip_manager.py b/custom_components/siedle/sip_manager.py index 1dd1352..3f31bcb 100644 --- a/custom_components/siedle/sip_manager.py +++ b/custom_components/siedle/sip_manager.py @@ -643,7 +643,13 @@ def register(self) -> bool: if not self._connected: if not self.connect(): return False - + + # Cap recv timeout at 10s so executor threads don't block longer than needed + try: + self._socket.settimeout(10) + except Exception: + pass + try: # Send initial REGISTER _LOGGER.info(f"{self.name}: Sending initial REGISTER to {self.config.host}:{self.config.port} " @@ -2242,6 +2248,12 @@ def _health_monitor_loop(self): # Check Siedle connection if self._siedle_conn and self._siedle_conn.connection_lost: _LOGGER.warning("Siedle SIP connection lost — attempting reconnect...") + # Clean up any active call so RTP bridge doesn't keep running + if self._siedle_call is not None: + try: + self._end_call() + except Exception as e: + _LOGGER.warning(f"Error ending call on connection loss: {e}") for attempt in range(3): if not self._running: break