Trigger an action every "exact" second after syncing with NTP Server

kb3523
Posts: 10
Joined: Thu Mar 08, 2018 11:32 pm

Trigger an action every "exact" second after syncing with NTP Server

Postby kb3523 » Mon Mar 12, 2018 9:32 am

Dear Guys and Girls,
/* you can also fast forward to my last post ;) */

How would you accomplish the above mentioned Task? (Using ESP-IDF)
I'm thinking of something similar:

1 Sync Time with NTP Service from a server and set the local clock in the ESP32 (this is well documented and working)
2 Read current usecond till next sec, register a Timer#1 to Trigger when the second arrives
3 In the callback function of the Timer#1 start Timer #2 with the interval of 1 sec.

Could you point me in an other direction? Or is this a correct approach? (I'm trying to sync several esp32 nodes to send sensor data every second).

Thank you :)
Last edited by kb3523 on Sat Mar 24, 2018 9:49 am, edited 3 times in total.

thethinker
Posts: 58
Joined: Thu Mar 01, 2018 1:26 am

Re: Sync with NTP and trigger an interrupt every second the clock ticks

Postby thethinker » Tue Mar 13, 2018 5:34 pm

kb3523 wrote:Dear ESP32 Guys and Girls,

How would you accomplish the above mentioned Task? (Using ESP-IDF)
I'm thinking of something similar:

1 Sync Time with NTP Service from a server and set the local clock in the ESP32 (this is well documented and working)
2 Read current usecond till next sec, register a Timer#1 to Trigger when the second arrives
3 In the callback function of the Timer#1 start Timer #2 with the interval of 1 sec.

Could you point me in an other direction? Or is this a correct approach? (I'm trying to sync several esp32 nodes to send sensor data every second).

Thank you :)
Check this out:
https://github.com/espressif/esp-idf/bl ... pts.h#L132

kb3523
Posts: 10
Joined: Thu Mar 08, 2018 11:32 pm

Re: Sync with NTP and trigger an interrupt every second the clock ticks

Postby kb3523 » Sat Mar 17, 2018 8:38 am

Thank you for your hint @thethinker!

Is SNTP_GET_SYSTEM_TIME under the surface the same as calling gettimeofday()?

Code: Select all

#define SNTP_GET_SYSTEM_TIME(sec, us) \
    do { \
        struct timeval tv = { .tv_sec = 0, .tv_usec = 0 }; \
        gettimeofday(&tv, NULL); \
        (sec) = tv.tv_sec;  \
        (us) = tv.tv_usec; \
    } while (0);
I came up with the following solution (at last in the post) which starts the event with an accuracy of 1000usec. But I'm polling on the usec value for a short while, to get a timer started as soon the time reaches a nearby "whole" second:

Code: Select all

while (temp.tv_usec/10 < 99999)
			{
				gettimeofday(&temp, NULL);
			}
Is there a nicer non blocking solution for this? As i continued reading about timers I found that the systems time keeping is done by the frc1 timer - is it possible to read out at which "count" of the timer the seconds get raised?

If someone coincidentally reads my code through and notices any other newbie programming mistakes or just anything what should be done otherwise, any information is appreciated.

Code: Select all

#include <string.h>
#include <time.h>
#include <sys/time.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "esp_system.h"
#include "esp_wifi.h"
#include "esp_event_loop.h"
#include "esp_log.h"
#include "esp_attr.h"
#include "nvs_flash.h"
#include "lwip/err.h"
#include "apps/sntp/sntp.h"
#include <sys/types.h> //contains struct timeval;
#include "apps/sntp/sntp_opts.h"

#define EXAMPLE_WIFI_SSID "mySSID" //CONFIG_WIFI_SSID
#define EXAMPLE_WIFI_PASS "myPW"//CONFIG_WIFI_PASSWORD

static EventGroupHandle_t wifi_event_group;
const int CONNECTED_BIT = BIT0;
static const char *TAG = "#Debug:";

time_t now;
struct tm timeinfo;

TaskHandle_t timer_start_task_handle;
TimerHandle_t tmr1; //Timer to Start the Event (Not Reloading)
TimerHandle_t tmr2; //Timer to Do Something every second, after the event started
int id1 = 1;
int id2 = 2;
int interval1 = 5000;
int interval2 = 1000;
char strftime_buf[64];

static void obtain_time(void);
static void initialize_sntp(void);
static void initialize_wifi(void);
static esp_err_t event_handler(void *ctx, system_event_t *event);
static void timer_start_task(void *pvParameter);
static void timerOneCallBack( TimerHandle_t xTimer );
static void timerTwoCallBack( TimerHandle_t xTimer );

void app_main()
{

    time(&now);
    localtime_r(&now, &timeinfo);
    tmr1 = xTimerCreate("StartTimer", pdMS_TO_TICKS(interval1), pdFALSE, ( void * )id1, &timerOneCallBack);
    tmr2 = xTimerCreate("SecTimer", pdMS_TO_TICKS(interval2), pdTRUE, ( void * )id2, &timerTwoCallBack);
    // Is time set? If not, tm_year will be (1970 - 1900).
    if (timeinfo.tm_year < (2016 - 1900)) {
        ESP_LOGI(TAG, "Time is not set yet. Connecting to WiFi and getting time over NTP.");
        obtain_time();
        // update 'now' variable with current time
        time(&now);
    }

    // Set Timezone to GMT+1
    setenv("TZ", "CET-1CEST,M3.5.0,M10.5.0/3", 1);
    tzset();
    localtime_r(&now, &timeinfo);
    strftime(strftime_buf, sizeof(strftime_buf), "%c", &timeinfo);
    ESP_LOGI(TAG, "The current date/time in Budapest is: %s", strftime_buf);

    xTaskCreate(&timer_start_task, "time_printf_task", 2048, NULL, 5, &timer_start_task_handle);

}

static void timerOneCallBack( TimerHandle_t xTimer )
{
	xTimerStart(tmr2, 0);
	struct timeval tmp_time = {0};
	SNTP_GET_SYSTEM_TIME(tmp_time.tv_sec, tmp_time.tv_usec); //Is it the same as using gettimeofaday(&tmp_time);
	ESP_LOGI(TAG,"###Ring Ring! Timer1 Callback activated.###");
	ESP_LOGI(TAG, "sec holds: %d", (int)tmp_time.tv_sec);
	ESP_LOGI(TAG, "usec holds: %d", (int)tmp_time.tv_usec);

	time(&now);

	localtime_r(&now, &timeinfo);
    strftime(strftime_buf, sizeof(strftime_buf), "%c", &timeinfo);
    ESP_LOGI(TAG, "The current date/time in Budapest / Vienna / Brüssel  is: %s", strftime_buf);
}

static void timerTwoCallBack( TimerHandle_t xTimer )
{
	ESP_LOGI(TAG,"###Ring Ring! Timer2 Callback activated.###");
}


static void obtain_time(void)
{
    ESP_ERROR_CHECK( nvs_flash_init() );
    initialize_wifi();
    xEventGroupWaitBits(wifi_event_group, CONNECTED_BIT,
                        false, true, portMAX_DELAY);
    initialize_sntp();

    // wait for time to be set
    time_t now = 0;
    struct tm timeinfo = { 0 };
    int retry = 0;
    const int retry_count = 10;
    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(1000 / portTICK_PERIOD_MS);
        time(&now);
        localtime_r(&now, &timeinfo);
    }

    ESP_ERROR_CHECK( esp_wifi_stop() ); //Remove if MQTT message is implemented
}

static void initialize_sntp(void)
{
    ESP_LOGI(TAG, "Initializing SNTP");
    sntp_setoperatingmode(SNTP_OPMODE_POLL);
    sntp_setservername(0, "pool.ntp.org");
    sntp_init();
}

static void initialize_wifi(void)
{
    tcpip_adapter_init();
    wifi_event_group = xEventGroupCreate();
    ESP_ERROR_CHECK( esp_event_loop_init(event_handler, NULL) );
    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
    ESP_ERROR_CHECK( esp_wifi_init(&cfg) );
    ESP_ERROR_CHECK( esp_wifi_set_storage(WIFI_STORAGE_RAM) );
    wifi_config_t wifi_config = {
        .sta = {
            .ssid = EXAMPLE_WIFI_SSID,
            .password = EXAMPLE_WIFI_PASS,
        },
    };
    ESP_LOGI(TAG, "Setting WiFi configuration SSID %s...", wifi_config.sta.ssid);
    ESP_ERROR_CHECK( esp_wifi_set_mode(WIFI_MODE_STA) );
    ESP_ERROR_CHECK( esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config) );
    ESP_ERROR_CHECK( esp_wifi_start() );
}

void timer_start_task(void *pvParameter)
{

    while(1)
    {
		// The Start time of the logging event - Later it will be received via MQTT
		struct tm start_time = {
				.tm_year = 118,
				.tm_mon = 2, //0 for January?
				.tm_mday = 17,
				.tm_hour = 10,
				.tm_min = 29,
				.tm_sec = 0,
				.tm_isdst = 1,
		};
		mktime(&start_time);
		strftime(strftime_buf, sizeof(strftime_buf), "%c", &start_time);
		ESP_LOGI(TAG, "The saved start_time is: %s", strftime_buf);

		time(&now);
    	localtime_r(&now, &timeinfo);

		strftime(strftime_buf, sizeof(strftime_buf), "%c", &timeinfo);
		ESP_LOGI(TAG, "The current date/time is: %s", strftime_buf);
		vTaskDelay(250 / portTICK_PERIOD_MS);

		time_t raw_start_time;
		time_t raw_now; // additional variable is not really needed
		raw_start_time = mktime(&start_time);
		time(&raw_now);
		int difi;
		signed int tmp_difi = difftime(raw_now, raw_start_time);
		if (tmp_difi > 0) {
			ESP_LOGI(TAG, "Start Time was in the past");
			break;
		}
		else
		{
			difi = abs(tmp_difi);
		}
		ESP_LOGI(TAG, "Difftime in Secs until event start is: %d", difi);

		struct timeval temp;
		SNTP_GET_SYSTEM_TIME(temp.tv_sec, temp.tv_usec);
		if ((int)temp.tv_usec/10 > 99999)
		{
			xTimerChangePeriod(tmr1, difi*100, 0);
			if( xTimerStart(tmr1, 0 ) != pdPASS )
			{
				ESP_LOGI(TAG, "Timer start error");
			}
			else {
				ESP_LOGI(TAG, "Timer started");
			}
		}
		else {
			while (temp.tv_usec/10 < 99999)
			{
				gettimeofday(&temp, NULL);
			}
			xTimerChangePeriod(tmr1, (difi-1)*100, 0);
			if( xTimerStart(tmr1, 0 ) != pdPASS )
			{
				ESP_LOGI(TAG, "Timer start error");
			}
			else {
				ESP_LOGI(TAG, "Timer started");
			}
		}
		break;
    }
    vTaskDelete(0);
}

static esp_err_t event_handler(void *ctx, system_event_t *event)
{
    switch(event->event_id) {
    case SYSTEM_EVENT_STA_START:
        esp_wifi_connect();
        break;
    case SYSTEM_EVENT_STA_GOT_IP:
        xEventGroupSetBits(wifi_event_group, CONNECTED_BIT);
        break;
    case SYSTEM_EVENT_STA_DISCONNECTED:
        /* This is a workaround as ESP32 WiFi libs don't currently
           auto-reassociate. */
        esp_wifi_connect();
        xEventGroupClearBits(wifi_event_group, CONNECTED_BIT);
        break;
    default:
        break;
    }
    return ESP_OK;
}
Terminal Output:

Code: Select all

I (3552) #Debug:: The current date/time in Budapest is: Sat Mar 17 09:28:54 2018
I (3552) #Debug:: The saved start_time is: Sat Mar 17 09:29:00 2018
I (3562) #Debug:: The current date/time is: Sat Mar 17 09:28:54 2018
I (3822) #Debug:: Difftime in Secs until event start is: 6
I (4052) #Debug:: Timer started
I (9052) #Debug:: ###Ring Ring! Timer1 Callback activated.###
I (9052) #Debug:: sec holds: 1521275339
I (9052) #Debug:: usec holds: 997817
I (9052) #Debug:: The current date/time in Budapest / Vienna / Brüssel  is: Sat Mar 17 09:29:00 2018
I (10052) #Debug:: ###Ring Ring! Timer2 Callback activated.###
I (11052) #Debug:: ###Ring Ring! Timer2 Callback activated.###
Thank you!

kb3523
Posts: 10
Joined: Thu Mar 08, 2018 11:32 pm

Re: Trigger a Function every "exact" second after syncing with NTP Server.

Postby kb3523 » Sat Mar 24, 2018 9:40 am

Hello everyone,

I’m still looking for a better approach on how to get an action happen every second the sntp synced internal clock ticks.. I would really like to know how someone with experience in programming would try to solve this. I read everything what could have something to do with this..

SNTP_SET_SYSTEM_TIME_US(t, us)[/code] uses settimeofaday() which leads to set_boot_time() in time.c.
I see the defines, and I realize that it works differently if RTC or FRC is used.
For RTC: (This is the setting which is also available from menuconfig?)
RTC_SLOW_CLK_CAL_REG
RTC_CLK_CAL_FRACT
RTC_BOOT_TIME_LOW_REG
RTC_BOOT_TIME_HIGH_REG
which hide the name of RTC_CNTL_STORE 1 to 4 registers.

(What I understand it is possible that WITH_FRC and WITH_RTC are also set, for deep sleep and non deep sleep timekeeping? In this case is esp_set_time_from_rtc used for waking up from deep sleep?)

But I cant figure out where the time is set for FRC.
s_boot_time holds the time epoche. But how is this incremented and passed to the FRC timer? How could I trigger an interrupt / callback every time it increments a sec?

I found the frc_timer_reg.h but I do not see how this connects with time.c or sntp. It also mentions “legacy timers” - is it old and shouldnt be used? The readme of the example says FRC1 timer should be used when the ESP32 is running. (That would be my use case)

My project is not using sleep, the goal is to start several esp32s, sync them with an sntp server running on a raspberry and publish to them a start time over mqtt and they should start sending measurement data every second after the start time passed.. I have a solution already, but I feel that this is far from good solution.

Edited Later:
I just found this from ESP_Sprite int the "new chip" topic
You can set the RTC to a certain date using (S)NTP or another method, then use mktime() to get the Unix timestamp (aka the amount of seconds since midnight 1-1-1970) of whatever date/time you want to have an alarm on, get the current Unix timestamp by calling time() and subtracting the two to get the amount of seconds to wait. Then wait that amount, using either something like vTaskDelay or deep sleep, and you're there.
now I belive that I overcomplicated this a bit... I also found for the necessary "busy wait" an other idea I can try from Kolban :
In ESP32 assembler, there is a special register called CCOUNT that increments (internally and in the hardware) every processor clock cycle.

meowsqueak
Posts: 151
Joined: Thu Jun 15, 2017 4:54 am
Location: New Zealand

Re: Trigger an action every "exact" second after syncing with NTP Server

Postby meowsqueak » Mon Mar 26, 2018 4:26 am

You can also use vTaskDelayUntil() in ESP-IDF v3.0rc for a slightly more accurate delay, if you need to take into consideration some processing between reading the current time and the task actually going to sleep, although note that your task is only going to start running on a scheduler tick boundary, which is 10 milliseconds by default.

egionet
Posts: 3
Joined: Thu Sep 26, 2024 4:34 am

Re: Trigger an action every "exact" second after syncing with NTP Server

Postby egionet » Thu Sep 26, 2024 4:41 am

Take a look at the following example: https://github.com/K0I05/esp32-s3/tree/ ... datalogger

The task_schedule.h component is a user-defined task delay that synchronizes with the system clock and accounts for the time it takes to run the task (~10 ms resolution). In the example, the component is configured to run the task once every 10-seconds at 0-seconds into the interval (i.e. 12:00:00, 12:00:10, 12:00:20, etc.).

Who is online

Users browsing this forum: Google [Bot] and 103 guests