This is how the web page looks after a quick redesign
(current state is #2 about 70% time remaining)
Here is the full code with the reworked web page
#include <WebServer.h>
#include <Adafruit_PCA9685.h>
SYSTEM_MODE(SEMI_AUTOMATIC)
const char htmlPAGE[] =
"<html><head><title>IAB Lightbox Control-Panel</title><style>"
"table, th, td {border:1px solid black;border-collapse:collapse}"
"th, td {width:15%%;text-align:center;padding:5px}"
"tr {background-color:#F8F8F0}"
"button {width:100%%;height:30px}"
"progress {direction:rtl}"
"</style></head><body><h1>IAB Lightbox 5 Control-Panel</h1>"
"<form action='/box' method='POST'>"
"<table><tr>"
"<th style='background-color:#FFAAAA'>red</th>"
"<th style='background-color:#AAFFAA'>green</th>"
"<th style='background-color:#AAAAFF'>blue</th>"
"<th style='background-color:#FFFFFF'>white</th>"
"<th style='color:#FF0000'>infrared</th>"
"<th>duration</th></tr>"
"<tr><th colspan='5'>µm Photons / m² * s</th><th>minutes</th></tr>"
"<tr style='background-color:%s'>"
"<td><input type='number' name='red_1' min='0' max='2000' value='%lu'></td>"
"<td><input type='number' name='green_1' min='0' max='2000'value='%lu'></td>"
"<td><input type='number' name='blue_1' min='0' max='2000' value='%lu'></td>"
"<td><input type='number' name='white_1' min='0' max='2000' value='%lu'></td>"
"<td><input type='number' name='infrared_1' min='0' max='2000' value='%lu'></td>"
"<td><input type='number' name='timer_1' min='0' max='600000' value='%lu'></td>"
"<tr style='background-color:%s'>"
"<td><input type='number' name='red_2' min='0' max='2000' value='%lu'></td>"
"<td><input type='number' name='green_2' min='0' max='2000' value='%lu'></td>"
"<td><input type='number' name='blue_2' min='0' max='2000' value='%lu'></td>"
"<td><input type='number' name='white_2' min='0' max='2000' value='%lu'></td>"
"<td><input type='number' name='infrared_2' min='0' max='2000' value='%lu'></td>"
"<td><input type='number' name='timer_2' min='0' max='600000' value='%lu'></td>"
"<tr style='background-color:%s'>"
"<td><input type='number' name='red_3' min='0' max='2000' value='%lu'></td>"
"<td><input type='number' name='green_3' min='0' max='2000' value='%lu'></td>"
"<td><input type='number' name='blue_3' min='0' max='2000' value='%lu'></td>"
"<td><input type='number' name='white_3' min='0' max='2000' value='%lu'></td>"
"<td><input type='number' name='infrared_3' min='0' max='2000' value='%lu'></td>"
"<td><input type='number' name='timer_3' min='0' max='600000' value='%lu'></td>"
"<tr style='background-color:%s'>"
"<td><input type='number' name='red_4' min='0' max='2000' value='%lu'></td>"
"<td><input type='number' name='green_4' min='0' max='2000' value='%lu'></td>"
"<td><input type='number' name='blue_4' min='0' max='2000' value='%lu'></td>"
"<td><input type='number' name='white_4' min='0' max='2000' value='%lu'></td>"
"<td><input type='number' name='infrared_4' min='0' max='2000' value='%lu'></td>"
"<td><input type='number' name='timer_4' min='0' max='600000' value='%lu'>"
"</tr><tr>"
"<td><button name='enable' value='1' onclick='window.location.reload();'>Start</button></td>"
"<td style='text-align:left'><input type='checkbox' name='repeat' value='1' %s>repeat</td>"
"<td><button name='enable' value='0' onclick='window.location.reload();'>Reset</button></td>"
"<td colspan='3' style='text-align:right'>Christian Streng & Christian Karle ©2019</td>"
"</tr><tr><td colspan='6'><progress value='%.2f' max='1' style='width:100%%'/></td></tr>"
"</body></html>"
;
IPAddress ipAddress( 192, 168, 5, 204 );
IPAddress ipGateway( 192, 168, 5, 100 );
IPAddress ipNetmask( 255, 255, 255, 0 );
IPAddress ipDNS ( 8, 8, 8, 8 );
const char PREFIX[] = "/box";
const int BUFFER_SIZE = sizeof(htmlPAGE)+512;
void wifiSetup() {
uint8_t ip[4];
EEPROM.get(0, ip);
WiFi.on();
if (!(ipAddress == ip)) {
WiFi.setStaticIP(ipAddress, ipNetmask, ipGateway, ipDNS);
WiFi.useStaticIP();
EEPROM.put(0, ipAddress.raw());
}
WiFi.connect();
}
STARTUP(wifiSetup());
const int STATE_COUNT = 5;
enum LEDS {
cGREEN = 0,
cIR,
cWHITE,
cRED,
cBLUE,
LED_COUNT
};
const char itemNames[][12] =
{ "green"
, "infrared"
, "white"
, "red"
, "blue"
, "timer"
};
struct LED_PARA {
float offset;
float factor;
} ledParameters[LED_COUNT] =
{ { -0.2930, 0.0099 } // originally { +0.239, 1.130 }
, { -0.6229, 0.0264 } // { -0.617, 2.609 }
, { -4.7511, 0.1155 } // { +6.701, 13.771 }
, { -1.1121, 0.0086 } // { +0.979, 1.087 }
, { -0.1621, 0.0197 } // { +1.308, 2.713 }
};
struct LED_STATE {
uint32_t base[LED_COUNT];
uint32_t duration;
} ledStates[STATE_COUNT];
int curState = 0; // number of current state (0..off)
uint32_t msState = 0; // time when the current state was initiated
bool repeat = false;
WebServer webServer(PREFIX, 80);
Adafruit_PCA9685 ledDriver(0x40, true);
void boxCmd(WebServer &server, WebServer::ConnectionType type, char *url_tail, bool tail_complete);
char ip[16];
void setup() {
Particle.variable("localIP", ip);
RGB.control(true);
ledDriver.begin();
ledDriver.setPWMFreq(300);
webServer.setDefaultCommand(&boxCmd);
webServer.begin();
strcpy(ip, WiFi.localIP().toString());
}
void loop() {
static int prevState = -1;
webServer.processConnection();
if (!curState) { // in idle state
if (curState != prevState) {
for(int i=0; i < LED_COUNT; i++) // switch off LEDs
ledDriver.setVal(i, 0);
RGB.color(0, 0, 0);
prevState = 0;
}
return;
}
if (curState != prevState) { // when transition to new state is required
for(int i=0; i < LED_COUNT; i++) // set LEDs according to state parameters
ledDriver.setVal(i, curState ? (ledStates[curState].base[i] + ledParameters[i].offset) / ledParameters[i].factor : 0 );
RGB.color(ledStates[curState].base[cRED], ledStates[curState].base[cGREEN], ledStates[curState].base[cBLUE]);
msState = millis(); // record when this state was started
prevState = curState;
}
if (millis() - msState >= ledStates[curState].duration * 60000) { // when state duration has elapsed
curState++; // push forward the current state
if (curState == STATE_COUNT) // when last state done
curState = repeat; // wrap round and stop or restart (depending on repeat flag)
}
}
void boxCmd(WebServer &server, WebServer::ConnectionType type, char *url_tail, bool tail_complete) {
if (type == WebServer::POST) {
char name[16];
char value[16];
char item[12];
int state = 0;
uint32_t val = 0;
while(server.readPOSTparam (name, sizeof(name)-1, value, sizeof(value)-1 )) {
val = strtoul(value, NULL, 10);
if (2 == sscanf(name, "%[^_]_%d", item, &state)) { // if name is of format XXXX_#
for(int i=0; i <= LED_COUNT; i++) { // travers itemNames list to find the respective item
if (!strcmp(item, itemNames[i])) { // when found
if (i < LED_COUNT) // and index indicates a LED
ledStates[state].base[i] = val; // store base value
else // must be a timer value
ledStates[state].duration = val; // store timer duration
break;
}
}
}
else if (!strcmp(name, "enable")) {
if (!(curState = atoi(value))) // if "enable" is false
memset(ledStates, 0, sizeof(ledStates)); // reset the letStates to 0
repeat = false; // reset repeat flag before it gets parsed next
}
else if (!strcmp(name, "repeat")) // checkbox will only be transmitted when checked
repeat = true;
}
server.httpSeeOther(PREFIX);
}
else if (type == WebServer::GET) {
char buffer[BUFFER_SIZE];
server.httpSuccess();
snprintf(buffer, sizeof(buffer)
, htmlPAGE
, 1==curState ? "#FFFFA0" : "", ledStates[1].base[cRED], ledStates[1].base[cGREEN], ledStates[1].base[cBLUE], ledStates[1].base[cWHITE], ledStates[1].base[cIR], ledStates[1].duration
, 2==curState ? "#FFFFA0" : "", ledStates[2].base[cRED], ledStates[2].base[cGREEN], ledStates[2].base[cBLUE], ledStates[2].base[cWHITE], ledStates[2].base[cIR], ledStates[2].duration
, 3==curState ? "#FFFFA0" : "", ledStates[3].base[cRED], ledStates[3].base[cGREEN], ledStates[3].base[cBLUE], ledStates[3].base[cWHITE], ledStates[3].base[cIR], ledStates[3].duration
, 4==curState ? "#FFFFA0" : "", ledStates[4].base[cRED], ledStates[4].base[cGREEN], ledStates[4].base[cBLUE], ledStates[4].base[cWHITE], ledStates[4].base[cIR], ledStates[4].duration
, repeat ? "checked" : "unchecked", curState ? (ledStates[curState].duration - (millis()-msState)/60000.0) : 0
);
server.write((uint8_t *)buffer, strlen(buffer));
}
}