# 以 Arduino + ESP8266 物聯網模組重作 NTP 實驗 (一)
其實我在去年底用 AT 指令進行測試時就遇到此問題了, 一直無法從 NTP 伺服器取得正確的時間回應, 參考 :
# 關於用 Arduino+ESP8266 進行網路對時的問題
後來找到 WeeESP8266 這個函式庫, 利用它所提供的 UDP 函式簡潔地解決了這個問題, 成功地從 NTP 伺服器取得同步時間. 參考 :
# 使用 ITEAD 的 WeeESP8266 函式庫進行網路對時
但原先直接使用 AT 指令查詢失敗的問題並未解決. 這次製作完成 Arduino + ESP8266 物聯網模組, 由於使用了 AllAboutEE 所提供的簡潔 AT 指令傳送函式, 想說就趁這機會來徹底解決此問題吧, 只是如上面實驗一所見, 還是不行, 問題應該是處理 NTP 回應訊息的方式不正確.
我使用最近製作的 Arduino + ESP8266 物聯網模組來進行測試, 好處是此模組可帶到任何 Wifi 環境, 將開關切換到設定模式, 即可使用手機瀏覽器設定此模組要連線哪一個無線基地台, 設定好後再切回工作模式即可, 不用換環境就要改程式中的 SSID 設定, 參考 :
# 製作 Arduino Nano + ESP8266 物聯網模組
或者也可以用麵包板自行接線, 電路圖如下 :
今早檢視實驗一的程式, 發現 if (esp8266.find("<")) {} 區塊似乎沒運作, 亦即根本沒送出 request, 奇怪, 看起來沒問題啊! 乾脆把尋找 "<" 出現與 "+IPD,48" 出現的 if 拿掉, 送出 AT+CIPSEND=48 的長度通知後, 直接送出 48 bytes 的 UDP 要求封包, 然後用 while 迴圈去讀取全部的 NTP 回應訊息, 嘿, 有收到回應了. 我把回應訊息列印出來, 與之前使用 WeeESP8266 函式庫的成功回應比對, 發現這樣的處理方式下, NTP 回應的時間戳記並非在使用 WeeESP8266 函式庫所用的 byte 40~43 位置, 而是在 byte 101~104 的位置, 因為我是讀取 NTP 回應的完整訊息.
下面是三筆連續查詢的回應, 每排十個 byte, 粗體藍色的就是 NTP 回應的時戳位置, 即 byte 緩衝器索引 [101]~[104] :
E3 00 06 EC 00 00 00 00 00 00
00 00 31 4E 31 34 00 00 00 00
00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 0D 0A
53 45 4E 44 20 4F 4B 0D 0A 0D
0A 2B 49 50 44 2C 34 38 3A 24
04 06 E9 00 00 00 BB 00 95 22
59 52 D1 F0 F1 DA 71 66 0F 17
9F 0C 52 00 00 00 00 00 00 00
00 DB 07 90 DE 1D E2 2B B8 DB
07 90 DE 1D E4 1A DA 0D 0A 4F
4B 0D 0A
E3 00 06 EC 00 00 00 00 00 00
00 00 31 4E 31 34 00 00 00 00
00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 0D 0A
53 45 4E 44 20 4F 4B 0D 0A 0D
0A 2B 49 50 44 2C 34 38 3A 24
04 06 E9 00 00 00 BB 00 95 22
66 52 D1 F0 F1 DA 71 66 0F 17
9F 0C 52 00 00 00 00 00 00 00
00 DB 07 90 EB E1 04 65 E7 DB
07 90 EB E1 07 1D 6F 0D 0A 4F
4B 0D 0A
E3 00 06 EC 00 00 00 00 00 00
00 00 31 4E 31 34 00 00 00 00
00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 0D 0A
53 45 4E 44 20 4F 4B 0D 0A 0D
0A 2B 49 50 44 2C 34 38 3A 24
04 06 E9 00 00 00 BB 00 95 22
74 52 D1 F0 F1 DA 71 66 0F 17
9F 0C 52 00 00 00 00 00 00 00
00 DB 07 90 F9 9E F0 02 17 DB
07 90 F9 9E F1 E5 5B 0D 0A 4F
4B 0D 0A
我對照 ASCII 表查了其中的部分碼, 可見 ESP8266 回應 "SEND OK" 後跳兩行就回應 "+IPD,48: ", 如果從 + 起算的話, NTP 回應的時戳確實在索引 40~43, 如果接收全部回應的話, 則是在索引 101~104 :
E3 00 06 EC 00 00 00 00 00 00
00 00 31 4E 31 34 00 00 00 00
00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 0D 0A
53 45 4E 44 20 4F 4B 0D 0A 0D
S E N D SP O K CR LF CR
0A 2B 49 50 44 2C 34 38 3A 24
LF + I P D , 4 8 : $
02 06 ED 00 00 00 09 00 00 05
E4 75 36 C1 B9 DB 07 26 8D 43
9E EB 5D 00 00 00 00 00 00 00
00 DB 07 29 7B 21 82 5F 16 DB
07 29 7B 21 93 F1 95 0D 0A 4F
CR LF O
4B 0D 0A
K CR LF
由上可知, NTP 的全部回應訊息共有 124 bytes, 原先所認為的 48 bytes 事實上是從 "+IPD,48:" 的 "+" 起算的 (但傳送 request 時只要傳 48 bytes 沒錯). 所以前文實驗一末尾所查到的資料說要把序列埠緩衝器從預設的 64 bytes 放寬為 128 bytes 是對的, 我把它複製如下 :
# Updated comment for UdpNTPClient
"NOTE: The serial buffer size must be larger than 36 + packet size
In this example we use an UDP packet of 48 bytes so the buffer must be
at least 36+48=84 bytes that exceeds the default buffer size (64).
You must modify the serial buffer size to 128
For HardwareSerial modify _SS_MAX_RX_BUFF in
Arduino\hardware\arduino\avr\cores\arduino\SoftwareSerial.h
For SoftwareSerial modify _SS_MAX_RX_BUFF in
Arduino\hardware\arduino\avr\libraries\SoftwareSerial\SoftwareSerial.h
*/"
"NOTE: The serial buffer size must be larger than 36 + packet size
In this example we use an UDP packet of 48 bytes so the buffer must be
at least 36+48=84 bytes that exceeds the default buffer size (64).
You must modify the serial buffer size to 128
For HardwareSerial modify _SS_MAX_RX_BUFF in
Arduino\hardware\arduino\avr\cores\arduino\SoftwareSerial.h
For SoftwareSerial modify _SS_MAX_RX_BUFF in
Arduino\hardware\arduino\avr\libraries\SoftwareSerial\SoftwareSerial.h
*/"
因為我使用軟體串列埠與 ESP8266 溝通, 因此我修改了軟體串列埠的緩衝器大小, 放寬為 128 bytes. 因為軟體序列埠已經被納為 Arduino 內建函式庫, 要修改的軟體序列埠檔案 SoftwareSerial.h 位置如下 :
E:\arduino-1.6.6\hardware\arduino\avr\libraries\SoftwareSerial\SoftwareSerial.h
將此檔案中的 _SS_MAX_RX_BUFF 常數放大為 128 即可 :
#define _SS_MAX_RX_BUFF 128 // RX buffer size
E:\arduino-1.6.6\hardware\arduino\avr\libraries\SoftwareSerial\SoftwareSerial.h
將此檔案中的 _SS_MAX_RX_BUFF 常數放大為 128 即可 :
#define _SS_MAX_RX_BUFF 128 // RX buffer size
#include <SoftwareSerial.h>
#define DEBUG true
//system use please do not edit
SoftwareSerial esp8266(7,8); //(RX,TX)
const int SW_PIN=4; //Pin to switch configuration or working mode
const int MAX_PAGE_NAME_LEN=48; //buffer size
char buffer[MAX_PAGE_NAME_LEN + 1]; //store page_name/ssid/pwd
int mode; //store current mode(LOW=configuration, HIGH=working)
//-----application codes listed bellow-----
//Request for NTP time stamp (in the first 48 bytes)
byte packetBuffer[128]; //buffer : send & recv data to/from NTP
void setup() {
//system use please do not edit
Serial.begin(9600);
esp8266.begin(9600);
sendData("AT+RST\r\n",2000,DEBUG); // reset ESP8266
pinMode(SW_PIN, INPUT);
mode=digitalRead(SW_PIN);
sendData("AT+GMR\r\n",1000,DEBUG);
if (mode==LOW) { //setup mode : for wifi configuration
sendData("AT+CWMODE=3\r\n",1000,DEBUG); //configure as access point
sendData("AT+CIPMUX=1\r\n",1000,DEBUG); //enable multiple connections
sendData("AT+CIPSERVER=1,80\r\n",2000,DEBUG); //turn on server 80 port
}
else { //working mode : for running application
sendData("AT+CWMODE=1\r\n",1000,DEBUG); //configure as a station
delay(3000); //wait for wifi connection to get local ip
}
sendData("AT+CIFSR\r\n",1000,DEBUG); //get ip address
//-----application codes listed bellow-----
}
void loop() {
if (mode==LOW) {setupWifi();}
else { //-----application codes listed bellow-----
Serial.println("Sending request to NTP server ...");
GetTime();
delay(5000);
}
}
unsigned long GetTime() {
//Start UDP Rrequest to NTP Server 91.226.136.136, 82.209.243.241,192.5.41.40
sendData("AT+CIPSTART=\"UDP\",\"82.209.243.241\",123\r\n",5000,DEBUG);
memset(packetBuffer,0,128); //clear buffer
packetBuffer[0]=0xE3; //LI, Version, Mode
packetBuffer[1]=0x00; //Stratum, or type of clock
packetBuffer[2]=0x06; //Polling Interval
packetBuffer[3]=0xEC; //Peer Clock Precision
packetBuffer[12]=0x31; //reference ID (4 bytes)
packetBuffer[13]=0x4E;
packetBuffer[14]=0x31;
packetBuffer[15]=0x34;
//send request to NTP Server
sendData("AT+CIPSEND=48\r\n",1000,DEBUG); //send data length
for (byte i=0; i < 48; i++) {
esp8266.write(packetBuffer[i]);
delay(5);
}
//deal with NTP response
memset(packetBuffer,0,128); //clear buffer to store NTP response
Serial.println();
Serial.println("NTP server answered : ");
int i=0; //packet byte counter
while (esp8266.available() > 0) { //if receive NTP response : fall in loop
byte ch=esp8266.read(); //got NTP response, read one byte for each loop
if (i < 128) {packetBuffer[i]=ch;} //store received byte to packet buffer
//show receving bytes in hex
if (ch < 0x10) {Serial.print('0');} //prefix with '0' if byte value 0~9
Serial.print(ch, HEX);
Serial.print(' ');
if ((((i+1) % 10) == 0)) {Serial.println();} //newline if exceeds 10 bytes
delay(5); //wait 5ms for next incoming byte
i++; //increment packet byte counter
if ((i < 104) && (esp8266.available() == 0)) { //wainting if lags
//Response packets not enough but no response : wait 1.5 seconds
byte wcount=0; //waiting counter
while (esp8266.available() == 0) { //loop until timeout (1.5 seconds)
Serial.print("!"); //show ! means waiting for response packet
delay(100);
wcount += 1; //increment waiting counter
if (wcount >= 15) {break;} //waiting timeout : quit loop
}
}
}
Serial.println();
Serial.println();
Serial.print(i+1);
Serial.println(" bytes received"); // will be more than 48
//Show time stamp (locates from byte 101~104 of the response packet)
Serial.print("NTP time stamp packets (byte 101~104)=");
Serial.print(packetBuffer[101],HEX);
Serial.print(" ");
Serial.print(packetBuffer[102],HEX);
Serial.print(" ");
Serial.print(packetBuffer[103],HEX);
Serial.print(" ");
Serial.print(packetBuffer[104],HEX);
Serial.println();
//handling time packets (4 bytes long) : combine them into words
unsigned long highWord=word(packetBuffer[101],packetBuffer[102]);
unsigned long lowWord=word(packetBuffer[103],packetBuffer[104]);
//shift high word 16 bits left & OR with low word to form a double word
//the result is a NTP time stamp (seconds since Jan 1 1900):
unsigned long secsSince1900=highWord << 16 | lowWord;
Serial.print("NTP time stamp (seconds since 1900-01-01)=");
Serial.println(secsSince1900);
//convert NTP time stamp to Unix time stamp :
//Unix time starts on Jan 1 1970=2208988800 seconds since 1900-01-01
//subtract seventy years to get Unix time stamp
unsigned long epoch=secsSince1900 - 2208988800UL;
Serial.print("Unix time stamp (seconds since 1970-01-01)=");
Serial.println(epoch); //print Unix time
//Convert epoch to UTC/GMT (Greenwich Meridian) hour:minute:second
Serial.print("UTC time="); // UTC is the time at
Serial.print((epoch % 86400L) / 3600); //hour (86400 secs per day)
Serial.print(':');
if (((epoch % 3600) / 60) < 10) {Serial.print('0');}
Serial.print((epoch % 3600) / 60); //minute (3600 secs per minute)
Serial.print(':');
if ((epoch % 60) < 10 ) {Serial.print('0');}
Serial.println(epoch % 60); //second
Serial.println();
sendData("AT+CIPCLOSE\r\n",1000,DEBUG); //close session
}
void setupWifi() {
if (esp8266.available()) { // check if the esp is sending a message
if (esp8266.find("+IPD,")) {
delay(1000);
//esp8266 link response : +IPD,0,498:GET / HTTP/1.1
//retrieve connection ID from response (0~4, after "+IPD,")
int connectionId=esp8266.read()-48; //from ASCII to number
//subtract 48 because read() returns ASCII decimal value
//and in ASCII, "0" (the first decimal number) starts at 48
if (esp8266.find("GET /")) { //retrieve page name (router)
memset(buffer, 0, sizeof(buffer)); //clear buffer (all set to 0)
if (esp8266.readBytesUntil('/', buffer, sizeof(buffer))) {
if (strcmp(buffer, "update") == 0) { //update wifi
//"?ssid=aaa&pwd=bbb HTTP/1.1"
esp8266.find("?ssid="); //skip ssid token
memset(buffer, 0, sizeof(buffer)); //clear buffer (all set to 0)
esp8266.readBytesUntil('&', buffer, sizeof(buffer)); //retrieve ssid
String ssid=buffer;
esp8266.find("pwd="); //skip pwd token
memset(buffer, 0, sizeof(buffer)); //clear buffer (all set to 0)
esp8266.readBytesUntil(' ', buffer, sizeof(buffer)); //retrieve pwd
String pwd=buffer;
//configure as a station
String res=sendData("AT+CWJAP=\"" + ssid + "\",\"" + pwd + "\"\r\n",6000,DEBUG);
//show setup result
String webpage="<html>Wifi setup ";
if (res.indexOf("OK") != -1) {webpage += "OK!</html>";}
else {webpage += "Failed!</html>";}
String cipSend="AT+CIPSEND=";
cipSend += connectionId;
cipSend += ",";
cipSend +=webpage.length();
cipSend +="\r\n";
sendData(cipSend,1000,DEBUG);
sendData(webpage,2000,DEBUG);
String closeCommand = "AT+CIPCLOSE=";
closeCommand+=connectionId; // append connection id
closeCommand+="\r\n";
sendData(closeCommand,3000,DEBUG);
}
else { //show setup page
String webpage="<html><form method=get action='/update/'>SSID ";
webpage += "<input name=ssid type=text><br>";
String cipSend = "AT+CIPSEND=";
cipSend += connectionId;
cipSend += ",";
cipSend +=webpage.length();
cipSend +="\r\n";
sendData(cipSend,1000,DEBUG);
sendData(webpage,2000,DEBUG);
webpage="PWD <input name=pwd type=text> ";
cipSend = "AT+CIPSEND=";
cipSend += connectionId;
cipSend += ",";
cipSend +=webpage.length();
cipSend +="\r\n";
sendData(cipSend,1000,DEBUG);
sendData(webpage,2000,DEBUG);
webpage="<input type=submit value=Connect></form></html>";
cipSend = "AT+CIPSEND=";
cipSend += connectionId;
cipSend += ",";
cipSend +=webpage.length();
cipSend +="\r\n";
sendData(cipSend,1000,DEBUG);
sendData(webpage,2000,DEBUG);
String closeCommand = "AT+CIPCLOSE=";
closeCommand+=connectionId; // append connection id
closeCommand+="\r\n";
sendData(closeCommand,3000,DEBUG);
}
}
}
}
}
}
String sendData(String command, const int timeout, boolean debug) {
String response="";
esp8266.print(command); // send the read character to the esp8266
long int time=millis();
while ((time+timeout) > millis()) {
while(esp8266.available()) {
// The esp has data so display its output to the serial window
char c=esp8266.read(); // read the next character.
response += c;
}
}
if (debug) {Serial.print(response);}
return response;
}
此程式編譯後所占記憶體如下, 全域變數吃掉近六成的 SRAM :
草稿碼使用了 10,908 bytes (35%) 的程式存儲空間。最大值為 30,720 bytes。
全域變數使用了 1,220 bytes (59%) 動態記憶體,剩餘 828 bytes 的局部變數。最大值為 2,048 bytes 。
注意, 上面程式中為了避免 NTP 回應有 lag 導致 while 迴圈跑不出來, 我加入了一個等待 1.5 秒就 timeout 的機制, 當回應有 lag 時會印出驚嘆號, 但超過 1.5 秒仍無回應的話就跳出. 從下面序列埠監控視窗擷取的訊息可見, 真的偶而會有延遲 :
AT+RST
OK
bB�鑭b禔S��"� N�侒��S��
[Vendor:www.ai-thinker.com Version:0.9.2.4]
ready
AT+GMR
0018000902-AI03
OK
AT+CWMODE?
+CWMODE:1
OK
AT+CIPMUX?
+CIPMUX:0
OK
AT+CIFSR
192.168.2.110
OK
Sending request to NTP server ...
AT+CIPSTART="UDP","82.209.243.241",123
OK
AT+CIPSEND=48
>
NTP server answered :
E3 00 06 EC 00 00 00 00 00 00
00 00 31 4E 31 34 00 00 00 00
00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 0D 0A
53 45 4E 44 20 4F 4B 0D 0A !!!!!0D
0A 2B 49 50 44 2C 34 38 3A 24
04 06 E9 00 00 00 BB 00 95 22
59 52 D1 F0 F1 DA 71 66 0F 17
9F 0C 52 00 00 00 00 00 00 00
00 DB 07 90 DE 1D E2 2B B8 DB
07 90 DE 1D E4 1A DA 0D 0A 4F
4B 0D 0A
124 bytes received
NTP time stamp packets (byte 101~104)=DB 7 90 DE
NTP time stamp (seconds since 1900-01-01)=3674706142
Unix time stamp (seconds since 1970-01-01)=1465717342
UTC time=7:42:22
AT+CIPCLOSE
OK
Unlink
Sending request to NTP server ...
AT+CIPSTART="UDP","82.209.243.241",123
OK
AT+CIPSEND=48
>
NTP server answered :
E3 00 06 EC 00 00 00 00 00 00
00 00 31 4E 31 34 00 00 00 00
00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 0D 0A
53 45 4E 44 20 4F 4B 0D 0A !!!!!0D
0A 2B 49 50 44 2C 34 38 3A 24
04 06 E9 00 00 00 BB 00 95 22
66 52 D1 F0 F1 DA 71 66 0F 17
9F 0C 52 00 00 00 00 00 00 00
00 DB 07 90 EB E1 04 65 E7 DB
07 90 EB E1 07 1D 6F 0D 0A 4F
4B 0D 0A
124 bytes received
NTP time stamp packets (byte 101~104)=DB 7 90 EB
NTP time stamp (seconds since 1900-01-01)=3674706155
Unix time stamp (seconds since 1970-01-01)=1465717355
UTC time=7:42:35
AT+CIPCLOSE
OK
Unlink
Sending request to NTP server ...
AT+CIPSTART="UDP","82.209.243.241",123
OK
AT+CIPSEND=48
>
NTP server answered :
E3 00 06 EC 00 00 00 00 00 00
00 00 31 4E 31 34 00 00 00 00
00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 0D 0A
53 45 4E 44 20 4F 4B 0D 0A !0D
0A 2B 49 50 44 2C 34 38 3A 24
04 06 E9 00 00 00 BB 00 95 22
74 52 D1 F0 F1 DA 71 66 0F 17
9F 0C 52 00 00 00 00 00 00 00
00 DB 07 90 F9 9E F0 02 17 DB
07 90 F9 9E F1 E5 5B 0D 0A 4F
4B 0D 0A
124 bytes received
NTP time stamp packets (byte 101~104)=DB 7 90 F9
NTP time stamp (seconds since 1900-01-01)=3674706169
Unix time stamp (seconds since 1970-01-01)=1465717369
UTC time=7:42:49
AT+CIPCLOSE
OK
Unlink
.....
參考資料 :
# serial.print or serial.write?
2016-07-11 補充 :
如果要取得中原標準時間, 那麼 UTC 時間要加 8 小時, 程式改成如下 :
#include <SoftwareSerial.h>
#define DEBUG true
//-----application include & def listed HERE-----
//system use please do not edit
SoftwareSerial esp8266(7,8); //(RX,TX)
const int SW_PIN=4; //Pin to switch configuration or working mode
const int MAX_PAGE_NAME_LEN=48; //buffer size
char buffer[MAX_PAGE_NAME_LEN + 1]; //store page_name/ssid/pwd
int mode; //store current mode(LOW=wifi setup, HIGH=working)
//-----application global variables listed HERE-----
//Request for NTP time stamp (in the first 48 bytes)
byte packetBuffer[128]; //buffer : send & recv data to/from NTP
void setup() {
//system use please do not edit
Serial.begin(9600);
esp8266.begin(9600);
sendData("AT+RST\r\n",2000,DEBUG); // reset ESP8266
pinMode(SW_PIN, INPUT);
mode=digitalRead(SW_PIN);
sendData("AT+GMR\r\n",1000,DEBUG);
if (mode==LOW) { //setup mode : for wifi configuration
sendData("AT+CWMODE=3\r\n",1000,DEBUG); //configure as access point
sendData("AT+CIPMUX=1\r\n",1000,DEBUG); //enable multiple connections
sendData("AT+CIPSERVER=1,80\r\n",2000,DEBUG); //turn on server 80 port
}
else { //working mode : for running application
sendData("AT+CWMODE=1\r\n",1000,DEBUG); //configure as a station
delay(3000); //wait for wifi connection to get local ip
}
sendData("AT+CIFSR\r\n",1000,DEBUG); //get ip address
//-----application setup codes listed HERE-----
}
void loop() {
if (mode==LOW) {setupWifi();} //system use please do not edit
else { //-----application loop codes listed HERE-----
//show ESP8266 response char to serial monitor window
//if (esp8266.available()) {Serial.write(esp8266.read());}
//send char to ESP8266 if got char from serial monitor window
//if (Serial.available()) {esp8266.write(Serial.read());}
Serial.println("Sending request to NTP server ...");
Serial.println(getCST());
delay(5000);
}
}
String getCST() {
//Start UDP Rrequest to NTP Server 91.226.136.136, 82.209.243.241,192.5.41.40
sendData("AT+CIPSTART=\"UDP\",\"91.226.136.136\",123\r\n",5000,DEBUG);
memset(packetBuffer,0,128); //clear buffer
packetBuffer[0]=0xE3; //LI, Version, Mode
packetBuffer[1]=0x00; //Stratum, or type of clock
packetBuffer[2]=0x06; //Polling Interval
packetBuffer[3]=0xEC; //Peer Clock Precision
packetBuffer[12]=0x31; //reference ID (4 bytes)
packetBuffer[13]=0x4E;
packetBuffer[14]=0x31;
packetBuffer[15]=0x34;
//send request to NTP Server
sendData("AT+CIPSEND=48\r\n",1000,DEBUG); //send data length
for (byte i=0; i < 48; i++) {
esp8266.write(packetBuffer[i]);
delay(5);
}
//deal with NTP response
memset(packetBuffer,0,128); //clear buffer to store NTP response
Serial.println();
Serial.println("NTP server answered : ");
int i=0; //packet byte counter
while (esp8266.available() > 0) { //if receive NTP response : fall in loop
byte ch=esp8266.read(); //got NTP response, read one byte for each loop
if (i < 128) {packetBuffer[i]=ch;} //store received byte to packet buffer
//show receving bytes in hex
if (ch < 0x10) {Serial.print('0');} //prefix with '0' if byte value 0~9
Serial.print(ch, HEX);
Serial.print(' ');
if ((((i+1) % 10) == 0)) {Serial.println();} //newline if exceeds 10 bytes
delay(5); //wait 5ms for next incoming byte
i++; //increment packet byte counter
if ((i < 104) && (esp8266.available() == 0)) { //wainting if lags
//Response packets not enough but no response : wait 1.5 seconds
byte wcount=0; //waiting counter
while (esp8266.available() == 0) { //loop until timeout (1.5 seconds)
Serial.print("!"); //show ! means waiting for response packet
delay(100);
wcount += 1; //increment waiting counter
if (wcount >= 15) {break;} //waiting timeout : quit loop
}
}
}
Serial.println();
Serial.println();
Serial.print(i+1);
Serial.println(" bytes received"); // will be more than 48
//Show time stamp (locates from byte 101~104 of the response packet)
Serial.print("NTP time stamp packets (byte 101~104)=");
Serial.print(packetBuffer[101],HEX);
Serial.print(" ");
Serial.print(packetBuffer[102],HEX);
Serial.print(" ");
Serial.print(packetBuffer[103],HEX);
Serial.print(" ");
Serial.print(packetBuffer[104],HEX);
Serial.println();
//handling time packets (4 bytes long) : combine them into words
unsigned long highWord=word(packetBuffer[101],packetBuffer[102]);
unsigned long lowWord=word(packetBuffer[103],packetBuffer[104]);
//shift high word 16 bits left & OR with low word to form a double word
//the result is a NTP time stamp (seconds since Jan 1 1900):
unsigned long secsSince1900=highWord << 16 | lowWord;
Serial.print("NTP time stamp (seconds since 1900-01-01)=");
Serial.println(secsSince1900);
//convert NTP time stamp to Unix time stamp :
//Unix time starts on Jan 1 1970=2208988800 seconds since 1900-01-01
//subtract seventy years to get Unix time stamp
unsigned long epoch=secsSince1900 - 2208988800UL;
Serial.print("Unix time stamp (seconds since 1970-01-01)=");
Serial.println(epoch); //print Unix time
//Convert epoch to UTC/GMT (Greenwich Meridian) hour:minute:second
String cst="";
byte hour=(epoch % 86400L) / 3600 + 8; //hour (86400 secs per day)
if (hour > 24) {hour -= 24;}
if (hour < 10) {cst += "0";} //prefix with "0" if single digit
cst.concat(hour);
cst.concat(":");
byte min=(epoch % 3600) / 60; //minute (3600 secs per minute)
if (min < 10) {cst.concat("0");} //prefix with "0" if single digit
cst.concat(min);
cst.concat(":");
byte sec=epoch % 60; //second
if (sec < 10) {cst.concat("0");} //prefix with "0" if single digit
cst.concat(sec);
sendData("AT+CIPCLOSE\r\n",1000,DEBUG); //close session
return cst;
}
void setupWifi() {
if (esp8266.available()) { // check if the esp is sending a message
if (esp8266.find("+IPD,")) {
delay(1000);
//esp8266 link response : +IPD,0,498:GET / HTTP/1.1
//retrieve connection ID from response (0~4, after "+IPD,")
int connectionId=esp8266.read()-48; //from ASCII to number
//subtract 48 because read() returns ASCII decimal value
//and in ASCII, "0" (the first decimal number) starts at 48
if (esp8266.find("GET /")) { //retrieve page name (router)
memset(buffer, 0, sizeof(buffer)); //clear buffer (all set to 0)
if (esp8266.readBytesUntil('/', buffer, sizeof(buffer))) {
if (strcmp(buffer, "update") == 0) { //update wifi
//"?ssid=aaa&pwd=bbb HTTP/1.1"
esp8266.find("?ssid="); //skip ssid token
memset(buffer, 0, sizeof(buffer)); //clear buffer (all set to 0)
esp8266.readBytesUntil('&', buffer, sizeof(buffer)); //retrieve ssid
String ssid=buffer;
esp8266.find("pwd="); //skip pwd token
memset(buffer, 0, sizeof(buffer)); //clear buffer (all set to 0)
esp8266.readBytesUntil(' ', buffer, sizeof(buffer)); //retrieve pwd
String pwd=buffer;
//configure as a station
String res=sendData("AT+CWJAP=\"" + ssid + "\",\"" + pwd + "\"\r\n",6000,DEBUG);
//show setup result
String webpage="<html>Wifi setup ";
if (res.indexOf("OK") != -1) {webpage += "OK!</html>";}
else {webpage += "Failed!</html>";}
String cipSend="AT+CIPSEND=";
cipSend += connectionId;
cipSend += ",";
cipSend +=webpage.length();
cipSend +="\r\n";
sendData(cipSend,1000,DEBUG);
sendData(webpage,2000,DEBUG);
String closeCommand = "AT+CIPCLOSE=";
closeCommand+=connectionId; // append connection id
closeCommand+="\r\n";
sendData(closeCommand,3000,DEBUG);
}
else { //show setup page
String webpage="<html><form method=get action='/update/'>SSID ";
webpage += "<input name=ssid type=text><br>";
String cipSend = "AT+CIPSEND=";
cipSend += connectionId;
cipSend += ",";
cipSend +=webpage.length();
cipSend +="\r\n";
sendData(cipSend,1000,DEBUG);
sendData(webpage,2000,DEBUG);
webpage="PWD <input name=pwd type=text> ";
cipSend = "AT+CIPSEND=";
cipSend += connectionId;
cipSend += ",";
cipSend +=webpage.length();
cipSend +="\r\n";
sendData(cipSend,1000,DEBUG);
sendData(webpage,2000,DEBUG);
webpage="<input type=submit value=Connect></form></html>";
cipSend = "AT+CIPSEND=";
cipSend += connectionId;
cipSend += ",";
cipSend +=webpage.length();
cipSend +="\r\n";
sendData(cipSend,1000,DEBUG);
sendData(webpage,2000,DEBUG);
String closeCommand = "AT+CIPCLOSE=";
closeCommand+=connectionId; // append connection id
closeCommand+="\r\n";
sendData(closeCommand,3000,DEBUG);
}
}
}
}
}
}
String sendData(String command, const int timeout, boolean debug) {
String response="";
esp8266.print(command); // send the read character to the esp8266
long int time=millis();
while ((time+timeout) > millis()) {
while(esp8266.available()) {
// The esp has data so display its output to the serial window
char c=esp8266.read(); // read the next character.
response += c;
}
}
if (debug) {Serial.print(response);}
return response;
}
序列埠監控視窗擷取訊息如下 :
AT+RST
OK
bB�鑭b禔S��"� B�侒��餾�
[System Ready, Vendor:www.ai-thinker.com]
AT+GMR
0018000902
OK
AT+CWMODE=1
no change
AT+CIFSR
192.168.43.111
OK
Sending request to NTP server ...
AT+CIPSTART="UDP","91.226.136.136",123
OK
AT+CIPSEND=48
>
NTP server answered :
E3 00 06 EC 00 00 00 00 00 00
00 00 31 4E 31 34 00 00 00 00
00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 0D 0A
53 45 4E 44 20 4F 4B 0D 0A !!!!!!!!!!!0D
0A 2B 49 50 44 2C 34 38 3A 24
02 06 ED 00 00 00 4D 00 00 05
03 79 F9 80 85 DB 2E 2D 7B 95
70 BF 8E 00 00 00 00 00 00 00
00 DB 2E 2F 2C 40 4B 86 AF DB
2E 2F 2C 40 53 25 07 0D 0A 4F
4B 0D 0A
124 bytes received
NTP time stamp packets (byte 101~104)=DB 2E 2F 2C
NTP time stamp (seconds since 1900-01-01)=3677237036
Unix time stamp (seconds since 1970-01-01)=1468248236
AT+CIPCLOSE
OK
Unlink
22:43:56
再次提醒, 上面的程式要能順利執行的話, 務必將序列埠接收緩衝器放大到至少 128 才行.
關於 NTP 回應訊息處理已有新方法, 參見 :
# 再探 NTP 協定
3 則留言 :
您好請問要怎麼用手機設定esp8266的ssid跟密碼呢?
請參考這篇 :
AllAboutEE 的 ESP8266 伺服器測試 (一)
http://yhhuang1966.blogspot.tw/2016/05/allaboutee-esp8266.html
AT+RST
OK
WIFI DISCONNECT
bBֆQR⸮⸮⸮ȤSN⸮ȤRN⸮H⸮⸮O⸮⸮b(D⸮⸮aH⸮⸮⸮Z⸮H⸮HH⸮⸮⸮⸮5
AT+GMR
AT version:1.1.0.0(May 11 2016 18:09:56)
SDK version:1.5.4(baaeaebb)
compile time:Feb 24 2017 10:13:27
OK
WIFI CONNECTED
AT+CWMODE=3
OK
AT+CIPMUX=1
OK
AT+CIPSERVER=1,80
OK
WIFI GOT IP
AT+CIFSR
+CIFSR:APIP,"192.168.4.1"
+CIFSR:APMAC,"a2:20:a6:21:b7:c8"
+CIFSR:STAIP,"192.168.1.243"
+CIFSR:STAMAC,"a0:20:a6:21:b7:c8"
OK
請問我顯示這樣就停住了是發生什麼事情
張貼留言