Advertisement
Guest User

Diyhue RGB-CCT Gradien

a guest
Nov 21st, 2023
57
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C 41.39 KB | Source Code | 0 0
  1. #include <WiFi.h>
  2. #include <WiFiUdp.h>
  3. #include <WebServer.h>
  4. #include <LittleFS.h>
  5. #include <HTTPUpdateServer.h>
  6. #include <WiFiManager.h>
  7. #include <ArduinoJson.h>
  8. #include <NeoPixelBus.h>
  9.  
  10. IPAddress address ( 192,  168,   0,  95); // choose an unique IP Adress
  11. IPAddress gateway ( 192,  168,   0,   1); // Router IP
  12. IPAddress submask(255, 255, 255,   0);
  13.  
  14. #define LIGHT_VERSION 4.2
  15. #define LIGHT_NAME_MAX_LENGTH 32 // Longer name will get stripped
  16. #define ENTERTAINMENT_TIMEOUT 1500 // millis
  17. #define POWER_MOSFET_PIN 4 // WS2812 consume ~1mA/led when off. By installing a MOSFET it will cut the power to the leds when lights ore off.
  18. #define ledGPIO 7
  19. #define DATA_PIN 10
  20.  
  21. #define CW_GPIO            4
  22. #define WW_GPIO            5
  23. #define LEDC_CHANNEL_0     0
  24. #define LEDC_CHANNEL_1     1
  25.  
  26. // use 12 bit precission for LEDC timer
  27. #define LEDC_TIMER_12_BIT  12
  28.  
  29. // use 5000 Hz as a LEDC base frequency
  30. #define LEDC_BASE_FREQ     5000
  31.  
  32.  
  33. // Reference:
  34. // https://andi-siess.de/rgb-to-color-temperature/
  35.  
  36. // Warm White (3000K)
  37. // #define WHITE_CT_R 255
  38. // #define WHITE_CT_G 180
  39. // #define WHITE_CT_B 107
  40.  
  41. // Natural White (4300K)
  42. #define WHITE_CT_R 255
  43. #define WHITE_CT_G 215
  44. #define WHITE_CT_B 177
  45.  
  46. // Cool White (6500K)
  47. // #define WHITE_CT_R 255
  48. // #define WHITE_CT_G 249
  49. // #define WHITE_CT_B 253
  50.  
  51.  
  52.  
  53. struct state {
  54.   uint8_t colors[5];
  55.   float x, y, stepLevel[5], currentColors[5];
  56. };
  57.  
  58. uint16_t ct = 200, hue;
  59. bool lightState;
  60. uint8_t bri = 100, sat = 254, colorMode = 2;
  61.  
  62. state lights[10];
  63. bool inTransition, entertainmentRun, mosftetState, useDhcp = true;
  64. byte mac[6], packetBuffer[46];
  65. unsigned long lastEPMillis, lastWiFiCheck;
  66.  
  67. //settings
  68. char lightName[LIGHT_NAME_MAX_LENGTH] = "DiyHue Light";
  69. uint8_t effect, scene, startup, onPin = 9, offPin = 8 ;
  70. bool hwSwitch = false;
  71. uint8_t rgb_multiplier[] = {100, 100, 100}; // light multiplier in percentage /R, G, B/
  72.  
  73. uint8_t lightsCount = 5;
  74.  
  75. uint16_t pixelCount = 60;
  76. uint16_t lightLedsCount = pixelCount / (lightsCount - 1);
  77.  
  78. WebServer server(80);
  79. WiFiUDP Udp;
  80. HTTPUpdateServer httpUpdateServer;
  81. WiFiManager wm;
  82.  
  83. RgbwColor red = RgbwColor(255, 0, 0, 0);
  84. RgbwColor green = RgbwColor(0, 255, 0, 0);
  85. RgbwColor white = RgbwColor(255);
  86. RgbwColor black = RgbwColor(0);
  87.  
  88. NeoPixelBus<NeoGrbwFeature, NeoEsp32Rmt0Sk6812Method>* strip = NULL;
  89.  
  90. void blinkLed(uint8_t count, uint16_t interval = 200) {
  91.   for (uint8_t i = 0; i < count; i++) {
  92.     digitalWrite(ledGPIO, LOW);
  93.     delay(interval);
  94.     digitalWrite(ledGPIO, HIGH);
  95.     delay(interval);
  96.   }
  97. }
  98.  
  99. const uint8_t kWhiteRedChannel = WHITE_CT_R;
  100. const uint8_t kWhiteGreenChannel = WHITE_CT_G;
  101. const uint8_t kWhiteBlueChannel = WHITE_CT_B;
  102.  
  103. // The transformation has to be normalized to 255
  104. static_assert(kWhiteRedChannel >= 255 ||
  105.               kWhiteGreenChannel >= 255 ||
  106.               kWhiteBlueChannel >= 255);
  107.  
  108.  
  109. void ledcAnalogWrite(uint8_t channel, uint32_t value, uint32_t valueMax = 255) {
  110.   // calculate duty, 4095 from 2 ^ 12 - 1
  111.   uint32_t duty = (4095 / valueMax) * min(value, valueMax);
  112.  
  113.   // write duty to LEDC
  114.   ledcWrite(channel, duty);
  115. }
  116.  
  117. void factoryReset() {
  118.   digitalWrite(ledGPIO, HIGH);
  119.   LittleFS.format();
  120.   WiFi.disconnect(false, true);
  121.   blinkLed(8, 100);
  122.   ESP.restart();
  123. }
  124.  
  125. void convertHue(uint8_t light) // convert hue / sat values from HUE API to RGB
  126. {
  127.   lights[light].colors[3] = 0;
  128.   lights[light].colors[4] = 0;
  129.   double      hh, p, q, t, ff, s, v;
  130.   long        i;
  131.  
  132.   s = sat / 255.0;
  133.   v = bri / 255.0;
  134.  
  135.   if (s <= 0.0) {      // < is bogus, just shuts up warnings
  136.     lights[light].colors[0] = v;
  137.     lights[light].colors[1] = v;
  138.     lights[light].colors[2] = v;
  139.     return;
  140.   }
  141.   hh = hue;
  142.   if (hh >= 65535.0) hh = 0.0;
  143.   hh /= 11850, 0;
  144.   i = (long)hh;
  145.   ff = hh - i;
  146.   p = v * (1.0 - s);
  147.   q = v * (1.0 - (s * ff));
  148.   t = v * (1.0 - (s * (1.0 - ff)));
  149.  
  150.   switch (i) {
  151.     case 0:
  152.       lights[light].colors[0] = v * 255.0;
  153.       lights[light].colors[1] = t * 255.0;
  154.       lights[light].colors[2] = p * 255.0;
  155.       break;
  156.     case 1:
  157.       lights[light].colors[0] = q * 255.0;
  158.       lights[light].colors[1] = v * 255.0;
  159.       lights[light].colors[2] = p * 255.0;
  160.       break;
  161.     case 2:
  162.       lights[light].colors[0] = p * 255.0;
  163.       lights[light].colors[1] = v * 255.0;
  164.       lights[light].colors[2] = t * 255.0;
  165.       break;
  166.  
  167.     case 3:
  168.       lights[light].colors[0] = p * 255.0;
  169.       lights[light].colors[1] = q * 255.0;
  170.       lights[light].colors[2] = v * 255.0;
  171.       break;
  172.     case 4:
  173.       lights[light].colors[0] = t * 255.0;
  174.       lights[light].colors[1] = p * 255.0;
  175.       lights[light].colors[2] = v * 255.0;
  176.       break;
  177.     case 5:
  178.     default:
  179.       lights[light].colors[0] = v * 255.0;
  180.       lights[light].colors[1] = p * 255.0;
  181.       lights[light].colors[2] = q * 255.0;
  182.       break;
  183.   }
  184.   convertRgbToRgbw(light);
  185. }
  186.  
  187. void convertXy(uint8_t light) // convert CIE xy values from HUE API to RGB
  188. {
  189.   lights[light].colors[3] = 0;
  190.   lights[light].colors[4] = 0;
  191.   uint8_t optimal_bri = bri;
  192.   if (optimal_bri < 5) {
  193.     optimal_bri = 5;
  194.   }
  195.   float Y = lights[light].y;
  196.   float X = lights[light].x;
  197.   float Z = 1.0f - lights[light].x - lights[light].y;
  198.  
  199.   // sRGB D65 conversion
  200.   float r =  X * 3.2406f - Y * 1.5372f - Z * 0.4986f;
  201.   float g = -X * 0.9689f + Y * 1.8758f + Z * 0.0415f;
  202.   float b =  X * 0.0557f - Y * 0.2040f + Z * 1.0570f;
  203.  
  204.  
  205.   // Apply gamma correction
  206.   r = r <= 0.0031308f ? 12.92f * r : (1.0f + 0.055f) * pow(r, (1.0f / 2.4f)) - 0.055f;
  207.   g = g <= 0.0031308f ? 12.92f * g : (1.0f + 0.055f) * pow(g, (1.0f / 2.4f)) - 0.055f;
  208.   b = b <= 0.0031308f ? 12.92f * b : (1.0f + 0.055f) * pow(b, (1.0f / 2.4f)) - 0.055f;
  209.  
  210.   // Apply multiplier for white correction
  211.   r = r * rgb_multiplier[0] / 100;
  212.   g = g * rgb_multiplier[1] / 100;
  213.   b = b * rgb_multiplier[2] / 100;
  214.  
  215.   if (r > b && r > g) {
  216.     // red is biggest
  217.     if (r > 1.0f) {
  218.       g = g / r;
  219.       b = b / r;
  220.       r = 1.0f;
  221.     }
  222.   }
  223.   else if (g > b && g > r) {
  224.     // green is biggest
  225.     if (g > 1.0f) {
  226.       r = r / g;
  227.       b = b / g;
  228.       g = 1.0f;
  229.     }
  230.   }
  231.   else if (b > r && b > g) {
  232.     // blue is biggest
  233.     if (b > 1.0f) {
  234.       r = r / b;
  235.       g = g / b;
  236.       b = 1.0f;
  237.     }
  238.   }
  239.  
  240.   r = r < 0 ? 0 : r;
  241.   g = g < 0 ? 0 : g;
  242.   b = b < 0 ? 0 : b;
  243.  
  244.   lights[light].colors[0] = (int) (r * optimal_bri); lights[light].colors[1] = (int) (g * optimal_bri); lights[light].colors[2] = (int) (b * optimal_bri);
  245.   convertRgbToRgbw(light);
  246. }
  247.  
  248. void convertCt(uint8_t light) // convert ct (color temperature) value from HUE API to RGB
  249. {
  250.   int optimal_bri = int( 10 + bri / 1.04);
  251.  
  252.   lights[light].colors[0] = 0;
  253.   lights[light].colors[1] = 0;
  254.   lights[light].colors[2] = 0;
  255.  
  256.   uint8_t percent_warm = ((ct - 150) * 100) / 350;
  257.  
  258.   lights[light].colors[3] =  (optimal_bri * (100 - percent_warm)) / 100;
  259.   lights[light].colors[4] = (optimal_bri * percent_warm) / 100;
  260.  
  261.   // not optimal fix to have transition with the pwm applied by lightEngine function
  262.   ledcWrite(LEDC_CHANNEL_0, (int)(lights[light].colors[3] * 16));
  263.   ledcWrite(LEDC_CHANNEL_1, (int)(lights[light].colors[4] * 16));
  264.  
  265. }
  266.  
  267. void handleNotFound() { // default webserver response for unknow requests
  268.   String message = "File Not Found\n\n";
  269.   message += "URI: ";
  270.   message += server.uri();
  271.   message += "\nMethod: ";
  272.   message += (server.method() == HTTP_GET) ? "GET" : "POST";
  273.   message += "\nArguments: ";
  274.   message += server.args();
  275.   message += "\n";
  276.   for (uint8_t i = 0; i < server.args(); i++) {
  277.     message += " " + server.argName(i) + ": " + server.arg(i) + "\n";
  278.   }
  279.   server.send(404, "text/plain", message);
  280. }
  281.  
  282. void convertRgbToRgbw(uint8_t light) {
  283.   //Get the maximum between R, G, and B
  284.   uint8_t r = lights[light].colors[0];
  285.   uint8_t g = lights[light].colors[1];
  286.   uint8_t b = lights[light].colors[2];
  287.  
  288.   // These values are what the 'white' value would need to
  289.   // be to get the corresponding color value.
  290.   float whiteValueForRed = r * 255.0 / kWhiteRedChannel;
  291.   float whiteValueForGreen = g * 255.0 / kWhiteGreenChannel;
  292.   float whiteValueForBlue = b * 255.0 / kWhiteBlueChannel;
  293.  
  294.   // Set the white value to the highest it can be for the given color
  295.   // (without over saturating any channel - thus the minimum of them).
  296.   float minWhiteValue = min(whiteValueForRed,
  297.                             min(whiteValueForGreen,
  298.                                 whiteValueForBlue));
  299.   uint8_t Wo = (minWhiteValue <= 255 ? (uint8_t) minWhiteValue : 255);
  300.  
  301.   // The rest of the channels will just be the original value minus the
  302.   // contribution by the white channel.
  303.   uint8_t Ro = (uint8_t)(r - minWhiteValue * kWhiteRedChannel / 255);
  304.   uint8_t Go = (uint8_t)(g - minWhiteValue * kWhiteGreenChannel / 255);
  305.   uint8_t Bo = (uint8_t)(b - minWhiteValue * kWhiteBlueChannel / 255);
  306.  
  307.   lights[light].colors[0] = Ro;
  308.   lights[light].colors[1] = Go;
  309.   lights[light].colors[2] = Bo;
  310.   lights[light].colors[3] = Wo / 2 ;
  311.   lights[light].colors[4] = Wo / 2;
  312. }
  313.  
  314. void infoLight(RgbwColor color) { // boot animation for leds count and wifi test
  315.   // Flash the strip in the selected color. White = booted, green = WLAN connected, red = WLAN could not connect
  316.   for (uint16_t i = 0; i < pixelCount; i++)
  317.   {
  318.     strip->SetPixelColor(i, color);
  319.     strip->Show();
  320.     delay(10);
  321.     strip->SetPixelColor(i, black);
  322.     strip->Show();
  323.   }
  324. }
  325.  
  326.  
  327. void apply_scene(uint8_t new_scene) { // these are internal scenes store in light firmware that can be applied on boot and manually from light web interface
  328.   for (uint8_t light = 0; light < lightsCount; light++) {
  329.     if ( new_scene == 1) {
  330.       bri = 254; ct = 346; colorMode = 2; convertCt(light);
  331.     } else if ( new_scene == 2) {
  332.       bri = 254; ct = 233; colorMode = 2; convertCt(light);
  333.     }  else if ( new_scene == 3) {
  334.       bri = 254; ct = 156; colorMode = 2; convertCt(light);
  335.     }  else if ( new_scene == 4) {
  336.       bri = 77; ct = 367; colorMode = 2; convertCt(light);
  337.     }  else if ( new_scene == 5) {
  338.       bri = 254; ct = 447; colorMode = 2; convertCt(light);
  339.     }  else if ( new_scene == 6) {
  340.       bri = 1; lights[light].x = 0.561; lights[light].y = 0.4042; colorMode = 1; convertXy(light);
  341.     }  else if ( new_scene == 7) {
  342.       bri = 203; lights[light].x = 0.380328; lights[light].y = 0.39986; colorMode = 1; convertXy(light);
  343.     }  else if ( new_scene == 8) {
  344.       bri = 112; lights[light].x = 0.359168; lights[light].y = 0.28807; colorMode = 1; convertXy(light);
  345.     }  else if ( new_scene == 9) {
  346.       bri = 142; lights[light].x = 0.267102; lights[light].y = 0.23755; colorMode = 1; convertXy(light);
  347.     }  else if ( new_scene == 10) {
  348.       bri = 216; lights[light].x = 0.393209; lights[light].y = 0.29961; colorMode = 1; convertXy(light);
  349.     } else {
  350.       bri = 144; ct = 447; colorMode = 2; convertCt(light);
  351.     }
  352.   }
  353. }
  354.  
  355. void processLightdata(uint8_t light, float transitiontime = 4) { // calculate the step level of every RGB channel for a smooth transition in requested transition time
  356.   transitiontime *= 17 - (pixelCount / 40); //every extra led add a small delay that need to be counted for transition time match
  357.   if (colorMode == 1 && lightState == true) {
  358.     convertXy(light);
  359.   } else if (colorMode == 2 && lightState == true) {
  360.     convertCt(light);
  361.   } else if (colorMode == 3 && lightState == true) {
  362.     convertHue(light);
  363.   }
  364.   for (uint8_t i = 0; i < 4; i++) {
  365.     if (lightState) {
  366.       lights[light].stepLevel[i] = ((float)lights[light].colors[i] - lights[light].currentColors[i]) / transitiontime;
  367.     } else {
  368.       lights[light].stepLevel[i] = lights[light].currentColors[i] / transitiontime;
  369.     }
  370.   }
  371.   inTransition = true;
  372. }
  373.  
  374.  
  375. void candleEffect() {
  376.   for (uint8_t light = 0; light < lightsCount; light++) {
  377.     lights[light].colors[0] = random(170, 254);
  378.     lights[light].colors[1] = random(37, 62);
  379.     lights[light].colors[2] = 0;
  380.     lights[light].colors[3] = 0;
  381.     lights[light].colors[4] = 0;
  382.     for (uint8_t i = 0; i < 4; i++) {
  383.       lights[light].stepLevel[i] = ((float)lights[light].colors[i] - lights[light].currentColors[i]) / random(5, 15);
  384.     }
  385.   }
  386.   inTransition = true;
  387. }
  388.  
  389. void firePlaceEffect() {
  390.   for (uint8_t light = 0; light < lightsCount; light++) {
  391.     lights[light].colors[0] = random(100, 254);
  392.     lights[light].colors[1] = random(10, 35);
  393.     lights[light].colors[2] = 0;
  394.     lights[light].colors[3] = 0;
  395.     lights[light].colors[4] = 0;
  396.     for (uint8_t i = 0; i < 4; i++) {
  397.       lights[light].stepLevel[i] = ((float)lights[light].colors[i] - lights[light].currentColors[i]) / random(5, 15);
  398.     }
  399.   }
  400.   inTransition = true;
  401. }
  402.  
  403. RgbwColor convFloat(float color[4]) { // return RgbColor from float
  404.   return RgbwColor((uint8_t)color[0], (uint8_t)color[1], (uint8_t)color[2], (uint8_t)color[3]);
  405. }
  406.  
  407. void togglePower() {
  408.  
  409.   if (!lightState && !inTransition && mosftetState) {
  410.     digitalWrite(POWER_MOSFET_PIN, LOW);
  411.     mosftetState = false;
  412.   } else if (lightState && !mosftetState) {
  413.     digitalWrite(POWER_MOSFET_PIN, HIGH);
  414.     mosftetState = true;
  415.     delay(50);
  416.   }
  417. }
  418.  
  419.  
  420. uint8_t pixelLight (uint8_t pixel) {
  421.   return int(pixel / lightLedsCount);
  422. }
  423.  
  424. void manageButtons() {
  425.   if (digitalRead(onPin) == LOW) { // on button pressed
  426.     uint8_t i = 0;
  427.     while (digitalRead(onPin) == LOW && i < 30) { // count how log is the button pressed
  428.       delay(20);
  429.       i++;
  430.     }
  431.     // factory reset if onPin also pressed
  432.     if (i == 30 && digitalRead(offPin) == LOW) {
  433.       delay(2000);
  434.       if (digitalRead(onPin) == LOW && digitalRead(offPin) == LOW) {
  435.         factoryReset();
  436.       }
  437.     }
  438.     for (uint8_t light = 0; light < lightsCount; light++) {
  439.       if (i < 30) { // there was a short press
  440.         togglePower();
  441.         lightState = true;
  442.       }
  443.       else { // there was a long press
  444.         if (bri < 210) {
  445.           bri += 30;
  446.         } else {
  447.           bri = 254;
  448.         }
  449.       }
  450.       processLightdata(light);
  451.     }
  452.   } else if (digitalRead(offPin) == LOW) { // off button pressed
  453.     uint8_t i = 0;
  454.     while (digitalRead(offPin) == LOW && i < 30) {
  455.       delay(20);
  456.       i++;
  457.     }
  458.     for (uint8_t light = 0; light < lightsCount; light++) {
  459.       if (i < 30) {
  460.         // there was a short press
  461.         lightState = false;
  462.       }
  463.       else {
  464.         // there was a long press
  465.         if (bri > 32) {
  466.           bri -= 30;
  467.         } else {
  468.           bri = 1;
  469.         }
  470.  
  471.       }
  472.       processLightdata(light);
  473.     }
  474.   }
  475. }
  476.  
  477. void lightEngine() {
  478.   if (inTransition) {
  479.     if (lightsCount == 1) {
  480.       strip->ClearTo(convFloat(lights[0].currentColors), 0, pixelCount - 1);
  481.     } else {
  482.       for (uint8_t pixel = 0; pixel < pixelCount; pixel++) {
  483.         uint8_t light = pixelLight(pixel);
  484.         strip->SetPixelColor(pixel, RgbwColor::LinearBlend(convFloat(lights[light].currentColors), convFloat(lights[light + 1].currentColors), (float)(pixel - (light * lightLedsCount)) / (float)lightLedsCount));
  485.       }
  486.     }
  487.     strip->Show();
  488.     bool allStatesSet = true;
  489.     for (uint8_t light = 0; light < lightsCount; light++) {
  490.       if (lightState) {
  491.         if (lights[light].colors[0] != lights[light].currentColors[0] || lights[light].colors[1] != lights[light].currentColors[1] || lights[light].colors[2] != lights[light].currentColors[2]) { // if not all RGB channels of the light are at desired level
  492.           for (uint8_t k = 0; k < 3; k++) { // loop with every RGB channel of the light
  493.             if (lights[light].colors[k] != lights[light].currentColors[k]) lights[light].currentColors[k] += lights[light].stepLevel[k]; // move RGB channel on step closer to desired level
  494.             if ((lights[light].stepLevel[k] > 0.0 && lights[light].currentColors[k] > lights[light].colors[k]) || (lights[light].stepLevel[k] < 0.0 && lights[light].currentColors[k] < lights[light].colors[k])) lights[light].currentColors[k] = lights[light].colors[k]; // if the current level go below desired level apply directly the desired level.
  495.           }
  496.           allStatesSet = false;
  497.         }
  498.       } else {
  499.         if (lights[light].currentColors[0] != 0 || lights[light].currentColors[1] != 0 || lights[light].currentColors[2] != 0) { // proceed forward only in case not all RGB channels are zero
  500.           for (uint8_t k = 0; k < 3; k++) { //loop with every RGB channel
  501.             if (lights[light].currentColors[k] != 0) lights[light].currentColors[k] -= lights[light].stepLevel[k]; // remove one step level
  502.             if (lights[light].currentColors[k] < 0) lights[light].currentColors[k] = 0; // save condition, if level go below zero set it to zero
  503.           }
  504.           allStatesSet = false;
  505.         }
  506.       }
  507.     }
  508.     if (allStatesSet) {
  509.       inTransition = false;
  510.     }
  511.     delay(5);
  512.   } else {
  513.     togglePower();
  514.     if (effect == 1) { // candle effect
  515.       candleEffect();
  516.     } else if (effect == 2) { // fireplace effect
  517.       firePlaceEffect();
  518.     }
  519.     if (hwSwitch == true) {
  520.       manageButtons();
  521.     }
  522.   }
  523. }
  524.  
  525.  
  526. void saveState() { // save the lights state on LittleFS partition in JSON format
  527.   DynamicJsonDocument json(1024);
  528.   json["on"] = lightState;
  529.   json["bri"] = bri;
  530.   if (colorMode == 2) {
  531.     json["ct"] = ct;
  532.   } else if (colorMode == 3) {
  533.     json["hue"] = hue;
  534.     json["sat"] = sat;
  535.   }
  536.   if (colorMode == 1) {
  537.     JsonArray points = json.createNestedArray("points");
  538.     for (uint8_t i = 0; i < lightsCount; i++) {
  539.       DynamicJsonDocument point(128);
  540.       point["x"] = lights[i].x;
  541.       point["y"] = lights[i].y;
  542.       points.add(point);
  543.     }
  544.   }
  545.   File stateFile = LittleFS.open("/state.json", "w");
  546.   serializeJson(json, stateFile);
  547. }
  548.  
  549. void restoreState() { // restore the lights state from LittleFS partition
  550.   File stateFile = LittleFS.open("/state.json", "r");
  551.   if (!stateFile) {
  552.     saveState();
  553.     return;
  554.   }
  555.  
  556.   DynamicJsonDocument json(1024);
  557.   DeserializationError error = deserializeJson(json, stateFile.readString());
  558.   if (error) {
  559.     //Serial.print(F("deserializeJson() state file failed: "));
  560.     //Serial.println(error.c_str());
  561.     return;
  562.   }
  563.   String output;
  564.   serializeJson(json, output);
  565.   //Serial.println(output);
  566.   lightState = json["on"];
  567.   bri = (uint8_t) json["bri"];
  568.   if (json.containsKey("ct")) {
  569.     ct = (uint16_t) json["ct"];
  570.     colorMode = 2;
  571.   } else if (json.containsKey("hue")) {
  572.     hue = (uint16_t) json["hue"];
  573.     sat = (uint8_t) json["sat"];
  574.     colorMode = 3;
  575.   } else if (json.containsKey("points")) {
  576.  
  577.     lightsCount = 0;
  578.     for (JsonObject point : json["points"].as<JsonArray>()) {
  579.       lights[lightsCount].x = point["x"];
  580.       lights[lightsCount].y = point["y"];
  581.       lightsCount += 1;
  582.     }
  583.     lightLedsCount = pixelCount / (lightsCount - 1);
  584.     colorMode = 1;
  585.   }
  586. }
  587.  
  588.  
  589. bool saveConfig() { // save config in LittleFS partition in JSON file
  590.   DynamicJsonDocument json(512);
  591.   json["name"] = lightName;
  592.   json["startup"] = startup;
  593.   json["scene"] = scene;
  594.   json["on"] = onPin;
  595.   json["off"] = offPin;
  596.   json["hw"] = hwSwitch;
  597.   json["dhcp"] = useDhcp;
  598.   json["pixelCount"] = pixelCount;
  599.   json["rpct"] = rgb_multiplier[0];
  600.   json["gpct"] = rgb_multiplier[1];
  601.   json["bpct"] = rgb_multiplier[2];
  602.   JsonArray addr = json.createNestedArray("addr");
  603.   addr.add(address[0]);
  604.   addr.add(address[1]);
  605.   addr.add(address[2]);
  606.   addr.add(address[3]);
  607.   JsonArray gw = json.createNestedArray("gw");
  608.   gw.add(gateway[0]);
  609.   gw.add(gateway[1]);
  610.   gw.add(gateway[2]);
  611.   gw.add(gateway[3]);
  612.   JsonArray mask = json.createNestedArray("mask");
  613.   mask.add(submask[0]);
  614.   mask.add(submask[1]);
  615.   mask.add(submask[2]);
  616.   mask.add(submask[3]);
  617.   File configFile = LittleFS.open("/config.json", "w");
  618.   if (!configFile) {
  619.     //Serial.println("Failed to open config file for writing");
  620.     return false;
  621.   }
  622.  
  623.   serializeJson(json, configFile);
  624.   return true;
  625. }
  626.  
  627. bool loadConfig() { // load the configuration from LittleFS partition
  628.   File configFile = LittleFS.open("/config.json", "r");
  629.   if (!configFile) {
  630.     //Serial.println("Create new file with default values");
  631.     return saveConfig();
  632.   }
  633.  
  634.   if (configFile.size() > 512) {
  635.     //Serial.println("Config file size is too large");
  636.     return false;
  637.   }
  638.  
  639.   DynamicJsonDocument json(512);
  640.   DeserializationError error = deserializeJson(json, configFile.readString());
  641.   if (error) {
  642.     //Serial.println("Failed to parse config file");
  643.     return false;
  644.   }
  645.  
  646.   strcpy(lightName, json["name"]);
  647.   startup = (uint8_t) json["startup"];
  648.   scene  = (uint8_t) json["scene"];
  649.   onPin = (uint8_t) json["on"];
  650.   offPin = (uint8_t) json["off"];
  651.   hwSwitch = json["hw"];
  652.   pixelCount = (uint16_t) json["pixelCount"];
  653.   if (json.containsKey("rpct")) {
  654.     rgb_multiplier[0] = (uint8_t) json["rpct"];
  655.     rgb_multiplier[1] = (uint8_t) json["gpct"];
  656.     rgb_multiplier[2] = (uint8_t) json["bpct"];
  657.   }
  658.   useDhcp = json["dhcp"];
  659.   address = {json["addr"][0], json["addr"][1], json["addr"][2], json["addr"][3]};
  660.   submask = {json["mask"][0], json["mask"][1], json["mask"][2], json["mask"][3]};
  661.   gateway = {json["gw"][0], json["gw"][1], json["gw"][2], json["gw"][3]};
  662.   return true;
  663. }
  664.  
  665. void ChangeNeoPixels(uint16_t newCount) // this set the number of leds of the strip based on web configuration
  666. {
  667.   if (strip != NULL) {
  668.     delete strip; // delete the previous dynamically created strip
  669.   }
  670.   strip = new NeoPixelBus<NeoGrbwFeature, NeoEsp32Rmt0Sk6812Method>(newCount, DATA_PIN); // and recreate with new count
  671.   strip->Begin();
  672. }
  673.  
  674. void setup() {
  675.   //Serial.begin(115200);
  676.   //Serial.println();
  677.   delay(500);
  678.   pinMode(ledGPIO, OUTPUT);
  679.   pinMode(POWER_MOSFET_PIN, OUTPUT);
  680.   blinkLed(2);
  681.   digitalWrite(POWER_MOSFET_PIN, HIGH); mosftetState = true; // reuired if HIGH logic power the strip, otherwise must be commented.
  682.  
  683.   ledcSetup(LEDC_CHANNEL_0, LEDC_BASE_FREQ, LEDC_TIMER_12_BIT);
  684.   ledcSetup(LEDC_CHANNEL_1, LEDC_BASE_FREQ, LEDC_TIMER_12_BIT);
  685.   ledcAttachPin(CW_GPIO, LEDC_CHANNEL_0);
  686.   ledcAttachPin(WW_GPIO, LEDC_CHANNEL_1);
  687.  
  688.   pinMode(ledGPIO, OUTPUT);
  689.   blinkLed(1);
  690.   ledcAnalogWrite(LEDC_CHANNEL_0, 50);
  691.  
  692.   //Serial.println("mounting FS...");
  693.  
  694.   if (!LittleFS.begin()) {
  695.     //Serial.println("Failed to mount file system");
  696.     LittleFS.format();
  697.   }
  698.  
  699.   if (!loadConfig()) {
  700.     //Serial.println("Failed to load config");
  701.   } else {
  702.     //Serial.println("Config loaded");
  703.   }
  704.  
  705.   ChangeNeoPixels(pixelCount);
  706.   lightLedsCount = pixelCount / (lightsCount - 1);
  707.  
  708.   if (startup == 1) {
  709.     lightState = true;
  710.   }
  711.   if (startup == 0) {
  712.     restoreState();
  713.   } else {
  714.     apply_scene(scene);
  715.   }
  716.   for (uint8_t i = 0; i < lightsCount; i++) {
  717.     processLightdata(i);
  718.   }
  719.   if (lightState) {
  720.     for (uint8_t i = 0; i < 200; i++) {
  721.       lightEngine();
  722.     }
  723.   }
  724.  
  725.  
  726.   if (hwSwitch == true) { // set buttons pins mode in case are used
  727.     pinMode(onPin, INPUT);
  728.     pinMode(offPin, INPUT);
  729.   }
  730.  
  731.   if (digitalRead(offPin) == LOW) {
  732.     factoryReset();
  733.   }
  734.  
  735.   WiFi.mode(WIFI_STA);
  736.   wm.setDebugOutput(false);
  737.   wm.setConfigPortalTimeout(120);
  738.   if (!useDhcp) {
  739.     wm.setSTAStaticIPConfig(address, gateway, submask);
  740.   }
  741.  
  742.   bool res;
  743.   res = wm.autoConnect(lightName);
  744.  
  745.   if (!res) {
  746.     ESP.restart();
  747.   }
  748.  
  749.   if (useDhcp) {
  750.     address = WiFi.localIP();
  751.     gateway = WiFi.gatewayIP();
  752.     submask = WiFi.subnetMask();
  753.   }
  754.  
  755.  
  756.   if (! lightState) { // test if light zero (must be at last one light) is not set to ON
  757.     infoLight(white); // play white anymation
  758.     while (WiFi.status() != WL_CONNECTED) { // connection to wifi still not ready
  759.       infoLight(red); // play red animation
  760.       delay(500);
  761.     }
  762.     // Show that we are connected
  763.     infoLight(green); // connected, play green animation
  764.  
  765.   }
  766.  
  767.   String hostname = lightName;
  768.   hostname.replace(" ", "-");
  769.  
  770.   WiFi.hostname("hue-" + hostname);
  771.   WiFi.macAddress(mac);
  772.  
  773.   httpUpdateServer.setup(&server); // start http server
  774.  
  775.   Udp.begin(2100); // start entertainment UDP server
  776.  
  777.   server.on("/state", HTTP_PUT, []() { // HTTP PUT request used to set a new light state
  778.     bool stateSave = false;
  779.     DynamicJsonDocument root(1024);
  780.     DeserializationError error = deserializeJson(root, server.arg("plain"));
  781.  
  782.     if (error) {
  783.       server.send(404, "text/plain", "FAIL. " + server.arg("plain"));
  784.     } else {
  785.       if (root.containsKey("effect")) {
  786.         if (root["effect"] == "no_effect") {
  787.           effect = 0;
  788.         } else if (root["effect"] == "candle") {
  789.           effect = 1;
  790.         } else if (root["effect"] == "fire") {
  791.           effect = 2;
  792.         }
  793.       }
  794.       if (root.containsKey("gradient")) {
  795.         if (root["gradient"].containsKey("points")) {
  796.           lightsCount = root["gradient"]["points"].size();
  797.           lightLedsCount = pixelCount / (lightsCount - 1);
  798.           for (uint8_t light = 0; light < lightsCount; light++) {
  799.             if (root["gradient"]["points"][light]["color"].containsKey("xy")) {
  800.               lights[light].x = root["gradient"]["points"][light]["color"]["xy"]["x"];
  801.               lights[light].y = root["gradient"]["points"][light]["color"]["xy"]["y"];
  802.             }
  803.           }
  804.           colorMode = 1;
  805.         }
  806.       }
  807.       if (root.containsKey("xy")) {
  808.         for (uint8_t light = 0; light < lightsCount; light++) {
  809.           lights[light].x = root["xy"][0];
  810.           lights[light].y = root["xy"][1];
  811.         }
  812.         colorMode = 1;
  813.       } else if (root.containsKey("ct")) {
  814.         ct = root["ct"];
  815.         colorMode = 2;
  816.       } else {
  817.         if (root.containsKey("hue")) {
  818.           hue = root["hue"];
  819.           colorMode = 3;
  820.         }
  821.         if (root.containsKey("sat")) {
  822.           sat = root["sat"];
  823.           colorMode = 3;
  824.         }
  825.       }
  826.  
  827.       if (root.containsKey("on")) {
  828.         if (root["on"]) {
  829.           lightState = true;
  830.           togglePower();// restore power
  831.         } else {
  832.           lightState = false;
  833.         }
  834.         if (startup == 0) {
  835.           stateSave = true;
  836.         }
  837.       }
  838.  
  839.       if (root.containsKey("bri")) {
  840.         bri = root["bri"];
  841.       }
  842.  
  843.       if (root.containsKey("bri_inc")) {
  844.         if (root["bri_inc"] > 0) {
  845.           if (bri + (int) root["bri_inc"] > 254) {
  846.             bri = 254;
  847.           } else {
  848.             bri += (int) root["bri_inc"];
  849.           }
  850.         } else {
  851.           if (bri - (int) root["bri_inc"] < 1) {
  852.             bri = 1;
  853.           } else {
  854.             bri += (int) root["bri_inc"];
  855.           }
  856.         }
  857.       }
  858.       uint16_t transitiontime = 4;
  859.       if (root.containsKey("transitiontime")) {
  860.         transitiontime = root["transitiontime"];
  861.       }
  862.       for (uint8_t light = 0; light < lightsCount; light++) {
  863.         if (root.containsKey("alert") && root["alert"] == "select") {
  864.           if (lightState) {
  865.             lights[light].currentColors[0] = 0; lights[light].currentColors[1] = 0; lights[light].currentColors[2] = 0;
  866.           } else {
  867.             lights[light].currentColors[1] = 126; lights[light].currentColors[2] = 126;
  868.           }
  869.         }
  870.  
  871.         processLightdata(light, transitiontime);
  872.       }
  873.       String output;
  874.       serializeJson(root, output);
  875.       server.send(200, "text/plain", output);
  876.       if (stateSave) {
  877.         saveState();
  878.       }
  879.     }
  880.   });
  881.  
  882.   server.on("/state", HTTP_GET, []() { // HTTP GET request used to fetch current light state
  883.     uint8_t light = 0;
  884.     DynamicJsonDocument root(1024);
  885.     root["on"] = lightState;
  886.     root["bri"] = bri;
  887.     JsonArray xy = root.createNestedArray("xy");
  888.     xy.add(lights[0].x);
  889.     xy.add(lights[0].y);
  890.     root["ct"] = ct;
  891.     root["hue"] = hue;
  892.     root["sat"] = sat;
  893.     if (colorMode == 1)
  894.       root["colormode"] = "xy";
  895.     else if (colorMode == 2)
  896.       root["colormode"] = "ct";
  897.     else if (colorMode == 3)
  898.       root["colormode"] = "hs";
  899.     String output;
  900.     serializeJson(root, output);
  901.     server.send(200, "text/plain", output);
  902.   });
  903.  
  904.   server.on("/detect", []() { // HTTP GET request used to discover the light type
  905.     char macString[32] = {0};
  906.     sprintf(macString, "%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
  907.     DynamicJsonDocument root(1024);
  908.     root["name"] = lightName;
  909.     root["protocol"] = "native_single";
  910.     root["modelid"] = "LCX004";
  911.     root["type"] = "sk6812_gradient_lightstrip";
  912.     root["mac"] = String(macString);
  913.     root["version"] = LIGHT_VERSION;
  914.     String output;
  915.     serializeJson(root, output);
  916.     server.send(200, "text/plain", output);
  917.   });
  918.  
  919.   server.on("/config", []() { // used by light web interface to get current configuration
  920.     DynamicJsonDocument root(1024);
  921.     root["name"] = lightName;
  922.     root["scene"] = scene;
  923.     root["startup"] = startup;
  924.     root["hw"] = hwSwitch;
  925.     root["on"] = onPin;
  926.     root["off"] = offPin;
  927.     root["hwswitch"] = (int)hwSwitch;
  928.     root["pixelcount"] = pixelCount;
  929.     root["rpct"] = rgb_multiplier[0];
  930.     root["gpct"] = rgb_multiplier[1];
  931.     root["bpct"] = rgb_multiplier[2];
  932.     root["disdhcp"] = (int)!useDhcp;
  933.     root["addr"] = (String)address[0] + "." + (String)address[1] + "." + (String)address[2] + "." + (String)address[3];
  934.     root["gw"] = (String)gateway[0] + "." + (String)gateway[1] + "." + (String)gateway[2] + "." + (String)gateway[3];
  935.     root["sm"] = (String)submask[0] + "." + (String)submask[1] + "." + (String)submask[2] + "." + (String)submask[3];
  936.     String output;
  937.     serializeJson(root, output);
  938.     server.send(200, "text/plain", output);
  939.   });
  940.  
  941.   server.on("/", []() { // light http web interface
  942.     if (server.arg("section").toInt() == 1) {
  943.       server.arg("name").toCharArray(lightName, LIGHT_NAME_MAX_LENGTH);
  944.       startup = server.arg("startup").toInt();
  945.       scene = server.arg("scene").toInt();
  946.       pixelCount = server.arg("pixelcount").toInt();
  947.       rgb_multiplier[0] = server.arg("rpct").toInt();
  948.       rgb_multiplier[1] = server.arg("gpct").toInt();
  949.       rgb_multiplier[2] = server.arg("bpct").toInt();
  950.       hwSwitch = server.hasArg("hwswitch") ? server.arg("hwswitch").toInt() : 0;
  951.       if (server.hasArg("hwswitch")) {
  952.         onPin = server.arg("on").toInt();
  953.         offPin = server.arg("off").toInt();
  954.       }
  955.       saveConfig();
  956.     } else if (server.arg("section").toInt() == 2) {
  957.       useDhcp = (!server.hasArg("disdhcp")) ? 1 : server.arg("disdhcp").toInt();
  958.       if (server.hasArg("disdhcp")) {
  959.         address.fromString(server.arg("addr"));
  960.         gateway.fromString(server.arg("gw"));
  961.         submask.fromString(server.arg("sm"));
  962.       }
  963.       saveConfig();
  964.     }
  965.     String htmlContent = "<!DOCTYPE html><html><head> <meta charset=\"UTF-8\"> <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"> <title>Hue Gradient Lightstrip - DiyHue</title> <link rel=\"icon\" type=\"image/png\" href=\"https://diyhue.org/wp-content/uploads/2019/11/cropped-Zeichenfl%C3%A4che-4-1-32x32.png\" sizes=\"32x32\"> <link href=\"https://fonts.googleapis.com/icon?family=Material+Icons\" rel=\"stylesheet\"> <link rel=\"stylesheet\" href=\"https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css\"> <link rel=\"stylesheet\" href=\"https://diyhue.org/cdn/nouislider.css\"/></head><body> <div class=\"wrapper\"> <nav class=\"nav-extended row\" style=\"background-color: #26a69a !important;\"> <div class=\"nav-wrapper col s12\"> <a href=\"#\" class=\"brand-logo\">DiyHue</a> <ul id=\"nav-mobile\" class=\"right hide-on-med-and-down\" style=\"position: relative;z-index: 10;\"> <li><a target=\"_blank\" href=\"https://github.com/diyhue\"><i class=\"material-icons left\">language</i>GitHub</a></li><li><a target=\"_blank\" href=\"https://diyhue.readthedocs.io/en/latest/\"><i class=\"material-icons left\">description</i>Documentation</a></li><li><a target=\"_blank\" href=\"https://diyhue.slack.com/\"><i class=\"material-icons left\">question_answer</i>Slack channel</a></li></ul> </div><div class=\"nav-content\"> <ul class=\"tabs tabs-transparent\"> <li class=\"tab\" title=\"#home\"><a class=\"active\" href=\"#home\">Home</a></li><li class=\"tab\" title=\"#preferences\"><a href=\"#preferences\">Preferences</a></li><li class=\"tab\" title=\"#network\"><a href=\"#network\">Network settings</a></li><li class=\"tab\" title=\"/update\"><a href=\"/update\">Updater</a></li></ul> </div></nav> <ul class=\"sidenav\" id=\"mobile-demo\"> <li><a target=\"_blank\" href=\"https://github.com/diyhue\">GitHub</a></li><li><a target=\"_blank\" href=\"https://diyhue.readthedocs.io/en/latest/\">Documentation</a></li><li><a target=\"_blank\" href=\"https://diyhue.slack.com/\">Slack channel</a></li></ul> <div class=\"container\"> <div class=\"section\"> <div id=\"home\" class=\"col s12\"> <form> <input type=\"hidden\" name=\"section\" value=\"1\"> <div class=\"row\"> <div class=\"col s10\"> <label for=\"power\">Power</label> <div id=\"power\" class=\"switch section\"> <label> Off <input type=\"checkbox\" name=\"pow\" id=\"pow\" value=\"1\"> <span class=\"lever\"></span> On </label> </div></div></div><div class=\"row\"> <div class=\"col s12 m10\"> <label for=\"bri\">Brightness</label> <input type=\"text\" id=\"bri\" class=\"js-range-slider\" name=\"bri\" value=\"\"/> </div></div><div class=\"row\"> <div class=\"col s12\"> <label for=\"hue\">Color</label> <div id=\"picker\" width=\"320px\" height=\"320px\"></div></div></div><div class=\"row\"> <div class=\"col s12\"> <label for=\"ct\">Color Temp</label> <div id=\"ct\" width=\"320px\" height=\"50px\"></div></div></div></form> </div><div id=\"preferences\" class=\"col s12\"> <form method=\"POST\" action=\"/\"> <input type=\"hidden\" name=\"section\" value=\"1\"> <div class=\"row\"> <div class=\"col s12\"> <label for=\"name\">Light Name</label> <input type=\"text\" id=\"name\" name=\"name\"> </div></div><div class=\"row\"> <div class=\"col s12 m6\"> <label for=\"startup\">Default Power:</label> <select name=\"startup\" id=\"startup\"> <option value=\"0\">Last State</option> <option value=\"1\">On</option> <option value=\"2\">Off</option> </select> </div></div><div class=\"row\"> <div class=\"col s12 m6\"> <label for=\"scene\">Default Scene:</label> <select name=\"scene\" id=\"scene\"> <option value=\"0\">Relax</option> <option value=\"1\">Read</option> <option value=\"2\">Concentrate</option> <option value=\"3\">Energize</option> <option value=\"4\">Bright</option> <option value=\"5\">Dimmed</option> <option value=\"6\">Nightlight</option> <option value=\"7\">Savanna sunset</option> <option value=\"8\">Tropical twilight</option> <option value=\"9\">Arctic aurora</option> <option value=\"10\">Spring blossom</option> </select> </div></div><div class=\"row\"> <div class=\"col s4 m3\"> <label for=\"pixelcount\" class=\"col-form-label\">Pixel count</label> <input type=\"number\" id=\"pixelcount\" name=\"pixelcount\"> </div></div><div class=\"row\"> <div class=\"col s4 m3\"> <label for=\"rpct\" class=\"form-label\">Red multiplier</label> <input type=\"number\" id=\"rpct\" class=\"js-range-slider\" data-skin=\"round\" name=\"rpct\" value=\"\"/> </div><div class=\"col s4 m3\"> <label for=\"gpct\" class=\"form-label\">Green multiplier</label> <input type=\"number\" id=\"gpct\" class=\"js-range-slider\" data-skin=\"round\" name=\"gpct\" value=\"\"/> </div><div class=\"col s4 m3\"> <label for=\"bpct\" class=\"form-label\">Blue multiplier</label> <input type=\"number\" id=\"bpct\" class=\"js-range-slider\" data-skin=\"round\" name=\"bpct\" value=\"\"/> </div></div><div class=\"row\"> <label class=\"control-label col s10\">HW buttons:</label> <div class=\"col s10\"> <div class=\"switch section\"> <label> Disable <input type=\"checkbox\" name=\"hwswitch\" id=\"hwswitch\" value=\"1\"> <span class=\"lever\"></span> Enable </label> </div></div></div><div class=\"switchable\"> <div class=\"row\"> <div class=\"col s4 m3\"> <label for=\"on\">On Pin</label> <input type=\"number\" id=\"on\" name=\"on\"> </div><div class=\"col s4 m3\"> <label for=\"off\">Off Pin</label> <input type=\"number\" id=\"off\" name=\"off\"> </div></div></div><div class=\"row\"> <div class=\"col s10\"> <button type=\"submit\" class=\"waves-effect waves-light btn teal\">Save</button> </div></div></form> </div><div id=\"network\" class=\"col s12\"> <form method=\"POST\" action=\"/\"> <input type=\"hidden\" name=\"section\" value=\"2\"> <div class=\"row\"> <div class=\"col s12\"> <label class=\"control-label\">Manual IP assignment:</label> <div class=\"switch section\"> <label> Disable <input type=\"checkbox\" name=\"disdhcp\" id=\"disdhcp\" value=\"0\"> <span class=\"lever\"></span> Enable </label> </div></div></div><div class=\"switchable\"> <div class=\"row\"> <div class=\"col s12 m3\"> <label for=\"addr\">Ip</label> <input type=\"text\" id=\"addr\" name=\"addr\"> </div><div class=\"col s12 m3\"> <label for=\"sm\">Submask</label> <input type=\"text\" id=\"sm\" name=\"sm\"> </div><div class=\"col s12 m3\"> <label for=\"gw\">Gateway</label> <input type=\"text\" id=\"gw\" name=\"gw\"> </div></div></div><div class=\"row\"> <div class=\"col s10\"> <button type=\"submit\" class=\"waves-effect waves-light btn teal\">Save</button> </div></div></form> </div></div></div></div><script src=\"https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js\"></script> <script src=\"https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/js/materialize.min.js\"></script> <script src=\"https://diyhue.org/cdn/iro.min.js\"></script> <script src=\"https://diyhue.org/cdn/nouislider.js\"></script> <script src=\"https://diyhue.org/cdn/gradient-iro.js\"></script> <script></script></body></html>";
  966.     server.send(200, "text/html", htmlContent);
  967.     if (server.args()) {
  968.       delay(1000); // needs to wait until response is received by browser. If ESP restarts too soon, browser will think there was an error.
  969.       ESP.restart();
  970.     }
  971.  
  972.   });
  973.  
  974.   server.on("/reset", []() { // trigger manual reset
  975.     server.send(200, "text/html", "reset");
  976.     delay(1000);
  977.     ESP.restart();
  978.   });
  979.  
  980.  
  981.   server.on("/factory", []() { // trigger manual reset
  982.     server.send(200, "text/html", "factory reset");
  983.     factoryReset();
  984.   });
  985.  
  986.   server.onNotFound(handleNotFound);
  987.  
  988.   server.begin();
  989.  
  990.   digitalWrite(ledGPIO, LOW); // turn off the led on full init.
  991. }
  992.  
  993. void entertainment() { // entertainment function
  994.   uint8_t packetSize = Udp.parsePacket(); // check if UDP received some bytes
  995.   if (packetSize) { // if nr of bytes is more than zero
  996.     if (!entertainmentRun) { // announce entertainment is running
  997.       entertainmentRun = true;
  998.     }
  999.     lastEPMillis = millis(); // update variable with last received package timestamp
  1000.     Udp.read(packetBuffer, packetSize);
  1001.     lightsCount = packetSize / 4;
  1002.     lightLedsCount = pixelCount / (lightsCount - 1);
  1003.     for (uint8_t i = 0; i < packetSize / 4; i++) { // loop with every light. There are 4 bytes for every light (light number, red, green, blue)
  1004.       float r, g, b;
  1005.       r = lights[packetBuffer[i * 4]].currentColors[0] = packetBuffer[i * 4 + 1] * rgb_multiplier[0] / 100;
  1006.       g = lights[packetBuffer[i * 4]].currentColors[1] = packetBuffer[i * 4 + 2] * rgb_multiplier[1] / 100;
  1007.       b = lights[packetBuffer[i * 4]].currentColors[2] = packetBuffer[i * 4 + 3] * rgb_multiplier[2] / 100;
  1008.  
  1009.       float whiteValueForRed = r * 255.0 / kWhiteRedChannel;
  1010.       float whiteValueForGreen = g * 255.0 / kWhiteGreenChannel;
  1011.       float whiteValueForBlue = b * 255.0 / kWhiteBlueChannel;
  1012.  
  1013.       float minWhiteValue = min(whiteValueForRed,
  1014.                                 min(whiteValueForGreen,
  1015.                                     whiteValueForBlue));
  1016.       lights[packetBuffer[i * 4]].currentColors[0] = r - minWhiteValue * kWhiteRedChannel / 255;
  1017.       lights[packetBuffer[i * 4]].currentColors[1] = g - minWhiteValue * kWhiteGreenChannel / 255;
  1018.       lights[packetBuffer[i * 4]].currentColors[2] = b - minWhiteValue * kWhiteBlueChannel / 255;
  1019.       lights[packetBuffer[i * 4]].currentColors[3] = (minWhiteValue <= 255 ? (uint8_t) minWhiteValue : 255);
  1020.     }
  1021.     for (uint8_t light = 0; light < lightsCount; light++) {
  1022.  
  1023.       if (light == 0) {
  1024.         if (lightsCount == 2) {
  1025.           for (uint8_t pixel = 0; pixel < pixelCount; pixel++) {
  1026.             strip->SetPixelColor(pixel, RgbwColor::LinearBlend(convFloat(lights[0].currentColors), convFloat(lights[1].currentColors),  (float)(pixel) / (float)pixelCount));
  1027.           }
  1028.         } else {
  1029.           for (uint8_t pixel = 0; pixel < lightLedsCount; pixel++) {
  1030.             strip->SetPixelColor(pixel, RgbwColor::LinearBlend(convFloat(lights[0].currentColors), convFloat(lights[1].currentColors),  (float)(pixel) / (float)lightLedsCount));
  1031.           }
  1032.         }
  1033.       } else if (light != lightsCount - 1) {
  1034.         for (uint8_t pixel = 0; pixel < lightLedsCount ; pixel++) {
  1035.           strip->SetPixelColor(pixel + lightLedsCount * light, RgbwColor::LinearBlend(convFloat(lights[light].currentColors), convFloat(lights[light + 1].currentColors), (float)(pixel) / (float)lightLedsCount));
  1036.         }
  1037.       }
  1038.  
  1039.     }
  1040.     strip->Show();
  1041.   }
  1042. }
  1043.  
  1044. void loop() {
  1045.   server.handleClient();
  1046.   if (!entertainmentRun) {
  1047.     lightEngine(); // process lights data set on http server
  1048.   } else {
  1049.     if ((millis() - lastEPMillis) >= ENTERTAINMENT_TIMEOUT) { // entertainment stream stop (timeout)
  1050.       entertainmentRun = false;
  1051.       for (uint8_t i = 0; i < lightsCount; i++) {
  1052.         processLightdata(i); //return to original colors with 0.4 sec transition
  1053.       }
  1054.     }
  1055.   }
  1056.   entertainment(); // process entertainment data on UDP server
  1057.  
  1058.   // blink the led if signal is lost
  1059.   if (millis() >=  lastWiFiCheck + 10000)  {
  1060.     if (WiFi.status() != WL_CONNECTED) {
  1061.       blinkLed(5);
  1062.       digitalWrite(ledGPIO, HIGH);
  1063.     } else {
  1064.       digitalWrite(ledGPIO, LOW);
  1065.     }
  1066.     lastWiFiCheck = millis();
  1067.   }
  1068. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement