Make project compatible with ESP32 & ESP8266. Add Cool and Warm white channels. Add OTA. Switch to 8bit resolution. BROKEN: message processing still needs fixed.

This commit is contained in:
Junior 2026-01-08 15:42:17 -05:00
parent 5885345140
commit dc1485e6e8
4 changed files with 212 additions and 135 deletions

6
include/esp32.h Normal file
View File

@ -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"

6
include/esp8266.h Normal file
View File

@ -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"

View File

@ -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}

View File

@ -1,38 +1,30 @@
#include <Arduino.h>
#include <WiFiManager.h>
#include <ArduinoOTA.h>
#if defined(ARDUINO_ARCH_ESP32)
#include <esp32.h>
#include <WiFi.h>
#include <HTTPClient.h>
#elif defined(ARDUINO_ARCH_ESP8266)
#include <esp8266.h>
#include <ESP8266WiFi.h>
#include <ESP8266HTTPClient.h>
#include <WiFiClient.h>
#endif
#include <WiFiUdp.h>
#include <stdlib.h>
#include <ArduinoOTA.h>
#include <DNSServer.h>
#include <ESP8266WebServer.h>
#include <WiFiManager.h>
#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() {
unsigned long sendDeviceStatus() {
if ( DEBUG ) Serial.println("Sending device status");
WiFiClient client;
HTTPClient http;
http.begin(client, STATUSURL.c_str());
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=" + FIRMWARE + "&service=" + SERVICE;
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);
// Turn off blue LED and disable interrupt
timer0_detachInterrupt();
analogWrite(GPIO_BLUE, 0);
if ( DEBUG ) {
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;
@ -379,6 +410,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();
if ( packetSize ) {
@ -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,