mirror of
https://github.com/esphome/esphome.git
synced 2026-02-25 12:55:30 -07:00
[uptime] Use scheduler millis_64() for rollover-safe uptime tracking (#14170)
This commit is contained in:
@@ -1,30 +1,16 @@
|
||||
#include "uptime_seconds_sensor.h"
|
||||
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace uptime {
|
||||
namespace esphome::uptime {
|
||||
|
||||
static const char *const TAG = "uptime.sensor";
|
||||
|
||||
void UptimeSecondsSensor::update() {
|
||||
const uint32_t ms = millis();
|
||||
const uint64_t ms_mask = (1ULL << 32) - 1ULL;
|
||||
const uint32_t last_ms = this->uptime_ & ms_mask;
|
||||
if (ms < last_ms) {
|
||||
this->uptime_ += ms_mask + 1ULL;
|
||||
ESP_LOGD(TAG, "Detected roll-over \xf0\x9f\xa6\x84");
|
||||
}
|
||||
this->uptime_ &= ~ms_mask;
|
||||
this->uptime_ |= ms;
|
||||
|
||||
// Do separate second and milliseconds conversion to avoid floating point division errors
|
||||
// Probably some IEEE standard already guarantees this division can be done without loss
|
||||
// of precision in a single division, but let's do it like this to be sure.
|
||||
const uint64_t seconds_int = this->uptime_ / 1000ULL;
|
||||
const float seconds = float(seconds_int) + (this->uptime_ % 1000ULL) / 1000.0f;
|
||||
const uint64_t uptime = App.scheduler.millis_64();
|
||||
const uint64_t seconds_int = uptime / 1000ULL;
|
||||
const float seconds = float(seconds_int) + (uptime % 1000ULL) / 1000.0f;
|
||||
this->publish_state(seconds);
|
||||
}
|
||||
float UptimeSecondsSensor::get_setup_priority() const { return setup_priority::HARDWARE; }
|
||||
@@ -33,5 +19,4 @@ void UptimeSecondsSensor::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, " Type: Seconds");
|
||||
}
|
||||
|
||||
} // namespace uptime
|
||||
} // namespace esphome
|
||||
} // namespace esphome::uptime
|
||||
|
||||
@@ -3,8 +3,7 @@
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/core/component.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace uptime {
|
||||
namespace esphome::uptime {
|
||||
|
||||
class UptimeSecondsSensor : public sensor::Sensor, public PollingComponent {
|
||||
public:
|
||||
@@ -12,10 +11,6 @@ class UptimeSecondsSensor : public sensor::Sensor, public PollingComponent {
|
||||
void dump_config() override;
|
||||
|
||||
float get_setup_priority() const override;
|
||||
|
||||
protected:
|
||||
uint64_t uptime_{0};
|
||||
};
|
||||
|
||||
} // namespace uptime
|
||||
} // namespace esphome
|
||||
} // namespace esphome::uptime
|
||||
|
||||
@@ -6,8 +6,7 @@
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace uptime {
|
||||
namespace esphome::uptime {
|
||||
|
||||
static const char *const TAG = "uptime.sensor";
|
||||
|
||||
@@ -33,7 +32,6 @@ void UptimeTimestampSensor::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, " Type: Timestamp");
|
||||
}
|
||||
|
||||
} // namespace uptime
|
||||
} // namespace esphome
|
||||
} // namespace esphome::uptime
|
||||
|
||||
#endif // USE_TIME
|
||||
|
||||
@@ -8,8 +8,7 @@
|
||||
#include "esphome/components/time/real_time_clock.h"
|
||||
#include "esphome/core/component.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace uptime {
|
||||
namespace esphome::uptime {
|
||||
|
||||
class UptimeTimestampSensor : public sensor::Sensor, public Component {
|
||||
public:
|
||||
@@ -24,7 +23,6 @@ class UptimeTimestampSensor : public sensor::Sensor, public Component {
|
||||
time::RealTimeClock *time_;
|
||||
};
|
||||
|
||||
} // namespace uptime
|
||||
} // namespace esphome
|
||||
} // namespace esphome::uptime
|
||||
|
||||
#endif // USE_TIME
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
#include "uptime_text_sensor.h"
|
||||
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace uptime {
|
||||
namespace esphome::uptime {
|
||||
|
||||
static const char *const TAG = "uptime.sensor";
|
||||
|
||||
@@ -17,22 +16,10 @@ static void append_unit(char *buf, size_t buf_size, size_t &pos, const char *sep
|
||||
pos = buf_append_printf(buf, buf_size, pos, "%u%s", value, label);
|
||||
}
|
||||
|
||||
void UptimeTextSensor::setup() {
|
||||
this->last_ms_ = millis();
|
||||
if (this->last_ms_ < 60 * 1000)
|
||||
this->last_ms_ = 0;
|
||||
this->update();
|
||||
}
|
||||
void UptimeTextSensor::setup() { this->update(); }
|
||||
|
||||
void UptimeTextSensor::update() {
|
||||
auto now = millis();
|
||||
// get whole seconds since last update. Note that even if the millis count has overflowed between updates,
|
||||
// the difference will still be correct due to the way twos-complement arithmetic works.
|
||||
uint32_t delta = now - this->last_ms_;
|
||||
this->last_ms_ = now - delta % 1000; // save remainder for next update
|
||||
delta /= 1000;
|
||||
this->uptime_ += delta;
|
||||
uint32_t uptime = this->uptime_;
|
||||
uint32_t uptime = static_cast<uint32_t>(App.scheduler.millis_64() / 1000);
|
||||
unsigned interval = this->get_update_interval() / 1000;
|
||||
|
||||
// Calculate all time units
|
||||
@@ -89,5 +76,4 @@ void UptimeTextSensor::update() {
|
||||
float UptimeTextSensor::get_setup_priority() const { return setup_priority::HARDWARE; }
|
||||
void UptimeTextSensor::dump_config() { LOG_TEXT_SENSOR("", "Uptime Text Sensor", this); }
|
||||
|
||||
} // namespace uptime
|
||||
} // namespace esphome
|
||||
} // namespace esphome::uptime
|
||||
|
||||
@@ -5,8 +5,7 @@
|
||||
#include "esphome/components/text_sensor/text_sensor.h"
|
||||
#include "esphome/core/component.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace uptime {
|
||||
namespace esphome::uptime {
|
||||
|
||||
class UptimeTextSensor : public text_sensor::TextSensor, public PollingComponent {
|
||||
public:
|
||||
@@ -35,9 +34,6 @@ class UptimeTextSensor : public text_sensor::TextSensor, public PollingComponent
|
||||
const char *seconds_text_;
|
||||
const char *separator_;
|
||||
bool expand_{};
|
||||
uint32_t uptime_{0}; // uptime in seconds, will overflow after 136 years
|
||||
uint32_t last_ms_{0};
|
||||
};
|
||||
|
||||
} // namespace uptime
|
||||
} // namespace esphome
|
||||
} // namespace esphome::uptime
|
||||
|
||||
@@ -675,6 +675,8 @@ bool HOT Scheduler::cancel_item_locked_(Component *component, NameType name_type
|
||||
return total_cancelled > 0;
|
||||
}
|
||||
|
||||
uint64_t Scheduler::millis_64() { return this->millis_64_(millis()); }
|
||||
|
||||
uint64_t Scheduler::millis_64_(uint32_t now) {
|
||||
// THREAD SAFETY NOTE:
|
||||
// This function has three implementations, based on the precompiler flags
|
||||
|
||||
@@ -116,6 +116,9 @@ class Scheduler {
|
||||
ESPDEPRECATED("cancel_retry is deprecated and will be removed in 2026.8.0.", "2026.2.0")
|
||||
bool cancel_retry(Component *component, uint32_t id);
|
||||
|
||||
/// Get 64-bit millisecond timestamp (handles 32-bit millis() rollover)
|
||||
uint64_t millis_64();
|
||||
|
||||
// Calculate when the next scheduled item should run
|
||||
// @param now Fresh timestamp from millis() - must not be stale/cached
|
||||
// Returns the time in milliseconds until the next scheduled item, or nullopt if no items
|
||||
|
||||
Reference in New Issue
Block a user