changed the way how to select which display is used. This is still in test and there is also some testcode include
This commit is contained in:
parent
019f8768cd
commit
493642aa90
8 changed files with 229 additions and 99 deletions
2
firmware/.gitignore
vendored
Normal file
2
firmware/.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
.pio/
|
||||||
|
.vscode/
|
|
@ -17,8 +17,8 @@
|
||||||
// mapping of Waveshare ESP32 Driver Board
|
// mapping of Waveshare ESP32 Driver Board
|
||||||
// BUSY -> 25, RST -> 26, DC -> 27, CS-> 15, CLK -> 13, DIN -> 14
|
// BUSY -> 25, RST -> 26, DC -> 27, CS-> 15, CLK -> 13, DIN -> 14
|
||||||
// uncomment next two lines for Waveshare ESP32 Driver Board
|
// uncomment next two lines for Waveshare ESP32 Driver Board
|
||||||
#define USE_HSPI_FOR_EPD
|
// #define USE_HSPI_FOR_EPD
|
||||||
#define ENABLE_GxEPD2_GFX 0
|
// #define ENABLE_GxEPD2_GFX 0
|
||||||
// *** end Waveshare ESP32 Driver board *** //
|
// *** end Waveshare ESP32 Driver board *** //
|
||||||
|
|
||||||
// NOTE: you may need to adapt or select for your wiring in the processor specific conditional compile sections below
|
// NOTE: you may need to adapt or select for your wiring in the processor specific conditional compile sections below
|
||||||
|
@ -30,14 +30,14 @@
|
||||||
|
|
||||||
// select the display driver class (only one) for your panel
|
// select the display driver class (only one) for your panel
|
||||||
|
|
||||||
// #define GxEPD2_DRIVER_CLASS GxEPD2_150_BN // 1.54 inch Waveshare e-paper display
|
#define GxEPD2_DRIVER_CLASS GxEPD2_150_BN // 1.54 inch Waveshare e-paper display
|
||||||
// const String display_type = "GxEPD2_150_BN";
|
const String display_type = "GxEPD2_150_BN";
|
||||||
|
|
||||||
// #define GxEPD2_DRIVER_CLASS GxEPD2_213_B74 // Waveshare 2.13 inch e-paper display - Version 3
|
// #define GxEPD2_DRIVER_CLASS GxEPD2_213_B74 // Waveshare 2.13 inch e-paper display - Version 3
|
||||||
// const String display_type = "GxEPD2_213_B74";
|
// const String display_type = "GxEPD2_213_B74";
|
||||||
|
|
||||||
#define GxEPD2_DRIVER_CLASS GxEPD2_213_flex // Waveshare 2.13 inch e-paper display - (D) flex (yellow)
|
// #define GxEPD2_DRIVER_CLASS GxEPD2_213_flex // Waveshare 2.13 inch e-paper display - (D) flex (yellow)
|
||||||
const String display_type = "GxEPD2_213_flex";
|
// const String display_type = "GxEPD2_213_flex";
|
||||||
|
|
||||||
// #define GxEPD2_DRIVER_CLASS GxEPD2_270 // Waveshare 264x176, 2.7inch E-Ink display - Version 1
|
// #define GxEPD2_DRIVER_CLASS GxEPD2_270 // Waveshare 264x176, 2.7inch E-Ink display - Version 1
|
||||||
// const String display_type = "GxEPD2_270";
|
// const String display_type = "GxEPD2_270";
|
||||||
|
|
|
@ -25,9 +25,7 @@ new version when next recompiled. The header file eliminates the labor of
|
||||||
finding and changing all the copies as well as the risk that a failure to
|
finding and changing all the copies as well as the risk that a failure to
|
||||||
find one copy will result in inconsistencies within a program.
|
find one copy will result in inconsistencies within a program.
|
||||||
|
|
||||||
In C, the usual convention is to give header files names that end with `.h'.
|
In C, the convention is to give header files names that end with `.h'.
|
||||||
It is most portable to use only letters, digits, dashes, and underscores in
|
|
||||||
header file names, and at most one dot.
|
|
||||||
|
|
||||||
Read more about using header files in official GCC documentation:
|
Read more about using header files in official GCC documentation:
|
||||||
|
|
||||||
|
|
|
@ -9,30 +9,20 @@
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <Hash.h>
|
#include <Hash.h>
|
||||||
#include <ctype.h>
|
#include <ctype.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
// ########################################
|
// ########################################
|
||||||
// ########### USER ACTION ###########
|
// ########### USER ACTION ###########
|
||||||
// ########################################
|
// ########################################
|
||||||
// Generate and copy in LNbits with the LNURLDevice extension the string for the ATM and paste it here:
|
// Generate and copy in LNbits with the LNURLDevice extension the string for the ATM and paste it here:
|
||||||
const String lnurlDeviceString = "https://legend.lnbits.com/lnurldevice/api/v1/lnurl/idexample,keyexample,EUR";
|
const char* lnurlDeviceString = "https://legend.lnbits.com/lnurldevice/api/v1/lnurl/idexample,keyexample,EUR";
|
||||||
// #################### EXAMPLE: https://legend.lnbits.com/lnurldevice/api/v1/lnurl/idexample,keyexample,EUR
|
// #################### EXAMPLE: https://legend.lnbits.com/lnurldevice/api/v1/lnurl/idexample,keyexample,EUR
|
||||||
// ########################################
|
// ########################################
|
||||||
// ########################################
|
// ########################################
|
||||||
// ########################################
|
// ########################################
|
||||||
|
|
||||||
// select the display class and display driver class in the following file (new style):
|
|
||||||
// 1.54 inch Waveshare e-paper display is "GxEPD2_DRIVER_CLASS GxEPD2_150_BN"
|
|
||||||
// Waveshare 264x176, 2.7inch E-Ink display - Version 1 = "GxEPD2_DRIVER_CLASS GxEPD2_270"
|
|
||||||
// Waveshare 264x176, 2.7inch E-Ink display - Version 2 = "GxEPD2_DRIVER_CLASS GxEPD2_270_GDEY027T91"
|
|
||||||
// Waveshare 2.13 inch e-paper display version 3 is "GxEPD2_DRIVER_CLASS GxEPD2_213_B74"
|
|
||||||
// Waveshare 2.13 inch e-paper display (D) flex (yellow) is "GxEPD2_DRIVER_CLASS GxEPD2_213_flex"
|
|
||||||
// use search to find the correct line, and uncomment the other display drivers in this header file:
|
|
||||||
#include "GxEPD2_display_selection_new_style.h"
|
|
||||||
|
|
||||||
// OTHER OPTIONS:
|
|
||||||
|
|
||||||
// Activate for debugging over Serial (1), deactivate in production use (0)
|
// Activate for debugging over Serial (1), deactivate in production use (0)
|
||||||
#define DEBUG_MODE 0
|
#define DEBUG_MODE 1
|
||||||
|
|
||||||
#define COIN_PIN 17
|
#define COIN_PIN 17
|
||||||
#define PULSE_TIMEOUT 200
|
#define PULSE_TIMEOUT 200
|
||||||
|
@ -41,6 +31,27 @@ const String lnurlDeviceString = "https://legend.lnbits.com/lnurldevice/api/v1/l
|
||||||
#define MOSFET_PIN 16 // old: 12 | new: 16
|
#define MOSFET_PIN 16 // old: 12 | new: 16
|
||||||
#define QR_VERSION 6 // 20 is standard. 6 for simpler QR code, but does not always work.
|
#define QR_VERSION 6 // 20 is standard. 6 for simpler QR code, but does not always work.
|
||||||
|
|
||||||
|
#define DSPLY_PIN_CS 26
|
||||||
|
#define DSPLY_PIN_DC 25
|
||||||
|
#define DSPLY_PIN_RST 33
|
||||||
|
#define DSPLY_PIN_BUSY 27
|
||||||
|
#define DSPLY_PIN_CLK 18 // SCK pin SPI
|
||||||
|
#define DSPLY_PIN_DIN 23 // MOSI pin SPI
|
||||||
|
|
||||||
|
// Folowing #defines are Taken from :
|
||||||
|
//#include "GxEPD2_display_selection_new_style.h"
|
||||||
|
const uint8_t nr_supported_display_types = 6;
|
||||||
|
const char* supported_display_types[nr_supported_display_types] = {"GxEPD2_420", "GxEPD2_150_BN", "GxEPD2_270", "GxEPD2_270_GDEY027T91", "GxEPD2_213_B74", "GxEPD2_213_flex"};
|
||||||
|
|
||||||
|
#undef USE_HSPI_FOR_EPD
|
||||||
|
#define GxEPD2_DISPLAY_CLASS GxEPD2_BW // Black and White e-Paper
|
||||||
|
#define GxEPD2_DRIVER_CLASS GxEPD2_420 // 4.2 inch Waveshare e-paper display
|
||||||
|
//#define GxEPD2_DRIVER_CLASS GxEPD2_150_BN // 1.54 inch Waveshare e-paper display
|
||||||
|
const char* display_type = "GxEPD2_420";
|
||||||
|
|
||||||
|
// Treiber für das 4.2" Schwarz-Weiß E-Paper
|
||||||
|
GxEPD2_DISPLAY_CLASS<GxEPD2_DRIVER_CLASS, GxEPD2_DRIVER_CLASS::HEIGHT> display(GxEPD2_DRIVER_CLASS(DSPLY_PIN_CS, DSPLY_PIN_DC, DSPLY_PIN_RST, DSPLY_PIN_BUSY)); // Waveshare ESP32 Driver Board
|
||||||
|
|
||||||
typedef struct s_qrdata
|
typedef struct s_qrdata
|
||||||
{
|
{
|
||||||
uint8_t current_y;
|
uint8_t current_y;
|
||||||
|
|
46
firmware/lib/README
Normal file
46
firmware/lib/README
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
|
||||||
|
This directory is intended for project specific (private) libraries.
|
||||||
|
PlatformIO will compile them to static libraries and link into the executable file.
|
||||||
|
|
||||||
|
The source code of each library should be placed in a separate directory
|
||||||
|
("lib/your_library_name/[Code]").
|
||||||
|
|
||||||
|
For example, see the structure of the following example libraries `Foo` and `Bar`:
|
||||||
|
|
||||||
|
|--lib
|
||||||
|
| |
|
||||||
|
| |--Bar
|
||||||
|
| | |--docs
|
||||||
|
| | |--examples
|
||||||
|
| | |--src
|
||||||
|
| | |- Bar.c
|
||||||
|
| | |- Bar.h
|
||||||
|
| | |- library.json (optional. for custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html
|
||||||
|
| |
|
||||||
|
| |--Foo
|
||||||
|
| | |- Foo.c
|
||||||
|
| | |- Foo.h
|
||||||
|
| |
|
||||||
|
| |- README --> THIS FILE
|
||||||
|
|
|
||||||
|
|- platformio.ini
|
||||||
|
|--src
|
||||||
|
|- main.c
|
||||||
|
|
||||||
|
Example contents of `src/main.c` using Foo and Bar:
|
||||||
|
```
|
||||||
|
#include <Foo.h>
|
||||||
|
#include <Bar.h>
|
||||||
|
|
||||||
|
int main (void)
|
||||||
|
{
|
||||||
|
...
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
The PlatformIO Library Dependency Finder will find automatically dependent
|
||||||
|
libraries by scanning project source files.
|
||||||
|
|
||||||
|
More information about PlatformIO Library Dependency Finder
|
||||||
|
- https://docs.platformio.org/page/librarymanager/ldf.html
|
|
@ -12,7 +12,7 @@
|
||||||
platform = espressif32
|
platform = espressif32
|
||||||
board = esp32dev
|
board = esp32dev
|
||||||
framework = arduino
|
framework = arduino
|
||||||
monitor_speed = 9600
|
monitor_speed = 115200
|
||||||
lib_deps =
|
lib_deps =
|
||||||
zinggjm/GxEPD2@^1.5.2
|
zinggjm/GxEPD2@^1.5.2
|
||||||
ricmoo/QRCode@^0.0.1
|
ricmoo/QRCode@^0.0.1
|
||||||
|
|
|
@ -1,12 +1,72 @@
|
||||||
|
|
||||||
|
|
||||||
|
#if 0
|
||||||
#include "lightning_atm.h"
|
#include "lightning_atm.h"
|
||||||
|
|
||||||
const unsigned int COINS[] = { 0, 0, 5, 10, 20, 50, 100, 200, 1, 2 };
|
//#include <GxEPD2_BW.h> // Bibliothek für Schwarz-Weiß-Displays
|
||||||
bool button_pressed = false;
|
#include <Fonts/FreeMonoBold9pt7b.h>
|
||||||
unsigned int inserted_cents = 0;
|
|
||||||
unsigned long long time_last_press = millis();
|
// #define EPD_CS 26
|
||||||
String baseURLATM;
|
// #define EPD_DC 25
|
||||||
String secretATM;
|
// #define EPD_RST 33
|
||||||
String currencyATM;
|
// #define EPD_BUSY 27
|
||||||
|
|
||||||
|
// // Treiber für das 4.2" Schwarz-Weiß E-Paper
|
||||||
|
// GxEPD2_BW<GxEPD2_420, GxEPD2_420::HEIGHT> display(GxEPD2_420(EPD_CS, EPD_DC, EPD_RST, EPD_BUSY));
|
||||||
|
|
||||||
|
void drawText(const char* text);
|
||||||
|
|
||||||
|
void setup() {
|
||||||
|
Serial.begin(115200);
|
||||||
|
|
||||||
|
/* Wichtige Pins manuell setzen
|
||||||
|
pinMode(EPD_CS, OUTPUT);
|
||||||
|
pinMode(EPD_DC, OUTPUT);
|
||||||
|
pinMode(EPD_RST, OUTPUT);
|
||||||
|
pinMode(EPD_BUSY, INPUT); // BUSY muss als INPUT definiert werden! */
|
||||||
|
|
||||||
|
display.init(115200, true, 2, false);
|
||||||
|
display.setRotation(1); // 0 = Hochformat, 1 = Querformat
|
||||||
|
|
||||||
|
// Bildschirm löschen
|
||||||
|
display.setFullWindow();
|
||||||
|
display.firstPage();
|
||||||
|
do {
|
||||||
|
display.fillScreen(GxEPD_WHITE);
|
||||||
|
} while (display.nextPage());
|
||||||
|
delay(1000);
|
||||||
|
drawText("Text Nummer Eins."); // Beispieltext anzeigen
|
||||||
|
delay(10000);
|
||||||
|
drawText("Text Nummer Zwei."); // Beispieltext anzeigen
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
}
|
||||||
|
|
||||||
|
void drawText(const char* text) {
|
||||||
|
Serial.printf("Text to show: '%s'\n",text);
|
||||||
|
display.setFullWindow();
|
||||||
|
display.firstPage();
|
||||||
|
do {
|
||||||
|
display.setCursor(50, 100);
|
||||||
|
display.setFont(&FreeMonoBold9pt7b);
|
||||||
|
display.setTextColor(GxEPD_BLACK);
|
||||||
|
display.print(text);
|
||||||
|
} while (display.nextPage());
|
||||||
|
Serial.println("Display updated.");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
#include "lightning_atm.h"
|
||||||
|
|
||||||
|
constexpr unsigned int COINS[] = { 0, 0, 5, 10, 20, 50, 100, 200, 1, 2 };
|
||||||
|
volatile bool button_pressed = false;
|
||||||
|
static unsigned int inserted_cents = 0;
|
||||||
|
static unsigned int pulses = 0;
|
||||||
|
static unsigned long long time_last_press = millis();
|
||||||
|
String baseURLATM, secretATM, currencyATM;
|
||||||
|
|
||||||
// *** for Waveshare ESP32 Driver board *** //
|
// *** for Waveshare ESP32 Driver board *** //
|
||||||
#if defined(ESP32) && defined(USE_HSPI_FOR_EPD)
|
#if defined(ESP32) && defined(USE_HSPI_FOR_EPD)
|
||||||
|
@ -14,11 +74,24 @@ SPIClass hspi(HSPI);
|
||||||
#endif
|
#endif
|
||||||
// *** end Waveshare ESP32 Driver board *** //
|
// *** end Waveshare ESP32 Driver board *** //
|
||||||
|
|
||||||
|
//GxEPD2_BW<GxEPD2_420, GxEPD2_420::HEIGHT> display(GxEPD2_420(DSPLY_PIN_CS, DSPLY_PIN_DC, DSPLY_PIN_RST, DSPLY_PIN_BUSY));
|
||||||
|
//GxEPD2_BW<GxEPD2_154, GxEPD2_154::HEIGHT> display(GxEPD2_154(DSPLY_PIN_CS, DSPLY_PIN_DC, DSPLY_PIN_RST, DSPLY_PIN_BUSY));
|
||||||
|
|
||||||
|
void IRAM_ATTR button_pressed_itr() {
|
||||||
|
button_pressed = true;
|
||||||
|
}
|
||||||
|
|
||||||
void setup()
|
void setup()
|
||||||
{
|
{
|
||||||
|
Serial.begin(115200);
|
||||||
|
|
||||||
initialize_display(); // connection to the e-ink display
|
initialize_display(); // connection to the e-ink display
|
||||||
Serial.begin(9600);
|
home_screen();
|
||||||
|
Serial.printf("Selected display type: %s\n", display_type);
|
||||||
|
|
||||||
|
while(0 == 0){}
|
||||||
|
|
||||||
|
|
||||||
// *** for Waveshare ESP32 Driver board *** //
|
// *** for Waveshare ESP32 Driver board *** //
|
||||||
#if defined(ESP32) && defined(USE_HSPI_FOR_EPD)
|
#if defined(ESP32) && defined(USE_HSPI_FOR_EPD)
|
||||||
hspi.begin(13, 12, 14, 15); // remap hspi for EPD (swap pins)
|
hspi.begin(13, 12, 14, 15); // remap hspi for EPD (swap pins)
|
||||||
|
@ -29,7 +102,7 @@ void setup()
|
||||||
{
|
{
|
||||||
sleep(3);
|
sleep(3);
|
||||||
Serial.println("Setup with debug mode..."); // for monitoring with serial monitor to debug
|
Serial.println("Setup with debug mode..."); // for monitoring with serial monitor to debug
|
||||||
Serial.println("Selected display type: " + display_type);
|
Serial.printf("Selected display type: %s\n", display_type);
|
||||||
}
|
}
|
||||||
pinMode(COIN_PIN, INPUT_PULLUP); // coin acceptor input
|
pinMode(COIN_PIN, INPUT_PULLUP); // coin acceptor input
|
||||||
pinMode(LED_BUTTON_PIN, OUTPUT); // LED of the LED Button
|
pinMode(LED_BUTTON_PIN, OUTPUT); // LED of the LED Button
|
||||||
|
@ -46,9 +119,9 @@ void setup()
|
||||||
|
|
||||||
void loop()
|
void loop()
|
||||||
{
|
{
|
||||||
unsigned int pulses = 0;
|
|
||||||
unsigned long long time_last_press;
|
|
||||||
|
|
||||||
|
|
||||||
|
pulses = 0;
|
||||||
pulses = detect_coin(); // detect_coin() is a loop to detect the input of coins, will return the amount of pulses
|
pulses = detect_coin(); // detect_coin() is a loop to detect the input of coins, will return the amount of pulses
|
||||||
if (pulses >= 2 && pulses <= 9)
|
if (pulses >= 2 && pulses <= 9)
|
||||||
{
|
{
|
||||||
|
@ -101,12 +174,6 @@ void loop()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// function to handle the button interrupt
|
|
||||||
void IRAM_ATTR button_pressed_itr()
|
|
||||||
{
|
|
||||||
button_pressed = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// blocking loop which is called when the qr code is shown
|
// blocking loop which is called when the qr code is shown
|
||||||
void wait_for_user_to_scan()
|
void wait_for_user_to_scan()
|
||||||
{
|
{
|
||||||
|
@ -202,37 +269,28 @@ unsigned int detect_coin()
|
||||||
** DISPLAY UTILS
|
** DISPLAY UTILS
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
bool display_check_type(const char* in_display_type)
|
||||||
|
{
|
||||||
|
for(size_t type_nr = 0; type_nr < nr_supported_display_types; type_nr++)
|
||||||
|
{
|
||||||
|
if(0 == strcmp(supported_display_types[type_nr], in_display_type))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Serial.printf("No suitable display class '%s' defined.\n", in_display_type);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// sleep is to put the screen in hibernation mode for longer static frames
|
// sleep is to put the screen in hibernation mode for longer static frames
|
||||||
void display_sleep()
|
void display_sleep()
|
||||||
{
|
{
|
||||||
if (display_type == "GxEPD2_150_BN")
|
|
||||||
display.hibernate();
|
display.hibernate();
|
||||||
else if (display_type == "GxEPD2_270")
|
|
||||||
display.hibernate();
|
|
||||||
else if (display_type == "GxEPD2_270_GDEY027T91")
|
|
||||||
display.hibernate();
|
|
||||||
else if (display_type == "GxEPD2_213_B74")
|
|
||||||
display.hibernate();
|
|
||||||
else if (display_type == "GxEPD2_213_flex")
|
|
||||||
display.hibernate();
|
|
||||||
else
|
|
||||||
Serial.println("No suitable display class defined.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void initialize_display()
|
void initialize_display()
|
||||||
{
|
{
|
||||||
if (display_type == "GxEPD2_150_BN")
|
|
||||||
display.init(115200, true, 2, false);
|
display.init(115200, true, 2, false);
|
||||||
else if (display_type == "GxEPD2_270")
|
|
||||||
display.init(115200, true, 2, false);
|
|
||||||
else if (display_type == "GxEPD2_270_GDEY027T91")
|
|
||||||
display.init(115200, true, 2, false);
|
|
||||||
else if (display_type == "GxEPD2_213_B74")
|
|
||||||
display.init(115200, true, 2, false);
|
|
||||||
else if (display_type == "GxEPD2_213_flex")
|
|
||||||
display.init(115200, true, 2, false);
|
|
||||||
else
|
|
||||||
Serial.println("No suitable display class defined.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void home_screen()
|
void home_screen()
|
||||||
|
@ -247,6 +305,8 @@ void home_screen()
|
||||||
home_screen_waveshare_2_13();
|
home_screen_waveshare_2_13();
|
||||||
else if (display_type == "GxEPD2_213_flex")
|
else if (display_type == "GxEPD2_213_flex")
|
||||||
home_screen_waveshare_2_13_flex();
|
home_screen_waveshare_2_13_flex();
|
||||||
|
else if (display_type == "GxEPD2_420")
|
||||||
|
home_screen_waveshare_2_13_flex();
|
||||||
else
|
else
|
||||||
Serial.println("No suitable display class defined.");
|
Serial.println("No suitable display class defined.");
|
||||||
if (DEBUG_MODE)
|
if (DEBUG_MODE)
|
||||||
|
@ -873,3 +933,5 @@ void clean_screen_waveshare_2_13_flex()
|
||||||
display.nextPage();
|
display.nextPage();
|
||||||
display.hibernate();
|
display.hibernate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endif
|
11
firmware/test/README
Normal file
11
firmware/test/README
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
|
||||||
|
This directory is intended for PlatformIO Test Runner and project tests.
|
||||||
|
|
||||||
|
Unit Testing is a software testing method by which individual units of
|
||||||
|
source code, sets of one or more MCU program modules together with associated
|
||||||
|
control data, usage procedures, and operating procedures, are tested to
|
||||||
|
determine whether they are fit for use. Unit testing finds problems early
|
||||||
|
in the development cycle.
|
||||||
|
|
||||||
|
More information about PlatformIO Unit Testing:
|
||||||
|
- https://docs.platformio.org/en/latest/advanced/unit-testing/index.html
|
Loading…
Add table
Reference in a new issue