homebridge-bond
Version:
A homebridge plugin to control your Bond devices over the v2 API.
259 lines (258 loc) • 10.2 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.BondPlatform = void 0;
const Bond_1 = require("./interface/Bond");
const platformAccessory_1 = require("./platformAccessory");
const config_1 = require("./interface/config");
const Device_1 = require("./interface/Device");
const settings_1 = require("./settings");
const dgram_1 = __importDefault(require("dgram"));
class BondPlatform {
constructor(log, config, api) {
this.log = log;
this.config = config;
this.api = api;
this.Service = this.api.hap.Service;
this.Characteristic = this.api.hap.Characteristic;
this.UUIDGen = this.api.hap.uuid;
this.accessories = [];
if (config === null) {
this.log.error('No config defined.');
return;
}
if (!config_1.BondPlatformConfig.isValid(this)) {
this.log.error(`Config: ${JSON.stringify(config, null, 2)}`);
return;
}
this.log.debug(`Config: ${JSON.stringify(config, null, 2)}`);
api.on('didFinishLaunching', () => {
// Delaying the initialization of bonds property because we need to
// get the device ids before doing anything
this.validateBonds();
});
}
// Validate that all of the bonds provided in the config are online and authenticaiton is working
validateBonds() {
const bonds = Bond_1.Bond.objects(this);
const validated = [];
Bond_1.Bond.validate(bonds).then(res => {
res.forEach(res => {
if (res !== undefined) {
validated.push(res);
}
});
this.setupBonds(validated);
});
}
setupBonds(bonds) {
if (bonds.length === 0) {
this.log.warn('No valid Bonds available.');
return;
}
// const bonds = Bond.objects(this);
Bond_1.Bond.updateDeviceIds(bonds).then(() => {
this.bonds = bonds;
this.log(`${this.accessories.length} cached accessories were loaded`);
this.bonds.forEach(bond => {
this.getDevices(bond);
this.setupBPUP(bond);
});
});
}
getDevices(bond) {
this.cleanupBondData(bond);
this.log(`Getting devices for this Bond (${bond.version.bondid})...`);
this.log(`${bond.deviceIds.length} devices were found on this Bond (${bond.version.bondid}).`);
const filtered = bond.deviceIds.filter(deviceId => {
const accessories = this.accessories.filter(acc => {
return acc.context.device.uniqueId === bond.uniqueDeviceId(deviceId);
});
return accessories.length === 0;
});
if (filtered.length === 0) {
this.log(`No new devices to add for this Bond (${bond.version.bondid}).`);
return;
}
this.log(`Attempting to add ${filtered.length} devices that were not previously added.`);
bond.api
.getDevices(filtered)
.then(devices => {
devices.forEach(device => {
// Set the unique id
device.uniqueId = bond.uniqueDeviceId(device.id);
// Set the bond id
device.bondId = bond.version.bondid;
});
this.addAccessories(devices);
})
.catch(error => {
this.log(`Error getting devices: ${error}`);
});
}
cleanupBondData(bond) {
// Data cleanup - Make sure all cached devices have uniqueId and bondId on them
bond.deviceIds.forEach(deviceId => {
this.accessories.forEach(accessory => {
// Only run if device does not have uniqueId
if (accessory.context.device.uniqueId === undefined
&& accessory.context.device.id === deviceId) {
const uniqueId = bond.uniqueDeviceId(deviceId);
this.log.debug(`Updating device data with uniqueId ${uniqueId}`);
accessory.context.device.uniqueId = bond.uniqueDeviceId(deviceId);
accessory.context.device.bondId = bond.version.bondid;
}
});
});
}
addAccessories(devices) {
devices.forEach(device => {
this.addAccessory(device);
});
}
// Accessory
/**
* Add a new accessory that hasn't been added before.
*/
addAccessory(device) {
const bond = this.bondForDevice(device);
// Make sure Bond exists
if (bond === undefined) {
this.log(`[${device.name}] Bond does not exist for device id: ${device.id}.`);
return;
}
// Make sure device shouldn't be excluded
if ((bond.config.hide_device_ids !== undefined
&& bond.config.hide_device_ids.includes(device.id))) {
this.log(`[${device.name}] Excluding ${device.id}.`);
return;
}
// Make sure device has supported actions
if (!Device_1.Device.isSupported(device)) {
this.log(`[${device.name}] Device has no supported actions.`);
return;
}
const uuid = this.UUIDGen.generate(device.uniqueId);
if (this.accessoryAdded(uuid)) {
this.log(`[${device.name}] Accessory already added.`);
return;
}
const displayName = Device_1.Device.displayName(device);
const accessory = new this.api.platformAccessory(`${displayName}`, uuid);
accessory.context.device = device;
this.create(accessory);
this.api.registerPlatformAccessories(settings_1.PLUGIN_NAME, settings_1.PLATFORM_NAME, [accessory]);
this.accessories.push(accessory);
this.log(`Adding Accessory ${accessory.displayName}`);
this.log.debug(`Device unique id: ${device.uniqueId}`);
}
removeAccessory(accessory) {
this.log(`Removing Accessory: ${accessory.displayName}`);
const index = this.accessories.indexOf(accessory);
if (index > -1) {
this.accessories.splice(index, 1);
}
this.api.unregisterPlatformAccessories(settings_1.PLUGIN_NAME, settings_1.PLATFORM_NAME, [accessory]);
}
configureAccessory(accessory) {
if (this.config === null || this.config.bonds === undefined) {
return;
}
this.accessories.push(accessory);
// If bonds hasn't been initilized, attempt to configure the accessory
// after a delay
if (this.bonds) {
this.log(`Configuring Accessory: ${accessory.displayName}`);
this.create(accessory);
}
else {
const that = this;
const timer = setInterval(() => {
if (this.bonds) {
that.log(`Configuring Accessory: ${accessory.displayName}`);
that.create(accessory);
clearInterval(timer);
}
}, 500);
}
}
create(accessory) {
const device = accessory.context.device;
const bond = this.bondForDevice(device);
if (!bond) {
return;
}
if ((bond.config.hide_device_ids
&& bond.config.hide_device_ids.includes(device.id))) {
this.removeAccessory(accessory);
return;
}
this.logAccessory(accessory, `actions: ${device.actions}`);
const bondAccessory = platformAccessory_1.BondAccessory.create(this, accessory, bond);
bond.accessories.push(bondAccessory);
}
setupBPUP(bond) {
const PORT = 30007;
const HOST = bond.config.ip_address;
const message = Buffer.from('');
const client = dgram_1.default.createSocket('udp4');
const log = this.log;
function send() {
client.send(message, 0, message.length, PORT, HOST, (err) => {
if (err) {
log.error(`Erorr sending UDP message: ${err}`);
throw err;
}
log.debug(`UDP message sent to ${HOST}:${PORT}`);
});
}
send();
// From Bond API Docs: The client should continue to send the Keep-Alive datagram on
// the same socket every 60 seconds to keep the connection active.
setInterval(send, 1000 * 60);
client.on('message', (message, remote) => {
const msg = message.toString().trim();
const packet = JSON.parse(msg);
log.debug(`UDP Message received from ${remote.address}:${remote.port} - ${msg}`);
bond.receivedBPUPPacket(packet);
});
client.on('close', () => {
this.log('Connection closed');
});
}
bondForDevice(device) {
if (this.bonds) {
const bond = this.bonds.find(x => x.version.bondid === device.bondId && x.deviceIds.includes(device.id));
if (bond === undefined) {
this.log.error(`No Bond found for Device: ${device.name}.
This Device may have been removed from your Bond but still exists in cachedAccessories.`);
}
return bond;
}
else {
this.log.error('config.bonds is not defined');
}
}
// Helper Methods
accessoryAdded(uuid) {
const accessories = this.accessories.filter(acc => {
return acc.UUID === uuid;
});
return accessories.length > 0;
}
debug(accessory, message) {
const device = accessory.context.device;
this.log.debug(`[${device.name}] ${message}`);
}
logAccessory(accessory, message) {
const device = accessory.context.device;
this.log(`[${device.name}] ${message}`);
}
error(accessory, message) {
const device = accessory.context.device;
this.log.error(`[${device.name}] ${message}`);
}
}
exports.BondPlatform = BondPlatform;