[Solved] MQTT ESPHome color channel integration

MQTT color channel only allows one commandTopic. It restricts your device to handle on/off (power), color, and brightness in one topic. While in the OP’s case it is possible because it takes a JSON where you could put any of those in, the brightness being embedded in the HSB (or RGB for that matter) isn’t suitable, at least for my case, because my devices have a separate brightness, especially when the light is in white mode.

Basically the short conclusion of the above, is that it’s more versatile to just have separate channels for each of those things

  • on/off (power)
  • brightness (independent of color settings)
  • color
  • color temperature

This is also easier because you don’t have to use any transformations. The caveat is to program your device to match and communicate using simple data such as plain r,g,b instead of using JSON.

I just created my own esphome yaml configuration - isn’t that how everyone does it? What’s your device and what does your esphome yaml configuration look like?

I can create whatever mqtt topic structure / data I want with esphome and in my case I tried to make it simpler to work with openhab.

esphome:
  name: smart-lamp

esp8266:
  board: d1_mini

# Enable logging
logger:

# OTA password
ota:
  password: "mypass"

wifi:
  ssid: "MySSID"
  password: "MyPass"
  manual_ip:
    static_ip: 192.168.xxx.xxx
    gateway: 192.168.xxx.xxx
    subnet: 255.255.255.0

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "Smart-Lamp"
    password: "MyPassword"

mqtt:
  broker: 192.168.xxx.xxx
  username: openhab
  password: mypass
  client_id: smart-lamp
  birth_message:
    topic: smart-lamp/light/smart-light/status
    payload: online
  will_message:
    topic: smart-lamp/light/smart-light/status
    payload: offline

light:
  - platform: neopixelbus
    type: GRB
    variant: WS2812X
    pin: GPIO3
    num_leds: 8
    name: "Smart-Light"
    method:
      type: esp8266_dma
    on_turn_on:
      - logger.log: "Light Turned On!"
    on_turn_off:
      - logger.log: "Light Turned Off!"
    effects:
      - pulse:
          name: "Fast Pulse"
          transition_length: 0.5s
          update_interval: 0.5s
      - pulse:
          name: "Slow Pulse"
          transition_length: 2s
          update_interval: 2s
      - random:
          name: "Random"
          transition_length: 5s
          update_interval: 7s
      - flicker:
          name: "Flicker"
          alpha: 95%
          intensity: 1.5%
      - addressable_rainbow:
          name: "Rainbow Full"
          speed: 10
          width: 8
      - addressable_rainbow:
          name: "Rainbow Double"
          speed: 10
          width: 16
      - addressable_color_wipe:
          name: "Color Wipe"
          colors:
            - red: 20%
              green: 0%
              blue: 80%
              num_leds: 6
            - red: 80%
              green: 0%
              blue: 80%
              num_leds: 8
            - red: 0%
              green: 0%
              blue: 80%
              num_leds: 6
            - red: 80%
              green: 0%
              blue: 80%
              num_leds: 8
          add_led_interval: 600ms
          reverse: false
      - addressable_scan:
          name: "Scan"
          move_interval: 200ms
          scan_width: 1
      - addressable_twinkle:
          name: "Twinkle"
          twinkle_probability: 7%
          progress_interval: 24ms
      - addressable_random_twinkle:
          name: "Random Twinkle"
          twinkle_probability: 7%
          progress_interval: 32ms
      - addressable_fireworks:
          name: "Fireworks"
          update_interval: 64ms
          spark_probability: 10%
          use_random_color: true
          fade_out_rate: 120
      
captive_portal:

it’s a Wemos D1 mini - esp8266

I thought your code is openhab yaml from MainUI but if I understood that right you mean it’s ESPHome yaml you posted… I have a closer look! but I’m not really deep in programming esphome lambdas etc. - a little explanation would be nice :wink:

Maybe it’s really the best solution to sort things out on devices side…

I’ll have a look at your code and the esphome docs, thanks

Edit: Is it possible to add a logic to the esp to switch state ON/OFF, if rgb color is changing from/to ‘0,0,0’ and how would that look like?? My coding skills are limited but this would be the easiest way to fix this last problem…

what I read in a thread at the homeassistant forum is the following:

"I hate to be a nag about this point but let me try again, you are not sending out 20% brightness. You are sending out 20% blue (or red or green). The following is directly from the ESPHome Light Component documentation:

Note

The red , green and blue values only control the color of the light, not its brightness! If you assign 50% to all RGB channels it will be interpreted as 100% on. Only use brightness to control the brightness of the light.

In other words if you send out 50% of each color the light will still be 100% on unless you change the brightness."

that’s where my confusion comes from concerning rgb and brightness… :wink:

I have not tested this, but give it a try:

substitutions:
  device_name: smart-lamp

esphome:
  name: ${device_name}
  includes:
    - includes/helpers.h
  on_boot:
    priority: -100
    then:
      - script.execute: publish_state

esp8266:
  board: d1_mini

# Enable logging
logger:

# OTA password
ota:
  password: "mypass"

wifi:
  ssid: "MySSID"
  password: "MyPass"
  manual_ip:
    static_ip: 192.168.xxx.xxx
    gateway: 192.168.xxx.xxx
    subnet: 255.255.255.0

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "Smart-Lamp"
    password: "MyPassword"

mqtt:
  broker: 192.168.xxx.xxx
  username: openhab
  password: mypass
  # client_id: smart-lamp # not necessary either
  # These aren't necessary. It should by default have the LWT topic of 'DEVICE_NAME/status'
  # birth_message:
  #   topic: smart-lamp/light/smart-light/status
  #   payload: online
  # will_message:
  #   topic: smart-lamp/light/smart-light/status
  #   payload: offline
  on_message:
    - topic: ${device_name}/power/set
      then:
        - lambda: |-
            switch(parse_on_off(x.c_str())) {
              case PARSE_ON: id(light1).turn_on().perform(); break;
              case PARSE_OFF: id(light1).turn_off().perform(); break;
              case PARSE_TOGGLE: id(light1).toggle().perform(); break;
              case PARSE_NONE: break;
            }

    - topic: ${device_name}/brightness/set
      then:
        - lambda: |-
            auto ct = parse_number<float>(x);
            if (ct.has_value()) {
              id(light1).turn_on().set_brightness(ct.value()/255).perform();
            }

    - topic: ${device_name}/color_temperature/set
      then:
        - lambda: |-
            auto ct = parse_number<float>(x);
            if (ct.has_value()) {
              id(light1).turn_on().set_color_temperature(ct).perform();
            }

    - topic: ${device_name}/rgb/set
      then:
        - lambda: |-
            std::vector<std::string> colors = split(x, ',');

            if (colors.size() != 3) {
              return;
            }

            auto r = parse_number<float>(colors[0]);
            auto g = parse_number<float>(colors[1]);
            auto b = parse_number<float>(colors[2]);

            if (r.has_value() && g.has_value() && b.has_value()) {
              id(light1).turn_on()
                .set_color_mode(ColorMode::RGB)
                .set_red(r.value()/255)
                .set_green(g.value()/255)
                .set_blue(b.value()/255)
                .perform();
            }

  

light:
  - platform: neopixelbus
    id: light1
    type: GRB
    variant: WS2812X
    pin: GPIO3
    num_leds: 8
    name: "Smart-Light"
    method:
      type: esp8266_dma
    on_turn_on:
      - logger.log: "Light Turned On!"
    on_turn_off:
      - logger.log: "Light Turned Off!"
    on_state:
      - script.execute: publish_state
    effects:
      - pulse:
          name: "Fast Pulse"
          transition_length: 0.5s
          update_interval: 0.5s
      - pulse:
          name: "Slow Pulse"
          transition_length: 2s
          update_interval: 2s
      - random:
          name: "Random"
          transition_length: 5s
          update_interval: 7s
      - flicker:
          name: "Flicker"
          alpha: 95%
          intensity: 1.5%
      - addressable_rainbow:
          name: "Rainbow Full"
          speed: 10
          width: 8
      - addressable_rainbow:
          name: "Rainbow Double"
          speed: 10
          width: 16
      - addressable_color_wipe:
          name: "Color Wipe"
          colors:
            - red: 20%
              green: 0%
              blue: 80%
              num_leds: 6
            - red: 80%
              green: 0%
              blue: 80%
              num_leds: 8
            - red: 0%
              green: 0%
              blue: 80%
              num_leds: 6
            - red: 80%
              green: 0%
              blue: 80%
              num_leds: 8
          add_led_interval: 600ms
          reverse: false
      - addressable_scan:
          name: "Scan"
          move_interval: 200ms
          scan_width: 1
      - addressable_twinkle:
          name: "Twinkle"
          twinkle_probability: 7%
          progress_interval: 24ms
      - addressable_random_twinkle:
          name: "Random Twinkle"
          twinkle_probability: 7%
          progress_interval: 32ms
      - addressable_fireworks:
          name: "Fireworks"
          update_interval: 64ms
          spark_probability: 10%
          use_random_color: true
          fade_out_rate: 120
      
captive_portal:

script:
  - id: publish_state
    then:
      - mqtt.publish:
          topic: ${device_name}/power/state
          retain: true
          payload: !lambda |-
            return id(light1).remote_values.is_on() ? "ON" : "OFF";
      - mqtt.publish:
          topic: ${device_name}/brightness/state
          retain: true
          payload: !lambda |-
            return to_string((int)(id(light1)->remote_values.get_brightness() * 255));
      - mqtt.publish:
          topic: ${device_name}/color_temperature/state
          retain: true
          payload: !lambda |-
            auto ct = id(light1)->remote_values.get_color_temperature();
            return to_string(clamp(ct, (float)176, (float)133));
      - mqtt.publish:
          topic: ${device_name}/rgb/state
          retain: true
          payload: !lambda |-
            return
              to_string((int)(id(light1)->remote_values.get_red()*255)) + "," +
              to_string((int)(id(light1)->remote_values.get_green()*255)) + "," +
              to_string((int)(id(light1)->remote_values.get_blue()*255))
              ;

You’ll need this include/helpers.h file to do the splitting

#pragma once

std::vector<std::string> split(const std::string& str, char delimiter) {
  std::vector<std::string> tokens;

  size_t last = 0;
  size_t next = 0;
  while ((next = str.find(delimiter, last)) != std::string::npos) {
    tokens.push_back(str.substr(last, next-last));
    last = next + 1;
  }
  tokens.push_back(str.substr(last));

  return tokens;
}

EDIT: I forgot to add on_state for your light in the original version.

2 Likes

Right, but is it the command that gets passed to the transformation or is it the state after the binding processes the command that gets passed to the transformation? I think it’s the latter. Transformations are not like rules in that way.

I would say then that ESPHome, rather than use one of the many already existing ways to represent color, has decided to invent their own, which is unfortunate.

funfact: the colorpicker dimms and ignores the esphome docs :joy:

but that’s where my information is from - seems like the docs are wrong or outdated :smirk:

Hi Jim…

I compiled and uploaded your code… no problems so far

I just have to add the new channels to OH config and see if it works like expected - I don’t completely understand your code but we will see…

hmm your code seems to have some logic errors…

brightness works like expected, power affects only the power switch but not the colorswitch, ct is not working - but I’m not sure if it’s supported at all when using ledstrips…

the weirdest behavior is the colorswitch :wink: when turned off it immediately turns back on, but the color is set to 0,0,0 which results in 100% white.

maybe you can understand what’s happening?

It was from my rgb light bulb with adjustable color temperature. I guess color temp isn’t applicable to your color strip.

What did you mean with “colorswitch”? I thought the code is very straight forward to read/understand even if you don’t fully understand the c++ syntax.

when you add a color item in OH it shows a switch and a color field… by clicking on the color you open the colorpicker

the switch should toggle the light on/off

yes but there are multiple areas where I don’t really understand what’s happening…
is it possible to “crossconnect” powerswitch and colorswitch so that one affects the other automatically??

the colorswitch sends a 0,0,0 when turned off - so maybe one can send

  • 0,0,0 to the color topic when turning OFF the powerswitch
  • OFF to the power topic if a 0,0,0 is received by the color topic?

and aditionally I can’t understand this weird behavior that the colorswitch immediately turns back on!

any ideas on that?

Oh, that’s the behaviour of the MainUI. The “Switch” on the color item basically does what Rich said above: it sets the “B” part of the HSB to 0. Say your color was set to (in HSB) 100, 95, 100. When you turned that switch on the color off, it will send the command “100, 95, 0” to the item.

Now mqtt translates that into the equivalent RGB, send it to esphome.

ESPhome has no idea about this trickery, and just treats it like a normal RGB command. As you can see in the c++ code, it turns on the light, then sets the r,g,b values. Now HSB X,X,0 (the Brightness 0) translates to RGB 0,0,0 regardless of what the H and S part is.

And your light component interprets it as 100% white (which seems weird, whereas my lightbulb just turns off).

So learning about this behaviour, you could just modify ESPHome code so that when it receives RGB 0,0,0, it just powers off the light, and not try to change the colour.

Happy days.
something like “if input string == “0,0,0” turn off, and don’t bother parsing it”

Here’s the relevant yaml part:

    - topic: ${device_name}/rgb/set
      then:
        - lambda: |-
            if (x == "0,0,0") {
              id(light1).turn_off().perform();
              return;
            }
            
            std::vector<std::string> colors = split(x, ',');

            if (colors.size() != 3) {
              return;
            }

            auto r = parse_number<float>(colors[0]);
            auto g = parse_number<float>(colors[1]);
            auto b = parse_number<float>(colors[2]);

            if (r.has_value() && g.has_value() && b.has_value()) {
              id(light1).turn_on()
                .set_color_mode(ColorMode::RGB)
                .set_red(r.value()/255)
                .set_green(g.value()/255)
                .set_blue(b.value()/255)
                .perform();
            }

EDIT: sorry as this was untested - I just noticed I needed to add .perform() at the end.

1 Like

thanks a lot this is working…

let me ask one last question, I’m struggeling removing the ct parts from your code…

I removed

      - mqtt.publish:
          topic: ${device_name}/color_temperature/state
          retain: true
          payload: !lambda |-
            auto ct = id(light1)->remote_values.get_color_temperature();

and this

    - topic: ${device_name}/color_temperature/set
      then:
        - lambda: |-
            auto ct = parse_number<float>(x);
            if (ct.has_value()) {
              id(light1).turn_on().set_color_temperature(ct).perform();
            }

then I thought this if section isn’t neccessary any more - but when I remove it, it breaks the brightness dimmer function… unsure which part to remove here…

    - topic: ${device_name}/brightness/set
      then:
        - lambda: |-
            auto ct = parse_number<float>(x);
            if (ct.has_value()) {
              id(light1).turn_on().set_brightness(ct.value()/255).perform();
            }

just remove color_temperature

brightness is for brightness (dimmer), not color_temperature.

hmm ok, I thought all of the lambda is unneccessary because it only relates to the non existing ct channel, or do I interpret that wrong??

big thanks anyway! :slight_smile: I’m very glad with the result…

and also a big thanks to @rossko57 and @rlkoshak ! Have a nice weekend guys…

1 Like

Hi Jim,
thanks for your post that help me a lot
:grinning:

1 Like

I was going through this thread over and over again and couldn’t get the incoming state to update the color. It suddenly started working when I changed json2rgb.js from

function(i) {
    var parsed = JSON.parse(i);
    return parsed.color.r+','+parsed.color.g+','+parsed.color.b;
}(input)

to

(function(i) {
    var parsed = JSON.parse(i);
    return parsed.color.r+','+parsed.color.g+','+parsed.color.b;
})(input)

It seems the function block needs to be enclosed in brackets.