WeMOS POST metode

Fra HTX Arduino
Spring til navigation Spring til søgning
WeMOS WiFi Processor
WeMOS Server Connection - Simpel Server - Flere SSID - ESP8266 - Watchdog - Interrupt
Anvendelse LED-eksempel - AD-eksempel - Mail - POST metode - Web Publicering

Den simple måde at sende data til WeMOS er at skrive dem med i URL'en, men det giver sikkerhedsproblemer, og det begrænser også noget hvad man kan sende.

At skrive data i URL'en lægger op til GET-metoden. Hvis man vil undgå det, så skal man bruge POST-metoden.

Dette eksempel tager udgangspunkt i et eksempel fundet ved techtutorialsx.com[1].

Formålet er at opbygge en kode, så man kan sende forskellige ting ind til siden ved hjælp af siden selv.

Hele den gennemgåede kode kan findes i Denne ZIP-fil

Variabler og biblioteker

#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>

ESP8266WebServer server(80);

/*
const char* ssid = "ssid-name";
const char* password = "ssid-password";
*/
#include "WifiHome.h"

boolean LED = LOW;
int ledPin = 2;

Server Connection

Til at begynde med sættes LED'en lige op og der klargøres til at skrive ud i Serial Monitor.

Som beskrevet i WeMOS Server Connection forbindes til Wifi og der sættes en server op. Det er primært i setup() det sker.

Derefter skrives en hjælpetekst til brugeren:

void setup() {

  pinMode(ledPin, OUTPUT);
  digitalWrite(ledPin, HIGH);

  Serial.begin(115200);
  WiFi.begin(ssid, password);  //Connect to the WiFi network

  while (WiFi.status() != WL_CONNECTED) {  //Wait for connection

    delay(500);
    Serial.println("Waiting to connect...");

  }

  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());  //Print the local IP

  Serial.print("Page Address: http://");
  Serial.print(WiFi.localIP());
  Serial.println("/home");  //Print the local IP
}

Det næste er lidt specielt for denne måde at håndtere server på, hvor der etableres forbindelse mellem en del af URL'en (home) og en funktion der servicerer den henvendelse. Det gøres med server.on metoden. :

  server.on("/home", handleHome); //Associate the handler function to the path

Til slut i setup() aktiveres serveren, så man kan henvende sig til den:

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

Connection response

Det eneste der sker i loop() er at der spørges til om en client har henvendt sig med noget. Det er det der sikrer at handleHome bliver kaldt:

void loop() {
  server.handleClient(); //Handling of incoming requests
}

Servicering af home henvendelsen

Først sættes det første af svaret til clienten op, som en start på HTML-koden der skal vises:

void handleHome() { //Handler for the body path

  String message = "<!DOCTYPE HTML>";
  message += "<html>";

Som det øverste på siden angives om der er sket en henvendelse til siden, og hvis der er, så angives indholdet af henvendelsen:

  if (server.hasArg("tekst")== false){ //Check if body received
    message += "Ingen data fra afsender<br />";
  } else {
    message += "Der er sendt teksten:";
    message += server.arg("tekst");
    message += "<br />";
    message += "LED saettes til: ";
    message += server.arg("led");
    message += "<br />";
  }

Hvis der er sendt et argument med i led-feltet, så sættes LED efter det der sendes, og LED'en sættes til det den nu er blevet:

  if (server.arg("led") == "ON") {
    LED = HIGH;
  } else if (server.arg("led") == "OFF") {
    LED = LOW;
  }
  digitalWrite(ledPin, ! LED); // LED tændes ved at trække lavt, derfor inverteringen

LED'ens status skrives så i indholdet hvad status er på LED'en:

  message += "LED er ";
  if(LED == HIGH) {
    message += "On<br />";  
  } else {
    message += "Off<br />";  
  }

Som det sidste indhold sættes en HTML-FORM op, så man faktisk kan sende noget til serveren - der er to input-texter, en til en tekst, der bares skrives i henvendelsen, og så en til led, der kan tænde og slukke LED'en med ON og OFF - det skal skrives med stort, ellers ignoreres det. Til slut sendes indholdet tilbage til clienten:

  message += "<br />";
  message += "<form action='/home' method='POST'>";
  message += "Input tekst:<input type='text' name='tekst' value='Test af POST'><br>";
  message += "LED ON/OFF:<input type='text' name='led' value='ON'><br>";
  message += "<input type='submit' value='Submit'></form>";
  message += "</html>";

  server.send(200, "text/html", message);
}

Test af POST-metoden

Ved en simpel henvendelse til serveren med URL: "http://192.168.0.14/home" får man følgende visning:
Response på en simpel henvendelse
Response på en simpel henvendelse

Hvis man blot klikker Submit får man følgende visning og LED'en tændes:
Response på en henvendelse med ON til LED'en
Response på en henvendelse med ON til LED'en

får man følgende visning:
Response på en henvendelse med OFF til LED'en
Response på en henvendelse med OFF til LED'en

Videre udvikling med flere undersider

Hvis man ønsker at WeMOS'en skal kunne håndtere mere komplekse ting, så kan man lave forskellig respons ved at lave forskellige undersider. Det gøres ved at der etableres ekstra forbindelse til URL'en (fx activate) og en funktion handleActivate() der servicerer den henvendelse. Det gøres ved at lave to kald til server.on metoden. :

  server.on("/home", handleHome); //Associate the handler function to the path

  server.on("/activate", handleActivate); //Associate the handler function to the path

For at man kan komme til at bruge siden activate, og måske sende noget til den, så etableres en ekstra HTML-form inde i handleHome funktionen. Den skal have en ny action der henviser til activate som viste her:

  message += "<form action='/activate' method='POST'>";
  message += "Akteverings-tekst:<input type='text' name='ac' value='Start'><br>";
  message += "<input type='submit' value='Aktiver'></form>";

Der skal så også oprettes den handleActivate, der giver responsen på activate siden - her er den bare lavet uden at den laver noget, men det viser princippet med at man kan komme tilbage til home-siden

void handleActivate() { //Handler for the acticate path

  String message = "<!DOCTYPE HTML>";
  message += "<html>";
  
  if (server.hasArg("ac")== false){ //Check if activate received
    message += "Ingen data fra afsender<br />";
  } else {
    message += "Der er sendt Aktiveringsteksten:";
    message += server.arg("ca");
    message += "<br />";
  }
  message += "Det laver ikke noget i programmet.<br />";
  message += "<form action='/home' method='POST'>";
  message += "<input type='submit' value='Retur'></form>";
  message += "</html>";

  server.send(200, "text/html", message);
}

Udvidelse med løbende opdatering af siden

Denne udvidelse tager udgangspunkt i WeMOS AD-eksempel, men bliver modificeret så det passer ind i strukturen med ESP8266WebServer biblioteket.

I setup() tilføjes en handler til den henvendelse der skal kunne opdatere en værdi på siden - her en AD-værdi:

  server.on("/ad", handleAD); //Associate the handler function to the path

Til handleren skal der være en handleAD() funktion der blot skal svare med AD tallet i plain text:

void handleAD() { //Handler for the ad path

  String message = String(analogRead(A0));  // Svaret er bare AD-tallet som tekst

  server.send(200, "text/plain", message);
}

Inde i visningen af siden skal man have tilføjet et felt, hvor værdien skal vises - her vises AD-værdien, så den får en visning fra starten, og span-tagget får et ID='demo', så AJAX funktionaliteten kan udskifte indholdet i dette felt:

  message += "<p>AD-tallet: <span id='demo'>";
  message += String(analogRead(A0));
  message += "</span></p>";

I starten af siden tilføjes et head-tag, der indeholder en javascript-funktion, der kan foretage en HTTP Request, som beder om AD-siden. Når den returnerer AD-værdien, så sætter funktionen den AD-værdi ind i span-tagget med ID'et demo.

  message += "<head>";
  message += "<script>";
  message += "function loadDoc() {";
  message += "  var xhttp = new XMLHttpRequest();";
  message += "  xhttp.onreadystatechange = function() {";
  message += "    if (this.readyState == 4 && this.status == 200) {";
  message += "      document.getElementById('demo').innerHTML =";
  message += "      this.responseText;";
  message += "    }";
  message += "  };";
  message += "  xhttp.open('GET', 'ad', true);";
  message += "  xhttp.send();";
  message += "}";
  message += "</script>";
  message += "</head>";

Endelig skal den sidste del af AJAX-teknikken etableres ved at der indføres et javascript på siden, som opdaterer siden ved at loadDoc() funktionen kaldes hvert sekund:

  message += "<script>";
  message += "setInterval(loadDoc, 1000)";
  message += "</script>";

Opdatering af Funktionalitet med forms

Hvis man gerne vil lave en form-visning afhængig af en værdi i WeMOS'en, så er det lidt mere tricky, for så kan man ikke bare smide HTML-koden ind på siden, når tilstanden er der - det vil gribe forstyrrende ind i indtastningen af værdien. Dette skal løses med en lidt anden teknik.

Her laves således at man kan indtaste et setpunkt på hvornår formen skal vises, så man kan indtaste hvis AD-værdien er over setpunktet, og formen er ikke vist hvis AD-værdien er under setpunktet.

Her skal laves en henvendelse der angiver om formen skal vises. Der skal etableres en handler i setup():

  server.on("/visning", handleVisning); //Associate the handler function to the path

Hertil skal der så være en handler-funktion handleVisning(), der svarer om formen skal vises:

void handleVisning() { //Handler for the visning path

  String message = " ";
  if (analogRead(A0) >= setpunkt) {
    message += "Vis";  // Svaret er bare Vis
  }

  server.send(200, "text/plain", message);
}

Der skal tilføjes en variabel der indeholder setpunktet, og den initialiseres til 512, som er midt i AD-området:

int setpunkt = 512;

Sammen med javascript-funktionen loadDoc() etableres en ny loadDoc2():

  message += "function loadDoc2() {";
  message += "  var xhttp2 = new XMLHttpRequest();";
  message += "  xhttp2.onreadystatechange = function() {";
  message += "    if (this.readyState == 4 && this.status == 200) {";
  message += "      if (this.responseText.indexOf('Vis') > -1) {";
  message += "        document.getElementById('setForm').style.display = 'block';";
  message += "      } else {";
  message += "        document.getElementById('setForm').style.display = 'none';";
  message += "      }";
  message += "    }";
  message += "  };";
  message += "  xhttp2.open('GET', 'visning', true);";
  message += "  xhttp2.send();";
  message += "}";

Sammen med håndteringen af de andre argumenter på siden laves en håndtering af setp-argumentet fra formen med Setpunkt. Der skal også beskyttes mod at man sætter for højt et setpunkt, så man kan komme til at vise formen igen:

  if (server.hasArg("setp") == true) {
    setpunkt = server.arg("setp").toInt();
    if (setpunkt >= 1024) {
      setpunkt = 512;
    }
  }

Under visningen af LED'en status laves en visning af hvad setpunkt variablen indeholder:

  message += "Setpunkt er : ";
  message += String(setpunkt, DEC);
  message += "<br />";

Mellem de andre forms placeres en ny form, der har et id='setForm', for at man kan henvende sig til den fra loadDoc2(), og den startes med visningen slået fra ved at sætte style til display=none, så man ikke lige får visningen ved første henvendelse, men først når det er konstateret om AD-værdien er over setpunktet:

  message += "<form action='/home' method='POST' id='setForm' style='display=none'>";
  message += "Visnings-setpunkt (1 - 1024):<input type='text' name='setp' value='512'><br>";
  message += "<input type='submit' value='Nyt Setpunkt'></form>";

Visningen i browseren når AD-tallet er over setpunktet er som vist her:
Visningen i browseren af webserveren bygget med WeMOS
Visningen i browseren af webserveren bygget med WeMOS

Udvidelse med sending af mail ved ændring af setpunkt

For at kunne sende information skal der tilføjes en HTTP Client som et bibliotek:

#include <ESP8266HTTPClient.h>

Det skal oprettes en instans af HTTP Cienten, angives en URL hvor der skal sendes og en variabel:

HTTPClient http;

const String serverAddress = "http://htx-elev.ucholstebro.dk/HX-17-el/bar/";      // web address for the webserver

int httpCode;

Der hvor man læser setpunktet ind fra formen og ændrer setpunkt-variablen skal man sende den henvendelse til siden, der kan sende mail som det er beskrevet i WeMOS Mail.

    String stringToSend = serverAddress;  // Serveradressen der skal henvendes til
    stringToSend += "sendMailSetpunkt.php?setpunkt="; // Filnavnet og parameter-navn
    stringToSend += String(setpunkt);                // Indholdet der skal sendes
    http.setTimeout(2000);                                 // Use 2 seconds timeout for HTTP connection
    http.begin(stringToSend);                              // Start HTTP connection and send HTTP header
    httpCode = http.GET();                                 // Get response from HTTP request
    digitalWrite(ledPin, !digitalRead(ledPin));
    if(httpCode > 0)                                       // httpCode will be negative on error
    {      
      Serial.printf("[HTTP] GET... code: %d\n", httpCode); // Response from server is not an error
      if(httpCode == HTTP_CODE_OK) {                       // Response from server is HTTP CODE 200
        String payload = http.getString();
        Serial.println(payload);
      }
    } else {
      Serial.printf("[HTTP] GET... failed, error: %s\n", http.errorToString(httpCode).c_str());
    }
    http.end();
  }

For at det skal fungere, så skal der selvfølgelig ligge den PHP-fil der henvises til, og den skal kunne sende en mail. Her er det lavet så det er en fast mailadresse den bruger, så vær lige venlig ikke at bruge samme adresse (jeg videresender ikke). Indholdet af PHP-filen ser ud som følger:

<?php
/* 
 * Mail example
 * Bent Arnoldsen, Holstebro HTX 2018
 */

if (isset($_GET['setpunkt'])) {
	$setpunkt = $_GET['setpunkt'];
	mail("bar@ucholstebro.dk", "Mail fra Alarmsystem", "Der er sat nyt setpunkt : " . $setpunkt);
}
?>
<html>
<h2>Mail-System til Setpunkt</h2>
</html>

Den samlede kode med alle udvidelserne sammen kan hentes i denne ZIP-fil.

Referencer