esphome::custom::CustomLightOutputConstructor' has no member named 'current_values'

Hello,

I’m trying to control a Kassil A160 light for reef tanks with ESPHome. This light can be controlled by two 0-10v analogue channels: one to control the intensity and one to control the color.
I have created the circuit to covert 2x ESP8266 PWM outputs to 0-10v analogue signals. And I’m now working on setup ESPHome to control these two PWM outputs.

I reviewed the different light components available in ESPHome and seems that none of them are fit to control a light that requires two channels with one for brightness and one for the color.

I have so created a custom light component (kessil.h) as follow:

#pragma once

#include "esphome.h"

class KessilLight : public Component, public LightOutput {
    public:

    KessilLight(FloatOutput *brightness_output, FloatOutput *color_output)
    {
        brightness_output_ = brightness_output;
        color_output_ = color_output;
    }

    LightTraits get_traits() override {
        auto traits = LightTraits();
        traits.set_supports_brightness(true);
        traits.set_supports_color_temperature(false);
        traits.set_supports_rgb(false);
        traits.set_supports_rgb_white_value(true);
        return traits;
    }

    void write_state(LightState *state) override {
        float brightness, red, green, blue, white;
        // use any of the provided current_values methods
        state->current_values_as_rgbw(&red, &green, &blue, &white, this->color_interlock_);
        state->current_values_as_brightness (&brightness);
        white = white / brightness;
        if (brightness == 0)
                {
            this->brightness_output_->set_level(0);
            this->color_output_->set_level(0);
        }
        else
        {
            this->brightness_output_->set_level(brightness);
            this->color_output_->set_level(white);
        }
        
    }
    
    protected:
    FloatOutput *brightness_output_;
    FloatOutput *color_output_;
    bool color_interlock_;
};

And in my ESPHome .yaml I have added:

esphome:
  includes:
    - kessil.h

light:
  - platform: custom
    id: reef_tank_light
    lambda: |-
      auto kessil_light = new KessilLight(id(reef_tank_light_brightness_pwm), id(reef_tank_light_color_pwm));
      App.register_component(kessil_light);
      return {kessil_light};
    lights:
      - name: "Reef Tank - Light"

output:
  - platform: pca9685
    id: reef_tank_light_brightness_pwm
    channel: 0
  - platform: pca9685
    id: reef_tank_light_color_pwm
    channel: 1

This works well to control the light and from HA I can adjust the light intensity (using the brightness slider) and the light color (using the the white slider).

Now I would like to create a sensor that returns the current brightness and white (color) value for this light. The reason for this is that I use very long transitions to reproduce dawn and dusk, and I would like to see brightness and color throughout the transition. When using a standard monochromatic light I would add the following to the ESPHome .yaml file:

  - platform: template
    name: "Reef Tank - Light Brightness"
    id: reef_tank_light_brightness
    update_interval: 20s
    accuracy_decimals: 0
    unit_of_measurement: "%"
    icon: "mdi:weather-sunny"
    lambda: |-
      return (int(id(reef_tank_light).current_values.get_brightness() * 100));

When using the same approach with the custom light I get the following error when trying to compile:

src/main.cpp: In lambda function:
src/main.cpp:720:35: error: 'const class esphome::custom::CustomLightOutputConstructor' has no member named 'current_values'
       return (int(reef_tank_light.current_values.get_brightness() * 100));

Any suggestions on how to implement the equivalent of .current_values.get_brightness() and .current_values.get_white() for a custom light?

I have tried several approaches (trying to overwrite get_brightness(), create a custom sensor etc.) but I’m getting nowhere… Any suggestion or hint would be much appreciated.

The closest I got to get the brightness and color values was to edit the custom light to behave a bit also like a custom sensor (not sure if this is correct or makes much sense):

#pragma once

#include "esphome.h"

class KessilLight : public PollingComponent, public LightOutput, public LightColorValues {
    public:
    Sensor *brightness_value = new Sensor();
    Sensor *color_value = new Sensor();

    KessilLight(FloatOutput *brightness_output, FloatOutput *color_output) : PollingComponent(30000)
    {
        brightness_output_ = brightness_output;
        color_output_ = color_output;
    }

    LightTraits get_traits() override {
        auto traits = LightTraits();
        traits.set_supports_brightness(true);
        traits.set_supports_color_temperature(false);
        traits.set_supports_rgb(false);
        traits.set_supports_rgb_white_value(true);
        return traits;
    }

    void setup() override {
    }

    void write_state(LightState *state) override {
        float red, green, blue;
        // use any of the provided current_values methods
        state->current_values_as_rgbw(&red, &green, &blue, &white, this->color_interlock_);
        state->current_values_as_brightness (&brightness);
        white = white / brightness;
        if (brightness == 0)
                {
            this->brightness_output_->set_level(0);
            this->color_output_->set_level(0);
        }
        else
        {
            this->brightness_output_->set_level(brightness);
            this->color_output_->set_level(white);
        }
        
    }

    void update() override {
        // This is the actual sensor reading logic.
        brightness_value->publish_state(brightness);
        color_value->publish_state(white);
    }
    
    protected:
    FloatOutput *brightness_output_;
    FloatOutput *color_output_;
    float brightness;
    float white;
    bool color_interlock_;
};

With this change in the log I can see the brightness and color values coming through:

[15:52:19][D][sensor:092]: '': Sending state 1.00000  with 0 decimals of accuracy
[15:52:19][D][sensor:092]: '': Sending state 1.00000  with 0 decimals of accuracy

But I cannot figure out how I could assign these values to a sensor so that they can be visualized in HA…

Hello all,

Following what suggested here I managed to find a solution that works. I have edit the custom light component (kessil.h) as follow:

#pragma once

#include "esphome.h"

class KessilLight : public Component, public LightOutput, public LightColorValues {
    public:
    KessilLight(FloatOutput *brightness_output, FloatOutput *color_output)
    {
        brightness_output_ = brightness_output;
        color_output_ = color_output;
    }

    LightTraits get_traits() override {
        auto traits = LightTraits();
        traits.set_supports_brightness(true);
        traits.set_supports_color_temperature(false);
        traits.set_supports_rgb(false);
        traits.set_supports_rgb_white_value(true);
        return traits;
    }

    void write_state(LightState *state) override {
        float red, green, blue;
        // use any of the provided current_values methods
        state->current_values_as_rgbw(&red, &green, &blue, &white, this->color_interlock_);
        state->current_values_as_brightness (&brightness);
        white = white / brightness;
        if (brightness == 0)
                {
            this->brightness_output_->set_level(0);
            this->color_output_->set_level(0);
            reef_tank_light_brightness_value->value() = 0;
            reef_tank_light_color_value->value() = 0;
        }
        else
        {
            this->brightness_output_->set_level(brightness);
            this->color_output_->set_level(white);
            reef_tank_light_brightness_value->value() = brightness * 100;
            reef_tank_light_color_value->value() = white * 100;
        }
        
    }

    protected:
    FloatOutput *brightness_output_;
    FloatOutput *color_output_;
    float brightness;
    float white;
    bool color_interlock_;
};

And I have edit my ESPHome .yaml as follow:

esphome:
  includes:
    - kessil.h

globals:
  - id: reef_tank_light_brightness_value
    type: float
  - id: reef_tank_light_color_value
    type: float

sensor:
  - platform: template
    name: "Reef Tank - Light Brightness"
    id: reef_tank_light_brightness
    update_interval: 20s
    accuracy_decimals: 0
    unit_of_measurement: "%"
    icon: "mdi:weather-sunny"
    lambda: |-
      return (id(reef_tank_light_brightness_value)); 
  - platform: template
    name: "Reef Tank - Light Color"
    id: reef_tank_light_color
    update_interval: 20s
    accuracy_decimals: 0
    unit_of_measurement: "%"
    icon: "mdi:palette-outline"
    lambda: |-
      return (id(reef_tank_light_color_value)); 

light:
  - platform: custom
    id: reef_tank_light
    lambda: |-
      auto kessil_light = new KessilLight(id(reef_tank_light_brightness_pwm), id(reef_tank_light_color_pwm));
      App.register_component(kessil_light);
      return {kessil_light};

This setup works. However, I would like to avoid hard coding in the kessil.h file the name of the esphome global variables to be used for the sensors (i.e. reef_tank_light_brightness_value and reef_tank_light_color_value).
I would like to be able to pass these variables to the KessilLight class when defining the custom light. I would like to be able to call in the .yaml file something like:

light:
  - platform: custom
    id: reef_tank_light
    lambda: |-
      auto kessil_light = new KessilLight(id(reef_tank_light_brightness_pwm), id(reef_tank_light_color_pwm), id(reef_tank_light_brightness_value), id(reef_tank_light_color_value));
      App.register_component(kessil_light);
      return {kessil_light};

I’m however stuck at this point… I have tried to define the KessilLight class as:

class KessilLight : public Component, public LightOutput, public LightColorValues {
    public:
    KessilLight(FloatOutput *brightness_output, FloatOutput *color_output, GlobalsComponent *brightness_value, GlobalsComponent *color_value)
    {
        brightness_output_ = brightness_output;
        color_output_ = color_output;
        brightness_value_ = brightness_value;
        color_value_ = color_value;
    }

However of course this does not work… :slight_smile:

Any suggestion on how to pass to the custom class the global variable “names” instead of hardcoding the actual varibales?

The solution is to not use global variables. Instead store the values in your custom object (where they conceptually belong anyway). For example:

kessil.h:

class KessilLight : public Component, public LightOutput {
    public:
      float brightness;
[...]

.yaml:

sensor:
  - platform: template
    id: reef_tank_light_brightness
    name: "Reef Tank - Light Brightness"
    lambda: |-
        return ( ((KessilLight*)reef_tank_light)->brightness );