matterbridge-aeg-robot
Version:
A Matterbridge plugin that connects AEG RX 9 / Electrolux Pure i9 robot vacuums to the Matter smart home ecosystem.
107 lines • 3.96 kB
JavaScript
// Matterbridge plugin for AEG RX9 / Electrolux Pure i9 robot vacuum
// Copyright © 2025 Alexander Thoukydides
import { setTimeout } from 'node:timers/promises';
import { logError } from './utils.js';
// Perform an operation periodically, with error handling
export class PeriodicOp {
log;
config;
// The result of the last action
lastError;
// Abort signal used to cancel timers
abortInterval;
abortWatchdog;
// Is this periodic operation enabled
enabled = true;
// Timing of the next operation
lastOpTime = 0;
rapidUntil = 0;
// The periodic operation loop
runPeriodicPromise;
// Create a new poller
constructor(log, config) {
this.log = log;
this.config = config;
this.runPeriodicPromise = this.runPeriodic();
}
// Stop polling
async stop() {
// Cancel any timer and prevent rescheduling
this.enabled = false;
this.abortInterval?.abort();
// Wait for the operation to terminate cleanly
await this.runPeriodicPromise;
this.stopWatchdogAndClearError();
}
// Temporarily poll more rapidly
requestRapid(duration) {
// Extend the use of rapid polling
this.rapidUntil = Math.max(this.rapidUntil, Date.now() + duration);
// Cancel any current interval timer
this.abortInterval?.abort();
this.abortInterval = undefined;
}
// Time until the next operation
get timeUntilNextOp() {
const now = Date.now();
const interval = this.config[now < this.rapidUntil ? 'intervalRapid' : 'interval'];
return Math.max(0, this.lastOpTime + interval - now);
}
// Perform the action periodically indefinitely
async runPeriodic() {
void this.restartWatchdog();
while (this.enabled) {
try {
// Wait until it is time for the next operation
this.abortInterval = new AbortController();
const { signal } = this.abortInterval;
await setTimeout(this.timeUntilNextOp, undefined, { signal });
// Attempt the operation
this.lastOpTime = Date.now();
await this.config.onOp();
// Clear any prior error and restart the watchdog
void this.restartWatchdog();
}
catch (err) {
if (err instanceof Error && err.name === 'AbortError') {
this.log.debug(`${this.config.name} early wake`);
}
else {
logError(this.log, this.config.name, err);
this.lastError = err;
}
}
}
}
// Stop any active watchdog and clear any error condition
stopWatchdogAndClearError() {
// Abort any active watchdog timer
this.abortWatchdog?.abort();
this.abortWatchdog = undefined;
// Clear any prior error (whether from the polled operation or watchdog)
if (this.lastError) {
this.lastError = undefined;
this.config.onError?.();
}
}
// Start or restart the watchdog
async restartWatchdog() {
try {
// Abort any existing watchdog
this.stopWatchdogAndClearError();
// Start a new watchdog
this.abortWatchdog = new AbortController();
const { signal } = this.abortWatchdog;
await setTimeout(this.config.timeout, undefined, { signal });
// The timeout has occurred, so report the failure
this.lastError ??= new Error(`${this.config.name} watchdog timeout`);
this.config.onError?.(this.lastError);
}
catch (err) {
if (err instanceof Error && err.name === 'AbortError')
return;
logError(this.log, this.config.name, err);
}
}
}
//# sourceMappingURL=periodic-op.js.map