3  * RGB(W) LED IM Dendrite module (for ESP8266)
 
   4  * Created for Interplaymediumâ„¢ project (https://interplaymedium.org)
 
   5  * Copyright Â© 2016 Dmitry Shalnov [interplaymedium.org]
 
   6  * Licensed under the Apache License, Version 2.0
 
  11 #include <ESP8266WiFi.h>
 
  12 #include <WiFiClient.h>
 
  13 #include <ESP8266WebServer.h>
 
  14 #include <ESP8266mDNS.h>
 
  17 #define VERSION         "0.1.4"
 
  19 #define EEPROM_STR_MAX_LEN      16
 
  20 #define EEPROM_OTHER_MAX_LEN    8
 
  23 #define EEPROM_STR_FLAG         17      // watch overlap: EEPROM_STR_MAX_LEN
 
  28 #define EEPROM_ROT_H            22
 
  29 #define EEPROM_ROT_L            23
 
  31 // -------------------- PWM settings  ----------------------------------------------------
 
  34         #include "pwm.h"        // Includes of Expressif SDK
 
  42 #define PWM_CHANNELS    4
 
  45 #define PWM_PERIOD      5000 // Period of PWM frequency, SDK default: 5000 -> * 200ns ^= 1 kHz
 
  47 unsigned int CIEL8[ PWMSTEPS ];
 
  49 int R=0, G=0, B=0, W=0;
 
  50 unsigned int Rnew=0, Gnew=0, Bnew=0, Wnew=0;
 
  51 unsigned int rotateDelay = 0;
 
  52 int signR = 1, signG = 1, signB = 1, signW = 1;
 
  53 unsigned int eTimer = 0;
 
  55 uint32 pwm_duty_init[PWM_CHANNELS]; // PWM initial duty: OFF by default
 
  57 uint32 io_info[PWM_CHANNELS][3] = {
 
  61 //      {PERIPHS_IO_MUX_GPIO5_U, FUNC_GPIO5,     5}, // D1
 
  62 //      {PERIPHS_IO_MUX_GPIO4_U, FUNC_GPIO4,     4}, // D2
 
  63         {PERIPHS_IO_MUX_GPIO0_U, FUNC_GPIO0,     LED1}, // D3   0
 
  64         {PERIPHS_IO_MUX_GPIO2_U, FUNC_GPIO2,     LED2}, // D4   2
 
  66         {PERIPHS_IO_MUX_U0RXD_U, FUNC_GPIO3,     LED3}, //      RX      3
 
  67         {PERIPHS_IO_MUX_U0TXD_U, FUNC_GPIO1,     LED4}, //      TX      1
 
  69 //      {PERIPHS_IO_MUX_MTMS_U, FUNC_GPIO14, 14}, // D5
 
  70 //      {PERIPHS_IO_MUX_MTDI_U, FUNC_GPIO12, 12}, // D6
 
  71 //      {PERIPHS_IO_MUX_MTCK_U, FUNC_GPIO13, 13}, // D7
 
  72 //      {PERIPHS_IO_MUX_MTDO_U, FUNC_GPIO15 ,15}, // D8
 
  74 //      D0 - not have PWM :-(                16
 
  77 // ------------------- server settings --------------------------------------------------
 
  79 #define HOST_DEFAULT                    "im_rgb5"
 
  81 char host[ EEPROM_STR_MAX_LEN ];
 
  83 const char* ssid        = IM_WIFI_SSID;
 
  84 const char* password    = IM_WIFI_PASS;
 
  86 ESP8266WebServer        server(80);
 
  90 // ----------------- change current R G B (W) to another RGBW set ----------------------
 
  92 void changeRGBto( int newR, int newG, int newB, int newW ){
 
  94         float stepR, stepG, stepB, stepW;
 
  99 //      R = EEPROM.read( 0 );
 
 100 //      G = EEPROM.read( 1 );
 
 101 //      B = EEPROM.read( 2 );
 
 102 //      W = EEPROM.read( 3 );
 
 104         stepR = (newR - R) / (float)PWMSTEPS;
 
 105         stepG = (newG - G) / (float)PWMSTEPS;
 
 106         stepB = (newB - B) / (float)PWMSTEPS;
 
 107         stepW = (newW - W) / (float)PWMSTEPS;
 
 109         for ( unsigned int a = 0; a < PWMSTEPS; a++ ){
 
 111                 R2 = R + round( stepR * (float)a );
 
 112                 G2 = G + round( stepG * (float)a );
 
 113                 B2 = B + round( stepB * (float)a );
 
 114                 W2 = W + round( stepW * (float)a );
 
 116                 pwm_set_duty( CIEL8[R2], 0 );
 
 117                 pwm_set_duty( CIEL8[G2], 1 );
 
 118                 pwm_set_duty( CIEL8[B2], 2 );
 
 119                 pwm_set_duty( CIEL8[W2], 3 );
 
 121                 pwm_start(); // commit
 
 123                 delay( 500 / PWMSTEPS );
 
 127 // ----------- explode for selected substring -----------
 
 129 String expld( String str, unsigned int numb, char delimiter ){
 
 131         unsigned int cnt = 0, a = 0, p2 = 0, p1 = 0, lng = 0;
 
 135         for ( a = 0; a < lng; a++ ){
 
 136                 if ( str.charAt( a ) == delimiter ) { 
 
 139                         if ( cnt == numb ) break;
 
 149         if ( numb > 0 ) p2 ++;
 
 151         return str.substring(p2, p1);
 
 155 unsigned char URIHasArg( String str, String arg ){
 
 158         unsigned char a = 0, delimiterCnt = 0, lng = str.length();
 
 160         for ( a = 0; a < lng; a++ ){
 
 161                 if ( str.charAt( a ) == '/' ) delimiterCnt++;
 
 164         for ( a=0; a < 10; a++ ){
 
 165                 if ( arg.equals( expld( str, a, '/' ) ) ) return a;     
 
 170 // ---------------- EEPROM String r/w -------------------
 
 172 void EEPROMStrRead( unsigned char addr, char * str ){
 
 173         for (unsigned char a = addr; a <  EEPROM_STR_MAX_LEN; a++ ){
 
 174                 str[ a ] = EEPROM.read( a );
 
 175                 if (str[ a ] == 0) break;
 
 179 void EEPROMStrWrite( unsigned char addr, char * str ){
 
 181         for (a = addr; a <  EEPROM_STR_MAX_LEN; a++ ){
 
 182                 EEPROM.write( a, str[ a ] );
 
 183                 if (str[ a ] == 0) break;
 
 185         EEPROM.write( a, 0 );
 
 188 // ---------------- misc ---------------------------------
 
 190 int str2HEX(const String str) {    
 
 191     return strtol( str.c_str(), 0, 16 );
 
 194 void blink( unsigned char times ){
 
 195         for (unsigned char a = 0; a < times; a++ ){
 
 196                 digitalWrite(LED4, HIGH);
 
 198                 digitalWrite(LED4, LOW);
 
 203 // --------------- Init --------------------------------
 
 207         HTTPresp.reserve(800); // lenght of Help message generally
 
 209         // calculate lookup array 
 
 211         for (unsigned int a = 1; a < PWMSTEPS; a++) CIEL8[ a ] = round( pow( PWM_PERIOD, (double)a / (PWMSTEPS-1) ) ); // calculate exponential lookup array for PWM
 
 216         pinMode(LED1, OUTPUT);
 
 217         pinMode(LED2, OUTPUT);
 
 218         pinMode(LED3, OUTPUT);
 
 219         pinMode(LED4, OUTPUT);
 
 221         digitalWrite(LED1, LOW);
 
 222         digitalWrite(LED2, LOW);
 
 223         digitalWrite(LED3, LOW);
 
 224         digitalWrite(LED4, LOW);
 
 228         for (uint8_t channel = 0; channel < PWM_CHANNELS; channel++) pwm_duty_init[channel] = 0;        // Initial duty -> all off
 
 229         uint32_t period = PWM_PERIOD;
 
 230         pwm_init(period, pwm_duty_init, PWM_CHANNELS, io_info);
 
 235         Serial.begin(115200);
 
 237         Serial.println("Booting Sketch...0");
 
 242 //      deviceURI.reserve(7);
 
 243 //      deviceURI = "im" + WiFi.macAddress().substring(12, 14) + WiFi.macAddress().substring(15, 17); // 00:00:00:00:00:00
 
 245         EEPROM.begin( EEPROM_STR_MAX_LEN + EEPROM_OTHER_MAX_LEN );
 
 247         if ( EEPROM.read( EEPROM_STR_FLAG ) == 1 ){
 
 248                 EEPROMStrRead( EEPROM_STR, host ); 
 
 250                 strcpy( host, HOST_DEFAULT ); // default URI and host name
 
 253         WiFi.hostname( host );
 
 255 //      WiFi.softAP(APssid, APpassword);
 
 257 //      WiFi.mode(WIFI_AP);
 
 258 //      WiFi.mode(WIFI_AP_STA); 
 
 260         WiFi.begin(ssid, password);
 
 262         if (WiFi.waitForConnectResult() == WL_CONNECTED) {
 
 264 //              MDNS.begin( deviceURI.c_str() );
 
 269                 server.onNotFound( []() {
 
 270                         server.sendHeader("Connection", "close");
 
 272                         unsigned int RGBW = 0xff;
 
 276                         // parse plain URI arguments TODO: not sure whether we need it, check Plan 9 specifications
 
 278                         param = server.arg("rgbw");
 
 280                                 param = URIHasArg( server.uri(), "rgbw" );
 
 285                         if ( server.hasArg("rgbw") ){ // || param != 0
 
 287                                 param = server.arg("rgbw");
 
 289                                 Rnew = str2HEX( param.substring(0, 2) );
 
 290                                 Gnew = str2HEX( param.substring(2, 4) );
 
 291                                 Bnew = str2HEX( param.substring(4, 6) );
 
 292                                 Wnew = str2HEX( param.substring(6, 8) );
 
 294                                 changeRGBto( Rnew, Gnew, Bnew, Wnew );
 
 304                         if ( server.hasArg("rotate") ){
 
 305                                 rotateDelay = str2HEX( server.arg("rotate") );
 
 310                         if ( server.hasArg("rename") ) {
 
 311                                 EEPROMStrWrite( EEPROM_STR, (char *)server.arg("rename").c_str() );
 
 312                                 EEPROM.write( EEPROM_STR_FLAG, 1 );
 
 315                                 server.sendHeader("Connection", "close");
 
 316                                 server.send_P(200, "text/plain", PSTR("Done. System rebooting...\n") );
 
 324                         HTTPresp = "host: " + String(host) + ".lan" + "\n";
 
 325                         HTTPresp += "URI: " + server.uri() + "\n";
 
 326 //                      HTTPresp += "Arg RGBW: " + String( URIHasArg( server.uri(), "test2" ) ) + "\n";         // URI contains /test2/
 
 327 //                      HTTPresp += "Arg RGBW: " + server.arg("aaaa") + "\n";                                   // GET or POST params has "aaaa"
 
 328                         HTTPresp += "rotate: " + String(rotateDelay) + "\n";
 
 329                         HTTPresp += "rgbw: " + String(R) + " " + String(G) + " " + String(B) + " " + String(W) + "\n";
 
 331                         server.send(200, "text/plain", HTTPresp);
 
 334                 // test EEPROM string
 
 336                 server.on("/eeprom", HTTP_GET, []() {
 
 338                         char * testStr2 = "asdfghjkl12345";
 
 340                         server.setContentLength(CONTENT_LENGTH_UNKNOWN);
 
 341                         server.send(200, "text/plain", "");
 
 343                         EEPROMStrRead( EEPROM_STR, testStr2 );
 
 345                         server.sendContent( testStr2 );
 
 346                         server.sendContent( "\n" );
 
 351                 server.on("/help", HTTP_GET, []() {
 
 353                         HTTPresp =      "Interplay Medium ESP8266 LED RGB(W) PWM Controller. Version: " + String(VERSION) + "\n";
 
 354                         HTTPresp +=     "Written by Dmitry Shalnov (c) 2017. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>. \n\n";
 
 356                         HTTPresp +=     " rgbw          Red Green Blue and White components in hexadecimal form (e.g. ff00ff00)\n";
 
 357                         HTTPresp +=     " rotate        Delay of components rotation FX in hexadecimal (e.g. ffff), set 0 to stop\n";
 
 358                         HTTPresp +=     " update        Wireless update of firmware (see example below)\n";
 
 359                         HTTPresp +=     " help          Send this help\n\n";
 
 361                         HTTPresp +=     "Usage:         curl http://" + String(host) + ".lan [--data \"rgbw=<hex RGBW>\"] [--data \"rotate=<hex delay, 0 = stop >\"] \n";
 
 362                         HTTPresp +=     "               curl http://" + String(host) + ".lan[/rgbw/<hex RGBW>][/rotate/<hex delay>] \n";
 
 363                         HTTPresp +=     "Examples:      curl http://" + String(host) + ".lan/?rgbw=00ff00ff&rotate=ff \n";
 
 364                         HTTPresp +=     "               curl -F image=@RGBW_Controller.bin " + String(host) + ".lan/update \n";
 
 366                         server.sendHeader("Connection", "close");
 
 367                         server.send( 200, "text/plain", HTTPresp );
 
 373                 server.on("/update", HTTP_POST, []() {
 
 375                         server.sendHeader("Connection", "close");
 
 376                         server.send(200, "text/plain", (Update.hasError()) ? "FAIL" : "OK");
 
 382                                 HTTPUpload& upload = server.upload();
 
 384                                 if (upload.status == UPLOAD_FILE_START) {
 
 390                                         Serial.setDebugOutput(true);
 
 395                                         Serial.printf("Update: %s\n", upload.filename.c_str());
 
 397                                         uint32_t maxSketchSpace = (ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000;
 
 399                                         if (!Update.begin(maxSketchSpace)) { //start with max available size
 
 401                                                 Update.printError(Serial);
 
 404                                 } else if (upload.status == UPLOAD_FILE_WRITE) {
 
 405                                         if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) {
 
 407                                                 Update.printError(Serial);
 
 410                                 } else if (upload.status == UPLOAD_FILE_END) {
 
 411                                         if (Update.end(true)) { //true to set the size to the current progress
 
 413                                                 server.sendHeader("Connection", "close");
 
 414                                                 server.send_P(200, "text/plain", PSTR("Success. Please wait until device replace firmware and boot up...\n") );
 
 416                                                 Serial.printf("Update Success: %u\nRebooting....\n", upload.totalSize); 
 
 419                                                 server.sendHeader("Connection", "close");
 
 420                                                 server.send_P(200, "text/plain", PSTR("Something went wrong. Please reset device and try again.\n") );
 
 422                                                 Update.printError(Serial);
 
 426                                         Serial.setDebugOutput(false);
 
 436                 MDNS.addService("http", "tcp", 80);
 
 438                 Serial.printf("Ready! Open http://%s.local in your browser\n", host);
 
 443                 Serial.println("WiFi Failed");
 
 450         server.handleClient();
 
 455         if ( eTimer >= rotateDelay && rotateDelay != 0 ) {
 
 464                 if ( R >= PWMSTEPS-1 ) { signR = -1; R = PWMSTEPS-1; }
 
 465                 if ( G >= PWMSTEPS-1 ) { signG = -1; G = PWMSTEPS-1; }
 
 466                 if ( B >= PWMSTEPS-1 ) { signB = -1; B = PWMSTEPS-1; }
 
 467                 if ( W >= PWMSTEPS-1 ) { signW = -1; W = PWMSTEPS-1; }
 
 469                 if ( R < 2 ) { signR = 1; R = 2; }
 
 470                 if ( G < 2 ) { signG = 1; G = 2; }
 
 471                 if ( B < 2 ) { signB = 1; B = 2; }
 
 472                 if ( W < 2 ) { signW = 1; W = 2; }
 
 474                 pwm_set_duty( CIEL8[R], 0 );
 
 475                 pwm_set_duty( CIEL8[G], 1 );
 
 476                 pwm_set_duty( CIEL8[B], 2 );
 
 477                 pwm_set_duty( CIEL8[W], 3 );
 
 479                 pwm_start(); // commit