diff --git a/hardware/Arduino/35-arduino.html b/hardware/Arduino/35-arduino.html index 760bebac..258069c9 100644 --- a/hardware/Arduino/35-arduino.html +++ b/hardware/Arduino/35-arduino.html @@ -4,21 +4,20 @@ @@ -177,6 +161,10 @@ @@ -235,7 +216,7 @@ color:"#3fadb5", defaults: { name: {value:""}, - pin: {value:"", validate: function(v) { + pin: {value:"",validate: function(v) { var ct = $("#node-input-state").val() || this.state; return ct === 'STRING' || (v !== ''); }}, diff --git a/hardware/Arduino/35-arduino.js b/hardware/Arduino/35-arduino.js index 5ec67787..8958bd70 100644 --- a/hardware/Arduino/35-arduino.js +++ b/hardware/Arduino/35-arduino.js @@ -1,8 +1,8 @@ -/* +/* ************************************************************ - This is a "Firmata protocol" based communication between + This is a "Firmata protocol" based communication between Node-RED and a connected board running firmata firmware. - It is working with many boards, not just Arduino. + It is working with many boards, not just Arduino. https://github.com/node-red/node-red-nodes ************************************************************ @@ -10,10 +10,10 @@ 1. First, the in/Out nodes will wait, until the "Main-board" = FirmataBoard is: - connecting - than getting the Version-info from board - - after success -> those "child-Nodes" getting notified by this event: nodeIn.on(c_brdStateChanged, function() + - after success -> those "child-Nodes" getting notified by this event: nodeIn.on(c_brdStateChanged, function() - the firmware-level board name is getting stored at: brdNode.FirmwareName See: Const BdStates ... and ... function startBoardLoopTimer() - + 1.2 If it fails, because of: - no board is set - no COM-port is set @@ -32,7 +32,7 @@ 3. TODO: periodically do a TEST to see, if the connection still lives? ! Warning: - board.Ready gets true, once Firmata: version+capabilities finished reading. + board.Ready gets true, once Firmata: version+capabilities finished reading. Firmata's: board.Pins[] array record structure: { @@ -68,17 +68,17 @@ Todo: (later) - flushDigitalPorts() = setting array of ports at once, not one-by-one - 1x queryPinState() vs. digitalRead() ... at startup? Need to investigate - digitalRead() adds a Listener - - Firmware-Name: show board name on Node-RED panel, which was set at firmata firmware at startup: + - Firmware-Name: show board name on Node-RED panel, which was set at firmata firmware at startup: get-name function? - - update Baudrate during run via: transport.update() ... see: + - update Baudrate during run via: transport.update() ... see: - new module name: red-firmata + new module name: red-firmata - put / get functions - type identify by number, not string "ANALOG" RED.events.on('runtime-state', handleRuntimeState) in the onpalleteadd function. After the first time it detects obj.state === 'start' - + Other ideas (later): - flushDigitalPorts() = setting array of ports at once, not one-by-one @@ -99,14 +99,14 @@ Memo for debugging: module.exports = function(RED) { "use strict"; - const { SerialPort } = require('serialport'); // changed format SP -> to {SP} at v9 => v10. See: https://serialport.io/docs/guide-upgrade + const SP = require('serialport'); const firmataBoard = require('./lib/firmata'); - + let moreLogs = RED.settings.verbose || false; // enable this for "debugging" // "global" constants: (these are used at Emitters too) - const c_fbr = " -- FirmataBoard:"; // this is used to print consol-log messages equally. + const c_fbr = " -- FirmataBoard:"; // this is used to print consol-log messages equally. const c_ANALOG = "ANALOG"; const c_INPUT = "INPUT" ; const c_PULLUP = "PULLUP"; // also input, with resistor activated @@ -122,7 +122,7 @@ module.exports = function(RED) { const c_invalidPin = "Invalid pin: "; - const c_brdStateChanged = "BrdStatCh"; // used by board-emitter + children's .on() Combined with BoardNode's + const c_brdStateChanged = "BrdStatCh"; // used by board-emitter + children's .on() Combined with BoardNode's const c_brdReady = "brdReady" ; // same as ^c_brdStateCh.. // state types of the Firmata Board: @@ -138,28 +138,28 @@ module.exports = function(RED) { OK : 3 // Ready }); - const c_ring = "ring"; + const c_ring = "ring"; const c_dot = "dot" ; const c_red = "red" ; const c_green = "green" ; const c_yellow = "yellow"; const c_blue = "blue" ; const c_grey = "grey" ; - + // global error handling for uncaught errors: process.on('uncaughtException', (err, origin) => { const e = "!!! Unhandled error in: [35-arduino.js] node-red module. >> " + err + " >> ORIGIN: [%o]"; try { this.error( e, origin ); - } catch (rrr) { + } catch (rrr) { console.error( e, origin ); } }); - RED.events.on("deploy", function() { // sadly this is not getting triggered on DEPLOY button click :-( - console.log(c_fbr + "MAIN - deploy happened!"); // manual says: "A new flow has been deployed" https://nodered.org/docs/api/ui/events/ - }); + // RED.events.on("deploy", function() { // sadly this is not getting triggered on DEPLOY button click :-( + // console.log(c_fbr + "MAIN - deploy happened!"); // manual says: "A new flow has been deployed" https://nodered.org/docs/api/ui/events/ + // }); function resetBoard(_brdNode){ @@ -172,7 +172,7 @@ module.exports = function(RED) { } catch(err) { _brdNode.error(c_fbr + "sending: reset failed. Error: " + err); - } + } }; @@ -180,7 +180,7 @@ module.exports = function(RED) { const ndStats = Object.freeze({ // (freeze = immutable record) noBoard : -3, // no firmata Board specified wrongPin : -2, // Error - pinConflict : -1, // Error: multiple nodes are set with same pin + pinConflict : -1, // Error: multiple nodes are set with same pin start : 0, equalsBoard : 1, // the node will reflect it's parent-board's status. Like: "no COM port set" OK : 3 @@ -189,16 +189,16 @@ module.exports = function(RED) { function updateNodeStatus(_n, new_stat) { if (_n.parentNode == null) new_stat = ndStats.noBoard; - if (((_n.parentNode != null) && (_n.parentNode.b_stat !== BdStates.OK)) + if (((_n.parentNode != null) && (_n.parentNode.b_stat !== BdStates.OK)) || (new_stat === ndStats.equalsBoard) ) // if the main board has some error, it has priority, Except: [noBoard, missingPin pinConflict] - { + { _n.n_status = ndStats.equalsBoard; switch (_n.parentNode.b_stat) { - case BdStates.permanentError: _n.status({fill:c_red , shape:c_ring, text:"Permanent Error: Board!"}); break; // -9 - case BdStates.portNotSet : _n.status({fill:c_red , shape:c_ring, text:"Error: Port not set!"}); break; // -4 - case BdStates.unknownError : _n.status({fill:c_red , shape:c_ring, text:"Unknown Error: Board!"}); break; // -3 - case BdStates.disconnected : _n.status({fill:c_red , shape:c_ring, text:"Disconnected!"}); break; - case BdStates.tryReconnect : _n.status({fill:c_red , shape:c_dot , text:"Try Reconnecting.."}); break; + case BdStates.permanentError: _n.status({fill:c_red , shape:c_ring, text:"Permanent Error: Board"}); break; // -9 + case BdStates.portNotSet : _n.status({fill:c_red , shape:c_ring, text:"Error: Port not set"}); break; // -4 + case BdStates.unknownError : _n.status({fill:c_red , shape:c_ring, text:"Unknown Error: Board"}); break; // -3 + case BdStates.disconnected : _n.status({fill:c_red , shape:c_ring, text:"Disconnected"}); break; + case BdStates.tryReconnect : _n.status({fill:c_red , shape:c_dot , text:"Reconnecting.."}); break; case BdStates.gettingVersion: _n.status({fill:c_blue , shape:c_dot , text:"Getting name+version"}); break; case BdStates.connecting : _n.status({fill:c_grey , shape:c_dot , text:"node-red:common.status.connecting"}); break; default : _n.status({}); break; @@ -208,9 +208,9 @@ module.exports = function(RED) { if (_n.n_status === new_stat) return; _n.n_status = new_stat; switch (new_stat) { - case ndStats.noBoard : _n.status({fill:c_red , shape:c_ring, text:"Error: No Board!"}); break; // -3 - case ndStats.wrongPin : _n.status({fill:c_red , shape:c_ring, text:"Error: Pin not set!"}); break; // -2 - case ndStats.pinConflict : _n.status({fill:c_red , shape:c_ring, text:"Error: Pin Conflict!"}); break; // -1 + case ndStats.noBoard : _n.status({fill:c_red , shape:c_ring, text:"Error: No board"}); break; // -3 + case ndStats.wrongPin : _n.status({fill:c_red , shape:c_ring, text:"Error: Pin not set"}); break; // -2 + case ndStats.pinConflict : _n.status({fill:c_red , shape:c_ring, text:"Error: Pin conflict"}); break; // -1 case ndStats.start : _n.status({fill:c_grey , shape:c_ring, text:"...start"}); break; // 0 // case ndStats.equalsBoard : = 1 case ndStats.OK : _n.status({fill:c_green , shape:c_ring, text:"OK"}); break; // +3 @@ -221,12 +221,12 @@ module.exports = function(RED) { function pinAlreadyUsed (_parentNode, _newNode) { // Check, if there is already a Node registered with same pin -> report pin-conflict if (_parentNode.myChirdren.length === 0) return false; - if (([c_RESET, c_STRING, c_SYSEX, c_INTER]).includes(_newNode.pinType)) return false; + if (([c_RESET, c_STRING, c_SYSEX, c_INTER]).includes(_newNode.type)) return false; let _pin = _newNode.pin; - if (_newNode.pinType === c_ANALOG) { + if (_newNode.type === c_ANALOG) { if (_newNode.pin >= _parentNode.board.analogPins.length) return false; // wrong pin number - _pin = _parentNode.board.analogPins[_newNode.pin]; // get from the reference Like: [26,27,28,29] _pin=3 -> _pin=29 + _pin = _parentNode.board.analogPins[_newNode.pin]; // get from the reference Like: [26,27,28,29] _pin=3 -> _pin=29 } for (let i = 0; i < _parentNode.myChirdren.length; i++) { const _ch = _parentNode.myChirdren[i]; @@ -239,7 +239,7 @@ module.exports = function(RED) { return true; } - if ( _ch.pinType === c_ANALOG) { + if ( _ch.type === c_ANALOG) { if (_pin === _parentNode.board.analogPins[_ch.pin]) return reportErrorAndExit(); // found similar! Exit } else { if (_pin === _ch.pin) return reportErrorAndExit(); // found similar! Exit @@ -266,21 +266,21 @@ module.exports = function(RED) { let brdNode = this; brdNode.b_stat = BdStates.start; // the status of the board brdNode.name = _setup.name || ""; - brdNode.samplingInt= _setup.samplingInt || 200; + brdNode.samplingInt= _setup.samplingInt || 250; if ((_setup.log2consol || moreLogs) === true) moreLogs = true; brdNode.port = _setup.device || ""; // port path. Like: "COM3" or "/dev/serial/by-id/usb-Arduino_RaspberryPi_Pico_076461E62D414FE3-if00" - // see also: "n.settings.serialport.path" . Sadly the original HTML is named it "device" :-( + // see also: "n.settings.serialport.path" . Sadly the original HTML is named it "device" :-( brdNode.FirmwareName = ""; brdNode.board = null; // the actual Firmata-Board brdNode.emitBaseStr = "" + brdNode.id + "-"; // for generating unique string to emit + subcribe to it. brdNode.myChirdren = []; brdNode.loop = null; // timer - brdNode.loopWaitMs = 0; // How many millisecond to wait until re-trying. This will grow in time dynimically, up to: 10sec + brdNode.loopWaitMs = 0; // How many millisecond to wait until re-trying. This will grow in time dynimically, up to: 10sec brdNode.closing = false; //TODO: used to distinguish between normal close (true) or unwanted disconnection (cable / wifi) const startOptions = { samplingInterval: brdNode.samplingInt, - serialport: { + serialport: { baudRate: 57600, // TODO: test, if it would be possible to increase speed during run via fimata.Update() path : brdNode.port } @@ -312,23 +312,23 @@ module.exports = function(RED) { const _t = brdNode.board.transport; // shorter form // if everything is fine. Startup already happened once, (and no disconnection since than,) so no need to run more times. - if ((brdNode.b_stat === BdStates.OK) && (brdNode.board != null) && (brdNode.board.isReady) && (_t.isOpen)) { + if ((brdNode.b_stat === BdStates.OK) && (brdNode.board != null) && (brdNode.board.isReady) && (_t.isOpen)) { if (moreLogs) console.info(c_fbr + " is already running fine. No need to start again. Port:" + brdNode.port ); -// TODO: send a test: queryPinState(pin, callback) +// TODO: send a test: queryPinState(pin, callback) brdNode.b_stat = BdStates.OK; brdNode.loopWaitMs = 0; // reset return; } let _newState = BdStates.connecting; // first time start - if (brdNode.b_stat < 0) + if (brdNode.b_stat < 0) _newState = BdStates.tryReconnect; - else + else if (!_t.opening && _t.isOpen && !brdNode.board.isReady) _newState = BdStates.gettingVersion; // first time start updateBrdState( _newState ); - if (!_t.opening && !_t.isOpen) // if not opening and not opened + if (!_t.opening && !_t.isOpen) // if not opening and not opened _t.open(); startBoardLoopTimer(); // increase time and check again @@ -343,7 +343,7 @@ module.exports = function(RED) { if (brdNode.board === null) {// if board does not exists yet ... brdNode.b_stat = BdStates.connecting; // first time start - if (moreLogs) brdNode.log(c_fbr + "creating new instance. Port:" + brdNode.port + " | Sampling-interval=" + brdNode.samplingInt); + if (moreLogs) brdNode.log(c_fbr + "creating new instance. Port:" + brdNode.port + " | Sampling-interval=" + brdNode.samplingInt); // creating new board, starting async promice to report state changes: brdNode.board = new firmataBoard(brdNode.port, startOptions, function(e) { // (port, options, callback) @@ -384,7 +384,7 @@ module.exports = function(RED) { // Firmata-board emitters: brdNode.board.on('error', function(err) { updateBrdState(BdStates.unknownError); - if (moreLogs) brdNode.error(c_fbr + ' Error: ' + JSON.stringify(err) ); //+ + if (moreLogs) brdNode.error(c_fbr + ' Error: ' + JSON.stringify(err) ); //+ }); // "connect" is called, once serial communication is established. After that queryFirmware is called. See: "ready" @@ -395,17 +395,17 @@ module.exports = function(RED) { updateBrdState(BdStates.OK); else { if (brdNode.board.versionReceived) brdNode.warn("versionReceived but FirmwareName=[]"); - updateBrdState(BdStates.gettingVersion); + updateBrdState(BdStates.gettingVersion); startBoardLoopTimer(); }; }); - // "ready" event is called, after the Firmware name+version + capabilities got querried within 5000ms successfully - brdNode.board.on('ready', function() { + // "ready" event is called, after the Firmware name+version + capabilities got querried within 5000ms successfully + brdNode.board.on('ready', function() { brdNode.closing = false; - brdNode.FirmwareName = brdNode.board.firmware.name; + brdNode.FirmwareName = brdNode.board.firmware.name; updateBrdState(BdStates.OK); - brdNode.log(RED._("arduino.status.connected",{device:brdNode.port}) + + brdNode.log(RED._("arduino.status.connected",{device:brdNode.port}) + " Firmware name:[" + brdNode.FirmwareName + "] " + RED._("arduino.status.version",{version: brdNode.board.version.major +"."+ brdNode.board.version.minor})); // notifying all children @@ -416,61 +416,65 @@ module.exports = function(RED) { }); brdNode.board.on('close', function(removed, done) { // Firmata (transport) closing -// todo : removed +// todo : removed updateBrdState(BdStates.disconnected); resetBoard(brdNode); // this will try to send a "last minute" signal to the board to: reset. if ( ! brdNode.closing) brdNode.error(RED._("arduino.status.portclosed")); - if (done !== undefined) done(); + if (done) { done(); } }); brdNode.board.on('disconnect', function() { updateBrdState(BdStates.disconnected); - if (moreLogs) brdNode.log(c_fbr + "Disconnected. Port:" + brdNode.port + " Firmware Name: ["+ brdNode.FirmwareName +"]"); + if (moreLogs) brdNode.log(c_fbr + "Disconnected. Port:" + brdNode.port + " Firmware Name: ["+ brdNode.FirmwareName +"]"); if ( ! brdNode.closing) startBoardLoopTimer(); // do not start, if proper closing is happening }); }; -//debugger +debugger // START board initialization the first time startupBrd(); - if (brdNode.loop == null) + if (brdNode.loop == null) { startBoardLoopTimer(); - else - if (moreLogs) brdNode.log(c_fbr + "already present. Loop start skipped. "); + } + else { + if (moreLogs) { + brdNode.log(c_fbr + "already present. Loop start skipped. "); + } + } // brdNode.removeAllListeners('close'); brdNode.on('close', function(removed, done) { // the Node itself is getting destoyed memo: function(done) did not work, TypeError brdNode.closing = true; if (!removed) updateBrdState(BdStates.disconnected); // this will notify clients too clearTimeout(brdNode.loop); brdNode.loop = null; - if ((brdNode.board == null) || (brdNode.board.transport == null) ) { - if (done !== undefined) done(); - return; - } + if ((brdNode.board == null) || (brdNode.board.transport == null) ) {done(); return; } if (brdNode.board.transport.closing) { - if (moreLogs) { brdNode.log(c_fbr + "Nothing to do, because this port is already closing: " + brdNode.port); } - if (done !== undefined) done(); + if (moreLogs) { brdNode.log(c_fbr + "Nothing to do, because this port is already closing: " + brdNode.port); } + if (done) { done(); } return;// EXIT }; if (moreLogs) { brdNode.log(c_fbr + "Trying to close port:" + brdNode.port); } - + if (brdNode.board.transport.isOpen) { //resetBoard(brdNode); try { brdNode.board.transport.close(function(err) { if (moreLogs) { brdNode.log(RED._("arduino.status.portclosed") + err?"Err: ":"" , err); } }); - if (done !== undefined) done(); + if (done) { done(); } } - catch(e) { + catch(e) { if (moreLogs) { brdNode.error("Could not close port: " + brdNode.port + (e?"Err: ":"") , e); } - } + } + } + else { + if (done) { done(); } + return; } - else { if (done !== undefined) done(); return;} }); } // *** REGISTERING the (parent) board node *** // @@ -481,18 +485,18 @@ module.exports = function(RED) { /** * The Input (child) Node * - * pinType: can be ANALOG, DIGITAL ... (Warning! in html it is called "state") + * type: can be ANALOG, DIGITAL ... (Warning! in html it is called "state") */ function DuinoNodeIn(_setupIn) { RED.nodes.createNode(this, _setupIn); // n = setupNode let nodeIn = this; nodeIn.n_status = ndStats.start; nodeIn.pin = _setupIn.pin; - nodeIn.pinType = _setupIn.state; // "state" is a wrong naming at the html definition. It is the current "Type" of the Pin. (Like: "ANALOG" or "PWM"...) + nodeIn.type = _setupIn.state; // "state" is a wrong naming at the html definition. It is the current type of the Pin. (Like: "ANALOG" or "PWM"...) nodeIn.parentNode = RED.nodes.getNode(_setupIn.arduino); // n.arduino = The ArduinoNode's ID. It is defined at `package.json`: "node-red": {... "nodes": { "arduino": "35-arduino.js" } let loopIn = null; // timer - // The creation process + // The creation process if (typeof nodeIn.parentNode === "object") { nodeIn.frmBoard = nodeIn.parentNode.board; // a shorter reference the parent-node's Firmata-Board class if (nodeIn.frmBoard == null) { @@ -506,9 +510,9 @@ module.exports = function(RED) { updateNodeStatus(nodeIn, nodeIn.n_status); }); - // *** first initialization *** // + // *** first initialization *** // let startupIn = function() { - if (moreLogs) console.info(c_fbr + "Node-In created." + (nodeIn.name ? " Name=["+ nodeIn.name +"]" : "") + " Pin=" + nodeIn.pin + " Type=" + nodeIn.pinType); + if (moreLogs) console.info(c_fbr + "Node-In created." + (nodeIn.name ? " Name=["+ nodeIn.name +"]" : "") + " Pin=" + nodeIn.pin + " Type=" + nodeIn.type); if (loopIn !== null) {clearTimeout(loopIn); loopIn = null}; //nodeIn.frmBoard.setMaxListeners(0); Deleted 2025-03-17. DO NOT USE THIS ! See: https://stackoverflow.com/a/44143119 // nodeIn.frmBoard.setMaxListeners(11); // no need either @@ -516,35 +520,35 @@ module.exports = function(RED) { updateNodeStatus(nodeIn, ndStats.equalsBoard); let doit = function() { - if (pinAlreadyUsed(nodeIn.parentNode, nodeIn) === true) return; // EXIT; // pin-conflict check - if (moreLogs) console.info(c_fbr + "Node-In init started." + (nodeIn.name ? " Name=["+ nodeIn.name +"]" : "") + " Pin=" + nodeIn.pin + " Type=" + nodeIn.pinType); + if (pinAlreadyUsed(nodeIn.parentNode, nodeIn) === true) return; // EXIT; // pin-conflict check + if (moreLogs) console.info(c_fbr + "Node-In init started." + (nodeIn.name ? " Name=["+ nodeIn.name +"]" : "") + " Pin=" + nodeIn.pin + " Type=" + nodeIn.type); let goodPin = (nodeIn.pin != null) && !isNaN(nodeIn.pin) && (nodeIn.pin >=0) && (nodeIn.pin < nodeIn.frmBoard.pins.length); - if (goodPin === true && nodeIn.pinType === c_ANALOG) { + if (goodPin === true && nodeIn.type === c_ANALOG) { goodPin = ( nodeIn.pin in nodeIn.frmBoard.analogPins ); // found analogue pin - if (!goodPin){ nodeIn.error( c_invalidPin + nodeIn.pin - + (nodeIn.frmBoard.analogPins ? ". Only these analogue pin numbers are allowed: [0.." - + (nodeIn.frmBoard.analogPins.length-1) + "]/n Reference GPIOs:" + nodeIn.frmBoard.analogPins + if (!goodPin){ nodeIn.error( c_invalidPin + nodeIn.pin + + (nodeIn.frmBoard.analogPins ? ". Only these analogue pin numbers are allowed: [0.." + + (nodeIn.frmBoard.analogPins.length-1) + "]/n Reference GPIOs:" + nodeIn.frmBoard.analogPins : "NO analogue pins are allowed with this firmware / board!")); //console.log('pins: %o', nodeIn.board.analogPins); // TESTS //const jsonString = JSON.stringify(node.board.pins); -//console.log( jsonString ); +//console.log( jsonString ); //node.error( jsonString ); // test } } if (goodPin === true) { try { - if (nodeIn.pinType === c_ANALOG) { nodeIn.frmBoard.pinMode(nodeIn.pin, 0x02); } else - if (nodeIn.pinType === c_INPUT ) { nodeIn.frmBoard.pinMode(nodeIn.pin, 0x00); } else - if (nodeIn.pinType === c_PULLUP) { nodeIn.frmBoard.pinMode(nodeIn.pin, 0x0B); } + if (nodeIn.type === c_ANALOG) { nodeIn.frmBoard.pinMode(nodeIn.pin, 0x02); } else + if (nodeIn.type === c_INPUT ) { nodeIn.frmBoard.pinMode(nodeIn.pin, 0x00); } else + if (nodeIn.type === c_PULLUP) { nodeIn.frmBoard.pinMode(nodeIn.pin, 0x0B); } updateNodeStatus(nodeIn, ndStats.OK); } catch (setPinError) { updateNodeStatus(nodeIn, ndStats.wrongPin); nodeIn.error(c_fbr + "Input Pin ["+ nodeIn.pin +"] setting error:" + setPinError); return; } - + // subscribing to pin-event listeners. These will call at firmata-io.js: board.addListener(`analog-read-${pin}`, callback); - if (nodeIn.pinType === c_ANALOG) { + if (nodeIn.type === c_ANALOG) { nodeIn.frmBoard.analogRead(nodeIn.pin, function(v) { if (nodeIn.n_status !== ndStats.OK) updateNodeStatus(nodeIn, ndStats.OK); if (v !== nodeIn.oldval) { @@ -553,7 +557,7 @@ module.exports = function(RED) { } }); } else - if (nodeIn.pinType === c_INPUT) { + if (nodeIn.type === c_INPUT) { nodeIn.frmBoard.digitalRead(nodeIn.pin, function(v) { if (nodeIn.n_status !== ndStats.OK) updateNodeStatus(nodeIn, ndStats.OK); if (v !== nodeIn.oldval) { @@ -563,7 +567,7 @@ module.exports = function(RED) { }); // nodeIn.frmBoard.queryPinState(nodeIn.pin, callback); // TODO ? } else - if (nodeIn.pinType === c_PULLUP) { + if (nodeIn.type === c_PULLUP) { nodeIn.frmBoard.digitalRead(nodeIn.pin, function(v) { if (nodeIn.n_status !== ndStats.OK) updateNodeStatus(nodeIn, ndStats.OK); if (v !== nodeIn.oldval) { @@ -573,7 +577,7 @@ module.exports = function(RED) { }); // nodeIn.frmBoard.queryPinState(nodeIn.pin); // TODO ? } else - if (nodeIn.pinType == c_STRING) { + if (nodeIn.type == c_STRING) { nodeIn.frmBoard.on('string', function(v) { if (nodeIn.n_status !== ndStats.OK) updateNodeStatus(nodeIn, ndStats.OK); // if (v !== nodeIn.oldval) { //OMG! deleted 2025-03-17 @@ -591,16 +595,16 @@ module.exports = function(RED) { // wait first, until Board's capabilities are reported. Configure child node after that. - if (nodeIn.frmBoard.isReady) { - doit(); + if (nodeIn.frmBoard.isReady) { + doit(); } else { - nodeIn.once(c_brdReady, function() { + nodeIn.once(c_brdReady, function() { doit(); - }); + }); } /* if (loopIn === null) { - if (moreLogs) console.info(c_fbr + "Timeout set for loop. Name="+ nodeIn.name + " Pin=" + nodeIn.pin + " State=" + nodeIn.pinType); + if (moreLogs) console.info(c_fbr + "Timeout set for loop. Name="+ nodeIn.name + " Pin=" + nodeIn.pin + " State=" + nodeIn.type); loopIn = setTimeout(function() { if (nodeIn.running === false) { startupIn(); } }, 4500); loopIn.name = "startupIN-timeout"; } @@ -614,10 +618,10 @@ module.exports = function(RED) { } nodeIn.on('close', function(removed, done) { // if remove === true -> it means this Node is getting deleted - clearTimeout(loopIn); + clearTimeout(loopIn); if (removed) {removeFromChildren(nodeIn);} else {updateNodeStatus(nodeIn, ndStats.equalsBoard);} - if (done !== undefined) done(); + done(); }); } RED.nodes.registerType("arduino in", DuinoNodeIn); @@ -626,32 +630,17 @@ module.exports = function(RED) { /** * The Output (child) Node * - * pinType: can be ANALOG, DIGITAL ... (Warning! in html "pinType" is called "state") - */ + * type: can be ANALOG, DIGITAL ... (Warning! in html "type" is called "state") + */ function DuinoNodeOut(_setupOut) { RED.nodes.createNode(this, _setupOut); let nodeOut = this; nodeOut.n_status = ndStats.start; nodeOut.pin = _setupOut.pin; - nodeOut.pinType = _setupOut.state; // "state" is a wrong naming at the html definition. It is the current type of the Pin. (Like: "ANALOG" or "PWM"...) + nodeOut.type = _setupOut.state; // "state" is a wrong naming at the html definition. It is the current type of the Pin. (Like: "ANALOG" or "PWM"...) nodeOut.parentNode = RED.nodes.getNode(_setupOut.arduino); // _setupOut.arduino = The board config. It is defined at `package.json`: "node-red": {... "nodes": { "arduino": "35-arduino.js" } let loopOut = null; // timer - // create an event-listener outside the scope of normal inputs, for "override-reset" - if (nodeOut.pinType === c_RESET) { - nodeOut.on("input", function(msg, send, done) { - if ( Boolean(msg.payload) === true) { - try { - resetBoard(nodeOut.parentNode); - if (moreLogs) nodeOut.warn(c_fbr + "... sending reset from NR"); - } catch (_error) { - nodeOut.error(c_fbr + "Could not send RESET to the board.", _error); - }; - if (done !== undefined) done(); - } - }); - } - // Test to see, what's inside: //console.log("ou- _setupOut.arduino: %o", _setupOut.arduino); //console.log("/n ====================================================================================== /n", n.arduino); @@ -659,8 +648,8 @@ module.exports = function(RED) { //console.log("/n ====================================================================================== /n", n.arduino); //console.log("ou- type of serverConfig: " + typeof nodeOut.boardConfig); - // The creation process - if (typeof nodeOut.parentNode === "object") { + // The creation process + if (typeof nodeOut.parentNode === "object") { nodeOut.frmBoard = nodeOut.parentNode.board; // a shorter reference directly to Firmata class if (nodeOut.frmBoard == null) { updateNodeStatus(nodeOut, ndStats.noBoard); @@ -674,26 +663,26 @@ module.exports = function(RED) { }); let startupOut = function() { - if (moreLogs) console.info(c_fbr + "Node-Out created." + (nodeOut.name ? " Name=["+ nodeOut.name + "]" : "") + " Pin=" + nodeOut.pin + " Type=" + nodeOut.pinType); + if (moreLogs) console.info(c_fbr + "Node-Out created." + (nodeOut.name ? " Name=["+ nodeOut.name + "]" : "") + " Pin=" + nodeOut.pin + " Type=" + nodeOut.type); if (loopOut !== null) {clearTimeout(loopOut); loopOut = null}; - + updateNodeStatus(nodeOut, ndStats.equalsBoard); - + let doit = function() { - if (pinAlreadyUsed(nodeOut.parentNode, nodeOut) === true) return; // EXIT; // pin-conflict check - if (moreLogs) console.info(c_fbr + "Node-Out init started." + (nodeOut.name ? " Name=["+ nodeOut.name + "]" : "") + " Pin=" + nodeOut.pin + " Type=" + nodeOut.pinType); - + if (pinAlreadyUsed(nodeOut.parentNode, nodeOut) === true) return; // EXIT; // pin-conflict check + if (moreLogs) console.info(c_fbr + "Node-Out init started." + (nodeOut.name ? " Name=["+ nodeOut.name + "]" : "") + " Pin=" + nodeOut.pin + " Type=" + nodeOut.type); + if ((nodeOut.pin != null) && !isNaN(nodeOut.pin) && nodeOut.pin >=0 && nodeOut.pin < nodeOut.frmBoard.pins.length) { - if (nodeOut.pinType === c_OUTPUT) { nodeOut.frmBoard.pinMode(nodeOut.pin, 0x01); } - if (nodeOut.pinType === c_PWM ) { nodeOut.frmBoard.pinMode(nodeOut.pin, 0x03); } - if (nodeOut.pinType === c_SERVO ) { nodeOut.frmBoard.pinMode(nodeOut.pin, 0x04); } - + if (nodeOut.type === c_OUTPUT) { nodeOut.frmBoard.pinMode(nodeOut.pin, 0x01); } + if (nodeOut.type === c_PWM ) { nodeOut.frmBoard.pinMode(nodeOut.pin, 0x03); } + if (nodeOut.type === c_SERVO ) { nodeOut.frmBoard.pinMode(nodeOut.pin, 0x04); } + updateNodeStatus(nodeOut, ndStats.OK); - + nodeOut.on("input", function(msg, send, done) { if ((msg == null) || (msg.payload == null)) { // NULL input -> send warning & exit nodeOut.warn("msg.payload must not be null!"); - if (done !== undefined) done(); + done(); return; } @@ -703,7 +692,7 @@ module.exports = function(RED) { updateNodeStatus(nodeOut, ndStats.equalsBoard); resetBoard(nodeOut.parentNode); } else - if (nodeOut.pinType === c_OUTPUT) { + if (nodeOut.type === c_OUTPUT) { const str = msg.payload.toString(); if ((msg.payload === true ) || (str === "1") || (str.toLowerCase() === "on")) { nodeOut.frmBoard.digitalWrite(nodeOut.pin, nodeOut.frmBoard.HIGH); @@ -712,42 +701,42 @@ module.exports = function(RED) { nodeOut.frmBoard.digitalWrite(nodeOut.pin, nodeOut.frmBoard.LOW); }; } else - if (nodeOut.pinType === c_PWM) { + if (nodeOut.type === c_PWM) { msg.payload = parseInt((msg.payload * 1) + 0.5); // round to int if ((msg.payload >= 0) && (msg.payload <= 255)) { nodeOut.frmBoard.analogWrite(nodeOut.pin, msg.payload); } else nodeOut.warn("PWM value must be: 0..255"); } else - if (nodeOut.pinType === c_SERVO) { + if (nodeOut.type === c_SERVO) { msg.payload = parseInt((msg.payload * 1) + 0.5); if ((msg.payload >= 0) && (msg.payload <= 180)) { nodeOut.frmBoard.servoWrite(nodeOut.pin, msg.payload); } else nodeOut.warn("PWM value must be: 0..180"); } else - if (nodeOut.pinType === c_SYSEX) { + if (nodeOut.type === c_SYSEX) { nodeOut.frmBoard.sysexCommand(msg.payload); } else - if (nodeOut.pinType === c_STRING) { + if (nodeOut.type === c_STRING) { nodeOut.frmBoard.sendString(msg.payload.toString()); } else -/* if (nodeOut.pinType === c_RESET) { + if (nodeOut.type === c_RESET) { if ( Boolean(msg.payload) === true) { updateNodeStatus(nodeOut, ndStats.equalsBoard); resetBoard(nodeOut.parentNode); } - } else */ - if (nodeOut.pinType === c_INTER) { + } else + if (nodeOut.type === c_INTER) { const i = 0 + msg.payload; if (i < 10 || i > 65535) { nodeOut.warn("Invalid new interval input value (10-65535): ["+ msg.payload +"]"); return; }; nodeOut.samplingInterval = i; - nodeOut.frmBoard.setSamplingInterval(i); + nodeOut.frmBoard.setSamplingInterval(i); nodeOut.status({fill:c_yellow, shape:c_ring, text:"Interval= " + i}); } } - if (done !== undefined) done(); + done(); }); } else { @@ -756,12 +745,12 @@ module.exports = function(RED) { } } - if (nodeOut.frmBoard.isReady) { - doit(); + if (nodeOut.frmBoard.isReady) { + doit(); } else { - nodeOut.once(c_brdReady, function() { + nodeOut.once(c_brdReady, function() { doit(); - }); + }); } /* if (loopOut === null) { @@ -774,7 +763,7 @@ module.exports = function(RED) { startupOut(); } else { - updateNodeStatus(nodeOut, ndStats.noBoard); + updateNodeStatus(nodeOut, ndStats.noBoard); nodeOut.warn(c_fbr + "Out: " + RED._("arduino.errors.portnotconf")); } @@ -782,79 +771,25 @@ module.exports = function(RED) { clearTimeout(loopOut); if (removed === true) {removeFromChildren(nodeOut);} else {updateNodeStatus(nodeOut, ndStats.equalsBoard);}; - if (done !== undefined) done(); + done(); }); } RED.nodes.registerType("arduino out", DuinoNodeOut); /** - * Listing serial ports (used at Board-configuration, by clicking the SEARCH button on the right side of port editbox) + * Listing serial ports (used at Board-configuration) * - */ + */ RED.httpAdmin.get("/arduinoports", RED.auth.needsPermission("arduino.read"), function(req, res) { -/* - - (async () => { - try { - const serialList = await SerialPort.list(); + SP.list().then( ports => { const a = ports.map(p => p.comName); res.json(a); - this.log(c_fbr + "Serial ports list:", a); }, err => { - this.error('Error listing serial ports', err); + this.log('Error listing serial ports', err) } - } catch (e) { - console.log(e); - } - })(); -*/ -// Firmata Example (not working ! because SerialPort changed to async) : https://github.com/yokobond/firmata-io#with-a-transport-instance - - let _arr = ["-- no ports found --"]; - async function listSerialPorts() { - try { - const ports = await SerialPort.list(); - console.log('Available Ports:', ports); - if ((ports != null) && (Array.isArray(ports)) && ports.length !== 0) - _arr = ports.map(p => p.path); - res.json(_arr); - } catch (err) { - console.error('Error listing ports:', err); - _arr.push( err.toString ); - res.json(_arr); - } - } - - listSerialPorts(); - -/* -Expected result of each port in the array: - interface PortInfo { - path: string; - manufacturer: string | undefined; - serialNumber: string | undefined; - pnpId: string | undefined; - locationId: string | undefined; - productId: string | undefined; - vendorId: string | undefined; - } - -For example on windows: -[ - { - path: 'COM3', - manufacturer: 'Microsoft', - serialNumber: '6&35CA52AB&0&0000', - pnpId: 'USB\\VID_2E8A&PID_00C0&MI_00\\6&35CA52AB&0&0000', - locationId: '0000.0014.0000.001.000.000.000.000.000', - friendlyName: 'Soros USB-eszköz (COM3)', - vendorId: '2E8A', - productId: '00C0' - } -] -*/ + ) }); } diff --git a/hardware/Arduino/README.md b/hardware/Arduino/README.md index d105ebc3..e8ddd059 100644 --- a/hardware/Arduino/README.md +++ b/hardware/Arduino/README.md @@ -12,14 +12,11 @@ _Typically plugged in to the host via USB to extend: I/O + analogue + I2C + OneW See the [main documentation](http://nodered.org/docs/hardware/arduino.html) for details and examples of how to use this node. -See list of changes and latest features (with pictures) at: [NR's forum](https://discourse.nodered.org/t/announce-node-red-node-arduino-firmata-remastered-v1-3-3/96317). - Install ------- -Either use the Menu - Manage Palette option, -or run the following command in your Node-RED user directory - typically `~/.node-red` +Either use the Menu - Manage Palette option or run the following command in your Node-RED user directory - typically `~/.node-red` npm i --unsafe-perm node-red-node-arduino diff --git a/hardware/Arduino/locales/en-US/35-arduino.json b/hardware/Arduino/locales/en-US/35-arduino.json index 283e3883..d3a38e32 100644 --- a/hardware/Arduino/locales/en-US/35-arduino.json +++ b/hardware/Arduino/locales/en-US/35-arduino.json @@ -1,6 +1,5 @@ { "arduino": { - "version": "version: 1.3.4-20250331", "label": { "pin": "Pin:", "type": "Type:", @@ -36,12 +35,12 @@ } }, "tip": { - "io": "Note: You cannot use the same pin for both output and input!", - "port": "

On Unix/linux systems, it is recommended to choose the serial port at: by-id path, so it works always, no matter which USB port it gets re-plugged.

(It will look like this: /dev/serial/by-id/usb-Arduino_RPi_Pico_0764FE3-if00)

", + "io": "Note: You cannot use the same pin for both output and input", + "port": "

On Unix/linux systems, it is recommended to choose the serial port at: by-id path, so it is fixed, no matter if the USB gets re-plugged.

(It will look like this: /dev/serial/by-id/usb-Arduino_RPi_Pico_0764FE3-if00)

", "search": "Tip: Use search button to try to auto-detect serial port.", "pin": "Analogue pins: usually between: 0..7
RPi-Pico has a built-in temperature reader at: A03", - "log2consol": "To view Logs: If you started node-red in a console, you will be able to see them there. Otherwise type in a consol/terminal: node-red-log ", - "otherboards": "To use this module with [Raspberry Pi - Pico] / [ESP32], you need to flash to the chip a [Configurable-Firmata] firmware: https://github.com/firmata/ConfigurableFirmata", + "log2consol": "To view Logs: If you started node-red in a console, you will be able to see them there. Otherwise type in a console/terminal: node-red-log ", + "otherboards": "To use this module with Raspberry Pi Pico or ESP32, you need to flash the chip with a Configurable-Firmata firmware e.g. https://github.com/firmata/ConfigurableFirmata", "more-at-help": "See more info at the Help tab." }, "errors": { diff --git a/hardware/Arduino/package.json b/hardware/Arduino/package.json index c6ba0888..b23920ee 100644 --- a/hardware/Arduino/package.json +++ b/hardware/Arduino/package.json @@ -1,6 +1,6 @@ { "name": "node-red-node-arduino", - "version": "1.3.4-20250331", + "version": "2.0.0", "description": "A Node-RED Node, to talk to a chip as I/O extender, which is running Firmata firmware", "dependencies": { "serialport": "^12.0.0" @@ -28,16 +28,16 @@ "node": ">=18.0.0" }, "author": { - "name": "Dave Conway-Jones (version <= 1.1)", + "name": "Dave Conway-Jones", "email": "dceejay@gmail.com", "url": "http://nodered.org" }, "contributors": [{ - "name": "PizzaProgram (from version 1.2+ 2025-03-21)", + "name": "PizzaProgram (from version 2)", "email": "info@pizzaprogram.hu", "url": "https://pizzaprogram.hu" }], - + "foreground-scripts": true - + }