esphome/esphome/components/api/api_server.cpp

1203 lines
36 KiB
C++

#include <utility>
#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"
#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 <algorithm>
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<esp32_camera::CameraImage> 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<void(std::string)> f) {
this->state_subs_.push_back(HomeAssistantStateSubscription{
.entity_id = std::move(entity_id),
.callback = std::move(f),
});
}
const std::vector<APIServer::HomeAssistantStateSubscription> &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<uint8_t *>(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) {
ESP_LOGD(TAG, "Error from client '%s': %d", this->client_info_.c_str(), 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<APIMessageType>(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<APIMessageType>(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<uint32_t>(type));
size_t needed_space = this->send_buffer_.size() + header_len;
if (needed_space > this->client_->space()) {
delay(5);
if (needed_space > this->client_->space()) {
if (type != APIMessageType::SUBSCRIBE_LOGS_RESPONSE) {
ESP_LOGV(TAG, "Cannot send message because of TCP buffer space");
}
delay(5);
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<char *>(header), header_len);
this->client_->add(reinterpret_cast<char *>(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<uint32_t>(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<uint32_t>(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) {
auto 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<esp32_camera::CameraImage> 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