Introduction: EPS8266 WEB LED Control + JSON + SPIFFS

About: esp8266 projects

Am Ende des LED Projekts werden 16 LED mit einem I2C Treiber über eine Weboberfläche per WLAN angesteuert.

In der finalen Oberfläche werden die LED über einen Raumplan gesteuert und visualisiert.

.

Im ersten Teil hatte ich die Hardware und eine kleine Startseite für die LEDs erstellt.

Im zweiten Teil habe ich eine Weboberfläche mit Raumplan für die einzelnen LEDs erstellt, die LED PWM Werte werden mittels JSON (JavaScript) zurückgelesen und es gibt eine Uploadmöglichkeit für Grafiken, CSS, JS und die html Seite.

Es muss nun nicht mehr der Sketch für Anpassungen an der Weboberfläche neu hochgeladen werden und es erfolgt kein reload der Seite beim Wechsel einer LED Einstellung. =)

Step 1: Erwiterungen in CaptivePortalAdvanced.ino

In der CaptivePortalAdvanced.ino Datei ist SPIFFS für das file handling und der Servereintrag ./readLED hinzugekommen.

#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <ESP8266WebServer.h>
#include <DNSServer.h>
#include <ESP8266mDNS.h>
#include <EEPROM.h>
#include <Wire.h>

/*
   This example serves a "hello world" on a WLAN and a SoftAP at the same time.
   The SoftAP allow you to configure WLAN parameters at run time. They are not setup in the sketch but saved on EEPROM.

   Connect your computer or cell phone to wifi network ESP_ap with password 12345678. A popup may appear and it allow you to go to WLAN config. If it does not then navigate to <a href="http://192.168.4.1/wifi"> <a href="http://192.168.4.1/wifi" rel="nofollow"> http://192.168.4.1/wifi </a> </a> and config it there.
   Then wait for the module to connect to your wifi and take note of the WLAN IP it got. Then you can disconnect from ESP_ap and return to your regular WLAN.

   Now the ESP8266 is in your network. You can reach it through <a href="http://192.168.x.x/"> <a href="http://192.168.4.1/wifi" rel="nofollow"> http://192.168.4.1/wifi </a> </a> (the IP you took note of) or maybe at <a href="http://esp8266.local"> <a href="http://192.168.4.1/wifi" rel="nofollow"> http://192.168.4.1/wifi </a> </a> too.

   This is a captive portal because through the softAP it will redirect any http request to <a href="http://192.168.4.1/"> <a href="http://192.168.4.1/wifi" rel="nofollow"> http://192.168.4.1/wifi </a> </a>
*/

//////////////////////////// PINs /////////////////////////////////

// LED
#define BLINKPIN D8       // Status LED indicates a wifi / user interaction 
#define BLINKPIN_TOGGLE() (digitalWrite(BLINKPIN, !digitalRead(BLINKPIN)))   
#define BLINKPIN_ON()     (digitalWrite(BLINKPIN, HIGH))
#define BLINKPIN_OFF()    (digitalWrite(BLINKPIN, LOW))

//I2C TLC59116
#define TLC59116_SCL D1
#define TLC59116_SDA D2 


//////////////////////////// TLC59116  /////////////////////////////////
// Demo für I2C-LED-Treiber mit TLC59116
// ELV-AG Leer
// letzte Änderung 21.4.2011
// I2C -Adresse = 0C hex (alle Jumper A0 bis A3 offen)
// Hinweis zur Slaveadresse:
// Da bei der "Wire"-Funktion entfällt das letzte Bit, das für Lesen oder Schreiben (R/W) steht.
// Die Adresse hat somit nur noch 7 Bit, und sieht am Beispiel von C0hex so aus : 1100000b (60hex)
// 8Bit : 11000000 = C0h
// 7Bit : 1100000  = 60h

#define BRIGHTNESS 32  // init max. 255
    int BRIGHTNESS_var = 8;
    int BRIGHTNESS_Array[16]={64,64,64,64, 64,64,64,64, 64,64,64,64, 64,64,64,64}; 
 String BRIGHTNESS_string = "NULL";  //html <=> js

//////////////////////////// SPIFFS / fsUploadFile /////////////////////////////////

#include <FS.h>  
    unsigned long Speicherbelegung = 0;
    File fsUploadFile;              // a File object to temporarily store the received file
    String getContentType(String filename); // convert the file extension to the MIME type
    bool handleFileRead(String path);       // send the right file to the client (if it exists)
    void handleFileUpload();                // upload a new file to the SPIFFS

    String Page404upload = "<html><head></head><body><form method='post' enctype='multipart/form-data'><input type='file' name='name'><input class='button' type='submit' value='Upload'></form></body></html>";    



/* Set these to your desired softAP credentials. They are not configurable at runtime */
#ifndef APSSID
#define APSSID "ESP_LED_TLC59116"
#define APPSK  "12345678"
#endif

const char *softAP_ssid = APSSID;
const char *softAP_password = APPSK;

/* hostname for mDNS. Should work at least on windows. Try <a href="http://esp8266.local"> <a href="http://esp8266.local" rel="nofollow"> http://192.168.4.1/wifi </a> </a> */
const char *myHostname = "esp8266";

/* Don't set this wifi credentials. They are configurated at runtime and stored on EEPROM */
char ssid[32] = "";
char password[32] = "";

// DNS server
const byte DNS_PORT = 53;
DNSServer dnsServer;

// Web server
ESP8266WebServer server(80);

/* Soft AP network parameters */
IPAddress apIP(172, 217, 28, 1);
IPAddress netMsk(255, 255, 255, 0);


/** Should I connect to WLAN asap? */
boolean connect;

/** Last time I tried to connect to WLAN */
unsigned long lastConnectTry = 0;

/** Current WLAN status */
unsigned int status = WL_IDLE_STATUS;

void setup() {
  
  pinMode(BLINKPIN, OUTPUT);  // Set pin as output
  BLINKPIN_ON();
  
  delay(1000);
  Serial.begin(9600);
  Serial.println();
  Serial.println("Configuring access point...");
  /* You can remove the password parameter if you want the AP to be open. */
  WiFi.softAPConfig(apIP, apIP, netMsk);
  WiFi.softAP(softAP_ssid, softAP_password);
  delay(500); // Without delay I've seen the IP address blank
  Serial.print("AP IP address: ");
  Serial.println(WiFi.softAPIP());

  /* Setup the DNS server redirecting all the domains to the apIP */
  dnsServer.setErrorReplyCode(DNSReplyCode::NoError);
  dnsServer.start(DNS_PORT, "*", apIP);

  /*  Init SPIFFS  */
  if (!SPIFFS.begin()) { 
    Serial.println("SPIFFS nicht initialisiert! Stop!");
    while (1) yield();
  }
  else {
      Dir dir = SPIFFS.openDir("/");
    while (dir.next()) {
      String fileName = dir.fileName();
      size_t fileSize = dir.fileSize();
      Speicherbelegung += fileSize;
      Serial.printf("FS File: %s, size: %s\n", fileName.c_str(), formatBytes(fileSize).c_str());
    }
    Serial.printf("\n");
    Serial.printf("Speicherbelegung:  %s\n", formatBytes(Speicherbelegung).c_str());
    Serial.printf("\n");
  }

     server.serveStatic("/",    SPIFFS, "/index.html");
     server.serveStatic("/img", SPIFFS, "/img");
     server.serveStatic("/js",  SPIFFS, "/js");
     server.serveStatic("/css", SPIFFS, "/css");  
     server.serveStatic("/dat", SPIFFS, "/data");

  /* Setup web page: file uploads */ 
  server.on("/upload", HTTP_GET, []() {                 // if the client requests the upload page
    if (!handleFileRead("/upload.html"))                // send it if it exists        
     server.send(404, "text/html", Page404upload);  
  }
  );

  server.on("/upload", HTTP_POST,                       // if the client posts to the upload page
    [](){ server.send(200); },                          // Send status 200 (OK) to tell the client we are ready to receive
    handleFileUpload                                    // Receive and save the file
  );

  /* Setup web pages: LED on/off + js LED status*/ 
  server.on("/LEDswitch", handleLED_switch);
  server.on("/readLED", handleStatusLED);
  
  /* Setup web pages: I2C info on COM port */
  server.on("/i2c", handleStatusI2C);
    
  /* Setup web pages: root, wifi config pages, SO captive portal detectors and not found. */
  //server.on("/", handleRoot); //old
  server.on("/",HTTP_GET,handleRoot);   // Files SPIFFS  
  server.on("/wifi", handleWifi);
  server.on("/wifisave", handleWifiSave);
  server.on("/generate_204", handleRoot);  //Android captive portal. Maybe not needed. Might be handled by notFound handler.
  server.on("/fwlink", handleRoot);  //Microsoft captive portal. Maybe not needed. Might be handled by notFound handler.
  server.onNotFound(handleNotFound);
  server.begin(); // Web server start
  Serial.println("HTTP server started");
  loadCredentials(); // Load WLAN credentials from network
  connect = strlen(ssid) > 0; // Request WLAN connect if there is a SSID

  init_TLC59116();
  Set_LED_ALL(BRIGHTNESS);
  
  BLINKPIN_OFF();  
}

void connectWifi() {
  Serial.println("Connecting as wifi client...");
  WiFi.disconnect();
  WiFi.begin(ssid, password);
  int connRes = WiFi.waitForConnectResult();
  Serial.print("connRes: ");
  Serial.println(connRes);
}

void loop() {
  if (connect) {
    Serial.println("Connect requested");
    connect = false;
    connectWifi();
    lastConnectTry = millis();
  }
  {
    unsigned int s = WiFi.status();
    if (s == 0 && millis() > (lastConnectTry + 60000)) {
      /* If WLAN disconnected and idle try to connect */
      /* Don't set retry time too low as retry interfere the softAP operation */
      connect = true;
    }
    if (status != s) { // WLAN status change
      Serial.print("Status: ");
      Serial.println(s);
      status = s;
      if (s == WL_CONNECTED) {
        /* Just connected to WLAN */
        Serial.println("");
        Serial.print("Connected to ");
        Serial.println(ssid);
        Serial.print("IP address: ");
        Serial.println(WiFi.localIP());

        // Setup MDNS responder
        if (!MDNS.begin(myHostname)) {
          Serial.println("Error setting up MDNS responder!");
        } else {
          Serial.println("mDNS responder started");
          // Add service to MDNS-SD
          MDNS.addService("http", "tcp", 80);
        }
      } else if (s == WL_NO_SSID_AVAIL) {
        WiFi.disconnect();
      }
    }
    if (s == WL_CONNECTED) {
      MDNS.update();
    }
  }
  // Do work:
  //DNS
  dnsServer.processNextRequest();
  //HTTP
  server.handleClient();
}

Step 2: Erwiterungen in Credentials.ino

keine

/** Load WLAN credentials from EEPROM */
void loadCredentials() {
  EEPROM.begin(512);
  EEPROM.get(0, ssid);
  EEPROM.get(0 + sizeof(ssid), password);
  char ok[2 + 1];
  EEPROM.get(0 + sizeof(ssid) + sizeof(password), ok);
  EEPROM.end();
  if (String(ok) != String("OK")) {
    ssid[0] = 0;
    password[0] = 0;
  }
  Serial.println("Recovered credentials:");
  Serial.println(ssid);
  Serial.println(strlen(password) > 0 ? "********" : "");
}

/** Store WLAN credentials to EEPROM */
void saveCredentials() {
  EEPROM.begin(512);
  EEPROM.put(0, ssid);
  EEPROM.put(0 + sizeof(ssid), password);
  char ok[2 + 1] = "OK";
  EEPROM.put(0 + sizeof(ssid) + sizeof(password), ok);
  EEPROM.commit();
  EEPROM.end();
}

Step 3: Erwiterungen in HandleHttp.ino

Das alte handleRoot() wurde vereinfacht.

Es sind (SPIFFS) Funktionen für die Bearbeitung von Dateien hinzugekommen. (Upload und html Server)

Im LED Funktionsbereich ist die Funktionen handleFileRead() für Rückgabe von LED PWM ergänzt worden.

/** Handle root or redirect to captive portal */
//void handleRoot() {
//  if (captivePortal()) { // If caprive portal redirect instead of displaying the page.
//    return;
//  }
//  server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate");
//  server.sendHeader("Pragma", "no-cache");
//  server.sendHeader("Expires", "-1");
//
//  String Page;
//  Page += F(
//            "<html><head></head><body>"
//            "<h1>HELLO WORLD!!</h1>");
//  if (server.client().localIP() == apIP) {
//    Page += String(F("<p>You are connected through the soft AP: ")) + softAP_ssid + F("</p>");
//  } else {
//    Page += String(F("<p>You are connected through the wifi network: ")) + ssid + F("</p>");
//  }
///** Handle LED Control*/
//
//  Page +=
//    String(F(
//             "\r\n<br />"
//             "<table><tr><th align='left'>LED Control</th></tr>"
//             "<tr><td>"));
//  Page += String(F("\r\n<tr><td><a href='/LEDswitch'> all LED </a></td></tr>")); 
//            
//    for (int i = 1; i < 17; i++) {
//      Page += String(F("\r\n<tr><td><a href='/LEDswitch?LED=")) + i + (F("'>LED")) + i + (F("</a></td></tr>"));
//    }
//
//    Page +=
//    String(F(
//      "</td></tr>"
//      "</table>"
//      "\r\n<br />"));
//
//  Page += F(
//            "<p>You may want to test the <a href='/i2c'>I2C status</a>. (COM port)</p>");
//              
//  Page += F(
//            "<p>You may want to <a href='/wifi'>config the wifi connection</a>.</p>"
//            "</body></html>");
//
//  server.send(200, "text/html", Page);
//}

/** Handle root or redirect to captive portal (Files SPIFFS)*/
void handleRoot() {
  Serial.printf("handleRoot");
  if (!handleFileRead(server.uri()))  server.send(404, "text/plain", "FileNotFound");
}

/** Redirect to captive portal if we got a request for another domain. Return true in that case so the page handler do not try to handle the request again. */
boolean captivePortal() {
  if (!isIp(server.hostHeader()) && server.hostHeader() != (String(myHostname) + ".local")) {
   // Serial.println("Request redirected to captive portal");
    server.sendHeader("Location", String("http://") + toStringIp(server.client().localIP()), true);
    server.send(302, "text/plain", "");   // Empty content inhibits Content-length header so we have to close the socket ourselves.
    server.client().stop(); // Stop is needed because we sent no content length
    return true;
  }
  return false;
}

/** Wifi config page handler */
void handleWifi() {
  server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate");
  server.sendHeader("Pragma", "no-cache");
  server.sendHeader("Expires", "-1");

  String Page;
  Page += F(
            "<html><head></head><body>"
            "<h1>Wifi config</h1>");
  if (server.client().localIP() == apIP) {
    Page += String(F("<p>You are connected through the soft AP: ")) + softAP_ssid + F("</p>");
  } else {
    Page += String(F("<p>You are connected through the wifi network: ")) + ssid + F("</p>");
  }
  Page +=
    String(F(
             "\r\n<br />"
             "<table><tr><th align='left'>SoftAP config</th></tr>"
             "<tr><td>SSID ")) +
    String(softAP_ssid) +
    F("</td></tr>"
      "<tr><td>IP ") +
    toStringIp(WiFi.softAPIP()) +
    F("</td></tr>"
      "</table>"
      "\r\n<br />"
      "<table><tr><th align='left'>WLAN config</th></tr>"
      "<tr><td>SSID ") +
    String(ssid) +
    F("</td></tr>"
      "<tr><td>IP ") +
    toStringIp(WiFi.localIP()) +
    F("</td></tr>"
      "</table>"
      "\r\n<br />"
      "<table><tr><th align='left'>WLAN list (refresh if any missing)</th></tr>");
  Serial.println("scan start");
  int n = WiFi.scanNetworks();
  Serial.println("scan done");
  if (n > 0) {
    for (int i = 0; i < n; i++) {
      Page += String(F("\r\n<tr><td>SSID ")) + WiFi.SSID(i) + ((WiFi.encryptionType(i) == ENC_TYPE_NONE) ? F(" ") : F(" *")) + F(" (") + WiFi.RSSI(i) + F(")</td></tr>");
    }
  } else {
    Page += F("<tr><td>No WLAN found</td></tr>");
  }
  Page += F(
            "</table>"
            "\r\n<br /><form method='POST' action='wifisave'><h4>Connect to network:</h4>"
            "<input type='text' placeholder='network' name='n'/>"
            "<br /><input type='password' placeholder='password' name='p'/>"
            "<br /><input type='submit' value='Connect/Disconnect'/></form>"
            "<p>You may want to <a href='/'>return to the home page</a>.</p>"
            "</body></html>");
  server.send(200, "text/html", Page);
  server.client().stop(); // Stop is needed because we sent no content length
}

/** Handle the WLAN save form and redirect to WLAN config page again */
void handleWifiSave() {
  Serial.println("wifi save");
  server.arg("n").toCharArray(ssid, sizeof(ssid) - 1);
  server.arg("p").toCharArray(password, sizeof(password) - 1);
  server.sendHeader("Location", "wifi", true);
  server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate");
  server.sendHeader("Pragma", "no-cache");
  server.sendHeader("Expires", "-1");
  server.send(302, "text/plain", "");    // Empty content inhibits Content-length header so we have to close the socket ourselves.
  server.client().stop(); // Stop is needed because we sent no content length
  saveCredentials();
  connect = strlen(ssid) > 0; // Request WLAN connect with new credentials if there is a SSID
}

void handleNotFound() {
  if (captivePortal()) { // If caprive portal redirect instead of displaying the error page.
    return;
  }
  String message = F("File Not Found\n\n");
  message += F("URI: ");
  message += server.uri();
  message += F("\nMethod: ");
  message += (server.method() == HTTP_GET) ? "GET" : "POST";
  message += F("\nArguments: ");
  message += server.args();
  message += F("\n");

  for (uint8_t i = 0; i < server.args(); i++) {
    message += String(F(" ")) + server.argName(i) + F(": ") + server.arg(i) + F("\n");
  }
  server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate");
  server.sendHeader("Pragma", "no-cache");
  server.sendHeader("Expires", "-1");
  server.send(404, "text/plain", message);
}


/** Handle files SPIFFS */
String getContentType(String filename) { // convert the file extension to the MIME type
  if (filename.endsWith(".html")) return "text/html";
  else if (filename.endsWith(".css")) return "text/css";
  else if (filename.endsWith(".js")) return "application/javascript";
  else if (filename.endsWith(".ico")) return "image/x-icon";
  else if (filename.endsWith(".jpg")) return "image/jpeg";
  else if (filename.endsWith(".gif")) return "image/gif";  
  return "text/plain";
}

bool handleFileRead(String path) { // send the right file to the client (if it exists)
  Serial.println("handleFileRead");
  if (path.endsWith("/")) path += "index.html";          // If a folder is requested, send the index file
  
  String contentType = getContentType(path);             // Get the MIME type
  String pathWithGz = path + ".gz";
  if (SPIFFS.exists(pathWithGz) || SPIFFS.exists(path)) { // If the file exists, either as a compressed archive, or normal
    if (SPIFFS.exists(pathWithGz))                         // If there's a compressed version available
      path += ".gz";                                       // Use the compressed verion
    File file = SPIFFS.open(path, "r");                    // Open the file
    size_t sent = server.streamFile(file, contentType);    // Send it to the client
    file.close();                                          // Close the file again
    Serial.println(String("\tSent file: ") + path);
    return true;
  }
  Serial.println(String("\tFile Not Found: ") + path);   // If the file doesn't exist, return false
  return false;
}

void handleFileUpload(){ // upload a new file to the SPIFFS
 
 String Page;
  Page += F(
            "<html><head>"
            "</head><body>"
            "<h1>FileUpload</h1>"); 
  
  HTTPUpload& upload = server.upload();
  
  if(upload.status == UPLOAD_FILE_START){
    
    String filename = upload.filename;
 // if(!filename.startsWith("/")) filename = "/"+filename;
    
    Serial.print("handleFileUpload Name: "); 
    Serial.println(filename);
    Page += String(F("<p>handleFileUpload Name: ")) + filename + F("</p>");    
/*                
  // Opens a file. path should be an absolute path starting with a slash (e.g. /dir/filename.txt). 
  // https://circuits4you.com/2018/01/31/example-of-esp8266-flash-file-system-spiffs/
*/  
     if (filename.endsWith(".jpg")) filename = "/img/"+filename; 
     else if (filename.endsWith(".gif")) filename = "/img/"+filename;
     else if (filename.endsWith(".png")) filename = "/img/"+filename;     
     else if (filename.endsWith(".css")) filename = "/css/"+filename;  
     else if (filename.endsWith(".js" ))  filename = "/js/"+filename;
          
     if (!filename.startsWith("/")) filename = "/"+filename;
        
     fsUploadFile = SPIFFS.open(filename, "w"); // Open the file for writing in SPIFFS (create if it doesn't exist)´     
    
     filename = String();
  }
  
  else if(upload.status == UPLOAD_FILE_WRITE){
    if(fsUploadFile)
      fsUploadFile.write(upload.buf, upload.currentSize); // Write the received bytes to the file
  }
   
  else if(upload.status == UPLOAD_FILE_END){
    if(fsUploadFile) {                                    // If the file was successfully created
      fsUploadFile.close();                               // Close the file again 
      Serial.print("handleFileUpload Size: "); 
      Serial.println(formatBytes(upload.totalSize).c_str());
      Serial.println("-------------------------\n");

      Page += String(F("<p>handleFileUpload Size:  ")) + formatBytes(upload.totalSize).c_str() + F("</p>");  
      Page += F(
            "<p>[ <a href='/'>RoomInfo Info</a> ]  [ <a href='/wifi'>wifi Info</a> ]  [<a href='/upload'> upload </a>]</p>"
            "</body></html>");       
   
      server.send(303, "text/html", Page);


 // Terminal
      {
      Dir dir = SPIFFS.openDir("/");
        while (dir.next()) {
        String fileName = dir.fileName();
        size_t fileSize = dir.fileSize();
        Speicherbelegung += fileSize;
        Serial.printf("FS File: %s, size: %s\n", fileName.c_str(), formatBytes(fileSize).c_str());
        }
       Serial.printf("\n");
       Serial.printf("Speicherbelegung:  %s\n", formatBytes(Speicherbelegung).c_str());
       Serial.printf("\n");
     }
    } else {
      server.send(500, "text/plain", "500: couldn't create file");
    }
  }
}


/** Handle the WLAN LED link and redirect to WLAN root page again */
void handleLED_switch() {
BLINKPIN_ON();
 //   server.argName(i) => server.arg(i)  
    int arg1 = 0;    

    if (server.args() > 0){ // Werte vorhanden?
    arg1 =  server.arg(0).toInt();
        
    if ((arg1 <= 16) && (arg1 > 0)) {
        handleLED_switch_fkt(arg1);
      }
                               
    } // server.argName = ""
    else  {
          if (BRIGHTNESS_var > 127)
            BRIGHTNESS_var = 0;    
          else if (BRIGHTNESS_var < 128)  
            BRIGHTNESS_var = 255; 
            
          Set_LED_ALL(BRIGHTNESS_var);         
   } 
      
  String message = F("URI: ");
  message += server.uri();
  message += F("\nMethod: ");
  message += (server.method() == HTTP_GET) ? "GET" : "POST";
  message += F("\nArguments: ");
  message += server.args();  
  message += F("\n");
  Serial.print(message);

  for (uint8_t i = 0; i < server.args(); i++) {
    message += String(F("> ")) + server.argName(i) + F(": ") + server.arg(i) + F(" <\n");
  }
    
   server.sendHeader("Location", "/",true);   //redirect to WLAN root page
   server.send(302, "text/plane","");     

BLINKPIN_OFF();
}


/** send LED PWM Array  */
void handleStatusLED() {
BLINKPIN_ON();
 send_LED_ALL_Array(); 
 server.send(200, "text/plane", BRIGHTNESS_string); // LED value - JSON to client ajax request
BLINKPIN_OFF();
}


/** Handle the WLAN I2C link and redirect to WLAN root page again */
void handleStatusI2C() {
byte error, address;
  int nDevices;

  Serial.println("Scanning...");

  nDevices = 0;
  for(address = 1; address < 127; address++ ) 
  {
    // The i2c_scanner uses the return value of
    // the Write.endTransmisstion to see if
    // a device did acknowledge to the address.
    Wire.beginTransmission(address);
    error = Wire.endTransmission();

    if (error == 0)
    {
      Serial.print("I2C device found at address 0x");
      if (address<16) 
        Serial.print("0");
      Serial.print(address,HEX);
      Serial.println("  !");

      nDevices++;
    }
    else if (error==4) 
    {
      Serial.print("Unknown error at address 0x");
      if (address<16) 
        Serial.print("0");
      Serial.println(address,HEX);
    }    
  }
  if (nDevices == 0)
    Serial.println("No I2C devices found\n");
  else
    Serial.println("done\n");
    
   server.sendHeader("Location", "/",true);   //redirect to WLAN root page
   server.send(302, "text/plane","");     
}

Step 4: Erwiterungen in Tools.ino

Für eine bessere Darstellung der Dateigrößen ist die (SPIFFS) Funktionen formatBytes()

hinzugekommen.

Im LED Funktionsbereich ist die Funktionen send_LED_ALL_Array() für die Stringerstellung

von den LED PWM ergänzt worden.

/** Is this an IP? */
boolean isIp(String str) {
  for (size_t i = 0; i < str.length(); i++) {
    int c = str.charAt(i);
    if (c != '.' && (c < '0' || c > '9')) {
      return false;
    }
  }
  return true;
}

/** IP to String? */
String toStringIp(IPAddress ip) {
  String res = "";
  for (int i = 0; i < 3; i++) {
    res += String((ip >> (8 * i)) & 0xFF) + ".";
  }
  res += String(((ip >> 8 * 3)) & 0xFF);
  return res;
}

//format bytes / SPIFFS
String formatBytes(size_t bytes) {
  if (bytes < 1024) {
    return String(bytes) + "B";
  } else if (bytes < (1024 * 1024)) {
    return String(bytes / 1024.0) + "KB";
  } else if (bytes < (1024 * 1024 * 1024)) {
    return String(bytes / 1024.0 / 1024.0) + "MB";
  } else {
    return String(bytes / 1024.0 / 1024.0 / 1024.0) + "GB";
  }
}

//////////////////////////// TLC59116  /////////////////////////////////

void init_TLC59116() {

  Wire.begin(TLC59116_SDA, TLC59116_SCL); // (SDA), (SCL)  
  Wire.beginTransmission(B1100000); // TLC59116 Slave Adresse ->C0 hex
  Wire.write(0x80);  // autoincrement ab Register 0h

  Wire.write(0x00);  // Register 00 /  Mode1  
  Wire.write(0x00);  // Register 01 /  Mode2 

  Wire.write(0x00);  // Register 02 /  PWM LED 1    // Default alle PWM auf 0
  Wire.write(0x00);  // Register 03 /  PWM LED 2    
  Wire.write(0x00);  // Register 04 /  PWM LED 3
  Wire.write(0x00);  // Register 05 /  PWM LED 4
  Wire.write(0x00);  // Register 06 /  PWM LED 5
  Wire.write(0x00);  // Register 07 /  PWM LED 6
  Wire.write(0x00);  // Register 08 /  PWM LED 7
  Wire.write(0x00);  // Register 09 /  PWM LED 8
  Wire.write(0x00);  // Register 0A /  PWM LED 9
  Wire.write(0x00);  // Register 0B /  PWM LED 10
  Wire.write(0x00);  // Register 0C /  PWM LED 11
  Wire.write(0x00);  // Register 0D /  PWM LED 12
  Wire.write(0x00);  // Register 0E /  PWM LED 13
  Wire.write(0x00);  // Register 0F /  PWM LED 14
  Wire.write(0x00);  // Register 10 /  PWM LED 15
  Wire.write(0x00);  // Register 11 /  PWM LED 16  // Default alle PWM auf 0

  Wire.write(0xFF);  // Register 12 /  Group duty cycle control
  Wire.write(0x00);  // Register 13 /  Group frequency
  Wire.write(0xAA);  // Register 14 /  LED output state 0  // Default alle LEDs auf PWM
  Wire.write(0xAA);  // Register 15 /  LED output state 1  // Default alle LEDs auf PWM
  Wire.write(0xAA);  // Register 16 /  LED output state 2  // Default alle LEDs auf PWM
  Wire.write(0xAA);  // Register 17 /  LED output state 3  // Default alle LEDs auf PWM
  Wire.write(0x00);  // Register 18 /  I2C bus subaddress 1
  Wire.write(0x00);  // Register 19 /  I2C bus subaddress 2
  Wire.write(0x00);  // Register 1A /  I2C bus subaddress 3
  Wire.write(0x00);  // Register 1B /  All Call I2C bus address
  Wire.write(0xFF);  // Register 1C /  IREF configuration  
  Wire.endTransmission();  // I2C-Stop
}


// Diese Funktion setzt die Helligkeit für ein LED-Register 
// Voraussetzung ist, das im entsprechende Register 14 bis 17 die LED aktiviert ist
// Übergabeparameter: LED = Nummer der LED / PWM = Helligkeitswert 0 -255

void Set_LED_PWM(int LED, int PWM)
{
  Wire.begin();             //I2C-Start
  Wire.beginTransmission(B1100000); // TLC59116 Slave Adresse ->C0 hex
  Wire.write(0x01 + LED);    // Register LED-Nr
  Wire.write(PWM);
  Wire.endTransmission();   // I2C-Stop
  BRIGHTNESS_Array[LED] = PWM;
}

// Diese Funktion setzt die Helligkeit für alle LED-Register gleichzeitig
// Voraussetzung ist, das im entsprechende Register 14 bis 17 die LED aktiviert ist
// Übergabeparameter: PWM = Helligkeitswert 0 -255

void Set_LED_ALL(int PWM)
{
  Wire.begin();                     // I2C-Start
  Wire.beginTransmission(B1100000); // TLC59116 Slave Adresse ->C0 hex
  Wire.write(0x82);                 // Startregister 02h 
  for (int i=1 ; i < 17; i++){      // 16Bytes (Register 02h bis 11h) schreiben
    Wire.write(PWM);
    BRIGHTNESS_Array[i] = PWM;
  }
  Wire.endTransmission();           // I2C-Stop
}


void send_LED_ALL_Array()
{  
        BRIGHTNESS_string  = "{\"LEDpwm\": [\""+ String(BRIGHTNESS_Array[1]) ;
        for (int i=2 ; i < 17; i++){      
          BRIGHTNESS_string += "\", \""+ String(BRIGHTNESS_Array[i]);
        }  
        BRIGHTNESS_string += "\"]}";        
        //Serial.println(BRIGHTNESS_string);
}


//////////////////////////// TLC59116 WLAN /////////////////////////////////
void handleLED_switch_fkt(int arg1) {

   if (BRIGHTNESS_Array[arg1] > 127)
      BRIGHTNESS_Array[arg1] = 0;    
   else if (BRIGHTNESS_Array[arg1] < 128)  
      BRIGHTNESS_Array[arg1] = 255; 
  
   Set_LED_PWM(arg1,BRIGHTNESS_Array[arg1]); 
   Serial.print(arg1);  
   Serial.println(" done\n");           
}

Step 5: Die Weboberfläche (index.html)

Die html Datei steuert über onclick javascript events (XMLHttpRequest) die LEDs und bekommt den aktuellen PWM Wert zurückgemeldet.

<!DOCTYPE html>
<html>
<title>LED Info</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">


<style>

    rect:hover {
	    fill: white;
	    opacity:0.5;
	}
	  
    rect {
	    fill: white;
		stroke-width:8;		
	    opacity:0.2;
	}

    
</style>
<body >
    [ <a href="./upload">Upload</a> ]
	[ <a onclick="getLEDswitch()">LED (an/aus)</a> ]
	[ <a href="./wifi">WIFI</a> ]	
	[ <a href="./i2c">I2C status</a> ]		
 <hr> 
<figure id="imageMapRoom">
<svg viewBox="0 0 462 286" >
  <defs>
    <style>

	  
	  rect {  
		fill:white;
		stroke:grey;
		stroke-width:5;
		fill-opacity:0.1;
		stroke-opacity:0.9
	  }

      rect:hover {
	    fill: white;
	    opacity:0.8;
	  }
    </style>
  </defs> 

	
	
  <image id="imageMapRoomPic" width="462" height="286" xlink:href="./img/living_room_demo.png" >
    <title>Living Room LEDs</title>
  </image>	
  
  <a onclick="UpdateTab(1)" >
    <rect id="Feld_LED_K1" x="10" y="10"  width="50" height="50"  rx="20" ry="20" />
  </a>
   <a onclick="UpdateTab(2)">
    <rect id="Feld_LED_K2" x="70" y="10"  width="50" height="50"  rx="20" ry="20" />
  </a>
   <a onclick="UpdateTab(3)">
	<rect id="Feld_LED_K3" x="130" y="10"  width="50" height="50" rx="20" ry="20" />
  </a>
  <a onclick="UpdateTab(4)">
	<rect id="Feld_LED_K4" x="190" y="10"  width="50" height="50" rx="20" ry="20" />
  </a>
  
  <a onclick="UpdateTab(5)">
	<rect id="Feld_LED_K5" x="10" y="70"  width="50" height="50" rx="20" ry="20" />
  </a>
    <a onclick="UpdateTab(6)">
	<rect id="Feld_LED_K6" x="70" y="70"  width="50" height="50" rx="20" ry="20" />
  </a>
  <a onclick="UpdateTab(7)">
	<rect id="Feld_LED_K7" x="130" y="70"  width="50" height="50" rx="20" ry="20" />
  </a>
    <a onclick="UpdateTab(8)">
	<rect id="Feld_LED_K8" x="190" y="70"  width="50" height="50" rx="20" ry="20" />
  </a>
  
  <a onclick="UpdateTab(9)">
	<rect id="Feld_LED_K9" x="10" y="130"  width="50" height="50" rx="20" ry="20" />
  </a>
    <a onclick="UpdateTab(10)">
	<rect id="Feld_LED_K10" x="70" y="130"  width="50" height="50" rx="20" ry="20" />
  </a>
  <a onclick="UpdateTab(11)">
	<rect id="Feld_LED_K11" x="130" y="130"  width="50" height="50" rx="20" ry="20" />
  </a>
    <a onclick="UpdateTab(12)">
	<rect id="Feld_LED_K12" x="190" y="130"  width="50" height="50" rx="20" ry="20" />
  </a>
  
    <a onclick="UpdateTab(13)">
	<rect id="Feld_LED_K13" x="10" y="190"  width="50" height="50" rx="20" ry="20" />
  </a>
    <a onclick="UpdateTab(14)">
	<rect id="Feld_LED_K14" x="70" y="190"  width="50" height="50" rx="20" ry="20" />
  </a>
  <a onclick="UpdateTab(15)">
	<rect id="Feld_LED_K15" x="130" y="190"  width="50" height="50" rx="20" ry="20" />
  </a>
    <a onclick="UpdateTab(16)">
	<rect id="Feld_LED_K16" x="190" y="190"  width="50" height="50" rx="20" ry="20" />

  </a>
  
</svg>
  <figcaption> auf die Felder klicken ...</figcaption>
</figure>

<script>
 
 getData();
  
function getLEDswitch(){
//"href="./LEDswitch?position="
  var xhttp = new XMLHttpRequest();
  xhttp.open("GET", "./LEDswitch" , true); //Handle readADC server on ESP8266
  xhttp.send();
  getData();
}

function UpdateTab(LEDnr) {  // onClick= "UpdateTab(1);" 
  var xhttp = new XMLHttpRequest();
  xhttp.open("GET", "./LEDswitch?LED=" + LEDnr, true); //Handle LED on ESP8266 server
  xhttp.send();
  getData();
}



//tutorial: <a href="https://www.w3schools.com/js/js_json_parse.asp"> https://www.w3schools.com/js/js_json_parse.asp </a> 
// {"LEDpwm": ["0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0"]}
function getData() {
  var xhttp = new XMLHttpRequest();
  xhttp.onreadystatechange = function() {
   if (this.readyState == 4 && this.status == 200) {
     //Push the data in array
  var txt = this.responseText;
  var obj = JSON.parse(txt); 
	
  var txtID = ""; 
  var feldID = "";
   
		for (var i=1;i<17;i++){

			feldID = "Feld_LED_K" + i;
		
		if ( parseInt(obj["LEDpwm"][i-1]) > 10 )    
			document.getElementById(feldID).style.stroke = "rgb(0,"+ parseInt(obj["LEDpwm"][i-1]) +",0)";
	    else  
			document.getElementById(feldID).style.stroke = "red";
	
		}

    }
	
  };
  xhttp.open("GET", "./readLED", true); //Handle readLED server on ESP8266
  xhttp.send();
}


	
</script>

</body>
</html>

Step 6: Weboberfläche

Die Veränderungen der LED PWM werden in der

Weboberfläche über onclick="UpdateTab(xx)" Aufrufe gesteuert. Diese ruft auf dem ESP Server die URL ./LEDswitch?LED=LEDnr auf und toggelt die jeweilige LEDnr. Die Aktualisierung aller LED PWMs Rückmeldungen in der Weboberfläche übernimmt getData().

.

  • Sektch hochladen
  • ./upload aufrufen und die index.html Datei hochladen (SPIFFS)
  • ./upload aufrufen und die living_room_demo.png Datei hochladen (SPIFFS)

.

Einen eigenen Plan kann man sehr einfach unter https://floorplanner.com erstellen.

und nun viel Spaß beim Nachbauen