{"id":410,"date":"2021-06-13T14:49:58","date_gmt":"2021-06-13T12:49:58","guid":{"rendered":"https:\/\/www.iot-embedded.de\/iot-2021\/?p=410"},"modified":"2021-06-13T14:50:00","modified_gmt":"2021-06-13T12:50:00","slug":"over-the-air-updates-ein-simples-beispiel","status":"publish","type":"post","link":"https:\/\/www.iot-embedded.de\/iot-2021\/beverage-monitoring\/over-the-air-updates-ein-simples-beispiel\/","title":{"rendered":"Over-the-Air Updates &#8211; ein simples Beispiel"},"content":{"rendered":"\n<p>In diesem Beitrag zeigen wir wie ein einfaches Over-The-Air (OTA) Beispiel f\u00fcr den ESP8266 ohne Verschl\u00fcsselung und ohne Signatur realisiert werden kann.<\/p>\n\n\n\n<p>Voraussetzung sind ein ESP8266 sowie eine Entwicklungsumgebung. In diesem Beispiel nutzen wir das PlatformIO CLI Tool, die Arduino IDE kann jedoch analog hierzu benutzt werden.<\/p>\n\n\n\n<p>Ziel ist es die laufende Logik eines ESPs \u00fcber eine Netzwerkverbindung zu \u00e4ndern. Hierf\u00fcr ben\u00f6tigen wir zum einen entsprechende Logik auf dem ESP, als auch einen HTTP Server, welcher das Update in bin\u00e4rer Form bereitstellt. In vorherigen Blockbeitrag haben wir gezeigt, dass das Update eigentlich durch ein Event getriggert werden soll. In diesem Beispiel konzentrieren wir uns aber nur auf den OTA Aspekt und lassen dieses Event weg. Das Update soll hiernach einfach durch das Ablaufen eines Timers getriggert werden.<\/p>\n\n\n\n<p>Zu Beginn initialisieren wir das Projekt mit der PlatformIO CLI und erstellen eine Datei, welche sp\u00e4ter unseren Code beinhaltet.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$ mkdir ota-example\n$ cd ota-example\n$ pio init --board nodemucv2\n$ touch src\/main.cpp<\/code><\/pre>\n\n\n\n<p>Wir nutzen ein Beispiel aus dem Arduino Repository, um ein OTA Update zu testen. Eine Code Vorlage finden wir in den <a href=\"https:\/\/github.com\/esp8266\/Arduino\/blob\/master\/libraries\/ESP8266httpUpdate\/examples\/httpUpdate\/httpUpdate.ino\">Beispielen f\u00fcr die HttpUpdate Komponente<\/a> im Arduino Repository f\u00fcr den ESP8266.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/**\n   httpUpdate.ino\n\n    Created on: 27.11.2015\n\n*\/\n\n#include &lt;Arduino.h&gt;\n\n#include &lt;ESP8266WiFi.h&gt;\n#include &lt;ESP8266WiFiMulti.h&gt;\n\n#include &lt;ESP8266HTTPClient.h&gt;\n#include &lt;ESP8266httpUpdate.h&gt;\n\n#ifndef APSSID\n#define APSSID \"APSSID\"\n#define APPSK  \"APPSK\"\n#endif\n\nESP8266WiFiMulti WiFiMulti;\n\nvoid setup() {\n\n  Serial.begin(115200);\n  \/\/ Serial.setDebugOutput(true);\n\n  Serial.println();\n  Serial.println();\n  Serial.println();\n\n  for (uint8_t t = 4; t &gt; 0; t--) {\n    Serial.printf(\"&#091;SETUP] WAIT %d...\\n\", t);\n    Serial.flush();\n    delay(1000);\n  }\n\n  WiFi.mode(WIFI_STA);\n  WiFiMulti.addAP(APSSID, APPSK);\n\n\n}\n\nvoid update_started() {\n  Serial.println(\"CALLBACK:  HTTP update process started\");\n}\n\nvoid update_finished() {\n  Serial.println(\"CALLBACK:  HTTP update process finished\");\n}\n\nvoid update_progress(int cur, int total) {\n  Serial.printf(\"CALLBACK:  HTTP update process at %d of %d bytes...\\n\", cur, total);\n}\n\nvoid update_error(int err) {\n  Serial.printf(\"CALLBACK:  HTTP update fatal error code %d\\n\", err);\n}\n\n\nvoid loop() {\n  \/\/ wait for WiFi connection\n  if ((WiFiMulti.run() == WL_CONNECTED)) {\n\n    WiFiClient client;\n\n    \/\/ The line below is optional. It can be used to blink the LED on the board during flashing\n    \/\/ The LED will be on during download of one buffer of data from the network. The LED will\n    \/\/ be off during writing that buffer to flash\n    \/\/ On a good connection the LED should flash regularly. On a bad connection the LED will be\n    \/\/ on much longer than it will be off. Other pins than LED_BUILTIN may be used. The second\n    \/\/ value is used to put the LED on. If the LED is on with HIGH, that value should be passed\n    ESPhttpUpdate.setLedPin(LED_BUILTIN, LOW);\n\n    \/\/ Add optional callback notifiers\n    ESPhttpUpdate.onStart(update_started);\n    ESPhttpUpdate.onEnd(update_finished);\n    ESPhttpUpdate.onProgress(update_progress);\n    ESPhttpUpdate.onError(update_error);\n\n    t_httpUpdate_return ret = ESPhttpUpdate.update(client, \"http:\/\/server\/file.bin\");\n    \/\/ Or:\n    \/\/t_httpUpdate_return ret = ESPhttpUpdate.update(client, \"server\", 80, \"file.bin\");\n\n    switch (ret) {\n      case HTTP_UPDATE_FAILED:\n        Serial.printf(\"HTTP_UPDATE_FAILD Error (%d): %s\\n\", ESPhttpUpdate.getLastError(), ESPhttpUpdate.getLastErrorString().c_str());\n        break;\n\n      case HTTP_UPDATE_NO_UPDATES:\n        Serial.println(\"HTTP_UPDATE_NO_UPDATES\");\n        break;\n\n      case HTTP_UPDATE_OK:\n        Serial.println(\"HTTP_UPDATE_OK\");\n        break;\n    }\n  }\n}<\/code><\/pre>\n\n\n\n<p>Die Callback Funktionen, welche uns \u00fcber den Stand des Updates informieren sind vorerst nicht wichtig, k\u00f6nnen aber z.B. sp\u00e4ter im Betrieb genutzt werden um zu Loggen, das ein ESP gerade ein Update startet bzw. beendet.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>void update_started() {\n  - Serial.println(\"CALLBACK:  HTTP update process started\");\n  + MQTT.send(updateTopic, format(\"INFO: ESP%s started to update\", ESP_MAC)); \/\/ Pseudo Code\n}\n\nvoid update_finished() {\n  - Serial.println(\"CALLBACK:  HTTP update process finished\");\n  + MQTT.send(updateTopic, format(\"INFO: ESP%s successfully updated\", ESP_MAC)); \/\/ Pseudo Code\n}\n\n- void update_progress(int cur, int total) {\n-   Serial.printf(\"CALLBACK:  HTTP update process at %d of %d bytes...\\n\", cur, total);\n- }\n\nvoid update_error(int err) {\n  - Serial.printf(\"CALLBACK:  HTTP update fatal error code %d\\n\", err);\n  + MQTT.send(updateTopic, format(\"ERROR: ESP%s failed to update\", ESP_MAC)); \/\/ Pseudo Code\n}<\/code><\/pre>\n\n\n\n<p>ACHTUNG: In diesem Beispiel lassen wir aber erst einmal alles so wie es ist, und loggen alles mit der <code>Serial.print<\/code> Funktion. Damit das OTA Update funktioniert m\u00fcssen wir noch valide WiFi Daten hinterlegen. Wir halten uns an ihre Vorgabe mit Pr\u00e4prozessor Definitionen allerdings lagern wir diese in eine andere Datei um. F\u00fcr das aktive Entwickeln in einem Repository bietet es sich an solche Informationen in einer separaten Datei zu speichern, z.B. einer neuen Header Datei <code>secret.h<\/code>.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$ touch src\/secret.h\n$ cat src\/secret.h\n#ifndef SECRET_H\n#define SECRET_H\n#define APSSID \"Wlan Network Name\"\n#define APPSK \"Password\"\n#endif<\/code><\/pre>\n\n\n\n<p>Damit diese Definitionen auch in <code>src\/main.cpp<\/code> genutzt werden k\u00f6nnen muss die Header Datei hinzugef\u00fcgt werden:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>...\n#include &lt;ESP8266HTTPClient.h&gt;\n#include &lt;ESP8266httpUpdate.h&gt;\n\n- #ifndef APSSID\n- #define APSSID \"APSSID\"\n- #define APPSK  \"APPSK\"\n- #endif\n+ #include \"secret.h\"\n\nESP8266WiFiMulti WiFiMulti;\n\nvoid setup() {\n...<\/code><\/pre>\n\n\n\n<p>Das Sch\u00f6ne an dieser extra Datei ist die M\u00f6glichkeit sie mit in die <code>.gitignore<\/code> Datei aufzunehmen, um zu verhindern, dass die WiFi Daten versehentlich in der Historie des Repositories auftauchen. TIPP: Auch an die <code>.gitignore<\/code> Datei denken ansonsten hilft das ganze nur wenig.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>echo \"src\/secret.h\" &gt;&gt; .gitignore<\/code><\/pre>\n\n\n\n<p>Nun schauen wir den Code in der <code>loop()<\/code> Methode an:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>void loop() {\n  \/\/ wait for WiFi connection\n  if ((WiFiMulti.run() == WL_CONNECTED)) {\n\n    WiFiClient client;\n\n    \/\/ The line below is optional. It can be used to blink the LED on the board during flashing\n    \/\/ The LED will be on during download of one buffer of data from the network. The LED will\n    \/\/ be off during writing that buffer to flash\n    \/\/ On a good connection the LED should flash regularly. On a bad connection the LED will be\n    \/\/ on much longer than it will be off. Other pins than LED_BUILTIN may be used. The second\n    \/\/ value is used to put the LED on. If the LED is on with HIGH, that value should be passed\n    ESPhttpUpdate.setLedPin(LED_BUILTIN, LOW);\n\n    \/\/ Add optional callback notifiers\n    ESPhttpUpdate.onStart(update_started);\n    ESPhttpUpdate.onEnd(update_finished);\n    ESPhttpUpdate.onProgress(update_progress);\n    ESPhttpUpdate.onError(update_error);<\/code><\/pre>\n\n\n\n<p>Wie schon beschrieben registrieren wir hier Funktionen, welche uns erm\u00f6glichen einzelne Schritte des Update Prozesses mitzuverfolgen und zu Loggen.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>    t_httpUpdate_return ret = ESPhttpUpdate.update(client, \"http:\/\/server\/file.bin\");\n    \/\/ Or:\n    \/\/t_httpUpdate_return ret = ESPhttpUpdate.update(client, \"server\", 80, \"file.bin\");\n\n    switch (ret) {\n      case HTTP_UPDATE_FAILED:\n        Serial.printf(\"HTTP_UPDATE_FAILD Error (%d): %s\\n\", ESPhttpUpdate.getLastError(), ESPhttpUpdate.getLastErrorString().c_str());\n        break;\n\n      case HTTP_UPDATE_NO_UPDATES:\n        Serial.println(\"HTTP_UPDATE_NO_UPDATES\");\n        break;\n\n      case HTTP_UPDATE_OK:\n        Serial.println(\"HTTP_UPDATE_OK\");\n        break;\n    }\n  }\n}<\/code><\/pre>\n\n\n\n<p>Dies ist der letzte ben\u00f6tigte Code-Teil. Wir rufend den httpUpdater auf, indem wir den WiFi Client \u00fcbergeben, sowie die genaue URL des Update Servers. Tats\u00e4chlich m\u00f6chten wir sogar die auskommentierte Methode nutzen, da diese uns erlaubt einen anderen Port als 80 anzusprechen. Dies ist n\u00fctzlich, wenn der Update Server auf einen anderen Port nutzt. Gerade auf Ger\u00e4ten, die keine Administrationsrechte besitzen ist dies oft der Fall, da das Nutzen von Port 80 Administrationsrechte ben\u00f6tigt. Dementsprechend \u00e4ndern wir den Port, sowie die Adresse des Servers. In unserem Fall m\u00f6chten wir gleich einen Update Server auf unserem eigenen Rechner starten, daher nutzen wir unsere eigen IP-Adresse.<\/p>\n\n\n\n<p>F\u00fcr Windows<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>ipconfig\n...\nIPv4-Adresse  . . . . . . . . . . : 192.168.178.123\n                                    ^^^^^^^^^^^^^^^\n...<\/code><\/pre>\n\n\n\n<p>F\u00fcr Linux (und vlt. auch MacOS)<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>ip address\n1: lo: &lt;LOOPBACK,UP,LOWER_UP&gt; mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000\n    ...\n2: enp4s0: &lt;BROADCAST,MULTICAST,UP,LOWER_UP&gt; mtu 1500 qdisc fq_codel state UP group default qlen 1000\n    link\/ether 18:c0:4d:a4:69:7c brd ff:ff:ff:ff:ff:ff\n    inet 192.168.178.123\/24 brd 192.168.178.255 scope global dynamic noprefixroute enp4s0\n         ^^^^^^^^^^^^^^^\n       valid_lft 855349sec preferred_lft 855349sec\n    ...<\/code><\/pre>\n\n\n\n<p>Unsere Adresse ist also <code>192.168.178.123<\/code>, als Port nehmen wir einfach mal <code>8080<\/code> und den Zusatz der URI <code>file.bin<\/code> an welchen sp\u00e4ter die Anfrage nach der Update Datei gesendet wird lassen wir gleich:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>    - t_httpUpdate_return ret = ESPhttpUpdate.update(client, \"http:\/\/server\/file.bin\");\n    - \/\/ Or:\n    - \/\/t_httpUpdate_return ret = ESPhttpUpdate.update(client, \"server\", 80, \"file.bin\");\n    + t_httpUpdate_return ret = ESPhttpUpdate.update(client, \"192.168.178.123, 8080, \"file.bin\");\n\n    switch (ret) {\n      case HTTP_UPDATE_FAILED:\n        Serial.printf(\"HTTP_UPDATE_FAILD Error (%d): %s\\n\", ESPhttpUpdate.getLastError(), ESPhttpUpdate.getLastErrorString().c_str());\n        break;\n\n      case HTTP_UPDATE_NO_UPDATES:\n        Serial.println(\"HTTP_UPDATE_NO_UPDATES\");\n        break;\n\n      case HTTP_UPDATE_OK:\n        Serial.println(\"HTTP_UPDATE_OK\");\n        break;\n    }\n  }\n}<\/code><\/pre>\n\n\n\n<p>Um einen Update Server zu starten, brauchen wir nun ein Update Datei. Hierf\u00fcr nehmen wir das Blink Beispiel, welches im Blogbeitrag zu den Entwicklungsumgebungen benutzt wurde. Unsere Build Artefakte werden von PlatformIO in den Ordner <code>.pio\/build\/nodemucv2<\/code> gelegt. Arduino nutzt normalerweise ein Temp Verzeichnis, in Linux oftmals <code>\/tmp\/...<\/code>, in Windows vielleicht <code>%APPDATA%<\/code>. Beim Bauen des Artefkates loggt die Arduino IDE meines Wissens, wo das Artefakt gespeichert ist.<\/p>\n\n\n\n<p>Neben dem Artefakt ben\u00f6tigen wir noch einen HTTP Server, f\u00fcr unser Beispiel nutzen wir das Python http Modul. Wir gehen in den Ordner, in dem das Blink Artefakt liegt. In unserem Fall ist das der Projekt Ordner aus dem IDE Blogbeitrag. Wir erstellen eine Kopie des Artefaktes, welches der gew\u00fcnschten URI entspricht. Nun starten wir mit Python einen HTTP Server, welcher auf die eigen IP und Port 8080 h\u00f6rt.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$ cd ${IDE_BLOG_POST}\n$ cd blink_example\n$ ls -la\n.gitignore\n.pio\ninclude\nlib\nplatformio.ini\nsrc\ntest\n$ cd .pio\/build\/nodemucv2\n$ cp firmware.bin file.bin\n$ python -m http.server --bind 192.168.178.123 8080<\/code><\/pre>\n\n\n\n<p>Nun h\u00f6rt der Server auf den Port 8080. Wir k\u00f6nnen lokal erst einmal testen ob der HTTP Server auch so funktioniert wie wir es uns w\u00fcnschen:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$ pwd\n{IDE_BLOG_POST}\/blink_example\/.pio\/build\/nodemcuv2\n$ curl 192.168.178.123:8080\/file.bin &gt; test.bin\ndiff file.bin test.bin\n\n# Kein Output zeigt dass beide Dateien gleich sind\n$ sha256sum file.bin\nd103291e7b41893d3210435c2f45c5c3e6eee24b167c82d17c9e1259cc88c3ab  file.bin\n$ sha256sum test.bin\nd103291e7b41893d3210435c2f45c5c3e6eee24b167c82d17c9e1259cc88c3ab  test.bin\n# Auch der Hash beider Dateien ist gleich<\/code><\/pre>\n\n\n\n<p>ACHTUNG: Damit der ESP auch den HTTP Server erreichen kann muss der TCP Port 8080 in der Firewall des eigenen Rechners offen sein. Je nach Betriebsystem, Distribution und Antivirus Programm muss hierf\u00fcr eine Regel angelegt werden. Hier muss ich leider auf Google etc. verweisen.<\/p>\n\n\n\n<p>Nun k\u00f6nnen wir den ESP mit dem OTA Code flashen. Wir erwarten, dass der ESP zu Beginn sich mit dem WiFi verbindet, kurz wartet, dann unseren HTTP Server anfr\u00e4gt, und das Blink Beispiel ausf\u00fchrt.<\/p>\n\n\n\n<p>Um den ESP zu flashen und hiernach die Logs, welche \u00fcber die serielle Schnittstelle geschrieben werden zu lesen, f\u00fchren wir zwei Kommandos direkt nacheinander aus:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$ pio run --target upload; pio device monitor -b 115200\n\nProcessing nodemcuv2 (platform: espressif8266; board: nodemcuv2; framework: arduino)\n--------------------------------------------------------------------------------------\nVerbose mode can be enabled via `-v, --verbose` option\n...\nRAM:   &#091;===       ]  34.0% (used 27872 bytes from 81920 bytes)\nFlash: &#091;===       ]  28.6% (used 298448 bytes from 1044464 bytes)\n...\nConfiguring flash size...\nCompressed 302608 bytes to 220648...\nWriting at 0x00000000... (7 %)\nWriting at 0x00004000... (14 %)\n...\nWriting at 0x00030000... (92 %)\nWriting at 0x00034000... (100 %)\nWrote 302608 bytes (220648 compressed) at 0x00000000 in 19.4 seconds (effective 125.0 kbit\/s)...\nHash of data verified.\n\nLeaving...\nHard resetting via RTS pin...\n============================ &#091;SUCCESS] Took 22.90 seconds ============================\n...\n--- Quit: Ctrl+C | Menu: Ctrl+T | Help: Ctrl+T followed by Ctrl+H ---\n&#091;SETUP] WAIT 3...\n&#091;SETUP] WAIT 2...\n&#091;SETUP] WAIT 1...\nCALLBACK:  HTTP update process started\nCALLBACK:  HTTP update process at 0 of 261488 bytes...\nCALLBACK:  HTTP update process at 0 of 261488 bytes...\nCALLBACK:  HTTP update process at 4096 of 261488 bytes...\n...\nCALLBACK:  HTTP update process at 261488 of 261488 bytes...\nCALLBACK:  HTTP update process finished\n\n ets Jan  8 2013,rst cause:2, boot mode:(3,6)\n\nload 0x4010f000, len 3584, room 16\ntail 0\nchksum 0xb0\ncsum 0xb0\nv2843a5ac\n@cp:0\nld<\/code><\/pre>\n\n\n\n<p>Das OTA Update war erfolgreich und der angeschlossene ESP f\u00fchrt nun das Blink Beispiel aus. Das Projekt ist <a href=\"https:\/\/github.com\/buermarc\/iot-blog-posts\/tree\/main\/simple-ota\">hier<\/a> auf Github zu finden. Im n\u00e4chsten Blog Post schauen wir uns an, wie man Updates signiert, um sicherzustellen, dass die erhaltenen Updates auch wirklich von uns stammen.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>In diesem Beitrag zeigen wir wie ein einfaches Over-The-Air (OTA) Beispiel f\u00fcr den ESP8266 ohne Verschl\u00fcsselung und ohne Signatur realisiert werden kann. Voraussetzung sind ein ESP8266 sowie eine Entwicklungsumgebung. In diesem Beispiel nutzen wir das PlatformIO CLI Tool, die Arduino<\/p>\n","protected":false},"author":3,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":[],"categories":[6],"tags":[],"_links":{"self":[{"href":"https:\/\/www.iot-embedded.de\/iot-2021\/wp-json\/wp\/v2\/posts\/410"}],"collection":[{"href":"https:\/\/www.iot-embedded.de\/iot-2021\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.iot-embedded.de\/iot-2021\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.iot-embedded.de\/iot-2021\/wp-json\/wp\/v2\/users\/3"}],"replies":[{"embeddable":true,"href":"https:\/\/www.iot-embedded.de\/iot-2021\/wp-json\/wp\/v2\/comments?post=410"}],"version-history":[{"count":2,"href":"https:\/\/www.iot-embedded.de\/iot-2021\/wp-json\/wp\/v2\/posts\/410\/revisions"}],"predecessor-version":[{"id":412,"href":"https:\/\/www.iot-embedded.de\/iot-2021\/wp-json\/wp\/v2\/posts\/410\/revisions\/412"}],"wp:attachment":[{"href":"https:\/\/www.iot-embedded.de\/iot-2021\/wp-json\/wp\/v2\/media?parent=410"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.iot-embedded.de\/iot-2021\/wp-json\/wp\/v2\/categories?post=410"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.iot-embedded.de\/iot-2021\/wp-json\/wp\/v2\/tags?post=410"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}