281 lines
9.9 KiB
JavaScript
281 lines
9.9 KiB
JavaScript
/* jshint -W097 */
|
|
/* jshint strict: false */
|
|
/* jslint node: true */
|
|
/* jslint esversion: 6 */
|
|
'use strict';
|
|
|
|
const inherits = require('util').inherits;
|
|
|
|
let User;
|
|
let hap;
|
|
//let version;
|
|
let Server;
|
|
//let Plugin;
|
|
//let log;
|
|
let Characteristic;
|
|
let once;
|
|
|
|
let server;
|
|
const charMap = {};
|
|
|
|
let logger;
|
|
let updateState;
|
|
let updateDev;
|
|
let updateChannel;
|
|
let setState;
|
|
let mapper;
|
|
let ignoreInfoAccessoryServices;
|
|
|
|
function customStringify(v, func, intent) {
|
|
const cache = new Map();
|
|
return JSON.stringify(v, function (key, value) {
|
|
if (typeof value === 'object' && value !== null) {
|
|
if (cache.get(value)) {
|
|
// Circular reference found, discard key
|
|
return;
|
|
}
|
|
// Store value in our map
|
|
cache.set(value, true);
|
|
}
|
|
return value;
|
|
}, intent);
|
|
}
|
|
|
|
function init(config) {
|
|
logger = config.logger;
|
|
updateState = config.updateState;
|
|
updateDev = config.updateDev;
|
|
updateChannel = config.updateChannel;
|
|
setState = config.setState;
|
|
ignoreInfoAccessoryServices = config.ignoreInfoAccessoryServices;
|
|
|
|
mapper = require('./mapper')(config);
|
|
|
|
User = require(config.homebridgeBasePath + 'lib/user').User;
|
|
User.setStoragePath(config.homebridgeConfigPath);
|
|
hap = require(config.homebridgeBasePath + 'node_modules/hap-nodejs');
|
|
//version = require(config.homebridgeBasePath + 'lib/version');
|
|
Server = require(config.homebridgeBasePath + 'lib/server').Server;
|
|
//Plugin = require(config.homebridgeBasePath + 'lib/plugin').Plugin;
|
|
//log = require(config.homebridgeBasePath + 'lib/logger')._system;
|
|
once = require(config.homebridgeBasePath + 'node_modules/hap-nodejs/lib/util/once').once;
|
|
Characteristic = hap.Characteristic;
|
|
|
|
inherits(MyBridge, hap.Bridge);
|
|
|
|
override(MyBridge, function publish(info, allowInsecureRequest) {
|
|
logger.debug('yunkong2.ham Bridge publish ' + customStringify(info));
|
|
// Вызов метода родительского класса
|
|
// Calling the method of the parent class
|
|
publish.inherited.call(this, info, allowInsecureRequest);
|
|
});
|
|
|
|
/*
|
|
// Переопределение метода в дочернем классе
|
|
// Overriding a method in a child class
|
|
override(MyBridge, function addService(service) {
|
|
// Собственный функционал
|
|
// Own Functionality
|
|
logger.info('yunkong2.ham Bridge addService '+JSON.stringify(service)); //OK, undefined
|
|
// Вызов метода родительского класса
|
|
// Calling the method of the parent class
|
|
return addService.inherited.call(this, service);
|
|
});
|
|
*/
|
|
function iterateCharArray(chars, accessory, service, dev_idname, sr_id, sr_idname) {
|
|
for (const channelIndex in chars) {
|
|
if (!chars.hasOwnProperty(channelIndex)) continue;
|
|
|
|
const char = chars[channelIndex];
|
|
const ch_id = char.UUID;
|
|
const ch_name = char.displayName;
|
|
const ch_val = char.value;
|
|
const ch_idname = mapper.mapCharacteristicType(sr_id, ch_id, ch_name);
|
|
const id = dev_idname + '.' + sr_idname + '.' + ch_idname;
|
|
|
|
const common = mapper.mapCharacteristicProperties(char);
|
|
logger.debug('Mapped Common for ' + id + ': ' + JSON.stringify(common));
|
|
if (common.write) {
|
|
charMap[id] = char; // TODO only if write allowed!
|
|
logger.silly('Add object to charmap with id ' + id + '/' + customStringify(char));
|
|
}
|
|
|
|
char.on('change', data => {
|
|
logger.debug('Char change event: ' + data.oldValue + ' --> ' + data.newValue);
|
|
handleCharValue(accessory, service, char, data.newValue);
|
|
});
|
|
|
|
updateState(dev_idname, sr_idname, ch_idname, ch_name, ch_val, common, ch_id, () => {
|
|
char.getValue((err, value) => {
|
|
if (err) {
|
|
logger.warn('Error while getting current value: ' + err);
|
|
return;
|
|
}
|
|
handleCharValue(accessory, service, char, value);
|
|
})
|
|
});
|
|
}
|
|
}
|
|
|
|
override(MyBridge, function addBridgedAccessory(accessory, deferUpdate) {
|
|
// Вызов метода родительского класса
|
|
// Calling the method of the parent class
|
|
accessory = addBridgedAccessory.inherited.call(this, accessory, deferUpdate);
|
|
logger.debug('yunkong2.ham Bridge addBridgedAccessory ' + customStringify(accessory)); //OK
|
|
// Новое устройство
|
|
// New device
|
|
const dev_id = accessory.UUID;
|
|
const dev_idname = mapper.mapAccessoryUUID(dev_id, accessory.displayName);
|
|
const dev_name = accessory.displayName;
|
|
const dev_cat = accessory.category;
|
|
|
|
updateDev(dev_idname, dev_name, dev_cat, dev_id);
|
|
|
|
for (const index in accessory.services) {
|
|
if (!accessory.services.hasOwnProperty(index)) continue;
|
|
|
|
const service = accessory.services[index];
|
|
const sr_id = service.UUID;
|
|
const sr_idname = mapper.mapServiceType(sr_id, service.displayName);
|
|
const sr_name = service.displayName;
|
|
|
|
if (ignoreInfoAccessoryServices && sr_idname === 'Accessory-Information') {
|
|
continue;
|
|
}
|
|
|
|
logger.silly('Add service class=' + customStringify(service));
|
|
updateChannel(dev_idname, sr_idname, sr_name, sr_id);
|
|
|
|
iterateCharArray(service.characteristics, accessory, service, dev_idname, sr_id, sr_idname);
|
|
if (service.optionalCharacteristics) {
|
|
iterateCharArray(service.optionalCharacteristics, accessory, service, dev_idname, sr_id, sr_idname);
|
|
}
|
|
|
|
}
|
|
return accessory;
|
|
});
|
|
|
|
Server.prototype._createBridge = function() {
|
|
logger.debug('yunkong2.ham Bridge create'); //OK
|
|
// pull out our custom Bridge settings from config.json, if any
|
|
const bridgeConfig = this._config.bridge || {};
|
|
|
|
// Create our Bridge which will host all loaded Accessories
|
|
return new MyBridge(bridgeConfig.name || 'Homebridge', hap.uuid.generate('HomeBridge'));
|
|
};
|
|
|
|
// Updated to compare value differetly. Needed till officially updated
|
|
Characteristic.prototype.setValue = function(newValue, callback, context, connectionID) {
|
|
|
|
if ( newValue instanceof Error ) {
|
|
this.status = newValue
|
|
} else {
|
|
this.status = null;
|
|
}
|
|
|
|
newValue = this.validateValue(newValue); //validateValue returns a value that has be cooerced into a valid value.
|
|
|
|
var oldValue = this.value;
|
|
|
|
if (this.listeners('set').length > 0) {
|
|
|
|
// allow a listener to handle the setting of this value, and wait for completion
|
|
this.emit('set', newValue, once(function(err) {
|
|
this.status = err;
|
|
if (err) {
|
|
// pass the error along to our callback
|
|
if (callback) callback(err);
|
|
}
|
|
else {
|
|
if (newValue === undefined || newValue === null)
|
|
newValue = this.getDefaultValue();
|
|
// setting the value was a success; so we can cache it now
|
|
this.value = newValue;
|
|
if (callback) callback();
|
|
|
|
if (this.eventOnlyCharacteristic === true || oldValue !== newValue)
|
|
this.emit('change', { oldValue:oldValue, newValue:newValue, context:context });
|
|
}
|
|
|
|
}.bind(this)), context, connectionID);
|
|
|
|
}
|
|
else {
|
|
if (newValue === undefined || newValue === null)
|
|
newValue = this.getDefaultValue();
|
|
// no one is listening to the 'set' event, so just assign the value blindly
|
|
this.value = newValue;
|
|
if (callback) callback();
|
|
|
|
if (this.eventOnlyCharacteristic === true || oldValue !== newValue)
|
|
this.emit('change', { oldValue:oldValue, newValue:newValue, context:context });
|
|
}
|
|
|
|
return this; // for chaining
|
|
}
|
|
|
|
function MyBridge(displayName, serialNumber) {
|
|
logger.debug('yunkong2.ham Bridge constructor');
|
|
MyBridge.super_.call(this, displayName, serialNumber);
|
|
}
|
|
}
|
|
|
|
function registerExistingAccessory(UUID, name) {
|
|
mapper.mapAccessoryUUID(UUID, name);
|
|
}
|
|
|
|
function start() {
|
|
const insecureAccess = false;
|
|
logger.info('Using Homebridge Config Path: ' + User.persistPath());
|
|
// Initialize HAP-NodeJS with a custom persist directory
|
|
hap.init(User.persistPath());
|
|
|
|
server = new Server(insecureAccess);
|
|
|
|
server.run();
|
|
}
|
|
|
|
function end() {
|
|
if (server) {
|
|
server._teardown();
|
|
// Save cached accessories to persist storage.
|
|
server._updateCachedAccessories();
|
|
}
|
|
}
|
|
|
|
function setValueForCharId(id, value) {
|
|
if (charMap[id]) {
|
|
logger.debug('set value of char for ' + id);
|
|
charMap[id].setValue(value);
|
|
}
|
|
}
|
|
|
|
function handleCharValue(accessory, serv, char, newValue){
|
|
logger.debug('handleCharValue = ' + newValue);
|
|
logger.silly('characteristic = ' + customStringify(char));
|
|
logger.silly('accessory =' + customStringify(accessory));
|
|
|
|
const sr_id = serv.UUID;
|
|
const sr_idname = mapper.mapServiceType(sr_id, serv.displayName);
|
|
const ch_id = char.UUID;
|
|
const ch_idname = mapper.mapCharacteristicType(sr_id, ch_id, char.displayName);
|
|
const dev_id = accessory.UUID;
|
|
const dev_idname = mapper.mapAccessoryUUID(dev_id, accessory.displayName);
|
|
const value = newValue;
|
|
|
|
setState(dev_idname, sr_idname, ch_idname, value);
|
|
}
|
|
|
|
// Средство для переопределения функций
|
|
// Tools for overriding functions
|
|
function override(child, fn) {
|
|
child.prototype[fn.name] = fn;
|
|
fn.inherited = child.super_.prototype[fn.name];
|
|
}
|
|
|
|
exports.init = init;
|
|
exports.end = end;
|
|
exports.setValueForCharId = setValueForCharId;
|
|
exports.start = start;
|
|
exports.registerExistingAccessory = registerExistingAccessory;
|