ESPHome custom component: Any way to publish a light state manually?

Hi all. I have been working on a couple of custom components to support the Gosund SW2 Wifi dimmer. At this point I have it working fine, but I can’t find answers to a couple questions and I think there may be a better way to do what I’m doing here.

The first question is whether or not there’s a way to update the state of a light without calling light.turn_on. The SW2 dimmer has a capacitive touch panel on the front, which dims the light and then informs the ESP8285 module of the new value over the UART interface. My custom UART component reads this value, and then calls light.turn_on with the new value in order to update the state. That causes the light component to tell the custom output component to send a new value to the dimmer, so I used a global variable as a flag to prevent that serial write when the user has changed the output manually at the dimmer.

The second question is regarding the power LED on the dimmer. My custom component is an output instead of a light so I could use the power_supply property of the output to turn on the LED on the along with the light itself. Is there a better way to do that? Should I be writing a custom light component that does all this instead?

Here’s the code for reference (any comments about stupid mistakes are welcome…):

gosund_sw2_output.h

#include "esphome.h"
using namespace esphome;

class GosundSW2Output : public Component, public FloatOutput
{
    public:
        void setup() override
        {
            // This will be called by App.setup()
        }

        void write_state(float state) override
        {
            uint8_t dimmerVal;

            if (id(set_by_touch))
            {   /* Set the set_by_touch flag to prevent sending the value back to the hardware */
                id(set_by_touch) = false;
                return;
            }

            // Write to HW
            if (state > 0.0)
            {
                dimmerVal = (127.0 * state);
                dimmerVal |= 0x80;  /* Bit 7 seems to control the relay */
            }
            else
            {
                dimmerVal = 0;
            }

            ESP_LOGD("gosund_sw2_out", "Writing dimmer value %02X", dimmerVal);
            Serial.write(dimmerVal);
        }
};

gosund_sw2_uart.h

#include "esphome.h"
using namespace esphome;

class GosundSW2Uart : public Component, public UARTDevice {
    protected:
        light::LightState *dimmer_{nullptr};

    public:
        GosundSW2Uart(UARTComponent *parent, light::LightState *dimmer)
            : UARTDevice(parent), dimmer_(dimmer) {}    // Need a pointer to the light component we're controlling

    void setup() override {
        // nothing to do here
    }

    void loop() override {
        if (Serial.available() >= 5)
        {
            char buff[5];
            while (Serial.available()) Serial.readBytes(buff, 5);

            float dimmerVal = buff[1] / 100.0;

            id(set_by_touch) = true;
            auto call = dimmer_->turn_on();
            call.set_brightness(dimmerVal);
            call.perform();
            
            ESP_LOGD("gosund_sw2_uart", "Received dimmer value %02X", buff[1]);
        }
    }
};

dining_room_light.yaml

...

# Disable logging via the UART
logger:
  baud_rate: 0

globals:
  id: set_by_touch
  type: bool

binary_sensor:
  - platform: gpio
    pin:
      number: GPIO4
      inverted: true
    id: button
    on_press:
      - light.toggle: dining_room_light
    on_click:
      min_length: 5s
      max_length: 10s
      then:
        - switch.turn_on: dining_room_light_restart

power_supply:
  - id: green_led
    pin:
      number: GPIO13
      inverted: true
    enable_time: 0s
    keep_on_time: 0s

output:
  - platform: custom
    type: float
    lambda: |-
      auto float_out = new GosundSW2Output();
      App.register_component(float_out);
      return {float_out};
    outputs:
      - id: dimmer
        power_supply: green_led
      
light:
  - platform: monochromatic
    name: "Dining Room Light"
    id: dining_room_light
    output: dimmer
    gamma_correct: 1.0
    default_transition_length: 0s

custom_component:
  - lambda: |-
      auto uart_out = new GosundSW2Uart(id(uart_bus), id(dining_room_light));
      return {uart_out};
    
status_led:
  pin:
    number: GPIO12
    inverted: true

...

I had a similar issue and nearly drove myself crazy looking for a solution.

You can change the reported state of an entity without switching it by using rest_command. It’s convoluted, but it works. See my explanation and example code here: https://github.com/ronschaeffer/hass-light-state/blob/master/README.md

Thanks for that. I love how elegantly simple this solution is (at least hardware-wise), and you definitely have me thinking about some other things that would be nice to report the state of in HA.

That said, I think the use case here is different. The switching all happens internally to my “smart dimmer” in this case, and I was just looking for the best way to optimize my ESPHome code. Seems like there should be a way to publish a new light state in ESPHome (which also pushes the new value to HA) without actually going through the whole “turn_on” action.

The Native API Component can be used

Can you share solution? I am having same Gosund SW2 dimmers.

Sure, the code is here: https://github.com/ab0tj/esphome-devices

The gosund_sw2 files would go in a folder inside of your esphome folder called devices. The YAML for the actual device looks like this:

substitutions:
  device_name: dining_room_light
  friendly_name: Dining Room Light

<<: !include devices/gosund_sw2.yaml

Also don’t forget to fill in devices/secrets.yaml

1 Like

Thank you for sharing your code. It works for me.
Only issue I found is when device brightness set to 100% in HA Light Card it is corresponds to 70%.
brightness_scale: 70 will help I believe but I cannot find a way to set it for discovered device yet.
Any advise appreciated.