embedblocks/time-service

1.0.2

Latest
uploaded 17 hours ago
Timestamp service for ESP32 that works without a dedicated RTC module. Starts from build time on every boot, then corrects via SNTP sync or manual set — whichever suits the application. All corrections are explicit and observable via jump callbacks, 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 that gives your ESP32 accurate timestamps without a dedicated RTC module.
 
On boot it sets the clock from the build timestamp so time is always running from something reasonable — no network required, no configuration needed. From there you can improve accuracy in whatever way suits your application: sync over SNTP once WiFi is up, set it manually from an external RTC or stored value, or both.
 
When time is corrected, your application is notified with the exact delta — no silent jumps, no surprises.
---

## 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.1"
```

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

```yaml
dependencies:
  embedblocks/time-service: "^1.0.1"
```

---

## 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.2"

download archive

Stats

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

Badge

embedblocks/time-service version: 1.0.2
|