UNPKG

node-red-contrib-myhome-bticino-v2

Version:
206 lines (189 loc) 10.5 kB
/*jshint esversion: 7, strict: implied, node: true */ module.exports = function (RED) { let mhutils = require ('./myhome-utils'); function MyHomeShutterNode (config) { RED.nodes.createNode (this, config); var node = this; let gateway = RED.nodes.getNode (config.gateway); let runningMonitor = new mhutils.eventsMonitor (gateway); // Build the shutter point name. If node is configured as being a group, add '#' as prefix // When NOT working on private riser, include the bus level (with suffix '#4#xx' where xx is the BUS id) let buslevel = config.buslevel || 'private_riser'; node.shuttergroupid = ((config.isgroup) ? '#' : '') + config.shutterid + ((buslevel === 'private_riser') ? '' : '#4#' + buslevel); // All current zone received values stored in memory from the moment node is loaded let payloadInfo = node.payloadInfo = {}; payloadInfo.state = '?'; node.lastPayloadInfo = JSON.stringify (payloadInfo); // SmartFilter : kept in memory to be able to compare whether an update occurred while processing msg ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Register node for updates node.on ('input', function (msg) { node.processInput (msg); }); ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Add listener on node linked to a dedicated function call to be able to remove it on close const listenerFunction = function (ownFamilyName , frame) { let msg = {}; node.processReceivedBUSFrames (msg, frame); }; runningMonitor.addMonitoredEvent ('OWN_SHUTTERS', listenerFunction); ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Function called when a MyHome BUS frame is received /////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// this.processReceivedBUSFrames = function (msg, frame) { if (typeof (msg.payload) === 'undefined') { msg.payload = {}; } let payload = msg.payload; // When the msg contains a topic with specified 'state/', it means function was called internally (from 'processInput') to refresh values. // In this case, even if no frame is found to update something, node is refreshed and msg are sent let forceRefreshAndMsg = (msg.topic === 'state/' + config.topic); // Check whether received command is linked to current configured shutter point let processedFrames = 0; for (let curFrame of (typeof(frame) === 'string') ? [frame] : frame) { let frameMatch; // Checks 1 : Shutter point/group update [*2*<status>*where##] // - <status> [0-2] : 0 = Stop / 1 = Up / 2 = Down frameMatch = curFrame.match ('^\\*2\\*(\\d+)\\*(' + node.shuttergroupid + '|0)##'); if (frameMatch !== null) { switch (frameMatch[1]) { case '0': // STOP payloadInfo.state = 'STOP'; node.status ({fill: 'grey', shape: (config.isgroup) ? 'ring' : 'dot', text: ((config.isgroup) ? RED._('mh-shutter.node.status-isgroup') : '') + RED._('mh-shutter.node.status-stopped')}); break; case '1': // OPEN payloadInfo.state = 'OPEN'; node.status ({fill: 'yellow', shape: (config.isgroup) ? 'ring' : 'dot', text: ((config.isgroup) ? RED._('mh-shutter.node.status-isgroup') : '') + RED._('mh-shutter.node.status-opened')}); break; case '2': // CLOSE payloadInfo.state = 'CLOSE'; node.status ({fill: 'grey', shape: (config.isgroup) ? 'ring' : 'dot', text: ((config.isgroup) ? RED._('mh-shutter.node.status-isgroup') : '') + RED._('mh-shutter.node.status-closed')}); break; } } // If we reached here with a non null match, it means command was useful for node if (frameMatch !== null) { processedFrames++; } } // Checks : all done, if nothing was processed, abord (no node / flow update detected), excepted when refresh is 'forced' if (processedFrames === 0 && !forceRefreshAndMsg) { return; } // Send msg back as new flow : only send update as new flow when something changed after having received this new BUS info // (but always send it when SmartFilter is disabled or when running in 'state/' mode, i.e. read-only mode) if (!config.skipevents || forceRefreshAndMsg) { let newPayloadinfo = JSON.stringify (payloadInfo); if (!config.smartfilter || newPayloadinfo !== node.lastPayloadInfo || forceRefreshAndMsg) { // MSG1 : Build primary msg // MSG1 : Received command info : only include source command when was provided as string (when is an array, it comes from .processInput redirected here) if (!Array.isArray(frame)) { payload.command_received = frame; } // MSG1 : add major node configuration info on both returned message msg.mh_nodeConfigInfo = { 'name' : config.name , 'topic' : config.topic , 'buslevel' : config.buslevel , 'shutterid' : config.shutterid , 'isgroup' : config.isgroup , 'gateway' : { 'name' : gateway.name , 'host' : gateway.host , 'port' : gateway.port } }; // MSG1 : Add all current node stored values to payload payload.state = payloadInfo.state; // MSG1 : Add misc info msg.topic = 'state/' + config.topic; // MSG2 : Build secondary payload let msg2 = mhutils.buildSecondaryOutput (RED.util.cloneMessage (msg), payloadInfo, config, 'On', 'OPEN', 'CLOSE'); // Store last sent payload info & send both msg to output1 and output2 node.lastPayloadInfo = newPayloadinfo; node.send ([msg, msg2]); } } }; ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Function called when a message (payload) is received from the node-RED flow /////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// this.processInput = function (msg) { if (typeof(msg) === 'string') { try {msg = JSON.parse(msg);} catch(error){} } // Only process input received from flow when the topic matches with configuration of nodes let isReadOnly = config.isstatusrequest || false; if (msg.topic === 'state/' + config.topic) { // Running in 'state/' mode, force read-only regardless of node config mode isReadOnly = true; } else if (msg.topic !== 'cmd/' + config.topic) { return; } // Get payload and apply conversions (asked state can be set in 'msg.payload', // 'msg.payload.state' or 'msg.payload.On', value being either true/false or OPEN/CLOSE/STOP // Final result is always kept in 'msg.payload.state' = 'OPEN', 'CLOSE' or 'STOP' if (msg.payload === undefined) { msg.payload = {}; } else if (typeof(msg.payload) === 'string') { try {msg.payload = JSON.parse(msg.payload);} catch(error){} } if (typeof(msg.payload) === 'object') { if (msg.payload.state === undefined && msg.payload.On !== undefined) { msg.payload.state = (msg.payload.On) ? 'OPEN' : 'CLOSE'; } } else if (typeof(msg.payload) === 'boolean') { msg.payload = {'state': (msg.payload) ? 'OPEN' : 'CLOSE'}; } else if (typeof(msg.payload) === 'string') { msg.payload = {'state': msg.payload}; } let payload = msg.payload; // Build the OpenWebNet command to be sent let commands = []; if (!isReadOnly) { if (payload.state === 'OPEN') { commands.push ('*2*1*' + node.shuttergroupid + '##'); } else if (payload.state === 'CLOSE') { commands.push ('*2*2*' + node.shuttergroupid + '##'); } else if (payload.state === 'STOP') { commands.push ('*2*0*' + node.shuttergroupid + '##'); } } if ((isReadOnly || commands.length) && !config.isgroup) { // In Read-Only mode : build a status enquiry request (no status update sent) // In Write mode : Since the gateway does not 'respond' when changing point state, we also add a second call to ask for point status after update. // Note : This does not work for groups commands.push ('*#2*' + node.shuttergroupid + '##'); } if (commands.length === 0) { return; } // Send the command on the BUS through the MyHome gateway mhutils.executeCommand (node, commands, gateway, 0, false, function (commands, cmd_responses, cmd_failed) { // Build main payload to return payloads to outputs payload.command_sent = commands; // Include initial SCS/BUS message which was sent in main payload payload.command_responses = cmd_responses; // include the BUS responses when emitted command provides a result (can hold multiple values) if (cmd_failed.length) { // Also add failed requests, but only if some failed payload.command_failed = cmd_failed; } // Once commands were sent, call internal function to force node info refresh (using 'state/') and msg outputs msg.topic = 'state/' + config.topic; node.processReceivedBUSFrames (msg, cmd_responses); }, function (cmd_failed, nodeStatusErrorMsg) { // Error, only update node state node.status ({fill: 'red', shape: 'dot', text: nodeStatusErrorMsg}); }); }; ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// node.on ('close', function(done) { // If any listener was defined, removed it now otherwise node will remain active in memory and keep responding to Gateway incoming calls runningMonitor.clearAllMonitoredEvents (); done(); }); } RED.nodes.registerType('myhome-shutter', MyHomeShutterNode); };