UNPKG

homebridge-knx

Version:

homebridge shim for KNX home automation.

379 lines (350 loc) 16.2 kB
/* jshint esversion: 6, strict: true, node: true */ 'use strict'; var HandlerPattern = require('./addins/handlerpattern.js'); var KNXAccess = require('./knxaccess.js'); var iterate = require('./iterate.js'); var userOpts = require('./user').User; var path = require('path'); var fs = require('fs'); /** * @classdesc represents the API to be used for knx service add-ins */ class customServiceAPI { /** * creates an API object for custom handler * * @param {ServiceKNX} serviceKNX - back reference to the managed service * @param {function} handler * @private */ constructor(serviceKNX, handlerName) { serviceKNX.globs.info("customServiceAPI.constructor(service, " + handlerName + ")"); // Instantiate the Handler variable as false for error catching. var Handler = false; // Define the possible locations where the Handler file could be var localHandlerPath = path.join(__dirname, './addins/' + handlerName + '.js'); var remoteHandlerPath = path.join(userOpts.addinsPath(), handlerName + '.js'); // Check the existance of a file at those locations if (fs.existsSync(localHandlerPath)) { /** @type {function} */ Handler = require(localHandlerPath); } else if (fs.existsSync(remoteHandlerPath)) { // We have to tell the remote handler where to find the handlerpattern // To gap the bridge we put it in a process enviroment variable process.env.handlerPattern = path.join(__dirname, './addins/handlerpattern.js'); /** @type {function} */ Handler = require(remoteHandlerPath); } else { throw (new Error('HANDLER CONFIGURATION ERROR').message = 'Error in ' + handlerName + ' - could not be found.'); } this.handler = new Handler(this); // FIXME: doesnt work if (this.handler instanceof HandlerPattern) { // everything fine serviceKNX.globs.info('HandlerPattern instantiated!'); } else { throw (new Error('HANDLER CONFIGURATION ERROR').message = 'Error in ' + handlerName + ' - not InstanceOf HandlerPattern().'); } this.serviceKNX = serviceKNX; this.handlerName = handlerName; /** * List of characteristis handled by this API * * @type {characteristicKNX[named]} */ this.characteristicsList = {}; // create a local characteristics list this.charValueList = {}; // later stores the values of the characteristics/fields } /** * Adds a new characteristic to the API instance. * * @param {CharacteristicKNX} characteristicKNX - The characteristic to be added */ addCharacteristic(characteristicKNX) { this.serviceKNX.globs.info(this.handlerName + ': Adding Characteristic ' + characteristicKNX.name); this.characteristicsList[characteristicKNX.name] = characteristicKNX; this.charValueList[characteristicKNX.name] = null; } /** * Adds an KNX-Object to the API instance * * @param {string} name - unique name * @param {string[]} setGroupAddresses - List of set Addresses * @param {string[]} listenGroupAddresses - List of set Addresses */ addPseudoCharacteristic(name, setGroupAddresses, listenGroupAddresses, dptype) { this.serviceKNX.globs.info(this.handlerName + ': Adding PseudoCharacteristic ' + name); if (this.characteristicsList[name]) { // name already given in that service throw (new Error('CONFIGURATION ERROR').message = 'Duplicate Type "' + name + '" service ' + this.serviceKNX.name + ' in knx_config.json'); } this.characteristicsList[name] = { name: name, }; this.charValueList[name] = null; this.characteristicsList[name].setGroupAddressList = []; this.characteristicsList[name].listenGroupAddressList = []; this.characteristicsList[name].pseudo = true; if (setGroupAddresses) { setGroupAddresses = [].concat(setGroupAddresses); for (let isga = 0; isga < setGroupAddresses.length; isga++) { let thisGA = setGroupAddresses[isga]; this.characteristicsList[name].setGroupAddressList.push({ address: thisGA, reverse: false, dptype: dptype }); } } this.characteristicsList[name].listenGroupAddressList = []; if (listenGroupAddresses) { listenGroupAddresses = [].concat(listenGroupAddresses); for (let isga = 0; isga < listenGroupAddresses.length; isga++) { let thisGA = listenGroupAddresses[isga]; this.characteristicsList[name].listenGroupAddressList.push({ address: thisGA, reverse: false, dptype: dptype }); this.serviceKNX.globs.knxmonitor.registerGA(thisGA, dptype, function (val, src, dest) { this.knxbusEventCatcher(name, val, src, dest); }.bind(this)); } // bind to KNX bus events } this.characteristicsList[name].getDPT = () => { return dptype } // create a getDPT method for the pseudo object } /** * Sets a homekit value for a local characteristic * * @param {string} field - The name of the characteristic, like "On" for lightbulb power * @param {primitive} value - the value for the characteristic, dependent on the characteristic's type */ setValue(field, value) { var chrKNX; this.serviceKNX.globs.info(this.handlerName + "->customServiceAPI.setValue(" + field + "," + value + ")"); // get the characteristic if (!this.characteristicsList[field]) { throw (new Error('HANDLER CONFIGURATION ERROR').message = 'Error in ' + this.handlerName + '. Field ' + field + ' does not exist'); } else { chrKNX = this.characteristicsList[field]; } if (!chrKNX.pseudo) { // push to HomeKit KNXAccess.writeValueHK(value, chrKNX, undefined, false); // Store value locally this.charValueList[chrKNX.name] = value; } else { throw (new Error('HANDLER CONFIGURATION ERROR').message = 'Error in ' + this.handlerName + '. Field ' + field + ' is no HomeKit object.'); } } /** * Returns a local characteristic's value * * @param {string} field - The name of the characteristic, like "On" for lightbulb power * @return {primitive} - Dependent on the charceristic's type */ getValue(field) { var chrKNX; this.serviceKNX.globs.info(this.handlerName + "->customServiceAPI.getValue(" + field + ")"); // get the characteristic if (!this.characteristicsList[field]) { throw (new Error('HANDLER CONFIGURATION ERROR').message = 'Error in ' + this.handlerName + '. Field ' + field + ' does not exist'); } else { chrKNX = this.characteristicsList[field]; } if (!chrKNX.pseudo) { this.serviceKNX.globs.info("Returning HomeKitValue"); var v = chrKNX.getHomekitCharacteristic().value; this.serviceKNX.globs.info("Returning HomeKitValue of " + v); return v; } else { this.serviceKNX.globs.info("Returning Pseudo characteristic value of " + this.charValueList[field]); return this.charValueList[field]; } } /** * Writes a value to the KNX bus. Requires a "Set" address in the characteristic * * @param {string} field - The name of the characteristic, like "On" for lightbulb power * @param {primitive} value - The value to be sent. * @param {string} dptype - Data Point Type like "DPT5" for 1 byte 0..255, "DPT5.001" for automatic conversion from * decimal 0..100 to 0..255 or "DPT1" for boolean */ knxWrite(field, value, dptype) { /** @type {CharacteristicKNX} */ var chrKNX; this.serviceKNX.globs.info(this.handlerName + "->customServiceAPI.knxWrite(" + field + "," + value + "," + dptype + ")"); // get the characteristic //iterate(this.characteristicsList); if (!this.characteristicsList[field]) { throw (new Error('HANDLER CONFIGURATION ERROR').message = 'Error in ' + this.handlerName + '. Field ' + field + ' does not exist'); } else { chrKNX = this.characteristicsList[field]; } if (!dptype) { // get the DPT of the characteristic dptype = chrKNX.getDPT(); } // iterate through all group addresses to be written for that characteristic for (var iGA = 0; iGA < chrKNX.setGroupAddressList.length; iGA++) { var gaddress = chrKNX.setGroupAddressList[iGA]; gaddress.dptype = dptype; KNXAccess.writeValueKNX(value, gaddress, undefined); } } /** * Sends a read request to the KNX bus. Answer will returned by a call of onKNXValueChange. * * @param {string} field - The name of the characteristic. Requires a "Listen" in the characteristic or KNXObject. */ knxReadRequest(field) { /** @type {CharacteristicKNX} */ var chrKNX; this.serviceKNX.globs.info(this.handlerName + "->customServiceAPI.knxReadRequest(" + field + ")"); // get the characteristic // iterate(this.characteristicsList); if (!this.characteristicsList[field]) { throw (new Error('HANDLER CONFIGURATION ERROR').message = 'Error in ' + this.handlerName + '. Field ' + field + ' does not exist'); } else { chrKNX = this.characteristicsList[field]; } // iterate through all group addresses to be written for that characteristic for (var iGA = 0; iGA < chrKNX.listenGroupAddressList.length; iGA++) { var gaddress = chrKNX.listenGroupAddressList[iGA]; KNXAccess.knxread(gaddress.address); } } /** * Returns a characteristic's value from another device/service. * * @param {string} device - unique name of the device as in the configuration * @param {string} service - unique name of the service as in the configuration * @param {string} field - The name of the characteristic, like "On" for lightbulb power * @return {primitive} */ getGlobalValue(device, service, field) { // var dev = globs.devices[index].name var myDevice, myService, thatService, thatChar; for (var iDevice = 0; iDevice < this.serviceKNX.globs.devices.length; iDevice++) { var thatDevice = this.serviceKNX.globs.devices[iDevice]; if (thatDevice.name === device) { myDevice = thatDevice; } } if (!myDevice) { this.service.globs.log("ERROR in custom handler "); throw (new Error("ERROR in custom handler").message = "Device " + device + " not found."); } for (var iService = 0; iService < myDevice.services.length; iService++) { thatService = myDevice.services[iService]; if (thatService.name === service) { myService = thatService; } } if (!myService) { this.serviceKNX.globs.log("ERROR in custom handler "); throw (new Error("ERROR in custom handler").message = "Service " + device + " not found in Device " + device + "."); } if (myService.handler === 'Default') { for (var iCHars = 0; iCHars < myService.myCharacteristics.length; iCHars++) { thatChar = myService.myCharacteristics[iCHars]; if (thatChar.name === field) { return thatChar.getHomekitCharacteristic().getValue(); } } } else { // get the value from customServiceAPI return myService.customServiceAPI.getValue(field); } } /** * Returns a local constant's value. Local constants are stored in the service's sub-section "LocalConstants" and * can be used to store referenced services and referenced devices for use with getGlobalValue() * * @param {string} field - The name of the constant * @return {primitive} - Dependent on the constant's type */ getLocalConstant(field) { var lc = this.serviceKNX.config.LocalConstants; if (lc) { return lc[field]; } } /** * Get a characteristics property. Used for getting the minValue or maxValue or stepValue properties from * the homekit characteristic, which might be overwritten by values in the knx_config.json file. Watch out, * these use the hap-nodejs-syntax, not the knx_config-syntax. * * @param {string} field - The name of the characteristic * @param {string} property - The name of the property * @return { * format: <one of Characteristic.Formats>, * unit: <one of Characteristic.Units>, * minValue: <minimum value for numeric characteristics>, * maxValue: <maximum value for numeric characteristics>, * minStep: <smallest allowed increment for numeric characteristics>, * perms: array of [Characteristic.Perms] like [Characteristic.Perms.READ, Characteristic.Perms.WRITE] * } * */ getProperty(field, property) { var chrKNX; this.serviceKNX.globs.info(this.handlerName + "->customServiceAPI.getProperty(" + field + ", " + property + ")"); // get the characteristic if (!this.characteristicsList[field]) { throw (new Error('HANDLER CONFIGURATION ERROR').message = 'Error in ' + this.handlerName + '. Field ' + field + ' does not exist'); } else { chrKNX = this.characteristicsList[field]; } if (!chrKNX.pseudo) { // it's a true homekit characteristic /** @type {propsObject} */ var p = chrKNX.getHomekitCharacteristic().props; if (p.hasOwnProperty(property)) { return p[property]; } return undefined; } else { // it's an invented one (KNXObject) this.serviceKNX.globs.info("Returning UNDEFINED for pseudo characteristic property " + property); return undefined; } } /** * homekitEventCatcher is bound to homebridge events * * @private */ homekitEventCatcher(characteristicName, value, callback, context) { if (context === 'fromKNXBus') { // done, call callback if (callback) { callback(); } } else { if (typeof this.handler.onHKValueChange === 'function') { // implementation looks good if (callback) { callback(); } this.handler.onHKValueChange(characteristicName, this.charValueList[characteristicName], value); this.charValueList[characteristicName] = value; } else if (callback) { callback(); } } } /** * knxbusEventCatcher(characteristicName, value globs.knxmonitor.registerGA(this.groupAddress.address, * this.update.bind(this)); update = function(val, src, dest) * @private */ knxbusEventCatcher(characteristicName, val, src, dest) { var temp = this.characteristicsList[characteristicName]; if ((!this.serviceKNX.globs.knxconnection || this.serviceKNX.globs.knxconnection !== 'knxjs') && temp.getDPT() === 'DPT5.001') { // convert 0..255 to 0..100 for HK val = val * 100 / 255; } var oldValue = this.charValueList[characteristicName]; this.handler.onKNXValueChange(characteristicName, oldValue, val); // in case of non-characteristic save the value locally: if (this.characteristicsList[characteristicName].pseudo) { this.charValueList[characteristicName] = val; } } } module.exports = customServiceAPI;