Skip to content

Commit

Permalink
Simplifying the example. Still more work to go.
Browse files Browse the repository at this point in the history
  • Loading branch information
slav-at-attachix committed Aug 1, 2019
1 parent 8c7cba8 commit e0eaead
Show file tree
Hide file tree
Showing 22 changed files with 108 additions and 1,198 deletions.
6 changes: 3 additions & 3 deletions Sming/Core/Network/Http/HttpBodyParser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -268,9 +268,9 @@ int MultipartParser::bodyEnd(multipart_parser_t* p)

void formMultipartParser(HttpRequest& request, const char* at, int length)
{
MultipartParser* parser = (MultipartParser*)request.args;
auto parser = static_cast<MultipartParser*>(request.args);

if(length == -1) {
if(length == PARSE_DATASTART) {
delete parser;

parser = new MultipartParser(&request);
Expand All @@ -279,7 +279,7 @@ void formMultipartParser(HttpRequest& request, const char* at, int length)
return;
}

if(length == -2) {
if(length == PARSE_DATAEND) {
delete parser;
request.args = nullptr;

Expand Down
2 changes: 1 addition & 1 deletion samples/HttpServer_FirmwareUpload/.gitignore
Original file line number Diff line number Diff line change
@@ -1 +1 @@
node_modules
web/.lastModified
44 changes: 9 additions & 35 deletions samples/HttpServer_FirmwareUpload/README.rst
Original file line number Diff line number Diff line change
@@ -1,44 +1,18 @@
HttpServer Config Network
HttpServer Firmware Upload
=========================

Introduction
------------

The HTTP server coming with Sming is quite powerful but it is limited
from the available resources of the underlining hardware (your favorite
ESP8266 microcontroller). Serving multiple files at once can be
problematic. It is not the size of the files that can cause problems,
but the number of simultaneous files that need to be delivered.
Therefore if you serve multiple CSS or JS files you can optimize your
web application before uploading it into your ESP8266 using the advice
below.
ESP8266 microcontroller).

Optimizing File Delivery
------------------------
This sample demonstrates how to enable file upload of the HTTP server.
On a normal computer the file uploads are usually using
temporary space on the hard disk or in memory to store the incoming data.

In this example you will see how to combine CSS and JS files, compress
them and deliver the optimized content via the HTTP server.

Installation
~~~~~~~~~~~~

The file optimization uses ``gulp``. To install it and the needed gulp
packages you need to install first `npm <https://www.npmjs.com/>`__. Npm
is the Node.JS package manager. Once you are done with the installation
you can run from the command line the following:

npm install

The command above will install gulp and its dependencies.

Usage
~~~~~

During the development of your web application you should work only in
the ``web/dev/`` folder. Once you are ready with the application you can
``pack`` the resources and ``upload`` them to your device. The commands
are

make web-pack make web-upload

That should be it.
On an embedded device that is a luxury that we can hardly afford.
In this sample we demonstrate how to define which file upload fields
should be stored and what (file) streams are responsible for storing the data.
If a field is not specified then its content will be discarded.
216 changes: 58 additions & 158 deletions samples/HttpServer_FirmwareUpload/app/application.cpp
Original file line number Diff line number Diff line change
@@ -1,72 +1,17 @@
#include <SmingCore.h>
#include <AppSettings.h>
#include <JsonObjectStream.h>
#include "Data/Stream/LimitedMemoryStream.h"
#include "Data/Stream/TemplateFlashMemoryStream.h"

#include <Data/Stream/LimitedMemoryStream.h>
#include <Network/Http/HttpMultipartResource.h>
//#include <Network/rBootHttpUpdate.h>
#include <Network/rBootHttpUpdate.h>

HttpServer server;
FtpServer ftp;

BssList networks;
String network, password;
Timer connectionTimer;

String lastModified;

// Instead of using a SPIFFS file, here we demonstrate usage of imported Flash Strings
IMPORT_FSTR(flashSettings, PROJECT_DIR "/web/build/settings.html")

void onIndex(HttpRequest& request, HttpResponse& response)
{
TemplateFileStream* tmpl = new TemplateFileStream("index.html");
auto& vars = tmpl->variables();
response.sendTemplate(tmpl); // will be automatically deleted
}

int onIpConfig(HttpServerConnection& connection, HttpRequest& request, HttpResponse& response)
{
if(request.method == HTTP_POST) {
debugf("Request coming from IP: %s", connection.getRemoteIp().toString().c_str());
// If desired you can also limit the access based on remote IP. Example below:
// if(!(IPAddress("192.168.4.23") == connection.getRemoteIp())) {
// return 1; // error
// }

AppSettings.dhcp = request.getPostParameter("dhcp") == "1";
AppSettings.ip = request.getPostParameter("ip");
AppSettings.netmask = request.getPostParameter("netmask");
AppSettings.gateway = request.getPostParameter("gateway");
debugf("Updating IP settings: %d", AppSettings.ip.isNull());
AppSettings.save();
}

/*
* We could use a regular SPIFFS file for this, but instead we demonstrate using a Flash String.
*
* TemplateFileStream* tmpl = new TemplateFileStream("settings.html");
*/
auto tmpl = new TemplateFlashMemoryStream(flashSettings);
auto& vars = tmpl->variables();

bool dhcp = WifiStation.isEnabledDHCP();
vars["dhcpon"] = dhcp ? "checked='checked'" : "";
vars["dhcpoff"] = !dhcp ? "checked='checked'" : "";

if(!WifiStation.getIP().isNull()) {
vars["ip"] = WifiStation.getIP().toString();
vars["netmask"] = WifiStation.getNetworkMask().toString();
vars["gateway"] = WifiStation.getNetworkGateway().toString();
} else {
vars["ip"] = "192.168.1.77";
vars["netmask"] = "255.255.255.0";
vars["gateway"] = "192.168.1.1";
}

response.sendTemplate(tmpl); // will be automatically deleted

return 0;
response.sendTemplate(tmpl);
}

void onFile(HttpRequest& request, HttpResponse& response)
Expand All @@ -90,132 +35,88 @@ void onFile(HttpRequest& request, HttpResponse& response)
}
}

void onAjaxNetworkList(HttpRequest& request, HttpResponse& response)
int onUpload(HttpServerConnection& connection, HttpRequest& request, HttpResponse& response)
{
JsonObjectStream* stream = new JsonObjectStream();
JsonObject json = stream->getRoot();
ReadWriteStream* file = request.files["firmware"];
if(file == nullptr) {
debug_e("Something went wrong with the file upload");
return 1;
}

json["status"] = (bool)true;
debugf("Uploaded length: %d", file->available());

bool connected = WifiStation.isConnected();
json["connected"] = connected;
if(connected) {
// Copy full string to JSON buffer memory
json["network"] = WifiStation.getSSID();
}
rboot_config bootConfig = rboot_get_config();
uint8_t slot = bootConfig.current_rom;
slot = (slot == 0 ? 1 : 0);
Serial.printf("Firmware updated, rebooting to rom %d...\r\n", slot);
rboot_set_current_rom(slot);
System.restart(5); // defer the restart with 5 seconds to give time to the web server to return the response

JsonArray netlist = json.createNestedArray("available");
for(unsigned i = 0; i < networks.count(); i++) {
if(networks[i].hidden)
continue;
JsonObject item = netlist.createNestedObject();
item["id"] = (int)networks[i].getHashId();
// Copy full string to JSON buffer memory
item["title"] = networks[i].ssid;
item["signal"] = networks[i].rssi;
item["encryption"] = networks[i].getAuthorizationMethodName();
}
response.sendFile("restart.html");

response.setAllowCrossDomainOrigin("*");
response.sendDataStream(stream, MIME_JSON);
return 0;
}

void makeConnection()
void simpleUploadMapper(HttpFiles& files)
{
WifiStation.enable(true);
WifiStation.config(network, password);

AppSettings.ssid = network;
AppSettings.password = password;
AppSettings.save();
/*
* On a normal computer the file uploads are usually using
* temporary space on the hard disk or in memory to store the incoming data.
*
* On an embedded device that is a luxury that we can hardly afford.
* Therefore we should define a `map` that specifies explicitly
* where a specific form field will be stored.
*
* If a field is not specified then its content will be discarded.
*/

network = ""; // task completed
// Below we instruct the server to store max 1024 bytes
// from the incoming data for the "firmware" form fields.
files["firmware"] = new LimitedMemoryStream(1024);
}

void onAjaxConnect(HttpRequest& request, HttpResponse& response)
void fileUploadMapper(HttpFiles& files)
{
JsonObjectStream* stream = new JsonObjectStream();
JsonObject json = stream->getRoot();

String curNet = request.getPostParameter("network");
String curPass = request.getPostParameter("password");

bool updating = curNet.length() > 0 && (WifiStation.getSSID() != curNet || WifiStation.getPassword() != curPass);
bool connectingNow = WifiStation.getConnectionStatus() == eSCS_Connecting || network.length() > 0;

if(updating && connectingNow) {
debugf("wrong action: %s %s, (updating: %d, connectingNow: %d)", network.c_str(), password.c_str(), updating,
connectingNow);
json["status"] = (bool)false;
json["connected"] = (bool)false;
} else {
json["status"] = (bool)true;
if(updating) {
network = curNet;
password = curPass;
debugf("CONNECT TO: %s %s", network.c_str(), password.c_str());
json["connected"] = false;
connectionTimer.initializeMs(1200, makeConnection).startOnce();
} else {
json["connected"] = WifiStation.isConnected();
debugf("Network already selected. Current status: %s", WifiStation.getConnectionStatusName());
}
}
/*
* On a normal computer the file uploads are usually using
* temporary space on the hard disk or in memory to store the incoming data.
*
* On an embedded device that is a luxury that we can hardly afford.
* Therefore we should define a `map` that specifies explicitly
* where a specific form field will be stored.
*
* If a field is not specified then its content will be discarded.
*/

if(!updating && !connectingNow && WifiStation.isConnectionFailed())
json["error"] = WifiStation.getConnectionStatusName();
// Get the address where the next firmware should be stored.
rboot_config bootConfig = rboot_get_config();
uint8_t slot = bootConfig.current_rom;
slot = (slot == 0 ? 1 : 0);
int romStartAddress = bootConfig.roms[slot];

response.setAllowCrossDomainOrigin("*");
response.sendDataStream(stream, MIME_JSON);
}
// We create a rBoot item to be stored
auto item = new rBootHttpUpdateItem();
item->size = 1; // must be bigger than 0
item->targetOffset = romStartAddress; // the start location on flash memory
item->url = "http://localhost"; // will be discarded

int onUpload(HttpServerConnection& connection, HttpRequest& request, HttpResponse& response)
{
ReadWriteStream* file = request.files["test"];
if(file == nullptr) {
debug_e("Something went wrong with the file upload");
return 1;
}
auto rBootStream = new rBootItemOutputStream();
rBootStream->setItem(item);

debugf("Uploaded length: %d", file->available());
return 0;
files["firmware"] = rBootStream;
}

void startWebServer()
{
server.listen(80);
server.paths.set("/", onIndex);

auto mapper = [](HttpFiles& files) {
/*
* On a normal computer the file uploads are usually using
* temporary space on the hard disk or in memory to store the incoming data.
*
* On an embedded device that is a luxury that we can hardly afford.
* Therefore we should define a `map` that specifies explicitly
* where a specific form field will be stored.
*
* If a field is not specified then its content will be discarded.
*/
files["test"] = new LimitedMemoryStream(1025);
};

HttpMultipartResource* uploadResouce = new HttpMultipartResource(mapper, onUpload);
HttpMultipartResource* uploadResouce = new HttpMultipartResource(fileUploadMapper, onUpload);
server.paths.set("/upload", uploadResouce);

server.paths.setDefault(onFile);
}

void networkScanCompleted(bool succeeded, BssList list)
{
if(succeeded) {
for(unsigned i = 0; i < list.count(); i++)
if(!list[i].hidden && list[i].ssid.length() > 0)
networks.add(list[i]);
}
networks.sort([](const BssInfo& a, const BssInfo& b) { return b.rssi - a.rssi; });
}

void init()
{
spiffs_mount(); // Mount file system, in order to work with files
Expand All @@ -228,7 +129,6 @@ void init()

Serial.begin(SERIAL_BAUD_RATE); // 115200 by default
Serial.systemDebugOutput(true); // Enable debug output to serial
AppSettings.load();

WifiStation.enable(true);

Expand Down
5 changes: 2 additions & 3 deletions samples/HttpServer_FirmwareUpload/component.mk
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
SPIFF_FILES = web/build
SPIFF_FILES = web/
ARDUINO_LIBRARIES := ArduinoJson6

# The line below enables the form upload support on the server
ENABLE_HTTP_SERVER_MULTIPART = 1

web-pack:
$(Q) gulp
$(Q) date +'%a, %d %b %Y %H:%M:%S GMT' -u > web/build/.lastModified
$(Q) date +'%a, %d %b %Y %H:%M:%S GMT' -u > web/.lastModified

web-upload: web-pack spiff_update
$(call WriteFlash,$(SPIFF_START_OFFSET)=$(SPIFF_BIN_OUT))
Loading

0 comments on commit e0eaead

Please sign in to comment.