mirror of
https://github.com/esphome/esphome.git
synced 2026-02-10 01:32:06 +00:00
Compare commits
84 Commits
beta_preme
...
integratio
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
86c1803538 | ||
|
|
eb8bb260e5 | ||
|
|
ef079f9113 | ||
|
|
647f39504a | ||
|
|
b6c98f0586 | ||
|
|
77b46ba90f | ||
|
|
8cc6915558 | ||
|
|
10e71255a0 | ||
|
|
30521f7de6 | ||
|
|
c97ae656e4 | ||
|
|
f09e72766e | ||
|
|
a320e87a17 | ||
|
|
45932fabea | ||
|
|
f62ea6bdc2 | ||
|
|
675625cf25 | ||
|
|
6baeaf5b7b | ||
|
|
ca8617cf10 | ||
|
|
a0bc6a9922 | ||
|
|
d5295a894b | ||
|
|
fb6b96ff58 | ||
|
|
e07144ef74 | ||
|
|
1c4cf1a3e8 | ||
|
|
a198df34ee | ||
|
|
15904ab583 | ||
|
|
9c9e8ac388 | ||
|
|
04f4636d36 | ||
|
|
3cbadfe42a | ||
|
|
277a11f0ea | ||
|
|
08cca414e7 | ||
|
|
4db5835b6f | ||
|
|
527003e16b | ||
|
|
e9a0d06880 | ||
|
|
b2879f7f99 | ||
|
|
44e9346e9c | ||
|
|
6670c2b6c4 | ||
|
|
6013b473ca | ||
|
|
cc1f83ac35 | ||
|
|
1f1405364d | ||
|
|
5d3ae8cbec | ||
|
|
59a2f6f538 | ||
|
|
a9c37cae26 | ||
|
|
c8a93f31e9 | ||
|
|
f79448a09a | ||
|
|
5e096826c3 | ||
|
|
457d68256d | ||
|
|
a9029fb67a | ||
|
|
cd891d4b16 | ||
|
|
2784059a64 | ||
|
|
4827f53156 | ||
|
|
8dff0ee449 | ||
|
|
a7f04a6cf9 | ||
|
|
53bde863f5 | ||
|
|
dfb0c8670d | ||
|
|
7490efedd7 | ||
|
|
a0f736b7aa | ||
|
|
21f270677b | ||
|
|
d6e692e302 | ||
|
|
991ce396a9 | ||
|
|
68dfb844bd | ||
|
|
9742880bf7 | ||
|
|
13f9726534 | ||
|
|
dd07e25a8f | ||
|
|
a875a2fb9b | ||
|
|
836bfc625d | ||
|
|
2a17592d57 | ||
|
|
04697ac223 | ||
|
|
3f3cf83aab | ||
|
|
39013388dd | ||
|
|
cfbeea9983 | ||
|
|
8f6e1abbce | ||
|
|
c77d70c093 | ||
|
|
25762c62f8 | ||
|
|
441ec35d9f | ||
|
|
33c831dbb8 | ||
|
|
38aeb9be37 | ||
|
|
6b7c52799d | ||
|
|
f19bb2cd0a | ||
|
|
26c98a1e25 | ||
|
|
b544cf2ffe | ||
|
|
6d1281301f | ||
|
|
901192cca1 | ||
|
|
67e7ba4812 | ||
|
|
572376091e | ||
|
|
e7c9808b87 |
@@ -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,
|
||||
"MAX_MESSAGES_PER_BATCH must be >= MAX_INITIAL_PER_BATCH");
|
||||
|
||||
class APIConnection final : public APIServerConnectionBase {
|
||||
class APIConnection final : public APIServerConnection {
|
||||
public:
|
||||
friend class APIServer;
|
||||
friend class ListEntitiesIterator;
|
||||
|
||||
@@ -440,6 +440,19 @@ class PingResponse final : public ProtoMessage {
|
||||
|
||||
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
|
||||
class AreaInfo final : public ProtoMessage {
|
||||
public:
|
||||
@@ -533,6 +546,19 @@ class DeviceInfoResponse final : public ProtoMessage {
|
||||
|
||||
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 {
|
||||
public:
|
||||
static constexpr uint8_t MESSAGE_TYPE = 19;
|
||||
@@ -546,6 +572,19 @@ class ListEntitiesDoneResponse final : public ProtoMessage {
|
||||
|
||||
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
|
||||
class ListEntitiesBinarySensorResponse final : public InfoResponseProtoMessage {
|
||||
public:
|
||||
@@ -998,6 +1037,19 @@ class NoiseEncryptionSetKeyResponse final : public ProtoMessage {
|
||||
};
|
||||
#endif
|
||||
#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 {
|
||||
public:
|
||||
StringRef key{};
|
||||
@@ -1065,6 +1117,19 @@ class HomeassistantActionResponse final : public ProtoDecodableMessage {
|
||||
};
|
||||
#endif
|
||||
#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 {
|
||||
public:
|
||||
static constexpr uint8_t MESSAGE_TYPE = 39;
|
||||
@@ -2095,6 +2160,19 @@ class BluetoothGATTNotifyDataResponse final : public ProtoMessage {
|
||||
|
||||
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 {
|
||||
public:
|
||||
static constexpr uint8_t MESSAGE_TYPE = 81;
|
||||
@@ -2201,6 +2279,19 @@ class BluetoothDeviceUnpairingResponse final : public ProtoMessage {
|
||||
|
||||
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 {
|
||||
public:
|
||||
static constexpr uint8_t MESSAGE_TYPE = 88;
|
||||
|
||||
@@ -764,6 +764,10 @@ const char *PingResponse::dump_to(DumpBuffer &out) const {
|
||||
out.append("PingResponse {}");
|
||||
return out.c_str();
|
||||
}
|
||||
const char *DeviceInfoRequest::dump_to(DumpBuffer &out) const {
|
||||
out.append("DeviceInfoRequest {}");
|
||||
return out.c_str();
|
||||
}
|
||||
#ifdef USE_AREAS
|
||||
const char *AreaInfo::dump_to(DumpBuffer &out) const {
|
||||
MessageDumpHelper helper(out, "AreaInfo");
|
||||
@@ -844,10 +848,18 @@ const char *DeviceInfoResponse::dump_to(DumpBuffer &out) const {
|
||||
#endif
|
||||
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 {
|
||||
out.append("ListEntitiesDoneResponse {}");
|
||||
return out.c_str();
|
||||
}
|
||||
const char *SubscribeStatesRequest::dump_to(DumpBuffer &out) const {
|
||||
out.append("SubscribeStatesRequest {}");
|
||||
return out.c_str();
|
||||
}
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
const char *ListEntitiesBinarySensorResponse::dump_to(DumpBuffer &out) const {
|
||||
MessageDumpHelper helper(out, "ListEntitiesBinarySensorResponse");
|
||||
@@ -1179,6 +1191,10 @@ const char *NoiseEncryptionSetKeyResponse::dump_to(DumpBuffer &out) const {
|
||||
}
|
||||
#endif
|
||||
#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 {
|
||||
MessageDumpHelper helper(out, "HomeassistantServiceMap");
|
||||
dump_field(out, "key", this->key);
|
||||
@@ -1229,6 +1245,10 @@ const char *HomeassistantActionResponse::dump_to(DumpBuffer &out) const {
|
||||
}
|
||||
#endif
|
||||
#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 {
|
||||
MessageDumpHelper helper(out, "SubscribeHomeAssistantStateResponse");
|
||||
dump_field(out, "entity_id", this->entity_id);
|
||||
@@ -1904,6 +1924,10 @@ const char *BluetoothGATTNotifyDataResponse::dump_to(DumpBuffer &out) const {
|
||||
dump_bytes_field(out, "data", this->data_ptr_, this->data_len_);
|
||||
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 {
|
||||
MessageDumpHelper helper(out, "BluetoothConnectionsFreeResponse");
|
||||
dump_field(out, "free", this->free);
|
||||
@@ -1946,6 +1970,10 @@ const char *BluetoothDeviceUnpairingResponse::dump_to(DumpBuffer &out) const {
|
||||
dump_field(out, "error", this->error);
|
||||
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 {
|
||||
MessageDumpHelper helper(out, "BluetoothDeviceClearCacheResponse");
|
||||
dump_field(out, "address", this->address);
|
||||
|
||||
@@ -21,23 +21,6 @@ void APIServerConnectionBase::log_receive_message_(const LogString *name) {
|
||||
#endif
|
||||
|
||||
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) {
|
||||
case HelloRequest::MESSAGE_TYPE: {
|
||||
HelloRequest msg;
|
||||
@@ -76,21 +59,21 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
||||
this->on_ping_response();
|
||||
break;
|
||||
}
|
||||
case 9 /* DeviceInfoRequest is empty */: {
|
||||
case DeviceInfoRequest::MESSAGE_TYPE: {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
this->log_receive_message_(LOG_STR("on_device_info_request"));
|
||||
#endif
|
||||
this->on_device_info_request();
|
||||
break;
|
||||
}
|
||||
case 11 /* ListEntitiesRequest is empty */: {
|
||||
case ListEntitiesRequest::MESSAGE_TYPE: {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
this->log_receive_message_(LOG_STR("on_list_entities_request"));
|
||||
#endif
|
||||
this->on_list_entities_request();
|
||||
break;
|
||||
}
|
||||
case 20 /* SubscribeStatesRequest is empty */: {
|
||||
case SubscribeStatesRequest::MESSAGE_TYPE: {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
this->log_receive_message_(LOG_STR("on_subscribe_states_request"));
|
||||
#endif
|
||||
@@ -151,7 +134,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_API_HOMEASSISTANT_SERVICES
|
||||
case 34 /* SubscribeHomeassistantServicesRequest is empty */: {
|
||||
case SubscribeHomeassistantServicesRequest::MESSAGE_TYPE: {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
this->log_receive_message_(LOG_STR("on_subscribe_homeassistant_services_request"));
|
||||
#endif
|
||||
@@ -169,7 +152,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
||||
break;
|
||||
}
|
||||
#ifdef USE_API_HOMEASSISTANT_STATES
|
||||
case 38 /* SubscribeHomeAssistantStatesRequest is empty */: {
|
||||
case SubscribeHomeAssistantStatesRequest::MESSAGE_TYPE: {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
this->log_receive_message_(LOG_STR("on_subscribe_home_assistant_states_request"));
|
||||
#endif
|
||||
@@ -376,7 +359,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
case 80 /* SubscribeBluetoothConnectionsFreeRequest is empty */: {
|
||||
case SubscribeBluetoothConnectionsFreeRequest::MESSAGE_TYPE: {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
this->log_receive_message_(LOG_STR("on_subscribe_bluetooth_connections_free_request"));
|
||||
#endif
|
||||
@@ -385,7 +368,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
case 87 /* UnsubscribeBluetoothLEAdvertisementsRequest is empty */: {
|
||||
case UnsubscribeBluetoothLEAdvertisementsRequest::MESSAGE_TYPE: {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
this->log_receive_message_(LOG_STR("on_unsubscribe_bluetooth_le_advertisements_request"));
|
||||
#endif
|
||||
@@ -640,4 +623,28 @@ 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
|
||||
|
||||
@@ -228,4 +228,9 @@ class APIServerConnectionBase : public ProtoService {
|
||||
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
|
||||
|
||||
@@ -25,9 +25,7 @@ template<typename... X> class TemplatableStringValue : public TemplatableValue<s
|
||||
|
||||
private:
|
||||
// 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)); // NOLINT
|
||||
}
|
||||
template<typename T> static std::string value_to_string(T &&val) { return to_string(std::forward<T>(val)); }
|
||||
|
||||
// Overloads for string types - needed because std::to_string doesn't support them
|
||||
static std::string value_to_string(char *val) {
|
||||
|
||||
@@ -5,14 +5,6 @@ namespace esphome::binary_sensor {
|
||||
|
||||
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) {
|
||||
// Handle duplicate events
|
||||
if (state == this->last_state_) {
|
||||
@@ -35,7 +27,7 @@ void MultiClickTrigger::on_state_(bool state) {
|
||||
evt.min_length, evt.max_length);
|
||||
this->at_index_ = 1;
|
||||
if (this->timing_.size() == 1 && evt.max_length == 4294967294UL) {
|
||||
this->set_timeout(MULTICLICK_TRIGGER_ID, evt.min_length, [this]() { this->trigger_(); });
|
||||
this->set_timeout("trigger", evt.min_length, [this]() { this->trigger_(); });
|
||||
} else {
|
||||
this->schedule_is_valid_(evt.min_length);
|
||||
this->schedule_is_not_valid_(evt.max_length);
|
||||
@@ -65,13 +57,13 @@ void MultiClickTrigger::on_state_(bool state) {
|
||||
this->schedule_is_not_valid_(evt.max_length);
|
||||
} else if (*this->at_index_ + 1 != this->timing_.size()) {
|
||||
ESP_LOGV(TAG, "B i=%zu min=%" PRIu32, *this->at_index_, evt.min_length); // NOLINT
|
||||
this->cancel_timeout(MULTICLICK_IS_NOT_VALID_ID);
|
||||
this->cancel_timeout("is_not_valid");
|
||||
this->schedule_is_valid_(evt.min_length);
|
||||
} else {
|
||||
ESP_LOGV(TAG, "C i=%zu min=%" PRIu32, *this->at_index_, evt.min_length); // NOLINT
|
||||
this->is_valid_ = false;
|
||||
this->cancel_timeout(MULTICLICK_IS_NOT_VALID_ID);
|
||||
this->set_timeout(MULTICLICK_TRIGGER_ID, evt.min_length, [this]() { this->trigger_(); });
|
||||
this->cancel_timeout("is_not_valid");
|
||||
this->set_timeout("trigger", evt.min_length, [this]() { this->trigger_(); });
|
||||
}
|
||||
|
||||
*this->at_index_ = *this->at_index_ + 1;
|
||||
@@ -79,14 +71,14 @@ void MultiClickTrigger::on_state_(bool state) {
|
||||
void MultiClickTrigger::schedule_cooldown_() {
|
||||
ESP_LOGV(TAG, "Multi Click: Invalid length of press, starting cooldown of %" PRIu32 " ms", this->invalid_cooldown_);
|
||||
this->is_in_cooldown_ = true;
|
||||
this->set_timeout(MULTICLICK_COOLDOWN_ID, this->invalid_cooldown_, [this]() {
|
||||
this->set_timeout("cooldown", this->invalid_cooldown_, [this]() {
|
||||
ESP_LOGV(TAG, "Multi Click: Cooldown ended, matching is now enabled again.");
|
||||
this->is_in_cooldown_ = false;
|
||||
});
|
||||
this->at_index_.reset();
|
||||
this->cancel_timeout(MULTICLICK_TRIGGER_ID);
|
||||
this->cancel_timeout(MULTICLICK_IS_VALID_ID);
|
||||
this->cancel_timeout(MULTICLICK_IS_NOT_VALID_ID);
|
||||
this->cancel_timeout("trigger");
|
||||
this->cancel_timeout("is_valid");
|
||||
this->cancel_timeout("is_not_valid");
|
||||
}
|
||||
void MultiClickTrigger::schedule_is_valid_(uint32_t min_length) {
|
||||
if (min_length == 0) {
|
||||
@@ -94,13 +86,13 @@ void MultiClickTrigger::schedule_is_valid_(uint32_t min_length) {
|
||||
return;
|
||||
}
|
||||
this->is_valid_ = false;
|
||||
this->set_timeout(MULTICLICK_IS_VALID_ID, min_length, [this]() {
|
||||
this->set_timeout("is_valid", min_length, [this]() {
|
||||
ESP_LOGV(TAG, "Multi Click: You can now %s the button.", this->parent_->state ? "RELEASE" : "PRESS");
|
||||
this->is_valid_ = true;
|
||||
});
|
||||
}
|
||||
void MultiClickTrigger::schedule_is_not_valid_(uint32_t max_length) {
|
||||
this->set_timeout(MULTICLICK_IS_NOT_VALID_ID, max_length, [this]() {
|
||||
this->set_timeout("is_not_valid", max_length, [this]() {
|
||||
ESP_LOGV(TAG, "Multi Click: You waited too long to %s.", this->parent_->state ? "RELEASE" : "PRESS");
|
||||
this->is_valid_ = false;
|
||||
this->schedule_cooldown_();
|
||||
@@ -114,9 +106,9 @@ void MultiClickTrigger::cancel() {
|
||||
void MultiClickTrigger::trigger_() {
|
||||
ESP_LOGV(TAG, "Multi Click: Hooray, multi click is valid. Triggering!");
|
||||
this->at_index_.reset();
|
||||
this->cancel_timeout(MULTICLICK_TRIGGER_ID);
|
||||
this->cancel_timeout(MULTICLICK_IS_VALID_ID);
|
||||
this->cancel_timeout(MULTICLICK_IS_NOT_VALID_ID);
|
||||
this->cancel_timeout("trigger");
|
||||
this->cancel_timeout("is_valid");
|
||||
this->cancel_timeout("is_not_valid");
|
||||
this->trigger();
|
||||
}
|
||||
|
||||
|
||||
@@ -6,14 +6,6 @@ namespace esphome::binary_sensor {
|
||||
|
||||
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) {
|
||||
if (this->next_ == nullptr) {
|
||||
this->parent_->send_state_internal(value);
|
||||
@@ -31,16 +23,16 @@ void Filter::input(bool value) {
|
||||
}
|
||||
|
||||
void TimeoutFilter::input(bool value) {
|
||||
this->set_timeout(FILTER_TIMEOUT_ID, this->timeout_delay_.value(), [this]() { this->parent_->invalidate_state(); });
|
||||
this->set_timeout("timeout", 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
|
||||
this->output(value);
|
||||
}
|
||||
|
||||
optional<bool> DelayedOnOffFilter::new_value(bool value) {
|
||||
if (value) {
|
||||
this->set_timeout(FILTER_TIMEOUT_ID, this->on_delay_.value(), [this]() { this->output(true); });
|
||||
this->set_timeout("ON_OFF", this->on_delay_.value(), [this]() { this->output(true); });
|
||||
} else {
|
||||
this->set_timeout(FILTER_TIMEOUT_ID, this->off_delay_.value(), [this]() { this->output(false); });
|
||||
this->set_timeout("ON_OFF", this->off_delay_.value(), [this]() { this->output(false); });
|
||||
}
|
||||
return {};
|
||||
}
|
||||
@@ -49,10 +41,10 @@ float DelayedOnOffFilter::get_setup_priority() const { return setup_priority::HA
|
||||
|
||||
optional<bool> DelayedOnFilter::new_value(bool value) {
|
||||
if (value) {
|
||||
this->set_timeout(FILTER_TIMEOUT_ID, this->delay_.value(), [this]() { this->output(true); });
|
||||
this->set_timeout("ON", this->delay_.value(), [this]() { this->output(true); });
|
||||
return {};
|
||||
} else {
|
||||
this->cancel_timeout(FILTER_TIMEOUT_ID);
|
||||
this->cancel_timeout("ON");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -61,10 +53,10 @@ float DelayedOnFilter::get_setup_priority() const { return setup_priority::HARDW
|
||||
|
||||
optional<bool> DelayedOffFilter::new_value(bool value) {
|
||||
if (!value) {
|
||||
this->set_timeout(FILTER_TIMEOUT_ID, this->delay_.value(), [this]() { this->output(false); });
|
||||
this->set_timeout("OFF", this->delay_.value(), [this]() { this->output(false); });
|
||||
return {};
|
||||
} else {
|
||||
this->cancel_timeout(FILTER_TIMEOUT_ID);
|
||||
this->cancel_timeout("OFF");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -84,8 +76,8 @@ optional<bool> AutorepeatFilter::new_value(bool value) {
|
||||
this->next_timing_();
|
||||
return true;
|
||||
} else {
|
||||
this->cancel_timeout(AUTOREPEAT_TIMING_ID);
|
||||
this->cancel_timeout(AUTOREPEAT_ON_OFF_ID);
|
||||
this->cancel_timeout("TIMING");
|
||||
this->cancel_timeout("ON_OFF");
|
||||
this->active_timing_ = 0;
|
||||
return false;
|
||||
}
|
||||
@@ -96,10 +88,8 @@ void AutorepeatFilter::next_timing_() {
|
||||
// 1st time: starts waiting the first delay
|
||||
// 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
|
||||
if (this->active_timing_ < this->timings_.size()) {
|
||||
this->set_timeout(AUTOREPEAT_TIMING_ID, this->timings_[this->active_timing_].delay,
|
||||
[this]() { this->next_timing_(); });
|
||||
}
|
||||
if (this->active_timing_ < this->timings_.size())
|
||||
this->set_timeout("TIMING", this->timings_[this->active_timing_].delay, [this]() { this->next_timing_(); });
|
||||
|
||||
if (this->active_timing_ <= this->timings_.size()) {
|
||||
this->active_timing_++;
|
||||
@@ -114,8 +104,7 @@ void AutorepeatFilter::next_timing_() {
|
||||
void AutorepeatFilter::next_value_(bool val) {
|
||||
const AutorepeatFilterTiming &timing = this->timings_[this->active_timing_ - 2];
|
||||
this->output(val); // This is at least the second one so not initial
|
||||
this->set_timeout(AUTOREPEAT_ON_OFF_ID, val ? timing.time_on : timing.time_off,
|
||||
[this, val]() { this->next_value_(!val); });
|
||||
this->set_timeout("ON_OFF", val ? timing.time_on : timing.time_off, [this, val]() { this->next_value_(!val); });
|
||||
}
|
||||
|
||||
float AutorepeatFilter::get_setup_priority() const { return setup_priority::HARDWARE; }
|
||||
@@ -126,7 +115,7 @@ optional<bool> LambdaFilter::new_value(bool value) { return this->f_(value); }
|
||||
|
||||
optional<bool> SettleFilter::new_value(bool value) {
|
||||
if (!this->steady_) {
|
||||
this->set_timeout(FILTER_TIMEOUT_ID, this->delay_.value(), [this, value]() {
|
||||
this->set_timeout("SETTLE", this->delay_.value(), [this, value]() {
|
||||
this->steady_ = true;
|
||||
this->output(value);
|
||||
});
|
||||
@@ -134,7 +123,7 @@ optional<bool> SettleFilter::new_value(bool value) {
|
||||
} else {
|
||||
this->steady_ = false;
|
||||
this->output(value);
|
||||
this->set_timeout(FILTER_TIMEOUT_ID, this->delay_.value(), [this]() { this->steady_ = true; });
|
||||
this->set_timeout("SETTLE", this->delay_.value(), [this]() { this->steady_ = true; });
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,16 +46,16 @@ static const uint32_t PKT_TIMEOUT_MS = 200;
|
||||
|
||||
void BL0942::loop() {
|
||||
DataPacket buffer;
|
||||
size_t avail = this->available();
|
||||
int avail = this->available();
|
||||
|
||||
if (!avail) {
|
||||
return;
|
||||
}
|
||||
if (avail < sizeof(buffer)) {
|
||||
if (static_cast<size_t>(avail) < sizeof(buffer)) {
|
||||
if (!this->rx_start_) {
|
||||
this->rx_start_ = millis();
|
||||
} else if (millis() > this->rx_start_ + PKT_TIMEOUT_MS) {
|
||||
ESP_LOGW(TAG, "Junk on wire. Throwing away partial message (%zu bytes)", avail);
|
||||
ESP_LOGW(TAG, "Junk on wire. Throwing away partial message (%d bytes)", avail);
|
||||
this->read_array((uint8_t *) &buffer, avail);
|
||||
this->rx_start_ = 0;
|
||||
}
|
||||
|
||||
@@ -28,15 +28,28 @@ void DlmsMeterComponent::dump_config() {
|
||||
|
||||
void DlmsMeterComponent::loop() {
|
||||
// Read while data is available, netznoe uses two frames so allow 2x max frame length
|
||||
while (this->available()) {
|
||||
if (this->receive_buffer_.size() >= MBUS_MAX_FRAME_LENGTH * 2) {
|
||||
int avail = this->available();
|
||||
if (avail > 0) {
|
||||
size_t remaining = MBUS_MAX_FRAME_LENGTH * 2 - this->receive_buffer_.size();
|
||||
if (remaining == 0) {
|
||||
ESP_LOGW(TAG, "Receive buffer full, dropping remaining bytes");
|
||||
break;
|
||||
} else {
|
||||
// Read all available bytes in batches to reduce UART call overhead.
|
||||
// Cap reads to remaining buffer capacity.
|
||||
if (static_cast<size_t>(avail) > remaining) {
|
||||
avail = remaining;
|
||||
}
|
||||
uint8_t buf[64];
|
||||
while (avail > 0) {
|
||||
size_t to_read = std::min(static_cast<size_t>(avail), sizeof(buf));
|
||||
if (!this->read_array(buf, to_read)) {
|
||||
break;
|
||||
}
|
||||
avail -= to_read;
|
||||
this->receive_buffer_.insert(this->receive_buffer_.end(), buf, buf + to_read);
|
||||
this->last_read_ = millis();
|
||||
}
|
||||
}
|
||||
uint8_t c;
|
||||
this->read_byte(&c);
|
||||
this->receive_buffer_.push_back(c);
|
||||
this->last_read_ = millis();
|
||||
}
|
||||
|
||||
if (!this->receive_buffer_.empty() && millis() - this->last_read_ > this->read_timeout_) {
|
||||
|
||||
@@ -40,9 +40,7 @@ bool Dsmr::ready_to_request_data_() {
|
||||
this->start_requesting_data_();
|
||||
}
|
||||
if (!this->requesting_data_) {
|
||||
while (this->available()) {
|
||||
this->read();
|
||||
}
|
||||
this->drain_rx_buffer_();
|
||||
}
|
||||
}
|
||||
return this->requesting_data_;
|
||||
@@ -115,13 +113,21 @@ void Dsmr::stop_requesting_data_() {
|
||||
} else {
|
||||
ESP_LOGV(TAG, "Stop reading data from P1 port");
|
||||
}
|
||||
while (this->available()) {
|
||||
this->read();
|
||||
}
|
||||
this->drain_rx_buffer_();
|
||||
this->requesting_data_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
void Dsmr::drain_rx_buffer_() {
|
||||
uint8_t buf[64];
|
||||
int avail;
|
||||
while ((avail = this->available()) > 0) {
|
||||
if (!this->read_array(buf, std::min(static_cast<size_t>(avail), sizeof(buf)))) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Dsmr::reset_telegram_() {
|
||||
this->header_found_ = false;
|
||||
this->footer_found_ = false;
|
||||
@@ -133,120 +139,144 @@ void Dsmr::reset_telegram_() {
|
||||
|
||||
void Dsmr::receive_telegram_() {
|
||||
while (this->available_within_timeout_()) {
|
||||
const char c = this->read();
|
||||
// Read all available bytes in batches to reduce UART call overhead.
|
||||
uint8_t buf[64];
|
||||
int avail = this->available();
|
||||
while (avail > 0) {
|
||||
size_t to_read = std::min(static_cast<size_t>(avail), sizeof(buf));
|
||||
if (!this->read_array(buf, to_read))
|
||||
return;
|
||||
avail -= to_read;
|
||||
|
||||
// Find a new telegram header, i.e. forward slash.
|
||||
if (c == '/') {
|
||||
ESP_LOGV(TAG, "Header of telegram found");
|
||||
this->reset_telegram_();
|
||||
this->header_found_ = true;
|
||||
}
|
||||
if (!this->header_found_)
|
||||
continue;
|
||||
for (size_t i = 0; i < to_read; i++) {
|
||||
const char c = static_cast<char>(buf[i]);
|
||||
|
||||
// Check for buffer overflow.
|
||||
if (this->bytes_read_ >= this->max_telegram_len_) {
|
||||
this->reset_telegram_();
|
||||
ESP_LOGE(TAG, "Error: telegram larger than buffer (%d bytes)", this->max_telegram_len_);
|
||||
return;
|
||||
}
|
||||
// Find a new telegram header, i.e. forward slash.
|
||||
if (c == '/') {
|
||||
ESP_LOGV(TAG, "Header of telegram found");
|
||||
this->reset_telegram_();
|
||||
this->header_found_ = true;
|
||||
}
|
||||
if (!this->header_found_)
|
||||
continue;
|
||||
|
||||
// Some v2.2 or v3 meters will send a new value which starts with '('
|
||||
// in a new line, while the value belongs to the previous ObisId. For
|
||||
// proper parsing, remove these new line characters.
|
||||
if (c == '(') {
|
||||
while (true) {
|
||||
auto previous_char = this->telegram_[this->bytes_read_ - 1];
|
||||
if (previous_char == '\n' || previous_char == '\r') {
|
||||
this->bytes_read_--;
|
||||
} else {
|
||||
break;
|
||||
// Check for buffer overflow.
|
||||
if (this->bytes_read_ >= this->max_telegram_len_) {
|
||||
this->reset_telegram_();
|
||||
ESP_LOGE(TAG, "Error: telegram larger than buffer (%d bytes)", this->max_telegram_len_);
|
||||
return;
|
||||
}
|
||||
|
||||
// Some v2.2 or v3 meters will send a new value which starts with '('
|
||||
// in a new line, while the value belongs to the previous ObisId. For
|
||||
// proper parsing, remove these new line characters.
|
||||
if (c == '(') {
|
||||
while (true) {
|
||||
auto previous_char = this->telegram_[this->bytes_read_ - 1];
|
||||
if (previous_char == '\n' || previous_char == '\r') {
|
||||
this->bytes_read_--;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Store the byte in the buffer.
|
||||
this->telegram_[this->bytes_read_] = c;
|
||||
this->bytes_read_++;
|
||||
|
||||
// Check for a footer, i.e. exclamation mark, followed by a hex checksum.
|
||||
if (c == '!') {
|
||||
ESP_LOGV(TAG, "Footer of telegram found");
|
||||
this->footer_found_ = true;
|
||||
continue;
|
||||
}
|
||||
// Check for the end of the hex checksum, i.e. a newline.
|
||||
if (this->footer_found_ && c == '\n') {
|
||||
// Parse the telegram and publish sensor values.
|
||||
this->parse_telegram();
|
||||
this->reset_telegram_();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Store the byte in the buffer.
|
||||
this->telegram_[this->bytes_read_] = c;
|
||||
this->bytes_read_++;
|
||||
|
||||
// Check for a footer, i.e. exclamation mark, followed by a hex checksum.
|
||||
if (c == '!') {
|
||||
ESP_LOGV(TAG, "Footer of telegram found");
|
||||
this->footer_found_ = true;
|
||||
continue;
|
||||
}
|
||||
// Check for the end of the hex checksum, i.e. a newline.
|
||||
if (this->footer_found_ && c == '\n') {
|
||||
// Parse the telegram and publish sensor values.
|
||||
this->parse_telegram();
|
||||
this->reset_telegram_();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Dsmr::receive_encrypted_telegram_() {
|
||||
while (this->available_within_timeout_()) {
|
||||
const char c = this->read();
|
||||
// Read all available bytes in batches to reduce UART call overhead.
|
||||
uint8_t buf[64];
|
||||
int avail = this->available();
|
||||
while (avail > 0) {
|
||||
size_t to_read = std::min(static_cast<size_t>(avail), sizeof(buf));
|
||||
if (!this->read_array(buf, to_read))
|
||||
return;
|
||||
avail -= to_read;
|
||||
|
||||
// Find a new telegram start byte.
|
||||
if (!this->header_found_) {
|
||||
if ((uint8_t) c != 0xDB) {
|
||||
continue;
|
||||
for (size_t i = 0; i < to_read; i++) {
|
||||
const char c = static_cast<char>(buf[i]);
|
||||
|
||||
// Find a new telegram start byte.
|
||||
if (!this->header_found_) {
|
||||
if ((uint8_t) c != 0xDB) {
|
||||
continue;
|
||||
}
|
||||
ESP_LOGV(TAG, "Start byte 0xDB of encrypted telegram found");
|
||||
this->reset_telegram_();
|
||||
this->header_found_ = true;
|
||||
}
|
||||
|
||||
// Check for buffer overflow.
|
||||
if (this->crypt_bytes_read_ >= this->max_telegram_len_) {
|
||||
this->reset_telegram_();
|
||||
ESP_LOGE(TAG, "Error: encrypted telegram larger than buffer (%d bytes)", this->max_telegram_len_);
|
||||
return;
|
||||
}
|
||||
|
||||
// Store the byte in the buffer.
|
||||
this->crypt_telegram_[this->crypt_bytes_read_] = c;
|
||||
this->crypt_bytes_read_++;
|
||||
|
||||
// Read the length of the incoming encrypted telegram.
|
||||
if (this->crypt_telegram_len_ == 0 && this->crypt_bytes_read_ > 20) {
|
||||
// Complete header + data bytes
|
||||
this->crypt_telegram_len_ = 13 + (this->crypt_telegram_[11] << 8 | this->crypt_telegram_[12]);
|
||||
ESP_LOGV(TAG, "Encrypted telegram length: %d bytes", this->crypt_telegram_len_);
|
||||
}
|
||||
|
||||
// Check for the end of the encrypted telegram.
|
||||
if (this->crypt_telegram_len_ == 0 || this->crypt_bytes_read_ != this->crypt_telegram_len_) {
|
||||
continue;
|
||||
}
|
||||
ESP_LOGV(TAG, "End of encrypted telegram found");
|
||||
|
||||
// Decrypt the encrypted telegram.
|
||||
GCM<AES128> *gcmaes128{new GCM<AES128>()};
|
||||
gcmaes128->setKey(this->decryption_key_.data(), gcmaes128->keySize());
|
||||
// the iv is 8 bytes of the system title + 4 bytes frame counter
|
||||
// system title is at byte 2 and frame counter at byte 15
|
||||
for (int i = 10; i < 14; i++)
|
||||
this->crypt_telegram_[i] = this->crypt_telegram_[i + 4];
|
||||
constexpr uint16_t iv_size{12};
|
||||
gcmaes128->setIV(&this->crypt_telegram_[2], iv_size);
|
||||
gcmaes128->decrypt(reinterpret_cast<uint8_t *>(this->telegram_),
|
||||
// the ciphertext start at byte 18
|
||||
&this->crypt_telegram_[18],
|
||||
// cipher size
|
||||
this->crypt_bytes_read_ - 17);
|
||||
delete gcmaes128; // NOLINT(cppcoreguidelines-owning-memory)
|
||||
|
||||
this->bytes_read_ = strnlen(this->telegram_, this->max_telegram_len_);
|
||||
ESP_LOGV(TAG, "Decrypted telegram size: %d bytes", this->bytes_read_);
|
||||
ESP_LOGVV(TAG, "Decrypted telegram: %s", this->telegram_);
|
||||
|
||||
// Parse the decrypted telegram and publish sensor values.
|
||||
this->parse_telegram();
|
||||
this->reset_telegram_();
|
||||
return;
|
||||
}
|
||||
ESP_LOGV(TAG, "Start byte 0xDB of encrypted telegram found");
|
||||
this->reset_telegram_();
|
||||
this->header_found_ = true;
|
||||
}
|
||||
|
||||
// Check for buffer overflow.
|
||||
if (this->crypt_bytes_read_ >= this->max_telegram_len_) {
|
||||
this->reset_telegram_();
|
||||
ESP_LOGE(TAG, "Error: encrypted telegram larger than buffer (%d bytes)", this->max_telegram_len_);
|
||||
return;
|
||||
}
|
||||
|
||||
// Store the byte in the buffer.
|
||||
this->crypt_telegram_[this->crypt_bytes_read_] = c;
|
||||
this->crypt_bytes_read_++;
|
||||
|
||||
// Read the length of the incoming encrypted telegram.
|
||||
if (this->crypt_telegram_len_ == 0 && this->crypt_bytes_read_ > 20) {
|
||||
// Complete header + data bytes
|
||||
this->crypt_telegram_len_ = 13 + (this->crypt_telegram_[11] << 8 | this->crypt_telegram_[12]);
|
||||
ESP_LOGV(TAG, "Encrypted telegram length: %d bytes", this->crypt_telegram_len_);
|
||||
}
|
||||
|
||||
// Check for the end of the encrypted telegram.
|
||||
if (this->crypt_telegram_len_ == 0 || this->crypt_bytes_read_ != this->crypt_telegram_len_) {
|
||||
continue;
|
||||
}
|
||||
ESP_LOGV(TAG, "End of encrypted telegram found");
|
||||
|
||||
// Decrypt the encrypted telegram.
|
||||
GCM<AES128> *gcmaes128{new GCM<AES128>()};
|
||||
gcmaes128->setKey(this->decryption_key_.data(), gcmaes128->keySize());
|
||||
// the iv is 8 bytes of the system title + 4 bytes frame counter
|
||||
// system title is at byte 2 and frame counter at byte 15
|
||||
for (int i = 10; i < 14; i++)
|
||||
this->crypt_telegram_[i] = this->crypt_telegram_[i + 4];
|
||||
constexpr uint16_t iv_size{12};
|
||||
gcmaes128->setIV(&this->crypt_telegram_[2], iv_size);
|
||||
gcmaes128->decrypt(reinterpret_cast<uint8_t *>(this->telegram_),
|
||||
// the ciphertext start at byte 18
|
||||
&this->crypt_telegram_[18],
|
||||
// cipher size
|
||||
this->crypt_bytes_read_ - 17);
|
||||
delete gcmaes128; // NOLINT(cppcoreguidelines-owning-memory)
|
||||
|
||||
this->bytes_read_ = strnlen(this->telegram_, this->max_telegram_len_);
|
||||
ESP_LOGV(TAG, "Decrypted telegram size: %d bytes", this->bytes_read_);
|
||||
ESP_LOGVV(TAG, "Decrypted telegram: %s", this->telegram_);
|
||||
|
||||
// Parse the decrypted telegram and publish sensor values.
|
||||
this->parse_telegram();
|
||||
this->reset_telegram_();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -85,6 +85,7 @@ class Dsmr : public Component, public uart::UARTDevice {
|
||||
void receive_telegram_();
|
||||
void receive_encrypted_telegram_();
|
||||
void reset_telegram_();
|
||||
void drain_rx_buffer_();
|
||||
|
||||
/// Wait for UART data to become available within the read timeout.
|
||||
///
|
||||
|
||||
@@ -1435,10 +1435,6 @@ async def to_code(config):
|
||||
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:
|
||||
cg.add_build_flag("-DUSE_ESP_IDF")
|
||||
cg.add_build_flag("-DUSE_ESP32_FRAMEWORK_ESP_IDF")
|
||||
|
||||
@@ -48,7 +48,7 @@ class ESPBTUUID {
|
||||
|
||||
// Remove before 2026.8.0
|
||||
ESPDEPRECATED("Use to_str() instead. Removed in 2026.8.0", "2026.2.0")
|
||||
std::string to_string() const; // NOLINT
|
||||
std::string to_string() const;
|
||||
const char *to_str(std::span<char, UUID_STR_LEN> output) const;
|
||||
|
||||
protected:
|
||||
|
||||
@@ -134,23 +134,25 @@ ErrorCode ArduinoI2CBus::write_readv(uint8_t address, const uint8_t *write_buffe
|
||||
for (size_t j = 0; j != read_count; j++)
|
||||
read_buffer[j] = wire_->read();
|
||||
}
|
||||
// Avoid switch to prevent compiler-generated lookup table in RAM on ESP8266
|
||||
if (status == 0)
|
||||
return ERROR_OK;
|
||||
if (status == 1) {
|
||||
ESP_LOGVV(TAG, "TX failed: buffer not large enough");
|
||||
return ERROR_UNKNOWN;
|
||||
switch (status) {
|
||||
case 0:
|
||||
return ERROR_OK;
|
||||
case 1:
|
||||
// transmit buffer not large enough
|
||||
ESP_LOGVV(TAG, "TX failed: buffer not large enough");
|
||||
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:
|
||||
|
||||
@@ -335,9 +335,10 @@ void LD2420Component::revert_config_action() {
|
||||
|
||||
void LD2420Component::loop() {
|
||||
// If there is a active send command do not process it here, the send command call will handle it.
|
||||
while (!this->cmd_active_ && this->available()) {
|
||||
this->readline_(this->read(), this->buffer_data_, MAX_LINE_LENGTH);
|
||||
if (this->cmd_active_) {
|
||||
return;
|
||||
}
|
||||
this->read_batch_(this->buffer_data_);
|
||||
}
|
||||
|
||||
void LD2420Component::update_radar_data(uint16_t const *gate_energy, uint8_t sample_number) {
|
||||
@@ -539,6 +540,23 @@ void LD2420Component::handle_simple_mode_(const uint8_t *inbuf, int len) {
|
||||
}
|
||||
}
|
||||
|
||||
void LD2420Component::read_batch_(std::span<uint8_t, MAX_LINE_LENGTH> buffer) {
|
||||
// Read all available bytes in batches to reduce UART call overhead.
|
||||
int avail = this->available();
|
||||
uint8_t buf[MAX_LINE_LENGTH];
|
||||
while (avail > 0) {
|
||||
size_t to_read = std::min(static_cast<size_t>(avail), sizeof(buf));
|
||||
if (!this->read_array(buf, to_read)) {
|
||||
break;
|
||||
}
|
||||
avail -= to_read;
|
||||
|
||||
for (size_t i = 0; i < to_read; i++) {
|
||||
this->readline_(buf[i], buffer.data(), buffer.size());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LD2420Component::handle_ack_data_(uint8_t *buffer, int len) {
|
||||
this->cmd_reply_.command = buffer[CMD_FRAME_COMMAND];
|
||||
this->cmd_reply_.length = buffer[CMD_FRAME_DATA_LENGTH];
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "esphome/components/uart/uart.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include <span>
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
#include "esphome/components/text_sensor/text_sensor.h"
|
||||
#endif
|
||||
@@ -165,6 +166,7 @@ class LD2420Component : public Component, public uart::UARTDevice {
|
||||
void handle_energy_mode_(uint8_t *buffer, int len);
|
||||
void handle_ack_data_(uint8_t *buffer, int len);
|
||||
void readline_(int rx_data, uint8_t *buffer, int len);
|
||||
void read_batch_(std::span<uint8_t, MAX_LINE_LENGTH> buffer);
|
||||
void set_calibration_(bool state) { this->calibration_ = state; };
|
||||
bool get_calibration_() { return this->calibration_; };
|
||||
|
||||
|
||||
@@ -120,101 +120,3 @@ DriverChip(
|
||||
(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,
|
||||
)
|
||||
from esphome.components.display import CONF_SHOW_TEST_CARD
|
||||
from esphome.components.esp32 import VARIANT_ESP32P4, VARIANT_ESP32S3, only_on_variant
|
||||
from esphome.components.esp32 import VARIANT_ESP32S3, only_on_variant
|
||||
from esphome.components.mipi import (
|
||||
COLOR_ORDERS,
|
||||
CONF_DE_PIN,
|
||||
@@ -225,7 +225,7 @@ def _config_schema(config):
|
||||
return cv.All(
|
||||
schema,
|
||||
cv.only_on_esp32,
|
||||
only_on_variant(supported=[VARIANT_ESP32S3, VARIANT_ESP32P4]),
|
||||
only_on_variant(supported=[VARIANT_ESP32S3]),
|
||||
)(config)
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#if defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32P4)
|
||||
#ifdef USE_ESP32_VARIANT_ESP32S3
|
||||
#include "mipi_rgb.h"
|
||||
#include "esphome/core/gpio.h"
|
||||
#include "esphome/core/hal.h"
|
||||
@@ -401,4 +401,4 @@ void MipiRgb::dump_config() {
|
||||
|
||||
} // namespace mipi_rgb
|
||||
} // namespace esphome
|
||||
#endif // defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32P4)
|
||||
#endif // USE_ESP32_VARIANT_ESP32S3
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#if defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32P4)
|
||||
#ifdef USE_ESP32_VARIANT_ESP32S3
|
||||
#include "esphome/core/gpio.h"
|
||||
#include "esphome/components/display/display.h"
|
||||
#include "esp_lcd_panel_ops.h"
|
||||
@@ -28,7 +28,7 @@ class MipiRgb : public display::Display {
|
||||
void setup() override;
|
||||
void loop() override;
|
||||
void update() override;
|
||||
void fill(Color color) override;
|
||||
void fill(Color color);
|
||||
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;
|
||||
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_data_(uint8_t value);
|
||||
void write_init_sequence_();
|
||||
void dump_config() override;
|
||||
void dump_config();
|
||||
|
||||
GPIOPin *dc_pin_{nullptr};
|
||||
std::vector<uint8_t> init_sequence_;
|
||||
|
||||
@@ -228,50 +228,39 @@ void Modbus::send(uint8_t address, uint8_t function_code, uint16_t start_address
|
||||
return;
|
||||
}
|
||||
|
||||
static constexpr size_t ADDR_SIZE = 1;
|
||||
static constexpr size_t FC_SIZE = 1;
|
||||
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;
|
||||
std::vector<uint8_t> data;
|
||||
data.push_back(address);
|
||||
data.push_back(function_code);
|
||||
if (this->role == ModbusRole::CLIENT) {
|
||||
data[pos++] = start_address >> 8;
|
||||
data[pos++] = start_address >> 0;
|
||||
data.push_back(start_address >> 8);
|
||||
data.push_back(start_address >> 0);
|
||||
if (function_code != ModbusFunctionCode::WRITE_SINGLE_COIL &&
|
||||
function_code != ModbusFunctionCode::WRITE_SINGLE_REGISTER) {
|
||||
data[pos++] = number_of_entities >> 8;
|
||||
data[pos++] = number_of_entities >> 0;
|
||||
data.push_back(number_of_entities >> 8);
|
||||
data.push_back(number_of_entities >> 0);
|
||||
}
|
||||
}
|
||||
|
||||
if (payload != nullptr) {
|
||||
if (this->role == ModbusRole::SERVER || function_code == ModbusFunctionCode::WRITE_MULTIPLE_COILS ||
|
||||
function_code == ModbusFunctionCode::WRITE_MULTIPLE_REGISTERS) { // Write multiple
|
||||
data[pos++] = payload_len; // Byte count is required for write
|
||||
data.push_back(payload_len); // Byte count is required for write
|
||||
} else {
|
||||
payload_len = 2; // Write single register or coil
|
||||
}
|
||||
for (int i = 0; i < payload_len; i++) {
|
||||
data[pos++] = payload[i];
|
||||
data.push_back(payload[i]);
|
||||
}
|
||||
}
|
||||
|
||||
auto crc = crc16(data, pos);
|
||||
data[pos++] = crc >> 0;
|
||||
data[pos++] = crc >> 8;
|
||||
auto crc = crc16(data.data(), data.size());
|
||||
data.push_back(crc >> 0);
|
||||
data.push_back(crc >> 8);
|
||||
|
||||
if (this->flow_control_pin_ != nullptr)
|
||||
this->flow_control_pin_->digital_write(true);
|
||||
|
||||
this->write_array(data, pos);
|
||||
this->write_array(data);
|
||||
this->flush();
|
||||
|
||||
if (this->flow_control_pin_ != nullptr)
|
||||
@@ -281,7 +270,7 @@ void Modbus::send(uint8_t address, uint8_t function_code, uint16_t start_address
|
||||
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
|
||||
char hex_buf[format_hex_pretty_size(MODBUS_MAX_LOG_BYTES)];
|
||||
#endif
|
||||
ESP_LOGV(TAG, "Modbus write: %s", format_hex_pretty_to(hex_buf, data, pos));
|
||||
ESP_LOGV(TAG, "Modbus write: %s", format_hex_pretty_to(hex_buf, data.data(), data.size()));
|
||||
}
|
||||
|
||||
// Helper function for lambdas
|
||||
|
||||
@@ -56,17 +56,23 @@ void PylontechComponent::setup() {
|
||||
void PylontechComponent::update() { this->write_str("pwr\n"); }
|
||||
|
||||
void PylontechComponent::loop() {
|
||||
if (this->available() > 0) {
|
||||
int avail = this->available();
|
||||
if (avail > 0) {
|
||||
// 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
|
||||
uint8_t data;
|
||||
int recv = 0;
|
||||
while (this->available() > 0) {
|
||||
if (this->read_byte(&data)) {
|
||||
buffer_[buffer_index_write_] += (char) data;
|
||||
recv++;
|
||||
if (buffer_[buffer_index_write_].back() == static_cast<char>(ASCII_LF) ||
|
||||
buffer_[buffer_index_write_].length() >= MAX_DATA_LENGTH_BYTES) {
|
||||
uint8_t buf[64];
|
||||
while (avail > 0) {
|
||||
size_t to_read = std::min(static_cast<size_t>(avail), sizeof(buf));
|
||||
if (!this->read_array(buf, to_read)) {
|
||||
break;
|
||||
}
|
||||
avail -= to_read;
|
||||
recv += to_read;
|
||||
|
||||
for (size_t i = 0; i < to_read; i++) {
|
||||
buffer_[buffer_index_write_] += (char) buf[i];
|
||||
if (buf[i] == ASCII_LF || buffer_[buffer_index_write_].length() >= MAX_DATA_LENGTH_BYTES) {
|
||||
// complete line received
|
||||
buffer_index_write_ = (buffer_index_write_ + 1) % NUM_BUFFERS;
|
||||
}
|
||||
|
||||
@@ -9,11 +9,6 @@ namespace esphome::sensor {
|
||||
|
||||
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
|
||||
void Filter::input(float value) {
|
||||
ESP_LOGVV(TAG, "Filter(%p)::input(%f)", this, value);
|
||||
@@ -196,7 +191,7 @@ optional<float> ThrottleAverageFilter::new_value(float value) {
|
||||
return {};
|
||||
}
|
||||
void ThrottleAverageFilter::setup() {
|
||||
this->set_interval(FILTER_ID, this->time_period_, [this]() {
|
||||
this->set_interval("throttle_average", this->time_period_, [this]() {
|
||||
ESP_LOGVV(TAG, "ThrottleAverageFilter(%p)::interval(sum=%f, n=%i)", this, this->sum_, this->n_);
|
||||
if (this->n_ == 0) {
|
||||
if (this->have_nan_)
|
||||
@@ -388,7 +383,7 @@ optional<float> TimeoutFilterConfigured::new_value(float value) {
|
||||
|
||||
// DebounceFilter
|
||||
optional<float> DebounceFilter::new_value(float value) {
|
||||
this->set_timeout(FILTER_ID, this->time_period_, [this, value]() { this->output(value); });
|
||||
this->set_timeout("debounce", this->time_period_, [this, value]() { this->output(value); });
|
||||
|
||||
return {};
|
||||
}
|
||||
@@ -411,7 +406,7 @@ optional<float> HeartbeatFilter::new_value(float value) {
|
||||
}
|
||||
|
||||
void HeartbeatFilter::setup() {
|
||||
this->set_interval(FILTER_ID, this->time_period_, [this]() {
|
||||
this->set_interval("heartbeat", this->time_period_, [this]() {
|
||||
ESP_LOGVV(TAG, "HeartbeatFilter(%p)::interval(has_value=%s, last_input=%f)", this, YESNO(this->has_value_),
|
||||
this->last_input_);
|
||||
if (!this->has_value_)
|
||||
|
||||
@@ -251,7 +251,7 @@ void Tormatic::stop_at_target_() {
|
||||
// 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.
|
||||
optional<GateStatus> Tormatic::read_gate_status_() {
|
||||
if (this->available() < sizeof(MessageHeader)) {
|
||||
if (this->available() < static_cast<int>(sizeof(MessageHeader))) {
|
||||
return {};
|
||||
}
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ class UARTDevice {
|
||||
return res;
|
||||
}
|
||||
|
||||
size_t available() { return this->parent_->available(); }
|
||||
int available() { return this->parent_->available(); }
|
||||
|
||||
void flush() { this->parent_->flush(); }
|
||||
|
||||
|
||||
@@ -5,13 +5,13 @@ namespace esphome::uart {
|
||||
static const char *const TAG = "uart";
|
||||
|
||||
bool UARTComponent::check_read_timeout_(size_t len) {
|
||||
if (this->available() >= len)
|
||||
if (this->available() >= int(len))
|
||||
return true;
|
||||
|
||||
uint32_t start_time = millis();
|
||||
while (this->available() < len) {
|
||||
while (this->available() < int(len)) {
|
||||
if (millis() - start_time > 100) {
|
||||
ESP_LOGE(TAG, "Reading from UART timed out at byte %zu!", this->available());
|
||||
ESP_LOGE(TAG, "Reading from UART timed out at byte %u!", this->available());
|
||||
return false;
|
||||
}
|
||||
yield();
|
||||
|
||||
@@ -69,7 +69,7 @@ class UARTComponent {
|
||||
|
||||
// Pure virtual method to return the number of bytes available for reading.
|
||||
// @return Number of available bytes.
|
||||
virtual size_t available() = 0;
|
||||
virtual int available() = 0;
|
||||
|
||||
// Pure virtual method to block until all bytes have been written to the UART bus.
|
||||
virtual void flush() = 0;
|
||||
|
||||
@@ -206,7 +206,7 @@ bool ESP8266UartComponent::read_array(uint8_t *data, size_t len) {
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
size_t ESP8266UartComponent::available() {
|
||||
int ESP8266UartComponent::available() {
|
||||
if (this->hw_serial_ != nullptr) {
|
||||
return this->hw_serial_->available();
|
||||
} else {
|
||||
@@ -329,14 +329,11 @@ uint8_t ESP8266SoftwareSerial::peek_byte() {
|
||||
void ESP8266SoftwareSerial::flush() {
|
||||
// Flush is a NO-OP with software serial, all bytes are written immediately.
|
||||
}
|
||||
size_t ESP8266SoftwareSerial::available() {
|
||||
// Read volatile rx_in_pos_ once to avoid TOCTOU race with ISR.
|
||||
// When in >= out, data is contiguous: [out..in).
|
||||
// When in < out, data wraps: [out..buf_size) + [0..in).
|
||||
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;
|
||||
int ESP8266SoftwareSerial::available() {
|
||||
int avail = int(this->rx_in_pos_) - int(this->rx_out_pos_);
|
||||
if (avail < 0)
|
||||
return avail + this->rx_buffer_size_;
|
||||
return avail;
|
||||
}
|
||||
|
||||
} // namespace esphome::uart
|
||||
|
||||
@@ -23,7 +23,7 @@ class ESP8266SoftwareSerial {
|
||||
|
||||
void write_byte(uint8_t data);
|
||||
|
||||
size_t available();
|
||||
int available();
|
||||
|
||||
protected:
|
||||
static void gpio_intr(ESP8266SoftwareSerial *arg);
|
||||
@@ -57,7 +57,7 @@ class ESP8266UartComponent : public UARTComponent, public Component {
|
||||
bool peek_byte(uint8_t *data) override;
|
||||
bool read_array(uint8_t *data, size_t len) override;
|
||||
|
||||
size_t available() override;
|
||||
int available() override;
|
||||
void flush() override;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
size_t IDFUARTComponent::available() {
|
||||
int IDFUARTComponent::available() {
|
||||
size_t available = 0;
|
||||
esp_err_t err;
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ class IDFUARTComponent : public UARTComponent, public Component {
|
||||
bool peek_byte(uint8_t *data) override;
|
||||
bool read_array(uint8_t *data, size_t len) override;
|
||||
|
||||
size_t available() override;
|
||||
int available() override;
|
||||
void flush() override;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
size_t HostUartComponent::available() {
|
||||
int HostUartComponent::available() {
|
||||
if (this->file_descriptor_ == -1) {
|
||||
return 0;
|
||||
}
|
||||
@@ -275,10 +275,9 @@ size_t HostUartComponent::available() {
|
||||
this->update_error_(strerror(errno));
|
||||
return 0;
|
||||
}
|
||||
size_t result = available;
|
||||
if (this->has_peek_)
|
||||
result++;
|
||||
return result;
|
||||
available++;
|
||||
return available;
|
||||
};
|
||||
|
||||
void HostUartComponent::flush() {
|
||||
|
||||
@@ -17,7 +17,7 @@ class HostUartComponent : public UARTComponent, public Component {
|
||||
void write_array(const uint8_t *data, size_t len) override;
|
||||
bool peek_byte(uint8_t *data) override;
|
||||
bool read_array(uint8_t *data, size_t len) override;
|
||||
size_t available() override;
|
||||
int available() override;
|
||||
void flush() override;
|
||||
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;
|
||||
}
|
||||
|
||||
size_t LibreTinyUARTComponent::available() { return this->serial_->available(); }
|
||||
int LibreTinyUARTComponent::available() { return this->serial_->available(); }
|
||||
void LibreTinyUARTComponent::flush() {
|
||||
ESP_LOGVV(TAG, " Flushing");
|
||||
this->serial_->flush();
|
||||
|
||||
@@ -21,7 +21,7 @@ class LibreTinyUARTComponent : public UARTComponent, public Component {
|
||||
bool peek_byte(uint8_t *data) override;
|
||||
bool read_array(uint8_t *data, size_t len) override;
|
||||
|
||||
size_t available() override;
|
||||
int available() override;
|
||||
void flush() override;
|
||||
|
||||
uint16_t get_config();
|
||||
|
||||
@@ -186,7 +186,7 @@ bool RP2040UartComponent::read_array(uint8_t *data, size_t len) {
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
size_t RP2040UartComponent::available() { return this->serial_->available(); }
|
||||
int RP2040UartComponent::available() { return this->serial_->available(); }
|
||||
void RP2040UartComponent::flush() {
|
||||
ESP_LOGVV(TAG, " Flushing");
|
||||
this->serial_->flush();
|
||||
|
||||
@@ -24,7 +24,7 @@ class RP2040UartComponent : public UARTComponent, public Component {
|
||||
bool peek_byte(uint8_t *data) override;
|
||||
bool read_array(uint8_t *data, size_t len) override;
|
||||
|
||||
size_t available() override;
|
||||
int available() override;
|
||||
void flush() override;
|
||||
|
||||
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;
|
||||
bool peek_byte(uint8_t *data) override;
|
||||
bool read_array(uint8_t *data, size_t len) override;
|
||||
size_t available() override;
|
||||
int available() override;
|
||||
void flush() override;
|
||||
|
||||
protected:
|
||||
|
||||
@@ -318,12 +318,12 @@ bool USBCDCACMInstance::read_array(uint8_t *data, size_t len) {
|
||||
return bytes_read == original_len;
|
||||
}
|
||||
|
||||
size_t USBCDCACMInstance::available() {
|
||||
int USBCDCACMInstance::available() {
|
||||
UBaseType_t waiting = 0;
|
||||
if (this->usb_rx_ringbuf_ != nullptr) {
|
||||
vRingbufferGetInfo(this->usb_rx_ringbuf_, nullptr, nullptr, nullptr, nullptr, &waiting);
|
||||
}
|
||||
return waiting + (this->has_peek_ ? 1 : 0);
|
||||
return static_cast<int>(waiting) + (this->has_peek_ ? 1 : 0);
|
||||
}
|
||||
|
||||
void USBCDCACMInstance::flush() {
|
||||
|
||||
@@ -97,7 +97,7 @@ class USBUartChannel : public uart::UARTComponent, public Parented<USBUartCompon
|
||||
bool peek_byte(uint8_t *data) override;
|
||||
;
|
||||
bool read_array(uint8_t *data, size_t len) override;
|
||||
size_t available() override { return this->input_buffer_.get_available(); }
|
||||
int available() override { return static_cast<int>(this->input_buffer_.get_available()); }
|
||||
void flush() override {}
|
||||
void check_logger_conflict() override {}
|
||||
void set_parity(UARTParityOptions parity) { this->parity_ = parity; }
|
||||
|
||||
@@ -371,12 +371,7 @@ async def to_code(config):
|
||||
if on_timer_tick := config.get(CONF_ON_TIMER_TICK):
|
||||
await automation.build_automation(
|
||||
var.get_timer_tick_trigger(),
|
||||
[
|
||||
(
|
||||
cg.std_vector.template(Timer).operator("const").operator("ref"),
|
||||
"timers",
|
||||
)
|
||||
],
|
||||
[(cg.std_vector.template(Timer), "timers")],
|
||||
on_timer_tick,
|
||||
)
|
||||
has_timers = True
|
||||
|
||||
@@ -859,43 +859,35 @@ void VoiceAssistant::on_audio(const api::VoiceAssistantAudio &msg) {
|
||||
}
|
||||
|
||||
void VoiceAssistant::on_timer_event(const api::VoiceAssistantTimerEventResponse &msg) {
|
||||
// Find existing timer or add a new one
|
||||
auto it = this->timers_.begin();
|
||||
for (; it != this->timers_.end(); ++it) {
|
||||
if (it->id == msg.timer_id)
|
||||
break;
|
||||
}
|
||||
if (it == this->timers_.end()) {
|
||||
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;
|
||||
|
||||
Timer timer = {
|
||||
.id = msg.timer_id,
|
||||
.name = msg.name,
|
||||
.total_seconds = msg.total_seconds,
|
||||
.seconds_left = msg.seconds_left,
|
||||
.is_active = msg.is_active,
|
||||
};
|
||||
this->timers_[timer.id] = timer;
|
||||
char timer_buf[Timer::TO_STR_BUFFER_SIZE];
|
||||
ESP_LOGD(TAG,
|
||||
"Timer Event\n"
|
||||
" Type: %" PRId32 "\n"
|
||||
" %s",
|
||||
msg.event_type, it->to_str(timer_buf));
|
||||
msg.event_type, timer.to_str(timer_buf));
|
||||
|
||||
switch (msg.event_type) {
|
||||
case api::enums::VOICE_ASSISTANT_TIMER_STARTED:
|
||||
this->timer_started_trigger_.trigger(*it);
|
||||
this->timer_started_trigger_.trigger(timer);
|
||||
break;
|
||||
case api::enums::VOICE_ASSISTANT_TIMER_UPDATED:
|
||||
this->timer_updated_trigger_.trigger(*it);
|
||||
this->timer_updated_trigger_.trigger(timer);
|
||||
break;
|
||||
case api::enums::VOICE_ASSISTANT_TIMER_CANCELLED:
|
||||
this->timer_cancelled_trigger_.trigger(*it);
|
||||
this->timers_.erase(it);
|
||||
this->timer_cancelled_trigger_.trigger(timer);
|
||||
this->timers_.erase(timer.id);
|
||||
break;
|
||||
case api::enums::VOICE_ASSISTANT_TIMER_FINISHED:
|
||||
this->timer_finished_trigger_.trigger(*it);
|
||||
this->timers_.erase(it);
|
||||
this->timer_finished_trigger_.trigger(timer);
|
||||
this->timers_.erase(timer.id);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -909,12 +901,16 @@ void VoiceAssistant::on_timer_event(const api::VoiceAssistantTimerEventResponse
|
||||
}
|
||||
|
||||
void VoiceAssistant::timer_tick_() {
|
||||
for (auto &timer : this->timers_) {
|
||||
std::vector<Timer> res;
|
||||
res.reserve(this->timers_.size());
|
||||
for (auto &pair : this->timers_) {
|
||||
auto &timer = pair.second;
|
||||
if (timer.is_active && timer.seconds_left > 0) {
|
||||
timer.seconds_left--;
|
||||
}
|
||||
res.push_back(timer);
|
||||
}
|
||||
this->timer_tick_trigger_.trigger(this->timers_);
|
||||
this->timer_tick_trigger_.trigger(res);
|
||||
}
|
||||
|
||||
void VoiceAssistant::on_announce(const api::VoiceAssistantAnnounceRequest &msg) {
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
#include "esphome/components/socket/socket.h"
|
||||
|
||||
#include <span>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace esphome {
|
||||
@@ -82,7 +83,7 @@ struct Timer {
|
||||
}
|
||||
// Remove before 2026.8.0
|
||||
ESPDEPRECATED("Use to_str() instead. Removed in 2026.8.0", "2026.2.0")
|
||||
std::string to_string() const { // NOLINT
|
||||
std::string to_string() const {
|
||||
char buffer[TO_STR_BUFFER_SIZE];
|
||||
return this->to_str(buffer);
|
||||
}
|
||||
@@ -225,9 +226,9 @@ class VoiceAssistant : public Component {
|
||||
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_finished_trigger() { return &this->timer_finished_trigger_; }
|
||||
Trigger<const std::vector<Timer> &> *get_timer_tick_trigger() { return &this->timer_tick_trigger_; }
|
||||
Trigger<std::vector<Timer>> *get_timer_tick_trigger() { return &this->timer_tick_trigger_; }
|
||||
void set_has_timers(bool has_timers) { this->has_timers_ = has_timers; }
|
||||
const std::vector<Timer> &get_timers() const { return this->timers_; }
|
||||
const std::unordered_map<std::string, Timer> &get_timers() const { return this->timers_; }
|
||||
|
||||
protected:
|
||||
bool allocate_buffers_();
|
||||
@@ -266,13 +267,13 @@ class VoiceAssistant : public Component {
|
||||
|
||||
api::APIConnection *api_client_{nullptr};
|
||||
|
||||
std::vector<Timer> timers_;
|
||||
std::unordered_map<std::string, Timer> timers_;
|
||||
void timer_tick_();
|
||||
Trigger<Timer> timer_started_trigger_;
|
||||
Trigger<Timer> timer_finished_trigger_;
|
||||
Trigger<Timer> timer_updated_trigger_;
|
||||
Trigger<Timer> timer_cancelled_trigger_;
|
||||
Trigger<const std::vector<Timer> &> timer_tick_trigger_;
|
||||
Trigger<std::vector<Timer>> timer_tick_trigger_;
|
||||
bool has_timers_{false};
|
||||
bool timer_tick_running_{false};
|
||||
|
||||
|
||||
@@ -90,22 +90,9 @@ class WaterHeaterCall {
|
||||
float get_target_temperature_low() const { return this->target_temperature_low_; }
|
||||
float get_target_temperature_high() const { return this->target_temperature_high_; }
|
||||
/// 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_; }
|
||||
|
||||
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 {};
|
||||
}
|
||||
/// Get mask of state flags that are being changed
|
||||
uint32_t get_state_mask() const { return this->state_mask_; }
|
||||
|
||||
protected:
|
||||
void validate_();
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
from pathlib import Path
|
||||
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ID
|
||||
from esphome.core import CORE, coroutine_with_priority
|
||||
from esphome.coroutine import CoroPriority
|
||||
from esphome.helpers import copy_file_if_changed
|
||||
|
||||
CODEOWNERS = ["@esphome/core"]
|
||||
DEPENDENCIES = ["network"]
|
||||
@@ -52,15 +49,5 @@ async def to_code(config):
|
||||
CORE.add_platformio_option(
|
||||
"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
|
||||
cg.add_library("ESP32Async/ESPAsyncWebServer", "3.9.6")
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
# 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);
|
||||
}
|
||||
|
||||
size_t WeikaiChannel::available() {
|
||||
int WeikaiChannel::available() {
|
||||
size_t available = this->receive_buffer_.count();
|
||||
if (!available)
|
||||
available = xfer_fifo_to_buffer_();
|
||||
|
||||
@@ -374,7 +374,7 @@ class WeikaiChannel : public uart::UARTComponent {
|
||||
|
||||
/// @brief Returns the number of bytes in the receive buffer
|
||||
/// @return the number of bytes available in the receiver fifo
|
||||
size_t available() override;
|
||||
int available() override;
|
||||
|
||||
/// @brief Flush the output fifo.
|
||||
/// @details If we refer to Serial.flush() in Arduino it says: ** Waits for the transmission of outgoing serial data
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
#include "esphome/core/log.h"
|
||||
#include <zephyr/settings/settings.h>
|
||||
#include <zephyr/storage/flash_map.h>
|
||||
#include "esphome/core/hal.h"
|
||||
|
||||
extern "C" {
|
||||
#include <zboss_api.h>
|
||||
@@ -224,7 +223,6 @@ void ZigbeeComponent::dump_config() {
|
||||
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_pan_id());
|
||||
dump_reporting_();
|
||||
}
|
||||
|
||||
static void send_attribute_report(zb_bufid_t bufid, zb_uint16_t cmd_id) {
|
||||
@@ -246,33 +244,6 @@ void ZigbeeComponent::factory_reset() {
|
||||
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
|
||||
|
||||
extern "C" void zboss_signal_handler(zb_uint8_t param) {
|
||||
|
||||
@@ -87,7 +87,6 @@ class ZigbeeComponent : public Component {
|
||||
#ifdef USE_ZIGBEE_WIPE_ON_BOOT
|
||||
void erase_flash_(int area);
|
||||
#endif
|
||||
void dump_reporting_();
|
||||
std::array<std::function<void(zb_bufid_t bufid)>, ZIGBEE_ENDPOINTS_COUNT> callbacks_{};
|
||||
CallbackManager<void()> join_cb_;
|
||||
Trigger<> join_trigger_;
|
||||
|
||||
@@ -191,17 +191,15 @@ template<typename... Ts> class DelayAction : public Action<Ts...>, public Compon
|
||||
// instead of std::bind to avoid bind overhead (~16 bytes heap + faster execution)
|
||||
if constexpr (sizeof...(Ts) == 0) {
|
||||
App.scheduler.set_timer_common_(
|
||||
this, Scheduler::SchedulerItem::TIMEOUT, Scheduler::NameType::NUMERIC_ID_INTERNAL, nullptr,
|
||||
static_cast<uint32_t>(InternalSchedulerID::DELAY_ACTION), this->delay_.value(),
|
||||
this, Scheduler::SchedulerItem::TIMEOUT, Scheduler::NameType::STATIC_STRING, "delay", 0, this->delay_.value(),
|
||||
[this]() { this->play_next_(); },
|
||||
/* is_retry= */ false, /* skip_cancel= */ this->num_running_ > 1);
|
||||
} else {
|
||||
// For delays with arguments, use std::bind to preserve argument values
|
||||
// Arguments must be copied because original references may be invalid after delay
|
||||
auto f = std::bind(&DelayAction<Ts...>::play_next_, this, x...);
|
||||
App.scheduler.set_timer_common_(this, Scheduler::SchedulerItem::TIMEOUT, Scheduler::NameType::NUMERIC_ID_INTERNAL,
|
||||
nullptr, static_cast<uint32_t>(InternalSchedulerID::DELAY_ACTION),
|
||||
this->delay_.value(x...), std::move(f),
|
||||
App.scheduler.set_timer_common_(this, Scheduler::SchedulerItem::TIMEOUT, Scheduler::NameType::STATIC_STRING,
|
||||
"delay", 0, this->delay_.value(x...), std::move(f),
|
||||
/* is_retry= */ false, /* skip_cancel= */ this->num_running_ > 1);
|
||||
}
|
||||
}
|
||||
@@ -210,7 +208,7 @@ template<typename... Ts> class DelayAction : public Action<Ts...>, public Compon
|
||||
void play(const Ts &...x) override { /* ignore - see play_complex */
|
||||
}
|
||||
|
||||
void stop() override { this->cancel_timeout(InternalSchedulerID::DELAY_ACTION); }
|
||||
void stop() override { this->cancel_timeout("delay"); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class LambdaAction : public Action<Ts...> {
|
||||
|
||||
@@ -152,10 +152,7 @@ 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,
|
||||
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);
|
||||
#pragma GCC diagnostic pop
|
||||
}
|
||||
|
||||
bool Component::cancel_retry(const std::string &name) { // NOLINT
|
||||
@@ -166,10 +163,7 @@ bool Component::cancel_retry(const std::string &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);
|
||||
#pragma GCC diagnostic pop
|
||||
}
|
||||
|
||||
void Component::set_timeout(const std::string &name, uint32_t timeout, std::function<void()> &&f) { // NOLINT
|
||||
@@ -201,38 +195,18 @@ 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); }
|
||||
|
||||
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
|
||||
App.scheduler.set_interval(this, id, interval, std::move(f));
|
||||
}
|
||||
|
||||
bool Component::cancel_interval(uint32_t id) { return App.scheduler.cancel_interval(this, id); }
|
||||
|
||||
void Component::set_interval(InternalSchedulerID id, uint32_t interval, std::function<void()> &&f) { // NOLINT
|
||||
App.scheduler.set_interval(this, id, interval, std::move(f));
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
bool Component::cancel_retry(uint32_t id) { return App.scheduler.cancel_retry(this, id); }
|
||||
|
||||
void Component::call_loop() { this->loop(); }
|
||||
void Component::call_setup() { this->setup(); }
|
||||
@@ -397,10 +371,7 @@ 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,
|
||||
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);
|
||||
#pragma GCC diagnostic pop
|
||||
}
|
||||
bool Component::is_failed() const { return (this->component_state_ & COMPONENT_STATE_MASK) == COMPONENT_STATE_FAILED; }
|
||||
bool Component::is_ready() const {
|
||||
@@ -545,12 +516,12 @@ void PollingComponent::call_setup() {
|
||||
|
||||
void PollingComponent::start_poller() {
|
||||
// Register interval.
|
||||
this->set_interval(InternalSchedulerID::POLLING_UPDATE, this->get_update_interval(), [this]() { this->update(); });
|
||||
this->set_interval("update", this->get_update_interval(), [this]() { this->update(); });
|
||||
}
|
||||
|
||||
void PollingComponent::stop_poller() {
|
||||
// Clear the interval to suspend component
|
||||
this->cancel_interval(InternalSchedulerID::POLLING_UPDATE);
|
||||
this->cancel_interval("update");
|
||||
}
|
||||
|
||||
uint32_t PollingComponent::get_update_interval() const { return this->update_interval_; }
|
||||
|
||||
@@ -49,14 +49,6 @@ extern const float LATE;
|
||||
|
||||
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
|
||||
class PollingComponent;
|
||||
|
||||
@@ -76,7 +68,6 @@ extern const uint8_t STATUS_LED_OK;
|
||||
extern const uint8_t STATUS_LED_WARNING;
|
||||
extern const uint8_t STATUS_LED_ERROR;
|
||||
|
||||
// Remove before 2026.8.0
|
||||
enum class RetryResult { DONE, RETRY };
|
||||
|
||||
extern const uint16_t WARN_IF_BLOCKING_OVER_MS;
|
||||
@@ -343,8 +334,6 @@ class Component {
|
||||
*/
|
||||
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
|
||||
|
||||
/** Cancel an interval function.
|
||||
@@ -357,42 +346,69 @@ class Component {
|
||||
bool cancel_interval(const std::string &name); // NOLINT
|
||||
bool cancel_interval(const char *name); // NOLINT
|
||||
bool cancel_interval(uint32_t id); // NOLINT
|
||||
bool cancel_interval(InternalSchedulerID id); // NOLINT
|
||||
|
||||
/// @deprecated set_retry is deprecated. Use set_timeout or set_interval instead. Removed in 2026.8.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")
|
||||
/** Set an retry function with a unique name. Empty name means no cancelling possible.
|
||||
*
|
||||
* This will call the retry function f on the next scheduler loop. f should return RetryResult::DONE if
|
||||
* it is successful and no repeat is required. Otherwise, returning RetryResult::RETRY will call f
|
||||
* 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
|
||||
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
|
||||
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")
|
||||
/** Set a retry function with a numeric ID (zero heap allocation).
|
||||
*
|
||||
* @param id The numeric identifier for this retry function
|
||||
* @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
|
||||
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
|
||||
float backoff_increase_factor = 1.0f); // NOLINT
|
||||
|
||||
// Remove before 2026.8.0
|
||||
ESPDEPRECATED("cancel_retry is deprecated and will be removed in 2026.8.0.", "2026.2.0")
|
||||
/** Cancel a retry function.
|
||||
*
|
||||
* @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
|
||||
// Remove before 2026.8.0
|
||||
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
|
||||
bool cancel_retry(const char *name); // NOLINT
|
||||
bool cancel_retry(uint32_t id); // NOLINT
|
||||
|
||||
/** Set a timeout function with a unique name.
|
||||
*
|
||||
@@ -436,8 +452,6 @@ class Component {
|
||||
*/
|
||||
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
|
||||
|
||||
/** Cancel a timeout function.
|
||||
@@ -450,7 +464,6 @@ class Component {
|
||||
bool cancel_timeout(const std::string &name); // NOLINT
|
||||
bool cancel_timeout(const char *name); // NOLINT
|
||||
bool cancel_timeout(uint32_t id); // NOLINT
|
||||
bool cancel_timeout(InternalSchedulerID id); // NOLINT
|
||||
|
||||
/** Defer a callback to the next loop() call.
|
||||
*
|
||||
|
||||
@@ -53,12 +53,9 @@ struct SchedulerNameLog {
|
||||
} else if (name_type == NameType::HASHED_STRING) {
|
||||
ESPHOME_snprintf_P(buffer, sizeof(buffer), ESPHOME_PSTR("hash:0x%08" PRIX32), hash_or_id);
|
||||
return buffer;
|
||||
} else if (name_type == NameType::NUMERIC_ID) {
|
||||
} else { // NUMERIC_ID
|
||||
ESPHOME_snprintf_P(buffer, sizeof(buffer), ESPHOME_PSTR("id:%" PRIu32), hash_or_id);
|
||||
return buffer;
|
||||
} else { // NUMERIC_ID_INTERNAL
|
||||
ESPHOME_snprintf_P(buffer, sizeof(buffer), ESPHOME_PSTR("iid:%" PRIu32), hash_or_id);
|
||||
return buffer;
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -140,9 +137,6 @@ void HOT Scheduler::set_timer_common_(Component *component, SchedulerItem::Type
|
||||
case NameType::NUMERIC_ID:
|
||||
item->set_numeric_id(hash_or_id);
|
||||
break;
|
||||
case NameType::NUMERIC_ID_INTERNAL:
|
||||
item->set_internal_id(hash_or_id);
|
||||
break;
|
||||
}
|
||||
item->type = type;
|
||||
item->callback = std::move(func);
|
||||
@@ -258,11 +252,6 @@ bool HOT Scheduler::cancel_interval(Component *component, uint32_t id) {
|
||||
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 {
|
||||
// Ordered to minimize padding on 32-bit systems
|
||||
std::function<RetryResult(uint8_t)> func;
|
||||
@@ -375,8 +364,6 @@ bool HOT Scheduler::cancel_retry(Component *component, uint32_t 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) {
|
||||
// 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
|
||||
|
||||
@@ -46,20 +46,11 @@ class Scheduler {
|
||||
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)
|
||||
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")
|
||||
bool cancel_timeout(Component *component, const std::string &name);
|
||||
bool cancel_timeout(Component *component, const char *name);
|
||||
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")
|
||||
void set_interval(Component *component, const std::string &name, uint32_t interval, std::function<void()> func);
|
||||
@@ -75,45 +66,24 @@ class Scheduler {
|
||||
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)
|
||||
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")
|
||||
bool cancel_interval(Component *component, const std::string &name);
|
||||
bool cancel_interval(Component *component, const char *name);
|
||||
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);
|
||||
}
|
||||
|
||||
// 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")
|
||||
ESPDEPRECATED("Use const char* or uint32_t overload instead. Removed in 2026.7.0", "2026.1.0")
|
||||
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);
|
||||
// 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,
|
||||
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")
|
||||
/// Set a retry with a numeric ID (zero heap allocation)
|
||||
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);
|
||||
|
||||
// Remove before 2026.8.0
|
||||
ESPDEPRECATED("cancel_retry is deprecated and will be removed in 2026.8.0.", "2026.2.0")
|
||||
ESPDEPRECATED("Use const char* or uint32_t overload instead. Removed in 2026.7.0", "2026.1.0")
|
||||
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);
|
||||
// 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);
|
||||
|
||||
// Calculate when the next scheduled item should run
|
||||
@@ -130,12 +100,11 @@ class Scheduler {
|
||||
void process_to_add();
|
||||
|
||||
// Name storage type discriminator for SchedulerItem
|
||||
// Used to distinguish between static strings, hashed strings, numeric IDs, and internal numeric IDs
|
||||
// Used to distinguish between static strings, hashed strings, and numeric IDs
|
||||
enum class NameType : uint8_t {
|
||||
STATIC_STRING = 0, // const char* pointer to static/flash storage
|
||||
HASHED_STRING = 1, // uint32_t FNV-1a hash of a runtime string
|
||||
NUMERIC_ID = 2, // uint32_t numeric identifier (component-level)
|
||||
NUMERIC_ID_INTERNAL = 3 // uint32_t numeric identifier (core/internal, separate namespace)
|
||||
STATIC_STRING = 0, // const char* pointer to static/flash storage
|
||||
HASHED_STRING = 1, // uint32_t FNV-1a hash of a runtime string
|
||||
NUMERIC_ID = 2 // uint32_t numeric identifier
|
||||
};
|
||||
|
||||
protected:
|
||||
@@ -166,7 +135,7 @@ class Scheduler {
|
||||
|
||||
// Bit-packed fields (4 bits used, 4 bits padding in 1 byte)
|
||||
enum Type : uint8_t { TIMEOUT, INTERVAL } type : 1;
|
||||
NameType name_type_ : 2; // Discriminator for name_ union (0–3, see NameType enum)
|
||||
NameType name_type_ : 2; // Discriminator for name_ union (STATIC_STRING, HASHED_STRING, NUMERIC_ID)
|
||||
bool is_retry : 1; // True if this is a retry timeout
|
||||
// 4 bits padding
|
||||
#else
|
||||
@@ -174,7 +143,7 @@ class Scheduler {
|
||||
// Bit-packed fields (5 bits used, 3 bits padding in 1 byte)
|
||||
enum Type : uint8_t { TIMEOUT, INTERVAL } type : 1;
|
||||
bool remove : 1;
|
||||
NameType name_type_ : 2; // Discriminator for name_ union (0–3, see NameType enum)
|
||||
NameType name_type_ : 2; // Discriminator for name_ union (STATIC_STRING, HASHED_STRING, NUMERIC_ID)
|
||||
bool is_retry : 1; // True if this is a retry timeout
|
||||
// 3 bits padding
|
||||
#endif
|
||||
@@ -237,12 +206,6 @@ class Scheduler {
|
||||
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);
|
||||
|
||||
// Note: We use 48 bits total (32 + 16), stored in a 64-bit value for API compatibility.
|
||||
@@ -268,14 +231,11 @@ class Scheduler {
|
||||
uint32_t hash_or_id, uint32_t delay, std::function<void()> func, bool is_retry = false,
|
||||
bool skip_cancel = false);
|
||||
|
||||
// Common implementation for retry - Remove before 2026.8.0
|
||||
// Common implementation for retry
|
||||
// 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,
|
||||
uint32_t initial_wait_time, uint8_t max_attempts, std::function<RetryResult(uint8_t)> func,
|
||||
float backoff_increase_factor);
|
||||
#pragma GCC diagnostic pop
|
||||
// Common implementation for cancel_retry
|
||||
bool cancel_retry_(Component *component, NameType name_type, const char *static_name, uint32_t hash_or_id);
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
[build-system]
|
||||
requires = ["setuptools==82.0.0", "wheel>=0.43,<0.47"]
|
||||
requires = ["setuptools==80.10.2", "wheel>=0.43,<0.47"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
|
||||
@@ -2277,12 +2277,6 @@ ifdefs: dict[str, str] = {}
|
||||
# Track messages with no fields (empty messages) for parameter elision
|
||||
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(
|
||||
desc: descriptor.DescriptorProto,
|
||||
@@ -2533,11 +2527,7 @@ def build_service_message_type(
|
||||
case += "#endif\n"
|
||||
case += f"this->{func}({'msg' if not is_empty else ''});\n"
|
||||
case += "break;"
|
||||
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)
|
||||
RECEIVE_CASES[id_] = (case, ifdef, mt.name)
|
||||
|
||||
# Only close ifdef if we opened it
|
||||
if ifdef is not None:
|
||||
@@ -2733,19 +2723,6 @@ static void dump_bytes_field(DumpBuffer &out, const char *field_name, const uint
|
||||
|
||||
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
|
||||
base_class_groups = collect_messages_by_base_class(mt)
|
||||
|
||||
@@ -2778,10 +2755,6 @@ 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):
|
||||
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)
|
||||
msg_ifdef = message_ifdef_map.get(m.name)
|
||||
|
||||
@@ -2908,8 +2881,33 @@ static const char *const TAG = "api.service";
|
||||
|
||||
cases = list(RECEIVE_CASES.items())
|
||||
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]
|
||||
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
|
||||
message_auth_map: dict[str, bool] = {}
|
||||
@@ -2924,90 +2922,67 @@ static const char *const TAG = "api.service";
|
||||
message_auth_map[inp] = needs_auth
|
||||
message_conn_map[inp] = needs_conn
|
||||
|
||||
# Generate optimized read_message with authentication checking
|
||||
# Categorize messages by their authentication requirements
|
||||
no_conn_ids: set[int] = set()
|
||||
conn_only_ids: set[int] = set()
|
||||
|
||||
# Build a reverse lookup from message id to message name for auth lookups
|
||||
id_to_msg_name: dict[int, str] = {}
|
||||
for mt in file.message_type:
|
||||
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]
|
||||
for id_, (_, _, case_msg_name) in cases:
|
||||
if case_msg_name in message_auth_map:
|
||||
needs_auth = message_auth_map[case_msg_name]
|
||||
needs_conn = message_conn_map[case_msg_name]
|
||||
|
||||
if not needs_conn:
|
||||
no_conn_ids.add(id_)
|
||||
elif not needs_auth:
|
||||
conn_only_ids.add(id_)
|
||||
|
||||
# Helper to generate case statements with ifdefs
|
||||
def generate_cases(ids: set[int], comment: str) -> str:
|
||||
result = ""
|
||||
for id_ in sorted(ids):
|
||||
_, ifdef, case_label = RECEIVE_CASES[id_]
|
||||
if ifdef:
|
||||
result += f"#ifdef {ifdef}\n"
|
||||
result += f" case {case_label}: {comment}\n"
|
||||
if ifdef:
|
||||
result += "#endif\n"
|
||||
return result
|
||||
|
||||
# Generate read_message with auth check before dispatch
|
||||
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"
|
||||
|
||||
# Auth check block before dispatch switch
|
||||
out += " // Check authentication/connection requirements\n"
|
||||
# Generate override if we have messages that skip checks
|
||||
if no_conn_ids or conn_only_ids:
|
||||
out += " switch (msg_type) {\n"
|
||||
# Helper to generate case statements with ifdefs
|
||||
def generate_cases(ids: set[int], comment: str) -> str:
|
||||
result = ""
|
||||
for id_ in sorted(ids):
|
||||
_, ifdef, msg_name = RECEIVE_CASES[id_]
|
||||
if ifdef:
|
||||
result += f"#ifdef {ifdef}\n"
|
||||
result += f" case {msg_name}::MESSAGE_TYPE: {comment}\n"
|
||||
if ifdef:
|
||||
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:
|
||||
out += generate_cases(no_conn_ids, "// No setup required")
|
||||
out += " break;\n"
|
||||
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:
|
||||
out += generate_cases(conn_only_ids, "// Connection setup only")
|
||||
out += " if (!this->check_connection_setup_()) {\n"
|
||||
out += " return;\n"
|
||||
out += " }\n"
|
||||
out += " break;\n"
|
||||
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"
|
||||
|
||||
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"
|
||||
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"
|
||||
|
||||
# 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 += " protected:\n"
|
||||
hpp += hpp_protected
|
||||
hpp += "};\n"
|
||||
|
||||
hpp += """\
|
||||
|
||||
@@ -756,53 +756,6 @@ 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(
|
||||
# Match scanf family functions: scanf, sscanf, fscanf, vscanf, vsscanf, vfscanf
|
||||
# Also match std:: prefixed versions
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
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
|
||||
@@ -1,6 +0,0 @@
|
||||
packages:
|
||||
spi: !include ../../test_build_components/common/spi/esp32-p4-idf.yaml
|
||||
|
||||
psram:
|
||||
|
||||
<<: !include common.yaml
|
||||
@@ -4,4 +4,58 @@ packages:
|
||||
psram:
|
||||
mode: octal
|
||||
|
||||
<<: !include common.yaml
|
||||
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
|
||||
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
|
||||
|
||||
@@ -29,7 +29,7 @@ class MockUARTComponent : public UARTComponent {
|
||||
|
||||
MOCK_METHOD(bool, read_array, (uint8_t * data, size_t len), (override));
|
||||
MOCK_METHOD(bool, peek_byte, (uint8_t * data), (override));
|
||||
MOCK_METHOD(size_t, available, (), (override));
|
||||
MOCK_METHOD(int, available, (), (override));
|
||||
MOCK_METHOD(void, flush, (), (override));
|
||||
MOCK_METHOD(void, check_logger_conflict, (), (override));
|
||||
};
|
||||
|
||||
@@ -68,24 +68,3 @@ voice_assistant:
|
||||
- logger.log:
|
||||
format: "Voice assistant error - code %s, message: %s"
|
||||
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,24 +58,3 @@ voice_assistant:
|
||||
- logger.log:
|
||||
format: "Voice assistant error - code %s, message: %s"
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -1,109 +0,0 @@
|
||||
esphome:
|
||||
name: scheduler-internal-id-test
|
||||
on_boot:
|
||||
priority: -100
|
||||
then:
|
||||
- logger.log: "Starting scheduler internal ID collision tests"
|
||||
|
||||
host:
|
||||
api:
|
||||
logger:
|
||||
level: VERBOSE
|
||||
|
||||
globals:
|
||||
- id: tests_done
|
||||
type: bool
|
||||
initial_value: 'false'
|
||||
|
||||
script:
|
||||
- id: test_internal_id_no_collision
|
||||
then:
|
||||
- logger.log: "Testing NUMERIC_ID_INTERNAL vs NUMERIC_ID isolation"
|
||||
- lambda: |-
|
||||
// All tests use the same component and the same uint32_t value (0).
|
||||
// NUMERIC_ID_INTERNAL and NUMERIC_ID are separate NameType values,
|
||||
// so the scheduler must treat them as independent timers.
|
||||
auto *comp = id(test_sensor);
|
||||
|
||||
// ---- Test 1: Both timeout types fire independently ----
|
||||
// Set an internal timeout with ID 0
|
||||
App.scheduler.set_timeout(comp, InternalSchedulerID{0}, 50, []() {
|
||||
ESP_LOGI("test", "Internal timeout 0 fired");
|
||||
});
|
||||
// Set a component numeric timeout with the same ID 0
|
||||
App.scheduler.set_timeout(comp, 0U, 50, []() {
|
||||
ESP_LOGI("test", "Numeric timeout 0 fired");
|
||||
});
|
||||
|
||||
// ---- Test 2: Cancelling numeric ID does NOT cancel internal ID ----
|
||||
// Set an internal timeout with ID 1
|
||||
App.scheduler.set_timeout(comp, InternalSchedulerID{1}, 100, []() {
|
||||
ESP_LOGI("test", "Internal timeout 1 survived cancel");
|
||||
});
|
||||
// Set a numeric timeout with the same ID 1
|
||||
App.scheduler.set_timeout(comp, 1U, 100, []() {
|
||||
ESP_LOGE("test", "ERROR: Numeric timeout 1 should have been cancelled");
|
||||
});
|
||||
// Cancel only the numeric one
|
||||
App.scheduler.cancel_timeout(comp, 1U);
|
||||
|
||||
// ---- Test 3: Cancelling internal ID does NOT cancel numeric ID ----
|
||||
// Set a numeric timeout with ID 2
|
||||
App.scheduler.set_timeout(comp, 2U, 150, []() {
|
||||
ESP_LOGI("test", "Numeric timeout 2 survived cancel");
|
||||
});
|
||||
// Set an internal timeout with the same ID 2
|
||||
App.scheduler.set_timeout(comp, InternalSchedulerID{2}, 150, []() {
|
||||
ESP_LOGE("test", "ERROR: Internal timeout 2 should have been cancelled");
|
||||
});
|
||||
// Cancel only the internal one
|
||||
App.scheduler.cancel_timeout(comp, InternalSchedulerID{2});
|
||||
|
||||
// ---- Test 4: Both interval types fire independently ----
|
||||
static int internal_interval_count = 0;
|
||||
static int numeric_interval_count = 0;
|
||||
App.scheduler.set_interval(comp, InternalSchedulerID{3}, 100, []() {
|
||||
internal_interval_count++;
|
||||
if (internal_interval_count == 2) {
|
||||
ESP_LOGI("test", "Internal interval 3 fired twice");
|
||||
App.scheduler.cancel_interval(id(test_sensor), InternalSchedulerID{3});
|
||||
}
|
||||
});
|
||||
App.scheduler.set_interval(comp, 3U, 100, []() {
|
||||
numeric_interval_count++;
|
||||
if (numeric_interval_count == 2) {
|
||||
ESP_LOGI("test", "Numeric interval 3 fired twice");
|
||||
App.scheduler.cancel_interval(id(test_sensor), 3U);
|
||||
}
|
||||
});
|
||||
|
||||
// ---- Test 5: String name does NOT collide with internal ID ----
|
||||
// Use string name and internal ID 10 on same component
|
||||
App.scheduler.set_timeout(comp, "collision_test", 200, []() {
|
||||
ESP_LOGI("test", "String timeout collision_test fired");
|
||||
});
|
||||
App.scheduler.set_timeout(comp, InternalSchedulerID{10}, 200, []() {
|
||||
ESP_LOGI("test", "Internal timeout 10 fired");
|
||||
});
|
||||
|
||||
// Log completion after all timers should have fired
|
||||
App.scheduler.set_timeout(comp, 9999U, 1500, []() {
|
||||
ESP_LOGI("test", "All collision tests complete");
|
||||
});
|
||||
|
||||
sensor:
|
||||
- platform: template
|
||||
name: Test Sensor
|
||||
id: test_sensor
|
||||
lambda: return 1.0;
|
||||
update_interval: never
|
||||
|
||||
interval:
|
||||
- interval: 0.1s
|
||||
then:
|
||||
- if:
|
||||
condition:
|
||||
lambda: 'return id(tests_done) == false;'
|
||||
then:
|
||||
- lambda: 'id(tests_done) = true;'
|
||||
- script.execute: test_internal_id_no_collision
|
||||
@@ -1,124 +0,0 @@
|
||||
"""Test that NUMERIC_ID_INTERNAL and NUMERIC_ID cannot collide.
|
||||
|
||||
Verifies that InternalSchedulerID (used by core base classes like
|
||||
PollingComponent and DelayAction) and uint32_t numeric IDs (used by
|
||||
components) are in completely separate matching namespaces, even when
|
||||
the underlying uint32_t values are identical and on the same component.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import re
|
||||
|
||||
import pytest
|
||||
|
||||
from .types import APIClientConnectedFactory, RunCompiledFunction
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_scheduler_internal_id_no_collision(
|
||||
yaml_config: str,
|
||||
run_compiled: RunCompiledFunction,
|
||||
api_client_connected: APIClientConnectedFactory,
|
||||
) -> None:
|
||||
"""Test that internal and numeric IDs with same value don't collide."""
|
||||
# Test 1: Both types fire independently with same ID
|
||||
internal_timeout_0_fired = asyncio.Event()
|
||||
numeric_timeout_0_fired = asyncio.Event()
|
||||
|
||||
# Test 2: Cancelling numeric doesn't cancel internal
|
||||
internal_timeout_1_survived = asyncio.Event()
|
||||
numeric_timeout_1_error = asyncio.Event()
|
||||
|
||||
# Test 3: Cancelling internal doesn't cancel numeric
|
||||
numeric_timeout_2_survived = asyncio.Event()
|
||||
internal_timeout_2_error = asyncio.Event()
|
||||
|
||||
# Test 4: Both interval types fire independently
|
||||
internal_interval_3_done = asyncio.Event()
|
||||
numeric_interval_3_done = asyncio.Event()
|
||||
|
||||
# Test 5: String name doesn't collide with internal ID
|
||||
string_timeout_fired = asyncio.Event()
|
||||
internal_timeout_10_fired = asyncio.Event()
|
||||
|
||||
# Completion
|
||||
all_tests_complete = asyncio.Event()
|
||||
|
||||
def on_log_line(line: str) -> None:
|
||||
clean_line = re.sub(r"\x1b\[[0-9;]*m", "", line)
|
||||
|
||||
if "Internal timeout 0 fired" in clean_line:
|
||||
internal_timeout_0_fired.set()
|
||||
elif "Numeric timeout 0 fired" in clean_line:
|
||||
numeric_timeout_0_fired.set()
|
||||
elif "Internal timeout 1 survived cancel" in clean_line:
|
||||
internal_timeout_1_survived.set()
|
||||
elif "ERROR: Numeric timeout 1 should have been cancelled" in clean_line:
|
||||
numeric_timeout_1_error.set()
|
||||
elif "Numeric timeout 2 survived cancel" in clean_line:
|
||||
numeric_timeout_2_survived.set()
|
||||
elif "ERROR: Internal timeout 2 should have been cancelled" in clean_line:
|
||||
internal_timeout_2_error.set()
|
||||
elif "Internal interval 3 fired twice" in clean_line:
|
||||
internal_interval_3_done.set()
|
||||
elif "Numeric interval 3 fired twice" in clean_line:
|
||||
numeric_interval_3_done.set()
|
||||
elif "String timeout collision_test fired" in clean_line:
|
||||
string_timeout_fired.set()
|
||||
elif "Internal timeout 10 fired" in clean_line:
|
||||
internal_timeout_10_fired.set()
|
||||
elif "All collision tests complete" in clean_line:
|
||||
all_tests_complete.set()
|
||||
|
||||
async with (
|
||||
run_compiled(yaml_config, line_callback=on_log_line),
|
||||
api_client_connected() as client,
|
||||
):
|
||||
device_info = await client.device_info()
|
||||
assert device_info is not None
|
||||
assert device_info.name == "scheduler-internal-id-test"
|
||||
|
||||
try:
|
||||
await asyncio.wait_for(all_tests_complete.wait(), timeout=5.0)
|
||||
except TimeoutError:
|
||||
pytest.fail("Not all collision tests completed within 5 seconds")
|
||||
|
||||
# Test 1: Both timeout types with same ID 0 must fire
|
||||
assert internal_timeout_0_fired.is_set(), (
|
||||
"Internal timeout with ID 0 should have fired"
|
||||
)
|
||||
assert numeric_timeout_0_fired.is_set(), (
|
||||
"Numeric timeout with ID 0 should have fired"
|
||||
)
|
||||
|
||||
# Test 2: Cancelling numeric ID must NOT cancel internal ID
|
||||
assert internal_timeout_1_survived.is_set(), (
|
||||
"Internal timeout 1 should survive cancellation of numeric timeout 1"
|
||||
)
|
||||
assert not numeric_timeout_1_error.is_set(), (
|
||||
"Numeric timeout 1 should have been cancelled"
|
||||
)
|
||||
|
||||
# Test 3: Cancelling internal ID must NOT cancel numeric ID
|
||||
assert numeric_timeout_2_survived.is_set(), (
|
||||
"Numeric timeout 2 should survive cancellation of internal timeout 2"
|
||||
)
|
||||
assert not internal_timeout_2_error.is_set(), (
|
||||
"Internal timeout 2 should have been cancelled"
|
||||
)
|
||||
|
||||
# Test 4: Both interval types with same ID must fire independently
|
||||
assert internal_interval_3_done.is_set(), (
|
||||
"Internal interval 3 should have fired at least twice"
|
||||
)
|
||||
assert numeric_interval_3_done.is_set(), (
|
||||
"Numeric interval 3 should have fired at least twice"
|
||||
)
|
||||
|
||||
# Test 5: String name and internal ID don't collide
|
||||
assert string_timeout_fired.is_set(), (
|
||||
"String timeout 'collision_test' should have fired"
|
||||
)
|
||||
assert internal_timeout_10_fired.is_set(), (
|
||||
"Internal timeout 10 should have fired alongside string timeout"
|
||||
)
|
||||
@@ -1,12 +0,0 @@
|
||||
# Common SPI configuration for ESP32-P4 IDF tests
|
||||
|
||||
substitutions:
|
||||
clk_pin: GPIO36
|
||||
mosi_pin: GPIO32
|
||||
miso_pin: GPIO33
|
||||
|
||||
spi:
|
||||
- id: spi_bus
|
||||
clk_pin: ${clk_pin}
|
||||
mosi_pin: ${mosi_pin}
|
||||
miso_pin: ${miso_pin}
|
||||
Reference in New Issue
Block a user