Skip to content

Commit

Permalink
Code deduplication; fixed accessory manufacturer/model/serial/name info
Browse files Browse the repository at this point in the history
  • Loading branch information
KyleBoyer committed Jul 23, 2024
1 parent e02b298 commit 286065c
Show file tree
Hide file tree
Showing 13 changed files with 229 additions and 654 deletions.
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
103 changes: 101 additions & 2 deletions src/accessory/base.ts
Original file line number Diff line number Diff line change
@@ -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<typeof Service> | Service): Service{
return this.accessory.getService(service as WithUUID<typeof Service>) ||
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);
}
}
96 changes: 9 additions & 87 deletions src/accessory/garage-door.ts
Original file line number Diff line number Diff line change
@@ -1,98 +1,48 @@
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,
LockStateType,
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));
Expand All @@ -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';
Expand Down Expand Up @@ -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)) {
Expand All @@ -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;
Expand Down
Loading

0 comments on commit 286065c

Please sign in to comment.