diff --git a/esphome/components/api/__init__.py b/esphome/components/api/__init__.py index 1076ebc707..d6b4416af8 100644 --- a/esphome/components/api/__init__.py +++ b/esphome/components/api/__init__.py @@ -18,6 +18,8 @@ from esphome.const import ( CONF_TRIGGER_ID, CONF_EVENT, CONF_TAG, + CONF_ON_CLIENT_CONNECTED, + CONF_ON_CLIENT_DISCONNECTED, ) from esphome.core import coroutine_with_priority @@ -87,6 +89,12 @@ CONFIG_SCHEMA = cv.Schema( cv.Required(CONF_KEY): validate_encryption_key, } ), + cv.Optional(CONF_ON_CLIENT_CONNECTED): automation.validate_automation( + single=True + ), + cv.Optional(CONF_ON_CLIENT_DISCONNECTED): automation.validate_automation( + single=True + ), } ).extend(cv.COMPONENT_SCHEMA) @@ -116,6 +124,20 @@ async def to_code(config): cg.add(var.register_user_service(trigger)) await automation.build_automation(trigger, func_args, conf) + if CONF_ON_CLIENT_CONNECTED in config: + await automation.build_automation( + var.get_client_connected_trigger(), + [(cg.std_string, "client_info"), (cg.std_string, "client_address")], + config[CONF_ON_CLIENT_CONNECTED], + ) + + if CONF_ON_CLIENT_DISCONNECTED in config: + await automation.build_automation( + var.get_client_disconnected_trigger(), + [(cg.std_string, "client_info"), (cg.std_string, "client_address")], + config[CONF_ON_CLIENT_DISCONNECTED], + ) + if encryption_config := config.get(CONF_ENCRYPTION): decoded = base64.b64decode(encryption_config[CONF_KEY]) cg.add(var.set_noise_psk(list(decoded))) diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 3172b71fa2..ea7a53266f 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -31,9 +31,9 @@ APIConnection::APIConnection(std::unique_ptr sock, APIServer *pa this->proto_write_buffer_.reserve(64); #if defined(USE_API_PLAINTEXT) - helper_ = std::unique_ptr{new APIPlaintextFrameHelper(std::move(sock))}; + this->helper_ = std::unique_ptr{new APIPlaintextFrameHelper(std::move(sock))}; #elif defined(USE_API_NOISE) - helper_ = std::unique_ptr{new APINoiseFrameHelper(std::move(sock), parent->get_noise_ctx())}; + this->helper_ = std::unique_ptr{new APINoiseFrameHelper(std::move(sock), parent->get_noise_ctx())}; #else #error "No frame helper defined" #endif @@ -41,14 +41,16 @@ APIConnection::APIConnection(std::unique_ptr sock, APIServer *pa void APIConnection::start() { this->last_traffic_ = millis(); - APIError err = helper_->init(); + APIError err = this->helper_->init(); if (err != APIError::OK) { on_fatal_error(); - ESP_LOGW(TAG, "%s: Helper init failed: %s errno=%d", client_info_.c_str(), api_error_to_str(err), errno); + ESP_LOGW(TAG, "%s: Helper init failed: %s errno=%d", this->client_combined_info_.c_str(), api_error_to_str(err), + errno); return; } - client_info_ = helper_->getpeername(); - helper_->set_log_info(client_info_); + this->client_info_ = helper_->getpeername(); + this->client_peername_ = this->client_info_; + this->helper_->set_log_info(this->client_info_); } APIConnection::~APIConnection() { @@ -57,6 +59,11 @@ APIConnection::~APIConnection() { bluetooth_proxy::global_bluetooth_proxy->unsubscribe_api_connection(this); } #endif +#ifdef USE_VOICE_ASSISTANT + if (voice_assistant::global_voice_assistant->get_api_connection() == this) { + voice_assistant::global_voice_assistant->client_subscription(this, false); + } +#endif } void APIConnection::loop() { @@ -67,7 +74,7 @@ void APIConnection::loop() { // when network is disconnected force disconnect immediately // don't wait for timeout this->on_fatal_error(); - ESP_LOGW(TAG, "%s: Network unavailable, disconnecting", client_info_.c_str()); + ESP_LOGW(TAG, "%s: Network unavailable, disconnecting", this->client_combined_info_.c_str()); return; } if (this->next_close_) { @@ -77,24 +84,26 @@ void APIConnection::loop() { return; } - APIError err = helper_->loop(); + APIError err = this->helper_->loop(); if (err != APIError::OK) { on_fatal_error(); - ESP_LOGW(TAG, "%s: Socket operation failed: %s errno=%d", client_info_.c_str(), api_error_to_str(err), errno); + ESP_LOGW(TAG, "%s: Socket operation failed: %s errno=%d", this->client_combined_info_.c_str(), + api_error_to_str(err), errno); return; } ReadPacketBuffer buffer; - err = helper_->read_packet(&buffer); + err = this->helper_->read_packet(&buffer); if (err == APIError::WOULD_BLOCK) { // pass } else if (err != APIError::OK) { on_fatal_error(); if (err == APIError::SOCKET_READ_FAILED && errno == ECONNRESET) { - ESP_LOGW(TAG, "%s: Connection reset", client_info_.c_str()); + ESP_LOGW(TAG, "%s: Connection reset", this->client_combined_info_.c_str()); } else if (err == APIError::CONNECTION_CLOSED) { - ESP_LOGW(TAG, "%s: Connection closed", client_info_.c_str()); + ESP_LOGW(TAG, "%s: Connection closed", this->client_combined_info_.c_str()); } else { - ESP_LOGW(TAG, "%s: Reading failed: %s errno=%d", client_info_.c_str(), api_error_to_str(err), errno); + ESP_LOGW(TAG, "%s: Reading failed: %s errno=%d", this->client_combined_info_.c_str(), api_error_to_str(err), + errno); } return; } else { @@ -114,7 +123,7 @@ void APIConnection::loop() { // Disconnect if not responded within 2.5*keepalive if (now - this->last_traffic_ > (keepalive * 5) / 2) { on_fatal_error(); - ESP_LOGW(TAG, "%s didn't respond to ping request in time. Disconnecting...", this->client_info_.c_str()); + ESP_LOGW(TAG, "%s didn't respond to ping request in time. Disconnecting...", this->client_combined_info_.c_str()); } } else if (now - this->last_traffic_ > keepalive) { ESP_LOGVV(TAG, "Sending keepalive PING..."); @@ -168,7 +177,7 @@ DisconnectResponse APIConnection::disconnect(const DisconnectRequest &msg) { // remote initiated disconnect_client // don't close yet, we still need to send the disconnect response // close will happen on next loop - ESP_LOGD(TAG, "%s requested disconnected", client_info_.c_str()); + ESP_LOGD(TAG, "%s requested disconnected", this->client_combined_info_.c_str()); this->next_close_ = true; DisconnectResponse resp; return resp; @@ -907,14 +916,17 @@ BluetoothConnectionsFreeResponse APIConnection::subscribe_bluetooth_connections_ #endif #ifdef USE_VOICE_ASSISTANT -bool APIConnection::request_voice_assistant(const VoiceAssistantRequest &msg) { - if (!this->voice_assistant_subscription_) - return false; - - return this->send_voice_assistant_request(msg); +void APIConnection::subscribe_voice_assistant(const SubscribeVoiceAssistantRequest &msg) { + if (voice_assistant::global_voice_assistant != nullptr) { + voice_assistant::global_voice_assistant->client_subscription(this, msg.subscribe); + } } void APIConnection::on_voice_assistant_response(const VoiceAssistantResponse &msg) { if (voice_assistant::global_voice_assistant != nullptr) { + if (voice_assistant::global_voice_assistant->get_api_connection() != this) { + return; + } + if (msg.error) { voice_assistant::global_voice_assistant->failed_to_start(); return; @@ -927,6 +939,10 @@ void APIConnection::on_voice_assistant_response(const VoiceAssistantResponse &ms }; void APIConnection::on_voice_assistant_event_response(const VoiceAssistantEventResponse &msg) { if (voice_assistant::global_voice_assistant != nullptr) { + if (voice_assistant::global_voice_assistant->get_api_connection() != this) { + return; + } + voice_assistant::global_voice_assistant->on_event(msg); } } @@ -1006,12 +1022,14 @@ bool APIConnection::send_log_message(int level, const char *tag, const char *lin } HelloResponse APIConnection::hello(const HelloRequest &msg) { - this->client_info_ = msg.client_info + " (" + this->helper_->getpeername() + ")"; - this->helper_->set_log_info(client_info_); + this->client_info_ = msg.client_info; + this->client_peername_ = this->helper_->getpeername(); + this->client_combined_info_ = this->client_info_ + " (" + this->client_peername_ + ")"; + this->helper_->set_log_info(this->client_combined_info_); this->client_api_version_major_ = msg.api_version_major; this->client_api_version_minor_ = msg.api_version_minor; - ESP_LOGV(TAG, "Hello from client: '%s' | API Version %" PRIu32 ".%" PRIu32, this->client_info_.c_str(), - this->client_api_version_major_, this->client_api_version_minor_); + ESP_LOGV(TAG, "Hello from client: '%s' | %s | API Version %" PRIu32 ".%" PRIu32, this->client_info_.c_str(), + this->client_peername_.c_str(), this->client_api_version_major_, this->client_api_version_minor_); HelloResponse resp; resp.api_version_major = 1; @@ -1029,9 +1047,9 @@ ConnectResponse APIConnection::connect(const ConnectRequest &msg) { // bool invalid_password = 1; resp.invalid_password = !correct; if (correct) { - ESP_LOGD(TAG, "%s: Connected successfully", this->client_info_.c_str()); + ESP_LOGD(TAG, "%s: Connected successfully", this->client_combined_info_.c_str()); this->connection_state_ = ConnectionState::AUTHENTICATED; - + this->parent_->get_client_connected_trigger()->trigger(this->client_info_, this->client_peername_); #ifdef USE_HOMEASSISTANT_TIME if (homeassistant::global_homeassistant_time != nullptr) { this->send_time_request(); @@ -1105,10 +1123,11 @@ bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint32_t message_type) return false; if (!this->helper_->can_write_without_blocking()) { delay(0); - APIError err = helper_->loop(); + APIError err = this->helper_->loop(); if (err != APIError::OK) { on_fatal_error(); - ESP_LOGW(TAG, "%s: Socket operation failed: %s errno=%d", client_info_.c_str(), api_error_to_str(err), errno); + ESP_LOGW(TAG, "%s: Socket operation failed: %s errno=%d", this->client_combined_info_.c_str(), + api_error_to_str(err), errno); return false; } if (!this->helper_->can_write_without_blocking()) { @@ -1127,9 +1146,10 @@ bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint32_t message_type) if (err != APIError::OK) { on_fatal_error(); if (err == APIError::SOCKET_WRITE_FAILED && errno == ECONNRESET) { - ESP_LOGW(TAG, "%s: Connection reset", client_info_.c_str()); + ESP_LOGW(TAG, "%s: Connection reset", this->client_combined_info_.c_str()); } else { - ESP_LOGW(TAG, "%s: Packet write failed %s errno=%d", client_info_.c_str(), api_error_to_str(err), errno); + ESP_LOGW(TAG, "%s: Packet write failed %s errno=%d", this->client_combined_info_.c_str(), api_error_to_str(err), + errno); } return false; } @@ -1138,11 +1158,11 @@ bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint32_t message_type) } void APIConnection::on_unauthenticated_access() { this->on_fatal_error(); - ESP_LOGD(TAG, "%s: tried to access without authentication.", this->client_info_.c_str()); + ESP_LOGD(TAG, "%s: tried to access without authentication.", this->client_combined_info_.c_str()); } void APIConnection::on_no_setup_connection() { this->on_fatal_error(); - ESP_LOGD(TAG, "%s: tried to access without full connection.", this->client_info_.c_str()); + ESP_LOGD(TAG, "%s: tried to access without full connection.", this->client_combined_info_.c_str()); } void APIConnection::on_fatal_error() { this->helper_->close(); diff --git a/esphome/components/api/api_connection.h b/esphome/components/api/api_connection.h index 2a62c2faff..82872c75de 100644 --- a/esphome/components/api/api_connection.h +++ b/esphome/components/api/api_connection.h @@ -121,10 +121,7 @@ class APIConnection : public APIServerConnection { #endif #ifdef USE_VOICE_ASSISTANT - void subscribe_voice_assistant(const SubscribeVoiceAssistantRequest &msg) override { - this->voice_assistant_subscription_ = msg.subscribe; - } - bool request_voice_assistant(const VoiceAssistantRequest &msg); + void subscribe_voice_assistant(const SubscribeVoiceAssistantRequest &msg) override; void on_voice_assistant_response(const VoiceAssistantResponse &msg) override; void on_voice_assistant_event_response(const VoiceAssistantEventResponse &msg) override; #endif @@ -183,6 +180,8 @@ class APIConnection : public APIServerConnection { } bool send_buffer(ProtoWriteBuffer buffer, uint32_t message_type) override; + std::string get_client_combined_info() const { return this->client_combined_info_; } + protected: friend APIServer; @@ -202,6 +201,8 @@ class APIConnection : public APIServerConnection { std::unique_ptr helper_; std::string client_info_; + std::string client_peername_; + std::string client_combined_info_; uint32_t client_api_version_major_{0}; uint32_t client_api_version_minor_{0}; #ifdef USE_ESP32_CAMERA @@ -213,9 +214,6 @@ class APIConnection : public APIServerConnection { uint32_t last_traffic_; bool sent_ping_{false}; bool service_call_subscription_{false}; -#ifdef USE_VOICE_ASSISTANT - bool voice_assistant_subscription_{false}; -#endif bool next_close_ = false; APIServer *parent_; InitialStateIterator initial_state_iterator_; diff --git a/esphome/components/api/api_server.cpp b/esphome/components/api/api_server.cpp index 54266ff0f0..4b113dbd07 100644 --- a/esphome/components/api/api_server.cpp +++ b/esphome/components/api/api_server.cpp @@ -111,6 +111,7 @@ void APIServer::loop() { [](const std::unique_ptr &conn) { return !conn->remove_; }); // print disconnection messages for (auto it = new_end; it != this->clients_.end(); ++it) { + this->client_disconnected_trigger_->trigger((*it)->client_info_, (*it)->client_peername_); ESP_LOGV(TAG, "Removing connection to %s", (*it)->client_info_.c_str()); } // resize vector @@ -322,30 +323,6 @@ void APIServer::on_shutdown() { delay(10); } -#ifdef USE_VOICE_ASSISTANT -bool APIServer::start_voice_assistant(const std::string &conversation_id, uint32_t flags, - const api::VoiceAssistantAudioSettings &audio_settings) { - VoiceAssistantRequest msg; - msg.start = true; - msg.conversation_id = conversation_id; - msg.flags = flags; - msg.audio_settings = audio_settings; - for (auto &c : this->clients_) { - if (c->request_voice_assistant(msg)) - return true; - } - return false; -} -void APIServer::stop_voice_assistant() { - VoiceAssistantRequest msg; - msg.start = false; - for (auto &c : this->clients_) { - if (c->request_voice_assistant(msg)) - return; - } -} -#endif - #ifdef USE_ALARM_CONTROL_PANEL void APIServer::on_alarm_control_panel_update(alarm_control_panel::AlarmControlPanel *obj) { if (obj->is_internal()) diff --git a/esphome/components/api/api_server.h b/esphome/components/api/api_server.h index a4454d4b84..55b18a0f60 100644 --- a/esphome/components/api/api_server.h +++ b/esphome/components/api/api_server.h @@ -4,6 +4,7 @@ #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/defines.h" @@ -80,12 +81,6 @@ class APIServer : public Component, public Controller { void request_time(); #endif -#ifdef USE_VOICE_ASSISTANT - bool start_voice_assistant(const std::string &conversation_id, uint32_t flags, - const api::VoiceAssistantAudioSettings &audio_settings); - void stop_voice_assistant(); -#endif - #ifdef USE_ALARM_CONTROL_PANEL void on_alarm_control_panel_update(alarm_control_panel::AlarmControlPanel *obj) override; #endif @@ -103,6 +98,11 @@ class APIServer : public Component, public Controller { const std::vector &get_state_subs() const; const std::vector &get_user_services() const { return this->user_services_; } + Trigger *get_client_connected_trigger() const { return this->client_connected_trigger_; } + Trigger *get_client_disconnected_trigger() const { + return this->client_disconnected_trigger_; + } + protected: std::unique_ptr socket_ = nullptr; uint16_t port_{6053}; @@ -112,6 +112,8 @@ class APIServer : public Component, public Controller { std::string password_; std::vector state_subs_; std::vector user_services_; + Trigger *client_connected_trigger_ = new Trigger(); + Trigger *client_disconnected_trigger_ = new Trigger(); #ifdef USE_API_NOISE std::shared_ptr noise_ctx_ = std::make_shared(); diff --git a/esphome/components/captive_portal/captive_portal.cpp b/esphome/components/captive_portal/captive_portal.cpp index cc78528e46..78eee4b226 100644 --- a/esphome/components/captive_portal/captive_portal.cpp +++ b/esphome/components/captive_portal/captive_portal.cpp @@ -48,7 +48,7 @@ void CaptivePortal::start() { this->dns_server_ = make_unique(); this->dns_server_->setErrorReplyCode(DNSReplyCode::NoError); network::IPAddress ip = wifi::global_wifi_component->wifi_soft_ap_ip(); - this->dns_server_->start(53, "*", IPAddress(ip)); + this->dns_server_->start(53, "*", ip); #endif this->base_->get_server()->onNotFound([this](AsyncWebServerRequest *req) { diff --git a/esphome/components/mqtt/mqtt_backend_esp8266.h b/esphome/components/mqtt/mqtt_backend_esp8266.h index 981d27693f..06d4993bdf 100644 --- a/esphome/components/mqtt/mqtt_backend_esp8266.h +++ b/esphome/components/mqtt/mqtt_backend_esp8266.h @@ -19,7 +19,7 @@ class MQTTBackendESP8266 final : public MQTTBackend { void set_will(const char *topic, uint8_t qos, bool retain, const char *payload) final { mqtt_client_.setWill(topic, qos, retain, payload); } - void set_server(network::IPAddress ip, uint16_t port) final { mqtt_client_.setServer(IPAddress(ip), port); } + void set_server(network::IPAddress ip, uint16_t port) final { mqtt_client_.setServer(ip, port); } void set_server(const char *host, uint16_t port) final { mqtt_client_.setServer(host, port); } #if ASYNC_TCP_SSL_ENABLED void set_secure(bool secure) { mqtt_client.setSecure(secure); } diff --git a/esphome/components/voice_assistant/__init__.py b/esphome/components/voice_assistant/__init__.py index 14176ad7cf..3270b9f370 100644 --- a/esphome/components/voice_assistant/__init__.py +++ b/esphome/components/voice_assistant/__init__.py @@ -6,6 +6,8 @@ from esphome.const import ( CONF_MICROPHONE, CONF_SPEAKER, CONF_MEDIA_PLAYER, + CONF_ON_CLIENT_CONNECTED, + CONF_ON_CLIENT_DISCONNECTED, ) from esphome import automation from esphome.automation import register_action, register_condition @@ -80,6 +82,12 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_ON_TTS_END): automation.validate_automation(single=True), cv.Optional(CONF_ON_END): automation.validate_automation(single=True), cv.Optional(CONF_ON_ERROR): automation.validate_automation(single=True), + cv.Optional(CONF_ON_CLIENT_CONNECTED): automation.validate_automation( + single=True + ), + cv.Optional(CONF_ON_CLIENT_DISCONNECTED): automation.validate_automation( + single=True + ), } ).extend(cv.COMPONENT_SCHEMA), ) @@ -155,6 +163,20 @@ async def to_code(config): config[CONF_ON_ERROR], ) + if CONF_ON_CLIENT_CONNECTED in config: + await automation.build_automation( + var.get_client_connected_trigger(), + [], + config[CONF_ON_CLIENT_CONNECTED], + ) + + if CONF_ON_CLIENT_DISCONNECTED in config: + await automation.build_automation( + var.get_client_disconnected_trigger(), + [], + config[CONF_ON_CLIENT_DISCONNECTED], + ) + cg.add_define("USE_VOICE_ASSISTANT") diff --git a/esphome/components/voice_assistant/voice_assistant.cpp b/esphome/components/voice_assistant/voice_assistant.cpp index df7853156d..d15d702d4b 100644 --- a/esphome/components/voice_assistant/voice_assistant.cpp +++ b/esphome/components/voice_assistant/voice_assistant.cpp @@ -127,8 +127,8 @@ int VoiceAssistant::read_microphone_() { } void VoiceAssistant::loop() { - if (this->state_ != State::IDLE && this->state_ != State::STOP_MICROPHONE && - this->state_ != State::STOPPING_MICROPHONE && !api::global_api_server->is_connected()) { + if (this->api_client_ == nullptr && this->state_ != State::IDLE && this->state_ != State::STOP_MICROPHONE && + this->state_ != State::STOPPING_MICROPHONE) { if (this->mic_->is_running() || this->state_ == State::STARTING_MICROPHONE) { this->set_state_(State::STOP_MICROPHONE, State::IDLE); } else { @@ -213,7 +213,14 @@ void VoiceAssistant::loop() { audio_settings.noise_suppression_level = this->noise_suppression_level_; audio_settings.auto_gain = this->auto_gain_; audio_settings.volume_multiplier = this->volume_multiplier_; - if (!api::global_api_server->start_voice_assistant(this->conversation_id_, flags, audio_settings)) { + + api::VoiceAssistantRequest msg; + msg.start = true; + msg.conversation_id = this->conversation_id_; + msg.flags = flags; + msg.audio_settings = audio_settings; + + if (this->api_client_ == nullptr || !this->api_client_->send_voice_assistant_request(msg)) { ESP_LOGW(TAG, "Could not request start."); this->error_trigger_->trigger("not-connected", "Could not request start."); this->continuous_ = false; @@ -326,6 +333,28 @@ void VoiceAssistant::loop() { } } +void VoiceAssistant::client_subscription(api::APIConnection *client, bool subscribe) { + if (!subscribe) { + if (this->api_client_ == nullptr || client != this->api_client_) { + ESP_LOGE(TAG, "Client attempting to unsubscribe that is not the current API Client"); + return; + } + this->api_client_ = nullptr; + this->client_disconnected_trigger_->trigger(); + return; + } + + if (this->api_client_ != nullptr) { + ESP_LOGE(TAG, "Multiple API Clients attempting to connect to Voice Assistant"); + ESP_LOGE(TAG, "Current client: %s", this->api_client_->get_client_combined_info().c_str()); + ESP_LOGE(TAG, "New client: %s", client->get_client_combined_info().c_str()); + return; + } + + this->api_client_ = client; + this->client_connected_trigger_->trigger(); +} + static const LogString *voice_assistant_state_to_string(State state) { switch (state) { case State::IDLE: @@ -408,7 +437,7 @@ void VoiceAssistant::start_streaming(struct sockaddr_storage *addr, uint16_t por } void VoiceAssistant::request_start(bool continuous, bool silence_detection) { - if (!api::global_api_server->is_connected()) { + if (this->api_client_ == nullptr) { ESP_LOGE(TAG, "No API client connected"); this->set_state_(State::IDLE, State::IDLE); this->continuous_ = false; @@ -459,9 +488,14 @@ void VoiceAssistant::request_stop() { } void VoiceAssistant::signal_stop_() { - ESP_LOGD(TAG, "Signaling stop..."); - api::global_api_server->stop_voice_assistant(); memset(&this->dest_addr_, 0, sizeof(this->dest_addr_)); + if (this->api_client_ == nullptr) { + return; + } + ESP_LOGD(TAG, "Signaling stop..."); + api::VoiceAssistantRequest msg; + msg.start = false; + this->api_client_->send_voice_assistant_request(msg); } void VoiceAssistant::on_event(const api::VoiceAssistantEventResponse &msg) { diff --git a/esphome/components/voice_assistant/voice_assistant.h b/esphome/components/voice_assistant/voice_assistant.h index cd448293db..a265522bca 100644 --- a/esphome/components/voice_assistant/voice_assistant.h +++ b/esphome/components/voice_assistant/voice_assistant.h @@ -8,8 +8,8 @@ #include "esphome/core/component.h" #include "esphome/core/helpers.h" +#include "esphome/components/api/api_connection.h" #include "esphome/components/api/api_pb2.h" -#include "esphome/components/api/api_server.h" #include "esphome/components/microphone/microphone.h" #ifdef USE_SPEAKER #include "esphome/components/speaker/speaker.h" @@ -109,6 +109,12 @@ class VoiceAssistant : public Component { Trigger<> *get_end_trigger() const { return this->end_trigger_; } Trigger *get_error_trigger() const { return this->error_trigger_; } + Trigger<> *get_client_connected_trigger() const { return this->client_connected_trigger_; } + Trigger<> *get_client_disconnected_trigger() const { return this->client_disconnected_trigger_; } + + void client_subscription(api::APIConnection *client, bool subscribe); + api::APIConnection *get_api_connection() const { return this->api_client_; } + protected: int read_microphone_(); void set_state_(State state); @@ -127,6 +133,11 @@ class VoiceAssistant : public Component { Trigger<> *end_trigger_ = new Trigger<>(); Trigger *error_trigger_ = new Trigger(); + Trigger<> *client_connected_trigger_ = new Trigger<>(); + Trigger<> *client_disconnected_trigger_ = new Trigger<>(); + + api::APIConnection *api_client_{nullptr}; + microphone::Microphone *mic_{nullptr}; #ifdef USE_SPEAKER speaker::Speaker *speaker_{nullptr}; diff --git a/esphome/components/wifi/wifi_component.cpp b/esphome/components/wifi/wifi_component.cpp index b08f20de21..bd267fb0fd 100644 --- a/esphome/components/wifi/wifi_component.cpp +++ b/esphome/components/wifi/wifi_component.cpp @@ -261,8 +261,8 @@ void WiFiComponent::set_sta(const WiFiAP &ap) { void WiFiComponent::clear_sta() { this->sta_.clear(); } void WiFiComponent::save_wifi_sta(const std::string &ssid, const std::string &password) { SavedWifiSettings save{}; - strncpy(save.ssid, ssid.c_str(), sizeof(save.ssid)); - strncpy(save.password, password.c_str(), sizeof(save.password)); + strncpy(save.ssid, ssid.c_str(), sizeof(save.ssid) - 1); + strncpy(save.password, password.c_str(), sizeof(save.password) - 1); this->pref_.save(&save); // ensure it's written immediately global_preferences->sync(); diff --git a/esphome/components/wifi/wifi_component_esp32_arduino.cpp b/esphome/components/wifi/wifi_component_esp32_arduino.cpp index 17b15757ef..c68c1b950c 100644 --- a/esphome/components/wifi/wifi_component_esp32_arduino.cpp +++ b/esphome/components/wifi/wifi_component_esp32_arduino.cpp @@ -164,8 +164,8 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) { // https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/network/esp_wifi.html#_CPPv417wifi_sta_config_t wifi_config_t conf; memset(&conf, 0, sizeof(conf)); - strncpy(reinterpret_cast(conf.sta.ssid), ap.get_ssid().c_str(), sizeof(conf.sta.ssid)); - strncpy(reinterpret_cast(conf.sta.password), ap.get_password().c_str(), sizeof(conf.sta.password)); + strncpy(reinterpret_cast(conf.sta.ssid), ap.get_ssid().c_str(), sizeof(conf.sta.ssid) - 1); + strncpy(reinterpret_cast(conf.sta.password), ap.get_password().c_str(), sizeof(conf.sta.password) - 1); // The weakest authmode to accept in the fast scan mode if (ap.get_password().empty()) { @@ -661,7 +661,7 @@ bool WiFiComponent::wifi_start_ap_(const WiFiAP &ap) { wifi_config_t conf; memset(&conf, 0, sizeof(conf)); - strncpy(reinterpret_cast(conf.ap.ssid), ap.get_ssid().c_str(), sizeof(conf.ap.ssid)); + strncpy(reinterpret_cast(conf.ap.ssid), ap.get_ssid().c_str(), sizeof(conf.ap.ssid) - 1); conf.ap.channel = ap.get_channel().value_or(1); conf.ap.ssid_hidden = ap.get_ssid().size(); conf.ap.max_connection = 5; @@ -672,7 +672,7 @@ bool WiFiComponent::wifi_start_ap_(const WiFiAP &ap) { *conf.ap.password = 0; } else { conf.ap.authmode = WIFI_AUTH_WPA2_PSK; - strncpy(reinterpret_cast(conf.ap.password), ap.get_password().c_str(), sizeof(conf.ap.ssid)); + strncpy(reinterpret_cast(conf.ap.password), ap.get_password().c_str(), sizeof(conf.ap.ssid) - 1); } conf.ap.pairwise_cipher = WIFI_CIPHER_TYPE_CCMP; diff --git a/esphome/components/wifi/wifi_component_esp8266.cpp b/esphome/components/wifi/wifi_component_esp8266.cpp index a48c6c711d..b289157bee 100644 --- a/esphome/components/wifi/wifi_component_esp8266.cpp +++ b/esphome/components/wifi/wifi_component_esp8266.cpp @@ -230,8 +230,8 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) { struct station_config conf {}; memset(&conf, 0, sizeof(conf)); - strncpy(reinterpret_cast(conf.ssid), ap.get_ssid().c_str(), sizeof(conf.ssid)); - strncpy(reinterpret_cast(conf.password), ap.get_password().c_str(), sizeof(conf.password)); + strncpy(reinterpret_cast(conf.ssid), ap.get_ssid().c_str(), sizeof(conf.ssid) - 1); + strncpy(reinterpret_cast(conf.password), ap.get_password().c_str(), sizeof(conf.password) - 1); if (ap.get_bssid().has_value()) { conf.bssid_set = 1; @@ -759,7 +759,7 @@ bool WiFiComponent::wifi_start_ap_(const WiFiAP &ap) { return false; struct softap_config conf {}; - strncpy(reinterpret_cast(conf.ssid), ap.get_ssid().c_str(), sizeof(conf.ssid)); + strncpy(reinterpret_cast(conf.ssid), ap.get_ssid().c_str(), sizeof(conf.ssid) - 1); conf.ssid_len = static_cast(ap.get_ssid().size()); conf.channel = ap.get_channel().value_or(1); conf.ssid_hidden = ap.get_hidden(); @@ -771,7 +771,7 @@ bool WiFiComponent::wifi_start_ap_(const WiFiAP &ap) { *conf.password = 0; } else { conf.authmode = AUTH_WPA2_PSK; - strncpy(reinterpret_cast(conf.password), ap.get_password().c_str(), sizeof(conf.password)); + strncpy(reinterpret_cast(conf.password), ap.get_password().c_str(), sizeof(conf.password) - 1); } ETS_UART_INTR_DISABLE(); diff --git a/esphome/components/wifi/wifi_component_esp_idf.cpp b/esphome/components/wifi/wifi_component_esp_idf.cpp index 34ecaf887d..9d88848567 100644 --- a/esphome/components/wifi/wifi_component_esp_idf.cpp +++ b/esphome/components/wifi/wifi_component_esp_idf.cpp @@ -275,8 +275,8 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) { // https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/network/esp_wifi.html#_CPPv417wifi_sta_config_t wifi_config_t conf; memset(&conf, 0, sizeof(conf)); - strncpy(reinterpret_cast(conf.sta.ssid), ap.get_ssid().c_str(), sizeof(conf.sta.ssid)); - strncpy(reinterpret_cast(conf.sta.password), ap.get_password().c_str(), sizeof(conf.sta.password)); + strncpy(reinterpret_cast(conf.sta.ssid), ap.get_ssid().c_str(), sizeof(conf.sta.ssid) - 1); + strncpy(reinterpret_cast(conf.sta.password), ap.get_password().c_str(), sizeof(conf.sta.password) - 1); // The weakest authmode to accept in the fast scan mode if (ap.get_password().empty()) { @@ -823,7 +823,7 @@ bool WiFiComponent::wifi_start_ap_(const WiFiAP &ap) { wifi_config_t conf; memset(&conf, 0, sizeof(conf)); - strncpy(reinterpret_cast(conf.ap.ssid), ap.get_ssid().c_str(), sizeof(conf.ap.ssid)); + strncpy(reinterpret_cast(conf.ap.ssid), ap.get_ssid().c_str(), sizeof(conf.ap.ssid) - 1); conf.ap.channel = ap.get_channel().value_or(1); conf.ap.ssid_hidden = ap.get_ssid().size(); conf.ap.max_connection = 5; @@ -834,7 +834,7 @@ bool WiFiComponent::wifi_start_ap_(const WiFiAP &ap) { *conf.ap.password = 0; } else { conf.ap.authmode = WIFI_AUTH_WPA2_PSK; - strncpy(reinterpret_cast(conf.ap.password), ap.get_password().c_str(), sizeof(conf.ap.password)); + strncpy(reinterpret_cast(conf.ap.password), ap.get_password().c_str(), sizeof(conf.ap.password) - 1); } // pairwise cipher of SoftAP, group cipher will be derived using this. diff --git a/esphome/const.py b/esphome/const.py index 47555cc144..046895447c 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2023.10.4" +__version__ = "2023.10.5" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" VALID_SUBSTITUTIONS_CHARACTERS = ( @@ -481,6 +481,8 @@ CONF_ON_BLE_MANUFACTURER_DATA_ADVERTISE = "on_ble_manufacturer_data_advertise" CONF_ON_BLE_SERVICE_DATA_ADVERTISE = "on_ble_service_data_advertise" CONF_ON_BOOT = "on_boot" CONF_ON_CLICK = "on_click" +CONF_ON_CLIENT_CONNECTED = "on_client_connected" +CONF_ON_CLIENT_DISCONNECTED = "on_client_disconnected" CONF_ON_CONNECT = "on_connect" CONF_ON_CONTROL = "on_control" CONF_ON_DISCONNECT = "on_disconnect"