├── .gitignore ├── DEMO.md ├── Espruino ├── modules │ ├── DS18B20_tve.js │ ├── MQTT.js │ └── ered_lib.js └── projects │ ├── e-red-esp8266.js │ └── e-red-linux.js ├── LICENSE ├── README.md ├── e-red ├── blinker.html ├── blinker.js ├── ds18b20.html ├── ds18b20.js ├── ered.js ├── func.html ├── func.js ├── gateway.html ├── gateway.js ├── icons │ └── chip.png ├── mqttq.html ├── mqttq.js ├── timer.html └── timer.js └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | 33 | # Optional npm cache directory 34 | .npm 35 | 36 | # Optional REPL history 37 | .node_repl_history 38 | -------------------------------------------------------------------------------- /DEMO.md: -------------------------------------------------------------------------------- 1 | Espruino-red demo 2 | ================= 3 | 4 | The following demo simulates having a DS18B20 temperature sensor and a blinking LED using the linux 5 | version of Espruino. The flow makes the LED blink at a rate controlled by the temperature. 6 | The simulation prints the LED state on stdout and ramps the temperature up slowly. The same 7 | flow can run using real ds18b20 and LED components on an ESP8266. 8 | 9 | The flow is: 10 | ``` 11 | [{"id":"751cab70.dc2334","type":"e-red function","z":"437d0112.082c6","name":"calc rate","board":"linux","code":"msg.payload = 1 + (msg.payload-60)/10; return msg;","x":420,"y":300,"wires":[["3c19a49f.1cbd6c","198b8e93.68ba81"]]},{"id":"9e6984e3.515e28","type":"inject","z":"437d0112.082c6","name":"60F","topic":"","payload":"60","payloadType":"num","repeat":"","crontab":"","once":false,"x":110,"y":100,"wires":[["751cab70.dc2334"]]},{"id":"331d4df3.117c62","type":"inject","z":"437d0112.082c6","name":"70F","topic":"","payload":"70","payloadType":"num","repeat":"","crontab":"","once":false,"x":110,"y":160,"wires":[["751cab70.dc2334"]]},{"id":"7cdf204e.35387","type":"inject","z":"437d0112.082c6","name":"80F","topic":"","payload":"80","payloadType":"num","repeat":"","crontab":"","once":false,"x":110,"y":220,"wires":[["751cab70.dc2334"]]},{"id":"3c19a49f.1cbd6c","type":"e-red blinker","z":"437d0112.082c6","name":"blinker","board":"linux","pin":"1","x":570,"y":300,"wires":[]},{"id":"198b8e93.68ba81","type":"debug","z":"437d0112.082c6","name":"blink rate","active":true,"console":"false","complete":"payload","x":480,"y":380,"wires":[]},{"id":"3b3aa691.b2c31a","type":"e-red ds18b20","z":"437d0112.082c6","name":"ds18b20","board":"linux","pin":"1","x":260,"y":300,"wires":[["751cab70.dc2334"]]},{"id":"7bf8273b.f62bf8","type":"e-red timer","z":"437d0112.082c6","name":"","board":"linux","rate":"0.2","x":100,"y":300,"wires":[["3b3aa691.b2c31a"]]}] 12 | ``` 13 | 14 | In addition, the following flow is needed to hook things up to MQTT: 15 | ``` 16 | [{"id":"deb880aa.f7238","type":"mqtt in","z":"c4220dc6.b5a2","name":"mqtt-in","topic":"e-red/core/#","qos":"1","broker":"dff8c141.3e5d7","x":110,"y":420,"wires":[["7e2ce79c.151d08"]]},{"id":"223c6741.0ddba8","type":"mqtt out","z":"c4220dc6.b5a2","name":"mqtt-out","topic":"","qos":"1","retain":"","broker":"dff8c141.3e5d7","x":400,"y":480,"wires":[]},{"id":"7e2ce79c.151d08","type":"e-red gateway","z":"c4220dc6.b5a2","name":"e-red gw","prefix":"e-red","x":260,"y":420,"wires":[["958469a4.0f53b8"]]},{"id":"3d8c26e1.fdaada","type":"debug","z":"c4220dc6.b5a2","name":"MQTT debug","active":true,"console":"false","complete":"payload","x":380,"y":540,"wires":[]},{"id":"958469a4.0f53b8","type":"mqtt queue","z":"c4220dc6.b5a2","name":"queue","broker":"dff8c141.3e5d7","x":190,"y":480,"wires":[["3d8c26e1.fdaada","223c6741.0ddba8"]]},{"id":"dff8c141.3e5d7","type":"mqtt-broker","z":"c4220dc6.b5a2","broker":"h.voneicken.com","port":"1883","tls":null,"clientid":"node-red-dev","usetls":false,"compatmode":false,"keepalive":"60","cleansession":true,"willTopic":"","willQos":"0","willRetain":null,"willPayload":"","birthTopic":"","birthQos":"0","birthRetain":null,"birthPayload":""}] 17 | ``` 18 | 19 | A linux version of espruino is needed, which must be compiled from source (sigh) 20 | from https://github.com/espruino/espruino 21 | 22 | Change the MQTT connect in `./Espruino/projects/e-red-linux.js` as well as in the 3 MQTT 23 | nodes in node-red (in, out, queue). 24 | 25 | Run using the command line `./espruino projects/e-red-linux.js` in `./Espruino` 26 | 27 | -------------------------------------------------------------------------------- /Espruino/modules/DS18B20_tve.js: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2013 Gordon Williams, Pur3 Ltd. See the file LICENSE for copying permission. */ 2 | /* Improved by Thorsten von Eicken, 2015 */ 3 | /* 4 | Module for the DS18B20 temperature sensor 5 | 6 | ``` 7 | var ow = new OneWire(A1); 8 | var sensor = require("DS18B20").connect(ow); 9 | console.log(sensor.getTemp()); 10 | sensor.setRes(9); 11 | console.log(sensor.getTemp()); 12 | var sensor2 = require("DS18B20").connect(ow, 1); 13 | var sensor3 = require("DS18B20").connect(ow, -8358680895374756824); 14 | ``` 15 | */ 16 | 17 | var C = { 18 | CV: 0x44, // convert command 19 | CP: 0x48, // copy scratchpad to eeprom 20 | RD: 0xBE, // read scratchpad 21 | WR: 0x4E // write scratchpad 22 | }; 23 | 24 | function DS18B20(oneWire, device) { 25 | this.bus = oneWire; 26 | this.res = 12; // worst case 27 | if (device === undefined) { 28 | this.sCode = this.bus.search()[0]; 29 | } else { 30 | if (parseInt(device, 16).toString()==device && device >= 0 && device <= 126) { 31 | this.sCode = this.bus.search()[device]; 32 | } else { 33 | this.sCode = device; 34 | } 35 | } 36 | } 37 | 38 | var _p = DS18B20.prototype; 39 | 40 | /** For internal use - read the scratchpad region */ 41 | _p._readSpad = function () { 42 | var spad = [], 43 | bus = this.bus; 44 | //bus.reset(); 45 | bus.select(this.sCode); 46 | bus.write(C.RD); 47 | for (var i = 0; i < 9; i++) { 48 | spad.push(bus.read()); 49 | } 50 | var crc = this._crc(spad.slice(0,8)); 51 | if (crc != spad[8]) spad = null; 52 | return spad; 53 | }; 54 | 55 | /** For internal use - start a conversion */ 56 | _p._cvt = function () { 57 | //this.bus.reset(); 58 | this.bus.select(this.sCode); 59 | this.bus.write(C.CV, true); 60 | }; 61 | 62 | /** For internal use - calculate the CRC */ 63 | _p._crc = function(data) { 64 | var crc=0; 65 | data.forEach(function(d){ 66 | for(var i=8;i>0;i--){ 67 | var m=(crc^d)&1; 68 | crc >>= 1; 69 | if (m) crc ^= 0x8c; 70 | d>>=1; 71 | } 72 | }); 73 | return crc; 74 | }; 75 | 76 | /** For internal use - write the scratchpad region */ 77 | _p._writeSpad = function (th, tl, conf) { 78 | var b = this.bus; 79 | //b.reset(); 80 | b.select(this.sCode); 81 | b.write([C.WR, th, tl, conf]); 82 | //b.reset(); 83 | b.select(this.sCode); 84 | b.write(C.CP); 85 | b.reset(); 86 | }; 87 | 88 | /** Set the resolution in bits. From 9 to 12 bits */ 89 | _p.setRes = function (res) { 90 | var spad = this._readSpad(); 91 | if (spad === null) return; 92 | this.res = res; 93 | res = [0x1F, 0x3F, 0x5F, 0x7F][E.clip(res, 9, 12) - 9]; 94 | this._writeSpad(spad[2], spad[3], res); 95 | }; 96 | 97 | /** Return the resolution in bits. From 9 to 12 bits */ 98 | /* 99 | _p.getRes = function () { 100 | return [0x1F, 0x3F, 0x5F, 0x7F].indexOf(this._readSpad()[4]) + 9; 101 | }; 102 | */ 103 | 104 | /** Return true if this device is present */ 105 | _p.isPresent = function () { 106 | return this.bus.search().indexOf(this.sCode) !== -1; 107 | }; 108 | 109 | /** For internal use - finish readout */ 110 | _p._getTemp = function(callback) { 111 | var s = this._readSpad(); 112 | var t = null; 113 | if (s !== null) { 114 | t = s[0] + (s[1]<<8); 115 | if (t > 32767) t -= 65536; 116 | t = t/16.0; 117 | } 118 | if (callback !== undefined) callback(t); 119 | }; 120 | 121 | /** Get a temperature reading, in degrees C */ 122 | _p.getTemp = function (callback) { 123 | this._cvt(); 124 | var self = this; 125 | // time for conversion depends on resolution 126 | setTimeout(function(){self._getTemp(callback)}, 127 | [100, 190, 380, 760][E.clip(this.res, 9, 12) - 9]); 128 | }; 129 | 130 | /* * Return a list of all DS18B20 sensors with the alarms set */ 131 | /* 132 | DS18B20.prototype.searchAlarm = function() { 133 | return this.bus.search(0xEC); 134 | }; 135 | */ 136 | 137 | /* * Set alarm low and high values in degrees C - see DS18B20.prototype.searchAlarm. 138 | If the temperature goes below `lo` or above `hi` the alarm will be set. */ 139 | /* 140 | DS18B20.prototype.setAlarm = function(lo,hi) { 141 | lo--; // DS18B20 alarms if (temp<=lo || temp>hi), but we want (temphi) 142 | if (lo<0) lo+=256; 143 | if (hi<0) hi+=256; 144 | var spad = this._readSpad(); 145 | this._writeSpad(hi,lo,spad[4]); 146 | }; 147 | */ 148 | 149 | /** Initialise a DS18B20 device. Use either as: 150 | connect(new OneWire(pin)) - use the first found DS18B20 device 151 | connect(new OneWire(pin), N) - use the Nth DS18B20 device 152 | connect(new OneWire(pin), ID) - use the DS18B20 device with the given ID 153 | */ 154 | exports.connect = function (oneWire, device) {return new DS18B20(oneWire, device);}; 155 | -------------------------------------------------------------------------------- /Espruino/modules/MQTT.js: -------------------------------------------------------------------------------- 1 | /* 2 | * tinyMQTT.js 3 | * Stripped out MQTT module that does basic PUB/SUB 4 | * Intended for devices running Espruino, particularly the ESP8266 5 | * Ollie Phillips 2015 6 | * MIT License 7 | * Modified by Thorsten von Eicken 2015 8 | */ 9 | 10 | var MQTT = function(server){ 11 | this.server = server; 12 | }; 13 | var sfcc = String.fromCharCode; 14 | var data = ""; 15 | var _p = MQTT.prototype; 16 | 17 | // event handler receiving data 18 | function mqEv(d) { 19 | data += d; 20 | while (data.length > 2) { 21 | var dcca = data.charCodeAt.bind(data); 22 | 23 | var cmd = dcca(0); 24 | // determine total packet length 25 | var i=1, len = dcca(i++); 26 | if (len > 127) len = (len&0x7F) | (dcca(i++)<<7); 27 | 28 | // if we don't have a full msg return 29 | if (data.length < len+i) return; 30 | 31 | if ((cmd >> 4) === 3) { 32 | // got a 'publish' message 33 | var tlen = (dcca(i+0) << 8) | dcca(i+1); 34 | this.emit('message', { 35 | topic: data.substr(i+2, tlen), 36 | message: data.substr(i+2+tlen, len-2-tlen), 37 | //dup: (cmd & 0b00001000) >> 3, 38 | //qos: (cmd & 0b00000110) >> 1, 39 | retain: cmd & 0b00000001 40 | }); 41 | } 42 | data = data.slice(i+len); 43 | } 44 | }; 45 | 46 | // encode a string for mqtt by prefixing the length (16-bits) 47 | function mqSt(str) { 48 | return sfcc(str.length >> 8, str.length&255) + str; 49 | }; 50 | 51 | // write an mqtt packet 52 | function mqWr(wr, cmd, topic, payload) { 53 | var l = topic.length+payload.length; 54 | //console.log("MQwr:", typeof(payload), l, 0x80+(l&0x7f), l>>7, payload); 55 | wr(l < 128 ? sfcc(cmd, l) : sfcc(cmd, 0x80+(l&0x7f), l>>7)); 56 | wr(topic); 57 | wr(payload); 58 | }; 59 | 60 | _p.connect = function(id, clean){ 61 | var mq = this; 62 | var onC = function() { 63 | //console.log("mqtt connected"); 64 | mq.wr = mq.cl.write.bind(mq.cl); // write method 65 | // send connect message 66 | var v = 67 | mqSt("MQTT") + // protocol name 68 | "\x04" + // protocol level, 4=v3.1.1 69 | (clean?"\x02":"\x00") + // connect flags, 02=clean session 70 | "\x00\x00"; // keep-alive timeout, 0=disable 71 | mqWr(mq.wr, 0b00010000, v, mqSt(id)); 72 | // register callbacks 73 | mq.emit("connected"); 74 | mq.cl.on('data', mqEv.bind(mq)); 75 | mq.cl.on('end', function() { mq.wr = null; mq.emit("disconnected"); }); 76 | }; 77 | mq.cl = require("net").connect({host : mq.server, port: 1883}, onC); 78 | }; 79 | 80 | _p.subscribe = function(topic) { 81 | mqWr(this.wr, 0b10000010, 82 | sfcc(0, 0), // packet identifier 83 | mqSt(topic) + sfcc(0)); // QoS 0 84 | }; 85 | 86 | _p.publish = function(topic, data, retain) { 87 | if (typeof(data) !== "string") data = JSON.stringify(data); 88 | mqWr(this.wr, 0b00110000 + (retain?1:0), // QoS0, retain=0 89 | mqSt(topic), 90 | data); 91 | this.emit("published"); 92 | }; 93 | 94 | _p.ready = function() { return this.wr !== null; } 95 | 96 | _p.disconnect = function(){ 97 | if (this.wr) { 98 | this.wr(sfcc(14<<4)+"\x00"); 99 | this.cl.end(); 100 | this.wr = null; 101 | } 102 | }; 103 | 104 | // Exports 105 | exports.create = function (server) { 106 | return new MQTT(server); 107 | }; 108 | -------------------------------------------------------------------------------- /Espruino/modules/ered_lib.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 Thorsten von Eicken. See the file LICENSE for copying permission. 2 | 3 | function func(type, config) { 4 | //this.input = new Function("msg", 'LL("func(",msg,")");' + config.code); 5 | this.input = new Function("msg", config.code); 6 | } 7 | 8 | function blinker(type, config) { 9 | var node = this; 10 | node.pin = parseInt(config.pin) || 0; 11 | LL("Blinker pin", node.pin); 12 | 13 | this.callback = function() { 14 | if (global.esp8266 !== undefined) { 15 | digitalWrite(node.pin, !digitalRead(node.pin)); 16 | } else { 17 | // linux 18 | LL("LED ", node.on?"ON":"OFF"); 19 | node.on = !node.on; 20 | } 21 | } 22 | 23 | this.close = function() { 24 | if (node.tmr) clearInterval(node.tmr); 25 | delete node.tmr; 26 | } 27 | 28 | this.input = function(msg) { 29 | node.rate = msg.payload; 30 | LL("blinker rate", node.rate); 31 | node.close(); 32 | if (node.rate > 0) { 33 | node.tmr = setInterval(node.callback, 500/node.rate); 34 | } 35 | } 36 | } 37 | 38 | function ds18b20(type, config) { 39 | var node = this; 40 | node.pin = parseInt(config.pin) || 0; 41 | LL("DS18B20 pin", node.pin); 42 | 43 | node.temp = 60; 44 | 45 | this.close = function() { 46 | } 47 | 48 | this.input = function(msg) { 49 | node.temp += 2; 50 | if (node.temp > 80) node.temp = 60; 51 | return { payload: node.temp }; 52 | } 53 | } 54 | 55 | function timer(type, config) { 56 | var node = this; 57 | node.rate = parseFloat(config.rate) || 0; 58 | LL("Timer rate", node.rate); 59 | 60 | setInterval(function() { 61 | node.send({ payload: node.rate }); 62 | }, 1000/node.rate); 63 | } 64 | 65 | function not_exist(type, config) { 66 | LL("*** Node type", type, "does not exist"); 67 | } 68 | 69 | module.exports = { 70 | function: func, 71 | blinker: blinker, 72 | ds18b20: ds18b20, 73 | timer: timer, 74 | "not-exist": not_exist, 75 | }; 76 | -------------------------------------------------------------------------------- /Espruino/projects/e-red-esp8266.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 Thorsten von Eicken. See the file LICENSE for copying permission. 2 | 3 | esp8266 = require("ESP8266"); 4 | wifi = require("Wifi"); 5 | mqtt = require("MQTT").create("h.voneicken.com"); 6 | 7 | var board = "dev"; 8 | 9 | var mqLED = D0; // LED to signal MQTT connection and messages 10 | var mqTmr; // Timer for mqLED 11 | var blinkLED = D12; // LED to play with 12 | 13 | // Helpers 14 | var LL = console.log; // shorter than "console.log" 15 | // RR rounds v to d digits, useful to display numbers 16 | function RR(v,d) { var f=Math.pow(10,d); return Math.round(v*f)/f; } 17 | 18 | // mqBlink makes the mqLED blink briefly to indicate mqtt activity 19 | function mqBlink() { 20 | mqLED.set(); 21 | if (mqTmr) clearTimeout(mqTmr); 22 | mqTmr = setTimeout(function() { if (mqtt.ready()) mqLED.reset(); mqTmr = 0; }, 100); 23 | } 24 | 25 | //===== mqtt 26 | 27 | function mqRecv(msg) { 28 | //LL("MQTT recv:", msg.topic, msg.message); 29 | mqBlink(); 30 | var t = msg.topic.split('/'); 31 | switch (t.shift()) { 32 | case 'system': break; 33 | case 'e-red': 34 | t.shift(); // drop board ID 35 | route(t[0], t[1], JSON.parse(msg.message)); 36 | LL(process.memory()); 37 | break; 38 | } 39 | } 40 | 41 | function mqConn() { 42 | //LL("MQTT..."); 43 | mqtt.connect(wifi.getHostname()+"-"+getSerial(), 0); 44 | } 45 | 46 | function mqReady() { 47 | LL("MQTT connected"); 48 | mqLED.reset(); 49 | mqtt.subscribe("system/#"); 50 | mqtt.subscribe("e-red/"+board+"/#"); 51 | } 52 | 53 | function mqDisc() { 54 | LL("MQTT disconnected"); 55 | mqLED.set(); 56 | setTimeout(mqConn, 5000); 57 | } 58 | 59 | function mqPub(t, d) { 60 | //LL("MQTT send:", t, d); 61 | if (mqtt.ready()) { 62 | mqtt.publish(t, d); 63 | mqBlink(); 64 | } 65 | } 66 | 67 | //===== E-RED board manager 68 | 69 | // nodes is the set of e-red nodes residing on this board. Each node is indexed by id and has: 70 | // type, wires, and config, which is the configuration params that were passed down from the 71 | // node-red node. 72 | var nodes = {}; 73 | // types is the set of e-red node types. Each node type is indexed by name and has: ... ? 74 | var types = {}; 75 | 76 | // route recevies an e-red mqtt message and interprets it 77 | function route(kind, id, payload) { 78 | var node; 79 | switch (kind) { 80 | case 'node': // create/destroy a node 81 | if (!payload || payload === "") { 82 | LL('destroy node', id); 83 | node = nodes[id]; 84 | if (node) { 85 | if (node.close) node.close(); 86 | nodes[id] = undefined; 87 | } 88 | } else { 89 | LL('creating node', id, payload.type); 90 | var constructor = types[payload.type] || types["not-exist"]; 91 | node = new constructor(payload.type, payload.config); 92 | if (node) { 93 | node.id = id; 94 | node.type = payload.type; 95 | nodes[id] = node; 96 | node.send = outputMsg.bind(null, node); 97 | } 98 | } 99 | break; 100 | case 'wire': // set the outgoing wires for a node 101 | node = nodes[id]; 102 | if (node) { 103 | LL("set wires for node", id, JSON.stringify(payload)); 104 | node.wires = payload; 105 | } 106 | break; 107 | case 'msg': // "data" message for a node's input 108 | runNode(id, payload); 109 | break; 110 | } 111 | } 112 | 113 | // outputMsg outputs a message from a node, scheduling local nodes for execution and pushing 114 | // MQTT messages to the broker as appropriate 115 | function outputMsg(node, msg) { 116 | var wires = node.wires; 117 | //LL("wires:", typeof wires, wires.length, wires); 118 | if (!wires) { LL("No wires"); return; } 119 | 120 | // iterate through all outputs and all destinations and either send a message or 121 | // use setTimeout to schedule the execution (yeah, there sure is a better way!) 122 | if (!Array.isArray(msg)) msg = [msg]; 123 | //LL("msg:", msg.length, msg); 124 | //LL('iterating through', msg.length, wires.length); 125 | // iterate through all outputs 126 | for (var o=0; o", out); 154 | if (out) outputMsg(node, out); 155 | } else { 156 | LL("Unknown node", id); 157 | } 158 | } 159 | 160 | //===== main 161 | 162 | function onInit() { 163 | esp8266.setLog(2); // log to memory and 2:uart0 3:uart1 164 | LL("Start..."); 165 | // load node types 166 | types = require('ered_lib'); 167 | // start mqtt 168 | mqtt.on("message", mqRecv); 169 | mqtt.on("connected", mqReady); 170 | mqtt.on("disconnected", mqDisc); 171 | mqConn(); 172 | } 173 | 174 | LL(String.fromCharCode(0x1B) +"[?7h"); 175 | LL(process.memory()); 176 | 177 | onInit(); 178 | -------------------------------------------------------------------------------- /Espruino/projects/e-red-linux.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 Thorsten von Eicken. See the file LICENSE for copying permission. 2 | 3 | mqtt = require("MQTT").create("h.voneicken.com"); 4 | 5 | var board = "linux"; 6 | 7 | // Helpers 8 | var LL = console.log; // shorter than "console.log" 9 | // RR rounds v to d digits, useful to display numbers 10 | function RR(v,d) { var f=Math.pow(10,d); return Math.round(v*f)/f; } 11 | 12 | //===== mqtt 13 | 14 | function mqRecv(msg) { 15 | //LL("MQTT recv:", msg.topic, msg.message); 16 | var t = msg.topic.split('/'); 17 | switch (t.shift()) { 18 | case 'system': break; 19 | case 'e-red': 20 | t.shift(); // drop board ID 21 | route(t[0], t[1], JSON.parse(msg.message)); 22 | LL(process.memory()); 23 | break; 24 | } 25 | } 26 | 27 | function mqConn() { 28 | //LL("MQTT..."); 29 | mqtt.connect("linux-espruino-red-"+getSerial(), 0); 30 | } 31 | 32 | function mqReady() { 33 | LL("MQTT connected"); 34 | mqtt.subscribe("system/#"); 35 | mqtt.subscribe("e-red/"+board+"/#"); 36 | } 37 | 38 | function mqDisc() { 39 | LL("MQTT disconnected"); 40 | setTimeout(mqConn, 5000); 41 | } 42 | 43 | function mqPub(t, d) { 44 | //LL("MQTT send:", t, d); 45 | if (mqtt.ready()) { 46 | mqtt.publish(t, d); 47 | } 48 | } 49 | 50 | //===== E-RED board manager 51 | 52 | // nodes is the set of e-red nodes residing on this board. Each node is indexed by id and has: 53 | // type, wires, and config, which is the configuration params that were passed down from the 54 | // node-red node. 55 | var nodes = {}; 56 | // types is the set of e-red node types. Each node type is indexed by name and has: ... ? 57 | var types = {}; 58 | 59 | // route recevies an e-red mqtt message and interprets it 60 | function route(kind, id, payload) { 61 | var node; 62 | switch (kind) { 63 | case 'node': // create/destroy a node 64 | if (!payload || payload === "") { 65 | LL('destroy node', id); 66 | node = nodes[id]; 67 | if (node) { 68 | if (node.close) node.close(); 69 | nodes[id] = undefined; 70 | } 71 | } else { 72 | LL('creating node', id, payload.type); 73 | var constructor = types[payload.type] || types["not-exist"]; 74 | node = new constructor(payload.type, payload.config); 75 | if (node) { 76 | node.id = id; 77 | node.type = payload.type; 78 | nodes[id] = node; 79 | node.send = outputMsg.bind(null, node); 80 | } 81 | } 82 | break; 83 | case 'wire': // set the outgoing wires for a node 84 | node = nodes[id]; 85 | if (node) { 86 | LL("set wires for node", id, JSON.stringify(payload)); 87 | node.wires = payload; 88 | } 89 | break; 90 | case 'msg': // "data" message for a node's input 91 | runNode(id, payload); 92 | break; 93 | } 94 | } 95 | 96 | // outputMsg outputs a message from a node, scheduling local nodes for execution and pushing 97 | // MQTT messages to the broker as appropriate 98 | function outputMsg(node, msg) { 99 | var wires = node.wires; 100 | //LL("wires:", typeof wires, wires.length, wires); 101 | if (!wires) { LL("No wires"); return; } 102 | 103 | // iterate through all outputs and all destinations and either send a message or 104 | // use setTimeout to schedule the execution (yeah, there sure is a better way!) 105 | if (!Array.isArray(msg)) msg = [msg]; 106 | //LL("msg:", msg.length, msg); 107 | //LL('iterating through', msg.length, wires.length); 108 | // iterate through all outputs 109 | for (var o=0; o", out); 137 | if (out) outputMsg(node, out); 138 | } else { 139 | LL("Unknown node", id); 140 | } 141 | } 142 | 143 | //===== main 144 | 145 | // load node types 146 | types = require('ered_lib'); 147 | // start mqtt 148 | mqtt.on("message", mqRecv); 149 | mqtt.on("connected", mqReady); 150 | mqtt.on("disconnected", mqDisc); 151 | mqConn(); 152 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2016 Thorsten von Eicken 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Espruino-Red 2 | ============ 3 | 4 | __WARNING__ Espruino-red is a prototype, don't expect it to work! 5 | 6 | To chat about espruino-red please use 7 | [![gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/jeelabs/espruino-red) 8 | or the [node-red Google group](https://groups.google.com/forum/#!forum/node-red). 9 | 10 | Espruino-red extends the node-red framework to run special nodes directly 11 | on a microcontroller that runs Espruino (http://www.espruino.com). 12 | 13 | Espruino can run on many embedded ARM boards as well as on the ESP8266. It 14 | is a tiny javascript interpreter that functions very similarly to 15 | node.js, but works with programs and data stored in a few tens of KB of 16 | RAM and/or flash. 17 | 18 | The main concept behind espruino-red is that a set of special node-red 19 | nodes run on espruino while the standard ones continue to run within 20 | node-red as usual. So, in essence, some nodes represent computation 21 | and I/O that is happening in Espruino. For example, there is an `e-red 22 | function` node that runs the javascript function in Espruino as opposed 23 | to what the normal function node does, which is to run the function 24 | in node-red on your latop, raspi, etc. 25 | 26 | When an e-red node is connected with a regular node the messages that 27 | traverse the wire need to be sent between Espruino and node-red over 28 | some network. The current implementation uses MQTT but this could be 29 | changed for a different technology very easily. When an e-red 30 | node is connected to another e-red node on the same embedded board the 31 | communication happens locally on the embedded board. Finally, two e-red 32 | nodes residing on different boards can be connected in which case MQTT 33 | is involved in routing messages from the first node to the second without 34 | going through node-red. 35 | 36 | Espruino-red is different from node-red-contrib-gpio in that node-red 37 | nodes and the wires between them run directly on Espruino, i.e., the 38 | microcontroller isn't just there do do the I/O. In addition, algorithms 39 | expressed in `e-red function` nodes also run directly on Espruino. 40 | All this means that if you shut down node-red the embedded board continue 41 | running their flows (although an MQTT broker may need to be running if 42 | the boards communicate with one-another). 43 | 44 | The node-red portion of Espruino0-red is not limited to Espruino, in fact 45 | it knows nothing about Espruino and has been designed such that a 46 | different embedded system can be used just as easily. The node-red 47 | portion is called "e-red" standing for "embedded-red" and it is 48 | entirely conceivable to write an embedded system that uses lua or 49 | forth instead of espruino. 50 | 51 | Terminology: 'board' refers to an embedded board running espruino-red, 52 | 'node' refers to a node in node-red, 'node-red' generally refers to the 53 | node-red process and environment running on some linux computer, 54 | 'standard node' refers to any of the normal nodes in node-red, while 55 | 'e-red node' refers to the special nodes that are built for 56 | espruino-red and that have both a component in node-red and in espruino-red. 57 | 58 | Espruino-red nodes 59 | ------------------ 60 | 61 | Espruino-red does _not_ just run standard node-red nodes on the embedded 62 | board! This would not be practical due to the various constraints of 63 | embedded boards. Instead it requires that special nodes be written that 64 | use fewer resources (!) and that target the useful set of features 65 | on an embedded system. 66 | 67 | Each espruino-red node (or e-red node in general) consists of 3 parts: 68 | the familiar node-red html file to display the node in the UI, the 69 | familiar node-red js file to serve as a local surrogate so node-red can 70 | instantiate something, and a new Espruino file/function that actually 71 | implements the node's embedded functionality and that will run in 72 | Espruino. 73 | 74 | The implementation of the espruino part of the node is relatively similar 75 | to the implementation of a regular node, just the javascript environment 76 | is more basic and instead of subscribing to inputs using events currently 77 | the node object must provide an input function that receives a msg and 78 | returns an output msg array. (These details could all be improved.) 79 | 80 | The standard node-red js implementation of the espruino-red node serves as 81 | local surrogate. It has two roles. The first is to gather the configuration 82 | from the UI and craft an ered\_config object which will be forwarded to 83 | the espruino constructor. The second role is to receive input messages coming from 84 | other standard nodes and forward them to the embedded board by calling into 85 | the e-red portion of the node-red runtime. (This second part could be 86 | delegated more generically to the e-red runtime than the current implenmentation 87 | does.) 88 | 89 | Network Messages 90 | ---------------- 91 | 92 | The communication between node-red and Espruino serves a number of 93 | purposes. When node-red instantiates an e-red node it needs to tell 94 | Espruino to also instantiate the node and node-red needs to send Espruino 95 | the details that are necessary. When a standard node-red node sends 96 | an e-red node a message then that message needs to be routed appropriately 97 | and vice-versa, when an e-red node sends a standard node a messagei 98 | that message needs to find its destination in node-red. 99 | 100 | The messages used in Espruino-red assume that all embedded boards are 101 | loaded with the Espruino-red firmware and configured to talk to a common 102 | MQTT broker and that each board is configured with its own unique name 103 | (a short string). E-red nodes are set-up in node-red with the name of 104 | the board they should reside on. Messages between node-red and Espruino 105 | then find their way using these board names, which are entirely the 106 | responsibility of the user to keep unique and consistent. 107 | 108 | Espruino-red assumes that each board is pre-loaded with the code necessary 109 | to implement all e-red nodes that will need to run there. For example, 110 | if an e-red node to display text onto a display attached to the board is 111 | deployed to the board using the node-red UI it is assumed that Espruino 112 | already has all the necessary code to instantiate the node. Of course in 113 | the case of the `e-red function` node, which contains user code, Espruino 114 | will receive the user code as part of the instantiation message. All this 115 | does not preclude devising a separate mechanism which would allow Espruino 116 | to download code dynamically for an e-red node using the node type, it's 117 | just not a part of the current espruino-red implementation. 118 | (Such a mechanism could actually be implemented using a node-red flow!) 119 | 120 | ### General message topic structure 121 | 122 | All MQTT messages use a first path component of `e-red` (this can be 123 | overridden in the e-red gateway node), which is followed 124 | by the name of the board or by `core` for the node-red process. The next 125 | path component contains the kind of message (currently `node`, `wire`, 126 | or `msg`) followed by the ID of the target node (i.e., the ID assigned 127 | by node-red to all nodes). Example: `e-red/espruino1/node/e7f801fa.4517d`. 128 | 129 | ### E-red node instantiation 130 | 131 | When node-red instantiates an e-red node a parallel instantiation needs to 132 | happen in Espruino. Node-red sends a message to `e-red//node/` 133 | where the `` is the ID assigned to the node by node-red. The message 134 | carries a payload that contains the node type, and any additional 135 | parameters that are needed to instantiate the node. The message needs 136 | to have the retain flag set so the instantiation is not lost if Espruino 137 | is not currently connected to the MQTT broker and so it can be re-created 138 | if Espruino restarts. 139 | 140 | ### E-red node destruction 141 | 142 | When node-red destroys an e-red node a parallel destruction needs to happen 143 | in Espruino. Node-red sends a message to `e-red//node/` with 144 | an empty message body. The message needs to have the retain flag set. 145 | It's not clear yet when and how these destroy messages can be garbage 146 | collected, perhaps Espruino can remove the message retention when it 147 | receives the message... 148 | 149 | ### E-red wire creation 150 | 151 | When node-red connects an e-red node output to another e-red node 152 | or to a regular node it sends Espruino a message to tell it how to 153 | route messages. The message is sent to `e-red//wire/` with 154 | a message body that contains an array of destinations for each output 155 | (i.e. nested arrays). This array has the same structure as the wire 156 | field in node-red except that each destination is the topic to reach the 157 | destination node if it is not local on the board. 158 | 159 | ### Message to a node 160 | 161 | To send a message to the input of a specific node the source (node-red 162 | or Espruino) sends a message to `e-red//msg/` where `` is 163 | either the board name or `core` for the node-red process, and `` is 164 | the node id assigned by node-red. The body of the message is node-red's 165 | `msg` object. (This means that somewhat confusingly there will often be 166 | a topic contained in the message body.) 167 | 168 | Note: there probably needs to be a denser encoding of messages than json, 169 | but for right now json will have to do for simnplicity's sake. 170 | -------------------------------------------------------------------------------- /e-red/blinker.html: -------------------------------------------------------------------------------- 1 | 16 | 17 | 34 | 35 | 40 | 41 | 62 | -------------------------------------------------------------------------------- /e-red/blinker.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016 Thorsten von Eicken 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | 17 | module.exports = function(RED) { 18 | "use strict"; 19 | var ered = require("./ered"); 20 | 21 | // The main node definition - most things happen in here 22 | function ERedBlinker(n) { 23 | var node = this; 24 | node.name = n.name; 25 | 26 | // Create a RED node 27 | RED.nodes.createNode(node, n); 28 | 29 | // Store the node configuration (as defined in the .html) into ered_config so it can be 30 | // shipped to Espruino 31 | node.board = n.board; 32 | node.ered_config = { pin: n.pin }; 33 | // add this node to the ered registry, this will instantiate it in Espruino as well 34 | ered.RegisterNode(node); 35 | 36 | this.on('input', function (msg) { 37 | // this node lives on Espruino, so we need to forward the message there... 38 | ered.SendTo(node.board, "msg", node.id, msg); 39 | }); 40 | 41 | this.on("close", function() { 42 | // deregister, which will tell Espruino to remove the node as well 43 | ered.DeregisterNode(node); 44 | }); 45 | } 46 | 47 | RED.nodes.registerType("e-red blinker", ERedBlinker); 48 | } 49 | -------------------------------------------------------------------------------- /e-red/ds18b20.html: -------------------------------------------------------------------------------- 1 | 16 | 17 | 34 | 35 | 40 | 41 | 62 | -------------------------------------------------------------------------------- /e-red/ds18b20.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016 Thorsten von Eicken 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | 17 | module.exports = function(RED) { 18 | "use strict"; 19 | var ered = require("./ered"); 20 | 21 | // The main node definition - most things happen in here 22 | function ERedDS18B20(n) { 23 | var node = this; 24 | node.name = n.name; 25 | 26 | // Create a RED node 27 | RED.nodes.createNode(node, n); 28 | 29 | // Store the node configuration (as defined in the .html) into ered_config so it can be 30 | // shipped to Espruino 31 | node.board = n.board; 32 | node.ered_config = { pin: n.pin }; 33 | // add this node to the ered registry, this will instantiate it in Espruino as well 34 | ered.RegisterNode(node); 35 | 36 | this.on('input', function (msg) { 37 | // this node lives on Espruino, so we need to forward the message there... 38 | ered.SendTo(node.board, "msg", node.id, msg); 39 | }); 40 | 41 | this.on("close", function() { 42 | // deregister, which will tell Espruino to remove the node as well 43 | ered.DeregisterNode(node); 44 | }); 45 | } 46 | 47 | RED.nodes.registerType("e-red ds18b20", ERedDS18B20); 48 | } 49 | -------------------------------------------------------------------------------- /e-red/ered.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016 Thorsten von Eicken 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | 17 | // Espruino-red core 18 | // Collection of common functions, node registry, and some abstractions 19 | // to communicate with boards via a gateway 20 | 21 | "use strict"; 22 | 23 | // Horrific way to require node-red/red/runtime/flows 24 | var flows = require(module.parent.parent.id + "/../../flows"); 25 | // Another hack to get log.warn, I'm sure there's a better way... 26 | var log = require(module.parent.parent.id + "/../../../log"); 27 | // And a hack to get events so we can hook to the nodes-started event 28 | var events = require(module.parent.parent.id + "/../../../events"); 29 | 30 | // ered_gateway is a node that handles communication to/from Espruino boards. 31 | // (It should probably be a map from board name to gateway for that board.) 32 | var ered_gateway; 33 | var ered_tq = []; // queue of messages waiting for gateway to appear 34 | var ered_started; // flag when nodes-started event has occurred 35 | 36 | const RETAIN = true; 37 | const EPHEMERAL = false; 38 | 39 | // RegisterNode expects the node to have a board name, a type, and ered_config. It contacts the 40 | // gateway to send information about the node to the board and it hooks the updateWires 41 | // function to be able to update the board when these change. 42 | exports.RegisterNode = function(n) { 43 | // Horrific hack to be notified when the output wires of the node being registered change: 44 | // we interpose our own function for updateWires so we can be part of the action 45 | n.originalUpdateWires = n.updateWires; 46 | n.updateWires = function(wires) { n.originalUpdateWires(wires); updateWires(n); } 47 | // craft message to Espruino's manager 48 | var t = n.type.replace("ered ", "").replace("e-red ", ""); 49 | var msg = { type: t, config: n.ered_config }; 50 | sendTo(n.board, "node", n.id, msg); 51 | // wire the node up 52 | updateWires(n); 53 | } 54 | 55 | // DeregisterNode sends a deletion message to the board 56 | exports.DeregisterNode = function(n) { 57 | sendTo(n.board, "node", n.id, ""); 58 | } 59 | 60 | // SendTo sends a node-red message to the input of a node running in node-red or on 61 | // a remote board. 62 | function sendTo(board, kind, id, msg) { 63 | // check local delivery first 64 | if (board === "core") { 65 | var dest = flows.get(id); 66 | //console.log("E-Red msg local delivery to", id, typeof msg, msg); 67 | if (dest) dest.receive(msg); 68 | return; 69 | } 70 | 71 | // remote message 72 | if (typeof msg !== "string") msg = JSON.stringify(msg); 73 | if (ered_gateway && ered_started) { 74 | // remote message, we have a way to send it, doit 75 | log.info("E-Red msg sent to "+board+"/"+kind+"/"+id); 76 | ered_gateway.Outbound(board, kind, id, msg); 77 | } else if (kind != "msg") { 78 | // remote message, no way (yet) to send it 79 | // we queue set-up/config messages but not regular data messages 80 | // TODO: limit or flush the queue at some point so we don't fill memory 81 | // if no gateway ever exists 82 | log.info("E-Red msg queued for "+board+"/"+kind+"/"+id); 83 | ered_tq.push({b:board, k:kind, i:id, m:msg}); 84 | } else { 85 | // no way to send regular message, drop it 86 | log.warn("E-Red has no gateway for board " + board); 87 | } 88 | } 89 | exports.SendTo = sendTo; 90 | 91 | // Flush queue of pending messages 92 | function flushQueue() { 93 | while (ered_tq.length > 0) { 94 | var m = ered_tq.shift(); 95 | sendTo(m.b, m.k, m.i, m.m); 96 | } 97 | } 98 | 99 | // Hook nodes-started event so we can flush the queue of pending messages 100 | events.on('nodes-started', function() { 101 | ered_started = true; 102 | if (ered_gateway) flushQueue(); 103 | }); 104 | 105 | // Wish this existed? 106 | //events.on('nodes-stopped', function() { 107 | // ered_started = false; 108 | //}); 109 | 110 | // updateWires iterates through all outbound wires of a node and updates the remote board 111 | // that owns the node with the routing info. 112 | function updateWires(n) { 113 | //console.log("Node has", n.wires.length, "outputs"); 114 | var info = []; 115 | var prefix = ered_gateway ? ered_gateway.prefix : "e-red"; // yuck 116 | // iterate through all outputs of the node 117 | for (var i=0; i 16 | 17 | 18 | 19 | 20 | 21 | 22 | 51 | 52 | 53 | 54 | 62 | 63 | 84 | -------------------------------------------------------------------------------- /e-red/func.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016 Thorsten von Eicken 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | 17 | // Espruino-red function node 18 | 19 | module.exports = function(RED) { 20 | "use strict"; 21 | // require any external libraries we may need.... 22 | var ered = require("./ered"); 23 | 24 | // The main node definition - most things happen in here 25 | function ERedFunction(n) { 26 | var node = this; 27 | node.name = n.name; 28 | 29 | // Create a RED node 30 | RED.nodes.createNode(node, n); 31 | 32 | // Store the node configuration (as defined in the .html) into ered_config so it can be 33 | // shipped to Espruino 34 | node.board = n.board; 35 | node.ered_config = { code: n.code }; 36 | 37 | //console.log("E-Red function constructor. I am:", node.type, node.id, "on", n.board); 38 | //console.log("Node:", JSON.stringify(node)); 39 | //console.log("N:", JSON.stringify(n)); 40 | 41 | // add this node to the ered registry, this will instantiate it in Espruino as well 42 | ered.RegisterNode(node); 43 | 44 | // respond to inputs.... 45 | this.on('input', function (msg) { 46 | //console.log("E-Red function input. I am:", node.kind, node.id); 47 | // this node lives on Espruino, so we need to forward the message there... 48 | ered.SendTo(node.board, "msg", node.id, msg); 49 | }); 50 | 51 | // Called when the node is shutdown - e.g. on redeploy 52 | this.on("close", function() { 53 | // deregister, which will tell Espruino to remove the node as well 54 | ered.DeregisterNode(node); 55 | }); 56 | } 57 | 58 | // Register the node by name. This must be called before overriding any of the 59 | // Node functions. 60 | RED.nodes.registerType("e-red function", ERedFunction); 61 | 62 | //console.log("E-RED:", module.parent.id); 63 | } 64 | -------------------------------------------------------------------------------- /e-red/gateway.html: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 44 | 45 | 46 | 47 | 61 | 62 | 63 | 64 | 84 | -------------------------------------------------------------------------------- /e-red/gateway.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016 Thorsten von Eicken 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | 17 | // Espruino-red gateway 18 | 19 | module.exports = function(RED) { 20 | "use strict"; 21 | // require any external libraries we may need.... 22 | var ered = require("./ered"); 23 | 24 | // The main node definition - most things happen in here 25 | function ERedGateway(n) { 26 | var node = this; 27 | node.name = n.name; 28 | node.prefix = n.prefix; 29 | 30 | // Create a RED node 31 | RED.nodes.createNode(node, n); 32 | 33 | //console.log("E-Red gateway constructor:", node.name, node.id, "prefix="+node.prefix); 34 | 35 | // input messages are received from Espruino and need to be forwarded to the actual 36 | // target node. Each message must have a topic and a payload, the topic must have a 37 | // structure of /e-red/core/msg/ 38 | this.on('input', function (msg) { 39 | //console.log("E-Red gateway incoming message:", msg); 40 | var t = msg.topic.split('/'); 41 | ered.SendTo(t[1], t[2], t[3], JSON.parse(msg.payload)); 42 | }); 43 | 44 | this.on("close", function() { 45 | //node.warn("E-Red gateway close."); 46 | ered.DeregisterGateway(node.id); 47 | }); 48 | 49 | this.Outbound = function(board, kind, id, payload) { 50 | var msg = { 51 | topic: node.prefix+"/"+board+"/"+kind+"/"+id, 52 | payload: payload, 53 | retain: kind !== "msg", 54 | }; 55 | //console.log("E-Red gateway outbound message: topic="+msg.topic, "retain="+msg.retain, "payload="+payload); 56 | node.send(msg); 57 | }; 58 | 59 | // register as gateway with ered 60 | ered.RegisterGateway(node); 61 | } 62 | 63 | // Register the node by name. This must be called before overriding any of the 64 | // Node functions. 65 | RED.nodes.registerType("e-red gateway", ERedGateway); 66 | } 67 | -------------------------------------------------------------------------------- /e-red/icons/chip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeelabs/espruino-red/f8b57e5148e5e295a07a3a494095df00ac6d8b04/e-red/icons/chip.png -------------------------------------------------------------------------------- /e-red/mqttq.html: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 33 | 34 | 35 | 36 | 46 | 47 | 67 | -------------------------------------------------------------------------------- /e-red/mqttq.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016 Thorsten von Eicken 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | 17 | // Espruino-red function node 18 | 19 | module.exports = function(RED) { 20 | "use strict"; 21 | 22 | function MQTTqueue(n) { 23 | var node = this; 24 | node.name = n.name; 25 | node.queue = []; 26 | this.broker = n.broker; 27 | this.brokerConn = RED.nodes.getNode(this.broker); 28 | 29 | // Create a RED node 30 | RED.nodes.createNode(node, n); 31 | 32 | if (this.brokerConn) { 33 | this.status({fill:"red",shape:"ring",text:"common.status.disconnected"}); 34 | 35 | this.on('input', function (msg) { 36 | if (msg.retain && (msg.retain === true || msg.retain === "true") && 37 | node.brokerConn && !node.brokerConn.connected) { 38 | // looks like we're not currently connected to the broker, so we'll queue messages 39 | node.queue.push(msg); 40 | //console.log("MQTT queue: holding message:", msg.topic); 41 | return; 42 | } 43 | // broker is connected or we don't care, just forward the message unchanged 44 | //console.log("MQTT queue: forwarding message:", msg.topic); 45 | node.send(msg); 46 | }); 47 | 48 | if (this.brokerConn.connected) { 49 | node.status({fill:"green",shape:"dot",text:"common.status.connected"}); 50 | } 51 | node.brokerConn.register(node); 52 | 53 | // interpose our own status function so we get notified when the broker connects and 54 | // we can flush queued messages. Yuck!!! 55 | node.origStatus = node.status; 56 | node.status = function(x) { 57 | //console.log("MQTT queue: broker", x.text); 58 | // if we have a queue and we seem to be connected: flush the queue 59 | if (node.queue.length > 0 && node.brokerConn.connected) { 60 | node.log("Flushing queue with " + node.queue.length + " messages"); 61 | node.send([node.queue]); 62 | node.queue = []; 63 | } 64 | return node.origStatus(x); 65 | }; 66 | 67 | this.on("close", function(done) { 68 | if (node.brokerConn) node.brokerConn.deregister(node, done); 69 | }); 70 | 71 | } else { 72 | this.error(RED._("mqtt.errors.missing-config")); 73 | } 74 | } 75 | 76 | RED.nodes.registerType("mqtt queue", MQTTqueue); 77 | } 78 | -------------------------------------------------------------------------------- /e-red/timer.html: -------------------------------------------------------------------------------- 1 | 16 | 17 | 34 | 35 | 39 | 40 | 61 | -------------------------------------------------------------------------------- /e-red/timer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016 Thorsten von Eicken 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | 17 | module.exports = function(RED) { 18 | "use strict"; 19 | var ered = require("./ered"); 20 | 21 | // The main node definition - most things happen in here 22 | function ERedTimer(n) { 23 | var node = this; 24 | node.name = n.name; 25 | 26 | // Create a RED node 27 | RED.nodes.createNode(node, n); 28 | 29 | // Store the node configuration (as defined in the .html) into ered_config so it can be 30 | // shipped to Espruino 31 | node.board = n.board; 32 | node.ered_config = { rate: n.rate }; 33 | // add this node to the ered registry, this will instantiate it in Espruino as well 34 | ered.RegisterNode(node); 35 | 36 | this.on('input', function (msg) { 37 | // this node lives on Espruino, so we need to forward the message there... 38 | ered.SendTo(node.board, "msg", node.id, msg); 39 | }); 40 | 41 | this.on("close", function() { 42 | // deregister, which will tell Espruino to remove the node as well 43 | ered.DeregisterNode(node); 44 | }); 45 | } 46 | 47 | RED.nodes.registerType("e-red timer", ERedTimer); 48 | } 49 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "espruino-red", 3 | "version" : "0.0.1", 4 | "description" : "Nodes that run directly on a microcontroller with Espruino", 5 | "dependencies": { 6 | }, 7 | "keywords": [ "node-red" ], 8 | "xxnode-red" : { 9 | "nodes": { 10 | "e-red function": "e-red/func.js", 11 | "e-red transponder": "e-red/transponder.js" 12 | } 13 | } 14 | } 15 | --------------------------------------------------------------------------------