mirror of
https://github.com/esphome/esphome.git
synced 2026-01-15 06:27:41 -07:00
Compare commits
8 Commits
sha256_ali
...
cse7766_st
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
325a812202 | ||
|
|
d49c06df35 | ||
|
|
0b676c0daa | ||
|
|
d4bbad9ea2 | ||
|
|
4befd86a96 | ||
|
|
e13743a9c3 | ||
|
|
d3d96afbba | ||
|
|
6e77182523 |
@@ -2,6 +2,7 @@
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include <cstdarg>
|
||||
|
||||
namespace esphome {
|
||||
namespace cse7766 {
|
||||
@@ -9,6 +10,32 @@ namespace cse7766 {
|
||||
static const char *const TAG = "cse7766";
|
||||
static constexpr size_t CSE7766_RAW_DATA_SIZE = 24;
|
||||
|
||||
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE
|
||||
/// @brief Safely append formatted string to buffer.
|
||||
/// @param buf Destination buffer (must be non-null)
|
||||
/// @param size Total buffer size in bytes
|
||||
/// @param pos Current write position (0 to size-1 for valid positions, size means full)
|
||||
/// @param fmt printf-style format string
|
||||
/// @return New write position: pos + chars_written, capped at size when buffer is full.
|
||||
/// Returns size (not size-1) when full because vsnprintf already wrote the null
|
||||
/// terminator at buf[size-1]. Returning size signals "no room for more content".
|
||||
/// On encoding error, returns pos unchanged (no write occurred).
|
||||
__attribute__((format(printf, 4, 5))) static size_t buf_append(char *buf, size_t size, size_t pos, const char *fmt,
|
||||
...) {
|
||||
if (pos >= size) {
|
||||
return size;
|
||||
}
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
int written = vsnprintf(buf + pos, size - pos, fmt, args);
|
||||
va_end(args);
|
||||
if (written < 0) {
|
||||
return pos; // encoding error
|
||||
}
|
||||
return std::min(pos + static_cast<size_t>(written), size);
|
||||
}
|
||||
#endif
|
||||
|
||||
void CSE7766Component::loop() {
|
||||
const uint32_t now = App.get_loop_component_start_time();
|
||||
if (now - this->last_transmission_ >= 500) {
|
||||
@@ -207,20 +234,23 @@ void CSE7766Component::parse_data_() {
|
||||
|
||||
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE
|
||||
{
|
||||
std::string buf = "Parsed:";
|
||||
// Buffer: 7 + 15 + 33 + 15 + 25 = 95 chars max + null, rounded to 128 for safety margin.
|
||||
// Float sizes with %.4f can be up to 11 chars for large values (e.g., 999999.9999).
|
||||
char buf[128];
|
||||
size_t pos = buf_append(buf, sizeof(buf), 0, "Parsed:");
|
||||
if (have_voltage) {
|
||||
buf += str_sprintf(" V=%fV", voltage);
|
||||
pos = buf_append(buf, sizeof(buf), pos, " V=%.4fV", voltage);
|
||||
}
|
||||
if (have_current) {
|
||||
buf += str_sprintf(" I=%fmA (~%fmA)", current * 1000.0f, calculated_current * 1000.0f);
|
||||
pos = buf_append(buf, sizeof(buf), pos, " I=%.4fmA (~%.4fmA)", current * 1000.0f, calculated_current * 1000.0f);
|
||||
}
|
||||
if (have_power) {
|
||||
buf += str_sprintf(" P=%fW", power);
|
||||
pos = buf_append(buf, sizeof(buf), pos, " P=%.4fW", power);
|
||||
}
|
||||
if (energy != 0.0f) {
|
||||
buf += str_sprintf(" E=%fkWh (%u)", energy, cf_pulses);
|
||||
buf_append(buf, sizeof(buf), pos, " E=%.4fkWh (%u)", energy, cf_pulses);
|
||||
}
|
||||
ESP_LOGVV(TAG, "%s", buf.c_str());
|
||||
ESP_LOGVV(TAG, "%s", buf);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -294,7 +294,8 @@ bool Esp32HostedUpdate::stream_firmware_to_coprocessor_() {
|
||||
}
|
||||
|
||||
// Stream firmware to coprocessor while computing SHA256
|
||||
sha256::SHA256 hasher;
|
||||
// Hardware SHA acceleration requires 32-byte alignment on some chips (ESP32-S3 with IDF 5.5.x+)
|
||||
alignas(32) sha256::SHA256 hasher;
|
||||
hasher.init();
|
||||
|
||||
uint8_t buffer[CHUNK_SIZE];
|
||||
@@ -351,7 +352,8 @@ bool Esp32HostedUpdate::write_embedded_firmware_to_coprocessor_() {
|
||||
}
|
||||
|
||||
// Verify SHA256 before writing
|
||||
sha256::SHA256 hasher;
|
||||
// Hardware SHA acceleration requires 32-byte alignment on some chips (ESP32-S3 with IDF 5.5.x+)
|
||||
alignas(32) sha256::SHA256 hasher;
|
||||
hasher.init();
|
||||
hasher.add(this->firmware_data_, this->firmware_size_);
|
||||
hasher.calculate();
|
||||
|
||||
@@ -563,9 +563,11 @@ bool ESPHomeOTAComponent::handle_auth_send_() {
|
||||
// [1+hex_size...1+2*hex_size-1]: cnonce (hex_size bytes) - client's nonce
|
||||
// [1+2*hex_size...1+3*hex_size-1]: response (hex_size bytes) - client's hash
|
||||
|
||||
// CRITICAL ESP32-S2/S3 HARDWARE SHA ACCELERATION: Hash object must stay in same stack frame
|
||||
// CRITICAL ESP32-S3 HARDWARE SHA ACCELERATION: Hash object must stay in same stack frame
|
||||
// (no passing to other functions). All hash operations must happen in this function.
|
||||
sha256::SHA256 hasher;
|
||||
// NOTE: On ESP32-S3 with IDF 5.5.x, the SHA256 context must be properly aligned for
|
||||
// hardware SHA acceleration DMA operations.
|
||||
alignas(32) sha256::SHA256 hasher;
|
||||
|
||||
const size_t hex_size = hasher.get_size() * 2;
|
||||
const size_t nonce_len = hasher.get_size() / 4;
|
||||
@@ -637,9 +639,11 @@ bool ESPHomeOTAComponent::handle_auth_read_() {
|
||||
const char *cnonce = nonce + hex_size;
|
||||
const char *response = cnonce + hex_size;
|
||||
|
||||
// CRITICAL ESP32-S2/S3 HARDWARE SHA ACCELERATION: Hash object must stay in same stack frame
|
||||
// CRITICAL ESP32-S3 HARDWARE SHA ACCELERATION: Hash object must stay in same stack frame
|
||||
// (no passing to other functions). All hash operations must happen in this function.
|
||||
sha256::SHA256 hasher;
|
||||
// NOTE: On ESP32-S3 with IDF 5.5.x, the SHA256 context must be properly aligned for
|
||||
// hardware SHA acceleration DMA operations.
|
||||
alignas(32) sha256::SHA256 hasher;
|
||||
|
||||
hasher.init();
|
||||
hasher.add(this->password_.c_str(), this->password_.length());
|
||||
|
||||
@@ -665,10 +665,15 @@ async def write_image(config, all_frames=False):
|
||||
if is_svg_file(path):
|
||||
import resvg_py
|
||||
|
||||
resize = resize or (None, None)
|
||||
image_data = resvg_py.svg_to_bytes(
|
||||
svg_path=str(path), width=resize[0], height=resize[1], dpi=100
|
||||
)
|
||||
if resize:
|
||||
width, height = resize
|
||||
# resvg-py allows rendering by width/height directly
|
||||
image_data = resvg_py.svg_to_bytes(
|
||||
svg_path=str(path), width=int(width), height=int(height)
|
||||
)
|
||||
else:
|
||||
# Default size
|
||||
image_data = resvg_py.svg_to_bytes(svg_path=str(path))
|
||||
|
||||
# Convert bytes to Pillow Image
|
||||
image = Image.open(io.BytesIO(image_data))
|
||||
|
||||
@@ -10,24 +10,26 @@ namespace esphome::sha256 {
|
||||
|
||||
#if defined(USE_ESP32) || defined(USE_LIBRETINY)
|
||||
|
||||
// CRITICAL ESP32 HARDWARE SHA ACCELERATION REQUIREMENTS (IDF 5.5.x):
|
||||
// CRITICAL ESP32-S3 HARDWARE SHA ACCELERATION REQUIREMENTS (IDF 5.5.x):
|
||||
//
|
||||
// ESP32 variants (except original ESP32) use DMA-based hardware SHA acceleration that requires
|
||||
// 32-byte aligned digest buffers. This is handled automatically via HashBase::digest_ which has
|
||||
// alignas(32) on these platforms. Two additional constraints apply:
|
||||
// The ESP32-S3 uses hardware DMA for SHA acceleration. The mbedtls_sha256_context structure contains
|
||||
// internal state that the DMA engine references. This imposes three critical constraints:
|
||||
//
|
||||
// 1. NO VARIABLE LENGTH ARRAYS (VLAs): VLAs corrupt the stack layout, causing the DMA engine to
|
||||
// 1. ALIGNMENT: The SHA256 object MUST be declared with `alignas(32)` for proper DMA alignment.
|
||||
// Without this, the DMA engine may crash with an abort in sha_hal_read_digest().
|
||||
//
|
||||
// 2. NO VARIABLE LENGTH ARRAYS (VLAs): VLAs corrupt the stack layout, causing the DMA engine to
|
||||
// write to incorrect memory locations. This results in null pointer dereferences and crashes.
|
||||
// ALWAYS use fixed-size arrays (e.g., char buf[65], not char buf[size+1]).
|
||||
//
|
||||
// 2. SAME STACK FRAME ONLY: The SHA256 object must be created and used entirely within the same
|
||||
// 3. SAME STACK FRAME ONLY: The SHA256 object must be created and used entirely within the same
|
||||
// function. NEVER pass the SHA256 object or HashBase pointer to another function. When the stack
|
||||
// frame changes (function call/return), the DMA references become invalid and will produce
|
||||
// truncated hash output (20 bytes instead of 32) or corrupt memory.
|
||||
//
|
||||
// CORRECT USAGE:
|
||||
// void my_function() {
|
||||
// sha256::SHA256 hasher;
|
||||
// alignas(32) sha256::SHA256 hasher; // Created locally with proper alignment
|
||||
// hasher.init();
|
||||
// hasher.add(data, len); // Any size, no chunking needed
|
||||
// hasher.calculate();
|
||||
@@ -35,9 +37,9 @@ namespace esphome::sha256 {
|
||||
// // hasher destroyed when function returns
|
||||
// }
|
||||
//
|
||||
// INCORRECT USAGE (WILL FAIL):
|
||||
// INCORRECT USAGE (WILL FAIL ON ESP32-S3):
|
||||
// void my_function() {
|
||||
// sha256::SHA256 hasher;
|
||||
// sha256::SHA256 hasher; // WRONG: Missing alignas(32)
|
||||
// helper(&hasher); // WRONG: Passed to different stack frame
|
||||
// }
|
||||
// void helper(HashBase *h) {
|
||||
|
||||
@@ -24,14 +24,13 @@ namespace esphome::sha256 {
|
||||
|
||||
/// SHA256 hash implementation.
|
||||
///
|
||||
/// CRITICAL for ESP32 variants (except original) with IDF 5.5.x hardware SHA acceleration:
|
||||
/// 1. The object MUST stay in the same stack frame (no passing to other functions)
|
||||
/// 2. NO Variable Length Arrays (VLAs) in the same function
|
||||
///
|
||||
/// Note: Alignment is handled automatically via the HashBase::digest_ member.
|
||||
/// CRITICAL for ESP32-S3 with IDF 5.5.x hardware SHA acceleration:
|
||||
/// 1. SHA256 objects MUST be declared with `alignas(32)` for proper DMA alignment
|
||||
/// 2. The object MUST stay in the same stack frame (no passing to other functions)
|
||||
/// 3. NO Variable Length Arrays (VLAs) in the same function
|
||||
///
|
||||
/// Example usage:
|
||||
/// sha256::SHA256 hasher;
|
||||
/// alignas(32) sha256::SHA256 hasher;
|
||||
/// hasher.init();
|
||||
/// hasher.add(data, len);
|
||||
/// hasher.calculate();
|
||||
|
||||
@@ -44,13 +44,7 @@ class HashBase {
|
||||
virtual size_t get_size() const = 0;
|
||||
|
||||
protected:
|
||||
// ESP32 variants with DMA-based hardware SHA (all except original ESP32) require 32-byte aligned buffers.
|
||||
// Original ESP32 uses a different hardware SHA implementation without DMA alignment requirements.
|
||||
// Other platforms (ESP8266, RP2040, LibreTiny) use software SHA and don't need alignment.
|
||||
#if defined(USE_ESP32) && !defined(USE_ESP32_VARIANT_ESP32)
|
||||
alignas(32)
|
||||
#endif
|
||||
uint8_t digest_[32]; // Storage sized for max(MD5=16, SHA256=32) bytes
|
||||
uint8_t digest_[32]; // Storage sized for max(MD5=16, SHA256=32) bytes
|
||||
};
|
||||
|
||||
} // namespace esphome
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="10mm" height="10mm" viewBox="0 0 100 100">
|
||||
<rect x="0" y="0" width="100" height="100" fill="#00FF00"/>
|
||||
<circle cx="50" cy="50" r="30" fill="#0000FF"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 248 B |
@@ -5,21 +5,17 @@ from __future__ import annotations
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from esphome import config_validation as cv
|
||||
from esphome.components.image import (
|
||||
CONF_INVERT_ALPHA,
|
||||
CONF_OPAQUE,
|
||||
CONF_TRANSPARENCY,
|
||||
CONFIG_SCHEMA,
|
||||
get_all_image_metadata,
|
||||
get_image_metadata,
|
||||
write_image,
|
||||
)
|
||||
from esphome.const import CONF_DITHER, CONF_FILE, CONF_ID, CONF_RAW_DATA_ID, CONF_TYPE
|
||||
from esphome.const import CONF_ID, CONF_RAW_DATA_ID, CONF_TYPE
|
||||
from esphome.core import CORE
|
||||
|
||||
|
||||
@@ -354,52 +350,3 @@ def test_get_all_image_metadata_empty() -> None:
|
||||
"get_all_image_metadata should always return a dict"
|
||||
)
|
||||
# Length could be 0 or more depending on what's in CORE at test time
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_progmem_array():
|
||||
"""Mock progmem_array to avoid needing a proper ID object in tests."""
|
||||
with patch("esphome.components.image.cg.progmem_array") as mock_progmem:
|
||||
mock_progmem.return_value = MagicMock()
|
||||
yield mock_progmem
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_svg_with_mm_dimensions_succeeds(
|
||||
component_config_path: Callable[[str], Path],
|
||||
mock_progmem_array: MagicMock,
|
||||
) -> None:
|
||||
"""Test that SVG files with dimensions in mm are successfully processed."""
|
||||
# Create a config for write_image without CONF_RESIZE
|
||||
config = {
|
||||
CONF_FILE: component_config_path("mm_dimensions.svg"),
|
||||
CONF_TYPE: "BINARY",
|
||||
CONF_TRANSPARENCY: CONF_OPAQUE,
|
||||
CONF_DITHER: "NONE",
|
||||
CONF_INVERT_ALPHA: False,
|
||||
CONF_RAW_DATA_ID: "test_raw_data_id",
|
||||
}
|
||||
|
||||
# This should succeed without raising an error
|
||||
result = await write_image(config)
|
||||
|
||||
# Verify that write_image returns the expected tuple
|
||||
assert isinstance(result, tuple), "write_image should return a tuple"
|
||||
assert len(result) == 6, "write_image should return 6 values"
|
||||
|
||||
prog_arr, width, height, image_type, trans_value, frame_count = result
|
||||
|
||||
# Verify the dimensions are positive integers
|
||||
# At 100 DPI, 10mm = ~39 pixels (10mm * 100dpi / 25.4mm_per_inch)
|
||||
assert isinstance(width, int), "Width should be an integer"
|
||||
assert isinstance(height, int), "Height should be an integer"
|
||||
assert width > 0, "Width should be positive"
|
||||
assert height > 0, "Height should be positive"
|
||||
assert frame_count == 1, "Single image should have frame_count of 1"
|
||||
# Verify we got reasonable dimensions from the mm-based SVG
|
||||
assert 30 < width < 50, (
|
||||
f"Width should be around 39 pixels for 10mm at 100dpi, got {width}"
|
||||
)
|
||||
assert 30 < height < 50, (
|
||||
f"Height should be around 39 pixels for 10mm at 100dpi, got {height}"
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user