ESPHome lambdas and C(++) for beginners
ESPHome is absolutely brilliant for people who wish to build their own smart home gadgets.
The recent acquisition of the project by Nabu Casa, the company behind Home Assistant has kicked some life in the previously stagnant project. Doing even complex things with it is quite easy, but as with all powerful frameworks going outside what the framework builders intended can be a rough ride.
This page collects all the things I wish I had been told about working with C(++) around ESPHome as a web developer and a human.
Be warned though, I found the below by trial and error, I have very limited C background.
Quick examples
Using lambdas
Lambdas are a way of running arbitrary C code
Getting a sensor value
lambda / included source file
id(api_key).state
The ID function can be used to access the sensors defined in YAML and the .state attribute contains the current state of the sensor.
For this to work, a sensor must be defined and an ID given to it in YAML.
project.yaml
text_sensor:
- platform: template
name: "API Key"
id: api_key
Construct a JSON string
lambda / included source file
char buf[256];
sprintf(buf, "{\"device\":{\"api_key\": \"%s\", \"temperature\": \"%%1.3f\" }}", id(api_key).state.c_str(), id(temperature).state );
return ((std::string) buf);
This creates a C style character array of max 256 characters, and constructs a string by inserting a string and floating point value to the string.
The last line returns the value as a C++ standard library string, which is the less capability challenged string data type common with embedded projects, which ESPHome uses.
Including external files
ESPHome allows you to include external C source files to be compiled into the firmware. The example includes a header file functions.h
project.yaml
esphome:
...
includes:
- functions.h
You can build anything in the included files, the most useful thing I found is to define reusable functions in the file.
Note though, that all the includes directive does, is copy the named file to
<projecdir>/.esphome/build/<projectname>/src
If you remove or rename the include, it will not remove the file!
Converting values to strings
Newer versions of C++ provide the ability to call std::to_string(12345); to convert floating point values to std::string, but this functionality is not available in ESPHome. Instead, I use the following function defined in an external header file:
functions.h
template <typename T>
std::string ToString(T val)
{
std::stringstream stream;
stream << val;
return stream.str();
}
Other tips
Arduino examples work, but only when not dealing with strings, as the Arduino framework has its own String implementation which offers yet another bad alternative in an already crowded market. See below for more information.
A full reference of ESPHome's internal functions is available here. Unfortunately, it mostly contains the name of the function, along with parameters and return value types. The rest is left as an exercise to the reader.
Skimming through the header file for helper functions can be interesting, it contains many functions for things like getting the MAC address, various type conversions and lighting-related calculations.
What is going on with char*, char, String and std::string?!
A reasonable person might assume that a good computer system has an object for storing text. If the developers of the system have been particularly misanthrophic, it might have two. In embedded C projects you are likely to encounter four. Here they are:
char* aka. "C-style string". A variable length character array.
char another C style string with static length
String Arduino framework's own attempt at fixing C strings
std::string C++ style string, used by ESPHome
To make things extra confusing, in addition to these, there are numerous other options for various types of encodings (8, 16 and 32 bit), string buffers and all manner of excitement.
Which one should you use?
std:string when you can
char* when you must
Other things if you really can't live without them