Fix stale data after HA outage recovery
Some checks failed
Release Drafter / update_release_draft (push) Failing after 5s
Some checks failed
Release Drafter / update_release_draft (push) Failing after 5s
Two bugs caused the module to show outdated data permanently after HA came back online: 1. refreshTimer was only checked at section level, but config sets it at the module config level. The setInterval never started, so there was no periodic re-render fallback when the WebSocket died. 2. _closeCircuit replayed queued templates but never reconnected the WebSocket. Without WS, no state_changed events fire, so the only render path was the (broken) refreshTimer. Also fixes a race condition in _healthCheck where breaker.state was briefly set to 'closed' before calling _openCircuit on failure. Now uses 'half-open' state instead.
This commit is contained in:
@@ -52,14 +52,28 @@ Module.register("MMM-HomeAssistantDisplay", {
|
|||||||
entity: section.triggerEntities[entity]
|
entity: section.triggerEntities[entity]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// Set up a timer to trigger re-rendering outside of any entity state update
|
}
|
||||||
if (section.refreshTimer) {
|
}
|
||||||
|
|
||||||
|
// Refresh timer: check section-level first, then fall back to config-level.
|
||||||
|
// One interval per module instance — renderTemplates already hits all sections.
|
||||||
|
var refreshInterval = null;
|
||||||
|
if (this.config.sections) {
|
||||||
|
for (const sectioid in this.config.sections) {
|
||||||
|
if (this.config.sections[sectioid].refreshTimer) {
|
||||||
|
refreshInterval = this.config.sections[sectioid].refreshTimer;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!refreshInterval && this.config.refreshTimer) {
|
||||||
|
refreshInterval = this.config.refreshTimer;
|
||||||
|
}
|
||||||
|
if (refreshInterval) {
|
||||||
setInterval(() => {
|
setInterval(() => {
|
||||||
this.renderTemplates("timeout");
|
this.renderTemplates("refreshTimer");
|
||||||
this.updateDom();
|
this.updateDom();
|
||||||
}, section.refreshTimer * 1000);
|
}, refreshInterval * 1000);
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
this.renderTemplates("foo");
|
this.renderTemplates("foo");
|
||||||
self.updateDom(self.config.animationSpeed);
|
self.updateDom(self.config.animationSpeed);
|
||||||
|
|||||||
@@ -121,6 +121,7 @@ function _getBreaker(identifier) {
|
|||||||
function _openCircuit(identifier) {
|
function _openCircuit(identifier) {
|
||||||
const breaker = this._getBreaker(identifier);
|
const breaker = this._getBreaker(identifier);
|
||||||
if (breaker.state === 'open') return; // already open
|
if (breaker.state === 'open') return; // already open
|
||||||
|
// Accepts 'closed' (first failure) or 'half-open' (health check retry failed)
|
||||||
|
|
||||||
breaker.state = 'open';
|
breaker.state = 'open';
|
||||||
breaker.failCount++;
|
breaker.failCount++;
|
||||||
@@ -150,6 +151,16 @@ function _closeCircuit(identifier) {
|
|||||||
breaker.retryTimer = null;
|
breaker.retryTimer = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reconnect WebSocket — it likely died during the outage and won't
|
||||||
|
// recover on its own because the frontend only sends RECONNECT_WS in
|
||||||
|
// response to HASSWS_DISCONNECTED, which the node_helper may never
|
||||||
|
// have emitted if the socket died silently.
|
||||||
|
const conn = this.connections[identifier];
|
||||||
|
if (conn && conn.connectionConfig) {
|
||||||
|
this.logger.info(`Reconnecting WebSocket for ${identifier} after circuit recovery`);
|
||||||
|
this.backoffWSConnection(identifier, conn.connectionConfig);
|
||||||
|
}
|
||||||
|
|
||||||
// Replay queued template evaluations
|
// Replay queued template evaluations
|
||||||
const queue = breaker.pendingQueue.splice(0);
|
const queue = breaker.pendingQueue.splice(0);
|
||||||
if (queue.length > 0) {
|
if (queue.length > 0) {
|
||||||
@@ -189,8 +200,8 @@ async function _healthCheck(identifier) {
|
|||||||
this._closeCircuit(identifier);
|
this._closeCircuit(identifier);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.logger.info(`Health check failed for ${identifier}: ${err.message}`);
|
this.logger.info(`Health check failed for ${identifier}: ${err.message}`);
|
||||||
// Re-arm with increased backoff
|
// Re-arm with increased backoff — reset state so _openCircuit can fire
|
||||||
breaker.state = 'closed'; // briefly close so _openCircuit fires
|
breaker.state = 'half-open';
|
||||||
this._openCircuit(identifier);
|
this._openCircuit(identifier);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -277,7 +288,8 @@ async function connect(payload) {
|
|||||||
this.logger.info(`HomeAssistant connected for ${payload.identifier}`);
|
this.logger.info(`HomeAssistant connected for ${payload.identifier}`);
|
||||||
this.connections[payload.identifier] = {
|
this.connections[payload.identifier] = {
|
||||||
hass,
|
hass,
|
||||||
entities: []
|
entities: [],
|
||||||
|
connectionConfig,
|
||||||
};
|
};
|
||||||
|
|
||||||
await this.backoffWSConnection(payload.identifier, connectionConfig)
|
await this.backoffWSConnection(payload.identifier, connectionConfig)
|
||||||
|
|||||||
Reference in New Issue
Block a user