[cc1101] Keep constant send_packet payloads in shared flash tables (#20264)

This commit is contained in:
J. Nick Koston
2026-10-07 10:54:56 -10:00
committed by GitHub
parent 24fc86c2c4
commit 12c03d539a
6 changed files with 107 additions and 36 deletions
+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
@@ -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)