ultra-bridge
Version:
Bridge Zwave device into Apple HomeKit protocol.
342 lines (298 loc) • 9.72 kB
JavaScript
'use strict';
const debug = require('debug')('Gateway');
const destroy_debug = require('debug')('Destroy');
const events = require('events');
const hap_nodejs = require('hap-nodejs');
const Accessory = hap_nodejs.Accessory;
const Service = hap_nodejs.Service;
const Characteristic = hap_nodejs.Characteristic;
const uuid = hap_nodejs.uuid;
const child_process = require('child_process');
const parser = require('xml2json').toJson;
const Bridge = hap_nodejs.Bridge;
const GatewayManager = require("./GatewayManager.js");
const GatewayInfo = require("./GatewayInfo.js");
const DeviceTemplate = require('./DeviceTemplate.js');
const isReachable = require('is-reachable');
const macaddress = require('macaddress');
const Device = require('./Device.js');
module.exports = Gateway;
function Gateway(mac, model, ip) {
this.mac = mac;
this._info = GatewayInfo.load(mac);
this.setting = {};
this.setting.ip = ip;
this.model = this._info.model;
this.name = this.model + " " + this.mac;
this.devices = [];
this.setBridge();
}
Gateway.prototype.isPaired = function () {
let clients = this.Bridge._accessoryInfo.pairedClients;
for (var client in clients) {
return true;
break;
}
return false;
};
Gateway.prototype.setBridge = function () {
// init a Bridge object;
this.Bridge = this.Bridge || new Bridge(this.name, uuid.generate(this.mac));
this.Bridge.username = this.mac.toUpperCase().match(/.{1,2}/g).join(':');
this.Bridge.getService(Service.AccessoryInformation)
.setCharacteristic(Characteristic.Manufacturer, "Avadesign Technology")
.setCharacteristic(Characteristic.Model, this.model)
.setCharacteristic(Characteristic.SerialNumber, this.mac);
this.Bridge.on('identify', (paired, callback) => {
debug("Gateway "+ this.name +' identified.');
callback();
});
};
Gateway.prototype.getInfrastructure = function (callback) {
debug('Getting Infrastructure on Gateway ' + this.mac);
child_process.exec('curl --digest ' + '-u ' + this.setting.acc + ':' +
this.setting.pwd + ' ' + this.setting.ip + ':5000/' + 'node_detail.cgi -d "fun=load"', {timeout: 5 * 1000}, (err, stdout) => {
let json = parser(stdout, {
object:true
});
callback(err, json);
});
}
Gateway.prototype.getScenes = function (callback) {
let cmd = 'curl --digest -u ' + this.setting.acc + ":"+ this.setting.pwd +' '+ this.setting.ip + ':5000/scenepost.html -d "fun=load"';
child_process.exec(cmd, function (err, stdout) {
let json = parser(stdout, {
object:true
});
callback(err, json);
});
}
var productToTemplate = require("./productToTemplate");
Gateway.prototype.bridgeDevice = function (node) {
debug('Gateway ' + this.mac + ' bridging ' + node.product + ":" + node.name);
try {
if (productToTemplate[node.product] == "WallSwitch") {
let device = new Device(DeviceTemplate.WallSwitch, this, node);
this.addDevice(device);
}
else if (productToTemplate[node.product] == "DoorSensor") {
let device = new Device(DeviceTemplate.DoorSensor, this, node);
this.addDevice(device);
}
else if (productToTemplate[node.product] == "PowerMonitor") {
let device = new Device(DeviceTemplate.PowerMonitor, this, node);
this.addDevice(device);
}
else if (productToTemplate[node.product] == "FourInOneSensor") {
let device = new Device(DeviceTemplate.FourInOneSensor, this, node);
this.addDevice(device);
}
else if (productToTemplate[node.product] == "FourInOneMotoionSensor") {
let device = new Device(DeviceTemplate.FourInOneMotoionSensor, this, node);
this.addDevice(device);
}
else if (productToTemplate[node.product] == "ElectronicLock") {
let device = new Device(DeviceTemplate.ElectronicLock, this, node);
this.addDevice(device);
}
else if (productToTemplate[node.product] == "Curtain") {
let device = new Device(DeviceTemplate.ZW4102CurtainControlModule, this, node);
this.addDevice(device);
}
else if (productToTemplate[node.product] == "ColourLed") {
let device = new Device(DeviceTemplate.ColourLed, this, node);
this.addDevice(device);
}
else {
debug('UltraBridge not verified product: [' + node.product + '] Ignored.');
}
} catch (e) {
console.log(e);
}
};
Gateway.prototype.addDevice = function (device) {
if (!device) throw Error("Device not exists.");
try {
this.Bridge.addBridgedAccessory(device.accessory);
this.devices.push(device);
} catch (e) {
device.remove();
debug('Fail to bridge ' + device.name);
}
};
Gateway.prototype.registerAdminUser = function (acc, pwd) {
this._info.acc = acc;
this._info.pwd = pwd;
this.setting.acc = acc;
this.setting.pwd = pwd;
};
Gateway.prototype.BridgeGateway = function (acc, pwd, callback) {
this.registerAdminUser(acc, pwd);
this.establishBridgedNodes(callback);
};
Gateway.prototype.establishBridgedNodes = function (callback) {
this.getInfrastructure((err, data) => {
try {
if (err) throw err;
if (!data.node_detail) throw "Can't get data from " + this.mac + ":" + this.setting.ip;
this.version = data.node_detail.version;
this.Bridge.getService(Service.AccessoryInformation)
.setCharacteristic(Characteristic.FirmwareRevision, this.version);
if (Array.isArray(data.node_detail.node)) {
data.node_detail.node.forEach((node) => {
this.bridgeDevice(node);
});
}
debug('Gateway ' + this.mac + ' setting success.');
this._info.save(); // acc, pwd and ip is correct, so save the info.
if (callback) callback();
} catch (e) {
debug(e);
debug('Gateway ' + this.mac + ' failed to bridge.');
this.unregisterAdminUser();
if (callback) callback(e);
}
});
};
Gateway.prototype.unregisterAdminUser = function () {
this._info.acc = '';
this._info.pwd = '';
this.setting.acc = '';
this.setting.pwd = '';
this._info.save();
};
Gateway.prototype.destroy = function () { // destroy Bridge and devices.
try {
this.Bridge.destroy(); // stop publishing
for (var i in this.devices) {
this.devices[i].remove();
}
this.devices = [];
this.Bridge.removeAllBridgedAccessories(); // remove accessory
destroy_debug('Gateway ' + this.mac + ' is destroyed.');
} catch (e) {
destroy_debug(e);
}
};
Gateway.prototype.publish = function (port, pincode) {
if (port && pincode) {
this.port = port;
this.pincode = pincode;
}
this.Bridge.publish({
port: this.port,
pincode: this.pincode,
username: this.Bridge.username,
category: Accessory.Categories.BRIDGE
});
this.Bridge._server.on('pair', () => {
this.devices.forEach((device) => {
device.startAutoUpdate();
});
});
this.Bridge._server.on('unpair', () => {
if (!this.isPaired()) { // stop device active if no one use it.
this.devices.forEach((device) => {
device.stopAutoUpdate();
});
}
});
this.reachable = true;
this.setWatchDog();
debug('Gateway ' + this.mac + ' published. ' + 'ip: ' + this.setting.ip);
};
Gateway.prototype.removeDevice = function (nodeId) {
for (var index in this.devices) {
var device = this.devices[index];
if (device.node.id === nodeId) {
this.devices.splice(index, 1);
device.remove();
this.Bridge.removeBridgedAccessory(device.accessory);
break;
}
}
};
Gateway.prototype.removeAllDevices = function () {
for (var index in this.devices) {
var device = this.devices[index];
device.remove();
this.Bridge.removeBridgedAccessory(device.accessory);
}
};
Gateway.prototype.getNodeValues = function (nodeId, callback) {
let values;
let error;
let proc = child_process.exec('curl --digest ' + '-u ' + this.setting.acc + ':' +
this.setting.pwd + ' ' + this.setting.ip + ':5000/' +
'node_detail.cgi -d "fun=load&id=' + nodeId + '"', {timeout: 3 * 1000},
(err, stdout) => {
try {
if (err) throw err;
let node_detail = parser(stdout, {object: true}).node_detail;
if (!node_detail) throw "Missing node_detail.";
if (node_detail && !node_detail.node){
this.removeDevice(nodeId); // it mean this node is removed from gateway.
throw "Missing node.";
}
if (node_detail.node.status == 'Dead') {
throw "node dead.";
}
values = node_detail.node.value;
} catch (e) {
error = e;
}
});
proc.on('close', (code, signal) => {
try {
if (code || signal || error) { // Can't poll data.
throw Error("Can't poll data from " + this.name);
} else {
callback(null, values);
}
} catch (e) {
// Can't poll ths node.
callback(e);
}
});
}
Gateway.prototype.setValue = function (nodeId, value, state, callback) {
let proc = child_process.exec('curl --digest ' + '-u ' + this.setting.acc + ':' +
this.setting.pwd + ' ' + this.setting.ip + ':5000/' + 'valuepost.html -d "' +
nodeId + '-'+ value.class.replace(/\s/,'+') + '-' + value.genre + '-' +
value.type + '-' + value.instance + '-' + value.index + '=' +
state + '"', {timeout: 3 * 1000});
proc.on('close', (code, signal) => {
try {
if (code || signal) {
throw Error("Can't control " + this.name);
} else {
callback(null);
}
} catch (e) {
callback(e);
}
});
};
Gateway.prototype.resetIP = function () {
debug(this.mac + ' receieve ip reset request.');
GatewayManager.scan(); // rescan gateway on network;
}
Gateway.prototype.setWatchDog = function () {
this.reachableUpdater = setInterval(() => {
isReachable(this.setting.ip + ":5000", {timeout: 1 * 1000}).then((reachable) => {
if (reachable) {
debug(this.mac + ' is reachable.');
this.reachable = true;
// if (this.setting.acc && this.setting.pwd) {
// this.establishBridgedNodes((err)=>{
// if (err) debug(err);
// });
// }
} else {
debug(this.mac + ' is unreachable.');
this.reachable = false;
this.resetIP();
}
});
}, 30 * 1000);
};