logo elektroda
logo elektroda
X
logo elektroda

Tuya CB3S BK7231N Curtain Motor Restoration with OpenBeken, MQTT, and Home Assistant

maxotkin31 60 0
ADVERTISEMENT
  • #1 21825382
    maxotkin31
    Level 1  
    How I revived a Tuya curtain motor after the CB3S burned out: OpenBeken + Home Assistant + a virtual curtain

    CB3S module with red, black, green, and white UART wires soldered to pins Tuya CB3S Wi-Fi module (BK7231N) on wooden surface, QR code and labeling visible CB3S module on a circuit board with a connected wire on black background. CB3S board with wires connected for USB-UART flashing


    I had a problem: the Wi-Fi board in my curtain motor (CB3S / BK7231N) burned out. I didn’t have the original backup, and flashing a dump from another motor caused a conflict in Tuya (one device connects while the other drops, then vice versa). In the end I built a proper local solution: OpenBeken + MQTT + Home Assistant, and everything works without the cloud.

    What you need

    CB3S / BK7231N board

    USB-UART (I used CP2102, 3.3V power)

    BK7231Flasher

    OpenBeken firmware for BK7231N

    Home Assistant + Mosquitto broker

    Where to get a CB3S board

    You can easily buy a Tuya CB3S (BK7231N) board on AliExpress — that’s what I did. I bought one заранее “as a spare”, and it sat unused for a long time until the original board in the motor burned out.

    1) Flash CB3S with OpenBeken

    To flash CB3S you need a regular USB-UART adapter (I used CP2102).

    Required wires

    Basically you only need 4 lines:

    GND ↔ GND

    3.3V ↔ VCC (3.3V only!)

    TX (adapter) ↔ RX (board)

    RX (adapter) ↔ TX (board)

    ⚠️ Important: power must be strictly 3.3V. Do NOT feed 5V — you can kill the module.

    Entering flash mode

    Usually BK7231Flasher shows something like “reboot device / power cycle”.
    Since I only had power + TX/RX accessible, I simply:

    removed power for a second

    and applied power again right when the program was waiting for a reboot.

    Tips

    If it won’t connect, swap TX/RX first.

    Make sure the adapter and the board share common GND.

    If the module keeps rebooting/glitching, use a stable 3.3V supply, not a weak 3.3V regulator on some UART adapters (depends on the adapter).

    Flash the CB3S (BK7231N) module using BK7231Flasher.

    ✅ Important: in recent versions of BK7231Flasher, OpenBeken is built in (there are ready images/firmwares inside the program), so you usually don’t need to search for and download a separate .bin file — just select OpenBeken in the UI and flash.

    2) Configure channels in OpenBeken

    Open the device web UI:

    Launch Web Application → Config → Channel Types

    Set:

    Channel 0 — Default

    Channel 1 — OpenStopClose

    Channel 2 — Dimmer

    Channel 3 — ReadOnly

    Note: you can also set channel types via autoexec.bat (below). I left it both in the UI and in autoexec — easier to reproduce.

    3) Create autoexec.bat (TuyaMCU + dpID)

    Open:

    FileSystem → Create File → autoexec.bat

    Paste:

    # Initialize communication with the motor controller
    startDriver TuyaMCU
    baud 9600

    # dpID 1: Main control (Enum)
    # 0 - Open, 1 - Stop, 2 - Close
    setChannelType 1 OpenStopClose
    linkTuyaMCUOutputToChannel 1 enum 1

    # dpID 2: Set position (Value)
    # Send percent (0-100) here
    setChannelType 2 Dimmer
    linkTuyaMCUOutputToChannel 2 val 2

    # dpID 3: Current position (Value)
    # Motor reports its current position (0-100)
    setChannelType 3 ReadOnly
    linkTuyaMCUOutputToChannel 3 val 3


    Save the file and reboot the module:

    Index → Restart (red button)

    After that, in the OpenBeken main menu you should see a 3-position control (Open/Stop/Close), a slider (Channel 2), and the current position display (Channel 3).

    4) Configure MQTT in OpenBeken

    Config → Configure MQTT

    Fill in Mosquitto details (Home Assistant IP, username/password). You can find the password in HA:

    Mosquitto broker → Configuration / Logins

    Key field:

    Client Topic (Base Topic): curtain_1

    (You can name it differently, but use the same name later in the code.)

    After that, Home Assistant will create entities from OpenBeken, but usually it’s not a “curtain cover” yet — just separate selects/sensors. That’s fine — we fix it next.

    5) Create a “virtual curtain” (cover) in Home Assistant

    Open:

    /config/configuration.yaml

    ⚠️ Important: there must be only one template: block. If you already have one, don’t create a second — add your cover inside the existing block.

    Paste (this is the version formatted for sites that break YAML indentation; replace .. with spaces later):

    template:
    ..- cover:
    ....- name: "North curtain (virtual)"
    ......unique_id: curtain_virtual

    ......# Current position (0-100) from sensor, inverted
    ......position: "{{ 100 - (states('sensor.curtain_3') | int(0)) }}"

    ......open_cover:
    ........- service: mqtt.publish
    ..........data:
    ............topic: "curtain_1/1/set"
    ............payload: "0"

    ......stop_cover:
    ........- service: mqtt.publish
    ..........data:
    ............topic: "curtain_1/1/set"
    ............payload: "1"

    ......close_cover:
    ........- service: mqtt.publish
    ..........data:
    ............topic: "curtain_1/1/set"
    ............payload: "2"

    ......# Position slider: send % to OpenBeken via MQTT to Channel 2
    ......set_cover_position:
    ........- service: mqtt.publish
    ..........data:
    ............topic: "curtain_1/2/set"
    ............payload: "{{ 100 - position }}"
    ............retain: false
    ............qos: 0


    How to read:

    .. = 2 spaces

    .... = 4 spaces

    ...... = 6 spaces
    and so on.

    Before pasting into Home Assistant, simply replace every .. with two normal spaces (Find/Replace in any editor).

    Restart Home Assistant. A new entity will appear, e.g.:

    cover.north_curtain_virtual (the exact name depends on your chosen name).

    6) If the direction is reversed

    There are three independent “inversions” — change them one by one (don’t change everything at once):

    A) Only the display direction (HA slider inverted)

    Inversion is done in position::

    No inversion:

    position: "{{ states('sensor.curtain_3') | int(0) }}"


    Inverted (as in the example):

    position: "{{ 100 - (states('sensor.curtain_3') | int(0)) }}"

    B) Slider moves the curtain the wrong way

    Change the payload in set_cover_position:

    No inversion:

    payload: "{{ position }}"


    Inverted:

    payload: "{{ 100 - position }}"

    C) Open/Close swapped

    Swap payload "0" and "2" in open_cover and close_cover.

    7) Result

    In the end:

    the motor runs locally on OpenBeken

    control in HA is a proper cover entity with slider + buttons

    you can later expose it to Yandex Alice, build automations, schedules, sensors, etc.

    Alternative: ESP8266 / ESPHome

    In theory (and in practice), instead of CB3S you can move fully to ESP8266/ESPHome and control the motor via your own controller. This option exists, and many people do it.

    I tried ESP8266 + ESPHome with the TuyaMCU component before. But with this particular motor it didn’t work properly: either TuyaMCU in ESPHome couldn’t control the motor correctly, or I couldn’t figure out which parameters and dpIDs were required for this device’s protocol.

    It’s also possible that the TuyaMCU driver in OpenBeken is implemented/tuned better for devices like this: it immediately picked up UART communication (9600) and both position reporting and commands worked correctly.

    So I initially went with the “closest to factory” route — using the original CB3S. And once it was working on OpenBeken and controllable from Home Assistant without the cloud, I didn’t go back to ESP8266: that would mean more soldering, protocol debugging, and time.

    In short: if it works — don’t touch it 😄
  • ADVERTISEMENT
ADVERTISEMENT