homebridge-keyble
Version:
HomeKit Support for Eqiva EQ3 Doorlocks
223 lines (192 loc) • 8.69 kB
text/typescript
import { Service, PlatformAccessory, CharacteristicValue, CharacteristicSetCallback, CharacteristicGetCallback } from 'homebridge';
import { KeyblePlatform } from './platform';
import { Key_Ble } from "keyble";
enum Position {
Lock = 0,
Unlock = 2,
Open = 3,
Unlock_MinStep = 1
}
/**
* 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 KeybleAccessory {
private service: Service;
private lock: Key_Ble;
/**
* These are just used to create a working example
* You should implement your own code to track the state of your accessory
*/
private state = {
currentPosition: Position.Lock,
movement: this.platform.Characteristic.PositionState.STOPPED,
targetPosition: Position.Lock
};
constructor(
private readonly platform: KeyblePlatform,
private readonly accessory: PlatformAccessory,
) {
this.lock = new Key_Ble({
address: accessory.context.config.address,
user_id: accessory.context.config.user,
user_key: accessory.context.config.key,
auto_disconnect_time: 15,
status_update_time: 600
})
// set accessory information
const accessoryService = this.accessory.getService(this.platform.Service.AccessoryInformation);
if (accessoryService) {
accessoryService.setCharacteristic(this.platform.Characteristic.Manufacturer, 'eqiva')
.setCharacteristic(this.platform.Characteristic.Model, 'eq3')
.setCharacteristic(this.platform.Characteristic.SerialNumber, 'Default-Serial');
}
// get the Door service if it exists, otherwise create a new Door service
// you can create multiple services for each accessory
this.service = this.accessory.getService(this.platform.Service.Door) || this.accessory.addService(this.platform.Service.Door);
// set the service name, this is what is displayed as the default name on the Home app
// in this example we are using the name we stored in the `accessory.context` in the `discoverDevices` method.
this.service.setCharacteristic(this.platform.Characteristic.Name, accessory.context.config.name);
// each service must implement at-minimum the "required characteristics" for the given service type
// see https://developers.homebridge.io/#/service/Door
this.service.getCharacteristic(this.platform.Characteristic.CurrentPosition)
.setProps({
minValue: Position.Lock,
minStep: Position.Unlock_MinStep,
maxValue: Position.Unlock
});
this.service.getCharacteristic(this.platform.Characteristic.TargetPosition)
.setProps({
minValue: Position.Lock,
minStep: Position.Unlock_MinStep,
maxValue: Position.Open
});
// register handlers for the CurrentPosition Characteristic
this.service.getCharacteristic(this.platform.Characteristic.CurrentPosition)
.on('get', this.getCurrentPosition.bind(this));
// register handlers for the State Characteristic
this.service.getCharacteristic(this.platform.Characteristic.PositionState)
.on('get', this.getState.bind(this));
// register handlers for the TargetPosition Characteristic
this.service.getCharacteristic(this.platform.Characteristic.TargetPosition)
.on('get', this.getTargetPosition.bind(this))
.on('set', this.setTargetPosition.bind(this));
// register handler for status changes from lock
this.lock
.on('status_change', this.handleStatusChange.bind(this));
this.lock.request_status();
}
/**
* Handle the "GET" requests from HomeKit
* These are sent when HomeKit wants to know the current state of the accessory, for example, checking if a Light bulb is on.
*
* GET requests should return as fast as possbile. A long delay here will result in
* HomeKit being unresponsive and a bad user experience in general.
*
* If your device takes time to respond you should update the status of your device
* asynchronously instead using the `updateCharacteristic` method instead.
* @example
* this.service.updateCharacteristic(this.platform.Characteristic.On, true)
*/
getCurrentPosition(callback: CharacteristicGetCallback): void {
const currentPosition = this.state.currentPosition;
this.platform.log.debug('Get Characteristic Current Position ->', currentPosition);
callback(null, currentPosition);
}
getState(callback: CharacteristicGetCallback): void {
const movement = this.state.movement;
this.platform.log.debug('Get Characteristic State ->', movement);
callback(null, movement);
}
getTargetPosition(callback: CharacteristicGetCallback): void {
const targetPosition = this.state.targetPosition;
this.platform.log.debug('Get Characteristic Target Position ->', targetPosition);
callback(null, targetPosition);
}
/**
* Handle "SET" requests from HomeKit
* These are sent when the user changes the state of an accessory, for example, changing the Brightness
*/
setTargetPosition(value: CharacteristicValue, callback: CharacteristicSetCallback): void {
if ( this.state.targetPosition != value ) {
this.state.targetPosition = value as number;
this.lock.ensure_connected();
this.platform.log.info('Set Characteristic Target Position -> ', value);
switch(value) {
case Position.Open:
this.platform.log.debug('Open')
this.state.movement = this.platform.Characteristic.PositionState.INCREASING;
this.lock.open()
.then(() => {this.updateCurrentPosition(value);})
.catch((error) => {this.handleLockError(error);});
break;
case Position.Unlock:
case Position.Unlock_MinStep:
this.platform.log.debug('Unlock')
this.state.movement = this.platform.Characteristic.PositionState.INCREASING;
this.lock.unlock()
.then(() => {this.updateCurrentPosition(value);})
.catch((error) => {this.handleLockError(error);});
break;
case Position.Lock:
this.platform.log.debug('Lock')
this.state.movement = this.platform.Characteristic.PositionState.DECREASING;
this.lock.lock()
.then(() => {this.updateCurrentPosition(value);})
.catch((error) => {this.handleLockError(error);});
}
}
callback(null);
}
/* Meant to be called with then after operating the lock
* This probably can be replaced by handleStatusChange
*/
updateCurrentPosition(position: Position): void {
this.platform.log.debug('updateCurrentPosition -> ', position)
this.service.getCharacteristic(this.platform.Characteristic.CurrentPosition)
.setValue(Math.min(position, Position.Unlock));
this.service.getCharacteristic(this.platform.Characteristic.PositionState)
.setValue(this.platform.Characteristic.PositionState.STOPPED);
}
handleLockError(error: string): void {
this.lock.request_status();
this.platform.log.error(error);
}
handleStatusChange(newStatus : {lock_status_id: number, lock_status: string}): void {
this.platform.log.debug('Status update: ', newStatus.lock_status);
switch(newStatus.lock_status_id) {
case 3: // LOCKED
this.state.targetPosition = Position.Lock;
this.state.currentPosition = Position.Lock;
this.state.movement = this.platform.Characteristic.PositionState.STOPPED;
break;
case 2: // UNLOCKED
case 4: // OPENED
this.state.targetPosition = Position.Unlock;
this.state.currentPosition = Position.Unlock;
this.state.movement = this.platform.Characteristic.PositionState.STOPPED;
break;
case 1: // MOVING
if (this.state.targetPosition > Position.Lock) {
this.state.movement = this.platform.Characteristic.PositionState.INCREASING;
} else {
this.state.movement = this.platform.Characteristic.PositionState.DECREASING;
}
break;
case 0: // UNKNOWN
this.platform.log.info("Lock reports state unknown");
break;
default:
this.platform.log.error("Received unkown state: ", newStatus);
break;
}
// Update homebridge values
this.service.getCharacteristic(this.platform.Characteristic.CurrentPosition)
.updateValue(this.state.currentPosition);
this.service.getCharacteristic(this.platform.Characteristic.TargetPosition)
.updateValue(this.state.targetPosition);
this.service.getCharacteristic(this.platform.Characteristic.PositionState)
.updateValue(this.state.movement);
}
}