+/*
+ * Serial LED Controller, IM Dendrite module (for ESP8266)
+ * Created for Interplaymedium™ project (https://interplaymedium.org)
+ * Copyright © 2016 Dmitry Shalnov [interplaymedium.org]
+ * Licensed under the Apache License, Version 2.0
+*/
+
+#define SERIAL 0
+
+#include "../../info"
+#include "version.c"
+
+#include <NTPClient.h>
+
+#include <ESP8266WiFi.h>
+#include <WiFiClient.h>
+#include <ESP8266WebServer.h>
+#include <ESP8266mDNS.h>
+#include <EEPROM.h>
+
+#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
+
+// -------------------- FastLED settings --------------
+
+#define FASTLED_ESP8266_RAW_PIN_ORDER
+#define FASTLED_INTERNAL // to remove #pragma diagnostics messages
+
+#include <FastLED.h>
+ // Serial // Switch 4 channel
+#define DATA_PIN 3 // 3 // 2 // DIn controller pin (GPIO2) TODO: switch to GPIO0 + pull down (to avoid blink on reset)
+#define ENABLE_GATE_PIN 2 // 2 // 3 // common ping for SN74HC08 AND gate (to prevent blink on power on + pull up the level)
+#define MAX_NUM_LEDS 256 // maximum number of leds in stripe
+#define DEF_NUM_LEDS 24 // default number of leds in stripe
+#define DEF_MAIN_DELAY 1 // default FX delay (in loop)
+
+#define FULL_OUTPUT 2 // default quiet level
+
+CRGB LEDStripe_RGB[ MAX_NUM_LEDS ]; // This is an array of leds. One item for each led in your stripe.
+CHSV color_HSV;
+String param;
+
+uint8_t paramCnt = 0;
+uint8_t JSONon = 0;
+
+uint8_t numberOfLEDs = DEF_NUM_LEDS;
+
+uint16_t mainDelay = 0, mainDelayNew = 0;
+
+unsigned char FX_PhaseXDelay = 0, FX_PhaseYDelay = 0, FX_PhaseZDelay = 0;
+
+// uint8_t EXP[ 256 ]; // lookup table
+
+unsigned long timeStamp = 0;
+uint8_t changeColor = 0, quiet = FULL_OUTPUT, commandReboot = false;
+
+// -------------------- EEPROM addresing ---------------
+
+#define EEPROM_HOST 0
+
+#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)
+#define EEPROM_OTHER_MAX_LEN 13 // total size of other EEPROM parameters (summ of all EEPROM values below, included EEPROM_FLAG)
+
+
+#define EEPROM_FLAG EEPROM_HOST_MAX_LEN + 1 // EEPROM has been changed (avoid overlap: EEPROM_HOST_MAX_LEN)
+
+#define EEPROM_numberOfLEDs EEPROM_HOST_MAX_LEN + 2
+
+#define EEPROM_HgradBegin EEPROM_HOST_MAX_LEN + 3 // (the order of defines matter)
+#define EEPROM_SgradBegin EEPROM_HOST_MAX_LEN + 4
+#define EEPROM_VgradBegin EEPROM_HOST_MAX_LEN + 5
+
+#define EEPROM_HgradEnd EEPROM_HOST_MAX_LEN + 6
+#define EEPROM_SgradEnd EEPROM_HOST_MAX_LEN + 7
+#define EEPROM_VgradEnd EEPROM_HOST_MAX_LEN + 8
+
+#define EEPROM_PERIOD EEPROM_HOST_MAX_LEN + 9
+
+#define EEPROM_FX_PhaseX EEPROM_HOST_MAX_LEN + 10
+#define EEPROM_FX_PhaseY EEPROM_HOST_MAX_LEN + 11
+#define EEPROM_FX_PhaseZ EEPROM_HOST_MAX_LEN + 12
+
+#define EEPROM_mainDelayNew EEPROM_HOST_MAX_LEN + 13
+
+
+// ------------------- array of parameters ------------- (the order of defines matter)
+
+#define NUMBER_OF_PARAM 10
+
+#define H_GRAD_BEG 0
+#define S_GRAD_BEG 1
+#define V_GRAD_BEG 2
+#define H_GRAD_END 3
+#define S_GRAD_END 4
+#define V_GRAD_END 5
+#define PERIOD 6
+#define PHASE_X_INC 7
+#define PHASE_Y_INC 8
+#define PHASE_Z_INC 9
+
+float FX_Arr[ NUMBER_OF_PARAM ] = {};
+float FX_ArrNew[ NUMBER_OF_PARAM ] = {};
+float FX_Coeff[ NUMBER_OF_PARAM ] = {};
+float Phase[ 3 ] = {};
+
+float FX_CoeffMax = 1.0;
+
+#define PHASE_X 0
+#define PHASE_Y 1
+#define PHASE_Z 2
+
+
+// ------------------- server settings -----------------
+
+#define HOST_DEFAULT "im_serial_"
+#define NTP_SERVER "europe.pool.ntp.org"
+
+char host[ EEPROM_HOST_MAX_LEN ];
+
+// const char* ssid = "axod_ap";
+// const char* password = "superstrongpassword"
+
+const char* ssid = IM_WIFI_SSID;
+const char* password = IM_WIFI_PASS;
+
+ESP8266WebServer server(80);
+
+String hostDefURI;
+String HTTPOut;
+String HTTPErr;
+
+WiFiUDP ntpUDP;
+NTPClient timeClient(ntpUDP, NTP_SERVER , 0, 60000);
+
+// ---------------- EEPROM String r/w ------------------
+
+void EEPROMStrRead( uint8_t addr, char * str ){
+ for (uint8_t a = addr; a < EEPROM_HOST_MAX_LEN; a++ ){
+ str[ a ] = EEPROM.read( a );
+ if (str[ a ] == 0) break;
+ }
+}
+
+void EEPROMStrWrite( uint8_t addr, char * str ){
+ uint8_t a = 0;
+ for (a = addr; a < EEPROM_HOST_MAX_LEN; a++ ){
+ EEPROM.write( a, str[ a ] );
+ if (str[ a ] == 0) break;
+ }
+ EEPROM.write( a, 0 );
+}
+
+// ---------------- misc -------------------------------
+
+int str2HEX(const String str) {
+ return strtol( str.c_str(), 0, 16 );
+}
+
+String printNum(unsigned long num, int base, char sign) {
+
+ char outbuf[12];
+
+ int i = 12;
+ int j = 0;
+ int n;
+
+ do {
+ outbuf[i] = "0123456789ABCDEF"[num % base];
+ i--;
+ num = num/base;
+ } while ( num > 0 );
+
+ if ( sign != ' ' ){
+ outbuf[0] = sign;
+ ++j;
+ }
+
+ while ( ++i < 13 ){
+ outbuf[j++] = outbuf[i];
+ }
+
+ if ( j == 1 ) { outbuf[ 1 ] = outbuf[ 0 ]; outbuf[ 0 ] = '0'; ++j; }
+
+ outbuf[j] = 0;
+
+ return outbuf;
+
+}
+
+uint8_t is_number( const char *s ){
+
+ while (*s) {
+ Serial.printf("s: %c\n", *s);
+ if ( *s++ <48 || *s >57 ) return 0;
+ }
+ return true;
+}
+
+// ----------- explode for selected substring -----------
+
+String expld( String str, unsigned int numb, char delimiter ){
+
+ unsigned int cnt = 0, a = 0, p2 = 0, p1 = 0, lng = 0;
+
+ lng = str.length();
+
+ for ( a = 0; a < lng; a++ ){
+ if ( str.charAt( a ) == delimiter ) {
+ p2 = p1;
+ p1 = a;
+ if ( cnt == numb ) break;
+ cnt ++;
+ }
+ }
+
+ if ( cnt < numb ) return "";
+
+ if ( a == lng ) {
+ p2 = p1;
+ p1 = lng;
+ }
+
+ if ( numb > 0 ) p2 ++;
+
+ return str.substring(p2, p1);
+
+}
+
+// ---------------- FX ---------------------------------
+
+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 ) {
+
+ unsigned int i = 0;
+ CHSV color;
+
+ #define Y_PHASE_SHIFT 128 // 128 // to avoid solid fill on phase = 0
+
+ // NOTE! phaseZ is for color component only since it is cyclic
+
+ for (i = 0; i < numberOfLEDs; i++) {
+
+// newHueColor = pow( (float)sin( (float)i*PI / (period * 2) ) ,2 ) * (gradEnd - gradBegin) + gradBegin; // MEMO! floating point
+
+ 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;
+ 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;
+ 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;
+
+// LEDStripe_RGB[ i ] = CHSV( color.h + phaseZ , color.s, color.v );
+
+ color.h += phaseZ;
+ hsv2rgb_spectrum(color, LEDStripe_RGB[ i ]);
+
+ }
+}
+
+// -----------------------------------------------------
+// --------------- Init --------------------------------
+// -----------------------------------------------------
+
+void setup(void) {
+
+ HTTPOut.reserve(800); // lenght of Help message generally
+
+ hostDefURI.reserve( EEPROM_HOST_MAX_LEN );
+ hostDefURI = HOST_DEFAULT + WiFi.macAddress().substring(12, 14) + WiFi.macAddress().substring(15, 17); // 00:00:00:00:00:00
+
+ // calculate lookup array
+
+// for (unsigned int a = 1; a <= 255; a++) EXP[ a ] = round( pow( 255, (double)a / (255) ) ); // calculate exponential lookup array (for PWM etc..)
+
+ // reset FX_Coeff (maybe not required)
+
+ for ( uint8_t a = 0; a < NUMBER_OF_PARAM; a++ ) FX_Coeff[ a ] = 1.0;
+
+#if SERIAL == 1
+
+ // Serial init
+
+ Serial.begin(115200);
+ Serial.println();
+ Serial.println("Booting Sketch...");
+#endif
+
+ // read parameters from EEPROM (if it has been saved early)
+
+ EEPROM.begin( EEPROM_HOST_MAX_LEN + EEPROM_OTHER_MAX_LEN );
+
+ if ( EEPROM.read( EEPROM_FLAG ) == 1 ){
+
+ EEPROMStrRead( EEPROM_HOST, host );
+
+ numberOfLEDs = EEPROM.read( EEPROM_numberOfLEDs );
+ mainDelayNew = EEPROM.read( EEPROM_mainDelayNew );
+
+ for ( uint8_t a = 0; a < NUMBER_OF_PARAM; a++ ) FX_ArrNew[ a ] = EEPROM.read( EEPROM_HgradBegin + a );
+
+ changeColor = true;
+
+ } else {
+
+ FX_ArrNew[ PERIOD ] = DEF_NUM_LEDS;
+ strcpy( host, (char*)hostDefURI.c_str() ); // default URI and host name
+ }
+
+ // Fast LED RGB init
+
+ pinMode(ENABLE_GATE_PIN, OUTPUT);
+ digitalWrite( ENABLE_GATE_PIN, HIGH );
+
+ delay(1000); // 2000 // sanity check delay - allows reprogramming if accidently blowing power w/leds
+
+ FastLED.addLeds<LED_STRIPE, DATA_PIN, GRB>( LEDStripe_RGB, numberOfLEDs );
+ fill_solid( LEDStripe_RGB, numberOfLEDs, CRGB(0, 0, 0) );
+ FastLED.show();
+
+ // WiFi init
+
+ if ( host == "" ) strcpy( host, (char*)hostDefURI.c_str() ); // if it was changed without parameters by mistake
+
+ WiFi.hostname( host );
+
+// WiFi.softAP(APssid, APpassword);
+// WiFi.mode(WIFI_AP);
+// WiFi.mode(WIFI_AP_STA);
+
+ WiFi.mode(WIFI_STA);
+ WiFi.setAutoReconnect( true );
+ WiFi.begin(ssid, password);
+
+ // main events handlers
+
+ if (WiFi.waitForConnectResult() == WL_CONNECTED) {
+
+ MDNS.begin( host );
+
+ // get time
+
+// timeClient.setTimeOffset(0);
+ timeClient.begin();
+ timeClient.update();
+
+ // start server
+
+ server.begin();
+ MDNS.addService("http", "tcp", 80);
+
+#if SERIAL == 1
+ Serial.printf("Ready. Try \"curl %s/help\" to see the list of commands and parameters.\n", host);
+#endif
+
+ server.onNotFound( []() {
+
+ server.sendHeader("Connection", "close");
+
+ param = "";
+// HTTPOut = "";
+
+ // no command given (see handlers of commands below)
+
+ if ( server.uri() != "/" )HTTPErr += "Command '" + server.uri() + "' not exist.\n";
+
+
+ for ( uint8_t i=0; i < server.args(); i++ ){
+
+ paramCnt = 0;
+
+ // ------------- specific parameters -----------------
+
+ // change number of leds in stripe
+
+ if ( server.argName(i) == "number" ) {
+
+ // TODO make it in MAIN with dimming
+
+ fill_solid( LEDStripe_RGB, numberOfLEDs, CRGB(0, 0, 0) );
+ FastLED.show();
+
+ numberOfLEDs = server.arg("number").toInt();
+ FastLED.addLeds<LED_STRIPE, DATA_PIN, GRB>( LEDStripe_RGB, numberOfLEDs );
+
+ paramCnt++;
+ }
+
+ // color gradient (hex)
+
+ if ( server.argName(i) == "gradient" ) {
+
+ param = server.arg("gradient");
+
+ for ( uint8_t a = 0; a < 6; a++ ) FX_ArrNew[ H_GRAD_BEG + a ] = str2HEX( param.substring( a*2, a*2+2) );
+
+ changeColor = true;
+ paramCnt++;
+
+ }
+
+ // sin/cos period (dec)
+
+ if ( server.argName(i) == "period" ) {
+
+ FX_ArrNew[ PERIOD ] = server.arg("period").toInt();
+
+ changeColor = true;
+ paramCnt++;
+ }
+
+
+ // pattern motion parameters (dec)
+
+ if ( server.argName(i) == "slidex" ) {
+ FX_ArrNew [ PHASE_X_INC ] = (float)server.arg("slidex").toInt();
+ changeColor = true;
+ paramCnt++;
+ }
+
+ if ( server.argName(i) == "slidey" ) {
+ FX_ArrNew [ PHASE_Y_INC ] = (float)server.arg("slidey").toInt();
+ changeColor = true;
+ paramCnt++;
+ }
+
+ if ( server.argName(i) == "slidez" ) {
+ FX_ArrNew [ PHASE_Z_INC ] = (float)server.arg("slidez").toInt();
+ changeColor = true;
+ paramCnt++;
+ }
+
+
+ // delay between frames
+
+ if ( server.argName(i) == "delay" ) {
+ mainDelayNew = server.arg("delay").toInt();
+// changeColor = true;
+ paramCnt++;
+ }
+
+ // ------------- common parameters -------------------
+
+ // debug data {Number R G B H S V}
+
+ if ( server.argName(i) == "debug" ){ // || param != 0
+
+ HTTPOut += "Stripe [debug]: ";
+
+ for (unsigned int i = 0; i < numberOfLEDs; i++) {
+ color_HSV = rgb2hsv_approximate( LEDStripe_RGB[ i ] );
+ 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";
+ }
+
+ paramCnt++;
+ }
+
+ // set time
+
+ if ( server.argName(i) == "time" ){ // || param != 0
+
+ if ( is_number( server.arg("time").c_str() ) ) {
+ timeClient.update();
+ timeStamp = strtol( server.arg("time").c_str(), 0, 10 );
+ } else {
+ HTTPErr += "Parameter 'time' must be specified in UNIX timestamp format.\n"; // 1631848103
+ }
+
+ paramCnt++;
+ }
+
+ // rename host
+
+ if ( server.argName(i) == "host" ) {
+
+ if ( server.arg("host") == "" ) {
+
+ HTTPErr += "Please specify new unit hostname.\n";
+
+ } else if ( server.arg("host").length() > EEPROM_HOST_MAX_LEN ) {
+
+ HTTPErr += "Unit hostname can not exceed " + String( EEPROM_HOST_MAX_LEN ) + " symbols.\n";
+
+ } else {
+
+ strcpy( host, (char*)server.arg("host").c_str() ); // host = (char *)server.arg("host").c_str();
+ WiFi.hostname( host );
+ MDNS.begin( host );
+
+ HTTPOut += "The host name has been changed to '" + String( host ) + "'. Do not forget to save current settings using the '/save' command.\n";
+ }
+
+ paramCnt++;
+ }
+
+ // save all parameters to EEPROM
+
+ if (server.argName(i) == "save" ){
+
+ EEPROM.write( EEPROM_numberOfLEDs, numberOfLEDs );
+ EEPROM.write( EEPROM_mainDelayNew, mainDelayNew );
+
+ for ( uint8_t a = 0; a < NUMBER_OF_PARAM; a++ ) EEPROM.write( EEPROM_HgradBegin + a, (uint8_t)roundf( FX_Arr[ a ] ) );
+
+ EEPROMStrWrite( EEPROM_HOST, host );
+ EEPROM.write( EEPROM_FLAG, 1 ); // EEPROM changed
+ EEPROM.commit();
+
+ HTTPOut += "The current settings are saved.\n";
+
+ paramCnt++;
+ }
+
+ // reset to default parameters
+
+ if ( server.argName(i) == "reset" ){
+
+ numberOfLEDs = DEF_NUM_LEDS;
+ mainDelayNew = DEF_MAIN_DELAY;
+
+ for ( uint8_t a = 0; a < NUMBER_OF_PARAM; a++ ) FX_ArrNew[ a ] = 0;
+
+ FX_Arr[ PERIOD ] = DEF_NUM_LEDS;
+ FX_ArrNew[ PERIOD ] = DEF_NUM_LEDS;
+
+ strcpy( host, (char*)hostDefURI.c_str() );
+ WiFi.hostname( host );
+
+ server.begin();
+
+ if ( quiet > 1 ) HTTPOut += "All parameters are set to default values. Don't forget to /save them.\n";
+
+ paramCnt++;
+
+ changeColor = true;
+
+ }
+
+ // reboot
+
+ if ( server.argName(i) == "reboot" ){
+
+ FX_ArrNew[ V_GRAD_BEG ] = 0;
+ FX_ArrNew[ V_GRAD_END ] = 0;
+
+ mainDelayNew = DEF_MAIN_DELAY;
+
+ if ( quiet > 1 ) {
+
+ server.send_P(200, "text/plain", PSTR("Unit rebooting...\n") );
+ server.begin();
+ }
+
+ paramCnt++;
+
+ changeColor = true;
+ commandReboot = true;
+ }
+
+ // quiet, level of output
+
+ if ( server.argName(i) == "quiet" ) {
+
+ quiet = str2HEX( server.arg("quiet") );
+ paramCnt++;
+ } else {
+ quiet = FULL_OUTPUT;
+ }
+
+ // json on
+
+ if ( server.argName(i) == "json" ) {
+ JSONon = true;
+ paramCnt++;
+ }
+
+ if ( !paramCnt ) HTTPErr += "Parameter '" + server.argName(i) + "' not exist.\n";
+ }
+
+
+ // ---------------- host default return ------------------------
+
+ if (HTTPErr != "" ) {
+
+ // list of errors
+
+ String Err = "";
+
+ paramCnt = 0;
+
+ if (quiet > 0) {
+
+ if ( JSONon ) {
+
+ HTTPOut += "{[";
+
+ while ( 1 ){
+ Err = expld( HTTPErr, paramCnt, '\n' );
+ if ( Err != "" ) HTTPOut += "\"" + Err + "\""; else break;
+ if ( expld( HTTPErr, paramCnt+1, '\n' ) != "" ) HTTPOut += ",";
+ paramCnt ++;
+ }
+
+ HTTPOut += "]}";
+
+ } else {
+
+ while ( 1 ){
+ Err = expld( HTTPErr, paramCnt, '\n' );
+ if ( Err != "" ) HTTPOut += "Error: " + Err + "\n"; else break;
+ paramCnt ++;
+ }
+// HTTPOut += "See '/help' for details\n";
+ }
+ }
+
+ } else {
+
+ // standart output
+
+ if ( quiet > 1 && HTTPOut == "" ) {
+
+ if ( JSONon ){
+
+ HTTPOut = "{";
+ HTTPOut += "\"MAC\":\"" + String( WiFi.macAddress() ) + "\",\n";
+ HTTPOut += "\"Host\":\"" + String( host ) + "\",\n";
+ HTTPOut += "\"Timestamp\":\"" + String( timeClient.getEpochTime() ) + "\",\n";
+ HTTPOut += "\"Target_timestamp\":\"" + String( timeStamp ) + "\",\n";
+
+ HTTPOut += "\"TNumber_of_LEDs\":\"" + String( numberOfLEDs ) + "\",\n";
+
+ HTTPOut += "\"SlideX:\":\"" + String( (int)roundf( FX_ArrNew[ PHASE_X_INC ] ) ) + "\",\n";
+ HTTPOut += "\"SlideY:\":\"" + String( (int)roundf( FX_ArrNew[ PHASE_Y_INC ] ) ) + "\",\n";
+ HTTPOut += "\"SlideZ:\":\"" + String( (int)roundf( FX_ArrNew[ PHASE_Z_INC ] ) ) + "\",\n";
+
+ HTTPOut += "\"Period:\":\"" + String( (int)roundf( FX_ArrNew[ PERIOD ] ) ) + "\",\n";
+ HTTPOut += "\"Delay:\":\"" + String( mainDelayNew ) + "\",\n";
+
+ HTTPOut += "\"Gradient_HSV:\":\"";
+
+ for ( uint8_t a = 0; a < 6 ; a++ ) HTTPOut += String( printNum( FX_ArrNew[ a ], 16, ' ') );
+
+ HTTPOut += "\",\n";
+
+ HTTPOut += "}";
+
+ } else {
+
+ HTTPOut = "MAC:" + String( WiFi.macAddress() ) + "\n";
+ HTTPOut += "Host:" + String( host ) + "\n";
+ HTTPOut += "Timestamp:" + String( timeClient.getEpochTime() ) + "\n";
+ HTTPOut += "Target timestamp:" + String( timeStamp ) + "\n";
+
+ HTTPOut += "Number of LEDs:" + String( numberOfLEDs ) + "\n";
+
+ HTTPOut += "Slide X:" + String( (int)roundf( FX_ArrNew[ PHASE_X_INC ] ) ) + "\n";
+ HTTPOut += "Slide Y:" + String( (int)roundf( FX_ArrNew[ PHASE_Y_INC ] ) ) + "\n";
+ HTTPOut += "Slide Z:" + String( (int)roundf( FX_ArrNew[ PHASE_Z_INC ] ) ) + "\n";
+
+ HTTPOut += "Period:" + String( (int)roundf( FX_ArrNew[ PERIOD ] ) ) + "\n";
+ HTTPOut += "Delay:" + String( mainDelayNew ) + "\n";
+
+ HTTPOut += "Gradient (HSV):";
+
+ for ( uint8_t a = 0; a < 6 ; a++ ) HTTPOut += String( printNum( roundf( FX_ArrNew[ a ] ), 16, ' ') ) + " ";
+
+ HTTPOut += "\n";
+
+ }
+ }
+ }
+
+ server.send(200, "text/plain", HTTPOut );
+
+ HTTPOut = "";
+ HTTPErr = "";
+ JSONon = 0;
+
+ });
+
+ // ui interface TODO xml inteface
+
+ server.on("/ui", HTTP_GET, []() {
+ HTTPOut = "User interface. Not yet implemented.\n";
+ server.send(200, "text/plain", HTTPOut);
+ });
+
+ // help
+
+ server.on("/help", HTTP_GET, []() {
+
+ HTTPOut = "Interplay Medium ESP8266 Serial LED Controller. Version: " + String(VERSION) + "\n";
+ HTTPOut += "Created by Dmitry Shalnov (c) 2017. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>. \n\n";
+
+ HTTPOut += "Parameters:\n";
+
+ HTTPOut += " ?number= Number of LEDs in stripe (max MAX_NUM_LEDS)\n";
+ HTTPOut += " ?gradient= Start and end colors of gradient (hex, 24bit/color in HLS format, e.g. 001122FFEECC)\n";
+ HTTPOut += " ?period= Sine period (number of leds)\n";
+ HTTPOut += " ?delay= Delay of state changing, integer\n";
+
+ HTTPOut += " ?slidex= Step of X shift per cycle, signed integer\n";
+ HTTPOut += " ?slidey= Step of Y shift per cycle, signed integer\n";
+ HTTPOut += " ?slidez= Step of Z shift per cycle, signed integer\n";
+
+ HTTPOut += " ?time= Target timestamp (when the changes will take effect, good for synch), decimal UNIX Timestamp\n\n";
+
+ HTTPOut += " ?host= Rename the unit\n";
+ HTTPOut += " ?quiet= Level of output (0/empty = no output, 1 = OK/Error)\n\n";
+
+ HTTPOut += "Commands:\n";
+
+ HTTPOut += " ?save Save current settings\n";
+ HTTPOut += " ?reset Reset to initial settings\n";
+ HTTPOut += " ?reboot Reboot unit\n";
+
+ HTTPOut += " \n";
+ HTTPOut += " /ui Output in XML UI format\n";
+ HTTPOut += " /json Output in JSON format\n";
+ HTTPOut += " /update Wireless update of firmware (see example below)\n";
+ HTTPOut += " /help This help\n\n";
+
+ HTTPOut += "Usage: curl " + String(host) + "?<parameter>=<value>\n";
+ HTTPOut += " curl " + String(host) + "/<command>\n";
+ HTTPOut += "Examples: curl \"" + String(host) + "?gradient=ffffffddff00&period=30\" \n";
+ HTTPOut += " curl -F image=@firmware.bin " + String(host) + "/update \n";
+
+ server.sendHeader("Connection", "close");
+ server.send( 200, "text/plain", HTTPOut );
+
+ });
+
+ // firmware update
+
+ server.on("/update", HTTP_POST, []() {
+
+ server.sendHeader("Connection", "close");
+ server.send(200, "text/plain", (Update.hasError()) ? "FAIL" : "OK");
+
+ ESP.restart();
+
+ }, []() {
+
+ HTTPUpload& upload = server.upload();
+
+ if (upload.status == UPLOAD_FILE_START) {
+
+#if SERIAL == 1
+ Serial.setDebugOutput(true);
+#endif
+ WiFiUDP::stopAll();
+
+#if SERIAL == 1
+ Serial.printf("Update: %s\n", upload.filename.c_str());
+#endif
+ uint32_t maxSketchSpace = (ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000;
+
+ if (!Update.begin(maxSketchSpace)) { //start with max available size
+#if SERIAL == 1
+ Update.printError(Serial);
+#endif
+ }
+ } else if (upload.status == UPLOAD_FILE_WRITE) {
+ if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) {
+#if SERIAL == 1
+ Update.printError(Serial);
+#endif
+ }
+ } else if (upload.status == UPLOAD_FILE_END) {
+ if (Update.end(true)) { //true to set the size to the current progress
+
+ server.sendHeader("Connection", "close");
+ server.send_P(200, "text/plain", PSTR("Success. Please wait until device replace firmware and boot up...\n") );
+#if SERIAL == 1
+ Serial.printf("Update Success: %u\nRebooting....\n", upload.totalSize);
+#endif
+ } else {
+ server.sendHeader("Connection", "close");
+ server.send_P(200, "text/plain", PSTR("Something went wrong. Please reset device and try again.\n") );
+#if SERIAL == 1
+ Update.printError(Serial);
+#endif
+ }
+#if SERIAL == 1
+ Serial.setDebugOutput(false);
+#endif
+ }
+
+ yield();
+ });
+
+ } else {
+#if SERIAL == 1
+ Serial.println("WiFi Failed");
+#endif
+ }
+}
+
+// -----------------------------------------------------
+// ------------------- MAIN ----------------------------
+// -----------------------------------------------------
+
+// uint8_t rebootColorsCnt = 0;
+
+void loop(void) {
+
+ server.handleClient();
+ MDNS.update();
+
+ // reconnect
+
+ if ( WiFi.status() != WL_CONNECTED ) {
+#if SERIAL == 1
+ Serial.printf(".");
+#endif
+ if (WiFi.waitForConnectResult() == WL_CONNECTED) {
+#if SERIAL == 1
+ Serial.println("Reconnected");
+#endif
+ MDNS.begin( host );
+
+ timeClient.begin();
+ timeClient.update();
+ }
+
+ }
+
+ if ( changeColor ) {
+
+ // set float coefficents
+
+ for ( uint8_t a = 0; a < NUMBER_OF_PARAM; a++ ) {
+ FX_Coeff[ H_GRAD_BEG + a ] = (float)FX_ArrNew[ H_GRAD_BEG + a ] - FX_Arr[ H_GRAD_BEG + a ];
+ if ( abs( FX_Coeff[ H_GRAD_BEG + a ] ) > FX_CoeffMax ) FX_CoeffMax = abs( FX_Coeff[ H_GRAD_BEG + a ] );
+ }
+
+ for ( uint8_t a = 0; a < 6; a++ ) FX_Coeff[ H_GRAD_BEG + a ] = FX_Coeff[ H_GRAD_BEG + a ] / FX_CoeffMax;
+
+ changeColor = false;
+
+#if SERIAL == 1
+ Serial.printf("FX_ArrNew: ");
+ for ( uint8_t a = 0; a < NUMBER_OF_PARAM; a++ ) Serial.printf("%f ", FX_ArrNew[ a ] );
+ Serial.printf("\n");
+ Serial.printf("FX_Coeff: ");
+ for ( uint8_t a = 0; a < NUMBER_OF_PARAM; a++ ) Serial.printf("%f ", FX_Coeff[ a ] );
+ Serial.printf("\n");
+#endif
+
+ } else {
+
+ if ( ++mainDelay == mainDelayNew /* && FX_Delay != 0 */ ) {
+
+#if SERIAL == 1
+// Serial.printf("FX_Arr: ");
+// for ( uint8_t a = 0; a < NUMBER_OF_PARAM; a++ ) Serial.printf("%f ", FX_Arr[ a ] );
+// Serial.printf("\n");
+// Serial.printf("Phase: %f %f %f\n", Phase[ PHASE_X ], Phase[ PHASE_Y ], Phase[ PHASE_Z ] );
+#endif
+
+ mainDelay = 0;
+
+ // fade out and reboot
+
+ if ( commandReboot && roundf( FX_Arr[ V_GRAD_BEG ] ) == 0 && roundf( FX_Arr[ V_GRAD_END ]) == 0 ) {
+ commandReboot = false;
+#if SERIAL == 1
+ Serial.printf("Reboot......\n");
+#endif
+ ESP.restart();
+// Serial.printf("Reboot.2...\n");
+ }
+
+ if ( timeClient.getEpochTime() > timeStamp ) {
+
+ // change color
+
+ for ( uint8_t a = 0; a < NUMBER_OF_PARAM; a++ ) {
+ if ( FX_ArrNew [ a ] != roundf( FX_Arr[ a ] ) ) { FX_Arr[ a ] += FX_Coeff[ a ]; } else FX_Arr[ a ] = (float)FX_ArrNew [ a ];
+ }
+
+ // change phase
+
+ for ( uint8_t a = 0; a < 3; a++ ) {
+
+ if ( FX_Arr[ PHASE_X_INC + a ] == 0 ) {
+ // shift phase to 0 after slide. = 0
+ if ( (uint8_t)round( Phase[ a ] ) != 0 ) Phase[ a ] -= FX_Coeff[ PHASE_X_INC + a ]/100; else Phase[ a ] = 0;
+ } else {
+ // routine shift phase
+ Phase[ a ] += FX_Arr[ PHASE_X_INC + a ]/100;
+ }
+ }
+
+ }
+
+ if ( FX_Arr[ PERIOD ] == 0 ) FX_Arr[ PERIOD ] = 1;
+
+ 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 ] ) );
+ FastLED.show();
+ delay(3);
+
+ }
+ }
+}