1
0
mirror of https://github.com/esphome/esphome.git synced 2026-02-08 00:31:58 +00:00

[uptime] Format text sensor output on stack to avoid heap allocations (#13150)

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
J. Nick Koston
2026-01-11 17:17:32 -10:00
committed by GitHub
parent 909bd1074a
commit 723ca57617
2 changed files with 62 additions and 33 deletions

View File

@@ -9,6 +9,19 @@ namespace uptime {
static const char *const TAG = "uptime.sensor"; static const char *const TAG = "uptime.sensor";
// Clamp position to valid buffer range when snprintf indicates truncation
static size_t clamp_buffer_pos(size_t pos, size_t buf_size) { return pos < buf_size ? pos : buf_size - 1; }
static void append_unit(char *buf, size_t buf_size, size_t &pos, const char *separator, unsigned value,
const char *label) {
if (pos > 0) {
pos += snprintf(buf + pos, buf_size - pos, "%s", separator);
pos = clamp_buffer_pos(pos, buf_size);
}
pos += snprintf(buf + pos, buf_size - pos, "%u%s", value, label);
pos = clamp_buffer_pos(pos, buf_size);
}
void UptimeTextSensor::setup() { void UptimeTextSensor::setup() {
this->last_ms_ = millis(); this->last_ms_ = millis();
if (this->last_ms_ < 60 * 1000) if (this->last_ms_ < 60 * 1000)
@@ -16,11 +29,6 @@ void UptimeTextSensor::setup() {
this->update(); this->update();
} }
void UptimeTextSensor::insert_buffer_(std::string &buffer, const char *key, unsigned value) const {
buffer.insert(0, this->separator_);
buffer.insert(0, str_sprintf("%u%s", value, key));
}
void UptimeTextSensor::update() { void UptimeTextSensor::update() {
auto now = millis(); auto now = millis();
// get whole seconds since last update. Note that even if the millis count has overflowed between updates, // get whole seconds since last update. Note that even if the millis count has overflowed between updates,
@@ -29,36 +37,58 @@ void UptimeTextSensor::update() {
this->last_ms_ = now - delta % 1000; // save remainder for next update this->last_ms_ = now - delta % 1000; // save remainder for next update
delta /= 1000; delta /= 1000;
this->uptime_ += delta; this->uptime_ += delta;
auto uptime = this->uptime_; uint32_t uptime = this->uptime_;
unsigned interval = this->get_update_interval() / 1000; unsigned interval = this->get_update_interval() / 1000;
std::string buffer{};
// display from the largest unit that corresponds to the update interval, drop larger units that are zero. // Calculate all time units
while (true) { // enable use of break for early exit unsigned seconds = uptime % 60;
unsigned remainder = uptime % 60; uptime /= 60;
uptime /= 60; unsigned minutes = uptime % 60;
if (interval < 30) { uptime /= 60;
this->insert_buffer_(buffer, this->seconds_text_, remainder); unsigned hours = uptime % 24;
if (!this->expand_ && uptime == 0) uptime /= 24;
break; unsigned days = uptime;
// Determine which units to display based on interval thresholds
bool seconds_enabled = interval < 30;
bool minutes_enabled = interval < 1800;
bool hours_enabled = interval < 12 * 3600;
// Show from highest non-zero unit (or all in expand mode) down to smallest enabled
bool show_days = this->expand_ || days > 0;
bool show_hours = hours_enabled && (show_days || hours > 0);
bool show_minutes = minutes_enabled && (show_hours || minutes > 0);
bool show_seconds = seconds_enabled && (show_minutes || seconds > 0);
// If nothing shown, show smallest enabled unit
if (!show_days && !show_hours && !show_minutes && !show_seconds) {
if (seconds_enabled) {
show_seconds = true;
} else if (minutes_enabled) {
show_minutes = true;
} else if (hours_enabled) {
show_hours = true;
} else {
show_days = true;
} }
remainder = uptime % 60;
uptime /= 60;
if (interval < 1800) {
this->insert_buffer_(buffer, this->minutes_text_, remainder);
if (!this->expand_ && uptime == 0)
break;
}
remainder = uptime % 24;
uptime /= 24;
if (interval < 12 * 3600) {
this->insert_buffer_(buffer, this->hours_text_, remainder);
if (!this->expand_ && uptime == 0)
break;
}
this->insert_buffer_(buffer, this->days_text_, (unsigned) uptime);
break;
} }
this->publish_state(buffer);
// Build output string on stack
// Home Assistant max state length is 255 chars + null terminator
char buf[256];
size_t pos = 0;
buf[0] = '\0'; // Initialize for empty case
if (show_days)
append_unit(buf, sizeof(buf), pos, this->separator_, days, this->days_text_);
if (show_hours)
append_unit(buf, sizeof(buf), pos, this->separator_, hours, this->hours_text_);
if (show_minutes)
append_unit(buf, sizeof(buf), pos, this->separator_, minutes, this->minutes_text_);
if (show_seconds)
append_unit(buf, sizeof(buf), pos, this->separator_, seconds, this->seconds_text_);
this->publish_state(buf);
} }
float UptimeTextSensor::get_setup_priority() const { return setup_priority::HARDWARE; } float UptimeTextSensor::get_setup_priority() const { return setup_priority::HARDWARE; }

View File

@@ -29,7 +29,6 @@ class UptimeTextSensor : public text_sensor::TextSensor, public PollingComponent
void set_seconds(const char *seconds_text) { this->seconds_text_ = seconds_text; } void set_seconds(const char *seconds_text) { this->seconds_text_ = seconds_text; }
protected: protected:
void insert_buffer_(std::string &buffer, const char *key, unsigned value) const;
const char *days_text_; const char *days_text_;
const char *hours_text_; const char *hours_text_;
const char *minutes_text_; const char *minutes_text_;