Initial commit of base code

This commit is contained in:
Donald R. Little Jr 2023-11-16 14:24:46 -05:00
parent 21caa63178
commit 3b63f9703d
7 changed files with 681 additions and 0 deletions

10
.vscode/extensions.json vendored Normal file
View File

@ -0,0 +1,10 @@
{
// See http://go.microsoft.com/fwlink/?LinkId=827846
// for the documentation about the extensions.json format
"recommendations": [
"platformio.platformio-ide"
],
"unwantedRecommendations": [
"ms-vscode.cpptools-extension-pack"
]
}

3
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,3 @@
{
"cmake.configureOnOpen": false
}

39
include/README Normal file
View File

@ -0,0 +1,39 @@
This directory is intended for project header files.
A header file is a file containing C declarations and macro definitions
to be shared between several project source files. You request the use of a
header file in your project source file (C, C++, etc) located in `src` folder
by including it, with the C preprocessing directive `#include'.
```src/main.c
#include "header.h"
int main (void)
{
...
}
```
Including a header file produces the same results as copying the header file
into each source file that needs it. Such copying would be time-consuming
and error-prone. With a header file, the related declarations appear
in only one place. If they need to be changed, they can be changed in one
place, and programs that include the header file will automatically use the
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
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'.
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:
* Include Syntax
* Include Operation
* Once-Only Headers
* Computed Includes
https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html

46
lib/README Normal file
View File

@ -0,0 +1,46 @@
This directory is intended for project specific (private) libraries.
PlatformIO will compile them to static libraries and link into executable file.
The source code of each library should be placed in a an own separate directory
("lib/your_library_name/[here are source files]").
For example, see a structure of the following two libraries `Foo` and `Bar`:
|--lib
| |
| |--Bar
| | |--docs
| | |--examples
| | |--src
| | |- Bar.c
| | |- Bar.h
| | |- library.json (optional, 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
and a contents of `src/main.c`:
```
#include <Foo.h>
#include <Bar.h>
int main (void)
{
...
}
```
PlatformIO Library Dependency Finder will find automatically dependent
libraries scanning project source files.
More information about PlatformIO Library Dependency Finder
- https://docs.platformio.org/page/librarymanager/ldf.html

29
platformio.ini Normal file
View File

@ -0,0 +1,29 @@
; PlatformIO Project Configuration File
;
; Build options: build flags, source filter
; Upload options: custom upload port, speed and extra flags
; Library options: dependencies, extra library storages
; Advanced options: extra scripting
;
; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html
[platformio]
default_envs = nodemcuv2
[env:huzzah]
platform = https://github.com/platformio/platform-espressif8266.git
board = huzzah
framework = arduino
lib_deps = tzapu/WiFiManager@^0.16.0
upload_speed = 115200
monitor_speed = 115200
[env:nodemcuv2]
platform = https://github.com/platformio/platform-espressif8266.git
board = nodemcuv2
framework = arduino
lib_deps = tzapu/WiFiManager@^0.16.0
upload_speed = 115200
monitor_speed = 115200
upload_port = COM5
monitor_port = COM5

543
src/main.cpp Normal file
View File

@ -0,0 +1,543 @@
#include <Arduino.h>
#include <ESP8266WiFi.h>
#include <WiFiUdp.h>
#include <stdlib.h>
#include <DNSServer.h>
#include <ESP8266WebServer.h>
#include <WiFiManager.h>
// The GPIO pin numbers for the three colors. Make sure these match your wiring/schematic.
#define GPIO_RED 13
#define GPIO_GREEN 12
#define GPIO_BLUE 15
// The target ID of this device on the network if ID_SWITCHED is false
// Commands sent to target ID 0 (zero) will be executed by ALL targets
#define TARGET_ID_FIXED 1
// If a 4-position dip switch is present and tied to 4 GPIO pins, set ID_SWITCHED to true and put the pin numbers in the ID_BIT_? settings
#define ID_SWITCHED true
#define ID_BIT_0 16
#define ID_BIT_1 14
#define ID_BIT_2 4
#define ID_BIT_3 5
// Used to turn debug serial output on/off
#define DEBUG false
// UDP Command values
#define CMD_OFF 0x00
#define CMD_SETLEVELS 0x01
#define CMD_AUTOPATTERN 0x02
#define CMD_AUTODISABLE 0x03
// Auto-cycler state conditions
#define AUTO_DISABLED 0x00
#define AUTO_ACTIVE 0x01
// Maximum analog output level
#define MAX_ANALOG 1023
// ID Numbers for the UDP Packet filter
#define PACKET_FILTER_1 4039196302
#define PACKET_FILTER_2 3194769291
// Set up this reference so we can put the huge array definition at the end
extern const uint16_t gamma10[];
// Variables for tracking blue LED blink state during initialization
volatile bool blueInitState = false;
volatile unsigned long blueToggleCount;
// Struct for storing color value sets
// Colors are obvious. restDuration is how long to rest on this color.
struct colorTriplet {
short red;
short green;
short blue;
unsigned long restDuration;
colorTriplet() {
red = 0;
green = 0;
blue = 0;
restDuration = 0;
}
};
// This gets dynamically reset in setup() from the DIP switch or the #define at the top of the sketch so make sure this is set to 0 (zero)
byte myTargetID = 0;
// Variables for keeping the state of automatic color switching
byte autoMode = AUTO_DISABLED;
bool autoActive = false;
// For storing automatic color values
#define MAX_AUTO_COLORS 35
unsigned int rampDuration;
byte numAutoColors = 0;
struct colorTriplet autoColors[MAX_AUTO_COLORS];
byte autoColorTargetIndex = 0;
short redStatic = 0;
short greenStatic = 0;
short blueStatic = 0;
bool resting = false;
// Debugging crap
String rampValues[200] = {""};
int rampValuesIndex = 0;
// We're going to listen on port 6565
WiFiUDP listener;
// Variables to track message IDs so we don't process duplicate messages
unsigned int lastMessageID = 0;
unsigned int curMessageID = 0;
// Reset function
void(* resetFunc) (void) = 0;
void inline blueBlinkISR(void) {
if ( blueToggleCount < 60 ) {
blueInitState = !blueInitState;
analogWrite(GPIO_BLUE, (blueInitState) ? MAX_ANALOG : 0);
}
blueToggleCount++;
if ( DEBUG ) {
Serial.print("Init State: bIS=");
Serial.print(blueInitState);
Serial.print(" bTC=");
Serial.println(blueToggleCount);
}
if ( blueToggleCount > 600 ) {
timer0_detachInterrupt();
analogWrite(GPIO_BLUE, 0);
ESP.restart();
}
timer0_write(ESP.getCycleCount() + 40000000);
}
// Set up our initial states and WiFi
void setup() {
// Set the three color PWM pins to output
pinMode(GPIO_RED, OUTPUT);
pinMode(GPIO_GREEN, OUTPUT);
pinMode(GPIO_BLUE, OUTPUT);
// If ID_SWITCHED is true then set myTargetID from the dip switch, otherwise set it from TARGET_ID_FIXED
if ( ID_SWITCHED ) {
pinMode(ID_BIT_0, INPUT_PULLUP);
pinMode(ID_BIT_1, INPUT_PULLUP);
pinMode(ID_BIT_2, INPUT_PULLUP);
pinMode(ID_BIT_3, INPUT_PULLUP);
bitWrite(myTargetID, 0, ((digitalRead(ID_BIT_0) == HIGH) ? 1 : 0));
bitWrite(myTargetID, 1, ((digitalRead(ID_BIT_1) == HIGH) ? 1 : 0));
bitWrite(myTargetID, 2, ((digitalRead(ID_BIT_2) == HIGH) ? 1 : 0));
bitWrite(myTargetID, 3, ((digitalRead(ID_BIT_3) == HIGH) ? 1 : 0));
//pinMode(ID_BIT_1, INPUT);
} else {
myTargetID = TARGET_ID_FIXED;
}
// Start up the serial console output
Serial.begin(115200);
Serial.println("--- Serial Output Started ---");
delay(10);
WiFiManager wifiManager;
//wifiManager.resetSettings();
noInterrupts();
timer0_isr_init();
timer0_attachInterrupt(blueBlinkISR);
timer0_write(ESP.getCycleCount() + 1000);
interrupts();
wifiManager.autoConnect("JaJLEDController");
// Show our ID!
Serial.println();
Serial.print("My target ID: ");
Serial.println(myTargetID);
// Turn off blue LED and disable interrupt
timer0_detachInterrupt();
analogWrite(GPIO_BLUE, 0);
Serial.println("");
Serial.println("WiFi connected");
Serial.println("IP address: ");
Serial.println(WiFi.localIP());
// Start listening for packets on port 6565 (yes, this should probably be defined at the top)
listener.begin(6565);
}
bool processMessage() {
struct colorTriplet colorBlank;
byte command;
uint32 messageTargetID = 0;
// Read in the packet data to buff (up to 1024 bytes which is way more than we would ever need)
char buff[1024];
listener.read(buff, 1024);
listener.flush();
// Check for the Packet Filter ID values and skip packet if not matched
unsigned int filter1, filter2;
memcpy(&filter1, (char*)buff, 4);
memcpy(&filter2, (char*)buff + 4, 4);
if ( (filter1 != PACKET_FILTER_1) || (filter2 != PACKET_FILTER_2) ) {
if ( DEBUG ) {
Serial.println("Filter Fail: " + String(filter1) + ", " + String(filter2));
}
return false;
}
// Parse out the messageID field
memcpy(&curMessageID, (char*)buff + 8, 4);
// If we've seen the message before, skip it, otherwise update lastMessageID
if ( curMessageID == lastMessageID ) return false;
lastMessageID = curMessageID;
// Parse out the command and target ID fields
memcpy(&command, (char*)buff + 12, 1);
memcpy(&messageTargetID, (char*)buff + 13, 4);
// If this packet isn't destined for ID=0 (all targets) or ID=TARGET_ID (our ID) then stop processing
if ( (myTargetID != 0 ) && (messageTargetID != 0) && (messageTargetID != myTargetID) ) {
if ( DEBUG ) {
Serial.println("Got packet for different ID! Mine=" + String(myTargetID) + " Target=" + String(messageTargetID));
}
return false;
}
if ( DEBUG ) Serial.println("Got UDP packet!");
// Clear out the global autoColors[] array
memset(autoColors, 0, sizeof(autoColors));
// If we get a CMD_SETLEVELS then disable autoMode, set the new static values and ramp to that color
if ( command == CMD_SETLEVELS ) {
autoMode = AUTO_DISABLED;
numAutoColors = 1;
autoColorTargetIndex = 0;
memcpy(&rampDuration, (char*)buff + 17, 4);
memcpy(&autoColors[0].red, (char*)buff + 21, 1);
memcpy(&autoColors[0].green, (char*)buff + 22, 1);
memcpy(&autoColors[0].blue, (char*)buff + 23, 1);
autoColors[0].red = map(autoColors[0].red, 0, 255, 0, 1023);
autoColors[0].green = map(autoColors[0].green, 0, 255, 0, 1023);
autoColors[0].blue = map(autoColors[0].blue, 0, 255, 0, 1023);
redStatic = autoColors[0].red;
greenStatic = autoColors[0].green;
blueStatic = autoColors[0].blue;
if ( DEBUG ) {
String debugOutput = "Setting Levels: ";
debugOutput += String(redStatic) + ", ";
debugOutput += String(greenStatic) + ", ";
debugOutput += String(blueStatic) + ", ";
debugOutput += String(rampDuration);
Serial.println(debugOutput);
}
return true;
}
// If we get a CMD_AUTOPATTERN then build up the autoColors array and set autoMode to AUTO_ACTIVE
if ( command == CMD_AUTOPATTERN ) {
String debugOutput = "";
autoMode = AUTO_ACTIVE;
autoColorTargetIndex = 0;
memcpy(&rampDuration, (char*)buff + 17, 4);
memcpy(&numAutoColors, (char*)buff + 21, 1);
if ( numAutoColors > MAX_AUTO_COLORS ) numAutoColors = MAX_AUTO_COLORS;
if ( DEBUG ) debugOutput += "New AutoPattern " + String(numAutoColors) + ": ";
for ( int i=0; i<numAutoColors; i++ ) {
memcpy(&autoColors[i].red, (char*)buff + 22 + (i*7), 1);
memcpy(&autoColors[i].green, (char*)buff + 23 + (i*7), 1);
memcpy(&autoColors[i].blue, (char*)buff + 24 + (i*7), 1);
memcpy(&autoColors[i].restDuration, (char*)buff + 25 + (i*7), 4);
autoColors[i].red = map(autoColors[i].red, 0, 255, 0, 1023);
autoColors[i].green = map(autoColors[i].green, 0, 255, 0, 1023);
autoColors[i].blue = map(autoColors[i].blue, 0, 255, 0, 1023);
if ( DEBUG ) {
debugOutput += String(i) + "=[";
debugOutput += String(autoColors[i].red) + ", ";
debugOutput += String(autoColors[i].green) + ", ";
debugOutput += String(autoColors[i].blue) + ", ";
debugOutput += String(autoColors[i].restDuration) + "], ";
}
}
if ( DEBUG ) {
debugOutput += "Ramp=" + String(rampDuration);
Serial.println(debugOutput);
}
return true;
}
// If we get a CMD_AUTODISABLE then disable autoMode and ramp back to the last static color levels
if ( command == CMD_AUTODISABLE ) {
autoMode = AUTO_DISABLED;
numAutoColors = 1;
autoColorTargetIndex = 0;
autoColors[0].red = redStatic;
autoColors[0].green = greenStatic;
autoColors[0].blue = blueStatic;
rampDuration = 1000;
if ( DEBUG ) {
String debugOutput = "Resetting Levels to static: ";
debugOutput += String(redStatic) + ", ";
debugOutput += String(greenStatic) + ", ";
debugOutput += String(blueStatic) + ", ";
debugOutput += "Ramp=" + String(rampDuration);
Serial.println(debugOutput);
}
return true;
}
// If we get a CMD_OFF then set the levels to zero and ensure autoMode is disabled
// The rampDuration is set to the same value as the step value during ramping (yes, this should probably be a constant or a #define)
if ( command == CMD_OFF ) {
autoMode = AUTO_DISABLED;
numAutoColors = 1;
autoColorTargetIndex = 0;
rampDuration = 10;
autoColors[0].restDuration = 10000;
redStatic = 0;
greenStatic = 0;
blueStatic = 0;
if ( DEBUG ) Serial.println("Shutting off LEDs");
return true;
}
return false;
}
void loop() {
static short redTarget = 0, greenTarget = 0, blueTarget = 0;
static short redInitial = 0, greenInitial = 0, blueInitial = 0;
static short redPrevious = 0, greenPrevious = 0, bluePrevious = 0;
static short redLevel = 0, greenLevel = 0, blueLevel = 0;
static bool newColor = false;
static unsigned long nextDIPSample = 0;
unsigned long nowMillis = millis();
static unsigned long nextRampMillis;
static unsigned long rampStartMillis;
static unsigned long restingEndMillis;
static unsigned long reportMillis = 0;
static unsigned long repCount = 0;
nowMillis = millis();
if ( nowMillis >= reportMillis ) {
repCount++;
//Serial.print("Still alive... ("); Serial.print(repCount); Serial.println(")");
/*
String debugOutput = "Current State: C=";
debugOutput += String(redLevel) + "," + String(greenLevel) + "," + String(blueLevel) + " ";
debugOutput += "T=" + String(redTarget) + "," + String(greenTarget) + "," + String(blueTarget) + " ";
debugOutput += "Index=" + String(autoColorTargetIndex) + " Ramp=" + String(rampDuration);
Serial.println(debugOutput);
*/
reportMillis = nowMillis + 1000;
}
// Check to see if we have a new packet waiting and parse it out if we do
int packetSize = listener.parsePacket();
if ( packetSize ) {
if ( processMessage() ) {
// Always reset newColor and resting states when we get a new UDP color command
newColor = true;
resting = false;
}
}
if ( newColor ) {
// Reset rampColors Debugging
for ( int i=0; i<200; i++ ) { rampValues[i] = ""; };
rampValuesIndex = 0;
// If the incoming auto-color pattern (from a CMD_AUTOPATTERN) has a single all zero color,
// and autoMode is AUTO_ACTIVE then generate colors randomly instead of from the pattern
if ( (autoMode == AUTO_ACTIVE) && (numAutoColors == 1) && (autoColors[0].red == 0) && (autoColors[0].green == 0) && (autoColors[0].blue == 0) ) {
//redTarget = pgm_read_word(&gamma10[random(0, MAX_ANALOG)]);
//greenTarget = pgm_read_word(&gamma10[random(0, MAX_ANALOG)]);
//blueTarget = pgm_read_word(&gamma10[random(0, MAX_ANALOG)]);
redTarget = gamma10[random(0, MAX_ANALOG)];
greenTarget = gamma10[random(0, MAX_ANALOG)];
blueTarget = gamma10[random(0, MAX_ANALOG)];
} else {
//redTarget = pgm_read_word(&gamma10[autoColors[autoColorTargetIndex].red]);
//greenTarget = pgm_read_word(&gamma10[autoColors[autoColorTargetIndex].green]);
//blueTarget = pgm_read_word(&gamma10[autoColors[autoColorTargetIndex].blue]);
redTarget = gamma10[autoColors[autoColorTargetIndex].red];
greenTarget = gamma10[autoColors[autoColorTargetIndex].green];
blueTarget = gamma10[autoColors[autoColorTargetIndex].blue];
}
redInitial = redLevel;
greenInitial = greenLevel;
blueInitial = blueLevel;
newColor = false;
rampStartMillis = nowMillis;
nextRampMillis = rampStartMillis;
if ( DEBUG ) {
String debugOutput = "Switching to new color: C=";
debugOutput += String(redInitial) + "," + String(greenInitial) + "," + String(blueInitial) + " ";
debugOutput += "T=" + String(redTarget) + "," + String(greenTarget) + "," + String(blueTarget) + " ";
debugOutput += "Index=" + String(autoColorTargetIndex) + " Ramp=" + String(rampDuration);
Serial.println(debugOutput);
}
}
if ( (redLevel != redTarget) || (greenLevel != greenTarget) || (blueLevel != blueTarget) ) {
// Calculate the current rampInterval (how many milliseconds between start of ramp and now)
unsigned long rampInterval = nowMillis - rampStartMillis;
// If nowMillis wrapped around the max INT32 we will just go straight to the last iteration of the ramp
if ( rampInterval > rampDuration ) rampInterval = rampDuration;
// If we aren't at the target values yet, map the current number of milliseconds
// into the ramp (rampInterval) to the (color)Initial -> (color)Target values
if ( redLevel != redTarget ) {
redLevel = map(rampInterval, 0, rampDuration, redInitial, redTarget);
if ( abs(redTarget - redLevel) == 1 ) redLevel = redTarget;
}
if ( greenLevel != greenTarget ) {
greenLevel = map(rampInterval, 0, rampDuration, greenInitial, greenTarget);
if ( abs(greenTarget - greenLevel) == 1 ) greenLevel = greenTarget;
}
if ( blueLevel != blueTarget ) {
blueLevel = map(rampInterval, 0, rampDuration, blueInitial, blueTarget);
if ( abs(blueTarget - blueLevel) == 1 ) blueLevel = blueTarget;
}
if ( (nowMillis >= nextRampMillis) || ((redLevel == redTarget) && (greenLevel == greenTarget) && (blueLevel == blueTarget)) ) {
if ( redLevel != redPrevious ) {
analogWrite(GPIO_RED, redLevel);
redPrevious = redLevel;
}
if ( greenLevel != greenPrevious ) {
analogWrite(GPIO_GREEN, greenLevel);
greenPrevious = greenLevel;
}
if ( blueLevel != bluePrevious ) {
analogWrite(GPIO_BLUE, blueLevel);
bluePrevious = blueLevel;
}
//rampValues[rampValuesIndex] = (String)"(" + (nowMillis - nextRampMillis) + "," + rampValuesIndex + ")" + "(" + redLevel + "," + greenLevel + "," + blueLevel + "),";
//rampValuesIndex++;
nextRampMillis = nowMillis + 15;
}
// If all the colors are at their targets and we aren't resting, then start resting
} else if ( !resting ) {
resting = true;
restingEndMillis = nowMillis + autoColors[autoColorTargetIndex].restDuration;
if ( DEBUG ) {
String debugOutput = "Now resting for: ";
debugOutput += String(autoColors[autoColorTargetIndex].restDuration) + " on ";
debugOutput += String(autoColorTargetIndex);
Serial.println(debugOutput);
//Serial.print("rampValues=[");
//for ( int i=0; i<rampValuesIndex; i++ ) Serial.print(rampValues[i]);
//Serial.println("]");
}
// If we are resting and the restingEndMillis passes then it is time to ramp to
// the next color IF AND ONLY IF we are in a pattern (i.e. don't keep ramping if we're on a static color)
} else if ( (nowMillis >= restingEndMillis) && (autoMode == AUTO_ACTIVE) ) {
if ( DEBUG ) { Serial.print("Done resting on index=" + String(autoColorTargetIndex)); }
resting = false;
newColor = true;
autoColorTargetIndex = (autoColorTargetIndex < (numAutoColors - 1)) ? autoColorTargetIndex + 1 : 0;
}
// If the DIP switches change, go ahead and adjust myTargetID accordingly. Saves having to restart.
// In order to save clock cycles this is only done every 10 seconds.
if ( ID_SWITCHED && (nowMillis >= nextDIPSample) ) {
byte newVal = 0;
bitWrite(newVal, 0, ((digitalRead(ID_BIT_0) == HIGH) ? 1 : 0));
bitWrite(newVal, 1, ((digitalRead(ID_BIT_1) == HIGH) ? 1 : 0));
bitWrite(newVal, 2, ((digitalRead(ID_BIT_2) == HIGH) ? 1 : 0));
bitWrite(newVal, 3, ((digitalRead(ID_BIT_3) == HIGH) ? 1 : 0));
if ( myTargetID != newVal ) {
myTargetID = newVal;
if ( DEBUG ) {
Serial.println("BIT_0: " + String(digitalRead(ID_BIT_0)));
Serial.println("BIT_1: " + String(digitalRead(ID_BIT_1)));
Serial.println("BIT_2: " + String(digitalRead(ID_BIT_2)));
Serial.println("BIT_3: " + String(digitalRead(ID_BIT_3)));
Serial.println("New Target ID Detected: " + String(myTargetID));
}
}
nextDIPSample = nowMillis + 10000; // Sample the DIP switches every 10 seconds
}
yield();
}
//const uint16_t PROGMEM gamma10[] {
const uint16_t gamma10[] {
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,2,2,2,2,2,
2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,
4,4,4,4,4,4,4,4,4,4,4,4,4,5,5,
5,5,5,5,5,5,5,5,5,6,6,6,6,6,6,
6,6,6,7,7,7,7,7,7,7,7,7,8,8,8,
8,8,8,8,8,9,9,9,9,9,9,9,9,10,10,
10,10,10,10,10,11,11,11,11,11,11,12,12,12,12,
12,12,12,13,13,13,13,13,13,14,14,14,14,14,15,
15,15,15,15,15,16,16,16,16,16,17,17,17,17,17,
18,18,18,18,18,19,19,19,19,20,20,20,20,20,21,
21,21,21,22,22,22,22,23,23,23,23,24,24,24,24,
25,25,25,25,26,26,26,26,27,27,27,27,28,28,28,
29,29,29,29,30,30,30,31,31,31,31,32,32,32,33,
33,33,34,34,34,35,35,35,35,36,36,36,37,37,37,
38,38,38,39,39,40,40,40,41,41,41,42,42,42,43,
43,43,44,44,45,45,45,46,46,46,47,47,48,48,48,
49,49,50,50,50,51,51,52,52,52,53,53,54,54,55,
55,55,56,56,57,57,58,58,58,59,59,60,60,61,61,
62,62,63,63,63,64,64,65,65,66,66,67,67,68,68,
69,69,70,70,71,71,72,72,73,73,74,74,75,75,76,
76,77,77,78,79,79,80,80,81,81,82,82,83,83,84,
85,85,86,86,87,87,88,89,89,90,90,91,92,92,93,
93,94,95,95,96,96,97,98,98,99,99,100,101,101,102,
103,103,104,105,105,106,106,107,108,108,109,110,110,111,112,
112,113,114,115,115,116,117,117,118,119,119,120,121,122,122,
123,124,124,125,126,127,127,128,129,130,130,131,132,132,133,
134,135,136,136,137,138,139,139,140,141,142,143,143,144,145,
146,146,147,148,149,150,151,151,152,153,154,155,155,156,157,
158,159,160,161,161,162,163,164,165,166,167,167,168,169,170,
171,172,173,174,175,175,176,177,178,179,180,181,182,183,184,
185,186,186,187,188,189,190,191,192,193,194,195,196,197,198,
199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,
214,215,216,217,218,219,220,221,222,223,224,225,226,228,229,
230,231,232,233,234,235,236,237,238,239,241,242,243,244,245,
246,247,248,249,251,252,253,254,255,256,257,259,260,261,262,
263,264,266,267,268,269,270,272,273,274,275,276,278,279,280,
281,282,284,285,286,287,289,290,291,292,294,295,296,297,299,
300,301,302,304,305,306,308,309,310,311,313,314,315,317,318,
319,321,322,323,325,326,327,329,330,331,333,334,336,337,338,
340,341,342,344,345,347,348,349,351,352,354,355,356,358,359,
361,362,364,365,366,368,369,371,372,374,375,377,378,380,381,
383,384,386,387,389,390,392,393,395,396,398,399,401,402,404,
405,407,408,410,412,413,415,416,418,419,421,423,424,426,427,
429,431,432,434,435,437,439,440,442,444,445,447,448,450,452,
453,455,457,458,460,462,463,465,467,468,470,472,474,475,477,
479,480,482,484,486,487,489,491,493,494,496,498,500,501,503,
505,507,509,510,512,514,516,518,519,521,523,525,527,528,530,
532,534,536,538,539,541,543,545,547,549,551,553,554,556,558,
560,562,564,566,568,570,572,574,575,577,579,581,583,585,587,
589,591,593,595,597,599,601,603,605,607,609,611,613,615,617,
619,621,623,625,627,629,631,633,635,637,640,642,644,646,648,
650,652,654,656,658,660,663,665,667,669,671,673,675,678,680,
682,684,686,688,690,693,695,697,699,701,704,706,708,710,712,
715,717,719,721,724,726,728,730,733,735,737,739,742,744,746,
749,751,753,755,758,760,762,765,767,769,772,774,776,779,781,
783,786,788,790,793,795,798,800,802,805,807,810,812,814,817,
819,822,824,827,829,831,834,836,839,841,844,846,849,851,854,
856,859,861,864,866,869,871,874,876,879,881,884,887,889,892,
894,897,899,902,905,907,910,912,915,918,920,923,925,928,931,
933,936,939,941,944,947,949,952,955,957,960,963,965,968,971,
973,976,979,982,984,987,990,992,995,998,1001,1004,1006,1009,1012,
1015,1017,1020,1023 };

11
test/README Normal file
View 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