[usb_uart][nextion][feedback][whirlpool][packet_transport][he60r][hc8][runtime_stats] Fix millis() wrapping bugs (#14474)

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
This commit is contained in:
Jonathan Swoboda
2026-03-05 16:08:58 -05:00
committed by GitHub
parent b0be02e16d
commit 3392e4d73b
11 changed files with 44 additions and 37 deletions
@@ -437,10 +437,15 @@ void FeedbackCover::recompute_position_() {
}
// check if we have an acceleration_wait_time, and remove from position computation
if (now > (this->start_dir_time_ + this->acceleration_wait_time_)) {
this->position +=
dir * (now - std::max(this->start_dir_time_ + this->acceleration_wait_time_, this->last_recompute_time_)) /
(action_dur - this->acceleration_wait_time_);
if (now - this->start_dir_time_ > this->acceleration_wait_time_) {
uint32_t accel_end_time = this->start_dir_time_ + this->acceleration_wait_time_;
uint32_t effective_start;
if (static_cast<int32_t>(accel_end_time - this->last_recompute_time_) >= 0) {
effective_start = accel_end_time;
} else {
effective_start = this->last_recompute_time_;
}
this->position += dir * (now - effective_start) / (action_dur - this->acceleration_wait_time_);
this->position = clamp(this->position, min_pos, max_pos);
}
this->last_recompute_time_ = now;
+10 -6
View File
@@ -24,12 +24,16 @@ void HC8Component::setup() {
}
void HC8Component::update() {
uint32_t now_ms = App.get_loop_component_start_time();
uint32_t warmup_ms = this->warmup_seconds_ * 1000;
if (now_ms < warmup_ms) {
ESP_LOGW(TAG, "HC8 warming up, %" PRIu32 " s left", (warmup_ms - now_ms) / 1000);
this->status_set_warning();
return;
if (!this->warmup_complete_) {
uint32_t now_ms = App.get_loop_component_start_time();
uint32_t warmup_ms = this->warmup_seconds_ * 1000;
if (now_ms < warmup_ms) {
ESP_LOGW(TAG, "HC8 warming up, %" PRIu32 " s left", (warmup_ms - now_ms) / 1000);
this->status_set_warning();
return;
}
this->warmup_complete_ = true;
this->status_clear_warning();
}
while (this->available())
+1
View File
@@ -23,6 +23,7 @@ class HC8Component : public PollingComponent, public uart::UARTDevice {
protected:
sensor::Sensor *co2_sensor_{nullptr};
uint32_t warmup_seconds_{0};
bool warmup_complete_{false};
};
template<typename... Ts> class HC8CalibrateAction : public Action<Ts...>, public Parented<HC8Component> {
+1 -1
View File
@@ -239,7 +239,7 @@ void HE60rCover::recompute_position_() {
return;
const uint32_t now = millis();
if (now > this->last_recompute_time_) {
if (now != this->last_recompute_time_) {
auto diff = (unsigned) (now - last_recompute_time_);
float delta;
switch (this->current_operation) {
+3 -3
View File
@@ -337,7 +337,7 @@ void Nextion::loop() {
this->started_ms_ = App.get_loop_component_start_time();
if (this->startup_override_ms_ > 0 &&
this->started_ms_ + this->startup_override_ms_ < App.get_loop_component_start_time()) {
App.get_loop_component_start_time() - this->started_ms_ > this->startup_override_ms_) {
ESP_LOGV(TAG, "Manual ready set");
this->connection_state_.nextion_reports_is_setup_ = true;
}
@@ -853,10 +853,10 @@ void Nextion::process_nextion_commands_() {
const uint32_t ms = App.get_loop_component_start_time();
if (this->max_q_age_ms_ > 0 && !this->nextion_queue_.empty() &&
this->nextion_queue_.front()->queue_time + this->max_q_age_ms_ < ms) {
ms - this->nextion_queue_.front()->queue_time > this->max_q_age_ms_) {
for (size_t i = 0; i < this->nextion_queue_.size(); i++) {
NextionComponentBase *component = this->nextion_queue_[i]->component;
if (this->nextion_queue_[i]->queue_time + this->max_q_age_ms_ < ms) {
if (ms - this->nextion_queue_[i]->queue_time > this->max_q_age_ms_) {
if (this->nextion_queue_[i]->queue_time == 0) {
ESP_LOGD(TAG, "Remove old queue '%s':'%s' (t=0)", component->get_queue_type_string().c_str(),
component->get_variable_name().c_str());
@@ -330,15 +330,16 @@ void PacketTransport::update() {
if (!this->ping_pong_enable_) {
return;
}
auto now = millis() / 1000;
if (this->last_key_time_ + this->ping_pong_recyle_time_ < now) {
uint32_t now = millis();
uint32_t ping_request_age = now - this->last_key_time_;
if (ping_request_age > this->ping_pong_recyle_time_ * 1000u) {
this->resend_ping_key_ = this->ping_pong_enable_;
ESP_LOGV(TAG, "Ping request, age %" PRIu32, now - this->last_key_time_);
ESP_LOGV(TAG, "Ping request, age %" PRIu32, ping_request_age);
this->last_key_time_ = now;
}
for (const auto &provider : this->providers_) {
uint32_t key_response_age = now - provider.second.last_key_response_time;
if (key_response_age > (this->ping_pong_recyle_time_ * 2u)) {
if (key_response_age > (this->ping_pong_recyle_time_ * 2000u)) {
#ifdef USE_STATUS_SENSOR
if (provider.second.status_sensor != nullptr && provider.second.status_sensor->state) {
ESP_LOGI(TAG, "Ping status for %s timeout at %" PRIu32 " with age %" PRIu32, provider.first.c_str(), now,
@@ -496,7 +497,7 @@ void PacketTransport::process_(std::span<const uint8_t> data) {
if (decoder.decode(PING_KEY, key) == DECODE_OK) {
if (key == this->ping_key_) {
ping_key_seen = true;
provider.last_key_response_time = millis() / 1000;
provider.last_key_response_time = millis();
ESP_LOGV(TAG, "Found good ping key %X at timestamp %" PRIu32, (unsigned) key, provider.last_key_response_time);
} else {
ESP_LOGV(TAG, "Unknown ping key %X", (unsigned) key);
@@ -9,21 +9,16 @@ namespace esphome {
namespace runtime_stats {
RuntimeStatsCollector::RuntimeStatsCollector() : log_interval_(60000), next_log_time_(0) {
RuntimeStatsCollector::RuntimeStatsCollector() : log_interval_(60000), next_log_time_(60000) {
global_runtime_stats = this;
}
void RuntimeStatsCollector::record_component_time(Component *component, uint32_t duration_us, uint32_t current_time) {
void RuntimeStatsCollector::record_component_time(Component *component, uint32_t duration_us) {
if (component == nullptr)
return;
// Record stats using component pointer as key
this->component_stats_[component].record_time(duration_us);
if (this->next_log_time_ == 0) {
this->next_log_time_ = current_time + this->log_interval_;
return;
}
}
void RuntimeStatsCollector::log_stats_() {
@@ -88,10 +83,7 @@ void RuntimeStatsCollector::log_stats_() {
}
void RuntimeStatsCollector::process_pending_stats(uint32_t current_time) {
if (this->next_log_time_ == 0)
return;
if (current_time >= this->next_log_time_) {
if ((int32_t) (current_time - this->next_log_time_) >= 0) {
this->log_stats_();
this->reset_stats_();
this->next_log_time_ = current_time + this->log_interval_;
@@ -7,6 +7,7 @@
#include <map>
#include <cstdint>
#include <cstring>
#include "esphome/core/hal.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
@@ -80,10 +81,13 @@ class RuntimeStatsCollector {
public:
RuntimeStatsCollector();
void set_log_interval(uint32_t log_interval) { this->log_interval_ = log_interval; }
void set_log_interval(uint32_t log_interval) {
this->log_interval_ = log_interval;
this->next_log_time_ = millis() + log_interval;
}
uint32_t get_log_interval() const { return this->log_interval_; }
void record_component_time(Component *component, uint32_t duration_us, uint32_t current_time);
void record_component_time(Component *component, uint32_t duration_us);
// Process any pending stats printing (should be called after component loop)
void process_pending_stats(uint32_t current_time);
@@ -101,7 +105,7 @@ class RuntimeStatsCollector {
// We use Component* as the key since each component is unique
std::map<Component *, ComponentRuntimeStats> component_stats_;
uint32_t log_interval_;
uint32_t next_log_time_;
uint32_t next_log_time_{0};
};
} // namespace runtime_stats
+2 -2
View File
@@ -171,8 +171,8 @@ void USBUartChannel::flush() {
// Safe to call from the main loop only.
// The 100 ms timeout guards against a device that stops responding mid-flush;
// in that case the main loop is blocked for the full duration.
uint32_t deadline = millis() + 100; // 100 ms safety timeout
while ((!this->output_queue_.empty() || this->output_started_.load()) && millis() < deadline) {
uint32_t start = millis(); // 100 ms safety timeout
while ((!this->output_queue_.empty() || this->output_started_.load()) && millis() - start < 100) {
// Kick start_output() in case data arrived but no transfer is in flight yet.
this->parent_->start_output(this);
yield();
+1 -1
View File
@@ -48,7 +48,7 @@ class WhirlpoolClimate : public climate_ir::ClimateIR {
/// Handle received IR Buffer
bool on_receive(remote_base::RemoteReceiveData data) override;
/// Set the time of the last transmission.
int32_t last_transmit_time_{};
uint32_t last_transmit_time_{};
bool send_swing_cmd_{false};
Model model_;
+1 -1
View File
@@ -534,7 +534,7 @@ uint32_t WarnIfComponentBlockingGuard::finish() {
// 1ms granularity, so results were essentially random noise.
if (global_runtime_stats != nullptr) {
uint32_t duration_us = micros() - this->started_us_;
global_runtime_stats->record_component_time(this->component_, duration_us, curr_time);
global_runtime_stats->record_component_time(this->component_, duration_us);
}
#endif
if (blocking_time > WARN_IF_BLOCKING_OVER_MS) {