Skocz do zawartości
rziomber

AJAX + (+ JSON) + ESP8266 = automatyczna aktualizacja odczytów czujników (BME280) bez konieczności odświeżania strony

Pomocna odpowiedź

AJAX umożliwia przekazywanie danych pomiędzy klientem a serwerem WWW bez konieczności przeładowania strony. Dodając do tego timer w JavaScript możemy uzyskać świeże dane na stronie generowanej przez ESP8266. Na początek stwórzmy w PHP najprostszą stronę WWW prezentującą aktualną godzinę pobieraną z serwera (nie "JavaScriptovy" czas z przeglądarki - w końcu docelowo chcemy pobierać dane z czujników podłączonych do "serwera WWW" postawionego na ESP8266):

<?
if ($_REQUEST["time"]) {
  echo date("G:i:s");
  exit;
}
?>

<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>AJAX test</title>
</head>
<body>
<span id="time"></span>

<script>
myTimer();
var myVar = setInterval(myTimer, 1000);

function myTimer() {
var xhttp = new XMLHttpRequest();
  xhttp.onreadystatechange = function() {
    if (this.readyState == 4 && this.status == 200) {
      document.getElementById("time").innerHTML =
      this.responseText;
      }
  };
  xhttp.open("GET", "test.php?time=1", true);
  xhttp.send();
}
</script>

</body>
</html>

1.thumb.png.b04797e73a40b960147625218239ea4d.png

Timer co sekundę przywołuje funkcję myTimer();

var myVar = setInterval(myTimer, 1000);

a ta pobiera zawartość podstrony test.php?time=1 i wstawia ją do elementu o nazwie "time" (document.getElementById("time").innerHTML =  this.responseText;).

Mamy tu tylko jeden przekazywany parametr. Co zrobić, by aktualizować kilka różnych wartości? Przywykłem do podstrony przekazującej parametry rozdzielone znakiem ; (odczyt1;odczyt2;odczyt3). Dzięki JavaScriptowej funkcji split możemy podzielić taki ciąg znaków na tablice, a potem przydzielić jej części do elementów SPAN o określonym ID.

<?
if ($_REQUEST["time"]) {
  echo date("d.m.y;G:i:s");
  exit;
}
?>

<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>AJAX test</title>
</head>
<body>
Date: <span id="date"></span><br>
Time: <span id="time"></span>

<script>
myTimer();
var myVar = setInterval(myTimer, 1000);

function myTimer() {
var xhttp = new XMLHttpRequest();
  xhttp.onreadystatechange = function() {
    if (this.readyState == 4 && this.status == 200) {
		var str = this.responseText; 
		var values = str.split(";");
		      document.getElementById("date").innerHTML =
      values[0];
	        document.getElementById("time").innerHTML =
      values[1];
      }
  };
  xhttp.open("GET", "test.php?time=1", true);
  xhttp.send();
}
</script>

</body>
</html>

  2.thumb.png.7da4d34f6162996fd0065bc7d61c8574.png

Działa! PHP po zainstalowania serwera np NGINX możemy uruchomić na Raspberry Pi. W połączeniu (komenda system()) z zewnętrznym skryptem Python lub programem w C korzystającym z WiringPi otrzymamy stronę z odczytami czujników hostowaną na Raspberry Pi!

Spróbujmy wreszcie zaprogramować mikrokontroler ESP8266. Podłączmy najpierw czujnik BME280.

5.thumb.png.9dd3dc538233812817b8b315c4fcd815.pngCRW_0069.thumb.jpg.cf159ba95724908e9468736f21204f7e.jpg

/*
I2C
D4 - SCL
D3 - SDA
*/

#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <ESP8266WebServer.h>
#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>

char* ssid = "Weather";
//const char *password = "";

ESP8266WebServer server(80);
Adafruit_BME280 bme;

void setup() {
Serial.begin(9600);
Wire.begin(D3, D4);
Wire.setClock(100000);
if (!bme.begin(0x76)) //changed from default I2C adress 0x77
{
Serial.println("Nie odnaleziono czujnika BMP085 / BMP180");
while (1) {
}
}
IPAddress apIP(192, 168, 1, 1);
WiFi.softAPConfig(apIP, apIP, IPAddress(255, 255, 255, 0));
// WiFi.softAP(ssid, password);
WiFi.softAP(ssid);
server.on("/", handleRoot);
server.on("/sensors", handleSensors);
server.begin();
}

void loop() {
server.handleClient();
}

void handleRoot() {
String content = "<html> <head><title>Weather</title></head><body>";
content += "<DIV style=\"display:table; font-size: large;\"><DIV style=\"border-style: solid;\">BME280:<BR>Temperature: <span id=\"tempBME\"></span>C<br>Humidity: <span id=\"humBME\"></span>%<br>Pressure: <span id=\"presBME\"></span>Pa<br></DIV>";
content += "<script>myTimer();var myVar = setInterval(myTimer, 1000);function myTimer() {var xhttp = new XMLHttpRequest(); xhttp.onreadystatechange = function() { if (this.readyState == 4 && this.status == 200) { var str = this.responseText; var values = str.split(\";\"); document.getElementById(\"tempBME\").innerHTML = values[0]; document.getElementById(\"humBME\").innerHTML = values[1]; document.getElementById(\"presBME\").innerHTML = values[2];} }; xhttp.open(\"GET\", \"sensors\", true); xhttp.send();}</script>";
content += "</body></html>";
server.send(200, "text/html", content);
}

void handleSensors() {
String content = String(bme.readTemperature()) + ";" + String(bme.readHumidity()) + ";" + String((int)bme.readPressure()) + ";";
server.send(200, "text/html", content);
}

3.thumb.png.7b8478be227341c49602e14b6875486f.png

Rozdzielanie danych przecinkiem czy średnikiem nie jest sposobem "zbyt profesjonalnym". Przy dużej ilości zmiennych łatwo też o pomyłkę. Dlatego lepiej wtedy stosować bardziej odpowiednie formaty danych: JSON czy XML. Ze względu na "bliskość" JSON z JavaScript skupię się tylko na nim. Gotowa biblioteka ArduinoJson wygeneruje dane za nas. Więcej informacji znajdziemy w rozdziale Serialize with ArduinoJson dokumentacji technicznej.

/*
  I2C
  D4 - SCL
  D3 - SDA
*/

#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <ESP8266WebServer.h>
#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>
#include <ArduinoJson.h> //https://github.com/bblanchon/ArduinoJson

char* ssid = "Weather";
//const char *password = "";

ESP8266WebServer server(80);
Adafruit_BME280 bme;

void setup() {
  Serial.begin(9600);
  Wire.begin(D3, D4);
  Wire.setClock(100000);
  if (!bme.begin(0x76)) //changed from default I2C adress 0x77
  {
    Serial.println("Nie odnaleziono czujnika BMP085 / BMP180");
    while (1) {
    }
  }
  IPAddress apIP(192, 168, 1, 1);
  WiFi.softAPConfig(apIP, apIP, IPAddress(255, 255, 255, 0));
  // WiFi.softAP(ssid, password);
  WiFi.softAP(ssid);
  server.on("/", handleRoot);
  server.on("/sensors", handleSensors);
  server.begin();
}

void loop() {
  server.handleClient();
}

void handleRoot() {
  String content = "<html> <head><title>Weather</title></head><body>";
  content += "<DIV style=\"display:table; font-size: large;\"><DIV style=\"border-style: solid;\">BME280:<BR>Temperature: <span id=\"tempBME\"></span>C<br>Humidity: <span id=\"humBME\"></span>%<br>Pressure: <span id=\"presBME\"></span>Pa<br></DIV>";
  content += "<script>myTimer();var myVar = setInterval(myTimer, 1000);function myTimer() {var xhttp = new XMLHttpRequest(); xhttp.onreadystatechange = function() { if (this.readyState == 4 && this.status == 200) { var str = this.responseText; var values = JSON.parse(str); document.getElementById(\"tempBME\").innerHTML = values.temp; document.getElementById(\"humBME\").innerHTML = values.hum; document.getElementById(\"presBME\").innerHTML = values.press;} }; xhttp.open(\"GET\", \"sensors\", true); xhttp.send();}</script>";
  content += "</body></html>";
  server.send(200, "text/html", content);
}

void handleSensors() {
  String content;
  StaticJsonBuffer<400> jsonBuffer;
  JsonObject& root = jsonBuffer.createObject();
  root["temp"] = bme.readTemperature();
  root["hum"] = bme.readHumidity();
  root["press"] = (int)bme.readPressure();

  root.printTo(content);
  server.send(200, "text/html", content);
}

Strona prezentować się będzie tak samo jak poprzednio. Zyskamy za to wygodny i czytelny sposób dopisywania nowych danych

root["temp"] = bme.readTemperature();

oraz ich odczytywania w kodzie źródłowym strony:

document.getElementById(\"tempBME\").innerHTML = values.temp;

Nie przejmujemy się już numeracją elementów długiej tablicy.

Sytuację możemy też odwrócić tworząc stronę WWW, której formularz sterować będzie pracą naszego fizycznego urządzenia. Oczywiście nadal niekonieczne będzie przeładowanie strony, by wysłać dane.

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

char* ssid = "Weather";
//const char *password = "";
int ledValue = 0;

ESP8266WebServer server(80);

void setup() {
  Serial.begin(9600);
  IPAddress apIP(192, 168, 1, 1);
  WiFi.softAPConfig(apIP, apIP, IPAddress(255, 255, 255, 0));
  // WiFi.softAP(ssid, password);
  WiFi.softAP(ssid);
  server.on("/", handleRoot);
  server.begin();
  pinMode(D1, OUTPUT);
  analogWriteRange(100); //http://esp8266.github.io/Arduino/versions/2.0.0/doc/reference.html
  analogWriteFreq(500);
}

void loop() {
  server.handleClient();
  analogWrite(D1, ledValue);
}

void handleRoot() {
  if (server.hasArg("ledVal") && server.arg("ledVal").toInt() >= 0 && server.arg("ledVal").toInt() <= 100) {
    ledValue = server.arg("ledVal").toInt();
    Serial.print("ledVal ");
    Serial.println(ledValue);
    server.send(200, "text/html", "");
    return;
  }

  //https://www.w3schools.com/howto/howto_js_rangeslider.asp
  //https://stackoverflow.com/questions/9713058/send-post-data-using-xmlhttprequest

  String content = "<!DOCTYPE html>"
                   "<html>"
                   "<head>"
                   "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">"
                   "<title>Luminosity setter</title>"
                   "<style>"
                   ".slidecontainer {"
                   "width: 1024px;"
                   "margin: 0 auto;"
                   "text-align: center;"
                   "}"
                   ".slider {"
                   "    -webkit-appearance: none;"
                   "    width: 100%;"
                   "    height: 25px;"
                   "    background: #d3d3d3;"
                   "    outline: none;"
                   "    opacity: 0.7;"
                   "    -webkit-transition: .2s;"
                   "    transition: opacity .2s;"
                   //                 "    margin-left: 20px;"
                   //                  "    margin-right: 20px;"
                   "}"
                   ".slider:hover {"
                   "    opacity: 1;"
                   "}"
                   ".slider::-webkit-slider-thumb {"
                   "    -webkit-appearance: none;"
                   "    appearance: none;"
                   "    width: 25px;"
                   "    height: 25px;"
                   "    background: #4CAF50;"
                   "    cursor: pointer;"
                   "}"
                   ".slider::-moz-range-thumb {"
                   "    width: 25px;"
                   "    height: 25px;"
                   "    background: #4CAF50;"
                   "    cursor: pointer;"
                   "}"
                   "</style>"
                   "</head>"
                   "<body>"
                   "<div class=\"slidecontainer\">"
                   "  <input type=\"range\" min=\"0\" max=\"100\" value=\"" + String(ledValue) + "\" class=\"slider\" id=\"myRange\">"
                   "  <p>Value: <span id=\"demo\"></span>%</p>"
                   "</div>"
                   "<script>"
                   "document.getElementById(\"demo\").innerHTML = document.getElementById(\"myRange\").value;"
                   "document.getElementById(\"myRange\").oninput = function() {"
                   "  document.getElementById(\"demo\").innerHTML = this.value;"
                   "var data = new FormData();"
                   "data.append('ledVal', this.value);"
                   "var xhr = new XMLHttpRequest();"
                   "xhr.open('POST', '/', true);"
                   "xhr.onload = function () {"
                   "    console.log(this.responseText);"
                   "};"
                   "xhr.send(data);"
                   "}"
                   "</script>"
                   "</body>"
                   "</html>";
  server.send(200, "text/html", content);
}

4.thumb.png.0e2abb464bd0bffe008ddb73529d75a7.png

W tym przypadku przesunięcie suwaka na stronie sterującej wywoła natychmiastową zmianę jasności diody LED podłączonej odpowiednim rezystorem do pinu D1.

  • Pomogłeś! 1

Udostępnij ten post


Link to post
Share on other sites

@rziomber, właśnie zaakceptowałem Twój opis, możesz go teraz zgłosić do akcji rabatowej umieszczając link w temacie zbiorczym. Dziękuję za przedstawienie ciekawego projektu, zachęcam do prezentowania kolejnych DIY oraz aktywności na naszym forum 🙂 

Przy okazji proszę inne osoby planujące opis projektów tego typu o wcześniejsze zgłaszanie się w celu wstępnej weryfikacji projektu (procedura jest opisana w ogłoszeniu).

Udostępnij ten post


Link to post
Share on other sites

Dołącz do dyskusji, napisz odpowiedź!

Jeśli masz już konto to zaloguj się teraz, aby opublikować wiadomość jako Ty. Możesz też napisać teraz i zarejestrować się później.
Uwaga: wgrywanie zdjęć i załączników dostępne jest po zalogowaniu!

Gość
Napisz odpowiedź...

×   Wklejony jako tekst z formatowaniem.   Przywróć formatowanie

  Dozwolonych jest tylko 75 emoji.

×   Twój link będzie automatycznie osadzony.   Wyświetlać jako link

×   Twoja poprzednia zawartość została przywrócona.   Wyczyść edytor

×   Nie możesz wkleić zdjęć bezpośrednio. Prześlij lub wstaw obrazy z adresu URL.


×
×
  • Utwórz nowe...