ESP8266 stürzt in nur einem WLAN ab und hängt in einer Bootschleife

Geschätzte Lesezeit: 7 Minuten.

Der Traum einer eigenen Hausautomation

Da wir seit kurzem Häuslebesitzer sind, wollte ich mein Heim etwas automatisieren. Bestehende Lösungen wie z.B. von Homematic und Konsorten, finde ich persönlich nicht gut, da sie entweder teuer oder unsicher sind und ich nicht weiß, was mit meinen Daten passiert, daher baue ich mir einfach etwas eigenes. Know-How ist vorhanden. Alles was fehlt, kann man kaufen oder sich aneignen. Im ersten Step, soll einfach eine simple Temperatur- und Luftfeuchtigkeitsmessung implementiert werden.

Aktuell arbeite ich mich in die Mikrocontroller von espressif ein, da Sie recht günstig und vollgestopft mit sinnvollen Funktionen sind. Aktuell kostet ein ESP32 Dev Board (bereits mit Pinouts und einigen Spielereien) ca. 10€. Entweder man bestellt Sie im Land der aufgehenden Sonne, muss aber dann einige Wochen warten, oder man wird hier direkt bei Amazon fündig:

Nach dem die guten Stücke dann angekommen sind, habe ich prompt begonnen die Firmware zu schreiben. Wie das geht, werde ich in einem späteren Artikel niederschreiben.

Etliche Stunden und Tests später, war die grundlegende Firmware „fertig“ (wann ist Software mal fertig?).

Automatische Updates und Zugriff auf externe APIs

Da ich in meinem Haus mindestens einen Controller pro Raum haben möchte, wäre es ziemlich dämlich, eventuelle neue Firmwares via USB auszurollen. Das ist zeitaufwendig und nervig, das will ich nicht.

Programmierer wie ich bin, habe ich dann einfach ein OTA-Update-System (over the air) implementiert. Zugegebenermaßen, ist das nicht auf meinen Mist gewachsen und habe mich der großen Community hinter den Controllern bedient und an meine Bedürfnisse angepasst und erweitert.

Der Vorteil eines solchen Systems liegt klar auf der Hand:

Ich schreibe eine neue Firmware bzw. implementiere neue Funktionen, teste sie mit einem Developer-Board direkt am Rechner, committe Sie nach GitLab und die CI baut mir die Firmware, mithilfe von PlatformIO und Docker, und schiebt sie auf einen FTP-Server. Von dort aus holen sich dann die Controller die Infos und die Binaries. Das ganze Verfahren klappt super und verlässlich. Wie genau das funktioniert, erfährst du in meinem Artikel Automatische Firmware-Updates für Microcontroller mit Gitlab und PlatformIO, da auch der Wunsch innerhalb der ESP-Gruppen im Facebook kam.

Das passende Diagramm sähe, im Groben, so aus:

Zu schnell gefreut, irgendetwas kommt immer

Mein ESP8266 startet nun aber nicht mehr und hängt in einer Bootschleife. Das passiert aber nur bei meinem WLAN zuhause. Mit dem ESP32 klappt das wunderbar und auch mit einem anderen WLAN mit dem ESP8266. Daher habe ich mir einmal den Exception-Stack genommen und diesen einmal entschlüsselt (wie das geht, erfährst du in meinem Artikel Exceptions und Fehlermeldungen eines ESP-Mikrocontroller entschlüsseln):

Decoding 10 results
Exception (28):
epc1=0x40219f8c epc2=0x00000000 epc3=0x00000000 excvaddr=0x00000000 depc=0x00000000
ctx: cont
sp: 3fff26e0 end: 3fff2960 offset: 01a0
>>>stack>>>
3fff2880: 00000000 3ffe8f0c 3fff1778 40219f8c
3fff2890: 00000000 00000000 00000000 00000000
3fff28a0: 00000000 00000000 00000000 00000000
3fff28b0: 00000000 3fff5f7c 0000003f 00000034
3fff28c0: 00000000 3fff3198 3fff1778 4021a0ed
3fff28d0: 00000000 00000000 00000000 4020ba68
3fff28e0: 00000000 00000000 3fff5e3c 4020bbcc
3fff28f0: 00000000 00000000 4021961c 4021b72c
3fff2900: 00000000 3fff14b8 3fff15cc 3fff5ea4
3fff2910: 0000000f 0000000a 3fff1778 4021197e
3fff2920: 00000000 3fff14b8 3fff1778 4021bb44
3fff2930: 00000000 00000000 00000000 feefeffe
3fff2940: 3fffdad0 00000000 3fff1924 40202ef8
3fff2950: feefeffe feefeffe 3fff1940 40205064
<<<stack<<<

Der „entschlüsselte“ Stack ist dieser hier:

Decoding 10 results
0x40219f8c: checkRegistration() at ?? line ?
0x4021a0ed: registerDeviceAPI() at ?? line ?
0x4020ba68: UdpContext::UdpContext() at ?? line ?
0x4020bbcc: WiFiUDP::begin(unsigned short) at ?? line ?
0x4021961c: std::_Function_base::_Base_manager ::_M_manager(std::_Any_data&, std::_Function_base::_Base_manager const&, std::_Manager_operation) at main.cpp line ?
0x4021b72c: std::_Function_handler ::_M_invoke(std::_Any_data const&) at main.cpp line ?
0x4021197e: Ticker::start() at ?? line ?
0x4021bb44: setup at ?? line ?
0x40202ef8: loop_wrapper() at core_esp8266_main.cpp line ?
0x40205064: cont_norm at cont.o line ?

Das Problem scheint also in der Function „checkRegistration“ zu passieren. Diese sieht wie folgt aus:

bool checkRegistration(){
  String params = "columns=ID&filter=MAC,eq,"+urlencode(WiFi.macAddress());
  // Debug.println("[checkRegistration]: Params: "+params);
  JsonObject& ret = api.get("Devices", params);
  if (ret.success()) {
    DeviceID = ret["Devices"][0]["ID"] | 0;
    if(DeviceID != 0){
      Debug.println("[checkRegistration]: Device already registered: "+String(DeviceID));
      return true;
    }else{
      Debug.println("[checkRegistration]: Device not registered");
      return false;
    }
  }else{
     Debug.println("[checkRegistration]: Failed!");
     return false;
  }
}
JsonObject& API::get(String url, String params){
    DynamicJsonBuffer apiJsonBuffer(2000);
    String URL = String(API_BASE)+"/"+url+"/?"+params+"&transform=1";
    if(verboseLogging){
        Debug.println("[API::get]: Calling API: "+URL);
    }

    API::apiObject.begin(URL);
    int httpCode = API::apiObject.GET();
    // delay(1000);
    if(httpCode) {
        Debug.println("[API::get]: Got HTTP: "+String(httpCode));
        if(httpCode == 200) {
            String retJson = API::apiObject.getString();
            retJson.replace("""", "\\""");
            Debug.println("[API::get]: Got JSON: "+String(retJson));

            JsonObject& ret = apiJsonBuffer.parseObject(retJson);
            apiJsonBuffer.clear();
            if (ret.success()) {
                return ret;
            }else{
                Debug.println("[API::get]: Could not parse JSON: ");
                return apiJsonBuffer.createObject();
            }
        }
        }else{
            Debug.println("[API::get]: Call Failed! HTTP-Code "+String(httpCode));
            // display_text("API::get failed");
            API::apiObject.end();
            return apiJsonBuffer.createObject();
        }
}

Das Objekt API::apiObject ist eigentlich nichts anderes, als eine Instanz der HTTPClient-Klasse, also kein Hexenwerk.
Ich habe auch schon mit diversen Ausgaben herausgefunden, dass er gar nicht erst in die Methode der API hineinläuft und scheinbar vor dem Aufruf in der „checkRegistration“ stehen bleibt und nichts mehr passiert.

Wie gesagt, das passiert auch nur in meinem WLAN zuhause. Zuhause habe ich auch nichts besonderes:
Eine einfache Unitymedia Horizon-Box mit 2,4 und 5GHz WLAN, ne Synology NAS und halt ein paar Clients, das war’s aber auch im großen und Ganzen.

Ernüchterung und etwas verbuggte Firmware

Bei Reddit hatte ich das Problem auch beschrieben, ebenso im Facebook. Viele meinten, es könne an Sonderzeichen innerhalb der SSID liegen. Ist bei mir auch der Fall, da meine SSID Leerzeichen enthält. Diese hatte ich aber schon in vorherigen Tests entfernt – ohne Erfolg.
Dann habe mir einmal mein iPhone und mein iPad genommen und einen Hotspot eingerichtet, so dass der ESP sich gegen diesen verbinden kann. Hier klappte es dann ohne Probleme.
Also muss es ein meinem WLAN liegen. Wie sich dann rausstellte, hat der ESP8266 anscheinend Probleme mit Sonderzeichen, im meinem Falle konkret ein Dollar-Zeichen ($). Wer auch immer hier die Finger mit im Spielhatte beim ESP8266, gehört eigentlich dafür erschlagen. Vermutlich werde ich aber dafür einen Call bei Espressif im Github aufmachen. Wenn es nicht sogar schon einen gibt, denn das ist schon ein kritischer Bug.

Jedenfalls klappt es jetzt, ohne Sonderzeichen.

Vielleicht hilft das dem Einen oder Anderen von Euch einmal 🙂

Byte-style.de ist mein persönlicher Blog über IT, Elektronik, Programmierung und dem allgemeinen Horror von Bits und Bytes. Zwischendurch blogge ich auch einfach einmal über Reiseorte und andere Dinge.

Ein Kommentar

Kommentar verfassen

Diese Website verwendet Akismet, um Spam zu reduzieren. Erfahre mehr darüber, wie deine Kommentardaten verarbeitet werden.