Category: Uncategorized

  • XCRhom.tech RGB Bulbs

    Table of Contents

    I bought some more ESPHome RGB bulbs from AliExpress (hey, the US is unpredictable right now in terms of tariffs so I’m avoiding purchasing from them until this “trade war” has gone away). They came in as XCRHom.tech RGB bulbs (great name, definitely not a typo) and they have a less than useful website for well, anything https://xcrhom.tech/

    There’s no details on what the actual config should be, the pinouts or anything like that.

    First Programming attempt

    I found these bulbs didn’t like connecting to my usual “IOT” network, which I suspect was due to the preinstalled firmware and the fact I use the same SSID for 2.4 and 5GHz (maybe I should change that at some point), so I used my IOS(hit) network which is just 2.4GHz. And that worked fine. From there I could hit them with a config file.

    Since they seemed very similar to my existing Athom bulbs, I used that config file, but it didn’t work that great

    • On/Off  ✅
    • Setting to White  ❌ The bulb went Blue
    • Setting to Red ❌ The bulb went White
    • Setting to Blue❌ The bulb went Green
    • Setting to Green ❌ The bulb went Red

    So this was less then awesome, but pretty easy to reorder the inputs

    Space issues

    The next issue I encountered was lack of space on reflashing, this was because it couldn’t store the new firmware to update to when it was running the previously programmed firmware. This wasn’t an issue with the first flash as the “staged” basic firmware takes up very little space.

    Since the athom bulbs came with a preprogrammed NTP config, which I didn’t really need, I just deleted that from the configuration file and it bought the size down enough to program without any issues

    Configuration files

    As with my previous configurations, I use a combination of files.

    xcrhom-rgb-1.yaml

    (repeat this file for each bulb just changing the file name and number)

    substitutions:
      number: "1"
      friendly_name: "XCRhome RGBCW Bulb ${number}"
      # Allows ESP device to be automatically linked to an 'Area' in Home Assistant. Typically used for areas such as 'Lounge Room', 'Kitchen' etc
      room: ""
      # Description as appears in ESPHome & top of webserver page
      device_description: "xcrhom 7w rgbcw light bulb"
      # Restore the light (GPO switch) upon reboot to state:
      light_restore_mode: RESTORE_DEFAULT_ON
      # Enables faster network connections, with last connected SSID being connected to and no full scan for SSID being undertaken
      wifi_fast_connect: "false"
      # Enable or disable the use of IPv6 networking on the device
      ipv6_enable: "false"
      color_interlock: "true"
    
    <<: !include common/xcrhom-rgbct.yaml

    xcrhom-rgb.yaml

    # Put these in the parent file
    # substitutions:
    #   number:
    #   friendly_name: "XCRHom RGBCW Bulb ${number}"
    #   # Allows ESP device to be automatically linked to an 'Area' in Home Assistant. Typically used for areas such as 'Lounge Room', 'Kitchen' etc
    #   room: ""
    #   # Description as appears in ESPHome & top of webserver page
    #   device_description: "XCR 7w rgbcw light bulb"
    #   # Restore the light (GPO switch) upon reboot to state:
    #   light_restore_mode: RESTORE_DEFAULT_ON
    #   # Define a domain for this device to use. i.e. iot.home.lan (so device will appear as athom-smart-plug-v2.iot.home.lan in DNS/DHCP logs)
    #   dns_domain: ".local"
    #   # Enables faster network connections, with last connected SSID being connected to and no full scan for SSID being undertaken
    #   wifi_fast_connect: "false"
    #   # Enable or disable the use of IPv6 networking on the device
    #   ipv6_enable: "false"
    #   color_interlock: "true"
    
    packages:
      restore_light_state: !include restore_light_state.yaml
      common: !include common.yaml
    
    esphome:
      name: "xcrhome-rgbcw-bulb-${number}"
      # Default friendly name
      friendly_name: "${friendly_name}"
      comment: "${device_description}"
      area: "${room}"
      name_add_mac_suffix: false
      min_version: 2024.6.0
    
    esp8266:
      board: esp8285
      restore_from_flash: true
    
    preferences:
      flash_write_interval: 1min
    
    binary_sensor:
      - platform: status
        name: "Status"
        entity_category: diagnostic
    
    sensor:
      - platform: uptime
        name: "Uptime Sensor"
        id: uptime_sensor
        entity_category: diagnostic
        internal: true
    
      - platform: wifi_signal
        name: "WiFi Signal dB"
        id: wifi_signal_db
        update_interval: 60s
        entity_category: "diagnostic"
    
    button:
      - platform: restart
        name: "Restart"
        entity_category: config
    
      - platform: safe_mode
        name: "Safe Mode"
        internal: false
        entity_category: config
    
    output:
      - platform: esp8266_pwm
        id: white_output
        pin: GPIO4
        min_power: 0.000499
        max_power: 1
      - platform: esp8266_pwm
        id: red_output
        pin: GPIO12
        min_power: 0.000499
        max_power: 1
      - platform: esp8266_pwm
        id: green_output
        pin: GPIO14
        min_power: 0.000499
        max_power: 1
      - platform: esp8266_pwm
        id: warm_white_output
        pin: GPIO13
        min_power: 0.000499
        max_power: 0.9
      - platform: esp8266_pwm
        id: blue_output
        pin: GPIO5
        min_power: 0.000499
        max_power: 0.9
    
    light:
      - platform: rgbww
        id: rgbww_light
        name: "RGBCW_Bulb"
        restore_mode: ${light_restore_mode}
        red: red_output
        green: green_output
        blue: blue_output
        warm_white: warm_white_output
        cold_white: white_output
        cold_white_color_temperature: 6000 K
        warm_white_color_temperature: 3000 K
        color_interlock: ${color_interlock}
    
    text_sensor:
      - platform: wifi_info
        ip_address:
          name: "IP Address"
          entity_category: diagnostic
        ssid:
          name: "Connected SSID"
          entity_category: diagnostic
        mac_address:
          name: "Mac Address"
          entity_category: diagnostic
    
      #  Creates a sensor showing when the device was last restarted
      - platform: template
        name: "Last Restart"
        id: device_last_restart
        icon: mdi:clock
        entity_category: diagnostic
    

    common.yaml

    substitutions:
      wifi_fast_connect: "false"
    
    # Common configuration to setup defaults
    logger:
    
    web_server:
    
    captive_portal:
    
    mdns:
    
    api:
    
    ota:
      platform: esphome
    
    wifi:
      ssid: !secret wifi_ssid
      password: !secret wifi_password
      ap:
      power_save_mode: none
      # Allow rapid re-connection to previously connect WiFi SSID, skipping scan of all SSID
      fast_connect: "${wifi_fast_connect}"
    

  • De-duplicating ESPHome configuration files

    It's time to de-duplicate and tidy up some of my ESPHome Configs
    This doc is broken down into a few sections, the explanation for why, the shared configurations, and finally my device type configurations, this last one has usage docs.

    Update 17th April 2025: restore_switch_state.yaml no longer uses method call/perform, I think this was always broken, but it was compiled and installed fine until ESPHome 2025.4.0

    Table of Contents

    Why de-duplicate?

    I want to make it easier to maintain and apply changes and keep configurations consistent, with my previous blog posts I had things working but they were inconsistent (because I pulled configs from t’internet)

    The issues I had were

    • Not all devices restored state, some of my bulbs did, none of my switches did, when power was restored
    • I had inconsistent IDs based on where configuration files came from (rgbww_light vs light_rgbww)
    • I wanted more consistency across the formatting
    • If I decided to add a configuration option in the future, I didn’t want to add it to 20+ devices by hand

    Research

    I found an initial “simple” solution from the ESPHome documentation, the YAML insertion operator

    The issues with this is it literally just adds the content to the current definition, so you can’t have duplicate sections

    If your main yaml has

    substitutions:
      some_var: value
    
    <<: !include common.yaml

    And the common.yaml has

    substitutions:
      other_var: other value

    You’ll get errors, this wasn’t great for my reduce, reuse, recycle plan.

    So then I found out about packages, this is exactly what I wanted since you merge the files this way.

    The format here is very similar, but also a bit odd (I wanted to know more about the naming and if I could reference things, but no luck)

    packages:
      common: !include common.yaml
      restore_light_state: !include restore_light_state.yaml

    Then my files are well configured (below to save repitition) to reduce the duplication

    Shared configurations

    These aren’t really device specific but are building blocks for the devices

    common.yaml

    This file defines my ESPHome configs that are common across all my devices

    substitutions:
      wifi_fast_connect: "false"
    
    # Common configuration to setup defaults
    logger:
    
    web_server:
    
    captive_portal:
    
    mdns:
    
    api:
    
    ota:
      platform: esphome
    
    wifi:
      ssid: !secret wifi_ssid
      password: !secret wifi_password
      ap:
      # Allow rapid re-connection to previously connect WiFi SSID, skipping scan of all SSID
      fast_connect: "${wifi_fast_connect}"
    

    restore_light_state.yaml

    This restores the light state based on the individual device configuration, my original configs had light_rgbww or rgbww_light as the light id, which I didn’t like!

    # Provides directive to allow the light state to be restored from the previous mode
    
    esphome:
      on_boot:
        then:
          - script.execute: fast_boot_script
          - select.set_index:
              id: power_mode
              index: !lambda |-
                return id(restore_mode)-1;
          - lambda: |-
              switch(id(restore_mode))
              {
              case 1:{
                      auto call = id(rgbww_light).turn_off();
                      call.perform();
                      break;
                      }
              case 2:{
                      auto call = id(rgbww_light).turn_on();
                      call.set_color_mode(ColorMode::WHITE);
                      call.set_brightness(1.0);
                      call.perform();
                      break;
                      }
              default:{
                      break;
                      }
              }
    
    button:
      - platform: factory_reset
        name: "Factory Reset"
        id: Reset
        entity_category: config
    globals:
      - id: fast_boot
        type: int
        restore_value: yes
        initial_value: "0"
    
      - id: restore_mode
        type: int
        restore_value: yes
        initial_value: "1"
    
    select:
      - platform: template
        name: "Power On State"
        id: "power_mode"
        optimistic: true
        options:
          - Always Off
          - Always On
          - Restore Power Off State
        on_value:
          then:
            - lambda: |-
                id(restore_mode)=i+1;
    script:
      - id: fast_boot_script
        then:
          - if:
              condition:
                lambda: return ( id(fast_boot) >= 3 );
              then:
                - lambda: |-
                    ESP_LOGD("${friendly_name}", "Now the counter is greater than or equal to 3, perform reset device and reboot");
                    id(fast_boot) = 0;
                    fast_boot->loop();
                    global_preferences->sync();
                    auto call = id(rgbww_light).turn_on();
                    call.set_transition_length(500);
                    call.set_brightness(1.0);
                    call.set_color_mode(ColorMode::RGB);
                    call.set_rgb(0.0, 0.0, 1.0);
                    call.perform();
                - delay: 5s
                - button.press: Reset
          - lambda: |-
              id(fast_boot) += 1;
              fast_boot->loop();
              global_preferences->sync();
              ESP_LOGD("${friendly_name}", "Now the counter is %d.  Reset the device when the counter is greater than or equal to 3", id(fast_boot));
          - delay: 10s
          - lambda: |-
              ESP_LOGD("${friendly_name}", "Reset counter");
              id(fast_boot) = 0;
              fast_boot->loop();
              global_preferences->sync();
    

    restore_switch_state.yaml

    Similar to restore light state, but for outlets! I could probably deduplicate some more, but this is fine for me for now

    Updated 2025/04/17

    # Provides directive to allow the switch state to be restored from the previous mode
    
    esphome:
      on_boot:
        then:
          - script.execute: fast_boot_script
          - select.set_index:
              id: power_mode
              index: !lambda |-
                return id(restore_mode)-1;
          - lambda: |-
              switch(id(restore_mode))
              {
              case 1:{
                      id(outlet).turn_off();
                      break;
                      }
              case 2:{
                      id(outlet).turn_on();
                      break;
                      }
              default:{
                      break;
                      }
              }
    
    globals:
      - id: fast_boot
        type: int
        restore_value: yes
        initial_value: "0"
    
      - id: restore_mode
        type: int
        restore_value: yes
        initial_value: "1"
    
    button:
      - platform: factory_reset
        name: "Factory Reset"
        id: Reset
        entity_category: config
    
    select:
      - platform: template
        name: "Power On State"
        id: "power_mode"
        optimistic: true
        options:
          - Always Off
          - Always On
          - Restore Power Off State
        on_value:
          then:
            - lambda: |-
                id(restore_mode)=i+1;
    script:
      - id: fast_boot_script
        then:
          - if:
              condition:
                lambda: return ( id(fast_boot) >= 3 );
              then:
                - lambda: |-
                    ESP_LOGD("${friendly_name}", "Now the counter is greater than or equal to 3, perform reset device and reboot");
                    id(fast_boot) = 0;
                    fast_boot->loop();
                    global_preferences->sync();
                    id(outlet).turn_on();
                - delay: 5s
                - button.press: Reset
          - lambda: |-
              id(fast_boot) += 1;
              fast_boot->loop();
              global_preferences->sync();
              ESP_LOGD("${friendly_name}", "Now the counter is %d.  Reset the device when the counter is greater than or equal to 3", id(fast_boot));
          - delay: 10s
          - lambda: |-
              ESP_LOGD("${device_name}", "Reset counter");
              id(fast_boot) = 0;
              fast_boot->loop();
              global_preferences->sync();
    

    Devices

    These are the device specific files, I add a quick and easy section to copy-and-paste to new devices if I need to re-use or share them with others

    globe-50323.yaml

    This is my core definition for the Globe 50232 pot lights

    ## Configuration for Global 50323 Pot Lights
    # substitutions:
    #   number: "4"
    
    # <<: !include common/globe-50323.yaml
    
    packages:
      common: !include common.yaml
      restore_light_state: !include restore_light_state.yaml
    
    esphome:
      name: "globe-50323-rgbct-recessed-${number}"
      friendly_name: "Globe Lighting 50323 Recessed RGBCT Light (${number})"
    
    bk72xx:
      board: generic-bk7231t-qfn32-tuya
    
    text_sensor:
      - platform: libretiny
        version:
          name: LibreTiny Version
    
    output:
      - platform: libretiny_pwm
        id: output_red
        pin: P8
      - platform: libretiny_pwm
        id: output_green
        pin: P7
      - platform: libretiny_pwm
        id: output_blue
        pin: P6
      - platform: libretiny_pwm
        id: output_cold
        pin: P26
      - platform: libretiny_pwm
        id: output_warm
        pin: P24
    
    light:
      - platform: rgbww
        id: rgbww_light
        name: Light
        color_interlock: true
        cold_white_color_temperature: 6500 K
        warm_white_color_temperature: 2700 K
        red: output_red
        green: output_green
        blue: output_blue
        cold_white: output_cold
        warm_white: output_warm
    

    globe-rgb.yaml

    This defines my Globe RGB WW Bulbs

    # Copy these to the parent
    # substitutions:
    #   light_restore_mode: RESTORE_DEFAULT_ON
    #   number: "X"
    #
    # <<: !include common/globe-rgb.yaml
    
    packages:
      restore_light_state: !include restore_light_state.yaml
      common: !include common.yaml
    
    esphome:
      friendly_name: "Globe RGB ${number}"
      name: "globe-rgb-${number}"
    
    bk72xx:
      board: generic-bk7231t-qfn32-tuya
    
    text_sensor:
      - platform: libretiny
        version:
          name: LibreTiny Version
    
    sm2135:
      clock_pin: P8
      data_pin: P26
      rgb_current: 20mA
      cw_current: 55mA
    
    output:
      - platform: sm2135
        id: output_red
        channel: 2
      - platform: sm2135
        id: output_green
        channel: 1
      - platform: sm2135
        id: output_blue
        channel: 0
      - platform: sm2135
        id: output_cold
        channel: 4
      - platform: sm2135
        id: output_warm
        channel: 3
    
    light:
      - platform: rgbww
        id: rgbww_light
        name: Light
        restore_mode: ${light_restore_mode}
        color_interlock: true
        cold_white_color_temperature: 6500 K
        warm_white_color_temperature: 2700 K
        red: output_red
        green: output_green
        blue: output_blue
        cold_white: output_cold
        warm_white: output_warm
    
    button:
      - platform: restart
        name: "Restart"
        entity_category: config
    
      - platform: safe_mode
        name: "Safe Mode"
        internal: false
        entity_category: config
    

    athom-rgbcw.yaml

    This is REALLY complex but I wanted to keep it around to remind myself of the complexity here

    # Put these in the parent file
    # substitutions:
    #   number:
    #   # Allows ESP device to be automatically linked to an 'Area' in Home Assistant. Typically used for areas such as 'Lounge Room', 'Kitchen' etc
    #   room: ""
    #   # Description as appears in ESPHome & top of webserver page
    #   device_description: "athom 7w rgbcw light bulb"
    #   # Project Name
    #   project_name: "Athom Technology.Athom RGBCW Bulb"
    #   # Projection version denotes the release version of the yaml file, allowing checking of deployed vs latest version
    #   project_version: "v1.1.3"
    #   # Restore the light (GPO switch) upon reboot to state:
    #   light_restore_mode: RESTORE_DEFAULT_ON
    #   # Define a domain for this device to use. i.e. iot.home.lan (so device will appear as athom-smart-plug-v2.iot.home.lan in DNS/DHCP logs)
    #   dns_domain: ".local"
    #   # Set timezone of the smart plug. Useful if the plug is in a location different to the HA server. Can be entered in unix Country/Area format (i.e. "Australia/Sydney")
    #   timezone: ""
    #   # Set the duration between the sntp service polling ntp.org servers for an update
    #   sntp_update_interval: 6h
    #   # Network time servers for your region, enter from lowest to highest priority. To use local servers update as per zones or countries at: https://www.ntppool.org/zone/@
    #   sntp_server_1: "0.pool.ntp.org"
    #   sntp_server_2: "1.pool.ntp.org"
    #   sntp_server_3: "2.pool.ntp.org"
    #   # Enables faster network connections, with last connected SSID being connected to and no full scan for SSID being undertaken
    #   wifi_fast_connect: "false"
    #   # Define logging level: NONE, ERROR, WARN, INFO, DEBUG (Default), VERBOSE, VERY_VERBOSE
    #   log_level: "DEBUG"
    #   # Enable or disable the use of IPv6 networking on the device
    #   ipv6_enable: "false"
    #   color_interlock: "true"
    
    packages:
      restore_light_state: !include restore_light_state.yaml
      common: !include common.yaml
    
    esphome:
      name: "athom-rgbcw-bulb-${number}"
      # Default friendly name
      friendly_name: "Athom RGBCW Bulb ${number}"
      comment: "${device_description}"
      area: "${room}"
      name_add_mac_suffix: false
      min_version: 2024.6.0
      project:
        name: "${project_name}"
        version: "${project_version}"
    
    esp8266:
      board: esp8285
      restore_from_flash: true
    
    preferences:
      flash_write_interval: 1min
    
    dashboard_import:
      package_import_url: github://athom-tech/athom-configs/athom-rgbww-light.yaml
    
    binary_sensor:
      - platform: status
        name: "Status"
        entity_category: diagnostic
    
    sensor:
      - platform: uptime
        name: "Uptime Sensor"
        id: uptime_sensor
        entity_category: diagnostic
        internal: true
    
      - platform: wifi_signal
        name: "WiFi Signal dB"
        id: wifi_signal_db
        update_interval: 60s
        entity_category: "diagnostic"
    
      - platform: copy
        source_id: wifi_signal_db
        name: "WiFi Signal Percent"
        filters:
          - lambda: return min(max(2 * (x + 100.0), 0.0), 100.0);
        unit_of_measurement: "Signal %"
        entity_category: "diagnostic"
        device_class: ""
    
    button:
      - platform: restart
        name: "Restart"
        entity_category: config
    
      - platform: safe_mode
        name: "Safe Mode"
        internal: false
        entity_category: config
    
    output:
      - platform: esp8266_pwm
        id: red_output
        pin: GPIO4
        min_power: 0.000499
        max_power: 1
      - platform: esp8266_pwm
        id: green_output
        pin: GPIO12
        min_power: 0.000499
        max_power: 1
      - platform: esp8266_pwm
        id: blue_output
        pin: GPIO14
        min_power: 0.000499
        max_power: 1
      - platform: esp8266_pwm
        id: warm_white_output
        pin: GPIO13
        min_power: 0.000499
        max_power: 0.9
      - platform: esp8266_pwm
        id: white_output
        pin: GPIO5
        min_power: 0.000499
        max_power: 0.9
    
    light:
      - platform: rgbww
        id: rgbww_light
        name: "RGBCW_Bulb"
        restore_mode: ${light_restore_mode}
        red: red_output
        green: green_output
        blue: blue_output
        warm_white: warm_white_output
        cold_white: white_output
        cold_white_color_temperature: 6000 K
        warm_white_color_temperature: 3000 K
        color_interlock: ${color_interlock}
    
    text_sensor:
      - platform: wifi_info
        ip_address:
          name: "IP Address"
          entity_category: diagnostic
        ssid:
          name: "Connected SSID"
          entity_category: diagnostic
        mac_address:
          name: "Mac Address"
          entity_category: diagnostic
    
      #  Creates a sensor showing when the device was last restarted
      - platform: template
        name: "Last Restart"
        id: device_last_restart
        icon: mdi:clock
        entity_category: diagnostic
      #    device_class: timestamp
    
      #  Creates a sensor of the uptime of the device, in formatted days, hours, minutes and seconds
      - platform: template
        name: "Uptime"
        entity_category: diagnostic
        lambda: |-
          int seconds = (id(uptime_sensor).state);
          int days = seconds / (24 * 3600);
          seconds = seconds % (24 * 3600);
          int hours = seconds / 3600;
          seconds = seconds % 3600;
          int minutes = seconds /  60;
          seconds = seconds % 60;
          if ( days > 3650 ) {
            return { "Starting up" };
          } else if ( days ) {
            return { (String(days) +"d " + String(hours) +"h " + String(minutes) +"m "+ String(seconds) +"s").c_str() };
          } else if ( hours ) {
            return { (String(hours) +"h " + String(minutes) +"m "+ String(seconds) +"s").c_str() };
          } else if ( minutes ) {
            return { (String(minutes) +"m "+ String(seconds) +"s").c_str() };
          } else {
            return { (String(seconds) +"s").c_str() };
          }
        icon: mdi:clock-start
    
    time:
      - platform: sntp
        id: sntp_time
        # Define the timezone of the device
        timezone: "${timezone}"
        # Change sync interval from default 5min to 6 hours (or as set in substitutions)
        update_interval: ${sntp_update_interval}
        # Set specific sntp servers to use
        servers:
          - "${sntp_server_1}"
          - "${sntp_server_2}"
          - "${sntp_server_3}"
        # Publish the time the device was last restarted
        on_time_sync:
          then:
            # Update last restart time, but only once.
            - if:
                condition:
                  lambda: 'return id(device_last_restart).state == "";'
                then:
                  - text_sensor.template.publish:
                      id: device_last_restart
                      state: !lambda 'return id(sntp_time).now().strftime("%a %d %b %Y - %I:%M:%S %p");'
    

    globe-smart-plug.yaml

    Again, similar to ones above but it uses the restore-switch-state package instead of light

    # Globe Electric 50329 smart plug
    # Copy this to the new config
    # substitutions:
    #   number: "1"
    
    # <<: !include common/globe-smart-plug.yaml
    
    packages:
      restore_outlet_state: !include restore_outlet_state.yaml
      common: !include common.yaml
    
    esphome:
      friendly_name: "Globe Plug ${number}"
      name: globe-plug-${number}
      comment: Globe Electric 50329 Smart plug
    
    bk72xx:
      board: wb2s
    
    time:
      - platform: homeassistant
        id: homeassistant_time
    
    sensor:
      - platform: uptime
        name: Uptime
        unit_of_measurement: minutes
        filters:
          - lambda: return x / 60.0;
    
      - platform: wifi_signal
        name: Signal
        update_interval: 60s
    
    light:
      - platform: status_led
        name: "led"
        internal: true
        id: led
        pin:
          number: P7
          inverted: true
    
    binary_sensor:
      - platform: gpio
        pin:
          number: P26
          inverted: true
        id: button1
        filters:
          - delayed_on: 10ms
          - delayed_off: 10ms
        on_click:
          - switch.toggle: outlet
    
      - platform: status
        name: Status
    
    switch:
      - platform: gpio
        name: Outlet
        id: outlet
        pin: P24
        icon: mdi:power-socket-us
        on_turn_on:
          - light.turn_on: led
        on_turn_off:
          - light.turn_off: led