Upgrade Costco Artika Skyshade LED Panel to Esp32 Esphome

by dmonty_ in Circuits > LEDs

423 Views, 0 Favorites, 0 Comments

Upgrade Costco Artika Skyshade LED Panel to Esp32 Esphome

esp32_costco_light.png

Upgrade Costco Artika Skyshade LED panel's wireless CBU chip to an esp32-C6. Flash the esp32 with esphome and integrate into Home Assistant. This allows for fast private LAN communication with the lights and Home Assistant automation. No special cloud apps required.

This project assumes you already have Home Assistant setup and know how to use ESPHome Builder.

Supplies

  1. Costco Artika Skyshade LED panel
  2. esp32-c6 - newer model
  3. dupont crimping tool
  4. dupont crimping end kit
  5. soldering iron, solder, flux
  6. heat shrink
  7. (optional hot air soldering)
  8. double sided foam tape
  9. wire strippers
  10. pliers
  11. Screwdriver & tiny precision screwdrivers
  12. small bowl to hold the screws

Flash the Esp32-C6 With EspHome

Screenshot_2025-09-16_21-17-11.png
Screenshot_2025-09-16_21-19-43.png

This tutorial assumes you have already setup Home Assistant and ESPHome Builder inside home assistant.

  1. Use ESPHome Builder inside Home Assistant to prepare the esp32-c6 board.
  2. Connect the Esp32-c6 board via usb to your computer and follow the wizard to prepare the board.
  3. Edit and upload the attached panel_light.txt configuration for the esp32-c6 board.
esphome:
name: esp32-c6v02-20250903-1
friendly_name: Pannel Light 1

esp32:
board: esp32-c6-devkitc-1
framework:
type: esp-idf

# Enable logging
logger:
#level: DEBUG
# Enable Home Assistant API
api:
encryption:
key: "s9P6ZwR5U6RJsE6ja9EVagvJoFzPZP/Rnncga6CeW5o="

ota:
- platform: esphome
password: "b1115e2eaf9b2215b509ea7925f5a45a"

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

# Enable fallback hotspot (captive portal) in case wifi connection fails
ap:
ssid: "Esp32-C6V02-20250903-1"
password: "8l30bPUavYB2"

captive_portal:
# PWM outputs for rgbct wires as per the order on the ribbon cable
# brightness, green, bue, red, temperature, negative, positive (3.3v written on the white cbu board)
output:
- platform: ledc
id: output_color_temp
pin: GPIO1 # White Temp
inverted: True
- platform: ledc
id: output_red
pin: GPIO7 # Red
- platform: ledc
id: output_blue
pin: GPIO6 # Blue
- platform: ledc
id: output_green
pin: GPIO5 # Green
- platform: ledc
id: output_white_brightness
pin: GPIO4 # White Brightness

# Light entity
light:
- platform: rgbct
id: panel_light
name: "Panel Light"
red: output_red
green: output_green
blue: output_blue
color_temperature: output_color_temp
white_brightness: output_white_brightness
cold_white_color_temperature: 5000 K
warm_white_color_temperature: 2700 K
color_interlock: True
default_transition_length: 500ms

- platform: esp32_rmt_led_strip
rgb_order: GRB
chipset: WS2812
pin: GPIO8
num_leds: 1
id: panel_neopixel
name: "Neopixel"

# Artika Skyshade Remote Control Configuration
remote_receiver:
pin:
number: GPIO10
inverted: true
dump: nec
on_nec:
# Power Button - 0xFA05
- if:
condition:
lambda: 'return (x.address == 0x7E7E && x.command == 0xFA05);'
then:
- logger.log: "Power button pressed"
- script.execute: power_pressed
# Red Button - 0xCD32
- if:
condition:
lambda: 'return (x.address == 0x7E7E && x.command == 0xCD32);'
then:
- logger.log: "Red button pressed"
- script.execute: red_pressed
# Green Button - 0xE51A
- if:
condition:
lambda: 'return (x.address == 0x7E7E && x.command == 0xE51A);'
then:
- logger.log: "Green button pressed"
- script.execute: green_pressed
# Blue Button - 0xCC33
- if:
condition:
lambda: 'return (x.address == 0x7E7E && x.command == 0xCC33);'
then:
- logger.log: "Blue button pressed"
- script.execute: blue_pressed
# White Button - 0xE41B
- if:
condition:
lambda: 'return (x.address == 0x7E7E && x.command == 0xE41B);'
then:
- logger.log: "White button pressed"
- script.execute: white_pressed
# Cycle Button - 0xCF30
- if:
condition:
lambda: 'return (x.address == 0x7E7E && x.command == 0xCF30);'
then:
- logger.log: "Cycle button pressed"
- script.execute: cycle_pressed
# Bright Button - 0xE619
- if:
condition:
lambda: 'return (x.address == 0x7E7E && x.command == 0xE619);'
then:
- logger.log: "Bright button pressed"
- script.execute: bright_pressed
# Dim Button - 0xCA35
- if:
condition:
lambda: 'return (x.address == 0x7E7E && x.command == 0xCA35);'
then:
- logger.log: "Dim button pressed"
- script.execute: dim_pressed
# Cool Button - 0xE916
- if:
condition:
lambda: 'return (x.address == 0x7E7E && x.command == 0xE916);'
then:
- logger.log: "Cool button pressed"
- script.execute: cool_pressed
# Warm Button - 0xC936
- if:
condition:
lambda: 'return (x.address == 0x7E7E && x.command == 0xC936);'
then:
- logger.log: "Warm button pressed"
- script.execute: warm_pressed
# Mode Button - 0xE718
- if:
condition:
lambda: 'return (x.address == 0x7E7E && x.command == 0xE718);'
then:
- logger.log: "Mode button pressed"
- script.execute: mode_pressed
# Favourite Button - 0xC837
- if:
condition:
lambda: 'return (x.address == 0x7E7E && x.command == 0xC837);'
then:
- logger.log: "Favourite button pressed"
- script.execute: favourite_pressed

# Global variables for state tracking
globals:
- id: current_hue
type: int
initial_value: '0'
- id: current_color_temp
type: int
restore_value: yes
initial_value: '6' # Middle value between 153-500 mireds (~3000K)
- id: warm_cool_toggle
type: bool
initial_value: '0'
# Script definitions for each button action
script:
- id: power_pressed
then:
- logger.log: "Power action triggered"
- light.toggle: panel_light
- id: red_pressed
then:
- logger.log: "Red color action triggered"
- light.turn_on:
id: panel_light
red: 80%
green: 0%
blue: 0%
- id: green_pressed
then:
- logger.log: "Green color action triggered"
- light.turn_on:
id: panel_light
red: 0%
green: 80%
blue: 0%
- id: blue_pressed
then:
- logger.log: "Blue color action triggered"
- light.turn_on:
id: panel_light
red: 0%
green: 0%
blue: 80%
- id: white_pressed
then:
- logger.log: "White color action triggered - Warm White"
- if:
condition:
lambda: 'return id(warm_cool_toggle);'
then:
- light.turn_on:
id: panel_light
color_temperature: 2700K
transition_length: 500ms
- lambda: |-
id(warm_cool_toggle) = false;
id(current_color_temp) = 0;
else:
- light.turn_on:
id: panel_light
color_temperature: 5000K
transition_length: 500ms
- lambda: |-
id(warm_cool_toggle) = true;
id(current_color_temp) = 6;
- id: cycle_pressed
then:
- logger.log: "Color cycle action triggered"
- lambda: |-
// Cycle through hue spectrum (0-360 degrees)
int hue = id(current_hue);
hue += 30; // Increment by 30 degrees each press
if (hue >= 360) hue = 0;
id(current_hue) = hue;
auto call = id(panel_light).turn_on();
call.set_rgb(1.0, 1.0, 1.0); // Full brightness
call.set_transition_length(500);
// Convert HSV to RGB
float h = hue / 60.0;
float c = 1.0; // Chroma at full saturation
float x = c * (1.0 - abs(fmod(h, 2.0) - 1.0));
float r, g, b;
if (h >= 0 && h < 1) { r = c; g = x; b = 0; }
else if (h >= 1 && h < 2) { r = x; g = c; b = 0; }
else if (h >= 2 && h < 3) { r = 0; g = c; b = x; }
else if (h >= 3 && h < 4) { r = 0; g = x; b = c; }
else if (h >= 4 && h < 5) { r = x; g = 0; b = c; }
else { r = c; g = 0; b = x; }
call.set_rgb(r, g, b);
call.perform();
ESP_LOGI("cycle", "Hue cycled to: %d degrees", hue);

- id: bright_pressed
then:
- logger.log: "Brightness increase action triggered"
- light.dim_relative:
id: panel_light
relative_brightness: 10%
- id: dim_pressed
then:
- logger.log: "Brightness decrease action triggered"
- light.dim_relative:
id: panel_light
relative_brightness: -10%

- id: cool_pressed
then:
- logger.log: "Cool white action triggered"
- lambda: |-
// Move towards cooler (higher kelvin values)
if (id(current_color_temp) < 6) {
id(current_color_temp) += 1;
}
int kelvin_values[] = {2700, 3000, 3500, 4000, 5000, 6500};
int current_kelvin = kelvin_values[id(current_color_temp) - 1];
int mireds = 1000000 / current_kelvin;
auto call = id(panel_light).turn_on();
call.set_color_temperature(mireds);
call.set_transition_length(500);
call.perform();
ESP_LOGD("main", "Color temp set to level %d (%dK)", id(current_color_temp), current_kelvin);

- id: warm_pressed
then:
- logger.log: "Warm white action triggered"
- lambda: |-
// Move towards warmer (lower kelvin values)
if (id(current_color_temp) > 1) {
id(current_color_temp) -= 1;
}
int kelvin_values[] = {2700, 3000, 3500, 4000, 5000, 6500};
int current_kelvin = kelvin_values[id(current_color_temp) - 1];
int mireds = 1000000 / current_kelvin;
auto call = id(panel_light).turn_on();
call.set_color_temperature(mireds);
call.set_transition_length(500);
call.perform();
ESP_LOGD("main", "Color temp set to level %d (%dK)", id(current_color_temp), current_kelvin);

- id: mode_pressed
then:
- logger.log: "Mode switch action triggered"
# Add mode switching logic here
- id: favourite_pressed
then:
- logger.log: "Favourite scene action triggered"
# Add favourite scene/preset logic here

# Optional: Binary sensors for Home Assistant integration
binary_sensor:
- platform: template
name: "Artika Remote Power"
id: remote_power_sensor
- platform: template
name: "Artika Remote Red"
id: remote_red_sensor
- platform: template
name: "Artika Remote Green"
id: remote_green_sensor
- platform: template
name: "Artika Remote Blue"
id: remote_blue_sensor
- platform: template
name: "Artika Remote White"
id: remote_white_sensor
- platform: template
name: "Artika Remote Cycle"
id: remote_cycle_sensor
- platform: template
name: "Artika Remote Bright"
id: remote_bright_sensor
- platform: template
name: "Artika Remote Dim"
id: remote_dim_sensor
- platform: template
name: "Artika Remote Cool"
id: remote_cool_sensor
- platform: template
name: "Artika Remote Warm"
id: remote_warm_sensor
- platform: template
name: "Artika Remote Mode"
id: remote_mode_sensor
- platform: template
name: "Artika Remote Favourite"
id: remote_favourite_sensor

# Restart button accessible via Home Assistant
button:
- platform: restart
name: "Restart Device"

Downloads

Disassemble the Light

20250907_211236.jpg
20250907_143246.jpg
20250907_143409.jpg
20250907_143514.jpg
20250907_143504.jpg
20250907_143520.jpg
20250907_144201.jpg
20250907_144208.jpg
  1. Use a small bowl to hold the screws. There are 2 types of screws and one ground screw.
  2. Flip the light upside down.
  3. Remove the silver mount plate.
  4. Note the location of the hanging bars and wire. Use a pen to mark the location.
  5. Remove all the screws, note there are two types. One set is for the mounting bars.
  6. Lift the power supply and remove the screw from the ground wire.
  7. Separate the diffuser/frame from the led panel.

Notes on Components

20250907_150059.jpg
20250907_150014.jpg
20250907_145808.jpg
20250907_144435.jpg
  1. There are RGB, Cool White, and Warm White LED lights.
  2. The CBU wifi micro-controller is soldered to a circuit board.
  3. The CBU wireless controller sends low voltage signals to the power supply which in turn sends higher voltage to the leds.
  4. The circuit board has a infrared receiver for the remote which we will re-use.
  5. NOTE: The circuit board has markings for the 3.3v, ground and rgb white light channels.

Remove the CBU Wireless Controller

20250907_150650.jpg
20250907_151544.jpg
20250907_151548.jpg
20250907_151743.jpg
20250907_152412.jpg
20250907_152604.jpg
20250907_152635.jpg
20250907_152651.jpg
  1. Remove the power supply wire from the CBU board by gently wiggling it.
  2. Remove the adhesive around the CBU board
  3. Use gentle heat and a plastic pry tool to remove the CBU board from the LED panel.

I decided I wanted to remove the CBU chip and re-use the infrared receiver with the new esp32

  1. Remove the adhesive around the inner CBU chip.
  2. There is one soldered pin that goes to the data pin of the infrared receiver and a row of soldered pins for: power ground RGB white-brightness and cool/warm white channels.
  3. First used a solder heat gun to remove the solder from the infrared receiver pin. If you don't have a hot air solder gun you can use a regular solder iron with solder wick or just cut the solder pads with a knife.
  4. Tip the board on it's side and heat the row of soldered pins till the inner CBU board drops off with gravity. I didn't want to pry up the solder pads.
  5. Use a multi-meter on ohms to confirm ground, power, and data traces going to to the infrared receiver. There are a few SMD components that are integrated into the infrared receiver that I wanted to use so I decided to power the receiver at from the main 3.3v and ground jumper area.

Add Dupont Ends to the Power Supply Wire

20250907_154906.jpg
20250907_152812.jpg
20250907_152914.jpg
20250907_155055.jpg
20250907_155301.jpg
20250907_155313.jpg
20250907_160912.jpg
  1. Use pliers to un-clip side of the data cable clip. Pull some slack through the hole so the wire is easier to work with.
  2. Use a tiny screwdriver to release the plastic from the existing pins.
  3. Use wire cutters to remove the existing connectors off the wire.
  4. NOTE: if you haven't crimped dupont cables before, you will want to first practice before working with this wire.
  5. Solder the gpio headers onto the esp32-c6
  6. For the row with 3.3v ground and gpio pins 4,5,6,7 etc remove the plastic off the pins and bend them 90 degrees away from the board. This way the wires connect directly onto the pins.
  7. As per the markings on the CBU board the 1st two wires are 3.3v and ground. I put a 2-pin female connector on these two.
  8. Crimp single dupont female connector on the next wire to connect to gpio 1.
  9. Crimp a 4-pin dupont female on the next 4 wires in sequence connect to gpio 4,5,6,7. All the gpio pins and wires should be flat and connect straight onto the board without crossing.
  10. [Skip gpio 0 as it is a strapping pin]

Tape the Esp32 to the LED Pannel

20250907_160912.jpg
20250907_191632.jpg
20250907_191940.jpg
20250907_194637b.jpg
  1. Pull the excess wire back through the hole to the power supply and re-clip it in place. The existing bends in the wire should assist in getting the wire back to where it was.
  2. Estimate where the ESP32-C6 will be placed so that the wires do not block the leds and at the same time are stretched out nicely.
  3. Apply double sided foam tape in the low spot of the led panel divot.
  4. Apply 2 layers of double sided foam to the bottom of the esp32 in order to stop the pins from resting on the metal led panel.
  5. Stick the esp32 to the panel - again ensuring the wires go between the leds nicely without blocking the light.

Prepare Power Wires for Esp32 and Infrared Receiver

20250907_193130.jpg
20250907_193938.jpg
20250907_194818.jpg
20250907_194637.jpg
20250907_205351.jpg
20250907_204119.jpg
  1. Solder and heat shrink 3 wires together for 3.3v (red). Two wires point the same direction.
  2. Repeat step 1 for black ground.
  3. On the infrared circuit board:
  4. Solder the 3.3v wire to the 3.3v pin. (red)
  5. Solder the ground wire to the ground pin. (black)
  6. Solder the gpio data wire (yellow) to the infrared receiver data pin.
  7. After finding an optimal location for the infrared circuit board. Apply foam tape to the infrared circuit board and stick it down to the led panel.
  8. Measure the yellow wire to gpio10. Trim the wire and add a dupont female connector.
  9. Measure the red wire to the esp32 3.3v pin. Trim the wire.
  10. Measure the black wire to the esp32 ground pin. Trim the wire.
  11. Add a 2-pin female dupont connector to the red and black wires.
  12. Measure the last red and black wires to attach to the power supply 3.3v and ground. Trim the wires and add a male 2-pin dupont connector.
  13. Connect all the wires.
  14. Temporarily and carefully connect the light to a plug using the orange marrettes provided with the light.
  15. Warning: this is high voltage. Connect the light through a power bar with built in breaker.
  16. Test the light using Home Assistant and the Infrared Remote. If the gpio pins and wires are in the same order everything should work. If not then adjust the sketch gpio pin numbers to match the wiring.
  17. Once tested - use hot glue to ensure the boards and wires are well organised and stay in place.

Re-assemble the Light Panel

20250907_205604.jpg
20250907_205816.jpg
20250907_143504.jpg
20250907_211205.jpg
20250907_143514.jpg
20250907_143409.jpg
  1. Place the led diffuser-frame back around the led panel.
  2. Connect the ground screw first under the power supply with the single screw with the flat brim.
  3. Carefully seat the power supply and secure it with two square-head screws.
  4. Align the one hanging bar next to the power supply and line up the holes. You may have marked this earlier.
  5. Hook the hanging wire to the hanging bar using an oval-head screw then attach the hanging bar with the oval-head screws. Start with the 2nd ground wire attached where it is marked.
  6. Repeat for the 2nd hanging bar.
  7. Screw in the rest of square-head screws go around the edge of the frame.
  8. Follow the instructions that came with the light to hang it to your roof.

Customize Your New Lights in Home Assistant

Now is when you can customise and automate these lights in home assistant. Here are some ideas:

  1. Integrate motion and door sensors to have the lights come on automatically.
  2. Integrate wall switches, touch panels, tablet/phone/pc dashboards to control your lights.
  3. Setup automation scripts and scenes for things like: board game night, study time, theatre lights, bedtime, morning time.
  4. The esp32-c6 has a neopixel. Setup timers or motion sensors to work as night-lights to dimly light the room when moving around in the dark.
  5. Customise and add your own 3.3v sensors to your esp32-c6.
  6. The esp32-c6 supports newer wireless technologies like matter, thread and zigbee. You can experiment with these protocols.
  7. Note1: I find cutting the AC mains power to the light or spamming the power light-switch can damage the electronics in these lights. It is best to leave the power on and use the infrared remote or home assistant to control the lights.
  8. Note2: I've found using static IPs for these esp32 devices makes flashing them more responsive than the default mdns.