# Topic: Arduino ESP8266 and NTP (Read 13317 times)
# http://www.foxhollow.ca/nodemgr/?info
只要對這些網址發出 HTTP 要求, 就能取得包括日期, 時間, 星期等訊息, 常用的連結如下 :
# http://www.foxhollow.ca/nodemgr/?date (日期 2016-Jun-20)
# http://www.foxhollow.ca/nodemgr/?time (12小時制時間 08:44:08pm)
# http://www.foxhollow.ca/nodemgr/?time24 (24小時制時間 20:44:41)
# http://www.foxhollow.ca/nodemgr/?Day (星期 Mon)
# http://www.foxhollow.ca/nodemgr/?Year (年 2016)
# http://www.foxhollow.ca/nodemgr/?month (月 06)
# http://www.foxhollow.ca/nodemgr/?day (日 20)
# http://www.foxhollow.ca/nodemgr/?Hour (24小時制時)
注意, 由於 Mark Brawell 的伺服器位於加拿大東部 (魁北克省), 因此回傳的時間訊息為美東時間, 換算成台北時間必須加 12 小時, 日期則需加 1 天. 星期幾比較簡單, 取得英文簡寫後用陣列轉換即可. 但時間與日期部分在跨年或跨月時就比較麻煩, 因為不只要換算時, 連日, 月, 年都要同步換算. 不如直接取得自 1970/1/1 以來的 Unix 時戳 (GMT 時間), 再加上 28800 秒 (即 8 小時) 後得到台北時間的時戳來換算比較簡單 :
# http://www.foxhollow.ca/nodemgr/?unixtime (Unix 時戳)
下面就使用上週製作好的 Arduino Nano + ESP8266 物聯網模組來測試一下以 TCP 協定從 Mark Bradwell 的網頁伺服器擷取時間. 關於使用 TCP/IP 抓取網頁的方法, 參考 :
# 用 ESP8266 的 TCP/IP 連線抓取網頁
關於從 NTP 伺服器取得時間的方法, 參考 :
# 以 Arduino + ESP8266 物聯網模組重作 NTP 實驗 (三)
本次實驗的程式如下 :
#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-----
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 HTTP request to www.foxhollow.ca web server ...");
Serial.println(getCST());
}
}
String getCST() {
sendData("AT+CIPSTART=\"TCP\",\"www.foxhollow.ca\",80\r\n",1000,DEBUG);
sendData("AT+CIPSEND=24\r\n",1000,DEBUG); //24 bytes include \r\n
String res=sendData("GET /nodemgr/?unixtime\r\n",3000,DEBUG);
res.replace("\r\n",""); //remove all line terminator for getting time stamp
//retrieve time stamp (between ":" and "OK")
//+IPD,10:1467029520
//OK
String t=res.substring(res.indexOf(":") + 1,res.lastIndexOf("OK"));
//Serial.println(t);
char buf[10]; //buffer to store unixtime string (for strtol)
//copy char in string to char array
for (byte i=0; i<t.length(); i++) {buf[i]=t.charAt(i);}
char *eptr; //end pointer used in strtol()
unsigned long epoch=strtol(buf, &eptr, 10); //convert string to long
Serial.println("GMT time stamp=" + epoch);
unsigned long c=epoch + 28800; //convert to CST time stamp (GMT+8hr)
//Convert CST (Taipei time) time stamp to hour:minute:second
String cst="";
byte hour=(c % 86400L) / 3600; //hour (86400 secs per day)
if (hour < 10) {cst += "0";} //prefix with "0" if single digit
cst.concat(hour);
cst.concat(":");
byte min=(c % 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=c % 60; //second
if (sec < 10) {cst.concat("0");} //prefix with "0" if single digit
cst.concat(sec);
res=sendData("AT+CIPCLOSE\r\n",5000,DEBUG); //close session
if (res.indexOf("OK") != -1) {return cst;}
else {return "00:00:00";}
}
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;
}
在此程式中, 首先我們對 ESP8266 下達如下 AT 指令以建立一條 TCP 連線 :
AT+CIPSTART="TCP", "www.foxhollow.ca", 80
然後再用下列 AT 指令告訴 ESP8266, 我們要傳送的資料長度含尾端跳行共 24 bytes :
AT+CIPSEND=24
接著傳送 HTTP 內容, 也就是 GET 方法與要存取之資源路徑 :
GET /nodemgr/?unixtime
內容只有 22 個字元, 但要加上尾巴的 \r\n 兩個跳行字元, 所以上面 CIPSEND 要設為 24. 然後利用字串的 substring() 函數擷取 Unix 時戳數據, 先將回應訊息中的跳行全數去除後, 抓取 ":" 與 "OK" 中間的字串即得. 再用迴圈將時戳字串複製到字元陣列中, 以便利用 strtol() 函數將字元陣列轉換成 long 型態整數, 這樣才能進一步換算成時間.
最後必須用下列指令釋放 TCP/IP 連線 :
AT+CIPCLOSE
上面程式編譯後記憶體耗用情形如下 :
草稿碼使用了 12,080 bytes (39%) 的程式存儲空間。最大值為 30,720 bytes。
全域變數使用了 924 bytes (45%) 的動態記憶體,剩餘 1,124 bytes 供局部變數。最大值為 2,048 bytes 。
可見比之前用 NTP 要節省 (分別是 43% 與 58%), 全域變數省最多.
下面是序列埠監控視窗擷取的輸出訊息 :
草稿碼使用了 12,080 bytes (39%) 的程式存儲空間。最大值為 30,720 bytes。
全域變數使用了 924 bytes (45%) 的動態記憶體,剩餘 1,124 bytes 供局部變數。最大值為 2,048 bytes 。
可見比之前用 NTP 要節省 (分別是 43% 與 58%), 全域變數省最多.
下面是序列埠監控視窗擷取的輸出訊息 :
AT+RST
OK
bB�鑭b禔S��"兀L�侒��餾�
[System Ready, Vendor:www.ai-thinker.com]
AT+GMR
0018000902
OK
AT+CWMODE=1
no change
AT+CIFSR
192.168.2.103
OK
Sending HTTP request to www.foxhollow.ca web server ...
AT+CIPSTART="TCP","www.foxhollow.ca",80
OK
Linked
AT+CIPSEND=24
> GET /nodemgr/?unixtime
SEND OK
+IPD,10:1467029879
OK
AT+CIPCLOSE
OK
Unlink
20:17:59
Sending HTTP request to www.foxhollow.ca web server ...
AT+CIPSTART="TCP","www.foxhollow.ca",80
OK
Linked
AT+CIPSEND=24
> GET /nodemgr/?unixtime
SEND OK
+IPD,10:1467029889
OK
� �
AT+CIPCLOSE
OK
Unlink
20:18:09
Sending HTTP request to www.foxhollow.ca web server ...
AT+CIPSTART="TCP","www.foxhollow.ca",80
OK
Linked
AT+CIPSEND=24
> GET /nodemgr/?unixtime
SEND OK
+IPD,10:1467029899
OK
? U
n
`
�
nan
AT+CIPCLOSE
OK
Unlink
20:18:19
除了使用古老的 NTP 協定外, 看起來用 TCP 協定抓時間比較簡單, 只要做字串處理即可.
參考資料 :
# C Language: strtoll function
# C library function - strcpy()
# Convert string to long long C?
# How to convert Unix TimeStamp into day:month:date:year format in C?
# C library function - strftime()
# [Arduino] Timestamp function
# Converting UNIX timestamp to seconds, minutes, hours, days, weeks, month, years, decades ago
# Arduino Time library
2016-06-28 補充 :
長時間觀察結果, 發現此種做法效果不佳, 該伺服器似乎不太能穩定回應要求, 還是用 NTP 為宜.
沒有留言:
張貼留言