How to config multiple sensor types and singleton controller?

Hello!

I have searched through the forums and other parts of the Internet (e.g. Home Assistant and ESPHome), but cannot find any information of how to config multiple types of sensors AND a controller (which is a single instance!) simultaneously.

Basically I have a custom board that has an ESP32 that should run an Arduino “sketch” as the RelayController. This class has the setup() and loop() functions to be called upon creation and execution in the same way it is normally done when not using ESPHome.

The RelaySwitches and RelaySensors classes needs to access the getUpdatedMessage() function from the singleton RelayController instance whenever their update() function is called to get the latest values from the RelayController.

The question I have is how to use my class RelayController in the yaml configuration file to implement this? I suppose the RelayController must be registered as a component somehow for the setup() and loop() functions to work properly? Maybe I have missed a better way to set this up in ESPHome?

I have good knowledge of Arduino programming in general but I just cannot get my head around the ESPHome configuration in order to add the sensors for Home Assistant in this case.

I hope that you guys can help me out here? All help and suggestions are welcome! :slight_smile:

Thanks!


esp-controller.yaml

esphome:
  name: esp-controller
  includes:
    - relay-controller.h
    - relay-sensors.h
    - relay-switches.h
  libraries:
    - Wire
    - LiquidCrystal_I2C

esp32:
  board: esp32dev
  framework:
    type: arduino

wifi:
  ssid: !secret wwww
  password: !secret pppp

logger:
  level: INFO
  baud_rate: 0
  
api:
  password: !secret xxxx
ota:
  password: !secret yyyy

sensor:
  - platform: custom
    lambda: |-
      auto relay_sensors = new RelaySensors();
      App.register_component(relay_sensors);
      return {
        relay_sensors->outdoorBrightness
      };

    sensors:
    - name: "Outdoor brightness"
      id: relay_controller_outdoor_brightness
      unit_of_measurement: lx
      device_class: "illuminance"
      state_class: "measurement"
      accuracy_decimals: 0

binary_sensor:
  - platform: custom
    lambda: |-
      auto relay_switches = new RelaySwitches();
      App.register_component(relay_switches);
      return {
        relay_switches->relay_1
      };
  
    binary_sensors:
    - name: "Relay 1"
      id: relay_controller_relay_1
      device_class: "light"

I suppose the RelayController must be registered as a component somehow in this yaml-file to call the setup() and loop() functions? How do I do this?

I guess something similar to this:

RelayController &relay_controller = RelayController::getInstance();
App.register_component(relay_controller);

relay-controller.h

#include "esphome.h"
#include <Wire.h>
#include <LiquidCrystal_I2C.h>

class Message {
  public:
    double outdoorBrightness;
    bool relay_1;
};

class RelayController : public Component {

  private:
    // Singleton!
    RelayController() {}

  public:
    static RelayController& getInstance() {
      static RelayController instance;
      return instance;
    }

    // Preventing copies of the singleton appearing.
    RelayController(RelayController const& copy) = delete;
    void operator=(RelayController const& copy) = delete;

    void setup() override {
      ESP_LOGI("exe", " >> SETUP Relay Controller ...");
    }

    void loop() override {
      // Do a lot of exiting stuff .... :)
    }

    Message getUpdatedMessage() {
      ESP_LOGI("exe", " > Getting Updated Message ...");

      // Hardcoded example!
      Message message = Message();
      message.outdoorBrightness = 1234;
      message.relay_1 = true;
      return message;
    }
}

relay-sensors.h

class RelaySensors : public PollingComponent, public Sensor {
  public:
    Sensor *outdoorBrightness = new Sensor();
    RelayController &theRelayController = RelayController::getInstance();

    RelaySensors() : PollingComponent(15000) {      
    }

    float get_setup_priority() const override { return esphome::setup_priority::HARDWARE; }

    void setup() override {
      ESP_LOGI("exe", " >> SETUP Relay Sensors ...");
    }

    void update() override {
      ESP_LOGI("exe", " > Publishing Relay Sensors ...");
      Message message = theRelayController.getUpdatedMessage();
      publishSensors(&message);
    }

  private:
    void publishSensors(Message* message) {
      outdoorBrightness->publish_state(message->outdoorBrightness);
    }
};

relay-switches.h

class RelaySwitches : public PollingComponent, public BinarySensor {
  public:
    RelayController &theRelayController = RelayController::getInstance();
    BinarySensor *relay_1 = new BinarySensor();

    RelaySwitches() : PollingComponent(15000) {
    }

    float get_setup_priority() const override { return esphome::setup_priority::HARDWARE; }

    void setup() override {
      ESP_LOGI("exe", " >> SETUP Relay Switches ...");
    }

    void update() override {
      ESP_LOGI("exe", " > Publishing Relay Switches ...");
      Message message = theRelayController.getUpdatedMessage();
      publishSensors(&message);
    }

  private:
    void publishSensors(Message* message) {
      relay_1->publish_state(message->relay_1);
    }
};

You usually don’t need all this to run a relay.

Hi nickrout!
Thanks for your reply!
Sure, I realize that this is the case for most applications.
But, in this case it actually is! :slight_smile:

To give you all some more background on this project:
I have already built my own hardware that is supposed to continue running its own application (i.e. Arduino Sketch) with schedules for turning on and off 8 relays controlling lights outside the house based on time and brightness. This means if the wifi goes down, or even I move out of the house taking the Home Assistant server with me, the thing would keep on working fine controlling outside lights anyway.

I think this is what is making it a bit more complicated, and hence I am looking for some tips to get it working.

The goal is to move the original code that is currently running on the ESP32 to the RelayController class, which I have done, and just add the two RelaySensors and RelaySwitches classes to be able to read the values from ESPHome into Home Assistant. I don’t think that was very hard. Except for the yaml-config… :stuck_out_tongue:

I thought connecting my project to ESPHome would be doable although some of you might well say it is over engineered… maybe it is, but hey you need to do fun stuff sometimes, right? :smiley:

Any ideas to move me forward would be much appreciated! I realize it is difficult to explain it all as simple as possible, so please ask if there is something I need to clearify.

Thanks again!

Hello again!

The problem is now solved. I share my solution here if anyone else finds this interesting. :slight_smile:

I changed the RelayController from being singleton to an ordinary sensor class and added the RelayController to one of the sensors in the lambda expression in the yaml-file:

    lambda: |-
      auto relay_controller = new RelayController();
      App.register_component(relay_controller);

      auto relay_sensors = new RelaySensors();
      App.register_component(relay_sensors);
      return {
        relay_sensors->outdoorBrightness
      };

Then I created a singleton MessageHandler instead that is used by the controller and the sensors to pass the data needed.

message-handler.h:

class Message {
  public:
    int outdoorBrightness = -1;
    bool relay_1 = false;
};

class MessageHandler {

  private:
    Message message;

    // Singleton!
    MessageHandler() {}

  public:
    static MessageHandler& getInstance() {
      static MessageHandler instance;
      return instance;
    }

    // Preventing copies of the singleton appearing.
    MessageHandler(const MessageHandler&) = delete;
    MessageHandler(MessageHandler&&) = delete;
    MessageHandler& operator=(const MessageHandler&) = delete;
    MessageHandler& operator=(MessageHandler&&) = delete;

    Message getMessage() {
      return message;
    }

    void setOutdoorBrightness(unsigned int brightness) {
      message.outdoorBrightness = brightness;
    }
};

This works great! :slight_smile:

All the best! Cheers!