
NodeMCU est un firmware interactif , qui permet d'exécuter l'interpréteur Lua sur le microcontrôleur ESP8266 (le support ESP32 est en cours de développement). En plus de toutes les interfaces matérielles habituelles, il dispose d'un module WiFi et d'un système de fichiers SPIFFS .
Cet article décrit le nouveau module pour le NodeMCU - sdm. SDM signifie modèle de pilote simple et fournit une abstraction de modèle de pilote de périphérique pour le système. Dans la première partie de cet article, nous discuterons du modèle lui-même et dans la deuxième partie, nous présenterons une interface utilisateur Web créée dynamiquement à l'aide de sdm avec quelques commentaires.
Principes de base du modèle de pilote
Les deux principaux composants du modèle sont les périphériques et les pilotes . Le périphérique est une représentation abstraite d'un matériel ou d'un périphérique virtuel. Il est logique de placer les appareils dans une hiérarchie arborescente, avec le microcontrôleur en haut, les bus au milieu et les capteurs en feuilles.
DEVICES + DRIVERS | +-----+ | +-----+ |1WIRE<----------------------+1WIRE| ++-+-++ | +-----+ | | | | +---------+ | +--------+ | +------+ | | | +------+DS1820| +---v----+ +---v----+ +---v----+ | | +------+ |DS1820|0| |DS1820|1| |DS1822|0| | | +---^----+ +---^----+ +---^----+ | | +------+ | | +--------------+DS1822| | | | | +------+ +-----------+------------------+ +
Le pilote de périphérique est un élément logique associé au périphérique donné. Les fonctions fournies par le pilote sont appelées méthodes , les conteneurs de données associés au pilote sont appelés attributs . Les méthodes et les attributs vivent dans le pilote.
Les attributs ont deux fonctions qui leur sont associées: les crochets getter et setter . Attribue donc des fonctionnalités de méthode de sur-ensemble, mais elles prennent également plus de mémoire (la mémoire du microcontrôleur est rare, vous vous souvenez?).
sdm.attr_add(drv,
Liaison de périphérique
La partie délicate du modèle de pilote est la liaison de pilote de périphérique. Le processus lui-même est assez simple: nous adaptons le périphérique à chaque pilote disponible jusqu'à ce qu'il corresponde. Il ne manque que deux parties: la logique de correspondance et certaines données auxquelles correspondre.
Dans sdm, la logique de correspondance réside dans les pilotes sous le nom _poll()
. Il s'agit d'une méthode régulière qui est appelée avec le descripteur de périphérique en tant que paramètre et renvoie true
ou false
si le périphérique peut ou ne peut pas être attaché au pilote respectivement.
sdm.method_add(drv, "_poll", nil, function(dev, drv, par) local attr = sdm.attr_data(sdm.local_attr_handle(dev, "id"))
Comme vu dans l'exemple ci-dessus, le pilote fait correspondre le périphérique à l'aide de l'attribut. Mais comme indiqué ci-dessus, les attributs ne s'associent qu'au pilote. En général, c'est vrai, mais certains attributs ne peuvent pas être récupérés via un logiciel. Ce sont des identifiants de puce, des broches utilisées, etc. Pour ceux-ci, un type d'attribut spécial a été ajouté à l'attribut sdm- local . Cet attribut est associé à une instance de l'appareil et généralement immuable.
La seule chose qui reste à dire sur la liaison du pilote. Habituellement, les appareils nécessitent une sorte d'initialisation au démarrage et au nettoyage après utilisation. À cette fin, sdm utilise les _init()
et _free()
.
Si le pilote a la méthode _init()
, il sera appelé automatiquement après la liaison du périphérique. Même chose avec _free()
.
sdm.method_add(drv, "_init", nil, function(dev, drv, par) sdm.device_rename(dev, sdm.request_name("DS18B20"))
Un lecteur attentif demanderait probablement: que signifie «copier l'attribut» dans l'exemple ci-dessus? Et il aurait raison, car cela a à voir avec le troisième type d'attribut dont nous n'avons pas encore discuté - l'attribut privé . Il n'est pas très logique de partager toutes les données d'attribut entre toutes les instances de périphérique. À cet effet, sdm fournit un mécanisme de copie d'attribut du pilote et de l'associer à l'appareil. Cela fait que le pilote attribue un prototype ou un modèle.
Un résumé rapide:
- les attributs locaux sont utilisés pour les données qui ne peuvent pas être récupérées par le logiciel. Comme les ID d'appareil, les broches connectées, etc.
- les attributs de pilote sont utilisés pour les données partagées entre toutes les instances de périphériques connectés à ce pilote.
- les attributs privés sont copiés à partir des attributs du pilote et contiennent des données associées à une seule instance de périphérique. Ce type est le plus courant.
Implémentation de l'interface utilisateur Web
Code serveur
Il y a un joli projet nodemcu-httpserver qui implémente le code serveur pour NudeMCU. Malheureusement, il semble être mort. Il a été utilisé comme base pour le serveur. Tout d'abord, les fonctions du serveur ont été déplacées vers LFS puis légèrement modifiées pour servir une page statique pour chaque appel. Vue.js est un choix parfait pour les pages Web basées sur des modèles. Il a donc été utilisé pour le frontend . Il convient de noter que NodeMCU n'est peut-être pas connecté à Internet. Pour cette raison, la bibliothèque vue.js
doit être présente localement et servie par le serveur NodeMCU.
Étant donné que tous les périphériques sont organisés en arborescence, ils sont accessibles comme un répertoire: /ESP8266/ESP8266_1W/DS18S20-0
. Ici /ESP8266
est une page NodeMCU, /ESP8266/ESP8266_1W
est une page de bus 1Wire et enfin /ESP8266/ESP8266_1W/DS18S20-0
est un capteur de température.
Comme mentionné précédemment, toutes les pages de l'appareil sont construites à partir d'une page de modèle qui est servie à chaque appel. Le code JS à l' intérieur de cette page fait ensuite la demande à la même URL, précédée de /api
. Pour l'exemple ci-dessus, l'URL d'appel serait /api/ESP8266/ESP8266_1W/DS18S20-0
. Sur ces demandes, le serveur répond avec des données spécifiques au périphérique encodées JSON, qui remplissent la page. Bien sûr, la demande de page HTML peut être ignorée si seules des données brutes sont nécessaires.
Arborescence des appareils
La configuration initiale du périphérique est effectuée à l'aide d' une structure arborescente de périphérique simple . C'est comme l' arborescence des appareils , mais plus simple. Il décrit la configuration du matériel, y compris les attributs locaux du périphérique.
local root={
Configuration matérielle
Ici commence la vitrine. À cet effet, un tas de capteurs ont été connectés au NodeMCU:
1Les capteurs à fil sont connectés à la même broche.

Pages Web et pilotes
périphérique racine
Le principal objectif du périphérique racine (alias ESP8266) est de fournir un espace de connexion à ses enfants. Cependant, il n'est pas limité d'avoir des méthodes ou des attributs qui lui sont associés.
Cet extrait de code est d' ici :
sdm.method_add(drv, "_init", nil, function(dev, drv, par) local attr = sdm.attr_handle(dev, "id")
Ce code ajoute un attribut float
qui est utilisé pour contenir le type de construction du firmware. Sa valeur est initialisée dans le _init()
qui est une fonction spéciale, qui s'exécute une fois lorsque le pilote se connecte au périphérique.
Il s'agit de la page générée pour le périphérique racine.

Ici, nous pouvons voir que le périphérique racine a un heap
méthode, deux attributs de pilote float
et id
. Enfin, il est connecté à deux appareils : les bus SPI et 1Wire .
SPI
Le pilote SPI n'est pas très intéressant. Il mappe simplement les fonctions NodeMCU SPI .

Mcp3208
Le MCP3208 est une puce ADC . Il mesure les tensions de zéro à ref et renvoie du code 12 bits. Ce qui est intéressant dans cette implémentation de pilote, c'est que l'attribut ref
ne serait présent que si le micrologiciel prend en charge l'arithmétique à virgule flottante. S'il n'est pas pris en charge, au lieu de la tension absolue, le code de tension est renvoyé par differential
méthodes single
et differential
.
sdm.method_add(drv, "single", "Single ended measure 0|1|2|3|4|5|6|7", function(dev, channel)

Notez également que cet appareil a l'attribut ref
marqué comme privé . Il est défini par périphérique.
1 fil
1 Le pilote pilote implémente la méthode d' poll
- recherche dynamique de périphériques .
Juste après la découverte de l'appareil, son type n'est pas connu. Ainsi, son adresse unique 1Wire est utilisée comme nouveau nom de périphérique (octets représentés par des nombres séparés par un caractère _
).
sdm.method_add(drv, "poll", "Poll for devices", function(bus, pin) local children = sdm.device_children(bus) or {}
Ceci est la page initiale du pilote 1Wire .

Après avoir émis un poll
avec l'argument 2
et une page rafraîchissante, la section enfants apparaît. Notez que les noms d'enfants sont lisibles par l'homme. En effet, la fonction device_rename()
été appelée lors de leur _init
.

DS18S20
Lors de l'initialisation, le pilote DS18S20 vérifie que l' ID de périphérique commence par 0x10
, qui est un code de famille de périphériques. Lorsque le périphérique est connecté au pilote, il est renommé DS18S20-X
, où DS18S20
est un nom de base et X
est un numéro d'instance.
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)

Les attributs locaux id
et datapin
n'ont pas de crochets getter
et setter
, donc seuls leurs noms sont visibles.
DS18B20
Le pilote DS18B20 est presque le même que le pilote DS18S20 . La seule différence est la méthode de precision
. Les deux pilotes DS18? 20 supposent une construction entière et n'utilisent pas la division en virgule flottante.
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 )

Utilisation de la mémoire
La mémoire libre de l' ESP8266 est d'environ 40k . Le code du serveur est déplacé vers LFS , il ne prend donc pas d'espace RAM au moment de l'initialisation ( le code d'origine prenait environ 10k ).
SDM prend environ 10k pour 5 pilotes de périphérique et 5 périphériques. Légèrement moindre pour la construction de firmware non flottant. Il est donc préférable de ne sélectionner dans le manifeste du pilote que les pilotes nécessaires à la tâche à accomplir. La tâche la plus consommatrice de mémoire consiste à servir la bibliothèque vue.js

En cas de demande de données brutes codées JSON (en utilisant curl
), la consommation de mémoire de pointe peut être considérablement réduite.

Au lieu d'un épilogue
L'une des premières méthodes que j'ai implémentées avec sdm a été la liaison pour
node.restart()
.
L'essayer en utilisant l'interface utilisateur Web a produit un résultat curieux. Juste après que le navigateur Web ait émis la demande, la puce a redémarré comme prévu. Mais parce que NodeMCU n'a pas répondu correctement à la demande HTTP, le navigateur Web a réessayé la même demande. Lorsque le serveur NodeMCU a redémarré et était de nouveau opérationnel, le navigateur s'y est connecté, réinitialise le compteur de node.restart()
interne et a appelé la méthode node.restart()
, commençant ainsi une boucle infinie de redémarrage de NodeMCU.