Skip to content

Commit

Permalink
Tidying
Browse files Browse the repository at this point in the history
  • Loading branch information
thoukydides committed Dec 31, 2023
1 parent bf892fe commit 3ae793e
Show file tree
Hide file tree
Showing 11 changed files with 114 additions and 64 deletions.
12 changes: 6 additions & 6 deletions src/aeg-account.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { Logger, LogLevel } from 'homebridge';
import { AEGAPI } from './aegapi';
import { AEGRobot } from './aeg-robot';
import { HealthCheck } from './aegapi-types';
import { columns } from './utils';
import { columns, formatList, MS, plural } from './utils';
import { Config } from './config-types';
import { AEGApplianceAPI } from './aegapi-appliance';
import { Heartbeat } from './heartbeat';
Expand Down Expand Up @@ -63,8 +63,8 @@ export class AEGAccount {
}
});
if (incompatible.length) {
this.log.info(`Ignoring ${incompatible.length} incompatible appliances: `
+ incompatible.join(', '));
this.log.info(`Ignoring ${plural(incompatible.length, 'incompatible appliance')}: `
+ formatList(incompatible));
}

// Update any robots with the domain details
Expand All @@ -82,7 +82,7 @@ export class AEGAccount {
['Feed', intervals.feedSeconds, this.pollFeed]
];
this.heartbeats = poll.map(action =>
new Heartbeat(this.log, action[0], action[1] * 1000, action[2].bind(this),
new Heartbeat(this.log, action[0], action[1] * MS, action[2].bind(this),
(err) => this.heartbeat(err)));
}

Expand Down Expand Up @@ -123,13 +123,13 @@ export class AEGAccount {

// Summary status
if (!failed) this.log.debug('All AEG API servers appear healthy:');
else this.log.error(`${failed} of ${servers.length} AEG API servers have problems:`);
else this.log.error(`${failed} of ${plural(servers.length, 'AEG API server')} have problems:`);

// Detailed status
const rows: string[][] = servers.map(server => ([
server.app,
server.release,
server.version || '',
server.version ?? '',
server.environment,
`${server.statusCode}`,
server.message
Expand Down
10 changes: 6 additions & 4 deletions src/aeg-robot-ctrl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,19 @@

import { Logger } from 'homebridge';

import { setTimeout } from 'node:timers/promises';

import { AEGRobot, SimpleActivity } from './aeg-robot';
import { AEGApplianceAPI } from './aegapi-appliance';
import { Activity, Appliance, CleaningCommand, PowerMode } from './aegapi-types';
import { logError, sleep } from './utils';
import { MS, logError } from './utils';
import { Config } from './config-types';

// Timezone to use when changing name if unable to determine
const DEFAULT_TIMEZONE = 'London/Europe';

// Timeout waiting for status to reflect a requested change
const TIMEOUT_MIN_MS = 20 * 1000;
const TIMEOUT_MIN_MS = 20 * MS;
const TIMEOUT_POLL_MULTIPLE = 3;

// An abstract AEG RX 9 / Electrolux Pure i9 robot controller
Expand Down Expand Up @@ -47,7 +49,7 @@ abstract class AEGRobotCtrl<Type extends number | string> {
this.api = robot.api;
this.timeout = Math.max(TIMEOUT_MIN_MS,
this.config.pollIntervals.statusSeconds
* 1000 * TIMEOUT_POLL_MULTIPLE);
* MS * TIMEOUT_POLL_MULTIPLE);
robot.on('preUpdate', () => {
if (this.target !== undefined) this.overrideStatus(this.target);
});
Expand Down Expand Up @@ -113,7 +115,7 @@ abstract class AEGRobotCtrl<Type extends number | string> {
await this.setTarget(target);

// Timeout waiting for status to reflect the requested change
const timeout = sleep(this.timeout);
const timeout = setTimeout(this.timeout);

// Wait for status update, change of target state, or timeout
let done: boolean | null, reason;
Expand Down
22 changes: 11 additions & 11 deletions src/aeg-robot-log.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { Logger, LogLevel } from 'homebridge';
import { AEGRobot, CleanedAreaWithMap } from './aeg-robot';
import { Activity, Battery, Capability, Completion, Dustbin,
FeedItem, Message, PowerMode } from './aegapi-types';
import { columns, formatDuration } from './utils';
import { columns, formatList, formatMilliseconds, formatSeconds, MS, plural } from './utils';
import { AEGRobotMap } from './aeg-map';

// Descriptions of the robot activity
Expand Down Expand Up @@ -63,7 +63,7 @@ const completionNames: Record<Completion, string> = {
};

// Robot tick duration
const TICK_MS = 1e-4;
const TICK_MS = 10 * MS;

// Logging of information about a robot
export class AEGRobotLog {
Expand Down Expand Up @@ -99,7 +99,7 @@ export class AEGRobotLog {
this.robot.on('rawName', (name: string) => {
this.log.info(`My name is "${name}"`);
}).on('capabilities', (capabilities: Capability[]) => {
this.log.info(`Supported capabilities: ${capabilities.join(', ')}`);
this.log.info(`Supported ${plural(capabilities.length, 'capability')}: ${formatList(capabilities)}`);
}).on('firmware', (firmware: string) => {
this.log.info(`Firmware version ${firmware} installed`);
}).on('battery', (battery?: Battery) => {
Expand Down Expand Up @@ -140,24 +140,24 @@ export class AEGRobotLog {
// Log messages from the robot
async logMessages(): Promise<void> {
this.robot.on('message', (message: Message) => {
const age = `${formatDuration(Date.now() - message.timestamp * 1000)} ago`;
const age = `${formatMilliseconds(Date.now() - message.timestamp * MS)} ago`;
const bits = [`type=${message.type}`];
if (message.userErrorID) bits.push(`user-error=${message.userErrorID}`);
if (message.internalErrorID) bits.push(`internal-error=${message.internalErrorID}`);
this.log.warn(`Message: ${message.text} (${age})`);
this.log.debug(`Message: ${bits.join(', ')}`);
this.log.debug(`Message: ${formatList(bits)}`);
}).on('feed', (item: FeedItem) => {
const age = `${formatDuration(Date.now() - Date.parse(item.createdAtUTC))} ago`;
const age = `${formatMilliseconds(Date.now() - Date.parse(item.createdAtUTC))} ago`;
switch (item.feedDataType) {
case 'RVCLastWeekCleanedArea':
this.log.info(`Weekly insight (${age}):`);
this.log.info(` Worked for ${formatDuration(item.data.cleaningDurationTicks * TICK_MS)}`);
this.log.info(` Worked for ${formatMilliseconds(item.data.cleaningDurationTicks * TICK_MS)}`);
this.log.info(` ${item.data.cleanedAreaSquareMeter} m² cleaned`);
this.log.info(` Cleaned ${item.data.sessionCount} times`);
this.log.info(` Recharged ${item.data.pitstopCount} times while cleaning`);
break;
case 'OsirisBusierWeekJobDone': {
const formatHours = (hours: number) => formatDuration(hours * 60 * 60 * 1000);
const formatHours = (hours: number) => formatSeconds(hours * 60 * 60);
this.log.info(`Worked more this week (${age}):`);
this.log.info(` Worked ${formatHours(item.data.current)} this week`);
this.log.info(` Worked ${formatHours(item.data.previous)} previous week`);
Expand Down Expand Up @@ -185,7 +185,7 @@ export class AEGRobotLog {
break;
case 'ApplianceBirthday':
this.log.info(`Happy birthday! (${age})`);
this.log.info(` Robot is ${item.data.age} year${item.data.age === 1 ? '' : 's'} old`);
this.log.info(` Robot is ${plural(item.data.age, 'year')} old`);
this.log.info(` First activated ${item.data.birthDay}`);
break;
default:
Expand All @@ -205,10 +205,10 @@ export class AEGRobotLog {
if (cleaningSession) {
const formatTime = (time: string) => new Date(time).toLocaleTimeString();
this.log.info(` ${formatTime(cleaningSession.startTime)} - ${formatTime(cleaningSession.eventTime)}`);
this.log.info(` Cleaned for ${formatDuration(cleaningSession.cleaningDuration * TICK_MS)}`);
this.log.info(` Cleaned for ${formatMilliseconds(cleaningSession.cleaningDuration * TICK_MS)}`);
if (cleaningSession.pitstopCount) {
this.log.info(` Recharged ${cleaningSession.pitstopCount} times while cleaning`);
this.log.info(` Charged for ${formatDuration(cleaningSession.pitstopDuration * TICK_MS)}`);
this.log.info(` Charged for ${formatMilliseconds(cleaningSession.pitstopDuration * TICK_MS)}`);
}
if (cleaningSession.completion) {
this.log.info(` ${completionNames[cleaningSession.completion]}`);
Expand Down
10 changes: 5 additions & 5 deletions src/aeg-robot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { Activity, Appliance, ApplianceNamePatch, Battery, Capability,
DomainAppliance, Dustbin, FeedItem, InteractiveMap, InteractiveMapData,
Message, PowerMode, Status } from './aegapi-types';
import { PrefixLogger } from './logger';
import { logError } from './utils';
import { MS, formatList, logError } from './utils';
import { AEGRobotLog } from './aeg-robot-log';
import { AEGRobotCtrlActivity, AEGRobotCtrlName,
AEGRobotCtrlPower } from './aeg-robot-ctrl';
Expand Down Expand Up @@ -138,7 +138,7 @@ export class AEGRobot extends EventEmitter {
// Initialise static information that is already known
this.applianceId = appliance.applianceId;
this.model = appliance.applianceData.modelName;
this.hardware = appliance.properties.reported.platform || '';
this.hardware = appliance.properties.reported.platform ?? '';

// Allow the robot to be controlled
this.setName = new AEGRobotCtrlName (this).makeSetter();
Expand Down Expand Up @@ -181,7 +181,7 @@ export class AEGRobot extends EventEmitter {
['Cleaned areas', intervals.cleanedAreasSeconds, this.pollCleanedAreas]
];
this.heartbeats = poll.map(action =>
new Heartbeat(this.log, action[0], action[1] * 1000, action[2].bind(this),
new Heartbeat(this.log, action[0], action[1] * MS, action[2].bind(this),
(err) => this.heartbeat(err)));
}

Expand All @@ -208,7 +208,7 @@ export class AEGRobot extends EventEmitter {

// Other details may be absent if the robot is not reachable
capabilities: Object.keys(reported.capabilities ?? {}),
firmware: reported.firmwareVersion || '',
firmware: reported.firmwareVersion ?? '',
battery: reported.batteryStatus,
activity: reported.robotStatus,
dustbin: reported.dustbinStatus,
Expand Down Expand Up @@ -340,7 +340,7 @@ export class AEGRobot extends EventEmitter {
};
const summary = changed.map(key =>
`${key}: ${toText(this.emittedStatus[key])}->${toText(this.status[key])}`);
this.log.debug(summary.join(', '));
this.log.debug(formatList(summary));

// Emit events for each change
changed.forEach(key => this.emit(key, this.status[key], this.emittedStatus[key]));
Expand Down
6 changes: 3 additions & 3 deletions src/aegapi-error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,8 @@ export class AEGAPIStatusCodeError extends AEGAPIError {
const statusCode = response.statusCode;
const statusCodeName = STATUS_CODES[statusCode];
const description = AEGAPIStatusCodeError.getBodyDescription(text)
|| AEGAPIStatusCodeError.getHeaderDescription(response)
|| 'No error message returned';
?? AEGAPIStatusCodeError.getHeaderDescription(response)
?? 'No error message returned';
return `[${statusCode} ${statusCodeName}] ${description}`;
}

Expand Down Expand Up @@ -105,7 +105,7 @@ export class AEGAPIStatusCodeError extends AEGAPIError {
// Attempt to extract a useful description from the response headers
static getHeaderDescription(response: Response): string | null {
const header = response.headers['www-authenticate']
|| response.headers['x-amzn-remapped-www-authenticate'];
?? response.headers['x-amzn-remapped-www-authenticate'];
return typeof header === 'string' && header.length ? header : null;
}
}
Expand Down
6 changes: 3 additions & 3 deletions src/aegapi-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { AEGAPI } from './aegapi';
import { AEGApplianceAPI } from './aegapi-appliance';
import { Appliance, Appliances, CleaningCommand, DomainAppliance, Domains,
NewTask, PowerMode, SettableProperties, User } from './aegapi-types';
import { logError } from './utils';
import { logError, plural } from './utils';

// A test failure
interface Failure {
Expand Down Expand Up @@ -75,7 +75,7 @@ export class AEGAPITest {
const user = await test(this.api.getCurrentUser);
const appliances = await test(this.api.getAppliances);
const domains = await test(this.api.getDomains);
const applianceIds = (appliances || []).map(a => a.applianceId);
const applianceIds = (appliances ?? []).map(a => a.applianceId);
await test(this.api.getWebShopURLs, applianceIds);

// Return results required for other tests
Expand Down Expand Up @@ -205,7 +205,7 @@ export class AEGAPITest {
// Log a summary of the results
summariseResults(): void {
if (this.failures.length) {
this.log.error(`${this.failures.length} of ${this.tests} API tests failed`);
this.log.error(`${this.failures.length} of ${plural(this.tests, 'API test')} failed`);
this.failures.forEach(failure => {
this.log.error(`${failure.logPrefix}: ${failure.testName}`);
this.log.error(` ${failure.error}`);
Expand Down
12 changes: 7 additions & 5 deletions src/aegapi-ua-auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,16 @@
// Copyright © 2022-2023 Alexander Thoukydides

import { Logger } from 'homebridge';

import { getItem, setItem } from 'node-persist';
import { CheckerT, createCheckers } from 'ts-interface-checker';
import { setTimeout } from 'node:timers/promises';

import { AuthToken, AuthUser, PostAuthTokenClient, PostAuthTokenExchange,
PostAuthTokenRefresh, PostAuthTokenRevoke, PostAuthToken, PostAuthUser,
AbsoluteAuthToken } from './aegapi-auth-types';
import { AEGUserAgent, Headers, Method, Request, UAOptions } from './aegapi-ua';
import { logError, sleep } from './utils';
import { MS, logError, sleep } from './utils';
import { AEG_CLIENT_ID, AEG_CLIENT_SECRET } from './settings';
import { AEGAPIAuthorisationError, AEGAPIError,
AEGAPIStatusCodeError } from './aegapi-error';
Expand All @@ -27,10 +29,10 @@ const checkers = createCheckers(aegapiTI) as {
export class AEGAuthoriseUserAgent extends AEGUserAgent {

// Time before token expiry to request a refresh
private readonly refreshWindow = 60 * 60 * 1000; // (milliseconds)
private readonly refreshWindow = 60 * 60 * MS;

// Delay between retrying failed authorisation operations
private readonly authRetryDelay = 60 * 1000; // (milliseconds)
private readonly authRetryDelay = 60 * MS;

// Promise that is resolved by successful (re)authorisation
private authorised: Promise<void>;
Expand Down Expand Up @@ -121,7 +123,7 @@ export class AEGAuthoriseUserAgent extends AEGUserAgent {

// Try to reauthorise after a short delay
this.token = undefined;
await sleep(this.authRetryDelay);
await setTimeout(this.authRetryDelay);
}
}
}
Expand Down Expand Up @@ -208,7 +210,7 @@ export class AEGAuthoriseUserAgent extends AEGUserAgent {
this.token = {
authorizationHeader: `${token.tokenType} ${token.accessToken}`,
refreshToken: token.refreshToken,
expiresAt: Date.now() + token.expiresIn * 1000
expiresAt: Date.now() + token.expiresIn * MS
};
}

Expand Down
7 changes: 4 additions & 3 deletions src/aegapi-ua.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,13 @@ import { Client, Dispatcher } from 'undici';
import { buffer } from 'stream/consumers';
import { gunzipSync } from 'zlib';
import { Checker, IErrorDetail } from 'ts-interface-checker';
import { setTimeout } from 'node:timers/promises';

import { AEG_API_KEY, AEG_API_URL,
PLUGIN_NAME, PLUGIN_VERSION } from './settings';
import { AEGAPIError, AEGAPIStatusCodeError,
AEGAPIValidationError } from './aegapi-error';
import { columns, getValidationTree, sleep } from './utils';
import { columns, getValidationTree, MS } from './utils';
import { Config } from './config-types';
import { IncomingHttpHeaders } from 'undici/types/header';

Expand Down Expand Up @@ -67,7 +68,7 @@ export class AEGUserAgent {
// Delays between retries
readonly retryDelay = {
min: 500,
max: 60 * 1000,
max: 60 * MS,
factor: 2.0
};

Expand Down Expand Up @@ -187,7 +188,7 @@ export class AEGUserAgent {
++retryCount;

// Delay before trying again
await sleep(retryDelay);
await setTimeout(retryDelay);
retryDelay = Math.min(retryDelay * this.retryDelay.factor, this.retryDelay.max);
}
}
Expand Down
9 changes: 6 additions & 3 deletions src/heartbeat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@
// Copyright © 2022-2023 Alexander Thoukydides

import { Logger } from 'homebridge';
import { logError, sleep } from './utils';

import { setTimeout as setTimeoutP } from 'node:timers/promises';

import { MS, logError, sleep } from './utils';

// Multiple of interval to treat as a failure
const TIMEOUT_MULTIPLE = 3;
const TIMEOUT_OFFSET = 10 * 1000;
const TIMEOUT_OFFSET = 10 * MS;

// Perform an action periodically with error reporting and timeout
export class Heartbeat {
Expand Down Expand Up @@ -39,7 +42,7 @@ export class Heartbeat {
logError(this.log, this.name, err);
this.lastError = err;
}
await sleep(this.interval);
await setTimeoutP(this.interval);
}
}

Expand Down
6 changes: 3 additions & 3 deletions src/platform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { checkDependencyVersions } from './check-versions';
import { AEGAccount } from './aeg-account';
import { AEGRobot } from './aeg-robot';
import { Config } from './config-types';
import { deepMerge, getValidationTree, logError } from './utils';
import { deepMerge, getValidationTree, logError, plural } from './utils';
import { PrefixLogger } from './logger';
import configTI from './ti/config-types-ti';

Expand Down Expand Up @@ -129,7 +129,7 @@ export class AEGPlatform implements DynamicPlatformPlugin {
// Add accessories for any robots associated with the AEG account
const account = new AEGAccount(this.log, this.config);
const robotPromises = await account.getRobots();
this.log.info(`Found ${robotPromises.length} robot vacuum(s)`);
this.log.info(`Found ${plural(robotPromises.length, 'robot vacuum')}`);
await Promise.all(robotPromises.map(this.addRobotAccessory.bind(this)));
}

Expand Down Expand Up @@ -165,7 +165,7 @@ export class AEGPlatform implements DynamicPlatformPlugin {
if (!rmAccessories.length) return;

// Remove the identified accessories
this.log.warn(`Removing ${rmAccessories.length} cached accessories that are no longer required`);
this.log.warn(`Removing ${plural(rmAccessories.length, 'cached accessory')} that are no longer required`);
this.hb.unregisterPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, rmAccessories);
rmAccessories.forEach(accessory => delete this.accessories[accessory.UUID]);
}
Expand Down
Loading

0 comments on commit 3ae793e

Please sign in to comment.