Guten Tag,

ursprünglich sollte sich diese Site auf mein Projekt rund um meine Zisterne beziehen. Datenerfassung zur Zisterne ist nach wie vor eines meiner Themen, inzwischen sind aber einige neue hinzugekommen, die sich um Mikrocontroller (µC), MQTT, Node-RED, Tasmota, JSON, ... drehen. Es geht um smart home als Teil des Gebietes IoT (Internet of Things). Darin kann auch ein Sprachassistent wie das Amazon Echo, auch unter "Alexa" bekannt, eingesetzt werden. Entsprechende Sprachassistenten gibt es von Google und anderen Herstellern/Anbietern.

Ich beginne also mal wieder neu. ;-)

Neu ist insbesondere, dass es inzwischen ein sehr gelungenes Projekt Tasmota32 für µC Boards mit dem ESP32 gibt. Unten dazu etwas mehr. Neu ist in Folge dessen auch, dass ich mich inzwischen dazu durchrang, einen weiteren Account zu erstellen/nutzen. Ich mag keine mal eben erstellte Accounts, die nach kurzer Zeit nicht mehr genutzt werden. Es gibt schon zuviel Datenmüll im Internet. Es handelt sich um einen GitHub Account, über den ich GitPod nutzen kann.

GitPod ist ein, wenn man will, sehr bequemes Online Werkzeug zum selbst kompilieren von Tasmota - und vielem mehr. Ich nutze es derzeit ausschließlich zur Erzeugung von Tasmota .bin Dateien. 

Aktuell befasse ich mich verstärkt mit Tasmota (Theo Arends Sonoff Mqtt Over The Air), ein Projekt für die µC ESP8266 und Kompatible - und als Tasmota32 auch für µC aus der ESP32 Familie. Das System erscheint mir sehr flexibel nutzbar, allerdings musste ich mich "durchärgern" und stecke (hoffentlich) inzwischen mittendrin.

Meine bisherigen Erfahrungen zu Tasmota

  • Welche binary ist zu empfehlen?
    Ich bevorzugte bisher tasmota-sensors.bin, weil diese binary die größte Palette an unmittelbar nutzbaren Sensoren bietet.

  • Ist das erstellen einer individuellen binary nicht zu bevorzugen?
    Jein ...
    Ich versuche derzeit, etwas zu verwenden, was ich Anfängern möglichst einfach vermitteln könnte. Dies muss nicht wirklich einfach sein (relativ), es sollte aber Einfachem möglichst nahe kommen. Deshalb setze ich eine binary ein.

  • Wie kann ich Tasmota flexibel einsetzen?
    Theo Arends hat auf github sehr umfänglich dokumentiert. Das ist hilfreich und notwendig, um Tasmota einsetzen zu können. Für meine Anliegen fehlen allerdings mitunter grundlegende Erklärungen. So war ich genötigt, mich an seinen mitunter sehr ausführlichen und gut dokumentierten Anwendungsbeispielen entlangzuhangeln, was sehr zeitaufwändig war/ist. Inzwischen habe ich einige Erkenntnisse gesammelt. ;-)

Ein wichtiges und grundlegendes Thema ist MQTT. Hierzu habe ich bisher noch nichts verfasst, weil es dazu im Internet viele Quellen gibt.

Meine Themen sind bisher ad hoc und ohne besondere Weiterentwicklung zusammengestellt.

Zudem gibt es inhaltliche Querbezüge der Themen, weshalb es zweckmäßig sein kann, zwischen den Kategorien (Menüpunkte) zu wechseln.

Einige Eindrücke zu und Erfahrungen mit Tasmota32

  • Grundlegend beinhaltet es die gleichen Möglichkeiten wie Tasmota für die ESP8266 Familie. Es gibt die Rules, die Var<x>, die Mem<x> und die gleichen Kommandos.
  • Zusätzlich ist eine kleine, sehr nützliche Hybrid-Programmiersprache Berry eingebaut.
    Hybrid: Es gibt eine Zwischensprache, die ein kleiner Interpreter interpretiert. Die Kompilierung von Berry Sourcecode erzeugt also per Interpreter lauffähige Programme. Das Prinzip gab es früher bei UCSD Pascal und ist bspw. heute mit Java im Einsatz.
  • Es gibt zwei weitere Konsolen.
    • Die "Berry Scripting console" ermöglicht das einfache Experimentieren mit dieser Sprache. Auch kann man hier sehr einfach ein Debugging betreiben.
    • Mit "Verwalte Dateisystem" lassen sich Dateien hochladen, editieren, löschen. Die vollständigen Möglichkeiten stehen erst zur Verfügung, wenn man entweder die kompilierte Tasmota32.bin/Tasmota32-DE.bin verwendet oder bei Selbstkompilieren das Dateisystem für SD Karten (SD Karte/LittleFS) einbezieht.

Mit Berry wird ein ESP32 fast zu einer sehr kleinen "Wundermaschine". Ich bin begeistert. Die Möglichkeiten der zusätzlichen Programmierung auf einer über dem Tasmota32 Grundsystem liegenden Ebene sind sehr flexibel. Zum automatischen laden von Berry Funktionen ... nach einem Neustart legt man eine Datei "autoexec.be" an, in welcher per load(<Berry-Datei>) die gewünschten Funktionen, Variablen und Anweisungen kompiliert und geladen werden.

Die Sprache Berry mit Tasmota32 Erweiterungen kurz umrissen

Fehlendes - jedenfalls fand ich folgendes nicht

  • keine literalen Konstanten, bspw. per def x=12 - diese würden die Lesbarkeit und die Pflege des Quellcodes unterstützen
  • keine zur Laufzeit persistenten lokalen Variablen, also solche, die nicht auf dem Stack angelegt werden und die zwischen Aufrufen ihren Wert behalten - in C++ bspw. static int x=0;
    Hierzu entdeckte ich inzwischen die "closures", mit denen persistente lokale Variable prinzipiell möglich sind. Ich bin noch auf dem Weg, dies zu verstehen und auf Anwendbarkeiten zu prüfen - s.a. unten Experimentelles und evtl. Nützliches.

Als Workaround können globale Variable eingesetzt werden - etwas suboptimal. Nur für persistente lokale Variable eine Klasse zu erstellen, finde ich zu aufwändig, ist aber möglich. 

Nützliches

  • Es gibt genügend einfache Datentypen und Operatoren.
  • Es gibt nützliche Konvertierungsfunktionen/-Methoden.
  • Kommunikation zwischen den nativen Rules und Berry Code ist relativ einfach möglich.
  • Indexierte Kommandos können selbst kreiert werden - sehr flexible Kommunikationsschnittstellen.
  • Es gibt nützliche komplexere Datentypen.
    • Range, für Zähler gesteuerte Schleifen bestens geeignet (for i:1..16)
    • Listen, als Datenfelder mit schnellem Zugriff per Index/Offset implementiert
      Methoden push() und pop() für leichte Manipulationen, tostring() zur Serialisierung ...
    • Maps, als assoziative Speicher per Key Value Paaren
      Methoden tostring() zur Serialisierung, insert() zum einfügen eines Key Value Paares, remove() zum entfernen eines Paares ...
  • Eine json Klasse u.a. zur Konvertierung JSON String -> Map (Parser) steht zur Verfügung.
  • Klassen können erstellt werden.

Experimentelles und evtl. Nützliches

Interessant sind die sog. "closures". Nach Manual sollen sich damit zugriffsgeschützte Variable implementieren lassen. Ich weiß gegenwärtig nicht, wie das mit einfachen Mitteln gelingen kann. Mit relativ komplexen Mitteln (Listen an Funktionsreferenzen, Maps mit Funktionsreferenzen als Werte) dürfte es gelingen, der Overhead erscheint mir aber erheblich.

Ich habe testweise Funktionen mit persistenten lokalen Variablen geschrieben. Variable einfachen Typs lassen sich damit auf einfache Weise schreibgeschützt implementieren, weil diese nicht per Referenz geliefert werden. Auf Inhalte/Werte von Variablen muss zumindest lesend zugegriffen werden können, andernfalls sind sie sinnfrei. Variable komplexen Typs wie list und map werden als Funktionsresultat per Referenz geliefert und sind somit auch veränderbar. Um einen Schreibschutz zu implementieren, müsste von einer solchen Variablen auch eine persistente Kopie existieren. Wenn per return ... eine Referenz auf die Kopie geliefert würde, könnte von außen das Original nicht verändert werden. Dies erscheint mir jedoch viel zu aufwändig. Wenn das Ziel verfolgt wird, schlanken und performanten Code zu erstellen, fallen solche "Lösungen" aus.

Als nützliche closures sehe ich Funktionen zur effizienten Handhabung von spezifischen Listen. Hierfür kann man eine äußere Initialisierungsfunktion und eine innere Arbeitsfunktion zusammenstellen. Dies erinnert etwas an C++ Funktoren. In meinem Zisternenprojekt will ich solche Listenfunktionen einsetzen, weil sie die Pflegbarkeit und die Lesbarkeit des Quellcodes steigern können. So habe ich Funktionen wie unsortedList(), insertSortList() und listCopy() erstellt und ausgiebig getestet.

Beispiel:
var store = insertSortList() # Leere Liste wird angelegt, store erhält die Funktionsreferenz auf die Arbeitsfunktion, welche neue Werte in die Liste einsortiert.
store(12) # sortiert 12 in die Liste ein und liefert die Referenz auf die Liste -> [12]
store(9) # sortiert 9 in die Liste ein und liefert die Referenz auf die Liste -> [9, 12]
store('text') # ist möglich, hier aber sinnfrei - vermutlich wird eine Ausnahme geworfen
store() # liefert nur die Referenz auf die Liste.
Für spezialisierte Listen sind solche Funktionen mit einer einzigen Arbeitsfunktion sehr gut geeignet. In meinem Messprojekt lasse ich bspw. Rohwerte sammeln, sortieren und spezifisch zu Nutzwerten verarbeiten.
Mit der per store() Aufruf gelieferten Listenreferenz kann die Liste auch mit list-Methoden manipuliert werden, bspw. store().push(3) fügt die 3, unsortiert, an die Liste an. Dies wäre hier kontraproduktiv.

Damit sind imho alle Dinge möglich, die man sich für ein ESP32 Board auszudenken in der Lage ist. Allenfalls die Zugriffsschutzschwächen seitens Berry können bei umfangreichen Projekten stören.

Ich habe inzwischen damit eine Messeinrichtung für den Wasserstand meiner Zisterne realisiert. Mehr dazu ist unter Projekte/Zisternenmessungen zu finden.

2021-08-20