From 3b63f9703da155f3f98bf59ed1754e38a18a028f Mon Sep 17 00:00:00 2001 From: "Donald R. Little Jr" Date: Thu, 16 Nov 2023 14:24:46 -0500 Subject: [PATCH] Initial commit of base code --- .vscode/extensions.json | 10 + .vscode/settings.json | 3 + include/README | 39 ++++ lib/README | 46 ++++ platformio.ini | 29 +++ src/main.cpp | 543 ++++++++++++++++++++++++++++++++++++++++++++++++ test/README | 11 + 7 files changed, 681 insertions(+) create mode 100644 .vscode/extensions.json create mode 100644 .vscode/settings.json create mode 100644 include/README create mode 100644 lib/README create mode 100644 platformio.ini create mode 100644 src/main.cpp create mode 100644 test/README diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..080e70d --- /dev/null +++ b/.vscode/extensions.json @@ -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" + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..cad7657 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "cmake.configureOnOpen": false +} \ No newline at end of file diff --git a/include/README b/include/README new file mode 100644 index 0000000..194dcd4 --- /dev/null +++ b/include/README @@ -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 diff --git a/lib/README b/lib/README new file mode 100644 index 0000000..6debab1 --- /dev/null +++ b/lib/README @@ -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 +#include + +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 diff --git a/platformio.ini b/platformio.ini new file mode 100644 index 0000000..1a4eef4 --- /dev/null +++ b/platformio.ini @@ -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 diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..1e66f4a --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,543 @@ +#include +#include +#include +#include + +#include +#include +#include + +// 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= 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= 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 }; diff --git a/test/README b/test/README new file mode 100644 index 0000000..9b1e87b --- /dev/null +++ b/test/README @@ -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