Merge remote-tracking branch 'upstream/dev' into proto_field_ifdefs

This commit is contained in:
J. Nick Koston 2025-07-15 15:52:18 -10:00
commit 545fa1f1bc
No known key found for this signature in database
58 changed files with 544 additions and 275 deletions

View File

@ -47,7 +47,7 @@ jobs:
- name: Set up Python
uses: actions/setup-python@v5.6.0
with:
python-version: "3.10"
python-version: "3.11"
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3.11.1

View File

@ -20,8 +20,8 @@ permissions:
contents: read
env:
DEFAULT_PYTHON: "3.10"
PYUPGRADE_TARGET: "--py310-plus"
DEFAULT_PYTHON: "3.11"
PYUPGRADE_TARGET: "--py311-plus"
concurrency:
# yamllint disable-line rule:line-length
@ -112,7 +112,6 @@ jobs:
fail-fast: false
matrix:
python-version:
- "3.10"
- "3.11"
- "3.12"
- "3.13"
@ -128,14 +127,10 @@ jobs:
os: windows-latest
- python-version: "3.12"
os: windows-latest
- python-version: "3.10"
os: windows-latest
- python-version: "3.13"
os: macOS-latest
- python-version: "3.12"
os: macOS-latest
- python-version: "3.10"
os: macOS-latest
runs-on: ${{ matrix.os }}
needs:
- common

View File

@ -96,7 +96,7 @@ jobs:
- name: Set up Python
uses: actions/setup-python@v5.6.0
with:
python-version: "3.10"
python-version: "3.11"
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3.11.1

View File

@ -40,7 +40,7 @@ repos:
rev: v3.20.0
hooks:
- id: pyupgrade
args: [--py310-plus]
args: [--py311-plus]
- repo: https://github.com/adrienverge/yamllint.git
rev: v1.37.1
hooks:

View File

@ -180,7 +180,8 @@ void APIConnection::loop() {
on_fatal_error();
ESP_LOGW(TAG, "%s is unresponsive; disconnecting", this->get_client_combined_info().c_str());
}
} else if (now - this->last_traffic_ > KEEPALIVE_TIMEOUT_MS) {
} else if (now - this->last_traffic_ > KEEPALIVE_TIMEOUT_MS && !this->flags_.remove) {
// Only send ping if we're not disconnecting
ESP_LOGVV(TAG, "Sending keepalive PING");
this->flags_.sent_ping = this->send_message(PingRequest());
if (!this->flags_.sent_ping) {
@ -1404,9 +1405,6 @@ void APIConnection::update_command(const UpdateCommandRequest &msg) {
#endif
bool APIConnection::try_send_log_message(int level, const char *tag, const char *line, size_t message_len) {
if (this->flags_.log_subscription < level)
return false;
// Pre-calculate message size to avoid reallocations
uint32_t msg_size = 0;
@ -1670,8 +1668,15 @@ void APIConnection::DeferredBatch::add_item(EntityBase *entity, MessageCreator c
void APIConnection::DeferredBatch::add_item_front(EntityBase *entity, MessageCreator creator, uint8_t message_type,
uint8_t estimated_size) {
// Insert at front for high priority messages (no deduplication check)
items.insert(items.begin(), BatchItem(entity, std::move(creator), message_type, estimated_size));
// Add high priority message and swap to front
// This avoids expensive vector::insert which shifts all elements
// Note: We only ever have one high-priority message at a time (ping OR disconnect)
// If we're disconnecting, pings are blocked, so this simple swap is sufficient
items.emplace_back(entity, std::move(creator), message_type, estimated_size);
if (items.size() > 1) {
// Swap the new high-priority item to the front
std::swap(items.front(), items.back());
}
}
bool APIConnection::schedule_batch_() {

View File

@ -209,6 +209,7 @@ class APIConnection : public APIServerConnection {
return static_cast<ConnectionState>(this->flags_.connection_state) == ConnectionState::CONNECTED ||
this->is_authenticated();
}
uint8_t get_log_subscription_level() const { return this->flags_.log_subscription; }
void on_fatal_error() override;
void on_unauthenticated_access() override;
void on_no_setup_connection() override;

View File

@ -244,7 +244,7 @@ void ListEntitiesBinarySensorResponse::encode(ProtoWriteBuffer buffer) const {
}
void ListEntitiesBinarySensorResponse::calculate_size(uint32_t &total_size) const {
ProtoSize::add_string_field(total_size, 1, this->object_id);
ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0);
ProtoSize::add_fixed32_field(total_size, 1, this->key);
ProtoSize::add_string_field(total_size, 1, this->name);
ProtoSize::add_string_field(total_size, 1, this->unique_id);
ProtoSize::add_string_field(total_size, 1, this->device_class);
@ -265,7 +265,7 @@ void BinarySensorStateResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_uint32(4, this->device_id);
}
void BinarySensorStateResponse::calculate_size(uint32_t &total_size) const {
ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0);
ProtoSize::add_fixed32_field(total_size, 1, this->key);
ProtoSize::add_bool_field(total_size, 1, this->state);
ProtoSize::add_bool_field(total_size, 1, this->missing_state);
ProtoSize::add_uint32_field(total_size, 1, this->device_id);
@ -293,7 +293,7 @@ void ListEntitiesCoverResponse::encode(ProtoWriteBuffer buffer) const {
}
void ListEntitiesCoverResponse::calculate_size(uint32_t &total_size) const {
ProtoSize::add_string_field(total_size, 1, this->object_id);
ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0);
ProtoSize::add_fixed32_field(total_size, 1, this->key);
ProtoSize::add_string_field(total_size, 1, this->name);
ProtoSize::add_string_field(total_size, 1, this->unique_id);
ProtoSize::add_bool_field(total_size, 1, this->assumed_state);
@ -319,10 +319,10 @@ void CoverStateResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_uint32(6, this->device_id);
}
void CoverStateResponse::calculate_size(uint32_t &total_size) const {
ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0);
ProtoSize::add_fixed32_field(total_size, 1, this->key);
ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->legacy_state));
ProtoSize::add_fixed_field<4>(total_size, 1, this->position != 0.0f);
ProtoSize::add_fixed_field<4>(total_size, 1, this->tilt != 0.0f);
ProtoSize::add_float_field(total_size, 1, this->position);
ProtoSize::add_float_field(total_size, 1, this->tilt);
ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->current_operation));
ProtoSize::add_uint32_field(total_size, 1, this->device_id);
}
@ -394,7 +394,7 @@ void ListEntitiesFanResponse::encode(ProtoWriteBuffer buffer) const {
}
void ListEntitiesFanResponse::calculate_size(uint32_t &total_size) const {
ProtoSize::add_string_field(total_size, 1, this->object_id);
ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0);
ProtoSize::add_fixed32_field(total_size, 1, this->key);
ProtoSize::add_string_field(total_size, 1, this->name);
ProtoSize::add_string_field(total_size, 1, this->unique_id);
ProtoSize::add_bool_field(total_size, 1, this->supports_oscillation);
@ -428,7 +428,7 @@ void FanStateResponse::encode(ProtoWriteBuffer buffer) const {
#endif
}
void FanStateResponse::calculate_size(uint32_t &total_size) const {
ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0);
ProtoSize::add_fixed32_field(total_size, 1, this->key);
ProtoSize::add_bool_field(total_size, 1, this->state);
ProtoSize::add_bool_field(total_size, 1, this->oscillating);
ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->speed));
@ -532,7 +532,7 @@ void ListEntitiesLightResponse::encode(ProtoWriteBuffer buffer) const {
}
void ListEntitiesLightResponse::calculate_size(uint32_t &total_size) const {
ProtoSize::add_string_field(total_size, 1, this->object_id);
ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0);
ProtoSize::add_fixed32_field(total_size, 1, this->key);
ProtoSize::add_string_field(total_size, 1, this->name);
ProtoSize::add_string_field(total_size, 1, this->unique_id);
if (!this->supported_color_modes.empty()) {
@ -544,8 +544,8 @@ void ListEntitiesLightResponse::calculate_size(uint32_t &total_size) const {
ProtoSize::add_bool_field(total_size, 1, this->legacy_supports_rgb);
ProtoSize::add_bool_field(total_size, 1, this->legacy_supports_white_value);
ProtoSize::add_bool_field(total_size, 1, this->legacy_supports_color_temperature);
ProtoSize::add_fixed_field<4>(total_size, 1, this->min_mireds != 0.0f);
ProtoSize::add_fixed_field<4>(total_size, 1, this->max_mireds != 0.0f);
ProtoSize::add_float_field(total_size, 1, this->min_mireds);
ProtoSize::add_float_field(total_size, 1, this->max_mireds);
if (!this->effects.empty()) {
for (const auto &it : this->effects) {
ProtoSize::add_string_field_repeated(total_size, 1, it);
@ -577,18 +577,18 @@ void LightStateResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_uint32(14, this->device_id);
}
void LightStateResponse::calculate_size(uint32_t &total_size) const {
ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0);
ProtoSize::add_fixed32_field(total_size, 1, this->key);
ProtoSize::add_bool_field(total_size, 1, this->state);
ProtoSize::add_fixed_field<4>(total_size, 1, this->brightness != 0.0f);
ProtoSize::add_float_field(total_size, 1, this->brightness);
ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->color_mode));
ProtoSize::add_fixed_field<4>(total_size, 1, this->color_brightness != 0.0f);
ProtoSize::add_fixed_field<4>(total_size, 1, this->red != 0.0f);
ProtoSize::add_fixed_field<4>(total_size, 1, this->green != 0.0f);
ProtoSize::add_fixed_field<4>(total_size, 1, this->blue != 0.0f);
ProtoSize::add_fixed_field<4>(total_size, 1, this->white != 0.0f);
ProtoSize::add_fixed_field<4>(total_size, 1, this->color_temperature != 0.0f);
ProtoSize::add_fixed_field<4>(total_size, 1, this->cold_white != 0.0f);
ProtoSize::add_fixed_field<4>(total_size, 1, this->warm_white != 0.0f);
ProtoSize::add_float_field(total_size, 1, this->color_brightness);
ProtoSize::add_float_field(total_size, 1, this->red);
ProtoSize::add_float_field(total_size, 1, this->green);
ProtoSize::add_float_field(total_size, 1, this->blue);
ProtoSize::add_float_field(total_size, 1, this->white);
ProtoSize::add_float_field(total_size, 1, this->color_temperature);
ProtoSize::add_float_field(total_size, 1, this->cold_white);
ProtoSize::add_float_field(total_size, 1, this->warm_white);
ProtoSize::add_string_field(total_size, 1, this->effect);
ProtoSize::add_uint32_field(total_size, 1, this->device_id);
}
@ -719,7 +719,7 @@ void ListEntitiesSensorResponse::encode(ProtoWriteBuffer buffer) const {
}
void ListEntitiesSensorResponse::calculate_size(uint32_t &total_size) const {
ProtoSize::add_string_field(total_size, 1, this->object_id);
ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0);
ProtoSize::add_fixed32_field(total_size, 1, this->key);
ProtoSize::add_string_field(total_size, 1, this->name);
ProtoSize::add_string_field(total_size, 1, this->unique_id);
#ifdef USE_ENTITY_ICON
@ -742,8 +742,8 @@ void SensorStateResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_uint32(4, this->device_id);
}
void SensorStateResponse::calculate_size(uint32_t &total_size) const {
ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0);
ProtoSize::add_fixed_field<4>(total_size, 1, this->state != 0.0f);
ProtoSize::add_fixed32_field(total_size, 1, this->key);
ProtoSize::add_float_field(total_size, 1, this->state);
ProtoSize::add_bool_field(total_size, 1, this->missing_state);
ProtoSize::add_uint32_field(total_size, 1, this->device_id);
}
@ -767,7 +767,7 @@ void ListEntitiesSwitchResponse::encode(ProtoWriteBuffer buffer) const {
}
void ListEntitiesSwitchResponse::calculate_size(uint32_t &total_size) const {
ProtoSize::add_string_field(total_size, 1, this->object_id);
ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0);
ProtoSize::add_fixed32_field(total_size, 1, this->key);
ProtoSize::add_string_field(total_size, 1, this->name);
ProtoSize::add_string_field(total_size, 1, this->unique_id);
#ifdef USE_ENTITY_ICON
@ -787,7 +787,7 @@ void SwitchStateResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_uint32(3, this->device_id);
}
void SwitchStateResponse::calculate_size(uint32_t &total_size) const {
ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0);
ProtoSize::add_fixed32_field(total_size, 1, this->key);
ProtoSize::add_bool_field(total_size, 1, this->state);
ProtoSize::add_uint32_field(total_size, 1, this->device_id);
}
@ -833,7 +833,7 @@ void ListEntitiesTextSensorResponse::encode(ProtoWriteBuffer buffer) const {
}
void ListEntitiesTextSensorResponse::calculate_size(uint32_t &total_size) const {
ProtoSize::add_string_field(total_size, 1, this->object_id);
ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0);
ProtoSize::add_fixed32_field(total_size, 1, this->key);
ProtoSize::add_string_field(total_size, 1, this->name);
ProtoSize::add_string_field(total_size, 1, this->unique_id);
#ifdef USE_ENTITY_ICON
@ -853,7 +853,7 @@ void TextSensorStateResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_uint32(4, this->device_id);
}
void TextSensorStateResponse::calculate_size(uint32_t &total_size) const {
ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0);
ProtoSize::add_fixed32_field(total_size, 1, this->key);
ProtoSize::add_string_field(total_size, 1, this->state);
ProtoSize::add_bool_field(total_size, 1, this->missing_state);
ProtoSize::add_uint32_field(total_size, 1, this->device_id);
@ -977,7 +977,7 @@ bool GetTimeResponse::decode_32bit(uint32_t field_id, Proto32Bit value) {
}
void GetTimeResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_fixed32(1, this->epoch_seconds); }
void GetTimeResponse::calculate_size(uint32_t &total_size) const {
ProtoSize::add_fixed_field<4>(total_size, 1, this->epoch_seconds != 0);
ProtoSize::add_fixed32_field(total_size, 1, this->epoch_seconds);
}
#ifdef USE_API_SERVICES
bool ListEntitiesServicesArgument::decode_varint(uint32_t field_id, ProtoVarInt value) {
@ -1017,7 +1017,7 @@ void ListEntitiesServicesResponse::encode(ProtoWriteBuffer buffer) const {
}
void ListEntitiesServicesResponse::calculate_size(uint32_t &total_size) const {
ProtoSize::add_string_field(total_size, 1, this->name);
ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0);
ProtoSize::add_fixed32_field(total_size, 1, this->key);
ProtoSize::add_repeated_message(total_size, 1, this->args);
}
bool ExecuteServiceArgument::decode_varint(uint32_t field_id, ProtoVarInt value) {
@ -1090,7 +1090,7 @@ void ExecuteServiceArgument::encode(ProtoWriteBuffer buffer) const {
void ExecuteServiceArgument::calculate_size(uint32_t &total_size) const {
ProtoSize::add_bool_field(total_size, 1, this->bool_);
ProtoSize::add_int32_field(total_size, 1, this->legacy_int);
ProtoSize::add_fixed_field<4>(total_size, 1, this->float_ != 0.0f);
ProtoSize::add_float_field(total_size, 1, this->float_);
ProtoSize::add_string_field(total_size, 1, this->string_);
ProtoSize::add_sint32_field(total_size, 1, this->int_);
if (!this->bool_array.empty()) {
@ -1151,7 +1151,7 @@ void ListEntitiesCameraResponse::encode(ProtoWriteBuffer buffer) const {
}
void ListEntitiesCameraResponse::calculate_size(uint32_t &total_size) const {
ProtoSize::add_string_field(total_size, 1, this->object_id);
ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0);
ProtoSize::add_fixed32_field(total_size, 1, this->key);
ProtoSize::add_string_field(total_size, 1, this->name);
ProtoSize::add_string_field(total_size, 1, this->unique_id);
ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default);
@ -1170,7 +1170,7 @@ void CameraImageResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_uint32(4, this->device_id);
}
void CameraImageResponse::calculate_size(uint32_t &total_size) const {
ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0);
ProtoSize::add_fixed32_field(total_size, 1, this->key);
ProtoSize::add_string_field(total_size, 1, this->data);
ProtoSize::add_bool_field(total_size, 1, this->done);
ProtoSize::add_uint32_field(total_size, 1, this->device_id);
@ -1236,7 +1236,7 @@ void ListEntitiesClimateResponse::encode(ProtoWriteBuffer buffer) const {
}
void ListEntitiesClimateResponse::calculate_size(uint32_t &total_size) const {
ProtoSize::add_string_field(total_size, 1, this->object_id);
ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0);
ProtoSize::add_fixed32_field(total_size, 1, this->key);
ProtoSize::add_string_field(total_size, 1, this->name);
ProtoSize::add_string_field(total_size, 1, this->unique_id);
ProtoSize::add_bool_field(total_size, 1, this->supports_current_temperature);
@ -1246,9 +1246,9 @@ void ListEntitiesClimateResponse::calculate_size(uint32_t &total_size) const {
ProtoSize::add_enum_field_repeated(total_size, 1, static_cast<uint32_t>(it));
}
}
ProtoSize::add_fixed_field<4>(total_size, 1, this->visual_min_temperature != 0.0f);
ProtoSize::add_fixed_field<4>(total_size, 1, this->visual_max_temperature != 0.0f);
ProtoSize::add_fixed_field<4>(total_size, 1, this->visual_target_temperature_step != 0.0f);
ProtoSize::add_float_field(total_size, 1, this->visual_min_temperature);
ProtoSize::add_float_field(total_size, 1, this->visual_max_temperature);
ProtoSize::add_float_field(total_size, 1, this->visual_target_temperature_step);
ProtoSize::add_bool_field(total_size, 1, this->legacy_supports_away);
ProtoSize::add_bool_field(total_size, 1, this->supports_action);
if (!this->supported_fan_modes.empty()) {
@ -1281,11 +1281,11 @@ void ListEntitiesClimateResponse::calculate_size(uint32_t &total_size) const {
ProtoSize::add_string_field(total_size, 2, this->icon);
#endif
ProtoSize::add_enum_field(total_size, 2, static_cast<uint32_t>(this->entity_category));
ProtoSize::add_fixed_field<4>(total_size, 2, this->visual_current_temperature_step != 0.0f);
ProtoSize::add_float_field(total_size, 2, this->visual_current_temperature_step);
ProtoSize::add_bool_field(total_size, 2, this->supports_current_humidity);
ProtoSize::add_bool_field(total_size, 2, this->supports_target_humidity);
ProtoSize::add_fixed_field<4>(total_size, 2, this->visual_min_humidity != 0.0f);
ProtoSize::add_fixed_field<4>(total_size, 2, this->visual_max_humidity != 0.0f);
ProtoSize::add_float_field(total_size, 2, this->visual_min_humidity);
ProtoSize::add_float_field(total_size, 2, this->visual_max_humidity);
#ifdef USE_DEVICES
ProtoSize::add_uint32_field(total_size, 2, this->device_id);
#endif
@ -1311,12 +1311,12 @@ void ClimateStateResponse::encode(ProtoWriteBuffer buffer) const {
#endif
}
void ClimateStateResponse::calculate_size(uint32_t &total_size) const {
ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0);
ProtoSize::add_fixed32_field(total_size, 1, this->key);
ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->mode));
ProtoSize::add_fixed_field<4>(total_size, 1, this->current_temperature != 0.0f);
ProtoSize::add_fixed_field<4>(total_size, 1, this->target_temperature != 0.0f);
ProtoSize::add_fixed_field<4>(total_size, 1, this->target_temperature_low != 0.0f);
ProtoSize::add_fixed_field<4>(total_size, 1, this->target_temperature_high != 0.0f);
ProtoSize::add_float_field(total_size, 1, this->current_temperature);
ProtoSize::add_float_field(total_size, 1, this->target_temperature);
ProtoSize::add_float_field(total_size, 1, this->target_temperature_low);
ProtoSize::add_float_field(total_size, 1, this->target_temperature_high);
ProtoSize::add_bool_field(total_size, 1, this->unused_legacy_away);
ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->action));
ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->fan_mode));
@ -1324,8 +1324,8 @@ void ClimateStateResponse::calculate_size(uint32_t &total_size) const {
ProtoSize::add_string_field(total_size, 1, this->custom_fan_mode);
ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->preset));
ProtoSize::add_string_field(total_size, 1, this->custom_preset);
ProtoSize::add_fixed_field<4>(total_size, 1, this->current_humidity != 0.0f);
ProtoSize::add_fixed_field<4>(total_size, 1, this->target_humidity != 0.0f);
ProtoSize::add_float_field(total_size, 1, this->current_humidity);
ProtoSize::add_float_field(total_size, 1, this->target_humidity);
#ifdef USE_DEVICES
ProtoSize::add_uint32_field(total_size, 2, this->device_id);
#endif
@ -1445,15 +1445,15 @@ void ListEntitiesNumberResponse::encode(ProtoWriteBuffer buffer) const {
}
void ListEntitiesNumberResponse::calculate_size(uint32_t &total_size) const {
ProtoSize::add_string_field(total_size, 1, this->object_id);
ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0);
ProtoSize::add_fixed32_field(total_size, 1, this->key);
ProtoSize::add_string_field(total_size, 1, this->name);
ProtoSize::add_string_field(total_size, 1, this->unique_id);
#ifdef USE_ENTITY_ICON
ProtoSize::add_string_field(total_size, 1, this->icon);
#endif
ProtoSize::add_fixed_field<4>(total_size, 1, this->min_value != 0.0f);
ProtoSize::add_fixed_field<4>(total_size, 1, this->max_value != 0.0f);
ProtoSize::add_fixed_field<4>(total_size, 1, this->step != 0.0f);
ProtoSize::add_float_field(total_size, 1, this->min_value);
ProtoSize::add_float_field(total_size, 1, this->max_value);
ProtoSize::add_float_field(total_size, 1, this->step);
ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default);
ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category));
ProtoSize::add_string_field(total_size, 1, this->unit_of_measurement);
@ -1468,8 +1468,8 @@ void NumberStateResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_uint32(4, this->device_id);
}
void NumberStateResponse::calculate_size(uint32_t &total_size) const {
ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0);
ProtoSize::add_fixed_field<4>(total_size, 1, this->state != 0.0f);
ProtoSize::add_fixed32_field(total_size, 1, this->key);
ProtoSize::add_float_field(total_size, 1, this->state);
ProtoSize::add_bool_field(total_size, 1, this->missing_state);
ProtoSize::add_uint32_field(total_size, 1, this->device_id);
}
@ -1517,7 +1517,7 @@ void ListEntitiesSelectResponse::encode(ProtoWriteBuffer buffer) const {
}
void ListEntitiesSelectResponse::calculate_size(uint32_t &total_size) const {
ProtoSize::add_string_field(total_size, 1, this->object_id);
ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0);
ProtoSize::add_fixed32_field(total_size, 1, this->key);
ProtoSize::add_string_field(total_size, 1, this->name);
ProtoSize::add_string_field(total_size, 1, this->unique_id);
#ifdef USE_ENTITY_ICON
@ -1541,7 +1541,7 @@ void SelectStateResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_uint32(4, this->device_id);
}
void SelectStateResponse::calculate_size(uint32_t &total_size) const {
ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0);
ProtoSize::add_fixed32_field(total_size, 1, this->key);
ProtoSize::add_string_field(total_size, 1, this->state);
ProtoSize::add_bool_field(total_size, 1, this->missing_state);
ProtoSize::add_uint32_field(total_size, 1, this->device_id);
@ -1599,7 +1599,7 @@ void ListEntitiesSirenResponse::encode(ProtoWriteBuffer buffer) const {
}
void ListEntitiesSirenResponse::calculate_size(uint32_t &total_size) const {
ProtoSize::add_string_field(total_size, 1, this->object_id);
ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0);
ProtoSize::add_fixed32_field(total_size, 1, this->key);
ProtoSize::add_string_field(total_size, 1, this->name);
ProtoSize::add_string_field(total_size, 1, this->unique_id);
#ifdef USE_ENTITY_ICON
@ -1624,7 +1624,7 @@ void SirenStateResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_uint32(3, this->device_id);
}
void SirenStateResponse::calculate_size(uint32_t &total_size) const {
ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0);
ProtoSize::add_fixed32_field(total_size, 1, this->key);
ProtoSize::add_bool_field(total_size, 1, this->state);
ProtoSize::add_uint32_field(total_size, 1, this->device_id);
}
@ -1703,7 +1703,7 @@ void ListEntitiesLockResponse::encode(ProtoWriteBuffer buffer) const {
}
void ListEntitiesLockResponse::calculate_size(uint32_t &total_size) const {
ProtoSize::add_string_field(total_size, 1, this->object_id);
ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0);
ProtoSize::add_fixed32_field(total_size, 1, this->key);
ProtoSize::add_string_field(total_size, 1, this->name);
ProtoSize::add_string_field(total_size, 1, this->unique_id);
#ifdef USE_ENTITY_ICON
@ -1725,7 +1725,7 @@ void LockStateResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_uint32(3, this->device_id);
}
void LockStateResponse::calculate_size(uint32_t &total_size) const {
ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0);
ProtoSize::add_fixed32_field(total_size, 1, this->key);
ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->state));
ProtoSize::add_uint32_field(total_size, 1, this->device_id);
}
@ -1784,7 +1784,7 @@ void ListEntitiesButtonResponse::encode(ProtoWriteBuffer buffer) const {
}
void ListEntitiesButtonResponse::calculate_size(uint32_t &total_size) const {
ProtoSize::add_string_field(total_size, 1, this->object_id);
ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0);
ProtoSize::add_fixed32_field(total_size, 1, this->key);
ProtoSize::add_string_field(total_size, 1, this->name);
ProtoSize::add_string_field(total_size, 1, this->unique_id);
#ifdef USE_ENTITY_ICON
@ -1882,7 +1882,7 @@ void ListEntitiesMediaPlayerResponse::encode(ProtoWriteBuffer buffer) const {
}
void ListEntitiesMediaPlayerResponse::calculate_size(uint32_t &total_size) const {
ProtoSize::add_string_field(total_size, 1, this->object_id);
ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0);
ProtoSize::add_fixed32_field(total_size, 1, this->key);
ProtoSize::add_string_field(total_size, 1, this->name);
ProtoSize::add_string_field(total_size, 1, this->unique_id);
#ifdef USE_ENTITY_ICON
@ -1904,9 +1904,9 @@ void MediaPlayerStateResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_uint32(5, this->device_id);
}
void MediaPlayerStateResponse::calculate_size(uint32_t &total_size) const {
ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0);
ProtoSize::add_fixed32_field(total_size, 1, this->key);
ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->state));
ProtoSize::add_fixed_field<4>(total_size, 1, this->volume != 0.0f);
ProtoSize::add_float_field(total_size, 1, this->volume);
ProtoSize::add_bool_field(total_size, 1, this->muted);
ProtoSize::add_uint32_field(total_size, 1, this->device_id);
}
@ -2508,7 +2508,7 @@ void VoiceAssistantAudioSettings::encode(ProtoWriteBuffer buffer) const {
void VoiceAssistantAudioSettings::calculate_size(uint32_t &total_size) const {
ProtoSize::add_uint32_field(total_size, 1, this->noise_suppression_level);
ProtoSize::add_uint32_field(total_size, 1, this->auto_gain);
ProtoSize::add_fixed_field<4>(total_size, 1, this->volume_multiplier != 0.0f);
ProtoSize::add_float_field(total_size, 1, this->volume_multiplier);
}
void VoiceAssistantRequest::encode(ProtoWriteBuffer buffer) const {
buffer.encode_bool(1, this->start);
@ -2750,7 +2750,7 @@ void ListEntitiesAlarmControlPanelResponse::encode(ProtoWriteBuffer buffer) cons
}
void ListEntitiesAlarmControlPanelResponse::calculate_size(uint32_t &total_size) const {
ProtoSize::add_string_field(total_size, 1, this->object_id);
ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0);
ProtoSize::add_fixed32_field(total_size, 1, this->key);
ProtoSize::add_string_field(total_size, 1, this->name);
ProtoSize::add_string_field(total_size, 1, this->unique_id);
#ifdef USE_ENTITY_ICON
@ -2771,7 +2771,7 @@ void AlarmControlPanelStateResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_uint32(3, this->device_id);
}
void AlarmControlPanelStateResponse::calculate_size(uint32_t &total_size) const {
ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0);
ProtoSize::add_fixed32_field(total_size, 1, this->key);
ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->state));
ProtoSize::add_uint32_field(total_size, 1, this->device_id);
}
@ -2830,7 +2830,7 @@ void ListEntitiesTextResponse::encode(ProtoWriteBuffer buffer) const {
}
void ListEntitiesTextResponse::calculate_size(uint32_t &total_size) const {
ProtoSize::add_string_field(total_size, 1, this->object_id);
ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0);
ProtoSize::add_fixed32_field(total_size, 1, this->key);
ProtoSize::add_string_field(total_size, 1, this->name);
ProtoSize::add_string_field(total_size, 1, this->unique_id);
#ifdef USE_ENTITY_ICON
@ -2853,7 +2853,7 @@ void TextStateResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_uint32(4, this->device_id);
}
void TextStateResponse::calculate_size(uint32_t &total_size) const {
ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0);
ProtoSize::add_fixed32_field(total_size, 1, this->key);
ProtoSize::add_string_field(total_size, 1, this->state);
ProtoSize::add_bool_field(total_size, 1, this->missing_state);
ProtoSize::add_uint32_field(total_size, 1, this->device_id);
@ -2906,7 +2906,7 @@ void ListEntitiesDateResponse::encode(ProtoWriteBuffer buffer) const {
}
void ListEntitiesDateResponse::calculate_size(uint32_t &total_size) const {
ProtoSize::add_string_field(total_size, 1, this->object_id);
ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0);
ProtoSize::add_fixed32_field(total_size, 1, this->key);
ProtoSize::add_string_field(total_size, 1, this->name);
ProtoSize::add_string_field(total_size, 1, this->unique_id);
#ifdef USE_ENTITY_ICON
@ -2927,7 +2927,7 @@ void DateStateResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_uint32(6, this->device_id);
}
void DateStateResponse::calculate_size(uint32_t &total_size) const {
ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0);
ProtoSize::add_fixed32_field(total_size, 1, this->key);
ProtoSize::add_bool_field(total_size, 1, this->missing_state);
ProtoSize::add_uint32_field(total_size, 1, this->year);
ProtoSize::add_uint32_field(total_size, 1, this->month);
@ -2981,7 +2981,7 @@ void ListEntitiesTimeResponse::encode(ProtoWriteBuffer buffer) const {
}
void ListEntitiesTimeResponse::calculate_size(uint32_t &total_size) const {
ProtoSize::add_string_field(total_size, 1, this->object_id);
ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0);
ProtoSize::add_fixed32_field(total_size, 1, this->key);
ProtoSize::add_string_field(total_size, 1, this->name);
ProtoSize::add_string_field(total_size, 1, this->unique_id);
#ifdef USE_ENTITY_ICON
@ -3002,7 +3002,7 @@ void TimeStateResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_uint32(6, this->device_id);
}
void TimeStateResponse::calculate_size(uint32_t &total_size) const {
ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0);
ProtoSize::add_fixed32_field(total_size, 1, this->key);
ProtoSize::add_bool_field(total_size, 1, this->missing_state);
ProtoSize::add_uint32_field(total_size, 1, this->hour);
ProtoSize::add_uint32_field(total_size, 1, this->minute);
@ -3060,7 +3060,7 @@ void ListEntitiesEventResponse::encode(ProtoWriteBuffer buffer) const {
}
void ListEntitiesEventResponse::calculate_size(uint32_t &total_size) const {
ProtoSize::add_string_field(total_size, 1, this->object_id);
ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0);
ProtoSize::add_fixed32_field(total_size, 1, this->key);
ProtoSize::add_string_field(total_size, 1, this->name);
ProtoSize::add_string_field(total_size, 1, this->unique_id);
#ifdef USE_ENTITY_ICON
@ -3084,7 +3084,7 @@ void EventResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_uint32(3, this->device_id);
}
void EventResponse::calculate_size(uint32_t &total_size) const {
ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0);
ProtoSize::add_fixed32_field(total_size, 1, this->key);
ProtoSize::add_string_field(total_size, 1, this->event_type);
ProtoSize::add_uint32_field(total_size, 1, this->device_id);
}
@ -3110,7 +3110,7 @@ void ListEntitiesValveResponse::encode(ProtoWriteBuffer buffer) const {
}
void ListEntitiesValveResponse::calculate_size(uint32_t &total_size) const {
ProtoSize::add_string_field(total_size, 1, this->object_id);
ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0);
ProtoSize::add_fixed32_field(total_size, 1, this->key);
ProtoSize::add_string_field(total_size, 1, this->name);
ProtoSize::add_string_field(total_size, 1, this->unique_id);
#ifdef USE_ENTITY_ICON
@ -3133,8 +3133,8 @@ void ValveStateResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_uint32(4, this->device_id);
}
void ValveStateResponse::calculate_size(uint32_t &total_size) const {
ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0);
ProtoSize::add_fixed_field<4>(total_size, 1, this->position != 0.0f);
ProtoSize::add_fixed32_field(total_size, 1, this->key);
ProtoSize::add_float_field(total_size, 1, this->position);
ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->current_operation));
ProtoSize::add_uint32_field(total_size, 1, this->device_id);
}
@ -3185,7 +3185,7 @@ void ListEntitiesDateTimeResponse::encode(ProtoWriteBuffer buffer) const {
}
void ListEntitiesDateTimeResponse::calculate_size(uint32_t &total_size) const {
ProtoSize::add_string_field(total_size, 1, this->object_id);
ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0);
ProtoSize::add_fixed32_field(total_size, 1, this->key);
ProtoSize::add_string_field(total_size, 1, this->name);
ProtoSize::add_string_field(total_size, 1, this->unique_id);
#ifdef USE_ENTITY_ICON
@ -3204,9 +3204,9 @@ void DateTimeStateResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_uint32(4, this->device_id);
}
void DateTimeStateResponse::calculate_size(uint32_t &total_size) const {
ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0);
ProtoSize::add_fixed32_field(total_size, 1, this->key);
ProtoSize::add_bool_field(total_size, 1, this->missing_state);
ProtoSize::add_fixed_field<4>(total_size, 1, this->epoch_seconds != 0);
ProtoSize::add_fixed32_field(total_size, 1, this->epoch_seconds);
ProtoSize::add_uint32_field(total_size, 1, this->device_id);
}
bool DateTimeCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
@ -3251,7 +3251,7 @@ void ListEntitiesUpdateResponse::encode(ProtoWriteBuffer buffer) const {
}
void ListEntitiesUpdateResponse::calculate_size(uint32_t &total_size) const {
ProtoSize::add_string_field(total_size, 1, this->object_id);
ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0);
ProtoSize::add_fixed32_field(total_size, 1, this->key);
ProtoSize::add_string_field(total_size, 1, this->name);
ProtoSize::add_string_field(total_size, 1, this->unique_id);
#ifdef USE_ENTITY_ICON
@ -3280,11 +3280,11 @@ void UpdateStateResponse::encode(ProtoWriteBuffer buffer) const {
#endif
}
void UpdateStateResponse::calculate_size(uint32_t &total_size) const {
ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0);
ProtoSize::add_fixed32_field(total_size, 1, this->key);
ProtoSize::add_bool_field(total_size, 1, this->missing_state);
ProtoSize::add_bool_field(total_size, 1, this->in_progress);
ProtoSize::add_bool_field(total_size, 1, this->has_progress);
ProtoSize::add_fixed_field<4>(total_size, 1, this->progress != 0.0f);
ProtoSize::add_float_field(total_size, 1, this->progress);
ProtoSize::add_string_field(total_size, 1, this->current_version);
ProtoSize::add_string_field(total_size, 1, this->latest_version);
ProtoSize::add_string_field(total_size, 1, this->title);

View File

@ -31,7 +31,6 @@ APIServer::APIServer() {
}
void APIServer::setup() {
ESP_LOGCONFIG(TAG, "Running setup");
this->setup_controller();
#ifdef USE_API_NOISE
@ -105,7 +104,7 @@ void APIServer::setup() {
return;
}
for (auto &c : this->clients_) {
if (!c->flags_.remove)
if (!c->flags_.remove && c->get_log_subscription_level() >= level)
c->try_send_log_message(level, tag, message, message_len);
}
});
@ -205,16 +204,16 @@ void APIServer::loop() {
void APIServer::dump_config() {
ESP_LOGCONFIG(TAG,
"API Server:\n"
"Server:\n"
" Address: %s:%u",
network::get_use_address().c_str(), this->port_);
#ifdef USE_API_NOISE
ESP_LOGCONFIG(TAG, " Using noise encryption: %s", YESNO(this->noise_ctx_->has_psk()));
ESP_LOGCONFIG(TAG, " Noise encryption: %s", YESNO(this->noise_ctx_->has_psk()));
if (!this->noise_ctx_->has_psk()) {
ESP_LOGCONFIG(TAG, " Supports noise encryption: YES");
ESP_LOGCONFIG(TAG, " Supports encryption: YES");
}
#else
ESP_LOGCONFIG(TAG, " Using noise encryption: NO");
ESP_LOGCONFIG(TAG, " Noise encryption: NO");
#endif
}

View File

@ -189,9 +189,9 @@ class ProtoWriteBuffer {
* @param field_id Field number (tag) in the protobuf message
* @param type Wire type value:
* - 0: Varint (int32, int64, uint32, uint64, sint32, sint64, bool, enum)
* - 1: 64-bit (fixed64, sfixed64, double)
* - 2: Length-delimited (string, bytes, embedded messages, packed repeated fields)
* - 5: 32-bit (fixed32, sfixed32, float)
* - Note: Wire type 1 (64-bit fixed) is not supported
*
* Following https://protobuf.dev/programming-guides/encoding/#structure
*/
@ -540,6 +540,42 @@ class ProtoSize {
total_size += field_id_size + NumBytes;
}
/**
* @brief Calculates and adds the size of a float field to the total message size
*/
static inline void add_float_field(uint32_t &total_size, uint32_t field_id_size, float value) {
if (value != 0.0f) {
total_size += field_id_size + 4;
}
}
// NOTE: add_double_field removed - wire type 1 (64-bit: double) not supported
// to reduce overhead on embedded systems
/**
* @brief Calculates and adds the size of a fixed32 field to the total message size
*/
static inline void add_fixed32_field(uint32_t &total_size, uint32_t field_id_size, uint32_t value) {
if (value != 0) {
total_size += field_id_size + 4;
}
}
// NOTE: add_fixed64_field removed - wire type 1 (64-bit: fixed64) not supported
// to reduce overhead on embedded systems
/**
* @brief Calculates and adds the size of a sfixed32 field to the total message size
*/
static inline void add_sfixed32_field(uint32_t &total_size, uint32_t field_id_size, int32_t value) {
if (value != 0) {
total_size += field_id_size + 4;
}
}
// NOTE: add_sfixed64_field removed - wire type 1 (64-bit: sfixed64) not supported
// to reduce overhead on embedded systems
/**
* @brief Calculates and adds the size of an enum field to the total message size
*

View File

@ -85,13 +85,13 @@ async def to_code(config):
await cg.register_component(var, config)
cg.add(var.set_active(config[CONF_ACTIVE]))
await esp32_ble_tracker.register_ble_device(var, config)
await esp32_ble_tracker.register_raw_ble_device(var, config)
for connection_conf in config.get(CONF_CONNECTIONS, []):
connection_var = cg.new_Pvariable(connection_conf[CONF_ID])
await cg.register_component(connection_var, connection_conf)
cg.add(var.register_connection(connection_var))
await esp32_ble_tracker.register_client(connection_var, connection_conf)
await esp32_ble_tracker.register_raw_client(connection_var, connection_conf)
if config.get(CONF_CACHE_SERVICES):
add_idf_sdkconfig_option("CONFIG_BT_GATTC_CACHE_NVS_FLASH", True)

View File

@ -42,15 +42,13 @@ void BluetoothProxy::send_bluetooth_scanner_state_(esp32_ble_tracker::ScannerSta
this->api_connection_->send_message(resp);
}
#ifdef USE_ESP32_BLE_DEVICE
bool BluetoothProxy::parse_device(const esp32_ble_tracker::ESPBTDevice &device) {
if (!api::global_api_server->is_connected() || this->api_connection_ == nullptr || this->raw_advertisements_)
// This method should never be called since bluetooth_proxy always uses raw advertisements
// but we need to provide an implementation to satisfy the virtual method requirement
return false;
ESP_LOGV(TAG, "Proxying packet from %s - %s. RSSI: %d dB", device.get_name().c_str(), device.address_str().c_str(),
device.get_rssi());
this->send_api_packet_(device);
return true;
}
#endif
// Batch size for BLE advertisements to maximize WiFi efficiency
// Each advertisement is up to 80 bytes when packaged (including protocol overhead)
@ -69,7 +67,7 @@ std::vector<api::BluetoothLERawAdvertisement> batch_buffer;
static std::vector<api::BluetoothLERawAdvertisement> &get_batch_buffer() { return batch_buffer; }
bool BluetoothProxy::parse_devices(const esp32_ble::BLEScanResult *scan_results, size_t count) {
if (!api::global_api_server->is_connected() || this->api_connection_ == nullptr || !this->raw_advertisements_)
if (!api::global_api_server->is_connected() || this->api_connection_ == nullptr)
return false;
// Get the batch buffer reference
@ -116,6 +114,7 @@ void BluetoothProxy::flush_pending_advertisements() {
this->api_connection_->send_message(resp);
}
#ifdef USE_ESP32_BLE_DEVICE
void BluetoothProxy::send_api_packet_(const esp32_ble_tracker::ESPBTDevice &device) {
api::BluetoothLEAdvertisementResponse resp;
resp.address = device.address_uint64();
@ -153,14 +152,14 @@ void BluetoothProxy::send_api_packet_(const esp32_ble_tracker::ESPBTDevice &devi
this->api_connection_->send_message(resp);
}
#endif // USE_ESP32_BLE_DEVICE
void BluetoothProxy::dump_config() {
ESP_LOGCONFIG(TAG, "Bluetooth Proxy:");
ESP_LOGCONFIG(TAG,
" Active: %s\n"
" Connections: %d\n"
" Raw advertisements: %s",
YESNO(this->active_), this->connections_.size(), YESNO(this->raw_advertisements_));
" Connections: %d",
YESNO(this->active_), this->connections_.size());
}
int BluetoothProxy::get_bluetooth_connections_free() {
@ -188,7 +187,6 @@ void BluetoothProxy::loop() {
}
// Flush any pending BLE advertisements that have been accumulated but not yet sent
if (this->raw_advertisements_) {
static uint32_t last_flush_time = 0;
uint32_t now = App.get_loop_component_start_time();
@ -197,7 +195,6 @@ void BluetoothProxy::loop() {
this->flush_pending_advertisements();
last_flush_time = now;
}
}
for (auto *connection : this->connections_) {
if (connection->send_service_ == connection->service_count_) {
connection->send_service_ = DONE_SENDING_SERVICES;
@ -318,9 +315,7 @@ void BluetoothProxy::loop() {
}
esp32_ble_tracker::AdvertisementParserType BluetoothProxy::get_advertisement_parser_type() {
if (this->raw_advertisements_)
return esp32_ble_tracker::AdvertisementParserType::RAW_ADVERTISEMENTS;
return esp32_ble_tracker::AdvertisementParserType::PARSED_ADVERTISEMENTS;
}
BluetoothConnection *BluetoothProxy::get_connection_(uint64_t address, bool reserve) {
@ -565,7 +560,6 @@ void BluetoothProxy::subscribe_api_connection(api::APIConnection *api_connection
return;
}
this->api_connection_ = api_connection;
this->raw_advertisements_ = flags & BluetoothProxySubscriptionFlag::SUBSCRIPTION_RAW_ADVERTISEMENTS;
this->parent_->recalculate_advertisement_parser_types();
this->send_bluetooth_scanner_state_(this->parent_->get_scanner_state());
@ -577,7 +571,6 @@ void BluetoothProxy::unsubscribe_api_connection(api::APIConnection *api_connecti
return;
}
this->api_connection_ = nullptr;
this->raw_advertisements_ = false;
this->parent_->recalculate_advertisement_parser_types();
}

View File

@ -51,7 +51,9 @@ enum BluetoothProxySubscriptionFlag : uint32_t {
class BluetoothProxy : public esp32_ble_tracker::ESPBTDeviceListener, public Component {
public:
BluetoothProxy();
#ifdef USE_ESP32_BLE_DEVICE
bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override;
#endif
bool parse_devices(const esp32_ble::BLEScanResult *scan_results, size_t count) override;
void dump_config() override;
void setup() override;
@ -129,7 +131,9 @@ class BluetoothProxy : public esp32_ble_tracker::ESPBTDeviceListener, public Com
}
protected:
#ifdef USE_ESP32_BLE_DEVICE
void send_api_packet_(const esp32_ble_tracker::ESPBTDevice &device);
#endif
void send_bluetooth_scanner_state_(esp32_ble_tracker::ScannerState state);
BluetoothConnection *get_connection_(uint64_t address, bool reserve);
@ -143,8 +147,7 @@ class BluetoothProxy : public esp32_ble_tracker::ESPBTDeviceListener, public Com
// Group 3: 1-byte types grouped together
bool active_;
bool raw_advertisements_{false};
// 2 bytes used, 2 bytes padding
// 1 byte used, 3 bytes padding
};
extern BluetoothProxy *global_bluetooth_proxy; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)

View File

@ -105,6 +105,7 @@ void BLEClientBase::dump_config() {
}
}
#ifdef USE_ESP32_BLE_DEVICE
bool BLEClientBase::parse_device(const espbt::ESPBTDevice &device) {
if (!this->auto_connect_)
return false;
@ -122,6 +123,7 @@ bool BLEClientBase::parse_device(const espbt::ESPBTDevice &device) {
this->remote_addr_type_ = device.get_address_type();
return true;
}
#endif
void BLEClientBase::connect() {
ESP_LOGI(TAG, "[%d] [%s] 0x%02x Attempting BLE connection", this->connection_index_, this->address_str_.c_str(),

View File

@ -31,7 +31,9 @@ class BLEClientBase : public espbt::ESPBTClient, public Component {
void dump_config() override;
void run_later(std::function<void()> &&f); // NOLINT
#ifdef USE_ESP32_BLE_DEVICE
bool parse_device(const espbt::ESPBTDevice &device) override;
#endif
void on_scan_end() override {}
bool gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
esp_ble_gattc_cb_param_t *param) override;

View File

@ -31,6 +31,8 @@ from esphome.const import (
CONF_TRIGGER_ID,
)
from esphome.core import CORE
from esphome.enum import StrEnum
from esphome.types import ConfigType
AUTO_LOAD = ["esp32_ble"]
DEPENDENCIES = ["esp32"]
@ -50,6 +52,25 @@ IDF_MAX_CONNECTIONS = 9
_LOGGER = logging.getLogger(__name__)
# Enum for BLE features
class BLEFeatures(StrEnum):
ESP_BT_DEVICE = "ESP_BT_DEVICE"
# Set to track which features are needed by components
_required_features: set[BLEFeatures] = set()
def register_ble_features(features: set[BLEFeatures]) -> None:
"""Register BLE features that a component needs.
Args:
features: Set of BLEFeatures enum members
"""
_required_features.update(features)
esp32_ble_tracker_ns = cg.esphome_ns.namespace("esp32_ble_tracker")
ESP32BLETracker = esp32_ble_tracker_ns.class_(
"ESP32BLETracker",
@ -277,6 +298,15 @@ async def to_code(config):
cg.add(var.set_scan_window(int(params[CONF_WINDOW].total_milliseconds / 0.625)))
cg.add(var.set_scan_active(params[CONF_ACTIVE]))
cg.add(var.set_scan_continuous(params[CONF_CONTINUOUS]))
# Register ESP_BT_DEVICE feature if any of the automation triggers are used
if (
config.get(CONF_ON_BLE_ADVERTISE)
or config.get(CONF_ON_BLE_SERVICE_DATA_ADVERTISE)
or config.get(CONF_ON_BLE_MANUFACTURER_DATA_ADVERTISE)
):
register_ble_features({BLEFeatures.ESP_BT_DEVICE})
for conf in config.get(CONF_ON_BLE_ADVERTISE, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
if CONF_MAC_ADDRESS in conf:
@ -334,6 +364,11 @@ async def to_code(config):
cg.add_define("USE_OTA_STATE_CALLBACK") # To be notified when an OTA update starts
cg.add_define("USE_ESP32_BLE_CLIENT")
# Add feature-specific defines based on what's needed
if BLEFeatures.ESP_BT_DEVICE in _required_features:
cg.add_define("USE_ESP32_BLE_DEVICE")
if config.get(CONF_SOFTWARE_COEXISTENCE):
cg.add_define("USE_ESP32_BLE_SOFTWARE_COEXISTENCE")
@ -382,13 +417,43 @@ async def esp32_ble_tracker_stop_scan_action_to_code(
return var
async def register_ble_device(var, config):
async def register_ble_device(
var: cg.SafeExpType, config: ConfigType
) -> cg.SafeExpType:
register_ble_features({BLEFeatures.ESP_BT_DEVICE})
paren = await cg.get_variable(config[CONF_ESP32_BLE_ID])
cg.add(paren.register_listener(var))
return var
async def register_client(var, config):
async def register_client(var: cg.SafeExpType, config: ConfigType) -> cg.SafeExpType:
register_ble_features({BLEFeatures.ESP_BT_DEVICE})
paren = await cg.get_variable(config[CONF_ESP32_BLE_ID])
cg.add(paren.register_client(var))
return var
async def register_raw_ble_device(
var: cg.SafeExpType, config: ConfigType
) -> cg.SafeExpType:
"""Register a BLE device listener that only needs raw advertisement data.
This does NOT register the ESP_BT_DEVICE feature, meaning ESPBTDevice
will not be compiled in if this is the only registration method used.
"""
paren = await cg.get_variable(config[CONF_ESP32_BLE_ID])
cg.add(paren.register_listener(var))
return var
async def register_raw_client(
var: cg.SafeExpType, config: ConfigType
) -> cg.SafeExpType:
"""Register a BLE client that only needs raw advertisement data.
This does NOT register the ESP_BT_DEVICE feature, meaning ESPBTDevice
will not be compiled in if this is the only registration method used.
"""
paren = await cg.get_variable(config[CONF_ESP32_BLE_ID])
cg.add(paren.register_client(var))
return var

View File

@ -7,6 +7,7 @@
namespace esphome {
namespace esp32_ble_tracker {
#ifdef USE_ESP32_BLE_DEVICE
class ESPBTAdvertiseTrigger : public Trigger<const ESPBTDevice &>, public ESPBTDeviceListener {
public:
explicit ESPBTAdvertiseTrigger(ESP32BLETracker *parent) { parent->register_listener(this); }
@ -87,6 +88,7 @@ class BLEEndOfScanTrigger : public Trigger<>, public ESPBTDeviceListener {
bool parse_device(const ESPBTDevice &device) override { return false; }
void on_scan_end() override { this->trigger(); }
};
#endif // USE_ESP32_BLE_DEVICE
template<typename... Ts> class ESP32BLEStartScanAction : public Action<Ts...> {
public:

View File

@ -141,6 +141,7 @@ void ESP32BLETracker::loop() {
}
if (this->parse_advertisements_) {
#ifdef USE_ESP32_BLE_DEVICE
ESPBTDevice device;
device.parse_scan_rst(scan_result);
@ -162,6 +163,7 @@ void ESP32BLETracker::loop() {
if (!found && !this->scan_continuous_) {
this->print_bt_device_info(device);
}
#endif // USE_ESP32_BLE_DEVICE
}
// Move to next entry in ring buffer
@ -511,6 +513,7 @@ void ESP32BLETracker::set_scanner_state_(ScannerState state) {
this->scanner_state_callbacks_.call(state);
}
#ifdef USE_ESP32_BLE_DEVICE
ESPBLEiBeacon::ESPBLEiBeacon(const uint8_t *data) { memcpy(&this->beacon_data_, data, sizeof(beacon_data_)); }
optional<ESPBLEiBeacon> ESPBLEiBeacon::from_manufacturer_data(const ServiceData &data) {
if (!data.uuid.contains(0x4C, 0x00))
@ -751,13 +754,16 @@ void ESPBTDevice::parse_adv_(const uint8_t *payload, uint8_t len) {
}
}
}
std::string ESPBTDevice::address_str() const {
char mac[24];
snprintf(mac, sizeof(mac), "%02X:%02X:%02X:%02X:%02X:%02X", this->address_[0], this->address_[1], this->address_[2],
this->address_[3], this->address_[4], this->address_[5]);
return mac;
}
uint64_t ESPBTDevice::address_uint64() const { return esp32_ble::ble_addr_to_uint64(this->address_); }
#endif // USE_ESP32_BLE_DEVICE
void ESP32BLETracker::dump_config() {
ESP_LOGCONFIG(TAG, "BLE Tracker:");
@ -796,6 +802,7 @@ void ESP32BLETracker::dump_config() {
}
}
#ifdef USE_ESP32_BLE_DEVICE
void ESP32BLETracker::print_bt_device_info(const ESPBTDevice &device) {
const uint64_t address = device.address_uint64();
for (auto &disc : this->already_discovered_) {
@ -866,8 +873,9 @@ bool ESPBTDevice::resolve_irk(const uint8_t *irk) const {
return ecb_ciphertext[15] == (addr64 & 0xff) && ecb_ciphertext[14] == ((addr64 >> 8) & 0xff) &&
ecb_ciphertext[13] == ((addr64 >> 16) & 0xff);
}
#endif // USE_ESP32_BLE_DEVICE
} // namespace esp32_ble_tracker
} // namespace esphome
#endif
#endif // USE_ESP32

View File

@ -39,6 +39,7 @@ struct ServiceData {
adv_data_t data;
};
#ifdef USE_ESP32_BLE_DEVICE
class ESPBLEiBeacon {
public:
ESPBLEiBeacon() { memset(&this->beacon_data_, 0, sizeof(this->beacon_data_)); }
@ -116,13 +117,16 @@ class ESPBTDevice {
std::vector<ServiceData> service_datas_{};
const BLEScanResult *scan_result_{nullptr};
};
#endif // USE_ESP32_BLE_DEVICE
class ESP32BLETracker;
class ESPBTDeviceListener {
public:
virtual void on_scan_end() {}
#ifdef USE_ESP32_BLE_DEVICE
virtual bool parse_device(const ESPBTDevice &device) = 0;
#endif
virtual bool parse_devices(const BLEScanResult *scan_results, size_t count) { return false; };
virtual AdvertisementParserType get_advertisement_parser_type() {
return AdvertisementParserType::PARSED_ADVERTISEMENTS;
@ -237,7 +241,9 @@ class ESP32BLETracker : public Component,
void register_client(ESPBTClient *client);
void recalculate_advertisement_parser_types();
#ifdef USE_ESP32_BLE_DEVICE
void print_bt_device_info(const ESPBTDevice &device);
#endif
void start_scan();
void stop_scan();

View File

@ -7,6 +7,7 @@
#include "esphome/core/hal.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#include <driver/gpio.h>
#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 3, 0)
#define SOC_HP_I2C_NUM SOC_I2C_NUM
@ -20,21 +21,72 @@ static const char *const TAG = "i2c.idf";
void IDFI2CBus::setup() {
ESP_LOGCONFIG(TAG, "Running setup");
static i2c_port_t next_port = I2C_NUM_0;
port_ = next_port;
this->port_ = next_port;
if (this->port_ == I2C_NUM_MAX) {
ESP_LOGE(TAG, "No more than %u buses supported", I2C_NUM_MAX);
this->mark_failed();
return;
}
if (this->timeout_ > 13000) {
ESP_LOGW(TAG, "Using max allowed timeout: 13 ms");
this->timeout_ = 13000;
}
this->recover_();
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 4, 2)
next_port = (i2c_port_t) (next_port + 1);
i2c_master_bus_config_t bus_conf{};
memset(&bus_conf, 0, sizeof(bus_conf));
bus_conf.sda_io_num = gpio_num_t(sda_pin_);
bus_conf.scl_io_num = gpio_num_t(scl_pin_);
bus_conf.i2c_port = this->port_;
bus_conf.glitch_ignore_cnt = 7;
#if SOC_LP_I2C_SUPPORTED
if (this->port_ < SOC_HP_I2C_NUM) {
bus_conf.clk_source = I2C_CLK_SRC_DEFAULT;
} else {
bus_conf.lp_source_clk = LP_I2C_SCLK_DEFAULT;
}
#else
bus_conf.clk_source = I2C_CLK_SRC_DEFAULT;
#endif
bus_conf.flags.enable_internal_pullup = sda_pullup_enabled_ || scl_pullup_enabled_;
esp_err_t err = i2c_new_master_bus(&bus_conf, &this->bus_);
if (err != ESP_OK) {
ESP_LOGW(TAG, "i2c_new_master_bus failed: %s", esp_err_to_name(err));
this->mark_failed();
return;
}
i2c_device_config_t dev_conf{};
memset(&dev_conf, 0, sizeof(dev_conf));
dev_conf.dev_addr_length = I2C_ADDR_BIT_LEN_7;
dev_conf.device_address = I2C_DEVICE_ADDRESS_NOT_USED;
dev_conf.scl_speed_hz = this->frequency_;
dev_conf.scl_wait_us = this->timeout_;
err = i2c_master_bus_add_device(this->bus_, &dev_conf, &this->dev_);
if (err != ESP_OK) {
ESP_LOGW(TAG, "i2c_master_bus_add_device failed: %s", esp_err_to_name(err));
this->mark_failed();
return;
}
this->initialized_ = true;
if (this->scan_) {
ESP_LOGV(TAG, "Scanning for devices");
this->i2c_scan_();
}
#else
#if SOC_HP_I2C_NUM > 1
next_port = (next_port == I2C_NUM_0) ? I2C_NUM_1 : I2C_NUM_MAX;
#else
next_port = I2C_NUM_MAX;
#endif
if (port_ == I2C_NUM_MAX) {
ESP_LOGE(TAG, "No more than %u buses supported", SOC_HP_I2C_NUM);
this->mark_failed();
return;
}
recover_();
i2c_config_t conf{};
memset(&conf, 0, sizeof(conf));
conf.mode = I2C_MODE_MASTER;
@ -53,11 +105,7 @@ void IDFI2CBus::setup() {
this->mark_failed();
return;
}
if (timeout_ > 0) { // if timeout specified in yaml:
if (timeout_ > 13000) {
ESP_LOGW(TAG, "i2c timeout of %" PRIu32 "us greater than max of 13ms on esp-idf, setting to max", timeout_);
timeout_ = 13000;
}
if (timeout_ > 0) {
err = i2c_set_timeout(port_, timeout_ * 80); // unit: APB 80MHz clock cycle
if (err != ESP_OK) {
ESP_LOGW(TAG, "i2c_set_timeout failed: %s", esp_err_to_name(err));
@ -73,12 +121,15 @@ void IDFI2CBus::setup() {
this->mark_failed();
return;
}
initialized_ = true;
if (this->scan_) {
ESP_LOGV(TAG, "Scanning bus for active devices");
this->i2c_scan_();
}
#endif
}
void IDFI2CBus::dump_config() {
ESP_LOGCONFIG(TAG, "I2C Bus:");
ESP_LOGCONFIG(TAG,
@ -123,6 +174,74 @@ ErrorCode IDFI2CBus::readv(uint8_t address, ReadBuffer *buffers, size_t cnt) {
ESP_LOGVV(TAG, "i2c bus not initialized!");
return ERROR_NOT_INITIALIZED;
}
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 4, 2)
i2c_operation_job_t jobs[cnt + 4];
uint8_t read = (address << 1) | I2C_MASTER_READ;
size_t last = 0, num = 0;
jobs[num].command = I2C_MASTER_CMD_START;
num++;
jobs[num].command = I2C_MASTER_CMD_WRITE;
jobs[num].write.ack_check = true;
jobs[num].write.data = &read;
jobs[num].write.total_bytes = 1;
num++;
// find the last valid index
for (size_t i = 0; i < cnt; i++) {
const auto &buf = buffers[i];
if (buf.len == 0) {
continue;
}
last = i;
}
for (size_t i = 0; i < cnt; i++) {
const auto &buf = buffers[i];
if (buf.len == 0) {
continue;
}
if (i == last) {
// the last byte read before stop should always be a nack,
// split the last read if len is larger than 1
if (buf.len > 1) {
jobs[num].command = I2C_MASTER_CMD_READ;
jobs[num].read.ack_value = I2C_ACK_VAL;
jobs[num].read.data = (uint8_t *) buf.data;
jobs[num].read.total_bytes = buf.len - 1;
num++;
}
jobs[num].command = I2C_MASTER_CMD_READ;
jobs[num].read.ack_value = I2C_NACK_VAL;
jobs[num].read.data = (uint8_t *) buf.data + buf.len - 1;
jobs[num].read.total_bytes = 1;
num++;
} else {
jobs[num].command = I2C_MASTER_CMD_READ;
jobs[num].read.ack_value = I2C_ACK_VAL;
jobs[num].read.data = (uint8_t *) buf.data;
jobs[num].read.total_bytes = buf.len;
num++;
}
}
jobs[num].command = I2C_MASTER_CMD_STOP;
num++;
esp_err_t err = i2c_master_execute_defined_operations(this->dev_, jobs, num, 20);
if (err == ESP_ERR_INVALID_STATE) {
ESP_LOGVV(TAG, "RX from %02X failed: not acked", address);
return ERROR_NOT_ACKNOWLEDGED;
} else if (err == ESP_ERR_TIMEOUT) {
ESP_LOGVV(TAG, "RX from %02X failed: timeout", address);
return ERROR_TIMEOUT;
} else if (err != ESP_OK) {
ESP_LOGVV(TAG, "RX from %02X failed: %s", address, esp_err_to_name(err));
return ERROR_UNKNOWN;
}
#else
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
esp_err_t err = i2c_master_start(cmd);
if (err != ESP_OK) {
@ -168,6 +287,7 @@ ErrorCode IDFI2CBus::readv(uint8_t address, ReadBuffer *buffers, size_t cnt) {
ESP_LOGVV(TAG, "RX from %02X failed: %s", address, esp_err_to_name(err));
return ERROR_UNKNOWN;
}
#endif
#ifdef ESPHOME_LOG_HAS_VERY_VERBOSE
char debug_buf[4];
@ -185,6 +305,7 @@ ErrorCode IDFI2CBus::readv(uint8_t address, ReadBuffer *buffers, size_t cnt) {
return ERROR_OK;
}
ErrorCode IDFI2CBus::writev(uint8_t address, WriteBuffer *buffers, size_t cnt, bool stop) {
// logging is only enabled with vv level, if warnings are shown the caller
// should log them
@ -207,6 +328,49 @@ ErrorCode IDFI2CBus::writev(uint8_t address, WriteBuffer *buffers, size_t cnt, b
ESP_LOGVV(TAG, "0x%02X TX %s", address, debug_hex.c_str());
#endif
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 4, 2)
i2c_operation_job_t jobs[cnt + 3];
uint8_t write = (address << 1) | I2C_MASTER_WRITE;
size_t num = 0;
jobs[num].command = I2C_MASTER_CMD_START;
num++;
jobs[num].command = I2C_MASTER_CMD_WRITE;
jobs[num].write.ack_check = true;
jobs[num].write.data = &write;
jobs[num].write.total_bytes = 1;
num++;
for (size_t i = 0; i < cnt; i++) {
const auto &buf = buffers[i];
if (buf.len == 0) {
continue;
}
jobs[num].command = I2C_MASTER_CMD_WRITE;
jobs[num].write.ack_check = true;
jobs[num].write.data = (uint8_t *) buf.data;
jobs[num].write.total_bytes = buf.len;
num++;
}
if (stop) {
jobs[num].command = I2C_MASTER_CMD_STOP;
num++;
}
esp_err_t err = i2c_master_execute_defined_operations(this->dev_, jobs, num, 20);
if (err == ESP_ERR_INVALID_STATE) {
ESP_LOGVV(TAG, "TX to %02X failed: not acked", address);
return ERROR_NOT_ACKNOWLEDGED;
} else if (err == ESP_ERR_TIMEOUT) {
ESP_LOGVV(TAG, "TX to %02X failed: timeout", address);
return ERROR_TIMEOUT;
} else if (err != ESP_OK) {
ESP_LOGVV(TAG, "TX to %02X failed: %s", address, esp_err_to_name(err));
return ERROR_UNKNOWN;
}
#else
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
esp_err_t err = i2c_master_start(cmd);
if (err != ESP_OK) {
@ -252,6 +416,7 @@ ErrorCode IDFI2CBus::writev(uint8_t address, WriteBuffer *buffers, size_t cnt, b
ESP_LOGVV(TAG, "TX to %02X failed: %s", address, esp_err_to_name(err));
return ERROR_UNKNOWN;
}
#endif
return ERROR_OK;
}

View File

@ -2,9 +2,14 @@
#ifdef USE_ESP_IDF
#include <driver/i2c.h>
#include "esphome/core/component.h"
#include "i2c_bus.h"
#include "esp_idf_version.h"
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 4, 2)
#include <driver/i2c_master.h>
#else
#include <driver/i2c.h>
#endif
namespace esphome {
namespace i2c {
@ -38,6 +43,10 @@ class IDFI2CBus : public InternalI2CBus, public Component {
RecoveryCode recovery_result_;
protected:
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 4, 2)
i2c_master_dev_handle_t dev_;
i2c_master_bus_handle_t bus_;
#endif
i2c_port_t port_;
uint8_t sda_pin_;
bool sda_pullup_enabled_;

View File

@ -76,6 +76,7 @@ async def theme_to_code(config):
for w_name, style in theme.items():
# Work around Python 3.10 bug with nested async comprehensions
# With Python 3.11 this could be simplified
# TODO: Now that we require Python 3.11+, this can be updated to use nested comprehensions
styles = {}
for part, states in collect_parts(style).items():
styles[part] = {

View File

@ -50,6 +50,7 @@ optional<float> MedianFilter::new_value(float value) {
if (!this->queue_.empty()) {
// Copy queue without NaN values
std::vector<float> median_queue;
median_queue.reserve(this->queue_.size());
for (auto v : this->queue_) {
if (!std::isnan(v)) {
median_queue.push_back(v);

View File

@ -71,7 +71,7 @@ void Application::setup() {
do {
uint8_t new_app_state = STATUS_LED_WARNING;
this->scheduler.call();
this->scheduler.call(millis());
this->feed_wdt();
for (uint32_t j = 0; j <= i; j++) {
// Update loop_component_start_time_ right before calling each component
@ -97,11 +97,11 @@ void Application::setup() {
void Application::loop() {
uint8_t new_app_state = 0;
this->scheduler.call();
// Get the initial loop time at the start
uint32_t last_op_end_time = millis();
this->scheduler.call(last_op_end_time);
// Feed WDT with time
this->feed_wdt(last_op_end_time);
@ -160,7 +160,7 @@ void Application::loop() {
this->yield_with_select_(0);
} else {
uint32_t delay_time = this->loop_interval_ - elapsed;
uint32_t next_schedule = this->scheduler.next_schedule_in().value_or(delay_time);
uint32_t next_schedule = this->scheduler.next_schedule_in(last_op_end_time).value_or(delay_time);
// next_schedule is max 0.5*delay_time
// otherwise interval=0 schedules result in constant looping with almost no sleep
next_schedule = std::max(next_schedule, delay_time / 2);

View File

@ -145,6 +145,7 @@
#define USE_CAPTIVE_PORTAL
#define USE_ESP32_BLE
#define USE_ESP32_BLE_CLIENT
#define USE_ESP32_BLE_DEVICE
#define USE_ESP32_BLE_SERVER
#define USE_I2C
#define USE_IMPROV

View File

@ -91,7 +91,7 @@ void HOT Scheduler::set_timer_common_(Component *component, SchedulerItem::Type
}
#endif
const auto now = this->millis_();
const auto now = this->millis_64_(millis());
// Type-specific setup
if (type == SchedulerItem::INTERVAL) {
@ -193,9 +193,7 @@ void HOT Scheduler::set_retry(Component *component, const std::string &name, uin
name.c_str(), initial_wait_time, max_attempts, backoff_increase_factor);
if (backoff_increase_factor < 0.0001) {
ESP_LOGE(TAG,
"set_retry(name='%s'): backoff_factor cannot be close to zero nor negative (%0.1f). Using 1.0 instead",
name.c_str(), backoff_increase_factor);
ESP_LOGE(TAG, "backoff_factor %0.1f too small, using 1.0: %s", backoff_increase_factor, name.c_str());
backoff_increase_factor = 1;
}
@ -215,19 +213,19 @@ bool HOT Scheduler::cancel_retry(Component *component, const std::string &name)
return this->cancel_timeout(component, "retry$" + name);
}
optional<uint32_t> HOT Scheduler::next_schedule_in() {
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 calls empty_() and accesses items_[0] without holding a lock, which is only
// safe when called from the main thread. Other threads must not call this method.
if (this->empty_())
return {};
auto &item = this->items_[0];
const auto now = this->millis_();
if (item->next_execution_ < now)
const auto now_64 = this->millis_64_(now);
if (item->next_execution_ < now_64)
return 0;
return item->next_execution_ - now;
return item->next_execution_ - now_64;
}
void HOT Scheduler::call() {
void HOT Scheduler::call(uint32_t now) {
#if !defined(USE_ESP8266) && !defined(USE_RP2040)
// Process defer queue first to guarantee FIFO execution order for deferred items.
// Previously, defer() used the heap which gave undefined order for equal timestamps,
@ -256,22 +254,22 @@ void HOT Scheduler::call() {
// Execute callback without holding lock to prevent deadlocks
// if the callback tries to call defer() again
if (!this->should_skip_item_(item.get())) {
this->execute_item_(item.get());
this->execute_item_(item.get(), now);
}
}
#endif
const auto now = this->millis_();
const auto now_64 = this->millis_64_(now);
this->process_to_add();
#ifdef ESPHOME_DEBUG_SCHEDULER
static uint64_t last_print = 0;
if (now - last_print > 2000) {
last_print = now;
if (now_64 - last_print > 2000) {
last_print = now_64;
std::vector<std::unique_ptr<SchedulerItem>> old_items;
ESP_LOGD(TAG, "Items: count=%zu, now=%" PRIu64 " (%u, %" PRIu32 ")", this->items_.size(), now, this->millis_major_,
this->last_millis_);
ESP_LOGD(TAG, "Items: count=%zu, now=%" PRIu64 " (%u, %" PRIu32 ")", this->items_.size(), now_64,
this->millis_major_, this->last_millis_);
while (!this->empty_()) {
std::unique_ptr<SchedulerItem> item;
{
@ -283,7 +281,7 @@ void HOT Scheduler::call() {
const char *name = item->get_name();
ESP_LOGD(TAG, " %s '%s/%s' interval=%" PRIu32 " next_execution in %" PRIu64 "ms at %" PRIu64,
item->get_type_str(), item->get_source(), name ? name : "(null)", item->interval,
item->next_execution_ - now, item->next_execution_);
item->next_execution_ - now_64, item->next_execution_);
old_items.push_back(std::move(item));
}
@ -328,7 +326,7 @@ void HOT Scheduler::call() {
{
// Don't copy-by value yet
auto &item = this->items_[0];
if (item->next_execution_ > now) {
if (item->next_execution_ > now_64) {
// Not reached timeout yet, done for this call
break;
}
@ -342,13 +340,13 @@ void HOT Scheduler::call() {
const char *item_name = item->get_name();
ESP_LOGV(TAG, "Running %s '%s/%s' with interval=%" PRIu32 " next_execution=%" PRIu64 " (now=%" PRIu64 ")",
item->get_type_str(), item->get_source(), item_name ? item_name : "(null)", item->interval,
item->next_execution_, now);
item->next_execution_, now_64);
#endif
// Warning: During callback(), a lot of stuff can happen, including:
// - timeouts/intervals get added, potentially invalidating vector pointers
// - timeouts/intervals get cancelled
this->execute_item_(item.get());
this->execute_item_(item.get(), now);
}
{
@ -367,7 +365,7 @@ void HOT Scheduler::call() {
}
if (item->type == SchedulerItem::INTERVAL) {
item->next_execution_ = now + item->interval;
item->next_execution_ = now_64 + item->interval;
// Add new item directly to to_add_
// since we have the lock held
this->to_add_.push_back(std::move(item));
@ -423,11 +421,9 @@ void HOT Scheduler::pop_raw_() {
}
// Helper to execute a scheduler item
void HOT Scheduler::execute_item_(SchedulerItem *item) {
void HOT Scheduler::execute_item_(SchedulerItem *item, uint32_t now) {
App.set_current_component(item->component);
uint32_t now_ms = millis();
WarnIfComponentBlockingGuard guard{item->component, now_ms};
WarnIfComponentBlockingGuard guard{item->component, now};
item->callback();
guard.finish();
}
@ -486,15 +482,15 @@ bool HOT Scheduler::cancel_item_locked_(Component *component, const char *name_c
return total_cancelled > 0;
}
uint64_t Scheduler::millis_() {
// Get the current 32-bit millis value
const uint32_t now = millis();
uint64_t Scheduler::millis_64_(uint32_t now) {
// Check for rollover by comparing with last value
if (now < this->last_millis_) {
// Detected rollover (happens every ~49.7 days)
this->millis_major_++;
#ifdef ESPHOME_DEBUG_SCHEDULER
ESP_LOGD(TAG, "Incrementing scheduler major at %" PRIu64 "ms",
now + (static_cast<uint64_t>(this->millis_major_) << 32));
#endif
}
this->last_millis_ = now;
// Combine major (high 32 bits) and now (low 32 bits) into 64-bit time

View File

@ -52,9 +52,9 @@ class Scheduler {
std::function<RetryResult(uint8_t)> func, float backoff_increase_factor = 1.0f);
bool cancel_retry(Component *component, const std::string &name);
optional<uint32_t> next_schedule_in();
optional<uint32_t> next_schedule_in(uint32_t now);
void call();
void call(uint32_t now);
void process_to_add();
@ -137,7 +137,7 @@ class Scheduler {
void set_timer_common_(Component *component, SchedulerItem::Type type, bool is_static_string, const void *name_ptr,
uint32_t delay, std::function<void()> func);
uint64_t millis_();
uint64_t millis_64_(uint32_t now);
void cleanup_();
void pop_raw_();
@ -175,7 +175,7 @@ class Scheduler {
}
// Helper to execute a scheduler item
void execute_item_(SchedulerItem *item);
void execute_item_(SchedulerItem *item, uint32_t now);
// Helper to check if item should be skipped
bool should_skip_item_(const SchedulerItem *item) const {

View File

@ -3,15 +3,9 @@ from __future__ import annotations
import asyncio
from contextlib import suppress
from ipaddress import ip_address
import sys
from icmplib import NameLookupError, async_resolve
if sys.version_info >= (3, 11):
from asyncio import timeout as async_timeout
else:
from async_timeout import timeout as async_timeout
RESOLVE_TIMEOUT = 3.0
@ -20,9 +14,9 @@ async def _async_resolve_wrapper(hostname: str) -> list[str] | Exception:
with suppress(ValueError):
return [str(ip_address(hostname))]
try:
async with async_timeout(RESOLVE_TIMEOUT):
async with asyncio.timeout(RESOLVE_TIMEOUT):
return await async_resolve(hostname)
except (asyncio.TimeoutError, NameLookupError, UnicodeError) as ex:
except (TimeoutError, NameLookupError, UnicodeError) as ex:
return ex

View File

@ -20,7 +20,7 @@ classifiers = [
"Programming Language :: Python :: 3",
"Topic :: Home Automation",
]
requires-python = ">=3.10.0"
requires-python = ">=3.11.0"
dynamic = ["dependencies", "optional-dependencies", "version"]
@ -62,7 +62,7 @@ addopts = [
]
[tool.pylint.MAIN]
py-version = "3.10"
py-version = "3.11"
ignore = [
"api_pb2.py",
]
@ -106,7 +106,7 @@ expected-line-ending-format = "LF"
[tool.ruff]
required-version = ">=0.5.0"
target-version = "py310"
target-version = "py311"
exclude = ['generated']
[tool.ruff.lint]

View File

@ -1,4 +1,3 @@
async_timeout==5.0.1; python_version <= "3.10"
cryptography==45.0.1
voluptuous==0.15.2
PyYAML==6.0.2

View File

@ -264,26 +264,6 @@ class TypeInfo(ABC):
value = value_expr if value_expr else name
return f"ProtoSize::{method}(total_size, {field_id_size}, {value});"
def _get_fixed_size_calculation(
self, name: str, force: bool, num_bytes: int, zero_check: str
) -> str:
"""Helper for fixed-size field calculations.
Args:
name: Field name
force: Whether this is for a repeated field
num_bytes: Number of bytes (4 or 8)
zero_check: Expression to check for zero value (e.g., "!= 0.0f")
"""
field_id_size = self.calculate_field_id_size()
# Fixed-size repeated fields are handled differently in RepeatedTypeInfo
# so we should never get force=True here
assert not force, (
"Fixed-size repeated fields should be handled by RepeatedTypeInfo"
)
method = f"add_fixed_field<{num_bytes}>"
return f"ProtoSize::{method}(total_size, {field_id_size}, {name} {zero_check});"
@abstractmethod
def get_size_calculation(self, name: str, force: bool = False) -> str:
"""Calculate the size needed for encoding this field.
@ -369,7 +349,8 @@ class DoubleType(TypeInfo):
return o
def get_size_calculation(self, name: str, force: bool = False) -> str:
return self._get_fixed_size_calculation(name, force, 8, "!= 0.0")
field_id_size = self.calculate_field_id_size()
return f"ProtoSize::add_double_field(total_size, {field_id_size}, {name});"
def get_fixed_size_bytes(self) -> int:
return 8
@ -392,7 +373,8 @@ class FloatType(TypeInfo):
return o
def get_size_calculation(self, name: str, force: bool = False) -> str:
return self._get_fixed_size_calculation(name, force, 4, "!= 0.0f")
field_id_size = self.calculate_field_id_size()
return f"ProtoSize::add_float_field(total_size, {field_id_size}, {name});"
def get_fixed_size_bytes(self) -> int:
return 4
@ -475,7 +457,8 @@ class Fixed64Type(TypeInfo):
return o
def get_size_calculation(self, name: str, force: bool = False) -> str:
return self._get_fixed_size_calculation(name, force, 8, "!= 0")
field_id_size = self.calculate_field_id_size()
return f"ProtoSize::add_fixed64_field(total_size, {field_id_size}, {name});"
def get_fixed_size_bytes(self) -> int:
return 8
@ -498,7 +481,8 @@ class Fixed32Type(TypeInfo):
return o
def get_size_calculation(self, name: str, force: bool = False) -> str:
return self._get_fixed_size_calculation(name, force, 4, "!= 0")
field_id_size = self.calculate_field_id_size()
return f"ProtoSize::add_fixed32_field(total_size, {field_id_size}, {name});"
def get_fixed_size_bytes(self) -> int:
return 4
@ -687,7 +671,8 @@ class SFixed32Type(TypeInfo):
return o
def get_size_calculation(self, name: str, force: bool = False) -> str:
return self._get_fixed_size_calculation(name, force, 4, "!= 0")
field_id_size = self.calculate_field_id_size()
return f"ProtoSize::add_sfixed32_field(total_size, {field_id_size}, {name});"
def get_fixed_size_bytes(self) -> int:
return 4
@ -710,7 +695,8 @@ class SFixed64Type(TypeInfo):
return o
def get_size_calculation(self, name: str, force: bool = False) -> str:
return self._get_fixed_size_calculation(name, force, 8, "!= 0")
field_id_size = self.calculate_field_id_size()
return f"ProtoSize::add_sfixed64_field(total_size, {field_id_size}, {name});"
def get_fixed_size_bytes(self) -> int:
return 8

View File

@ -137,7 +137,7 @@ def main():
print()
print("Running pyupgrade...")
print()
PYUPGRADE_TARGET = "--py310-plus"
PYUPGRADE_TARGET = "--py311-plus"
for files in filesets:
cmd = ["pyupgrade", PYUPGRADE_TARGET] + files
log = get_err(*cmd)

View File

@ -395,7 +395,7 @@ async def wait_and_connect_api_client(
# Wait for connection with timeout
try:
await asyncio.wait_for(connected_future, timeout=timeout)
except asyncio.TimeoutError:
except TimeoutError:
raise TimeoutError(f"Failed to connect to API after {timeout} seconds")
yield client
@ -575,12 +575,12 @@ async def run_binary_and_wait_for_port(
process.send_signal(signal.SIGINT)
try:
await asyncio.wait_for(process.wait(), timeout=SIGINT_TIMEOUT)
except asyncio.TimeoutError:
except TimeoutError:
# If SIGINT didn't work, try SIGTERM
process.terminate()
try:
await asyncio.wait_for(process.wait(), timeout=SIGTERM_TIMEOUT)
except asyncio.TimeoutError:
except TimeoutError:
# Last resort: SIGKILL
process.kill()
await process.wait()

View File

@ -177,7 +177,7 @@ async def test_api_message_size_batching(
# Wait for states with timeout
try:
await asyncio.wait_for(states_future, timeout=5.0)
except asyncio.TimeoutError:
except TimeoutError:
missing_keys = expected_keys - received_keys
pytest.fail(
f"Did not receive states from all entities within 5 seconds. "

View File

@ -29,7 +29,7 @@ async def test_api_reboot_timeout(
# (0.5s reboot timeout + some margin for processing)
try:
await asyncio.wait_for(reboot_future, timeout=2.0)
except asyncio.TimeoutError:
except TimeoutError:
pytest.fail("Device did not reboot within expected timeout")
# Test passes if we get here - reboot was detected

View File

@ -98,7 +98,7 @@ async def test_areas_and_devices(
# Wait for sensor states
try:
await asyncio.wait_for(states_future, timeout=10.0)
except asyncio.TimeoutError:
except TimeoutError:
pytest.fail(
f"Did not receive all sensor states within 10 seconds. "
f"Received {len(states)} states"

View File

@ -77,7 +77,7 @@ async def test_device_id_in_state(
# Wait for states
try:
await asyncio.wait_for(states_future, timeout=10.0)
except asyncio.TimeoutError:
except TimeoutError:
pytest.fail(
f"Did not receive all entity states within 10 seconds. "
f"Received {len(states)} states, expected {len(entity_device_mapping)}"

View File

@ -206,7 +206,7 @@ async def test_duplicate_entities_not_allowed_on_different_devices(
# Wait for all entity states
try:
await asyncio.wait_for(states_future, timeout=10.0)
except asyncio.TimeoutError:
except TimeoutError:
pytest.fail(
f"Did not receive all entity states within 10 seconds. "
f"Expected {expected_count}, received {state_count}"

View File

@ -82,7 +82,7 @@ async def test_entity_icon(
# Wait for states
try:
await asyncio.wait_for(state_received.wait(), timeout=5.0)
except asyncio.TimeoutError:
except TimeoutError:
pytest.fail("No states received within 5 seconds")
# Verify we received states

View File

@ -44,7 +44,7 @@ async def test_host_mode_batch_delay(
# Wait for states from all entities with timeout
try:
entity_count = await asyncio.wait_for(entity_count_future, timeout=5.0)
except asyncio.TimeoutError:
except TimeoutError:
pytest.fail(
f"Did not receive states from at least 7 entities within 5 seconds. "
f"Received {len(states)} states"

View File

@ -99,7 +99,7 @@ async def test_host_mode_empty_string_options(
# Wait for initial states with timeout
try:
await asyncio.wait_for(states_received_future, timeout=5.0)
except asyncio.TimeoutError:
except TimeoutError:
pytest.fail(
f"Did not receive states for all select entities. "
f"Expected keys: {expected_select_keys}, Received: {received_select_keys}"

View File

@ -86,7 +86,7 @@ async def test_host_mode_entity_fields(
# Wait for at least one state
try:
await asyncio.wait_for(state_received.wait(), timeout=5.0)
except asyncio.TimeoutError:
except TimeoutError:
pytest.fail("No states received within 5 seconds")
# Verify we received states (which means has_state flag is working)

View File

@ -41,7 +41,7 @@ async def test_host_mode_many_entities(
# Wait for states from at least 50 sensors with timeout
try:
sensor_count = await asyncio.wait_for(sensor_count_future, timeout=10.0)
except asyncio.TimeoutError:
except TimeoutError:
sensor_states = [
s
for s in states.values()

View File

@ -50,7 +50,7 @@ async def test_host_mode_many_entities_multiple_connections(
asyncio.wait_for(client1_ready, timeout=10.0),
asyncio.wait_for(client2_ready, timeout=10.0),
)
except asyncio.TimeoutError:
except TimeoutError:
pytest.fail(
f"One or both clients did not receive enough states within 10 seconds. "
f"Client1: {len(states1)}, Client2: {len(states2)}"

View File

@ -40,7 +40,7 @@ async def test_host_mode_with_sensor(
# Wait for sensor with specific value (42.0) with timeout
try:
test_sensor_state = await asyncio.wait_for(sensor_future, timeout=5.0)
except asyncio.TimeoutError:
except TimeoutError:
pytest.fail(
f"Sensor with value 42.0 not received within 5 seconds. "
f"Received states: {list(states.values())}"

View File

@ -150,7 +150,7 @@ async def test_loop_disable_enable(
# Wait for self_disable_10 to disable itself
try:
await asyncio.wait_for(self_disable_10_disabled.wait(), timeout=10.0)
except asyncio.TimeoutError:
except TimeoutError:
pytest.fail("self_disable_10 did not disable itself within 10 seconds")
# Verify it ran at least 10 times before disabling
@ -164,7 +164,7 @@ async def test_loop_disable_enable(
# Wait for normal_component to run at least 10 times
try:
await asyncio.wait_for(normal_component_10_loops.wait(), timeout=10.0)
except asyncio.TimeoutError:
except TimeoutError:
pytest.fail(
f"normal_component did not reach 10 loops within timeout, got {len(normal_component_counts)}"
)
@ -172,12 +172,12 @@ async def test_loop_disable_enable(
# Wait for redundant operation tests
try:
await asyncio.wait_for(redundant_enable_tested.wait(), timeout=10.0)
except asyncio.TimeoutError:
except TimeoutError:
pytest.fail("redundant_enable did not test enabling when already enabled")
try:
await asyncio.wait_for(redundant_disable_tested.wait(), timeout=10.0)
except asyncio.TimeoutError:
except TimeoutError:
pytest.fail(
"redundant_disable did not test disabling when will be disabled"
)
@ -185,7 +185,7 @@ async def test_loop_disable_enable(
# Wait to see if self_disable_10 gets re-enabled
try:
await asyncio.wait_for(self_disable_10_re_enabled.wait(), timeout=5.0)
except asyncio.TimeoutError:
except TimeoutError:
pytest.fail("self_disable_10 was not re-enabled within 5 seconds")
# Component was re-enabled - verify it ran more times
@ -198,7 +198,7 @@ async def test_loop_disable_enable(
# Wait for ISR component to disable itself after 5 loops
try:
await asyncio.wait_for(isr_component_disabled.wait(), timeout=3.0)
except asyncio.TimeoutError:
except TimeoutError:
pytest.fail("ISR component did not disable itself within 3 seconds")
# Verify it ran exactly 5 times before disabling
@ -210,7 +210,7 @@ async def test_loop_disable_enable(
# Wait for component to be re-enabled by periodic ISR simulation and run again
try:
await asyncio.wait_for(isr_component_re_enabled.wait(), timeout=2.0)
except asyncio.TimeoutError:
except TimeoutError:
pytest.fail("ISR component was not re-enabled after ISR call")
# Verify it's running again after ISR enable
@ -222,7 +222,7 @@ async def test_loop_disable_enable(
# Wait for pure ISR enable (no main loop enable) to work
try:
await asyncio.wait_for(isr_component_pure_re_enabled.wait(), timeout=2.0)
except asyncio.TimeoutError:
except TimeoutError:
pytest.fail("ISR component was not re-enabled by pure ISR call")
# Verify it ran after pure ISR enable
@ -235,7 +235,7 @@ async def test_loop_disable_enable(
# Wait for update component to disable its loop
try:
await asyncio.wait_for(update_component_loop_disabled.wait(), timeout=3.0)
except asyncio.TimeoutError:
except TimeoutError:
pytest.fail("Update component did not disable its loop within 3 seconds")
# Verify it ran exactly 3 loops before disabling
@ -248,7 +248,7 @@ async def test_loop_disable_enable(
await asyncio.wait_for(
update_component_manual_update_called.wait(), timeout=5.0
)
except asyncio.TimeoutError:
except TimeoutError:
pytest.fail("Manual component.update was not called within 5 seconds")
# The key test: verify that manual component.update worked after loop was disabled

View File

@ -103,7 +103,7 @@ async def test_scheduler_bulk_cleanup(
# Wait for test completion
try:
await asyncio.wait_for(test_complete_future, timeout=10.0)
except asyncio.TimeoutError:
except TimeoutError:
pytest.fail("Bulk cleanup test timed out")
# Verify bulk cleanup was triggered

View File

@ -85,7 +85,7 @@ async def test_scheduler_defer_cancel(
try:
await asyncio.wait_for(test_complete_future, timeout=10.0)
executed_defer = await asyncio.wait_for(test_result_future, timeout=1.0)
except asyncio.TimeoutError:
except TimeoutError:
pytest.fail("Test did not complete within timeout")
# Verify that only defer 10 was executed

View File

@ -64,7 +64,7 @@ async def test_scheduler_defer_cancels_regular(
# Wait for test completion
try:
await asyncio.wait_for(test_complete_future, timeout=5.0)
except asyncio.TimeoutError:
except TimeoutError:
pytest.fail(f"Test timed out. Log messages: {log_messages}")
# Verify results

View File

@ -90,7 +90,7 @@ async def test_scheduler_defer_fifo_simple(
try:
await asyncio.wait_for(test_complete_future, timeout=5.0)
test1_passed = await asyncio.wait_for(test_result_future, timeout=1.0)
except asyncio.TimeoutError:
except TimeoutError:
pytest.fail("Test set_timeout(0) did not complete within 5 seconds")
assert test1_passed is True, (
@ -108,7 +108,7 @@ async def test_scheduler_defer_fifo_simple(
try:
await asyncio.wait_for(test_complete_future, timeout=5.0)
test2_passed = await asyncio.wait_for(test_result_future, timeout=1.0)
except asyncio.TimeoutError:
except TimeoutError:
pytest.fail("Test defer() did not complete within 5 seconds")
# Verify the test passed

View File

@ -97,7 +97,7 @@ async def test_scheduler_defer_stress(
# Wait for all defers to execute (should be quick)
try:
await asyncio.wait_for(test_complete_future, timeout=5.0)
except asyncio.TimeoutError:
except TimeoutError:
# Report how many we got
pytest.fail(
f"Stress test timed out. Only {len(executed_defers)} of "

View File

@ -104,7 +104,7 @@ async def test_scheduler_heap_stress(
# Wait for all callbacks to execute (should be quick, but give more time for scheduling)
try:
await asyncio.wait_for(test_complete_future, timeout=60.0)
except asyncio.TimeoutError:
except TimeoutError:
# Report how many we got
pytest.fail(
f"Stress test timed out. Only {len(executed_callbacks)} of "

View File

@ -53,7 +53,7 @@ async def test_scheduler_null_name(
# Wait for test completion
try:
await asyncio.wait_for(test_complete_future, timeout=10.0)
except asyncio.TimeoutError:
except TimeoutError:
pytest.fail(
"Test did not complete within timeout - likely crashed due to NULL name"
)

View File

@ -112,7 +112,7 @@ async def test_scheduler_rapid_cancellation(
# Wait for test to complete with timeout
try:
await asyncio.wait_for(test_complete_future, timeout=10.0)
except asyncio.TimeoutError:
except TimeoutError:
pytest.fail(f"Test timed out. Stats: {test_stats}")
# Check for any errors

View File

@ -84,7 +84,7 @@ async def test_scheduler_recursive_timeout(
# Wait for test to complete
try:
await asyncio.wait_for(test_complete_future, timeout=10.0)
except asyncio.TimeoutError:
except TimeoutError:
pytest.fail(
f"Recursive timeout test timed out. Got sequence: {execution_sequence}"
)

View File

@ -103,7 +103,7 @@ async def test_scheduler_simultaneous_callbacks(
# Wait for test to complete
try:
await asyncio.wait_for(test_complete_future, timeout=30.0)
except asyncio.TimeoutError:
except TimeoutError:
pytest.fail(f"Simultaneous callbacks test timed out. Stats: {test_stats}")
# Check for any errors

View File

@ -157,7 +157,7 @@ async def test_scheduler_string_lifetime(
client.execute_service(test_services["final"], {})
await asyncio.wait_for(all_tests_complete.wait(), timeout=5.0)
except asyncio.TimeoutError:
except TimeoutError:
pytest.fail(f"String lifetime test timed out. Stats: {test_stats}")
# Check for any errors

View File

@ -97,7 +97,7 @@ async def test_scheduler_string_name_stress(
# Wait for test to complete or crash
try:
await asyncio.wait_for(test_complete_future, timeout=30.0)
except asyncio.TimeoutError:
except TimeoutError:
pytest.fail(
f"String name stress test timed out. Executed {len(executed_callbacks)} callbacks. "
f"This might indicate a deadlock."

View File

@ -122,22 +122,22 @@ async def test_scheduler_string_test(
# Wait for static string tests
try:
await asyncio.wait_for(static_timeout_1_fired.wait(), timeout=0.5)
except asyncio.TimeoutError:
except TimeoutError:
pytest.fail("Static timeout 1 did not fire within 0.5 seconds")
try:
await asyncio.wait_for(static_timeout_2_fired.wait(), timeout=0.5)
except asyncio.TimeoutError:
except TimeoutError:
pytest.fail("Static timeout 2 did not fire within 0.5 seconds")
try:
await asyncio.wait_for(static_interval_fired.wait(), timeout=1.0)
except asyncio.TimeoutError:
except TimeoutError:
pytest.fail("Static interval did not fire within 1 second")
try:
await asyncio.wait_for(static_interval_cancelled.wait(), timeout=2.0)
except asyncio.TimeoutError:
except TimeoutError:
pytest.fail("Static interval was not cancelled within 2 seconds")
# Verify static interval ran at least 3 times
@ -153,41 +153,41 @@ async def test_scheduler_string_test(
# Wait for static defer tests
try:
await asyncio.wait_for(static_defer_1_fired.wait(), timeout=0.5)
except asyncio.TimeoutError:
except TimeoutError:
pytest.fail("Static defer 1 did not fire within 0.5 seconds")
try:
await asyncio.wait_for(static_defer_2_fired.wait(), timeout=0.5)
except asyncio.TimeoutError:
except TimeoutError:
pytest.fail("Static defer 2 did not fire within 0.5 seconds")
# Wait for dynamic string tests
try:
await asyncio.wait_for(dynamic_timeout_fired.wait(), timeout=1.0)
except asyncio.TimeoutError:
except TimeoutError:
pytest.fail("Dynamic timeout did not fire within 1 second")
try:
await asyncio.wait_for(dynamic_interval_fired.wait(), timeout=1.5)
except asyncio.TimeoutError:
except TimeoutError:
pytest.fail("Dynamic interval did not fire within 1.5 seconds")
# Wait for dynamic defer test
try:
await asyncio.wait_for(dynamic_defer_fired.wait(), timeout=1.0)
except asyncio.TimeoutError:
except TimeoutError:
pytest.fail("Dynamic defer did not fire within 1 second")
# Wait for cancel test
try:
await asyncio.wait_for(cancel_test_done.wait(), timeout=1.0)
except asyncio.TimeoutError:
except TimeoutError:
pytest.fail("Cancel test did not complete within 1 second")
# Wait for final results
try:
await asyncio.wait_for(final_results_logged.wait(), timeout=4.0)
except asyncio.TimeoutError:
except TimeoutError:
pytest.fail("Final results were not logged within 4 seconds")
# Verify results