Skip to content

Commit

Permalink
Implement support for Bold Connect Controller
Browse files Browse the repository at this point in the history
  • Loading branch information
StefanNienhuis committed Aug 23, 2023
1 parent fdee2eb commit 08dd4c9
Show file tree
Hide file tree
Showing 12 changed files with 6,810 additions and 809 deletions.
5 changes: 5 additions & 0 deletions plugin/config.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@
"title": "Use legacy authentication",
"type": "boolean",
"description": "Switch between default and legacy authentication. This settings will impact token refreshing."
},
"showControllerAsLock": {
"title": "Show Controller as lock",
"type": "boolean",
"description": "When using the Bold Connect's built-in relay (called 'Controller' in the Bold app), it will show as a switch in HomeKit. This option will make it show as a lock instead."
}
}
}
Expand Down
7,250 changes: 6,553 additions & 697 deletions plugin/package-lock.json

Large diffs are not rendered by default.

5 changes: 3 additions & 2 deletions plugin/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "homebridge-bold",
"displayName": "Homebridge Bold",
"version": "2.1.5",
"version": "2.1.6",
"description": "HomeKit support for the Bold Smart Locks.",
"main": "build/index.js",
"engines": {
Expand Down Expand Up @@ -32,7 +32,8 @@
"@typescript-eslint/eslint-plugin": "^4.32.0",
"@vitejs/plugin-react": "^1.3.2",
"eslint": "^7.32.0",
"homebridge": "^1.3.4",
"homebridge": "^1.6.0",
"homebridge-config-ui-x": "^4.50.5",
"react": "^18.1.0",
"react-dom": "^18.1.0",
"react-qr-code": "^2.0.7",
Expand Down
39 changes: 39 additions & 0 deletions plugin/src/accessories/base-accessory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import {Device} from '../device';
import {BoldPlatform} from '../index';
import {PlatformAccessory} from 'homebridge';
import {DeviceConfig} from '../types';

export abstract class BaseAccessory extends Device {

protected constructor(
protected platform: BoldPlatform,
protected accessory: PlatformAccessory,
protected deviceConfig: DeviceConfig
) {
super(deviceConfig, platform.bold);

this.platform.log.info(`Configuring accessory for device '${deviceConfig.name}'`);

let informationService = this.accessory.getService(this.platform.hap.Service.AccessoryInformation);

if (!informationService) {
informationService = this.accessory.addService(this.platform.hap.Service.AccessoryInformation);
}

informationService.getCharacteristic(this.platform.hap.Characteristic.Name)
.onGet(() => deviceConfig.name || 'Bold Device');

informationService.getCharacteristic(this.platform.hap.Characteristic.Manufacturer)
.onGet(() => deviceConfig.model.make || 'Bold');

informationService.getCharacteristic(this.platform.hap.Characteristic.Model)
.onGet(() => deviceConfig.model.model || 'Unknown');

informationService.getCharacteristic(this.platform.hap.Characteristic.SerialNumber)
.onGet(() => deviceConfig.serial || 'Unknown');

informationService.getCharacteristic(this.platform.hap.Characteristic.FirmwareRevision)
.onGet(() => `${deviceConfig.actualFirmwareVersion || 'Unknown'}`);
}

}
45 changes: 45 additions & 0 deletions plugin/src/accessories/lock-accessory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { BoldPlatform } from '../';
import { PlatformAccessory } from 'homebridge';
import { DeviceConfig } from '../types';
import { BaseAccessory } from './base-accessory';

enum LockState {
Unlocked = 0,
Locked = 1
}

export class LockAccessory extends BaseAccessory {

constructor(
protected platform: BoldPlatform,
protected accessory: PlatformAccessory,
protected deviceConfig: DeviceConfig
) {
super(platform, accessory, deviceConfig);

let lockService = accessory.getService(this.platform.hap.Service.LockMechanism);

if (!lockService) {
lockService = accessory.addService(this.platform.hap.Service.LockMechanism);
}

let currentState = lockService.getCharacteristic(this.platform.hap.Characteristic.LockCurrentState);
let targetState = lockService.getCharacteristic(this.platform.hap.Characteristic.LockTargetState);

currentState.onGet(() => this.isActivated ? LockState.Unlocked : LockState.Locked);

targetState.onGet(() => this.isActivated ? LockState.Unlocked : LockState.Locked)
.onSet((newState) => this.setState(newState == LockState.Unlocked));
}

onStateChange(activated: boolean): void {
let service = this.accessory.getService(this.platform.hap.Service.LockMechanism);

service?.getCharacteristic(this.platform.hap.Characteristic.LockCurrentState)
.updateValue(activated ? LockState.Unlocked : LockState.Locked);

service?.getCharacteristic(this.platform.hap.Characteristic.LockTargetState)
.updateValue(activated ? LockState.Unlocked : LockState.Locked);
}

}
34 changes: 34 additions & 0 deletions plugin/src/accessories/switch-accessory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { BoldPlatform } from '../';
import { DeviceConfig } from '../types';
import { BaseAccessory } from './base-accessory';
import { PlatformAccessory } from 'homebridge';

export class SwitchAccessory extends BaseAccessory {

constructor(
protected platform: BoldPlatform,
protected accessory: PlatformAccessory,
protected deviceConfig: DeviceConfig
) {
super(platform, accessory, deviceConfig);

let switchService = accessory.getService(this.platform.hap.Service.Switch);

if (!switchService) {
switchService = accessory.addService(this.platform.hap.Service.Switch);
}

let on = switchService.getCharacteristic(this.platform.hap.Characteristic.On);

on.onGet(() => this.isActivated)
.onSet((newState) => this.setState(newState == true));
}

onStateChange(activated: boolean): void {
let service = this.accessory.getService(this.platform.hap.Service.Switch);

service?.getCharacteristic(this.platform.hap.Characteristic.On)
.updateValue(activated);
}

}
8 changes: 4 additions & 4 deletions plugin/src/bold.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import axios, { AxiosError, Method } from 'axios';
import FormData from 'form-data';
import { Logger } from 'homebridge';
import { REFRESH_URL, LEGACY_CLIENT_ID, LEGACY_CLIENT_SECRET } from './const';
import { Config, Device } from './types';
import { Config, DeviceConfig } from './types';

interface APISuccess<Data> {
success: true;
Expand Down Expand Up @@ -75,7 +75,7 @@ export class BoldAPI {
}
}

async getDevices(): Promise<Device[]> {
async getDevices(): Promise<DeviceConfig[]> {
this.log.debug('Getting all devices');

let response = await this.request('GET', '/v1/effective-device-permissions');
Expand All @@ -85,8 +85,8 @@ export class BoldAPI {
}

if (Array.isArray(response.data)) {
let devices = response.data as Device[];
let supportedDevices = devices.filter((device) => device.id != null && device.name && device.type.id == 1 && device.gateway != null);
let devices = response.data as DeviceConfig[];
let supportedDevices = devices.filter((device) => device.id != null && device.name && device.featureSet.isActivatable && device.gateway != null);

this.log.debug(`Total device count: ${devices.length}, Supported device count: ${supportedDevices.length}`);

Expand Down
61 changes: 61 additions & 0 deletions plugin/src/device.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { BoldAPI } from './bold';
import {DeviceConfig} from './types';

/// Class representing an activatable Bold device.
/// This class is used by each accessory to interact with the Bold API.
export abstract class Device {

// Getters

/// Whether the device is currently activated
get isActivated(): boolean {
return this.activatedUntil > new Date();
}

// Private properties

private activatedUntil: Date;

constructor(
private readonly config: DeviceConfig,
private readonly bold: BoldAPI
) {
// Initialize with current Date, making it not activated.
this.activatedUntil = new Date();
}

// Methods

async setState(activate: boolean): Promise<void> {
if (activate) {
if (await this.bold.activate(this.config.id)) {
// Store activation end date
let date = new Date();

date.setSeconds(date.getSeconds() + this.config.settings.activationTime);

this.activatedUntil = date;

// Call state change handler
this.onStateChange(true);

// Deactivate after activation time
setTimeout(() => {
this.setState(false);
}, this.config.settings.activationTime * 1000);
} else {
// If failed to activate, set device state to deactivated.
await this.setState(false);
}
} else {
// Set activation end date to now
this.activatedUntil = new Date();

// Call state change handler
this.onStateChange(false);
}
}

abstract onStateChange(activated: boolean): void;

}
Loading

0 comments on commit 08dd4c9

Please sign in to comment.