How to wire switch to dimmer (FS-05R)?

I’ve managed to get it to work! I’m sharing my ESPHome config here for others. I do not have a physical dimmer switch so I only wired up the dumb switch to P6 and P23. In order to adjust the brightness outside of the app, I set up the dumb switch to trigger a cycle between brightness settings if someone repeatedly toggles it within 5 seconds:

esphome:
  name: fs05r
  name_add_mac_suffix: false
  friendly_name: FS-05R Mini Dimmer Switch
  on_boot:
    priority: -10
    then:
      - globals.set:
            id: boot_complete
            value: "true"
      - light.turn_off: out

bk72xx:
  board: cb2s

# Enable logging
logger:

# Enable Home Assistant API
api:
  encryption:
    key: !secret encryption_key

ota:
  - platform: esphome

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "FS-05R Fallback Hotspot"
    password: !secret fallback_hotspot

captive_portal:

globals:
  - id: brightness_index
    type: int
    restore_value: no
    initial_value: '0'
  - id: last_press_time
    type: unsigned long
    restore_value: no
    initial_value: '0'
  - id: boot_complete
    type: bool
    restore_value: no
    initial_value: "false"

# https://www.elektroda.com/rtvforum/topic4039890.html
status_led:
  pin:
    number: P8
    inverted: true

uart:
  id: uartbus
  rx_pin: RX1
  tx_pin: TX1
  baud_rate: 115200
  debug:
    direction: BOTH

time:
  - platform: sntp
    id: sntp_time
    on_time_sync:
      then:
        - lambda: |-
            id(last_press_time) = id(sntp_time).now().timestamp;

output:
  - platform: gpio
    pin: P23
    id: S0
  - platform: template
    id: fake_tuya
    type: float
    min_power: 0.0
    max_power: 1.0
    write_action:
      - uart.write:
          # https://www.elektroda.com/rtvforum/topic3880546.html
          id: uartbus
          data: !lambda |-
            uint8_t brightness_level = int(id(out).remote_values.get_brightness() * 255);
            uint8_t toggle_value = int(id(out).remote_values.is_on());

            // Calculate the value to send to the Tuya device
            uint16_t val = brightness_level * 3 * toggle_value;

            ESP_LOGD("fake_tuya", "Brightness: %d, Toggle: %d, val: %d", brightness_level, toggle_value, val);

            // Split the value into two bytes
            uint8_t high = (val >> 8) & 0xFF;
            uint8_t low = val & 0xFF;

            // Prepare TuyaMCU data packet
            // [0] 0x55       - Start byte
            // [1] 0xAA       - Second byte
            // [2] 0x00       - Protocol version or command type (can vary)
            // [4] 0x30       - Command type of package (content)
            // [5] 0x00       - The size of the packet`s data content in bytes (high)
            // [6] 0x03       - The size of the packet`s data content in bytes (low)
            // [7] 0x00       - Unknown
            // [8] high       - High byte of the calculated value (brightness * 3 * toggle_value)
            // [9] low        - Low byte of the calculated value
            // [10] checksum  - Checksum
            std::vector<uint8_t> data = {0x55, 0xAA, 0x00, 0x30, 0x00, 0x03, 0x00, high, low, 0x00};

            // Sum all bytes except the checksum itself (last byte)
            uint16_t sum = 0;
            for (size_t i = 0; i < data.size() - 1; ++i) {
              sum += data[i];
            }

            // Take the result modulo 256 to get the checksum
            uint8_t checksum = static_cast<uint8_t>(sum % 256);
            data[data.size() - 1] = checksum;

            ESP_LOGD("fake_tuya", "High: %d, Low: %d, Checksum: %d", high, low, checksum);

            return data;

light:
  - platform: monochromatic
    id: out
    name: "Dimmer"
    restore_mode: ALWAYS_OFF
    default_transition_length: 0s
    output: fake_tuya

number:
  - platform: template
    name: "Brightness Adjustment Interval"
    id: brightness_adjustment_interval
    min_value: 0.0
    max_value: 10.0
    initial_value: 5.0
    step: 0.5
    unit_of_measurement: "s"
    restore_value: yes
    optimistic: true
    icon: "mdi:timer-sand"
    entity_category: config

binary_sensor:
  - platform: gpio
    pin: P7
    id: button
    name: "Button"
    internal: true
    on_press:
      - script.execute: cycle
  - platform: gpio
    pin: P24
    id: dimmer
    name: "Dimmer"
    internal: true
    on_press:
      then:
        - light.dim_relative:
            id: out
            relative_brightness: -10%
  - platform: gpio
    pin: P26
    id: brighter
    name: "Bright"
    internal: true
    on_press:
      then:
        - light.dim_relative:
            id: out
            relative_brightness: 10%
  - platform: gpio
    pin:
      number: P6
      inverted: true
    id: dumb_switch
    name: "Dumb Switch"
    internal: true
    filters:
      - delayed_on_off: 100ms
    on_state:
        - if:
            condition:
              - lambda: "return id(boot_complete);"
            then:
              - script.execute: cycle

script:
  - id: cycle
    mode: restart
    then:
      if:
        condition:
          light.is_on: out
        then:
          - light.turn_off: out
        else:
          - lambda: |-
              uint32_t current_time = id(sntp_time).now().timestamp;
              float cycle_interval = id(brightness_adjustment_interval).state;

              auto call = id(out).turn_on();

              // Check if the switch is pressed within the cycle interval
              uint32_t time_difference = current_time - id(last_press_time);
              if (time_difference < cycle_interval) {
                // Switch the brightness index
                id(brightness_index) = (id(brightness_index) + 1) % 4;  // Cycle through brightness levels

                // Set brightness based on the profile
                if (id(brightness_index) == 0) {
                  call.set_brightness(1.0);
                  ESP_LOGD("cycle", "Brightness set to 100%");
                } else if (id(brightness_index) == 1) {
                  call.set_brightness(0.66);
                  ESP_LOGD("cycle", "Brightness set to 66%");
                } else if (id(brightness_index) == 2) {
                  call.set_brightness(0.33);
                  ESP_LOGD("cycle", "Brightness set to 33%");
                } else {
                  call.set_brightness(0.15);
                  ESP_LOGD("cycle", "Brightness set to 15%");
                }
              }

              call.perform();

              ESP_LOGI("cycle", "Time difference: %lu seconds, Brightness index: %d", time_difference, id(brightness_index));

              // Update the last press time
              id(last_press_time) = current_time;
2 Likes