mirror of
https://github.com/esphome/esphome.git
synced 2026-09-14 08:38:39 +00:00
[uptime] Format text sensor output on stack to avoid heap allocations
This commit is contained in:
@@ -9,6 +9,19 @@ namespace uptime {
|
||||
|
||||
static const char *const TAG = "uptime.sensor";
|
||||
|
||||
// Cap position to prevent buffer overflow from snprintf return value
|
||||
inline 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() {
|
||||
this->last_ms_ = millis();
|
||||
if (this->last_ms_ < 60 * 1000)
|
||||
@@ -16,11 +29,6 @@ void UptimeTextSensor::setup() {
|
||||
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() {
|
||||
auto now = millis();
|
||||
// get whole seconds since last update. Note that even if the millis count has overflowed between updates,
|
||||
@@ -29,36 +37,70 @@ void UptimeTextSensor::update() {
|
||||
this->last_ms_ = now - delta % 1000; // save remainder for next update
|
||||
delta /= 1000;
|
||||
this->uptime_ += delta;
|
||||
auto uptime = this->uptime_;
|
||||
uint32_t uptime = this->uptime_;
|
||||
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.
|
||||
while (true) { // enable use of break for early exit
|
||||
unsigned remainder = uptime % 60;
|
||||
uptime /= 60;
|
||||
if (interval < 30) {
|
||||
this->insert_buffer_(buffer, this->seconds_text_, remainder);
|
||||
if (!this->expand_ && uptime == 0)
|
||||
break;
|
||||
}
|
||||
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;
|
||||
|
||||
// Calculate all time units
|
||||
unsigned seconds = uptime % 60;
|
||||
uptime /= 60;
|
||||
unsigned minutes = uptime % 60;
|
||||
uptime /= 60;
|
||||
unsigned hours = uptime % 24;
|
||||
uptime /= 24;
|
||||
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;
|
||||
|
||||
// Determine which units to show
|
||||
bool show_days, show_hours, show_minutes, show_seconds;
|
||||
|
||||
if (this->expand_) {
|
||||
// Show all enabled units
|
||||
show_days = true;
|
||||
show_hours = hours_enabled;
|
||||
show_minutes = minutes_enabled;
|
||||
show_seconds = seconds_enabled;
|
||||
} else {
|
||||
// Start with only the smallest enabled unit
|
||||
show_seconds = seconds_enabled;
|
||||
show_minutes = minutes_enabled && !show_seconds;
|
||||
show_hours = hours_enabled && !show_minutes && !show_seconds;
|
||||
show_days = !show_hours && !show_minutes && !show_seconds;
|
||||
|
||||
// Add larger non-zero units
|
||||
if (days > 0)
|
||||
show_days = true;
|
||||
if (hours > 0 && hours_enabled)
|
||||
show_hours = true;
|
||||
if (minutes > 0 && minutes_enabled)
|
||||
show_minutes = true;
|
||||
|
||||
// Fill in gaps (e.g., show 0h between 1d and 0m)
|
||||
if (show_days && hours_enabled)
|
||||
show_hours = true;
|
||||
if (show_hours && minutes_enabled)
|
||||
show_minutes = true;
|
||||
}
|
||||
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; }
|
||||
|
||||
@@ -29,7 +29,6 @@ class UptimeTextSensor : public text_sensor::TextSensor, public PollingComponent
|
||||
void set_seconds(const char *seconds_text) { this->seconds_text_ = seconds_text; }
|
||||
|
||||
protected:
|
||||
void insert_buffer_(std::string &buffer, const char *key, unsigned value) const;
|
||||
const char *days_text_;
|
||||
const char *hours_text_;
|
||||
const char *minutes_text_;
|
||||
|
||||
Reference in New Issue
Block a user