#pragma once #include #include "api_server.h" #ifdef USE_API #ifdef USE_API_USER_DEFINED_ACTIONS #include "user_services.h" #endif namespace esphome::api { #ifdef USE_API_USER_DEFINED_ACTIONS template class CustomAPIDeviceService : public UserServiceDynamic { public: CustomAPIDeviceService(const std::string &name, const std::array &arg_names, T *obj, void (T::*callback)(Ts...)) : UserServiceDynamic(name, arg_names), obj_(obj), callback_(callback) {} protected: // CustomAPIDevice services don't support action responses - ignore call_id and return_response void execute(uint32_t /*call_id*/, bool /*return_response*/, Ts... x) override { (this->obj_->*this->callback_)(x...); // NOLINT } T *obj_; void (T::*callback_)(Ts...); }; #endif // USE_API_USER_DEFINED_ACTIONS class CustomAPIDevice { public: /// Return if a client (such as Home Assistant) is connected to the native API. bool is_connected() const { return global_api_server->is_connected(); } /** Register a custom native API service that will show up in Home Assistant. * * Usage: * * ```cpp * void setup() override { * register_service(&CustomNativeAPI::on_start_washer_cycle, "start_washer_cycle", * {"cycle_length"}); * } * * void on_start_washer_cycle(int cycle_length) { * // Start washer cycle. * } * ``` * * @tparam T The class type creating the service, automatically deduced from the function pointer. * @tparam Ts The argument types for the service, automatically deduced from the function arguments. * @param callback The member function to call when the service is triggered. * @param name The name of the service to register. * @param arg_names The name of the arguments for the service, must match the arguments of the function. */ #ifdef USE_API_USER_DEFINED_ACTIONS template void register_service(void (T::*callback)(Ts...), const std::string &name, const std::array &arg_names) { #ifdef USE_API_CUSTOM_SERVICES auto *service = new CustomAPIDeviceService(name, arg_names, (T *) this, callback); // NOLINT global_api_server->register_user_service(service); #else static_assert( sizeof(T) == 0, "register_service() requires 'custom_services: true' in the 'api:' section of your YAML configuration"); #endif } #else template void register_service(void (T::*callback)(Ts...), const std::string &name, const std::array &arg_names) { static_assert( sizeof(T) == 0, "register_service() requires 'custom_services: true' in the 'api:' section of your YAML configuration"); } #endif /** Register a custom native API service that will show up in Home Assistant. * * Usage: * * ```cpp * void setup() override { * register_service(&CustomNativeAPI::on_hello_world, "hello_world"); * } * * void on_hello_world() { * // Hello World service called. * } * ``` * * @tparam T The class type creating the service, automatically deduced from the function pointer. * @param callback The member function to call when the service is triggered. * @param name The name of the arguments for the service, must match the arguments of the function. */ #ifdef USE_API_USER_DEFINED_ACTIONS template void register_service(void (T::*callback)(), const std::string &name) { #ifdef USE_API_CUSTOM_SERVICES auto *service = new CustomAPIDeviceService(name, {}, (T *) this, callback); // NOLINT global_api_server->register_user_service(service); #else static_assert( sizeof(T) == 0, "register_service() requires 'custom_services: true' in the 'api:' section of your YAML configuration"); #endif } #else template void register_service(void (T::*callback)(), const std::string &name) { static_assert( sizeof(T) == 0, "register_service() requires 'custom_services: true' in the 'api:' section of your YAML configuration"); } #endif #ifdef USE_API_HOMEASSISTANT_STATES /** Subscribe to the state (or attribute state) of an entity from Home Assistant. * * Usage: * * ```cpp * void setup() override { * subscribe_homeassistant_state(&CustomNativeAPI::on_state_changed, "climate.kitchen", "current_temperature"); * } * * void on_state_changed(StringRef state) { * // State of climate.kitchen current_temperature is `state` * // Use state.c_str() for C string, state.str() for std::string * } * ``` * * @tparam T The class type creating the service, automatically deduced from the function pointer. * @param callback The member function to call when the entity state changes (zero-allocation). * @param entity_id The entity_id to track. * @param attribute The entity state attribute to track. */ template void subscribe_homeassistant_state(void (T::*callback)(StringRef), const std::string &entity_id, const std::string &attribute = "") { auto f = std::bind(callback, (T *) this, std::placeholders::_1); global_api_server->subscribe_home_assistant_state(entity_id, optional(attribute), std::move(f)); } /** Subscribe to the state (or attribute state) of an entity from Home Assistant (legacy std::string version). * * @deprecated Use the StringRef overload for zero-allocation callbacks. Will be removed in 2027.1.0. */ template ESPDEPRECATED("Use void callback(StringRef) instead. Will be removed in 2027.1.0.", "2026.1.0") void subscribe_homeassistant_state(void (T::*callback)(std::string), const std::string &entity_id, const std::string &attribute = "") { auto f = std::bind(callback, (T *) this, std::placeholders::_1); // Explicit type to disambiguate overload resolution global_api_server->subscribe_home_assistant_state(entity_id, optional(attribute), std::function(f)); } /** Subscribe to the state (or attribute state) of an entity from Home Assistant. * * Usage: * * ```cpp * void setup() override { * subscribe_homeassistant_state(&CustomNativeAPI::on_state_changed, "sensor.weather_forecast"); * } * * void on_state_changed(const std::string &entity_id, StringRef state) { * // State of `entity_id` is `state` * } * ``` * * @tparam T The class type creating the service, automatically deduced from the function pointer. * @param callback The member function to call when the entity state changes (zero-allocation for state). * @param entity_id The entity_id to track. * @param attribute The entity state attribute to track. */ template void subscribe_homeassistant_state(void (T::*callback)(const std::string &, StringRef), const std::string &entity_id, const std::string &attribute = "") { auto f = std::bind(callback, (T *) this, entity_id, std::placeholders::_1); global_api_server->subscribe_home_assistant_state(entity_id, optional(attribute), std::move(f)); } /** Subscribe to the state (or attribute state) of an entity from Home Assistant (legacy std::string version). * * @deprecated Use the StringRef overload for zero-allocation callbacks. Will be removed in 2027.1.0. */ template ESPDEPRECATED("Use void callback(const std::string &, StringRef) instead. Will be removed in 2027.1.0.", "2026.1.0") void subscribe_homeassistant_state(void (T::*callback)(std::string, std::string), const std::string &entity_id, const std::string &attribute = "") { auto f = std::bind(callback, (T *) this, entity_id, std::placeholders::_1); // Explicit type to disambiguate overload resolution global_api_server->subscribe_home_assistant_state(entity_id, optional(attribute), std::function(f)); } #else template void subscribe_homeassistant_state(void (T::*callback)(StringRef), const std::string &entity_id, const std::string &attribute = "") { static_assert(sizeof(T) == 0, "subscribe_homeassistant_state() requires 'homeassistant_states: true' in the 'api:' section " "of your YAML configuration"); } template void subscribe_homeassistant_state(void (T::*callback)(std::string), const std::string &entity_id, const std::string &attribute = "") { static_assert(sizeof(T) == 0, "subscribe_homeassistant_state() requires 'homeassistant_states: true' in the 'api:' section " "of your YAML configuration"); } template void subscribe_homeassistant_state(void (T::*callback)(const std::string &, StringRef), const std::string &entity_id, const std::string &attribute = "") { static_assert(sizeof(T) == 0, "subscribe_homeassistant_state() requires 'homeassistant_states: true' in the 'api:' section " "of your YAML configuration"); } template void subscribe_homeassistant_state(void (T::*callback)(std::string, std::string), const std::string &entity_id, const std::string &attribute = "") { static_assert(sizeof(T) == 0, "subscribe_homeassistant_state() requires 'homeassistant_states: true' in the 'api:' section " "of your YAML configuration"); } #endif #ifdef USE_API_HOMEASSISTANT_SERVICES /** Call a Home Assistant service from ESPHome. * * Usage: * * ```cpp * call_homeassistant_service("homeassistant.restart"); * ``` * * @param service_name The service to call. */ void call_homeassistant_service(const std::string &service_name) { HomeassistantActionRequest resp; resp.service = StringRef(service_name); global_api_server->send_homeassistant_action(resp); } /** Call a Home Assistant service from ESPHome. * * Usage: * * ```cpp * call_homeassistant_service("light.turn_on", { * {"entity_id", "light.my_light"}, * {"brightness", "127"}, * }); * ``` * * @param service_name The service to call. * @param data The data for the service call, mapping from string to string. */ void call_homeassistant_service(const std::string &service_name, const std::map &data) { HomeassistantActionRequest resp; resp.service = StringRef(service_name); resp.data.init(data.size()); for (auto &it : data) { auto &kv = resp.data.emplace_back(); kv.key = StringRef(it.first); kv.value = StringRef(it.second); // data map lives until send completes } global_api_server->send_homeassistant_action(resp); } /** Fire an ESPHome event in Home Assistant. * * Usage: * * ```cpp * fire_homeassistant_event("esphome.something_happened"); * ``` * * @param event_name The event to fire. */ void fire_homeassistant_event(const std::string &event_name) { HomeassistantActionRequest resp; resp.service = StringRef(event_name); resp.is_event = true; global_api_server->send_homeassistant_action(resp); } /** Fire an ESPHome event in Home Assistant. * * Usage: * * ```cpp * fire_homeassistant_event("esphome.something_happened", { * {"my_value", "500"}, * }); * ``` * * @param event_name The event to fire. * @param data The data for the event, mapping from string to string. */ void fire_homeassistant_event(const std::string &service_name, const std::map &data) { HomeassistantActionRequest resp; resp.service = StringRef(service_name); resp.is_event = true; resp.data.init(data.size()); for (auto &it : data) { auto &kv = resp.data.emplace_back(); kv.key = StringRef(it.first); kv.value = StringRef(it.second); // data map lives until send completes } global_api_server->send_homeassistant_action(resp); } #else template void call_homeassistant_service(const std::string &service_name) { static_assert(sizeof(T) == 0, "call_homeassistant_service() requires 'homeassistant_services: true' in the 'api:' " "section of your YAML configuration"); } template void call_homeassistant_service(const std::string &service_name, const std::map &data) { static_assert(sizeof(T) == 0, "call_homeassistant_service() requires 'homeassistant_services: true' in the 'api:' " "section of your YAML configuration"); } template void fire_homeassistant_event(const std::string &event_name) { static_assert(sizeof(T) == 0, "fire_homeassistant_event() requires 'homeassistant_services: true' in the 'api:' " "section of your YAML configuration"); } template void fire_homeassistant_event(const std::string &service_name, const std::map &data) { static_assert(sizeof(T) == 0, "fire_homeassistant_event() requires 'homeassistant_services: true' in the 'api:' " "section of your YAML configuration"); } #endif }; } // namespace esphome::api #endif