embedblocks/time-service

1.0.0

Latest
uploaded 12 hours ago
Accurate system time and timestamp service for ESP32 with SNTP sync and manual control. Provides explicit, deterministic time management — no hidden updates, no background sync. All time changes are user-initiated, observable via jump callbacks, and safe for logging systems.

readme

# time-service

![ESP-IDF](https://img.shields.io/badge/ESP--IDF-v5.x-blue)
![Espressif Component Registry](https://img.shields.io/badge/Espressif-Component%20Registry-orange)
![License](https://img.shields.io/badge/license-MIT-green)

ESP-IDF component for **accurate system time management** on ESP32 with SNTP synchronization and manual control.

Instead of letting the system time change silently in the background, this component enforces explicit, observable time control — every change is user-initiated, and every component that cares is notified.

---

## Features

- **Explicit synchronization only** — no hidden background updates
- **Async SNTP sync** with single-flight coalescing — multiple callers share one network request
- **Jump callbacks** — every time change delivers old time, new time, delta, and origin
- **Graceful offline operation** — falls back to build timestamp, works without network stack
- **Manual time set** — accept time from RTC module, user input, or any external source
- **Human-readable conversion** — fixed-format timestamp strings for logs
- **Thread-safe reads** — safe to call `time_service_now()` from any task
- **Configurable SNTP servers** via `menuconfig`

---

## Installation

### Using ESP-IDF Component Manager (Recommended)

```bash
idf.py add-dependency "embedblocks/time_service^1.0.0"
```

Or in your project's `idf_component.yml`:

```yaml
dependencies:
  embedblocks/time_service: "^1.0.0"
```

---

## How It Works

The component acts as a **time authority controller** — a policy layer over `settimeofday()`. The ESP32 RTC and ESP-IDF handle continuous time progression. This component governs who sets the time, when it changes, and how changes are communicated.

```
┌─────────────────────────────────────┐
│           Time Inputs               │
│                                     │
│  SNTP sync  │  User set  │  Build   │
└──────┬──────┴─────┬──────┴────┬─────┘
       │            │           │
       └────────────┼───────────┘
                    ↓
           time_service
           (policy layer)
                    ↓
           settimeofday()
                    ↓
         ESP-IDF + RTC layer
         (continuous progression)
                    ↓
       time_service_now()
```

At init, the component sets the system clock from the build timestamp — no network required. When the application has network connectivity, it can request an explicit SNTP sync via `time_service_sync_async()`.

---

## Time Sources

| Source | When Used | Origin Tag |
|---|---|---|
| SNTP | Explicit `sync_async()` call | `TIME_ORIGIN_SNTP` |
| Build timestamp | Init fallback, no network required | `TIME_ORIGIN_BUILD` |
| User provided | `set_time()` or `set_time_from_human()` | `TIME_ORIGIN_USER` |

---

## Offline Behavior

The component is designed to work without a network stack. If no network interfaces are registered when sync is requested:

- SNTP is skipped immediately with a warning log
- Callback fires with `success = false`
- System time is unchanged — continues on build time or last user-set time
- No crash, no assert, no attempt to initialize the network stack

---

## API

### Initialization

```c
esp_err_t time_service_init(time_init_result_t *result);
```

Safe to call before network initialization. Always succeeds.

---

### Core

```c
time_t    time_service_now(void);
esp_err_t time_service_set_time(time_t t);
esp_err_t time_service_set_source(time_sync_source_t source);
void      time_service_get_status(time_status_t *status);
```

---

### Async Sync

```c
esp_err_t time_service_sync_async(time_sync_cb_t cb);
```

Non-blocking. If a sync is already in progress, the callback is coalesced — one network request, all callbacks fire on completion.

---

### Human-Readable Time

```c
bool   time_service_to_human(time_t epoch, int32_t offset_sec, time_human_t *out);
bool   time_service_now_human(int32_t offset_sec, time_human_t *out);
size_t time_service_format(const time_human_t *ht, char *buf, size_t len);
size_t time_service_now_str(int32_t offset_sec, char *buf, size_t len);
esp_err_t time_service_set_time_from_human(const time_human_t *ht);
```

Fixed output format: `YYYY-MM-DD HH:MM:SS ±HHMM`

GMT offset is specified in **seconds** (e.g. `18000` for UTC+5 / Pakistan Standard Time).

---

## Usage Example

### Offline — Manual Time Set

```c
#include "time_service.h"

void app_main(void)
{
    time_init_result_t result;
    time_service_init(&result);

    // Set time manually (from RTC module, user input, etc.)
    time_service_set_time(1705314600);

    char buf[TIME_SERVICE_STR_BUF_SIZE];
    while (1) {
        time_service_now_str(18000, buf, sizeof(buf));
        ESP_LOGI(TAG, "[%s] running", buf);
        vTaskDelay(pdMS_TO_TICKS(2000));
    }
}
```

---

### Online — SNTP Sync After WiFi Connect

```c
#include "time_service.h"

static void on_sync_done(const time_sync_result_t *res)
{
    if (res->success) {
        ESP_LOGI(TAG, "Synced — jumped %ld sec via %d",
                 (long)res->jump.delta_sec, res->jump.origin);
    } else {
        ESP_LOGW(TAG, "Sync failed");
    }
}

void app_main(void)
{
    time_init_result_t result;
    time_service_init(&result);

    // ... connect WiFi ...

    // Request sync once network is ready
    vTaskDelay(pdMS_TO_TICKS(2000)); // allow stack to settle
    time_service_sync_async(on_sync_done);

    char buf[TIME_SERVICE_STR_BUF_SIZE];
    while (1) {
        time_service_now_str(18000, buf, sizeof(buf));
        ESP_LOGI(TAG, "[%s]", buf);
        vTaskDelay(pdMS_TO_TICKS(2000));
    }
}
```

---

## Configuration

Via `idf.py menuconfig` → **Time Service Configuration**:

| Key | Default | Description |
|---|---|---|
| `TIME_SERVICE_SNTP_SERVER_0` | `pool.ntp.org` | Primary SNTP server |
| `TIME_SERVICE_SNTP_SERVER_1` | `time.google.com` | Secondary SNTP server |
| `TIME_SERVICE_SNTP_SERVER_2` | `time.windows.com` | Tertiary SNTP server |
| `TIME_SERVICE_SNTP_TIMEOUT_SEC` | `10` | Sync timeout in seconds |
| `TIME_SERVICE_MAX_SYNC_CALLBACKS` | `8` | Max coalesced callbacks per sync |
| `TIME_SERVICE_TASK_STACK_SIZE` | `4096` | Internal task stack (bytes) |
| `TIME_SERVICE_TASK_PRIORITY` | `5` | Internal task FreeRTOS priority |

---

## Network Requirements

The component does **not** manage the network stack. The application is responsible for:

- `esp_netif_init()`
- `esp_event_loop_create_default()`
- WiFi or Ethernet initialization and connection

`time_service_sync_async()` should only be called after IP connectivity is established. If called without a network, it fails gracefully via the callback.

---

## Chip Support

| Chip | Status |
|---|---|
| ESP32 | ✅ Tested |
| ESP32-S2 | ⚠️ Expected to work |
| ESP32-S3 | ⚠️ Expected to work |
| ESP32-C3 | ⚠️ Expected to work |

---

## Examples

| Example | Description |
|---|---|
| `offline` | Sets time manually via `set_time()` and logs human-readable timestamps. No network required. |
| `online` | Connects to WiFi using `example_connect`, performs SNTP sync, and logs timestamps with jump notifications. |

### Running an Example

```bash
cd examples/offline
idf.py build flash monitor
```

```bash
cd examples/online
idf.py build flash monitor
```

For the online example, configure your WiFi credentials first:

```bash
idf.py menuconfig   # → Example Connection Configuration
```

---

## Limitations

- No automatic drift correction — sync is always explicit
- No DST handling — UTC offset is fixed, specified by the caller
- No custom timestamp format strings — one fixed, sortable format
- No deinit — component runs for the lifetime of the application
- Single internal sync task — concurrent sync requests are coalesced, not parallelized

---

## License

MIT License

Links

Supports all targets

License: MIT

To add this component to your project, run:

idf.py add-dependency "embedblocks/time-service^1.0.0"

download archive

Stats

  • Archive size
    Archive size ~ 33.94 KB
  • Downloaded in total
    Downloaded in total 0 times
  • Downloaded this version
    This version: 0 times

Badge

embedblocks/time-service version: 1.0.0
|