| name | tuyaopen/smart-product-dev |
| description | End-to-end IoT product development orchestration for TuyaOpen projects. Guides from requirements gathering → Tuya Platform product/DP creation → complete embedded firmware generation. State-machine: detects project state and picks up from wherever development currently stands. |
| when_to_use | Use when the developer says "I want to make a [device]", "帮我做一个XX", "what's next?", "下一步该干什么", or describes product features and expects end-to-end guidance. Do NOT use for pure platform ops (→ tuya-iot-platform), pure build/debug (→ tuyaopen/dev-loop), or project creation only (→ tuyaopen/project-config). |
| id | smart-product-dev |
| surface | embedded |
| tags | ["product","dp","pid","embedded","iot","workflow","orchestration"] |
| license | Apache-2.0 |
| defaultEnabled | true |
| related | ["tuya-iot-platform","tuyaopen/dev-loop","tuyaopen/project-config","tuyaopen/env-setup"] |
| command | tuyaopen.skill.smartProductDev |
TuyaOpen Smart Product Development
End-to-end orchestration: requirements → Tuya Platform product/DP → embedded firmware.
Disambiguation: If only doing one sub-task, use the dedicated skill:
- Platform operations only →
tuya-iot-platform
- Build/debug only →
tuyaopen/dev-loop
- Project creation only →
tuyaopen/project-config
- "Add a DP to my existing product" →
tuya-iot-platform
dpSchema Unwrap Convention
Always unwrap before any DP access — dpSchema may be a { ok, data } wrapper:
dpSchema = snapshot.dpSchema?.data ?? snapshot.dpSchema
dps = dpSchema?.dps ?? []
selectedDps = dps.filter(dp => dp.selected === true)
Never access snapshot.dpSchema.dps directly.
Pre-flight Checks
Run in order. Stop on first failure.
| Check | How | If failing |
|---|
| CLI binary | .tuyaopen/ide/bin/tuya-devplat-cli exists | "Please open this project in TuyaOpen IDE first — the IDE writes the CLI wrapper on project open." |
| Platform auth | tuya-devplat-cli auth status --format json → exit 0 AND authenticated: true | "Please sign in via TuyaOpen IDE → Developer Platform sidebar." Never run auth login. Timeout >10 s → report network issue. |
| SDK env | $OPEN_SDK_ROOT set and dir contains export.sh/export.bat/export.ps1 | SDK present but not activated → delegate to tuyaopen/env-setup. SDK absent → "Please clone the SDK via TuyaOpen IDE → Library." |
Context Reading (Every Entry)
Read ALL files fresh on every entry including re-entry. Never carry state from prior conversation.
| File | What to extract |
|---|
tuyaopen.project.ini | [product] pid, [platform] target |
.tuyaopen/project.json | ai.intent, ai.expectedDps, ai.productCategory |
.tuyaopen/platform/product-<pid>.json | full snapshot (apply unwrap), fetchError |
.tuyaopen/ide/platform.json | peripherals, connectivity, pinout, flashAndDebug |
.tuyaopen/ide/board.json | peripheralPatterns |
.tuyaopen/architecture.json | surfaces.embedded.peripherals |
source/embedded/src/tuya_app_main.c | #include lines (text grep only) |
source/embedded/src/ listing | all .c filenames present |
If .tuyaopen/ does not exist → state is no-project.
State Detection
Evaluate top-to-bottom. First match wins.
no-project .tuyaopen/ does not exist
bare [product] pid is empty or missing
has-pid pid non-empty AND any of:
· product-<pid>.json missing
· product-<pid>.json has fetchError
· selectedDps (after unwrap) count === 0
has-dps pid non-empty
AND product-<pid>.json exists, no fetchError
AND selectedDps count ≥ 1
AND architecture.json surfaces.embedded.peripherals is empty/absent
AND source/embedded/src/ has only scaffold files*
in-progress pid non-empty
AND product-<pid>.json exists, no fetchError
AND selectedDps count ≥ 1
AND at least one of:
· architecture.json surfaces.embedded.peripherals has any entry ← authoritative
· source/embedded/src/ has non-scaffold .c files
*Scaffold files: at time of writing, only tuya_app_main.c. When architecture.json.surfaces.embedded.peripherals has entries, that is the definitive in-progress signal regardless of file listing. Do not rely on header scanning alone — the scaffold may include umbrella headers (e.g., tal_api.h) that reference hardware headers transitively.
State: no-project
Delegate to tuyaopen/project-config to create the project. After creation re-run from Context Reading.
State: bare
Goal: requirements → create product + DPs on platform → bind PID.
Step 1 — Requirements
Ask developer to describe the product freely. Extract:
- Features (what the device does)
- Product category: ask explicitly —
dj (灯具), kt (空调), qt (通用), etc. Affects DP numbering and standard templates.
Communication type: Infer from [platform] target + platform.json.connectivity. Do not ask.
- Supported: Wi-Fi+BT, Wi-Fi-only, Linux/Raspberry Pi
- Unsupported: pure Bluetooth, Zigbee, Z-Wave → fail-fast, explain why
Wi-Fi-only path: If platform.json.connectivity.ble.enabled === false — still run Steps 2 and 3. After Step 3, tell developer: "Your board is Wi-Fi-only. tuya-iot-platform only supports Wi-Fi+BT product creation. Please create the product manually on platform.tuya.com and give me the PID." Then jump to Step 6 (skip Steps 4–5 — product was created manually).
Do not ask about GPIO/hardware here — that belongs in has-dps.
Step 2 — DP Mapping
Map features to DP codes + types. For known categories, use standard codes. Present for confirmation:
I'll create these DPs:
switch_led (bool) — on/off
bright_value (integer, 10–1000) — brightness
temp_value (integer, 0–1000) — color temperature
Does this look right? Any additions or changes?
Wait for explicit confirmation before proceeding.
Step 3 — Persist Intent (always, including Wi-Fi-only path)
Merge/update .tuyaopen/project.json. Preserve all existing top-level fields and all ai.* fields not listed below. Only set:
{
"ai": {
"intent": "<developer description verbatim>",
"expectedDps": ["switch_led", "bright_value", "temp_value"],
"productCategory": "dj"
}
}
Step 4 — Create Product
Delegate to tuya-iot-platform → ops/product.md. Show dry-run preview + riskLevel. Require explicit developer approval before --confirm. On failure: do not write PID to ini, stop.
Step 5 — Create DPs
Delegate to tuya-iot-platform → ops/manage-dp.md. Dry-run → developer approves → confirm. On partial failure: report which DPs failed, stop.
Step 6 — Bind PID
Edit tuyaopen.project.ini → [product] pid = <pid>.
Tell developer: "Please press Sync in TuyaOpen IDE → Project Details. Let me know when done."
When developer says done: re-run full state detection from Context Reading. Trust the file, not the claim. If still has-pid, tell developer and wait again.
Rollback: If writing ini fails after product was created: "Product created, PID <pid>. Please add manually: [product] pid = <pid> in tuyaopen.project.ini."
State: has-pid
Goal: Diagnose and fix missing/incomplete DPs.
| Condition | Action |
|---|
product-<pid>.json missing | "Please press Sync in TuyaOpen IDE → Project Details." Re-run state detection after developer confirms. Trust the file. |
fetchError in snapshot | Show error text. Ask developer to check credentials/network, Sync again. |
selectedDps count === 0, ai.expectedDps exists | Run DP creation (bare Step 5). Ask Sync. Re-run state detection. |
selectedDps count === 0, no ai.expectedDps | Ask developer what DPs are needed. Write to project.json ai.expectedDps (merge/update, preserve other fields). Run DP creation. Ask Sync. |
DP completeness (when selectedDps ≥ 1 and ai.expectedDps exists):
- Codes in
ai.expectedDps but not in selectedDps → add via tuya-iot-platform → ops/manage-dp.md (dry-run → approve → confirm)
- Codes in
selectedDps but not in ai.expectedDps → ask developer if intentional. If yes: add to ai.expectedDps (merge/update project.json)
After any fix: ask Sync, re-run state detection.
State: has-dps
Goal: Hardware wiring + complete firmware generation.
Step 1 — Reserved Pin Set
Collect from ALL sources:
board.json peripheralPatterns[*].pins[*][*].gpio
platform.json flashAndDebug.flash.pins
platform.json flashAndDebug.debug port → look up TX/RX via pinout[]
platform.json peripherals.uart[*] where logPort === true → look up TX/RX via pinout[]
Available after subtracting reserved:
- PWM:
peripherals.pwm.spec.channels[] — each channel lists valid pin options; exclude options whose GPIO is reserved
- I2C:
peripherals.i2c.spec.buses[] — each bus needs SDA + SCL (2 GPIO); exclude buses with no free pin pair
- GPIO:
peripherals.gpio.spec.pins[] minus all reserved numbers
Read architecture.json surfaces.embedded.peripherals — skip Step 3 inquiry for peripherals already wired there.
Step 2 — Pin Budget
GPIO demand per interface:
| Interface | GPIO pins |
|---|
| PWM channel | 1 |
| I2C bus | 2 (SDA + SCL) |
| SPI bus | 4 (MOSI + MISO + CLK + CS) |
| GPIO output/input | 1 |
If demand > available: tell developer. Suggest alternatives (different board, I2C expander, fewer channels). Do not continue until resolved.
Step 3 — Hardware Inquiry
For each DP needing hardware (not already in architecture.json), present available options then ask:
Brightness control (warm + cool LED) via PWM:
PWM0 → valid pins: 6, 18 (pin 4 reserved: board STATUS_LED)
PWM1 → valid pins: 7, 19
PWM2 → valid pins: 8, 20
Which channel + pin for warm-white LED?
Which channel + pin for cool-white LED?
Active-high or active-low?
Never assume a pin. If developer picks a reserved GPIO: "GPIO X is already used by [board.json component]. Please choose from the options above."
Step 4 — Plan Confirmation
Present plan including Kconfig changes. Wait for approval.
Implementation plan:
Warm LED: PWM0 / pin 6 / active-high
Cool LED: PWM1 / pin 7 / active-high
DP handlers: switch_led (id 1), bright_value (id 2), temp_value (id 3)
Kconfig: CONFIG_ENABLE_PWM
Headers: tuya_iot_dp.h, tal_pwm.h
Cloud: solution type from product snapshot
Does this look right?
Step 5 — Kconfig Update
Update source/embedded/app_default.config:
peripherals.<name>.enableMacro (skip if null)
connectivity.<radio>.enableMacro (skip if null)
Run tos.py check. If it fails, fix Kconfig and re-check. Do not generate code until tos.py check passes.
Step 6 — Code Generation
Use TAL APIs (tal_*). Do not call tkl_* (platform layer) directly.
Look up from platform.json:
peripherals.<name>.tklHeader → #include header path
peripherals.<name>.idPrefix → prefix for port/pin C enums
Generate:
Entry point: tuya_app_main() in source/embedded/src/tuya_app_main.c.
Debug output: PR_DEBUG(fmt, ...).
Step 7 — Update architecture.json
Write new peripherals and modules to architecture.json surfaces.embedded. This is the authoritative in-progress signal. Write only after Step 6 completes.
Step 8 — Build
Delegate to tuyaopen/dev-loop. If build fails, diagnose and fix in place. If Kconfig is root cause, go to Step 5 and rebuild.
State: in-progress
Goal: Gap analysis → complete code.
Step 1 — Gap Analysis
Cross-reference source files, architecture.json, ai.expectedDps, and selectedDps:
- DPs in
ai.expectedDps with no handler in source → missing
- Peripherals in
architecture.json surfaces.embedded.peripherals not initialized in code → missing init
- DPs in
selectedDps not in ai.expectedDps → ask developer if they should be handled
Step 2 — Surface Gap
Reading existing code...
Handled: switch_led ✓
Missing: bright_value — no PWM init or handler
Missing: temp_value — no PWM handler
Completing now...
Step 3 — Complete Code
Run has-dps Step 3 hardware inquiry only for missing parts. Read architecture.json first — do not re-ask for already-wired peripherals.
After completing, update architecture.json (has-dps Step 7).
Step 4 — Build
Delegate to tuyaopen/dev-loop.
Reverse Transitions
| Trigger | Action |
|---|
DP found missing vs ai.expectedDps | → has-pid (add DP), return to current state |
| Developer adds new feature | Update ai.expectedDps. → has-pid (add DP). Return to in-progress. |
| Build fails: missing Kconfig | → has-dps Step 5 |
| Developer wants different PID | Clear [product] pid from ini. Delete product-<old-pid>.json. Keep ai.*. → bare Step 3 (persist intent) then Step 6 (bind the new PID). Skip Steps 4–5 — the product already exists on the platform. |
| Change product category | Clear ai.productCategory + ai.expectedDps from project.json. → bare Step 1. |
Always / Never
Always:
- Show dry-run
preview + riskLevel before any --confirm
- Require explicit developer approval for every platform mutation
- Read
ai.intent / ai.expectedDps before re-asking requirements
- Show available peripheral options before asking developer to choose pins
- Re-read all context files on every entry
- Trust snapshot files — not developer's oral Sync confirmation
- Apply dpSchema unwrap before any DP access
Never:
- Run
tuya-devplat-cli auth login
- Invent a PID or DP code
- Assume a GPIO pin without developer confirmation
- Re-ask hardware questions already in
architecture.json
- Proceed past dry-run without approval
- Skip pre-flight checks
- Call
tkl_* APIs in generated code
Failure & Rollback
| Failure | Recovery |
|---|
| Product created, write ini fails | Report PID. Ask developer to add [product] pid = <pid> manually. |
| DP creation partially fails | Report which failed. Re-enter has-pid on next run — ai.expectedDps comparison catches the gap. |
| Sync not done | Re-run state detection after each "done" claim. Trust the file. |
Kconfig / tos.py check fails | Fix before generating code. |
| Build fails | Stay in in-progress. Debug via dev-loop. |
| Wi-Fi-only board | Run Steps 1–3, ask developer to create product manually, continue from Step 6. |