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
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);
|
|
}
|
|
|