mirror of
https://github.com/esphome/esphome.git
synced 2026-02-10 19:47:35 -07:00
Compare commits
45 Commits
integratio
...
optimize_b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a932ea343d | ||
|
|
bab8d1e8b2 | ||
|
|
e3141211c3 | ||
|
|
e85a022c77 | ||
|
|
1c3af30299 | ||
|
|
5caed68cd9 | ||
|
|
b97a728cf1 | ||
|
|
dcbb020479 | ||
|
|
87ac263264 | ||
|
|
097901e9c8 | ||
|
|
01a90074ba | ||
|
|
57b85a8400 | ||
|
|
2edfcf278f | ||
|
|
bcd4a9fc39 | ||
|
|
78df8be31f | ||
|
|
dacc557a16 | ||
|
|
3767c5ec91 | ||
|
|
7c1327f96a | ||
|
|
475db750e0 | ||
|
|
8f74b027b4 | ||
|
|
b2b9e0cb0a | ||
|
|
dbf202bf0d | ||
|
|
b6fdd29953 | ||
|
|
00256e3ca0 | ||
|
|
e0712cc53b | ||
|
|
6c6da8a3cd | ||
|
|
e4ea016d1e | ||
|
|
41a9588d81 | ||
|
|
cd55eb927d | ||
|
|
4a9ff48f02 | ||
|
|
8fffe7453d | ||
|
|
a5ee451043 | ||
|
|
e176cf50ab | ||
|
|
e7a900fbaa | ||
|
|
623f33c9f9 | ||
|
|
8b24112be5 | ||
|
|
d33f23dc43 | ||
|
|
c43d3889b0 | ||
|
|
50fe8e51f9 | ||
|
|
c7883cb5ae | ||
|
|
3b0df145b7 | ||
|
|
2383b6b8b4 | ||
|
|
c658d7b57f | ||
|
|
04a6238c7b | ||
|
|
919afa1553 |
@@ -1155,9 +1155,11 @@ enum WaterHeaterCommandHasField {
|
|||||||
WATER_HEATER_COMMAND_HAS_NONE = 0;
|
WATER_HEATER_COMMAND_HAS_NONE = 0;
|
||||||
WATER_HEATER_COMMAND_HAS_MODE = 1;
|
WATER_HEATER_COMMAND_HAS_MODE = 1;
|
||||||
WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE = 2;
|
WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE = 2;
|
||||||
WATER_HEATER_COMMAND_HAS_STATE = 4;
|
WATER_HEATER_COMMAND_HAS_STATE = 4 [deprecated=true];
|
||||||
WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE_LOW = 8;
|
WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE_LOW = 8;
|
||||||
WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE_HIGH = 16;
|
WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE_HIGH = 16;
|
||||||
|
WATER_HEATER_COMMAND_HAS_ON_STATE = 32;
|
||||||
|
WATER_HEATER_COMMAND_HAS_AWAY_STATE = 64;
|
||||||
}
|
}
|
||||||
|
|
||||||
message WaterHeaterCommandRequest {
|
message WaterHeaterCommandRequest {
|
||||||
|
|||||||
@@ -1343,8 +1343,12 @@ void APIConnection::on_water_heater_command_request(const WaterHeaterCommandRequ
|
|||||||
call.set_target_temperature_low(msg.target_temperature_low);
|
call.set_target_temperature_low(msg.target_temperature_low);
|
||||||
if (msg.has_fields & enums::WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE_HIGH)
|
if (msg.has_fields & enums::WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE_HIGH)
|
||||||
call.set_target_temperature_high(msg.target_temperature_high);
|
call.set_target_temperature_high(msg.target_temperature_high);
|
||||||
if (msg.has_fields & enums::WATER_HEATER_COMMAND_HAS_STATE) {
|
if ((msg.has_fields & enums::WATER_HEATER_COMMAND_HAS_AWAY_STATE) ||
|
||||||
|
(msg.has_fields & enums::WATER_HEATER_COMMAND_HAS_STATE)) {
|
||||||
call.set_away((msg.state & water_heater::WATER_HEATER_STATE_AWAY) != 0);
|
call.set_away((msg.state & water_heater::WATER_HEATER_STATE_AWAY) != 0);
|
||||||
|
}
|
||||||
|
if ((msg.has_fields & enums::WATER_HEATER_COMMAND_HAS_ON_STATE) ||
|
||||||
|
(msg.has_fields & enums::WATER_HEATER_COMMAND_HAS_STATE)) {
|
||||||
call.set_on((msg.state & water_heater::WATER_HEATER_STATE_ON) != 0);
|
call.set_on((msg.state & water_heater::WATER_HEATER_STATE_ON) != 0);
|
||||||
}
|
}
|
||||||
call.perform();
|
call.perform();
|
||||||
@@ -1895,10 +1899,6 @@ bool APIConnection::schedule_batch_() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void APIConnection::process_batch_() {
|
void APIConnection::process_batch_() {
|
||||||
// Ensure MessageInfo remains trivially destructible for our placement new approach
|
|
||||||
static_assert(std::is_trivially_destructible<MessageInfo>::value,
|
|
||||||
"MessageInfo must remain trivially destructible with this placement-new approach");
|
|
||||||
|
|
||||||
if (this->deferred_batch_.empty()) {
|
if (this->deferred_batch_.empty()) {
|
||||||
this->flags_.batch_scheduled = false;
|
this->flags_.batch_scheduled = false;
|
||||||
return;
|
return;
|
||||||
@@ -1923,6 +1923,10 @@ void APIConnection::process_batch_() {
|
|||||||
for (size_t i = 0; i < num_items; i++) {
|
for (size_t i = 0; i < num_items; i++) {
|
||||||
total_estimated_size += this->deferred_batch_[i].estimated_size;
|
total_estimated_size += this->deferred_batch_[i].estimated_size;
|
||||||
}
|
}
|
||||||
|
// Clamp to MAX_BATCH_PACKET_SIZE — we won't send more than that per batch
|
||||||
|
if (total_estimated_size > MAX_BATCH_PACKET_SIZE) {
|
||||||
|
total_estimated_size = MAX_BATCH_PACKET_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
this->prepare_first_message_buffer(shared_buf, header_padding, total_estimated_size);
|
this->prepare_first_message_buffer(shared_buf, header_padding, total_estimated_size);
|
||||||
|
|
||||||
@@ -1946,7 +1950,20 @@ void APIConnection::process_batch_() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t messages_to_process = std::min(num_items, MAX_MESSAGES_PER_BATCH);
|
// Multi-message path — heavy stack frame isolated in separate noinline function
|
||||||
|
this->process_batch_multi_(shared_buf, num_items, header_padding, footer_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Separated from process_batch_() so the single-message fast path gets a minimal
|
||||||
|
// stack frame without the MAX_MESSAGES_PER_BATCH * sizeof(MessageInfo) array.
|
||||||
|
void APIConnection::process_batch_multi_(std::vector<uint8_t> &shared_buf, size_t num_items, uint8_t header_padding,
|
||||||
|
uint8_t footer_size) {
|
||||||
|
// Ensure MessageInfo remains trivially destructible for our placement new approach
|
||||||
|
static_assert(std::is_trivially_destructible<MessageInfo>::value,
|
||||||
|
"MessageInfo must remain trivially destructible with this placement-new approach");
|
||||||
|
|
||||||
|
const size_t messages_to_process = std::min(num_items, MAX_MESSAGES_PER_BATCH);
|
||||||
|
const uint8_t frame_overhead = header_padding + footer_size;
|
||||||
|
|
||||||
// Stack-allocated array for message info
|
// Stack-allocated array for message info
|
||||||
alignas(MessageInfo) char message_info_storage[MAX_MESSAGES_PER_BATCH * sizeof(MessageInfo)];
|
alignas(MessageInfo) char message_info_storage[MAX_MESSAGES_PER_BATCH * sizeof(MessageInfo)];
|
||||||
@@ -1973,7 +1990,7 @@ void APIConnection::process_batch_() {
|
|||||||
|
|
||||||
// Message was encoded successfully
|
// Message was encoded successfully
|
||||||
// payload_size is header_padding + actual payload size + footer_size
|
// payload_size is header_padding + actual payload size + footer_size
|
||||||
uint16_t proto_payload_size = payload_size - header_padding - footer_size;
|
uint16_t proto_payload_size = payload_size - frame_overhead;
|
||||||
// Use placement new to construct MessageInfo in pre-allocated stack array
|
// Use placement new to construct MessageInfo in pre-allocated stack array
|
||||||
// This avoids default-constructing all MAX_MESSAGES_PER_BATCH elements
|
// This avoids default-constructing all MAX_MESSAGES_PER_BATCH elements
|
||||||
// Explicit destruction is not needed because MessageInfo is trivially destructible,
|
// Explicit destruction is not needed because MessageInfo is trivially destructible,
|
||||||
@@ -1989,42 +2006,38 @@ void APIConnection::process_batch_() {
|
|||||||
current_offset = shared_buf.size() + footer_size;
|
current_offset = shared_buf.size() + footer_size;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (items_processed == 0) {
|
if (items_processed > 0) {
|
||||||
this->deferred_batch_.clear();
|
// Add footer space for the last message (for Noise protocol MAC)
|
||||||
return;
|
if (footer_size > 0) {
|
||||||
}
|
shared_buf.resize(shared_buf.size() + footer_size);
|
||||||
|
}
|
||||||
|
|
||||||
// Add footer space for the last message (for Noise protocol MAC)
|
// Send all collected messages
|
||||||
if (footer_size > 0) {
|
APIError err = this->helper_->write_protobuf_messages(ProtoWriteBuffer{&shared_buf},
|
||||||
shared_buf.resize(shared_buf.size() + footer_size);
|
std::span<const MessageInfo>(message_info, items_processed));
|
||||||
}
|
if (err != APIError::OK && err != APIError::WOULD_BLOCK) {
|
||||||
|
this->fatal_error_with_log_(LOG_STR("Batch write failed"), err);
|
||||||
// Send all collected messages
|
}
|
||||||
APIError err = this->helper_->write_protobuf_messages(ProtoWriteBuffer{&shared_buf},
|
|
||||||
std::span<const MessageInfo>(message_info, items_processed));
|
|
||||||
if (err != APIError::OK && err != APIError::WOULD_BLOCK) {
|
|
||||||
this->fatal_error_with_log_(LOG_STR("Batch write failed"), err);
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
// 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_[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
|
// Partial batch — remove processed items and reschedule
|
||||||
if (items_processed < this->deferred_batch_.size()) {
|
if (items_processed < this->deferred_batch_.size()) {
|
||||||
// Remove processed items from the beginning
|
this->deferred_batch_.remove_front(items_processed);
|
||||||
this->deferred_batch_.remove_front(items_processed);
|
this->schedule_batch_();
|
||||||
// Reschedule for remaining items
|
return;
|
||||||
this->schedule_batch_();
|
}
|
||||||
} else {
|
|
||||||
// All items processed
|
|
||||||
this->clear_batch_();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// All items processed (or none could be processed)
|
||||||
|
this->clear_batch_();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dispatch message encoding based on message_type
|
// Dispatch message encoding based on message_type
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ static constexpr size_t MAX_INITIAL_PER_BATCH = 34; // For clients >= AP
|
|||||||
static_assert(MAX_MESSAGES_PER_BATCH >= MAX_INITIAL_PER_BATCH,
|
static_assert(MAX_MESSAGES_PER_BATCH >= MAX_INITIAL_PER_BATCH,
|
||||||
"MAX_MESSAGES_PER_BATCH must be >= MAX_INITIAL_PER_BATCH");
|
"MAX_MESSAGES_PER_BATCH must be >= MAX_INITIAL_PER_BATCH");
|
||||||
|
|
||||||
class APIConnection final : public APIServerConnection {
|
class APIConnection final : public APIServerConnectionBase {
|
||||||
public:
|
public:
|
||||||
friend class APIServer;
|
friend class APIServer;
|
||||||
friend class ListEntitiesIterator;
|
friend class ListEntitiesIterator;
|
||||||
@@ -549,8 +549,8 @@ class APIConnection final : public APIServerConnection {
|
|||||||
batch_start_time = 0;
|
batch_start_time = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove processed items from the front
|
// Remove processed items from the front — noinline to keep memmove out of warm callers
|
||||||
void remove_front(size_t count) { items.erase(items.begin(), items.begin() + count); }
|
void remove_front(size_t count) __attribute__((noinline)) { 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(); }
|
size_t size() const { return items.size(); }
|
||||||
@@ -622,6 +622,8 @@ class APIConnection final : public APIServerConnection {
|
|||||||
|
|
||||||
bool schedule_batch_();
|
bool schedule_batch_();
|
||||||
void process_batch_();
|
void process_batch_();
|
||||||
|
void process_batch_multi_(std::vector<uint8_t> &shared_buf, size_t num_items, uint8_t header_padding,
|
||||||
|
uint8_t footer_size) __attribute__((noinline));
|
||||||
void clear_batch_() {
|
void clear_batch_() {
|
||||||
this->deferred_batch_.clear();
|
this->deferred_batch_.clear();
|
||||||
this->flags_.batch_scheduled = false;
|
this->flags_.batch_scheduled = false;
|
||||||
|
|||||||
@@ -147,6 +147,8 @@ enum WaterHeaterCommandHasField : uint32_t {
|
|||||||
WATER_HEATER_COMMAND_HAS_STATE = 4,
|
WATER_HEATER_COMMAND_HAS_STATE = 4,
|
||||||
WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE_LOW = 8,
|
WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE_LOW = 8,
|
||||||
WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE_HIGH = 16,
|
WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE_HIGH = 16,
|
||||||
|
WATER_HEATER_COMMAND_HAS_ON_STATE = 32,
|
||||||
|
WATER_HEATER_COMMAND_HAS_AWAY_STATE = 64,
|
||||||
};
|
};
|
||||||
#ifdef USE_NUMBER
|
#ifdef USE_NUMBER
|
||||||
enum NumberMode : uint32_t {
|
enum NumberMode : uint32_t {
|
||||||
@@ -440,19 +442,6 @@ class PingResponse final : public ProtoMessage {
|
|||||||
|
|
||||||
protected:
|
protected:
|
||||||
};
|
};
|
||||||
class DeviceInfoRequest final : public ProtoMessage {
|
|
||||||
public:
|
|
||||||
static constexpr uint8_t MESSAGE_TYPE = 9;
|
|
||||||
static constexpr uint8_t ESTIMATED_SIZE = 0;
|
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
|
||||||
const char *message_name() const override { return "device_info_request"; }
|
|
||||||
#endif
|
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
|
||||||
const char *dump_to(DumpBuffer &out) const override;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
protected:
|
|
||||||
};
|
|
||||||
#ifdef USE_AREAS
|
#ifdef USE_AREAS
|
||||||
class AreaInfo final : public ProtoMessage {
|
class AreaInfo final : public ProtoMessage {
|
||||||
public:
|
public:
|
||||||
@@ -546,19 +535,6 @@ class DeviceInfoResponse final : public ProtoMessage {
|
|||||||
|
|
||||||
protected:
|
protected:
|
||||||
};
|
};
|
||||||
class ListEntitiesRequest final : public ProtoMessage {
|
|
||||||
public:
|
|
||||||
static constexpr uint8_t MESSAGE_TYPE = 11;
|
|
||||||
static constexpr uint8_t ESTIMATED_SIZE = 0;
|
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
|
||||||
const char *message_name() const override { return "list_entities_request"; }
|
|
||||||
#endif
|
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
|
||||||
const char *dump_to(DumpBuffer &out) const override;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
protected:
|
|
||||||
};
|
|
||||||
class ListEntitiesDoneResponse final : public ProtoMessage {
|
class ListEntitiesDoneResponse final : public ProtoMessage {
|
||||||
public:
|
public:
|
||||||
static constexpr uint8_t MESSAGE_TYPE = 19;
|
static constexpr uint8_t MESSAGE_TYPE = 19;
|
||||||
@@ -572,19 +548,6 @@ class ListEntitiesDoneResponse final : public ProtoMessage {
|
|||||||
|
|
||||||
protected:
|
protected:
|
||||||
};
|
};
|
||||||
class SubscribeStatesRequest final : public ProtoMessage {
|
|
||||||
public:
|
|
||||||
static constexpr uint8_t MESSAGE_TYPE = 20;
|
|
||||||
static constexpr uint8_t ESTIMATED_SIZE = 0;
|
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
|
||||||
const char *message_name() const override { return "subscribe_states_request"; }
|
|
||||||
#endif
|
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
|
||||||
const char *dump_to(DumpBuffer &out) const override;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
protected:
|
|
||||||
};
|
|
||||||
#ifdef USE_BINARY_SENSOR
|
#ifdef USE_BINARY_SENSOR
|
||||||
class ListEntitiesBinarySensorResponse final : public InfoResponseProtoMessage {
|
class ListEntitiesBinarySensorResponse final : public InfoResponseProtoMessage {
|
||||||
public:
|
public:
|
||||||
@@ -1037,19 +1000,6 @@ class NoiseEncryptionSetKeyResponse final : public ProtoMessage {
|
|||||||
};
|
};
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_API_HOMEASSISTANT_SERVICES
|
#ifdef USE_API_HOMEASSISTANT_SERVICES
|
||||||
class SubscribeHomeassistantServicesRequest final : public ProtoMessage {
|
|
||||||
public:
|
|
||||||
static constexpr uint8_t MESSAGE_TYPE = 34;
|
|
||||||
static constexpr uint8_t ESTIMATED_SIZE = 0;
|
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
|
||||||
const char *message_name() const override { return "subscribe_homeassistant_services_request"; }
|
|
||||||
#endif
|
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
|
||||||
const char *dump_to(DumpBuffer &out) const override;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
protected:
|
|
||||||
};
|
|
||||||
class HomeassistantServiceMap final : public ProtoMessage {
|
class HomeassistantServiceMap final : public ProtoMessage {
|
||||||
public:
|
public:
|
||||||
StringRef key{};
|
StringRef key{};
|
||||||
@@ -1117,19 +1067,6 @@ class HomeassistantActionResponse final : public ProtoDecodableMessage {
|
|||||||
};
|
};
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_API_HOMEASSISTANT_STATES
|
#ifdef USE_API_HOMEASSISTANT_STATES
|
||||||
class SubscribeHomeAssistantStatesRequest final : public ProtoMessage {
|
|
||||||
public:
|
|
||||||
static constexpr uint8_t MESSAGE_TYPE = 38;
|
|
||||||
static constexpr uint8_t ESTIMATED_SIZE = 0;
|
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
|
||||||
const char *message_name() const override { return "subscribe_home_assistant_states_request"; }
|
|
||||||
#endif
|
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
|
||||||
const char *dump_to(DumpBuffer &out) const override;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
protected:
|
|
||||||
};
|
|
||||||
class SubscribeHomeAssistantStateResponse final : public ProtoMessage {
|
class SubscribeHomeAssistantStateResponse final : public ProtoMessage {
|
||||||
public:
|
public:
|
||||||
static constexpr uint8_t MESSAGE_TYPE = 39;
|
static constexpr uint8_t MESSAGE_TYPE = 39;
|
||||||
@@ -2160,19 +2097,6 @@ class BluetoothGATTNotifyDataResponse final : public ProtoMessage {
|
|||||||
|
|
||||||
protected:
|
protected:
|
||||||
};
|
};
|
||||||
class SubscribeBluetoothConnectionsFreeRequest final : public ProtoMessage {
|
|
||||||
public:
|
|
||||||
static constexpr uint8_t MESSAGE_TYPE = 80;
|
|
||||||
static constexpr uint8_t ESTIMATED_SIZE = 0;
|
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
|
||||||
const char *message_name() const override { return "subscribe_bluetooth_connections_free_request"; }
|
|
||||||
#endif
|
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
|
||||||
const char *dump_to(DumpBuffer &out) const override;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
protected:
|
|
||||||
};
|
|
||||||
class BluetoothConnectionsFreeResponse final : public ProtoMessage {
|
class BluetoothConnectionsFreeResponse final : public ProtoMessage {
|
||||||
public:
|
public:
|
||||||
static constexpr uint8_t MESSAGE_TYPE = 81;
|
static constexpr uint8_t MESSAGE_TYPE = 81;
|
||||||
@@ -2279,19 +2203,6 @@ class BluetoothDeviceUnpairingResponse final : public ProtoMessage {
|
|||||||
|
|
||||||
protected:
|
protected:
|
||||||
};
|
};
|
||||||
class UnsubscribeBluetoothLEAdvertisementsRequest final : public ProtoMessage {
|
|
||||||
public:
|
|
||||||
static constexpr uint8_t MESSAGE_TYPE = 87;
|
|
||||||
static constexpr uint8_t ESTIMATED_SIZE = 0;
|
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
|
||||||
const char *message_name() const override { return "unsubscribe_bluetooth_le_advertisements_request"; }
|
|
||||||
#endif
|
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
|
||||||
const char *dump_to(DumpBuffer &out) const override;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
protected:
|
|
||||||
};
|
|
||||||
class BluetoothDeviceClearCacheResponse final : public ProtoMessage {
|
class BluetoothDeviceClearCacheResponse final : public ProtoMessage {
|
||||||
public:
|
public:
|
||||||
static constexpr uint8_t MESSAGE_TYPE = 88;
|
static constexpr uint8_t MESSAGE_TYPE = 88;
|
||||||
|
|||||||
@@ -385,6 +385,10 @@ const char *proto_enum_to_string<enums::WaterHeaterCommandHasField>(enums::Water
|
|||||||
return "WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE_LOW";
|
return "WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE_LOW";
|
||||||
case enums::WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE_HIGH:
|
case enums::WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE_HIGH:
|
||||||
return "WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE_HIGH";
|
return "WATER_HEATER_COMMAND_HAS_TARGET_TEMPERATURE_HIGH";
|
||||||
|
case enums::WATER_HEATER_COMMAND_HAS_ON_STATE:
|
||||||
|
return "WATER_HEATER_COMMAND_HAS_ON_STATE";
|
||||||
|
case enums::WATER_HEATER_COMMAND_HAS_AWAY_STATE:
|
||||||
|
return "WATER_HEATER_COMMAND_HAS_AWAY_STATE";
|
||||||
default:
|
default:
|
||||||
return "UNKNOWN";
|
return "UNKNOWN";
|
||||||
}
|
}
|
||||||
@@ -764,10 +768,6 @@ const char *PingResponse::dump_to(DumpBuffer &out) const {
|
|||||||
out.append("PingResponse {}");
|
out.append("PingResponse {}");
|
||||||
return out.c_str();
|
return out.c_str();
|
||||||
}
|
}
|
||||||
const char *DeviceInfoRequest::dump_to(DumpBuffer &out) const {
|
|
||||||
out.append("DeviceInfoRequest {}");
|
|
||||||
return out.c_str();
|
|
||||||
}
|
|
||||||
#ifdef USE_AREAS
|
#ifdef USE_AREAS
|
||||||
const char *AreaInfo::dump_to(DumpBuffer &out) const {
|
const char *AreaInfo::dump_to(DumpBuffer &out) const {
|
||||||
MessageDumpHelper helper(out, "AreaInfo");
|
MessageDumpHelper helper(out, "AreaInfo");
|
||||||
@@ -848,18 +848,10 @@ const char *DeviceInfoResponse::dump_to(DumpBuffer &out) const {
|
|||||||
#endif
|
#endif
|
||||||
return out.c_str();
|
return out.c_str();
|
||||||
}
|
}
|
||||||
const char *ListEntitiesRequest::dump_to(DumpBuffer &out) const {
|
|
||||||
out.append("ListEntitiesRequest {}");
|
|
||||||
return out.c_str();
|
|
||||||
}
|
|
||||||
const char *ListEntitiesDoneResponse::dump_to(DumpBuffer &out) const {
|
const char *ListEntitiesDoneResponse::dump_to(DumpBuffer &out) const {
|
||||||
out.append("ListEntitiesDoneResponse {}");
|
out.append("ListEntitiesDoneResponse {}");
|
||||||
return out.c_str();
|
return out.c_str();
|
||||||
}
|
}
|
||||||
const char *SubscribeStatesRequest::dump_to(DumpBuffer &out) const {
|
|
||||||
out.append("SubscribeStatesRequest {}");
|
|
||||||
return out.c_str();
|
|
||||||
}
|
|
||||||
#ifdef USE_BINARY_SENSOR
|
#ifdef USE_BINARY_SENSOR
|
||||||
const char *ListEntitiesBinarySensorResponse::dump_to(DumpBuffer &out) const {
|
const char *ListEntitiesBinarySensorResponse::dump_to(DumpBuffer &out) const {
|
||||||
MessageDumpHelper helper(out, "ListEntitiesBinarySensorResponse");
|
MessageDumpHelper helper(out, "ListEntitiesBinarySensorResponse");
|
||||||
@@ -1191,10 +1183,6 @@ const char *NoiseEncryptionSetKeyResponse::dump_to(DumpBuffer &out) const {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_API_HOMEASSISTANT_SERVICES
|
#ifdef USE_API_HOMEASSISTANT_SERVICES
|
||||||
const char *SubscribeHomeassistantServicesRequest::dump_to(DumpBuffer &out) const {
|
|
||||||
out.append("SubscribeHomeassistantServicesRequest {}");
|
|
||||||
return out.c_str();
|
|
||||||
}
|
|
||||||
const char *HomeassistantServiceMap::dump_to(DumpBuffer &out) const {
|
const char *HomeassistantServiceMap::dump_to(DumpBuffer &out) const {
|
||||||
MessageDumpHelper helper(out, "HomeassistantServiceMap");
|
MessageDumpHelper helper(out, "HomeassistantServiceMap");
|
||||||
dump_field(out, "key", this->key);
|
dump_field(out, "key", this->key);
|
||||||
@@ -1245,10 +1233,6 @@ const char *HomeassistantActionResponse::dump_to(DumpBuffer &out) const {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_API_HOMEASSISTANT_STATES
|
#ifdef USE_API_HOMEASSISTANT_STATES
|
||||||
const char *SubscribeHomeAssistantStatesRequest::dump_to(DumpBuffer &out) const {
|
|
||||||
out.append("SubscribeHomeAssistantStatesRequest {}");
|
|
||||||
return out.c_str();
|
|
||||||
}
|
|
||||||
const char *SubscribeHomeAssistantStateResponse::dump_to(DumpBuffer &out) const {
|
const char *SubscribeHomeAssistantStateResponse::dump_to(DumpBuffer &out) const {
|
||||||
MessageDumpHelper helper(out, "SubscribeHomeAssistantStateResponse");
|
MessageDumpHelper helper(out, "SubscribeHomeAssistantStateResponse");
|
||||||
dump_field(out, "entity_id", this->entity_id);
|
dump_field(out, "entity_id", this->entity_id);
|
||||||
@@ -1924,10 +1908,6 @@ const char *BluetoothGATTNotifyDataResponse::dump_to(DumpBuffer &out) const {
|
|||||||
dump_bytes_field(out, "data", this->data_ptr_, this->data_len_);
|
dump_bytes_field(out, "data", this->data_ptr_, this->data_len_);
|
||||||
return out.c_str();
|
return out.c_str();
|
||||||
}
|
}
|
||||||
const char *SubscribeBluetoothConnectionsFreeRequest::dump_to(DumpBuffer &out) const {
|
|
||||||
out.append("SubscribeBluetoothConnectionsFreeRequest {}");
|
|
||||||
return out.c_str();
|
|
||||||
}
|
|
||||||
const char *BluetoothConnectionsFreeResponse::dump_to(DumpBuffer &out) const {
|
const char *BluetoothConnectionsFreeResponse::dump_to(DumpBuffer &out) const {
|
||||||
MessageDumpHelper helper(out, "BluetoothConnectionsFreeResponse");
|
MessageDumpHelper helper(out, "BluetoothConnectionsFreeResponse");
|
||||||
dump_field(out, "free", this->free);
|
dump_field(out, "free", this->free);
|
||||||
@@ -1970,10 +1950,6 @@ const char *BluetoothDeviceUnpairingResponse::dump_to(DumpBuffer &out) const {
|
|||||||
dump_field(out, "error", this->error);
|
dump_field(out, "error", this->error);
|
||||||
return out.c_str();
|
return out.c_str();
|
||||||
}
|
}
|
||||||
const char *UnsubscribeBluetoothLEAdvertisementsRequest::dump_to(DumpBuffer &out) const {
|
|
||||||
out.append("UnsubscribeBluetoothLEAdvertisementsRequest {}");
|
|
||||||
return out.c_str();
|
|
||||||
}
|
|
||||||
const char *BluetoothDeviceClearCacheResponse::dump_to(DumpBuffer &out) const {
|
const char *BluetoothDeviceClearCacheResponse::dump_to(DumpBuffer &out) const {
|
||||||
MessageDumpHelper helper(out, "BluetoothDeviceClearCacheResponse");
|
MessageDumpHelper helper(out, "BluetoothDeviceClearCacheResponse");
|
||||||
dump_field(out, "address", this->address);
|
dump_field(out, "address", this->address);
|
||||||
|
|||||||
@@ -21,6 +21,23 @@ void APIServerConnectionBase::log_receive_message_(const LogString *name) {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, const uint8_t *msg_data) {
|
void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, const uint8_t *msg_data) {
|
||||||
|
// Check authentication/connection requirements
|
||||||
|
switch (msg_type) {
|
||||||
|
case HelloRequest::MESSAGE_TYPE: // No setup required
|
||||||
|
case DisconnectRequest::MESSAGE_TYPE: // No setup required
|
||||||
|
case PingRequest::MESSAGE_TYPE: // No setup required
|
||||||
|
break;
|
||||||
|
case 9 /* DeviceInfoRequest is empty */: // Connection setup only
|
||||||
|
if (!this->check_connection_setup_()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
if (!this->check_authenticated_()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
switch (msg_type) {
|
switch (msg_type) {
|
||||||
case HelloRequest::MESSAGE_TYPE: {
|
case HelloRequest::MESSAGE_TYPE: {
|
||||||
HelloRequest msg;
|
HelloRequest msg;
|
||||||
@@ -59,21 +76,21 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
|||||||
this->on_ping_response();
|
this->on_ping_response();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case DeviceInfoRequest::MESSAGE_TYPE: {
|
case 9 /* DeviceInfoRequest is empty */: {
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
this->log_receive_message_(LOG_STR("on_device_info_request"));
|
this->log_receive_message_(LOG_STR("on_device_info_request"));
|
||||||
#endif
|
#endif
|
||||||
this->on_device_info_request();
|
this->on_device_info_request();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ListEntitiesRequest::MESSAGE_TYPE: {
|
case 11 /* ListEntitiesRequest is empty */: {
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
this->log_receive_message_(LOG_STR("on_list_entities_request"));
|
this->log_receive_message_(LOG_STR("on_list_entities_request"));
|
||||||
#endif
|
#endif
|
||||||
this->on_list_entities_request();
|
this->on_list_entities_request();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case SubscribeStatesRequest::MESSAGE_TYPE: {
|
case 20 /* SubscribeStatesRequest is empty */: {
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
this->log_receive_message_(LOG_STR("on_subscribe_states_request"));
|
this->log_receive_message_(LOG_STR("on_subscribe_states_request"));
|
||||||
#endif
|
#endif
|
||||||
@@ -134,7 +151,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_API_HOMEASSISTANT_SERVICES
|
#ifdef USE_API_HOMEASSISTANT_SERVICES
|
||||||
case SubscribeHomeassistantServicesRequest::MESSAGE_TYPE: {
|
case 34 /* SubscribeHomeassistantServicesRequest is empty */: {
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
this->log_receive_message_(LOG_STR("on_subscribe_homeassistant_services_request"));
|
this->log_receive_message_(LOG_STR("on_subscribe_homeassistant_services_request"));
|
||||||
#endif
|
#endif
|
||||||
@@ -152,7 +169,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
#ifdef USE_API_HOMEASSISTANT_STATES
|
#ifdef USE_API_HOMEASSISTANT_STATES
|
||||||
case SubscribeHomeAssistantStatesRequest::MESSAGE_TYPE: {
|
case 38 /* SubscribeHomeAssistantStatesRequest is empty */: {
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
this->log_receive_message_(LOG_STR("on_subscribe_home_assistant_states_request"));
|
this->log_receive_message_(LOG_STR("on_subscribe_home_assistant_states_request"));
|
||||||
#endif
|
#endif
|
||||||
@@ -359,7 +376,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_BLUETOOTH_PROXY
|
#ifdef USE_BLUETOOTH_PROXY
|
||||||
case SubscribeBluetoothConnectionsFreeRequest::MESSAGE_TYPE: {
|
case 80 /* SubscribeBluetoothConnectionsFreeRequest is empty */: {
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
this->log_receive_message_(LOG_STR("on_subscribe_bluetooth_connections_free_request"));
|
this->log_receive_message_(LOG_STR("on_subscribe_bluetooth_connections_free_request"));
|
||||||
#endif
|
#endif
|
||||||
@@ -368,7 +385,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_BLUETOOTH_PROXY
|
#ifdef USE_BLUETOOTH_PROXY
|
||||||
case UnsubscribeBluetoothLEAdvertisementsRequest::MESSAGE_TYPE: {
|
case 87 /* UnsubscribeBluetoothLEAdvertisementsRequest is empty */: {
|
||||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||||
this->log_receive_message_(LOG_STR("on_unsubscribe_bluetooth_le_advertisements_request"));
|
this->log_receive_message_(LOG_STR("on_unsubscribe_bluetooth_le_advertisements_request"));
|
||||||
#endif
|
#endif
|
||||||
@@ -623,28 +640,4 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void APIServerConnection::read_message(uint32_t msg_size, uint32_t msg_type, const uint8_t *msg_data) {
|
|
||||||
// Check authentication/connection requirements for messages
|
|
||||||
switch (msg_type) {
|
|
||||||
case HelloRequest::MESSAGE_TYPE: // No setup required
|
|
||||||
case DisconnectRequest::MESSAGE_TYPE: // No setup required
|
|
||||||
case PingRequest::MESSAGE_TYPE: // No setup required
|
|
||||||
break; // Skip all checks for these messages
|
|
||||||
case DeviceInfoRequest::MESSAGE_TYPE: // Connection setup only
|
|
||||||
if (!this->check_connection_setup_()) {
|
|
||||||
return; // Connection not setup
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
// All other messages require authentication (which includes connection check)
|
|
||||||
if (!this->check_authenticated_()) {
|
|
||||||
return; // Authentication failed
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Call base implementation to process the message
|
|
||||||
APIServerConnectionBase::read_message(msg_size, msg_type, msg_data);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace esphome::api
|
} // namespace esphome::api
|
||||||
|
|||||||
@@ -228,9 +228,4 @@ class APIServerConnectionBase : public ProtoService {
|
|||||||
void read_message(uint32_t msg_size, uint32_t msg_type, const uint8_t *msg_data) override;
|
void read_message(uint32_t msg_size, uint32_t msg_type, const uint8_t *msg_data) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
class APIServerConnection : public APIServerConnectionBase {
|
|
||||||
protected:
|
|
||||||
void read_message(uint32_t msg_size, uint32_t msg_type, const uint8_t *msg_data) override;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace esphome::api
|
} // namespace esphome::api
|
||||||
|
|||||||
@@ -25,7 +25,9 @@ template<typename... X> class TemplatableStringValue : public TemplatableValue<s
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
// Helper to convert value to string - handles the case where value is already a string
|
// Helper to convert value to string - handles the case where value is already a string
|
||||||
template<typename T> static std::string value_to_string(T &&val) { return to_string(std::forward<T>(val)); }
|
template<typename T> static std::string value_to_string(T &&val) {
|
||||||
|
return to_string(std::forward<T>(val)); // NOLINT
|
||||||
|
}
|
||||||
|
|
||||||
// Overloads for string types - needed because std::to_string doesn't support them
|
// Overloads for string types - needed because std::to_string doesn't support them
|
||||||
static std::string value_to_string(char *val) {
|
static std::string value_to_string(char *val) {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
#include "abstract_aqi_calculator.h"
|
#include "abstract_aqi_calculator.h"
|
||||||
@@ -14,7 +15,11 @@ class AQICalculator : public AbstractAQICalculator {
|
|||||||
float pm2_5_index = calculate_index(pm2_5_value, PM2_5_GRID);
|
float pm2_5_index = calculate_index(pm2_5_value, PM2_5_GRID);
|
||||||
float pm10_0_index = calculate_index(pm10_0_value, PM10_0_GRID);
|
float pm10_0_index = calculate_index(pm10_0_value, PM10_0_GRID);
|
||||||
|
|
||||||
return static_cast<uint16_t>(std::round((pm2_5_index < pm10_0_index) ? pm10_0_index : pm2_5_index));
|
float aqi = std::max(pm2_5_index, pm10_0_index);
|
||||||
|
if (aqi < 0.0f) {
|
||||||
|
aqi = 0.0f;
|
||||||
|
}
|
||||||
|
return static_cast<uint16_t>(std::lround(aqi));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
@@ -22,13 +27,27 @@ class AQICalculator : public AbstractAQICalculator {
|
|||||||
|
|
||||||
static constexpr int INDEX_GRID[NUM_LEVELS][2] = {{0, 50}, {51, 100}, {101, 150}, {151, 200}, {201, 300}, {301, 500}};
|
static constexpr int INDEX_GRID[NUM_LEVELS][2] = {{0, 50}, {51, 100}, {101, 150}, {151, 200}, {201, 300}, {301, 500}};
|
||||||
|
|
||||||
static constexpr float PM2_5_GRID[NUM_LEVELS][2] = {{0.0f, 9.0f}, {9.1f, 35.4f},
|
static constexpr float PM2_5_GRID[NUM_LEVELS][2] = {
|
||||||
{35.5f, 55.4f}, {55.5f, 125.4f},
|
// clang-format off
|
||||||
{125.5f, 225.4f}, {225.5f, std::numeric_limits<float>::max()}};
|
{0.0f, 9.1f},
|
||||||
|
{9.1f, 35.5f},
|
||||||
|
{35.5f, 55.5f},
|
||||||
|
{55.5f, 125.5f},
|
||||||
|
{125.5f, 225.5f},
|
||||||
|
{225.5f, std::numeric_limits<float>::max()}
|
||||||
|
// clang-format on
|
||||||
|
};
|
||||||
|
|
||||||
static constexpr float PM10_0_GRID[NUM_LEVELS][2] = {{0.0f, 54.0f}, {55.0f, 154.0f},
|
static constexpr float PM10_0_GRID[NUM_LEVELS][2] = {
|
||||||
{155.0f, 254.0f}, {255.0f, 354.0f},
|
// clang-format off
|
||||||
{355.0f, 424.0f}, {425.0f, std::numeric_limits<float>::max()}};
|
{0.0f, 55.0f},
|
||||||
|
{55.0f, 155.0f},
|
||||||
|
{155.0f, 255.0f},
|
||||||
|
{255.0f, 355.0f},
|
||||||
|
{355.0f, 425.0f},
|
||||||
|
{425.0f, std::numeric_limits<float>::max()}
|
||||||
|
// clang-format on
|
||||||
|
};
|
||||||
|
|
||||||
static float calculate_index(float value, const float array[NUM_LEVELS][2]) {
|
static float calculate_index(float value, const float array[NUM_LEVELS][2]) {
|
||||||
int grid_index = get_grid_index(value, array);
|
int grid_index = get_grid_index(value, array);
|
||||||
@@ -45,7 +64,10 @@ class AQICalculator : public AbstractAQICalculator {
|
|||||||
|
|
||||||
static int get_grid_index(float value, const float array[NUM_LEVELS][2]) {
|
static int get_grid_index(float value, const float array[NUM_LEVELS][2]) {
|
||||||
for (int i = 0; i < NUM_LEVELS; i++) {
|
for (int i = 0; i < NUM_LEVELS; i++) {
|
||||||
if (value >= array[i][0] && value <= array[i][1]) {
|
const bool in_range =
|
||||||
|
(value >= array[i][0]) && ((i == NUM_LEVELS - 1) ? (value <= array[i][1]) // last bucket inclusive
|
||||||
|
: (value < array[i][1])); // others exclusive on hi
|
||||||
|
if (in_range) {
|
||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
#include "abstract_aqi_calculator.h"
|
#include "abstract_aqi_calculator.h"
|
||||||
@@ -12,7 +13,11 @@ class CAQICalculator : public AbstractAQICalculator {
|
|||||||
float pm2_5_index = calculate_index(pm2_5_value, PM2_5_GRID);
|
float pm2_5_index = calculate_index(pm2_5_value, PM2_5_GRID);
|
||||||
float pm10_0_index = calculate_index(pm10_0_value, PM10_0_GRID);
|
float pm10_0_index = calculate_index(pm10_0_value, PM10_0_GRID);
|
||||||
|
|
||||||
return static_cast<uint16_t>(std::round((pm2_5_index < pm10_0_index) ? pm10_0_index : pm2_5_index));
|
float aqi = std::max(pm2_5_index, pm10_0_index);
|
||||||
|
if (aqi < 0.0f) {
|
||||||
|
aqi = 0.0f;
|
||||||
|
}
|
||||||
|
return static_cast<uint16_t>(std::lround(aqi));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
@@ -21,10 +26,24 @@ class CAQICalculator : public AbstractAQICalculator {
|
|||||||
static constexpr int INDEX_GRID[NUM_LEVELS][2] = {{0, 25}, {26, 50}, {51, 75}, {76, 100}, {101, 400}};
|
static constexpr int INDEX_GRID[NUM_LEVELS][2] = {{0, 25}, {26, 50}, {51, 75}, {76, 100}, {101, 400}};
|
||||||
|
|
||||||
static constexpr float PM2_5_GRID[NUM_LEVELS][2] = {
|
static constexpr float PM2_5_GRID[NUM_LEVELS][2] = {
|
||||||
{0.0f, 15.0f}, {15.1f, 30.0f}, {30.1f, 55.0f}, {55.1f, 110.0f}, {110.1f, std::numeric_limits<float>::max()}};
|
// clang-format off
|
||||||
|
{0.0f, 15.1f},
|
||||||
|
{15.1f, 30.1f},
|
||||||
|
{30.1f, 55.1f},
|
||||||
|
{55.1f, 110.1f},
|
||||||
|
{110.1f, std::numeric_limits<float>::max()}
|
||||||
|
// clang-format on
|
||||||
|
};
|
||||||
|
|
||||||
static constexpr float PM10_0_GRID[NUM_LEVELS][2] = {
|
static constexpr float PM10_0_GRID[NUM_LEVELS][2] = {
|
||||||
{0.0f, 25.0f}, {25.1f, 50.0f}, {50.1f, 90.0f}, {90.1f, 180.0f}, {180.1f, std::numeric_limits<float>::max()}};
|
// clang-format off
|
||||||
|
{0.0f, 25.1f},
|
||||||
|
{25.1f, 50.1f},
|
||||||
|
{50.1f, 90.1f},
|
||||||
|
{90.1f, 180.1f},
|
||||||
|
{180.1f, std::numeric_limits<float>::max()}
|
||||||
|
// clang-format on
|
||||||
|
};
|
||||||
|
|
||||||
static float calculate_index(float value, const float array[NUM_LEVELS][2]) {
|
static float calculate_index(float value, const float array[NUM_LEVELS][2]) {
|
||||||
int grid_index = get_grid_index(value, array);
|
int grid_index = get_grid_index(value, array);
|
||||||
@@ -42,7 +61,10 @@ class CAQICalculator : public AbstractAQICalculator {
|
|||||||
|
|
||||||
static int get_grid_index(float value, const float array[NUM_LEVELS][2]) {
|
static int get_grid_index(float value, const float array[NUM_LEVELS][2]) {
|
||||||
for (int i = 0; i < NUM_LEVELS; i++) {
|
for (int i = 0; i < NUM_LEVELS; i++) {
|
||||||
if (value >= array[i][0] && value <= array[i][1]) {
|
const bool in_range =
|
||||||
|
(value >= array[i][0]) && ((i == NUM_LEVELS - 1) ? (value <= array[i][1]) // last bucket inclusive
|
||||||
|
: (value < array[i][1])); // others exclusive on hi
|
||||||
|
if (in_range) {
|
||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,14 @@ namespace esphome::binary_sensor {
|
|||||||
|
|
||||||
static const char *const TAG = "binary_sensor.automation";
|
static const char *const TAG = "binary_sensor.automation";
|
||||||
|
|
||||||
|
// MultiClickTrigger timeout IDs.
|
||||||
|
// MultiClickTrigger is its own Component instance, so the scheduler scopes
|
||||||
|
// IDs by component pointer — no risk of collisions between instances.
|
||||||
|
constexpr uint32_t MULTICLICK_TRIGGER_ID = 0;
|
||||||
|
constexpr uint32_t MULTICLICK_COOLDOWN_ID = 1;
|
||||||
|
constexpr uint32_t MULTICLICK_IS_VALID_ID = 2;
|
||||||
|
constexpr uint32_t MULTICLICK_IS_NOT_VALID_ID = 3;
|
||||||
|
|
||||||
void MultiClickTrigger::on_state_(bool state) {
|
void MultiClickTrigger::on_state_(bool state) {
|
||||||
// Handle duplicate events
|
// Handle duplicate events
|
||||||
if (state == this->last_state_) {
|
if (state == this->last_state_) {
|
||||||
@@ -27,7 +35,7 @@ void MultiClickTrigger::on_state_(bool state) {
|
|||||||
evt.min_length, evt.max_length);
|
evt.min_length, evt.max_length);
|
||||||
this->at_index_ = 1;
|
this->at_index_ = 1;
|
||||||
if (this->timing_.size() == 1 && evt.max_length == 4294967294UL) {
|
if (this->timing_.size() == 1 && evt.max_length == 4294967294UL) {
|
||||||
this->set_timeout("trigger", evt.min_length, [this]() { this->trigger_(); });
|
this->set_timeout(MULTICLICK_TRIGGER_ID, evt.min_length, [this]() { this->trigger_(); });
|
||||||
} else {
|
} else {
|
||||||
this->schedule_is_valid_(evt.min_length);
|
this->schedule_is_valid_(evt.min_length);
|
||||||
this->schedule_is_not_valid_(evt.max_length);
|
this->schedule_is_not_valid_(evt.max_length);
|
||||||
@@ -57,13 +65,13 @@ void MultiClickTrigger::on_state_(bool state) {
|
|||||||
this->schedule_is_not_valid_(evt.max_length);
|
this->schedule_is_not_valid_(evt.max_length);
|
||||||
} else if (*this->at_index_ + 1 != this->timing_.size()) {
|
} else if (*this->at_index_ + 1 != this->timing_.size()) {
|
||||||
ESP_LOGV(TAG, "B i=%zu min=%" PRIu32, *this->at_index_, evt.min_length); // NOLINT
|
ESP_LOGV(TAG, "B i=%zu min=%" PRIu32, *this->at_index_, evt.min_length); // NOLINT
|
||||||
this->cancel_timeout("is_not_valid");
|
this->cancel_timeout(MULTICLICK_IS_NOT_VALID_ID);
|
||||||
this->schedule_is_valid_(evt.min_length);
|
this->schedule_is_valid_(evt.min_length);
|
||||||
} else {
|
} else {
|
||||||
ESP_LOGV(TAG, "C i=%zu min=%" PRIu32, *this->at_index_, evt.min_length); // NOLINT
|
ESP_LOGV(TAG, "C i=%zu min=%" PRIu32, *this->at_index_, evt.min_length); // NOLINT
|
||||||
this->is_valid_ = false;
|
this->is_valid_ = false;
|
||||||
this->cancel_timeout("is_not_valid");
|
this->cancel_timeout(MULTICLICK_IS_NOT_VALID_ID);
|
||||||
this->set_timeout("trigger", evt.min_length, [this]() { this->trigger_(); });
|
this->set_timeout(MULTICLICK_TRIGGER_ID, evt.min_length, [this]() { this->trigger_(); });
|
||||||
}
|
}
|
||||||
|
|
||||||
*this->at_index_ = *this->at_index_ + 1;
|
*this->at_index_ = *this->at_index_ + 1;
|
||||||
@@ -71,14 +79,14 @@ void MultiClickTrigger::on_state_(bool state) {
|
|||||||
void MultiClickTrigger::schedule_cooldown_() {
|
void MultiClickTrigger::schedule_cooldown_() {
|
||||||
ESP_LOGV(TAG, "Multi Click: Invalid length of press, starting cooldown of %" PRIu32 " ms", this->invalid_cooldown_);
|
ESP_LOGV(TAG, "Multi Click: Invalid length of press, starting cooldown of %" PRIu32 " ms", this->invalid_cooldown_);
|
||||||
this->is_in_cooldown_ = true;
|
this->is_in_cooldown_ = true;
|
||||||
this->set_timeout("cooldown", this->invalid_cooldown_, [this]() {
|
this->set_timeout(MULTICLICK_COOLDOWN_ID, this->invalid_cooldown_, [this]() {
|
||||||
ESP_LOGV(TAG, "Multi Click: Cooldown ended, matching is now enabled again.");
|
ESP_LOGV(TAG, "Multi Click: Cooldown ended, matching is now enabled again.");
|
||||||
this->is_in_cooldown_ = false;
|
this->is_in_cooldown_ = false;
|
||||||
});
|
});
|
||||||
this->at_index_.reset();
|
this->at_index_.reset();
|
||||||
this->cancel_timeout("trigger");
|
this->cancel_timeout(MULTICLICK_TRIGGER_ID);
|
||||||
this->cancel_timeout("is_valid");
|
this->cancel_timeout(MULTICLICK_IS_VALID_ID);
|
||||||
this->cancel_timeout("is_not_valid");
|
this->cancel_timeout(MULTICLICK_IS_NOT_VALID_ID);
|
||||||
}
|
}
|
||||||
void MultiClickTrigger::schedule_is_valid_(uint32_t min_length) {
|
void MultiClickTrigger::schedule_is_valid_(uint32_t min_length) {
|
||||||
if (min_length == 0) {
|
if (min_length == 0) {
|
||||||
@@ -86,13 +94,13 @@ void MultiClickTrigger::schedule_is_valid_(uint32_t min_length) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this->is_valid_ = false;
|
this->is_valid_ = false;
|
||||||
this->set_timeout("is_valid", min_length, [this]() {
|
this->set_timeout(MULTICLICK_IS_VALID_ID, min_length, [this]() {
|
||||||
ESP_LOGV(TAG, "Multi Click: You can now %s the button.", this->parent_->state ? "RELEASE" : "PRESS");
|
ESP_LOGV(TAG, "Multi Click: You can now %s the button.", this->parent_->state ? "RELEASE" : "PRESS");
|
||||||
this->is_valid_ = true;
|
this->is_valid_ = true;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
void MultiClickTrigger::schedule_is_not_valid_(uint32_t max_length) {
|
void MultiClickTrigger::schedule_is_not_valid_(uint32_t max_length) {
|
||||||
this->set_timeout("is_not_valid", max_length, [this]() {
|
this->set_timeout(MULTICLICK_IS_NOT_VALID_ID, max_length, [this]() {
|
||||||
ESP_LOGV(TAG, "Multi Click: You waited too long to %s.", this->parent_->state ? "RELEASE" : "PRESS");
|
ESP_LOGV(TAG, "Multi Click: You waited too long to %s.", this->parent_->state ? "RELEASE" : "PRESS");
|
||||||
this->is_valid_ = false;
|
this->is_valid_ = false;
|
||||||
this->schedule_cooldown_();
|
this->schedule_cooldown_();
|
||||||
@@ -106,9 +114,9 @@ void MultiClickTrigger::cancel() {
|
|||||||
void MultiClickTrigger::trigger_() {
|
void MultiClickTrigger::trigger_() {
|
||||||
ESP_LOGV(TAG, "Multi Click: Hooray, multi click is valid. Triggering!");
|
ESP_LOGV(TAG, "Multi Click: Hooray, multi click is valid. Triggering!");
|
||||||
this->at_index_.reset();
|
this->at_index_.reset();
|
||||||
this->cancel_timeout("trigger");
|
this->cancel_timeout(MULTICLICK_TRIGGER_ID);
|
||||||
this->cancel_timeout("is_valid");
|
this->cancel_timeout(MULTICLICK_IS_VALID_ID);
|
||||||
this->cancel_timeout("is_not_valid");
|
this->cancel_timeout(MULTICLICK_IS_NOT_VALID_ID);
|
||||||
this->trigger();
|
this->trigger();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,14 @@ namespace esphome::binary_sensor {
|
|||||||
|
|
||||||
static const char *const TAG = "sensor.filter";
|
static const char *const TAG = "sensor.filter";
|
||||||
|
|
||||||
|
// Timeout IDs for filter classes.
|
||||||
|
// Each filter is its own Component instance, so the scheduler scopes
|
||||||
|
// IDs by component pointer — no risk of collisions between instances.
|
||||||
|
constexpr uint32_t FILTER_TIMEOUT_ID = 0;
|
||||||
|
// AutorepeatFilter needs two distinct IDs (both timeouts on the same component)
|
||||||
|
constexpr uint32_t AUTOREPEAT_TIMING_ID = 0;
|
||||||
|
constexpr uint32_t AUTOREPEAT_ON_OFF_ID = 1;
|
||||||
|
|
||||||
void Filter::output(bool value) {
|
void Filter::output(bool value) {
|
||||||
if (this->next_ == nullptr) {
|
if (this->next_ == nullptr) {
|
||||||
this->parent_->send_state_internal(value);
|
this->parent_->send_state_internal(value);
|
||||||
@@ -23,16 +31,16 @@ void Filter::input(bool value) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void TimeoutFilter::input(bool value) {
|
void TimeoutFilter::input(bool value) {
|
||||||
this->set_timeout("timeout", this->timeout_delay_.value(), [this]() { this->parent_->invalidate_state(); });
|
this->set_timeout(FILTER_TIMEOUT_ID, this->timeout_delay_.value(), [this]() { this->parent_->invalidate_state(); });
|
||||||
// we do not de-dup here otherwise changes from invalid to valid state will not be output
|
// we do not de-dup here otherwise changes from invalid to valid state will not be output
|
||||||
this->output(value);
|
this->output(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
optional<bool> DelayedOnOffFilter::new_value(bool value) {
|
optional<bool> DelayedOnOffFilter::new_value(bool value) {
|
||||||
if (value) {
|
if (value) {
|
||||||
this->set_timeout("ON_OFF", this->on_delay_.value(), [this]() { this->output(true); });
|
this->set_timeout(FILTER_TIMEOUT_ID, this->on_delay_.value(), [this]() { this->output(true); });
|
||||||
} else {
|
} else {
|
||||||
this->set_timeout("ON_OFF", this->off_delay_.value(), [this]() { this->output(false); });
|
this->set_timeout(FILTER_TIMEOUT_ID, this->off_delay_.value(), [this]() { this->output(false); });
|
||||||
}
|
}
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
@@ -41,10 +49,10 @@ float DelayedOnOffFilter::get_setup_priority() const { return setup_priority::HA
|
|||||||
|
|
||||||
optional<bool> DelayedOnFilter::new_value(bool value) {
|
optional<bool> DelayedOnFilter::new_value(bool value) {
|
||||||
if (value) {
|
if (value) {
|
||||||
this->set_timeout("ON", this->delay_.value(), [this]() { this->output(true); });
|
this->set_timeout(FILTER_TIMEOUT_ID, this->delay_.value(), [this]() { this->output(true); });
|
||||||
return {};
|
return {};
|
||||||
} else {
|
} else {
|
||||||
this->cancel_timeout("ON");
|
this->cancel_timeout(FILTER_TIMEOUT_ID);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -53,10 +61,10 @@ float DelayedOnFilter::get_setup_priority() const { return setup_priority::HARDW
|
|||||||
|
|
||||||
optional<bool> DelayedOffFilter::new_value(bool value) {
|
optional<bool> DelayedOffFilter::new_value(bool value) {
|
||||||
if (!value) {
|
if (!value) {
|
||||||
this->set_timeout("OFF", this->delay_.value(), [this]() { this->output(false); });
|
this->set_timeout(FILTER_TIMEOUT_ID, this->delay_.value(), [this]() { this->output(false); });
|
||||||
return {};
|
return {};
|
||||||
} else {
|
} else {
|
||||||
this->cancel_timeout("OFF");
|
this->cancel_timeout(FILTER_TIMEOUT_ID);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -76,8 +84,8 @@ optional<bool> AutorepeatFilter::new_value(bool value) {
|
|||||||
this->next_timing_();
|
this->next_timing_();
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
this->cancel_timeout("TIMING");
|
this->cancel_timeout(AUTOREPEAT_TIMING_ID);
|
||||||
this->cancel_timeout("ON_OFF");
|
this->cancel_timeout(AUTOREPEAT_ON_OFF_ID);
|
||||||
this->active_timing_ = 0;
|
this->active_timing_ = 0;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -88,8 +96,10 @@ void AutorepeatFilter::next_timing_() {
|
|||||||
// 1st time: starts waiting the first delay
|
// 1st time: starts waiting the first delay
|
||||||
// 2nd time: starts waiting the second delay and starts toggling with the first time_off / _on
|
// 2nd time: starts waiting the second delay and starts toggling with the first time_off / _on
|
||||||
// last time: no delay to start but have to bump the index to reflect the last
|
// last time: no delay to start but have to bump the index to reflect the last
|
||||||
if (this->active_timing_ < this->timings_.size())
|
if (this->active_timing_ < this->timings_.size()) {
|
||||||
this->set_timeout("TIMING", this->timings_[this->active_timing_].delay, [this]() { this->next_timing_(); });
|
this->set_timeout(AUTOREPEAT_TIMING_ID, this->timings_[this->active_timing_].delay,
|
||||||
|
[this]() { this->next_timing_(); });
|
||||||
|
}
|
||||||
|
|
||||||
if (this->active_timing_ <= this->timings_.size()) {
|
if (this->active_timing_ <= this->timings_.size()) {
|
||||||
this->active_timing_++;
|
this->active_timing_++;
|
||||||
@@ -104,7 +114,8 @@ void AutorepeatFilter::next_timing_() {
|
|||||||
void AutorepeatFilter::next_value_(bool val) {
|
void AutorepeatFilter::next_value_(bool val) {
|
||||||
const AutorepeatFilterTiming &timing = this->timings_[this->active_timing_ - 2];
|
const AutorepeatFilterTiming &timing = this->timings_[this->active_timing_ - 2];
|
||||||
this->output(val); // This is at least the second one so not initial
|
this->output(val); // This is at least the second one so not initial
|
||||||
this->set_timeout("ON_OFF", val ? timing.time_on : timing.time_off, [this, val]() { this->next_value_(!val); });
|
this->set_timeout(AUTOREPEAT_ON_OFF_ID, val ? timing.time_on : timing.time_off,
|
||||||
|
[this, val]() { this->next_value_(!val); });
|
||||||
}
|
}
|
||||||
|
|
||||||
float AutorepeatFilter::get_setup_priority() const { return setup_priority::HARDWARE; }
|
float AutorepeatFilter::get_setup_priority() const { return setup_priority::HARDWARE; }
|
||||||
@@ -115,7 +126,7 @@ optional<bool> LambdaFilter::new_value(bool value) { return this->f_(value); }
|
|||||||
|
|
||||||
optional<bool> SettleFilter::new_value(bool value) {
|
optional<bool> SettleFilter::new_value(bool value) {
|
||||||
if (!this->steady_) {
|
if (!this->steady_) {
|
||||||
this->set_timeout("SETTLE", this->delay_.value(), [this, value]() {
|
this->set_timeout(FILTER_TIMEOUT_ID, this->delay_.value(), [this, value]() {
|
||||||
this->steady_ = true;
|
this->steady_ = true;
|
||||||
this->output(value);
|
this->output(value);
|
||||||
});
|
});
|
||||||
@@ -123,7 +134,7 @@ optional<bool> SettleFilter::new_value(bool value) {
|
|||||||
} else {
|
} else {
|
||||||
this->steady_ = false;
|
this->steady_ = false;
|
||||||
this->output(value);
|
this->output(value);
|
||||||
this->set_timeout("SETTLE", this->delay_.value(), [this]() { this->steady_ = true; });
|
this->set_timeout(FILTER_TIMEOUT_ID, this->delay_.value(), [this]() { this->steady_ = true; });
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,16 +46,16 @@ static const uint32_t PKT_TIMEOUT_MS = 200;
|
|||||||
|
|
||||||
void BL0942::loop() {
|
void BL0942::loop() {
|
||||||
DataPacket buffer;
|
DataPacket buffer;
|
||||||
int avail = this->available();
|
size_t avail = this->available();
|
||||||
|
|
||||||
if (!avail) {
|
if (!avail) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (static_cast<size_t>(avail) < sizeof(buffer)) {
|
if (avail < sizeof(buffer)) {
|
||||||
if (!this->rx_start_) {
|
if (!this->rx_start_) {
|
||||||
this->rx_start_ = millis();
|
this->rx_start_ = millis();
|
||||||
} else if (millis() > this->rx_start_ + PKT_TIMEOUT_MS) {
|
} else if (millis() > this->rx_start_ + PKT_TIMEOUT_MS) {
|
||||||
ESP_LOGW(TAG, "Junk on wire. Throwing away partial message (%d bytes)", avail);
|
ESP_LOGW(TAG, "Junk on wire. Throwing away partial message (%zu bytes)", avail);
|
||||||
this->read_array((uint8_t *) &buffer, avail);
|
this->read_array((uint8_t *) &buffer, avail);
|
||||||
this->rx_start_ = 0;
|
this->rx_start_ = 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ void CSE7766Component::loop() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Early return prevents updating last_transmission_ when no data is available.
|
// Early return prevents updating last_transmission_ when no data is available.
|
||||||
int avail = this->available();
|
size_t avail = this->available();
|
||||||
if (avail <= 0) {
|
if (avail == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -27,7 +27,7 @@ void CSE7766Component::loop() {
|
|||||||
// At 4800 baud (~480 bytes/sec) with ~122 Hz loop rate, typically ~4 bytes per call.
|
// At 4800 baud (~480 bytes/sec) with ~122 Hz loop rate, typically ~4 bytes per call.
|
||||||
uint8_t buf[CSE7766_RAW_DATA_SIZE];
|
uint8_t buf[CSE7766_RAW_DATA_SIZE];
|
||||||
while (avail > 0) {
|
while (avail > 0) {
|
||||||
size_t to_read = std::min(static_cast<size_t>(avail), sizeof(buf));
|
size_t to_read = std::min(avail, sizeof(buf));
|
||||||
if (!this->read_array(buf, to_read)) {
|
if (!this->read_array(buf, to_read)) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -133,10 +133,10 @@ void DFPlayer::send_cmd_(uint8_t cmd, uint16_t argument) {
|
|||||||
|
|
||||||
void DFPlayer::loop() {
|
void DFPlayer::loop() {
|
||||||
// Read all available bytes in batches to reduce UART call overhead.
|
// Read all available bytes in batches to reduce UART call overhead.
|
||||||
int avail = this->available();
|
size_t avail = this->available();
|
||||||
uint8_t buf[64];
|
uint8_t buf[64];
|
||||||
while (avail > 0) {
|
while (avail > 0) {
|
||||||
size_t to_read = std::min(static_cast<size_t>(avail), sizeof(buf));
|
size_t to_read = std::min(avail, sizeof(buf));
|
||||||
if (!this->read_array(buf, to_read)) {
|
if (!this->read_array(buf, to_read)) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ void DlmsMeterComponent::dump_config() {
|
|||||||
|
|
||||||
void DlmsMeterComponent::loop() {
|
void DlmsMeterComponent::loop() {
|
||||||
// Read while data is available, netznoe uses two frames so allow 2x max frame length
|
// Read while data is available, netznoe uses two frames so allow 2x max frame length
|
||||||
int avail = this->available();
|
size_t avail = this->available();
|
||||||
if (avail > 0) {
|
if (avail > 0) {
|
||||||
size_t remaining = MBUS_MAX_FRAME_LENGTH * 2 - this->receive_buffer_.size();
|
size_t remaining = MBUS_MAX_FRAME_LENGTH * 2 - this->receive_buffer_.size();
|
||||||
if (remaining == 0) {
|
if (remaining == 0) {
|
||||||
@@ -36,12 +36,12 @@ void DlmsMeterComponent::loop() {
|
|||||||
} else {
|
} else {
|
||||||
// Read all available bytes in batches to reduce UART call overhead.
|
// Read all available bytes in batches to reduce UART call overhead.
|
||||||
// Cap reads to remaining buffer capacity.
|
// Cap reads to remaining buffer capacity.
|
||||||
if (static_cast<size_t>(avail) > remaining) {
|
if (avail > remaining) {
|
||||||
avail = remaining;
|
avail = remaining;
|
||||||
}
|
}
|
||||||
uint8_t buf[64];
|
uint8_t buf[64];
|
||||||
while (avail > 0) {
|
while (avail > 0) {
|
||||||
size_t to_read = std::min(static_cast<size_t>(avail), sizeof(buf));
|
size_t to_read = std::min(avail, sizeof(buf));
|
||||||
if (!this->read_array(buf, to_read)) {
|
if (!this->read_array(buf, to_read)) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -120,9 +120,9 @@ void Dsmr::stop_requesting_data_() {
|
|||||||
|
|
||||||
void Dsmr::drain_rx_buffer_() {
|
void Dsmr::drain_rx_buffer_() {
|
||||||
uint8_t buf[64];
|
uint8_t buf[64];
|
||||||
int avail;
|
size_t avail;
|
||||||
while ((avail = this->available()) > 0) {
|
while ((avail = this->available()) > 0) {
|
||||||
if (!this->read_array(buf, std::min(static_cast<size_t>(avail), sizeof(buf)))) {
|
if (!this->read_array(buf, std::min(avail, sizeof(buf)))) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -134,16 +134,15 @@ void Dsmr::reset_telegram_() {
|
|||||||
this->bytes_read_ = 0;
|
this->bytes_read_ = 0;
|
||||||
this->crypt_bytes_read_ = 0;
|
this->crypt_bytes_read_ = 0;
|
||||||
this->crypt_telegram_len_ = 0;
|
this->crypt_telegram_len_ = 0;
|
||||||
this->last_read_time_ = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Dsmr::receive_telegram_() {
|
void Dsmr::receive_telegram_() {
|
||||||
while (this->available_within_timeout_()) {
|
while (this->available_within_timeout_()) {
|
||||||
// Read all available bytes in batches to reduce UART call overhead.
|
// Read all available bytes in batches to reduce UART call overhead.
|
||||||
uint8_t buf[64];
|
uint8_t buf[64];
|
||||||
int avail = this->available();
|
size_t avail = this->available();
|
||||||
while (avail > 0) {
|
while (avail > 0) {
|
||||||
size_t to_read = std::min(static_cast<size_t>(avail), sizeof(buf));
|
size_t to_read = std::min(avail, sizeof(buf));
|
||||||
if (!this->read_array(buf, to_read))
|
if (!this->read_array(buf, to_read))
|
||||||
return;
|
return;
|
||||||
avail -= to_read;
|
avail -= to_read;
|
||||||
@@ -207,9 +206,9 @@ void Dsmr::receive_encrypted_telegram_() {
|
|||||||
while (this->available_within_timeout_()) {
|
while (this->available_within_timeout_()) {
|
||||||
// Read all available bytes in batches to reduce UART call overhead.
|
// Read all available bytes in batches to reduce UART call overhead.
|
||||||
uint8_t buf[64];
|
uint8_t buf[64];
|
||||||
int avail = this->available();
|
size_t avail = this->available();
|
||||||
while (avail > 0) {
|
while (avail > 0) {
|
||||||
size_t to_read = std::min(static_cast<size_t>(avail), sizeof(buf));
|
size_t to_read = std::min(avail, sizeof(buf));
|
||||||
if (!this->read_array(buf, to_read))
|
if (!this->read_array(buf, to_read))
|
||||||
return;
|
return;
|
||||||
avail -= to_read;
|
avail -= to_read;
|
||||||
|
|||||||
@@ -1435,6 +1435,10 @@ async def to_code(config):
|
|||||||
CORE.relative_internal_path(".espressif")
|
CORE.relative_internal_path(".espressif")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Set the uv cache inside the data dir so "Clean All" clears it.
|
||||||
|
# Avoids persistent corrupted cache from mid-stream download failures.
|
||||||
|
os.environ["UV_CACHE_DIR"] = str(CORE.relative_internal_path(".uv_cache"))
|
||||||
|
|
||||||
if conf[CONF_TYPE] == FRAMEWORK_ESP_IDF:
|
if conf[CONF_TYPE] == FRAMEWORK_ESP_IDF:
|
||||||
cg.add_build_flag("-DUSE_ESP_IDF")
|
cg.add_build_flag("-DUSE_ESP_IDF")
|
||||||
cg.add_build_flag("-DUSE_ESP32_FRAMEWORK_ESP_IDF")
|
cg.add_build_flag("-DUSE_ESP32_FRAMEWORK_ESP_IDF")
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ class ESPBTUUID {
|
|||||||
|
|
||||||
// Remove before 2026.8.0
|
// Remove before 2026.8.0
|
||||||
ESPDEPRECATED("Use to_str() instead. Removed in 2026.8.0", "2026.2.0")
|
ESPDEPRECATED("Use to_str() instead. Removed in 2026.8.0", "2026.2.0")
|
||||||
std::string to_string() const;
|
std::string to_string() const; // NOLINT
|
||||||
const char *to_str(std::span<char, UUID_STR_LEN> output) const;
|
const char *to_str(std::span<char, UUID_STR_LEN> output) const;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|||||||
@@ -1,20 +1,16 @@
|
|||||||
#include "hlk_fm22x.h"
|
#include "hlk_fm22x.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
#include "esphome/core/helpers.h"
|
#include "esphome/core/helpers.h"
|
||||||
#include <array>
|
|
||||||
#include <cinttypes>
|
#include <cinttypes>
|
||||||
|
|
||||||
namespace esphome::hlk_fm22x {
|
namespace esphome::hlk_fm22x {
|
||||||
|
|
||||||
static const char *const TAG = "hlk_fm22x";
|
static const char *const TAG = "hlk_fm22x";
|
||||||
|
|
||||||
// Maximum response size is 36 bytes (VERIFY reply: face_id + 32-byte name)
|
|
||||||
static constexpr size_t HLK_FM22X_MAX_RESPONSE_SIZE = 36;
|
|
||||||
|
|
||||||
void HlkFm22xComponent::setup() {
|
void HlkFm22xComponent::setup() {
|
||||||
ESP_LOGCONFIG(TAG, "Setting up HLK-FM22X...");
|
ESP_LOGCONFIG(TAG, "Setting up HLK-FM22X...");
|
||||||
this->set_enrolling_(false);
|
this->set_enrolling_(false);
|
||||||
while (this->available()) {
|
while (this->available() > 0) {
|
||||||
this->read();
|
this->read();
|
||||||
}
|
}
|
||||||
this->defer([this]() { this->send_command_(HlkFm22xCommand::GET_STATUS); });
|
this->defer([this]() { this->send_command_(HlkFm22xCommand::GET_STATUS); });
|
||||||
@@ -35,7 +31,7 @@ void HlkFm22xComponent::update() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void HlkFm22xComponent::enroll_face(const std::string &name, HlkFm22xFaceDirection direction) {
|
void HlkFm22xComponent::enroll_face(const std::string &name, HlkFm22xFaceDirection direction) {
|
||||||
if (name.length() > 31) {
|
if (name.length() > HLK_FM22X_NAME_SIZE - 1) {
|
||||||
ESP_LOGE(TAG, "enroll_face(): name too long '%s'", name.c_str());
|
ESP_LOGE(TAG, "enroll_face(): name too long '%s'", name.c_str());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -88,7 +84,7 @@ void HlkFm22xComponent::send_command_(HlkFm22xCommand command, const uint8_t *da
|
|||||||
}
|
}
|
||||||
this->wait_cycles_ = 0;
|
this->wait_cycles_ = 0;
|
||||||
this->active_command_ = command;
|
this->active_command_ = command;
|
||||||
while (this->available())
|
while (this->available() > 0)
|
||||||
this->read();
|
this->read();
|
||||||
this->write((uint8_t) (START_CODE >> 8));
|
this->write((uint8_t) (START_CODE >> 8));
|
||||||
this->write((uint8_t) (START_CODE & 0xFF));
|
this->write((uint8_t) (START_CODE & 0xFF));
|
||||||
@@ -137,17 +133,24 @@ void HlkFm22xComponent::recv_command_() {
|
|||||||
checksum ^= byte;
|
checksum ^= byte;
|
||||||
length |= byte;
|
length |= byte;
|
||||||
|
|
||||||
std::vector<uint8_t> data;
|
if (length > HLK_FM22X_MAX_RESPONSE_SIZE) {
|
||||||
data.reserve(length);
|
ESP_LOGE(TAG, "Response too large: %u bytes", length);
|
||||||
|
// Discard exactly the remaining payload and checksum for this frame
|
||||||
|
for (uint16_t i = 0; i < length + 1 && this->available() > 0; ++i)
|
||||||
|
this->read();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
for (uint16_t idx = 0; idx < length; ++idx) {
|
for (uint16_t idx = 0; idx < length; ++idx) {
|
||||||
byte = this->read();
|
byte = this->read();
|
||||||
checksum ^= byte;
|
checksum ^= byte;
|
||||||
data.push_back(byte);
|
this->recv_buf_[idx] = byte;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
|
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
|
||||||
char hex_buf[format_hex_pretty_size(HLK_FM22X_MAX_RESPONSE_SIZE)];
|
char hex_buf[format_hex_pretty_size(HLK_FM22X_MAX_RESPONSE_SIZE)];
|
||||||
ESP_LOGV(TAG, "Recv type: 0x%.2X, data: %s", response_type, format_hex_pretty_to(hex_buf, data.data(), data.size()));
|
ESP_LOGV(TAG, "Recv type: 0x%.2X, data: %s", response_type,
|
||||||
|
format_hex_pretty_to(hex_buf, this->recv_buf_.data(), length));
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
byte = this->read();
|
byte = this->read();
|
||||||
@@ -157,10 +160,10 @@ void HlkFm22xComponent::recv_command_() {
|
|||||||
}
|
}
|
||||||
switch (response_type) {
|
switch (response_type) {
|
||||||
case HlkFm22xResponseType::NOTE:
|
case HlkFm22xResponseType::NOTE:
|
||||||
this->handle_note_(data);
|
this->handle_note_(this->recv_buf_.data(), length);
|
||||||
break;
|
break;
|
||||||
case HlkFm22xResponseType::REPLY:
|
case HlkFm22xResponseType::REPLY:
|
||||||
this->handle_reply_(data);
|
this->handle_reply_(this->recv_buf_.data(), length);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
ESP_LOGW(TAG, "Unexpected response type: 0x%.2X", response_type);
|
ESP_LOGW(TAG, "Unexpected response type: 0x%.2X", response_type);
|
||||||
@@ -168,11 +171,15 @@ void HlkFm22xComponent::recv_command_() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void HlkFm22xComponent::handle_note_(const std::vector<uint8_t> &data) {
|
void HlkFm22xComponent::handle_note_(const uint8_t *data, size_t length) {
|
||||||
|
if (length < 1) {
|
||||||
|
ESP_LOGE(TAG, "Empty note data");
|
||||||
|
return;
|
||||||
|
}
|
||||||
switch (data[0]) {
|
switch (data[0]) {
|
||||||
case HlkFm22xNoteType::FACE_STATE:
|
case HlkFm22xNoteType::FACE_STATE:
|
||||||
if (data.size() < 17) {
|
if (length < 17) {
|
||||||
ESP_LOGE(TAG, "Invalid face note data size: %u", data.size());
|
ESP_LOGE(TAG, "Invalid face note data size: %zu", length);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
@@ -209,9 +216,13 @@ void HlkFm22xComponent::handle_note_(const std::vector<uint8_t> &data) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void HlkFm22xComponent::handle_reply_(const std::vector<uint8_t> &data) {
|
void HlkFm22xComponent::handle_reply_(const uint8_t *data, size_t length) {
|
||||||
auto expected = this->active_command_;
|
auto expected = this->active_command_;
|
||||||
this->active_command_ = HlkFm22xCommand::NONE;
|
this->active_command_ = HlkFm22xCommand::NONE;
|
||||||
|
if (length < 2) {
|
||||||
|
ESP_LOGE(TAG, "Reply too short: %zu bytes", length);
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (data[0] != (uint8_t) expected) {
|
if (data[0] != (uint8_t) expected) {
|
||||||
ESP_LOGE(TAG, "Unexpected response command. Expected: 0x%.2X, Received: 0x%.2X", expected, data[0]);
|
ESP_LOGE(TAG, "Unexpected response command. Expected: 0x%.2X, Received: 0x%.2X", expected, data[0]);
|
||||||
return;
|
return;
|
||||||
@@ -238,16 +249,20 @@ void HlkFm22xComponent::handle_reply_(const std::vector<uint8_t> &data) {
|
|||||||
}
|
}
|
||||||
switch (expected) {
|
switch (expected) {
|
||||||
case HlkFm22xCommand::VERIFY: {
|
case HlkFm22xCommand::VERIFY: {
|
||||||
|
if (length < 4 + HLK_FM22X_NAME_SIZE) {
|
||||||
|
ESP_LOGE(TAG, "VERIFY response too short: %zu bytes", length);
|
||||||
|
break;
|
||||||
|
}
|
||||||
int16_t face_id = ((int16_t) data[2] << 8) | data[3];
|
int16_t face_id = ((int16_t) data[2] << 8) | data[3];
|
||||||
std::string name(data.begin() + 4, data.begin() + 36);
|
const char *name_ptr = reinterpret_cast<const char *>(data + 4);
|
||||||
ESP_LOGD(TAG, "Face verified. ID: %d, name: %s", face_id, name.c_str());
|
ESP_LOGD(TAG, "Face verified. ID: %d, name: %.*s", face_id, (int) HLK_FM22X_NAME_SIZE, name_ptr);
|
||||||
if (this->last_face_id_sensor_ != nullptr) {
|
if (this->last_face_id_sensor_ != nullptr) {
|
||||||
this->last_face_id_sensor_->publish_state(face_id);
|
this->last_face_id_sensor_->publish_state(face_id);
|
||||||
}
|
}
|
||||||
if (this->last_face_name_text_sensor_ != nullptr) {
|
if (this->last_face_name_text_sensor_ != nullptr) {
|
||||||
this->last_face_name_text_sensor_->publish_state(name);
|
this->last_face_name_text_sensor_->publish_state(name_ptr, HLK_FM22X_NAME_SIZE);
|
||||||
}
|
}
|
||||||
this->face_scan_matched_callback_.call(face_id, name);
|
this->face_scan_matched_callback_.call(face_id, std::string(name_ptr, HLK_FM22X_NAME_SIZE));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case HlkFm22xCommand::ENROLL: {
|
case HlkFm22xCommand::ENROLL: {
|
||||||
@@ -266,9 +281,8 @@ void HlkFm22xComponent::handle_reply_(const std::vector<uint8_t> &data) {
|
|||||||
this->defer([this]() { this->send_command_(HlkFm22xCommand::GET_VERSION); });
|
this->defer([this]() { this->send_command_(HlkFm22xCommand::GET_VERSION); });
|
||||||
break;
|
break;
|
||||||
case HlkFm22xCommand::GET_VERSION:
|
case HlkFm22xCommand::GET_VERSION:
|
||||||
if (this->version_text_sensor_ != nullptr) {
|
if (this->version_text_sensor_ != nullptr && length > 2) {
|
||||||
std::string version(data.begin() + 2, data.end());
|
this->version_text_sensor_->publish_state(reinterpret_cast<const char *>(data + 2), length - 2);
|
||||||
this->version_text_sensor_->publish_state(version);
|
|
||||||
}
|
}
|
||||||
this->defer([this]() { this->get_face_count_(); });
|
this->defer([this]() { this->get_face_count_(); });
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -7,12 +7,15 @@
|
|||||||
#include "esphome/components/text_sensor/text_sensor.h"
|
#include "esphome/components/text_sensor/text_sensor.h"
|
||||||
#include "esphome/components/uart/uart.h"
|
#include "esphome/components/uart/uart.h"
|
||||||
|
|
||||||
|
#include <array>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace esphome::hlk_fm22x {
|
namespace esphome::hlk_fm22x {
|
||||||
|
|
||||||
static const uint16_t START_CODE = 0xEFAA;
|
static const uint16_t START_CODE = 0xEFAA;
|
||||||
|
static constexpr size_t HLK_FM22X_NAME_SIZE = 32;
|
||||||
|
// Maximum response payload: command(1) + result(1) + face_id(2) + name(32) = 36
|
||||||
|
static constexpr size_t HLK_FM22X_MAX_RESPONSE_SIZE = 36;
|
||||||
enum HlkFm22xCommand {
|
enum HlkFm22xCommand {
|
||||||
NONE = 0x00,
|
NONE = 0x00,
|
||||||
RESET = 0x10,
|
RESET = 0x10,
|
||||||
@@ -118,10 +121,11 @@ class HlkFm22xComponent : public PollingComponent, public uart::UARTDevice {
|
|||||||
void get_face_count_();
|
void get_face_count_();
|
||||||
void send_command_(HlkFm22xCommand command, const uint8_t *data = nullptr, size_t size = 0);
|
void send_command_(HlkFm22xCommand command, const uint8_t *data = nullptr, size_t size = 0);
|
||||||
void recv_command_();
|
void recv_command_();
|
||||||
void handle_note_(const std::vector<uint8_t> &data);
|
void handle_note_(const uint8_t *data, size_t length);
|
||||||
void handle_reply_(const std::vector<uint8_t> &data);
|
void handle_reply_(const uint8_t *data, size_t length);
|
||||||
void set_enrolling_(bool enrolling);
|
void set_enrolling_(bool enrolling);
|
||||||
|
|
||||||
|
std::array<uint8_t, HLK_FM22X_MAX_RESPONSE_SIZE> recv_buf_;
|
||||||
HlkFm22xCommand active_command_ = HlkFm22xCommand::NONE;
|
HlkFm22xCommand active_command_ = HlkFm22xCommand::NONE;
|
||||||
uint16_t wait_cycles_ = 0;
|
uint16_t wait_cycles_ = 0;
|
||||||
sensor::Sensor *face_count_sensor_{nullptr};
|
sensor::Sensor *face_count_sensor_{nullptr};
|
||||||
|
|||||||
@@ -134,25 +134,23 @@ ErrorCode ArduinoI2CBus::write_readv(uint8_t address, const uint8_t *write_buffe
|
|||||||
for (size_t j = 0; j != read_count; j++)
|
for (size_t j = 0; j != read_count; j++)
|
||||||
read_buffer[j] = wire_->read();
|
read_buffer[j] = wire_->read();
|
||||||
}
|
}
|
||||||
switch (status) {
|
// Avoid switch to prevent compiler-generated lookup table in RAM on ESP8266
|
||||||
case 0:
|
if (status == 0)
|
||||||
return ERROR_OK;
|
return ERROR_OK;
|
||||||
case 1:
|
if (status == 1) {
|
||||||
// transmit buffer not large enough
|
ESP_LOGVV(TAG, "TX failed: buffer not large enough");
|
||||||
ESP_LOGVV(TAG, "TX failed: buffer not large enough");
|
return ERROR_UNKNOWN;
|
||||||
return ERROR_UNKNOWN;
|
|
||||||
case 2:
|
|
||||||
case 3:
|
|
||||||
ESP_LOGVV(TAG, "TX failed: not acknowledged: %d", status);
|
|
||||||
return ERROR_NOT_ACKNOWLEDGED;
|
|
||||||
case 5:
|
|
||||||
ESP_LOGVV(TAG, "TX failed: timeout");
|
|
||||||
return ERROR_UNKNOWN;
|
|
||||||
case 4:
|
|
||||||
default:
|
|
||||||
ESP_LOGVV(TAG, "TX failed: unknown error %u", status);
|
|
||||||
return ERROR_UNKNOWN;
|
|
||||||
}
|
}
|
||||||
|
if (status == 2 || status == 3) {
|
||||||
|
ESP_LOGVV(TAG, "TX failed: not acknowledged: %u", status);
|
||||||
|
return ERROR_NOT_ACKNOWLEDGED;
|
||||||
|
}
|
||||||
|
if (status == 5) {
|
||||||
|
ESP_LOGVV(TAG, "TX failed: timeout");
|
||||||
|
return ERROR_UNKNOWN;
|
||||||
|
}
|
||||||
|
ESP_LOGVV(TAG, "TX failed: unknown error %u", status);
|
||||||
|
return ERROR_UNKNOWN;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Perform I2C bus recovery, see:
|
/// Perform I2C bus recovery, see:
|
||||||
|
|||||||
@@ -276,10 +276,10 @@ void LD2410Component::restart_and_read_all_info() {
|
|||||||
|
|
||||||
void LD2410Component::loop() {
|
void LD2410Component::loop() {
|
||||||
// Read all available bytes in batches to reduce UART call overhead.
|
// Read all available bytes in batches to reduce UART call overhead.
|
||||||
int avail = this->available();
|
size_t avail = this->available();
|
||||||
uint8_t buf[MAX_LINE_LENGTH];
|
uint8_t buf[MAX_LINE_LENGTH];
|
||||||
while (avail > 0) {
|
while (avail > 0) {
|
||||||
size_t to_read = std::min(static_cast<size_t>(avail), sizeof(buf));
|
size_t to_read = std::min(avail, sizeof(buf));
|
||||||
if (!this->read_array(buf, to_read)) {
|
if (!this->read_array(buf, to_read)) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -311,10 +311,10 @@ void LD2412Component::restart_and_read_all_info() {
|
|||||||
|
|
||||||
void LD2412Component::loop() {
|
void LD2412Component::loop() {
|
||||||
// Read all available bytes in batches to reduce UART call overhead.
|
// Read all available bytes in batches to reduce UART call overhead.
|
||||||
int avail = this->available();
|
size_t avail = this->available();
|
||||||
uint8_t buf[MAX_LINE_LENGTH];
|
uint8_t buf[MAX_LINE_LENGTH];
|
||||||
while (avail > 0) {
|
while (avail > 0) {
|
||||||
size_t to_read = std::min(static_cast<size_t>(avail), sizeof(buf));
|
size_t to_read = std::min(avail, sizeof(buf));
|
||||||
if (!this->read_array(buf, to_read)) {
|
if (!this->read_array(buf, to_read)) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -542,10 +542,10 @@ void LD2420Component::handle_simple_mode_(const uint8_t *inbuf, int len) {
|
|||||||
|
|
||||||
void LD2420Component::read_batch_(std::span<uint8_t, MAX_LINE_LENGTH> buffer) {
|
void LD2420Component::read_batch_(std::span<uint8_t, MAX_LINE_LENGTH> buffer) {
|
||||||
// Read all available bytes in batches to reduce UART call overhead.
|
// Read all available bytes in batches to reduce UART call overhead.
|
||||||
int avail = this->available();
|
size_t avail = this->available();
|
||||||
uint8_t buf[MAX_LINE_LENGTH];
|
uint8_t buf[MAX_LINE_LENGTH];
|
||||||
while (avail > 0) {
|
while (avail > 0) {
|
||||||
size_t to_read = std::min(static_cast<size_t>(avail), sizeof(buf));
|
size_t to_read = std::min(avail, sizeof(buf));
|
||||||
if (!this->read_array(buf, to_read)) {
|
if (!this->read_array(buf, to_read)) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
|
from esphome import automation
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
from esphome.components import uart
|
from esphome.components import uart
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.const import CONF_ID, CONF_THROTTLE
|
from esphome.const import CONF_ID, CONF_ON_DATA, CONF_THROTTLE, CONF_TRIGGER_ID
|
||||||
|
|
||||||
AUTO_LOAD = ["ld24xx"]
|
AUTO_LOAD = ["ld24xx"]
|
||||||
DEPENDENCIES = ["uart"]
|
DEPENDENCIES = ["uart"]
|
||||||
@@ -11,6 +12,8 @@ MULTI_CONF = True
|
|||||||
ld2450_ns = cg.esphome_ns.namespace("ld2450")
|
ld2450_ns = cg.esphome_ns.namespace("ld2450")
|
||||||
LD2450Component = ld2450_ns.class_("LD2450Component", cg.Component, uart.UARTDevice)
|
LD2450Component = ld2450_ns.class_("LD2450Component", cg.Component, uart.UARTDevice)
|
||||||
|
|
||||||
|
LD2450DataTrigger = ld2450_ns.class_("LD2450DataTrigger", automation.Trigger.template())
|
||||||
|
|
||||||
CONF_LD2450_ID = "ld2450_id"
|
CONF_LD2450_ID = "ld2450_id"
|
||||||
|
|
||||||
CONFIG_SCHEMA = cv.All(
|
CONFIG_SCHEMA = cv.All(
|
||||||
@@ -20,6 +23,11 @@ CONFIG_SCHEMA = cv.All(
|
|||||||
cv.Optional(CONF_THROTTLE): cv.invalid(
|
cv.Optional(CONF_THROTTLE): cv.invalid(
|
||||||
f"{CONF_THROTTLE} has been removed; use per-sensor filters, instead"
|
f"{CONF_THROTTLE} has been removed; use per-sensor filters, instead"
|
||||||
),
|
),
|
||||||
|
cv.Optional(CONF_ON_DATA): automation.validate_automation(
|
||||||
|
{
|
||||||
|
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(LD2450DataTrigger),
|
||||||
|
}
|
||||||
|
),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.extend(uart.UART_DEVICE_SCHEMA)
|
.extend(uart.UART_DEVICE_SCHEMA)
|
||||||
@@ -45,3 +53,6 @@ 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)
|
||||||
await uart.register_uart_device(var, config)
|
await uart.register_uart_device(var, config)
|
||||||
|
for conf in config.get(CONF_ON_DATA, []):
|
||||||
|
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||||
|
await automation.build_automation(trigger, [], conf)
|
||||||
|
|||||||
@@ -277,10 +277,10 @@ void LD2450Component::dump_config() {
|
|||||||
|
|
||||||
void LD2450Component::loop() {
|
void LD2450Component::loop() {
|
||||||
// Read all available bytes in batches to reduce UART call overhead.
|
// Read all available bytes in batches to reduce UART call overhead.
|
||||||
int avail = this->available();
|
size_t avail = this->available();
|
||||||
uint8_t buf[MAX_LINE_LENGTH];
|
uint8_t buf[MAX_LINE_LENGTH];
|
||||||
while (avail > 0) {
|
while (avail > 0) {
|
||||||
size_t to_read = std::min(static_cast<size_t>(avail), sizeof(buf));
|
size_t to_read = std::min(avail, sizeof(buf));
|
||||||
if (!this->read_array(buf, to_read)) {
|
if (!this->read_array(buf, to_read)) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -413,6 +413,10 @@ void LD2450Component::restart_and_read_all_info() {
|
|||||||
this->set_timeout(1500, [this]() { this->read_all_info(); });
|
this->set_timeout(1500, [this]() { this->read_all_info(); });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void LD2450Component::add_on_data_callback(std::function<void()> &&callback) {
|
||||||
|
this->data_callback_.add(std::move(callback));
|
||||||
|
}
|
||||||
|
|
||||||
// Send command with values to LD2450
|
// Send command with values to LD2450
|
||||||
void LD2450Component::send_command_(uint8_t command, const uint8_t *command_value, uint8_t command_value_len) {
|
void LD2450Component::send_command_(uint8_t command, const uint8_t *command_value, uint8_t command_value_len) {
|
||||||
ESP_LOGV(TAG, "Sending COMMAND %02X", command);
|
ESP_LOGV(TAG, "Sending COMMAND %02X", command);
|
||||||
@@ -613,6 +617,8 @@ void LD2450Component::handle_periodic_data_() {
|
|||||||
this->still_presence_millis_ = App.get_loop_component_start_time();
|
this->still_presence_millis_ = App.get_loop_component_start_time();
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
this->data_callback_.call();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool LD2450Component::handle_ack_data_() {
|
bool LD2450Component::handle_ack_data_() {
|
||||||
|
|||||||
@@ -141,6 +141,9 @@ class LD2450Component : public Component, public uart::UARTDevice {
|
|||||||
int32_t zone2_x1, int32_t zone2_y1, int32_t zone2_x2, int32_t zone2_y2, int32_t zone3_x1,
|
int32_t zone2_x1, int32_t zone2_y1, int32_t zone2_x2, int32_t zone2_y2, int32_t zone3_x1,
|
||||||
int32_t zone3_y1, int32_t zone3_x2, int32_t zone3_y2);
|
int32_t zone3_y1, int32_t zone3_x2, int32_t zone3_y2);
|
||||||
|
|
||||||
|
/// Add a callback that will be called after each successfully processed periodic data frame.
|
||||||
|
void add_on_data_callback(std::function<void()> &&callback);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void send_command_(uint8_t command_str, const uint8_t *command_value, uint8_t command_value_len);
|
void send_command_(uint8_t command_str, const uint8_t *command_value, uint8_t command_value_len);
|
||||||
void set_config_mode_(bool enable);
|
void set_config_mode_(bool enable);
|
||||||
@@ -190,6 +193,15 @@ class LD2450Component : public Component, public uart::UARTDevice {
|
|||||||
#ifdef USE_TEXT_SENSOR
|
#ifdef USE_TEXT_SENSOR
|
||||||
std::array<text_sensor::TextSensor *, 3> direction_text_sensors_{};
|
std::array<text_sensor::TextSensor *, 3> direction_text_sensors_{};
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
LazyCallbackManager<void()> data_callback_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class LD2450DataTrigger : public Trigger<> {
|
||||||
|
public:
|
||||||
|
explicit LD2450DataTrigger(LD2450Component *parent) {
|
||||||
|
parent->add_on_data_callback([this]() { this->trigger(); });
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace esphome::ld2450
|
} // namespace esphome::ld2450
|
||||||
|
|||||||
@@ -36,8 +36,9 @@ void HOT Logger::log_vprintf_(uint8_t level, const char *tag, int line, const ch
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Fast path: main thread, no recursion (99.9% of all logs)
|
// Fast path: main thread, no recursion (99.9% of all logs)
|
||||||
|
// Pass nullptr for thread_name since we already know this is the main task
|
||||||
if (is_main_task && !this->main_task_recursion_guard_) [[likely]] {
|
if (is_main_task && !this->main_task_recursion_guard_) [[likely]] {
|
||||||
this->log_message_to_buffer_and_send_(this->main_task_recursion_guard_, level, tag, line, format, args);
|
this->log_message_to_buffer_and_send_(this->main_task_recursion_guard_, level, tag, line, format, args, nullptr);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,21 +48,23 @@ void HOT Logger::log_vprintf_(uint8_t level, const char *tag, int line, const ch
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Non-main thread handling (~0.1% of logs)
|
// Non-main thread handling (~0.1% of logs)
|
||||||
|
// Resolve thread name once and pass it through the logging chain.
|
||||||
|
// ESP32/LibreTiny: use TaskHandle_t overload to avoid redundant xTaskGetCurrentTaskHandle()
|
||||||
|
// (we already have the handle from the main task check above).
|
||||||
|
// Host: pass a stack buffer for pthread_getname_np to write into.
|
||||||
#if defined(USE_ESP32) || defined(USE_LIBRETINY)
|
#if defined(USE_ESP32) || defined(USE_LIBRETINY)
|
||||||
this->log_vprintf_non_main_thread_(level, tag, line, format, args, current_task);
|
const char *thread_name = get_thread_name_(current_task);
|
||||||
#else // USE_HOST
|
#else // USE_HOST
|
||||||
this->log_vprintf_non_main_thread_(level, tag, line, format, args);
|
char thread_name_buf[THREAD_NAME_BUF_SIZE];
|
||||||
|
const char *thread_name = this->get_thread_name_(thread_name_buf);
|
||||||
#endif
|
#endif
|
||||||
|
this->log_vprintf_non_main_thread_(level, tag, line, format, args, thread_name);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handles non-main thread logging only
|
// Handles non-main thread logging only
|
||||||
// Kept separate from hot path to improve instruction cache performance
|
// Kept separate from hot path to improve instruction cache performance
|
||||||
#if defined(USE_ESP32) || defined(USE_LIBRETINY)
|
|
||||||
void Logger::log_vprintf_non_main_thread_(uint8_t level, const char *tag, int line, const char *format, va_list args,
|
void Logger::log_vprintf_non_main_thread_(uint8_t level, const char *tag, int line, const char *format, va_list args,
|
||||||
TaskHandle_t current_task) {
|
const char *thread_name) {
|
||||||
#else // USE_HOST
|
|
||||||
void Logger::log_vprintf_non_main_thread_(uint8_t level, const char *tag, int line, const char *format, va_list args) {
|
|
||||||
#endif
|
|
||||||
// Check if already in recursion for this non-main thread/task
|
// Check if already in recursion for this non-main thread/task
|
||||||
if (this->is_non_main_task_recursive_()) {
|
if (this->is_non_main_task_recursive_()) {
|
||||||
return;
|
return;
|
||||||
@@ -73,12 +76,8 @@ void Logger::log_vprintf_non_main_thread_(uint8_t level, const char *tag, int li
|
|||||||
bool message_sent = false;
|
bool message_sent = false;
|
||||||
#ifdef USE_ESPHOME_TASK_LOG_BUFFER
|
#ifdef USE_ESPHOME_TASK_LOG_BUFFER
|
||||||
// For non-main threads/tasks, queue the message for callbacks
|
// For non-main threads/tasks, queue the message for callbacks
|
||||||
#if defined(USE_ESP32) || defined(USE_LIBRETINY)
|
|
||||||
message_sent =
|
message_sent =
|
||||||
this->log_buffer_->send_message_thread_safe(level, tag, static_cast<uint16_t>(line), current_task, format, args);
|
this->log_buffer_->send_message_thread_safe(level, tag, static_cast<uint16_t>(line), thread_name, format, args);
|
||||||
#else // USE_HOST
|
|
||||||
message_sent = this->log_buffer_->send_message_thread_safe(level, tag, static_cast<uint16_t>(line), format, args);
|
|
||||||
#endif
|
|
||||||
if (message_sent) {
|
if (message_sent) {
|
||||||
// Enable logger loop to process the buffered message
|
// Enable logger loop to process the buffered message
|
||||||
// This is safe to call from any context including ISRs
|
// This is safe to call from any context including ISRs
|
||||||
@@ -101,19 +100,27 @@ void Logger::log_vprintf_non_main_thread_(uint8_t level, const char *tag, int li
|
|||||||
#endif
|
#endif
|
||||||
char console_buffer[MAX_CONSOLE_LOG_MSG_SIZE]; // MUST be stack allocated for thread safety
|
char console_buffer[MAX_CONSOLE_LOG_MSG_SIZE]; // MUST be stack allocated for thread safety
|
||||||
LogBuffer buf{console_buffer, MAX_CONSOLE_LOG_MSG_SIZE};
|
LogBuffer buf{console_buffer, MAX_CONSOLE_LOG_MSG_SIZE};
|
||||||
this->format_log_to_buffer_with_terminator_(level, tag, line, format, args, buf);
|
this->format_log_to_buffer_with_terminator_(level, tag, line, format, args, buf, thread_name);
|
||||||
this->write_to_console_(buf);
|
this->write_to_console_(buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
// RAII guard automatically resets on return
|
// RAII guard automatically resets on return
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
// Implementation for all other platforms (single-task, no threading)
|
// Implementation for single-task platforms (ESP8266, RP2040, Zephyr)
|
||||||
|
// TODO: Zephyr may have multiple threads (work queues, etc.) but uses this single-task path.
|
||||||
|
// Logging calls are NOT thread-safe: global_recursion_guard_ is a plain bool and tx_buffer_ has no locking.
|
||||||
|
// Not a problem in practice yet since Zephyr has no API support (logs are console-only).
|
||||||
void HOT Logger::log_vprintf_(uint8_t level, const char *tag, int line, const char *format, va_list args) { // NOLINT
|
void HOT Logger::log_vprintf_(uint8_t level, const char *tag, int line, const char *format, va_list args) { // NOLINT
|
||||||
if (level > this->level_for(tag) || global_recursion_guard_)
|
if (level > this->level_for(tag) || global_recursion_guard_)
|
||||||
return;
|
return;
|
||||||
|
#ifdef USE_ZEPHYR
|
||||||
this->log_message_to_buffer_and_send_(global_recursion_guard_, level, tag, line, format, args);
|
char tmp[MAX_POINTER_REPRESENTATION];
|
||||||
|
this->log_message_to_buffer_and_send_(global_recursion_guard_, level, tag, line, format, args,
|
||||||
|
this->get_thread_name_(tmp));
|
||||||
|
#else // Other single-task platforms don't have thread names, so pass nullptr
|
||||||
|
this->log_message_to_buffer_and_send_(global_recursion_guard_, level, tag, line, format, args, nullptr);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
#endif // USE_ESP32 / USE_HOST / USE_LIBRETINY
|
#endif // USE_ESP32 / USE_HOST / USE_LIBRETINY
|
||||||
|
|
||||||
@@ -129,7 +136,7 @@ void Logger::log_vprintf_(uint8_t level, const char *tag, int line, const __Flas
|
|||||||
if (level > this->level_for(tag) || global_recursion_guard_)
|
if (level > this->level_for(tag) || global_recursion_guard_)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
this->log_message_to_buffer_and_send_(global_recursion_guard_, level, tag, line, format, args);
|
this->log_message_to_buffer_and_send_(global_recursion_guard_, level, tag, line, format, args, nullptr);
|
||||||
}
|
}
|
||||||
#endif // USE_STORE_LOG_STR_IN_FLASH
|
#endif // USE_STORE_LOG_STR_IN_FLASH
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include <cstdarg>
|
#include <cstdarg>
|
||||||
#include <map>
|
#include <map>
|
||||||
|
#include <span>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
#if defined(USE_ESP32) || defined(USE_HOST)
|
#if defined(USE_ESP32) || defined(USE_HOST)
|
||||||
#include <pthread.h>
|
#include <pthread.h>
|
||||||
@@ -124,6 +125,10 @@ static constexpr uint16_t MAX_HEADER_SIZE = 128;
|
|||||||
// "0x" + 2 hex digits per byte + '\0'
|
// "0x" + 2 hex digits per byte + '\0'
|
||||||
static constexpr size_t MAX_POINTER_REPRESENTATION = 2 + sizeof(void *) * 2 + 1;
|
static constexpr size_t MAX_POINTER_REPRESENTATION = 2 + sizeof(void *) * 2 + 1;
|
||||||
|
|
||||||
|
// Stack buffer size for retrieving thread/task names from the OS
|
||||||
|
// macOS allows up to 64 bytes, Linux up to 16
|
||||||
|
static constexpr size_t THREAD_NAME_BUF_SIZE = 64;
|
||||||
|
|
||||||
// Buffer wrapper for log formatting functions
|
// Buffer wrapper for log formatting functions
|
||||||
struct LogBuffer {
|
struct LogBuffer {
|
||||||
char *data;
|
char *data;
|
||||||
@@ -408,34 +413,24 @@ class Logger : public Component {
|
|||||||
|
|
||||||
#if defined(USE_ESP32) || defined(USE_HOST) || defined(USE_LIBRETINY)
|
#if defined(USE_ESP32) || defined(USE_HOST) || defined(USE_LIBRETINY)
|
||||||
// Handles non-main thread logging only (~0.1% of calls)
|
// Handles non-main thread logging only (~0.1% of calls)
|
||||||
#if defined(USE_ESP32) || defined(USE_LIBRETINY)
|
// thread_name is resolved by the caller from the task handle, avoiding redundant lookups
|
||||||
// ESP32/LibreTiny: Pass task handle to avoid calling xTaskGetCurrentTaskHandle() twice
|
|
||||||
void log_vprintf_non_main_thread_(uint8_t level, const char *tag, int line, const char *format, va_list args,
|
void log_vprintf_non_main_thread_(uint8_t level, const char *tag, int line, const char *format, va_list args,
|
||||||
TaskHandle_t current_task);
|
const char *thread_name);
|
||||||
#else // USE_HOST
|
|
||||||
// Host: No task handle parameter needed (not used in send_message_thread_safe)
|
|
||||||
void log_vprintf_non_main_thread_(uint8_t level, const char *tag, int line, const char *format, va_list args);
|
|
||||||
#endif
|
|
||||||
#endif
|
#endif
|
||||||
void process_messages_();
|
void process_messages_();
|
||||||
void write_msg_(const char *msg, uint16_t len);
|
void write_msg_(const char *msg, uint16_t len);
|
||||||
|
|
||||||
// Format a log message with printf-style arguments and write it to a buffer with header, footer, and null terminator
|
// Format a log message with printf-style arguments and write it to a buffer with header, footer, and null terminator
|
||||||
|
// thread_name: name of the calling thread/task, or nullptr for main task (callers already know which task they're on)
|
||||||
inline void HOT format_log_to_buffer_with_terminator_(uint8_t level, const char *tag, int line, const char *format,
|
inline void HOT format_log_to_buffer_with_terminator_(uint8_t level, const char *tag, int line, const char *format,
|
||||||
va_list args, LogBuffer &buf) {
|
va_list args, LogBuffer &buf, const char *thread_name) {
|
||||||
#if defined(USE_ESP32) || defined(USE_LIBRETINY) || defined(USE_HOST)
|
buf.write_header(level, tag, line, thread_name);
|
||||||
buf.write_header(level, tag, line, this->get_thread_name_());
|
|
||||||
#elif defined(USE_ZEPHYR)
|
|
||||||
char tmp[MAX_POINTER_REPRESENTATION];
|
|
||||||
buf.write_header(level, tag, line, this->get_thread_name_(tmp));
|
|
||||||
#else
|
|
||||||
buf.write_header(level, tag, line, nullptr);
|
|
||||||
#endif
|
|
||||||
buf.format_body(format, args);
|
buf.format_body(format, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef USE_STORE_LOG_STR_IN_FLASH
|
#ifdef USE_STORE_LOG_STR_IN_FLASH
|
||||||
// Format a log message with flash string format and write it to a buffer with header, footer, and null terminator
|
// Format a log message with flash string format and write it to a buffer with header, footer, and null terminator
|
||||||
|
// ESP8266-only (single-task), thread_name is always nullptr
|
||||||
inline void HOT format_log_to_buffer_with_terminator_P_(uint8_t level, const char *tag, int line,
|
inline void HOT format_log_to_buffer_with_terminator_P_(uint8_t level, const char *tag, int line,
|
||||||
const __FlashStringHelper *format, va_list args,
|
const __FlashStringHelper *format, va_list args,
|
||||||
LogBuffer &buf) {
|
LogBuffer &buf) {
|
||||||
@@ -466,9 +461,10 @@ class Logger : public Component {
|
|||||||
|
|
||||||
// Helper to format and send a log message to both console and listeners
|
// Helper to format and send a log message to both console and listeners
|
||||||
// Template handles both const char* (RAM) and __FlashStringHelper* (flash) format strings
|
// Template handles both const char* (RAM) and __FlashStringHelper* (flash) format strings
|
||||||
|
// thread_name: name of the calling thread/task, or nullptr for main task
|
||||||
template<typename FormatType>
|
template<typename FormatType>
|
||||||
inline void HOT log_message_to_buffer_and_send_(bool &recursion_guard, uint8_t level, const char *tag, int line,
|
inline void HOT log_message_to_buffer_and_send_(bool &recursion_guard, uint8_t level, const char *tag, int line,
|
||||||
FormatType format, va_list args) {
|
FormatType format, va_list args, const char *thread_name) {
|
||||||
RecursionGuard guard(recursion_guard);
|
RecursionGuard guard(recursion_guard);
|
||||||
LogBuffer buf{this->tx_buffer_, this->tx_buffer_size_};
|
LogBuffer buf{this->tx_buffer_, this->tx_buffer_size_};
|
||||||
#ifdef USE_STORE_LOG_STR_IN_FLASH
|
#ifdef USE_STORE_LOG_STR_IN_FLASH
|
||||||
@@ -477,7 +473,7 @@ class Logger : public Component {
|
|||||||
} else
|
} else
|
||||||
#endif
|
#endif
|
||||||
{
|
{
|
||||||
this->format_log_to_buffer_with_terminator_(level, tag, line, format, args, buf);
|
this->format_log_to_buffer_with_terminator_(level, tag, line, format, args, buf, thread_name);
|
||||||
}
|
}
|
||||||
this->notify_listeners_(level, tag, buf);
|
this->notify_listeners_(level, tag, buf);
|
||||||
this->write_log_buffer_to_console_(buf);
|
this->write_log_buffer_to_console_(buf);
|
||||||
@@ -565,37 +561,57 @@ class Logger : public Component {
|
|||||||
bool global_recursion_guard_{false}; // Simple global recursion guard for single-task platforms
|
bool global_recursion_guard_{false}; // Simple global recursion guard for single-task platforms
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(USE_ESP32) || defined(USE_LIBRETINY) || defined(USE_ZEPHYR)
|
// --- get_thread_name_ overloads (per-platform) ---
|
||||||
const char *HOT get_thread_name_(
|
|
||||||
#ifdef USE_ZEPHYR
|
#if defined(USE_ESP32) || defined(USE_LIBRETINY)
|
||||||
char *buff
|
// Primary overload - takes a task handle directly to avoid redundant xTaskGetCurrentTaskHandle() calls
|
||||||
|
// when the caller already has the handle (e.g. from the main task check in log_vprintf_)
|
||||||
|
const char *get_thread_name_(TaskHandle_t task) {
|
||||||
|
if (task == this->main_task_) {
|
||||||
|
return nullptr; // Main task
|
||||||
|
}
|
||||||
|
#if defined(USE_ESP32)
|
||||||
|
return pcTaskGetName(task);
|
||||||
|
#elif defined(USE_LIBRETINY)
|
||||||
|
return pcTaskGetTaskName(task);
|
||||||
#endif
|
#endif
|
||||||
) {
|
}
|
||||||
#ifdef USE_ZEPHYR
|
|
||||||
|
// Convenience overload - gets the current task handle and delegates
|
||||||
|
const char *HOT get_thread_name_() { return this->get_thread_name_(xTaskGetCurrentTaskHandle()); }
|
||||||
|
|
||||||
|
#elif defined(USE_HOST)
|
||||||
|
// Takes a caller-provided buffer for the thread name (stack-allocated for thread safety)
|
||||||
|
const char *HOT get_thread_name_(std::span<char> buff) {
|
||||||
|
pthread_t current_thread = pthread_self();
|
||||||
|
if (pthread_equal(current_thread, main_thread_)) {
|
||||||
|
return nullptr; // Main thread
|
||||||
|
}
|
||||||
|
// For non-main threads, get the thread name into the caller-provided buffer
|
||||||
|
if (pthread_getname_np(current_thread, buff.data(), buff.size()) == 0) {
|
||||||
|
return buff.data();
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
#elif defined(USE_ZEPHYR)
|
||||||
|
const char *HOT get_thread_name_(std::span<char> buff) {
|
||||||
k_tid_t current_task = k_current_get();
|
k_tid_t current_task = k_current_get();
|
||||||
#else
|
|
||||||
TaskHandle_t current_task = xTaskGetCurrentTaskHandle();
|
|
||||||
#endif
|
|
||||||
if (current_task == main_task_) {
|
if (current_task == main_task_) {
|
||||||
return nullptr; // Main task
|
return nullptr; // Main task
|
||||||
} else {
|
|
||||||
#if defined(USE_ESP32)
|
|
||||||
return pcTaskGetName(current_task);
|
|
||||||
#elif defined(USE_LIBRETINY)
|
|
||||||
return pcTaskGetTaskName(current_task);
|
|
||||||
#elif defined(USE_ZEPHYR)
|
|
||||||
const char *name = k_thread_name_get(current_task);
|
|
||||||
if (name) {
|
|
||||||
// zephyr print task names only if debug component is present
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
std::snprintf(buff, MAX_POINTER_REPRESENTATION, "%p", current_task);
|
|
||||||
return buff;
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
const char *name = k_thread_name_get(current_task);
|
||||||
|
if (name) {
|
||||||
|
// zephyr print task names only if debug component is present
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
std::snprintf(buff.data(), buff.size(), "%p", current_task);
|
||||||
|
return buff.data();
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// --- Non-main task recursion guards (per-platform) ---
|
||||||
|
|
||||||
#if defined(USE_ESP32) || defined(USE_HOST)
|
#if defined(USE_ESP32) || defined(USE_HOST)
|
||||||
// RAII guard for non-main task recursion using pthread TLS
|
// RAII guard for non-main task recursion using pthread TLS
|
||||||
class NonMainTaskRecursionGuard {
|
class NonMainTaskRecursionGuard {
|
||||||
@@ -635,22 +651,6 @@ class Logger : public Component {
|
|||||||
inline RecursionGuard make_non_main_task_guard_() { return RecursionGuard(non_main_task_recursion_guard_); }
|
inline RecursionGuard make_non_main_task_guard_() { return RecursionGuard(non_main_task_recursion_guard_); }
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef USE_HOST
|
|
||||||
const char *HOT get_thread_name_() {
|
|
||||||
pthread_t current_thread = pthread_self();
|
|
||||||
if (pthread_equal(current_thread, main_thread_)) {
|
|
||||||
return nullptr; // Main thread
|
|
||||||
}
|
|
||||||
// For non-main threads, return the thread name
|
|
||||||
// We store it in thread-local storage to avoid allocation
|
|
||||||
static thread_local char thread_name_buf[32];
|
|
||||||
if (pthread_getname_np(current_thread, thread_name_buf, sizeof(thread_name_buf)) == 0) {
|
|
||||||
return thread_name_buf;
|
|
||||||
}
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if defined(USE_ESP32) || defined(USE_LIBRETINY)
|
#if defined(USE_ESP32) || defined(USE_LIBRETINY)
|
||||||
// Disable loop when task buffer is empty (with USB CDC check on ESP32)
|
// Disable loop when task buffer is empty (with USB CDC check on ESP32)
|
||||||
inline void disable_loop_when_buffer_empty_() {
|
inline void disable_loop_when_buffer_empty_() {
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ void TaskLogBuffer::release_message_main_loop(void *token) {
|
|||||||
last_processed_counter_ = message_counter_.load(std::memory_order_relaxed);
|
last_processed_counter_ = message_counter_.load(std::memory_order_relaxed);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TaskLogBuffer::send_message_thread_safe(uint8_t level, const char *tag, uint16_t line, TaskHandle_t task_handle,
|
bool TaskLogBuffer::send_message_thread_safe(uint8_t level, const char *tag, uint16_t line, const char *thread_name,
|
||||||
const char *format, va_list args) {
|
const char *format, va_list args) {
|
||||||
// First, calculate the exact length needed using a null buffer (no actual writing)
|
// First, calculate the exact length needed using a null buffer (no actual writing)
|
||||||
va_list args_copy;
|
va_list args_copy;
|
||||||
@@ -95,7 +95,6 @@ bool TaskLogBuffer::send_message_thread_safe(uint8_t level, const char *tag, uin
|
|||||||
// Store the thread name now instead of waiting until main loop processing
|
// Store the thread name now instead of waiting until main loop processing
|
||||||
// This avoids crashes if the task completes or is deleted between when this message
|
// This avoids crashes if the task completes or is deleted between when this message
|
||||||
// is enqueued and when it's processed by the main loop
|
// is enqueued and when it's processed by the main loop
|
||||||
const char *thread_name = pcTaskGetName(task_handle);
|
|
||||||
if (thread_name != nullptr) {
|
if (thread_name != nullptr) {
|
||||||
strncpy(msg->thread_name, thread_name, sizeof(msg->thread_name) - 1);
|
strncpy(msg->thread_name, thread_name, sizeof(msg->thread_name) - 1);
|
||||||
msg->thread_name[sizeof(msg->thread_name) - 1] = '\0'; // Ensure null termination
|
msg->thread_name[sizeof(msg->thread_name) - 1] = '\0'; // Ensure null termination
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ class TaskLogBuffer {
|
|||||||
void release_message_main_loop(void *token);
|
void release_message_main_loop(void *token);
|
||||||
|
|
||||||
// Thread-safe - send a message to the ring buffer from any thread
|
// Thread-safe - send a message to the ring buffer from any thread
|
||||||
bool send_message_thread_safe(uint8_t level, const char *tag, uint16_t line, TaskHandle_t task_handle,
|
bool send_message_thread_safe(uint8_t level, const char *tag, uint16_t line, const char *thread_name,
|
||||||
const char *format, va_list args);
|
const char *format, va_list args);
|
||||||
|
|
||||||
// Check if there are messages ready to be processed using an atomic counter for performance
|
// Check if there are messages ready to be processed using an atomic counter for performance
|
||||||
|
|||||||
@@ -70,8 +70,8 @@ void TaskLogBufferHost::commit_write_slot_(int slot_index) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TaskLogBufferHost::send_message_thread_safe(uint8_t level, const char *tag, uint16_t line, const char *format,
|
bool TaskLogBufferHost::send_message_thread_safe(uint8_t level, const char *tag, uint16_t line, const char *thread_name,
|
||||||
va_list args) {
|
const char *format, va_list args) {
|
||||||
// Acquire a slot
|
// Acquire a slot
|
||||||
int slot_index = this->acquire_write_slot_();
|
int slot_index = this->acquire_write_slot_();
|
||||||
if (slot_index < 0) {
|
if (slot_index < 0) {
|
||||||
@@ -85,11 +85,9 @@ bool TaskLogBufferHost::send_message_thread_safe(uint8_t level, const char *tag,
|
|||||||
msg.tag = tag;
|
msg.tag = tag;
|
||||||
msg.line = line;
|
msg.line = line;
|
||||||
|
|
||||||
// Get thread name using pthread
|
// Store the thread name now to avoid crashes if thread exits before processing
|
||||||
char thread_name_buf[LogMessage::MAX_THREAD_NAME_SIZE];
|
if (thread_name != nullptr) {
|
||||||
// pthread_getname_np works the same on Linux and macOS
|
strncpy(msg.thread_name, thread_name, sizeof(msg.thread_name) - 1);
|
||||||
if (pthread_getname_np(pthread_self(), thread_name_buf, sizeof(thread_name_buf)) == 0) {
|
|
||||||
strncpy(msg.thread_name, thread_name_buf, sizeof(msg.thread_name) - 1);
|
|
||||||
msg.thread_name[sizeof(msg.thread_name) - 1] = '\0';
|
msg.thread_name[sizeof(msg.thread_name) - 1] = '\0';
|
||||||
} else {
|
} else {
|
||||||
msg.thread_name[0] = '\0';
|
msg.thread_name[0] = '\0';
|
||||||
|
|||||||
@@ -86,7 +86,8 @@ class TaskLogBufferHost {
|
|||||||
|
|
||||||
// Thread-safe - send a message to the buffer from any thread
|
// Thread-safe - send a message to the buffer from any thread
|
||||||
// Returns true if message was queued, false if buffer is full
|
// Returns true if message was queued, false if buffer is full
|
||||||
bool send_message_thread_safe(uint8_t level, const char *tag, uint16_t line, const char *format, va_list args);
|
bool send_message_thread_safe(uint8_t level, const char *tag, uint16_t line, const char *thread_name,
|
||||||
|
const char *format, va_list args);
|
||||||
|
|
||||||
// Check if there are messages ready to be processed
|
// Check if there are messages ready to be processed
|
||||||
inline bool HOT has_messages() const {
|
inline bool HOT has_messages() const {
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ void TaskLogBufferLibreTiny::release_message_main_loop() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool TaskLogBufferLibreTiny::send_message_thread_safe(uint8_t level, const char *tag, uint16_t line,
|
bool TaskLogBufferLibreTiny::send_message_thread_safe(uint8_t level, const char *tag, uint16_t line,
|
||||||
TaskHandle_t task_handle, const char *format, va_list args) {
|
const char *thread_name, const char *format, va_list args) {
|
||||||
// First, calculate the exact length needed using a null buffer (no actual writing)
|
// First, calculate the exact length needed using a null buffer (no actual writing)
|
||||||
va_list args_copy;
|
va_list args_copy;
|
||||||
va_copy(args_copy, args);
|
va_copy(args_copy, args);
|
||||||
@@ -162,7 +162,6 @@ bool TaskLogBufferLibreTiny::send_message_thread_safe(uint8_t level, const char
|
|||||||
msg->line = line;
|
msg->line = line;
|
||||||
|
|
||||||
// Store the thread name now to avoid crashes if task is deleted before processing
|
// Store the thread name now to avoid crashes if task is deleted before processing
|
||||||
const char *thread_name = pcTaskGetTaskName(task_handle);
|
|
||||||
if (thread_name != nullptr) {
|
if (thread_name != nullptr) {
|
||||||
strncpy(msg->thread_name, thread_name, sizeof(msg->thread_name) - 1);
|
strncpy(msg->thread_name, thread_name, sizeof(msg->thread_name) - 1);
|
||||||
msg->thread_name[sizeof(msg->thread_name) - 1] = '\0';
|
msg->thread_name[sizeof(msg->thread_name) - 1] = '\0';
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ class TaskLogBufferLibreTiny {
|
|||||||
void release_message_main_loop();
|
void release_message_main_loop();
|
||||||
|
|
||||||
// Thread-safe - send a message to the buffer from any thread
|
// Thread-safe - send a message to the buffer from any thread
|
||||||
bool send_message_thread_safe(uint8_t level, const char *tag, uint16_t line, TaskHandle_t task_handle,
|
bool send_message_thread_safe(uint8_t level, const char *tag, uint16_t line, const char *thread_name,
|
||||||
const char *format, va_list args);
|
const char *format, va_list args);
|
||||||
|
|
||||||
// Fast check using volatile counter - no lock needed
|
// Fast check using volatile counter - no lock needed
|
||||||
|
|||||||
@@ -120,3 +120,101 @@ DriverChip(
|
|||||||
(0xB2, 0x10),
|
(0xB2, 0x10),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
DriverChip(
|
||||||
|
"WAVESHARE-ESP32-P4-WIFI6-TOUCH-LCD-3.4C",
|
||||||
|
height=800,
|
||||||
|
width=800,
|
||||||
|
hsync_back_porch=20,
|
||||||
|
hsync_pulse_width=20,
|
||||||
|
hsync_front_porch=40,
|
||||||
|
vsync_back_porch=12,
|
||||||
|
vsync_pulse_width=4,
|
||||||
|
vsync_front_porch=24,
|
||||||
|
pclk_frequency="80MHz",
|
||||||
|
lane_bit_rate="1.5Gbps",
|
||||||
|
swap_xy=cv.UNDEFINED,
|
||||||
|
color_order="RGB",
|
||||||
|
initsequence=[
|
||||||
|
(0xE0, 0x00), # select userpage
|
||||||
|
(0xE1, 0x93), (0xE2, 0x65), (0xE3, 0xF8),
|
||||||
|
(0x80, 0x01), # Select number of lanes (2)
|
||||||
|
(0xE0, 0x01), # select page 1
|
||||||
|
(0x00, 0x00), (0x01, 0x41), (0x03, 0x10), (0x04, 0x44), (0x17, 0x00), (0x18, 0xD0), (0x19, 0x00), (0x1A, 0x00),
|
||||||
|
(0x1B, 0xD0), (0x1C, 0x00), (0x24, 0xFE), (0x35, 0x26), (0x37, 0x09), (0x38, 0x04), (0x39, 0x08), (0x3A, 0x0A),
|
||||||
|
(0x3C, 0x78), (0x3D, 0xFF), (0x3E, 0xFF), (0x3F, 0xFF), (0x40, 0x00), (0x41, 0x64), (0x42, 0xC7), (0x43, 0x18),
|
||||||
|
(0x44, 0x0B), (0x45, 0x14), (0x55, 0x02), (0x57, 0x49), (0x59, 0x0A), (0x5A, 0x1B), (0x5B, 0x19), (0x5D, 0x7F),
|
||||||
|
(0x5E, 0x56), (0x5F, 0x43), (0x60, 0x37), (0x61, 0x33), (0x62, 0x25), (0x63, 0x2A), (0x64, 0x16), (0x65, 0x30),
|
||||||
|
(0x66, 0x2F), (0x67, 0x32), (0x68, 0x53), (0x69, 0x43), (0x6A, 0x4C), (0x6B, 0x40), (0x6C, 0x3D), (0x6D, 0x31),
|
||||||
|
(0x6E, 0x20), (0x6F, 0x0F), (0x70, 0x7F), (0x71, 0x56), (0x72, 0x43), (0x73, 0x37), (0x74, 0x33), (0x75, 0x25),
|
||||||
|
(0x76, 0x2A), (0x77, 0x16), (0x78, 0x30), (0x79, 0x2F), (0x7A, 0x32), (0x7B, 0x53), (0x7C, 0x43), (0x7D, 0x4C),
|
||||||
|
(0x7E, 0x40), (0x7F, 0x3D), (0x80, 0x31), (0x81, 0x20), (0x82, 0x0F),
|
||||||
|
(0xE0, 0x02), # select page 2
|
||||||
|
(0x00, 0x5F), (0x01, 0x5F), (0x02, 0x5E), (0x03, 0x5E), (0x04, 0x50), (0x05, 0x48), (0x06, 0x48), (0x07, 0x4A),
|
||||||
|
(0x08, 0x4A), (0x09, 0x44), (0x0A, 0x44), (0x0B, 0x46), (0x0C, 0x46), (0x0D, 0x5F), (0x0E, 0x5F), (0x0F, 0x57),
|
||||||
|
(0x10, 0x57), (0x11, 0x77), (0x12, 0x77), (0x13, 0x40), (0x14, 0x42), (0x15, 0x5F), (0x16, 0x5F), (0x17, 0x5F),
|
||||||
|
(0x18, 0x5E), (0x19, 0x5E), (0x1A, 0x50), (0x1B, 0x49), (0x1C, 0x49), (0x1D, 0x4B), (0x1E, 0x4B), (0x1F, 0x45),
|
||||||
|
(0x20, 0x45), (0x21, 0x47), (0x22, 0x47), (0x23, 0x5F), (0x24, 0x5F), (0x25, 0x57), (0x26, 0x57), (0x27, 0x77),
|
||||||
|
(0x28, 0x77), (0x29, 0x41), (0x2A, 0x43), (0x2B, 0x5F), (0x2C, 0x1E), (0x2D, 0x1E), (0x2E, 0x1F), (0x2F, 0x1F),
|
||||||
|
(0x30, 0x10), (0x31, 0x07), (0x32, 0x07), (0x33, 0x05), (0x34, 0x05), (0x35, 0x0B), (0x36, 0x0B), (0x37, 0x09),
|
||||||
|
(0x38, 0x09), (0x39, 0x1F), (0x3A, 0x1F), (0x3B, 0x17), (0x3C, 0x17), (0x3D, 0x17), (0x3E, 0x17), (0x3F, 0x03),
|
||||||
|
(0x40, 0x01), (0x41, 0x1F), (0x42, 0x1E), (0x43, 0x1E), (0x44, 0x1F), (0x45, 0x1F), (0x46, 0x10), (0x47, 0x06),
|
||||||
|
(0x48, 0x06), (0x49, 0x04), (0x4A, 0x04), (0x4B, 0x0A), (0x4C, 0x0A), (0x4D, 0x08), (0x4E, 0x08), (0x4F, 0x1F),
|
||||||
|
(0x50, 0x1F), (0x51, 0x17), (0x52, 0x17), (0x53, 0x17), (0x54, 0x17), (0x55, 0x02), (0x56, 0x00), (0x57, 0x1F),
|
||||||
|
(0xE0, 0x02), # select page 2
|
||||||
|
(0x58, 0x40), (0x59, 0x00), (0x5A, 0x00), (0x5B, 0x30), (0x5C, 0x01), (0x5D, 0x30), (0x5E, 0x01), (0x5F, 0x02),
|
||||||
|
(0x60, 0x30), (0x61, 0x03), (0x62, 0x04), (0x63, 0x04), (0x64, 0xA6), (0x65, 0x43), (0x66, 0x30), (0x67, 0x73),
|
||||||
|
(0x68, 0x05), (0x69, 0x04), (0x6A, 0x7F), (0x6B, 0x08), (0x6C, 0x00), (0x6D, 0x04), (0x6E, 0x04), (0x6F, 0x88),
|
||||||
|
(0x75, 0xD9), (0x76, 0x00), (0x77, 0x33), (0x78, 0x43),
|
||||||
|
(0xE0, 0x00), # select userpage
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
DriverChip(
|
||||||
|
"WAVESHARE-ESP32-P4-WIFI6-TOUCH-LCD-4C",
|
||||||
|
height=720,
|
||||||
|
width=720,
|
||||||
|
hsync_back_porch=20,
|
||||||
|
hsync_pulse_width=20,
|
||||||
|
hsync_front_porch=40,
|
||||||
|
vsync_back_porch=12,
|
||||||
|
vsync_pulse_width=4,
|
||||||
|
vsync_front_porch=24,
|
||||||
|
pclk_frequency="80MHz",
|
||||||
|
lane_bit_rate="1.5Gbps",
|
||||||
|
swap_xy=cv.UNDEFINED,
|
||||||
|
color_order="RGB",
|
||||||
|
initsequence=[
|
||||||
|
(0xE0, 0x00), # select userpage
|
||||||
|
(0xE1, 0x93), (0xE2, 0x65), (0xE3, 0xF8),
|
||||||
|
(0x80, 0x01), # Select number of lanes (2)
|
||||||
|
(0xE0, 0x01), # select page 1
|
||||||
|
(0x00, 0x00), (0x01, 0x41), (0x03, 0x10), (0x04, 0x44), (0x17, 0x00), (0x18, 0xD0), (0x19, 0x00), (0x1A, 0x00),
|
||||||
|
(0x1B, 0xD0), (0x1C, 0x00), (0x24, 0xFE), (0x35, 0x26), (0x37, 0x09), (0x38, 0x04), (0x39, 0x08), (0x3A, 0x0A),
|
||||||
|
(0x3C, 0x78), (0x3D, 0xFF), (0x3E, 0xFF), (0x3F, 0xFF), (0x40, 0x04), (0x41, 0x64), (0x42, 0xC7), (0x43, 0x18),
|
||||||
|
(0x44, 0x0B), (0x45, 0x14), (0x55, 0x02), (0x57, 0x49), (0x59, 0x0A), (0x5A, 0x1B), (0x5B, 0x19), (0x5D, 0x7F),
|
||||||
|
(0x5E, 0x56), (0x5F, 0x43), (0x60, 0x37), (0x61, 0x33), (0x62, 0x25), (0x63, 0x2A), (0x64, 0x16), (0x65, 0x30),
|
||||||
|
(0x66, 0x2F), (0x67, 0x32), (0x68, 0x53), (0x69, 0x43), (0x6A, 0x4C), (0x6B, 0x40), (0x6C, 0x3D), (0x6D, 0x31),
|
||||||
|
(0x6E, 0x20), (0x6F, 0x0F), (0x70, 0x7F), (0x71, 0x56), (0x72, 0x43), (0x73, 0x37), (0x74, 0x33), (0x75, 0x25),
|
||||||
|
(0x76, 0x2A), (0x77, 0x16), (0x78, 0x30), (0x79, 0x2F), (0x7A, 0x32), (0x7B, 0x53), (0x7C, 0x43), (0x7D, 0x4C),
|
||||||
|
(0x7E, 0x40), (0x7F, 0x3D), (0x80, 0x31), (0x81, 0x20), (0x82, 0x0F),
|
||||||
|
(0xE0, 0x02), # select page 2
|
||||||
|
(0x00, 0x5F), (0x01, 0x5F), (0x02, 0x5E), (0x03, 0x5E), (0x04, 0x50), (0x05, 0x48), (0x06, 0x48), (0x07, 0x4A),
|
||||||
|
(0x08, 0x4A), (0x09, 0x44), (0x0A, 0x44), (0x0B, 0x46), (0x0C, 0x46), (0x0D, 0x5F), (0x0E, 0x5F), (0x0F, 0x57),
|
||||||
|
(0x10, 0x57), (0x11, 0x77), (0x12, 0x77), (0x13, 0x40), (0x14, 0x42), (0x15, 0x5F), (0x16, 0x5F), (0x17, 0x5F),
|
||||||
|
(0x18, 0x5E), (0x19, 0x5E), (0x1A, 0x50), (0x1B, 0x49), (0x1C, 0x49), (0x1D, 0x4B), (0x1E, 0x4B), (0x1F, 0x45),
|
||||||
|
(0x20, 0x45), (0x21, 0x47), (0x22, 0x47), (0x23, 0x5F), (0x24, 0x5F), (0x25, 0x57), (0x26, 0x57), (0x27, 0x77),
|
||||||
|
(0x28, 0x77), (0x29, 0x41), (0x2A, 0x43), (0x2B, 0x5F), (0x2C, 0x1E), (0x2D, 0x1E), (0x2E, 0x1F), (0x2F, 0x1F),
|
||||||
|
(0x30, 0x10), (0x31, 0x07), (0x32, 0x07), (0x33, 0x05), (0x34, 0x05), (0x35, 0x0B), (0x36, 0x0B), (0x37, 0x09),
|
||||||
|
(0x38, 0x09), (0x39, 0x1F), (0x3A, 0x1F), (0x3B, 0x17), (0x3C, 0x17), (0x3D, 0x17), (0x3E, 0x17), (0x3F, 0x03),
|
||||||
|
(0x40, 0x01), (0x41, 0x1F), (0x42, 0x1E), (0x43, 0x1E), (0x44, 0x1F), (0x45, 0x1F), (0x46, 0x10), (0x47, 0x06),
|
||||||
|
(0x48, 0x06), (0x49, 0x04), (0x4A, 0x04), (0x4B, 0x0A), (0x4C, 0x0A), (0x4D, 0x08), (0x4E, 0x08), (0x4F, 0x1F),
|
||||||
|
(0x50, 0x1F), (0x51, 0x17), (0x52, 0x17), (0x53, 0x17), (0x54, 0x17), (0x55, 0x02), (0x56, 0x00), (0x57, 0x1F),
|
||||||
|
(0xE0, 0x02), # select page 2
|
||||||
|
(0x58, 0x40), (0x59, 0x00), (0x5A, 0x00), (0x5B, 0x30), (0x5C, 0x01), (0x5D, 0x30), (0x5E, 0x01), (0x5F, 0x02),
|
||||||
|
(0x60, 0x30), (0x61, 0x03), (0x62, 0x04), (0x63, 0x04), (0x64, 0xA6), (0x65, 0x43), (0x66, 0x30), (0x67, 0x73),
|
||||||
|
(0x68, 0x05), (0x69, 0x04), (0x6A, 0x7F), (0x6B, 0x08), (0x6C, 0x00), (0x6D, 0x04), (0x6E, 0x04), (0x6F, 0x88),
|
||||||
|
(0x75, 0xD9), (0x76, 0x00), (0x77, 0x33), (0x78, 0x43),
|
||||||
|
(0xE0, 0x00), # select userpage
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ from esphome.components.const import (
|
|||||||
CONF_DRAW_ROUNDING,
|
CONF_DRAW_ROUNDING,
|
||||||
)
|
)
|
||||||
from esphome.components.display import CONF_SHOW_TEST_CARD
|
from esphome.components.display import CONF_SHOW_TEST_CARD
|
||||||
from esphome.components.esp32 import VARIANT_ESP32S3, only_on_variant
|
from esphome.components.esp32 import VARIANT_ESP32P4, VARIANT_ESP32S3, only_on_variant
|
||||||
from esphome.components.mipi import (
|
from esphome.components.mipi import (
|
||||||
COLOR_ORDERS,
|
COLOR_ORDERS,
|
||||||
CONF_DE_PIN,
|
CONF_DE_PIN,
|
||||||
@@ -225,7 +225,7 @@ def _config_schema(config):
|
|||||||
return cv.All(
|
return cv.All(
|
||||||
schema,
|
schema,
|
||||||
cv.only_on_esp32,
|
cv.only_on_esp32,
|
||||||
only_on_variant(supported=[VARIANT_ESP32S3]),
|
only_on_variant(supported=[VARIANT_ESP32S3, VARIANT_ESP32P4]),
|
||||||
)(config)
|
)(config)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#ifdef USE_ESP32_VARIANT_ESP32S3
|
#if defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32P4)
|
||||||
#include "mipi_rgb.h"
|
#include "mipi_rgb.h"
|
||||||
#include "esphome/core/gpio.h"
|
#include "esphome/core/gpio.h"
|
||||||
#include "esphome/core/hal.h"
|
#include "esphome/core/hal.h"
|
||||||
@@ -401,4 +401,4 @@ void MipiRgb::dump_config() {
|
|||||||
|
|
||||||
} // namespace mipi_rgb
|
} // namespace mipi_rgb
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
#endif // USE_ESP32_VARIANT_ESP32S3
|
#endif // defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32P4)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#ifdef USE_ESP32_VARIANT_ESP32S3
|
#if defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32P4)
|
||||||
#include "esphome/core/gpio.h"
|
#include "esphome/core/gpio.h"
|
||||||
#include "esphome/components/display/display.h"
|
#include "esphome/components/display/display.h"
|
||||||
#include "esp_lcd_panel_ops.h"
|
#include "esp_lcd_panel_ops.h"
|
||||||
@@ -28,7 +28,7 @@ class MipiRgb : public display::Display {
|
|||||||
void setup() override;
|
void setup() override;
|
||||||
void loop() override;
|
void loop() override;
|
||||||
void update() override;
|
void update() override;
|
||||||
void fill(Color color);
|
void fill(Color color) override;
|
||||||
void draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *ptr, display::ColorOrder order,
|
void draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *ptr, display::ColorOrder order,
|
||||||
display::ColorBitness bitness, bool big_endian, int x_offset, int y_offset, int x_pad) override;
|
display::ColorBitness bitness, bool big_endian, int x_offset, int y_offset, int x_pad) override;
|
||||||
void write_to_display_(int x_start, int y_start, int w, int h, const uint8_t *ptr, int x_offset, int y_offset,
|
void write_to_display_(int x_start, int y_start, int w, int h, const uint8_t *ptr, int x_offset, int y_offset,
|
||||||
@@ -115,7 +115,7 @@ class MipiRgbSpi : public MipiRgb,
|
|||||||
void write_command_(uint8_t value);
|
void write_command_(uint8_t value);
|
||||||
void write_data_(uint8_t value);
|
void write_data_(uint8_t value);
|
||||||
void write_init_sequence_();
|
void write_init_sequence_();
|
||||||
void dump_config();
|
void dump_config() override;
|
||||||
|
|
||||||
GPIOPin *dc_pin_{nullptr};
|
GPIOPin *dc_pin_{nullptr};
|
||||||
std::vector<uint8_t> init_sequence_;
|
std::vector<uint8_t> init_sequence_;
|
||||||
|
|||||||
@@ -20,10 +20,10 @@ void Modbus::loop() {
|
|||||||
const uint32_t now = App.get_loop_component_start_time();
|
const uint32_t now = App.get_loop_component_start_time();
|
||||||
|
|
||||||
// Read all available bytes in batches to reduce UART call overhead.
|
// Read all available bytes in batches to reduce UART call overhead.
|
||||||
int avail = this->available();
|
size_t avail = this->available();
|
||||||
uint8_t buf[64];
|
uint8_t buf[64];
|
||||||
while (avail > 0) {
|
while (avail > 0) {
|
||||||
size_t to_read = std::min(static_cast<size_t>(avail), sizeof(buf));
|
size_t to_read = std::min(avail, sizeof(buf));
|
||||||
if (!this->read_array(buf, to_read)) {
|
if (!this->read_array(buf, to_read)) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -228,39 +228,50 @@ void Modbus::send(uint8_t address, uint8_t function_code, uint16_t start_address
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<uint8_t> data;
|
static constexpr size_t ADDR_SIZE = 1;
|
||||||
data.push_back(address);
|
static constexpr size_t FC_SIZE = 1;
|
||||||
data.push_back(function_code);
|
static constexpr size_t START_ADDR_SIZE = 2;
|
||||||
|
static constexpr size_t NUM_ENTITIES_SIZE = 2;
|
||||||
|
static constexpr size_t BYTE_COUNT_SIZE = 1;
|
||||||
|
static constexpr size_t MAX_PAYLOAD_SIZE = std::numeric_limits<uint8_t>::max();
|
||||||
|
static constexpr size_t CRC_SIZE = 2;
|
||||||
|
static constexpr size_t MAX_FRAME_SIZE =
|
||||||
|
ADDR_SIZE + FC_SIZE + START_ADDR_SIZE + NUM_ENTITIES_SIZE + BYTE_COUNT_SIZE + MAX_PAYLOAD_SIZE + CRC_SIZE;
|
||||||
|
uint8_t data[MAX_FRAME_SIZE];
|
||||||
|
size_t pos = 0;
|
||||||
|
|
||||||
|
data[pos++] = address;
|
||||||
|
data[pos++] = function_code;
|
||||||
if (this->role == ModbusRole::CLIENT) {
|
if (this->role == ModbusRole::CLIENT) {
|
||||||
data.push_back(start_address >> 8);
|
data[pos++] = start_address >> 8;
|
||||||
data.push_back(start_address >> 0);
|
data[pos++] = start_address >> 0;
|
||||||
if (function_code != ModbusFunctionCode::WRITE_SINGLE_COIL &&
|
if (function_code != ModbusFunctionCode::WRITE_SINGLE_COIL &&
|
||||||
function_code != ModbusFunctionCode::WRITE_SINGLE_REGISTER) {
|
function_code != ModbusFunctionCode::WRITE_SINGLE_REGISTER) {
|
||||||
data.push_back(number_of_entities >> 8);
|
data[pos++] = number_of_entities >> 8;
|
||||||
data.push_back(number_of_entities >> 0);
|
data[pos++] = number_of_entities >> 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (payload != nullptr) {
|
if (payload != nullptr) {
|
||||||
if (this->role == ModbusRole::SERVER || function_code == ModbusFunctionCode::WRITE_MULTIPLE_COILS ||
|
if (this->role == ModbusRole::SERVER || function_code == ModbusFunctionCode::WRITE_MULTIPLE_COILS ||
|
||||||
function_code == ModbusFunctionCode::WRITE_MULTIPLE_REGISTERS) { // Write multiple
|
function_code == ModbusFunctionCode::WRITE_MULTIPLE_REGISTERS) { // Write multiple
|
||||||
data.push_back(payload_len); // Byte count is required for write
|
data[pos++] = payload_len; // Byte count is required for write
|
||||||
} else {
|
} else {
|
||||||
payload_len = 2; // Write single register or coil
|
payload_len = 2; // Write single register or coil
|
||||||
}
|
}
|
||||||
for (int i = 0; i < payload_len; i++) {
|
for (int i = 0; i < payload_len; i++) {
|
||||||
data.push_back(payload[i]);
|
data[pos++] = payload[i];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto crc = crc16(data.data(), data.size());
|
auto crc = crc16(data, pos);
|
||||||
data.push_back(crc >> 0);
|
data[pos++] = crc >> 0;
|
||||||
data.push_back(crc >> 8);
|
data[pos++] = crc >> 8;
|
||||||
|
|
||||||
if (this->flow_control_pin_ != nullptr)
|
if (this->flow_control_pin_ != nullptr)
|
||||||
this->flow_control_pin_->digital_write(true);
|
this->flow_control_pin_->digital_write(true);
|
||||||
|
|
||||||
this->write_array(data);
|
this->write_array(data, pos);
|
||||||
this->flush();
|
this->flush();
|
||||||
|
|
||||||
if (this->flow_control_pin_ != nullptr)
|
if (this->flow_control_pin_ != nullptr)
|
||||||
@@ -270,7 +281,7 @@ void Modbus::send(uint8_t address, uint8_t function_code, uint16_t start_address
|
|||||||
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
|
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
|
||||||
char hex_buf[format_hex_pretty_size(MODBUS_MAX_LOG_BYTES)];
|
char hex_buf[format_hex_pretty_size(MODBUS_MAX_LOG_BYTES)];
|
||||||
#endif
|
#endif
|
||||||
ESP_LOGV(TAG, "Modbus write: %s", format_hex_pretty_to(hex_buf, data.data(), data.size()));
|
ESP_LOGV(TAG, "Modbus write: %s", format_hex_pretty_to(hex_buf, data, pos));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper function for lambdas
|
// Helper function for lambdas
|
||||||
|
|||||||
@@ -398,10 +398,10 @@ bool Nextion::remove_from_q_(bool report_empty) {
|
|||||||
|
|
||||||
void Nextion::process_serial_() {
|
void Nextion::process_serial_() {
|
||||||
// Read all available bytes in batches to reduce UART call overhead.
|
// Read all available bytes in batches to reduce UART call overhead.
|
||||||
int avail = this->available();
|
size_t avail = this->available();
|
||||||
uint8_t buf[64];
|
uint8_t buf[64];
|
||||||
while (avail > 0) {
|
while (avail > 0) {
|
||||||
size_t to_read = std::min(static_cast<size_t>(avail), sizeof(buf));
|
size_t to_read = std::min(avail, sizeof(buf));
|
||||||
if (!this->read_array(buf, to_read)) {
|
if (!this->read_array(buf, to_read)) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,9 +14,9 @@ void Pipsolar::setup() {
|
|||||||
|
|
||||||
void Pipsolar::empty_uart_buffer_() {
|
void Pipsolar::empty_uart_buffer_() {
|
||||||
uint8_t buf[64];
|
uint8_t buf[64];
|
||||||
int avail;
|
size_t avail;
|
||||||
while ((avail = this->available()) > 0) {
|
while ((avail = this->available()) > 0) {
|
||||||
if (!this->read_array(buf, std::min(static_cast<size_t>(avail), sizeof(buf)))) {
|
if (!this->read_array(buf, std::min(avail, sizeof(buf)))) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -97,10 +97,10 @@ void Pipsolar::loop() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this->state_ == STATE_COMMAND || this->state_ == STATE_POLL) {
|
if (this->state_ == STATE_COMMAND || this->state_ == STATE_POLL) {
|
||||||
int avail = this->available();
|
size_t avail = this->available();
|
||||||
while (avail > 0) {
|
while (avail > 0) {
|
||||||
uint8_t buf[64];
|
uint8_t buf[64];
|
||||||
size_t to_read = std::min(static_cast<size_t>(avail), sizeof(buf));
|
size_t to_read = std::min(avail, sizeof(buf));
|
||||||
if (!this->read_array(buf, to_read)) {
|
if (!this->read_array(buf, to_read)) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,14 +56,14 @@ void PylontechComponent::setup() {
|
|||||||
void PylontechComponent::update() { this->write_str("pwr\n"); }
|
void PylontechComponent::update() { this->write_str("pwr\n"); }
|
||||||
|
|
||||||
void PylontechComponent::loop() {
|
void PylontechComponent::loop() {
|
||||||
int avail = this->available();
|
size_t avail = this->available();
|
||||||
if (avail > 0) {
|
if (avail > 0) {
|
||||||
// pylontech sends a lot of data very suddenly
|
// pylontech sends a lot of data very suddenly
|
||||||
// we need to quickly put it all into our own buffer, otherwise the uart's buffer will overflow
|
// we need to quickly put it all into our own buffer, otherwise the uart's buffer will overflow
|
||||||
int recv = 0;
|
int recv = 0;
|
||||||
uint8_t buf[64];
|
uint8_t buf[64];
|
||||||
while (avail > 0) {
|
while (avail > 0) {
|
||||||
size_t to_read = std::min(static_cast<size_t>(avail), sizeof(buf));
|
size_t to_read = std::min(avail, sizeof(buf));
|
||||||
if (!this->read_array(buf, to_read)) {
|
if (!this->read_array(buf, to_read)) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -82,10 +82,10 @@ void RD03DComponent::dump_config() {
|
|||||||
|
|
||||||
void RD03DComponent::loop() {
|
void RD03DComponent::loop() {
|
||||||
// Read all available bytes in batches to reduce UART call overhead.
|
// Read all available bytes in batches to reduce UART call overhead.
|
||||||
int avail = this->available();
|
size_t avail = this->available();
|
||||||
uint8_t buf[64];
|
uint8_t buf[64];
|
||||||
while (avail > 0) {
|
while (avail > 0) {
|
||||||
size_t to_read = std::min(static_cast<size_t>(avail), sizeof(buf));
|
size_t to_read = std::min(avail, sizeof(buf));
|
||||||
if (!this->read_array(buf, to_read)) {
|
if (!this->read_array(buf, to_read)) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -136,10 +136,10 @@ void RFBridgeComponent::loop() {
|
|||||||
this->last_bridge_byte_ = now;
|
this->last_bridge_byte_ = now;
|
||||||
}
|
}
|
||||||
|
|
||||||
int avail = this->available();
|
size_t avail = this->available();
|
||||||
while (avail > 0) {
|
while (avail > 0) {
|
||||||
uint8_t buf[64];
|
uint8_t buf[64];
|
||||||
size_t to_read = std::min(static_cast<size_t>(avail), sizeof(buf));
|
size_t to_read = std::min(avail, sizeof(buf));
|
||||||
if (!this->read_array(buf, to_read)) {
|
if (!this->read_array(buf, to_read)) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -107,10 +107,10 @@ void MR24HPC1Component::update_() {
|
|||||||
// main loop
|
// main loop
|
||||||
void MR24HPC1Component::loop() {
|
void MR24HPC1Component::loop() {
|
||||||
// Read all available bytes in batches to reduce UART call overhead.
|
// Read all available bytes in batches to reduce UART call overhead.
|
||||||
int avail = this->available();
|
size_t avail = this->available();
|
||||||
uint8_t buf[64];
|
uint8_t buf[64];
|
||||||
while (avail > 0) {
|
while (avail > 0) {
|
||||||
size_t to_read = std::min(static_cast<size_t>(avail), sizeof(buf));
|
size_t to_read = std::min(avail, sizeof(buf));
|
||||||
if (!this->read_array(buf, to_read)) {
|
if (!this->read_array(buf, to_read)) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,10 +31,10 @@ void MR60BHA2Component::dump_config() {
|
|||||||
// main loop
|
// main loop
|
||||||
void MR60BHA2Component::loop() {
|
void MR60BHA2Component::loop() {
|
||||||
// Read all available bytes in batches to reduce UART call overhead.
|
// Read all available bytes in batches to reduce UART call overhead.
|
||||||
int avail = this->available();
|
size_t avail = this->available();
|
||||||
uint8_t buf[64];
|
uint8_t buf[64];
|
||||||
while (avail > 0) {
|
while (avail > 0) {
|
||||||
size_t to_read = std::min(static_cast<size_t>(avail), sizeof(buf));
|
size_t to_read = std::min(avail, sizeof(buf));
|
||||||
if (!this->read_array(buf, to_read)) {
|
if (!this->read_array(buf, to_read)) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,10 +50,10 @@ void MR60FDA2Component::setup() {
|
|||||||
// main loop
|
// main loop
|
||||||
void MR60FDA2Component::loop() {
|
void MR60FDA2Component::loop() {
|
||||||
// Read all available bytes in batches to reduce UART call overhead.
|
// Read all available bytes in batches to reduce UART call overhead.
|
||||||
int avail = this->available();
|
size_t avail = this->available();
|
||||||
uint8_t buf[64];
|
uint8_t buf[64];
|
||||||
while (avail > 0) {
|
while (avail > 0) {
|
||||||
size_t to_read = std::min(static_cast<size_t>(avail), sizeof(buf));
|
size_t to_read = std::min(avail, sizeof(buf));
|
||||||
if (!this->read_array(buf, to_read)) {
|
if (!this->read_array(buf, to_read)) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,11 @@ namespace esphome::sensor {
|
|||||||
|
|
||||||
static const char *const TAG = "sensor.filter";
|
static const char *const TAG = "sensor.filter";
|
||||||
|
|
||||||
|
// Filter scheduler IDs.
|
||||||
|
// Each filter is its own Component instance, so the scheduler scopes
|
||||||
|
// IDs by component pointer — no risk of collisions between instances.
|
||||||
|
constexpr uint32_t FILTER_ID = 0;
|
||||||
|
|
||||||
// Filter
|
// Filter
|
||||||
void Filter::input(float value) {
|
void Filter::input(float value) {
|
||||||
ESP_LOGVV(TAG, "Filter(%p)::input(%f)", this, value);
|
ESP_LOGVV(TAG, "Filter(%p)::input(%f)", this, value);
|
||||||
@@ -191,7 +196,7 @@ optional<float> ThrottleAverageFilter::new_value(float value) {
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
void ThrottleAverageFilter::setup() {
|
void ThrottleAverageFilter::setup() {
|
||||||
this->set_interval("throttle_average", this->time_period_, [this]() {
|
this->set_interval(FILTER_ID, this->time_period_, [this]() {
|
||||||
ESP_LOGVV(TAG, "ThrottleAverageFilter(%p)::interval(sum=%f, n=%i)", this, this->sum_, this->n_);
|
ESP_LOGVV(TAG, "ThrottleAverageFilter(%p)::interval(sum=%f, n=%i)", this, this->sum_, this->n_);
|
||||||
if (this->n_ == 0) {
|
if (this->n_ == 0) {
|
||||||
if (this->have_nan_)
|
if (this->have_nan_)
|
||||||
@@ -383,7 +388,7 @@ optional<float> TimeoutFilterConfigured::new_value(float value) {
|
|||||||
|
|
||||||
// DebounceFilter
|
// DebounceFilter
|
||||||
optional<float> DebounceFilter::new_value(float value) {
|
optional<float> DebounceFilter::new_value(float value) {
|
||||||
this->set_timeout("debounce", this->time_period_, [this, value]() { this->output(value); });
|
this->set_timeout(FILTER_ID, this->time_period_, [this, value]() { this->output(value); });
|
||||||
|
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
@@ -406,7 +411,7 @@ optional<float> HeartbeatFilter::new_value(float value) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void HeartbeatFilter::setup() {
|
void HeartbeatFilter::setup() {
|
||||||
this->set_interval("heartbeat", this->time_period_, [this]() {
|
this->set_interval(FILTER_ID, this->time_period_, [this]() {
|
||||||
ESP_LOGVV(TAG, "HeartbeatFilter(%p)::interval(has_value=%s, last_input=%f)", this, YESNO(this->has_value_),
|
ESP_LOGVV(TAG, "HeartbeatFilter(%p)::interval(has_value=%s, last_input=%f)", this, YESNO(this->has_value_),
|
||||||
this->last_input_);
|
this->last_input_);
|
||||||
if (!this->has_value_)
|
if (!this->has_value_)
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import esphome.codegen as cg
|
|||||||
from esphome.components import water_heater
|
from esphome.components import water_heater
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.const import (
|
from esphome.const import (
|
||||||
|
CONF_AWAY,
|
||||||
CONF_ID,
|
CONF_ID,
|
||||||
CONF_MODE,
|
CONF_MODE,
|
||||||
CONF_OPTIMISTIC,
|
CONF_OPTIMISTIC,
|
||||||
@@ -18,6 +19,7 @@ from esphome.types import ConfigType
|
|||||||
from .. import template_ns
|
from .. import template_ns
|
||||||
|
|
||||||
CONF_CURRENT_TEMPERATURE = "current_temperature"
|
CONF_CURRENT_TEMPERATURE = "current_temperature"
|
||||||
|
CONF_IS_ON = "is_on"
|
||||||
|
|
||||||
TemplateWaterHeater = template_ns.class_(
|
TemplateWaterHeater = template_ns.class_(
|
||||||
"TemplateWaterHeater", cg.Component, water_heater.WaterHeater
|
"TemplateWaterHeater", cg.Component, water_heater.WaterHeater
|
||||||
@@ -51,6 +53,8 @@ CONFIG_SCHEMA = (
|
|||||||
cv.Optional(CONF_SUPPORTED_MODES): cv.ensure_list(
|
cv.Optional(CONF_SUPPORTED_MODES): cv.ensure_list(
|
||||||
water_heater.validate_water_heater_mode
|
water_heater.validate_water_heater_mode
|
||||||
),
|
),
|
||||||
|
cv.Optional(CONF_AWAY): cv.returning_lambda,
|
||||||
|
cv.Optional(CONF_IS_ON): cv.returning_lambda,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.extend(cv.COMPONENT_SCHEMA)
|
.extend(cv.COMPONENT_SCHEMA)
|
||||||
@@ -98,6 +102,22 @@ async def to_code(config: ConfigType) -> None:
|
|||||||
if CONF_SUPPORTED_MODES in config:
|
if CONF_SUPPORTED_MODES in config:
|
||||||
cg.add(var.set_supported_modes(config[CONF_SUPPORTED_MODES]))
|
cg.add(var.set_supported_modes(config[CONF_SUPPORTED_MODES]))
|
||||||
|
|
||||||
|
if CONF_AWAY in config:
|
||||||
|
template_ = await cg.process_lambda(
|
||||||
|
config[CONF_AWAY],
|
||||||
|
[],
|
||||||
|
return_type=cg.optional.template(bool),
|
||||||
|
)
|
||||||
|
cg.add(var.set_away_lambda(template_))
|
||||||
|
|
||||||
|
if CONF_IS_ON in config:
|
||||||
|
template_ = await cg.process_lambda(
|
||||||
|
config[CONF_IS_ON],
|
||||||
|
[],
|
||||||
|
return_type=cg.optional.template(bool),
|
||||||
|
)
|
||||||
|
cg.add(var.set_is_on_lambda(template_))
|
||||||
|
|
||||||
|
|
||||||
@automation.register_action(
|
@automation.register_action(
|
||||||
"water_heater.template.publish",
|
"water_heater.template.publish",
|
||||||
@@ -110,6 +130,8 @@ async def to_code(config: ConfigType) -> None:
|
|||||||
cv.Optional(CONF_MODE): cv.templatable(
|
cv.Optional(CONF_MODE): cv.templatable(
|
||||||
water_heater.validate_water_heater_mode
|
water_heater.validate_water_heater_mode
|
||||||
),
|
),
|
||||||
|
cv.Optional(CONF_AWAY): cv.templatable(cv.boolean),
|
||||||
|
cv.Optional(CONF_IS_ON): cv.templatable(cv.boolean),
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -134,4 +156,12 @@ async def water_heater_template_publish_to_code(
|
|||||||
template_ = await cg.templatable(mode, args, water_heater.WaterHeaterMode)
|
template_ = await cg.templatable(mode, args, water_heater.WaterHeaterMode)
|
||||||
cg.add(var.set_mode(template_))
|
cg.add(var.set_mode(template_))
|
||||||
|
|
||||||
|
if CONF_AWAY in config:
|
||||||
|
template_ = await cg.templatable(config[CONF_AWAY], args, bool)
|
||||||
|
cg.add(var.set_away(template_))
|
||||||
|
|
||||||
|
if CONF_IS_ON in config:
|
||||||
|
template_ = await cg.templatable(config[CONF_IS_ON], args, bool)
|
||||||
|
cg.add(var.set_is_on(template_))
|
||||||
|
|
||||||
return var
|
return var
|
||||||
|
|||||||
@@ -11,12 +11,15 @@ class TemplateWaterHeaterPublishAction : public Action<Ts...>, public Parented<T
|
|||||||
TEMPLATABLE_VALUE(float, current_temperature)
|
TEMPLATABLE_VALUE(float, current_temperature)
|
||||||
TEMPLATABLE_VALUE(float, target_temperature)
|
TEMPLATABLE_VALUE(float, target_temperature)
|
||||||
TEMPLATABLE_VALUE(water_heater::WaterHeaterMode, mode)
|
TEMPLATABLE_VALUE(water_heater::WaterHeaterMode, mode)
|
||||||
|
TEMPLATABLE_VALUE(bool, away)
|
||||||
|
TEMPLATABLE_VALUE(bool, is_on)
|
||||||
|
|
||||||
void play(const Ts &...x) override {
|
void play(const Ts &...x) override {
|
||||||
if (this->current_temperature_.has_value()) {
|
if (this->current_temperature_.has_value()) {
|
||||||
this->parent_->set_current_temperature(this->current_temperature_.value(x...));
|
this->parent_->set_current_temperature(this->current_temperature_.value(x...));
|
||||||
}
|
}
|
||||||
bool needs_call = this->target_temperature_.has_value() || this->mode_.has_value();
|
bool needs_call = this->target_temperature_.has_value() || this->mode_.has_value() || this->away_.has_value() ||
|
||||||
|
this->is_on_.has_value();
|
||||||
if (needs_call) {
|
if (needs_call) {
|
||||||
auto call = this->parent_->make_call();
|
auto call = this->parent_->make_call();
|
||||||
if (this->target_temperature_.has_value()) {
|
if (this->target_temperature_.has_value()) {
|
||||||
@@ -25,6 +28,12 @@ class TemplateWaterHeaterPublishAction : public Action<Ts...>, public Parented<T
|
|||||||
if (this->mode_.has_value()) {
|
if (this->mode_.has_value()) {
|
||||||
call.set_mode(this->mode_.value(x...));
|
call.set_mode(this->mode_.value(x...));
|
||||||
}
|
}
|
||||||
|
if (this->away_.has_value()) {
|
||||||
|
call.set_away(this->away_.value(x...));
|
||||||
|
}
|
||||||
|
if (this->is_on_.has_value()) {
|
||||||
|
call.set_on(this->is_on_.value(x...));
|
||||||
|
}
|
||||||
call.perform();
|
call.perform();
|
||||||
} else {
|
} else {
|
||||||
this->parent_->publish_state();
|
this->parent_->publish_state();
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ void TemplateWaterHeater::setup() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!this->current_temperature_f_.has_value() && !this->target_temperature_f_.has_value() &&
|
if (!this->current_temperature_f_.has_value() && !this->target_temperature_f_.has_value() &&
|
||||||
!this->mode_f_.has_value())
|
!this->mode_f_.has_value() && !this->away_f_.has_value() && !this->is_on_f_.has_value())
|
||||||
this->disable_loop();
|
this->disable_loop();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,6 +32,12 @@ water_heater::WaterHeaterTraits TemplateWaterHeater::traits() {
|
|||||||
if (this->target_temperature_f_.has_value()) {
|
if (this->target_temperature_f_.has_value()) {
|
||||||
traits.add_feature_flags(water_heater::WATER_HEATER_SUPPORTS_TARGET_TEMPERATURE);
|
traits.add_feature_flags(water_heater::WATER_HEATER_SUPPORTS_TARGET_TEMPERATURE);
|
||||||
}
|
}
|
||||||
|
if (this->away_f_.has_value()) {
|
||||||
|
traits.set_supports_away_mode(true);
|
||||||
|
}
|
||||||
|
if (this->is_on_f_.has_value()) {
|
||||||
|
traits.add_feature_flags(water_heater::WATER_HEATER_SUPPORTS_ON_OFF);
|
||||||
|
}
|
||||||
return traits;
|
return traits;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,6 +68,22 @@ void TemplateWaterHeater::loop() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto away = this->away_f_.call();
|
||||||
|
if (away.has_value()) {
|
||||||
|
if (*away != this->is_away()) {
|
||||||
|
this->set_state_flag_(water_heater::WATER_HEATER_STATE_AWAY, *away);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto is_on = this->is_on_f_.call();
|
||||||
|
if (is_on.has_value()) {
|
||||||
|
if (*is_on != this->is_on()) {
|
||||||
|
this->set_state_flag_(water_heater::WATER_HEATER_STATE_ON, *is_on);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (changed) {
|
if (changed) {
|
||||||
this->publish_state();
|
this->publish_state();
|
||||||
}
|
}
|
||||||
@@ -90,6 +112,17 @@ void TemplateWaterHeater::control(const water_heater::WaterHeaterCall &call) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (call.get_away().has_value()) {
|
||||||
|
if (this->optimistic_) {
|
||||||
|
this->set_state_flag_(water_heater::WATER_HEATER_STATE_AWAY, *call.get_away());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (call.get_on().has_value()) {
|
||||||
|
if (this->optimistic_) {
|
||||||
|
this->set_state_flag_(water_heater::WATER_HEATER_STATE_ON, *call.get_on());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this->set_trigger_.trigger();
|
this->set_trigger_.trigger();
|
||||||
|
|
||||||
if (this->optimistic_) {
|
if (this->optimistic_) {
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ class TemplateWaterHeater : public Component, public water_heater::WaterHeater {
|
|||||||
this->target_temperature_f_.set(std::forward<F>(f));
|
this->target_temperature_f_.set(std::forward<F>(f));
|
||||||
}
|
}
|
||||||
template<typename F> void set_mode_lambda(F &&f) { this->mode_f_.set(std::forward<F>(f)); }
|
template<typename F> void set_mode_lambda(F &&f) { this->mode_f_.set(std::forward<F>(f)); }
|
||||||
|
template<typename F> void set_away_lambda(F &&f) { this->away_f_.set(std::forward<F>(f)); }
|
||||||
|
template<typename F> void set_is_on_lambda(F &&f) { this->is_on_f_.set(std::forward<F>(f)); }
|
||||||
|
|
||||||
void set_optimistic(bool optimistic) { this->optimistic_ = optimistic; }
|
void set_optimistic(bool optimistic) { this->optimistic_ = optimistic; }
|
||||||
void set_restore_mode(TemplateWaterHeaterRestoreMode restore_mode) { this->restore_mode_ = restore_mode; }
|
void set_restore_mode(TemplateWaterHeaterRestoreMode restore_mode) { this->restore_mode_ = restore_mode; }
|
||||||
@@ -49,6 +51,8 @@ class TemplateWaterHeater : public Component, public water_heater::WaterHeater {
|
|||||||
TemplateLambda<float> current_temperature_f_;
|
TemplateLambda<float> current_temperature_f_;
|
||||||
TemplateLambda<float> target_temperature_f_;
|
TemplateLambda<float> target_temperature_f_;
|
||||||
TemplateLambda<water_heater::WaterHeaterMode> mode_f_;
|
TemplateLambda<water_heater::WaterHeaterMode> mode_f_;
|
||||||
|
TemplateLambda<bool> away_f_;
|
||||||
|
TemplateLambda<bool> is_on_f_;
|
||||||
TemplateWaterHeaterRestoreMode restore_mode_{WATER_HEATER_NO_RESTORE};
|
TemplateWaterHeaterRestoreMode restore_mode_{WATER_HEATER_NO_RESTORE};
|
||||||
water_heater::WaterHeaterModeMask supported_modes_;
|
water_heater::WaterHeaterModeMask supported_modes_;
|
||||||
bool optimistic_{true};
|
bool optimistic_{true};
|
||||||
|
|||||||
@@ -251,7 +251,7 @@ void Tormatic::stop_at_target_() {
|
|||||||
// Read a GateStatus from the unit. The unit only sends messages in response to
|
// Read a GateStatus from the unit. The unit only sends messages in response to
|
||||||
// status requests or commands, so a message needs to be sent first.
|
// status requests or commands, so a message needs to be sent first.
|
||||||
optional<GateStatus> Tormatic::read_gate_status_() {
|
optional<GateStatus> Tormatic::read_gate_status_() {
|
||||||
if (this->available() < static_cast<int>(sizeof(MessageHeader))) {
|
if (this->available() < sizeof(MessageHeader)) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -32,10 +32,10 @@ void Tuya::setup() {
|
|||||||
|
|
||||||
void Tuya::loop() {
|
void Tuya::loop() {
|
||||||
// Read all available bytes in batches to reduce UART call overhead.
|
// Read all available bytes in batches to reduce UART call overhead.
|
||||||
int avail = this->available();
|
size_t avail = this->available();
|
||||||
uint8_t buf[64];
|
uint8_t buf[64];
|
||||||
while (avail > 0) {
|
while (avail > 0) {
|
||||||
size_t to_read = std::min(static_cast<size_t>(avail), sizeof(buf));
|
size_t to_read = std::min(avail, sizeof(buf));
|
||||||
if (!this->read_array(buf, to_read)) {
|
if (!this->read_array(buf, to_read)) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,12 +3,16 @@
|
|||||||
#include "esphome/core/defines.h"
|
#include "esphome/core/defines.h"
|
||||||
#include "esphome/core/helpers.h"
|
#include "esphome/core/helpers.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
#include "esphome/core/progmem.h"
|
||||||
#include <cinttypes>
|
#include <cinttypes>
|
||||||
|
|
||||||
namespace esphome::uart {
|
namespace esphome::uart {
|
||||||
|
|
||||||
static const char *const TAG = "uart";
|
static const char *const TAG = "uart";
|
||||||
|
|
||||||
|
// UART parity strings indexed by UARTParityOptions enum (0-2): NONE, EVEN, ODD
|
||||||
|
PROGMEM_STRING_TABLE(UARTParityStrings, "NONE", "EVEN", "ODD", "UNKNOWN");
|
||||||
|
|
||||||
void UARTDevice::check_uart_settings(uint32_t baud_rate, uint8_t stop_bits, UARTParityOptions parity,
|
void UARTDevice::check_uart_settings(uint32_t baud_rate, uint8_t stop_bits, UARTParityOptions parity,
|
||||||
uint8_t data_bits) {
|
uint8_t data_bits) {
|
||||||
if (this->parent_->get_baud_rate() != baud_rate) {
|
if (this->parent_->get_baud_rate() != baud_rate) {
|
||||||
@@ -30,16 +34,7 @@ void UARTDevice::check_uart_settings(uint32_t baud_rate, uint8_t stop_bits, UART
|
|||||||
}
|
}
|
||||||
|
|
||||||
const LogString *parity_to_str(UARTParityOptions parity) {
|
const LogString *parity_to_str(UARTParityOptions parity) {
|
||||||
switch (parity) {
|
return UARTParityStrings::get_log_str(static_cast<uint8_t>(parity), UARTParityStrings::LAST_INDEX);
|
||||||
case UART_CONFIG_PARITY_NONE:
|
|
||||||
return LOG_STR("NONE");
|
|
||||||
case UART_CONFIG_PARITY_EVEN:
|
|
||||||
return LOG_STR("EVEN");
|
|
||||||
case UART_CONFIG_PARITY_ODD:
|
|
||||||
return LOG_STR("ODD");
|
|
||||||
default:
|
|
||||||
return LOG_STR("UNKNOWN");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace esphome::uart
|
} // namespace esphome::uart
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ class UARTDevice {
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
int available() { return this->parent_->available(); }
|
size_t available() { return this->parent_->available(); }
|
||||||
|
|
||||||
void flush() { this->parent_->flush(); }
|
void flush() { this->parent_->flush(); }
|
||||||
|
|
||||||
|
|||||||
@@ -5,13 +5,13 @@ namespace esphome::uart {
|
|||||||
static const char *const TAG = "uart";
|
static const char *const TAG = "uart";
|
||||||
|
|
||||||
bool UARTComponent::check_read_timeout_(size_t len) {
|
bool UARTComponent::check_read_timeout_(size_t len) {
|
||||||
if (this->available() >= int(len))
|
if (this->available() >= len)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
uint32_t start_time = millis();
|
uint32_t start_time = millis();
|
||||||
while (this->available() < int(len)) {
|
while (this->available() < len) {
|
||||||
if (millis() - start_time > 100) {
|
if (millis() - start_time > 100) {
|
||||||
ESP_LOGE(TAG, "Reading from UART timed out at byte %u!", this->available());
|
ESP_LOGE(TAG, "Reading from UART timed out at byte %zu!", this->available());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
yield();
|
yield();
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ class UARTComponent {
|
|||||||
|
|
||||||
// Pure virtual method to return the number of bytes available for reading.
|
// Pure virtual method to return the number of bytes available for reading.
|
||||||
// @return Number of available bytes.
|
// @return Number of available bytes.
|
||||||
virtual int available() = 0;
|
virtual size_t available() = 0;
|
||||||
|
|
||||||
// Pure virtual method to block until all bytes have been written to the UART bus.
|
// Pure virtual method to block until all bytes have been written to the UART bus.
|
||||||
virtual void flush() = 0;
|
virtual void flush() = 0;
|
||||||
|
|||||||
@@ -206,7 +206,7 @@ bool ESP8266UartComponent::read_array(uint8_t *data, size_t len) {
|
|||||||
#endif
|
#endif
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
int ESP8266UartComponent::available() {
|
size_t ESP8266UartComponent::available() {
|
||||||
if (this->hw_serial_ != nullptr) {
|
if (this->hw_serial_ != nullptr) {
|
||||||
return this->hw_serial_->available();
|
return this->hw_serial_->available();
|
||||||
} else {
|
} else {
|
||||||
@@ -329,11 +329,14 @@ uint8_t ESP8266SoftwareSerial::peek_byte() {
|
|||||||
void ESP8266SoftwareSerial::flush() {
|
void ESP8266SoftwareSerial::flush() {
|
||||||
// Flush is a NO-OP with software serial, all bytes are written immediately.
|
// Flush is a NO-OP with software serial, all bytes are written immediately.
|
||||||
}
|
}
|
||||||
int ESP8266SoftwareSerial::available() {
|
size_t ESP8266SoftwareSerial::available() {
|
||||||
int avail = int(this->rx_in_pos_) - int(this->rx_out_pos_);
|
// Read volatile rx_in_pos_ once to avoid TOCTOU race with ISR.
|
||||||
if (avail < 0)
|
// When in >= out, data is contiguous: [out..in).
|
||||||
return avail + this->rx_buffer_size_;
|
// When in < out, data wraps: [out..buf_size) + [0..in).
|
||||||
return avail;
|
size_t in = this->rx_in_pos_;
|
||||||
|
if (in >= this->rx_out_pos_)
|
||||||
|
return in - this->rx_out_pos_;
|
||||||
|
return this->rx_buffer_size_ - this->rx_out_pos_ + in;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace esphome::uart
|
} // namespace esphome::uart
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ class ESP8266SoftwareSerial {
|
|||||||
|
|
||||||
void write_byte(uint8_t data);
|
void write_byte(uint8_t data);
|
||||||
|
|
||||||
int available();
|
size_t available();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
static void gpio_intr(ESP8266SoftwareSerial *arg);
|
static void gpio_intr(ESP8266SoftwareSerial *arg);
|
||||||
@@ -57,7 +57,7 @@ class ESP8266UartComponent : public UARTComponent, public Component {
|
|||||||
bool peek_byte(uint8_t *data) override;
|
bool peek_byte(uint8_t *data) override;
|
||||||
bool read_array(uint8_t *data, size_t len) override;
|
bool read_array(uint8_t *data, size_t len) override;
|
||||||
|
|
||||||
int available() override;
|
size_t available() override;
|
||||||
void flush() override;
|
void flush() override;
|
||||||
|
|
||||||
uint32_t get_config();
|
uint32_t get_config();
|
||||||
|
|||||||
@@ -338,7 +338,7 @@ bool IDFUARTComponent::read_array(uint8_t *data, size_t len) {
|
|||||||
return read_len == (int32_t) length_to_read;
|
return read_len == (int32_t) length_to_read;
|
||||||
}
|
}
|
||||||
|
|
||||||
int IDFUARTComponent::available() {
|
size_t IDFUARTComponent::available() {
|
||||||
size_t available = 0;
|
size_t available = 0;
|
||||||
esp_err_t err;
|
esp_err_t err;
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ class IDFUARTComponent : public UARTComponent, public Component {
|
|||||||
bool peek_byte(uint8_t *data) override;
|
bool peek_byte(uint8_t *data) override;
|
||||||
bool read_array(uint8_t *data, size_t len) override;
|
bool read_array(uint8_t *data, size_t len) override;
|
||||||
|
|
||||||
int available() override;
|
size_t available() override;
|
||||||
void flush() override;
|
void flush() override;
|
||||||
|
|
||||||
uint8_t get_hw_serial_number() { return this->uart_num_; }
|
uint8_t get_hw_serial_number() { return this->uart_num_; }
|
||||||
|
|||||||
@@ -265,7 +265,7 @@ bool HostUartComponent::read_array(uint8_t *data, size_t len) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
int HostUartComponent::available() {
|
size_t HostUartComponent::available() {
|
||||||
if (this->file_descriptor_ == -1) {
|
if (this->file_descriptor_ == -1) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -275,9 +275,10 @@ int HostUartComponent::available() {
|
|||||||
this->update_error_(strerror(errno));
|
this->update_error_(strerror(errno));
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
size_t result = available;
|
||||||
if (this->has_peek_)
|
if (this->has_peek_)
|
||||||
available++;
|
result++;
|
||||||
return available;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
void HostUartComponent::flush() {
|
void HostUartComponent::flush() {
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ class HostUartComponent : public UARTComponent, public Component {
|
|||||||
void write_array(const uint8_t *data, size_t len) override;
|
void write_array(const uint8_t *data, size_t len) override;
|
||||||
bool peek_byte(uint8_t *data) override;
|
bool peek_byte(uint8_t *data) override;
|
||||||
bool read_array(uint8_t *data, size_t len) override;
|
bool read_array(uint8_t *data, size_t len) override;
|
||||||
int available() override;
|
size_t available() override;
|
||||||
void flush() override;
|
void flush() override;
|
||||||
void set_name(std::string port_name) { port_name_ = port_name; };
|
void set_name(std::string port_name) { port_name_ = port_name; };
|
||||||
|
|
||||||
|
|||||||
@@ -169,7 +169,7 @@ bool LibreTinyUARTComponent::read_array(uint8_t *data, size_t len) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
int LibreTinyUARTComponent::available() { return this->serial_->available(); }
|
size_t LibreTinyUARTComponent::available() { return this->serial_->available(); }
|
||||||
void LibreTinyUARTComponent::flush() {
|
void LibreTinyUARTComponent::flush() {
|
||||||
ESP_LOGVV(TAG, " Flushing");
|
ESP_LOGVV(TAG, " Flushing");
|
||||||
this->serial_->flush();
|
this->serial_->flush();
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ class LibreTinyUARTComponent : public UARTComponent, public Component {
|
|||||||
bool peek_byte(uint8_t *data) override;
|
bool peek_byte(uint8_t *data) override;
|
||||||
bool read_array(uint8_t *data, size_t len) override;
|
bool read_array(uint8_t *data, size_t len) override;
|
||||||
|
|
||||||
int available() override;
|
size_t available() override;
|
||||||
void flush() override;
|
void flush() override;
|
||||||
|
|
||||||
uint16_t get_config();
|
uint16_t get_config();
|
||||||
|
|||||||
@@ -186,7 +186,7 @@ bool RP2040UartComponent::read_array(uint8_t *data, size_t len) {
|
|||||||
#endif
|
#endif
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
int RP2040UartComponent::available() { return this->serial_->available(); }
|
size_t RP2040UartComponent::available() { return this->serial_->available(); }
|
||||||
void RP2040UartComponent::flush() {
|
void RP2040UartComponent::flush() {
|
||||||
ESP_LOGVV(TAG, " Flushing");
|
ESP_LOGVV(TAG, " Flushing");
|
||||||
this->serial_->flush();
|
this->serial_->flush();
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ class RP2040UartComponent : public UARTComponent, public Component {
|
|||||||
bool peek_byte(uint8_t *data) override;
|
bool peek_byte(uint8_t *data) override;
|
||||||
bool read_array(uint8_t *data, size_t len) override;
|
bool read_array(uint8_t *data, size_t len) override;
|
||||||
|
|
||||||
int available() override;
|
size_t available() override;
|
||||||
void flush() override;
|
void flush() override;
|
||||||
|
|
||||||
uint16_t get_config();
|
uint16_t get_config();
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ class USBCDCACMInstance : public uart::UARTComponent, public Parented<USBCDCACMC
|
|||||||
void write_array(const uint8_t *data, size_t len) override;
|
void write_array(const uint8_t *data, size_t len) override;
|
||||||
bool peek_byte(uint8_t *data) override;
|
bool peek_byte(uint8_t *data) override;
|
||||||
bool read_array(uint8_t *data, size_t len) override;
|
bool read_array(uint8_t *data, size_t len) override;
|
||||||
int available() override;
|
size_t available() override;
|
||||||
void flush() override;
|
void flush() override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|||||||
@@ -318,12 +318,12 @@ bool USBCDCACMInstance::read_array(uint8_t *data, size_t len) {
|
|||||||
return bytes_read == original_len;
|
return bytes_read == original_len;
|
||||||
}
|
}
|
||||||
|
|
||||||
int USBCDCACMInstance::available() {
|
size_t USBCDCACMInstance::available() {
|
||||||
UBaseType_t waiting = 0;
|
UBaseType_t waiting = 0;
|
||||||
if (this->usb_rx_ringbuf_ != nullptr) {
|
if (this->usb_rx_ringbuf_ != nullptr) {
|
||||||
vRingbufferGetInfo(this->usb_rx_ringbuf_, nullptr, nullptr, nullptr, nullptr, &waiting);
|
vRingbufferGetInfo(this->usb_rx_ringbuf_, nullptr, nullptr, nullptr, nullptr, &waiting);
|
||||||
}
|
}
|
||||||
return static_cast<int>(waiting) + (this->has_peek_ ? 1 : 0);
|
return waiting + (this->has_peek_ ? 1 : 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void USBCDCACMInstance::flush() {
|
void USBCDCACMInstance::flush() {
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ class USBUartChannel : public uart::UARTComponent, public Parented<USBUartCompon
|
|||||||
bool peek_byte(uint8_t *data) override;
|
bool peek_byte(uint8_t *data) override;
|
||||||
;
|
;
|
||||||
bool read_array(uint8_t *data, size_t len) override;
|
bool read_array(uint8_t *data, size_t len) override;
|
||||||
int available() override { return static_cast<int>(this->input_buffer_.get_available()); }
|
size_t available() override { return this->input_buffer_.get_available(); }
|
||||||
void flush() override {}
|
void flush() override {}
|
||||||
void check_logger_conflict() override {}
|
void check_logger_conflict() override {}
|
||||||
void set_parity(UARTParityOptions parity) { this->parity_ = parity; }
|
void set_parity(UARTParityOptions parity) { this->parity_ = parity; }
|
||||||
|
|||||||
@@ -371,7 +371,12 @@ async def to_code(config):
|
|||||||
if on_timer_tick := config.get(CONF_ON_TIMER_TICK):
|
if on_timer_tick := config.get(CONF_ON_TIMER_TICK):
|
||||||
await automation.build_automation(
|
await automation.build_automation(
|
||||||
var.get_timer_tick_trigger(),
|
var.get_timer_tick_trigger(),
|
||||||
[(cg.std_vector.template(Timer), "timers")],
|
[
|
||||||
|
(
|
||||||
|
cg.std_vector.template(Timer).operator("const").operator("ref"),
|
||||||
|
"timers",
|
||||||
|
)
|
||||||
|
],
|
||||||
on_timer_tick,
|
on_timer_tick,
|
||||||
)
|
)
|
||||||
has_timers = True
|
has_timers = True
|
||||||
|
|||||||
@@ -859,35 +859,43 @@ void VoiceAssistant::on_audio(const api::VoiceAssistantAudio &msg) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void VoiceAssistant::on_timer_event(const api::VoiceAssistantTimerEventResponse &msg) {
|
void VoiceAssistant::on_timer_event(const api::VoiceAssistantTimerEventResponse &msg) {
|
||||||
Timer timer = {
|
// Find existing timer or add a new one
|
||||||
.id = msg.timer_id,
|
auto it = this->timers_.begin();
|
||||||
.name = msg.name,
|
for (; it != this->timers_.end(); ++it) {
|
||||||
.total_seconds = msg.total_seconds,
|
if (it->id == msg.timer_id)
|
||||||
.seconds_left = msg.seconds_left,
|
break;
|
||||||
.is_active = msg.is_active,
|
}
|
||||||
};
|
if (it == this->timers_.end()) {
|
||||||
this->timers_[timer.id] = timer;
|
this->timers_.push_back({});
|
||||||
|
it = this->timers_.end() - 1;
|
||||||
|
}
|
||||||
|
it->id = msg.timer_id;
|
||||||
|
it->name = msg.name;
|
||||||
|
it->total_seconds = msg.total_seconds;
|
||||||
|
it->seconds_left = msg.seconds_left;
|
||||||
|
it->is_active = msg.is_active;
|
||||||
|
|
||||||
char timer_buf[Timer::TO_STR_BUFFER_SIZE];
|
char timer_buf[Timer::TO_STR_BUFFER_SIZE];
|
||||||
ESP_LOGD(TAG,
|
ESP_LOGD(TAG,
|
||||||
"Timer Event\n"
|
"Timer Event\n"
|
||||||
" Type: %" PRId32 "\n"
|
" Type: %" PRId32 "\n"
|
||||||
" %s",
|
" %s",
|
||||||
msg.event_type, timer.to_str(timer_buf));
|
msg.event_type, it->to_str(timer_buf));
|
||||||
|
|
||||||
switch (msg.event_type) {
|
switch (msg.event_type) {
|
||||||
case api::enums::VOICE_ASSISTANT_TIMER_STARTED:
|
case api::enums::VOICE_ASSISTANT_TIMER_STARTED:
|
||||||
this->timer_started_trigger_.trigger(timer);
|
this->timer_started_trigger_.trigger(*it);
|
||||||
break;
|
break;
|
||||||
case api::enums::VOICE_ASSISTANT_TIMER_UPDATED:
|
case api::enums::VOICE_ASSISTANT_TIMER_UPDATED:
|
||||||
this->timer_updated_trigger_.trigger(timer);
|
this->timer_updated_trigger_.trigger(*it);
|
||||||
break;
|
break;
|
||||||
case api::enums::VOICE_ASSISTANT_TIMER_CANCELLED:
|
case api::enums::VOICE_ASSISTANT_TIMER_CANCELLED:
|
||||||
this->timer_cancelled_trigger_.trigger(timer);
|
this->timer_cancelled_trigger_.trigger(*it);
|
||||||
this->timers_.erase(timer.id);
|
this->timers_.erase(it);
|
||||||
break;
|
break;
|
||||||
case api::enums::VOICE_ASSISTANT_TIMER_FINISHED:
|
case api::enums::VOICE_ASSISTANT_TIMER_FINISHED:
|
||||||
this->timer_finished_trigger_.trigger(timer);
|
this->timer_finished_trigger_.trigger(*it);
|
||||||
this->timers_.erase(timer.id);
|
this->timers_.erase(it);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -901,16 +909,12 @@ void VoiceAssistant::on_timer_event(const api::VoiceAssistantTimerEventResponse
|
|||||||
}
|
}
|
||||||
|
|
||||||
void VoiceAssistant::timer_tick_() {
|
void VoiceAssistant::timer_tick_() {
|
||||||
std::vector<Timer> res;
|
for (auto &timer : this->timers_) {
|
||||||
res.reserve(this->timers_.size());
|
|
||||||
for (auto &pair : this->timers_) {
|
|
||||||
auto &timer = pair.second;
|
|
||||||
if (timer.is_active && timer.seconds_left > 0) {
|
if (timer.is_active && timer.seconds_left > 0) {
|
||||||
timer.seconds_left--;
|
timer.seconds_left--;
|
||||||
}
|
}
|
||||||
res.push_back(timer);
|
|
||||||
}
|
}
|
||||||
this->timer_tick_trigger_.trigger(res);
|
this->timer_tick_trigger_.trigger(this->timers_);
|
||||||
}
|
}
|
||||||
|
|
||||||
void VoiceAssistant::on_announce(const api::VoiceAssistantAnnounceRequest &msg) {
|
void VoiceAssistant::on_announce(const api::VoiceAssistantAnnounceRequest &msg) {
|
||||||
|
|||||||
@@ -24,7 +24,6 @@
|
|||||||
#include "esphome/components/socket/socket.h"
|
#include "esphome/components/socket/socket.h"
|
||||||
|
|
||||||
#include <span>
|
#include <span>
|
||||||
#include <unordered_map>
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
@@ -83,7 +82,7 @@ struct Timer {
|
|||||||
}
|
}
|
||||||
// Remove before 2026.8.0
|
// Remove before 2026.8.0
|
||||||
ESPDEPRECATED("Use to_str() instead. Removed in 2026.8.0", "2026.2.0")
|
ESPDEPRECATED("Use to_str() instead. Removed in 2026.8.0", "2026.2.0")
|
||||||
std::string to_string() const {
|
std::string to_string() const { // NOLINT
|
||||||
char buffer[TO_STR_BUFFER_SIZE];
|
char buffer[TO_STR_BUFFER_SIZE];
|
||||||
return this->to_str(buffer);
|
return this->to_str(buffer);
|
||||||
}
|
}
|
||||||
@@ -226,9 +225,9 @@ class VoiceAssistant : public Component {
|
|||||||
Trigger<Timer> *get_timer_updated_trigger() { return &this->timer_updated_trigger_; }
|
Trigger<Timer> *get_timer_updated_trigger() { return &this->timer_updated_trigger_; }
|
||||||
Trigger<Timer> *get_timer_cancelled_trigger() { return &this->timer_cancelled_trigger_; }
|
Trigger<Timer> *get_timer_cancelled_trigger() { return &this->timer_cancelled_trigger_; }
|
||||||
Trigger<Timer> *get_timer_finished_trigger() { return &this->timer_finished_trigger_; }
|
Trigger<Timer> *get_timer_finished_trigger() { return &this->timer_finished_trigger_; }
|
||||||
Trigger<std::vector<Timer>> *get_timer_tick_trigger() { return &this->timer_tick_trigger_; }
|
Trigger<const std::vector<Timer> &> *get_timer_tick_trigger() { return &this->timer_tick_trigger_; }
|
||||||
void set_has_timers(bool has_timers) { this->has_timers_ = has_timers; }
|
void set_has_timers(bool has_timers) { this->has_timers_ = has_timers; }
|
||||||
const std::unordered_map<std::string, Timer> &get_timers() const { return this->timers_; }
|
const std::vector<Timer> &get_timers() const { return this->timers_; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
bool allocate_buffers_();
|
bool allocate_buffers_();
|
||||||
@@ -267,13 +266,13 @@ class VoiceAssistant : public Component {
|
|||||||
|
|
||||||
api::APIConnection *api_client_{nullptr};
|
api::APIConnection *api_client_{nullptr};
|
||||||
|
|
||||||
std::unordered_map<std::string, Timer> timers_;
|
std::vector<Timer> timers_;
|
||||||
void timer_tick_();
|
void timer_tick_();
|
||||||
Trigger<Timer> timer_started_trigger_;
|
Trigger<Timer> timer_started_trigger_;
|
||||||
Trigger<Timer> timer_finished_trigger_;
|
Trigger<Timer> timer_finished_trigger_;
|
||||||
Trigger<Timer> timer_updated_trigger_;
|
Trigger<Timer> timer_updated_trigger_;
|
||||||
Trigger<Timer> timer_cancelled_trigger_;
|
Trigger<Timer> timer_cancelled_trigger_;
|
||||||
Trigger<std::vector<Timer>> timer_tick_trigger_;
|
Trigger<const std::vector<Timer> &> timer_tick_trigger_;
|
||||||
bool has_timers_{false};
|
bool has_timers_{false};
|
||||||
bool timer_tick_running_{false};
|
bool timer_tick_running_{false};
|
||||||
|
|
||||||
|
|||||||
@@ -90,9 +90,22 @@ class WaterHeaterCall {
|
|||||||
float get_target_temperature_low() const { return this->target_temperature_low_; }
|
float get_target_temperature_low() const { return this->target_temperature_low_; }
|
||||||
float get_target_temperature_high() const { return this->target_temperature_high_; }
|
float get_target_temperature_high() const { return this->target_temperature_high_; }
|
||||||
/// Get state flags value
|
/// Get state flags value
|
||||||
|
ESPDEPRECATED("get_state() is deprecated, use get_away() and get_on() instead. (Removed in 2026.8.0)", "2026.2.0")
|
||||||
uint32_t get_state() const { return this->state_; }
|
uint32_t get_state() const { return this->state_; }
|
||||||
/// Get mask of state flags that are being changed
|
|
||||||
uint32_t get_state_mask() const { return this->state_mask_; }
|
optional<bool> get_away() const {
|
||||||
|
if (this->state_mask_ & WATER_HEATER_STATE_AWAY) {
|
||||||
|
return (this->state_ & WATER_HEATER_STATE_AWAY) != 0;
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
optional<bool> get_on() const {
|
||||||
|
if (this->state_mask_ & WATER_HEATER_STATE_ON) {
|
||||||
|
return (this->state_ & WATER_HEATER_STATE_ON) != 0;
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void validate_();
|
void validate_();
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
|
from pathlib import Path
|
||||||
|
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.const import CONF_ID
|
from esphome.const import CONF_ID
|
||||||
from esphome.core import CORE, coroutine_with_priority
|
from esphome.core import CORE, coroutine_with_priority
|
||||||
from esphome.coroutine import CoroPriority
|
from esphome.coroutine import CoroPriority
|
||||||
|
from esphome.helpers import copy_file_if_changed
|
||||||
|
|
||||||
CODEOWNERS = ["@esphome/core"]
|
CODEOWNERS = ["@esphome/core"]
|
||||||
DEPENDENCIES = ["network"]
|
DEPENDENCIES = ["network"]
|
||||||
@@ -49,5 +52,15 @@ async def to_code(config):
|
|||||||
CORE.add_platformio_option(
|
CORE.add_platformio_option(
|
||||||
"lib_ignore", ["ESPAsyncTCP", "AsyncTCP", "AsyncTCP_RP2040W"]
|
"lib_ignore", ["ESPAsyncTCP", "AsyncTCP", "AsyncTCP_RP2040W"]
|
||||||
)
|
)
|
||||||
|
# ESPAsyncWebServer uses Hash library for sha1() on RP2040
|
||||||
|
cg.add_library("Hash", None)
|
||||||
|
# Fix Hash.h include conflict: Crypto-no-arduino (used by dsmr)
|
||||||
|
# provides a Hash.h that shadows the framework's Hash library.
|
||||||
|
# Prepend the framework Hash path so it's found first.
|
||||||
|
copy_file_if_changed(
|
||||||
|
Path(__file__).parent / "fix_rp2040_hash.py.script",
|
||||||
|
CORE.relative_build_path("fix_rp2040_hash.py"),
|
||||||
|
)
|
||||||
|
cg.add_platformio_option("extra_scripts", ["pre:fix_rp2040_hash.py"])
|
||||||
# https://github.com/ESP32Async/ESPAsyncWebServer/blob/main/library.json
|
# https://github.com/ESP32Async/ESPAsyncWebServer/blob/main/library.json
|
||||||
cg.add_library("ESP32Async/ESPAsyncWebServer", "3.9.6")
|
cg.add_library("ESP32Async/ESPAsyncWebServer", "3.9.6")
|
||||||
|
|||||||
11
esphome/components/web_server_base/fix_rp2040_hash.py.script
Normal file
11
esphome/components/web_server_base/fix_rp2040_hash.py.script
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
# ESPAsyncWebServer includes <Hash.h> expecting the Arduino-Pico framework's Hash
|
||||||
|
# library (which provides sha1() functions). However, the Crypto-no-arduino library
|
||||||
|
# (used by dsmr) also provides a Hash.h that can shadow the framework version when
|
||||||
|
# PlatformIO's chain+ LDF mode auto-discovers it as a dependency.
|
||||||
|
# Prepend the framework Hash path to CXXFLAGS so it is found first.
|
||||||
|
import os
|
||||||
|
|
||||||
|
Import("env")
|
||||||
|
framework_dir = env.PioPlatform().get_package_dir("framework-arduinopico")
|
||||||
|
hash_src = os.path.join(framework_dir, "libraries", "Hash", "src")
|
||||||
|
env.Prepend(CXXFLAGS=["-I" + hash_src])
|
||||||
@@ -401,7 +401,7 @@ bool WeikaiChannel::peek_byte(uint8_t *buffer) {
|
|||||||
return this->receive_buffer_.peek(*buffer);
|
return this->receive_buffer_.peek(*buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
int WeikaiChannel::available() {
|
size_t WeikaiChannel::available() {
|
||||||
size_t available = this->receive_buffer_.count();
|
size_t available = this->receive_buffer_.count();
|
||||||
if (!available)
|
if (!available)
|
||||||
available = xfer_fifo_to_buffer_();
|
available = xfer_fifo_to_buffer_();
|
||||||
|
|||||||
@@ -374,7 +374,7 @@ class WeikaiChannel : public uart::UARTComponent {
|
|||||||
|
|
||||||
/// @brief Returns the number of bytes in the receive buffer
|
/// @brief Returns the number of bytes in the receive buffer
|
||||||
/// @return the number of bytes available in the receiver fifo
|
/// @return the number of bytes available in the receiver fifo
|
||||||
int available() override;
|
size_t available() override;
|
||||||
|
|
||||||
/// @brief Flush the output fifo.
|
/// @brief Flush the output fifo.
|
||||||
/// @details If we refer to Serial.flush() in Arduino it says: ** Waits for the transmission of outgoing serial data
|
/// @details If we refer to Serial.flush() in Arduino it says: ** Waits for the transmission of outgoing serial data
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
#include <zephyr/settings/settings.h>
|
#include <zephyr/settings/settings.h>
|
||||||
#include <zephyr/storage/flash_map.h>
|
#include <zephyr/storage/flash_map.h>
|
||||||
|
#include "esphome/core/hal.h"
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#include <zboss_api.h>
|
#include <zboss_api.h>
|
||||||
@@ -223,6 +224,7 @@ void ZigbeeComponent::dump_config() {
|
|||||||
get_wipe_on_boot(), YESNO(zb_zdo_joined()), zb_get_current_channel(), zb_get_current_page(),
|
get_wipe_on_boot(), YESNO(zb_zdo_joined()), zb_get_current_channel(), zb_get_current_page(),
|
||||||
zb_get_sleep_threshold(), role(), ieee_addr_buf, zb_get_short_address(), extended_pan_id_buf,
|
zb_get_sleep_threshold(), role(), ieee_addr_buf, zb_get_short_address(), extended_pan_id_buf,
|
||||||
zb_get_pan_id());
|
zb_get_pan_id());
|
||||||
|
dump_reporting_();
|
||||||
}
|
}
|
||||||
|
|
||||||
static void send_attribute_report(zb_bufid_t bufid, zb_uint16_t cmd_id) {
|
static void send_attribute_report(zb_bufid_t bufid, zb_uint16_t cmd_id) {
|
||||||
@@ -244,6 +246,33 @@ void ZigbeeComponent::factory_reset() {
|
|||||||
ZB_SCHEDULE_APP_CALLBACK(zb_bdb_reset_via_local_action, 0);
|
ZB_SCHEDULE_APP_CALLBACK(zb_bdb_reset_via_local_action, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ZigbeeComponent::dump_reporting_() {
|
||||||
|
#ifdef ESPHOME_LOG_HAS_VERBOSE
|
||||||
|
auto now = millis();
|
||||||
|
bool first = true;
|
||||||
|
for (zb_uint8_t j = 0; j < ZCL_CTX().device_ctx->ep_count; j++) {
|
||||||
|
if (ZCL_CTX().device_ctx->ep_desc_list[j]->reporting_info) {
|
||||||
|
zb_zcl_reporting_info_t *rep_info = ZCL_CTX().device_ctx->ep_desc_list[j]->reporting_info;
|
||||||
|
for (zb_uint8_t i = 0; i < ZCL_CTX().device_ctx->ep_desc_list[j]->rep_info_count; i++) {
|
||||||
|
if (!first) {
|
||||||
|
ESP_LOGV(TAG, "");
|
||||||
|
}
|
||||||
|
first = false;
|
||||||
|
ESP_LOGV(TAG, "Endpoint: %d, cluster_id %d, attr_id %d, flags %d, report in %ums", rep_info->ep,
|
||||||
|
rep_info->cluster_id, rep_info->attr_id, rep_info->flags,
|
||||||
|
ZB_ZCL_GET_REPORTING_FLAG(rep_info, ZB_ZCL_REPORT_TIMER_STARTED)
|
||||||
|
? ZB_TIME_BEACON_INTERVAL_TO_MSEC(rep_info->run_time) - now
|
||||||
|
: 0);
|
||||||
|
ESP_LOGV(TAG, "Min_interval %ds, max_interval %ds, def_min_interval %ds, def_max_interval %ds",
|
||||||
|
rep_info->u.send_info.min_interval, rep_info->u.send_info.max_interval,
|
||||||
|
rep_info->u.send_info.def_min_interval, rep_info->u.send_info.def_max_interval);
|
||||||
|
rep_info++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace esphome::zigbee
|
} // namespace esphome::zigbee
|
||||||
|
|
||||||
extern "C" void zboss_signal_handler(zb_uint8_t param) {
|
extern "C" void zboss_signal_handler(zb_uint8_t param) {
|
||||||
|
|||||||
@@ -87,6 +87,7 @@ class ZigbeeComponent : public Component {
|
|||||||
#ifdef USE_ZIGBEE_WIPE_ON_BOOT
|
#ifdef USE_ZIGBEE_WIPE_ON_BOOT
|
||||||
void erase_flash_(int area);
|
void erase_flash_(int area);
|
||||||
#endif
|
#endif
|
||||||
|
void dump_reporting_();
|
||||||
std::array<std::function<void(zb_bufid_t bufid)>, ZIGBEE_ENDPOINTS_COUNT> callbacks_{};
|
std::array<std::function<void(zb_bufid_t bufid)>, ZIGBEE_ENDPOINTS_COUNT> callbacks_{};
|
||||||
CallbackManager<void()> join_cb_;
|
CallbackManager<void()> join_cb_;
|
||||||
Trigger<> join_trigger_;
|
Trigger<> join_trigger_;
|
||||||
|
|||||||
@@ -191,15 +191,17 @@ template<typename... Ts> class DelayAction : public Action<Ts...>, public Compon
|
|||||||
// instead of std::bind to avoid bind overhead (~16 bytes heap + faster execution)
|
// instead of std::bind to avoid bind overhead (~16 bytes heap + faster execution)
|
||||||
if constexpr (sizeof...(Ts) == 0) {
|
if constexpr (sizeof...(Ts) == 0) {
|
||||||
App.scheduler.set_timer_common_(
|
App.scheduler.set_timer_common_(
|
||||||
this, Scheduler::SchedulerItem::TIMEOUT, Scheduler::NameType::STATIC_STRING, "delay", 0, this->delay_.value(),
|
this, Scheduler::SchedulerItem::TIMEOUT, Scheduler::NameType::NUMERIC_ID_INTERNAL, nullptr,
|
||||||
|
static_cast<uint32_t>(InternalSchedulerID::DELAY_ACTION), this->delay_.value(),
|
||||||
[this]() { this->play_next_(); },
|
[this]() { this->play_next_(); },
|
||||||
/* is_retry= */ false, /* skip_cancel= */ this->num_running_ > 1);
|
/* is_retry= */ false, /* skip_cancel= */ this->num_running_ > 1);
|
||||||
} else {
|
} else {
|
||||||
// For delays with arguments, use std::bind to preserve argument values
|
// For delays with arguments, use std::bind to preserve argument values
|
||||||
// Arguments must be copied because original references may be invalid after delay
|
// Arguments must be copied because original references may be invalid after delay
|
||||||
auto f = std::bind(&DelayAction<Ts...>::play_next_, this, x...);
|
auto f = std::bind(&DelayAction<Ts...>::play_next_, this, x...);
|
||||||
App.scheduler.set_timer_common_(this, Scheduler::SchedulerItem::TIMEOUT, Scheduler::NameType::STATIC_STRING,
|
App.scheduler.set_timer_common_(this, Scheduler::SchedulerItem::TIMEOUT, Scheduler::NameType::NUMERIC_ID_INTERNAL,
|
||||||
"delay", 0, this->delay_.value(x...), std::move(f),
|
nullptr, static_cast<uint32_t>(InternalSchedulerID::DELAY_ACTION),
|
||||||
|
this->delay_.value(x...), std::move(f),
|
||||||
/* is_retry= */ false, /* skip_cancel= */ this->num_running_ > 1);
|
/* is_retry= */ false, /* skip_cancel= */ this->num_running_ > 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -208,7 +210,7 @@ template<typename... Ts> class DelayAction : public Action<Ts...>, public Compon
|
|||||||
void play(const Ts &...x) override { /* ignore - see play_complex */
|
void play(const Ts &...x) override { /* ignore - see play_complex */
|
||||||
}
|
}
|
||||||
|
|
||||||
void stop() override { this->cancel_timeout("delay"); }
|
void stop() override { this->cancel_timeout(InternalSchedulerID::DELAY_ACTION); }
|
||||||
};
|
};
|
||||||
|
|
||||||
template<typename... Ts> class LambdaAction : public Action<Ts...> {
|
template<typename... Ts> class LambdaAction : public Action<Ts...> {
|
||||||
|
|||||||
@@ -152,7 +152,10 @@ void Component::set_retry(const std::string &name, uint32_t initial_wait_time, u
|
|||||||
|
|
||||||
void Component::set_retry(const char *name, uint32_t initial_wait_time, uint8_t max_attempts,
|
void Component::set_retry(const char *name, uint32_t initial_wait_time, uint8_t max_attempts,
|
||||||
std::function<RetryResult(uint8_t)> &&f, float backoff_increase_factor) { // NOLINT
|
std::function<RetryResult(uint8_t)> &&f, float backoff_increase_factor) { // NOLINT
|
||||||
|
#pragma GCC diagnostic push
|
||||||
|
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
||||||
App.scheduler.set_retry(this, name, initial_wait_time, max_attempts, std::move(f), backoff_increase_factor);
|
App.scheduler.set_retry(this, name, initial_wait_time, max_attempts, std::move(f), backoff_increase_factor);
|
||||||
|
#pragma GCC diagnostic pop
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Component::cancel_retry(const std::string &name) { // NOLINT
|
bool Component::cancel_retry(const std::string &name) { // NOLINT
|
||||||
@@ -163,7 +166,10 @@ bool Component::cancel_retry(const std::string &name) { // NOLINT
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool Component::cancel_retry(const char *name) { // NOLINT
|
bool Component::cancel_retry(const char *name) { // NOLINT
|
||||||
|
#pragma GCC diagnostic push
|
||||||
|
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
||||||
return App.scheduler.cancel_retry(this, name);
|
return App.scheduler.cancel_retry(this, name);
|
||||||
|
#pragma GCC diagnostic pop
|
||||||
}
|
}
|
||||||
|
|
||||||
void Component::set_timeout(const std::string &name, uint32_t timeout, std::function<void()> &&f) { // NOLINT
|
void Component::set_timeout(const std::string &name, uint32_t timeout, std::function<void()> &&f) { // NOLINT
|
||||||
@@ -195,18 +201,38 @@ void Component::set_timeout(uint32_t id, uint32_t timeout, std::function<void()>
|
|||||||
|
|
||||||
bool Component::cancel_timeout(uint32_t id) { return App.scheduler.cancel_timeout(this, id); }
|
bool Component::cancel_timeout(uint32_t id) { return App.scheduler.cancel_timeout(this, id); }
|
||||||
|
|
||||||
|
void Component::set_timeout(InternalSchedulerID id, uint32_t timeout, std::function<void()> &&f) { // NOLINT
|
||||||
|
App.scheduler.set_timeout(this, id, timeout, std::move(f));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Component::cancel_timeout(InternalSchedulerID id) { return App.scheduler.cancel_timeout(this, id); }
|
||||||
|
|
||||||
void Component::set_interval(uint32_t id, uint32_t interval, std::function<void()> &&f) { // NOLINT
|
void Component::set_interval(uint32_t id, uint32_t interval, std::function<void()> &&f) { // NOLINT
|
||||||
App.scheduler.set_interval(this, id, interval, std::move(f));
|
App.scheduler.set_interval(this, id, interval, std::move(f));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Component::cancel_interval(uint32_t id) { return App.scheduler.cancel_interval(this, id); }
|
bool Component::cancel_interval(uint32_t id) { return App.scheduler.cancel_interval(this, id); }
|
||||||
|
|
||||||
void Component::set_retry(uint32_t id, uint32_t initial_wait_time, uint8_t max_attempts,
|
void Component::set_interval(InternalSchedulerID id, uint32_t interval, std::function<void()> &&f) { // NOLINT
|
||||||
std::function<RetryResult(uint8_t)> &&f, float backoff_increase_factor) { // NOLINT
|
App.scheduler.set_interval(this, id, interval, std::move(f));
|
||||||
App.scheduler.set_retry(this, id, initial_wait_time, max_attempts, std::move(f), backoff_increase_factor);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Component::cancel_retry(uint32_t id) { return App.scheduler.cancel_retry(this, id); }
|
bool Component::cancel_interval(InternalSchedulerID id) { return App.scheduler.cancel_interval(this, id); }
|
||||||
|
|
||||||
|
void Component::set_retry(uint32_t id, uint32_t initial_wait_time, uint8_t max_attempts,
|
||||||
|
std::function<RetryResult(uint8_t)> &&f, float backoff_increase_factor) { // NOLINT
|
||||||
|
#pragma GCC diagnostic push
|
||||||
|
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
||||||
|
App.scheduler.set_retry(this, id, initial_wait_time, max_attempts, std::move(f), backoff_increase_factor);
|
||||||
|
#pragma GCC diagnostic pop
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Component::cancel_retry(uint32_t id) {
|
||||||
|
#pragma GCC diagnostic push
|
||||||
|
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
||||||
|
return App.scheduler.cancel_retry(this, id);
|
||||||
|
#pragma GCC diagnostic pop
|
||||||
|
}
|
||||||
|
|
||||||
void Component::call_loop() { this->loop(); }
|
void Component::call_loop() { this->loop(); }
|
||||||
void Component::call_setup() { this->setup(); }
|
void Component::call_setup() { this->setup(); }
|
||||||
@@ -371,7 +397,10 @@ void Component::set_interval(uint32_t interval, std::function<void()> &&f) { //
|
|||||||
}
|
}
|
||||||
void Component::set_retry(uint32_t initial_wait_time, uint8_t max_attempts, std::function<RetryResult(uint8_t)> &&f,
|
void Component::set_retry(uint32_t initial_wait_time, uint8_t max_attempts, std::function<RetryResult(uint8_t)> &&f,
|
||||||
float backoff_increase_factor) { // NOLINT
|
float backoff_increase_factor) { // NOLINT
|
||||||
|
#pragma GCC diagnostic push
|
||||||
|
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
||||||
App.scheduler.set_retry(this, "", initial_wait_time, max_attempts, std::move(f), backoff_increase_factor);
|
App.scheduler.set_retry(this, "", initial_wait_time, max_attempts, std::move(f), backoff_increase_factor);
|
||||||
|
#pragma GCC diagnostic pop
|
||||||
}
|
}
|
||||||
bool Component::is_failed() const { return (this->component_state_ & COMPONENT_STATE_MASK) == COMPONENT_STATE_FAILED; }
|
bool Component::is_failed() const { return (this->component_state_ & COMPONENT_STATE_MASK) == COMPONENT_STATE_FAILED; }
|
||||||
bool Component::is_ready() const {
|
bool Component::is_ready() const {
|
||||||
@@ -516,12 +545,12 @@ void PollingComponent::call_setup() {
|
|||||||
|
|
||||||
void PollingComponent::start_poller() {
|
void PollingComponent::start_poller() {
|
||||||
// Register interval.
|
// Register interval.
|
||||||
this->set_interval("update", this->get_update_interval(), [this]() { this->update(); });
|
this->set_interval(InternalSchedulerID::POLLING_UPDATE, this->get_update_interval(), [this]() { this->update(); });
|
||||||
}
|
}
|
||||||
|
|
||||||
void PollingComponent::stop_poller() {
|
void PollingComponent::stop_poller() {
|
||||||
// Clear the interval to suspend component
|
// Clear the interval to suspend component
|
||||||
this->cancel_interval("update");
|
this->cancel_interval(InternalSchedulerID::POLLING_UPDATE);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t PollingComponent::get_update_interval() const { return this->update_interval_; }
|
uint32_t PollingComponent::get_update_interval() const { return this->update_interval_; }
|
||||||
|
|||||||
@@ -49,6 +49,14 @@ extern const float LATE;
|
|||||||
|
|
||||||
static const uint32_t SCHEDULER_DONT_RUN = 4294967295UL;
|
static const uint32_t SCHEDULER_DONT_RUN = 4294967295UL;
|
||||||
|
|
||||||
|
/// Type-safe scheduler IDs for core base classes.
|
||||||
|
/// Uses a separate NameType (NUMERIC_ID_INTERNAL) so IDs can never collide
|
||||||
|
/// with component-level NUMERIC_ID values, even if the uint32_t values overlap.
|
||||||
|
enum class InternalSchedulerID : uint32_t {
|
||||||
|
POLLING_UPDATE = 0, // PollingComponent interval
|
||||||
|
DELAY_ACTION = 1, // DelayAction timeout
|
||||||
|
};
|
||||||
|
|
||||||
// Forward declaration
|
// Forward declaration
|
||||||
class PollingComponent;
|
class PollingComponent;
|
||||||
|
|
||||||
@@ -68,6 +76,7 @@ extern const uint8_t STATUS_LED_OK;
|
|||||||
extern const uint8_t STATUS_LED_WARNING;
|
extern const uint8_t STATUS_LED_WARNING;
|
||||||
extern const uint8_t STATUS_LED_ERROR;
|
extern const uint8_t STATUS_LED_ERROR;
|
||||||
|
|
||||||
|
// Remove before 2026.8.0
|
||||||
enum class RetryResult { DONE, RETRY };
|
enum class RetryResult { DONE, RETRY };
|
||||||
|
|
||||||
extern const uint16_t WARN_IF_BLOCKING_OVER_MS;
|
extern const uint16_t WARN_IF_BLOCKING_OVER_MS;
|
||||||
@@ -334,6 +343,8 @@ class Component {
|
|||||||
*/
|
*/
|
||||||
void set_interval(uint32_t id, uint32_t interval, std::function<void()> &&f); // NOLINT
|
void set_interval(uint32_t id, uint32_t interval, std::function<void()> &&f); // NOLINT
|
||||||
|
|
||||||
|
void set_interval(InternalSchedulerID id, uint32_t interval, std::function<void()> &&f); // NOLINT
|
||||||
|
|
||||||
void set_interval(uint32_t interval, std::function<void()> &&f); // NOLINT
|
void set_interval(uint32_t interval, std::function<void()> &&f); // NOLINT
|
||||||
|
|
||||||
/** Cancel an interval function.
|
/** Cancel an interval function.
|
||||||
@@ -346,69 +357,42 @@ class Component {
|
|||||||
bool cancel_interval(const std::string &name); // NOLINT
|
bool cancel_interval(const std::string &name); // NOLINT
|
||||||
bool cancel_interval(const char *name); // NOLINT
|
bool cancel_interval(const char *name); // NOLINT
|
||||||
bool cancel_interval(uint32_t id); // NOLINT
|
bool cancel_interval(uint32_t id); // NOLINT
|
||||||
|
bool cancel_interval(InternalSchedulerID id); // NOLINT
|
||||||
|
|
||||||
/** Set an retry function with a unique name. Empty name means no cancelling possible.
|
/// @deprecated set_retry is deprecated. Use set_timeout or set_interval instead. Removed in 2026.8.0.
|
||||||
*
|
// Remove before 2026.8.0
|
||||||
* This will call the retry function f on the next scheduler loop. f should return RetryResult::DONE if
|
ESPDEPRECATED("set_retry is deprecated and will be removed in 2026.8.0. Use set_timeout or set_interval instead.",
|
||||||
* it is successful and no repeat is required. Otherwise, returning RetryResult::RETRY will call f
|
"2026.2.0")
|
||||||
* again in the future.
|
|
||||||
*
|
|
||||||
* The first retry of f happens after `initial_wait_time` milliseconds. The delay between retries is
|
|
||||||
* increased by multiplying by `backoff_increase_factor` each time. If no backoff_increase_factor is
|
|
||||||
* supplied (default = 1.0), the wait time will stay constant.
|
|
||||||
*
|
|
||||||
* The retry function f needs to accept a single argument: the number of attempts remaining. On the
|
|
||||||
* final retry of f, this value will be 0.
|
|
||||||
*
|
|
||||||
* This retry function can also be cancelled by name via cancel_retry().
|
|
||||||
*
|
|
||||||
* IMPORTANT: Do not rely on this having correct timing. This is only called from
|
|
||||||
* loop() and therefore can be significantly delayed.
|
|
||||||
*
|
|
||||||
* REMARK: It is an error to supply a negative or zero `backoff_increase_factor`, and 1.0 will be used instead.
|
|
||||||
*
|
|
||||||
* REMARK: The interval between retries is stored into a `uint32_t`, so this doesn't behave correctly
|
|
||||||
* if `initial_wait_time * (backoff_increase_factor ** (max_attempts - 2))` overflows.
|
|
||||||
*
|
|
||||||
* @param name The identifier for this retry function.
|
|
||||||
* @param initial_wait_time The time in ms before f is called again
|
|
||||||
* @param max_attempts The maximum number of executions
|
|
||||||
* @param f The function (or lambda) that should be called
|
|
||||||
* @param backoff_increase_factor time between retries is multiplied by this factor on every retry after the first
|
|
||||||
* @see cancel_retry()
|
|
||||||
*/
|
|
||||||
// Remove before 2026.7.0
|
|
||||||
ESPDEPRECATED("Use const char* or uint32_t overload instead. Removed in 2026.7.0", "2026.1.0")
|
|
||||||
void set_retry(const std::string &name, uint32_t initial_wait_time, uint8_t max_attempts, // NOLINT
|
void set_retry(const std::string &name, uint32_t initial_wait_time, uint8_t max_attempts, // NOLINT
|
||||||
std::function<RetryResult(uint8_t)> &&f, float backoff_increase_factor = 1.0f); // NOLINT
|
std::function<RetryResult(uint8_t)> &&f, float backoff_increase_factor = 1.0f); // NOLINT
|
||||||
|
|
||||||
|
// Remove before 2026.8.0
|
||||||
|
ESPDEPRECATED("set_retry is deprecated and will be removed in 2026.8.0. Use set_timeout or set_interval instead.",
|
||||||
|
"2026.2.0")
|
||||||
void set_retry(const char *name, uint32_t initial_wait_time, uint8_t max_attempts, // NOLINT
|
void set_retry(const char *name, uint32_t initial_wait_time, uint8_t max_attempts, // NOLINT
|
||||||
std::function<RetryResult(uint8_t)> &&f, float backoff_increase_factor = 1.0f); // NOLINT
|
std::function<RetryResult(uint8_t)> &&f, float backoff_increase_factor = 1.0f); // NOLINT
|
||||||
|
|
||||||
/** Set a retry function with a numeric ID (zero heap allocation).
|
// Remove before 2026.8.0
|
||||||
*
|
ESPDEPRECATED("set_retry is deprecated and will be removed in 2026.8.0. Use set_timeout or set_interval instead.",
|
||||||
* @param id The numeric identifier for this retry function
|
"2026.2.0")
|
||||||
* @param initial_wait_time The wait time after the first execution
|
|
||||||
* @param max_attempts The max number of attempts
|
|
||||||
* @param f The function to call
|
|
||||||
* @param backoff_increase_factor The factor to increase the retry interval by
|
|
||||||
*/
|
|
||||||
void set_retry(uint32_t id, uint32_t initial_wait_time, uint8_t max_attempts, // NOLINT
|
void set_retry(uint32_t id, uint32_t initial_wait_time, uint8_t max_attempts, // NOLINT
|
||||||
std::function<RetryResult(uint8_t)> &&f, float backoff_increase_factor = 1.0f); // NOLINT
|
std::function<RetryResult(uint8_t)> &&f, float backoff_increase_factor = 1.0f); // NOLINT
|
||||||
|
|
||||||
|
// Remove before 2026.8.0
|
||||||
|
ESPDEPRECATED("set_retry is deprecated and will be removed in 2026.8.0. Use set_timeout or set_interval instead.",
|
||||||
|
"2026.2.0")
|
||||||
void set_retry(uint32_t initial_wait_time, uint8_t max_attempts, std::function<RetryResult(uint8_t)> &&f, // NOLINT
|
void set_retry(uint32_t initial_wait_time, uint8_t max_attempts, std::function<RetryResult(uint8_t)> &&f, // NOLINT
|
||||||
float backoff_increase_factor = 1.0f); // NOLINT
|
float backoff_increase_factor = 1.0f); // NOLINT
|
||||||
|
|
||||||
/** Cancel a retry function.
|
// Remove before 2026.8.0
|
||||||
*
|
ESPDEPRECATED("cancel_retry is deprecated and will be removed in 2026.8.0.", "2026.2.0")
|
||||||
* @param name The identifier for this retry function.
|
|
||||||
* @return Whether a retry function was deleted.
|
|
||||||
*/
|
|
||||||
// Remove before 2026.7.0
|
|
||||||
ESPDEPRECATED("Use const char* or uint32_t overload instead. Removed in 2026.7.0", "2026.1.0")
|
|
||||||
bool cancel_retry(const std::string &name); // NOLINT
|
bool cancel_retry(const std::string &name); // NOLINT
|
||||||
bool cancel_retry(const char *name); // NOLINT
|
// Remove before 2026.8.0
|
||||||
bool cancel_retry(uint32_t id); // NOLINT
|
ESPDEPRECATED("cancel_retry is deprecated and will be removed in 2026.8.0.", "2026.2.0")
|
||||||
|
bool cancel_retry(const char *name); // NOLINT
|
||||||
|
// Remove before 2026.8.0
|
||||||
|
ESPDEPRECATED("cancel_retry is deprecated and will be removed in 2026.8.0.", "2026.2.0")
|
||||||
|
bool cancel_retry(uint32_t id); // NOLINT
|
||||||
|
|
||||||
/** Set a timeout function with a unique name.
|
/** Set a timeout function with a unique name.
|
||||||
*
|
*
|
||||||
@@ -452,6 +436,8 @@ class Component {
|
|||||||
*/
|
*/
|
||||||
void set_timeout(uint32_t id, uint32_t timeout, std::function<void()> &&f); // NOLINT
|
void set_timeout(uint32_t id, uint32_t timeout, std::function<void()> &&f); // NOLINT
|
||||||
|
|
||||||
|
void set_timeout(InternalSchedulerID id, uint32_t timeout, std::function<void()> &&f); // NOLINT
|
||||||
|
|
||||||
void set_timeout(uint32_t timeout, std::function<void()> &&f); // NOLINT
|
void set_timeout(uint32_t timeout, std::function<void()> &&f); // NOLINT
|
||||||
|
|
||||||
/** Cancel a timeout function.
|
/** Cancel a timeout function.
|
||||||
@@ -464,6 +450,7 @@ class Component {
|
|||||||
bool cancel_timeout(const std::string &name); // NOLINT
|
bool cancel_timeout(const std::string &name); // NOLINT
|
||||||
bool cancel_timeout(const char *name); // NOLINT
|
bool cancel_timeout(const char *name); // NOLINT
|
||||||
bool cancel_timeout(uint32_t id); // NOLINT
|
bool cancel_timeout(uint32_t id); // NOLINT
|
||||||
|
bool cancel_timeout(InternalSchedulerID id); // NOLINT
|
||||||
|
|
||||||
/** Defer a callback to the next loop() call.
|
/** Defer a callback to the next loop() call.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -53,9 +53,12 @@ struct SchedulerNameLog {
|
|||||||
} else if (name_type == NameType::HASHED_STRING) {
|
} else if (name_type == NameType::HASHED_STRING) {
|
||||||
ESPHOME_snprintf_P(buffer, sizeof(buffer), ESPHOME_PSTR("hash:0x%08" PRIX32), hash_or_id);
|
ESPHOME_snprintf_P(buffer, sizeof(buffer), ESPHOME_PSTR("hash:0x%08" PRIX32), hash_or_id);
|
||||||
return buffer;
|
return buffer;
|
||||||
} else { // NUMERIC_ID
|
} else if (name_type == NameType::NUMERIC_ID) {
|
||||||
ESPHOME_snprintf_P(buffer, sizeof(buffer), ESPHOME_PSTR("id:%" PRIu32), hash_or_id);
|
ESPHOME_snprintf_P(buffer, sizeof(buffer), ESPHOME_PSTR("id:%" PRIu32), hash_or_id);
|
||||||
return buffer;
|
return buffer;
|
||||||
|
} else { // NUMERIC_ID_INTERNAL
|
||||||
|
ESPHOME_snprintf_P(buffer, sizeof(buffer), ESPHOME_PSTR("iid:%" PRIu32), hash_or_id);
|
||||||
|
return buffer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -137,6 +140,9 @@ void HOT Scheduler::set_timer_common_(Component *component, SchedulerItem::Type
|
|||||||
case NameType::NUMERIC_ID:
|
case NameType::NUMERIC_ID:
|
||||||
item->set_numeric_id(hash_or_id);
|
item->set_numeric_id(hash_or_id);
|
||||||
break;
|
break;
|
||||||
|
case NameType::NUMERIC_ID_INTERNAL:
|
||||||
|
item->set_internal_id(hash_or_id);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
item->type = type;
|
item->type = type;
|
||||||
item->callback = std::move(func);
|
item->callback = std::move(func);
|
||||||
@@ -252,6 +258,11 @@ bool HOT Scheduler::cancel_interval(Component *component, uint32_t id) {
|
|||||||
return this->cancel_item_(component, NameType::NUMERIC_ID, nullptr, id, SchedulerItem::INTERVAL);
|
return this->cancel_item_(component, NameType::NUMERIC_ID, nullptr, id, SchedulerItem::INTERVAL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Suppress deprecation warnings for RetryResult usage in the still-present (but deprecated) retry implementation.
|
||||||
|
// Remove before 2026.8.0 along with all retry code.
|
||||||
|
#pragma GCC diagnostic push
|
||||||
|
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
||||||
|
|
||||||
struct RetryArgs {
|
struct RetryArgs {
|
||||||
// Ordered to minimize padding on 32-bit systems
|
// Ordered to minimize padding on 32-bit systems
|
||||||
std::function<RetryResult(uint8_t)> func;
|
std::function<RetryResult(uint8_t)> func;
|
||||||
@@ -364,6 +375,8 @@ bool HOT Scheduler::cancel_retry(Component *component, uint32_t id) {
|
|||||||
return this->cancel_retry_(component, NameType::NUMERIC_ID, nullptr, id);
|
return this->cancel_retry_(component, NameType::NUMERIC_ID, nullptr, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#pragma GCC diagnostic pop // End suppression of deprecated RetryResult warnings
|
||||||
|
|
||||||
optional<uint32_t> HOT Scheduler::next_schedule_in(uint32_t now) {
|
optional<uint32_t> HOT Scheduler::next_schedule_in(uint32_t now) {
|
||||||
// IMPORTANT: This method should only be called from the main thread (loop task).
|
// IMPORTANT: This method should only be called from the main thread (loop task).
|
||||||
// It performs cleanup and accesses items_[0] without holding a lock, which is only
|
// It performs cleanup and accesses items_[0] without holding a lock, which is only
|
||||||
|
|||||||
@@ -46,11 +46,20 @@ class Scheduler {
|
|||||||
void set_timeout(Component *component, const char *name, uint32_t timeout, std::function<void()> func);
|
void set_timeout(Component *component, const char *name, uint32_t timeout, std::function<void()> func);
|
||||||
/// Set a timeout with a numeric ID (zero heap allocation)
|
/// Set a timeout with a numeric ID (zero heap allocation)
|
||||||
void set_timeout(Component *component, uint32_t id, uint32_t timeout, std::function<void()> func);
|
void set_timeout(Component *component, uint32_t id, uint32_t timeout, std::function<void()> func);
|
||||||
|
/// Set a timeout with an internal scheduler ID (separate namespace from component NUMERIC_ID)
|
||||||
|
void set_timeout(Component *component, InternalSchedulerID id, uint32_t timeout, std::function<void()> func) {
|
||||||
|
this->set_timer_common_(component, SchedulerItem::TIMEOUT, NameType::NUMERIC_ID_INTERNAL, nullptr,
|
||||||
|
static_cast<uint32_t>(id), timeout, std::move(func));
|
||||||
|
}
|
||||||
|
|
||||||
ESPDEPRECATED("Use const char* or uint32_t overload instead. Removed in 2026.7.0", "2026.1.0")
|
ESPDEPRECATED("Use const char* or uint32_t overload instead. Removed in 2026.7.0", "2026.1.0")
|
||||||
bool cancel_timeout(Component *component, const std::string &name);
|
bool cancel_timeout(Component *component, const std::string &name);
|
||||||
bool cancel_timeout(Component *component, const char *name);
|
bool cancel_timeout(Component *component, const char *name);
|
||||||
bool cancel_timeout(Component *component, uint32_t id);
|
bool cancel_timeout(Component *component, uint32_t id);
|
||||||
|
bool cancel_timeout(Component *component, InternalSchedulerID id) {
|
||||||
|
return this->cancel_item_(component, NameType::NUMERIC_ID_INTERNAL, nullptr, static_cast<uint32_t>(id),
|
||||||
|
SchedulerItem::TIMEOUT);
|
||||||
|
}
|
||||||
|
|
||||||
ESPDEPRECATED("Use const char* or uint32_t overload instead. Removed in 2026.7.0", "2026.1.0")
|
ESPDEPRECATED("Use const char* or uint32_t overload instead. Removed in 2026.7.0", "2026.1.0")
|
||||||
void set_interval(Component *component, const std::string &name, uint32_t interval, std::function<void()> func);
|
void set_interval(Component *component, const std::string &name, uint32_t interval, std::function<void()> func);
|
||||||
@@ -66,24 +75,45 @@ class Scheduler {
|
|||||||
void set_interval(Component *component, const char *name, uint32_t interval, std::function<void()> func);
|
void set_interval(Component *component, const char *name, uint32_t interval, std::function<void()> func);
|
||||||
/// Set an interval with a numeric ID (zero heap allocation)
|
/// Set an interval with a numeric ID (zero heap allocation)
|
||||||
void set_interval(Component *component, uint32_t id, uint32_t interval, std::function<void()> func);
|
void set_interval(Component *component, uint32_t id, uint32_t interval, std::function<void()> func);
|
||||||
|
/// Set an interval with an internal scheduler ID (separate namespace from component NUMERIC_ID)
|
||||||
|
void set_interval(Component *component, InternalSchedulerID id, uint32_t interval, std::function<void()> func) {
|
||||||
|
this->set_timer_common_(component, SchedulerItem::INTERVAL, NameType::NUMERIC_ID_INTERNAL, nullptr,
|
||||||
|
static_cast<uint32_t>(id), interval, std::move(func));
|
||||||
|
}
|
||||||
|
|
||||||
ESPDEPRECATED("Use const char* or uint32_t overload instead. Removed in 2026.7.0", "2026.1.0")
|
ESPDEPRECATED("Use const char* or uint32_t overload instead. Removed in 2026.7.0", "2026.1.0")
|
||||||
bool cancel_interval(Component *component, const std::string &name);
|
bool cancel_interval(Component *component, const std::string &name);
|
||||||
bool cancel_interval(Component *component, const char *name);
|
bool cancel_interval(Component *component, const char *name);
|
||||||
bool cancel_interval(Component *component, uint32_t id);
|
bool cancel_interval(Component *component, uint32_t id);
|
||||||
|
bool cancel_interval(Component *component, InternalSchedulerID id) {
|
||||||
|
return this->cancel_item_(component, NameType::NUMERIC_ID_INTERNAL, nullptr, static_cast<uint32_t>(id),
|
||||||
|
SchedulerItem::INTERVAL);
|
||||||
|
}
|
||||||
|
|
||||||
ESPDEPRECATED("Use const char* or uint32_t overload instead. Removed in 2026.7.0", "2026.1.0")
|
// Remove before 2026.8.0
|
||||||
|
ESPDEPRECATED("set_retry is deprecated and will be removed in 2026.8.0. Use set_timeout or set_interval instead.",
|
||||||
|
"2026.2.0")
|
||||||
void set_retry(Component *component, const std::string &name, uint32_t initial_wait_time, uint8_t max_attempts,
|
void set_retry(Component *component, const std::string &name, uint32_t initial_wait_time, uint8_t max_attempts,
|
||||||
std::function<RetryResult(uint8_t)> func, float backoff_increase_factor = 1.0f);
|
std::function<RetryResult(uint8_t)> func, float backoff_increase_factor = 1.0f);
|
||||||
|
// Remove before 2026.8.0
|
||||||
|
ESPDEPRECATED("set_retry is deprecated and will be removed in 2026.8.0. Use set_timeout or set_interval instead.",
|
||||||
|
"2026.2.0")
|
||||||
void set_retry(Component *component, const char *name, uint32_t initial_wait_time, uint8_t max_attempts,
|
void set_retry(Component *component, const char *name, uint32_t initial_wait_time, uint8_t max_attempts,
|
||||||
std::function<RetryResult(uint8_t)> func, float backoff_increase_factor = 1.0f);
|
std::function<RetryResult(uint8_t)> func, float backoff_increase_factor = 1.0f);
|
||||||
/// Set a retry with a numeric ID (zero heap allocation)
|
// Remove before 2026.8.0
|
||||||
|
ESPDEPRECATED("set_retry is deprecated and will be removed in 2026.8.0. Use set_timeout or set_interval instead.",
|
||||||
|
"2026.2.0")
|
||||||
void set_retry(Component *component, uint32_t id, uint32_t initial_wait_time, uint8_t max_attempts,
|
void set_retry(Component *component, uint32_t id, uint32_t initial_wait_time, uint8_t max_attempts,
|
||||||
std::function<RetryResult(uint8_t)> func, float backoff_increase_factor = 1.0f);
|
std::function<RetryResult(uint8_t)> func, float backoff_increase_factor = 1.0f);
|
||||||
|
|
||||||
ESPDEPRECATED("Use const char* or uint32_t overload instead. Removed in 2026.7.0", "2026.1.0")
|
// Remove before 2026.8.0
|
||||||
|
ESPDEPRECATED("cancel_retry is deprecated and will be removed in 2026.8.0.", "2026.2.0")
|
||||||
bool cancel_retry(Component *component, const std::string &name);
|
bool cancel_retry(Component *component, const std::string &name);
|
||||||
|
// Remove before 2026.8.0
|
||||||
|
ESPDEPRECATED("cancel_retry is deprecated and will be removed in 2026.8.0.", "2026.2.0")
|
||||||
bool cancel_retry(Component *component, const char *name);
|
bool cancel_retry(Component *component, const char *name);
|
||||||
|
// Remove before 2026.8.0
|
||||||
|
ESPDEPRECATED("cancel_retry is deprecated and will be removed in 2026.8.0.", "2026.2.0")
|
||||||
bool cancel_retry(Component *component, uint32_t id);
|
bool cancel_retry(Component *component, uint32_t id);
|
||||||
|
|
||||||
// Calculate when the next scheduled item should run
|
// Calculate when the next scheduled item should run
|
||||||
@@ -100,11 +130,12 @@ class Scheduler {
|
|||||||
void process_to_add();
|
void process_to_add();
|
||||||
|
|
||||||
// Name storage type discriminator for SchedulerItem
|
// Name storage type discriminator for SchedulerItem
|
||||||
// Used to distinguish between static strings, hashed strings, and numeric IDs
|
// Used to distinguish between static strings, hashed strings, numeric IDs, and internal numeric IDs
|
||||||
enum class NameType : uint8_t {
|
enum class NameType : uint8_t {
|
||||||
STATIC_STRING = 0, // const char* pointer to static/flash storage
|
STATIC_STRING = 0, // const char* pointer to static/flash storage
|
||||||
HASHED_STRING = 1, // uint32_t FNV-1a hash of a runtime string
|
HASHED_STRING = 1, // uint32_t FNV-1a hash of a runtime string
|
||||||
NUMERIC_ID = 2 // uint32_t numeric identifier
|
NUMERIC_ID = 2, // uint32_t numeric identifier (component-level)
|
||||||
|
NUMERIC_ID_INTERNAL = 3 // uint32_t numeric identifier (core/internal, separate namespace)
|
||||||
};
|
};
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
@@ -135,7 +166,7 @@ class Scheduler {
|
|||||||
|
|
||||||
// Bit-packed fields (4 bits used, 4 bits padding in 1 byte)
|
// Bit-packed fields (4 bits used, 4 bits padding in 1 byte)
|
||||||
enum Type : uint8_t { TIMEOUT, INTERVAL } type : 1;
|
enum Type : uint8_t { TIMEOUT, INTERVAL } type : 1;
|
||||||
NameType name_type_ : 2; // Discriminator for name_ union (STATIC_STRING, HASHED_STRING, NUMERIC_ID)
|
NameType name_type_ : 2; // Discriminator for name_ union (0–3, see NameType enum)
|
||||||
bool is_retry : 1; // True if this is a retry timeout
|
bool is_retry : 1; // True if this is a retry timeout
|
||||||
// 4 bits padding
|
// 4 bits padding
|
||||||
#else
|
#else
|
||||||
@@ -143,7 +174,7 @@ class Scheduler {
|
|||||||
// Bit-packed fields (5 bits used, 3 bits padding in 1 byte)
|
// Bit-packed fields (5 bits used, 3 bits padding in 1 byte)
|
||||||
enum Type : uint8_t { TIMEOUT, INTERVAL } type : 1;
|
enum Type : uint8_t { TIMEOUT, INTERVAL } type : 1;
|
||||||
bool remove : 1;
|
bool remove : 1;
|
||||||
NameType name_type_ : 2; // Discriminator for name_ union (STATIC_STRING, HASHED_STRING, NUMERIC_ID)
|
NameType name_type_ : 2; // Discriminator for name_ union (0–3, see NameType enum)
|
||||||
bool is_retry : 1; // True if this is a retry timeout
|
bool is_retry : 1; // True if this is a retry timeout
|
||||||
// 3 bits padding
|
// 3 bits padding
|
||||||
#endif
|
#endif
|
||||||
@@ -206,6 +237,12 @@ class Scheduler {
|
|||||||
name_type_ = NameType::NUMERIC_ID;
|
name_type_ = NameType::NUMERIC_ID;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper to set an internal numeric ID (separate namespace from NUMERIC_ID)
|
||||||
|
void set_internal_id(uint32_t id) {
|
||||||
|
name_.hash_or_id = id;
|
||||||
|
name_type_ = NameType::NUMERIC_ID_INTERNAL;
|
||||||
|
}
|
||||||
|
|
||||||
static bool cmp(const std::unique_ptr<SchedulerItem> &a, const std::unique_ptr<SchedulerItem> &b);
|
static bool cmp(const std::unique_ptr<SchedulerItem> &a, const std::unique_ptr<SchedulerItem> &b);
|
||||||
|
|
||||||
// Note: We use 48 bits total (32 + 16), stored in a 64-bit value for API compatibility.
|
// Note: We use 48 bits total (32 + 16), stored in a 64-bit value for API compatibility.
|
||||||
@@ -231,11 +268,14 @@ class Scheduler {
|
|||||||
uint32_t hash_or_id, uint32_t delay, std::function<void()> func, bool is_retry = false,
|
uint32_t hash_or_id, uint32_t delay, std::function<void()> func, bool is_retry = false,
|
||||||
bool skip_cancel = false);
|
bool skip_cancel = false);
|
||||||
|
|
||||||
// Common implementation for retry
|
// Common implementation for retry - Remove before 2026.8.0
|
||||||
// name_type determines storage type: STATIC_STRING uses static_name, others use hash_or_id
|
// name_type determines storage type: STATIC_STRING uses static_name, others use hash_or_id
|
||||||
|
#pragma GCC diagnostic push
|
||||||
|
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
||||||
void set_retry_common_(Component *component, NameType name_type, const char *static_name, uint32_t hash_or_id,
|
void set_retry_common_(Component *component, NameType name_type, const char *static_name, uint32_t hash_or_id,
|
||||||
uint32_t initial_wait_time, uint8_t max_attempts, std::function<RetryResult(uint8_t)> func,
|
uint32_t initial_wait_time, uint8_t max_attempts, std::function<RetryResult(uint8_t)> func,
|
||||||
float backoff_increase_factor);
|
float backoff_increase_factor);
|
||||||
|
#pragma GCC diagnostic pop
|
||||||
// Common implementation for cancel_retry
|
// Common implementation for cancel_retry
|
||||||
bool cancel_retry_(Component *component, NameType name_type, const char *static_name, uint32_t hash_or_id);
|
bool cancel_retry_(Component *component, NameType name_type, const char *static_name, uint32_t hash_or_id);
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
[build-system]
|
[build-system]
|
||||||
requires = ["setuptools==80.10.2", "wheel>=0.43,<0.47"]
|
requires = ["setuptools==82.0.0", "wheel>=0.43,<0.47"]
|
||||||
build-backend = "setuptools.build_meta"
|
build-backend = "setuptools.build_meta"
|
||||||
|
|
||||||
[project]
|
[project]
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ pyserial==3.5
|
|||||||
platformio==6.1.19
|
platformio==6.1.19
|
||||||
esptool==5.1.0
|
esptool==5.1.0
|
||||||
click==8.1.7
|
click==8.1.7
|
||||||
esphome-dashboard==20260110.0
|
esphome-dashboard==20260210.0
|
||||||
aioesphomeapi==43.14.0
|
aioesphomeapi==44.0.0
|
||||||
zeroconf==0.148.0
|
zeroconf==0.148.0
|
||||||
puremagic==1.30
|
puremagic==1.30
|
||||||
ruamel.yaml==0.19.1 # dashboard_import
|
ruamel.yaml==0.19.1 # dashboard_import
|
||||||
|
|||||||
@@ -2277,6 +2277,12 @@ ifdefs: dict[str, str] = {}
|
|||||||
# Track messages with no fields (empty messages) for parameter elision
|
# Track messages with no fields (empty messages) for parameter elision
|
||||||
EMPTY_MESSAGES: set[str] = set()
|
EMPTY_MESSAGES: set[str] = set()
|
||||||
|
|
||||||
|
# Track empty SOURCE_CLIENT messages that don't need class generation
|
||||||
|
# These messages have no fields and are only received (never sent), so the
|
||||||
|
# class definition (vtable, dump_to, message_name, ESTIMATED_SIZE) is dead code
|
||||||
|
# that the compiler compiles but the linker strips away.
|
||||||
|
SKIP_CLASS_GENERATION: set[str] = set()
|
||||||
|
|
||||||
|
|
||||||
def get_opt(
|
def get_opt(
|
||||||
desc: descriptor.DescriptorProto,
|
desc: descriptor.DescriptorProto,
|
||||||
@@ -2527,7 +2533,11 @@ def build_service_message_type(
|
|||||||
case += "#endif\n"
|
case += "#endif\n"
|
||||||
case += f"this->{func}({'msg' if not is_empty else ''});\n"
|
case += f"this->{func}({'msg' if not is_empty else ''});\n"
|
||||||
case += "break;"
|
case += "break;"
|
||||||
RECEIVE_CASES[id_] = (case, ifdef, mt.name)
|
if mt.name in SKIP_CLASS_GENERATION:
|
||||||
|
case_label = f"{id_} /* {mt.name} is empty */"
|
||||||
|
else:
|
||||||
|
case_label = f"{mt.name}::MESSAGE_TYPE"
|
||||||
|
RECEIVE_CASES[id_] = (case, ifdef, case_label)
|
||||||
|
|
||||||
# Only close ifdef if we opened it
|
# Only close ifdef if we opened it
|
||||||
if ifdef is not None:
|
if ifdef is not None:
|
||||||
@@ -2723,6 +2733,19 @@ static void dump_bytes_field(DumpBuffer &out, const char *field_name, const uint
|
|||||||
|
|
||||||
mt = file.message_type
|
mt = file.message_type
|
||||||
|
|
||||||
|
# Identify empty SOURCE_CLIENT messages that don't need class generation
|
||||||
|
for m in mt:
|
||||||
|
if m.options.deprecated:
|
||||||
|
continue
|
||||||
|
if not m.options.HasExtension(pb.id):
|
||||||
|
continue
|
||||||
|
source = message_source_map.get(m.name)
|
||||||
|
if source != SOURCE_CLIENT:
|
||||||
|
continue
|
||||||
|
has_fields = any(not field.options.deprecated for field in m.field)
|
||||||
|
if not has_fields:
|
||||||
|
SKIP_CLASS_GENERATION.add(m.name)
|
||||||
|
|
||||||
# Collect messages by base class
|
# Collect messages by base class
|
||||||
base_class_groups = collect_messages_by_base_class(mt)
|
base_class_groups = collect_messages_by_base_class(mt)
|
||||||
|
|
||||||
@@ -2755,6 +2778,10 @@ static void dump_bytes_field(DumpBuffer &out, const char *field_name, const uint
|
|||||||
if m.name not in used_messages and not m.options.HasExtension(pb.id):
|
if m.name not in used_messages and not m.options.HasExtension(pb.id):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
# Skip class generation for empty SOURCE_CLIENT messages
|
||||||
|
if m.name in SKIP_CLASS_GENERATION:
|
||||||
|
continue
|
||||||
|
|
||||||
s, c, dc = build_message_type(m, base_class_fields, message_source_map)
|
s, c, dc = build_message_type(m, base_class_fields, message_source_map)
|
||||||
msg_ifdef = message_ifdef_map.get(m.name)
|
msg_ifdef = message_ifdef_map.get(m.name)
|
||||||
|
|
||||||
@@ -2881,33 +2908,8 @@ static const char *const TAG = "api.service";
|
|||||||
|
|
||||||
cases = list(RECEIVE_CASES.items())
|
cases = list(RECEIVE_CASES.items())
|
||||||
cases.sort()
|
cases.sort()
|
||||||
hpp += " protected:\n"
|
|
||||||
hpp += " void read_message(uint32_t msg_size, uint32_t msg_type, const uint8_t *msg_data) override;\n"
|
|
||||||
out = f"void {class_name}::read_message(uint32_t msg_size, uint32_t msg_type, const uint8_t *msg_data) {{\n"
|
|
||||||
out += " switch (msg_type) {\n"
|
|
||||||
for i, (case, ifdef, message_name) in cases:
|
|
||||||
if ifdef is not None:
|
|
||||||
out += f"#ifdef {ifdef}\n"
|
|
||||||
|
|
||||||
c = f" case {message_name}::MESSAGE_TYPE: {{\n"
|
|
||||||
c += indent(case, " ") + "\n"
|
|
||||||
c += " }"
|
|
||||||
out += c + "\n"
|
|
||||||
if ifdef is not None:
|
|
||||||
out += "#endif\n"
|
|
||||||
out += " default:\n"
|
|
||||||
out += " break;\n"
|
|
||||||
out += " }\n"
|
|
||||||
out += "}\n"
|
|
||||||
cpp += out
|
|
||||||
hpp += "};\n"
|
|
||||||
|
|
||||||
serv = file.service[0]
|
serv = file.service[0]
|
||||||
class_name = "APIServerConnection"
|
|
||||||
hpp += "\n"
|
|
||||||
hpp += f"class {class_name} : public {class_name}Base {{\n"
|
|
||||||
hpp_protected = ""
|
|
||||||
cpp += "\n"
|
|
||||||
|
|
||||||
# Build a mapping of message input types to their authentication requirements
|
# Build a mapping of message input types to their authentication requirements
|
||||||
message_auth_map: dict[str, bool] = {}
|
message_auth_map: dict[str, bool] = {}
|
||||||
@@ -2922,67 +2924,90 @@ static const char *const TAG = "api.service";
|
|||||||
message_auth_map[inp] = needs_auth
|
message_auth_map[inp] = needs_auth
|
||||||
message_conn_map[inp] = needs_conn
|
message_conn_map[inp] = needs_conn
|
||||||
|
|
||||||
# Generate optimized read_message with authentication checking
|
|
||||||
# Categorize messages by their authentication requirements
|
# Categorize messages by their authentication requirements
|
||||||
no_conn_ids: set[int] = set()
|
no_conn_ids: set[int] = set()
|
||||||
conn_only_ids: set[int] = set()
|
conn_only_ids: set[int] = set()
|
||||||
|
|
||||||
for id_, (_, _, case_msg_name) in cases:
|
# Build a reverse lookup from message id to message name for auth lookups
|
||||||
if case_msg_name in message_auth_map:
|
id_to_msg_name: dict[int, str] = {}
|
||||||
needs_auth = message_auth_map[case_msg_name]
|
for mt in file.message_type:
|
||||||
needs_conn = message_conn_map[case_msg_name]
|
id_ = get_opt(mt, pb.id)
|
||||||
|
if id_ is not None and not mt.options.deprecated:
|
||||||
|
id_to_msg_name[id_] = mt.name
|
||||||
|
|
||||||
|
for id_, (_, _, case_label) in cases:
|
||||||
|
msg_name = id_to_msg_name.get(id_, "")
|
||||||
|
if msg_name in message_auth_map:
|
||||||
|
needs_auth = message_auth_map[msg_name]
|
||||||
|
needs_conn = message_conn_map[msg_name]
|
||||||
|
|
||||||
if not needs_conn:
|
if not needs_conn:
|
||||||
no_conn_ids.add(id_)
|
no_conn_ids.add(id_)
|
||||||
elif not needs_auth:
|
elif not needs_auth:
|
||||||
conn_only_ids.add(id_)
|
conn_only_ids.add(id_)
|
||||||
|
|
||||||
# Generate override if we have messages that skip checks
|
# Helper to generate case statements with ifdefs
|
||||||
if no_conn_ids or conn_only_ids:
|
def generate_cases(ids: set[int], comment: str) -> str:
|
||||||
# Helper to generate case statements with ifdefs
|
result = ""
|
||||||
def generate_cases(ids: set[int], comment: str) -> str:
|
for id_ in sorted(ids):
|
||||||
result = ""
|
_, ifdef, case_label = RECEIVE_CASES[id_]
|
||||||
for id_ in sorted(ids):
|
if ifdef:
|
||||||
_, ifdef, msg_name = RECEIVE_CASES[id_]
|
result += f"#ifdef {ifdef}\n"
|
||||||
if ifdef:
|
result += f" case {case_label}: {comment}\n"
|
||||||
result += f"#ifdef {ifdef}\n"
|
if ifdef:
|
||||||
result += f" case {msg_name}::MESSAGE_TYPE: {comment}\n"
|
result += "#endif\n"
|
||||||
if ifdef:
|
return result
|
||||||
result += "#endif\n"
|
|
||||||
return result
|
|
||||||
|
|
||||||
hpp_protected += " void read_message(uint32_t msg_size, uint32_t msg_type, const uint8_t *msg_data) override;\n"
|
|
||||||
|
|
||||||
cpp += f"\nvoid {class_name}::read_message(uint32_t msg_size, uint32_t msg_type, const uint8_t *msg_data) {{\n"
|
|
||||||
cpp += " // Check authentication/connection requirements for messages\n"
|
|
||||||
cpp += " switch (msg_type) {\n"
|
|
||||||
|
|
||||||
# Messages that don't need any checks
|
|
||||||
if no_conn_ids:
|
|
||||||
cpp += generate_cases(no_conn_ids, "// No setup required")
|
|
||||||
cpp += " break; // Skip all checks for these messages\n"
|
|
||||||
|
|
||||||
# Messages that only need connection setup
|
|
||||||
if conn_only_ids:
|
|
||||||
cpp += generate_cases(conn_only_ids, "// Connection setup only")
|
|
||||||
cpp += " if (!this->check_connection_setup_()) {\n"
|
|
||||||
cpp += " return; // Connection not setup\n"
|
|
||||||
cpp += " }\n"
|
|
||||||
cpp += " break;\n"
|
|
||||||
|
|
||||||
cpp += " default:\n"
|
|
||||||
cpp += " // All other messages require authentication (which includes connection check)\n"
|
|
||||||
cpp += " if (!this->check_authenticated_()) {\n"
|
|
||||||
cpp += " return; // Authentication failed\n"
|
|
||||||
cpp += " }\n"
|
|
||||||
cpp += " break;\n"
|
|
||||||
cpp += " }\n\n"
|
|
||||||
cpp += " // Call base implementation to process the message\n"
|
|
||||||
cpp += f" {class_name}Base::read_message(msg_size, msg_type, msg_data);\n"
|
|
||||||
cpp += "}\n"
|
|
||||||
|
|
||||||
|
# Generate read_message with auth check before dispatch
|
||||||
hpp += " protected:\n"
|
hpp += " protected:\n"
|
||||||
hpp += hpp_protected
|
hpp += " void read_message(uint32_t msg_size, uint32_t msg_type, const uint8_t *msg_data) override;\n"
|
||||||
|
|
||||||
|
out = f"void {class_name}::read_message(uint32_t msg_size, uint32_t msg_type, const uint8_t *msg_data) {{\n"
|
||||||
|
|
||||||
|
# Auth check block before dispatch switch
|
||||||
|
out += " // Check authentication/connection requirements\n"
|
||||||
|
if no_conn_ids or conn_only_ids:
|
||||||
|
out += " switch (msg_type) {\n"
|
||||||
|
|
||||||
|
if no_conn_ids:
|
||||||
|
out += generate_cases(no_conn_ids, "// No setup required")
|
||||||
|
out += " break;\n"
|
||||||
|
|
||||||
|
if conn_only_ids:
|
||||||
|
out += generate_cases(conn_only_ids, "// Connection setup only")
|
||||||
|
out += " if (!this->check_connection_setup_()) {\n"
|
||||||
|
out += " return;\n"
|
||||||
|
out += " }\n"
|
||||||
|
out += " break;\n"
|
||||||
|
|
||||||
|
out += " default:\n"
|
||||||
|
out += " if (!this->check_authenticated_()) {\n"
|
||||||
|
out += " return;\n"
|
||||||
|
out += " }\n"
|
||||||
|
out += " break;\n"
|
||||||
|
out += " }\n"
|
||||||
|
else:
|
||||||
|
out += " if (!this->check_authenticated_()) {\n"
|
||||||
|
out += " return;\n"
|
||||||
|
out += " }\n"
|
||||||
|
|
||||||
|
# Dispatch switch
|
||||||
|
out += " switch (msg_type) {\n"
|
||||||
|
for i, (case, ifdef, case_label) in cases:
|
||||||
|
if ifdef is not None:
|
||||||
|
out += f"#ifdef {ifdef}\n"
|
||||||
|
|
||||||
|
c = f" case {case_label}: {{\n"
|
||||||
|
c += indent(case, " ") + "\n"
|
||||||
|
c += " }"
|
||||||
|
out += c + "\n"
|
||||||
|
if ifdef is not None:
|
||||||
|
out += "#endif\n"
|
||||||
|
out += " default:\n"
|
||||||
|
out += " break;\n"
|
||||||
|
out += " }\n"
|
||||||
|
out += "}\n"
|
||||||
|
cpp += out
|
||||||
hpp += "};\n"
|
hpp += "};\n"
|
||||||
|
|
||||||
hpp += """\
|
hpp += """\
|
||||||
|
|||||||
@@ -756,6 +756,53 @@ def lint_no_sprintf(fname, match):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@lint_re_check(
|
||||||
|
# Match std::to_string() or unqualified to_string() calls
|
||||||
|
# The esphome namespace has "using std::to_string;" so unqualified calls resolve to std::to_string
|
||||||
|
# Use negative lookbehind for unqualified calls to avoid matching:
|
||||||
|
# - Function definitions: "const char *to_string(" or "std::string to_string("
|
||||||
|
# - Method definitions: "Class::to_string("
|
||||||
|
# - Method calls: ".to_string(" or "->to_string("
|
||||||
|
# - Other identifiers: "_to_string("
|
||||||
|
# Also explicitly match std::to_string since : is in the lookbehind
|
||||||
|
r"(?:(?<![*&.\w>:])to_string|std\s*::\s*to_string)\s*\(" + CPP_RE_EOL,
|
||||||
|
include=cpp_include,
|
||||||
|
exclude=[
|
||||||
|
# Vendored library
|
||||||
|
"esphome/components/http_request/httplib.h",
|
||||||
|
# Deprecated helpers that return std::string
|
||||||
|
"esphome/core/helpers.cpp",
|
||||||
|
# The using declaration itself
|
||||||
|
"esphome/core/helpers.h",
|
||||||
|
# Test fixtures - not production embedded code
|
||||||
|
"tests/integration/fixtures/*",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def lint_no_std_to_string(fname, match):
|
||||||
|
return (
|
||||||
|
f"{highlight('std::to_string()')} (including unqualified {highlight('to_string()')}) "
|
||||||
|
f"allocates heap memory. On long-running embedded devices, repeated heap allocations "
|
||||||
|
f"fragment memory over time.\n"
|
||||||
|
f"Please use {highlight('snprintf()')} with a stack buffer instead.\n"
|
||||||
|
f"\n"
|
||||||
|
f"Buffer sizes and format specifiers (sizes include sign and null terminator):\n"
|
||||||
|
f" uint8_t: 4 chars - %u (or PRIu8)\n"
|
||||||
|
f" int8_t: 5 chars - %d (or PRId8)\n"
|
||||||
|
f" uint16_t: 6 chars - %u (or PRIu16)\n"
|
||||||
|
f" int16_t: 7 chars - %d (or PRId16)\n"
|
||||||
|
f" uint32_t: 11 chars - %" + "PRIu32\n"
|
||||||
|
" int32_t: 12 chars - %" + "PRId32\n"
|
||||||
|
" uint64_t: 21 chars - %" + "PRIu64\n"
|
||||||
|
" int64_t: 21 chars - %" + "PRId64\n"
|
||||||
|
f" float/double: 24 chars - %.8g (15 digits + sign + decimal + e+XXX)\n"
|
||||||
|
f" 317 chars - %f (for DBL_MAX: 309 int digits + decimal + 6 frac + sign)\n"
|
||||||
|
f"\n"
|
||||||
|
f"For sensor values, use value_accuracy_to_buf() from helpers.h.\n"
|
||||||
|
f'Example: char buf[11]; snprintf(buf, sizeof(buf), "%" PRIu32, value);\n'
|
||||||
|
f"(If strictly necessary, add `{highlight('// NOLINT')}` to the end of the line)"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@lint_re_check(
|
@lint_re_check(
|
||||||
# Match scanf family functions: scanf, sscanf, fscanf, vscanf, vsscanf, vfscanf
|
# Match scanf family functions: scanf, sscanf, fscanf, vscanf, vsscanf, vfscanf
|
||||||
# Also match std:: prefixed versions
|
# Also match std:: prefixed versions
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
ld2450:
|
ld2450:
|
||||||
- id: ld2450_radar
|
- id: ld2450_radar
|
||||||
|
on_data:
|
||||||
|
then:
|
||||||
|
- logger.log: "LD2450 Radar Data Received"
|
||||||
|
|
||||||
button:
|
button:
|
||||||
- platform: ld2450
|
- platform: ld2450
|
||||||
|
|||||||
52
tests/components/mipi_rgb/common.yaml
Normal file
52
tests/components/mipi_rgb/common.yaml
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
display:
|
||||||
|
- platform: mipi_rgb
|
||||||
|
spi_id: spi_bus
|
||||||
|
model: ZX2D10GE01R-V4848
|
||||||
|
update_interval: 1s
|
||||||
|
color_order: BGR
|
||||||
|
draw_rounding: 2
|
||||||
|
pixel_mode: 18bit
|
||||||
|
invert_colors: false
|
||||||
|
use_axis_flips: true
|
||||||
|
pclk_frequency: 15000000.0
|
||||||
|
pclk_inverted: true
|
||||||
|
byte_order: big_endian
|
||||||
|
hsync_pulse_width: 10
|
||||||
|
hsync_back_porch: 10
|
||||||
|
hsync_front_porch: 10
|
||||||
|
vsync_pulse_width: 2
|
||||||
|
vsync_back_porch: 12
|
||||||
|
vsync_front_porch: 14
|
||||||
|
data_pins:
|
||||||
|
red:
|
||||||
|
- number: 10
|
||||||
|
- number: 16
|
||||||
|
- number: 9
|
||||||
|
- number: 15
|
||||||
|
- number: 46
|
||||||
|
green:
|
||||||
|
- number: 8
|
||||||
|
- number: 13
|
||||||
|
- number: 18
|
||||||
|
- number: 12
|
||||||
|
- number: 11
|
||||||
|
- number: 17
|
||||||
|
blue:
|
||||||
|
- number: 47
|
||||||
|
- number: 1
|
||||||
|
- number: 0
|
||||||
|
- number: 42
|
||||||
|
- number: 14
|
||||||
|
de_pin:
|
||||||
|
number: 39
|
||||||
|
pclk_pin:
|
||||||
|
number: 45
|
||||||
|
hsync_pin:
|
||||||
|
number: 38
|
||||||
|
vsync_pin:
|
||||||
|
number: 48
|
||||||
|
data_rate: 1000000.0
|
||||||
|
spi_mode: MODE0
|
||||||
|
cs_pin:
|
||||||
|
number: 21
|
||||||
|
show_test_card: true
|
||||||
6
tests/components/mipi_rgb/test.esp32-p4-idf.yaml
Normal file
6
tests/components/mipi_rgb/test.esp32-p4-idf.yaml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
packages:
|
||||||
|
spi: !include ../../test_build_components/common/spi/esp32-p4-idf.yaml
|
||||||
|
|
||||||
|
psram:
|
||||||
|
|
||||||
|
<<: !include common.yaml
|
||||||
@@ -4,58 +4,4 @@ packages:
|
|||||||
psram:
|
psram:
|
||||||
mode: octal
|
mode: octal
|
||||||
|
|
||||||
display:
|
<<: !include common.yaml
|
||||||
- platform: mipi_rgb
|
|
||||||
spi_id: spi_bus
|
|
||||||
model: ZX2D10GE01R-V4848
|
|
||||||
update_interval: 1s
|
|
||||||
color_order: BGR
|
|
||||||
draw_rounding: 2
|
|
||||||
pixel_mode: 18bit
|
|
||||||
invert_colors: false
|
|
||||||
use_axis_flips: true
|
|
||||||
pclk_frequency: 15000000.0
|
|
||||||
pclk_inverted: true
|
|
||||||
byte_order: big_endian
|
|
||||||
hsync_pulse_width: 10
|
|
||||||
hsync_back_porch: 10
|
|
||||||
hsync_front_porch: 10
|
|
||||||
vsync_pulse_width: 2
|
|
||||||
vsync_back_porch: 12
|
|
||||||
vsync_front_porch: 14
|
|
||||||
data_pins:
|
|
||||||
red:
|
|
||||||
- number: 10
|
|
||||||
- number: 16
|
|
||||||
- number: 9
|
|
||||||
- number: 15
|
|
||||||
- number: 46
|
|
||||||
ignore_strapping_warning: true
|
|
||||||
green:
|
|
||||||
- number: 8
|
|
||||||
- number: 13
|
|
||||||
- number: 18
|
|
||||||
- number: 12
|
|
||||||
- number: 11
|
|
||||||
- number: 17
|
|
||||||
blue:
|
|
||||||
- number: 47
|
|
||||||
- number: 1
|
|
||||||
- number: 0
|
|
||||||
ignore_strapping_warning: true
|
|
||||||
- number: 42
|
|
||||||
- number: 14
|
|
||||||
de_pin:
|
|
||||||
number: 39
|
|
||||||
pclk_pin:
|
|
||||||
number: 45
|
|
||||||
ignore_strapping_warning: true
|
|
||||||
hsync_pin:
|
|
||||||
number: 38
|
|
||||||
vsync_pin:
|
|
||||||
number: 48
|
|
||||||
data_rate: 1000000.0
|
|
||||||
spi_mode: MODE0
|
|
||||||
cs_pin:
|
|
||||||
number: 21
|
|
||||||
show_test_card: true
|
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ esphome:
|
|||||||
id: template_water_heater
|
id: template_water_heater
|
||||||
target_temperature: 50.0
|
target_temperature: 50.0
|
||||||
mode: ECO
|
mode: ECO
|
||||||
|
away: false
|
||||||
|
is_on: true
|
||||||
|
|
||||||
# Templated
|
# Templated
|
||||||
- water_heater.template.publish:
|
- water_heater.template.publish:
|
||||||
@@ -20,6 +22,8 @@ esphome:
|
|||||||
current_temperature: !lambda "return 45.0;"
|
current_temperature: !lambda "return 45.0;"
|
||||||
target_temperature: !lambda "return 55.0;"
|
target_temperature: !lambda "return 55.0;"
|
||||||
mode: !lambda "return water_heater::WATER_HEATER_MODE_GAS;"
|
mode: !lambda "return water_heater::WATER_HEATER_MODE_GAS;"
|
||||||
|
away: !lambda "return true;"
|
||||||
|
is_on: !lambda "return false;"
|
||||||
|
|
||||||
# Test C++ API: set_template() with stateless lambda (no captures)
|
# Test C++ API: set_template() with stateless lambda (no captures)
|
||||||
# NOTE: set_template() is not intended to be a public API, but we test it to ensure it doesn't break.
|
# NOTE: set_template() is not intended to be a public API, but we test it to ensure it doesn't break.
|
||||||
@@ -414,6 +418,8 @@ water_heater:
|
|||||||
current_temperature: !lambda "return 42.0f;"
|
current_temperature: !lambda "return 42.0f;"
|
||||||
target_temperature: !lambda "return 60.0f;"
|
target_temperature: !lambda "return 60.0f;"
|
||||||
mode: !lambda "return water_heater::WATER_HEATER_MODE_ECO;"
|
mode: !lambda "return water_heater::WATER_HEATER_MODE_ECO;"
|
||||||
|
away: !lambda "return false;"
|
||||||
|
is_on: !lambda "return true;"
|
||||||
supported_modes:
|
supported_modes:
|
||||||
- "OFF"
|
- "OFF"
|
||||||
- ECO
|
- ECO
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ class MockUARTComponent : public UARTComponent {
|
|||||||
|
|
||||||
MOCK_METHOD(bool, read_array, (uint8_t * data, size_t len), (override));
|
MOCK_METHOD(bool, read_array, (uint8_t * data, size_t len), (override));
|
||||||
MOCK_METHOD(bool, peek_byte, (uint8_t * data), (override));
|
MOCK_METHOD(bool, peek_byte, (uint8_t * data), (override));
|
||||||
MOCK_METHOD(int, available, (), (override));
|
MOCK_METHOD(size_t, available, (), (override));
|
||||||
MOCK_METHOD(void, flush, (), (override));
|
MOCK_METHOD(void, flush, (), (override));
|
||||||
MOCK_METHOD(void, check_logger_conflict, (), (override));
|
MOCK_METHOD(void, check_logger_conflict, (), (override));
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -68,3 +68,24 @@ voice_assistant:
|
|||||||
- logger.log:
|
- logger.log:
|
||||||
format: "Voice assistant error - code %s, message: %s"
|
format: "Voice assistant error - code %s, message: %s"
|
||||||
args: [code.c_str(), message.c_str()]
|
args: [code.c_str(), message.c_str()]
|
||||||
|
on_timer_started:
|
||||||
|
- logger.log:
|
||||||
|
format: "Timer started: %s"
|
||||||
|
args: [timer.id.c_str()]
|
||||||
|
on_timer_updated:
|
||||||
|
- logger.log:
|
||||||
|
format: "Timer updated: %s"
|
||||||
|
args: [timer.id.c_str()]
|
||||||
|
on_timer_cancelled:
|
||||||
|
- logger.log:
|
||||||
|
format: "Timer cancelled: %s"
|
||||||
|
args: [timer.id.c_str()]
|
||||||
|
on_timer_finished:
|
||||||
|
- logger.log:
|
||||||
|
format: "Timer finished: %s"
|
||||||
|
args: [timer.id.c_str()]
|
||||||
|
on_timer_tick:
|
||||||
|
- lambda: |-
|
||||||
|
for (auto &timer : timers) {
|
||||||
|
ESP_LOGD("timer", "Timer %s: %" PRIu32 "s left", timer.name.c_str(), timer.seconds_left);
|
||||||
|
}
|
||||||
|
|||||||
@@ -58,3 +58,24 @@ voice_assistant:
|
|||||||
- logger.log:
|
- logger.log:
|
||||||
format: "Voice assistant error - code %s, message: %s"
|
format: "Voice assistant error - code %s, message: %s"
|
||||||
args: [code.c_str(), message.c_str()]
|
args: [code.c_str(), message.c_str()]
|
||||||
|
on_timer_started:
|
||||||
|
- logger.log:
|
||||||
|
format: "Timer started: %s"
|
||||||
|
args: [timer.id.c_str()]
|
||||||
|
on_timer_updated:
|
||||||
|
- logger.log:
|
||||||
|
format: "Timer updated: %s"
|
||||||
|
args: [timer.id.c_str()]
|
||||||
|
on_timer_cancelled:
|
||||||
|
- logger.log:
|
||||||
|
format: "Timer cancelled: %s"
|
||||||
|
args: [timer.id.c_str()]
|
||||||
|
on_timer_finished:
|
||||||
|
- logger.log:
|
||||||
|
format: "Timer finished: %s"
|
||||||
|
args: [timer.id.c_str()]
|
||||||
|
on_timer_tick:
|
||||||
|
- lambda: |-
|
||||||
|
for (auto &timer : timers) {
|
||||||
|
ESP_LOGD("timer", "Timer %s: %" PRIu32 "s left", timer.name.c_str(), timer.seconds_left);
|
||||||
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user