ESP8266 im Tiefschlaf

Send to Kindle

Im letzten Beitrag habe ich in die Grundlagen der Verwendung des ESP8266 eingeführt. Nun möchte ich für den Garten einen Sensorknoten bauen, der alle fünf Minuten Bodenfeuchte, Temperatur und Sonnenlicht misst und per UDP überträgt. Ein Empfängerscript auf einem Raspberry Pi soll die gemessenen Werte dann entgegen nehmen und an eine CSV-Datei anhängen oder in eine Datenbank schreiben.

Ein minimales Empfängerscript in Python, welches eingegangene Daten roh auf die Standardausgabe schreibt, kann wie folgt aussehen. Zu beachten ist hier, dass Daten auf Absenderseite bereits als String formatiert sind:

#!/usr/bin/python
import socket
import time

UDP_IP = "10.76.23.66" # an diese IP binden
UDP_PORT = 5678

sock = socket.socket(socket.AF_INET, # Internet
        socket.SOCK_DGRAM) # Unix Datagram Protocol
sock.bind((UDP_IP, UDP_PORT))

while True:
        data, addr = sock.recvfrom(1024) # Puffer 1024 bytes
        print  time.time(), ": " , data

Reset statt Schlaf

Nun kommen wir zum ESP8266. Sein hoher Strombedarf steht eigentlich dem Betrieb mit AA-Zellen entgegen. Mit einem WLAN verbunden benötigt er dauerhaft wenigstens 15 bis 20mA – zwei AA-Zellen sind damit nach etwa einer Woche leer. Da liegt es nahe, ihn schlafen zu legen. Hierfür stellen die Arduino-Bibliotheken für den ESP8266 die Funktion

ESP.deepSleep(int nanoseconds)

zur Verfügung. Wer schon mit dem AVR und Watchdog-Timern gearbeitet hat, muss an dieser Stelle umdenken, denn die Funktion schaltet den ESP8266 de facto ab, aber stellt einen Wecker der internen RTC an GPIO16 (manchmal auch als “Wake” oder “User” bezeichnet). Diesen muss man dem Reset-Pin verbinden (“R” oder “RST”), damit das Aufwecken einen Reset auslöst und damit den ESP8266 neu startet. Achtung: Wegen des Resets muss gleich nach dem Upload des Sketches die Verbindung zwischen Masse und GPIO0 getrennt werden, sonst landet man nach dem ersten Tiefschlaf im Bootloader!

Im Sketch ist massives Umdenken gegenüber AVR angesagt: Da der Sketch ja bei jedem Aufwecken neu startet, wird in vielen Fällen nur eine leere loop() Funktion benötigt. Und: Natürlich sind Variablen nach dem Neustart weg. Die bequeme AVR-Möglichkeit, bestimmte Messungen nur jeden dritten Durchlauf durchzuführen (Bodenfeuchte ändert sich langsamer als Lichteinfall) oder mehrere Messungen bündelt und gemeinsam überträgt, indem man einen Counter inkrementiert, fällt damit weg.

Immerhin ist der resultierende Sketch recht simpel, da keine Register direkt manipuliert werden müssen:

#include <ESP8266WiFi.h>
#include <WiFiUdp.h>
#define LED_PIN 14
#define SLEEP_TIME 180

const char WiFiSSID[] = "mein Netz :-)";
const char WiFiPSK[] = "G4nz6eh31m";

WiFiUDP Udp;
unsigned int localUdpPort = 5678;
char incomingPacket[255];

void setup() {
  pinMode(LED_PIN, OUTPUT);
  digitalWrite(LED_PIN, LOW);
  connectWiFi();
  digitalWrite(LED_PIN, HIGH);
  Udp.begin(localUdpPort);
  Udp.beginPacket("10.76.23.66", 5678);
  Udp.write("Hallo Welt");
  Udp.endPacket();
  delay(200);
  WiFi.disconnect(); 
  digitalWrite(LED_PIN, LOW);
  ESP.deepSleep(SLEEP_TIME * 1000000);
}

void loop() {}

void connectWiFi() {
  byte ledStatus = LOW;
  WiFi.mode(WIFI_STA);
  WiFi.begin(WiFiSSID, WiFiPSK);
  /* IPAddress ip(10,76,23,244);   
  IPAddress gateway(10,76,23,252);   
  IPAddress subnet(255,255,255,0);   
  WiFi.config(ip, gateway, subnet);*/
  while (WiFi.status() != WL_CONNECTED) {
    digitalWrite(LED_PIN, ledStatus);
    ledStatus = (ledStatus == HIGH) ? LOW : HIGH;
    delay(100);
  }
}

Erläuterungsbedarf gibt es bei den Feinheiten der WLAN-Verbindung: Der ESP8266 verbindet sich bei jedem Neustart neu mit dem WLAN. Einige Accesspoints führen auch inaktive Hosts mehrere Minuten noch als aktiv. Meine Fritzbox 7490 lehnte minütliche Neuverbindungen ab. Mit Intervallen ab zwei Minuten ging es dann – für den angedachten Praxiseinsatz mit fünf Minuten Abstand kommt stabil eine neue Verbindung zustande.

Optimierungspotential

Im Sketch sehen Sie auskommentiert Einstellungen für eine fixe IP-Adresse. Das hat mit der Fritzbox nicht zuverlässig funktioniert. Mir scheint, dass andere Hosts im Netz ihre ARP-Tabellen noch nicht aktualisiert hatten als das UDP-Paket längst versandt war. Etwas herumspielen mit delay() oder anpingen könnte hier helfen. Immerhin: An dieser Stelle ist noch viel Einsparpotential, benötigt doch Anmelden im Netzwerk mit DHCP-Request rund drei Sekunden – mit statischer IP-Adresse sollte sich dies auf unter eine Sekunde senken lassen.

Was ist gewonnen? Setzen wir für den Boot des ESP8266 mit WLAN-Login rund 100mA an und gehen von einem Duty Cycle von 1% aus (300 Sekunden Tiefschlaf im Wechsel mit 3 Sekunden Arbeit), ergibt sich noch ein Durchschnittlicher Strombedarf von 1mA oder eine Laufzeit von etwa drei Monaten auf AA-Zellen.

Wie geht es weiter?

Wegen der recht spartanischen Ausstattung mit analogen Inputs möchte ich einen AVR (Atmega oder Attiny) mit dem ESP8266 verbinden und die beiden miteinander kommunizieren lassen. Dabei soll der Watchdog Timer des AVR benutzt werden, beide aufzuwecken.

Diskussionsbedarf? Bitte auf Google+ diskutieren.