Wir entwickeln eine Website für einen Mikrocontroller



Mit dem Aufkommen verschiedener Arten von intelligenten Sockeln, Glühbirnen und ähnlichen Geräten in unserem Leben ist der Bedarf an Websites auf Mikrocontrollern unbestreitbar geworden. Und dank des lwIP-Projekts (und seines jüngeren Bruders uIP) werden Sie niemanden mit solchen Funktionen überraschen. Da lwIP jedoch darauf abzielt, die Ressourcen in Bezug auf Design, Funktionalität sowie Benutzerfreundlichkeit und Entwicklung zu minimieren, bleiben solche Websites weit hinter denen zurück, an die wir gewöhnt sind. Vergleichen Sie beispielsweise auch bei eingebetteten Systemen eine Verwaltungssite auf den billigsten Routern. In diesem Artikel werden wir versuchen, eine Site unter Linux für ein intelligentes Gerät zu entwickeln und auf einem Mikrocontroller auszuführen.



Um auf einem Mikrocontroller zu laufen, verwenden wir Embox . Dieses RTOS enthält einen CGI-fähigen HTTP-Server. Wir werden den in Python integrierten HTTP-Server als HTTP-Server unter Linux verwenden.



python3 -m http.server -d <site folder>
      
      





Statische Stelle



Beginnen wir mit einer einfachen statischen Site, die aus einer oder mehreren Seiten besteht.

Hier ist alles einfach. Erstellen wir einen Ordner und eine index.html. Diese Datei wird standardmäßig heruntergeladen, wenn im Browser nur die Site-Adresse angegeben ist.



$ ls website/
em_big.png  index.html

      
      





Die Site wird auch das Embox-Logo enthalten, die Datei "em_big.png", die wir in das HTML einbetten werden.



Starten wir den http-Server:



python3 -m http.server -d website/
      
      





Gehen wir im Browser zu localhost: 8000.







Fügen wir nun unsere statische Site zum Embox-Dateisystem hinzu. Dies kann durch Kopieren unseres Ordners in den Ordner rootfs / template erfolgen (die aktuelle Vorlage befindet sich im Ordner conf / rootfs). Oder erstellen Sie ein Modul, in dem Dateien für Rootfs angegeben sind.



$ ls website/
em_big.png  index.html  Mybuild

      
      





Inhalt von Mybuild.



package embox.demo

module website {
    @InitFS
    source "index.html",
        "em_big.png",
}
      
      





Der Einfachheit halber legen wir unsere Site direkt im Stammordner ab (Anmerkung @InitFs ohne Parameter).



Wir müssen unsere Site auch in die Konfigurationsdatei mods.conf aufnehmen und dort den httd-Server selbst hinzufügen:



    include embox.cmd.net.httpd    
    include embox.demo.website
      
      





Lassen Sie uns den Server auch während des Systemstarts mit unserer Website starten. Fügen Sie dazu der Datei conf / system_start.inc eine Zeile hinzu:



"service httpd /",
      
      





Natürlich müssen alle diese Manipulationen mit der Konfiguration für die Karte durchgeführt werden. Danach sammeln wir und rennen. Wir gehen im Browser zur Adresse Ihres Boards. In meinem Fall ist es 192.168.2.128.



Und wir haben das gleiche Bild wie für die lokale Website.







Wir sind keine Spezialisten für Webentwicklung, haben jedoch gehört, dass verschiedene Frameworks verwendet werden, um schöne Websites zu erstellen. Beispielsweise wird häufig AngularJS verwendet . Daher werden wir weitere Beispiele dafür geben. Gleichzeitig werden wir jedoch nicht auf Details eingehen und uns im Voraus entschuldigen, wenn wir uns irgendwo stark auf das Webdesign eingestellt haben.



Unabhängig davon, welchen statischen Inhalt wir in den Site-Ordner legen, z. B. JS- oder CSS-Dateien, können wir ihn ohne zusätzlichen Aufwand verwenden.



Fügen wir unserer Site app.js (eine eckige Site) und einige Registerkarten hinzu. Wir werden die Seiten für diese Registerkarten in den Teilordner, Bilder in den Bildern / Ordner und CSS-Dateien in CSS / legen.



$ ls website/
app.js  css  images  index.html  Mybuild  partials
      
      





Lassen Sie uns unsere Website starten.







Stimmen Sie zu, die Seite sieht viel vertrauter und angenehmer aus. Und das alles auf der Browserseite. Wie gesagt, der gesamte Kontext ist immer noch statisch. Und wir können es auf dem Host wie eine normale Website entwickeln.



Natürlich können Sie alle Entwicklungstools gängiger Webentwickler verwenden. Beim Öffnen der Konsole im Browser wurde eine Fehlermeldung gefunden, dass favicon.ico fehlte: Wir haben







festgestellt, dass dies das Symbol ist, das auf der Registerkarte Browser angezeigt wird. Sie können natürlich eine Datei mit diesem Namen einfügen, aber manchmal möchten Sie nicht für diesen Ort ausgeben. Ich möchte Sie daran erinnern, dass wir auch auf Mikrocontrollern laufen möchten, auf denen wenig Speicher vorhanden ist.



Eine Suche im Internet hat sofort gezeigt, dass Sie auf eine Datei verzichten können. Sie müssen lediglich eine Zeile zum HTML-Abschnitt head hinzufügen. Obwohl der Fehler nicht störte, ist es immer angenehm, die Seite ein wenig besser zu machen. Und vor allem haben wir sichergestellt, dass die üblichen Entwicklertools mit dem vorgeschlagenen Ansatz durchaus anwendbar sind.



Dynamischer Inhalt



CGI



Kommen wir zu dynamischen Inhalten. Common Gateway Interface (CGI) ist eine Schnittstelle für die Interaktion eines Webservers mit Befehlszeilenprogrammen, mit der dynamische Inhalte erstellt werden können. Mit anderen Worten, mit CGI können Sie die Ausgabe von Dienstprogrammen verwenden, um dynamischen Inhalt zu generieren.



Schauen wir uns ein CGI-Skript an:



#!/bin/bash

echo -ne "HTTP/1.1 200 OK\r\n"
echo -ne "Content-Type: application/json\r\n"
echo -ne "Connection: Connection: close\r\n"
echo -ne "\r\n"

tm=`LC_ALL=C date +%c`
echo -ne "\"$tm\"\n\n"
      
      





Zuerst wird der http-Header in der Standardausgabe gedruckt, und dann werden die Daten der Seite selbst gedruckt. Die Ausgabe kann überall umgeleitet werden. Sie können dieses Skript einfach über die Konsole ausführen. Wir werden folgendes sehen:



./cgi-bin/gettime
HTTP/1.1 200 OK
Content-Type: application/json
Connection: Connection: close

"Fri Feb  5 20:58:19 2021"
      
      





Und wenn es sich anstelle der Standardausgabe um einen Socket handelt, empfängt der Browser diese Daten.



CGI wird oft mit Skripten implementiert, sogar CGI-Skripten werden gesagt. Dies ist jedoch nicht erforderlich, sondern nur, dass solche Dinge in Skriptsprachen schneller und bequemer sind. Ein Dienstprogramm, das CGI bereitstellt, kann in jeder Sprache implementiert werden. Und da wir uns auf Mikrocontroller konzentrieren, versuchen wir, Ressourcen zu sparen. Machen wir dasselbe in C.



#include <stdio.h>
#include <unistd.h>
#include <string.h>

int main(int argc, char *argv[]) {
    char buf[128];
    char *pbuf;
    struct timeval tv;
    time_t time;

    printf(
        "HTTP/1.1 200 OK\r\n"
        "Content-Type: application/json\r\n"
        "Connection: Connection: close\r\n"
        "\r\n"
    );


    pbuf = buf;

    pbuf += sprintf(pbuf, "\"");

    gettimeofday(&tv, NULL);
    time = tv.tv_sec;
    ctime_r(&time, pbuf);

    strcat(pbuf, "\"\n\n");

    printf("%s", buf);

    return 0;
}
      
      





Wenn wir diesen Code kompilieren und ausführen, sehen wir genau die gleiche Ausgabe wie im Fall des Skripts.



Fügen Sie in unserer app.js einen Handler hinzu, um ein CGI-Skript für eine unserer Registerkarten aufzurufen:



app.controller("SystemCtrl", ['$scope', '$http', function($scope, $http) {
    $scope.time = null;

    $scope.update = function() {
        $http.get('cgi-bin/gettime').then(function (r) {
            $scope.time = r.data;
        });
    };

    $scope.update();
}]);
      
      





Eine kleine Nuance für die Ausführung unter Linux mit dem integrierten Python-Server. Wir müssen das Argument --cgi zu unserer Startlinie hinzufügen, um CGI zu unterstützen:



python3 -m http.server --cgi -d .
      
      









Automatische Aktualisierung von dynamischen Inhalten



Schauen wir uns nun eine weitere sehr wichtige Eigenschaft einer dynamischen Site an - automatische Inhaltsaktualisierungen. Es gibt verschiedene Mechanismen für seine Implementierung:



  • Server Side Includes (SSI)
  • Vom Server gesendete Ereignisse (SSE)
  • WebSockets
  • Usw


Server Side Includes (SSI)



Server Side Includes (SSI) . Es ist eine unkomplizierte Sprache zum dynamischen Erstellen von Webseiten. Normalerweise liegen Dateien mit SSI im HTML-Format vor.



SSI selbst hat sogar Kontrollanweisungen, wenn sonst, und so weiter. In den meisten Mikrocontroller-Beispielen, die wir gefunden haben, wird es jedoch wie folgt verwendet. In die .shtml-Seite wird eine Direktive eingefügt, die die gesamte Seite regelmäßig neu lädt. Dies könnte zum Beispiel sein:



<meta http-equiv="refresh" content="1">
      
      





Oder:



<BODY onLoad="window.setTimeout("location.href='runtime.shtml'",2000)">
      
      





Auf die eine oder andere Weise werden Inhalte beispielsweise durch Festlegen eines speziellen Handlers generiert.



Der Vorteil dieser Methode ist ihre Einfachheit und der minimale Ressourcenbedarf. Auf der anderen Seite ist hier ein Beispiel, wie es aussieht.







Die Seitenaktualisierung (siehe Registerkarte) ist sehr auffällig. Das Neuladen der gesamten Seite scheint eine übermäßig redundante Aktion zu sein.



Ein Standardbeispiel von FreeRTOS wird bereitgestellt - https://www.freertos.org/FreeRTOS-For-STM32-Connectivity-Line-With-WEB-Server-Example.html



Vom Server gesendete Ereignisse



Server-Sended Events (SSE) ist ein Mechanismus, der eine Halbduplex-Verbindung (Einwegverbindung) zwischen einem Client und einem Server ermöglicht. In diesem Fall öffnet der Client eine Verbindung und der Server überträgt damit Daten an den Client. Gleichzeitig bietet SSE im Gegensatz zu klassischen CGI-Skripten, mit denen eine Antwort generiert und an den Client gesendet und anschließend abgeschlossen werden soll, einen „kontinuierlichen“ Modus. Das heißt, der Server kann so viele Daten wie nötig senden, bis er sich entweder selbst abgeschlossen hat oder der Client die Verbindung schließt.



Es gibt einige geringfügige Unterschiede zu regulären CGI-Skripten. Erstens wird der http-Header etwas anders sein:



        "Content-Type: text/event-stream\r\n"
        "Cache-Control: no-cache\r\n"
        "Connection: keep-alive\r\n"
      
      





Wie Sie sehen, ist die Verbindung nicht eng, sondern am Leben, dh eine fortlaufende Verbindung. Um zu verhindern, dass der Browser Daten zwischenspeichert, müssen Sie Cache-Control no-cache angeben. Schließlich müssen Sie angeben, dass der spezielle Datentyp Inhaltstyp Text / Ereignisstrom verwendet wird.



Dieser Datentyp ist ein spezielles Format für SSE :



: this is a test stream

data: some text

data: another message
data: with two lines

      
      





In unserem Fall müssen die Daten in die folgende Zeile gepackt werden:



data: { “time”: “<real date>”}
      
      





Unser CGI-Skript sieht folgendermaßen aus:



#!/bin/bash

echo -ne "HTTP/1.1 200 OK\r\n"
echo -ne "Content-Type: text/event-stream\r\n"
echo -ne "Cache-Control: no-cache\r\n"
echo -ne "Connection: keep-alive\r\n"
echo -ne "\r\n"

while true; do
    tm=`LC_ALL=C date +%c`
    echo -ne "data: {\"time\" : \"$tm\"}\n\n" 2>/dev/null || exit 0
    sleep 1
done
      
      





Ausgabe, wenn Sie das Skript ausführen:



$ ./cgi-bin/gettime
HTTP/1.1 200 OK
Content-Type: text/event-stream
Cache-Control: no-cache
Connection: keep-alive

data: {"time" : "Fri Feb  5 21:48:11 2021"}

data: {"time" : "Fri Feb  5 21:48:12 2021"}

data: {"time" : "Fri Feb  5 21:48:13 2021"}
      
      







Und so weiter, einmal in der Sekunde.



Gleiches in C:



#include <stdio.h>
#include <unistd.h>
#include <string.h>

int main(int argc, char *argv[]) {
    char buf[128];
    char *pbuf;
    struct timeval tv;
    time_t time;

    printf(
        "HTTP/1.1 200 OK\r\n"
        "Content-Type: text/event-stream\r\n"
        "Cache-Control: no-cache\r\n"
        "Connection: keep-alive\r\n"
        "\r\n"
    );

    while (1) {
        pbuf = buf;

        pbuf += sprintf(pbuf, "data: {\"time\" : \"");

        gettimeofday(&tv, NULL);
        time = tv.tv_sec;
        ctime_r(&time, pbuf);

        strcat(pbuf, "\"}\n\n");

        if (0 > printf("%s", buf)) {
            break;
        }

        sleep(1);
    }

    return 0;
}

      
      





Und schließlich müssen wir Angular auch mitteilen, dass wir SSE haben, dh den Code für unseren Controller ändern:



app.controller("SystemCtrl", ['$scope', '$http', function($scope, $http) {
    $scope.time = null;

    var eventCallbackTime = function (msg) {
        $scope.$apply(function () {
            $scope.time = JSON.parse(msg.data).time
        });
    }

    var source_time = new EventSource('/cgi-bin/gettime');
    source_time.addEventListener('message', eventCallbackTime);

    $scope.$on('$destroy', function () {
        source_time.close();
    });

    $scope.update = function() {
    };

    $scope.update();
}]);
      
      





Wenn wir die Site starten, sehen wir Folgendes:







Es fällt auf, dass die Seite im Gegensatz zur Verwendung von SSI nicht überladen wird und die Daten für das Auge reibungslos und angenehm aktualisiert werden.



Demo



Natürlich sind die angegebenen Beispiele nicht real, weil sie sehr einfach sind. Ihr Ziel ist es, den Unterschied zwischen den auf Mikrocontrollern und in anderen Systemen verwendeten Ansätzen aufzuzeigen.



Wir haben eine kleine Demo mit echten Aufgaben gemacht. Steuern von LEDs, Empfangen von Echtzeitdaten von einem Winkelgeschwindigkeitssensor (Gyroskop) und einer Registerkarte mit Systeminformationen.



Die Seite wurde auf dem Host entwickelt. Es mussten nur kleine Stecker hergestellt werden, um die LEDs und Daten vom Sensor zu emulieren. Sensordaten sind nur zufällige Werte, die über das Standard-RANDOM empfangen werden



#!/bin/bash

echo -ne "HTTP/1.1 200 OK\r\n"
echo -ne "Content-Type: text/event-stream\r\n"
echo -ne "Cache-Control: no-cache\r\n"
echo -ne "Connection: keep-alive\r\n"
echo -ne "\r\n"

while true; do
    x=$((1 + $RANDOM % 15000))
    y=$((1 + $RANDOM % 15000))
    z=$((1 + $RANDOM % 15000))
    echo -ne "data: {\"rate\" : \"x:$x y:$y z:$z\"}\n\n" 2>/dev/null || exit 0
    sleep 1
done
      
      





Wir speichern einfach den Status der LEDs in einer Datei.



#!/bin/python3

import cgi
import sys

print("HTTP/1.1 200 OK")
print("Content-Type: text/plain")
print("Connection: close")
print()

form = cgi.FieldStorage()
cmd = form['cmd'].value

if cmd == 'serialize_states':
    with open('cgi-bin/leds.txt', 'r') as f:
        print('[' + f.read() + ']')

elif cmd == 'clr' or cmd == 'set':
    led_nr = int(form['led'].value)

    with open('cgi-bin/leds.txt', 'r+') as f:
        leds = f.read().split(',')
        leds[led_nr] = str(1 if cmd == 'set' else 0)
        f.seek(0)
        f.write(','.join(leds))
      
      





Das gleiche ist in der C-Variante trivial implementiert. Wenn Sie möchten, können Sie den Code im Repository- Ordner (Projekt / Website) sehen.



Auf dem Mikrocontroller werden natürlich Implementierungen verwendet, die mit realen Peripheriegeräten interagieren. Da es sich jedoch nur um Befehle und Treiber handelt, wurden sie separat getestet. Daher dauerte die Übertragung des Standorts auf den Mikrocontroller nicht lange.



Der auf dem Host ausgeführte Screenshot sieht folgendermaßen aus:







In einem kurzen Video können Sie die Arbeit an einem echten Mikrocontroller sehen. Beachten Sie, dass nicht nur über http kommuniziert wird, sondern beispielsweise auch das Datum mithilfe von ntp über die Befehlszeile in Embox festgelegt und natürlich Peripheriegeräte verarbeitet werden.





Unabhängig davon kann alles, was im Artikel angegeben ist, gemäß den Anweisungen in unserem Wiki reproduziert werden



Fazit



In dem Artikel haben wir gezeigt, dass es möglich ist, schöne interaktive Sites zu entwickeln und auf Mikrocontrollern auszuführen. Darüber hinaus kann es einfach und schnell mit allen Entwicklungstools für den Host durchgeführt und dann auf Mikrocontrollern ausgeführt werden. Natürlich kann die Entwicklung der Site von einem professionellen Webdesigner durchgeführt werden, während der eingebettete Entwickler die Logik des Geräts implementiert. Das ist sehr praktisch und spart Zeit bei der Markteinführung.



Dafür müssen Sie natürlich bezahlen. Ja, SSE benötigt etwas mehr Ressourcen als SSI. Mit Hilfe von Embox passen wir jedoch problemlos ohne Optimierung in den STM32F4 und verwenden nur 128 KB RAM. Sie haben nichts weniger überprüft. Der Overhead ist also nicht so groß. Der Komfort der Entwicklung und die Qualität der Website selbst sind viel höher. Und vergessen Sie natürlich nicht, dass moderne Mikrocontroller spürbar gewachsen sind und dies auch weiterhin tun. Schließlich müssen Geräte immer intelligenter werden.



All Articles