2 * Serial LED Controller, IM Dendrite module (for ESP8266)
3 * Created for Interplaymediumâ„¢ project (https://interplaymedium.org)
4 * Copyright © 2016 Dmitry Shalnov [interplaymedium.org]
5 * Licensed under the Apache License, Version 2.0
13 #include <NTPClient.h>
15 #include <ESP8266WiFi.h>
16 #include <WiFiClient.h>
17 #include <ESP8266WebServer.h>
18 #include <ESP8266mDNS.h>
21 #define LED_STRIPE WS2811 // WS2812B // APA106 // type of LEDs Supported LED chipsets: http://fastled.io/docs/3.1/md__r_e_a_d_m_e.html
23 // -------------------- FastLED settings --------------
25 #define FASTLED_ESP8266_RAW_PIN_ORDER
26 #define FASTLED_INTERNAL // to remove #pragma diagnostics messages
29 // Serial // Switch 4 channel
30 #define DATA_PIN 3 // 3 // 2 // DIn controller pin (GPIO2) TODO: switch to GPIO0 + pull down (to avoid blink on reset)
31 #define ENABLE_GATE_PIN 2 // 2 // 3 // common ping for SN74HC08 AND gate (to prevent blink on power on + pull up the level)
32 #define MAX_NUM_LEDS 256 // maximum number of leds in stripe
33 #define DEF_NUM_LEDS 24 // default number of leds in stripe
34 #define DEF_MAIN_DELAY 1 // default FX delay (in loop)
36 #define FULL_OUTPUT 2 // default quiet level
38 CRGB LEDStripe_RGB[ MAX_NUM_LEDS ]; // This is an array of leds. One item for each led in your stripe.
45 uint8_t numberOfLEDs = DEF_NUM_LEDS;
47 uint16_t mainDelay = 0, mainDelayNew = 0;
49 unsigned char FX_PhaseXDelay = 0, FX_PhaseYDelay = 0, FX_PhaseZDelay = 0;
51 // uint8_t EXP[ 256 ]; // lookup table
53 unsigned long timeStamp = 0;
54 uint8_t changeColor = 0, quiet = FULL_OUTPUT, commandReboot = false;
56 // -------------------- EEPROM addresing ---------------
60 #define EEPROM_HOST_MAX_LEN 57 // Host name max length, can't be > 57 (could be 64, buth there is a bug in avahi https://github.com/lathiat/avahi/issues/273)
61 #define EEPROM_OTHER_MAX_LEN 13 // total size of other EEPROM parameters (summ of all EEPROM values below, included EEPROM_FLAG)
64 #define EEPROM_FLAG EEPROM_HOST_MAX_LEN + 1 // EEPROM has been changed (avoid overlap: EEPROM_HOST_MAX_LEN)
66 #define EEPROM_numberOfLEDs EEPROM_HOST_MAX_LEN + 2
68 #define EEPROM_HgradBegin EEPROM_HOST_MAX_LEN + 3 // (the order of defines matter)
69 #define EEPROM_SgradBegin EEPROM_HOST_MAX_LEN + 4
70 #define EEPROM_VgradBegin EEPROM_HOST_MAX_LEN + 5
72 #define EEPROM_HgradEnd EEPROM_HOST_MAX_LEN + 6
73 #define EEPROM_SgradEnd EEPROM_HOST_MAX_LEN + 7
74 #define EEPROM_VgradEnd EEPROM_HOST_MAX_LEN + 8
76 #define EEPROM_PERIOD EEPROM_HOST_MAX_LEN + 9
78 #define EEPROM_FX_PhaseX EEPROM_HOST_MAX_LEN + 10
79 #define EEPROM_FX_PhaseY EEPROM_HOST_MAX_LEN + 11
80 #define EEPROM_FX_PhaseZ EEPROM_HOST_MAX_LEN + 12
82 #define EEPROM_mainDelayNew EEPROM_HOST_MAX_LEN + 13
85 // ------------------- array of parameters ------------- (the order of defines matter)
87 #define NUMBER_OF_PARAM 10
100 float FX_Arr[ NUMBER_OF_PARAM ] = {};
101 float FX_ArrNew[ NUMBER_OF_PARAM ] = {};
102 float FX_Coeff[ NUMBER_OF_PARAM ] = {};
103 float Phase[ 3 ] = {};
105 float FX_CoeffMax = 1.0;
112 // ------------------- server settings -----------------
114 #define HOST_DEFAULT "im_serial_"
115 #define NTP_SERVER "europe.pool.ntp.org"
117 char host[ EEPROM_HOST_MAX_LEN ];
119 // const char* ssid = "axod_ap";
120 // const char* password = "superstrongpassword"
122 const char* ssid = IM_WIFI_SSID;
123 const char* password = IM_WIFI_PASS;
125 ESP8266WebServer server(80);
132 NTPClient timeClient(ntpUDP, NTP_SERVER , 0, 60000);
134 // ---------------- EEPROM String r/w ------------------
136 void EEPROMStrRead( uint8_t addr, char * str ){
137 for (uint8_t a = addr; a < EEPROM_HOST_MAX_LEN; a++ ){
138 str[ a ] = EEPROM.read( a );
139 if (str[ a ] == 0) break;
143 void EEPROMStrWrite( uint8_t addr, char * str ){
145 for (a = addr; a < EEPROM_HOST_MAX_LEN; a++ ){
146 EEPROM.write( a, str[ a ] );
147 if (str[ a ] == 0) break;
149 EEPROM.write( a, 0 );
152 // ---------------- misc -------------------------------
154 int str2HEX(const String str) {
155 return strtol( str.c_str(), 0, 16 );
158 String printNum(unsigned long num, int base, char sign) {
167 outbuf[i] = "0123456789ABCDEF"[num % base];
178 outbuf[j++] = outbuf[i];
181 if ( j == 1 ) { outbuf[ 1 ] = outbuf[ 0 ]; outbuf[ 0 ] = '0'; ++j; }
189 uint8_t is_number( const char *s ){
192 Serial.printf("s: %c\n", *s);
193 if ( *s++ <48 || *s >57 ) return 0;
198 // ----------- explode for selected substring -----------
200 String expld( String str, unsigned int numb, char delimiter ){
202 unsigned int cnt = 0, a = 0, p2 = 0, p1 = 0, lng = 0;
206 for ( a = 0; a < lng; a++ ){
207 if ( str.charAt( a ) == delimiter ) {
210 if ( cnt == numb ) break;
215 if ( cnt < numb ) return "";
222 if ( numb > 0 ) p2 ++;
224 return str.substring(p2, p1);
228 // ---------------- FX ---------------------------------
230 void FX_Gradient_RT( uint8_t HgradBegin, uint8_t SgradBegin, uint8_t VgradBegin, uint8_t HgradEnd, uint8_t SgradEnd, uint8_t VgradEnd, uint8_t period, uint8_t phaseX, uint8_t phaseY, uint8_t phaseZ ) {
235 #define Y_PHASE_SHIFT 128 // 128 // to avoid solid fill on phase = 0
237 // NOTE! phaseZ is for color component only since it is cyclic
239 for (i = 0; i < numberOfLEDs; i++) {
241 // newHueColor = pow( (float)sin( (float)i*PI / (period * 2) ) ,2 ) * (gradEnd - gradBegin) + gradBegin; // MEMO! floating point
243 if ( HgradEnd != HgradBegin ) color.h = ( ((int)cos8( (i + phaseX) * 128 / period ) - 128) * ((int)sin8( (phaseY + Y_PHASE_SHIFT) * 128 / period ) - 128) / 128 + 128 ) / ( 256 / ( HgradEnd - HgradBegin ) ) + HgradBegin; else color.h = HgradEnd;
244 if ( SgradEnd != SgradBegin ) color.s = ( ((int)cos8( (i + phaseX) * 128 / period ) - 128) * ((int)sin8( (phaseY + Y_PHASE_SHIFT) * 128 / period ) - 128) / 128 + 128 ) / ( 256 / ( SgradEnd - SgradBegin ) ) + SgradBegin; else color.s = SgradEnd;
245 if ( VgradEnd != VgradBegin ) color.v = ( ((int)cos8( (i + phaseX) * 128 / period ) - 128) * ((int)sin8( (phaseY + Y_PHASE_SHIFT) * 128 / period ) - 128) / 128 + 128 ) / ( 256 / ( VgradEnd - VgradBegin ) ) + VgradBegin; else color.v = VgradEnd;
247 // LEDStripe_RGB[ i ] = CHSV( color.h + phaseZ , color.s, color.v );
250 hsv2rgb_spectrum(color, LEDStripe_RGB[ i ]);
255 // -----------------------------------------------------
256 // --------------- Init --------------------------------
257 // -----------------------------------------------------
261 HTTPOut.reserve(800); // lenght of Help message generally
263 hostDefURI.reserve( EEPROM_HOST_MAX_LEN );
264 hostDefURI = HOST_DEFAULT + WiFi.macAddress().substring(12, 14) + WiFi.macAddress().substring(15, 17); // 00:00:00:00:00:00
266 // calculate lookup array
268 // for (unsigned int a = 1; a <= 255; a++) EXP[ a ] = round( pow( 255, (double)a / (255) ) ); // calculate exponential lookup array (for PWM etc..)
270 // reset FX_Coeff (maybe not required)
272 for ( uint8_t a = 0; a < NUMBER_OF_PARAM; a++ ) FX_Coeff[ a ] = 1.0;
278 Serial.begin(115200);
280 Serial.println("Booting Sketch...");
283 // read parameters from EEPROM (if it has been saved early)
285 EEPROM.begin( EEPROM_HOST_MAX_LEN + EEPROM_OTHER_MAX_LEN );
287 if ( EEPROM.read( EEPROM_FLAG ) == 1 ){
289 EEPROMStrRead( EEPROM_HOST, host );
291 numberOfLEDs = EEPROM.read( EEPROM_numberOfLEDs );
292 mainDelayNew = EEPROM.read( EEPROM_mainDelayNew );
294 for ( uint8_t a = 0; a < NUMBER_OF_PARAM; a++ ) FX_ArrNew[ a ] = EEPROM.read( EEPROM_HgradBegin + a );
300 FX_ArrNew[ PERIOD ] = DEF_NUM_LEDS;
301 strcpy( host, (char*)hostDefURI.c_str() ); // default URI and host name
306 pinMode(ENABLE_GATE_PIN, OUTPUT);
307 digitalWrite( ENABLE_GATE_PIN, HIGH );
309 delay(1000); // 2000 // sanity check delay - allows reprogramming if accidently blowing power w/leds
311 FastLED.addLeds<LED_STRIPE, DATA_PIN, GRB>( LEDStripe_RGB, numberOfLEDs );
312 fill_solid( LEDStripe_RGB, numberOfLEDs, CRGB(0, 0, 0) );
317 if ( host == "" ) strcpy( host, (char*)hostDefURI.c_str() ); // if it was changed without parameters by mistake
319 WiFi.hostname( host );
321 // WiFi.softAP(APssid, APpassword);
322 // WiFi.mode(WIFI_AP);
323 // WiFi.mode(WIFI_AP_STA);
326 WiFi.setAutoReconnect( true );
327 WiFi.begin(ssid, password);
329 // main events handlers
331 if (WiFi.waitForConnectResult() == WL_CONNECTED) {
337 // timeClient.setTimeOffset(0);
344 MDNS.addService("http", "tcp", 80);
347 Serial.printf("Ready. Try \"curl %s/help\" to see the list of commands and parameters.\n", host);
350 server.onNotFound( []() {
352 server.sendHeader("Connection", "close");
357 // no command given (see handlers of commands below)
359 if ( server.uri() != "/" )HTTPErr += "Command '" + server.uri() + "' not exist.\n";
362 for ( uint8_t i=0; i < server.args(); i++ ){
366 // ------------- specific parameters -----------------
368 // change number of leds in stripe
370 if ( server.argName(i) == "number" ) {
372 // TODO make it in MAIN with dimming
374 fill_solid( LEDStripe_RGB, numberOfLEDs, CRGB(0, 0, 0) );
377 numberOfLEDs = server.arg("number").toInt();
378 FastLED.addLeds<LED_STRIPE, DATA_PIN, GRB>( LEDStripe_RGB, numberOfLEDs );
383 // color gradient (hex)
385 if ( server.argName(i) == "gradient" ) {
387 param = server.arg("gradient");
389 for ( uint8_t a = 0; a < 6; a++ ) FX_ArrNew[ H_GRAD_BEG + a ] = str2HEX( param.substring( a*2, a*2+2) );
396 // sin/cos period (dec)
398 if ( server.argName(i) == "period" ) {
400 FX_ArrNew[ PERIOD ] = server.arg("period").toInt();
407 // pattern motion parameters (dec)
409 if ( server.argName(i) == "slidex" ) {
410 FX_ArrNew [ PHASE_X_INC ] = (float)server.arg("slidex").toInt();
415 if ( server.argName(i) == "slidey" ) {
416 FX_ArrNew [ PHASE_Y_INC ] = (float)server.arg("slidey").toInt();
421 if ( server.argName(i) == "slidez" ) {
422 FX_ArrNew [ PHASE_Z_INC ] = (float)server.arg("slidez").toInt();
428 // delay between frames
430 if ( server.argName(i) == "delay" ) {
431 mainDelayNew = server.arg("delay").toInt();
432 // changeColor = true;
436 // ------------- common parameters -------------------
438 // debug data {Number R G B H S V}
440 if ( server.argName(i) == "debug" ){ // || param != 0
442 HTTPOut += "Stripe [debug]: ";
444 for (unsigned int i = 0; i < numberOfLEDs; i++) {
445 color_HSV = rgb2hsv_approximate( LEDStripe_RGB[ i ] );
446 HTTPOut += String( i ) + "\t" + String( LEDStripe_RGB[ i ].r ) + "\t" + String( LEDStripe_RGB[ i ].g ) + "\t" + String( LEDStripe_RGB[ i ].b ) + "\t" + String( color_HSV.h ) + "\t" + String( color_HSV.s ) + "\t" + String( color_HSV.v ) + "\n";
454 if ( server.argName(i) == "time" ){ // || param != 0
456 if ( is_number( server.arg("time").c_str() ) ) {
458 timeStamp = strtol( server.arg("time").c_str(), 0, 10 );
460 HTTPErr += "Parameter 'time' must be specified in UNIX timestamp format.\n"; // 1631848103
468 if ( server.argName(i) == "host" ) {
470 if ( server.arg("host") == "" ) {
472 HTTPErr += "Please specify new unit hostname.\n";
474 } else if ( server.arg("host").length() > EEPROM_HOST_MAX_LEN ) {
476 HTTPErr += "Unit hostname can not exceed " + String( EEPROM_HOST_MAX_LEN ) + " symbols.\n";
480 strcpy( host, (char*)server.arg("host").c_str() ); // host = (char *)server.arg("host").c_str();
481 WiFi.hostname( host );
484 HTTPOut += "The host name has been changed to '" + String( host ) + "'. Do not forget to save current settings using the '/save' command.\n";
490 // save all parameters to EEPROM
492 if (server.argName(i) == "save" ){
494 EEPROM.write( EEPROM_numberOfLEDs, numberOfLEDs );
495 EEPROM.write( EEPROM_mainDelayNew, mainDelayNew );
497 for ( uint8_t a = 0; a < NUMBER_OF_PARAM; a++ ) EEPROM.write( EEPROM_HgradBegin + a, (uint8_t)roundf( FX_Arr[ a ] ) );
499 EEPROMStrWrite( EEPROM_HOST, host );
500 EEPROM.write( EEPROM_FLAG, 1 ); // EEPROM changed
503 HTTPOut += "The current settings are saved.\n";
508 // reset to default parameters
510 if ( server.argName(i) == "reset" ){
512 numberOfLEDs = DEF_NUM_LEDS;
513 mainDelayNew = DEF_MAIN_DELAY;
515 for ( uint8_t a = 0; a < NUMBER_OF_PARAM; a++ ) FX_ArrNew[ a ] = 0;
517 FX_Arr[ PERIOD ] = DEF_NUM_LEDS;
518 FX_ArrNew[ PERIOD ] = DEF_NUM_LEDS;
520 strcpy( host, (char*)hostDefURI.c_str() );
521 WiFi.hostname( host );
525 if ( quiet > 1 ) HTTPOut += "All parameters are set to default values. Don't forget to /save them.\n";
535 if ( server.argName(i) == "reboot" ){
537 FX_ArrNew[ V_GRAD_BEG ] = 0;
538 FX_ArrNew[ V_GRAD_END ] = 0;
540 mainDelayNew = DEF_MAIN_DELAY;
544 server.send_P(200, "text/plain", PSTR("Unit rebooting...\n") );
551 commandReboot = true;
554 // quiet, level of output
556 if ( server.argName(i) == "quiet" ) {
558 quiet = str2HEX( server.arg("quiet") );
566 if ( server.argName(i) == "json" ) {
571 if ( !paramCnt ) HTTPErr += "Parameter '" + server.argName(i) + "' not exist.\n";
575 // ---------------- host default return ------------------------
577 if (HTTPErr != "" ) {
592 Err = expld( HTTPErr, paramCnt, '\n' );
593 if ( Err != "" ) HTTPOut += "\"" + Err + "\""; else break;
594 if ( expld( HTTPErr, paramCnt+1, '\n' ) != "" ) HTTPOut += ",";
603 Err = expld( HTTPErr, paramCnt, '\n' );
604 if ( Err != "" ) HTTPOut += "Error: " + Err + "\n"; else break;
607 // HTTPOut += "See '/help' for details\n";
615 if ( quiet > 1 && HTTPOut == "" ) {
620 HTTPOut += "\"MAC\":\"" + String( WiFi.macAddress() ) + "\",\n";
621 HTTPOut += "\"Host\":\"" + String( host ) + "\",\n";
622 HTTPOut += "\"Timestamp\":\"" + String( timeClient.getEpochTime() ) + "\",\n";
623 HTTPOut += "\"Target_timestamp\":\"" + String( timeStamp ) + "\",\n";
625 HTTPOut += "\"TNumber_of_LEDs\":\"" + String( numberOfLEDs ) + "\",\n";
627 HTTPOut += "\"SlideX:\":\"" + String( (int)roundf( FX_ArrNew[ PHASE_X_INC ] ) ) + "\",\n";
628 HTTPOut += "\"SlideY:\":\"" + String( (int)roundf( FX_ArrNew[ PHASE_Y_INC ] ) ) + "\",\n";
629 HTTPOut += "\"SlideZ:\":\"" + String( (int)roundf( FX_ArrNew[ PHASE_Z_INC ] ) ) + "\",\n";
631 HTTPOut += "\"Period:\":\"" + String( (int)roundf( FX_ArrNew[ PERIOD ] ) ) + "\",\n";
632 HTTPOut += "\"Delay:\":\"" + String( mainDelayNew ) + "\",\n";
634 HTTPOut += "\"Gradient_HSV:\":\"";
636 for ( uint8_t a = 0; a < 6 ; a++ ) HTTPOut += String( printNum( FX_ArrNew[ a ], 16, ' ') );
644 HTTPOut = "MAC:" + String( WiFi.macAddress() ) + "\n";
645 HTTPOut += "Host:" + String( host ) + "\n";
646 HTTPOut += "Timestamp:" + String( timeClient.getEpochTime() ) + "\n";
647 HTTPOut += "Target timestamp:" + String( timeStamp ) + "\n";
649 HTTPOut += "Number of LEDs:" + String( numberOfLEDs ) + "\n";
651 HTTPOut += "Slide X:" + String( (int)roundf( FX_ArrNew[ PHASE_X_INC ] ) ) + "\n";
652 HTTPOut += "Slide Y:" + String( (int)roundf( FX_ArrNew[ PHASE_Y_INC ] ) ) + "\n";
653 HTTPOut += "Slide Z:" + String( (int)roundf( FX_ArrNew[ PHASE_Z_INC ] ) ) + "\n";
655 HTTPOut += "Period:" + String( (int)roundf( FX_ArrNew[ PERIOD ] ) ) + "\n";
656 HTTPOut += "Delay:" + String( mainDelayNew ) + "\n";
658 HTTPOut += "Gradient (HSV):";
660 for ( uint8_t a = 0; a < 6 ; a++ ) HTTPOut += String( printNum( roundf( FX_ArrNew[ a ] ), 16, ' ') ) + " ";
668 server.send(200, "text/plain", HTTPOut );
676 // ui interface TODO xml inteface
678 server.on("/ui", HTTP_GET, []() {
679 HTTPOut = "User interface. Not yet implemented.\n";
680 server.send(200, "text/plain", HTTPOut);
685 server.on("/help", HTTP_GET, []() {
687 HTTPOut = "Interplay Medium ESP8266 Serial LED Controller. Version: " + String(VERSION) + "\n";
688 HTTPOut += "Created by Dmitry Shalnov (c) 2017. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>. \n\n";
690 HTTPOut += "Parameters:\n";
692 HTTPOut += " ?number= Number of LEDs in stripe (max MAX_NUM_LEDS)\n";
693 HTTPOut += " ?gradient= Start and end colors of gradient (hex, 24bit/color in HLS format, e.g. 001122FFEECC)\n";
694 HTTPOut += " ?period= Sine period (number of leds)\n";
695 HTTPOut += " ?delay= Delay of state changing, integer\n";
697 HTTPOut += " ?slidex= Step of X shift per cycle, signed integer\n";
698 HTTPOut += " ?slidey= Step of Y shift per cycle, signed integer\n";
699 HTTPOut += " ?slidez= Step of Z shift per cycle, signed integer\n";
701 HTTPOut += " ?time= Target timestamp (when the changes will take effect, good for synch), decimal UNIX Timestamp\n\n";
703 HTTPOut += " ?host= Rename the unit\n";
704 HTTPOut += " ?quiet= Level of output (0/empty = no output, 1 = OK/Error)\n\n";
706 HTTPOut += "Commands:\n";
708 HTTPOut += " ?save Save current settings\n";
709 HTTPOut += " ?reset Reset to initial settings\n";
710 HTTPOut += " ?reboot Reboot unit\n";
713 HTTPOut += " /ui Output in XML UI format\n";
714 HTTPOut += " /json Output in JSON format\n";
715 HTTPOut += " /update Wireless update of firmware (see example below)\n";
716 HTTPOut += " /help This help\n\n";
718 HTTPOut += "Usage: curl " + String(host) + "?<parameter>=<value>\n";
719 HTTPOut += " curl " + String(host) + "/<command>\n";
720 HTTPOut += "Examples: curl \"" + String(host) + "?gradient=ffffffddff00&period=30\" \n";
721 HTTPOut += " curl -F image=@firmware.bin " + String(host) + "/update \n";
723 server.sendHeader("Connection", "close");
724 server.send( 200, "text/plain", HTTPOut );
730 server.on("/update", HTTP_POST, []() {
732 server.sendHeader("Connection", "close");
733 server.send(200, "text/plain", (Update.hasError()) ? "FAIL" : "OK");
739 HTTPUpload& upload = server.upload();
741 if (upload.status == UPLOAD_FILE_START) {
744 Serial.setDebugOutput(true);
749 Serial.printf("Update: %s\n", upload.filename.c_str());
751 uint32_t maxSketchSpace = (ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000;
753 if (!Update.begin(maxSketchSpace)) { //start with max available size
755 Update.printError(Serial);
758 } else if (upload.status == UPLOAD_FILE_WRITE) {
759 if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) {
761 Update.printError(Serial);
764 } else if (upload.status == UPLOAD_FILE_END) {
765 if (Update.end(true)) { //true to set the size to the current progress
767 server.sendHeader("Connection", "close");
768 server.send_P(200, "text/plain", PSTR("Success. Please wait until device replace firmware and boot up...\n") );
770 Serial.printf("Update Success: %u\nRebooting....\n", upload.totalSize);
773 server.sendHeader("Connection", "close");
774 server.send_P(200, "text/plain", PSTR("Something went wrong. Please reset device and try again.\n") );
776 Update.printError(Serial);
780 Serial.setDebugOutput(false);
789 Serial.println("WiFi Failed");
794 // -----------------------------------------------------
795 // ------------------- MAIN ----------------------------
796 // -----------------------------------------------------
798 // uint8_t rebootColorsCnt = 0;
802 server.handleClient();
807 if ( WiFi.status() != WL_CONNECTED ) {
811 if (WiFi.waitForConnectResult() == WL_CONNECTED) {
813 Serial.println("Reconnected");
825 // set float coefficents
827 for ( uint8_t a = 0; a < NUMBER_OF_PARAM; a++ ) {
828 FX_Coeff[ H_GRAD_BEG + a ] = (float)FX_ArrNew[ H_GRAD_BEG + a ] - FX_Arr[ H_GRAD_BEG + a ];
829 if ( abs( FX_Coeff[ H_GRAD_BEG + a ] ) > FX_CoeffMax ) FX_CoeffMax = abs( FX_Coeff[ H_GRAD_BEG + a ] );
832 for ( uint8_t a = 0; a < 6; a++ ) FX_Coeff[ H_GRAD_BEG + a ] = FX_Coeff[ H_GRAD_BEG + a ] / FX_CoeffMax;
837 Serial.printf("FX_ArrNew: ");
838 for ( uint8_t a = 0; a < NUMBER_OF_PARAM; a++ ) Serial.printf("%f ", FX_ArrNew[ a ] );
840 Serial.printf("FX_Coeff: ");
841 for ( uint8_t a = 0; a < NUMBER_OF_PARAM; a++ ) Serial.printf("%f ", FX_Coeff[ a ] );
847 if ( ++mainDelay == mainDelayNew /* && FX_Delay != 0 */ ) {
850 // Serial.printf("FX_Arr: ");
851 // for ( uint8_t a = 0; a < NUMBER_OF_PARAM; a++ ) Serial.printf("%f ", FX_Arr[ a ] );
852 // Serial.printf("\n");
853 // Serial.printf("Phase: %f %f %f\n", Phase[ PHASE_X ], Phase[ PHASE_Y ], Phase[ PHASE_Z ] );
858 // fade out and reboot
860 if ( commandReboot && roundf( FX_Arr[ V_GRAD_BEG ] ) == 0 && roundf( FX_Arr[ V_GRAD_END ]) == 0 ) {
861 commandReboot = false;
863 Serial.printf("Reboot......\n");
866 // Serial.printf("Reboot.2...\n");
869 if ( timeClient.getEpochTime() > timeStamp ) {
873 for ( uint8_t a = 0; a < NUMBER_OF_PARAM; a++ ) {
874 if ( FX_ArrNew [ a ] != roundf( FX_Arr[ a ] ) ) { FX_Arr[ a ] += FX_Coeff[ a ]; } else FX_Arr[ a ] = (float)FX_ArrNew [ a ];
879 for ( uint8_t a = 0; a < 3; a++ ) {
881 if ( FX_Arr[ PHASE_X_INC + a ] == 0 ) {
882 // shift phase to 0 after slide. = 0
883 if ( (uint8_t)round( Phase[ a ] ) != 0 ) Phase[ a ] -= FX_Coeff[ PHASE_X_INC + a ]/100; else Phase[ a ] = 0;
885 // routine shift phase
886 Phase[ a ] += FX_Arr[ PHASE_X_INC + a ]/100;
892 if ( FX_Arr[ PERIOD ] == 0 ) FX_Arr[ PERIOD ] = 1;
894 FX_Gradient_RT( roundf( FX_Arr[ H_GRAD_BEG ] ), roundf( FX_Arr[ S_GRAD_BEG ] ), roundf( FX_Arr[ V_GRAD_BEG ] ), roundf( FX_Arr[ H_GRAD_END ] ), roundf( FX_Arr[ S_GRAD_END ] ), roundf( FX_Arr[ V_GRAD_END ] ), roundf( FX_Arr[ PERIOD ] ), roundf( Phase[ PHASE_X ] ), roundf( Phase[ PHASE_Y ] ), roundf( Phase[ PHASE_Z ] ) );