透過WiFi,傳送UDP封包,向NTP伺服器取得時間。
底下程式碼中,關於NTP的部份,主要來自Arduino官網的範例Arduino - UdpNTPClient,改用LinkIt ONE的API。原始碼可到GitHub下載。
先看看執行結果,送序列埠送出控制字元,操控WiFi模組,連線後,送出UDP封包向NTP伺服器取得時間。有哪些控制字元,請見程式碼。
程式碼:
#include <stdarg.h>
#include <LWiFi.h>
#include <LWiFiUdp.h>
#define BAUDRATE 115200
// 請修改成你的WiFi名稱與密碼
#define WIFI_AP "__WiFi_AP_NAME__"
#define WIFI_PASSWD __WiFi_PASSWORD__"
// NTP伺服器
#define NTP_SERVER "time.stdtime.gov.tw" // (118, 163, 81, 61)
IPAddress ntpServerIP;
#define ntpServerPort 123
#define LOCAL_PORT 2390
#define NTP_PACKET_SIZE 48
// UDP客戶端
LWiFiUDP udpcli;
// 類似於printf
void pf(const char *fmt, ... ){
char tmp[128]; // limited to 128 chars
va_list args;
va_start(args, fmt);
vsnprintf(tmp, 128, fmt, args);
va_end(args);
Serial.print(tmp);
}
// 根據WiFi模組狀態,回傳容易看懂的字串
const char * const wifi_status_str(LWifiStatus ws){
switch(ws){
case LWIFI_STATUS_DISABLED:
return "WiFi module status: disabled";
break;
case LWIFI_STATUS_DISCONNECTED:
return "WiFi module status: disconnected";
break;
case LWIFI_STATUS_CONNECTED:
return "WiFi module status: connected";
break;
}
return "WiFi status: error, no such status";
}
void setup(){
Serial.begin(BAUDRATE);
udpcli.begin(LOCAL_PORT); // 綁定某的通訊埠
}
// 送出詢問時間的UDP封包,詳情請看RFC 5905 unsigned long sendNTPpacket(IPAddress &address)
{
byte buf[NTP_PACKET_SIZE];
memset(buf, 0, NTP_PACKET_SIZE); // set all bytes in the buffer to 0
// Initialize values needed to form NTP request
buf[0] = 0b11100011; // LI, Version, Mode
buf[1] = 0; // Stratum, or type of clock
buf[2] = 6; // Polling Interval
buf[3] = 0xEC; // Peer Clock Precision
// 8 bytes of zero for Root Delay & Root Dispersion
buf[12] = 49;
buf[13] = 0x4E;
buf[14] = 49;
buf[15] = 52;
// send a packet requesting a timestamp:
udpcli.beginPacket(address, ntpServerPort);
udpcli.write(buf, NTP_PACKET_SIZE);
udpcli.endPacket();
}
// 拿到秒數(從1900年1月1日 0時0分0秒),印出
// 轉成Unix epoch time,印出
// 捨棄年月日,印出時分秒,HH:MM:SS void printTime(unsigned long secsSince1900){
pf("Seconds since Jan 1 1900: %u\n", secsSince1900);
unsigned long epoch = secsSince1900 - 2208988800UL; // seventy years
pf("Unix time: %u\n", epoch); // 00:00:00 1 January 1970, UTC
epoch %= 86400L;
unsigned long hh = epoch / 3600;
epoch %= 3600;
unsigned mm = epoch / 60;
epoch %= 60;
unsigned ss = epoch;
pf("The UTC time: %02u:%02u:%02u\n", hh, mm, ss);
}
// 主迴圈,從序列埠接收控制字元,做該做的事情 void loop(){
if(Serial.available()){
char d = Serial.read();
switch(d){
case 'l':{ // 搜尋WiFi網路
int i;
int n_ap = LWiFi.scanNetworks();
pf("Number of WiFi AP found: %d\n", n_ap);
for(i = 0; i < n_ap; i++){
pf("%d, %s, RSSI: %d\n", i, LWiFi.SSID(i), LWiFi.RSSI(i));
}
}
break;
case 's':{ // 印出狀態
LWifiStatus ws = LWiFi.status();
Serial.println(wifi_status_str(ws));
uint8_t ma[VM_WLAN_WNDRV_MAC_ADDRESS_LEN] = {0};
LWiFi.macAddress(ma);
Serial.print("MAC address: ");
int i;
for(i = 0; i < VM_WLAN_WNDRV_MAC_ADDRESS_LEN-1; i++){
pf("%02X:", ma[i]);
}
pf("%02X\n", ma[i]);
if(ws == LWIFI_STATUS_CONNECTED){
IPAddress ipa = LWiFi.localIP();
Serial.print("localIP: ");
ipa.printTo(Serial);
Serial.println("");
Serial.print("subnetMask: ");
ipa = LWiFi.subnetMask();
ipa.printTo(Serial);
Serial.println("");
Serial.print("gatewayIP: ");
ipa = LWiFi.gatewayIP();
ipa.printTo(Serial);
Serial.println("");
}
}
break;
case 'b':{ // 開啟WiFi模組
Serial.println("LWiFi.begin() turn on WiFi module");
LWiFi.begin();
}
break;
case 'e':{ // 關閉WiFi模組
Serial.println("LWiFi.end() turn off WiFi module");
LWiFi.end();
}
break;
case 'c':{ // 連接WiFi AP
Serial.print("Connecting...");
if(LWiFi.connect(WIFI_AP, LWiFiLoginInfo(LWIFI_WPA, WIFI_PASSWD)) > 0){
Serial.println(" succeed");
}
else{
Serial.println(" failed");
}
}
break;
case 'd':{ // 切斷與WiFi AP的連線
LWiFi.disconnect();
Serial.println("Disconnected");
}
break;
case 't':{ // 向NTP伺服器詢問時間
if(ntpServerIP == INADDR_NONE){
if(LWiFi.hostByName(NTP_SERVER, ntpServerIP) != 1){
pf("DNS lookup failed: %s", NTP_SERVER);
}
}
if(ntpServerIP != INADDR_NONE){
pf("Send request to NTP server %s\n", NTP_SERVER);
sendNTPpacket(ntpServerIP);
}
}
break;
}
}
if(udpcli.parsePacket()){ // 收到NTP伺服器傳回來的時間
byte packetBuffer[NTP_PACKET_SIZE];
udpcli.read(packetBuffer, NTP_PACKET_SIZE);
const unsigned long highWord = word(packetBuffer[40], packetBuffer[41]);
const unsigned long lowWord = word(packetBuffer[42], packetBuffer[43]);
const unsigned long secsSince1900 = highWord << 16 | lowWord;
printTime(secsSince1900);
}
}
葉大:
ReplyDelete您好,請問一下,我已從此範例成功向NTP取得時間,放在secsSince1900裡;我又從您另一篇文章看到"GPS設定時間",有一個不懂之處,就是在GPS那篇提到:
datetimeInfo dt;
LDateTime.setTime(&dt);
LDateTime.setTime(&dt); // 此處只設定時間
我要把secsSince1900的值放到哪裡才可以設定時間?
謝謝您~
葉大:
ReplyDelete您好,請問一下,我已從此範例成功向NTP取得時間,放在secsSince1900裡;我又從您另一篇文章看到"GPS設定時間",有一個不懂之處,就是在GPS那篇提到:
datetimeInfo dt;
LDateTime.setTime(&dt);
LDateTime.setTime(&dt); // 此處只設定時間
我要把secsSince1900的值放到哪裡才可以設定時間?
謝謝您~
拿到secsSince1900後,算出年、月、日、時、分、秒,
Delete然後放進LinkIt ONE API定義的datetimeInfo結構,
請參考
http://labs.mediatek.com/site/global/developer_tools/mediatek_linkit/api_references/datetimeInfo.gsp
譬如把年月日時分秒,放進底下變數t,
datetimeInfo t;
然後呼叫
LDateTime.setTime(&t);
把日期時間傳給LDateTime,便可設定。
葉大:
Delete您好,看了您的解釋我似懂非懂!
我有找到一個範例程式:
datetimeInfo now;
now.year = 2015;
now.mon = 12;
now.day = 21;
LDateTime.setTime(&now);
您的意思是不是把secsSince1900算出來的年月日時分秒取代2015、12、21?
我現在遇到的問題是不知道要怎麼把secsSince1900變成年月日時分秒?
我有看到另一個範例是:
time_t t1 = 1471688234
struct tm*nPtr = localtime (&t1)
我如果把time_t t1 = secsSince1900,是不是tm_year的值就是year的值?
secsSince1900是自從1900年1月1日 0時0分0秒所經過的秒數,
Delete一年365天 一天24時 一時60分 一分60秒
不斷進行除法 就可以得到 但要注意閏年
可上網找找 應該已經有人寫好了
我這篇文章 只有轉換出 時分秒
datetimeInfo 是LinkIt ONE 定義的結構
定義請見http://labs.mediatek.com/site/global/developer_tools/mediatek_linkit/api_references/datetimeInfo.gsp
用來存放年月日時分秒
放好後 再傳入給LDateTime.setTime
便可設定日期與時間