2016年6月12日 星期日

以 Arduino + ESP8266 物聯網模組重作 NTP 實驗 (二)

今年晚到的梅雨還真會下, 從端午下到現在已四日矣. 今天還是陣雨不斷, 早上九點風雨甚大, 沒上市集採買 (因端午那天買的菜還夠). 因此早上就在家繼續研究 NTP 時間同步的問題, 它已困住我三天囉! 參考前文 :

# 以 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
*/"

因為我使用軟體串列埠與 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

找出關鍵訊息的正確位置後, 我修改之前的程式再測試, 弄到近中午時終於搞定了!  完整可用的程式如下 :

#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 才行.

2016-08-05 補充 :

關於 NTP 回應訊息處理已有新方法, 參見 :

再探 NTP 協定


3 則留言 :

Unknown 提到...

您好請問要怎麼用手機設定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


請問我顯示這樣就停住了是發生什麼事情