Skip to content

Commit

Permalink
Add ProgmemStream
Browse files Browse the repository at this point in the history
  • Loading branch information
bblanchon committed Nov 14, 2022
1 parent 0bdecbc commit a72a6d5
Show file tree
Hide file tree
Showing 11 changed files with 299 additions and 1 deletion.
6 changes: 6 additions & 0 deletions .github/workflows/ci.yml
Expand Up @@ -122,6 +122,9 @@ jobs:
- name: Build Logger
run: arduino-cli compile --library . --warnings all -b ${{ matrix.board }} "examples/Logger/Logger.ino"

- name: Build ProgmemStream
run: arduino-cli compile --library . --warnings all -b ${{ matrix.board }} "examples/ProgmemStream/ProgmemStream.ino"

- name: Build ReadBuffer
run: arduino-cli compile --library . --warnings all -b ${{ matrix.board }} "examples/ReadBuffer/ReadBuffer.ino"

Expand Down Expand Up @@ -233,6 +236,9 @@ jobs:
- name: Build Logger
run: platformio ci "examples/Logger/Logger.ino" -l '.' -b ${{ matrix.board }}

- name: Build ProgmemStream
run: platformio ci "examples/ProgmemStream/ProgmemStream.ino" -l '.' -b ${{ matrix.board }}

- name: Build ReadBuffer
run: platformio ci "examples/ReadBuffer/ReadBuffer.ino" -l '.' -b ${{ matrix.board }}

Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -5,6 +5,7 @@ HEAD
----

* Add support for `Print::flush()` on ESP32 and STM32
* Add `ProgmemStream`

1.6.3 (2022/05/11)
-----
Expand Down
20 changes: 19 additions & 1 deletion README.md
Expand Up @@ -26,7 +26,7 @@ For example, with this library, you can:
* improve the reliability of a serial connection by adding error correction codes
* debug your program more easily by logging what it sends to a Web service
* send large data with the [Wire library](https://www.arduino.cc/en/reference/wire)
* use a `String` or EEPROM with a stream interface
* use a `String`, EEPROM, or `PROGMEM` with a stream interface

Read on to see how StreamUtils can help you!

Expand Down Expand Up @@ -329,6 +329,24 @@ deserializeJson(doc, eepromStream);
```


How to use `PROGMEM` as a stream?
------------------------------

SteamUtils also allows reading `PROGMEM` buffers with a `Stream` inteface.

![ProgmemStream](extras/images/ProgmemStream.svg)

Simply create an instance of `ProgmemStream` and pass the pointer to the `PROGMEM` buffer.

```c++
const char buffer[] PROGMEM = "This string is in program memory"
ProgmemStream stream{buffer};
Serial.println(stream.readString());
```
`ProgmemStream`'s constructor also supports `const __FlashStringHelper*` (the type returned by the `F()` macro) and an optional second argument to specify the size of the buffer.
Summary
-------
Expand Down
53 changes: 53 additions & 0 deletions examples/ProgmemStream/ProgmemStream.ino
@@ -0,0 +1,53 @@
// StreamUtils - github.com/bblanchon/ArduinoStreamUtils
// Copyright Benoit Blanchon 2019-2022
// MIT License
//
// This example shows how to use StringStream

#include <StreamUtils.h>

const char data[] PROGMEM = "This string is in program memory.";

void setup() {
// Initialize serial port
Serial.begin(9600);
while (!Serial)
continue;

ProgmemStream stream{data};
while (stream.available()) {
Serial.write(stream.read());
}
}

void loop() {
// not used in this example
}

/*****************************************************
* *
* Love this project? *
* Star it on GitHub! *
* *
* .,,. *
* ,,:1. *
* ,.,:;1 *
* .,,,::;: *
* ,,,,::;;. *
* .,,,:::;;; *
* .....,,,,...,.,,,,,,:::,,,,,,,,,,,,, *
* ,,,,,,,,,,,:,...,,,,,,:::,,,,:::;;;11l *
* .;::::::::,,,,,,,,,,:::::,,::;;;1lt *
* .;;;:::,,,,,,,,::::::;:::;;1t: *
* :;;:,,,,,,::::::;;;;;;l1 *
* ,,,,:::::::;;;;;;l *
* .,,,,::::;;;;;;;:::: *
* ,,,,,:::;;;;;::,:::1 *
* ,,,,,::;;;t1:,,:::::;l *
* .,,,,:;;ll ;::::::;;, *
* ,,,:;ll. .1:::;;l *
* .,:lt, .1;;l: *
* *
* https://github.com/bblanchon/ArduinoStreamUtils *
* *
*****************************************************/
1 change: 1 addition & 0 deletions extras/images/ProgmemStream.svg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
118 changes: 118 additions & 0 deletions extras/test/ProgmemStreamTest.cpp
@@ -0,0 +1,118 @@
// StreamUtils - github.com/bblanchon/ArduinoStreamUtils
// Copyright Benoit Blanchon 2019-2022
// MIT License

#include "StreamUtils/Streams/ProgmemStream.hpp"

#include "doctest.h"

using namespace StreamUtils;

TEST_CASE("ProgmemStream") {
GIVEN("constructor was called with {0}") {
ProgmemStream s{static_cast<const char*>(0)};

THEN("available() should return 0") {
CHECK(s.available() == 0);
}

THEN("peek() should return -1") {
CHECK(s.peek() == -1);
}

THEN("read() should return -1") {
CHECK(s.read() == -1);
}

THEN("readBytes() should return 0") {
char buffer[8];
CHECK(s.readBytes(buffer, 8) == 0);
}
}

GIVEN("constructor was called with {F(\"hello\"))}") {
ProgmemStream s{F("hello")};

THEN("available() should return 5") {
CHECK(s.available() == 5);
}
}

GIVEN("constructor was called with {F(\"hello\"), 5)}") {
ProgmemStream s(F("hello"), 5);

THEN("write(char) should return 0") {
CHECK(s.write('a') == 0);
}

THEN("print(const char*) should return 0") {
CHECK(s.print("abc") == 0);
}

THEN("available() should return 5") {
CHECK(s.available() == 5);
}

THEN("peek() should return 'h'") {
CHECK(s.peek() == 'h');
}

WHEN("read() is called") {
int c = s.read();

THEN("it sould return 'h'") {
CHECK(c == 'h');
}

THEN("available() should return 4") {
CHECK(s.available() == 4);
}

THEN("peek() should return 'e'") {
CHECK(s.peek() == 'e');
}
}

WHEN("readBytes(buffer, 4) is called") {
char buffer[4];
int n = s.readBytes(buffer, 4);

THEN("it should return 4") {
CHECK(n == 4);
}

THEN("available() should return 1") {
CHECK(s.available() == 1);
}

THEN("peek() should return 'o'") {
CHECK(s.peek() == 'o');
}

THEN("read() should return 'o'") {
CHECK(s.read() == 'o');
}
}

WHEN("readBytes(buffer, 8) is called") {
char buffer[8];
int n = s.readBytes(buffer, 8);

THEN("it should return 5") {
CHECK(n == 5);
}

THEN("available() should return 0") {
CHECK(s.available() == 0);
}

THEN("peek() should return -1") {
CHECK(s.peek() == -1);
}

THEN("read() should return -1") {
CHECK(s.read() == -1);
}
}
}
}
1 change: 1 addition & 0 deletions extras/test/cores/avr/Arduino.h
Expand Up @@ -8,6 +8,7 @@
#include <Print.h>
#include <Stream.h>
#include <WString.h>
#include <avr/pgmspace.h>

#include <time.h>

Expand Down
22 changes: 22 additions & 0 deletions extras/test/cores/avr/avr/pgmspace.h
@@ -0,0 +1,22 @@
#pragma once

#include <stdint.h>
#include <string.h>

class __FlashStringHelper;

inline const __FlashStringHelper* F(const char* s) {
return reinterpret_cast<const __FlashStringHelper*>(s + 42);
}

inline size_t strlen_P(const char* s) {
return strlen(s - 42);
}

inline uint8_t pgm_read_byte(const char* p) {
return static_cast<uint8_t>(p[-42]);
}

inline void* memcpy_P(void* dest, const void* src, size_t size) {
return memcpy(dest, reinterpret_cast<const char*>(src) - 42, size);
}
3 changes: 3 additions & 0 deletions keywords.txt
@@ -1,12 +1,15 @@
BufferingPrint KEYWORD1
EepromStream KEYWORD1
LoggingClient KEYWORD1
LoggingPrint KEYWORD1
LoggingStream KEYWORD1
ProgmemStream KEYWORD1
ReadBufferingClient KEYWORD1
ReadBufferingStream KEYWORD1
ReadLoggingClient KEYWORD1
ReadLoggingStream KEYWORD1
ReadThrottlingStream KEYWORD1
StringStream KEYWORD1
WriteBufferingClient KEYWORD1
WriteBufferingStream KEYWORD1
WriteLoggingClient KEYWORD1
Expand Down
1 change: 1 addition & 0 deletions src/StreamUtils.hpp
Expand Up @@ -19,6 +19,7 @@
#include "StreamUtils/Streams/HammingEncodingStream.hpp"
#include "StreamUtils/Streams/HammingStream.hpp"
#include "StreamUtils/Streams/LoggingStream.hpp"
#include "StreamUtils/Streams/ProgmemStream.hpp"
#include "StreamUtils/Streams/ReadBufferingStream.hpp"
#include "StreamUtils/Streams/ReadLoggingStream.hpp"
#include "StreamUtils/Streams/ReadThrottlingStream.hpp"
Expand Down
74 changes: 74 additions & 0 deletions src/StreamUtils/Streams/ProgmemStream.hpp
@@ -0,0 +1,74 @@
// StreamUtils - github.com/bblanchon/ArduinoStreamUtils
// Copyright Benoit Blanchon 2019-2022
// MIT License

#pragma once

#include <Arduino.h>

#include "../Configuration.hpp"

class __FlashStringHelper;

namespace StreamUtils {

class ProgmemStream : public Stream {
public:
ProgmemStream(const void* ptr, size_t size)
: _ptr(reinterpret_cast<const char*>(ptr)), _size(size) {}

ProgmemStream(const char* ptr) : _ptr(ptr), _size(ptr ? strlen_P(ptr) : 0) {}

ProgmemStream(const __FlashStringHelper* ptr)
: ProgmemStream{reinterpret_cast<const char*>(ptr)} {}

int available() override {
return _size;
}

int read() override {
if (_size <= 0)
return -1;
_size--;
return pgm_read_byte(_ptr++);
}

#if STREAMUTILS_STREAM_READBYTES_IS_VIRTUAL
size_t readBytes(char* buffer, size_t size) override {
if (size > _size)
size = _size;
memcpy_P(buffer, _ptr, size);
_ptr += size;
_size -= size;
return size;
}
#endif

int peek() override {
if (_size <= 0)
return -1;
return pgm_read_byte(_ptr);
}

void flush() override {}

#if STREAMUTILS_PRINT_WRITE_VOID_UINT32
size_t write(const void*, uint32) override {
return 0;
}
#else
size_t write(const uint8_t*, size_t) override {
return 0;
}
#endif

size_t write(uint8_t) override {
return 0;
}

private:
const char* _ptr;
size_t _size;
};

} // namespace StreamUtils

0 comments on commit a72a6d5

Please sign in to comment.