logo elektroda
logo elektroda
X
logo elektroda

Carro Ceiling Fan OpenBeken Config: WBR3 RTL8720CF TuyaMCU, DPIDs, Light & Fan Control

jeremysalwen 300 2
ADVERTISEMENT
  • #1 21753849
    jeremysalwen
    Level 2  
    Hello, I have successfully ported the cloudcutter exploit to the RTL8720CF and flashed my Carro ceiling fan with openbeken.

    It's a TuyaMCU ceiling fan with a light attached to it, with an independently controlled "white" and "warm" LED. The fan also is reversible and has an oscillation mode. There apprently is a timer based on the DPIDs but nothing I could control from the remote and I'm not sure what it does.

    This is my configuration I have hacked together: (autoexec.bat)
    
    startDriver TuyaMCU
    
    # ------------------------------------------------------
    # Fan power (DP 1)
    # ------------------------------------------------------
    setChannelType 1 Toggle
    SetChannelLabel 1 "Fan Power"
    linkTuyaMCUOutputToChannel 1 1 1
    
    
    # ------------------------------------------------------
    # Fan speed (DP 3) - enum 1..10
    # ------------------------------------------------------
    setChannelType 2 Enum
    SetChannelLabel 2 "Fan Speed"
    SetChannelEnum 2 0:0 1:1 2:2 3:3 4:4 5:5 6:6 7:7 8:8 9:9 10:10
    linkTuyaMCUOutputToChannel 3 4 2
    
    
    # ------------------------------------------------------
    # Fan mode (DP 2) - enum sleep/normal/nature
    # ------------------------------------------------------
    setChannelType 3 Enum
    SetChannelLabel 3 "Fan Mode"
    SetChannelEnum 3 0:Sleep 1:Normal 2:Nature
    linkTuyaMCUOutputToChannel 2 4 3
    
    
    # ------------------------------------------------------
    # Fan direction (DP 103) - enum forward/reverse
    # ------------------------------------------------------
    setChannelType 4 Enum
    SetChannelLabel 4 "Fan Direction"
    SetChannelEnum 4 0:Forward 1:Reverse
    linkTuyaMCUOutputToChannel 103 4 4
    
    
    # ------------------------------------------------------
    # Warm light (DP 9 switch, DP 10 brightness)
    # ------------------------------------------------------
    setChannelType 5 Toggle
    SetChannelLabel 5 "Warm Light"
    linkTuyaMCUOutputToChannel 9 1 5
    
    setChannelType 6 Dimmer1000
    SetChannelLabel 6 "Warm Brightness"
    linkTuyaMCUOutputToChannel 10 2 6
    
    
    # ------------------------------------------------------
    # Cool light (DP 101 switch, DP 102 brightness)
    # ------------------------------------------------------
    setChannelType 7 Toggle
    SetChannelLabel 7 "Cool Light"
    linkTuyaMCUOutputToChannel 101 1 7
    
    setChannelType 8 Dimmer1000
    SetChannelLabel 8 "Cool Brightness"
    linkTuyaMCUOutputToChannel 102 2 8
    
    
    # ------------------------------------------------------
    # Tell OBK our brightness scale
    # ------------------------------------------------------
    tuyaMcu_setDimmerRange 10 1000
    
    
    # ------------------------------------------------------
    # Countdown (DP 6 enum hours 0-8)
    # ------------------------------------------------------
    setChannelType 9 Enum
    SetChannelLabel 9 "Countdown (h)"
    SetChannelEnum 9 0:Off 1:"1h" 2:"2h" 3:"3h" 4:"4h" 5:"5h" 6:"6h" 7:"7h" 8:"8h"
    linkTuyaMCUOutputToChannel 6 4 9
    
    
    # ------------------------------------------------------
    # Countdown remaining (DP 7 minutes, read-only)
    # ------------------------------------------------------
    setChannelType 10 ReadOnly
    SetChannelLabel 10 "Countdown Left (min)"
    linkTuyaMCUOutputToChannel 7 2 10
    
    


    I am not totally happy with it, since Fan Speed is an enum but really it should be a value between 0 and 10, I just couldn't figure out how to get it working.

    For reference here is the tuya local configuration which matches what I have: https://github.com/make-all/tuya-local/issues/930
    
    {
      "result": {
        "model": "{\"modelId\":\"0000003ncr\",\"services\":[{\"actions\":[],\"code\":\"\",\"description\":\"\",\"events\":[],\"name\":\"默认服务\",\"properties\":[
    {\"abilityId\":1,\"accessMode\":\"rw\",\"code\":\"switch\",\"description\":\"【必选】\\n风扇开关\",\"extensions\":{\"iconName\":\"icon-power\",\"attribute\":\"1\"},\"name\":\"开关\",\"typeSpec\":{\"type\":\"bool\",\"typeDefaultValue\":false}},
    {\"abilityId\":2,\"accessMode\":\"rw\",\"code\":\"mode\",\"description\":\"正常风\\n睡眠风\\n自然风\",\"extensions\":{\"iconName\":\"icon-fengli\"},\"name\":\"工作模式\",\"typeSpec\":{\"range\":[\"sleep\",\"normal\",\"nature\"],\"type\":\"enum\",\"typeDefaultValue\":\"sleep\"}},
    {\"abilityId\":3,\"accessMode\":\"rw\",\"code\":\"fan_speed\",\"description\":\"【必选】可根据实际档位需求扩展\",\"extensions\":{\"iconName\":\"icon-a_fan_low\",\"attribute\":\"4\"},\"name\":\"风速\",\"typeSpec\":{\"range\":[\"1\",\"2\",\"3\",\"4\",\"5\",\"6\",\"7\",\"8\",\"9\",\"10\"],\"type\":\"enum\",\"typeDefaultValue\":\"1\"}},
    {\"abilityId\":6,\"accessMode\":\"rw\",\"code\":\"countdown\",\"description\":\"0:取消\\n1:1小时\\n2:2小时\\n.....\",\"extensions\":{\"iconName\":\"icon-dp_time2\"},\"name\":\"倒计时\",\"typeSpec\":{\"range\":[\"0\",\"1\",\"2\",\"3\",\"4\",\"5\",\"6\",\"7\",\"8\"],\"type\":\"enum\",\"typeDefaultValue\":\"0\"}},
    {\"abilityId\":7,\"accessMode\":\"ro\",\"code\":\"countdown_left\",\"description\":\"\",\"extensions\":{\"iconName\":\"icon-dp_time3\"},\"name\":\"倒计时剩余时间\",\"typeSpec\":{\"max\":480,\"min\":0,\"scale\":0,\"step\":1,\"type\":\"value\",\"typeDefaultValue\":0,\"unit\":\"分钟\"}},
    {\"abilityId\":9,\"accessMode\":\"rw\",\"code\":\"light\",\"description\":\"黄灯开关\",\"extensions\":{\"iconName\":\"icon-tcl_function_light\",\"attribute\":\"20\"},\"name\":\"黄灯\",\"typeSpec\":{\"type\":\"bool\",\"typeDefaultValue\":false}},
    {\"abilityId\":10,\"accessMode\":\"rw\",\"code\":\"bright_value\",\"description\":\"\",\"extensions\":{\"iconName\":\"icon-liangdu\"},\"name\":\"灯光亮度\",\"typeSpec\":{\"max\":1000,\"min\":10,\"scale\":0,\"step\":1,\"type\":\"value\",\"typeDefaultValue\":10,\"unit\":\"\"}},
    {\"abilityId\":101,\"accessMode\":\"rw\",\"code\":\"whitelight_switch\",\"description\":\"\",\"extensions\":{\"iconName\":\"icon-tcl_function_light\",\"attribute\":\"20\"},\"name\":\"白灯\",\"typeSpec\":{\"type\":\"bool\",\"typeDefaultValue\":false}},
    {\"abilityId\":102,\"accessMode\":\"rw\",\"code\":\"whitebright_value\",\"description\":\"\",\"name\":\"白光亮度\",\"typeSpec\":{\"max\":1000,\"min\":10,\"scale\":0,\"step\":1,\"type\":\"value\",\"typeDefaultValue\":10,\"unit\":\"%\"}},
    {\"abilityId\":103,\"accessMode\":\"rw\",\"code\":\"fan_direction\",\"description\":\"正转\\n反转\",\"extensions\":{\"iconName\":\"icon-a_function_vs\",\"attribute\":\"4\"},\"name\":\"风向\",\"typeSpec\":{\"range\":[\"forward\",\"reverse\"],\"type\":\"enum\",\"typeDefaultValue\":\"forward\"}}]}]}"
      },
      "success": true,
      "t": 1690425453193,
      "tid": "89fcd1bf2c2611ee9fecaa50f188a4b0"
    }
    


    and also the device.json from cloudcutter:

    
    {
        "manufacturer": "Carro",
        "name": "Ceiling Fan v2.5.2",
        "key": "keyjkajwawthq4f5",
        "ap_ssid": "SmartLife",
        "github_issues": [],
        "image_urls": [
            "https://tuya-cloudcutter.github.io/images/tuya-generic-yd001-motion-night-light.jpg"
        ],
        "profiles": [{
                "slug": "rtl8720cf-common-user-config-ty-2.5.2-sdk-2.3.0-40.00",
                "name": "2.5.2 RTL8720CF",
                "type": "CLASSIC",
                "sub_name": "rtl8720cf_common_user_config_ty",
                "icon": "memory"
            }
        ],
        "schemas": {
            "0000003ncr": [{
                    "mode": "rw",
                    "property": {
                        "type": "bool"
                    },
                    "id": 1,
                    "type": "obj"
                }, {
                    "mode": "rw",
                    "property": {
                        "range": ["sleep", "normal", "nature"],
                        "type": "enum"
                    },
                    "id": 2,
                    "type": "obj"
                }, {
                    "mode": "rw",
                    "property": {
                        "range": ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"],
                        "type": "enum"
                    },
                    "id": 3,
                    "type": "obj"
                }, {
                    "mode": "rw",
                    "property": {
                        "range": ["0", "1", "2", "3", "4", "5", "6", "7", "8"],
                        "type": "enum"
                    },
                    "id": 6,
                    "type": "obj"
                }, {
                    "mode": "ro",
                    "property": {
                        "min": 0,
                        "max": 480,
                        "scale": 0,
                        "step": 1,
                        "type": "value"
                    },
                    "id": 7,
                    "type": "obj"
                }, {
                    "mode": "rw",
                    "property": {
                        "type": "bool"
                    },
                    "id": 9,
                    "type": "obj"
                }, {
                    "mode": "rw",
                    "property": {
                        "min": 10,
                        "max": 1000,
                        "scale": 0,
                        "step": 1,
                        "type": "value"
                    },
                    "id": 10,
                    "type": "obj"
                }, {
                    "mode": "rw",
                    "property": {
                        "type": "bool"
                    },
                    "id": 101,
                    "type": "obj"
                }, {
                    "mode": "rw",
                    "property": {
                        "min": 10,
                        "max": 1000,
                        "scale": 0,
                        "step": 1,
                        "type": "value"
                    },
                    "id": 102,
                    "type": "obj"
                }, {
                    "mode": "rw",
                    "property": {
                        "range": ["forward", "reverse"],
                        "type": "enum"
                    },
                    "id": 103,
                    "type": "obj"
                }
            ]
        },
        "slug": "rtl8720cf-common-user-config-ty-2.5.2-sdk-2.3.0-40.00",
        "image_url": "https://tuya-cloudcutter.github.io/images/thumbs/tuya-generic-yd001-motion-night-light.jpg"
    }
    


    EDIT: I tried cleaning up my script, now it seems like it broke the UI. I can't load the site without getting connection reset. I also can see that it thinks the fan is a dimmer rather than a fan on HomeAssistant:

    EDIT2: The broken UI was not caused by my current script, it was leftover configuration. After running the ClearIO command, everything started working again. Here is my current script. The only issues I have are the fan speed being an enum (more of a minor annoyance), and the fact that homeassistant gets totally confused about what is what. It thinks the color brightness is a dimmer control for the fan (which it thinks is a lightbulb)! It would be nice for it to just treat it as a single light bulb with adjustable brightness and color temperature.
    
    startDriver TuyaMCU
    
    # ------------------------------------------------------
    # Fan power (DP 1)
    # ------------------------------------------------------
    setChannelType 1 Toggle
    SetChannelLabel 1 "Fan"
    linkTuyaMCUOutputToChannel 1 bool 1
    
    
    # ------------------------------------------------------
    # Fan speed (DP 3) - enum 1..10
    # ------------------------------------------------------
    setChannelType 2 Enum
    SetChannelLabel 2 "Fan Speed"
    SetChannelEnum 2 0:0 1:1 2:2 3:3 4:4 5:5 6:6 7:7 8:8 9:9 10:10
    linkTuyaMCUOutputToChannel 3 enum 2
    
    
    # ------------------------------------------------------
    # Fan mode (DP 2) - enum sleep/normal/nature
    # ------------------------------------------------------
    setChannelType 3 Enum
    SetChannelLabel 3 "Fan Mode"
    SetChannelEnum 3 0:Sleep 1:Normal 2:Nature
    linkTuyaMCUOutputToChannel 2 enum 3
    
    
    # ------------------------------------------------------
    # Fan direction (DP 103) - enum forward/reverse
    # ------------------------------------------------------
    setChannelType 4 Enum
    SetChannelLabel 4 "Fan Direction"
    SetChannelEnum 4 0:Forward 1:Reverse
    linkTuyaMCUOutputToChannel 103 enum 4
    
    
    # ------------------------------------------------------
    # Warm light (DP 9 switch, DP 10 brightness)
    # ------------------------------------------------------
    setChannelType 5 Toggle
    SetChannelLabel 5 "Warm Light"
    SetChannelPrivate 5 1
    linkTuyaMCUOutputToChannel 9 bool 5
    
    setChannelType 6 Dimmer1000
    SetChannelLabel 6 "Warm Brightness"
    linkTuyaMCUOutputToChannel 10 val 6
    
    
    # ------------------------------------------------------
    # Cool light (DP 101 switch, DP 102 brightness)
    # ------------------------------------------------------
    setChannelType 7 Toggle
    SetChannelLabel 7 "Cool Light"
    SetChannelPrivate 7 1
    linkTuyaMCUOutputToChannel 101 bool 7
    
    setChannelType 8 Dimmer1000
    SetChannelLabel 8 "Cool Brightness"
    linkTuyaMCUOutputToChannel 102 val 8
    
    
    # ------------------------------------------------------
    # Tell OBK our brightness scale
    # ------------------------------------------------------
    tuyaMcu_setDimmerRange 10 1000
    
    # ------------------------------------------------------
    # All lights master toggle (bidirectional)
    # ------------------------------------------------------
    setChannelType 11 Toggle
    SetChannelLabel 11 "Light"
    
    # When All Lights is turned ON, turn both lights ON
    AddChangeHandler Channel11 == 1 backlog SetChannel 5 1;SetChannel 7 1
    
    # When All Lights is turned OFF, turn both lights OFF
    AddChangeHandler Channel11 == 0 backlog SetChannel 5 0;SetChannel 7 0
    
    # If either individual light is turned ON, set All Lights = ON
    AddChangeHandler Channel5 == 1 SetChannel 11 1
    AddChangeHandler Channel7 == 1 SetChannel 11 1
    
    # If a light is turned OFF, and BOTH are now OFF, set All Lights = OFF
    AddChangeHandler Channel5 == 0 if $CH5==0&&$CH7==0 then SetChannel 11 0
    #AddChangeHandler Channel7 == 0 if $CH5==0&&$CH7==0 then SetChannel 11 0
    
    # Optional: sync All Lights at boot based on current light states
    if $CH5!=0||$CH7!=0 then SetChannel 11 1
    if $CH5==0&&$CH7==0 then SetChannel 11 0
    
  • ADVERTISEMENT
  • #2 21754415
    divadiow
    Level 37  
    Hello

    I don't have the real thing of course but I did stick your autoexec into OpenRTL87x0C tester and I am not getting any web console access issues

    OpenRTL87X0C web interface showing fan and light status controls

    Control panel for device rtl87x0CE9DB081B with lights and fan enabled
  • #3 21754486
    jeremysalwen
    Level 2  
    >>21754415
    Thank you. I figured out that it was old configuration that I needed to clear out using ClearIO. I thought rebooting would clear everything not in my autoexec.bat, so I was confused.

    Do you have suggestions of how to make it appear properly in Home Assistant as a single light fan?

    I had the idea to use change handlers to have virtual brightness and color temperature controls that set both light statuses, but for some reason my change handlers don't seem to be firing consistently (if I run RecalcCCT from the console it seems to do the right thing!)

    
    setChannelType 12 Dimmer1000
    SetChannelLabel 12 "Light Brightness"
    setChannelType 13 Dimmer1000
    SetChannelLabel 13 "Light CT"
    alias RecalcCCT backlog setChannel 6 2*$CH12*(1000-$CH13)/1000;  setChannel 8 2*$CH12*$CH13/1000;
    AddChangeHandler Channel12 > -1 RecalcCCT
    AddChangeHandler Channel13 > -1 RecalcCCT
    


    Added after 5 [hours] 50 [minutes]:

    Okay, I got it working good enough as a virtual light. The only thing that is not totally great is that brightness changes from the remote are not reflected in openbeken/homeassistant (although color temperature changes, aka the power states of the two leds, are). I had to use a manual homeassistant mqtt configuration

    ```
    startDriver TuyaMCU

    # ------------------------------------------------------
    # Fan power (DP 1)
    # ------------------------------------------------------
    setChannelType 1 Toggle
    SetChannelLabel 1 "Fan"
    linkTuyaMCUOutputToChannel 1 bool 1


    # ------------------------------------------------------
    # Fan speed (DP 3) - enum 1..10
    # ------------------------------------------------------
    setChannelType 2 Enum
    SetChannelLabel 2 "Fan Speed"
    SetChannelEnum 2 0:0 1:1 2:2 3:3 4:4 5:5 6:6 7:7 8:8 9:9 10:10
    linkTuyaMCUOutputToChannel 3 enum 2


    # ------------------------------------------------------
    # Fan mode (DP 2) - enum sleep/normal/nature
    # ------------------------------------------------------
    setChannelType 3 Enum
    SetChannelLabel 3 "Fan Mode"
    SetChannelEnum 3 0:Sleep 1:Normal 2:Nature
    linkTuyaMCUOutputToChannel 2 enum 3


    # ------------------------------------------------------
    # Fan direction (DP 103) - enum forward/reverse
    # ------------------------------------------------------
    setChannelType 4 Enum
    SetChannelLabel 4 "Fan Direction"
    SetChannelEnum 4 0:Forward 1:Reverse
    linkTuyaMCUOutputToChannel 103 enum 4


    # ------------------------------------------------------
    # Warm light (DP 9 switch, DP 10 brightness) - PHYSICAL
    # ------------------------------------------------------
    setChannelType 5 Toggle
    SetChannelLabel 5 "Warm Light"
    SetChannelPrivate 5 1
    linkTuyaMCUOutputToChannel 9 bool 5

    setChannelType 6 Dimmer1000
    SetChannelLabel 6 "Warm Brightness"
    SetChannelPrivate 6 1
    linkTuyaMCUOutputToChannel 10 val 6


    # ------------------------------------------------------
    # Cool light (DP 101 switch, DP 102 brightness) - PHYSICAL
    # ------------------------------------------------------
    setChannelType 7 Toggle
    SetChannelLabel 7 "Cool Light"
    SetChannelPrivate 7 1
    linkTuyaMCUOutputToChannel 101 bool 7

    setChannelType 8 Dimmer1000
    SetChannelLabel 8 "Cool Brightness"
    SetChannelPrivate 8 1
    linkTuyaMCUOutputToChannel 102 val 8


    # ------------------------------------------------------
    # Tell OBK our brightness scale for TuyaMCU dimmers
    # ------------------------------------------------------
    tuyaMcu_setDimmerRange 10 1000


    # ------------------------------------------------------
    # VIRTUAL CCT LIGHT
    # CH11 = master power
    # CH12 = virtual brightness (0..1000)
    # CH13 = virtual color temp (0..1000, 0=warm, 1000=cool)
    # ------------------------------------------------------

    # Master power
    setChannelType 11 Toggle
    SetChannelLabel 11 "Light"

    # Virtual color temperature
    setChannelType 12 Dimmer1000
    SetChannelLabel 12 "Light CT"

    # Virtual brightness
    setChannelType 13 Dimmer1000
    SetChannelLabel 13 "Light Brightness"


    # ------------------------------------------------------
    # Helpers for virtual CCT logic
    # ------------------------------------------------------

    setChannelType 14 Dimmer1000
    SetChannelLabel 14 "CCT Helper"
    SetChannelPrivate 14 1
    SetChannelVisible 14 0

    alias RecalcCCT backlog setChannel 14 2*$CH13*(1000-$CH12)/1000; if $CH14>1000 then "setChannel 6 1000" else "setChannel 6 $CH14"; setChannel 14 2*$CH13*$CH12/1000; if $CH14>1000 then "setChannel 8 1000" else "setChannel 8 $CH14";

    alias DoSyncVirtualFromPhysical backlog setChannel 13 ($CH6+$CH8)/2; setChannel 12 1000*$CH8/($CH6+$CH8);
    alias SyncVirtualFromPhysical backlog if $CH6!=0||$CH8!=0 then DoSyncVirtualFromPhysical else "setChannel 13 0"

    # ------------------------------------------------------
    # Virtual light handlers
    # ------------------------------------------------------

    # Any change of Brightness or CT should recompute mix
    addEventHandler OnChannelChange 12 RecalcCCT
    addEventHandler OnChannelChange 13 RecalcCCT


    # When All Lights is turned ON, turn both lights ON
    AddChangeHandler Channel11 == 1 backlog SetChannel 5 1;SetChannel 7 1

    # When All Lights is turned OFF, turn both lights OFF
    AddChangeHandler Channel11 == 0 backlog SetChannel 5 0;SetChannel 7 0
    # ------------------------------------------------------
    # Keep virtual Light in sync with PHYSICAL Tuya switches
    # (so local MCU buttons still affect CH11 state)
    # ------------------------------------------------------

    # If either individual light is turned ON, set master Light = ON
    AddChangeHandler Channel5 == 1 SetChannel 11 1
    AddChangeHandler Channel7 == 1 SetChannel 11 1

    # If a light is turned OFF, and BOTH are now OFF, set master Light = OFF
    AddChangeHandler Channel5 == 0 if $CH5==0&&$CH7==0 then SetChannel 11 0
    # You can also enable this if you want symmetrical behaviour:
    AddChangeHandler Channel7 == 0 if $CH5==0&&$CH7==0 then SetChannel 11 0


    # ------------------------------------------------------
    # Initial sync at boot
    # ------------------------------------------------------
    if $CH5!=0||$CH7!=0 then SetChannel 11 1
    if $CH5==0&&$CH7==0 then SetChannel 11 0

    ```

    HomeAssistant Configuration:
    ```
    mqtt:
    fan:
    - name: "Living Room Fan 1"
    unique_id: "livingroomfan1.fan"
    state_topic: "livingroomfan1/1/get"
    command_topic: "livingroomfan1/1/set"
    payload_on: "1"
    payload_off: "0"

    availability_topic: "livingroomfan1/connected"
    payload_available: "online"
    payload_not_available: "offline"
    qos: 1
    retain: true

    # ---- Speed as percentage mapped to Enum 0..10 on Channel 2 ----
    percentage_state_topic: "livingroomfan1/2/get"
    percentage_command_topic: "livingroomfan1/2/set"

    # Home Assistant UI is 0–100%, Tuya speed is 0–10
    speed_range_min: 1
    speed_range_max: 100

    percentage_value_template: >
    {# MQTT payload (channel 2) is 0..10, convert to 0..100% #}
    {% set s = value | int %}
    {{ (s * 100 / 10) | round(0) }}

    percentage_command_template: >
    {# HA sends 0..100%, convert back to 0..10 enum #}
    {% set p = value | int %}
    {% if p <= 0 %}
    0
    {% elif p >= 100 %}
    10
    {% else %}
    {{ (p * 10 / 100) | round(0) }}
    {% endif %}

    # ---- Direction (Channel 4: 0=Forward, 1=Reverse) ----
    direction_state_topic: "livingroomfan1/4/get"
    direction_command_topic: "livingroomfan1/4/set"
    direction_value_template: >
    {% if value | int == 1 %}
    reverse
    {% else %}
    forward
    {% endif %}
    direction_command_template: >
    {% if value == 'reverse' %}
    1
    {% else %}
    0
    {% endif %}

    # ---- Modes mapped to your Fan Mode enum on Channel 3 ----
    # CH3: 0:Sleep 1:Normal 2:Nature
    preset_modes:
    - sleep
    - normal
    - nature
    preset_mode_state_topic: "livingroomfan1/3/get"
    preset_mode_command_topic: "livingroomfan1/3/set"
    preset_mode_value_template: >
    {% set v = value | int %}
    {% if v == 0 %}
    sleep
    {% elif v == 1 %}
    normal
    {% elif v == 2 %}
    nature
    {% else %}
    normal
    {% endif %}
    preset_mode_command_template: >
    {% if value == 'sleep' %}
    0
    {% elif value == 'normal' %}
    1
    {% elif value == 'nature' %}
    2
    {% else %}
    1
    {% endif %}

    device:
    identifiers: ["livingroomfan1"]
    manufacturer: "OpenBeken"
    model: "Tuya Fan (BK7231)"
    name: "Living Room Fan 1"
    configuration_url: "http://192.168.1.213/index"
    light:
    - name: "Living Room Fan 1 Light"
    unique_id: "livingroomfan1.light"

    # Master power (Channel 11)
    state_topic: "livingroomfan1/11/get"
    command_topic: "livingroomfan1/11/set"
    payload_on: "1"
    payload_off: "0"
    qos: 1
    retain: true

    # --- Brightness (Channel 13: 0..1000) <-> 0..255 in HA ---
    brightness_state_topic: "livingroomfan1/13/get"
    brightness_command_topic: "livingroomfan1/13/set"

    brightness_value_template: >
    {# 0..1000 (OBK) -> 0..255 (HA) #}
    {% set v = value | int %}
    {{ (v * 255 / 1000) | round(0) }}

    brightness_command_template: >
    {# 0..255 (HA) -> 0..1000 (OBK) #}
    {% set b = value | int %}
    {% if b <= 0 %}
    0
    {% elif b >= 255 %}
    1000
    {% else %}
    {{ (b * 1000 / 255) | round(0) }}
    {% endif %}

    # --- Color temperature: Channel 12 (0..1000 warm->cool) ---
    # Map to mired range ~2700K..6500K
    color_temp_state_topic: "livingroomfan1/12/get"
    color_temp_command_topic: "livingroomfan1/12/set"

    # These should match the range you want in HA
    min_mireds: 153 # ~6500K (cool)
    max_mireds: 370 # ~2700K (warm)

    color_temp_value_template: >
    {# 0..1000 (0=warm,1000=cool) -> mireds (370..153) #}
    {% set v = value | int %}
    {% set min_m = 153 %}
    {% set max_m = 370 %}
    {% set v_clamped = [1000, [0, v] | max] | min %}
    {{ (max_m - (v_clamped * (max_m - min_m) / 1000)) | round(0) }}

    color_temp_command_template: >
    {# mireds (370..153) -> 0..1000 (0=warm,1000=cool) #}
    {% set m = value | int %}
    {% set min_m = 153 %}
    {% set max_m = 370 %}
    {% set m_clamped = [max_m, [min_m, m] | max] | min %}
    {{ ((max_m - m_clamped) * 1000 / (max_m - min_m)) | round(0) }}

    # Optional: same device grouping as the fan
    device:
    identifiers: ["livingroomfan1"]
    manufacturer: "OpenBeken"
    model: "Tuya Fan (BK7231)"
    name: "Living Room Fan 1"
    configuration_url: "http://192.168.1.213/index"
    ```
ADVERTISEMENT