mirror of
https://github.com/esphome/esphome.git
synced 2026-10-08 12:04:03 +00:00
[cc1101] Keep constant send_packet payloads in shared flash tables (#20264)
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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)
|
||||
Reference in New Issue
Block a user