
NodeMCU es un firmware interactivo que permite ejecutar el intérprete de Lua en el microcontrolador ESP8266 (el soporte ESP32 está en desarrollo). Junto con todas las interfaces de hardware normales, tiene un módulo WiFi y un sistema de archivos SPIFFS .
Este artículo describe el nuevo módulo para NodeMCU - sdm. SDM significa modelo de controlador simple y proporciona abstracción del modelo de controlador de dispositivo para el sistema. En la primera parte de este artículo discutiremos el modelo en sí y en la segunda parte se mostrará una interfaz de usuario web creada dinámicamente usando sdm con algunos comentarios.
Conceptos básicos del modelo de controlador
Dos componentes principales del modelo son dispositivos y controladores . Dispositivo es una representación abstracta de algún hardware o dispositivo virtual. Tiene sentido colocar dispositivos en la jerarquía de árboles, con el microcontrolador en la parte superior, los autobuses en el medio y los sensores como hojas.
DEVICES + DRIVERS | +-----+ | +-----+ |1WIRE<----------------------+1WIRE| ++-+-++ | +-----+ | | | | +---------+ | +--------+ | +------+ | | | +------+DS1820| +---v----+ +---v----+ +---v----+ | | +------+ |DS1820|0| |DS1820|1| |DS1822|0| | | +---^----+ +---^----+ +---^----+ | | +------+ | | +--------------+DS1822| | | | | +------+ +-----------+------------------+ +
El controlador de dispositivo es una pieza lógica asociada con un dispositivo dado. Las funciones proporcionadas por el controlador se denominan métodos , los contenedores de datos asociados con el controlador se denominan atributos . Ambos métodos y atributos viven dentro del controlador.
Los atributos tienen dos funciones asociadas con ellos: ganchos getter y setter . Por lo tanto, atribuye la funcionalidad del método de superconjunto, pero también ocupan más memoria (la memoria del microcontrolador es escasa, ¿recuerdas?).
sdm.attr_add(drv,
Dispositivo vinculante
La parte difícil del modelo de controlador es el enlace dispositivo-controlador. El proceso en sí es bastante simple: hacemos coincidir el dispositivo con cada controlador disponible hasta que encaje. Solo faltan dos partes: lógica coincidente y algunos datos con los que coincidir.
En la lógica de coincidencia SDM vive en los controladores bajo el nombre _poll()
. Es un método regular que se llama con el identificador del dispositivo como parámetro y devuelve true
o false
si el dispositivo podría o no conectarse al controlador respectivamente.
sdm.method_add(drv, "_poll", nil, function(dev, drv, par) local attr = sdm.attr_data(sdm.local_attr_handle(dev, "id"))
Como se ve en el ejemplo anterior, el controlador coincide con el dispositivo mediante el atributo Pero como se señaló anteriormente, los atributos se asocian solo con el controlador. En general es cierto, pero hay algunos atributos que no se pueden recuperar a través del software. Estas son ID de chip, pines usados, etc. Para ellos, se agregó un tipo especial de atributo al sdm - atributo local . Este atributo está asociado con una instancia del dispositivo y generalmente es inmutable.
Lo único que queda por decir sobre el enlace del controlador. Por lo general, los dispositivos requieren algún tipo de inicialización en el inicio y la limpieza después del uso. Para este propósito, SDM utiliza los _init()
y _free()
.
Si el controlador tiene el método _init()
, se llamará automáticamente después del enlace del dispositivo. Lo mismo con _free()
.
sdm.method_add(drv, "_init", nil, function(dev, drv, par) sdm.device_rename(dev, sdm.request_name("DS18B20"))
Un lector atento probablemente pregunte: ¿qué significa "copiar atributo" en el ejemplo anterior? Y tendría razón, porque esto tiene que ver con el tercer tipo de atributo que aún no hemos discutido: el atributo privado . No tiene mucho sentido compartir todos los datos de atributos entre todas las instancias de dispositivos. Para este propósito, SDM proporciona un mecanismo para copiar el atributo del controlador y asociarlo con el dispositivo. Esto hace que el atributo del controlador sea un prototipo o plantilla.
Un resumen rápido:
- Los atributos locales se utilizan para datos que el software no puede recuperar. Como ID de dispositivo, pines conectados, etc.
- Los atributos del controlador se utilizan para los datos compartidos entre todas las instancias de dispositivos conectados a este controlador.
- Los atributos privados se copian de los atributos del controlador y contienen datos asociados con una sola instancia del dispositivo. Este tipo es el más común.
Implementación de interfaz de usuario web
Código del servidor
Hay un hermoso proyecto nodemcu-httpserver que implementa el código del servidor para NudeMCU. Lamentablemente parece estar muerto. Fue utilizado como base para el servidor. En primer lugar, las funciones del servidor se movieron a LFS y luego se modificaron ligeramente para servir una página estática para cada llamada. Vue.js es una opción perfecta para páginas web basadas en plantillas. Entonces se usó para frontend . Vale la pena señalar que NodeMCU puede no estar conectado a Internet. Debido a esto, la biblioteca vue.js
debe estar presente localmente y servida por el servidor NodeMCU.
Como todos los dispositivos están organizados en estructura de árbol, se accede a ellos como un directorio: /ESP8266/ESP8266_1W/DS18S20-0
. Aquí /ESP8266
es una página NodeMCU, /ESP8266/ESP8266_1W
es una página de bus 1Wire y finalmente /ESP8266/ESP8266_1W/DS18S20-0
es un sensor de temperatura.
Como se mencionó anteriormente, todas las páginas del dispositivo se crean a partir de una página de plantilla que se sirve en cada llamada. El código JS dentro de esta página realiza una solicitud a la misma URL, antepuesta con /api
. Para el ejemplo anterior, la URL de la llamada sería /api/ESP8266/ESP8266_1W/DS18S20-0
. En tales solicitudes, el servidor responde con datos específicos del dispositivo codificados con JSON , que pueblan la página. Por supuesto, la solicitud de página HTML puede omitirse si solo se necesitan datos sin procesar.
Árbol de dispositivos
La configuración inicial del dispositivo se realiza utilizando una estructura de árbol de dispositivo simple . Es como el árbol de dispositivos , pero más simple. Describe la configuración del hardware, incluidos los atributos locales del dispositivo.
local root={
Configuración de hardware
Aquí comienza el escaparate. Para este propósito, se conectaron un grupo de sensores al NodeMCU:
1Los sensores de cable están conectados al mismo pin.

Páginas web y controladores
dispositivo raíz
El objetivo principal del dispositivo raíz (también conocido como ESP8266) es proporcionar un lugar para que sus hijos se conecten. Sin embargo, no está restringido tener métodos o atributos asociados con él.
Este fragmento de código es de aquí :
sdm.method_add(drv, "_init", nil, function(dev, drv, par) local attr = sdm.attr_handle(dev, "id")
Este código agrega float
atributo que se utiliza para contener el tipo de compilación de firmware. Su valor se inicializa en el _init()
que es una función especial, que se ejecuta una vez cuando el controlador se conecta al dispositivo.
Esta es la página generada para el dispositivo raíz.

Aquí podemos ver que el dispositivo raíz tiene un heap
métodos, dos atributos de controlador float
e id
. Finalmente, tiene dos dispositivos conectados: los buses SPI y 1Wire .
SPI
El controlador SPI no es muy interesante. Simplemente asigna funciones NodeMCU SPI .

Mcp3208
MCP3208 es un chip ADC . Mide voltajes de cero a ref y devuelve un código de 12 bits. Lo interesante de esta implementación de controlador es que la ref
atributo estaría presente compilación solo si el firmware admite aritmética de punto flotante. Si no es compatible, en lugar de voltaje absoluto, el código de voltaje se devuelve por métodos single
y differential
.
sdm.method_add(drv, "single", "Single ended measure 0|1|2|3|4|5|6|7", function(dev, channel)

También tenga en cuenta que este dispositivo tiene el atributo ref
marcado como privado . Se establece por dispositivo.
1 cable
1 Driver driver implementa el método de poll
: búsqueda dinámica de dispositivos .
Inmediatamente después del descubrimiento del dispositivo, se desconoce su tipo. Por lo tanto, su dirección única 1Wire se usa como un nuevo nombre 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 es la página inicial para el controlador 1Wire .

Después de emitir una llamada de poll
con el argumento 2
y actualizar la página, aparece la sección secundaria. Tenga en cuenta que los nombres de los niños son legibles por humanos. Esto se debe a que la función device_rename()
fue llamada durante su _init
.

DS18S20
Tras la inicialización, el controlador DS18S20 comprueba que la ID del dispositivo comienza con 0x10
, que es un código de familia del dispositivo. Cuando el dispositivo se conecta al controlador, se renombra a DS18S20-X
, donde DS18S20
es un nombre base y X
es un número de instancia.
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)

Los atributos locales id
y datapin
no tienen ganchos getter
y setter
, por lo que solo sus nombres son visibles.
DS18B20
El controlador DS18B20 es casi lo mismo que el controlador DS18S20 . La única diferencia es el método de precision
. Ambos controladores DS18-20 asumen la construcción de enteros y no utilizan la división de punto flotante.
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 la memoria
La memoria libre ESP8266 es de aproximadamente 40k . El código del servidor se mueve a LFS , por lo que no ocupa espacio RAM en el momento de la inicialización (el código original tardó aproximadamente 10k ).
SDM ocupa aproximadamente 10k para 5 controladores de dispositivo y 5 dispositivos. Ligeramente menor para la compilación de firmware no flotante. Por lo tanto, es preferible seleccionar en el manifiesto del controlador solo los controladores necesarios para la tarea en cuestión. La tarea que consume más memoria es servir la biblioteca vue.js

En caso de solicitar datos sin codificar JSON (usando curl
), el consumo máximo de memoria puede reducirse significativamente.

En lugar de un epílogo
Uno de los primeros métodos que implementé con sdm fue el enlace para
node.restart()
.
Probarlo usando la interfaz de usuario web produjo un resultado curioso. Justo después de que el navegador web emitió la solicitud, el chip se reinició como se esperaba. Pero debido a que NodeMCU no respondió correctamente a la solicitud HTTP, el navegador web intentó la misma solicitud nuevamente. Cuando el servidor NodeMCU se reinició y volvió a funcionar, el navegador se conectó a él, restableció el contador interno de prueba nuevamente y llamó al método node.restart()
, comenzando así un bucle infinito de reinicio de NodeMCU.