From 286065c0f100b1704524ad2fce56c16cb64490fc Mon Sep 17 00:00:00 2001 From: kyleboyer Date: Mon, 22 Jul 2024 22:46:25 -0500 Subject: [PATCH] Code deduplication; fixed accessory manufacturer/model/serial/name info --- package-lock.json | 4 +- package.json | 2 +- src/accessory/base.ts | 103 +++++++++++++++++++- src/accessory/garage-door.ts | 96 ++----------------- src/accessory/garage-learn-mode.ts | 95 ++----------------- src/accessory/garage-light.ts | 94 ++---------------- src/accessory/garage-lock.ts | 95 ++----------------- src/accessory/garage-motion-sensor.ts | 93 ++---------------- src/accessory/garage-obstruction-sensor.ts | 97 +++---------------- src/accessory/garage-pre-close-warning.ts | 95 ++----------------- src/hub.ts | 105 ++++++++++++--------- src/platform.ts | 2 +- src/utils/formatters.ts | 2 + 13 files changed, 229 insertions(+), 654 deletions(-) create mode 100644 src/utils/formatters.ts diff --git a/package-lock.json b/package-lock.json index 605bf98..f390e5f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "homebridge-blaq", - "version": "0.2.16", + "version": "0.2.17", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "homebridge-blaq", - "version": "0.2.16", + "version": "0.2.17", "funding": [ { "type": "github", diff --git a/package.json b/package.json index 343a868..9b1d61c 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "private": false, "displayName": "Konnected BlaQ", "name": "homebridge-blaq", - "version": "0.2.16", + "version": "0.2.17", "description": "Control and view your garage door(s) remotely with real-time updates using Konnected's BlaQ hardware", "license": "Apache-2.0", "type": "module", diff --git a/src/accessory/base.ts b/src/accessory/base.ts index 6a692fc..3ef574b 100644 --- a/src/accessory/base.ts +++ b/src/accessory/base.ts @@ -1,8 +1,107 @@ -import { LogMessageEvent, PingMessageEvent, StateUpdateMessageEvent } from '../utils/eventsource'; +import { CharacteristicValue, Logger, PlatformAccessory, Service, WithUUID } from 'homebridge'; +import { LogMessageEvent, PingMessageEvent, StateUpdateMessageEvent, StateUpdateRecord } from '../utils/eventsource'; +import { BlaQHomebridgePluginPlatform } from '../platform'; +import { BlaQTextSensorEvent } from '../types'; -export interface BaseBlaQAccessory { +export interface BaseBlaQAccessoryInterface { setAPIBaseURL: (apiBaseURL: string) => void; handleStateEvent: (stateEvent: StateUpdateMessageEvent) => void; handleLogEvent?: (logEvent: LogMessageEvent) => void; handlePingEvent?: (pingEvent: PingMessageEvent) => void; } + +export type BaseBlaQAccessoryConstructorParams = { + accessory: PlatformAccessory; + apiBaseURL: string; + friendlyName: string; + platform: BlaQHomebridgePluginPlatform; + serialNumber: string; +}; + +export const correctAPIBaseURL = (inputURL: string) => { + let correctedAPIBaseURL = inputURL; + if(!correctedAPIBaseURL.includes('://')){ + correctedAPIBaseURL = `http://${correctedAPIBaseURL}`; + } + if(correctedAPIBaseURL.endsWith('/')){ + correctedAPIBaseURL = correctedAPIBaseURL.slice(0, -1); + } + return correctedAPIBaseURL; +}; + +export class BaseBlaQAccessory implements BaseBlaQAccessoryInterface { + protected apiBaseURL: string; + protected firmwareVersion?: string; + protected readonly accessory: PlatformAccessory; + protected readonly accessoryInformationService: Service; + protected readonly logger: Logger; + protected readonly friendlyName: string; + protected readonly platform: BlaQHomebridgePluginPlatform; + protected readonly serialNumber: string; + + constructor({ + accessory, + apiBaseURL, + friendlyName, + platform, + serialNumber, + }: BaseBlaQAccessoryConstructorParams){ + this.platform = platform; + this.logger = this.platform.logger; + this.accessory = accessory; + this.friendlyName = friendlyName; + this.serialNumber = serialNumber; + this.apiBaseURL = correctAPIBaseURL(apiBaseURL); + this.accessoryInformationService = this.getOrAddService(this.platform.service.AccessoryInformation); + // set accessory information + this.accessoryInformationService + .setCharacteristic(this.platform.characteristic.Manufacturer, 'Konnected') + .setCharacteristic(this.platform.characteristic.Model, 'GDO BlaQ') + .setCharacteristic(this.platform.characteristic.SerialNumber, this.serialNumber) + .setCharacteristic(this.platform.characteristic.Name, this.friendlyName); + // Publish firmware version; this may not be initialized yet, so we set a getter. + // Note that this is against the AccessoryInformation service, not the GDO service. + this.accessoryInformationService + .getCharacteristic(this.platform.characteristic.FirmwareRevision) + .onGet(this.getFirmwareVersion.bind(this)); + } + + protected getOrAddService(service: WithUUID | Service): Service{ + return this.accessory.getService(service as WithUUID) || + this.accessory.addService(service as Service); + } + + handleStateEvent(stateEvent: StateUpdateMessageEvent){ + try { + const stateInfo = JSON.parse(stateEvent.data) as StateUpdateRecord; + if (['text_sensor-esphome_version', 'text_sensor-firmware_version'].includes(stateInfo.id)) { + const b = stateInfo as BlaQTextSensorEvent; + if (b.value && b.value === b.state) { + this.setFirmwareVersion(b.value); + } else { + this.logger.error('Mismatched firmware versions in value/state:', b.value, b.state); + this.firmwareVersion = undefined; + } + } + } catch(e) { + this.logger.error('Cannot deserialize message:', stateEvent); + this.logger.error('Deserialization yielded:', e); + } + } + + getFirmwareVersion(): CharacteristicValue { + return this.firmwareVersion || ''; + } + + protected setFirmwareVersion(version: string) { + this.firmwareVersion = version; + this.accessoryInformationService.setCharacteristic( + this.platform.characteristic.FirmwareRevision, + version, + ); + } + + setAPIBaseURL(url: string){ + this.apiBaseURL = correctAPIBaseURL(url); + } +} \ No newline at end of file diff --git a/src/accessory/garage-door.ts b/src/accessory/garage-door.ts index ed81ce2..6a1c4eb 100644 --- a/src/accessory/garage-door.ts +++ b/src/accessory/garage-door.ts @@ -1,12 +1,10 @@ -import { CharacteristicValue, Logger, PlatformAccessory, Service } from 'homebridge'; +import { CharacteristicValue, Service } from 'homebridge'; import fetch from 'node-fetch'; // I am, in fact, trying to make fetch happen. -import { BlaQHomebridgePluginPlatform } from '../platform.js'; import { BlaQBinarySensorEvent, BlaQCoverDoorEvent, BlaQLockEvent, - BlaQTextSensorEvent, CurrentOperationType, GarageCoverType, GarageLockType, @@ -14,85 +12,37 @@ import { OpenClosedStateType, } from '../types.js'; import { LogMessageEvent, StateUpdateMessageEvent, StateUpdateRecord } from '../utils/eventsource.js'; -import { BaseBlaQAccessory } from './base.js'; +import { BaseBlaQAccessory, BaseBlaQAccessoryConstructorParams } from './base.js'; const BINARY_SENSOR_PREFIX = 'binary_sensor-'; const COVER_PREFIX = 'cover-'; const LOCK_PREFIX = 'lock-'; -const correctAPIBaseURL = (inputURL: string) => { - let correctedAPIBaseURL = inputURL; - if(!correctedAPIBaseURL.includes('://')){ - correctedAPIBaseURL = `http://${correctedAPIBaseURL}`; - } - if(correctedAPIBaseURL.endsWith('/')){ - correctedAPIBaseURL = correctedAPIBaseURL.slice(0, -1); - } - return correctedAPIBaseURL; -}; - -type BlaQGarageDoorAccessoryConstructorParams = { - platform: BlaQHomebridgePluginPlatform; - accessory: PlatformAccessory; - model: string; - serialNumber: string; - apiBaseURL: string; -}; - /** * Platform Accessory * An instance of this class is created for each accessory your platform registers * Each accessory may expose multiple services of different service types. */ -export class BlaQGarageDoorAccessory implements BaseBlaQAccessory { - private logger: Logger; - private accessoryInformationService: Service; +export class BlaQGarageDoorAccessory extends BaseBlaQAccessory { private garageDoorService: Service; private state?: OpenClosedStateType; private position?: number; // percentage open(100)/closed(0); might not always be accurate private targetPosition?: number; // percentage open(100)/closed(0); might not always be accurate private currentOperation?: CurrentOperationType; private obstructed?: boolean; - private firmwareVersion?: string; private lockState: LockStateType = 'UNKNOWN'; private lockType?: GarageLockType = 'lock'; private coverType?: GarageCoverType = 'garage_door'; private preClosing?: boolean; - private apiBaseURL: string; - private readonly platform: BlaQHomebridgePluginPlatform; - private readonly accessory: PlatformAccessory; - private readonly model: string; - private readonly serialNumber: string; - - constructor({ - platform, - accessory, - model, - serialNumber, - apiBaseURL, - }: BlaQGarageDoorAccessoryConstructorParams) { - this.platform = platform; - this.logger = this.platform.logger; + + constructor(args: BaseBlaQAccessoryConstructorParams) { + super(args); this.logger.debug('Initializing BlaQGarageDoorAccessory...'); - this.accessory = accessory; - this.model = model; - this.serialNumber = serialNumber; - this.apiBaseURL = correctAPIBaseURL(apiBaseURL); - this.garageDoorService = this.accessory.getService(this.platform.service.GarageDoorOpener) - || this.accessory.addService(this.platform.service.GarageDoorOpener); - - this.accessoryInformationService = this.accessory.getService(this.platform.service.AccessoryInformation) - || this.accessory.addService(this.platform.service.AccessoryInformation); - - // set accessory information - this.accessoryInformationService - .setCharacteristic(this.platform.characteristic.Manufacturer, 'Konnected') - .setCharacteristic(this.platform.characteristic.Model, this.model) - .setCharacteristic(this.platform.characteristic.SerialNumber, this.serialNumber); + this.garageDoorService = this.getOrAddService(this.platform.service.GarageDoorOpener); // Set the service name. This is what is displayed as the name on the Home // app. We use what we stored in `accessory.context` in `discoverDevices`. - this.garageDoorService.setCharacteristic(this.platform.characteristic.Name, accessory.context.device.displayName); + this.garageDoorService.setCharacteristic(this.platform.characteristic.Name, this.accessory.context.device.displayName); this.garageDoorService.getCharacteristic(this.platform.characteristic.CurrentDoorState) .onGet(this.getCurrentDoorState.bind(this)); @@ -117,26 +67,9 @@ export class BlaQGarageDoorAccessory implements BaseBlaQAccessory { this.garageDoorService.getCharacteristic(this.platform.characteristic.LockTargetState) .onSet(this.updateLockState.bind(this)); - // Publish firmware version; this may not be initialized yet, so we set a getter. - // Note that this is against the AccessoryInformation service, not the GDO service. - this.accessoryInformationService - .getCharacteristic(this.platform.characteristic.FirmwareRevision) - .onGet(this.getFirmwareVersion.bind(this)); this.logger.debug('Initialized BlaQGarageDoorAccessory!'); } - getFirmwareVersion(): CharacteristicValue { - return this.firmwareVersion || ''; - } - - private setFirmwareVersion(version: string) { - this.firmwareVersion = version; - this.accessoryInformationService.setCharacteristic( - this.platform.characteristic.FirmwareRevision, - version, - ); - } - private async updateLockState(lockState: CharacteristicValue){ const lockDesired = lockState === this.platform.characteristic.LockTargetState.SECURED; const apiTarget: string = lockDesired ? 'lock' : 'unlock'; @@ -326,11 +259,8 @@ export class BlaQGarageDoorAccessory implements BaseBlaQAccessory { ); } - setAPIBaseURL(url: string){ - this.apiBaseURL = correctAPIBaseURL(url); - } - handleStateEvent(stateEvent: StateUpdateMessageEvent){ + super.handleStateEvent(stateEvent); try { const stateInfo = JSON.parse(stateEvent.data) as StateUpdateRecord; if (['cover-garage_door', 'cover-door'].includes(stateInfo.id)) { @@ -349,14 +279,6 @@ export class BlaQGarageDoorAccessory implements BaseBlaQAccessory { if (short_id === 'obstruction') { this.setObstructed(binarySensorEvent.value); } - } else if (['text_sensor-esphome_version', 'text_sensor-firmware_version'].includes(stateInfo.id)) { - const b = stateInfo as BlaQTextSensorEvent; - if (b.value === b.state && b.value !== '' && b.value !== null && b.value !== undefined) { - this.setFirmwareVersion(b.value); - } else { - this.logger.error('Mismatched firmware versions in value/state:', b.value, b.state); - this.firmwareVersion = undefined; - } } else if (['lock-lock', 'lock-lock_remotes'].includes(stateInfo.id)) { this.lockType = stateInfo.id.split(LOCK_PREFIX).pop() as GarageLockType; const b = stateInfo as BlaQLockEvent; diff --git a/src/accessory/garage-learn-mode.ts b/src/accessory/garage-learn-mode.ts index 6e67177..94f95e6 100644 --- a/src/accessory/garage-learn-mode.ts +++ b/src/accessory/garage-learn-mode.ts @@ -1,32 +1,11 @@ -import { CharacteristicValue, Logger, PlatformAccessory, Service } from 'homebridge'; +import { CharacteristicValue, Service } from 'homebridge'; import fetch from 'node-fetch'; // I am, in fact, trying to make fetch happen. -import { BlaQHomebridgePluginPlatform } from '../platform.js'; import { BlaQButtonEvent, - BlaQTextSensorEvent, } from '../types.js'; import { LogMessageEvent, StateUpdateMessageEvent, StateUpdateRecord } from '../utils/eventsource.js'; -import { BaseBlaQAccessory } from './base.js'; - -const correctAPIBaseURL = (inputURL: string) => { - let correctedAPIBaseURL = inputURL; - if(!correctedAPIBaseURL.includes('://')){ - correctedAPIBaseURL = `http://${correctedAPIBaseURL}`; - } - if(correctedAPIBaseURL.endsWith('/')){ - correctedAPIBaseURL = correctedAPIBaseURL.slice(0, -1); - } - return correctedAPIBaseURL; -}; - -type BlaQGarageLearnModeAccessoryConstructorParams = { - platform: BlaQHomebridgePluginPlatform; - accessory: PlatformAccessory; - model: string; - serialNumber: string; - apiBaseURL: string; -}; +import { BaseBlaQAccessory, BaseBlaQAccessoryConstructorParams } from './base.js'; export const label = 'Learn/Pair Mode'; @@ -35,72 +14,25 @@ export const label = 'Learn/Pair Mode'; * An instance of this class is created for each accessory your platform registers * Each accessory may expose multiple services of different service types. */ -export class BlaQGarageLearnModeAccessory implements BaseBlaQAccessory { - private logger: Logger; - private accessoryInformationService: Service; +export class BlaQGarageLearnModeAccessory extends BaseBlaQAccessory { private switchService: Service; - private apiBaseURL: string; - private firmwareVersion?: string; private isOn?: boolean; - private readonly platform: BlaQHomebridgePluginPlatform; - private readonly accessory: PlatformAccessory; - private readonly model: string; - private readonly serialNumber: string; - constructor({ - platform, - accessory, - model, - serialNumber, - apiBaseURL, - }: BlaQGarageLearnModeAccessoryConstructorParams) { - this.platform = platform; - this.logger = this.platform.logger; + constructor(args: BaseBlaQAccessoryConstructorParams) { + super(args); this.logger.debug('Initializing BlaQGarageLearnModeAccessory...'); - this.accessory = accessory; - this.model = model; - this.serialNumber = serialNumber; - this.apiBaseURL = correctAPIBaseURL(apiBaseURL); - this.switchService = this.accessory.getService(this.platform.service.Switch) - || this.accessory.addService(this.platform.service.Switch); - - this.accessoryInformationService = this.accessory.getService(this.platform.service.AccessoryInformation) - || this.accessory.addService(this.platform.service.AccessoryInformation); - - // set accessory information - this.accessoryInformationService - .setCharacteristic(this.platform.characteristic.Manufacturer, 'Konnected') - .setCharacteristic(this.platform.characteristic.Model, this.model) - .setCharacteristic(this.platform.characteristic.SerialNumber, this.serialNumber); + this.switchService = this.getOrAddService(this.platform.service.Switch); // Set the service name. This is what is displayed as the name on the Home // app. We use what we stored in `accessory.context` in `discoverDevices`. - this.switchService.setCharacteristic(this.platform.characteristic.Name, accessory.context.device.displayName + ' ' + label); + this.switchService.setCharacteristic(this.platform.characteristic.Name, this.accessory.context.device.displayName + ' ' + label); this.switchService.getCharacteristic(this.platform.characteristic.On) .onGet(this.getIsOn.bind(this)) .onSet(this.changeIsOn.bind(this)); - - // Publish firmware version; this may not be initialized yet, so we set a getter. - // Note that this is against the AccessoryInformation service, not the GDO service. - this.accessoryInformationService - .getCharacteristic(this.platform.characteristic.FirmwareRevision) - .onGet(this.getFirmwareVersion.bind(this)); this.logger.debug('Initialized BlaQGarageLearnModeAccessory!'); } - getFirmwareVersion(): CharacteristicValue { - return this.firmwareVersion || ''; - } - - private setFirmwareVersion(version: string) { - this.firmwareVersion = version; - this.accessoryInformationService.setCharacteristic( - this.platform.characteristic.FirmwareRevision, - version, - ); - } - getIsOn(): CharacteristicValue { return this.isOn || false; } @@ -120,11 +52,8 @@ export class BlaQGarageLearnModeAccessory implements BaseBlaQAccessory { } } - setAPIBaseURL(url: string){ - this.apiBaseURL = correctAPIBaseURL(url); - } - handleStateEvent(stateEvent: StateUpdateMessageEvent){ + super.handleStateEvent(stateEvent); try { const stateInfo = JSON.parse(stateEvent.data) as StateUpdateRecord; if (['switch-learn'].includes(stateInfo.id)) { @@ -132,14 +61,6 @@ export class BlaQGarageLearnModeAccessory implements BaseBlaQAccessory { if(['OFF', 'ON'].includes(buttonEvent.state?.toUpperCase() || '')){ this.setIsOn(buttonEvent.state?.toUpperCase() === 'ON'); } - } else if (['text_sensor-esphome_version', 'text_sensor-firmware_version'].includes(stateInfo.id)) { - const b = stateInfo as BlaQTextSensorEvent; - if (b.value === b.state && b.value !== '' && b.value !== null && b.value !== undefined) { - this.setFirmwareVersion(b.value); - } else { - this.logger.error('Mismatched firmware versions in value/state:', b.value, b.state); - this.firmwareVersion = undefined; - } } } catch(e) { this.logger.error('Cannot deserialize message:', stateEvent); diff --git a/src/accessory/garage-light.ts b/src/accessory/garage-light.ts index 84392b9..0092c1e 100644 --- a/src/accessory/garage-light.ts +++ b/src/accessory/garage-light.ts @@ -1,36 +1,15 @@ -import { CharacteristicValue, Logger, PlatformAccessory, Service } from 'homebridge'; +import { CharacteristicValue, Service } from 'homebridge'; import fetch from 'node-fetch'; // I am, in fact, trying to make fetch happen. -import { BlaQHomebridgePluginPlatform } from '../platform.js'; import { BlaQButtonEvent, - BlaQTextSensorEvent, GarageLightType, } from '../types.js'; import { LogMessageEvent, StateUpdateMessageEvent, StateUpdateRecord } from '../utils/eventsource.js'; -import { BaseBlaQAccessory } from './base.js'; +import { BaseBlaQAccessory, BaseBlaQAccessoryConstructorParams } from './base.js'; const LIGHT_PREFIX = 'light-'; -const correctAPIBaseURL = (inputURL: string) => { - let correctedAPIBaseURL = inputURL; - if(!correctedAPIBaseURL.includes('://')){ - correctedAPIBaseURL = `http://${correctedAPIBaseURL}`; - } - if(correctedAPIBaseURL.endsWith('/')){ - correctedAPIBaseURL = correctedAPIBaseURL.slice(0, -1); - } - return correctedAPIBaseURL; -}; - -type BlaQGarageLightAccessoryConstructorParams = { - platform: BlaQHomebridgePluginPlatform; - accessory: PlatformAccessory; - model: string; - serialNumber: string; - apiBaseURL: string; -}; - export const label = 'Light'; /** @@ -38,73 +17,27 @@ export const label = 'Light'; * An instance of this class is created for each accessory your platform registers * Each accessory may expose multiple services of different service types. */ -export class BlaQGarageLightAccessory implements BaseBlaQAccessory { - private logger: Logger; - private accessoryInformationService: Service; +export class BlaQGarageLightAccessory extends BaseBlaQAccessory { private lightbulbService: Service; - private apiBaseURL: string; - private firmwareVersion?: string; private isOn?: boolean; private lightType?: GarageLightType = 'garage_light'; - private readonly platform: BlaQHomebridgePluginPlatform; - private readonly accessory: PlatformAccessory; - private readonly model: string; - private readonly serialNumber: string; - constructor({ - platform, - accessory, - model, - serialNumber, - apiBaseURL, - }: BlaQGarageLightAccessoryConstructorParams) { - this.platform = platform; - this.logger = this.platform.logger; + constructor(args: BaseBlaQAccessoryConstructorParams) { + super(args); this.logger.debug('Initializing BlaQGarageLightAccessory...'); - this.accessory = accessory; - this.model = model; - this.serialNumber = serialNumber; - this.apiBaseURL = correctAPIBaseURL(apiBaseURL); - this.lightbulbService = this.accessory.getService(this.platform.service.Lightbulb) - || this.accessory.addService(this.platform.service.Lightbulb); - - this.accessoryInformationService = this.accessory.getService(this.platform.service.AccessoryInformation) - || this.accessory.addService(this.platform.service.AccessoryInformation); - - // set accessory information - this.accessoryInformationService - .setCharacteristic(this.platform.characteristic.Manufacturer, 'Konnected') - .setCharacteristic(this.platform.characteristic.Model, this.model) - .setCharacteristic(this.platform.characteristic.SerialNumber, this.serialNumber); + this.lightbulbService = this.getOrAddService(this.platform.service.Lightbulb); // Set the service name. This is what is displayed as the name on the Home // app. We use what we stored in `accessory.context` in `discoverDevices`. - this.lightbulbService.setCharacteristic(this.platform.characteristic.Name, accessory.context.device.displayName + ' ' + label); + this.lightbulbService.setCharacteristic(this.platform.characteristic.Name, this.accessory.context.device.displayName + ' ' + label); this.lightbulbService.getCharacteristic(this.platform.characteristic.On) .onGet(this.getPowerState.bind(this)) .onSet(this.changePowerState.bind(this)); - // Publish firmware version; this may not be initialized yet, so we set a getter. - // Note that this is against the AccessoryInformation service, not the GDO service. - this.accessoryInformationService - .getCharacteristic(this.platform.characteristic.FirmwareRevision) - .onGet(this.getFirmwareVersion.bind(this)); this.logger.debug('Initialized BlaQGarageLightAccessory!'); } - getFirmwareVersion(): CharacteristicValue { - return this.firmwareVersion || ''; - } - - private setFirmwareVersion(version: string) { - this.firmwareVersion = version; - this.accessoryInformationService.setCharacteristic( - this.platform.characteristic.FirmwareRevision, - version, - ); - } - getPowerState(): CharacteristicValue { return this.isOn || false; } @@ -124,11 +57,8 @@ export class BlaQGarageLightAccessory implements BaseBlaQAccessory { } } - setAPIBaseURL(url: string){ - this.apiBaseURL = correctAPIBaseURL(url); - } - handleStateEvent(stateEvent: StateUpdateMessageEvent){ + super.handleStateEvent(stateEvent); try { const stateInfo = JSON.parse(stateEvent.data) as StateUpdateRecord; if (['light-garage_light', 'light-light'].includes(stateInfo.id)) { @@ -137,14 +67,6 @@ export class BlaQGarageLightAccessory implements BaseBlaQAccessory { if(['OFF', 'ON'].includes(buttonEvent.state?.toUpperCase() || '')){ this.setPowerState(buttonEvent.state?.toUpperCase() === 'ON'); } - } else if (['text_sensor-esphome_version', 'text_sensor-firmware_version'].includes(stateInfo.id)) { - const b = stateInfo as BlaQTextSensorEvent; - if (b.value === b.state && b.value !== '' && b.value !== null && b.value !== undefined) { - this.setFirmwareVersion(b.value); - } else { - this.logger.error('Mismatched firmware versions in value/state:', b.value, b.state); - this.firmwareVersion = undefined; - } } } catch(e) { this.logger.error('Cannot deserialize message:', stateEvent); diff --git a/src/accessory/garage-lock.ts b/src/accessory/garage-lock.ts index 14d8af1..02d6241 100644 --- a/src/accessory/garage-lock.ts +++ b/src/accessory/garage-lock.ts @@ -1,36 +1,15 @@ -import { CharacteristicValue, Logger, PlatformAccessory, Service } from 'homebridge'; +import { CharacteristicValue, Service } from 'homebridge'; import fetch from 'node-fetch'; // I am, in fact, trying to make fetch happen. -import { BlaQHomebridgePluginPlatform } from '../platform.js'; import { BlaQButtonEvent, - BlaQTextSensorEvent, GarageLockType, } from '../types.js'; import { LogMessageEvent, StateUpdateMessageEvent, StateUpdateRecord } from '../utils/eventsource.js'; -import { BaseBlaQAccessory } from './base.js'; +import { BaseBlaQAccessory, BaseBlaQAccessoryConstructorParams } from './base.js'; const LOCK_PREFIX = 'lock-'; -const correctAPIBaseURL = (inputURL: string) => { - let correctedAPIBaseURL = inputURL; - if(!correctedAPIBaseURL.includes('://')){ - correctedAPIBaseURL = `http://${correctedAPIBaseURL}`; - } - if(correctedAPIBaseURL.endsWith('/')){ - correctedAPIBaseURL = correctedAPIBaseURL.slice(0, -1); - } - return correctedAPIBaseURL; -}; - -type BlaQGarageLockAccessoryConstructorParams = { - platform: BlaQHomebridgePluginPlatform; - accessory: PlatformAccessory; - model: string; - serialNumber: string; - apiBaseURL: string; -}; - export const label = 'Lock Remotes'; /** @@ -38,75 +17,28 @@ export const label = 'Lock Remotes'; * An instance of this class is created for each accessory your platform registers * Each accessory may expose multiple services of different service types. */ -export class BlaQGarageLockAccessory implements BaseBlaQAccessory { - private logger: Logger; - private accessoryInformationService: Service; +export class BlaQGarageLockAccessory extends BaseBlaQAccessory { private lockService: Service; - private apiBaseURL: string; - private firmwareVersion?: string; private isLocked?: boolean; private lockType?: GarageLockType = 'lock'; - private readonly platform: BlaQHomebridgePluginPlatform; - private readonly accessory: PlatformAccessory; - private readonly model: string; - private readonly serialNumber: string; - constructor({ - platform, - accessory, - model, - serialNumber, - apiBaseURL, - }: BlaQGarageLockAccessoryConstructorParams) { - this.platform = platform; - this.logger = this.platform.logger; + constructor(args: BaseBlaQAccessoryConstructorParams) { + super(args); this.logger.debug('Initializing BlaQGarageLockAccessory...'); - this.accessory = accessory; - this.model = model; - this.serialNumber = serialNumber; - this.apiBaseURL = correctAPIBaseURL(apiBaseURL); - this.lockService = this.accessory.getService(this.platform.service.LockMechanism) - || this.accessory.addService(this.platform.service.LockMechanism); - - this.accessoryInformationService = this.accessory.getService(this.platform.service.AccessoryInformation) - || this.accessory.addService(this.platform.service.AccessoryInformation); - - // set accessory information - this.accessoryInformationService - .setCharacteristic(this.platform.characteristic.Manufacturer, 'Konnected') - .setCharacteristic(this.platform.characteristic.Model, this.model) - .setCharacteristic(this.platform.characteristic.SerialNumber, this.serialNumber); + this.lockService = this.getOrAddService(this.platform.service.LockMechanism); // Set the service name. This is what is displayed as the name on the Home // app. We use what we stored in `accessory.context` in `discoverDevices`. - this.lockService.setCharacteristic(this.platform.characteristic.Name, accessory.context.device.displayName + ' ' + label); + this.lockService.setCharacteristic(this.platform.characteristic.Name, this.accessory.context.device.displayName + ' ' + label); this.lockService.getCharacteristic(this.platform.characteristic.LockCurrentState) .onGet(this.getLockState.bind(this)); this.lockService.getCharacteristic(this.platform.characteristic.LockTargetState) .onSet(this.changeLockState.bind(this)); - - // Publish firmware version; this may not be initialized yet, so we set a getter. - // Note that this is against the AccessoryInformation service, not the GDO service. - this.accessoryInformationService - .getCharacteristic(this.platform.characteristic.FirmwareRevision) - .onGet(this.getFirmwareVersion.bind(this)); this.logger.debug('Initialized BlaQGarageLockAccessory!'); } - getFirmwareVersion(): CharacteristicValue { - return this.firmwareVersion || ''; - } - - private setFirmwareVersion(version: string) { - this.firmwareVersion = version; - this.accessoryInformationService.setCharacteristic( - this.platform.characteristic.FirmwareRevision, - version, - ); - } - getLockState(): CharacteristicValue { if(this.isLocked === undefined || this.isLocked === null){ return this.platform.characteristic.LockCurrentState.UNKNOWN; @@ -138,11 +70,8 @@ export class BlaQGarageLockAccessory implements BaseBlaQAccessory { } } - setAPIBaseURL(url: string){ - this.apiBaseURL = correctAPIBaseURL(url); - } - handleStateEvent(stateEvent: StateUpdateMessageEvent){ + super.handleStateEvent(stateEvent); try { const stateInfo = JSON.parse(stateEvent.data) as StateUpdateRecord; if (['lock-lock', 'lock-lock_remotes'].includes(stateInfo.id)) { @@ -151,14 +80,6 @@ export class BlaQGarageLockAccessory implements BaseBlaQAccessory { if(['UNLOCKED', 'LOCKED'].includes(buttonEvent.state?.toUpperCase() || '')){ this.setLockState(buttonEvent.state?.toUpperCase() === 'LOCKED'); } - } else if (['text_sensor-esphome_version', 'text_sensor-firmware_version'].includes(stateInfo.id)) { - const b = stateInfo as BlaQTextSensorEvent; - if (b.value === b.state && b.value !== '' && b.value !== null && b.value !== undefined) { - this.setFirmwareVersion(b.value); - } else { - this.logger.error('Mismatched firmware versions in value/state:', b.value, b.state); - this.firmwareVersion = undefined; - } } } catch(e) { this.logger.error('Cannot deserialize message:', stateEvent); diff --git a/src/accessory/garage-motion-sensor.ts b/src/accessory/garage-motion-sensor.ts index 739359c..e39579e 100644 --- a/src/accessory/garage-motion-sensor.ts +++ b/src/accessory/garage-motion-sensor.ts @@ -1,31 +1,11 @@ -import { CharacteristicValue, Logger, PlatformAccessory, Service } from 'homebridge'; +import { CharacteristicValue, Service } from 'homebridge'; -import { BlaQHomebridgePluginPlatform } from '../platform.js'; import { BlaQBinarySensorEvent, - BlaQTextSensorEvent, } from '../types.js'; import { LogMessageEvent, StateUpdateMessageEvent, StateUpdateRecord } from '../utils/eventsource.js'; -import { BaseBlaQAccessory } from './base.js'; +import { BaseBlaQAccessory, BaseBlaQAccessoryConstructorParams } from './base.js'; -const correctAPIBaseURL = (inputURL: string) => { - let correctedAPIBaseURL = inputURL; - if(!correctedAPIBaseURL.includes('://')){ - correctedAPIBaseURL = `http://${correctedAPIBaseURL}`; - } - if(correctedAPIBaseURL.endsWith('/')){ - correctedAPIBaseURL = correctedAPIBaseURL.slice(0, -1); - } - return correctedAPIBaseURL; -}; - -type BlaQGarageMotionSensorAccessoryConstructorParams = { - platform: BlaQHomebridgePluginPlatform; - accessory: PlatformAccessory; - model: string; - serialNumber: string; - apiBaseURL: string; -}; export const label = 'Motion Sensor'; @@ -34,71 +14,25 @@ export const label = 'Motion Sensor'; * An instance of this class is created for each accessory your platform registers * Each accessory may expose multiple services of different service types. */ -export class BlaQGarageMotionSensorAccessory implements BaseBlaQAccessory { - private logger: Logger; - private accessoryInformationService: Service; +export class BlaQGarageMotionSensorAccessory extends BaseBlaQAccessory { private motionSensorService: Service; - private apiBaseURL: string; - private firmwareVersion?: string; private motionDetected?: boolean; - private readonly platform: BlaQHomebridgePluginPlatform; - private readonly accessory: PlatformAccessory; - private readonly model: string; - private readonly serialNumber: string; - constructor({ - platform, - accessory, - model, - serialNumber, - apiBaseURL, - }: BlaQGarageMotionSensorAccessoryConstructorParams) { - this.platform = platform; - this.logger = this.platform.logger; + constructor(args: BaseBlaQAccessoryConstructorParams) { + super(args); this.logger.debug('Initializing BlaQGarageMotionSensorAccessory...'); - this.accessory = accessory; - this.model = model; - this.serialNumber = serialNumber; - this.apiBaseURL = correctAPIBaseURL(apiBaseURL); - this.motionSensorService = this.accessory.getService(this.platform.service.MotionSensor) - || this.accessory.addService(this.platform.service.MotionSensor); - - this.accessoryInformationService = this.accessory.getService(this.platform.service.AccessoryInformation) - || this.accessory.addService(this.platform.service.AccessoryInformation); - - // set accessory information - this.accessoryInformationService - .setCharacteristic(this.platform.characteristic.Manufacturer, 'Konnected') - .setCharacteristic(this.platform.characteristic.Model, this.model) - .setCharacteristic(this.platform.characteristic.SerialNumber, this.serialNumber); + this.motionSensorService = this.getOrAddService(this.platform.service.MotionSensor); // Set the service name. This is what is displayed as the name on the Home // app. We use what we stored in `accessory.context` in `discoverDevices`. - this.motionSensorService.setCharacteristic(this.platform.characteristic.Name, accessory.context.device.displayName + ' ' + label); + this.motionSensorService.setCharacteristic(this.platform.characteristic.Name, this.accessory.context.device.displayName + ' ' + label); this.motionSensorService.getCharacteristic(this.platform.characteristic.MotionDetected) .onGet(this.getMotionDetected.bind(this)); - // Publish firmware version; this may not be initialized yet, so we set a getter. - // Note that this is against the AccessoryInformation service, not the GDO service. - this.accessoryInformationService - .getCharacteristic(this.platform.characteristic.FirmwareRevision) - .onGet(this.getFirmwareVersion.bind(this)); this.logger.debug('Initialized BlaQGarageMotionSensorAccessory!'); } - getFirmwareVersion(): CharacteristicValue { - return this.firmwareVersion || ''; - } - - private setFirmwareVersion(version: string) { - this.firmwareVersion = version; - this.accessoryInformationService.setCharacteristic( - this.platform.characteristic.FirmwareRevision, - version, - ); - } - getMotionDetected(): CharacteristicValue { return this.motionDetected || false; } @@ -111,11 +45,8 @@ export class BlaQGarageMotionSensorAccessory implements BaseBlaQAccessory { ); } - setAPIBaseURL(url: string){ - this.apiBaseURL = correctAPIBaseURL(url); - } - handleStateEvent(stateEvent: StateUpdateMessageEvent){ + super.handleStateEvent(stateEvent); try { const stateInfo = JSON.parse(stateEvent.data) as StateUpdateRecord; if (['binary_sensor-motion'].includes(stateInfo.id)) { @@ -123,14 +54,6 @@ export class BlaQGarageMotionSensorAccessory implements BaseBlaQAccessory { if(['OFF', 'ON'].includes(sensorEvent.state.toUpperCase())){ this.setMotionDetected(sensorEvent.state.toUpperCase() === 'ON'); } - } else if (['text_sensor-esphome_version', 'text_sensor-firmware_version'].includes(stateInfo.id)) { - const b = stateInfo as BlaQTextSensorEvent; - if (b.value === b.state && b.value !== '' && b.value !== null && b.value !== undefined) { - this.setFirmwareVersion(b.value); - } else { - this.logger.error('Mismatched firmware versions in value/state:', b.value, b.state); - this.firmwareVersion = undefined; - } } } catch(e) { this.logger.error('Cannot deserialize message:', stateEvent); diff --git a/src/accessory/garage-obstruction-sensor.ts b/src/accessory/garage-obstruction-sensor.ts index 9e1706e..6f20a09 100644 --- a/src/accessory/garage-obstruction-sensor.ts +++ b/src/accessory/garage-obstruction-sensor.ts @@ -1,31 +1,10 @@ -import { CharacteristicValue, Logger, PlatformAccessory, Service } from 'homebridge'; +import { CharacteristicValue, Service } from 'homebridge'; -import { BlaQHomebridgePluginPlatform } from '../platform.js'; import { BlaQBinarySensorEvent, - BlaQTextSensorEvent, } from '../types.js'; import { LogMessageEvent, StateUpdateMessageEvent, StateUpdateRecord } from '../utils/eventsource.js'; -import { BaseBlaQAccessory } from './base.js'; - -const correctAPIBaseURL = (inputURL: string) => { - let correctedAPIBaseURL = inputURL; - if(!correctedAPIBaseURL.includes('://')){ - correctedAPIBaseURL = `http://${correctedAPIBaseURL}`; - } - if(correctedAPIBaseURL.endsWith('/')){ - correctedAPIBaseURL = correctedAPIBaseURL.slice(0, -1); - } - return correctedAPIBaseURL; -}; - -type BlaQGarageObstructionSensorAccessoryConstructorParams = { - platform: BlaQHomebridgePluginPlatform; - accessory: PlatformAccessory; - model: string; - serialNumber: string; - apiBaseURL: string; -}; +import { BaseBlaQAccessory, BaseBlaQAccessoryConstructorParams } from './base.js'; export const label = 'Obstruction Sensor'; @@ -34,71 +13,28 @@ export const label = 'Obstruction Sensor'; * An instance of this class is created for each accessory your platform registers * Each accessory may expose multiple services of different service types. */ -export class BlaQGarageObstructionSensorAccessory implements BaseBlaQAccessory { - private logger: Logger; - private accessoryInformationService: Service; +export class BlaQGarageObstructionSensorAccessory extends BaseBlaQAccessory { private occupancySensorService: Service; - private apiBaseURL: string; - private firmwareVersion?: string; private obstructionDetected?: boolean; - private readonly platform: BlaQHomebridgePluginPlatform; - private readonly accessory: PlatformAccessory; - private readonly model: string; - private readonly serialNumber: string; - constructor({ - platform, - accessory, - model, - serialNumber, - apiBaseURL, - }: BlaQGarageObstructionSensorAccessoryConstructorParams) { - this.platform = platform; - this.logger = this.platform.logger; + constructor(args: BaseBlaQAccessoryConstructorParams) { + super(args); this.logger.debug('Initializing BlaQGarageObstructionSensorAccessory...'); - this.accessory = accessory; - this.model = model; - this.serialNumber = serialNumber; - this.apiBaseURL = correctAPIBaseURL(apiBaseURL); - this.occupancySensorService = this.accessory.getService(this.platform.service.OccupancySensor) - || this.accessory.addService(this.platform.service.OccupancySensor); - - this.accessoryInformationService = this.accessory.getService(this.platform.service.AccessoryInformation) - || this.accessory.addService(this.platform.service.AccessoryInformation); - - // set accessory information - this.accessoryInformationService - .setCharacteristic(this.platform.characteristic.Manufacturer, 'Konnected') - .setCharacteristic(this.platform.characteristic.Model, this.model) - .setCharacteristic(this.platform.characteristic.SerialNumber, this.serialNumber); + this.occupancySensorService = this.getOrAddService(this.platform.service.OccupancySensor); // Set the service name. This is what is displayed as the name on the Home // app. We use what we stored in `accessory.context` in `discoverDevices`. - this.occupancySensorService.setCharacteristic(this.platform.characteristic.Name, accessory.context.device.displayName + ' ' + label); + this.occupancySensorService.setCharacteristic( + this.platform.characteristic.Name, + this.accessory.context.device.displayName + ' ' + label, + ); this.occupancySensorService.getCharacteristic(this.platform.characteristic.OccupancyDetected) .onGet(this.getObstructionDetected.bind(this)); - // Publish firmware version; this may not be initialized yet, so we set a getter. - // Note that this is against the AccessoryInformation service, not the GDO service. - this.accessoryInformationService - .getCharacteristic(this.platform.characteristic.FirmwareRevision) - .onGet(this.getFirmwareVersion.bind(this)); this.logger.debug('Initialized BlaQGarageObstructionSensorAccessory!'); } - getFirmwareVersion(): CharacteristicValue { - return this.firmwareVersion || ''; - } - - private setFirmwareVersion(version: string) { - this.firmwareVersion = version; - this.accessoryInformationService.setCharacteristic( - this.platform.characteristic.FirmwareRevision, - version, - ); - } - getObstructionDetected(): CharacteristicValue { return this.obstructionDetected || false; } @@ -111,11 +47,8 @@ export class BlaQGarageObstructionSensorAccessory implements BaseBlaQAccessory { ); } - setAPIBaseURL(url: string){ - this.apiBaseURL = correctAPIBaseURL(url); - } - handleStateEvent(stateEvent: StateUpdateMessageEvent){ + super.handleStateEvent(stateEvent); try { const stateInfo = JSON.parse(stateEvent.data) as StateUpdateRecord; if (['binary_sensor-obstruction'].includes(stateInfo.id)) { @@ -123,14 +56,6 @@ export class BlaQGarageObstructionSensorAccessory implements BaseBlaQAccessory { if(['OFF', 'ON'].includes(sensorEvent.state.toUpperCase())){ this.setObstructionDetected(sensorEvent.state.toUpperCase() === 'ON'); } - } else if (['text_sensor-esphome_version', 'text_sensor-firmware_version'].includes(stateInfo.id)) { - const b = stateInfo as BlaQTextSensorEvent; - if (b.value === b.state && b.value !== '' && b.value !== null && b.value !== undefined) { - this.setFirmwareVersion(b.value); - } else { - this.logger.error('Mismatched firmware versions in value/state:', b.value, b.state); - this.firmwareVersion = undefined; - } } } catch(e) { this.logger.error('Cannot deserialize message:', stateEvent); diff --git a/src/accessory/garage-pre-close-warning.ts b/src/accessory/garage-pre-close-warning.ts index 7922977..e94e652 100644 --- a/src/accessory/garage-pre-close-warning.ts +++ b/src/accessory/garage-pre-close-warning.ts @@ -1,32 +1,11 @@ -import { CharacteristicValue, Logger, PlatformAccessory, Service } from 'homebridge'; +import { CharacteristicValue, Service } from 'homebridge'; import fetch from 'node-fetch'; // I am, in fact, trying to make fetch happen. -import { BlaQHomebridgePluginPlatform } from '../platform.js'; import { BlaQButtonEvent, - BlaQTextSensorEvent, } from '../types.js'; import { LogMessageEvent, StateUpdateMessageEvent, StateUpdateRecord } from '../utils/eventsource.js'; -import { BaseBlaQAccessory } from './base.js'; - -const correctAPIBaseURL = (inputURL: string) => { - let correctedAPIBaseURL = inputURL; - if(!correctedAPIBaseURL.includes('://')){ - correctedAPIBaseURL = `http://${correctedAPIBaseURL}`; - } - if(correctedAPIBaseURL.endsWith('/')){ - correctedAPIBaseURL = correctedAPIBaseURL.slice(0, -1); - } - return correctedAPIBaseURL; -}; - -type BlaQGaragePreCloseWarningAccessoryConstructorParams = { - platform: BlaQHomebridgePluginPlatform; - accessory: PlatformAccessory; - model: string; - serialNumber: string; - apiBaseURL: string; -}; +import { BaseBlaQAccessory, BaseBlaQAccessoryConstructorParams } from './base.js'; export const label = 'Pre-close Warning'; @@ -35,72 +14,25 @@ export const label = 'Pre-close Warning'; * An instance of this class is created for each accessory your platform registers * Each accessory may expose multiple services of different service types. */ -export class BlaQGaragePreCloseWarningAccessory implements BaseBlaQAccessory { - private logger: Logger; - private accessoryInformationService: Service; +export class BlaQGaragePreCloseWarningAccessory extends BaseBlaQAccessory { private outletService: Service; - private apiBaseURL: string; - private firmwareVersion?: string; private isOn?: boolean; - private readonly platform: BlaQHomebridgePluginPlatform; - private readonly accessory: PlatformAccessory; - private readonly model: string; - private readonly serialNumber: string; - constructor({ - platform, - accessory, - model, - serialNumber, - apiBaseURL, - }: BlaQGaragePreCloseWarningAccessoryConstructorParams) { - this.platform = platform; - this.logger = this.platform.logger; + constructor(args: BaseBlaQAccessoryConstructorParams) { + super(args); this.logger.debug('Initializing BlaQGaragePreCloseWarningAccessory...'); - this.accessory = accessory; - this.model = model; - this.serialNumber = serialNumber; - this.apiBaseURL = correctAPIBaseURL(apiBaseURL); - this.outletService = this.accessory.getService(this.platform.service.Outlet) - || this.accessory.addService(this.platform.service.Outlet); - - this.accessoryInformationService = this.accessory.getService(this.platform.service.AccessoryInformation) - || this.accessory.addService(this.platform.service.AccessoryInformation); - - // set accessory information - this.accessoryInformationService - .setCharacteristic(this.platform.characteristic.Manufacturer, 'Konnected') - .setCharacteristic(this.platform.characteristic.Model, this.model) - .setCharacteristic(this.platform.characteristic.SerialNumber, this.serialNumber); + this.outletService = this.getOrAddService(this.platform.service.Outlet); // Set the service name. This is what is displayed as the name on the Home // app. We use what we stored in `accessory.context` in `discoverDevices`. - this.outletService.setCharacteristic(this.platform.characteristic.Name, accessory.context.device.displayName + ' ' + label); + this.outletService.setCharacteristic(this.platform.characteristic.Name, this.accessory.context.device.displayName + ' ' + label); this.outletService.getCharacteristic(this.platform.characteristic.On) .onGet(this.getIsOn.bind(this)) .onSet(this.changeIsOn.bind(this)); - - // Publish firmware version; this may not be initialized yet, so we set a getter. - // Note that this is against the AccessoryInformation service, not the GDO service. - this.accessoryInformationService - .getCharacteristic(this.platform.characteristic.FirmwareRevision) - .onGet(this.getFirmwareVersion.bind(this)); this.logger.debug('Initialized BlaQGaragePreCloseWarningAccessory!'); } - getFirmwareVersion(): CharacteristicValue { - return this.firmwareVersion || ''; - } - - private setFirmwareVersion(version: string) { - this.firmwareVersion = version; - this.accessoryInformationService.setCharacteristic( - this.platform.characteristic.FirmwareRevision, - version, - ); - } - getIsOn(): CharacteristicValue { return this.isOn || false; } @@ -119,11 +51,8 @@ export class BlaQGaragePreCloseWarningAccessory implements BaseBlaQAccessory { } } - setAPIBaseURL(url: string){ - this.apiBaseURL = correctAPIBaseURL(url); - } - handleStateEvent(stateEvent: StateUpdateMessageEvent){ + super.handleStateEvent(stateEvent); try { const stateInfo = JSON.parse(stateEvent.data) as StateUpdateRecord; if (['button-pre-close_warning'].includes(stateInfo.id)) { @@ -131,14 +60,6 @@ export class BlaQGaragePreCloseWarningAccessory implements BaseBlaQAccessory { if(['OFF', 'ON'].includes(buttonEvent.state?.toUpperCase() || '')){ this.setIsOn(buttonEvent.state?.toUpperCase() === 'ON'); } - } else if (['text_sensor-esphome_version', 'text_sensor-firmware_version'].includes(stateInfo.id)) { - const b = stateInfo as BlaQTextSensorEvent; - if (b.value === b.state && b.value !== '' && b.value !== null && b.value !== undefined) { - this.setFirmwareVersion(b.value); - } else { - this.logger.error('Mismatched firmware versions in value/state:', b.value, b.state); - this.firmwareVersion = undefined; - } } } catch(e) { this.logger.error('Cannot deserialize message:', stateEvent); diff --git a/src/hub.ts b/src/hub.ts index ae05fb8..4330ce0 100644 --- a/src/hub.ts +++ b/src/hub.ts @@ -1,7 +1,7 @@ import { AutoReconnectingEventSource, LogMessageEvent, PingMessageEvent, StateUpdateMessageEvent } from './utils/eventsource.js'; import { Logger, PlatformAccessory, PlatformConfig } from 'homebridge'; -import { ConfigDevice } from './types.js'; -import { BaseBlaQAccessory } from './accessory/base.js'; +import { BlaQTextSensorEvent, ConfigDevice } from './types.js'; +import { BaseBlaQAccessoryInterface } from './accessory/base.js'; import { BlaQHomebridgePluginPlatform } from './platform.js'; import { BlaQGarageDoorAccessory } from './accessory/garage-door.js'; import { BlaQGarageLightAccessory } from './accessory/garage-light.js'; @@ -10,6 +10,7 @@ import { BlaQGarageMotionSensorAccessory } from './accessory/garage-motion-senso import { BlaQGaragePreCloseWarningAccessory } from './accessory/garage-pre-close-warning.js'; import { BlaQGarageLearnModeAccessory } from './accessory/garage-learn-mode.js'; import { BlaQGarageObstructionSensorAccessory } from './accessory/garage-obstruction-sensor.js'; +import { formatMAC } from './utils/formatters.js'; interface BlaQPingEvent { title: string; @@ -19,12 +20,12 @@ interface BlaQPingEvent { lang: string; } -type ModelAndSerialNumber = { - model: string; +type FriendlyNameAndSerialNumber = { + friendlyName: string; serialNumber: string; }; -type InitAccessoryParams = ModelAndSerialNumber & { +type InitAccessoryParams = FriendlyNameAndSerialNumber & { platform: BlaQHomebridgePluginPlatform; accessory: PlatformAccessory; }; @@ -35,13 +36,15 @@ export type BlaQInitAccessoryCallback = (configDevice: ConfigDevice, Model: stri }; export class BlaQHub { + private accessories: BaseBlaQAccessoryInterface[] = []; + private eventSource?: AutoReconnectingEventSource; private host: string; + private initialized = false; + private friendlyName?: string; + private deviceMac?: string; private port: number; + private readonly initAccessoryCallback: BlaQInitAccessoryCallback; private readonly logger: Logger; - private eventSource?: AutoReconnectingEventSource; - private initAccessoryCallback: BlaQInitAccessoryCallback; - private initialized = false; - private accessories: BaseBlaQAccessory[] = []; constructor( private readonly pluginConfig: PlatformConfig, @@ -86,7 +89,32 @@ export class BlaQHub { } } + private possiblyFinalizeInit(){ + if(this.friendlyName && this.deviceMac){ + this.logger.info('[init] Publishing accessories with device model:', this.friendlyName); + this.initAccessories({ + friendlyName: this.friendlyName, + serialNumber: this.deviceMac, + }); + this.logger.debug('[init] Accessories initialized!'); + this.initialized = true; + } + } + private handleStateUpdate(msg: StateUpdateMessageEvent){ + if (!this.initialized && msg.data !== '' ) { + try { + const b = JSON.parse(msg.data) as BlaQTextSensorEvent; + if(['text_sensor-device_id'].includes(b.id)){ + this.deviceMac = formatMAC(b.value); + } + this.possiblyFinalizeInit(); + } catch (e) { + this.logger.debug('[init] Got event:', msg); + this.logger.debug('[init] Got event data:', msg.data); + this.logger.error('[init] Cannot parse BlaQTextSensorEvent', e); + } + } this.logger.debug('Processing state event:', msg.data); this.accessories.forEach(accessory => { if(accessory.handleStateEvent){ @@ -104,90 +132,81 @@ export class BlaQHub { }); } - private initGarageDoorAccessory({ platform, accessory, model, serialNumber}: InitAccessoryParams){ + private initGarageDoorAccessory({ platform, accessory, friendlyName, serialNumber}: InitAccessoryParams){ this.accessories.push(new BlaQGarageDoorAccessory({ - platform, accessory, model, serialNumber, apiBaseURL: this.getAPIBaseURL(), + platform, accessory, friendlyName, serialNumber, apiBaseURL: this.getAPIBaseURL(), })); } - private initGarageLightAccessory({ platform, accessory, model, serialNumber}: InitAccessoryParams){ + private initGarageLightAccessory({ platform, accessory, friendlyName, serialNumber}: InitAccessoryParams){ if(this.pluginConfig.enableLight ?? true) { this.accessories.push(new BlaQGarageLightAccessory({ - platform, accessory, model, serialNumber, apiBaseURL: this.getAPIBaseURL(), + platform, accessory, friendlyName, serialNumber, apiBaseURL: this.getAPIBaseURL(), })); } } - private initGarageLockAccessory({ platform, accessory, model, serialNumber}: InitAccessoryParams){ + private initGarageLockAccessory({ platform, accessory, friendlyName, serialNumber}: InitAccessoryParams){ if(this.pluginConfig.enableLockRemotes ?? true){ this.accessories.push(new BlaQGarageLockAccessory({ - platform, accessory, model, serialNumber, apiBaseURL: this.getAPIBaseURL(), + platform, accessory, friendlyName, serialNumber, apiBaseURL: this.getAPIBaseURL(), })); } } - private initGarageMotionSensorAccessory({ platform, accessory, model, serialNumber}: InitAccessoryParams){ + private initGarageMotionSensorAccessory({ platform, accessory, friendlyName, serialNumber}: InitAccessoryParams){ if(this.pluginConfig.enableMotionSensor ?? true){ this.accessories.push(new BlaQGarageMotionSensorAccessory({ - platform, accessory, model, serialNumber, apiBaseURL: this.getAPIBaseURL(), + platform, accessory, friendlyName, serialNumber, apiBaseURL: this.getAPIBaseURL(), })); } } - private initGaragePreCloseWarningAccessory({ platform, accessory, model, serialNumber}: InitAccessoryParams){ + private initGaragePreCloseWarningAccessory({ platform, accessory, friendlyName, serialNumber}: InitAccessoryParams){ if(this.pluginConfig.enablePreCloseWarning ?? true){ this.accessories.push(new BlaQGaragePreCloseWarningAccessory({ - platform, accessory, model, serialNumber, apiBaseURL: this.getAPIBaseURL(), + platform, accessory, friendlyName, serialNumber, apiBaseURL: this.getAPIBaseURL(), })); } } - private initGarageLearnModeAccessory({ platform, accessory, model, serialNumber}: InitAccessoryParams){ + private initGarageLearnModeAccessory({ platform, accessory, friendlyName, serialNumber}: InitAccessoryParams){ if(this.pluginConfig.enableLearnMode ?? true){ this.accessories.push(new BlaQGarageLearnModeAccessory({ - platform, accessory, model, serialNumber, apiBaseURL: this.getAPIBaseURL(), + platform, accessory, friendlyName, serialNumber, apiBaseURL: this.getAPIBaseURL(), })); } } - private initGarageObstructionSensorAccessory({ platform, accessory, model, serialNumber}: InitAccessoryParams){ + private initGarageObstructionSensorAccessory({ platform, accessory, friendlyName, serialNumber}: InitAccessoryParams){ if(this.pluginConfig.enableSeparateObstructionSensor ?? true){ this.accessories.push(new BlaQGarageObstructionSensorAccessory({ - platform, accessory, model, serialNumber, apiBaseURL: this.getAPIBaseURL(), + platform, accessory, friendlyName, serialNumber, apiBaseURL: this.getAPIBaseURL(), })); } } - private initAccessories({ model, serialNumber }: ModelAndSerialNumber){ + private initAccessories({ friendlyName, serialNumber }: FriendlyNameAndSerialNumber){ const {platform, accessory} = this.initAccessoryCallback( this.configDevice, - model, + friendlyName, serialNumber, ); - this.initGarageDoorAccessory({ platform, accessory, model, serialNumber }); - this.initGarageLightAccessory({ platform, accessory, model, serialNumber }); - this.initGarageLockAccessory({ platform, accessory, model, serialNumber }); - this.initGarageMotionSensorAccessory({ platform, accessory, model, serialNumber }); - this.initGaragePreCloseWarningAccessory({ platform, accessory, model, serialNumber }); - this.initGarageLearnModeAccessory({ platform, accessory, model, serialNumber }); - this.initGarageObstructionSensorAccessory({ platform, accessory, model, serialNumber }); + this.initGarageDoorAccessory({ platform, accessory, friendlyName, serialNumber }); + this.initGarageLightAccessory({ platform, accessory, friendlyName, serialNumber }); + this.initGarageLockAccessory({ platform, accessory, friendlyName, serialNumber }); + this.initGarageMotionSensorAccessory({ platform, accessory, friendlyName, serialNumber }); + this.initGaragePreCloseWarningAccessory({ platform, accessory, friendlyName, serialNumber }); + this.initGarageLearnModeAccessory({ platform, accessory, friendlyName, serialNumber }); + this.initGarageObstructionSensorAccessory({ platform, accessory, friendlyName, serialNumber }); } private handlePingUpdate(msg: PingMessageEvent){ if (!this.initialized && msg.data !== '' ) { try { const b = JSON.parse(msg.data) as BlaQPingEvent; - this.logger.info('[init] Publishing accessories with device model:', b.title); - // title example = GDO blaQ 6084d8 - const titleWithoutGDO = b.title.replace(/^GDO /, ''); - const model = titleWithoutGDO.split(' ').shift() || 'Unknown'; - const serialNumber = titleWithoutGDO.split(' ').pop() || 'Unknown'; - this.initAccessories({ - model, - serialNumber, - }); - this.logger.debug('[init] Accessories initialized!'); - this.initialized = true; + this.friendlyName = b.title; + this.possiblyFinalizeInit(); } catch (e) { this.logger.debug('[init] Got event:', msg); this.logger.debug('[init] Got event data:', msg.data); diff --git a/src/platform.ts b/src/platform.ts index 08b46bb..fe5cfcb 100644 --- a/src/platform.ts +++ b/src/platform.ts @@ -91,7 +91,7 @@ export class BlaQHomebridgePluginPlatform implements DynamicPlatformPlugin { host: service.addresses?.[0] || service.host, port: service.port, displayName: service.txt?.friendly_name, - mac: service.txt?.mac?.toUpperCase()?.replaceAll(/[^A-F0-9]/g, '')?.match(/.{2}/g)?.join(':'), + mac: formatMAC(service.txt?.mac), }; this.logger.debug(`Discovered device via mDNS: ${JSON.stringify(configEntry)}`); this.possiblyRegisterNewDevice(configEntry); diff --git a/src/utils/formatters.ts b/src/utils/formatters.ts new file mode 100644 index 0000000..fc2fc61 --- /dev/null +++ b/src/utils/formatters.ts @@ -0,0 +1,2 @@ +export const formatMAC = (str?: string) => + str?.toUpperCase()?.replaceAll(/[^A-F0-9]/g, '')?.match(/.{2}/g)?.join(':'); \ No newline at end of file