, ARDUINO
Physio-light mapping
#include WiFi.h
#include WebServer.h
#include Adafruit_NeoPixel.h
#include stdio.h
#include string.h
#include ctype.h
#define LED_PIN 18
#define NUM_LEDS 58
#define MAX_ROWS 100
#define STEP_TIME_MS 3000UL
const char* AP_SSID = "PHYSIO_LIGHT_DEMO";
const char* AP_PASSWORD = "physiolight";
Adafruit_NeoPixel strip(NUM_LEDS, LED_PIN, NEO_GRB + NEO_KHZ800);
WebServer server(80);
const char csvData[] =
"r,g,b,brightness\n"
"255,130,0,6\n"
"255,130,0,6\n"
"255,130,0,6\n"
"255,131,0,6\n"
"255,137,15,7\n"
"255,140,24,7\n"
"255,143,31,7\n"
"255,164,82,8\n"
"255,168,91,9\n"
"255,174,102,11\n"
"255,178,111,12\n"
"255,183,122,13\n"
"255,185,127,15\n"
"255,187,130,16\n"
"255,189,134,16\n"
"255,190,135,17\n"
"255,192,139,18\n"
"255,193,141,21\n"
"255,194,143,196\n"
"255,195,145,210\n"
"255,196,147,213\n"
"255,197,150,216\n"
"255,198,151,218\n"
"255,200,155,220\n"
"255,202,158,221\n"
"255,203,160,223\n"
"255,204,162,224\n"
"255,207,168,225\n"
"255,209,172,225\n"
"255,211,175,226\n"
"255,213,180,228\n"
"255,217,187,228\n"
"255,223,197,230\n"
"255,228,205,231\n"
"255,229,208,233\n"
"255,232,212,234\n"
"255,234,217,234\n"
"255,236,220,235\n"
"255,237,221,237\n"
"255,240,226,237\n"
"255,249,241,237\n"
"255,250,244,239\n"
"255,251,245,240\n"
"255,251,245,240\n"
"255,251,245,240\n"
"255,250,244,239\n"
"255,249,241,237\n"
"255,240,226,237\n"
"255,237,221,237\n"
"255,236,220,235\n"
"255,234,217,234\n"
"255,232,212,234\n"
"255,229,208,233\n"
"255,228,205,231\n"
"255,223,197,230\n"
"255,217,187,228\n"
"255,213,180,228\n"
"255,211,175,226\n"
"255,209,172,225\n"
"255,207,168,225\n"
"255,204,162,224\n"
"255,203,160,223\n"
"255,202,158,221\n"
"255,200,155,220\n"
"255,198,151,218\n"
"255,197,150,216\n"
"255,196,147,213\n"
"255,195,145,210\n"
"255,194,143,196\n"
"255,193,141,21\n"
"255,192,139,18\n"
"255,190,135,17\n"
"255,189,134,16\n"
"255,187,130,16\n"
"255,185,127,15\n"
"255,183,122,13\n"
"255,178,111,12\n"
"255,174,102,11\n"
"255,168,91,9\n"
"255,164,82,8\n"
"255,143,31,7\n"
"255,140,24,7\n"
"255,137,15,7\n"
"255,131,0,6\n"
"255,130,0,6\n"
"255,130,0,6\n"
"255,130,0,6\n";
struct LedStep { uint8_t r; uint8_t g; uint8_t b; uint8_t brightness;};
LedStep steps[MAX_ROWS];
int stepCount = 0;
int currentStep = 0;
unsigned long lastStepChange = 0;
bool simulationRunning = false;
uint8_t manualR = 255;
uint8_t manualG = 160;
uint8_t manualB = 90;
uint8_t manualBrightness = 40;
String manualState = "manual";
String manualMode = "manual";
uint8_t clampToByte(int value) { if (value < 0) return 0; if (value > 255) return 255; return (uint8_t)value;}
void addCorsHeaders() { server.sendHeader("Access-Control-Allow-Origin", "*"); server.sendHeader("Access-Control-Allow-Headers", "Content-Type");
server.sendHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS");}
void sendJsonResponse(int code, const String& payload) { addCorsHeaders(); server.send(code, "application/json", payload);}
void handleOptions() { addCorsHeaders(); server.send(204);}
bool extractIntField(const String& body, const char* key, int& valueOut) {
int keyPos = body.indexOf(key);
if (keyPos < 0) return false;
int colonPos = body.indexOf(':', keyPos);
if (colonPos < 0) return false;
int pos = colonPos + 1;
while (pos < body.length() && isspace((unsigned char)body[pos])) pos++;
bool negative = false;
if (pos < body.length() && body[pos] == '-') {
negative = true;
pos++;
}
int start = pos;
while (pos < body.length() && isdigit((unsigned char)body[pos])) pos++;
if (start == pos) return false;
int parsed = body.substring(start, pos).toInt();
valueOut = negative ? -parsed : parsed;
return true;
}
String extractStringField(const String& body, const char* key) {
int keyPos = body.indexOf(key);
if (keyPos < 0) return "";
int colonPos = body.indexOf(':', keyPos);
if (colonPos < 0) return "";
int firstQuote = body.indexOf('"', colonPos + 1);
if (firstQuote < 0) return "";
int secondQuote = body.indexOf('"', firstQuote + 1);
if (secondQuote < 0) return "";
return body.substring(firstQuote + 1, secondQuote);
}
void applyColorToStrip(uint8_t r, uint8_t g, uint8_t b, uint8_t brightness) {
uint8_t rScaled = (uint16_t)r * brightness / 255;
uint8_t gScaled = (uint16_t)g * brightness / 255;
uint8_t bScaled = (uint16_t)b * brightness / 255;
for (int i = 0; i < NUM_LEDS; i++) {
strip.setPixelColor(i, strip.Color(rScaled, gScaled, bScaled));
}
strip.show();
}
void showSimulationStep(int index) {
if (index < 0 || index >= stepCount) return;
applyColorToStrip(
steps[index].r,
steps[index].g,
steps[index].b,
steps[index].brightness
);
Serial.print("SIM step ");
Serial.print(index + 1);
Serial.print("/");
Serial.print(stepCount);
Serial.print(" -> RGB(");
Serial.print(steps[index].r);
Serial.print(", ");
Serial.print(steps[index].g);
Serial.print(", ");
Serial.print(steps[index].b);
Serial.print(") BR ");
Serial.println(steps[index].brightness);
}
void showManualScene() {
applyColorToStrip(manualR, manualG, manualB, manualBrightness);
Serial.print("MANUAL -> RGB(");
Serial.print(manualR);
Serial.print(", ");
Serial.print(manualG);
Serial.print(", ");
Serial.print(manualB);
Serial.print(") BR ");
Serial.print(manualBrightness);
Serial.print(" | state: ");
Serial.print(manualState);
Serial.print(" | mode: ");
Serial.println(manualMode);
}
void parseCSV() {
stepCount = 0;
char buffer[sizeof(csvData)];
strncpy(buffer, csvData, sizeof(buffer) - 1);
buffer[sizeof(buffer) - 1] = '\0';
char* line = strtok(buffer, "\n");
bool firstLine = true;
while (line != NULL && stepCount < MAX_ROWS) {
if (firstLine) {
firstLine = false;
} else {
int r, g, b, brightness;
int parsed = sscanf(line, "%d,%d,%d,%d", &r, &g, &b, &brightness);
if (parsed == 4) {
steps[stepCount].r = clampToByte(r);
steps[stepCount].g = clampToByte(g);
steps[stepCount].b = clampToByte(b);
steps[stepCount].brightness = clampToByte(brightness);
stepCount++;
}
}
line = strtok(NULL, "\n");
}
}
void handleRoot() {
String html =
"Physio Light ESP32"
"Physio Light ESP32 is online
"
"POST /light
"
"POST /simulation/start
"
"POST /simulation/stop
"
"GET /status
"
"";
addCorsHeaders();
server.send(200, "text/html", html);
}
void handleStatus() {
String mode = simulationRunning ? "simulation" : "manual";
String json = "{";
json += "\"ok\":true,";
json += "\"mode\":\"" + mode + "\",";
json += "\"simulation_running\":" + String(simulationRunning ? "true" : "false") + ",";
json += "\"current_step\":" + String(currentStep) + ",";
json += "\"step_count\":" + String(stepCount) + ",";
json += "\"step_time_ms\":" + String(STEP_TIME_MS) + ",";
json += "\"ip\":\"" + WiFi.softAPIP().toString() + "\"";
json += "}";
sendJsonResponse(200, json);
}
void handleLight() {
if (!server.hasArg("plain")) { sendJsonResponse(400, "{\"ok\":false,\"error\":\"Missing JSON body\"}"); return; }
String body = server.arg("plain");
int r = manualR;
int g = manualG;
int b = manualB;
int brightness = manualBrightness;
if (!extractIntField(body, "\"r\"", r) ||
!extractIntField(body, "\"g\"", g) ||
!extractIntField(body, "\"b\"", b) ||
!extractIntField(body, "\"brightness\"", brightness)) {
sendJsonResponse(400, "{\"ok\":false,\"error\":\"JSON must contain r, g, b, brightness\"}");
return;
}
manualR = clampToByte(r);
manualG = clampToByte(g);
manualB = clampToByte(b);
manualBrightness = clampToByte(brightness);
String parsedState = extractStringField(body, "\"state\"");
String parsedMode = extractStringField(body, "\"mode\"");
if (parsedState.length() > 0) manualState = parsedState;
if (parsedMode.length() > 0) manualMode = parsedMode;
simulationRunning = false;
showManualScene();
String json = "{";
json += "\"ok\":true,";
json += "\"mode\":\"manual\",";
json += "\"r\":" + String(manualR) + ",";
json += "\"g\":" + String(manualG) + ",";
json += "\"b\":" + String(manualB) + ",";
json += "\"brightness\":" + String(manualBrightness);
json += "}";
sendJsonResponse(200, json);
}
void handleSimulationStart() {
if (stepCount == 0) { sendJsonResponse(500, "{\"ok\":false,\"error\":\"No simulation steps loaded\"}"); return;}
currentStep = 0;
simulationRunning = true;
lastStepChange = millis();
showSimulationStep(currentStep);
String json = "{";
json += "\"ok\":true,";
json += "\"mode\":\"simulation\",";
json += "\"step_count\":" + String(stepCount) + ",";
json += "\"step_time_ms\":" + String(STEP_TIME_MS);
json += "}";
sendJsonResponse(200, json);
}
void handleSimulationStop() {
simulationRunning = false;
showManualScene();
sendJsonResponse(200, "{\"ok\":true,\"mode\":\"manual\",\"simulation_running\":false}");
}
void handleNotFound() {
if (server.method() == HTTP_OPTIONS) {
handleOptions();
return;
}
sendJsonResponse(404, "{\"ok\":false,\"error\":\"Not found\"}");
}
void registerRoutes() {
server.on("/", HTTP_GET, handleRoot);
server.on("/status", HTTP_GET, handleStatus);
server.on("/light", HTTP_POST, handleLight);
server.on("/simulation/start", HTTP_POST, handleSimulationStart);
server.on("/simulation/stop", HTTP_POST, handleSimulationStop);
server.on("/light", HTTP_OPTIONS, handleOptions);
server.on("/simulation/start", HTTP_OPTIONS, handleOptions);
server.on("/simulation/stop", HTTP_OPTIONS, handleOptions);
server.on("/status", HTTP_OPTIONS, handleOptions);
server.onNotFound(handleNotFound);
}
void setupAccessPoint() {
WiFi.mode(WIFI_AP);
bool apStarted = WiFi.softAP(AP_SSID, AP_PASSWORD);
Serial.println();
Serial.println("Wi-Fi Access Point");
Serial.print("SSID: ");
Serial.println(AP_SSID);
Serial.print("Password: ");
Serial.println(AP_PASSWORD);
Serial.print("Started: ");
Serial.println(apStarted ? "yes" : "no");
Serial.print("IP: ");
Serial.println(WiFi.softAPIP());
}
void setup() {
Serial.begin(115200);
delay(1200);
Serial.println();
Serial.println("PHYSIO LIGHT DEMO STARTING");
strip.begin();
strip.clear();
strip.show();
parseCSV();
Serial.print("Simulation steps loaded: ");
Serial.println(stepCount);
Serial.print("Total loop duration (s): ");
Serial.println((stepCount * STEP_TIME_MS) / 1000.0f);
setupAccessPoint();
registerRoutes();
server.begin();
Serial.println("HTTP server started");
showManualScene();
}
void loop() {
server.handleClient();
if (simulationRunning && stepCount > 0) {
unsigned long now = millis();
if (now - lastStepChange >= STEP_TIME_MS) {
currentStep++;
if (currentStep >= stepCount) currentStep = 0;
showSimulationStep(currentStep);
lastStepChange = now;
}
}
}