You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

301 lines
11 KiB

/* jshint -W097 */
/* jshint -W030 */
/* jshint strict: false */
/* jslint node: true */
/* jslint esversion: 6 */
'use strict';
const nodeFS = require('fs');
const child_process = require('child_process');
// you have to require the utils module and call adapter function
const utils = require(__dirname + '/lib/utils'); // Get common adapter utils
const path = require('path');
// it is not an object.
function createHam(options) {
const dataDir = path.normalize(path.join(utils.controllerDir, require(path.join(utils.controllerDir, 'lib', 'tools.js')).getDefaultDataDir()));
// you have to call the adapter function and pass a options object
// name has to be set and has to be equal to adapters folder name and main file name excluding extension
// adapter will be restarted automatically every time as the configuration changed, e.g system.adapter.template.0
const adapter = new utils.Adapter(options);
let homebridgeHandler;
const attempts = {};
// is called when adapter shuts down - callback has to be called under any circumstances!
adapter.on('unload', callback => {
try {
adapter.log.info('cleaned everything up...');
homebridgeHandler.end();
callback();
} catch (e) {
callback();
}
});
process.on('SIGINT', () => homebridgeHandler.end());
process.on('SIGTERM', () => homebridgeHandler.end());
process.on('uncaughtException', err => {
if (adapter && adapter.log) {
adapter.log.warn('Exception: ' + err);
}
homebridgeHandler.end();
});
// is called if a subscribed state changes
adapter.on('stateChange', (id, state) => {
// Warning, state can be null if it was deleted
adapter.log.info('stateChange ' + id + ' ' + JSON.stringify(state));
id = id.substr(adapter.namespace.length+1);
adapter.log.debug('lookup id: ' + id);
// you can use the ack flag to detect if it is status (true) or command (false)
if (state && !state.ack) {
adapter.log.debug('ack is not set!');
homebridgeHandler.setValueForCharId(id, state.val);
}
});
function updateDev(dev_id, dev_name, dev_type, dev_uuid) {
adapter.log.info('updateDev ' + dev_id + ': name = ' + dev_name + ' /type= ' + dev_type);
// create dev
adapter.getObject(dev_id, (err, obj) => {
if (!err && obj) {
adapter.extendObject(dev_id, {
type: 'device',
common: {name: dev_name},
native: {
UUID: dev_uuid,
displayName: dev_name,
category: dev_type
}
});
}
else {
adapter.setObject(dev_id, {
type: 'device',
common: {name: dev_name},
native: {
UUID: dev_uuid,
displayName: dev_name,
category: dev_type
}
}, {});
}
});
}
function updateChannel(dev_id, ch_id, name, ch_uuid) {
const id = dev_id + '.' + ch_id;
// create channel for dev
adapter.log.info('updateChannel ' + id + ': name = ' + name);
adapter.getObject(id, (err, obj) => {
if (!err && obj) {
adapter.extendObject(id, {
type: 'channel',
common: {name: name},
native: {
UUID: ch_uuid,
displayName: name
}
});
}
else {
adapter.setObject(id, {
type: 'channel',
common: {name: name},
native: {
UUID: ch_uuid,
displayName: name
}
}, {});
}
});
}
function updateState(dev_id, ch_id, st_id, name, value, common, st_uuid, callback) {
const id = dev_id + '.' + ch_id + '.'+ st_id;
if (!common) common = {};
if (common.name === undefined) common.name = name;
if (common.role === undefined) common.role = 'state';
if (common.read === undefined && common.write === undefined) common.read = true;
if (common.type === undefined) common.type = 'string';
if (common.unit === undefined) common.unit = '';
adapter.log.info('updateState ' + id + ': value = ' + value + ' /common= ' + JSON.stringify(common));
adapter.getObject(id, (err, obj) => {
if (!err && obj) {
adapter.extendObject(id, {
type: 'state',
common: common,
native: {
UUID: st_uuid,
displayName: name
}
}, callback);
}
else {
adapter.setObject(id, {
type: 'state',
common: common,
native: {
UUID: st_uuid,
displayName: name
}
}, callback);
}
});
}
function setState(dev_id, ch_id, st_id, value) {
const id = dev_id + '.' + ch_id + '.' + st_id;
adapter.setState(id, value, true);
}
// is called when databases are connected and adapter received configuration.
// start here!
adapter.on('ready', main);
function loadExistingAccessories(callback) {
adapter.getDevices((err, res) => {
if (err) {
adapter.log.error('Can not get all existing devices: ' + err);
return;
}
for (let i = 0; i < res.length; i++) {
if (res[i].native && res[i].native.UUID) {
adapter.log.debug('Remember existing Accessory ' + res[i].native.displayName + ' with UUID ' + res[i].native.UUID);
homebridgeHandler.registerExistingAccessory(res[i].native.UUID, res[i].native.displayName);
}
}
if (callback) callback();
});
}
//Catch Homebridge Console Logging
if (process.argv.indexOf('--logs') === -1 && process.argv.indexOf('-l') === -1) {
console.log = function (logs) {
if (adapter && adapter.log && adapter.log.debug) {
adapter.log.debug(logs);
}
process.stdout.write(logs + '\n');
};
}
function main() {
const usedLogger = {
info: adapter.log.debug,
warn: adapter.log.warn,
debug: adapter.log.silly,
silly: adapter.log.silly
};
if (adapter.config.useGlobalHomebridge) {
homebridgeHandler = require('./lib/global-handler');
homebridgeHandler.init({
logger: usedLogger,
homebridgeBasePath: adapter.config.globalHomebridgeBasePath,
homebridgeConfigPath: adapter.config.globalHomebridgeConfigPath,
updateDev: updateDev,
updateChannel: updateChannel,
updateState: updateState,
setState: setState,
ignoreInfoAccessoryServices: adapter.config.ignoreInfoAccessoryServices
});
}
else {
homebridgeHandler = require('./lib/wrapper-handler');
homebridgeHandler.init({
logger: usedLogger,
homebridgeConfigPath: dataDir + adapter.namespace.replace('.', '_'),
updateDev: updateDev,
updateChannel: updateChannel,
updateState: updateState,
setState: setState,
wrapperConfig: adapter.config.wrapperConfig,
ignoreInfoAccessoryServices: adapter.config.ignoreInfoAccessoryServices,
characteristicPollingInterval: adapter.config.characteristicPollingInterval * 1000
});
}
// in this template all states changes inside the adapters namespace are subscribed
adapter.subscribeStates('*');
installLibraries(() => {
loadExistingAccessories(() => {
homebridgeHandler.start();
options.exitAfter && setTimeout(() => adapter && adapter.stop(), 10000);
});
});
}
function installNpm(npmLib, callback) {
const path = __dirname;
if (typeof npmLib === 'function') {
callback = npmLib;
npmLib = undefined;
}
const cmd = 'npm install ' + npmLib + ' --production --prefix "' + path + '"';
adapter.log.info(cmd + ' (System call)');
// Install node modules as system call
// System call used for update of js-controller itself,
// because during installation npm packet will be deleted too, but some files must be loaded even during the install process.
const child = child_process.exec(cmd);
child.stdout.on('data', buf => adapter.log.info(buf.toString('utf8')));
child.stderr.on('data', buf => adapter.log.error(buf.toString('utf8')));
child.on('exit', (code /* , signal */) => {
if (code) {
adapter.log.error('Cannot install ' + npmLib + ': ' + code);
}
// command succeeded
if (typeof callback === 'function') callback(npmLib);
});
}
function installLibraries(callback) {
let allInstalled = true;
if (adapter.config && adapter.config.libraries && !adapter.config.useGlobalHomebridge) {
const libraries = adapter.config.libraries.split(/[,;\s]+/);
for (let lib = 0; lib < libraries.length; lib++) {
if (libraries[lib] && libraries[lib].trim()) {
libraries[lib] = libraries[lib].trim();
if (!nodeFS.existsSync(__dirname + '/node_modules/' + libraries[lib] + '/package.json')) {
if (!attempts[libraries[lib]]) {
attempts[libraries[lib]] = 1;
} else {
attempts[libraries[lib]]++;
}
if (attempts[libraries[lib]] > 3) {
adapter.log.error('Cannot install npm packet: ' + libraries[lib]);
continue;
}
installNpm(libraries[lib], () => installLibraries(callback));
allInstalled = false;
break;
}
}
}
}
if (allInstalled) callback();
}
return adapter;
}
if (!module || !module.parent) {
createHam('ham');
} else {
module.exports = createHam;
}