Skip to content

Overview

IoT devices often need accurate local time (timestamps for data reporting, scheduled tasks). SNTP (Simple Network Time Protocol) syncs time automatically by visiting an NTP server, with errors down to the millisecond level. This tutorial demonstrates: after connecting to Wi-Fi, fetching the time from ntp.aliyun.com and converting it to Beijing time (UTC+8) to print.

In plain words: SNTP is the board setting its clock online — like when your watch is inaccurate and you call the "time service center" to ask what time it is, then calibrate your watch accordingly. Every time the board powers on and gets online it automatically asks the NTP server (this tutorial uses ntp.aliyun.com) for the standard time once, and after getting it converts to Beijing time per your timezone to print. That way the timestamps (the numbers recording "what time on which year-month-day") in device data reports are trustworthy.

This tutorial is based on the official Ai-Thinker SDK (Ai-Thinker-Open/Ai-Thinker-WB2, version release_bl_iot_sdk_1.6.40) example applications/protocols/sntp_demo; the code can be found directly in your local SDK.

🎯Page GoalSync time from a public NTP server via SNTP and convert it to Beijing time, mastering sntp initialization, time fetching and local timezone conversion.
🧰Prerequisites① Ai-WB2 development board (Type-C data cable) ② A 2.4GHz router (with public internet access) ③ Environment set up per [SDK Installation](../../sdk/sdk_intro) and completed [Connect Wi-Fi](./wifi_connect).
🔗RelatedNetworking basics: [Connect Wi-Fi](./wifi_connect); timestamps are often used in reporting scenarios like [MQTT Connection](./mqtt).

Enter the Example Project

Open a terminal and enter the official sntp_demo example project directory:

cd ~/Ai-Thinker-WB2/applications/protocols/sntp_demo

Note: cd is the “change directory” command, entering the official example project; all subsequent make commands must run in this directory.

Modify the Router Parameters and Timezone

Open sntp_demo/main.c and change the SSID/password at the top (same as Connect Wi-Fi), and adjust the timezone as needed:

#define ROUTER_SSID "ssid"
#define ROUTER_PWD "password"
#define sntp_server "ntp.aliyun.com"
#define UTC 8       //your Timezone, for example,beijing Timezone is GMT+8
Macro Default Description
sntp_server ntp.aliyun.com NTP server address (alternatives: ntp.ntsc.ac.cn etc.); if it can’t be reached, time won’t sync
UTC 8 Timezone offset (Beijing time = UTC+8; Tokyo = 9, London = 0); without it the printed time is UTC, 8 hours off
Write the Code

Open sntp_demo/main.c. The full code for this step has been moved to the end of this page:

📜 Full Code — in the “Full Code” section below, collapsed by default — click to expand, identical to the official example (applications/protocols/sntp_demo/sntp_demo/main.c).

Code highlights:

Code Purpose
sntp_setoperatingmode(SNTP_OPMODE_POLL) Sets SNTP poll mode (actively asks the server at intervals, no manual management)
sntp_setservername(0, "ntp.aliyun.com") Specifies the NTP server (tells it who to ask for the time)
sntp_init() Starts SNTP sync (without calling it nothing works)
tcpip_callback(_startup_sntp, NULL) Starts SNTP in the lwIP context (thread-safe); calling it directly in an event callback crashes the board
sntp_get_time(&seconds, &frags) Gets the synced time: seconds since 1970 + subseconds; before a successful sync the value is stale
utils_time_date_from_epoch(seconds+UTC*60*60, &date) Adds the timezone offset to the seconds and converts to a date struct (seconds are unreadable; this turns them into year-month-day hour:minute:second)

💡 The seconds returned by sntp_get_time is a Unix timestamp (since 1970-01-01 00:00:00 UTC); adding UTC*60*60 (8 hours) gives Beijing time. SNTP must be started after GOT_IP.

Build the Project

Build in the project directory:

make -j8

Note: make is the “build” command, turning code into firmware (the program flashed into the board) the board can run; -j8 builds with 8 parallel cores, faster.

On success a firmware build_out/sntp_demo.bin is generated.

Flash the Firmware

Keep the board connected via USB, confirm the serial device, and flash:

make flash p=/dev/ttyUSB0 b=921600

Note: make flash is the “flash” command, writing the compiled firmware into the board. After p= comes the serial device (often /dev/ttyUSB0 on Linux, COM3-like on Windows — use your computer’s actual one), b=921600 is the flash baud rate (serial transfer speed), keep the default.

⏳ During flashing, press and hold the EN button on the board when prompted to enter download mode; wait for the progress bar to complete — that means the flash succeeded.

Run and Verify

After flashing, the board automatically restarts and runs. Open a serial assistant (baud rate 921600, the “speech speed” of serial data — both ends must match). You first see the Wi-Fi connection events, then:

[APP] [EVT] GOT IP 5
--------------------------------------- Start NTP now
--------------------------------------- Start NTP Done
[NTP] time is 4123456789:0
Date & time is: 2026-08-11 14:53:30 (Day 2 of week, Day 223 of Year)

Date & time is: appearing with a date that’s not 1970 means success — the time matches your computer/phone’s current time (in the Beijing timezone).

💡 Pitfall: if the date never prints, or the time stays at 1970, SNTP sync failed — first confirm the board is online (you saw GOT IP), the router can access the public internet, and the NTP server (ntp.aliyun.com) is reachable; try switching servers (e.g. ntp.ntsc.ac.cn), see the FAQ at the end.


API Summary for This Tutorial

sntp_setoperatingmode(mode)

Sets the SNTP working mode.

Parameters:

  • mode: working mode, SNTP_OPMODE_POLL (poll mode)

Return: none

sntp_setservername(idx, server)

Sets the address of the idx-th NTP server.

Parameters:

  • idx: server index (starting from 0, the official example uses 0)
  • server: server domain/address, e.g. "ntp.aliyun.com"

Return: none

sntp_init

Starts SNTP time synchronization.

Return: none

sntp_get_time(&sec, &frag)

Gets the synced time (Unix seconds + subseconds).

Parameters:

  • sec: output parameter, Unix seconds (uint32_t)
  • frag: output parameter, subsecond part

Return: 0 on success; negative error code on failure

utils_time_date_from_epoch(epoch, &date)

Converts Unix seconds to a date struct (year-month-day hour:minute:second/week/day of year).

Parameters:

  • epoch: Unix seconds
  • date: output parameter, utils_time_date_t struct

Return: 0 on success; negative error code on failure

tcpip_callback(fn, arg)

Executes a function in the lwIP context (thread-safe; recommended for calling lwIP APIs across tasks).

Parameters:

  • fn: the callback function to execute
  • arg: callback argument

Return: 0 on success; negative error code on failure

wifi_mgmr_sta_connect(if, ssid, pwd, ...)

Connects to a router (same as Connect Wi-Fi).

Parameters:

  • See the "API Summary for This Tutorial" of Connect Wi-Fi for the parameter descriptions

Return: 0 on success; negative error code on failure

📌 Date struct utils_time_date_t fields: ntp_year (year), ntp_month (month), ntp_date (day), ntp_hour (hour), ntp_minute (minute), ntp_second (second), ntp_week_day (weekday 1~7), day_of_year (day of year).


Full Code

Below is the complete sntp_demo/main.c source, identical to the official example (applications/protocols/sntp_demo/sntp_demo/main.c):

📜 Click to expand the full sntp_demo/main.c code
c

#include <FreeRTOS.h>
#include <task.h>
#include <stdio.h>
#include <string.h>
#include "blog.h"
#include <aos/yloop.h>
#include <aos/kernel.h>
#include <lwip/tcpip.h>
#include <wifi_mgmr_ext.h>
#include <hal_wifi.h>
#include <lwip/tcpip.h>
#include <sntp.h>
#include <utils_time.h>

#define ROUTER_SSID "ssid"
#define ROUTER_PWD "password"
#define sntp_server "ntp.aliyun.com"
#define UTC 8       //your Timezone, for example,beijing Timezone is GMT+8

static wifi_conf_t conf =
{
    .country_code = "CN",
};

static void wifi_sta_connect(char* ssid, char* password)
{
    wifi_interface_t wifi_interface;

    wifi_interface = wifi_mgmr_sta_enable();
    wifi_mgmr_sta_connect(wifi_interface, ssid, password, NULL, NULL, 0, 0);
}

void _startup_sntp(void *arg)
{
    blog_info("--------------------------------------- Start NTP now\r\n");
    sntp_setoperatingmode(SNTP_OPMODE_POLL);
    sntp_setservername(0, sntp_server);
    sntp_init();
    blog_info("--------------------------------------- Start NTP Done\r\n");
}

void sntp_task()
{
    tcpip_callback(_startup_sntp, NULL); 
    vTaskDelay(5000 / portTICK_PERIOD_MS);
    while(1)
    {
        uint32_t seconds = 0, frags = 0;
        utils_time_date_t date;
        sntp_get_time(&seconds, &frags);
        blog_info("[NTP] time is %lu:%lu\r\n", seconds, frags);
        utils_time_date_from_epoch(seconds+UTC*60*60, &date);
        blog_info("Date & time is: %u-%02u-%02u %02u:%02u:%02u (Day %u of week, Day %u of Year)\r\n",
            date.ntp_year,
            date.ntp_month,
            date.ntp_date,
            date.ntp_hour,
            date.ntp_minute,
            date.ntp_second,
            date.ntp_week_day,
            date.day_of_year
        );        
        vTaskDelay(1000 / portTICK_RATE_MS); 
    }
}

static void event_cb_wifi_event(input_event_t* event, void* private_data)
{
    static char* ssid;
    static char* password;

    switch (event->code)
    {
        case CODE_WIFI_ON_INIT_DONE:
        {
            blog_info("[APP] [EVT] INIT DONE %lld", aos_now_ms());
            wifi_mgmr_start_background(&conf);
        }
        break;
        case CODE_WIFI_ON_MGMR_DONE:
        {
            blog_info("[APP] [EVT] MGMR DONE %lld", aos_now_ms());
            //_connect_wifi();

            wifi_sta_connect(ROUTER_SSID, ROUTER_PWD);
        }
        break;
        case CODE_WIFI_ON_SCAN_DONE:
        {
            blog_info("[APP] [EVT] SCAN Done %lld", aos_now_ms());
            // wifi_mgmr_cli_scanlist();
        }
        break;
        case CODE_WIFI_ON_DISCONNECT:
        {
            blog_info("[APP] [EVT] disconnect %lld", aos_now_ms());
        }
        break;
        case CODE_WIFI_ON_CONNECTING:
        {
            blog_info("[APP] [EVT] Connecting %lld", aos_now_ms());
        }
        break;
        case CODE_WIFI_CMD_RECONNECT:
        {
            blog_info("[APP] [EVT] Reconnect %lld", aos_now_ms());
        }
        break;
        case CODE_WIFI_ON_CONNECTED:
        {
            blog_info("[APP] [EVT] connected %lld", aos_now_ms());
        }
        break;
        case CODE_WIFI_ON_PRE_GOT_IP:
        {
            blog_info("[APP] [EVT] connected %lld", aos_now_ms());
        }
        break;
        case CODE_WIFI_ON_GOT_IP:
        {
            blog_info("[APP] [EVT] GOT IP %lld", aos_now_ms());
            blog_info("[SYS] Memory left is %d Bytes", xPortGetFreeHeapSize());
            xTaskCreate(sntp_task, (char*)"sntp_task", 1024*4, NULL, 15, NULL);
        }
        break;
        case CODE_WIFI_ON_PROV_SSID:
        {
            blog_info("[APP] [EVT] [PROV] [SSID] %lld: %s",
                   aos_now_ms(),
                   event->value ? (const char*)event->value : "UNKNOWN");
            if (ssid)
            {
                vPortFree(ssid);
                ssid = NULL;
            }
            ssid = (char*)event->value;
        }
        break;
        case CODE_WIFI_ON_PROV_BSSID:
        {
            blog_info("[APP] [EVT] [PROV] [BSSID] %lld: %s",
                   aos_now_ms(),
                   event->value ? (const char*)event->value : "UNKNOWN");
            if (event->value)
            {
                vPortFree((void*)event->value);
            }
        }
        break;
        case CODE_WIFI_ON_PROV_PASSWD:
        {
            blog_info("[APP] [EVT] [PROV] [PASSWD] %lld: %s", aos_now_ms(),
                   event->value ? (const char*)event->value : "UNKNOWN");
            if (password)
            {
                vPortFree(password);
                password = NULL;
            }
            password = (char*)event->value;
        }
        break;
        case CODE_WIFI_ON_PROV_CONNECT:
        {
            blog_info("[APP] [EVT] [PROV] [CONNECT] %lld", aos_now_ms());
            blog_info("connecting to %s:%s...", ssid, password);
            wifi_sta_connect(ssid, password);
        }
        break;
        case CODE_WIFI_ON_PROV_DISCONNECT:
        {
            blog_info("[APP] [EVT] [PROV] [DISCONNECT] %lld", aos_now_ms());
        }
        break;
        default:
        {
            blog_info("[APP] [EVT] Unknown code %u, %lld", event->code, aos_now_ms());
            /*nothing*/
        }
    }
}

static void proc_main_entry(void* pvParameters)
{
    aos_register_event_filter(EV_WIFI, event_cb_wifi_event, NULL);
    hal_wifi_start_firmware_task();
    aos_post_event(EV_WIFI, CODE_WIFI_ON_INIT_DONE, 0);
    vTaskDelete(NULL);
}


void main()
{
    puts("[OS] Starting TCP/IP Stack...");
    tcpip_init(NULL, NULL);
    puts("[OS] proc_main_entry task...");
    xTaskCreate(proc_main_entry, (char*)"main_entry", 1024, NULL, 15, NULL);
}

FAQ & Troubleshooting

⚠️ Time stays at 1970
Cause: SNTP didn't sync successfully (server unreachable, DNS resolution failed, router can't access the public internet)
Fix: confirm you can browse the web normally; switch NTP servers (ntp.ntsc.ac.cn, time.windows.com); check that SNTP is started only after GOT_IP

⚠️ Time is 8 hours off
Cause: the UTC timezone macro isn't set to 8 (UTC time is printed by default)
Fix: #define UTC 8; change it per the local timezone of other countries/regions

⚠️ Crashes calling sntp APIs directly in an event callback
Cause: the Wi-Fi event callback isn't in the lwIP context; calling lwIP APIs directly isn't safe
Fix: follow the official approach and wrap the startup logic with tcpip_callback(_startup_sntp, NULL)

⚠️ The fetched time is off (by tens of seconds)
Cause: network latency, heavy server load
Fix: SNTP polling keeps correcting; wait a few minutes for it to converge naturally; for high precision use PTP or a closer NTP server

⚠️ Keeps printing Connecting, never GOT IP (can't connect to the router)
Cause: wrong SSID/password, the router is on 5GHz, or the signal is too weak
Fix: double-check ROUTER_SSID/ROUTER_PWD in sntp_demo/main.c match the router exactly; confirm the router is 2.4GHz (the board doesn't support 5GHz); try moving the board closer to the router

⚠️ Serial device not found / can't open
Cause: USB-to-serial driver not installed, insufficient permission, or the cable only charges and can't transfer data
Fix: on Linux check the device with lsusb/dmesg; if permission denied run sudo chmod 666 /dev/ttyUSB0; on Windows install the driver and check the COM port in Device Manager; try a data-capable cable

⚠️ Flashing keeps waiting / fails
Cause: download mode wasn't entered, wrong baud rate, or a wrong serial number
Fix: press and hold EN during flashing to enter download mode as prompted; change p=/dev/ttyUSB0 to your actual serial port; try another USB port or cable

Self-Check

The Beijing time printed every second on the serial matches the real time — SNTP network time sync is verified.

Released under the MIT License. Build Time 2026-09-11 14:52:23