UNPKG

node-red-contrib-heater-controller

Version:
220 lines (205 loc) 6.99 kB
var _ = require('lodash'); var FrontEnd = require('./frontEnd') const WsServer = require('./webSocketServer') const frontConfigOptions = [ 'title', 'topic', 'logLength', 'threshold', 'calendar', 'unit', 'displayMode', 'sliderMaxValue', 'sliderMinValue', 'sliderStep' ]; const frontEndTopics = ['status', 'config']; class UINode { status = undefined; config = undefined; #events = {}; #ui = undefined; #doneUI = undefined; #RED = undefined; #wsServer = undefined; constructor(RED, config) { this.config = config; this.#RED = RED; try { if (this.#ui === undefined) { this.#ui = RED.require('node-red-dashboard')(RED); } RED.nodes.createNode(this, this.config); this.startSocketIOServer(); this._createWidget(); this.on('input', this.input.bind(this)); } catch (error) { this.error(error); throw error; } this.on('close', this._close.bind(this)); } addEvent(topic, func) { if (typeof (topic) !== 'string' || typeof (func) !== 'function') { this.error('Invalid arguments [topic:string, func:function]'); throw new Error('Invalid arguments [topic:string, func:function]'); } this.#events[topic] = func; if (topic === 'userConfig') { //Front-end request bind to the input back-end this.#wsServer.registerIncomingEvents(topic, this.receive.bind(this), this.id); } } removeEvent(topic) { if (typeof (topic) !== 'string') { this.error('Invalid argument [topic:string]'); throw new Error('Invalid argument [topic:string]'); } delete this.#events[topic]; } startSocketIOServer() { this.#wsServer = WsServer.createInstance(this.#RED, this.id); this.#wsServer.registerIncomingEvents('connection', this._newClientConnected.bind(this), this.id); } filterConfig(config) { var frontEndConf = {}; for (var i in frontConfigOptions) { var key = frontConfigOptions[i]; frontEndConf[key] = config[key]; } return frontEndConf; } _newClientConnected() { this.#wsServer.send(this.id, 'config', this.filterConfig(this.config)); this.#wsServer.send(this.id, 'status', this.status); } sendStatus() { this.#wsServer.send(this.id, 'status', this.status); } /** * @private * * Called when the node needs to be unloaded * - Calls onClose if there is an implementation of it; * - call dashboard remove node if any; */ _close(resolve) { this.debug('Close called'); this.#wsServer.unRegister(this.id); if (this.onClose) { this.onClose(); } if (this.#doneUI) { this.#doneUI(); } resolve(); } /** * @private * * Called when a new message arrives * - Calls onInput if there is an implementation of it; * - Calls the end process method if nodeJS > 1.0 * - In case of an error throws it to nodeJS for handling * @param {Object} msg the message * @param {function} send function for sending a message from output * @param {function} doneCB function to call when processing is finished or an error occurred */ input(msg, send, doneCB) { this.debug('input: ' + JSON.stringify(msg)); try { this._messageIn(msg, send); if (doneCB) { doneCB(); } } catch (err) { // my error message is not showing up because of ui this.error(err, msg); if (doneCB) { // Node-RED 1.0 compatible doneCB(err); } } } /** * @private * * Called in constructor to build the interface */ _createWidget() { this.debug('createWidget called'); try { var frontEnd = new FrontEnd(); var frontEndHtml = frontEnd.getHTML(this.config.displayMode, this.#ui.isDark(), this.#ui.getTheme()); var frontEndController = frontEnd.getController(this.#wsServer.getURL(this.id)); this.#doneUI = this.#ui.addWidget(Object.assign({ node: this, width: parseInt(this.config.width), height: parseInt(this.config.height), group: this.config.group, order: this.config.order || 0 }, { format: frontEndHtml, templateScope: 'local', emitOnlyNewValues: false, forwardInputMessages: false, storeFrontEndInputAsState: true, initController: frontEndController })); } catch (error) { this.error(error); throw error; } } /** * back to front * Called before a new message arrives * @param {Object} msg the message */ _messageIn(msg, send) { if (typeof (msg) !== 'object' || typeof (msg.topic) !== 'string') { this.error('Invalid Topic!!! ', msg); throw new Error('Invalid Topic!!!'); } //clones initial status this.oldStatus = _.cloneDeepWith(this.status, function (value) { /* istanbul ignore next */ if (_.isFunction(value)) { return; } }); //process message; var resp; var eventItem = this.#events[msg.topic]; if (eventItem) { resp = eventItem.call(this, msg); } else { this.error('Calling unregistered event: ' + msg.topic); throw new Error('Calling unregistered event: ' + msg.topic); } if (typeof (resp) === 'undefined') return; this._sendToFrontEnd(resp); //send messages to front-end if any this._sendOutPut(resp, send, msg._msgid); //send message to back-end if any } _sendOutPut(msg, send, id) { if (typeof (msg) !== 'object') return; var heaterStatus = typeof (msg.heaterStatus) !== 'undefined' ? { topic: this.config.topic, payload: msg.heaterStatus } : undefined; delete msg.heaterStatus; if (typeof (heaterStatus) !== 'undefined') { send([heaterStatus]); } for (var i in msg) { var message = { topic: i, payload: msg[i] }; if (id) { message._msgid = id; } send([undefined, message]); } } _sendToFrontEnd(obj) { for (var i in frontEndTopics) { var topic = frontEndTopics[i]; var payload = obj[topic]; if (!['undefined', 'function'].includes(typeof (payload))) this.#wsServer.send(this.id, topic, payload); } } } module.exports = UINode;