diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index 244d307f8f1..b09e8ccbdf6 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -569,21 +569,9 @@ size_t value_accuracy_with_uom_to_buf(std::span bu if (unit_of_measurement.empty()) { return len; } - // Append " " directly - char *p = buf.data() + len; - size_t remaining = buf.size() - len; - size_t uom_len = unit_of_measurement.size(); - // Need space for: ' ' + uom + '\0' - if (remaining < 2) { - return len; - } - *p++ = ' '; - remaining--; - size_t copy_len = std::min(uom_len, remaining - 1); - memcpy(p, unit_of_measurement.c_str(), copy_len); - p += copy_len; - *p = '\0'; - return static_cast(p - buf.data()); + char *end = buf_append_sep_str(buf.data() + len, buf.size() - len, ' ', unit_of_measurement.c_str(), + unit_of_measurement.size()); + return static_cast(end - buf.data()); } int8_t step_to_accuracy_decimals(float step) { diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index ee6f76a09d5..3b93a2c4763 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -1295,6 +1295,21 @@ inline char *int8_to_str(char *buf, int8_t val) { return buf; } +/// Append a separator char and a string to a buffer, respecting remaining space. +/// Returns pointer past last char written (null terminator is written). +inline char *buf_append_sep_str(char *buf, size_t remaining, char separator, const char *str, size_t str_len) { + if (remaining < 2) { + return buf; + } + *buf++ = separator; + remaining--; + size_t copy_len = std::min(str_len, remaining - 1); + memcpy(buf, str, copy_len); + buf += copy_len; + *buf = '\0'; + return buf; +} + /// Return 10^n for small non-negative n (0-3) as uint32_t, avoiding float. inline uint32_t small_pow10(int8_t n) { if (n == 1) { diff --git a/tests/components/core/test_helpers.cpp b/tests/components/core/test_helpers.cpp index 50df1492ada..261a4111b16 100644 --- a/tests/components/core/test_helpers.cpp +++ b/tests/components/core/test_helpers.cpp @@ -116,4 +116,39 @@ TEST(FracToStr, ZeroDivisor) { EXPECT_EQ(end, buf); // writes nothing } +// --- buf_append_sep_str() --- + +TEST(BufAppendSepStr, Basic) { + char buf[32] = "23.46"; + char *start = buf + 5; + char *end = buf_append_sep_str(start, sizeof(buf) - 5, ' ', "°C", 3); + EXPECT_STREQ(buf, "23.46 °C"); + EXPECT_EQ(end - buf, 9); // "°C" is 3 bytes (UTF-8) +} + +TEST(BufAppendSepStr, EmptyString) { + char buf[32] = "100"; + char *start = buf + 3; + char *end = buf_append_sep_str(start, sizeof(buf) - 3, ' ', "", 0); + EXPECT_STREQ(buf, "100 "); + EXPECT_EQ(end - start, 1); // just the separator +} + +TEST(BufAppendSepStr, NoRoom) { + char buf[8] = "1234567"; + char *start = buf + 7; + char *end = buf_append_sep_str(start, 1, ' ', "unit", 4); + EXPECT_EQ(end, start); // nothing written +} + +TEST(BufAppendSepStr, Truncation) { + char buf[8] = "val"; + char *start = buf + 3; + // remaining = 5, separator takes 1, so 3 chars of string fit + null + char *end = buf_append_sep_str(start, 5, ' ', "longunit", 8); + *end = '\0'; + EXPECT_STREQ(buf, "val lon"); + EXPECT_EQ(end - buf, 7); +} + } // namespace esphome::testing