homebridge-hca
Version:
HCA plugin for Homebridge
208 lines (162 loc) • 6.31 kB
JavaScript
// 'use strict';
const inherits = require('util').inherits;
const AccessoryBase = require('../AccessoryBase');
const itemType = require('node-hca/lib/Design/itemType');
class HcaLightbulb extends AccessoryBase {
constructor(log, item, client) {
super(log, item, client);
}
// TODO: Move outside of class so it's callable when restored.
identify(paired, callback) {
const item = this.context.item;
this.log('Identifying %s (%s).', this.displayName, item.id);
callback();
}
}
function init(accessory) {
const item = accessory.context.item;
accessory.context.dimmingTimer = null;
accessory.context.isDimming = false;
const service =
accessory.getService(Service.Lightbulb) ||
accessory.addService(Service.Lightbulb, accessory.displayName);
service
.getCharacteristic(Characteristic.On)
.onGet(getPowerState.bind(accessory))
.onSet(setPowerState.bind(accessory));
if (item.isDimmable) {
const brightness =
service.getCharacteristic(Characteristic.Brightness) ||
service.addCharacteristic(Characteristic.Brightness)
brightness
.onGet(getBrightness.bind(accessory))
.onSet(setBrightness.bind(accessory));
}
bindUpdates(accessory);
}
// Inform HomeKit about changes that occurred outside of HomeKit
function bindUpdates(accessory) {
const item = accessory.context.item;
accessory.client.designManager
.on('Design:Updated:' + item.id, function (e) {
const lastKnownState = accessory.context.lastKnownState;
const currentState = item.state;
// Ignore duplicate messages. (HCA may send the same message when device or room state changes, based on a change from the other).
if (lastKnownState === currentState) return;
accessory.context.lastKnownState = currentState;
const isReachable = e.errorState == 0;
const isOn = item.state > 0;
const isDimming = accessory.context.isDimming;
const brightness = item.state;
// Programs require additional evaluation
if (item.type == itemType.program && item.currentIconName.length > 0) {
isOn = parseInt(item.currentIconRepresentation) > 0;
brightness = parseInt(item.currentIconRepresentation);
}
accessory.log.debug('%s (%s) has been updated:', accessory.displayName, item.id, JSON.stringify(item));
if (!isReachable)
accessory.log.warn('%s (%s) did not acknowledge receipt of this request.', accessory.displayName, item.id)
if (!isDimming) {
accessory.log.info('%s (%s) has been turned %s.', accessory.displayName, item.id, isOn ? 'on' : 'off');
accessory.log.debug('Updating %s (%s) power state to \'%s\'.', accessory.displayName, item.id, isOn);
}
accessory
.getService(Service.Lightbulb)
.getCharacteristic(Characteristic.On)
.updateValue(isOn);
if (item.isDimmable) {
if (isDimming) {
accessory.log.info('%s (%s) has been set to brightness %s%.', accessory.displayName, item.id, brightness);
accessory.log.debug('Updating %s (%s) brightness to %s%.', accessory.displayName, item.id, brightness);
accessory.context.isDimming = false; // Reset 'isDimming' flag.
}
accessory
.getService(Service.Lightbulb)
.getCharacteristic(Characteristic.Brightness)
.updateValue(brightness);
}
});
}
async function getBrightness() {
const accessory = this;
const item = accessory.context.item;
accessory.log.debug("%s (%s) brightness is %s%.", accessory.displayName, item.id, item.state);
return Promise.resolve(item.state);
}
async function setBrightness(value) {
const accessory = this;
const item = accessory.context.item;
const isOn = item.state > 0;
const typeName = getTypeName(item);
let command, params;
if (item.type == itemType.program) {
command = `${typeName}.StartEx`;
params = ['HCAObject', command, item.id, "dim", value, "", ""];
} else {
command = `${typeName}.DimToPercent`;
params = ['HCAObject', command, item.id, value];
}
// Reset dimming timer.
clearTimeout(accessory.context.dimmingTimer);
accessory.context.isDimming = true;
accessory.context.dimmingTimer = setTimeout(() => {
accessory.log.info("Setting %s (%s) brightness to %s%.", accessory.displayName, item.id, value);
accessory.client.send(params);
}, 500);
}
async function getPowerState() {
const accessory = this;
const item = accessory.context.item;
const isOn = item.state > 0;
accessory.log.debug("%s (%s) is %s.", accessory.displayName, item.id, isOn ? "on" : "off");
return Promise.resolve(isOn);
}
async function setPowerState(value) {
const accessory = this;
const item = accessory.context.item;
const currentState = item.state > 0;
const targetState = Boolean(value);
const targetStateName = targetState ? 'on' : 'off';
const typeName = getTypeName(item);
const isDimming = accessory.context.isDimming;
// Prevent excessive updates when changing brightness.
if (currentState == targetState && isDimming || isDimming) {
accessory.log.debug(`${accessory.displayName} (${item.id}) is ignoring extraneous '${targetStateName}' request.`);
return Promise.resolve();
}
const command = targetState == true ? `${typeName}.On` : `${typeName}.Off`;
const params = ['HCAObject', command, item.id];
accessory.log.info("Turning %s (%s) %s.", accessory.displayName, item.id, targetState ? "on" : "off");
accessory.client.send(params);
return Promise.resolve();
}
function getTypeName(item) {
let typeName;
switch (item.type) {
case itemType.device:
typeName = 'Device';
break;
case itemType.program:
typeName = 'Program';
break;
case itemType.group:
typeName = 'Group';
break;
case itemType.controller:
typeName = 'Controller';
break;
default:
break;
}
return typeName;
}
module.exports = function (accessory, service, characteristic, ouuid) {
this.Accessory = accessory;
this.Service = service;
this.Characteristic = characteristic;
this.uuid = ouuid;
inherits(HcaLightbulb, Accessory);
return HcaLightbulb;
};
module.exports.HcaLightbulb = HcaLightbulb;
module.exports.HcaLightbulb.init = init;