#include #include "api_server.h" #include "basic_messages.h" #include "esphome/core/log.h" #include "esphome/core/application.h" #include "esphome/core/util.h" #include "esphome/core/defines.h" #include "esphome/core/version.h" #ifdef USE_DEEP_SLEEP #include "esphome/components/deep_sleep/deep_sleep_component.h" #endif #ifdef USE_HOMEASSISTANT_TIME #include "esphome/components/homeassistant/time/homeassistant_time.h" #endif #ifdef USE_LOGGER #include "esphome/components/logger/logger.h" #endif #include namespace esphome { namespace api { static const char *TAG = "api"; // APIServer void APIServer::setup() { ESP_LOGCONFIG(TAG, "Setting up Home Assistant API server..."); this->setup_controller(); this->server_ = AsyncServer(this->port_); this->server_.setNoDelay(false); this->server_.begin(); this->server_.onClient( [](void *s, AsyncClient *client) { if (client == nullptr) return; // can't print here because in lwIP thread // ESP_LOGD(TAG, "New client connected from %s", client->remoteIP().toString().c_str()); auto *a_this = (APIServer *) s; a_this->clients_.push_back(new APIConnection(client, a_this)); }, this); #ifdef USE_LOGGER if (logger::global_logger != nullptr) { logger::global_logger->add_on_log_callback([this](int level, const char *tag, const char *message) { for (auto *c : this->clients_) { if (!c->remove_) c->send_log_message(level, tag, message); } }); } #endif this->last_connected_ = millis(); #ifdef USE_ESP32_CAMERA if (esp32_camera::global_esp32_camera != nullptr) { esp32_camera::global_esp32_camera->add_image_callback([this](std::shared_ptr image) { for (auto *c : this->clients_) if (!c->remove_) c->send_camera_state(image); }); } #endif } void APIServer::loop() { // Partition clients into remove and active auto new_end = std::partition(this->clients_.begin(), this->clients_.end(), [](APIConnection *conn) { return !conn->remove_; }); // print disconnection messages for (auto it = new_end; it != this->clients_.end(); ++it) { ESP_LOGD(TAG, "Disconnecting %s", (*it)->client_info_.c_str()); } // only then delete the pointers, otherwise log routine // would access freed memory for (auto it = new_end; it != this->clients_.end(); ++it) delete *it; // resize vector this->clients_.erase(new_end, this->clients_.end()); for (auto *client : this->clients_) { client->loop(); } if (this->reboot_timeout_ != 0) { const uint32_t now = millis(); if (!this->is_connected()) { if (now - this->last_connected_ > this->reboot_timeout_) { ESP_LOGE(TAG, "No client connected to API. Rebooting..."); App.reboot(); } this->status_set_warning(); } else { this->last_connected_ = now; this->status_clear_warning(); } } } void APIServer::dump_config() { ESP_LOGCONFIG(TAG, "API Server:"); ESP_LOGCONFIG(TAG, " Address: %s:%u", network_get_address().c_str(), this->port_); } bool APIServer::uses_password() const { return !this->password_.empty(); } bool APIServer::check_password(const std::string &password) const { // depend only on input password length const char *a = this->password_.c_str(); uint32_t len_a = this->password_.length(); const char *b = password.c_str(); uint32_t len_b = password.length(); // disable optimization with volatile volatile uint32_t length = len_b; volatile const char *left = nullptr; volatile const char *right = b; uint8_t result = 0; if (len_a == length) { left = *((volatile const char **) &a); result = 0; } if (len_a != length) { left = b; result = 1; } for (size_t i = 0; i < length; i++) { result |= *left++ ^ *right++; // NOLINT } return result == 0; } void APIServer::handle_disconnect(APIConnection *conn) {} #ifdef USE_BINARY_SENSOR void APIServer::on_binary_sensor_update(binary_sensor::BinarySensor *obj, bool state) { if (obj->is_internal()) return; for (auto *c : this->clients_) c->send_binary_sensor_state(obj, state); } #endif #ifdef USE_COVER void APIServer::on_cover_update(cover::Cover *obj) { if (obj->is_internal()) return; for (auto *c : this->clients_) c->send_cover_state(obj); } #endif #ifdef USE_FAN void APIServer::on_fan_update(fan::FanState *obj) { if (obj->is_internal()) return; for (auto *c : this->clients_) c->send_fan_state(obj); } #endif #ifdef USE_LIGHT void APIServer::on_light_update(light::LightState *obj) { if (obj->is_internal()) return; for (auto *c : this->clients_) c->send_light_state(obj); } #endif #ifdef USE_SENSOR void APIServer::on_sensor_update(sensor::Sensor *obj, float state) { if (obj->is_internal()) return; for (auto *c : this->clients_) c->send_sensor_state(obj, state); } #endif #ifdef USE_SWITCH void APIServer::on_switch_update(switch_::Switch *obj, bool state) { if (obj->is_internal()) return; for (auto *c : this->clients_) c->send_switch_state(obj, state); } #endif #ifdef USE_TEXT_SENSOR void APIServer::on_text_sensor_update(text_sensor::TextSensor *obj, std::string state) { if (obj->is_internal()) return; for (auto *c : this->clients_) c->send_text_sensor_state(obj, state); } #endif #ifdef USE_CLIMATE void APIServer::on_climate_update(climate::Climate *obj) { if (obj->is_internal()) return; for (auto *c : this->clients_) c->send_climate_state(obj); } #endif float APIServer::get_setup_priority() const { return setup_priority::AFTER_WIFI; } void APIServer::set_port(uint16_t port) { this->port_ = port; } APIServer *global_api_server = nullptr; void APIServer::set_password(const std::string &password) { this->password_ = password; } void APIServer::send_service_call(ServiceCallResponse &call) { for (auto *client : this->clients_) { client->send_service_call(call); } } APIServer::APIServer() { global_api_server = this; } void APIServer::subscribe_home_assistant_state(std::string entity_id, std::function f) { this->state_subs_.push_back(HomeAssistantStateSubscription{ .entity_id = std::move(entity_id), .callback = std::move(f), }); } const std::vector &APIServer::get_state_subs() const { return this->state_subs_; } uint16_t APIServer::get_port() const { return this->port_; } void APIServer::set_reboot_timeout(uint32_t reboot_timeout) { this->reboot_timeout_ = reboot_timeout; } #ifdef USE_HOMEASSISTANT_TIME void APIServer::request_time() { for (auto *client : this->clients_) { if (!client->remove_ && client->connection_state_ == APIConnection::ConnectionState::CONNECTED) client->send_time_request(); } } #endif bool APIServer::is_connected() const { return !this->clients_.empty(); } void APIServer::on_shutdown() { for (auto *c : this->clients_) { c->send_disconnect_request(); } delay(10); } // APIConnection APIConnection::APIConnection(AsyncClient *client, APIServer *parent) : client_(client), parent_(parent), initial_state_iterator_(parent, this), list_entities_iterator_(parent, this) { this->client_->onError([](void *s, AsyncClient *c, int8_t error) { ((APIConnection *) s)->on_error_(error); }, this); this->client_->onDisconnect([](void *s, AsyncClient *c) { ((APIConnection *) s)->on_disconnect_(); }, this); this->client_->onTimeout([](void *s, AsyncClient *c, uint32_t time) { ((APIConnection *) s)->on_timeout_(time); }, this); this->client_->onData([](void *s, AsyncClient *c, void *buf, size_t len) { ((APIConnection *) s)->on_data_(reinterpret_cast(buf), len); }, this); this->send_buffer_.reserve(64); this->recv_buffer_.reserve(32); this->client_info_ = this->client_->remoteIP().toString().c_str(); this->last_traffic_ = millis(); } APIConnection::~APIConnection() { delete this->client_; } void APIConnection::on_error_(int8_t error) { // disconnect will also be called, nothing to do here this->remove_ = true; } void APIConnection::on_disconnect_() { // delete self, generally unsafe but not in this case. this->remove_ = true; } void APIConnection::on_timeout_(uint32_t time) { this->disconnect_client(); } void APIConnection::on_data_(uint8_t *buf, size_t len) { if (len == 0 || buf == nullptr) return; this->recv_buffer_.insert(this->recv_buffer_.end(), buf, buf + len); // TODO: On ESP32, use queue to notify main thread of new data } void APIConnection::parse_recv_buffer_() { if (this->recv_buffer_.empty() || this->remove_) return; while (!this->recv_buffer_.empty()) { if (this->recv_buffer_[0] != 0x00) { ESP_LOGW(TAG, "Invalid preamble from %s", this->client_info_.c_str()); this->fatal_error_(); return; } uint32_t i = 1; const uint32_t size = this->recv_buffer_.size(); uint32_t msg_size = 0; while (i < size) { const uint8_t dat = this->recv_buffer_[i]; msg_size |= (dat & 0x7F); // consume i += 1; if ((dat & 0x80) == 0x00) { break; } else { msg_size <<= 7; } } if (i == size) // not enough data there yet return; uint32_t msg_type = 0; bool msg_type_done = false; while (i < size) { const uint8_t dat = this->recv_buffer_[i]; msg_type |= (dat & 0x7F); // consume i += 1; if ((dat & 0x80) == 0x00) { msg_type_done = true; break; } else { msg_type <<= 7; } } if (!msg_type_done) // not enough data there yet return; if (size - i < msg_size) // message body not fully received return; // ESP_LOGVV(TAG, "RECV Message: Size=%u Type=%u", msg_size, msg_type); if (!this->valid_rx_message_type_(msg_type)) { ESP_LOGE(TAG, "Not a valid message type: %u", msg_type); this->fatal_error_(); return; } uint8_t *msg = &this->recv_buffer_[i]; this->read_message_(msg_size, msg_type, msg); if (this->remove_) return; // pop front uint32_t total = i + msg_size; this->recv_buffer_.erase(this->recv_buffer_.begin(), this->recv_buffer_.begin() + total); } } void APIConnection::read_message_(uint32_t size, uint32_t type, uint8_t *msg) { this->last_traffic_ = millis(); switch (static_cast(type)) { case APIMessageType::HELLO_REQUEST: { HelloRequest req; req.decode(msg, size); this->on_hello_request_(req); break; } case APIMessageType::HELLO_RESPONSE: { // Invalid break; } case APIMessageType::CONNECT_REQUEST: { ConnectRequest req; req.decode(msg, size); this->on_connect_request_(req); break; } case APIMessageType::CONNECT_RESPONSE: // Invalid break; case APIMessageType::DISCONNECT_REQUEST: { DisconnectRequest req; req.decode(msg, size); this->on_disconnect_request_(req); break; } case APIMessageType::DISCONNECT_RESPONSE: { DisconnectResponse req; req.decode(msg, size); this->on_disconnect_response_(req); break; } case APIMessageType::PING_REQUEST: { PingRequest req; req.decode(msg, size); this->on_ping_request_(req); break; } case APIMessageType::PING_RESPONSE: { PingResponse req; req.decode(msg, size); this->on_ping_response_(req); break; } case APIMessageType::DEVICE_INFO_REQUEST: { DeviceInfoRequest req; req.decode(msg, size); this->on_device_info_request_(req); break; } case APIMessageType::DEVICE_INFO_RESPONSE: { // Invalid break; } case APIMessageType::LIST_ENTITIES_REQUEST: { ListEntitiesRequest req; req.decode(msg, size); this->on_list_entities_request_(req); break; } case APIMessageType::LIST_ENTITIES_BINARY_SENSOR_RESPONSE: case APIMessageType::LIST_ENTITIES_COVER_RESPONSE: case APIMessageType::LIST_ENTITIES_FAN_RESPONSE: case APIMessageType::LIST_ENTITIES_LIGHT_RESPONSE: case APIMessageType::LIST_ENTITIES_SENSOR_RESPONSE: case APIMessageType::LIST_ENTITIES_SWITCH_RESPONSE: case APIMessageType::LIST_ENTITIES_TEXT_SENSOR_RESPONSE: case APIMessageType::LIST_ENTITIES_SERVICE_RESPONSE: case APIMessageType::LIST_ENTITIES_CAMERA_RESPONSE: case APIMessageType::LIST_ENTITIES_CLIMATE_RESPONSE: case APIMessageType::LIST_ENTITIES_DONE_RESPONSE: // Invalid break; case APIMessageType::SUBSCRIBE_STATES_REQUEST: { SubscribeStatesRequest req; req.decode(msg, size); this->on_subscribe_states_request_(req); break; } case APIMessageType::BINARY_SENSOR_STATE_RESPONSE: case APIMessageType::COVER_STATE_RESPONSE: case APIMessageType::FAN_STATE_RESPONSE: case APIMessageType::LIGHT_STATE_RESPONSE: case APIMessageType::SENSOR_STATE_RESPONSE: case APIMessageType::SWITCH_STATE_RESPONSE: case APIMessageType::TEXT_SENSOR_STATE_RESPONSE: case APIMessageType::CAMERA_IMAGE_RESPONSE: case APIMessageType::CLIMATE_STATE_RESPONSE: // Invalid break; case APIMessageType::SUBSCRIBE_LOGS_REQUEST: { SubscribeLogsRequest req; req.decode(msg, size); this->on_subscribe_logs_request_(req); break; } case APIMessageType ::SUBSCRIBE_LOGS_RESPONSE: // Invalid break; case APIMessageType::COVER_COMMAND_REQUEST: { #ifdef USE_COVER CoverCommandRequest req; req.decode(msg, size); this->on_cover_command_request_(req); #endif break; } case APIMessageType::FAN_COMMAND_REQUEST: { #ifdef USE_FAN FanCommandRequest req; req.decode(msg, size); this->on_fan_command_request_(req); #endif break; } case APIMessageType::LIGHT_COMMAND_REQUEST: { #ifdef USE_LIGHT LightCommandRequest req; req.decode(msg, size); this->on_light_command_request_(req); #endif break; } case APIMessageType::SWITCH_COMMAND_REQUEST: { #ifdef USE_SWITCH SwitchCommandRequest req; req.decode(msg, size); this->on_switch_command_request_(req); #endif break; } case APIMessageType::CLIMATE_COMMAND_REQUEST: { #ifdef USE_CLIMATE ClimateCommandRequest req; req.decode(msg, size); this->on_climate_command_request_(req); #endif break; } case APIMessageType::SUBSCRIBE_SERVICE_CALLS_REQUEST: { SubscribeServiceCallsRequest req; req.decode(msg, size); this->on_subscribe_service_calls_request_(req); break; } case APIMessageType::SERVICE_CALL_RESPONSE: // Invalid break; case APIMessageType::GET_TIME_REQUEST: // Invalid break; case APIMessageType::GET_TIME_RESPONSE: { #ifdef USE_HOMEASSISTANT_TIME homeassistant::GetTimeResponse req; req.decode(msg, size); #endif break; } case APIMessageType::SUBSCRIBE_HOME_ASSISTANT_STATES_REQUEST: { SubscribeHomeAssistantStatesRequest req; req.decode(msg, size); this->on_subscribe_home_assistant_states_request_(req); break; } case APIMessageType::SUBSCRIBE_HOME_ASSISTANT_STATE_RESPONSE: // Invalid break; case APIMessageType::HOME_ASSISTANT_STATE_RESPONSE: { HomeAssistantStateResponse req; req.decode(msg, size); this->on_home_assistant_state_response_(req); break; } case APIMessageType::EXECUTE_SERVICE_REQUEST: { ExecuteServiceRequest req; req.decode(msg, size); this->on_execute_service_(req); break; } case APIMessageType::CAMERA_IMAGE_REQUEST: { #ifdef USE_ESP32_CAMERA CameraImageRequest req; req.decode(msg, size); this->on_camera_image_request_(req); #endif break; } } } void APIConnection::on_hello_request_(const HelloRequest &req) { ESP_LOGVV(TAG, "on_hello_request_(client_info='%s')", req.get_client_info().c_str()); this->client_info_ = req.get_client_info() + " (" + this->client_->remoteIP().toString().c_str(); this->client_info_ += ")"; ESP_LOGV(TAG, "Hello from client: '%s'", this->client_info_.c_str()); auto buffer = this->get_buffer(); // uint32 api_version_major = 1; -> 1 buffer.encode_uint32(1, 1); // uint32 api_version_minor = 2; -> 1 buffer.encode_uint32(2, 1); // string server_info = 3; buffer.encode_string(3, App.get_name() + " (esphome v" ESPHOME_VERSION ")"); bool success = this->send_buffer(APIMessageType::HELLO_RESPONSE); if (!success) { this->fatal_error_(); return; } this->connection_state_ = ConnectionState::WAITING_FOR_CONNECT; } void APIConnection::on_connect_request_(const ConnectRequest &req) { ESP_LOGVV(TAG, "on_connect_request_(password='%s')", req.get_password().c_str()); bool correct = this->parent_->check_password(req.get_password()); auto buffer = this->get_buffer(); // bool invalid_password = 1; buffer.encode_bool(1, !correct); bool success = this->send_buffer(APIMessageType::CONNECT_RESPONSE); if (!success) { this->fatal_error_(); return; } if (correct) { ESP_LOGD(TAG, "Client '%s' connected successfully!", this->client_info_.c_str()); this->connection_state_ = ConnectionState::CONNECTED; #ifdef USE_HOMEASSISTANT_TIME if (homeassistant::global_homeassistant_time != nullptr) { this->send_time_request(); } #endif } } void APIConnection::on_disconnect_request_(const DisconnectRequest &req) { ESP_LOGVV(TAG, "on_disconnect_request_"); // remote initiated disconnect_client if (!this->send_empty_message(APIMessageType::DISCONNECT_RESPONSE)) { this->fatal_error_(); return; } this->disconnect_client(); } void APIConnection::on_disconnect_response_(const DisconnectResponse &req) { ESP_LOGVV(TAG, "on_disconnect_response_"); // we initiated disconnect_client this->disconnect_client(); } void APIConnection::on_ping_request_(const PingRequest &req) { ESP_LOGVV(TAG, "on_ping_request_"); PingResponse resp; this->send_message(resp); } void APIConnection::on_ping_response_(const PingResponse &req) { ESP_LOGVV(TAG, "on_ping_response_"); // we initiated ping this->sent_ping_ = false; } void APIConnection::on_device_info_request_(const DeviceInfoRequest &req) { ESP_LOGVV(TAG, "on_device_info_request_"); auto buffer = this->get_buffer(); // bool uses_password = 1; buffer.encode_bool(1, this->parent_->uses_password()); // string name = 2; buffer.encode_string(2, App.get_name()); // string mac_address = 3; buffer.encode_string(3, get_mac_address_pretty()); // string esphome_version = 4; buffer.encode_string(4, ESPHOME_VERSION); // string compilation_time = 5; buffer.encode_string(5, App.get_compilation_time()); #ifdef ARDUINO_BOARD // string model = 6; buffer.encode_string(6, ARDUINO_BOARD); #endif #ifdef USE_DEEP_SLEEP // bool has_deep_sleep = 7; buffer.encode_bool(7, deep_sleep::global_has_deep_sleep); #endif this->send_buffer(APIMessageType::DEVICE_INFO_RESPONSE); } void APIConnection::on_list_entities_request_(const ListEntitiesRequest &req) { ESP_LOGVV(TAG, "on_list_entities_request_"); this->list_entities_iterator_.begin(); } void APIConnection::on_subscribe_states_request_(const SubscribeStatesRequest &req) { ESP_LOGVV(TAG, "on_subscribe_states_request_"); this->state_subscription_ = true; this->initial_state_iterator_.begin(); } void APIConnection::on_subscribe_logs_request_(const SubscribeLogsRequest &req) { ESP_LOGVV(TAG, "on_subscribe_logs_request_"); this->log_subscription_ = req.get_level(); if (req.get_dump_config()) { App.schedule_dump_config(); } } void APIConnection::fatal_error_() { this->client_->close(); this->remove_ = true; } bool APIConnection::valid_rx_message_type_(uint32_t type) { switch (static_cast(type)) { case APIMessageType::HELLO_RESPONSE: case APIMessageType::CONNECT_RESPONSE: return false; case APIMessageType::HELLO_REQUEST: return this->connection_state_ == ConnectionState::WAITING_FOR_HELLO; case APIMessageType::CONNECT_REQUEST: return this->connection_state_ == ConnectionState::WAITING_FOR_CONNECT; case APIMessageType::PING_REQUEST: case APIMessageType::PING_RESPONSE: case APIMessageType::DISCONNECT_REQUEST: case APIMessageType::DISCONNECT_RESPONSE: case APIMessageType::DEVICE_INFO_REQUEST: if (this->connection_state_ == ConnectionState::WAITING_FOR_CONNECT) return true; default: return this->connection_state_ == ConnectionState::CONNECTED; } } bool APIConnection::send_message(APIMessage &msg) { this->send_buffer_.clear(); APIBuffer buf(&this->send_buffer_); msg.encode(buf); return this->send_buffer(msg.message_type()); } bool APIConnection::send_empty_message(APIMessageType type) { this->send_buffer_.clear(); return this->send_buffer(type); } void APIConnection::disconnect_client() { this->client_->close(); this->remove_ = true; } void encode_varint(uint8_t *dat, uint8_t *len, uint32_t value) { if (value <= 0x7F) { *dat = value; (*len)++; return; } while (value) { uint8_t temp = value & 0x7F; value >>= 7; if (value) { *dat = temp | 0x80; } else { *dat = temp; } dat++; (*len)++; } } bool APIConnection::send_buffer(APIMessageType type) { uint8_t header[20]; header[0] = 0x00; uint8_t header_len = 1; encode_varint(header + header_len, &header_len, this->send_buffer_.size()); encode_varint(header + header_len, &header_len, static_cast(type)); size_t needed_space = this->send_buffer_.size() + header_len; if (needed_space > this->client_->space()) { delay(0); if (needed_space > this->client_->space()) { if (type != APIMessageType::SUBSCRIBE_LOGS_RESPONSE) { ESP_LOGV(TAG, "Cannot send message because of TCP buffer space"); } delay(0); return false; } } // char buffer[512]; // uint32_t offset = 0; // for (int j = 0; j < header_len; j++) { // offset += snprintf(buffer + offset, 512 - offset, "0x%02X ", header[j]); // } // offset += snprintf(buffer + offset, 512 - offset, "| "); // for (auto &it : this->send_buffer_) { // int i = snprintf(buffer + offset, 512 - offset, "0x%02X ", it); // if (i <= 0) // break; // offset += i; // } // ESP_LOGVV(TAG, "SEND %s", buffer); this->client_->add(reinterpret_cast(header), header_len); this->client_->add(reinterpret_cast(this->send_buffer_.data()), this->send_buffer_.size()); return this->client_->send(); } void APIConnection::loop() { if (!network_is_connected()) { // when network is disconnected force disconnect immediately // don't wait for timeout this->fatal_error_(); return; } if (this->client_->disconnected()) { // failsafe for disconnect logic this->on_disconnect_(); return; } this->parse_recv_buffer_(); this->list_entities_iterator_.advance(); this->initial_state_iterator_.advance(); const uint32_t keepalive = 60000; if (this->sent_ping_) { if (millis() - this->last_traffic_ > (keepalive * 3) / 2) { ESP_LOGW(TAG, "'%s' didn't respond to ping request in time. Disconnecting...", this->client_info_.c_str()); this->disconnect_client(); } } else if (millis() - this->last_traffic_ > keepalive) { this->sent_ping_ = true; this->send_ping_request(); } #ifdef USE_ESP32_CAMERA if (this->image_reader_.available()) { uint32_t space = this->client_->space(); // reserve 15 bytes for metadata, and at least 64 bytes of data if (space >= 15 + 64) { uint32_t to_send = std::min(space - 15, this->image_reader_.available()); auto buffer = this->get_buffer(); // fixed32 key = 1; buffer.encode_fixed32(1, esp32_camera::global_esp32_camera->get_object_id_hash()); // bytes data = 2; buffer.encode_bytes(2, this->image_reader_.peek_data_buffer(), to_send); // bool done = 3; bool done = this->image_reader_.available() == to_send; buffer.encode_bool(3, done); bool success = this->send_buffer(APIMessageType::CAMERA_IMAGE_RESPONSE); if (success) { this->image_reader_.consume_data(to_send); } if (success && done) { this->image_reader_.return_image(); } } } #endif } #ifdef USE_BINARY_SENSOR bool APIConnection::send_binary_sensor_state(binary_sensor::BinarySensor *binary_sensor, bool state) { if (!this->state_subscription_) return false; auto buffer = this->get_buffer(); // fixed32 key = 1; buffer.encode_fixed32(1, binary_sensor->get_object_id_hash()); // bool state = 2; buffer.encode_bool(2, state); return this->send_buffer(APIMessageType::BINARY_SENSOR_STATE_RESPONSE); } #endif #ifdef USE_COVER bool APIConnection::send_cover_state(cover::Cover *cover) { if (!this->state_subscription_) return false; auto buffer = this->get_buffer(); auto traits = cover->get_traits(); // fixed32 key = 1; buffer.encode_fixed32(1, cover->get_object_id_hash()); // enum LegacyCoverState { // OPEN = 0; // CLOSED = 1; // } // LegacyCoverState legacy_state = 2; uint32_t state = (cover->position == cover::COVER_OPEN) ? 0 : 1; buffer.encode_uint32(2, state); // float position = 3; buffer.encode_float(3, cover->position); if (traits.get_supports_tilt()) { // float tilt = 4; buffer.encode_float(4, cover->tilt); } // enum CoverCurrentOperation { // IDLE = 0; // IS_OPENING = 1; // IS_CLOSING = 2; // } // CoverCurrentOperation current_operation = 5; buffer.encode_uint32(5, cover->current_operation); return this->send_buffer(APIMessageType::COVER_STATE_RESPONSE); } #endif #ifdef USE_FAN bool APIConnection::send_fan_state(fan::FanState *fan) { if (!this->state_subscription_) return false; auto buffer = this->get_buffer(); // fixed32 key = 1; buffer.encode_fixed32(1, fan->get_object_id_hash()); // bool state = 2; buffer.encode_bool(2, fan->state); // bool oscillating = 3; if (fan->get_traits().supports_oscillation()) { buffer.encode_bool(3, fan->oscillating); } // enum FanSpeed { // LOW = 0; // MEDIUM = 1; // HIGH = 2; // } // FanSpeed speed = 4; if (fan->get_traits().supports_speed()) { buffer.encode_uint32(4, fan->speed); } return this->send_buffer(APIMessageType::FAN_STATE_RESPONSE); } #endif #ifdef USE_LIGHT bool APIConnection::send_light_state(light::LightState *light) { if (!this->state_subscription_) return false; auto buffer = this->get_buffer(); auto traits = light->get_traits(); auto values = light->remote_values; // fixed32 key = 1; buffer.encode_fixed32(1, light->get_object_id_hash()); // bool state = 2; buffer.encode_bool(2, values.get_state() != 0.0f); // float brightness = 3; if (traits.get_supports_brightness()) { buffer.encode_float(3, values.get_brightness()); } if (traits.get_supports_rgb()) { // float red = 4; buffer.encode_float(4, values.get_red()); // float green = 5; buffer.encode_float(5, values.get_green()); // float blue = 6; buffer.encode_float(6, values.get_blue()); } // float white = 7; if (traits.get_supports_rgb_white_value()) { buffer.encode_float(7, values.get_white()); } // float color_temperature = 8; if (traits.get_supports_color_temperature()) { buffer.encode_float(8, values.get_color_temperature()); } // string effect = 9; if (light->supports_effects()) { buffer.encode_string(9, light->get_effect_name()); } return this->send_buffer(APIMessageType::LIGHT_STATE_RESPONSE); } #endif #ifdef USE_SENSOR bool APIConnection::send_sensor_state(sensor::Sensor *sensor, float state) { if (!this->state_subscription_) return false; auto buffer = this->get_buffer(); // fixed32 key = 1; buffer.encode_fixed32(1, sensor->get_object_id_hash()); // float state = 2; buffer.encode_float(2, state); return this->send_buffer(APIMessageType::SENSOR_STATE_RESPONSE); } #endif #ifdef USE_SWITCH bool APIConnection::send_switch_state(switch_::Switch *a_switch, bool state) { if (!this->state_subscription_) return false; auto buffer = this->get_buffer(); // fixed32 key = 1; buffer.encode_fixed32(1, a_switch->get_object_id_hash()); // bool state = 2; buffer.encode_bool(2, state); return this->send_buffer(APIMessageType::SWITCH_STATE_RESPONSE); } #endif #ifdef USE_TEXT_SENSOR bool APIConnection::send_text_sensor_state(text_sensor::TextSensor *text_sensor, std::string state) { if (!this->state_subscription_) return false; auto buffer = this->get_buffer(); // fixed32 key = 1; buffer.encode_fixed32(1, text_sensor->get_object_id_hash()); // string state = 2; buffer.encode_string(2, state); return this->send_buffer(APIMessageType::TEXT_SENSOR_STATE_RESPONSE); } #endif #ifdef USE_CLIMATE bool APIConnection::send_climate_state(climate::Climate *climate) { if (!this->state_subscription_) return false; auto buffer = this->get_buffer(); auto traits = climate->get_traits(); // fixed32 key = 1; buffer.encode_fixed32(1, climate->get_object_id_hash()); // ClimateMode mode = 2; buffer.encode_uint32(2, static_cast(climate->mode)); // float current_temperature = 3; if (traits.get_supports_current_temperature()) { buffer.encode_float(3, climate->current_temperature); } if (traits.get_supports_two_point_target_temperature()) { // float target_temperature_low = 5; buffer.encode_float(5, climate->target_temperature_low); // float target_temperature_high = 6; buffer.encode_float(6, climate->target_temperature_high); } else { // float target_temperature = 4; buffer.encode_float(4, climate->target_temperature); } // bool away = 7; if (traits.get_supports_away()) { buffer.encode_bool(7, climate->away); } return this->send_buffer(APIMessageType::CLIMATE_STATE_RESPONSE); } #endif bool APIConnection::send_log_message(int level, const char *tag, const char *line) { if (this->log_subscription_ < level) return false; auto buffer = this->get_buffer(); // LogLevel level = 1; buffer.encode_uint32(1, static_cast(level)); // string tag = 2; // buffer.encode_string(2, tag, strlen(tag)); // string message = 3; buffer.encode_string(3, line, strlen(line)); bool success = this->send_buffer(APIMessageType::SUBSCRIBE_LOGS_RESPONSE); if (!success) { buffer = this->get_buffer(); // bool send_failed = 4; buffer.encode_bool(4, true); return this->send_buffer(APIMessageType::SUBSCRIBE_LOGS_RESPONSE); } else { return true; } } bool APIConnection::send_disconnect_request() { DisconnectRequest req; return this->send_message(req); } bool APIConnection::send_ping_request() { ESP_LOGVV(TAG, "Sending ping..."); PingRequest req; return this->send_message(req); } #ifdef USE_COVER void APIConnection::on_cover_command_request_(const CoverCommandRequest &req) { ESP_LOGVV(TAG, "on_cover_command_request_"); cover::Cover *cover = App.get_cover_by_key(req.get_key()); if (cover == nullptr) return; auto call = cover->make_call(); if (req.get_legacy_command().has_value()) { auto cmd = *req.get_legacy_command(); switch (cmd) { case LEGACY_COVER_COMMAND_OPEN: call.set_command_open(); break; case LEGACY_COVER_COMMAND_CLOSE: call.set_command_close(); break; case LEGACY_COVER_COMMAND_STOP: call.set_command_stop(); break; } } if (req.get_position().has_value()) { auto pos = *req.get_position(); call.set_position(pos); } if (req.get_tilt().has_value()) { auto tilt = *req.get_tilt(); call.set_tilt(tilt); } if (req.get_stop()) { call.set_command_stop(); } call.perform(); } #endif #ifdef USE_FAN void APIConnection::on_fan_command_request_(const FanCommandRequest &req) { ESP_LOGVV(TAG, "on_fan_command_request_"); fan::FanState *fan = App.get_fan_by_key(req.get_key()); if (fan == nullptr) return; auto call = fan->make_call(); call.set_state(req.get_state()); call.set_oscillating(req.get_oscillating()); call.set_speed(req.get_speed()); call.perform(); } #endif #ifdef USE_LIGHT void APIConnection::on_light_command_request_(const LightCommandRequest &req) { ESP_LOGVV(TAG, "on_light_command_request_"); light::LightState *light = App.get_light_by_key(req.get_key()); if (light == nullptr) return; auto call = light->make_call(); call.set_state(req.get_state()); call.set_brightness(req.get_brightness()); call.set_red(req.get_red()); call.set_green(req.get_green()); call.set_blue(req.get_blue()); call.set_white(req.get_white()); call.set_color_temperature(req.get_color_temperature()); call.set_transition_length(req.get_transition_length()); call.set_flash_length(req.get_flash_length()); call.set_effect(req.get_effect()); call.perform(); } #endif #ifdef USE_SWITCH void APIConnection::on_switch_command_request_(const SwitchCommandRequest &req) { ESP_LOGVV(TAG, "on_switch_command_request_"); switch_::Switch *a_switch = App.get_switch_by_key(req.get_key()); if (a_switch == nullptr || a_switch->is_internal()) return; if (req.get_state()) { a_switch->turn_on(); } else { a_switch->turn_off(); } } #endif #ifdef USE_CLIMATE void APIConnection::on_climate_command_request_(const ClimateCommandRequest &req) { ESP_LOGVV(TAG, "on_climate_command_request_"); climate::Climate *climate = App.get_climate_by_key(req.get_key()); if (climate == nullptr) return; auto call = climate->make_call(); if (req.get_mode().has_value()) call.set_mode(*req.get_mode()); if (req.get_target_temperature().has_value()) call.set_target_temperature(*req.get_target_temperature()); if (req.get_target_temperature_low().has_value()) call.set_target_temperature_low(*req.get_target_temperature_low()); if (req.get_target_temperature_high().has_value()) call.set_target_temperature_high(*req.get_target_temperature_high()); if (req.get_away().has_value()) call.set_away(*req.get_away()); call.perform(); } #endif void APIConnection::on_subscribe_service_calls_request_(const SubscribeServiceCallsRequest &req) { this->service_call_subscription_ = true; } void APIConnection::send_service_call(ServiceCallResponse &call) { if (!this->service_call_subscription_) return; this->send_message(call); } void APIConnection::on_subscribe_home_assistant_states_request_(const SubscribeHomeAssistantStatesRequest &req) { for (auto &it : this->parent_->get_state_subs()) { auto buffer = this->get_buffer(); // string entity_id = 1; buffer.encode_string(1, it.entity_id); this->send_buffer(APIMessageType::SUBSCRIBE_HOME_ASSISTANT_STATE_RESPONSE); } } void APIConnection::on_home_assistant_state_response_(const HomeAssistantStateResponse &req) { for (auto &it : this->parent_->get_state_subs()) { if (it.entity_id == req.get_entity_id()) { it.callback(req.get_state()); } } } void APIConnection::on_execute_service_(const ExecuteServiceRequest &req) { ESP_LOGVV(TAG, "on_execute_service_"); bool found = false; for (auto *service : this->parent_->get_user_services()) { if (service->execute_service(req)) { found = true; } } if (!found) { ESP_LOGV(TAG, "Could not find matching service!"); } } APIBuffer APIConnection::get_buffer() { this->send_buffer_.clear(); return {&this->send_buffer_}; } #ifdef USE_HOMEASSISTANT_TIME void APIConnection::send_time_request() { this->send_empty_message(APIMessageType::GET_TIME_REQUEST); } #endif #ifdef USE_ESP32_CAMERA void APIConnection::send_camera_state(std::shared_ptr image) { if (!this->state_subscription_) return; if (this->image_reader_.available()) return; this->image_reader_.set_image(image); } #endif #ifdef USE_ESP32_CAMERA void APIConnection::on_camera_image_request_(const CameraImageRequest &req) { if (esp32_camera::global_esp32_camera == nullptr) return; ESP_LOGV(TAG, "on_camera_image_request_ stream=%s single=%s", YESNO(req.get_stream()), YESNO(req.get_single())); if (req.get_single()) { esp32_camera::global_esp32_camera->request_image(); } if (req.get_stream()) { esp32_camera::global_esp32_camera->request_stream(); } } #endif } // namespace api } // namespace esphome