2014年7月1日 星期二

使用Spider L3S(CC3000)抓取網路時鐘範例

在未來的許多雲端運用中,會需要在傳輸的資料內夾帶時間資訊,一般是運用板子上搭載RTC時鐘來取得時間資訊。

不過因為我們有使用WiFi模組接上網際網路,若能透過網路上的時間伺服器抓取時間來使用,不僅可以省下RTC,就使用上也比較方便!






要如何取得時間資訊呢?只要透過SNTP封包(Simple Network Time Protocol)對時間伺服器要求傳送時間封包,時間伺服器就會回傳時間資料回來,資料封包格式如下:


 

傳輸與接收端均以相同的資料封包做傳輸,在這邊可以看到一堆的資料如Root Delay,可做為封包傳輸時延遲的時間修正,但是在這邊並不追求過於精細的時間資訊,只做時間的接收與換算。

若需要對時間伺服器要求傳送封包,只需要對VN與Mode下定義就好,定義可以參考RFC2030,節錄如下:


VN為NTP的版本編號,最新版本為第四版,所以VN就設定為100


為了接收時間,所以Mode要設定成Client接收Server傳過來的時間。

待傳送結束後,就可以準備接收時間伺服器回傳的封包,時間的區塊有四個區塊,分別為如下:


根據說明,Transmit Timestamp這個區塊是時間伺服器傳輸封包給客戶端的時間,故將以此區塊作為參考時間。

接下來將說明如何實作,完整程式碼可以參考:Github

1. 設定UDP客戶端發送接收封包,先用gethostbyname取得時間伺服器的IP位址。

char ntp_server[] = "time.stdtime.gov.tw";

ret = gethostbyname((char*)ntp_server, strlen(ntp_server), &(tar_ip.ulip));



2. 設定方式如下:
    // Get socket from spider L3.
    ntp_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    if(ntp_socket < 0){
        ntp_socket = 0xFFFFFFFF;
        return -2;
    }

    // Setting BSD socket timeout.
    ntp_timeout = 1000;
    ret = setsockopt(ntp_socket, SOL_SOCKET, SOCKOPT_RECV_TIMEOUT, &ntp_timeout, sizeof(ntp_timeout));
    if(ret < 0){
        closesocket(ntp_socket);
        ntp_socket = 0xFFFFFFFF;
        return -3;
    }

    // Setting BSD like socket setting.
    host_addr.sa_family = AF_INET;
    tar_port.usport = ntp_port;

    host_addr.sa_data[0] = tar_port.ucport[1];
    host_addr.sa_data[1] = tar_port.ucport[0];

    host_addr.sa_data[2] = tar_ip.ucip[3];
    host_addr.sa_data[3] = tar_ip.ucip[2];
    host_addr.sa_data[4] = tar_ip.ucip[1];
    host_addr.sa_data[5] = tar_ip.ucip[0];



3. 傳送封包要求時間伺服器傳送時間資訊
    // NTP time stamp request header, version 3, mode : 3 as client
    // Reference => http://tools.ietf.org/html/rfc2030
    msg[0] = 0b00100011;
    sendto(ntp_socket, msg, sizeof(msg), 0, &host_addr, sizeof(sockaddr));

    memset(&recv_addr, 0, sizeof(sockaddr));
    recv_addr_l = 0;
    recvfrom(ntp_socket, msg, sizeof(msg), 0, &recv_addr, &recv_addr_l);


4. 接收時間資訊封包
    memset(&recv_addr, 0, sizeof(sockaddr));
    recv_addr_l = 0;
    recvfrom(ntp_socket, msg, sizeof(msg), 0, &recv_addr, &recv_addr_l);


5. TimeStamp換算,計算出1970/01/01的Timestamp
    gmt_offset = (MY_GMT) * 60 * 60;
    // Caculate timestamp rightnow
    timestamp_now = ((unsigned long)msg[40] << 24) | ((unsigned long)msg[41] << 16) | ((unsigned long)msg[42] << 8) | ((unsigned long)msg[43]);
    // Dec 70 years in second.
    timestamp_now -= 2208988800UL;
    timestamp_now += gmt_offset;


6. 計算出秒分時
    /* counting reference : http://maumaubug.blogspot.tw/2013/05/sntp-ntp-02-epoch-time.html */

    *second = timestamp_now % 60;

    *minute = (timestamp_now / 60) % 60;

    *hour =   (timestamp_now / 3600) % 24;



7. 利用閏年的公式計算出現在的年份
// Count years.
    *year = 1970;
    while(counter > 365){
        if(((*year % 4 == 0) && (*year % 100 != 0)) || (*year % 400 == 0)){
            counter-= 366;
        }
        else{
            counter -=365;
        }
        *year = *year + 1;
    }


8. 運用查表法算出月份與日期,先確認是否為閏年
    unsigned char m_d_table_normal[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
    unsigned char m_d_table_leap[]   = {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

    unsigned char *m_d_ptr = 0;

    // Count month and days.
    if(((*year % 4 == 0) && (*year % 100 != 0)) || (*year % 400 == 0)){
        m_d_ptr = m_d_table_leap;
    }
    else{
        m_d_ptr = m_d_table_normal;
    }



9. 查表計算

    *month = 0;
    for(ret = 0; ret < 12; ret++){
        *month = *month + 1;
        if(counter > m_d_ptr[ret]){
            counter -= m_d_ptr[ret];
        }
        else{
            *day = counter;
            break;
        }
    }


10. 印出結果
        unsigned int  n_year = 0;
        unsigned char n_month = 0;
        unsigned int  n_day = 0;
        unsigned char n_hour = 0;
        unsigned char n_minute = 0;
        unsigned char n_second = 0;

        get_time(&n_year, &n_month, &n_day, &n_hour, &n_minute, &n_second);

        Serial.print("Time :");
        Serial.print(n_year, DEC);
        Serial.print("-");
        Serial.print(n_month, DEC);
        Serial.print("-");
        Serial.print(n_day, DEC);
        Serial.print(" ");
        Serial.print(n_hour, DEC);
        Serial.print(":");
        Serial.print(n_minute, DEC);
        Serial.print(":");
        Serial.print(n_second, DEC);
        Serial.println();


11. 成功轉換出時間與日期

 

透過這些流程,只要WiFi模組連在網路上,就可以隨時取得網路時鐘伺服器的時間。我有另外把程式整理包成NTP.c與NTP.h,放在Spider L3S Library的資料夾下,這樣在開發APP時就可以很簡單的取得時間了。

沒有留言:

張貼留言