[Announce] node-red-node-Arduino (Firmata) remastered `v1.3.4-20250331`

... after 1+ month (200+ hours of time investment), I proudly present:

The first stable version of Arduino / Firmata module.

Currently it can be downloaded from: here v1.3.3. I'm planning to create a pull request too.

The 1.1 version currently available for download at npm + Github is:

  • not working in many ways,

so I had to rewrite it (almost) completely. (ca. 70 lines left untouched from 770.) The whole process of it is: here...

What changed:

  1. "max pin" problem solved. Now works again with Raspberry Pi - Pico (+ESP32) boards too!

  2. analogue pin checking! (NR crashed before)

  3. more help added to html = setup panels

    • number inputs for pin instead of text,
    • min-max,
    • added more tips & warning,
    • etc...

  1. msg.payload = "reset" >> resets board (more later...)

  2. added many else and constants to speed up execution

  3. renamed variables to be unique. Like: node -> brdNode, nodeIn, nodeOut

  4. added error handling for unhandled errors

  5. changed var to let everywhere

  6. added emit -> to immediate auto-update all in/out nodes
    if master-board state changed !

  7. created clear status definitions. + according visual colour + text status for each

  8. added "sampling interval" (more later ... )

  9. added: log2consol checkbox (setup)

  10. Deploy works !

  11. Partial deploy works also!! :1st_place_medal:
    Even changing 1-1 pin during run without stopping or restart.

  12. msg.payload null warning for Out-node input

  13. added new Out-node types: [RESET, INTERVAL]

  14. Event / emit exceed limit problems of v1.1 solved!

  15. Added: Name config for better naming / label showing

  16. can handle "no board" / "no com set" / "no pin set"

  17. endless "board-search" animation probably solved. (need testing!)

  18. Auto-timed connect retry (100ms - 10sec increasing)

  • so if board gets only later plugged in (after start) >> it works too!
  1. auto-pin-conflict search and show system
  2. version is shown at in+out config panel

+ all kinds of bug fixes ...

About Sampling-interval:

A value of millisecond between 10-65500 can be set ! :+1:

  • Both at startup on the Arduino setup panel,
  • and at runtime via msg.payload = 500 if node-out Type is set for this.

This will reduce or increase the speed, how (analogue) data will flow (if changed).
This is a global value. So individual pins can not be set differently at Firmata firmware.
Default value is now changed from 19ms to: 200ms, because even my high speed laptop could not handle the amount of debugs showing even for 1 pin.

  • 200ms => max 5 message will be sent pro second, if value changes
    (for each analogue pin you subscribed for).

About board-reset:

This function has 2 purpose:

  1. it will stop sending analogue values + will set all output pins to default LOW
  2. helps after restart, because the board can be re-attached to NR.

Note: it happens very fast. So if there is only a "LED blink", you will hardly notice anything, because "output" will work again after a few milliseconds.
Analogue read (and maybe digital in too!) will stop completely until flow restart.

Two ways to do it:

  1. Set Arduino-out node to: reset + send a: msg.payoload = true or 1
  2. Send msg.payload = reset to any "Out" node.

Try it yourself, and leave a comment!

5 Likes
  1. Fixed some sentences at HTML + Readme.md + Help
    (could not figure out, how to embed active URL links into help)
  2. Deleted half of the tips visible up here, as @dceejay requested at his PMs
  3. Log to console default is: OFF (unchecked)
  4. changed version number to: 1.3.3-20250330 (from ..0329)
  5. added help to "Arduino out" node too !
  6. uploaded and requested a Pull at github.

Very bad news:

Just tested:

  • if sampling interval is set to a high value (eg. 2000ms)
  • short pressing digital input will not work!

I wrote to all the help files explanations sentences which suggests: it only affects the analogue ports.

I will open an issue at Configurable-Firmata firmware page, maybe there is a workaround?

Auto-port-search finally fixed!

  1. had to rewrite both (html+JS) functions to async !
  2. added try-catch for error handling
  3. response naming changed to path ! (had to debug for hours...)
  4. added logs to both browser's console & node-red-log (console)
Available Ports: [
  {
    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'
  }
]
1 Like

News:

1. Pull Request created. Waiting for accept of: v1.3.4-20250331.


About earlier:

2. Asked the programmer of Firmata

to enhance the code to be able to accept different setup for different type of pins.

1 Like

Good news:

A code part of Configurable Firmata has been found, prevented reporting of digital pins at higher sampling rate.
Read here...

The only "side effect" is, that fluctuating pins will be reported more times,
but I think that's ok, because usually if someone is not using proper pullup-resistors, it's not a software problem.

After this pull request is accepted, everyone should upgrade his Firmata firmware.


I2C and SPI is also affected, because until now sampling rate was not effecting them.
From now on, it will!

Note:

if using unshielded or long cables, (especially in electric noisy or 110V/240V environment,) it is strongly recommended to use optocoupler. There are complete 4 channel boards under $2 price, and can handle 12V/24V to work stable even at 100/1000 meters of cable.

Changes at: v1.4.1-20250405

  1. In-nodes do not report / send new message on start!

  2. Fixed: Reported "pin conflict" at "String", "sampling rate", etc. types too.

  3. Fixed: Reported "wrong pin" at "String-in" type. (strings do not use Pins)
    _Strings nodes did not accept Pin values, like: 99

  4. String-in node can start before board-report. (no need to wait)
    This way it will report even "...starting" message coming from Firmata board.

  5. Circumvent Firmata-io is reporting "opening port" messages as "Error".
    (while it is not one.)

Noticed behaviour at disconnect:

While unplugging / disconnecting the board, sometimes it can happen, that:

  • Close event

gets called by Node-red Firmata-io, instead of:

  • (transport-) Disconnect event

In these cases it will completely close all sub-nodes, so

In-nodes will stop receiving in-values !

What will still work after reconnecting:

  • Outputs, like turning a Pin High/Low
  • String-in

PS: I have no Wifi capable boards to test, but behaviour will probably the same.
Because Node-RED does not properly propagating the current state of NR service, like:

RED.starting
RED.stopping
RED.started
RED.restarting
RED.flowRestart
RED.deploying

it is nearly impossible to distinguish, why close is happening.

If these values of NR would be available, the nodes could start a repeating:

  • reconnecting + event-re-subscibing function.

About Temperature read at RPi-Pico - SOLVED!

The problem was on CustomFirmata's Firmware side:

  1. The board definition is wrong. There are 31 pins, including 5 analogue. (Not 30 / 4!)
    So, for Pico1 board, you must change the file ...Arduino\libraries
    770. lines to this:

    #define TOTAL_ANALOG_PINS     5
    #define TOTAL_PINS              31
    

    Will add a push req. to Github code soon.

  2. Had to change the resolution to 12 bit. (See code below)

  3. Also IMHO it is essential, to use an up-to-date board library, called: Earle Phil's Arduino-Pico instead of the 4 year old "embedOS".

So, it is necessary adding following lines to the setup() part, before exporting and flashing to RPi-Pico board:

void setup()
{
...
  analogReadResolution(12);
  // send TEMP °C + RawTempValue, formatted as JSON.
#include <stdio.h>
  float c = analogReadTemp();
  char buf[50], buf2[25];
  snprintf(buf , 25, "{\"celsius\": %f, ", c);

#include <hardware/structs/adc.h>
  hw_set_bits(&adc_hw->cs, ADC_CS_TS_EN_BITS);
  delay(1); // Allow things to settle.  Without this, readings can be erratic
  int _rt = analogRead(TOTAL_PINS - 1);
  snprintf(buf2, 25, "\"rawTemp\": %d }", _rt);
  strcat  (buf , buf2);

  Firmata.sendString(F( buf ));	

//  float celsius = 27.0f - ((v * vref / 4096.0f) - 0.706f) / 0.001721f;  // const float vref = 3.3f; 
}

Result during bootup is coming through Adruino-in STRING node:

{"celsius": 23.3933, "rawTemp": 881 }

The main difference was this: analogReadResolution(12);

Without that line, only values like: 219, 220 came always. (Probably because the default resolution is 10bit.)

So now I can read the same values (779-882) with Node-RED too ! :slight_smile:
Anyway the interesting thing is, that even at max resolution (12 bit) the temp values having too big jumps if I apply the math:
Ca. 0.47 °C jumps between 2 values.

860 => 34.628632 
861 => 34.160488 
862 => 33.692379 
863 => 33.224236 
864 => 32.756092 
865 => 32.287949 
866 => 31.819803 
867 => 31.351692 
868 => 30.883549 
869 => 30.415405 
870 => 29.947262 
871 => 29.479116 
872 => 29.011007 
873 => 28.542864 
874 => 28.074718 
875 => 27.606575 
876 => 27.138432 
877 => 26.670322 
878 => 26.202177 
879 => 25.734034 
880 => 25.265888 
881 => 24.797745 
882 => 24.329636 
883 => 23.861492 
884 => 23.393347 
885 => 22.925203 
886 => 22.457060 
887 => 21.988949 
888 => 21.520805 
889 => 21.052662 
890 => 20.584518 
891 => 20.116373 
892 => 19.648264 
893 => 19.180119 
894 => 18.711975 
895 => 18.243832 
896 => 17.775688 
897 => 17.307579 
898 => 16.839434 
899 => 16.371290 
900 => 15.903146 
901 => 15.435001 
902 => 14.966892 
903 => 14.498748 
904 => 14.030603 
905 => 13.562460 
906 => 13.094316 
907 => 12.626206 
908 => 12.158062 
909 => 11.689918 
910 => 11.221774 
911 => 10.753630 
...

For those, who want to compile Firmata for RPi-Pico boards, hereby I'm sharing my improved .INO file that is working better, until it is officially released as v3.4.0+

It has many improvements compared to the standard ConfigurableFirmata example file.
(See comments inside the code)

I strongly recommend to compile it with board library, called: Earle Phil's Arduino-Pico instead of the 4 year old "embedOS".

Remember to

overwrite the boards.h file in the library folder!

/*
 * ConfigurableFirmata standard example file, for Serial + Wifi communication.
 *
 *     Read more about board support and how to install necessary libraries here:
 *     https://github.com/firmata/ConfigurableFirmata/blob/master/BoardSupport.md
 */


/* Usage: Easily enable or disable each module. 
 * To disable, put: // before "#define ... " lines.
 * Disable everything you do not need. 
 * Enable modules one by one, and re-re-try to compile to see if it works? 
 */


/* 1.1) Change this string to identify this board easier. Like: "My Pico-robot2025"
 *      Firmata will send this info to client library (which is running on a PC or RPi-Pi) after first connect.
 *      Also: version protocol + capabilities will be sent too.  */
const char* boardName  = "myFirmataBoard";


/* 1.2.) Safety Password for future use. Not implemented yet. 
 *       ! WARNING ! Never send the plain boardPassw back to the host! 
 * Mix it with the "salt" ( = random code / timestamp), you recieved from host, and create a (hexa)HASH from those two before sending it back. */
const char* boardPassw = ""; 


/* 2.1.) ENABLE_WIFI
 *       Uncomment this, to enable WIFI instead of serial communication. 
 * (Tested on ESP32, but should also work with Wifi-enabled Arduinos + PicoW + Pico2W)  
 */
//#define ENABLE_WIFI
const char* ssid       = "your-ssid";
const char* password   = "your-password";
const int NETWORK_PORT = 27016;


/* 2.2.) ... or set serial baud rate:
 * !WARNING! This value should match with the client library connection speed!
 *   (Because usually UART chips can not handle assimetric connections.) 
 * Default was 57600 for long time. The "firmata.js" client library is still using that value. 
 * If connected via USB-UART, this value has no effect, because the rate is always maximum. (1Mbit)
 * read more here: https://github.com/firmata/ConfigurableFirmata/issues/180 
 */
const int BAUD_RATE    = 1200; // 2400 3600 7200 9600 14400 28800 57600 115200 230400 460800 921600 or higher?. 


/* 3.1.) DISABLE_BLINK_VERSION
 *         Uncomment to save a couple of seconds by disabling the startup blink sequence,
 * !WARNING!  Some LEDs are connected to real GPIO ports! Like at: Arduino Nano board.
 *         So this prevents turning relays ON/OFF/ON/OFF... quickly during boot, what can be bad. :-(
 */
//#define DISABLE_BLINK_VERSION


/* 3.2.) ENABLE_START_BLINK
           Uncomment this to pause booting process by blinking the LED 2x, 7 seconds long.
					 See at: setup() at near end of this file.
           It is useful, if you need time to start a debugger process or serial monitor before boot happens. 
*/
//#define ENABLE_START_BLINK


/* 3.3.) Uncomment these 2 lines, to Set On / Off state to then opposite:
 *       (Some cheap relay boards need GND to close the circuit, and 5V to open.)
 * ! WARNING ! This "Software Solution" is not 100% safe to use! 
 * ! DANGER  ! On critical applications, (water boil, solar, industry ...) rather solve it on the the hardware side. 
 *             it is always wiser to use proper opto NPN decouplers between this board and the relays.
 */
// #define LOW  1
// #define HIGH 0 



/* 4.1) Note that the SERVO module is not supported yet on ESP32. So either disable this or patch the library
 *      More info / current state of fix: https://github.com/firmata/ConfigurableFirmata/issues/178 */
#ifndef ESP32
//  #define ENABLE_SERVO 
#endif

// 4.2.) Other modules:  (simply disable everything you do not need)  
//#define ENABLE_ONE_WIRE
//#define ENABLE_ACCELSTEPPER
//#define ENABLE_BASIC_SCHEDULER // This is rarely used and probably will not compile/work
//#define ENABLE_SERIAL // This is for using an other serial through GPIO pins. Not for default Host-USB-serial.
//#define ENABLE_I2C
//#define ENABLE_SPI
#define ENABLE_ANALOG
#define ENABLE_DIGITAL
//#define ENABLE_DHT 
#define ENABLE_FREQUENCY

// 4.3.)  SLEEP is supported only for AVR and ESP32
#if defined (ESP32) || defined (ARDUINO_ARCH_AVR)
  #define ENABLE_SLEEP
#endif

// 5.)  LED signaling the state of board. 
//#define BLINK_SERIAL_STATE // enable this if you want your LED blinking: slow/fast, if serial is: OK/not

#ifdef BLINK_SERIAL_STATE
  #define NORMAL_BLINK_INTERVAL 1000
  #define FAST_BLINK_INTERVAL 100
  #define SERIAL_TIMEOUT 2000

  unsigned long lastSerialTime = 0;
  unsigned long lastBlinkTime = 0;
  unsigned long blinkInterval = NORMAL_BLINK_INTERVAL;
  bool ledState = false;
#endif



/***********************************************
***   DO NOT CHANGE things down from here    ***
         (unless you know what you do)

     The setup() and loop() are at the end.
************************************************/
#include <ConfigurableFirmata.h>

#ifdef ENABLE_DIGITAL
  #include <DigitalInputFirmata.h>
  DigitalInputFirmata digitalInput;

  #include <DigitalOutputFirmata.h>
  DigitalOutputFirmata digitalOutput;
#endif

#ifdef ENABLE_SLEEP
  #include "ArduinoSleep.h"
  ArduinoSleep sleeper(39, 0);
#endif

#ifdef ENABLE_ANALOG
  #include <AnalogInputFirmata.h>
  AnalogInputFirmata analogInput;

  #include <AnalogOutputFirmata.h>
  AnalogOutputFirmata analogOutput;
#endif


#ifdef ENABLE_WIFI
  #include <WiFi.h>
  #include "utility/WiFiClientStream.h"
  #include "utility/WiFiServerStream.h"
  WiFiServerStream serverStream(NETWORK_PORT);
#endif

#ifdef ENABLE_I2C
  #include <Wire.h>
  #include <I2CFirmata.h>
  I2CFirmata i2c;
#endif

#ifdef ENABLE_SPI
  #include <Wire.h>
  #include <SpiFirmata.h>
  SpiFirmata spi;
#endif

#ifdef ENABLE_ONE_WIRE
  #include <OneWireFirmata.h>
  OneWireFirmata oneWire;
#endif

#ifdef ENABLE_SERIAL
  #include <SerialFirmata.h>
  SerialFirmata serial;
#endif

#ifdef ENABLE_DHT
  #include <DhtFirmata.h>
  DhtFirmata dhtFirmata;
#endif

#include <FirmataExt.h>
FirmataExt firmataExt;

#ifdef ENABLE_SERVO
  #include <Servo.h>
  #include <ServoFirmata.h>
  ServoFirmata servo;
#endif

#include <FirmataReporting.h>
FirmataReporting reporting;

#ifdef ENABLE_ACCELSTEPPER
#include <AccelStepperFirmata.h>
  AccelStepperFirmata accelStepper;
#endif

#ifdef ENABLE_FREQUENCY
#include <Frequency.h>
  Frequency frequency;
#endif

#ifdef ENABLE_BASIC_SCHEDULER
// The scheduler allows to store scripts on the board, however this requires a kind of compiler on the client side.
// When running dotnet/iot on the client side, prefer using the FirmataIlExecutor module instead
  #include <FirmataScheduler.h>
  FirmataScheduler scheduler;
#endif

void systemResetCallback()
{
// Does more harm than good on ESP32 (because may touch pins reserved
// for memory IO and other reserved functions)
#ifndef ESP32 
	for (byte i = 0; i < TOTAL_PINS; i++) 
	{
		if (FIRMATA_IS_PIN_ANALOG(i)) 
		{
			Firmata.setPinMode(i, PIN_MODE_ANALOG);
		} 
		else if (IS_PIN_DIGITAL(i)) 
		{
			Firmata.setPinMode  (i, PIN_MODE_OUTPUT);
		}
	}
#endif
	firmataExt.reset();
}

void initTransport()
{
#ifdef DISABLE_BLINK_VERSION
  Firmata.disableBlinkVersion();
#endif
  
#ifdef ESP8266
  // need to ignore pins 1 and 3 when using an ESP8266 board. These are used for the serial communication.
  Firmata.setPinMode(1, PIN_MODE_IGNORE);
  Firmata.setPinMode(3, PIN_MODE_IGNORE);
#endif

#ifdef ENABLE_WIFI
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  pinMode(VERSION_BLINK_PIN, OUTPUT);
  bool pinIsOn = false;
  while (WiFi.status() != WL_CONNECTED)
	{
		delay(100);
		pinIsOn = !pinIsOn;
		digitalWrite(VERSION_BLINK_PIN, pinIsOn);
	}
	Firmata.begin(serverStream);
	Firmata.blinkVersion(); // Because the above doesn't do it.
#else 
	Firmata.begin(BAUD_RATE);
#endif
}

void initFirmata()
{
#ifdef ENABLE_DIGITAL
	firmataExt.addFeature(digitalInput);
	firmataExt.addFeature(digitalOutput);
#endif
	
#ifdef ENABLE_ANALOG
	firmataExt.addFeature(analogInput);
	firmataExt.addFeature(analogOutput);
#endif
	
#ifdef ENABLE_SERVO
	firmataExt.addFeature(servo);
#endif
	
#ifdef ENABLE_I2C
	firmataExt.addFeature(i2c);
#endif
	
#ifdef ENABLE_ONE_WIRE
	firmataExt.addFeature(oneWire);
#endif

#ifdef ENABLE_SERIAL
	firmataExt.addFeature(serial);
#endif
	
#ifdef ENABLE_BASIC_SCHEDULER
	firmataExt.addFeature(scheduler);
#endif
	
  firmataExt.addFeature(reporting);
#ifdef ENABLE_SPI
	firmataExt.addFeature(spi);
#endif
#ifdef ENABLE_ACCELSTEPPER
	firmataExt.addFeature(accelStepper);
#endif
	
#ifdef ENABLE_DHT
	firmataExt.addFeature(dhtFirmata);
#endif

#ifdef ENABLE_FREQUENCY
	firmataExt.addFeature(frequency);
#endif

#ifdef ENABLE_SLEEP
	firmataExt.addFeature(sleeper);
#endif

	Firmata.attach(SYSTEM_RESET, systemResetCallback);
}

/*************    END of  Definitions part   *************/




void setup()
{

  // Boot pause BLINK test: 
#ifdef ENABLE_START_BLINK
  pinMode(VERSION_BLINK_PIN, OUTPUT);
      digitalWrite(VERSION_BLINK_PIN, HIGH);
      delay(4000);
      digitalWrite(VERSION_BLINK_PIN, LOW);
      delay(1000);
      digitalWrite(VERSION_BLINK_PIN, HIGH);
      delay(100);
      digitalWrite(VERSION_BLINK_PIN, LOW);
#endif

#ifdef BLINK_SERIAL_STATE
  pinMode(VERSION_BLINK_PIN, OUTPUT);
  digitalWrite(VERSION_BLINK_PIN, LOW);
  lastSerialTime = millis();
#endif

	// Set firmware name and version.
	// (Need to do this before initTransport(), because some client libraries expect that a reset sends this automatically.)
	Firmata.setFirmwareNameAndVersion(boardName, FIRMATA_FIRMWARE_MAJOR_VERSION, FIRMATA_FIRMWARE_MINOR_VERSION);

	initTransport();
	Firmata.sendString(F("Booting device. Stand by...\n"));

	initFirmata();
	Firmata.sendString(F("init complete \n"));

	Firmata.parse(SYSTEM_RESET);
	Firmata.sendString(F("parse complete \n"));

  // Raspberry Pi Pico/Pico2/W temperature initialization, using Earle F.'s library.

	analogReadResolution(12);
	// send TEMP °C + RawTempValue, formatted as JSON.
#include <stdio.h>
  float c = analogReadTemp();
  char buf[127], buf2[32];
  snprintf(buf , 25, "{\"celsius\": %f ", c);

#include <hardware/structs/adc.h>
	hw_set_bits(&adc_hw->cs, ADC_CS_TS_EN_BITS);
  delay(1); // Allow things to settle.  Without this, readings can be erratic
	int _rt = analogRead(TOTAL_PINS - 1);
  snprintf(buf2, 25, ",\"rawTemp\": %d", _rt);
  strcat  (buf , buf2);
	//  float celsius = 27.0f - ((v * vref / 4096.0f) - 0.706f) / 0.001721f;  // const float vref = 3.3f; 

  // Send serial + boardName
  uint len;
  pico_get_unique_board_id_string(buf2, len);
  strcat  (buf , ",\"serial\":\"");
  strcat  (buf , buf2);
  strcat  (buf , "\",\"boardName\":\"");
  strcat  (buf , boardName);
  strcat  (buf , "\"}");
  Firmata.sendString(F( buf ));

/*
  // speed test: send 1000*100 char and measure speed (100 char = 1424 bit including SYSEX start/stop)

  unsigned long t1, t2 = 0;
  t1 = millis();
  for (int k = 0; k<1000; k++) {
    Firmata.sendString(F( "1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890" ));
  }
  t2 = millis();
  snprintf(buf , 32, "start (ms): %d ", t1); // 1882ms
  Firmata.sendString(F( buf ));
  snprintf(buf , 32, "end (ms): %d ", t2); // 3466ms
  Firmata.sendString(F( buf ));
  snprintf(buf , 32, "Ellapsed: %d ", t2-t1); //1424 bit = 1584ms ->  904126 baud 
  Firmata.sendString(F( buf ));
*/  
  
}


void loop()
{
#ifdef BLINK_SERIAL_STATE
  // Check for incoming serial data
  if (Serial.available()) {
    lastSerialTime = millis();
  }

  // Adjust blink interval based on serial timeout
  if (millis() - lastSerialTime > SERIAL_TIMEOUT) {
    blinkInterval = FAST_BLINK_INTERVAL;
  } else {
    blinkInterval = NORMAL_BLINK_INTERVAL;
  }

  // Blink LED
  if (millis() - lastBlinkTime >= blinkInterval) {
    ledState = !ledState;
    digitalWrite(VERSION_BLINK_PIN, ledState);
    lastBlinkTime = millis();
  }
#endif

	while(Firmata.available()) 
	{
		Firmata.processInput();
		if (!Firmata.isParsingMessage()) 
		{
			break;
		}
	}

//  delay(1);

	firmataExt.report(reporting.elapsed());
#ifdef ENABLE_WIFI
	serverStream.maintain();
#endif
}