ESP8266学习进阶协议类(2)——SNTP服务

SNTP服务有啥用:可校时获取时间等等哈

ESP8266-RTOS-SDK中有提供LwIP的这个组件:LwIP是Light Weight (轻型)IP协议,有无操作系统的支持都可以运行。LwIP实现的重点是在保持TCP协议主要功能的基础上减少对RAM 的占用,它只需十几KB的RAM和40K左右的ROM就可以运行,这使LwIP协议栈适合在低端的嵌入式系统中使用。(摘自百度百科)

LwIP组件下有提供SNTP服务,SNTP(Simple Network Time protocol)直译即意思:简单网络时间协议,用于获取和实现网络时间同步的协议,它是基于NTP((Network Time Protocol)一 种用于计算机时钟同步的协议)简化后的产物;

虽然SDK有提供对应的SNTP接口,可以很方便的使用,但是也得先了解一下SNTP的工作原理,才不至于变成一个纯搬砖的工程师嘛。

简述工作原理

SNTP协议可以使用单播、广播或多播模式进行工作。

  • 单播模式是指一个客户发送请求到预先指定的一个服务器地址,然后从服务器获得准确的时间、来回时延和与服务器时间的偏差。
  • 广播模式是指一个广播服务器周期地向指定广播地址发送时间信息,在这组地址内的服务器侦听广播并且不发送请求。
  • 多播模式是对广播模式的一种扩展,它设计的目的是对地址未知的一组服务器进行协调。在这种模式下,多播客户发送一个普通的NTP请求给指定的广播地址,多个多播服务器在此地址上进行侦听。一旦收到一个请求信息,一个多播服务器就对客户返回一个普通的NTP服务器应答,然后客户依此对广播地址内剩下的所有服务器作同样的操作,最后利用NTP迁移算法筛选出最好的三台服务器使用。

本文使用单播模式;

灵魂画师的示意图见谅,工作原理很简单,大概场景:就是客户端(client)在 t1时刻(Originate Timestamp) 为了调整自己的时钟,向服务端(server)发送了一个校时请求,服务端在 t2时刻(Receive Timestamp) 收到了客户端发来的校时请求后,在 t3时刻(Transmit Timestamp) 将通过来自GPS信号或自带的原子钟的标准时间回发给客户端,客户端在 t4时刻(Destination Timestamp) 收到该校时数据,校时数据中也会包含t2和t3的时间哈,据此,客户端通过t1、t2、t3、t4计算出时差去调整本地时钟,大概流程就这样,更加详细的可以自行百度查阅;

ESP8266学习进阶协议类(2)——SNTP服务_第1张图片

在8266中实现

官方提供的SDK还是很贴心的,几乎都提供好了API接口;

SNTP头文件

#include "lwip/apps/sntp.h"

开启SNTP需要使用到的API不多,下面针对关键的几个进行分析:

设置SNTP的工作模式

前面有介绍到SNTP有三种工作模式,单播、广播、组播这些都是需要通过API进行设置的,但是没有提供组播这种模式;

void sntp_setoperatingmode(u8_t operating_mode)
宏定义 解析
#define SNTP_OPMODE_POLL 0 单播模式
#define SNTP_OPMODE_LISTENONLY 1 广播模式

设置SNTP的服务器地址

设置完SNTP的工作模式后,需要配置SNTP的服务器地址,用来发送校时请求,通过IP地址初始化一个NTP服务器;

void sntp_setserver(u8_t idx, const ip_addr_t *server)
参数 解析
idx 设置SNTP服务器的数量(这里不可大于SNTP_MAX_SERVERS),可以填0
server 要设置的NTP服务器的服务器IP地址

初始化这个模块

void sntp_init(void)

就以上三步,就算是配置并对SNTP进行了初始化同时向服务器发出校时请求并修改本地时间。


本文以通过以SNTP获取最新时间,并通过OLED显示时钟,具体实现代码及解析如下:

初始化SNTP
pool.ntp.org 是一个以时间服务器的大虚拟部署为上百万的客户端提供可靠的易用的网络时间协议(NTP)服务的项目

static void initialize_sntp(void)               //初始化SNTP
{
    ESP_LOGI(TAG, "Initializing SNTP");
    sntp_setoperatingmode(SNTP_OPMODE_POLL);    //设置为单播模式    
    sntp_setservername(0, "pool.ntp.org");      //设置SNTP服务器
    sntp_init();                                //初始化,并向SNTP服务器发出校时请求
}

获取时间

static void obtain_time(void)
{
    xEventGroupWaitBits(wifi_event_group, CONNECTED_BIT,
                        false, true, portMAX_DELAY);    //等待WIFI连接成功
    initialize_sntp();      //wifi连接成功后进行SNTP校时                         

    time_t now = 0;
    struct tm timeinfo = { 0 };
    int retry = 0;
    const int retry_count = 10;
    
    //8266上电没校时的话,时钟是1970年开始的,这里的while设置了20s超时处理
    while (timeinfo.tm_year < (2016 - 1900) && ++retry < retry_count) { 
        ESP_LOGI(TAG, "Waiting for system time to be set... (%d/%d)", retry, retry_count);
        vTaskDelay(2000 / portTICK_PERIOD_MS);     
        time(&now);
        localtime_r(&now, &timeinfo);       //获取系统时间,如果系统时间被成功修改后,今年时2020年,timeinfo.tm_year应该为120 > 116
    }
}

显示

static void showtime_task(void *arg)
{
    time_t now;
    struct tm timeinfo;
    
    char strftime_datebuf[32];  //保存日期
    char strftime_timebuf[32];  //保存时间
    
    OLED_Init();    //OLED初始化
    OLED_Clear();   //OLED清屏幕
    
    time(&now);     
    localtime_r(&now, &timeinfo);           //获取本地时间
    if (timeinfo.tm_year < (2016 - 1900)) { //判断是否为修改过
        ESP_LOGI(TAG, "Time is not set yet. Connecting to WiFi and getting time over NTP.");
        obtain_time();      //获取时间
    }

    setenv("TZ", "CST-8", 1);           //设置为中国区时间
    tzset();                            //设置时间环境变量
    
    while(1)
    {
        time(&now);
        localtime_r(&now, &timeinfo);
        
        if (timeinfo.tm_year < (2016 - 1900)) {
            ESP_LOGE(TAG, "The current date/time error");
        } else {
            strftime(strftime_datebuf, sizeof(strftime_datebuf), "%F", &timeinfo);//取出日期/年月日
            strftime(strftime_timebuf, sizeof(strftime_timebuf), "%T", &timeinfo);//取出时间/时分秒
            //ESP_LOGI(TAG, "The current date in Shanghai is: %s", strftime_datebuf);
            //ESP_LOGI(TAG, "The current time in Shanghai is: %s", strftime_timebuf);
            OLED_ShowString(24, 0, (uint8_t *)strftime_datebuf, 16); 
            OLED_ShowString(32, 2, (uint8_t *)strftime_timebuf, 16); 
        }

        vTaskDelay(1000 / portTICK_RATE_MS);
    }
}

附加说明:strftime()这个函数可以将 struct tm 格式的时间信息结构体转成我们想要的格式,并保存到指定的内存去,可参考(https://www.runoob.com/cprogramming/c-function-strftime.html)

实物展示

ESP8266学习进阶协议类(2)——SNTP服务_第2张图片
ESP8266学习进阶协议类(2)——SNTP服务_第3张图片
具体完整代码,由于文中没有很好的优化,可能会出现跳秒的情况,可自行优化!有帮助请点个赞,谢谢~


我的GITHUB

我的个人博客

CSDN

你可能感兴趣的:(IoT入门到实战)