Skip to content

Commit

Permalink
Added pre-close-warning
Browse files Browse the repository at this point in the history
  • Loading branch information
KyleBoyer committed Jul 13, 2024
1 parent 94f70f0 commit 79373e0
Show file tree
Hide file tree
Showing 5 changed files with 199 additions and 5 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ This plugin enables the use of a GDO BlaQ device with Homebridge (and derivative
* Garage Light Status/Control
* Garage Remote Lock Status/Control
* Firmware Version Status
* Motion Sensor Status
* Play Pre-close Warning
* Obstruction Sensor Status (coming soon on v0.2.X)
* Motion Sensor Status (coming soon on v0.2.X)
* Play Pre-close Warning (coming soon on v0.2.X)

It *could*, but does not currently, support:

Expand Down
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.3",
"version": "0.2.4",
"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
178 changes: 178 additions & 0 deletions src/accessory/garage-pre-close-warning.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
import { CharacteristicValue, Logger, PlatformAccessory, 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;
};

/**
* 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 BlaQGaragePreCloseWarningAccessory implements BaseBlaQAccessory {
private logger: Logger;
private accessoryInformationService: Service;
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;
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);

// 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);

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;
}

setIsOn(isOn: boolean) {
this.isOn = isOn;
this.outletService.setCharacteristic(
this.platform.characteristic.On,
this.isOn,
);
}

private async changeIsOn(target: CharacteristicValue){
if(target && target !== this.isOn){ // only call the API when target = true (button on)
await fetch(`${this.apiBaseURL}/button/pre_close_warning/press`, {method: 'POST'});
}
}

setAPIBaseURL(url: string){
this.apiBaseURL = correctAPIBaseURL(url);
}

handleStateEvent(stateEvent: StateUpdateMessageEvent){
this.logger.debug('Processing state event:', stateEvent.data);
try {
const stateInfo = JSON.parse(stateEvent.data) as StateUpdateRecord;
if (['button-pre-close_warning'].includes(stateInfo.id)) {
const buttonEvent = stateInfo as BlaQButtonEvent & { state: 'ON' | 'OFF' };
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.logger.info('Firmware version:', b.value);
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);
}
}

handleLogEvent(logEvent: LogMessageEvent){
this.logger.debug('BlaQ log:', logEvent.data);
try {
const logStr = logEvent.data;
const lowercaseLogStr = logStr.toLowerCase();
const preCloseWarningPressed =
lowercaseLogStr.includes('pre') &&
lowercaseLogStr.includes('close') &&
lowercaseLogStr.includes('warning') &&
lowercaseLogStr.includes('pressed');
const playSoundPressed =
lowercaseLogStr.includes('play') &&
lowercaseLogStr.includes('sound') &&
lowercaseLogStr.includes('pressed');
const playingSong =
lowercaseLogStr.includes('playing') &&
lowercaseLogStr.includes('song');
const playbackFinished =
lowercaseLogStr.includes('playback') &&
lowercaseLogStr.includes('finished');
if (preCloseWarningPressed || playSoundPressed || playingSong) {
this.setIsOn(true);
} else if (playbackFinished) {
this.setIsOn(false);
}
} catch(e) {
this.logger.error('Log parsing error:', e);
}
}
}
16 changes: 16 additions & 0 deletions src/hub.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { BlaQGarageDoorAccessory } from './accessory/garage-door.js';
import { BlaQGarageLightAccessory } from './accessory/garage-light.js';
import { BlaQGarageLockAccessory } from './accessory/garage-lock.js';
import { BlaQGarageMotionSensorAccessory } from './accessory/garage-motion-sensor.js';
import { BlaQGaragePreCloseWarningAccessory } from './accessory/garage-pre-close-warning.js';

interface BlaQPingEvent {
title: string;
Expand Down Expand Up @@ -142,11 +143,26 @@ export class BlaQHub {
}));
}

private initGaragePreCloseWarningAccessory({ model, serialNumber }: ModelAndSerialNumber){
const accessorySuffix = 'pre-close-warning';
const nonMainAccessoryMACAddress = this.configDevice.mac && `${this.configDevice.mac}-${accessorySuffix}`;
const nonMainAccessorySerialNumber = `${serialNumber}-${accessorySuffix}`;
const {platform, accessory} = this.initAccessoryCallback(
{ ...this.configDevice, mac: nonMainAccessoryMACAddress },
model,
nonMainAccessorySerialNumber,
);
this.accessories.push(new BlaQGaragePreCloseWarningAccessory({
platform, accessory, model, serialNumber, apiBaseURL: this.getAPIBaseURL(),
}));
}

private initAccessories({ model, serialNumber }: ModelAndSerialNumber){
this.initGarageDoorAccessory({ model, serialNumber });
this.initGarageLightAccessory({ model, serialNumber });
this.initGarageLockAccessory({ model, serialNumber });
this.initGarageMotionSensorAccessory({ model, serialNumber });
this.initGaragePreCloseWarningAccessory({ model, serialNumber });
}

private handlePingUpdate(msg: PingMessageEvent){
Expand Down

0 comments on commit 79373e0

Please sign in to comment.