Casalux Smart RGBW LED-strip with IR-remote (47278 + 852592)
Device Type:
Electrical Standard:
Board:
Difficulty:Soldering required (4/5)
Casalux Smart RGBW LED-strip with IR-remote (47278 + 852592)
5m 24V RGBW LED strip with 150 LEDS, controller, IR-remote control, and power supply. The packaging claims 11W power usage and 800lm brightness.
Out of the box supported by Tuya app.
The custom PCB contains a BK7231N, IR receiver, and a push button.
To flash, the device needs to be opened and wires connected to the clearly marked points.
For example, ltchiptool seems to work for the task.
GPIO Pinout
| Pin | Function |
|---|---|
| GPIO06 | PWM Red channel |
| GPIO08 | PWM Blue channel |
| GPIO09 | PWM White channel |
| GPIO20 | Push button |
| GPIO24 | PWM Green channel |
| GPIO26 | IR receiver |
Basic Config
Config including only hardware bindings.
substitutions: device_name: casalux-led-strip friendly_name: Casalux LED Strip
esphome: name: ${device_name} friendly_name: ${friendly_name}
# BK7231N chip configurationbk72xx: board: generic-bk7231n-qfn32-tuya
# Enable logginglogger: level: WARN logs: light: WARN remote_receiver: WARN
# Enable Home Assistant APIapi: encryption: key: ""
ota: - platform: esphome password: ""
wifi: ssid: !secret wifi_ssid password: !secret wifi_password
# Enable fallback hotspot (captive portal) in case wifi connection fails ap: ssid: "${friendly_name} Fallback" password: ""
captive_portal:
web_server: port: 80
# Physical button on GPIO20binary_sensor: - platform: gpio pin: number: P20 mode: INPUT_PULLUP inverted: true name: "${friendly_name} Button" id: physical_button
# IR Receiver on GPIO26remote_receiver: pin: number: P26 inverted: true mode: INPUT dump: nec # Error tolerances tolerance: type: percentage value: 40 idle: 10ms
# PWM outputs for RGBWoutput: # Red - GPIO6 - platform: libretiny_pwm id: pwm_red pin: P6 frequency: 1000 Hz inverted: false
# Green - GPIO24 - platform: libretiny_pwm id: pwm_green pin: P24 frequency: 1000 Hz inverted: false
# Blue - GPIO8 - platform: libretiny_pwm id: pwm_blue pin: P8 frequency: 1000 Hz inverted: false
# White - GPIO9 - platform: libretiny_pwm id: pwm_white pin: P9 frequency: 1000 Hz inverted: false
# Main RGBW Lightlight: - platform: rgbw name: "${friendly_name}" id: rgbcw_light red: pwm_red green: pwm_green blue: pwm_blue white: pwm_white
color_interlock: false # Allow RGB + white mixing default_transition_length: 0s restore_mode: RESTORE_DEFAULT_ON gamma_correct: 2.8
# Diagnostic sensorssensor: - platform: uptime name: "${friendly_name} Uptime"
- platform: wifi_signal name: "${friendly_name} WiFi Signal" update_interval: 60s
text_sensor: - platform: wifi_info ip_address: name: "${friendly_name} IP Address" ssid: name: "${friendly_name} Connected SSID"Full config
The config below attempts to recreate the full IR remote control functionality, including the scenes.
substitutions: device_name: casalux-led-strip friendly_name: Casalux LED Strip
esphome: name: ${device_name} friendly_name: ${friendly_name}
# BK7231N chip configurationbk72xx: board: generic-bk7231n-qfn32-tuya
# Enable logginglogger: level: WARN logs: light: WARN remote_receiver: WARN
# Enable Home Assistant APIapi: encryption: key: ""
ota: - platform: esphome password: ""
wifi: ssid: !secret wifi_ssid password: !secret wifi_password
# Enable fallback hotspot (captive portal) in case wifi connection fails ap: ssid: "${friendly_name} Fallback" password: ""
captive_portal:
web_server: port: 80
# Globals for state managementglobals: # Track effect speed - id: effect_speed type: int restore_value: yes initial_value: '100'
# Track current scene - id: current_scene type: int restore_value: yes initial_value: '0'
# Physical button on GPIO20binary_sensor: - platform: gpio pin: number: P20 mode: INPUT_PULLUP inverted: true name: "${friendly_name} Button" id: physical_button filters: - delayed_on: 10ms # debounce on_click: # Short press - toggle light - min_length: 50ms max_length: 1s then: - light.toggle: rgbcw_light - logger.log: "Button: Toggle light"
# Long press 5 seconds - factory reset - min_length: 5s max_length: 10s then: - logger.log: "Button: Factory reset to defaults" - lambda: |- // Reset to defaults auto call = id(rgbcw_light).make_call(); call.set_state(true); call.set_brightness(0.50); call.set_white(1.0); call.set_rgb(0.0, 0.0, 0.0); call.perform();
// Reset effect speed to default id(effect_speed) = 100; id(effect_speed_control).publish_state(100);
# IR Receiver on GPIO26remote_receiver: pin: number: P26 inverted: true mode: INPUT dump: nec # Error tolerances tolerance: type: percentage value: 40 idle: 10ms
# Handle NEC protocol with customcode: 239 (0xEF) on_nec: then: - lambda: |- // NEC address is 0xEF00 (customcode 239 in high byte) if (x.address == 0xEF00) { id(handle_ir_command).execute(x.command); } else { ESP_LOGD("IR", "Ignoring NEC code from address 0x%02X", x.address); }
# PWM outputs for RGBWoutput: # Red - GPIO6 - platform: libretiny_pwm id: pwm_red pin: P6 frequency: 1000 Hz inverted: false
# Green - GPIO24 - platform: libretiny_pwm id: pwm_green pin: P24 frequency: 1000 Hz inverted: false
# Blue - GPIO8 - platform: libretiny_pwm id: pwm_blue pin: P8 frequency: 1000 Hz inverted: false
# White - GPIO9 - platform: libretiny_pwm id: pwm_white pin: P9 frequency: 1000 Hz inverted: false
# Main RGBW Lightlight: - platform: rgbw name: "${friendly_name}" id: rgbcw_light red: pwm_red green: pwm_green blue: pwm_blue white: pwm_white
color_interlock: false # Allow RGB + white mixing default_transition_length: 0s restore_mode: RESTORE_DEFAULT_ON gamma_correct: 2.8
effects: # Flash effect - lambda: name: "Flash" update_interval: 50ms lambda: |- static bool flash_bright = false; static uint32_t last_toggle = 0; uint32_t now = millis(); uint32_t interval = 500 * (100.0 / id(effect_speed));
if (now - last_toggle >= interval) { auto call = id(rgbcw_light).make_call(); flash_bright = !flash_bright;
// Don't turn off state - just change brightness! if (flash_bright) { call.set_brightness(0.85); } else { call.set_brightness(0.10); // dim, not off }
call.perform(); last_toggle = now; }
# Strobe effect - lambda: name: "Strobe" update_interval: 20ms lambda: |- static bool strobe_bright = false; static uint32_t last_toggle = 0; uint32_t now = millis(); uint32_t interval = 100 * (100.0 / id(effect_speed));
if (now - last_toggle >= interval) { auto call = id(rgbcw_light).make_call(); strobe_bright = !strobe_bright;
// Don't turn off state - just change brightness! if (strobe_bright) { call.set_brightness(1.0); // Full bright } else { call.set_brightness(0.10); // dim, not off }
call.perform(); last_toggle = now; }
# Fade effect - lambda: name: "Fade" update_interval: 50ms lambda: |- static float brightness = 0.1; static bool up = true;
auto call = id(rgbcw_light).make_call();
brightness += up ? 0.01 : -0.01; if (brightness >= 0.85) up = false; if (brightness <= 0.1) up = true;
call.set_brightness(brightness); call.set_white(0.0);
call.perform();
# Smooth effect - lambda: name: "Smooth" update_interval: 50ms lambda: |- static int pos = 0; static uint32_t last_update = 0; uint32_t now = millis(); uint32_t interval = 50 * (100.0 / id(effect_speed));
if (now - last_update < interval) { return; } last_update = now;
auto call = id(rgbcw_light).make_call(); pos = (pos + 1) % 360; float brightness = 0.85; float hue = pos / 360.0; float r, g, b;
if (hue < 1.0/6.0) { r = brightness; g = brightness * (hue * 6.0); b = 0; } else if (hue < 2.0/6.0) { r = brightness * (1 - (hue - 1.0/6.0) * 6.0); g = brightness; b = 0; } else if (hue < 3.0/6.0) { r = 0; g = brightness; b = brightness * ((hue - 2.0/6.0) * 6.0); } else if (hue < 4.0/6.0) { r = 0; g = brightness * (1 - (hue - 3.0/6.0) * 6.0); b = brightness; } else if (hue < 5.0/6.0) { r = brightness * ((hue - 4.0/6.0) * 6.0); g = 0; b = brightness; } else { r = brightness; g = 0; b = brightness * (1 - (hue - 5.0/6.0) * 6.0); }
call.set_rgb(r, g, b); // Turn off white for color effects call.set_white(0.0); call.perform();
# Number inputs for user controlnumber: # Effect speed control - platform: template name: "${friendly_name} Effect Speed" id: effect_speed_control min_value: 20 max_value: 200 step: 5 mode: slider optimistic: true initial_value: 100 on_value: - globals.set: id: effect_speed value: !lambda 'return (int)x;'
# Select for effect/mode controlselect: # Current effect selector - platform: template name: "${friendly_name} Effect" id: effect_selector options: - "None" - "Flash" - "Strobe" - "Fade" - "Smooth" initial_option: "None" optimistic: true on_value: - lambda: |- auto call = id(rgbcw_light).make_call(); std::string effect = x;
if (effect == "None") { call.set_effect("None"); } else { call.set_effect(effect); } call.perform();
script: # IR Remote command handler - id: handle_ir_command mode: restart parameters: code: int then: - lambda: |- ESP_LOGD("IR", "Received NEC code: 0x%02X (%d)", code, code);
auto call = id(rgbcw_light).make_call(); auto current = id(rgbcw_light).current_values; float brightness = current.get_brightness();
// Helper: set color auto set_color = [&](float r, float g, float b, const char* name) { call.set_state(true); call.set_rgb(r, g, b); call.set_white(0.0); call.set_effect("None"); ESP_LOGD("IR", "%s", name); };
// IR key mapping switch(code) { // Power case 0xFC03: // ON call.set_state(true); ESP_LOGD("IR", "Power ON"); break;
case 0xFD02: // OFF call.set_state(false); ESP_LOGD("IR", "Power OFF"); break;
// Brightness case 0xFF00: // Brightness UP brightness = min(1.0f, brightness + 0.05f); call.set_brightness(brightness); ESP_LOGD("IR", "Brightness UP: %.0f%%", brightness * 100); break;
case 0xFE01: // Brightness DOWN brightness = max(0.10f, brightness - 0.05f); call.set_brightness(brightness); ESP_LOGD("IR", "Brightness DOWN: %.0f%%", brightness * 100); break;
// Pure colors case 0xFB04: // Red 100% set_color(1.0, 0.0, 0.0, "Red 100%"); break;
case 0xFA05: // Green 100% set_color(0.0, 1.0, 0.0, "Green 100%"); break;
case 0xF906: // Blue 100% set_color(0.0, 0.0, 1.0, "Blue 100%"); break;
case 0xF807: // White 100% call.set_state(true); call.set_rgb(0.0, 0.0, 0.0); // Always turn off RGB for white call.set_white(1.0); call.set_effect("None"); ESP_LOGD("IR", "White 100%%"); break;
// Red-Yellow gradient case 0xF708: // Yellow 25% + Red 75% set_color(1.0, 0.25, 0.0, "Red 75% + Yellow 25%"); break;
case 0xF30C: // Yellow 50% + Red 50% set_color(1.0, 0.5, 0.0, "Red 50% + Yellow 50%"); break;
case 0xEF10: // Yellow 75% + Red 25% set_color(1.0, 0.75, 0.0, "Red 25% + Yellow 75%"); break;
case 0xEB14: // Yellow 100% set_color(1.0, 1.0, 0.0, "Yellow 100%"); break;
// Green-Cyan gradient case 0xF609: // Cyan 25% + Green 75% set_color(0.0, 1.0, 0.25, "Green 75% + Cyan 25%"); break;
case 0xF20D: // Cyan 50% + Green 50% set_color(0.0, 1.0, 0.5, "Green 50% + Cyan 50%"); break;
case 0xEE11: // Cyan 75% + Green 25% set_color(0.0, 1.0, 0.75, "Green 25% + Cyan 75%"); break;
case 0xEA15: // Cyan 100% set_color(0.0, 1.0, 1.0, "Cyan 100%"); break;
// Blue-Magenta gradient case 0xF50A: // Magenta 25% + Blue 75% set_color(0.25, 0.0, 1.0, "Blue 75% + Magenta 25%"); break;
case 0xF10E: // Magenta 50% + Blue 50% set_color(0.5, 0.0, 1.0, "Blue 50% + Magenta 50%"); break;
case 0xED12: // Magenta 75% + Blue 25% set_color(0.75, 0.0, 1.0, "Blue 25% + Magenta 75%"); break;
case 0xE916: // Magenta 100% set_color(1.0, 0.0, 1.0, "Magenta 100%"); break;
// Effects case 0xF40B: // Flash effect call.set_state(true); call.set_effect("Flash"); id(effect_selector).publish_state("Flash"); ESP_LOGD("IR", "Effect: Flash"); break;
case 0xF00F: // Strobe effect call.set_state(true); call.set_effect("Strobe"); id(effect_selector).publish_state("Strobe"); ESP_LOGD("IR", "Effect: Strobe"); break;
case 0xEC13: // Fade effect call.set_state(true); call.set_effect("Fade"); id(effect_selector).publish_state("Fade"); ESP_LOGD("IR", "Effect: Fade"); break;
case 0xE817: // Smooth effect call.set_state(true); call.set_effect("Smooth"); id(effect_selector).publish_state("Smooth"); ESP_LOGD("IR", "Effect: Smooth"); break;
default: ESP_LOGW("IR", "Unknown IR code: 0x%02X", code); return; }
call.perform();
# Diagnostic sensorssensor: - platform: uptime name: "${friendly_name} Uptime"
- platform: wifi_signal name: "${friendly_name} WiFi Signal" update_interval: 60s
text_sensor: - platform: wifi_info ip_address: name: "${friendly_name} IP Address" ssid: name: "${friendly_name} Connected SSID"