Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added support for static content like HTML pages or XML #233

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
################################################################################
# This .gitignore file was automatically created by Microsoft(R) Visual Studio.
################################################################################

*.suo
*.db
*.opendb
/.vs
/.vscode
/examples/ESP8266_page/.vs
/examples/ESP8266_page/.vscode
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,12 @@ You can also define your own functions in your sketch that can be called using t
* `rest.function("led",ledControl);` declares the function in the Arduino sketch
* `/led?params=0` executes the function

### Pages

Like for the functions, you can also define your own String content in your sketch that can be called using the REST API. This can be useful is you want to return an XML file or a simple HTML page. To access a page defined in your sketch, you have to declare it first, and then call it from with a REST call. Note that all functions needs to take a String as the unique argument (for parameters to be passed to the function) and return a String. Note that during the declaration, the page need to specify the content type that will be returned. For example, if your aREST instance is called "rest" and the function "html":
* `rest.function("thml","text/hml",ledControl);` declares the page in the Arduino sketch
* `/html?params=0` executes the page

### Get data about the board

You can also access a description of all the variables that were declared on the board with a single command. This is useful to automatically build graphical interfaces based on the variables exposed to the API. This can be done via the following calls:
Expand Down
164 changes: 126 additions & 38 deletions aREST.h
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,10 @@
#define NUMBER_FUNCTIONS AREST_NUMBER_FUNCTIONS
#endif

#ifdef AREST_NUMBER_PAGES
#define NUMBER_PAGES AREST_NUMBER_PAGES
#endif

// Default number of max. exposed variables
#ifndef NUMBER_VARIABLES
#if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) || defined(CORE_WILDFIRE) || defined(ESP8266)|| defined(ESP32) || !defined(ADAFRUIT_CC3000_H)
Expand All @@ -159,6 +163,14 @@
#endif
#endif

// Default number of max. exposed pages
#ifndef NUMBER_PAGES
#if defined(__AVR_ATmega1280__) || defined(ESP32) || defined(__AVR_ATmega2560__) || defined(CORE_WILDFIRE) || defined(ESP8266)
#define NUMBER_PAGES 10
#else
#define NUMBER_PAGES 5
#endif
#endif

#ifdef AREST_BUFFER_SIZE
#define OUTPUT_BUFFER_SIZE AREST_BUFFER_SIZE
Expand Down Expand Up @@ -375,10 +387,46 @@ void addToBufferF(const __FlashStringHelper *toAdd){
}
}

void addStringToBuffer(const char * toAdd, bool quotable){

if (DEBUG_MODE) {
#if defined(ESP8266)|| defined (ESP32)
Serial.print("Memory loss:");
Serial.println(freeMemory - ESP.getFreeHeap(),DEC);
freeMemory = ESP.getFreeHeap();
#endif
Serial.print(F("Added to buffer as char: "));
Serial.println(toAdd);
}

if(quotable) {
addQuote();
}

for (int i = 0; i < strlen(toAdd) && index < OUTPUT_BUFFER_SIZE; i++, index++) {
// Handle quoting quotes and backslashes
if(quotable && (toAdd[i] == '"' || toAdd[i] == '\\')) {
if(index == OUTPUT_BUFFER_SIZE - 1) // No room!
return;
buffer[index] = '\\';
index++;
}

buffer[index] = toAdd[i];
}

if(quotable) {
addQuote();
}
}


// Send HTTP headers for Ethernet & WiFi
void send_http_headers(){

addToBufferF(F("HTTP/1.1 200 OK\r\nAccess-Control-Allow-Origin: *\r\nAccess-Control-Allow-Methods: POST, GET, PUT, OPTIONS\r\nContent-Type: application/json\r\nConnection: close\r\n\r\n"));
addToBufferF(F("HTTP/1.1 200 OK\r\nAccess-Control-Allow-Origin: *\r\nAccess-Control-Allow-Methods: POST, GET, PUT, OPTIONS\r\nContent-Type: "));
addStringToBuffer(content_type.c_str(), false);
addToBufferF(F("\r\nConnection: close\r\n\r\n"));

}

Expand Down Expand Up @@ -1137,6 +1185,47 @@ void process(char c) {
}
}

// Check if page name is in array
for (uint8_t i = 0; i < pages_index; i++) {
if (answer.startsWith(pages_names[i])) {

// End here
pin_selected = true;
state = 'x';

// Set state
command = 'p';
value = i;

content_type = pages_content_type[i];

answer.trim();

// We're expecting a string of the form <functionName>?xxxxx=<arguments>, where xxxxx can be almost anything as long as it's followed by an '='
// Get command -- Anything following the first '=' in answer will be put in the arguments string.
arguments = "";
uint16_t header_length = strlen(pages_names[i]);
if (answer.substring(header_length, header_length + 1) == "?") {
uint16_t footer_start = answer.length();
if (answer.endsWith(" HTTP/"))
footer_start -= 6; // length of " HTTP/"

// Standard operation --> strip off anything preceeding the first "=", pass the rest to the function
if(AREST_PARAMS_MODE == 0) {
uint16_t eq_position = answer.indexOf('=', header_length); // Replacing 'magic number' 8 for fixed location of '='
if (eq_position != -1)
arguments = answer.substring(eq_position + 1, footer_start);
}
// All params mode --> pass all parameters, if any, to the function. Function will be resonsible for parsing
else if(AREST_PARAMS_MODE == 1) {
arguments = answer.substring(header_length + 1, footer_start);
}
}

break; // We found what we're looking for
}
}

// If the command is "id", return device id, name and status
if ((answer[0] == 'i' && answer[1] == 'd')) {

Expand All @@ -1157,7 +1246,12 @@ void process(char c) {
pin_selected = true;
state = 'x';
}


// Check if we return content type as json
if (command != 'p')
{
content_type = "application/json";
}
// Check the type of HTTP request
// if (answer.startsWith("GET")) {method = "GET";}
// if (answer.startsWith("POST")) {method = "POST";}
Expand Down Expand Up @@ -1427,6 +1521,17 @@ bool send_command(bool headers, bool decodeArgs) {
}
}

// page selected
if (command == 'p') {

// Execute function
if (decodeArgs)
urldecode(arguments); // Modifies arguments

String result = pages[value](arguments);
addStringToBuffer(result.c_str(), false);
}

if (command == 'r' || command == 'u') {
root_answer();
}
Expand All @@ -1444,7 +1549,7 @@ bool send_command(bool headers, bool decodeArgs) {
addToBufferF(F("\r\n"));
}

else {
else if (command != 'p') {
if (command != 'r' && command != 'u') {
addHardwareToBuffer();
addToBufferF(F("\r\n"));
Expand Down Expand Up @@ -1511,6 +1616,17 @@ void function(char * function_name, int (*f)(String)){
functions_index++;
}

// To add static content page
// page_content_type can be any type like application/json or text/html
// it will be added automatically to the headers
void page(char * page_name, char * page_content_type, String (*f)(String)){

pages_names[pages_index] = page_name;
pages_content_type[pages_index] = page_content_type;
pages[pages_index] = f;
pages_index++;
}

// Set device ID
void set_id(const String& device_id) {

Expand Down Expand Up @@ -1629,41 +1745,6 @@ void addQuote() {
}
}


void addStringToBuffer(const char * toAdd, bool quotable){

if (DEBUG_MODE) {
#if defined(ESP8266)|| defined (ESP32)
Serial.print("Memory loss:");
Serial.println(freeMemory - ESP.getFreeHeap(),DEC);
freeMemory = ESP.getFreeHeap();
#endif
Serial.print(F("Added to buffer as char: "));
Serial.println(toAdd);
}

if(quotable) {
addQuote();
}

for (int i = 0; i < strlen(toAdd) && index < OUTPUT_BUFFER_SIZE; i++, index++) {
// Handle quoting quotes and backslashes
if(quotable && (toAdd[i] == '"' || toAdd[i] == '\\')) {
if(index == OUTPUT_BUFFER_SIZE - 1) // No room!
return;
buffer[index] = '\\';
index++;
}

buffer[index] = toAdd[i];
}

if(quotable) {
addQuote();
}
}


// Add to output buffer

template <typename T>
Expand Down Expand Up @@ -1892,6 +1973,7 @@ void setMQTTServer(char* new_mqtt_server){
char state;
uint16_t value;
boolean pin_selected;
String content_type;

char* remote_server;
int port;
Expand Down Expand Up @@ -1937,6 +2019,12 @@ void setMQTTServer(char* new_mqtt_server){
int (*functions[NUMBER_FUNCTIONS])(String);
char * functions_names[NUMBER_FUNCTIONS];

// Pages array
uint8_t pages_index;
String (*pages[NUMBER_PAGES])(String);
char * pages_names[NUMBER_PAGES];
char * pages_content_type[NUMBER_PAGES];

// Memory debug
#if defined(ESP8266) || defined(ESP32)
int freeMemory;
Expand Down
101 changes: 101 additions & 0 deletions examples/ESP8266_page/ESP8266_page.ino
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/*
This a simple example of the aREST Library for the ESP8266 WiFi chip.
See the README file for more details.

Written in 2015 by Marco Schwartz under a GPL license.
*/

// Import required libraries
#include <ESP8266WiFi.h>
#include <aREST.h>

// Create aREST instance
aREST rest = aREST();

// WiFi parameters
const char* ssid = "your_wifi_network_name";
const char* password = "your_wifi_network_password";

// The port to listen for incoming TCP connections
#define LISTEN_PORT 80

// Create an instance of the server
WiFiServer server(LISTEN_PORT);

// Variables to be exposed to the API
int temperature;
int humidity;

// Declare functions to be exposed to the API
int ledControl(String command);

void setup(void)
{
// Start Serial
Serial.begin(115200);

// Init variables and expose them to REST API
temperature = 24;
humidity = 40;
rest.variable("temperature",&temperature);
rest.variable("humidity",&humidity);

// Function to be exposed
rest.function("led",ledControl);

// page to be returned
rest.page("html","text/html",Html);

// Give name & ID to the device (ID should be 6 characters long)
rest.set_id("1");
rest.set_name("esp8266");

// Connect to WiFi
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.println("WiFi connected");

// Start the server
server.begin();
Serial.println("Server started");

// Print the IP address
Serial.println(WiFi.localIP());
}

void loop() {

// Handle REST calls
WiFiClient client = server.available();
if (!client) {
return;
}
while(!client.available()){
delay(1);
}
rest.handle(client);

}

String Html(String command) {
String ret = "<html><head><title>This is a web page</title></head><body><h1>Great page!</h1><img src='data:image/png;base64, iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==' />And this is text with Base64 encoded image<br>";
ret += "Temperature: " + String(temperature);
ret += "<br>Humidity: " + String(humidity);
ret += "<br>Command passed: " + command;
ret += "</body></html>";
return ret;
}

// Custom function accessible by the API
int ledControl(String command) {

// Get state from command
int state = command.toInt();

digitalWrite(6,state);
return 1;
}