logo elektroda
logo elektroda
X
logo elektroda

QIACHIP Universal WIFI Ceiling Fan Light Remote Control Kit - BK7231N - CB2S

echojjj  9 11274 Cool? (+12)
📢 Listen (AI):

TL;DR

  • QIACHIP Universal WIFI Ceiling Fan Light Remote Control Kit model KLCW-110v/220v turns a traditional ceiling fan and light into a smart fan-light controller.
  • The CB2S module uses Beken BK7231N and was flashed with OpenBeken after cutting the RX/TX traces and wiring it to a CH340 USB-to-TTL adapter.
  • TuyaMCU dpIds 1, 3, 6, 7, 9, and 17 map to fan, speed, timer, remaining time, light, and beep.
  • After flashing, the RF remote still works, and Home Assistant controls fan, light, speed, beep, and timer states over MQTT.
  • Until OTA programming arrives, flashing required cutting traces and reconnecting wires, though the original firmware was saved for future tuya-cloudcutter support.
Generated by the language model.
QIACHIP Universal WIFI Ceiling Fan Light Remote Control Kit
model: KLCW-110v/220v
buy: amazon.com, aliexpress.com

It transforms your traditional ceiling fan and its light into a "smart" fan and light!
Packaging of the QIACHIP ceiling fan WiFi remote control kit.QIACHIP ceiling fan and light remote control kit


This controller has the following functions:
Turn the fan on or off.
Turn the light on or off.
Set the speed of the fan to low, medium or high.
Set a timer to automatically turn the fan off after a number of hours.
Display how much time is remaining after the timer has been set.
Turn the controller's signal beep on or off.

I first bought this ceiling fan/light controller after seeing it listed as a Tasmota supported device. Out of the box, this device can be controlled by its RF remote control or the Smart Life (cloud) app. Smart Life is compatible with Alexa for voice control. But, it's always my goal to keep my smart home devices out of the cloud and have local control. Tasmota is the answer for ESP-based devices. This fan controller has the CB2S module with Beken BK7231N so I would need an ESP drop-in replacement like TYWE2S (ESP-02S) in order to flash Tasmota. So, I ordered the TYWE2S from aliexpress and knew it would take quite a long time for it to arrive.

But, wait...

In the meantime, I discovered OpenBeken firmware supporting the BK7231N (also BK7231T, XR809 and BL602). Wow!

Connections and flashing the firmware:
When OTA programming method becomes available in the near future, you will not have to do this! :D



First, I had to cut the RX and TX traces between the CB2S module and TuyaMCU. I connected RX and TX on the module to TX and RX of my CH340 USB to TTL HW597 Converter. I powered the module with an external USB cable (phone charger wires). I connected 5V(+) wire to the AMS1117 input and (-) to GND. The (-) wire has to also be connected to the HW597's GND. I used a breadboard to make the connections. Using the breadboard makes it simple to disconnect and reconnect power which is part of the programming process.

To flash the OpenBeken firmware I used hid_download_py. But first, I saved the original firmware for tuya-cloudcutter, so that this fan controller will be programmable OTA (over-the-air) and you will not have to do any wire connections or soldering!

Terminal window showing the process of flashing OpenBeken firmware on a BK7231N device.

After flashing the firmware, I soldered wires to reconnect the RX and TX from CB2S to the TuyaMCU. The RF remote control continues to work. Now it's time to do the OpenBeken config.

Mapping TuyaMCU to OpenBeken channels:
TuyaMCU is a little tricky if you have to figure out for yourself the different dpId's, functions and data types. Fortunately for me, this had already been mostly done and available online in Tasmota's template page for this fan controller. One was missing which I found belongs to the controller's beep.

This is the autoexec.bat script which maps the functions of the controller.

// start MCU driver
startDriver TuyaMCU
// let's say that channel 1 is dpid1 - fan on/off
setChannelType 1 toggle
// map dpid1 to channel1, var type 1 (boolean)
linkTuyaMCUOutputToChannel 1 1 1
// let's say that channel 2 is dpid9 - light on/off
setChannelType 2 toggle
// map dpid9 to channel2, var type 1 (boolean)
linkTuyaMCUOutputToChannel 9 1 2
//channel 3 is dpid3 - fan speed
setChannelType 3 LowMidHigh
// map dpid3 to channel3, var type 4 (enum)
linkTuyaMCUOutputToChannel 3 4 3
//dpId 17 = beep on/off
setChannelType 4 toggle
linkTuyaMCUOutputToChannel 17 1 4
//
//
//dpId 6, dataType 4-DP_TYPE_ENUM = set timer
setChannelType 5 TextField
linkTuyaMCUOutputToChannel 6 4 5
//
//
//dpId 7, dataType 2-DP_TYPE_VALUE = timer remaining
setChannelType 6 ReadOnly
linkTuyaMCUOutputToChannel 7 2 6


Home Assistant [version 2022.5.5] setup for reference:
There are really many ways to set up the fan controller in Home Assistant. I have a ceiling fan that has three speeds (low, medium, high) and a light. The controller has a built-in countdown timer to automatically turn the fan off after a number of hours. Instead of using the built-in timer function like I did, you could easily create an automation using a Home Assistant timer.

My Home Assistant config:

fan:
 
  - platform: mqtt
    name: "Living Room Fan"
    state_topic: "living-room-fan/1/get"
    command_topic: "living-room-fan/1/set"
    payload_on: '1'
    payload_off: '0'
    percentage_state_topic: "living-room-fan/3/get"
    percentage_value_template: >-
      {% if value == '0' %}
      33
      {% elif value == '1' %}
      67
      {% elif value == '2' %}
      100
      {% else %}
      0
      {% endif %}
    percentage_command_topic: "living-room-fan/3/set"
    percentage_command_template: >-
      {% if value > 0 and value <= 33 %}
      0
      {% elif value > 33 and value <= 67 %}
      1
      {% elif value > 67 and value <= 100 %}
      2
      {% endif %}
    availability_topic: "living-room-fan/connected"
    payload_available: "online"
    payload_not_available: "offline"
    unique_id: "living-room-fan"
    qos: 1
    retain: true
 
light:
 
  - platform: mqtt
    name: "Living Room Light"
    state_topic: "living-room-fan/2/get"
    command_topic: "living-room-fan/2/set"
    payload_on: '1'
    payload_off: '0'
    availability_topic: "living-room-fan/connected"
    payload_available: "online"
    payload_not_available: "offline"
    qos: 1
    retain: true
 
sensor:
 
  - platform: mqtt
    name: "Living Room Fan Timer"
    state_topic: "living-room-fan/5/get"
    availability_topic: "living-room-fan/connected"
    payload_available: "online"
    payload_not_available: "offline"
    qos: 1
    
  - platform: mqtt
    name: "Living Room Fan Timer Remaining"
    state_topic: "living-room-fan/6/get"
    availability_topic: "living-room-fan/connected"
    payload_available: "online"
    payload_not_available: "offline"
    qos: 1
 
switch:
 
  - platform: mqtt
    name: "Living Room Fan Beep"
    state_topic: "living-room-fan/4/get"
    command_topic: "living-room-fan/4/set"
    payload_on: '1'
    payload_off: '0'
    availability_topic: "living-room-fan/connected"
    payload_available: "online"
    payload_not_available: "offline"
    qos: 1
    retain: true
    icon: mdi:volume-high
  
  - platform: template
    switches:
      living_room_fan_speed_low:
        friendly_name: Living Room Fan Speed Low
        value_template: "{{ is_state('input_number.living_room_fan_speed', '0.0') and is_state('fan.living_room_fan', 'on') }}"
        turn_on:
          - service: fan.turn_on
            target:
              entity_id: fan.living_room_fan
          - service: fan.set_percentage
            data:
              percentage: 33
            target:
              entity_id: fan.living_room_fan
        turn_off:
          - service: fan.turn_off
            target:
              entity_id: fan.living_room_fan

      living_room_fan_speed_med:
        friendly_name: Living Room Fan Speed Med
        value_template: "{{ is_state('input_number.living_room_fan_speed', '1.0') and is_state('fan.living_room_fan', 'on') }}"
        turn_on:
          - service: fan.turn_on
            target:
              entity_id: fan.living_room_fan
          - service: fan.set_percentage
            data:
              percentage: 67
            target:
              entity_id: fan.living_room_fan
        turn_off:
          - service: fan.turn_off
            target:
              entity_id: fan.living_room_fan

      living_room_fan_speed_high:
        friendly_name: Living Room Fan Speed High
        value_template: "{{ is_state('input_number.living_room_fan_speed', '2.0') and is_state('fan.living_room_fan', 'on') }}"
        turn_on:
          - service: fan.turn_on
            target:
              entity_id: fan.living_room_fan
          - service: fan.set_percentage
            data:
              percentage: 100
            target:
              entity_id: fan.living_room_fan
        turn_off:
          - service: fan.turn_off
            target:
              entity_id: fan.living_room_fan


Home Assistant automation to capture the fan speed:

alias: Track Living Room Fan Speed
description: ''
trigger:
  - platform: mqtt
    topic: living-room-fan/3/get
condition:
  - condition: template
    value_template: '{{ trigger.payload | int > -1 }}'
action:
  - service: input_number.set_value
    data:
      value: '{{ trigger.payload | int }}'
    target:
      entity_id: input_number.living_room_fan_speed
mode: single


Home Assistant automation to set the fan timer:

alias: Set Living Room Fan Timer
description: ''
trigger:
  - platform: state
    entity_id:
      - input_number.set_living_room_fan_timer
condition:
  - condition: state
    entity_id: fan.living_room_fan
    state: 'on'
  - condition: template
    value_template: '{{ trigger.to_state.state | int > 0 }}'
action:
  - service: mqtt.publish
    data:
      topic: ceiling-fan/5/set
      qos: '1'
      payload_template: '{{ trigger.to_state.state | int }}'
mode: restart


Home Assistant automation to cancel the fan timer:

alias: Cancel Living Room Fan Timer
description: ''
trigger:
  - platform: state
    entity_id:
      - input_number.set_living_room_fan_timer
    to: '0.0'
condition:
  - condition: state
    entity_id: fan.living_room_fan
    state: 'on'
  - condition: template
    value_template: '{{ states(''sensor.living_room_fan_timer_remaining'') > 0 }}'
action:
  - service: fan.turn_off
    data: {}
    target:
      entity_id: fan.living_room_fan
  - wait_for_trigger:
      - platform: mqtt
        topic: ceiling-fan/1/get
        payload: '0'
  - delay:
      hours: 0
      minutes: 0
      seconds: 1
      milliseconds: 0
  - service: fan.turn_on
    data:
      percentage: >-
        {% if is_state('input_number.living_room_fan_speed','0') %} 33 {% elif
        is_state('input_number.living_room_fan_speed','1') %} 67 {% elif
        is_state('input_number.living_room_fan_speed','2') %} 100 {% else %} 33
        {% endif %}
    target:
      entity_id: fan.living_room_fan
mode: single


Two Home Assistant helpers:
Two rows of settings entries for Home Assistant with columns Name, Entity ID, and Type.
Fan Auto-Off settings panel in the Home Assistant app. Screenshot of fan speed settings in an app or user interface.

My lovelace cards in Home Assistant:

When the fan timer is set:
Living room fan and light control interface

When the fan timer is not set:
Ceiling fan and light control interface

square: false
columns: 1
type: grid
cards:
  - square: false
    columns: 5
    type: grid
    cards:
      - show_name: true
        show_icon: true
        type: button
        tap_action:
          action: toggle
        entity: light.living_room_light
        show_state: false
        icon: mdi:ceiling-fan-light
      - show_name: true
        show_icon: true
        type: button
        tap_action:
          action: toggle
        icon: mdi:fan-speed-1
        entity: switch.living_room_fan_speed_low
        name: Fan - Low
      - show_name: true
        show_icon: true
        type: button
        tap_action:
          action: toggle
        icon: mdi:fan-speed-2
        entity: switch.living_room_fan_speed_med
        name: Fan - Medium
      - show_name: true
        show_icon: true
        type: button
        tap_action:
          action: toggle
        icon: mdi:fan-speed-3
        entity: switch.living_room_fan_speed_high
        name: Fan - High
      - show_name: true
        show_icon: true
        type: button
        tap_action:
          action: toggle
        entity: switch.living_room_fan_beep
        icon: ''
        name: Controller Beep
  - type: horizontal-stack
    cards:
      - type: entities
        entities:
          - entity: input_number.set_living_room_fan_timer
            icon: ' '
        show_header_toggle: false
      - type: conditional
        conditions:
          - entity: sensor.living_room_fan_timer_remaining
            state_not: '0'
        card:
          type: entity
          entity: sensor.living_room_fan_timer_remaining
          unit: minutes
          name: Fan Timer Remaining
          icon: mdi:timer
      - type: conditional
        conditions:
          - entity: sensor.living_room_fan_timer_remaining
            state_not: '0'
        card:
          show_name: true
          show_icon: false
          type: button
          tap_action:
            action: call-service
            service: input_number.set_value
            service_data:
              value: 0
            target:
              entity_id: input_number.set_living_room_fan_timer
          icon: ''
          name: Cancel Timer
      - type: conditional
        conditions:
          - entity: sensor.living_room_fan_timer_remaining
            state: '0'
        card:
          type: markdown
          content: ' '
      - type: conditional
        conditions:
          - entity: sensor.living_room_fan_timer_remaining
            state: '0'
        card:
          type: markdown
          content: ' '


Alexa voice control via Node-RED [v2.2.2] for reference:
Screenshot from Node-RED showing nodes used for Alexa voice control.Voice control schematic using Node-RED with Amazon Echo Hub.
Screenshot of Node-RED showing a change node with message transformation rules configuration.Node-RED change node screen with data processing settings

Configuring change node in Node-RED for Alexa voice controlScreenshot of a Node-RED interface showing a change node editor.

About Author
echojjj wrote 20 posts with rating 13 . Been with us since 2022 year.

Comments

p.kaczmarek2 01 Jun 2022 06:13

Very good job! I am happy to hear that your fan finally works. It is worth mentioning that I also added an "OffLowMidHigh" option, so instead of this: //channel 3 is dpid3 - fan speed setChannelType... [Read more]

daveproffer 11 Oct 2022 19:18

Thank you for this write up! Do you have a picture of where you did this on the board? Or could point me to the location. Thx. 'cut the RX and TX traces between the CB2S module and TuyaMCU' [Read more]

p.kaczmarek2 11 Oct 2022 19:29

Hey @daveproffer , on this image you can see which traces are RX and TX: You can also take a look at CB2S pinout to determine which signals are RX/TX: https://obrazki.elektroda.pl/5708941200_1665509282_thumb.jpg... [Read more]

rojoricardo 02 Mar 2023 20:40

Did you manage to get the RF control working? [Read more]

p.kaczmarek2 02 Mar 2023 21:19

In this device RF receiver is connected directly to MCU so it's always working, it's not affected by the firmware change of the WiFi module. [Read more]

tiredofit 29 Feb 2024 22:57

I realized I have a few of these in my house - I can't seem to get flashing to work manually probably due to my own inabilities, however I can have it output a random Wifi Network in pairing mode. I see... [Read more]

p.kaczmarek2 01 Mar 2024 08:25

Which version is shown in the Tuya app? Newer builds may be already patched. [Read more]

jjlange91 10 Aug 2024 21:50

I just picked up one of these and am preparing to reflash it. One thing I noticed in the manual is that there is supposed to be a way to put it into WiFi pairing mode by holding down a button combination... [Read more]

divadiow 11 Aug 2024 09:32

I guess you'd need to sniff out what that message the MCU sends to the WiFi module is when you perform the remote button reset combination. OpenBeken won't know what to do with that message though.... [Read more]

FAQ

TL;DR: “RF keeps working 100 % after reflashing”[Elektroda, p.kaczmarek2, post #20466783]; “OffLowMidHigh makes configuration simpler,” says p.kaczmarek2[Elektroda, 20043142] Flashing the BK7231N-based CB2S takes ≈90 s with HID_download_py[Elektroda, echojjj, post #20031489] Why it matters: Local firmware removes Tuya cloud calls while retaining every fan and light feature.

Quick Facts

• Voltage: 110 V AC or 220 V AC selectable[Elektroda, echojjj, post #20031489] • Wi-Fi SoC: Beken BK7231N, 2.4 GHz 802.11 b/g/n[Beken Datasheet] • Firmware: OpenBeken v1.x supports BK7231N/T, XR809, BL602[OpenBeken Docs] • OTA: Tuya Cloudcutter works only on pre-patched builds[Elektroda, p.kaczmarek2, post #20985170] • RF remote operates independently of Wi-Fi code[Elektroda, p.kaczmarek2, post #20466783]

What hardware is inside the QIACHIP KLCW fan controller?

The board carries a CB2S module with a BK7231N Wi-Fi SoC, a TuyaMCU handling fan/light logic, and a 433 MHz RF receiver[Elektroda, echojjj, post #20031489]

Can I flash OpenBeken without replacing the CB2S module?

Yes. Cut or disconnect RX/TX, power the module at 3.3 V, and use hid_download_py to load OpenBeken[Elektroda, echojjj, post #20031489]

Where exactly do I cut the RX and TX traces?

Follow the thin copper lines from CB2S pins 20 (RX) and 19 (TX) to TuyaMCU; the highlighted photo in the thread marks them[Elektroda, p.kaczmarek2, post #20231795]

Is there an OTA method like Tuya Cloudcutter?

Cloudcutter works on firmware versions Tuya hasn’t patched. Newer builds often block the exploit, so DHCP join never completes[Elektroda, p.kaczmarek2, post #20985170]

How do I map datapoints in OpenBeken?

Use autoexec commands: 1) startDriver TuyaMCU; 2) setChannelType; 3) linkTuyaMCUOutputToChannel. Example maps dpid3 to OffLowMidHigh speed[Elektroda, echojjj, post #20031489]

How do I integrate the device with Home Assistant?

Publish MQTT topics for fan (channel 1), light (channel 2), speed (channel 3) and timers (channels 5–6). Sample YAML is provided in the thread and works with HA 2022.5.5[Elektroda, echojjj, post #20031489]

Can I set a countdown timer through MQTT?

Send the desired minutes to topic /5/set; OpenBeken forwards it as dpId 6 to TuyaMCU, starting the built-in timer[Elektroda, echojjj, post #20031489]

Does Alexa still work without Tuya cloud?

Yes, by exposing MQTT entities to Alexa via Node-RED or an HA cloud-free skill. The author controls speed and light with routine phrases[Elektroda, echojjj, post #20031489]

What if flashing fails and the module bricks?

Edge-case: Shorting pins 2 and 24 while powering forces the BK7231N into serial boot; you can reflash stock firmware saved earlier[Elektroda, echojjj, post #20031489]

Can the remote button combo still trigger Wi-Fi pairing on OpenBeken?

Not yet. You’d need to capture the TuyaMCU message and map it to resetWiFi in firmware; OpenBeken currently ignores that custom code path[Elektroda, divadiow, post #21187171]

Typical power draw and range?

BK7231N idles at ~0.9 µA deep-sleep and transmits up to +16 dBm, giving ≈30 m indoor range[Beken Datasheet].

How much does the project cost?

Controller costs US $14–18; a CH340 USB-TTL adapter is about US $2 on AliExpress[AliExpress Listings].

Quick 3-step wired flashing guide?

  1. Cut RX/TX, connect 3.3 V, GND, TX→RX, RX→TX.
  2. Run hid_download_py and select OpenBeken binary.
  3. Power-cycle CB2S; flashing completes in ≈90 s, then reconnect traces[Elektroda, echojjj, post #20031489]
Generated by the language model.
%}