mirror of
https://github.com/esphome/esphome.git
synced 2025-08-07 19:07:45 +00:00
Merge branch 'dev' into camera-platform
This commit is contained in:
commit
64eb70444d
@ -125,6 +125,7 @@ esphome/components/dht/* @OttoWinter
|
|||||||
esphome/components/display_menu_base/* @numo68
|
esphome/components/display_menu_base/* @numo68
|
||||||
esphome/components/dps310/* @kbx81
|
esphome/components/dps310/* @kbx81
|
||||||
esphome/components/ds1307/* @badbadc0ffee
|
esphome/components/ds1307/* @badbadc0ffee
|
||||||
|
esphome/components/ds2484/* @mrk-its
|
||||||
esphome/components/dsmr/* @glmnet @zuidwijk
|
esphome/components/dsmr/* @glmnet @zuidwijk
|
||||||
esphome/components/duty_time/* @dudanov
|
esphome/components/duty_time/* @dudanov
|
||||||
esphome/components/ee895/* @Stock-M
|
esphome/components/ee895/* @Stock-M
|
||||||
@ -499,6 +500,7 @@ esphome/components/voice_assistant/* @jesserockz @kahrendt
|
|||||||
esphome/components/wake_on_lan/* @clydebarrow @willwill2will54
|
esphome/components/wake_on_lan/* @clydebarrow @willwill2will54
|
||||||
esphome/components/watchdog/* @oarcher
|
esphome/components/watchdog/* @oarcher
|
||||||
esphome/components/waveshare_epaper/* @clydebarrow
|
esphome/components/waveshare_epaper/* @clydebarrow
|
||||||
|
esphome/components/web_server/ota/* @esphome/core
|
||||||
esphome/components/web_server_base/* @OttoWinter
|
esphome/components/web_server_base/* @OttoWinter
|
||||||
esphome/components/web_server_idf/* @dentra
|
esphome/components/web_server_idf/* @dentra
|
||||||
esphome/components/weikai/* @DrCoolZic
|
esphome/components/weikai/* @DrCoolZic
|
||||||
|
@ -15,8 +15,7 @@ namespace adc {
|
|||||||
|
|
||||||
#ifdef USE_ESP32
|
#ifdef USE_ESP32
|
||||||
// clang-format off
|
// clang-format off
|
||||||
#if (ESP_IDF_VERSION_MAJOR == 4 && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 4, 7)) || \
|
#if (ESP_IDF_VERSION_MAJOR == 5 && \
|
||||||
(ESP_IDF_VERSION_MAJOR == 5 && \
|
|
||||||
((ESP_IDF_VERSION_MINOR == 0 && ESP_IDF_VERSION_PATCH >= 5) || \
|
((ESP_IDF_VERSION_MINOR == 0 && ESP_IDF_VERSION_PATCH >= 5) || \
|
||||||
(ESP_IDF_VERSION_MINOR == 1 && ESP_IDF_VERSION_PATCH >= 3) || \
|
(ESP_IDF_VERSION_MINOR == 1 && ESP_IDF_VERSION_PATCH >= 3) || \
|
||||||
(ESP_IDF_VERSION_MINOR >= 2)) \
|
(ESP_IDF_VERSION_MINOR >= 2)) \
|
||||||
@ -100,11 +99,7 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage
|
|||||||
adc1_channel_t channel1_{ADC1_CHANNEL_MAX};
|
adc1_channel_t channel1_{ADC1_CHANNEL_MAX};
|
||||||
adc2_channel_t channel2_{ADC2_CHANNEL_MAX};
|
adc2_channel_t channel2_{ADC2_CHANNEL_MAX};
|
||||||
bool autorange_{false};
|
bool autorange_{false};
|
||||||
#if ESP_IDF_VERSION_MAJOR >= 5
|
|
||||||
esp_adc_cal_characteristics_t cal_characteristics_[SOC_ADC_ATTEN_NUM] = {};
|
esp_adc_cal_characteristics_t cal_characteristics_[SOC_ADC_ATTEN_NUM] = {};
|
||||||
#else
|
|
||||||
esp_adc_cal_characteristics_t cal_characteristics_[ADC_ATTEN_MAX] = {};
|
|
||||||
#endif // ESP_IDF_VERSION_MAJOR
|
|
||||||
#endif // USE_ESP32
|
#endif // USE_ESP32
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -132,7 +132,9 @@ async def to_code(config):
|
|||||||
await cg.register_component(var, config)
|
await cg.register_component(var, config)
|
||||||
|
|
||||||
cg.add(var.set_port(config[CONF_PORT]))
|
cg.add(var.set_port(config[CONF_PORT]))
|
||||||
cg.add(var.set_password(config[CONF_PASSWORD]))
|
if config[CONF_PASSWORD]:
|
||||||
|
cg.add_define("USE_API_PASSWORD")
|
||||||
|
cg.add(var.set_password(config[CONF_PASSWORD]))
|
||||||
cg.add(var.set_reboot_timeout(config[CONF_REBOOT_TIMEOUT]))
|
cg.add(var.set_reboot_timeout(config[CONF_REBOOT_TIMEOUT]))
|
||||||
cg.add(var.set_batch_delay(config[CONF_BATCH_DELAY]))
|
cg.add(var.set_batch_delay(config[CONF_BATCH_DELAY]))
|
||||||
|
|
||||||
|
@ -1508,7 +1508,10 @@ HelloResponse APIConnection::hello(const HelloRequest &msg) {
|
|||||||
return resp;
|
return resp;
|
||||||
}
|
}
|
||||||
ConnectResponse APIConnection::connect(const ConnectRequest &msg) {
|
ConnectResponse APIConnection::connect(const ConnectRequest &msg) {
|
||||||
bool correct = this->parent_->check_password(msg.password);
|
bool correct = true;
|
||||||
|
#ifdef USE_API_PASSWORD
|
||||||
|
correct = this->parent_->check_password(msg.password);
|
||||||
|
#endif
|
||||||
|
|
||||||
ConnectResponse resp;
|
ConnectResponse resp;
|
||||||
// bool invalid_password = 1;
|
// bool invalid_password = 1;
|
||||||
@ -1529,7 +1532,11 @@ ConnectResponse APIConnection::connect(const ConnectRequest &msg) {
|
|||||||
}
|
}
|
||||||
DeviceInfoResponse APIConnection::device_info(const DeviceInfoRequest &msg) {
|
DeviceInfoResponse APIConnection::device_info(const DeviceInfoRequest &msg) {
|
||||||
DeviceInfoResponse resp{};
|
DeviceInfoResponse resp{};
|
||||||
|
#ifdef USE_API_PASSWORD
|
||||||
resp.uses_password = this->parent_->uses_password();
|
resp.uses_password = this->parent_->uses_password();
|
||||||
|
#else
|
||||||
|
resp.uses_password = false;
|
||||||
|
#endif
|
||||||
resp.name = App.get_name();
|
resp.name = App.get_name();
|
||||||
resp.friendly_name = App.get_friendly_name();
|
resp.friendly_name = App.get_friendly_name();
|
||||||
resp.suggested_area = App.get_area();
|
resp.suggested_area = App.get_area();
|
||||||
@ -1692,7 +1699,9 @@ void APIConnection::DeferredBatch::add_item(EntityBase *entity, MessageCreator c
|
|||||||
// O(n) but optimized for RAM and not performance.
|
// O(n) but optimized for RAM and not performance.
|
||||||
for (auto &item : items) {
|
for (auto &item : items) {
|
||||||
if (item.entity == entity && item.message_type == message_type) {
|
if (item.entity == entity && item.message_type == message_type) {
|
||||||
// Update the existing item with the new creator
|
// Clean up old creator before replacing
|
||||||
|
item.creator.cleanup(message_type);
|
||||||
|
// Move assign the new creator
|
||||||
item.creator = std::move(creator);
|
item.creator = std::move(creator);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -1735,11 +1744,11 @@ void APIConnection::process_batch_() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t num_items = this->deferred_batch_.items.size();
|
size_t num_items = this->deferred_batch_.size();
|
||||||
|
|
||||||
// Fast path for single message - allocate exact size needed
|
// Fast path for single message - allocate exact size needed
|
||||||
if (num_items == 1) {
|
if (num_items == 1) {
|
||||||
const auto &item = this->deferred_batch_.items[0];
|
const auto &item = this->deferred_batch_[0];
|
||||||
|
|
||||||
// Let the creator calculate size and encode if it fits
|
// Let the creator calculate size and encode if it fits
|
||||||
uint16_t payload_size =
|
uint16_t payload_size =
|
||||||
@ -1769,7 +1778,8 @@ void APIConnection::process_batch_() {
|
|||||||
|
|
||||||
// Pre-calculate exact buffer size needed based on message types
|
// Pre-calculate exact buffer size needed based on message types
|
||||||
uint32_t total_estimated_size = 0;
|
uint32_t total_estimated_size = 0;
|
||||||
for (const auto &item : this->deferred_batch_.items) {
|
for (size_t i = 0; i < this->deferred_batch_.size(); i++) {
|
||||||
|
const auto &item = this->deferred_batch_[i];
|
||||||
total_estimated_size += get_estimated_message_size(item.message_type);
|
total_estimated_size += get_estimated_message_size(item.message_type);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1790,7 +1800,8 @@ void APIConnection::process_batch_() {
|
|||||||
uint32_t current_offset = 0;
|
uint32_t current_offset = 0;
|
||||||
|
|
||||||
// Process items and encode directly to buffer
|
// Process items and encode directly to buffer
|
||||||
for (const auto &item : this->deferred_batch_.items) {
|
for (size_t i = 0; i < this->deferred_batch_.size(); i++) {
|
||||||
|
const auto &item = this->deferred_batch_[i];
|
||||||
// Try to encode message
|
// Try to encode message
|
||||||
// The creator will calculate overhead to determine if the message fits
|
// The creator will calculate overhead to determine if the message fits
|
||||||
uint16_t payload_size = item.creator(item.entity, this, remaining_size, false, item.message_type);
|
uint16_t payload_size = item.creator(item.entity, this, remaining_size, false, item.message_type);
|
||||||
@ -1845,17 +1856,15 @@ void APIConnection::process_batch_() {
|
|||||||
// Log messages after send attempt for VV debugging
|
// Log messages after send attempt for VV debugging
|
||||||
// It's safe to use the buffer for logging at this point regardless of send result
|
// It's safe to use the buffer for logging at this point regardless of send result
|
||||||
for (size_t i = 0; i < items_processed; i++) {
|
for (size_t i = 0; i < items_processed; i++) {
|
||||||
const auto &item = this->deferred_batch_.items[i];
|
const auto &item = this->deferred_batch_[i];
|
||||||
this->log_batch_item_(item);
|
this->log_batch_item_(item);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Handle remaining items more efficiently
|
// Handle remaining items more efficiently
|
||||||
if (items_processed < this->deferred_batch_.items.size()) {
|
if (items_processed < this->deferred_batch_.size()) {
|
||||||
// Remove processed items from the beginning
|
// Remove processed items from the beginning with proper cleanup
|
||||||
this->deferred_batch_.items.erase(this->deferred_batch_.items.begin(),
|
this->deferred_batch_.remove_front(items_processed);
|
||||||
this->deferred_batch_.items.begin() + items_processed);
|
|
||||||
|
|
||||||
// Reschedule for remaining items
|
// Reschedule for remaining items
|
||||||
this->schedule_batch_();
|
this->schedule_batch_();
|
||||||
} else {
|
} else {
|
||||||
@ -1866,23 +1875,16 @@ void APIConnection::process_batch_() {
|
|||||||
|
|
||||||
uint16_t APIConnection::MessageCreator::operator()(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
|
uint16_t APIConnection::MessageCreator::operator()(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
|
||||||
bool is_single, uint16_t message_type) const {
|
bool is_single, uint16_t message_type) const {
|
||||||
if (has_tagged_string_ptr_()) {
|
|
||||||
// Handle string-based messages
|
|
||||||
switch (message_type) {
|
|
||||||
#ifdef USE_EVENT
|
#ifdef USE_EVENT
|
||||||
case EventResponse::MESSAGE_TYPE: {
|
// Special case: EventResponse uses string pointer
|
||||||
auto *e = static_cast<event::Event *>(entity);
|
if (message_type == EventResponse::MESSAGE_TYPE) {
|
||||||
return APIConnection::try_send_event_response(e, *get_string_ptr_(), conn, remaining_size, is_single);
|
auto *e = static_cast<event::Event *>(entity);
|
||||||
}
|
return APIConnection::try_send_event_response(e, *data_.string_ptr, conn, remaining_size, is_single);
|
||||||
#endif
|
|
||||||
default:
|
|
||||||
// Should not happen, return 0 to indicate no message
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Function pointer case
|
|
||||||
return data_.ptr(entity, conn, remaining_size, is_single);
|
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// All other message types use function pointers
|
||||||
|
return data_.function_ptr(entity, conn, remaining_size, is_single);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint16_t APIConnection::try_send_list_info_done(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
|
uint16_t APIConnection::try_send_list_info_done(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
|
||||||
|
@ -451,96 +451,53 @@ class APIConnection : public APIServerConnection {
|
|||||||
// Function pointer type for message encoding
|
// Function pointer type for message encoding
|
||||||
using MessageCreatorPtr = uint16_t (*)(EntityBase *, APIConnection *, uint32_t remaining_size, bool is_single);
|
using MessageCreatorPtr = uint16_t (*)(EntityBase *, APIConnection *, uint32_t remaining_size, bool is_single);
|
||||||
|
|
||||||
// Optimized MessageCreator class using tagged pointer
|
|
||||||
class MessageCreator {
|
class MessageCreator {
|
||||||
// Ensure pointer alignment allows LSB tagging
|
|
||||||
static_assert(alignof(std::string *) > 1, "String pointer alignment must be > 1 for LSB tagging");
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
// Constructor for function pointer
|
// Constructor for function pointer
|
||||||
MessageCreator(MessageCreatorPtr ptr) {
|
MessageCreator(MessageCreatorPtr ptr) { data_.function_ptr = ptr; }
|
||||||
// Function pointers are always aligned, so LSB is 0
|
|
||||||
data_.ptr = ptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Constructor for string state capture
|
// Constructor for string state capture
|
||||||
explicit MessageCreator(const std::string &str_value) {
|
explicit MessageCreator(const std::string &str_value) { data_.string_ptr = new std::string(str_value); }
|
||||||
// Allocate string and tag the pointer
|
|
||||||
auto *str = new std::string(str_value);
|
|
||||||
// Set LSB to 1 to indicate string pointer
|
|
||||||
data_.tagged = reinterpret_cast<uintptr_t>(str) | 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Destructor
|
// No destructor - cleanup must be called explicitly with message_type
|
||||||
~MessageCreator() {
|
|
||||||
if (has_tagged_string_ptr_()) {
|
|
||||||
delete get_string_ptr_();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy constructor
|
// Delete copy operations - MessageCreator should only be moved
|
||||||
MessageCreator(const MessageCreator &other) {
|
MessageCreator(const MessageCreator &other) = delete;
|
||||||
if (other.has_tagged_string_ptr_()) {
|
MessageCreator &operator=(const MessageCreator &other) = delete;
|
||||||
auto *str = new std::string(*other.get_string_ptr_());
|
|
||||||
data_.tagged = reinterpret_cast<uintptr_t>(str) | 1;
|
|
||||||
} else {
|
|
||||||
data_ = other.data_;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Move constructor
|
// Move constructor
|
||||||
MessageCreator(MessageCreator &&other) noexcept : data_(other.data_) { other.data_.ptr = nullptr; }
|
MessageCreator(MessageCreator &&other) noexcept : data_(other.data_) { other.data_.function_ptr = nullptr; }
|
||||||
|
|
||||||
// Assignment operators (needed for batch deduplication)
|
|
||||||
MessageCreator &operator=(const MessageCreator &other) {
|
|
||||||
if (this != &other) {
|
|
||||||
// Clean up current string data if needed
|
|
||||||
if (has_tagged_string_ptr_()) {
|
|
||||||
delete get_string_ptr_();
|
|
||||||
}
|
|
||||||
// Copy new data
|
|
||||||
if (other.has_tagged_string_ptr_()) {
|
|
||||||
auto *str = new std::string(*other.get_string_ptr_());
|
|
||||||
data_.tagged = reinterpret_cast<uintptr_t>(str) | 1;
|
|
||||||
} else {
|
|
||||||
data_ = other.data_;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// Move assignment
|
||||||
MessageCreator &operator=(MessageCreator &&other) noexcept {
|
MessageCreator &operator=(MessageCreator &&other) noexcept {
|
||||||
if (this != &other) {
|
if (this != &other) {
|
||||||
// Clean up current string data if needed
|
// IMPORTANT: Caller must ensure cleanup() was called if this contains a string!
|
||||||
if (has_tagged_string_ptr_()) {
|
// In our usage, this happens in add_item() deduplication and vector::erase()
|
||||||
delete get_string_ptr_();
|
|
||||||
}
|
|
||||||
// Move data
|
|
||||||
data_ = other.data_;
|
data_ = other.data_;
|
||||||
// Reset other to safe state
|
other.data_.function_ptr = nullptr;
|
||||||
other.data_.ptr = nullptr;
|
|
||||||
}
|
}
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Call operator - now accepts message_type as parameter
|
// Call operator - uses message_type to determine union type
|
||||||
uint16_t operator()(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single,
|
uint16_t operator()(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single,
|
||||||
uint16_t message_type) const;
|
uint16_t message_type) const;
|
||||||
|
|
||||||
private:
|
// Manual cleanup method - must be called before destruction for string types
|
||||||
// Check if this contains a string pointer
|
void cleanup(uint16_t message_type) {
|
||||||
bool has_tagged_string_ptr_() const { return (data_.tagged & 1) != 0; }
|
#ifdef USE_EVENT
|
||||||
|
if (message_type == EventResponse::MESSAGE_TYPE && data_.string_ptr != nullptr) {
|
||||||
// Get the actual string pointer (clears the tag bit)
|
delete data_.string_ptr;
|
||||||
std::string *get_string_ptr_() const {
|
data_.string_ptr = nullptr;
|
||||||
// NOLINTNEXTLINE(performance-no-int-to-ptr)
|
}
|
||||||
return reinterpret_cast<std::string *>(data_.tagged & ~uintptr_t(1));
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
union {
|
private:
|
||||||
MessageCreatorPtr ptr;
|
union Data {
|
||||||
uintptr_t tagged;
|
MessageCreatorPtr function_ptr;
|
||||||
} data_; // 4 bytes on 32-bit
|
std::string *string_ptr;
|
||||||
|
} data_; // 4 bytes on 32-bit, 8 bytes on 64-bit - same as before
|
||||||
};
|
};
|
||||||
|
|
||||||
// Generic batching mechanism for both state updates and entity info
|
// Generic batching mechanism for both state updates and entity info
|
||||||
@ -558,20 +515,46 @@ class APIConnection : public APIServerConnection {
|
|||||||
std::vector<BatchItem> items;
|
std::vector<BatchItem> items;
|
||||||
uint32_t batch_start_time{0};
|
uint32_t batch_start_time{0};
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Helper to cleanup items from the beginning
|
||||||
|
void cleanup_items_(size_t count) {
|
||||||
|
for (size_t i = 0; i < count; i++) {
|
||||||
|
items[i].creator.cleanup(items[i].message_type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
DeferredBatch() {
|
DeferredBatch() {
|
||||||
// Pre-allocate capacity for typical batch sizes to avoid reallocation
|
// Pre-allocate capacity for typical batch sizes to avoid reallocation
|
||||||
items.reserve(8);
|
items.reserve(8);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
~DeferredBatch() {
|
||||||
|
// Ensure cleanup of any remaining items
|
||||||
|
clear();
|
||||||
|
}
|
||||||
|
|
||||||
// Add item to the batch
|
// Add item to the batch
|
||||||
void add_item(EntityBase *entity, MessageCreator creator, uint16_t message_type);
|
void add_item(EntityBase *entity, MessageCreator creator, uint16_t message_type);
|
||||||
// Add item to the front of the batch (for high priority messages like ping)
|
// Add item to the front of the batch (for high priority messages like ping)
|
||||||
void add_item_front(EntityBase *entity, MessageCreator creator, uint16_t message_type);
|
void add_item_front(EntityBase *entity, MessageCreator creator, uint16_t message_type);
|
||||||
|
|
||||||
|
// Clear all items with proper cleanup
|
||||||
void clear() {
|
void clear() {
|
||||||
|
cleanup_items_(items.size());
|
||||||
items.clear();
|
items.clear();
|
||||||
batch_start_time = 0;
|
batch_start_time = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remove processed items from the front with proper cleanup
|
||||||
|
void remove_front(size_t count) {
|
||||||
|
cleanup_items_(count);
|
||||||
|
items.erase(items.begin(), items.begin() + count);
|
||||||
|
}
|
||||||
|
|
||||||
bool empty() const { return items.empty(); }
|
bool empty() const { return items.empty(); }
|
||||||
|
size_t size() const { return items.size(); }
|
||||||
|
const BatchItem &operator[](size_t index) const { return items[index]; }
|
||||||
};
|
};
|
||||||
|
|
||||||
// DeferredBatch here (16 bytes, 4-byte aligned)
|
// DeferredBatch here (16 bytes, 4-byte aligned)
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -2,6 +2,8 @@
|
|||||||
// See script/api_protobuf/api_protobuf.py
|
// See script/api_protobuf/api_protobuf.py
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/core/defines.h"
|
||||||
|
|
||||||
#include "proto.h"
|
#include "proto.h"
|
||||||
#include "api_pb2_size.h"
|
#include "api_pb2_size.h"
|
||||||
|
|
||||||
@ -15,6 +17,7 @@ enum EntityCategory : uint32_t {
|
|||||||
ENTITY_CATEGORY_CONFIG = 1,
|
ENTITY_CATEGORY_CONFIG = 1,
|
||||||
ENTITY_CATEGORY_DIAGNOSTIC = 2,
|
ENTITY_CATEGORY_DIAGNOSTIC = 2,
|
||||||
};
|
};
|
||||||
|
#ifdef USE_COVER
|
||||||
enum LegacyCoverState : uint32_t {
|
enum LegacyCoverState : uint32_t {
|
||||||
LEGACY_COVER_STATE_OPEN = 0,
|
LEGACY_COVER_STATE_OPEN = 0,
|
||||||
LEGACY_COVER_STATE_CLOSED = 1,
|
LEGACY_COVER_STATE_CLOSED = 1,
|
||||||
@ -29,6 +32,8 @@ enum LegacyCoverCommand : uint32_t {
|
|||||||
LEGACY_COVER_COMMAND_CLOSE = 1,
|
LEGACY_COVER_COMMAND_CLOSE = 1,
|
||||||
LEGACY_COVER_COMMAND_STOP = 2,
|
LEGACY_COVER_COMMAND_STOP = 2,
|
||||||
};
|
};
|
||||||
|
#endif
|
||||||
|
#ifdef USE_FAN
|
||||||
enum FanSpeed : uint32_t {
|
enum FanSpeed : uint32_t {
|
||||||
FAN_SPEED_LOW = 0,
|
FAN_SPEED_LOW = 0,
|
||||||
FAN_SPEED_MEDIUM = 1,
|
FAN_SPEED_MEDIUM = 1,
|
||||||
@ -38,6 +43,8 @@ enum FanDirection : uint32_t {
|
|||||||
FAN_DIRECTION_FORWARD = 0,
|
FAN_DIRECTION_FORWARD = 0,
|
||||||
FAN_DIRECTION_REVERSE = 1,
|
FAN_DIRECTION_REVERSE = 1,
|
||||||
};
|
};
|
||||||
|
#endif
|
||||||
|
#ifdef USE_LIGHT
|
||||||
enum ColorMode : uint32_t {
|
enum ColorMode : uint32_t {
|
||||||
COLOR_MODE_UNKNOWN = 0,
|
COLOR_MODE_UNKNOWN = 0,
|
||||||
COLOR_MODE_ON_OFF = 1,
|
COLOR_MODE_ON_OFF = 1,
|
||||||
@ -51,6 +58,8 @@ enum ColorMode : uint32_t {
|
|||||||
COLOR_MODE_RGB_COLOR_TEMPERATURE = 47,
|
COLOR_MODE_RGB_COLOR_TEMPERATURE = 47,
|
||||||
COLOR_MODE_RGB_COLD_WARM_WHITE = 51,
|
COLOR_MODE_RGB_COLD_WARM_WHITE = 51,
|
||||||
};
|
};
|
||||||
|
#endif
|
||||||
|
#ifdef USE_SENSOR
|
||||||
enum SensorStateClass : uint32_t {
|
enum SensorStateClass : uint32_t {
|
||||||
STATE_CLASS_NONE = 0,
|
STATE_CLASS_NONE = 0,
|
||||||
STATE_CLASS_MEASUREMENT = 1,
|
STATE_CLASS_MEASUREMENT = 1,
|
||||||
@ -62,6 +71,7 @@ enum SensorLastResetType : uint32_t {
|
|||||||
LAST_RESET_NEVER = 1,
|
LAST_RESET_NEVER = 1,
|
||||||
LAST_RESET_AUTO = 2,
|
LAST_RESET_AUTO = 2,
|
||||||
};
|
};
|
||||||
|
#endif
|
||||||
enum LogLevel : uint32_t {
|
enum LogLevel : uint32_t {
|
||||||
LOG_LEVEL_NONE = 0,
|
LOG_LEVEL_NONE = 0,
|
||||||
LOG_LEVEL_ERROR = 1,
|
LOG_LEVEL_ERROR = 1,
|
||||||
@ -82,6 +92,7 @@ enum ServiceArgType : uint32_t {
|
|||||||
SERVICE_ARG_TYPE_FLOAT_ARRAY = 6,
|
SERVICE_ARG_TYPE_FLOAT_ARRAY = 6,
|
||||||
SERVICE_ARG_TYPE_STRING_ARRAY = 7,
|
SERVICE_ARG_TYPE_STRING_ARRAY = 7,
|
||||||
};
|
};
|
||||||
|
#ifdef USE_CLIMATE
|
||||||
enum ClimateMode : uint32_t {
|
enum ClimateMode : uint32_t {
|
||||||
CLIMATE_MODE_OFF = 0,
|
CLIMATE_MODE_OFF = 0,
|
||||||
CLIMATE_MODE_HEAT_COOL = 1,
|
CLIMATE_MODE_HEAT_COOL = 1,
|
||||||
@ -127,11 +138,15 @@ enum ClimatePreset : uint32_t {
|
|||||||
CLIMATE_PRESET_SLEEP = 6,
|
CLIMATE_PRESET_SLEEP = 6,
|
||||||
CLIMATE_PRESET_ACTIVITY = 7,
|
CLIMATE_PRESET_ACTIVITY = 7,
|
||||||
};
|
};
|
||||||
|
#endif
|
||||||
|
#ifdef USE_NUMBER
|
||||||
enum NumberMode : uint32_t {
|
enum NumberMode : uint32_t {
|
||||||
NUMBER_MODE_AUTO = 0,
|
NUMBER_MODE_AUTO = 0,
|
||||||
NUMBER_MODE_BOX = 1,
|
NUMBER_MODE_BOX = 1,
|
||||||
NUMBER_MODE_SLIDER = 2,
|
NUMBER_MODE_SLIDER = 2,
|
||||||
};
|
};
|
||||||
|
#endif
|
||||||
|
#ifdef USE_LOCK
|
||||||
enum LockState : uint32_t {
|
enum LockState : uint32_t {
|
||||||
LOCK_STATE_NONE = 0,
|
LOCK_STATE_NONE = 0,
|
||||||
LOCK_STATE_LOCKED = 1,
|
LOCK_STATE_LOCKED = 1,
|
||||||
@ -145,6 +160,8 @@ enum LockCommand : uint32_t {
|
|||||||
LOCK_LOCK = 1,
|
LOCK_LOCK = 1,
|
||||||
LOCK_OPEN = 2,
|
LOCK_OPEN = 2,
|
||||||
};
|
};
|
||||||
|
#endif
|
||||||
|
#ifdef USE_MEDIA_PLAYER
|
||||||
enum MediaPlayerState : uint32_t {
|
enum MediaPlayerState : uint32_t {
|
||||||
MEDIA_PLAYER_STATE_NONE = 0,
|
MEDIA_PLAYER_STATE_NONE = 0,
|
||||||
MEDIA_PLAYER_STATE_IDLE = 1,
|
MEDIA_PLAYER_STATE_IDLE = 1,
|
||||||
@ -162,6 +179,8 @@ enum MediaPlayerFormatPurpose : uint32_t {
|
|||||||
MEDIA_PLAYER_FORMAT_PURPOSE_DEFAULT = 0,
|
MEDIA_PLAYER_FORMAT_PURPOSE_DEFAULT = 0,
|
||||||
MEDIA_PLAYER_FORMAT_PURPOSE_ANNOUNCEMENT = 1,
|
MEDIA_PLAYER_FORMAT_PURPOSE_ANNOUNCEMENT = 1,
|
||||||
};
|
};
|
||||||
|
#endif
|
||||||
|
#ifdef USE_BLUETOOTH_PROXY
|
||||||
enum BluetoothDeviceRequestType : uint32_t {
|
enum BluetoothDeviceRequestType : uint32_t {
|
||||||
BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT = 0,
|
BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT = 0,
|
||||||
BLUETOOTH_DEVICE_REQUEST_TYPE_DISCONNECT = 1,
|
BLUETOOTH_DEVICE_REQUEST_TYPE_DISCONNECT = 1,
|
||||||
@ -183,6 +202,7 @@ enum BluetoothScannerMode : uint32_t {
|
|||||||
BLUETOOTH_SCANNER_MODE_PASSIVE = 0,
|
BLUETOOTH_SCANNER_MODE_PASSIVE = 0,
|
||||||
BLUETOOTH_SCANNER_MODE_ACTIVE = 1,
|
BLUETOOTH_SCANNER_MODE_ACTIVE = 1,
|
||||||
};
|
};
|
||||||
|
#endif
|
||||||
enum VoiceAssistantSubscribeFlag : uint32_t {
|
enum VoiceAssistantSubscribeFlag : uint32_t {
|
||||||
VOICE_ASSISTANT_SUBSCRIBE_NONE = 0,
|
VOICE_ASSISTANT_SUBSCRIBE_NONE = 0,
|
||||||
VOICE_ASSISTANT_SUBSCRIBE_API_AUDIO = 1,
|
VOICE_ASSISTANT_SUBSCRIBE_API_AUDIO = 1,
|
||||||
@ -192,6 +212,7 @@ enum VoiceAssistantRequestFlag : uint32_t {
|
|||||||
VOICE_ASSISTANT_REQUEST_USE_VAD = 1,
|
VOICE_ASSISTANT_REQUEST_USE_VAD = 1,
|
||||||
VOICE_ASSISTANT_REQUEST_USE_WAKE_WORD = 2,
|
VOICE_ASSISTANT_REQUEST_USE_WAKE_WORD = 2,
|
||||||
};
|
};
|
||||||
|
#ifdef USE_VOICE_ASSISTANT
|
||||||
enum VoiceAssistantEvent : uint32_t {
|
enum VoiceAssistantEvent : uint32_t {
|
||||||
VOICE_ASSISTANT_ERROR = 0,
|
VOICE_ASSISTANT_ERROR = 0,
|
||||||
VOICE_ASSISTANT_RUN_START = 1,
|
VOICE_ASSISTANT_RUN_START = 1,
|
||||||
@ -216,6 +237,8 @@ enum VoiceAssistantTimerEvent : uint32_t {
|
|||||||
VOICE_ASSISTANT_TIMER_CANCELLED = 2,
|
VOICE_ASSISTANT_TIMER_CANCELLED = 2,
|
||||||
VOICE_ASSISTANT_TIMER_FINISHED = 3,
|
VOICE_ASSISTANT_TIMER_FINISHED = 3,
|
||||||
};
|
};
|
||||||
|
#endif
|
||||||
|
#ifdef USE_ALARM_CONTROL_PANEL
|
||||||
enum AlarmControlPanelState : uint32_t {
|
enum AlarmControlPanelState : uint32_t {
|
||||||
ALARM_STATE_DISARMED = 0,
|
ALARM_STATE_DISARMED = 0,
|
||||||
ALARM_STATE_ARMED_HOME = 1,
|
ALARM_STATE_ARMED_HOME = 1,
|
||||||
@ -237,20 +260,27 @@ enum AlarmControlPanelStateCommand : uint32_t {
|
|||||||
ALARM_CONTROL_PANEL_ARM_CUSTOM_BYPASS = 5,
|
ALARM_CONTROL_PANEL_ARM_CUSTOM_BYPASS = 5,
|
||||||
ALARM_CONTROL_PANEL_TRIGGER = 6,
|
ALARM_CONTROL_PANEL_TRIGGER = 6,
|
||||||
};
|
};
|
||||||
|
#endif
|
||||||
|
#ifdef USE_TEXT
|
||||||
enum TextMode : uint32_t {
|
enum TextMode : uint32_t {
|
||||||
TEXT_MODE_TEXT = 0,
|
TEXT_MODE_TEXT = 0,
|
||||||
TEXT_MODE_PASSWORD = 1,
|
TEXT_MODE_PASSWORD = 1,
|
||||||
};
|
};
|
||||||
|
#endif
|
||||||
|
#ifdef USE_VALVE
|
||||||
enum ValveOperation : uint32_t {
|
enum ValveOperation : uint32_t {
|
||||||
VALVE_OPERATION_IDLE = 0,
|
VALVE_OPERATION_IDLE = 0,
|
||||||
VALVE_OPERATION_IS_OPENING = 1,
|
VALVE_OPERATION_IS_OPENING = 1,
|
||||||
VALVE_OPERATION_IS_CLOSING = 2,
|
VALVE_OPERATION_IS_CLOSING = 2,
|
||||||
};
|
};
|
||||||
|
#endif
|
||||||
|
#ifdef USE_UPDATE
|
||||||
enum UpdateCommand : uint32_t {
|
enum UpdateCommand : uint32_t {
|
||||||
UPDATE_COMMAND_NONE = 0,
|
UPDATE_COMMAND_NONE = 0,
|
||||||
UPDATE_COMMAND_UPDATE = 1,
|
UPDATE_COMMAND_UPDATE = 1,
|
||||||
UPDATE_COMMAND_CHECK = 2,
|
UPDATE_COMMAND_CHECK = 2,
|
||||||
};
|
};
|
||||||
|
#endif
|
||||||
|
|
||||||
} // namespace enums
|
} // namespace enums
|
||||||
|
|
||||||
@ -523,6 +553,7 @@ class SubscribeStatesRequest : public ProtoMessage {
|
|||||||
|
|
||||||
protected:
|
protected:
|
||||||
};
|
};
|
||||||
|
#ifdef USE_BINARY_SENSOR
|
||||||
class ListEntitiesBinarySensorResponse : public InfoResponseProtoMessage {
|
class ListEntitiesBinarySensorResponse : public InfoResponseProtoMessage {
|
||||||
public:
|
public:
|
||||||
static constexpr uint16_t MESSAGE_TYPE = 12;
|
static constexpr uint16_t MESSAGE_TYPE = 12;
|
||||||
@ -562,6 +593,8 @@ class BinarySensorStateResponse : public StateResponseProtoMessage {
|
|||||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||||
};
|
};
|
||||||
|
#endif
|
||||||
|
#ifdef USE_COVER
|
||||||
class ListEntitiesCoverResponse : public InfoResponseProtoMessage {
|
class ListEntitiesCoverResponse : public InfoResponseProtoMessage {
|
||||||
public:
|
public:
|
||||||
static constexpr uint16_t MESSAGE_TYPE = 13;
|
static constexpr uint16_t MESSAGE_TYPE = 13;
|
||||||
@ -631,6 +664,8 @@ class CoverCommandRequest : public ProtoMessage {
|
|||||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||||
};
|
};
|
||||||
|
#endif
|
||||||
|
#ifdef USE_FAN
|
||||||
class ListEntitiesFanResponse : public InfoResponseProtoMessage {
|
class ListEntitiesFanResponse : public InfoResponseProtoMessage {
|
||||||
public:
|
public:
|
||||||
static constexpr uint16_t MESSAGE_TYPE = 14;
|
static constexpr uint16_t MESSAGE_TYPE = 14;
|
||||||
@ -709,6 +744,8 @@ class FanCommandRequest : public ProtoMessage {
|
|||||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||||
};
|
};
|
||||||
|
#endif
|
||||||
|
#ifdef USE_LIGHT
|
||||||
class ListEntitiesLightResponse : public InfoResponseProtoMessage {
|
class ListEntitiesLightResponse : public InfoResponseProtoMessage {
|
||||||
public:
|
public:
|
||||||
static constexpr uint16_t MESSAGE_TYPE = 15;
|
static constexpr uint16_t MESSAGE_TYPE = 15;
|
||||||
@ -810,6 +847,8 @@ class LightCommandRequest : public ProtoMessage {
|
|||||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||||
};
|
};
|
||||||
|
#endif
|
||||||
|
#ifdef USE_SENSOR
|
||||||
class ListEntitiesSensorResponse : public InfoResponseProtoMessage {
|
class ListEntitiesSensorResponse : public InfoResponseProtoMessage {
|
||||||
public:
|
public:
|
||||||
static constexpr uint16_t MESSAGE_TYPE = 16;
|
static constexpr uint16_t MESSAGE_TYPE = 16;
|
||||||
@ -853,6 +892,8 @@ class SensorStateResponse : public StateResponseProtoMessage {
|
|||||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||||
};
|
};
|
||||||
|
#endif
|
||||||
|
#ifdef USE_SWITCH
|
||||||
class ListEntitiesSwitchResponse : public InfoResponseProtoMessage {
|
class ListEntitiesSwitchResponse : public InfoResponseProtoMessage {
|
||||||
public:
|
public:
|
||||||
static constexpr uint16_t MESSAGE_TYPE = 17;
|
static constexpr uint16_t MESSAGE_TYPE = 17;
|
||||||
@ -910,6 +951,8 @@ class SwitchCommandRequest : public ProtoMessage {
|
|||||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||||
};
|
};
|
||||||
|
#endif
|
||||||
|
#ifdef USE_TEXT_SENSOR
|
||||||
class ListEntitiesTextSensorResponse : public InfoResponseProtoMessage {
|
class ListEntitiesTextSensorResponse : public InfoResponseProtoMessage {
|
||||||
public:
|
public:
|
||||||
static constexpr uint16_t MESSAGE_TYPE = 18;
|
static constexpr uint16_t MESSAGE_TYPE = 18;
|
||||||
@ -949,6 +992,7 @@ class TextSensorStateResponse : public StateResponseProtoMessage {
|
|||||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||||
};
|
};
|
||||||
|
#endif
|
||||||
class SubscribeLogsRequest : public ProtoMessage {
|
class SubscribeLogsRequest : public ProtoMessage {
|
||||||
public:
|
public:
|
||||||
static constexpr uint16_t MESSAGE_TYPE = 28;
|
static constexpr uint16_t MESSAGE_TYPE = 28;
|
||||||
@ -987,6 +1031,7 @@ class SubscribeLogsResponse : public ProtoMessage {
|
|||||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||||
};
|
};
|
||||||
|
#ifdef USE_API_NOISE
|
||||||
class NoiseEncryptionSetKeyRequest : public ProtoMessage {
|
class NoiseEncryptionSetKeyRequest : public ProtoMessage {
|
||||||
public:
|
public:
|
||||||
static constexpr uint16_t MESSAGE_TYPE = 124;
|
static constexpr uint16_t MESSAGE_TYPE = 124;
|
||||||
@ -1021,6 +1066,7 @@ class NoiseEncryptionSetKeyResponse : public ProtoMessage {
|
|||||||
protected:
|
protected:
|
||||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||||
};
|
};
|
||||||
|
#endif
|
||||||
class SubscribeHomeassistantServicesRequest : public ProtoMessage {
|
class SubscribeHomeassistantServicesRequest : public ProtoMessage {
|
||||||
public:
|
public:
|
||||||
static constexpr uint16_t MESSAGE_TYPE = 34;
|
static constexpr uint16_t MESSAGE_TYPE = 34;
|
||||||
@ -1226,6 +1272,7 @@ class ExecuteServiceRequest : public ProtoMessage {
|
|||||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||||
};
|
};
|
||||||
|
#ifdef USE_ESP32_CAMERA
|
||||||
class ListEntitiesCameraResponse : public InfoResponseProtoMessage {
|
class ListEntitiesCameraResponse : public InfoResponseProtoMessage {
|
||||||
public:
|
public:
|
||||||
static constexpr uint16_t MESSAGE_TYPE = 43;
|
static constexpr uint16_t MESSAGE_TYPE = 43;
|
||||||
@ -1283,6 +1330,8 @@ class CameraImageRequest : public ProtoMessage {
|
|||||||
protected:
|
protected:
|
||||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||||
};
|
};
|
||||||
|
#endif
|
||||||
|
#ifdef USE_CLIMATE
|
||||||
class ListEntitiesClimateResponse : public InfoResponseProtoMessage {
|
class ListEntitiesClimateResponse : public InfoResponseProtoMessage {
|
||||||
public:
|
public:
|
||||||
static constexpr uint16_t MESSAGE_TYPE = 46;
|
static constexpr uint16_t MESSAGE_TYPE = 46;
|
||||||
@ -1392,6 +1441,8 @@ class ClimateCommandRequest : public ProtoMessage {
|
|||||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||||
};
|
};
|
||||||
|
#endif
|
||||||
|
#ifdef USE_NUMBER
|
||||||
class ListEntitiesNumberResponse : public InfoResponseProtoMessage {
|
class ListEntitiesNumberResponse : public InfoResponseProtoMessage {
|
||||||
public:
|
public:
|
||||||
static constexpr uint16_t MESSAGE_TYPE = 49;
|
static constexpr uint16_t MESSAGE_TYPE = 49;
|
||||||
@ -1453,6 +1504,8 @@ class NumberCommandRequest : public ProtoMessage {
|
|||||||
protected:
|
protected:
|
||||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||||
};
|
};
|
||||||
|
#endif
|
||||||
|
#ifdef USE_SELECT
|
||||||
class ListEntitiesSelectResponse : public InfoResponseProtoMessage {
|
class ListEntitiesSelectResponse : public InfoResponseProtoMessage {
|
||||||
public:
|
public:
|
||||||
static constexpr uint16_t MESSAGE_TYPE = 52;
|
static constexpr uint16_t MESSAGE_TYPE = 52;
|
||||||
@ -1511,6 +1564,8 @@ class SelectCommandRequest : public ProtoMessage {
|
|||||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||||
};
|
};
|
||||||
|
#endif
|
||||||
|
#ifdef USE_SIREN
|
||||||
class ListEntitiesSirenResponse : public InfoResponseProtoMessage {
|
class ListEntitiesSirenResponse : public InfoResponseProtoMessage {
|
||||||
public:
|
public:
|
||||||
static constexpr uint16_t MESSAGE_TYPE = 55;
|
static constexpr uint16_t MESSAGE_TYPE = 55;
|
||||||
@ -1577,6 +1632,8 @@ class SirenCommandRequest : public ProtoMessage {
|
|||||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||||
};
|
};
|
||||||
|
#endif
|
||||||
|
#ifdef USE_LOCK
|
||||||
class ListEntitiesLockResponse : public InfoResponseProtoMessage {
|
class ListEntitiesLockResponse : public InfoResponseProtoMessage {
|
||||||
public:
|
public:
|
||||||
static constexpr uint16_t MESSAGE_TYPE = 58;
|
static constexpr uint16_t MESSAGE_TYPE = 58;
|
||||||
@ -1639,6 +1696,8 @@ class LockCommandRequest : public ProtoMessage {
|
|||||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||||
};
|
};
|
||||||
|
#endif
|
||||||
|
#ifdef USE_BUTTON
|
||||||
class ListEntitiesButtonResponse : public InfoResponseProtoMessage {
|
class ListEntitiesButtonResponse : public InfoResponseProtoMessage {
|
||||||
public:
|
public:
|
||||||
static constexpr uint16_t MESSAGE_TYPE = 61;
|
static constexpr uint16_t MESSAGE_TYPE = 61;
|
||||||
@ -1675,6 +1734,8 @@ class ButtonCommandRequest : public ProtoMessage {
|
|||||||
protected:
|
protected:
|
||||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||||
};
|
};
|
||||||
|
#endif
|
||||||
|
#ifdef USE_MEDIA_PLAYER
|
||||||
class MediaPlayerSupportedFormat : public ProtoMessage {
|
class MediaPlayerSupportedFormat : public ProtoMessage {
|
||||||
public:
|
public:
|
||||||
std::string format{};
|
std::string format{};
|
||||||
@ -1759,6 +1820,8 @@ class MediaPlayerCommandRequest : public ProtoMessage {
|
|||||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||||
};
|
};
|
||||||
|
#endif
|
||||||
|
#ifdef USE_BLUETOOTH_PROXY
|
||||||
class SubscribeBluetoothLEAdvertisementsRequest : public ProtoMessage {
|
class SubscribeBluetoothLEAdvertisementsRequest : public ProtoMessage {
|
||||||
public:
|
public:
|
||||||
static constexpr uint16_t MESSAGE_TYPE = 66;
|
static constexpr uint16_t MESSAGE_TYPE = 66;
|
||||||
@ -2313,6 +2376,8 @@ class BluetoothScannerSetModeRequest : public ProtoMessage {
|
|||||||
protected:
|
protected:
|
||||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||||
};
|
};
|
||||||
|
#endif
|
||||||
|
#ifdef USE_VOICE_ASSISTANT
|
||||||
class SubscribeVoiceAssistantRequest : public ProtoMessage {
|
class SubscribeVoiceAssistantRequest : public ProtoMessage {
|
||||||
public:
|
public:
|
||||||
static constexpr uint16_t MESSAGE_TYPE = 89;
|
static constexpr uint16_t MESSAGE_TYPE = 89;
|
||||||
@ -2562,6 +2627,8 @@ class VoiceAssistantSetConfiguration : public ProtoMessage {
|
|||||||
protected:
|
protected:
|
||||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||||
};
|
};
|
||||||
|
#endif
|
||||||
|
#ifdef USE_ALARM_CONTROL_PANEL
|
||||||
class ListEntitiesAlarmControlPanelResponse : public InfoResponseProtoMessage {
|
class ListEntitiesAlarmControlPanelResponse : public InfoResponseProtoMessage {
|
||||||
public:
|
public:
|
||||||
static constexpr uint16_t MESSAGE_TYPE = 94;
|
static constexpr uint16_t MESSAGE_TYPE = 94;
|
||||||
@ -2622,6 +2689,8 @@ class AlarmControlPanelCommandRequest : public ProtoMessage {
|
|||||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||||
};
|
};
|
||||||
|
#endif
|
||||||
|
#ifdef USE_TEXT
|
||||||
class ListEntitiesTextResponse : public InfoResponseProtoMessage {
|
class ListEntitiesTextResponse : public InfoResponseProtoMessage {
|
||||||
public:
|
public:
|
||||||
static constexpr uint16_t MESSAGE_TYPE = 97;
|
static constexpr uint16_t MESSAGE_TYPE = 97;
|
||||||
@ -2683,6 +2752,8 @@ class TextCommandRequest : public ProtoMessage {
|
|||||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||||
};
|
};
|
||||||
|
#endif
|
||||||
|
#ifdef USE_DATETIME_DATE
|
||||||
class ListEntitiesDateResponse : public InfoResponseProtoMessage {
|
class ListEntitiesDateResponse : public InfoResponseProtoMessage {
|
||||||
public:
|
public:
|
||||||
static constexpr uint16_t MESSAGE_TYPE = 100;
|
static constexpr uint16_t MESSAGE_TYPE = 100;
|
||||||
@ -2743,6 +2814,8 @@ class DateCommandRequest : public ProtoMessage {
|
|||||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||||
};
|
};
|
||||||
|
#endif
|
||||||
|
#ifdef USE_DATETIME_TIME
|
||||||
class ListEntitiesTimeResponse : public InfoResponseProtoMessage {
|
class ListEntitiesTimeResponse : public InfoResponseProtoMessage {
|
||||||
public:
|
public:
|
||||||
static constexpr uint16_t MESSAGE_TYPE = 103;
|
static constexpr uint16_t MESSAGE_TYPE = 103;
|
||||||
@ -2803,6 +2876,8 @@ class TimeCommandRequest : public ProtoMessage {
|
|||||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||||
};
|
};
|
||||||
|
#endif
|
||||||
|
#ifdef USE_EVENT
|
||||||
class ListEntitiesEventResponse : public InfoResponseProtoMessage {
|
class ListEntitiesEventResponse : public InfoResponseProtoMessage {
|
||||||
public:
|
public:
|
||||||
static constexpr uint16_t MESSAGE_TYPE = 107;
|
static constexpr uint16_t MESSAGE_TYPE = 107;
|
||||||
@ -2841,6 +2916,8 @@ class EventResponse : public StateResponseProtoMessage {
|
|||||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||||
};
|
};
|
||||||
|
#endif
|
||||||
|
#ifdef USE_VALVE
|
||||||
class ListEntitiesValveResponse : public InfoResponseProtoMessage {
|
class ListEntitiesValveResponse : public InfoResponseProtoMessage {
|
||||||
public:
|
public:
|
||||||
static constexpr uint16_t MESSAGE_TYPE = 109;
|
static constexpr uint16_t MESSAGE_TYPE = 109;
|
||||||
@ -2903,6 +2980,8 @@ class ValveCommandRequest : public ProtoMessage {
|
|||||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||||
};
|
};
|
||||||
|
#endif
|
||||||
|
#ifdef USE_DATETIME_DATETIME
|
||||||
class ListEntitiesDateTimeResponse : public InfoResponseProtoMessage {
|
class ListEntitiesDateTimeResponse : public InfoResponseProtoMessage {
|
||||||
public:
|
public:
|
||||||
static constexpr uint16_t MESSAGE_TYPE = 112;
|
static constexpr uint16_t MESSAGE_TYPE = 112;
|
||||||
@ -2958,6 +3037,8 @@ class DateTimeCommandRequest : public ProtoMessage {
|
|||||||
protected:
|
protected:
|
||||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||||
};
|
};
|
||||||
|
#endif
|
||||||
|
#ifdef USE_UPDATE
|
||||||
class ListEntitiesUpdateResponse : public InfoResponseProtoMessage {
|
class ListEntitiesUpdateResponse : public InfoResponseProtoMessage {
|
||||||
public:
|
public:
|
||||||
static constexpr uint16_t MESSAGE_TYPE = 116;
|
static constexpr uint16_t MESSAGE_TYPE = 116;
|
||||||
@ -3023,6 +3104,7 @@ class UpdateCommandRequest : public ProtoMessage {
|
|||||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||||
};
|
};
|
||||||
|
#endif
|
||||||
|
|
||||||
} // namespace api
|
} // namespace api
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
4228
esphome/components/api/api_pb2_dump.cpp
Normal file
4228
esphome/components/api/api_pb2_dump.cpp
Normal file
File diff suppressed because it is too large
Load Diff
@ -2,9 +2,10 @@
|
|||||||
// See script/api_protobuf/api_protobuf.py
|
// See script/api_protobuf/api_protobuf.py
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "api_pb2.h"
|
|
||||||
#include "esphome/core/defines.h"
|
#include "esphome/core/defines.h"
|
||||||
|
|
||||||
|
#include "api_pb2.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace api {
|
namespace api {
|
||||||
|
|
||||||
|
@ -217,6 +217,7 @@ void APIServer::dump_config() {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef USE_API_PASSWORD
|
||||||
bool APIServer::uses_password() const { return !this->password_.empty(); }
|
bool APIServer::uses_password() const { return !this->password_.empty(); }
|
||||||
|
|
||||||
bool APIServer::check_password(const std::string &password) const {
|
bool APIServer::check_password(const std::string &password) const {
|
||||||
@ -247,6 +248,7 @@ bool APIServer::check_password(const std::string &password) const {
|
|||||||
|
|
||||||
return result == 0;
|
return result == 0;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
void APIServer::handle_disconnect(APIConnection *conn) {}
|
void APIServer::handle_disconnect(APIConnection *conn) {}
|
||||||
|
|
||||||
@ -430,7 +432,9 @@ float APIServer::get_setup_priority() const { return setup_priority::AFTER_WIFI;
|
|||||||
|
|
||||||
void APIServer::set_port(uint16_t port) { this->port_ = port; }
|
void APIServer::set_port(uint16_t port) { this->port_ = port; }
|
||||||
|
|
||||||
|
#ifdef USE_API_PASSWORD
|
||||||
void APIServer::set_password(const std::string &password) { this->password_ = password; }
|
void APIServer::set_password(const std::string &password) { this->password_ = password; }
|
||||||
|
#endif
|
||||||
|
|
||||||
void APIServer::set_batch_delay(uint16_t batch_delay) { this->batch_delay_ = batch_delay; }
|
void APIServer::set_batch_delay(uint16_t batch_delay) { this->batch_delay_ = batch_delay; }
|
||||||
|
|
||||||
|
@ -35,10 +35,12 @@ class APIServer : public Component, public Controller {
|
|||||||
void dump_config() override;
|
void dump_config() override;
|
||||||
void on_shutdown() override;
|
void on_shutdown() override;
|
||||||
bool teardown() override;
|
bool teardown() override;
|
||||||
|
#ifdef USE_API_PASSWORD
|
||||||
bool check_password(const std::string &password) const;
|
bool check_password(const std::string &password) const;
|
||||||
bool uses_password() const;
|
bool uses_password() const;
|
||||||
void set_port(uint16_t port);
|
|
||||||
void set_password(const std::string &password);
|
void set_password(const std::string &password);
|
||||||
|
#endif
|
||||||
|
void set_port(uint16_t port);
|
||||||
void set_reboot_timeout(uint32_t reboot_timeout);
|
void set_reboot_timeout(uint32_t reboot_timeout);
|
||||||
void set_batch_delay(uint16_t batch_delay);
|
void set_batch_delay(uint16_t batch_delay);
|
||||||
uint16_t get_batch_delay() const { return batch_delay_; }
|
uint16_t get_batch_delay() const { return batch_delay_; }
|
||||||
@ -179,7 +181,9 @@ class APIServer : public Component, public Controller {
|
|||||||
|
|
||||||
// Vectors and strings (12 bytes each on 32-bit)
|
// Vectors and strings (12 bytes each on 32-bit)
|
||||||
std::vector<std::unique_ptr<APIConnection>> clients_;
|
std::vector<std::unique_ptr<APIConnection>> clients_;
|
||||||
|
#ifdef USE_API_PASSWORD
|
||||||
std::string password_;
|
std::string password_;
|
||||||
|
#endif
|
||||||
std::vector<uint8_t> shared_write_buffer_; // Shared proto write buffer for all connections
|
std::vector<uint8_t> shared_write_buffer_; // Shared proto write buffer for all connections
|
||||||
std::vector<HomeAssistantStateSubscription> state_subs_;
|
std::vector<HomeAssistantStateSubscription> state_subs_;
|
||||||
#ifdef USE_API_YAML_SERVICES
|
#ifdef USE_API_YAML_SERVICES
|
||||||
|
@ -12,7 +12,7 @@ from esphome.const import (
|
|||||||
)
|
)
|
||||||
from esphome.core import CORE, coroutine_with_priority
|
from esphome.core import CORE, coroutine_with_priority
|
||||||
|
|
||||||
AUTO_LOAD = ["web_server_base"]
|
AUTO_LOAD = ["web_server_base", "ota.web_server"]
|
||||||
DEPENDENCIES = ["wifi"]
|
DEPENDENCIES = ["wifi"]
|
||||||
CODEOWNERS = ["@OttoWinter"]
|
CODEOWNERS = ["@OttoWinter"]
|
||||||
|
|
||||||
|
@ -47,7 +47,6 @@ void CaptivePortal::start() {
|
|||||||
this->base_->init();
|
this->base_->init();
|
||||||
if (!this->initialized_) {
|
if (!this->initialized_) {
|
||||||
this->base_->add_handler(this);
|
this->base_->add_handler(this);
|
||||||
this->base_->add_ota_handler();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef USE_ARDUINO
|
#ifdef USE_ARDUINO
|
||||||
|
1
esphome/components/ds2484/__init__.py
Normal file
1
esphome/components/ds2484/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
CODEOWNERS = ["@mrk-its"]
|
209
esphome/components/ds2484/ds2484.cpp
Normal file
209
esphome/components/ds2484/ds2484.cpp
Normal file
@ -0,0 +1,209 @@
|
|||||||
|
#include "ds2484.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace ds2484 {
|
||||||
|
static const char *const TAG = "ds2484.onewire";
|
||||||
|
|
||||||
|
void DS2484OneWireBus::setup() {
|
||||||
|
ESP_LOGCONFIG(TAG, "Running setup");
|
||||||
|
this->reset_device();
|
||||||
|
this->search();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DS2484OneWireBus::dump_config() {
|
||||||
|
ESP_LOGCONFIG(TAG, "1-wire bus:");
|
||||||
|
this->dump_devices_(TAG);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DS2484OneWireBus::read_status_(uint8_t *status) {
|
||||||
|
for (uint8_t retry_nr = 0; retry_nr < 10; retry_nr++) {
|
||||||
|
if (this->read(status, 1) != i2c::ERROR_OK) {
|
||||||
|
ESP_LOGE(TAG, "read status error");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ESP_LOGVV(TAG, "status: %02x", *status);
|
||||||
|
if (!(*status & 1)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ESP_LOGE(TAG, "read status error: too many retries");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DS2484OneWireBus::wait_for_completion_() {
|
||||||
|
uint8_t status;
|
||||||
|
return this->read_status_(&status);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DS2484OneWireBus::reset_device() {
|
||||||
|
ESP_LOGVV(TAG, "reset_device");
|
||||||
|
uint8_t device_reset_cmd = 0xf0;
|
||||||
|
uint8_t response;
|
||||||
|
if (this->write(&device_reset_cmd, 1) != i2c::ERROR_OK) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!this->wait_for_completion_()) {
|
||||||
|
ESP_LOGE(TAG, "reset_device: can't complete");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
uint8_t config = (this->active_pullup_ ? 1 : 0) | (this->strong_pullup_ ? 4 : 0);
|
||||||
|
uint8_t write_config[2] = {0xd2, (uint8_t) (config | (~config << 4))};
|
||||||
|
if (this->write(write_config, 2) != i2c::ERROR_OK) {
|
||||||
|
ESP_LOGE(TAG, "reset_device: can't write config");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (this->read(&response, 1) != i2c::ERROR_OK) {
|
||||||
|
ESP_LOGE(TAG, "can't read read8 response");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (response != (write_config[1] & 0xf)) {
|
||||||
|
ESP_LOGE(TAG, "configuration didn't update");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
int DS2484OneWireBus::reset_int() {
|
||||||
|
ESP_LOGVV(TAG, "reset");
|
||||||
|
uint8_t reset_cmd = 0xb4;
|
||||||
|
if (this->write(&reset_cmd, 1) != i2c::ERROR_OK) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return this->wait_for_completion_() ? 1 : 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
void DS2484OneWireBus::write8_(uint8_t value) {
|
||||||
|
uint8_t buffer[2] = {0xa5, value};
|
||||||
|
this->write(buffer, 2);
|
||||||
|
this->wait_for_completion_();
|
||||||
|
};
|
||||||
|
|
||||||
|
void DS2484OneWireBus::write8(uint8_t value) {
|
||||||
|
ESP_LOGVV(TAG, "write8: %02x", value);
|
||||||
|
this->write8_(value);
|
||||||
|
};
|
||||||
|
|
||||||
|
void DS2484OneWireBus::write64(uint64_t value) {
|
||||||
|
ESP_LOGVV(TAG, "write64: %llx", value);
|
||||||
|
for (uint8_t i = 0; i < 8; i++) {
|
||||||
|
this->write8_((value >> (i * 8)) & 0xff);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t DS2484OneWireBus::read8() {
|
||||||
|
uint8_t read8_cmd = 0x96;
|
||||||
|
uint8_t set_read_reg_cmd[2] = {0xe1, 0xe1};
|
||||||
|
uint8_t response = 0;
|
||||||
|
if (this->write(&read8_cmd, 1) != i2c::ERROR_OK) {
|
||||||
|
ESP_LOGE(TAG, "can't write read8 cmd");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
this->wait_for_completion_();
|
||||||
|
if (this->write(set_read_reg_cmd, 2) != i2c::ERROR_OK) {
|
||||||
|
ESP_LOGE(TAG, "can't set read data reg");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (this->read(&response, 1) != i2c::ERROR_OK) {
|
||||||
|
ESP_LOGE(TAG, "can't read read8 response");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t DS2484OneWireBus::read64() {
|
||||||
|
uint8_t response = 0;
|
||||||
|
for (uint8_t i = 0; i < 8; i++) {
|
||||||
|
response |= (this->read8() << (i * 8));
|
||||||
|
}
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DS2484OneWireBus::reset_search() {
|
||||||
|
this->last_discrepancy_ = 0;
|
||||||
|
this->last_device_flag_ = false;
|
||||||
|
this->address_ = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DS2484OneWireBus::one_wire_triple_(bool *branch, bool *id_bit, bool *cmp_id_bit) {
|
||||||
|
uint8_t buffer[2] = {(uint8_t) 0x78, (uint8_t) (*branch ? 0x80u : 0)};
|
||||||
|
uint8_t status;
|
||||||
|
if (!this->read_status_(&status)) {
|
||||||
|
ESP_LOGE(TAG, "one_wire_triple start: read status error");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (this->write(buffer, 2) != i2c::ERROR_OK) {
|
||||||
|
ESP_LOGV(TAG, "one_wire_triple: can't write cmd");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!this->read_status_(&status)) {
|
||||||
|
ESP_LOGE(TAG, "one_wire_triple: read status error");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
*id_bit = bool(status & 0x20);
|
||||||
|
*cmp_id_bit = bool(status & 0x40);
|
||||||
|
*branch = bool(status & 0x80);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t IRAM_ATTR DS2484OneWireBus::search_int() {
|
||||||
|
ESP_LOGVV(TAG, "search_int");
|
||||||
|
if (this->last_device_flag_) {
|
||||||
|
ESP_LOGVV(TAG, "last device flag set, quitting");
|
||||||
|
return 0u;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t last_zero = 0;
|
||||||
|
uint64_t bit_mask = 1;
|
||||||
|
uint64_t address = this->address_;
|
||||||
|
|
||||||
|
// Initiate search
|
||||||
|
for (uint8_t bit_number = 1; bit_number <= 64; bit_number++, bit_mask <<= 1) {
|
||||||
|
bool branch;
|
||||||
|
|
||||||
|
// compute branch value for the case when there is a discrepancy
|
||||||
|
// (there are devices with both 0s and 1s at this bit)
|
||||||
|
if (bit_number < this->last_discrepancy_) {
|
||||||
|
branch = (address & bit_mask) > 0;
|
||||||
|
} else {
|
||||||
|
branch = bit_number == this->last_discrepancy_;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool id_bit, cmp_id_bit;
|
||||||
|
bool branch_before = branch;
|
||||||
|
if (!this->one_wire_triple_(&branch, &id_bit, &cmp_id_bit)) {
|
||||||
|
ESP_LOGW(TAG, "one wire triple error, quitting");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (id_bit && cmp_id_bit) {
|
||||||
|
ESP_LOGW(TAG, "no devices on the bus, quitting");
|
||||||
|
// No devices participating in search
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!id_bit && !cmp_id_bit && !branch) {
|
||||||
|
last_zero = bit_number;
|
||||||
|
}
|
||||||
|
|
||||||
|
ESP_LOGVV(TAG, "%d %d branch: %d %d", id_bit, cmp_id_bit, branch_before, branch);
|
||||||
|
|
||||||
|
if (branch) {
|
||||||
|
address |= bit_mask;
|
||||||
|
} else {
|
||||||
|
address &= ~bit_mask;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ESP_LOGVV(TAG, "last_discepancy: %d", last_zero);
|
||||||
|
ESP_LOGVV(TAG, "address: %llx", address);
|
||||||
|
this->last_discrepancy_ = last_zero;
|
||||||
|
if (this->last_discrepancy_ == 0) {
|
||||||
|
// we're at root and have no choices left, so this was the last one.
|
||||||
|
this->last_device_flag_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
this->address_ = address;
|
||||||
|
return address;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace ds2484
|
||||||
|
} // namespace esphome
|
43
esphome/components/ds2484/ds2484.h
Normal file
43
esphome/components/ds2484/ds2484.h
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
#include "esphome/core/hal.h"
|
||||||
|
#include "esphome/core/preferences.h"
|
||||||
|
#include "esphome/components/i2c/i2c.h"
|
||||||
|
#include "esphome/components/one_wire/one_wire.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace ds2484 {
|
||||||
|
|
||||||
|
class DS2484OneWireBus : public one_wire::OneWireBus, public i2c::I2CDevice, public Component {
|
||||||
|
public:
|
||||||
|
void setup() override;
|
||||||
|
void dump_config() override;
|
||||||
|
float get_setup_priority() const override { return setup_priority::BUS - 1.0; }
|
||||||
|
|
||||||
|
bool reset_device();
|
||||||
|
int reset_int() override;
|
||||||
|
void write8(uint8_t) override;
|
||||||
|
void write64(uint64_t) override;
|
||||||
|
uint8_t read8() override;
|
||||||
|
uint64_t read64() override;
|
||||||
|
|
||||||
|
void set_active_pullup(bool value) { this->active_pullup_ = value; }
|
||||||
|
void set_strong_pullup(bool value) { this->strong_pullup_ = value; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void reset_search() override;
|
||||||
|
uint64_t search_int() override;
|
||||||
|
bool read_status_(uint8_t *);
|
||||||
|
bool wait_for_completion_();
|
||||||
|
void write8_(uint8_t);
|
||||||
|
bool one_wire_triple_(bool *branch, bool *id_bit, bool *cmp_id_bit);
|
||||||
|
|
||||||
|
uint64_t address_;
|
||||||
|
uint8_t last_discrepancy_{0};
|
||||||
|
bool last_device_flag_{false};
|
||||||
|
bool active_pullup_{false};
|
||||||
|
bool strong_pullup_{false};
|
||||||
|
};
|
||||||
|
} // namespace ds2484
|
||||||
|
} // namespace esphome
|
37
esphome/components/ds2484/one_wire.py
Normal file
37
esphome/components/ds2484/one_wire.py
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import esphome.codegen as cg
|
||||||
|
from esphome.components import i2c
|
||||||
|
from esphome.components.one_wire import OneWireBus
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
from esphome.const import CONF_ID
|
||||||
|
|
||||||
|
ds2484_ns = cg.esphome_ns.namespace("ds2484")
|
||||||
|
|
||||||
|
CONF_ACTIVE_PULLUP = "active_pullup"
|
||||||
|
CONF_STRONG_PULLUP = "strong_pullup"
|
||||||
|
|
||||||
|
CODEOWNERS = ["@mrk-its"]
|
||||||
|
DEPENDENCIES = ["i2c"]
|
||||||
|
|
||||||
|
DS2484OneWireBus = ds2484_ns.class_(
|
||||||
|
"DS2484OneWireBus", OneWireBus, i2c.I2CDevice, cg.Component
|
||||||
|
)
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = (
|
||||||
|
cv.Schema(
|
||||||
|
{
|
||||||
|
cv.GenerateID(): cv.declare_id(DS2484OneWireBus),
|
||||||
|
cv.Optional(CONF_ACTIVE_PULLUP, default=False): cv.boolean,
|
||||||
|
cv.Optional(CONF_STRONG_PULLUP, default=False): cv.boolean,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.extend(cv.COMPONENT_SCHEMA)
|
||||||
|
.extend(i2c.i2c_device_schema(0x18))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def to_code(config):
|
||||||
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
|
await i2c.register_i2c_device(var, config)
|
||||||
|
await cg.register_component(var, config)
|
||||||
|
cg.add(var.set_active_pullup(config[CONF_ACTIVE_PULLUP]))
|
||||||
|
cg.add(var.set_strong_pullup(config[CONF_STRONG_PULLUP]))
|
@ -411,8 +411,8 @@ def _esp_idf_check_versions(value):
|
|||||||
version = cv.Version.parse(cv.version_number(value[CONF_VERSION]))
|
version = cv.Version.parse(cv.version_number(value[CONF_VERSION]))
|
||||||
source = value.get(CONF_SOURCE, None)
|
source = value.get(CONF_SOURCE, None)
|
||||||
|
|
||||||
if version < cv.Version(4, 0, 0):
|
if version < cv.Version(5, 0, 0):
|
||||||
raise cv.Invalid("Only ESP-IDF 4.0+ is supported.")
|
raise cv.Invalid("Only ESP-IDF 5.0+ is supported.")
|
||||||
|
|
||||||
# flag this for later *before* we set value[CONF_PLATFORM_VERSION] below
|
# flag this for later *before* we set value[CONF_PLATFORM_VERSION] below
|
||||||
has_platform_ver = CONF_PLATFORM_VERSION in value
|
has_platform_ver = CONF_PLATFORM_VERSION in value
|
||||||
@ -422,20 +422,15 @@ def _esp_idf_check_versions(value):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if (
|
if (
|
||||||
(is_platformio := _platform_is_platformio(value[CONF_PLATFORM_VERSION]))
|
is_platformio := _platform_is_platformio(value[CONF_PLATFORM_VERSION])
|
||||||
and version.major >= 5
|
) and version not in SUPPORTED_PLATFORMIO_ESP_IDF_5X:
|
||||||
and version not in SUPPORTED_PLATFORMIO_ESP_IDF_5X
|
|
||||||
):
|
|
||||||
raise cv.Invalid(
|
raise cv.Invalid(
|
||||||
f"ESP-IDF {str(version)} not supported by platformio/espressif32"
|
f"ESP-IDF {str(version)} not supported by platformio/espressif32"
|
||||||
)
|
)
|
||||||
|
|
||||||
if (
|
if (
|
||||||
version.major < 5
|
version in SUPPORTED_PLATFORMIO_ESP_IDF_5X
|
||||||
or (
|
and version not in SUPPORTED_PIOARDUINO_ESP_IDF_5X
|
||||||
version in SUPPORTED_PLATFORMIO_ESP_IDF_5X
|
|
||||||
and version not in SUPPORTED_PIOARDUINO_ESP_IDF_5X
|
|
||||||
)
|
|
||||||
) and not has_platform_ver:
|
) and not has_platform_ver:
|
||||||
raise cv.Invalid(
|
raise cv.Invalid(
|
||||||
f"ESP-IDF {value[CONF_VERSION]} may be supported by platformio/espressif32; please specify '{CONF_PLATFORM_VERSION}'"
|
f"ESP-IDF {value[CONF_VERSION]} may be supported by platformio/espressif32; please specify '{CONF_PLATFORM_VERSION}'"
|
||||||
@ -801,14 +796,9 @@ async def to_code(config):
|
|||||||
|
|
||||||
if advanced.get(CONF_IGNORE_EFUSE_MAC_CRC):
|
if advanced.get(CONF_IGNORE_EFUSE_MAC_CRC):
|
||||||
add_idf_sdkconfig_option("CONFIG_ESP_MAC_IGNORE_MAC_CRC_ERROR", True)
|
add_idf_sdkconfig_option("CONFIG_ESP_MAC_IGNORE_MAC_CRC_ERROR", True)
|
||||||
if (framework_ver.major, framework_ver.minor) >= (4, 4):
|
add_idf_sdkconfig_option(
|
||||||
add_idf_sdkconfig_option(
|
"CONFIG_ESP_PHY_CALIBRATION_AND_DATA_STORAGE", False
|
||||||
"CONFIG_ESP_PHY_CALIBRATION_AND_DATA_STORAGE", False
|
)
|
||||||
)
|
|
||||||
else:
|
|
||||||
add_idf_sdkconfig_option(
|
|
||||||
"CONFIG_ESP32_PHY_CALIBRATION_AND_DATA_STORAGE", False
|
|
||||||
)
|
|
||||||
if advanced.get(CONF_ENABLE_IDF_EXPERIMENTAL_FEATURES):
|
if advanced.get(CONF_ENABLE_IDF_EXPERIMENTAL_FEATURES):
|
||||||
_LOGGER.warning(
|
_LOGGER.warning(
|
||||||
"Using experimental features in ESP-IDF may result in unexpected failures."
|
"Using experimental features in ESP-IDF may result in unexpected failures."
|
||||||
|
@ -56,11 +56,7 @@ void arch_init() {
|
|||||||
void IRAM_ATTR HOT arch_feed_wdt() { esp_task_wdt_reset(); }
|
void IRAM_ATTR HOT arch_feed_wdt() { esp_task_wdt_reset(); }
|
||||||
|
|
||||||
uint8_t progmem_read_byte(const uint8_t *addr) { return *addr; }
|
uint8_t progmem_read_byte(const uint8_t *addr) { return *addr; }
|
||||||
#if ESP_IDF_VERSION_MAJOR >= 5
|
|
||||||
uint32_t arch_get_cpu_cycle_count() { return esp_cpu_get_cycle_count(); }
|
uint32_t arch_get_cpu_cycle_count() { return esp_cpu_get_cycle_count(); }
|
||||||
#else
|
|
||||||
uint32_t arch_get_cpu_cycle_count() { return cpu_hal_get_cycle_count(); }
|
|
||||||
#endif
|
|
||||||
uint32_t arch_get_cpu_freq_hz() {
|
uint32_t arch_get_cpu_freq_hz() {
|
||||||
uint32_t freq = 0;
|
uint32_t freq = 0;
|
||||||
#ifdef USE_ESP_IDF
|
#ifdef USE_ESP_IDF
|
||||||
|
@ -29,8 +29,6 @@ from esphome.const import (
|
|||||||
CONF_ON_BLE_SERVICE_DATA_ADVERTISE,
|
CONF_ON_BLE_SERVICE_DATA_ADVERTISE,
|
||||||
CONF_SERVICE_UUID,
|
CONF_SERVICE_UUID,
|
||||||
CONF_TRIGGER_ID,
|
CONF_TRIGGER_ID,
|
||||||
KEY_CORE,
|
|
||||||
KEY_FRAMEWORK_VERSION,
|
|
||||||
)
|
)
|
||||||
from esphome.core import CORE
|
from esphome.core import CORE
|
||||||
|
|
||||||
@ -323,10 +321,7 @@ async def to_code(config):
|
|||||||
# https://github.com/espressif/esp-idf/issues/2503
|
# https://github.com/espressif/esp-idf/issues/2503
|
||||||
# Match arduino CONFIG_BTU_TASK_STACK_SIZE
|
# Match arduino CONFIG_BTU_TASK_STACK_SIZE
|
||||||
# https://github.com/espressif/arduino-esp32/blob/fd72cf46ad6fc1a6de99c1d83ba8eba17d80a4ee/tools/sdk/esp32/sdkconfig#L1866
|
# https://github.com/espressif/arduino-esp32/blob/fd72cf46ad6fc1a6de99c1d83ba8eba17d80a4ee/tools/sdk/esp32/sdkconfig#L1866
|
||||||
if CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] >= cv.Version(4, 4, 6):
|
add_idf_sdkconfig_option("CONFIG_BT_BTU_TASK_STACK_SIZE", 8192)
|
||||||
add_idf_sdkconfig_option("CONFIG_BT_BTU_TASK_STACK_SIZE", 8192)
|
|
||||||
else:
|
|
||||||
add_idf_sdkconfig_option("CONFIG_BTU_TASK_STACK_SIZE", 8192)
|
|
||||||
add_idf_sdkconfig_option("CONFIG_BT_ACL_CONNECTIONS", 9)
|
add_idf_sdkconfig_option("CONFIG_BT_ACL_CONNECTIONS", 9)
|
||||||
add_idf_sdkconfig_option(
|
add_idf_sdkconfig_option(
|
||||||
"CONFIG_BTDM_CTRL_BLE_MAX_CONN", config[CONF_MAX_CONNECTIONS]
|
"CONFIG_BTDM_CTRL_BLE_MAX_CONN", config[CONF_MAX_CONNECTIONS]
|
||||||
@ -335,8 +330,7 @@ async def to_code(config):
|
|||||||
# max notifications in 5.x, setting CONFIG_BT_ACL_CONNECTIONS
|
# max notifications in 5.x, setting CONFIG_BT_ACL_CONNECTIONS
|
||||||
# is enough in 4.x
|
# is enough in 4.x
|
||||||
# https://github.com/esphome/issues/issues/6808
|
# https://github.com/esphome/issues/issues/6808
|
||||||
if CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] >= cv.Version(5, 0, 0):
|
add_idf_sdkconfig_option("CONFIG_BT_GATTC_NOTIF_REG_MAX", 9)
|
||||||
add_idf_sdkconfig_option("CONFIG_BT_GATTC_NOTIF_REG_MAX", 9)
|
|
||||||
|
|
||||||
cg.add_define("USE_OTA_STATE_CALLBACK") # To be notified when an OTA update starts
|
cg.add_define("USE_OTA_STATE_CALLBACK") # To be notified when an OTA update starts
|
||||||
cg.add_define("USE_ESP32_BLE_CLIENT")
|
cg.add_define("USE_ESP32_BLE_CLIENT")
|
||||||
|
@ -21,6 +21,43 @@ static const uint32_t RMT_CLK_FREQ = 80000000;
|
|||||||
static const uint8_t RMT_CLK_DIV = 2;
|
static const uint8_t RMT_CLK_DIV = 2;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
static const size_t RMT_SYMBOLS_PER_BYTE = 8;
|
||||||
|
|
||||||
|
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0)
|
||||||
|
static size_t IRAM_ATTR HOT encoder_callback(const void *data, size_t size, size_t symbols_written, size_t symbols_free,
|
||||||
|
rmt_symbol_word_t *symbols, bool *done, void *arg) {
|
||||||
|
auto *params = static_cast<LedParams *>(arg);
|
||||||
|
const auto *bytes = static_cast<const uint8_t *>(data);
|
||||||
|
size_t index = symbols_written / RMT_SYMBOLS_PER_BYTE;
|
||||||
|
|
||||||
|
// convert byte to symbols
|
||||||
|
if (index < size) {
|
||||||
|
if (symbols_free < RMT_SYMBOLS_PER_BYTE) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
for (int32_t i = 0; i < RMT_SYMBOLS_PER_BYTE; i++) {
|
||||||
|
if (bytes[index] & (1 << (7 - i))) {
|
||||||
|
symbols[i] = params->bit1;
|
||||||
|
} else {
|
||||||
|
symbols[i] = params->bit0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ((index + 1) >= size && params->reset.duration0 == 0 && params->reset.duration1 == 0) {
|
||||||
|
*done = true;
|
||||||
|
}
|
||||||
|
return RMT_SYMBOLS_PER_BYTE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// send reset
|
||||||
|
if (symbols_free < 1) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
symbols[0] = params->reset;
|
||||||
|
*done = true;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
void ESP32RMTLEDStripLightOutput::setup() {
|
void ESP32RMTLEDStripLightOutput::setup() {
|
||||||
ESP_LOGCONFIG(TAG, "Running setup");
|
ESP_LOGCONFIG(TAG, "Running setup");
|
||||||
|
|
||||||
@ -42,10 +79,15 @@ void ESP32RMTLEDStripLightOutput::setup() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0)
|
||||||
|
// copy of the led buffer
|
||||||
|
this->rmt_buf_ = allocator.allocate(buffer_size);
|
||||||
|
#else
|
||||||
RAMAllocator<rmt_symbol_word_t> rmt_allocator(this->use_psram_ ? 0 : RAMAllocator<rmt_symbol_word_t>::ALLOC_INTERNAL);
|
RAMAllocator<rmt_symbol_word_t> rmt_allocator(this->use_psram_ ? 0 : RAMAllocator<rmt_symbol_word_t>::ALLOC_INTERNAL);
|
||||||
|
|
||||||
// 8 bits per byte, 1 rmt_symbol_word_t per bit + 1 rmt_symbol_word_t for reset
|
// 8 bits per byte, 1 rmt_symbol_word_t per bit + 1 rmt_symbol_word_t for reset
|
||||||
this->rmt_buf_ = rmt_allocator.allocate(buffer_size * 8 + 1);
|
this->rmt_buf_ = rmt_allocator.allocate(buffer_size * 8 + 1);
|
||||||
|
#endif
|
||||||
|
|
||||||
rmt_tx_channel_config_t channel;
|
rmt_tx_channel_config_t channel;
|
||||||
memset(&channel, 0, sizeof(channel));
|
memset(&channel, 0, sizeof(channel));
|
||||||
@ -65,6 +107,18 @@ void ESP32RMTLEDStripLightOutput::setup() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0)
|
||||||
|
rmt_simple_encoder_config_t encoder;
|
||||||
|
memset(&encoder, 0, sizeof(encoder));
|
||||||
|
encoder.callback = encoder_callback;
|
||||||
|
encoder.arg = &this->params_;
|
||||||
|
encoder.min_chunk_size = 8;
|
||||||
|
if (rmt_new_simple_encoder(&encoder, &this->encoder_) != ESP_OK) {
|
||||||
|
ESP_LOGE(TAG, "Encoder creation failed");
|
||||||
|
this->mark_failed();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#else
|
||||||
rmt_copy_encoder_config_t encoder;
|
rmt_copy_encoder_config_t encoder;
|
||||||
memset(&encoder, 0, sizeof(encoder));
|
memset(&encoder, 0, sizeof(encoder));
|
||||||
if (rmt_new_copy_encoder(&encoder, &this->encoder_) != ESP_OK) {
|
if (rmt_new_copy_encoder(&encoder, &this->encoder_) != ESP_OK) {
|
||||||
@ -72,6 +126,7 @@ void ESP32RMTLEDStripLightOutput::setup() {
|
|||||||
this->mark_failed();
|
this->mark_failed();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
if (rmt_enable(this->channel_) != ESP_OK) {
|
if (rmt_enable(this->channel_) != ESP_OK) {
|
||||||
ESP_LOGE(TAG, "Enabling channel failed");
|
ESP_LOGE(TAG, "Enabling channel failed");
|
||||||
@ -85,20 +140,20 @@ void ESP32RMTLEDStripLightOutput::set_led_params(uint32_t bit0_high, uint32_t bi
|
|||||||
float ratio = (float) RMT_CLK_FREQ / RMT_CLK_DIV / 1e09f;
|
float ratio = (float) RMT_CLK_FREQ / RMT_CLK_DIV / 1e09f;
|
||||||
|
|
||||||
// 0-bit
|
// 0-bit
|
||||||
this->bit0_.duration0 = (uint32_t) (ratio * bit0_high);
|
this->params_.bit0.duration0 = (uint32_t) (ratio * bit0_high);
|
||||||
this->bit0_.level0 = 1;
|
this->params_.bit0.level0 = 1;
|
||||||
this->bit0_.duration1 = (uint32_t) (ratio * bit0_low);
|
this->params_.bit0.duration1 = (uint32_t) (ratio * bit0_low);
|
||||||
this->bit0_.level1 = 0;
|
this->params_.bit0.level1 = 0;
|
||||||
// 1-bit
|
// 1-bit
|
||||||
this->bit1_.duration0 = (uint32_t) (ratio * bit1_high);
|
this->params_.bit1.duration0 = (uint32_t) (ratio * bit1_high);
|
||||||
this->bit1_.level0 = 1;
|
this->params_.bit1.level0 = 1;
|
||||||
this->bit1_.duration1 = (uint32_t) (ratio * bit1_low);
|
this->params_.bit1.duration1 = (uint32_t) (ratio * bit1_low);
|
||||||
this->bit1_.level1 = 0;
|
this->params_.bit1.level1 = 0;
|
||||||
// reset
|
// reset
|
||||||
this->reset_.duration0 = (uint32_t) (ratio * reset_time_high);
|
this->params_.reset.duration0 = (uint32_t) (ratio * reset_time_high);
|
||||||
this->reset_.level0 = 1;
|
this->params_.reset.level0 = 1;
|
||||||
this->reset_.duration1 = (uint32_t) (ratio * reset_time_low);
|
this->params_.reset.duration1 = (uint32_t) (ratio * reset_time_low);
|
||||||
this->reset_.level1 = 0;
|
this->params_.reset.level1 = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ESP32RMTLEDStripLightOutput::write_state(light::LightState *state) {
|
void ESP32RMTLEDStripLightOutput::write_state(light::LightState *state) {
|
||||||
@ -122,6 +177,9 @@ void ESP32RMTLEDStripLightOutput::write_state(light::LightState *state) {
|
|||||||
}
|
}
|
||||||
delayMicroseconds(50);
|
delayMicroseconds(50);
|
||||||
|
|
||||||
|
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0)
|
||||||
|
memcpy(this->rmt_buf_, this->buf_, this->get_buffer_size_());
|
||||||
|
#else
|
||||||
size_t buffer_size = this->get_buffer_size_();
|
size_t buffer_size = this->get_buffer_size_();
|
||||||
|
|
||||||
size_t size = 0;
|
size_t size = 0;
|
||||||
@ -131,7 +189,7 @@ void ESP32RMTLEDStripLightOutput::write_state(light::LightState *state) {
|
|||||||
while (size < buffer_size) {
|
while (size < buffer_size) {
|
||||||
uint8_t b = *psrc;
|
uint8_t b = *psrc;
|
||||||
for (int i = 0; i < 8; i++) {
|
for (int i = 0; i < 8; i++) {
|
||||||
pdest->val = b & (1 << (7 - i)) ? this->bit1_.val : this->bit0_.val;
|
pdest->val = b & (1 << (7 - i)) ? this->params_.bit1.val : this->params_.bit0.val;
|
||||||
pdest++;
|
pdest++;
|
||||||
len++;
|
len++;
|
||||||
}
|
}
|
||||||
@ -139,17 +197,20 @@ void ESP32RMTLEDStripLightOutput::write_state(light::LightState *state) {
|
|||||||
psrc++;
|
psrc++;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this->reset_.duration0 > 0 || this->reset_.duration1 > 0) {
|
if (this->params_.reset.duration0 > 0 || this->params_.reset.duration1 > 0) {
|
||||||
pdest->val = this->reset_.val;
|
pdest->val = this->params_.reset.val;
|
||||||
pdest++;
|
pdest++;
|
||||||
len++;
|
len++;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
rmt_transmit_config_t config;
|
rmt_transmit_config_t config;
|
||||||
memset(&config, 0, sizeof(config));
|
memset(&config, 0, sizeof(config));
|
||||||
config.loop_count = 0;
|
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0)
|
||||||
config.flags.eot_level = 0;
|
error = rmt_transmit(this->channel_, this->encoder_, this->rmt_buf_, this->get_buffer_size_(), &config);
|
||||||
|
#else
|
||||||
error = rmt_transmit(this->channel_, this->encoder_, this->rmt_buf_, len * sizeof(rmt_symbol_word_t), &config);
|
error = rmt_transmit(this->channel_, this->encoder_, this->rmt_buf_, len * sizeof(rmt_symbol_word_t), &config);
|
||||||
|
#endif
|
||||||
if (error != ESP_OK) {
|
if (error != ESP_OK) {
|
||||||
ESP_LOGE(TAG, "RMT TX error");
|
ESP_LOGE(TAG, "RMT TX error");
|
||||||
this->status_set_warning();
|
this->status_set_warning();
|
||||||
|
@ -25,6 +25,12 @@ enum RGBOrder : uint8_t {
|
|||||||
ORDER_BRG,
|
ORDER_BRG,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct LedParams {
|
||||||
|
rmt_symbol_word_t bit0;
|
||||||
|
rmt_symbol_word_t bit1;
|
||||||
|
rmt_symbol_word_t reset;
|
||||||
|
};
|
||||||
|
|
||||||
class ESP32RMTLEDStripLightOutput : public light::AddressableLight {
|
class ESP32RMTLEDStripLightOutput : public light::AddressableLight {
|
||||||
public:
|
public:
|
||||||
void setup() override;
|
void setup() override;
|
||||||
@ -72,12 +78,15 @@ class ESP32RMTLEDStripLightOutput : public light::AddressableLight {
|
|||||||
|
|
||||||
uint8_t *buf_{nullptr};
|
uint8_t *buf_{nullptr};
|
||||||
uint8_t *effect_data_{nullptr};
|
uint8_t *effect_data_{nullptr};
|
||||||
|
LedParams params_;
|
||||||
rmt_channel_handle_t channel_{nullptr};
|
rmt_channel_handle_t channel_{nullptr};
|
||||||
rmt_encoder_handle_t encoder_{nullptr};
|
rmt_encoder_handle_t encoder_{nullptr};
|
||||||
|
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0)
|
||||||
|
uint8_t *rmt_buf_{nullptr};
|
||||||
|
#else
|
||||||
rmt_symbol_word_t *rmt_buf_{nullptr};
|
rmt_symbol_word_t *rmt_buf_{nullptr};
|
||||||
rmt_symbol_word_t bit0_, bit1_, reset_;
|
#endif
|
||||||
uint32_t rmt_symbols_{48};
|
uint32_t rmt_symbols_{48};
|
||||||
|
|
||||||
uint8_t pin_;
|
uint8_t pin_;
|
||||||
uint16_t num_leds_;
|
uint16_t num_leds_;
|
||||||
bool is_rgbw_{false};
|
bool is_rgbw_{false};
|
||||||
|
@ -1,355 +0,0 @@
|
|||||||
#ifdef USE_ESP32
|
|
||||||
|
|
||||||
#include "esp32_touch.h"
|
|
||||||
#include "esphome/core/application.h"
|
|
||||||
#include "esphome/core/log.h"
|
|
||||||
#include "esphome/core/hal.h"
|
|
||||||
|
|
||||||
#include <cinttypes>
|
|
||||||
|
|
||||||
namespace esphome {
|
|
||||||
namespace esp32_touch {
|
|
||||||
|
|
||||||
static const char *const TAG = "esp32_touch";
|
|
||||||
|
|
||||||
void ESP32TouchComponent::setup() {
|
|
||||||
ESP_LOGCONFIG(TAG, "Running setup");
|
|
||||||
touch_pad_init();
|
|
||||||
// set up and enable/start filtering based on ESP32 variant
|
|
||||||
#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
|
|
||||||
if (this->filter_configured_()) {
|
|
||||||
touch_filter_config_t filter_info = {
|
|
||||||
.mode = this->filter_mode_,
|
|
||||||
.debounce_cnt = this->debounce_count_,
|
|
||||||
.noise_thr = this->noise_threshold_,
|
|
||||||
.jitter_step = this->jitter_step_,
|
|
||||||
.smh_lvl = this->smooth_level_,
|
|
||||||
};
|
|
||||||
touch_pad_filter_set_config(&filter_info);
|
|
||||||
touch_pad_filter_enable();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this->denoise_configured_()) {
|
|
||||||
touch_pad_denoise_t denoise = {
|
|
||||||
.grade = this->grade_,
|
|
||||||
.cap_level = this->cap_level_,
|
|
||||||
};
|
|
||||||
touch_pad_denoise_set_config(&denoise);
|
|
||||||
touch_pad_denoise_enable();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this->waterproof_configured_()) {
|
|
||||||
touch_pad_waterproof_t waterproof = {
|
|
||||||
.guard_ring_pad = this->waterproof_guard_ring_pad_,
|
|
||||||
.shield_driver = this->waterproof_shield_driver_,
|
|
||||||
};
|
|
||||||
touch_pad_waterproof_set_config(&waterproof);
|
|
||||||
touch_pad_waterproof_enable();
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
if (this->iir_filter_enabled_()) {
|
|
||||||
touch_pad_filter_start(this->iir_filter_);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if ESP_IDF_VERSION_MAJOR >= 5 && defined(USE_ESP32_VARIANT_ESP32)
|
|
||||||
touch_pad_set_measurement_clock_cycles(this->meas_cycle_);
|
|
||||||
touch_pad_set_measurement_interval(this->sleep_cycle_);
|
|
||||||
#else
|
|
||||||
touch_pad_set_meas_time(this->sleep_cycle_, this->meas_cycle_);
|
|
||||||
#endif
|
|
||||||
touch_pad_set_voltage(this->high_voltage_reference_, this->low_voltage_reference_, this->voltage_attenuation_);
|
|
||||||
|
|
||||||
for (auto *child : this->children_) {
|
|
||||||
#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
|
|
||||||
touch_pad_config(child->get_touch_pad());
|
|
||||||
#else
|
|
||||||
// Disable interrupt threshold
|
|
||||||
touch_pad_config(child->get_touch_pad(), 0);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
|
|
||||||
touch_pad_set_fsm_mode(TOUCH_FSM_MODE_TIMER);
|
|
||||||
touch_pad_fsm_start();
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
void ESP32TouchComponent::dump_config() {
|
|
||||||
ESP_LOGCONFIG(TAG,
|
|
||||||
"Config for ESP32 Touch Hub:\n"
|
|
||||||
" Meas cycle: %.2fms\n"
|
|
||||||
" Sleep cycle: %.2fms",
|
|
||||||
this->meas_cycle_ / (8000000.0f / 1000.0f), this->sleep_cycle_ / (150000.0f / 1000.0f));
|
|
||||||
|
|
||||||
const char *lv_s;
|
|
||||||
switch (this->low_voltage_reference_) {
|
|
||||||
case TOUCH_LVOLT_0V5:
|
|
||||||
lv_s = "0.5V";
|
|
||||||
break;
|
|
||||||
case TOUCH_LVOLT_0V6:
|
|
||||||
lv_s = "0.6V";
|
|
||||||
break;
|
|
||||||
case TOUCH_LVOLT_0V7:
|
|
||||||
lv_s = "0.7V";
|
|
||||||
break;
|
|
||||||
case TOUCH_LVOLT_0V8:
|
|
||||||
lv_s = "0.8V";
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
lv_s = "UNKNOWN";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
ESP_LOGCONFIG(TAG, " Low Voltage Reference: %s", lv_s);
|
|
||||||
|
|
||||||
const char *hv_s;
|
|
||||||
switch (this->high_voltage_reference_) {
|
|
||||||
case TOUCH_HVOLT_2V4:
|
|
||||||
hv_s = "2.4V";
|
|
||||||
break;
|
|
||||||
case TOUCH_HVOLT_2V5:
|
|
||||||
hv_s = "2.5V";
|
|
||||||
break;
|
|
||||||
case TOUCH_HVOLT_2V6:
|
|
||||||
hv_s = "2.6V";
|
|
||||||
break;
|
|
||||||
case TOUCH_HVOLT_2V7:
|
|
||||||
hv_s = "2.7V";
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
hv_s = "UNKNOWN";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
ESP_LOGCONFIG(TAG, " High Voltage Reference: %s", hv_s);
|
|
||||||
|
|
||||||
const char *atten_s;
|
|
||||||
switch (this->voltage_attenuation_) {
|
|
||||||
case TOUCH_HVOLT_ATTEN_1V5:
|
|
||||||
atten_s = "1.5V";
|
|
||||||
break;
|
|
||||||
case TOUCH_HVOLT_ATTEN_1V:
|
|
||||||
atten_s = "1V";
|
|
||||||
break;
|
|
||||||
case TOUCH_HVOLT_ATTEN_0V5:
|
|
||||||
atten_s = "0.5V";
|
|
||||||
break;
|
|
||||||
case TOUCH_HVOLT_ATTEN_0V:
|
|
||||||
atten_s = "0V";
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
atten_s = "UNKNOWN";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
ESP_LOGCONFIG(TAG, " Voltage Attenuation: %s", atten_s);
|
|
||||||
|
|
||||||
#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
|
|
||||||
if (this->filter_configured_()) {
|
|
||||||
const char *filter_mode_s;
|
|
||||||
switch (this->filter_mode_) {
|
|
||||||
case TOUCH_PAD_FILTER_IIR_4:
|
|
||||||
filter_mode_s = "IIR_4";
|
|
||||||
break;
|
|
||||||
case TOUCH_PAD_FILTER_IIR_8:
|
|
||||||
filter_mode_s = "IIR_8";
|
|
||||||
break;
|
|
||||||
case TOUCH_PAD_FILTER_IIR_16:
|
|
||||||
filter_mode_s = "IIR_16";
|
|
||||||
break;
|
|
||||||
case TOUCH_PAD_FILTER_IIR_32:
|
|
||||||
filter_mode_s = "IIR_32";
|
|
||||||
break;
|
|
||||||
case TOUCH_PAD_FILTER_IIR_64:
|
|
||||||
filter_mode_s = "IIR_64";
|
|
||||||
break;
|
|
||||||
case TOUCH_PAD_FILTER_IIR_128:
|
|
||||||
filter_mode_s = "IIR_128";
|
|
||||||
break;
|
|
||||||
case TOUCH_PAD_FILTER_IIR_256:
|
|
||||||
filter_mode_s = "IIR_256";
|
|
||||||
break;
|
|
||||||
case TOUCH_PAD_FILTER_JITTER:
|
|
||||||
filter_mode_s = "JITTER";
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
filter_mode_s = "UNKNOWN";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
ESP_LOGCONFIG(TAG,
|
|
||||||
" Filter mode: %s\n"
|
|
||||||
" Debounce count: %" PRIu32 "\n"
|
|
||||||
" Noise threshold coefficient: %" PRIu32 "\n"
|
|
||||||
" Jitter filter step size: %" PRIu32,
|
|
||||||
filter_mode_s, this->debounce_count_, this->noise_threshold_, this->jitter_step_);
|
|
||||||
const char *smooth_level_s;
|
|
||||||
switch (this->smooth_level_) {
|
|
||||||
case TOUCH_PAD_SMOOTH_OFF:
|
|
||||||
smooth_level_s = "OFF";
|
|
||||||
break;
|
|
||||||
case TOUCH_PAD_SMOOTH_IIR_2:
|
|
||||||
smooth_level_s = "IIR_2";
|
|
||||||
break;
|
|
||||||
case TOUCH_PAD_SMOOTH_IIR_4:
|
|
||||||
smooth_level_s = "IIR_4";
|
|
||||||
break;
|
|
||||||
case TOUCH_PAD_SMOOTH_IIR_8:
|
|
||||||
smooth_level_s = "IIR_8";
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
smooth_level_s = "UNKNOWN";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
ESP_LOGCONFIG(TAG, " Smooth level: %s", smooth_level_s);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this->denoise_configured_()) {
|
|
||||||
const char *grade_s;
|
|
||||||
switch (this->grade_) {
|
|
||||||
case TOUCH_PAD_DENOISE_BIT12:
|
|
||||||
grade_s = "BIT12";
|
|
||||||
break;
|
|
||||||
case TOUCH_PAD_DENOISE_BIT10:
|
|
||||||
grade_s = "BIT10";
|
|
||||||
break;
|
|
||||||
case TOUCH_PAD_DENOISE_BIT8:
|
|
||||||
grade_s = "BIT8";
|
|
||||||
break;
|
|
||||||
case TOUCH_PAD_DENOISE_BIT4:
|
|
||||||
grade_s = "BIT4";
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
grade_s = "UNKNOWN";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
ESP_LOGCONFIG(TAG, " Denoise grade: %s", grade_s);
|
|
||||||
|
|
||||||
const char *cap_level_s;
|
|
||||||
switch (this->cap_level_) {
|
|
||||||
case TOUCH_PAD_DENOISE_CAP_L0:
|
|
||||||
cap_level_s = "L0";
|
|
||||||
break;
|
|
||||||
case TOUCH_PAD_DENOISE_CAP_L1:
|
|
||||||
cap_level_s = "L1";
|
|
||||||
break;
|
|
||||||
case TOUCH_PAD_DENOISE_CAP_L2:
|
|
||||||
cap_level_s = "L2";
|
|
||||||
break;
|
|
||||||
case TOUCH_PAD_DENOISE_CAP_L3:
|
|
||||||
cap_level_s = "L3";
|
|
||||||
break;
|
|
||||||
case TOUCH_PAD_DENOISE_CAP_L4:
|
|
||||||
cap_level_s = "L4";
|
|
||||||
break;
|
|
||||||
case TOUCH_PAD_DENOISE_CAP_L5:
|
|
||||||
cap_level_s = "L5";
|
|
||||||
break;
|
|
||||||
case TOUCH_PAD_DENOISE_CAP_L6:
|
|
||||||
cap_level_s = "L6";
|
|
||||||
break;
|
|
||||||
case TOUCH_PAD_DENOISE_CAP_L7:
|
|
||||||
cap_level_s = "L7";
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
cap_level_s = "UNKNOWN";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
ESP_LOGCONFIG(TAG, " Denoise capacitance level: %s", cap_level_s);
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
if (this->iir_filter_enabled_()) {
|
|
||||||
ESP_LOGCONFIG(TAG, " IIR Filter: %" PRIu32 "ms", this->iir_filter_);
|
|
||||||
} else {
|
|
||||||
ESP_LOGCONFIG(TAG, " IIR Filter DISABLED");
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if (this->setup_mode_) {
|
|
||||||
ESP_LOGCONFIG(TAG, " Setup Mode ENABLED");
|
|
||||||
}
|
|
||||||
|
|
||||||
for (auto *child : this->children_) {
|
|
||||||
LOG_BINARY_SENSOR(" ", "Touch Pad", child);
|
|
||||||
ESP_LOGCONFIG(TAG, " Pad: T%" PRIu32, (uint32_t) child->get_touch_pad());
|
|
||||||
ESP_LOGCONFIG(TAG, " Threshold: %" PRIu32, child->get_threshold());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t ESP32TouchComponent::component_touch_pad_read(touch_pad_t tp) {
|
|
||||||
#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
|
|
||||||
uint32_t value = 0;
|
|
||||||
if (this->filter_configured_()) {
|
|
||||||
touch_pad_filter_read_smooth(tp, &value);
|
|
||||||
} else {
|
|
||||||
touch_pad_read_raw_data(tp, &value);
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
uint16_t value = 0;
|
|
||||||
if (this->iir_filter_enabled_()) {
|
|
||||||
touch_pad_read_filtered(tp, &value);
|
|
||||||
} else {
|
|
||||||
touch_pad_read(tp, &value);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ESP32TouchComponent::loop() {
|
|
||||||
const uint32_t now = App.get_loop_component_start_time();
|
|
||||||
bool should_print = this->setup_mode_ && now - this->setup_mode_last_log_print_ > 250;
|
|
||||||
for (auto *child : this->children_) {
|
|
||||||
child->value_ = this->component_touch_pad_read(child->get_touch_pad());
|
|
||||||
#if !(defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3))
|
|
||||||
child->publish_state(child->value_ < child->get_threshold());
|
|
||||||
#else
|
|
||||||
child->publish_state(child->value_ > child->get_threshold());
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if (should_print) {
|
|
||||||
ESP_LOGD(TAG, "Touch Pad '%s' (T%" PRIu32 "): %" PRIu32, child->get_name().c_str(),
|
|
||||||
(uint32_t) child->get_touch_pad(), child->value_);
|
|
||||||
}
|
|
||||||
|
|
||||||
App.feed_wdt();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (should_print) {
|
|
||||||
// Avoid spamming logs
|
|
||||||
this->setup_mode_last_log_print_ = now;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ESP32TouchComponent::on_shutdown() {
|
|
||||||
bool is_wakeup_source = false;
|
|
||||||
|
|
||||||
#if !(defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3))
|
|
||||||
if (this->iir_filter_enabled_()) {
|
|
||||||
touch_pad_filter_stop();
|
|
||||||
touch_pad_filter_delete();
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
for (auto *child : this->children_) {
|
|
||||||
if (child->get_wakeup_threshold() != 0) {
|
|
||||||
if (!is_wakeup_source) {
|
|
||||||
is_wakeup_source = true;
|
|
||||||
// Touch sensor FSM mode must be 'TOUCH_FSM_MODE_TIMER' to use it to wake-up.
|
|
||||||
touch_pad_set_fsm_mode(TOUCH_FSM_MODE_TIMER);
|
|
||||||
}
|
|
||||||
|
|
||||||
#if !(defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3))
|
|
||||||
// No filter available when using as wake-up source.
|
|
||||||
touch_pad_config(child->get_touch_pad(), child->get_wakeup_threshold());
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!is_wakeup_source) {
|
|
||||||
touch_pad_deinit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ESP32TouchBinarySensor::ESP32TouchBinarySensor(touch_pad_t touch_pad, uint32_t threshold, uint32_t wakeup_threshold)
|
|
||||||
: touch_pad_(touch_pad), threshold_(threshold), wakeup_threshold_(wakeup_threshold) {}
|
|
||||||
|
|
||||||
} // namespace esp32_touch
|
|
||||||
} // namespace esphome
|
|
||||||
|
|
||||||
#endif
|
|
@ -9,10 +9,26 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include <driver/touch_sensor.h>
|
#include <driver/touch_sensor.h>
|
||||||
|
#include <freertos/FreeRTOS.h>
|
||||||
|
#include <freertos/queue.h>
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace esp32_touch {
|
namespace esp32_touch {
|
||||||
|
|
||||||
|
// IMPORTANT: Touch detection logic differs between ESP32 variants:
|
||||||
|
// - ESP32 v1 (original): Touch detected when value < threshold (capacitance increase causes value decrease)
|
||||||
|
// - ESP32-S2/S3 v2: Touch detected when value > threshold (capacitance increase causes value increase)
|
||||||
|
// This inversion is due to different hardware implementations between chip generations.
|
||||||
|
//
|
||||||
|
// INTERRUPT BEHAVIOR:
|
||||||
|
// - ESP32 v1: Interrupts fire when ANY pad is touched and continue while touched.
|
||||||
|
// Releases are detected by timeout since hardware doesn't generate release interrupts.
|
||||||
|
// - ESP32-S2/S3 v2: Hardware supports both touch and release interrupts, but release
|
||||||
|
// interrupts are unreliable and sometimes don't fire. We now only use touch interrupts
|
||||||
|
// and detect releases via timeout, similar to v1.
|
||||||
|
|
||||||
|
static const uint32_t SETUP_MODE_LOG_INTERVAL_MS = 250;
|
||||||
|
|
||||||
class ESP32TouchBinarySensor;
|
class ESP32TouchBinarySensor;
|
||||||
|
|
||||||
class ESP32TouchComponent : public Component {
|
class ESP32TouchComponent : public Component {
|
||||||
@ -31,6 +47,14 @@ class ESP32TouchComponent : public Component {
|
|||||||
void set_voltage_attenuation(touch_volt_atten_t voltage_attenuation) {
|
void set_voltage_attenuation(touch_volt_atten_t voltage_attenuation) {
|
||||||
this->voltage_attenuation_ = voltage_attenuation;
|
this->voltage_attenuation_ = voltage_attenuation;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setup() override;
|
||||||
|
void dump_config() override;
|
||||||
|
void loop() override;
|
||||||
|
float get_setup_priority() const override { return setup_priority::DATA; }
|
||||||
|
|
||||||
|
void on_shutdown() override;
|
||||||
|
|
||||||
#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
|
#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
|
||||||
void set_filter_mode(touch_filter_mode_t filter_mode) { this->filter_mode_ = filter_mode; }
|
void set_filter_mode(touch_filter_mode_t filter_mode) { this->filter_mode_ = filter_mode; }
|
||||||
void set_debounce_count(uint32_t debounce_count) { this->debounce_count_ = debounce_count; }
|
void set_debounce_count(uint32_t debounce_count) { this->debounce_count_ = debounce_count; }
|
||||||
@ -47,16 +71,90 @@ class ESP32TouchComponent : public Component {
|
|||||||
void set_iir_filter(uint32_t iir_filter) { this->iir_filter_ = iir_filter; }
|
void set_iir_filter(uint32_t iir_filter) { this->iir_filter_ = iir_filter; }
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
uint32_t component_touch_pad_read(touch_pad_t tp);
|
protected:
|
||||||
|
// Common helper methods
|
||||||
|
void dump_config_base_();
|
||||||
|
void dump_config_sensors_();
|
||||||
|
bool create_touch_queue_();
|
||||||
|
void cleanup_touch_queue_();
|
||||||
|
void configure_wakeup_pads_();
|
||||||
|
|
||||||
void setup() override;
|
// Helper methods for loop() logic
|
||||||
void dump_config() override;
|
void process_setup_mode_logging_(uint32_t now);
|
||||||
void loop() override;
|
bool should_check_for_releases_(uint32_t now);
|
||||||
|
void publish_initial_state_if_needed_(ESP32TouchBinarySensor *child, uint32_t now);
|
||||||
|
void check_and_disable_loop_if_all_released_(size_t pads_off);
|
||||||
|
void calculate_release_timeout_();
|
||||||
|
|
||||||
void on_shutdown() override;
|
// Common members
|
||||||
|
std::vector<ESP32TouchBinarySensor *> children_;
|
||||||
|
bool setup_mode_{false};
|
||||||
|
uint32_t setup_mode_last_log_print_{0};
|
||||||
|
uint32_t last_release_check_{0};
|
||||||
|
uint32_t release_timeout_ms_{1500};
|
||||||
|
uint32_t release_check_interval_ms_{50};
|
||||||
|
|
||||||
|
// Common configuration parameters
|
||||||
|
uint16_t sleep_cycle_{4095};
|
||||||
|
uint16_t meas_cycle_{65535};
|
||||||
|
touch_low_volt_t low_voltage_reference_{TOUCH_LVOLT_0V5};
|
||||||
|
touch_high_volt_t high_voltage_reference_{TOUCH_HVOLT_2V7};
|
||||||
|
touch_volt_atten_t voltage_attenuation_{TOUCH_HVOLT_ATTEN_0V};
|
||||||
|
|
||||||
|
// Common constants
|
||||||
|
static constexpr uint32_t MINIMUM_RELEASE_TIME_MS = 100;
|
||||||
|
|
||||||
|
// ==================== PLATFORM SPECIFIC ====================
|
||||||
|
|
||||||
|
#ifdef USE_ESP32_VARIANT_ESP32
|
||||||
|
// ESP32 v1 specific
|
||||||
|
|
||||||
|
static void touch_isr_handler(void *arg);
|
||||||
|
QueueHandle_t touch_queue_{nullptr};
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Touch event structure for ESP32 v1
|
||||||
|
// Contains touch pad info, value, and touch state for queue communication
|
||||||
|
struct TouchPadEventV1 {
|
||||||
|
touch_pad_t pad;
|
||||||
|
uint32_t value;
|
||||||
|
bool is_touched;
|
||||||
|
};
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
|
uint32_t iir_filter_{0};
|
||||||
|
|
||||||
|
bool iir_filter_enabled_() const { return this->iir_filter_ > 0; }
|
||||||
|
|
||||||
|
#elif defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
|
||||||
|
// ESP32-S2/S3 v2 specific
|
||||||
|
static void touch_isr_handler(void *arg);
|
||||||
|
QueueHandle_t touch_queue_{nullptr};
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Touch event structure for ESP32 v2 (S2/S3)
|
||||||
|
// Contains touch pad and interrupt mask for queue communication
|
||||||
|
struct TouchPadEventV2 {
|
||||||
|
touch_pad_t pad;
|
||||||
|
uint32_t intr_mask;
|
||||||
|
};
|
||||||
|
|
||||||
|
protected:
|
||||||
|
// Filter configuration
|
||||||
|
touch_filter_mode_t filter_mode_{TOUCH_PAD_FILTER_MAX};
|
||||||
|
uint32_t debounce_count_{0};
|
||||||
|
uint32_t noise_threshold_{0};
|
||||||
|
uint32_t jitter_step_{0};
|
||||||
|
touch_smooth_mode_t smooth_level_{TOUCH_PAD_SMOOTH_MAX};
|
||||||
|
|
||||||
|
// Denoise configuration
|
||||||
|
touch_pad_denoise_grade_t grade_{TOUCH_PAD_DENOISE_MAX};
|
||||||
|
touch_pad_denoise_cap_t cap_level_{TOUCH_PAD_DENOISE_CAP_MAX};
|
||||||
|
|
||||||
|
// Waterproof configuration
|
||||||
|
touch_pad_t waterproof_guard_ring_pad_{TOUCH_PAD_MAX};
|
||||||
|
touch_pad_shield_driver_t waterproof_shield_driver_{TOUCH_PAD_SHIELD_DRV_MAX};
|
||||||
|
|
||||||
bool filter_configured_() const {
|
bool filter_configured_() const {
|
||||||
return (this->filter_mode_ != TOUCH_PAD_FILTER_MAX) && (this->smooth_level_ != TOUCH_PAD_SMOOTH_MAX);
|
return (this->filter_mode_ != TOUCH_PAD_FILTER_MAX) && (this->smooth_level_ != TOUCH_PAD_SMOOTH_MAX);
|
||||||
}
|
}
|
||||||
@ -67,43 +165,78 @@ class ESP32TouchComponent : public Component {
|
|||||||
return (this->waterproof_guard_ring_pad_ != TOUCH_PAD_MAX) &&
|
return (this->waterproof_guard_ring_pad_ != TOUCH_PAD_MAX) &&
|
||||||
(this->waterproof_shield_driver_ != TOUCH_PAD_SHIELD_DRV_MAX);
|
(this->waterproof_shield_driver_ != TOUCH_PAD_SHIELD_DRV_MAX);
|
||||||
}
|
}
|
||||||
#else
|
|
||||||
bool iir_filter_enabled_() const { return this->iir_filter_ > 0; }
|
// Helper method to read touch values - non-blocking operation
|
||||||
|
// Returns the current touch pad value using either filtered or raw reading
|
||||||
|
// based on the filter configuration
|
||||||
|
uint32_t read_touch_value(touch_pad_t pad) const;
|
||||||
|
|
||||||
|
// Helper to update touch state with a known state
|
||||||
|
void update_touch_state_(ESP32TouchBinarySensor *child, bool is_touched);
|
||||||
|
|
||||||
|
// Helper to read touch value and update state for a given child
|
||||||
|
bool check_and_update_touch_state_(ESP32TouchBinarySensor *child);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
std::vector<ESP32TouchBinarySensor *> children_;
|
// Helper functions for dump_config - common to both implementations
|
||||||
bool setup_mode_{false};
|
static const char *get_low_voltage_reference_str(touch_low_volt_t ref) {
|
||||||
uint32_t setup_mode_last_log_print_{0};
|
switch (ref) {
|
||||||
// common parameters
|
case TOUCH_LVOLT_0V5:
|
||||||
uint16_t sleep_cycle_{4095};
|
return "0.5V";
|
||||||
uint16_t meas_cycle_{65535};
|
case TOUCH_LVOLT_0V6:
|
||||||
touch_low_volt_t low_voltage_reference_{TOUCH_LVOLT_0V5};
|
return "0.6V";
|
||||||
touch_high_volt_t high_voltage_reference_{TOUCH_HVOLT_2V7};
|
case TOUCH_LVOLT_0V7:
|
||||||
touch_volt_atten_t voltage_attenuation_{TOUCH_HVOLT_ATTEN_0V};
|
return "0.7V";
|
||||||
#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
|
case TOUCH_LVOLT_0V8:
|
||||||
touch_filter_mode_t filter_mode_{TOUCH_PAD_FILTER_MAX};
|
return "0.8V";
|
||||||
uint32_t debounce_count_{0};
|
default:
|
||||||
uint32_t noise_threshold_{0};
|
return "UNKNOWN";
|
||||||
uint32_t jitter_step_{0};
|
}
|
||||||
touch_smooth_mode_t smooth_level_{TOUCH_PAD_SMOOTH_MAX};
|
}
|
||||||
touch_pad_denoise_grade_t grade_{TOUCH_PAD_DENOISE_MAX};
|
|
||||||
touch_pad_denoise_cap_t cap_level_{TOUCH_PAD_DENOISE_CAP_MAX};
|
static const char *get_high_voltage_reference_str(touch_high_volt_t ref) {
|
||||||
touch_pad_t waterproof_guard_ring_pad_{TOUCH_PAD_MAX};
|
switch (ref) {
|
||||||
touch_pad_shield_driver_t waterproof_shield_driver_{TOUCH_PAD_SHIELD_DRV_MAX};
|
case TOUCH_HVOLT_2V4:
|
||||||
#else
|
return "2.4V";
|
||||||
uint32_t iir_filter_{0};
|
case TOUCH_HVOLT_2V5:
|
||||||
#endif
|
return "2.5V";
|
||||||
|
case TOUCH_HVOLT_2V6:
|
||||||
|
return "2.6V";
|
||||||
|
case TOUCH_HVOLT_2V7:
|
||||||
|
return "2.7V";
|
||||||
|
default:
|
||||||
|
return "UNKNOWN";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char *get_voltage_attenuation_str(touch_volt_atten_t atten) {
|
||||||
|
switch (atten) {
|
||||||
|
case TOUCH_HVOLT_ATTEN_1V5:
|
||||||
|
return "1.5V";
|
||||||
|
case TOUCH_HVOLT_ATTEN_1V:
|
||||||
|
return "1V";
|
||||||
|
case TOUCH_HVOLT_ATTEN_0V5:
|
||||||
|
return "0.5V";
|
||||||
|
case TOUCH_HVOLT_ATTEN_0V:
|
||||||
|
return "0V";
|
||||||
|
default:
|
||||||
|
return "UNKNOWN";
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Simple helper class to expose a touch pad value as a binary sensor.
|
/// Simple helper class to expose a touch pad value as a binary sensor.
|
||||||
class ESP32TouchBinarySensor : public binary_sensor::BinarySensor {
|
class ESP32TouchBinarySensor : public binary_sensor::BinarySensor {
|
||||||
public:
|
public:
|
||||||
ESP32TouchBinarySensor(touch_pad_t touch_pad, uint32_t threshold, uint32_t wakeup_threshold);
|
ESP32TouchBinarySensor(touch_pad_t touch_pad, uint32_t threshold, uint32_t wakeup_threshold)
|
||||||
|
: touch_pad_(touch_pad), threshold_(threshold), wakeup_threshold_(wakeup_threshold) {}
|
||||||
|
|
||||||
touch_pad_t get_touch_pad() const { return this->touch_pad_; }
|
touch_pad_t get_touch_pad() const { return this->touch_pad_; }
|
||||||
uint32_t get_threshold() const { return this->threshold_; }
|
uint32_t get_threshold() const { return this->threshold_; }
|
||||||
void set_threshold(uint32_t threshold) { this->threshold_ = threshold; }
|
void set_threshold(uint32_t threshold) { this->threshold_ = threshold; }
|
||||||
|
#ifdef USE_ESP32_VARIANT_ESP32
|
||||||
uint32_t get_value() const { return this->value_; }
|
uint32_t get_value() const { return this->value_; }
|
||||||
|
#endif
|
||||||
uint32_t get_wakeup_threshold() const { return this->wakeup_threshold_; }
|
uint32_t get_wakeup_threshold() const { return this->wakeup_threshold_; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
@ -111,8 +244,22 @@ class ESP32TouchBinarySensor : public binary_sensor::BinarySensor {
|
|||||||
|
|
||||||
touch_pad_t touch_pad_{TOUCH_PAD_MAX};
|
touch_pad_t touch_pad_{TOUCH_PAD_MAX};
|
||||||
uint32_t threshold_{0};
|
uint32_t threshold_{0};
|
||||||
|
uint32_t benchmark_{};
|
||||||
|
#ifdef USE_ESP32_VARIANT_ESP32
|
||||||
uint32_t value_{0};
|
uint32_t value_{0};
|
||||||
|
#endif
|
||||||
|
bool last_state_{false};
|
||||||
const uint32_t wakeup_threshold_{0};
|
const uint32_t wakeup_threshold_{0};
|
||||||
|
|
||||||
|
// Track last touch time for timeout-based release detection
|
||||||
|
// Design note: last_touch_time_ does not require synchronization primitives because:
|
||||||
|
// 1. ESP32 guarantees atomic 32-bit aligned reads/writes
|
||||||
|
// 2. ISR only writes timestamps, main loop only reads
|
||||||
|
// 3. Timing tolerance allows for occasional stale reads (50ms check interval)
|
||||||
|
// 4. Queue operations provide implicit memory barriers
|
||||||
|
// Using atomic/critical sections would add overhead without meaningful benefit
|
||||||
|
uint32_t last_touch_time_{};
|
||||||
|
bool initial_state_published_{};
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace esp32_touch
|
} // namespace esp32_touch
|
||||||
|
162
esphome/components/esp32_touch/esp32_touch_common.cpp
Normal file
162
esphome/components/esp32_touch/esp32_touch_common.cpp
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
#ifdef USE_ESP32
|
||||||
|
|
||||||
|
#include "esp32_touch.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
#include <cinttypes>
|
||||||
|
|
||||||
|
#include "soc/rtc.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace esp32_touch {
|
||||||
|
|
||||||
|
static const char *const TAG = "esp32_touch";
|
||||||
|
|
||||||
|
void ESP32TouchComponent::dump_config_base_() {
|
||||||
|
const char *lv_s = get_low_voltage_reference_str(this->low_voltage_reference_);
|
||||||
|
const char *hv_s = get_high_voltage_reference_str(this->high_voltage_reference_);
|
||||||
|
const char *atten_s = get_voltage_attenuation_str(this->voltage_attenuation_);
|
||||||
|
|
||||||
|
ESP_LOGCONFIG(TAG,
|
||||||
|
"Config for ESP32 Touch Hub:\n"
|
||||||
|
" Meas cycle: %.2fms\n"
|
||||||
|
" Sleep cycle: %.2fms\n"
|
||||||
|
" Low Voltage Reference: %s\n"
|
||||||
|
" High Voltage Reference: %s\n"
|
||||||
|
" Voltage Attenuation: %s\n"
|
||||||
|
" Release Timeout: %" PRIu32 "ms\n",
|
||||||
|
this->meas_cycle_ / (8000000.0f / 1000.0f), this->sleep_cycle_ / (150000.0f / 1000.0f), lv_s, hv_s,
|
||||||
|
atten_s, this->release_timeout_ms_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ESP32TouchComponent::dump_config_sensors_() {
|
||||||
|
for (auto *child : this->children_) {
|
||||||
|
LOG_BINARY_SENSOR(" ", "Touch Pad", child);
|
||||||
|
ESP_LOGCONFIG(TAG,
|
||||||
|
" Pad: T%u\n"
|
||||||
|
" Threshold: %" PRIu32 "\n"
|
||||||
|
" Benchmark: %" PRIu32,
|
||||||
|
(unsigned) child->touch_pad_, child->threshold_, child->benchmark_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ESP32TouchComponent::create_touch_queue_() {
|
||||||
|
// Queue size calculation: children * 4 allows for burst scenarios where ISR
|
||||||
|
// fires multiple times before main loop processes.
|
||||||
|
size_t queue_size = this->children_.size() * 4;
|
||||||
|
if (queue_size < 8)
|
||||||
|
queue_size = 8;
|
||||||
|
|
||||||
|
#ifdef USE_ESP32_VARIANT_ESP32
|
||||||
|
this->touch_queue_ = xQueueCreate(queue_size, sizeof(TouchPadEventV1));
|
||||||
|
#else
|
||||||
|
this->touch_queue_ = xQueueCreate(queue_size, sizeof(TouchPadEventV2));
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (this->touch_queue_ == nullptr) {
|
||||||
|
ESP_LOGE(TAG, "Failed to create touch event queue of size %" PRIu32, (uint32_t) queue_size);
|
||||||
|
this->mark_failed();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ESP32TouchComponent::cleanup_touch_queue_() {
|
||||||
|
if (this->touch_queue_) {
|
||||||
|
vQueueDelete(this->touch_queue_);
|
||||||
|
this->touch_queue_ = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ESP32TouchComponent::configure_wakeup_pads_() {
|
||||||
|
bool is_wakeup_source = false;
|
||||||
|
|
||||||
|
// Check if any pad is configured for wakeup
|
||||||
|
for (auto *child : this->children_) {
|
||||||
|
if (child->get_wakeup_threshold() != 0) {
|
||||||
|
is_wakeup_source = true;
|
||||||
|
|
||||||
|
#ifdef USE_ESP32_VARIANT_ESP32
|
||||||
|
// ESP32 v1: No filter available when using as wake-up source.
|
||||||
|
touch_pad_config(child->get_touch_pad(), child->get_wakeup_threshold());
|
||||||
|
#else
|
||||||
|
// ESP32-S2/S3 v2: Set threshold for wakeup
|
||||||
|
touch_pad_set_thresh(child->get_touch_pad(), child->get_wakeup_threshold());
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is_wakeup_source) {
|
||||||
|
// If no pad is configured for wakeup, deinitialize touch pad
|
||||||
|
touch_pad_deinit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ESP32TouchComponent::process_setup_mode_logging_(uint32_t now) {
|
||||||
|
if (this->setup_mode_ && now - this->setup_mode_last_log_print_ > SETUP_MODE_LOG_INTERVAL_MS) {
|
||||||
|
for (auto *child : this->children_) {
|
||||||
|
#ifdef USE_ESP32_VARIANT_ESP32
|
||||||
|
ESP_LOGD(TAG, "Touch Pad '%s' (T%" PRIu32 "): %" PRIu32, child->get_name().c_str(),
|
||||||
|
(uint32_t) child->get_touch_pad(), child->value_);
|
||||||
|
#else
|
||||||
|
// Read the value being used for touch detection
|
||||||
|
uint32_t value = this->read_touch_value(child->get_touch_pad());
|
||||||
|
ESP_LOGD(TAG, "Touch Pad '%s' (T%d): %d", child->get_name().c_str(), child->get_touch_pad(), value);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
this->setup_mode_last_log_print_ = now;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ESP32TouchComponent::should_check_for_releases_(uint32_t now) {
|
||||||
|
if (now - this->last_release_check_ < this->release_check_interval_ms_) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
this->last_release_check_ = now;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ESP32TouchComponent::publish_initial_state_if_needed_(ESP32TouchBinarySensor *child, uint32_t now) {
|
||||||
|
if (!child->initial_state_published_) {
|
||||||
|
// Check if enough time has passed since startup
|
||||||
|
if (now > this->release_timeout_ms_) {
|
||||||
|
child->publish_initial_state(false);
|
||||||
|
child->initial_state_published_ = true;
|
||||||
|
ESP_LOGV(TAG, "Touch Pad '%s' state: OFF (initial)", child->get_name().c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ESP32TouchComponent::check_and_disable_loop_if_all_released_(size_t pads_off) {
|
||||||
|
// Disable the loop to save CPU cycles when all pads are off and not in setup mode.
|
||||||
|
if (pads_off == this->children_.size() && !this->setup_mode_) {
|
||||||
|
this->disable_loop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ESP32TouchComponent::calculate_release_timeout_() {
|
||||||
|
// Calculate release timeout based on sleep cycle
|
||||||
|
// Design note: Hardware limitation - interrupts only fire reliably on touch (not release)
|
||||||
|
// We must use timeout-based detection for release events
|
||||||
|
// Formula: 3 sleep cycles converted to ms, with MINIMUM_RELEASE_TIME_MS minimum
|
||||||
|
// Per ESP-IDF docs: t_sleep = sleep_cycle / SOC_CLK_RC_SLOW_FREQ_APPROX
|
||||||
|
|
||||||
|
uint32_t rtc_freq = rtc_clk_slow_freq_get_hz();
|
||||||
|
|
||||||
|
// Calculate timeout as 3 sleep cycles
|
||||||
|
this->release_timeout_ms_ = (this->sleep_cycle_ * 1000 * 3) / rtc_freq;
|
||||||
|
|
||||||
|
if (this->release_timeout_ms_ < MINIMUM_RELEASE_TIME_MS) {
|
||||||
|
this->release_timeout_ms_ = MINIMUM_RELEASE_TIME_MS;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for releases at 1/4 the timeout interval
|
||||||
|
// Since hardware doesn't generate reliable release interrupts, we must poll
|
||||||
|
// for releases in the main loop. Checking at 1/4 the timeout interval provides
|
||||||
|
// a good balance between responsiveness and efficiency.
|
||||||
|
this->release_check_interval_ms_ = this->release_timeout_ms_ / 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace esp32_touch
|
||||||
|
} // namespace esphome
|
||||||
|
|
||||||
|
#endif // USE_ESP32
|
238
esphome/components/esp32_touch/esp32_touch_v1.cpp
Normal file
238
esphome/components/esp32_touch/esp32_touch_v1.cpp
Normal file
@ -0,0 +1,238 @@
|
|||||||
|
#ifdef USE_ESP32_VARIANT_ESP32
|
||||||
|
|
||||||
|
#include "esp32_touch.h"
|
||||||
|
#include "esphome/core/application.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
#include "esphome/core/hal.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cinttypes>
|
||||||
|
|
||||||
|
// Include HAL for ISR-safe touch reading
|
||||||
|
#include "hal/touch_sensor_ll.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace esp32_touch {
|
||||||
|
|
||||||
|
static const char *const TAG = "esp32_touch";
|
||||||
|
|
||||||
|
void ESP32TouchComponent::setup() {
|
||||||
|
// Create queue for touch events
|
||||||
|
// Queue size calculation: children * 4 allows for burst scenarios where ISR
|
||||||
|
// fires multiple times before main loop processes. This is important because
|
||||||
|
// ESP32 v1 scans all pads on each interrupt, potentially sending multiple events.
|
||||||
|
if (!this->create_touch_queue_()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
touch_pad_init();
|
||||||
|
touch_pad_set_fsm_mode(TOUCH_FSM_MODE_TIMER);
|
||||||
|
|
||||||
|
// Set up IIR filter if enabled
|
||||||
|
if (this->iir_filter_enabled_()) {
|
||||||
|
touch_pad_filter_start(this->iir_filter_);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure measurement parameters
|
||||||
|
#if ESP_IDF_VERSION_MAJOR >= 5
|
||||||
|
touch_pad_set_measurement_clock_cycles(this->meas_cycle_);
|
||||||
|
touch_pad_set_measurement_interval(this->sleep_cycle_);
|
||||||
|
#else
|
||||||
|
touch_pad_set_meas_time(this->sleep_cycle_, this->meas_cycle_);
|
||||||
|
#endif
|
||||||
|
touch_pad_set_voltage(this->high_voltage_reference_, this->low_voltage_reference_, this->voltage_attenuation_);
|
||||||
|
|
||||||
|
// Configure each touch pad
|
||||||
|
for (auto *child : this->children_) {
|
||||||
|
touch_pad_config(child->get_touch_pad(), child->get_threshold());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register ISR handler
|
||||||
|
esp_err_t err = touch_pad_isr_register(touch_isr_handler, this);
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
ESP_LOGE(TAG, "Failed to register touch ISR: %s", esp_err_to_name(err));
|
||||||
|
this->cleanup_touch_queue_();
|
||||||
|
this->mark_failed();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate release timeout based on sleep cycle
|
||||||
|
this->calculate_release_timeout_();
|
||||||
|
|
||||||
|
// Enable touch pad interrupt
|
||||||
|
touch_pad_intr_enable();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ESP32TouchComponent::dump_config() {
|
||||||
|
this->dump_config_base_();
|
||||||
|
|
||||||
|
if (this->iir_filter_enabled_()) {
|
||||||
|
ESP_LOGCONFIG(TAG, " IIR Filter: %" PRIu32 "ms", this->iir_filter_);
|
||||||
|
} else {
|
||||||
|
ESP_LOGCONFIG(TAG, " IIR Filter DISABLED");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this->setup_mode_) {
|
||||||
|
ESP_LOGCONFIG(TAG, " Setup Mode ENABLED");
|
||||||
|
}
|
||||||
|
|
||||||
|
this->dump_config_sensors_();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ESP32TouchComponent::loop() {
|
||||||
|
const uint32_t now = App.get_loop_component_start_time();
|
||||||
|
|
||||||
|
// Print debug info for all pads in setup mode
|
||||||
|
this->process_setup_mode_logging_(now);
|
||||||
|
|
||||||
|
// Process any queued touch events from interrupts
|
||||||
|
// Note: Events are only sent by ISR for pads that were measured in that cycle (value != 0)
|
||||||
|
// This is more efficient than sending all pad states every interrupt
|
||||||
|
TouchPadEventV1 event;
|
||||||
|
while (xQueueReceive(this->touch_queue_, &event, 0) == pdTRUE) {
|
||||||
|
// Find the corresponding sensor - O(n) search is acceptable since events are infrequent
|
||||||
|
for (auto *child : this->children_) {
|
||||||
|
if (child->get_touch_pad() != event.pad) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Found matching pad - process it
|
||||||
|
child->value_ = event.value;
|
||||||
|
|
||||||
|
// The interrupt gives us the touch state directly
|
||||||
|
bool new_state = event.is_touched;
|
||||||
|
|
||||||
|
// Track when we last saw this pad as touched
|
||||||
|
if (new_state) {
|
||||||
|
child->last_touch_time_ = now;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only publish if state changed - this filters out repeated events
|
||||||
|
if (new_state != child->last_state_) {
|
||||||
|
child->last_state_ = new_state;
|
||||||
|
child->publish_state(new_state);
|
||||||
|
// Original ESP32: ISR only fires when touched, release is detected by timeout
|
||||||
|
// Note: ESP32 v1 uses inverted logic - touched when value < threshold
|
||||||
|
ESP_LOGV(TAG, "Touch Pad '%s' state: ON (value: %" PRIu32 " < threshold: %" PRIu32 ")",
|
||||||
|
child->get_name().c_str(), event.value, child->get_threshold());
|
||||||
|
}
|
||||||
|
break; // Exit inner loop after processing matching pad
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for released pads periodically
|
||||||
|
if (!this->should_check_for_releases_(now)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t pads_off = 0;
|
||||||
|
for (auto *child : this->children_) {
|
||||||
|
// Handle initial state publication after startup
|
||||||
|
this->publish_initial_state_if_needed_(child, now);
|
||||||
|
|
||||||
|
if (child->last_state_) {
|
||||||
|
// Pad is currently in touched state - check for release timeout
|
||||||
|
// Using subtraction handles 32-bit rollover correctly
|
||||||
|
uint32_t time_diff = now - child->last_touch_time_;
|
||||||
|
|
||||||
|
// Check if we haven't seen this pad recently
|
||||||
|
if (time_diff > this->release_timeout_ms_) {
|
||||||
|
// Haven't seen this pad recently, assume it's released
|
||||||
|
child->last_state_ = false;
|
||||||
|
child->publish_state(false);
|
||||||
|
ESP_LOGV(TAG, "Touch Pad '%s' state: OFF (timeout)", child->get_name().c_str());
|
||||||
|
pads_off++;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Pad is already off
|
||||||
|
pads_off++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disable the loop to save CPU cycles when all pads are off and not in setup mode.
|
||||||
|
// The loop will be re-enabled by the ISR when any touch pad is touched.
|
||||||
|
// v1 hardware limitations require us to check all pads are off because:
|
||||||
|
// - v1 only generates interrupts on touch events (not releases)
|
||||||
|
// - We must poll for release timeouts in the main loop
|
||||||
|
// - We can only safely disable when no pads need timeout monitoring
|
||||||
|
this->check_and_disable_loop_if_all_released_(pads_off);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ESP32TouchComponent::on_shutdown() {
|
||||||
|
touch_pad_intr_disable();
|
||||||
|
touch_pad_isr_deregister(touch_isr_handler, this);
|
||||||
|
this->cleanup_touch_queue_();
|
||||||
|
|
||||||
|
if (this->iir_filter_enabled_()) {
|
||||||
|
touch_pad_filter_stop();
|
||||||
|
touch_pad_filter_delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure wakeup pads if any are set
|
||||||
|
this->configure_wakeup_pads_();
|
||||||
|
}
|
||||||
|
|
||||||
|
void IRAM_ATTR ESP32TouchComponent::touch_isr_handler(void *arg) {
|
||||||
|
ESP32TouchComponent *component = static_cast<ESP32TouchComponent *>(arg);
|
||||||
|
|
||||||
|
touch_pad_clear_status();
|
||||||
|
|
||||||
|
// INTERRUPT BEHAVIOR: On ESP32 v1 hardware, the interrupt fires when ANY configured
|
||||||
|
// touch pad detects a touch (value goes below threshold). The hardware does NOT
|
||||||
|
// generate interrupts on release - only on touch events.
|
||||||
|
// The interrupt will continue to fire periodically (based on sleep_cycle) as long
|
||||||
|
// as any pad remains touched. This allows us to detect both new touches and
|
||||||
|
// continued touches, but releases must be detected by timeout in the main loop.
|
||||||
|
|
||||||
|
// Process all configured pads to check their current state
|
||||||
|
// Note: ESP32 v1 doesn't tell us which specific pad triggered the interrupt,
|
||||||
|
// so we must scan all configured pads to find which ones were touched
|
||||||
|
for (auto *child : component->children_) {
|
||||||
|
touch_pad_t pad = child->get_touch_pad();
|
||||||
|
|
||||||
|
// Read current value using ISR-safe API
|
||||||
|
uint32_t value;
|
||||||
|
if (component->iir_filter_enabled_()) {
|
||||||
|
uint16_t temp_value = 0;
|
||||||
|
touch_pad_read_filtered(pad, &temp_value);
|
||||||
|
value = temp_value;
|
||||||
|
} else {
|
||||||
|
// Use low-level HAL function when filter is not enabled
|
||||||
|
value = touch_ll_read_raw_data(pad);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip pads with 0 value - they haven't been measured in this cycle
|
||||||
|
// This is important: not all pads are measured every interrupt cycle,
|
||||||
|
// only those that the hardware has updated
|
||||||
|
if (value == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// IMPORTANT: ESP32 v1 touch detection logic - INVERTED compared to v2!
|
||||||
|
// ESP32 v1: Touch is detected when capacitance INCREASES, causing the measured value to DECREASE
|
||||||
|
// Therefore: touched = (value < threshold)
|
||||||
|
// This is opposite to ESP32-S2/S3 v2 where touched = (value > threshold)
|
||||||
|
bool is_touched = value < child->get_threshold();
|
||||||
|
|
||||||
|
// Always send the current state - the main loop will filter for changes
|
||||||
|
// We send both touched and untouched states because the ISR doesn't
|
||||||
|
// track previous state (to keep ISR fast and simple)
|
||||||
|
TouchPadEventV1 event;
|
||||||
|
event.pad = pad;
|
||||||
|
event.value = value;
|
||||||
|
event.is_touched = is_touched;
|
||||||
|
|
||||||
|
// Send to queue from ISR - non-blocking, drops if queue full
|
||||||
|
BaseType_t x_higher_priority_task_woken = pdFALSE;
|
||||||
|
xQueueSendFromISR(component->touch_queue_, &event, &x_higher_priority_task_woken);
|
||||||
|
component->enable_loop_soon_any_context();
|
||||||
|
if (x_higher_priority_task_woken) {
|
||||||
|
portYIELD_FROM_ISR();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace esp32_touch
|
||||||
|
} // namespace esphome
|
||||||
|
|
||||||
|
#endif // USE_ESP32_VARIANT_ESP32
|
397
esphome/components/esp32_touch/esp32_touch_v2.cpp
Normal file
397
esphome/components/esp32_touch/esp32_touch_v2.cpp
Normal file
@ -0,0 +1,397 @@
|
|||||||
|
#if defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
|
||||||
|
|
||||||
|
#include "esp32_touch.h"
|
||||||
|
#include "esphome/core/application.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
#include "esphome/core/hal.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace esp32_touch {
|
||||||
|
|
||||||
|
static const char *const TAG = "esp32_touch";
|
||||||
|
|
||||||
|
// Helper to update touch state with a known state
|
||||||
|
void ESP32TouchComponent::update_touch_state_(ESP32TouchBinarySensor *child, bool is_touched) {
|
||||||
|
// Always update timer when touched
|
||||||
|
if (is_touched) {
|
||||||
|
child->last_touch_time_ = App.get_loop_component_start_time();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (child->last_state_ != is_touched) {
|
||||||
|
child->last_state_ = is_touched;
|
||||||
|
child->publish_state(is_touched);
|
||||||
|
if (is_touched) {
|
||||||
|
// ESP32-S2/S3 v2: touched when value > threshold
|
||||||
|
ESP_LOGV(TAG, "Touch Pad '%s' state: ON (value: %" PRIu32 " > threshold: %" PRIu32 ")", child->get_name().c_str(),
|
||||||
|
this->read_touch_value(child->touch_pad_), child->threshold_ + child->benchmark_);
|
||||||
|
} else {
|
||||||
|
ESP_LOGV(TAG, "Touch Pad '%s' state: OFF", child->get_name().c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper to read touch value and update state for a given child (used for timeout events)
|
||||||
|
bool ESP32TouchComponent::check_and_update_touch_state_(ESP32TouchBinarySensor *child) {
|
||||||
|
// Read current touch value
|
||||||
|
uint32_t value = this->read_touch_value(child->touch_pad_);
|
||||||
|
|
||||||
|
// ESP32-S2/S3 v2: Touch is detected when value > threshold + benchmark
|
||||||
|
ESP_LOGV(TAG,
|
||||||
|
"Checking touch state for '%s' (T%d): value = %" PRIu32 ", threshold = %" PRIu32 ", benchmark = %" PRIu32,
|
||||||
|
child->get_name().c_str(), child->touch_pad_, value, child->threshold_, child->benchmark_);
|
||||||
|
bool is_touched = value > child->benchmark_ + child->threshold_;
|
||||||
|
|
||||||
|
this->update_touch_state_(child, is_touched);
|
||||||
|
return is_touched;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ESP32TouchComponent::setup() {
|
||||||
|
// Create queue for touch events first
|
||||||
|
if (!this->create_touch_queue_()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize touch pad peripheral
|
||||||
|
esp_err_t init_err = touch_pad_init();
|
||||||
|
if (init_err != ESP_OK) {
|
||||||
|
ESP_LOGE(TAG, "Failed to initialize touch pad: %s", esp_err_to_name(init_err));
|
||||||
|
this->mark_failed();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure each touch pad first
|
||||||
|
for (auto *child : this->children_) {
|
||||||
|
esp_err_t config_err = touch_pad_config(child->touch_pad_);
|
||||||
|
if (config_err != ESP_OK) {
|
||||||
|
ESP_LOGE(TAG, "Failed to configure touch pad %d: %s", child->touch_pad_, esp_err_to_name(config_err));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up filtering if configured
|
||||||
|
if (this->filter_configured_()) {
|
||||||
|
touch_filter_config_t filter_info = {
|
||||||
|
.mode = this->filter_mode_,
|
||||||
|
.debounce_cnt = this->debounce_count_,
|
||||||
|
.noise_thr = this->noise_threshold_,
|
||||||
|
.jitter_step = this->jitter_step_,
|
||||||
|
.smh_lvl = this->smooth_level_,
|
||||||
|
};
|
||||||
|
touch_pad_filter_set_config(&filter_info);
|
||||||
|
touch_pad_filter_enable();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this->denoise_configured_()) {
|
||||||
|
touch_pad_denoise_t denoise = {
|
||||||
|
.grade = this->grade_,
|
||||||
|
.cap_level = this->cap_level_,
|
||||||
|
};
|
||||||
|
touch_pad_denoise_set_config(&denoise);
|
||||||
|
touch_pad_denoise_enable();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this->waterproof_configured_()) {
|
||||||
|
touch_pad_waterproof_t waterproof = {
|
||||||
|
.guard_ring_pad = this->waterproof_guard_ring_pad_,
|
||||||
|
.shield_driver = this->waterproof_shield_driver_,
|
||||||
|
};
|
||||||
|
touch_pad_waterproof_set_config(&waterproof);
|
||||||
|
touch_pad_waterproof_enable();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure measurement parameters
|
||||||
|
touch_pad_set_voltage(this->high_voltage_reference_, this->low_voltage_reference_, this->voltage_attenuation_);
|
||||||
|
touch_pad_set_charge_discharge_times(this->meas_cycle_);
|
||||||
|
touch_pad_set_measurement_interval(this->sleep_cycle_);
|
||||||
|
|
||||||
|
// Configure timeout if needed
|
||||||
|
touch_pad_timeout_set(true, TOUCH_PAD_THRESHOLD_MAX);
|
||||||
|
|
||||||
|
// Register ISR handler with interrupt mask
|
||||||
|
esp_err_t err =
|
||||||
|
touch_pad_isr_register(touch_isr_handler, this, static_cast<touch_pad_intr_mask_t>(TOUCH_PAD_INTR_MASK_ALL));
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
ESP_LOGE(TAG, "Failed to register touch ISR: %s", esp_err_to_name(err));
|
||||||
|
this->cleanup_touch_queue_();
|
||||||
|
this->mark_failed();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set thresholds for each pad BEFORE starting FSM
|
||||||
|
for (auto *child : this->children_) {
|
||||||
|
if (child->threshold_ != 0) {
|
||||||
|
touch_pad_set_thresh(child->touch_pad_, child->threshold_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enable interrupts - only ACTIVE and TIMEOUT
|
||||||
|
// NOTE: We intentionally don't enable INACTIVE interrupts because they are unreliable
|
||||||
|
// on ESP32-S2/S3 hardware and sometimes don't fire. Instead, we use timeout-based
|
||||||
|
// release detection with the ability to verify the actual state.
|
||||||
|
touch_pad_intr_enable(static_cast<touch_pad_intr_mask_t>(TOUCH_PAD_INTR_MASK_ACTIVE | TOUCH_PAD_INTR_MASK_TIMEOUT));
|
||||||
|
|
||||||
|
// Set FSM mode before starting
|
||||||
|
touch_pad_set_fsm_mode(TOUCH_FSM_MODE_TIMER);
|
||||||
|
|
||||||
|
// Start FSM
|
||||||
|
touch_pad_fsm_start();
|
||||||
|
|
||||||
|
// Calculate release timeout based on sleep cycle
|
||||||
|
this->calculate_release_timeout_();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ESP32TouchComponent::dump_config() {
|
||||||
|
this->dump_config_base_();
|
||||||
|
|
||||||
|
if (this->filter_configured_()) {
|
||||||
|
const char *filter_mode_s;
|
||||||
|
switch (this->filter_mode_) {
|
||||||
|
case TOUCH_PAD_FILTER_IIR_4:
|
||||||
|
filter_mode_s = "IIR_4";
|
||||||
|
break;
|
||||||
|
case TOUCH_PAD_FILTER_IIR_8:
|
||||||
|
filter_mode_s = "IIR_8";
|
||||||
|
break;
|
||||||
|
case TOUCH_PAD_FILTER_IIR_16:
|
||||||
|
filter_mode_s = "IIR_16";
|
||||||
|
break;
|
||||||
|
case TOUCH_PAD_FILTER_IIR_32:
|
||||||
|
filter_mode_s = "IIR_32";
|
||||||
|
break;
|
||||||
|
case TOUCH_PAD_FILTER_IIR_64:
|
||||||
|
filter_mode_s = "IIR_64";
|
||||||
|
break;
|
||||||
|
case TOUCH_PAD_FILTER_IIR_128:
|
||||||
|
filter_mode_s = "IIR_128";
|
||||||
|
break;
|
||||||
|
case TOUCH_PAD_FILTER_IIR_256:
|
||||||
|
filter_mode_s = "IIR_256";
|
||||||
|
break;
|
||||||
|
case TOUCH_PAD_FILTER_JITTER:
|
||||||
|
filter_mode_s = "JITTER";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
filter_mode_s = "UNKNOWN";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
ESP_LOGCONFIG(TAG,
|
||||||
|
" Filter mode: %s\n"
|
||||||
|
" Debounce count: %" PRIu32 "\n"
|
||||||
|
" Noise threshold coefficient: %" PRIu32 "\n"
|
||||||
|
" Jitter filter step size: %" PRIu32,
|
||||||
|
filter_mode_s, this->debounce_count_, this->noise_threshold_, this->jitter_step_);
|
||||||
|
const char *smooth_level_s;
|
||||||
|
switch (this->smooth_level_) {
|
||||||
|
case TOUCH_PAD_SMOOTH_OFF:
|
||||||
|
smooth_level_s = "OFF";
|
||||||
|
break;
|
||||||
|
case TOUCH_PAD_SMOOTH_IIR_2:
|
||||||
|
smooth_level_s = "IIR_2";
|
||||||
|
break;
|
||||||
|
case TOUCH_PAD_SMOOTH_IIR_4:
|
||||||
|
smooth_level_s = "IIR_4";
|
||||||
|
break;
|
||||||
|
case TOUCH_PAD_SMOOTH_IIR_8:
|
||||||
|
smooth_level_s = "IIR_8";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
smooth_level_s = "UNKNOWN";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
ESP_LOGCONFIG(TAG, " Smooth level: %s", smooth_level_s);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this->denoise_configured_()) {
|
||||||
|
const char *grade_s;
|
||||||
|
switch (this->grade_) {
|
||||||
|
case TOUCH_PAD_DENOISE_BIT12:
|
||||||
|
grade_s = "BIT12";
|
||||||
|
break;
|
||||||
|
case TOUCH_PAD_DENOISE_BIT10:
|
||||||
|
grade_s = "BIT10";
|
||||||
|
break;
|
||||||
|
case TOUCH_PAD_DENOISE_BIT8:
|
||||||
|
grade_s = "BIT8";
|
||||||
|
break;
|
||||||
|
case TOUCH_PAD_DENOISE_BIT4:
|
||||||
|
grade_s = "BIT4";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
grade_s = "UNKNOWN";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
ESP_LOGCONFIG(TAG, " Denoise grade: %s", grade_s);
|
||||||
|
|
||||||
|
const char *cap_level_s;
|
||||||
|
switch (this->cap_level_) {
|
||||||
|
case TOUCH_PAD_DENOISE_CAP_L0:
|
||||||
|
cap_level_s = "L0";
|
||||||
|
break;
|
||||||
|
case TOUCH_PAD_DENOISE_CAP_L1:
|
||||||
|
cap_level_s = "L1";
|
||||||
|
break;
|
||||||
|
case TOUCH_PAD_DENOISE_CAP_L2:
|
||||||
|
cap_level_s = "L2";
|
||||||
|
break;
|
||||||
|
case TOUCH_PAD_DENOISE_CAP_L3:
|
||||||
|
cap_level_s = "L3";
|
||||||
|
break;
|
||||||
|
case TOUCH_PAD_DENOISE_CAP_L4:
|
||||||
|
cap_level_s = "L4";
|
||||||
|
break;
|
||||||
|
case TOUCH_PAD_DENOISE_CAP_L5:
|
||||||
|
cap_level_s = "L5";
|
||||||
|
break;
|
||||||
|
case TOUCH_PAD_DENOISE_CAP_L6:
|
||||||
|
cap_level_s = "L6";
|
||||||
|
break;
|
||||||
|
case TOUCH_PAD_DENOISE_CAP_L7:
|
||||||
|
cap_level_s = "L7";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
cap_level_s = "UNKNOWN";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
ESP_LOGCONFIG(TAG, " Denoise capacitance level: %s", cap_level_s);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this->setup_mode_) {
|
||||||
|
ESP_LOGCONFIG(TAG, " Setup Mode ENABLED");
|
||||||
|
}
|
||||||
|
|
||||||
|
this->dump_config_sensors_();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ESP32TouchComponent::loop() {
|
||||||
|
const uint32_t now = App.get_loop_component_start_time();
|
||||||
|
|
||||||
|
// V2 TOUCH HANDLING:
|
||||||
|
// Due to unreliable INACTIVE interrupts on ESP32-S2/S3, we use a hybrid approach:
|
||||||
|
// 1. Process ACTIVE interrupts when pads are touched
|
||||||
|
// 2. Use timeout-based release detection (like v1)
|
||||||
|
// 3. But smarter than v1: verify actual state before releasing on timeout
|
||||||
|
// This prevents false releases if we missed interrupts
|
||||||
|
|
||||||
|
// In setup mode, periodically log all pad values
|
||||||
|
this->process_setup_mode_logging_(now);
|
||||||
|
|
||||||
|
// Process any queued touch events from interrupts
|
||||||
|
TouchPadEventV2 event;
|
||||||
|
while (xQueueReceive(this->touch_queue_, &event, 0) == pdTRUE) {
|
||||||
|
ESP_LOGD(TAG, "Event received, mask = 0x%" PRIx32 ", pad = %d", event.intr_mask, event.pad);
|
||||||
|
// Handle timeout events
|
||||||
|
if (event.intr_mask & TOUCH_PAD_INTR_MASK_TIMEOUT) {
|
||||||
|
// Resume measurement after timeout
|
||||||
|
touch_pad_timeout_resume();
|
||||||
|
// For timeout events, always check the current state
|
||||||
|
} else if (!(event.intr_mask & TOUCH_PAD_INTR_MASK_ACTIVE)) {
|
||||||
|
// Skip if not an active/timeout event
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the child for the pad that triggered the interrupt
|
||||||
|
for (auto *child : this->children_) {
|
||||||
|
if (child->touch_pad_ == event.pad) {
|
||||||
|
if (event.intr_mask & TOUCH_PAD_INTR_MASK_TIMEOUT) {
|
||||||
|
// For timeout events, we need to read the value to determine state
|
||||||
|
this->check_and_update_touch_state_(child);
|
||||||
|
} else if (event.intr_mask & TOUCH_PAD_INTR_MASK_ACTIVE) {
|
||||||
|
// We only get ACTIVE interrupts now, releases are detected by timeout
|
||||||
|
this->update_touch_state_(child, true); // Always touched for ACTIVE interrupts
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for released pads periodically (like v1)
|
||||||
|
if (!this->should_check_for_releases_(now)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t pads_off = 0;
|
||||||
|
for (auto *child : this->children_) {
|
||||||
|
if (child->benchmark_ == 0)
|
||||||
|
touch_pad_read_benchmark(child->touch_pad_, &child->benchmark_);
|
||||||
|
// Handle initial state publication after startup
|
||||||
|
this->publish_initial_state_if_needed_(child, now);
|
||||||
|
|
||||||
|
if (child->last_state_) {
|
||||||
|
// Pad is currently in touched state - check for release timeout
|
||||||
|
// Using subtraction handles 32-bit rollover correctly
|
||||||
|
uint32_t time_diff = now - child->last_touch_time_;
|
||||||
|
|
||||||
|
// Check if we haven't seen this pad recently
|
||||||
|
if (time_diff > this->release_timeout_ms_) {
|
||||||
|
// Haven't seen this pad recently - verify actual state
|
||||||
|
// Unlike v1, v2 hardware allows us to read the current state anytime
|
||||||
|
// This makes v2 smarter: we can verify if it's actually released before
|
||||||
|
// declaring a timeout, preventing false releases if interrupts were missed
|
||||||
|
bool still_touched = this->check_and_update_touch_state_(child);
|
||||||
|
|
||||||
|
if (still_touched) {
|
||||||
|
// Still touched! Timer was reset in update_touch_state_
|
||||||
|
ESP_LOGVV(TAG, "Touch Pad '%s' still touched after %" PRIu32 "ms timeout, resetting timer",
|
||||||
|
child->get_name().c_str(), this->release_timeout_ms_);
|
||||||
|
} else {
|
||||||
|
// Actually released - already handled by check_and_update_touch_state_
|
||||||
|
pads_off++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Pad is already off
|
||||||
|
pads_off++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disable the loop when all pads are off and not in setup mode (like v1)
|
||||||
|
// We need to keep checking for timeouts, so only disable when all pads are confirmed off
|
||||||
|
this->check_and_disable_loop_if_all_released_(pads_off);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ESP32TouchComponent::on_shutdown() {
|
||||||
|
// Disable interrupts
|
||||||
|
touch_pad_intr_disable(static_cast<touch_pad_intr_mask_t>(TOUCH_PAD_INTR_MASK_ACTIVE | TOUCH_PAD_INTR_MASK_TIMEOUT));
|
||||||
|
touch_pad_isr_deregister(touch_isr_handler, this);
|
||||||
|
this->cleanup_touch_queue_();
|
||||||
|
|
||||||
|
// Configure wakeup pads if any are set
|
||||||
|
this->configure_wakeup_pads_();
|
||||||
|
}
|
||||||
|
|
||||||
|
void IRAM_ATTR ESP32TouchComponent::touch_isr_handler(void *arg) {
|
||||||
|
ESP32TouchComponent *component = static_cast<ESP32TouchComponent *>(arg);
|
||||||
|
BaseType_t x_higher_priority_task_woken = pdFALSE;
|
||||||
|
|
||||||
|
// Read interrupt status
|
||||||
|
TouchPadEventV2 event;
|
||||||
|
event.intr_mask = touch_pad_read_intr_status_mask();
|
||||||
|
event.pad = touch_pad_get_current_meas_channel();
|
||||||
|
|
||||||
|
// Send event to queue for processing in main loop
|
||||||
|
xQueueSendFromISR(component->touch_queue_, &event, &x_higher_priority_task_woken);
|
||||||
|
component->enable_loop_soon_any_context();
|
||||||
|
|
||||||
|
if (x_higher_priority_task_woken) {
|
||||||
|
portYIELD_FROM_ISR();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t ESP32TouchComponent::read_touch_value(touch_pad_t pad) const {
|
||||||
|
// Unlike ESP32 v1, touch reads on ESP32-S2/S3 v2 are non-blocking operations.
|
||||||
|
// The hardware continuously samples in the background and we can read the
|
||||||
|
// latest value at any time without waiting.
|
||||||
|
uint32_t value = 0;
|
||||||
|
if (this->filter_configured_()) {
|
||||||
|
// Read filtered/smoothed value when filter is enabled
|
||||||
|
touch_pad_filter_read_smooth(pad, &value);
|
||||||
|
} else {
|
||||||
|
// Read raw value when filter is not configured
|
||||||
|
touch_pad_read_raw_data(pad, &value);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace esp32_touch
|
||||||
|
} // namespace esphome
|
||||||
|
|
||||||
|
#endif // USE_ESP32_VARIANT_ESP32S2 || USE_ESP32_VARIANT_ESP32S3
|
@ -23,8 +23,10 @@ from esphome.const import (
|
|||||||
CONF_INTERRUPT_PIN,
|
CONF_INTERRUPT_PIN,
|
||||||
CONF_MANUAL_IP,
|
CONF_MANUAL_IP,
|
||||||
CONF_MISO_PIN,
|
CONF_MISO_PIN,
|
||||||
|
CONF_MODE,
|
||||||
CONF_MOSI_PIN,
|
CONF_MOSI_PIN,
|
||||||
CONF_PAGE_ID,
|
CONF_PAGE_ID,
|
||||||
|
CONF_PIN,
|
||||||
CONF_POLLING_INTERVAL,
|
CONF_POLLING_INTERVAL,
|
||||||
CONF_RESET_PIN,
|
CONF_RESET_PIN,
|
||||||
CONF_SPI,
|
CONF_SPI,
|
||||||
@ -49,6 +51,7 @@ PHYRegister = ethernet_ns.struct("PHYRegister")
|
|||||||
CONF_PHY_ADDR = "phy_addr"
|
CONF_PHY_ADDR = "phy_addr"
|
||||||
CONF_MDC_PIN = "mdc_pin"
|
CONF_MDC_PIN = "mdc_pin"
|
||||||
CONF_MDIO_PIN = "mdio_pin"
|
CONF_MDIO_PIN = "mdio_pin"
|
||||||
|
CONF_CLK = "clk"
|
||||||
CONF_CLK_MODE = "clk_mode"
|
CONF_CLK_MODE = "clk_mode"
|
||||||
CONF_POWER_PIN = "power_pin"
|
CONF_POWER_PIN = "power_pin"
|
||||||
CONF_PHY_REGISTERS = "phy_registers"
|
CONF_PHY_REGISTERS = "phy_registers"
|
||||||
@ -73,26 +76,18 @@ SPI_ETHERNET_TYPES = ["W5500", "DM9051"]
|
|||||||
SPI_ETHERNET_DEFAULT_POLLING_INTERVAL = TimePeriodMilliseconds(milliseconds=10)
|
SPI_ETHERNET_DEFAULT_POLLING_INTERVAL = TimePeriodMilliseconds(milliseconds=10)
|
||||||
|
|
||||||
emac_rmii_clock_mode_t = cg.global_ns.enum("emac_rmii_clock_mode_t")
|
emac_rmii_clock_mode_t = cg.global_ns.enum("emac_rmii_clock_mode_t")
|
||||||
emac_rmii_clock_gpio_t = cg.global_ns.enum("emac_rmii_clock_gpio_t")
|
|
||||||
CLK_MODES = {
|
CLK_MODES = {
|
||||||
"GPIO0_IN": (
|
"CLK_EXT_IN": emac_rmii_clock_mode_t.EMAC_CLK_EXT_IN,
|
||||||
emac_rmii_clock_mode_t.EMAC_CLK_EXT_IN,
|
"CLK_OUT": emac_rmii_clock_mode_t.EMAC_CLK_OUT,
|
||||||
emac_rmii_clock_gpio_t.EMAC_CLK_IN_GPIO,
|
|
||||||
),
|
|
||||||
"GPIO0_OUT": (
|
|
||||||
emac_rmii_clock_mode_t.EMAC_CLK_OUT,
|
|
||||||
emac_rmii_clock_gpio_t.EMAC_APPL_CLK_OUT_GPIO,
|
|
||||||
),
|
|
||||||
"GPIO16_OUT": (
|
|
||||||
emac_rmii_clock_mode_t.EMAC_CLK_OUT,
|
|
||||||
emac_rmii_clock_gpio_t.EMAC_CLK_OUT_GPIO,
|
|
||||||
),
|
|
||||||
"GPIO17_OUT": (
|
|
||||||
emac_rmii_clock_mode_t.EMAC_CLK_OUT,
|
|
||||||
emac_rmii_clock_gpio_t.EMAC_CLK_OUT_180_GPIO,
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CLK_MODES_DEPRECATED = {
|
||||||
|
"GPIO0_IN": ("CLK_EXT_IN", 0),
|
||||||
|
"GPIO0_OUT": ("CLK_OUT", 0),
|
||||||
|
"GPIO16_OUT": ("CLK_OUT", 16),
|
||||||
|
"GPIO17_OUT": ("CLK_OUT", 17),
|
||||||
|
}
|
||||||
|
|
||||||
MANUAL_IP_SCHEMA = cv.Schema(
|
MANUAL_IP_SCHEMA = cv.Schema(
|
||||||
{
|
{
|
||||||
@ -154,6 +149,18 @@ def _validate(config):
|
|||||||
f"({CORE.target_framework} {CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION]}), "
|
f"({CORE.target_framework} {CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION]}), "
|
||||||
f"'{CONF_INTERRUPT_PIN}' is a required option for [ethernet]."
|
f"'{CONF_INTERRUPT_PIN}' is a required option for [ethernet]."
|
||||||
)
|
)
|
||||||
|
elif config[CONF_TYPE] != "OPENETH":
|
||||||
|
if CONF_CLK_MODE in config:
|
||||||
|
LOGGER.warning(
|
||||||
|
"[ethernet] The 'clk_mode' option is deprecated and will be removed in ESPHome 2026.1. "
|
||||||
|
"Please update your configuration to use 'clk' instead."
|
||||||
|
)
|
||||||
|
mode = CLK_MODES_DEPRECATED[config[CONF_CLK_MODE]]
|
||||||
|
config[CONF_CLK] = CLK_SCHEMA({CONF_MODE: mode[0], CONF_PIN: mode[1]})
|
||||||
|
del config[CONF_CLK_MODE]
|
||||||
|
elif CONF_CLK not in config:
|
||||||
|
raise cv.Invalid("'clk' is a required option for [ethernet].")
|
||||||
|
|
||||||
return config
|
return config
|
||||||
|
|
||||||
|
|
||||||
@ -177,14 +184,21 @@ PHY_REGISTER_SCHEMA = cv.Schema(
|
|||||||
cv.Optional(CONF_PAGE_ID): cv.hex_int,
|
cv.Optional(CONF_PAGE_ID): cv.hex_int,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
CLK_SCHEMA = cv.Schema(
|
||||||
|
{
|
||||||
|
cv.Required(CONF_MODE): cv.enum(CLK_MODES, upper=True, space="_"),
|
||||||
|
cv.Required(CONF_PIN): pins.internal_gpio_pin_number,
|
||||||
|
}
|
||||||
|
)
|
||||||
RMII_SCHEMA = BASE_SCHEMA.extend(
|
RMII_SCHEMA = BASE_SCHEMA.extend(
|
||||||
cv.Schema(
|
cv.Schema(
|
||||||
{
|
{
|
||||||
cv.Required(CONF_MDC_PIN): pins.internal_gpio_output_pin_number,
|
cv.Required(CONF_MDC_PIN): pins.internal_gpio_output_pin_number,
|
||||||
cv.Required(CONF_MDIO_PIN): pins.internal_gpio_output_pin_number,
|
cv.Required(CONF_MDIO_PIN): pins.internal_gpio_output_pin_number,
|
||||||
cv.Optional(CONF_CLK_MODE, default="GPIO0_IN"): cv.enum(
|
cv.Optional(CONF_CLK_MODE): cv.enum(
|
||||||
CLK_MODES, upper=True, space="_"
|
CLK_MODES_DEPRECATED, upper=True, space="_"
|
||||||
),
|
),
|
||||||
|
cv.Optional(CONF_CLK): CLK_SCHEMA,
|
||||||
cv.Optional(CONF_PHY_ADDR, default=0): cv.int_range(min=0, max=31),
|
cv.Optional(CONF_PHY_ADDR, default=0): cv.int_range(min=0, max=31),
|
||||||
cv.Optional(CONF_POWER_PIN): pins.internal_gpio_output_pin_number,
|
cv.Optional(CONF_POWER_PIN): pins.internal_gpio_output_pin_number,
|
||||||
cv.Optional(CONF_PHY_REGISTERS): cv.ensure_list(PHY_REGISTER_SCHEMA),
|
cv.Optional(CONF_PHY_REGISTERS): cv.ensure_list(PHY_REGISTER_SCHEMA),
|
||||||
@ -308,7 +322,8 @@ async def to_code(config):
|
|||||||
cg.add(var.set_phy_addr(config[CONF_PHY_ADDR]))
|
cg.add(var.set_phy_addr(config[CONF_PHY_ADDR]))
|
||||||
cg.add(var.set_mdc_pin(config[CONF_MDC_PIN]))
|
cg.add(var.set_mdc_pin(config[CONF_MDC_PIN]))
|
||||||
cg.add(var.set_mdio_pin(config[CONF_MDIO_PIN]))
|
cg.add(var.set_mdio_pin(config[CONF_MDIO_PIN]))
|
||||||
cg.add(var.set_clk_mode(*CLK_MODES[config[CONF_CLK_MODE]]))
|
cg.add(var.set_clk_mode(config[CONF_CLK][CONF_MODE]))
|
||||||
|
cg.add(var.set_clk_pin(config[CONF_CLK][CONF_PIN]))
|
||||||
if CONF_POWER_PIN in config:
|
if CONF_POWER_PIN in config:
|
||||||
cg.add(var.set_power_pin(config[CONF_POWER_PIN]))
|
cg.add(var.set_power_pin(config[CONF_POWER_PIN]))
|
||||||
for register_value in config.get(CONF_PHY_REGISTERS, []):
|
for register_value in config.get(CONF_PHY_REGISTERS, []):
|
||||||
|
@ -19,11 +19,7 @@
|
|||||||
#include <sys/cdefs.h>
|
#include <sys/cdefs.h>
|
||||||
#include "esp_log.h"
|
#include "esp_log.h"
|
||||||
#include "esp_eth.h"
|
#include "esp_eth.h"
|
||||||
#if ESP_IDF_VERSION_MAJOR >= 5
|
|
||||||
#include "esp_eth_phy_802_3.h"
|
#include "esp_eth_phy_802_3.h"
|
||||||
#else
|
|
||||||
#include "eth_phy_regs_struct.h"
|
|
||||||
#endif
|
|
||||||
#include "freertos/FreeRTOS.h"
|
#include "freertos/FreeRTOS.h"
|
||||||
#include "freertos/task.h"
|
#include "freertos/task.h"
|
||||||
#include "driver/gpio.h"
|
#include "driver/gpio.h"
|
||||||
@ -174,11 +170,7 @@ static esp_err_t jl1101_reset_hw(esp_eth_phy_t *phy) {
|
|||||||
return ESP_OK;
|
return ESP_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if ESP_IDF_VERSION_MAJOR >= 5
|
|
||||||
static esp_err_t jl1101_negotiate(esp_eth_phy_t *phy, eth_phy_autoneg_cmd_t cmd, bool *nego_state) {
|
static esp_err_t jl1101_negotiate(esp_eth_phy_t *phy, eth_phy_autoneg_cmd_t cmd, bool *nego_state) {
|
||||||
#else
|
|
||||||
static esp_err_t jl1101_negotiate(esp_eth_phy_t *phy) {
|
|
||||||
#endif
|
|
||||||
phy_jl1101_t *jl1101 = __containerof(phy, phy_jl1101_t, parent);
|
phy_jl1101_t *jl1101 = __containerof(phy, phy_jl1101_t, parent);
|
||||||
esp_eth_mediator_t *eth = jl1101->eth;
|
esp_eth_mediator_t *eth = jl1101->eth;
|
||||||
/* in case any link status has changed, let's assume we're in link down status */
|
/* in case any link status has changed, let's assume we're in link down status */
|
||||||
@ -293,11 +285,7 @@ static esp_err_t jl1101_init(esp_eth_phy_t *phy) {
|
|||||||
esp_eth_mediator_t *eth = jl1101->eth;
|
esp_eth_mediator_t *eth = jl1101->eth;
|
||||||
// Detect PHY address
|
// Detect PHY address
|
||||||
if (jl1101->addr == ESP_ETH_PHY_ADDR_AUTO) {
|
if (jl1101->addr == ESP_ETH_PHY_ADDR_AUTO) {
|
||||||
#if ESP_IDF_VERSION_MAJOR >= 5
|
|
||||||
PHY_CHECK(esp_eth_phy_802_3_detect_phy_addr(eth, &jl1101->addr) == ESP_OK, "Detect PHY address failed", err);
|
PHY_CHECK(esp_eth_phy_802_3_detect_phy_addr(eth, &jl1101->addr) == ESP_OK, "Detect PHY address failed", err);
|
||||||
#else
|
|
||||||
PHY_CHECK(esp_eth_detect_phy_addr(eth, &jl1101->addr) == ESP_OK, "Detect PHY address failed", err);
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
/* Power on Ethernet PHY */
|
/* Power on Ethernet PHY */
|
||||||
PHY_CHECK(jl1101_pwrctl(phy, true) == ESP_OK, "power control failed", err);
|
PHY_CHECK(jl1101_pwrctl(phy, true) == ESP_OK, "power control failed", err);
|
||||||
@ -336,11 +324,7 @@ esp_eth_phy_t *esp_eth_phy_new_jl1101(const eth_phy_config_t *config) {
|
|||||||
jl1101->parent.init = jl1101_init;
|
jl1101->parent.init = jl1101_init;
|
||||||
jl1101->parent.deinit = jl1101_deinit;
|
jl1101->parent.deinit = jl1101_deinit;
|
||||||
jl1101->parent.set_mediator = jl1101_set_mediator;
|
jl1101->parent.set_mediator = jl1101_set_mediator;
|
||||||
#if ESP_IDF_VERSION_MAJOR >= 5
|
|
||||||
jl1101->parent.autonego_ctrl = jl1101_negotiate;
|
jl1101->parent.autonego_ctrl = jl1101_negotiate;
|
||||||
#else
|
|
||||||
jl1101->parent.negotiate = jl1101_negotiate;
|
|
||||||
#endif
|
|
||||||
jl1101->parent.get_link = jl1101_get_link;
|
jl1101->parent.get_link = jl1101_get_link;
|
||||||
jl1101->parent.pwrctl = jl1101_pwrctl;
|
jl1101->parent.pwrctl = jl1101_pwrctl;
|
||||||
jl1101->parent.get_addr = jl1101_get_addr;
|
jl1101->parent.get_addr = jl1101_get_addr;
|
||||||
|
@ -17,6 +17,22 @@
|
|||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace ethernet {
|
namespace ethernet {
|
||||||
|
|
||||||
|
#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 4, 2)
|
||||||
|
// work around IDF compile issue on P4 https://github.com/espressif/esp-idf/pull/15637
|
||||||
|
#ifdef USE_ESP32_VARIANT_ESP32P4
|
||||||
|
#undef ETH_ESP32_EMAC_DEFAULT_CONFIG
|
||||||
|
#define ETH_ESP32_EMAC_DEFAULT_CONFIG() \
|
||||||
|
{ \
|
||||||
|
.smi_gpio = {.mdc_num = 31, .mdio_num = 52}, .interface = EMAC_DATA_INTERFACE_RMII, \
|
||||||
|
.clock_config = {.rmii = {.clock_mode = EMAC_CLK_EXT_IN, .clock_gpio = (emac_rmii_clock_gpio_t) 50}}, \
|
||||||
|
.dma_burst_len = ETH_DMA_BURST_LEN_32, .intr_priority = 0, \
|
||||||
|
.emac_dataif_gpio = \
|
||||||
|
{.rmii = {.tx_en_num = 49, .txd0_num = 34, .txd1_num = 35, .crs_dv_num = 28, .rxd0_num = 29, .rxd1_num = 30}}, \
|
||||||
|
.clock_config_out_in = {.rmii = {.clock_mode = EMAC_CLK_EXT_IN, .clock_gpio = (emac_rmii_clock_gpio_t) -1}}, \
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
static const char *const TAG = "ethernet";
|
static const char *const TAG = "ethernet";
|
||||||
|
|
||||||
EthernetComponent *global_eth_component; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
EthernetComponent *global_eth_component; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||||
@ -106,25 +122,12 @@ void EthernetComponent::setup() {
|
|||||||
.post_cb = nullptr,
|
.post_cb = nullptr,
|
||||||
};
|
};
|
||||||
|
|
||||||
#if ESP_IDF_VERSION_MAJOR >= 5
|
|
||||||
#if CONFIG_ETH_SPI_ETHERNET_W5500
|
#if CONFIG_ETH_SPI_ETHERNET_W5500
|
||||||
eth_w5500_config_t w5500_config = ETH_W5500_DEFAULT_CONFIG(host, &devcfg);
|
eth_w5500_config_t w5500_config = ETH_W5500_DEFAULT_CONFIG(host, &devcfg);
|
||||||
#endif
|
#endif
|
||||||
#if CONFIG_ETH_SPI_ETHERNET_DM9051
|
#if CONFIG_ETH_SPI_ETHERNET_DM9051
|
||||||
eth_dm9051_config_t dm9051_config = ETH_DM9051_DEFAULT_CONFIG(host, &devcfg);
|
eth_dm9051_config_t dm9051_config = ETH_DM9051_DEFAULT_CONFIG(host, &devcfg);
|
||||||
#endif
|
#endif
|
||||||
#else
|
|
||||||
spi_device_handle_t spi_handle = nullptr;
|
|
||||||
err = spi_bus_add_device(host, &devcfg, &spi_handle);
|
|
||||||
ESPHL_ERROR_CHECK(err, "SPI bus add device error");
|
|
||||||
|
|
||||||
#if CONFIG_ETH_SPI_ETHERNET_W5500
|
|
||||||
eth_w5500_config_t w5500_config = ETH_W5500_DEFAULT_CONFIG(spi_handle);
|
|
||||||
#endif
|
|
||||||
#if CONFIG_ETH_SPI_ETHERNET_DM9051
|
|
||||||
eth_dm9051_config_t dm9051_config = ETH_DM9051_DEFAULT_CONFIG(spi_handle);
|
|
||||||
#endif
|
|
||||||
#endif // ESP_IDF_VERSION_MAJOR >= 5
|
|
||||||
|
|
||||||
#if CONFIG_ETH_SPI_ETHERNET_W5500
|
#if CONFIG_ETH_SPI_ETHERNET_W5500
|
||||||
w5500_config.int_gpio_num = this->interrupt_pin_;
|
w5500_config.int_gpio_num = this->interrupt_pin_;
|
||||||
@ -150,22 +153,18 @@ void EthernetComponent::setup() {
|
|||||||
phy_config.phy_addr = this->phy_addr_;
|
phy_config.phy_addr = this->phy_addr_;
|
||||||
phy_config.reset_gpio_num = this->power_pin_;
|
phy_config.reset_gpio_num = this->power_pin_;
|
||||||
|
|
||||||
#if ESP_IDF_VERSION_MAJOR >= 5
|
|
||||||
eth_esp32_emac_config_t esp32_emac_config = ETH_ESP32_EMAC_DEFAULT_CONFIG();
|
eth_esp32_emac_config_t esp32_emac_config = ETH_ESP32_EMAC_DEFAULT_CONFIG();
|
||||||
|
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0)
|
||||||
|
esp32_emac_config.smi_gpio.mdc_num = this->mdc_pin_;
|
||||||
|
esp32_emac_config.smi_gpio.mdio_num = this->mdio_pin_;
|
||||||
|
#else
|
||||||
esp32_emac_config.smi_mdc_gpio_num = this->mdc_pin_;
|
esp32_emac_config.smi_mdc_gpio_num = this->mdc_pin_;
|
||||||
esp32_emac_config.smi_mdio_gpio_num = this->mdio_pin_;
|
esp32_emac_config.smi_mdio_gpio_num = this->mdio_pin_;
|
||||||
|
#endif
|
||||||
esp32_emac_config.clock_config.rmii.clock_mode = this->clk_mode_;
|
esp32_emac_config.clock_config.rmii.clock_mode = this->clk_mode_;
|
||||||
esp32_emac_config.clock_config.rmii.clock_gpio = this->clk_gpio_;
|
esp32_emac_config.clock_config.rmii.clock_gpio = (emac_rmii_clock_gpio_t) this->clk_pin_;
|
||||||
|
|
||||||
esp_eth_mac_t *mac = esp_eth_mac_new_esp32(&esp32_emac_config, &mac_config);
|
esp_eth_mac_t *mac = esp_eth_mac_new_esp32(&esp32_emac_config, &mac_config);
|
||||||
#else
|
|
||||||
mac_config.smi_mdc_gpio_num = this->mdc_pin_;
|
|
||||||
mac_config.smi_mdio_gpio_num = this->mdio_pin_;
|
|
||||||
mac_config.clock_config.rmii.clock_mode = this->clk_mode_;
|
|
||||||
mac_config.clock_config.rmii.clock_gpio = this->clk_gpio_;
|
|
||||||
|
|
||||||
esp_eth_mac_t *mac = esp_eth_mac_new_esp32(&mac_config);
|
|
||||||
#endif
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
switch (this->type_) {
|
switch (this->type_) {
|
||||||
@ -199,11 +198,7 @@ void EthernetComponent::setup() {
|
|||||||
}
|
}
|
||||||
case ETHERNET_TYPE_KSZ8081:
|
case ETHERNET_TYPE_KSZ8081:
|
||||||
case ETHERNET_TYPE_KSZ8081RNA: {
|
case ETHERNET_TYPE_KSZ8081RNA: {
|
||||||
#if ESP_IDF_VERSION_MAJOR >= 5
|
|
||||||
this->phy_ = esp_eth_phy_new_ksz80xx(&phy_config);
|
this->phy_ = esp_eth_phy_new_ksz80xx(&phy_config);
|
||||||
#else
|
|
||||||
this->phy_ = esp_eth_phy_new_ksz8081(&phy_config);
|
|
||||||
#endif
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@ -387,10 +382,11 @@ void EthernetComponent::dump_config() {
|
|||||||
ESP_LOGCONFIG(TAG, " Power Pin: %u", this->power_pin_);
|
ESP_LOGCONFIG(TAG, " Power Pin: %u", this->power_pin_);
|
||||||
}
|
}
|
||||||
ESP_LOGCONFIG(TAG,
|
ESP_LOGCONFIG(TAG,
|
||||||
|
" CLK Pin: %u\n"
|
||||||
" MDC Pin: %u\n"
|
" MDC Pin: %u\n"
|
||||||
" MDIO Pin: %u\n"
|
" MDIO Pin: %u\n"
|
||||||
" PHY addr: %u",
|
" PHY addr: %u",
|
||||||
this->mdc_pin_, this->mdio_pin_, this->phy_addr_);
|
this->clk_pin_, this->mdc_pin_, this->mdio_pin_, this->phy_addr_);
|
||||||
#endif
|
#endif
|
||||||
ESP_LOGCONFIG(TAG, " Type: %s", eth_type);
|
ESP_LOGCONFIG(TAG, " Type: %s", eth_type);
|
||||||
}
|
}
|
||||||
@ -611,10 +607,8 @@ void EthernetComponent::set_phy_addr(uint8_t phy_addr) { this->phy_addr_ = phy_a
|
|||||||
void EthernetComponent::set_power_pin(int power_pin) { this->power_pin_ = power_pin; }
|
void EthernetComponent::set_power_pin(int power_pin) { this->power_pin_ = power_pin; }
|
||||||
void EthernetComponent::set_mdc_pin(uint8_t mdc_pin) { this->mdc_pin_ = mdc_pin; }
|
void EthernetComponent::set_mdc_pin(uint8_t mdc_pin) { this->mdc_pin_ = mdc_pin; }
|
||||||
void EthernetComponent::set_mdio_pin(uint8_t mdio_pin) { this->mdio_pin_ = mdio_pin; }
|
void EthernetComponent::set_mdio_pin(uint8_t mdio_pin) { this->mdio_pin_ = mdio_pin; }
|
||||||
void EthernetComponent::set_clk_mode(emac_rmii_clock_mode_t clk_mode, emac_rmii_clock_gpio_t clk_gpio) {
|
void EthernetComponent::set_clk_pin(uint8_t clk_pin) { this->clk_pin_ = clk_pin; }
|
||||||
this->clk_mode_ = clk_mode;
|
void EthernetComponent::set_clk_mode(emac_rmii_clock_mode_t clk_mode) { this->clk_mode_ = clk_mode; }
|
||||||
this->clk_gpio_ = clk_gpio;
|
|
||||||
}
|
|
||||||
void EthernetComponent::add_phy_register(PHYRegister register_value) { this->phy_registers_.push_back(register_value); }
|
void EthernetComponent::add_phy_register(PHYRegister register_value) { this->phy_registers_.push_back(register_value); }
|
||||||
#endif
|
#endif
|
||||||
void EthernetComponent::set_type(EthernetType type) { this->type_ = type; }
|
void EthernetComponent::set_type(EthernetType type) { this->type_ = type; }
|
||||||
|
@ -76,7 +76,8 @@ class EthernetComponent : public Component {
|
|||||||
void set_power_pin(int power_pin);
|
void set_power_pin(int power_pin);
|
||||||
void set_mdc_pin(uint8_t mdc_pin);
|
void set_mdc_pin(uint8_t mdc_pin);
|
||||||
void set_mdio_pin(uint8_t mdio_pin);
|
void set_mdio_pin(uint8_t mdio_pin);
|
||||||
void set_clk_mode(emac_rmii_clock_mode_t clk_mode, emac_rmii_clock_gpio_t clk_gpio);
|
void set_clk_pin(uint8_t clk_pin);
|
||||||
|
void set_clk_mode(emac_rmii_clock_mode_t clk_mode);
|
||||||
void add_phy_register(PHYRegister register_value);
|
void add_phy_register(PHYRegister register_value);
|
||||||
#endif
|
#endif
|
||||||
void set_type(EthernetType type);
|
void set_type(EthernetType type);
|
||||||
@ -123,10 +124,10 @@ class EthernetComponent : public Component {
|
|||||||
// Group all 32-bit members first
|
// Group all 32-bit members first
|
||||||
int power_pin_{-1};
|
int power_pin_{-1};
|
||||||
emac_rmii_clock_mode_t clk_mode_{EMAC_CLK_EXT_IN};
|
emac_rmii_clock_mode_t clk_mode_{EMAC_CLK_EXT_IN};
|
||||||
emac_rmii_clock_gpio_t clk_gpio_{EMAC_CLK_IN_GPIO};
|
|
||||||
std::vector<PHYRegister> phy_registers_{};
|
std::vector<PHYRegister> phy_registers_{};
|
||||||
|
|
||||||
// Group all 8-bit members together
|
// Group all 8-bit members together
|
||||||
|
uint8_t clk_pin_{0};
|
||||||
uint8_t phy_addr_{0};
|
uint8_t phy_addr_{0};
|
||||||
uint8_t mdc_pin_{23};
|
uint8_t mdc_pin_{23};
|
||||||
uint8_t mdio_pin_{18};
|
uint8_t mdio_pin_{18};
|
||||||
|
@ -70,6 +70,7 @@ PROTOCOLS = {
|
|||||||
"airway": Protocol.PROTOCOL_AIRWAY,
|
"airway": Protocol.PROTOCOL_AIRWAY,
|
||||||
"bgh_aud": Protocol.PROTOCOL_BGH_AUD,
|
"bgh_aud": Protocol.PROTOCOL_BGH_AUD,
|
||||||
"panasonic_altdke": Protocol.PROTOCOL_PANASONIC_ALTDKE,
|
"panasonic_altdke": Protocol.PROTOCOL_PANASONIC_ALTDKE,
|
||||||
|
"philco_phs32": Protocol.PROTOCOL_PHILCO_PHS32,
|
||||||
"vaillantvai8": Protocol.PROTOCOL_VAILLANTVAI8,
|
"vaillantvai8": Protocol.PROTOCOL_VAILLANTVAI8,
|
||||||
"r51m": Protocol.PROTOCOL_R51M,
|
"r51m": Protocol.PROTOCOL_R51M,
|
||||||
}
|
}
|
||||||
|
@ -65,6 +65,7 @@ const std::map<Protocol, std::function<HeatpumpIR *()>> PROTOCOL_CONSTRUCTOR_MAP
|
|||||||
{PROTOCOL_AIRWAY, []() { return new AIRWAYHeatpumpIR(); }}, // NOLINT
|
{PROTOCOL_AIRWAY, []() { return new AIRWAYHeatpumpIR(); }}, // NOLINT
|
||||||
{PROTOCOL_BGH_AUD, []() { return new BGHHeatpumpIR(); }}, // NOLINT
|
{PROTOCOL_BGH_AUD, []() { return new BGHHeatpumpIR(); }}, // NOLINT
|
||||||
{PROTOCOL_PANASONIC_ALTDKE, []() { return new PanasonicAltDKEHeatpumpIR(); }}, // NOLINT
|
{PROTOCOL_PANASONIC_ALTDKE, []() { return new PanasonicAltDKEHeatpumpIR(); }}, // NOLINT
|
||||||
|
{PROTOCOL_PHILCO_PHS32, []() { return new PhilcoPHS32HeatpumpIR(); }}, // NOLINT
|
||||||
{PROTOCOL_VAILLANTVAI8, []() { return new VaillantHeatpumpIR(); }}, // NOLINT
|
{PROTOCOL_VAILLANTVAI8, []() { return new VaillantHeatpumpIR(); }}, // NOLINT
|
||||||
{PROTOCOL_R51M, []() { return new R51MHeatpumpIR(); }}, // NOLINT
|
{PROTOCOL_R51M, []() { return new R51MHeatpumpIR(); }}, // NOLINT
|
||||||
};
|
};
|
||||||
|
@ -65,6 +65,7 @@ enum Protocol {
|
|||||||
PROTOCOL_AIRWAY,
|
PROTOCOL_AIRWAY,
|
||||||
PROTOCOL_BGH_AUD,
|
PROTOCOL_BGH_AUD,
|
||||||
PROTOCOL_PANASONIC_ALTDKE,
|
PROTOCOL_PANASONIC_ALTDKE,
|
||||||
|
PROTOCOL_PHILCO_PHS32,
|
||||||
PROTOCOL_VAILLANTVAI8,
|
PROTOCOL_VAILLANTVAI8,
|
||||||
PROTOCOL_R51M,
|
PROTOCOL_R51M,
|
||||||
};
|
};
|
||||||
|
@ -44,3 +44,4 @@ async def to_code(config):
|
|||||||
cg.add_build_flag("-std=gnu++20")
|
cg.add_build_flag("-std=gnu++20")
|
||||||
cg.add_define("ESPHOME_BOARD", "host")
|
cg.add_define("ESPHOME_BOARD", "host")
|
||||||
cg.add_platformio_option("platform", "platformio/native")
|
cg.add_platformio_option("platform", "platformio/native")
|
||||||
|
cg.add_platformio_option("lib_ldf_mode", "off")
|
||||||
|
@ -133,7 +133,6 @@ std::shared_ptr<HttpContainer> HttpRequestArduino::perform(std::string url, std:
|
|||||||
std::string header_value = container->client_.header(i).c_str();
|
std::string header_value = container->client_.header(i).c_str();
|
||||||
ESP_LOGD(TAG, "Received response header, name: %s, value: %s", header_name.c_str(), header_value.c_str());
|
ESP_LOGD(TAG, "Received response header, name: %s, value: %s", header_name.c_str(), header_value.c_str());
|
||||||
container->response_headers_[header_name].push_back(header_value);
|
container->response_headers_[header_name].push_back(header_value);
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,7 +42,6 @@ esp_err_t HttpRequestIDF::http_event_handler(esp_http_client_event_t *evt) {
|
|||||||
const std::string header_value = evt->header_value;
|
const std::string header_value = evt->header_value;
|
||||||
ESP_LOGD(TAG, "Received response header, name: %s, value: %s", header_name.c_str(), header_value.c_str());
|
ESP_LOGD(TAG, "Received response header, name: %s, value: %s", header_name.c_str(), header_value.c_str());
|
||||||
user_data->response_headers[header_name].push_back(header_value);
|
user_data->response_headers[header_name].push_back(header_value);
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -57,7 +57,7 @@ void HttpRequestUpdate::update_task(void *params) {
|
|||||||
RAMAllocator<uint8_t> allocator;
|
RAMAllocator<uint8_t> allocator;
|
||||||
uint8_t *data = allocator.allocate(container->content_length);
|
uint8_t *data = allocator.allocate(container->content_length);
|
||||||
if (data == nullptr) {
|
if (data == nullptr) {
|
||||||
std::string msg = str_sprintf("Failed to allocate %d bytes for manifest", container->content_length);
|
std::string msg = str_sprintf("Failed to allocate %zu bytes for manifest", container->content_length);
|
||||||
this_update->status_set_error(msg.c_str());
|
this_update->status_set_error(msg.c_str());
|
||||||
container->end();
|
container->end();
|
||||||
UPDATE_RETURN;
|
UPDATE_RETURN;
|
||||||
|
@ -9,8 +9,6 @@ from esphome.const import (
|
|||||||
CONF_FREQUENCY,
|
CONF_FREQUENCY,
|
||||||
CONF_I2C_ID,
|
CONF_I2C_ID,
|
||||||
CONF_ID,
|
CONF_ID,
|
||||||
CONF_INPUT,
|
|
||||||
CONF_OUTPUT,
|
|
||||||
CONF_SCAN,
|
CONF_SCAN,
|
||||||
CONF_SCL,
|
CONF_SCL,
|
||||||
CONF_SDA,
|
CONF_SDA,
|
||||||
@ -73,20 +71,15 @@ def validate_config(config):
|
|||||||
return config
|
return config
|
||||||
|
|
||||||
|
|
||||||
pin_with_input_and_output_support = pins.internal_gpio_pin_number(
|
|
||||||
{CONF_OUTPUT: True, CONF_INPUT: True}
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
CONFIG_SCHEMA = cv.All(
|
CONFIG_SCHEMA = cv.All(
|
||||||
cv.Schema(
|
cv.Schema(
|
||||||
{
|
{
|
||||||
cv.GenerateID(): _bus_declare_type,
|
cv.GenerateID(): _bus_declare_type,
|
||||||
cv.Optional(CONF_SDA, default="SDA"): pin_with_input_and_output_support,
|
cv.Optional(CONF_SDA, default="SDA"): pins.internal_gpio_pin_number,
|
||||||
cv.SplitDefault(CONF_SDA_PULLUP_ENABLED, esp32_idf=True): cv.All(
|
cv.SplitDefault(CONF_SDA_PULLUP_ENABLED, esp32_idf=True): cv.All(
|
||||||
cv.only_with_esp_idf, cv.boolean
|
cv.only_with_esp_idf, cv.boolean
|
||||||
),
|
),
|
||||||
cv.Optional(CONF_SCL, default="SCL"): pin_with_input_and_output_support,
|
cv.Optional(CONF_SCL, default="SCL"): pins.internal_gpio_pin_number,
|
||||||
cv.SplitDefault(CONF_SCL_PULLUP_ENABLED, esp32_idf=True): cv.All(
|
cv.SplitDefault(CONF_SCL_PULLUP_ENABLED, esp32_idf=True): cv.All(
|
||||||
cv.only_with_esp_idf, cv.boolean
|
cv.only_with_esp_idf, cv.boolean
|
||||||
),
|
),
|
||||||
|
@ -9,14 +9,7 @@ from esphome.components.esp32.const import (
|
|||||||
VARIANT_ESP32S3,
|
VARIANT_ESP32S3,
|
||||||
)
|
)
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.const import (
|
from esphome.const import CONF_BITS_PER_SAMPLE, CONF_CHANNEL, CONF_ID, CONF_SAMPLE_RATE
|
||||||
CONF_BITS_PER_SAMPLE,
|
|
||||||
CONF_CHANNEL,
|
|
||||||
CONF_ID,
|
|
||||||
CONF_SAMPLE_RATE,
|
|
||||||
KEY_CORE,
|
|
||||||
KEY_FRAMEWORK_VERSION,
|
|
||||||
)
|
|
||||||
from esphome.core import CORE
|
from esphome.core import CORE
|
||||||
from esphome.cpp_generator import MockObjClass
|
from esphome.cpp_generator import MockObjClass
|
||||||
import esphome.final_validate as fv
|
import esphome.final_validate as fv
|
||||||
@ -250,8 +243,7 @@ def _final_validate(_):
|
|||||||
|
|
||||||
|
|
||||||
def use_legacy():
|
def use_legacy():
|
||||||
framework_version = CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION]
|
if CORE.using_esp_idf:
|
||||||
if CORE.using_esp_idf and framework_version >= cv.Version(5, 0, 0):
|
|
||||||
if not _use_legacy_driver:
|
if not _use_legacy_driver:
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
@ -9,15 +9,11 @@ namespace i2s_audio {
|
|||||||
|
|
||||||
static const char *const TAG = "i2s_audio";
|
static const char *const TAG = "i2s_audio";
|
||||||
|
|
||||||
#if ESP_IDF_VERSION_MAJOR >= 5
|
|
||||||
static const uint8_t I2S_NUM_MAX = SOC_I2S_NUM; // because IDF 5+ took this away :(
|
|
||||||
#endif
|
|
||||||
|
|
||||||
void I2SAudioComponent::setup() {
|
void I2SAudioComponent::setup() {
|
||||||
ESP_LOGCONFIG(TAG, "Running setup");
|
ESP_LOGCONFIG(TAG, "Running setup");
|
||||||
|
|
||||||
static i2s_port_t next_port_num = I2S_NUM_0;
|
static i2s_port_t next_port_num = I2S_NUM_0;
|
||||||
if (next_port_num >= I2S_NUM_MAX) {
|
if (next_port_num >= SOC_I2S_NUM) {
|
||||||
ESP_LOGE(TAG, "Too many components");
|
ESP_LOGE(TAG, "Too many components");
|
||||||
this->mark_failed();
|
this->mark_failed();
|
||||||
return;
|
return;
|
||||||
|
@ -59,11 +59,7 @@ optional<uint8_t> ImprovSerialComponent::read_byte_() {
|
|||||||
break;
|
break;
|
||||||
#if defined(USE_LOGGER_USB_CDC) && defined(CONFIG_ESP_CONSOLE_USB_CDC)
|
#if defined(USE_LOGGER_USB_CDC) && defined(CONFIG_ESP_CONSOLE_USB_CDC)
|
||||||
case logger::UART_SELECTION_USB_CDC:
|
case logger::UART_SELECTION_USB_CDC:
|
||||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
|
|
||||||
if (esp_usb_console_available_for_read()) {
|
if (esp_usb_console_available_for_read()) {
|
||||||
#else
|
|
||||||
if (esp_usb_console_read_available()) {
|
|
||||||
#endif
|
|
||||||
esp_usb_console_read_buf((char *) &data, 1);
|
esp_usb_console_read_buf((char *) &data, 1);
|
||||||
byte = data;
|
byte = data;
|
||||||
}
|
}
|
||||||
|
@ -10,11 +10,7 @@ uint8_t temprature_sens_read();
|
|||||||
#elif defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || \
|
#elif defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || \
|
||||||
defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32H2) || \
|
defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32H2) || \
|
||||||
defined(USE_ESP32_VARIANT_ESP32C2) || defined(USE_ESP32_VARIANT_ESP32P4)
|
defined(USE_ESP32_VARIANT_ESP32C2) || defined(USE_ESP32_VARIANT_ESP32P4)
|
||||||
#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0)
|
|
||||||
#include "driver/temp_sensor.h"
|
|
||||||
#else
|
|
||||||
#include "driver/temperature_sensor.h"
|
#include "driver/temperature_sensor.h"
|
||||||
#endif // ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0)
|
|
||||||
#endif // USE_ESP32_VARIANT
|
#endif // USE_ESP32_VARIANT
|
||||||
#endif // USE_ESP32
|
#endif // USE_ESP32
|
||||||
#ifdef USE_RP2040
|
#ifdef USE_RP2040
|
||||||
@ -31,12 +27,11 @@ namespace internal_temperature {
|
|||||||
|
|
||||||
static const char *const TAG = "internal_temperature";
|
static const char *const TAG = "internal_temperature";
|
||||||
#ifdef USE_ESP32
|
#ifdef USE_ESP32
|
||||||
#if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)) && \
|
#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32S2) || \
|
||||||
(defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32S2) || \
|
defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32H2) || defined(USE_ESP32_VARIANT_ESP32C2) || \
|
||||||
defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32H2) || defined(USE_ESP32_VARIANT_ESP32C2) || \
|
defined(USE_ESP32_VARIANT_ESP32P4)
|
||||||
defined(USE_ESP32_VARIANT_ESP32P4))
|
|
||||||
static temperature_sensor_handle_t tsensNew = NULL;
|
static temperature_sensor_handle_t tsensNew = NULL;
|
||||||
#endif // ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) && USE_ESP32_VARIANT
|
#endif // USE_ESP32_VARIANT
|
||||||
#endif // USE_ESP32
|
#endif // USE_ESP32
|
||||||
|
|
||||||
void InternalTemperatureSensor::update() {
|
void InternalTemperatureSensor::update() {
|
||||||
@ -51,24 +46,11 @@ void InternalTemperatureSensor::update() {
|
|||||||
#elif defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || \
|
#elif defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || \
|
||||||
defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32H2) || \
|
defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32H2) || \
|
||||||
defined(USE_ESP32_VARIANT_ESP32C2) || defined(USE_ESP32_VARIANT_ESP32P4)
|
defined(USE_ESP32_VARIANT_ESP32C2) || defined(USE_ESP32_VARIANT_ESP32P4)
|
||||||
#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0)
|
|
||||||
temp_sensor_config_t tsens = TSENS_CONFIG_DEFAULT();
|
|
||||||
temp_sensor_set_config(tsens);
|
|
||||||
temp_sensor_start();
|
|
||||||
#if defined(USE_ESP32_VARIANT_ESP32S3) && (ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(4, 4, 3))
|
|
||||||
#error \
|
|
||||||
"ESP32-S3 internal temperature sensor requires ESP IDF V4.4.3 or higher. See https://github.com/esphome/issues/issues/4271"
|
|
||||||
#endif
|
|
||||||
esp_err_t result = temp_sensor_read_celsius(&temperature);
|
|
||||||
temp_sensor_stop();
|
|
||||||
success = (result == ESP_OK);
|
|
||||||
#else
|
|
||||||
esp_err_t result = temperature_sensor_get_celsius(tsensNew, &temperature);
|
esp_err_t result = temperature_sensor_get_celsius(tsensNew, &temperature);
|
||||||
success = (result == ESP_OK);
|
success = (result == ESP_OK);
|
||||||
if (!success) {
|
if (!success) {
|
||||||
ESP_LOGE(TAG, "Reading failed (%d)", result);
|
ESP_LOGE(TAG, "Reading failed (%d)", result);
|
||||||
}
|
}
|
||||||
#endif // ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0)
|
|
||||||
#endif // USE_ESP32_VARIANT
|
#endif // USE_ESP32_VARIANT
|
||||||
#endif // USE_ESP32
|
#endif // USE_ESP32
|
||||||
#ifdef USE_RP2040
|
#ifdef USE_RP2040
|
||||||
@ -99,10 +81,9 @@ void InternalTemperatureSensor::update() {
|
|||||||
|
|
||||||
void InternalTemperatureSensor::setup() {
|
void InternalTemperatureSensor::setup() {
|
||||||
#ifdef USE_ESP32
|
#ifdef USE_ESP32
|
||||||
#if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)) && \
|
#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32S2) || \
|
||||||
(defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32C6) || defined(USE_ESP32_VARIANT_ESP32S2) || \
|
defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32H2) || defined(USE_ESP32_VARIANT_ESP32C2) || \
|
||||||
defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32H2) || defined(USE_ESP32_VARIANT_ESP32C2) || \
|
defined(USE_ESP32_VARIANT_ESP32P4)
|
||||||
defined(USE_ESP32_VARIANT_ESP32P4))
|
|
||||||
ESP_LOGCONFIG(TAG, "Running setup");
|
ESP_LOGCONFIG(TAG, "Running setup");
|
||||||
|
|
||||||
temperature_sensor_config_t tsens_config = TEMPERATURE_SENSOR_CONFIG_DEFAULT(-10, 80);
|
temperature_sensor_config_t tsens_config = TEMPERATURE_SENSOR_CONFIG_DEFAULT(-10, 80);
|
||||||
@ -120,7 +101,7 @@ void InternalTemperatureSensor::setup() {
|
|||||||
this->mark_failed();
|
this->mark_failed();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
#endif // ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) && USE_ESP32_VARIANT
|
#endif // USE_ESP32_VARIANT
|
||||||
#endif // USE_ESP32
|
#endif // USE_ESP32
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,46 +1,21 @@
|
|||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
from esphome.components import sensor
|
from esphome.components import sensor
|
||||||
from esphome.components.esp32 import get_esp32_variant
|
|
||||||
from esphome.components.esp32.const import VARIANT_ESP32S3
|
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.const import (
|
from esphome.const import (
|
||||||
DEVICE_CLASS_TEMPERATURE,
|
DEVICE_CLASS_TEMPERATURE,
|
||||||
ENTITY_CATEGORY_DIAGNOSTIC,
|
ENTITY_CATEGORY_DIAGNOSTIC,
|
||||||
KEY_CORE,
|
|
||||||
KEY_FRAMEWORK_VERSION,
|
|
||||||
PLATFORM_BK72XX,
|
PLATFORM_BK72XX,
|
||||||
PLATFORM_ESP32,
|
PLATFORM_ESP32,
|
||||||
PLATFORM_RP2040,
|
PLATFORM_RP2040,
|
||||||
STATE_CLASS_MEASUREMENT,
|
STATE_CLASS_MEASUREMENT,
|
||||||
UNIT_CELSIUS,
|
UNIT_CELSIUS,
|
||||||
)
|
)
|
||||||
from esphome.core import CORE
|
|
||||||
|
|
||||||
internal_temperature_ns = cg.esphome_ns.namespace("internal_temperature")
|
internal_temperature_ns = cg.esphome_ns.namespace("internal_temperature")
|
||||||
InternalTemperatureSensor = internal_temperature_ns.class_(
|
InternalTemperatureSensor = internal_temperature_ns.class_(
|
||||||
"InternalTemperatureSensor", sensor.Sensor, cg.PollingComponent
|
"InternalTemperatureSensor", sensor.Sensor, cg.PollingComponent
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def validate_config(config):
|
|
||||||
if CORE.is_esp32:
|
|
||||||
variant = get_esp32_variant()
|
|
||||||
if variant == VARIANT_ESP32S3:
|
|
||||||
if CORE.using_arduino and CORE.data[KEY_CORE][
|
|
||||||
KEY_FRAMEWORK_VERSION
|
|
||||||
] < cv.Version(2, 0, 6):
|
|
||||||
raise cv.Invalid(
|
|
||||||
"ESP32-S3 Internal Temperature Sensor requires framework version 2.0.6 or higher. See <https://github.com/esphome/issues/issues/4271>."
|
|
||||||
)
|
|
||||||
if CORE.using_esp_idf and CORE.data[KEY_CORE][
|
|
||||||
KEY_FRAMEWORK_VERSION
|
|
||||||
] < cv.Version(4, 4, 3):
|
|
||||||
raise cv.Invalid(
|
|
||||||
"ESP32-S3 Internal Temperature Sensor requires framework version 4.4.3 or higher. See <https://github.com/esphome/issues/issues/4271>."
|
|
||||||
)
|
|
||||||
return config
|
|
||||||
|
|
||||||
|
|
||||||
CONFIG_SCHEMA = cv.All(
|
CONFIG_SCHEMA = cv.All(
|
||||||
sensor.sensor_schema(
|
sensor.sensor_schema(
|
||||||
InternalTemperatureSensor,
|
InternalTemperatureSensor,
|
||||||
@ -51,7 +26,6 @@ CONFIG_SCHEMA = cv.All(
|
|||||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||||
).extend(cv.polling_component_schema("60s")),
|
).extend(cv.polling_component_schema("60s")),
|
||||||
cv.only_on([PLATFORM_ESP32, PLATFORM_RP2040, PLATFORM_BK72XX]),
|
cv.only_on([PLATFORM_ESP32, PLATFORM_RP2040, PLATFORM_BK72XX]),
|
||||||
validate_config,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -83,9 +83,7 @@ void init_uart(uart_port_t uart_num, uint32_t baud_rate, int tx_buffer_size) {
|
|||||||
uart_config.parity = UART_PARITY_DISABLE;
|
uart_config.parity = UART_PARITY_DISABLE;
|
||||||
uart_config.stop_bits = UART_STOP_BITS_1;
|
uart_config.stop_bits = UART_STOP_BITS_1;
|
||||||
uart_config.flow_ctrl = UART_HW_FLOWCTRL_DISABLE;
|
uart_config.flow_ctrl = UART_HW_FLOWCTRL_DISABLE;
|
||||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
|
|
||||||
uart_config.source_clk = UART_SCLK_DEFAULT;
|
uart_config.source_clk = UART_SCLK_DEFAULT;
|
||||||
#endif
|
|
||||||
uart_param_config(uart_num, &uart_config);
|
uart_param_config(uart_num, &uart_config);
|
||||||
const int uart_buffer_size = tx_buffer_size;
|
const int uart_buffer_size = tx_buffer_size;
|
||||||
// Install UART driver using an event queue here
|
// Install UART driver using an event queue here
|
||||||
|
@ -8,8 +8,6 @@ from esphome.const import (
|
|||||||
CONF_PROTOCOL,
|
CONF_PROTOCOL,
|
||||||
CONF_SERVICE,
|
CONF_SERVICE,
|
||||||
CONF_SERVICES,
|
CONF_SERVICES,
|
||||||
KEY_CORE,
|
|
||||||
KEY_FRAMEWORK_VERSION,
|
|
||||||
)
|
)
|
||||||
from esphome.core import CORE, coroutine_with_priority
|
from esphome.core import CORE, coroutine_with_priority
|
||||||
|
|
||||||
@ -85,9 +83,7 @@ async def to_code(config):
|
|||||||
elif CORE.is_rp2040:
|
elif CORE.is_rp2040:
|
||||||
cg.add_library("LEAmDNS", None)
|
cg.add_library("LEAmDNS", None)
|
||||||
|
|
||||||
if CORE.using_esp_idf and CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] >= cv.Version(
|
if CORE.using_esp_idf:
|
||||||
5, 0, 0
|
|
||||||
):
|
|
||||||
add_idf_component(name="espressif/mdns", ref="1.8.2")
|
add_idf_component(name="espressif/mdns", ref="1.8.2")
|
||||||
|
|
||||||
cg.add_define("USE_MDNS")
|
cg.add_define("USE_MDNS")
|
||||||
|
@ -39,7 +39,7 @@ void MMC5603Component::setup() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (id != MMC56X3_CHIP_ID) {
|
if (id != 0 && id != MMC56X3_CHIP_ID) { // ID is not reported correctly by all chips, 0 on some chips
|
||||||
ESP_LOGCONFIG(TAG, "Chip Wrong");
|
ESP_LOGCONFIG(TAG, "Chip Wrong");
|
||||||
this->error_code_ = ID_REGISTERS;
|
this->error_code_ = ID_REGISTERS;
|
||||||
this->mark_failed();
|
this->mark_failed();
|
||||||
|
@ -90,15 +90,24 @@ bool Modbus::parse_modbus_byte_(uint8_t byte) {
|
|||||||
|
|
||||||
} else {
|
} else {
|
||||||
// data starts at 2 and length is 4 for read registers commands
|
// data starts at 2 and length is 4 for read registers commands
|
||||||
if (this->role == ModbusRole::SERVER && (function_code == 0x1 || function_code == 0x3 || function_code == 0x4)) {
|
if (this->role == ModbusRole::SERVER) {
|
||||||
data_offset = 2;
|
if (function_code == 0x1 || function_code == 0x3 || function_code == 0x4 || function_code == 0x6) {
|
||||||
data_len = 4;
|
data_offset = 2;
|
||||||
}
|
data_len = 4;
|
||||||
|
} else if (function_code == 0x10) {
|
||||||
// the response for write command mirrors the requests and data starts at offset 2 instead of 3 for read commands
|
if (at < 6) {
|
||||||
if (function_code == 0x5 || function_code == 0x06 || function_code == 0xF || function_code == 0x10) {
|
return true;
|
||||||
data_offset = 2;
|
}
|
||||||
data_len = 4;
|
data_offset = 2;
|
||||||
|
// starting address (2 bytes) + quantity of registers (2 bytes) + byte count itself (1 byte) + actual byte count
|
||||||
|
data_len = 2 + 2 + 1 + raw[6];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// the response for write command mirrors the requests and data starts at offset 2 instead of 3 for read commands
|
||||||
|
if (function_code == 0x5 || function_code == 0x06 || function_code == 0xF || function_code == 0x10) {
|
||||||
|
data_offset = 2;
|
||||||
|
data_len = 4;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Error ( msb indicates error )
|
// Error ( msb indicates error )
|
||||||
@ -132,6 +141,7 @@ bool Modbus::parse_modbus_byte_(uint8_t byte) {
|
|||||||
bool found = false;
|
bool found = false;
|
||||||
for (auto *device : this->devices_) {
|
for (auto *device : this->devices_) {
|
||||||
if (device->address_ == address) {
|
if (device->address_ == address) {
|
||||||
|
found = true;
|
||||||
// Is it an error response?
|
// Is it an error response?
|
||||||
if ((function_code & 0x80) == 0x80) {
|
if ((function_code & 0x80) == 0x80) {
|
||||||
ESP_LOGD(TAG, "Modbus error function code: 0x%X exception: %d", function_code, raw[2]);
|
ESP_LOGD(TAG, "Modbus error function code: 0x%X exception: %d", function_code, raw[2]);
|
||||||
@ -141,13 +151,21 @@ bool Modbus::parse_modbus_byte_(uint8_t byte) {
|
|||||||
// Ignore modbus exception not related to a pending command
|
// Ignore modbus exception not related to a pending command
|
||||||
ESP_LOGD(TAG, "Ignoring Modbus error - not expecting a response");
|
ESP_LOGD(TAG, "Ignoring Modbus error - not expecting a response");
|
||||||
}
|
}
|
||||||
} else if (this->role == ModbusRole::SERVER && (function_code == 0x3 || function_code == 0x4)) {
|
continue;
|
||||||
device->on_modbus_read_registers(function_code, uint16_t(data[1]) | (uint16_t(data[0]) << 8),
|
|
||||||
uint16_t(data[3]) | (uint16_t(data[2]) << 8));
|
|
||||||
} else {
|
|
||||||
device->on_modbus_data(data);
|
|
||||||
}
|
}
|
||||||
found = true;
|
if (this->role == ModbusRole::SERVER) {
|
||||||
|
if (function_code == 0x3 || function_code == 0x4) {
|
||||||
|
device->on_modbus_read_registers(function_code, uint16_t(data[1]) | (uint16_t(data[0]) << 8),
|
||||||
|
uint16_t(data[3]) | (uint16_t(data[2]) << 8));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (function_code == 0x6 || function_code == 0x10) {
|
||||||
|
device->on_modbus_write_registers(function_code, data);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// fallthrough for other function codes
|
||||||
|
device->on_modbus_data(data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
waiting_for_response = 0;
|
waiting_for_response = 0;
|
||||||
|
@ -59,6 +59,7 @@ class ModbusDevice {
|
|||||||
virtual void on_modbus_data(const std::vector<uint8_t> &data) = 0;
|
virtual void on_modbus_data(const std::vector<uint8_t> &data) = 0;
|
||||||
virtual void on_modbus_error(uint8_t function_code, uint8_t exception_code) {}
|
virtual void on_modbus_error(uint8_t function_code, uint8_t exception_code) {}
|
||||||
virtual void on_modbus_read_registers(uint8_t function_code, uint16_t start_address, uint16_t number_of_registers){};
|
virtual void on_modbus_read_registers(uint8_t function_code, uint16_t start_address, uint16_t number_of_registers){};
|
||||||
|
virtual void on_modbus_write_registers(uint8_t function_code, const std::vector<uint8_t> &data){};
|
||||||
void send(uint8_t function, uint16_t start_address, uint16_t number_of_entities, uint8_t payload_len = 0,
|
void send(uint8_t function, uint16_t start_address, uint16_t number_of_entities, uint8_t payload_len = 0,
|
||||||
const uint8_t *payload = nullptr) {
|
const uint8_t *payload = nullptr) {
|
||||||
this->parent_->send(this->address_, function, start_address, number_of_entities, payload_len, payload);
|
this->parent_->send(this->address_, function, start_address, number_of_entities, payload_len, payload);
|
||||||
|
@ -39,6 +39,7 @@ CODEOWNERS = ["@martgras"]
|
|||||||
AUTO_LOAD = ["modbus"]
|
AUTO_LOAD = ["modbus"]
|
||||||
|
|
||||||
CONF_READ_LAMBDA = "read_lambda"
|
CONF_READ_LAMBDA = "read_lambda"
|
||||||
|
CONF_WRITE_LAMBDA = "write_lambda"
|
||||||
CONF_SERVER_REGISTERS = "server_registers"
|
CONF_SERVER_REGISTERS = "server_registers"
|
||||||
MULTI_CONF = True
|
MULTI_CONF = True
|
||||||
|
|
||||||
@ -148,6 +149,7 @@ ModbusServerRegisterSchema = cv.Schema(
|
|||||||
cv.Required(CONF_ADDRESS): cv.positive_int,
|
cv.Required(CONF_ADDRESS): cv.positive_int,
|
||||||
cv.Optional(CONF_VALUE_TYPE, default="U_WORD"): cv.enum(SENSOR_VALUE_TYPE),
|
cv.Optional(CONF_VALUE_TYPE, default="U_WORD"): cv.enum(SENSOR_VALUE_TYPE),
|
||||||
cv.Required(CONF_READ_LAMBDA): cv.returning_lambda,
|
cv.Required(CONF_READ_LAMBDA): cv.returning_lambda,
|
||||||
|
cv.Optional(CONF_WRITE_LAMBDA): cv.returning_lambda,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -318,6 +320,17 @@ async def to_code(config):
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
if CONF_WRITE_LAMBDA in server_register:
|
||||||
|
cg.add(
|
||||||
|
server_register_var.set_write_lambda(
|
||||||
|
cg.TemplateArguments(cpp_type),
|
||||||
|
await cg.process_lambda(
|
||||||
|
server_register[CONF_WRITE_LAMBDA],
|
||||||
|
parameters=[(cg.uint16, "address"), (cpp_type, "x")],
|
||||||
|
return_type=cg.bool_,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
cg.add(var.add_server_register(server_register_var))
|
cg.add(var.add_server_register(server_register_var))
|
||||||
await register_modbus_device(var, config)
|
await register_modbus_device(var, config)
|
||||||
for conf in config.get(CONF_ON_COMMAND_SENT, []):
|
for conf in config.get(CONF_ON_COMMAND_SENT, []):
|
||||||
|
@ -152,6 +152,86 @@ void ModbusController::on_modbus_read_registers(uint8_t function_code, uint16_t
|
|||||||
this->send(function_code, start_address, number_of_registers, response.size(), response.data());
|
this->send(function_code, start_address, number_of_registers, response.size(), response.data());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ModbusController::on_modbus_write_registers(uint8_t function_code, const std::vector<uint8_t> &data) {
|
||||||
|
uint16_t number_of_registers;
|
||||||
|
uint16_t payload_offset;
|
||||||
|
|
||||||
|
if (function_code == 0x10) {
|
||||||
|
number_of_registers = uint16_t(data[3]) | (uint16_t(data[2]) << 8);
|
||||||
|
if (number_of_registers == 0 || number_of_registers > 0x7B) {
|
||||||
|
ESP_LOGW(TAG, "Invalid number of registers %d. Sending exception response.", number_of_registers);
|
||||||
|
send_error(function_code, 3);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
uint16_t payload_size = data[4];
|
||||||
|
if (payload_size != number_of_registers * 2) {
|
||||||
|
ESP_LOGW(TAG, "Payload size of %d bytes is not 2 times the number of registers (%d). Sending exception response.",
|
||||||
|
payload_size, number_of_registers);
|
||||||
|
send_error(function_code, 3);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
payload_offset = 5;
|
||||||
|
} else if (function_code == 0x06) {
|
||||||
|
number_of_registers = 1;
|
||||||
|
payload_offset = 2;
|
||||||
|
} else {
|
||||||
|
ESP_LOGW(TAG, "Invalid function code 0x%X. Sending exception response.", function_code);
|
||||||
|
send_error(function_code, 1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t start_address = uint16_t(data[1]) | (uint16_t(data[0]) << 8);
|
||||||
|
ESP_LOGD(TAG,
|
||||||
|
"Received write holding registers for device 0x%X. FC: 0x%X. Start address: 0x%X. Number of registers: "
|
||||||
|
"0x%X.",
|
||||||
|
this->address_, function_code, start_address, number_of_registers);
|
||||||
|
|
||||||
|
auto for_each_register = [this, start_address, number_of_registers, payload_offset](
|
||||||
|
const std::function<bool(ServerRegister *, uint16_t offset)> &callback) -> bool {
|
||||||
|
uint16_t offset = payload_offset;
|
||||||
|
for (uint16_t current_address = start_address; current_address < start_address + number_of_registers;) {
|
||||||
|
bool ok = false;
|
||||||
|
for (auto *server_register : this->server_registers_) {
|
||||||
|
if (server_register->address == current_address) {
|
||||||
|
ok = callback(server_register, offset);
|
||||||
|
current_address += server_register->register_count;
|
||||||
|
offset += server_register->register_count * sizeof(uint16_t);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ok) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
// check all registers are writable before writing to any of them:
|
||||||
|
if (!for_each_register([](ServerRegister *server_register, uint16_t offset) -> bool {
|
||||||
|
return server_register->write_lambda != nullptr;
|
||||||
|
})) {
|
||||||
|
send_error(function_code, 1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Actually write to the registers:
|
||||||
|
if (!for_each_register([&data](ServerRegister *server_register, uint16_t offset) {
|
||||||
|
int64_t number = payload_to_number(data, server_register->value_type, offset, 0xFFFFFFFF);
|
||||||
|
return server_register->write_lambda(number);
|
||||||
|
})) {
|
||||||
|
send_error(function_code, 4);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint8_t> response;
|
||||||
|
response.reserve(6);
|
||||||
|
response.push_back(this->address_);
|
||||||
|
response.push_back(function_code);
|
||||||
|
response.insert(response.end(), data.begin(), data.begin() + 4);
|
||||||
|
this->send_raw(response);
|
||||||
|
}
|
||||||
|
|
||||||
SensorSet ModbusController::find_sensors_(ModbusRegisterType register_type, uint16_t start_address) const {
|
SensorSet ModbusController::find_sensors_(ModbusRegisterType register_type, uint16_t start_address) const {
|
||||||
auto reg_it = std::find_if(
|
auto reg_it = std::find_if(
|
||||||
std::begin(this->register_ranges_), std::end(this->register_ranges_),
|
std::begin(this->register_ranges_), std::end(this->register_ranges_),
|
||||||
|
@ -258,6 +258,7 @@ class SensorItem {
|
|||||||
|
|
||||||
class ServerRegister {
|
class ServerRegister {
|
||||||
using ReadLambda = std::function<int64_t()>;
|
using ReadLambda = std::function<int64_t()>;
|
||||||
|
using WriteLambda = std::function<bool(int64_t value)>;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
ServerRegister(uint16_t address, SensorValueType value_type, uint8_t register_count) {
|
ServerRegister(uint16_t address, SensorValueType value_type, uint8_t register_count) {
|
||||||
@ -277,6 +278,17 @@ class ServerRegister {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
void set_write_lambda(const std::function<bool(uint16_t address, const T v)> &&user_write_lambda) {
|
||||||
|
this->write_lambda = [this, user_write_lambda](int64_t number) {
|
||||||
|
if constexpr (std::is_same_v<T, float>) {
|
||||||
|
float float_value = bit_cast<float>(static_cast<uint32_t>(number));
|
||||||
|
return user_write_lambda(this->address, float_value);
|
||||||
|
}
|
||||||
|
return user_write_lambda(this->address, static_cast<T>(number));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// Formats a raw value into a string representation based on the value type for debugging
|
// Formats a raw value into a string representation based on the value type for debugging
|
||||||
std::string format_value(int64_t value) const {
|
std::string format_value(int64_t value) const {
|
||||||
switch (this->value_type) {
|
switch (this->value_type) {
|
||||||
@ -304,6 +316,7 @@ class ServerRegister {
|
|||||||
SensorValueType value_type{SensorValueType::RAW};
|
SensorValueType value_type{SensorValueType::RAW};
|
||||||
uint8_t register_count{0};
|
uint8_t register_count{0};
|
||||||
ReadLambda read_lambda;
|
ReadLambda read_lambda;
|
||||||
|
WriteLambda write_lambda;
|
||||||
};
|
};
|
||||||
|
|
||||||
// ModbusController::create_register_ranges_ tries to optimize register range
|
// ModbusController::create_register_ranges_ tries to optimize register range
|
||||||
@ -485,6 +498,8 @@ class ModbusController : public PollingComponent, public modbus::ModbusDevice {
|
|||||||
void on_modbus_error(uint8_t function_code, uint8_t exception_code) override;
|
void on_modbus_error(uint8_t function_code, uint8_t exception_code) override;
|
||||||
/// called when a modbus request (function code 0x03 or 0x04) was parsed without errors
|
/// called when a modbus request (function code 0x03 or 0x04) was parsed without errors
|
||||||
void on_modbus_read_registers(uint8_t function_code, uint16_t start_address, uint16_t number_of_registers) final;
|
void on_modbus_read_registers(uint8_t function_code, uint16_t start_address, uint16_t number_of_registers) final;
|
||||||
|
/// called when a modbus request (function code 0x06 or 0x10) was parsed without errors
|
||||||
|
void on_modbus_write_registers(uint8_t function_code, const std::vector<uint8_t> &data) final;
|
||||||
/// default delegate called by process_modbus_data when a response has retrieved from the incoming queue
|
/// default delegate called by process_modbus_data when a response has retrieved from the incoming queue
|
||||||
void on_register_data(ModbusRegisterType register_type, uint16_t start_address, const std::vector<uint8_t> &data);
|
void on_register_data(ModbusRegisterType register_type, uint16_t start_address, const std::vector<uint8_t> &data);
|
||||||
/// default delegate called by process_modbus_data when a response for a write response has retrieved from the
|
/// default delegate called by process_modbus_data when a response for a write response has retrieved from the
|
||||||
|
@ -14,49 +14,6 @@ namespace mqtt {
|
|||||||
static const char *const TAG = "mqtt.idf";
|
static const char *const TAG = "mqtt.idf";
|
||||||
|
|
||||||
bool MQTTBackendESP32::initialize_() {
|
bool MQTTBackendESP32::initialize_() {
|
||||||
#if ESP_IDF_VERSION_MAJOR < 5
|
|
||||||
mqtt_cfg_.user_context = (void *) this;
|
|
||||||
mqtt_cfg_.buffer_size = MQTT_BUFFER_SIZE;
|
|
||||||
|
|
||||||
mqtt_cfg_.host = this->host_.c_str();
|
|
||||||
mqtt_cfg_.port = this->port_;
|
|
||||||
mqtt_cfg_.keepalive = this->keep_alive_;
|
|
||||||
mqtt_cfg_.disable_clean_session = !this->clean_session_;
|
|
||||||
|
|
||||||
if (!this->username_.empty()) {
|
|
||||||
mqtt_cfg_.username = this->username_.c_str();
|
|
||||||
if (!this->password_.empty()) {
|
|
||||||
mqtt_cfg_.password = this->password_.c_str();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this->lwt_topic_.empty()) {
|
|
||||||
mqtt_cfg_.lwt_topic = this->lwt_topic_.c_str();
|
|
||||||
this->mqtt_cfg_.lwt_qos = this->lwt_qos_;
|
|
||||||
this->mqtt_cfg_.lwt_retain = this->lwt_retain_;
|
|
||||||
|
|
||||||
if (!this->lwt_message_.empty()) {
|
|
||||||
mqtt_cfg_.lwt_msg = this->lwt_message_.c_str();
|
|
||||||
mqtt_cfg_.lwt_msg_len = this->lwt_message_.size();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this->client_id_.empty()) {
|
|
||||||
mqtt_cfg_.client_id = this->client_id_.c_str();
|
|
||||||
}
|
|
||||||
if (ca_certificate_.has_value()) {
|
|
||||||
mqtt_cfg_.cert_pem = ca_certificate_.value().c_str();
|
|
||||||
mqtt_cfg_.skip_cert_common_name_check = skip_cert_cn_check_;
|
|
||||||
mqtt_cfg_.transport = MQTT_TRANSPORT_OVER_SSL;
|
|
||||||
|
|
||||||
if (this->cl_certificate_.has_value() && this->cl_key_.has_value()) {
|
|
||||||
mqtt_cfg_.client_cert_pem = this->cl_certificate_.value().c_str();
|
|
||||||
mqtt_cfg_.client_key_pem = this->cl_key_.value().c_str();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
mqtt_cfg_.transport = MQTT_TRANSPORT_OVER_TCP;
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
mqtt_cfg_.broker.address.hostname = this->host_.c_str();
|
mqtt_cfg_.broker.address.hostname = this->host_.c_str();
|
||||||
mqtt_cfg_.broker.address.port = this->port_;
|
mqtt_cfg_.broker.address.port = this->port_;
|
||||||
mqtt_cfg_.session.keepalive = this->keep_alive_;
|
mqtt_cfg_.session.keepalive = this->keep_alive_;
|
||||||
@ -95,7 +52,7 @@ bool MQTTBackendESP32::initialize_() {
|
|||||||
} else {
|
} else {
|
||||||
mqtt_cfg_.broker.address.transport = MQTT_TRANSPORT_OVER_TCP;
|
mqtt_cfg_.broker.address.transport = MQTT_TRANSPORT_OVER_TCP;
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
auto *mqtt_client = esp_mqtt_client_init(&mqtt_cfg_);
|
auto *mqtt_client = esp_mqtt_client_init(&mqtt_cfg_);
|
||||||
if (mqtt_client) {
|
if (mqtt_client) {
|
||||||
handler_.reset(mqtt_client);
|
handler_.reset(mqtt_client);
|
||||||
@ -163,12 +120,20 @@ void MQTTBackendESP32::mqtt_event_handler_(const Event &event) {
|
|||||||
case MQTT_EVENT_CONNECTED:
|
case MQTT_EVENT_CONNECTED:
|
||||||
ESP_LOGV(TAG, "MQTT_EVENT_CONNECTED");
|
ESP_LOGV(TAG, "MQTT_EVENT_CONNECTED");
|
||||||
this->is_connected_ = true;
|
this->is_connected_ = true;
|
||||||
|
#if defined(USE_MQTT_IDF_ENQUEUE)
|
||||||
|
this->last_dropped_log_time_ = 0;
|
||||||
|
xTaskNotifyGive(this->task_handle_);
|
||||||
|
#endif
|
||||||
this->on_connect_.call(event.session_present);
|
this->on_connect_.call(event.session_present);
|
||||||
break;
|
break;
|
||||||
case MQTT_EVENT_DISCONNECTED:
|
case MQTT_EVENT_DISCONNECTED:
|
||||||
ESP_LOGV(TAG, "MQTT_EVENT_DISCONNECTED");
|
ESP_LOGV(TAG, "MQTT_EVENT_DISCONNECTED");
|
||||||
// TODO is there a way to get the disconnect reason?
|
// TODO is there a way to get the disconnect reason?
|
||||||
this->is_connected_ = false;
|
this->is_connected_ = false;
|
||||||
|
#if defined(USE_MQTT_IDF_ENQUEUE)
|
||||||
|
this->last_dropped_log_time_ = 0;
|
||||||
|
xTaskNotifyGive(this->task_handle_);
|
||||||
|
#endif
|
||||||
this->on_disconnect_.call(MQTTClientDisconnectReason::TCP_DISCONNECTED);
|
this->on_disconnect_.call(MQTTClientDisconnectReason::TCP_DISCONNECTED);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
@ -116,7 +116,7 @@ struct QueueElement {
|
|||||||
class MQTTBackendESP32 final : public MQTTBackend {
|
class MQTTBackendESP32 final : public MQTTBackend {
|
||||||
public:
|
public:
|
||||||
static const size_t MQTT_BUFFER_SIZE = 4096;
|
static const size_t MQTT_BUFFER_SIZE = 4096;
|
||||||
static const size_t TASK_STACK_SIZE = 2048;
|
static const size_t TASK_STACK_SIZE = 3072;
|
||||||
static const size_t TASK_STACK_SIZE_TLS = 4096; // Larger stack for TLS operations
|
static const size_t TASK_STACK_SIZE_TLS = 4096; // Larger stack for TLS operations
|
||||||
static const ssize_t TASK_PRIORITY = 5;
|
static const ssize_t TASK_PRIORITY = 5;
|
||||||
static const uint8_t MQTT_QUEUE_LENGTH = 30; // 30*12 bytes = 360
|
static const uint8_t MQTT_QUEUE_LENGTH = 30; // 30*12 bytes = 360
|
||||||
|
@ -164,7 +164,7 @@ void Nextion::dump_config() {
|
|||||||
#endif // USE_NEXTION_MAX_COMMANDS_PER_LOOP
|
#endif // USE_NEXTION_MAX_COMMANDS_PER_LOOP
|
||||||
|
|
||||||
if (this->touch_sleep_timeout_ != 0) {
|
if (this->touch_sleep_timeout_ != 0) {
|
||||||
ESP_LOGCONFIG(TAG, " Touch Timeout: %" PRIu32, this->touch_sleep_timeout_);
|
ESP_LOGCONFIG(TAG, " Touch Timeout: %" PRIu16, this->touch_sleep_timeout_);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this->wake_up_page_ != -1) {
|
if (this->wake_up_page_ != -1) {
|
||||||
@ -302,11 +302,11 @@ void Nextion::loop() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if a startup page has been set and send the command
|
// Check if a startup page has been set and send the command
|
||||||
if (this->start_up_page_ != -1) {
|
if (this->start_up_page_ >= 0) {
|
||||||
this->goto_page(this->start_up_page_);
|
this->goto_page(this->start_up_page_);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this->wake_up_page_ != -1) {
|
if (this->wake_up_page_ >= 0) {
|
||||||
this->set_wake_up_page(this->wake_up_page_);
|
this->set_wake_up_page(this->wake_up_page_);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -418,12 +418,12 @@ void Nextion::process_nextion_commands_() {
|
|||||||
ESP_LOGN(TAG, "Add 0xFF");
|
ESP_LOGN(TAG, "Add 0xFF");
|
||||||
}
|
}
|
||||||
|
|
||||||
this->nextion_event_ = this->command_data_[0];
|
const uint8_t nextion_event = this->command_data_[0];
|
||||||
|
|
||||||
to_process_length -= 1;
|
to_process_length -= 1;
|
||||||
to_process = this->command_data_.substr(1, to_process_length);
|
to_process = this->command_data_.substr(1, to_process_length);
|
||||||
|
|
||||||
switch (this->nextion_event_) {
|
switch (nextion_event) {
|
||||||
case 0x00: // instruction sent by user has failed
|
case 0x00: // instruction sent by user has failed
|
||||||
ESP_LOGW(TAG, "Invalid instruction");
|
ESP_LOGW(TAG, "Invalid instruction");
|
||||||
this->remove_from_q_();
|
this->remove_from_q_();
|
||||||
@ -562,9 +562,9 @@ void Nextion::process_nextion_commands_() {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint16_t x = (uint16_t(to_process[0]) << 8) | to_process[1];
|
const uint16_t x = (uint16_t(to_process[0]) << 8) | to_process[1];
|
||||||
uint16_t y = (uint16_t(to_process[2]) << 8) | to_process[3];
|
const uint16_t y = (uint16_t(to_process[2]) << 8) | to_process[3];
|
||||||
uint8_t touch_event = to_process[4]; // 0 -> release, 1 -> press
|
const uint8_t touch_event = to_process[4]; // 0 -> release, 1 -> press
|
||||||
ESP_LOGD(TAG, "Touch %s at %u,%u", touch_event ? "PRESS" : "RELEASE", x, y);
|
ESP_LOGD(TAG, "Touch %s at %u,%u", touch_event ? "PRESS" : "RELEASE", x, y);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -820,15 +820,14 @@ void Nextion::process_nextion_commands_() {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
ESP_LOGW(TAG, "Unknown event: 0x%02X", this->nextion_event_);
|
ESP_LOGW(TAG, "Unknown event: 0x%02X", nextion_event);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ESP_LOGN(TAG, "nextion_event_ deleting from 0 to %d", to_process_length + COMMAND_DELIMITER.length() + 1);
|
|
||||||
this->command_data_.erase(0, to_process_length + COMMAND_DELIMITER.length() + 1);
|
this->command_data_.erase(0, to_process_length + COMMAND_DELIMITER.length() + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t ms = App.get_loop_component_start_time();
|
const uint32_t ms = App.get_loop_component_start_time();
|
||||||
|
|
||||||
if (!this->nextion_queue_.empty() && this->nextion_queue_.front()->queue_time + this->max_q_age_ms_ < ms) {
|
if (!this->nextion_queue_.empty() && this->nextion_queue_.front()->queue_time + this->max_q_age_ms_ < ms) {
|
||||||
for (size_t i = 0; i < this->nextion_queue_.size(); i++) {
|
for (size_t i = 0; i < this->nextion_queue_.size(); i++) {
|
||||||
@ -960,7 +959,6 @@ void Nextion::update_components_by_prefix(const std::string &prefix) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
uint16_t Nextion::recv_ret_string_(std::string &response, uint32_t timeout, bool recv_flag) {
|
uint16_t Nextion::recv_ret_string_(std::string &response, uint32_t timeout, bool recv_flag) {
|
||||||
uint16_t ret = 0;
|
|
||||||
uint8_t c = 0;
|
uint8_t c = 0;
|
||||||
uint8_t nr_of_ff_bytes = 0;
|
uint8_t nr_of_ff_bytes = 0;
|
||||||
bool exit_flag = false;
|
bool exit_flag = false;
|
||||||
@ -1003,8 +1001,7 @@ uint16_t Nextion::recv_ret_string_(std::string &response, uint32_t timeout, bool
|
|||||||
if (ff_flag)
|
if (ff_flag)
|
||||||
response = response.substr(0, response.length() - 3); // Remove last 3 0xFF
|
response = response.substr(0, response.length() - 3); // Remove last 3 0xFF
|
||||||
|
|
||||||
ret = response.length();
|
return response.length();
|
||||||
return ret;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1190,11 +1190,11 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe
|
|||||||
* After 30 seconds the display will go to sleep. Note: the display will only wakeup by a restart or by setting up
|
* After 30 seconds the display will go to sleep. Note: the display will only wakeup by a restart or by setting up
|
||||||
* `thup`.
|
* `thup`.
|
||||||
*/
|
*/
|
||||||
void set_touch_sleep_timeout(uint32_t touch_sleep_timeout);
|
void set_touch_sleep_timeout(uint16_t touch_sleep_timeout);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets which page Nextion loads when exiting sleep mode. Note this can be set even when Nextion is in sleep mode.
|
* Sets which page Nextion loads when exiting sleep mode. Note this can be set even when Nextion is in sleep mode.
|
||||||
* @param wake_up_page The page id, from 0 to the lage page in Nextion. Set 255 (not set to any existing page) to
|
* @param wake_up_page The page id, from 0 to the last page in Nextion. Set -1 (not set to any existing page) to
|
||||||
* wakes up to current page.
|
* wakes up to current page.
|
||||||
*
|
*
|
||||||
* Example:
|
* Example:
|
||||||
@ -1204,11 +1204,11 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe
|
|||||||
*
|
*
|
||||||
* The display will wake up to page 2.
|
* The display will wake up to page 2.
|
||||||
*/
|
*/
|
||||||
void set_wake_up_page(uint8_t wake_up_page = 255);
|
void set_wake_up_page(int16_t wake_up_page = -1);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets which page Nextion loads when connecting to ESPHome.
|
* Sets which page Nextion loads when connecting to ESPHome.
|
||||||
* @param start_up_page The page id, from 0 to the lage page in Nextion. Set 255 (not set to any existing page) to
|
* @param start_up_page The page id, from 0 to the last page in Nextion. Set -1 (not set to any existing page) to
|
||||||
* wakes up to current page.
|
* wakes up to current page.
|
||||||
*
|
*
|
||||||
* Example:
|
* Example:
|
||||||
@ -1218,7 +1218,7 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe
|
|||||||
*
|
*
|
||||||
* The display will go to page 2 when it establishes a connection to ESPHome.
|
* The display will go to page 2 when it establishes a connection to ESPHome.
|
||||||
*/
|
*/
|
||||||
void set_start_up_page(uint8_t start_up_page = 255) { this->start_up_page_ = start_up_page; }
|
void set_start_up_page(int16_t start_up_page = -1) { this->start_up_page_ = start_up_page; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets if Nextion should auto-wake from sleep when touch press occurs.
|
* Sets if Nextion should auto-wake from sleep when touch press occurs.
|
||||||
@ -1330,7 +1330,7 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe
|
|||||||
std::deque<NextionQueue *> waveform_queue_;
|
std::deque<NextionQueue *> waveform_queue_;
|
||||||
uint16_t recv_ret_string_(std::string &response, uint32_t timeout, bool recv_flag);
|
uint16_t recv_ret_string_(std::string &response, uint32_t timeout, bool recv_flag);
|
||||||
void all_components_send_state_(bool force_update = false);
|
void all_components_send_state_(bool force_update = false);
|
||||||
uint64_t comok_sent_ = 0;
|
uint32_t comok_sent_ = 0;
|
||||||
bool remove_from_q_(bool report_empty = true);
|
bool remove_from_q_(bool report_empty = true);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1340,12 +1340,10 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe
|
|||||||
bool ignore_is_setup_ = false;
|
bool ignore_is_setup_ = false;
|
||||||
|
|
||||||
bool nextion_reports_is_setup_ = false;
|
bool nextion_reports_is_setup_ = false;
|
||||||
uint8_t nextion_event_;
|
|
||||||
|
|
||||||
void process_nextion_commands_();
|
void process_nextion_commands_();
|
||||||
void process_serial_();
|
void process_serial_();
|
||||||
bool is_updating_ = false;
|
bool is_updating_ = false;
|
||||||
uint32_t touch_sleep_timeout_ = 0;
|
uint16_t touch_sleep_timeout_ = 0;
|
||||||
int16_t wake_up_page_ = -1;
|
int16_t wake_up_page_ = -1;
|
||||||
int16_t start_up_page_ = -1;
|
int16_t start_up_page_ = -1;
|
||||||
bool auto_wake_on_touch_ = true;
|
bool auto_wake_on_touch_ = true;
|
||||||
|
@ -10,12 +10,12 @@ static const char *const TAG = "nextion";
|
|||||||
// Sleep safe commands
|
// Sleep safe commands
|
||||||
void Nextion::soft_reset() { this->send_command_("rest"); }
|
void Nextion::soft_reset() { this->send_command_("rest"); }
|
||||||
|
|
||||||
void Nextion::set_wake_up_page(uint8_t wake_up_page) {
|
void Nextion::set_wake_up_page(int16_t wake_up_page) {
|
||||||
this->wake_up_page_ = wake_up_page;
|
this->wake_up_page_ = wake_up_page;
|
||||||
this->add_no_result_to_queue_with_set_internal_("wake_up_page", "wup", wake_up_page, true);
|
this->add_no_result_to_queue_with_set_internal_("wake_up_page", "wup", wake_up_page, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Nextion::set_touch_sleep_timeout(uint32_t touch_sleep_timeout) {
|
void Nextion::set_touch_sleep_timeout(uint16_t touch_sleep_timeout) {
|
||||||
if (touch_sleep_timeout < 3) {
|
if (touch_sleep_timeout < 3) {
|
||||||
ESP_LOGD(TAG, "Sleep timeout out of bounds (3-65535)");
|
ESP_LOGD(TAG, "Sleep timeout out of bounds (3-65535)");
|
||||||
return;
|
return;
|
||||||
|
@ -272,18 +272,13 @@ bool OpenTherm::init_esp32_timer_() {
|
|||||||
this->timer_idx_ = timer_idx;
|
this->timer_idx_ = timer_idx;
|
||||||
|
|
||||||
timer_config_t const config = {
|
timer_config_t const config = {
|
||||||
.alarm_en = TIMER_ALARM_EN,
|
.alarm_en = TIMER_ALARM_EN,
|
||||||
.counter_en = TIMER_PAUSE,
|
.counter_en = TIMER_PAUSE,
|
||||||
.intr_type = TIMER_INTR_LEVEL,
|
.intr_type = TIMER_INTR_LEVEL,
|
||||||
.counter_dir = TIMER_COUNT_UP,
|
.counter_dir = TIMER_COUNT_UP,
|
||||||
.auto_reload = TIMER_AUTORELOAD_EN,
|
.auto_reload = TIMER_AUTORELOAD_EN,
|
||||||
#if ESP_IDF_VERSION_MAJOR >= 5
|
.clk_src = TIMER_SRC_CLK_DEFAULT,
|
||||||
.clk_src = TIMER_SRC_CLK_DEFAULT,
|
.divider = 80,
|
||||||
#endif
|
|
||||||
.divider = 80,
|
|
||||||
#if defined(SOC_TIMER_GROUP_SUPPORT_XTAL) && ESP_IDF_VERSION_MAJOR < 5
|
|
||||||
.clk_src = TIMER_SRC_CLK_APB
|
|
||||||
#endif
|
|
||||||
};
|
};
|
||||||
|
|
||||||
esp_err_t result;
|
esp_err_t result;
|
||||||
|
@ -11,6 +11,7 @@ from esphome.const import CONF_CHANNEL, CONF_ENABLE_IPV6, CONF_ID
|
|||||||
import esphome.final_validate as fv
|
import esphome.final_validate as fv
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
|
CONF_DEVICE_TYPE,
|
||||||
CONF_EXT_PAN_ID,
|
CONF_EXT_PAN_ID,
|
||||||
CONF_FORCE_DATASET,
|
CONF_FORCE_DATASET,
|
||||||
CONF_MDNS_ID,
|
CONF_MDNS_ID,
|
||||||
@ -22,7 +23,6 @@ from .const import (
|
|||||||
CONF_SRP_ID,
|
CONF_SRP_ID,
|
||||||
CONF_TLV,
|
CONF_TLV,
|
||||||
)
|
)
|
||||||
from .tlv import parse_tlv
|
|
||||||
|
|
||||||
CODEOWNERS = ["@mrene"]
|
CODEOWNERS = ["@mrene"]
|
||||||
|
|
||||||
@ -33,6 +33,11 @@ AUTO_LOAD = ["network"]
|
|||||||
CONFLICTS_WITH = ["wifi"]
|
CONFLICTS_WITH = ["wifi"]
|
||||||
DEPENDENCIES = ["esp32"]
|
DEPENDENCIES = ["esp32"]
|
||||||
|
|
||||||
|
CONF_DEVICE_TYPES = [
|
||||||
|
"FTD",
|
||||||
|
"MTD",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
def set_sdkconfig_options(config):
|
def set_sdkconfig_options(config):
|
||||||
# and expose options for using SPI/UART RCPs
|
# and expose options for using SPI/UART RCPs
|
||||||
@ -43,58 +48,58 @@ def set_sdkconfig_options(config):
|
|||||||
add_idf_sdkconfig_option("CONFIG_OPENTHREAD_CLI", False)
|
add_idf_sdkconfig_option("CONFIG_OPENTHREAD_CLI", False)
|
||||||
|
|
||||||
add_idf_sdkconfig_option("CONFIG_OPENTHREAD_ENABLED", True)
|
add_idf_sdkconfig_option("CONFIG_OPENTHREAD_ENABLED", True)
|
||||||
add_idf_sdkconfig_option("CONFIG_OPENTHREAD_NETWORK_PANID", config[CONF_PAN_ID])
|
|
||||||
add_idf_sdkconfig_option("CONFIG_OPENTHREAD_NETWORK_CHANNEL", config[CONF_CHANNEL])
|
|
||||||
add_idf_sdkconfig_option(
|
|
||||||
"CONFIG_OPENTHREAD_NETWORK_MASTERKEY", f"{config[CONF_NETWORK_KEY]:X}".lower()
|
|
||||||
)
|
|
||||||
|
|
||||||
if network_name := config.get(CONF_NETWORK_NAME):
|
if tlv := config.get(CONF_TLV):
|
||||||
add_idf_sdkconfig_option("CONFIG_OPENTHREAD_NETWORK_NAME", network_name)
|
cg.add_define("USE_OPENTHREAD_TLVS", tlv)
|
||||||
|
else:
|
||||||
|
if pan_id := config.get(CONF_PAN_ID):
|
||||||
|
add_idf_sdkconfig_option("CONFIG_OPENTHREAD_NETWORK_PANID", pan_id)
|
||||||
|
|
||||||
if (ext_pan_id := config.get(CONF_EXT_PAN_ID)) is not None:
|
if channel := config.get(CONF_CHANNEL):
|
||||||
add_idf_sdkconfig_option(
|
add_idf_sdkconfig_option("CONFIG_OPENTHREAD_NETWORK_CHANNEL", channel)
|
||||||
"CONFIG_OPENTHREAD_NETWORK_EXTPANID", f"{ext_pan_id:X}".lower()
|
|
||||||
)
|
|
||||||
if (mesh_local_prefix := config.get(CONF_MESH_LOCAL_PREFIX)) is not None:
|
|
||||||
add_idf_sdkconfig_option(
|
|
||||||
"CONFIG_OPENTHREAD_MESH_LOCAL_PREFIX", f"{mesh_local_prefix}".lower()
|
|
||||||
)
|
|
||||||
if (pskc := config.get(CONF_PSKC)) is not None:
|
|
||||||
add_idf_sdkconfig_option("CONFIG_OPENTHREAD_NETWORK_PSKC", f"{pskc:X}".lower())
|
|
||||||
|
|
||||||
if CONF_FORCE_DATASET in config:
|
if network_key := config.get(CONF_NETWORK_KEY):
|
||||||
if config[CONF_FORCE_DATASET]:
|
add_idf_sdkconfig_option(
|
||||||
cg.add_define("CONFIG_OPENTHREAD_FORCE_DATASET")
|
"CONFIG_OPENTHREAD_NETWORK_MASTERKEY", f"{network_key:X}".lower()
|
||||||
|
)
|
||||||
|
|
||||||
|
if network_name := config.get(CONF_NETWORK_NAME):
|
||||||
|
add_idf_sdkconfig_option("CONFIG_OPENTHREAD_NETWORK_NAME", network_name)
|
||||||
|
|
||||||
|
if (ext_pan_id := config.get(CONF_EXT_PAN_ID)) is not None:
|
||||||
|
add_idf_sdkconfig_option(
|
||||||
|
"CONFIG_OPENTHREAD_NETWORK_EXTPANID", f"{ext_pan_id:X}".lower()
|
||||||
|
)
|
||||||
|
if (mesh_local_prefix := config.get(CONF_MESH_LOCAL_PREFIX)) is not None:
|
||||||
|
add_idf_sdkconfig_option(
|
||||||
|
"CONFIG_OPENTHREAD_MESH_LOCAL_PREFIX", f"{mesh_local_prefix}".lower()
|
||||||
|
)
|
||||||
|
if (pskc := config.get(CONF_PSKC)) is not None:
|
||||||
|
add_idf_sdkconfig_option(
|
||||||
|
"CONFIG_OPENTHREAD_NETWORK_PSKC", f"{pskc:X}".lower()
|
||||||
|
)
|
||||||
|
|
||||||
|
if force_dataset := config.get(CONF_FORCE_DATASET):
|
||||||
|
if force_dataset:
|
||||||
|
cg.add_define("USE_OPENTHREAD_FORCE_DATASET")
|
||||||
|
|
||||||
add_idf_sdkconfig_option("CONFIG_OPENTHREAD_DNS64_CLIENT", True)
|
add_idf_sdkconfig_option("CONFIG_OPENTHREAD_DNS64_CLIENT", True)
|
||||||
add_idf_sdkconfig_option("CONFIG_OPENTHREAD_SRP_CLIENT", True)
|
add_idf_sdkconfig_option("CONFIG_OPENTHREAD_SRP_CLIENT", True)
|
||||||
add_idf_sdkconfig_option("CONFIG_OPENTHREAD_SRP_CLIENT_MAX_SERVICES", 5)
|
add_idf_sdkconfig_option("CONFIG_OPENTHREAD_SRP_CLIENT_MAX_SERVICES", 5)
|
||||||
|
|
||||||
# TODO: Add suport for sleepy end devices
|
# TODO: Add suport for sleepy end devices
|
||||||
add_idf_sdkconfig_option("CONFIG_OPENTHREAD_FTD", True) # Full Thread Device
|
add_idf_sdkconfig_option(f"CONFIG_OPENTHREAD_{config.get(CONF_DEVICE_TYPE)}", True)
|
||||||
|
|
||||||
|
|
||||||
openthread_ns = cg.esphome_ns.namespace("openthread")
|
openthread_ns = cg.esphome_ns.namespace("openthread")
|
||||||
OpenThreadComponent = openthread_ns.class_("OpenThreadComponent", cg.Component)
|
OpenThreadComponent = openthread_ns.class_("OpenThreadComponent", cg.Component)
|
||||||
OpenThreadSrpComponent = openthread_ns.class_("OpenThreadSrpComponent", cg.Component)
|
OpenThreadSrpComponent = openthread_ns.class_("OpenThreadSrpComponent", cg.Component)
|
||||||
|
|
||||||
|
|
||||||
def _convert_tlv(config):
|
|
||||||
if tlv := config.get(CONF_TLV):
|
|
||||||
config = config.copy()
|
|
||||||
parsed_tlv = parse_tlv(tlv)
|
|
||||||
validated = _CONNECTION_SCHEMA(parsed_tlv)
|
|
||||||
config.update(validated)
|
|
||||||
del config[CONF_TLV]
|
|
||||||
return config
|
|
||||||
|
|
||||||
|
|
||||||
_CONNECTION_SCHEMA = cv.Schema(
|
_CONNECTION_SCHEMA = cv.Schema(
|
||||||
{
|
{
|
||||||
cv.Inclusive(CONF_PAN_ID, "manual"): cv.hex_int,
|
cv.Optional(CONF_PAN_ID): cv.hex_int,
|
||||||
cv.Inclusive(CONF_CHANNEL, "manual"): cv.int_,
|
cv.Optional(CONF_CHANNEL): cv.int_,
|
||||||
cv.Inclusive(CONF_NETWORK_KEY, "manual"): cv.hex_int,
|
cv.Optional(CONF_NETWORK_KEY): cv.hex_int,
|
||||||
cv.Optional(CONF_EXT_PAN_ID): cv.hex_int,
|
cv.Optional(CONF_EXT_PAN_ID): cv.hex_int,
|
||||||
cv.Optional(CONF_NETWORK_NAME): cv.string_strict,
|
cv.Optional(CONF_NETWORK_NAME): cv.string_strict,
|
||||||
cv.Optional(CONF_PSKC): cv.hex_int,
|
cv.Optional(CONF_PSKC): cv.hex_int,
|
||||||
@ -108,12 +113,14 @@ CONFIG_SCHEMA = cv.All(
|
|||||||
cv.GenerateID(): cv.declare_id(OpenThreadComponent),
|
cv.GenerateID(): cv.declare_id(OpenThreadComponent),
|
||||||
cv.GenerateID(CONF_SRP_ID): cv.declare_id(OpenThreadSrpComponent),
|
cv.GenerateID(CONF_SRP_ID): cv.declare_id(OpenThreadSrpComponent),
|
||||||
cv.GenerateID(CONF_MDNS_ID): cv.use_id(MDNSComponent),
|
cv.GenerateID(CONF_MDNS_ID): cv.use_id(MDNSComponent),
|
||||||
|
cv.Optional(CONF_DEVICE_TYPE, default="FTD"): cv.one_of(
|
||||||
|
*CONF_DEVICE_TYPES, upper=True
|
||||||
|
),
|
||||||
cv.Optional(CONF_FORCE_DATASET): cv.boolean,
|
cv.Optional(CONF_FORCE_DATASET): cv.boolean,
|
||||||
cv.Optional(CONF_TLV): cv.string_strict,
|
cv.Optional(CONF_TLV): cv.string_strict,
|
||||||
}
|
}
|
||||||
).extend(_CONNECTION_SCHEMA),
|
).extend(_CONNECTION_SCHEMA),
|
||||||
cv.has_exactly_one_key(CONF_PAN_ID, CONF_TLV),
|
cv.has_exactly_one_key(CONF_NETWORK_KEY, CONF_TLV),
|
||||||
_convert_tlv,
|
|
||||||
cv.only_with_esp_idf,
|
cv.only_with_esp_idf,
|
||||||
only_on_variant(supported=[VARIANT_ESP32C6, VARIANT_ESP32H2]),
|
only_on_variant(supported=[VARIANT_ESP32C6, VARIANT_ESP32H2]),
|
||||||
)
|
)
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
CONF_DEVICE_TYPE = "device_type"
|
||||||
CONF_EXT_PAN_ID = "ext_pan_id"
|
CONF_EXT_PAN_ID = "ext_pan_id"
|
||||||
CONF_FORCE_DATASET = "force_dataset"
|
CONF_FORCE_DATASET = "force_dataset"
|
||||||
CONF_MDNS_ID = "mdns_id"
|
CONF_MDNS_ID = "mdns_id"
|
||||||
|
@ -111,14 +111,36 @@ void OpenThreadComponent::ot_main() {
|
|||||||
esp_openthread_cli_create_task();
|
esp_openthread_cli_create_task();
|
||||||
#endif
|
#endif
|
||||||
ESP_LOGI(TAG, "Activating dataset...");
|
ESP_LOGI(TAG, "Activating dataset...");
|
||||||
otOperationalDatasetTlvs dataset;
|
otOperationalDatasetTlvs dataset = {};
|
||||||
|
|
||||||
#ifdef CONFIG_OPENTHREAD_FORCE_DATASET
|
#ifndef USE_OPENTHREAD_FORCE_DATASET
|
||||||
ESP_ERROR_CHECK(esp_openthread_auto_start(NULL));
|
// Check if openthread has a valid dataset from a previous execution
|
||||||
#else
|
|
||||||
otError error = otDatasetGetActiveTlvs(esp_openthread_get_instance(), &dataset);
|
otError error = otDatasetGetActiveTlvs(esp_openthread_get_instance(), &dataset);
|
||||||
ESP_ERROR_CHECK(esp_openthread_auto_start((error == OT_ERROR_NONE) ? &dataset : NULL));
|
if (error != OT_ERROR_NONE) {
|
||||||
|
// Make sure the length is 0 so we fallback to the configuration
|
||||||
|
dataset.mLength = 0;
|
||||||
|
} else {
|
||||||
|
ESP_LOGI(TAG, "Found OpenThread-managed dataset, ignoring esphome configuration");
|
||||||
|
ESP_LOGI(TAG, "(set force_dataset: true to override)");
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef USE_OPENTHREAD_TLVS
|
||||||
|
if (dataset.mLength == 0) {
|
||||||
|
// If we didn't have an active dataset, and we have tlvs, parse it and pass it to esp_openthread_auto_start
|
||||||
|
size_t len = (sizeof(USE_OPENTHREAD_TLVS) - 1) / 2;
|
||||||
|
if (len > sizeof(dataset.mTlvs)) {
|
||||||
|
ESP_LOGW(TAG, "TLV buffer too small, truncating");
|
||||||
|
len = sizeof(dataset.mTlvs);
|
||||||
|
}
|
||||||
|
parse_hex(USE_OPENTHREAD_TLVS, sizeof(USE_OPENTHREAD_TLVS) - 1, dataset.mTlvs, len);
|
||||||
|
dataset.mLength = len;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Pass the existing dataset, or NULL which will use the preprocessor definitions
|
||||||
|
ESP_ERROR_CHECK(esp_openthread_auto_start(dataset.mLength > 0 ? &dataset : nullptr));
|
||||||
|
|
||||||
esp_openthread_launch_mainloop();
|
esp_openthread_launch_mainloop();
|
||||||
|
|
||||||
// Clean up
|
// Clean up
|
||||||
|
@ -1,65 +0,0 @@
|
|||||||
# Sourced from https://gist.github.com/agners/0338576e0003318b63ec1ea75adc90f9
|
|
||||||
import binascii
|
|
||||||
import ipaddress
|
|
||||||
|
|
||||||
from esphome.const import CONF_CHANNEL
|
|
||||||
|
|
||||||
from . import (
|
|
||||||
CONF_EXT_PAN_ID,
|
|
||||||
CONF_MESH_LOCAL_PREFIX,
|
|
||||||
CONF_NETWORK_KEY,
|
|
||||||
CONF_NETWORK_NAME,
|
|
||||||
CONF_PAN_ID,
|
|
||||||
CONF_PSKC,
|
|
||||||
)
|
|
||||||
|
|
||||||
TLV_TYPES = {
|
|
||||||
0: CONF_CHANNEL,
|
|
||||||
1: CONF_PAN_ID,
|
|
||||||
2: CONF_EXT_PAN_ID,
|
|
||||||
3: CONF_NETWORK_NAME,
|
|
||||||
4: CONF_PSKC,
|
|
||||||
5: CONF_NETWORK_KEY,
|
|
||||||
7: CONF_MESH_LOCAL_PREFIX,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def parse_tlv(tlv) -> dict:
|
|
||||||
data = binascii.a2b_hex(tlv)
|
|
||||||
output = {}
|
|
||||||
pos = 0
|
|
||||||
while pos < len(data):
|
|
||||||
tag = data[pos]
|
|
||||||
pos += 1
|
|
||||||
_len = data[pos]
|
|
||||||
pos += 1
|
|
||||||
val = data[pos : pos + _len]
|
|
||||||
pos += _len
|
|
||||||
if tag in TLV_TYPES:
|
|
||||||
if tag == 3:
|
|
||||||
output[TLV_TYPES[tag]] = val.decode("utf-8")
|
|
||||||
elif tag == 7:
|
|
||||||
mesh_local_prefix = binascii.hexlify(val).decode("utf-8")
|
|
||||||
mesh_local_prefix_str = f"{mesh_local_prefix}0000000000000000"
|
|
||||||
ipv6_bytes = bytes.fromhex(mesh_local_prefix_str)
|
|
||||||
ipv6_address = ipaddress.IPv6Address(ipv6_bytes)
|
|
||||||
output[TLV_TYPES[tag]] = f"{ipv6_address}/64"
|
|
||||||
else:
|
|
||||||
output[TLV_TYPES[tag]] = int.from_bytes(val)
|
|
||||||
return output
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
import sys
|
|
||||||
|
|
||||||
args = sys.argv[1:]
|
|
||||||
parsed = parse_tlv(args[0])
|
|
||||||
# print the parsed TLV data
|
|
||||||
for key, value in parsed.items():
|
|
||||||
if isinstance(value, bytes):
|
|
||||||
value = value.hex()
|
|
||||||
print(f"{key}: {value}")
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
@ -67,7 +67,28 @@ class OTAComponent : public Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
CallbackManager<void(ota::OTAState, float, uint8_t)> state_callback_{};
|
/** Extended callback manager with deferred call support.
|
||||||
|
*
|
||||||
|
* This adds a call_deferred() method for thread-safe execution from other tasks.
|
||||||
|
*/
|
||||||
|
class StateCallbackManager : public CallbackManager<void(OTAState, float, uint8_t)> {
|
||||||
|
public:
|
||||||
|
StateCallbackManager(OTAComponent *component) : component_(component) {}
|
||||||
|
|
||||||
|
/** Call callbacks with deferral to main loop (for thread safety).
|
||||||
|
*
|
||||||
|
* This should be used by OTA implementations that run in separate tasks
|
||||||
|
* (like web_server OTA) to ensure callbacks execute in the main loop.
|
||||||
|
*/
|
||||||
|
void call_deferred(ota::OTAState state, float progress, uint8_t error) {
|
||||||
|
component_->defer([this, state, progress, error]() { this->call(state, progress, error); });
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
OTAComponent *component_;
|
||||||
|
};
|
||||||
|
|
||||||
|
StateCallbackManager state_callback_{this};
|
||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -89,6 +110,11 @@ class OTAGlobalCallback {
|
|||||||
|
|
||||||
OTAGlobalCallback *get_global_ota_callback();
|
OTAGlobalCallback *get_global_ota_callback();
|
||||||
void register_ota_platform(OTAComponent *ota_caller);
|
void register_ota_platform(OTAComponent *ota_caller);
|
||||||
|
|
||||||
|
// OTA implementations should use:
|
||||||
|
// - state_callback_.call() when already in main loop (e.g., esphome OTA)
|
||||||
|
// - state_callback_.call_deferred() when in separate task (e.g., web_server OTA)
|
||||||
|
// This ensures proper callback execution in all contexts.
|
||||||
#endif
|
#endif
|
||||||
std::unique_ptr<ota::OTABackend> make_ota_backend();
|
std::unique_ptr<ota::OTABackend> make_ota_backend();
|
||||||
|
|
||||||
|
@ -15,6 +15,11 @@ static const char *const TAG = "ota.arduino_esp32";
|
|||||||
std::unique_ptr<ota::OTABackend> make_ota_backend() { return make_unique<ota::ArduinoESP32OTABackend>(); }
|
std::unique_ptr<ota::OTABackend> make_ota_backend() { return make_unique<ota::ArduinoESP32OTABackend>(); }
|
||||||
|
|
||||||
OTAResponseTypes ArduinoESP32OTABackend::begin(size_t image_size) {
|
OTAResponseTypes ArduinoESP32OTABackend::begin(size_t image_size) {
|
||||||
|
// Handle UPDATE_SIZE_UNKNOWN (0) which is used by web server OTA
|
||||||
|
// where the exact firmware size is unknown due to multipart encoding
|
||||||
|
if (image_size == 0) {
|
||||||
|
image_size = UPDATE_SIZE_UNKNOWN;
|
||||||
|
}
|
||||||
bool ret = Update.begin(image_size, U_FLASH);
|
bool ret = Update.begin(image_size, U_FLASH);
|
||||||
if (ret) {
|
if (ret) {
|
||||||
return OTA_RESPONSE_OK;
|
return OTA_RESPONSE_OK;
|
||||||
@ -29,7 +34,10 @@ OTAResponseTypes ArduinoESP32OTABackend::begin(size_t image_size) {
|
|||||||
return OTA_RESPONSE_ERROR_UNKNOWN;
|
return OTA_RESPONSE_ERROR_UNKNOWN;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ArduinoESP32OTABackend::set_update_md5(const char *md5) { Update.setMD5(md5); }
|
void ArduinoESP32OTABackend::set_update_md5(const char *md5) {
|
||||||
|
Update.setMD5(md5);
|
||||||
|
this->md5_set_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
OTAResponseTypes ArduinoESP32OTABackend::write(uint8_t *data, size_t len) {
|
OTAResponseTypes ArduinoESP32OTABackend::write(uint8_t *data, size_t len) {
|
||||||
size_t written = Update.write(data, len);
|
size_t written = Update.write(data, len);
|
||||||
@ -44,7 +52,9 @@ OTAResponseTypes ArduinoESP32OTABackend::write(uint8_t *data, size_t len) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
OTAResponseTypes ArduinoESP32OTABackend::end() {
|
OTAResponseTypes ArduinoESP32OTABackend::end() {
|
||||||
if (Update.end()) {
|
// Use strict validation (false) when MD5 is set, lenient validation (true) when no MD5
|
||||||
|
// This matches the behavior of the old web_server OTA implementation
|
||||||
|
if (Update.end(!this->md5_set_)) {
|
||||||
return OTA_RESPONSE_OK;
|
return OTA_RESPONSE_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,6 +16,9 @@ class ArduinoESP32OTABackend : public OTABackend {
|
|||||||
OTAResponseTypes end() override;
|
OTAResponseTypes end() override;
|
||||||
void abort() override;
|
void abort() override;
|
||||||
bool supports_compression() override { return false; }
|
bool supports_compression() override { return false; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool md5_set_{false};
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace ota
|
} // namespace ota
|
||||||
|
@ -17,6 +17,11 @@ static const char *const TAG = "ota.arduino_esp8266";
|
|||||||
std::unique_ptr<ota::OTABackend> make_ota_backend() { return make_unique<ota::ArduinoESP8266OTABackend>(); }
|
std::unique_ptr<ota::OTABackend> make_ota_backend() { return make_unique<ota::ArduinoESP8266OTABackend>(); }
|
||||||
|
|
||||||
OTAResponseTypes ArduinoESP8266OTABackend::begin(size_t image_size) {
|
OTAResponseTypes ArduinoESP8266OTABackend::begin(size_t image_size) {
|
||||||
|
// Handle UPDATE_SIZE_UNKNOWN (0) by calculating available space
|
||||||
|
if (image_size == 0) {
|
||||||
|
// NOLINTNEXTLINE(readability-static-accessed-through-instance)
|
||||||
|
image_size = (ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000;
|
||||||
|
}
|
||||||
bool ret = Update.begin(image_size, U_FLASH);
|
bool ret = Update.begin(image_size, U_FLASH);
|
||||||
if (ret) {
|
if (ret) {
|
||||||
esp8266::preferences_prevent_write(true);
|
esp8266::preferences_prevent_write(true);
|
||||||
@ -38,7 +43,10 @@ OTAResponseTypes ArduinoESP8266OTABackend::begin(size_t image_size) {
|
|||||||
return OTA_RESPONSE_ERROR_UNKNOWN;
|
return OTA_RESPONSE_ERROR_UNKNOWN;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ArduinoESP8266OTABackend::set_update_md5(const char *md5) { Update.setMD5(md5); }
|
void ArduinoESP8266OTABackend::set_update_md5(const char *md5) {
|
||||||
|
Update.setMD5(md5);
|
||||||
|
this->md5_set_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
OTAResponseTypes ArduinoESP8266OTABackend::write(uint8_t *data, size_t len) {
|
OTAResponseTypes ArduinoESP8266OTABackend::write(uint8_t *data, size_t len) {
|
||||||
size_t written = Update.write(data, len);
|
size_t written = Update.write(data, len);
|
||||||
@ -53,13 +61,19 @@ OTAResponseTypes ArduinoESP8266OTABackend::write(uint8_t *data, size_t len) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
OTAResponseTypes ArduinoESP8266OTABackend::end() {
|
OTAResponseTypes ArduinoESP8266OTABackend::end() {
|
||||||
if (Update.end()) {
|
// Use strict validation (false) when MD5 is set, lenient validation (true) when no MD5
|
||||||
|
// This matches the behavior of the old web_server OTA implementation
|
||||||
|
bool success = Update.end(!this->md5_set_);
|
||||||
|
|
||||||
|
// On ESP8266, Update.end() might return false even with error code 0
|
||||||
|
// Check the actual error code to determine success
|
||||||
|
uint8_t error = Update.getError();
|
||||||
|
|
||||||
|
if (success || error == UPDATE_ERROR_OK) {
|
||||||
return OTA_RESPONSE_OK;
|
return OTA_RESPONSE_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t error = Update.getError();
|
|
||||||
ESP_LOGE(TAG, "End error: %d", error);
|
ESP_LOGE(TAG, "End error: %d", error);
|
||||||
|
|
||||||
return OTA_RESPONSE_ERROR_UPDATE_END;
|
return OTA_RESPONSE_ERROR_UPDATE_END;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,6 +21,9 @@ class ArduinoESP8266OTABackend : public OTABackend {
|
|||||||
#else
|
#else
|
||||||
bool supports_compression() override { return false; }
|
bool supports_compression() override { return false; }
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool md5_set_{false};
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace ota
|
} // namespace ota
|
||||||
|
@ -15,6 +15,11 @@ static const char *const TAG = "ota.arduino_libretiny";
|
|||||||
std::unique_ptr<ota::OTABackend> make_ota_backend() { return make_unique<ota::ArduinoLibreTinyOTABackend>(); }
|
std::unique_ptr<ota::OTABackend> make_ota_backend() { return make_unique<ota::ArduinoLibreTinyOTABackend>(); }
|
||||||
|
|
||||||
OTAResponseTypes ArduinoLibreTinyOTABackend::begin(size_t image_size) {
|
OTAResponseTypes ArduinoLibreTinyOTABackend::begin(size_t image_size) {
|
||||||
|
// Handle UPDATE_SIZE_UNKNOWN (0) which is used by web server OTA
|
||||||
|
// where the exact firmware size is unknown due to multipart encoding
|
||||||
|
if (image_size == 0) {
|
||||||
|
image_size = UPDATE_SIZE_UNKNOWN;
|
||||||
|
}
|
||||||
bool ret = Update.begin(image_size, U_FLASH);
|
bool ret = Update.begin(image_size, U_FLASH);
|
||||||
if (ret) {
|
if (ret) {
|
||||||
return OTA_RESPONSE_OK;
|
return OTA_RESPONSE_OK;
|
||||||
@ -29,7 +34,10 @@ OTAResponseTypes ArduinoLibreTinyOTABackend::begin(size_t image_size) {
|
|||||||
return OTA_RESPONSE_ERROR_UNKNOWN;
|
return OTA_RESPONSE_ERROR_UNKNOWN;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ArduinoLibreTinyOTABackend::set_update_md5(const char *md5) { Update.setMD5(md5); }
|
void ArduinoLibreTinyOTABackend::set_update_md5(const char *md5) {
|
||||||
|
Update.setMD5(md5);
|
||||||
|
this->md5_set_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
OTAResponseTypes ArduinoLibreTinyOTABackend::write(uint8_t *data, size_t len) {
|
OTAResponseTypes ArduinoLibreTinyOTABackend::write(uint8_t *data, size_t len) {
|
||||||
size_t written = Update.write(data, len);
|
size_t written = Update.write(data, len);
|
||||||
@ -44,7 +52,9 @@ OTAResponseTypes ArduinoLibreTinyOTABackend::write(uint8_t *data, size_t len) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
OTAResponseTypes ArduinoLibreTinyOTABackend::end() {
|
OTAResponseTypes ArduinoLibreTinyOTABackend::end() {
|
||||||
if (Update.end()) {
|
// Use strict validation (false) when MD5 is set, lenient validation (true) when no MD5
|
||||||
|
// This matches the behavior of the old web_server OTA implementation
|
||||||
|
if (Update.end(!this->md5_set_)) {
|
||||||
return OTA_RESPONSE_OK;
|
return OTA_RESPONSE_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,6 +15,9 @@ class ArduinoLibreTinyOTABackend : public OTABackend {
|
|||||||
OTAResponseTypes end() override;
|
OTAResponseTypes end() override;
|
||||||
void abort() override;
|
void abort() override;
|
||||||
bool supports_compression() override { return false; }
|
bool supports_compression() override { return false; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool md5_set_{false};
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace ota
|
} // namespace ota
|
||||||
|
@ -17,6 +17,8 @@ static const char *const TAG = "ota.arduino_rp2040";
|
|||||||
std::unique_ptr<ota::OTABackend> make_ota_backend() { return make_unique<ota::ArduinoRP2040OTABackend>(); }
|
std::unique_ptr<ota::OTABackend> make_ota_backend() { return make_unique<ota::ArduinoRP2040OTABackend>(); }
|
||||||
|
|
||||||
OTAResponseTypes ArduinoRP2040OTABackend::begin(size_t image_size) {
|
OTAResponseTypes ArduinoRP2040OTABackend::begin(size_t image_size) {
|
||||||
|
// OTA size of 0 is not currently handled, but
|
||||||
|
// web_server is not supported for RP2040, so this is not an issue.
|
||||||
bool ret = Update.begin(image_size, U_FLASH);
|
bool ret = Update.begin(image_size, U_FLASH);
|
||||||
if (ret) {
|
if (ret) {
|
||||||
rp2040::preferences_prevent_write(true);
|
rp2040::preferences_prevent_write(true);
|
||||||
@ -38,7 +40,10 @@ OTAResponseTypes ArduinoRP2040OTABackend::begin(size_t image_size) {
|
|||||||
return OTA_RESPONSE_ERROR_UNKNOWN;
|
return OTA_RESPONSE_ERROR_UNKNOWN;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ArduinoRP2040OTABackend::set_update_md5(const char *md5) { Update.setMD5(md5); }
|
void ArduinoRP2040OTABackend::set_update_md5(const char *md5) {
|
||||||
|
Update.setMD5(md5);
|
||||||
|
this->md5_set_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
OTAResponseTypes ArduinoRP2040OTABackend::write(uint8_t *data, size_t len) {
|
OTAResponseTypes ArduinoRP2040OTABackend::write(uint8_t *data, size_t len) {
|
||||||
size_t written = Update.write(data, len);
|
size_t written = Update.write(data, len);
|
||||||
@ -53,7 +58,9 @@ OTAResponseTypes ArduinoRP2040OTABackend::write(uint8_t *data, size_t len) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
OTAResponseTypes ArduinoRP2040OTABackend::end() {
|
OTAResponseTypes ArduinoRP2040OTABackend::end() {
|
||||||
if (Update.end()) {
|
// Use strict validation (false) when MD5 is set, lenient validation (true) when no MD5
|
||||||
|
// This matches the behavior of the old web_server OTA implementation
|
||||||
|
if (Update.end(!this->md5_set_)) {
|
||||||
return OTA_RESPONSE_OK;
|
return OTA_RESPONSE_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,6 +17,9 @@ class ArduinoRP2040OTABackend : public OTABackend {
|
|||||||
OTAResponseTypes end() override;
|
OTAResponseTypes end() override;
|
||||||
void abort() override;
|
void abort() override;
|
||||||
bool supports_compression() override { return false; }
|
bool supports_compression() override { return false; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool md5_set_{false};
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace ota
|
} // namespace ota
|
||||||
|
@ -6,10 +6,7 @@
|
|||||||
|
|
||||||
#include <esp_ota_ops.h>
|
#include <esp_ota_ops.h>
|
||||||
#include <esp_task_wdt.h>
|
#include <esp_task_wdt.h>
|
||||||
|
|
||||||
#if ESP_IDF_VERSION_MAJOR >= 5
|
|
||||||
#include <spi_flash_mmap.h>
|
#include <spi_flash_mmap.h>
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace ota {
|
namespace ota {
|
||||||
@ -24,7 +21,6 @@ OTAResponseTypes IDFOTABackend::begin(size_t image_size) {
|
|||||||
|
|
||||||
#if CONFIG_ESP_TASK_WDT_TIMEOUT_S < 15
|
#if CONFIG_ESP_TASK_WDT_TIMEOUT_S < 15
|
||||||
// The following function takes longer than the 5 seconds timeout of WDT
|
// The following function takes longer than the 5 seconds timeout of WDT
|
||||||
#if ESP_IDF_VERSION_MAJOR >= 5
|
|
||||||
esp_task_wdt_config_t wdtc;
|
esp_task_wdt_config_t wdtc;
|
||||||
wdtc.idle_core_mask = 0;
|
wdtc.idle_core_mask = 0;
|
||||||
#if CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0
|
#if CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0
|
||||||
@ -36,21 +32,14 @@ OTAResponseTypes IDFOTABackend::begin(size_t image_size) {
|
|||||||
wdtc.timeout_ms = 15000;
|
wdtc.timeout_ms = 15000;
|
||||||
wdtc.trigger_panic = false;
|
wdtc.trigger_panic = false;
|
||||||
esp_task_wdt_reconfigure(&wdtc);
|
esp_task_wdt_reconfigure(&wdtc);
|
||||||
#else
|
|
||||||
esp_task_wdt_init(15, false);
|
|
||||||
#endif
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
esp_err_t err = esp_ota_begin(this->partition_, image_size, &this->update_handle_);
|
esp_err_t err = esp_ota_begin(this->partition_, image_size, &this->update_handle_);
|
||||||
|
|
||||||
#if CONFIG_ESP_TASK_WDT_TIMEOUT_S < 15
|
#if CONFIG_ESP_TASK_WDT_TIMEOUT_S < 15
|
||||||
// Set the WDT back to the configured timeout
|
// Set the WDT back to the configured timeout
|
||||||
#if ESP_IDF_VERSION_MAJOR >= 5
|
|
||||||
wdtc.timeout_ms = CONFIG_ESP_TASK_WDT_TIMEOUT_S * 1000;
|
wdtc.timeout_ms = CONFIG_ESP_TASK_WDT_TIMEOUT_S * 1000;
|
||||||
esp_task_wdt_reconfigure(&wdtc);
|
esp_task_wdt_reconfigure(&wdtc);
|
||||||
#else
|
|
||||||
esp_task_wdt_init(CONFIG_ESP_TASK_WDT_TIMEOUT_S, false);
|
|
||||||
#endif
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (err != ESP_OK) {
|
if (err != ESP_OK) {
|
||||||
@ -67,7 +56,10 @@ OTAResponseTypes IDFOTABackend::begin(size_t image_size) {
|
|||||||
return OTA_RESPONSE_OK;
|
return OTA_RESPONSE_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
void IDFOTABackend::set_update_md5(const char *expected_md5) { memcpy(this->expected_bin_md5_, expected_md5, 32); }
|
void IDFOTABackend::set_update_md5(const char *expected_md5) {
|
||||||
|
memcpy(this->expected_bin_md5_, expected_md5, 32);
|
||||||
|
this->md5_set_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
OTAResponseTypes IDFOTABackend::write(uint8_t *data, size_t len) {
|
OTAResponseTypes IDFOTABackend::write(uint8_t *data, size_t len) {
|
||||||
esp_err_t err = esp_ota_write(this->update_handle_, data, len);
|
esp_err_t err = esp_ota_write(this->update_handle_, data, len);
|
||||||
@ -84,10 +76,12 @@ OTAResponseTypes IDFOTABackend::write(uint8_t *data, size_t len) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
OTAResponseTypes IDFOTABackend::end() {
|
OTAResponseTypes IDFOTABackend::end() {
|
||||||
this->md5_.calculate();
|
if (this->md5_set_) {
|
||||||
if (!this->md5_.equals_hex(this->expected_bin_md5_)) {
|
this->md5_.calculate();
|
||||||
this->abort();
|
if (!this->md5_.equals_hex(this->expected_bin_md5_)) {
|
||||||
return OTA_RESPONSE_ERROR_MD5_MISMATCH;
|
this->abort();
|
||||||
|
return OTA_RESPONSE_ERROR_MD5_MISMATCH;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
esp_err_t err = esp_ota_end(this->update_handle_);
|
esp_err_t err = esp_ota_end(this->update_handle_);
|
||||||
this->update_handle_ = 0;
|
this->update_handle_ = 0;
|
||||||
|
@ -24,6 +24,7 @@ class IDFOTABackend : public OTABackend {
|
|||||||
const esp_partition_t *partition_;
|
const esp_partition_t *partition_;
|
||||||
md5::MD5Digest md5_{};
|
md5::MD5Digest md5_{};
|
||||||
char expected_bin_md5_[32];
|
char expected_bin_md5_[32];
|
||||||
|
bool md5_set_{false};
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace ota
|
} // namespace ota
|
||||||
|
@ -74,7 +74,7 @@ BASE_SCHEMA = cv.All(
|
|||||||
{
|
{
|
||||||
cv.Required(CONF_PATH): validate_yaml_filename,
|
cv.Required(CONF_PATH): validate_yaml_filename,
|
||||||
cv.Optional(CONF_VARS, default={}): cv.Schema(
|
cv.Optional(CONF_VARS, default={}): cv.Schema(
|
||||||
{cv.string: cv.string}
|
{cv.string: object}
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
@ -148,7 +148,6 @@ def _process_base_package(config: dict) -> dict:
|
|||||||
raise cv.Invalid(
|
raise cv.Invalid(
|
||||||
f"Current ESPHome Version is too old to use this package: {ESPHOME_VERSION} < {min_version}"
|
f"Current ESPHome Version is too old to use this package: {ESPHOME_VERSION} < {min_version}"
|
||||||
)
|
)
|
||||||
vars = {k: str(v) for k, v in vars.items()}
|
|
||||||
new_yaml = yaml_util.substitute_vars(new_yaml, vars)
|
new_yaml = yaml_util.substitute_vars(new_yaml, vars)
|
||||||
packages[f"{filename}{idx}"] = new_yaml
|
packages[f"{filename}{idx}"] = new_yaml
|
||||||
except EsphomeError as e:
|
except EsphomeError as e:
|
||||||
|
@ -2,9 +2,7 @@
|
|||||||
#ifdef USE_ESP32
|
#ifdef USE_ESP32
|
||||||
#include "psram.h"
|
#include "psram.h"
|
||||||
#include <esp_idf_version.h>
|
#include <esp_idf_version.h>
|
||||||
#if defined(USE_ESP_IDF) && ESP_IDF_VERSION_MAJOR >= 5
|
|
||||||
#include <esp_psram.h>
|
#include <esp_psram.h>
|
||||||
#endif // USE_ESP_IDF
|
|
||||||
|
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
@ -16,7 +14,6 @@ static const char *const TAG = "psram";
|
|||||||
|
|
||||||
void PsramComponent::dump_config() {
|
void PsramComponent::dump_config() {
|
||||||
ESP_LOGCONFIG(TAG, "PSRAM:");
|
ESP_LOGCONFIG(TAG, "PSRAM:");
|
||||||
#if defined(USE_ESP_IDF) && ESP_IDF_VERSION_MAJOR >= 5
|
|
||||||
bool available = esp_psram_is_initialized();
|
bool available = esp_psram_is_initialized();
|
||||||
|
|
||||||
ESP_LOGCONFIG(TAG, " Available: %s", YESNO(available));
|
ESP_LOGCONFIG(TAG, " Available: %s", YESNO(available));
|
||||||
@ -26,23 +23,6 @@ void PsramComponent::dump_config() {
|
|||||||
ESP_LOGCONFIG(TAG, " ECC enabled: YES");
|
ESP_LOGCONFIG(TAG, " ECC enabled: YES");
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
#else
|
|
||||||
// Technically this can be false if the PSRAM is full, but heap_caps_get_total_size() isn't always available, and it's
|
|
||||||
// very unlikely for the PSRAM to be full.
|
|
||||||
bool available = heap_caps_get_free_size(MALLOC_CAP_SPIRAM) > 0;
|
|
||||||
ESP_LOGCONFIG(TAG, " Available: %s", YESNO(available));
|
|
||||||
|
|
||||||
if (available) {
|
|
||||||
const size_t psram_total_size_bytes = heap_caps_get_total_size(MALLOC_CAP_SPIRAM);
|
|
||||||
const float psram_total_size_kb = psram_total_size_bytes / 1024.0f;
|
|
||||||
|
|
||||||
if (abs(std::round(psram_total_size_kb) - psram_total_size_kb) < 0.05f) {
|
|
||||||
ESP_LOGCONFIG(TAG, " Size: %.0f KB", psram_total_size_kb);
|
|
||||||
} else {
|
|
||||||
ESP_LOGCONFIG(TAG, " Size: %zu bytes", psram_total_size_bytes);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif // USE_ESP_IDF
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace psram
|
} // namespace psram
|
||||||
|
@ -31,6 +31,10 @@ void PulseMeterSensor::setup() {
|
|||||||
this->pulse_state_.latched_ = this->last_pin_val_;
|
this->pulse_state_.latched_ = this->last_pin_val_;
|
||||||
this->pin_->attach_interrupt(PulseMeterSensor::pulse_intr, this, gpio::INTERRUPT_ANY_EDGE);
|
this->pin_->attach_interrupt(PulseMeterSensor::pulse_intr, this, gpio::INTERRUPT_ANY_EDGE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this->total_sensor_ != nullptr) {
|
||||||
|
this->total_sensor_->publish_state(this->total_pulses_);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void PulseMeterSensor::loop() {
|
void PulseMeterSensor::loop() {
|
||||||
|
@ -146,11 +146,7 @@ void PVVXDisplay::sync_time_() {
|
|||||||
}
|
}
|
||||||
time.recalc_timestamp_utc(true); // calculate timestamp of local time
|
time.recalc_timestamp_utc(true); // calculate timestamp of local time
|
||||||
uint8_t blk[5] = {};
|
uint8_t blk[5] = {};
|
||||||
#if ESP_IDF_VERSION_MAJOR >= 5
|
|
||||||
ESP_LOGD(TAG, "[%s] Sync time with timestamp %" PRIu64 ".", this->parent_->address_str().c_str(), time.timestamp);
|
ESP_LOGD(TAG, "[%s] Sync time with timestamp %" PRIu64 ".", this->parent_->address_str().c_str(), time.timestamp);
|
||||||
#else
|
|
||||||
ESP_LOGD(TAG, "[%s] Sync time with timestamp %lu.", this->parent_->address_str().c_str(), time.timestamp);
|
|
||||||
#endif
|
|
||||||
blk[0] = 0x23;
|
blk[0] = 0x23;
|
||||||
blk[1] = time.timestamp & 0xff;
|
blk[1] = time.timestamp & 0xff;
|
||||||
blk[2] = (time.timestamp >> 8) & 0xff;
|
blk[2] = (time.timestamp >> 8) & 0xff;
|
||||||
|
@ -21,21 +21,24 @@ ECC = {
|
|||||||
"HIGH": qrcodegen_Ecc.qrcodegen_Ecc_HIGH,
|
"HIGH": qrcodegen_Ecc.qrcodegen_Ecc_HIGH,
|
||||||
}
|
}
|
||||||
|
|
||||||
CONFIG_SCHEMA = cv.Schema(
|
CONFIG_SCHEMA = cv.ensure_list(
|
||||||
{
|
cv.Schema(
|
||||||
cv.Required(CONF_ID): cv.declare_id(QRCode),
|
{
|
||||||
cv.Required(CONF_VALUE): cv.string,
|
cv.Required(CONF_ID): cv.declare_id(QRCode),
|
||||||
cv.Optional(CONF_ECC, default="LOW"): cv.enum(ECC, upper=True),
|
cv.Required(CONF_VALUE): cv.string,
|
||||||
}
|
cv.Optional(CONF_ECC, default="LOW"): cv.enum(ECC, upper=True),
|
||||||
|
}
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
async def to_code(config):
|
async def to_code(config):
|
||||||
cg.add_library("wjtje/qr-code-generator-library", "^1.7.0")
|
cg.add_library("wjtje/qr-code-generator-library", "^1.7.0")
|
||||||
|
|
||||||
var = cg.new_Pvariable(config[CONF_ID])
|
for entry in config:
|
||||||
cg.add(var.set_value(config[CONF_VALUE]))
|
var = cg.new_Pvariable(entry[CONF_ID])
|
||||||
cg.add(var.set_ecc(ECC[config[CONF_ECC]]))
|
cg.add(var.set_value(entry[CONF_VALUE]))
|
||||||
await cg.register_component(var, config)
|
cg.add(var.set_ecc(ECC[entry[CONF_ECC]]))
|
||||||
|
await cg.register_component(var, entry)
|
||||||
|
|
||||||
cg.add_define("USE_QR_CODE")
|
cg.add_define("USE_QR_CODE")
|
||||||
|
@ -10,10 +10,8 @@ void RpiDpiRgb::setup() {
|
|||||||
this->reset_display_();
|
this->reset_display_();
|
||||||
esp_lcd_rgb_panel_config_t config{};
|
esp_lcd_rgb_panel_config_t config{};
|
||||||
config.flags.fb_in_psram = 1;
|
config.flags.fb_in_psram = 1;
|
||||||
#if ESP_IDF_VERSION_MAJOR >= 5
|
|
||||||
config.bounce_buffer_size_px = this->width_ * 10;
|
config.bounce_buffer_size_px = this->width_ * 10;
|
||||||
config.num_fbs = 1;
|
config.num_fbs = 1;
|
||||||
#endif // ESP_IDF_VERSION_MAJOR
|
|
||||||
config.timings.h_res = this->width_;
|
config.timings.h_res = this->width_;
|
||||||
config.timings.v_res = this->height_;
|
config.timings.v_res = this->height_;
|
||||||
config.timings.hsync_pulse_width = this->hsync_pulse_width_;
|
config.timings.hsync_pulse_width = this->hsync_pulse_width_;
|
||||||
@ -47,10 +45,8 @@ void RpiDpiRgb::setup() {
|
|||||||
ESP_LOGCONFIG(TAG, "RPI_DPI_RGB setup complete");
|
ESP_LOGCONFIG(TAG, "RPI_DPI_RGB setup complete");
|
||||||
}
|
}
|
||||||
void RpiDpiRgb::loop() {
|
void RpiDpiRgb::loop() {
|
||||||
#if ESP_IDF_VERSION_MAJOR >= 5
|
|
||||||
if (this->handle_ != nullptr)
|
if (this->handle_ != nullptr)
|
||||||
esp_lcd_rgb_panel_restart(this->handle_);
|
esp_lcd_rgb_panel_restart(this->handle_);
|
||||||
#endif // ESP_IDF_VERSION_MAJOR
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void RpiDpiRgb::draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *ptr, display::ColorOrder order,
|
void RpiDpiRgb::draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *ptr, display::ColorOrder order,
|
||||||
|
@ -371,6 +371,7 @@ void Rtttl::finish_() {
|
|||||||
ESP_LOGD(TAG, "Playback finished");
|
ESP_LOGD(TAG, "Playback finished");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_DEBUG
|
||||||
static const LogString *state_to_string(State state) {
|
static const LogString *state_to_string(State state) {
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case STATE_STOPPED:
|
case STATE_STOPPED:
|
||||||
@ -387,6 +388,7 @@ static const LogString *state_to_string(State state) {
|
|||||||
return LOG_STR("UNKNOWN");
|
return LOG_STR("UNKNOWN");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
#endif
|
||||||
|
|
||||||
void Rtttl::set_state_(State state) {
|
void Rtttl::set_state_(State state) {
|
||||||
State old_state = this->state_;
|
State old_state = this->state_;
|
||||||
|
@ -6,6 +6,7 @@ from esphome.const import (
|
|||||||
CONF_DIELECTRIC_CONSTANT,
|
CONF_DIELECTRIC_CONSTANT,
|
||||||
CONF_ID,
|
CONF_ID,
|
||||||
CONF_MOISTURE,
|
CONF_MOISTURE,
|
||||||
|
CONF_PERMITTIVITY,
|
||||||
CONF_TEMPERATURE,
|
CONF_TEMPERATURE,
|
||||||
CONF_VOLTAGE,
|
CONF_VOLTAGE,
|
||||||
DEVICE_CLASS_TEMPERATURE,
|
DEVICE_CLASS_TEMPERATURE,
|
||||||
@ -33,7 +34,10 @@ CONFIG_SCHEMA = (
|
|||||||
accuracy_decimals=0,
|
accuracy_decimals=0,
|
||||||
state_class=STATE_CLASS_MEASUREMENT,
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
),
|
),
|
||||||
cv.Optional(CONF_DIELECTRIC_CONSTANT): sensor.sensor_schema(
|
cv.Optional(CONF_DIELECTRIC_CONSTANT): cv.invalid(
|
||||||
|
"Use 'permittivity' instead"
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_PERMITTIVITY): sensor.sensor_schema(
|
||||||
unit_of_measurement=UNIT_EMPTY,
|
unit_of_measurement=UNIT_EMPTY,
|
||||||
accuracy_decimals=2,
|
accuracy_decimals=2,
|
||||||
state_class=STATE_CLASS_MEASUREMENT,
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
@ -76,9 +80,9 @@ async def to_code(config):
|
|||||||
sens = await sensor.new_sensor(config[CONF_COUNTS])
|
sens = await sensor.new_sensor(config[CONF_COUNTS])
|
||||||
cg.add(var.set_counts_sensor(sens))
|
cg.add(var.set_counts_sensor(sens))
|
||||||
|
|
||||||
if CONF_DIELECTRIC_CONSTANT in config:
|
if CONF_PERMITTIVITY in config:
|
||||||
sens = await sensor.new_sensor(config[CONF_DIELECTRIC_CONSTANT])
|
sens = await sensor.new_sensor(config[CONF_PERMITTIVITY])
|
||||||
cg.add(var.set_dielectric_constant_sensor(sens))
|
cg.add(var.set_permittivity_sensor(sens))
|
||||||
|
|
||||||
if CONF_TEMPERATURE in config:
|
if CONF_TEMPERATURE in config:
|
||||||
sens = await sensor.new_sensor(config[CONF_TEMPERATURE])
|
sens = await sensor.new_sensor(config[CONF_TEMPERATURE])
|
||||||
|
@ -16,7 +16,7 @@ void SMT100Component::loop() {
|
|||||||
while (this->available() != 0) {
|
while (this->available() != 0) {
|
||||||
if (readline_(read(), buffer, MAX_LINE_LENGTH) > 0) {
|
if (readline_(read(), buffer, MAX_LINE_LENGTH) > 0) {
|
||||||
int counts = (int) strtol((strtok(buffer, ",")), nullptr, 10);
|
int counts = (int) strtol((strtok(buffer, ",")), nullptr, 10);
|
||||||
float dielectric_constant = (float) strtod((strtok(nullptr, ",")), nullptr);
|
float permittivity = (float) strtod((strtok(nullptr, ",")), nullptr);
|
||||||
float moisture = (float) strtod((strtok(nullptr, ",")), nullptr);
|
float moisture = (float) strtod((strtok(nullptr, ",")), nullptr);
|
||||||
float temperature = (float) strtod((strtok(nullptr, ",")), nullptr);
|
float temperature = (float) strtod((strtok(nullptr, ",")), nullptr);
|
||||||
float voltage = (float) strtod((strtok(nullptr, ",")), nullptr);
|
float voltage = (float) strtod((strtok(nullptr, ",")), nullptr);
|
||||||
@ -25,8 +25,8 @@ void SMT100Component::loop() {
|
|||||||
counts_sensor_->publish_state(counts);
|
counts_sensor_->publish_state(counts);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this->dielectric_constant_sensor_ != nullptr) {
|
if (this->permittivity_sensor_ != nullptr) {
|
||||||
dielectric_constant_sensor_->publish_state(dielectric_constant);
|
permittivity_sensor_->publish_state(permittivity);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this->moisture_sensor_ != nullptr) {
|
if (this->moisture_sensor_ != nullptr) {
|
||||||
@ -49,8 +49,8 @@ float SMT100Component::get_setup_priority() const { return setup_priority::DATA;
|
|||||||
void SMT100Component::dump_config() {
|
void SMT100Component::dump_config() {
|
||||||
ESP_LOGCONFIG(TAG, "SMT100:");
|
ESP_LOGCONFIG(TAG, "SMT100:");
|
||||||
|
|
||||||
LOG_SENSOR(TAG, "Counts", this->temperature_sensor_);
|
LOG_SENSOR(TAG, "Counts", this->counts_sensor_);
|
||||||
LOG_SENSOR(TAG, "Dielectric Constant", this->temperature_sensor_);
|
LOG_SENSOR(TAG, "Permittivity", this->permittivity_sensor_);
|
||||||
LOG_SENSOR(TAG, "Temperature", this->temperature_sensor_);
|
LOG_SENSOR(TAG, "Temperature", this->temperature_sensor_);
|
||||||
LOG_SENSOR(TAG, "Moisture", this->moisture_sensor_);
|
LOG_SENSOR(TAG, "Moisture", this->moisture_sensor_);
|
||||||
LOG_UPDATE_INTERVAL(this);
|
LOG_UPDATE_INTERVAL(this);
|
||||||
|
@ -20,8 +20,8 @@ class SMT100Component : public PollingComponent, public uart::UARTDevice {
|
|||||||
float get_setup_priority() const override;
|
float get_setup_priority() const override;
|
||||||
|
|
||||||
void set_counts_sensor(sensor::Sensor *counts_sensor) { this->counts_sensor_ = counts_sensor; }
|
void set_counts_sensor(sensor::Sensor *counts_sensor) { this->counts_sensor_ = counts_sensor; }
|
||||||
void set_dielectric_constant_sensor(sensor::Sensor *dielectric_constant_sensor) {
|
void set_permittivity_sensor(sensor::Sensor *permittivity_sensor) {
|
||||||
this->dielectric_constant_sensor_ = dielectric_constant_sensor;
|
this->permittivity_sensor_ = permittivity_sensor;
|
||||||
}
|
}
|
||||||
void set_temperature_sensor(sensor::Sensor *temperature_sensor) { this->temperature_sensor_ = temperature_sensor; }
|
void set_temperature_sensor(sensor::Sensor *temperature_sensor) { this->temperature_sensor_ = temperature_sensor; }
|
||||||
void set_moisture_sensor(sensor::Sensor *moisture_sensor) { this->moisture_sensor_ = moisture_sensor; }
|
void set_moisture_sensor(sensor::Sensor *moisture_sensor) { this->moisture_sensor_ = moisture_sensor; }
|
||||||
@ -31,7 +31,7 @@ class SMT100Component : public PollingComponent, public uart::UARTDevice {
|
|||||||
int readline_(int readch, char *buffer, int len);
|
int readline_(int readch, char *buffer, int len);
|
||||||
|
|
||||||
sensor::Sensor *counts_sensor_{nullptr};
|
sensor::Sensor *counts_sensor_{nullptr};
|
||||||
sensor::Sensor *dielectric_constant_sensor_{nullptr};
|
sensor::Sensor *permittivity_sensor_{nullptr};
|
||||||
sensor::Sensor *moisture_sensor_{nullptr};
|
sensor::Sensor *moisture_sensor_{nullptr};
|
||||||
sensor::Sensor *temperature_sensor_{nullptr};
|
sensor::Sensor *temperature_sensor_{nullptr};
|
||||||
sensor::Sensor *voltage_sensor_{nullptr};
|
sensor::Sensor *voltage_sensor_{nullptr};
|
||||||
|
@ -12,10 +12,8 @@ void ST7701S::setup() {
|
|||||||
|
|
||||||
esp_lcd_rgb_panel_config_t config{};
|
esp_lcd_rgb_panel_config_t config{};
|
||||||
config.flags.fb_in_psram = 1;
|
config.flags.fb_in_psram = 1;
|
||||||
#if ESP_IDF_VERSION_MAJOR >= 5
|
|
||||||
config.bounce_buffer_size_px = this->width_ * 10;
|
config.bounce_buffer_size_px = this->width_ * 10;
|
||||||
config.num_fbs = 1;
|
config.num_fbs = 1;
|
||||||
#endif // ESP_IDF_VERSION_MAJOR
|
|
||||||
config.timings.h_res = this->width_;
|
config.timings.h_res = this->width_;
|
||||||
config.timings.v_res = this->height_;
|
config.timings.v_res = this->height_;
|
||||||
config.timings.hsync_pulse_width = this->hsync_pulse_width_;
|
config.timings.hsync_pulse_width = this->hsync_pulse_width_;
|
||||||
@ -48,10 +46,8 @@ void ST7701S::setup() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ST7701S::loop() {
|
void ST7701S::loop() {
|
||||||
#if ESP_IDF_VERSION_MAJOR >= 5
|
|
||||||
if (this->handle_ != nullptr)
|
if (this->handle_ != nullptr)
|
||||||
esp_lcd_rgb_panel_restart(this->handle_);
|
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,
|
void ST7701S::draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *ptr, display::ColorOrder order,
|
||||||
|
@ -5,6 +5,13 @@ from esphome.config_helpers import Extend, Remove, merge_config
|
|||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.const import CONF_SUBSTITUTIONS, VALID_SUBSTITUTIONS_CHARACTERS
|
from esphome.const import CONF_SUBSTITUTIONS, VALID_SUBSTITUTIONS_CHARACTERS
|
||||||
from esphome.yaml_util import ESPHomeDataBase, make_data_base
|
from esphome.yaml_util import ESPHomeDataBase, make_data_base
|
||||||
|
from .jinja import (
|
||||||
|
Jinja,
|
||||||
|
JinjaStr,
|
||||||
|
has_jinja,
|
||||||
|
TemplateError,
|
||||||
|
TemplateRuntimeError,
|
||||||
|
)
|
||||||
|
|
||||||
CODEOWNERS = ["@esphome/core"]
|
CODEOWNERS = ["@esphome/core"]
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
@ -28,7 +35,7 @@ def validate_substitution_key(value):
|
|||||||
|
|
||||||
CONFIG_SCHEMA = cv.Schema(
|
CONFIG_SCHEMA = cv.Schema(
|
||||||
{
|
{
|
||||||
validate_substitution_key: cv.string_strict,
|
validate_substitution_key: object,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -37,7 +44,42 @@ async def to_code(config):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def _expand_substitutions(substitutions, value, path, ignore_missing):
|
def _expand_jinja(value, orig_value, path, jinja, ignore_missing):
|
||||||
|
if has_jinja(value):
|
||||||
|
# If the original value passed in to this function is a JinjaStr, it means it contains an unresolved
|
||||||
|
# Jinja expression from a previous pass.
|
||||||
|
if isinstance(orig_value, JinjaStr):
|
||||||
|
# Rebuild the JinjaStr in case it was lost while replacing substitutions.
|
||||||
|
value = JinjaStr(value, orig_value.upvalues)
|
||||||
|
try:
|
||||||
|
# Invoke the jinja engine to evaluate the expression.
|
||||||
|
value, err = jinja.expand(value)
|
||||||
|
if err is not None:
|
||||||
|
if not ignore_missing and "password" not in path:
|
||||||
|
_LOGGER.warning(
|
||||||
|
"Found '%s' (see %s) which looks like an expression,"
|
||||||
|
" but could not resolve all the variables: %s",
|
||||||
|
value,
|
||||||
|
"->".join(str(x) for x in path),
|
||||||
|
err.message,
|
||||||
|
)
|
||||||
|
except (
|
||||||
|
TemplateError,
|
||||||
|
TemplateRuntimeError,
|
||||||
|
RuntimeError,
|
||||||
|
ArithmeticError,
|
||||||
|
AttributeError,
|
||||||
|
TypeError,
|
||||||
|
) as err:
|
||||||
|
raise cv.Invalid(
|
||||||
|
f"{type(err).__name__} Error evaluating jinja expression '{value}': {str(err)}."
|
||||||
|
f" See {'->'.join(str(x) for x in path)}",
|
||||||
|
path,
|
||||||
|
)
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
def _expand_substitutions(substitutions, value, path, jinja, ignore_missing):
|
||||||
if "$" not in value:
|
if "$" not in value:
|
||||||
return value
|
return value
|
||||||
|
|
||||||
@ -47,7 +89,8 @@ def _expand_substitutions(substitutions, value, path, ignore_missing):
|
|||||||
while True:
|
while True:
|
||||||
m = cv.VARIABLE_PROG.search(value, i)
|
m = cv.VARIABLE_PROG.search(value, i)
|
||||||
if not m:
|
if not m:
|
||||||
# Nothing more to match. Done
|
# No more variable substitutions found. See if the remainder looks like a jinja template
|
||||||
|
value = _expand_jinja(value, orig_value, path, jinja, ignore_missing)
|
||||||
break
|
break
|
||||||
|
|
||||||
i, j = m.span(0)
|
i, j = m.span(0)
|
||||||
@ -67,8 +110,15 @@ def _expand_substitutions(substitutions, value, path, ignore_missing):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
sub = substitutions[name]
|
sub = substitutions[name]
|
||||||
|
|
||||||
|
if i == 0 and j == len(value):
|
||||||
|
# The variable spans the whole expression, e.g., "${varName}". Return its resolved value directly
|
||||||
|
# to conserve its type.
|
||||||
|
value = sub
|
||||||
|
break
|
||||||
|
|
||||||
tail = value[j:]
|
tail = value[j:]
|
||||||
value = value[:i] + sub
|
value = value[:i] + str(sub)
|
||||||
i = len(value)
|
i = len(value)
|
||||||
value += tail
|
value += tail
|
||||||
|
|
||||||
@ -77,36 +127,40 @@ def _expand_substitutions(substitutions, value, path, ignore_missing):
|
|||||||
if isinstance(orig_value, ESPHomeDataBase):
|
if isinstance(orig_value, ESPHomeDataBase):
|
||||||
# even though string can get larger or smaller, the range should point
|
# even though string can get larger or smaller, the range should point
|
||||||
# to original document marks
|
# to original document marks
|
||||||
return make_data_base(value, orig_value)
|
value = make_data_base(value, orig_value)
|
||||||
|
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
def _substitute_item(substitutions, item, path, ignore_missing):
|
def _substitute_item(substitutions, item, path, jinja, ignore_missing):
|
||||||
if isinstance(item, list):
|
if isinstance(item, list):
|
||||||
for i, it in enumerate(item):
|
for i, it in enumerate(item):
|
||||||
sub = _substitute_item(substitutions, it, path + [i], ignore_missing)
|
sub = _substitute_item(substitutions, it, path + [i], jinja, ignore_missing)
|
||||||
if sub is not None:
|
if sub is not None:
|
||||||
item[i] = sub
|
item[i] = sub
|
||||||
elif isinstance(item, dict):
|
elif isinstance(item, dict):
|
||||||
replace_keys = []
|
replace_keys = []
|
||||||
for k, v in item.items():
|
for k, v in item.items():
|
||||||
if path or k != CONF_SUBSTITUTIONS:
|
if path or k != CONF_SUBSTITUTIONS:
|
||||||
sub = _substitute_item(substitutions, k, path + [k], ignore_missing)
|
sub = _substitute_item(
|
||||||
|
substitutions, k, path + [k], jinja, ignore_missing
|
||||||
|
)
|
||||||
if sub is not None:
|
if sub is not None:
|
||||||
replace_keys.append((k, sub))
|
replace_keys.append((k, sub))
|
||||||
sub = _substitute_item(substitutions, v, path + [k], ignore_missing)
|
sub = _substitute_item(substitutions, v, path + [k], jinja, ignore_missing)
|
||||||
if sub is not None:
|
if sub is not None:
|
||||||
item[k] = sub
|
item[k] = sub
|
||||||
for old, new in replace_keys:
|
for old, new in replace_keys:
|
||||||
item[new] = merge_config(item.get(old), item.get(new))
|
item[new] = merge_config(item.get(old), item.get(new))
|
||||||
del item[old]
|
del item[old]
|
||||||
elif isinstance(item, str):
|
elif isinstance(item, str):
|
||||||
sub = _expand_substitutions(substitutions, item, path, ignore_missing)
|
sub = _expand_substitutions(substitutions, item, path, jinja, ignore_missing)
|
||||||
if sub != item:
|
if isinstance(sub, JinjaStr) or sub != item:
|
||||||
return sub
|
return sub
|
||||||
elif isinstance(item, (core.Lambda, Extend, Remove)):
|
elif isinstance(item, (core.Lambda, Extend, Remove)):
|
||||||
sub = _expand_substitutions(substitutions, item.value, path, ignore_missing)
|
sub = _expand_substitutions(
|
||||||
|
substitutions, item.value, path, jinja, ignore_missing
|
||||||
|
)
|
||||||
if sub != item:
|
if sub != item:
|
||||||
item.value = sub
|
item.value = sub
|
||||||
return None
|
return None
|
||||||
@ -116,11 +170,11 @@ def do_substitution_pass(config, command_line_substitutions, ignore_missing=Fals
|
|||||||
if CONF_SUBSTITUTIONS not in config and not command_line_substitutions:
|
if CONF_SUBSTITUTIONS not in config and not command_line_substitutions:
|
||||||
return
|
return
|
||||||
|
|
||||||
substitutions = config.get(CONF_SUBSTITUTIONS)
|
# Merge substitutions in config, overriding with substitutions coming from command line:
|
||||||
if substitutions is None:
|
substitutions = {
|
||||||
substitutions = command_line_substitutions
|
**config.get(CONF_SUBSTITUTIONS, {}),
|
||||||
elif command_line_substitutions:
|
**(command_line_substitutions or {}),
|
||||||
substitutions = {**substitutions, **command_line_substitutions}
|
}
|
||||||
with cv.prepend_path("substitutions"):
|
with cv.prepend_path("substitutions"):
|
||||||
if not isinstance(substitutions, dict):
|
if not isinstance(substitutions, dict):
|
||||||
raise cv.Invalid(
|
raise cv.Invalid(
|
||||||
@ -133,7 +187,7 @@ def do_substitution_pass(config, command_line_substitutions, ignore_missing=Fals
|
|||||||
sub = validate_substitution_key(key)
|
sub = validate_substitution_key(key)
|
||||||
if sub != key:
|
if sub != key:
|
||||||
replace_keys.append((key, sub))
|
replace_keys.append((key, sub))
|
||||||
substitutions[key] = cv.string_strict(value)
|
substitutions[key] = value
|
||||||
for old, new in replace_keys:
|
for old, new in replace_keys:
|
||||||
substitutions[new] = substitutions[old]
|
substitutions[new] = substitutions[old]
|
||||||
del substitutions[old]
|
del substitutions[old]
|
||||||
@ -141,4 +195,7 @@ def do_substitution_pass(config, command_line_substitutions, ignore_missing=Fals
|
|||||||
config[CONF_SUBSTITUTIONS] = substitutions
|
config[CONF_SUBSTITUTIONS] = substitutions
|
||||||
# Move substitutions to the first place to replace substitutions in them correctly
|
# Move substitutions to the first place to replace substitutions in them correctly
|
||||||
config.move_to_end(CONF_SUBSTITUTIONS, False)
|
config.move_to_end(CONF_SUBSTITUTIONS, False)
|
||||||
_substitute_item(substitutions, config, [], ignore_missing)
|
|
||||||
|
# Create a Jinja environment that will consider substitutions in scope:
|
||||||
|
jinja = Jinja(substitutions)
|
||||||
|
_substitute_item(substitutions, config, [], jinja, ignore_missing)
|
||||||
|
99
esphome/components/substitutions/jinja.py
Normal file
99
esphome/components/substitutions/jinja.py
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
import logging
|
||||||
|
import math
|
||||||
|
import re
|
||||||
|
import jinja2 as jinja
|
||||||
|
from jinja2.nativetypes import NativeEnvironment
|
||||||
|
|
||||||
|
TemplateError = jinja.TemplateError
|
||||||
|
TemplateSyntaxError = jinja.TemplateSyntaxError
|
||||||
|
TemplateRuntimeError = jinja.TemplateRuntimeError
|
||||||
|
UndefinedError = jinja.UndefinedError
|
||||||
|
Undefined = jinja.Undefined
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
DETECT_JINJA = r"(\$\{)"
|
||||||
|
detect_jinja_re = re.compile(
|
||||||
|
r"<%.+?%>" # Block form expression: <% ... %>
|
||||||
|
r"|\$\{[^}]+\}", # Braced form expression: ${ ... }
|
||||||
|
flags=re.MULTILINE,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def has_jinja(st):
|
||||||
|
return detect_jinja_re.search(st) is not None
|
||||||
|
|
||||||
|
|
||||||
|
class JinjaStr(str):
|
||||||
|
"""
|
||||||
|
Wraps a string containing an unresolved Jinja expression,
|
||||||
|
storing the variables visible to it when it failed to resolve.
|
||||||
|
For example, an expression inside a package, `${ A * B }` may fail
|
||||||
|
to resolve at package parsing time if `A` is a local package var
|
||||||
|
but `B` is a substitution defined in the root yaml.
|
||||||
|
Therefore, we store the value of `A` as an upvalue bound
|
||||||
|
to the original string so we may be able to resolve `${ A * B }`
|
||||||
|
later in the main substitutions pass.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __new__(cls, value: str, upvalues=None):
|
||||||
|
obj = super().__new__(cls, value)
|
||||||
|
obj.upvalues = upvalues or {}
|
||||||
|
return obj
|
||||||
|
|
||||||
|
def __init__(self, value: str, upvalues=None):
|
||||||
|
self.upvalues = upvalues or {}
|
||||||
|
|
||||||
|
|
||||||
|
class Jinja:
|
||||||
|
"""
|
||||||
|
Wraps a Jinja environment
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, context_vars):
|
||||||
|
self.env = NativeEnvironment(
|
||||||
|
trim_blocks=True,
|
||||||
|
lstrip_blocks=True,
|
||||||
|
block_start_string="<%",
|
||||||
|
block_end_string="%>",
|
||||||
|
line_statement_prefix="#",
|
||||||
|
line_comment_prefix="##",
|
||||||
|
variable_start_string="${",
|
||||||
|
variable_end_string="}",
|
||||||
|
undefined=jinja.StrictUndefined,
|
||||||
|
)
|
||||||
|
self.env.add_extension("jinja2.ext.do")
|
||||||
|
self.env.globals["math"] = math # Inject entire math module
|
||||||
|
self.context_vars = {**context_vars}
|
||||||
|
self.env.globals = {**self.env.globals, **self.context_vars}
|
||||||
|
|
||||||
|
def expand(self, content_str):
|
||||||
|
"""
|
||||||
|
Renders a string that may contain Jinja expressions or statements
|
||||||
|
Returns the resulting processed string if all values could be resolved.
|
||||||
|
Otherwise, it returns a tagged (JinjaStr) string that captures variables
|
||||||
|
in scope (upvalues), like a closure for later evaluation.
|
||||||
|
"""
|
||||||
|
result = None
|
||||||
|
override_vars = {}
|
||||||
|
if isinstance(content_str, JinjaStr):
|
||||||
|
# If `value` is already a JinjaStr, it means we are trying to evaluate it again
|
||||||
|
# in a parent pass.
|
||||||
|
# Hopefully, all required variables are visible now.
|
||||||
|
override_vars = content_str.upvalues
|
||||||
|
try:
|
||||||
|
template = self.env.from_string(content_str)
|
||||||
|
result = template.render(override_vars)
|
||||||
|
if isinstance(result, Undefined):
|
||||||
|
# This happens when the expression is simply an undefined variable. Jinja does not
|
||||||
|
# raise an exception, instead we get "Undefined".
|
||||||
|
# Trigger an UndefinedError exception so we skip to below.
|
||||||
|
print("" + result)
|
||||||
|
except (TemplateSyntaxError, UndefinedError) as err:
|
||||||
|
# `content_str` contains a Jinja expression that refers to a variable that is undefined
|
||||||
|
# in this scope. Perhaps it refers to a root substitution that is not visible yet.
|
||||||
|
# Therefore, return the original `content_str` as a JinjaStr, which contains the variables
|
||||||
|
# that are actually visible to it at this point to postpone evaluation.
|
||||||
|
return JinjaStr(content_str, {**self.context_vars, **override_vars}), err
|
||||||
|
|
||||||
|
return result, None
|
@ -268,7 +268,19 @@ def validate_tz(value: str) -> str:
|
|||||||
|
|
||||||
TIME_SCHEMA = cv.Schema(
|
TIME_SCHEMA = cv.Schema(
|
||||||
{
|
{
|
||||||
cv.Optional(CONF_TIMEZONE, default=detect_tz): validate_tz,
|
cv.SplitDefault(
|
||||||
|
CONF_TIMEZONE,
|
||||||
|
esp8266=detect_tz,
|
||||||
|
esp32=detect_tz,
|
||||||
|
rp2040=detect_tz,
|
||||||
|
bk72xx=detect_tz,
|
||||||
|
rtl87xx=detect_tz,
|
||||||
|
ln882x=detect_tz,
|
||||||
|
host=detect_tz,
|
||||||
|
): cv.All(
|
||||||
|
cv.only_with_framework(["arduino", "esp-idf", "host"]),
|
||||||
|
validate_tz,
|
||||||
|
),
|
||||||
cv.Optional(CONF_ON_TIME): automation.validate_automation(
|
cv.Optional(CONF_ON_TIME): automation.validate_automation(
|
||||||
{
|
{
|
||||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(CronTrigger),
|
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(CronTrigger),
|
||||||
@ -293,7 +305,9 @@ TIME_SCHEMA = cv.Schema(
|
|||||||
|
|
||||||
|
|
||||||
async def setup_time_core_(time_var, config):
|
async def setup_time_core_(time_var, config):
|
||||||
cg.add(time_var.set_timezone(config[CONF_TIMEZONE]))
|
if timezone := config.get(CONF_TIMEZONE):
|
||||||
|
cg.add(time_var.set_timezone(timezone))
|
||||||
|
cg.add_define("USE_TIME_TIMEZONE")
|
||||||
|
|
||||||
for conf in config.get(CONF_ON_TIME, []):
|
for conf in config.get(CONF_ON_TIME, []):
|
||||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], time_var)
|
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], time_var)
|
||||||
|
@ -35,8 +35,10 @@ void RealTimeClock::synchronize_epoch_(uint32_t epoch) {
|
|||||||
ret = settimeofday(&timev, nullptr);
|
ret = settimeofday(&timev, nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef USE_TIME_TIMEZONE
|
||||||
// Move timezone back to local timezone.
|
// Move timezone back to local timezone.
|
||||||
this->apply_timezone_();
|
this->apply_timezone_();
|
||||||
|
#endif
|
||||||
|
|
||||||
if (ret != 0) {
|
if (ret != 0) {
|
||||||
ESP_LOGW(TAG, "setimeofday() failed with code %d", ret);
|
ESP_LOGW(TAG, "setimeofday() failed with code %d", ret);
|
||||||
@ -49,10 +51,12 @@ void RealTimeClock::synchronize_epoch_(uint32_t epoch) {
|
|||||||
this->time_sync_callback_.call();
|
this->time_sync_callback_.call();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef USE_TIME_TIMEZONE
|
||||||
void RealTimeClock::apply_timezone_() {
|
void RealTimeClock::apply_timezone_() {
|
||||||
setenv("TZ", this->timezone_.c_str(), 1);
|
setenv("TZ", this->timezone_.c_str(), 1);
|
||||||
tzset();
|
tzset();
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
} // namespace time
|
} // namespace time
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
@ -20,6 +20,7 @@ class RealTimeClock : public PollingComponent {
|
|||||||
public:
|
public:
|
||||||
explicit RealTimeClock();
|
explicit RealTimeClock();
|
||||||
|
|
||||||
|
#ifdef USE_TIME_TIMEZONE
|
||||||
/// Set the time zone.
|
/// Set the time zone.
|
||||||
void set_timezone(const std::string &tz) {
|
void set_timezone(const std::string &tz) {
|
||||||
this->timezone_ = tz;
|
this->timezone_ = tz;
|
||||||
@ -28,6 +29,7 @@ class RealTimeClock : public PollingComponent {
|
|||||||
|
|
||||||
/// Get the time zone currently in use.
|
/// Get the time zone currently in use.
|
||||||
std::string get_timezone() { return this->timezone_; }
|
std::string get_timezone() { return this->timezone_; }
|
||||||
|
#endif
|
||||||
|
|
||||||
/// Get the time in the currently defined timezone.
|
/// Get the time in the currently defined timezone.
|
||||||
ESPTime now() { return ESPTime::from_epoch_local(this->timestamp_now()); }
|
ESPTime now() { return ESPTime::from_epoch_local(this->timestamp_now()); }
|
||||||
@ -38,7 +40,7 @@ class RealTimeClock : public PollingComponent {
|
|||||||
/// Get the current time as the UTC epoch since January 1st 1970.
|
/// Get the current time as the UTC epoch since January 1st 1970.
|
||||||
time_t timestamp_now() { return ::time(nullptr); }
|
time_t timestamp_now() { return ::time(nullptr); }
|
||||||
|
|
||||||
void add_on_time_sync_callback(std::function<void()> callback) {
|
void add_on_time_sync_callback(std::function<void()> &&callback) {
|
||||||
this->time_sync_callback_.add(std::move(callback));
|
this->time_sync_callback_.add(std::move(callback));
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -46,8 +48,10 @@ class RealTimeClock : public PollingComponent {
|
|||||||
/// Report a unix epoch as current time.
|
/// Report a unix epoch as current time.
|
||||||
void synchronize_epoch_(uint32_t epoch);
|
void synchronize_epoch_(uint32_t epoch);
|
||||||
|
|
||||||
|
#ifdef USE_TIME_TIMEZONE
|
||||||
std::string timezone_{};
|
std::string timezone_{};
|
||||||
void apply_timezone_();
|
void apply_timezone_();
|
||||||
|
#endif
|
||||||
|
|
||||||
CallbackManager<void()> time_sync_callback_;
|
CallbackManager<void()> time_sync_callback_;
|
||||||
};
|
};
|
||||||
|
@ -48,11 +48,7 @@ uart_config_t IDFUARTComponent::get_config_() {
|
|||||||
uart_config.parity = parity;
|
uart_config.parity = parity;
|
||||||
uart_config.stop_bits = this->stop_bits_ == 1 ? UART_STOP_BITS_1 : UART_STOP_BITS_2;
|
uart_config.stop_bits = this->stop_bits_ == 1 ? UART_STOP_BITS_1 : UART_STOP_BITS_2;
|
||||||
uart_config.flow_ctrl = UART_HW_FLOWCTRL_DISABLE;
|
uart_config.flow_ctrl = UART_HW_FLOWCTRL_DISABLE;
|
||||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
|
|
||||||
uart_config.source_clk = UART_SCLK_DEFAULT;
|
uart_config.source_clk = UART_SCLK_DEFAULT;
|
||||||
#else
|
|
||||||
uart_config.source_clk = UART_SCLK_APB;
|
|
||||||
#endif
|
|
||||||
uart_config.rx_flow_ctrl_thresh = 122;
|
uart_config.rx_flow_ctrl_thresh = 122;
|
||||||
|
|
||||||
return uart_config;
|
return uart_config;
|
||||||
|
@ -38,16 +38,12 @@ WatchdogManager::~WatchdogManager() {
|
|||||||
void WatchdogManager::set_timeout_(uint32_t timeout_ms) {
|
void WatchdogManager::set_timeout_(uint32_t timeout_ms) {
|
||||||
ESP_LOGV(TAG, "Adjusting WDT to %" PRIu32 "ms", timeout_ms);
|
ESP_LOGV(TAG, "Adjusting WDT to %" PRIu32 "ms", timeout_ms);
|
||||||
#ifdef USE_ESP32
|
#ifdef USE_ESP32
|
||||||
#if ESP_IDF_VERSION_MAJOR >= 5
|
|
||||||
esp_task_wdt_config_t wdt_config = {
|
esp_task_wdt_config_t wdt_config = {
|
||||||
.timeout_ms = timeout_ms,
|
.timeout_ms = timeout_ms,
|
||||||
.idle_core_mask = (1 << SOC_CPU_CORES_NUM) - 1,
|
.idle_core_mask = (1 << SOC_CPU_CORES_NUM) - 1,
|
||||||
.trigger_panic = true,
|
.trigger_panic = true,
|
||||||
};
|
};
|
||||||
esp_task_wdt_reconfigure(&wdt_config);
|
esp_task_wdt_reconfigure(&wdt_config);
|
||||||
#else
|
|
||||||
esp_task_wdt_init(timeout_ms / 1000, true);
|
|
||||||
#endif // ESP_IDF_VERSION_MAJOR
|
|
||||||
#endif // USE_ESP32
|
#endif // USE_ESP32
|
||||||
|
|
||||||
#ifdef USE_RP2040
|
#ifdef USE_RP2040
|
||||||
|
@ -33,6 +33,7 @@ from esphome.const import (
|
|||||||
)
|
)
|
||||||
from esphome.core import CORE, coroutine_with_priority
|
from esphome.core import CORE, coroutine_with_priority
|
||||||
import esphome.final_validate as fv
|
import esphome.final_validate as fv
|
||||||
|
from esphome.types import ConfigType
|
||||||
|
|
||||||
AUTO_LOAD = ["json", "web_server_base"]
|
AUTO_LOAD = ["json", "web_server_base"]
|
||||||
|
|
||||||
@ -40,13 +41,14 @@ CONF_SORTING_GROUP_ID = "sorting_group_id"
|
|||||||
CONF_SORTING_GROUPS = "sorting_groups"
|
CONF_SORTING_GROUPS = "sorting_groups"
|
||||||
CONF_SORTING_WEIGHT = "sorting_weight"
|
CONF_SORTING_WEIGHT = "sorting_weight"
|
||||||
|
|
||||||
|
|
||||||
web_server_ns = cg.esphome_ns.namespace("web_server")
|
web_server_ns = cg.esphome_ns.namespace("web_server")
|
||||||
WebServer = web_server_ns.class_("WebServer", cg.Component, cg.Controller)
|
WebServer = web_server_ns.class_("WebServer", cg.Component, cg.Controller)
|
||||||
|
|
||||||
sorting_groups = {}
|
sorting_groups = {}
|
||||||
|
|
||||||
|
|
||||||
def default_url(config):
|
def default_url(config: ConfigType) -> ConfigType:
|
||||||
config = config.copy()
|
config = config.copy()
|
||||||
if config[CONF_VERSION] == 1:
|
if config[CONF_VERSION] == 1:
|
||||||
if CONF_CSS_URL not in config:
|
if CONF_CSS_URL not in config:
|
||||||
@ -66,19 +68,27 @@ def default_url(config):
|
|||||||
return config
|
return config
|
||||||
|
|
||||||
|
|
||||||
def validate_local(config):
|
def validate_local(config: ConfigType) -> ConfigType:
|
||||||
if CONF_LOCAL in config and config[CONF_VERSION] == 1:
|
if CONF_LOCAL in config and config[CONF_VERSION] == 1:
|
||||||
raise cv.Invalid("'local' is not supported in version 1")
|
raise cv.Invalid("'local' is not supported in version 1")
|
||||||
return config
|
return config
|
||||||
|
|
||||||
|
|
||||||
def validate_ota(config):
|
def validate_ota_removed(config: ConfigType) -> ConfigType:
|
||||||
if CORE.using_esp_idf and config[CONF_OTA]:
|
# Only raise error if OTA is explicitly enabled (True)
|
||||||
raise cv.Invalid("Enabling 'ota' is not supported for IDF framework yet")
|
# If it's False or not specified, we can safely ignore it
|
||||||
|
if config.get(CONF_OTA):
|
||||||
|
raise cv.Invalid(
|
||||||
|
f"The '{CONF_OTA}' option has been removed from 'web_server'. "
|
||||||
|
f"Please use the new OTA platform structure instead:\n\n"
|
||||||
|
f"ota:\n"
|
||||||
|
f" - platform: web_server\n\n"
|
||||||
|
f"See https://esphome.io/components/ota for more information."
|
||||||
|
)
|
||||||
return config
|
return config
|
||||||
|
|
||||||
|
|
||||||
def validate_sorting_groups(config):
|
def validate_sorting_groups(config: ConfigType) -> ConfigType:
|
||||||
if CONF_SORTING_GROUPS in config and config[CONF_VERSION] != 3:
|
if CONF_SORTING_GROUPS in config and config[CONF_VERSION] != 3:
|
||||||
raise cv.Invalid(
|
raise cv.Invalid(
|
||||||
f"'{CONF_SORTING_GROUPS}' is only supported in 'web_server' version 3"
|
f"'{CONF_SORTING_GROUPS}' is only supported in 'web_server' version 3"
|
||||||
@ -89,7 +99,7 @@ def validate_sorting_groups(config):
|
|||||||
def _validate_no_sorting_component(
|
def _validate_no_sorting_component(
|
||||||
sorting_component: str,
|
sorting_component: str,
|
||||||
webserver_version: int,
|
webserver_version: int,
|
||||||
config: dict,
|
config: ConfigType,
|
||||||
path: list[str] | None = None,
|
path: list[str] | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
if path is None:
|
if path is None:
|
||||||
@ -112,7 +122,7 @@ def _validate_no_sorting_component(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _final_validate_sorting(config):
|
def _final_validate_sorting(config: ConfigType) -> ConfigType:
|
||||||
if (webserver_version := config.get(CONF_VERSION)) != 3:
|
if (webserver_version := config.get(CONF_VERSION)) != 3:
|
||||||
_validate_no_sorting_component(
|
_validate_no_sorting_component(
|
||||||
CONF_SORTING_WEIGHT, webserver_version, fv.full_config.get()
|
CONF_SORTING_WEIGHT, webserver_version, fv.full_config.get()
|
||||||
@ -175,15 +185,7 @@ CONFIG_SCHEMA = cv.All(
|
|||||||
web_server_base.WebServerBase
|
web_server_base.WebServerBase
|
||||||
),
|
),
|
||||||
cv.Optional(CONF_INCLUDE_INTERNAL, default=False): cv.boolean,
|
cv.Optional(CONF_INCLUDE_INTERNAL, default=False): cv.boolean,
|
||||||
cv.SplitDefault(
|
cv.Optional(CONF_OTA, default=False): cv.boolean,
|
||||||
CONF_OTA,
|
|
||||||
esp8266=True,
|
|
||||||
esp32_arduino=True,
|
|
||||||
esp32_idf=False,
|
|
||||||
bk72xx=True,
|
|
||||||
ln882x=True,
|
|
||||||
rtl87xx=True,
|
|
||||||
): cv.boolean,
|
|
||||||
cv.Optional(CONF_LOG, default=True): cv.boolean,
|
cv.Optional(CONF_LOG, default=True): cv.boolean,
|
||||||
cv.Optional(CONF_LOCAL): cv.boolean,
|
cv.Optional(CONF_LOCAL): cv.boolean,
|
||||||
cv.Optional(CONF_SORTING_GROUPS): cv.ensure_list(sorting_group),
|
cv.Optional(CONF_SORTING_GROUPS): cv.ensure_list(sorting_group),
|
||||||
@ -200,8 +202,8 @@ CONFIG_SCHEMA = cv.All(
|
|||||||
),
|
),
|
||||||
default_url,
|
default_url,
|
||||||
validate_local,
|
validate_local,
|
||||||
validate_ota,
|
|
||||||
validate_sorting_groups,
|
validate_sorting_groups,
|
||||||
|
validate_ota_removed,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -285,7 +287,8 @@ async def to_code(config):
|
|||||||
else:
|
else:
|
||||||
cg.add(var.set_css_url(config[CONF_CSS_URL]))
|
cg.add(var.set_css_url(config[CONF_CSS_URL]))
|
||||||
cg.add(var.set_js_url(config[CONF_JS_URL]))
|
cg.add(var.set_js_url(config[CONF_JS_URL]))
|
||||||
cg.add(var.set_allow_ota(config[CONF_OTA]))
|
# OTA is now handled by the web_server OTA platform
|
||||||
|
# The CONF_OTA option is kept only for backwards compatibility validation
|
||||||
cg.add(var.set_expose_log(config[CONF_LOG]))
|
cg.add(var.set_expose_log(config[CONF_LOG]))
|
||||||
if config[CONF_ENABLE_PRIVATE_NETWORK_ACCESS]:
|
if config[CONF_ENABLE_PRIVATE_NETWORK_ACCESS]:
|
||||||
cg.add_define("USE_WEBSERVER_PRIVATE_NETWORK_ACCESS")
|
cg.add_define("USE_WEBSERVER_PRIVATE_NETWORK_ACCESS")
|
||||||
|
32
esphome/components/web_server/ota/__init__.py
Normal file
32
esphome/components/web_server/ota/__init__.py
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import esphome.codegen as cg
|
||||||
|
from esphome.components.esp32 import add_idf_component
|
||||||
|
from esphome.components.ota import BASE_OTA_SCHEMA, OTAComponent, ota_to_code
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
from esphome.const import CONF_ID
|
||||||
|
from esphome.core import CORE, coroutine_with_priority
|
||||||
|
|
||||||
|
CODEOWNERS = ["@esphome/core"]
|
||||||
|
DEPENDENCIES = ["network", "web_server_base"]
|
||||||
|
|
||||||
|
web_server_ns = cg.esphome_ns.namespace("web_server")
|
||||||
|
WebServerOTAComponent = web_server_ns.class_("WebServerOTAComponent", OTAComponent)
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = (
|
||||||
|
cv.Schema(
|
||||||
|
{
|
||||||
|
cv.GenerateID(): cv.declare_id(WebServerOTAComponent),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.extend(BASE_OTA_SCHEMA)
|
||||||
|
.extend(cv.COMPONENT_SCHEMA)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@coroutine_with_priority(52.0)
|
||||||
|
async def to_code(config):
|
||||||
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
|
await ota_to_code(var, config)
|
||||||
|
await cg.register_component(var, config)
|
||||||
|
cg.add_define("USE_WEBSERVER_OTA")
|
||||||
|
if CORE.using_esp_idf:
|
||||||
|
add_idf_component(name="zorxx/multipart-parser", ref="1.0.1")
|
210
esphome/components/web_server/ota/ota_web_server.cpp
Normal file
210
esphome/components/web_server/ota/ota_web_server.cpp
Normal file
@ -0,0 +1,210 @@
|
|||||||
|
#include "ota_web_server.h"
|
||||||
|
#ifdef USE_WEBSERVER_OTA
|
||||||
|
|
||||||
|
#include "esphome/components/ota/ota_backend.h"
|
||||||
|
#include "esphome/core/application.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
|
#ifdef USE_ARDUINO
|
||||||
|
#ifdef USE_ESP8266
|
||||||
|
#include <Updater.h>
|
||||||
|
#elif defined(USE_ESP32) || defined(USE_LIBRETINY)
|
||||||
|
#include <Update.h>
|
||||||
|
#endif
|
||||||
|
#endif // USE_ARDUINO
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace web_server {
|
||||||
|
|
||||||
|
static const char *const TAG = "web_server.ota";
|
||||||
|
|
||||||
|
class OTARequestHandler : public AsyncWebHandler {
|
||||||
|
public:
|
||||||
|
OTARequestHandler(WebServerOTAComponent *parent) : parent_(parent) {}
|
||||||
|
void handleRequest(AsyncWebServerRequest *request) override;
|
||||||
|
void handleUpload(AsyncWebServerRequest *request, const String &filename, size_t index, uint8_t *data, size_t len,
|
||||||
|
bool final) override;
|
||||||
|
bool canHandle(AsyncWebServerRequest *request) const override {
|
||||||
|
return request->url() == "/update" && request->method() == HTTP_POST;
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOLINTNEXTLINE(readability-identifier-naming)
|
||||||
|
bool isRequestHandlerTrivial() const override { return false; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void report_ota_progress_(AsyncWebServerRequest *request);
|
||||||
|
void schedule_ota_reboot_();
|
||||||
|
void ota_init_(const char *filename);
|
||||||
|
|
||||||
|
uint32_t last_ota_progress_{0};
|
||||||
|
uint32_t ota_read_length_{0};
|
||||||
|
WebServerOTAComponent *parent_;
|
||||||
|
bool ota_success_{false};
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::unique_ptr<ota::OTABackend> ota_backend_{nullptr};
|
||||||
|
};
|
||||||
|
|
||||||
|
void OTARequestHandler::report_ota_progress_(AsyncWebServerRequest *request) {
|
||||||
|
const uint32_t now = millis();
|
||||||
|
if (now - this->last_ota_progress_ > 1000) {
|
||||||
|
float percentage = 0.0f;
|
||||||
|
if (request->contentLength() != 0) {
|
||||||
|
// Note: Using contentLength() for progress calculation is technically wrong as it includes
|
||||||
|
// multipart headers/boundaries, but it's only off by a small amount and we don't have
|
||||||
|
// access to the actual firmware size until the upload is complete. This is intentional
|
||||||
|
// as it still gives the user a reasonable progress indication.
|
||||||
|
percentage = (this->ota_read_length_ * 100.0f) / request->contentLength();
|
||||||
|
ESP_LOGD(TAG, "OTA in progress: %0.1f%%", percentage);
|
||||||
|
} else {
|
||||||
|
ESP_LOGD(TAG, "OTA in progress: %u bytes read", this->ota_read_length_);
|
||||||
|
}
|
||||||
|
#ifdef USE_OTA_STATE_CALLBACK
|
||||||
|
// Report progress - use call_deferred since we're in web server task
|
||||||
|
this->parent_->state_callback_.call_deferred(ota::OTA_IN_PROGRESS, percentage, 0);
|
||||||
|
#endif
|
||||||
|
this->last_ota_progress_ = now;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void OTARequestHandler::schedule_ota_reboot_() {
|
||||||
|
ESP_LOGI(TAG, "OTA update successful!");
|
||||||
|
this->parent_->set_timeout(100, []() {
|
||||||
|
ESP_LOGI(TAG, "Performing OTA reboot now");
|
||||||
|
App.safe_reboot();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void OTARequestHandler::ota_init_(const char *filename) {
|
||||||
|
ESP_LOGI(TAG, "OTA Update Start: %s", filename);
|
||||||
|
this->ota_read_length_ = 0;
|
||||||
|
this->ota_success_ = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OTARequestHandler::handleUpload(AsyncWebServerRequest *request, const String &filename, size_t index,
|
||||||
|
uint8_t *data, size_t len, bool final) {
|
||||||
|
ota::OTAResponseTypes error_code = ota::OTA_RESPONSE_OK;
|
||||||
|
|
||||||
|
if (index == 0 && !this->ota_backend_) {
|
||||||
|
// Initialize OTA on first call
|
||||||
|
this->ota_init_(filename.c_str());
|
||||||
|
|
||||||
|
#ifdef USE_OTA_STATE_CALLBACK
|
||||||
|
// Notify OTA started - use call_deferred since we're in web server task
|
||||||
|
this->parent_->state_callback_.call_deferred(ota::OTA_STARTED, 0.0f, 0);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Platform-specific pre-initialization
|
||||||
|
#ifdef USE_ARDUINO
|
||||||
|
#ifdef USE_ESP8266
|
||||||
|
Update.runAsync(true);
|
||||||
|
#endif
|
||||||
|
#if defined(USE_ESP32) || defined(USE_LIBRETINY)
|
||||||
|
if (Update.isRunning()) {
|
||||||
|
Update.abort();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#endif // USE_ARDUINO
|
||||||
|
|
||||||
|
this->ota_backend_ = ota::make_ota_backend();
|
||||||
|
if (!this->ota_backend_) {
|
||||||
|
ESP_LOGE(TAG, "Failed to create OTA backend");
|
||||||
|
#ifdef USE_OTA_STATE_CALLBACK
|
||||||
|
this->parent_->state_callback_.call_deferred(ota::OTA_ERROR, 0.0f,
|
||||||
|
static_cast<uint8_t>(ota::OTA_RESPONSE_ERROR_UNKNOWN));
|
||||||
|
#endif
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Web server OTA uses multipart uploads where the actual firmware size
|
||||||
|
// is unknown (contentLength includes multipart overhead)
|
||||||
|
// Pass 0 to indicate unknown size
|
||||||
|
error_code = this->ota_backend_->begin(0);
|
||||||
|
if (error_code != ota::OTA_RESPONSE_OK) {
|
||||||
|
ESP_LOGE(TAG, "OTA begin failed: %d", error_code);
|
||||||
|
this->ota_backend_.reset();
|
||||||
|
#ifdef USE_OTA_STATE_CALLBACK
|
||||||
|
this->parent_->state_callback_.call_deferred(ota::OTA_ERROR, 0.0f, static_cast<uint8_t>(error_code));
|
||||||
|
#endif
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this->ota_backend_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process data
|
||||||
|
if (len > 0) {
|
||||||
|
error_code = this->ota_backend_->write(data, len);
|
||||||
|
if (error_code != ota::OTA_RESPONSE_OK) {
|
||||||
|
ESP_LOGE(TAG, "OTA write failed: %d", error_code);
|
||||||
|
this->ota_backend_->abort();
|
||||||
|
this->ota_backend_.reset();
|
||||||
|
#ifdef USE_OTA_STATE_CALLBACK
|
||||||
|
this->parent_->state_callback_.call_deferred(ota::OTA_ERROR, 0.0f, static_cast<uint8_t>(error_code));
|
||||||
|
#endif
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this->ota_read_length_ += len;
|
||||||
|
this->report_ota_progress_(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finalize
|
||||||
|
if (final) {
|
||||||
|
ESP_LOGD(TAG, "OTA final chunk: index=%u, len=%u, total_read=%u, contentLength=%u", index, len,
|
||||||
|
this->ota_read_length_, request->contentLength());
|
||||||
|
|
||||||
|
// For Arduino framework, the Update library tracks expected size from firmware header
|
||||||
|
// If we haven't received enough data, calling end() will fail
|
||||||
|
// This can happen if the upload is interrupted or the client disconnects
|
||||||
|
error_code = this->ota_backend_->end();
|
||||||
|
if (error_code == ota::OTA_RESPONSE_OK) {
|
||||||
|
this->ota_success_ = true;
|
||||||
|
#ifdef USE_OTA_STATE_CALLBACK
|
||||||
|
// Report completion before reboot - use call_deferred since we're in web server task
|
||||||
|
this->parent_->state_callback_.call_deferred(ota::OTA_COMPLETED, 100.0f, 0);
|
||||||
|
#endif
|
||||||
|
this->schedule_ota_reboot_();
|
||||||
|
} else {
|
||||||
|
ESP_LOGE(TAG, "OTA end failed: %d", error_code);
|
||||||
|
#ifdef USE_OTA_STATE_CALLBACK
|
||||||
|
this->parent_->state_callback_.call_deferred(ota::OTA_ERROR, 0.0f, static_cast<uint8_t>(error_code));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
this->ota_backend_.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void OTARequestHandler::handleRequest(AsyncWebServerRequest *request) {
|
||||||
|
AsyncWebServerResponse *response;
|
||||||
|
// Use the ota_success_ flag to determine the actual result
|
||||||
|
const char *msg = this->ota_success_ ? "Update Successful!" : "Update Failed!";
|
||||||
|
response = request->beginResponse(200, "text/plain", msg);
|
||||||
|
response->addHeader("Connection", "close");
|
||||||
|
request->send(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebServerOTAComponent::setup() {
|
||||||
|
// Get the global web server base instance and register our handler
|
||||||
|
auto *base = web_server_base::global_web_server_base;
|
||||||
|
if (base == nullptr) {
|
||||||
|
ESP_LOGE(TAG, "WebServerBase not found");
|
||||||
|
this->mark_failed();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// AsyncWebServer takes ownership of the handler and will delete it when the server is destroyed
|
||||||
|
base->add_handler(new OTARequestHandler(this)); // NOLINT
|
||||||
|
#ifdef USE_OTA_STATE_CALLBACK
|
||||||
|
// Register with global OTA callback system
|
||||||
|
ota::register_ota_platform(this);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebServerOTAComponent::dump_config() { ESP_LOGCONFIG(TAG, "Web Server OTA"); }
|
||||||
|
|
||||||
|
} // namespace web_server
|
||||||
|
} // namespace esphome
|
||||||
|
|
||||||
|
#endif // USE_WEBSERVER_OTA
|
26
esphome/components/web_server/ota/ota_web_server.h
Normal file
26
esphome/components/web_server/ota/ota_web_server.h
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/core/defines.h"
|
||||||
|
#ifdef USE_WEBSERVER_OTA
|
||||||
|
|
||||||
|
#include "esphome/components/ota/ota_backend.h"
|
||||||
|
#include "esphome/components/web_server_base/web_server_base.h"
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace web_server {
|
||||||
|
|
||||||
|
class WebServerOTAComponent : public ota::OTAComponent {
|
||||||
|
public:
|
||||||
|
void setup() override;
|
||||||
|
void dump_config() override;
|
||||||
|
float get_setup_priority() const override { return setup_priority::AFTER_WIFI; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
friend class OTARequestHandler;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace web_server
|
||||||
|
} // namespace esphome
|
||||||
|
|
||||||
|
#endif // USE_WEBSERVER_OTA
|
@ -273,7 +273,11 @@ std::string WebServer::get_config_json() {
|
|||||||
return json::build_json([this](JsonObject root) {
|
return json::build_json([this](JsonObject root) {
|
||||||
root["title"] = App.get_friendly_name().empty() ? App.get_name() : App.get_friendly_name();
|
root["title"] = App.get_friendly_name().empty() ? App.get_name() : App.get_friendly_name();
|
||||||
root["comment"] = App.get_comment();
|
root["comment"] = App.get_comment();
|
||||||
root["ota"] = this->allow_ota_;
|
#ifdef USE_WEBSERVER_OTA
|
||||||
|
root["ota"] = true; // web_server OTA platform is configured
|
||||||
|
#else
|
||||||
|
root["ota"] = false;
|
||||||
|
#endif
|
||||||
root["log"] = this->expose_log_;
|
root["log"] = this->expose_log_;
|
||||||
root["lang"] = "en";
|
root["lang"] = "en";
|
||||||
});
|
});
|
||||||
@ -299,8 +303,7 @@ void WebServer::setup() {
|
|||||||
#endif
|
#endif
|
||||||
this->base_->add_handler(this);
|
this->base_->add_handler(this);
|
||||||
|
|
||||||
if (this->allow_ota_)
|
// OTA is now handled by the web_server OTA platform
|
||||||
this->base_->add_ota_handler();
|
|
||||||
|
|
||||||
// doesn't need defer functionality - if the queue is full, the client JS knows it's alive because it's clearly
|
// doesn't need defer functionality - if the queue is full, the client JS knows it's alive because it's clearly
|
||||||
// getting a lot of events
|
// getting a lot of events
|
||||||
@ -2030,6 +2033,10 @@ void WebServer::handleRequest(AsyncWebServerRequest *request) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// No matching handler found - send 404
|
||||||
|
ESP_LOGV(TAG, "Request for unknown URL: %s", request->url().c_str());
|
||||||
|
request->send(404, "text/plain", "Not Found");
|
||||||
}
|
}
|
||||||
|
|
||||||
bool WebServer::isRequestHandlerTrivial() const { return false; }
|
bool WebServer::isRequestHandlerTrivial() const { return false; }
|
||||||
|
@ -212,11 +212,6 @@ class WebServer : public Controller, public Component, public AsyncWebHandler {
|
|||||||
* @param include_internal Whether internal components should be displayed.
|
* @param include_internal Whether internal components should be displayed.
|
||||||
*/
|
*/
|
||||||
void set_include_internal(bool include_internal) { include_internal_ = include_internal; }
|
void set_include_internal(bool include_internal) { include_internal_ = include_internal; }
|
||||||
/** Set whether or not the webserver should expose the OTA form and handler.
|
|
||||||
*
|
|
||||||
* @param allow_ota.
|
|
||||||
*/
|
|
||||||
void set_allow_ota(bool allow_ota) { this->allow_ota_ = allow_ota; }
|
|
||||||
/** Set whether or not the webserver should expose the Log.
|
/** Set whether or not the webserver should expose the Log.
|
||||||
*
|
*
|
||||||
* @param expose_log.
|
* @param expose_log.
|
||||||
@ -525,7 +520,6 @@ class WebServer : public Controller, public Component, public AsyncWebHandler {
|
|||||||
#ifdef USE_WEBSERVER_JS_INCLUDE
|
#ifdef USE_WEBSERVER_JS_INCLUDE
|
||||||
const char *js_include_{nullptr};
|
const char *js_include_{nullptr};
|
||||||
#endif
|
#endif
|
||||||
bool allow_ota_{true};
|
|
||||||
bool expose_log_{true};
|
bool expose_log_{true};
|
||||||
#ifdef USE_ESP32
|
#ifdef USE_ESP32
|
||||||
std::deque<std::function<void()>> to_schedule_;
|
std::deque<std::function<void()>> to_schedule_;
|
||||||
|
@ -192,11 +192,10 @@ void WebServer::handle_index_request(AsyncWebServerRequest *request) {
|
|||||||
|
|
||||||
stream->print(F("</tbody></table><p>See <a href=\"https://esphome.io/web-api/index.html\">ESPHome Web API</a> for "
|
stream->print(F("</tbody></table><p>See <a href=\"https://esphome.io/web-api/index.html\">ESPHome Web API</a> for "
|
||||||
"REST API documentation.</p>"));
|
"REST API documentation.</p>"));
|
||||||
if (this->allow_ota_) {
|
#ifdef USE_WEBSERVER_OTA
|
||||||
stream->print(
|
stream->print(F("<h2>OTA Update</h2><form method=\"POST\" action=\"/update\" enctype=\"multipart/form-data\"><input "
|
||||||
F("<h2>OTA Update</h2><form method=\"POST\" action=\"/update\" enctype=\"multipart/form-data\"><input "
|
"type=\"file\" name=\"update\"><input type=\"submit\" value=\"Update\"></form>"));
|
||||||
"type=\"file\" name=\"update\"><input type=\"submit\" value=\"Update\"></form>"));
|
#endif
|
||||||
}
|
|
||||||
stream->print(F("<h2>Debug Log</h2><pre id=\"log\"></pre>"));
|
stream->print(F("<h2>Debug Log</h2><pre id=\"log\"></pre>"));
|
||||||
#ifdef USE_WEBSERVER_JS_INCLUDE
|
#ifdef USE_WEBSERVER_JS_INCLUDE
|
||||||
if (this->js_include_ != nullptr) {
|
if (this->js_include_ != nullptr) {
|
||||||
|
@ -30,6 +30,7 @@ CONFIG_SCHEMA = cv.Schema(
|
|||||||
async def to_code(config):
|
async def to_code(config):
|
||||||
var = cg.new_Pvariable(config[CONF_ID])
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
await cg.register_component(var, config)
|
await cg.register_component(var, config)
|
||||||
|
cg.add(cg.RawExpression(f"{web_server_base_ns}::global_web_server_base = {var}"))
|
||||||
|
|
||||||
if CORE.using_arduino:
|
if CORE.using_arduino:
|
||||||
if CORE.is_esp32:
|
if CORE.is_esp32:
|
||||||
|
@ -4,21 +4,13 @@
|
|||||||
#include "esphome/core/helpers.h"
|
#include "esphome/core/helpers.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
#ifdef USE_ARDUINO
|
|
||||||
#include <StreamString.h>
|
|
||||||
#if defined(USE_ESP32) || defined(USE_LIBRETINY)
|
|
||||||
#include <Update.h>
|
|
||||||
#endif
|
|
||||||
#ifdef USE_ESP8266
|
|
||||||
#include <Updater.h>
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace web_server_base {
|
namespace web_server_base {
|
||||||
|
|
||||||
static const char *const TAG = "web_server_base";
|
static const char *const TAG = "web_server_base";
|
||||||
|
|
||||||
|
WebServerBase *global_web_server_base = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||||
|
|
||||||
void WebServerBase::add_handler(AsyncWebHandler *handler) {
|
void WebServerBase::add_handler(AsyncWebHandler *handler) {
|
||||||
// remove all handlers
|
// remove all handlers
|
||||||
|
|
||||||
@ -31,90 +23,6 @@ void WebServerBase::add_handler(AsyncWebHandler *handler) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void report_ota_error() {
|
|
||||||
#ifdef USE_ARDUINO
|
|
||||||
StreamString ss;
|
|
||||||
Update.printError(ss);
|
|
||||||
ESP_LOGW(TAG, "OTA Update failed! Error: %s", ss.c_str());
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
void OTARequestHandler::handleUpload(AsyncWebServerRequest *request, const String &filename, size_t index,
|
|
||||||
uint8_t *data, size_t len, bool final) {
|
|
||||||
#ifdef USE_ARDUINO
|
|
||||||
bool success;
|
|
||||||
if (index == 0) {
|
|
||||||
ESP_LOGI(TAG, "OTA Update Start: %s", filename.c_str());
|
|
||||||
this->ota_read_length_ = 0;
|
|
||||||
#ifdef USE_ESP8266
|
|
||||||
Update.runAsync(true);
|
|
||||||
// NOLINTNEXTLINE(readability-static-accessed-through-instance)
|
|
||||||
success = Update.begin((ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000);
|
|
||||||
#endif
|
|
||||||
#if defined(USE_ESP32_FRAMEWORK_ARDUINO) || defined(USE_LIBRETINY)
|
|
||||||
if (Update.isRunning()) {
|
|
||||||
Update.abort();
|
|
||||||
}
|
|
||||||
success = Update.begin(UPDATE_SIZE_UNKNOWN, U_FLASH);
|
|
||||||
#endif
|
|
||||||
if (!success) {
|
|
||||||
report_ota_error();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else if (Update.hasError()) {
|
|
||||||
// don't spam logs with errors if something failed at start
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
success = Update.write(data, len) == len;
|
|
||||||
if (!success) {
|
|
||||||
report_ota_error();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this->ota_read_length_ += len;
|
|
||||||
|
|
||||||
const uint32_t now = millis();
|
|
||||||
if (now - this->last_ota_progress_ > 1000) {
|
|
||||||
if (request->contentLength() != 0) {
|
|
||||||
float percentage = (this->ota_read_length_ * 100.0f) / request->contentLength();
|
|
||||||
ESP_LOGD(TAG, "OTA in progress: %0.1f%%", percentage);
|
|
||||||
} else {
|
|
||||||
ESP_LOGD(TAG, "OTA in progress: %u bytes read", this->ota_read_length_);
|
|
||||||
}
|
|
||||||
this->last_ota_progress_ = now;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (final) {
|
|
||||||
if (Update.end(true)) {
|
|
||||||
ESP_LOGI(TAG, "OTA update successful!");
|
|
||||||
this->parent_->set_timeout(100, []() { App.safe_reboot(); });
|
|
||||||
} else {
|
|
||||||
report_ota_error();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
void OTARequestHandler::handleRequest(AsyncWebServerRequest *request) {
|
|
||||||
#ifdef USE_ARDUINO
|
|
||||||
AsyncWebServerResponse *response;
|
|
||||||
if (!Update.hasError()) {
|
|
||||||
response = request->beginResponse(200, "text/plain", "Update Successful!");
|
|
||||||
} else {
|
|
||||||
StreamString ss;
|
|
||||||
ss.print("Update Failed: ");
|
|
||||||
Update.printError(ss);
|
|
||||||
response = request->beginResponse(200, "text/plain", ss);
|
|
||||||
}
|
|
||||||
response->addHeader("Connection", "close");
|
|
||||||
request->send(response);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
void WebServerBase::add_ota_handler() {
|
|
||||||
#ifdef USE_ARDUINO
|
|
||||||
this->add_handler(new OTARequestHandler(this)); // NOLINT
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
float WebServerBase::get_setup_priority() const {
|
float WebServerBase::get_setup_priority() const {
|
||||||
// Before WiFi (captive portal)
|
// Before WiFi (captive portal)
|
||||||
return setup_priority::WIFI + 2.0f;
|
return setup_priority::WIFI + 2.0f;
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user