ESP8266 měřící bod

Python, Go: přijímání JSONů po HTTP

Popularita výkoných mikrokontrolerů (Arduino, ESP8266/ESP32, …) přivedla nejednoho bastlíře k pokusům s internetem věcí (IoT). Občas je potřeba vyřešit posílání dat co nejjednodušším způsobem a použití JSONu přes POST je asi nejtriviálnější možnost.

Potřebuji začít řešit jeden nápad a nevidím důvod se nepodělit o používané postupy. Netvrdím, že jde o nejlepší možný postup na světě, připadá mi však efektivnější, než to co jsem dříve navrhl a kamarád nápad akceptoval (odesílání dat přes sftp a zpracování dat v pravidelných intervalech). Bohužel jsem narychlo navrhl asi nejhorší možnou variantu na světě, jež třeba nedovoluje okamžitou reakci na straně klienta.

Odesílání dat z ESP8266 zařízení

Na straně měřící sondy je běžný mikropočítač s Espresiff ESP8266 a čidla pro teplotu, vlhkost i tlak. Část starající se o odesílání je tak primitivní, jak to je možné:

const char* ssid = "moje_wifi";
const char* password = "tajny.K1ic";

const char* srvURL = "http://10.11.12.13:8085/submdata/";

HTTPClient http;

... //setup(), loop(), ...

void submData(float hum, float tem, float prs) {
  http.begin(srvURL);      //Specify request destination
  http.addHeader("Content-Type", "application/json");

  //String json = "{\"h\": " + String(hum) + ", \"t\": " + String(tem) + " }";

  int httpCode = http.POST("{\"h\": " + String(hum) + ", \"t\": " + String(tem) + ", \"p\": " + String(prs) +  " }");
  String payload = http.getString();

  Serial.print("httpCode: ");
  Serial.println(httpCode);
  Serial.print("payload: ");
  Serial.println(payload);

  http.end();
  return;
}

Neřeším ssl, autentizaci a další detaily, je to jen testovací režim, protože mě zajímá jak přijmout data na druhé straně. Dost dobře si dovedu představit nemít na straně „měřící stanice“ ESP8266, ale třeba Raspberry Pi Zero, jež bude sbírat komplexnější data, nějakým způsobem na ně reagovat a zároveň je odesílat. Tím nechci ESP8266, nebo nové ESP32, podceňovat, dá se s nimi udělat mnohé.

Příjem dat pro jejich další zpracování

PHP

Pokud se jedná o běžný webový server, kde běží některá varianta s PHP, tak snad každý programátor umí zpracovat formulářová data zaslaná POST požadavkem (superglobální pole $_POST[]).

Osobně nejsem zastánce PHP cesty, narazil jsem na pár nejednoznačností v preferovaném PDO (pro PostgreSQL jsem se musel „drbat levou nohou za pravým uchem“) a výkon by při desítkách klientů odesílajících data v krátkých intervalech nemusel být to pravé ořechové.

Python

Pokud je potřebné doplnit webovou aplikaci ve Flasku (pro takové vizualizace dat jej preferuji), nebo jiném webovém frameworku, obvykle postačí doplnit route ve stylu:

@app.route('/submdata/', methods=['POST','GET'])
def submdata():
    js = json.loads(str(request.data,'utf-8'))
    #print jen pro ukázku, normálně uložení/zpracování dat
    print('data:', request.data)
    print('temperature:', js["t"])
    print('humidity: ', js["h"])
    return(...)

Pokud jede web na jiném serveru, je napsaný v jiném programovací jazyce, nebo to prostě chcete mít oddělené a preferujete Python, je potřebné spustit httpserver v němž obsloužíte POST požadavek.

from http.server import BaseHTTPRequestHandler, HTTPServer
import json
import psycopg2

class S(BaseHTTPRequestHandler):

    def _set_headers(self):
        self.send_response(200)
        self.send_header('Content-type', 'text/html')
        self.end_headers()

    def do_GET(self):
        self.send_response(404)

    def do_POST(self):
        if self.path == '/submdata/':
            content_len = int(self.headers['Content-Length'])
            post_body = self.rfile.read(content_len)
            js = json.loads(str((post_body),'utf-8'))
            print ("JSON: %s" % (js))
        self.send_response(200)

def run(server_class=HTTPServer, handler_class=S, port=8085):
    server_address = ('', port)
    httpd = server_class(server_address, handler_class)
    try:
        httpd.serve_forever()
    except KeyboardInterrupt:
        pass
    httpd.server_close()

if(__name__=='__main__'):
    run()

Vynechal jsem nepodstatné části, třeba uložení dat do (nějaké) databáze. Stejně tak neřeším https, nebo autentizaci, či zahazuji GET požadavky, podstatné je jen to, jak krátký kód (do_POST) stačí pro odchycení dat. Naslouchání je nastavené na port 8085, protože pro standarní port 80 by musel script běžet pod superuživatelem (root).

Go (aka golang)

V jazyce Go je vše podobně jednoduché, jako v Pythonu:

package main

import (
    "fmt"
    "net/http"
    "encoding/json"
)

type env_struct struct {
    T float64
    H float64
}

func main(){
    http.HandleFunc("/submdata/", fetchPost)
    http.ListenAndServe(":8085",nil)
}

func fetchPost(wr http.ResponseWriter, re *http.Request){
    var s env_struct
    err := json.NewDecoder(re.Body).Decode(&s)
    if(err != nil){
        panic(err)
    }
    fmt.Println("values: ")
    fmt.Println("Temperature: ", s.T)
    fmt.Println("Humidity: ", s.H)
    fmt.Println("===================")
}

Go od verze 1.6 podporuje https2, bez obezliček (Python/Flask), nebo „velkého serveru“ (PHP).

Pokud bych čekal maximálně nižší desítky klientů, zvolil bych Python, ve větším měřítku jednoznačně jazyk Go; je kompilovaný a v případě delšího zpracování (nejen uložení do databáze) bych využil i možností paralelního zpracování požadavků.

Závěrem

Vytvořit POST požadavek (odesílaný formulář) je snadné (v některých jazycích snadnější, než v jiných). Zpětně mě tak trochu štve, že v jednom projektu mě okamžitě nenapadlo posílat data přes POST, hodnoty by byly hned zpracovány. Při potvrzení, tj. po zpracovaném importu, by bylo možné poslat nějakou zpětnou reakci pro měřící stanici (třeba ať si synchronizuje čas).

Samozřejmě že vím, na co je MQTT, ale pro „jednosměrné tlačení dat“ s občasnou zpětnou vazbou mi implementačně připadá jednodušší použít http/https i přes trochu větší síťový provoz.

Flattr this!