Skip to content

TuyaMCU driver never processes real incoming UART data on BK7231T (works via fakeTuyaPacket, fails via real UART / uartFakeHex) #2153

Description

@fafavorit

Title

TuyaMCU driver never processes real incoming UART data on BK7231T (works via fakeTuyaPacket, fails via real UART / uartFakeHex)

Environment

  • Chip: BK7231T (WB3S module)
  • Firmware tested: 1.18.296 (current) and 1.18.197 (~8 months older, Oct 2025) — same bug in both, so this is not a recent regression
  • Board: custom OEM ceiling light driver board (silkscreen "Fan_wifi_Colorful REV:A3 P2020.08.27"), external "MCU" is a SinOne SC92F7423 (generic 8051, TuyaMCU-style firmware)

Summary

The local MCU sends valid, checksum-correct TuyaMCU protocol frames on UART1, confirmed by an independent external hardware UART sniff (logic-level tap + raw capture, decoded by hand against the Tuya serial protocol spec). These bytes physically arrive at the BK7231T's UART1 RX pin while OpenBK7231T is running, but the TuyaMCU driver's heartbeat_valid / product_information_valid state never transitions away from 0, even after several minutes of continuous observation and multiple full power-cycles.

Steps to reproduce

  1. Wire an external device (or in our case a real "dumb" TuyaMCU-speaking MCU) to UART1 (RXD1/TXD1) of a BK7231T module.
  2. startDriver TuyaMCU, loglevel 5, enable RAW + TuyaMCU log features.
  3. Observe: ExtraDebug:TuyaMCU:TuyaMCU heartbeat_valid = 0, product_information_valid=0, ... repeats forever, no matter how long you wait, even though bytes are confirmed present on the wire (see below).

Independent proof that bytes are arriving at the chip

  • Tapped an external USB-UART adapter directly onto the BK7231T's UART1 RX net (same net, high-impedance passive listen, GND shared) while OpenBK7231T was running.
  • Captured, in the exact same time window that OpenBK7231T's log showed only heartbeat_valid = 0, 12 well-formed frames:
    55 AA 00 00 00 00 FF   (repeated, ~3.75s interval — matches TuyaMCU's own heartbeat_timer=3 constant)
    
    Header 55 AA, version 00, cmd 00 (HEARTBEAT), length 00 00, checksum FF (= 0x55+0xAA, correct).
  • Ruled out crosstalk from the neighboring TX1 line: TX1 was independently confirmed silent (0 bytes in a 45s capture) during the exact same test, while RX1 kept showing the frames — reproducible across repeated captures.
  • Also confirmed (with stock/original Tuya firmware temporarily reflashed) that the same MCU sends the same well-formed heartbeat frames spontaneously and continuously (~1.2s interval under stock firmware) — so the MCU and wiring are not in question.

Isolating the bug: fakeTuyaPacket works, uartFakeHex does not

Using the exact same 7-byte hex string (55AA00000000FF) for both:

  • fakeTuyaPacket 55AA00000000FF (calls TuyaMCU_ProcessIncoming() directly, bypassing the UART ring buffer) → works correctly:
    Info:TuyaMCU:ProcessIncoming[v=0]: cmd 0 (Hearbeat) len 7
    ExtraDebug:TuyaMCU:TuyaMCU heartbeat_valid = 1, product_information_valid=0, ...
    ExtraDebug:TuyaMCU:Will send TUYA_CMD_QUERY_PRODUCT.
    
  • uartFakeHex 55AA00000000FF (appends bytes to the real UART receive ring buffer via UART_AppendByteToReceiveRingBuffer, exercising the same path real hardware reception uses) → fails silently: no ProcessIncoming log line ever appears, heartbeat_valid stays 0, even after a fresh reboot with an empty buffer.

This isolates the problem specifically to UART_TryToGetNextTuyaPacket() in drv_tuyaMCU.c (or the ring-buffer plumbing it depends on in drv_uart.c) — not to TuyaMCU_ProcessIncoming or the protocol/state-machine logic, which both work fine when fed directly.

Other checks that ruled out simpler explanations

  • Tested baud rates 9600 (default), 4800, 19200, 115200 — same negative result at all.
  • Tested with OBK_FLAG_USE_SECONDARY_UART (UART2) — same negative result (though UART2 wiring to the MCU wasn't independently wire-confirmed, so this data point is weaker).
  • Tested across a full, clean power-on boot with startDriver TuyaMCU in the startup commands (so the driver is listening from t=0) — no difference.
  • Tested on two firmware versions ~8 months apart — same result on both.

Suspicious code (not confirmed as root cause, offered as leads)

  1. Length parsing bug in UART_TryToGetNextTuyaPacket() (drv_tuyaMCU.c):

    lena = UART_GetByte(4); // hi
    lenb = UART_GetByte(5); // lo
    len = lenb | lena >> 8;

    This should presumably be len = (lena << 8) | lenb;. As written, lena >> 8 is always 0 for a byte, so the high length byte is silently discarded. This doesn't affect our zero-length HEARTBEAT case, but will break any frame where the length field's high byte is non-zero (i.e. any payload ≥ 256 bytes, or more subtly any case where lena isn't 0).

  2. No resync/timeout for stuck partial frames. If cs < len (not enough bytes buffered yet to complete the declared frame), the function returns 0 without consuming anything — correct in principle, but there's no mechanism to ever discard a frame whose declared length will never be satisfied (e.g. from a single corrupted/malformed byte sequence that happens to start with 55 AA). We reproduced this by accident: sending a malformed 6-byte uartFakeHex string once left 6 stray bytes permanently stuck in the ring buffer (never consumed, since the length check failed and returned early), which then corrupted the framing of every subsequent injection. If real line noise or a boot-time glitch ever produces a similar false-header + implausible-length sequence, the parser could get permanently wedged, silently dropping all real traffic behind it — which would exactly match the symptom we're seeing (never processes anything, even a clean reboot doesn't help since the wedge could occur again in the very first few bytes of every boot).

  3. Zero-timeout mutex in the quick-tick dispatcher (drv_main.c):

    void DRV_RunQuickTick() {
      if (DRV_Mutex_Take(0) == false) {
        return;
      }
      for (i = 0; i < g_numDrivers; i++) { ... g_drivers[i].runQuickTick(); ... }
    }

    TuyaMCU_RunFrame (which drains the UART receive buffer) is registered as runQuickTick. If the mutex is ever contended when this runs, the entire quick-tick pass for all drivers is skipped that cycle with no retry/backoff/logging. We haven't confirmed this is the actual cause, but it's a plausible contributor to inconsistent/starved UART draining.

Attachments

  • Raw hex capture of MCU heartbeat traffic under stock Tuya firmware (2 min) and under OpenBK7231T (45s), plus a small Python decoder script that parses/validates the Tuya frame format — happy to attach if useful.

Ask

Any pointers on whether this is a known issue, or where else to look, would be much appreciated. Happy to test patches — I have full UART access (both to the WB3S module and, independently, to the external MCU) and can reproduce this reliably.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions