2015
|
Dauerbetrieb mit ESP8266 - Zeitsteuerung - IoT |
|
Netzzeit/Timeserver oder
Steuern mit WLAN III
Ergänzung zum eBook "Messen und Steuern mit dem Smartphone"
|
|
Mit Klebeband gehalten: 1.8"-TFT-Display ST7735 mit der Netzzeit und den Klima-Daten vom DHT11. Nicht im Bild: ESP8266 und Arduino Uno. |
Netzsteuerungen benötigen eventuell eine genaue Zeit - nur so können die Dinge im Netz zum richtigen Zeitpunkt, wie gewünscht beeinflusst werden. Da globale Steuerungen nur mit Internet-Anbindung funktionieren, liegt es nahe, darüber auch eine Zeitreferenz zu beziehen.
Auch sechs Monate nach dem Erscheinen des ESP8266 gab es noch keine Zeit-Bibliothek für den Arduino, um die Zeit eines NTP-Servers richtig zu erhalten. Das ist erstaunlich. Zwar stand eine ESP-Firmware bereit die das kann, das Betriebssystem des ESP soll hier aber (noch) nicht verändert werden. Die genaue Zeit aus dem Netz zu erhalten ist - systembedingt, nicht ganz einfach und das NTP-Protokoll ist auch nicht sofort jedem klar. Für heimische Schaltvorgänge reicht möglicherweise aber auch eine Referenzzeit, wie die von http://nist.gov/ bereitgestellte UTC-Tageszeit (daylight-time).
Als Szenario diene weiterhin ein fiktives Ferienhaus, in dem einige Dinge über das Internet (IoT) gesteuert werden sollen. Ein Wlan-Router z.B. eine alte Fritzbox, sei dort vorhanden und dauerhaft online. ESP und Fritzbox kennen sich bereits und die IP sei auch global erreichbar. Vor Ort befindet sich ein Grundaufbau, wie er schon in Steuern mit WLAN I - lokales Steuern/Schalten eingesetzt wurde. Des Sketch aus Steuern mit WLAN II - globales Schalten/Steuern wird hier entsprechend modifiziert.
Quarztoleranz
Ein hier vorliegender Arduino Uno R3 mit seinem 16 MHz-Quarz, läuft laut Messungen in 6 Stunden etwa 30 Sekunden nach. Addiert man alle 3600000 millis() 6 Sekunden, so läuft die interne Uhr relativ genau, ohne Aufruf einer externen Referenz. Gemessen wurden nach dieser Angleichung etwa 5 Sekunden in 12 Stunden. Falls kein Rechenfehler vorliegt, wäre die Ungenauigkeit dann 1 Stunde/Jahr. Allerdings scheint diese Abweichung nicht konstant zu sein. Vermutlich haben Temperatur und Art des Sketches Einfluss auf den internen Timer.
Um zur Not auch die Zeit manuell verändern zu können (lokal, kein Netz), wird eine Eingabe via Smartphone und TCP-Client vorgesehen. Auch die Internetzeit kann via TCP-Client manuell angefordert werden.
Zeitserver-Referenz
Die Zeitabfrage via Internet und ESP8266 setzt Internetzugriff voraus. Der Arduino ist über den ESP8266 lokal am Fritzbox-Router über IP 192.162.xxx.xxx erreichbar und umgekehrt. Der Aufruf von time() fordert die Internetzeit an:
void time()
{String cmd="AT+CIPCLOSE=4";
Serial.println(cmd);espReadLine(1000);
cmd="AT+CIPSTART=4,\"TCP\",\"";
cmd+="time.nist.gov";cmd+="\",13";
Serial.println(cmd);
}
Der Zeitserver darf nicht zu oft bemüht werden, da er sonst den Zugang verweigert. Damit der Aufruf möglichst ungestört verläuft, erfolgt der erste Synchronisationsversuch 30 Sekunden nach dem Start. Dies wird alle 30 Sekunden wiederholt, bis einmal eine erfolgreiche Verbindung zustande kam. Der Sekundenzeiger der oben dargestellten Analoguhr folgt sofort, Minute und Stunde wird aus grafischen Gründen nur zur vollen Minute neu gezeichnet. Diese Uhrendarstellung benutzt als Vorlage die Canvas-Variante hier auf dieser Seite mit Arduino-Quellen von Gilchrist 6/2/2014 1.0, Updated by Alan Senior 5/1/2015 aus dem Uhrenbeispiel Adafruit_ST7735_AS\examples\TFT_Clock_ST7735.
|
... neben einer 5stelligen Zahl sind das die Daten der Zeit - wie im Format-String zu erkennen. ...
|
Die Kommunikation erfolgt über die Verbindung 4 des ESP8266, die zunächst geschlossen wird, anschließend wird eine Sekunde Zeit für die Reaktion eingeräumt. Erst jetzt erfolgt der Aufbau der neuen TCP-Verbindung 4 an Port 13 mit der Zeitserver-URL. Sollte die Anfrage erfolgreich sein, so antwortet der Server mit einer Zeichenfolge, der 8 Parameter entnommen werden können. Neben einer 5stelligen Zahl sind das die Daten der Zeit - wie im Format-String zu erkennen. Der Rest der Zeile wird einfach in der Zeichenkette t abgelegt. Dort findet man auch die Zeichenfolge UTC.
...
//Check TimeStamp Daylight
k=sscanf(s,"%5d %2d-%2d-%2d %2d:%2d:%2d %s",
&r5,&y,&m,&d,&hh,&mm,&ss,t);
if(k==8)
{setTime(hh,mm,ss,d,m,y);
tc=millis();// next hour automatic sync if needed
tf=0; //sync
}
//TimeClock UTC to CET
cet=CE.toLocal(now(),&tcr)+2;
...
Mit settime() aus der Bibliothek 'Time.h ' wird die 'Unix-Uhr' im Arduino vom 01.01.1970 auf die aktuelle UTC-Zeit gestellt, mit tc=0 eine erneute automatische Zeitabfrage im 30-Sekunden-Takt unterbunden. Um von UTC nach CET zu kommen ist eine weitere Zeit-Bibliothek 'Timezone.h ' erforderlich, die diese Konvertierung vornimmt. Die Umrechnung in mitteleuropäische Zeit unter Berücksichtigung von Sommer- und Winterzeit erfolgt mit CE.toLocal() , wobei CE eine Zeitzone ist und cet eine Zeitstruktur, die als weitere Variable neben der Systemzeit in UTC, die CET-Zeit enthält. Die Zeilen stammen aus dem WorldClock-Beispiel der Timezone-Library, welches den Bedürfnissen entsprechend gekürzt und angepasst wurde. Dort findet man auch die Sommertzeit-Regeln anderer Regionen.
#include <Time.h>
#include <Timezone.h>
//https://github.com/JChristensen/Timezone
//From WorlClock Example
//Central European Time (Frankfurt, Paris)
TimeChangeRule CEST = {"CEST", Last, Sun, Mar, 2, 120};
TimeChangeRule CET = {"CET ", Last, Sun, Oct, 3, 60};
Timezone CE(CEST, CET);
TimeChangeRule *tcr;
time_t cet;
|
Zeitsketch ohne Anzeige
|
Der folgende Sketch hat keine direkte Ausgabe, sondern kommuniziert nur über TCP/IP mit einem verbundenen Gerät. Dynamiche IP und Routeranbindung ist hier nicht enthalten, trotzdem ist der Sketch in Verbindung mit einem 'eingestellten' ESP8266 lauffähig. Diese Einschränkung erfolgt aus Gründen der Übersichtlichkeit. Mit den ASCII-Zeichen '0' und '1' kann weiterhin ein Relais oder eine LED geschaltet werden. Mit 'z' wird die Uhrzeit erfragt, mit 't' eine unplanmäßige Zeitsynchronisation gestartet. Zeichen '8' liefert das aktuelle Betriebssystem und mit 'T' kann die Zeit (UTC) manuell gesetzt werden im Format, wie es 'd' als Zeit und Datum zurück liefert. Am Ende der loop() könnte eine Zeitanzeige eingebaut werden, die aufgrund des seriellen Timeouts etwa jede Sekunde aktualisiert würde. Dort könnte also die Ansteuerung einer Rheinturmuhr-Variante mit ihrere Anzeige erfolgen.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130 | // ----------------------------------------------
// ESP8266 - Internet-Time
// Daylight - Timestamp
// Steuern mit WLAN III
// Bezug: http://hjberndt.de/soft/ardesp8266time.html
// ----------------------------------------------
#include <Time.h>
#include <Timezone.h>
//https://github.com/JChristensen/Timezone
//From WorlClock Example
//Central European Time (Frankfurt, Paris)
TimeChangeRule CEST = {"CEST", Last, Sun, Mar, 2, 120};
TimeChangeRule CET = {"CET ", Last, Sun, Oct, 3, 60};
Timezone CE(CEST, CET);
TimeChangeRule *tcr;
time_t cet;
#define LED 7 // Relais/Relay
#define RTIME 180000 //TRIGGER ESP every RTIME ms
#define RRTIME 900000 //RESTART ESP every RRTIME ms
#define CETIME (3600000)
char *startup[]={"ATE0","AT+CWMODE=3","AT+CIPMUX=1",
"AT+CIPSERVER=1","AT+CIPSTATUS"};
volatile int last_id; //sender id for message
unsigned long t0,t1,tc,tf=30000;//first sync after boot
void time()
{String cmd="AT+CIPCLOSE=4";
Serial.println(cmd);espReadLine(1000);
cmd="AT+CIPSTART=4,\"TCP\",\"";
cmd+="time.nist.gov";cmd+="\",13";
Serial.println(cmd);
}
void setup()
{pinMode(LED,OUTPUT); Serial.begin(9600);
t0=millis();t1=t0;tc=t0; espReset();
}
void loop()
{int id,len,ix;char t[80],s[80],p[80];
strcpy(p,espReadLine(0));
ix=sscanf(p,"+IPD,%d,%d:%s",&id,&len,s);
if(3==ix)
{last_id=id;
switch (s[0])
{case '0':digitalWrite(LED,LOW);showLed(id);break;
case '1':digitalWrite(LED,HIGH);showLed(id);break;
case '8':espSendLine(id,"show os version");
Serial.println("AT+GMR");break;
case 'T'://Settime "T00:00:00D01.01.2000" UTC
{int y,m,d,hh,mm,ss;
ix=sscanf(s,"T%2d:%2d:%2dD%2d.%2d.%4d",
&hh,&mm,&ss,&d,&m,&y);
if(ix==6)
{setTime(hh,mm,ss,d,m,y);
espSendLine(id,"UTC Time Set.");
}
}
break;
case 't':espSendLine(id,"try sync.");time();break;
case 'z':sprintf(t,"%02d:%02d:%02d",
hour(cet),minute(cet),second(cet));
espSendLine(id,t);
break;
case 'd':sprintf(t,"T%02d:%02d:%02dD%02d.%02d.%04d",
hour(cet),minute(cet),second(cet),
day(cet),month(cet),year(cet));
espSendLine(id,t);
break;
default:showLed(id);
}
}
//Check prompt
len=strlen(p);
if(p[0]=='>' && len>5)
{for(int i;i<len;i++)if(p[i]<32)p[i]=0;
espSendLine(last_id,p);//reply to last sender
}
if(millis()>(t0+RTIME)){espReset();t0=millis();}
if(millis()>(tc+CETIME))
{if(timeStatus()<2)time();//ONE SYNC
else adjustTime(6); // sec per hour
tc=millis(); //next hour
}
if(tf>0)if(millis()>(t0+tf))
{tf+=tf; time();}//first sync after tf
// CLOCK
// drawClockHands(cet); //not included
}
void showLed(int id)
{ char s[80];
sprintf(s,"AT+CIPSEND=%d,12",id);// Message to id
Serial.println(s);espReadLine(0);
Serial.println(digitalRead(LED)?"LED is ON ":"LED is OFF");
}
void espSendLine(int id, char *t)
{char s[80];
sprintf(s,"AT+CIPSEND=%d,%d",id,strlen(t)+2);
Serial.println(s); Serial.println(t);// Message to id
}
char *espReadLine(int ms)
{int k,r5,y,m,d,hh,mm,ss;char t[80],s[80]; //buffer
if(ms)delay(ms);
int len=Serial.readBytesUntil('\n',s,sizeof(s)-4);
s[len]=0;
//Check TimeStamp Daylight
k=sscanf(s,"%5d %2d-%2d-%2d %2d:%2d:%2d %s",
&r5,&y,&m,&d,&hh,&mm,&ss,t);
if(k==8)
{setTime(hh,mm,ss,d,m,y);
tc=millis();// automatic sync if needed
tf=0;
}
//TimeClock UTC to CET
cet=CE.toLocal(now(),&tcr)+2;
return s;
}
void espReset()
{if(analogRead(A0)==0 || millis()>(t1+RRTIME))
{Serial.println("AT+RST");delay(2000);
espReadLine(1000); t1=millis();}
for(int i=0;i<5;i++)
{Serial.println(startup[i]);espReadLine(0);}
}
|
|
Manuell stellt die Zeit ein
|
Empfängt der Arduino an seiner seriellen Schnittstelle, die mit dem ESP8266 mit dem Netztwerk verbunden ist, ein "T" als erstes Zeichen, so erfolgt eine manuelle Zeiteingabe. Dies dient quasi als Notfunktion, falls der Zeitserver ausfällt, oder im lokalen Netz kein Internetzugang vorhanden ist. Das Format ist:
Thh:mm:ssDtt.mm.jjjj
Dabei wird die Zeit als UTC erwartet. Eine Umrechnung erfolgt nicht. Über diesen Weg ist es auch möglich die Zeit von anderen Quellen entsprechend einzuspeisen. Ist beispielsweise ein Android-Smartphone und rfo-Basic vorhanden, ist es möglich die Handy-Zeit zu übertragen, oder sogar die GPS-Zeit vom Smartphone zu übernehmen.
|
GPS- oder GSM-Zeit Handyzeit
|
Nach diesem 'Muster' kann ein Handy, bei entsprechender Formatierung, die aktuelle Zeit übertragen. Ist auch diese Uhr zu ungenau und das mobile Gerät verfügt über GPS, so kann die GPS-Zeit der Navigationssatelliten als Referenz übernommen werden. Ein Listing für rfo-Basic könnte wie folgt aussehen. Dabei wird lediglich die gewünschte Quelle (GSM/GPS) in Zeile 20 als Unterprogramm aufgerufen:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56 | PRINT "WLAN Steuerung mit ESP8266."
PRINT "Arduino Relais schalten."
PRINT "GPS/GSM-Zeit als UTC übernehmen."
SOCKET.CLIENT.CONNECT "meine.url.de",333
SOCKET.CLIENT.STATUS r
IF r THEN
!PRINT "Connected to ";
SOCKET.CLIENT.SERVER.IP a$
!PRINT a$
ELSE
END
ENDIF
lf$=CHR$(13)+CHR$(10)
SOCKET.CLIENT.WRITE.LINE "z"+lf$
GOSUB readline
SOCKET.CLIENT.WRITE.LINE "d"+lf$
GOSUB readline
!-----------------
GOSUB getgsmtime
!-----------------
SOCKET.CLIENT.WRITE.LINE a$+lf$
PRINT a$ + " gesendet."
GOSUB readline
SOCKET.CLIENT.WRITE.LINE "z"+lf$
GOSUB readline
SOCKET.CLIENT.CLOSE
END
ReadLine:
t0=CLOCK()
rmsg$=""
DO
SOCKET.CLIENT.READ.READY rr
IF rr
SOCKET.CLIENT.READ.LINE rmsg$
PRINT rmsg$
ENDIF
UNTIL ((CLOCK()-t0)>2000) | (rmsg$<>"")
RETURN
GetGpsTime:
GPS.OPEN
PAUSE 100
GPS.TIME t
GPS.CLOSE
TIMEZONE.SET "UTC"
TIME t,y$,m$,d$,hh$,mm$,ss$,wd,ds
A$="T"+hh$+":"+mm$+":"+ss$+"D"+d$+"."+m$+"."+y$
RETURN
GetGsmTime:
TIMEZONE.SET "UTC"
TIME y$,m$,d$,hh$,mm$,ss$,wd,ds
A$="T"+hh$+":"+mm$+":"+ss$+"D"+d$+"."+m$+"."+y$
RETURN
|
Inzwischen ist hier ein preiswerter GPS-Empfänger eingetroffen. Damit erhält die Funkuhr via DCF77 neue Konkurrenz. Endlich funkgenaue Zeit an jedem Ort der Erde und die Rheinturmuhr läuft auch in Fernost auf die Sekunde genau - wenn es denn sein soll.
|
Steuern mit Bluetooth
Steuern mit WLAN I - Lokal
Steuern mit WLAN II - Global
Weltweite Funkzeit via GPS
|
Weitere Software
|