1
0
mirror of https://github.com/esphome/esphome.git synced 2025-10-29 22:24:26 +00:00

Merge branch 'guard_trq_allocation' into integration

This commit is contained in:
J. Nick Koston
2025-09-28 18:27:18 -05:00
20 changed files with 220 additions and 52 deletions

View File

@@ -2310,11 +2310,13 @@ message ZWaveProxyFrame {
enum ZWaveProxyRequestType {
ZWAVE_PROXY_REQUEST_TYPE_SUBSCRIBE = 0;
ZWAVE_PROXY_REQUEST_TYPE_UNSUBSCRIBE = 1;
ZWAVE_PROXY_REQUEST_TYPE_HOME_ID_CHANGE = 2;
}
message ZWaveProxyRequest {
option (id) = 129;
option (source) = SOURCE_CLIENT;
option (source) = SOURCE_BOTH;
option (ifdef) = "USE_ZWAVE_PROXY";
ZWaveProxyRequestType type = 1;
bytes data = 2 [(pointer_to_buffer) = true];
}

View File

@@ -3112,6 +3112,27 @@ bool ZWaveProxyRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
}
return true;
}
bool ZWaveProxyRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 2: {
// Use raw data directly to avoid allocation
this->data = value.data();
this->data_len = value.size();
break;
}
default:
return false;
}
return true;
}
void ZWaveProxyRequest::encode(ProtoWriteBuffer buffer) const {
buffer.encode_uint32(1, static_cast<uint32_t>(this->type));
buffer.encode_bytes(2, this->data, this->data_len);
}
void ZWaveProxyRequest::calculate_size(ProtoSize &size) const {
size.add_uint32(1, static_cast<uint32_t>(this->type));
size.add_length(2, this->data_len);
}
#endif
} // namespace esphome::api

View File

@@ -280,6 +280,7 @@ enum UpdateCommand : uint32_t {
enum ZWaveProxyRequestType : uint32_t {
ZWAVE_PROXY_REQUEST_TYPE_SUBSCRIBE = 0,
ZWAVE_PROXY_REQUEST_TYPE_UNSUBSCRIBE = 1,
ZWAVE_PROXY_REQUEST_TYPE_HOME_ID_CHANGE = 2,
};
#endif
@@ -2971,16 +2972,21 @@ class ZWaveProxyFrame final : public ProtoDecodableMessage {
class ZWaveProxyRequest final : public ProtoDecodableMessage {
public:
static constexpr uint8_t MESSAGE_TYPE = 129;
static constexpr uint8_t ESTIMATED_SIZE = 2;
static constexpr uint8_t ESTIMATED_SIZE = 21;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "z_wave_proxy_request"; }
#endif
enums::ZWaveProxyRequestType type{};
const uint8_t *data{nullptr};
uint16_t data_len{0};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(ProtoSize &size) 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;
};
#endif

View File

@@ -662,6 +662,8 @@ template<> const char *proto_enum_to_string<enums::ZWaveProxyRequestType>(enums:
return "ZWAVE_PROXY_REQUEST_TYPE_SUBSCRIBE";
case enums::ZWAVE_PROXY_REQUEST_TYPE_UNSUBSCRIBE:
return "ZWAVE_PROXY_REQUEST_TYPE_UNSUBSCRIBE";
case enums::ZWAVE_PROXY_REQUEST_TYPE_HOME_ID_CHANGE:
return "ZWAVE_PROXY_REQUEST_TYPE_HOME_ID_CHANGE";
default:
return "UNKNOWN";
}
@@ -2161,6 +2163,9 @@ void ZWaveProxyFrame::dump_to(std::string &out) const {
void ZWaveProxyRequest::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "ZWaveProxyRequest");
dump_field(out, "type", static_cast<enums::ZWaveProxyRequestType>(this->type));
out.append(" data: ");
out.append(format_hex_pretty(this->data, this->data_len));
out.append("\n");
}
#endif

View File

@@ -356,6 +356,15 @@ void APIServer::on_update(update::UpdateEntity *obj) {
}
#endif
#ifdef USE_ZWAVE_PROXY
void APIServer::on_zwave_proxy_request(const esphome::api::ProtoMessage &msg) {
// We could add code to manage a second subscription type, but, since this message type is
// very infrequent and small, we simply send it to all clients
for (auto &c : this->clients_)
c->send_message(msg, api::ZWaveProxyRequest::MESSAGE_TYPE);
}
#endif
#ifdef USE_ALARM_CONTROL_PANEL
API_DISPATCH_UPDATE(alarm_control_panel::AlarmControlPanel, alarm_control_panel)
#endif

View File

@@ -126,6 +126,9 @@ class APIServer : public Component, public Controller {
#ifdef USE_UPDATE
void on_update(update::UpdateEntity *obj) override;
#endif
#ifdef USE_ZWAVE_PROXY
void on_zwave_proxy_request(const esphome::api::ProtoMessage &msg);
#endif
bool is_connected() const;

View File

@@ -276,9 +276,6 @@ def get_spi_interface(index):
return ["&SPI", "&SPI1"][index]
if index == 0:
return "&SPI"
# Following code can't apply to C2, H2 or 8266 since they have only one SPI
if get_target_variant() in (VARIANT_ESP32S3, VARIANT_ESP32S2):
return "new SPIClass(FSPI)"
return "new SPIClass(HSPI)"

View File

@@ -217,7 +217,7 @@ void SX126x::configure() {
this->write_opcode_(RADIO_SET_MODULATIONPARAMS, buf, 4);
// 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) {
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);
// 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()) {
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[3] = this->sync_value_.size() * 8;
buf[4] = 0x00;
buf[5] = 0x00;
buf[5] = (this->payload_length_ > 0) ? 0x00 : 0x01;
buf[6] = payload_length;
buf[7] = this->crc_enable_ ? 0x06 : 0x01;
buf[8] = 0x00;
@@ -314,6 +314,9 @@ SX126xError SX126x::transmit_packet(const std::vector<uint8_t> &packet) {
buf[0] = 0xFF;
buf[1] = 0xFF;
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_) {
this->set_mode_rx();
} else {

View File

@@ -1,5 +1,6 @@
import esphome.codegen as cg
from esphome.components.esp32 import (
VARIANT_ESP32P4,
VARIANT_ESP32S2,
VARIANT_ESP32S3,
add_idf_sdkconfig_option,
@@ -47,7 +48,7 @@ CONFIG_SCHEMA = cv.All(
}
),
cv.only_with_esp_idf,
only_on_variant(supported=[VARIANT_ESP32S2, VARIANT_ESP32S3]),
only_on_variant(supported=[VARIANT_ESP32S2, VARIANT_ESP32S3, VARIANT_ESP32P4]),
)

View File

@@ -1,7 +1,7 @@
#pragma once
// Should not be needed, but it's required to pass CI clang-tidy checks
#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32P4)
#include "esphome/core/component.h"
#include <vector>
#include "usb/usb_host.h"
@@ -9,11 +9,31 @@
#include <freertos/task.h>
#include "esphome/core/lock_free_queue.h"
#include "esphome/core/event_pool.h"
#include <list>
#include <atomic>
namespace esphome {
namespace usb_host {
// THREADING MODEL:
// This component uses a dedicated USB task for event processing to prevent data loss.
// - USB Task (high priority): Handles USB events, executes transfer callbacks
// - Main Loop Task: Initiates transfers, processes completion events
//
// Thread-safe communication:
// - Lock-free queues for USB task -> main loop events (SPSC pattern)
// - Lock-free TransferRequest pool using atomic bitmask (MCSP pattern)
//
// TransferRequest pool access pattern:
// - get_trq_() [allocate]: Called from BOTH USB task and main loop threads
// * USB task: via USB UART input callbacks that restart transfers immediately
// * Main loop: for output transfers and flow-controlled input restarts
// - release_trq() [deallocate]: Called from main loop thread only
//
// The multi-threaded allocation is intentional for performance:
// - USB task can immediately restart input transfers without context switching
// - Main loop controls backpressure by deciding when to restart after consuming data
// The atomic bitmask ensures thread-safe allocation without mutex blocking.
static const char *const TAG = "usb_host";
// Forward declarations
@@ -98,13 +118,7 @@ class USBClient : public Component {
friend class USBHost;
public:
USBClient(uint16_t vid, uint16_t pid) : vid_(vid), pid_(pid) { init_pool(); }
void init_pool() {
this->trq_pool_.clear();
for (size_t i = 0; i != MAX_REQUESTS; i++)
this->trq_pool_.push_back(&this->requests_[i]);
}
USBClient(uint16_t vid, uint16_t pid) : vid_(vid), pid_(pid), trq_in_use_(0) {}
void setup() override;
void loop() override;
// setup must happen after the host bus has been setup
@@ -126,10 +140,13 @@ class USBClient : public Component {
protected:
bool register_();
TransferRequest *get_trq_();
TransferRequest *get_trq_(); // Lock-free allocation using atomic bitmask (multi-consumer safe)
virtual void disconnect();
virtual void on_connected() {}
virtual void on_disconnected() { this->init_pool(); }
virtual void on_disconnected() {
// Reset all requests to available (all bits to 0)
this->trq_in_use_.store(0);
}
// USB task management
static void usb_task_fn(void *arg);
@@ -143,7 +160,11 @@ class USBClient : public Component {
int state_{USB_CLIENT_INIT};
uint16_t vid_{};
uint16_t pid_{};
std::list<TransferRequest *> trq_pool_{};
// Lock-free pool management using atomic bitmask (no dynamic allocation)
// Bit i = 1: requests_[i] is in use, Bit i = 0: requests_[i] is available
// Supports multiple concurrent consumers (both threads can allocate)
// Single producer for deallocation (main loop only)
std::atomic<uint16_t> trq_in_use_;
TransferRequest requests_[MAX_REQUESTS]{};
};
class USBHost : public Component {

View File

@@ -1,5 +1,5 @@
// Should not be needed, but it's required to pass CI clang-tidy checks
#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32P4)
#include "usb_host.h"
#include "esphome/core/log.h"
#include "esphome/core/hal.h"
@@ -7,6 +7,7 @@
#include <cinttypes>
#include <cstring>
#include <atomic>
namespace esphome {
namespace usb_host {
@@ -185,9 +186,11 @@ void USBClient::setup() {
this->mark_failed();
return;
}
for (auto *trq : this->trq_pool_) {
usb_host_transfer_alloc(64, 0, &trq->transfer);
trq->client = this;
// Pre-allocate USB transfer buffers for all slots at startup
// This avoids any dynamic allocation during runtime
for (size_t i = 0; i < MAX_REQUESTS; i++) {
usb_host_transfer_alloc(64, 0, &this->requests_[i].transfer);
this->requests_[i].client = this; // Set once, never changes
}
// Create and start USB task
@@ -347,17 +350,39 @@ static void control_callback(const usb_transfer_t *xfer) {
queue_transfer_cleanup(trq, EVENT_CONTROL_COMPLETE);
}
// THREAD CONTEXT: Called from both USB task and main loop threads (multi-consumer)
// - USB task: USB UART input callbacks restart transfers for immediate data reception
// - Main loop: Output transfers and flow-controlled input restarts after consuming data
//
// THREAD SAFETY: Lock-free using atomic compare-and-swap on bitmask
// This multi-threaded access is intentional for performance - USB task can
// immediately restart transfers without waiting for main loop scheduling.
TransferRequest *USBClient::get_trq_() {
if (this->trq_pool_.empty()) {
ESP_LOGE(TAG, "Too many requests queued");
return nullptr;
uint16_t mask = this->trq_in_use_.load(std::memory_order_relaxed);
// Find first available slot (bit = 0) and try to claim it atomically
for (size_t i = 0; i < MAX_REQUESTS; i++) {
if (!(mask & (1U << i))) {
// Slot i appears available, try to claim it atomically
uint16_t expected = mask;
uint16_t desired = mask | (1U << i); // Set bit i to mark as in-use
if (this->trq_in_use_.compare_exchange_weak(expected, desired, std::memory_order_acquire,
std::memory_order_relaxed)) {
// Successfully claimed slot i - prepare the TransferRequest
auto *trq = &this->requests_[i];
trq->transfer->context = trq;
trq->transfer->device_handle = this->device_handle_;
return trq;
}
// Another thread claimed this slot, retry with updated mask
mask = expected;
i--; // Retry the same index with new mask value
}
}
auto *trq = this->trq_pool_.front();
this->trq_pool_.pop_front();
trq->client = this;
trq->transfer->context = trq;
trq->transfer->device_handle = this->device_handle_;
return trq;
ESP_LOGE(TAG, "Too many requests queued (all %d slots in use)", MAX_REQUESTS);
return nullptr;
}
void USBClient::disconnect() {
this->on_disconnected();
@@ -370,6 +395,8 @@ void USBClient::disconnect() {
this->device_addr_ = -1;
}
// THREAD CONTEXT: Called from main loop thread only
// - Used for device configuration and control operations
bool USBClient::control_transfer(uint8_t type, uint8_t request, uint16_t value, uint16_t index,
const transfer_cb_t &callback, const std::vector<uint8_t> &data) {
auto *trq = this->get_trq_();
@@ -425,6 +452,9 @@ static void transfer_callback(usb_transfer_t *xfer) {
}
/**
* Performs a transfer input operation.
* THREAD CONTEXT: Called from both USB task and main loop threads!
* - USB task: USB UART input callbacks call start_input() which calls this
* - Main loop: Initial setup and other components
*
* @param ep_address The endpoint address.
* @param callback The callback function to be called when the transfer is complete.
@@ -451,6 +481,9 @@ void USBClient::transfer_in(uint8_t ep_address, const transfer_cb_t &callback, u
/**
* Performs an output transfer operation.
* THREAD CONTEXT: Called from main loop thread only
* - USB UART output uses defer() to ensure main loop context
* - Modbus and other components call from loop()
*
* @param ep_address The endpoint address.
* @param callback The callback function to be called when the transfer is complete.
@@ -483,7 +516,28 @@ void USBClient::dump_config() {
" Product id %04X",
this->vid_, this->pid_);
}
void USBClient::release_trq(TransferRequest *trq) { this->trq_pool_.push_back(trq); }
// THREAD CONTEXT: Only called from main loop thread (single producer for deallocation)
// - Via event processing when handling EVENT_TRANSFER_COMPLETE/EVENT_CONTROL_COMPLETE
// - Directly when transfer submission fails
//
// THREAD SAFETY: Lock-free using atomic AND to clear bit
// Single-producer pattern makes this simpler than allocation
void USBClient::release_trq(TransferRequest *trq) {
if (trq == nullptr)
return;
// Calculate index from pointer arithmetic
size_t index = trq - this->requests_;
if (index >= MAX_REQUESTS) {
ESP_LOGE(TAG, "Invalid TransferRequest pointer");
return;
}
// Atomically clear bit i to mark slot as available
// fetch_and with inverted bitmask clears the bit atomically
uint16_t bit = 1U << index;
this->trq_in_use_.fetch_and(~bit, std::memory_order_release);
}
} // namespace usb_host
} // namespace esphome

View File

@@ -1,5 +1,5 @@
// Should not be needed, but it's required to pass CI clang-tidy checks
#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32P4)
#include "usb_host.h"
#include <cinttypes>
#include "esphome/core/log.h"

View File

@@ -24,7 +24,6 @@ usb_uart_ns = cg.esphome_ns.namespace("usb_uart")
USBUartComponent = usb_uart_ns.class_("USBUartComponent", Component)
USBUartChannel = usb_uart_ns.class_("USBUartChannel", UARTComponent)
UARTParityOptions = usb_uart_ns.enum("UARTParityOptions")
UART_PARITY_OPTIONS = {
"NONE": UARTParityOptions.UART_CONFIG_PARITY_NONE,

View File

@@ -1,4 +1,4 @@
#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32P4)
#include "usb_uart.h"
#include "usb/usb_host.h"
#include "esphome/core/log.h"
@@ -72,6 +72,7 @@ void USBUartTypeCH34X::enable_channels() {
if (channel->index_ >= 2)
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 + 3, 0x80, 0, callback);
}
USBUartTypeCdcAcm::enable_channels();
}

View File

@@ -1,4 +1,4 @@
#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32P4)
#include "usb_uart.h"
#include "usb/usb_host.h"
#include "esphome/core/log.h"

View File

@@ -1,5 +1,5 @@
// Should not be needed, but it's required to pass CI clang-tidy checks
#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32P4)
#include "usb_uart.h"
#include "esphome/core/log.h"
#include "esphome/components/uart/uart_debugger.h"
@@ -216,9 +216,16 @@ void USBUartComponent::dump_config() {
void USBUartComponent::start_input(USBUartChannel *channel) {
if (!channel->initialised_.load() || channel->input_started_.load())
return;
// Note: This function is called from both USB task and main loop, so we cannot
// directly check ring buffer space here. Backpressure is handled by the chunk pool:
// when exhausted, USB input stops until chunks are freed by the main loop
// THREAD CONTEXT: Called from both USB task and main loop threads
// - USB task: Immediate restart after successful transfer for continuous data flow
// - Main loop: Controlled restart after consuming data (backpressure mechanism)
//
// This dual-thread access is intentional for performance:
// - USB task restarts avoid context switch delays for high-speed data
// - Main loop restarts provide flow control when buffers are full
//
// The underlying transfer_in() uses lock-free atomic allocation from the
// TransferRequest pool, making this multi-threaded access safe
const auto *ep = channel->cdc_dev_.in_ep;
// CALLBACK CONTEXT: This lambda is executed in USB task via transfer_callback
auto callback = [this, channel](const usb_host::TransferStatus &status) {
@@ -297,7 +304,8 @@ static void fix_mps(const usb_ep_desc_t *ep) {
if (ep != nullptr) {
auto *ep_mutable = const_cast<usb_ep_desc_t *>(ep);
if (ep->wMaxPacketSize > 64) {
ESP_LOGW(TAG, "Corrected MPS of EP %u from %u to 64", ep->bEndpointAddress, ep->wMaxPacketSize);
ESP_LOGW(TAG, "Corrected MPS of EP 0x%02X from %u to 64", static_cast<uint8_t>(ep->bEndpointAddress & 0xFF),
ep->wMaxPacketSize);
ep_mutable->wMaxPacketSize = 64;
}
}
@@ -314,7 +322,7 @@ void USBUartTypeCdcAcm::on_connected() {
for (auto *channel : this->channels_) {
if (i == cdc_devs.size()) {
ESP_LOGE(TAG, "No configuration found for channel %d", channel->index_);
this->status_set_warning(LOG_STR("No configuration found for channel"));
this->status_set_warning("No configuration found for channel");
break;
}
channel->cdc_dev_ = cdc_devs[i++];

View File

@@ -1,6 +1,6 @@
#pragma once
#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32P4)
#include "esphome/core/component.h"
#include "esphome/core/helpers.h"
#include "esphome/components/uart/uart_component.h"

View File

@@ -1,4 +1,5 @@
#include "zwave_proxy.h"
#include "esphome/components/api/api_server.h"
#include "esphome/core/application.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
@@ -97,12 +98,19 @@ void ZWaveProxy::process_uart_() {
// - buffer_[3]: Command ID (0x20 for GET_NETWORK_IDS)
if (this->buffer_[3] == ZWAVE_COMMAND_GET_NETWORK_IDS && this->buffer_[2] == ZWAVE_COMMAND_TYPE_RESPONSE &&
this->buffer_[1] >= ZWAVE_MIN_GET_NETWORK_IDS_LENGTH && this->buffer_[0] == ZWAVE_FRAME_TYPE_START) {
// Extract the 4-byte Home ID starting at offset 4
// Store the 4-byte Home ID, which starts at offset 4, and notify connected clients if it changed
// 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());
if (this->set_home_id(&this->buffer_[4])) {
api::ZWaveProxyRequest msg;
msg.type = api::enums::ZWAVE_PROXY_REQUEST_TYPE_HOME_ID_CHANGE;
msg.data = this->home_id_.data();
msg.data_len = this->home_id_.size();
if (api::global_api_server != nullptr) {
// We could add code to manage a second subscription type, but, since this message is
// very infrequent and small, we simply send it to all clients
api::global_api_server->on_zwave_proxy_request(msg);
}
}
}
ESP_LOGV(TAG, "Sending to client: %s", YESNO(this->api_connection_ != nullptr));
if (this->api_connection_ != nullptr) {
@@ -120,7 +128,12 @@ void ZWaveProxy::process_uart_() {
}
}
void ZWaveProxy::dump_config() { ESP_LOGCONFIG(TAG, "Z-Wave Proxy"); }
void ZWaveProxy::dump_config() {
ESP_LOGCONFIG(TAG,
"Z-Wave Proxy:\n"
" Home ID: %s",
format_hex_pretty(this->home_id_.data(), this->home_id_.size(), ':', false).c_str());
}
void ZWaveProxy::zwave_proxy_request(api::APIConnection *api_connection, api::enums::ZWaveProxyRequestType type) {
switch (type) {
@@ -145,6 +158,17 @@ void ZWaveProxy::zwave_proxy_request(api::APIConnection *api_connection, api::en
}
}
bool ZWaveProxy::set_home_id(const uint8_t *new_home_id) {
if (std::memcmp(this->home_id_.data(), new_home_id, this->home_id_.size()) == 0) {
ESP_LOGV(TAG, "Home ID unchanged");
return false; // No change
}
std::memcpy(this->home_id_.data(), new_home_id, this->home_id_.size());
ESP_LOGI(TAG, "Home ID: %s", format_hex_pretty(this->home_id_.data(), this->home_id_.size(), ':', false).c_str());
this->home_id_ready_ = true;
return true; // Home ID was changed
}
void ZWaveProxy::send_frame(const uint8_t *data, size_t length) {
if (length == 1 && data[0] == this->last_response_) {
ESP_LOGV(TAG, "Skipping sending duplicate response: 0x%02X", data[0]);

View File

@@ -56,6 +56,7 @@ class ZWaveProxy : public uart::UARTDevice, public Component {
uint32_t get_home_id() {
return encode_uint32(this->home_id_[0], this->home_id_[1], this->home_id_[2], this->home_id_[3]);
}
bool set_home_id(const uint8_t *new_home_id); // Store a new home ID. Returns true if it changed.
void send_frame(const uint8_t *data, size_t length);

View File

@@ -0,0 +1,13 @@
spi:
- id: three_spi
interface: spi3
clk_pin:
number: 47
mosi_pin:
number: 40
- id: hw_spi
interface: hardware
clk_pin:
number: 0
miso_pin:
number: 41