Com o advento de vários tipos de tomadas inteligentes, lâmpadas e outros dispositivos semelhantes em nossas vidas, a necessidade de sites em microcontroladores se tornou inegável. E graças ao projeto lwIP (e seu irmão mais novo, uIP), você não vai surpreender ninguém com essa funcionalidade. Mas, uma vez que o lwIP visa minimizar recursos, em termos de design, funcionalidade, bem como usabilidade e desenvolvimento, esses sites estão muito aquém daqueles a que estamos acostumados. Mesmo para sistemas embarcados, compare, por exemplo, com um site de administração nos roteadores mais baratos. Neste artigo, tentaremos desenvolver um site no Linux para algum dispositivo inteligente e executá-lo em um microcontrolador.
Para rodar em um microcontrolador, usaremos Embox . Este RTOS inclui um servidor HTTP habilitado para CGI. Usaremos o servidor HTTP embutido no python como um servidor HTTP no Linux.
python3 -m http.server -d <site folder>
Site estático
Vamos começar com um site estático simples que consiste em uma ou mais páginas.
Tudo é simples aqui, vamos criar uma pasta e index.html nela. Este arquivo será baixado por padrão se apenas o endereço do site for especificado no navegador.
$ ls website/ em_big.png index.html
O site também conterá o logotipo da Embox, o arquivo “em_big.png”, que incorporaremos no html.
Vamos iniciar o servidor http:
python3 -m http.server -d website/
Vamos para localhost: 8000 no navegador.Agora
vamos adicionar nosso site estático ao sistema de arquivos Embox. Isso pode ser feito copiando nossa pasta para a pasta rootfs / template (o template atual está na pasta conf / rootfs). Ou crie um módulo especificando arquivos para rootfs nele.
$ ls website/ em_big.png index.html Mybuild
Conteúdo de Mybuild.
package embox.demo module website { @InitFS source "index.html", "em_big.png", }
Para simplificar, colocaremos nosso site diretamente na pasta raiz (anotação @InitFs sem parâmetros).
Também precisamos incluir nosso site no arquivo de configuração mods.conf e adicionar o próprio servidor httd lá:
include embox.cmd.net.httpd include embox.demo.website
Além disso, vamos iniciar o servidor com nosso site durante a inicialização do sistema. Para fazer isso, adicione uma linha ao arquivo conf / system_start.inc:
"service httpd /",
Naturalmente, todas essas manipulações precisam ser feitas com a configuração da placa. Depois disso, nós coletamos e corremos. Vamos no navegador ao endereço do seu conselho. No meu caso é 192.168.2.128
E temos a mesma imagem do site local,
não somos especialistas em desenvolvimento web, mas ouvimos dizer que vários frameworks são usados para criar belos sites. Por exemplo, AngularJS é freqüentemente usado . Portanto, daremos mais exemplos de como usá-lo. Mas, ao mesmo tempo, não vamos entrar em detalhes e pedir desculpas antecipadamente se em algum lugar tivermos nos ajustado fortemente com o web design.
Qualquer que seja o conteúdo estático que colocarmos na pasta do site, por exemplo, arquivos js ou css, podemos usá-lo sem nenhum esforço adicional.
Vamos adicionar app.js (um site angular) ao nosso site e nele algumas guias. Colocaremos as páginas dessas guias na pasta parcial, as imagens na pasta imagens / e os arquivos css em css /.
$ ls website/ app.js css images index.html Mybuild partials
Vamos lançar nosso site.
Concordo, o site parece muito mais familiar e agradável. E tudo isso é feito no lado do navegador. Como dissemos, todo o contexto ainda é estático. E podemos desenvolvê-lo no host como um site normal.
Naturalmente, você pode usar todas as ferramentas de desenvolvimento de desenvolvedores web comuns. Assim, abrindo o console no navegador, encontramos uma mensagem de erro informando que o favicon.ico estava faltando:
Descobrimos que este é o ícone que é exibido na guia do navegador. Você pode, é claro, colocar um arquivo com este nome, mas às vezes você não quer gastar neste local. Deixe-me lembrá-lo de que queremos rodar também em microcontroladores onde há pouca memória.
Uma pesquisa na Internet mostrou imediatamente que você pode fazer sem um arquivo, basta adicionar uma linha na seção head html. Embora o erro não atrapalhe, é sempre agradável deixar o site um pouco melhor. E o mais importante, certificamo-nos de que as ferramentas usuais do desenvolvedor são bastante aplicáveis com a abordagem proposta.
Conteúdo dinâmico
CGI
Vamos passar para o conteúdo dinâmico. Common Gateway Interface (CGI) é uma interface para interação de um servidor web com utilitários de linha de comando, que permite a criação de conteúdo dinâmico. Em outras palavras, CGI permite que você use a saída de utilitários para gerar conteúdo dinâmico.
Vamos dar uma olhada em alguns scripts 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"
Primeiro, o cabeçalho http é impresso na saída padrão e, em seguida, os dados da própria página são impressos. a saída pode ser redirecionada para qualquer lugar. Você pode simplesmente executar este script a partir do console. Veremos o seguinte:
./cgi-bin/gettime
HTTP/1.1 200 OK
Content-Type: application/json
Connection: Connection: close
"Fri Feb 5 20:58:19 2021"
E se em vez da saída padrão for um soquete, o navegador receberá esses dados.
CGI é frequentemente implementado com scripts, até mesmo scripts cgi são mencionados. Mas isso não é necessário, apenas em linguagens de script essas coisas são mais rápidas e convenientes. Um utilitário que fornece CGI pode ser implementado em qualquer idioma. E como nos concentramos em microcontroladores, portanto, procuramos economizar recursos. Vamos fazer o mesmo em 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;
}
Se compilarmos esse código e executarmos, veremos exatamente a mesma saída que no caso do script.
Em nosso app.js, vamos adicionar um manipulador para chamar um script CGI para uma de nossas guias:
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();
}]);
Uma pequena nuance para executar no Linux usando o servidor python integrado. Precisamos adicionar o argumento --cgi à nossa linha de lançamento para suportar CGI:
python3 -m http.server --cgi -d .
Atualização automática de conteúdo dinâmico
Agora, vamos dar uma olhada em outra propriedade muito importante de um site dinâmico - atualizações automáticas de conteúdo. Existem vários mecanismos para sua implementação:
- Server Side Includes (SSI)
- Eventos enviados pelo servidor (SSE)
- WebSockets
- Etc
Server Side Includes (SSI)
Server Side Includes (SSI) . É uma linguagem descomplicada para criar páginas da web dinamicamente. Normalmente, os arquivos que usam SSI estão no formato .shtml.
O próprio SSI tem até diretivas de controle, se mais e assim por diante. Mas na maioria dos exemplos de microcontroladores que encontramos, ele é usado da seguinte maneira. Uma diretiva é inserida na página .shtml que recarrega periodicamente a página inteira. Pode ser, por exemplo:
<meta http-equiv="refresh" content="1">
Ou:
<BODY onLoad="window.setTimeout("location.href='runtime.shtml'",2000)">
E de uma forma ou de outra, o conteúdo é gerado, por exemplo, definindo um manipulador especial.
A vantagem desse método é sua simplicidade e requisitos mínimos de recursos. Mas, por outro lado, aqui está um exemplo de sua aparência.
A atualização da página (ver guia) é muito perceptível. E recarregar a página inteira parece uma ação excessivamente redundante.
Um exemplo padrão do FreeRTOS é fornecido - https://www.freertos.org/FreeRTOS-For-STM32-Connectivity-Line-With-WEB-Server-Example.html
Eventos enviados pelo servidor
Os eventos enviados pelo servidor (SSE) são um mecanismo que permite uma conexão half-duplex (unidirecional) entre um cliente e um servidor. O cliente, neste caso, abre uma conexão e o servidor a usa para transferir dados para o cliente. Ao mesmo tempo, ao contrário dos scripts CGI clássicos, cujo objetivo é gerar e enviar uma resposta ao cliente e, em seguida, concluir, o SSE oferece um modo “contínuo”. Ou seja, o servidor pode enviar quantos dados forem necessários até que seja concluído ou o cliente feche a conexão.
Existem algumas pequenas diferenças em relação aos scripts CGI regulares. Primeiro, o cabeçalho http será um pouco diferente:
"Content-Type: text/event-stream\r\n"
"Cache-Control: no-cache\r\n"
"Connection: keep-alive\r\n"
A conexão, como você pode ver, não é estreita, mas mantém viva, ou seja, uma conexão contínua. Para evitar que o navegador armazene dados em cache, você precisa especificar Cache-Control no-cache. Finalmente, você precisa especificar que o tipo de dados especial Content-Type text / event-stream é usado.
Este tipo de dados é um formato especial para SSE :
: this is a test stream data: some text data: another message data: with two lines
Em nosso caso, os dados precisam ser compactados na seguinte linha:
data: { “time”: “<real date>”}
Nosso script CGI será semelhante a:
#!/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
Resultado se você executar o 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"}
E assim por diante, uma vez por segundo.
Mesma coisa em 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;
}
E, finalmente, também precisamos informar ao angular que temos SSE, ou seja, modificar o código do nosso 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();
}]);
Ao lançarmos o site, vemos o seguinte:
É perceptível que, ao contrário do SSI, a página não se sobrecarrega e os dados são atualizados de forma suave e agradável para os olhos.
Demo
É claro que os exemplos dados não são reais porque são muito simples. O objetivo é mostrar a diferença entre as abordagens usadas em microcontroladores e em outros sistemas.
Fizemos uma pequena demonstração com tarefas reais. Controlando LEDs, recebendo dados em tempo real de um sensor de taxa angular (giroscópio) e uma guia com informações do sistema.
O site foi desenvolvido no host. Foi necessário apenas fazer pequenos plugues para emular os LEDs e os dados do sensor. Os dados do sensor são apenas valores aleatórios recebidos por meio do RANDOM padrão
#!/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
Simplesmente armazenamos o estado dos LEDs em um arquivo.
#!/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))
O mesmo é implementado trivialmente na variante C. Se desejar, você pode ver o código na pasta do repositório (projeto / site).
No microcontrolador, é claro, são usadas implementações que interagem com periféricos reais. Mas como esses são apenas comandos e drivers, eles foram depurados separadamente. Portanto, a própria transferência do site para o microcontrolador não demorou.
A captura de tela rodando no host é assim:
em um pequeno vídeo você pode ver o trabalho em um microcontrolador real. Observe que não há apenas comunicação via http, mas também, por exemplo, definir a data usando ntp na linha de comando do Embox e, claro, lidar com periféricos.
De forma independente, tudo o que é fornecido no artigo pode ser reproduzido de acordo com as instruções em nosso wiki
Conclusão
No artigo, mostramos que é possível desenvolver belos sites interativos e rodá-los em microcontroladores. Além disso, isso pode ser feito de forma fácil e rápida usando todas as ferramentas de desenvolvimento para o host e depois executado em microcontroladores. Naturalmente, o desenvolvimento do site pode ser feito por um web designer profissional, enquanto o desenvolvedor embarcado implementará a lógica do dispositivo. O que é muito conveniente e economiza tempo de comercialização.
Naturalmente, você terá que pagar por isso. Sim, o SSE exigirá um pouco mais de recursos do que o SSI. Mas com a ajuda da Embox, encaixamos facilmente no STM32F4 sem otimização e usamos apenas 128 KB de RAM. Eles não verificaram nada menos. Portanto, a sobrecarga não é tão grande. E a conveniência de desenvolvimento e a qualidade do próprio site são muito maiores. E, ao mesmo tempo, é claro, não se esqueça de que os microcontroladores modernos cresceram visivelmente e continuam a crescer. Afinal, os dispositivos precisam ser cada vez mais inteligentes.