I recently reverse-engineered the Tuya interface used in a Breezary Levelle 52-inch ceiling fan and now have the fan operating locally through ESPHome without the Tuya / Smart Life cloud.
The fan uses a T1-2S-NL module with a BK7238, communicating with a separate receiver MCU over the Tuya MCU protocol at 9600 baud.
The original RF remote communicates with the receiver rather than directly with the Wi-Fi module, so replacing the Tuya firmware does not sacrifice the factory remote.
What is working
At this point I’ve physically tested and reproduced:
- Fan OFF
- Fan speeds 1–6
- Clockwise / counter-clockwise direction
- White light ON/OFF
- White brightness
- White color temperature
- RGB ON/OFF
- All 7 factory static RGB colors
- All 11 factory RGB effects
- Synchronization when controls are changed from the original RF remote
- An ESP-local fan shutdown timer
Hardware
The tested Wi-Fi module is:
- Module: T1-2S-NL
- MCU: BK7238 / T1 family
- Flash: 2 MiB
- ESPHome board:
generic-bk7238-tuya - Receiver UART: 9600 baud
Accessible module pads are:
3V3 | GND | RX1 | TX1 | P9 | P24
In this fan the factory Tuya module can be reflashed in place, so an additional ESP does not need to be retrofitted into the receiver.
Fan datapoints
The fan controls were physically identified as:
DP104 -> direction
DP105 -> speed 1–6
DP107 -> fan running/stopped
Direction was confirmed as:
0 -> Counter-Clockwise
1 -> Clockwise
One behavior worth noting is that the receiver itself starts the fan when direction is changed while the fan is stopped. The ESPHome configuration compensates for that behavior when issuing a direction command while stopped.
RGB report/write datapoint pairs
One of the more interesting findings was that the Tuya datapoints used to report RGB state are not necessarily the datapoints that should be written.
For static colors:
DP24 -> reported/live static color
DP61 -> writable/persistent static color
For effects:
DP25 -> reported/live effect
DP56 -> writable/persistent effect
Capturing and replaying the factory payloads through DP61 and DP56 reproduced the remote’s seven static colors and eleven RGB effects.
The complete captured payloads are documented in the repository.
Why I didn’t use ESPHome’s native light:component
I initially tried modeling the lighting with ESPHome’s native light: component.
The receiver’s feedback behavior caused unwanted state interactions, including RGB operations resulting in transitions back into white mode.
The final implementation therefore exposes the receiver through discrete template switches, numbers and buttons instead of forcing the hardware into the standard ESPHome light abstraction.
That has been considerably more reliable with this receiver.
Factory 2-hour timer
I also investigated the factory 2-hour timer.
Pressing the physical remote’s timer button consistently reports:
DP103 = 120
DP103 = 7200
The receiver also flashes the light as acknowledgement.
However, sending those values back over UART does not reproduce the receiver’s internal countdown. Additional DP103 values and sequences were tested without establishing an arbitrary writable countdown.
Rather than assign behavior that wasn’t physically demonstrated, the production firmware implements the shutdown timer locally on the ESP and sends DP107 OFF when it expires.
The local timer supports 0–240 minutes. Once started, Home Assistant does not need to remain connected for it to expire. Timer expiration stops only the fan; the lighting remains in its current state.
Repository
I’ve published the complete production ESPHome YAML along with the reverse-engineering notes, datapoint map, captured RGB payloads, flashing information, and physical validation results:
The repository includes:
breezary-levelle.yaml— tested production ESPHome configurationdocs/datapoints.md— datapoint map and captured RGB payloadsdocs/reverse-engineering.md— hardware, UART, flashing and protocol investigationdocs/testing.md— physical validation results
The factory firmware itself is not distributed.
I made two independent complete 2 MiB factory backups before modifying the module. Both produced the same SHA-256, which is documented in the repository as a reference for anyone investigating matching hardware.
Still unknown
Several Tuya datapoints remain unidentified, and I’ve intentionally left them documented as observations rather than assigning speculative functions.
Likewise, the raw RGB payload structures have not been completely decoded. The firmware reproduces the known-good factory payloads that were physically verified instead of assigning unsupported meanings to every byte.
The factory DP103 timer protocol also remains only partially understood.
If anyone has the same Breezary Levelle, or another fan/receiver using the T1-2S-NL + BK7238 combination, I’d be very interested to know whether the datapoint map and RGB payloads match your hardware.