Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ jobs:
- name: Build
run: cmake --build build -j 4

- name: FreeRTOS PAL header compatibility
run: cmake --build build --target check_freertos_pal

- name: Test
run: ctest --test-dir build --output-on-failure --no-tests=error --timeout 180

Expand Down
6 changes: 5 additions & 1 deletion .gitlab-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,9 @@ test:
echo "Route them through the log_tag_* macros in common/log.h instead."
exit 1
fi
# Reproduce the pinned upstream-header checks; the host CTest matrix below
# separately exercises PAL behavior against test shims.
- cmake --build ${CMAKE_BUILD_DIR} --target check_freertos_pal
# cd into the build dir rather than using `--test-dir` (the CI image's
# ctest does not honour that flag and would silently find no tests).
- cd ${CMAKE_BUILD_DIR}
Expand All @@ -87,6 +90,7 @@ test:memory-leak:
VG=modules/iot-client/test/run_valgrind_check.sh
chmod +x "$VG"
# Leak-check the pure (no-subprocess) binaries, grouped by module:
# pal : pal_freertos_time_*_test, pal_posix_time_test
# rtc-tcp-client : tai_unit_tests, tai_log_level_zero_tests,
# tai_log_module_zero_tests, tai_integration_tests
# iot-client : iot_cipher_test, iot_ai_ctrl_test
Expand All @@ -96,7 +100,7 @@ test:memory-leak:
# test, so there is nothing to leak-check for it here. The mock-driven iot
# tests are covered by the `test` job; under valgrind their Python
# handshakes would time out.
for t in biz_ai_role_test tai_unit_tests tai_log_level_zero_tests tai_log_module_zero_tests tai_integration_tests iot_cipher_test iot_ai_ctrl_test tuya_ble_test; do
for t in pal_freertos_time_16_1000_test pal_freertos_time_32_1000_test pal_freertos_time_32_100_test pal_freertos_time_32_128_test pal_freertos_time_32_2000_test pal_freertos_time_64_1000_test pal_posix_time_test biz_ai_role_test tai_unit_tests tai_log_level_zero_tests tai_log_module_zero_tests tai_integration_tests iot_cipher_test iot_ai_ctrl_test tuya_ble_test; do
echo "==================== valgrind: ${t} ===================="
"$VG" "./${CMAKE_BUILD_DIR}/${t}" 2>&1 | tee "valgrind-${t}.log"
rc=${PIPESTATUS[0]}
Expand Down
22 changes: 16 additions & 6 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ ctest --test-dir build --output-on-failure --no-tests=error --timeout 180 # ne
`run_leaks_check.sh ./build/iot_cipher_test` (macOS) — no-subprocess binaries only; under
valgrind the Python mock handshakes time out. Both scripts **ignore the test's exit code**: a
suite failing every assertion still prints "RESULT: NO MEMORY LEAKS DETECTED" and exits 0.
Coverage is 3 of 13 binaries, hand-listed in `.gitlab-ci.yml`; GitHub's sanitizer job sets
GitLab hand-lists the no-subprocess binaries, including PAL clock tests, in
`.gitlab-ci.yml`'s valgrind loop; GitHub's sanitizer job sets
`detect_leaks=0`, so nothing else checks leaks anywhere.
- **Examples** — a separate top-level project, and `AGENTIC_KIT_BUILD_EXAMPLES` defaults OFF, so
a plain root `cmake --build build` builds none:
Expand Down Expand Up @@ -98,9 +99,17 @@ ctest --test-dir build --output-on-failure --no-tests=error --timeout 180 # ne
`TAI_LOG*`, `TUYA_BLE_HAL_LOG*`). A direct `malloc` or
`printf` is a porting bug even where it links on the host -- and the `test` CI job greps for
raw `log_emit(`/`printf(` call sites outside `common/log.{h,c}` and the test trees, so one
fails the pipeline. The one deliberate gap: `pal_t` has
only a monotonic `time_ms`, so ATOP signing reads libc `time(NULL)` — a port needs a real-time
clock the C library can see, or every signed request carries a `t` the cloud rejects.
fails the pipeline. `pal_t.time_ms` supplies monotonic elapsed time;
`pal_t.wal_clock_time` supplies Unix UTC milliseconds for cloud signing and media
timestamps. POSIX reads `CLOCK_REALTIME`; the FreeRTOS default UTC callback
reads libc `gettimeofday()` and returns zero on a failed read or non-positive
Unix seconds. Its platform/libc must provide that API and a UTC source, maintained
by the app through RTC/SNTP; without the API, adapt the PAL or provide a libc shim.
If that source does not fit the platform, clone the factory PAL into one
long-lived table, replace its UTC callback before
initialization, and share that PAL across IoT and TAI. The app owns UTC
synchronization. When TLS certificate-time checks are enabled, the port must
also expose that UTC source to libc/mbedTLS.
6. **CHANGELOG entries are terse, and carry a PR number.** One line per change —
`- <module> — <what changed>(#<PR>).` — with indented sub-bullets only for specifics a reader
acts on (a new symbol, a changed default, a migration step). `## [0.3.0]` is the reference for
Expand Down Expand Up @@ -132,8 +141,9 @@ ordinary changes.
`CMakeLists.txt` — `agentic_kit_add_iot_test(name source)` for an iot-client suite,
`add_executable` + `add_test` otherwise; a binary that spawns no subprocess also goes in
`.gitlab-ci.yml`'s valgrind list. Each suite derives its count from the calls it actually made,
and no target in this project enables `-Wall`, so an uncalled `static` test is not even a
warning. Never delete `CMakeLists.txt`'s explicit `enable_testing()` (the comment above it says
and most suites do not enable `-Wall`, so an uncalled `static` test need not produce a
warning. PAL clock tests use `-Wall -Wextra -Werror`. Never delete `CMakeLists.txt`'s
explicit `enable_testing()` (the comment above it says
why): re-configures would silently stop regenerating the ctest list, `ctest` exits **0** on an
empty list, and GitLab's test stage has no `--no-tests=error`.

Expand Down
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Examples — `unbind_demo` checks cloud binding status before client initialization/reconnect and after a removal notice (#44).
- Examples — `activate_demo --release` prints cloud binding status before and after reset (#44).

### Changed

- **BREAKING** pal — mandatory `wal_clock_time` provides Unix UTC milliseconds; `time_ms` keeps its monotonic timing contract (#45).
- Custom PALs must supply `wal_clock_time` and rebuild SDK/PAL consumers.
- FreeRTOS defaults to libc `gettimeofday()`; platforms using another UTC source can override the callback. POSIX keeps `CLOCK_REALTIME`.
- iot-client — cloud signing and DP timestamps use `wal_clock_time()` directly, converting UTC milliseconds to seconds (#45).
- iot-client, rtc-tcp-client — ATOP signing, non-empty DP reports and `tai_connect()` warn when PAL UTC is unavailable instead of silently sending zero timestamps (#45).
- rtc-tcp-client — media and Ping timestamps use PAL UTC milliseconds instead of monotonic time (#45).

### Fixed

- pal — FreeRTOS `time_ms` preserves tick wraps and converts non-integral tick periods correctly (#45).
- biz-client — role request timestamps and the clock gate read PAL UTC instead of libc time, so ports without a synchronized libc clock work (#45).

## [0.5.0] - 2026-09-24

### Added
Expand Down
45 changes: 45 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,51 @@ if(AGENTIC_KIT_BUILD_TESTS AND AGENTIC_KIT_ENABLE_PROJECT_TESTS)
set(IOT_CLIENT_TESTS_DIR "${CMAKE_CURRENT_SOURCE_DIR}/modules/iot-client/test")
set(TUYA_BLE_TESTS_DIR "${CMAKE_CURRENT_SOURCE_DIR}/modules/tuya-ble/test")

# Compile the actual FreeRTOS PAL against host-only kernel/lwIP shims and
# exercise its clock callbacks. The test shims are never IDF AK_SRCS.
foreach(clock_config IN ITEMS "16_1000" "32_1000" "32_100" "32_128" "32_2000" "64_1000")
string(REPLACE "_" ";" clock_parts "${clock_config}")
list(GET clock_parts 0 clock_bits)
list(GET clock_parts 1 clock_hz)
set(clock_target "pal_freertos_time_${clock_config}_test")
add_executable(${clock_target}
"${PAL_DIR}/pal_freertos.c" "${PAL_DIR}/test/test_freertos_time.c")
target_include_directories(${clock_target} PRIVATE
"${PAL_DIR}/test" "${PAL_DIR}" "${COMMON_DIR}")
target_compile_definitions(${clock_target} PRIVATE
TEST_TICK_BITS=${clock_bits} TEST_TICK_HZ=${clock_hz}
AGENTIC_KIT_LOG_LEVEL=0 gettimeofday=pal_test_gettimeofday)
if(CMAKE_C_COMPILER_ID MATCHES "GNU|Clang")
target_compile_options(${clock_target} PRIVATE -Wall -Wextra -Werror)
endif()
add_test(NAME ${clock_target} COMMAND ${clock_target})
endforeach()

# Inject clock_gettime only in this target to exercise the production
# POSIX PAL when UTC is unset, unavailable, or corrected.
add_executable(pal_posix_time_test
"${PAL_DIR}/pal_posix.c" "${PAL_DIR}/test/test_posix_time.c")
target_include_directories(pal_posix_time_test PRIVATE
"${PAL_DIR}" "${COMMON_DIR}")
target_compile_definitions(pal_posix_time_test PRIVATE
AGENTIC_KIT_LOG_LEVEL=0 clock_gettime=pal_test_clock_gettime)
target_link_libraries(pal_posix_time_test PRIVATE Threads::Threads)
if(CMAKE_C_COMPILER_ID MATCHES "GNU|Clang")
target_compile_options(pal_posix_time_test PRIVATE -Wall -Wextra -Werror)
endif()
add_test(NAME pal_posix_time_test COMMAND pal_posix_time_test)

# Offline cache/path/concurrency regressions run in both CI CTest jobs.
add_test(NAME freertos_pal_cache_test
COMMAND "${Python3_EXECUTABLE}" "${CMAKE_CURRENT_SOURCE_DIR}/tools/test_check_freertos_pal.py")

# Explicit networked check, separate from the offline CTest suite.
add_custom_target(check_freertos_pal
COMMAND "${Python3_EXECUTABLE}" "${CMAKE_CURRENT_SOURCE_DIR}/tools/check_freertos_pal.py"
--cc "${CMAKE_C_COMPILER}"
COMMENT "Check the PAL against pinned official FreeRTOS 10/11 headers"
VERBATIM)

add_executable(tai_unit_tests
${AI_TCP_SOURCES}
"${AI_TCP_TESTS_DIR}/test_core.c"
Expand Down
2 changes: 1 addition & 1 deletion docs-site/docs/concepts.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ PAL 是 Agentic-kit 的可移植边界。SDK 不直接绑定某个 RTOS 或芯
| --- | --- | --- |
| 网络 | `connect / send / recv / close / poll` | 超时、部分收发、TLS 依赖、断网恢复 |
| 并发 | `thread_create / join`、mutex | 线程栈、回调上下文、锁粒度 |
| 系统 | `time_ms`、`malloc / free` | 时钟单调性、内存峰值与碎片 |
| 系统 | `wal_clock_time`、`time_ms`、`malloc / free` | UTC 校时与单调计时、内存峰值与碎片 |

## 物理设备控制:DP 与端侧 MCP {#物理设备控制dp-与端侧-mcp}

Expand Down
3 changes: 2 additions & 1 deletion docs-site/docs/guides/atop-generic-call.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,10 +92,11 @@ void iot_atop_response_free(iot_client_t *client, iot_atop_response_t *response)
```c
#include "iot_atop.h"

uint32_t timestamp = (uint32_t)(client->pal->wal_clock_time() / 1000U);
char body[192];
snprintf(body, sizeof(body),
"{\"schemaId\":\"%s\",\"version\":\"\",\"t\":%u}",
schema_id, (unsigned)time(NULL));
schema_id, (unsigned)timestamp);

iot_atop_request_t req = { .api = "tuya.device.schema.newest.get",
.version = "1.0",
Expand Down
8 changes: 7 additions & 1 deletion docs-site/docs/guides/ota-upgrade.md
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,8 @@ CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_FULL=y

### 3. 初始化与升级查询 {#3-初始化与升级查询}

FreeRTOS PAL 默认通过 `gettimeofday()` 读取 libc UTC,应用需先通过平台 RTC/SNTP 完成校时。只有该 UTC 源不适用时才需要替换回调(见[FreeRTOS UTC 时钟](./porting-to-new-platform.md#freertos-wall-clock))。

```c
#include "iot_client.h"
#include "iot_ota.h"
Expand All @@ -241,8 +243,12 @@ iot_client_config_t iot_cfg = {
.cert_bundle_attach = (tls_cert_bundle_attach_fn)esp_crt_bundle_attach,
};

/* 默认 PAL 读取已由应用同步的 libc UTC,与其他 SDK 模块共用。 */
const pal_t *pal = tai_pal_freertos();
/* iot_init(pal) 必须在 iot_client_init 前调用,否则 iot_client_init 返回 NULL */
iot_init(tai_pal_freertos());
if (iot_init(pal) != OPRT_OK) {
return;
}

iot_client_t *iot = iot_client_init(&iot_cfg);

Expand Down
Loading
Loading