Merge branch 'dev' into api_avoid_copies

This commit is contained in:
J. Nick Koston
2026-01-02 20:19:11 -10:00
committed by GitHub
12 changed files with 153 additions and 19 deletions

View File

@@ -2425,7 +2425,7 @@ message ZWaveProxyFrame {
option (ifdef) = "USE_ZWAVE_PROXY";
option (no_delay) = true;
bytes data = 1 [(pointer_to_buffer) = true];
bytes data = 1;
}
enum ZWaveProxyRequestType {
@@ -2439,5 +2439,5 @@ message ZWaveProxyRequest {
option (ifdef) = "USE_ZWAVE_PROXY";
ZWaveProxyRequestType type = 1;
bytes data = 2 [(pointer_to_buffer) = true];
bytes data = 2;
}

View File

@@ -1046,7 +1046,7 @@ class SubscribeLogsRequest final : public ProtoDecodableMessage {
class SubscribeLogsResponse final : public ProtoMessage {
public:
static constexpr uint8_t MESSAGE_TYPE = 29;
static constexpr uint8_t ESTIMATED_SIZE = 11;
static constexpr uint8_t ESTIMATED_SIZE = 21;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "subscribe_logs_response"; }
#endif
@@ -1069,7 +1069,7 @@ class SubscribeLogsResponse final : public ProtoMessage {
class NoiseEncryptionSetKeyRequest final : public ProtoDecodableMessage {
public:
static constexpr uint8_t MESSAGE_TYPE = 124;
static constexpr uint8_t ESTIMATED_SIZE = 9;
static constexpr uint8_t ESTIMATED_SIZE = 19;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "noise_encryption_set_key_request"; }
#endif
@@ -1161,7 +1161,7 @@ class HomeassistantActionRequest final : public ProtoMessage {
class HomeassistantActionResponse final : public ProtoDecodableMessage {
public:
static constexpr uint8_t MESSAGE_TYPE = 130;
static constexpr uint8_t ESTIMATED_SIZE = 24;
static constexpr uint8_t ESTIMATED_SIZE = 34;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "homeassistant_action_response"; }
#endif
@@ -1388,7 +1388,7 @@ class ListEntitiesCameraResponse final : public InfoResponseProtoMessage {
class CameraImageResponse final : public StateResponseProtoMessage {
public:
static constexpr uint8_t MESSAGE_TYPE = 44;
static constexpr uint8_t ESTIMATED_SIZE = 20;
static constexpr uint8_t ESTIMATED_SIZE = 30;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "camera_image_response"; }
#endif
@@ -2123,7 +2123,7 @@ class BluetoothGATTReadRequest final : public ProtoDecodableMessage {
class BluetoothGATTReadResponse final : public ProtoMessage {
public:
static constexpr uint8_t MESSAGE_TYPE = 74;
static constexpr uint8_t ESTIMATED_SIZE = 17;
static constexpr uint8_t ESTIMATED_SIZE = 27;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "bluetooth_gatt_read_response"; }
#endif
@@ -2146,7 +2146,7 @@ class BluetoothGATTReadResponse final : public ProtoMessage {
class BluetoothGATTWriteRequest final : public ProtoDecodableMessage {
public:
static constexpr uint8_t MESSAGE_TYPE = 75;
static constexpr uint8_t ESTIMATED_SIZE = 19;
static constexpr uint8_t ESTIMATED_SIZE = 29;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "bluetooth_gatt_write_request"; }
#endif
@@ -2182,7 +2182,7 @@ class BluetoothGATTReadDescriptorRequest final : public ProtoDecodableMessage {
class BluetoothGATTWriteDescriptorRequest final : public ProtoDecodableMessage {
public:
static constexpr uint8_t MESSAGE_TYPE = 77;
static constexpr uint8_t ESTIMATED_SIZE = 17;
static constexpr uint8_t ESTIMATED_SIZE = 27;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "bluetooth_gatt_write_descriptor_request"; }
#endif
@@ -2218,7 +2218,7 @@ class BluetoothGATTNotifyRequest final : public ProtoDecodableMessage {
class BluetoothGATTNotifyDataResponse final : public ProtoMessage {
public:
static constexpr uint8_t MESSAGE_TYPE = 79;
static constexpr uint8_t ESTIMATED_SIZE = 17;
static constexpr uint8_t ESTIMATED_SIZE = 27;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "bluetooth_gatt_notify_data_response"; }
#endif

View File

@@ -3,7 +3,7 @@ import esphome.codegen as cg
from esphome.components import binary_sensor, esp32_ble, improv_base, output
from esphome.components.esp32_ble import BTLoggers
import esphome.config_validation as cv
from esphome.const import CONF_ID, CONF_ON_STATE, CONF_TRIGGER_ID
from esphome.const import CONF_ID, CONF_ON_START, CONF_ON_STATE, CONF_TRIGGER_ID
AUTO_LOAD = ["esp32_ble_server", "improv_base"]
CODEOWNERS = ["@jesserockz"]
@@ -15,7 +15,6 @@ CONF_BLE_SERVER_ID = "ble_server_id"
CONF_IDENTIFY_DURATION = "identify_duration"
CONF_ON_PROVISIONED = "on_provisioned"
CONF_ON_PROVISIONING = "on_provisioning"
CONF_ON_START = "on_start"
CONF_ON_STOP = "on_stop"
CONF_STATUS_INDICATOR = "status_indicator"
CONF_WIFI_TIMEOUT = "wifi_timeout"

View File

@@ -28,6 +28,7 @@ from .const import (
KEY_ESP8266,
KEY_FLASH_SIZE,
KEY_PIN_INITIAL_STATES,
KEY_WAVEFORM_REQUIRED,
esp8266_ns,
)
from .gpio import PinInitialState, add_pin_initial_states_array
@@ -192,7 +193,12 @@ async def to_code(config):
cg.add_platformio_option(
"extra_scripts",
["pre:testing_mode.py", "pre:exclude_updater.py", "post:post_build.py"],
[
"pre:testing_mode.py",
"pre:exclude_updater.py",
"pre:exclude_waveform.py",
"post:post_build.py",
],
)
conf = config[CONF_FRAMEWORK]
@@ -264,10 +270,24 @@ async def to_code(config):
cg.add_platformio_option("board_build.ldscript", ld_script)
CORE.add_job(add_pin_initial_states_array)
CORE.add_job(finalize_waveform_config)
@coroutine_with_priority(CoroPriority.WORKAROUNDS)
async def finalize_waveform_config() -> None:
"""Add waveform stubs define if waveform is not required.
This runs at WORKAROUNDS priority (-999) to ensure all components
have had a chance to call require_waveform() first.
"""
if not CORE.data.get(KEY_ESP8266, {}).get(KEY_WAVEFORM_REQUIRED, False):
# No component needs waveform - enable stubs and exclude Arduino waveform code
# Use build flag (visible to both C++ code and PlatformIO script)
cg.add_build_flag("-DUSE_ESP8266_WAVEFORM_STUBS")
# Called by writer.py
def copy_files():
def copy_files() -> None:
dir = Path(__file__).parent
post_build_file = dir / "post_build.py.script"
copy_file_if_changed(
@@ -284,3 +304,8 @@ def copy_files():
exclude_updater_file,
CORE.relative_build_path("exclude_updater.py"),
)
exclude_waveform_file = dir / "exclude_waveform.py.script"
copy_file_if_changed(
exclude_waveform_file,
CORE.relative_build_path("exclude_waveform.py"),
)

View File

@@ -1,4 +1,5 @@
import esphome.codegen as cg
from esphome.core import CORE
KEY_ESP8266 = "esp8266"
KEY_BOARD = "board"
@@ -6,6 +7,25 @@ KEY_PIN_INITIAL_STATES = "pin_initial_states"
CONF_RESTORE_FROM_FLASH = "restore_from_flash"
CONF_EARLY_PIN_INIT = "early_pin_init"
KEY_FLASH_SIZE = "flash_size"
KEY_WAVEFORM_REQUIRED = "waveform_required"
# esp8266 namespace is already defined by arduino, manually prefix esphome
esp8266_ns = cg.global_ns.namespace("esphome").namespace("esp8266")
def require_waveform() -> None:
"""Mark that Arduino waveform/PWM support is required.
Call this from components that need the Arduino waveform generator
(startWaveform, stopWaveform, analogWrite, Tone, Servo).
If no component calls this, the waveform code is excluded from the build
to save ~596 bytes of RAM and 464 bytes of flash.
Example:
from esphome.components.esp8266.const import require_waveform
async def to_code(config):
require_waveform()
"""
CORE.data.setdefault(KEY_ESP8266, {})[KEY_WAVEFORM_REQUIRED] = True

View File

@@ -0,0 +1,50 @@
# pylint: disable=E0602
Import("env") # noqa
import os
# Filter out waveform/PWM code from the Arduino core build
# This saves ~596 bytes of RAM and 464 bytes of flash by not
# instantiating the waveform generator state structures (wvfState + pwmState).
#
# The waveform code is used by: analogWrite, Tone, Servo, and direct
# startWaveform/stopWaveform calls. ESPHome's esp8266_pwm component
# calls require_waveform() to keep this code when needed.
#
# When excluded, we provide stub implementations of stopWaveform() and
# _stopPWM() since digitalWrite() calls these unconditionally.
def has_define_flag(env, name):
"""Check if a define exists in the build flags."""
define_flag = f"-D{name}"
# Check BUILD_FLAGS (where ESPHome puts its defines)
for flag in env.get("BUILD_FLAGS", []):
if flag == define_flag or flag.startswith(f"{define_flag}="):
return True
# Also check CPPDEFINES list (parsed defines)
for define in env.get("CPPDEFINES", []):
if isinstance(define, tuple):
if define[0] == name:
return True
elif define == name:
return True
return False
# USE_ESP8266_WAVEFORM_STUBS is defined when no component needs waveform
if has_define_flag(env, "USE_ESP8266_WAVEFORM_STUBS"):
def filter_waveform_from_core(env, node):
"""Filter callback to exclude waveform files from framework build."""
path = node.get_path()
filename = os.path.basename(path)
if filename in (
"core_esp8266_waveform_pwm.cpp",
"core_esp8266_waveform_phase.cpp",
):
print(f"ESPHome: Excluding {filename} from build (waveform not required)")
return None
return node
# Apply the filter to framework sources
env.AddBuildMiddleware(filter_waveform_from_core, "**/cores/esp8266/*.cpp")

View File

@@ -0,0 +1,34 @@
#ifdef USE_ESP8266_WAVEFORM_STUBS
// Stub implementations for Arduino waveform/PWM functions.
//
// When the waveform generator is not needed (no esp8266_pwm component),
// we exclude core_esp8266_waveform_pwm.cpp from the build to save ~596 bytes
// of RAM and 464 bytes of flash.
//
// These stubs satisfy calls from the Arduino GPIO code when the real
// waveform implementation is excluded. They must be in the global namespace
// with C linkage to match the Arduino core function declarations.
#include <cstdint>
// Empty namespace to satisfy linter - actual stubs must be at global scope
namespace esphome::esp8266 {} // namespace esphome::esp8266
extern "C" {
// Called by Arduino GPIO code to stop any waveform on a pin
int stopWaveform(uint8_t pin) {
(void) pin;
return 1; // Success (no waveform to stop)
}
// Called by Arduino GPIO code to stop any PWM on a pin
bool _stopPWM(uint8_t pin) {
(void) pin;
return false; // No PWM was running
}
} // extern "C"
#endif // USE_ESP8266_WAVEFORM_STUBS

View File

@@ -1,6 +1,7 @@
from esphome import automation, pins
import esphome.codegen as cg
from esphome.components import output
from esphome.components.esp8266.const import require_waveform
import esphome.config_validation as cv
from esphome.const import CONF_FREQUENCY, CONF_ID, CONF_NUMBER, CONF_PIN
@@ -34,7 +35,9 @@ CONFIG_SCHEMA = cv.All(
)
async def to_code(config):
async def to_code(config) -> None:
require_waveform()
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await output.register_output(var, config)

View File

@@ -11,6 +11,7 @@ from esphome.const import (
CONF_ON_CLIENT_DISCONNECTED,
CONF_ON_ERROR,
CONF_ON_IDLE,
CONF_ON_START,
CONF_SPEAKER,
)
@@ -24,7 +25,6 @@ CONF_ON_INTENT_END = "on_intent_end"
CONF_ON_INTENT_PROGRESS = "on_intent_progress"
CONF_ON_INTENT_START = "on_intent_start"
CONF_ON_LISTENING = "on_listening"
CONF_ON_START = "on_start"
CONF_ON_STT_END = "on_stt_end"
CONF_ON_STT_VAD_END = "on_stt_vad_end"
CONF_ON_STT_VAD_START = "on_stt_vad_start"

View File

@@ -710,6 +710,7 @@ CONF_ON_RELEASE = "on_release"
CONF_ON_RESPONSE = "on_response"
CONF_ON_SHUTDOWN = "on_shutdown"
CONF_ON_SPEED_SET = "on_speed_set"
CONF_ON_START = "on_start"
CONF_ON_STATE = "on_state"
CONF_ON_SUCCESS = "on_success"
CONF_ON_TAG = "on_tag"

View File

@@ -362,12 +362,12 @@ def create_field_type_info(
# Traditional fixed array approach with copy (takes priority)
return FixedArrayBytesType(field, fixed_size)
# For SOURCE_CLIENT only messages (decode but no encode), use pointer
# For messages that decode (SOURCE_CLIENT or SOURCE_BOTH), use pointer
# for zero-copy access to the receive buffer
if needs_decode and not needs_encode:
if needs_decode:
return PointerToBytesBufferType(field, None)
# For SOURCE_BOTH/SOURCE_SERVER, explicit annotation is still needed
# For SOURCE_SERVER (encode only), explicit annotation is still needed
if get_field_opt(field, pb.pointer_to_buffer, False):
return PointerToBytesBufferType(field, None)

View File

@@ -552,6 +552,8 @@ def convert_path_to_relative(abspath, current):
exclude=[
"esphome/components/libretiny/generate_components.py",
"esphome/components/web_server/__init__.py",
# const.py has absolute import in docstring example for external components
"esphome/components/esp8266/const.py",
],
)
def lint_relative_py_import(fname: Path, line, col, content):