-->
Page 1 of 6

Webserver on ESP8266, serving files from Flash

PostPosted: Fri Jun 26, 2015 4:01 pm
by Torx
Hello,
I wanted to easily convert the files from a subfolder called "website" to a C-header file. This works, because I wrote a BASH script that creates the header file for me.

Code: Select all#!/bin/bash

echo "Converting files in folder \"websites\" to C Header file websites.h"

WEBSITE="website/"
CURRDIR="$(pwd)"
OUTFILE="$CURRDIR/websites.h"

#change into website folder
cd $WEBSITE

cat > $OUTFILE <<DELIMITER
//
// converted websites to mainly flash variables
//

#include "Flash.h"

DELIMITER

#convert contents into array of bytes
INDEX=0
for i in $(ls -1); do
  CONTENT=$(cat $i | xxd -i)
  printf "FLASH_ARRAY(uint8_t, file_$INDEX,\n$CONTENT\n);\n" >> $OUTFILE
  echo >> $OUTFILE
  INDEX=$((INDEX+1))
done

# write typedefinition
cat >> $OUTFILE <<DELIMITER
struct t_websitefiles {
  const char* filename;
  const char* mime;
  const unsigned int len;
  const _FLASH_ARRAY<uint8_t>* content;
} files[] = {
DELIMITER

# add other data and create array
INDEX=0
for i in $(ls -1); do
  CONTENT=$(cat $i | xxd -i)
  CONTENT_LEN=$(echo $CONTENT | grep -o '0x' | wc -l) 
  MIMETYPE=$(file -ib $i)
 
  echo "  {" >> $OUTFILE
  echo "    .filename = \"$i\"," >> $OUTFILE
  echo "    .mime = \"$MIMETYPE\"," >> $OUTFILE
  echo "    .len = $CONTENT_LEN," >> $OUTFILE
  echo "    .content = &file_$INDEX" >> $OUTFILE
  echo "  }," >> $OUTFILE

  INDEX=$((INDEX+1))
done
echo "};" >> $OUTFILE

cd $CURRDIR


The resulting header file "websites.h" can look like this:
Code: Select all//
// converted websites to mainly flash variables
//

#include "Flash.h"

FLASH_ARRAY(uint8_t, file_0,
  0x3c, 0x68, 0x74, 0x6d, 0x6c, 0x3e, 0x0a, 0x3c, 0x68, 0x65, 0x61, 0x64,
  0x65, 0x72, 0x3e, 0x54, 0x65, 0x73, 0x74, 0x20, 0x3a, 0x3a, 0x20, 0x69,
  0x6e, 0x64, 0x65, 0x78, 0x32, 0x2e, 0x68, 0x74, 0x6d, 0x6c, 0x3c, 0x2f,
  0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x3e, 0x0a, 0x3c, 0x62, 0x6f, 0x64,
  0x79, 0x3e, 0x0a, 0x3c, 0x68, 0x31, 0x3e, 0x49, 0x4e, 0x44, 0x45, 0x58,
  0x20, 0x32, 0x3c, 0x2f, 0x68, 0x31, 0x3e, 0x0a, 0x3c, 0x69, 0x6d, 0x67,
  0x20, 0x73, 0x72, 0x63, 0x3d, 0x22, 0x70, 0x69, 0x63, 0x74, 0x75, 0x72,
  0x65, 0x2e, 0x70, 0x6e, 0x67, 0x22, 0x3e, 0x3c, 0x2f, 0x69, 0x6d, 0x67,
  0x3e, 0x0a, 0x3c, 0x2f, 0x62, 0x6f, 0x64, 0x79, 0x3e, 0x0a, 0x3c, 0x2f,
  0x68, 0x74, 0x6d, 0x6c, 0x3e
);

FLASH_ARRAY(uint8_t, file_1,
  0x3c, 0x68, 0x74, 0x6d, 0x6c, 0x3e, 0x0a, 0x3c, 0x68, 0x65, 0x61, 0x64,
  0x65, 0x72, 0x3e, 0x54, 0x65, 0x73, 0x74, 0x20, 0x3a, 0x3a, 0x20, 0x69,
  0x6e, 0x64, 0x65, 0x78, 0x31, 0x2e, 0x68, 0x74, 0x6d, 0x6c, 0x3c, 0x2f,
  0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x3e, 0x0a, 0x3c, 0x62, 0x6f, 0x64,
  0x79, 0x3e, 0x0a, 0x3c, 0x68, 0x31, 0x3e, 0x49, 0x4e, 0x44, 0x45, 0x58,
  0x20, 0x31, 0x3c, 0x2f, 0x68, 0x31, 0x3e, 0x0a, 0x3c, 0x2f, 0x62, 0x6f,
  0x64, 0x79, 0x3e, 0x0a, 0x3c, 0x2f, 0x68, 0x74, 0x6d, 0x6c, 0x3e
);

FLASH_ARRAY(uint8_t, file_2,
  0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d,
  0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0xd2, 0x00, 0x00, 0x00, 0x78,

 ... shortened ...
 
 0xa5, 0x01, 0x04, 0x84, 0x51, 0x16, 0xed, 0x10, 0x10, 0x86, 0xe2, 0xff,
  0x01, 0x14, 0xe7, 0xb2, 0x57, 0x6e, 0xe7, 0x42, 0x2b, 0x00, 0x00, 0x00,
  0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82
);

struct t_websitefiles {
  const char* filename;
  const char* mime;
  const unsigned int len;
  const _FLASH_ARRAY<uint8_t>* content;
} files[] = {
  {
    .filename = "index2.html",
    .mime = "text/html; charset=us-ascii",
    .len = 113,
    .content = &file_0
  },
  {
    .filename = "index.html",
    .mime = "text/html; charset=us-ascii",
    .len = 83,
    .content = &file_1
  },
  {
    .filename = "picture.png",
    .mime = "image/png; charset=binary",
    .len = 7929,
    .content = &file_2
  },
};


Then, I wanted to be able to store larger files and I found out about PROGMEM. I ported and extended the code for ESP8266 from http://arduiniana.org/libraries/flash/ because initially it was AVR only. I was looking for bytes, thus I only used the suitable class and stored it as "Flash.h":
Code: Select all#ifndef __FLASH_H__
#define __FLASH_H__

#include <pgmspace.h>
#include "Arduino.h"

// Example: FLASH_ARRAY(float, temperatures, 98.1, 98.5, 99.1, 102.1);
#define FLASH_ARRAY(type, name, values...) \
  static const type name##_flash[] PROGMEM = { values }; \
  _FLASH_ARRAY<type> name(name##_flash, sizeof(name##_flash) / sizeof(type));


#ifndef ARDUINO_CORE_PRINTABLE_SUPPORT
class _Printable
{
public:
  virtual void print(Print &stream) const = 0;
};
#endif

/* _FLASH_ARRAY template class.  Use the FLASH_ARRAY() macro to create these. */
template<class T>
class _FLASH_ARRAY : public _Printable
{
  typedef T _DataType;

public:
  _FLASH_ARRAY(const _DataType *arr, size_t count) : _arr(arr), _size(count)
  { }

  size_t count() const
  { return _size; }
 
  size_t size() const
  { return _size; }
 
  size_t available() const
  { return _size; }
 
  size_t read(uint8_t *dst, size_t len) const
  {
    for(size_t i; i<len; i++) {
      dst[i] = pgm_read_byte(_arr + i);
    }
  }

  const _DataType *access() const
  { return _arr; }

  T operator[](int index) const
  {
    uint32_t val = 0;
    if (sizeof(T) == 1)
      val = pgm_read_byte(_arr + index);
    else if (sizeof(T) == 2)
      val = pgm_read_word(_arr + index);
    else if (sizeof(T) == 4)
      val = pgm_read_dword(_arr + index);
    return *reinterpret_cast<T *>(&val);
  }

  void print(Print &stream) const
  {
    for (size_t i=0; i<_size; ++i)
    {
      stream.print((*this)[i]);
      if (i < _size - 1)
        stream.print(",");
    }
  }

private:
  const _DataType *_arr;
  size_t _size;
};

#ifndef ARDUINO_STREAMING
#define ARDUINO_STREAMING

template<class T>
inline Print &operator <<(Print &stream, T arg)
{ stream.print(arg); return stream; }

#endif

inline Print &operator <<(Print &stream, const _Printable &printable)
{ printable.print(stream); return stream; }

template<class T>
inline Print &operator <<(Print &stream, const _FLASH_ARRAY<T> &printable)
{ printable.print(stream); return stream; }

#endif // def __FLASH_H__


And then I used everything in the following ino file:
Code: Select all#include <NeoPixelBus.h>
#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <ESP8266WebServer.h>
#include "websites.h"

#define pixelCount 8
#define NodeMCU_NeoPixelGPIO 5
#define UserSwitchGPIO 16

NeoPixelBus strip = NeoPixelBus(pixelCount, NodeMCU_NeoPixelGPIO);
uint16_t effectState = 0;

const char *ssid = "LED Lamp";
const char *ssid2 = "LED Lamp :: Config Mode";
const char *password = "1234567890";

ESP8266WebServer server(80);

// store the current mode
typedef enum _Mode{ModeUnkown=0, ModeRun=1, ModeConfig=2} Mode;
Mode CurrentMode = ModeUnkown;

void setup()
{
  // Enable serial port for debugging purposes
  Serial.begin ( 115200 );
  Serial.println("ESP starting up...");
 
  // start WS2812b strip and library
  strip.Begin();
  strip.Show();
  SetRandomSeed();
 
  // enable reading of USR switch, if pressed it is low
  pinMode(UserSwitchGPIO, INPUT);
 
  WiFi.softAP(ssid);
  CurrentMode = ModeRun;
 
  // in the notFound we search for files ourselves
  server.onNotFound(handleNotFound);
  server.begin();
}

bool loadFromFlash(String path) {
  String dataType = "text/plain";
  if(path.endsWith("/")) path += "index.html";

  int NumFiles = sizeof(files)/sizeof(struct t_websitefiles);
 
  for(int i=0; i<NumFiles; i++) {
    if(path.endsWith(String(files[i].filename))) {     
      _FLASH_ARRAY<uint8_t>* filecontent;
     
      filecontent = (_FLASH_ARRAY<uint8_t>*)files[i].content;
      server.streamFile(*filecontent, files[i].mime);
     
      return true;
    }
  }
 
  return false;
}

void handleNotFound() {
 
  // try to find the file in the flash
  if(loadFromFlash(server.uri())) return;
 
  String message = "File Not Found\n\n";
  message += "URI..........: ";
  message += server.uri();
  message += "\nMethod.....: ";
  message += (server.method() == HTTP_GET)?"GET":"POST";
  message += "\nArguments..: ";
  message += server.args();
  message += "\n";
  for (uint8_t i=0; i<server.args(); i++){
    message += " " + server.argName(i) + ": " + server.arg(i) + "\n";
  }
  message += "\n";
  message += "FreeHeap.....: " + String(ESP.getFreeHeap()) + "\n";
  message += "ChipID.......: " + String(ESP.getChipId()) + "\n";
  message += "FlashChipId..: " + String(ESP.getFlashChipId()) + "\n";
  message += "FlashChipSize: " + String(ESP.getFlashChipSize()) + " bytes\n";
  message += "getCycleCount: " + String(ESP.getCycleCount()) + " Cycles\n";
  message += "Milliseconds.: " + String(millis()) + " Milliseconds\n";
  server.send(404, "text/plain", message);
}

void loop()
{
  // process webserver
  server.handleClient();

  PickRandom(50);
 
  // start animating
  strip.StartAnimating();
 
  if( (digitalRead(UserSwitchGPIO) == LOW) && (CurrentMode != ModeConfig) ) {
    strip.SetPixelColor(0, RgbColor(255,0,0));
    strip.Show();
    delay(2000);
    WiFi.softAP(ssid2);
    CurrentMode = ModeConfig;
    pinMode(UserSwitchGPIO, OUTPUT);
    digitalWrite(UserSwitchGPIO, HIGH);
    pinMode(UserSwitchGPIO, INPUT);
  }
 
  if( (digitalRead(UserSwitchGPIO) == LOW) && (CurrentMode != ModeRun) ) {
    strip.SetPixelColor(0, RgbColor(0,255,0));
    strip.Show();
    delay(2000);
    WiFi.softAP(ssid);
    CurrentMode = ModeRun;
    pinMode(UserSwitchGPIO, OUTPUT);
    digitalWrite(UserSwitchGPIO, HIGH);
    pinMode(UserSwitchGPIO, INPUT);
  }
 
  // wait until no more animations are running
  while (strip.IsAnimating())
  {
    strip.UpdateAnimations();
    strip.Show();
    delay(31); // ~30hz change cycle
  }
}

void FadeInFadeOutRinseRepeat(uint8_t peak)
{
  if (effectState == 0)
  {
    for (uint8_t pixel = 0; pixel < pixelCount; pixel++)
    {
      uint16_t time = random(800,1000);
      strip.LinearFadePixelColor(time, pixel, RgbColor(random(peak), random(peak), random(peak)));
    }
  }
  else if (effectState == 1)
  {
    for (uint8_t pixel = 0; pixel < pixelCount; pixel++)
    {
      uint16_t time = random(600,700);
      strip.LinearFadePixelColor(time, pixel, RgbColor(0, 0, 0));
    }
  }
  effectState = (effectState + 1) % 2; // next effectState and keep within the number of effectStates
 
}

void PickRandom(uint8_t peak)
{

  // pick random set of pixels to animate
  uint8_t count = random(pixelCount);
  while (count > 0)
  {
    uint8_t pixel = random(pixelCount);
   
    // configure the animations
    RgbColor color; // = strip.getPixelColor(pixel);

    color = RgbColor(random(peak), random(peak), random(peak));

   
    uint16_t time = random(100,400);
    strip.LinearFadePixelColor( time, pixel, color);
   
    count--;
  }
}

void LoopAround(uint8_t peak, uint16_t speed)
{
  // Looping around the ring sample
  uint16_t prevPixel;
  RgbColor prevColor;
 
  // fade previous one dark
  prevPixel = (effectState + (pixelCount - 5)) % pixelCount;
  strip.LinearFadePixelColor(speed, prevPixel, RgbColor(0, 0, 0));
 
  // fade previous one dark
  prevPixel = (effectState + (pixelCount - 4)) % pixelCount;
  prevColor = strip.GetPixelColor( prevPixel );
  prevColor.Darken(prevColor.CalculateBrightness() / 2);
  strip.LinearFadePixelColor(speed, prevPixel, prevColor);
 
  // fade previous one dark
  prevPixel = (effectState + (pixelCount - 3)) % pixelCount;
  prevColor = strip.GetPixelColor( prevPixel );
  prevColor.Darken(prevColor.CalculateBrightness() / 2);
  strip.LinearFadePixelColor(speed, prevPixel, prevColor);
 
  // fade previous one dark
  prevPixel = (effectState + (pixelCount - 2)) % pixelCount;
  prevColor = strip.GetPixelColor( prevPixel );
  prevColor.Darken(prevColor.CalculateBrightness() / 2);
  strip.LinearFadePixelColor(speed, prevPixel, prevColor);
 
  // fade previous one dark
  prevPixel = (effectState + (pixelCount - 1)) % pixelCount;
  prevColor = strip.GetPixelColor( prevPixel );
  prevColor.Darken(prevColor.CalculateBrightness() / 2);
  strip.LinearFadePixelColor(speed, prevPixel, prevColor);
 
  // fade current one light
  strip.LinearFadePixelColor(speed, effectState, RgbColor(random(peak), random(peak), random(peak)));
  effectState = (effectState + 1) % pixelCount;
}

void SetRandomSeed()
{
  uint32_t seed;
 
  // random works best with a seed that can use 31 bits
  // analogRead on a unconnected pin tends toward less than four bits
  seed = analogRead(0);
  delay(1);
 
  for (int shifts = 3; shifts < 31; shifts += 3)
  {
    seed ^= analogRead(0) << shifts;
    delay(1);
  }
 
  // Serial.println(seed);
  randomSeed(seed);
}


BUT: Everything works for small HTML-Files, but when I try to access the 7,8k large PNG files the download starts, but it does not finish - instead the connection is cut.

Any ideas how the ESP webserver can handle larger files? Later I want to add even more graphics and JS files etc.

The server also only answers the first request quickly, if requested to quick it slows down a lot until I give him a break of a couple of seconds.

Looking forward to your answers or ideas,
Tom

Re: Webserver on ESP8266, serving files from Flash

PostPosted: Fri Jun 26, 2015 5:16 pm
by SwiCago
I believe the webserver can only handle one connection at a time. If your webpage includes images and files, then the browser will attempt to make multiple connections at the same time and try to fetch more than one file at one shot. The esp can't handle that. You may want to consider embedding js and image files into the HTML. Use image URL data for images, all modern browsers can handle that. It is html5 spec. If you cannot embed files into your page, then consider using ajax to async load each image/file one at a time with small delays to give the esp time to breath. Try to make the client do more work smarter.

Re: Webserver on ESP8266, serving files from Flash

PostPosted: Fri Jun 26, 2015 10:13 pm
by SwiCago
I had a closer look and the webserver written for this device and I do not think it will be able to handle what it is you want to do.
Ideally you'd want to write each char/byte that represents the image to the client and not pass a huge long byte[]/char[]/String to a method and have it iterate and send each byte/char. This uses more memory (RAM), than the ESP can handle.

You will have to write your own TCP handler. Return proper headers and return each byte/char representing the data one at a time. This will use only one char/byte of RAM space. The ESP can easily handle that.
Or instead of writing your own TCP handler, extend on top of the current one to handle such a situation. Look at ESP8266WebServer::sendContent ..Make a method that mimic what it does, but reads from a pointer and sends one char/byte at a time.

I plan to do something similar soon, where a client can upload an image and the ESP saves it to SD card and can later fetch said image again.

Re: Webserver on ESP8266, serving files from Flash

PostPosted: Wed Jul 01, 2015 3:12 pm
by Torx
Hi and thanks for the answer.

Fortunately it is working now reliably. I can serve now HTML websites and large (100k+) PNG and JS files using the most recent ESP8266WebServer webserver. I improved how files are transferred from FLASH to the client and decreased the used buffer size.

The website files are in the Arduino project folder in a subdirectory named "website/". So far I only have one directory, no subdirectories are supported. The good thing is now, that I can simply call ./convert.sh from the Arduino project folder and it automagically converts all files in my website folder to a single C-Header-Style file named "website.h". The file contents are stored in FLASH and do not eat up RAM. The ESP-12 has plenty of FLASH but like all other modules very little RAM.

Finally I can focus on working on a nice website, call .convert.sh and compile everything into a single flash-image. Changes are now easy, no need to mix Programming files and HTML/JS/CSS/picture files.

The converte script is as follows:
Code: Select all#!/bin/bash

echo "Converting files in folder \"websites\" to C Header file websites.h"

WEBSITE="website/"
CURRDIR="$(pwd)"
OUTFILE="$CURRDIR/websites.h"

#change into website folder
cd $WEBSITE

cat > $OUTFILE <<DELIMITER
//
// converted websites to mainly flash variables
//

#include "Flash.h"

DELIMITER

#convert contents into array of bytes
INDEX=0
for i in $(ls -1); do
  CONTENT=$(cat $i | xxd -i)
  printf "FLASH_ARRAY(uint8_t, file_$INDEX,\n$CONTENT\n);\n" >> $OUTFILE
  echo >> $OUTFILE
  INDEX=$((INDEX+1))
done

# write typedefinition
cat >> $OUTFILE <<DELIMITER
struct t_websitefiles {
  const char* filename;
  const char* mime;
  const unsigned int len;
  const _FLASH_ARRAY<uint8_t>* content;
} files[] = {
DELIMITER

# add other data and create array
INDEX=0
for i in $(ls -1); do
  CONTENT=$(cat $i | xxd -i)
  CONTENT_LEN=$(echo $CONTENT | grep -o '0x' | wc -l) 
  MIMETYPE=$(file --mime-type -b $i)
 
  echo "  {" >> $OUTFILE
  echo "    .filename = \"$i\"," >> $OUTFILE
  echo "    .mime = \"$MIMETYPE\"," >> $OUTFILE
  echo "    .len = $CONTENT_LEN," >> $OUTFILE
  echo "    .content = &file_$INDEX" >> $OUTFILE
  echo "  }," >> $OUTFILE

  INDEX=$((INDEX+1))
done
echo "};" >> $OUTFILE

cd $CURRDIR


The file to easily store files in FLASH and access them in a flexible way named "Flash.h":
Code: Select all#ifndef __FLASH_H__
#define __FLASH_H__

#include <pgmspace.h>
#include "Arduino.h"

// Example: FLASH_ARRAY(float, temperatures, 98.1, 98.5, 99.1, 102.1);
#define FLASH_ARRAY(type, name, values...) \
  static const type name##_flash[] PROGMEM = { values }; \
  _FLASH_ARRAY<type> name(name##_flash, sizeof(name##_flash) / sizeof(type));


#ifndef ARDUINO_CORE_PRINTABLE_SUPPORT
class _Printable
{
public:
  virtual void print(Print &stream) const = 0;
};
#endif

/* _FLASH_ARRAY template class.  Use the FLASH_ARRAY() macro to create these. */
template<class T>
class _FLASH_ARRAY : public _Printable
{
  typedef T _DataType;

public:
  _FLASH_ARRAY(const _DataType *arr, size_t count) : _arr(arr), _size(count)
  { }

  size_t count() const
  { return _size; }
 
  size_t size() const
  { return _size; }
 
  size_t available()
  { return _size -_lastread; }
 
  void open()
  { _lastread=0; }
 
  void close()
  { _lastread=0; }
 
  size_t read(uint8_t *dst, size_t len)
  {
    size_t i = 0;
   
    for(i=0; i<len; i++) {
     
      if( _lastread >= _size ) {
        break;
      }
     
      dst[i] = (*this)[_lastread];
      _lastread++;
    }
    return i;
  }

  const _DataType *access() const
  { return _arr; }

  T operator[](int index) const
  {
    uint32_t val = 0;
    if (sizeof(T) == 1)
      val = pgm_read_byte(_arr + index);
    else if (sizeof(T) == 2)
      val = pgm_read_word(_arr + index);
    else if (sizeof(T) == 4)
      val = pgm_read_dword(_arr + index);
    return *reinterpret_cast<T *>(&val);
  }

  void print(Print &stream) const
  {
    for (size_t i=0; i<_size; ++i)
    {
      stream.print((*this)[i]);
      if (i < _size - 1)
        stream.print(",");
    }
  }

private:
  const _DataType *_arr;
  size_t _size;
  size_t _lastread;
};

#ifndef ARDUINO_STREAMING
#define ARDUINO_STREAMING

template<class T>
inline Print &operator <<(Print &stream, T arg)
{ stream.print(arg); return stream; }

#endif

inline Print &operator <<(Print &stream, const _Printable &printable)
{ printable.print(stream); return stream; }

template<class T>
inline Print &operator <<(Print &stream, const _FLASH_ARRAY<T> &printable)
{ printable.print(stream); return stream; }

#endif // def __FLASH_H__


And finally the INO file:
Code: Select all#include <NeoPixelBus.h>
#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <ESP8266WebServer.h>
#include "websites.h"

#define pixelCount 8
#define NodeMCU_NeoPixelGPIO 5
#define UserSwitchGPIO 16

NeoPixelBus strip = NeoPixelBus(pixelCount, NodeMCU_NeoPixelGPIO);
uint16_t effectState = 0;

const char *ssid = "LED Lamp";
const char *ssid2 = "LED Lamp :: Config Mode";
const char *password = "1234567890";

ESP8266WebServer server(80);

// store the current mode
typedef enum _Mode{ModeUnkown=0, ModeRun=1, ModeConfig=2} Mode;
Mode CurrentMode = ModeUnkown;

void setup()
{
  // Enable serial port for debugging purposes
  Serial.begin ( 115200 );
  Serial.println("ESP starting up...");
 
  // start WS2812b strip and library
  strip.Begin();
  strip.Show();
  SetRandomSeed();
 
  // enable reading of USR switch, if pressed it is low
  pinMode(UserSwitchGPIO, INPUT);
 
  WiFi.softAP(ssid);
  CurrentMode = ModeRun;
 
  // in the notFound we search for files ourselves
  server.onNotFound(handleNotFound);
  server.begin();
}

bool loadFromFlash(String path) {
  if(path.endsWith("/")) path += "index.html";

  int NumFiles = sizeof(files)/sizeof(struct t_websitefiles);
 
  for(int i=0; i<NumFiles; i++) {
    if(path.endsWith(String(files[i].filename))) {     
      _FLASH_ARRAY<uint8_t>* filecontent;
      String dataType = "text/plain";
      unsigned int len = 0;
     
      dataType = files[i].mime;
      len = files[i].len;
     
      server.sendHeader("Content-Length", String(len));
      server.send(200, files[i].mime, "");
     
      filecontent = (_FLASH_ARRAY<uint8_t>*)files[i].content;
     
      filecontent->open();
     
      WiFiClient client = server.client();
      client.write(*filecontent, 100);
     
      return true;
    }
  }
 
  return false;
}

void handleNotFound() {
 
  // try to find the file in the flash
  if(loadFromFlash(server.uri())) return;
 
  String message = "File Not Found\n\n";
  message += "URI..........: ";
  message += server.uri();
  message += "\nMethod.....: ";
  message += (server.method() == HTTP_GET)?"GET":"POST";
  message += "\nArguments..: ";
  message += server.args();
  message += "\n";
  for (uint8_t i=0; i<server.args(); i++){
    message += " " + server.argName(i) + ": " + server.arg(i) + "\n";
  }
  message += "\n";
  message += "FreeHeap.....: " + String(ESP.getFreeHeap()) + "\n";
  message += "ChipID.......: " + String(ESP.getChipId()) + "\n";
  message += "FlashChipId..: " + String(ESP.getFlashChipId()) + "\n";
  message += "FlashChipSize: " + String(ESP.getFlashChipSize()) + " bytes\n";
  message += "getCycleCount: " + String(ESP.getCycleCount()) + " Cycles\n";
  message += "Milliseconds.: " + String(millis()) + " Milliseconds\n";
  server.send(404, "text/plain", message);
}

void loop()
{
  // process webserver
  server.handleClient();

  PickRandom(50);
 
  // start animating
  strip.StartAnimating();
 
  if( (digitalRead(UserSwitchGPIO) == LOW) && (CurrentMode != ModeConfig) ) {
    strip.SetPixelColor(0, RgbColor(255,0,0));
    strip.Show();
    delay(2000);
    WiFi.softAP(ssid2);
    CurrentMode = ModeConfig;
    pinMode(UserSwitchGPIO, OUTPUT);
    digitalWrite(UserSwitchGPIO, HIGH);
    pinMode(UserSwitchGPIO, INPUT);
  }
 
  if( (digitalRead(UserSwitchGPIO) == LOW) && (CurrentMode != ModeRun) ) {
    strip.SetPixelColor(0, RgbColor(0,255,0));
    strip.Show();
    delay(2000);
    WiFi.softAP(ssid);
    CurrentMode = ModeRun;
    pinMode(UserSwitchGPIO, OUTPUT);
    digitalWrite(UserSwitchGPIO, HIGH);
    pinMode(UserSwitchGPIO, INPUT);
  }
 
  // wait until no more animations are running
  while (strip.IsAnimating())
  {
    strip.UpdateAnimations();
    strip.Show();
    delay(31); // ~30hz change cycle
  }
}

void FadeInFadeOutRinseRepeat(uint8_t peak)
{
  if (effectState == 0)
  {
    for (uint8_t pixel = 0; pixel < pixelCount; pixel++)
    {
      uint16_t time = random(800,1000);
      strip.LinearFadePixelColor(time, pixel, RgbColor(random(peak), random(peak), random(peak)));
    }
  }
  else if (effectState == 1)
  {
    for (uint8_t pixel = 0; pixel < pixelCount; pixel++)
    {
      uint16_t time = random(600,700);
      strip.LinearFadePixelColor(time, pixel, RgbColor(0, 0, 0));
    }
  }
  effectState = (effectState + 1) % 2; // next effectState and keep within the number of effectStates
 
}

void PickRandom(uint8_t peak)
{

  // pick random set of pixels to animate
  uint8_t count = random(pixelCount);
  while (count > 0)
  {
    uint8_t pixel = random(pixelCount);
   
    // configure the animations
    RgbColor color; // = strip.getPixelColor(pixel);

    color = RgbColor(random(peak), random(peak), random(peak));

   
    uint16_t time = random(100,400);
    strip.LinearFadePixelColor( time, pixel, color);
   
    count--;
  }
}

void LoopAround(uint8_t peak, uint16_t speed)
{
  // Looping around the ring sample
  uint16_t prevPixel;
  RgbColor prevColor;
 
  // fade previous one dark
  prevPixel = (effectState + (pixelCount - 5)) % pixelCount;
  strip.LinearFadePixelColor(speed, prevPixel, RgbColor(0, 0, 0));
 
  // fade previous one dark
  prevPixel = (effectState + (pixelCount - 4)) % pixelCount;
  prevColor = strip.GetPixelColor( prevPixel );
  prevColor.Darken(prevColor.CalculateBrightness() / 2);
  strip.LinearFadePixelColor(speed, prevPixel, prevColor);
 
  // fade previous one dark
  prevPixel = (effectState + (pixelCount - 3)) % pixelCount;
  prevColor = strip.GetPixelColor( prevPixel );
  prevColor.Darken(prevColor.CalculateBrightness() / 2);
  strip.LinearFadePixelColor(speed, prevPixel, prevColor);
 
  // fade previous one dark
  prevPixel = (effectState + (pixelCount - 2)) % pixelCount;
  prevColor = strip.GetPixelColor( prevPixel );
  prevColor.Darken(prevColor.CalculateBrightness() / 2);
  strip.LinearFadePixelColor(speed, prevPixel, prevColor);
 
  // fade previous one dark
  prevPixel = (effectState + (pixelCount - 1)) % pixelCount;
  prevColor = strip.GetPixelColor( prevPixel );
  prevColor.Darken(prevColor.CalculateBrightness() / 2);
  strip.LinearFadePixelColor(speed, prevPixel, prevColor);
 
  // fade current one light
  strip.LinearFadePixelColor(speed, effectState, RgbColor(random(peak), random(peak), random(peak)));
  effectState = (effectState + 1) % pixelCount;
}

void SetRandomSeed()
{
  uint32_t seed;
 
  // random works best with a seed that can use 31 bits
  // analogRead on a unconnected pin tends toward less than four bits
  seed = analogRead(0);
  delay(1);
 
  for (int shifts = 3; shifts < 31; shifts += 3)
  {
    seed ^= analogRead(0) << shifts;
    delay(1);
  }
 
  // Serial.println(seed);
  randomSeed(seed);
}



The important function is
Code: Select allbool loadFromFlash(String path)