我主要是參考下面這篇範例來改寫 :
# Arduino/NTPClient.ino at master · esp8266/Arduino
關於 ESP8266WiFi.h 函式庫文件參考 :
# Arduino/doc/esp8266wifi/
本系列之前的測試紀錄參考 :
# 使用 Arduino IDE 開發 ESP8266 應用 (一) : 環境設定與韌體上傳
# 使用 Arduino IDE 開發 ESP8266 應用 (二) : 在網頁上控制 LED
測試 1 : 從 NTP 伺服器取得 UTC 網路時間
#include <ESP8266WiFi.h>
#include <WiFiUdp.h>
char* ssid="H30-L02-webbot"; //WiFi SSID
char* password="1234567890"; //WiFi password
unsigned int localPort=2390; //local port to listen for UDP packets
IPAddress timeServerIP; //time.nist.gov NTP server address
const char* ntpServerName="time.nist.gov"; //NTP Server host name
const int NTP_PACKET_SIZE=48; // NTP time stamp is in the first 48 bytes of the message
byte packetBuffer[ NTP_PACKET_SIZE]; //buffer to hold incoming and outgoing packets
WiFiUDP udp; //UDP instance to let us send and receive packets over UDP
void setup() {
Serial.begin(115200);
Serial.println();
Serial.println();
//Connecting to a WiFi network
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.print("IP address: ");
Serial.println(WiFi.localIP());
//Start UDP
Serial.println("Starting UDP");
udp.begin(localPort);
Serial.print("Local port: ");
Serial.println(udp.localPort());
}
void loop() {
//get a random server from the pool (get an IP from Server Name)
WiFi.hostByName(ntpServerName, timeServerIP);
sendNTPpacket(timeServerIP); //send an NTP packet to a time server
delay(1000); // wait to see if a reply is available
int cb=udp.parsePacket(); //return bytes received
if (!cb) {Serial.println("no packet yet");}
else { //received a packet, read the data from the buffer
Serial.print("packet received, length=");
Serial.println(cb); //=48
udp.read(packetBuffer, NTP_PACKET_SIZE); //read the packet into the buffer
//the timestamp starts at byte 40 of the received packet and is four bytes,
//or two words, long. First, esxtract the two words:
unsigned long highWord=word(packetBuffer[40], packetBuffer[41]);
unsigned long lowWord=word(packetBuffer[42], packetBuffer[43]);
//combine the four bytes (two words) into a long integer
//this is NTP time (seconds since Jan 1 1900):
unsigned long secsSince1900=highWord << 16 | lowWord;
Serial.print("Seconds since Jan 1 1900 = " );
Serial.println(secsSince1900);
//now convert NTP time into everyday time:
Serial.print("Unix time = ");
// Unix time starts on Jan 1 1970. In seconds, that's 2208988800:
const unsigned long seventyYears=2208988800UL;
// subtract seventy years:
unsigned long epoch=secsSince1900 - seventyYears;
// print Unix time:
Serial.println(epoch);
// print the hour, minute and second:
Serial.print("The UTC time is "); //UTC=Greenwich Meridian (GMT)
Serial.print((epoch % 86400L) / 3600); //print the hour (86400 secs/day)
Serial.print(':');
//In the first 10 minutes of each hour, we'll want a leading '0'
if ( ((epoch % 3600) / 60) < 10 ) {Serial.print('0');}
Serial.print((epoch % 3600) / 60); // print the minute (3600 secs/minute)
Serial.print(':');
// In the first 10 seconds of each minute, we'll want a leading '0'
if ( (epoch % 60) < 10 ) {Serial.print('0');}
Serial.println(epoch % 60); // print the second
}
delay(10000);
}
unsigned long sendNTPpacket(IPAddress& address) {
Serial.println("sending NTP packet...");
// set all bytes in the buffer to 0
memset(packetBuffer, 0, NTP_PACKET_SIZE); //clear the buffer
//Initialize values needed to form NTP request
//(see URL above for details on the packets)
packetBuffer[0]=0b11100011; // LI, Version, Mode
packetBuffer[1]=0; // Stratum, or type of clock
packetBuffer[2]=6; // Polling Interval
packetBuffer[3]=0xEC; // Peer Clock Precision
//8 bytes of zero for Root Delay & Root Dispersion
packetBuffer[12]=49;
packetBuffer[13]=0x4E;
packetBuffer[14]=49;
packetBuffer[15]=52;
// all NTP fields have been given values, now
// you can send a packet requesting a timestamp:
udp.beginPacket(address, 123); //NTP requests are to port 123
udp.write(packetBuffer, NTP_PACKET_SIZE); //send UDP request to NTP server
udp.endPacket();
}
NTP 伺服器使用的是 UDP 協定, 傳送與接收的封包數都是 48 個 Bytes, 參考以前在 Arduino+ESP8266 所做的測試紀錄 :
# 再探 NTP 協定
# 利用 NTP 伺服器來同步 Arduino 系統時鐘 (三)
注意, 使用 UDP 協定功能必須匯入 WiFiUdp.h 函式庫, 此函式庫在開發板設定時即已下載, 不須另外安裝. 上面的程式編譯上傳後重開機執行, 序列埠監控視窗輸出如下 :
Connecting to H30-L02-webbot
..
WiFi connected
IP address: 192.168.43.163
Starting UDP
Local port: 2390
sending NTP packet...
packet received, length=48
Seconds since Jan 1 1900 = 3715429678
Unix time = 1506440878
The UTC time is 15:47:58
sending NTP packet...
packet received, length=48
Seconds since Jan 1 1900 = 3715429684
Unix time = 1506440884
The UTC time is 15:48:04
sending NTP packet...
packet received, length=48
Seconds since Jan 1 1900 = 3715429691
Unix time = 1506440891
The UTC time is 15:48:11
sending NTP packet...
packet received, length=48
Seconds since Jan 1 1900 = 3715429697
Unix time = 1506440897
The UTC time is 15:48:17
sending NTP packet...
packet received, length=48
Seconds since Jan 1 1900 = 3715429704
Unix time = 1506440904
The UTC time is 15:48:24
參考 "利用 NTP 伺服器來同步 Arduino 系統時鐘 (三)" 的做法, 把 UTC 時間加上 28800 秒後轉成台灣的時戳, 然後用 Arduino 的 setTime() 函數將此時戳設定到 ESP8266 的內部時鐘, 這樣就可以呼叫 Time.h 與 TimeAlarms.h 函式庫的 year(), month(), hour() 等函數了, 注意, Time.h 與 TimeAlarms.h 都要匯入.
函式 | 說明 |
hour() | 傳回現在的時 (24 小時制) |
hourFormat12() | 傳回現在的時 (12小時制) |
minute() | 傳回現在的分 |
second() | 傳回現在的秒 |
year() | 傳回現在的年 |
month() | 傳回現在的月 |
day() | 傳回現在的日 |
weekday() | 傳回現在的星期 (星期日為 1) |
測試 2 : 從 NTP 伺服器取得 UTC 網路時間更新內件 RTC (使用計時器但無效)
#include <WiFiUdp.h>
#include <Time.h>
#include <TimeAlarms.h>
char* ssid="H30-L02-webbot"; //WiFi SSID
char* password="1234567890"; //WiFi password
unsigned int localPort=2390; //local port to listen for UDP packets
IPAddress timeServerIP; //time.nist.gov NTP server address
const char* ntpServerName="time.nist.gov"; //NTP Server host name
const int NTP_PACKET_SIZE=48; //NTP timestamp resides in the first 48 bytes of packets
byte packetBuffer[ NTP_PACKET_SIZE]; //buffer to hold incoming and outgoing packets
WiFiUDP udp; //UDP instance to let us send and receive packets over UDP
void setup() {
Serial.begin(115200);
Serial.println();
Serial.println();
//Connecting to a WiFi network
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.print("IP address: ");
Serial.println(WiFi.localIP());
//Start UDP
Serial.println("Starting UDP");
udp.begin(localPort);
Serial.print("Local port: ");
Serial.println(udp.localPort());
sync_clock();
Alarm.timerRepeat(60, sync_clock); //timer task every 60 seconds
}
void loop() {
String d=getDate();
String t=getTime();
String w=getWeek();
Serial.println(d + " " + t + " " + w);
delay(10000);
}
void sync_clock() {
setTime(getUnixTime() + 28800L);
}
String getDate() {
String d=(String)year() + "-";
byte M=month();
if (M < 10) {d.concat('0');}
d.concat(M);
d.concat('-');
byte D=day();
if (D < 10) {d.concat('0');}
d.concat(D);
return d;
}
String getTime() {
String t="";
byte h=hour();
if (h < 10) {t.concat('0');}
t.concat(h);
t.concat(':');
byte m=minute();
if (m < 10) {t.concat('0');}
t.concat(m);
t.concat(':');
byte s=second();
if (s < 10) {t.concat('0');}
t.concat(s);
return t;
}
String getWeek() {
String w[]={"Sun","Mon","Tue","Wed","Thu","Fri","Sat"};
return w[weekday()-1];
}
unsigned long getUnixTime() {
WiFi.hostByName(ntpServerName, timeServerIP); //get a random server from the pool
sendNTPpacket(timeServerIP); //send an NTP packet to a time server
delay(1000); // wait to see if a reply is available
int cb=udp.parsePacket(); //return bytes received
unsigned long unix_time=0;
if (!cb) {Serial.println("no packet yet");}
else { //received a packet, read the data from the buffer
Serial.print("packet received, length=");
Serial.println(cb); //=48
udp.read(packetBuffer, NTP_PACKET_SIZE); //read the packet into the buffer
//the timestamp starts at byte 40 of the received packet and is four bytes,
//or two words, long. First, esxtract the two words:
unsigned long highWord=word(packetBuffer[40], packetBuffer[41]);
unsigned long lowWord=word(packetBuffer[42], packetBuffer[43]);
//combine the four bytes (two words) into a long integer
//this is NTP time (seconds since Jan 1 1900):
unsigned long secsSince1900=highWord << 16 | lowWord;
Serial.print("Seconds since Jan 1 1900=" );
Serial.println(secsSince1900);
Serial.print("Unix time=");
//Unix time starts on Jan 1 1970. In seconds, that's 2208988800:
unix_time=secsSince1900 - 2208988800UL;
Serial.print(F("Unix time stamp (seconds since 1970-01-01)="));
Serial.println(unix_time); //print Unix time
}
return unix_time; //return seconds since 1970-01-01
}
unsigned long sendNTPpacket(IPAddress& address) {
Serial.println("sending NTP packet...");
// set all bytes in the buffer to 0
memset(packetBuffer, 0, NTP_PACKET_SIZE);
//Initialize values needed to form NTP request
//(see URL above for details on the packets)
packetBuffer[0]=0b11100011; // LI, Version, Mode
packetBuffer[1]=0; // Stratum, or type of clock
packetBuffer[2]=6; // Polling Interval
packetBuffer[3]=0xEC; // Peer Clock Precision
//8 bytes of zero for Root Delay & Root Dispersion
packetBuffer[12]=49;
packetBuffer[13]=0x4E;
packetBuffer[14]=49;
packetBuffer[15]=52;
// all NTP fields have been given values, now
// you can send a packet requesting a timestamp:
udp.beginPacket(address, 123); //NTP requests are to port 123
udp.write(packetBuffer, NTP_PACKET_SIZE);
udp.endPacket();
}
序列埠監控視窗輸出如下 :
Connecting to H30-L02-webbot
..
WiFi connected
IP address: 192.168.43.163
Starting UDP
Local port: 2390
sending NTP packet...
packet received, length=48
Seconds since Jan 1 1900=3715472215
Unix time=Unix time stamp (seconds since 1970-01-01)=1506483415
2017-09-27 11:36:55 Wed
2017-09-27 11:37:05 Wed
2017-09-27 11:37:15 Wed
2017-09-27 11:37:25 Wed
2017-09-27 11:37:35 Wed
2017-09-27 11:37:45 Wed
2017-09-27 11:37:55 Wed
2017-09-27 11:38:05 Wed
2017-09-27 11:38:15 Wed
2017-09-27 11:38:25 Wed
2017-09-27 11:38:35 Wed
2017-09-27 11:38:45 Wed
2017-09-27 11:38:55 Wed
2017-09-27 11:39:05 Wed
2017-09-27 11:39:15 Wed
2017-09-27 11:39:25 Wed
看起來 Arduino 內建的 setTime() 可以正常設定 ESP8266 的 RTC 內部時鐘, 但 Alarm.timerRepeat() 似乎沒有運作! 照理應該每 60 秒呼叫一次 sync_clock() 才對, 但除了在 setup() 中呼叫過一次 sync_clock() 外, 之後就沒有再呼叫了, 奇怪, 不知哪裡出問題.
我在下列葉難的文章中看到 Timer 函式庫 :
# Arduino一個好用的計時器程式庫
我在下列葉難的文章中看到 Timer 函式庫 :
# Arduino一個好用的計時器程式庫
改用 Timer.h 函式庫裡的 Timer.every() 也是一樣無作用, 我猜有可能這些函式庫是針對 Arduino 板子, 可能對 ESP8266 無效? 參考下面這篇 :
# ESP8266 Timer
看起來在 Arduino IDE 中要搞定 ESP8266 的 Timer 似乎很麻煩哩! 但我要的只是想固定一段時間 (例如 40 秒) 就從 NTP 伺服器取得最新時間來更新內部時鐘而已, 免得內部時鐘誤差越來越大. 其實若 TimerAlarms.h 不能用, 也可以用計數器, 例如下面範例每秒顯示一次內部時鐘, 但每 40 次迴圈 (即大約每 40 秒) 就讀取 NTP 時間來同步 :
測試 3 : 從 NTP 伺服器取得 UTC 網路時間更新內建 RTC (使用計數器)
#include <ESP8266WiFi.h>
#include <WiFiUdp.h>
#include <Time.h>
#include <TimeAlarms.h>
char* ssid="H30-L02-webbot"; //WiFi SSID
char* password="1234567890"; //WiFi password
unsigned int localPort=2390; //local port to listen for UDP packets
IPAddress timeServerIP; //time.nist.gov NTP server address
const char* ntpServerName="time.nist.gov"; //NTP Server host name
const int NTP_PACKET_SIZE=48; //NTP timestamp resides in the first 48 bytes of packets
byte packetBuffer[ NTP_PACKET_SIZE]; //buffer to hold incoming and outgoing packets
WiFiUDP udp; //UDP instance to let us send and receive packets over UDP
int count=0;
void setup() {
Serial.begin(115200);
Serial.println();
Serial.println();
//Connecting to a WiFi network
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.print("IP address: ");
Serial.println(WiFi.localIP());
//Start UDP
Serial.println("Starting UDP");
udp.begin(localPort);
Serial.print("Local port: ");
Serial.println(udp.localPort());
sync_clock(); //以 NTP 時間初始設定內部時鐘
}
void loop() {
String d=getDate();
String t=getTime();
String w=getWeek();
int Hm=hour()*100 + minute(); //時:分整數, 0~2359
Serial.print(d + " " + t + " " + w + " ");
Serial.println(Hm);
++count;
if (count >= 40) { //每 40 次迴圈與 NTP 同步一次
sync_clock();
count=0;
}
delay(1000);
}
void sync_clock() {
unsigned long GMT=getUnixTime();
if (GMT != 0) { //有得到 NTP 回應才更新 ESP8266 內建 RTC
setTime(GMT + 28800L); //以台灣時間更新內部時鐘
}
}
String getDate() {
String d=(String)year() + "-";
byte M=month();
if (M < 10) {d.concat('0');}
d.concat(M);
d.concat('-');
byte D=day();
if (D < 10) {d.concat('0');}
d.concat(D);
return d;
}
String getTime() {
String t="";
byte h=hour();
if (h < 10) {t.concat('0');}
t.concat(h);
t.concat(':');
byte m=minute();
if (m < 10) {t.concat('0');}
t.concat(m);
t.concat(':');
byte s=second();
if (s < 10) {t.concat('0');}
t.concat(s);
return t;
}
String getWeek() {
String w[]={"Sun","Mon","Tue","Wed","Thu","Fri","Sat"};
return w[weekday()-1];
}
String getDateTime() { //傳回日期時間
String dt=(String)year() + "-";
byte M=month();
if (M < 10) {dt.concat('0');}
dt.concat(M);
dt.concat('-');
byte d=day();
if (d < 10) {dt.concat('0');}
dt.concat(d);
dt.concat(' ');
byte h=hour();
if (h < 10) {dt.concat('0');}
dt.concat(h);
dt.concat(':');
byte m=minute();
if (m < 10) {dt.concat('0');}
dt.concat(m);
dt.concat(':');
byte s=second();
if (s < 10) {dt.concat('0');}
dt.concat(s);
return dt; //傳回格式如 2016-07-16 16:09:23 的日期時間字串
}
unsigned long getUnixTime() {
WiFi.hostByName(ntpServerName, timeServerIP); //get a random server from the pool
sendNTPpacket(timeServerIP); //send an NTP packet to a time server
delay(1000); // wait to see if a reply is available
int cb=udp.parsePacket(); //return bytes received
unsigned long unix_time=0; //預設傳回 0, 表示未收到 NTP 回應
if (!cb) {Serial.println("no packet yet");}
else { //received a packet, read the data from the buffer
Serial.print("packet received, length=");
Serial.println(cb); //=48
udp.read(packetBuffer, NTP_PACKET_SIZE); //read the packet into the buffer
//the timestamp starts at byte 40 of the received packet and is four bytes,
//or two words, long. First, esxtract the two words:
unsigned long highWord=word(packetBuffer[40], packetBuffer[41]);
unsigned long lowWord=word(packetBuffer[42], packetBuffer[43]);
//combine the four bytes (two words) into a long integer
//this is NTP time (seconds since Jan 1 1900):
unsigned long secsSince1900=highWord << 16 | lowWord;
Serial.print("Seconds since Jan 1 1900=" );
Serial.println(secsSince1900);
Serial.print("Unix time=");
//Unix time starts on Jan 1 1970. In seconds, that's 2208988800:
unix_time=secsSince1900 - 2208988800UL; //更新 unix_time
Serial.print(F("Unix time stamp (seconds since 1970-01-01)="));
Serial.println(unix_time); //print Unix time
}
return unix_time; //return seconds since 1970-01-01
}
unsigned long sendNTPpacket(IPAddress& address) {
Serial.println("sending NTP packet...");
// set all bytes in the buffer to 0
memset(packetBuffer, 0, NTP_PACKET_SIZE);
//Initialize values needed to form NTP request
//(see URL above for details on the packets)
packetBuffer[0]=0b11100011; // LI, Version, Mode
packetBuffer[1]=0; // Stratum, or type of clock
packetBuffer[2]=6; // Polling Interval
packetBuffer[3]=0xEC; // Peer Clock Precision
//8 bytes of zero for Root Delay & Root Dispersion
packetBuffer[12]=49;
packetBuffer[13]=0x4E;
packetBuffer[14]=49;
packetBuffer[15]=52;
// all NTP fields have been given values, now
// you can send a packet requesting a timestamp:
udp.beginPacket(address, 123); //NTP requests are to port 123
udp.write(packetBuffer, NTP_PACKET_SIZE);
udp.endPacket();
}
注意, 在 loop() 中的 Hm 變數是由 Hour 乘以 100 後加上 Minute, 這樣會組成 0~2359 的 "時分 整數", 可應用在物聯網插頭或自動澆水器等應用的時間判斷上, 例如如果要在早上六點與傍晚六點各澆水十分鐘, 則啟動馬達的 "時分整數" 位於 600~610 與 1800~1810 兩個區間內, 可用如下程式碼判斷 :
if ((Hm >= 600 && Hm <=610) || (Hm >= 1800 && Hm <=1810)) {
digitalWrite(motorPin, HIGH); //motor on
}
else {
digitalWrite(motorPin, LOW); //motor off
}
當然如果需要也可以精細到秒, 例如 :
HmS=int Hm=hour()*10000 + minute()*100 + second();
這個 HmS 的值區間為 0~235959. 要注意的是, 不管是 Hm 還是 HmS, 部分值域是無意義的, 例如 Hm 的 178 就是無意義的值, 因為 3 位數最大只到 159 (1 點 59 分), 接著就跳到 200 (2 點) 了.
序列埠監控視窗輸出訊息 :
Connecting to H30-L02-webbot
.................
WiFi connected
IP address: 192.168.43.163
Starting UDP
Local port: 2390
sending NTP packet...
packet received, length=48
Seconds since Jan 1 1900=3715498311
Unix time=Unix time stamp (seconds since 1970-01-01)=1506509511
2017-09-27 18:51:51 Wed 1851
2017-09-27 18:51:52 Wed 1851
2017-09-27 18:51:53 Wed 1851
2017-09-27 18:51:54 Wed 1851
2017-09-27 18:51:55 Wed 1851
2017-09-27 18:51:56 Wed 1851
2017-09-27 18:51:57 Wed 1851
2017-09-27 18:51:58 Wed 1851
2017-09-27 18:51:59 Wed 1851
2017-09-27 18:52:00 Wed 1852
2017-09-27 18:52:01 Wed 1852
2017-09-27 18:52:02 Wed 1852
2017-09-27 18:52:03 Wed 1852
2017-09-27 18:52:04 Wed 1852
2017-09-27 18:52:05 Wed 1852
2017-09-27 18:52:06 Wed 1852
2017-09-27 18:52:07 Wed 1852
2017-09-27 18:52:08 Wed 1852
2017-09-27 18:52:09 Wed 1852
2017-09-27 18:52:10 Wed 1852
2017-09-27 18:52:11 Wed 1852
2017-09-27 18:52:12 Wed 1852
2017-09-27 18:52:13 Wed 1852
2017-09-27 18:52:14 Wed 1852
2017-09-27 18:52:15 Wed 1852
2017-09-27 18:52:16 Wed 1852
2017-09-27 18:52:17 Wed 1852
2017-09-27 18:52:18 Wed 1852
2017-09-27 18:52:19 Wed 1852
2017-09-27 18:52:20 Wed 1852
2017-09-27 18:52:21 Wed 1852
2017-09-27 18:52:22 Wed 1852
2017-09-27 18:52:23 Wed 1852
2017-09-27 18:52:24 Wed 1852
2017-09-27 18:52:25 Wed 1852
2017-09-27 18:52:26 Wed 1852
2017-09-27 18:52:27 Wed 1852
2017-09-27 18:52:28 Wed 1852
2017-09-27 18:52:29 Wed 1852
2017-09-27 18:52:30 Wed 1852
sending NTP packet...
packet received, length=48
Seconds since Jan 1 1900=3715498352
Unix time=Unix time stamp (seconds since 1970-01-01)=1506509552
2017-09-27 18:52:33 Wed 1852
2017-09-27 18:52:34 Wed 1852
2017-09-27 18:52:35 Wed 1852
2017-09-27 18:52:36 Wed 1852
2017-09-27 18:52:37 Wed 1852
2017-09-27 18:52:38 Wed 1852
2017-09-27 18:52:39 Wed 1852
可見確實每 40 次迴圈就會與 NTP 伺服器同步一次, 事實上頻率不用這麼高, 跑了數百次到數千次再同步也不會有顯著誤差. 但是要注意, 下面這篇文件提到, ESP8266 內建 RTC 每 7 個小時 45 分會溢位, 因此 7 個小時內一定要再跟 NTP 伺服器同步一次, 參考 :
# http://docs.micropython.org/en/v1.8.7/esp8266/esp8266/general.html#real-time-clock
如果只是要取得 "時分" 或 "時分秒" 整數以判別目前時間, 其實也不需要匯入 Time.h 與 TimeAlarms.h, 直接將時戳做餘數與加法運算即可, 如下列兩個函數 :
int getHm(unsigned long T) {
return ((T % 86400L) / 3600)*100 + ((T % 3600) / 60);
}
int getHmS(unsigned long T) {
return ((T % 86400L) / 3600)*10000 + ((T % 3600) / 60)*100 + (T % 60);
}
使用時只要傳入時戳 T 呼叫 Hm() 或 HmS() 即可 :
unsigned long T=getUnixTime() + 28800L; //計算 GMT+8 時戳
if (Hm(T) >= 600 && Hm(T) <= 610) {
digitalWrite(motorPin, HIGH); //motor on
}
else {
digitalWrite(motorPin, LOW); //motor off
}
例如下列範例 :
測試 4 : 從 NTP 伺服器取得網路時間模擬控制馬達開關 (不使用時間函式庫)
#include <ESP8266WiFi.h>
#include <WiFiUdp.h>
char* ssid="H30-L02-webbot"; //WiFi SSID
char* password="a5572056"; //WiFi password
unsigned int localPort=2390; //local port to listen for UDP packets
IPAddress timeServerIP; //time.nist.gov NTP server address
const char* ntpServerName="time.nist.gov"; //NTP Server host name
const int NTP_PACKET_SIZE=48; //NTP timestamp resides in the first 48 bytes of packets
byte packetBuffer[ NTP_PACKET_SIZE]; //buffer to hold incoming and outgoing packets
WiFiUDP udp; //UDP instance to let us send and receive packets over UDP
const int motorPin=2; // GPIO2
void setup() {
pinMode(motorPin, OUTPUT);
digitalWrite(motorPin, LOW);
Serial.begin(115200);
Serial.println();
Serial.println();
//Connecting to a WiFi network
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.print("IP address: ");
Serial.println(WiFi.localIP());
//Start UDP
Serial.println("Starting UDP");
udp.begin(localPort);
Serial.print("Local port: ");
Serial.println(udp.localPort());
}
void loop() {
int Hm=getHm(getUnixTime() + 28800L); //取得台灣時間的 "時分" 整數
Serial.println(Hm);
if (Hm >= 1500 && Hm <= 1510) { //在 15:00 ~15:10 間啟動馬達
digitalWrite(motorPin, HIGH); //motor on
Serial.println("Motor is ON");
}
else {
digitalWrite(motorPin, LOW); //motor off
Serial.println("Motor is OFF");
}
delay(60000);
}
int getHm(unsigned long T) { //return 0~2359
return ((T % 86400L) / 3600)*100 + ((T % 3600) / 60);
}
int getHmS(unsigned long T) { //return 0~235959
return ((T % 86400L) / 3600)*10000 + ((T % 3600) / 60)*100 + (T % 60);
}
unsigned long getUnixTime() { //get GMT epoch
WiFi.hostByName(ntpServerName, timeServerIP); //get a random server from the pool
sendNTPpacket(timeServerIP); //send an NTP packet to a time server
delay(1000); // wait to see if a reply is available
int cb=udp.parsePacket(); //return bytes received
unsigned long unix_time=0;
if (!cb) {Serial.println("no packet yet");}
else { //received a packet, read the data from the buffer
Serial.print("packet received, length=");
Serial.println(cb); //=48
udp.read(packetBuffer, NTP_PACKET_SIZE); //read the packet into the buffer
//the timestamp starts at byte 40 of the received packet and is four bytes,
//or two words, long. First, esxtract the two words:
unsigned long highWord=word(packetBuffer[40], packetBuffer[41]);
unsigned long lowWord=word(packetBuffer[42], packetBuffer[43]);
//combine the four bytes (two words) into a long integer
//this is NTP time (seconds since Jan 1 1900):
unsigned long secsSince1900=highWord << 16 | lowWord;
Serial.print("Seconds since Jan 1 1900=" );
Serial.println(secsSince1900);
Serial.print("Unix time=");
//Unix time starts on Jan 1 1970. In seconds, that's 2208988800:
unix_time=secsSince1900 - 2208988800UL;
Serial.print(F("Unix time stamp (seconds since 1970-01-01)="));
Serial.println(unix_time); //print Unix time
}
return unix_time; //return seconds since 1970-01-01
}
unsigned long sendNTPpacket(IPAddress& address) {
Serial.println("sending NTP packet...");
// set all bytes in the buffer to 0
memset(packetBuffer, 0, NTP_PACKET_SIZE);
//Initialize values needed to form NTP request
//(see URL above for details on the packets)
packetBuffer[0]=0b11100011; // LI, Version, Mode
packetBuffer[1]=0; // Stratum, or type of clock
packetBuffer[2]=6; // Polling Interval
packetBuffer[3]=0xEC; // Peer Clock Precision
//8 bytes of zero for Root Delay & Root Dispersion
packetBuffer[12]=49;
packetBuffer[13]=0x4E;
packetBuffer[14]=49;
packetBuffer[15]=52;
// all NTP fields have been given values, now
// you can send a packet requesting a timestamp:
udp.beginPacket(address, 123); //NTP requests are to port 123
udp.write(packetBuffer, NTP_PACKET_SIZE);
udp.endPacket();
}
不過上面這個程式有風險, 因為它完全依靠 UDP 查詢到的時間來決定是否啟閉馬達, 而 UDP 協定是不保證傳輸品質的協定, 有時候收不到回應, 這時 getUnixTime() 會傳回 0. 比較保險的做法還是像測試 3 那樣依靠 ESP8266 內部的 RTC, 使用 Time.h 與 TimeAlarms.h 的 hour(), minute() 一定可以取得資料製作 "時分" 整數, 只要定期與 UDP 同步即可.
測試結果 :
Connecting to H30-L02-webbot
..
WiFi connected
IP address: 192.168.43.163
Starting UDP
Local port: 2390
sending NTP packet...
packet received, length=48
Seconds since Jan 1 1900=3715570098
Unix time=Unix time stamp (seconds since 1970-01-01)=1506581298
1448
Motor is OFF
....
....
sending NTP packet...
packet received, length=48
Seconds since Jan 1 1900=3715570680
Unix time=Unix time stamp (seconds since 1970-01-01)=1506581880
1458
Motor is OFF
sending NTP packet...
packet received, length=48
Seconds since Jan 1 1900=3715570741
Unix time=Unix time stamp (seconds since 1970-01-01)=1506581941
1459
Motor is OFF
sending NTP packet...
packet received, length=48
Seconds since Jan 1 1900=3715570803
Unix time=Unix time stamp (seconds since 1970-01-01)=1506582003
1500
Motor is ON
sending NTP packet...
packet received, length=48
Seconds since Jan 1 1900=3715570865
Unix time=Unix time stamp (seconds since 1970-01-01)=1506582065
1501
Motor is ON
....
....
Motor is ON
sending NTP packet...
packet received, length=48
Seconds since Jan 1 1900=3715571050
Unix time=Unix time stamp (seconds since 1970-01-01)=1506582250
1504
Motor is ON
sending NTP packet...
no packet yet
800
Motor is OFF (未取得 1505 回應, 導致馬達關閉)
sending NTP packet...
packet received, length=48
Seconds since Jan 1 1900=3715571173
Unix time=Unix time stamp (seconds since 1970-01-01)=1506582373
1506
Motor is ON
sending NTP packet...
packet received, length=48
Seconds since Jan 1 1900=3715571235
Unix time=Unix time stamp (seconds since 1970-01-01)=1506582435
1507
Motor is ON
sending NTP packet...
no packet yet
800
Motor is OFF (未取得 1508 回應, 導致馬達關閉)
sending NTP packet...
packet received, length=48
Seconds since Jan 1 1900=3715571358
Unix time=Unix time stamp (seconds since 1970-01-01)=1506582558
1509
Motor is ON
sending NTP packet...
packet received, length=48
Seconds since Jan 1 1900=3715571420
Unix time=Unix time stamp (seconds since 1970-01-01)=1506582620
1510
Motor is ON
sending NTP packet...
packet received, length=48
Seconds since Jan 1 1900=3715571481
Unix time=Unix time stamp (seconds since 1970-01-01)=1506582681
1511
Motor is OFF
unsigned long T=getUnixTime();
if (T != 0) {
int Hm=getHm(T + 28800L); //取得台灣時間的 "時分" 整數
Serial.println(Hm);
if (Hm >= 1500 && Hm <= 1510) { //在 15:00 ~15:10 間啟動馬達
digitalWrite(motorPin, HIGH); //motor on
Serial.println("Motor is ON");
}
else {
digitalWrite(motorPin, LOW); //motor off
Serial.println("Motor is OFF");
}
}
參考 :
# https://github.com/esp8266/Arduino/blob/master/libraries/ESP8266WiFi/src/WiFiUdp.h
# ESP8266 Timer0 and ISR
# http://twincati.blogspot.tw/2016/12/esp8266-timer.html
# 86Duino 程式語法參考
2017-09-28 補充 :
這幾天停下 C 語言來做 Arduino on ESP8266 實驗要暫停一下, 這些實驗是專為 512K Flash 的 ESP-01 而做的, 我想將其應用在小型終端控制器, 例如物聯網開關與自動灑水系統. 有空回來時要繼續做的項目如下 :
- HTTP 客戶端 (MySQL 資料庫, ThingSpeak, Twitter 控制 ...)
- WiFi 基地台連線設定
- Blynk on ESP8266 Standalone
1 則留言 :
void loop() {
String d=getDate();
String t=getTime();
String w=getWeek();
Serial.println(d + " " + t + " " + w);
Alarm.delay(10000);
}
你只要把ALARM 的內部計時器有做工作就會正常工作。
張貼留言