owned this note
owned this note
Published
Linked with GitHub
###### tags: `blogger` date: `2018.02`
:::danger
本文章為舊版Blogger內容轉移,目前暫停維護
:::
# 使用Nodemcu連接NTP伺服器更新內部時鐘並製成html網頁
<p style="text-align:center;"><img src="https://1.bp.blogspot.com/-Hn3xZGtHyj4/YP5wZkDs-UI/AAAAAAAAWUk/FQqzR1i7OHkjpwf0okZ5lj5EkomvmEztQCLcBGAsYHQ/s320/UC0Korb.png" width="50%"></p>
## 一.使用器材、前情提要
* 使用器材:Arduino IDE、Nodemcu
* 關於IDE的設定可以參考佑來了的教學影片
[Arduino教學- WiFi無線入門](https://www.youtube.com/watch?v=q-14MtNWltg)
* 本文章以範例檔`WifiWebServer`的架構改寫並參考了小狐狸事務所的教學文章[從 NTP 伺服器取得網路時間](http://yhhuang1966.blogspot.com/2017/09/arduino-ide-esp8266-ntp.html?m=1)
## 二.程式碼部分
* 函式庫請至GitHub下載最新版本即可
* 如有註解不當或缺失歡迎留言更正
**宣告部分**
```c++=
#include <TimeLib.h>
#include <ESP8266WiFi.h>
#include <WiFiUdp.h>
#define NTP_PACKET_SIZE 48
#define update_delay 70
#define ntp_correct 2
//緩存區大小 訊息的前48個字節為NTP時間戳記
//每計數一次之等待秒數
//NTP延遲校正(單位s)
const char* ssid="Home Wifi";
const char* password="12345678";
const char* ntpServerName="time.nist.gov";
//Wifi SSID
//Wifi PassWord
//NTP 伺服器名稱
byte packetBuffer[NTP_PACKET_SIZE];
int update_count = 99;
String http_sent ="";
String http_get ="";
String w[]={"","Sun","Mon","Tue","Wed","Thu","Fri","Sat"};
unsigned long GMT = 2208988800;
//宣告緩存區
//更新時間用計數器 每100次loop更新一次時間
//http傳送字串
//http接收字串
//設定代號[註1]
//接收到之時間
IPAddress ntpServerIP;
WiFiUDP udp;
WiFiServer server(80);
//宣告一個IPAddress物件來存放NTP伺服器的IP
//宣告一個UDP物件來傳送和接收封包
//宣告一個伺服器來接收埠上的訊息
```
**函式宣告**
```c++=
unsigned long getUnixTime() {//取得NTP時間
WiFi.hostByName(ntpServerName, ntpServerIP); //獲取一個隨機的伺服器
Serial.print("Sending NTP packet...");
memset(packetBuffer, 0, NTP_PACKET_SIZE);
//將緩存區每個byte設為0(清理緩衝區)
//初始化請求所需的值
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;
udp.beginPacket(ntpServerIP, 123); //埠123為網路時間協定通訊埠
udp.write(packetBuffer, NTP_PACKET_SIZE); //向NTP伺服器發送請求
udp.endPacket();
//NTP packet END
delay(1500); //等待
int cb=udp.parsePacket(); //回傳接收到之位元數
unsigned long unix_time=0; //預設傳回 0, 表示未收到 NTP 回應
if (cb>0) {
udp.read(packetBuffer, NTP_PACKET_SIZE);
unsigned long highWord=word(packetBuffer[40], packetBuffer[41]);
//將封包讀入緩存區 時間戳從接收到的封包的字節40開始
unsigned long lowWord=word(packetBuffer[42], packetBuffer[43]);
//是四個字節或兩個字長
unsigned long secsSince1900=highWord << 16 | lowWord;
//將四個字節組合成一個長整數 NTP時間(1900年1月1日以來的秒數)
unix_time=secsSince1900 - 2208988800UL;
//更新 unix_time(減掉70年的秒數 即1970年1月1日以來的秒數)
}
return unix_time; //回傳1970年1月1日以來的秒數
}
```
**初始化**
```c++=
void setup() {
Serial.begin(115200);
Serial.println("\nConnecting to ");
Serial.println(ssid);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) { //等待連線
delay(500);
Serial.print(".");
}
Serial.print("\nWiFi connected\nIP address: ");
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
Serial.println("Starting UDP");
udp.begin(2390); //本地使用埠2390監聽UDP數據包
Serial.print("Local port: ");
Serial.println(udp.localPort());
server.begin();
Serial.println("Web server started");
pinMode(LED_BUILTIN, OUTPUT);
}
```
**迴圈部分**
```c++=
void loop() {
if (WiFi.status() != WL_CONNECTED) { //檢查連線
delay(2000);
return;
}
WiFiClient client = server.available();
if (!client) { //未接收到http訊息要做的事
update_count+=1;
if(update_count==100){update_count=0;}
//計數達100次向伺服器獲取時間並歸零計數器
else{
delay(update_delay);
return;
}
digitalWrite(LED_BUILTIN, LOW); //led on[註2]
GMT=getUnixTime();
if(GMT!=0){ //檢查是否正確獲取時間
GMT=GMT + 28800 + ntp_correct; //GMT+8及延遲校正
Serial.print("Successed\nUnix time=" );
Serial.println(GMT);
setTime(GMT); //以NTP時間更新內部時鐘
Serial.print((String)year() + "-" + (String)month() +
"-" + (String)day()+ " ");
Serial.print(w[weekday()] + " ");
Serial.println((String)hour() + ":" + (String)minute() +
":" + (String)second());
digitalWrite(LED_BUILTIN, HIGH);//led off
}else{
Serial.println("Failed");
}
delay(100);
return;
}
while(!client.available()){
delay(1);
} //接收到資料要做的事
String http_get = client.readStringUntil('\r');
Serial.println(http_get);
client.flush();
client.print("HTTP/1.1 200 OK\r\nContent-Type: text/
html\r\n\r\n<!DOCTYPE HTML>\r\n<html>\r\n"
+ (String)year() + "-" + (String)month() + "-"
+ (String)day() + " " + w[weekday()] + " "
+ (String)hour() + ":" + (String)minute() + ":"
+ (String)second() + "< /html >\n");
delay(10);
}
```
```
[註1]星期日為1依此類推
[註2]Nodemcu板上之LED燈為 High時滅燈 LOW時亮燈
```
**實際執行結果如下**
``` c
Connecting to
Home Wifi
...
WiFi connected
IP address: IP address: 192.168.50.147
Starting UDP
Local port: 2390
Web server started
Sending NTP packet...Successed
Unix time=1518909722
2018-2-17 Sat 23:22:2
Sending NTP packet...Successed
Unix time=1518909731
2018-2-17 Sat 23:22:11
Sending NTP packet...Failed
```
## 三.結語
* (本項待確認)~~ESP8266 內建 RTC 每 7 個小時 45 分會超出型態範圍,因此 7 個小時45分內一定要再跟 NTP 伺服器同步一次確保時間的準確性~~
* 在NTP取得時間時有時會失敗,不知為何(等待不夠久?)
* 在經過多次測試後發現按下Nodemcu板上方的Flash按鈕後有明顯改善可能是記憶體的問題(不確定)
* 如果有先進知道可能的原因請不吝留言補充
==再次感謝您的閱讀 ❤❤❤==