mirror of
https://github.com/esphome/esphome.git
synced 2025-11-11 20:35:51 +00:00
Compare commits
14 Commits
2025.9.1
...
release-te
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
93e18e850e | ||
|
|
59c0ffb98b | ||
|
|
29658b79bc | ||
|
|
158a59aa83 | ||
|
|
c95180504a | ||
|
|
a96c013eb1 | ||
|
|
58166b3e71 | ||
|
|
345fc0b6ca | ||
|
|
127058e700 | ||
|
|
57f7a709cf | ||
|
|
f2a9e9265e | ||
|
|
1ecd26adb5 | ||
|
|
6d9fc672d5 | ||
|
|
b9361b0868 |
2
Doxyfile
2
Doxyfile
@@ -48,7 +48,7 @@ PROJECT_NAME = ESPHome
|
|||||||
# could be handy for archiving the generated documentation or if some version
|
# could be handy for archiving the generated documentation or if some version
|
||||||
# control system is used.
|
# control system is used.
|
||||||
|
|
||||||
PROJECT_NUMBER = 2025.9.1
|
PROJECT_NUMBER = 2025.9.3
|
||||||
|
|
||||||
# Using the PROJECT_BRIEF tag one can provide an optional one line description
|
# Using the PROJECT_BRIEF tag one can provide an optional one line description
|
||||||
# for a project that appears at the top of each page and should give viewer a
|
# for a project that appears at the top of each page and should give viewer a
|
||||||
|
|||||||
@@ -193,6 +193,7 @@ async def to_code(config):
|
|||||||
if key := encryption_config.get(CONF_KEY):
|
if key := encryption_config.get(CONF_KEY):
|
||||||
decoded = base64.b64decode(key)
|
decoded = base64.b64decode(key)
|
||||||
cg.add(var.set_noise_psk(list(decoded)))
|
cg.add(var.set_noise_psk(list(decoded)))
|
||||||
|
cg.add_define("USE_API_NOISE_PSK_FROM_YAML")
|
||||||
else:
|
else:
|
||||||
# No key provided, but encryption desired
|
# No key provided, but encryption desired
|
||||||
# This will allow a plaintext client to provide a noise key,
|
# This will allow a plaintext client to provide a noise key,
|
||||||
|
|||||||
@@ -37,12 +37,14 @@ void APIServer::setup() {
|
|||||||
|
|
||||||
this->noise_pref_ = global_preferences->make_preference<SavedNoisePsk>(hash, true);
|
this->noise_pref_ = global_preferences->make_preference<SavedNoisePsk>(hash, true);
|
||||||
|
|
||||||
|
#ifndef USE_API_NOISE_PSK_FROM_YAML
|
||||||
|
// Only load saved PSK if not set from YAML
|
||||||
SavedNoisePsk noise_pref_saved{};
|
SavedNoisePsk noise_pref_saved{};
|
||||||
if (this->noise_pref_.load(&noise_pref_saved)) {
|
if (this->noise_pref_.load(&noise_pref_saved)) {
|
||||||
ESP_LOGD(TAG, "Loaded saved Noise PSK");
|
ESP_LOGD(TAG, "Loaded saved Noise PSK");
|
||||||
|
|
||||||
this->set_noise_psk(noise_pref_saved.psk);
|
this->set_noise_psk(noise_pref_saved.psk);
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Schedule reboot if no clients connect within timeout
|
// Schedule reboot if no clients connect within timeout
|
||||||
@@ -409,6 +411,12 @@ void APIServer::set_reboot_timeout(uint32_t reboot_timeout) { this->reboot_timeo
|
|||||||
|
|
||||||
#ifdef USE_API_NOISE
|
#ifdef USE_API_NOISE
|
||||||
bool APIServer::save_noise_psk(psk_t psk, bool make_active) {
|
bool APIServer::save_noise_psk(psk_t psk, bool make_active) {
|
||||||
|
#ifdef USE_API_NOISE_PSK_FROM_YAML
|
||||||
|
// When PSK is set from YAML, this function should never be called
|
||||||
|
// but if it is, reject the change
|
||||||
|
ESP_LOGW(TAG, "Key set in YAML");
|
||||||
|
return false;
|
||||||
|
#else
|
||||||
auto &old_psk = this->noise_ctx_->get_psk();
|
auto &old_psk = this->noise_ctx_->get_psk();
|
||||||
if (std::equal(old_psk.begin(), old_psk.end(), psk.begin())) {
|
if (std::equal(old_psk.begin(), old_psk.end(), psk.begin())) {
|
||||||
ESP_LOGW(TAG, "New PSK matches old");
|
ESP_LOGW(TAG, "New PSK matches old");
|
||||||
@@ -437,6 +445,7 @@ bool APIServer::save_noise_psk(psk_t psk, bool make_active) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ using namespace bytebuffer;
|
|||||||
|
|
||||||
static const char *const TAG = "esp32_improv.component";
|
static const char *const TAG = "esp32_improv.component";
|
||||||
static const char *const ESPHOME_MY_LINK = "https://my.home-assistant.io/redirect/config_flow_start?domain=esphome";
|
static const char *const ESPHOME_MY_LINK = "https://my.home-assistant.io/redirect/config_flow_start?domain=esphome";
|
||||||
|
static constexpr uint16_t STOP_ADVERTISING_DELAY =
|
||||||
|
10000; // Delay (ms) before stopping service to allow BLE clients to read the final state
|
||||||
|
|
||||||
ESP32ImprovComponent::ESP32ImprovComponent() { global_improv_component = this; }
|
ESP32ImprovComponent::ESP32ImprovComponent() { global_improv_component = this; }
|
||||||
|
|
||||||
@@ -31,6 +33,9 @@ void ESP32ImprovComponent::setup() {
|
|||||||
#endif
|
#endif
|
||||||
global_ble_server->on(BLEServerEvt::EmptyEvt::ON_DISCONNECT,
|
global_ble_server->on(BLEServerEvt::EmptyEvt::ON_DISCONNECT,
|
||||||
[this](uint16_t conn_id) { this->set_error_(improv::ERROR_NONE); });
|
[this](uint16_t conn_id) { this->set_error_(improv::ERROR_NONE); });
|
||||||
|
|
||||||
|
// Start with loop disabled - will be enabled by start() when needed
|
||||||
|
this->disable_loop();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ESP32ImprovComponent::setup_characteristics() {
|
void ESP32ImprovComponent::setup_characteristics() {
|
||||||
@@ -190,6 +195,25 @@ void ESP32ImprovComponent::set_status_indicator_state_(bool state) {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_DEBUG
|
||||||
|
const char *ESP32ImprovComponent::state_to_string_(improv::State state) {
|
||||||
|
switch (state) {
|
||||||
|
case improv::STATE_STOPPED:
|
||||||
|
return "STOPPED";
|
||||||
|
case improv::STATE_AWAITING_AUTHORIZATION:
|
||||||
|
return "AWAITING_AUTHORIZATION";
|
||||||
|
case improv::STATE_AUTHORIZED:
|
||||||
|
return "AUTHORIZED";
|
||||||
|
case improv::STATE_PROVISIONING:
|
||||||
|
return "PROVISIONING";
|
||||||
|
case improv::STATE_PROVISIONED:
|
||||||
|
return "PROVISIONED";
|
||||||
|
default:
|
||||||
|
return "UNKNOWN";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
bool ESP32ImprovComponent::check_identify_() {
|
bool ESP32ImprovComponent::check_identify_() {
|
||||||
uint32_t now = millis();
|
uint32_t now = millis();
|
||||||
|
|
||||||
@@ -203,31 +227,42 @@ bool ESP32ImprovComponent::check_identify_() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ESP32ImprovComponent::set_state_(improv::State state) {
|
void ESP32ImprovComponent::set_state_(improv::State state) {
|
||||||
ESP_LOGV(TAG, "Setting state: %d", state);
|
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_DEBUG
|
||||||
|
if (this->state_ != state) {
|
||||||
|
ESP_LOGD(TAG, "State transition: %s (0x%02X) -> %s (0x%02X)", this->state_to_string_(this->state_), this->state_,
|
||||||
|
this->state_to_string_(state), state);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
this->state_ = state;
|
this->state_ = state;
|
||||||
if (this->status_->get_value().empty() || this->status_->get_value()[0] != state) {
|
if (this->status_ != nullptr && (this->status_->get_value().empty() || this->status_->get_value()[0] != state)) {
|
||||||
this->status_->set_value(ByteBuffer::wrap(static_cast<uint8_t>(state)));
|
this->status_->set_value(ByteBuffer::wrap(static_cast<uint8_t>(state)));
|
||||||
if (state != improv::STATE_STOPPED)
|
if (state != improv::STATE_STOPPED)
|
||||||
this->status_->notify();
|
this->status_->notify();
|
||||||
}
|
}
|
||||||
std::vector<uint8_t> service_data(8, 0);
|
// Only advertise valid Improv states (0x01-0x04).
|
||||||
service_data[0] = 0x77; // PR
|
// STATE_STOPPED (0x00) is internal only and not part of the Improv spec.
|
||||||
service_data[1] = 0x46; // IM
|
// Advertising 0x00 causes undefined behavior in some clients and makes them
|
||||||
service_data[2] = static_cast<uint8_t>(state);
|
// repeatedly connect trying to determine the actual state.
|
||||||
|
if (state != improv::STATE_STOPPED) {
|
||||||
|
std::vector<uint8_t> service_data(8, 0);
|
||||||
|
service_data[0] = 0x77; // PR
|
||||||
|
service_data[1] = 0x46; // IM
|
||||||
|
service_data[2] = static_cast<uint8_t>(state);
|
||||||
|
|
||||||
uint8_t capabilities = 0x00;
|
uint8_t capabilities = 0x00;
|
||||||
#ifdef USE_OUTPUT
|
#ifdef USE_OUTPUT
|
||||||
if (this->status_indicator_ != nullptr)
|
if (this->status_indicator_ != nullptr)
|
||||||
capabilities |= improv::CAPABILITY_IDENTIFY;
|
capabilities |= improv::CAPABILITY_IDENTIFY;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
service_data[3] = capabilities;
|
service_data[3] = capabilities;
|
||||||
service_data[4] = 0x00; // Reserved
|
service_data[4] = 0x00; // Reserved
|
||||||
service_data[5] = 0x00; // Reserved
|
service_data[5] = 0x00; // Reserved
|
||||||
service_data[6] = 0x00; // Reserved
|
service_data[6] = 0x00; // Reserved
|
||||||
service_data[7] = 0x00; // Reserved
|
service_data[7] = 0x00; // Reserved
|
||||||
|
|
||||||
esp32_ble::global_ble->advertising_set_service_data(service_data);
|
esp32_ble::global_ble->advertising_set_service_data(service_data);
|
||||||
|
}
|
||||||
#ifdef USE_ESP32_IMPROV_STATE_CALLBACK
|
#ifdef USE_ESP32_IMPROV_STATE_CALLBACK
|
||||||
this->state_callback_.call(this->state_, this->error_state_);
|
this->state_callback_.call(this->state_, this->error_state_);
|
||||||
#endif
|
#endif
|
||||||
@@ -237,7 +272,12 @@ void ESP32ImprovComponent::set_error_(improv::Error error) {
|
|||||||
if (error != improv::ERROR_NONE) {
|
if (error != improv::ERROR_NONE) {
|
||||||
ESP_LOGE(TAG, "Error: %d", error);
|
ESP_LOGE(TAG, "Error: %d", error);
|
||||||
}
|
}
|
||||||
if (this->error_->get_value().empty() || this->error_->get_value()[0] != error) {
|
// The error_ characteristic is initialized in setup_characteristics() which is called
|
||||||
|
// from the loop, while the BLE disconnect callback is registered in setup().
|
||||||
|
// error_ can be nullptr if:
|
||||||
|
// 1. A client connects/disconnects before setup_characteristics() is called
|
||||||
|
// 2. The device is already provisioned so the service never starts (should_start_ is false)
|
||||||
|
if (this->error_ != nullptr && (this->error_->get_value().empty() || this->error_->get_value()[0] != error)) {
|
||||||
this->error_->set_value(ByteBuffer::wrap(static_cast<uint8_t>(error)));
|
this->error_->set_value(ByteBuffer::wrap(static_cast<uint8_t>(error)));
|
||||||
if (this->state_ != improv::STATE_STOPPED)
|
if (this->state_ != improv::STATE_STOPPED)
|
||||||
this->error_->notify();
|
this->error_->notify();
|
||||||
@@ -261,7 +301,10 @@ void ESP32ImprovComponent::start() {
|
|||||||
|
|
||||||
void ESP32ImprovComponent::stop() {
|
void ESP32ImprovComponent::stop() {
|
||||||
this->should_start_ = false;
|
this->should_start_ = false;
|
||||||
this->set_timeout("end-service", 1000, [this] {
|
// Wait before stopping the service to ensure all BLE clients see the state change.
|
||||||
|
// This prevents clients from repeatedly reconnecting and wasting resources by allowing
|
||||||
|
// them to observe that the device is provisioned before the service disappears.
|
||||||
|
this->set_timeout("end-service", STOP_ADVERTISING_DELAY, [this] {
|
||||||
if (this->state_ == improv::STATE_STOPPED || this->service_ == nullptr)
|
if (this->state_ == improv::STATE_STOPPED || this->service_ == nullptr)
|
||||||
return;
|
return;
|
||||||
this->service_->stop();
|
this->service_->stop();
|
||||||
|
|||||||
@@ -79,12 +79,12 @@ class ESP32ImprovComponent : public Component {
|
|||||||
std::vector<uint8_t> incoming_data_;
|
std::vector<uint8_t> incoming_data_;
|
||||||
wifi::WiFiAP connecting_sta_;
|
wifi::WiFiAP connecting_sta_;
|
||||||
|
|
||||||
BLEService *service_ = nullptr;
|
BLEService *service_{nullptr};
|
||||||
BLECharacteristic *status_;
|
BLECharacteristic *status_{nullptr};
|
||||||
BLECharacteristic *error_;
|
BLECharacteristic *error_{nullptr};
|
||||||
BLECharacteristic *rpc_;
|
BLECharacteristic *rpc_{nullptr};
|
||||||
BLECharacteristic *rpc_response_;
|
BLECharacteristic *rpc_response_{nullptr};
|
||||||
BLECharacteristic *capabilities_;
|
BLECharacteristic *capabilities_{nullptr};
|
||||||
|
|
||||||
#ifdef USE_BINARY_SENSOR
|
#ifdef USE_BINARY_SENSOR
|
||||||
binary_sensor::BinarySensor *authorizer_{nullptr};
|
binary_sensor::BinarySensor *authorizer_{nullptr};
|
||||||
@@ -108,6 +108,9 @@ class ESP32ImprovComponent : public Component {
|
|||||||
void process_incoming_data_();
|
void process_incoming_data_();
|
||||||
void on_wifi_connect_timeout_();
|
void on_wifi_connect_timeout_();
|
||||||
bool check_identify_();
|
bool check_identify_();
|
||||||
|
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_DEBUG
|
||||||
|
const char *state_to_string_(improv::State state);
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
||||||
|
|||||||
@@ -128,4 +128,4 @@ async def to_code(config):
|
|||||||
|
|
||||||
cg.add_library("tonia/HeatpumpIR", "1.0.37")
|
cg.add_library("tonia/HeatpumpIR", "1.0.37")
|
||||||
if CORE.is_libretiny or CORE.is_esp32:
|
if CORE.is_libretiny or CORE.is_esp32:
|
||||||
CORE.add_platformio_option("lib_ignore", "IRremoteESP8266")
|
CORE.add_platformio_option("lib_ignore", ["IRremoteESP8266"])
|
||||||
|
|||||||
@@ -401,6 +401,12 @@ class DriverChip:
|
|||||||
sequence.append((MADCTL, madctl))
|
sequence.append((MADCTL, madctl))
|
||||||
return madctl
|
return madctl
|
||||||
|
|
||||||
|
def skip_command(self, command: str):
|
||||||
|
"""
|
||||||
|
Allow suppressing a standard command in the init sequence.
|
||||||
|
"""
|
||||||
|
return self.get_default(f"no_{command.lower()}", False)
|
||||||
|
|
||||||
def get_sequence(self, config) -> tuple[tuple[int, ...], int]:
|
def get_sequence(self, config) -> tuple[tuple[int, ...], int]:
|
||||||
"""
|
"""
|
||||||
Create the init sequence for the display.
|
Create the init sequence for the display.
|
||||||
@@ -432,7 +438,9 @@ class DriverChip:
|
|||||||
sequence.append((INVOFF,))
|
sequence.append((INVOFF,))
|
||||||
if brightness := config.get(CONF_BRIGHTNESS, self.get_default(CONF_BRIGHTNESS)):
|
if brightness := config.get(CONF_BRIGHTNESS, self.get_default(CONF_BRIGHTNESS)):
|
||||||
sequence.append((BRIGHTNESS, brightness))
|
sequence.append((BRIGHTNESS, brightness))
|
||||||
sequence.append((SLPOUT,))
|
# Add a SLPOUT command if required.
|
||||||
|
if not self.skip_command("SLPOUT"):
|
||||||
|
sequence.append((SLPOUT,))
|
||||||
sequence.append((DISPON,))
|
sequence.append((DISPON,))
|
||||||
|
|
||||||
# Flatten the sequence into a list of bytes, with the length of each command
|
# Flatten the sequence into a list of bytes, with the length of each command
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ wave_4_3 = DriverChip(
|
|||||||
"ESP32-S3-TOUCH-LCD-4.3",
|
"ESP32-S3-TOUCH-LCD-4.3",
|
||||||
swap_xy=UNDEFINED,
|
swap_xy=UNDEFINED,
|
||||||
initsequence=(),
|
initsequence=(),
|
||||||
|
color_order="RGB",
|
||||||
width=800,
|
width=800,
|
||||||
height=480,
|
height=480,
|
||||||
pclk_frequency="16MHz",
|
pclk_frequency="16MHz",
|
||||||
|
|||||||
@@ -27,7 +27,8 @@ DriverChip(
|
|||||||
bus_mode=TYPE_QUAD,
|
bus_mode=TYPE_QUAD,
|
||||||
brightness=0xD0,
|
brightness=0xD0,
|
||||||
color_order=MODE_RGB,
|
color_order=MODE_RGB,
|
||||||
initsequence=(SLPOUT,), # Requires early SLPOUT
|
no_slpout=True, # SLPOUT is in the init sequence, early
|
||||||
|
initsequence=(SLPOUT,),
|
||||||
)
|
)
|
||||||
|
|
||||||
DriverChip(
|
DriverChip(
|
||||||
@@ -95,6 +96,7 @@ CO5300 = DriverChip(
|
|||||||
brightness=0xD0,
|
brightness=0xD0,
|
||||||
color_order=MODE_RGB,
|
color_order=MODE_RGB,
|
||||||
bus_mode=TYPE_QUAD,
|
bus_mode=TYPE_QUAD,
|
||||||
|
no_slpout=True,
|
||||||
initsequence=(
|
initsequence=(
|
||||||
(SLPOUT,), # Requires early SLPOUT
|
(SLPOUT,), # Requires early SLPOUT
|
||||||
(PAGESEL, 0x00),
|
(PAGESEL, 0x00),
|
||||||
|
|||||||
@@ -288,11 +288,15 @@ void Sim800LComponent::parse_cmd_(std::string message) {
|
|||||||
if (item == 3) { // stat
|
if (item == 3) { // stat
|
||||||
uint8_t current_call_state = parse_number<uint8_t>(message.substr(start, end - start)).value_or(6);
|
uint8_t current_call_state = parse_number<uint8_t>(message.substr(start, end - start)).value_or(6);
|
||||||
if (current_call_state != this->call_state_) {
|
if (current_call_state != this->call_state_) {
|
||||||
ESP_LOGD(TAG, "Call state is now: %d", current_call_state);
|
if (current_call_state == 4) {
|
||||||
if (current_call_state == 0)
|
ESP_LOGV(TAG, "Premature call state '4'. Ignoring, waiting for RING");
|
||||||
this->call_connected_callback_.call();
|
} else {
|
||||||
|
this->call_state_ = current_call_state;
|
||||||
|
ESP_LOGD(TAG, "Call state is now: %d", current_call_state);
|
||||||
|
if (current_call_state == 0)
|
||||||
|
this->call_connected_callback_.call();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
this->call_state_ = current_call_state;
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
// item 4 = ""
|
// item 4 = ""
|
||||||
|
|||||||
@@ -217,7 +217,7 @@ void SX126x::configure() {
|
|||||||
this->write_opcode_(RADIO_SET_MODULATIONPARAMS, buf, 4);
|
this->write_opcode_(RADIO_SET_MODULATIONPARAMS, buf, 4);
|
||||||
|
|
||||||
// set packet params and sync word
|
// set packet params and sync word
|
||||||
this->set_packet_params_(this->payload_length_);
|
this->set_packet_params_(this->get_max_packet_size());
|
||||||
if (this->sync_value_.size() == 2) {
|
if (this->sync_value_.size() == 2) {
|
||||||
this->write_register_(REG_LORA_SYNCWORD, this->sync_value_.data(), this->sync_value_.size());
|
this->write_register_(REG_LORA_SYNCWORD, this->sync_value_.data(), this->sync_value_.size());
|
||||||
}
|
}
|
||||||
@@ -236,7 +236,7 @@ void SX126x::configure() {
|
|||||||
this->write_opcode_(RADIO_SET_MODULATIONPARAMS, buf, 8);
|
this->write_opcode_(RADIO_SET_MODULATIONPARAMS, buf, 8);
|
||||||
|
|
||||||
// set packet params and sync word
|
// set packet params and sync word
|
||||||
this->set_packet_params_(this->payload_length_);
|
this->set_packet_params_(this->get_max_packet_size());
|
||||||
if (!this->sync_value_.empty()) {
|
if (!this->sync_value_.empty()) {
|
||||||
this->write_register_(REG_GFSK_SYNCWORD, this->sync_value_.data(), this->sync_value_.size());
|
this->write_register_(REG_GFSK_SYNCWORD, this->sync_value_.data(), this->sync_value_.size());
|
||||||
}
|
}
|
||||||
@@ -274,7 +274,7 @@ void SX126x::set_packet_params_(uint8_t payload_length) {
|
|||||||
buf[2] = (this->preamble_detect_ > 0) ? ((this->preamble_detect_ - 1) | 0x04) : 0x00;
|
buf[2] = (this->preamble_detect_ > 0) ? ((this->preamble_detect_ - 1) | 0x04) : 0x00;
|
||||||
buf[3] = this->sync_value_.size() * 8;
|
buf[3] = this->sync_value_.size() * 8;
|
||||||
buf[4] = 0x00;
|
buf[4] = 0x00;
|
||||||
buf[5] = 0x00;
|
buf[5] = (this->payload_length_ > 0) ? 0x00 : 0x01;
|
||||||
buf[6] = payload_length;
|
buf[6] = payload_length;
|
||||||
buf[7] = this->crc_enable_ ? 0x06 : 0x01;
|
buf[7] = this->crc_enable_ ? 0x06 : 0x01;
|
||||||
buf[8] = 0x00;
|
buf[8] = 0x00;
|
||||||
@@ -314,6 +314,9 @@ SX126xError SX126x::transmit_packet(const std::vector<uint8_t> &packet) {
|
|||||||
buf[0] = 0xFF;
|
buf[0] = 0xFF;
|
||||||
buf[1] = 0xFF;
|
buf[1] = 0xFF;
|
||||||
this->write_opcode_(RADIO_CLR_IRQSTATUS, buf, 2);
|
this->write_opcode_(RADIO_CLR_IRQSTATUS, buf, 2);
|
||||||
|
if (this->payload_length_ == 0) {
|
||||||
|
this->set_packet_params_(this->get_max_packet_size());
|
||||||
|
}
|
||||||
if (this->rx_start_) {
|
if (this->rx_start_) {
|
||||||
this->set_mode_rx();
|
this->set_mode_rx();
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -72,6 +72,7 @@ void USBUartTypeCH34X::enable_channels() {
|
|||||||
if (channel->index_ >= 2)
|
if (channel->index_ >= 2)
|
||||||
cmd += 0xE;
|
cmd += 0xE;
|
||||||
this->control_transfer(USB_VENDOR_DEV | usb_host::USB_DIR_OUT, cmd, value, (factor << 8) | divisor, callback);
|
this->control_transfer(USB_VENDOR_DEV | usb_host::USB_DIR_OUT, cmd, value, (factor << 8) | divisor, callback);
|
||||||
|
this->control_transfer(USB_VENDOR_DEV | usb_host::USB_DIR_OUT, cmd + 3, 0x80, 0, callback);
|
||||||
}
|
}
|
||||||
USBUartTypeCdcAcm::enable_channels();
|
USBUartTypeCdcAcm::enable_channels();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -242,7 +242,6 @@ void VoiceAssistant::loop() {
|
|||||||
msg.flags = flags;
|
msg.flags = flags;
|
||||||
msg.audio_settings = audio_settings;
|
msg.audio_settings = audio_settings;
|
||||||
msg.set_wake_word_phrase(StringRef(this->wake_word_));
|
msg.set_wake_word_phrase(StringRef(this->wake_word_));
|
||||||
this->wake_word_ = "";
|
|
||||||
|
|
||||||
// Reset media player state tracking
|
// Reset media player state tracking
|
||||||
#ifdef USE_MEDIA_PLAYER
|
#ifdef USE_MEDIA_PLAYER
|
||||||
|
|||||||
@@ -40,5 +40,7 @@ async def to_code(config):
|
|||||||
cg.add_library("Update", None)
|
cg.add_library("Update", None)
|
||||||
if CORE.is_esp8266:
|
if CORE.is_esp8266:
|
||||||
cg.add_library("ESP8266WiFi", None)
|
cg.add_library("ESP8266WiFi", None)
|
||||||
|
if CORE.is_libretiny:
|
||||||
|
CORE.add_platformio_option("lib_ignore", ["ESPAsyncTCP", "RPAsyncTCP"])
|
||||||
# https://github.com/ESP32Async/ESPAsyncWebServer/blob/main/library.json
|
# https://github.com/ESP32Async/ESPAsyncWebServer/blob/main/library.json
|
||||||
cg.add_library("ESP32Async/ESPAsyncWebServer", "3.7.10")
|
cg.add_library("ESP32Async/ESPAsyncWebServer", "3.7.10")
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ from enum import Enum
|
|||||||
|
|
||||||
from esphome.enum import StrEnum
|
from esphome.enum import StrEnum
|
||||||
|
|
||||||
__version__ = "2025.9.1"
|
__version__ = "2025.9.3"
|
||||||
|
|
||||||
ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
|
ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
|
||||||
VALID_SUBSTITUTIONS_CHARACTERS = (
|
VALID_SUBSTITUTIONS_CHARACTERS = (
|
||||||
|
|||||||
@@ -396,7 +396,7 @@ async def add_includes(includes):
|
|||||||
async def _add_platformio_options(pio_options):
|
async def _add_platformio_options(pio_options):
|
||||||
# Add includes at the very end, so that they override everything
|
# Add includes at the very end, so that they override everything
|
||||||
for key, val in pio_options.items():
|
for key, val in pio_options.items():
|
||||||
if key == "build_flags" and not isinstance(val, list):
|
if key in ["build_flags", "lib_ignore"] and not isinstance(val, list):
|
||||||
val = [val]
|
val = [val]
|
||||||
cg.add_platformio_option(key, val)
|
cg.add_platformio_option(key, val)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
esphome:
|
||||||
|
name: noise-key-test
|
||||||
|
|
||||||
|
host:
|
||||||
|
|
||||||
|
api:
|
||||||
|
encryption:
|
||||||
|
key: "zX9/JHxMKwpP0jUGsF0iESCm1wRvNgR6NkKVOhn7kSs="
|
||||||
|
|
||||||
|
logger:
|
||||||
51
tests/integration/test_noise_encryption_key_protection.py
Normal file
51
tests/integration/test_noise_encryption_key_protection.py
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
"""Integration test for noise encryption key protection from YAML."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import base64
|
||||||
|
|
||||||
|
from aioesphomeapi import InvalidEncryptionKeyAPIError
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from .types import APIClientConnectedFactory, RunCompiledFunction
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_noise_encryption_key_protection(
|
||||||
|
yaml_config: str,
|
||||||
|
run_compiled: RunCompiledFunction,
|
||||||
|
api_client_connected: APIClientConnectedFactory,
|
||||||
|
) -> None:
|
||||||
|
"""Test that noise encryption key set in YAML cannot be changed via API."""
|
||||||
|
# The key that's set in the YAML fixture
|
||||||
|
noise_psk = "zX9/JHxMKwpP0jUGsF0iESCm1wRvNgR6NkKVOhn7kSs="
|
||||||
|
|
||||||
|
# Keep ESPHome process running throughout all tests
|
||||||
|
async with run_compiled(yaml_config):
|
||||||
|
# First connection - test key change attempt
|
||||||
|
async with api_client_connected(noise_psk=noise_psk) as client:
|
||||||
|
# Verify connection is established
|
||||||
|
device_info = await client.device_info()
|
||||||
|
assert device_info is not None
|
||||||
|
|
||||||
|
# Try to set a new encryption key via API
|
||||||
|
new_key = base64.b64encode(
|
||||||
|
b"x" * 32
|
||||||
|
) # Valid 32-byte key in base64 as bytes
|
||||||
|
|
||||||
|
# This should fail since key was set in YAML
|
||||||
|
success = await client.noise_encryption_set_key(new_key)
|
||||||
|
assert success is False
|
||||||
|
|
||||||
|
# Reconnect with the original key to verify it still works
|
||||||
|
async with api_client_connected(noise_psk=noise_psk) as client:
|
||||||
|
# Verify connection is still successful with original key
|
||||||
|
device_info = await client.device_info()
|
||||||
|
assert device_info is not None
|
||||||
|
assert device_info.name == "noise-key-test"
|
||||||
|
|
||||||
|
# Verify that connecting with a wrong key fails
|
||||||
|
wrong_key = base64.b64encode(b"y" * 32).decode() # Different key
|
||||||
|
with pytest.raises(InvalidEncryptionKeyAPIError):
|
||||||
|
async with api_client_connected(noise_psk=wrong_key) as client:
|
||||||
|
await client.device_info()
|
||||||
Reference in New Issue
Block a user