mirror of
https://github.com/esphome/esphome.git
synced 2026-06-24 14:37:04 +00:00
libc++ eagerly instantiates the unique_ptr<APIConnection> destructor when std::array<std::unique_ptr<APIConnection>, N> is parsed, requiring sizeof(APIConnection). api_server.h only forward-declares APIConnection (via list_entities.h), so the destructor instantiation fails for any translation unit that includes api_server.h without also including api_connection.h first. Wrap the unique_ptr in a custom deleter (APIConnectionDeleter) whose operator() is defined out-of-line in api_server.cpp where APIConnection is complete. The default_delete<APIConnection> path is never instantiated, so libc++'s incomplete-type assertion is avoided. GCC/libstdc++ already deferred this instantiation, so this only affects macOS host-platform builds (used by integration tests).
364 lines
15 KiB
C++
364 lines
15 KiB
C++
#pragma once
|
|
|
|
#include "esphome/core/defines.h"
|
|
#ifdef USE_API
|
|
#include "api_buffer.h"
|
|
#include "api_noise_context.h"
|
|
#include "api_pb2.h"
|
|
#include "api_pb2_service.h"
|
|
#include "esphome/components/socket/socket.h"
|
|
#include "esphome/core/automation.h"
|
|
#include "esphome/core/component.h"
|
|
#include "esphome/core/controller.h"
|
|
#include "esphome/core/log.h"
|
|
#include "esphome/core/string_ref.h"
|
|
#include "list_entities.h"
|
|
#include "subscribe_state.h"
|
|
#ifdef USE_LOGGER
|
|
#include "esphome/components/logger/logger.h"
|
|
#endif
|
|
#ifdef USE_CAMERA
|
|
#include "esphome/components/camera/camera.h"
|
|
#endif
|
|
|
|
#include <array>
|
|
#include <memory>
|
|
#include <vector>
|
|
|
|
namespace esphome::api {
|
|
|
|
#ifdef USE_API_USER_DEFINED_ACTIONS
|
|
// Forward declaration - full definition in user_services.h
|
|
class UserServiceDescriptor;
|
|
#endif
|
|
|
|
#ifdef USE_API_NOISE
|
|
struct SavedNoisePsk {
|
|
psk_t psk;
|
|
} PACKED; // NOLINT
|
|
#endif
|
|
|
|
class APIServer final : public Component,
|
|
public Controller
|
|
#ifdef USE_CAMERA
|
|
,
|
|
public camera::CameraListener
|
|
#endif
|
|
{
|
|
public:
|
|
APIServer();
|
|
void setup() override;
|
|
uint16_t get_port() const;
|
|
float get_setup_priority() const override;
|
|
void loop() override;
|
|
void dump_config() override;
|
|
void on_shutdown() override;
|
|
bool teardown() override;
|
|
#ifdef USE_LOGGER
|
|
void on_log(uint8_t level, const char *tag, const char *message, size_t message_len);
|
|
#endif
|
|
#ifdef USE_CAMERA
|
|
void on_camera_image(const std::shared_ptr<camera::CameraImage> &image) override;
|
|
#endif
|
|
void set_port(uint16_t port);
|
|
void set_reboot_timeout(uint32_t reboot_timeout);
|
|
void set_batch_delay(uint16_t batch_delay);
|
|
uint16_t get_batch_delay() const { return batch_delay_; }
|
|
void set_listen_backlog(uint8_t listen_backlog) { this->listen_backlog_ = listen_backlog; }
|
|
|
|
// Get reference to shared buffer for API connections
|
|
APIBuffer &get_shared_buffer_ref() { return shared_write_buffer_; }
|
|
|
|
#ifdef USE_API_NOISE
|
|
bool save_noise_psk(psk_t psk, bool make_active = true);
|
|
bool clear_noise_psk(bool make_active = true);
|
|
void set_noise_psk(psk_t psk) { this->noise_ctx_.set_psk(psk); }
|
|
APINoiseContext &get_noise_ctx() { return this->noise_ctx_; }
|
|
#endif // USE_API_NOISE
|
|
|
|
void handle_disconnect(APIConnection *conn);
|
|
#ifdef USE_BINARY_SENSOR
|
|
void on_binary_sensor_update(binary_sensor::BinarySensor *obj) override;
|
|
#endif
|
|
#ifdef USE_COVER
|
|
void on_cover_update(cover::Cover *obj) override;
|
|
#endif
|
|
#ifdef USE_FAN
|
|
void on_fan_update(fan::Fan *obj) override;
|
|
#endif
|
|
#ifdef USE_LIGHT
|
|
void on_light_update(light::LightState *obj) override;
|
|
#endif
|
|
#ifdef USE_SENSOR
|
|
void on_sensor_update(sensor::Sensor *obj) override;
|
|
#endif
|
|
#ifdef USE_SWITCH
|
|
void on_switch_update(switch_::Switch *obj) override;
|
|
#endif
|
|
#ifdef USE_TEXT_SENSOR
|
|
void on_text_sensor_update(text_sensor::TextSensor *obj) override;
|
|
#endif
|
|
#ifdef USE_CLIMATE
|
|
void on_climate_update(climate::Climate *obj) override;
|
|
#endif
|
|
#ifdef USE_NUMBER
|
|
void on_number_update(number::Number *obj) override;
|
|
#endif
|
|
#ifdef USE_DATETIME_DATE
|
|
void on_date_update(datetime::DateEntity *obj) override;
|
|
#endif
|
|
#ifdef USE_DATETIME_TIME
|
|
void on_time_update(datetime::TimeEntity *obj) override;
|
|
#endif
|
|
#ifdef USE_DATETIME_DATETIME
|
|
void on_datetime_update(datetime::DateTimeEntity *obj) override;
|
|
#endif
|
|
#ifdef USE_TEXT
|
|
void on_text_update(text::Text *obj) override;
|
|
#endif
|
|
#ifdef USE_SELECT
|
|
void on_select_update(select::Select *obj) override;
|
|
#endif
|
|
#ifdef USE_LOCK
|
|
void on_lock_update(lock::Lock *obj) override;
|
|
#endif
|
|
#ifdef USE_VALVE
|
|
void on_valve_update(valve::Valve *obj) override;
|
|
#endif
|
|
#ifdef USE_MEDIA_PLAYER
|
|
void on_media_player_update(media_player::MediaPlayer *obj) override;
|
|
#endif
|
|
#ifdef USE_WATER_HEATER
|
|
void on_water_heater_update(water_heater::WaterHeater *obj) override;
|
|
#endif
|
|
#ifdef USE_API_HOMEASSISTANT_SERVICES
|
|
void send_homeassistant_action(const HomeassistantActionRequest &call);
|
|
|
|
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
|
|
// Action response handling
|
|
using ActionResponseCallback = std::function<void(const class ActionResponse &)>;
|
|
void register_action_response_callback(uint32_t call_id, ActionResponseCallback callback);
|
|
void handle_action_response(uint32_t call_id, bool success, StringRef error_message);
|
|
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
|
|
void handle_action_response(uint32_t call_id, bool success, StringRef error_message, const uint8_t *response_data,
|
|
size_t response_data_len);
|
|
#endif // USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
|
|
#endif // USE_API_HOMEASSISTANT_ACTION_RESPONSES
|
|
#endif // USE_API_HOMEASSISTANT_SERVICES
|
|
#ifdef USE_API_USER_DEFINED_ACTIONS
|
|
void initialize_user_services(std::initializer_list<UserServiceDescriptor *> services) {
|
|
this->user_services_.assign(services);
|
|
}
|
|
#ifdef USE_API_CUSTOM_SERVICES
|
|
// Only compile push_back method when custom_services: true (external components)
|
|
void register_user_service(UserServiceDescriptor *descriptor) { this->user_services_.push_back(descriptor); }
|
|
#endif
|
|
#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES
|
|
// Action call context management - supports concurrent calls from multiple clients
|
|
// Returns server-generated action_call_id to avoid collisions when clients use same call_id
|
|
uint32_t register_active_action_call(uint32_t client_call_id, APIConnection *conn);
|
|
void unregister_active_action_call(uint32_t action_call_id);
|
|
void unregister_active_action_calls_for_connection(APIConnection *conn);
|
|
// Send response for a specific action call (uses action_call_id, sends client_call_id in response)
|
|
void send_action_response(uint32_t action_call_id, bool success, StringRef error_message);
|
|
#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES_JSON
|
|
void send_action_response(uint32_t action_call_id, bool success, StringRef error_message,
|
|
const uint8_t *response_data, size_t response_data_len);
|
|
#endif // USE_API_USER_DEFINED_ACTION_RESPONSES_JSON
|
|
#endif // USE_API_USER_DEFINED_ACTION_RESPONSES
|
|
#endif
|
|
#ifdef USE_HOMEASSISTANT_TIME
|
|
void request_time();
|
|
#endif
|
|
|
|
#ifdef USE_ALARM_CONTROL_PANEL
|
|
void on_alarm_control_panel_update(alarm_control_panel::AlarmControlPanel *obj) override;
|
|
#endif
|
|
#ifdef USE_EVENT
|
|
void on_event(event::Event *obj) override;
|
|
#endif
|
|
#ifdef USE_UPDATE
|
|
void on_update(update::UpdateEntity *obj) override;
|
|
#endif
|
|
#ifdef USE_ZWAVE_PROXY
|
|
void on_zwave_proxy_request(const ZWaveProxyRequest &msg);
|
|
#endif
|
|
#if defined(USE_IR_RF) || defined(USE_RADIO_FREQUENCY)
|
|
void send_infrared_rf_receive_event(uint32_t device_id, uint32_t key, const std::vector<int32_t> *timings);
|
|
#endif
|
|
|
|
bool is_connected() const { return this->api_connection_count_ != 0; }
|
|
bool is_connected_with_state_subscription() const;
|
|
|
|
// Range-for view over the populated slice [0, api_connection_count_). Read-only with respect
|
|
// to ownership — callers get `const unique_ptr&` so they can invoke non-const methods on the
|
|
// APIConnection but cannot reset/move the slot and break the count invariant.
|
|
// Custom deleter is defined out-of-line in api_server.cpp so libc++ does not
|
|
// eagerly instantiate `delete static_cast<APIConnection *>(p)` here, where
|
|
// only the forward declaration of APIConnection is visible (incomplete type).
|
|
struct APIConnectionDeleter {
|
|
void operator()(APIConnection *p) const;
|
|
};
|
|
using APIConnectionPtr = std::unique_ptr<APIConnection, APIConnectionDeleter>;
|
|
class ActiveClientsView {
|
|
const APIConnectionPtr *begin_;
|
|
const APIConnectionPtr *end_;
|
|
|
|
public:
|
|
ActiveClientsView(const APIConnectionPtr *b, const APIConnectionPtr *e) : begin_(b), end_(e) {}
|
|
const APIConnectionPtr *begin() const { return this->begin_; }
|
|
const APIConnectionPtr *end() const { return this->end_; }
|
|
};
|
|
ActiveClientsView active_clients() const {
|
|
return {this->clients_.data(), this->clients_.data() + this->api_connection_count_};
|
|
}
|
|
|
|
#ifdef USE_API_HOMEASSISTANT_STATES
|
|
struct HomeAssistantStateSubscription {
|
|
const char *entity_id; // Pointer to flash (internal) or heap (external)
|
|
const char *attribute; // Pointer to flash or nullptr (nullptr means no attribute)
|
|
std::function<void(StringRef)> callback;
|
|
bool once;
|
|
|
|
// Dynamic storage for external components using std::string API (custom_api_device.h)
|
|
// These are only allocated when using the std::string overload (nullptr for const char* overload)
|
|
std::unique_ptr<std::string> entity_id_dynamic_storage;
|
|
std::unique_ptr<std::string> attribute_dynamic_storage;
|
|
};
|
|
|
|
// New const char* overload (for internal components - zero allocation)
|
|
void subscribe_home_assistant_state(const char *entity_id, const char *attribute, std::function<void(StringRef)> &&f);
|
|
void get_home_assistant_state(const char *entity_id, const char *attribute, std::function<void(StringRef)> &&f);
|
|
|
|
// std::string overload with StringRef callback (for custom_api_device.h with zero-allocation callback)
|
|
void subscribe_home_assistant_state(std::string entity_id, optional<std::string> attribute,
|
|
std::function<void(StringRef)> &&f);
|
|
void get_home_assistant_state(std::string entity_id, optional<std::string> attribute,
|
|
std::function<void(StringRef)> &&f);
|
|
|
|
// Legacy std::string overload (for custom_api_device.h - converts StringRef to std::string for callback)
|
|
void subscribe_home_assistant_state(std::string entity_id, optional<std::string> attribute,
|
|
std::function<void(const std::string &)> &&f);
|
|
void get_home_assistant_state(std::string entity_id, optional<std::string> attribute,
|
|
std::function<void(const std::string &)> &&f);
|
|
|
|
const std::vector<HomeAssistantStateSubscription> &get_state_subs() const;
|
|
#endif
|
|
#ifdef USE_API_USER_DEFINED_ACTIONS
|
|
const std::vector<UserServiceDescriptor *> &get_user_services() const { return this->user_services_; }
|
|
#endif
|
|
|
|
#ifdef USE_API_CLIENT_CONNECTED_TRIGGER
|
|
Trigger<std::string, std::string> *get_client_connected_trigger() { return &this->client_connected_trigger_; }
|
|
#endif
|
|
#ifdef USE_API_CLIENT_DISCONNECTED_TRIGGER
|
|
Trigger<std::string, std::string> *get_client_disconnected_trigger() { return &this->client_disconnected_trigger_; }
|
|
#endif
|
|
|
|
protected:
|
|
// Accept incoming socket connections. Only called when socket has pending connections.
|
|
void __attribute__((noinline)) accept_new_connections_();
|
|
// Remove a disconnected client by index. Swaps with the last populated slot and resets it.
|
|
void __attribute__((noinline)) remove_client_(uint8_t client_index);
|
|
|
|
#ifdef USE_API_NOISE
|
|
bool update_noise_psk_(const SavedNoisePsk &new_psk, const LogString *save_log_msg, const LogString *fail_log_msg,
|
|
bool make_active);
|
|
// Load saved PSK from preferences and apply it. Returns true on success.
|
|
bool load_and_apply_noise_psk_();
|
|
#endif // USE_API_NOISE
|
|
#ifdef USE_API_HOMEASSISTANT_STATES
|
|
// Helper methods to reduce code duplication
|
|
void add_state_subscription_(const char *entity_id, const char *attribute, std::function<void(StringRef)> &&f,
|
|
bool once);
|
|
void add_state_subscription_(std::string entity_id, optional<std::string> attribute,
|
|
std::function<void(StringRef)> &&f, bool once);
|
|
// Legacy helper: wraps std::string callback and delegates to StringRef version
|
|
void add_state_subscription_(std::string entity_id, optional<std::string> attribute,
|
|
std::function<void(const std::string &)> &&f, bool once);
|
|
#endif // USE_API_HOMEASSISTANT_STATES
|
|
// No explicit close() needed — listen sockets have no active connections on
|
|
// failure/shutdown. Destructor handles fd cleanup (close or abort per platform).
|
|
inline void destroy_socket_() {
|
|
delete this->socket_;
|
|
this->socket_ = nullptr;
|
|
}
|
|
void socket_failed_(const LogString *msg);
|
|
// Pointers and pointer-like types first (4 bytes each)
|
|
socket::ListenSocket *socket_{nullptr};
|
|
#ifdef USE_API_CLIENT_CONNECTED_TRIGGER
|
|
Trigger<std::string, std::string> client_connected_trigger_;
|
|
#endif
|
|
#ifdef USE_API_CLIENT_DISCONNECTED_TRIGGER
|
|
Trigger<std::string, std::string> client_disconnected_trigger_;
|
|
#endif
|
|
|
|
// 4-byte aligned types
|
|
uint32_t reboot_timeout_{300000};
|
|
uint32_t last_connected_{0};
|
|
|
|
// Slots [0, api_connection_count_) are populated; trailing slots are always nullptr.
|
|
std::array<APIConnectionPtr, MAX_API_CONNECTIONS> clients_{};
|
|
// Vectors and strings (12 bytes each on 32-bit)
|
|
// Shared proto write buffer for all connections.
|
|
// Not pre-allocated: all send paths call prepare_first_message_buffer() which
|
|
// reserves the exact needed size. Pre-allocating here would cause heap fragmentation
|
|
// since the buffer would almost always reallocate on first use.
|
|
APIBuffer shared_write_buffer_;
|
|
#ifdef USE_API_HOMEASSISTANT_STATES
|
|
std::vector<HomeAssistantStateSubscription> state_subs_;
|
|
#endif
|
|
#ifdef USE_API_USER_DEFINED_ACTIONS
|
|
std::vector<UserServiceDescriptor *> user_services_;
|
|
#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES
|
|
// Active action calls - supports concurrent calls from multiple clients
|
|
// Uses server-generated action_call_id to avoid collisions when multiple clients use same call_id
|
|
struct ActiveActionCall {
|
|
uint32_t action_call_id; // Server-generated unique ID (passed to actions)
|
|
uint32_t client_call_id; // Client's original call_id (used in response)
|
|
APIConnection *connection;
|
|
};
|
|
std::vector<ActiveActionCall> active_action_calls_;
|
|
uint32_t next_action_call_id_{1}; // Counter for generating unique action_call_ids
|
|
#endif // USE_API_USER_DEFINED_ACTION_RESPONSES
|
|
#endif
|
|
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
|
|
struct PendingActionResponse {
|
|
uint32_t call_id;
|
|
ActionResponseCallback callback;
|
|
};
|
|
std::vector<PendingActionResponse> action_response_callbacks_;
|
|
#endif
|
|
|
|
// Group smaller types together
|
|
uint16_t port_{6053};
|
|
uint16_t batch_delay_{100};
|
|
// Connection limits - these defaults will be overridden by config values
|
|
// from cv.SplitDefault in __init__.py which sets platform-specific defaults.
|
|
uint8_t listen_backlog_{4};
|
|
bool shutting_down_ = false;
|
|
uint8_t api_connection_count_{0};
|
|
// 7 bytes used, 1 byte padding
|
|
|
|
#ifdef USE_API_NOISE
|
|
APINoiseContext noise_ctx_;
|
|
ESPPreferenceObject noise_pref_;
|
|
#endif // USE_API_NOISE
|
|
};
|
|
|
|
extern APIServer *global_api_server; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
|
|
|
template<typename... Ts> class APIConnectedCondition : public Condition<Ts...> {
|
|
TEMPLATABLE_VALUE(bool, state_subscription_only)
|
|
public:
|
|
bool check(const Ts &...x) override {
|
|
if (this->state_subscription_only_.value(x...)) {
|
|
return global_api_server->is_connected_with_state_subscription();
|
|
}
|
|
return global_api_server->is_connected();
|
|
}
|
|
};
|
|
|
|
} // namespace esphome::api
|
|
#endif
|