mirror of
https://github.com/esphome/esphome.git
synced 2025-09-29 16:42:19 +01:00
Merge remote-tracking branch 'upstream/dev' into integration
This commit is contained in:
@@ -738,6 +738,16 @@ def command_clean_mqtt(args: ArgsProtocol, config: ConfigType) -> int | None:
|
||||
return clean_mqtt(config, args)
|
||||
|
||||
|
||||
def command_clean_platform(args: ArgsProtocol, config: ConfigType) -> int | None:
|
||||
try:
|
||||
writer.clean_platform()
|
||||
except OSError as err:
|
||||
_LOGGER.error("Error deleting platform files: %s", err)
|
||||
return 1
|
||||
_LOGGER.info("Done!")
|
||||
return 0
|
||||
|
||||
|
||||
def command_mqtt_fingerprint(args: ArgsProtocol, config: ConfigType) -> int | None:
|
||||
from esphome import mqtt
|
||||
|
||||
@@ -936,9 +946,10 @@ POST_CONFIG_ACTIONS = {
|
||||
"upload": command_upload,
|
||||
"logs": command_logs,
|
||||
"run": command_run,
|
||||
"clean-mqtt": command_clean_mqtt,
|
||||
"mqtt-fingerprint": command_mqtt_fingerprint,
|
||||
"clean": command_clean,
|
||||
"clean-mqtt": command_clean_mqtt,
|
||||
"clean-platform": command_clean_platform,
|
||||
"mqtt-fingerprint": command_mqtt_fingerprint,
|
||||
"idedata": command_idedata,
|
||||
"rename": command_rename,
|
||||
"discover": command_discover,
|
||||
@@ -947,6 +958,7 @@ POST_CONFIG_ACTIONS = {
|
||||
SIMPLE_CONFIG_ACTIONS = [
|
||||
"clean",
|
||||
"clean-mqtt",
|
||||
"clean-platform",
|
||||
"config",
|
||||
]
|
||||
|
||||
@@ -1162,6 +1174,13 @@ def parse_args(argv):
|
||||
"configuration", help="Your YAML configuration file(s).", nargs="+"
|
||||
)
|
||||
|
||||
parser_clean = subparsers.add_parser(
|
||||
"clean-platform", help="Delete all platform files."
|
||||
)
|
||||
parser_clean.add_argument(
|
||||
"configuration", help="Your YAML configuration file(s).", nargs="+"
|
||||
)
|
||||
|
||||
parser_dashboard = subparsers.add_parser(
|
||||
"dashboard", help="Create a simple web server for a dashboard."
|
||||
)
|
||||
|
@@ -1571,7 +1571,7 @@ message BluetoothGATTWriteRequest {
|
||||
uint32 handle = 2;
|
||||
bool response = 3;
|
||||
|
||||
bytes data = 4;
|
||||
bytes data = 4 [(pointer_to_buffer) = true];
|
||||
}
|
||||
|
||||
message BluetoothGATTReadDescriptorRequest {
|
||||
@@ -1591,7 +1591,7 @@ message BluetoothGATTWriteDescriptorRequest {
|
||||
uint64 address = 1;
|
||||
uint32 handle = 2;
|
||||
|
||||
bytes data = 3;
|
||||
bytes data = 3 [(pointer_to_buffer) = true];
|
||||
}
|
||||
|
||||
message BluetoothGATTNotifyRequest {
|
||||
|
@@ -2037,9 +2037,12 @@ bool BluetoothGATTWriteRequest::decode_varint(uint32_t field_id, ProtoVarInt val
|
||||
}
|
||||
bool BluetoothGATTWriteRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
|
||||
switch (field_id) {
|
||||
case 4:
|
||||
this->data = value.as_string();
|
||||
case 4: {
|
||||
// Use raw data directly to avoid allocation
|
||||
this->data = value.data();
|
||||
this->data_len = value.size();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -2073,9 +2076,12 @@ bool BluetoothGATTWriteDescriptorRequest::decode_varint(uint32_t field_id, Proto
|
||||
}
|
||||
bool BluetoothGATTWriteDescriptorRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
|
||||
switch (field_id) {
|
||||
case 3:
|
||||
this->data = value.as_string();
|
||||
case 3: {
|
||||
// Use raw data directly to avoid allocation
|
||||
this->data = value.data();
|
||||
this->data_len = value.size();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
@@ -1988,14 +1988,15 @@ class BluetoothGATTReadResponse final : public ProtoMessage {
|
||||
class BluetoothGATTWriteRequest final : public ProtoDecodableMessage {
|
||||
public:
|
||||
static constexpr uint8_t MESSAGE_TYPE = 75;
|
||||
static constexpr uint8_t ESTIMATED_SIZE = 19;
|
||||
static constexpr uint8_t ESTIMATED_SIZE = 29;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *message_name() const override { return "bluetooth_gatt_write_request"; }
|
||||
#endif
|
||||
uint64_t address{0};
|
||||
uint32_t handle{0};
|
||||
bool response{false};
|
||||
std::string data{};
|
||||
const uint8_t *data{nullptr};
|
||||
uint16_t data_len{0};
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
@@ -2023,13 +2024,14 @@ class BluetoothGATTReadDescriptorRequest final : public ProtoDecodableMessage {
|
||||
class BluetoothGATTWriteDescriptorRequest final : public ProtoDecodableMessage {
|
||||
public:
|
||||
static constexpr uint8_t MESSAGE_TYPE = 77;
|
||||
static constexpr uint8_t ESTIMATED_SIZE = 17;
|
||||
static constexpr uint8_t ESTIMATED_SIZE = 27;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *message_name() const override { return "bluetooth_gatt_write_descriptor_request"; }
|
||||
#endif
|
||||
uint64_t address{0};
|
||||
uint32_t handle{0};
|
||||
std::string data{};
|
||||
const uint8_t *data{nullptr};
|
||||
uint16_t data_len{0};
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
|
@@ -1658,7 +1658,7 @@ void BluetoothGATTWriteRequest::dump_to(std::string &out) const {
|
||||
dump_field(out, "handle", this->handle);
|
||||
dump_field(out, "response", this->response);
|
||||
out.append(" data: ");
|
||||
out.append(format_hex_pretty(reinterpret_cast<const uint8_t *>(this->data.data()), this->data.size()));
|
||||
out.append(format_hex_pretty(this->data, this->data_len));
|
||||
out.append("\n");
|
||||
}
|
||||
void BluetoothGATTReadDescriptorRequest::dump_to(std::string &out) const {
|
||||
@@ -1671,7 +1671,7 @@ void BluetoothGATTWriteDescriptorRequest::dump_to(std::string &out) const {
|
||||
dump_field(out, "address", this->address);
|
||||
dump_field(out, "handle", this->handle);
|
||||
out.append(" data: ");
|
||||
out.append(format_hex_pretty(reinterpret_cast<const uint8_t *>(this->data.data()), this->data.size()));
|
||||
out.append(format_hex_pretty(this->data, this->data_len));
|
||||
out.append("\n");
|
||||
}
|
||||
void BluetoothGATTNotifyRequest::dump_to(std::string &out) const {
|
||||
|
@@ -514,7 +514,8 @@ esp_err_t BluetoothConnection::read_characteristic(uint16_t handle) {
|
||||
return this->check_and_log_error_("esp_ble_gattc_read_char", err);
|
||||
}
|
||||
|
||||
esp_err_t BluetoothConnection::write_characteristic(uint16_t handle, const std::string &data, bool response) {
|
||||
esp_err_t BluetoothConnection::write_characteristic(uint16_t handle, const uint8_t *data, size_t length,
|
||||
bool response) {
|
||||
if (!this->connected()) {
|
||||
this->log_gatt_not_connected_("write", "characteristic");
|
||||
return ESP_GATT_NOT_CONNECTED;
|
||||
@@ -522,8 +523,11 @@ esp_err_t BluetoothConnection::write_characteristic(uint16_t handle, const std::
|
||||
ESP_LOGV(TAG, "[%d] [%s] Writing GATT characteristic handle %d", this->connection_index_, this->address_str_.c_str(),
|
||||
handle);
|
||||
|
||||
// ESP-IDF's API requires a non-const uint8_t* but it doesn't modify the data
|
||||
// The BTC layer immediately copies the data to its own buffer (see btc_gattc.c)
|
||||
// const_cast is safe here and was previously hidden by a C-style cast
|
||||
esp_err_t err =
|
||||
esp_ble_gattc_write_char(this->gattc_if_, this->conn_id_, handle, data.size(), (uint8_t *) data.data(),
|
||||
esp_ble_gattc_write_char(this->gattc_if_, this->conn_id_, handle, length, const_cast<uint8_t *>(data),
|
||||
response ? ESP_GATT_WRITE_TYPE_RSP : ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
|
||||
return this->check_and_log_error_("esp_ble_gattc_write_char", err);
|
||||
}
|
||||
@@ -540,7 +544,7 @@ esp_err_t BluetoothConnection::read_descriptor(uint16_t handle) {
|
||||
return this->check_and_log_error_("esp_ble_gattc_read_char_descr", err);
|
||||
}
|
||||
|
||||
esp_err_t BluetoothConnection::write_descriptor(uint16_t handle, const std::string &data, bool response) {
|
||||
esp_err_t BluetoothConnection::write_descriptor(uint16_t handle, const uint8_t *data, size_t length, bool response) {
|
||||
if (!this->connected()) {
|
||||
this->log_gatt_not_connected_("write", "descriptor");
|
||||
return ESP_GATT_NOT_CONNECTED;
|
||||
@@ -548,8 +552,11 @@ esp_err_t BluetoothConnection::write_descriptor(uint16_t handle, const std::stri
|
||||
ESP_LOGV(TAG, "[%d] [%s] Writing GATT descriptor handle %d", this->connection_index_, this->address_str_.c_str(),
|
||||
handle);
|
||||
|
||||
// ESP-IDF's API requires a non-const uint8_t* but it doesn't modify the data
|
||||
// The BTC layer immediately copies the data to its own buffer (see btc_gattc.c)
|
||||
// const_cast is safe here and was previously hidden by a C-style cast
|
||||
esp_err_t err = esp_ble_gattc_write_char_descr(
|
||||
this->gattc_if_, this->conn_id_, handle, data.size(), (uint8_t *) data.data(),
|
||||
this->gattc_if_, this->conn_id_, handle, length, const_cast<uint8_t *>(data),
|
||||
response ? ESP_GATT_WRITE_TYPE_RSP : ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
|
||||
return this->check_and_log_error_("esp_ble_gattc_write_char_descr", err);
|
||||
}
|
||||
|
@@ -18,9 +18,9 @@ class BluetoothConnection final : public esp32_ble_client::BLEClientBase {
|
||||
esp32_ble_tracker::AdvertisementParserType get_advertisement_parser_type() override;
|
||||
|
||||
esp_err_t read_characteristic(uint16_t handle);
|
||||
esp_err_t write_characteristic(uint16_t handle, const std::string &data, bool response);
|
||||
esp_err_t write_characteristic(uint16_t handle, const uint8_t *data, size_t length, bool response);
|
||||
esp_err_t read_descriptor(uint16_t handle);
|
||||
esp_err_t write_descriptor(uint16_t handle, const std::string &data, bool response);
|
||||
esp_err_t write_descriptor(uint16_t handle, const uint8_t *data, size_t length, bool response);
|
||||
|
||||
esp_err_t notify_characteristic(uint16_t handle, bool enable);
|
||||
|
||||
|
@@ -305,7 +305,7 @@ void BluetoothProxy::bluetooth_gatt_write(const api::BluetoothGATTWriteRequest &
|
||||
return;
|
||||
}
|
||||
|
||||
auto err = connection->write_characteristic(msg.handle, msg.data, msg.response);
|
||||
auto err = connection->write_characteristic(msg.handle, msg.data, msg.data_len, msg.response);
|
||||
if (err != ESP_OK) {
|
||||
this->send_gatt_error(msg.address, msg.handle, err);
|
||||
}
|
||||
@@ -331,7 +331,7 @@ void BluetoothProxy::bluetooth_gatt_write_descriptor(const api::BluetoothGATTWri
|
||||
return;
|
||||
}
|
||||
|
||||
auto err = connection->write_descriptor(msg.handle, msg.data, true);
|
||||
auto err = connection->write_descriptor(msg.handle, msg.data, msg.data_len, true);
|
||||
if (err != ESP_OK) {
|
||||
this->send_gatt_error(msg.address, msg.handle, err);
|
||||
}
|
||||
|
@@ -1,4 +1,5 @@
|
||||
#include "zwave_proxy.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/util.h"
|
||||
@@ -12,6 +13,7 @@ static constexpr uint8_t ZWAVE_COMMAND_GET_NETWORK_IDS = 0x20;
|
||||
// GET_NETWORK_IDS response: [SOF][LENGTH][TYPE][CMD][HOME_ID(4)][NODE_ID][...]
|
||||
static constexpr uint8_t ZWAVE_COMMAND_TYPE_RESPONSE = 0x01; // Response type field value
|
||||
static constexpr uint8_t ZWAVE_MIN_GET_NETWORK_IDS_LENGTH = 9; // TYPE + CMD + HOME_ID(4) + NODE_ID + checksum
|
||||
static constexpr uint32_t HOME_ID_TIMEOUT_MS = 100; // Timeout for waiting for home ID during setup
|
||||
|
||||
static uint8_t calculate_frame_checksum(const uint8_t *data, uint8_t length) {
|
||||
// Calculate Z-Wave frame checksum
|
||||
@@ -26,7 +28,44 @@ static uint8_t calculate_frame_checksum(const uint8_t *data, uint8_t length) {
|
||||
|
||||
ZWaveProxy::ZWaveProxy() { global_zwave_proxy = this; }
|
||||
|
||||
void ZWaveProxy::setup() { this->send_simple_command_(ZWAVE_COMMAND_GET_NETWORK_IDS); }
|
||||
void ZWaveProxy::setup() {
|
||||
this->setup_time_ = App.get_loop_component_start_time();
|
||||
this->send_simple_command_(ZWAVE_COMMAND_GET_NETWORK_IDS);
|
||||
}
|
||||
|
||||
float ZWaveProxy::get_setup_priority() const {
|
||||
// Set up before API so home ID is ready when API starts
|
||||
return setup_priority::BEFORE_CONNECTION;
|
||||
}
|
||||
|
||||
bool ZWaveProxy::can_proceed() {
|
||||
// If we already have the home ID, we can proceed
|
||||
if (this->home_id_ready_) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Handle any pending responses
|
||||
if (this->response_handler_()) {
|
||||
ESP_LOGV(TAG, "Handled response during setup");
|
||||
}
|
||||
|
||||
// Process UART data to check for home ID
|
||||
this->process_uart_();
|
||||
|
||||
// Check if we got the home ID after processing
|
||||
if (this->home_id_ready_) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Wait up to HOME_ID_TIMEOUT_MS for home ID response
|
||||
const uint32_t now = App.get_loop_component_start_time();
|
||||
if (now - this->setup_time_ > HOME_ID_TIMEOUT_MS) {
|
||||
ESP_LOGW(TAG, "Timeout reading Home ID during setup");
|
||||
return true; // Proceed anyway after timeout
|
||||
}
|
||||
|
||||
return false; // Keep waiting
|
||||
}
|
||||
|
||||
void ZWaveProxy::loop() {
|
||||
if (this->response_handler_()) {
|
||||
@@ -37,6 +76,11 @@ void ZWaveProxy::loop() {
|
||||
this->api_connection_ = nullptr; // Unsubscribe if disconnected
|
||||
}
|
||||
|
||||
this->process_uart_();
|
||||
this->status_clear_warning();
|
||||
}
|
||||
|
||||
void ZWaveProxy::process_uart_() {
|
||||
while (this->available()) {
|
||||
uint8_t byte;
|
||||
if (!this->read_byte(&byte)) {
|
||||
@@ -56,6 +100,7 @@ void ZWaveProxy::loop() {
|
||||
// Extract the 4-byte Home ID starting at offset 4
|
||||
// The frame parser has already validated the checksum and ensured all bytes are present
|
||||
std::memcpy(this->home_id_.data(), this->buffer_.data() + 4, this->home_id_.size());
|
||||
this->home_id_ready_ = true;
|
||||
ESP_LOGI(TAG, "Home ID: %s",
|
||||
format_hex_pretty(this->home_id_.data(), this->home_id_.size(), ':', false).c_str());
|
||||
}
|
||||
@@ -73,7 +118,6 @@ void ZWaveProxy::loop() {
|
||||
}
|
||||
}
|
||||
}
|
||||
this->status_clear_warning();
|
||||
}
|
||||
|
||||
void ZWaveProxy::dump_config() { ESP_LOGCONFIG(TAG, "Z-Wave Proxy"); }
|
||||
|
@@ -44,6 +44,8 @@ class ZWaveProxy : public uart::UARTDevice, public Component {
|
||||
void setup() override;
|
||||
void loop() override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override;
|
||||
bool can_proceed() override;
|
||||
|
||||
void zwave_proxy_request(api::APIConnection *api_connection, api::enums::ZWaveProxyRequestType type);
|
||||
api::APIConnection *get_api_connection() { return this->api_connection_; }
|
||||
@@ -60,19 +62,24 @@ class ZWaveProxy : public uart::UARTDevice, public Component {
|
||||
bool parse_byte_(uint8_t byte); // Returns true if frame parsing was completed (a frame is ready in the buffer)
|
||||
void parse_start_(uint8_t byte);
|
||||
bool response_handler_();
|
||||
|
||||
api::APIConnection *api_connection_{nullptr}; // Current subscribed client
|
||||
|
||||
std::array<uint8_t, 4> home_id_{0, 0, 0, 0}; // Fixed buffer for home ID
|
||||
std::array<uint8_t, sizeof(api::ZWaveProxyFrame::data)> buffer_; // Fixed buffer for incoming data
|
||||
uint8_t buffer_index_{0}; // Index for populating the data buffer
|
||||
uint8_t end_frame_after_{0}; // Payload reception ends after this index
|
||||
uint8_t last_response_{0}; // Last response type sent
|
||||
ZWaveParsingState parsing_state_{ZWAVE_PARSING_STATE_WAIT_START};
|
||||
bool in_bootloader_{false}; // True if the device is detected to be in bootloader mode
|
||||
void process_uart_(); // Process all available UART data
|
||||
|
||||
// Pre-allocated message - always ready to send
|
||||
api::ZWaveProxyFrame outgoing_proto_msg_;
|
||||
std::array<uint8_t, sizeof(api::ZWaveProxyFrame::data)> buffer_; // Fixed buffer for incoming data
|
||||
std::array<uint8_t, 4> home_id_{0, 0, 0, 0}; // Fixed buffer for home ID
|
||||
|
||||
// Pointers and 32-bit values (aligned together)
|
||||
api::APIConnection *api_connection_{nullptr}; // Current subscribed client
|
||||
uint32_t setup_time_{0}; // Time when setup() was called
|
||||
|
||||
// 8-bit values (grouped together to minimize padding)
|
||||
uint8_t buffer_index_{0}; // Index for populating the data buffer
|
||||
uint8_t end_frame_after_{0}; // Payload reception ends after this index
|
||||
uint8_t last_response_{0}; // Last response type sent
|
||||
ZWaveParsingState parsing_state_{ZWAVE_PARSING_STATE_WAIT_START};
|
||||
bool in_bootloader_{false}; // True if the device is detected to be in bootloader mode
|
||||
bool home_id_ready_{false}; // True when home ID has been received from Z-Wave module
|
||||
};
|
||||
|
||||
extern ZWaveProxy *global_zwave_proxy; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
@@ -479,6 +479,12 @@ class EsphomeCleanMqttHandler(EsphomeCommandWebSocket):
|
||||
return [*DASHBOARD_COMMAND, "clean-mqtt", config_file]
|
||||
|
||||
|
||||
class EsphomeCleanPlatformHandler(EsphomeCommandWebSocket):
|
||||
async def build_command(self, json_message: dict[str, Any]) -> list[str]:
|
||||
config_file = settings.rel_path(json_message["configuration"])
|
||||
return [*DASHBOARD_COMMAND, "clean-platform", config_file]
|
||||
|
||||
|
||||
class EsphomeCleanHandler(EsphomeCommandWebSocket):
|
||||
async def build_command(self, json_message: dict[str, Any]) -> list[str]:
|
||||
config_file = settings.rel_path(json_message["configuration"])
|
||||
@@ -1313,6 +1319,7 @@ def make_app(debug=get_bool_env(ENV_DEV)) -> tornado.web.Application:
|
||||
(f"{rel}compile", EsphomeCompileHandler),
|
||||
(f"{rel}validate", EsphomeValidateHandler),
|
||||
(f"{rel}clean-mqtt", EsphomeCleanMqttHandler),
|
||||
(f"{rel}clean-platform", EsphomeCleanPlatformHandler),
|
||||
(f"{rel}clean", EsphomeCleanHandler),
|
||||
(f"{rel}vscode", EsphomeVscodeHandler),
|
||||
(f"{rel}ace", EsphomeAceEditorHandler),
|
||||
|
@@ -323,17 +323,39 @@ def clean_build():
|
||||
# Clean PlatformIO cache to resolve CMake compiler detection issues
|
||||
# This helps when toolchain paths change or get corrupted
|
||||
try:
|
||||
from platformio.project.helpers import get_project_cache_dir
|
||||
from platformio.project.config import ProjectConfig
|
||||
except ImportError:
|
||||
# PlatformIO is not available, skip cache cleaning
|
||||
pass
|
||||
else:
|
||||
cache_dir = get_project_cache_dir()
|
||||
if cache_dir and cache_dir.strip():
|
||||
cache_path = Path(cache_dir)
|
||||
if cache_path.is_dir():
|
||||
_LOGGER.info("Deleting PlatformIO cache %s", cache_dir)
|
||||
shutil.rmtree(cache_dir)
|
||||
config = ProjectConfig.get_instance()
|
||||
cache_dir = Path(config.get("platformio", "cache_dir"))
|
||||
if cache_dir.is_dir():
|
||||
_LOGGER.info("Deleting PlatformIO cache %s", cache_dir)
|
||||
shutil.rmtree(cache_dir)
|
||||
|
||||
|
||||
def clean_platform():
|
||||
import shutil
|
||||
|
||||
# Clean entire build dir
|
||||
if CORE.build_path.is_dir():
|
||||
_LOGGER.info("Deleting %s", CORE.build_path)
|
||||
shutil.rmtree(CORE.build_path)
|
||||
|
||||
# Clean PlatformIO project files
|
||||
try:
|
||||
from platformio.project.config import ProjectConfig
|
||||
except ImportError:
|
||||
# PlatformIO is not available, skip cleaning
|
||||
pass
|
||||
else:
|
||||
config = ProjectConfig.get_instance()
|
||||
for pio_dir in ["cache_dir", "packages_dir", "platforms_dir", "core_dir"]:
|
||||
path = Path(config.get("platformio", pio_dir))
|
||||
if path.is_dir():
|
||||
_LOGGER.info("Deleting PlatformIO %s %s", pio_dir, path)
|
||||
shutil.rmtree(path)
|
||||
|
||||
|
||||
GITIGNORE_CONTENT = """# Gitignore settings for ESPHome
|
||||
|
Reference in New Issue
Block a user