From de19d25a3c7f3993df2d51e89a7589eeaa6786b5 Mon Sep 17 00:00:00 2001 From: Oleg Tarasov Date: Mon, 16 Sep 2024 00:59:10 +0300 Subject: [PATCH 01/20] Add OpenTherm component (part 1: communication layer and hub) (#6645) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- CODEOWNERS | 1 + esphome/components/opentherm/__init__.py | 57 ++ esphome/components/opentherm/hub.cpp | 277 +++++++++ esphome/components/opentherm/hub.h | 110 ++++ esphome/components/opentherm/opentherm.cpp | 568 ++++++++++++++++++ esphome/components/opentherm/opentherm.h | 347 +++++++++++ tests/components/opentherm/common.yaml | 3 + .../components/opentherm/test.esp32-ard.yaml | 1 + .../opentherm/test.esp32-c3-ard.yaml | 1 + .../opentherm/test.esp32-c3-idf.yaml | 1 + .../components/opentherm/test.esp32-idf.yaml | 1 + .../opentherm/test.esp8266-ard.yaml | 1 + 12 files changed, 1368 insertions(+) create mode 100644 esphome/components/opentherm/__init__.py create mode 100644 esphome/components/opentherm/hub.cpp create mode 100644 esphome/components/opentherm/hub.h create mode 100644 esphome/components/opentherm/opentherm.cpp create mode 100644 esphome/components/opentherm/opentherm.h create mode 100644 tests/components/opentherm/common.yaml create mode 100644 tests/components/opentherm/test.esp32-ard.yaml create mode 100644 tests/components/opentherm/test.esp32-c3-ard.yaml create mode 100644 tests/components/opentherm/test.esp32-c3-idf.yaml create mode 100644 tests/components/opentherm/test.esp32-idf.yaml create mode 100644 tests/components/opentherm/test.esp8266-ard.yaml diff --git a/CODEOWNERS b/CODEOWNERS index 0b1b88fbc8..f7fbbf9374 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -289,6 +289,7 @@ esphome/components/noblex/* @AGalfra esphome/components/number/* @esphome/core esphome/components/one_wire/* @ssieb esphome/components/online_image/* @guillempages +esphome/components/opentherm/* @olegtarasov esphome/components/ota/* @esphome/core esphome/components/output/* @esphome/core esphome/components/pca6416a/* @Mat931 diff --git a/esphome/components/opentherm/__init__.py b/esphome/components/opentherm/__init__.py new file mode 100644 index 0000000000..23443a4028 --- /dev/null +++ b/esphome/components/opentherm/__init__.py @@ -0,0 +1,57 @@ +from typing import Any + +from esphome import pins +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.const import CONF_ID, PLATFORM_ESP32, PLATFORM_ESP8266 + +CODEOWNERS = ["@olegtarasov"] +MULTI_CONF = True + +CONF_IN_PIN = "in_pin" +CONF_OUT_PIN = "out_pin" +CONF_CH_ENABLE = "ch_enable" +CONF_DHW_ENABLE = "dhw_enable" +CONF_COOLING_ENABLE = "cooling_enable" +CONF_OTC_ACTIVE = "otc_active" +CONF_CH2_ACTIVE = "ch2_active" +CONF_SYNC_MODE = "sync_mode" + +opentherm_ns = cg.esphome_ns.namespace("opentherm") +OpenthermHub = opentherm_ns.class_("OpenthermHub", cg.Component) + +CONFIG_SCHEMA = cv.All( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(OpenthermHub), + cv.Required(CONF_IN_PIN): pins.internal_gpio_input_pin_schema, + cv.Required(CONF_OUT_PIN): pins.internal_gpio_output_pin_schema, + cv.Optional(CONF_CH_ENABLE, True): cv.boolean, + cv.Optional(CONF_DHW_ENABLE, True): cv.boolean, + cv.Optional(CONF_COOLING_ENABLE, False): cv.boolean, + cv.Optional(CONF_OTC_ACTIVE, False): cv.boolean, + cv.Optional(CONF_CH2_ACTIVE, False): cv.boolean, + cv.Optional(CONF_SYNC_MODE, False): cv.boolean, + } + ).extend(cv.COMPONENT_SCHEMA), + cv.only_on([PLATFORM_ESP32, PLATFORM_ESP8266]), +) + + +async def to_code(config: dict[str, Any]) -> None: + # Create the hub, passing the two callbacks defined below + # Since the hub is used in the callbacks, we need to define it first + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + + # Set pins + in_pin = await cg.gpio_pin_expression(config[CONF_IN_PIN]) + cg.add(var.set_in_pin(in_pin)) + + out_pin = await cg.gpio_pin_expression(config[CONF_OUT_PIN]) + cg.add(var.set_out_pin(out_pin)) + + non_sensors = {CONF_ID, CONF_IN_PIN, CONF_OUT_PIN} + for key, value in config.items(): + if key not in non_sensors: + cg.add(getattr(var, f"set_{key}")(value)) diff --git a/esphome/components/opentherm/hub.cpp b/esphome/components/opentherm/hub.cpp new file mode 100644 index 0000000000..c26fbced32 --- /dev/null +++ b/esphome/components/opentherm/hub.cpp @@ -0,0 +1,277 @@ +#include "hub.h" +#include "esphome/core/helpers.h" + +#include + +namespace esphome { +namespace opentherm { + +static const char *const TAG = "opentherm"; + +OpenthermData OpenthermHub::build_request_(MessageId request_id) { + OpenthermData data; + data.type = 0; + data.id = 0; + data.valueHB = 0; + data.valueLB = 0; + + // First, handle the status request. This requires special logic, because we + // wouldn't want to inadvertently disable domestic hot water, for example. + // It is also included in the macro-generated code below, but that will + // never be executed, because we short-circuit it here. + if (request_id == MessageId::STATUS) { + bool const ch_enabled = this->ch_enable; + bool dhw_enabled = this->dhw_enable; + bool cooling_enabled = this->cooling_enable; + bool otc_enabled = this->otc_active; + bool ch2_enabled = this->ch2_active; + + data.type = MessageType::READ_DATA; + data.id = MessageId::STATUS; + data.valueHB = ch_enabled | (dhw_enabled << 1) | (cooling_enabled << 2) | (otc_enabled << 3) | (ch2_enabled << 4); + +// Disable incomplete switch statement warnings, because the cases in each +// switch are generated based on the configured sensors and inputs. +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wswitch" + + // TODO: This is a placeholder for an auto-generated switch statement which builds request structure based on + // which sensors are enabled in config. + +#pragma GCC diagnostic pop + + return data; + } + return OpenthermData(); +} + +OpenthermHub::OpenthermHub() : Component() {} + +void OpenthermHub::process_response(OpenthermData &data) { + ESP_LOGD(TAG, "Received OpenTherm response with id %d (%s)", data.id, + this->opentherm_->message_id_to_str((MessageId) data.id)); + ESP_LOGD(TAG, "%s", this->opentherm_->debug_data(data).c_str()); +} + +void OpenthermHub::setup() { + ESP_LOGD(TAG, "Setting up OpenTherm component"); + this->opentherm_ = make_unique(this->in_pin_, this->out_pin_); + if (!this->opentherm_->initialize()) { + ESP_LOGE(TAG, "Failed to initialize OpenTherm protocol. See previous log messages for details."); + this->mark_failed(); + return; + } + + // Ensure that there is at least one request, as we are required to + // communicate at least once every second. Sending the status request is + // good practice anyway. + this->add_repeating_message(MessageId::STATUS); + + this->current_message_iterator_ = this->initial_messages_.begin(); +} + +void OpenthermHub::on_shutdown() { this->opentherm_->stop(); } + +void OpenthermHub::loop() { + if (this->sync_mode_) { + this->sync_loop_(); + return; + } + + auto cur_time = millis(); + auto const cur_mode = this->opentherm_->get_mode(); + switch (cur_mode) { + case OperationMode::WRITE: + case OperationMode::READ: + case OperationMode::LISTEN: + if (!this->check_timings_(cur_time)) { + break; + } + this->last_mode_ = cur_mode; + break; + case OperationMode::ERROR_PROTOCOL: + if (this->last_mode_ == OperationMode::WRITE) { + this->handle_protocol_write_error_(); + } else if (this->last_mode_ == OperationMode::READ) { + this->handle_protocol_read_error_(); + } + + this->stop_opentherm_(); + break; + case OperationMode::ERROR_TIMEOUT: + this->handle_timeout_error_(); + this->stop_opentherm_(); + break; + case OperationMode::IDLE: + if (this->should_skip_loop_(cur_time)) { + break; + } + this->start_conversation_(); + break; + case OperationMode::SENT: + // Message sent, now listen for the response. + this->opentherm_->listen(); + break; + case OperationMode::RECEIVED: + this->read_response_(); + break; + } +} + +void OpenthermHub::sync_loop_() { + if (!this->opentherm_->is_idle()) { + ESP_LOGE(TAG, "OpenTherm is not idle at the start of the loop"); + return; + } + + auto cur_time = millis(); + + this->check_timings_(cur_time); + + if (this->should_skip_loop_(cur_time)) { + return; + } + + this->start_conversation_(); + + if (!this->spin_wait_(1150, [&] { return this->opentherm_->is_active(); })) { + ESP_LOGE(TAG, "Hub timeout triggered during send"); + this->stop_opentherm_(); + return; + } + + if (this->opentherm_->is_error()) { + this->handle_protocol_write_error_(); + this->stop_opentherm_(); + return; + } else if (!this->opentherm_->is_sent()) { + ESP_LOGW(TAG, "Unexpected state after sending request: %s", + this->opentherm_->operation_mode_to_str(this->opentherm_->get_mode())); + this->stop_opentherm_(); + return; + } + + // Listen for the response + this->opentherm_->listen(); + if (!this->spin_wait_(1150, [&] { return this->opentherm_->is_active(); })) { + ESP_LOGE(TAG, "Hub timeout triggered during receive"); + this->stop_opentherm_(); + return; + } + + if (this->opentherm_->is_timeout()) { + this->handle_timeout_error_(); + this->stop_opentherm_(); + return; + } else if (this->opentherm_->is_protocol_error()) { + this->handle_protocol_read_error_(); + this->stop_opentherm_(); + return; + } else if (!this->opentherm_->has_message()) { + ESP_LOGW(TAG, "Unexpected state after receiving response: %s", + this->opentherm_->operation_mode_to_str(this->opentherm_->get_mode())); + this->stop_opentherm_(); + return; + } + + this->read_response_(); +} + +bool OpenthermHub::check_timings_(uint32_t cur_time) { + if (this->last_conversation_start_ > 0 && (cur_time - this->last_conversation_start_) > 1150) { + ESP_LOGW(TAG, + "%d ms elapsed since the start of the last convo, but 1150 ms are allowed at maximum. Look at other " + "components that might slow the loop down.", + (int) (cur_time - this->last_conversation_start_)); + this->stop_opentherm_(); + return false; + } + + return true; +} + +bool OpenthermHub::should_skip_loop_(uint32_t cur_time) const { + if (this->last_conversation_end_ > 0 && (cur_time - this->last_conversation_end_) < 100) { + ESP_LOGV(TAG, "Less than 100 ms elapsed since last convo, skipping this iteration"); + return true; + } + + return false; +} + +void OpenthermHub::start_conversation_() { + if (this->sending_initial_ && this->current_message_iterator_ == this->initial_messages_.end()) { + this->sending_initial_ = false; + this->current_message_iterator_ = this->repeating_messages_.begin(); + } else if (this->current_message_iterator_ == this->repeating_messages_.end()) { + this->current_message_iterator_ = this->repeating_messages_.begin(); + } + + auto request = this->build_request_(*this->current_message_iterator_); + + ESP_LOGD(TAG, "Sending request with id %d (%s)", request.id, + this->opentherm_->message_id_to_str((MessageId) request.id)); + ESP_LOGD(TAG, "%s", this->opentherm_->debug_data(request).c_str()); + // Send the request + this->last_conversation_start_ = millis(); + this->opentherm_->send(request); +} + +void OpenthermHub::read_response_() { + OpenthermData response; + if (!this->opentherm_->get_message(response)) { + ESP_LOGW(TAG, "Couldn't get the response, but flags indicated success. This is a bug."); + this->stop_opentherm_(); + return; + } + + this->stop_opentherm_(); + + this->process_response(response); + + this->current_message_iterator_++; +} + +void OpenthermHub::stop_opentherm_() { + this->opentherm_->stop(); + this->last_conversation_end_ = millis(); +} + +void OpenthermHub::handle_protocol_write_error_() { + ESP_LOGW(TAG, "Error while sending request: %s", + this->opentherm_->operation_mode_to_str(this->opentherm_->get_mode())); + ESP_LOGW(TAG, "%s", this->opentherm_->debug_data(this->last_request_).c_str()); +} + +void OpenthermHub::handle_protocol_read_error_() { + OpenThermError error; + this->opentherm_->get_protocol_error(error); + ESP_LOGW(TAG, "Protocol error occured while receiving response: %s", this->opentherm_->debug_error(error).c_str()); +} + +void OpenthermHub::handle_timeout_error_() { + ESP_LOGW(TAG, "Receive response timed out at a protocol level"); + this->stop_opentherm_(); +} + +#define ID(x) x +#define SHOW2(x) #x +#define SHOW(x) SHOW2(x) + +void OpenthermHub::dump_config() { + ESP_LOGCONFIG(TAG, "OpenTherm:"); + LOG_PIN(" In: ", this->in_pin_); + LOG_PIN(" Out: ", this->out_pin_); + ESP_LOGCONFIG(TAG, " Sync mode: %d", this->sync_mode_); + ESP_LOGCONFIG(TAG, " Initial requests:"); + for (auto type : this->initial_messages_) { + ESP_LOGCONFIG(TAG, " - %d", type); + } + ESP_LOGCONFIG(TAG, " Repeating requests:"); + for (auto type : this->repeating_messages_) { + ESP_LOGCONFIG(TAG, " - %d", type); + } +} + +} // namespace opentherm +} // namespace esphome diff --git a/esphome/components/opentherm/hub.h b/esphome/components/opentherm/hub.h new file mode 100644 index 0000000000..ce9f09fe33 --- /dev/null +++ b/esphome/components/opentherm/hub.h @@ -0,0 +1,110 @@ +#pragma once + +#include "esphome/core/defines.h" +#include "esphome/core/hal.h" +#include "esphome/core/component.h" +#include "esphome/core/log.h" + +#include "opentherm.h" + +#include +#include +#include +#include + +namespace esphome { +namespace opentherm { + +// OpenTherm component for ESPHome +class OpenthermHub : public Component { + protected: + // Communication pins for the OpenTherm interface + InternalGPIOPin *in_pin_, *out_pin_; + // The OpenTherm interface + std::unique_ptr opentherm_; + + // The set of initial messages to send on starting communication with the boiler + std::unordered_set initial_messages_; + // and the repeating messages which are sent repeatedly to update various sensors + // and boiler parameters (like the setpoint). + std::unordered_set repeating_messages_; + // Indicates if we are still working on the initial requests or not + bool sending_initial_ = true; + // Index for the current request in one of the _requests sets. + std::unordered_set::const_iterator current_message_iterator_; + + uint32_t last_conversation_start_ = 0; + uint32_t last_conversation_end_ = 0; + OperationMode last_mode_ = IDLE; + OpenthermData last_request_; + + // Synchronous communication mode prevents other components from disabling interrupts while + // we are talking to the boiler. Enable if you experience random intermittent invalid response errors. + // Very likely to happen while using Dallas temperature sensors. + bool sync_mode_ = false; + + // Create OpenTherm messages based on the message id + OpenthermData build_request_(MessageId request_id); + void handle_protocol_write_error_(); + void handle_protocol_read_error_(); + void handle_timeout_error_(); + void stop_opentherm_(); + void start_conversation_(); + void read_response_(); + bool check_timings_(uint32_t cur_time); + bool should_skip_loop_(uint32_t cur_time) const; + void sync_loop_(); + + template bool spin_wait_(uint32_t timeout, F func) { + auto start_time = millis(); + while (func()) { + yield(); + auto cur_time = millis(); + if (cur_time - start_time >= timeout) { + return false; + } + } + return true; + } + + public: + // Constructor with references to the global interrupt handlers + OpenthermHub(); + + // Handle responses from the OpenTherm interface + void process_response(OpenthermData &data); + + // Setters for the input and output OpenTherm interface pins + void set_in_pin(InternalGPIOPin *in_pin) { this->in_pin_ = in_pin; } + void set_out_pin(InternalGPIOPin *out_pin) { this->out_pin_ = out_pin; } + + // Add a request to the set of initial requests + void add_initial_message(MessageId message_id) { this->initial_messages_.insert(message_id); } + // Add a request to the set of repeating requests. Note that a large number of repeating + // requests will slow down communication with the boiler. Each request may take up to 1 second, + // so with all sensors enabled, it may take about half a minute before a change in setpoint + // will be processed. + void add_repeating_message(MessageId message_id) { this->repeating_messages_.insert(message_id); } + + // There are five status variables, which can either be set as a simple variable, + // or using a switch. ch_enable and dhw_enable default to true, the others to false. + bool ch_enable = true, dhw_enable = true, cooling_enable = false, otc_active = false, ch2_active = false; + + // Setters for the status variables + void set_ch_enable(bool value) { this->ch_enable = value; } + void set_dhw_enable(bool value) { this->dhw_enable = value; } + void set_cooling_enable(bool value) { this->cooling_enable = value; } + void set_otc_active(bool value) { this->otc_active = value; } + void set_ch2_active(bool value) { this->ch2_active = value; } + void set_sync_mode(bool sync_mode) { this->sync_mode_ = sync_mode; } + + float get_setup_priority() const override { return setup_priority::HARDWARE; } + + void setup() override; + void on_shutdown() override; + void loop() override; + void dump_config() override; +}; + +} // namespace opentherm +} // namespace esphome diff --git a/esphome/components/opentherm/opentherm.cpp b/esphome/components/opentherm/opentherm.cpp new file mode 100644 index 0000000000..b830cc01d3 --- /dev/null +++ b/esphome/components/opentherm/opentherm.cpp @@ -0,0 +1,568 @@ +/* + * OpenTherm protocol implementation. Originally taken from https://github.com/jpraus/arduino-opentherm, but + * heavily modified to comply with ESPHome coding standards and provide better logging. + * Original code is licensed under Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International + * Public License, which is compatible with GPLv3 license, which covers C++ part of ESPHome project. + */ + +#include "opentherm.h" +#include "esphome/core/helpers.h" +#if defined(ESP32) || defined(USE_ESP_IDF) +#include "driver/timer.h" +#include "esp_err.h" +#endif +#ifdef ESP8266 +#include "Arduino.h" +#endif +#include +#include +#include + +namespace esphome { +namespace opentherm { + +using std::string; +using std::bitset; +using std::stringstream; +using std::to_string; + +static const char *const TAG = "opentherm"; + +#ifdef ESP8266 +OpenTherm *OpenTherm::instance_ = nullptr; +#endif + +OpenTherm::OpenTherm(InternalGPIOPin *in_pin, InternalGPIOPin *out_pin, int32_t device_timeout) + : in_pin_(in_pin), + out_pin_(out_pin), +#if defined(ESP32) || defined(USE_ESP_IDF) + timer_group_(TIMER_GROUP_0), + timer_idx_(TIMER_0), +#endif + mode_(OperationMode::IDLE), + error_type_(ProtocolErrorType::NO_ERROR), + capture_(0), + clock_(0), + data_(0), + bit_pos_(0), + timeout_counter_(-1), + device_timeout_(device_timeout) { + this->isr_in_pin_ = in_pin->to_isr(); + this->isr_out_pin_ = out_pin->to_isr(); +} + +bool OpenTherm::initialize() { +#ifdef ESP8266 + OpenTherm::instance_ = this; +#endif + this->in_pin_->pin_mode(gpio::FLAG_INPUT); + this->out_pin_->pin_mode(gpio::FLAG_OUTPUT); + this->out_pin_->digital_write(true); + +#if defined(ESP32) || defined(USE_ESP_IDF) + return this->init_esp32_timer_(); +#else + return true; +#endif +} + +void OpenTherm::listen() { + this->stop_timer_(); + this->timeout_counter_ = this->device_timeout_ * 5; // timer_ ticks at 5 ticks/ms + + this->mode_ = OperationMode::LISTEN; + this->data_ = 0; + this->bit_pos_ = 0; + + this->start_read_timer_(); +} + +void OpenTherm::send(OpenthermData &data) { + this->stop_timer_(); + this->data_ = data.type; + this->data_ = (this->data_ << 12) | data.id; + this->data_ = (this->data_ << 8) | data.valueHB; + this->data_ = (this->data_ << 8) | data.valueLB; + if (!check_parity_(this->data_)) { + this->data_ = this->data_ | 0x80000000; + } + + this->clock_ = 1; // clock starts at HIGH + this->bit_pos_ = 33; // count down (33 == start bit, 32-1 data, 0 == stop bit) + this->mode_ = OperationMode::WRITE; + + this->start_write_timer_(); +} + +bool OpenTherm::get_message(OpenthermData &data) { + if (this->mode_ == OperationMode::RECEIVED) { + data.type = (this->data_ >> 28) & 0x7; + data.id = (this->data_ >> 16) & 0xFF; + data.valueHB = (this->data_ >> 8) & 0xFF; + data.valueLB = this->data_ & 0xFF; + return true; + } + return false; +} + +bool OpenTherm::get_protocol_error(OpenThermError &error) { + if (this->mode_ != OperationMode::ERROR_PROTOCOL) { + return false; + } + + error.error_type = this->error_type_; + error.bit_pos = this->bit_pos_; + error.capture = this->capture_; + error.clock = this->clock_; + error.data = this->data_; + + return true; +} + +void OpenTherm::stop() { + this->stop_timer_(); + this->mode_ = OperationMode::IDLE; +} + +void IRAM_ATTR OpenTherm::read_() { + this->data_ = 0; + this->bit_pos_ = 0; + this->mode_ = OperationMode::READ; + this->capture_ = 1; // reset counter and add as if read start bit + this->clock_ = 1; // clock is high at the start of comm + this->start_read_timer_(); // get us into 1/4 of manchester code. 5 timer ticks constitute 1 ms, which is 1 bit + // period in OpenTherm. +} + +bool IRAM_ATTR OpenTherm::timer_isr(OpenTherm *arg) { + if (arg->mode_ == OperationMode::LISTEN) { + if (arg->timeout_counter_ == 0) { + arg->mode_ = OperationMode::ERROR_TIMEOUT; + arg->stop_timer_(); + return false; + } + bool const value = arg->isr_in_pin_.digital_read(); + if (value) { // incoming data (rising signal) + arg->read_(); + } + if (arg->timeout_counter_ > 0) { + arg->timeout_counter_--; + } + } else if (arg->mode_ == OperationMode::READ) { + bool const value = arg->isr_in_pin_.digital_read(); + uint8_t const last = (arg->capture_ & 1); + if (value != last) { + // transition of signal from last sampling + if (arg->clock_ == 1 && arg->capture_ > 0xF) { + // no transition in the middle of the bit + arg->mode_ = OperationMode::ERROR_PROTOCOL; + arg->error_type_ = ProtocolErrorType::NO_TRANSITION; + arg->stop_timer_(); + return false; + } else if (arg->clock_ == 1 || arg->capture_ > 0xF) { + // transition in the middle of the bit OR no transition between two bit, both are valid data points + if (arg->bit_pos_ == BitPositions::STOP_BIT) { + // expecting stop bit + auto stop_bit_error = arg->verify_stop_bit_(last); + if (stop_bit_error == ProtocolErrorType::NO_ERROR) { + arg->mode_ = OperationMode::RECEIVED; + arg->stop_timer_(); + return false; + } else { + // end of data not verified, invalid data + arg->mode_ = OperationMode::ERROR_PROTOCOL; + arg->error_type_ = stop_bit_error; + arg->stop_timer_(); + return false; + } + } else { + // normal data point at clock high + arg->bit_read_(last); + arg->clock_ = 0; + } + } else { + // clock low, not a data point, switch clock + arg->clock_ = 1; + } + arg->capture_ = 1; // reset counter + } else if (arg->capture_ > 0xFF) { + // no change for too long, invalid mancheter encoding + arg->mode_ = OperationMode::ERROR_PROTOCOL; + arg->error_type_ = ProtocolErrorType::NO_CHANGE_TOO_LONG; + arg->stop_timer_(); + return false; + } + arg->capture_ = (arg->capture_ << 1) | value; + } else if (arg->mode_ == OperationMode::WRITE) { + // write data to pin + if (arg->bit_pos_ == 33 || arg->bit_pos_ == 0) { // start bit + arg->write_bit_(1, arg->clock_); + } else { // data bits + arg->write_bit_(read_bit(arg->data_, arg->bit_pos_ - 1), arg->clock_); + } + if (arg->clock_ == 0) { + if (arg->bit_pos_ <= 0) { // check termination + arg->mode_ = OperationMode::SENT; // all data written + arg->stop_timer_(); + } + arg->bit_pos_--; + arg->clock_ = 1; + } else { + arg->clock_ = 0; + } + } + + return false; +} + +#ifdef ESP8266 +void IRAM_ATTR OpenTherm::esp8266_timer_isr() { OpenTherm::timer_isr(OpenTherm::instance_); } +#endif + +void IRAM_ATTR OpenTherm::bit_read_(uint8_t value) { + this->data_ = (this->data_ << 1) | value; + this->bit_pos_++; +} + +ProtocolErrorType OpenTherm::verify_stop_bit_(uint8_t value) { + if (value) { // stop bit detected + return check_parity_(this->data_) ? ProtocolErrorType::NO_ERROR : ProtocolErrorType::PARITY_ERROR; + } else { // no stop bit detected, error + return ProtocolErrorType::INVALID_STOP_BIT; + } +} + +void IRAM_ATTR OpenTherm::write_bit_(uint8_t high, uint8_t clock) { + if (clock == 1) { // left part of manchester encoding + this->isr_out_pin_.digital_write(!high); // low means logical 1 to protocol + } else { // right part of manchester encoding + this->isr_out_pin_.digital_write(high); // high means logical 0 to protocol + } +} + +#if defined(ESP32) || defined(USE_ESP_IDF) + +bool OpenTherm::init_esp32_timer_() { + // Search for a free timer. Maybe unstable, we'll see. + int cur_timer = 0; + timer_group_t timer_group = TIMER_GROUP_0; + timer_idx_t timer_idx = TIMER_0; + bool timer_found = false; + + for (; cur_timer < SOC_TIMER_GROUP_TOTAL_TIMERS; cur_timer++) { + timer_config_t temp_config; + timer_group = cur_timer < 2 ? TIMER_GROUP_0 : TIMER_GROUP_1; + timer_idx = cur_timer < 2 ? (timer_idx_t) cur_timer : (timer_idx_t) (cur_timer - 2); + + auto err = timer_get_config(timer_group, timer_idx, &temp_config); + if (err == ESP_ERR_INVALID_ARG) { + // Error means timer was not initialized (or other things, but we are careful with our args) + timer_found = true; + break; + } + + ESP_LOGD(TAG, "Timer %d:%d seems to be occupied, will try another", timer_group, timer_idx); + } + + if (!timer_found) { + ESP_LOGE(TAG, "No free timer was found! OpenTherm cannot function without a timer."); + return false; + } + + ESP_LOGD(TAG, "Found free timer %d:%d", timer_group, timer_idx); + this->timer_group_ = timer_group; + this->timer_idx_ = timer_idx; + + timer_config_t const config = { + .alarm_en = TIMER_ALARM_EN, + .counter_en = TIMER_PAUSE, + .intr_type = TIMER_INTR_LEVEL, + .counter_dir = TIMER_COUNT_UP, + .auto_reload = TIMER_AUTORELOAD_EN, +#if ESP_IDF_VERSION_MAJOR >= 5 + .clk_src = TIMER_SRC_CLK_DEFAULT, +#endif + .divider = 80, + }; + + esp_err_t result; + + result = timer_init(this->timer_group_, this->timer_idx_, &config); + if (result != ESP_OK) { + const auto *error = esp_err_to_name(result); + ESP_LOGE(TAG, "Failed to init timer. Error: %s", error); + return false; + } + + result = timer_set_counter_value(this->timer_group_, this->timer_idx_, 0); + if (result != ESP_OK) { + const auto *error = esp_err_to_name(result); + ESP_LOGE(TAG, "Failed to set counter value. Error: %s", error); + return false; + } + + result = timer_isr_callback_add(this->timer_group_, this->timer_idx_, reinterpret_cast(timer_isr), + this, 0); + if (result != ESP_OK) { + const auto *error = esp_err_to_name(result); + ESP_LOGE(TAG, "Failed to register timer interrupt. Error: %s", error); + return false; + } + + return true; +} + +void IRAM_ATTR OpenTherm::start_esp32_timer_(uint64_t alarm_value) { + esp_err_t result; + + result = timer_set_alarm_value(this->timer_group_, this->timer_idx_, alarm_value); + if (result != ESP_OK) { + const auto *error = esp_err_to_name(result); + ESP_LOGE(TAG, "Failed to set alarm value. Error: %s", error); + return; + } + + result = timer_start(this->timer_group_, this->timer_idx_); + if (result != ESP_OK) { + const auto *error = esp_err_to_name(result); + ESP_LOGE(TAG, "Failed to start the timer. Error: %s", error); + return; + } +} + +// 5 kHz timer_ +void IRAM_ATTR OpenTherm::start_read_timer_() { + InterruptLock const lock; + this->start_esp32_timer_(200); +} + +// 2 kHz timer_ +void IRAM_ATTR OpenTherm::start_write_timer_() { + InterruptLock const lock; + this->start_esp32_timer_(500); +} + +void IRAM_ATTR OpenTherm::stop_timer_() { + InterruptLock const lock; + + esp_err_t result; + + result = timer_pause(this->timer_group_, this->timer_idx_); + if (result != ESP_OK) { + const auto *error = esp_err_to_name(result); + ESP_LOGE(TAG, "Failed to pause the timer. Error: %s", error); + return; + } + + result = timer_set_counter_value(this->timer_group_, this->timer_idx_, 0); + if (result != ESP_OK) { + const auto *error = esp_err_to_name(result); + ESP_LOGE(TAG, "Failed to set timer counter to 0 after pausing. Error: %s", error); + return; + } +} + +#endif // END ESP32 + +#ifdef ESP8266 +// 5 kHz timer_ +void OpenTherm::start_read_timer_() { + InterruptLock const lock; + timer1_attachInterrupt(OpenTherm::esp8266_timer_isr); + timer1_enable(TIM_DIV16, TIM_EDGE, TIM_LOOP); // 5MHz (5 ticks/us - 1677721.4 us max) + timer1_write(1000); // 5kHz +} + +// 2 kHz timer_ +void OpenTherm::start_write_timer_() { + InterruptLock const lock; + timer1_attachInterrupt(OpenTherm::esp8266_timer_isr); + timer1_enable(TIM_DIV16, TIM_EDGE, TIM_LOOP); // 5MHz (5 ticks/us - 1677721.4 us max) + timer1_write(2500); // 2kHz +} + +void OpenTherm::stop_timer_() { + InterruptLock const lock; + timer1_disable(); + timer1_detachInterrupt(); +} + +#endif // END ESP8266 + +// https://stackoverflow.com/questions/21617970/how-to-check-if-value-has-even-parity-of-bits-or-odd +bool OpenTherm::check_parity_(uint32_t val) { + val ^= val >> 16; + val ^= val >> 8; + val ^= val >> 4; + val ^= val >> 2; + val ^= val >> 1; + return (~val) & 1; +} + +#define TO_STRING_MEMBER(name) \ + case name: \ + return #name; + +const char *OpenTherm::operation_mode_to_str(OperationMode mode) { + switch (mode) { + TO_STRING_MEMBER(IDLE) + TO_STRING_MEMBER(LISTEN) + TO_STRING_MEMBER(READ) + TO_STRING_MEMBER(RECEIVED) + TO_STRING_MEMBER(WRITE) + TO_STRING_MEMBER(SENT) + TO_STRING_MEMBER(ERROR_PROTOCOL) + TO_STRING_MEMBER(ERROR_TIMEOUT) + default: + return ""; + } +} +const char *OpenTherm::protocol_error_to_to_str(ProtocolErrorType error_type) { + switch (error_type) { + TO_STRING_MEMBER(NO_ERROR) + TO_STRING_MEMBER(NO_TRANSITION) + TO_STRING_MEMBER(INVALID_STOP_BIT) + TO_STRING_MEMBER(PARITY_ERROR) + TO_STRING_MEMBER(NO_CHANGE_TOO_LONG) + default: + return ""; + } +} +const char *OpenTherm::message_type_to_str(MessageType message_type) { + switch (message_type) { + TO_STRING_MEMBER(READ_DATA) + TO_STRING_MEMBER(READ_ACK) + TO_STRING_MEMBER(WRITE_DATA) + TO_STRING_MEMBER(WRITE_ACK) + TO_STRING_MEMBER(INVALID_DATA) + TO_STRING_MEMBER(DATA_INVALID) + TO_STRING_MEMBER(UNKNOWN_DATAID) + default: + return ""; + } +} + +const char *OpenTherm::message_id_to_str(MessageId id) { + switch (id) { + TO_STRING_MEMBER(STATUS) + TO_STRING_MEMBER(CH_SETPOINT) + TO_STRING_MEMBER(CONTROLLER_CONFIG) + TO_STRING_MEMBER(DEVICE_CONFIG) + TO_STRING_MEMBER(COMMAND_CODE) + TO_STRING_MEMBER(FAULT_FLAGS) + TO_STRING_MEMBER(REMOTE) + TO_STRING_MEMBER(COOLING_CONTROL) + TO_STRING_MEMBER(CH2_SETPOINT) + TO_STRING_MEMBER(CH_SETPOINT_OVERRIDE) + TO_STRING_MEMBER(TSP_COUNT) + TO_STRING_MEMBER(TSP_COMMAND) + TO_STRING_MEMBER(FHB_SIZE) + TO_STRING_MEMBER(FHB_COMMAND) + TO_STRING_MEMBER(MAX_MODULATION_LEVEL) + TO_STRING_MEMBER(MAX_BOILER_CAPACITY) + TO_STRING_MEMBER(ROOM_SETPOINT) + TO_STRING_MEMBER(MODULATION_LEVEL) + TO_STRING_MEMBER(CH_WATER_PRESSURE) + TO_STRING_MEMBER(DHW_FLOW_RATE) + TO_STRING_MEMBER(DAY_TIME) + TO_STRING_MEMBER(DATE) + TO_STRING_MEMBER(YEAR) + TO_STRING_MEMBER(ROOM_SETPOINT_CH2) + TO_STRING_MEMBER(ROOM_TEMP) + TO_STRING_MEMBER(FEED_TEMP) + TO_STRING_MEMBER(DHW_TEMP) + TO_STRING_MEMBER(OUTSIDE_TEMP) + TO_STRING_MEMBER(RETURN_WATER_TEMP) + TO_STRING_MEMBER(SOLAR_STORE_TEMP) + TO_STRING_MEMBER(SOLAR_COLLECT_TEMP) + TO_STRING_MEMBER(FEED_TEMP_CH2) + TO_STRING_MEMBER(DHW2_TEMP) + TO_STRING_MEMBER(EXHAUST_TEMP) + TO_STRING_MEMBER(FAN_SPEED) + TO_STRING_MEMBER(FLAME_CURRENT) + TO_STRING_MEMBER(DHW_BOUNDS) + TO_STRING_MEMBER(CH_BOUNDS) + TO_STRING_MEMBER(OTC_CURVE_BOUNDS) + TO_STRING_MEMBER(DHW_SETPOINT) + TO_STRING_MEMBER(MAX_CH_SETPOINT) + TO_STRING_MEMBER(OTC_CURVE_RATIO) + TO_STRING_MEMBER(HVAC_STATUS) + TO_STRING_MEMBER(REL_VENT_SETPOINT) + TO_STRING_MEMBER(DEVICE_VENT) + TO_STRING_MEMBER(REL_VENTILATION) + TO_STRING_MEMBER(REL_HUMID_EXHAUST) + TO_STRING_MEMBER(SUPPLY_INLET_TEMP) + TO_STRING_MEMBER(SUPPLY_OUTLET_TEMP) + TO_STRING_MEMBER(EXHAUST_INLET_TEMP) + TO_STRING_MEMBER(EXHAUST_OUTLET_TEMP) + TO_STRING_MEMBER(NOM_REL_VENTILATION) + TO_STRING_MEMBER(OVERRIDE_FUNC) + TO_STRING_MEMBER(OEM_DIAGNOSTIC) + TO_STRING_MEMBER(BURNER_STARTS) + TO_STRING_MEMBER(CH_PUMP_STARTS) + TO_STRING_MEMBER(DHW_PUMP_STARTS) + TO_STRING_MEMBER(DHW_BURNER_STARTS) + TO_STRING_MEMBER(BURNER_HOURS) + TO_STRING_MEMBER(CH_PUMP_HOURS) + TO_STRING_MEMBER(DHW_PUMP_HOURS) + TO_STRING_MEMBER(DHW_BURNER_HOURS) + TO_STRING_MEMBER(OT_VERSION_CONTROLLER) + TO_STRING_MEMBER(OT_VERSION_DEVICE) + TO_STRING_MEMBER(VERSION_CONTROLLER) + TO_STRING_MEMBER(VERSION_DEVICE) + default: + return ""; + } +} + +string OpenTherm::debug_data(OpenthermData &data) { + stringstream result; + result << bitset<8>(data.type) << " " << bitset<8>(data.id) << " " << bitset<8>(data.valueHB) << " " + << bitset<8>(data.valueLB) << "\n"; + result << "type: " << this->message_type_to_str((MessageType) data.type) << "; "; + result << "id: " << to_string(data.id) << "; "; + result << "HB: " << to_string(data.valueHB) << "; "; + result << "LB: " << to_string(data.valueLB) << "; "; + result << "uint_16: " << to_string(data.u16()) << "; "; + result << "float: " << to_string(data.f88()); + + return result.str(); +} +std::string OpenTherm::debug_error(OpenThermError &error) { + stringstream result; + result << "type: " << this->protocol_error_to_to_str(error.error_type) << "; "; + result << "data: "; + result << format_hex(error.data); + result << "; clock: " << to_string(clock_); + result << "; capture: " << bitset<32>(error.capture); + result << "; bit_pos: " << to_string(error.bit_pos); + + return result.str(); +} + +float OpenthermData::f88() { return ((float) this->s16()) / 256.0; } + +void OpenthermData::f88(float value) { this->s16((int16_t) (value * 256)); } + +uint16_t OpenthermData::u16() { + uint16_t const value = this->valueHB; + return (value << 8) | this->valueLB; +} + +void OpenthermData::u16(uint16_t value) { + this->valueLB = value & 0xFF; + this->valueHB = (value >> 8) & 0xFF; +} + +int16_t OpenthermData::s16() { + int16_t const value = this->valueHB; + return (value << 8) | this->valueLB; +} + +void OpenthermData::s16(int16_t value) { + this->valueLB = value & 0xFF; + this->valueHB = (value >> 8) & 0xFF; +} + +} // namespace opentherm +} // namespace esphome diff --git a/esphome/components/opentherm/opentherm.h b/esphome/components/opentherm/opentherm.h new file mode 100644 index 0000000000..609cfb6243 --- /dev/null +++ b/esphome/components/opentherm/opentherm.h @@ -0,0 +1,347 @@ +/* + * OpenTherm protocol implementation. Originally taken from https://github.com/jpraus/arduino-opentherm, but + * heavily modified to comply with ESPHome coding standards and provide better logging. + * Original code is licensed under Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International + * Public License, which is compatible with GPLv3 license, which covers C++ part of ESPHome project. + */ + +#pragma once + +#include +#include +#include +#include "esphome/core/hal.h" +#include "esphome/core/log.h" + +#if defined(ESP32) || defined(USE_ESP_IDF) +#include "driver/timer.h" +#endif + +namespace esphome { +namespace opentherm { + +// TODO: Account for immutable semantics change in hub.cpp when doing later installments of OpenTherm PR +template constexpr T read_bit(T value, uint8_t bit) { return (value >> bit) & 0x01; } + +template constexpr T set_bit(T value, uint8_t bit) { return value |= (1UL << bit); } + +template constexpr T clear_bit(T value, uint8_t bit) { return value &= ~(1UL << bit); } + +template constexpr T write_bit(T value, uint8_t bit, uint8_t bit_value) { + return bit_value ? setBit(value, bit) : clearBit(value, bit); +} + +enum OperationMode { + IDLE = 0, // no operation + + LISTEN = 1, // waiting for transmission to start + READ = 2, // reading 32-bit data frame + RECEIVED = 3, // data frame received with valid start and stop bit + + WRITE = 4, // writing data with timer_ + SENT = 5, // all data written to output + + ERROR_PROTOCOL = 8, // manchester protocol data transfer error + ERROR_TIMEOUT = 9 // read timeout +}; + +enum ProtocolErrorType { + NO_ERROR = 0, // No error + NO_TRANSITION = 1, // No transition in the middle of the bit + INVALID_STOP_BIT = 2, // Stop bit wasn't present when expected + PARITY_ERROR = 3, // Parity check didn't pass + NO_CHANGE_TOO_LONG = 4, // No level change for too much timer ticks +}; + +enum MessageType { + READ_DATA = 0, + READ_ACK = 4, + WRITE_DATA = 1, + WRITE_ACK = 5, + INVALID_DATA = 2, + DATA_INVALID = 6, + UNKNOWN_DATAID = 7 +}; + +enum MessageId { + STATUS = 0, + CH_SETPOINT = 1, + CONTROLLER_CONFIG = 2, + DEVICE_CONFIG = 3, + COMMAND_CODE = 4, + FAULT_FLAGS = 5, + REMOTE = 6, + COOLING_CONTROL = 7, + CH2_SETPOINT = 8, + CH_SETPOINT_OVERRIDE = 9, + TSP_COUNT = 10, + TSP_COMMAND = 11, + FHB_SIZE = 12, + FHB_COMMAND = 13, + MAX_MODULATION_LEVEL = 14, + MAX_BOILER_CAPACITY = 15, // u8_hb - u8_lb gives min modulation level + ROOM_SETPOINT = 16, + MODULATION_LEVEL = 17, + CH_WATER_PRESSURE = 18, + DHW_FLOW_RATE = 19, + DAY_TIME = 20, + DATE = 21, + YEAR = 22, + ROOM_SETPOINT_CH2 = 23, + ROOM_TEMP = 24, + FEED_TEMP = 25, + DHW_TEMP = 26, + OUTSIDE_TEMP = 27, + RETURN_WATER_TEMP = 28, + SOLAR_STORE_TEMP = 29, + SOLAR_COLLECT_TEMP = 30, + FEED_TEMP_CH2 = 31, + DHW2_TEMP = 32, + EXHAUST_TEMP = 33, + FAN_SPEED = 35, + FLAME_CURRENT = 36, + DHW_BOUNDS = 48, + CH_BOUNDS = 49, + OTC_CURVE_BOUNDS = 50, + DHW_SETPOINT = 56, + MAX_CH_SETPOINT = 57, + OTC_CURVE_RATIO = 58, + + // HVAC Specific Message IDs + HVAC_STATUS = 70, + REL_VENT_SETPOINT = 71, + DEVICE_VENT = 74, + REL_VENTILATION = 77, + REL_HUMID_EXHAUST = 78, + SUPPLY_INLET_TEMP = 80, + SUPPLY_OUTLET_TEMP = 81, + EXHAUST_INLET_TEMP = 82, + EXHAUST_OUTLET_TEMP = 83, + NOM_REL_VENTILATION = 87, + + OVERRIDE_FUNC = 100, + OEM_DIAGNOSTIC = 115, + BURNER_STARTS = 116, + CH_PUMP_STARTS = 117, + DHW_PUMP_STARTS = 118, + DHW_BURNER_STARTS = 119, + BURNER_HOURS = 120, + CH_PUMP_HOURS = 121, + DHW_PUMP_HOURS = 122, + DHW_BURNER_HOURS = 123, + OT_VERSION_CONTROLLER = 124, + OT_VERSION_DEVICE = 125, + VERSION_CONTROLLER = 126, + VERSION_DEVICE = 127 +}; + +enum BitPositions { STOP_BIT = 33 }; + +/** + * Structure to hold Opentherm data packet content. + * Use f88(), u16() or s16() functions to get appropriate value of data packet accoridng to id of message. + */ +struct OpenthermData { + uint8_t type; + uint8_t id; + uint8_t valueHB; + uint8_t valueLB; + + OpenthermData() : type(0), id(0), valueHB(0), valueLB(0) {} + + /** + * @return float representation of data packet value + */ + float f88(); + + /** + * @param float number to set as value of this data packet + */ + void f88(float value); + + /** + * @return unsigned 16b integer representation of data packet value + */ + uint16_t u16(); + + /** + * @param unsigned 16b integer number to set as value of this data packet + */ + void u16(uint16_t value); + + /** + * @return signed 16b integer representation of data packet value + */ + int16_t s16(); + + /** + * @param signed 16b integer number to set as value of this data packet + */ + void s16(int16_t value); +}; + +struct OpenThermError { + ProtocolErrorType error_type; + uint32_t capture; + uint8_t clock; + uint32_t data; + uint8_t bit_pos; +}; + +/** + * Opentherm static class that supports either listening or sending Opentherm data packets in the same time + */ +class OpenTherm { + public: + OpenTherm(InternalGPIOPin *in_pin, InternalGPIOPin *out_pin, int32_t device_timeout = 800); + + /** + * Setup pins. + */ + bool initialize(); + + /** + * Start listening for Opentherm data packet comming from line connected to given pin. + * If data packet is received then has_message() function returns true and data packet can be retrieved by calling + * get_message() function. If timeout > 0 then this function waits for incomming data package for timeout millis and + * if no data packet is recevived, error state is indicated by is_error() function. If either data packet is received + * or timeout is reached listening is stopped. + */ + void listen(); + + /** + * Use this function to check whether listen() function already captured a valid data packet. + * + * @return true if data packet has been captured from line by listen() function. + */ + bool has_message() { return mode_ == OperationMode::RECEIVED; } + + /** + * Use this to retrive data packed captured by listen() function. Data packet is ready when has_message() function + * returns true. This function can be called multiple times until stop() is called. + * + * @param data reference to data structure to which fill the data packet data. + * @return true if packet was ready and was filled into data structure passed, false otherwise. + */ + bool get_message(OpenthermData &data); + + /** + * Immediately send out Opentherm data packet to line connected on given pin. + * Completed data transfer is indicated by is_sent() function. + * Error state is indicated by is_error() function. + * + * @param data Opentherm data packet. + */ + void send(OpenthermData &data); + + /** + * Stops listening for data packet or sending out data packet and resets internal state of this class. + * Stops all timers and unattaches all interrupts. + */ + void stop(); + + /** + * Get protocol error details in case a protocol error occured. + * @param error reference to data structure to which fill the error details + * @return true if protocol error occured during last conversation, false otherwise. + */ + bool get_protocol_error(OpenThermError &error); + + /** + * Use this function to check whether send() function already finished sending data packed to line. + * + * @return true if data packet has been sent, false otherwise. + */ + bool is_sent() { return mode_ == OperationMode::SENT; } + + /** + * Indicates whether listinig or sending is not in progress. + * That also means that no timers are running and no interrupts are attached. + * + * @return true if listening nor sending is in progress. + */ + bool is_idle() { return mode_ == OperationMode::IDLE; } + + /** + * Indicates whether last listen() or send() operation ends up with an error. Includes both timeout and + * protocol errors. + * + * @return true if last listen() or send() operation ends up with an error. + */ + bool is_error() { return mode_ == OperationMode::ERROR_TIMEOUT || mode_ == OperationMode::ERROR_PROTOCOL; } + + /** + * Indicates whether last listen() or send() operation ends up with a *timeout* error + * @return true if last listen() or send() operation ends up with a *timeout* error. + */ + bool is_timeout() { return mode_ == OperationMode::ERROR_TIMEOUT; } + + /** + * Indicates whether last listen() or send() operation ends up with a *protocol* error + * @return true if last listen() or send() operation ends up with a *protocol* error. + */ + bool is_protocol_error() { return mode_ == OperationMode::ERROR_PROTOCOL; } + + bool is_active() { return mode_ == LISTEN || mode_ == READ || mode_ == WRITE; } + + OperationMode get_mode() { return mode_; } + + std::string debug_data(OpenthermData &data); + std::string debug_error(OpenThermError &error); + + const char *protocol_error_to_to_str(ProtocolErrorType error_type); + const char *message_type_to_str(MessageType message_type); + const char *operation_mode_to_str(OperationMode mode); + const char *message_id_to_str(MessageId id); + + static bool timer_isr(OpenTherm *arg); + +#ifdef ESP8266 + static void esp8266_timer_isr(); +#endif + + private: + InternalGPIOPin *in_pin_; + InternalGPIOPin *out_pin_; + ISRInternalGPIOPin isr_in_pin_; + ISRInternalGPIOPin isr_out_pin_; + +#if defined(ESP32) || defined(USE_ESP_IDF) + timer_group_t timer_group_; + timer_idx_t timer_idx_; +#endif + + OperationMode mode_; + ProtocolErrorType error_type_; + uint32_t capture_; + uint8_t clock_; + uint32_t data_; + uint8_t bit_pos_; + int32_t timeout_counter_; // <0 no timeout + + int32_t device_timeout_; + +#if defined(ESP32) || defined(USE_ESP_IDF) + bool init_esp32_timer_(); + void start_esp32_timer_(uint64_t alarm_value); +#endif + + void stop_timer_(); + + void read_(); // data detected start reading + void start_read_timer_(); // reading timer_ to sample at 1/5 of manchester code bit length (at 5kHz) + void start_write_timer_(); // writing timer_ to send manchester code (at 2kHz) + bool check_parity_(uint32_t val); + + void bit_read_(uint8_t value); + ProtocolErrorType verify_stop_bit_(uint8_t value); + void write_bit_(uint8_t high, uint8_t clock); + +#ifdef ESP8266 + // ESP8266 timer can accept callback with no parameters, so we have this hack to save a static instance of OpenTherm + static OpenTherm *instance_; +#endif +}; + +} // namespace opentherm +} // namespace esphome diff --git a/tests/components/opentherm/common.yaml b/tests/components/opentherm/common.yaml new file mode 100644 index 0000000000..4148b280d0 --- /dev/null +++ b/tests/components/opentherm/common.yaml @@ -0,0 +1,3 @@ +opentherm: + in_pin: 1 + out_pin: 2 diff --git a/tests/components/opentherm/test.esp32-ard.yaml b/tests/components/opentherm/test.esp32-ard.yaml new file mode 100644 index 0000000000..dade44d145 --- /dev/null +++ b/tests/components/opentherm/test.esp32-ard.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/opentherm/test.esp32-c3-ard.yaml b/tests/components/opentherm/test.esp32-c3-ard.yaml new file mode 100644 index 0000000000..dade44d145 --- /dev/null +++ b/tests/components/opentherm/test.esp32-c3-ard.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/opentherm/test.esp32-c3-idf.yaml b/tests/components/opentherm/test.esp32-c3-idf.yaml new file mode 100644 index 0000000000..dade44d145 --- /dev/null +++ b/tests/components/opentherm/test.esp32-c3-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/opentherm/test.esp32-idf.yaml b/tests/components/opentherm/test.esp32-idf.yaml new file mode 100644 index 0000000000..dade44d145 --- /dev/null +++ b/tests/components/opentherm/test.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common.yaml diff --git a/tests/components/opentherm/test.esp8266-ard.yaml b/tests/components/opentherm/test.esp8266-ard.yaml new file mode 100644 index 0000000000..dade44d145 --- /dev/null +++ b/tests/components/opentherm/test.esp8266-ard.yaml @@ -0,0 +1 @@ +<<: !include common.yaml From 7f00b5eb658e2871afb73c5988d49b8da0fbde8b Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 12 Sep 2024 14:08:41 +1200 Subject: [PATCH 02/20] [voice-assistant] Dont error on ``no_wake_word`` timeout error with streaming wake word (#7435) --- esphome/components/voice_assistant/voice_assistant.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/voice_assistant/voice_assistant.cpp b/esphome/components/voice_assistant/voice_assistant.cpp index 577de630fb..a2210f188d 100644 --- a/esphome/components/voice_assistant/voice_assistant.cpp +++ b/esphome/components/voice_assistant/voice_assistant.cpp @@ -755,7 +755,7 @@ void VoiceAssistant::on_event(const api::VoiceAssistantEventResponse &msg) { message = std::move(arg.value); } } - if (code == "wake-word-timeout" || code == "wake_word_detection_aborted") { + if (code == "wake-word-timeout" || code == "wake_word_detection_aborted" || code == "no_wake_word") { // Don't change state here since either the "tts-end" or "run-end" events will do it. return; } else if (code == "wake-provider-missing" || code == "wake-engine-missing") { From 80e3de94d3d581f91f0197ea2ab49c2c5b6fca45 Mon Sep 17 00:00:00 2001 From: Tomer <57483589+tomer-w@users.noreply.github.com> Date: Fri, 13 Sep 2024 03:56:04 +0300 Subject: [PATCH 03/20] =?UTF-8?q?Improve=20manufacturer=20data=20tracing?= =?UTF-8?q?=20to=20identify=20BLE=20devices=20a=20bit=20easie=E2=80=A6=20(?= =?UTF-8?q?#7332)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/ble_presence/binary_sensor.py | 4 ++-- esphome/components/esp32_ble/ble_uuid.cpp | 7 +++++++ esphome/components/esp32_ble/ble_uuid.h | 1 + .../esp32_ble_tracker/esp32_ble_tracker.cpp | 20 ++++++++++--------- .../esp32_ble_tracker/esp32_ble_tracker.h | 6 +++--- 5 files changed, 24 insertions(+), 14 deletions(-) diff --git a/esphome/components/ble_presence/binary_sensor.py b/esphome/components/ble_presence/binary_sensor.py index d1fdc80289..3a0f1ade98 100644 --- a/esphome/components/ble_presence/binary_sensor.py +++ b/esphome/components/ble_presence/binary_sensor.py @@ -41,7 +41,7 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_SERVICE_UUID): esp32_ble_tracker.bt_uuid, cv.Optional(CONF_IBEACON_MAJOR): cv.uint16_t, cv.Optional(CONF_IBEACON_MINOR): cv.uint16_t, - cv.Optional(CONF_IBEACON_UUID): cv.uuid, + cv.Optional(CONF_IBEACON_UUID): esp32_ble_tracker.bt_uuid, cv.Optional(CONF_TIMEOUT, default="5min"): cv.positive_time_period, cv.Optional(CONF_MIN_RSSI): cv.All( cv.decibel, cv.int_range(min=-100, max=-30) @@ -83,7 +83,7 @@ async def to_code(config): cg.add(var.set_service_uuid128(uuid128)) if ibeacon_uuid := config.get(CONF_IBEACON_UUID): - ibeacon_uuid = esp32_ble_tracker.as_hex_array(str(ibeacon_uuid)) + ibeacon_uuid = esp32_ble_tracker.as_reversed_hex_array(ibeacon_uuid) cg.add(var.set_ibeacon_uuid(ibeacon_uuid)) if (ibeacon_major := config.get(CONF_IBEACON_MAJOR)) is not None: diff --git a/esphome/components/esp32_ble/ble_uuid.cpp b/esphome/components/esp32_ble/ble_uuid.cpp index 57c2f9df94..07ac719434 100644 --- a/esphome/components/esp32_ble/ble_uuid.cpp +++ b/esphome/components/esp32_ble/ble_uuid.cpp @@ -31,6 +31,13 @@ ESPBTUUID ESPBTUUID::from_raw(const uint8_t *data) { memcpy(ret.uuid_.uuid.uuid128, data, ESP_UUID_LEN_128); return ret; } +ESPBTUUID ESPBTUUID::from_raw_reversed(const uint8_t *data) { + ESPBTUUID ret; + ret.uuid_.len = ESP_UUID_LEN_128; + for (int i = 0; i < ESP_UUID_LEN_128; i++) + ret.uuid_.uuid.uuid128[ESP_UUID_LEN_128 - 1 - i] = data[i]; + return ret; +} ESPBTUUID ESPBTUUID::from_raw(const std::string &data) { ESPBTUUID ret; if (data.length() == 4) { diff --git a/esphome/components/esp32_ble/ble_uuid.h b/esphome/components/esp32_ble/ble_uuid.h index 790a57c59d..d90db3a599 100644 --- a/esphome/components/esp32_ble/ble_uuid.h +++ b/esphome/components/esp32_ble/ble_uuid.h @@ -20,6 +20,7 @@ class ESPBTUUID { static ESPBTUUID from_uint32(uint32_t uuid); static ESPBTUUID from_raw(const uint8_t *data); + static ESPBTUUID from_raw_reversed(const uint8_t *data); static ESPBTUUID from_raw(const std::string &data); diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp index d154d4e519..74b4b9aa89 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp @@ -462,14 +462,16 @@ void ESPBTDevice::parse_scan_rst(const esp_ble_gap_cb_param_t::ble_scan_result_e ESP_LOGVV(TAG, " Service UUID: %s", uuid.to_string().c_str()); } for (auto &data : this->manufacturer_datas_) { - ESP_LOGVV(TAG, " Manufacturer data: %s", format_hex_pretty(data.data).c_str()); - if (this->get_ibeacon().has_value()) { - auto ibeacon = this->get_ibeacon().value(); - ESP_LOGVV(TAG, " iBeacon data:"); - ESP_LOGVV(TAG, " UUID: %s", ibeacon.get_uuid().to_string().c_str()); - ESP_LOGVV(TAG, " Major: %u", ibeacon.get_major()); - ESP_LOGVV(TAG, " Minor: %u", ibeacon.get_minor()); - ESP_LOGVV(TAG, " TXPower: %d", ibeacon.get_signal_power()); + auto ibeacon = ESPBLEiBeacon::from_manufacturer_data(data); + if (ibeacon.has_value()) { + ESP_LOGVV(TAG, " Manufacturer iBeacon:"); + ESP_LOGVV(TAG, " UUID: %s", ibeacon.value().get_uuid().to_string().c_str()); + ESP_LOGVV(TAG, " Major: %u", ibeacon.value().get_major()); + ESP_LOGVV(TAG, " Minor: %u", ibeacon.value().get_minor()); + ESP_LOGVV(TAG, " TXPower: %d", ibeacon.value().get_signal_power()); + } else { + ESP_LOGVV(TAG, " Manufacturer ID: %s, data: %s", data.uuid.to_string().c_str(), + format_hex_pretty(data.data).c_str()); } } for (auto &data : this->service_datas_) { @@ -478,7 +480,7 @@ void ESPBTDevice::parse_scan_rst(const esp_ble_gap_cb_param_t::ble_scan_result_e ESP_LOGVV(TAG, " Data: %s", format_hex_pretty(data.data).c_str()); } - ESP_LOGVV(TAG, "Adv data: %s", format_hex_pretty(param.ble_adv, param.adv_data_len + param.scan_rsp_len).c_str()); + ESP_LOGVV(TAG, " Adv data: %s", format_hex_pretty(param.ble_adv, param.adv_data_len + param.scan_rsp_len).c_str()); #endif } void ESPBTDevice::parse_adv_(const esp_ble_gap_cb_param_t::ble_scan_result_evt_param ¶m) { diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h index 3db7a54f6e..d2bb6a6e6d 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h @@ -44,10 +44,10 @@ class ESPBLEiBeacon { ESPBLEiBeacon(const uint8_t *data); static optional from_manufacturer_data(const ServiceData &data); - uint16_t get_major() { return ((this->beacon_data_.major & 0xFF) << 8) | (this->beacon_data_.major >> 8); } - uint16_t get_minor() { return ((this->beacon_data_.minor & 0xFF) << 8) | (this->beacon_data_.minor >> 8); } + uint16_t get_major() { return byteswap(this->beacon_data_.major); } + uint16_t get_minor() { return byteswap(this->beacon_data_.minor); } int8_t get_signal_power() { return this->beacon_data_.signal_power; } - ESPBTUUID get_uuid() { return ESPBTUUID::from_raw(this->beacon_data_.proximity_uuid); } + ESPBTUUID get_uuid() { return ESPBTUUID::from_raw_reversed(this->beacon_data_.proximity_uuid); } protected: struct { From 5d8fb7cdf4a21b649427318cd9d883bb679c8d80 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 16 Sep 2024 10:01:34 +1200 Subject: [PATCH 04/20] Bump version to 2024.9.0b2 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index fb745fd506..ce6b9f3d01 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2024.9.0b1" +__version__ = "2024.9.0b2" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" VALID_SUBSTITUTIONS_CHARACTERS = ( From f652cd3851d17a4434876ab546021c6f698cb47f Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Mon, 16 Sep 2024 08:42:45 +1000 Subject: [PATCH 05/20] [st7701s] Make use of IDF5.x to speed up display operations (#7447) --- esphome/components/st7701s/st7701s.cpp | 32 +++++++++++++++++++------- esphome/components/st7701s/st7701s.h | 2 ++ 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/esphome/components/st7701s/st7701s.cpp b/esphome/components/st7701s/st7701s.cpp index 7f02fe1774..7248bc044e 100644 --- a/esphome/components/st7701s/st7701s.cpp +++ b/esphome/components/st7701s/st7701s.cpp @@ -8,8 +8,22 @@ namespace st7701s { void ST7701S::setup() { esph_log_config(TAG, "Setting up ST7701S"); this->spi_setup(); + this->write_init_sequence_(); +} + +// called after a delay after writing the init sequence +void ST7701S::complete_setup_() { + this->write_command_(SLEEP_OUT); + this->write_command_(DISPLAY_ON); + this->spi_teardown(); // SPI not needed after this + delay(10); + esp_lcd_rgb_panel_config_t config{}; config.flags.fb_in_psram = 1; +#if ESP_IDF_VERSION_MAJOR >= 5 + config.bounce_buffer_size_px = this->width_ * 10; + config.num_fbs = 1; +#endif // ESP_IDF_VERSION_MAJOR config.timings.h_res = this->width_; config.timings.v_res = this->height_; config.timings.hsync_pulse_width = this->hsync_pulse_width_; @@ -21,7 +35,6 @@ void ST7701S::setup() { config.timings.flags.pclk_active_neg = this->pclk_inverted_; config.timings.pclk_hz = this->pclk_frequency_; config.clk_src = LCD_CLK_SRC_PLL160M; - config.sram_trans_align = 64; config.psram_trans_align = 64; size_t data_pin_count = sizeof(this->data_pins_) / sizeof(this->data_pins_[0]); for (size_t i = 0; i != data_pin_count; i++) { @@ -34,15 +47,21 @@ void ST7701S::setup() { config.de_gpio_num = this->de_pin_->get_pin(); config.pclk_gpio_num = this->pclk_pin_->get_pin(); esp_err_t err = esp_lcd_new_rgb_panel(&config, &this->handle_); + ESP_ERROR_CHECK(esp_lcd_panel_reset(this->handle_)); + ESP_ERROR_CHECK(esp_lcd_panel_init(this->handle_)); if (err != ESP_OK) { esph_log_e(TAG, "lcd_new_rgb_panel failed: %s", esp_err_to_name(err)); } - ESP_ERROR_CHECK(esp_lcd_panel_reset(this->handle_)); - ESP_ERROR_CHECK(esp_lcd_panel_init(this->handle_)); - this->write_init_sequence_(); esph_log_config(TAG, "ST7701S setup complete"); } +void ST7701S::loop() { +#if ESP_IDF_VERSION_MAJOR >= 5 + if (this->handle_ != nullptr) + esp_lcd_rgb_panel_restart(this->handle_); +#endif // ESP_IDF_VERSION_MAJOR +} + void ST7701S::draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *ptr, display::ColorOrder order, display::ColorBitness bitness, bool big_endian, int x_offset, int y_offset, int x_pad) { if (w <= 0 || h <= 0) @@ -160,10 +179,7 @@ void ST7701S::write_init_sequence_() { this->write_data_(val); ESP_LOGD(TAG, "write MADCTL %X", val); this->write_command_(this->invert_colors_ ? INVERT_ON : INVERT_OFF); - this->set_timeout(120, [this] { - this->write_command_(SLEEP_OUT); - this->write_command_(DISPLAY_ON); - }); + this->set_timeout(120, [this] { this->complete_setup_(); }); } void ST7701S::dump_config() { diff --git a/esphome/components/st7701s/st7701s.h b/esphome/components/st7701s/st7701s.h index 80e5b81f4a..a1e3c2e54a 100644 --- a/esphome/components/st7701s/st7701s.h +++ b/esphome/components/st7701s/st7701s.h @@ -33,6 +33,8 @@ class ST7701S : public display::Display, public: void update() override { this->do_update_(); } void setup() override; + void complete_setup_(); + void loop() override; void draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *ptr, display::ColorOrder order, display::ColorBitness bitness, bool big_endian, int x_offset, int y_offset, int x_pad) override; From 857d79dc714f123dabafe46eacbbbae0e979a19c Mon Sep 17 00:00:00 2001 From: Michael Hansen Date: Sun, 15 Sep 2024 18:46:54 -0500 Subject: [PATCH 06/20] Add sample_bytes to media player supported format (#7451) --- esphome/components/api/api.proto | 1 + esphome/components/api/api_connection.cpp | 1 + esphome/components/api/api_pb2.cpp | 10 ++++++++++ esphome/components/api/api_pb2.h | 1 + esphome/components/media_player/media_player.h | 1 + 5 files changed, 14 insertions(+) diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index 1c40e8014e..a966643d30 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -1118,6 +1118,7 @@ message MediaPlayerSupportedFormat { uint32 sample_rate = 2; uint32 num_channels = 3; MediaPlayerFormatPurpose purpose = 4; + uint32 sample_bytes = 5; } message ListEntitiesMediaPlayerResponse { option (id) = 63; diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 6b7051a704..20e9a45314 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -1032,6 +1032,7 @@ bool APIConnection::send_media_player_info(media_player::MediaPlayer *media_play media_format.sample_rate = supported_format.sample_rate; media_format.num_channels = supported_format.num_channels; media_format.purpose = static_cast(supported_format.purpose); + media_format.sample_bytes = supported_format.sample_bytes; msg.supported_formats.push_back(media_format); } diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index 2a1552d6fc..955f17612a 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -5149,6 +5149,10 @@ bool MediaPlayerSupportedFormat::decode_varint(uint32_t field_id, ProtoVarInt va this->purpose = value.as_enum(); return true; } + case 5: { + this->sample_bytes = value.as_uint32(); + return true; + } default: return false; } @@ -5168,6 +5172,7 @@ void MediaPlayerSupportedFormat::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint32(2, this->sample_rate); buffer.encode_uint32(3, this->num_channels); buffer.encode_enum(4, this->purpose); + buffer.encode_uint32(5, this->sample_bytes); } #ifdef HAS_PROTO_MESSAGE_DUMP void MediaPlayerSupportedFormat::dump_to(std::string &out) const { @@ -5190,6 +5195,11 @@ void MediaPlayerSupportedFormat::dump_to(std::string &out) const { out.append(" purpose: "); out.append(proto_enum_to_string(this->purpose)); out.append("\n"); + + out.append(" sample_bytes: "); + sprintf(buffer, "%" PRIu32, this->sample_bytes); + out.append(buffer); + out.append("\n"); out.append("}"); } #endif diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index 6fab1f57e0..1ce6482b09 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -1277,6 +1277,7 @@ class MediaPlayerSupportedFormat : public ProtoMessage { uint32_t sample_rate{0}; uint32_t num_channels{0}; enums::MediaPlayerFormatPurpose purpose{}; + uint32_t sample_bytes{0}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; diff --git a/esphome/components/media_player/media_player.h b/esphome/components/media_player/media_player.h index 26bef55afc..78b3ed6216 100644 --- a/esphome/components/media_player/media_player.h +++ b/esphome/components/media_player/media_player.h @@ -37,6 +37,7 @@ struct MediaPlayerSupportedFormat { uint32_t sample_rate; uint32_t num_channels; MediaPlayerFormatPurpose purpose; + uint32_t sample_bytes; }; class MediaPlayer; From bfde7fd9d796a2e65f92e6d074e432de00a8989b Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 16 Sep 2024 12:32:39 +1200 Subject: [PATCH 07/20] [docker] Bump git from 1:2.39.2-1.1 to 1:2.39.5-0+deb12u1 (#7452) --- docker/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index e255f4e2fc..85823687c2 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -33,7 +33,7 @@ RUN \ python3-venv=3.11.2-1+b1 \ python3-wheel=0.38.4-2 \ iputils-ping=3:20221126-1 \ - git=1:2.39.2-1.1 \ + git=1:2.39.5-0+deb12u1 \ curl=7.88.1-10+deb12u7 \ openssh-client=1:9.2p1-2+deb12u3 \ python3-cffi=1.15.1-5 \ From e7fe9b374fb8ac9c1e7c5ab5869f8114509fbe6a Mon Sep 17 00:00:00 2001 From: Michael Hansen Date: Sun, 15 Sep 2024 18:46:54 -0500 Subject: [PATCH 08/20] Add sample_bytes to media player supported format (#7451) --- esphome/components/api/api.proto | 1 + esphome/components/api/api_connection.cpp | 1 + esphome/components/api/api_pb2.cpp | 10 ++++++++++ esphome/components/api/api_pb2.h | 1 + esphome/components/media_player/media_player.h | 1 + 5 files changed, 14 insertions(+) diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index 1c40e8014e..a966643d30 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -1118,6 +1118,7 @@ message MediaPlayerSupportedFormat { uint32 sample_rate = 2; uint32 num_channels = 3; MediaPlayerFormatPurpose purpose = 4; + uint32 sample_bytes = 5; } message ListEntitiesMediaPlayerResponse { option (id) = 63; diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 6b7051a704..20e9a45314 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -1032,6 +1032,7 @@ bool APIConnection::send_media_player_info(media_player::MediaPlayer *media_play media_format.sample_rate = supported_format.sample_rate; media_format.num_channels = supported_format.num_channels; media_format.purpose = static_cast(supported_format.purpose); + media_format.sample_bytes = supported_format.sample_bytes; msg.supported_formats.push_back(media_format); } diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index 2a1552d6fc..955f17612a 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -5149,6 +5149,10 @@ bool MediaPlayerSupportedFormat::decode_varint(uint32_t field_id, ProtoVarInt va this->purpose = value.as_enum(); return true; } + case 5: { + this->sample_bytes = value.as_uint32(); + return true; + } default: return false; } @@ -5168,6 +5172,7 @@ void MediaPlayerSupportedFormat::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint32(2, this->sample_rate); buffer.encode_uint32(3, this->num_channels); buffer.encode_enum(4, this->purpose); + buffer.encode_uint32(5, this->sample_bytes); } #ifdef HAS_PROTO_MESSAGE_DUMP void MediaPlayerSupportedFormat::dump_to(std::string &out) const { @@ -5190,6 +5195,11 @@ void MediaPlayerSupportedFormat::dump_to(std::string &out) const { out.append(" purpose: "); out.append(proto_enum_to_string(this->purpose)); out.append("\n"); + + out.append(" sample_bytes: "); + sprintf(buffer, "%" PRIu32, this->sample_bytes); + out.append(buffer); + out.append("\n"); out.append("}"); } #endif diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index 6fab1f57e0..1ce6482b09 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -1277,6 +1277,7 @@ class MediaPlayerSupportedFormat : public ProtoMessage { uint32_t sample_rate{0}; uint32_t num_channels{0}; enums::MediaPlayerFormatPurpose purpose{}; + uint32_t sample_bytes{0}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; diff --git a/esphome/components/media_player/media_player.h b/esphome/components/media_player/media_player.h index 26bef55afc..78b3ed6216 100644 --- a/esphome/components/media_player/media_player.h +++ b/esphome/components/media_player/media_player.h @@ -37,6 +37,7 @@ struct MediaPlayerSupportedFormat { uint32_t sample_rate; uint32_t num_channels; MediaPlayerFormatPurpose purpose; + uint32_t sample_bytes; }; class MediaPlayer; From 6483ceb6eb0b4aeee7f2fe1dc9c2cf8d5f3d448d Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 16 Sep 2024 12:32:39 +1200 Subject: [PATCH 09/20] [docker] Bump git from 1:2.39.2-1.1 to 1:2.39.5-0+deb12u1 (#7452) --- docker/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index e255f4e2fc..85823687c2 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -33,7 +33,7 @@ RUN \ python3-venv=3.11.2-1+b1 \ python3-wheel=0.38.4-2 \ iputils-ping=3:20221126-1 \ - git=1:2.39.2-1.1 \ + git=1:2.39.5-0+deb12u1 \ curl=7.88.1-10+deb12u7 \ openssh-client=1:9.2p1-2+deb12u3 \ python3-cffi=1.15.1-5 \ From a63b9a9e0c58b27279ead5d3238273fce45cd6ba Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 16 Sep 2024 13:17:06 +1200 Subject: [PATCH 10/20] Bump version to 2024.9.0b3 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index ce6b9f3d01..24deba1f15 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2024.9.0b2" +__version__ = "2024.9.0b3" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" VALID_SUBSTITUTIONS_CHARACTERS = ( From 3835ad8c1f793f19d21cf2a158744ae6228ae0b4 Mon Sep 17 00:00:00 2001 From: Michael Hansen Date: Sun, 15 Sep 2024 20:40:45 -0500 Subject: [PATCH 11/20] Add voice assistant configuration messages (#7445) --- esphome/components/api/api.proto | 30 +++++ esphome/components/api/api_pb2.cpp | 143 +++++++++++++++++++++ esphome/components/api/api_pb2.h | 48 +++++++ esphome/components/api/api_pb2_service.cpp | 35 +++++ esphome/components/api/api_pb2_service.h | 9 ++ 5 files changed, 265 insertions(+) diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index a966643d30..92fb57b711 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -1571,6 +1571,36 @@ message VoiceAssistantAnnounceFinished { bool success = 1; } +message VoiceAssistantWakeWord { + uint32 id = 1; + string wake_word = 2; + repeated string trained_languages = 3; +} + +message VoiceAssistantConfigurationRequest { + option (id) = 121; + option (source) = SOURCE_CLIENT; + option (ifdef) = "USE_VOICE_ASSISTANT"; +} + +message VoiceAssistantConfigurationResponse { + option (id) = 122; + option (source) = SOURCE_SERVER; + option (ifdef) = "USE_VOICE_ASSISTANT"; + + repeated VoiceAssistantWakeWord available_wake_words = 1; + repeated uint32 active_wake_words = 2; + uint32 max_active_wake_words = 3; +} + +message VoiceAssistantSetConfiguration { + option (id) = 123; + option (source) = SOURCE_CLIENT; + option (ifdef) = "USE_VOICE_ASSISTANT"; + + repeated uint32 active_wake_words = 1; +} + // ==================== ALARM CONTROL PANEL ==================== enum AlarmControlPanelState { ALARM_STATE_DISARMED = 0; diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index 955f17612a..791de29511 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -7124,6 +7124,149 @@ void VoiceAssistantAnnounceFinished::dump_to(std::string &out) const { out.append("}"); } #endif +bool VoiceAssistantWakeWord::decode_varint(uint32_t field_id, ProtoVarInt value) { + switch (field_id) { + case 1: { + this->id = value.as_uint32(); + return true; + } + default: + return false; + } +} +bool VoiceAssistantWakeWord::decode_length(uint32_t field_id, ProtoLengthDelimited value) { + switch (field_id) { + case 2: { + this->wake_word = value.as_string(); + return true; + } + case 3: { + this->trained_languages.push_back(value.as_string()); + return true; + } + default: + return false; + } +} +void VoiceAssistantWakeWord::encode(ProtoWriteBuffer buffer) const { + buffer.encode_uint32(1, this->id); + buffer.encode_string(2, this->wake_word); + for (auto &it : this->trained_languages) { + buffer.encode_string(3, it, true); + } +} +#ifdef HAS_PROTO_MESSAGE_DUMP +void VoiceAssistantWakeWord::dump_to(std::string &out) const { + __attribute__((unused)) char buffer[64]; + out.append("VoiceAssistantWakeWord {\n"); + out.append(" id: "); + sprintf(buffer, "%" PRIu32, this->id); + out.append(buffer); + out.append("\n"); + + out.append(" wake_word: "); + out.append("'").append(this->wake_word).append("'"); + out.append("\n"); + + for (const auto &it : this->trained_languages) { + out.append(" trained_languages: "); + out.append("'").append(it).append("'"); + out.append("\n"); + } + out.append("}"); +} +#endif +void VoiceAssistantConfigurationRequest::encode(ProtoWriteBuffer buffer) const {} +#ifdef HAS_PROTO_MESSAGE_DUMP +void VoiceAssistantConfigurationRequest::dump_to(std::string &out) const { + out.append("VoiceAssistantConfigurationRequest {}"); +} +#endif +bool VoiceAssistantConfigurationResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { + switch (field_id) { + case 2: { + this->active_wake_words.push_back(value.as_uint32()); + return true; + } + case 3: { + this->max_active_wake_words = value.as_uint32(); + return true; + } + default: + return false; + } +} +bool VoiceAssistantConfigurationResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { + switch (field_id) { + case 1: { + this->available_wake_words.push_back(value.as_message()); + return true; + } + default: + return false; + } +} +void VoiceAssistantConfigurationResponse::encode(ProtoWriteBuffer buffer) const { + for (auto &it : this->available_wake_words) { + buffer.encode_message(1, it, true); + } + for (auto &it : this->active_wake_words) { + buffer.encode_uint32(2, it, true); + } + buffer.encode_uint32(3, this->max_active_wake_words); +} +#ifdef HAS_PROTO_MESSAGE_DUMP +void VoiceAssistantConfigurationResponse::dump_to(std::string &out) const { + __attribute__((unused)) char buffer[64]; + out.append("VoiceAssistantConfigurationResponse {\n"); + for (const auto &it : this->available_wake_words) { + out.append(" available_wake_words: "); + it.dump_to(out); + out.append("\n"); + } + + for (const auto &it : this->active_wake_words) { + out.append(" active_wake_words: "); + sprintf(buffer, "%" PRIu32, it); + out.append(buffer); + out.append("\n"); + } + + out.append(" max_active_wake_words: "); + sprintf(buffer, "%" PRIu32, this->max_active_wake_words); + out.append(buffer); + out.append("\n"); + out.append("}"); +} +#endif +bool VoiceAssistantSetConfiguration::decode_varint(uint32_t field_id, ProtoVarInt value) { + switch (field_id) { + case 1: { + this->active_wake_words.push_back(value.as_uint32()); + return true; + } + default: + return false; + } +} +void VoiceAssistantSetConfiguration::encode(ProtoWriteBuffer buffer) const { + for (auto &it : this->active_wake_words) { + buffer.encode_uint32(1, it, true); + } +} +#ifdef HAS_PROTO_MESSAGE_DUMP +void VoiceAssistantSetConfiguration::dump_to(std::string &out) const { + __attribute__((unused)) char buffer[64]; + out.append("VoiceAssistantSetConfiguration {\n"); + for (const auto &it : this->active_wake_words) { + out.append(" active_wake_words: "); + sprintf(buffer, "%" PRIu32, it); + out.append(buffer); + out.append("\n"); + } + out.append("}"); +} +#endif bool ListEntitiesAlarmControlPanelResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { switch (field_id) { case 6: { diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index 1ce6482b09..8631884f94 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -1849,6 +1849,54 @@ class VoiceAssistantAnnounceFinished : public ProtoMessage { protected: bool decode_varint(uint32_t field_id, ProtoVarInt value) override; }; +class VoiceAssistantWakeWord : public ProtoMessage { + public: + uint32_t id{0}; + std::string wake_word{}; + std::vector trained_languages{}; + void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP + void dump_to(std::string &out) const override; +#endif + + protected: + bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; + bool decode_varint(uint32_t field_id, ProtoVarInt value) override; +}; +class VoiceAssistantConfigurationRequest : public ProtoMessage { + public: + void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP + void dump_to(std::string &out) const override; +#endif + + protected: +}; +class VoiceAssistantConfigurationResponse : public ProtoMessage { + public: + std::vector available_wake_words{}; + std::vector active_wake_words{}; + uint32_t max_active_wake_words{0}; + void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP + void dump_to(std::string &out) const override; +#endif + + protected: + bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; + bool decode_varint(uint32_t field_id, ProtoVarInt value) override; +}; +class VoiceAssistantSetConfiguration : public ProtoMessage { + public: + std::vector active_wake_words{}; + void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP + void dump_to(std::string &out) const override; +#endif + + protected: + bool decode_varint(uint32_t field_id, ProtoVarInt value) override; +}; class ListEntitiesAlarmControlPanelResponse : public ProtoMessage { public: std::string object_id{}; diff --git a/esphome/components/api/api_pb2_service.cpp b/esphome/components/api/api_pb2_service.cpp index faa977389a..454f20d50a 100644 --- a/esphome/components/api/api_pb2_service.cpp +++ b/esphome/components/api/api_pb2_service.cpp @@ -496,6 +496,19 @@ bool APIServerConnectionBase::send_voice_assistant_announce_finished(const Voice return this->send_message_(msg, 120); } #endif +#ifdef USE_VOICE_ASSISTANT +#endif +#ifdef USE_VOICE_ASSISTANT +bool APIServerConnectionBase::send_voice_assistant_configuration_response( + const VoiceAssistantConfigurationResponse &msg) { +#ifdef HAS_PROTO_MESSAGE_DUMP + ESP_LOGVV(TAG, "send_voice_assistant_configuration_response: %s", msg.dump().c_str()); +#endif + return this->send_message_(msg, 122); +} +#endif +#ifdef USE_VOICE_ASSISTANT +#endif #ifdef USE_ALARM_CONTROL_PANEL bool APIServerConnectionBase::send_list_entities_alarm_control_panel_response( const ListEntitiesAlarmControlPanelResponse &msg) { @@ -1156,6 +1169,28 @@ bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, ESP_LOGVV(TAG, "on_voice_assistant_announce_request: %s", msg.dump().c_str()); #endif this->on_voice_assistant_announce_request(msg); +#endif + break; + } + case 121: { +#ifdef USE_VOICE_ASSISTANT + VoiceAssistantConfigurationRequest msg; + msg.decode(msg_data, msg_size); +#ifdef HAS_PROTO_MESSAGE_DUMP + ESP_LOGVV(TAG, "on_voice_assistant_configuration_request: %s", msg.dump().c_str()); +#endif + this->on_voice_assistant_configuration_request(msg); +#endif + break; + } + case 123: { +#ifdef USE_VOICE_ASSISTANT + VoiceAssistantSetConfiguration msg; + msg.decode(msg_data, msg_size); +#ifdef HAS_PROTO_MESSAGE_DUMP + ESP_LOGVV(TAG, "on_voice_assistant_set_configuration: %s", msg.dump().c_str()); +#endif + this->on_voice_assistant_set_configuration(msg); #endif break; } diff --git a/esphome/components/api/api_pb2_service.h b/esphome/components/api/api_pb2_service.h index f3803ad628..e69954f7ab 100644 --- a/esphome/components/api/api_pb2_service.h +++ b/esphome/components/api/api_pb2_service.h @@ -253,6 +253,15 @@ class APIServerConnectionBase : public ProtoService { #ifdef USE_VOICE_ASSISTANT bool send_voice_assistant_announce_finished(const VoiceAssistantAnnounceFinished &msg); #endif +#ifdef USE_VOICE_ASSISTANT + virtual void on_voice_assistant_configuration_request(const VoiceAssistantConfigurationRequest &value){}; +#endif +#ifdef USE_VOICE_ASSISTANT + bool send_voice_assistant_configuration_response(const VoiceAssistantConfigurationResponse &msg); +#endif +#ifdef USE_VOICE_ASSISTANT + virtual void on_voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &value){}; +#endif #ifdef USE_ALARM_CONTROL_PANEL bool send_list_entities_alarm_control_panel_response(const ListEntitiesAlarmControlPanelResponse &msg); #endif From 73e469ae528cb4a522f8125837416f97a91e50de Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 16 Sep 2024 13:43:45 +1200 Subject: [PATCH 12/20] [modbus_controller] Fix linting and formatting issues (#7441) --- .../components/modbus_controller/__init__.py | 12 ++++++---- .../binary_sensor/__init__.py | 14 +++++------ .../modbus_controller/number/__init__.py | 11 ++++----- .../modbus_controller/output/__init__.py | 24 ++++++++----------- .../modbus_controller/select/__init__.py | 2 +- .../modbus_controller/sensor/__init__.py | 16 ++++++------- .../modbus_controller/switch/__init__.py | 15 ++++++------ .../modbus_controller/text_sensor/__init__.py | 19 +++++++-------- 8 files changed, 54 insertions(+), 59 deletions(-) diff --git a/esphome/components/modbus_controller/__init__.py b/esphome/components/modbus_controller/__init__.py index 6917807b07..488baa245a 100644 --- a/esphome/components/modbus_controller/__init__.py +++ b/esphome/components/modbus_controller/__init__.py @@ -1,27 +1,29 @@ import binascii -import esphome.codegen as cg -import esphome.config_validation as cv + from esphome import automation +import esphome.codegen as cg from esphome.components import modbus +import esphome.config_validation as cv from esphome.const import ( CONF_ADDRESS, CONF_ID, - CONF_NAME, CONF_LAMBDA, + CONF_NAME, CONF_OFFSET, CONF_TRIGGER_ID, ) from esphome.cpp_helpers import logging + from .const import ( CONF_ALLOW_DUPLICATE_COMMANDS, CONF_BITMASK, CONF_BYTE_OFFSET, CONF_COMMAND_THROTTLE, - CONF_OFFLINE_SKIP_UPDATES, CONF_CUSTOM_COMMAND, CONF_FORCE_NEW_RANGE, - CONF_MODBUS_CONTROLLER_ID, CONF_MAX_CMD_RETRIES, + CONF_MODBUS_CONTROLLER_ID, + CONF_OFFLINE_SKIP_UPDATES, CONF_ON_COMMAND_SENT, CONF_REGISTER_COUNT, CONF_REGISTER_TYPE, diff --git a/esphome/components/modbus_controller/binary_sensor/__init__.py b/esphome/components/modbus_controller/binary_sensor/__init__.py index 5315167479..2ae008f630 100644 --- a/esphome/components/modbus_controller/binary_sensor/__init__.py +++ b/esphome/components/modbus_controller/binary_sensor/__init__.py @@ -1,16 +1,16 @@ +import esphome.codegen as cg from esphome.components import binary_sensor import esphome.config_validation as cv -import esphome.codegen as cg - from esphome.const import CONF_ADDRESS, CONF_ID + from .. import ( - add_modbus_base_properties, - modbus_controller_ns, - modbus_calc_properties, - validate_modbus_register, + MODBUS_REGISTER_TYPE, ModbusItemBaseSchema, SensorItem, - MODBUS_REGISTER_TYPE, + add_modbus_base_properties, + modbus_calc_properties, + modbus_controller_ns, + validate_modbus_register, ) from ..const import ( CONF_BITMASK, diff --git a/esphome/components/modbus_controller/number/__init__.py b/esphome/components/modbus_controller/number/__init__.py index fe99b28a00..b5efd7abf0 100644 --- a/esphome/components/modbus_controller/number/__init__.py +++ b/esphome/components/modbus_controller/number/__init__.py @@ -1,6 +1,6 @@ import esphome.codegen as cg -import esphome.config_validation as cv from esphome.components import number +import esphome.config_validation as cv from esphome.const import ( CONF_ADDRESS, CONF_ID, @@ -12,14 +12,13 @@ from esphome.const import ( from .. import ( MODBUS_WRITE_REGISTER_TYPE, - add_modbus_base_properties, - modbus_controller_ns, - modbus_calc_properties, + SENSOR_VALUE_TYPE, ModbusItemBaseSchema, SensorItem, - SENSOR_VALUE_TYPE, + add_modbus_base_properties, + modbus_calc_properties, + modbus_controller_ns, ) - from ..const import ( CONF_BITMASK, CONF_CUSTOM_COMMAND, diff --git a/esphome/components/modbus_controller/output/__init__.py b/esphome/components/modbus_controller/output/__init__.py index 1bf989ce8b..1800a90d57 100644 --- a/esphome/components/modbus_controller/output/__init__.py +++ b/esphome/components/modbus_controller/output/__init__.py @@ -1,20 +1,15 @@ import esphome.codegen as cg -import esphome.config_validation as cv from esphome.components import output -from esphome.const import ( - CONF_ADDRESS, - CONF_ID, - CONF_MULTIPLY, -) +import esphome.config_validation as cv +from esphome.const import CONF_ADDRESS, CONF_ID, CONF_MULTIPLY from .. import ( - modbus_controller_ns, - modbus_calc_properties, + SENSOR_VALUE_TYPE, ModbusItemBaseSchema, SensorItem, - SENSOR_VALUE_TYPE, + modbus_calc_properties, + modbus_controller_ns, ) - from ..const import ( CONF_MODBUS_CONTROLLER_ID, CONF_REGISTER_TYPE, @@ -65,6 +60,7 @@ CONFIG_SCHEMA = cv.typed_schema( async def to_code(config): byte_offset, reg_count = modbus_calc_properties(config) # Binary Output + write_template = None if config[CONF_REGISTER_TYPE] == "coil": var = cg.new_Pvariable( config[CONF_ID], @@ -72,7 +68,7 @@ async def to_code(config): byte_offset, ) if CONF_WRITE_LAMBDA in config: - template_ = await cg.process_lambda( + write_template = await cg.process_lambda( config[CONF_WRITE_LAMBDA], [ (ModbusBinaryOutput.operator("ptr"), "item"), @@ -92,7 +88,7 @@ async def to_code(config): ) cg.add(var.set_write_multiply(config[CONF_MULTIPLY])) if CONF_WRITE_LAMBDA in config: - template_ = await cg.process_lambda( + write_template = await cg.process_lambda( config[CONF_WRITE_LAMBDA], [ (ModbusFloatOutput.operator("ptr"), "item"), @@ -105,5 +101,5 @@ async def to_code(config): parent = await cg.get_variable(config[CONF_MODBUS_CONTROLLER_ID]) cg.add(var.set_use_write_mutiple(config[CONF_USE_WRITE_MULTIPLE])) cg.add(var.set_parent(parent)) - if CONF_WRITE_LAMBDA in config: - cg.add(var.set_write_template(template_)) + if write_template: + cg.add(var.set_write_template(write_template)) diff --git a/esphome/components/modbus_controller/select/__init__.py b/esphome/components/modbus_controller/select/__init__.py index 5692fea3e3..c94532da51 100644 --- a/esphome/components/modbus_controller/select/__init__.py +++ b/esphome/components/modbus_controller/select/__init__.py @@ -1,6 +1,6 @@ import esphome.codegen as cg -import esphome.config_validation as cv from esphome.components import select +import esphome.config_validation as cv from esphome.const import CONF_ADDRESS, CONF_ID, CONF_LAMBDA, CONF_OPTIMISTIC from .. import ( diff --git a/esphome/components/modbus_controller/sensor/__init__.py b/esphome/components/modbus_controller/sensor/__init__.py index 0e4588cfef..d8fce54ece 100644 --- a/esphome/components/modbus_controller/sensor/__init__.py +++ b/esphome/components/modbus_controller/sensor/__init__.py @@ -1,17 +1,17 @@ +import esphome.codegen as cg from esphome.components import sensor import esphome.config_validation as cv -import esphome.codegen as cg +from esphome.const import CONF_ADDRESS, CONF_ID -from esphome.const import CONF_ID, CONF_ADDRESS from .. import ( - add_modbus_base_properties, - modbus_controller_ns, - modbus_calc_properties, - validate_modbus_register, - ModbusItemBaseSchema, - SensorItem, MODBUS_REGISTER_TYPE, SENSOR_VALUE_TYPE, + ModbusItemBaseSchema, + SensorItem, + add_modbus_base_properties, + modbus_calc_properties, + modbus_controller_ns, + validate_modbus_register, ) from ..const import ( CONF_BITMASK, diff --git a/esphome/components/modbus_controller/switch/__init__.py b/esphome/components/modbus_controller/switch/__init__.py index 9490325968..258d87fd25 100644 --- a/esphome/components/modbus_controller/switch/__init__.py +++ b/esphome/components/modbus_controller/switch/__init__.py @@ -1,17 +1,16 @@ +import esphome.codegen as cg from esphome.components import switch import esphome.config_validation as cv -import esphome.codegen as cg +from esphome.const import CONF_ADDRESS, CONF_ID - -from esphome.const import CONF_ID, CONF_ADDRESS from .. import ( - add_modbus_base_properties, - modbus_controller_ns, - modbus_calc_properties, - validate_modbus_register, + MODBUS_REGISTER_TYPE, ModbusItemBaseSchema, SensorItem, - MODBUS_REGISTER_TYPE, + add_modbus_base_properties, + modbus_calc_properties, + modbus_controller_ns, + validate_modbus_register, ) from ..const import ( CONF_BITMASK, diff --git a/esphome/components/modbus_controller/text_sensor/__init__.py b/esphome/components/modbus_controller/text_sensor/__init__.py index 81d6453c6f..35cae645e1 100644 --- a/esphome/components/modbus_controller/text_sensor/__init__.py +++ b/esphome/components/modbus_controller/text_sensor/__init__.py @@ -1,26 +1,25 @@ +import esphome.codegen as cg from esphome.components import text_sensor import esphome.config_validation as cv -import esphome.codegen as cg - - from esphome.const import CONF_ADDRESS, CONF_ID + from .. import ( - add_modbus_base_properties, - modbus_controller_ns, - modbus_calc_properties, - validate_modbus_register, + MODBUS_REGISTER_TYPE, ModbusItemBaseSchema, SensorItem, - MODBUS_REGISTER_TYPE, + add_modbus_base_properties, + modbus_calc_properties, + modbus_controller_ns, + validate_modbus_register, ) from ..const import ( CONF_FORCE_NEW_RANGE, CONF_MODBUS_CONTROLLER_ID, + CONF_RAW_ENCODE, CONF_REGISTER_COUNT, + CONF_REGISTER_TYPE, CONF_RESPONSE_SIZE, CONF_SKIP_UPDATES, - CONF_RAW_ENCODE, - CONF_REGISTER_TYPE, ) DEPENDENCIES = ["modbus_controller"] From 435789a96020435b6d0c345bebb6ef19a98fd818 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 16 Sep 2024 17:21:42 +1200 Subject: [PATCH 13/20] Bump pylint from 3.1.0 to 3.2.7 (#7438) --- requirements_test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test.txt b/requirements_test.txt index 94abe1cd76..5d94f7f640 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,4 +1,4 @@ -pylint==3.1.0 +pylint==3.2.7 flake8==7.0.0 # also change in .pre-commit-config.yaml when updating black==24.4.2 # also change in .pre-commit-config.yaml when updating pyupgrade==3.15.2 # also change in .pre-commit-config.yaml when updating From 857a3dcf72d74fa53fba2f6c63f2ceaaec3e5e59 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 16 Sep 2024 18:03:51 +1200 Subject: [PATCH 14/20] Dont replace project name spaces with underlines (#7455) --- esphome/core/config.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/esphome/core/config.py b/esphome/core/config.py index 739a8a1aea..f4253bee87 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -100,9 +100,6 @@ def valid_include(value): def valid_project_name(value: str): if value.count(".") != 1: raise cv.Invalid("project name needs to have a namespace") - - value = value.replace(" ", "_") - return value From cb86749545ef26b543118aa2b349376add89829f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 17 Sep 2024 13:47:16 +1200 Subject: [PATCH 15/20] Bump peter-evans/create-pull-request from 7.0.2 to 7.0.3 (#7457) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/sync-device-classes.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/sync-device-classes.yml b/.github/workflows/sync-device-classes.yml index 1418867240..eeb8386e74 100644 --- a/.github/workflows/sync-device-classes.yml +++ b/.github/workflows/sync-device-classes.yml @@ -36,7 +36,7 @@ jobs: python ./script/sync-device_class.py - name: Commit changes - uses: peter-evans/create-pull-request@v7.0.2 + uses: peter-evans/create-pull-request@v7.0.3 with: commit-message: "Synchronise Device Classes from Home Assistant" committer: esphomebot From 5a3e1d57921a839a5f383350e45cdd3304efa311 Mon Sep 17 00:00:00 2001 From: Michael Hansen Date: Tue, 17 Sep 2024 18:38:39 -0500 Subject: [PATCH 16/20] Add voice assistant methods for configuration (#7459) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/api/api.proto | 8 ++-- esphome/components/api/api_connection.cpp | 33 +++++++++++++++ esphome/components/api/api_connection.h | 3 ++ esphome/components/api/api_pb2.cpp | 41 ++++++++----------- esphome/components/api/api_pb2.h | 9 ++-- esphome/components/api/api_pb2_service.cpp | 29 +++++++++++++ esphome/components/api/api_pb2_service.h | 13 ++++++ .../voice_assistant/voice_assistant.h | 16 ++++++++ 8 files changed, 119 insertions(+), 33 deletions(-) diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index 92fb57b711..684540ffa6 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -62,6 +62,8 @@ service APIConnection { rpc unsubscribe_bluetooth_le_advertisements(UnsubscribeBluetoothLEAdvertisementsRequest) returns (void) {} rpc subscribe_voice_assistant(SubscribeVoiceAssistantRequest) returns (void) {} + rpc voice_assistant_get_configuration(VoiceAssistantConfigurationRequest) returns (VoiceAssistantConfigurationResponse) {} + rpc voice_assistant_set_configuration(VoiceAssistantSetConfiguration) returns (void) {} rpc alarm_control_panel_command (AlarmControlPanelCommandRequest) returns (void) {} } @@ -1572,7 +1574,7 @@ message VoiceAssistantAnnounceFinished { } message VoiceAssistantWakeWord { - uint32 id = 1; + string id = 1; string wake_word = 2; repeated string trained_languages = 3; } @@ -1589,7 +1591,7 @@ message VoiceAssistantConfigurationResponse { option (ifdef) = "USE_VOICE_ASSISTANT"; repeated VoiceAssistantWakeWord available_wake_words = 1; - repeated uint32 active_wake_words = 2; + repeated string active_wake_words = 2; uint32 max_active_wake_words = 3; } @@ -1598,7 +1600,7 @@ message VoiceAssistantSetConfiguration { option (source) = SOURCE_CLIENT; option (ifdef) = "USE_VOICE_ASSISTANT"; - repeated uint32 active_wake_words = 1; + repeated string active_wake_words = 1; } // ==================== ALARM CONTROL PANEL ==================== diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 20e9a45314..7ea52e9a9e 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -1224,6 +1224,39 @@ void APIConnection::on_voice_assistant_announce_request(const VoiceAssistantAnno } } +VoiceAssistantConfigurationResponse APIConnection::voice_assistant_get_configuration( + const VoiceAssistantConfigurationRequest &msg) { + VoiceAssistantConfigurationResponse resp; + if (voice_assistant::global_voice_assistant != nullptr) { + if (voice_assistant::global_voice_assistant->get_api_connection() != this) { + return resp; + } + + auto &config = voice_assistant::global_voice_assistant->get_configuration(); + for (auto &wake_word : config.available_wake_words) { + VoiceAssistantWakeWord resp_wake_word; + resp_wake_word.id = wake_word.id; + resp_wake_word.wake_word = wake_word.wake_word; + for (const auto &lang : wake_word.trained_languages) { + resp_wake_word.trained_languages.push_back(lang); + } + resp.available_wake_words.push_back(std::move(resp_wake_word)); + } + resp.max_active_wake_words = config.max_active_wake_words; + } + return resp; +} + +void APIConnection::voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &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_set_configuration(msg.active_wake_words); + } +} + #endif #ifdef USE_ALARM_CONTROL_PANEL diff --git a/esphome/components/api/api_connection.h b/esphome/components/api/api_connection.h index e8d66a5e07..f176cf7c56 100644 --- a/esphome/components/api/api_connection.h +++ b/esphome/components/api/api_connection.h @@ -152,6 +152,9 @@ class APIConnection : public APIServerConnection { void on_voice_assistant_audio(const VoiceAssistantAudio &msg) override; void on_voice_assistant_timer_event_response(const VoiceAssistantTimerEventResponse &msg) override; void on_voice_assistant_announce_request(const VoiceAssistantAnnounceRequest &msg) override; + VoiceAssistantConfigurationResponse voice_assistant_get_configuration( + const VoiceAssistantConfigurationRequest &msg) override; + void voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &msg) override; #endif #ifdef USE_ALARM_CONTROL_PANEL diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index 791de29511..8df152881c 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -7124,18 +7124,12 @@ void VoiceAssistantAnnounceFinished::dump_to(std::string &out) const { out.append("}"); } #endif -bool VoiceAssistantWakeWord::decode_varint(uint32_t field_id, ProtoVarInt value) { - switch (field_id) { - case 1: { - this->id = value.as_uint32(); - return true; - } - default: - return false; - } -} bool VoiceAssistantWakeWord::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { + case 1: { + this->id = value.as_string(); + return true; + } case 2: { this->wake_word = value.as_string(); return true; @@ -7149,7 +7143,7 @@ bool VoiceAssistantWakeWord::decode_length(uint32_t field_id, ProtoLengthDelimit } } void VoiceAssistantWakeWord::encode(ProtoWriteBuffer buffer) const { - buffer.encode_uint32(1, this->id); + buffer.encode_string(1, this->id); buffer.encode_string(2, this->wake_word); for (auto &it : this->trained_languages) { buffer.encode_string(3, it, true); @@ -7160,8 +7154,7 @@ void VoiceAssistantWakeWord::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; out.append("VoiceAssistantWakeWord {\n"); out.append(" id: "); - sprintf(buffer, "%" PRIu32, this->id); - out.append(buffer); + out.append("'").append(this->id).append("'"); out.append("\n"); out.append(" wake_word: "); @@ -7184,10 +7177,6 @@ void VoiceAssistantConfigurationRequest::dump_to(std::string &out) const { #endif bool VoiceAssistantConfigurationResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { switch (field_id) { - case 2: { - this->active_wake_words.push_back(value.as_uint32()); - return true; - } case 3: { this->max_active_wake_words = value.as_uint32(); return true; @@ -7202,6 +7191,10 @@ bool VoiceAssistantConfigurationResponse::decode_length(uint32_t field_id, Proto this->available_wake_words.push_back(value.as_message()); return true; } + case 2: { + this->active_wake_words.push_back(value.as_string()); + return true; + } default: return false; } @@ -7211,7 +7204,7 @@ void VoiceAssistantConfigurationResponse::encode(ProtoWriteBuffer buffer) const buffer.encode_message(1, it, true); } for (auto &it : this->active_wake_words) { - buffer.encode_uint32(2, it, true); + buffer.encode_string(2, it, true); } buffer.encode_uint32(3, this->max_active_wake_words); } @@ -7227,8 +7220,7 @@ void VoiceAssistantConfigurationResponse::dump_to(std::string &out) const { for (const auto &it : this->active_wake_words) { out.append(" active_wake_words: "); - sprintf(buffer, "%" PRIu32, it); - out.append(buffer); + out.append("'").append(it).append("'"); out.append("\n"); } @@ -7239,10 +7231,10 @@ void VoiceAssistantConfigurationResponse::dump_to(std::string &out) const { out.append("}"); } #endif -bool VoiceAssistantSetConfiguration::decode_varint(uint32_t field_id, ProtoVarInt value) { +bool VoiceAssistantSetConfiguration::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { case 1: { - this->active_wake_words.push_back(value.as_uint32()); + this->active_wake_words.push_back(value.as_string()); return true; } default: @@ -7251,7 +7243,7 @@ bool VoiceAssistantSetConfiguration::decode_varint(uint32_t field_id, ProtoVarIn } void VoiceAssistantSetConfiguration::encode(ProtoWriteBuffer buffer) const { for (auto &it : this->active_wake_words) { - buffer.encode_uint32(1, it, true); + buffer.encode_string(1, it, true); } } #ifdef HAS_PROTO_MESSAGE_DUMP @@ -7260,8 +7252,7 @@ void VoiceAssistantSetConfiguration::dump_to(std::string &out) const { out.append("VoiceAssistantSetConfiguration {\n"); for (const auto &it : this->active_wake_words) { out.append(" active_wake_words: "); - sprintf(buffer, "%" PRIu32, it); - out.append(buffer); + out.append("'").append(it).append("'"); out.append("\n"); } out.append("}"); diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index 8631884f94..063c217bf7 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -1851,7 +1851,7 @@ class VoiceAssistantAnnounceFinished : public ProtoMessage { }; class VoiceAssistantWakeWord : public ProtoMessage { public: - uint32_t id{0}; + std::string id{}; std::string wake_word{}; std::vector trained_languages{}; void encode(ProtoWriteBuffer buffer) const override; @@ -1861,7 +1861,6 @@ class VoiceAssistantWakeWord : public ProtoMessage { protected: bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; - bool decode_varint(uint32_t field_id, ProtoVarInt value) override; }; class VoiceAssistantConfigurationRequest : public ProtoMessage { public: @@ -1875,7 +1874,7 @@ class VoiceAssistantConfigurationRequest : public ProtoMessage { class VoiceAssistantConfigurationResponse : public ProtoMessage { public: std::vector available_wake_words{}; - std::vector active_wake_words{}; + std::vector active_wake_words{}; uint32_t max_active_wake_words{0}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP @@ -1888,14 +1887,14 @@ class VoiceAssistantConfigurationResponse : public ProtoMessage { }; class VoiceAssistantSetConfiguration : public ProtoMessage { public: - std::vector active_wake_words{}; + std::vector active_wake_words{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif protected: - bool decode_varint(uint32_t field_id, ProtoVarInt value) override; + bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; }; class ListEntitiesAlarmControlPanelResponse : public ProtoMessage { public: diff --git a/esphome/components/api/api_pb2_service.cpp b/esphome/components/api/api_pb2_service.cpp index 454f20d50a..6e11d7169d 100644 --- a/esphome/components/api/api_pb2_service.cpp +++ b/esphome/components/api/api_pb2_service.cpp @@ -1681,6 +1681,35 @@ void APIServerConnection::on_subscribe_voice_assistant_request(const SubscribeVo this->subscribe_voice_assistant(msg); } #endif +#ifdef USE_VOICE_ASSISTANT +void APIServerConnection::on_voice_assistant_configuration_request(const VoiceAssistantConfigurationRequest &msg) { + if (!this->is_connection_setup()) { + this->on_no_setup_connection(); + return; + } + if (!this->is_authenticated()) { + this->on_unauthenticated_access(); + return; + } + VoiceAssistantConfigurationResponse ret = this->voice_assistant_get_configuration(msg); + if (!this->send_voice_assistant_configuration_response(ret)) { + this->on_fatal_error(); + } +} +#endif +#ifdef USE_VOICE_ASSISTANT +void APIServerConnection::on_voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &msg) { + if (!this->is_connection_setup()) { + this->on_no_setup_connection(); + return; + } + if (!this->is_authenticated()) { + this->on_unauthenticated_access(); + return; + } + this->voice_assistant_set_configuration(msg); +} +#endif #ifdef USE_ALARM_CONTROL_PANEL void APIServerConnection::on_alarm_control_panel_command_request(const AlarmControlPanelCommandRequest &msg) { if (!this->is_connection_setup()) { diff --git a/esphome/components/api/api_pb2_service.h b/esphome/components/api/api_pb2_service.h index e69954f7ab..51b94bf530 100644 --- a/esphome/components/api/api_pb2_service.h +++ b/esphome/components/api/api_pb2_service.h @@ -434,6 +434,13 @@ class APIServerConnection : public APIServerConnectionBase { #ifdef USE_VOICE_ASSISTANT virtual void subscribe_voice_assistant(const SubscribeVoiceAssistantRequest &msg) = 0; #endif +#ifdef USE_VOICE_ASSISTANT + virtual VoiceAssistantConfigurationResponse voice_assistant_get_configuration( + const VoiceAssistantConfigurationRequest &msg) = 0; +#endif +#ifdef USE_VOICE_ASSISTANT + virtual void voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &msg) = 0; +#endif #ifdef USE_ALARM_CONTROL_PANEL virtual void alarm_control_panel_command(const AlarmControlPanelCommandRequest &msg) = 0; #endif @@ -535,6 +542,12 @@ class APIServerConnection : public APIServerConnectionBase { #ifdef USE_VOICE_ASSISTANT void on_subscribe_voice_assistant_request(const SubscribeVoiceAssistantRequest &msg) override; #endif +#ifdef USE_VOICE_ASSISTANT + void on_voice_assistant_configuration_request(const VoiceAssistantConfigurationRequest &msg) override; +#endif +#ifdef USE_VOICE_ASSISTANT + void on_voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &msg) override; +#endif #ifdef USE_ALARM_CONTROL_PANEL void on_alarm_control_panel_command_request(const AlarmControlPanelCommandRequest &msg) override; #endif diff --git a/esphome/components/voice_assistant/voice_assistant.h b/esphome/components/voice_assistant/voice_assistant.h index b0a172332f..56ada0e75a 100644 --- a/esphome/components/voice_assistant/voice_assistant.h +++ b/esphome/components/voice_assistant/voice_assistant.h @@ -77,6 +77,18 @@ struct Timer { } }; +struct WakeWord { + std::string id; + std::string wake_word; + std::vector trained_languages; +}; + +struct Configuration { + std::vector available_wake_words; + std::vector active_wake_words; + uint32_t max_active_wake_words; +}; + class VoiceAssistant : public Component { public: void setup() override; @@ -133,6 +145,8 @@ class VoiceAssistant : public Component { void on_audio(const api::VoiceAssistantAudio &msg); void on_timer_event(const api::VoiceAssistantTimerEventResponse &msg); void on_announce(const api::VoiceAssistantAnnounceRequest &msg); + void on_set_configuration(const std::vector &active_wake_words){}; + const Configuration &get_configuration() { return this->config_; }; bool is_running() const { return this->state_ != State::IDLE; } void set_continuous(bool continuous) { this->continuous_ = continuous; } @@ -279,6 +293,8 @@ class VoiceAssistant : public Component { AudioMode audio_mode_{AUDIO_MODE_UDP}; bool udp_socket_running_{false}; bool start_udp_socket_(); + + Configuration config_{}; }; template class StartAction : public Action, public Parented { From f87d9be60dc74208ce18cdcb1dd9cf87c43fe7aa Mon Sep 17 00:00:00 2001 From: Michael Hansen Date: Sun, 15 Sep 2024 20:40:45 -0500 Subject: [PATCH 17/20] Add voice assistant configuration messages (#7445) --- esphome/components/api/api.proto | 30 +++++ esphome/components/api/api_pb2.cpp | 143 +++++++++++++++++++++ esphome/components/api/api_pb2.h | 48 +++++++ esphome/components/api/api_pb2_service.cpp | 35 +++++ esphome/components/api/api_pb2_service.h | 9 ++ 5 files changed, 265 insertions(+) diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index a966643d30..92fb57b711 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -1571,6 +1571,36 @@ message VoiceAssistantAnnounceFinished { bool success = 1; } +message VoiceAssistantWakeWord { + uint32 id = 1; + string wake_word = 2; + repeated string trained_languages = 3; +} + +message VoiceAssistantConfigurationRequest { + option (id) = 121; + option (source) = SOURCE_CLIENT; + option (ifdef) = "USE_VOICE_ASSISTANT"; +} + +message VoiceAssistantConfigurationResponse { + option (id) = 122; + option (source) = SOURCE_SERVER; + option (ifdef) = "USE_VOICE_ASSISTANT"; + + repeated VoiceAssistantWakeWord available_wake_words = 1; + repeated uint32 active_wake_words = 2; + uint32 max_active_wake_words = 3; +} + +message VoiceAssistantSetConfiguration { + option (id) = 123; + option (source) = SOURCE_CLIENT; + option (ifdef) = "USE_VOICE_ASSISTANT"; + + repeated uint32 active_wake_words = 1; +} + // ==================== ALARM CONTROL PANEL ==================== enum AlarmControlPanelState { ALARM_STATE_DISARMED = 0; diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index 955f17612a..791de29511 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -7124,6 +7124,149 @@ void VoiceAssistantAnnounceFinished::dump_to(std::string &out) const { out.append("}"); } #endif +bool VoiceAssistantWakeWord::decode_varint(uint32_t field_id, ProtoVarInt value) { + switch (field_id) { + case 1: { + this->id = value.as_uint32(); + return true; + } + default: + return false; + } +} +bool VoiceAssistantWakeWord::decode_length(uint32_t field_id, ProtoLengthDelimited value) { + switch (field_id) { + case 2: { + this->wake_word = value.as_string(); + return true; + } + case 3: { + this->trained_languages.push_back(value.as_string()); + return true; + } + default: + return false; + } +} +void VoiceAssistantWakeWord::encode(ProtoWriteBuffer buffer) const { + buffer.encode_uint32(1, this->id); + buffer.encode_string(2, this->wake_word); + for (auto &it : this->trained_languages) { + buffer.encode_string(3, it, true); + } +} +#ifdef HAS_PROTO_MESSAGE_DUMP +void VoiceAssistantWakeWord::dump_to(std::string &out) const { + __attribute__((unused)) char buffer[64]; + out.append("VoiceAssistantWakeWord {\n"); + out.append(" id: "); + sprintf(buffer, "%" PRIu32, this->id); + out.append(buffer); + out.append("\n"); + + out.append(" wake_word: "); + out.append("'").append(this->wake_word).append("'"); + out.append("\n"); + + for (const auto &it : this->trained_languages) { + out.append(" trained_languages: "); + out.append("'").append(it).append("'"); + out.append("\n"); + } + out.append("}"); +} +#endif +void VoiceAssistantConfigurationRequest::encode(ProtoWriteBuffer buffer) const {} +#ifdef HAS_PROTO_MESSAGE_DUMP +void VoiceAssistantConfigurationRequest::dump_to(std::string &out) const { + out.append("VoiceAssistantConfigurationRequest {}"); +} +#endif +bool VoiceAssistantConfigurationResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { + switch (field_id) { + case 2: { + this->active_wake_words.push_back(value.as_uint32()); + return true; + } + case 3: { + this->max_active_wake_words = value.as_uint32(); + return true; + } + default: + return false; + } +} +bool VoiceAssistantConfigurationResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { + switch (field_id) { + case 1: { + this->available_wake_words.push_back(value.as_message()); + return true; + } + default: + return false; + } +} +void VoiceAssistantConfigurationResponse::encode(ProtoWriteBuffer buffer) const { + for (auto &it : this->available_wake_words) { + buffer.encode_message(1, it, true); + } + for (auto &it : this->active_wake_words) { + buffer.encode_uint32(2, it, true); + } + buffer.encode_uint32(3, this->max_active_wake_words); +} +#ifdef HAS_PROTO_MESSAGE_DUMP +void VoiceAssistantConfigurationResponse::dump_to(std::string &out) const { + __attribute__((unused)) char buffer[64]; + out.append("VoiceAssistantConfigurationResponse {\n"); + for (const auto &it : this->available_wake_words) { + out.append(" available_wake_words: "); + it.dump_to(out); + out.append("\n"); + } + + for (const auto &it : this->active_wake_words) { + out.append(" active_wake_words: "); + sprintf(buffer, "%" PRIu32, it); + out.append(buffer); + out.append("\n"); + } + + out.append(" max_active_wake_words: "); + sprintf(buffer, "%" PRIu32, this->max_active_wake_words); + out.append(buffer); + out.append("\n"); + out.append("}"); +} +#endif +bool VoiceAssistantSetConfiguration::decode_varint(uint32_t field_id, ProtoVarInt value) { + switch (field_id) { + case 1: { + this->active_wake_words.push_back(value.as_uint32()); + return true; + } + default: + return false; + } +} +void VoiceAssistantSetConfiguration::encode(ProtoWriteBuffer buffer) const { + for (auto &it : this->active_wake_words) { + buffer.encode_uint32(1, it, true); + } +} +#ifdef HAS_PROTO_MESSAGE_DUMP +void VoiceAssistantSetConfiguration::dump_to(std::string &out) const { + __attribute__((unused)) char buffer[64]; + out.append("VoiceAssistantSetConfiguration {\n"); + for (const auto &it : this->active_wake_words) { + out.append(" active_wake_words: "); + sprintf(buffer, "%" PRIu32, it); + out.append(buffer); + out.append("\n"); + } + out.append("}"); +} +#endif bool ListEntitiesAlarmControlPanelResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { switch (field_id) { case 6: { diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index 1ce6482b09..8631884f94 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -1849,6 +1849,54 @@ class VoiceAssistantAnnounceFinished : public ProtoMessage { protected: bool decode_varint(uint32_t field_id, ProtoVarInt value) override; }; +class VoiceAssistantWakeWord : public ProtoMessage { + public: + uint32_t id{0}; + std::string wake_word{}; + std::vector trained_languages{}; + void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP + void dump_to(std::string &out) const override; +#endif + + protected: + bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; + bool decode_varint(uint32_t field_id, ProtoVarInt value) override; +}; +class VoiceAssistantConfigurationRequest : public ProtoMessage { + public: + void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP + void dump_to(std::string &out) const override; +#endif + + protected: +}; +class VoiceAssistantConfigurationResponse : public ProtoMessage { + public: + std::vector available_wake_words{}; + std::vector active_wake_words{}; + uint32_t max_active_wake_words{0}; + void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP + void dump_to(std::string &out) const override; +#endif + + protected: + bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; + bool decode_varint(uint32_t field_id, ProtoVarInt value) override; +}; +class VoiceAssistantSetConfiguration : public ProtoMessage { + public: + std::vector active_wake_words{}; + void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP + void dump_to(std::string &out) const override; +#endif + + protected: + bool decode_varint(uint32_t field_id, ProtoVarInt value) override; +}; class ListEntitiesAlarmControlPanelResponse : public ProtoMessage { public: std::string object_id{}; diff --git a/esphome/components/api/api_pb2_service.cpp b/esphome/components/api/api_pb2_service.cpp index faa977389a..454f20d50a 100644 --- a/esphome/components/api/api_pb2_service.cpp +++ b/esphome/components/api/api_pb2_service.cpp @@ -496,6 +496,19 @@ bool APIServerConnectionBase::send_voice_assistant_announce_finished(const Voice return this->send_message_(msg, 120); } #endif +#ifdef USE_VOICE_ASSISTANT +#endif +#ifdef USE_VOICE_ASSISTANT +bool APIServerConnectionBase::send_voice_assistant_configuration_response( + const VoiceAssistantConfigurationResponse &msg) { +#ifdef HAS_PROTO_MESSAGE_DUMP + ESP_LOGVV(TAG, "send_voice_assistant_configuration_response: %s", msg.dump().c_str()); +#endif + return this->send_message_(msg, 122); +} +#endif +#ifdef USE_VOICE_ASSISTANT +#endif #ifdef USE_ALARM_CONTROL_PANEL bool APIServerConnectionBase::send_list_entities_alarm_control_panel_response( const ListEntitiesAlarmControlPanelResponse &msg) { @@ -1156,6 +1169,28 @@ bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, ESP_LOGVV(TAG, "on_voice_assistant_announce_request: %s", msg.dump().c_str()); #endif this->on_voice_assistant_announce_request(msg); +#endif + break; + } + case 121: { +#ifdef USE_VOICE_ASSISTANT + VoiceAssistantConfigurationRequest msg; + msg.decode(msg_data, msg_size); +#ifdef HAS_PROTO_MESSAGE_DUMP + ESP_LOGVV(TAG, "on_voice_assistant_configuration_request: %s", msg.dump().c_str()); +#endif + this->on_voice_assistant_configuration_request(msg); +#endif + break; + } + case 123: { +#ifdef USE_VOICE_ASSISTANT + VoiceAssistantSetConfiguration msg; + msg.decode(msg_data, msg_size); +#ifdef HAS_PROTO_MESSAGE_DUMP + ESP_LOGVV(TAG, "on_voice_assistant_set_configuration: %s", msg.dump().c_str()); +#endif + this->on_voice_assistant_set_configuration(msg); #endif break; } diff --git a/esphome/components/api/api_pb2_service.h b/esphome/components/api/api_pb2_service.h index f3803ad628..e69954f7ab 100644 --- a/esphome/components/api/api_pb2_service.h +++ b/esphome/components/api/api_pb2_service.h @@ -253,6 +253,15 @@ class APIServerConnectionBase : public ProtoService { #ifdef USE_VOICE_ASSISTANT bool send_voice_assistant_announce_finished(const VoiceAssistantAnnounceFinished &msg); #endif +#ifdef USE_VOICE_ASSISTANT + virtual void on_voice_assistant_configuration_request(const VoiceAssistantConfigurationRequest &value){}; +#endif +#ifdef USE_VOICE_ASSISTANT + bool send_voice_assistant_configuration_response(const VoiceAssistantConfigurationResponse &msg); +#endif +#ifdef USE_VOICE_ASSISTANT + virtual void on_voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &value){}; +#endif #ifdef USE_ALARM_CONTROL_PANEL bool send_list_entities_alarm_control_panel_response(const ListEntitiesAlarmControlPanelResponse &msg); #endif From 749f6643308109cae5387bf7e5114fa16a0d78b0 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 16 Sep 2024 18:03:51 +1200 Subject: [PATCH 18/20] Dont replace project name spaces with underlines (#7455) --- esphome/core/config.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/esphome/core/config.py b/esphome/core/config.py index 739a8a1aea..f4253bee87 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -100,9 +100,6 @@ def valid_include(value): def valid_project_name(value: str): if value.count(".") != 1: raise cv.Invalid("project name needs to have a namespace") - - value = value.replace(" ", "_") - return value From 571c0eb82703e29de70f5c82ed018b4dde2d9dc3 Mon Sep 17 00:00:00 2001 From: Michael Hansen Date: Tue, 17 Sep 2024 18:38:39 -0500 Subject: [PATCH 19/20] Add voice assistant methods for configuration (#7459) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/api/api.proto | 8 ++-- esphome/components/api/api_connection.cpp | 33 +++++++++++++++ esphome/components/api/api_connection.h | 3 ++ esphome/components/api/api_pb2.cpp | 41 ++++++++----------- esphome/components/api/api_pb2.h | 9 ++-- esphome/components/api/api_pb2_service.cpp | 29 +++++++++++++ esphome/components/api/api_pb2_service.h | 13 ++++++ .../voice_assistant/voice_assistant.h | 16 ++++++++ 8 files changed, 119 insertions(+), 33 deletions(-) diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index 92fb57b711..684540ffa6 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -62,6 +62,8 @@ service APIConnection { rpc unsubscribe_bluetooth_le_advertisements(UnsubscribeBluetoothLEAdvertisementsRequest) returns (void) {} rpc subscribe_voice_assistant(SubscribeVoiceAssistantRequest) returns (void) {} + rpc voice_assistant_get_configuration(VoiceAssistantConfigurationRequest) returns (VoiceAssistantConfigurationResponse) {} + rpc voice_assistant_set_configuration(VoiceAssistantSetConfiguration) returns (void) {} rpc alarm_control_panel_command (AlarmControlPanelCommandRequest) returns (void) {} } @@ -1572,7 +1574,7 @@ message VoiceAssistantAnnounceFinished { } message VoiceAssistantWakeWord { - uint32 id = 1; + string id = 1; string wake_word = 2; repeated string trained_languages = 3; } @@ -1589,7 +1591,7 @@ message VoiceAssistantConfigurationResponse { option (ifdef) = "USE_VOICE_ASSISTANT"; repeated VoiceAssistantWakeWord available_wake_words = 1; - repeated uint32 active_wake_words = 2; + repeated string active_wake_words = 2; uint32 max_active_wake_words = 3; } @@ -1598,7 +1600,7 @@ message VoiceAssistantSetConfiguration { option (source) = SOURCE_CLIENT; option (ifdef) = "USE_VOICE_ASSISTANT"; - repeated uint32 active_wake_words = 1; + repeated string active_wake_words = 1; } // ==================== ALARM CONTROL PANEL ==================== diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 20e9a45314..7ea52e9a9e 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -1224,6 +1224,39 @@ void APIConnection::on_voice_assistant_announce_request(const VoiceAssistantAnno } } +VoiceAssistantConfigurationResponse APIConnection::voice_assistant_get_configuration( + const VoiceAssistantConfigurationRequest &msg) { + VoiceAssistantConfigurationResponse resp; + if (voice_assistant::global_voice_assistant != nullptr) { + if (voice_assistant::global_voice_assistant->get_api_connection() != this) { + return resp; + } + + auto &config = voice_assistant::global_voice_assistant->get_configuration(); + for (auto &wake_word : config.available_wake_words) { + VoiceAssistantWakeWord resp_wake_word; + resp_wake_word.id = wake_word.id; + resp_wake_word.wake_word = wake_word.wake_word; + for (const auto &lang : wake_word.trained_languages) { + resp_wake_word.trained_languages.push_back(lang); + } + resp.available_wake_words.push_back(std::move(resp_wake_word)); + } + resp.max_active_wake_words = config.max_active_wake_words; + } + return resp; +} + +void APIConnection::voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &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_set_configuration(msg.active_wake_words); + } +} + #endif #ifdef USE_ALARM_CONTROL_PANEL diff --git a/esphome/components/api/api_connection.h b/esphome/components/api/api_connection.h index e8d66a5e07..f176cf7c56 100644 --- a/esphome/components/api/api_connection.h +++ b/esphome/components/api/api_connection.h @@ -152,6 +152,9 @@ class APIConnection : public APIServerConnection { void on_voice_assistant_audio(const VoiceAssistantAudio &msg) override; void on_voice_assistant_timer_event_response(const VoiceAssistantTimerEventResponse &msg) override; void on_voice_assistant_announce_request(const VoiceAssistantAnnounceRequest &msg) override; + VoiceAssistantConfigurationResponse voice_assistant_get_configuration( + const VoiceAssistantConfigurationRequest &msg) override; + void voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &msg) override; #endif #ifdef USE_ALARM_CONTROL_PANEL diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index 791de29511..8df152881c 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -7124,18 +7124,12 @@ void VoiceAssistantAnnounceFinished::dump_to(std::string &out) const { out.append("}"); } #endif -bool VoiceAssistantWakeWord::decode_varint(uint32_t field_id, ProtoVarInt value) { - switch (field_id) { - case 1: { - this->id = value.as_uint32(); - return true; - } - default: - return false; - } -} bool VoiceAssistantWakeWord::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { + case 1: { + this->id = value.as_string(); + return true; + } case 2: { this->wake_word = value.as_string(); return true; @@ -7149,7 +7143,7 @@ bool VoiceAssistantWakeWord::decode_length(uint32_t field_id, ProtoLengthDelimit } } void VoiceAssistantWakeWord::encode(ProtoWriteBuffer buffer) const { - buffer.encode_uint32(1, this->id); + buffer.encode_string(1, this->id); buffer.encode_string(2, this->wake_word); for (auto &it : this->trained_languages) { buffer.encode_string(3, it, true); @@ -7160,8 +7154,7 @@ void VoiceAssistantWakeWord::dump_to(std::string &out) const { __attribute__((unused)) char buffer[64]; out.append("VoiceAssistantWakeWord {\n"); out.append(" id: "); - sprintf(buffer, "%" PRIu32, this->id); - out.append(buffer); + out.append("'").append(this->id).append("'"); out.append("\n"); out.append(" wake_word: "); @@ -7184,10 +7177,6 @@ void VoiceAssistantConfigurationRequest::dump_to(std::string &out) const { #endif bool VoiceAssistantConfigurationResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { switch (field_id) { - case 2: { - this->active_wake_words.push_back(value.as_uint32()); - return true; - } case 3: { this->max_active_wake_words = value.as_uint32(); return true; @@ -7202,6 +7191,10 @@ bool VoiceAssistantConfigurationResponse::decode_length(uint32_t field_id, Proto this->available_wake_words.push_back(value.as_message()); return true; } + case 2: { + this->active_wake_words.push_back(value.as_string()); + return true; + } default: return false; } @@ -7211,7 +7204,7 @@ void VoiceAssistantConfigurationResponse::encode(ProtoWriteBuffer buffer) const buffer.encode_message(1, it, true); } for (auto &it : this->active_wake_words) { - buffer.encode_uint32(2, it, true); + buffer.encode_string(2, it, true); } buffer.encode_uint32(3, this->max_active_wake_words); } @@ -7227,8 +7220,7 @@ void VoiceAssistantConfigurationResponse::dump_to(std::string &out) const { for (const auto &it : this->active_wake_words) { out.append(" active_wake_words: "); - sprintf(buffer, "%" PRIu32, it); - out.append(buffer); + out.append("'").append(it).append("'"); out.append("\n"); } @@ -7239,10 +7231,10 @@ void VoiceAssistantConfigurationResponse::dump_to(std::string &out) const { out.append("}"); } #endif -bool VoiceAssistantSetConfiguration::decode_varint(uint32_t field_id, ProtoVarInt value) { +bool VoiceAssistantSetConfiguration::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { case 1: { - this->active_wake_words.push_back(value.as_uint32()); + this->active_wake_words.push_back(value.as_string()); return true; } default: @@ -7251,7 +7243,7 @@ bool VoiceAssistantSetConfiguration::decode_varint(uint32_t field_id, ProtoVarIn } void VoiceAssistantSetConfiguration::encode(ProtoWriteBuffer buffer) const { for (auto &it : this->active_wake_words) { - buffer.encode_uint32(1, it, true); + buffer.encode_string(1, it, true); } } #ifdef HAS_PROTO_MESSAGE_DUMP @@ -7260,8 +7252,7 @@ void VoiceAssistantSetConfiguration::dump_to(std::string &out) const { out.append("VoiceAssistantSetConfiguration {\n"); for (const auto &it : this->active_wake_words) { out.append(" active_wake_words: "); - sprintf(buffer, "%" PRIu32, it); - out.append(buffer); + out.append("'").append(it).append("'"); out.append("\n"); } out.append("}"); diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index 8631884f94..063c217bf7 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -1851,7 +1851,7 @@ class VoiceAssistantAnnounceFinished : public ProtoMessage { }; class VoiceAssistantWakeWord : public ProtoMessage { public: - uint32_t id{0}; + std::string id{}; std::string wake_word{}; std::vector trained_languages{}; void encode(ProtoWriteBuffer buffer) const override; @@ -1861,7 +1861,6 @@ class VoiceAssistantWakeWord : public ProtoMessage { protected: bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; - bool decode_varint(uint32_t field_id, ProtoVarInt value) override; }; class VoiceAssistantConfigurationRequest : public ProtoMessage { public: @@ -1875,7 +1874,7 @@ class VoiceAssistantConfigurationRequest : public ProtoMessage { class VoiceAssistantConfigurationResponse : public ProtoMessage { public: std::vector available_wake_words{}; - std::vector active_wake_words{}; + std::vector active_wake_words{}; uint32_t max_active_wake_words{0}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP @@ -1888,14 +1887,14 @@ class VoiceAssistantConfigurationResponse : public ProtoMessage { }; class VoiceAssistantSetConfiguration : public ProtoMessage { public: - std::vector active_wake_words{}; + std::vector active_wake_words{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; #endif protected: - bool decode_varint(uint32_t field_id, ProtoVarInt value) override; + bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; }; class ListEntitiesAlarmControlPanelResponse : public ProtoMessage { public: diff --git a/esphome/components/api/api_pb2_service.cpp b/esphome/components/api/api_pb2_service.cpp index 454f20d50a..6e11d7169d 100644 --- a/esphome/components/api/api_pb2_service.cpp +++ b/esphome/components/api/api_pb2_service.cpp @@ -1681,6 +1681,35 @@ void APIServerConnection::on_subscribe_voice_assistant_request(const SubscribeVo this->subscribe_voice_assistant(msg); } #endif +#ifdef USE_VOICE_ASSISTANT +void APIServerConnection::on_voice_assistant_configuration_request(const VoiceAssistantConfigurationRequest &msg) { + if (!this->is_connection_setup()) { + this->on_no_setup_connection(); + return; + } + if (!this->is_authenticated()) { + this->on_unauthenticated_access(); + return; + } + VoiceAssistantConfigurationResponse ret = this->voice_assistant_get_configuration(msg); + if (!this->send_voice_assistant_configuration_response(ret)) { + this->on_fatal_error(); + } +} +#endif +#ifdef USE_VOICE_ASSISTANT +void APIServerConnection::on_voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &msg) { + if (!this->is_connection_setup()) { + this->on_no_setup_connection(); + return; + } + if (!this->is_authenticated()) { + this->on_unauthenticated_access(); + return; + } + this->voice_assistant_set_configuration(msg); +} +#endif #ifdef USE_ALARM_CONTROL_PANEL void APIServerConnection::on_alarm_control_panel_command_request(const AlarmControlPanelCommandRequest &msg) { if (!this->is_connection_setup()) { diff --git a/esphome/components/api/api_pb2_service.h b/esphome/components/api/api_pb2_service.h index e69954f7ab..51b94bf530 100644 --- a/esphome/components/api/api_pb2_service.h +++ b/esphome/components/api/api_pb2_service.h @@ -434,6 +434,13 @@ class APIServerConnection : public APIServerConnectionBase { #ifdef USE_VOICE_ASSISTANT virtual void subscribe_voice_assistant(const SubscribeVoiceAssistantRequest &msg) = 0; #endif +#ifdef USE_VOICE_ASSISTANT + virtual VoiceAssistantConfigurationResponse voice_assistant_get_configuration( + const VoiceAssistantConfigurationRequest &msg) = 0; +#endif +#ifdef USE_VOICE_ASSISTANT + virtual void voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &msg) = 0; +#endif #ifdef USE_ALARM_CONTROL_PANEL virtual void alarm_control_panel_command(const AlarmControlPanelCommandRequest &msg) = 0; #endif @@ -535,6 +542,12 @@ class APIServerConnection : public APIServerConnectionBase { #ifdef USE_VOICE_ASSISTANT void on_subscribe_voice_assistant_request(const SubscribeVoiceAssistantRequest &msg) override; #endif +#ifdef USE_VOICE_ASSISTANT + void on_voice_assistant_configuration_request(const VoiceAssistantConfigurationRequest &msg) override; +#endif +#ifdef USE_VOICE_ASSISTANT + void on_voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &msg) override; +#endif #ifdef USE_ALARM_CONTROL_PANEL void on_alarm_control_panel_command_request(const AlarmControlPanelCommandRequest &msg) override; #endif diff --git a/esphome/components/voice_assistant/voice_assistant.h b/esphome/components/voice_assistant/voice_assistant.h index b0a172332f..56ada0e75a 100644 --- a/esphome/components/voice_assistant/voice_assistant.h +++ b/esphome/components/voice_assistant/voice_assistant.h @@ -77,6 +77,18 @@ struct Timer { } }; +struct WakeWord { + std::string id; + std::string wake_word; + std::vector trained_languages; +}; + +struct Configuration { + std::vector available_wake_words; + std::vector active_wake_words; + uint32_t max_active_wake_words; +}; + class VoiceAssistant : public Component { public: void setup() override; @@ -133,6 +145,8 @@ class VoiceAssistant : public Component { void on_audio(const api::VoiceAssistantAudio &msg); void on_timer_event(const api::VoiceAssistantTimerEventResponse &msg); void on_announce(const api::VoiceAssistantAnnounceRequest &msg); + void on_set_configuration(const std::vector &active_wake_words){}; + const Configuration &get_configuration() { return this->config_; }; bool is_running() const { return this->state_ != State::IDLE; } void set_continuous(bool continuous) { this->continuous_ = continuous; } @@ -279,6 +293,8 @@ class VoiceAssistant : public Component { AudioMode audio_mode_{AUDIO_MODE_UDP}; bool udp_socket_running_{false}; bool start_udp_socket_(); + + Configuration config_{}; }; template class StartAction : public Action, public Parented { From a930b377b0cc7d6c2b60611fba7361cbbd8040e9 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 18 Sep 2024 12:57:27 +1200 Subject: [PATCH 20/20] Bump version to 2024.9.0b4 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 24deba1f15..929a6bea84 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2024.9.0b3" +__version__ = "2024.9.0b4" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" VALID_SUBSTITUTIONS_CHARACTERS = (