//heaviliy inpspired from Benjamin Garel's PHP SDK, located in
// http://bgarel.free.fr/Zibase/
var http = require("http");
var request = require("request");
var net = require("net");
var url = require("url");
var dgram = require("dgram");
var util = require("util");
var events = require("events");
var assert = require("assert");
var logger = require("tracer").colorConsole({
transport: function (data) {
console.log(data.output);
if (exports.test_logger == true) {
exports.test_logger_data = data;
}
},
dateformat: "dd/mm/yyyy HH:MM:ss.l",
level: 3 //0:'test', 1:'trace', 2:'debug', 3:'info', 4:'warn', 5:'error'
});
/**
* Zibase protocols
* @namespace
*/
var ZbProtocol = new function () {
/** * @constant */
this.PRESET = 0;
/** * @constant */
this.VISONIC433 = 1;
/** * @constant */
this.VISONIC868 = 2;
/** * @constant */
this.CHACON = 3;
/** * @constant */
this.DOMIA = 4;
/** * @constant */
this.X10 = 5;
/** * @constant */
this.ZWAVE = 6;
/** * @constant */
this.RFS10 = 7;
/** * @constant */
this.X2D433 = 8;
/** * @constant */
this.X2D433ALRM = 8;
/** * @constant */
this.X2D868 = 9;
/** * @constant */
this.X2D868ALRM = 9;
/** * @constant */
this.X2D868INSH = 10;
/** * @constant */
this.X2D868PIWI = 11;
/** * @constant */
this.X2D868BOAC = 12;
};
exports.ZbProtocol = ZbProtocol;
/**
* Virtual probes
* @namespace
*/
var ZbVirtualProbe = new function () {
/** * @constant */
this.OREGON = 17;
/** * @constant */
this.OWL = 20;
};
/**
* Possible actions of the Zibase
* @namespace
*/
var ZbAction = new function () {
/** * @constant */
this.OFF = 0;
/** * @constant */
this.ON = 1;
/** * @constant */
this.DIM_BRIGHT = 2;
/** * @constant */
this.ALL_LIGHTS_ON = 4;
/** * @constant */
this.ALL_LIGHTS_OFF = 5;
/** * @constant */
this.ALL_OFF = 6;
/** * @constant */
this.ASSOC = 7;
};
exports.ZbAction = ZbAction;
/*
* Possible states of alerts in the Zibase
* @namespace
var ZbEventType = new function () {
this.OFF = 9;
this.ON = 4;
};
exports.ZbEventType = ZbEventType;
*/
/**
* Creates an empty request.
* @param {string} description Description of the request, which is useful for error tracking and debugging.
* @class Handles requests to the Zibase
*/
function ZbRequest(description) {
this.description = description;
this.header = "ZSIG";
this.command = 0;
this.reserved1 = "";
this.zibaseId = "";
this.reserved2 = "";
this.param1 = 0;
this.param2 = 0;
this.param3 = 0;
this.param4 = 0;
this.myCount = 0;
this.yourCount = 0;
this.message = null;
}
/**
* Formats the request into a binary array understandable by the Zibase.
* @return {Buffer} Binary array
*/
ZbRequest.prototype.toBinaryArray = function () {
var header = new Buffer(this.header);
var command = new Buffer(2)
command.writeUInt16BE(this.command, 0);
var reserved1 = new Buffer(16);
reserved1.fill(String.fromCharCode(0));
reserved1.write(this.reserved1);
var zibaseId = new Buffer(16);
zibaseId.fill(String.fromCharCode(0));
zibaseId.write(this.zibaseId);
var reserved2 = new Buffer(12);
reserved2.fill(String.fromCharCode(0));
reserved2.write(this.reserved2);
var param1 = new Buffer(4);
param1.writeUInt32BE(this.param1, 0);
var param2 = new Buffer(4);
param2.writeUInt32BE(this.param2, 0);
var param3 = new Buffer(4);
param3.writeUInt32BE(this.param3, 0);
var param4 = new Buffer(4);
param4.writeUInt32BE(this.param4, 0);
var myCount = new Buffer(2);
myCount.writeUInt16BE(this.myCount, 0);
var yourCount = new Buffer(2);
yourCount.writeUInt16BE(this.yourCount, 0);
var data;
if (this.message != null) {
var message = new Buffer(96);
message.fill(String.fromCharCode(0));
message.write(this.message);
data = Buffer.concat([header, command, reserved1, zibaseId, reserved2, param1, param2, param3, param4, myCount, yourCount, message]);
} else {
data = Buffer.concat([header, command, reserved1, zibaseId, reserved2, param1, param2, param3, param4, myCount, yourCount]);
}
logger.debug(data)
return data;
}
/**
* Creates a response from the binary data sent by the Zibase
* @class Handles responses from the Zibase
* @param {Buffer} buffer Binary data
*/
function ZbResponse(buffer) {
var tempString = "";
this.header = buffer.toString('utf8', 0, 4);
this.command = buffer.readUInt16BE(4);
tempString = buffer.toString('utf8', 6, 21);
this.reserved1 = tempString.substr(0, tempString.indexOf('\u0000'));
tempString = buffer.toString('utf8', 22, 37);
this.zibaseId = tempString.substr(0, tempString.indexOf('\u0000'));
tempString = buffer.toString('utf8', 38, 49);
this.reserved2 = tempString.substr(0, tempString.indexOf('\u0000'));
this.param1 = buffer.readUInt32BE(50);
this.param2 = buffer.readUInt32BE(54);
this.param3 = buffer.readUInt32BE(58);
this.param4 = buffer.readUInt32BE(62);
this.myCount = buffer.readUInt16BE(64);
this.yourCount = buffer.readUInt16BE(66);
tempString = buffer.toString('utf8', 70);
this.message = tempString.substr(0, tempString.indexOf('\u0000'));
};
/**
*
* @class Allows to manipulate the zibase. The IP of the zibase is needed.
* @param {string} ipAddr IP address of the zibase
* @param {string} deviceId Device ID of the zibase
* @param {string} token Token of the zibase
* @param {function} callback Callback to call when the connection to the zibase is established.
* @extends events.EventEmitter
*/
function ZiBase(ipAddr, deviceId, token, callback) {
this.ip = ipAddr;
this.port = 49999;
this.localport = undefined;
this.myip = require("ip").address();
this.deregistered = false; // true if deregistration has been requested
this.deviceId = deviceId;
this.token = token;
this.timeZone = "Europe/Paris";
events.EventEmitter.call(this);
this.emitEvent = function (event, arg1, arg2) {
if (arg2) {
var id = arg1;
var arg = arg2;
this.emit(event + ":" + id, arg);
} else {
this.emit(event, arg1);
}
}
var self = this;
this.loadDescriptors(function (err) {
self.listenToZiBase(self.processZiBaseData);
/* istanbul ignore else */
if (callback)
callback(err);
});
}
util.inherits(ZiBase, events.EventEmitter);
exports.ZiBase = ZiBase;
/**
* Loads the descriptors of declared devices and scenarios
* @param {function} cb Callback to be called when the descriptors are loaded
*/
ZiBase.prototype.loadDescriptors = function (cb) {
this.descriptors = [];
this.descriptorsByID = [];
var self = this;
request.get("https://zibase.net/m/get_xml.php?device=" + this.deviceId + "&token=" + this.token,
function (error, response, bodyString) {
if (error) {
cb(error);
return;
}
if (bodyString == "") {
cb(new Error("Cannot load descriptors, empty response from https://zibase.net/m/get_xml.php?device=XXX&token=XXX"));
return;
}
var re = /<([m|e])\s+([^>]*)>\s*<n>([^<]*)<\/n>\s*<\/[m|e]>/g;
var match;
while ((match = re.exec(bodyString)) != undefined) {
var type = match[1];
var props = match[2];
var name = match[3];
var desc = {};
switch (type) {
case 'e': desc.type = "device"; break;
case 'm': desc.type = "scenario"; break;
/* istanbul ignore next */
default: desc.type = "";
logger.error("unexpected type '" + type + "' from zibase descriptors, 'e' or 'm' expected.");
break;
}
desc.name = name;
var rep = /([^=]+)="([^"]*)"\s*/g;
var matchp;
while ((matchp = rep.exec(props)) != undefined) {
var id = matchp[1];
var value = matchp[2];
desc[id] = value;
}
self.descriptors.push(desc);
var id = desc.type == "device" ? "c" : "id";
assert.notEqual(desc[id], undefined);
if (desc.type == "device" && desc.p == 6) {
// ZWave
self.descriptorsByID["Z" + desc[id]] = desc;
} else
self.descriptorsByID[desc[id]] = desc;
}
cb(null);
});
}
/**
* retrieves the descriptor with a given id
* @param {string} id ID of the descriptor to retrieve
*/
ZiBase.prototype.getDescriptor = function (id) {
return this.descriptorsByID[id];
}
/**
* listens to events
* @param {string} event Event to be listened to
* @param {string} [id] The ID of the device that triggers the event. Don't specify any when the event is triggered by the zibase
* @param {function} callback Callback to be called when the event is triggered. The parameters of the callback depend on the event
*/
ZiBase.prototype.on = function (event, id, callback) {
/* istanbul ignore else */
if ((typeof event === 'string') && (typeof id === 'string') && (typeof callback === 'function')) {
event = event + ":" + id
}
/* istanbul ignore else */
if ((typeof event === 'string') && (typeof id === 'function') && (typeof callback === 'undefined')) {
callback = id
}
ZiBase.super_.prototype.on.call(this, event, callback);
}
/**
* same as method <tt>on</tt> but only for one event
* @see Zibase#on
* @param {string} event Event to be listened to
* @param {string} [id] The ID of the device that triggers the event. Don't specify any when the event is triggered by the zibase
* @param {function} callback Callback to be called when the event is triggered. The parameters of the callback depend on the event
*/
ZiBase.prototype.once = function (event, id, callback) {
/* istanbul ignore else */
if ((typeof event === 'string') && (typeof id === 'string') && (typeof callback === 'function')) {
// normal call
event = event + ":" + id
}
/* istanbul ignore else */
if ((typeof event === 'string') && (typeof id === 'function') && (typeof callback === 'undefined')) {
callback = id
}
ZiBase.super_.prototype.once.call(this, event, callback);
//logger.error(this)
}
/**
* process the information messages sent by the zibase.
* Emits events depending on the content of the response.
* @param {Response} response A response to process, as sent by the zibase
*/
ZiBase.prototype.processZiBaseData = function (response) {
//response.message = "Received radio ID (<rf>433Mhz</rf> Noise=<noise>2090</noise> Level=<lev>2.3</lev><id>OS3930858754</id>"
if (response.reserved1 == "TEXTMSG") {
function replaceid(zb, message, entire_string, id, before, id_modified, after) {
var name;
var desc = zb.descriptorsByID[id];
/* istanbul ignore else */
if (desc != undefined)
name = desc.name;
/* istanbul ignore else */
if (name != undefined) {
return message.replace(entire_string, before + id_modified + " (" + name + ")" + after);
}
return message;
}
var infos = {};
//Received radio ID (<rf>ZWAVE ZA5</rf> <dev>CMD/INTER</dev> Batt=<bat>Ok</bat>): <id>ZA5_OFF</id>
if (/Received radio ID \(.*CMD\/INTER/.test(response.message)) {
var re = /([^:]+:\s*<id>)(([^_]+)(_OFF)?)(<\/id>)/;
if ((match = re.exec(response.message)) != null) {
logger.trace(match);
infos.id = match[3];
infos.value = (match[4] == undefined) ? "ON" : "OFF";
infos.dev = 'CMD/INTER';
}
var msg = replaceid(this,
response.message,
match[0], // entire string
match[3], // ID
match[1], // start
match[2], // "ID modified" to be replaced by "ID modified (name)"
match[5] // end
);
logger.info(msg);
this.emitEvent("message", { message: msg, raw_message: response.message });
logger.trace("infos=", infos)
/* istanbul ignore else */
if (infos.id != undefined) {
this.emitEvent("change", infos.id, infos)
}
}
//Received radio ID (<rf>433Mhz</rf> Noise=<noise>2175</noise> Level=<lev>2.3</lev>/5 <dev>Oregon THWR288A-THN132N</dev> Ch=<ch>2</ch> T=<tem>+3.7</tem>°C (+38.6°F) Batt=<bat>Ok</bat>): <id>OS3930858754</id>
else if (/Received radio ID \(/.test(response.message)) {
var re = /<([^>]+)>([^<]*)<\/(\1)>/g
while ((match = re.exec(response.message)) != null) {
logger.trace(match);
infos[match[1]] = match[2];
}
var trace = replaceid(this,
response.message,
"<id>" + infos["id"] + "</id>", // entire string
infos["id"], // ID
"<id>", // start
infos["id"], // "ID modified" to be replaced by "ID modified (name)"
"</id>" // end
);
re = /(<rf>ZWAVE )([^<]+)(<\/rf>)/;
/* istanbul ignore else */
if ((match = re.exec(trace)) != null) {
trace = replaceid(this,
trace,
match[0], // entire string
match[2], // ID
match[1], // start
match[2], // "ID modified" to be replaced by "ID modified (name)"
match[3] // end
);
}
logger.info(trace);
this.emitEvent("message", { message: trace, raw_message: response.reserved1 });
logger.trace("infos=", infos)
/* istanbul ignore else */
if (infos.id != undefined) {
this.emitEvent("change", infos.id, infos)
}
}
//Sent radio ID (1 Burst(s), Protocols='Family http' ): I5_OFF
else if (/Sent radio ID \(/.test(response.message)) {
var msg;
var re = /([^:]+:\s*)(([^_]+)(_OFF|_ON)?)/
if ((match = re.exec(response.message)) != null) {
logger.trace(match);
infos.id = match[3];
infos.value = (match[4] == "_OFF") ? "OFF" : "ON"
msg = replaceid(this,
response.message,
match[0], // entire string
match[3], // ID
match[1], // start
match[2], // "ID modified" to be replaced by "ID modified (name)"
"" // end
);
logger.info(msg);
} else {
msg = response.message;
logger.error("Error, regexp " + re + " not found in response.message!");
logger.info(response.message);
}
this.emitEvent("message", { message: msg, raw_message: response.message });
logger.trace("infos=", infos)
/* istanbul ignore else */
if (infos.id != undefined) {
this.emitEvent("change", infos.id, infos)
}
}
//ZWave warning - Device ZA8 is unreachable! : ERR_ZA8
else /* istanbul ignore else */ if (/ZWave warning.*ERR_/.test(response.message)) {
var re = /([^:]+:\s*)(ERR_([^_]+))/;
var msg;
if ((match = re.exec(response.message)) != null) {
logger.trace(match);
infos.id = match[3];
infos.value = "ERR";
logger.debug(infos);
logger.debug(match);
msg = replaceid(this,
response.message,
match[0], // entire string
match[3], // ID
match[1], // start
match[2], // "ID modified" to be replaced by "ID modified (name)"
"" // end
);
} else {
msg = response.message;
logger.error("Error, regexp " + re + " not found in response.message!");
}
logger.info(msg);
this.emitEvent("message", { message: msg, raw_message: response.message });
/* istanbul ignore else */
if (infos.id != undefined) {
this.emitEvent("error", infos.id, infos)
}
}
// ZWave warning - Device ZP16 is unknown!
else if (/ZWave warning.*unknown/.test(response.message)) {
var re = /(.*Device\s*)([A-Z]+[0-9]+)/;
var msg;
if ((match = re.exec(response.message)) != null) {
logger.trace(match);
infos.id = match[2];
infos.value = "ERR";
logger.debug(infos);
logger.debug(match);
msg = replaceid(this,
response.message,
match[0], // entire string
match[2], // ID
match[1], // start
match[2], // "ID modified" to be replaced by "ID modified (name)"
"" // end
);
} else {
msg = response.message;
logger.error("Error, regexp " + re + " not found in response.message!");
}
logger.info(msg);
this.emitEvent("message", { message: msg, raw_message: response.message });
/* istanbul ignore else */
if (infos.id != undefined) {
this.emitEvent("error", infos.id, infos)
}
}
// Completed SCENARIO: 45
else if (/(Completed|Launch) SCENARIO:/.test(response.message)) {
var re = /(SCENARIO: )([0-9]+)/;
/* istanbul ignore else */
if ((match = re.exec(response.message)) != null) {
logger.trace(match);
}
var msg = replaceid(this,
response.message,
match[0], // entire string
match[2], // ID
match[1], // start
match[2], // "ID modified" to be replaced by "ID modified (name)"
"" // end
);
logger.info(msg);
this.emitEvent("message", { message: msg, raw_message: response.message });
} else {
logger.info(response.message);
this.emitEvent("message", { message: response.message, raw_message: response.message });
}
} else if (response.reserved1 == "SLAMSIG") {
this.emitEvent("restart");
// zibase is restarting
// let's reinit
self = this;
this.loadDescriptors(function (err) {
// deregister first, just in case
self.deregisterListener();
// then re-listen to zibase
self.deregistered = false;
self.listenToZiBase(self.processZiBaseData);
self.emitEvent("restarted");
});
} else {
logger.warn("Unsupported response:", response)
}
};
var messageQueue = [];
function nextCallback() {
logger.debug("nextCallback called, %d to process", messageQueue.length)
// removing previous request, which has been handled
//messageQueue.shift();
// check if still a message to process
/* istanbul ignore else */
if (messageQueue.length > 0) {
var callback = messageQueue[0];
logger.debug("queue not empty, processing", util.inspect(callback))
callback();
messageQueue.shift();
nextCallback();
}
};
function pushRequest(requestFunc) {
logger.debug("pushing request", util.inspect(requestFunc))
messageQueue.push(requestFunc);
nextCallback();
}
/**
* Sends a request to the zibase
* @param {ZbRequest} request Request to be sent
* @param {boolean} withResponse Indicates if a response is to be expected
* @param {function} callback Callback to be called when the response is received
*/
ZiBase.prototype.sendRequest = function (request, withResponse, callback) {
logger.debug('request=', request);
/* istanbul ignore else */
if (withResponse == undefined) {
withResponse = true
}
var self = this;
pushRequest(function executeRequest() {
var socket = dgram.createSocket('udp4');
var time_sent; // time at which request is sent
var time_received; // time at which response is received
if (withResponse) {
var t = setTimeout(function () {
var address = socket.address();
logger.debug("socket closing " + socket.address().port);
socket.close();
if (!request.inRetryMode) {
request.inRetryMode = true;
logger.warn("socket timeout for request '" + request.description + "'. Retrying...");
// nextCallback(); // unpile the current call
// and try again
// self.sendRequest(request, withResponse, callback);
executeRequest();
} else {
var err = new Error("socket timeout while waiting for response on " + address.port + ", request was: '" + request.description + "'.");
callback(err, undefined)
}
nextCallback();
}, 5 * 1000);
// 5 seconds x 2 retries = 10 s
socket.on("message", function (msg, rinfo) {
clearTimeout(t);
logger.trace("socket got: " + msg + " from " + rinfo.address + ":" + rinfo.port);
var response = null;
/* istanbul ignore else */
if (msg.length > 0) {
time_received = new Date();
response = new ZbResponse(msg);
logger.trace("response to request '" + request.description + "' received after " + ((time_received - time_sent) / 1000) + "s=", response)
callback(null, response);
}
logger.debug("socket closing " + socket.address().port);
socket.close();
nextCallback();
});
socket.on("listening", function () {
var address = socket.address();
logger.debug("socket listening from SendRequest on " + address.port);
});
socket.on("error", function () {
var address = socket.address();
logger.error("socket error on port " + address.port);
nextCallback();
});
socket.bind();
}
var buffer = request.toBinaryArray();
time_sent = new Date();
socket.send(buffer, 0, buffer.length, self.port, self.ip, function (err, bytes) {
logger.trace("buffer.length=", buffer.length);
logger.trace("err=", err);
logger.trace("bytes=", bytes);
logger.trace("fin");
/* istanbul ignore else */
if (!withResponse) {
logger.debug("socket closing " + socket.address().port);
socket.close();
nextCallback();
}
});
});
};
/**
* Ask the zibase to send a command to an activator specified by its ID and protocol
* @param {string} address Address of the activator in X10 format (e.g. B5)
* @param {ZbAction} action Action to execute
* @param {ZbProtocol} protocol Protocol RF to be used
* @param {int} dimLevel Not supported by the Zibase for now
* @param {int} nbBurst Number of RF emissions
*/
ZiBase.prototype.sendCommand = function (address, action, protocol, dimLevel, nbBurst) {
logger.debug("params:", address, action, protocol, dimLevel, nbBurst)
/* istanbul ignore else */
if (protocol == undefined) {
protocol = ZbProtocol.PRESET
}
/* istanbul ignore else */
if (dimLevel == undefined) {
dimLevel = 0
}
/* istanbul ignore else */
if (nbBurst == undefined) {
nbBurst = 1
}
var description = "sendCommand(" + address + " " + action + "...)";
if (/^[zZ]?[a-pA-P]([1-9]|1[0-6])$/.test(address)) {
address = address.toUpperCase();
/* istanbul ignore else */
if (address[0] == "Z") {
address = address.substr(1);
protocol = ZbProtocol.ZWAVE;
}
var request = new ZbRequest(description);
request.command = 11;
/* istanbul ignore else */
if (action == ZbAction.DIM_BRIGHT && dimLevel == 0)
action = ZbAction.OFF;
request.param2 = action;
logger.debug("action = ", action)
request.param2 |= (protocol & 0xFF) << 0x08;
/* istanbul ignore else */
if (action == ZbAction.DIM_BRIGHT)
request.param2 |= (dimLevel & 0xFF) << 0x10;
/* istanbul ignore else */
if (nbBurst > 1)
request.param2 |= (nbBurst & 0xFF) << 0x18;
request.param3 = 0 + address.substr(1) - 1;
request.param4 = address.charCodeAt(0) - 0x41;
this.sendRequest(request, true, function (response) {
logger.debug("response from Zibase = ", response);
});
} else {
throw new Error("address must be (Z)[A-P]1-16.")
}
};
/**
* Asks the zibase to run the scenario specified by its number
* @param {int|string} scenario the scenario number or name
* @returns {boolean} false if scenario doesn't resolve to a scenario number
*/
ZiBase.prototype.runScenario = function (scenario) {
logger.info("runScenario", scenario);
var request = new ZbRequest("runScenario(" + scenario + ")");
request.command = 11;
request.param1 = 1;
var numScenario;
if (typeof scenario == 'number') {
numScenario = scenario;
} else {
for (var d in this.descriptors) {
/* istanbul ignore else */
if (this.descriptors[d].type == "scenario" &&
this.descriptors[d].name == scenario) {
numScenario = parseInt(this.descriptors[d].id);
break;
}
}
}
/* istanbul ignore else */
if (typeof numScenario != 'number') {
logger.error("Error: unknown scenario '" +
JSON.stringify(scenario) +
"'. Maybe the scenario is not visible in Zibase?");
return false;
}
request.param2 = numScenario;
this.sendRequest(request, true, function (response) {
logger.info("response from Zibase = ", response);
});
return true;
}
/**
* Positions a Zibase alert to the state ON or OFF, or simulates a sensor ID message in the activity log of the Zibase
* @param {int} action The action: 0 - deactivate an alert, 1 - activate an alert, 2 - simulate a sensor ID message (can trigger the execution of a scenario)
* @param {string} address Address of the activator in X10 format (e.g. B5 or ZA14)
*/
ZiBase.prototype.setEvent = function (action, address) {
logger.info("setEvent", action, address);
var request = new ZbRequest("setEvent(" + address + " " + action + ")");
request.command = 11;
request.param1 = 4;
request.param2 = action;
var ev_type;
/* istanbul ignore else */
if (action == 0) {
ev_type = 9
}
/* istanbul ignore else */
if (action == 1 || action == 2) {
ev_type = 4
}
var protocol;
/* istanbul ignore else */
if (address.length > 1) {
address = address.toUpperCase();
if (address[0] == "Z") {
address = address.substr(1);
protocol = ZbProtocol.ZWAVE;
}
var letter = address.charCodeAt(0) - 0x41;
var device = 0 + address.substr(1) - 1;
var id = letter * 16 + device
}
request.param3 = id;
/* istanbul ignore else */
if (protocol == ZbProtocol.ZWAVE) {
if (ev_type == 4)
ev_type = 19
if (ev_type == 9)
ev_type = 20
}
request.param4 = ev_type;
this.sendRequest(request, false, function (response) {
logger.info("response from Zibase = ", response);
});
}
/**
* Get the value of a Vx variable of the Zibase
* @param {int} numVar number of the variable (0 to 31)
* @param {function} callback Callback to be called when the value is retrieved
*/
ZiBase.prototype.getVariable = function (numVar, callback) {
logger.trace("entering getVariable", numVar);
var request = new ZbRequest("getVariable(" + numVar + ")");
request.command = 11;
request.param1 = 5;
request.param3 = 0;
request.param4 = numVar;
this.sendRequest(request, true, function (err, response) {
logger.debug("getVariable", numVar, "=> err=", err, "value=", (response != null) ? response.param1 : null);
callback(err, (response != null) ? response.param1 : null);
});
}
/**
* Asks the Zibase to register the caller client as a listener.
* After this call, the Zibase will send its activity log to the caller, on the given port.
* @param {int} port The port to listen in order to receive the messages
*/
ZiBase.prototype.registerListener = function (port) {
this.localport = port;
var ip = this.myip;
logger.debug("registerListener", ip, port);
var request = new ZbRequest("registerListener(" + ip + " " + port + ")");
request.command = 13;
request.param1 = ip2long(ip);
request.param2 = port;
request.param3 = 0;
request.param4 = 0;
this.sendRequest(request, false);
};
/**
* Asks the Zibase to unregister the caller client
*/
ZiBase.prototype.deregisterListener = function () {
logger.debug("deregisterListener", this.myip, this.localport);
this.deregistered = true;
/* istanbul ignore else */
if (this.socket != undefined) {
var a = undefined;
try {
a = this.socket.address()
} catch (e) { a = '<unbound socket>'}
logger.debug("socket closing " + a);
this.socket.close();
this.socket = undefined;
}
var request = new ZbRequest("deregisterListener(" + this.myip + " " + this.localport + ")");
request.command = 22;
request.param1 = ip2long(this.myip);
request.param2 = this.localport;
request.param3 = 0;
request.param4 = 0;
this.sendRequest(request, false);
};
/**
* Gets the state of an activator from the Zibase
* @param {string} adress X10 formatted address of the activator
* @param {function} callback Callback to be called when the state is received
*/
ZiBase.prototype.getState = function (address, callback) {
logger.trace("getState", address);
var description = "getState(" + address + ")";
var isZWave = false;
/* istanbul ignore else */
if (address.length > 1) {
address = address.toUpperCase();
/* istanbul ignore else */
if (address[0] == "Z") {
isZWave = true;
address = address.substr(1);
}
}
/* istanbul ignore else */
if (address.length > 1) {
var request = new ZbRequest(description);
request.command = 11;
request.param1 = 5;
request.param3 = 4;
var houseCode = address.charCodeAt(0) - 0x41;
var device = 0 + address.substr(1) - 1;
request.param4 = device;
request.param4 |= (houseCode & 0x0F) << 0x04;
// Pour le zwave, il faut mettre le 9e bit à 1
/* istanbul ignore else */
if (isZWave)
request.param4 |= 0x0100;
this.sendRequest(request, true, function (err, response) {
logger.debug("getState", address, "=> err=", err, "value=", (response != null) ? response.param1 : null);
callback(err, (response != null) ? response.param1 : null);
});
}
};
/**
* retrieves the information about a given sensor
* @param {string} idSensor ID of the sensor
* @param {function} callback Callback to be called when the info is retrieved
*/
ZiBase.prototype.getSensorInfo = function (idSensor, callback) {
var typeSensor = idSensor.substring(0, 2);
var numberSensor = idSensor.substring(2, 10000);
var zibaseIP = this.ip;
var self = this;
var singleTimeout = 1000; // timeout of a single request
var timeout = 15000; // global timeout
var start = Date.now();
function getAndProcess() {
var singleStart = Date.now();
request.get("http://" + zibaseIP + "/sensors.xml", { timeout: singleTimeout }, function (err, response, bodyString) {
/* istanbul ignore else */
if (err) {
if (Date.now() - start > timeout) {
logger.error(err);
callback(err);
} else {
var delay = singleTimeout - (Date.now() - singleStart);
if (delay <= 0) {
getAndProcess();
} else {
setTimeout(function () {
getAndProcess();
}, delay);
}
}
return;
}
var re = new RegExp('<ev type="([^"]*)" +pro="' + typeSensor + '" +id="' + numberSensor + '" +gmt="([^"]*)" +v1="([^"]*)" +v2="([^"]*)" +lowbatt="([^"]*)"/>', "g");
var match;
if ((match = re.exec(bodyString)) != undefined) {
for (i = 1; i < match.length; i++) {
var to = match[i];
logger.trace(to);
}
var type = match[1];
// var pro = match[2];
// var id = match[3];
var gmt = match[2];
var v1 = match[3];
var v2 = match[4];
var lowbat = match[5];
// create a new javascript Date object based on the timestamp
// multiplied by 1000 so that the argument is in milliseconds, not seconds
var date = new Date(gmt * 1000);
// hours part from the timestamp
var hours = date.getHours();
// minutes part from the timestamp
var minutes = date.getMinutes();
// seconds part from the timestamp
var seconds = date.getSeconds();
logger.trace("date=", date);
logger.trace("v1=", v1);
logger.trace("v2=", v2);
var results = new Object
results.date = date
results.v1 = v1
results.v2 = v2
callback(null, results);
} else {
// found nothing
callback(new Error("idSensor '" + idSensor + "' not found in http://" + zibaseIP + "/sensors.xml"), {
date: null,
v1: 0,
v2: 0
});
}
});
}
getAndProcess();
};
function ip2long(IP) {
// http://kevin.vanzonneveld.net
// + original by: Waldo Malqui Silva
// + improved by: Victor
// + revised by: fearphage (http://http/my.opera.com/fearphage/)
// + revised by: Theriault
// * example 1: ip2long('192.0.34.166');
// * returns 1: 3221234342
// * example 2: ip2long('0.0xABCDEF');
// * returns 2: 11259375
// * example 3: ip2long('255.255.255.256');
// * returns 3: false
var i = 0;
// PHP allows decimal, octal, and hexadecimal IP components.
// PHP allows between 1 (e.g. 127) to 4 (e.g 127.0.0.1) components.
IP = IP.match(/^([1-9]\d*|0[0-7]*|0x[\da-f]+)(?:\.([1-9]\d*|0[0-7]*|0x[\da-f]+))?(?:\.([1-9]\d*|0[0-7]*|0x[\da-f]+))?(?:\.([1-9]\d*|0[0-7]*|0x[\da-f]+))?$/i);
// Verify IP format.
/* istanbul ignore else */
if (!IP) {
return false;
// Invalid format.
}
// Reuse IP variable for component counter.
IP[0] = 0;
for (i = 1; i < 5; i += 1) {
IP[0] += !!((IP[i] || '').length);
IP[i] = parseInt(IP[i]) || 0;
}
// Continue to use IP for overflow values.
// PHP does not allow any component to overflow.
IP.push(256, 256, 256, 256);
// Recalculate overflow of last component supplied to make up for missing components.
IP[4 + IP[0]] *= Math.pow(256, 4 - IP[0]);
/* istanbul ignore else */
if (IP[1] >= IP[5] || IP[2] >= IP[6] || IP[3] >= IP[7] || IP[4] >= IP[8]) {
return false;
}
return IP[1] * (IP[0] === 1 || 16777216) + IP[2] * (IP[0] <= 2 || 65536) + IP[3] * (IP[0] <= 3 || 256) + IP[4] * 1;
}
ZiBase.prototype.listenToZiBase = function (processDataMethod) {
var socket = dgram.createSocket('udp4');
var self = this;
if (self.socket) {
logger.debug("socket closing " + self.socket.address().port);
self.socket.close();
}
self.socket = socket;
socket.on("message", function (msg, rinfo) {
if (msg.length > 0) {
var response = new ZbResponse(msg);
processDataMethod.call(self, response);
}
});
socket.on("listening", function () {
var address = socket.address();
logger.debug("socket listening on: " + address.port);
if (self.deregistered == false) {
self.registerListener(address.port);
} else {
logger.debug("socket closing: " + address.port);
socket.close();
self.socket = undefined;
}
});
socket.bind();
};
ZiBase.prototype.executeRemote = function (id, action) {
request.get("https://zibase.net/api/get/ZAPI.php?zibase=" + this.deviceId + "&token=" + this.token + "&service=execute&target=remote&id=" + id + "&action=" + action,
function (error, response, bodyString) {
if (error)
logger.error(error);
if (!/"head" : "success"/.test(bodyString)) {
logger.error("executeRemote(" + id + ") failed. Response=" + bodyString);
}
});
}