new release
This commit is contained in:
267
components/time_service/src/time_service.c
Normal file
267
components/time_service/src/time_service.c
Normal file
@@ -0,0 +1,267 @@
|
||||
#include "time_service.h"
|
||||
#include "time_service_events.h"
|
||||
#include "network_events.h"
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
|
||||
#include "esp_err.h"
|
||||
#include "esp_event.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_netif_sntp.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <sys/time.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
static const char *TAG = "time_service";
|
||||
|
||||
/*
|
||||
* Portugal continental / Madeira / Canárias partilham esta regra POSIX em ESP-IDF.
|
||||
* Fonte da equivalência Europe/Lisbon -> POSIX: documentação Espressif RainMaker.
|
||||
*/
|
||||
#define TIME_SERVICE_TZ_NAME "Europe/Lisbon"
|
||||
#define TIME_SERVICE_TZ_POSIX "WET0WEST,M3.5.0/1,M10.5.0"
|
||||
|
||||
#define TIME_SERVICE_SYNC_WAIT_MS 15000
|
||||
#define TIME_SERVICE_MIN_VALID_YEAR 2024
|
||||
|
||||
typedef struct {
|
||||
bool timezone_configured;
|
||||
bool sntp_initialized;
|
||||
bool sync_in_progress;
|
||||
bool synced;
|
||||
bool inited;
|
||||
} time_service_runtime_t;
|
||||
|
||||
static time_service_runtime_t s_rt = {
|
||||
.timezone_configured = false,
|
||||
.sntp_initialized = false,
|
||||
.sync_in_progress = false,
|
||||
.synced = false,
|
||||
.inited = false,
|
||||
};
|
||||
|
||||
static TaskHandle_t s_sync_wait_task = NULL;
|
||||
|
||||
static void publish_state(time_service_event_id_t id)
|
||||
{
|
||||
time_service_state_t st = {
|
||||
.timezone_configured = s_rt.timezone_configured,
|
||||
.sntp_initialized = s_rt.sntp_initialized,
|
||||
.sync_in_progress = s_rt.sync_in_progress,
|
||||
.synced = s_rt.synced,
|
||||
.valid_time = time_service_has_valid_time(),
|
||||
.now = time(NULL),
|
||||
};
|
||||
|
||||
(void)esp_event_post(TIME_SERVICE_EVENTS, id, &st, sizeof(st), portMAX_DELAY);
|
||||
}
|
||||
|
||||
static void set_portugal_timezone(void)
|
||||
{
|
||||
setenv("TZ", TIME_SERVICE_TZ_POSIX, 1);
|
||||
tzset();
|
||||
s_rt.timezone_configured = true;
|
||||
ESP_LOGI(TAG, "Timezone set: %s (%s)", TIME_SERVICE_TZ_NAME, TIME_SERVICE_TZ_POSIX);
|
||||
}
|
||||
|
||||
static void time_sync_notification_cb(struct timeval *tv)
|
||||
{
|
||||
(void)tv;
|
||||
s_rt.synced = true;
|
||||
s_rt.sync_in_progress = false;
|
||||
|
||||
time_t now = time(NULL);
|
||||
struct tm tm_now = {0};
|
||||
localtime_r(&now, &tm_now);
|
||||
|
||||
ESP_LOGI(TAG, "Time synchronized: %04d-%02d-%02d %02d:%02d:%02d",
|
||||
tm_now.tm_year + 1900,
|
||||
tm_now.tm_mon + 1,
|
||||
tm_now.tm_mday,
|
||||
tm_now.tm_hour,
|
||||
tm_now.tm_min,
|
||||
tm_now.tm_sec);
|
||||
|
||||
publish_state(TIME_SERVICE_EVENT_SYNC_OK);
|
||||
}
|
||||
|
||||
static void ensure_sntp_initialized(void)
|
||||
{
|
||||
if (s_rt.sntp_initialized)
|
||||
return;
|
||||
|
||||
esp_sntp_config_t cfg = ESP_NETIF_SNTP_DEFAULT_CONFIG("pool.ntp.org");
|
||||
cfg.sync_cb = time_sync_notification_cb;
|
||||
|
||||
esp_err_t err = esp_netif_sntp_init(&cfg);
|
||||
if (err == ESP_OK)
|
||||
{
|
||||
s_rt.sntp_initialized = true;
|
||||
ESP_LOGI(TAG, "SNTP initialized");
|
||||
return;
|
||||
}
|
||||
|
||||
if (err == ESP_ERR_INVALID_STATE)
|
||||
{
|
||||
s_rt.sntp_initialized = true;
|
||||
ESP_LOGW(TAG, "SNTP already initialized elsewhere");
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGE(TAG, "esp_netif_sntp_init failed: %s", esp_err_to_name(err));
|
||||
}
|
||||
|
||||
static void sync_wait_task(void *arg)
|
||||
{
|
||||
(void)arg;
|
||||
|
||||
esp_err_t err = esp_netif_sntp_sync_wait(pdMS_TO_TICKS(TIME_SERVICE_SYNC_WAIT_MS));
|
||||
if (err == ESP_OK)
|
||||
{
|
||||
s_rt.synced = true;
|
||||
s_rt.sync_in_progress = false;
|
||||
ESP_LOGI(TAG, "Initial SNTP sync complete");
|
||||
publish_state(TIME_SERVICE_EVENT_SYNC_OK);
|
||||
}
|
||||
else
|
||||
{
|
||||
s_rt.sync_in_progress = false;
|
||||
ESP_LOGW(TAG, "SNTP sync wait timeout/error: %s", esp_err_to_name(err));
|
||||
publish_state(TIME_SERVICE_EVENT_SYNC_TIMEOUT);
|
||||
}
|
||||
|
||||
s_sync_wait_task = NULL;
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
static esp_err_t start_sync_wait(void)
|
||||
{
|
||||
if (!s_rt.sntp_initialized)
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
|
||||
if (s_rt.sync_in_progress)
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
|
||||
s_rt.sync_in_progress = true;
|
||||
publish_state(TIME_SERVICE_EVENT_SYNC_STARTED);
|
||||
|
||||
BaseType_t ok = xTaskCreate(
|
||||
sync_wait_task,
|
||||
"time_sync_wait",
|
||||
4096,
|
||||
NULL,
|
||||
4,
|
||||
&s_sync_wait_task);
|
||||
|
||||
if (ok != pdPASS)
|
||||
{
|
||||
s_rt.sync_in_progress = false;
|
||||
s_sync_wait_task = NULL;
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static void network_event_handler(void *arg, esp_event_base_t base, int32_t id, void *data)
|
||||
{
|
||||
(void)arg;
|
||||
(void)base;
|
||||
(void)data;
|
||||
|
||||
if (id == NETWORK_EVENT_STA_GOT_IP)
|
||||
{
|
||||
ESP_LOGI(TAG, "NETWORK_EVENT_STA_GOT_IP -> init/check SNTP");
|
||||
ensure_sntp_initialized();
|
||||
|
||||
if (!s_rt.synced && !s_rt.sync_in_progress)
|
||||
{
|
||||
esp_err_t err = start_sync_wait();
|
||||
if (err != ESP_OK)
|
||||
{
|
||||
ESP_LOGW(TAG, "start_sync_wait failed: %s", esp_err_to_name(err));
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (id == NETWORK_EVENT_STA_LOST_IP)
|
||||
{
|
||||
ESP_LOGW(TAG, "NETWORK_EVENT_STA_LOST_IP");
|
||||
publish_state(TIME_SERVICE_EVENT_NETWORK_LOST);
|
||||
}
|
||||
}
|
||||
|
||||
void time_service_init(void)
|
||||
{
|
||||
if (s_rt.inited)
|
||||
{
|
||||
ESP_LOGW(TAG, "time_service_init called twice");
|
||||
return;
|
||||
}
|
||||
|
||||
set_portugal_timezone();
|
||||
|
||||
ESP_ERROR_CHECK(esp_event_handler_register(
|
||||
NETWORK_EVENTS,
|
||||
NETWORK_EVENT_STA_GOT_IP,
|
||||
network_event_handler,
|
||||
NULL));
|
||||
|
||||
ESP_ERROR_CHECK(esp_event_handler_register(
|
||||
NETWORK_EVENTS,
|
||||
NETWORK_EVENT_STA_LOST_IP,
|
||||
network_event_handler,
|
||||
NULL));
|
||||
|
||||
s_rt.inited = true;
|
||||
publish_state(TIME_SERVICE_EVENT_INIT);
|
||||
}
|
||||
|
||||
bool time_service_is_synced(void)
|
||||
{
|
||||
return s_rt.synced;
|
||||
}
|
||||
|
||||
bool time_service_is_sync_in_progress(void)
|
||||
{
|
||||
return s_rt.sync_in_progress;
|
||||
}
|
||||
|
||||
bool time_service_has_valid_time(void)
|
||||
{
|
||||
time_t now = time(NULL);
|
||||
if (now <= 0)
|
||||
return false;
|
||||
|
||||
struct tm tm_now = {0};
|
||||
localtime_r(&now, &tm_now);
|
||||
return (tm_now.tm_year + 1900) >= TIME_SERVICE_MIN_VALID_YEAR;
|
||||
}
|
||||
|
||||
esp_err_t time_service_force_resync(void)
|
||||
{
|
||||
if (!s_rt.sntp_initialized)
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
|
||||
if (s_rt.sync_in_progress)
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
|
||||
return start_sync_wait();
|
||||
}
|
||||
|
||||
const char *time_service_get_tz_name(void)
|
||||
{
|
||||
return TIME_SERVICE_TZ_NAME;
|
||||
}
|
||||
|
||||
const char *time_service_get_tz_posix(void)
|
||||
{
|
||||
return TIME_SERVICE_TZ_POSIX;
|
||||
}
|
||||
|
||||
time_t time_service_get_epoch(void)
|
||||
{
|
||||
return time(NULL);
|
||||
}
|
||||
3
components/time_service/src/time_service_events.c
Normal file
3
components/time_service/src/time_service_events.c
Normal file
@@ -0,0 +1,3 @@
|
||||
#include "time_service_events.h"
|
||||
|
||||
ESP_EVENT_DEFINE_BASE(TIME_SERVICE_EVENTS);
|
||||
Reference in New Issue
Block a user