From c1a2fc13a2f856d098da09398b3f1931c0657921 Mon Sep 17 00:00:00 2001 From: Long Zhao Date: Thu, 1 Aug 2024 12:59:59 +1000 Subject: [PATCH] feat: Update garage door status pooling logic --- src/CGDGarageDoor.ts | 65 +++++++++++++++++++++++++++++++++----------- src/platform.ts | 26 +++++++----------- src/retry.ts | 13 +++++++-- 3 files changed, 70 insertions(+), 34 deletions(-) diff --git a/src/CGDGarageDoor.ts b/src/CGDGarageDoor.ts index fffc2d6..a366a59 100644 --- a/src/CGDGarageDoor.ts +++ b/src/CGDGarageDoor.ts @@ -27,14 +27,20 @@ enum DoorState { Error, } +type StatusUpdateListener = () => void; + + export class CGDGarageDoor { private readonly log: Logging; private config: Config; private status?: Status; + private statusUpdateListener?: StatusUpdateListener; constructor(log: Logging, config: Config) { this.log = log; this.config = config; + + this.poolStatus(); } private run = async ({ cmd, value, softValue = value }) => { @@ -63,9 +69,12 @@ export class CGDGarageDoor { }, { retries: 3, onRetry: (error, retries) => { - this.log.warn(`Failed to run command[Retrying ${retries}]: ${cmd}=${value}`); + this.log.warn(`Failed to run command [${retries} retries]: ${cmd}=${value}`); this.log.warn(JSON.stringify(error)); }, + onRecover: (retries) => { + this.log.info(`Recovered to run command [${retries} retries]: ${cmd}=${value}`); + }, onFail: (error) => { this.log.error(`Failed to run command: ${cmd}=${value}`); this.log.error(JSON.stringify(error)); @@ -87,6 +96,13 @@ export class CGDGarageDoor { this.status = data as Status; }; + private poolStatus = async () => { + await this.refreshStatus(); + this.statusUpdateListener?.(); + + setTimeout(this.poolStatus, 2000); + }; + private getDoorState = (): DoorState => { if (this.status?.door.startsWith('Closed')) { return DoorState.Closed; @@ -108,25 +124,38 @@ export class CGDGarageDoor { return DoorState.Stopped; } - this.log.error(`[getDoorCurrentState] Unknown door status: ${this.status?.door}`); + this.log.error(`[getDoorState] Unknown door status: ${this.status?.door}`); return DoorState.Error; }; - public poolStatus = async () => { - await this.refreshStatus(); - - setTimeout(this.poolStatus, 2000); + public onStatusUpdate = (listener: StatusUpdateListener) => { + this.statusUpdateListener = listener; }; - public getDoorCurrentState = (): number => { + public waitForStatus = () => new Promise((resolve) => { + this.log.info('Pulse - Ping...'); + const interval = setInterval(() => { + if (this.status) { + this.log.info('Pulse - Pong!'); + clearInterval(interval); + resolve(); + } + }, 1000); + }); + + public getCurrentDoorState = (): number => { const doorState = this.getDoorState(); - // static readonly OPEN = 0; - // static readonly CLOSED = 1; - // static readonly OPENING = 2; - // static readonly CLOSING = 3; - // static readonly STOPPED = 4; + // export declare class CurrentDoorState extends Characteristic { + // static readonly UUID: string; + // static readonly OPEN = 0; + // static readonly CLOSED = 1; + // static readonly OPENING = 2; + // static readonly CLOSING = 3; + // static readonly STOPPED = 4; + // constructor(); + // } if (doorState === DoorState.Error) { this.log.error(`[getDoorCurrentState] Unknown door state: ${doorState}`); @@ -142,11 +171,15 @@ export class CGDGarageDoor { }[doorState]; }; - public getDoorTargetState = (): number => { + public getTargetDoorState = (): number => { const doorState = this.getDoorState(); - // static readonly OPEN = 0; - // static readonly CLOSED = 1; + // export declare class TargetDoorState extends Characteristic { + // static readonly UUID: string; + // static readonly OPEN = 0; + // static readonly CLOSED = 1; + // constructor(); + // } if (doorState === DoorState.Error) { this.log.error(`[getDoorTargetState] Unknown door state: ${doorState}`); @@ -162,7 +195,7 @@ export class CGDGarageDoor { }[doorState]; }; - public setDoorTargetState = async (value: number): Promise => { + public setTargetDoorState = async (value: number): Promise => { if (value === 0) { this.log.debug('Opening door...'); await this.run({ cmd: 'door', value: 'open', softValue: 'Opening' }); diff --git a/src/platform.ts b/src/platform.ts index 2dcc230..77560b7 100644 --- a/src/platform.ts +++ b/src/platform.ts @@ -16,8 +16,6 @@ export class CGDCameraPlatform implements DynamicPlatformPlugin { private readonly accessories: PlatformAccessory[] = []; - private refreshInterval?: NodeJS.Timer; - constructor(log: Logging, config: PlatformConfig, api: API) { this.log = log; this.api = api; @@ -39,13 +37,6 @@ export class CGDCameraPlatform implements DynamicPlatformPlugin { this.log('Did finish launching'); this.addAccessory(deviceHostname, cgdGarageDoor); }); - - api.on(APIEvent.SHUTDOWN, () => { - this.log('SHUTDOWN'); - if (this.refreshInterval) { - clearInterval(this.refreshInterval); - } - }); } configureAccessory(accessory: PlatformAccessory): void { @@ -54,7 +45,7 @@ export class CGDCameraPlatform implements DynamicPlatformPlugin { } async addAccessory(name: string, cgdGarageDoor: CGDGarageDoor) { - await cgdGarageDoor.poolStatus(); + await cgdGarageDoor.waitForStatus(); this.log('Adding new accessory with name %s', name); @@ -86,11 +77,11 @@ export class CGDCameraPlatform implements DynamicPlatformPlugin { const garageDoorOpener = accessory.getService(this.api.hap.Service.GarageDoorOpener) || accessory.addService(new this.api.hap.Service.GarageDoorOpener(accessory.displayName)); garageDoorOpener.getCharacteristic(this.api.hap.Characteristic.CurrentDoorState) - .onGet(() => cgdGarageDoor.getDoorCurrentState()); + .onGet(() => cgdGarageDoor.getCurrentDoorState()); garageDoorOpener.getCharacteristic(this.api.hap.Characteristic.TargetDoorState) - .onGet(() => cgdGarageDoor.getDoorTargetState()) - .onSet((value) => cgdGarageDoor.setDoorTargetState(+value)); + .onGet(() => cgdGarageDoor.getTargetDoorState()) + .onSet((value) => cgdGarageDoor.setTargetDoorState(+value)); const lightbulb = accessory.getService(this.api.hap.Service.Lightbulb) || accessory.addService(new this.api.hap.Service.Lightbulb(accessory.displayName)); @@ -104,16 +95,19 @@ export class CGDCameraPlatform implements DynamicPlatformPlugin { .onGet(() => cgdGarageDoor.getVacation()) .onSet((value) => cgdGarageDoor.setVacation(value)); - this.refreshInterval = setInterval(() => { + cgdGarageDoor.onStatusUpdate(() => { garageDoorOpener - .getCharacteristic(this.api.hap.Characteristic.CurrentDoorState).updateValue(cgdGarageDoor.getDoorCurrentState()); + .getCharacteristic(this.api.hap.Characteristic.CurrentDoorState).updateValue(cgdGarageDoor.getCurrentDoorState()); + + garageDoorOpener + .getCharacteristic(this.api.hap.Characteristic.TargetDoorState).updateValue(cgdGarageDoor.getTargetDoorState()); lightbulb .getCharacteristic(this.api.hap.Characteristic.On).updateValue(cgdGarageDoor.getLightbulb()); vacationSwitch .getCharacteristic(this.api.hap.Characteristic.On).updateValue(cgdGarageDoor.getVacation()); - }, 2000); + }); this.log('Garage Door Accessory %s configured!', accessory.displayName); } diff --git a/src/retry.ts b/src/retry.ts index 272a643..dbecbdc 100644 --- a/src/retry.ts +++ b/src/retry.ts @@ -1,14 +1,22 @@ interface Config { retries: number; + isRetry?: boolean; onRetry: (error: unknown, retries: number) => void; + onRecover: (retries: number) => void; onFail: (error: unknown) => void; } const retry = async (fn: () => Promise, config: Config) => { - const { retries, onRetry, onFail } = config; + const { retries, onRetry, onRecover, onFail, isRetry } = config; try { - return await fn(); + const data = await fn(); + + if (isRetry) { + onRecover(retries); + } + + return data; } catch (error) { if (retries === 0) { return onFail(error); @@ -18,6 +26,7 @@ const retry = async (fn: () => Promise, config: Config) => { return retry(fn, { ...config, + isRetry: true, retries: retries - 1, }); }