diff --git a/AGENTS.md b/AGENTS.md index bfe5c66..bfb9092 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -83,7 +83,11 @@ ctest --test-dir build --output-on-failure --no-tests=error --timeout 180 # ne 3. **`modules/*/include/` is the public API; `src/` is not** — but the host build puts iot-client's `src/` on the *PUBLIC* include path, so app code that includes a private header compiles here and fails on ESP-IDF (two shipped demos do this). To expose something, promote - the declaration into `iot_client.h` with `IOT_API`. + the declaration into `iot_client.h` with `IOT_API`. The `*_config_defaults.h` knob-default + sheets are the one deliberate exception: they sit on the public include path because they + are integration config surface (integrators derive buffer sizes from the `AGENTIC_KIT_*` + defaults; the ESP-IDF component compiles against that path), but they are not API — the + stable names are the knobs themselves, not the files' contents. 4. **Every new `.c` goes in two source lists**: the root `CMakeLists.txt` and `AK_SRCS` in `examples/esp-idf/components/agentic_kit/CMakeLists.txt`. No CI job runs `idf.py`, so an omission is invisible until someone flashes a board. They diverge on purpose — `pal_posix.c` @@ -298,7 +302,7 @@ ordinary changes. leaving both NULL is not "use the system trust store" (there is none on an embedded target), it connects with verification disabled behind one `log_warn`. - **`iot_client_process(client, timeout_ms)` ignores `timeout_ms`.** The real blocking budget is - the compile-time `MQTT_RECV_TIMEOUT_MS` (1000 ms), and the CONNECT sets a 60 s keepalive. Do + the compile-time `AGENTIC_KIT_MQTT_RECV_TIMEOUT_MS` (1000 ms), and the CONNECT sets a 60 s keepalive. Do not use the argument to pace the app loop. - **`iot_client_connect()` never refreshes the CA and never retries.** The app owns cert recovery: on `OPRT_TLS_HANDSHAKE_FAILED`, call `iot_get_ca_certificate()` and reassign diff --git a/CHANGELOG.md b/CHANGELOG.md index cca9dc5..b2fd8af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- SDK-wide — every build-time knob is renamed with an `AGENTIC_KIT_` prefix and its default moves to its owning subsystem's config file; override per product with one `agentic_kit_config.h` on the include path, `-D=`, or `AGENTIC_KIT_USER_CONFIG` (#38). + - **BREAKING** `TAI_FRAG_BUF_SIZE` → `AGENTIC_KIT_TAI_FRAG_BUF_SIZE`, `RESPONSE_BUFFER_SIZE` → `AGENTIC_KIT_RESPONSE_BUFFER_SIZE`, and likewise the rest — old→new table in `docs-site/docs/guides/compile-time-knobs.md`. + - Knob defaults live at: the override pickup in `common/log.h`; PAL task sizing in `pal/pal_config_defaults.h`; per-module knobs in each module's include/ (`iot_client_config_defaults.h`, `tai_config_defaults.h`; tuya-ble has no knobs, so no defaults file until one appears). Non-knob module data (version strings, endpoints, log binding) lives in each module's src-side internal header (`src/iot_internal.h` restores and renames the former `iot_config_defaults.h`). + - Removed the dead `IOT_DO_NOT_USE_CUSTOM_CONFIG` flag from the ESP-IDF component. - tuya-ble — the SDK-internal log-facade binding, scan-token rotation and pending-credential delivery are each defined once in `tuya_ble_internal.h` instead of being repeated per module (PR pending). - tuya-ble — bounded per-state Trsmitr reassembly, queued TX with backpressure, and configurable radio capability (#35). diff --git a/README.md b/README.md index d2beffe..ac35b55 100644 --- a/README.md +++ b/README.md @@ -72,7 +72,7 @@ cmake --build build-examples ## 版本与发布 -SDK 版本定义在 `modules/iot-client/src/iot_config_defaults.h` 的 `SDK_VERSION` 宏中,有两种状态(类似 maven 的 release / `-SNAPSHOT`): +SDK 版本定义在 `modules/iot-client/include/iot_client_config_defaults.h` 的 `SDK_VERSION` 宏中,有两种状态(类似 maven 的 release / `-SNAPSHOT`): - `agentic-kit_X.Y.Z` — 已发布状态(该值对应 `vX.Y.Z` 标签) - `agentic-kit_X.Y.Z-dev` — 开发周期状态 diff --git a/common/core_mqtt_config.h b/common/core_mqtt_config.h index 458da8e..4f467ef 100644 --- a/common/core_mqtt_config.h +++ b/common/core_mqtt_config.h @@ -26,7 +26,7 @@ /* coreMQTT invokes these with a doubly-parenthesised argument -- * LogError( ( "fmt", args ) ) -- so `message` arrives complete with its own * parentheses, which then serve as the call parentheses of the macro below. - * Same shape as the log_* wrappers in iot_config_defaults.h. */ + * Same shape as the log_* wrappers in iot_client_config_defaults.h. */ #define CORE_MQTT_LOG_ERROR( ... ) log_emit(LOG_ERROR, "[mqtt] " __VA_ARGS__) #define CORE_MQTT_LOG_WARN( ... ) log_emit(LOG_WARN, "[mqtt] " __VA_ARGS__) diff --git a/common/log.h b/common/log.h index 09b36a7..ef12e7b 100644 --- a/common/log.h +++ b/common/log.h @@ -20,6 +20,60 @@ #include #include +/* ------------------------------------------------------------------------- + * Build-time config: the integrator-override pickup. Every SDK subsystem + * keeps its knob defaults in its own *_config_defaults.h + * (modules//include/, pal/pal_config_defaults.h); the one thing they + * all share is this header, because every SDK translation unit includes + * it. That makes log.h the right home for the override pickup: it must + * run BEFORE any #ifndef knob default, whichever file that default lives + * in -- and each defaults file includes this header first for exactly + * that reason. + * + * Integrators override by defining a knob FIRST -- pick whichever fits the + * build system (all of them must apply to every target that compiles SDK + * sources, so no translation unit sees a different value): + * 1. Create your own agentic_kit_config.h holding only the knobs you + * want to change (plain #define, no #ifndef), and put its directory + * on the include path (-I / target_include_directories). It is + * picked up automatically, before every default. + * 2. Add -D= to the compile options. + * 3. -DAGENTIC_KIT_USER_CONFIG='"my_kit_opts.h"' names an override + * header with an arbitrary file name (its directory still needs + * to be on the include path) -- for toolchains without __has_include. + * When set, it wins and the agentic_kit_config.h search is skipped. + * + * Why the SDK never owns a file named agentic_kit_config.h: a quoted + * include (and __has_include with quotes) searches the includer's own + * directory (common/) BEFORE the -I path, so an SDK-owned + * common/agentic_kit_config.h could never be shadowed by an integrator's + * same-named file. Reserving that name for the integrator is the whole + * trick (lwIP lwipopts.h / mbedTLS mbedtls_config.h / FreeRTOS + * FreeRTOSConfig.h pattern). + * + * All SDK knobs are prefixed AGENTIC_KIT_ to keep them out of the + * integrator's namespace (coreMQTT's own core_mqtt_config_defaults.h + * defines a same-named MQTT_SEND_TIMEOUT_MS that collided for real). + * Tables, per-knob rationale and migration live in + * docs-site/docs/guides/compile-time-knobs.md. + * + * The pickup runs BEFORE the extern "C" opener: it pulls in the + * integrator's override header, and a C++ translation unit's config must + * not silently acquire C linkage. + * ------------------------------------------------------------------------- */ + +/* Integrator overrides come first, so every #ifndef knob default -- here + * and in every *_config_defaults.h -- loses to them. */ +#ifdef AGENTIC_KIT_USER_CONFIG +#include AGENTIC_KIT_USER_CONFIG +#else +#if defined(__has_include) +#if __has_include("agentic_kit_config.h") +#include "agentic_kit_config.h" +#endif +#endif +#endif + #ifdef __cplusplus extern "C" { #endif diff --git a/docs-site/docs/guides/atop-generic-call.md b/docs-site/docs/guides/atop-generic-call.md index 2132367..3f01a96 100644 --- a/docs-site/docs/guides/atop-generic-call.md +++ b/docs-site/docs/guides/atop-generic-call.md @@ -110,7 +110,7 @@ iot_atop_response_free(client, &resp); /* 每条路径都要调,包括失败 **只支持已激活的设备。** 通用入口用 `devid` + `secret_key` 签名。激活本身用的是 `uuid` + `authkey`,是另一条路径,仍然只能通过 `iot_client_init_on_boarding()` 走。在没有凭据的 client 上调用返回 `OPRT_UNINITIALIZED`。 -**单次响应不能超过 4096 字节,连 HTTP 头一起算。** 状态行、响应头和加密后的响应体共用一个固定大小的缓冲区(`RESPONSE_BUFFER_SIZE`,`http_client_interface.c`),没有分段续读。溢出时底层 coreHTTP 返回 `HTTPInsufficientMemory`,SDK 把它折叠成 `OPRT_COMMUNICATION_ERROR`——和"socket 断了"是同一个返回值,而 coreHTTP 自己的解释性日志在本项目里被编译掉了(`HTTP_DO_NOT_USE_CUSTOM_CONFIG`),所以现场只看得到一次"传输层失败",重试也不会好。扣掉响应头、base64 膨胀和信封字段,**解密后 JSON 的实际上限约 2.8 KB**。返回定长字段的接口绰绰有余;返回列表、DP schema 这类长度随产品增长的接口很容易撞上——遇到这种接口请提 issue,把它变成具名接口的同一个改动里需要把这个常量一起抬上去。 +**单次响应不能超过 4096 字节,连 HTTP 头一起算。** 状态行、响应头和加密后的响应体共用一个固定大小的缓冲区(`AGENTIC_KIT_RESPONSE_BUFFER_SIZE`,默认值与调整说明见 `modules/iot-client/include/iot_client_config_defaults.h`),没有分段续读。溢出时底层 coreHTTP 返回 `HTTPInsufficientMemory`,SDK 把它折叠成 `OPRT_COMMUNICATION_ERROR`——和"socket 断了"是同一个返回值,而 coreHTTP 自己的解释性日志在本项目里被编译掉了(`HTTP_DO_NOT_USE_CUSTOM_CONFIG`),所以现场只看得到一次"传输层失败",重试也不会好。扣掉响应头、base64 膨胀和信封字段,**解密后 JSON 的实际上限约 2.8 KB**。返回定长字段的接口绰绰有余;返回列表、DP schema 这类长度随产品增长的接口很容易撞上——遇到这种接口请提 issue,把它变成具名接口的同一个改动里需要把这个常量一起抬上去。 **请求体原样透传。** SDK 不会改写你的请求体,所以接口要求的字段必须自己带齐——**包括大多数 ATOP 接口在请求体里要求的 `t` 时间戳字段**。SDK 只校验到"能解析成 JSON 对象"为止,目的是把手误变成一个立刻返回的 `OPRT_INVALID_PARAMETER`,而不是花一次 HTTPS 往返换一句含义模糊的云端拒绝。 diff --git a/docs-site/docs/guides/compile-time-knobs.md b/docs-site/docs/guides/compile-time-knobs.md new file mode 100644 index 0000000..fc3b4d5 --- /dev/null +++ b/docs-site/docs/guides/compile-time-knobs.md @@ -0,0 +1,165 @@ +--- +title: 编译期旋钮配置 +sidebar_label: 编译期旋钮 +sidebar_position: 9 +--- + +# 编译期旋钮配置 + +SDK 的全部编译期旋钮——TAI 收发缓冲与调度、MQTT 超时与包大小、ATOP HTTP 缓冲、FreeRTOS 任务栈——共 18 个,默认值按**所属子系统**存放(集成方覆盖的统一挂载点在 `common/log.h`,原因见下文):FreeRTOS 任务旋钮在 `pal/pal_config_defaults.h`;模块旋钮在各自 include/ 目录——`modules/iot-client/include/iot_client_config_defaults.h`(MQTT + ATOP HTTP)、`modules/rtc-tcp-client/include/tai_config_defaults.h`(TAI 缓冲与调度);tuya-ble 目前没有编译期旋钮(原因见下文速查表)。每个旋钮的完整说明(单位、联动、踩过的坑)在各自文件的注释里;本页讲怎么按产品覆盖它们、这套机制为什么长这样,并给出速查表。 + +## 三种覆盖方式(任选其一) {#三种覆盖方式任选其一} + +### 方式一(推荐):自建 `agentic_kit_config.h` {#方式一推荐自建-agentic_kit_configh} + +只写要改的旋钮(普通 `#define`,不用 `#ifndef`),把文件所在目录放进**编译 SDK 源码的 target** 的 include 路径——SDK 编译每个源文件时会自动捡起它,不需要任何 `-D`。无论旋钮属于哪个子系统,都写在这**一个**文件里(`common/log.h` 的捡起逻辑会在所有 `#ifndef` 默认值之前应用它),不需要按模块建多个覆盖文件: + +```c +/* agentic_kit_config.h —— 只写要改的,其余用 SDK 默认 */ +#define AGENTIC_KIT_RESPONSE_BUFFER_SIZE 8192 /* 产品 DP schema 大 */ +#define AGENTIC_KIT_TAI_FRAG_BUF_SIZE 16000U /* ESP32 无 PSRAM:缩小收包缓冲 */ +``` + +ESP-IDF 工程(SDK 以组件形式编译):把文件放进项目任意目录,加一行让组件看得到它—— + +```cmake +# 放在本组件的 CMakeLists.txt 里;路径指向你放 agentic_kit_config.h 的目录 +target_include_directories(${COMPONENT_LIB} PRIVATE "${CMAKE_CURRENT_LIST_DIR}/../../kit_opts") +``` + +普通 CMake 工程: + +```sh +cmake -B build -DCMAKE_C_FLAGS="-I" +``` + +### 方式二:逐个 `-D` {#方式二逐个-d} + +只动一两个旋钮、或想在 CI 里按矩阵切换时最省事: + +```cmake +target_compile_definitions(my_sdk_target PRIVATE + AGENTIC_KIT_RESPONSE_BUFFER_SIZE=8192) +``` + +### 方式三:`AGENTIC_KIT_USER_CONFIG` 指定任意文件名 {#方式三-agentic_kit_user_config-指定任意文件名} + +工具链没有 `__has_include`(如旧版 armcc)时的兜底。注意宏值要**带引号再整体一层引号**,这是最常见的拼写错误: + +```sh +-DAGENTIC_KIT_USER_CONFIG='"my_kit_opts.h"' # 文件目录同样要在 include 路径上 +``` + +设置了它就优先于方式一的探测(不会两个都包含);同一个旋钮不要在两处重复定义。 + +## 一条铁律:对所有编译 SDK 源码的 target 保持一致 {#一条铁律对所有编译-sdk-源码的-target-保持一致} + +旋钮是编译期常量,直接决定结构体布局和缓冲尺寸。**不同编译单元看到不同的值不会有链接错误**——`tai_ctx_size()` 按一套值算大小、任务按另一套值分配内存,越界是静默发生的。无论用哪种方式,都要让每一个编译 SDK 源码的 target(SDK 库、直接编进应用的 SDK 源文件、测试程序)看到同一份配置。 + +## 为什么这样设计 {#为什么这样设计} + +**为什么默认值分散在各子系统、覆盖挂载点却只有一个。** 这些默认值原本散落在各调用点,缓冲大小实际是**产品属性**(schema 多大、有没有 PSRAM、音频帧长多少),选型时需要按内存预算逐项审;生产事故复盘时也要能一眼回答"这块内存是哪个旋钮、为什么是这个值"。现在默认值跟着所属子系统走——FreeRTOS 任务在 `pal/pal_config_defaults.h`、模块旋钮在各自 include/——审预算、做评审时对着所属文件即可。而集成方覆盖的**捡起逻辑**只存在于 `common/log.h` 一处:SDK 每个编译单元都包含这个头,各 `*_config_defaults.h` 也都先包含它,因此任何 `#ifndef` 默认值生效前,你的覆盖一定已经就位——一份 `agentic_kit_config.h` 打动全部 18 个旋钮,不需要按子系统拆多个覆盖文件。 + +**为什么都加 `AGENTIC_KIT_` 前缀。** 撞名不是假设出来的风险:coreMQTT 自带的 `core_mqtt_config_defaults.h` 定义了同名 `MQTT_SEND_TIMEOUT_MS`(默认 20000U),与 SDK 的 2000U 谁生效取决于包含顺序;`LOG_LEVEL` 也被多个平台 SDK 占用。前缀把这些名字搬进 SDK 自己的命名空间——你的 `-D` 不会再打到别人的宏,别人的也不会打到你的。 + +**为什么 SDK 文件叫 `_defaults.h`,把裸名 `agentic_kit_config.h` 留给你。** `#include "..."`(带引号)会先搜索包含者所在目录(SDK 的 `common/`),再搜 `-I` 路径。如果 SDK 自己占用 `agentic_kit_config.h` 这个名字,你放在任何 include 路径里的同名文件都永远先被 SDK 自己的文件挡住。把裸名预留给你,是 lwIP `lwipopts.h`、mbedTLS `mbedtls_config.h`、FreeRTOS `FreeRTOSConfig.h` 一脉相承的约定:**覆盖文件的名字归集成方所有**。 + +**为什么是 `#ifndef` 默认 + 先包含你的文件,而不是让你直接改 SDK 文件。** 你不碰 SDK 源文件,升级没有合并冲突;你的文件里没写的旋钮自动跟随 SDK 默认值。 + +## 旋钮速查 {#旋钮速查} + +默认值与详细理由以各 config 文件的注释为准;"何时调整"是最常见的场景提示。各表所在文件:PAL 表在 `pal/pal_config_defaults.h`;iot-client 两表在 `modules/iot-client/include/iot_client_config_defaults.h`;TAI 表在 `modules/rtc-tcp-client/include/tai_config_defaults.h`。 + +### iot-client:MQTT {#iot-client-mqtt} + +| 旋钮 | 默认 | 何时调整 | +|------|------|---------| +| `AGENTIC_KIT_MQTT_MAX_PACKET_SIZE` | 4096 | 单个 CONNECT/SUBSCRIBE/PUBLISH 包超限时。**联动**:`iot_dp.c` 的 DP 上报门槛 `DP_MQTT_MAX_PAYLOAD` 直接由它派生,自动跟随 | +| `AGENTIC_KIT_MQTT_SEND_TIMEOUT_MS` | 2000U | 网络差、大包发送超时 | +| `AGENTIC_KIT_MQTT_RECV_TIMEOUT_MS` | 1000U | 调小让处理循环更勤,调大省唤醒 | +| `AGENTIC_KIT_MQTT_CONNECT_TIMEOUT_MS` | 10000U | 弱网环境连接握手预算 | + +### iot-client:ATOP over HTTP {#iot-client-atop-over-http} + +| 旋钮 | 默认 | 何时调整 | +|------|------|---------| +| `AGENTIC_KIT_REQUEST_HEADER_BUFFER_SIZE` | 1024 | 请求头(请求行 + 头部)拼不下的场景 | +| `AGENTIC_KIT_RESPONSE_BUFFER_SIZE` | 4096 | **DP schema 大的产品必看**:状态行 + 响应头 + 加密响应体共用这一个缓冲,溢出报 `OPRT_COMMUNICATION_ERROR`。解密后 JSON 上限约 2.8 KB(详见[通用 ATOP 调用](./atop-generic-call)) | + +### rtc-tcp-client(TAI 2.1) {#rtc-tcp-client-tai-21} + +| 旋钮 | 默认 | 何时调整 | +|------|------|---------| +| `AGENTIC_KIT_TAI_MAX_FRAGMENT_PAYLOAD` | 4096U | 传输分片上限,同时在 ClientHello 里通告给服务端。缩小省 RX 内存、增多分片开销 | +| `AGENTIC_KIT_TAI_FRAG_BUF_SIZE` | 32000U | 重组缓冲 = 最大下行应用包上限(大 Event / MCP 命令 / context JSON)。无 PSRAM 的 ESP32 常调小 | +| `AGENTIC_KIT_TAI_TX_HDR_BUF_SIZE` | 256U | 发送侧 scatter-gather 头缓冲,一般不动 | +| `AGENTIC_KIT_TAI_FRAME_COALESCE_LIMIT` | 512U | 小帧合并阈值,调小只缩窄合并窗口,安全 | +| `AGENTIC_KIT_TAI_TX_CTRL_BUF_SIZE` | 1024U | 控制包组装缓冲,约 `2×strlen(session JSON) + 115`。session/event 配置丰富时抬到 2048/4096 | +| `AGENTIC_KIT_TAI_MAX_ATTRS` | 32 | 单包最大属性数,一般不动 | +| `AGENTIC_KIT_TAI_DRAIN_BUDGET_MS` | 150U | 收包 worker 单轮排水预算,影响洪泛下 keepalive/关停延迟 | +| `AGENTIC_KIT_TAI_WORKER_POLL_CAP_MS` | 2000U | worker 空闲阻塞上限,影响 `tai_disconnect()` 响应速度 | +| `AGENTIC_KIT_TAI_LOG_MEDIA_SAMPLE_N` | 50 | 媒体中间帧 1/N 采样打日志;0 = 关采样(全部降为 DEBUG) | + +### PAL:FreeRTOS 任务 {#pal-freertos-任务} + +| 旋钮 | 默认 | 何时调整 | +|------|------|---------| +| `AGENTIC_KIT_PAL_FR_TASK_STACK_WORDS` | 6144 | **单位是 StackType_t 字,不是字节**(32 位平台 6144 ≈ 24 KB;TLS 握手跑在这个任务上,别按 6 kB 误砍) | +| `AGENTIC_KIT_PAL_FR_TASK_PRIORITY` | `tskIDLE_PRIORITY + 5` | 与音频任务、应用主任务的相对优先级 | +| `AGENTIC_KIT_PAL_FR_TASK_NAME` | `"tai_worker"` | 仅调试显示 | + +## 从旧名字迁移 {#从旧名字迁移} + +所有旋钮已统一加 `AGENTIC_KIT_` 前缀(撞名背景见上文)。旧的 `-D` 与覆盖头文件做机械改名即可: + +| 旧名 | 新名 | +|------|------| +| `MQTT_MAX_PACKET_SIZE` | `AGENTIC_KIT_MQTT_MAX_PACKET_SIZE` | +| `MQTT_SEND_TIMEOUT_MS` | `AGENTIC_KIT_MQTT_SEND_TIMEOUT_MS` | +| `MQTT_RECV_TIMEOUT_MS` | `AGENTIC_KIT_MQTT_RECV_TIMEOUT_MS` | +| `MQTT_CONNECT_TIMEOUT_MS` | `AGENTIC_KIT_MQTT_CONNECT_TIMEOUT_MS` | +| `REQUEST_HEADER_BUFFER_SIZE` | `AGENTIC_KIT_REQUEST_HEADER_BUFFER_SIZE` | +| `RESPONSE_BUFFER_SIZE` | `AGENTIC_KIT_RESPONSE_BUFFER_SIZE` | +| `TAI_LOG_MEDIA_SAMPLE_N` | `AGENTIC_KIT_TAI_LOG_MEDIA_SAMPLE_N` | +| `TAI_MAX_FRAGMENT_PAYLOAD` | `AGENTIC_KIT_TAI_MAX_FRAGMENT_PAYLOAD` | +| `TAI_FRAG_BUF_SIZE` | `AGENTIC_KIT_TAI_FRAG_BUF_SIZE` | +| `TAI_TX_HDR_BUF_SIZE` | `AGENTIC_KIT_TAI_TX_HDR_BUF_SIZE` | +| `TAI_FRAME_COALESCE_LIMIT` | `AGENTIC_KIT_TAI_FRAME_COALESCE_LIMIT` | +| `TAI_TX_CTRL_BUF_SIZE` | `AGENTIC_KIT_TAI_TX_CTRL_BUF_SIZE` | +| `TAI_MAX_ATTRS` | `AGENTIC_KIT_TAI_MAX_ATTRS` | +| `TAI_DRAIN_BUDGET_MS` | `AGENTIC_KIT_TAI_DRAIN_BUDGET_MS` | +| `TAI_WORKER_POLL_CAP_MS` | `AGENTIC_KIT_TAI_WORKER_POLL_CAP_MS` | +| `PAL_FR_TASK_STACK_WORDS` | `AGENTIC_KIT_PAL_FR_TASK_STACK_WORDS` | +| `PAL_FR_TASK_PRIORITY` | `AGENTIC_KIT_PAL_FR_TASK_PRIORITY` | +| `PAL_FR_TASK_NAME` | `AGENTIC_KIT_PAL_FR_TASK_NAME` | + +## 这些名字故意不在 config 文件里 {#这些名字故意不在-config-文件里} + +- **`TUYA_BLE_HAL_LOGI/LOGW/LOGE/HEXDUMP`** —— 定义在 `modules/tuya-ble/include/tuya_ble_prov.h`,是该公开头文件的端口绑定契约,由端口在包含前覆盖。 +- **`TUYA_BLE_RX_BUF_SIZE` / `TUYA_BLE_TX_BUF_SIZE` / `TUYA_BLE_TX_QUEUE_DEPTH`** —— 同样在 `tuya_ble_prov.h`,但原因不同:它们决定公共结构体 `tuya_ble_prov_state_t` 的布局,端口按它们设定自己的缓冲尺寸,属于端口 API 的一部分;改它们改的是端口要跟着重编、重定尺寸的结构体布局(该头文件本身声明布局不保证 ABI 稳定),不是构建旋钮。tuya-ble 目前因此没有任何编译期旋钮(完整说明见 `tuya_ble_prov.h` 的头注释;将来出现旋钮时将以 `AGENTIC_KIT_TUYA_BLE_*` 命名落在 `modules/tuya-ble/include/tuya_ble_config_defaults.h`)。 +- **`IOT_SDK_SW_VER` / `PV` / `BV` 与区域 ATOP 域名** —— `modules/iot-client/src/iot_internal.h`,发版管理的值,不是构建旋钮(内部头,与旋钮分家,不走覆盖机制)。 +- **coreMQTT / coreHTTP 日志路由** —— `common/core_mqtt_config.h`、`common/core_http_config.h`;它们把 coreMQTT/coreHTTP 的内部日志路由到全局日志 facade,不是构建旋钮。 + +## 怎么确认覆盖生效了 {#怎么确认覆盖生效了} + +**编译探针**(最直接)。用**与正式构建完全相同的 include 路径和 `-D`**(方式二/三用了 `-D` 的话探针也要带上)编译这个小文件,报 `#error` 即未生效: + +```c +/* probe.c —— 包含定义该旋钮的 defaults 文件(都会先捡起你的覆盖) */ +#include "iot_client_config_defaults.h" +#if AGENTIC_KIT_RESPONSE_BUFFER_SIZE != 8192 +#error "override not picked up" +#endif +``` + +```sh +cc -I/modules/iot-client/include -I/common -I<你的 config 目录> -c probe.c # 安静通过 = 生效 +``` + +**行为观察**。ATOP 响应超限时,错误日志会直接给出当前缓冲大小并建议对应 `-D`(默认 handler 的输出形状是 `HH:MM:SS [E] [模块tag]`;`(server said …)` 是 HTTP 状态码——服务端往往已成功返回 200,这正是这行日志要澄清的误解): + +```text +14:13:15 [E] [iot] HTTP response does not fit: need 6558 B body + 300 B headers, buffer is 4096 B (server said 200). Rebuild with a larger -DAGENTIC_KIT_RESPONSE_BUFFER_SIZE. +``` + +**产物检查**。日志级别调低后,对应级别的行不再输出;要确认 DEBUG 字符串没进固件,用一条你认识的 DEBUG 文案在产物里搜(`strings <库或对象文件> | grep '<那条文案>'` 应为空)。注意 `[ble]` 这类 tag 前缀横跨 error/warn/debug 三个级别,不能单独当作 DEBUG 的判据。 diff --git a/docs-site/docs/guides/porting-to-new-platform.md b/docs-site/docs/guides/porting-to-new-platform.md index 08a924c..fb2281e 100644 --- a/docs-site/docs/guides/porting-to-new-platform.md +++ b/docs-site/docs/guides/porting-to-new-platform.md @@ -116,11 +116,11 @@ TCP 部分取决于具体的网络协议栈(lwIP、AT 指令等)。 | 组件 | 内存需求 | 建议分配位置 | |------|---------|-------------| -| `tai_ctx_size()` | 依赖编译期缓冲配置,默认约 38 KB(调大 `TAI_FRAG_BUF_SIZE` 等缓冲后会相应增长) | 若平台支持,可优先考虑大块外部内存(如 ESP32-S3 PSRAM) | +| `tai_ctx_size()` | 依赖编译期缓冲配置,默认约 38 KB(调大 `AGENTIC_KIT_TAI_FRAG_BUF_SIZE` 等缓冲后会相应增长,旋钮默认值见 `modules/rtc-tcp-client/include/tai_config_defaults.h`) | 若平台支持,可优先考虑大块外部内存(如 ESP32-S3 PSRAM) | | TLS 工作区 | ~30 KB | PSRAM | | 音频发送缓冲 | ~4-8 KB | 内部 SRAM | | 音频接收缓冲 | ~8-16 KB | 内部 SRAM 或 PSRAM | -| FreeRTOS 任务栈 | ~4-8 KB per task | 内部 SRAM | +| FreeRTOS 任务栈 | SDK worker 任务默认 6144 words ≈ 24 KB(`AGENTIC_KIT_PAL_FR_TASK_STACK_WORDS`,单位是字不是字节;TLS 握手在该任务上,勿按 4-8 KB 砍);其他任务 ~4-8 KB | 内部 SRAM | ```c void *mem = heap_caps_malloc(tai_ctx_size(), MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT); @@ -146,7 +146,8 @@ CONFIG_FREERTOS_HZ=1000 ## 通用注意事项 {#通用注意事项} -- PAL `thread_create` 需要设置足够的栈大小;`pal_freertos.c` 默认使用 `PAL_FR_TASK_STACK_WORDS`(6144 words,32 位平台上约 24KB,可按平台内存情况调小或调大) +- SDK 全部编译期旋钮(TAI 缓冲与调度、MQTT 超时与包大小、ATOP HTTP 缓冲、FreeRTOS 任务栈、日志级别)的默认值与说明按所属子系统存放:日志上限与集成方覆盖的统一挂载点在 `common/log.h`(SDK 每个编译单元都包含它),FreeRTOS 任务旋钮在 `pal/pal_config_defaults.h`,模块旋钮在各自 include/ 下——`modules/iot-client/include/iot_client_config_defaults.h`、`modules/rtc-tcp-client/include/tai_config_defaults.h`(tuya-ble 目前没有编译期旋钮)。按产品覆盖任选其一(见 `common/log.h` 头注释;完整说明见[编译期旋钮](./compile-time-knobs)):自己创建 `agentic_kit_config.h`(只写要改的 `#define`,无论旋钮属于哪个子系统都写在这一个文件里),把所在目录加进编译 SDK 源码的 target 的 include 路径(CMake:`target_include_directories( PRIVATE <目录>)`)即可被自动捡起;沿用 `-D<宏>=<值>`;或用 `-DAGENTIC_KIT_USER_CONFIG='"my_opts.h"'` 指定任意文件名(无 `__has_include` 的工具链用这种)。注意:旋钮值必须对每个编译 SDK 源码的 target 保持一致 +- PAL `thread_create` 需要设置足够的栈大小;`pal_freertos.c` 默认使用 `AGENTIC_KIT_PAL_FR_TASK_STACK_WORDS`(6144 words,32 位平台上约 24KB,可按平台内存情况调小或调大) - SDK 内部通过 mbedTLS 处理 TLS,需要正确的系统时间用于证书验证;若未提供 CA 证书,TLS 连接可能退化为不校验证书的模式 - `tcp_recv` 应支持阻塞/超时语义(后台线程会循环调用) - `tcp_poll` 用于检查套接字的可读/可写状态,需正确实现 events 位掩码 diff --git a/docs-site/i18n/en/docusaurus-plugin-content-docs/current/guides/atop-generic-call.md b/docs-site/i18n/en/docusaurus-plugin-content-docs/current/guides/atop-generic-call.md index d62dbea..2344d3c 100644 --- a/docs-site/i18n/en/docusaurus-plugin-content-docs/current/guides/atop-generic-call.md +++ b/docs-site/i18n/en/docusaurus-plugin-content-docs/current/guides/atop-generic-call.md @@ -110,7 +110,7 @@ This is the most important point when using the generic call. The SDK does not k **Only activated devices are supported.** The generic call signs requests with `devid` + `secret_key`. Activation itself uses `uuid` + `authkey`, which is a separate path and must still go through `iot_client_init_on_boarding()`. Calling the generic call on a client without credentials returns `OPRT_UNINITIALIZED`. -**A single response cannot exceed 4096 bytes, including HTTP headers.** The status line, response headers, and encrypted response body share one fixed-size buffer (`RESPONSE_BUFFER_SIZE` in `http_client_interface.c`), with no segmented continuation reads. On overflow, the underlying coreHTTP returns `HTTPInsufficientMemory`, which the SDK collapses to `OPRT_COMMUNICATION_ERROR`—the same return value as a broken socket. Because coreHTTP's own explanatory logging is compiled out in this project (`HTTP_DO_NOT_USE_CUSTOM_CONFIG`), the only field symptom is a single "transport-layer failure," and retrying will not help. After accounting for response headers, base64 expansion, and Envelope fields, **the practical limit for decrypted JSON is approximately 2.8 KB**. This is ample for interfaces that return fixed-length fields, but interfaces returning lists or a DP Schema whose length grows with the product can easily exceed it. If you encounter such an interface, open an issue; the change that adds its named wrapper must also increase this constant. +**A single response cannot exceed 4096 bytes, including HTTP headers.** The status line, response headers, and encrypted response body share one fixed-size buffer (`AGENTIC_KIT_RESPONSE_BUFFER_SIZE`; default and tuning notes in `modules/iot-client/include/iot_client_config_defaults.h`), with no segmented continuation reads. On overflow, the underlying coreHTTP returns `HTTPInsufficientMemory`, which the SDK collapses to `OPRT_COMMUNICATION_ERROR`—the same return value as a broken socket. Because coreHTTP's own explanatory logging is compiled out in this project (`HTTP_DO_NOT_USE_CUSTOM_CONFIG`), the only field symptom is a single "transport-layer failure," and retrying will not help. After accounting for response headers, base64 expansion, and the ATOP envelope, **the practical limit for decrypted JSON is approximately 2.8 KB**. This is ample for interfaces that return fixed-length fields, but interfaces returning lists or a DP schema whose length grows with the product can easily exceed it. If you encounter such an interface, open an issue; the change that adds its named wrapper must also raise this constant. **The request body is passed through unchanged.** The SDK does not rewrite it, so you must provide every field required by the interface—**including the `t` timestamp field that most ATOP interfaces require in the request body**. The SDK validates only that the body can be parsed as a JSON object. This turns a typo into an immediate `OPRT_INVALID_PARAMETER` rather than spending an HTTPS round trip to receive an ambiguous cloud rejection. diff --git a/docs-site/i18n/en/docusaurus-plugin-content-docs/current/guides/compile-time-knobs.md b/docs-site/i18n/en/docusaurus-plugin-content-docs/current/guides/compile-time-knobs.md new file mode 100644 index 0000000..6309c08 --- /dev/null +++ b/docs-site/i18n/en/docusaurus-plugin-content-docs/current/guides/compile-time-knobs.md @@ -0,0 +1,165 @@ +--- +title: Compile-Time Knobs +sidebar_label: Compile-Time Knobs +sidebar_position: 9 +--- + +# Compile-Time Knobs + +Every compile-time knob in the SDK — TAI send/receive buffers and scheduling, MQTT timeouts and packet size, the ATOP-over-HTTP buffers, the FreeRTOS task stack — 18 knobs in total — keeps its default **with the subsystem that owns it** (the single integrator-override pickup lives in `common/log.h` — why, below): the FreeRTOS task knobs live in `pal/pal_config_defaults.h`; the per-module knobs live in each module's include/ directory — `modules/iot-client/include/iot_client_config_defaults.h` (MQTT + ATOP HTTP), `modules/rtc-tcp-client/include/tai_config_defaults.h` (TAI buffers and scheduling); tuya-ble has no compile-time knobs today (why, in the quick-reference below). The complete story for each knob (units, couplings, pitfalls hit) is in the comments of the file it lives in; this page covers how to override them per product, why the mechanism is shaped this way, and gives the quick-reference tables. + +## Three Ways to Override (Pick One) {#三种覆盖方式任选其一} + +### Option 1 (recommended): create your own `agentic_kit_config.h` {#方式一推荐自建-agentic_kit_configh} + +Write only the knobs you want to change (plain `#define`, no `#ifndef`), and put the file's directory on the include path of **every target that compiles SDK sources** — the SDK picks it up automatically while compiling each source file, no `-D` needed. Whichever subsystem a knob belongs to, it goes in this **one** file (the pickup in `common/log.h` applies it before every `#ifndef` default), so you never need one override file per module: + +```c +/* agentic_kit_config.h — only what you change; the rest follows SDK defaults */ +#define AGENTIC_KIT_RESPONSE_BUFFER_SIZE 8192 /* the product's DP schema is large */ +#define AGENTIC_KIT_TAI_FRAG_BUF_SIZE 16000U /* ESP32 without PSRAM: shrink the RX reassembly buffer */ +``` + +ESP-IDF project (SDK compiled as a component): put the file anywhere in your project and add one line so the component can see it — + +```cmake +# In this component's CMakeLists.txt; the path points at where you keep agentic_kit_config.h +target_include_directories(${COMPONENT_LIB} PRIVATE "${CMAKE_CURRENT_LIST_DIR}/../../kit_opts") +``` + +Plain CMake project: + +```sh +cmake -B build -DCMAKE_C_FLAGS="-I" +``` + +### Option 2: individual `-D` flags {#方式二逐个-d} + +Cheapest when touching one or two knobs, or when a CI matrix switches them: + +```cmake +target_compile_definitions(my_sdk_target PRIVATE + AGENTIC_KIT_RESPONSE_BUFFER_SIZE=8192) +``` + +### Option 3: `AGENTIC_KIT_USER_CONFIG` names an arbitrary file {#方式三-agentic_kit_user_config-指定任意文件名} + +The fallback for toolchains without `__has_include` (older armcc, for example). Note the macro value needs **quotes inside quotes** — the most common spelling mistake: + +```sh +-DAGENTIC_KIT_USER_CONFIG='"my_kit_opts.h"' # the file's directory must also be on the include path +``` + +When set it takes priority over the Option-1 probe (both are never included); do not define the same knob in both places. + +## One Iron Rule: Identical for Every Target That Compiles SDK Sources {#一条铁律对所有编译-sdk-源码的-target-保持一致} + +Knobs are compile-time constants that directly determine struct layouts and buffer sizes. **Different translation units seeing different values produces no linker error** — `tai_ctx_size()` computes the size with one set of values while the task allocates memory with another, and the overflow happens silently. Whichever way you override, every target that compiles SDK sources (the SDK library, SDK sources compiled directly into the app, test programs) must see the same configuration. + +## Why It Is Designed This Way {#为什么这样设计} + +**Why the defaults are distributed across subsystems while the override pickup is in one place.** These defaults used to be scattered at their call sites, and buffer sizes are really **product properties** (how large the schema is, whether there is PSRAM, the audio frame length) — choosing them means going over the memory budget item by item, and a production incident review needs to answer at a glance "which knob owns this memory and why that value". Now each default lives with its owning subsystem — the FreeRTOS task in `pal/pal_config_defaults.h`, module knobs in each module's include/ — so budget reviews and code reviews look at the owning file. The **pickup logic** for integrator overrides, though, exists in exactly one place, `common/log.h`: every SDK translation unit includes that header, and every `*_config_defaults.h` includes it first, so by the time any `#ifndef` default takes effect your override is already in place — one `agentic_kit_config.h` moves all 18 knobs, with no need to split overrides per subsystem. + +**Why every knob carries the `AGENTIC_KIT_` prefix.** The name collision is not hypothetical: coreMQTT's bundled `core_mqtt_config_defaults.h` defines a same-named `MQTT_SEND_TIMEOUT_MS` (default 20000U) that fought the SDK's 2000U by include order, and `LOG_LEVEL` is claimed by several platform SDKs. The prefix moves these names into the SDK's own namespace — your `-D` no longer hits someone else's macro, and theirs no longer hits yours. + +**Why the SDK files are named `_defaults.h`, leaving the plain name `agentic_kit_config.h` to you.** `#include "..."` (quoted) searches the includer's own directory (the SDK's `common/`) before the `-I` path. If the SDK itself occupied the name `agentic_kit_config.h`, your same-named file on any include path would forever be shadowed by the SDK's own. Reserving the plain name for the integrator is the same convention as lwIP's `lwipopts.h`, mbedTLS's `mbedtls_config.h`, and FreeRTOS's `FreeRTOSConfig.h`: **the override file's name belongs to the integrator**. + +**Why `#ifndef` defaults plus including your file first, instead of letting you edit the SDK files.** You never touch SDK sources, so upgrades merge cleanly; knobs your file doesn't mention automatically follow the SDK defaults. + +## Knob Quick Reference {#旋钮速查} + +Defaults and detailed rationale live in each config file's comments; "when to adjust" is the most common scenario hint. Where each table lives: the PAL table in `pal/pal_config_defaults.h`; both iot-client tables in `modules/iot-client/include/iot_client_config_defaults.h`; the TAI table in `modules/rtc-tcp-client/include/tai_config_defaults.h`. + +### iot-client: MQTT {#iot-client-mqtt} + +| Knob | Default | When to adjust | +|------|------|---------| +| `AGENTIC_KIT_MQTT_MAX_PACKET_SIZE` | 4096 | When a single CONNECT/SUBSCRIBE/PUBLISH packet overflows. **Coupling**: the DP publish gate `DP_MQTT_MAX_PAYLOAD` in `iot_dp.c` derives from it and follows automatically | +| `AGENTIC_KIT_MQTT_SEND_TIMEOUT_MS` | 2000U | Weak networks, large-packet send timeouts | +| `AGENTIC_KIT_MQTT_RECV_TIMEOUT_MS` | 1000U | Lower for a more responsive processing loop, raise to save wakeups | +| `AGENTIC_KIT_MQTT_CONNECT_TIMEOUT_MS` | 10000U | Connection handshake budget on weak networks | + +### iot-client: ATOP over HTTP {#iot-client-atop-over-http} + +| Knob | Default | When to adjust | +|------|------|---------| +| `AGENTIC_KIT_REQUEST_HEADER_BUFFER_SIZE` | 1024 | When assembled request headers (request line + headers) no longer fit | +| `AGENTIC_KIT_RESPONSE_BUFFER_SIZE` | 4096 | **Must-read for products with a large DP schema**: the status line, response headers, and encrypted response body share this one buffer; overflow reports `OPRT_COMMUNICATION_ERROR`. The decrypted-JSON cap is roughly 2.8 KB net of headers (see [Generic ATOP Calls](./atop-generic-call)) | + +### rtc-tcp-client (TAI 2.1) {#rtc-tcp-client-tai-21} + +| Knob | Default | When to adjust | +|------|------|---------| +| `AGENTIC_KIT_TAI_MAX_FRAGMENT_PAYLOAD` | 4096U | Transport fragmentation cap, also advertised to the server in ClientHello. Lower saves RX memory, costs more fragments | +| `AGENTIC_KIT_TAI_FRAG_BUF_SIZE` | 32000U | Reassembly buffer = the maximum downlink application packet (large Events / MCP commands / context JSON). Often lowered on PSRAM-less ESP32 | +| `AGENTIC_KIT_TAI_TX_HDR_BUF_SIZE` | 256U | Send-side scatter-gather header buffer; rarely touched | +| `AGENTIC_KIT_TAI_FRAME_COALESCE_LIMIT` | 512U | Small-frame coalescing threshold; lowering only narrows the coalescing window — safe | +| `AGENTIC_KIT_TAI_TX_CTRL_BUF_SIZE` | 1024U | Control-packet assembly buffer, roughly `2×strlen(session JSON) + 115`. Raise to 2048/4096 for session/event-heavy configurations | +| `AGENTIC_KIT_TAI_MAX_ATTRS` | 32 | Maximum attributes per packet; rarely touched | +| `AGENTIC_KIT_TAI_DRAIN_BUDGET_MS` | 150U | Per-round drain budget of the receive worker; affects keepalive/shutdown latency under floods | +| `AGENTIC_KIT_TAI_WORKER_POLL_CAP_MS` | 2000U | Worker idle block cap; affects how fast `tai_disconnect()` responds | +| `AGENTIC_KIT_TAI_LOG_MEDIA_SAMPLE_N` | 50 | Logs every Nth intermediate media frame; 0 = sampling off (all demoted to DEBUG) | + +### PAL: FreeRTOS Task {#pal-freertos-任务} + +| Knob | Default | When to adjust | +|------|------|---------| +| `AGENTIC_KIT_PAL_FR_TASK_STACK_WORDS` | 6144 | **The unit is StackType_t words, not bytes** (6144 ≈ 24 KB on a 32-bit platform; the TLS handshake runs on this task — don't cut it to 6 KB by mistake) | +| `AGENTIC_KIT_PAL_FR_TASK_PRIORITY` | `tskIDLE_PRIORITY + 5` | Relative to the audio task and the application's main tasks | +| `AGENTIC_KIT_PAL_FR_TASK_NAME` | `"tai_worker"` | Debug display only | + +## Migrating from the Old Names {#从旧名字迁移} + +All knobs now carry the `AGENTIC_KIT_` prefix (the collision backstory is above). A mechanical rename of old `-D` flags and override headers is all it takes: + +| Old name | New name | +|------|------| +| `MQTT_MAX_PACKET_SIZE` | `AGENTIC_KIT_MQTT_MAX_PACKET_SIZE` | +| `MQTT_SEND_TIMEOUT_MS` | `AGENTIC_KIT_MQTT_SEND_TIMEOUT_MS` | +| `MQTT_RECV_TIMEOUT_MS` | `AGENTIC_KIT_MQTT_RECV_TIMEOUT_MS` | +| `MQTT_CONNECT_TIMEOUT_MS` | `AGENTIC_KIT_MQTT_CONNECT_TIMEOUT_MS` | +| `REQUEST_HEADER_BUFFER_SIZE` | `AGENTIC_KIT_REQUEST_HEADER_BUFFER_SIZE` | +| `RESPONSE_BUFFER_SIZE` | `AGENTIC_KIT_RESPONSE_BUFFER_SIZE` | +| `TAI_LOG_MEDIA_SAMPLE_N` | `AGENTIC_KIT_TAI_LOG_MEDIA_SAMPLE_N` | +| `TAI_MAX_FRAGMENT_PAYLOAD` | `AGENTIC_KIT_TAI_MAX_FRAGMENT_PAYLOAD` | +| `TAI_FRAG_BUF_SIZE` | `AGENTIC_KIT_TAI_FRAG_BUF_SIZE` | +| `TAI_TX_HDR_BUF_SIZE` | `AGENTIC_KIT_TAI_TX_HDR_BUF_SIZE` | +| `TAI_FRAME_COALESCE_LIMIT` | `AGENTIC_KIT_TAI_FRAME_COALESCE_LIMIT` | +| `TAI_TX_CTRL_BUF_SIZE` | `AGENTIC_KIT_TAI_TX_CTRL_BUF_SIZE` | +| `TAI_MAX_ATTRS` | `AGENTIC_KIT_TAI_MAX_ATTRS` | +| `TAI_DRAIN_BUDGET_MS` | `AGENTIC_KIT_TAI_DRAIN_BUDGET_MS` | +| `TAI_WORKER_POLL_CAP_MS` | `AGENTIC_KIT_TAI_WORKER_POLL_CAP_MS` | +| `PAL_FR_TASK_STACK_WORDS` | `AGENTIC_KIT_PAL_FR_TASK_STACK_WORDS` | +| `PAL_FR_TASK_PRIORITY` | `AGENTIC_KIT_PAL_FR_TASK_PRIORITY` | +| `PAL_FR_TASK_NAME` | `AGENTIC_KIT_PAL_FR_TASK_NAME` | + +## Names Deliberately Outside the Config Files {#这些名字故意不在-config-文件里} + +- **`TUYA_BLE_HAL_LOGI/LOGW/LOGE/HEXDUMP`** — defined in `modules/tuya-ble/include/tuya_ble_prov.h`; they are the public header's port-binding contract, overridden by the port before inclusion. +- **`TUYA_BLE_RX_BUF_SIZE` / `TUYA_BLE_TX_BUF_SIZE` / `TUYA_BLE_TX_QUEUE_DEPTH`** — also in `tuya_ble_prov.h`, but for a different reason: they determine the layout of the public struct `tuya_ble_prov_state_t`, and ports size their own buffers against them, which makes them part of the port API; changing them changes a struct layout that ports must recompile and re-size against (the header itself declares the layout not ABI-stable) — they are not build knobs. This is why tuya-ble has no compile-time knobs today (the full story sits in the `tuya_ble_prov.h` header comment; when one appears it will be named `AGENTIC_KIT_TUYA_BLE_*` in `modules/tuya-ble/include/tuya_ble_config_defaults.h`). +- **`IOT_SDK_SW_VER` / `PV` / `BV` and the per-region ATOP hosts** — `modules/iot-client/src/iot_internal.h`; release-managed values, not build knobs (an internal header, split from the knobs, not riding the override mechanism). +- **coreMQTT / coreHTTP log routing** — `common/core_mqtt_config.h`, `common/core_http_config.h`; they route coreMQTT/coreHTTP internal logs into the global log facade, they are not build knobs. + +## How to Confirm an Override Took Effect {#怎么确认覆盖生效了} + +**Compile probe** (the most direct). With **exactly the same include paths and `-D`s as the real build** (if Option 2/3 used `-D`, the probe needs them too), compile this small file — an `#error` means it did not take: + +```c +/* probe.c — include the defaults file that defines the knob (all of them pick up your override first) */ +#include "iot_client_config_defaults.h" +#if AGENTIC_KIT_RESPONSE_BUFFER_SIZE != 8192 +#error "override not picked up" +#endif +``` + +```sh +cc -I/modules/iot-client/include -I/common -I -c probe.c # quiet pass = effective +``` + +**Behavioral observation.** When an ATOP response overflows, the error log states the current buffer size and suggests the matching `-D` (the default handler's output shape is `HH:MM:SS [E] [module tag]`; `(server said …)` is the HTTP status — the server usually already returned 200 successfully, which is exactly the misunderstanding this line exists to clear): + +```text +14:13:15 [E] [iot] HTTP response does not fit: need 6558 B body + 300 B headers, buffer is 4096 B (server said 200). Rebuild with a larger -DAGENTIC_KIT_RESPONSE_BUFFER_SIZE. +``` + +**Artifact inspection.** After lowering the log level, lines of the removed level stop printing; to confirm DEBUG strings never entered the firmware, search the artifact for a DEBUG message you recognize (`strings | grep ''` should come up empty). Note that a tag prefix like `[ble]` spans error/warn/debug levels and cannot by itself identify DEBUG output. diff --git a/docs-site/i18n/en/docusaurus-plugin-content-docs/current/guides/porting-to-new-platform.md b/docs-site/i18n/en/docusaurus-plugin-content-docs/current/guides/porting-to-new-platform.md index a59aa98..5d7947b 100644 --- a/docs-site/i18n/en/docusaurus-plugin-content-docs/current/guides/porting-to-new-platform.md +++ b/docs-site/i18n/en/docusaurus-plugin-content-docs/current/guides/porting-to-new-platform.md @@ -117,11 +117,11 @@ The TCP implementation depends on the specific network stack, such as lwIP or AT | Component | Memory requirement | Recommended location | |------|---------|-------------| -| `tai_ctx_size()` | Depends on compile-time buffer configuration; approximately 38 KB by default (it grows if buffers such as `TAI_FRAG_BUF_SIZE` are increased) | Prefer large external memory, such as ESP32-S3 PSRAM, if the platform supports it | +| `tai_ctx_size()` | Depends on compile-time buffer configuration; approximately 38 KB by default (it grows when buffers such as `AGENTIC_KIT_TAI_FRAG_BUF_SIZE` are raised — knob defaults live in `modules/rtc-tcp-client/include/tai_config_defaults.h`) | Prefer large external memory, such as ESP32-S3 PSRAM, if the platform supports it | | TLS workspace | ~30 KB | PSRAM | | Audio send buffer | ~4-8 KB | Internal SRAM | | Audio receive buffer | ~8-16 KB | Internal SRAM or PSRAM | -| FreeRTOS task stack | ~4-8 KB per task | Internal SRAM | +| FreeRTOS task stack | The SDK worker task defaults to 6144 words ≈ 24 KB (`AGENTIC_KIT_PAL_FR_TASK_STACK_WORDS` — the unit is words, not bytes; the TLS handshake runs on this task, so do not cut it to 4-8 KB); other tasks ~4-8 KB | Internal SRAM | ```c void *mem = heap_caps_malloc(tai_ctx_size(), MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT); @@ -147,7 +147,8 @@ CONFIG_FREERTOS_HZ=1000 ## General Considerations {#通用注意事项} -- PAL `thread_create` must configure a sufficiently large stack. `pal_freertos.c` uses `PAL_FR_TASK_STACK_WORDS` by default (6144 words, approximately 24 KB on a 32-bit platform), which can be reduced or increased according to the platform's memory constraints. +- Defaults and documentation for every SDK compile-time knob (TAI buffers and scheduling, MQTT timeouts and packet size, ATOP HTTP buffers, the FreeRTOS task stack, log levels) live with the subsystem that owns them: the log ceiling and the single integrator-override pickup live in `common/log.h` (every SDK translation unit includes it), the FreeRTOS task knobs in `pal/pal_config_defaults.h`, and the per-module knobs in each module's include/ directory — `modules/iot-client/include/iot_client_config_defaults.h`, `modules/rtc-tcp-client/include/tai_config_defaults.h` (tuya-ble has no compile-time knobs today). To override per product, pick one (see the banner in `common/log.h`; the full guide is [Compile-Time Knobs](./compile-time-knobs)): create your own `agentic_kit_config.h` (only the `#define`s you want to change — one file regardless of which subsystem a knob belongs to), add its directory to the include path of every target that compiles SDK sources (CMake: `target_include_directories( PRIVATE )`) and it is picked up automatically; or keep using `-D=`; or use `-DAGENTIC_KIT_USER_CONFIG='"my_opts.h"'` to name an arbitrary file (for toolchains without `__has_include`). Note: knob values must stay identical for every target that compiles SDK sources +- PAL `thread_create` must configure a sufficiently large stack. `pal_freertos.c` defaults to `AGENTIC_KIT_PAL_FR_TASK_STACK_WORDS` (6144 words, approximately 24 KB on a 32-bit platform), which can be reduced or increased according to the platform's memory constraints. - The SDK handles TLS internally through mbedTLS and requires the correct system time for certificate verification. If no CA certificate is provided, the TLS connection may fall back to a mode that does not verify certificates. - `tcp_recv` should support blocking and timeout semantics; the background thread calls it repeatedly. - `tcp_poll` checks whether the socket is readable or writable and must implement the events bitmask correctly. diff --git a/docs-site/sidebars.ts b/docs-site/sidebars.ts index 9d38df5..a42e959 100644 --- a/docs-site/sidebars.ts +++ b/docs-site/sidebars.ts @@ -58,6 +58,7 @@ const sidebars: SidebarsConfig = { 'guides/atop-generic-call', 'guides/tls-cert-verification', 'guides/porting-to-new-platform', + 'guides/compile-time-knobs', ], }, ], diff --git a/examples/esp-idf/components/agentic_kit/CMakeLists.txt b/examples/esp-idf/components/agentic_kit/CMakeLists.txt index eaa54ca..55ebc95 100644 --- a/examples/esp-idf/components/agentic_kit/CMakeLists.txt +++ b/examples/esp-idf/components/agentic_kit/CMakeLists.txt @@ -71,10 +71,14 @@ idf_component_register( # No MQTT_DO_NOT_USE_CUSTOM_CONFIG: coreMQTT must pick up common/core_mqtt_config.h # here too, or a refused CONNECT logs only a bare MQTTServerRefused on exactly the # target this diagnostic exists for. ${AK_DIR}/common is already in INCLUDE_DIRS -# above, so the header resolves with no further wiring. -target_compile_definitions(${COMPONENT_LIB} PRIVATE - IOT_DO_NOT_USE_CUSTOM_CONFIG -) +# above, so common/log.h (the override pickup) resolves with +# no further wiring; knob defaults resolve the same way from each module's include/ +# dir and ${AK_DIR}/pal listed above. +# +# Per-product knob overrides (optional): create agentic_kit_config.h holding only +# the #defines you want to change, put it anywhere in your project, and point this +# component at its directory (must reach EVERY target that compiles SDK sources): +# target_include_directories(${COMPONENT_LIB} PRIVATE "${CMAKE_CURRENT_LIST_DIR}/../../kit_opts") target_compile_options(${COMPONENT_LIB} PRIVATE -Wno-error=format diff --git a/examples/posix/ai/rtc-tcp-client/agent_trigger_demo.c b/examples/posix/ai/rtc-tcp-client/agent_trigger_demo.c index d7fb07b..5f25285 100644 --- a/examples/posix/ai/rtc-tcp-client/agent_trigger_demo.c +++ b/examples/posix/ai/rtc-tcp-client/agent_trigger_demo.c @@ -693,7 +693,7 @@ static int tai_link_up(tai_ctx_t *ctx, demo_reconnect_t *r) * Deliberately deadline-driven rather than a fixed iteration count: * iot_client_process() DISCARDS its timeout argument (mqtt.c does * `(void)timeout_ms;`) and blocks for up to the compile-time - * MQTT_RECV_TIMEOUT_MS -- 1000 ms, five times the 200 we pass. Counting + * AGENTIC_KIT_MQTT_RECV_TIMEOUT_MS -- 1000 ms, five times the 200 we pass. Counting * iterations therefore overshoots by 5x on an idle link. Overshoot here is * bounded by one recv budget instead. */ static void pump_for(iot_client_t *iot, int seconds) diff --git a/modules/iot-client/include/iot_client_config_defaults.h b/modules/iot-client/include/iot_client_config_defaults.h new file mode 100644 index 0000000..b0fdd29 --- /dev/null +++ b/modules/iot-client/include/iot_client_config_defaults.h @@ -0,0 +1,73 @@ +/* + * iot_client_config_defaults.h -- iot-client build-time knobs: the + * AGENTIC_KIT_* #ifndef defaults, nothing else. Non-knob module data -- + * release-managed version strings, the per-region ATOP/MQTT endpoints, the + * log-facade binding with its PAL helpers -- lives in src/iot_internal.h + * (which pulls this file in), mirroring tai_config_defaults.h's split. + * + * It pulls common/log.h FIRST: that header is where integrator overrides + * (agentic_kit_config.h on the include path, -D, AGENTIC_KIT_USER_CONFIG) + * are applied -- so overrides win over every knob below. Not a public + * API header. + * + * @copyright Copyright (c) 2021-2025 Tuya Inc. All Rights Reserved. + */ + +#ifndef AGENTIC_KIT_IOT_CLIENT_CONFIG_DEFAULTS_H +#define AGENTIC_KIT_IOT_CLIENT_CONFIG_DEFAULTS_H + +#include "log.h" + +/* ========================================================================= + * iot-client: MQTT transport (modules/iot-client/src/mqtt.c) + * ========================================================================= */ + +/* coreMQTT fixed network buffer for one packet (CONNECT/SUBSCRIBE/PUBLISH). + * PAL-allocated, so raising it lands in PSRAM on targets that route large + * allocations there. The DP publish gate (DP_MQTT_MAX_PAYLOAD in iot_dp.c) + * derives from this, so it follows automatically. */ +#ifndef AGENTIC_KIT_MQTT_MAX_PACKET_SIZE +#define AGENTIC_KIT_MQTT_MAX_PACKET_SIZE 4096 +#endif + +/* Per transport-write send timeout, handed to the shared TLS/TCP layer. */ +#ifndef AGENTIC_KIT_MQTT_SEND_TIMEOUT_MS +#define AGENTIC_KIT_MQTT_SEND_TIMEOUT_MS 2000U +#endif + +/* Receive poll timeout driving the MQTT process loop; a receive that gets + * no bytes within it polls again rather than failing. */ +#ifndef AGENTIC_KIT_MQTT_RECV_TIMEOUT_MS +#define AGENTIC_KIT_MQTT_RECV_TIMEOUT_MS 1000U +#endif + +/* Whole CONNECT handshake budget (TCP + TLS + MQTT CONNECT). */ +#ifndef AGENTIC_KIT_MQTT_CONNECT_TIMEOUT_MS +#define AGENTIC_KIT_MQTT_CONNECT_TIMEOUT_MS 10000U +#endif + +/* ========================================================================= + * iot-client: ATOP-over-HTTP transport (http_client_interface.c) + * ========================================================================= */ + +/* Assembled ATOP request headers (request line + headers), one buffer. */ +#ifndef AGENTIC_KIT_REQUEST_HEADER_BUFFER_SIZE +#define AGENTIC_KIT_REQUEST_HEADER_BUFFER_SIZE 1024 +#endif + +/* Status line + response headers + the ENCRYPTED response body share this + * single fixed buffer -- there is no streamed/continued read. A larger + * response makes coreHTTP return HTTPInsufficientMemory, which the SDK + * folds into OPRT_COMMUNICATION_ERROR (indistinguishable from a broken + * socket at the call site). This is not hypothetical: a product hit it in + * production with contentLength 6558 against the 4096 default, on an + * activation that had already SUCCEEDED server-side. Both buffers come + * from the PAL allocator, so raising this lands in PSRAM where routed. + * Net of headers, base64 expansion and the ATOP envelope, the decrypted + * JSON payload cap is roughly 2.8 KB -- see + * docs-site/docs/guides/atop-generic-call.md. */ +#ifndef AGENTIC_KIT_RESPONSE_BUFFER_SIZE +#define AGENTIC_KIT_RESPONSE_BUFFER_SIZE 4096 +#endif + +#endif /* AGENTIC_KIT_IOT_CLIENT_CONFIG_DEFAULTS_H */ diff --git a/modules/iot-client/src/atop.c b/modules/iot-client/src/atop.c index b35e506..28675b6 100644 --- a/modules/iot-client/src/atop.c +++ b/modules/iot-client/src/atop.c @@ -1,7 +1,7 @@ #include "atop.h" #include "atop_base.h" #include "cJSON.h" -#include "iot_config_defaults.h" +#include "iot_internal.h" #include "cipher_wrapper.h" #include diff --git a/modules/iot-client/src/atop_base.c b/modules/iot-client/src/atop_base.c index a106de1..d5de202 100644 --- a/modules/iot-client/src/atop_base.c +++ b/modules/iot-client/src/atop_base.c @@ -27,7 +27,7 @@ #include "cipher_wrapper.h" #include "rng.h" -#include "iot_config_defaults.h" +#include "iot_internal.h" #include "cJSON.h" #include "mbedtls/base64.h" #include "mbedtls/md5.h" diff --git a/modules/iot-client/src/cipher_wrapper.c b/modules/iot-client/src/cipher_wrapper.c index ea68c3e..a1a822c 100644 --- a/modules/iot-client/src/cipher_wrapper.c +++ b/modules/iot-client/src/cipher_wrapper.c @@ -1,5 +1,5 @@ #include "cipher_wrapper.h" -#include "iot_config_defaults.h" +#include "iot_internal.h" #include #include diff --git a/modules/iot-client/src/http_client_interface.c b/modules/iot-client/src/http_client_interface.c index aed467e..774360e 100644 --- a/modules/iot-client/src/http_client_interface.c +++ b/modules/iot-client/src/http_client_interface.c @@ -1,7 +1,7 @@ #include "http_client_interface.h" #include "core_http_client.h" #include "transport_interface.h" -#include "iot_config_defaults.h" +#include "iot_internal.h" #include #include @@ -223,31 +223,27 @@ http_client_status_t http_client_request(const http_client_request_t *request, // One block holds both the request-header and response buffers (both alive // through HTTPClient_Send) -- one allocation instead of two. - /* Both are overridable at build time (-D...) because the right size is a - * property of the PRODUCT, not of the SDK: an ATOP activation response - * carries the device's DP schema, so a product with a moderately large - * schema overflows any fixed default. Observed on a Tuya alarm-clock - * product: contentLength 6558 with a 4096 buffer -> HTTPInsufficientMemory, - * even though the server had returned 200 and the activation had SUCCEEDED - * on its side. The buffer is taken from the PAL allocator, so raising it + /* Sizes are overridable at build time (-D or your own agentic_kit_config.h + * on the include path; defaults and rationale in + * include/iot_client_config_defaults.h) because the right size is a property of + * the PRODUCT, not of the SDK: an ATOP activation response carries the + * device's DP schema, so a product with a moderately large schema + * overflows any fixed default. Observed on a Tuya alarm-clock product: + * contentLength 6558 with a 4096 buffer -> HTTPInsufficientMemory, even + * though the server had returned 200 and the activation had SUCCEEDED on + * its side. The buffer is taken from the PAL allocator, so raising it * lands in PSRAM on targets that route large allocations there. */ - #ifndef REQUEST_HEADER_BUFFER_SIZE - #define REQUEST_HEADER_BUFFER_SIZE 1024 - #endif - #ifndef RESPONSE_BUFFER_SIZE - #define RESPONSE_BUFFER_SIZE 4096 - #endif - uint8_t *http_buf = (uint8_t *)pal->malloc(REQUEST_HEADER_BUFFER_SIZE + RESPONSE_BUFFER_SIZE); + uint8_t *http_buf = (uint8_t *)pal->malloc(AGENTIC_KIT_REQUEST_HEADER_BUFFER_SIZE + AGENTIC_KIT_RESPONSE_BUFFER_SIZE); if (!http_buf) { log_error("Failed to allocate HTTP buffers"); disconnect(network_ctx); return HTTP_CLIENT_ERROR; } uint8_t *request_header_buffer = http_buf; - uint8_t *response_buffer = http_buf + REQUEST_HEADER_BUFFER_SIZE; + uint8_t *response_buffer = http_buf + AGENTIC_KIT_REQUEST_HEADER_BUFFER_SIZE; HTTPRequestHeaders_t request_headers = { .pBuffer = request_header_buffer, - .bufferLen = REQUEST_HEADER_BUFFER_SIZE, + .bufferLen = AGENTIC_KIT_REQUEST_HEADER_BUFFER_SIZE, .headersLen = 0 }; @@ -288,7 +284,7 @@ http_client_status_t http_client_request(const http_client_request_t *request, HTTPResponse_t http_response = { .pBuffer = response_buffer, - .bufferLen = RESPONSE_BUFFER_SIZE, + .bufferLen = AGENTIC_KIT_RESPONSE_BUFFER_SIZE, .pHeaderParsingCallback = NULL, .getTime = NULL, .pHeaders = NULL, @@ -351,10 +347,10 @@ http_client_status_t http_client_request(const http_client_request_t *request, if (http_status == HTTPInsufficientMemory) { log_error("HTTP response does not fit: need %u B body + %u B headers, " "buffer is %d B (server said %u). Rebuild with a larger " - "-DRESPONSE_BUFFER_SIZE.", + "-DAGENTIC_KIT_RESPONSE_BUFFER_SIZE.", (unsigned)http_response.contentLength, (unsigned)http_response.headersLen, - RESPONSE_BUFFER_SIZE, + AGENTIC_KIT_RESPONSE_BUFFER_SIZE, (unsigned)http_response.statusCode); } else { log_error("HTTP request failed: %d", http_status); diff --git a/modules/iot-client/src/iot_atop.c b/modules/iot-client/src/iot_atop.c index 802dbeb..e24db22 100644 --- a/modules/iot-client/src/iot_atop.c +++ b/modules/iot-client/src/iot_atop.c @@ -17,7 +17,7 @@ #include "atop_base.h" #include "cJSON.h" -#include "iot_config_defaults.h" /* IOT_DEFAULT_PORT */ +#include "iot_internal.h" /* IOT_DEFAULT_PORT */ #include "iot_dp_internal.h" /* iot_client_resolve_atop_host */ #include diff --git a/modules/iot-client/src/iot_client.c b/modules/iot-client/src/iot_client.c index ac980c2..2766bae 100644 --- a/modules/iot-client/src/iot_client.c +++ b/modules/iot-client/src/iot_client.c @@ -1,14 +1,14 @@ #include "iot_client.h" #include "iot_dp.h" #include "iot_dp_internal.h" -#include "iot_client_internal.h" +#include "iot_internal.h" #include "iot_on_boarding.h" #include "iot_dns.h" #include "iot_client_message.h" #include "iot_ota.h" #include "iot_atop.h" #include "cipher_wrapper.h" -#include "iot_config_defaults.h" +#include "iot_internal.h" #include "rng.h" #include "atop.h" diff --git a/modules/iot-client/src/iot_client_internal.h b/modules/iot-client/src/iot_client_internal.h deleted file mode 100644 index e254d9c..0000000 --- a/modules/iot-client/src/iot_client_internal.h +++ /dev/null @@ -1,28 +0,0 @@ -#ifndef __IOT_CLIENT_INTERNAL_H__ -#define __IOT_CLIENT_INTERNAL_H__ - -/* - * Internal (src-private) hooks for the client core, used by iot_client.c and - * the host test suites. The public client API lives in include/iot_client.h. - */ - -#include "iot_client.h" - -/** - * @brief Issue the two init-time version reports — SDK meta save and firmware - * version update — unless config->skip_version_report is set. - * - * Non-fatal by design: each failed report logs a warning and client init - * proceeds, exactly as when this lived inline in iot_client_init(). Split out - * so host tests can pin the skip / no-skip contract against the ATOP mock - * with a hand-shaped client, without the DNS / activation path in front of it - * (iot_client_init() with a non-empty devid queries the real IoT-DNS host). - * - * @return OPRT_OK when both reports were skipped or both succeeded; - * OPRT_INVALID_PARAMETER for a NULL client or config; - * otherwise the first failure (both reports are still attempted). - */ -int iot_client_report_init_versions(iot_client_t *client, - const iot_client_config_t *config); - -#endif /* __IOT_CLIENT_INTERNAL_H__ */ diff --git a/modules/iot-client/src/iot_client_message.c b/modules/iot-client/src/iot_client_message.c index 65ab8ec..14070df 100644 --- a/modules/iot-client/src/iot_client_message.c +++ b/modules/iot-client/src/iot_client_message.c @@ -1,7 +1,7 @@ #include "iot_client_message.h" #include "mqtt.h" #include "cipher_wrapper.h" -#include "iot_config_defaults.h" +#include "iot_internal.h" #include "iot_dp_internal.h" #include "cJSON.h" diff --git a/modules/iot-client/src/iot_config_defaults.h b/modules/iot-client/src/iot_config_defaults.h deleted file mode 100644 index 4f0570a..0000000 --- a/modules/iot-client/src/iot_config_defaults.h +++ /dev/null @@ -1,85 +0,0 @@ -/** - * @file iot_config_defaults.h - * @brief IoT SDK configuration defaults and logging macros - * - * This file provides default configuration values for the IoT SDK library - * and defines logging macros that can be customized by the application. - * - * @copyright Copyright (c) 2021-2025 Tuya Inc. All Rights Reserved. - */ - -#ifndef __IOT_CONFIG_DEFAULTS_H__ -#define __IOT_CONFIG_DEFAULTS_H__ - -#include -#include -#include "iot_client.h" -#include "log.h" - -#define IOT_HTTP_TIMEOUT_MS_DEFAULT 5000 - -// Default Tuya cloud server configuration -#define IOT_DEFAULT_HOST "a1.tuyacn.com" -#define IOT_DEFAULT_PRE_HOST "a1-cn.wgine.com" -#define IOT_CN_HOST "a1.tuyacn.com" -#define IOT_CN_PRE_HOST "a1-cn.wgine.com" -#define IOT_AZ_HOST "a1.tuyaus.com" -#define IOT_AZ_PRE_HOST "a1-us.wgine.com" -#define IOT_UEAZ_HOST "a1-ueaz.tuyaus.com" -#define IOT_UEAZ_PRE_HOST "a1-ueaz.wgine.com" -#define IOT_EU_HOST "a1.tuyaeu.com" -#define IOT_EU_PRE_HOST "a1-eu.wgine.com" -#define IOT_WEAZ_HOST "a1-weaz.tuyaeu.com" -#define IOT_WEAZ_PRE_HOST "a1-weaz.wgine.com" -#define IOT_IN_HOST "a1.tuyain.com" -#define IOT_IN_PRE_HOST "a1-in.wgine.com" -#define IOT_SG_HOST "a1-sg.iotbing.com" -#define IOT_TEST_HOST "https://127.0.0.1:8443" - - -#define IOT_DEFAULT_MQTT_URL "mqtts://a6.tuyacn.com:8883" - -#define IOT_DEFAULT_PORT 443 - -#ifndef IOT_SDK_SW_VER -#define IOT_SDK_SW_VER "1.0.0" -#endif -#ifndef IOT_SDK_PV -#define IOT_SDK_PV "2.3" -#endif -#ifndef IOT_SDK_BV -#define IOT_SDK_BV "2.0" -#endif -#define SDK_VERSION "agentic-kit_0.4.0" - -/** - * @brief Get the built-in default PAL adapter (POSIX or FreeRTOS). - * @return Pointer to a static pal_t with all required function pointers set - */ -const pal_t *get_default_pal(void); - -/* Module-tagged dispatch into the global log facade. Tag is folded - * into the format string at compile time, so the log handler stays - * tag-agnostic and the call site reads exactly like printf(). */ -#define log_error(fmt, ...) log_emit(LOG_ERROR, "[iot] " fmt, ##__VA_ARGS__) -#define log_warn(fmt, ...) log_emit(LOG_WARN, "[iot] " fmt, ##__VA_ARGS__) -#define log_info(fmt, ...) log_emit(LOG_INFO, "[iot] " fmt, ##__VA_ARGS__) -#define log_debug(fmt, ...) log_emit(LOG_DEBUG, "[iot] " fmt, ##__VA_ARGS__) - -/** - * @brief Duplicate a string using PAL's allocator. - * - * @param pal PAL adapter providing malloc - * @param str Source string (NULL returns NULL) - * @return Newly allocated copy, or NULL on failure. Caller must free via pal->free. - */ -static inline char *pal_strdup(const pal_t *pal, const char *str) -{ - if (!str) return NULL; - size_t len = strlen(str) + 1; - char *copy = (char *)pal->malloc(len); - if (copy) memcpy(copy, str, len); - return copy; -} - -#endif /* ifndef __IOT_CONFIG_DEFAULTS_H__ */ diff --git a/modules/iot-client/src/iot_dns.c b/modules/iot-client/src/iot_dns.c index 136e7cc..d43dfd8 100644 --- a/modules/iot-client/src/iot_dns.c +++ b/modules/iot-client/src/iot_dns.c @@ -1,6 +1,6 @@ #include "iot_dns.h" #include "http_client_interface.h" -#include "iot_config_defaults.h" +#include "iot_internal.h" #include "cipher_wrapper.h" #include "cJSON.h" diff --git a/modules/iot-client/src/iot_dns.h b/modules/iot-client/src/iot_dns.h index d752d85..449811c 100644 --- a/modules/iot-client/src/iot_dns.h +++ b/modules/iot-client/src/iot_dns.h @@ -4,7 +4,8 @@ #include #include #include -#include "iot_config_defaults.h" +#include "iot_internal.h" +#include "iot_client.h" #include "tls.h" #define IOT_DNS_DEFAULT_HOST "h1.iot-dns.com" diff --git a/modules/iot-client/src/iot_dp.c b/modules/iot-client/src/iot_dp.c index 1de7bbd..2a5933c 100644 --- a/modules/iot-client/src/iot_dp.c +++ b/modules/iot-client/src/iot_dp.c @@ -12,7 +12,7 @@ #include "iot_dp.h" #include "iot_dp_internal.h" -#include "iot_config_defaults.h" /* pal_strdup, log_*, IOT_DEFAULT_PORT */ +#include "iot_internal.h" /* pal_strdup, log_*, IOT_DEFAULT_PORT, DP publish gate */ #include "atop.h" /* atop_schema_newest_get */ #include "iot_dns.h" /* iot_region_to_host (via resolve helper) */ #include "cJSON.h" @@ -27,8 +27,11 @@ #define DP_PROTO_REPORT 4 /* uplink DP report (device -> cloud) */ #define DP_PROTO_DOWN 5 /* downlink DP set (cloud -> device) */ -/* Mirror of mqtt.c MQTT_MAX_PACKET_SIZE and iot_client_message.c PV23_OVERHEAD. */ -#define DP_MQTT_MAX_PAYLOAD 4096 +/* Derived from AGENTIC_KIT_MQTT_MAX_PACKET_SIZE (include/iot_client_config_defaults.h) + * so the DP publish gate follows the knob automatically. DP_PV23_OVERHEAD mirrors the + * private PV23_OVERHEAD in iot_client_message.c (AAD 12 + IV 12 + TAG 16) -- crypto + * geometry, not a knob. */ +#define DP_MQTT_MAX_PAYLOAD AGENTIC_KIT_MQTT_MAX_PACKET_SIZE #define DP_PV23_OVERHEAD 40 typedef struct { diff --git a/modules/iot-client/src/iot_internal.h b/modules/iot-client/src/iot_internal.h new file mode 100644 index 0000000..885b138 --- /dev/null +++ b/modules/iot-client/src/iot_internal.h @@ -0,0 +1,138 @@ +/* + * iot_internal.h -- internal (src-private) definitions for iot-client: + * everything the module's sources and host tests share that is NOT a + * build-time knob. It restores, renamed, the former src/iot_config_defaults.h + * (which had been absorbed into include/iot_client_config_defaults.h) and + * absorbs the former src/iot_client_internal.h. The AGENTIC_KIT_* knob + * defaults stay in include/iot_client_config_defaults.h, pulled in below -- + * mirroring tai_internal.h; that file includes common/log.h first, the + * integrator-override pickup. The public client API lives in + * include/iot_client.h. + * + * @copyright Copyright (c) 2021-2025 Tuya Inc. All Rights Reserved. + */ + +#ifndef IOT_INTERNAL_H +#define IOT_INTERNAL_H + +#include "iot_client_config_defaults.h" + +#include +#include + +#include "iot_client.h" +#include "log.h" +#include "pal.h" + +/* ========================================================================= + * iot-client: release-managed version data (tools/bump_version rewrites + * SDK_VERSION below; these ride releases, not integrator overrides -- + * not build knobs) + * ========================================================================= */ + +#ifndef IOT_SDK_SW_VER +#define IOT_SDK_SW_VER "1.0.0" +#endif +#ifndef IOT_SDK_PV +#define IOT_SDK_PV "2.3" +#endif +#ifndef IOT_SDK_BV +#define IOT_SDK_BV "2.0" +#endif +#define SDK_VERSION "agentic-kit_0.4.0" + +/* ========================================================================= + * iot-client: service endpoints & protocol constants (fixed by the Tuya + * cloud; not tunable per product) + * ========================================================================= */ + +#define IOT_HTTP_TIMEOUT_MS_DEFAULT 5000 + +/* Default Tuya cloud server configuration */ +#define IOT_DEFAULT_HOST "a1.tuyacn.com" +#define IOT_DEFAULT_PRE_HOST "a1-cn.wgine.com" +#define IOT_CN_HOST "a1.tuyacn.com" +#define IOT_CN_PRE_HOST "a1-cn.wgine.com" +#define IOT_AZ_HOST "a1.tuyaus.com" +#define IOT_AZ_PRE_HOST "a1-us.wgine.com" +#define IOT_UEAZ_HOST "a1-ueaz.tuyaus.com" +#define IOT_UEAZ_PRE_HOST "a1-ueaz.wgine.com" +#define IOT_EU_HOST "a1.tuyaeu.com" +#define IOT_EU_PRE_HOST "a1-eu.wgine.com" +#define IOT_WEAZ_HOST "a1-weaz.tuyaeu.com" +#define IOT_WEAZ_PRE_HOST "a1-weaz.wgine.com" +#define IOT_IN_HOST "a1.tuyain.com" +#define IOT_IN_PRE_HOST "a1-in.wgine.com" +#define IOT_SG_HOST "a1-sg.iotbing.com" +#define IOT_TEST_HOST "https://127.0.0.1:8443" + +#define IOT_DEFAULT_MQTT_URL "mqtts://a6.tuyacn.com:8883" + +#define IOT_DEFAULT_PORT 443 + +/* ========================================================================= + * iot-client: host-side PAL default (src/iot_pal_defaults.c defines it; + * each ESP-IDF app provides its own get_default_pal) + * ========================================================================= */ + +/** + * @brief Get the built-in default PAL adapter (POSIX or FreeRTOS). + * @return Pointer to a static pal_t with all required function pointers set + */ +#ifdef __cplusplus +extern "C" { +#endif + +const pal_t *get_default_pal(void); + +#ifdef __cplusplus +} +#endif + +/* Module-tagged dispatch into the global log facade. Tag is folded + * into the format string at compile time, so the log handler stays + * tag-agnostic and the call site reads exactly like printf(). */ +#define log_error(fmt, ...) log_emit(LOG_ERROR, "[iot] " fmt, ##__VA_ARGS__) +#define log_warn(fmt, ...) log_emit(LOG_WARN, "[iot] " fmt, ##__VA_ARGS__) +#define log_info(fmt, ...) log_emit(LOG_INFO, "[iot] " fmt, ##__VA_ARGS__) +#define log_debug(fmt, ...) log_emit(LOG_DEBUG, "[iot] " fmt, ##__VA_ARGS__) + +/** + * @brief Duplicate a string using PAL's allocator. + * + * @param pal PAL adapter providing malloc + * @param str Source string (NULL returns NULL) + * @return Newly allocated copy, or NULL on failure. Caller must free via pal->free. + */ +static inline char *pal_strdup(const pal_t *pal, const char *str) +{ + if (!str) return NULL; + size_t len = strlen(str) + 1; + char *copy = (char *)pal->malloc(len); + if (copy) memcpy(copy, str, len); + return copy; +} + +/* ========================================================================= + * iot-client: src-private hooks for the client core, used by iot_client.c + * and the host test suites. + * ========================================================================= */ + +/** + * @brief Issue the two init-time version reports — SDK meta save and firmware + * version update — unless config->skip_version_report is set. + * + * Non-fatal by design: each failed report logs a warning and client init + * proceeds, exactly as when this lived inline in iot_client_init(). Split out + * so host tests can pin the skip / no-skip contract against the ATOP mock + * with a hand-shaped client, without the DNS / activation path in front of it + * (iot_client_init() with a non-empty devid queries the real IoT-DNS host). + * + * @return OPRT_OK when both reports were skipped or both succeeded; + * OPRT_INVALID_PARAMETER for a NULL client or config; + * otherwise the first failure (both reports are still attempted). + */ +int iot_client_report_init_versions(iot_client_t *client, + const iot_client_config_t *config); + +#endif /* IOT_INTERNAL_H */ diff --git a/modules/iot-client/src/iot_on_boarding.c b/modules/iot-client/src/iot_on_boarding.c index e752639..e193cc3 100644 --- a/modules/iot-client/src/iot_on_boarding.c +++ b/modules/iot-client/src/iot_on_boarding.c @@ -2,7 +2,7 @@ #include "iot_client.h" #include "mqtt.h" #include "iot_dns.h" -#include "iot_config_defaults.h" +#include "iot_internal.h" #include "cJSON.h" #include "cipher_wrapper.h" #include "atop.h" diff --git a/modules/iot-client/src/iot_on_boarding.h b/modules/iot-client/src/iot_on_boarding.h index 8e207f3..4029b0a 100644 --- a/modules/iot-client/src/iot_on_boarding.h +++ b/modules/iot-client/src/iot_on_boarding.h @@ -1,7 +1,9 @@ #ifndef __IOT_ON_BOARDING_H__ #define __IOT_ON_BOARDING_H__ -#include "iot_config_defaults.h" +#include "iot_internal.h" +#include "iot_client.h" +#include "tls.h" /** @brief Internal on-boarding configuration (populated by iot_client.c from public config). */ typedef struct { diff --git a/modules/iot-client/src/iot_ota.c b/modules/iot-client/src/iot_ota.c index 4f37ad7..415e4cc 100644 --- a/modules/iot-client/src/iot_ota.c +++ b/modules/iot-client/src/iot_ota.c @@ -1,7 +1,7 @@ #include "iot_ota.h" #include "atop.h" #include "iot_dp_internal.h" -#include "iot_config_defaults.h" +#include "iot_internal.h" #include diff --git a/modules/iot-client/src/iot_ota_verify.c b/modules/iot-client/src/iot_ota_verify.c index 145ca41..2c90a0d 100644 --- a/modules/iot-client/src/iot_ota_verify.c +++ b/modules/iot-client/src/iot_ota_verify.c @@ -1,5 +1,5 @@ #include "iot_ota.h" -#include "iot_config_defaults.h" +#include "iot_internal.h" #include #include diff --git a/modules/iot-client/src/iot_pal_defaults.c b/modules/iot-client/src/iot_pal_defaults.c index 4a25076..a6c0905 100644 --- a/modules/iot-client/src/iot_pal_defaults.c +++ b/modules/iot-client/src/iot_pal_defaults.c @@ -1,4 +1,4 @@ -#include "iot_config_defaults.h" +#include "iot_internal.h" extern const pal_t *tai_pal_posix(void); diff --git a/modules/iot-client/src/mqtt.c b/modules/iot-client/src/mqtt.c index 231d9bd..831fe86 100644 --- a/modules/iot-client/src/mqtt.c +++ b/modules/iot-client/src/mqtt.c @@ -1,5 +1,7 @@ #include "mqtt.h" -#include "iot_config_defaults.h" +/* Transport knob defaults (AGENTIC_KIT_MQTT_MAX_PACKET_SIZE etc.) live in + * include/iot_client_config_defaults.h. */ +#include "iot_internal.h" #include #include @@ -8,22 +10,6 @@ #include "core_mqtt.h" -#ifndef MQTT_MAX_PACKET_SIZE -#define MQTT_MAX_PACKET_SIZE 4096 -#endif - -#ifndef MQTT_SEND_TIMEOUT_MS -#define MQTT_SEND_TIMEOUT_MS 2000U -#endif - -#ifndef MQTT_RECV_TIMEOUT_MS -#define MQTT_RECV_TIMEOUT_MS 1000U -#endif - -#ifndef MQTT_CONNECT_TIMEOUT_MS -#define MQTT_CONNECT_TIMEOUT_MS 10000U -#endif - // Shared TLS transport (mbedTLS lives entirely inside common/tls). #include "tls.h" @@ -107,7 +93,7 @@ static int32_t transport_send(NetworkContext_t *pNetworkContext, } int bytes_sent = pNetworkContext->client->pal->tcp_send( pNetworkContext->tcp_handle, (const uint8_t *)pBuffer, bytesToSend, - MQTT_SEND_TIMEOUT_MS); + AGENTIC_KIT_MQTT_SEND_TIMEOUT_MS); if (bytes_sent == PAL_ERR_AGAIN) { return 0; } @@ -129,7 +115,7 @@ static int32_t transport_recv(NetworkContext_t *pNetworkContext, if (pNetworkContext->use_tls) { int n = tls_read(pNetworkContext->tls, (uint8_t *)pBuffer, bytesToRecv, - MQTT_RECV_TIMEOUT_MS); + AGENTIC_KIT_MQTT_RECV_TIMEOUT_MS); if (n == TLS_ERR_AGAIN) { return OPRT_OK; // no data within timeout: coreMQTT polls again } @@ -148,7 +134,7 @@ static int32_t transport_recv(NetworkContext_t *pNetworkContext, } int bytes_received = pNetworkContext->client->pal->tcp_recv( pNetworkContext->tcp_handle, (uint8_t *)pBuffer, bytesToRecv, - MQTT_RECV_TIMEOUT_MS); + AGENTIC_KIT_MQTT_RECV_TIMEOUT_MS); if (bytes_received == PAL_ERR_AGAIN) { return OPRT_OK; } @@ -197,7 +183,7 @@ static int parse_broker_url(const char *url, char *host, int *port) { // Establish TCP connection to broker (non-TLS) static void *connect_to_broker(mqtt_client *client) { void *handle = client->pal->tcp_connect(client->broker_host, (uint16_t)client->broker_port, - MQTT_CONNECT_TIMEOUT_MS); + AGENTIC_KIT_MQTT_CONNECT_TIMEOUT_MS); if (!handle) { log_error("Failed to connect to broker %s:%d", client->broker_host, client->broker_port); return NULL; @@ -223,7 +209,7 @@ static int connect_to_broker_tls(NetworkContext_t *network_ctx, const char *host .verify = TLS_VERIFY_NONE, // no CA -> no verification (legacy behaviour) .force_tls12 = true, .ciphersuites = tls_ciphersuites_tuya_default(), - .connect_timeout_ms = MQTT_CONNECT_TIMEOUT_MS, + .connect_timeout_ms = AGENTIC_KIT_MQTT_CONNECT_TIMEOUT_MS, .pal = network_ctx->client->pal, }; @@ -439,13 +425,13 @@ int mqtt_client_connect(mqtt_client *client) { // Allocate the MQTT fixed buffer on demand (freed on disconnect/destroy) if (!client->buffer) { - client->buffer = client->pal->malloc(MQTT_MAX_PACKET_SIZE); + client->buffer = client->pal->malloc(AGENTIC_KIT_MQTT_MAX_PACKET_SIZE); if (!client->buffer) { - log_error("Failed to allocate MQTT buffer (%u bytes)", MQTT_MAX_PACKET_SIZE); + log_error("Failed to allocate MQTT buffer (%u bytes)", AGENTIC_KIT_MQTT_MAX_PACKET_SIZE); return OPRT_MALLOC_FAILED; } client->fixed_buffer.pBuffer = client->buffer; - client->fixed_buffer.size = MQTT_MAX_PACKET_SIZE; + client->fixed_buffer.size = AGENTIC_KIT_MQTT_MAX_PACKET_SIZE; } // Set client pointer in network context for callback access @@ -508,7 +494,7 @@ int mqtt_client_connect(mqtt_client *client) { bool sessionPresent = false; status = MQTT_Connect(&client->mqtt_context, &connect_info, NULL, - MQTT_SEND_TIMEOUT_MS, &sessionPresent); + AGENTIC_KIT_MQTT_SEND_TIMEOUT_MS, &sessionPresent); if (status != MQTTSuccess) { log_error("MQTT_Connect failed: %s (%d)", MQTT_Status_strerror(status), status); diff --git a/modules/iot-client/test/atop_test.c b/modules/iot-client/test/atop_test.c index 0cf1831..5dec5ba 100644 --- a/modules/iot-client/test/atop_test.c +++ b/modules/iot-client/test/atop_test.c @@ -16,7 +16,7 @@ #include "atop.h" #include "atop_base.h" #include "iot_client.h" -#include "iot_config_defaults.h" +#include "iot_internal.h" #define MOCK_HOST "127.0.0.1" #define MOCK_PORT 8443 diff --git a/modules/iot-client/test/cipher_test.c b/modules/iot-client/test/cipher_test.c index ebd1929..b8e097a 100644 --- a/modules/iot-client/test/cipher_test.c +++ b/modules/iot-client/test/cipher_test.c @@ -4,7 +4,7 @@ #include "cipher_wrapper.h" #include "iot_client.h" -#include "iot_config_defaults.h" +#include "iot_internal.h" #include "rng.h" static int tests_run = 0; diff --git a/modules/iot-client/test/dns_test.c b/modules/iot-client/test/dns_test.c index 0dc1968..e3fc1bc 100644 --- a/modules/iot-client/test/dns_test.c +++ b/modules/iot-client/test/dns_test.c @@ -13,7 +13,7 @@ #include "iot_dns.h" #include "iot_client.h" -#include "iot_config_defaults.h" +#include "iot_internal.h" #define MOCK_HOST "127.0.0.1" #define MOCK_PORT 8198 diff --git a/modules/iot-client/test/iot_atop_call_test.c b/modules/iot-client/test/iot_atop_call_test.c index 1d9a764..e2e45e5 100644 --- a/modules/iot-client/test/iot_atop_call_test.c +++ b/modules/iot-client/test/iot_atop_call_test.c @@ -27,7 +27,7 @@ #include "iot_atop.h" #include "iot_client.h" -#include "iot_config_defaults.h" +#include "iot_internal.h" #include "log.h" #include @@ -462,7 +462,7 @@ static int test_unknown_api_reaches_the_cloud(void) return 0; } -/* A response larger than RESPONSE_BUFFER_SIZE must fail loudly. +/* A response larger than AGENTIC_KIT_RESPONSE_BUFFER_SIZE must fail loudly. * * This is the failure from CHANGELOG: a product whose schema came back at * contentLength 6558 against a 4096 buffer, where coreHTTP returned diff --git a/modules/iot-client/test/iot_client_message_test.c b/modules/iot-client/test/iot_client_message_test.c index 23dd264..89f816d 100644 --- a/modules/iot-client/test/iot_client_message_test.c +++ b/modules/iot-client/test/iot_client_message_test.c @@ -11,8 +11,8 @@ #include "iot_client.h" #include "iot_client_message.h" -#include "iot_client_internal.h" -#include "iot_config_defaults.h" +#include "iot_internal.h" +#include "iot_internal.h" #define TEST_DEVID "test_device_msg_001" diff --git a/modules/iot-client/test/iot_dp_test.c b/modules/iot-client/test/iot_dp_test.c index e8c569f..4c6c157 100644 --- a/modules/iot-client/test/iot_dp_test.c +++ b/modules/iot-client/test/iot_dp_test.c @@ -23,7 +23,7 @@ #include "iot_client.h" #include "iot_client_message.h" -#include "iot_config_defaults.h" +#include "iot_internal.h" #include "iot_dp.h" #include "iot_dp_internal.h" #include "atop.h" diff --git a/modules/iot-client/test/iot_ota_test.c b/modules/iot-client/test/iot_ota_test.c index 0fa927b..a09fa6d 100644 --- a/modules/iot-client/test/iot_ota_test.c +++ b/modules/iot-client/test/iot_ota_test.c @@ -13,7 +13,7 @@ #include "atop.h" #include "iot_client.h" -#include "iot_config_defaults.h" +#include "iot_internal.h" #define MOCK_HOST "127.0.0.1" #define MOCK_PORT 8443 diff --git a/modules/iot-client/test/iot_ota_verify_test.c b/modules/iot-client/test/iot_ota_verify_test.c index 78e3d95..6c50fff 100644 --- a/modules/iot-client/test/iot_ota_verify_test.c +++ b/modules/iot-client/test/iot_ota_verify_test.c @@ -18,7 +18,7 @@ #include "iot_client.h" #include "iot_ota.h" -#include "iot_config_defaults.h" +#include "iot_internal.h" #define TEST_KEY "test_secret_key_0123456789abcd" /* 30 chars + NUL fits secret_key[32] */ #define DATA_LEN 1000 diff --git a/modules/iot-client/test/iot_reset_test.c b/modules/iot-client/test/iot_reset_test.c index 82b4864..a623190 100644 --- a/modules/iot-client/test/iot_reset_test.c +++ b/modules/iot-client/test/iot_reset_test.c @@ -35,7 +35,7 @@ #include #include "iot_client.h" -#include "iot_config_defaults.h" +#include "iot_internal.h" #define MOCK_HOST "127.0.0.1" #define MOCK_PORT 8443 diff --git a/modules/iot-client/test/iot_session_token_test.c b/modules/iot-client/test/iot_session_token_test.c index 4b70ab0..6e44ee5 100644 --- a/modules/iot-client/test/iot_session_token_test.c +++ b/modules/iot-client/test/iot_session_token_test.c @@ -29,7 +29,7 @@ #include #include "iot_client.h" -#include "iot_config_defaults.h" +#include "iot_internal.h" #include "log.h" #define MOCK_HOST "127.0.0.1" diff --git a/modules/iot-client/test/mock/atop_mock.py b/modules/iot-client/test/mock/atop_mock.py index a2ecb87..8181b94 100755 --- a/modules/iot-client/test/mock/atop_mock.py +++ b/modules/iot-client/test/mock/atop_mock.py @@ -732,7 +732,7 @@ def do_POST(self): response_json = handle_upgrade_status_update(decrypted_data, self.config) elif api == 'tuya.test.huge.result': # Test-only: a response deliberately larger than the default - # 4096-byte RESPONSE_BUFFER_SIZE, reproducing the real failure + # 4096-byte AGENTIC_KIT_RESPONSE_BUFFER_SIZE, reproducing the real failure # recorded in CHANGELOG (a product whose schema came back at # contentLength 6558). coreHTTP answers HTTPInsufficientMemory; # the point of the test is that it now SAYS so. diff --git a/modules/iot-client/test/mqtt_test.c b/modules/iot-client/test/mqtt_test.c index 391d84c..e50d40b 100644 --- a/modules/iot-client/test/mqtt_test.c +++ b/modules/iot-client/test/mqtt_test.c @@ -9,7 +9,7 @@ #include "mqtt.h" #include "iot_client.h" -#include "iot_config_defaults.h" +#include "iot_internal.h" #include "log.h" #define TEST_CLIENT_ID "mqtt_test_client" diff --git a/modules/iot-client/test/on_boarding_test.c b/modules/iot-client/test/on_boarding_test.c index 4070d4f..6867a4c 100644 --- a/modules/iot-client/test/on_boarding_test.c +++ b/modules/iot-client/test/on_boarding_test.c @@ -13,7 +13,7 @@ #include "iot_on_boarding.h" #include "iot_client.h" -#include "iot_config_defaults.h" +#include "iot_internal.h" #define MOCK_DNS_HOST "127.0.0.1" diff --git a/modules/rtc-tcp-client/CONTEXT.md b/modules/rtc-tcp-client/CONTEXT.md index 8959e17..9ef2d42 100644 --- a/modules/rtc-tcp-client/CONTEXT.md +++ b/modules/rtc-tcp-client/CONTEXT.md @@ -151,7 +151,7 @@ no large contiguous frame buffer: `send_one_frame_sg` signs the logical `[frame header || app header || payload]` via `tai_frame_hmac_sg` (byte-identical to the contiguous HMAC — the receiver is unchanged), then -emits the frame in one of two shapes, split by `TAI_FRAME_COALESCE_LIMIT` (default 512 B, +emits the frame in one of two shapes, split by `AGENTIC_KIT_TAI_FRAME_COALESCE_LIMIT` (default 512 B, counting the whole frame: header, app header, payload, signature): - **Below the limit**: the frame is coalesced into `tx_ctrl_buf` and sent as ONE TLS record. @@ -192,7 +192,7 @@ is signed. The worker loops: check the liveness deadline, send a Ping when due, then block in `tai_recv_data` until bytes arrive or the next Ping falls due, then drain. The drain is -time-bounded (`TAI_DRAIN_BUDGET_MS`, default 150 ms) so a sustained downstream flood cannot +time-bounded (`AGENTIC_KIT_TAI_DRAIN_BUDGET_MS`, default 150 ms) so a sustained downstream flood cannot starve the Ping / liveness / shutdown checks — leftover bytes wait for the next pass; and any successful receive refreshes the liveness clock. Bytes accumulate in a sliding receive buffer; EOF or a transport error makes the worker fire `on_disconnect`. diff --git a/modules/rtc-tcp-client/include/tai_config_defaults.h b/modules/rtc-tcp-client/include/tai_config_defaults.h new file mode 100644 index 0000000..f448894 --- /dev/null +++ b/modules/rtc-tcp-client/include/tai_config_defaults.h @@ -0,0 +1,115 @@ +/* + * tai_config_defaults.h -- rtc-tcp-client (TAI 2.1) build-time knobs: + * buffer sizing and worker scheduling. Lives beside the module's public + * headers; module sources include it directly (src/tai_internal.h). + * + * It pulls common/log.h FIRST: that header is where integrator overrides + * (agentic_kit_config.h on the include path, -D, AGENTIC_KIT_USER_CONFIG) + * are applied -- so overrides win over every default below. Not a public + * API header. + */ + +#ifndef AGENTIC_KIT_TAI_CONFIG_DEFAULTS_H +#define AGENTIC_KIT_TAI_CONFIG_DEFAULTS_H + +#include "log.h" + +/* ========================================================================= + * rtc-tcp-client / TAI 2.1 (src/tai_internal.h, src/tai_pkt_log.c) + * ========================================================================= + * Buffer-size compile-time knobs: reduce these for memory-constrained + * targets (e.g. ESP32 without PSRAM). + * + * Send path (§6): media packets (audio / image / large text / MCP JSON) are + * streamed scatter-gather -- only a small header is built (tx_hdr_buf) and + * the caller's payload is signed + sent zero-copy, so there is NO large TX + * buffer. Control packets (hello, session, event start/end, ping) are still + * assembled contiguously in tx_ctrl_buf because their attribute block can + * carry the user session/event JSON (escaped) -- which must fit, hence the + * kilobyte sizing. + */ + +/* Sample 1-in-N media MIDDLE frames to INFO (tai_pkt_log.c). All + * non-sampled MIDDLE frames are dropped (no log line). Set to 0 to disable + * sampling, in which case every MIDDLE frame logs at DEBUG instead. */ +#ifndef AGENTIC_KIT_TAI_LOG_MEDIA_SAMPLE_N +#define AGENTIC_KIT_TAI_LOG_MEDIA_SAMPLE_N 50 +#endif + +/* Maximum bytes per transport fragment payload. Used both to fragment + * OUTBOUND packets and -- crucially -- advertised to the server in + * ClientHello as TAI_ATTR_MAX_FRAGMENT_LEN, so the server must not send an + * inbound fragment whose frame exceeds rx_buf (see TAI_RX_BUF_SIZE, derived + * in tai_internal.h). Smaller = less RX RAM but more frames (per-frame + * 5+sig overhead) for large payloads. */ +#ifndef AGENTIC_KIT_TAI_MAX_FRAGMENT_PAYLOAD +#define AGENTIC_KIT_TAI_MAX_FRAGMENT_PAYLOAD 4096U +#endif + +/* Fragment reassembly buffer. A transport-fragmented packet (FRAG_FIRST..LAST) + * is reassembled here before the whole packet is decoded, so this bounds the + * largest INBOUND application packet (not fragment): it must be >= the + * largest downstream packet the server may send (a big Event / MCP-command / + * context JSON). A packet that reassembles larger is fail-fast + * (TAI_PROTO_ERR_FRAG). 32000 ~= 7 max fragments. */ +#ifndef AGENTIC_KIT_TAI_FRAG_BUF_SIZE +#define AGENTIC_KIT_TAI_FRAG_BUF_SIZE 32000U +#endif + +/* Scatter-gather header buffer: [5-byte frame header][app header] for one + * frame. Bounds the application header (pkt byte + attr block + media/text + * header); the streamed payload is never copied here. */ +#ifndef AGENTIC_KIT_TAI_TX_HDR_BUF_SIZE +#define AGENTIC_KIT_TAI_TX_HDR_BUF_SIZE 256U +#endif + +/* Small-frame coalesce threshold: a whole frame (frame hdr + app hdr + + * payload + signature) STRICTLY smaller than this is copied into + * tx_ctrl_buf and sent as one transport write (one TLS record instead of + * 2-3); at or above it the frame keeps the zero-copy scatter-gather path. + * The send path also caps coalescing at AGENTIC_KIT_TAI_TX_CTRL_BUF_SIZE, so shrinking + * either knob is safe -- it only narrows the size window that gets + * coalesced. */ +#ifndef AGENTIC_KIT_TAI_FRAME_COALESCE_LIMIT +#define AGENTIC_KIT_TAI_FRAME_COALESCE_LIMIT 512U +#endif + +/* Control-packet assembly buffer. Must hold the largest control application + * packet -- dominated by the session/event JSON escaped into attr 111. The + * SessionNew / EventStart packet is roughly 2*strlen(JSON) + ~115 bytes of + * framing/attrs, so the session/event JSON must satisfy that bound or + * SessionNew/EventStart returns TAI_ERR_MEM. Default 1024 ~= 4x the largest + * packet the bundled examples build (~260 B) and fits JSON up to ~700 chars; + * raise it (e.g. 2048/4096) for richer session configs. It doubles as the + * small-frame coalesce scratch (see AGENTIC_KIT_TAI_FRAME_COALESCE_LIMIT) -- a control + * packet is shifted in place inside the same buffer, never copied out. */ +#ifndef AGENTIC_KIT_TAI_TX_CTRL_BUF_SIZE +#define AGENTIC_KIT_TAI_TX_CTRL_BUF_SIZE 1024U +#endif + +/* Maximum attributes decoded from a single packet. */ +#ifndef AGENTIC_KIT_TAI_MAX_ATTRS +#define AGENTIC_KIT_TAI_MAX_ATTRS 32 +#endif + +/* Max wall-clock the receive worker spends draining buffered frames before + * yielding to periodic ping / pong-timeout / shutdown checks. Bounds + * keepalive and shutdown latency under a sustained downstream flood; any + * leftover bytes stay buffered and are processed on the next loop + * iteration. */ +#ifndef AGENTIC_KIT_TAI_DRAIN_BUDGET_MS +#define AGENTIC_KIT_TAI_DRAIN_BUDGET_MS 150U +#endif + +/* Upper bound on a single idle receive-block in the worker loop. The worker + * would otherwise block until the next ping is due (up to ping_interval_ms, + * default 60 s); capping it bounds how long tai_disconnect (running=0) + * waits for the worker to notice and exit, without depending on the PAL to + * cap its own recv timeout. Idle cost: the worker wakes ~1000/cap times per + * second to re-check; it does not affect inbound-data latency (recv returns + * as soon as bytes arrive). */ +#ifndef AGENTIC_KIT_TAI_WORKER_POLL_CAP_MS +#define AGENTIC_KIT_TAI_WORKER_POLL_CAP_MS 2000U +#endif + +#endif /* AGENTIC_KIT_TAI_CONFIG_DEFAULTS_H */ diff --git a/modules/rtc-tcp-client/src/tai_client.c b/modules/rtc-tcp-client/src/tai_client.c index fcd5ba4..fc877a1 100644 --- a/modules/rtc-tcp-client/src/tai_client.c +++ b/modules/rtc-tcp-client/src/tai_client.c @@ -115,12 +115,12 @@ static void log_send_packet(tai_ctx_t *ctx, const uint8_t *app_bytes, size_t app_len) { uint8_t pkt_type; - tai_attr_t attrs[TAI_MAX_ATTRS]; + tai_attr_t attrs[AGENTIC_KIT_TAI_MAX_ATTRS]; int attr_count = 0; const uint8_t *payload; size_t payload_len; if (tai_packet_decode(ctx->proto_ver, app_bytes, app_len, - &pkt_type, attrs, TAI_MAX_ATTRS, &attr_count, + &pkt_type, attrs, AGENTIC_KIT_TAI_MAX_ATTRS, &attr_count, &payload, &payload_len) == TAI_OK) { tai_log_packet(ctx->proto_ver, 1, pkt_type, attrs, attr_count, payload, payload_len); @@ -187,7 +187,7 @@ static int send_one_frame_sg(tai_ctx_t *ctx, uint8_t frag_flag, uint16_t seq, * the frame header overwrites its front; the HMAC above sampled the * original bytes, which the in-place shift preserves. Capped by the smaller * of the coalesce limit and tx_ctrl_buf so shrinking either knob stays safe. */ - if (wire_len < TAI_FRAME_COALESCE_LIMIT && + if (wire_len < AGENTIC_KIT_TAI_FRAME_COALESCE_LIMIT && wire_len <= sizeof(ctx->tx_ctrl_buf)) { uint8_t *buf = ctx->tx_ctrl_buf; if (pay_len) /* pay is NULL when 0 */ @@ -221,11 +221,11 @@ static int send_app_sg(tai_ctx_t *ctx, size_t hdr_len, const uint8_t *payload, size_t payload_len) { if (TAI_FRAME_HDR_LEN + hdr_len > sizeof(ctx->tx_hdr_buf)) return TAI_ERR_MEM; - if (hdr_len >= TAI_MAX_FRAGMENT_PAYLOAD) return TAI_ERR_MEM; + if (hdr_len >= AGENTIC_KIT_TAI_MAX_FRAGMENT_PAYLOAD) return TAI_ERR_MEM; size_t total = hdr_len + payload_len; - if (total <= TAI_MAX_FRAGMENT_PAYLOAD) { + if (total <= AGENTIC_KIT_TAI_MAX_FRAGMENT_PAYLOAD) { uint16_t seq = tai_next_seq(ctx); int rc = send_one_frame_sg(ctx, TAI_FRAG_NONE, seq, hdr_len, payload, payload_len); @@ -237,12 +237,12 @@ static int send_app_sg(tai_ctx_t *ctx, size_t hdr_len, } /* Fragment over the logical hdr||payload concat. The header lives only in - * the first fragment (hdr_len < TAI_MAX_FRAGMENT_PAYLOAD guarantees the + * the first fragment (hdr_len < AGENTIC_KIT_TAI_MAX_FRAGMENT_PAYLOAD guarantees the * whole header plus some payload fits the first chunk). */ size_t offset = 0; while (offset < total) { size_t chunk = total - offset; - if (chunk > TAI_MAX_FRAGMENT_PAYLOAD) chunk = TAI_MAX_FRAGMENT_PAYLOAD; + if (chunk > AGENTIC_KIT_TAI_MAX_FRAGMENT_PAYLOAD) chunk = AGENTIC_KIT_TAI_MAX_FRAGMENT_PAYLOAD; uint8_t flag = (offset == 0) ? TAI_FRAG_FIRST : (offset + chunk >= total) ? TAI_FRAG_LAST : TAI_FRAG_MIDDLE; @@ -473,11 +473,11 @@ int tai_connect(tai_ctx_t *ctx) uint16_t seq = tai_next_seq(ctx); /* 5-byte frame hdr + the ClientHello app block (built into tx_ctrl_buf, so - * bounded by TAI_TX_CTRL_BUF_SIZE). Sized to that bound rather than a fixed + * bounded by AGENTIC_KIT_TAI_TX_CTRL_BUF_SIZE). Sized to that bound rather than a fixed * 256 so a long client_id/device_id frames fine — the only limit is the * same tx_ctrl_buf that the build step already enforces. Unsigned, so no * signature trailer. */ - uint8_t ch_frame[TAI_FRAME_HDR_LEN + TAI_TX_CTRL_BUF_SIZE]; + uint8_t ch_frame[TAI_FRAME_HDR_LEN + AGENTIC_KIT_TAI_TX_CTRL_BUF_SIZE]; int frame_len = tai_frame_encode(TAI_FRAG_NONE, seq, ctx->tx_ctrl_buf, (size_t)app_len, ctx->sign_key, 0, /* sig_len=0 */ @@ -593,7 +593,7 @@ void tai_disconnect(tai_ctx_t *ctx) TAI_LOGI(ctx->pal, TAG, "disconnecting"); /* Stop and join the background worker FIRST. With running=0 the worker exits - * its next loop pass -- bounded by TAI_WORKER_POLL_CAP_MS (~200 ms) even when + * its next loop pass -- bounded by AGENTIC_KIT_TAI_WORKER_POLL_CAP_MS (~200 ms) even when * idle, so this no longer blocks up to a whole ping interval. Joining BEFORE * we send SessionClose / close the socket is deliberate: it guarantees the * worker can neither (a) race our send on the shared TX buffers, nor (b) @@ -649,7 +649,7 @@ static int process_app_packet(tai_ctx_t *ctx, const uint8_t *app_bytes, size_t app_len) { uint8_t pkt_type; - tai_attr_t attrs[TAI_MAX_ATTRS]; + tai_attr_t attrs[AGENTIC_KIT_TAI_MAX_ATTRS]; int attr_count = 0; const uint8_t *payload; size_t payload_len; @@ -657,7 +657,7 @@ static int process_app_packet(tai_ctx_t *ctx, int rc = tai_packet_decode(ctx->proto_ver, app_bytes, app_len, &pkt_type, - attrs, TAI_MAX_ATTRS, &attr_count, + attrs, AGENTIC_KIT_TAI_MAX_ATTRS, &attr_count, &payload, &payload_len); if (rc != TAI_OK) { TAI_LOGW(ctx->pal, TAG, "packet decode failed: %d (app_len=%zu)", rc, app_len); @@ -1286,8 +1286,8 @@ static void *worker_thread(void *arg) ? 1 : (uint32_t)(ctx->ping_interval_ms - since_ping); /* Cap the idle block so tai_disconnect (running=0) is noticed within - * ~TAI_WORKER_POLL_CAP_MS instead of waiting out a whole ping interval. */ - if (wait_ms > TAI_WORKER_POLL_CAP_MS) wait_ms = TAI_WORKER_POLL_CAP_MS; + * ~AGENTIC_KIT_TAI_WORKER_POLL_CAP_MS instead of waiting out a whole ping interval. */ + if (wait_ms > AGENTIC_KIT_TAI_WORKER_POLL_CAP_MS) wait_ms = AGENTIC_KIT_TAI_WORKER_POLL_CAP_MS; uint64_t drain_start = ctx->pal->time_ms(); int n = tai_recv_data(ctx, wait_ms); @@ -1306,7 +1306,7 @@ static void *worker_thread(void *arg) /* Bound the greedy drain so periodic ping / liveness / shutdown * checks run even under a sustained flood; leftover bytes wait for * the next pass. */ - if (ctx->pal->time_ms() - drain_start > TAI_DRAIN_BUDGET_MS) + if (ctx->pal->time_ms() - drain_start > AGENTIC_KIT_TAI_DRAIN_BUDGET_MS) break; n = tai_recv_data(ctx, 0); /* drain remainder non-blocking */ } diff --git a/modules/rtc-tcp-client/src/tai_internal.h b/modules/rtc-tcp-client/src/tai_internal.h index 00f2246..369b95a 100644 --- a/modules/rtc-tcp-client/src/tai_internal.h +++ b/modules/rtc-tcp-client/src/tai_internal.h @@ -15,6 +15,11 @@ #include "pal.h" #include "log.h" +/* TAI knob defaults live in include/tai_config_defaults.h, pulled in below; + * it includes common/log.h first -- that header is where integrator + * overrides are picked up (log.h is also included above). */ +#include "tai_config_defaults.h" + #include "tls.h" /* ========================================================================= @@ -65,99 +70,21 @@ #endif /* ========================================================================= - * Buffer-size compile-time knobs - * Reduce these for memory-constrained targets (e.g. ESP32 without PSRAM). - * - * Send path (§6): media packets (audio / image / large text / MCP JSON) are - * streamed scatter-gather — only a small header is built (tx_hdr_buf) and the - * caller's payload is signed + sent zero-copy, so there is NO large TX buffer. - * Control packets (hello, session, event start/end, ping) are still assembled - * contiguously in tx_ctrl_buf because their attribute block can carry the user - * session/event JSON (escaped) — which must fit, hence the kilobyte sizing. + * Buffer sizes — defaults & docs: include/tai_config_defaults.h + * (AGENTIC_KIT_TAI_MAX_FRAGMENT_PAYLOAD, AGENTIC_KIT_TAI_FRAG_BUF_SIZE, AGENTIC_KIT_TAI_TX_HDR_BUF_SIZE, + * AGENTIC_KIT_TAI_FRAME_COALESCE_LIMIT, AGENTIC_KIT_TAI_TX_CTRL_BUF_SIZE, AGENTIC_KIT_TAI_MAX_ATTRS, + * AGENTIC_KIT_TAI_DRAIN_BUDGET_MS, AGENTIC_KIT_TAI_WORKER_POLL_CAP_MS; reduce for + * memory-constrained targets, e.g. ESP32 without PSRAM.) * ========================================================================= */ -/* Maximum bytes per transport fragment payload. Used both to fragment OUTBOUND - * packets and — crucially — advertised to the server in ClientHello as - * TAI_ATTR_MAX_FRAGMENT_LEN, so the server must not send an inbound fragment - * whose frame exceeds rx_buf (see TAI_RX_BUF_SIZE). Smaller = less RX RAM but - * more frames (per-frame 5+sig overhead) for large payloads. */ -#ifndef TAI_MAX_FRAGMENT_PAYLOAD -# define TAI_MAX_FRAGMENT_PAYLOAD 4096U -#endif - /* RX sliding-window buffer. Sized to EXACTLY one maximum wire frame — - * 5 (frame header) + TAI_MAX_FRAGMENT_PAYLOAD (max fragment) + 32 (max HMAC) — + * 5 (frame header) + AGENTIC_KIT_TAI_MAX_FRAGMENT_PAYLOAD (max fragment) + 32 (max HMAC) — * so it is derived, not independently tunable. NOTE: zero headroom. This relies * on the server honouring the TAI_ATTR_MAX_FRAGMENT_LEN we advertise: an inbound * frame larger than this cannot be assembled, so the receive loop stalls and the * connection is torn down by the liveness timeout. There is also no batching — * the worker processes at most one max-size frame per recv pass. */ -#define TAI_RX_BUF_SIZE (TAI_MAX_FRAGMENT_PAYLOAD + 37U) - -/* Fragment reassembly buffer. A transport-fragmented packet (FRAG_FIRST..LAST) - * is reassembled here before the whole packet is decoded, so this bounds the - * largest INBOUND application packet (not fragment): it must be >= the largest - * downstream packet the server may send (a big Event / MCP-command / context - * JSON). A packet that reassembles larger is fail-fast (TAI_PROTO_ERR_FRAG). - * 32000 ≈ 7 max fragments. */ -#ifndef TAI_FRAG_BUF_SIZE -# define TAI_FRAG_BUF_SIZE 32000U -#endif - -/* Scatter-gather header buffer: [5-byte frame header][app header] for one - * frame. Bounds the application header (pkt byte + attr block + media/text - * header); the streamed payload is never copied here. */ -#ifndef TAI_TX_HDR_BUF_SIZE -# define TAI_TX_HDR_BUF_SIZE 256U -#endif - -/* Small-frame coalesce threshold: a whole frame (frame hdr + app hdr + payload - * + signature) STRICTLY smaller than this is copied into tx_ctrl_buf and sent - * as one transport write (one TLS record instead of 2-3); at or above it the - * frame keeps the zero-copy scatter-gather path. The send path also caps - * coalescing at TAI_TX_CTRL_BUF_SIZE, so shrinking either knob is safe — it - * only narrows the size window that gets coalesced. */ -#ifndef TAI_FRAME_COALESCE_LIMIT -# define TAI_FRAME_COALESCE_LIMIT 512U -#endif - -/* Control-packet assembly buffer. Must hold the largest control application - * packet — dominated by the session/event JSON escaped into attr 111. The - * SessionNew / EventStart packet is roughly 2*strlen(JSON) + ~115 bytes of - * framing/attrs, so the session/event JSON must satisfy that bound or - * SessionNew/EventStart returns TAI_ERR_MEM. Default 1024 ≈ 4x the largest - * packet the bundled examples build (~260 B) and fits JSON up to ~700 chars; - * raise it (e.g. 2048/4096) for richer session configs. It doubles as the - * small-frame coalesce scratch (see TAI_FRAME_COALESCE_LIMIT) — a control - * packet is shifted in place inside the same buffer, never copied out. */ -#ifndef TAI_TX_CTRL_BUF_SIZE -# define TAI_TX_CTRL_BUF_SIZE 1024U -#endif - -/* Maximum attributes decoded from a single packet */ -#ifndef TAI_MAX_ATTRS -# define TAI_MAX_ATTRS 32 -#endif - -/* Max wall-clock the receive worker spends draining buffered frames before - * yielding to periodic ping / pong-timeout / shutdown checks. Bounds keepalive - * and shutdown latency under a sustained downstream flood; any leftover bytes - * stay buffered and are processed on the next loop iteration. */ -#ifndef TAI_DRAIN_BUDGET_MS -# define TAI_DRAIN_BUDGET_MS 150U -#endif - -/* Upper bound on a single idle receive-block in the worker loop. The worker - * would otherwise block until the next ping is due (up to ping_interval_ms, - * default 60 s); capping it bounds how long tai_disconnect (running=0) waits for - * the worker to notice and exit, without depending on the PAL to cap its own - * recv timeout. Idle cost: the worker wakes ~1000/cap times per second to - * re-check; it does not affect inbound-data latency (recv returns as soon as - * bytes arrive). */ -#ifndef TAI_WORKER_POLL_CAP_MS -# define TAI_WORKER_POLL_CAP_MS 2000U -#endif - +#define TAI_RX_BUF_SIZE (AGENTIC_KIT_TAI_MAX_FRAGMENT_PAYLOAD + 37U) /* ========================================================================= * Attribute type codes (Appendix A) @@ -319,7 +246,7 @@ struct tai_ctx { size_t rx_len; /* Fragment reassembly */ - uint8_t frag_buf[TAI_FRAG_BUF_SIZE]; + uint8_t frag_buf[AGENTIC_KIT_TAI_FRAG_BUF_SIZE]; size_t frag_len; uint8_t frag_state; /* 0=idle, 1=assembling */ @@ -331,10 +258,10 @@ struct tai_ctx { * assembles a control packet (its attribute block can carry the user * session/event JSON) which is then sent as that zero-copy payload, and * doubles as the coalesce scratch for whole frames under - * TAI_FRAME_COALESCE_LIMIT (one transport write; a control packet shifts + * AGENTIC_KIT_TAI_FRAME_COALESCE_LIMIT (one transport write; a control packet shifts * in place within the same buffer). */ - uint8_t tx_ctrl_buf[TAI_TX_CTRL_BUF_SIZE]; - uint8_t tx_hdr_buf[TAI_TX_HDR_BUF_SIZE]; + uint8_t tx_ctrl_buf[AGENTIC_KIT_TAI_TX_CTRL_BUF_SIZE]; + uint8_t tx_hdr_buf[AGENTIC_KIT_TAI_TX_HDR_BUF_SIZE]; uint8_t tx_sig[32]; /* Background worker thread (auto-started by tai_connect) */ diff --git a/modules/rtc-tcp-client/src/tai_pkt_log.c b/modules/rtc-tcp-client/src/tai_pkt_log.c index c7d4d4b..741e163 100644 --- a/modules/rtc-tcp-client/src/tai_pkt_log.c +++ b/modules/rtc-tcp-client/src/tai_pkt_log.c @@ -8,11 +8,11 @@ * * Log level / volume policy for streaming media (audio/video/image): * - START / END / ONE_SHOT frames : always logged at INFO - * - MIDDLE frames, TAI_LOG_MEDIA_SAMPLE_N > 0 (default): + * - MIDDLE frames, AGENTIC_KIT_TAI_LOG_MEDIA_SAMPLE_N > 0 (default): * only every N-th MIDDLE frame is logged, at INFO, with a * "sample-every" / "sample-idx" marker; all others are dropped * (no DEBUG trace) to avoid flooding the log pipeline. - * - MIDDLE frames, TAI_LOG_MEDIA_SAMPLE_N == 0: + * - MIDDLE frames, AGENTIC_KIT_TAI_LOG_MEDIA_SAMPLE_N == 0: * every MIDDLE frame is logged at DEBUG (developer "flood" mode). * * Every media packet also carries an "order" field -- a per-direction @@ -29,14 +29,7 @@ #define TAG "pkt" -/* Sample 1-in-N media MIDDLE frames to INFO. All non-sampled MIDDLE - * frames are dropped (no log line). Set to 0 to disable sampling, in - * which case every MIDDLE frame logs at DEBUG instead. Override with - * cmake -DTAI_LOG_MEDIA_SAMPLE_N= - */ -#ifndef TAI_LOG_MEDIA_SAMPLE_N -#define TAI_LOG_MEDIA_SAMPLE_N 50 -#endif +/* AGENTIC_KIT_TAI_LOG_MEDIA_SAMPLE_N default & docs: include/tai_config_defaults.h. */ /* snprintf into buf at *pos; advances pos. Bails (returning 0) if there is * no room left for at least one char + NUL. Always keeps *pos strictly @@ -526,11 +519,11 @@ void tai_log_packet(uint8_t proto_ver, } if (stream_flag == TAI_STREAM_MIDDLE) { - if (TAI_LOG_MEDIA_SAMPLE_N > 0) { + if (AGENTIC_KIT_TAI_LOG_MEDIA_SAMPLE_N > 0) { /* Sample every N-th; drop the rest entirely. */ static uint32_t sample_counter = 0; uint32_t n = ++sample_counter; - if ((n % TAI_LOG_MEDIA_SAMPLE_N) != 0) + if ((n % AGENTIC_KIT_TAI_LOG_MEDIA_SAMPLE_N) != 0) return; /* dropped -- no log line */ sample_idx = n; /* keep at INFO, mark sampled */ } else { @@ -568,7 +561,7 @@ void tai_log_packet(uint8_t proto_ver, if (sample_idx) bput(buf, cap, &pos, ",\"sample-every\":%u,\"sample-idx\":%u", - (unsigned)TAI_LOG_MEDIA_SAMPLE_N, (unsigned)sample_idx); + (unsigned)AGENTIC_KIT_TAI_LOG_MEDIA_SAMPLE_N, (unsigned)sample_idx); if (!is_send) bput(buf, cap, &pos, ",\"payload-len\":%zu", payload_len); diff --git a/modules/rtc-tcp-client/src/tai_protocol.c b/modules/rtc-tcp-client/src/tai_protocol.c index 31ec8ef..e0c0f64 100644 --- a/modules/rtc-tcp-client/src/tai_protocol.c +++ b/modules/rtc-tcp-client/src/tai_protocol.c @@ -103,7 +103,7 @@ static int gen_id(tai_ctx_t *ctx, const char *prefix, * client-id (12) : string — derived_client_id * security-suit (10) : bytes — [sign_level(1)][encrypt_random(32)] * max-fragment-len (15) : uint32 — largest transport fragment payload the - * client uses/accepts (TAI_MAX_FRAGMENT_PAYLOAD) + * client uses/accepts (AGENTIC_KIT_TAI_MAX_FRAGMENT_PAYLOAD) * ping-interval (20) : uint32 — keepalive interval (ms) * * Sent unencrypted (sig_len=0 handled by caller). @@ -125,7 +125,7 @@ int tai_proto_build_client_hello(tai_ctx_t *ctx, attrs[na++] = tai_attr_strv(TAI_ATTR_CLIENT_ID, ctx->client_id); attrs[na++] = tai_attr_bytesv(TAI_ATTR_SECURITY_SUIT, security_suit, 33); attrs[na++] = tai_attr_u32v(TAI_ATTR_MAX_FRAGMENT_LEN, s_maxfrag, - TAI_MAX_FRAGMENT_PAYLOAD); + AGENTIC_KIT_TAI_MAX_FRAGMENT_PAYLOAD); attrs[na++] = tai_attr_u32v(TAI_ATTR_PING_INTERVAL, s_ping, ctx->ping_interval_ms); diff --git a/modules/rtc-tcp-client/test/tai_pal_loopback.c b/modules/rtc-tcp-client/test/tai_pal_loopback.c index c9a2dc1..cec298a 100644 --- a/modules/rtc-tcp-client/test/tai_pal_loopback.c +++ b/modules/rtc-tcp-client/test/tai_pal_loopback.c @@ -272,10 +272,10 @@ static void lb_hs_feed(const uint8_t *buf, size_t len) if (tai_frame_decode(g_hs.acc, need, 0, &frag, &fseq, &pl, &pl_len) != TAI_OK) { g_hs.done = 1; return; } - uint8_t pkt_type; tai_attr_t attrs[TAI_MAX_ATTRS]; int na = 0; + uint8_t pkt_type; tai_attr_t attrs[AGENTIC_KIT_TAI_MAX_ATTRS]; int na = 0; const uint8_t *payload; size_t payload_len; if (tai_packet_decode(TAI_VER_21, pl, pl_len, &pkt_type, - attrs, TAI_MAX_ATTRS, &na, &payload, &payload_len) != TAI_OK + attrs, AGENTIC_KIT_TAI_MAX_ATTRS, &na, &payload, &payload_len) != TAI_OK || pkt_type != TAI_PKT_CLIENT_HELLO) { g_hs.done = 1; return; } diff --git a/modules/rtc-tcp-client/test/test_core.c b/modules/rtc-tcp-client/test/test_core.c index 99abaee..ed16ebe 100644 --- a/modules/rtc-tcp-client/test/test_core.c +++ b/modules/rtc-tcp-client/test/test_core.c @@ -418,9 +418,9 @@ static void test_proto_client_hello(void) /* Remaining 32 bytes should be our fixed encrypt_random */ uint8_t expected_random[32]; memset(expected_random, 0xBB, 32); CHECK(memcmp(ss->value + 1, expected_random, 32) == 0); - /* Attr: max-fragment-len = TAI_MAX_FRAGMENT_PAYLOAD */ + /* Attr: max-fragment-len = AGENTIC_KIT_TAI_MAX_FRAGMENT_PAYLOAD */ const tai_attr_t *mf = tai_attr_find(attrs, nattrs, TAI_ATTR_MAX_FRAGMENT_LEN); - CHECK(mf && tai_attr_u32(mf) == TAI_MAX_FRAGMENT_PAYLOAD); + CHECK(mf && tai_attr_u32(mf) == AGENTIC_KIT_TAI_MAX_FRAGMENT_PAYLOAD); PASS(); TEST("build_session_new"); diff --git a/modules/rtc-tcp-client/test/test_integration.c b/modules/rtc-tcp-client/test/test_integration.c index 93373ab..7368b5e 100644 --- a/modules/rtc-tcp-client/test/test_integration.c +++ b/modules/rtc-tcp-client/test/test_integration.c @@ -238,7 +238,7 @@ static void on_disconnect(tai_ctx_t *ctx, const tai_disconnect_msg_t *msg, void typedef struct { uint8_t pkt_type; uint8_t frag; - tai_attr_t attrs[TAI_MAX_ATTRS]; + tai_attr_t attrs[AGENTIC_KIT_TAI_MAX_ATTRS]; int attr_count; const uint8_t *payload; size_t payload_len; @@ -281,7 +281,7 @@ static int decode_captured(uint8_t *tx, size_t tx_len, out[n].app_len = payload_len; rc = tai_packet_decode(TAI_VER_21, payload, payload_len, &out[n].pkt_type, - out[n].attrs, TAI_MAX_ATTRS, + out[n].attrs, AGENTIC_KIT_TAI_MAX_ATTRS, &out[n].attr_count, &out[n].payload, &out[n].payload_len); if (rc != TAI_OK) { @@ -1308,7 +1308,7 @@ static void test_media_audio_remainder(void) * Test: a whole (FRAG_NONE) audio packet is split into frame_size chunks plus a * trailing remainder. fs=1000 over a 3500-byte body -> 3 full frames + a 500 * remainder. The packet stays within one transport frame (a downstream packet - * cannot exceed TAI_MAX_FRAGMENT_PAYLOAD, which bounds rx_buf). + * cannot exceed AGENTIC_KIT_TAI_MAX_FRAGMENT_PAYLOAD, which bounds rx_buf). * ========================================================================= */ static void test_media_audio_large_frame(void) { @@ -1322,13 +1322,13 @@ static void test_media_audio_large_frame(void) static uint8_t body[3500]; for (size_t i = 0; i < sizeof(body); i++) body[i] = (uint8_t)((i * 7) & 0xFF); - uint8_t app[TAI_MAX_FRAGMENT_PAYLOAD]; + uint8_t app[AGENTIC_KIT_TAI_MAX_FRAGMENT_PAYLOAD]; int app_len = build_audio_app(ctx, TAI_STREAM_START, "111 1 16 16000 0 16000 20 1000", /* fs=1000 */ body, sizeof(body), app, sizeof(app)); CHECK(app_len > 0); - uint8_t frame[TAI_MAX_FRAGMENT_PAYLOAD + 64]; + uint8_t frame[AGENTIC_KIT_TAI_MAX_FRAGMENT_PAYLOAD + 64]; int flen = tai_frame_encode(TAI_FRAG_NONE, fseq++, app, (size_t)app_len, ctx->sign_key, 32, ctx->pal, frame, sizeof(frame)); CHECK(flen > 0); @@ -1482,12 +1482,12 @@ static void test_media_reconnect_midstream(void) /* ========================================================================= * Test: scatter-gather fragmented uplink (§6). An audio chunk larger than - * TAI_MAX_FRAGMENT_PAYLOAD is streamed via send_app_sg, which fragments the + * AGENTIC_KIT_TAI_MAX_FRAGMENT_PAYLOAD is streamed via send_app_sg, which fragments the * logical hdr||payload concat. Each frame is signed INDEPENDENTLY by the * segmented HMAC, so we verify every frame's HMAC, the FIRST/MIDDLE/LAST flags, * and that the reassembled application packet carries the exact pcm bytes * (proving zero-copy payload integrity across the fragment boundaries). - * Sized to exactly 3 fragments regardless of the TAI_MAX_FRAGMENT_PAYLOAD knob. + * Sized to exactly 3 fragments regardless of the AGENTIC_KIT_TAI_MAX_FRAGMENT_PAYLOAD knob. * ========================================================================= */ static void test_sg_audio_fragmented_uplink(void) { @@ -1497,7 +1497,7 @@ static void test_sg_audio_fragmented_uplink(void) CHECK(ctx != NULL); CHECK_EQ_INT(tai_connect(ctx), TAI_OK); - static uint8_t tx[3 * TAI_MAX_FRAGMENT_PAYLOAD + 512]; + static uint8_t tx[3 * AGENTIC_KIT_TAI_MAX_FRAGMENT_PAYLOAD + 512]; (void)tai_loopback_pop_sent(tx, sizeof(tx)); /* discard handshake */ CHECK_EQ_INT(tai_send_audio_start(ctx, TAI_AUDIO_OPUS, 1, 16, 16000), TAI_OK); @@ -1505,7 +1505,7 @@ static void test_sg_audio_fragmented_uplink(void) (void)tai_loopback_pop_sent(tx, sizeof(tx)); /* discard EventStart */ /* 2 full fragments + a partial -> exactly FIRST/MIDDLE/LAST. */ - static uint8_t pcm[2 * TAI_MAX_FRAGMENT_PAYLOAD + 1000]; + static uint8_t pcm[2 * AGENTIC_KIT_TAI_MAX_FRAGMENT_PAYLOAD + 1000]; for (size_t i = 0; i < sizeof(pcm); i++) pcm[i] = (uint8_t)((i * 5 + 3) & 0xFF); CHECK_EQ_INT(tai_send_audio_chunk(ctx, pcm, sizeof(pcm)), TAI_OK); sleep_ms(10); @@ -1513,7 +1513,7 @@ static void test_sg_audio_fragmented_uplink(void) CHECK(txn > 0); /* Walk frames: verify HMAC + flags, reassemble the application packet. */ - static uint8_t app[3 * TAI_MAX_FRAGMENT_PAYLOAD + 512]; + static uint8_t app[3 * AGENTIC_KIT_TAI_MAX_FRAGMENT_PAYLOAD + 512]; size_t app_len = 0, off = 0; int nframes = 0; uint8_t flags[8] = {0}; @@ -1533,10 +1533,10 @@ static void test_sg_audio_fragmented_uplink(void) CHECK_EQ_INT(flags[1], TAI_FRAG_MIDDLE); CHECK_EQ_INT(flags[2], TAI_FRAG_LAST); - uint8_t pkt_type; tai_attr_t attrs[TAI_MAX_ATTRS]; int na = 0; + uint8_t pkt_type; tai_attr_t attrs[AGENTIC_KIT_TAI_MAX_ATTRS]; int na = 0; const uint8_t *payload; size_t payload_len; CHECK(tai_packet_decode(TAI_VER_21, app, app_len, &pkt_type, attrs, - TAI_MAX_ATTRS, &na, &payload, &payload_len) == TAI_OK); + AGENTIC_KIT_TAI_MAX_ATTRS, &na, &payload, &payload_len) == TAI_OK); CHECK_EQ_INT(pkt_type, TAI_PKT_AUDIO); CHECK_EQ_INT(payload_len, 8 + sizeof(pcm)); /* 8-byte media hdr + pcm */ CHECK(memcmp(payload + 8, pcm, sizeof(pcm)) == 0); @@ -1611,11 +1611,11 @@ static void test_sg_send_failure(void) /* Header write (call 0) ok, payload write (call 1) fails: mid-frame, bytes * already committed -> TAI_ERR_NET, but the SDK must NOT tear down. - * pcm must push the whole frame over TAI_FRAME_COALESCE_LIMIT so it + * pcm must push the whole frame over AGENTIC_KIT_TAI_FRAME_COALESCE_LIMIT so it * takes the 2-3-write scatter path — a coalesced small frame is a * single write and has no mid-frame boundary to fail on. */ tai_loopback_fail_send_after(1); - uint8_t pcm[TAI_FRAME_COALESCE_LIMIT]; memset(pcm, 0x42, sizeof(pcm)); + uint8_t pcm[AGENTIC_KIT_TAI_FRAME_COALESCE_LIMIT]; memset(pcm, 0x42, sizeof(pcm)); CHECK_EQ_INT(tai_send_audio_chunk(ctx, pcm, sizeof(pcm)), TAI_ERR_NET); tai_loopback_fail_send_after(-1); @@ -1710,24 +1710,24 @@ static void test_sg_image_fragmented_uplink(void) CHECK(ctx != NULL); CHECK_EQ_INT(tai_connect(ctx), TAI_OK); - static uint8_t tx[3 * TAI_MAX_FRAGMENT_PAYLOAD + 512]; + static uint8_t tx[3 * AGENTIC_KIT_TAI_MAX_FRAGMENT_PAYLOAD + 512]; (void)tai_loopback_pop_sent(tx, sizeof(tx)); - static uint8_t img[2 * TAI_MAX_FRAGMENT_PAYLOAD + 1000]; /* -> 3 fragments */ + static uint8_t img[2 * AGENTIC_KIT_TAI_MAX_FRAGMENT_PAYLOAD + 1000]; /* -> 3 fragments */ for (size_t i = 0; i < sizeof(img); i++) img[i] = (uint8_t)((i * 11 + 7) & 0xFF); CHECK_EQ_INT(tai_send_image(ctx, img, sizeof(img), TAI_IMG_JPEG, 640, 480), TAI_OK); sleep_ms(10); size_t txn = tai_loopback_pop_sent(tx, sizeof(tx)); CHECK(txn > 0); - static uint8_t app[3 * TAI_MAX_FRAGMENT_PAYLOAD + 512]; + static uint8_t app[3 * AGENTIC_KIT_TAI_MAX_FRAGMENT_PAYLOAD + 512]; size_t app_len = 0; int nf = sg_reassemble_fragged(ctx, tx, txn, app, sizeof(app), &app_len); CHECK_EQ_INT(nf, 3); /* (img + hdr) over MAX = 3 */ - uint8_t pt; tai_attr_t attrs[TAI_MAX_ATTRS]; int na = 0; + uint8_t pt; tai_attr_t attrs[AGENTIC_KIT_TAI_MAX_ATTRS]; int na = 0; const uint8_t *payload; size_t payload_len; - CHECK(tai_packet_decode(TAI_VER_21, app, app_len, &pt, attrs, TAI_MAX_ATTRS, + CHECK(tai_packet_decode(TAI_VER_21, app, app_len, &pt, attrs, AGENTIC_KIT_TAI_MAX_ATTRS, &na, &payload, &payload_len) == TAI_OK); CHECK_EQ_INT(pt, TAI_PKT_IMAGE); CHECK(tai_attr_find(attrs, na, TAI_ATTR_IMAGE_PARAMS) != NULL); @@ -1743,7 +1743,7 @@ static void test_sg_image_fragmented_uplink(void) * findings #2/#5). The text header is 5 bytes (pkt byte + [id:2][flags:1] * [varint seq=0:1]). A body sized so hdr+body == MAX+1 must split into EXACTLY * two frames (FIRST + a 1-byte LAST); a body sized so hdr+body == MAX must NOT - * fragment at all. Pins the chunk/flag boundary math at TAI_MAX_FRAGMENT_PAYLOAD. + * fragment at all. Pins the chunk/flag boundary math at AGENTIC_KIT_TAI_MAX_FRAGMENT_PAYLOAD. * ========================================================================= */ static void test_sg_text_fragmented_uplink(void) { @@ -1753,24 +1753,24 @@ static void test_sg_text_fragmented_uplink(void) CHECK(ctx != NULL); CHECK_EQ_INT(tai_connect(ctx), TAI_OK); - static uint8_t tx[TAI_MAX_FRAGMENT_PAYLOAD + 512]; + static uint8_t tx[AGENTIC_KIT_TAI_MAX_FRAGMENT_PAYLOAD + 512]; (void)tai_loopback_pop_sent(tx, sizeof(tx)); /* body = MAX-4 -> total (5 + MAX-4) = MAX+1 = one past the limit -> 2 frames. */ - static char txt[TAI_MAX_FRAGMENT_PAYLOAD - 4]; + static char txt[AGENTIC_KIT_TAI_MAX_FRAGMENT_PAYLOAD - 4]; for (size_t i = 0; i < sizeof(txt); i++) txt[i] = (char)('a' + (i % 26)); CHECK_EQ_INT(tai_send_text(ctx, txt, sizeof(txt)), TAI_OK); sleep_ms(10); size_t txn = tai_loopback_pop_sent(tx, sizeof(tx)); - static uint8_t app[TAI_MAX_FRAGMENT_PAYLOAD + 512]; + static uint8_t app[AGENTIC_KIT_TAI_MAX_FRAGMENT_PAYLOAD + 512]; size_t app_len = 0; int nf = sg_reassemble_fragged(ctx, tx, txn, app, sizeof(app), &app_len); CHECK_EQ_INT(nf, 2); /* FIRST + 1-byte LAST */ - uint8_t pt; tai_attr_t attrs[TAI_MAX_ATTRS]; int na = 0; + uint8_t pt; tai_attr_t attrs[AGENTIC_KIT_TAI_MAX_ATTRS]; int na = 0; const uint8_t *payload; size_t payload_len; - CHECK(tai_packet_decode(TAI_VER_21, app, app_len, &pt, attrs, TAI_MAX_ATTRS, + CHECK(tai_packet_decode(TAI_VER_21, app, app_len, &pt, attrs, AGENTIC_KIT_TAI_MAX_ATTRS, &na, &payload, &payload_len) == TAI_OK); CHECK_EQ_INT(pt, TAI_PKT_TEXT); CHECK(payload_len >= sizeof(txt)); @@ -1779,7 +1779,7 @@ static void test_sg_text_fragmented_uplink(void) /* Boundary: body = MAX-5 -> total == MAX -> single frame, no fragmentation * (sg_reassemble_fragged finds no FRAG_FIRST). */ (void)tai_loopback_pop_sent(tx, sizeof(tx)); - static char txt2[TAI_MAX_FRAGMENT_PAYLOAD - 5]; + static char txt2[AGENTIC_KIT_TAI_MAX_FRAGMENT_PAYLOAD - 5]; for (size_t i = 0; i < sizeof(txt2); i++) txt2[i] = (char)('A' + (i % 26)); CHECK_EQ_INT(tai_send_text(ctx, txt2, sizeof(txt2)), TAI_OK); sleep_ms(10); @@ -1791,7 +1791,7 @@ static void test_sg_text_fragmented_uplink(void) } /* ========================================================================= - * Test: small-frame coalesce boundary (TAI_FRAME_COALESCE_LIMIT). One text + * Test: small-frame coalesce boundary (AGENTIC_KIT_TAI_FRAME_COALESCE_LIMIT). One text * send emits 4 packets: EventStart / PayloadsEnd / EventEnd are CONTROL frames * assembled in tx_ctrl_buf — so on the coalesced path the payload and the * scratch buffer are the SAME memory (the aliasing hazard) — while the Text @@ -1812,8 +1812,8 @@ static void test_sg_coalesce_boundary(void) (void)tai_loopback_pop_sent(tx, sizeof(tx)); /* discard handshake */ /* text hdr = pkt byte + [id:2][flags:1][varint seq:1] = 5 bytes. */ - static char under[TAI_FRAME_COALESCE_LIMIT - 5 - 5 - 32 - 1]; - static char over [TAI_FRAME_COALESCE_LIMIT - 5 - 5 - 32]; + static char under[AGENTIC_KIT_TAI_FRAME_COALESCE_LIMIT - 5 - 5 - 32 - 1]; + static char over [AGENTIC_KIT_TAI_FRAME_COALESCE_LIMIT - 5 - 5 - 32]; for (size_t i = 0; i < sizeof(under); i++) under[i] = (char)('a' + (i % 26)); for (size_t i = 0; i < sizeof(over); i++) over[i] = (char)('A' + (i % 26)); @@ -1836,9 +1836,9 @@ static void test_sg_coalesce_boundary(void) uint8_t frag, pt; uint16_t seq; const uint8_t *pl; size_t pll; CHECK(tai_frame_decode(tx + off, flen, 32, &frag, &seq, &pl, &pll) == TAI_OK); CHECK_EQ_INT(frag, TAI_FRAG_NONE); /* never fragments here */ - tai_attr_t attrs[TAI_MAX_ATTRS]; int na = 0; + tai_attr_t attrs[AGENTIC_KIT_TAI_MAX_ATTRS]; int na = 0; const uint8_t *payload; size_t payload_len; - CHECK(tai_packet_decode(TAI_VER_21, pl, pll, &pt, attrs, TAI_MAX_ATTRS, + CHECK(tai_packet_decode(TAI_VER_21, pl, pll, &pt, attrs, AGENTIC_KIT_TAI_MAX_ATTRS, &na, &payload, &payload_len) == TAI_OK); if (pt == TAI_PKT_TEXT) { ntext++; @@ -1980,7 +1980,7 @@ static void test_confirmed_connect(void) * out a whole ping interval for the worker's blocking recv to expire. We raise * the loopback recv cap so it honours the full requested timeout like a real * PAL, set a long ping interval, then assert disconnect finishes well within it - * (bounded by the SDK worker poll cap, TAI_WORKER_POLL_CAP_MS). A clean + * (bounded by the SDK worker poll cap, AGENTIC_KIT_TAI_WORKER_POLL_CAP_MS). A clean * shutdown must also fire NO on_disconnect callback. * ========================================================================= */ static void test_disconnect_latency(void) @@ -1992,9 +1992,9 @@ static void test_disconnect_latency(void) CHECK(ctx != NULL); /* Ping interval set well above the worker poll cap: an UN-capped worker would * block ~ping_interval in recv, while a capped one wakes within the cap. - * Derive both from TAI_WORKER_POLL_CAP_MS so this stays correct if the cap is + * Derive both from AGENTIC_KIT_TAI_WORKER_POLL_CAP_MS so this stays correct if the cap is * retuned. */ - const uint32_t cap = TAI_WORKER_POLL_CAP_MS; + const uint32_t cap = AGENTIC_KIT_TAI_WORKER_POLL_CAP_MS; ctx->ping_interval_ms = cap * 5; /* Make the loopback honour the full recv timeout, like a production PAL, so * the SDK worker poll cap (not the loopback's 50 ms default) bounds it. */ diff --git a/modules/tuya-ble/include/tuya_ble_prov.h b/modules/tuya-ble/include/tuya_ble_prov.h index f02a558..a614187 100644 --- a/modules/tuya-ble/include/tuya_ble_prov.h +++ b/modules/tuya-ble/include/tuya_ble_prov.h @@ -9,6 +9,16 @@ extern "C" { #include #include +/* Protocol geometry and port-API sizing, not build knobs -- tuya-ble has no + * compile-time knobs today. The lengths and encryption-mode bytes below are + * fixed by the Tuya BLE pairing / big-data protocol. TUYA_BLE_RX_BUF_SIZE / + * TUYA_BLE_TX_BUF_SIZE / TUYA_BLE_TX_QUEUE_DEPTH additionally size the public + * tuya_ble_prov_state_t that ports embed and size their own buffers against, + * making them part of the port API surface: changing one changes a struct + * layout ports must recompile and re-size against, not something a build-time + * override can do. When the first real tuya-ble knob appears it gets its own + * include/tuya_ble_config_defaults.h named AGENTIC_KIT_TUYA_BLE_*, anchored to + * the common/log.h override pickup like every other defaults file. */ #define TUYA_BLE_SSID_MAX_LEN 64 #define TUYA_BLE_PASSWORD_MAX_LEN 64 #define TUYA_BLE_TOKEN_MAX_LEN 16 diff --git a/modules/tuya-ble/src/tuya_ble_internal.h b/modules/tuya-ble/src/tuya_ble_internal.h index 819ab9e..9cec3f3 100644 --- a/modules/tuya-ble/src/tuya_ble_internal.h +++ b/modules/tuya-ble/src/tuya_ble_internal.h @@ -5,6 +5,12 @@ #include +/* tuya-ble has no build-time knobs today -- why the constants in + * tuya_ble_prov.h are not knobs is documented there. When the first one + * appears it lands in include/tuya_ble_config_defaults.h as + * AGENTIC_KIT_TUYA_BLE_*; that file must include common/log.h first, since + * log.h is where integrator overrides are picked up. */ + /* Shared SDK-internal binding of the HAL log macros onto the PAL log facade: * every module diagnostic carries the "[ble] " prefix. */ #undef TUYA_BLE_HAL_LOGI diff --git a/pal/pal_config_defaults.h b/pal/pal_config_defaults.h new file mode 100644 index 0000000..df752ce --- /dev/null +++ b/pal/pal_config_defaults.h @@ -0,0 +1,41 @@ +/* + * pal_config_defaults.h -- PAL build-time knobs: the FreeRTOS + lwIP worker + * task sizing (pal/pal_freertos.c). The POSIX PAL (pal_posix.c) has no + * knobs -- pthreads take the platform default stack. + * + * pal_freertos.c includes this file directly. It pulls common/log.h FIRST: + * that header is where integrator overrides (agentic_kit_config.h on the + * include path, -D, AGENTIC_KIT_USER_CONFIG) are applied -- so overrides + * win over every default below. Not a public API header. + */ + +#ifndef AGENTIC_KIT_PAL_CONFIG_DEFAULTS_H +#define AGENTIC_KIT_PAL_CONFIG_DEFAULTS_H + +#include "log.h" + +/* ========================================================================= + * PAL: FreeRTOS + lwIP worker task (pal/pal_freertos.c) + * ========================================================================= */ + +/* Worker task stack, in StackType_t WORDS (not bytes) -- passed straight to + * xTaskCreate as usStackDepth. The 6144 default is ~24 KB on a 32-bit port; + * do not "correct" it to 1536 to get 6 KB, the TLS handshake runs on this + * task. */ +#ifndef AGENTIC_KIT_PAL_FR_TASK_STACK_WORDS +#define AGENTIC_KIT_PAL_FR_TASK_STACK_WORDS 6144 +#endif + +/* Worker task priority. The default expands at its use site in + * pal_freertos.c, where the FreeRTOS headers are in scope -- keep + * tskIDLE_PRIORITY unquoted here. */ +#ifndef AGENTIC_KIT_PAL_FR_TASK_PRIORITY +#define AGENTIC_KIT_PAL_FR_TASK_PRIORITY (tskIDLE_PRIORITY + 5) +#endif + +/* Worker task name string (debug only). */ +#ifndef AGENTIC_KIT_PAL_FR_TASK_NAME +#define AGENTIC_KIT_PAL_FR_TASK_NAME "tai_worker" +#endif + +#endif /* AGENTIC_KIT_PAL_CONFIG_DEFAULTS_H */ diff --git a/pal/pal_freertos.c b/pal/pal_freertos.c index eda76b1..afc456f 100644 --- a/pal/pal_freertos.c +++ b/pal/pal_freertos.c @@ -34,15 +34,10 @@ * LWIP_PROVIDE_ERRNO 1 (per-task errno; otherwise a libc errno is fine) * * -------------------------------------------------------------------------- - * Build-time tunables - * -------------------------------------------------------------------------- - * PAL_FR_TASK_STACK_WORDS worker task stack, in StackType_t WORDS (not - * bytes) -- passed straight to xTaskCreate as - * usStackDepth. The 6144 default is ~24 KB on a - * 32-bit port; do not "correct" it to 1536 to get - * 6 KB, the TLS handshake runs on this task. - * PAL_FR_TASK_PRIORITY worker task priority - * PAL_FR_TASK_NAME task name string (debug only) + * Build-time tunables (AGENTIC_KIT_PAL_FR_TASK_STACK_WORDS / + * _PRIORITY / _NAME) + * Defaults & docs: pal_config_defaults.h (overrides are picked up via + * log.h; see that header's banner). * * Usage: * cfg.pal = tai_pal_freertos(); @@ -63,16 +58,10 @@ #include "pal.h" #include "log.h" -/* Worker task tunables — override via -D at build time if needed. */ -#ifndef PAL_FR_TASK_STACK_WORDS -#define PAL_FR_TASK_STACK_WORDS 6144 -#endif -#ifndef PAL_FR_TASK_PRIORITY -#define PAL_FR_TASK_PRIORITY (tskIDLE_PRIORITY + 5) -#endif -#ifndef PAL_FR_TASK_NAME -#define PAL_FR_TASK_NAME "tai_worker" -#endif +/* Worker task knob defaults (AGENTIC_KIT_PAL_FR_TASK_*) live in + * pal_config_defaults.h, included below; it includes log.h first, so + * integrator overrides still win. */ +#include "pal_config_defaults.h" /* lwIP doesn't have signals; MSG_NOSIGNAL is a no-op. */ #ifndef MSG_NOSIGNAL @@ -360,10 +349,10 @@ static int pal_thread_create(void **handle, void *(*func)(void *), void *arg) if (!t->done) { pal_free(t); return -1; } BaseType_t rc = xTaskCreate(thread_shim, - PAL_FR_TASK_NAME, - PAL_FR_TASK_STACK_WORDS, + AGENTIC_KIT_PAL_FR_TASK_NAME, + AGENTIC_KIT_PAL_FR_TASK_STACK_WORDS, t, - PAL_FR_TASK_PRIORITY, + AGENTIC_KIT_PAL_FR_TASK_PRIORITY, &t->task); if (rc != pdPASS) { vSemaphoreDelete(t->done); diff --git a/tools/bump_version b/tools/bump_version index efc7d5b..e2be40a 100755 --- a/tools/bump_version +++ b/tools/bump_version @@ -16,7 +16,7 @@ # bump_version.sh release strip -dev (digits unchanged) # # An optional second argument overrides the header file path (used by the -# self-test; defaults to this repo's iot_config_defaults.h). +# self-test; defaults to this repo's iot_client_config_defaults.h). # # The script only edits the header. Committing, tagging, pushing and the # GitHub release remain manual - the script prints a checklist instead. @@ -24,7 +24,7 @@ set -euo pipefail REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)" -DEFAULT_HEADER="$REPO_ROOT/modules/iot-client/src/iot_config_defaults.h" +DEFAULT_HEADER="$REPO_ROOT/modules/iot-client/include/iot_client_config_defaults.h" usage() { sed -n '2,25p' "$0" | sed 's/^# \{0,1\}//'