//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); } }); }