
O NodeMCU é um firmware interativo , que permite executar o interpretador Lua no microcontrolador ESP8266 (o suporte ao ESP32 está em desenvolvimento). Juntamente com todas as interfaces de hardware regulares, possui módulo WiFi e sistema de arquivos SPIFFS .
Este artigo descreve o novo módulo para o NodeMCU - sdm. SDM significa modelo de driver simples e fornece abstração de modelo de driver de dispositivo para o sistema. Na primeira parte deste artigo, discutiremos o próprio modelo e, na segunda parte, será mostrada uma interface de usuário da web criada dinamicamente usando sdm com alguns comentários.
Noções básicas do modelo de driver
Dois componentes principais do modelo são dispositivos e drivers . Dispositivo é uma representação abstrata de algum hardware ou dispositivo virtual. Faz sentido colocar dispositivos na hierarquia da árvore, com o microcontrolador no topo, os barramentos no meio e os sensores como folhas.
DEVICES + DRIVERS | +-----+ | +-----+ |1WIRE<----------------------+1WIRE| ++-+-++ | +-----+ | | | | +---------+ | +--------+ | +------+ | | | +------+DS1820| +---v----+ +---v----+ +---v----+ | | +------+ |DS1820|0| |DS1820|1| |DS1822|0| | | +---^----+ +---^----+ +---^----+ | | +------+ | | +--------------+DS1822| | | | | +------+ +-----------+------------------+ +
O driver de dispositivo é uma parte da lógica associada a um determinado dispositivo. As funções fornecidas pelo driver são chamadas de métodos , os contêineres de dados associados ao driver são chamados de atributos . Ambos os métodos e atributos vivem dentro do driver.
Os atributos têm duas funções associadas a eles: ganchos getter e setter . Portanto, atribui uma funcionalidade de superconjunto ao método, mas também consome mais memória (a memória do microcontrolador é escassa, lembra?
sdm.attr_add(drv,
Ligação de dispositivo
Parte complicada do modelo de driver é a ligação de driver de dispositivo. O processo em si é bastante simples: combinamos o dispositivo com cada driver disponível até que ele se encaixe. Faltam apenas duas partes - lógica correspondente e alguns dados aos quais corresponder.
Na correspondência sdm, a lógica reside nos drivers com o nome _poll()
. É um método regular chamado de identificador de dispositivo como parâmetro e retorna true
ou false
se o dispositivo puder ou não ser conectado ao driver, respectivamente.
sdm.method_add(drv, "_poll", nil, function(dev, drv, par) local attr = sdm.attr_data(sdm.local_attr_handle(dev, "id"))
Como visto no exemplo acima, o driver corresponde ao dispositivo usando o atributo Mas, como observado acima, os atributos se associam apenas ao driver. Geralmente é verdade, mas existem alguns atributos que não podem ser recuperados via software. Estes são IDs de chip, pinos usados etc. Para aqueles, um tipo especial de atributo foi adicionado ao atributo sdm - local . Este atributo está associado a uma instância do dispositivo e geralmente imutável.
A única coisa que resta a dizer sobre a ligação do driver. Normalmente, os dispositivos requerem algum tipo de inicialização na inicialização e limpeza após o uso. Para esse propósito, o sdm usa os métodos _init()
e _free()
.
Se o driver tiver o método _init()
, ele será chamado automaticamente após a ligação do dispositivo. O mesmo com _free()
.
sdm.method_add(drv, "_init", nil, function(dev, drv, par) sdm.device_rename(dev, sdm.request_name("DS18B20"))
O leitor atento provavelmente perguntaria: o que significa "copiar atributo" no exemplo acima? E ele estaria certo, porque isso tem a ver com o terceiro tipo de atributo que ainda não discutimos - o atributo privado . Não faz muito sentido ter todos os dados de atributos compartilhados entre todas as instâncias do dispositivo. Para esse fim, o sdm fornece um mecanismo de cópia do atributo do driver e o associa ao dispositivo. Isso faz com que o driver atribua um protótipo ou modelo.
Um resumo rápido:
- atributos locais são usados para dados que não podem ser recuperados pelo software. Como IDs de dispositivos, pinos conectados etc.
- os atributos do driver são usados para dados compartilhados entre todas as instâncias de dispositivos conectados a esse driver.
- os atributos privados são copiados dos atributos do driver e mantêm os dados associados a apenas uma instância do dispositivo. Esse tipo é o mais comum.
Implementação da Interface com o Usuário da Web
Código do servidor
Há um adorável projeto nodemcu-httpserver que implementa o código do servidor para o NudeMCU. Infelizmente, parece estar morto. Foi usado como base para o servidor. Primeiro, as funções do servidor foram movidas para o LFS e, em seguida, ligeiramente modificadas para atender a uma página estática para cada chamada. O Vue.js é uma escolha perfeita para páginas da Web baseadas em modelos. Então foi usado para frontend . Vale ressaltar que o NodeMCU pode não estar conectado à Internet. Por esse vue.js
, a biblioteca vue.js
precisa estar presente localmente e ser atendida pelo servidor NodeMCU.
Como todos os dispositivos estão organizados em estrutura em árvore, eles são acessados como um diretório: /ESP8266/ESP8266_1W/DS18S20-0
. Aqui /ESP8266
é uma página do NodeMCU, /ESP8266/ESP8266_1W
é uma página de barramento 1Wire e, finalmente, /ESP8266/ESP8266_1W/DS18S20-0
é um sensor de temperatura.
Como mencionado anteriormente, todas as páginas do dispositivo são criadas a partir de uma página de modelo que é veiculada em todas as chamadas. O código JS dentro desta página faz a solicitação para a mesma URL, anexada com /api
. Para o exemplo acima, o URL da chamada seria /api/ESP8266/ESP8266_1W/DS18S20-0
. Em tais solicitações, o servidor responde com dados específicos do dispositivo codificado em JSON , que preenchem a página. Obviamente, a solicitação da página HTML pode ser ignorada se apenas dados brutos forem necessários.
Árvore de dispositivos
A configuração inicial do dispositivo é feita usando uma estrutura de árvore de dispositivos simples . É como uma árvore de dispositivos , mas mais simples. Ele descreve a configuração do hardware, incluindo atributos locais do dispositivo.
local root={
Configuração de hardware
Aqui começa a vitrine. Para esse propósito, vários sensores foram conectados ao NodeMCU:
Os sensores 1Wire estão conectados ao mesmo pino.

Páginas da Web e drivers
dispositivo raiz
O principal objetivo do dispositivo raiz (também conhecido como ESP8266) é fornecer espaço para seus filhos se conectarem. No entanto, não é restrito ter métodos ou atributos associados a ele.
Este trecho de código é daqui :
sdm.method_add(drv, "_init", nil, function(dev, drv, par) local attr = sdm.attr_handle(dev, "id")
Esse código adiciona o atributo float
que é usado para armazenar o tipo de construção do firmware. Seu valor é inicializado no gancho _init()
que é uma função especial, executada uma vez quando o driver é conectado ao dispositivo.
Esta é a página gerada para o dispositivo raiz.

Aqui podemos ver que o dispositivo raiz tem um heap
método, dois atributos de driver float
e id
. Por fim, ele possui dois dispositivos conectados - os barramentos SPI e 1Wire .
SPI
O driver SPI não é muito interessante. Apenas mapeia as funções do NodeMCU SPI .

Mcp3208
MCP3208 é um chip ADC . Ele mede tensões de zero a ref e retorna o código de 12 bits. O interessante dessa implementação de driver é que o atributo ref
estaria presente na compilação apenas se o firmware oferecer suporte à aritmética de ponto flutuante. Se não for suportado, em vez da tensão absoluta, o código de voltagem será retornado pelos métodos single
e differential
.
sdm.method_add(drv, "single", "Single ended measure 0|1|2|3|4|5|6|7", function(dev, channel)

Observe também que este dispositivo tem o atributo ref
marcado como privado . É definido por dispositivo.
1 fio
1 O driver do driver implementa o método de poll
- pesquisa dinâmica de dispositivos .
Logo após a descoberta do dispositivo, seu tipo não é conhecido. Portanto, seu endereço exclusivo 1Wire é usado como um novo nome de dispositivo (bytes representados como números separados por caracteres _
).
sdm.method_add(drv, "poll", "Poll for devices", function(bus, pin) local children = sdm.device_children(bus) or {}
Esta é a página inicial do driver 1Wire .

Após emitir a chamada de poll
com o argumento 2
e a página atualizada, a seção filhos é exibida. Observe que os nomes dos filhos são legíveis por humanos. Isso ocorre porque a função device_rename()
foi chamada durante a _init
.

DS18S20
Na inicialização, o driver DS18S20 verifica se o ID do dispositivo começa com 0x10
, que é um código de família do dispositivo. Quando o dispositivo é conectado ao driver, ele é renomeado para DS18S20-X
, onde DS18S20
é um nome de base e X
é um número de instância.
sdm.method_add(drv, "_poll", nil, function(dev, drv, par) local attr = sdm.attr_data(sdm.local_attr_handle(dev, "id")) if attr == nil then return false end return (sdm.device_name(par) == "ESP8266_1W") and (attr:byte(1) == 0x10)

O atributo local id
e datapin
não têm ganchos getter
e setter
, portanto, apenas seus nomes são visíveis.
DS18B20
O driver DS18B20 é quase o mesmo que o driver DS18S20 . A única diferença é o método de precision
. Os dois drivers DS18-20 assumem a construção de números inteiros e não usam divisão de ponto flutuante.
sdm.attr_add(drv, "precision", "Precision (9|10|11|12)", 12, function(dev, precision) local attr = sdm.attr_dev_handle(dev, "precision") return sdm.attr_data(attr) end, function(dev, precision) local par = sdm.device_parent(dev) local attr = sdm.attr_dev_handle(dev, "precision") local ex = sdm.method_func(sdm.method_dev_handle(par, "exchange")) local modes = {[9]=0x1f, [10]=0x3f, [11]=0x5f, [12]=0x7f} if modes[precision] ~= nil then ex(par, dev, {0x4e, 0, 0, modes[precision]}) sdm.attr_set(attr, precision) end end )

Uso de memória
A memória livre do ESP8266 é de cerca de 40k . O código do servidor é movido para o LFS , portanto, ele não ocupa espaço de RAM no momento da inicialização ( o código original levou cerca de 10k ).
O SDM ocupa cerca de 10k para 5 drivers de dispositivo e 5 dispositivos. Um pouco menor para a construção de firmware não flutuante. Portanto, é preferível selecionar no manifesto do driver apenas os drivers necessários para a tarefa em questão. A tarefa que consome mais memória é servir a biblioteca vue.js

No caso de solicitar dados brutos codificados em JSON (usando curl
), o consumo de memória de pico pode ser significativamente reduzido.

Em vez de um epílogo
Um dos primeiros métodos que implementei com sdm foi a ligação para
node.restart()
.
Experimentá-lo usando a interface do usuário da web produziu um resultado curioso. Logo depois que o navegador da web emitiu a solicitação, o chip foi reiniciado conforme o esperado. Mas como o NodeMCU não respondeu adequadamente à solicitação HTTP, o navegador da Web tentou a mesma solicitação novamente. Quando o servidor NodeMCU foi reiniciado e foi reiniciado, o navegador conectado a ele, redefiniu o contador de tentativa interna novamente e chamou o método node.restart()
, iniciando assim um loop infinito de reinicialização do NodeMCU.