mirror of
https://github.com/esphome/esphome.git
synced 2026-09-03 19:46:02 +00:00
Merge remote-tracking branch 'upstream/fast-millis-esp32' into integration
This commit is contained in:
+1
-1
@@ -1 +1 @@
|
||||
88bba93420f1dbc8c64a555c631195a8dc9544e6dc98ae45ac04978206c01bd7
|
||||
ba69d5a66178882091cd943a22d1df9e461bff19bda4e312150650a39143ab8b
|
||||
|
||||
@@ -339,7 +339,7 @@ jobs:
|
||||
echo "binary=$BINARY" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Run CodSpeed benchmarks
|
||||
uses: CodSpeedHQ/action@db35df748deb45fdef0960669f57d627c1956c30 # v4
|
||||
uses: CodSpeedHQ/action@658a901452bb54c799643e060733b7afe9121b8d # v4.14.0
|
||||
with:
|
||||
run: ${{ steps.build.outputs.binary }}
|
||||
mode: simulation
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
|
||||
#include "esphome/core/alloc_helpers.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace anova {
|
||||
|
||||
@@ -105,14 +107,14 @@ void AnovaCodec::decode(const uint8_t *data, uint16_t length) {
|
||||
}
|
||||
case READ_TARGET_TEMPERATURE:
|
||||
case SET_TARGET_TEMPERATURE: {
|
||||
this->target_temp_ = parse_number<float>(str_until(buf, '\r')).value_or(0.0f);
|
||||
this->target_temp_ = parse_number<float>(str_until(buf, '\r')).value_or(0.0f); // NOLINT
|
||||
if (this->fahrenheit_)
|
||||
this->target_temp_ = ftoc(this->target_temp_);
|
||||
this->has_target_temp_ = true;
|
||||
break;
|
||||
}
|
||||
case READ_CURRENT_TEMPERATURE: {
|
||||
this->current_temp_ = parse_number<float>(str_until(buf, '\r')).value_or(0.0f);
|
||||
this->current_temp_ = parse_number<float>(str_until(buf, '\r')).value_or(0.0f); // NOLINT
|
||||
if (this->fahrenheit_)
|
||||
this->current_temp_ = ftoc(this->current_temp_);
|
||||
this->has_current_temp_ = true;
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
#include "epaper_spi_ssd1683.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome::epaper_spi {
|
||||
static constexpr const char *const TAG = "epaper_spi.mono";
|
||||
|
||||
void EPaperSSD1683::refresh_screen(bool partial) {
|
||||
ESP_LOGV(TAG, "Refresh screen");
|
||||
this->cmd_data(0x3C, {partial ? (uint8_t) 0x80 : (uint8_t) 0x01});
|
||||
// On partial update, set red RAM to inverse to remove BW ghosting
|
||||
this->cmd_data(0x21, {partial ? (uint8_t) 0x80 : (uint8_t) 0x40, (uint8_t) 0x00});
|
||||
// Set full update to 0xD7 for fast update, 0xF7 for normal
|
||||
// Fast update flashes less and draws sooner but is in busy state for the same amount of time
|
||||
// Manufacturer recommends not using fast update all the time, TODO expose this to the user
|
||||
this->cmd_data(0x22, {partial ? (uint8_t) 0xFC : (uint8_t) 0xF7});
|
||||
this->command(0x20);
|
||||
}
|
||||
|
||||
// Puts the display into deep sleep mode 1, only way to get out is to reset the display
|
||||
// Mode 1 retains RAM while sleeping, necessary for future partial and window updates
|
||||
void EPaperSSD1683::deep_sleep() {
|
||||
if (this->is_using_partial_update_()) {
|
||||
ESP_LOGV(TAG, "Deep sleep mode 1");
|
||||
this->cmd_data(0x10, {0x01}); // deep sleep, retain RAM
|
||||
} else {
|
||||
ESP_LOGV(TAG, "Deep sleep mode 2");
|
||||
this->cmd_data(0x10, {0x03}); // deep sleep, lose RAM
|
||||
}
|
||||
}
|
||||
|
||||
void EPaperSSD1683::set_window() {
|
||||
// if not using partial update, the display will go into deep sleep mode 2, so must rewrite entire
|
||||
// buffer since the display RAM will not retain contents
|
||||
if (!this->is_using_partial_update_()) {
|
||||
this->x_low_ = 0;
|
||||
this->x_high_ = this->width_;
|
||||
this->y_low_ = 0;
|
||||
this->y_high_ = this->height_;
|
||||
}
|
||||
|
||||
// round x-coordinates to byte boundaries
|
||||
this->x_low_ /= 8;
|
||||
this->x_high_ += 7;
|
||||
this->x_high_ /= 8;
|
||||
|
||||
this->cmd_data(0x44, {(uint8_t) this->x_low_, (uint8_t) (this->x_high_ - 1)});
|
||||
this->cmd_data(0x45, {(uint8_t) this->y_low_, (uint8_t) (this->y_low_ / 256), (uint8_t) (this->y_high_ - 1),
|
||||
(uint8_t) ((this->y_high_ - 1) / 256)});
|
||||
this->cmd_data(0x4E, {(uint8_t) this->x_low_});
|
||||
this->cmd_data(0x4F, {(uint8_t) this->y_low_, (uint8_t) (this->y_low_ / 256)});
|
||||
}
|
||||
|
||||
bool HOT EPaperSSD1683::transfer_data() {
|
||||
auto start_time = millis();
|
||||
if (this->current_data_index_ == 0) {
|
||||
if (this->send_red_) {
|
||||
// round to byte boundaries
|
||||
this->set_window();
|
||||
}
|
||||
// for monochrome, we need to send red on every refresh to prevent dirty pixels
|
||||
// when doing a partial refresh
|
||||
this->command(this->send_red_ ? 0x26 : 0x24);
|
||||
this->current_data_index_ = this->y_low_; // actually current line
|
||||
}
|
||||
size_t row_length = this->x_high_ - this->x_low_;
|
||||
FixedVector<uint8_t> bytes_to_send{};
|
||||
bytes_to_send.init(row_length);
|
||||
ESP_LOGV(TAG, "Writing %u bytes at line %zu at %ums", row_length, this->current_data_index_, (unsigned) millis());
|
||||
this->start_data_();
|
||||
while (this->current_data_index_ != this->y_high_) {
|
||||
size_t data_idx = this->current_data_index_ * this->row_width_ + this->x_low_;
|
||||
for (size_t i = 0; i != row_length; i++) {
|
||||
bytes_to_send[i] = this->buffer_[data_idx++];
|
||||
}
|
||||
++this->current_data_index_;
|
||||
this->write_array(&bytes_to_send.front(), row_length); // NOLINT
|
||||
if (millis() - start_time > MAX_TRANSFER_TIME) {
|
||||
// Let the main loop run and come back next loop
|
||||
this->disable();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
this->disable();
|
||||
this->current_data_index_ = 0;
|
||||
if (this->send_red_) {
|
||||
this->send_red_ = false;
|
||||
return false;
|
||||
}
|
||||
this->send_red_ = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace esphome::epaper_spi
|
||||
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
#include "epaper_spi_mono.h"
|
||||
|
||||
namespace esphome::epaper_spi {
|
||||
/**
|
||||
* A class for Solomon SSD1683 epaper displays.
|
||||
*/
|
||||
class EPaperSSD1683 : public EPaperMono {
|
||||
public:
|
||||
EPaperSSD1683(const char *name, uint16_t width, uint16_t height, const uint8_t *init_sequence,
|
||||
size_t init_sequence_length)
|
||||
: EPaperMono(name, width, height, init_sequence, init_sequence_length) {}
|
||||
|
||||
protected:
|
||||
void refresh_screen(bool partial) override;
|
||||
void deep_sleep() override;
|
||||
void set_window() override;
|
||||
bool transfer_data() override;
|
||||
};
|
||||
|
||||
} // namespace esphome::epaper_spi
|
||||
@@ -0,0 +1,27 @@
|
||||
from esphome.const import CONF_DATA_RATE
|
||||
|
||||
from . import EpaperModel
|
||||
|
||||
|
||||
class SSD1683(EpaperModel):
|
||||
def __init__(self, name, class_name="EPaperSSD1683", data_rate="20MHz", **defaults):
|
||||
defaults[CONF_DATA_RATE] = data_rate
|
||||
super().__init__(name, class_name, **defaults)
|
||||
|
||||
# fmt: off
|
||||
def get_init_sequence(self, config: dict):
|
||||
_width, height = self.get_dimensions(config)
|
||||
return (
|
||||
(0x01, (height - 1) % 256, (height - 1) // 256, 0x00), # Set column gate limit
|
||||
(0x18, 0x80), # Select internal Temp sensor
|
||||
(0x11, 0x03), # Set transform
|
||||
)
|
||||
|
||||
|
||||
ssd1683 = SSD1683("ssd1683")
|
||||
|
||||
goodisplay_gdey042t81 = ssd1683.extend(
|
||||
"goodisplay-gdey042t81-4.2",
|
||||
width=400,
|
||||
height=300,
|
||||
)
|
||||
@@ -22,7 +22,7 @@ void HttpRequestComponent::dump_config() {
|
||||
}
|
||||
|
||||
std::string HttpContainer::get_response_header(const std::string &header_name) {
|
||||
auto lower = str_lower_case(header_name);
|
||||
auto lower = str_lower_case(header_name); // NOLINT
|
||||
for (const auto &entry : this->response_headers_) {
|
||||
if (entry.name == lower) {
|
||||
ESP_LOGD(TAG, "Header with name %s found with value %s", lower.c_str(), entry.value.c_str());
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/alloc_helpers.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
@@ -400,7 +401,7 @@ class HttpRequestComponent : public Component {
|
||||
std::vector<std::string> lower;
|
||||
lower.reserve(collect_headers.size());
|
||||
for (const auto &h : collect_headers) {
|
||||
lower.push_back(str_lower_case(h));
|
||||
lower.push_back(str_lower_case(h)); // NOLINT
|
||||
}
|
||||
return this->perform(url, method, body, request_headers, lower);
|
||||
}
|
||||
@@ -415,7 +416,7 @@ class HttpRequestComponent : public Component {
|
||||
std::vector<std::string> lower;
|
||||
lower.reserve(collect_headers.size());
|
||||
for (const auto &h : collect_headers) {
|
||||
lower.push_back(str_lower_case(h));
|
||||
lower.push_back(str_lower_case(h)); // NOLINT
|
||||
}
|
||||
return this->perform(url, method, body, std::vector<Header>(request_headers.begin(), request_headers.end()), lower);
|
||||
}
|
||||
|
||||
@@ -161,7 +161,7 @@ std::shared_ptr<HttpContainer> HttpRequestArduino::perform(const std::string &ur
|
||||
container->response_headers_.clear();
|
||||
auto header_count = container->client_.headers();
|
||||
for (int i = 0; i < header_count; i++) {
|
||||
const std::string header_name = str_lower_case(container->client_.headerName(i).c_str());
|
||||
const std::string header_name = str_lower_case(container->client_.headerName(i).c_str()); // NOLINT
|
||||
if (should_collect_header(lower_case_collect_headers, header_name)) {
|
||||
std::string header_value = container->client_.header(i).c_str();
|
||||
ESP_LOGD(TAG, "Received response header, name: %s, value: %s", header_name.c_str(), header_value.c_str());
|
||||
|
||||
@@ -115,7 +115,7 @@ std::shared_ptr<HttpContainer> HttpRequestHost::perform(const std::string &url,
|
||||
container->content_length = container->response_body_.size();
|
||||
for (auto header : response.headers) {
|
||||
ESP_LOGD(TAG, "Header: %s: %s", header.first.c_str(), header.second.c_str());
|
||||
auto lower_name = str_lower_case(header.first);
|
||||
auto lower_name = str_lower_case(header.first); // NOLINT
|
||||
if (should_collect_header(lower_case_collect_headers, lower_name)) {
|
||||
container->response_headers_.push_back({lower_name, header.second});
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ esp_err_t HttpRequestIDF::http_event_handler(esp_http_client_event_t *evt) {
|
||||
|
||||
switch (evt->event_id) {
|
||||
case HTTP_EVENT_ON_HEADER: {
|
||||
const std::string header_name = str_lower_case(evt->header_key);
|
||||
const std::string header_name = str_lower_case(evt->header_key); // NOLINT
|
||||
if (should_collect_header(user_data->lower_case_collect_headers, header_name)) {
|
||||
const std::string header_value = evt->header_value;
|
||||
ESP_LOGD(TAG, "Received response header, name: %s, value: %s", header_name.c_str(), header_value.c_str());
|
||||
|
||||
@@ -756,7 +756,7 @@ async def write_image(config, all_frames=False):
|
||||
for col in range(width):
|
||||
encoder.encode(pixels[row * width + col])
|
||||
encoder.end_row()
|
||||
encoder.end_image()
|
||||
encoder.end_image()
|
||||
|
||||
rhs = [HexInt(x) for x in encoder.data]
|
||||
prog_arr = cg.progmem_array(config[CONF_RAW_DATA_ID], rhs)
|
||||
|
||||
@@ -89,10 +89,12 @@
|
||||
id: hello_world_label_
|
||||
text: "Hello World!"
|
||||
align: center
|
||||
- obj:
|
||||
- container:
|
||||
id: hello_world_qrcode_
|
||||
outline_width: 0
|
||||
border_width: 0
|
||||
height: 100
|
||||
width: 100
|
||||
hidden: !lambda |-
|
||||
return lv_obj_get_width(lv_screen_active()) < 300 && lv_obj_get_height(lv_screen_active()) < 400;
|
||||
widgets:
|
||||
|
||||
@@ -88,6 +88,12 @@ inline void lv_obj_set_style_bitmap_mask_src(lv_obj_t *obj, image::Image *image,
|
||||
inline void lv_obj_set_style_bg_image_src(lv_obj_t *obj, image::Image *image, lv_style_selector_t selector) {
|
||||
::lv_obj_set_style_bg_image_src(obj, image->get_lv_image_dsc(), selector);
|
||||
}
|
||||
inline void lv_style_set_bg_image_src(lv_style_t *style, image::Image *image) {
|
||||
::lv_style_set_bg_image_src(style, image->get_lv_image_dsc());
|
||||
}
|
||||
inline void lv_style_set_bitmap_mask_src(lv_style_t *style, image::Image *image) {
|
||||
::lv_style_set_bitmap_mask_src(style, image->get_lv_image_dsc());
|
||||
}
|
||||
#endif // USE_LVGL_IMAGE
|
||||
#ifdef USE_LVGL_ANIMIMG
|
||||
inline void lv_animimg_set_src(lv_obj_t *img, std::vector<image::Image *> images) {
|
||||
|
||||
@@ -52,19 +52,23 @@ class KeyboardType(WidgetType):
|
||||
if mode := config.get(CONF_MODE):
|
||||
await w.set_property(CONF_MODE, await KEYBOARD_MODES.process(mode))
|
||||
if textarea := config.get(CONF_TEXTAREA):
|
||||
# If a textarea is configured, it must be generated before the keyboard can attach it.
|
||||
# If not yet configured, defer the attachment code.
|
||||
if not is_widget_completed(textarea):
|
||||
# Can only happen for an initial config, where the keyboard is configured before the
|
||||
# textarea, so it's ok to always emit into the global context
|
||||
async def add_textarea():
|
||||
async with LvContext():
|
||||
await w.set_property(
|
||||
CONF_TEXTAREA,
|
||||
(await get_widgets(config, CONF_TEXTAREA))[0].obj,
|
||||
)
|
||||
|
||||
async def add_textarea():
|
||||
async with LvContext():
|
||||
await w.set_property(
|
||||
CONF_TEXTAREA, (await get_widgets(config, CONF_TEXTAREA))[0].obj
|
||||
)
|
||||
|
||||
if is_widget_completed(textarea):
|
||||
await add_textarea()
|
||||
else:
|
||||
CORE.add_job(add_textarea)
|
||||
else:
|
||||
# Handles updates in automations, and properly ordered initial config. Code is generated
|
||||
# into the enclosing context (main or lambda)
|
||||
await w.set_property(
|
||||
CONF_TEXTAREA, (await get_widgets(config, CONF_TEXTAREA))[0].obj
|
||||
)
|
||||
|
||||
|
||||
keyboard_spec = KeyboardType()
|
||||
|
||||
@@ -454,12 +454,12 @@ async def to_code(config):
|
||||
# Pin esp-nn for stable future builds (esp-tflite-micro depends on esp-nn)
|
||||
esp32.add_idf_component(name="espressif/esp-nn", ref="1.1.2")
|
||||
|
||||
esp32.add_idf_component(name="esphome/esp-micro-speech-features", ref="1.2.3")
|
||||
|
||||
cg.add_build_flag("-DTF_LITE_STATIC_MEMORY")
|
||||
cg.add_build_flag("-DTF_LITE_DISABLE_X86_NEON")
|
||||
cg.add_build_flag("-DESP_NN")
|
||||
|
||||
cg.add_library("kahrendt/ESPMicroSpeechFeatures", "1.1.0")
|
||||
|
||||
if vad_model := config.get(CONF_VAD):
|
||||
cg.add_define("USE_MICRO_WAKE_WORD_VAD")
|
||||
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
from esphome.components.mipi import DriverChip
|
||||
import esphome.config_validation as cv
|
||||
|
||||
# Standalone display
|
||||
# Product page: https://www.seeedstudio.com/reTerminal-D1001-p-6729.html
|
||||
DriverChip(
|
||||
"SEEED-RETERMINAL-D1001",
|
||||
height=1280,
|
||||
width=800,
|
||||
hsync_back_porch=20,
|
||||
hsync_pulse_width=20,
|
||||
hsync_front_porch=40,
|
||||
vsync_back_porch=12,
|
||||
vsync_pulse_width=4,
|
||||
vsync_front_porch=30,
|
||||
pclk_frequency="80MHz",
|
||||
lane_bit_rate="1.5Gbps",
|
||||
swap_xy=cv.UNDEFINED,
|
||||
color_order="RGB",
|
||||
enable_pin=[{"xl9535": None, "number": 0}, {"xl9535": None, "number": 7}],
|
||||
reset_pin={"xl9535": None, "number": 2},
|
||||
initsequence=(
|
||||
(0xE0, 0x00),
|
||||
(0xE1, 0x93),
|
||||
(0xE2, 0x65),
|
||||
(0xE3, 0xF8),
|
||||
(0x80, 0x01),
|
||||
),
|
||||
)
|
||||
@@ -0,0 +1,51 @@
|
||||
from esphome.components.mipi import DriverChip
|
||||
from esphome.config_validation import UNDEFINED
|
||||
|
||||
# fmt: off
|
||||
sunton = DriverChip(
|
||||
"ESP32-8048S070",
|
||||
swap_xy=UNDEFINED,
|
||||
initsequence=(),
|
||||
width=800,
|
||||
height=480,
|
||||
pclk_frequency="12.5MHz",
|
||||
de_pin=41,
|
||||
hsync_pin=39,
|
||||
vsync_pin=40,
|
||||
pclk_pin=42,
|
||||
hsync_pulse_width=30,
|
||||
hsync_back_porch=16,
|
||||
hsync_front_porch=210,
|
||||
vsync_pulse_width=13,
|
||||
vsync_back_porch=10,
|
||||
vsync_front_porch=22,
|
||||
data_pins={
|
||||
"red": [14, 21, 47, 48, 45],
|
||||
"green": [9, 46, 3, 8, 16, 1],
|
||||
"blue": [15, 7, 6, 5, 4],
|
||||
},
|
||||
)
|
||||
|
||||
sunton.extend(
|
||||
"ESP32-8048S050",
|
||||
swap_xy=UNDEFINED,
|
||||
initsequence=(),
|
||||
width=800,
|
||||
height=480,
|
||||
pclk_frequency="16MHz",
|
||||
de_pin=40,
|
||||
hsync_pin=39,
|
||||
vsync_pin=41,
|
||||
pclk_pin=42,
|
||||
hsync_back_porch=8,
|
||||
hsync_front_porch=8,
|
||||
hsync_pulse_width=4,
|
||||
vsync_back_porch=8,
|
||||
vsync_front_porch=8,
|
||||
vsync_pulse_width=4,
|
||||
data_pins={
|
||||
"red": [45, 48, 47, 21, 14],
|
||||
"green": [5, 6, 7, 15, 16, 4],
|
||||
"blue": [8, 3, 46, 9, 1],
|
||||
},
|
||||
)
|
||||
@@ -5,6 +5,29 @@ namespace esphome::modbus::helpers {
|
||||
|
||||
static const char *const TAG = "modbus_helpers";
|
||||
|
||||
static size_t required_payload_size(SensorValueType sensor_value_type) {
|
||||
switch (sensor_value_type) {
|
||||
case SensorValueType::U_WORD:
|
||||
case SensorValueType::S_WORD:
|
||||
return 2;
|
||||
case SensorValueType::U_DWORD:
|
||||
case SensorValueType::FP32:
|
||||
case SensorValueType::U_DWORD_R:
|
||||
case SensorValueType::FP32_R:
|
||||
case SensorValueType::S_DWORD:
|
||||
case SensorValueType::S_DWORD_R:
|
||||
return 4;
|
||||
case SensorValueType::U_QWORD:
|
||||
case SensorValueType::S_QWORD:
|
||||
case SensorValueType::U_QWORD_R:
|
||||
case SensorValueType::S_QWORD_R:
|
||||
return 8;
|
||||
case SensorValueType::RAW:
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
void number_to_payload(std::vector<uint16_t> &data, int64_t value, SensorValueType value_type) {
|
||||
switch (value_type) {
|
||||
case SensorValueType::U_WORD:
|
||||
@@ -47,93 +70,70 @@ int64_t payload_to_number(const std::vector<uint8_t> &data, SensorValueType sens
|
||||
uint32_t bitmask) {
|
||||
int64_t value = 0; // int64_t because it can hold signed and unsigned 32 bits
|
||||
|
||||
if (offset > data.size()) {
|
||||
ESP_LOGE(TAG, "not enough data for value");
|
||||
// Validate offset against the buffer for all types, including RAW/unsupported, so
|
||||
// a malformed or misconfigured frame still produces an error log.
|
||||
if (static_cast<size_t>(offset) > data.size()) {
|
||||
ESP_LOGE(TAG, "not enough data for value type=%u offset=%u size=%zu", static_cast<unsigned int>(sensor_value_type),
|
||||
static_cast<unsigned int>(offset), data.size());
|
||||
return value;
|
||||
}
|
||||
|
||||
const size_t required_size = required_payload_size(sensor_value_type);
|
||||
if (required_size == 0) {
|
||||
return value;
|
||||
}
|
||||
|
||||
if (data.size() - offset < required_size) {
|
||||
ESP_LOGE(TAG, "not enough data for value type=%u offset=%u size=%zu required=%zu",
|
||||
static_cast<unsigned int>(sensor_value_type), static_cast<unsigned int>(offset), data.size(),
|
||||
required_size);
|
||||
return value;
|
||||
}
|
||||
|
||||
size_t size = data.size() - offset;
|
||||
bool error = false;
|
||||
switch (sensor_value_type) {
|
||||
case SensorValueType::U_WORD:
|
||||
if (size >= 2) {
|
||||
value = mask_and_shift_by_rightbit(get_data<uint16_t>(data, offset),
|
||||
bitmask); // default is 0xFFFF ;
|
||||
} else {
|
||||
error = true;
|
||||
}
|
||||
value = mask_and_shift_by_rightbit(get_data<uint16_t>(data, offset), bitmask); // default is 0xFFFF ;
|
||||
break;
|
||||
case SensorValueType::U_DWORD:
|
||||
case SensorValueType::FP32:
|
||||
if (size >= 4) {
|
||||
value = get_data<uint32_t>(data, offset);
|
||||
value = mask_and_shift_by_rightbit((uint32_t) value, bitmask);
|
||||
} else {
|
||||
error = true;
|
||||
}
|
||||
value = get_data<uint32_t>(data, offset);
|
||||
value = mask_and_shift_by_rightbit((uint32_t) value, bitmask);
|
||||
break;
|
||||
case SensorValueType::U_DWORD_R:
|
||||
case SensorValueType::FP32_R:
|
||||
if (size >= 4) {
|
||||
value = get_data<uint32_t>(data, offset);
|
||||
value = static_cast<uint32_t>(value & 0xFFFF) << 16 | (value & 0xFFFF0000) >> 16;
|
||||
value = mask_and_shift_by_rightbit((uint32_t) value, bitmask);
|
||||
} else {
|
||||
error = true;
|
||||
}
|
||||
value = get_data<uint32_t>(data, offset);
|
||||
value = static_cast<uint32_t>(value & 0xFFFF) << 16 | (value & 0xFFFF0000) >> 16;
|
||||
value = mask_and_shift_by_rightbit((uint32_t) value, bitmask);
|
||||
break;
|
||||
case SensorValueType::S_WORD:
|
||||
if (size >= 2) {
|
||||
value = mask_and_shift_by_rightbit(get_data<int16_t>(data, offset),
|
||||
bitmask); // default is 0xFFFF ;
|
||||
} else {
|
||||
error = true;
|
||||
}
|
||||
value = mask_and_shift_by_rightbit(get_data<int16_t>(data, offset), bitmask); // default is 0xFFFF ;
|
||||
break;
|
||||
case SensorValueType::S_DWORD:
|
||||
if (size >= 4) {
|
||||
value = mask_and_shift_by_rightbit(get_data<int32_t>(data, offset), bitmask);
|
||||
} else {
|
||||
error = true;
|
||||
}
|
||||
value = mask_and_shift_by_rightbit(get_data<int32_t>(data, offset), bitmask);
|
||||
break;
|
||||
case SensorValueType::S_DWORD_R: {
|
||||
if (size >= 4) {
|
||||
value = get_data<uint32_t>(data, offset);
|
||||
// Currently the high word is at the low position
|
||||
// the sign bit is therefore at low before the switch
|
||||
uint32_t sign_bit = (value & 0x8000) << 16;
|
||||
value = mask_and_shift_by_rightbit(
|
||||
static_cast<int32_t>(((value & 0x7FFF) << 16 | (value & 0xFFFF0000) >> 16) | sign_bit), bitmask);
|
||||
} else {
|
||||
error = true;
|
||||
}
|
||||
value = get_data<uint32_t>(data, offset);
|
||||
// Currently the high word is at the low position
|
||||
// the sign bit is therefore at low before the switch
|
||||
uint32_t sign_bit = (value & 0x8000) << 16;
|
||||
value = mask_and_shift_by_rightbit(
|
||||
static_cast<int32_t>(((value & 0x7FFF) << 16 | (value & 0xFFFF0000) >> 16) | sign_bit), bitmask);
|
||||
} break;
|
||||
case SensorValueType::U_QWORD:
|
||||
case SensorValueType::S_QWORD:
|
||||
// Ignore bitmask for QWORD
|
||||
if (size >= 8) {
|
||||
value = get_data<uint64_t>(data, offset);
|
||||
} else {
|
||||
error = true;
|
||||
}
|
||||
value = get_data<uint64_t>(data, offset);
|
||||
break;
|
||||
case SensorValueType::U_QWORD_R:
|
||||
case SensorValueType::S_QWORD_R: {
|
||||
// Ignore bitmask for QWORD
|
||||
if (size >= 8) {
|
||||
uint64_t tmp = get_data<uint64_t>(data, offset);
|
||||
value = (tmp << 48) | (tmp >> 48) | ((tmp & 0xFFFF0000) << 16) | ((tmp >> 16) & 0xFFFF0000);
|
||||
} else {
|
||||
error = true;
|
||||
}
|
||||
uint64_t tmp = get_data<uint64_t>(data, offset);
|
||||
value = (tmp << 48) | (tmp >> 48) | ((tmp & 0xFFFF0000) << 16) | ((tmp >> 16) & 0xFFFF0000);
|
||||
} break;
|
||||
case SensorValueType::RAW:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (error)
|
||||
ESP_LOGE(TAG, "not enough data for value");
|
||||
return value;
|
||||
}
|
||||
} // namespace esphome::modbus::helpers
|
||||
|
||||
@@ -294,57 +294,59 @@ void Rtttl::play(std::string rtttl) {
|
||||
}
|
||||
ESP_LOGD(TAG, "Playing song %.*s", (int) this->position_, this->rtttl_.c_str());
|
||||
|
||||
// Get default duration
|
||||
this->position_ = this->rtttl_.find("d=", this->position_);
|
||||
if (this->position_ == std::string::npos) {
|
||||
ESP_LOGE(TAG, "Missing 'd='");
|
||||
return;
|
||||
}
|
||||
this->position_ += 2;
|
||||
num = this->get_integer_();
|
||||
if (num == 1 || num == 2 || num == 4 || num == 8 || num == 16 || num == 32) {
|
||||
this->default_note_denominator_ = num;
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Invalid default duration: %d", num);
|
||||
return;
|
||||
}
|
||||
|
||||
// Get default octave
|
||||
this->position_ = this->rtttl_.find("o=", this->position_);
|
||||
if (this->position_ == std::string::npos) {
|
||||
ESP_LOGE(TAG, "Missing 'o=");
|
||||
return;
|
||||
}
|
||||
this->position_ += 2;
|
||||
num = this->get_integer_();
|
||||
if (num >= MIN_OCTAVE && num <= MAX_OCTAVE) {
|
||||
this->default_octave_ = num;
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Invalid default octave: %d", num);
|
||||
return;
|
||||
}
|
||||
|
||||
// Get BPM
|
||||
this->position_ = this->rtttl_.find("b=", this->position_);
|
||||
if (this->position_ == std::string::npos) {
|
||||
ESP_LOGE(TAG, "Missing b=");
|
||||
return;
|
||||
}
|
||||
this->position_ += 2;
|
||||
num = this->get_integer_();
|
||||
if (num >= 4) { // Below 4 is not realistic and would cause a integer overflow
|
||||
bpm = num;
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Invalid BPM: %d", num);
|
||||
return;
|
||||
}
|
||||
|
||||
this->position_ = this->rtttl_.find(':', this->position_);
|
||||
if (this->position_ == std::string::npos) {
|
||||
size_t name_end_position = this->position_;
|
||||
size_t control_end = this->rtttl_.find(':', name_end_position + 1);
|
||||
if (control_end == std::string::npos) {
|
||||
ESP_LOGE(TAG, "Missing second ':'");
|
||||
return;
|
||||
}
|
||||
this->position_++;
|
||||
|
||||
// Get default duration
|
||||
size_t pos = this->rtttl_.find("d=", name_end_position);
|
||||
if (pos == std::string::npos || pos >= control_end) {
|
||||
ESP_LOGW(TAG, "Missing 'd='; use default duration %d", this->default_note_denominator_);
|
||||
} else {
|
||||
this->position_ = pos + 2;
|
||||
num = this->get_integer_();
|
||||
if (num == 1 || num == 2 || num == 4 || num == 8 || num == 16 || num == 32) {
|
||||
this->default_note_denominator_ = num;
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Invalid default duration: %d", num);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Get default octave
|
||||
pos = this->rtttl_.find("o=", name_end_position);
|
||||
if (pos == std::string::npos || pos >= control_end) {
|
||||
ESP_LOGW(TAG, "Missing 'o='; use default octave %d", this->default_octave_);
|
||||
} else {
|
||||
this->position_ = pos + 2;
|
||||
num = this->get_integer_();
|
||||
if (num >= MIN_OCTAVE && num <= MAX_OCTAVE) {
|
||||
this->default_octave_ = num;
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Invalid default octave: %d", num);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Get BPM
|
||||
pos = this->rtttl_.find("b=", name_end_position);
|
||||
if (pos == std::string::npos || pos >= control_end) {
|
||||
ESP_LOGW(TAG, "Missing 'b='; use default BPM %d", bpm);
|
||||
} else {
|
||||
this->position_ = pos + 2;
|
||||
num = this->get_integer_();
|
||||
if (num >= 4) { // Below 4 is not realistic and would cause a integer overflow
|
||||
bpm = num;
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Invalid BPM: %d", num);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this->position_ = control_end + 1;
|
||||
|
||||
// BPM usually expresses the number of quarter notes per minute
|
||||
this->wholenote_duration_ = 60 * 1000L * 4 / bpm; // This is the time for whole note (in milliseconds)
|
||||
|
||||
@@ -0,0 +1,229 @@
|
||||
#include "esphome/core/alloc_helpers.h"
|
||||
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <cstdarg>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
|
||||
namespace esphome {
|
||||
|
||||
// --- String helpers ---
|
||||
|
||||
std::string str_truncate(const std::string &str, size_t length) {
|
||||
return str.length() > length ? str.substr(0, length) : str;
|
||||
}
|
||||
|
||||
std::string str_until(const char *str, char ch) {
|
||||
const char *pos = strchr(str, ch);
|
||||
return pos == nullptr ? std::string(str) : std::string(str, pos - str);
|
||||
}
|
||||
std::string str_until(const std::string &str, char ch) { return str.substr(0, str.find(ch)); }
|
||||
|
||||
// wrapper around std::transform to run safely on functions from the ctype.h header
|
||||
// see https://en.cppreference.com/w/cpp/string/byte/toupper#Notes
|
||||
template<int (*fn)(int)> std::string str_ctype_transform(const std::string &str) {
|
||||
std::string result;
|
||||
result.resize(str.length());
|
||||
std::transform(str.begin(), str.end(), result.begin(), [](unsigned char ch) { return fn(ch); });
|
||||
return result;
|
||||
}
|
||||
std::string str_lower_case(const std::string &str) { return str_ctype_transform<std::tolower>(str); }
|
||||
|
||||
std::string str_upper_case(const std::string &str) {
|
||||
std::string result;
|
||||
result.resize(str.length());
|
||||
std::transform(str.begin(), str.end(), result.begin(), [](unsigned char ch) { return std::toupper(ch); });
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string str_snake_case(const std::string &str) {
|
||||
std::string result = str;
|
||||
for (char &c : result) {
|
||||
c = to_snake_case_char(c);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string str_sanitize(const std::string &str) {
|
||||
std::string result;
|
||||
result.resize(str.size());
|
||||
str_sanitize_to(&result[0], str.size() + 1, str.c_str());
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string str_snprintf(const char *fmt, size_t len, ...) {
|
||||
std::string str;
|
||||
va_list args;
|
||||
|
||||
str.resize(len);
|
||||
va_start(args, len);
|
||||
size_t out_length = vsnprintf(&str[0], len + 1, fmt, args);
|
||||
va_end(args);
|
||||
|
||||
if (out_length < len)
|
||||
str.resize(out_length);
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
std::string str_sprintf(const char *fmt, ...) {
|
||||
std::string str;
|
||||
va_list args;
|
||||
|
||||
va_start(args, fmt);
|
||||
size_t length = vsnprintf(nullptr, 0, fmt, args);
|
||||
va_end(args);
|
||||
|
||||
str.resize(length);
|
||||
va_start(args, fmt);
|
||||
vsnprintf(&str[0], length + 1, fmt, args);
|
||||
va_end(args);
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
// --- Value formatting helpers ---
|
||||
|
||||
std::string value_accuracy_to_string(float value, int8_t accuracy_decimals) {
|
||||
char buf[VALUE_ACCURACY_MAX_LEN];
|
||||
value_accuracy_to_buf(buf, value, accuracy_decimals);
|
||||
return std::string(buf);
|
||||
}
|
||||
|
||||
// --- Base64 helpers ---
|
||||
|
||||
static constexpr const char *BASE64_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
"abcdefghijklmnopqrstuvwxyz"
|
||||
"0123456789+/";
|
||||
|
||||
// Encode 3 input bytes to 4 base64 characters, append 'count' to ret.
|
||||
static inline void base64_encode_triple(const char *char_array_3, int count, std::string &ret) {
|
||||
char char_array_4[4];
|
||||
char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
|
||||
char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
|
||||
char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
|
||||
char_array_4[3] = char_array_3[2] & 0x3f;
|
||||
|
||||
for (int j = 0; j < count; j++)
|
||||
ret += BASE64_CHARS[static_cast<uint8_t>(char_array_4[j])];
|
||||
}
|
||||
|
||||
std::string base64_encode(const std::vector<uint8_t> &buf) { return base64_encode(buf.data(), buf.size()); }
|
||||
|
||||
std::string base64_encode(const uint8_t *buf, size_t buf_len) {
|
||||
std::string ret;
|
||||
int i = 0;
|
||||
char char_array_3[3];
|
||||
|
||||
while (buf_len--) {
|
||||
char_array_3[i++] = *(buf++);
|
||||
if (i == 3) {
|
||||
base64_encode_triple(char_array_3, 4, ret);
|
||||
i = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (i) {
|
||||
for (int j = i; j < 3; j++)
|
||||
char_array_3[j] = '\0';
|
||||
|
||||
base64_encode_triple(char_array_3, i + 1, ret);
|
||||
|
||||
while ((i++ < 3))
|
||||
ret += '=';
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> base64_decode(const std::string &encoded_string) {
|
||||
// Calculate maximum decoded size: every 4 base64 chars = 3 bytes
|
||||
size_t max_len = ((encoded_string.size() + 3) / 4) * 3;
|
||||
std::vector<uint8_t> ret(max_len);
|
||||
size_t actual_len = base64_decode(encoded_string, ret.data(), max_len);
|
||||
ret.resize(actual_len);
|
||||
return ret;
|
||||
}
|
||||
|
||||
// --- Hex/binary formatting helpers ---
|
||||
|
||||
std::string format_mac_address_pretty(const uint8_t *mac) {
|
||||
char buf[18];
|
||||
format_mac_addr_upper(mac, buf);
|
||||
return std::string(buf);
|
||||
}
|
||||
|
||||
std::string format_hex(const uint8_t *data, size_t length) {
|
||||
std::string ret;
|
||||
ret.resize(length * 2);
|
||||
format_hex_to(&ret[0], length * 2 + 1, data, length);
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::string format_hex(const std::vector<uint8_t> &data) { return format_hex(data.data(), data.size()); }
|
||||
|
||||
// Shared implementation for uint8_t and string hex pretty formatting
|
||||
static std::string format_hex_pretty_uint8(const uint8_t *data, size_t length, char separator, bool show_length) {
|
||||
if (data == nullptr || length == 0)
|
||||
return "";
|
||||
std::string ret;
|
||||
size_t hex_len = separator ? (length * 3 - 1) : (length * 2);
|
||||
ret.resize(hex_len);
|
||||
format_hex_pretty_to(&ret[0], hex_len + 1, data, length, separator);
|
||||
if (show_length && length > 4)
|
||||
return ret + " (" + std::to_string(length) + ")";
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::string format_hex_pretty(const uint8_t *data, size_t length, char separator, bool show_length) {
|
||||
return format_hex_pretty_uint8(data, length, separator, show_length);
|
||||
}
|
||||
std::string format_hex_pretty(const std::vector<uint8_t> &data, char separator, bool show_length) {
|
||||
return format_hex_pretty(data.data(), data.size(), separator, show_length);
|
||||
}
|
||||
|
||||
std::string format_hex_pretty(const uint16_t *data, size_t length, char separator, bool show_length) {
|
||||
if (data == nullptr || length == 0)
|
||||
return "";
|
||||
std::string ret;
|
||||
size_t hex_len = separator ? (length * 5 - 1) : (length * 4);
|
||||
ret.resize(hex_len);
|
||||
format_hex_pretty_to(&ret[0], hex_len + 1, data, length, separator);
|
||||
if (show_length && length > 4)
|
||||
return ret + " (" + std::to_string(length) + ")";
|
||||
return ret;
|
||||
}
|
||||
std::string format_hex_pretty(const std::vector<uint16_t> &data, char separator, bool show_length) {
|
||||
return format_hex_pretty(data.data(), data.size(), separator, show_length);
|
||||
}
|
||||
std::string format_hex_pretty(const std::string &data, char separator, bool show_length) {
|
||||
return format_hex_pretty_uint8(reinterpret_cast<const uint8_t *>(data.data()), data.length(), separator, show_length);
|
||||
}
|
||||
|
||||
std::string format_bin(const uint8_t *data, size_t length) {
|
||||
std::string result;
|
||||
result.resize(length * 8);
|
||||
format_bin_to(&result[0], length * 8 + 1, data, length);
|
||||
return result;
|
||||
}
|
||||
|
||||
// --- MAC address helpers ---
|
||||
|
||||
std::string get_mac_address() {
|
||||
uint8_t mac[6];
|
||||
get_mac_address_raw(mac);
|
||||
char buf[13];
|
||||
format_mac_addr_lower_no_sep(mac, buf);
|
||||
return std::string(buf);
|
||||
}
|
||||
|
||||
std::string get_mac_address_pretty() {
|
||||
char buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
|
||||
return std::string(get_mac_address_pretty_into_buffer(buf));
|
||||
}
|
||||
|
||||
} // namespace esphome
|
||||
@@ -0,0 +1,128 @@
|
||||
#pragma once
|
||||
|
||||
/// @file alloc_helpers.h
|
||||
/// @brief Heap-allocating helper functions.
|
||||
///
|
||||
/// These functions return std::string and allocate heap memory on every call.
|
||||
/// On long-running embedded devices, repeated heap allocations fragment memory
|
||||
/// over time, eventually causing crashes even with free memory available.
|
||||
///
|
||||
/// Prefer the stack-based alternatives documented on each function instead.
|
||||
/// New code should avoid using these functions.
|
||||
|
||||
#include <cstdarg>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace esphome {
|
||||
|
||||
// --- String helpers (allocating) ---
|
||||
|
||||
/// Truncate a string to a specific length.
|
||||
/// @warning Allocates heap memory. Avoid in new code - causes heap fragmentation on long-running devices.
|
||||
std::string str_truncate(const std::string &str, size_t length);
|
||||
|
||||
/// Extract the part of the string until either the first occurrence of the specified character, or the end
|
||||
/// (requires str to be null-terminated).
|
||||
/// @warning Allocates heap memory. Avoid in new code - causes heap fragmentation on long-running devices.
|
||||
std::string str_until(const char *str, char ch);
|
||||
/// Extract the part of the string until either the first occurrence of the specified character, or the end.
|
||||
/// @warning Allocates heap memory. Avoid in new code - causes heap fragmentation on long-running devices.
|
||||
std::string str_until(const std::string &str, char ch);
|
||||
|
||||
/// Convert the string to lower case.
|
||||
/// @warning Allocates heap memory. Avoid in new code - causes heap fragmentation on long-running devices.
|
||||
std::string str_lower_case(const std::string &str);
|
||||
|
||||
/// Convert the string to upper case.
|
||||
/// @warning Allocates heap memory. Avoid in new code - causes heap fragmentation on long-running devices.
|
||||
std::string str_upper_case(const std::string &str);
|
||||
|
||||
/// Convert the string to snake case (lowercase with underscores).
|
||||
/// @warning Allocates heap memory. Avoid in new code - causes heap fragmentation on long-running devices.
|
||||
std::string str_snake_case(const std::string &str);
|
||||
|
||||
/// Sanitizes the input string by removing all characters but alphanumerics, dashes and underscores.
|
||||
/// @warning Allocates heap memory. Use str_sanitize_to() with a stack buffer instead.
|
||||
std::string str_sanitize(const std::string &str);
|
||||
|
||||
/// snprintf-like function returning std::string of maximum length \p len (excluding null terminator).
|
||||
/// @warning Allocates heap memory. Use snprintf() with a stack buffer instead.
|
||||
std::string __attribute__((format(printf, 1, 3))) str_snprintf(const char *fmt, size_t len, ...);
|
||||
|
||||
/// sprintf-like function returning std::string.
|
||||
/// @warning Allocates heap memory. Use snprintf() with a stack buffer instead.
|
||||
std::string __attribute__((format(printf, 1, 2))) str_sprintf(const char *fmt, ...);
|
||||
|
||||
// --- Hex/binary formatting helpers (allocating) ---
|
||||
|
||||
/// Format the six-byte array \p mac into a MAC address string.
|
||||
/// @warning Allocates heap memory. Use format_mac_addr_upper() with a stack buffer instead.
|
||||
std::string format_mac_address_pretty(const uint8_t mac[6]);
|
||||
|
||||
/// Format the byte array \p data of length \p len in lowercased hex.
|
||||
/// @warning Allocates heap memory. Use format_hex_to() with a stack buffer instead.
|
||||
std::string format_hex(const uint8_t *data, size_t length);
|
||||
|
||||
/// Format the vector \p data in lowercased hex.
|
||||
/// @warning Allocates heap memory. Use format_hex_to() with a stack buffer instead.
|
||||
std::string format_hex(const std::vector<uint8_t> &data);
|
||||
|
||||
/// Format a byte array in pretty-printed, human-readable hex format.
|
||||
/// @warning Allocates heap memory. Use format_hex_pretty_to() with a stack buffer instead.
|
||||
std::string format_hex_pretty(const uint8_t *data, size_t length, char separator = '.', bool show_length = true);
|
||||
|
||||
/// Format a 16-bit word array in pretty-printed, human-readable hex format.
|
||||
/// @warning Allocates heap memory. Use format_hex_pretty_to() with a stack buffer instead.
|
||||
std::string format_hex_pretty(const uint16_t *data, size_t length, char separator = '.', bool show_length = true);
|
||||
|
||||
/// Format a byte vector in pretty-printed, human-readable hex format.
|
||||
/// @warning Allocates heap memory. Use format_hex_pretty_to() with a stack buffer instead.
|
||||
std::string format_hex_pretty(const std::vector<uint8_t> &data, char separator = '.', bool show_length = true);
|
||||
|
||||
/// Format a 16-bit word vector in pretty-printed, human-readable hex format.
|
||||
/// @warning Allocates heap memory. Use format_hex_pretty_to() with a stack buffer instead.
|
||||
std::string format_hex_pretty(const std::vector<uint16_t> &data, char separator = '.', bool show_length = true);
|
||||
|
||||
/// Format a string's bytes in pretty-printed, human-readable hex format.
|
||||
/// @warning Allocates heap memory. Use format_hex_pretty_to() with a stack buffer instead.
|
||||
std::string format_hex_pretty(const std::string &data, char separator = '.', bool show_length = true);
|
||||
|
||||
/// Format the byte array \p data of length \p len in binary.
|
||||
/// @warning Allocates heap memory. Use format_bin_to() with a stack buffer instead.
|
||||
std::string format_bin(const uint8_t *data, size_t length);
|
||||
|
||||
// --- Value formatting helpers (allocating) ---
|
||||
|
||||
/// Format a float value with accuracy decimals to a string.
|
||||
/// @deprecated Allocates heap memory. Use value_accuracy_to_buf() instead. Removed in 2026.7.0.
|
||||
__attribute__((deprecated("Allocates heap memory. Use value_accuracy_to_buf() instead. Removed in 2026.7.0.")))
|
||||
std::string
|
||||
value_accuracy_to_string(float value, int8_t accuracy_decimals);
|
||||
|
||||
// --- Base64 helpers (allocating) ---
|
||||
|
||||
/// Encode a byte buffer to base64 string.
|
||||
/// @warning Allocates heap memory.
|
||||
std::string base64_encode(const uint8_t *buf, size_t buf_len);
|
||||
/// Encode a byte vector to base64 string.
|
||||
/// @warning Allocates heap memory.
|
||||
std::string base64_encode(const std::vector<uint8_t> &buf);
|
||||
|
||||
/// Decode a base64 string to a byte vector.
|
||||
/// @warning Allocates heap memory. Use base64_decode(data, len, buf, buf_len) with a pre-allocated buffer instead.
|
||||
std::vector<uint8_t> base64_decode(const std::string &encoded_string);
|
||||
|
||||
// --- MAC address helpers (allocating) ---
|
||||
|
||||
/// Get the device MAC address as a string, in lowercase hex notation.
|
||||
/// @warning Allocates heap memory. Use get_mac_address_into_buffer() instead.
|
||||
std::string get_mac_address();
|
||||
|
||||
/// Get the device MAC address as a string, in colon-separated uppercase hex notation.
|
||||
/// @warning Allocates heap memory. Use get_mac_address_pretty_into_buffer() instead.
|
||||
std::string get_mac_address_pretty();
|
||||
|
||||
} // namespace esphome
|
||||
@@ -214,7 +214,7 @@ void Application::process_dump_config_() {
|
||||
|
||||
void Application::feed_wdt() {
|
||||
// Cold entry: callers without a millis() timestamp in hand. Fetches the
|
||||
// time and takes the same rate-limit path as feed_wdt_with_time().
|
||||
// time and takes the same rate-limit paths as feed_wdt_with_time().
|
||||
uint32_t now = MillisInternal::get();
|
||||
if (now - this->last_wdt_feed_ > WDT_FEED_INTERVAL_MS) {
|
||||
this->feed_wdt_slow_(now);
|
||||
|
||||
@@ -262,7 +262,7 @@ class Application {
|
||||
/// When USE_STATUS_LED is compiled in, also gates a separate (shorter)
|
||||
/// interval for dispatching status_led so the LED blink pattern stays
|
||||
/// readable even though arch_feed_wdt pokes are now rate-limited at
|
||||
/// 300 ms. The two rate limits are independent so raising
|
||||
/// WDT_FEED_INTERVAL_MS. The two rate limits are independent so raising
|
||||
/// WDT_FEED_INTERVAL_MS does not distort the LED cadence.
|
||||
void ESPHOME_ALWAYS_INLINE feed_wdt_with_time(uint32_t time) {
|
||||
if (static_cast<uint32_t>(time - this->last_wdt_feed_) > WDT_FEED_INTERVAL_MS) [[unlikely]] {
|
||||
|
||||
+10
-179
@@ -221,31 +221,7 @@ bool str_endswith_ignore_case(const char *str, size_t str_len, const char *suffi
|
||||
return strncasecmp(str + str_len - suffix_len, suffix, suffix_len) == 0;
|
||||
}
|
||||
|
||||
std::string str_truncate(const std::string &str, size_t length) {
|
||||
return str.length() > length ? str.substr(0, length) : str;
|
||||
}
|
||||
std::string str_until(const char *str, char ch) {
|
||||
const char *pos = strchr(str, ch);
|
||||
return pos == nullptr ? std::string(str) : std::string(str, pos - str);
|
||||
}
|
||||
std::string str_until(const std::string &str, char ch) { return str.substr(0, str.find(ch)); }
|
||||
// wrapper around std::transform to run safely on functions from the ctype.h header
|
||||
// see https://en.cppreference.com/w/cpp/string/byte/toupper#Notes
|
||||
template<int (*fn)(int)> std::string str_ctype_transform(const std::string &str) {
|
||||
std::string result;
|
||||
result.resize(str.length());
|
||||
std::transform(str.begin(), str.end(), result.begin(), [](unsigned char ch) { return fn(ch); });
|
||||
return result;
|
||||
}
|
||||
std::string str_lower_case(const std::string &str) { return str_ctype_transform<std::tolower>(str); }
|
||||
std::string str_upper_case(const std::string &str) { return str_ctype_transform<std::toupper>(str); }
|
||||
std::string str_snake_case(const std::string &str) {
|
||||
std::string result = str;
|
||||
for (char &c : result) {
|
||||
c = to_snake_case_char(c);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
// str_truncate, str_until, str_lower_case, str_upper_case, str_snake_case moved to alloc_helpers.cpp
|
||||
char *str_sanitize_to(char *buffer, size_t buffer_size, const char *str) {
|
||||
if (buffer_size == 0) {
|
||||
return buffer;
|
||||
@@ -258,41 +234,7 @@ char *str_sanitize_to(char *buffer, size_t buffer_size, const char *str) {
|
||||
return buffer;
|
||||
}
|
||||
|
||||
std::string str_sanitize(const std::string &str) {
|
||||
std::string result;
|
||||
result.resize(str.size());
|
||||
str_sanitize_to(&result[0], str.size() + 1, str.c_str());
|
||||
return result;
|
||||
}
|
||||
std::string str_snprintf(const char *fmt, size_t len, ...) {
|
||||
std::string str;
|
||||
va_list args;
|
||||
|
||||
str.resize(len);
|
||||
va_start(args, len);
|
||||
size_t out_length = vsnprintf(&str[0], len + 1, fmt, args);
|
||||
va_end(args);
|
||||
|
||||
if (out_length < len)
|
||||
str.resize(out_length);
|
||||
|
||||
return str;
|
||||
}
|
||||
std::string str_sprintf(const char *fmt, ...) {
|
||||
std::string str;
|
||||
va_list args;
|
||||
|
||||
va_start(args, fmt);
|
||||
size_t length = vsnprintf(nullptr, 0, fmt, args);
|
||||
va_end(args);
|
||||
|
||||
str.resize(length);
|
||||
va_start(args, fmt);
|
||||
vsnprintf(&str[0], length + 1, fmt, args);
|
||||
va_end(args);
|
||||
|
||||
return str;
|
||||
}
|
||||
// str_sanitize, str_snprintf, str_sprintf moved to alloc_helpers.cpp
|
||||
|
||||
// Maximum size for name with suffix: 120 (max friendly name) + 1 (separator) + 6 (MAC suffix) + 1 (null term)
|
||||
static constexpr size_t MAX_NAME_WITH_SUFFIX_SIZE = 128;
|
||||
@@ -341,11 +283,7 @@ size_t parse_hex(const char *str, size_t length, uint8_t *data, size_t count) {
|
||||
return chars;
|
||||
}
|
||||
|
||||
std::string format_mac_address_pretty(const uint8_t *mac) {
|
||||
char buf[18];
|
||||
format_mac_addr_upper(mac, buf);
|
||||
return std::string(buf);
|
||||
}
|
||||
// format_mac_address_pretty moved to alloc_helpers.cpp
|
||||
|
||||
// Internal helper for hex formatting - base is 'a' for lowercase or 'A' for uppercase.
|
||||
// When separator is set, it is written unconditionally after each byte and the last
|
||||
@@ -398,13 +336,7 @@ char *format_hex_to(char *buffer, size_t buffer_size, const uint8_t *data, size_
|
||||
return format_hex_internal(buffer, buffer_size, data, length, 0, 'a');
|
||||
}
|
||||
|
||||
std::string format_hex(const uint8_t *data, size_t length) {
|
||||
std::string ret;
|
||||
ret.resize(length * 2);
|
||||
format_hex_to(&ret[0], length * 2 + 1, data, length);
|
||||
return ret;
|
||||
}
|
||||
std::string format_hex(const std::vector<uint8_t> &data) { return format_hex(data.data(), data.size()); }
|
||||
// format_hex (std::string returning overloads) moved to alloc_helpers.cpp
|
||||
|
||||
char *format_hex_pretty_to(char *buffer, size_t buffer_size, const uint8_t *data, size_t length, char separator) {
|
||||
return format_hex_internal(buffer, buffer_size, data, length, separator, 'A');
|
||||
@@ -441,43 +373,7 @@ char *format_hex_pretty_to(char *buffer, size_t buffer_size, const uint16_t *dat
|
||||
return buffer;
|
||||
}
|
||||
|
||||
// Shared implementation for uint8_t and string hex formatting
|
||||
static std::string format_hex_pretty_uint8(const uint8_t *data, size_t length, char separator, bool show_length) {
|
||||
if (data == nullptr || length == 0)
|
||||
return "";
|
||||
std::string ret;
|
||||
size_t hex_len = separator ? (length * 3 - 1) : (length * 2);
|
||||
ret.resize(hex_len);
|
||||
format_hex_pretty_to(&ret[0], hex_len + 1, data, length, separator);
|
||||
if (show_length && length > 4)
|
||||
return ret + " (" + std::to_string(length) + ")";
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::string format_hex_pretty(const uint8_t *data, size_t length, char separator, bool show_length) {
|
||||
return format_hex_pretty_uint8(data, length, separator, show_length);
|
||||
}
|
||||
std::string format_hex_pretty(const std::vector<uint8_t> &data, char separator, bool show_length) {
|
||||
return format_hex_pretty(data.data(), data.size(), separator, show_length);
|
||||
}
|
||||
|
||||
std::string format_hex_pretty(const uint16_t *data, size_t length, char separator, bool show_length) {
|
||||
if (data == nullptr || length == 0)
|
||||
return "";
|
||||
std::string ret;
|
||||
size_t hex_len = separator ? (length * 5 - 1) : (length * 4);
|
||||
ret.resize(hex_len);
|
||||
format_hex_pretty_to(&ret[0], hex_len + 1, data, length, separator);
|
||||
if (show_length && length > 4)
|
||||
return ret + " (" + std::to_string(length) + ")";
|
||||
return ret;
|
||||
}
|
||||
std::string format_hex_pretty(const std::vector<uint16_t> &data, char separator, bool show_length) {
|
||||
return format_hex_pretty(data.data(), data.size(), separator, show_length);
|
||||
}
|
||||
std::string format_hex_pretty(const std::string &data, char separator, bool show_length) {
|
||||
return format_hex_pretty_uint8(reinterpret_cast<const uint8_t *>(data.data()), data.length(), separator, show_length);
|
||||
}
|
||||
// format_hex_pretty (all std::string returning overloads) moved to alloc_helpers.cpp
|
||||
|
||||
char *format_bin_to(char *buffer, size_t buffer_size, const uint8_t *data, size_t length) {
|
||||
if (buffer_size == 0) {
|
||||
@@ -500,12 +396,7 @@ char *format_bin_to(char *buffer, size_t buffer_size, const uint8_t *data, size_
|
||||
return buffer;
|
||||
}
|
||||
|
||||
std::string format_bin(const uint8_t *data, size_t length) {
|
||||
std::string result;
|
||||
result.resize(length * 8);
|
||||
format_bin_to(&result[0], length * 8 + 1, data, length);
|
||||
return result;
|
||||
}
|
||||
// format_bin moved to alloc_helpers.cpp
|
||||
|
||||
ParseOnOffState parse_on_off(const char *str, const char *on, const char *off) {
|
||||
if (on == nullptr && ESPHOME_strcasecmp_P(str, ESPHOME_PSTR("on")) == 0)
|
||||
@@ -537,11 +428,7 @@ static inline void normalize_accuracy_decimals(float &value, int8_t &accuracy_de
|
||||
}
|
||||
}
|
||||
|
||||
std::string value_accuracy_to_string(float value, int8_t accuracy_decimals) {
|
||||
char buf[VALUE_ACCURACY_MAX_LEN];
|
||||
value_accuracy_to_buf(buf, value, accuracy_decimals);
|
||||
return std::string(buf);
|
||||
}
|
||||
// value_accuracy_to_string moved to alloc_helpers.cpp
|
||||
|
||||
// Fast float-to-string for accuracy_decimals 0-3 (covers virtually all sensor usage).
|
||||
// Avoids snprintf("%.*f") which pulls in heavy float formatting machinery.
|
||||
@@ -636,45 +523,7 @@ static inline uint8_t base64_find_char(char c) {
|
||||
// Check if character is valid base64 or base64url
|
||||
static inline bool is_base64(char c) { return (isalnum(c) || (c == '+') || (c == '/') || (c == '-') || (c == '_')); }
|
||||
|
||||
std::string base64_encode(const std::vector<uint8_t> &buf) { return base64_encode(buf.data(), buf.size()); }
|
||||
|
||||
// Encode 3 input bytes to 4 base64 characters, append 'count' to ret.
|
||||
static inline void base64_encode_triple(const char *char_array_3, int count, std::string &ret) {
|
||||
char char_array_4[4];
|
||||
char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
|
||||
char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
|
||||
char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
|
||||
char_array_4[3] = char_array_3[2] & 0x3f;
|
||||
|
||||
for (int j = 0; j < count; j++)
|
||||
ret += BASE64_CHARS[static_cast<uint8_t>(char_array_4[j])];
|
||||
}
|
||||
|
||||
std::string base64_encode(const uint8_t *buf, size_t buf_len) {
|
||||
std::string ret;
|
||||
int i = 0;
|
||||
char char_array_3[3];
|
||||
|
||||
while (buf_len--) {
|
||||
char_array_3[i++] = *(buf++);
|
||||
if (i == 3) {
|
||||
base64_encode_triple(char_array_3, 4, ret);
|
||||
i = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (i) {
|
||||
for (int j = i; j < 3; j++)
|
||||
char_array_3[j] = '\0';
|
||||
|
||||
base64_encode_triple(char_array_3, i + 1, ret);
|
||||
|
||||
while ((i++ < 3))
|
||||
ret += '=';
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
// base64_encode (both overloads) moved to alloc_helpers.cpp
|
||||
|
||||
size_t base64_decode(const std::string &encoded_string, uint8_t *buf, size_t buf_len) {
|
||||
return base64_decode(reinterpret_cast<const uint8_t *>(encoded_string.data()), encoded_string.size(), buf, buf_len);
|
||||
@@ -735,14 +584,7 @@ size_t base64_decode(const uint8_t *encoded_data, size_t encoded_len, uint8_t *b
|
||||
return out;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> base64_decode(const std::string &encoded_string) {
|
||||
// Calculate maximum decoded size: every 4 base64 chars = 3 bytes
|
||||
size_t max_len = ((encoded_string.size() + 3) / 4) * 3;
|
||||
std::vector<uint8_t> ret(max_len);
|
||||
size_t actual_len = base64_decode(encoded_string, ret.data(), max_len);
|
||||
ret.resize(actual_len);
|
||||
return ret;
|
||||
}
|
||||
// base64_decode (vector-returning overload) moved to alloc_helpers.cpp
|
||||
|
||||
/// Decode base64/base64url string directly into vector of little-endian int32 values
|
||||
/// @param base64 Base64 or base64url encoded string (both +/ and -_ accepted)
|
||||
@@ -881,18 +723,7 @@ void HighFrequencyLoopRequester::stop() {
|
||||
this->started_ = false;
|
||||
}
|
||||
|
||||
std::string get_mac_address() {
|
||||
uint8_t mac[6];
|
||||
get_mac_address_raw(mac);
|
||||
char buf[13];
|
||||
format_mac_addr_lower_no_sep(mac, buf);
|
||||
return std::string(buf);
|
||||
}
|
||||
|
||||
std::string get_mac_address_pretty() {
|
||||
char buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
|
||||
return std::string(get_mac_address_pretty_into_buffer(buf));
|
||||
}
|
||||
// get_mac_address, get_mac_address_pretty moved to alloc_helpers.cpp
|
||||
|
||||
void get_mac_address_into_buffer(std::span<char, MAC_ADDRESS_BUFFER_SIZE> buf) {
|
||||
uint8_t mac[6];
|
||||
|
||||
+53
-223
@@ -21,6 +21,12 @@
|
||||
|
||||
#include "esphome/core/optional.h"
|
||||
|
||||
// Backward compatibility re-export of heap-allocating helpers.
|
||||
// These functions have moved to alloc_helpers.h. External components should
|
||||
// update their includes to use #include "esphome/core/alloc_helpers.h" directly.
|
||||
// This re-export will be removed in 2026.11.0.
|
||||
#include "esphome/core/alloc_helpers.h"
|
||||
|
||||
#ifdef USE_ESP8266
|
||||
#include <Esp.h>
|
||||
#include <pgmspace.h>
|
||||
@@ -979,27 +985,13 @@ inline bool str_endswith_ignore_case(const std::string &str, const char *suffix)
|
||||
return str_endswith_ignore_case(str.c_str(), str.size(), suffix, strlen(suffix));
|
||||
}
|
||||
|
||||
/// Truncate a string to a specific length.
|
||||
/// @warning Allocates heap memory. Avoid in new code - causes heap fragmentation on long-running devices.
|
||||
std::string str_truncate(const std::string &str, size_t length);
|
||||
// str_truncate moved to alloc_helpers.h - remove this include before 2026.11.0
|
||||
|
||||
/// Extract the part of the string until either the first occurrence of the specified character, or the end
|
||||
/// (requires str to be null-terminated).
|
||||
std::string str_until(const char *str, char ch);
|
||||
/// Extract the part of the string until either the first occurrence of the specified character, or the end.
|
||||
std::string str_until(const std::string &str, char ch);
|
||||
|
||||
/// Convert the string to lower case.
|
||||
std::string str_lower_case(const std::string &str);
|
||||
/// Convert the string to upper case.
|
||||
/// @warning Allocates heap memory. Avoid in new code - causes heap fragmentation on long-running devices.
|
||||
std::string str_upper_case(const std::string &str);
|
||||
// str_until, str_lower_case, str_upper_case moved to alloc_helpers.h - remove this comment before 2026.11.0
|
||||
|
||||
/// Convert a single char to snake_case: lowercase and space to underscore.
|
||||
constexpr char to_snake_case_char(char c) { return (c == ' ') ? '_' : (c >= 'A' && c <= 'Z') ? c + ('a' - 'A') : c; }
|
||||
/// Convert the string to snake case (lowercase with underscores).
|
||||
/// @warning Allocates heap memory. Avoid in new code - causes heap fragmentation on long-running devices.
|
||||
std::string str_snake_case(const std::string &str);
|
||||
// str_snake_case moved to alloc_helpers.h - remove this comment before 2026.11.0
|
||||
|
||||
/// Sanitize a single char: keep alphanumerics, dashes, underscores; replace others with underscore.
|
||||
constexpr char to_sanitized_char(char c) {
|
||||
@@ -1022,9 +1014,7 @@ template<size_t N> inline char *str_sanitize_to(char (&buffer)[N], const char *s
|
||||
return str_sanitize_to(buffer, N, str);
|
||||
}
|
||||
|
||||
/// Sanitizes the input string by removing all characters but alphanumerics, dashes and underscores.
|
||||
/// @warning Allocates heap memory. Use str_sanitize_to() with a stack buffer instead.
|
||||
std::string str_sanitize(const std::string &str);
|
||||
// str_sanitize moved to alloc_helpers.h - remove this comment before 2026.11.0
|
||||
|
||||
/// Calculate FNV-1 hash of a string while applying snake_case + sanitize transformations.
|
||||
/// This computes object_id hashes directly from names without creating an intermediate buffer.
|
||||
@@ -1040,13 +1030,7 @@ inline uint32_t fnv1_hash_object_id(const char *str, size_t len) {
|
||||
return hash;
|
||||
}
|
||||
|
||||
/// snprintf-like function returning std::string of maximum length \p len (excluding null terminator).
|
||||
/// @warning Allocates heap memory. Use snprintf() with a stack buffer instead.
|
||||
std::string __attribute__((format(printf, 1, 3))) str_snprintf(const char *fmt, size_t len, ...);
|
||||
|
||||
/// sprintf-like function returning std::string.
|
||||
/// @warning Allocates heap memory. Use snprintf() with a stack buffer instead.
|
||||
std::string __attribute__((format(printf, 1, 2))) str_sprintf(const char *fmt, ...);
|
||||
// str_snprintf, str_sprintf moved to alloc_helpers.h - remove this comment before 2026.11.0
|
||||
|
||||
#ifdef USE_ESP8266
|
||||
// ESP8266: Use vsnprintf_P to keep format strings in flash (PROGMEM)
|
||||
@@ -1095,7 +1079,33 @@ __attribute__((format(printf, 4, 5))) inline size_t buf_append_printf(char *buf,
|
||||
}
|
||||
#endif
|
||||
|
||||
/// Safely append a string to buffer without format parsing, returning new position (capped at size).
|
||||
#ifdef USE_ESP8266
|
||||
/// Safely append a PROGMEM string to buffer, returning new position (capped at size).
|
||||
/// ESP8266 internal implementation — prefer the `buf_append_str` macro which wraps
|
||||
/// literals with `PSTR()` automatically so they stay in flash instead of eating RAM.
|
||||
/// @param buf Output buffer
|
||||
/// @param size Total buffer size
|
||||
/// @param pos Current position in buffer
|
||||
/// @param str PROGMEM-resident string to append (must not be null)
|
||||
/// @return New position after appending; returns `size` if `pos >= size`, otherwise
|
||||
/// returns at most `size - 1` because one byte is reserved for the null terminator
|
||||
inline size_t buf_append_str_p(char *buf, size_t size, size_t pos, PGM_P str) {
|
||||
if (pos >= size) {
|
||||
return size;
|
||||
}
|
||||
size_t remaining = size - pos - 1; // reserve space for null terminator
|
||||
size_t len = strnlen_P(str, remaining);
|
||||
memcpy_P(buf + pos, str, len);
|
||||
pos += len;
|
||||
buf[pos] = '\0';
|
||||
return pos;
|
||||
}
|
||||
/// Safely append a string to buffer, returning new position (capped at size).
|
||||
/// More efficient than buf_append_printf for plain string literals.
|
||||
/// On ESP8266 the literal is wrapped with PSTR() so it stays in flash.
|
||||
#define buf_append_str(buf, size, pos, str) buf_append_str_p(buf, size, pos, PSTR(str))
|
||||
#else
|
||||
/// Safely append a string to buffer, returning new position (capped at size).
|
||||
/// More efficient than buf_append_printf for plain string literals.
|
||||
/// @param buf Output buffer
|
||||
/// @param size Total buffer size
|
||||
@@ -1107,15 +1117,13 @@ inline size_t buf_append_str(char *buf, size_t size, size_t pos, const char *str
|
||||
return size;
|
||||
}
|
||||
size_t remaining = size - pos - 1; // reserve space for null terminator
|
||||
size_t len = strlen(str);
|
||||
if (len > remaining) {
|
||||
len = remaining;
|
||||
}
|
||||
size_t len = strnlen(str, remaining);
|
||||
memcpy(buf + pos, str, len);
|
||||
pos += len;
|
||||
buf[pos] = '\0';
|
||||
return pos;
|
||||
}
|
||||
#endif
|
||||
|
||||
/// Concatenate a name with a separator and suffix using an efficient stack-based approach.
|
||||
/// This avoids multiple heap allocations during string construction.
|
||||
@@ -1476,189 +1484,26 @@ inline void format_mac_addr_lower_no_sep(const uint8_t *mac, char *output) {
|
||||
format_hex_to(output, MAC_ADDRESS_BUFFER_SIZE, mac, MAC_ADDRESS_SIZE);
|
||||
}
|
||||
|
||||
/// Format the six-byte array \p mac into a MAC address.
|
||||
/// @warning Allocates heap memory. Use format_mac_addr_upper() with a stack buffer instead.
|
||||
/// Causes heap fragmentation on long-running devices.
|
||||
std::string format_mac_address_pretty(const uint8_t mac[6]);
|
||||
/// Format the byte array \p data of length \p len in lowercased hex.
|
||||
/// @warning Allocates heap memory. Use format_hex_to() with a stack buffer instead.
|
||||
/// Causes heap fragmentation on long-running devices.
|
||||
std::string format_hex(const uint8_t *data, size_t length);
|
||||
/// Format the vector \p data in lowercased hex.
|
||||
/// @warning Allocates heap memory. Use format_hex_to() with a stack buffer instead.
|
||||
/// Causes heap fragmentation on long-running devices.
|
||||
std::string format_hex(const std::vector<uint8_t> &data);
|
||||
// format_mac_address_pretty, format_hex (all overloads) moved to alloc_helpers.h
|
||||
// Remove this comment and the template overloads below before 2026.11.0
|
||||
|
||||
/// Format an unsigned integer in lowercased hex, starting with the most significant byte.
|
||||
/// @warning Allocates heap memory. Use format_hex_to() with a stack buffer instead.
|
||||
/// Causes heap fragmentation on long-running devices.
|
||||
template<typename T, enable_if_t<std::is_unsigned<T>::value, int> = 0> std::string format_hex(T val) {
|
||||
val = convert_big_endian(val);
|
||||
return format_hex(reinterpret_cast<uint8_t *>(&val), sizeof(T));
|
||||
}
|
||||
/// Format the std::array \p data in lowercased hex.
|
||||
/// @warning Allocates heap memory. Use format_hex_to() with a stack buffer instead.
|
||||
/// Causes heap fragmentation on long-running devices.
|
||||
template<std::size_t N> std::string format_hex(const std::array<uint8_t, N> &data) {
|
||||
return format_hex(data.data(), data.size());
|
||||
}
|
||||
|
||||
/** Format a byte array in pretty-printed, human-readable hex format.
|
||||
*
|
||||
* Converts binary data to a hexadecimal string representation with customizable formatting.
|
||||
* Each byte is displayed as a two-digit uppercase hex value, separated by the specified separator.
|
||||
* Optionally includes the total byte count in parentheses at the end.
|
||||
*
|
||||
* @warning Allocates heap memory. Use format_hex_pretty_to() with a stack buffer instead.
|
||||
* Causes heap fragmentation on long-running devices.
|
||||
*
|
||||
* @param data Pointer to the byte array to format.
|
||||
* @param length Number of bytes in the array.
|
||||
* @param separator Character to use between hex bytes (default: '.').
|
||||
* @param show_length Whether to append the byte count in parentheses (default: true).
|
||||
* @return Formatted hex string, e.g., "A1.B2.C3.D4.E5 (5)" or "A1:B2:C3" depending on parameters.
|
||||
*
|
||||
* @note Returns empty string if data is nullptr or length is 0.
|
||||
* @note The length will only be appended if show_length is true AND the length is greater than 4.
|
||||
*
|
||||
* Example:
|
||||
* @code
|
||||
* uint8_t data[] = {0xA1, 0xB2, 0xC3};
|
||||
* format_hex_pretty(data, 3); // Returns "A1.B2.C3" (no length shown for <= 4 parts)
|
||||
* uint8_t data2[] = {0xA1, 0xB2, 0xC3, 0xD4, 0xE5};
|
||||
* format_hex_pretty(data2, 5); // Returns "A1.B2.C3.D4.E5 (5)"
|
||||
* format_hex_pretty(data2, 5, ':'); // Returns "A1:B2:C3:D4:E5 (5)"
|
||||
* format_hex_pretty(data2, 5, '.', false); // Returns "A1.B2.C3.D4.E5"
|
||||
* @endcode
|
||||
*/
|
||||
std::string format_hex_pretty(const uint8_t *data, size_t length, char separator = '.', bool show_length = true);
|
||||
// format_hex_pretty (all overloads) moved to alloc_helpers.h
|
||||
// Remove this comment and the template overload below before 2026.11.0
|
||||
|
||||
/** Format a 16-bit word array in pretty-printed, human-readable hex format.
|
||||
*
|
||||
* Similar to the byte array version, but formats 16-bit words as 4-digit hex values.
|
||||
*
|
||||
* @warning Allocates heap memory. Use format_hex_pretty_to() with a stack buffer instead.
|
||||
* Causes heap fragmentation on long-running devices.
|
||||
*
|
||||
* @param data Pointer to the 16-bit word array to format.
|
||||
* @param length Number of 16-bit words in the array.
|
||||
* @param separator Character to use between hex words (default: '.').
|
||||
* @param show_length Whether to append the word count in parentheses (default: true).
|
||||
* @return Formatted hex string with 4-digit hex values per word.
|
||||
*
|
||||
* @note The length will only be appended if show_length is true AND the length is greater than 4.
|
||||
*
|
||||
* Example:
|
||||
* @code
|
||||
* uint16_t data[] = {0xA1B2, 0xC3D4};
|
||||
* format_hex_pretty(data, 2); // Returns "A1B2.C3D4" (no length shown for <= 4 parts)
|
||||
* uint16_t data2[] = {0xA1B2, 0xC3D4, 0xE5F6};
|
||||
* format_hex_pretty(data2, 3); // Returns "A1B2.C3D4.E5F6 (3)"
|
||||
* @endcode
|
||||
*/
|
||||
std::string format_hex_pretty(const uint16_t *data, size_t length, char separator = '.', bool show_length = true);
|
||||
|
||||
/** Format a byte vector in pretty-printed, human-readable hex format.
|
||||
*
|
||||
* Convenience overload for std::vector<uint8_t>. Formats each byte as a two-digit
|
||||
* uppercase hex value with customizable separator.
|
||||
*
|
||||
* @warning Allocates heap memory. Use format_hex_pretty_to() with a stack buffer instead.
|
||||
* Causes heap fragmentation on long-running devices.
|
||||
*
|
||||
* @param data Vector of bytes to format.
|
||||
* @param separator Character to use between hex bytes (default: '.').
|
||||
* @param show_length Whether to append the byte count in parentheses (default: true).
|
||||
* @return Formatted hex string representation of the vector contents.
|
||||
*
|
||||
* @note The length will only be appended if show_length is true AND the vector size is greater than 4.
|
||||
*
|
||||
* Example:
|
||||
* @code
|
||||
* std::vector<uint8_t> data = {0xDE, 0xAD, 0xBE, 0xEF};
|
||||
* format_hex_pretty(data); // Returns "DE.AD.BE.EF" (no length shown for <= 4 parts)
|
||||
* std::vector<uint8_t> data2 = {0xDE, 0xAD, 0xBE, 0xEF, 0xCA};
|
||||
* format_hex_pretty(data2); // Returns "DE.AD.BE.EF.CA (5)"
|
||||
* format_hex_pretty(data2, '-'); // Returns "DE-AD-BE-EF-CA (5)"
|
||||
* @endcode
|
||||
*/
|
||||
std::string format_hex_pretty(const std::vector<uint8_t> &data, char separator = '.', bool show_length = true);
|
||||
|
||||
/** Format a 16-bit word vector in pretty-printed, human-readable hex format.
|
||||
*
|
||||
* Convenience overload for std::vector<uint16_t>. Each 16-bit word is formatted
|
||||
* as a 4-digit uppercase hex value in big-endian order.
|
||||
*
|
||||
* @warning Allocates heap memory. Use format_hex_pretty_to() with a stack buffer instead.
|
||||
* Causes heap fragmentation on long-running devices.
|
||||
*
|
||||
* @param data Vector of 16-bit words to format.
|
||||
* @param separator Character to use between hex words (default: '.').
|
||||
* @param show_length Whether to append the word count in parentheses (default: true).
|
||||
* @return Formatted hex string representation of the vector contents.
|
||||
*
|
||||
* @note The length will only be appended if show_length is true AND the vector size is greater than 4.
|
||||
*
|
||||
* Example:
|
||||
* @code
|
||||
* std::vector<uint16_t> data = {0x1234, 0x5678};
|
||||
* format_hex_pretty(data); // Returns "1234.5678" (no length shown for <= 4 parts)
|
||||
* std::vector<uint16_t> data2 = {0x1234, 0x5678, 0x9ABC};
|
||||
* format_hex_pretty(data2); // Returns "1234.5678.9ABC (3)"
|
||||
* @endcode
|
||||
*/
|
||||
std::string format_hex_pretty(const std::vector<uint16_t> &data, char separator = '.', bool show_length = true);
|
||||
|
||||
/** Format a string's bytes in pretty-printed, human-readable hex format.
|
||||
*
|
||||
* Treats each character in the string as a byte and formats it in hex.
|
||||
* Useful for debugging binary data stored in std::string containers.
|
||||
*
|
||||
* @warning Allocates heap memory. Use format_hex_pretty_to() with a stack buffer instead.
|
||||
* Causes heap fragmentation on long-running devices.
|
||||
*
|
||||
* @param data String whose bytes should be formatted as hex.
|
||||
* @param separator Character to use between hex bytes (default: '.').
|
||||
* @param show_length Whether to append the byte count in parentheses (default: true).
|
||||
* @return Formatted hex string representation of the string's byte contents.
|
||||
*
|
||||
* @note The length will only be appended if show_length is true AND the string length is greater than 4.
|
||||
*
|
||||
* Example:
|
||||
* @code
|
||||
* std::string data = "ABC"; // ASCII: 0x41, 0x42, 0x43
|
||||
* format_hex_pretty(data); // Returns "41.42.43" (no length shown for <= 4 parts)
|
||||
* std::string data2 = "ABCDE";
|
||||
* format_hex_pretty(data2); // Returns "41.42.43.44.45 (5)"
|
||||
* @endcode
|
||||
*/
|
||||
std::string format_hex_pretty(const std::string &data, char separator = '.', bool show_length = true);
|
||||
|
||||
/** Format an unsigned integer in pretty-printed, human-readable hex format.
|
||||
*
|
||||
* Converts the integer to big-endian byte order and formats each byte as hex.
|
||||
* The most significant byte appears first in the output string.
|
||||
*
|
||||
* @warning Allocates heap memory. Use format_hex_pretty_to() with a stack buffer instead.
|
||||
* Causes heap fragmentation on long-running devices.
|
||||
*
|
||||
* @tparam T Unsigned integer type (uint8_t, uint16_t, uint32_t, uint64_t, etc.).
|
||||
* @param val The unsigned integer value to format.
|
||||
* @param separator Character to use between hex bytes (default: '.').
|
||||
* @param show_length Whether to append the byte count in parentheses (default: true).
|
||||
* @return Formatted hex string with most significant byte first.
|
||||
*
|
||||
* @note The length will only be appended if show_length is true AND sizeof(T) is greater than 4.
|
||||
*
|
||||
* Example:
|
||||
* @code
|
||||
* uint32_t value = 0x12345678;
|
||||
* format_hex_pretty(value); // Returns "12.34.56.78" (no length shown for <= 4 parts)
|
||||
* uint64_t value2 = 0x123456789ABCDEF0;
|
||||
* format_hex_pretty(value2); // Returns "12.34.56.78.9A.BC.DE.F0 (8)"
|
||||
* format_hex_pretty(value2, ':'); // Returns "12:34:56:78:9A:BC:DE:F0 (8)"
|
||||
* format_hex_pretty<uint16_t>(0x1234); // Returns "12.34"
|
||||
* @endcode
|
||||
*/
|
||||
/// Format an unsigned integer in pretty-printed, human-readable hex format.
|
||||
/// @warning Allocates heap memory. Use format_hex_pretty_to() with a stack buffer instead.
|
||||
template<typename T, enable_if_t<std::is_unsigned<T>::value, int> = 0>
|
||||
std::string format_hex_pretty(T val, char separator = '.', bool show_length = true) {
|
||||
val = convert_big_endian(val);
|
||||
@@ -1718,13 +1563,10 @@ inline char *format_bin_to(char (&buffer)[N], T val) {
|
||||
return format_bin_to(buffer, reinterpret_cast<const uint8_t *>(&val), sizeof(T));
|
||||
}
|
||||
|
||||
/// Format the byte array \p data of length \p len in binary.
|
||||
/// @warning Allocates heap memory. Use format_bin_to() with a stack buffer instead.
|
||||
/// Causes heap fragmentation on long-running devices.
|
||||
std::string format_bin(const uint8_t *data, size_t length);
|
||||
// format_bin moved to alloc_helpers.h - remove this comment and template overload before 2026.11.0
|
||||
|
||||
/// Format an unsigned integer in binary, starting with the most significant byte.
|
||||
/// @warning Allocates heap memory. Use format_bin_to() with a stack buffer instead.
|
||||
/// Causes heap fragmentation on long-running devices.
|
||||
template<typename T, enable_if_t<std::is_unsigned<T>::value, int> = 0> std::string format_bin(T val) {
|
||||
val = convert_big_endian(val);
|
||||
return format_bin(reinterpret_cast<uint8_t *>(&val), sizeof(T));
|
||||
@@ -1740,9 +1582,7 @@ enum ParseOnOffState : uint8_t {
|
||||
/// Parse a string that contains either on, off or toggle.
|
||||
ParseOnOffState parse_on_off(const char *str, const char *on = nullptr, const char *off = nullptr);
|
||||
|
||||
/// @deprecated Allocates heap memory. Use value_accuracy_to_buf() instead. Removed in 2026.7.0.
|
||||
ESPDEPRECATED("Allocates heap memory. Use value_accuracy_to_buf() instead. Removed in 2026.7.0.", "2026.1.0")
|
||||
std::string value_accuracy_to_string(float value, int8_t accuracy_decimals);
|
||||
// value_accuracy_to_string moved to alloc_helpers.h - remove this comment before 2026.11.0
|
||||
|
||||
/// Maximum buffer size for value_accuracy formatting (float ~15 chars + space + UOM ~40 chars + null)
|
||||
static constexpr size_t VALUE_ACCURACY_MAX_LEN = 64;
|
||||
@@ -1756,10 +1596,8 @@ size_t value_accuracy_with_uom_to_buf(std::span<char, VALUE_ACCURACY_MAX_LEN> bu
|
||||
/// Derive accuracy in decimals from an increment step.
|
||||
int8_t step_to_accuracy_decimals(float step);
|
||||
|
||||
std::string base64_encode(const uint8_t *buf, size_t buf_len);
|
||||
std::string base64_encode(const std::vector<uint8_t> &buf);
|
||||
|
||||
std::vector<uint8_t> base64_decode(const std::string &encoded_string);
|
||||
// base64_encode (both overloads), base64_decode (vector overload) moved to alloc_helpers.h
|
||||
// Remove this comment before 2026.11.0
|
||||
size_t base64_decode(std::string const &encoded_string, uint8_t *buf, size_t buf_len);
|
||||
size_t base64_decode(const uint8_t *encoded_data, size_t encoded_len, uint8_t *buf, size_t buf_len);
|
||||
|
||||
@@ -2195,15 +2033,7 @@ class HighFrequencyLoopRequester {
|
||||
/// Get the device MAC address as raw bytes, written into the provided byte array (6 bytes).
|
||||
void get_mac_address_raw(uint8_t *mac); // NOLINT(readability-non-const-parameter)
|
||||
|
||||
/// Get the device MAC address as a string, in lowercase hex notation.
|
||||
/// @warning Allocates heap memory. Avoid in new code - causes heap fragmentation on long-running devices.
|
||||
/// Use get_mac_address_into_buffer() instead.
|
||||
std::string get_mac_address();
|
||||
|
||||
/// Get the device MAC address as a string, in colon-separated uppercase hex notation.
|
||||
/// @warning Allocates heap memory. Avoid in new code - causes heap fragmentation on long-running devices.
|
||||
/// Use get_mac_address_pretty_into_buffer() instead.
|
||||
std::string get_mac_address_pretty();
|
||||
// get_mac_address, get_mac_address_pretty moved to alloc_helpers.h - remove this comment before 2026.11.0
|
||||
|
||||
/// Get the device MAC address into the given buffer, in lowercase hex notation.
|
||||
/// Assumes buffer length is MAC_ADDRESS_BUFFER_SIZE (12 digits for hexadecimal representation followed by null
|
||||
|
||||
+33
-17
@@ -631,33 +631,43 @@ def Pvariable(id_: ID, rhs: SafeExpType, type_: "MockObj" = None) -> "MockObj":
|
||||
if isinstance(rhs, MockObj) and rhs.is_new_expr:
|
||||
# For 'new' allocations, use placement new into static storage
|
||||
# to avoid heap fragmentation on embedded devices.
|
||||
the_type = id_.type
|
||||
#
|
||||
# Storage must be sized and aligned for the actual instantiated class,
|
||||
# which may be a subclass of id_.type (e.g. `cv.declare_id(BaseClass)`
|
||||
# combined with `SubClass.new()` — used by ili9xxx, waveshare_epaper,
|
||||
# etc. to select a model-specific constructor). Using id_.type would
|
||||
# run the base-class default constructor instead, silently losing any
|
||||
# subclass initialization. Template args live on the CallExpression
|
||||
# and are re-emitted below.
|
||||
call_expr = rhs.base
|
||||
assert isinstance(call_expr, CallExpression), (
|
||||
f"Expected CallExpression for placement new, got {type(call_expr)}"
|
||||
)
|
||||
actual_type = rhs.new_type if rhs.new_type is not None else id_.type
|
||||
if call_expr.template_args is not None:
|
||||
actual_type = f"{actual_type}{call_expr.template_args}"
|
||||
pointer_type = id_.type
|
||||
# Extract component namespace from type for memory analysis attribution
|
||||
component_ns = _extract_component_ns(str(the_type))
|
||||
component_ns = _extract_component_ns(str(actual_type))
|
||||
storage_name = f"{component_ns}__{id_.id}__pstorage"
|
||||
|
||||
# Declare aligned byte array for the object storage
|
||||
CORE.add_global(
|
||||
RawStatement(
|
||||
f"alignas({the_type}) static unsigned char {storage_name}[sizeof({the_type})];"
|
||||
f"alignas({actual_type}) static unsigned char {storage_name}[sizeof({actual_type})];"
|
||||
)
|
||||
)
|
||||
# Pointer declaration uses id_.type to preserve the declared base-class
|
||||
# pointer type for downstream callers (polymorphism through base ptr).
|
||||
CORE.add_global(
|
||||
AssignmentExpression(
|
||||
f"static {the_type}",
|
||||
f"static {pointer_type}",
|
||||
"*const ",
|
||||
id_,
|
||||
MockObj(f"reinterpret_cast<{the_type} *>({storage_name})"),
|
||||
MockObj(f"reinterpret_cast<{pointer_type} *>({storage_name})"),
|
||||
)
|
||||
)
|
||||
# Extract args from the CallExpression and rebuild as placement new.
|
||||
# Template args are already encoded in the_type (e.g. GlobalsComponent<int>),
|
||||
# so we only pass the constructor args, not template_args.
|
||||
call_expr = rhs.base
|
||||
assert isinstance(call_expr, CallExpression), (
|
||||
f"Expected CallExpression for placement new, got {type(call_expr)}"
|
||||
)
|
||||
placement_new = CallExpression(f"new({id_.id}) {the_type}", *call_expr.args)
|
||||
placement_new = CallExpression(f"new({id_.id}) {actual_type}", *call_expr.args)
|
||||
CORE.add(ExpressionStatement(placement_new))
|
||||
else:
|
||||
decl = VariableDeclarationExpression(id_.type, "*", id_, static=True)
|
||||
@@ -894,12 +904,16 @@ class MockObj(Expression):
|
||||
Mostly consists of magic methods that allow ESPHome's codegen syntax.
|
||||
"""
|
||||
|
||||
__slots__ = ("base", "op", "is_new_expr")
|
||||
__slots__ = ("base", "op", "is_new_expr", "new_type")
|
||||
|
||||
def __init__(self, base, op=".", is_new_expr=False) -> None:
|
||||
def __init__(self, base, op=".", is_new_expr=False, new_type=None) -> None:
|
||||
self.base = base
|
||||
self.op = op
|
||||
self.is_new_expr = is_new_expr
|
||||
# For `is_new_expr=True` objects, `new_type` holds the class name being
|
||||
# constructed (e.g. "ili9xxx::ILI9XXXST7789V"). Needed by Pvariable so
|
||||
# placement new uses the actual subclass rather than id_.type.
|
||||
self.new_type = new_type
|
||||
|
||||
def __getattr__(self, attr: str) -> "MockObj":
|
||||
# prevent python dunder methods being replaced by mock objects
|
||||
@@ -914,7 +928,9 @@ class MockObj(Expression):
|
||||
|
||||
def __call__(self, *args: SafeExpType) -> "MockObj":
|
||||
call = CallExpression(self.base, *args)
|
||||
return MockObj(call, self.op, is_new_expr=self.is_new_expr)
|
||||
return MockObj(
|
||||
call, self.op, is_new_expr=self.is_new_expr, new_type=self.new_type
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return str(self.base)
|
||||
@@ -928,7 +944,7 @@ class MockObj(Expression):
|
||||
|
||||
@property
|
||||
def new(self) -> "MockObj":
|
||||
return MockObj(f"new {self.base}", "->", is_new_expr=True)
|
||||
return MockObj(f"new {self.base}", "->", is_new_expr=True, new_type=self.base)
|
||||
|
||||
def template(self, *args: SafeExpType) -> "MockObj":
|
||||
"""Apply template parameters to this object."""
|
||||
|
||||
@@ -3,6 +3,8 @@ dependencies:
|
||||
version: "7.4.2"
|
||||
esphome/esp-audio-libs:
|
||||
version: 2.0.4
|
||||
esphome/esp-micro-speech-features:
|
||||
version: 1.2.3
|
||||
esphome/micro-decoder:
|
||||
version: 0.1.1
|
||||
esphome/micro-flac:
|
||||
|
||||
@@ -162,7 +162,6 @@ lib_deps =
|
||||
makuna/NeoPixelBus@2.8.0 ; neopixelbus
|
||||
esphome/ESP32-audioI2S@2.3.0 ; i2s_audio
|
||||
droscy/esp_wireguard@0.4.5 ; wireguard
|
||||
kahrendt/ESPMicroSpeechFeatures@1.1.0 ; micro_wake_word
|
||||
|
||||
build_flags =
|
||||
${common:arduino.build_flags}
|
||||
@@ -184,7 +183,6 @@ framework = espidf
|
||||
lib_deps =
|
||||
${common:idf.lib_deps}
|
||||
droscy/esp_wireguard@0.4.5 ; wireguard
|
||||
kahrendt/ESPMicroSpeechFeatures@1.1.0 ; micro_wake_word
|
||||
tonia/HeatpumpIR@1.0.41 ; heatpumpir
|
||||
build_flags =
|
||||
${common:idf.build_flags}
|
||||
|
||||
+15
-1
@@ -722,18 +722,22 @@ def lint_trailing_whitespace(fname, match):
|
||||
# Heap-allocating helpers that cause fragmentation on long-running embedded devices.
|
||||
# These return std::string and should be replaced with stack-based alternatives.
|
||||
HEAP_ALLOCATING_HELPERS = {
|
||||
"base64_encode": "base64_encode_to() with a pre-allocated buffer",
|
||||
"format_bin": "format_bin_to() with a stack buffer",
|
||||
"format_hex": "format_hex_to() with a stack buffer",
|
||||
"format_hex_pretty": "format_hex_pretty_to() with a stack buffer",
|
||||
"format_mac_address_pretty": "format_mac_addr_upper() with a stack buffer",
|
||||
"get_mac_address": "get_mac_address_into_buffer() with a stack buffer",
|
||||
"get_mac_address_pretty": "get_mac_address_pretty_into_buffer() with a stack buffer",
|
||||
"str_lower_case": "manual tolower() with a stack buffer",
|
||||
"str_sanitize": "str_sanitize_to() with a stack buffer",
|
||||
"str_truncate": "removal (function is unused)",
|
||||
"str_until": "manual strchr()/find() with a StringRef or stack buffer",
|
||||
"str_upper_case": "removal (function is unused)",
|
||||
"str_snake_case": "removal (function is unused)",
|
||||
"str_sprintf": "snprintf() with a stack buffer",
|
||||
"str_snprintf": "snprintf() with a stack buffer",
|
||||
"value_accuracy_to_string": "value_accuracy_to_buf() with a stack buffer",
|
||||
}
|
||||
|
||||
|
||||
@@ -743,24 +747,33 @@ HEAP_ALLOCATING_HELPERS = {
|
||||
# get_mac_address(?!_) ensures we don't match get_mac_address_into_buffer, etc.
|
||||
# CPP_RE_EOL captures rest of line so NOLINT comments are detected
|
||||
r"[^\w]("
|
||||
r"base64_encode(?!_)|"
|
||||
r"format_bin(?!_)|"
|
||||
r"format_hex(?!_)|"
|
||||
r"format_hex_pretty(?!_)|"
|
||||
r"format_mac_address_pretty|"
|
||||
r"get_mac_address_pretty(?!_)|"
|
||||
r"get_mac_address(?!_)|"
|
||||
r"str_lower_case|"
|
||||
r"str_sanitize(?!_)|"
|
||||
r"str_truncate|"
|
||||
r"str_until|"
|
||||
r"str_upper_case|"
|
||||
r"str_snake_case|"
|
||||
r"str_sprintf|"
|
||||
r"str_snprintf"
|
||||
r"str_snprintf|"
|
||||
r"value_accuracy_to_string"
|
||||
r")\s*\(" + CPP_RE_EOL,
|
||||
include=cpp_include,
|
||||
exclude=[
|
||||
# The definitions themselves
|
||||
"esphome/core/alloc_helpers.h",
|
||||
"esphome/core/alloc_helpers.cpp",
|
||||
# Backward compatibility re-exports (remove before 2026.11.0)
|
||||
"esphome/core/helpers.h",
|
||||
"esphome/core/helpers.cpp",
|
||||
# Vendored third-party library
|
||||
"esphome/components/http_request/httplib.h",
|
||||
],
|
||||
)
|
||||
def lint_no_heap_allocating_helpers(fname, match):
|
||||
@@ -812,6 +825,7 @@ def lint_no_sprintf(fname, match):
|
||||
"esphome/components/http_request/httplib.h",
|
||||
# Deprecated helpers that return std::string
|
||||
"esphome/core/helpers.cpp",
|
||||
"esphome/core/alloc_helpers.cpp",
|
||||
# The using declaration itself
|
||||
"esphome/core/helpers.h",
|
||||
# Test fixtures - not production embedded code
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
esphome:
|
||||
name: test
|
||||
|
||||
esp32:
|
||||
board: esp32dev
|
||||
framework:
|
||||
type: arduino
|
||||
|
||||
spi:
|
||||
clk_pin: GPIO18
|
||||
mosi_pin: GPIO23
|
||||
|
||||
display:
|
||||
- platform: ili9xxx
|
||||
id: tft_display
|
||||
model: ST7789V
|
||||
cs_pin: GPIO5
|
||||
dc_pin: GPIO17
|
||||
reset_pin: GPIO16
|
||||
invert_colors: false
|
||||
@@ -0,0 +1,31 @@
|
||||
"""Tests for the ili9xxx component."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def test_ili9xxx_placement_new_uses_model_subclass(
|
||||
generate_main: Callable[[str | Path], str],
|
||||
component_config_path: Callable[[str], Path],
|
||||
) -> None:
|
||||
"""Regression test for ili9xxx picking the right constructor under placement new.
|
||||
|
||||
ili9xxx declares the ID as the base ``ILI9XXXDisplay`` but constructs a
|
||||
model-specific subclass (e.g. ``ILI9XXXST7789V``) via ``MODELS[...].new()``.
|
||||
Pvariable must emit placement new for the subclass — otherwise the base
|
||||
default constructor runs and the panel is left with a null init sequence
|
||||
and 0x0 dimensions, producing a silent blank screen.
|
||||
"""
|
||||
main_cpp = generate_main(component_config_path("ili9xxx_test.yaml"))
|
||||
|
||||
# Storage is sized for the subclass so the full object fits.
|
||||
assert "sizeof(ili9xxx::ILI9XXXST7789V)" in main_cpp
|
||||
assert "alignas(ili9xxx::ILI9XXXST7789V)" in main_cpp
|
||||
# Pointer is declared as the base type for polymorphism.
|
||||
assert "static ili9xxx::ILI9XXXDisplay *const tft_display" in main_cpp
|
||||
# Placement new runs the subclass constructor — this is the actual regression fix.
|
||||
assert "new(tft_display) ili9xxx::ILI9XXXST7789V()" in main_cpp
|
||||
# Base-class default constructor must NOT be used.
|
||||
assert "new(tft_display) ili9xxx::ILI9XXXDisplay()" not in main_cpp
|
||||
@@ -7,6 +7,11 @@ import pytest
|
||||
|
||||
from esphome import config_validation as cv
|
||||
from esphome.components.esp32 import KEY_BOARD, VARIANT_ESP32P4
|
||||
|
||||
# Importing xl9535 registers its pin schema with pins.PIN_SCHEMA_REGISTRY so that
|
||||
# models (e.g. SEEED-RETERMINAL-D1001) that reference xl9535-backed pins in their
|
||||
# defaults can be validated by the mipi_dsi CONFIG_SCHEMA in this test.
|
||||
import esphome.components.xl9535 # noqa: F401
|
||||
from esphome.const import (
|
||||
CONF_DIMENSIONS,
|
||||
CONF_HEIGHT,
|
||||
|
||||
@@ -145,3 +145,19 @@ display:
|
||||
it.filled_rectangle(0, 0, it.get_width(), it.get_height(), Color::WHITE);
|
||||
it.circle(it.get_width() / 2, it.get_height() / 2, 30, Color::BLACK);
|
||||
it.circle(it.get_width() / 2, it.get_height() / 2, 20, Color(255, 0, 0));
|
||||
|
||||
- platform: epaper_spi
|
||||
spi_id: spi_bus
|
||||
model: goodisplay-gdey042t81-4.2
|
||||
cs_pin:
|
||||
allow_other_uses: true
|
||||
number: GPIO5
|
||||
dc_pin:
|
||||
allow_other_uses: true
|
||||
number: GPIO17
|
||||
reset_pin:
|
||||
allow_other_uses: true
|
||||
number: GPIO16
|
||||
busy_pin:
|
||||
allow_other_uses: true
|
||||
number: GPIO4
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "esphome/components/modbus/modbus_helpers.h"
|
||||
|
||||
namespace esphome::modbus::helpers {
|
||||
|
||||
TEST(ModbusHelpersTest, PayloadToNumberRejectsOffsetAtEndOfBuffer) {
|
||||
const std::vector<uint8_t> data{0x12, 0x34};
|
||||
EXPECT_EQ(payload_to_number(data, SensorValueType::U_WORD, 2, 0xFFFFFFFF), 0);
|
||||
}
|
||||
|
||||
TEST(ModbusHelpersTest, PayloadToNumberRejectsTruncatedMultiRegisterValue) {
|
||||
const std::vector<uint8_t> data{0x12, 0x34, 0x56};
|
||||
EXPECT_EQ(payload_to_number(data, SensorValueType::U_DWORD, 0, 0xFFFFFFFF), 0);
|
||||
}
|
||||
|
||||
TEST(ModbusHelpersTest, PayloadToNumberDecodesValidWord) {
|
||||
const std::vector<uint8_t> data{0x12, 0x34};
|
||||
EXPECT_EQ(payload_to_number(data, SensorValueType::U_WORD, 0, 0xFFFFFFFF), 0x1234);
|
||||
}
|
||||
|
||||
} // namespace esphome::modbus::helpers
|
||||
@@ -3,6 +3,22 @@ esphome:
|
||||
then:
|
||||
- rtttl.play: 'siren:d=8,o=5,b=100:d,e,d,e,d,e,d,e'
|
||||
- rtttl.stop
|
||||
# Test all note features: all notes, denominators (1,2,4,8,16,32), sharp (#), octaves (4-7), dotted (.), note gap (c5,c5), pause (p)
|
||||
- rtttl.play: 'special:d=4,o=5,b=120:1c4,2d#5,4e6.,8f#7,16g4,32a5,8a#5,4b6,8h5,c5,c5,8p,2c4'
|
||||
# Different orders of control parameters
|
||||
- rtttl.play: 'test_odb:o=5,d=8,b=100:c'
|
||||
- rtttl.play: 'test_bod:b=100,o=5,d=8:c'
|
||||
- rtttl.play: 'test_bdo:b=100,d=8,o=5:c'
|
||||
- rtttl.play: 'test_obd:o=5,b=100,d=8:c'
|
||||
- rtttl.play: 'test_dbo:d=8,b=100,o=5:c'
|
||||
# Missing parameters (use defaults)
|
||||
- rtttl.play: 'test_no_d:o=5,b=100:c'
|
||||
- rtttl.play: 'test_no_o:d=8,b=100:c'
|
||||
- rtttl.play: 'test_no_b:d=8,o=5:c'
|
||||
- rtttl.play: 'test_only_d:d=8:c'
|
||||
- rtttl.play: 'test_only_o:o=5:c'
|
||||
- rtttl.play: 'test_only_b:b=100:c'
|
||||
- rtttl.play: 'test_empty::c'
|
||||
|
||||
output:
|
||||
- platform: ${output_platform}
|
||||
|
||||
Reference in New Issue
Block a user