redmatic-homekit
Version:
HAP-Nodejs based Node-RED nodes to create HomeKit Accessories
185 lines (152 loc) • 7.33 kB
JavaScript
class Service {
constructor(acc, subtype) {
this.acc = acc;
this.subtype = subtype;
return this;
}
get(characteristic, endpoint, cluster, attribute, transform) {
if (!transform) {
transform = function (data) {
return data;
};
}
this.acc.proxy.on('message', msg => {
if (msg.device.ieeeAddr === this.acc.device.ieeeAddr && msg.endpoint.ID === endpoint && msg.cluster === cluster && typeof msg.data[attribute] !== 'undefined') {
this.acc.node.debug(`msg ${characteristic} ${this.acc.device.meta.name} ${msg.cluster} ${JSON.stringify(msg.data)}`);
const val = transform(msg.data[attribute]);
if (typeof val !== 'undefined' && !this.suppressUpdate) {
this.acc.updateCharacteristic(this.subtype, characteristic, val);
}
}
});
if (this.acc.device.getEndpoint(endpoint) && this.acc.device.getEndpoint(endpoint).clusters[cluster]) {
this.acc.updateCharacteristic(this.subtype, characteristic, transform(this.acc.device.getEndpoint(endpoint).clusters[cluster].attributes[attribute], true));
}
return this;
}
set(characteristic, endpoint, cluster, transform, suppressUpdate) {
this.acc.addListener('set', this.subtype, characteristic, (value, callback) => {
this.acc.node.debug(`set ${this.subtype} ${characteristic} ${value}`);
const {command, payload} = transform(value);
this.acc.node.debug(`command ${this.acc.device.meta.name} ${cluster} ${command} ${payload ? JSON.stringify(payload) : ''}`);
clearTimeout(this.suppressUpdateTimer);
this.suppressUpdate = this.suppressUpdate || suppressUpdate;
this.suppressUpdateTimer = setTimeout(() => {
this.suppressUpdate = false;
}, 15000);
this.acc.device.getEndpoint(endpoint).command(cluster, command, payload)
.then(() => {
callback();
})
.catch(error => {
this.suppressUpdate = false;
this.acc.node.debug(`command error ${this.acc.device.meta.name} ${cluster} ${command} ${payload ? JSON.stringify(payload) : ''} ${error.message}`);
callback(new Error(this.acc.hap.HAPServer.Status.SERVICE_COMMUNICATION_FAILURE));
});
});
return this;
}
update(characteristic, value) {
this.acc.updateCharacteristic(this.subtype, characteristic, value);
}
setProps(characteristic, props) {
this.acc.setProps(this.subtype, characteristic, props);
return this;
}
fault(datapointNameArr, transformArr) {
this.acc.datapointsFault(this.subtype, datapointNameArr, transformArr);
return this;
}
}
module.exports = class Accessory {
constructor(node, device) {
this.device = device;
this.node = node;
const {bridgeConfig, herdsman, proxy} = node;
const {hap} = bridgeConfig;
this.bridgeConfig = bridgeConfig;
this.herdsman = herdsman;
this.proxy = proxy;
this.hap = hap;
this.acc = bridgeConfig.accessory({id: this.device.ieeeAddr, name: this.device.meta.name});
if (!this.acc) {
return;
}
this.acc.getService(hap.Service.AccessoryInformation)
.setCharacteristic(hap.Characteristic.Manufacturer, this.device.manufacturerName)
.setCharacteristic(hap.Characteristic.Model, this.device.modelID)
.setCharacteristic(hap.Characteristic.SerialNumber, this.device.ieeeAddr)
.setCharacteristic(hap.Characteristic.FirmwareRevision, this.device.softwareBuildID);
this.acc.on('identify', (paired, callback) => {
this.identify(paired, callback);
});
this.listeners = [];
this.subscriptions = [];
this.subtypeCounter = 0;
node.on('close', () => {
node.debug('removing listeners ' + this.device.ieeeAddr + ' ' + this.device.meta.name);
this.acc.removeListener('identify', () => this.identify());
this.removeListeners();
});
if (typeof this.init === 'function') {
node.debug('init accessory ' + this.device.ieeeAddr + ' ' + this.device.meta.name);
this.init(device, node);
}
}
addService(type, name, subtypeIdentifier = '') {
const subtype = subtypeIdentifier + String(this.subtypeCounter++);
this.node.debug(`addService ${type} ${name} ${subtype}`);
if (this.acc.getService(subtype)) {
this.node.debug('service (' + subtype + ') already existing ');
} else {
this.node.debug('add service ' + type + ' (' + subtype + ') to ' + this.device.ieeeAddr + ' ' + this.device.meta.name);
this.acc.addService(this.hap.Service[type], this.device.meta.name, subtype);
}
return new Service(this, subtype);
}
setProps(subtype, characteristic, props) {
this.node.debug(`setProps ${subtype} ${characteristic} ${props}`);
this.acc.getService(subtype)
.getCharacteristic(this.hap.Characteristic[characteristic])
.setProps(props);
}
updateCharacteristic(subtype, characteristic, value) {
this.node.debug('update ' + this.device.meta.name + ' (' + subtype + ') ' + characteristic + ' ' + value);
this.acc.getService(subtype)
.updateCharacteristic(this.hap.Characteristic[characteristic], value);
}
addListener(event, subtype, characteristic, callback) {
if (this.acc.getService(subtype)) {
this.acc.getService(subtype).getCharacteristic(this.hap.Characteristic[characteristic]).on(event, callback);
this.node.debug('add ' + event + ' listener ' + characteristic + ' (' + subtype + ') to ' + this.device.meta.name);
this.listeners.push({event, subtype, characteristic, callback});
} else {
this.node.error('service (' + subtype + ') does not exist on ' + this.device.meta.name);
}
}
removeListeners() {
if (this.listeners.length > 0) {
const {event, subtype, characteristic, callback} = this.listeners.shift();
this.node.debug('remove ' + event + ' listener ' + characteristic + ' (' + subtype + ') from ' + this.device.meta.name);
this.acc.getService(subtype).getCharacteristic(this.hap.Characteristic[characteristic]).removeListener(event, callback);
this.removeListeners();
}
}
identify(paired, callback) {
this.node.log('identify ' + (paired ? '(paired)' : '(unpaired)') + ' ' + this.device.manufacturerName + ' ' + this.device.meta.name + ' ' + this.device.modelID + ' ' + this.device.ieeeAddr);
try {
callback();
} catch (error) {
this.node.error(error);
}
}
percent(value, lower = 2, upper = 3) {
let p = Math.round((value - lower) * (100 / (upper - lower)));
if (!p || p < 0) {
p = 0;
} else if (p > 100) {
p = 100;
}
return p;
}
};