PlatformIOでESP32を開発する

NTP Client(UDP Client)


Arduino-IDEでは、WiFiUDPオブジェクトを使ってUDP/IP通信を行いますが、
PlatformIO for ESP32にはlwIPが組み込まれているので、以下のAPIを使うことができます。
・raw API(callback API)
・Sequential-style API(Netconn APIとNETIF API)
・Socket API(BSD-Socket API)

そこで、WiFiオブジェクトを使ってAPに接続し、Socket APIを使ったNTP Client(UDP Client)を動かしてみました。

SSID、PASSWORD、SNTP_SERVERは適当に変更してください。
#include "freertos/FreeRTOS.h"
#include "Arduino.h"
#include "WiFi.h"

#include "lwip/err.h"
#include "lwip/sockets.h"
#include "lwip/sys.h"
#include "lwip/netdb.h"
#include "lwip/dns.h"

#define SNTP_SERVER "ntp.nict.jp"
//#define SNTP_SERVER "pool.ntp.org"
#define SNTP_PORT 123

#define SNTP_OFFSET_LI_VN_MODE      0
#define SNTP_LI_MASK                0xC0
#define SNTP_LI_NO_WARNING          0x00
#define SNTP_LI_LAST_MINUTE_61_SEC  0x01
#define SNTP_LI_LAST_MINUTE_59_SEC  0x02
#define SNTP_LI_ALARM_CONDITION     0x03 /* (clock not synchronized) */

#define SNTP_VERSION_MASK           0x38
#define SNTP_VERSION                (4/* NTP Version 4*/<<3)

#define SNTP_MODE_MASK              0x07
#define SNTP_MODE_CLIENT            0x03
#define SNTP_MODE_SERVER            0x04
#define SNTP_MODE_BROADCAST         0x05

/* number of seconds between 1900 and 1970 */
#define DIFF_SEC_1900_1970         (2208988800UL)

const char* WIFI_SSID = "SSID";
const char* WIFI_PASS = "PASSWORD";

// https://qiita.com/koara-local/items/585755faac70c8b37b5b
// [C++11 ~] variadic templates
template <typename ... Args>
void _printf(const char *format, Args const & ... args) {
    // int printf(const char *format, ...);
    TickType_t _nowTick = xTaskGetTickCount();
    char * _taskName = pcTaskGetTaskName( NULL );
    printf("[%s:%d] ",_taskName, _nowTick);
    printf(format, args ...);
}

#if 0
// [C/C++] stdarg
void _printf(const char *format, ...) {
    va_list va;
    va_start(va, format);
    // int vprintf(const char *format, va_list ap);
    TickType_t _nowTick = xTaskGetTickCount();
    char * _taskName = pcTaskGetTaskName( NULL );
    printf("[%s:%d] ",_taskName, _nowTick);
    vprintf(format, va);
    va_end(va);
}
#endif

void showNetworkInfo() {
    IPAddress ip = WiFi.localIP();
    IPAddress mk = WiFi.subnetMask();
    IPAddress gw = WiFi.gatewayIP();
    _printf("IP address=%d.%d.%d.%d\n", ip[0], ip[1], ip[2], ip[3]);
    _printf("Netmask   =%d.%d.%d.%d\n", mk[0], mk[1], mk[2], mk[3]);
    _printf("Gateway   =%d.%d.%d.%d\n", gw[0], gw[1], gw[2], gw[3]);
}

// NTP Client Task
void task1(void *pvParameters)
{
  typedef struct
  {
    uint8_t li_vn_mode;      // Eight bits. li, vn, and mode.
                             // li.   Two bits.   Leap indicator.
                             // vn.   Three bits. Version number of the protocol.
                             // mode. Three bits. Client will pick mode 3 for client.

    uint8_t stratum;         // Eight bits. Stratum level of the local clock.
    uint8_t poll;            // Eight bits. Maximum interval between successive messages.
    uint8_t precision;       // Eight bits. Precision of the local clock.

    uint32_t rootDelay;      // 32 bits. Total round trip delay time.
    uint32_t rootDispersion; // 32 bits. Max error aloud from primary clock source.
    uint32_t refId;          // 32 bits. Reference clock identifier.

    uint32_t refTm_s;        // 32 bits. Reference time-stamp seconds.
    uint32_t refTm_f;        // 32 bits. Reference time-stamp fraction of a second.

    uint32_t origTm_s;       // 32 bits. Originate time-stamp seconds.
    uint32_t origTm_f;       // 32 bits. Originate time-stamp fraction of a second.

    uint32_t rxTm_s;         // 32 bits. Received time-stamp seconds.
    uint32_t rxTm_f;         // 32 bits. Received time-stamp fraction of a second.

    uint32_t txTm_s;         // 32 bits and the most important field the client cares about. Transmit time-stamp seconds.
    uint32_t txTm_f;         // 32 bits. Transmit time-stamp fraction of a second.

  } ntp_packet;              // Total: 384 bits or 48 bytes.


    _printf("Start\n");

    /* Convert URL to IP */
    struct hostent *server; // Server data structure.
    server = gethostbyname(SNTP_SERVER);
    LWIP_ASSERT("server != NULL", server != NULL);

    /* set up address to connect to */
    struct sockaddr_in addr;
    memset(&addr, 0, sizeof(addr));
    //addr.sin_len = sizeof(addr);
    addr.sin_family = AF_INET;
    //addr.sin_port = PP_HTONS(SNTP_PORT);
    addr.sin_port = htons(SNTP_PORT);
    //addr.sin_addr.s_addr = inet_addr("129.250.35.250");

    // Copy the server's IP address to the server address structure.
    bcopy( ( char* )server->h_addr, ( char* ) &addr.sin_addr.s_addr, server->h_length );

    /* create the socket */
    int fd;
    int ret;
    fd = lwip_socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP ); // Create a UDP socket.
    LWIP_ASSERT("fd >= 0", fd >= 0);

    /* set NTP packet */
    ntp_packet packet;
    memset( &packet, 0, sizeof( ntp_packet ) );
    packet.li_vn_mode = SNTP_LI_NO_WARNING | SNTP_VERSION | SNTP_MODE_CLIENT;

    /* send NTP packet */
    ret = lwip_sendto(fd, (char*)&packet, sizeof(ntp_packet), 0, (struct sockaddr *)&addr,sizeof(addr));
    LWIP_ASSERT("ret == 48", ret == 48);
    _printf("lwip_sendto ret=%d\n",ret);

    /* recv NTP packet */
    ret = lwip_recv(fd, (char*) &packet, sizeof(ntp_packet), 0);
    LWIP_ASSERT("ret > 0", ret > 0);
    _printf("lwip_recv ret=%d\n",ret);

    // NTPタイムをUNIXタイムに変換する
    // NTPタイムは1900年1月1日0時0分からはじまる積算秒数
    // UNITタイムは1970年1月1日0時0分からはじまる積算秒数
    // 1900年から1970年の70年を秒で表すと2208988800秒になる
    // NTPタイムから70年分の秒を引くとUNIXタイムが得られる
    uint32_t ntpTime = ntohl( packet.txTm_s ); // NTP Time-stamp seconds.
    _printf("The NTP Time = %u\n",ntpTime);
    // グリニッジ標準時間
    time_t txTm = ntpTime - DIFF_SEC_1900_1970; // UNIT Time-stamp seconds.
    _printf("The UTC Time: %s",ctime( ( const time_t* ) &txTm ) );
    // 日本標準時にあわせるために+9時間しておく
    txTm = txTm + (9 * 60 * 60);
    _printf("The JST Time: %s",ctime( ( const time_t* ) &txTm ) );

    /* Convert tm to timeval */
    struct timeval now = { .tv_sec = txTm };

    /* Set the time as well as a timezone */
    settimeofday(&now, NULL);

    /* close */
    ret = lwip_close(fd);
    LWIP_ASSERT("ret == 0", ret == 0);
    vTaskDelete( NULL );

}

// Clock Task
void task2(void *pvParameters)
{
    _printf("Start\n");

    while(1) {
      time_t ts = time(NULL);
      _printf("%s",ctime(&ts));
      vTaskDelay(2000 / portTICK_PERIOD_MS);
    }
    vTaskDelete( NULL );
}

void setup() {
    vTaskDelay(2000 / portTICK_PERIOD_MS);
    _printf("start Priority=%d\n",uxTaskPriorityGet( NULL ));
    _printf("portTICK_PERIOD_MS=%d\n",portTICK_PERIOD_MS);

    /* Connect WiFi */
    _printf("Connecting to %s\n",WIFI_SSID);
    WiFi.mode(WIFI_STA);
    WiFi.begin(WIFI_SSID, WIFI_PASS);

    while (WiFi.status() != WL_CONNECTED) {
      vTaskDelay(5000 / portTICK_PERIOD_MS);
    }

    _printf("WiFi connected\n");
    showNetworkInfo();

    /* Start NTP Client */
    xTaskCreatePinnedToCore(task1, "NTP", 4096, NULL, 1, NULL, tskNO_AFFINITY);
    /* Start Clock Task */
    xTaskCreatePinnedToCore(task2, "Clock", 4096, NULL, 1, NULL, tskNO_AFFINITY);

    /* stop loop task */
    vTaskDelete( NULL );
}

void loop() { // Never run
    _printf("loop\n");
}

実行すると7786Tickで時刻同期を行い、それ以降正しい時刻を刻み始めます。
--- Miniterm on /dev/ttyUSB0  115200,8,N,1 ---
--- Quit: Ctrl+C | Menu: Ctrl+T | Help: Ctrl+T followed by Ctrl+H ---
[loopTask:2036] start Priority=1
[loopTask:2037] portTICK_PERIOD_MS=1
[loopTask:2037] Connecting to aterm-e625c0-g
[loopTask:7184] WiFi connected
[loopTask:7184] IP address=192.168.10.115
[loopTask:7184] Netmask   =255.255.255.0
[loopTask:7185] Gateway   =192.168.10.1
[NTP:7187] Start
[Clock:7188] Start
[Clock:7190] Thu Jan  1 00:00:07 1970
[NTP:7764] lwip_sendto ret=48
[NTP:7786] lwip_recv ret=48
[NTP:7786] The NTP Time = 3760610285
[NTP:7786] The UTC Time: Sun Mar  3 13:58:05 2019
[NTP:7786] The JST Time: Sun Mar  3 22:58:05 2019
[Clock:9194] Sun Mar  3 22:58:06 2019
[Clock:11194] Sun Mar  3 22:58:08 2019
[Clock:13194] Sun Mar  3 22:58:10 2019
[Clock:15194] Sun Mar  3 22:58:12 2019
[Clock:17194] Sun Mar  3 22:58:14 2019
[Clock:19194] Sun Mar  3 22:58:16 2019
[Clock:21194] Sun Mar  3 22:58:18 2019
[Clock:23194] Sun Mar  3 22:58:20 2019
[Clock:25194] Sun Mar  3 22:58:22 2019
[Clock:27194] Sun Mar  3 22:58:24 2019
[Clock:29194] Sun Mar  3 22:58:26 2019
[Clock:31194] Sun Mar  3 22:58:28 2019
[Clock:33194] Sun Mar  3 22:58:30 2019
[Clock:35194] Sun Mar  3 22:58:32 2019

Socket APIが使えるとLinux用に公開されているソースをそのまま使うことができます。
また、lwIPはESP8266のesp-open-rtosやESP8266_RTOS_SDKでも使うことができるので、
ESP8266とESP32で同じコードを使いまわすことができます。

続く....