self._certificate: RTCCertificate
self._enabled: bool = False
self._using_ha_cloud: bool = False
- self._starting: bool = False # Prevents concurrent gateway starts
self._on_unload_callbacks: list[Callable[[], None]] = []
async def setup(self) -> None:
self._register_api_commands()
self.mass.subscribe(self._on_providers_updated, EventType.PROVIDERS_UPDATED)
if self._enabled:
- self._schedule_start()
+ await self._schedule_start()
async def close(self) -> None:
"""Cleanup on exit."""
for unload_cb in self._on_unload_callbacks:
unload_cb()
- def _schedule_start(self) -> None:
- """Schedule a debounced gateway start."""
+ async def _schedule_start(self) -> None:
+ """Schedule a debounced gateway start, cancelling any existing connection first."""
+ # Cancel any pending timer
+ self.mass.cancel_timer(TASK_ID_START_GATEWAY)
+ # Stop any existing gateway
+ await self.stop()
+ # Schedule new start
self.logger.debug("Scheduling remote access gateway start in %s seconds", STARTUP_DELAY)
self.mass.call_later(
STARTUP_DELAY,
async def _start_gateway(self) -> None:
"""Start the remote access gateway (internal implementation)."""
- if self.is_running or self._starting:
- self.logger.debug("Gateway already running or starting, skipping start")
- return
if not self._enabled:
self.logger.debug("Remote access disabled, skipping start")
return
- self._starting = True
- try:
- base_url = self.mass.webserver.base_url
- local_ws_url = base_url.replace("http", "ws")
- if not local_ws_url.endswith("/"):
- local_ws_url += "/"
- local_ws_url += "ws"
+ base_url = self.mass.webserver.base_url
+ local_ws_url = base_url.replace("http", "ws")
+ if not local_ws_url.endswith("/"):
+ local_ws_url += "/"
+ local_ws_url += "ws"
- ha_cloud_available, ice_servers = await self._get_ha_cloud_status()
- self._using_ha_cloud = bool(ha_cloud_available and ice_servers)
-
- mode = "optimized" if self._using_ha_cloud else "basic"
- self.logger.info("Starting remote access in %s mode", mode)
-
- self.gateway = WebRTCGateway(
- http_session=self.mass.http_session,
- remote_id=self._remote_id,
- certificate=self._certificate,
- signaling_url=SIGNALING_SERVER_URL,
- local_ws_url=local_ws_url,
- ice_servers=ice_servers,
- # Pass callback to get fresh ICE servers for each client connection
- # This ensures TURN credentials are always valid
- ice_servers_callback=self.get_ice_servers if ha_cloud_available else None,
- )
+ ha_cloud_available, ice_servers = await self._get_ha_cloud_status()
+ self._using_ha_cloud = bool(ha_cloud_available and ice_servers)
+
+ mode = "optimized" if self._using_ha_cloud else "basic"
+ self.logger.info("Starting remote access in %s mode", mode)
+
+ self.gateway = WebRTCGateway(
+ http_session=self.mass.http_session,
+ remote_id=self._remote_id,
+ certificate=self._certificate,
+ signaling_url=SIGNALING_SERVER_URL,
+ local_ws_url=local_ws_url,
+ ice_servers=ice_servers,
+ # Pass callback to get fresh ICE servers for each client connection
+ # This ensures TURN credentials are always valid
+ ice_servers_callback=self.get_ice_servers if ha_cloud_available else None,
+ )
- await self.gateway.start()
- finally:
- self._starting = False
+ await self.gateway.start()
async def stop(self) -> None:
"""Stop the remote access gateway."""
if not self._enabled:
return
- # If not running yet and not currently starting, schedule start (debounced)
- if not self.is_running and not self._starting:
- self._schedule_start()
- return
-
# Check if HA Cloud status changed
ha_cloud_available, ice_servers = await self._get_ha_cloud_status()
new_using_ha_cloud = bool(ha_cloud_available and ice_servers)
if new_using_ha_cloud != self._using_ha_cloud:
self.logger.info("HA Cloud status changed, restarting remote access")
- await self.stop()
- self._schedule_start()
+ await self._schedule_start()
async def _get_ha_cloud_status(self) -> tuple[bool, list[dict[str, str]] | None]:
"""Get Home Assistant Cloud status and ICE servers.