Love Streaming.h? Meet NeoStreaming: A C++ Streaming Alternative

Hey everyone,

If you’ve been in the Arduino ecosystem for a while, you’re likely familiar with Mikal Hart’s excellent Streaming.h library. It was a game-changer for cleaning up messy Serial.print() lines by bringing C++ << syntax to Arduino. As great of a library as it is, I started seeing its limitations.

The "Stateless" Nightmare

The Issue (Streaming.h): Formatting not only doesn’t follow the same syntax standard as C++, it’s also not persistent. If you want to print five floating-point numbers with 3 decimal places, you must wrap every single variable individually:

Serial << _FLOAT(v1, 3) << " " << _FLOAT(v2, 3) << " " << _FLOAT(v3, 3);

Which can become as redundant as typing out Serial.print() every time. Creating formatted logs became incredibly tedious to write and hard to read.

The NeoStreaming Fix: Introduces State Persistence. You set the state once, and it applies to everything that follows.

cout << setprecision(3) << v1 << " " << v2 << " " << v3;

No Native Time Formatting

The Issue (Streaming.h): There is no support for standard C struct tm timestamp formatting.

To print a timestamp from an RTC, you must manually chain together variables:

Serial << year << "-" << month << "-" << day << " " << hour << ":" << minute;

The NeoStreaming Fix: Includes put_time(). It securely formats the entire timestamp inside a bounded stack array using standard %Y-%m-%d %H:%M:%S sequences.

Summary

NeoStreaming.h is a true formatting engine. It intercepts the data, formats it into strict column widths, calculates precision, handles scientific notation, and parses timestamps all inside deterministic, zero-heap stack boundaries before handing it off to the serial port.

Feedback

I encourage you guys to give honest feedback.

You can download it from GitHub, or add it using the Arduino Library Manager.

Thanks for sharing!

Hallo,

The demo won't compile for me.
NeoStreaming v1.0.1.

Tested with ArduinoESP32 (Core 2.0.18) and ESP32S3 (Core 3.3.10)

...\AppData\Local\Temp\.arduinoIDE-unsaved2026530-20924-1mbbprv.psro\NeoStreaming_Demo\NeoStreaming_Demo.ino:17:
...\Arduino IDE 2.3.10\portable\sketchbook\libraries\NeoStreaming\src/NeoStreaming.h:35:10: fatal error: utility/util.h: No such file or directory
   35 | #include "utility/util.h"
      |          ^~~~~~~~~~~~~~~~
compilation terminated.

Tested with ATmega2560

...\arduino-1.8.19\portable\sketchbook\libraries\NeoStreaming\examples\NeoStreaming_Demo\NeoStreaming_Demo.ino:17:0:
...\arduino-1.8.19\portable\sketchbook\libraries\NeoStreaming\src/NeoStreaming.h:35:10: fatal error: utility/util.h: No such file or directory
 #include "utility/util.h"
          ^~~~~~~~~~~~~~~~
compilation terminated.

There is no folder named “utility.”
The toolchain contains only the folder
...\packages\arduino\tools\avr-gcc\7.3.0-atmel3.6.1-arduino7\avr\include\util
There is no file named “util.h” in it.
Where did you get #include “utility/util.h” from?

Thanks for catching that. I renamed the utility's folder and file name to be more descriptive. It's now macros/macros.h. However, I forgot to correct the file path in NeoStreaming.h to the newly renamed macros folder.

The No such file or directory issue has been resolved. I also removed the String conversion from the fallback template in NeoStreaming.h. Hope you get to enjoy this library now.

Hi,

Okay, the macro works, it compiles.
However, it has not yet been thoroughly tested. Have you enabled all warnings in the IDE?
For an ATmega2560, it looks different but similar. Here is the output with ESP32S3 and Core 3.3.10.

Line 20:

...\packages\esp32\hardware\esp32\3.3.10\cores\esp32/Arduino.h:202,
...\NeoStreaming_Demo.ino.cpp:1:
C:\Users\Worker\AppData\Local\Arduino15\packages\esp32\hardware\esp32\3.3.10\cores\esp32/HardwareSerial.h:442:16: error: invalid initialization of reference of type 'HardwareSerial&' from expression of type 'HWCDC'
  442 | #define Serial HWCDCSerial
      |                ^~~~~~~~~~~
C:\Users\Worker\AppData\Local\Temp\.arduinoIDE-unsaved2026530-18364-1s9a6nn.fn9mk\NeoStreaming_Demo\NeoStreaming_Demo.ino:20:24: note: in expansion of macro 'Serial'
   20 | HardwareSerial& cout = Serial;
      |                        ^~~~~~

It would be better to:
Stream &cout {Serial};
or
Print &cout {Serial};

Line 84:

...\NeoStreaming_Demo.ino:84:28: warning: missing initializer for member 'tm::tm_min' [-Wmissing-field-initializers]
   84 |     struct tm bootTime = {0};

{0} sets only the first element. A cleaner way would be to use an empty parenthesis without an equals sign. It would be better to:
struct tm bootTime {};

Line 115:

...\NeoStreaming_Demo\NeoStreaming_Demo.ino:115:55: warning: missing initializer for member 'tm::tm_wday' [-Wmissing-field-initializers]
  115 |     static struct tm liveTime = {0, 30, 14, 14, 5, 126}; // Starts at 14:30:00

tm has 9 parameters, and 126 for the year can't be right either.

struct tm
{
  int	tm_sec;
  int	tm_min;
  int	tm_hour;
  int	tm_mday;
  int	tm_mon;
  int	tm_year;
  int	tm_wday;
  int	tm_yday;
  int	tm_isdst;

It would be better to:
static struct tm liveTime {0, 30, 14, 14, 5, 26, 0, 0 -1}; // Starts at 14:30:00

When you release a library, you should test it more thoroughly before publishing it.

I was unaware of using Stream or Print as an alias for Serial. And after some reading, I plan to keep using this method. You sir, have taught me something new.

You are also right that I could have initilized the struct as you suggested. However, how I did it achieves the same result. Because I only initilized the first element with a 0, every element after was automatically set to 0. I know an empty brace does the samething, I prefer the visual. It's really up to the end user to use whichever they prefer.

Yes, there are 9 parameters for the tm struct. However, my demo sketch wasn't about tm, but only using the needed parameters for the demonstration. That's why the others were left out.

126 is correct for the year because the **NeoStreaming** library uses the same syntax under the hood as std::tm. Here is a link to the C++ reference site showing the year - 1900. std::tm - cppreference.com.

Thank you Doc for your feedback.

Hi,

Print and Stream are Arduino classes.

Even though
struct tm bootTime = {0};
works in practice, it’s still messy, otherwise, the compiler wouldn’t issue a warning. :wink:

struct tm bootTime {}; calls the default constructor, which initializes everything properly to 0.

If you ignore 3 out of 9 parameters and receive warnings as a result, that’s still messy. In that case, write a constructor to handle it.

I wasn’t aware of the int tm_year - years since 1900 thing.

Look at it from the user’s perspective. What kind of impression does it make when an example throws warnings? I don’t like that. It usually leads people to draw conclusions about how cleanly the library is programmed. Is that what you want? I don’t think so. Otherwise, I find your library good, so nothing should be off-putting. :wink:

Doc, I really do appreciate all the feedback you're giving. I learn more from my mistakes than I ever do my successes. You do make a great point about peoples preception of a library based on how it's coded. So moving forward I will be more mindful of the syntax used in future releases.

Also, I didn't have verbose on for compiling. So I was missing a lot of silent errors before. I installed all the latest boards for esp and compiled every ESP32S3 with core 3.3.10. And I was able to see the issues you mentioned.

Please continue to offer feedback, solutions, or recommendations. Also, test the library and see what it's strengths and weaknesses are. There may be an edge case that I didn't think about checking for.