UNPKG

node-red-contrib-dsm

Version:

A dynamic state machine node based on the finite state machine model.

405 lines (355 loc) 11.7 kB
"use strict"; module.exports = function(RED) { function DsmNode(n) { RED.nodes.createNode(this, n); this.sm_config = n.sm_config; this.sm_set; this.sm = {}; this.timeout = {}; this.obj = {}; var node = this; var context = this.context(); var global = this.context().global; var flow = this.context().flow; var sm = this.sm; var timeout = this.timeout; var obj = this.obj; var sm_set = this.sm_set; var sta = {fill:"grey",shape:"dot",text:"dsm undefined"}; var output; if (this.sm_config) { sm = JSON.parse(this.sm_config); set_dsm(sm); context.set('sm', sm); sm_set = true; } else { sm = context.get('sm'); if (typeof sm === "undefined") { sm_set = false; } else { set_dsm(sm); sm_set = true; if (typeof sm.currentState !== "undefined") { sta = {fill:"green",shape:"dot",text:sm.currentState}; } else { sta = {}; } } } node.status(sta); this.on('input', function(msg) { var sm_on_input = {}; output = false; sm = context.get('sm'); if (typeof sm === "undefined") { sm_set = false; } else { sm_on_input = RED.util.cloneMessage(sm); sm_set = true; } process_input(msg); if (sm_set) { const globalOutput = sm.globalOutput || false; const flowOutput = sm.flowOutput || false; if (globalOutput && sm.currentState) { global.set(globalOutput, sm.currentState); } if (flowOutput && sm.currentState) { flow.set(flowOutput, sm.currentState); } // save sm if modified if (!RED.util.compareObjects(sm, sm_on_input)) { context.set('sm', sm); } } node.status(sta); if (output) { if (sm.data) { msg.data = sm.data; } node.send(msg); } }); this.on('close', function(removed, done) { // removed not used if (Object.keys(timeout).length > 0) { for (var k in timeout) { clearTimeout(timeout[k]); } } done(); }); /** * dsm specific functions **/ function process_input(msg) { switch (msg.topic) { case "set": if (typeof msg.payload !== "object") { sta = {fill:"red",shape:"ring",text:"invalid payload, not an object"}; } else { sm = msg.payload; set_dsm(sm); sm_set = true; if (sm.states && sm.currentState) { const stateOutput = sm.stateOutput || "topic"; RED.util.setMessageProperty(msg,stateOutput,sm.currentState); } if (typeof sm.methods !== "undefined" && sm.methods.set) { process_method(msg, sm, "set"); } } break; default: if (!sm_set) { sta = {fill:"red",shape:"ring",text:"dsm undefined"}; } else { if (typeof sm.methods !== "undefined") { if (sm.methods.onBeforeTransition) { process_method(msg, sm, "onBeforeTransition"); } } const triggerInput = sm.triggerInput || "topic"; const method = RED.util.getMessageProperty(msg,triggerInput); if (sm.states) { process_tran(msg, sm, method); } if (typeof sm.methods !== "undefined") { if (sm.methods[method]) { process_method(msg, sm, method); } else if (sm.methods.default) { process_method(msg, sm, "default"); } if (sm.methods.onTransition) { process_method(msg, sm, "onTransition"); } if (sm.methods.onAfterTransition) { process_method(msg, sm, "onAfterTransition"); } if (sm.methods.status) { process_status(msg, sm, sm.methods.status); } /* experimental if (sm.methods[sm.currentState]) { process_method(msg, sm, sm.currentState); } */ } } } } function set_dsm(sm) { if (sm.currentState) { sta = {fill:"green",shape:"dot",text:sm.currentState}; } else { sta = {}; } if (typeof sm.methods !== "undefined" && sm.methods.init) { var stmnt = sm.methods.init; if (typeof stmnt === "string") { execute(null, sm, stmnt); } else if (Array.isArray(stmnt)) { execute(null, sm, stmnt.join('')); } } /* experimental 'sm.trans' is not used sm.trans = []; if (sm.states) { Object.keys(sm.states).forEach(function(key) { Object.keys(sm.states[key]).forEach(function(s) { if (sm.trans.indexOf(s) < 0) sm.trans.push(s); }); }); } */ } function process_tran(msg, sm, method) { const triggerInput = sm.triggerInput || "topic"; const stateOutput = sm.stateOutput || "topic"; const preStateOutput = sm.preStateOutput || "preState"; const state = sm.currentState; const tran = RED.util.getMessageProperty(msg,triggerInput); if (typeof sm.states[state] === "undefined") { sta = {fill:"red",shape:"ring",text:"state undefined"}; } else { if (sm.states[state][tran]) { output = true; sm.preState = sm.currentState; RED.util.setMessageProperty(msg,preStateOutput,sm.preState); sm.currentState = sm.states[state][tran]; RED.util.setMessageProperty(msg,stateOutput,sm.currentState); sta = {fill:"green",shape:"dot",text:sm.currentState}; } } } function process_method(msg, sm, method) { var stmnt = sm.methods[method]; if (typeof stmnt === "string") { execute(msg, sm, stmnt); } else if (Array.isArray(stmnt)) { execute(msg, sm, stmnt.join('')); } else { // build in methods var param; if (typeof sm.data !== "undefined" && typeof sm.data[stmnt.param] !== "undefined") { param = sm.data[stmnt.param]; } else { param = stmnt.param; } switch (stmnt.name) { case "setData": const triggerInput = sm.triggerInput || "topic"; const name = RED.util.getMessageProperty(msg,triggerInput); sm.data[name] = msg.payload; output = false; break; case "getData": msg.payload = sm.data; output = true; break; case "timer": process_timer(msg, sm, method, stmnt, param); break; case "watchdog": process_watchdog(msg, sm, method, stmnt, param); break; case "resetTimer": process_resetTimer(msg, sm, method, stmnt, param); break; } } } function process_timer(msg, sm, method, stmnt, param) { if (timeout[method]) { clearTimeout(timeout[method]); } parse_methods(msg, sm, method, stmnt); timeout[method] = setTimeout(function() { if (typeof sm.send !== "undefined" && sm.send[method]) { msg.payload = sm.send[method]; node.send(msg); } else if (typeof sm.do !== "undefined" && sm.do[method]) { execute(msg, sm, sm.do[method]); } else { node.send(msg); } node.status(sta); timeout[method] = null; }, param); output = false; } function process_watchdog(msg, sm, method, stmnt, param) { if (timeout[method]) { clearTimeout(timeout[method]); } parse_methods(msg, sm, method, stmnt); timeout[method] = setTimeout(function() { if (typeof sm.send !== "undefined" && sm.send[method]) { msg.payload = sm.send[method]; node.send(msg); } else if (typeof sm.do !== "undefined" && sm.do[method]) { execute(msg, sm, sm.do[method]); } else { node.send(msg); } node.status(sta); timeout[method] = null; }, param); output = false; } function process_resetTimer(msg, sm, method, stmnt, param) { if (timeout[param]) { clearTimeout(timeout[param]); } parse_methods(msg, sm, method, stmnt); if (typeof sm.send !== "undefined" && sm.send[method]) { msg.payload = sm.send[method]; node.send(msg); } else if (typeof sm.do !== "undefined" && sm.do[method]) { execute(msg, sm, sm.do[method]); } output = false; } function parse_methods(msg, sm, method, stmnt) { if (stmnt.send) { if (!sm.send) sm.send = {}; if (typeof stmnt.send === "string") { sm.send[method] = stmnt.send; } else { if (typeof stmnt.send.get === "string") { sm.send[method] = execute(msg, sm, stmnt.send.get); } else { if (Array.isArray(stmnt.send.get)) { sm.send[method] = execute(msg, sm, stmnt.send.get.join('')); } } } } else if (stmnt.do) { if (!sm.do) sm.do = {}; if (typeof stmnt.do === "string") { sm.do[method] = stmnt.do; } else if (Array.isArray(stmnt.do)) { sm.do[method] = stmnt.do.join(''); } } } function process_status(msg, sm, status) { if (status.fill) { process_property(msg, sm, status, 'fill'); } if (status.shape) { process_property(msg, sm, status, 'shape'); } if (status.text) { process_property(msg, sm, status, 'text'); } } function process_property(msg, sm, status, prop) { if (typeof status[prop] === "string") { sta[prop] = status[prop]; } else { if (typeof status[prop].get === "string") { sta[prop] = execute(msg, sm, status[prop].get); } else { if (Array.isArray(status[prop].get)) { sta[prop] = execute(msg, sm, status[prop].get.join('')); } } } } function execute(msg, sm, stmnt) { try { return eval(stmnt); } catch(err) { node.error(err); node.error(stmnt); } } function resume(trigger, msg) { const triggerInput = sm.triggerInput || "topic"; msg[triggerInput] = trigger; //node.warn(triggerInput+ ' '+msg[triggerInput]); node.emit('input', msg); } /** * utility functions **/ // return "YYYY-MM-DDThh:mm:ss.sss" function timestamp() { var d = new Date(); return d.getFullYear() + '-' + pad(d.getMonth() + 1) + '-' + pad(d.getDate()) + 'T' + pad(d.getHours()) + ':' + pad(d.getMinutes()) + ':' + pad(d.getSeconds()) + '.' + (d.getMilliseconds() / 1000).toFixed(3).slice(2, 5); } function pad(number) { if (number < 10) { return '0' + number; } return number; } } RED.nodes.registerType("dsm",DsmNode); };