Compare commits

...
14 changed files with 152 additions and 57 deletions

No files matched your search

@@ -15,6 +15,8 @@ on:
# base branch checkout, so fork code never runs with the token.
pull_request_target:
types: [opened, synchronize, reopened]
branches:
- dev
paths:
- requirements_dev.txt
- requirements_test.txt
+1 -1
View File
@@ -48,7 +48,7 @@ PROJECT_NAME = ESPHome
# could be handy for archiving the generated documentation or if some version
# control system is used.
PROJECT_NUMBER = 2026.10.0b1
PROJECT_NUMBER = 2026.11.0-dev
# Using the PROJECT_BRIEF tag one can provide an optional one line description
# for a project that appears at the top of each page and should give viewer a
+18 -2
View File
@@ -383,6 +383,22 @@ class ProtoEncode {
encode_field_raw(uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM, uint32_t field_id, uint32_t type) {
return encode_varint_raw(pos PROTO_ENCODE_DEBUG_ARG, proto_tag(field_id, type));
}
/// Write a field key through the local cursor. Sub-message fields commonly have a 2-byte tag.
[[nodiscard]] static inline uint8_t *ESPHOME_ALWAYS_INLINE
write_tag(uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM, uint32_t field_id, uint32_t type) {
uint32_t tag = proto_tag(field_id, type);
#ifdef ESPHOME_DEBUG_API
return encode_varint_raw(pos PROTO_ENCODE_DEBUG_ARG, tag);
#else
uint8_t *__restrict__ p = pos;
while (tag > 0x7F) {
*p++ = static_cast<uint8_t>(tag | 0x80);
tag >>= 7;
}
*p++ = static_cast<uint8_t>(tag);
return p;
#endif
}
/// Write a single precomputed tag byte. Tag must be < 128.
[[nodiscard]] static inline uint8_t *ESPHOME_ALWAYS_INLINE
write_raw_byte(uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM, uint8_t b) {
@@ -568,7 +584,7 @@ class ProtoEncode {
template<typename T>
[[nodiscard]] static inline uint8_t *encode_sub_message(uint8_t *__restrict__ pos PROTO_ENCODE_DEBUG_PARAM,
uint32_t field_id, const T &value) {
pos = encode_field_raw(pos PROTO_ENCODE_DEBUG_ARG, field_id, 2);
pos = write_tag(pos PROTO_ENCODE_DEBUG_ARG, field_id, WIRE_TYPE_LENGTH_DELIMITED);
return encode_sub_message_body(pos PROTO_ENCODE_DEBUG_ARG, &value, &T::encode_msg);
}
/// Singular sub-message field, skipped when it encodes to nothing.
@@ -578,7 +594,7 @@ class ProtoEncode {
uint32_t nested_size = T::calc_size_msg(&value);
if (nested_size == 0)
return pos;
pos = encode_field_raw(pos PROTO_ENCODE_DEBUG_ARG, field_id, 2);
pos = write_tag(pos PROTO_ENCODE_DEBUG_ARG, field_id, WIRE_TYPE_LENGTH_DELIMITED);
#ifdef ESPHOME_DEBUG_API
uint8_t *end = encode_sized_sub_message_body(pos PROTO_ENCODE_DEBUG_ARG, nested_size, &value, &T::encode_msg);
proto_check_sub_message_size(field_id, nested_size, pos, end);
+11 -13
View File
@@ -13,7 +13,6 @@ from esphome.const import (
CONF_VALUE,
CONF_WAIT_TIME,
)
from esphome.core import ID
CODEOWNERS = ["@lygris", "@gabest11"]
DEPENDENCIES = ["spi"]
@@ -384,7 +383,10 @@ def validate_raw_data(value):
SEND_PACKET_ACTION_SCHEMA = cv.maybe_simple_value(
{
cv.GenerateID(): cv.use_id(CC1101Component),
cv.Required(CONF_DATA): cv.templatable(validate_raw_data),
# TX FIFO size
cv.Required(CONF_DATA): cv.templatable(
cv.All(validate_raw_data, cv.Length(max=64))
),
},
key=CONF_DATA,
)
@@ -399,17 +401,13 @@ SEND_PACKET_ACTION_SCHEMA = cv.maybe_simple_value(
async def send_packet_action_to_code(config, action_id, template_arg, args):
var = cg.new_Pvariable(action_id, template_arg)
await cg.register_parented(var, config[CONF_ID])
data = config[CONF_DATA]
if isinstance(data, bytes):
data = list(data)
if cg.is_template(data):
templ = await cg.templatable(data, args, cg.std_vector.template(cg.uint8))
cg.add(var.set_data_template(templ))
else:
# Generate static array in flash to avoid RAM copy
arr_id = ID(f"{action_id}_data", is_declaration=True, type=cg.uint8)
arr = cg.static_const_array(arr_id, cg.ArrayInitializer(*data))
cg.add(var.set_data_static(arr, len(data)))
await automation.templatable_bytes(
config[CONF_DATA],
args,
var.set_data_template,
var.set_data_static,
"cc1101_data",
)
return var
+24 -4
View File
@@ -3,10 +3,16 @@
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#include <cmath>
#include <cstring>
#ifdef USE_ESP32
#include <esp_memory_utils.h>
#endif
namespace esphome::cc1101 {
static const char *const TAG = "cc1101";
static constexpr size_t TX_FIFO_SIZE = 64;
static void split_float(float value, int mbits, uint8_t &e, uint32_t &m) {
int e_tmp;
@@ -379,18 +385,32 @@ void CC1101Component::read_(Register reg, uint8_t *buffer, size_t length) {
this->disable();
}
CC1101Error CC1101Component::transmit_packet(const std::vector<uint8_t> &packet) {
CC1101Error CC1101Component::transmit_packet(const uint8_t *data, size_t len) {
if (this->state_.PKT_FORMAT != static_cast<uint8_t>(PacketFormat::PACKET_FORMAT_FIFO)) {
return CC1101Error::PARAMS;
}
const bool variable = this->state_.LENGTH_CONFIG == static_cast<uint8_t>(LengthConfig::LENGTH_CONFIG_VARIABLE);
// In variable length mode the FIFO also holds the length byte
if (len + (variable ? 1 : 0) > TX_FIFO_SIZE) {
ESP_LOGE(TAG, "Packet of %u bytes does not fit the TX FIFO", static_cast<unsigned>(len));
return CC1101Error::PARAMS;
}
// Write packet
this->enter_idle_();
this->strobe_(Command::FTX);
if (this->state_.LENGTH_CONFIG == static_cast<uint8_t>(LengthConfig::LENGTH_CONFIG_VARIABLE)) {
this->write_(Register::FIFO, static_cast<uint8_t>(packet.size()));
if (variable) {
this->write_(Register::FIFO, static_cast<uint8_t>(len));
}
this->write_(Register::FIFO, packet.data(), packet.size());
#ifdef USE_ESP32
// SPI DMA cannot read flash or unaligned buffers; stage them here so the driver does not allocate a copy
alignas(4) uint8_t staged[TX_FIFO_SIZE];
if (len != 0 && (!esp_ptr_dma_capable(data) || (reinterpret_cast<uintptr_t>(data) & 3) != 0)) {
std::memcpy(staged, data, len);
data = staged;
}
#endif
this->write_(Register::FIFO, data, len);
// Calibrate PLL
if (!this->enter_calibrated_(State::FSTXON, Command::FSTXON)) {
+6 -19
View File
@@ -89,7 +89,10 @@ class CC1101Component final : public Component,
void set_whitening(bool value);
// Packet mode operations
CC1101Error transmit_packet(const std::vector<uint8_t> &packet);
CC1101Error transmit_packet(const std::vector<uint8_t> &packet) {
return this->transmit_packet(packet.data(), packet.size());
}
CC1101Error transmit_packet(const uint8_t *data, size_t len);
void register_listener(CC1101Listener *listener) { this->listeners_.push_back(listener); }
Trigger<std::vector<uint8_t>, float, float, uint8_t> *get_packet_trigger() { return &this->packet_trigger_; }
@@ -131,27 +134,11 @@ class CC1101Component final : public Component,
// Action Wrappers
template<typename... Ts> class SendPacketAction final : public Action<Ts...>, public Parented<CC1101Component> {
public:
void set_data_template(std::function<std::vector<uint8_t>(Ts...)> func) { this->data_func_ = func; }
void set_data_static(const uint8_t *data, size_t len) {
this->data_static_ = data;
this->data_static_len_ = len;
}
TEMPLATABLE_BYTES(data)
void play(const Ts &...x) override {
if (this->data_func_) {
auto data = this->data_func_(x...);
this->parent_->transmit_packet(data);
} else if (this->data_static_ != nullptr) {
std::vector<uint8_t> data(this->data_static_, this->data_static_ + this->data_static_len_);
this->parent_->transmit_packet(data);
}
this->data_.visit([this](const uint8_t *data, size_t len) { this->parent_->transmit_packet(data, len); }, x...);
}
protected:
std::function<std::vector<uint8_t>(Ts...)> data_func_{};
const uint8_t *data_static_{nullptr};
size_t data_static_len_{0};
};
} // namespace esphome::cc1101
@@ -15,9 +15,12 @@ static const uint8_t STARTUP_SEQUENCE[][2] PROGMEM = {
{0x60, 0x01}, // ADR_PIN_CTRL: output
{0x7D, 0x11},
{0x7E, 0xFF},
{0x00, 0x01},
{0x00, 0x01}, // Page 1
{0x51, 0x05},
{0x00, 0x00},
{0x00, 0x00}, // Page 0
// Additional register configuration
{0x53, 0x60}, // ANA_CTRL: for high audio performance use 175kHz bandwidth with Fsw=768kHz
};
static const LogString *model_name() { return LOG_STR("TAS5805M"); }
+13 -14
View File
@@ -4,13 +4,11 @@
namespace esphome::tas58xx {
// Remainder of the startup sequence, run after the reset. Register 0x00 selects the page.
// All writes except starting and last three register writes are exactly the startup sequence from TI PurePath Console.
// Registers 0x46, 0x7D, 0x7E, page 1 register 0x51 and Page 2 registers 0x1D, 0x19 are not documented in the datasheet.
// clang-format off
// Remainder of the startup sequence, run after the reset in TAS58xx::init_(). Register 0x00 selects the page.
// Based on the TI PurePath Console (PPC) export for "TAS5825M 2-Band DRC&AGL 2.0 96k".
// Registers 0x46, 0x7D, 0x7E, page 1 register 0x51 and page 2 registers 0x1D, 0x19 are not documented in datasheet.
static const uint8_t STARTUP_SEQUENCE[][2] PROGMEM = {
{0x03, 0x00}, // Deep Sleep
{0x03, 0x00}, // DEVICE_CTRL_2: deep sleep, the state the PPC sequence starts from
{0x7D, 0x11},
{0x7E, 0xFF},
{0x00, 0x01}, // Page 1
@@ -20,15 +18,16 @@ static const uint8_t STARTUP_SEQUENCE[][2] PROGMEM = {
{0x19, 0x80},
{0x00, 0x00}, // Page 0
{0x46, 0x11},
{0x02, 0x00}, // DEVICE_CTRL_1 - BD MODE, Damp to PBL MODE
{0x53, 0x01}, // ANA_CTRL PWM Phase Control - in phase
{0x54, 0x00}, // AGAIN 0dB
{0x03, 0x02}, // Hi-Z
{0x61, 0x0B}, // GPIO0_SEL - GPIO0 as FAULTZ output
{0x60, 0x01}, // GPIO0_OE - Enable GPIO0 as output
{0x77, 0x07}, // CBC_CONTROL enabling CBC function for warnings and faults
// PPC also writes 0x02 (DEVICE_CTRL_1) and 0x54 (AGAIN) here; init_() writes both from YAML
{0x53, 0x01}, // ANA_CTRL: in phase, 100kHz bandwidth for high audio performance with Fsw=384kHz
{0x03, 0x02}, // DEVICE_CTRL_2: Hi-Z
// Additional register configuration
// GPIO1 is a dedicated pin on TAS5825M (separate from ADR), used as FAULTZ
{0x62, 0x0B}, // GPIO1_SEL: GPIO1 as FAULTZ
{0x60, 0x02}, // GPIO_CTRL: GPIO1 is output
{0x77, 0x07}, // CBC_CONTROL: enable cycle-by-cycle current limit for warnings and faults
};
// clang-format on
static const LogString *model_name() { return LOG_STR("TAS5825M"); }
+1 -1
View File
@@ -4,7 +4,7 @@ from enum import Enum
from esphome.enum import StrEnum
__version__ = "2026.10.0b1"
__version__ = "2026.11.0-dev"
ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
VALID_SUBSTITUTIONS_CHARACTERS = (
+4
View File
@@ -405,10 +405,14 @@ template<typename... X> class TemplatableValue<std::string, X...> {
/// Check if this holds a static string (const char* stored without allocation)
/// The pointer is always directly readable (RAM or flash-mapped).
/// Returns false for FLASH_STRING (PROGMEM on ESP8266, requires _P functions).
// Remove before 2027.4.0
ESPDEPRECATED("Read the value with ref_or_copy_to() instead. Removed in 2027.4.0", "2026.10.0")
bool is_static_string() const { return this->type_ == STATIC_STRING; }
/// Get the static string pointer (only valid if is_static_string() returns true)
/// The pointer is always directly readable — FLASH_STRING uses a separate type.
// Remove before 2027.4.0
ESPDEPRECATED("Use ref_or_copy_to() instead. Removed in 2027.4.0", "2026.10.0")
const char *get_static_string() const { return this->static_str_; }
/// Check if the string value is empty without allocating.
+1 -1
View File
@@ -12,7 +12,7 @@ pyserial==3.5
platformio==6.2.0
esptool==5.4.0
click==8.5.0
aioesphomeapi==46.6.0
aioesphomeapi==46.8.0
aiohappyeyeballs==2.7.1 # Happy Eyeballs for requests downloads; already pulled in by aioesphomeapi
zeroconf==0.151.5
puremagic==2.2.0
Whitespace-only changes.
@@ -0,0 +1,25 @@
---
esphome:
name: test
on_boot:
then:
- cc1101.send_packet: [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08]
- cc1101.send_packet:
data: [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08]
- cc1101.send_packet: !lambda return {0x09};
esp32:
board: esp32dev
spi:
clk_pin: 18
mosi_pin: 23
miso_pin: 19
cc1101:
id: transceiver
cs_pin: 5
gdo0_pin: 4
frequency: 433.92MHz
packet_mode: true
packet_length: 8
@@ -0,0 +1,41 @@
"""Tests for cc1101 constant payloads in shared PROGMEM tables."""
from collections.abc import Callable
from pathlib import Path
import re
import pytest
from esphome.components.cc1101 import SEND_PACKET_ACTION_SCHEMA
import esphome.config_validation as cv
def test_constant_payloads_share_progmem_tables(
generate_main: Callable[[str | Path], str],
component_config_path: Callable[[str], Path],
) -> None:
"""Equal send_packet payloads share one table; lambdas stay templates."""
main_cpp = generate_main(component_config_path("payload_tables.yaml"))
tables = re.findall(
r"static constexpr uint8_t (\w+)\[\] PROGMEM = (\{[^}]*\});", main_cpp
)
assert [v for _, v in tables] == [
"{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}"
]
assert main_cpp.count(f"set_data_static({tables[0][0]}, 8);") == 2
assert "set_data_template(" in main_cpp
@pytest.mark.parametrize("size", [64, 65])
@pytest.mark.parametrize("make", [lambda n: [0x01] * n, lambda n: "a" * n])
def test_send_packet_tx_fifo_limit(
size: int, make: Callable[[int], list[int] | str]
) -> None:
"""Payloads up to the 64 byte TX FIFO are accepted, longer ones rejected."""
config = {"id": "transceiver", "data": make(size)}
if size <= 64:
assert len(SEND_PACKET_ACTION_SCHEMA(config)["data"]) == size
else:
with pytest.raises(cv.Invalid, match="at most 64"):
SEND_PACKET_ACTION_SCHEMA(config)