Nous développons un site Web pour un microcontrôleur



Avec l'arrivée de divers types de prises intelligentes, d'ampoules et d'autres appareils similaires dans nos vies, le besoin de sites Web sur microcontrôleurs est devenu indéniable. Et grâce au projet lwIP (et à son petit frère uIP), vous ne surprendrez personne avec une telle fonctionnalité. Mais comme lwIP vise à minimiser les ressources, en termes de conception, de fonctionnalité, ainsi que d'ergonomie et de développement, ces sites sont loin derrière ceux auxquels nous sommes habitués. Même pour les systèmes embarqués, comparez, par exemple, avec un site d'administration sur les routeurs les moins chers. Dans cet article, nous allons essayer de développer un site sous Linux pour certains appareils intelligents et de l'exécuter sur un microcontrôleur.



Pour fonctionner sur un microcontrôleur, nous utiliserons Embox . Ce RTOS comprend un serveur HTTP compatible CGI. Nous utiliserons le serveur HTTP intégré à python comme serveur HTTP sous Linux.



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





Site statique



Commençons par un simple site statique composé d'une ou plusieurs pages.

Tout est simple ici, créons un dossier et index.html dedans. Ce fichier sera téléchargé par défaut si seule l'adresse du site est spécifiée dans le navigateur.



$ ls website/
em_big.png  index.html

      
      





Le site contiendra également le logo Embox, le fichier «em_big.png», que nous intégrerons dans le html.



Commençons le serveur http:



python3 -m http.server -d website/
      
      





Passons à localhost: 8000 dans le navigateur







, ajoutons maintenant notre site statique au système de fichiers Embox. Cela peut être fait en copiant notre dossier dans le dossier rootfs / template (le modèle actuel se trouve dans le dossier conf / rootfs). Ou créez un module spécifiant les fichiers pour rootfs qu'il contient.



$ ls website/
em_big.png  index.html  Mybuild

      
      





Contenu de Mybuild.



package embox.demo

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





Par souci de simplicité, nous mettrons notre site directement dans le dossier racine (annotation @InitFs sans paramètres).



Nous devons également inclure notre site dans le fichier de configuration mods.conf et y ajouter le serveur httd lui-même:



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





Commençons également le serveur avec notre site Web lors du démarrage du système. Pour ce faire, ajoutez une ligne au fichier conf / system_start.inc:



"service httpd /",
      
      





Naturellement, toutes ces manipulations doivent être effectuées avec la configuration de la carte. Après cela, nous collectons et courons. Nous allons dans le navigateur à l'adresse de votre board. Dans mon cas, c'est 192.168.2.128



Et nous avons la même image que pour le site







local.Nous ne sommes pas des spécialistes du développement Web, mais nous avons entendu dire que divers frameworks sont utilisés pour créer de beaux sites Web. Par exemple, AngularJS est souvent utilisé . Par conséquent, nous donnerons d'autres exemples de son utilisation. Mais en même temps, nous n'entrerons pas dans les détails et ne nous excuserons pas si quelque part nous nous sommes fortement adaptés à la conception Web.



Quel que soit le contenu statique que nous mettons dans le dossier du site, par exemple des fichiers js ou css, nous pouvons l'utiliser sans effort supplémentaire.



Ajoutons app.js (un site angulaire) à notre site et y ajoutons quelques onglets. Nous placerons les pages de ces onglets dans le dossier partials, les images dans le dossier images / et les fichiers css dans css /.



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





Lançons notre site Web.







D'accord, le site semble beaucoup plus familier et agréable. Et tout cela se fait du côté du navigateur. Comme nous l'avons dit, tout le contexte est toujours statique. Et nous pouvons le développer sur l'hébergeur comme un site Web régulier.



Naturellement, vous pouvez utiliser tous les outils de développement des développeurs Web courants. Ainsi, en ouvrant la console dans le navigateur, nous avons trouvé un message d'erreur indiquant que le favicon.ico était manquant: nous avons







découvert qu'il s'agit de l'icône qui s'affiche dans l'onglet du navigateur. Vous pouvez, bien sûr, mettre un fichier avec ce nom, mais parfois vous ne voulez pas dépenser pour cet endroit. Permettez-moi de vous rappeler que nous voulons aussi fonctionner sur des microcontrôleurs où il y a peu de mémoire.



Une recherche sur Internet a immédiatement montré que vous pouvez vous passer d'un fichier, il vous suffit d'ajouter une ligne dans la section head html. Bien que l'erreur n'ait pas interféré, il est toujours agréable d'améliorer un peu le site. Et surtout, nous nous sommes assurés que les outils de développement habituels sont tout à fait applicables à l'approche proposée.



Contenu dynamique



CGI



Passons au contenu dynamique. Common Gateway Interface (CGI) est une interface d'interaction d'un serveur Web avec des utilitaires de ligne de commande, qui permet de créer du contenu dynamique. En d'autres termes, CGI vous permet d'utiliser la sortie des utilitaires pour générer du contenu dynamique.



Jetons un coup d'œil à un script CGI:



#!/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"
      
      





Tout d'abord, l'en-tête http est imprimé sur la sortie standard, puis les données de la page elle-même sont imprimées. la sortie peut être redirigée n'importe où. Vous pouvez simplement exécuter ce script depuis la console. Nous verrons ce qui suit:



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

"Fri Feb  5 20:58:19 2021"
      
      





Et si au lieu de la sortie standard, il s'agit d'une socket, le navigateur recevra ces données.



CGI est souvent implémenté avec des scripts, même les scripts cgi sont dits. Mais ce n'est pas nécessaire, c'est juste que dans les langages de script, de telles choses sont plus rapides et plus pratiques. Un utilitaire fournissant CGI peut être implémenté dans n'importe quelle langue. Et puisque nous nous concentrons sur les microcontrôleurs, par conséquent, nous essayons de prendre soin d'économiser des ressources. Faisons de même en 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;
}
      
      





Si nous compilons ce code et l'exécutons, nous verrons exactement la même sortie que dans le cas du script.



Dans notre app.js, ajoutons un gestionnaire pour appeler un script CGI pour l'un de nos onglets:



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





Une petite nuance pour fonctionner sous Linux en utilisant le serveur python intégré. Nous devons ajouter l'argument --cgi à notre ligne de lancement pour prendre en charge CGI:



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









Mise à jour automatique du contenu dynamique



Examinons maintenant une autre propriété très importante d'un site dynamique: les mises à jour automatiques du contenu. Il existe plusieurs mécanismes pour sa mise en œuvre:



  • Côté serveur inclut (SSI)
  • Événements envoyés par le serveur (SSE)
  • WebSockets
  • Etc


Côté serveur inclut (SSI)



Le côté serveur inclut (SSI) . C'est un langage simple pour créer dynamiquement des pages Web. Les fichiers utilisant SSI sont généralement au format .shtml.



SSI lui-même a même des directives de contrôle, sinon, et ainsi de suite. Mais dans la plupart des exemples de microcontrôleurs que nous avons trouvés, il est utilisé comme suit. Une directive est insérée dans la page .shtml qui recharge périodiquement la page entière. Cela pourrait être, par exemple:



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





Ou:



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





Et d'une manière ou d'une autre, le contenu est généré, par exemple, en définissant un gestionnaire spécial.



L'avantage de cette méthode est sa simplicité et ses besoins en ressources minimales. Mais d'un autre côté, voici un exemple de son apparence.







Le rafraîchissement de la page (voir onglet) est très perceptible. Et recharger la page entière ressemble à une action trop redondante.



Un exemple standard de FreeRTOS est fourni - https://www.freertos.org/FreeRTOS-For-STM32-Connectivity-Line-With-WEB-Server-Example.html



Événements envoyés par le serveur



Les événements envoyés par le serveur (SSE) sont un mécanisme qui permet une connexion semi-duplex (unidirectionnelle) entre un client et un serveur. Le client dans ce cas ouvre une connexion et le serveur l'utilise pour transférer des données vers le client. Dans le même temps, contrairement aux scripts CGI classiques dont le but est de générer et d'envoyer une réponse au client, puis de la compléter, SSE propose un mode «continu». Autrement dit, le serveur peut envoyer autant de données que nécessaire jusqu'à ce qu'il se termine ou que le client ferme la connexion.



Il existe quelques différences mineures par rapport aux scripts CGI classiques. Tout d'abord, l'en-tête http sera légèrement différent:



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





La connexion, comme vous pouvez le voir, n'est pas proche, mais persistante, c'est-à-dire une connexion continue. Pour empêcher le navigateur de mettre les données en cache, vous devez spécifier Cache-Control no-cache. Enfin, vous devez spécifier que le type de données spécial Content-Type text / event-stream est utilisé.



Ce type de données est un format spécial pour SSE :



: this is a test stream

data: some text

data: another message
data: with two lines

      
      





Dans notre cas, les données doivent être regroupées dans la ligne suivante:



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





Notre script CGI ressemblera à:



#!/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
      
      





Sortie si vous exécutez le script:



$ ./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"}
      
      







Et ainsi de suite, une fois par seconde.



Même chose en 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;
}

      
      





Et enfin, nous devons également dire à angular que nous avons SSE, c'est-à-dire modifier le code de notre contrôleur:



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





Nous lançons le site, nous voyons ce qui suit:







Il est à noter que, contrairement à l'utilisation de SSI, la page ne surcharge pas, et les données sont mises à jour en douceur et agréablement pour l'œil.



Démo



Bien entendu, les exemples donnés ne sont pas réels car ils sont très simples. Leur objectif est de montrer la différence entre les approches utilisées sur les microcontrôleurs et dans d'autres systèmes.



Nous avons fait une petite démo avec de vraies tâches. Contrôle des LED, réception des données en temps réel d'un capteur de vitesse angulaire (gyroscope) et d'un onglet avec des informations système.



Le site a été développé sur l'hôte. Il était seulement nécessaire de faire de petites prises pour émuler les LED et les données du capteur. Les données du capteur ne sont que des valeurs aléatoires reçues via le RANDOM standard



#!/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
      
      





Nous stockons simplement l'état des LED dans un fichier.



#!/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))
      
      





La même chose est mise en œuvre de manière triviale dans la variante C. Si vous le souhaitez, vous pouvez voir le code dans le dossier du référentiel (projet / site Web).



Sur le microcontrôleur, bien sûr, on utilise des implémentations qui interagissent avec de vrais périphériques. Mais comme ce ne sont que des commandes et des pilotes, ils ont été débogués séparément. Par conséquent, le transfert même du site vers le microcontrôleur n'a pas pris de temps.



La capture d'écran en cours d'exécution sur l'hôte ressemble à ceci:







Dans une courte vidéo, vous pouvez voir le travail sur un vrai microcontrôleur. Notez qu'il n'y a pas que la communication via http, mais aussi, par exemple, le réglage de la date à l'aide de ntp depuis la ligne de commande dans Embox, et bien sûr la gestion des périphériques.





Indépendamment, tout ce qui est donné dans l'article peut être reproduit selon les instructions sur notre wiki



Conclusion



Dans l'article, nous avons montré qu'il est possible de développer de beaux sites interactifs et de les exécuter sur des microcontrôleurs. De plus, cela peut être fait facilement et rapidement en utilisant tous les outils de développement de l'hôte, puis exécutés à partir de microcontrôleurs. Naturellement, le développement du site peut être effectué par un web designer professionnel, tandis que le développeur embarqué mettra en œuvre la logique de l'appareil. Ce qui est très pratique et permet de gagner du temps sur le marché.



Naturellement, vous devrez payer pour cela. Oui, SSE nécessitera un peu plus de ressources que SSI. Mais avec l'aide d'Embox, nous nous installons facilement dans le STM32F4 sans optimisation et n'utilisons que 128 Ko de RAM. Ils n'ont rien vérifié de moins. Les frais généraux ne sont donc pas si importants. Et la commodité du développement et la qualité du site lui-même sont beaucoup plus élevées. Et en même temps, bien sûr, n'oubliez pas que les microcontrôleurs modernes ont considérablement augmenté et continuent de le faire. Après tout, les appareils doivent être de plus en plus intelligents.



All Articles