You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

215 lines
8.4 KiB

#include <WiFi.h>
#include <ESPmDNS.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include <Preferences.h>
#include "shelf.h"
#include "webserver.h"
using namespace std;
extern Shelf* shelf;
extern volatile SemaphoreHandle_t xPreferencesSemaphore;
Webserver::Webserver(void)
: server(80) {
}
String processor(const String& var)
{
if(var == "HELLO_FROM_TEMPLATE")
return F("Hello world!");
if (var.startsWith("ITEM")) {
int num = var.substring(4,5).toInt();
if (var.endsWith("_NAME")) {
return shelf->item.at(num)->name;
} else if (var.endsWith("_QUANTITY")) {
return String(shelf->item.at(num)->quantity);
} else if (var.endsWith("_PRICE")) {
return String(shelf->item.at(num)->price);
}
}
return String();
}
void requestDispense(AsyncWebServerRequest *request) {
if(request->hasParam("num")) {
AsyncWebParameter* p = request->getParam("num");
const int num = atoi( p->value().c_str() );
shelf->dispense(num);
}
request->send(200, "text/plain", "OK");
}
const char index_html[] PROGMEM =
"<html><head><title>Vending Machine</title><style>table, th, td { border: 2px solid black; }</style></head>"
"<body><h1>Vending Machine</h1><table>"
"<tr><td><strong>%ITEM0_NAME%</strong><br/><span style='text-align:left; float:left;'>%ITEM0_QUANTITY%</span><span style='text-align:right; float:right;'>%ITEM0_PRICE%</span></td><td><strong>%ITEM1_NAME%</strong><br/><span style='text-align:left; float:left;'>%ITEM1_QUANTITY%</span><span style='text-align:right; float:right;'>%ITEM1_PRICE%</span></td><td><strong>%ITEM2_NAME%</strong><br/><span style='text-align:left; float:left;'>%ITEM2_QUANTITY%</span><span style='text-align:right; float:right;'>%ITEM2_PRICE%</span></td></tr>"
"<tr><td><a href=\"/dispense?num=0\">DISPENSE</a></td><td><a href=\"/dispense?num=1\">DISPENSE</a></td><td><a href=\"/dispense?num=2\">DISPENSE</a></td></tr>"
"<tr><td><strong>%ITEM3_NAME%</strong><br/><span style='text-align:left; float:left;'>%ITEM3_QUANTITY%</span><span style='text-align:right; float:right;'>%ITEM3_PRICE%</span></td><td><strong>%ITEM4_NAME%</strong><br/><span style='text-align:left; float:left;'>%ITEM4_QUANTITY%</span><span style='text-align:right; float:right;'>%ITEM4_PRICE%</span></td><td><strong>%ITEM5_NAME%</strong><br/><span style='text-align:left; float:left;'>%ITEM5_QUANTITY%</span><span style='text-align:right; float:right;'>%ITEM5_PRICE%</span></td></tr>"
"<tr><td><a href=\"/dispense?num=3\">DISPENSE</a></td><td><a href=\"/dispense?num=4\">DISPENSE</a></td><td><a href=\"/dispense?num=5\">DISPENSE</a></td></tr>"
"</table><hr /><a href=\"/configure\">configure</a>"
"</body></html>"; // large char array, tested with 14k
void Webserver::start(void) {
startMDNS();
// respond to GET requests on URL /heap
server.on("/heap", HTTP_GET, [](AsyncWebServerRequest *request){
request->send(200, "text/plain", String(ESP.getFreeHeap()));
});
//First request will return 0 results unless you start scan from somewhere else (loop/setup)
//Do not request more often than 3-5 seconds
server.on("/scan", HTTP_GET, [](AsyncWebServerRequest *request){
String json = "[";
int n = WiFi.scanComplete();
if(n == -2){
WiFi.scanNetworks(true);
} else if(n){
for (int i = 0; i < n; ++i){
if(i) json += ",";
json += "{";
json += "\"rssi\":"+String(WiFi.RSSI(i));
json += ",\"ssid\":\""+WiFi.SSID(i)+"\"";
json += ",\"bssid\":\""+WiFi.BSSIDstr(i)+"\"";
json += ",\"channel\":"+String(WiFi.channel(i));
json += ",\"secure\":"+String(WiFi.encryptionType(i));
// json += ",\"hidden\":"+String(WiFi.isHidden(i)?"true":"false");
json += "}";
}
WiFi.scanDelete();
if(WiFi.scanComplete() == -2){
WiFi.scanNetworks(true);
}
}
json += "]";
request->send(200, "text/json", json);
json = String();
});
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
request->send_P(200, "text/html", index_html, processor);
});
server.on("/dispense", HTTP_GET, requestDispense);
// Config
server.on("/configure", HTTP_GET, [](AsyncWebServerRequest *request){
AsyncResponseStream *response = request->beginResponseStream("text/html");
response->printf("<!DOCTYPE html><html><head><title>Vending Machine at %s</title></head><body>", request->url().c_str());
response->print("<form method='POST' action='/configure' enctype='multipart/form-data'>");
for(int i=0; i<shelf->num_items; i++) {
response->printf("Item %u: ", i);
response->printf("<input type='text' name='item%u_name' value='%s'>", i, shelf->item.at(i)->name);
response->printf("<input type='text' name='item%u_quantity' value='%u'>", i, shelf->item.at(i)->quantity);
response->printf("<input type='text' name='item%u_price' value='%u'>", i, shelf->item.at(i)->price);
response->print("<hr />");
}
response->print("<input type='submit' value='Update'></form>");
response->print("</body></html>");
request->send(response);
});
server.on("/configure", HTTP_POST, [](AsyncWebServerRequest *request){
if (xSemaphoreTake(xPreferencesSemaphore, portMAX_DELAY) == pdTRUE) {
Preferences preferences;
preferences.begin("vending-items", false);
for (int i=0; i<shelf->num_items; i++) {
char buffer[50];
sprintf(buffer, "item%u_name", i);
if(request->hasParam(buffer, true)) {
AsyncWebParameter* p = request->getParam(buffer, true);
const char* name = p->value().c_str();
preferences.putString(buffer, name);
}
sprintf(buffer, "item%u_quantity", i);
if(request->hasParam(buffer, true)) {
AsyncWebParameter* p = request->getParam(buffer, true);
const int num = atoi( p->value().c_str() );
preferences.putUInt(buffer, num);
}
sprintf(buffer, "item%u_price", i);
if(request->hasParam(buffer, true)) {
AsyncWebParameter* p = request->getParam(buffer, true);
const int price = atoi( p->value().c_str() );
preferences.putUInt(buffer, price);
}
}
preferences.end();
xSemaphoreGive(xPreferencesSemaphore);
shelf->reload();
}
request->send(200, "text/html", "OK");
});
server.onNotFound([](AsyncWebServerRequest *request){
Serial.printf("NOT_FOUND: ");
if(request->method() == HTTP_GET)
Serial.printf("GET");
else if(request->method() == HTTP_POST)
Serial.printf("POST");
else if(request->method() == HTTP_DELETE)
Serial.printf("DELETE");
else if(request->method() == HTTP_PUT)
Serial.printf("PUT");
else if(request->method() == HTTP_PATCH)
Serial.printf("PATCH");
else if(request->method() == HTTP_HEAD)
Serial.printf("HEAD");
else if(request->method() == HTTP_OPTIONS)
Serial.printf("OPTIONS");
else
Serial.printf("UNKNOWN");
Serial.printf(" http://%s%s\n", request->host().c_str(), request->url().c_str());
if(request->contentLength()){
Serial.printf("_CONTENT_TYPE: %s\n", request->contentType().c_str());
Serial.printf("_CONTENT_LENGTH: %u\n", request->contentLength());
}
int headers = request->headers();
int i;
for(i=0;i<headers;i++){
AsyncWebHeader* h = request->getHeader(i);
Serial.printf("_HEADER[%s]: %s\n", h->name().c_str(), h->value().c_str());
}
int params = request->params();
for(i=0;i<params;i++){
AsyncWebParameter* p = request->getParam(i);
if(p->isFile()){
Serial.printf("_FILE[%s]: %s, size: %u\n", p->name().c_str(), p->value().c_str(), p->size());
} else if(p->isPost()){
Serial.printf("_POST[%s]: %s\n", p->name().c_str(), p->value().c_str());
} else {
Serial.printf("_GET[%s]: %s\n", p->name().c_str(), p->value().c_str());
}
}
request->send(404);
});
server.begin();
Serial.println("Webserver started.");
}
void Webserver::startMDNS(void) {
// Set up mDNS responder:
// - first argument is the domain name, in this example
// the fully-qualified domain name is "esp8266.local"
// - second argument is the IP address to advertise
// we send our IP address on the WiFi network
if (!MDNS.begin("esp32")) {
Serial.println("Error setting up MDNS responder!");
while(1) {
delay(1000);
}
}
Serial.println("mDNS responder started");
// Add service to MDNS-SD
MDNS.addService("http", "tcp", 80);
}