Reusable function for lambda's?

Hi all,

Still very new to ESPHome and finding it a very steep learning curve. Note that I am not using HA.

I would like to have some reusable functions that I can call in lambda’s. Can someone please point me in the right direction? I am not a C/C++ programmer and I’ve only used the Arduino IDE previously so if I need to write some C++ code, I might need a bit of a hand, sorry.

Thanks in advance.

1 Like

You can just create a “.h” file next to your yaml file and reference it under includes like so: https://esphome.io/components/esphome.html#includes

In that header file you can just write C/C++ functions that you can use in lambdas. For example:

int add3(int x) {
    return x+3;
}

They should be able to access most of the normal ESPHome C++ api and also normal Arduino stuff and everything from the libraries you can add (also under “esphome:”)

2 Likes

Oh and if you do not want to use C/C++ you can also look at scripts.

You can define a sequence of actions there (even with conditions) and call that from a lot of places in ESPHome.

(you should read the whole section about automations in the documentation)

3 Likes

Many thanks for your responses, very helpful.

It was the C++ code that I needed as I need to manage some things in lambda’s that don’t really have an equivalent in ESPhome.

One area is some detailed timing - where I need the timing to be variable based on an inbound MQTT topic. This seems quite complex to achieve in yaml but should be fairly simple in code.

The other area is display positioning. I want to be able to configure pages using a more logical layout than having to trial and error pixel positions. Using rows and columns. Of course, these need to be based on either a fixed grid or on a grid that is dependent on the font size.

The other thing that I need custom code for is to integrate more of my M5stack Basic device functions like the card reader. I just about got the speaker working with existing yaml along with the Temperature/Humidity sensor in the base-unit. Buttons I’ve also done in yaml fairly easily.

Loving the fact that I can use a single set of yaml configurations though across multiple device types even between ESP32 and ESP8266 so despite the horribly steep learning curve, ESPHome is still proving its worth.

1 Like

Depending on what you mean with “detailed timing” it may very well be possible with yaml and a little C in a lambda. Have a look at Automations and Templates — ESPHome especially delay that can take the time from a lambda.

Compine that with the “on_json_message” trigger of the mqtt component: MQTT Client Component — ESPHome and you may be able to do it without a lot of C code.

That seems quite lot of work to do right. Maybe there a good librarys for that kind of thing? AdafruitGFX the most used library for graphics and text on displays is not good enough on its own.

I used to write my ESP32/8266 code in C/C++ with the Arduino IDE. But I have recently moved 100% to ESPHome. You can still use all the C/C++ but have a great base system for things like WLAN, OTA, MQTT, Home Assistant API, Time and so on. So even if you want more than just a simple sensor or switch ESPHome can be a good base for that.

If you have normal Arduino code most of the time it is easy to port it to ESPHome as a custom component. They also have a setup() and a loop() method.

Thanks, I think I’ve got it sorted. The MQTT msg is simply a command to change the timeout. For example, I’ve set my M5stack basic to turn off the backlight after a fixed 1 minute. But I want to be able to control that and make the time variable.

Indeed it is and it always surprises me that nobody has actually made the effort since, once done, it makes life so much easier. Line/column screen placement has been a fundamental of computing ever since displays existed. I shouldn’t need to keep messing with pixel placement. And I HATE having to repeat myself in code!

But I don’t have to get it perfect. It is enough to build up some standard variables that can be adjusted according to font size. Row placement is actually really easy. The font height + a line spacing variable and a top-left position. 3 variables and a bit of basic adding :slight_smile:

Horizontal positioning is a little harder when using variable-width fonts. But here we can take a leaf from web-based UX. Using a horizontal positioning grid.

I don’t like the AdaFruit library as it is far too complex and heavy.

Totally agree and I’m certainly getting there. Slowly. It has been a week or two of hard work getting my head around how it works and the documentation certainly needs some improvements to take out some of the assumptions so that beginners can make better speed. I think there is too much assumption that you know C++ and PlatformIO.

But I’ve already deconstructed my configuration so that I can reuse all of the common parts. I just need to now find the time to rebuild my various platforms.

Definitely some things I still need to learn how to integrate though.

SPIFFS for example, I’d like to be able to make use of that. I can do it in Arduino code so I know I’ll get there but surprised to see nothing for that built into ESPHome (unless I missed it). Reading from and writing to SPIFFS should be quite a common requirement I would have thought.

Battery management is another thing. Again, I’ve done that with Arduino code in the past so I know I’ll get there, just not had time yet.

Definitely getting there anyway and many thanks for your help.

Hello Daniel,

your advice has serious limitation. It only works for top level functions. If I want to use function within a function like following example, I get compilation error:

int add3(int x) {
    return x+multiplier();
}

int multiplier() {
  return 3;
}

error:

In file included from src/main.cpp:151:0:
src/test.h: In function 'int add3(int)':
src/test.h:2:25: error: 'multiplier' was not declared in this scope
     return x+multiplier();
                         ^
*** [.pioenvs/blinds-brain/src/main.cpp.o] Error 1

Any advice to that please? I have really complex structure of functions and it does not compile.

Hm I fixed it by switching functions. Apparently the definitions must be in correct order.

1 Like

Yes is one option.

If you do not like to be forced to order the functions another option are forward declarations.

You can just put the declaration of the functions at the top of the file (without everything including the curly braces). The compiler will then know that the function exists and is happy but expect it to find the implementation somewhere.

In the example:

Put

int multiplier();

somewhere at the top. You can then put the actual function with implementation where ever you like.

3 Likes

Hello, little late here but…

The ESPHome docs says you can also include folders, headers and code files (entire folders, .h / .hpp / .tcc and .c / .cpp files).
https://esphome.io/components/esphome.html#includes

They behave differently depending on what you are including.
I’m using this to declare lambdas function to reuse across many different ESPs (devices). For example:
in my “funcoes.h” file I have function declarations only, like:

void natal(AddressableLight &p_it)
{
    Color fila[p_it.size()];
    static int z;
    ...
}

And then, in my YAML files for ESPHome devices, I just do:

esphome:
  name: ${devicename}
  platform: ESP8266
  board: esp01_1m
  includes: 
    - funcoes.h

And later, on my lambdas (inside light component):

      - addressable_lambda:
          name: "Natal"
          update_interval: 200ms
          lambda: natal(it);

This way I only need to update code once, then just update nodes (compile and update ESPHome devices).

If I need to get functions which access runtime values and objects, I would include a .cpp file instead. I almost did it because of the “it” object (AddressableLight component), but I could solve it by passing the reference parameter instead.

1 Like

Sorry to revive this - I am trying to learn how to code for my ESPHome display using a separate file similar to the example above.

I just want to move the giant lamba that I already have into a .h file and then start writing reusable functions in there so that I can get rid of some duplicated code.

My display looks like this:

display:
  - id: my_display
    platform: ili9xxx
    model: ili9341
    spi_id: tft
    cs_pin: GPIO15
    dc_pin: GPIO2
    rotation: 90
    update_interval: 1s
    pages:
      - id: splash
        lambda: |-
          it.print(150, 120, id(roboto20), id(white), TextAlign::CENTER, "Connecting...");
      - id: page1
        lambda: test(it);

My test.h looks like this:

void test(SOMETHING &it) {
    esphome::Color p1s_1_color = id(white);    
    ...
}

I am not sure how I should be defining the parameter to my test function. How do I determine what SOMETHING is for my display? Also can I even access things like esphome:: within the function? I am not a C++ coder. My background is PHP & Javascript.

@fernando how did you know to use AddressableLight in yours? And why do you use p_it instead of it?

I got a bit further after a lot of trial an error. Seems I simply need to use type Display, however I thought it wasn’t that simple because I keep getting errors about printf:

HARDWARE: ESP32 240MHz, 320KB RAM, 4MB Flash
 - toolchain-xtensa-esp32 @ 8.4.0+2021r2-patch5
Dependency Graph
|-- AsyncTCP-esphome @ 2.0.1
|-- WiFi @ 2.0.0
|-- FS @ 2.0.0
|-- Update @ 2.0.0
|-- ESPAsyncWebServer-esphome @ 3.1.0
|-- DNSServer @ 2.0.0
|-- ESPmDNS @ 2.0.0
|-- SPI @ 2.0.0
Compiling .pioenvs/esp-printer-dashboard/src/main.cpp.o
In file included from src/main.cpp:74:
src/test.h: In function 'void test(esphome::display::Display&)':
src/test.h:27:108: error: no matching function for call to 'esphome::display::Display::printf(int, int, esphome::font::Font&, esphome::Color&, esphome::display::TextAlign, const char [3], const char*&)'
     d_it.printf(80 * 1, -15 + 60 * 1, id(roboto16), p1s_1_color, TextAlign::CENTER, "%s", p1s_1_status_text);
                                                                                                            ^
In file included from src/esphome.h:17,
                 from src/main.cpp:3:
src/esphome/components/display/display.h:256:8: note: candidate: 'void esphome::display::Display::printf(int, int, esphome::display::BaseFont*, esphome::Color, esphome::display::TextAlign, const char*, ...)'
   void printf(int x, int y, BaseFont *font, Color color, TextAlign align, const char *format, ...)
        ^~~~~~
src/esphome/components/display/display.h:256:8: note:   no known conversion for argument 3 from 'esphome::font::Font' to 'esphome::display::BaseFont*'
src/esphome/components/display/display.h:268:8: note: candidate: 'void esphome::display::Display::printf(int, int, esphome::display::BaseFont*, esphome::Color, const char*, ...)'
   void printf(int x, int y, BaseFont *font, Color color, const char *format, ...) __attribute__((format(printf, 6, 7)));
        ^~~~~~
src/esphome/components/display/display.h:268:8: note:   no known conversion for argument 3 from 'esphome::font::Font' to 'esphome::display::BaseFont*'
src/esphome/components/display/display.h:279:8: note: candidate: 'void esphome::display::Display::printf(int, int, esphome::display::BaseFont*, esphome::display::TextAlign, const char*, ...)'
   void printf(int x, int y, BaseFont *font, TextAlign align, const char *format, ...)
        ^~~~~~
src/esphome/components/display/display.h:279:8: note:   no known conversion for argument 3 from 'esphome::font::Font' to 'esphome::display::BaseFont*'
src/esphome/components/display/display.h:290:8: note: candidate: 'void esphome::display::Display::printf(int, int, esphome::display::BaseFont*, const char*, ...)'
   void printf(int x, int y, BaseFont *font, const char *format, ...) __attribute__((format(printf, 5, 6)));
        ^~~~~~
src/esphome/components/display/display.h:290:8: note:   no known conversion for argument 3 from 'esphome::font::Font' to 'esphome::display::BaseFont*'
*** [.pioenvs/esp-printer-dashboard/src/main.cpp.o] Error 1
========================== [FAILED] Took 3.69 seconds ==========================

It seems like I can draw shapes no problem, but rendering text doesn’t work the same as it did when written in a lambda…