diff --git a/src/card-controller/hass/hass-manager.ts b/src/card-controller/hass/hass-manager.ts index a7e86f4d..719c3e10 100644 --- a/src/card-controller/hass/hass-manager.ts +++ b/src/card-controller/hass/hass-manager.ts @@ -1,6 +1,8 @@ import { hasHAConnectionStateChanged } from '../../ha/has-hass-connection-changed'; import { HomeAssistant } from '../../ha/types'; import { localize } from '../../localize/localize'; +import { log } from '../../utils/debug'; +import { InitializationAspect } from '../initialization-manager'; import { CardHASSAPI } from '../types'; import { StateWatcher, StateWatcherSubscriptionInterface } from './state-watcher'; @@ -36,6 +38,28 @@ export class HASSManager { }); } else { this._api.getMessageManager().resetType('connection'); + + // When the HA WebSocket connection is restored after a drop, + // reinitialize cameras and the view. This is necessary because + // event subscriptions (e.g. Frigate WebSocket subscriptions via + // hass.connection.subscribeMessage) are tied to the old connection + // and are lost when it drops. Without reinitialization, triggers + // and thumbnail updates stop working. + if (this._hass) { + log( + this._api.getConfigManager().getCardWideConfig(), + 'Advanced Camera Card: HA connection restored, reinitializing...', + ); + + this._api + .getInitializationManager() + .uninitialize(InitializationAspect.CAMERAS); + this._api.getCameraManager().destroy(); + this._api.getInitializationManager().uninitialize(InitializationAspect.VIEW); + this._api + .getInitializationManager() + .uninitialize(InitializationAspect.INITIAL_TRIGGER); + } } } diff --git a/src/card-controller/types.ts b/src/card-controller/types.ts index 11c56025..9c67592a 100644 --- a/src/card-controller/types.ts +++ b/src/card-controller/types.ts @@ -188,6 +188,7 @@ export interface CardHASSAPI { getConditionStateManager(): ConditionStateManager; getConfigManager(): ConfigManager; getDefaultManager(): DefaultManager; + getInitializationManager(): InitializationManager; getInteractionManager(): InteractionManager; getMediaPlayerManager(): MediaPlayerManager; getMessageManager(): MessageManager; diff --git a/tests/card-controller/hass/hass-manager.test.ts b/tests/card-controller/hass/hass-manager.test.ts index 900c2d5e..c3e4198f 100644 --- a/tests/card-controller/hass/hass-manager.test.ts +++ b/tests/card-controller/hass/hass-manager.test.ts @@ -114,6 +114,36 @@ describe('HASSManager', () => { expect(api.getMessageManager().resetType).toBeCalled(); }); + it('reconnected reinitializes cameras and view', () => { + const api = createCardAPI(); + const manager = new HASSManager(api); + + // First establish a connected state. + const connectedHASS = createHASS(); + connectedHASS.connected = true; + manager.setHASS(connectedHASS); + + // Simulate disconnection. + const disconnectedHASS = createHASS(); + disconnectedHASS.connected = false; + manager.setHASS(disconnectedHASS); + + // Simulate reconnection. + const reconnectedHASS = createHASS(); + reconnectedHASS.connected = true; + manager.setHASS(reconnectedHASS); + + // Cameras and view should be uninitialized so they get re-subscribed + // to event sources (e.g. Frigate WebSocket events) on the next + // render cycle. + expect(api.getInitializationManager().uninitialize).toBeCalledWith('cameras'); + expect(api.getCameraManager().destroy).toBeCalled(); + expect(api.getInitializationManager().uninitialize).toBeCalledWith('view'); + expect(api.getInitializationManager().uninitialize).toBeCalledWith( + 'initial-trigger', + ); + }); + it('hass is null', () => { const api = createCardAPI(); const manager = new HASSManager(api);