From dc1485e6e848ab629eceef05d053d9daa0a7a786 Mon Sep 17 00:00:00 2001 From: Junior Date: Thu, 8 Jan 2026 15:42:17 -0500 Subject: [PATCH] Make project compatible with ESP32 & ESP8266. Add Cool and Warm white channels. Add OTA. Switch to 8bit resolution. BROKEN: message processing still needs fixed. --- include/esp32.h | 6 ++ include/esp8266.h | 6 ++ platformio.ini | 51 ++++++++-- src/main.cpp | 284 ++++++++++++++++++++++++++++++------------------------ 4 files changed, 212 insertions(+), 135 deletions(-) create mode 100644 include/esp32.h create mode 100644 include/esp8266.h diff --git a/include/esp32.h b/include/esp32.h new file mode 100644 index 0000000..cbef870 --- /dev/null +++ b/include/esp32.h @@ -0,0 +1,6 @@ +#define GPIO_RED 17 +#define GPIO_GREEN 18 +#define GPIO_BLUE 16 +#define GPIO_COOLWHITE 15 +#define GPIO_WARMWHITE 19 +#define BOARD "ESP32" \ No newline at end of file diff --git a/include/esp8266.h b/include/esp8266.h new file mode 100644 index 0000000..edd53e9 --- /dev/null +++ b/include/esp8266.h @@ -0,0 +1,6 @@ +#define GPIO_RED 13 +#define GPIO_GREEN 12 +#define GPIO_BLUE 15 +#define GPIO_COOLWHITE 4 +#define GPIO_WARMWHITE 5 +#define BOARD "ESP8266" \ No newline at end of file diff --git a/platformio.ini b/platformio.ini index 2c8edb5..94c4a04 100644 --- a/platformio.ini +++ b/platformio.ini @@ -7,23 +7,56 @@ ; ; 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 +[platformio] +;default_envs = esp32doit-devkit-v1 +;default_envs = nodemcuv2 +default_envs = huzzah + +[common] +build_flags = + -D DEBUG=true +lib_deps = + tzapu/WiFiManager@^2.0.17 + +[env:esp32doit-devkit-v1] +platform = espressif32 +board = esp32doit-devkit-v1 framework = arduino -lib_deps = tzapu/WiFiManager@^0.16.0 -upload_speed = 115200 monitor_speed = 115200 +upload_protocol = espota +upload_port = 192.168.2.217 +;upload_port = COM7 +build_flags = + ${common.build_flags} +lib_deps = + ${common.lib_deps} +lib_compat_mode = strict + [env:nodemcuv2] platform = https://github.com/platformio/platform-espressif8266.git board = nodemcuv2 framework = arduino -lib_deps = tzapu/WiFiManager@^0.16.0 +upload_protocol = espota +upload_port = 192.168.2.238 +;upload_port = COM6 upload_speed = 115200 monitor_speed = 115200 -;upload_protocol = espota +build_flags = + ${common.build_flags} +lib_deps = + ${common.lib_deps} + +[env:huzzah] +platform = https://github.com/platformio/platform-espressif8266.git +board = huzzah +framework = arduino ;upload_port = 192.168.2.238 +upload_port = COM6 +upload_speed = 115200 +monitor_speed = 115200 +build_flags = + ${common.build_flags} +lib_deps = + ${common.lib_deps} \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index b82289e..7446fc6 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,38 +1,30 @@ #include -#include -#include -#include +#include +#include +#if defined(ARDUINO_ARCH_ESP32) + #include + #include + #include +#elif defined(ARDUINO_ARCH_ESP8266) + #include + #include + #include +#endif #include #include -#include -#include -#include -#include -#define FIRMWARE "2.2" -#define BOARD "ESP8266 Dev" +#define BOARDFIRMWARE "3.0.0" #define SERVICE "LEDController" -const String STATUSURL = "http://moon.basement.lan/l/boardstatus.php"; -// 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 +#define UDP_PORT 6565 -// 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 defined(ARDUINO_ARCH_ESP32) +hw_timer_t *timer = NULL; +#endif -// 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 +const String HOSTADDRESS = "moon.basement.lan"; +const unsigned int HOSTPORT = 80; +const String STATUSURL = "/l/boardstatus.php"; // UDP Command values #define CMD_OFF 0x00 @@ -45,7 +37,7 @@ const String STATUSURL = "http://moon.basement.lan/l/boardstatus.php"; #define AUTO_ACTIVE 0x01 // Maximum analog output level -#define MAX_ANALOG 1023 +#define MAX_ANALOG 255 // ID Numbers for the UDP Packet filter #define PACKET_FILTER_1 4039196302 @@ -53,6 +45,7 @@ const String STATUSURL = "http://moon.basement.lan/l/boardstatus.php"; // Set up this reference so we can put the huge array definition at the end extern const uint16_t gamma10[]; +extern const uint8_t gamma8[]; // Variables for tracking blue LED blink state during initialization volatile bool blueInitState = false; @@ -64,17 +57,21 @@ struct colorTriplet { short red; short green; short blue; + short warmwhite; + short coolwhite; unsigned long restDuration; colorTriplet() { red = 0; green = 0; blue = 0; + warmwhite = 0; + coolwhite = 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) +// This gets set by the return value from setting the board status. Zero (0) means "all boards" byte myTargetID = 0; // Variables for keeping the state of automatic color switching @@ -90,100 +87,129 @@ byte autoColorTargetIndex = 0; short redStatic = 0; short greenStatic = 0; short blueStatic = 0; -bool resting = false; +short warmwhiteStatic = 0; +short coolwhiteStatic = 0; +bool resting = true; // Debugging crap String rampValues[200] = {""}; int rampValuesIndex = 0; -// We're going to listen on port 6565 +// We're going to listen on port UDP_PORT 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; - +#if defined(ARDUINO_ARCH_ESP32) +void ARDUINO_ISR_ATTR blueBlinkISR() { +#elif defined(ARDUINO_ARCH_ESP8266) void inline blueBlinkISR(void) { - if ( blueToggleCount < 60 ) { +#endif + if ( blueToggleCount <= 600 ) { 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); - } + blueToggleCount = blueToggleCount + 1; if ( blueToggleCount > 600 ) { + #if defined(ARDUINO_ARCH_ESP8266) timer0_detachInterrupt(); + #endif analogWrite(GPIO_BLUE, 0); + analogWrite(GPIO_RED, MAX_ANALOG); + delay(1000); ESP.restart(); } + #if defined(ARDUINO_ARCH_ESP8266) timer0_write(ESP.getCycleCount() + 40000000); + #endif } -void sendDeviceStatus() { - WiFiClient client; - HTTPClient http; - http.begin(client, STATUSURL.c_str()); - http.addHeader("Content-Type", "application/x-www-form-urlencoded"); - String httpData = "mac=" + WiFi.macAddress() + "&board=" + BOARD + "&firmware=" + FIRMWARE + "&service=" + SERVICE; - int httpResponseCode = http.POST(httpData); - http.end(); +unsigned long sendDeviceStatus() { + if ( DEBUG ) Serial.println("Sending device status"); + WiFiClient client; + HTTPClient http; + http.begin(client, ("http://" + HOSTADDRESS + ":" + String(HOSTPORT) + STATUSURL).c_str()); + http.addHeader("Content-Type", "application/x-www-form-urlencoded"); + String httpData = "mac=" + WiFi.macAddress() + "&board=" + BOARD + "&firmware=" + BOARDFIRMWARE + "&service=" + SERVICE; + int httpResponseCode = http.POST(httpData); + if ( DEBUG && (httpResponseCode != 200) ) { + Serial.println("Error sending status: " + String(httpResponseCode)); + } else if ( httpResponseCode == 200 ) { + String response = http.getString(); + myTargetID = response.toInt(); + if ( DEBUG ) { + Serial.println("Device status sent successfully"); + Serial.println("Received Target ID: " + String(myTargetID)); + } + } + http.end(); + return millis(); } // Set up our initial states and WiFi void setup() { - // Set the three color PWM pins to output + // Set the five 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; - } + pinMode(GPIO_WARMWHITE, OUTPUT); + pinMode(GPIO_COOLWHITE, OUTPUT); + analogWrite(GPIO_RED, 0); + analogWrite(GPIO_GREEN, 0); + analogWrite(GPIO_BLUE, 0); + analogWrite(GPIO_WARMWHITE, 0); + analogWrite(GPIO_COOLWHITE, 0); // Start up the serial console output Serial.begin(115200); - Serial.println("--- Serial Output Started ---"); + if ( DEBUG ) Serial.println("--- Serial Output Started ---"); delay(10); WiFiManager wifiManager; //wifiManager.resetSettings(); + #if defined(ARDUINO_ARCH_ESP32) + timer = timerBegin(500000); + timerAttachInterrupt(timer, &blueBlinkISR); + timerAlarm(timer, 500000, true, 0); // Set to 500ms intervals, auto-reload + #elif defined(ARDUINO_ARCH_ESP8266) noInterrupts(); timer0_isr_init(); timer0_attachInterrupt(blueBlinkISR); timer0_write(ESP.getCycleCount() + 1000); interrupts(); + #endif wifiManager.autoConnect("JaJLEDController"); + // Turn off blue LED and disable interrupt + #if defined(ARDUINO_ARCH_ESP32) + timerDetachInterrupt(timer); + timerAlarm(timer, 50, false, 0); + #elif defined(ARDUINO_ARCH_ESP8266) + timer0_detachInterrupt(); + #endif + analogWrite(GPIO_BLUE, 0); sendDeviceStatus(); ArduinoOTA.onStart([]() { + analogWrite(GPIO_RED, 0); + analogWrite(GPIO_GREEN, 0); + analogWrite(GPIO_BLUE, 0); + analogWrite(GPIO_WARMWHITE, 0); + analogWrite(GPIO_COOLWHITE, 0); String type; if ( ArduinoOTA.getCommand() == U_FLASH ) type = "sketch"; else // U_SPIFFS type = "filesystem"; - Serial.println("Start OTA updating " + type); + if ( DEBUG ) Serial.println("Start OTA updating " + type); }); ArduinoOTA.onEnd([]() { - Serial.println("\nOTA Update Completed"); + if ( DEBUG ) Serial.println("\nOTA Update Completed"); + analogWrite(GPIO_BLUE, 0); + delay(1000); }); ArduinoOTA.onError([](ota_error_t error) { + if ( !DEBUG ) return; Serial.printf("OTA Update Error[%u]: ", error); if (error == OTA_AUTH_ERROR) Serial.println("Auth Failed"); else if (error == OTA_BEGIN_ERROR) Serial.println("Begin Failed"); @@ -191,30 +217,35 @@ void setup() { else if (error == OTA_RECEIVE_ERROR) Serial.println("Receive Failed"); else if (error == OTA_END_ERROR) Serial.println("End Failed"); }); + ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) { + uint8_t level = ((progress * 255) / total); + analogWrite(GPIO_GREEN, level); + analogWrite(GPIO_BLUE, level); + }); + ArduinoOTA.onStart([]() { + analogWrite(GPIO_RED, 0); + analogWrite(GPIO_GREEN, 0); + analogWrite(GPIO_BLUE, 0); + analogWrite(GPIO_WARMWHITE, 0); + analogWrite(GPIO_COOLWHITE, 0); + }); ArduinoOTA.begin(); - // Show our ID! - Serial.println(); - Serial.print("My target ID: "); - Serial.println(myTargetID); + if ( DEBUG ) { + Serial.println(""); + Serial.println("WiFi connected"); + Serial.println("IP address: "); + Serial.println(WiFi.localIP()); + } - // 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); + // Start listening for packets on port UDP_PORT + listener.begin(UDP_PORT); } bool processMessage() { struct colorTriplet colorBlank; byte command; - uint32 messageTargetID = 0; + uint32_t 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]; @@ -226,9 +257,7 @@ bool processMessage() { 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)); - } + if ( DEBUG ) Serial.println("Filter Fail: " + String(filter1) + ", " + String(filter2)); return false; } @@ -245,9 +274,7 @@ bool processMessage() { // 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)); - } + if ( DEBUG ) Serial.println("Got packet for different ID! Mine=" + String(myTargetID) + " Target=" + String(messageTargetID)); return false; } @@ -265,9 +292,11 @@ bool processMessage() { 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); + if ( MAX_ANALOG != 255 ) { + autoColors[0].red = map(autoColors[0].red, 0, 255, 0, MAX_ANALOG); + autoColors[0].green = map(autoColors[0].green, 0, 255, 0, MAX_ANALOG); + autoColors[0].blue = map(autoColors[0].blue, 0, 255, 0, MAX_ANALOG); + } redStatic = autoColors[0].red; greenStatic = autoColors[0].green; blueStatic = autoColors[0].blue; @@ -296,9 +325,11 @@ bool processMessage() { 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 ( MAX_ANALOG != 255 ) { + autoColors[i].red = map(autoColors[i].red, 0, 255, 0, MAX_ANALOG); + autoColors[i].green = map(autoColors[i].green, 0, 255, 0, MAX_ANALOG); + autoColors[i].blue = map(autoColors[i].blue, 0, 255, 0, MAX_ANALOG); + } if ( DEBUG ) { debugOutput += String(i) + "=["; debugOutput += String(autoColors[i].red) + ", "; @@ -358,8 +389,8 @@ void loop() { 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 nextStatusMillis = millis() + 600000; // 10 minutes static unsigned long nextRampMillis; static unsigned long rampStartMillis; static unsigned long restingEndMillis; @@ -378,6 +409,10 @@ void loop() { */ reportMillis = nowMillis + 1000; } + + if ( nowMillis >= nextStatusMillis ) { + nextStatusMillis = sendDeviceStatus() + 600000; // 10 minutes + } // Check to see if we have a new packet waiting and parse it out if we do int packetSize = listener.parsePacket(); @@ -399,16 +434,16 @@ void loop() { //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)]; + redTarget = gamma8[random(0, MAX_ANALOG)]; + greenTarget = gamma8[random(0, MAX_ANALOG)]; + blueTarget = gamma8[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]; + redTarget = gamma8[autoColors[autoColorTargetIndex].red]; + greenTarget = gamma8[autoColors[autoColorTargetIndex].green]; + blueTarget = gamma8[autoColors[autoColorTargetIndex].blue]; } redInitial = redLevel; greenInitial = greenLevel; @@ -484,31 +519,28 @@ void loop() { 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 - } ArduinoOTA.handle(); yield(); } +const uint8_t gamma8[] = { + 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, 2, 2, 2, 2, 2, 2, 2, + 2, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 5, 5, 5, + 5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10, + 10, 10, 11, 11, 11, 12, 12, 13, 13, 13, 14, 14, 15, 15, 16, 16, + 17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22, 23, 24, 24, 25, + 25, 26, 27, 27, 28, 29, 29, 30, 31, 32, 32, 33, 34, 35, 35, 36, + 37, 38, 39, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 50, + 51, 52, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 66, 67, 68, + 69, 70, 72, 73, 74, 75, 77, 78, 79, 81, 82, 83, 85, 86, 87, 89, + 90, 92, 93, 95, 96, 98, 99,101,102,104,105,107,109,110,112,114, + 115,117,119,120,122,124,126,127,129,131,133,135,137,138,140,142, + 144,146,148,150,152,154,156,158,160,162,164,167,169,171,173,175, + 177,180,182,184,186,189,191,193,196,198,200,203,205,208,210,213, + 215,218,220,223,225,228,231,233,236,239,241,244,247,249,252,255 }; + //const uint16_t PROGMEM gamma10[] { const uint16_t gamma10[] { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, @@ -579,4 +611,4 @@ const uint16_t gamma10[] { 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 }; +1015,1017,1020,1023 }; \ No newline at end of file