Scripting in presets
User defined scripts can now be used in presets allowing dynamic values and complex provisioning workflow.
This commit is contained in:
1 parent
ba33fc0fe0
commit
48d9adbaff
7 files changed
+441
-117
No files matched your search
@@ -0,0 +1,175 @@
|
||||
###
|
||||
# Copyright 2013-2016 Zaid Abdulla
|
||||
#
|
||||
# GenieACS is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# GenieACS is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with GenieACS. If not, see <http://www.gnu.org/licenses/>.
|
||||
###
|
||||
|
||||
vm = require 'vm'
|
||||
crypto = require 'crypto'
|
||||
|
||||
db = require './db'
|
||||
|
||||
REFRESH = 3000
|
||||
|
||||
nextRefresh = 0
|
||||
hash = null
|
||||
presets = null
|
||||
scripts = null
|
||||
|
||||
|
||||
UNLOCK_SCRIPT = 'if redis.call("get", KEYS[1]) == ARGV[1] then return redis.call("del", KEYS[1]) else return 0 end'
|
||||
EXTEND_SCRIPT = 'if redis.call("get", KEYS[1]) == ARGV[1] then return redis.call("pexpire", KEYS[1], ARGV[2]) else return 0 end'
|
||||
|
||||
lock = (lockName, ttl, callback) ->
|
||||
token = Math.random().toString(36).slice(2)
|
||||
unlockOrExtend = (ttl) ->
|
||||
if not ttl
|
||||
db.redisClient.eval(UNLOCK_SCRIPT, 1, lockName, token, (err, res) ->
|
||||
throw err or new Error('Lock expired') if err or not res
|
||||
)
|
||||
else
|
||||
db.redisClient.eval(EXTEND_SCRIPT, 1, lockName, token, ttl, (err, res) ->
|
||||
throw err or new Error('Lock expired') if err or not res
|
||||
)
|
||||
return
|
||||
|
||||
db.redisClient.set(lockName, token, 'NX', 'PX', ttl, (err, res) ->
|
||||
if err or res
|
||||
return callback(err, unlockOrExtend)
|
||||
|
||||
setTimeout(() ->
|
||||
return lock(lockName, ttl, callback)
|
||||
, 200)
|
||||
)
|
||||
|
||||
|
||||
computeHash = () ->
|
||||
# MD5 hash for presets and scripts for detecting changes
|
||||
h = crypto.createHash('md5')
|
||||
for p in presets
|
||||
h.update(JSON.stringify(p.name))
|
||||
h.update(JSON.stringify(p.precondition))
|
||||
h.update(JSON.stringify(p.provisions))
|
||||
|
||||
for s in Object.keys(scripts).sort()
|
||||
h.update(JSON.stringify(s))
|
||||
h.update(JSON.stringify(scripts[s].source))
|
||||
|
||||
return h.digest('hex')
|
||||
|
||||
|
||||
refresh = (callback) ->
|
||||
now = Date.now()
|
||||
db.redisClient.get('presets_hash', (err, res) ->
|
||||
return callback(err) if err
|
||||
|
||||
if hash? and res == hash
|
||||
nextRefresh = now + REFRESH
|
||||
return callback()
|
||||
|
||||
lock('presets_hash_lock', 3000, (err, unlockOrExtend) ->
|
||||
counter = 2
|
||||
db.presetsCollection.find().toArray((err, res) ->
|
||||
if err
|
||||
callback(err) if -- counter >= 0
|
||||
counter = 0
|
||||
return
|
||||
|
||||
res.sort((a, b) ->
|
||||
if a.weight == b.weight
|
||||
return a._id > b._id
|
||||
else
|
||||
return a.weight - b.weight
|
||||
)
|
||||
|
||||
presets = []
|
||||
|
||||
for preset in res
|
||||
precondition = JSON.parse(preset.precondition)
|
||||
|
||||
provisions = preset.provisions or []
|
||||
|
||||
# Generate provisions from the old configuration format
|
||||
for c in preset.configurations
|
||||
switch c.type
|
||||
when 'age'
|
||||
provisions.push(['refresh', c.name, c.age * -1000])
|
||||
when 'value'
|
||||
provisions.push(['value', c.name, c.value])
|
||||
when 'add_tag'
|
||||
provisions.push(['tag', c.tag, true])
|
||||
when 'delete_tag'
|
||||
provisions.push(['tag', c.tag, false])
|
||||
when 'script'
|
||||
provisions.push([c.name].concat(c.args or []))
|
||||
else
|
||||
throw new Error("Unknown configuration type #{c.type}")
|
||||
|
||||
presets.push({name: preset._id, precondition: precondition, provisions: provisions})
|
||||
|
||||
if -- counter == 0
|
||||
computeHash()
|
||||
db.redisClient.setex("presets_hash", 300, hash, (err) ->
|
||||
unlockOrExtend(0)
|
||||
nextRefresh = now + REFRESH
|
||||
return callback()
|
||||
)
|
||||
)
|
||||
|
||||
db.scriptsCollection.find().toArray((err, res) ->
|
||||
if err
|
||||
callback(err) if -- counter >= 0
|
||||
counter = 0
|
||||
return
|
||||
|
||||
scripts = {}
|
||||
for r in res
|
||||
scripts[r._id] = {}
|
||||
scripts[r._id].source = r.source
|
||||
scripts[r._id].md5 = r.md5
|
||||
scripts[r._id].compiled = vm.Script(r.source, {filename: r._id, timeout: 50})
|
||||
|
||||
if -- counter == 0
|
||||
computeHash()
|
||||
db.redisClient.setex("presets_hash", 300, hash, (err) ->
|
||||
unlockOrExtend(0)
|
||||
nextRefresh = now + REFRESH
|
||||
return callback()
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
|
||||
getPresets = (callback) ->
|
||||
if Date.now() < nextRefresh
|
||||
return callback(null, hash, presets)
|
||||
|
||||
refresh((err) ->
|
||||
return callback(err, hash, presets)
|
||||
)
|
||||
|
||||
|
||||
getScripts = (callback) ->
|
||||
if Date.now() < nextRefresh
|
||||
return callback(null, hash, scripts)
|
||||
|
||||
refresh((err) ->
|
||||
return callback(err, hash, scripts)
|
||||
)
|
||||
|
||||
|
||||
exports.getPresets = getPresets
|
||||
exports.getScripts = getScripts
|
||||
+10
-3
@@ -44,10 +44,10 @@ zlib = require 'zlib'
|
||||
config = require './config'
|
||||
common = require './common'
|
||||
soap = require './soap'
|
||||
db = require './db'
|
||||
session = require './session'
|
||||
query = require './query'
|
||||
device = require './device'
|
||||
cache = require './cache'
|
||||
|
||||
|
||||
writeResponse = (currentRequest, res) ->
|
||||
@@ -104,7 +104,11 @@ inform = (currentRequest, cwmpRequest) ->
|
||||
|
||||
|
||||
applyPresets = (currentRequest) ->
|
||||
db.getPresetsObjectsAliases((presets, objects, aliases) ->
|
||||
cache.getPresets((err, presetsHash, presets) ->
|
||||
throw err if err
|
||||
|
||||
currentRequest.sessionData.presetsHash = presetsHash
|
||||
|
||||
parameters = {}
|
||||
for p in presets
|
||||
for a in Object.keys(query.queryProjection(p.precondition))
|
||||
@@ -126,10 +130,12 @@ applyPresets = (currentRequest) ->
|
||||
|
||||
for p in presets
|
||||
if query.test(parameters, p.precondition)
|
||||
session.addPreset(currentRequest.sessionData, p._id, p)
|
||||
session.addProvisions(currentRequest.sessionData, p.provisions)
|
||||
|
||||
session.rpcRequest(currentRequest.sessionData, null, (err, id, rpcRequest) ->
|
||||
throw err if err
|
||||
if not rpcRequest?
|
||||
session.clearProvisions(currentRequest.sessionData)
|
||||
sendRpcRequest(currentRequest, id, rpcRequest)
|
||||
)
|
||||
)
|
||||
@@ -140,6 +146,7 @@ nextRpc = (currentRequest) ->
|
||||
session.rpcRequest(currentRequest.sessionData, null, (err, id, rpcRequest) ->
|
||||
throw err if err
|
||||
if not rpcRequest?
|
||||
session.clearProvisions(currentRequest.sessionData)
|
||||
return applyPresets(currentRequest)
|
||||
|
||||
sendRpcRequest(currentRequest, id, rpcRequest)
|
||||
|
||||
+9
-1
@@ -26,10 +26,11 @@ tasksCollection = null
|
||||
devicesCollection = null
|
||||
presetsCollection = null
|
||||
objectsCollection = null
|
||||
scriptsCollection = null
|
||||
|
||||
|
||||
connect = (callback) ->
|
||||
callbackCounter = 6
|
||||
callbackCounter = 7
|
||||
mongodb.MongoClient.connect(config.get('MONGODB_CONNECTION_URL'), {db:{w:1},server:{autoReconnect:true}}, (err, db) ->
|
||||
return callback(err) if err
|
||||
exports.mongoDb = db
|
||||
@@ -71,6 +72,13 @@ connect = (callback) ->
|
||||
return callback(err)
|
||||
)
|
||||
|
||||
db.collection('scripts', (err, collection) ->
|
||||
exports.scriptsCollection = scriptsCollection = collection
|
||||
if --callbackCounter == 0 or err
|
||||
callbackCounter = 0
|
||||
return callback(err)
|
||||
)
|
||||
|
||||
exports.redisClient = redisClient = redis.createClient(config.get('REDIS_PORT'), config.get('REDIS_HOST'))
|
||||
redisClient.select(config.get('REDIS_DB'), (err) ->
|
||||
if --callbackCounter == 0 or err
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
url = require 'url'
|
||||
mongodb = require 'mongodb'
|
||||
querystring = require 'querystring'
|
||||
crypto = require 'crypto'
|
||||
|
||||
config = require './config'
|
||||
common = require './common'
|
||||
@@ -58,6 +59,7 @@ FILES_REGEX = /^\/files\/([a-zA-Z0-9\%\!\*\'\(\)\;\:\@\&\=\+\$\,\?\#\[\]\-\_\.\~
|
||||
PING_REGEX = /^\/ping\/([a-zA-Z0-9\-\_\.]+)\/?$/
|
||||
QUERY_REGEX = /^\/([a-zA-Z0-9_]+s)\/?$/
|
||||
DELETE_DEVICE_REGEX = /^\/devices\/([a-zA-Z0-9\-\_\%]+)\/?$/
|
||||
SCRIPTS_REGEX = /^\/scripts\/([a-zA-Z0-9\-\_\%]+)\/?$/
|
||||
|
||||
|
||||
errorToString = (err) ->
|
||||
@@ -149,6 +151,41 @@ listener = (request, response) ->
|
||||
else
|
||||
response.writeHead 405, {'Allow': 'PUT, DELETE'}
|
||||
response.end('405 Method Not Allowed')
|
||||
else if SCRIPTS_REGEX.test(urlParts.pathname)
|
||||
scriptName = querystring.unescape(SCRIPTS_REGEX.exec(urlParts.pathname)[1])
|
||||
if request.method == 'PUT'
|
||||
object = {
|
||||
_id: scriptName
|
||||
source: body.toString()
|
||||
md5: crypto.createHash('md5').update(body).digest('hex')
|
||||
}
|
||||
|
||||
db.scriptsCollection.save(object, (err) ->
|
||||
db.redisClient.del('presets_hash', (err) ->
|
||||
throw err if err
|
||||
)
|
||||
if err
|
||||
response.writeHead(500)
|
||||
response.end(errorToString(err))
|
||||
return
|
||||
response.writeHead(200)
|
||||
response.end()
|
||||
)
|
||||
else if request.method == 'DELETE'
|
||||
db.scriptsCollection.remove({'_id' : scriptName}, (err) ->
|
||||
db.redisClient.del('presets_hash', (err) ->
|
||||
throw err if err
|
||||
)
|
||||
if err
|
||||
response.writeHead(500)
|
||||
response.end(errorToString(err))
|
||||
return
|
||||
response.writeHead(200)
|
||||
response.end()
|
||||
)
|
||||
else
|
||||
response.writeHead 405, {'Allow': 'PUT, DELETE'}
|
||||
response.end('405 Method Not Allowed')
|
||||
else if TAGS_REGEX.test(urlParts.pathname)
|
||||
r = TAGS_REGEX.exec(urlParts.pathname)
|
||||
deviceId = querystring.unescape(r[1])
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
###
|
||||
# Copyright 2013-2016 Zaid Abdulla
|
||||
#
|
||||
# GenieACS is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# GenieACS is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with GenieACS. If not, see <http://www.gnu.org/licenses/>.
|
||||
###
|
||||
|
||||
common = require './common'
|
||||
|
||||
|
||||
processProvision = (type, args, callback) ->
|
||||
switch type
|
||||
when 'refresh'
|
||||
declarations = []
|
||||
path = common.parsePath(args[0])
|
||||
declaration = [args[1], null, null, null, null, null, args[1]]
|
||||
for i in [path.length...14] by 1
|
||||
path = path.slice()
|
||||
path.length = i
|
||||
declarations.push([path].concat(declaration))
|
||||
|
||||
return callback(null, declarations)
|
||||
|
||||
when 'value'
|
||||
declarations = [[common.parsePath(args[0]), args[1], null, null, null, null, null, args[1]]]
|
||||
return callback(null, declarations)
|
||||
|
||||
when 'tag'
|
||||
declarations = [[['Tags', args[0]], null, null, null, null, null, null, null, [args[1], 'xsd:boolean']]]
|
||||
return callback(null, declarations)
|
||||
|
||||
else
|
||||
return callback(new Error("Unknown provision type '#{type}'"))
|
||||
|
||||
|
||||
exports.processProvision = processProvision
|
||||
@@ -0,0 +1,121 @@
|
||||
###
|
||||
# Copyright 2013-2016 Zaid Abdulla
|
||||
#
|
||||
# GenieACS is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# GenieACS is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with GenieACS. If not, see <http://www.gnu.org/licenses/>.
|
||||
###
|
||||
|
||||
vm = require 'vm'
|
||||
|
||||
common = require './common'
|
||||
device = require './device'
|
||||
|
||||
|
||||
# Used for throwing to exit user script
|
||||
class Declare;
|
||||
|
||||
|
||||
sandbox = {
|
||||
deviceData: null
|
||||
args: null
|
||||
revision: null
|
||||
maxRevision: null
|
||||
declarations: null
|
||||
context: vm.createContext()
|
||||
}
|
||||
|
||||
sandbox.context.declare = (decs) ->
|
||||
if ++ sandbox.revision > sandbox.maxRevision + 1
|
||||
throw new Error('Declare function should not be called from within a try/catch block')
|
||||
|
||||
sandbox.declarations = decs
|
||||
|
||||
if sandbox.revision == sandbox.maxRevision + 1
|
||||
throw new Declare()
|
||||
|
||||
res = {}
|
||||
for k, v of decs
|
||||
path = common.parsePath(k)
|
||||
all = device.getAll(sandbox.deviceData, path, sandbox.revision)
|
||||
res[k] = {}
|
||||
for a in all
|
||||
r = {}
|
||||
r.exist = a[1] if v.exist?
|
||||
r.type = a.slice(3, 5) if v.type?
|
||||
r.writable = a.slice(5, 7) if v.writable?
|
||||
r.value = a.slice(7, 9) if v.value? and a[8]?
|
||||
|
||||
p = a[0].join('.')
|
||||
if p == k
|
||||
res[k] = r
|
||||
else
|
||||
res[k][p] = r
|
||||
|
||||
return res
|
||||
|
||||
Object.freeze(sandbox.context)
|
||||
|
||||
|
||||
# Convert from friendly declaration format
|
||||
toIndexDeclaration = (keyDeclaraiton) ->
|
||||
indexDeclaraiton = []
|
||||
|
||||
if keyDeclaraiton['exist']?
|
||||
if Array.isArray(keyDeclaraiton['exist'])
|
||||
indexDeclaraiton[0] = keyDeclaraiton['exist'][0]
|
||||
else
|
||||
indexDeclaraiton[0] = keyDeclaraiton['exist']
|
||||
|
||||
if keyDeclaraiton['type']?
|
||||
if Array.isArray(keyDeclaraiton['type'])
|
||||
indexDeclaraiton[2] = keyDeclaraiton['type'][0]
|
||||
else
|
||||
indexDeclaraiton[2] = keyDeclaraiton['type']
|
||||
|
||||
if keyDeclaraiton['writable']?
|
||||
if Array.isArray(keyDeclaraiton['writable'])
|
||||
indexDeclaraiton[4] = keyDeclaraiton['writable'][0]
|
||||
else
|
||||
indexDeclaraiton[4] = keyDeclaraiton['writable']
|
||||
|
||||
if keyDeclaraiton['value']?
|
||||
if Array.isArray(keyDeclaraiton['value'])
|
||||
indexDeclaraiton[6] = keyDeclaraiton['value'][0]
|
||||
indexDeclaraiton[7] = keyDeclaraiton['value'][1]
|
||||
else
|
||||
indexDeclaraiton[6] = keyDeclaraiton['value']
|
||||
|
||||
return indexDeclaraiton
|
||||
|
||||
|
||||
run = (script, args, deviceData, revision, declarations) ->
|
||||
sandbox.deviceData = deviceData
|
||||
sandbox.args = args
|
||||
sandbox.maxRevision = revision
|
||||
sandbox.revision = 0
|
||||
sandbox.declarations = []
|
||||
|
||||
try
|
||||
script.runInNewContext(sandbox.context)
|
||||
catch err
|
||||
throw err if err not instanceof Declare
|
||||
for k, v of sandbox.declarations
|
||||
declarations.push([common.parsePath(k)].concat(toIndexDeclaration(v)))
|
||||
return true
|
||||
|
||||
for k, v of sandbox.declarations
|
||||
declarations.push([common.parsePath(k)].concat(toIndexDeclaration(v)))
|
||||
return false
|
||||
|
||||
|
||||
exports.run = run
|
||||
+89
-67
@@ -21,7 +21,8 @@ config = require './config'
|
||||
common = require './common'
|
||||
db = require './db'
|
||||
device = require './device'
|
||||
provisions = require './provisions'
|
||||
sandbox = require './sandbox'
|
||||
cache = require './cache'
|
||||
|
||||
|
||||
init = (deviceId, cwmpVersion, timeout) ->
|
||||
@@ -35,8 +36,8 @@ init = (deviceId, cwmpVersion, timeout) ->
|
||||
deviceData : deviceData
|
||||
cwmpVersion : cwmpVersion
|
||||
timeout : timeout
|
||||
presets : {}
|
||||
rpcCount : 0
|
||||
provisions: []
|
||||
counter : [0, 0, 0] # rpc, revision, batch
|
||||
}
|
||||
|
||||
return sessionData
|
||||
@@ -65,26 +66,47 @@ loadParameters = (sessionData, toLoad, callback) ->
|
||||
)
|
||||
|
||||
|
||||
inform = (sessionData, rpcRequest, callback) ->
|
||||
timestamp = sessionData.timestamp
|
||||
device.set(sessionData.deviceData, ['DeviceID', 'Manufacturer'], 1, [timestamp, 1, timestamp, 0, timestamp, 0, timestamp, [rpcRequest.deviceId.Manufacturer, 'xsd:string']])
|
||||
device.set(sessionData.deviceData, ['DeviceID', 'OUI'], 1, [timestamp, 1, timestamp, 0, timestamp, 0, timestamp, [rpcRequest.deviceId.OUI, 'xsd:string']])
|
||||
device.set(sessionData.deviceData, ['DeviceID', 'ProductClass'], 1, [timestamp, 1, timestamp, 0, timestamp, 0, timestamp, [rpcRequest.deviceId.ProductClass, 'xsd:string']])
|
||||
device.set(sessionData.deviceData, ['DeviceID', 'SerialNumber'], 1, [timestamp, 1, timestamp, 0, timestamp, 0, timestamp, [rpcRequest.deviceId.SerialNumber, 'xsd:string']])
|
||||
generateRpcId = (sessionData) ->
|
||||
if sessionData.counter[0] > 255 or sessionData.counter[1] > 15 or sessionData.counter[2] > 15
|
||||
throw new Error('Too many RPCs')
|
||||
|
||||
for p in rpcRequest.parameterList
|
||||
return sessionData.counter[2].toString(16).slice(-1) +
|
||||
sessionData.counter[1].toString(16).slice(-1) +
|
||||
('0' + sessionData.counter[0].toString(16)).slice(-2)
|
||||
|
||||
|
||||
inform = (sessionData, rpcReq, callback) ->
|
||||
timestamp = sessionData.timestamp
|
||||
device.set(sessionData.deviceData, ['DeviceID', 'Manufacturer'], 1, [timestamp, 1, timestamp, 0, timestamp, 0, timestamp, [rpcReq.deviceId.Manufacturer, 'xsd:string']])
|
||||
device.set(sessionData.deviceData, ['DeviceID', 'OUI'], 1, [timestamp, 1, timestamp, 0, timestamp, 0, timestamp, [rpcReq.deviceId.OUI, 'xsd:string']])
|
||||
device.set(sessionData.deviceData, ['DeviceID', 'ProductClass'], 1, [timestamp, 1, timestamp, 0, timestamp, 0, timestamp, [rpcReq.deviceId.ProductClass, 'xsd:string']])
|
||||
device.set(sessionData.deviceData, ['DeviceID', 'SerialNumber'], 1, [timestamp, 1, timestamp, 0, timestamp, 0, timestamp, [rpcReq.deviceId.SerialNumber, 'xsd:string']])
|
||||
|
||||
for p in rpcReq.parameterList
|
||||
device.set(sessionData.deviceData, common.parsePath(p[0]), 1, [timestamp, 1, timestamp, 0, null, null, timestamp, p.slice(1)])
|
||||
|
||||
device.set(sessionData.deviceData, ['Events', 'Inform'], 1, [timestamp, 1, timestamp, 0, timestamp, 0, timestamp, [timestamp, 'xsd:dateTime']])
|
||||
|
||||
for e in rpcRequest.event
|
||||
for e in rpcReq.event
|
||||
device.set(sessionData.deviceData, ['Events', e.replace(' ', '_')], 1, [timestamp, 1, timestamp, 0, timestamp, 0, timestamp, [timestamp, 'xsd:dateTime']])
|
||||
|
||||
return callback(null, {type : 'InformResponse'})
|
||||
|
||||
|
||||
addPreset = (sessionData, name, preset) ->
|
||||
sessionData.presets[name] = preset
|
||||
addProvisions = (sessionData, provisions) ->
|
||||
sessionData.provisions = sessionData.provisions.concat(provisions)
|
||||
if sessionData.counter[1] > 0
|
||||
sessionData.counter[2] += 1
|
||||
sessionData.counter[1] = 0
|
||||
sessionData.counter[0] = 0
|
||||
|
||||
|
||||
clearProvisions = (sessionData) ->
|
||||
sessionData.provisions = []
|
||||
if sessionData.counter[1] > 0
|
||||
sessionData.counter[2] += 1
|
||||
sessionData.counter[1] = 0
|
||||
sessionData.counter[0] = 0
|
||||
|
||||
|
||||
generateRpcRequest = (sessionData, declarations, callback) ->
|
||||
@@ -214,26 +236,26 @@ generateRpcRequest = (sessionData, declarations, callback) ->
|
||||
return
|
||||
|
||||
if res.gpn?
|
||||
rpcRequest = {
|
||||
rpcReq = {
|
||||
type: 'GetParameterNames'
|
||||
parameterPath: res.gpn[0].join('.')
|
||||
nextLevel: true
|
||||
}
|
||||
return callback(null, rpcRequest)
|
||||
return callback(null, rpcReq)
|
||||
|
||||
if res.gpv?
|
||||
rpcRequest = {
|
||||
rpcReq = {
|
||||
type: 'GetParameterValues'
|
||||
parameterNames: (p.join('.') for p in res.gpv.slice(0, config.get('TASK_PARAMETERS_BATCH_SIZE', sessionData.deviceId)))
|
||||
}
|
||||
return callback(null, rpcRequest)
|
||||
return callback(null, rpcReq)
|
||||
|
||||
if res.spv?
|
||||
rpcRequest = {
|
||||
rpcReq = {
|
||||
type: 'SetParameterValues'
|
||||
parameterList: ([p[0].join('.'), p[1], p[2]] for p in res.spv.slice(0, config.get('TASK_PARAMETERS_BATCH_SIZE', sessionData.deviceId)))
|
||||
}
|
||||
return callback(null, rpcRequest)
|
||||
return callback(null, rpcReq)
|
||||
|
||||
return cb()
|
||||
|
||||
@@ -263,74 +285,73 @@ generateRpcRequest = (sessionData, declarations, callback) ->
|
||||
|
||||
|
||||
rpcRequest = (sessionData, allDeclarations, callback) ->
|
||||
presetNames = Object.keys(sessionData.presets)
|
||||
presetNames.sort((a, b) ->
|
||||
if sessionData.presets[a].weight == sessionData.presets[b].weight
|
||||
return a > b
|
||||
else
|
||||
return sessionData.presets[a].weight - sessionData.presets[b].weight
|
||||
)
|
||||
|
||||
allDeclarations = allDeclarations?.slice() ? []
|
||||
|
||||
counter = 1
|
||||
for presetName in presetNames
|
||||
do (presetName) ->
|
||||
for provision in sessionData.presets[presetName].provisions
|
||||
++ counter
|
||||
provisions.processProvision(provision[0], provision[1..], (err, declarations) ->
|
||||
if err
|
||||
callback(err) if -- counter >= 0
|
||||
counter = 0
|
||||
return
|
||||
nextRevision = false
|
||||
|
||||
allDeclarations = allDeclarations.concat(declarations)
|
||||
cache.getScripts((err, presetsHash, scripts) ->
|
||||
return callback(err) if err or presetsHash != sessionData.presetsHash
|
||||
|
||||
if -- counter == 0
|
||||
generateRpcRequest(sessionData, allDeclarations, (err, rpcRequest) ->
|
||||
sessionData.rpcRequest = rpcRequest
|
||||
if err or not rpcRequest?
|
||||
return callback(err)
|
||||
for provision in sessionData.provisions
|
||||
if not scripts[provision[0]]?
|
||||
switch provision[0]
|
||||
when 'refresh'
|
||||
path = common.parsePath(provision[1])
|
||||
for i in [path.length...16] by 1
|
||||
path.length = i
|
||||
allDeclarations.push([path.slice(), provision[2], null, null, null, null, null, provision[2]])
|
||||
when 'value'
|
||||
allDeclarations.push([[common.parsePath(provision[1]), provision[2], null, null, null, null, null, provision[2]]])
|
||||
when 'tag'
|
||||
allDeclarations.push([[['Tags', provision[1]], null, null, null, null, null, null, null, [provision[2], 'xsd:boolean']]])
|
||||
continue
|
||||
|
||||
callback(err, sessionData.rpcCount++, rpcRequest)
|
||||
nextRevision ||= sandbox.run(scripts[provision[0]].compiled, provision.slice(1), sessionData.deviceData, sessionData.counter[1], allDeclarations)
|
||||
|
||||
generateRpcRequest(sessionData, allDeclarations, (err, rpcReq) ->
|
||||
sessionData.rpcRequest = rpcReq
|
||||
return callback(err) if err
|
||||
|
||||
if not rpcReq?
|
||||
if not nextRevision
|
||||
return callback()
|
||||
sessionData.counter[0] = 0
|
||||
sessionData.counter[1] += 1
|
||||
return rpcRequest(sessionData, null, callback)
|
||||
|
||||
callback(err, generateRpcId(sessionData), rpcReq)
|
||||
)
|
||||
return
|
||||
)
|
||||
|
||||
if -- counter == 0
|
||||
generateRpcRequest(sessionData, allDeclarations, (err, rpcRequest) ->
|
||||
sessionData.rpcRequest = rpcRequest
|
||||
if err or not rpcRequest?
|
||||
return callback(err)
|
||||
|
||||
callback(err, sessionData.rpcCount++, rpcRequest)
|
||||
)
|
||||
return
|
||||
|
||||
|
||||
rpcResponse = (sessionData, id, rpcResponse, callback) ->
|
||||
# TODO verify ID
|
||||
rpcRequest = sessionData.rpcRequest
|
||||
rpcResponse = (sessionData, id, rpcRes, callback) ->
|
||||
if id != generateRpcId(sessionData)
|
||||
return callback(new Error('Request ID not recognized'))
|
||||
|
||||
++ sessionData.counter[0]
|
||||
|
||||
rpcReq = sessionData.rpcRequest
|
||||
sessionData.rpcRequest = null
|
||||
|
||||
timestamp = sessionData.timestamp
|
||||
|
||||
switch rpcResponse.type
|
||||
switch rpcRes.type
|
||||
when 'GetParameterValuesResponse'
|
||||
return callback(new Error('Response type does not match request type')) if rpcRequest.type isnt 'GetParameterValues'
|
||||
return callback(new Error('Response type does not match request type')) if rpcReq.type isnt 'GetParameterValues'
|
||||
|
||||
for p in rpcResponse.parameterList
|
||||
for p in rpcRes.parameterList
|
||||
device.set(sessionData.deviceData, common.parsePath(p[0]), 1, [timestamp, 1, timestamp, 0, null, null, timestamp, p.slice(1)])
|
||||
|
||||
when 'GetParameterNamesResponse'
|
||||
return callback(new Error('Response type does not match request type')) if rpcRequest.type isnt 'GetParameterNames'
|
||||
return callback(new Error('Response type does not match request type')) if rpcReq.type isnt 'GetParameterNames'
|
||||
|
||||
device.set(sessionData.deviceData, common.parsePath(rpcRequest.parameterPath).concat(null), 1, timestamp)
|
||||
device.set(sessionData.deviceData, common.parsePath(rpcReq.parameterPath).concat(null), 1, timestamp)
|
||||
|
||||
for p in rpcResponse.parameterList
|
||||
for p in rpcRes.parameterList
|
||||
if common.endsWith(p[0], '.')
|
||||
path = common.parsePath(p[0][0...-1])
|
||||
if not rpcRequest.nextLevel
|
||||
if not rpcReq.nextLevel
|
||||
device.set(sessionData.deviceData, path.conact(null), 1, timestamp)
|
||||
|
||||
device.set(sessionData.deviceData, path, 1, [timestamp, 1, timestamp, 1, timestamp, if p[1] then 1 else 0])
|
||||
@@ -338,9 +359,9 @@ rpcResponse = (sessionData, id, rpcResponse, callback) ->
|
||||
device.set(sessionData.deviceData, common.parsePath(p[0]), 1, [timestamp, 1, timestamp, 0, timestamp, if p[1] then 1 else 0])
|
||||
|
||||
when 'SetParameterValuesResponse'
|
||||
return callback(new Error('Response type does not match request type')) if rpcRequest.type isnt 'SetParameterValues'
|
||||
return callback(new Error('Response type does not match request type')) if rpcReq.type isnt 'SetParameterValues'
|
||||
|
||||
for p in rpcRequest.parameterList
|
||||
for p in rpcReq.parameterList
|
||||
device.set(sessionData.deviceData, common.parsePath(p[0]), 1, [timestamp, 1, timestamp, 0, timestamp, 1, timestamp, p.slice(1)])
|
||||
|
||||
else
|
||||
@@ -410,7 +431,8 @@ end = (sessionData, callback) ->
|
||||
|
||||
exports.init = init
|
||||
exports.inform = inform
|
||||
exports.addPreset = addPreset
|
||||
exports.addProvisions = addProvisions
|
||||
exports.clearProvisions = clearProvisions
|
||||
exports.rpcRequest = rpcRequest
|
||||
exports.rpcResponse = rpcResponse
|
||||
exports.rpcFault = rpcFault
|
||||
|
||||
Reference in new issue
Block a user