TuyaOpen Build-Deploy-Debug Loop
Loop Workflow
The standard development iteration cycle for TuyaOpen hardware:
┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
│ Build │────>│ Flash │────>│ Monitor │────>│ Analyze │────>│ Decide │
│ │ │ │ │ Logs │ │ Results │ │ │
└─────────┘ └─────────┘ └─────────┘ └─────────┘ └────┬────┘
^ │
│ ┌──────────┐ │
└─────────────────────────│ Fix Code │<─────── if error ────────┘
└──────────┘ if ok → done
Step-by-step
Build:
tos.py build— compile firmware (see skilltuyaopen/build)Flash: Flash firmware to the device from the project directory:
tos.py flash -p <port> # specify port (recommended) tos.py flash # auto-detect port tos.py flash -p <port> -d # debug outputWhich port? Run
tyutool_cli list-ports --jsonand group onusbSerial— one physical board is oneusbSerial:- 1 port → single-serial board: flash, auth and log all share it.
- 2+ ports → dual-serial board (T5/T5AI etc.): flash = lowest
usbInterface, monitor/log = the other.
Rank by
usbInterface, not byCOM/ttyACMnumber — on Windows the flash port can be the higher COM. Typical, not guaranteed: if flash fails on one port, swap to the other of the sameusbSerial. Serial permission required on Linux (once):sudo usermod -aG dialout $USERthen reboot.On single-serial boards the loop is sequential: the monitor holds the only port, so
stopit before flashing (elseAccess is denied/Device or resource busy) andstartit again after. Dual-serial boards can keep the log port open across a flash.Monitor / capture logs:
tos.py monitor -p <port>for interactive sessions, or hands-off background logging viatuyaopen/debug-helper(monitor_helper.py start -p <port>→tail→stop).Analyze: read the log file under
<project_dir>/.target_logging/for errors, warnings, crash indicators (patterns below)Decide: pass (device healthy) or fail (fix code and restart loop)
LINUX shortcut
For LINUX platform targets, skip flash/monitor — use the bundled script:
$OPEN_SDK_PYTHON .agents/skills/tuyaopen-dev-loop/scripts/build_run.py # build + run + auto-analyze (30s timeout)
$OPEN_SDK_PYTHON .agents/skills/tuyaopen-dev-loop/scripts/build_run.py 60 # custom timeout in seconds
$OPEN_SDK_PYTHON .agents/skills/tuyaopen-dev-loop/scripts/build_run.py 0 # no timeout
Or manually:
./dist/<project>_<version>/<project>_<version>.elf 2>&1 | tee device.log
Both dist/ (canonical output) and .build/bin/ (build intermediate) contain the ELF. Use dist/ for consistency.
Log Format & Patterns
TuyaOpen log format
[MM-DD HH:MM:SS ty X][source_file.c:line] message
Where X is the log level: E (error), W (warn), N (notice), I (info), D (debug), T (trace).
Key patterns to watch
| Pattern | Meaning | Action |
|---|---|---|
[... ty E] |
Error-level log (PR_ERR) |
Analyze the error message and source location |
[... ty W] |
Warning (PR_WARN) |
Usually non-fatal but worth investigating |
feed watchdog |
Health monitor heartbeat (every ~10s) | Normal — device is alive |
OPRT_ followed by negative number |
SDK operation failed | Look up error code (see references/ERROR_CODES.md) |
mqtt connected or MQTT_CONNECTED |
Cloud connection established | Success — device is online |
TUYA_EVENT_DIRECT_MQTT_CONNECTED |
Direct MQTT event | Cloud connection confirmed |
Replace the TUYA_OPENSDK_UUID |
Placeholder credentials detected | User must configure real UUID/AuthKey |
| No output after flash | Device crashed or wrong serial port | Check baud rate and port; try reset |
| Repeated reset / boot loop | Crash during init or watchdog timeout | Check last error before reset |
malloc failed or OPRT_MALLOC_FAILED |
Out of memory | Reduce buffer sizes or optimize memory |
Log level hierarchy
ERR > WARN > NOTICE > INFO > DEBUG > TRACE
Default log level: DEBUG. Set via tal_log_init(TAL_LOG_LEVEL_DEBUG, 1024, callback).
CLI Testing
Built-in CLI (tal_cli) via debug UART (prompt: tuya> ). Commands, registration, batch testing: references/ERROR_CODES.md.
Device State
| State | Signal |
|---|---|
| Healthy | feed watchdog every ~10s; no PR_ERR after init; mqtt connected |
| No output | Wrong port, wrong baud, crash before log init |
| Boot loop | Crash in init — check last error before reset |
| Watchdog reset | Deadlock or infinite loop — check PR_ERR before reset |
| MQTT fail | Check network, credentials, PID mismatch |
OPRT_MALLOC_FAILED |
OOM — reduce buffer sizes |
Agent Iteration Strategy
On build failure
- Read the compiler error output carefully.
- Identify the source file and line.
- Fix the code.
tos.py buildagain. Repeat until build succeeds.
On flash failure
- Check serial port (T5 dual-port: try the other port if flash fails — see the Flash step above).
- Wait ~1 minute if port is busy.
- Retry with the other port if available.
- If still failing, ask the user to check hardware connection.
On runtime error (log analysis)
- Capture log output after flash (monitor for 10-30 seconds).
- Search for
ty E(errors) andOPRT_patterns. - Map error codes using
references/ERROR_CODES.md. - Identify the source file and line from the log.
- Fix the code based on the error context.
- Restart the loop: build → flash → monitor.
On no output
- Verify serial port and baud rate match the chip (check the baud rate table: T2=115200, T3/T5AI=460800, ESP32=115200, LN882H=921600).
- Reset the device manually.
- If still no output, the firmware may have crashed before log init — review recent code changes.
AI agent helper: tuyaopen/debug-helper (monitor_helper.py)
Full reference: skill tuyaopen/debug-helper. Script path (relative to SDK root):
.agents/skills/tuyaopen-debug-helper/scripts/monitor_helper.py
Logs are always written to <project_dir>/.target_logging/ (gitignored by the SDK).
Typical flow
# 1. Start background monitor (non-blocking)
$OPEN_SDK_PYTHON .agents/skills/tuyaopen-debug-helper/scripts/monitor_helper.py \
--json start -p /dev/ttyACM1
# 2. Flash on the other port while monitor keeps logging
tos.py flash -p /dev/ttyACM0
# 3. Read log after boot
$OPEN_SDK_PYTHON .agents/skills/tuyaopen-debug-helper/scripts/monitor_helper.py \
--json tail -n 200
# 4. Stop and release port
$OPEN_SDK_PYTHON .agents/skills/tuyaopen-debug-helper/scripts/monitor_helper.py stop
Iteration loop (analyze → fix → re-run)
Repeat until logs are clean:
- Build →
tos.py flash -p <port> monitor_helper.py start -p <monitor-port>— capture boot + runtime tracemonitor_helper.py tail -n 200→ searchty E,OPRT_, watchdog, MQTT- Edit code → go to step 1
monitor_helper.py stopwhen done so the port is free for the next flash