Desarrollamos un sitio web para un microcontrolador



Con la llegada de varios tipos de enchufes inteligentes, bombillas y otros dispositivos similares en nuestras vidas, la necesidad de sitios web en microcontroladores se ha vuelto innegable. Y gracias al proyecto lwIP (y su hermano menor uIP), no sorprenderá a nadie con esta funcionalidad. Pero dado que lwIP tiene como objetivo minimizar los recursos, en términos de diseño, funcionalidad, así como usabilidad y desarrollo, estos sitios están muy por detrás de aquellos a los que estamos acostumbrados. Incluso para sistemas integrados, compare, por ejemplo, con un sitio de administración en los enrutadores más baratos. En este artículo intentaremos desarrollar un sitio en Linux para algún dispositivo inteligente y ejecutarlo en un microcontrolador.



Para ejecutar en un microcontrolador, usaremos Embox . Este RTOS incluye un servidor HTTP habilitado para CGI. Usaremos el servidor HTTP integrado en Python como servidor HTTP en Linux.



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





Sitio estático



Comencemos con un sitio estático simple que consta de una o más páginas.

Aquí todo es simple, creemos una carpeta e index.html en ella. Este archivo se descargará de forma predeterminada si solo se especifica la dirección del sitio en el navegador.



$ ls website/
em_big.png  index.html

      
      





El sitio también contendrá el logotipo de Embox, el archivo “em_big.png”, que integraremos en el html.



Iniciemos el servidor http:



python3 -m http.server -d website/
      
      





Vayamos a localhost: 8000 en el navegador







Ahora agreguemos nuestro sitio estático al sistema de archivos Embox. Esto se puede hacer copiando nuestra carpeta a la carpeta rootfs / template (la plantilla actual está en la carpeta conf / rootfs). O cree un módulo que especifique archivos para rootfs en él.



$ ls website/
em_big.png  index.html  Mybuild

      
      





Contenido de Mybuild.



package embox.demo

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





En aras de la simplicidad, pondremos nuestro sitio directamente en la carpeta raíz (anotación @InitFs sin parámetros).



También necesitamos incluir nuestro sitio en el archivo de configuración mods.conf y agregar allí el servidor http:



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





Además, iniciemos el servidor con nuestro sitio web durante el inicio del sistema. Para hacer esto, agregue una línea al archivo conf / system_start.inc:



"service httpd /",
      
      





Naturalmente, todas estas manipulaciones deben realizarse con la configuración de la placa. Después de eso, recolectamos y corremos. Vamos en el navegador a la dirección de tu tablero. En mi caso es 192.168.2.128



Y tenemos la misma imagen que para el sitio local,







no somos especialistas en desarrollo web, pero hemos escuchado que se utilizan varios frameworks para crear sitios web bonitos. Por ejemplo, AngularJS se usa a menudo . Por lo tanto, daremos más ejemplos de su uso. Pero al mismo tiempo, no entraremos en detalles y nos disculparemos de antemano si en algún lugar nos hemos ajustado fuertemente con el diseño web.



Independientemente del contenido estático que coloquemos en la carpeta del sitio, por ejemplo, archivos js o css, podemos usarlo sin ningún esfuerzo adicional.



Agreguemos app.js (un sitio angular) a nuestro sitio y en él un par de pestañas. Pondremos las páginas para estas pestañas en la carpeta de parciales, las imágenes en la carpeta images / y los archivos css en css /.



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





Iniciemos nuestro sitio web.







De acuerdo, el sitio parece mucho más familiar y agradable. Y todo esto se hace en el lado del navegador. Como dijimos, todo el contexto sigue siendo estático. Y podemos desarrollarlo en el host como un sitio web normal.



Naturalmente, puede utilizar todas las herramientas de desarrollo de los desarrolladores web habituales. Entonces, al abrir la consola en el navegador, encontramos un mensaje de error que indicaba que faltaba favicon.ico:







Descubrimos que este es el icono que se muestra en la pestaña del navegador. Por supuesto, puede poner un archivo con este nombre, pero a veces no quiere gastar en este lugar. Permítanme recordarles que queremos ejecutar también en microcontroladores donde hay poca memoria.



Una búsqueda en Internet mostró inmediatamente que puede prescindir de un archivo, solo necesita agregar una línea a la sección html principal. Aunque el error no interfirió, siempre es agradable mejorar un poco el sitio. Y lo más importante, nos aseguramos de que las herramientas de desarrollo habituales sean bastante aplicables con el enfoque propuesto.



Contenido dinámico



CGI



Pasemos al contenido dinámico. Common Gateway Interface (CGI) es una interfaz para la interacción de un servidor web con utilidades de línea de comandos, que permite crear contenido dinámico. En otras palabras, CGI le permite utilizar la salida de utilidades para generar contenido dinámico.



Echemos un vistazo a 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"
      
      





Primero, el encabezado http se imprime en la salida estándar y luego se imprimen los datos de la página. la salida se puede redirigir a cualquier lugar. Simplemente puede ejecutar este script desde la consola. Veremos lo siguiente:



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

"Fri Feb  5 20:58:19 2021"
      
      





Y si en lugar de la salida estándar es socket, entonces el navegador recibirá estos datos.



CGI a menudo se implementa con scripts, incluso se dicen scripts cgi. Pero esto no es necesario, es solo que en los lenguajes de scripting tales cosas son más rápidas y convenientes. Una utilidad que proporciona CGI se puede implementar en cualquier idioma. Y como nos enfocamos en los microcontroladores, por lo tanto, tratamos de cuidar el ahorro de recursos. Hagamos lo mismo 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 compilamos este código y lo ejecutamos, veremos exactamente el mismo resultado que en el caso del script.



En nuestro app.js, agreguemos un controlador para llamar a un script CGI para una de nuestras pestañas:



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





Un pequeño matiz para ejecutar en Linux usando el servidor Python incorporado. Necesitamos agregar el argumento --cgi a nuestra línea de lanzamiento para admitir CGI:



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









Actualización automática de contenido dinámico



Ahora echemos un vistazo a otra propiedad muy importante de un sitio dinámico: las actualizaciones automáticas de contenido. Existen varios mecanismos para su implementación:



  • El lado del servidor incluye (SSI)
  • Eventos enviados por el servidor (SSE)
  • WebSockets
  • Etc.


El lado del servidor incluye (SSI)



El lado del servidor incluye (SSI) . Es un lenguaje sencillo para crear páginas web de forma dinámica. Por lo general, los archivos que utilizan SSI están en formato .shtml.



El propio SSI incluso tiene directivas de control, si es que lo hace, etc. Pero en la mayoría de los ejemplos de microcontroladores que encontramos, se usa de la siguiente manera. Se inserta una directiva en la página .shtml que recarga periódicamente toda la página. Esto podría ser, por ejemplo:



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





O:



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





Y de una forma u otra, el contenido se genera, por ejemplo, configurando un controlador especial.



La ventaja de este método es su simplicidad y requisitos mínimos de recursos. Pero, por otro lado, aquí hay un ejemplo de cómo se ve.







La actualización de la página (ver pestaña) es muy notable. Y volver a cargar toda la página parece una acción excesivamente redundante.



Se proporciona un ejemplo estándar de FreeRTOS: https://www.freertos.org/FreeRTOS-For-STM32-Connectivity-Line-With-WEB-Server-Example.html



Eventos enviados por el servidor



Los eventos enviados por el servidor (SSE) es un mecanismo que permite una conexión semidúplex (unidireccional) entre un cliente y un servidor. En este caso, el cliente abre una conexión y el servidor la usa para transferir datos al cliente. Al mismo tiempo, a diferencia de los scripts CGI clásicos, cuyo propósito es generar y enviar una respuesta al cliente, y luego completarla, SSE ofrece un modo “continuo”. Es decir, el servidor puede enviar tantos datos como sea necesario hasta que se complete o el cliente cierre la conexión.



Existen algunas diferencias menores con los scripts CGI normales. Primero, el encabezado http será ligeramente diferente:



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





La conexión, como puede ver, no es cercana, sino que se mantiene viva, es decir, una conexión continua. Para evitar que el navegador almacene datos en caché, debe especificar Cache-Control sin caché. Por último, debe especificar que se utiliza el tipo de datos especial Content-Type text / event-stream.



Este tipo de datos es un formato especial para SSE :



: this is a test stream

data: some text

data: another message
data: with two lines

      
      





En nuestro caso, los datos deben empaquetarse en la siguiente línea:



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





Nuestro script CGI se verá así:



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





Salida si ejecuta el 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"}
      
      







Y así sucesivamente, una vez por segundo.



Lo mismo 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;
}

      
      





Y finalmente, también necesitamos decirle a angular que tenemos SSE, es decir, modificar el código de nuestro controlador:



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





Lanzamos el sitio, vemos lo siguiente:







Se nota que, a diferencia de usar SSI, la página no se sobrecarga, y los datos se actualizan de manera fluida y agradable a la vista.



Manifestación



Por supuesto, los ejemplos dados no son reales porque son muy simples. Su objetivo es mostrar la diferencia entre los enfoques utilizados en microcontroladores y en otros sistemas.



Hicimos una pequeña demostración con tareas reales. Control de LED, recepción de datos en tiempo real de un sensor de velocidad angular (giroscopio) y una pestaña con información del sistema.



El sitio fue desarrollado en el host. Solo fue necesario hacer pequeños enchufes para emular los LED y los datos del sensor. Los datos del sensor son solo valores aleatorios recibidos a través del estándar RANDOM



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





Simplemente almacenamos el estado de los LED en un archivo.



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





Lo mismo se implementa trivialmente en la variante C. Si lo desea, puede ver el código en la carpeta del repositorio (proyecto / sitio web).



En el microcontrolador, por supuesto, se utilizan implementaciones que interactúan con periféricos reales. Pero como estos son solo comandos y controladores, se depuraron por separado. Por lo tanto, la transferencia misma del sitio al microcontrolador no tomó tiempo.



La captura de pantalla que se ejecuta en el host se ve así:







en un video corto se puede ver el trabajo en un microcontrolador real. Tenga en cuenta que no solo hay comunicación a través de http, sino también, por ejemplo, establecer la fecha usando ntp desde la línea de comando en Embox y, por supuesto, manejar periféricos.





Independientemente, todo lo que se da en el artículo se puede reproducir de acuerdo con las instrucciones de nuestra wiki.



Conclusión



En el artículo, mostramos que es posible desarrollar hermosos sitios interactivos y ejecutarlos en microcontroladores. Además, se puede hacer fácil y rápidamente utilizando todas las herramientas de desarrollo para el host y luego ejecutar desde microcontroladores. Naturalmente, el desarrollo del sitio puede realizarlo un diseñador web profesional, mientras que el desarrollador integrado implementará la lógica del dispositivo. Lo cual es muy conveniente y ahorra tiempo de comercialización.



Naturalmente, tendrás que pagar por esto. Sí, SSE requerirá un poco más de recursos que SSI. Pero con la ayuda de Embox, encajamos fácilmente en el STM32F4 sin optimización y usamos solo 128 KB de RAM. No marcaron nada menos. Entonces la sobrecarga no es tan grande. Y la conveniencia del desarrollo y la calidad del sitio en sí es mucho mayor. Y al mismo tiempo, por supuesto, no olvide que los microcontroladores modernos han crecido notablemente y continúan haciéndolo. Después de todo, los dispositivos deben ser cada vez más inteligentes.



All Articles