Entwicklung intelligenter Geräte am Beispiel eines Fußbodenreglers auf ESP8266

Ich möchte meine Erfahrungen bei der Entwicklung eines intelligenten Geräts teilen. In dieser Veröffentlichung werde ich Hardware (kurz) und Software (detaillierter) beschreiben.

Die Steuerung dient zur Analyse der Messwerte der Sensoren (verkabelt und drahtlos) und zur Aufrechterhaltung der eingestellten Temperatur (einschließlich des Zeitplans einschließlich der Wochentage) in jeder einzelnen Zone, indem der Kessel ein- und ausgeschaltet und die Heizungskreise des Wasserbodens mithilfe der Wärmeköpfe am Kollektor gesteuert werden.

Hardware


Als Controller wurde ESP8266 (WeMos D1 mini) ausgewählt, als hat WiFi an Bord. Anstelle von ESP8266 kann jeder andere Controller oder Mikrocomputer verwendet werden - die allgemeinen Ideen bleiben unverändert. Hauptsache, ein WEB-Server mit Unterstützung für WEB-Sockets kann auf dem ausgewählten System bereitgestellt werden. Folgendes wurde auch im Projekt verwendet:

  • RTC: DS3231 - Der Wochentag und die aktuelle Uhrzeit müssen ermittelt werden. Das Projekt wurde als eigenständiges Gerät konzipiert, das ohne Internet arbeiten kann, sodass NTP nicht geeignet ist.
  • Drahtlose Temperatursensoren: NoName, 433 MHz, von der chinesischen Wetterstation - eine schlüsselfertige Lösung, die mit Batterien arbeitet. Was brauchst du noch? Es ist jedoch erforderlich, dass die Datenübertragungsdauer nicht festgelegt ist. Das Problem ist, dass die Übertragungszeit 35 Sekunden beträgt und nicht viel schwimmt. Und es gibt Situationen, in denen sich die Signale zweier Sensoren überlappen. In diesem Fall fallen ein oder zwei Sensoren für eine Weile aus dem System aus. Das Problem kann mit ähnlichen Sensoren gelöst werden, bei denen durch Kanalumschaltung auch die Datenübertragungsdauer geändert wird.
  • 433-MHz-Empfänger: Rxb6 - Internet-Bewertungen und persönliche Erfahrungen sind kein schlechter Empfänger.
  • Kabelgebundene Temperatursensoren: DS18B20 - sehr praktisch, da Sie die Anzahl der Sensoren nicht im Voraus kennen müssen.
  • 1Wire-Bus-Master: DS2482-100 - Das 1Wire-Protokoll reagiert sehr empfindlich auf Timings. Daher verwenden alle Implementierungen des Programmbus-Masters eine Verzögerung, die für Multitasking nicht sehr gut ist. Mit diesem Chip können Sie den 1Wire-Bus nutzen und seine Mängel beseitigen, indem Sie 1Wire <-> i2c senden. Das i2c-Protokoll verfügt über eine Synchronisationsleitung, aufgrund derer es für das Timing nicht kritisch ist und häufig in Hardware in Controllern implementiert wird.
  • Watchdog-Timer: TPL5000DGST - Die kontinuierliche Verfügbarkeit ist für dieses Projekt nicht so wichtig, die Zugänglichkeit ist jedoch sehr wichtig. Der ESP8266 verfügt über einen integrierten Watchdog-Timer. Aber wie die Praxis gezeigt hat, gibt es manchmal immer noch Situationen, in denen es nicht zurechtkommt und das System einfriert. Ein externer Hardware-Watchdog-Timer wurde für Notfallsituationen entwickelt. Konfiguriert für eine Verzögerung von 64 Sekunden. An den Bein-TX angeschlossen - Während des Betriebs schreibt das System ständig Debugging-Informationen an Serial, und die mangelnde Aktivität von mehr als einer Minute weist auf einen Stillstand des Systems hin.
  • Port-Expander: 74HC595 - Für die Verwendung dieses Expanders sind 4 Beine des Controllers erforderlich - drei, um den Status zu übertragen, und einer, damit die Relais beim Einschalten nicht klicken. Das nächste Mal werde ich PCF8574 verwenden - der i2c-Bus wird immer noch verwendet, d. H. Es sind keine zusätzlichen MCU-Beine erforderlich und die Ausgänge 1 werden eingestellt, wenn Strom angelegt wird.
  • Relaismodul: NoName, 8-Kanal, 5 V - es gibt nichts zu sagen, außer dass das Relais an den Moduleingängen auf einem niedrigen Pegel eingeschaltet ist. Halbleiterrelais sind in diesem Projekt nicht zulässig Die Kesselkontakte müssen durch einen Trockenkontakt geschaltet werden - im Allgemeinen kenne ich die Spannung und den Gleich- oder Wechselstrom an den Kontakten nicht.

Operationssystem


Das Betriebssystem ist die gesamte Software, die die Funktionsfähigkeit des Anwendungsprogramms sicherstellt. Das Betriebssystem ist auch eine Schicht zwischen der Hardware und dem Anwendungsprogramm und bietet eine übergeordnete Schnittstelle für den Zugriff auf Hardwareressourcen. Im Fall von ESP können die Komponenten des Betriebssystems berücksichtigt werden:

Dateisystem


Das Projekt verwendet SPIFFS, hier scheint alles klar zu sein, dies ist der einfachste Weg. Für den einfachen Zugriff auf Dateien auf dem Gerät von außen verwende ich die Bibliothek pipebuster / esp8266FTPServer.

CPU-Zeitzuweisungssystem


Dies ist eine der Hauptfunktionen des Betriebssystems, und ESP ist keine Ausnahme. Für die parallele Ausführung verschiedener Abläufe des Algorithmus sind die Global Object (Singleton) Timer verantwortlich. Die Klasse ist recht einfach und bietet die folgenden Funktionen:

  • Periodische Ausführung der Funktion mit einem festgelegten Intervall. Beispiel für eine Timer-Initialisierung:

    Timers.add(doLoop, 6000, F("OneWireSensorsClass::doLoop")); //   –   
  • Eine einzelne Ausführung einer Funktion nach einem bestimmten Zeitraum. Beispielsweise wird ein verzögerter Scan von WiFi-Netzwerken folgendermaßen durchgeführt:

     Timers.once([]() { WiFi.scanNetworks(true);}, 1); 

Die Schleifenfunktion sieht also folgendermaßen aus:

 void loop(void) { ESP.wdtFeed(); Timers.doLoop(); CPULoadInfo.doLoop(); } 

In der Praxis enthält die Schleifenfunktion einige weitere Zeilen, die nachfolgend beschrieben werden.
Eine Liste der Timer-Klasse ist beigefügt.

CPU-Zeitabrechnung


Servicefunktion, die keine praktische Anwendung hat. Trotzdem ist sie. Implementiert vom Singleton CPULoadInfo. Wenn das Objekt initialisiert wird, wird die Anzahl der Iterationen der leeren Schleife für einen kurzen Zeitraum gemessen:

 void CPULoadInfoClass::init() { uint32_t currTime = millis(); //      1 while ((millis() - currTime) < 10) { delay(0); MaxLoopsInSecond++; } MaxLoopsInSecond *= 100; } 

Dann zählen wir die Anzahl der Schleifenprozeduraufrufe pro Sekunde, berechnen die Prozessorlast in Prozent und speichern die Daten im Puffer:

 void CPULoadInfoClass::doLoop() { static uint32_t prevTime = 0; uint32_t currTime = millis(); LoopsInSecond++; if ((currTime - prevTime) > 1000) { memmove(CPULoadPercentHistory, &CPULoadPercentHistory[1], sizeof(CPULoadPercentHistory) - 1); int8_t load = ((MaxLoopsInSecond - LoopsInSecond) * 100) / MaxLoopsInSecond; CPULoadPercentHistory[sizeof(CPULoadPercentHistory) - 1] = load; prevTime = currTime; LoopsInSecond = 0; } } 

Wenn Sie diesen Ansatz verwenden, können Sie für jeden einzelnen Thread die gleiche Prozessorauslastung erzielen (wenn Sie dieses Subsystem mit der Timers-Klasse verbinden), aber wie gesagt - ich sehe keine praktische Anwendung dafür.

Eingabe-Ausgabe-System


Für die Kommunikation mit dem Benutzer werden die UART-USB- und die WEB-Schnittstelle verwendet. Ich denke an UART, ich muss nicht im Detail sprechen. Das einzige, worauf Sie achten sollten, ist die Bequemlichkeit und Kompatibilität mit Nicht-ESP. Die Funktion serialEvent () ist implementiert:

 void loop(void) { // … if (Serial.available()) serialEvent(); // … } 

Mit der WEB-Oberfläche ist alles viel interessanter. Wir widmen ihm einen eigenen Abschnitt.

WEB-Schnittstelle


Bei intelligenten Geräten ist die WEB-Oberfläche meiner Meinung nach die benutzerfreundlichste Lösung.

Ich halte die Verwendung eines an das Gerät angeschlossenen Bildschirms für eine veraltete Praxis - es ist unmöglich, eine einfache, bequeme und schöne Benutzeroberfläche zu erstellen, wenn ein kleiner Bildschirm und eine begrenzte Anzahl von Tasten verwendet werden.

Die Verwendung spezifischer Programme zur Steuerung des Geräts führt zu Einschränkungen für den Benutzer, erhöht die Notwendigkeit, diese Programme zu entwickeln und zu unterstützen, und der Entwickler muss sich auch um die Bereitstellung dieser Programme für die Endgeräte des Benutzers kümmern. In guter Weise sollte die Anwendung in den Anwendungsspeichern von Google, Apple und Windows veröffentlicht und auch in Linux-Repositorys in Form von Deb- und RPM-Paketen verfügbar sein. Andernfalls kann der Zugriff auf die Benutzeroberfläche des Geräts für einen Teil der Zielgruppe schwierig sein.

Der Zugriff auf die WEB-Oberfläche des Geräts ist von jedem Betriebssystem aus möglich - Linux, Windows, Android, MacOS, auf dem Desktop, Laptop, Tablet, Smartphone -, nur um einen Browser zu haben. Natürlich muss der Entwickler der WEB-Schnittstelle die Funktionen verschiedener Geräte berücksichtigen, dies betrifft jedoch hauptsächlich die Größe und Auflösung. Der Zugriff auf die WEB-Schnittstelle eines Smart-Geräts in einem Haus / Apartment / Cottage erfolgt problemlos von außen über das Internet. Jetzt ist es schwer vorstellbar, dass es in einem Haus / einer Wohnung Smart-Geräte und keinen Router und kein Internet gibt. Im Router wird dieser Zugriff mit wenigen Klicks konfiguriert (für diejenigen, die dies tun) Die Schlüsselwörter, die völlig vom Thema abweichen, helfen ("Portweiterleitung" und "dynamisches DNS"). Im Falle einer Sommerresidenz kann der Zugang über einen 3G-Router erfolgen.

Zur Implementierung der WEB-Schnittstelle ist ein WEB-Server erforderlich. Ich verwende die Bibliothek me-no-dev / ESPAsyncWebServer. Diese Bibliothek bietet die folgenden Funktionen:

  • Rückgabe von statischem Inhalt, einschließlich mit gzip Komprimierungsunterstützung. Es werden virtuelle Verzeichnisse unterstützt, mit der Möglichkeit, für jedes Verzeichnis eine Hauptdatei (normalerweise index.htm) anzugeben.
  • Zuweisen von Rückruffunktionen zu verschiedenen URLs basierend auf der Art der Anforderung (GET, POST, ...)
  • Unterstützung für WEB-Sockets am selben Port (wichtig bei der Portweiterleitung).
  • BASIC-Autorisierung. Darüber hinaus wird die Autorisierung für jede URL individuell festgelegt. Das ist wichtig, weil Beispielsweise fordert Google Chrome beim Erstellen einer Seitenverknüpfung auf dem Hauptbildschirm ein Symbol und eine Manifestdatei an und überträgt keine Autorisierungsdaten. Daher werden einige Dateien in einem virtuellen Verzeichnis abgelegt und die Autorisierung für dieses Verzeichnis ist deaktiviert.

Betriebssystem für HTTP-Dienste


Im aktuellen Projekt werden alle Betriebssystemeinstellungen über HTTP-Dienste durchgeführt. Der HTTP-Dienst ist eine kleine unabhängige Funktion zum Abrufen / Ändern von Daten, die über HTTP verfügbar ist. Betrachten Sie als Nächstes eine Liste dieser Dienste.

Hilfe


Die Implementierung einer beliebigen Befehlsliste halte ich für richtig, um mit der Implementierung des HELP-Teams zu beginnen. Unten ist der WEB-Server-Initialisierungsblock:

 void HTTPserverClass::init() { //SERVER INIT help_info.concat(F("/'doc_hame.ext': load file from server. Allow methods: HTTP_GET\n")); AsyncStaticWebHandler& handler = server.serveStatic("na/", SPIFFS, "na/"); serveStaticHandlerNA = &handler; //       server.serveStatic("/", SPIFFS, "/"); //    //info //       help_info.concat(F("/info: get system info. Allow methods: HTTP_GET\n")); server.on("/info", HTTP_GET, handleInfo); … server.on("/help", HTTP_GET, [](AsyncWebServerRequest *request) { request->send(200, ContentTypesStrings[ContentTypes::text_plain], help_info.c_str()); }); //    setAuthentication(ConfigStore.getAdminName(), ConfigStore.getAdminPassword()); DefaultHeaders::Instance().addHeader("Access-Control-Allow-Origin", "*"); //  -  //   server.begin(); //       DEBUG_PRINT(F("HTTP server started")); } 

Warum brauche ich ein Hilfesystem? Ich denke, es lohnt sich nicht, es zu erzählen. Einige Entwickler verschieben die Implementierung des Hilfesystems auf später, aber dieses "spätere" tritt normalerweise nicht auf. Es ist viel einfacher, es zu Beginn des Projekts umzusetzen und während der Entwicklung des Projekts zu ergänzen.
In meinem Projekt wird auch eine Liste möglicher Dienste mit einem 404-Fehler angezeigt - die Seite wurde nicht gefunden. Derzeit implementiert die folgenden Dienste:

http://tc-demo.vehs.ru/help

 /'doc_hame.ext': load file from server. Allow methods: HTTP_GET /info: get system info. Allow methods: HTTP_GET /time: get time as string (eg: 20140527T123456). Allow methods: HTTP_GET /uptime: get uptime as string (eg: 123D123456). Allow methods: HTTP_GET /rtc: set RTC time. Allow methods: HTTP_GET, HTTP_POST /list ? [dir=...] & [format=json]: get file list as text or json. Allow methods: HTTP_GET /edit: edit files. Allow methods: HTTP_GET, HTTP_PUT, HTTP_DELETE, HTTP_POST /wifi: edit wifi settings. Allow methods: HTTP_GET, HTTP_POST /wifi-scan ? [format=json]: get wifi list as text or json. Allow methods: HTTP_GET /wifi-info ? [format=json]: get wifi info as text or json. Allow methods: HTTP_GET /ap: edit soft ap settings. Allow methods: HTTP_GET, HTTP_POST /user: edit user settings. Allow methods: HTTP_GET, HTTP_POST /user-info ? [format=json]: get user info as text or json. Allow methods: HTTP_GET /update: update flash. Allow methods: HTTP_GET, HTTP_POST /restart: restart system. Allow methods: HTTP_GET, HTTP_POST /ws: web socket url. Allow methods: HTTP_GET /help: list allow URLs. Allow methods: HTTP_GET 

Wie Sie sehen können, gibt es in der Liste der Dienste keine Anwendungsdienste. Alle HTTP-Dienste beziehen sich auf das Betriebssystem. Jeder Dienst führt eine kleine Aufgabe aus. Wenn der Dienst die Eingabe von Daten erfordert, gibt GET auf Anfrage ein minimalistisches Eingabeformular zurück:

 #ifdef USE_RTC_CLOCK help_info.concat(F("/rtc: set RTC time. Allow methods: HTTP_GET, HTTP_POST\n")); const char* urlNTP = "/rtc"; server.on(urlNTP, HTTP_GET, [](AsyncWebServerRequest *request) { DEBUG_PRINT(F("/rtc")); request->send(200, ContentTypesStrings[ContentTypes::text_html], String(F("<head><title>RTC time</title></head><body><form method=\"post\" action=\"rtc\"><input name=\"newtime\" length=\"15\" placeholder=\"yyyyMMddThhmmss\"><button type=\"submit\">set</button></form></body></html>"))); }); server.on(urlNTP, HTTP_POST, handleSetRTC_time); #endif // USE_RTC_CLOCK 



Später wird dieser Dienst in einer schöneren Oberfläche verwendet:



Anwendungssoftware


Schließlich kamen wir zu dem Punkt, für den das System erstellt wurde. Nämlich - zur Umsetzung der angewandten Aufgabe.

Jede Anwendung muss die Quelldaten empfangen, verarbeiten und das Ergebnis erstellen. Es ist auch möglich, dass das System den aktuellen Status meldet.

Die Quelldaten für den Fußbodenheizungsregler sind:

  • Sensordaten - Das System ist nicht an bestimmte Sensoren gebunden. Für jeden Sensor wird eine eindeutige Kennung generiert. Bei Funksensoren wird ihre Kennung mit Nullen bis 16 Bit aufgefüllt, bei 1Wire-Sensoren wird CRC16 basierend auf ihrer internen Kennung berechnet und als Sensorkennung verwendet. Somit haben alle Sensoren Kennungen mit einer Länge von 2 Bytes.
  • Daten zu Heizzonen - Die Anzahl der Zonen ist nicht festgelegt, die maximale Anzahl wird durch das verwendete Relaismodul begrenzt. Angesichts dieser Einschränkung wurde auch die WEB-Schnittstelle entwickelt.
  • Zieltemperatur und Zeitplan - Ich habe versucht, die flexibelsten Einstellungen vorzunehmen. Sie können mehrere Heizschemata erstellen und jeder Zone sogar ein eigenes Einstellungsschema zuweisen.

Daher gibt es eine Reihe von Einstellungen, die irgendwie vorgenommen werden müssen, und es gibt eine Reihe von Parametern, die das System als aktuellen Status meldet.
Für die Kommunikation zwischen der Steuerung und der Außenwelt habe ich einen Befehlsinterpreter implementiert, mit dem ich sowohl die Steuerung der Steuerung als auch den Empfang von Statusdaten implementieren konnte. Befehle werden in lesbarer Form an die Steuerung übertragen und können über einen UART- oder WEB-Socket übertragen werden (wenn Sie möchten, können Sie die Unterstützung für andere Protokolle implementieren, z. B. Telnet).
Die Befehlszeile beginnt mit dem Zeichen '#' und endet mit einem Nullzeichen oder einem Zeilenumbruchzeichen. Alle Befehle bestehen aus einem Befehlsnamen und einem Operanden, die durch einen Doppelpunkt getrennt sind. Bei einigen Befehlen ist der Operand optional. In diesem Fall werden Doppelpunkt und Operand nicht angegeben. Befehle in einer Zeile werden durch ein Komma getrennt. Zum Beispiel:

 #ZonesInfo:1,SensorsInfo 

Und natürlich beginnt die Liste der Befehle mit dem Befehl Hilfe, der eine Liste aller gültigen Befehle anzeigt (der Einfachheit halber beginnen die übertragenen Befehle mit einem '>' anstelle von '#'):

 >help Help SetZonesCount Zone SetName SetSensor ... LoadCfg SaveCfg #Cmd:Help,CmdRes:Ok 

Ein Merkmal der Implementierung des Befehlsinterpreters besteht darin, dass Informationen über das Ergebnis der Befehlsausführung auch in Form eines Befehls oder einer Reihe von Befehlen ausgegeben werden:

 >help#Cmd:Help,CmdRes:Ok >zone:123 #Cmd:Zone,Value:123,CmdRes:Error,Error:Zone 123 not in range 1-5 >SchemasInfo #SchemasCount:2 #Schema:1,Name:,DOWs:0b0000000 #Schema:2,Name:,DOWs:0b0000000 #Cmd:SchemasInfo,CmdRes:Ok 

Auf der WEB-Client-Seite ist auch eine Shell implementiert, die diese Befehle akzeptiert und in eine grafische Ansicht konvertiert. Zum Beispiel:

 >zonesInfo:3 #Zone:3,Name:,Sensor:0x5680,Schema:1,DeltaT:-20 #Cmd:ZonesInfo,CmdRes:Ok 

Die WEB-Schnittstelle schickte eine Anfrage an die Steuerung bezüglich der Zonennummer 3 und erhielt als Antwort den Namen der Zone, die Kennung des der Zone zugeordneten Sensors, die Kennung des der Zone zugewiesenen Stromkreises und die Temperaturkorrektur für die Zone. Die Schale versteht keine Bruchzahlen, so dass die Temperatur in Zehntelgraden übertragen wird, d.h. 12,3 Grad sind 123 Zehntel.

Das Hauptmerkmal ist, dass der Controller unabhängig von der Methode zur Eingabe des Befehls auf alle Clients gleichzeitig auf einen Befehl reagiert. Auf diese Weise können Sie die Statusänderung sofort in allen Sitzungen der WEB-Schnittstelle anzeigen. Weil Der Hauptaustauschtransport zwischen der Steuerung und der WEB-Schnittstelle sind WEB-Sockets. Die Steuerung kann dann Daten ohne Anforderung übertragen, z. B. wenn neue Daten von Sensoren stammen:

 #sensor:0x5A20,type:w433th,battery:1,button_tx:0,channel:0,temperature:228,humidity:34,uptime_label:130308243,time_label:20180521T235126 

Oder zum Beispiel, dass diese Zonen aktualisiert werden müssen:

 #Zone:2,TargetTemp:220,CurrentTemp:228,Error:Ok 

Die WEB-Schnittstelle des Controllers basiert auf der Verwendung von Textbefehlen. Auf einer der Registerkarten der Benutzeroberfläche befindet sich ein Terminal, mit dem Sie Befehle in Textform eingeben können. Auf dieser Registerkarte können Sie zu Debugging-Zwecken herausfinden, welche Befehle die WEB-Schnittstelle mit verschiedenen Benutzeraktionen sendet und empfängt.

Der Befehlsinterpreter erleichtert das Ändern und Erweitern der Funktionalität des Geräts, indem vorhandene Befehle geändert und neue hinzugefügt werden. Gleichzeitig ist das Debuggen eines solchen Systems viel einfacher. Die Kommunikation mit dem Controller erfolgt ausschließlich in einer für Menschen lesbaren Sprache.

Fazit


Mit einem ähnlichen Ansatz, nämlich:

  • Trennung von Software in Betriebssystem und Anwendungsprogramm
  • Implementieren von Betriebssystemeinstellungen in Form minimalistischer HTTP-Dienste
  • Systemlogik von Datenpräsentation trennen
  • Verwenden von lesbaren Kommunikationsprotokollen

ermöglicht es Ihnen, Lösungen zu erstellen, die sowohl für Benutzer als auch für Entwickler verständlich sind. Solche Lösungen sind leicht zu modifizieren. Basierend auf solchen Lösungen ist es einfach, neue Geräte mit einer völlig anderen Logik zu bauen, die jedoch nach denselben Prinzipien arbeiten. Sie können eine Reihe von Geräten mit demselben Schnittstellentyp erstellen:



Wie Sie sehen können, beziehen sich in diesem Projekt nur die ersten drei Seiten der Benutzeroberfläche direkt auf die Anwendung, und der Rest ist nahezu universell.

In dieser Veröffentlichung beschreibe ich nur meine Meinung zum Aufbau intelligenter Geräte und behaupte auf keinen Fall, die ultimative Wahrheit zu sein.

Für wen dieses Thema interessant ist - schreiben Sie, vielleicht irre ich mich in etwas, aber vielleicht sind einige Details sinnvoll, um sie genauer zu beschreiben.

Was am Ende passiert ist: Fiasko. Die Geschichte eines hausgemachten IoT

Source: https://habr.com/ru/post/de412889/


All Articles