Skip to content

Commit

Permalink
Merge pull request #15 from justjam2013/doorbell-switch
Browse files Browse the repository at this point in the history
Add companion switch to trigger doorbell
  • Loading branch information
justjam2013 authored Nov 16, 2024
2 parents c530811 + 5edebf3 commit 7c23de8
Show file tree
Hide file tree
Showing 22 changed files with 337 additions and 139 deletions.
1 change: 0 additions & 1 deletion config.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,6 @@
"description": "Doorbell volume *",
"type": "integer",
"minimum": 0,
"maximum": 100,
"condition": {
"functionBody": "return model.devices[arrayIndices].accessoryType === 'doorbell';"
}
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 @@
"name": "homebridge-virtual-accessories",
"displayName": "Virtual Accessories for Homebridge",
"type": "module",
"version": "1.0.0-beta.8",
"version": "1.0.0-beta.9",
"description": "Virtual accessories for Homebridge.",
"author": "justjam2013",
"license": "Apache-2.0",
Expand Down
73 changes: 64 additions & 9 deletions src/accessories/virtualAccessoryDoorbell.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,20 @@ import type { CharacteristicValue, PlatformAccessory } from 'homebridge';

import { VirtualAccessoryPlatform } from '../platform.js';
import { Accessory } from './virtualAccessory.js';
import { AccessoryFactory } from '../accessoryFactory.js';
import { Switch } from './virtualAccessorySwitch.js';
import { AccessoryNotAllowedError } from '../errors.js';

/**
* Doorbell - Accessory implementation
*/
export class Doorbell extends Accessory {

private SINGLE_PRESS: number = this.platform.Characteristic.ProgrammableSwitchEvent.SINGLE_PRESS; // 0
private DOUBLE_PRESS: number = this.platform.Characteristic.ProgrammableSwitchEvent.DOUBLE_PRESS; // 1
private LONG_PRESS: number = this.platform.Characteristic.ProgrammableSwitchEvent.LONG_PRESS; // 2
static readonly SINGLE_PRESS: number = 0; // Characteristic.ProgrammableSwitchEvent.SINGLE_PRESS
static readonly DOUBLE_PRESS: number = 1; // Characteristic.ProgrammableSwitchEvent.DOUBLE_PRESS
static readonly LONG_PRESS: number = 2; // Characteristic.ProgrammableSwitchEvent.LONG_PRESS;

private companionSensorResetTimerId: ReturnType<typeof setTimeout> | undefined;

/**
* These are just used to create a working example
Expand All @@ -20,6 +25,8 @@ export class Doorbell extends Accessory {
Volume: 100,
};

protected companionSwitch?: Switch;

constructor(
platform: VirtualAccessoryPlatform,
accessory: PlatformAccessory,
Expand Down Expand Up @@ -66,6 +73,13 @@ export class Doorbell extends Accessory {
* can use the same subtype id.)
*/

// Create switch service
this.companionSwitch = AccessoryFactory.createVirtualCompanionSwitch(
this.platform, this.accessory, this.accessoryConfiguration.accessoryName + ' Switch');

// Overwrite the "onSet" handler to trigger doorbell
this.companionSwitch!.service!.getCharacteristic(this.platform.Characteristic.On)
.onSet(this.setOn.bind(this)); // SET - bind to the `setOn` method below
}

/**
Expand All @@ -74,9 +88,9 @@ export class Doorbell extends Accessory {
*/
async handleProgrammableSwitchEventGet(): Promise<CharacteristicValue> {
// implement your own code to check if the device is on
const pressEvent = this.SINGLE_PRESS;
const pressEvent = Doorbell.SINGLE_PRESS;

this.platform.log.debug(`[${this.accessoryConfiguration.accessoryName}] Getting Programmable Switch Event: ${this.getEventName(pressEvent)}`);
this.platform.log.debug(`[${this.accessoryConfiguration.accessoryName}] Getting Programmable Switch Event: ${Doorbell.getEventName(pressEvent)}`);

// if you need to return an error to show the device as "Not Responding" in the Home app:
// throw new this.platform.api.hap.HapStatusError(this.platform.api.hap.HAPStatus.SERVICE_COMMUNICATION_FAILURE);
Expand Down Expand Up @@ -118,14 +132,55 @@ export class Doorbell extends Accessory {
return volume;
}

private getEventName(event: number): string {
/**
* Handle "SET" requests from HomeKit
* These are sent when the user changes the state of an accessory, for example, turning on a Light bulb.
*/
async setOn(value: CharacteristicValue) {
// implement your own code to turn your device on/off
const newState = value as boolean;
this.companionSwitch!.setCompanionSwitchState(newState);

if (newState === Switch.ON) {
// this.service!.getCharacteristic(this.platform.Characteristic.ProgrammableSwitchEvent).updateValue(this.state);
this.triggerDoorbellEvent(Doorbell.SINGLE_PRESS, this.companionSwitch!);

if (this.companionSensorResetTimerId) {
this.platform.log.debug(`[${this.accessoryConfiguration.accessoryName}] Clearing reset timer: ${this.companionSensorResetTimerId}`);
clearTimeout(this.companionSensorResetTimerId);
}

// Reset switch after timer delay
this.companionSensorResetTimerId = setTimeout(() => {
this.companionSwitch!.service!.setCharacteristic(this.platform.Characteristic.On, Switch.OFF);
}, 1000);
this.platform.log.debug(`[${this.accessoryConfiguration.accessoryName}] Set new reset timer: ${this.companionSensorResetTimerId}`);
}

this.platform.log.info(`[${this.accessoryConfiguration.accessoryName}] Setting Companion Switch Current State: ${Switch.getStateName(newState)}`);
}

/**
* This method is called by the switch to ring the doorbell
*/
private async triggerDoorbellEvent(event: number, accessory: Accessory) {
if (!(accessory.accessoryConfiguration.accessoryID === this.accessoryConfiguration.accessoryID)) {
throw new AccessoryNotAllowedError(`Switch ${accessory.accessoryConfiguration.accessoryName} is not allowed to trigger this sensor`);
}

this.service!.updateCharacteristic(this.platform.Characteristic.ProgrammableSwitchEvent, (event));

this.platform.log.info(`[${this.accessoryConfiguration.accessoryName}] Triggered doorbell event: ${Doorbell.getEventName(event)}`);
}

static getEventName(event: number): string {
let eventName: string;

switch (event) {
case undefined: { eventName = 'undefined'; break; }
case this.SINGLE_PRESS: { eventName = 'SINGLE_PRESS'; break; }
case this.DOUBLE_PRESS: { eventName = 'DOUBLE_PRESS'; break; }
case this.LONG_PRESS: { eventName = 'LONG_PRESS'; break; }
case Doorbell.SINGLE_PRESS: { eventName = 'SINGLE_PRESS'; break; }
case Doorbell.DOUBLE_PRESS: { eventName = 'DOUBLE_PRESS'; break; }
case Doorbell.LONG_PRESS: { eventName = 'LONG_PRESS'; break; }
default: { eventName = event.toString(); }
}

Expand Down
24 changes: 12 additions & 12 deletions src/accessories/virtualAccessoryGarageDoor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,18 @@ import { Accessory } from './virtualAccessory.js';
*/
export class GarageDoor extends Accessory {

private OPEN: number = this.platform.Characteristic.CurrentDoorState.OPEN; // 0
private CLOSED: number = this.platform.Characteristic.CurrentDoorState.CLOSED; // 1
private OPENING: number = this.platform.Characteristic.CurrentDoorState.OPENING; // 2
private CLOSING: number = this.platform.Characteristic.CurrentDoorState.CLOSING; // 3
private STOPPED: number = this.platform.Characteristic.CurrentDoorState.STOPPED; // 4
static readonly OPEN: number = 0; // Characteristic.CurrentDoorState.OPEN;
static readonly CLOSED: number = 1; // Characteristic.CurrentDoorState.CLOSED;
static readonly OPENING: number = 2; // Characteristic.CurrentDoorState.OPENING;
static readonly CLOSING: number = 3; // Characteristic.CurrentDoorState.CLOSING;
static readonly STOPPED: number = 4; // Characteristic.CurrentDoorState.STOPPED;

/**
* These are just used to create a working example
* You should implement your own code to track the state of your accessory
*/
private states = {
GarageDoorState: this.CLOSED,
GarageDoorState: GarageDoor.CLOSED,
ObstructionDetected: false,
};

Expand All @@ -32,7 +32,7 @@ export class GarageDoor extends Accessory {
super(platform, accessory);

// First configure the device based on the accessory details
this.defaultState = this.accessoryConfiguration.garageDoorDefaultState === 'open' ? this.OPEN : this.CLOSED;
this.defaultState = this.accessoryConfiguration.garageDoorDefaultState === 'open' ? GarageDoor.OPEN : GarageDoor.CLOSED;

// If the accessory is stateful retrieve stored state, otherwise set to default state
if (this.accessoryConfiguration.accessoryIsStateful) {
Expand Down Expand Up @@ -172,11 +172,11 @@ export class GarageDoor extends Accessory {

switch (state) {
case undefined: { stateName = 'undefined'; break; }
case this.OPEN: { stateName = 'OPEN'; break; }
case this.CLOSED: { stateName = 'CLOSED'; break; }
case this.OPENING: { stateName = 'OPENING'; break; }
case this.CLOSING: { stateName = 'CLOSING'; break; }
case this.STOPPED: { stateName = 'STOPPED'; break; }
case GarageDoor.OPEN: { stateName = 'OPEN'; break; }
case GarageDoor.CLOSED: { stateName = 'CLOSED'; break; }
case GarageDoor.OPENING: { stateName = 'OPENING'; break; }
case GarageDoor.CLOSING: { stateName = 'CLOSING'; break; }
case GarageDoor.STOPPED: { stateName = 'STOPPED'; break; }
default: { stateName = state.toString(); }
}

Expand Down
20 changes: 10 additions & 10 deletions src/accessories/virtualAccessoryLock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,17 @@ import { Accessory } from './virtualAccessory.js';
*/
export class Lock extends Accessory {

private UNSECURED: number = this.platform.Characteristic.LockCurrentState.UNSECURED; // 0
private SECURED: number = this.platform.Characteristic.LockCurrentState.SECURED; // 1
private JAMMED: number = this.platform.Characteristic.LockCurrentState.JAMMED; // 2
private UNKNOWN: number = this.platform.Characteristic.LockCurrentState.UNKNOWN; // 3
static readonly UNSECURED: number = 0; // Characteristic.LockCurrentState.UNSECURED;
static readonly SECURED: number = 0; // Characteristic.LockCurrentState.SECURED;
static readonly JAMMED: number = 0; // Characteristic.LockCurrentState.JAMMED;
static readonly UNKNOWN: number = 0; // Characteristic.LockCurrentState.UNKNOWN;

/**
* These are just used to create a working example
* You should implement your own code to track the state of your accessory
*/
private states = {
LockState: this.UNSECURED,
LockState: Lock.UNSECURED,
};

private readonly stateStorageKey: string = 'LockState';
Expand All @@ -30,7 +30,7 @@ export class Lock extends Accessory {
super(platform, accessory);

// First configure the device based on the accessory details
this.defaultState = this.accessoryConfiguration.lockDefaultState === 'unlocked' ? this.UNSECURED : this.SECURED;
this.defaultState = this.accessoryConfiguration.lockDefaultState === 'unlocked' ? Lock.UNSECURED : Lock.SECURED;

// If the accessory is stateful retrieve stored state, otherwise set to default state
if (this.accessoryConfiguration.accessoryIsStateful) {
Expand Down Expand Up @@ -185,10 +185,10 @@ export class Lock extends Accessory {

switch (state) {
case undefined: { stateName = 'undefined'; break; }
case this.UNSECURED: { stateName = 'UNSECURED'; break; }
case this.SECURED: { stateName = 'SECURED'; break; }
case this.JAMMED: { stateName = 'JAMMED'; break; }
case this.UNKNOWN: { stateName = 'UNKNOWN'; break; }
case Lock.UNSECURED: { stateName = 'UNSECURED'; break; }
case Lock.SECURED: { stateName = 'SECURED'; break; }
case Lock.JAMMED: { stateName = 'JAMMED'; break; }
case Lock.UNKNOWN: { stateName = 'UNKNOWN'; break; }
default: { stateName = state.toString(); }
}

Expand Down
Loading

0 comments on commit 7c23de8

Please sign in to comment.