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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Fixed

- iot-client — route BLE and QR/MQTT Self activation and later TLS requests with the opaque registration key and DNS-provided CA; document credential/key persistence and preserve legacy records without a key (#43).
- tuya-ble — accept incoming Trsmitr versions >= 2 for app Pairing compatibility while keeping TX at version 4 (#39).
- Docs — the pair-by-ble callback sample no longer claims credentials are never logged; the
demo's DEBUG protocol log prints the credential JSON on purpose, for device bring-up.
Expand Down
32 changes: 29 additions & 3 deletions docs-site/docs/reference/iot-client.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ IoT Client 模块(CMake 目标 `tuya_iot_client`,产物 `libtuya_iot_client.
| `local_key` | `char[32]` | 本地加密密钥 |
| `region` | `iot_region_t` | 数据中心区域 |
| `env` | `iot_env_t` | 环境 |
| `registration_key` | `char[5]` | App 原始注册 key,1~4 个可打印字节,末尾及剩余空间补零;全空时沿用旧 `env` 路由。须随激活凭据持久化、重启恢复 |
| `mqtt_disable_tls` | `bool` | `false`(默认)使用 MQTTS,`true` 使用明文 MQTT |
| `mqtt_disable_auto_connect` | `bool` | `false`(默认)初始化后自动连接 MQTT;`true` 需手动调用 [`iot_client_connect()`](#iot_client_connect) |
| `skip_version_report` | `bool` | `false`(默认)初始化时上报 SDK meta 和固件版本;`true` 跳过这两次上报(仅在云端已有当前版本时设置) |
Expand Down Expand Up @@ -178,7 +179,7 @@ IoT Client 模块(CMake 目标 `tuya_iot_client`,产物 `libtuya_iot_client.
| `feature` | `const char *` | Feature 信息(可为 NULL) |
| `skill_param` | `const char *` | Skill 参数(可为 NULL) |
| `timeout_ms` | `int` | 激活超时时间(毫秒) |
| `env` | `iot_env_t` | 环境:`PROD`(默认)或 `PRE` |
| `env` | `iot_env_t` | 激活消息监听的引导环境枚举(默认 `PROD`);收到 App 注册 key 后,Self 地址由 DNS 决定,不改写此枚举 |
| `mqtt_disable_tls` | `bool` | TLS 开关 |
| `mqtt_disable_auto_connect` | `bool` | `false`(默认)激活后自动连接 MQTT;`true` 需手动调用 [`iot_client_connect()`](#iot_client_connect) |
| `skip_version_report` | `bool` | `false`(默认)激活后上报 SDK meta 和固件版本;`true` 跳过这两次上报(仅在云端已有当前版本时设置) |
Expand All @@ -202,6 +203,7 @@ IoT Client 模块(CMake 目标 `tuya_iot_client`,产物 `libtuya_iot_client.
| `local_key` | `char[32]` | 本地加密密钥 |
| `region` | `iot_region_t` | 服务器区域 |
| `env` | `iot_env_t` | 环境 |
| `registration_key` | `char[5]` | App 原始注册 key;须随激活凭据保存,重启时恢复到 `iot_client_config_t` |

## API 函数 {#api-函数}

Expand Down Expand Up @@ -237,6 +239,9 @@ iot_client_t *iot_client_init_on_boarding(const iot_on_boarding_config_t *config
```

阻塞等待 App 扫码激活。内部通过 MQTT 监听激活事件,激活成功后返回包含 `devid`、`secret_key`、`local_key` 的客户端实例。
与 TuyaOpen 一致,MQTT 激活消息的 `data.env` 原样作为注册 key 保存,缺省为 `pro`,接受 1~4 个可打印字节(如 `pr_0`、`da_0`、`pro`、`x_ab`)。设备不把它映射为环境枚举。用该 key 查询 IoT DNS 的 `httpsSelfUrl`、`mqttsSelfUrl` 及 CA,激活和后续连接均走 DNS 返回的 Self 地址;不采用消息中的 `httpsUrl`。`client->env` 保持 `config.env`。

两种 App 配网入口均需保持 `mqtt_disable_tls=false`,并配置可信的引导 CA 或证书包。DNS 无有效 Self 地址或 CA 时返回失败,不回退线上。

**返回值:** 成功返回 `iot_client_t *`;超时或失败返回 `NULL`。

Expand All @@ -250,14 +255,35 @@ iot_client_t *iot_client_init_on_boarding_with_token(
const char *token);
```

使用预知的激活 Token 直接发起激活请求,跳过 MQTT 等待。Region 由 token 前两个字符自动推导。
使用预知的激活 Token 直接发起激活请求,跳过 MQTT 等待。Region 由 token 前两个字符自动推导。涂鸦 App 的 BLE 配网 Token 固定为 `[区域码:2][激活 token:8][secret:4]`,例如 `AYH73H8u7Apr_0`。末尾 4 字节 `secret` 是不透明值,设备原样作为 IoT DNS 请求的 `env` 参数,用于获取该环境对应的 Self HTTPS/MQTT 地址和 CA;设备不把它映射成 `pre`、`pro` 或 `prod`,也不根据它设置 `client->env`。`config.env` 只保留为客户端环境枚举,不决定这条 App token 路由。QR/MQTT 激活也使用原始注册 key,但其 `data.env` 可以是三字节的 `pro`,不改变 BLE 的固定 14 字节格式。

**参数:**
- `config` — 配网配置
- `token` — 激活 Token(格式:`{region}{token}{secret}`,如 `AYH73H8u7Ap4pX`)
- `token` — App BLE 配网 Token,格式为 `[区域码:2][激活 token:8][secret:4]`,例如 `AYH73H8u7Apr_0`

**返回值:** 成功返回 `iot_client_t *`;失败返回 `NULL`。

#### 持久化与重启恢复 {#registration-key-persistence}

两种激活入口都返回 `client->registration_key`。应用须把 `devid`、`secret_key`、`local_key`、`region`、`env` 和 `registration_key` 一起安全保存;仅保存三个设备凭据会丢失 App 选择的 DNS 路由。SDK 不代管 NVS 或文件存储。

```c
/* 激活成功后,构造应用要保存的字段;不要直接序列化含指针的配置结构。 */
iot_client_config_t restored = {0};
snprintf(restored.devid, sizeof(restored.devid), "%s", client->devid);
snprintf(restored.secret_key, sizeof(restored.secret_key), "%s", client->secret_key);
snprintf(restored.local_key, sizeof(restored.local_key), "%s", client->local_key);
restored.region = client->region;
restored.env = client->env;
memcpy(restored.registration_key, client->registration_key,
sizeof(restored.registration_key));
/* 用应用自己的持久化接口保存以上字段,并在下次启动时读取。 */
restored.cacert = bootstrap_ca_pem; /* 每次启动配置可信 CA,或 cert_bundle_attach。 */
iot_client_t *reconnected = iot_client_init(&restored);
```

无需持久化 DNS 地址或 Self CA,SDK 在连接时重新查询。旧存储记录若没有 `registration_key`,保持该字段全零并恢复原 `env`,无需清除绑定或重新配网;不要自动补成 `pro`。非空但损坏的 key 应报错,不能清空后静默回退线上。新激活后即使暂时断网,也应保存已返回客户端的绑定信息,再调用 `iot_client_connect()` 重试。

---

### `iot_client_reset` {#iot_client_reset}
Expand Down
2 changes: 1 addition & 1 deletion docs-site/docs/tutorials/scan-by-device.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ iot_on_boarding_config_t cfg = {
| `product_key` | 产品 PID |
| `firmware_key` | 固件 Key(可为空) |
| `timeout_ms` | 激活超时时间(毫秒) |
| `env` | 环境:`PROD` / `PRE` |
| `env` | 客户端环境枚举。App BLE Token 尾部 4 字节 `secret` 原样作为 IoT DNS 的 `env` 参数;不映射为 `pre`/`pro`/`prod`,也不由此字段决定 App token 的路由 |

**返回值:** 成功返回 `iot_client_t *`,其中包含激活后的 `devid`、
`secret_key`、`local_key`,后续可直接用于初始化 `iot_client_init()`;失败返
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ Initialization configuration for an activated device.
| `local_key` | `char[32]` | Local encryption key |
| `region` | `iot_region_t` | Data-center region |
| `env` | `iot_env_t` | Environment |
| `registration_key` | `char[5]` | Raw App registration key: 1-4 printable bytes with zero-filled termination/padding; all zeros retain legacy `env` routing. Persist with credentials and restore on reboot |
| `mqtt_disable_tls` | `bool` | `false` (default) uses MQTTS; `true` uses plaintext MQTT |
| `mqtt_disable_auto_connect` | `bool` | `false` (default) connects to MQTT automatically after initialization; when `true`, you must call [`iot_client_connect()`](#iot_client_connect) manually |
| `skip_version_report` | `bool` | `false` (default) reports the SDK metadata and firmware version during initialization; `true` skips these two reports (set only when the cloud already has the current version) |
Expand Down Expand Up @@ -177,7 +178,7 @@ Configuration for device provisioning and Activation.
| `feature` | `const char *` | Feature information (can be NULL) |
| `skill_param` | `const char *` | Skill parameters (can be NULL) |
| `timeout_ms` | `int` | Activation timeout in milliseconds |
| `env` | `iot_env_t` | Environment: `PROD` (default) or `PRE` |
| `env` | `iot_env_t` | Bootstrap environment enum for listening to Activation messages (default `PROD`); after receiving the App key, DNS determines Self endpoints without rewriting this enum |
| `mqtt_disable_tls` | `bool` | TLS switch |
| `mqtt_disable_auto_connect` | `bool` | `false` (default) connects to MQTT automatically after Activation; when `true`, you must call [`iot_client_connect()`](#iot_client_connect) manually |
| `skip_version_report` | `bool` | `false` (default) reports the SDK metadata and firmware version after Activation; `true` skips these two reports (set only when the cloud already has the current version) |
Expand All @@ -201,6 +202,7 @@ The client instance returned by `iot_client_init()` or a provisioning API contai
| `local_key` | `char[32]` | Local encryption key |
| `region` | `iot_region_t` | Server region |
| `env` | `iot_env_t` | Environment |
| `registration_key` | `char[5]` | Raw App registration key; persist with Activation credentials and restore in `iot_client_config_t` on reboot |

## API Functions {#api-函数}

Expand Down Expand Up @@ -236,6 +238,9 @@ iot_client_t *iot_client_init_on_boarding(const iot_on_boarding_config_t *config
```

Blocks while waiting for App QR-code Activation. Internally, it listens for the Activation event over MQTT and, after successful Activation, returns a client instance containing `devid`, `secret_key`, and `local_key`.
As in TuyaOpen, the MQTT Activation message's `data.env` is saved unchanged as the registration key, defaults to `pro` when absent, and accepts 1-4 printable bytes (such as `pr_0`, `da_0`, `pro`, or `x_ab`). The device does not map it to an environment enum. It uses this key to query IoT DNS for `httpsSelfUrl`, `mqttsSelfUrl`, and CA; Activation and later connections use these Self endpoints, not the message's `httpsUrl`. `client->env` remains `config.env`.

Both App onboarding paths require `mqtt_disable_tls=false` and a trusted bootstrap CA or certificate bundle. Missing or invalid Self endpoints or CA cause failure, not fallback to production.

**Return value:** An `iot_client_t *` on success; `NULL` on timeout or failure.

Expand All @@ -249,14 +254,35 @@ iot_client_t *iot_client_init_on_boarding_with_token(
const char *token);
```

Starts Activation directly with a known Activation Token, skipping the MQTT wait. The Region is derived automatically from the token's first two characters.
Starts Activation directly with a known Activation Token, skipping the MQTT wait. The Region is derived from the first two characters. Tuya App BLE provisioning tokens have the fixed format `[region:2][activation token:8][secret:4]`, for example `AYH73H8u7Apr_0`. The trailing four-byte `secret` is opaque and is passed unchanged as the IoT DNS request's `env` parameter to obtain Self HTTPS/MQTT endpoints and CA for that environment. The device does not map it to `pre`, `pro`, or `prod`, nor use it to set `client->env`. `config.env` remains the client environment enum and does not determine this App-token route. QR/MQTT Activation also uses a raw registration key, but `data.env` may be the three-byte `pro`; this does not relax BLE's fixed 14-byte format.

**Parameters:**
- `config` - Provisioning configuration
- `token` - Activation Token (format: `{region}{token}{secret}`, for example `AYH73H8u7Ap4pX`)
- `token` - Tuya App BLE provisioning token in `[region:2][activation token:8][secret:4]` format, for example `AYH73H8u7Apr_0`

**Return value:** An `iot_client_t *` on success; `NULL` on failure.

#### Persistence and restart recovery {#registration-key-persistence}

Both Activation paths return `client->registration_key`. The application must securely persist `devid`, `secret_key`, `local_key`, `region`, `env`, and `registration_key` together. Saving only the three device credentials loses the App-selected DNS route. The SDK does not manage NVS or file storage.

```c
/* After Activation, prepare the fields to save; do not serialize a config with pointers. */
iot_client_config_t restored = {0};
snprintf(restored.devid, sizeof(restored.devid), "%s", client->devid);
snprintf(restored.secret_key, sizeof(restored.secret_key), "%s", client->secret_key);
snprintf(restored.local_key, sizeof(restored.local_key), "%s", client->local_key);
restored.region = client->region;
restored.env = client->env;
memcpy(restored.registration_key, client->registration_key,
sizeof(restored.registration_key));
/* Save these fields using application storage, then read them on the next boot. */
restored.cacert = bootstrap_ca_pem; /* Configure trusted CA or cert_bundle_attach each boot. */
iot_client_t *reconnected = iot_client_init(&restored);
```

DNS endpoints and Self CA need not be persisted; the SDK queries them again when connecting. For older records without a `registration_key`, keep that field all-zero and restore the original `env`; do not clear the binding, re-activate, or automatically fill in `pro`. A corrupt nonempty key must be treated as an error, not cleared to silently fall back to production. Even if the network fails after new Activation, persist the returned client's binding information and retry with `iot_client_connect()`.

---

### `iot_client_reset` {#iot_client_reset}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ Only if the application explicitly sets `.mqtt_disable_auto_connect = true` must
| `product_key` | Product PID |
| `firmware_key` | Firmware Key (may be empty) |
| `timeout_ms` | Activation timeout (milliseconds) |
| `env` | Environment: `PROD` / `PRE` |
| `env` | Client environment enum. The trailing four-byte `secret` in an App BLE token is passed unchanged as IoT DNS `env`; it is not mapped to `pre`/`pro`/`prod`, and this field does not select the App-token route |

**Return value:** On success, returns an `iot_client_t *` that contains the `devid`, `secret_key`, and `local_key` after
Activation, which can be used directly to initialize `iot_client_init()` later; on failure, returns `NULL`.
Expand Down
11 changes: 11 additions & 0 deletions modules/iot-client/CONTEXT.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,17 @@ First-time provisioning that authenticates the device and returns its credential
(devid / secret_key / local_key) together with its schema and schema id.
_Avoid_: pairing, registration, binding (those are app/cloud-side terms).

**Registration key**:
The opaque four-byte secret appended to the App's BLE authToken after the
two-byte region and eight-byte activation token, or the 1-4 byte `data.env`
from QR/MQTT activation (default `pro` when absent). The device passes it unchanged
as IoT DNS `env` to discover the Self HTTPS/MQTT endpoints before activation;
it is not the device credential `secret_key` and is not an `iot_env_t` value.
Applications persist it together with credentials and region, then restore
`iot_client_config_t.registration_key` on reboot. Older records without a key
retain their legacy `env` routing; do not force them to `pro` or re-activate them.
_Avoid_: mapping its spelling to production/pre-production enum values.

**Schema upgrade**:
Replacing the device's schema with a newer version for the same Schema ID, fetched by
the application polling the cloud (there is no MQTT schema-change notification).
Expand Down
Loading
Loading