For some time, when I needed to call a method on my custom component from a lambda function, I used a complicated typecasting macro I saw on an example that goes like this (custom cover component): #define get_am25(constructor) static_cast<Am25 *>(const_cast<custom::CustomCoverConstructor *>(&constructor)->get_cover(0))
Then, from the yaml file, I would call the method like this:
esphome:
includes:
- custom_component.h
cover:
- platform: custom
id: lblind
lambda: |-
auto lblind = new Am25(22, 1, id(lb_motor), id(lb_error), id(lb_error_str));
App.register_component (lblind);
return {lblind};
covers:
- name: "Window left blind"
id: lblindcover
device_class: blind
switch:
- platform: template
name: Window LB cal top
turn_on_action:
- lambda: 'get_am25(lblind)->caltop();'
It worked fine until ESPHome 1.17, 1.18 or something. Now, in ESPHome 2021.12, I get an error:
/config/esphome/window.yaml: In lambda function:
/config/esphome/window.yaml:510:16: error: 'lblind' was not declared in this scope
- lambda: 'get_am25(lblind)->caltop();'
^
src/living_window_am25.h:5:97: note: in definition of macro 'get_am25'
#define get_am25(constructor) static_cast<Am25 *>(const_cast<custom::CustomCoverConstructor *>(&constructor)->get_cover(0)) ^
Is there a proper way to call a method in a custom component class? Is there some bug on the newer versions of ESPHome, or was this some unavoidable side-effect of the various changes in the code?
My C++ is beyond poor, but it seems like you’re calling the constructor again - not something you usually want/need to do, as it temporarily instantiates the class, just so you can call one method.
Rather, you probably want to call the method via the existing instance of the class, which is called here ‘lblind’.
So maybe you need to reference it via the custom platform instance’s ID, and not by calling the class itself, as in:
id(lblind)->caltop();
-or-
id(lblind).caltop();
(I’m not sure which way to call it would be correct - per aforementioned C++ skills absence.)
Thanks for the response. I tried all those ways (I too get a little confused about when an object is considered a pointer), i get either base operand of '->' has non-pointer type 'const esphome::custom::CustomCoverConstructor, or 'class esphome::cover::Cover' has no member named 'caltop', or const class esphome::custom::CustomCoverConstructor' has no member named 'count'.
The typecast macro was to signal the compiler that the object I was referencing was not it’s parent class (Cover / CustomCoverConstructor), so it would not complain about “no member named”, but it looks like the newer version might have shuffled some declarations. I see in the generated main.cpp that the Cover components are being declared at the beggining:
cover::Cover *lblindcover;
But not my custom cover lblind (which is the one that has the method).
Sounds like we’re both in sort of the same boat with the C++ mysteries of it.
Perhaps someone more knowlegeable than I will step in and provide a useful and correct answer.
I’m sure there’s a way…
Hi, I have the exact same question: How do we call a custom component’s method from another component/lambda? I’m sure there must be some proper way. Does anybody know it?
Thank you, this is excellent, and is exactly what I was looking for!
Using this approach it’s possible to have a single C++ component that provides esphome entities of different type, as well as custom methods to be used in lambdas. As a bonus, if you’d like to keep the ugly casts out of your yaml, you can make a little helper method in C++.
Well, I finally got around to trying again. @mdvorak and @mag1024 answers gave me some insight and are great alternatives depending on how you want to implement it. But in the end, I got my code working by simply using id(component) instead of component in the macro call (get_am25(id(lblind))->caltop();). I suppose using component in the macro call stopped working because object declarations got shuffled around on C++ code generation by newer ESPHome versions and the object had not been declared yet when it inserted the lambda code. The extremely ugly const_cast, as I see it, is to get around C++ protections against const potential changes via pointers.
Really if your are only planning to use your custom component simply as a library of functions, you can simply declare a global instance variable instantation of your class. For example for custom component class include of " my_component.h"
class myclass: xxxxx {
}
Add this after your class definition in your header file:
myclass myclassobjvar();
#or if you prefer a pointer declaration
myclass * myclassobjvar = new myclass();
No need for a custom component declaration in your yaml at all as you can now refer to your new global class object in your lambdas as:
myclassobjvar.myclassfunc();
or if using a pointer declaration
myclassobjvar->myclassfunc();
You can still use the global variable in your custom_component declaration, just don’t use the “auto” attribute in front of the variable. In this case you will need to use the pointer variation to declare your global variable first (without instantatiating it). ie myclass * myclassobjvar; The instantiation in your customcomponent section will then assign to your previously declared global.
I should clarify that this works fine for custom_components but I have not tried it with App.register for sensors, switches, etc.
For more details see this other thread on a similar topic:
That’s an interesting approach, @Dilbert66. In my case, the class (window blinds) is an actual class for a custom cover, with properties, methods and gets instantiated to two objects (left and right blind). I had actually tried something similar (global pointer declaration in the header file), but could not get it to work at the time. Anyway, I am not going to mess with the code I just got to work ;-), but this may be useful for some other case.