From ff286080e8c9c4004eea2a89fbf80376dfe60de9 Mon Sep 17 00:00:00 2001 From: Zaid Abdulla Date: Tue, 10 Sep 2013 15:54:09 +0300 Subject: [PATCH 01/11] Initial implementation of custom commands feature (e.g. telnet). --- acs.coffee | 7 ++++++ config.coffee | 2 ++ config/custom_commands-sample.json | 3 +++ customCommands.coffee | 17 ++++++++++++++ tasks.coffee | 36 ++++++++++++++++++++++++++++-- 5 files changed, 63 insertions(+), 2 deletions(-) create mode 100644 config/custom_commands-sample.json create mode 100644 customCommands.coffee diff --git a/acs.coffee b/acs.coffee index 4b66085..19ec12b 100644 --- a/acs.coffee +++ b/acs.coffee @@ -102,6 +102,13 @@ updateDevice = (currentRequest, actions, callback) -> updates["#{path}_writable"] = p[1] if p[1]? updates["#{path}_timestamp"] = now + if actions.customCommands? + for p in actions.customCommands + commandName = p[0] + updates["_customCommands.#{commandName}._value"] = p[1] + updates["_customCommands.#{commandName}._timestamp"] = now + updates["_customCommands.#{commandName}._commands"] = p[2] if p[2]? + if Object.keys(updates).length > 0 or Object.keys(deletes).length > 0 db.devicesCollection.update({'_id' : currentRequest.deviceId}, {'$set' : updates, '$unset' : deletes}, {}, (err, count) -> if (err) diff --git a/config.coffee b/config.coffee index 5c9e453..4b2781c 100644 --- a/config.coffee +++ b/config.coffee @@ -49,3 +49,5 @@ exports.ALIASES = aliases # load authentication scripts exports.auth = require('./config/auth') + +exports.CUSTOM_COMMANDS = require('./config/custom_commands') diff --git a/config/custom_commands-sample.json b/config/custom_commands-sample.json new file mode 100644 index 0000000..b9200a0 --- /dev/null +++ b/config/custom_commands-sample.json @@ -0,0 +1,3 @@ +{ + "seowon_frequencies" : "/^[0-9]+\\-SWU\\%2D9200\\-[0-9A-Z]+$/" +} diff --git a/customCommands.coffee b/customCommands.coffee new file mode 100644 index 0000000..6f7f3b3 --- /dev/null +++ b/customCommands.coffee @@ -0,0 +1,17 @@ +common = require './common' + +exports.execute = (deviceId, command, callback) -> + args = command.split(/\s+/) + f = args.shift() + c = args.shift() + + file = require "./config/custom_commands/#{f}" + file[c](deviceId, args.join(' '), callback) + + +exports.getCommands = (filename) -> + commands = [] + f = require("./config/custom_commands/#{filename}") + for k,v of f + commands.push(k) + return commands diff --git a/tasks.coffee b/tasks.coffee index 8f7f1a5..e833295 100644 --- a/tasks.coffee +++ b/tasks.coffee @@ -2,6 +2,7 @@ config = require './config' common = require './common' db = require './db' mongodb = require 'mongodb' +customCommands = require './customCommands' BATCH_SIZE = 16 exports.STATUS_QUEUED = STATUS_QUEUED = 0 @@ -11,12 +12,25 @@ exports.STATUS_FAULT = STATUS_FAULT = 3 exports.STATUS_FINISHED = STATUS_FINISHED = 4 +initCustomCommands = (deviceId, callback) -> + updates = {customCommands : []} + counter = 0 + for k,v of config.CUSTOM_COMMANDS + if eval(v).test(deviceId) + ++counter + customCommands.execute(deviceId, "#{k} init", (err, value) -> + updates.customCommands.push([k, value, customCommands.getCommands(k)]) + callback(updates) if --counter <= 0 + ) + callback(updates) if counter <= 0 + + this.init = (task, methodResponse, callback) -> if not task.subtask? task.subtask = {device : task.device, name : 'getParameterNames', parameterPath : '', nextLevel : false} if task.subtask.name == 'getParameterNames' - if methodResponse.faultcode? + if methodResponse.faultcode? task.fault = methodResponse callback(null, STATUS_FAULT) return @@ -42,7 +56,13 @@ this.init = (task, methodResponse, callback) -> # Ignore GetParameterValues errors. A workaround for the crappy Seewon devices. methodResponse = {parameterList : {}} this.getParameterValues(task.subtask, methodResponse, (err, status, cwmpResponse, deviceUpdates) -> - callback(err, status, cwmpResponse, deviceUpdates) + if status is STATUS_FINISHED + initCustomCommands(task.device, (updates) -> + common.extend(deviceUpdates, updates) + callback(err, status, cwmpResponse, deviceUpdates) + ) + else + callback(err, status, cwmpResponse, deviceUpdates) ) else throw Error('Unexpected subtask name') @@ -335,5 +355,17 @@ this.download = (task, methodResponse, callback) -> callback(null, STATUS_FINISHED) +this.customCommand = (task, methodResponse, callback) -> + # TODO implement timeout + customCommands.execute(task.device, task.command, (err, value) -> + if err? + task.fault = err + callback(null, STATUS_FAULT) + else + commandName = task.command.split(' ', 2)[0] + callback(null, STATUS_FINISHED, null, {customCommands : [[commandName, value]]}) + ) + + exports.task = (task, methodResponse, callback) -> this[task.name](task, methodResponse, callback) From bfd7d706488d460c19a483f926a4bfe6505a8f15 Mon Sep 17 00:00:00 2001 From: Zaid Abdulla Date: Tue, 10 Sep 2013 15:59:27 +0300 Subject: [PATCH 02/11] Added .gitignore file. --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0b5d77f --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +*~ +*.js +node_modules From 0bf912591383122eea91e5abebf705006a91202a Mon Sep 17 00:00:00 2001 From: Zaid Abdulla Date: Wed, 11 Sep 2013 10:41:09 +0300 Subject: [PATCH 03/11] Implemented preset support for custom commands. --- presets.coffee | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/presets.coffee b/presets.coffee index 10b12e1..9a58de3 100644 --- a/presets.coffee +++ b/presets.coffee @@ -75,6 +75,9 @@ exports.assertPresets = (deviceId, presetsHash, callback) -> switch c.type when 'value', 'age' projection[c.name] = 1 + when 'custom_command_value', 'custom_command_age' + # TODO only get individual commands + projection['_customCommands'] = 1 when 'firmware' projection['InternetGatewayDevice.DeviceInfo.SoftwareVersion'] = 1 when 'add_tag', 'delete_tag' @@ -120,6 +123,23 @@ exports.assertPresets = (deviceId, presetsHash, callback) -> getParameterValues.push(c.name) else expiry = Math.min(expiry, c.age - timeDiff) + when 'custom_command_value' + [filename, commandName] = c.command.split(' ', 2) + command = common.getParamValueFromPath(device, "_customCommands.#{filename}") + continue if not command? or commandName not in command._commands + dst = common.matchType(command._value, c.value) + if command._value != dst + taskList.push({device : deviceId, name : 'customCommand', command : c.command}) + when 'custom_command_age' + [filename, commandName] = c.command.split(' ', 2) + command = common.getParamValueFromPath(device, "_customCommands.#{filename}") + continue if not command? or commandName not in command._commands + timeDiff = (now - command._timestamp) / 1000 + if (c.age - timeDiff < config.PRESETS_TIME_PADDING) + expiry = Math.min(expiry, c.age) + taskList.push({device : deviceId, name : 'customCommand', command : c.command}) + else + expiry = Math.min(expiry, c.age - timeDiff) when 'add_tag' add_tags.push(c.tag) if not device['_tags']? or c.tag not in device['_tags'] when 'delete_tag' @@ -200,6 +220,7 @@ getObjectHash = (object) -> hash += "#{k}=#{object[k]}" return hash + accumulateConfigurations = (presets, objects) -> maxWeights = {} configurations = {} From 0a2da2fc70f520ba9be4fa4ddeaedbcb663a1120 Mon Sep 17 00:00:00 2001 From: Zaid Abdulla Date: Wed, 11 Sep 2013 11:48:14 +0300 Subject: [PATCH 04/11] Added command file for changing frequencies for Seowon devices. --- config/custom_commands/seowon_frequencies.js | 188 +++++++++++++++++++ 1 file changed, 188 insertions(+) create mode 100644 config/custom_commands/seowon_frequencies.js diff --git a/config/custom_commands/seowon_frequencies.js b/config/custom_commands/seowon_frequencies.js new file mode 100644 index 0000000..6a2d37e --- /dev/null +++ b/config/custom_commands/seowon_frequencies.js @@ -0,0 +1,188 @@ +// Change frequency plans for Seowon devices. +// Args format: 3532500 5000,3537500 5000,3542500 5000,3547500 5000,3552500 5000,3557500 5000,3505000 10000,3515000 10000,3525000 10000,3535000 10000,3545000 10000,3555000 10000 +var net = require('net'); +//var host = '192.168.1.1'; +var PORT = 23; +var USERNAME = 'admin'; +var PASSWORD = 'admin'; + +var FREQUENCIES_REGEX = /\s*([\d]+)\.\s*Frequency\s*:\s*([\d]+)\s\(KHz\),\s*Bandwidth\s*:\s*([\d\.]+)\s*\(MHz\)/gm + +var telnetConnect = function(host, callback) { + var client = net.connect(PORT, host, function() { + // enter username + telnetExecute(USERNAME, client, "Password: ", function(response) { + // enter password + telnetExecute(PASSWORD, client, "MT7109> ", function(response) { + callback(client); + }); + }); + }); + return client; +} + +var telnetExecute = function(command, connection, prompt, callback) { + command += "\n"; + var response = ''; + var listener = function(chunk) { + response += chunk.toString(); + if (response.slice(0 - prompt.length) == prompt) { + connection.removeListener('data', listener); + callback(response.slice(command.length, 0 - prompt.length)); + } + }; + + var end = function() { + connection.removeListener('end', end); + callback(response.slice(command.length)); + }; + + if (prompt) + connection.on('data', listener); + else + connection.on('end', end); + + if (command) + connection.write(command); +} + +var parseFrequencies = function(frequenciesString) { + var a = frequenciesString.split(',') + var l = []; + for (i in a) { + var sp = a[i].trim().split(' '); + var frequency = parseInt(sp[0]); + var bandwidth = parseFloat(sp[1]); + l.push([frequency, bandwidth]); + } + return l; +} + +var findFrequency = function(haystack, needle) { + for (i in haystack) { + if (haystack[i][0] == needle[0] && haystack[i][1] == needle[1]) + return i; + } + return -1; +} + +var getDeviceIp = function(deviceId, callback) { + var db = require('../../db'); + var URL = require('url'); + + db.devicesCollection.findOne({_id : deviceId}, {'InternetGatewayDevice.ManagementServer.ConnectionRequestURL._value' : 1}, function(err, device) { + var connectionRequestUrl = device.InternetGatewayDevice.ManagementServer.ConnectionRequestURL._value; + var url = URL.parse(connectionRequestUrl); + callback(url.hostname); + }); +}; + +exports.init = function(deviceId, args, callback) { + exports.get(deviceId, args, callback); +}; + +exports.get = function(deviceId, args, callback) { + var currentFrequencies = []; + + getDeviceIp(deviceId, function(ip) { + var client = telnetConnect(ip, function() { + // enter privileged mode + telnetExecute("enable", client, "MT7109# ", function(response) { + // show frequencies + telnetExecute("show wmx freq", client, "MT7109# ", function(response) { + while (f = FREQUENCIES_REGEX.exec(response)) { + var frequency = parseInt(f[2]); + var bandwidth = Math.floor(parseFloat(f[3]) * 1000); + var freqStr = String(frequency) + ' ' + String(bandwidth); + currentFrequencies.push(freqStr); + } + // exist privileged mode + telnetExecute("exit", client, "MT7109> ", function(response) { + // log out + telnetExecute("logout", client, null, function(response) { + callback(null, currentFrequencies.join(',')); + }); + }); + }); + }); + }); + }); +}; + +exports.set = function(deviceId, args, callback) { + var newFrequencies = parseFrequencies(args); + var addFrequencies = []; + var delFrequencies = []; + + getDeviceIp(deviceId, function(ip) { + var client = telnetConnect(ip, function() { + // enter privileged mode + telnetExecute("enable", client, "MT7109# ", function(response) { + // show frequencies + telnetExecute("show wmx freq", client, "MT7109# ", function(response) { + while (f = FREQUENCIES_REGEX.exec(response)) { + var index = parseInt(f[1]); + var frequency = parseInt(f[2]); + var bandwidth = Math.floor(parseFloat(f[3])); + + var i = findFrequency(newFrequencies, [frequency, bandwidth * 1000]); + if (i == -1) + delFrequencies.push(index); + else + newFrequencies.splice(i, 1); + } + for (f in newFrequencies) + addFrequencies.push(newFrequencies[f]); + + if (addFrequencies.length == 0 && delFrequencies.length == 0) { + // exit privileged mode + telnetExecute("exit", client, "MT7109> ", function(response) { + // log out + telnetExecute("logout", client, null, function(response) { + callback(null, args); + }); + }); + return; + } + + telnetExecute("wimax", client, "MT7109 (WiMax)# ", function(response) { + var add, del; + del = function() { + if (delFrequencies.length == 0) { + add(); + return; + } + var i = delFrequencies.pop(); + telnetExecute("wmx freq del " + i, client, "MT7109 (WiMax)# ", function(response) { + del(); + }); + }; + add = function() { + if (addFrequencies.length == 0) { + // commit + telnetExecute("commit", client, "MT7109 (WiMax)# ", function(response) { + // exist wimax mode + telnetExecute("exit", client, "MT7109# ", function(response) { + // exit privileged mode + telnetExecute("exit", client, "MT7109> ", function(response) { + // log out + telnetExecute("logout", client, null, function(response) { + callback(null, args); + }); + }); + }); + }); + return; + } + var i = addFrequencies.pop(); + telnetExecute("wmx freq add " + i[0] + ' ' + i[1]/1000, client, "MT7109 (WiMax)# ", function(response) { + add(); + }); + }; + del(); + }); + }); + }); + }); + }); +}; From 86ff19a6e2b14e81c62886b2b5a7243e1af706b5 Mon Sep 17 00:00:00 2001 From: Zaid Abdulla Date: Thu, 12 Sep 2013 15:15:39 +0300 Subject: [PATCH 05/11] Timeout handling for seowon_frequencies custom command. --- config/custom_commands/seowon_frequencies.js | 93 +++++++++++--------- 1 file changed, 52 insertions(+), 41 deletions(-) diff --git a/config/custom_commands/seowon_frequencies.js b/config/custom_commands/seowon_frequencies.js index 6a2d37e..3869b05 100644 --- a/config/custom_commands/seowon_frequencies.js +++ b/config/custom_commands/seowon_frequencies.js @@ -1,7 +1,6 @@ // Change frequency plans for Seowon devices. // Args format: 3532500 5000,3537500 5000,3542500 5000,3547500 5000,3552500 5000,3557500 5000,3505000 10000,3515000 10000,3525000 10000,3535000 10000,3545000 10000,3555000 10000 var net = require('net'); -//var host = '192.168.1.1'; var PORT = 23; var USERNAME = 'admin'; var PASSWORD = 'admin'; @@ -9,16 +8,23 @@ var PASSWORD = 'admin'; var FREQUENCIES_REGEX = /\s*([\d]+)\.\s*Frequency\s*:\s*([\d]+)\s\(KHz\),\s*Bandwidth\s*:\s*([\d\.]+)\s*\(MHz\)/gm var telnetConnect = function(host, callback) { - var client = net.connect(PORT, host, function() { + var client = net.connect(PORT, host, function() { // enter username - telnetExecute(USERNAME, client, "Password: ", function(response) { + telnetExecute(USERNAME, client, "Password: ", function(err, response) { + if (err) return callback(err, client); + if (!timeout) return; // enter password - telnetExecute(PASSWORD, client, "MT7109> ", function(response) { - callback(client); + telnetExecute(PASSWORD, client, "MT7109> ", function(err, response) { + if (!timeout) return; + clearTimeout(timeout); + return callback(err, client); }); }); }); - return client; + var timeout = setTimeout(function() { + timeout = null; + return callback('timeout', client); + }, 2000); } var telnetExecute = function(command, connection, prompt, callback) { @@ -27,16 +33,25 @@ var telnetExecute = function(command, connection, prompt, callback) { var listener = function(chunk) { response += chunk.toString(); if (response.slice(0 - prompt.length) == prompt) { + if (!timeout) return; + clearTimeout(timeout); connection.removeListener('data', listener); - callback(response.slice(command.length, 0 - prompt.length)); + return callback(null, response.slice(command.length, 0 - prompt.length)); } }; var end = function() { + if (!timeout) return; + clearTimeout(timeout); connection.removeListener('end', end); - callback(response.slice(command.length)); + return callback(null, response.slice(command.length)); }; + var timeout = setTimeout(function() { + timeout = null; + return callback('timeout', response); + }, 2000); + if (prompt) connection.on('data', listener); else @@ -73,35 +88,34 @@ var getDeviceIp = function(deviceId, callback) { db.devicesCollection.findOne({_id : deviceId}, {'InternetGatewayDevice.ManagementServer.ConnectionRequestURL._value' : 1}, function(err, device) { var connectionRequestUrl = device.InternetGatewayDevice.ManagementServer.ConnectionRequestURL._value; var url = URL.parse(connectionRequestUrl); - callback(url.hostname); + return callback(url.hostname); }); }; exports.init = function(deviceId, args, callback) { - exports.get(deviceId, args, callback); + return exports.get(deviceId, args, callback); }; exports.get = function(deviceId, args, callback) { var currentFrequencies = []; getDeviceIp(deviceId, function(ip) { - var client = telnetConnect(ip, function() { + telnetConnect(ip, function(err, client) { // enter privileged mode - telnetExecute("enable", client, "MT7109# ", function(response) { + telnetExecute("enable", client, "MT7109# ", function(err, response) { + if (err) return callback(err); // show frequencies - telnetExecute("show wmx freq", client, "MT7109# ", function(response) { + telnetExecute("show wmx freq", client, "MT7109# ", function(err, response) { + if (err) return callback(err); while (f = FREQUENCIES_REGEX.exec(response)) { var frequency = parseInt(f[2]); var bandwidth = Math.floor(parseFloat(f[3]) * 1000); var freqStr = String(frequency) + ' ' + String(bandwidth); currentFrequencies.push(freqStr); } - // exist privileged mode - telnetExecute("exit", client, "MT7109> ", function(response) { - // log out - telnetExecute("logout", client, null, function(response) { - callback(null, currentFrequencies.join(',')); - }); + // log out + telnetExecute("logout", client, null, function(err, response) { + return callback(err, currentFrequencies.join(',')); }); }); }); @@ -115,11 +129,13 @@ exports.set = function(deviceId, args, callback) { var delFrequencies = []; getDeviceIp(deviceId, function(ip) { - var client = telnetConnect(ip, function() { + telnetConnect(ip, function(err, client) { // enter privileged mode - telnetExecute("enable", client, "MT7109# ", function(response) { + telnetExecute("enable", client, "MT7109# ", function(err, response) { + if (err) return callback(err); // show frequencies - telnetExecute("show wmx freq", client, "MT7109# ", function(response) { + telnetExecute("show wmx freq", client, "MT7109# ", function(err, response) { + if (err) return callback(err); while (f = FREQUENCIES_REGEX.exec(response)) { var index = parseInt(f[1]); var frequency = parseInt(f[2]); @@ -135,17 +151,15 @@ exports.set = function(deviceId, args, callback) { addFrequencies.push(newFrequencies[f]); if (addFrequencies.length == 0 && delFrequencies.length == 0) { - // exit privileged mode - telnetExecute("exit", client, "MT7109> ", function(response) { - // log out - telnetExecute("logout", client, null, function(response) { - callback(null, args); - }); + // log out + telnetExecute("logout", client, null, function(err, response) { + return callback(err, args); }); return; } - telnetExecute("wimax", client, "MT7109 (WiMax)# ", function(response) { + telnetExecute("wimax", client, "MT7109 (WiMax)# ", function(err, response) { + if (err) return callback(err); var add, del; del = function() { if (delFrequencies.length == 0) { @@ -153,29 +167,26 @@ exports.set = function(deviceId, args, callback) { return; } var i = delFrequencies.pop(); - telnetExecute("wmx freq del " + i, client, "MT7109 (WiMax)# ", function(response) { + telnetExecute("wmx freq del " + i, client, "MT7109 (WiMax)# ", function(err, response) { + if (err) return callback(err); del(); }); }; add = function() { if (addFrequencies.length == 0) { // commit - telnetExecute("commit", client, "MT7109 (WiMax)# ", function(response) { - // exist wimax mode - telnetExecute("exit", client, "MT7109# ", function(response) { - // exit privileged mode - telnetExecute("exit", client, "MT7109> ", function(response) { - // log out - telnetExecute("logout", client, null, function(response) { - callback(null, args); - }); - }); + telnetExecute("commit", client, "MT7109 (WiMax)# ", function(err, response) { + if (err) return callback(err); + // log out + telnetExecute("logout", client, null, function(err, response) { + return callback(null, args); }); }); return; } var i = addFrequencies.pop(); - telnetExecute("wmx freq add " + i[0] + ' ' + i[1]/1000, client, "MT7109 (WiMax)# ", function(response) { + telnetExecute("wmx freq add " + i[0] + ' ' + i[1]/1000, client, "MT7109 (WiMax)# ", function(err, response) { + if (err) return callback(err); add(); }); }; From 9537bfad0f48a42d262bdd371319596c74e9dd6c Mon Sep 17 00:00:00 2001 From: Zaid Abdulla Date: Fri, 13 Sep 2013 13:02:44 +0300 Subject: [PATCH 06/11] Do not store available custom commands in device doc, find them from config. No longer having to reinitialize the device after adding a custom command. --- acs.coffee | 1 - ...mCommands.coffee => custom-commands.coffee | 12 +++++++++++- presets.coffee | 19 +++++++++++-------- tasks.coffee | 8 ++++---- 4 files changed, 26 insertions(+), 14 deletions(-) rename customCommands.coffee => custom-commands.coffee (58%) diff --git a/acs.coffee b/acs.coffee index 19ec12b..53d94ab 100644 --- a/acs.coffee +++ b/acs.coffee @@ -107,7 +107,6 @@ updateDevice = (currentRequest, actions, callback) -> commandName = p[0] updates["_customCommands.#{commandName}._value"] = p[1] updates["_customCommands.#{commandName}._timestamp"] = now - updates["_customCommands.#{commandName}._commands"] = p[2] if p[2]? if Object.keys(updates).length > 0 or Object.keys(deletes).length > 0 db.devicesCollection.update({'_id' : currentRequest.deviceId}, {'$set' : updates, '$unset' : deletes}, {}, (err, count) -> diff --git a/customCommands.coffee b/custom-commands.coffee similarity index 58% rename from customCommands.coffee rename to custom-commands.coffee index 6f7f3b3..314fa03 100644 --- a/customCommands.coffee +++ b/custom-commands.coffee @@ -1,5 +1,7 @@ +config = require './config' common = require './common' + exports.execute = (deviceId, command, callback) -> args = command.split(/\s+/) f = args.shift() @@ -9,9 +11,17 @@ exports.execute = (deviceId, command, callback) -> file[c](deviceId, args.join(' '), callback) -exports.getCommands = (filename) -> +exports.getFileCommands = (filename) -> commands = [] f = require("./config/custom_commands/#{filename}") for k,v of f commands.push(k) return commands + + +exports.getDeviceCustomCommands = (deviceId) -> + commands = {} + for k,v of config.CUSTOM_COMMANDS + if eval(v).test(deviceId) + commands[k] = exports.getFileCommands(k) + return commands diff --git a/presets.coffee b/presets.coffee index 9a58de3..9d62c0c 100644 --- a/presets.coffee +++ b/presets.coffee @@ -4,6 +4,7 @@ db = require './db' mongoQuery = require './mongo-query' query = require './query' log = require('util').log +customCommands = require './custom-commands' getPresets = (callback) -> @@ -65,6 +66,7 @@ matchObject = (object, param) -> exports.assertPresets = (deviceId, presetsHash, callback) -> getPresets((presets, objects) -> + deviceCustomCommands = customCommands.getDeviceCustomCommands(deviceId) # only fetch relevant params projection = {_id : 1} for p in presets @@ -76,8 +78,9 @@ exports.assertPresets = (deviceId, presetsHash, callback) -> when 'value', 'age' projection[c.name] = 1 when 'custom_command_value', 'custom_command_age' - # TODO only get individual commands - projection['_customCommands'] = 1 + commandfile = c.command.split(' ', 1) + if commandfile of deviceCustomCommands + projection["_customCommands.#{c.command.split(' ', 1)}"] = 1 when 'firmware' projection['InternetGatewayDevice.DeviceInfo.SoftwareVersion'] = 1 when 'add_tag', 'delete_tag' @@ -125,16 +128,16 @@ exports.assertPresets = (deviceId, presetsHash, callback) -> expiry = Math.min(expiry, c.age - timeDiff) when 'custom_command_value' [filename, commandName] = c.command.split(' ', 2) - command = common.getParamValueFromPath(device, "_customCommands.#{filename}") - continue if not command? or commandName not in command._commands - dst = common.matchType(command._value, c.value) - if command._value != dst + continue if not deviceCustomCommands[filename]? or commandName not in deviceCustomCommands[filename] + currentValue = try common.getParamValueFromPath(device, "_customCommands.#{filename}")._value catch then undefined + dst = common.matchType(currentValue, c.value) + if currentValue != dst taskList.push({device : deviceId, name : 'customCommand', command : c.command}) when 'custom_command_age' [filename, commandName] = c.command.split(' ', 2) + continue if not deviceCustomCommands[filename]? or commandName not in deviceCustomCommands[filename] command = common.getParamValueFromPath(device, "_customCommands.#{filename}") - continue if not command? or commandName not in command._commands - timeDiff = (now - command._timestamp) / 1000 + timeDiff = try (now - command._timestamp) / 1000 catch then 9999999 if (c.age - timeDiff < config.PRESETS_TIME_PADDING) expiry = Math.min(expiry, c.age) taskList.push({device : deviceId, name : 'customCommand', command : c.command}) diff --git a/tasks.coffee b/tasks.coffee index e833295..1fc7994 100644 --- a/tasks.coffee +++ b/tasks.coffee @@ -2,7 +2,7 @@ config = require './config' common = require './common' db = require './db' mongodb = require 'mongodb' -customCommands = require './customCommands' +customCommands = require './custom-commands' BATCH_SIZE = 16 exports.STATUS_QUEUED = STATUS_QUEUED = 0 @@ -15,11 +15,11 @@ exports.STATUS_FINISHED = STATUS_FINISHED = 4 initCustomCommands = (deviceId, callback) -> updates = {customCommands : []} counter = 0 - for k,v of config.CUSTOM_COMMANDS - if eval(v).test(deviceId) + for k,v of customCommands.getDeviceCustomCommands(deviceId) + if 'init' in v ++counter customCommands.execute(deviceId, "#{k} init", (err, value) -> - updates.customCommands.push([k, value, customCommands.getCommands(k)]) + updates.customCommands.push([k, value]) callback(updates) if --counter <= 0 ) callback(updates) if counter <= 0 From c72705d77718ad4ca050012135388fee0e7c1391 Mon Sep 17 00:00:00 2001 From: Zaid Abdulla Date: Fri, 13 Sep 2013 16:23:51 +0300 Subject: [PATCH 07/11] Added task status code to force saving task. Used in addObject task to avoid data inconsistency and crashing. --- acs.coffee | 6 ++++++ tasks.coffee | 10 ++++++---- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/acs.coffee b/acs.coffee index 53d94ab..6821ccc 100644 --- a/acs.coffee +++ b/acs.coffee @@ -180,6 +180,12 @@ runTask = (currentRequest, task, methodResponse) -> res = tr069.response(task._id, cwmpResponse) writeResponse(currentRequest, res) ) + when tasks.STATUS_SAVE + db.saveTask(task, (err) -> + throw new Error(err) if err? + res = tr069.response(task._id, cwmpResponse) + writeResponse(currentRequest, res) + ) else throw Error('Unknown task status') ) diff --git a/tasks.coffee b/tasks.coffee index 1fc7994..a710f28 100644 --- a/tasks.coffee +++ b/tasks.coffee @@ -7,9 +7,10 @@ BATCH_SIZE = 16 exports.STATUS_QUEUED = STATUS_QUEUED = 0 exports.STATUS_STARTED = STATUS_STARTED = 1 -exports.STATUS_PENDING = STATUS_PENDING = 2 -exports.STATUS_FAULT = STATUS_FAULT = 3 -exports.STATUS_FINISHED = STATUS_FINISHED = 4 +exports.STATUS_SAVE = STATUS_SAVE = 2 +exports.STATUS_PENDING = STATUS_PENDING = 3 +exports.STATUS_FAULT = STATUS_FAULT = 4 +exports.STATUS_FINISHED = STATUS_FINISHED = 5 initCustomCommands = (deviceId, callback) -> @@ -237,7 +238,8 @@ this.addObject = (task, methodResponse, callback) -> task.parameterNames = [] allDeviceUpdates.instanceName = [["#{task.objectName}.#{task.instanceNumber}", task.instanceName]] if task.instanceName? else - callback(null, STATUS_STARTED, {methodRequest : {type : 'AddObject', objectName : "#{task.objectName}."}}) + # Use STATUSS_SAVE to avoid adding object again in case of failure + callback(null, STATUS_SAVE, {methodRequest : {type : 'AddObject', objectName : "#{task.objectName}."}}) return subtask = () => From 82e7c60a5b4b2e66612929e0f6fa41eaf6cb3fe8 Mon Sep 17 00:00:00 2001 From: Zaid Abdulla Date: Fri, 13 Sep 2013 18:29:13 +0300 Subject: [PATCH 08/11] Fixed possible bug caused by not clearing task cache when saving. --- db.coffee | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/db.coffee b/db.coffee index be9d80d..d6bbb78 100644 --- a/db.coffee +++ b/db.coffee @@ -72,9 +72,14 @@ updateTask = (task, callback) -> saveTask = (task, callback) -> - task._id = mongodb.ObjectID(String(task._id)) + # task ID can either be a string or a MongoDB ID + id = String(task._id) + task._id = mongodb.ObjectID(id) tasksCollection.save(task, (err) -> - callback(err) + throw new Error(err) if err + memcached.del(id, (err, res) -> + callback(err) + ) ) From 7828142391c7a2504d8d66534adad0c00fb46cf6 Mon Sep 17 00:00:00 2001 From: Zaid Abdulla Date: Fri, 13 Sep 2013 19:45:00 +0300 Subject: [PATCH 09/11] Return error instead of throwing exception when saving task. --- db.coffee | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/db.coffee b/db.coffee index d6bbb78..c93c2be 100644 --- a/db.coffee +++ b/db.coffee @@ -75,10 +75,9 @@ saveTask = (task, callback) -> # task ID can either be a string or a MongoDB ID id = String(task._id) task._id = mongodb.ObjectID(id) - tasksCollection.save(task, (err) -> - throw new Error(err) if err - memcached.del(id, (err, res) -> - callback(err) + tasksCollection.save(task, (mongoErr) -> + memcached.del(id, (memcachedErr, res) -> + callback(mongoErr or memcachedErr) ) ) From 6b359c8dfbdb126508e936964bffb8fbb531834f Mon Sep 17 00:00:00 2001 From: Zaid Abdulla Date: Sun, 15 Sep 2013 22:35:35 +0300 Subject: [PATCH 10/11] Fixed saving task in addObject. Should be saved after receiving instance number rather than before. --- tasks.coffee | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tasks.coffee b/tasks.coffee index a710f28..c335eb7 100644 --- a/tasks.coffee +++ b/tasks.coffee @@ -238,8 +238,7 @@ this.addObject = (task, methodResponse, callback) -> task.parameterNames = [] allDeviceUpdates.instanceName = [["#{task.objectName}.#{task.instanceNumber}", task.instanceName]] if task.instanceName? else - # Use STATUSS_SAVE to avoid adding object again in case of failure - callback(null, STATUS_SAVE, {methodRequest : {type : 'AddObject', objectName : "#{task.objectName}."}}) + callback(null, STATUS_STARTED, {methodRequest : {type : 'AddObject', objectName : "#{task.objectName}."}}) return subtask = () => @@ -255,7 +254,11 @@ this.addObject = (task, methodResponse, callback) -> task.subtask = {name : 'getParameterValues', parameterNames : task.parameterNames} subtask() else if status is STATUS_STARTED - callback(err, STATUS_STARTED, cwmpResponse, allDeviceUpdates) + if allDeviceUpdates.instanceName? + # Use STATUSS_SAVE to avoid adding duplicate object in case of error + callback(err, STATUS_SAVE, cwmpResponse, allDeviceUpdates) + else + callback(err, STATUS_STARTED, cwmpResponse, allDeviceUpdates) else throw Error('Unexpected subtask status') ) From a7d75b42f6fbcaee6ae226267d3c2c8d0779fbb8 Mon Sep 17 00:00:00 2001 From: Zaid Abdulla Date: Sun, 15 Sep 2013 23:11:07 +0300 Subject: [PATCH 11/11] Increase telnet timeout for seowon frequencies command. --- config/custom_commands/seowon_frequencies.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/custom_commands/seowon_frequencies.js b/config/custom_commands/seowon_frequencies.js index 3869b05..e39554c 100644 --- a/config/custom_commands/seowon_frequencies.js +++ b/config/custom_commands/seowon_frequencies.js @@ -24,7 +24,7 @@ var telnetConnect = function(host, callback) { var timeout = setTimeout(function() { timeout = null; return callback('timeout', client); - }, 2000); + }, 4000); } var telnetExecute = function(command, connection, prompt, callback) {