
NodeMCU ist eine interaktive Firmware , mit der der Lua- Interpreter auf dem ESP8266- Mikrocontroller ausgeführt werden kann ( ESP32- Unterstützung befindet sich in der Entwicklung). Neben allen regulären Hardwareschnittstellen verfügt es über ein WiFi-Modul und ein SPIFFS- Dateisystem.
Dieser Artikel beschreibt das neue Modul für die NodeMCU - sdm. SDM steht für einfaches Treibermodell und bietet eine Abstraktion des Gerätetreibermodells für das System. Im ersten Teil dieses Artikels werden wir das Modell selbst diskutieren und im zweiten Teil werden einige dynamisch erstellte Webbenutzeroberflächen mit sdm mit einigen Kommentaren vorgestellt.
Grundlagen des Fahrermodells
Zwei Hauptkomponenten des Modells sind Geräte und Treiber . Gerät ist eine abstrakte Darstellung einer Hardware oder eines virtuellen Geräts. Es ist sinnvoll, Geräte in einer Baumhierarchie zu platzieren, mit dem Mikrocontroller oben, Bussen in der Mitte und Sensoren als Blätter.
DEVICES + DRIVERS | +-----+ | +-----+ |1WIRE<----------------------+1WIRE| ++-+-++ | +-----+ | | | | +---------+ | +--------+ | +------+ | | | +------+DS1820| +---v----+ +---v----+ +---v----+ | | +------+ |DS1820|0| |DS1820|1| |DS1822|0| | | +---^----+ +---^----+ +---^----+ | | +------+ | | +--------------+DS1822| | | | | +------+ +-----------+------------------+ +
Der Gerätetreiber ist eine Logik, die einem bestimmten Gerät zugeordnet ist. Vom Treiber bereitgestellte Funktionen werden als Methoden bezeichnet , dem Treiber zugeordnete Datencontainer werden als Attribute bezeichnet . Sowohl Methoden als auch Attribute befinden sich im Treiber.
Mit Attributen sind zwei Funktionen verbunden: Getter- und Setter- Hooks. Attribute also Superset-Methodenfunktionalität, aber sie beanspruchen auch mehr Speicher (Mikrocontroller-Speicher ist knapp, erinnerst du dich?).
sdm.attr_add(drv,
Gerätebindung
Ein schwieriger Teil des Treibermodells ist die Geräte-Treiber-Bindung. Der Vorgang selbst ist recht einfach: Wir passen das Gerät an jeden verfügbaren Treiber an, bis es passt. Es fehlen nur zwei Teile - die Übereinstimmungslogik und einige Daten, mit denen abgeglichen werden soll.
In SDM lebt die Matching-Logik in Treibern unter dem Namen _poll()
. Es handelt sich um eine reguläre Methode, die mit dem Gerätehandle als Parameter aufgerufen wird und true
oder false
zurückgibt true
wenn das Gerät an den Treiber angehängt werden kann oder nicht.
sdm.method_add(drv, "_poll", nil, function(dev, drv, par) local attr = sdm.attr_data(sdm.local_attr_handle(dev, "id"))
Wie im obigen Beispiel zu sehen ist, stimmt der Treiber mit dem Attribut des Geräts überein. Wie oben erwähnt, werden Attribute jedoch nur dem Treiber zugeordnet. Im Allgemeinen ist es wahr, aber es gibt einige Attribute, die nicht über Software abgerufen werden können. Dies sind Chip-IDs, gebrauchte Pins usw. Für diese wurde dem Attribut sdm - local ein spezieller Attributtyp hinzugefügt. Dieses Attribut ist einer Instanz des Geräts zugeordnet und normalerweise unveränderlich.
Das einzige, was noch über die Treiberbindung zu sagen ist. Normalerweise erfordern Geräte beim Start und bei der Bereinigung nach der Verwendung eine Art Initialisierung. Zu diesem Zweck verwendet _init()
Methoden _init()
und _free()
.
Wenn der Treiber über die Methode _init()
, wird diese nach der _init()
automatisch aufgerufen. Gleiches gilt für _free()
.
sdm.method_add(drv, "_init", nil, function(dev, drv, par) sdm.device_rename(dev, sdm.request_name("DS18B20"))
Ein aufmerksamer Leser würde wahrscheinlich fragen: Was bedeutet "Attribut kopieren" im obigen Beispiel? Und er hätte Recht, denn dies hat mit der dritten Art von Attribut zu tun, die wir noch nicht besprochen haben - dem privaten Attribut . Es ist wenig sinnvoll, alle Attributdaten von allen Geräteinstanzen gemeinsam zu nutzen. Zu diesem Zweck bietet sdm einen Mechanismus zum Kopieren von Attributen vom Treiber und zum Verknüpfen mit dem Gerät. Dadurch wird das Treiberattribut zu einem Prototyp oder einer Vorlage.
Eine kurze Zusammenfassung:
- Lokale Attribute werden für Daten verwendet, die von der Software nicht abgerufen werden können. Wie Geräte-IDs, angeschlossene Pins usw.
- Treiberattribute werden für Daten verwendet, die von allen Instanzen von Geräten gemeinsam genutzt werden, die an diesen Treiber angeschlossen sind.
- Private Attribute werden aus Treiberattributen kopiert und enthalten Daten, die nur einer Geräteinstanz zugeordnet sind. Dieser Typ ist der häufigste.
Implementierung der Webbenutzeroberfläche
Servercode
Es gibt ein schönes nodemcu-httpserver- Projekt, das Servercode für NudeMCU implementiert. Leider scheint es tot zu sein. Es wurde als Basis für den Server verwendet. Zunächst wurden die Serverfunktionen in LFS verschoben und dann geringfügig geändert , um für jeden Aufruf eine statische Seite bereitzustellen. Vue.js ist die perfekte Wahl für vorlagenbasierte Webseiten. Also wurde es für das Frontend verwendet . Es ist erwähnenswert, dass NodeMCU möglicherweise nicht mit dem Internet verbunden ist. Aus diesem vue.js
Bibliothek vue.js
lokal vorhanden sein und vom NodeMCU-Server bereitgestellt werden.
Da alle Geräte in einer Baumstruktur organisiert sind, wird auf sie wie auf ein Verzeichnis zugegriffen: /ESP8266/ESP8266_1W/DS18S20-0
. Hier ist /ESP8266
ESP8266 eine NodeMCU-Seite, /ESP8266/ESP8266_1W
ist eine 1Wire-Busseite und schließlich ist /ESP8266/ESP8266_1W/DS18S20-0
ein Temperatursensor.
Wie bereits erwähnt, werden alle Geräteseiten aus einer Vorlagenseite erstellt, die bei jedem Aufruf bereitgestellt wird. Der JS- Code auf dieser Seite fordert dann dieselbe URL an, der /api
vorangestellt ist. Für das obige Beispiel wäre die Aufruf-URL /api/ESP8266/ESP8266_1W/DS18S20-0
. Auf solche Anfragen antwortet der Server mit JSON- codierten gerätespezifischen Daten, die die Seite füllen. Natürlich kann die HTML- Seitenanforderung übersprungen werden, wenn nur Rohdaten benötigt werden.
Gerätebaum
Die anfängliche Gerätekonfiguration erfolgt mithilfe einer einfachen Gerätebaumstruktur . Es ist wie ein Gerätebaum , aber einfacher. Es beschreibt die Konfiguration der Hardware einschließlich der lokalen Geräteattribute.
local root={
Hardware-Setup
Hier beginnt das Schaufenster. Zu diesem Zweck wurden eine Reihe von Sensoren an die NodeMCU angeschlossen:
1Drahtsensoren sind an denselben Pin angeschlossen.

Webseiten und Treiber
Root-Gerät
Der Hauptzweck des Root-Geräts (auch bekannt als ESP8266) besteht darin, seinen Kindern einen Platz zum Herstellen einer Verbindung zu bieten. Es ist jedoch nicht darauf beschränkt, Methoden oder Attribute zuzuordnen.
Dieses Code-Snippet stammt von hier :
sdm.method_add(drv, "_init", nil, function(dev, drv, par) local attr = sdm.attr_handle(dev, "id")
Dieser Code fügt das Attribut float
hinzu, das zum Speichern des Firmware- Build-Typs verwendet wird . Sein Wert wird im _init()
Hook initialisiert, einer speziellen Funktion, die einmal ausgeführt wird, wenn der Treiber eine Verbindung zum Gerät herstellt.
Dies ist die generierte Seite für das Root-Gerät.

Hier können wir sehen, dass das Root-Gerät einen Methodenheap, zwei Treiberattribute float
und id
. Schließlich sind zwei Geräte angeschlossen - SPI- und 1Wire- Busse.
SPI
SPI- Treiber ist nicht sehr interessant. Es werden nur NodeMCU SPI- Funktionen zugeordnet.

Mcp3208
MCP3208 ist ein ADC- Chip. Es misst Spannungen von Null bis Ref und gibt 12-Bit-Code zurück. Das Interessante an dieser Treiberimplementierung ist, dass die Attributreferenz nur dann vorhanden ist, wenn die Firmware Gleitkomma-Arithmetik unterstützt. Wenn dies nicht unterstützt wird, wird anstelle der absoluten Spannung der Spannungscode sowohl mit single
als auch mit differential
.
sdm.method_add(drv, "single", "Single ended measure 0|1|2|3|4|5|6|7", function(dev, channel)

Beachten Sie auch, dass für dieses Gerät die Attributreferenz als privat markiert ist. Es wird pro Gerät festgelegt.
1draht
1 Treiber Treiber implementiert poll
- dynamische Suche nach Geräten .
Unmittelbar nach der Geräteerkennung ist der Typ nicht bekannt. Daher wird die eindeutige 1Wire- Adresse als neuer Gerätename verwendet (Bytes werden als durch _
Zeichen getrennte Zahlen dargestellt).
sdm.method_add(drv, "poll", "Poll for devices", function(bus, pin) local children = sdm.device_children(bus) or {}
Dies ist die erste Seite für den 1Wire- Treiber.

Nach dem Ausgeben eines poll
mit Argument 2
und dem Aktualisieren der Seite wird der untergeordnete Abschnitt angezeigt. Beachten Sie, dass Kindernamen für Menschen lesbar sind. Dies liegt daran, device_rename()
Funktion device_rename()
während ihrer _init
.

DS18S20
Bei der Initialisierung überprüft der DS18S20-Treiber , ob die Geräte- ID mit 0x10 beginnt, einem Gerätefamiliencode. Wenn das Gerät an den Treiber angeschlossen ist, wird es in DS18S20-X
, wobei DS18S20
ein Basisname und X
eine DS18S20
ist.
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)

Die lokalen Attribute id
und datapin
haben keine getter
und setter
Hooks, daher sind nur deren Namen sichtbar.
DS18B20
Der DS18B20-Treiber ist fast identisch mit dem DS18S20-Treiber . Der einzige Unterschied ist die precision
. Beide DS18-20- Treiber gehen von einer Ganzzahlbildung aus und verwenden keine Gleitkommadivision.
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 )

Speichernutzung
Der freie Speicher des ESP8266 beträgt ca. 40 KB . Der Servercode wird in LFS verschoben, sodass zum Zeitpunkt der Initialisierung kein RAM-Speicherplatz benötigt wird (der ursprüngliche Code benötigte ca. 10.000 KB ).
SDM benötigt ungefähr 10 KB für 5 Gerätetreiber und 5 Geräte. Etwas weniger für nicht schwebende Firmware-Builds. Daher ist es vorzuziehen, im Treibermanifest nur Treiber auszuwählen, die für die jeweilige Aufgabe benötigt werden. Die speicherintensivste Aufgabe besteht darin, die Bibliothek vue.js
.

Im Fall der Anforderung von JSON- Rohdaten (unter Verwendung von curl
) kann der maximale Speicherverbrauch erheblich reduziert werden.

Anstelle eines Nachworts
Eine der ersten Methoden, die ich mit sdm implementiert habe, war die Bindung für
node.restart()
.
Das Ausprobieren über die Webbenutzeroberfläche führte zu einem merkwürdigen Ergebnis. Unmittelbar nachdem der Webbrowser die Anforderung ausgegeben hatte, wurde der Chip wie erwartet neu gestartet. Da NodeMCU jedoch nicht ordnungsgemäß auf die HTTP-Anforderung reagiert hat, hat der Webbrowser dieselbe Anforderung erneut versucht. Wenn der NodeMCU-Server neu gestartet wurde und wieder aktiv war, stellte der Browser eine Verbindung her, setzte den internen Zähler für erneute Versuche zurück und rief die Methode node.restart()
, wodurch eine Endlosschleife des Neustarts von NodeMCU gestartet wurde.