Archiv der Kategorie: ESP8266

OTA, ESP8266 und Arduino IDE – ein starkes Team

gks_ota0

Einleitung

In letzter Zeit habe ich mich viel mit dem ESP8266 beschäftigt und in einigen Blogbeiträgen bereits darüber berichtet. Jetzt bin ich auf ein neues Thema gestoßen, dass ich sofort weiter teilen muss. OTA over-the-air, also Upload eines Programmes über die WiFi Schnittstelle ist vor allem da hilfreich und sinnvoll, wo ich die Schaltung nicht mehr im Zugriff habe oder wo ich keinen USB-Seriell-Wandler anschließen möchte, weil die Schaltung mit Netzspannung betrieben wird (z.B. SONOFF). Meine Begeisterung für OTA möchte ich in diesem BlogPost ausdrücken.

Obwohl ich heute meine Programme hauptsächlich auf dem WeMos D1 Mini entwickel, weil er mit seinem USB-Seriell Wandler einfach zu handhaben ist und sehr kompakt baut (Bezug z.B. über: http://stores.ebay.de/modulink) werde ich in diesem Post beschreiben, wie OTA auf einem ESP-12E funktioniert, der ja von Hause aus keinen USB-Seriell-Wandler mit sich bringt und deshalb ist OTA hier besonders interessant. Leider funktioniert OTA nicht mit dem ESP-01. 512k Speicher sind für OTA einfach zu wenig. Für OTA muss der Chip mindestens doppelt so viel Flash bereits stellen, wie das kompilierte Programm groß ist. Der OTA Loader für sich braucht schon ca. 240k.

Der OTA Loader

Als ersten Schritt muss man auf den ESP ein Programm laden, dass die Schnittstelle für OTA bereit stellt. Für diesem Schritt benötigt man natürlich einen USB-Seriell-Wandler, der wie hier beschrieben angeschlossen wird. Das von mir präferierte OTA-Basis Programm ist in einer Library enthalten, die über den Library Manager geladen wird. Dazu gehen wir in der Arduino IDE auf Sketch->Bibliothek einbinden->Bibliotheken verwalten und geben als Suchbegriff OTA ein. Die ArduinoOTA Library sollte dann als oberstes Suchergebnis erscheinen.

gks_ota1

Diese Library installieren. Unter Datei->Beispiele->ArduinoOTA->BasicOTA laden wir jetzt das Programm. Dieses Programm muss nur an zwei Stellen angepasst werden. Der ESP verbindet sich mit einem WLAN. Dafür muss im Programm die SSID und das WLAN Passwort eingetragen werden.

gks_ota2

Das Programm unter neuem Namen abspeichern. Es wird später dann als Basic Programm immer wieder als Grundgerüst verwendet.Vor dem Upload des Programms wird noch unter Werkzeuge->Board das ESP Board auswählen – in meinem Fall für den ESP-12E das NodeMCU-1.0(ESP-12E Module).

gks_ota6

Unter Werkzeuge->Port muss noch der richtige Port eingestellt werden unter dem der USB-Seriell-Wandler sichtbar ist. Im nächsten Schritt erfolgt der Upload des Programms dann wie immer mit der Abfolge
– ESP in den Programmiermodus versetzen, dazu GPIO0 auf LOW ziehen und den ESP aus- und wieder einschalten oder resetten. (Bei anderen ESP Varianten entfällt dieser Schritt ggf)
– Upload des Programms

Nach dem Upload des Programms zeigt der serielle Monitor das Einbuchen des ESP ins WLAN. Der ESP macht jetzt nichts weiter als auf ein neues Programm zu warten.

gks_ota4Nach einem Neustart der Arduino IDE sieht man jetzt einen neuen Port, der jetzt für den OTA Upload ausgewählt wird.

gks_ota5 Der Rechner, von dem die Programmierung und der Upload erfolgt, muss sich natürlich im gleichen Netz befinden.

zusätzlich benötigt – Python 2.7

Bevor es mit dem OTA Upload losgehen kann, muss noch folgendes geprüft werden. Für den Upload over-the-air nutzt die IDE ein Python Script. Dafür muss auf dem Rechner Python in der Version 2.7 installiert sein (die Version 3.x funktioniert hier nicht). Windows User können die Version 2.7 hier downloaden. Auf Linux und OSx Systemen sollte Python bereits vorinstalliert sein. Auch hier auf die Version achten. Anleitungen zur Installation von Python gibt es zahlreiche im Netz, deshalb gehe ich hier nicht ins Detail. Für Windows Nutzer ist noch wichtig, dass der Installationspfad von Python der Path Variablen hinzugefügt wird. Das kann bei der Installation als zweiter Schritt beim Setup ausgeführt werden. Unter Customize Python Add Python to path auswählen.

Erster Upload

Bis zu diesem Punkt ist der ESP mit dem BasicOTA Programm geflasht, dass momentan nichts anderes macht, als auf ein neues Programm zu warten. Das eigene Programm wird dem BasicOTA Programm hinzugefügt. Wichtig ist jetzt der Befehl  ArduinoOTA.handle();, der ganz oben in der loop() steht, dieser Befehl muss zyklisch immer wieder aufgerufen werden, weil hier geprüft wird, ob ein OTA Update ansteht. Das USB Kabel könnte jetzt prinzipiell entfernt werden, es ist aber ganz interessant beim ersten OTA Upload die Ausgabe mal anzusehen. Allerdings muss dafür eine zweite Instanz von  der Arduino IDE oder ein anderes Terminalprogramm benutzt werde. Ich nutze dafür unter Linux CuteCom, unter Windows könnte man Putty nutzen.

Als Beispiel habe ich das BasicOTA Programm um eine Ausgabe in der loop() erweitert, alle 5s wird über die serielle Schnittstelle ein Status ausgegeben. In der loop() sollte kein delay() benutzt werden, weil damit der Test, ob ein OTA Update ansteht (s.o.) für diese Zeit nicht ausgeführt würde und die IDE einen Fehler melden würde, da sie keine Antwort vom ESP bekommt.

 void loop() {
ArduinoOTA.handle();
if (millis() - lastmillis >  5000) {
Serial.print("still running since ");
Serial.print(millis()/1000);
Serial.println(" seconds");
lastmillis = millis();
}
} ... 

(edit nach Hinweis von Wolfgang) Zusätzlich wird die globale Variable

unsigned long lastmillis; 

im oberen Teil des Programms definiert.
Das Programm wird jetzt wie gewohnt über den Upload Button auf den ESP geladen. Es ist nicht notwendig, den ESP in den Programmiermodus zu versetzen oder zu resetten. Ein sehr erfreulicher Nebeneffekt ist beim Upload zu sehen, der Upload geht sehr viel schneller als über die serielle Schnittstelle. Wenn ein Terminal offen ist, wird der Upload Fortschritt auch hier angezeigt. Sobald der Upload abgeschlossen ist, resettet sich der ESP und startet das neue Programm.

gks_ota7

Anmerkungen zur Sicherheit

Solange man sich im eigenen Netz befindet und man sich sicher fühlt, kann man das BasicOTA so nutzen, wie es bereitgestellt wird. Es wäre aber prinzipiell möglich für jemanden, der sich im selben Netz befindet, einen SW Upload zu starten. Es gibt 2 Möglichkeiten, das zu erschweren. Im BasicOTA Code kann der IP-Port, unter dem der ESP gefunden wird, verändert werden, standardmäßig ist hier Port 8266 gesetzt. Zusätzlich kann man ein Passwort setzen, dass dann in der IDE vor dem Upload abgefragt wird.
Darüber hinaus muss man sich genau überlegen, was das laufende Programm möglicherweise gerade macht, wenn der Upload beginnt. Hat der ESP gerade das Wasserventil geöffnet, um den Garten zu bewässern, muss man sichergehen, dass das Ventil geschlossen wird, bevor der Upload beginnt.
Im BasicOTA Code sind deshalb Programm-Module vorgesehen, die beim Upload zu einem bestimmten Status aufgerufen werden.

   ArduinoOTA.onStart([]() {
Serial.println("Start");
});
ArduinoOTA.onEnd([]() {
Serial.println("\nEnd");
});

Hier kann man eigenen Code einbauen, der einen definierten Zustand herstellt, also die o.g. Ventile schließt oder einen Status ins EEPROM schreibt. Da nach dem Upload der ESP resettet wird, wird eh das gesamte setup() durchlaufen.

Ausblick

Das Verfahren des Programm Uploads over-the-air in der beschriebenen Form ist für den Entwicklungsprozess aus der IDE vorgesehen. Für Schaltungen, die sich in anderen Netzen befinden gibt es die Möglichkeit des Uploads über Webserver (von einem Rechner aus dem gleichen Netz) z.B. von jemandem, der mit der Arduino IDE nicht vertraut ist oder über einen Server in einem anderen Netz, wo sich der ESP selbstständig eine neue Software abholt. Diese Varianten habe ich aber noch nicht erprobt, deshalb kann ich nicht näher darauf eingehen.

 

 

Werbung

Tutorial: Website parsen mit dem ESP8266 – Teil 3 – Projekt Hühnerstall – Step-by-step

WordPress1Einleitung

Wie bereits im letzten Teil dieses Tutorials angekündigt, möchte ich heute ein konkretes Projekt vorstellen, dass – zugegebenermaßen – meiner Fantasie entspringt, sich aber auch mit wenigen Änderungen auf andere Anwendungen anpassen lässt.

Dieser Teil ist deutlich größer geworden, als ursprünglich geplant, ein Lob und Dank also jetzt schon an alle, die sich jetzt hier durcharbeiten werden. Aber es wird sich lohnen. Ziel ist es, eine Steuerung der Ein- und Ausgangsklappe eines Hühnerstalls in Abhängigkeit von Sonnenauf- und -untergang zu bauen oder mit anderen Worten: wenn die Sonne weg ist soll die Klappe zu sein. Die Information für die Zeiten des Sonnauf- und -untergangs hole ich mir – ihr könnt es euch denken – von einer Webseite. Einwände, dass man die Zeiten auch berechnen kann, möchte ich mir an dieser Stelle gar nicht anhören, vielleicht soll die Zahl der Hühner ja im nächsten Schritt getwittert werden, dann braucht man eh den Internetzugang.

In diesem Tutorial wird nicht nur das Titelthema „parsen“ behandelt, sondern auch der Umgang mit der Time Library und der Sleep Mode für den ESP. Bis zum endgültigen Projekt Code gibt es viele Beispiele, die man zum besseren Verständnis einzel durchspielen kann.

Auf geht’s

Das Vorgehen ist wie im letzten Beitrag bereits beschrieben

  1. Webseite aussuchen: Ich habe mich für die Seite http://www.netzwolf.info/astronomie/ephemeriden/sonne entschieden, da sie die gewünschte Information ohne viel unnötige zusätzliche Daten bereitstellt.
  2. Quelltext der Seite untersuchen und die Suchbegriffe finden, glücklicherweise stellt die Webseite uns auch die aktuelle Zeit als UTC zur Verfügung und die Information, ob es Winterzeit (W) oder Sommerzeit (S) ist.
 
.... 
Weltzeit (UTC) : <b>14:57</b> 13.02.2016
....
Sonnenaufgang : <b>07:51</b> 13.02.2016 (W)
Kulmination 26.0°: <b>12:48</b> 13.02.2016
Sonnenuntergang : <b>17:45</b> 13.02.2016 (W)
....

Wie man sieht, ist die Information sehr leicht zu parsen. Suche nach „Sonnenaufgang : <b>“, nachfolgende 5 Zeichen sind die gesuchte Zeit, genauso für den Sonnenuntergang. Wir benötigen aber auch das Datum, da immer der nächste Sonnenauf- oder -untergang angegeben wird. Zusätzlich sollte man immer den Quelltext daraufhin untersuchen, welche Zeilenumbrüche genutzt werden, damit der korrekte bzw. ein sinnvoller Delimiter für den einzulesenden String bestimmt wird, in diesem Fall ist das 0x0A, also das ASCII Zeichen für LF oder „\n“.

3. in den Code (aus dem Teil 1) einbauen

Im Header Bereich des Codes zusätzliche Variablen Definitionen einbauen und Server und die URL eintragen

 
...
String SonnenA, SonnenU, WeltZ;
boolean sommerzeit;
... 
const char* host = "www.netzwolf.info";
... 
  String url = "/astronomie/ephemeriden/sonne";
... 

Für einen ersten Test den Suchstring einsetzen (Achtung: die Zahl der Leerzeichen im Suchstring)

  while (client.available()) {
    String line = client.readStringUntil('\n');
    if (line.indexOf("Sonnenaufgang    : <b>") >= 0) { // Searchstring exists?
      Serial.println();
      Serial.println(line);
      SonnenA=line.substring(22, 27) + line.substring(32, 43);
      Serial.println(SonnenA);
      if (line.indexOf("(S)")>=0) {
        sommerzeit=true;
      }
      else if (line.indexOf("(W)")>=0) {
        sommerzeit=false;
      }
      if (sommerzeit) Serial.println("Es ist Sommerzeit"); else Serial.println("Es ist Winterzeit");
    }
    if (line.indexOf("Sonnenuntergang  : <b>") >= 0) { // Searchstring exists?
      Serial.println();
      Serial.println(line);
      SonnenU=line.substring(22, 27) + line.substring(32, 43);
      Serial.println(SonnenU);
    }
    if (line.indexOf("Weltzeit (UTC)   : <b>") >= 0) { // Searchstring exists?
      Serial.println();
      Serial.println(line);
      WeltZ=line.substring(22, 27) + line.substring(32, 43);
      Serial.println(WeltZ);
    }
  }

und die Ausgabe prüfen.

Connecting to TP-LINK_360E60 
........... 
WiFi connected 
IP address:  
192.168.0.100 
 
connecting to www.netzwolf.info 
Requesting URL: /astronomie/ephemeriden/sonne 
 
Weltzeit (UTC)   : <b>20:31</b>  13.02.2016 
20:31 13.02.2016 
 
Sonnenaufgang    : <b>07:49</b>  14.02.2016 (W) 
07:49 14.02.2016 
Es ist Winterzeit 
 
Sonnenuntergang  : <b>17:47</b>  14.02.2016 (W) 
17:47 14.02.2016 
 
closing connection 

Soweit so gut, die Suche funktioniert, jetzt geht es daran, die Zeiten verarbeitbar zu machen.

Um mit den mit den Uhrzeiten bequemer umgehen und rechnen zu können, setze ich die neueste Time Library ein, die ich mit dem Library Manager aktualisiere.

OpenWeather2

Ein Ausflug in die Grundlagen von time.h

Die Funktionen der Time Library sind nicht ganz einfach zu verstehen, deshalb möchte ich an dieser Stelle einen kleinen Ausflug in die Grundlagen machen und zeigen wie die Berechnung funktioniert. Ich habe mal ein Programm erstellt, dass ich zum besseren Verständnis schrittweise durchgehen werde. Das Programm berechnet die Sekunden bis zum Sonnauf- oder -untergang. Die Zeiten sind dabei als Stringkonstanten genauso im Programm hinterlegt, wie wir sie gerade aus der Webseite erzeugt haben. Das Programm kann auch auf einem UNO ausprobiert werden.

Das Programm macht aus den Strings jeweils für die Time Library verarbeitbare Zeitwerte. Als erstes wird die „Uhr“ der Time Library gesetzt, dann die Zeitwerte für Sonnenauf- und -untergang in die korrekte Form (struct) der Lib gebracht. Ausgegeben wird schlussendlich die Zeit bis zum nächsten Sonnenauf- bzw -untergang.

#include <TimeLib.h> 
tmElements_t SunA; 
tmElements_t SunU; 
//                0123456789012345 
String WeltZ =   "16:24 13.02.2016";
String SonnenA = "07:49 14.02.2016";    
String SonnenU = "17:47 14.02.2016";
boolean sommerzeit=false;
int timezone=1;
unsigned int Hour, Minute, Second, Day, Month, Year;

In diesem Code Block wird die Time Library included und die Variablen definiert. Eine besondere Bedeutung habe dabei die struct  Definitionen (hier in der Theorie nachzulesen). Die struct Definition selbst ist in der Time Library enthalten, hier wird diese struct unseren Zeitvariablen zugewiesen. Zum besseren Verständnis hier ein Auszug aus der Library selbst mit der Definition der struct.

typedef struct { 
 uint8_t Second; 
 uint8_t Minute; 
 uint8_t Hour; 
 uint8_t Wday; // day of week, sunday is day 1
 uint8_t Day;
 uint8_t Month; 
 uint8_t Year; // offset from 1970; 
} tmElements_t, TimeElements, *tmElementsPtr_t;

Weiter gehts im Code

void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);
  Serial.print("WeltZ (UTC) = ");
  Serial.println(WeltZ);
  Serial.print("SonnenA = ");
  Serial.println(SonnenA);
  Serial.print("SonnenU = ");
  Serial.println(SonnenU);
  
  // set time to actual time 
  Hour=atoi(WeltZ.substring(0,2).c_str());
  Minute=atoi(WeltZ.substring(3,5).c_str());
  Day=atoi(WeltZ.substring(6,8).c_str());
  Month=atoi(WeltZ.substring(9,11).c_str());
  Year=atoi(WeltZ.substring(13,16).c_str());
  setTime(Hour, Minute, 0, Day, Month, Year);    // time is set to UTC
  Serial.print("Arduino is set to UTC ");
  digitalClockDisplay();
  Serial.print("eqivalent UNIX time is ");
  Serial.println(now());

Im setup() werden die Strings zunächst noch einmal seriell ausgegeben. Im zweiten Teil wird der String für die Weltzeit (die aktuelle Zeit) auseinandergenommen und zunächst den Integern Hour, Minute, Second, usw zugewiesen. Dabei benutze ich neben dem Befehl String.substring() den wir bereits in den ersten Teilen des Tutorials kennengelernt haben, den Befehl atoi(). Da atoi() einen char typ erwartet muss mit .c_str() der Pointer auf den String übergeben werden. Mit setTime() wird die Uhrzeit dann „gesetzt“, d.h. ab jetzt tickt die interne Uhr. Mit der helper Funktion  digitalClockDisplay() wird die Uhrzeit in lesbarer Form ausgegeben. Da alle weitren Berechnungen in Sekunden laufen werden alle weiteren Berechnungen mit der Unixtime durchgeführt.

  // make SonnenA struct
  // make SonnenA struct
  SunA.Hour = atoi(SonnenA.substring(0, 2).c_str());
  SunA.Minute = atoi(SonnenA.substring(3, 5).c_str());
  SunA.Second = 0;
  SunA.Day = atoi(SonnenA.substring(6, 8).c_str());
  SunA.Month = atoi(SonnenA.substring(9, 11).c_str());
  SunA.Year = y2kYearToTm(atoi(SonnenA.substring(13, 16).c_str()));  // offset from 1970
  unsigned long SunA_ux = makeTime(SunA);     // unixtime
  SunA_ux = sommerzeit ? SunA_ux - 2 * timezone * 3600 : SunA_ux - timezone * 3600; // Timezone und summer/wintertime
  if (now() >= SunA_ux) {
    Serial.print("letzter Sonnenaufgang war ");
    Serial.println(SunA_ux);
    Serial.print("vor ");
    Serial.print(now() - SunA_ux);
    Serial.println(" Sekunden");
  }
  else {
    Serial.print("nächster Sonnenaufgang ");
    Serial.println(SunA_ux);
    Serial.print("in ");
    Serial.print(SunA_ux - now());
    Serial.println(" Sekunden");
  }

In diesem Codeblock wird die struct mit der Zeit für den Sonnenaufgang belegt, die Umwandlung des Strings erfolgt analog der Weltzeit mit dem kleinen Unetrschied, dass ein Offset für das Jahr eingebaut werden muss. Dafür benutze ich ein Makro,y2kYearToTm() ,dass in der Time Library enthalten ist. In den nachfolgenden Zeilen wird die Unixtime für den Sonnenaufgang berechnet, der Befehl makeTime() berechnet diese aus der struct. In der nächsten Zeile wird dann die Umberechnung auf UTC gemäß Zeitzone und Sommer-/Winterzeit durchgeführt. Dieser Block wird analog für den Sonnenuntergang wiederholt.

Die loop() bleibt leer, die Berechnungen brauchen wir hier als Lehrbeispiel nur ein einziges Mal. Die beiden Funktionen am Schluß des Codes werden auch in den Beispielen der Time Lib zur fomatierten Ausgabe von Zeit und Datum benutzt.

Der Code kann hier komplett kopiert werden, jedoch muss in der letzte Funktion ein „<“ korrigiert werden, Damit kommt WordPress im Code-Block nicht klar :-(
Den File gibt es auch auf meinem git als Download.

#include <code><TimeLib.h></code>              <-code tags ------- / entfernen
tmElements_t SunA;
tmElements_t SunU;
//                0123456789012345
String WeltZ =   "01:38 14.02.2016";
String SonnenA = "07:49 14.02.2016";
String SonnenU = "17:47 14.02.2016";
boolean sommerzeit = false;
int timezone = 1;
unsigned int Hour, Minute, Second, Day, Month, Year;

void setup() {
// put your setup code here, to run once:
Serial.begin(115200);
Serial.print("*******************************\nWeltZ (UTC) = ");
Serial.println(WeltZ);
Serial.print("SonnenA = ");
Serial.println(SonnenA);
Serial.print("SonnenU = ");
Serial.println(SonnenU);

// set time to actual time
Hour = atoi(WeltZ.substring(0, 2).c_str());
Minute = atoi(WeltZ.substring(3, 5).c_str());
Day = atoi(WeltZ.substring(6, 8).c_str());
Month = atoi(WeltZ.substring(9, 11).c_str());
Year = atoi(WeltZ.substring(13, 16).c_str());
setTime(Hour, Minute, 0, Day, Month, Year);    // time is set to UTC
Serial.print("Arduino is set to UTC ");
digitalClockDisplay();
Serial.print("eqivalent UNIX time is ");
Serial.println(now());

// make SonnenA struct
SunA.Hour = atoi(SonnenA.substring(0, 2).c_str());
SunA.Minute = atoi(SonnenA.substring(3, 5).c_str());
SunA.Second = 0;
SunA.Day = atoi(SonnenA.substring(6, 8).c_str());
SunA.Month = atoi(SonnenA.substring(9, 11).c_str());
SunA.Year = y2kYearToTm(atoi(SonnenA.substring(13, 16).c_str()));  // offset from 1970
unsigned long SunA_ux = makeTime(SunA);     // unixtime
SunA_ux = sommerzeit ? SunA_ux - 2 * timezone * 3600 : SunA_ux - timezone * 3600; // Timezone und summer/wintertime
if (now() >= SunA_ux) {
Serial.print("letzter Sonnenaufgang war ");
Serial.println(SunA_ux);
Serial.print("vor ");
Serial.print(now() - SunA_ux);
Serial.println(" Sekunden");
}
else {
Serial.print("nächster Sonnenaufgang ");
Serial.println(SunA_ux);
Serial.print("in ");
Serial.print(SunA_ux - now());
Serial.println(" Sekunden");
}

// make SonnenU struct
SunU.Hour = atoi(SonnenU.substring(0, 2).c_str());
SunU.Minute = atoi(SonnenU.substring(3, 5).c_str());
SunU.Second = 0;
SunU.Day = atoi(SonnenU.substring(6, 8).c_str());
SunU.Month = atoi(SonnenU.substring(9, 11).c_str());
SunU.Year = y2kYearToTm(atoi(SonnenU.substring(13, 16).c_str()));  // offset from 1970
unsigned long SunU_ux = makeTime(SunU);
SunU_ux = sommerzeit ? SunU_ux - 2 * timezone * 3600 : SunU_ux - timezone * 3600; // Timezone und summer/wintertime
if (now() >= SunU_ux) {
Serial.print("letzter Sonnenuntergang war ");
Serial.println(SunU_ux);
Serial.print("vor ");
Serial.print(now() - SunU_ux);
Serial.println(" Sekunden");
}
else {
Serial.print("nächster Sonnenuntergang ");
Serial.println(SunU_ux);
Serial.print("in ");
Serial.print(SunU_ux - now());
Serial.println(" Sekunden");
}
}

void loop() {
// put your main code here, to run repeatedly:
}

void digitalClockDisplay() {      // helper function to print time/date readable from structure
Serial.print(hour());
printDigits(minute());
printDigits(second());
Serial.print(" ");
Serial.print(day());
Serial.print(".");
Serial.print(month());
Serial.print(".");
Serial.print(year());
Serial.println();
}

void printDigits(int digits) {
// utility function for digital clock display: prints preceding colon and leading 0
Serial.print(":");
<code>if (digits < 10) </code>                 x------ remove <code></code>
Serial.print('0');
Serial.print(digits);
}

Zurück zum Projekt

Die Stringverarbeitung aus dem letzten Programm werden jetzt in den ursprünglichen Code integriert. Der geplante Programmablauf ist folgender:

Bei der ersten Inbetriebnahme weiß der Controller zunächst nicht, ob die Hühnstall Türe offen oder geschlosssen sein soll und wann die nächste Aktion ansteht. Die aktuelle Zeit wird also bestimmt und das nächste Schaltevent bestimmt. Wichtig ist hier, dass der erste Zustand der Türe zunächst undefiniert ist und erst mit dem ersten Event korrekt gesetzt wird. Der Controller weiß jetzt auch, wie lange es bis zur nächsten Aktion noch dauert und legt sich schlafen. Da die Funktion sleep als Input den maximalem Wert einer unsigned long Variable annimmt, kann das modul maximal hexFFFF FFFF = 4.294.967.295 μs schlafen, das sind ca. 1h 12min. Um einfacher zu debuggen, habe ich mich entschieden, den ESP maximal 1h schlafen zu legen. Da ich festgestellt habe, dass eine Stunde schlafen nicht exakt eien Stunde ist, muss man sich an das Event „heranarbeiten“. Auch wenn es nicht so entscheidend ist, will ich den Zeitpunkt des Sonnenauf- bzw -untergangs doch ziemlich genau treffen.

Folgende Logik ist im Programm abgebildet, nachdem zunächst die Zeit bis zum nächsten Schaltevent bestimmt wurde:
Ist die Zeit bis zum nächsten Event < 1 min wird die Zeit mit delay() überbrückt
Ist die Zeit bis zum nächsten Event < 65 min geht der ESP bis 1 min vor dem nächsten Event schlafen, beim nächsten Aufwachen triggert dann die letzte Bedingung
Treffen beide Bedingungen nicht zu, legt der ESP sich für eine Stunde schlafen.

Den ESP schlafen zu legen ist relativ einfach. Außer einem include benötigt man nur 2 Befehle. #include verweist direkt auf die Espressif SDK. Der Zeit Parameter der übergeben wird ist vom Typ unsigned long. Wird „0“ als Wert übergeben, schläft der ESP für immer. Das ist wichtig zu beachten, wenn man mit berechneten Sleepzeiten arbeitet, hier muss man Vorkehrungen treffen, dass der Wert nicht „0“ wird. Mit ist das einmal in der Testphase passiert und ich habe lange nach dem Fehler gesucht.


extern "C" {
#include "user_interface.h"
}

void setup() {
Serial.begin(115200);
Serial.println("\r\nStart...");
}
void loop() {
Serial.println("going to sleep now...");
system_deep_sleep_set_option(0);
system_deep_sleep(10000000);            // deep sleep for 10 seconds
delay(1000);
}

Der endgültige Code ist schon recht umfangreich geworden. Es sind viele Debug Informationen Prints im Code , um diesen besser zu verstehen. Das Ansteuern der Hühnerklappe habe ich mir noch geschenkt, weil ich den Beitrag endlich fertig stellen wollte. Danke allen, die sich bis hier durchgearbeitet haben. Den aktuellen Code könnt ihr von meinem GIT laden. Feedback ist wie immer sehr willkommmen.

 

 

 

Tutorial: Website parsen mit dem ESP8266 – Teil 1

Einleitung

In den letzten Tagen habe ich mich verstärkt mit der Abfrage verschiedener Websites beschäftigt und der Nutzung der Informationen im eigenen Programm. Das Finden und Extrahieren von Informationen aus einem Text, einem String oder einer Seite nennt man parsen. Da ich dabei viel gelernt habe, möchte ich dieses Wissen gerne weitergeben. Wichtig ist mir dabei wie immer, dass ihr das versteht und selbstständig nachvollziehen könnt. Deshalb werden meine Erklärungen vielleicht etwas ausführlicher und richten sich an die Programmieranfänger unter euch. Da ich das Tutorial für den ESP schreibe, werde ich auch auch auf die grundsätzliche Abfrage von Websites mit dem ESP eingehen.

Der ESP als Client

In den Libraries des ESP ist ein Beispiel enthalten, wie mit den ESP als Client Informationen von einem Webserver abgerufen werden können. In meinem Tutorial über die Speicherung von MySQL Daten habe ich ja bereits ein Beispiel zur Abfrage eines Webservers gezeigt. Das folgende Beispiel zeigt die Anwendung des ESP als Client mit Nutzung der neuesten Libraries. Das Beispiel ruft google.de auf und schreibt die Ausgabe auf die serielle Schnittstelle.

/*
 * Basic ESP Client example, based on the ESP libraries examples
 */

#include <ESP8266WiFi.h>

const char* ssid     = "yourSSID";
const char* password = "yourPASSPHRASE";
const char* host = "google.de";

void setup() {
  Serial.begin(115200);
  delay(10);

  // We start by connecting to a WiFi network

  Serial.println();
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(ssid);

  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  Serial.println("");
  Serial.println("WiFi connected");  
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
  Serial.println();
}

void loop() {
  Serial.print("connecting to ");
  Serial.println(host);
  // Use WiFiClient class to create TCP connections
  WiFiClient client;
  const int httpPort = 80;
  if (!client.connect(host, httpPort)) {
    Serial.println("connection failed");
    return;
  }
  
  // We now create a URI for the request
  String url = "/";
  
  Serial.print("Requesting URL: ");
  Serial.println(url);
  
  // This will send the request to the server
  client.print(String("GET ") + url + " HTTP/1.1\r\n" +
               "Host: " + host + "\r\n" + 
               "Connection: close\r\n\r\n");
  // start waiting for the response             
  unsigned long lasttime = millis();
  while (!client.available() && millis() - lasttime < 1000) {delay(1);}   // wait max 1s for data
  // Read all the lines of the reply from server and print them to Serial
  while(client.available()){
    char readchar = client.read();
    Serial.print(readchar);
  }
  Serial.println();
  Serial.println("closing connection");
  delay(5000);
}

Auf einige Teile dieses Codes möchte ich nochmal tiefer eingehen.

// We start by connecting to a WiFi network

  Serial.println();
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(ssid);

  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

Im setup wird die Verbindung zum lokalen WLAN aufgebaut, dazu müssen im Parameterblock oben im Programm SSID und Passwort eingetragen werden.
Das Programm versucht nun (hier endlos) eine Verbindung zum WLAN aufzubauen. In meinem Fall wird die Verbindung nach ca. 5-8 Sekunden aufgebaut, wenn es bis dahin keine Verbindung gibt, muss man ein Reset machen und manchmal auch den Code neu flashen.

  WiFiClient client;
  const int httpPort = 80;
  if (!client.connect(host, httpPort)) {
    Serial.println("connection failed");
    return;
  }

Im loop wird dann die Verbindung zum Host, hier google.de aufgebaut

// We now create a URI for the request
  String url = "/";
  
  Serial.print("Requesting URL: ");
  Serial.println(url);
  
  // This will send the request to the server
  client.print(String("GET ") + url + " HTTP/1.1\r\n" +
               "Host: " + host + "\r\n" + 
               "Connection: close\r\n\r\n");

Zunächst wird die URL für die Abfrage zusammengesetzt. Als erstes wird der Teil der nach dem Host in der URL kommt in einen String geschrieben. In diesem Fall kommt nach google.de nichts mehr, deshalb steht hier nur ein „/“. Im nchsten Schritt werden die für die Abfrage notwendigen Parameter zum String hinzugefügt. ( die \r\n stehen für CR und LF und müssen an diesen Stellen stehen, am Ende der Abfrage sogar zweimal). Die client.print() Anweisung schickt die Abfrage zum Server.

// start waiting for the response             
  unsigned long lasttime = millis();
  while (!client.available() && millis() - lasttime < 1000) {delay(1);}   // wait max 1s for data

Jetzt wartet der Client auf eine Antwort des Servers. Manche Server lassen sich damit Zeit, deshalb wartet der Client entweder bis er Daten bekommt (client-available()=true) oder die Zeit abgelaufen ist (hier 1000ms).

  while(client.available()){
    char readchar = client.read();
    Serial.print(readchar);
  }

Solange Daten empfangen werden, gibt der ESP diese Buchstabe für Buchstabe auf der seriellen Schnittstelle aus. Im Client der Library ist hier alternativ eine Methode vorgeschlagen, die Daten zeilenweise (bis zum CR) zu lesen und auszugeben. Diese Methode werden wir für die nächsten Beispiele verwenden, also jetzt schon mal in das Beispiel einbauen. Manchmal ist es sinnvoller, statt nach einem CR ein LF zu suchen, dann sind die Strings möglicherweise kürzer, doch dazu später mehr.

  // Read all the lines of the reply from server and print them to Serial
  while(client.available()){
    String line = client.readStringUntil('\r');
    Serial.print(line);
  }

Die Aussgabe auf der seriellen Schnittstelle sieht dann so aus

Connecting to DD-WRT 
............. 
WiFi connected 
IP address:  
192.168.2.109 
 
connecting to google.de 
Requesting URL: / 
HTTP/1.1 301 Moved Permanently 
Location: http://www.google.de/ 
Content-Type: text/html; charset=UTF-8 
Date: Sat, 30 Jan 2016 22:25:54 GMT 
Expires: Mon, 29 Feb 2016 22:25:54 GMT 
Cache-Control: public, max-age=2592000 
Server: gws 
Content-Length: 218 
X-XSS-Protection: 1; mode=block 
X-Frame-Options: SAMEORIGIN 
Connection: close 
 
<HTML><HEAD><meta http-equiv="content-type" content="text/html;charset=utf-8">
<TITLE>301 Moved</TITLE></HEAD><BODY>
<H1>301 Moved</H1>
The document has moved
<A HREF="http://www.google.de/">here</A>. 
</BODY></HTML> 
 
closing connection 

Am Anfang der Ausgabe kommen Informationen über den Server, die eigentliche Ausgabe der Webseite beginnt ab <HTML>. Die Webseite bietet nicht viel an nutzbarer Information, allerdings könnte man aus den Header Daten die Uhrzeit parsen, anstatt den NTP Client einzubinden. Das möchte ich als abschließendes erstes Beispiel für das parsen zeigen. Wichtig beim parsen ist, dass man ein sich eindeutige Suchbegriffe sucht, über die man die Zeile in der die Information steht und als zweites der Platz in der Zeile eindeutig bestimmen kann. Für die Zeitabfrage ist das die Zeile

Date: Sat, 30 Jan 2016 22:25:54 GMT

Als erstes suchen wir also im eingelesenen String nach dem Suchwort „Date“ und wissen dann, dass wir in der richtigen Zeile sind. Das Schöne an diesem Beispiel ist, dass die Zeit immer genau an der selben Stelle im String steht.

Die beiden wichtigsten Befehle beim parsen sind:

String.indexOf(„Suchstring“)  ->  gibt als int die Position im String aus, wo der Suchstring gefunden wird

String.substring(von, bis)  -> liefert String, der bei „von“ beginnt und vor „bis“ endet.

Da wir wissen, dass das Suchwort „Date“ immer an Position 1 im String zu finden ist (auf Position 0 steht ein LF), können wir einfach die Position im String abzählen, wo der gesuchte Teil steht. Doch zuvor testen wir, ob der Suchstring im gelesenen String vorkommt.

_________1_________2_________3_________4
1234567890123456789012345678901234567890
Date: Sat, 30 Jan 2016 22:25:54 GMT

Der entsprechende Code-Block sieht dann so aus:

  while (client.available()) {
    String line = client.readStringUntil('\r');
    if (line.indexOf("Date:")>=0) {   // Searchstring exists?
      Serial.println();
      Serial.print("Heute ist der ");
      Serial.print(line.substring(12, 23));
      Serial.print(", es ist ");
      Serial.println(line.substring(24, 32));
    }
    Serial.print(line);
  }

Die Uhrzeit wird in GMT ausgegeben, zur korrekten Darstellung muss (in der Winterzeit) 1 Stunde addiert werden. Dafür müssen Teile des Strings zunächst mit dem Befehl atoi() in einen Integer umgewandelt werden. Dieser Befehl kann nicht direkt auf den String ausgeführt werden. Dafür wird zunächste der Teilstring extrahiert und an die Funktion atoi() der Pointer auf den neuen String übergeben. Dafür setzen wir die Funktion String.c_str() ein.

  while (client.available()) {
    String line = client.readStringUntil('\r');
    if (line.indexOf("Date:")>=1) {   // Searchstring exists?
      Serial.println();
      Serial.print("Heute ist der ");
      Serial.print(line.substring(12, 23));
      String stundeSubString = line.substring(24,26);
      int stunde = atoi(stundeSubString.c_str());
      if (stunde==23) stunde=0; else stunde++;      
      Serial.print(", es ist ");
      if (stunde<10) Serial.print("0");
      Serial.print(stunde);
      Serial.print(line.substring(26, 32));
      if (stunde==0) Serial.print(" + 1 Tag"); else Serial.println();  // Tagsprung ??
    }

Natürlich müsste bei einem Tagsprung, also bei 0 Uhr bis 0:59 auch der Tag geändert werden. Das ist eine nette Übung für euch. Man sieht, mit der Umwandlung der Ausgabe in rechenbares Format, kann der Code schnell umfangreicher werden.
Die Ausgabe sieht jetzt so aus:

Connecting to DD-WRT 
............. 
WiFi connected 
IP address:  
192.168.2.109 
 
connecting to google.de 
Requesting URL: / 
HTTP/1.1 301 Moved Permanently
Location: http://www.google.de/
Content-Type: text/html; charset=UTF-8 
Heute ist der 31 Jan 2016, es ist 08:33:24 

Date: Sun, 31 Jan 2016 07:33:24 GMT
Expires: Tue, 01 Mar 2016 07:33:24 GMT
Cache-Control: public, max-age=2592000
Server: gws
Content-Length: 218
X-XSS-Protection: 1; mode=block
X-Frame-Options: SAMEORIGIN
Connection: close

<HTML><HEAD><meta http-equiv="content-type" content="text/html;charset=utf-8">
<TITLE>301 Moved</TITLE></HEAD><BODY>
<H1>301 Moved</H1>
The document has moved
<A HREF="http://www.google.de/">here</A>.
</BODY></HTML>
 
closing connection 

Die war der erste Teil meines Tutorials zum parsen von Webseiten. Ich hoffe, ihr konntet meinen Erklärungen folgen und die Beispiele funktionierten auch bei euch. Wie immer, freue ich mich auf eure Rückmeldungen. Im zweiten Teil werde ich euch weitere Tips geben, wie eine Webseite „auseinander genommen“ werden kann.

Anmerkung: google.de gibt dem ESP nur eine sehr reduzierte Information zurück. Offensichtlich erkennt google, dass hier kein Browser anfragt.

Sony Camera Remote Control mit ESP8266

Einleitung

Seit kurzem bin ich begeisterter Besitzer einer Sony DSC-HX90V. Für die Fernsteuerung dieser Kamera hat Sony eine WiFi Schnittstelle eingebaut und bietet eine App für Smartphones an, siehe PlayMemories. Leider gibt es keine weitere Schnittstelle (IR, elektrisch, Funk) um die Kamera remote zu bedienen.

Die Idee

Warum also sollte man die Kamera nicht über die WiFi Schnittstelle fernsteuern und damit gleichzeitig eine Schnittstelle per Mikrocontroller schaffen. Eine Realisierung einer WiFi Remote für eine GoPro wurde vor kurzem auf Hackaday vorgestellt. Eine Suche nach entsprechenden Programmen für Sony Kameras war ohne Erfolg. Auch die Sony Dokumentation der API brachte mich nicht weiter, da ich kein Android programmiere.
Über das ESP8266 Forum, fand ich zumindest Interessierte mit der gleichen Idee, aber keine Lösung.

ESP_Sony3

Reverse Engineering zur Lösung

Durch Zufall stieß ich auf die TimeLapse App von Thibaud Michel.
Mit Hilfe eines WiFi PacketAnalysers habe ich mir auf dem Tablett das Protokoll näher angeschaut und dann mit der Sony Doku der APK verglichen.
ESP_Sony1
Nach einiger Recherche fand ich heraus, dass es sich um ein einfaches HTTP POST Verfahren handelt mit der Übergabe der Parameter in einem json Format. Das nachzubilden mit dem ESP8266 war nur noch Fleißarbeit.

Proof of Concept

Die hier vorgestellte Lösung ist ein erster „Proof of Concept“ ohne jegliches Fehlerhandling und soll zunächst nur die Funktion des Schnittstelle darstellen. An einem ESP8266-01 ist lediglich ein Pushbutton zur Auslösung der Kamera angeschlossen der Rest ist Programm. Als Test wurde das Programm auf meinem Prototyp Board entwickelt. Auf diesem Board ist die 3,3V Stromversorgung und der FTDI zur Programmierung und zur Ausgabe von Debug Informationen fest verbunden. Der ESP8266 ist steckbar. Einzige zusätzliche Komponente ist der Pushbutton zur Fernauslösung der Kamera. Eine Beschreibung des Protoboards und die Einbindung der ESP8266 Libraries in die Arduino IDE findet sich hier. Die prinzipielle Verschaltung hier. Der Code ist ausführlich kommentiert und weicht nicht erheblich von den bekannten WiFiClient Codes ab, sollte also leicht verständlich sein. Auf eine Auswertung der json Antworten der Kamera wurde verzichtet.

//----------------------------------------------------------------------------------------------------------------------
// This program is based on: WiFiClient from ESP libraries
//
// Camera handling by Reinhard Nickels https://glaskugelsehen.wordpress.com/
// tested with DSC-HX90V, more about protocol in documentation of CameraRemoteAPI https://developer.sony.com/develop/cameras/
// 
// Licenced under the Creative Commons Attribution-ShareAlike 3.0 Unported (CC BY-SA 3.0) licence:
// http://creativecommons.org/licenses/by-sa/3.0/
//
// Requires Arduino IDE with esp8266 core: https://github.com/esp8266/Arduino install by boardmanager
//----------------------------------------------------------------------------------------------------------------------

#include <ESP8266WiFi.h>
#define DEBUG 1
#define BUTTON 2   // pushbutoon on GPIO2
volatile int counter;

const char* ssid     = "DIRECT-GKC2:DSC-HX90V";
const char* password = "5uv2LSwj";     // your WPA2 password

const char* host = "192.168.122.1";   // fixed IP of camera
const int httpPort = 8080;

char JSON_1[] = "{\"version\":\"1.0\",\"id\":1,\"method\":\"getVersions\",\"params\":[]}";
char JSON_2[] = "{\"version\":\"1.0\",\"id\":1,\"method\":\"startRecMode\",\"params\":[]}";
char JSON_3[] = "{\"version\":\"1.0\",\"id\":1,\"method\":\"startLiveview\",\"params\":[]}";
char JSON_4[] = "{\"version\":\"1.0\",\"id\":1,\"method\":\"stopLiveview\",\"params\":[]}";
char JSON_5[] = "{\"version\":\"1.0\",\"id\":1,\"method\":\"actTakePicture\",\"params\":[]}";
// char JSON_6[]="{\"method\":\"getEvent\",\"params\":[true],\"id\":1,\"version\":\"1.0\"}";


unsigned long lastmillis;

WiFiClient client;

void setup() {
  Serial.begin(115200);
  delay(10);

  pinMode(BUTTON, INPUT_PULLUP);
  attachInterrupt(2, pulleddown, FALLING);  // handled by interrupt to debounce

  // We start by connecting to a WiFi network

  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(ssid);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {   // wait for WiFi connection
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.println("WiFi connected");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
  delay(1000);
  httpPost(JSON_1);  // initial connect to camera
  httpPost(JSON_2); // startRecMode
  httpPost(JSON_3);  //startLiveview  - in this mode change camera settings  (skip to speedup operation)
}

void loop() {
  if (buttonpressed()){
    Serial.println("pressed..");
    httpPost(JSON_4); //stopLiveview    (skip to speedup operation)
    httpPost(JSON_5);  //actTakePicture
    httpPost(JSON_3);  //startLiveview    (skip to speedup operation)
    }
}

void httpPost(char* jString) {
  if (DEBUG) {Serial.print("Msg send: ");Serial.println(jString);}
  Serial.print("connecting to ");
  Serial.println(host);
  if (!client.connect(host, httpPort)) {
    Serial.println("connection failed");
    return;
  }
  else {
    Serial.print("connected to ");
    Serial.print(host);
    Serial.print(":");
    Serial.println(httpPort);
  }

  // We now create a URI for the request
  String url = "/sony/camera/";

  Serial.print("Requesting URL: ");
  Serial.println(url);

  // This will send the request to the server
  client.print(String("POST " + url + " HTTP/1.1\r\n" + "Host: " + host + "\r\n"));
  client.println("Content-Type: application/json");
  client.print("Content-Length: ");
  client.println(strlen(jString));
  // End of headers
  client.println();
  // Request body
  client.println(jString);
  Serial.println("wait for data");
  lastmillis = millis();
  while (!client.available() && millis() - lastmillis < 8000) {} // wait 8s max for answer

  // Read all the lines of the reply from server and print them to Serial
  while (client.available()) {
    String line = client.readStringUntil('\r');
    Serial.print(line);
  }
  Serial.println();
  Serial.println("----closing connection----");
  Serial.println();
  client.stop();
}

void pulleddown()  // Interrupt handler
{
  counter++;
}

boolean buttonpressed() {  // function to check if pressed
  if (counter!=0) {  
    counter=0;
    delay(10);     // je nach Schalter 
    if (counter==0 && !digitalRead(BUTTON)) return true;
  }
  return false;
}
 

ESP_Sony2

Bedienung

Für die Funktion muss auf der Kamera die aktuelle Version der Smart-Fernbedienung (das Gegenstück für die PlayMemories App) installiert sein.

Im Programm muss die SSID und das Passwort der benutzten Kamera eingetragen werden. Die Kamera wird nach dem Einschalten über das Menue in den Fernsteuermodus gebracht. Nach dem Einschalten des ESP8266 wird die Verbindung zur Kamera hergestellt und die Kamera wird in einen Modus versetzt, wo Einstellungen (Zoom, Blende, Zeit, …) möglich sind.

Ein Druck auf den Pushbutton löst die Kamera aus und die Kamera kehrt in den vorhergehenden Modus zurück.

Über die serielle Schnittstelle werden Debug Informationen ausgegeben, hier ein Beispiel nach dem Einschalten des ESP bis nach der ersten Fernauslösung der Kamera.

Connecting to DIRECT-GKC2:DSC-HX90V 
....... 
WiFi connected 
IP address:  
192.168.122.115 
Msg send: {"version":"1.0","id":1,"method":"getVersions","params":[]} 
connecting to 192.168.122.1 
connected to 192.168.122.1:8080 
Requesting URL: /sony/camera/ 
wait for data 
HTTP/1.1 200 OK
Connection: close
Content-Length: 51
Content-Type: application/json

{"result":[["1.0","1.1","1.2","1.3","1.4"]],"id":1} 
----closing connection---- 
 
Msg send: {"version":"1.0","id":1,"method":"startRecMode","params":[]} 
connecting to 192.168.122.1 
connected to 192.168.122.1:8080 
Requesting URL: /sony/camera/ 
wait for data 
HTTP/1.1 200 OK
Connection: close
Content-Length: 21
Content-Type: application/json

{"result":[0],"id":1} 
----closing connection---- 
 
Msg send: {"version":"1.0","id":1,"method":"startLiveview","params":[]} 
connecting to 192.168.122.1 
connected to 192.168.122.1:8080 
Requesting URL: /sony/camera/ 
wait for data 
HTTP/1.1 200 OK
Connection: close
Content-Length: 75
Content-Type: application/json

{"result":["http:\/\/192.168.122.1:8080\/liveview\/liveviewstream"],"id":1} 
----closing connection---- 
 
pressed.. 
Msg send: {"version":"1.0","id":1,"method":"stopLiveview","params":[]} 
connecting to 192.168.122.1 
connected to 192.168.122.1:8080 
Requesting URL: /sony/camera/ 
wait for data 
HTTP/1.1 200 OK
Connection: close
Content-Length: 21
Content-Type: application/json

{"result":[0],"id":1} 
----closing connection---- 
 
Msg send: {"version":"1.0","id":1,"method":"actTakePicture","params":[]} 
connecting to 192.168.122.1 
connected to 192.168.122.1:8080 
Requesting URL: /sony/camera/ 
wait for data 
HTTP/1.1 200 OK
Connection: close
Content-Length: 108
Content-Type: application/json

{"result":[["http:\/\/192.168.122.1:8080\/postview\/memory\/DCIM\/100MSDCF\/DSC01272.JPG?size=Scn"]],"id":1} 
----closing connection---- 
 
Msg send: {"version":"1.0","id":1,"method":"startLiveview","params":[]} 
connecting to 192.168.122.1 
connected to 192.168.122.1:8080 
Requesting URL: /sony/camera/ 
wait for data 
HTTP/1.1 200 OK
Connection: close
Content-Length: 75
Content-Type: application/json

{"result":["http:\/\/192.168.122.1:8080\/liveview\/liveviewstream"],"id":1} 
----closing connection---- 
 

Video

Video (Downloadlink):

Fazit und Ausblick

Dieser Blogeintrag beschreibt in einfacher Weise die grundsätzliche Funktion der WiFi Fernsteuerung einer Sony Kamera. Die Funktionsweise lässt sich zumindest auf die Kameras übertragen, die PlayMemories kompatibel sind. Möglicherweise ist dann die Serveradresse der Kamera zu modifizieren. Der Schritt zu komplexeren Lösungen ist reine Programmierarbeit. Ersetzt man den Pushbutton mit einer Zeitschleife kann man sehr einfach die Einzelbilder für TimeLapse Videos (Zeitraffer) aufnehmen. Die Auslösung kann natürlich auch durch Sensoren (Annäherung, Bewegung, …) erfolgen. Prinzipiell ist auch der Start und Stop einer Videoaufnahme (Kamera im Videomodus) möglich. Die entsprechenden json Kommandos sind in der Doku der APK enthalten. Wenn man auf den Modus „Liveview“ verzichtet ist die Auslöseverzögerung nur minimal (siehe Kommentar im Code, diese Zeilen dann auskommentieren), allerdings verliert man dann die Möglichkeit Kameraeinstellungen zu ändern. Für einen Batteriebetrieb ist der ESP8266 bekanntermaßen nicht so gut geeignet, aber für einen einzelnen Shot braucht es nicht viel Zeit. Für Timelapse Projekte wäre es sicher sinnvoll den Sleep Mode des ESP zu nutzen (was bei der Bauform ESP-01 nicht funktioniert ohne Modifikation). Ich würde hier das Modell ESP-12 empfehlen, das außerdem CE und FCC zertifiziert ist.

 

Addendum2 zum Tutorial – Speicherung von MySQL DB Werten mit ESP8266

Nachdem ich mich in meinen letzten Blogs intensiv mit dem ESP8266 auseinandergesetzt habe, steht logischerweise eine Erweiterung meines Tutorials zur Speicherung von Messwerten in eine MySQL DB unter Verwendung des ESP8266 an. Das Programm wurde weitestgehend analog dem bisherigen Programm aufgebaut, so dass es relativ einfach zu verstehen sein sollte.  Im Programm müssen noch die persönlichen Daten für das WLAN und den Host eingetragen werden.

/* 
 Programm zur Speicherung von Messwerten in einer webbasierten MySQL DB
  based on standard programs of the ESP8266Wifi library 
  and examples on sparkfun.com
 */
 
#include <ESP8266WiFi.h>
#define CYCLE 60000

const char* ssid     = "hier deine SSID";
const char* password = "und das Passwort des WLANs";
const char* host = "hier deinen Host eintragen";
unsigned long value = 0;
unsigned int lastcall = CYCLE;
int conn_time;

void setup() {
  Serial.begin(115200);
  delay(10);

  // We start by connecting to a WiFi network

  Serial.println();
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(ssid);

  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
    conn_time++;
    if (conn_time > 20) {
      break;
    }
  }
  if (WiFi.status() == WL_CONNECTED) {
    Serial.println("");
    Serial.println("WiFi connected");
    Serial.println("IP address: ");
    Serial.println(WiFi.localIP());
  }
  else {
    Serial.println("");
    Serial.println("no WiFi connection");
    ESP.deepSleep(100000);
    Serial.println("gone to sleep");
  }
}

void loop() {
  while (millis() - lastcall < CYCLE) {
    delay(1);
  }
  lastcall = millis();
  ++value;

  Serial.print("connecting to ");
  Serial.println(host);

  // Use WiFiClient class to create TCP connections
  WiFiClient client;
  const int httpPort = 80;
  if (!client.connect(host, httpPort)) {
    Serial.println("connection failed");
    return;
  }

  // We now create a URI for the request
  String url = "/w2mysql.php";
  url += "?A0=";
  url += millis();
  url += "&A1=";
  url += value;
 
  Serial.print("Requesting URL: ");
  Serial.println(url);

  // This will send the request to the server
  client.print(String("GET ") + url + " HTTP/1.1\r\n" +
               "Host: " + host + "\r\n" +
               "Connection: close\r\n\r\n");
  delay(1000);

  // Read all the lines of the reply from server and print them to Serial
  while (client.available()) {
    String line = client.readStringUntil('\r');
    Serial.print(line);
  }

  Serial.println();
  Serial.println("closing connection");
}

Performance Test ESP8266

Der ESP8266 gehört inzwischen zu meinen Standardboards. Die Integration in die Arduino IDE ist erfolgt und die Zahl der Libraries nimmt ständig zu. Um ein Gefühl für die Performance des Boards zu bekommen habe ich das Primzahlenprogramm genutzt, mit dem ich bereits den STM32 Maple gegen den Arduino UNO getestet habe.

Das Ergebnis als Übersicht, alle Zeitangaben in Millisekunden.

Datentyp Suchbereich Arduino Maple ESP8266
uint8_t 255 550 30 83
uint16_t 65535 10760 241 1054
uint32_t 2*65535 84551 583 2919
uint32_t 1000000 abgebrochen 10042 30369

Auf eine Besonderheit möchte ich an dieser Stelle hinweisen:

Ich hatte anfänglich Schwierigkeiten beim ESP mit dem uint32_t Datenformat. Aus zunächst unerklärlichen Gründen gab es immer Abstürze des Programms aufgrund von Watchdog Fehlern. Das hängt wohl damit zusammen, dass der ESP seine komplette WLAN Abarbeitung im Hintergrund erledigt. Wenn aber längere Loops im Programm sind, bekommt er offensichtlich Schwierigkeiten. Um das zu vermeiden habe ich den Befehl yield() in die Loop eingebaut. Das verhindert den Absturz, frisst aber auch ca. 6μs je Aufruf.

Integration des ESP8266 in die Standard Arduino IDE

In meinem letzten Beitrag habe ich beschrieben wie der ESP8266 angeschlossen werden muss, um mit der Arduinio IDE geflashed zu werden. Zu diesem Zeitpunkt war eine eigenständige IDE (siehe Link) notwendig. Heute ist die Integration des ESP8266 in die Standard IDE sehr viel einfacher. Adafruit hat eine ausführliche Anleitung veröffentlicht, die bis jetzt allerding nicht auf Deutsch verfügbar ist. Deshalb und da die Anleitung auch für den ESP8266-01 gültig ist, diese Anleitung hier. Zunächst installieren wir uns die aktuellste IDE von arduino.cc. In meinem Fall ist es die Version 1.6.5.

Nach der Installation der IDE wird diese gestartet. Unter Datei->Voreinstellungen muss nun unter Additional Boards Manager URLs folgende Eintragung vorgenommen werden: http://arduino.esp8266.com/staging/package_esp8266com_index.json . Mit OK wird das Fenster geschlossen. (Mit der stableVersion hatte ich einen CRC Fehler, deshalb hier die staging-Version).

ESP_BoardsManager4Auf ein Detail möchte ich an dieser Stelle noch hinweisen. Es ist zweckmäßig für die ESP Sketches ein eigenes Sketchbook Verzeichnis unter Sketchbook-Speicherort zu definieren, da für den ESP teilweise eigene Libraries gebraucht werden, die nicht mit den AVR Libraries kompatibel sind.

Jetzt kommt der eigentliche Clou. Arduino hat mit dem Board Manager ein tolles Tool geschaffen, um neue Boards in die IDE zu integrieren. Unter Werkzeuge->Platine rufen wir jetzt den Boards Manager auf und geben in das Suchfeld ESP8266 ein.  Die Boards Erweiterung der ESP Community wird schnell gefunden.

ESP_BoardsManager2Wir wählen die Erweiterung aus und wählen Install. Nach dem Download der Erweiterung, meldet der Board-Manager INSTALLED.

ESP_BoardsManager3Jetzt können wir unter Werkzeuge->Platine die Veränderung bewundern. Für den ESP8266-01 wählen wir jetzt das Generic ESP8266 Module.

ESP_BoardsManager5Wenn wir dann Werkzeuge->Platine erneut aufrufen, sehen wir weitere Einstellungen für den ESP. Hier können wir getrost die default-Einstellungen übernehmen.

ESP_BoardsManager6Zusätzlich tauchen unter Datei->Beispiele spezifische Sketches für den ESP8266 auf. Wer Lust hat kann jetzt mit der Beschaltung und Beschreibung meines letzten Blog Posts das Blink Beipiel flashen.

ESP_BoardsManager7Da mit der Aufbau auf dem Steckbrett auf Dauer zu fehleranfällig war, habe ich mir eine Programmier-Platine auf einer Streifenraster-Platine zusammengelötet.

guvcview_image-5Rechts unten kann ich die GPIO Pins des ESP abgreifen, die Buchse ist mit langen Pins nach unten ausgestattet, so dass ich die Platine auch auf ein Steckbrett stecken kann. Für die Erzeugung der 3.3V habe ich ein AMS1117 Modul integriert. Um in den Flash Mode zu kommen habe ich einen Pushbutton für GPIO0 auf GND und einen Schalter, um die Spannungsversorgung zu unterbrechen eingebaut. Vor dem Flashen wird GPIO0 auf GND gezogen, dann die Spannungsversorgung kurz unterbrochen. Der ESP ist jetzt im Flash Mode. Wie gewohnt wird das Programm mit dem -> hochgeladen.

ESP_BoardsManager8

ESP8266 mit Arduino IDE flashen

esp_blink640

Mit der angepassten IDE auf https://github.com/esp8266/Arduino lässt sich der ESP8266 direkt flashen.

Hier die Verschaltung zum Flashen des ESP8266. Wenn ein 3,3V FTDI zur Verfügung steht, kann man sich den Spannungsteiler auf der TX Leitung zum ESP sparen.

Variante 1: 3,3V FTDI (an der oberen Leiste des Steckbretts liegen 3,3V an)

 

ESP8266_Steckplatine_3.3Variante 2: 5V FTDI (an der oberen Leiste des Steckbretts liegen 3,3V an)

 

ESP8266_5VFTDI_Steckplatine

 

Zum Flashen wird zunächst der Schalter umgelegt und damit der GPIO0 auf GND gezogen. Dann einmal die 3,3V Spannungsversorgung aus- und wieder einschalten. Jetzt ist der ESP im Flash Modus.

Programm hochladen. Der ESP bekommt nach dem Flashen ein Reset und läuft direkt los, allerdings werden Einstellungen für den Modus (Access Point, Client, usw) erst nach einem Aus- und Wiedereinschalten  übernommen. Ich schalte nach dem Flashen den GPIO0 wieder auf offen und mache die Schaltung einmal stromlos, so bin ich sicher, das alles übernommen ist.

Download der Fritzing Files:

https://www.dropbox.com/s/sl04lnjxaocozg6/ESP8266.fzz?dl=0

https://www.dropbox.com/s/j5b2rium000nciw/ESP8266_5VFTDI.fzz?dl=0

What IF ? — Kein FTDI zur Hand

Der FTDI ist der wohl bekannteste USB Seriell Wandler auf dem Markt und wird in der Mikrocontroller Entwicklung sehr häufig eingesetzt, nämlich immer dann, wenn es darum, geht ein serielles Device direkt über USB anzusprechen. Ich setze den FTDI gerne ein, wenn ich ein HC-05 Bluetooth Modul mit AT Befehlen umkonfiguriere oder wenn ich die FW Version eines ESP8366 neu flashen will.

Doch was, wenn man gerade keinen FTDI zur Hand hat? Dumm gelaufen, macht aber nix, wenn man zumindest einen Arduino UNO zur Hand hat.

Auf dem UNO ist mit dem ATmega 16U2 bereits ein USB-Seriell Wandler eingebaut und der zweite wichtige Punkt ist, dass man den ATmega328, also den Chip auf dem die Arduino Programme laufen, entnehmen kann. Leider funktionioert das Beschriebene deshalb auch nicht mit der SMD Version.

Als erstes entnehmen wir vorsichtig den Chip von der Platine und passen auf, dass keine Beinchen verbiegen. An Pin 0 und 1 auf dem Platine kann jetzt das serielle Signal abgenommen werden. Wichtig ist zu beachten, dass die Beschriftung jetzt sozusagen für den Empfänger gilt und deshlab müssen RX und TX zwischen den beiden Devices 1:1 verbunden werden.

Ich habe das mal mit einem HC-05 aufgebaut. Der Einfachheit halber bin ich davon ausgegangen, dass der HC-05 in diesem Fall 5V tolerant ist.

hc-05-serial_Steckplatine

Als serielles Terminal kann jetzt die Arduino IDE genommen werden, ich persönlich bevorzuge Cutecom unter Linux. Die einzustellende Baudrate enspricht der Baudrate des HC-05.

Die ist der erste Blogpost zum Thema „What IF“  –  kleine Tips, die das Leben einfacher machen. Weitere werden folgen.

 

 

Arduino WiFi für unter 3 Euro – der ESP8266

IMG_20150116_090344

In den letzten Monaten ist ein regelrechter Hype ausgebrochen um den ESP8266, ein Chip aus chinesischer Entwicklung, der auf einer winzigen Fläche sowohl Access Point als auch WiFi Client sein kann. Inzwischen exisitieren verschiedene Breakout Boards, die zunächst eine UART-WiFi Schnittstelle bereitstellen und auf eBay für unter 3 Euro gehandelt werden. Da der Chip einen 32bit Prozessor enthält kann er aber auch mit eigener Firmware bestückt werden. Ich möchte mich hier nicht näher mit Details beschäftigen, da es bereits viele Webseiten zu diesem Thema gibt. Eine kurze und knappe Zusammenfassung auf deutsch mit Verlinkung zu weiteren Seiten hat das FabLab Nürnberg erstellt.

Inzwischen konnte ich einige Erfahrungen mit diesem Modul machen und solange man kein Highspeed WiFi braucht ist das Modul eine gute Wahl und macht für kleines Geld IoT möglich. Die ersten Versuche mit diesem Modul habe ich in Verbindung mit einem Arduino MEGA gemacht. Der MEGA hat mehrere in Hardware implementierte serielle Schnittstellen, so kann man sowohl die Verbindung zum Computer als auch die Verbindung zum ESP8266 mit 115200 Baud laufen lassen. In den neueren Firmwares des ESP ist die Baudrate aber auch einstellbar, so dass ein Betrieb an einer SoftSerial Schnittstelle bei z.B. 9600 Baud, damit an einem UNO, auch möglich ist.  Die Verbindung des ESP an den MEGA muss über Pegelwandler 5V->3.3V erfolgen, die Schnittstelle ist hier nicht tolerant. Ich habe die Verbindung von TX (MEGA) zu RX (ESP) über einen Spannungsteile aus 1k und 2.2k herunter geteilt. Der ESP braucht einen eigenen 3.3V Spannungswandler, der 3.3V Ausgang des Arduino liefert zu wenig Strom für den ESP (max 200mA).

Für erste Experimente, bei denen die AT Befehle „per Hand“ eingegeben werden, eignet sich das folgende kurze Programm.

void setup() {
  Serial.begin(115200);
  Serial1.begin(115200);
  Serial.println("Program started");
}

void loop() {
  if(Serial1.available()) {   
    char ser = Serial1.read();
    /*if (ser==10) {         // kann genutzt werden, um ein Gefühl für die Antwortzeiten zu bekommen
      Serial.print("  ");
      Serial.print(millis());
    }*/
    Serial.print(ser);
  }
  if(Serial.available()) {   
    char ser = Serial.read();
    Serial1.print(ser);
  }
}

Die meisten Einstellungen des Chip bleiben über ein Power on Reset bestehen. Es ist also nicht notwendig, im Programm die Einstellungen jedes mal neu zu setzen. Ich habe mir zu diesem Zweck ein Programm erstellt mit den wichtigsten Einstellungen (Access Point, SSID, PASS, Client usw.). Mit diesem Programm mache ich die Grundkonfiguration des Chip.

// ESP_Minimal.ino - sketch to quickly view and change some of ESP8266 parameters
// most of the ESP parameters are stored in eeprom, so also outlive the power on reset
// special thanks to http://rayshobby.net/?p=9734
// sketch is written for ArduinoMega, ESP connected with voltage devider to Serial1
// This code is in the public domain.

#define SET // for just viewing parameters, set this as comment

#define BUFFER_SIZE 512
#define esp Serial1
#define dbg Serial //For debugging purposes

char buffer[BUFFER_SIZE];
char OKrn[] = "OK\r\n";
int mode;

boolean wait_for_esp_response(int timeout, char* term=OKrn) {
 unsigned long t=millis();
 bool found=false;
 int i=0;
 int len=strlen(term);
 // wait for at most timeout milliseconds
 // or if OK\r\n is found
 while(millis()<t+timeout) {
 if(esp.available()) {
 buffer[i++]=esp.read();
 if(i>=len) {
 if(strncmp(buffer+i-len, term, len)==0) {
 found=true;
 break;
 }
 }
 }
 }
 buffer[i]=0;
 dbg.print(buffer);
 return found;
}

void setup() {
 esp.begin(115200); //esp baud rate
 dbg.begin(115200); //debugger baud rate
 dbg.println("start of program ...");
 esp.println("AT"); // check for existence of ESP8266
 if (!wait_for_esp_response(1000)) {
 dbg.println("no ESP found");
 while(true); // hang forever
 }
 // some important checks of operation modes
 esp.println("AT+CWMODE?"); // check mode 1=Client, 2=AP, 3=both
 wait_for_esp_response(1000);
 char *result = strstr(buffer, "+CWMODE:");
 if (result!=0) {
 mode=atoi(result+8);
 if (mode!=1) {
 esp.println("AT+CWSAP?"); // actual AP configuration
 }
 if (mode!=3) {
 esp.println("AT+CWJAP?"); // actual connected WiFi
 wait_for_esp_response(1000);
 }
 }

 esp.println("AT+CIPMUX?"); // check connection mode 0=single, 1=multiple
 wait_for_esp_response(1000);
 esp.println("AT+CIPMODE?"); // check data mode 0=normal, 1=transparent
 wait_for_esp_response(1000);
 esp.println("AT+CIFSR"); // actual IP adress
 wait_for_esp_response(1000);

#ifdef SET
 esp.println("AT+CWMODE=3"); // set mode 1=Client, 2=AP, 3=both
 wait_for_esp_response(1000);
 // set AP or Client
 esp.println("AT+CWMODE?"); // check mode 1=Client, 2=AP, 3=both
 wait_for_esp_response(1000);
 result = strstr(buffer, "+CWMODE:");
 if (result!=0) {
 if (mode!=1) { // AP settings
 esp.println("AT+CWSAP=\"ESP_9F2556\",\"blablabla\",1,3"); // set AP: SSID, PASS, channel, encryption
 wait_for_esp_response(1000); // 0=open, 1=WEP, 2=WPA_PSK, 3=WPA2_PSK, 4=WPA_WPA2_PSK
 }
 if (mode!=2) { // Client settings
 esp.println("AT+CWJAP=\"yourSSID\",\"passphrase\""); // connect client to AP
 wait_for_esp_response(5000); // was really that long!
 }
 }

#endif

 esp.println("AT+RST"); // RESET, necessary for some changes of settings
 wait_for_esp_response(3000, "ready");
}

void loop() {

}

Die zu ändernden Parameter werden direkt im Code verändert. Wenn die erste Zeile #define SET auskommentiert wird, werden die wichtigsten Parameter nur angezeigt.