Webserver on ESP8266, serving files from Flash
Posted: Fri Jun 26, 2015 4:01 pm
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.
The resulting header file "websites.h" can look like this:
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":
And then I used everything in the following ino file:
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
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