mirror of
https://github.com/esphome/esphome.git
synced 2026-10-27 00:12:11 +00:00
Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
43c18c9e1c | ||
|
|
96df32024d | ||
|
|
83b38d9cdd | ||
|
|
ebef8390aa | ||
|
|
ad1e5ef9d0 |
No files matched your search
@@ -601,7 +601,7 @@ async def to_code(config: ConfigType) -> None:
|
||||
|
||||
if (encryption_config := config.get(CONF_ENCRYPTION, None)) is not None:
|
||||
if key := encryption_config.get(CONF_KEY):
|
||||
cg.add(var.set_noise_psk(new_psk_progmem(key)))
|
||||
cg.add(var.set_noise_psk(new_psk_progmem(config[CONF_ID], key)))
|
||||
cg.add_define("USE_API_NOISE_PSK_FROM_YAML")
|
||||
else:
|
||||
# No key provided, but encryption desired
|
||||
|
||||
@@ -92,21 +92,6 @@ uint8_t ES8311::calculate_resolution_value(ES8311Resolution resolution) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Encodes the MCLK pre-multiplier for REG02 bits 3-4. The coefficient table stores the multiplier as the factor
|
||||
/// itself (1, 2, 4 or 8), while the register takes 0, 1, 2 or 3 (ES8311 datasheet, register 0x02 MULT_PRE).
|
||||
static uint8_t encode_pre_mult(uint8_t pre_mult) {
|
||||
switch (pre_mult) {
|
||||
case 2:
|
||||
return 1;
|
||||
case 4:
|
||||
return 2;
|
||||
case 8:
|
||||
return 3;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
const ES8311Coefficient *ES8311::get_coefficient(uint32_t mclk, uint32_t rate) {
|
||||
for (const auto &coefficient : ES8311_COEFFICIENTS) {
|
||||
if (coefficient.mclk == mclk && coefficient.rate == rate)
|
||||
@@ -142,7 +127,7 @@ bool ES8311::configure_clock_() {
|
||||
ES8311_ERROR_CHECK(this->read_byte(ES8311_REG02_CLK_MANAGER, ®02));
|
||||
reg02 &= 0x07;
|
||||
reg02 |= (coefficient->pre_div - 1) << 5;
|
||||
reg02 |= encode_pre_mult(coefficient->pre_mult) << 3;
|
||||
reg02 |= coefficient->pre_mult << 3;
|
||||
ES8311_ERROR_CHECK(this->write_byte(ES8311_REG02_CLK_MANAGER, reg02));
|
||||
|
||||
// Register 0x03
|
||||
|
||||
@@ -360,7 +360,7 @@ async def to_code(config: ConfigType) -> None:
|
||||
# Build time key: the ota keeps its own pointer so safe mode, which
|
||||
# has no api server, still has it
|
||||
cg.add_define("USE_OTA_ENCRYPTION")
|
||||
cg.add(var.set_noise_psk(new_psk_progmem(key)))
|
||||
cg.add(var.set_noise_psk(new_psk_progmem(config[CONF_ID], key)))
|
||||
elif CONF_ENCRYPTION in api_conf:
|
||||
# Runtime key: found in the api server, or in preferences in safe mode
|
||||
cg.add_define("USE_OTA_ENCRYPTION")
|
||||
|
||||
@@ -726,8 +726,7 @@ async def to_code(config):
|
||||
)
|
||||
]
|
||||
|
||||
# constexpr Glyph table in flash; on ESP8266 static_const_array would place it in RAM.
|
||||
glyphs = cg.progmem_array(config[CONF_RAW_GLYPH_ID], glyph_initializer)
|
||||
glyphs = cg.static_const_array(config[CONF_RAW_GLYPH_ID], glyph_initializer)
|
||||
|
||||
font_height = pt_to_px(base_font.size.height)
|
||||
ascender = pt_to_px(base_font.size.ascender)
|
||||
|
||||
@@ -309,8 +309,7 @@ void Font::print(int x_start, int y_start, display::Display *display, Color colo
|
||||
// Unknown char, skip
|
||||
ESP_LOGW(TAG, "Codepoint 0x%08" PRIx32 " not found in font", code_point);
|
||||
if (!this->glyphs_.empty()) {
|
||||
// Full-width read: a narrowing byte load would fault on a PROGMEM table on ESP8266.
|
||||
int glyph_width = this->glyphs_[0].advance;
|
||||
uint8_t glyph_width = this->glyphs_[0].advance;
|
||||
display->rectangle(x_at, y_start, glyph_width, this->height_, color);
|
||||
x_at += glyph_width;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
#include "esphome/core/color.h"
|
||||
#include "esphome/core/datatypes.h"
|
||||
#include "esphome/core/defines.h"
|
||||
@@ -38,18 +36,6 @@ class Glyph final {
|
||||
int width;
|
||||
int height;
|
||||
};
|
||||
// The glyph table lives in flash, read with plain loads, which ESP8266 only allows for whole words.
|
||||
static_assert(std::is_same_v<decltype(Glyph::code_point), const uint32_t>, "Glyph fields must stay word sized");
|
||||
static_assert(std::is_same_v<decltype(Glyph::data), const uint8_t *>, "Glyph fields must stay word sized");
|
||||
static_assert(std::is_same_v<decltype(Glyph::advance), int>, "Glyph fields must stay word sized");
|
||||
static_assert(std::is_same_v<decltype(Glyph::offset_x), int>, "Glyph fields must stay word sized");
|
||||
static_assert(std::is_same_v<decltype(Glyph::offset_y), int>, "Glyph fields must stay word sized");
|
||||
static_assert(std::is_same_v<decltype(Glyph::width), int>, "Glyph fields must stay word sized");
|
||||
static_assert(std::is_same_v<decltype(Glyph::height), int>, "Glyph fields must stay word sized");
|
||||
#ifdef USE_ESP8266
|
||||
static_assert(alignof(Glyph) == sizeof(uint32_t), "Glyph fields must stay word sized");
|
||||
static_assert(sizeof(Glyph) == 7 * sizeof(uint32_t), "Glyph is read from flash with word loads");
|
||||
#endif
|
||||
|
||||
class Font final
|
||||
#ifdef USE_DISPLAY
|
||||
|
||||
@@ -7,23 +7,19 @@ from .. import (
|
||||
HOME_ASSISTANT_IMPORT_CONTROL_SCHEMA,
|
||||
homeassistant_ns,
|
||||
setup_home_assistant_entity,
|
||||
validate_entity_domain,
|
||||
)
|
||||
|
||||
CODEOWNERS = ["@landonr"]
|
||||
DEPENDENCIES = ["api"]
|
||||
|
||||
SUPPORTED_DOMAINS = ["input_number", "number"]
|
||||
|
||||
HomeassistantNumber = homeassistant_ns.class_(
|
||||
"HomeassistantNumber", number.Number, cg.Component
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
CONFIG_SCHEMA = (
|
||||
number.number_schema(HomeassistantNumber)
|
||||
.extend(HOME_ASSISTANT_IMPORT_CONTROL_SCHEMA)
|
||||
.extend(cv.COMPONENT_SCHEMA),
|
||||
validate_entity_domain("number", SUPPORTED_DOMAINS),
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
#include "homeassistant_number.h"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#include "esphome/components/api/api_pb2.h"
|
||||
#include "esphome/components/api/api_server.h"
|
||||
#include "esphome/core/log.h"
|
||||
@@ -82,19 +80,12 @@ void HomeassistantNumber::control(float value) {
|
||||
|
||||
this->publish_state(value);
|
||||
|
||||
static constexpr auto NUMBER_SERVICE_NAME = StringRef::from_lit("number.set_value");
|
||||
static constexpr auto INPUT_NUMBER_SERVICE_NAME = StringRef::from_lit("input_number.set_value");
|
||||
static constexpr char INPUT_PREFIX[] = "input_";
|
||||
static constexpr auto SERVICE_NAME = StringRef::from_lit("number.set_value");
|
||||
static constexpr auto ENTITY_ID_KEY = StringRef::from_lit("entity_id");
|
||||
static constexpr auto VALUE_KEY = StringRef::from_lit("value");
|
||||
|
||||
api::HomeassistantActionRequest resp;
|
||||
// input_number entities have their own set_value action
|
||||
if (strncmp(this->entity_id_, INPUT_PREFIX, sizeof(INPUT_PREFIX) - 1) == 0) {
|
||||
resp.service = INPUT_NUMBER_SERVICE_NAME;
|
||||
} else {
|
||||
resp.service = NUMBER_SERVICE_NAME;
|
||||
}
|
||||
resp.service = SERVICE_NAME;
|
||||
|
||||
resp.data.init(2);
|
||||
auto &entity_id = resp.data.emplace_back();
|
||||
|
||||
@@ -25,10 +25,11 @@ std::string HttpContainer::get_response_header(const std::string &header_name) {
|
||||
auto lower = str_lower_case(header_name); // NOLINT
|
||||
for (const auto &entry : this->response_headers_) {
|
||||
if (entry.name == lower) {
|
||||
ESP_LOGV(TAG, "Header with name %s found with value %s", lower.c_str(), entry.value.c_str());
|
||||
ESP_LOGD(TAG, "Header with name %s found with value %s", lower.c_str(), entry.value.c_str());
|
||||
return entry.value;
|
||||
}
|
||||
}
|
||||
ESP_LOGW(TAG, "No header with name %s found", lower.c_str());
|
||||
return "";
|
||||
}
|
||||
|
||||
|
||||
@@ -268,11 +268,6 @@ class HttpContainer : public Parented<HttpRequestComponent> {
|
||||
return !this->is_chunked_ && this->bytes_read_ >= this->content_length;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get the value of a response header by name.
|
||||
* @param header_name The name of the header to retrieve.
|
||||
* @return The value of the header if found, otherwise an empty string.
|
||||
*/
|
||||
std::string get_response_header(const std::string &header_name);
|
||||
|
||||
protected:
|
||||
|
||||
@@ -60,11 +60,11 @@ class I2SAudioMicrophone final : public I2SAudioIn, public microphone::Microphon
|
||||
|
||||
gpio_num_t din_pin_{I2S_GPIO_UNUSED};
|
||||
i2s_chan_handle_t rx_handle_;
|
||||
bool pdm_{false};
|
||||
#if SOC_I2S_SUPPORTS_PDM_RX
|
||||
i2s_pdm_dsr_t pdm_dsr_{I2S_PDM_DSR_8S};
|
||||
#endif
|
||||
|
||||
bool pdm_{false};
|
||||
bool correct_dc_offset_;
|
||||
bool locked_driver_{false};
|
||||
int32_t dc_offset_prev_input_{0};
|
||||
|
||||
@@ -176,10 +176,6 @@ class LD2450Component : public Component, public uart::UARTDevice {
|
||||
bool bluetooth_on_{false};
|
||||
Target target_info_[MAX_TARGETS];
|
||||
Zone zone_config_[MAX_ZONES];
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
// Byte aligned, so it packs after zone_config_ instead of padding before data_callback_
|
||||
std::array<Deduplicator<uint8_t>, MAX_TARGETS> direction_dedup_{};
|
||||
#endif
|
||||
|
||||
#ifdef USE_NUMBER
|
||||
ESPPreferenceObject pref_; // only used when numbers are in use
|
||||
@@ -198,6 +194,7 @@ class LD2450Component : public Component, public uart::UARTDevice {
|
||||
#endif
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
std::array<text_sensor::TextSensor *, MAX_TARGETS> direction_text_sensors_{};
|
||||
std::array<Deduplicator<uint8_t>, MAX_TARGETS> direction_dedup_{};
|
||||
#endif
|
||||
|
||||
LazyCallbackManager<void()> data_callback_;
|
||||
|
||||
@@ -35,11 +35,11 @@ class LEDCOutput final : public output::FloatOutput, public Component {
|
||||
InternalGPIOPin *pin_;
|
||||
uint8_t channel_{};
|
||||
uint8_t bit_depth_{};
|
||||
bool initialized_ = false;
|
||||
float phase_angle_{0.0f};
|
||||
float frequency_{};
|
||||
float duty_{0.0f};
|
||||
uint32_t last_duty_{UINT32_MAX};
|
||||
bool initialized_ = false;
|
||||
};
|
||||
|
||||
} // namespace esphome::ledc
|
||||
|
||||
@@ -32,7 +32,6 @@ from esphome.storage_json import StorageJSON
|
||||
from . import gpio # noqa: F401
|
||||
from .const import (
|
||||
COMPONENT_BK72XX,
|
||||
COMPONENT_LN882X,
|
||||
CONF_GPIO_RECOVER,
|
||||
CONF_LOGLEVEL,
|
||||
CONF_SDK_SILENT,
|
||||
@@ -491,10 +490,6 @@ async def component_to_code(config):
|
||||
# Not enabled on RTL87xx/LN882x — costs more heap than it saves there.
|
||||
cg.add_build_flag("-DconfigSUPPORT_STATIC_ALLOCATION=1")
|
||||
|
||||
# LN882x: a zero-size allocation must not trip the SDK's assert (see ln882x_zero_malloc.c).
|
||||
if config[CONF_COMPONENT_ID] == COMPONENT_LN882X:
|
||||
cg.add_build_flag("-Wl,--wrap=pvPortMalloc")
|
||||
|
||||
# RTL8710B needs FreeRTOS 8.2.3+ for xTaskNotifyGive/ulTaskNotifyTake
|
||||
# required by AsyncTCP 3.4.3+ (https://github.com/esphome/esphome/issues/10220)
|
||||
# RTL8720C (ambz2) requires FreeRTOS 10.x so this only applies to RTL8710B
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
// LN882x: a zero-size pvPortMalloc trips the SDK's malloc-failed LN_ASSERT; ask for 1 byte instead.
|
||||
|
||||
#ifdef USE_LN882X
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
void *__real_pvPortMalloc(size_t size); // NOLINT(readability-identifier-naming)
|
||||
|
||||
void *__wrap_pvPortMalloc(size_t size) { // NOLINT(readability-identifier-naming)
|
||||
return __real_pvPortMalloc(size == 0 ? 1 : size);
|
||||
}
|
||||
|
||||
#endif // USE_LN882X
|
||||
@@ -48,7 +48,6 @@ from esphome.cpp_generator import MockObj
|
||||
from esphome.final_validate import full_config
|
||||
from esphome.helpers import write_file_if_changed
|
||||
from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor
|
||||
from esphome.types import ConfigType
|
||||
from esphome.writer import clean_build
|
||||
from esphome.yaml_util import load_yaml
|
||||
|
||||
@@ -642,15 +641,7 @@ LVGL_TOP_LEVEL_SCHEMA = (
|
||||
)
|
||||
|
||||
|
||||
def _not_on_esp8266(config: ConfigType) -> ConfigType:
|
||||
# ESP8266 does not have enough RAM for LVGL to be practical.
|
||||
if CORE.is_esp8266:
|
||||
raise cv.Invalid("LVGL is not supported on ESP8266")
|
||||
return config
|
||||
|
||||
|
||||
LVGL_SCHEMA = cv.All(
|
||||
_not_on_esp8266,
|
||||
container_schema(obj_spec, LVGL_TOP_LEVEL_SCHEMA),
|
||||
cv.has_at_most_one_key(CONF_PAGES, df.CONF_LAYOUT),
|
||||
add_hello_world,
|
||||
|
||||
@@ -1037,7 +1037,7 @@ void lv_mem_init() {}
|
||||
|
||||
void lv_mem_deinit() {}
|
||||
|
||||
#ifndef USE_ESP32
|
||||
#if defined(USE_HOST) || defined(USE_RP2) || defined(USE_ESP8266)
|
||||
void *lv_malloc_core(size_t size) {
|
||||
auto *ptr = malloc(size); // NOLINT
|
||||
if (ptr == nullptr) {
|
||||
@@ -1053,7 +1053,7 @@ static void *lv_alloc_draw_buf(size_t size, bool internal) {
|
||||
return malloc(size); // NOLINT
|
||||
}
|
||||
|
||||
#else
|
||||
#elif defined(USE_ESP32)
|
||||
static unsigned cap_bits = MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT; // NOLINT
|
||||
|
||||
static void *lv_alloc_draw_buf(size_t size, bool internal) {
|
||||
|
||||
@@ -34,7 +34,7 @@ class MicrophoneSource final {
|
||||
*/
|
||||
public:
|
||||
MicrophoneSource(Microphone *mic, uint8_t bits_per_sample, int32_t gain_factor, bool passive)
|
||||
: mic_(mic), gain_factor_(gain_factor), passive_(passive), bits_per_sample_(bits_per_sample) {}
|
||||
: mic_(mic), bits_per_sample_(bits_per_sample), gain_factor_(gain_factor), passive_(passive) {}
|
||||
|
||||
/// @brief Enables a channel to be processed through the callback.
|
||||
///
|
||||
@@ -81,11 +81,11 @@ class MicrophoneSource final {
|
||||
std::shared_ptr<std::vector<uint8_t>> processed_samples_;
|
||||
|
||||
Microphone *mic_;
|
||||
uint8_t bits_per_sample_;
|
||||
std::bitset<8> channels_;
|
||||
int32_t gain_factor_;
|
||||
bool enabled_{false};
|
||||
bool passive_; // Only pass audio if ``mic_`` is already running
|
||||
uint8_t bits_per_sample_;
|
||||
};
|
||||
|
||||
} // namespace esphome::microphone
|
||||
@@ -107,12 +107,12 @@ class SourceSpeaker final : public speaker::Speaker, public Component {
|
||||
bool stop_gracefully_{false};
|
||||
|
||||
bool pause_state_{false};
|
||||
std::atomic<bool> has_contributed_{false}; // Tracks if source has contributed during this session
|
||||
|
||||
esp_audio_libs::gain::GainRamp ducking_ramp_;
|
||||
|
||||
std::atomic<uint32_t> pending_playback_frames_{0};
|
||||
std::atomic<uint32_t> playback_delay_frames_{0}; // Frames in output pipeline when this source started contributing
|
||||
std::atomic<bool> has_contributed_{false}; // Tracks if source has contributed during this session
|
||||
|
||||
EventGroupHandle_t event_group_{nullptr};
|
||||
uint32_t stopping_start_ms_{0};
|
||||
|
||||
@@ -5,11 +5,12 @@ from typing import Any
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ENCRYPTION, CONF_KEY
|
||||
from esphome.core import CORE
|
||||
from esphome.core import CORE, ID
|
||||
from esphome.cpp_generator import MockObj
|
||||
from esphome.types import ConfigType
|
||||
|
||||
CODEOWNERS = ["@esphome/core"]
|
||||
DOMAIN = "noise"
|
||||
|
||||
# Keep in sync with platformio.ini and esphome/idf_component.yml.
|
||||
# LIBSODIUM_VERSION must match the version noise-c pins in its manifests.
|
||||
@@ -73,13 +74,18 @@ def static_encryption_key(conf: ConfigType) -> str | None:
|
||||
return (conf.get(CONF_ENCRYPTION) or {}).get(CONF_KEY) or None
|
||||
|
||||
|
||||
def new_psk_progmem(key: str) -> MockObj:
|
||||
def new_psk_progmem(parent_id: ID, key: str) -> MockObj:
|
||||
"""Emit the decoded key as a PROGMEM array; the component keeps a pointer
|
||||
so the key never occupies RAM. Components sharing one key (api and ota)
|
||||
share the array."""
|
||||
return cg.shared_progmem_array(
|
||||
"noise_psk", cg.uint8, list(decode_encryption_key(key))
|
||||
)
|
||||
decoded = decode_encryption_key(key)
|
||||
arrays: dict[bytes, MockObj] = CORE.data.setdefault(DOMAIN, {})
|
||||
if (array := arrays.get(decoded)) is None:
|
||||
array = arrays[decoded] = cg.progmem_array(
|
||||
ID(f"{parent_id.id}_psk", is_declaration=True, type=cg.uint8),
|
||||
list(decoded),
|
||||
)
|
||||
return array
|
||||
|
||||
|
||||
def encryption_schema(config: ConfigType | None) -> ConfigType:
|
||||
|
||||
@@ -10,9 +10,8 @@ namespace esphome::ring_buffer {
|
||||
static const char *const TAG = "ring_buffer";
|
||||
|
||||
RingBuffer::~RingBuffer() {
|
||||
// create() only builds the ring buffer once storage_ is allocated
|
||||
if (this->storage_ != nullptr) {
|
||||
vRingbufferDelete(this->handle_());
|
||||
if (this->handle_ != nullptr) {
|
||||
vRingbufferDelete(this->handle_);
|
||||
RAMAllocator<uint8_t> allocator;
|
||||
allocator.deallocate(this->storage_, this->size_);
|
||||
}
|
||||
@@ -32,12 +31,7 @@ std::unique_ptr<RingBuffer> RingBuffer::create(size_t len, MemoryPreference pref
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// handle_() assumes the returned handle is &structure_; NULL means the arguments were rejected
|
||||
if (xRingbufferCreateStatic(rb->size_, RINGBUF_TYPE_BYTEBUF, rb->storage_, &rb->structure_) != rb->handle_()) {
|
||||
allocator.deallocate(rb->storage_, rb->size_);
|
||||
rb->storage_ = nullptr;
|
||||
return nullptr;
|
||||
}
|
||||
rb->handle_ = xRingbufferCreateStatic(rb->size_, RINGBUF_TYPE_BYTEBUF, rb->storage_, &rb->structure_);
|
||||
ESP_LOGD(TAG, "Created ring buffer with size %u", len);
|
||||
|
||||
return rb;
|
||||
@@ -45,16 +39,16 @@ std::unique_ptr<RingBuffer> RingBuffer::create(size_t len, MemoryPreference pref
|
||||
|
||||
void *RingBuffer::receive_acquire(size_t &length, size_t max_length, TickType_t ticks_to_wait) {
|
||||
length = 0;
|
||||
void *buffer_data = xRingbufferReceiveUpTo(this->handle_(), &length, ticks_to_wait, max_length);
|
||||
void *buffer_data = xRingbufferReceiveUpTo(this->handle_, &length, ticks_to_wait, max_length);
|
||||
return buffer_data;
|
||||
}
|
||||
|
||||
void RingBuffer::receive_release(void *item) { vRingbufferReturnItem(this->handle_(), item); }
|
||||
void RingBuffer::receive_release(void *item) { vRingbufferReturnItem(this->handle_, item); }
|
||||
|
||||
size_t RingBuffer::read(void *data, size_t len, TickType_t ticks_to_wait) {
|
||||
size_t bytes_read = 0;
|
||||
|
||||
void *buffer_data = xRingbufferReceiveUpTo(this->handle_(), &bytes_read, ticks_to_wait, len);
|
||||
void *buffer_data = xRingbufferReceiveUpTo(this->handle_, &bytes_read, ticks_to_wait, len);
|
||||
|
||||
if (buffer_data == nullptr) {
|
||||
return 0;
|
||||
@@ -62,14 +56,14 @@ size_t RingBuffer::read(void *data, size_t len, TickType_t ticks_to_wait) {
|
||||
|
||||
std::memcpy(data, buffer_data, bytes_read);
|
||||
|
||||
vRingbufferReturnItem(this->handle_(), buffer_data);
|
||||
vRingbufferReturnItem(this->handle_, buffer_data);
|
||||
|
||||
if (bytes_read < len) {
|
||||
// Data may have wrapped around, so read a second time to receive the remainder
|
||||
size_t follow_up_bytes_read = 0;
|
||||
size_t bytes_remaining = len - bytes_read;
|
||||
|
||||
buffer_data = xRingbufferReceiveUpTo(this->handle_(), &follow_up_bytes_read, 0, bytes_remaining);
|
||||
buffer_data = xRingbufferReceiveUpTo(this->handle_, &follow_up_bytes_read, 0, bytes_remaining);
|
||||
|
||||
if (buffer_data == nullptr) {
|
||||
return bytes_read;
|
||||
@@ -77,7 +71,7 @@ size_t RingBuffer::read(void *data, size_t len, TickType_t ticks_to_wait) {
|
||||
|
||||
std::memcpy((void *) ((uint8_t *) (data) + bytes_read), buffer_data, follow_up_bytes_read);
|
||||
|
||||
vRingbufferReturnItem(this->handle_(), buffer_data);
|
||||
vRingbufferReturnItem(this->handle_, buffer_data);
|
||||
bytes_read += follow_up_bytes_read;
|
||||
}
|
||||
|
||||
@@ -95,13 +89,13 @@ size_t RingBuffer::write(const void *data, size_t len) {
|
||||
|
||||
size_t RingBuffer::write_without_replacement(const void *data, size_t len, TickType_t ticks_to_wait,
|
||||
bool write_partial) {
|
||||
if (!xRingbufferSend(this->handle_(), data, len, ticks_to_wait)) {
|
||||
if (!xRingbufferSend(this->handle_, data, len, ticks_to_wait)) {
|
||||
if (!write_partial) {
|
||||
return 0; // Not enough space available and not allowed to write partial data
|
||||
}
|
||||
// Couldn't fit all the data, write what will fit
|
||||
size_t free = std::min(this->free(), len);
|
||||
if (xRingbufferSend(this->handle_(), data, free, 0)) {
|
||||
if (xRingbufferSend(this->handle_, data, free, 0)) {
|
||||
return free;
|
||||
}
|
||||
return 0;
|
||||
@@ -111,11 +105,11 @@ size_t RingBuffer::write_without_replacement(const void *data, size_t len, TickT
|
||||
|
||||
size_t RingBuffer::available() const {
|
||||
UBaseType_t ux_items_waiting = 0;
|
||||
vRingbufferGetInfo(this->handle_(), nullptr, nullptr, nullptr, nullptr, &ux_items_waiting);
|
||||
vRingbufferGetInfo(this->handle_, nullptr, nullptr, nullptr, nullptr, &ux_items_waiting);
|
||||
return ux_items_waiting;
|
||||
}
|
||||
|
||||
size_t RingBuffer::free() const { return xRingbufferGetCurFreeSize(this->handle_()); }
|
||||
size_t RingBuffer::free() const { return xRingbufferGetCurFreeSize(this->handle_); }
|
||||
|
||||
BaseType_t RingBuffer::reset() {
|
||||
// Discards all the available data
|
||||
@@ -125,15 +119,15 @@ BaseType_t RingBuffer::reset() {
|
||||
bool RingBuffer::discard_bytes_(size_t discard_bytes) {
|
||||
size_t bytes_read = 0;
|
||||
|
||||
void *buffer_data = xRingbufferReceiveUpTo(this->handle_(), &bytes_read, 0, discard_bytes);
|
||||
void *buffer_data = xRingbufferReceiveUpTo(this->handle_, &bytes_read, 0, discard_bytes);
|
||||
if (buffer_data != nullptr)
|
||||
vRingbufferReturnItem(this->handle_(), buffer_data);
|
||||
vRingbufferReturnItem(this->handle_, buffer_data);
|
||||
|
||||
if (bytes_read < discard_bytes) {
|
||||
size_t wrapped_bytes_read = 0;
|
||||
buffer_data = xRingbufferReceiveUpTo(this->handle_(), &wrapped_bytes_read, 0, discard_bytes - bytes_read);
|
||||
buffer_data = xRingbufferReceiveUpTo(this->handle_, &wrapped_bytes_read, 0, discard_bytes - bytes_read);
|
||||
if (buffer_data != nullptr) {
|
||||
vRingbufferReturnItem(this->handle_(), buffer_data);
|
||||
vRingbufferReturnItem(this->handle_, buffer_data);
|
||||
bytes_read += wrapped_bytes_read;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,9 +115,7 @@ class RingBuffer {
|
||||
/// @return True if all bytes were successfully discarded, false otherwise
|
||||
bool discard_bytes_(size_t discard_bytes);
|
||||
|
||||
// xRingbufferCreateStatic() returns the static structure itself as the handle
|
||||
RingbufHandle_t handle_() const { return const_cast<StaticRingbuffer_t *>(&this->structure_); }
|
||||
|
||||
RingbufHandle_t handle_{nullptr};
|
||||
StaticRingbuffer_t structure_;
|
||||
uint8_t *storage_{nullptr};
|
||||
size_t size_{0};
|
||||
|
||||
@@ -82,14 +82,14 @@ class Rtttl final : public Component {
|
||||
uint16_t note_duration_{0};
|
||||
/// The duration of a whole note in milliseconds.
|
||||
uint16_t wholenote_duration_;
|
||||
/// The current state of the RTTTL player.
|
||||
State state_{State::STOPPED};
|
||||
/// The time in milliseconds since microcontroller boot when the last note was started.
|
||||
uint32_t last_note_start_time_;
|
||||
/// The frequency of the current note in Hz.
|
||||
uint32_t output_freq_{0};
|
||||
/// The gain of the output.
|
||||
float gain_{0.6f};
|
||||
/// The current state of the RTTTL player.
|
||||
State state_{State::STOPPED};
|
||||
|
||||
#ifdef USE_OUTPUT
|
||||
/// The output to write the sound to.
|
||||
|
||||
@@ -308,9 +308,6 @@ class SendspinHub final : public Component,
|
||||
CallbackManager<void(const sendspin::GroupUpdateObject &)> group_update_callbacks_{};
|
||||
|
||||
bool task_stack_in_psram_{false};
|
||||
#ifdef USE_MDNS_SUPPORTS_ENABLE_DISABLE
|
||||
bool mdns_advertised_{false}; // Last state requested from mdns
|
||||
#endif
|
||||
|
||||
// Requested client state, applied from loop(). Empty until the switch restores its state.
|
||||
std::optional<bool> enabled_;
|
||||
@@ -323,6 +320,7 @@ class SendspinHub final : public Component,
|
||||
|
||||
#ifdef USE_MDNS_SUPPORTS_ENABLE_DISABLE
|
||||
mdns::MDNSComponent *mdns_{nullptr};
|
||||
bool mdns_advertised_{false}; // Last state requested from mdns
|
||||
#endif
|
||||
};
|
||||
|
||||
|
||||
@@ -127,9 +127,9 @@ class Speaker {
|
||||
#endif
|
||||
|
||||
State state_{STATE_STOPPED};
|
||||
bool mute_state_{false};
|
||||
audio::AudioStreamInfo audio_stream_info_;
|
||||
float volume_{1.0f};
|
||||
bool mute_state_{false};
|
||||
|
||||
#ifdef USE_AUDIO_DAC
|
||||
audio_dac::AudioDac *audio_dac_{nullptr};
|
||||
|
||||
@@ -181,7 +181,9 @@ async def to_code(config: ConfigType) -> None:
|
||||
sequence = []
|
||||
for seq in config[CONF_INIT_SEQUENCE]:
|
||||
sequence.extend(seq)
|
||||
cg.add(var.set_init_sequence(sequence))
|
||||
if sequence:
|
||||
table = cg.shared_progmem_array("st7701s_init_sequence", cg.uint8, sequence)
|
||||
cg.add(var.set_init_sequence(table, len(sequence)))
|
||||
cg.add(var.set_color_mode(COLOR_ORDERS[config[CONF_COLOR_ORDER]]))
|
||||
cg.add(var.set_invert_colors(config[CONF_INVERT_COLORS]))
|
||||
cg.add(var.set_hsync_pulse_width(config[CONF_HSYNC_PULSE_WIDTH]))
|
||||
|
||||
@@ -144,14 +144,14 @@ void ST7701S::write_sequence_(uint8_t cmd, size_t len, const uint8_t *bytes) {
|
||||
}
|
||||
|
||||
void ST7701S::write_init_sequence_() {
|
||||
for (size_t i = 0; i != this->init_sequence_.size();) {
|
||||
for (size_t i = 0; i != this->init_sequence_len_;) {
|
||||
uint8_t cmd = this->init_sequence_[i++];
|
||||
size_t len = this->init_sequence_[i++];
|
||||
if (len == ST7701S_DELAY_FLAG) {
|
||||
ESP_LOGV(TAG, "Delay %dms", cmd);
|
||||
delay(cmd);
|
||||
} else {
|
||||
this->write_sequence_(cmd, len, &this->init_sequence_[i]);
|
||||
this->write_sequence_(cmd, len, this->init_sequence_ + i);
|
||||
i += len;
|
||||
ESP_LOGV(TAG, "Command %X, %d bytes", cmd, len);
|
||||
if (cmd == SW_RESET_CMD)
|
||||
|
||||
@@ -62,7 +62,10 @@ class ST7701S final : public display::Display,
|
||||
void set_vsync_pulse_width(uint16_t vsync_pulse_width) { this->vsync_pulse_width_ = vsync_pulse_width; }
|
||||
void set_vsync_back_porch(uint16_t vsync_back_porch) { this->vsync_back_porch_ = vsync_back_porch; }
|
||||
void set_vsync_front_porch(uint16_t vsync_front_porch) { this->vsync_front_porch_ = vsync_front_porch; }
|
||||
void set_init_sequence(const std::vector<uint8_t> &init_sequence) { this->init_sequence_ = init_sequence; }
|
||||
void set_init_sequence(const uint8_t *init_sequence, size_t len) {
|
||||
this->init_sequence_ = init_sequence;
|
||||
this->init_sequence_len_ = len;
|
||||
}
|
||||
void set_mirror_x(bool mirror_x) { this->mirror_x_ = mirror_x; }
|
||||
void set_mirror_y(bool mirror_y) { this->mirror_y_ = mirror_y; }
|
||||
void set_offsets(int16_t offset_x, int16_t offset_y) {
|
||||
@@ -95,7 +98,9 @@ class ST7701S final : public display::Display,
|
||||
uint16_t vsync_pulse_width_ = 10;
|
||||
uint16_t vsync_back_porch_ = 10;
|
||||
uint16_t vsync_front_porch_ = 10;
|
||||
std::vector<uint8_t> init_sequence_;
|
||||
// Shared PROGMEM table
|
||||
const uint8_t *init_sequence_{nullptr};
|
||||
size_t init_sequence_len_{0};
|
||||
uint32_t pclk_frequency_ = 16 * 1000 * 1000;
|
||||
bool pclk_inverted_{true};
|
||||
|
||||
|
||||
@@ -25,8 +25,8 @@ class TemplateNumber final : public number::Number, public PollingComponent {
|
||||
protected:
|
||||
void control(float value) override;
|
||||
bool optimistic_{false};
|
||||
bool restore_value_{false};
|
||||
float initial_value_{NAN};
|
||||
bool restore_value_{false};
|
||||
Trigger<float> set_trigger_;
|
||||
TemplateLambda<float> f_;
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ enum VoiceAssistantFeature : uint32_t {
|
||||
FEATURE_MULTI_CHANNEL_AUDIO = 1 << 6,
|
||||
};
|
||||
|
||||
enum class State : uint8_t {
|
||||
enum class State {
|
||||
IDLE,
|
||||
START_MICROPHONE,
|
||||
STARTING_MICROPHONE,
|
||||
@@ -96,7 +96,7 @@ struct Configuration {
|
||||
};
|
||||
|
||||
#ifdef USE_MEDIA_PLAYER
|
||||
enum class MediaPlayerResponseState : uint8_t {
|
||||
enum class MediaPlayerResponseState {
|
||||
IDLE,
|
||||
URL_SENT,
|
||||
PLAYING,
|
||||
@@ -237,7 +237,6 @@ class VoiceAssistant final : public Component {
|
||||
|
||||
void set_state_(State state);
|
||||
void set_state_(State state, State desired_state);
|
||||
bool start_udp_socket_();
|
||||
void signal_stop_();
|
||||
void start_playback_timeout_();
|
||||
|
||||
@@ -281,6 +280,8 @@ class VoiceAssistant final : public Component {
|
||||
Trigger<Timer> timer_updated_trigger_;
|
||||
Trigger<Timer> timer_cancelled_trigger_;
|
||||
Trigger<const std::vector<Timer> &> timer_tick_trigger_;
|
||||
bool has_timers_{false};
|
||||
bool timer_tick_running_{false};
|
||||
|
||||
microphone::MicrophoneSource *mic_source_{nullptr};
|
||||
microphone::MicrophoneSource *mic_source2_{nullptr};
|
||||
@@ -291,12 +292,19 @@ class VoiceAssistant final : public Component {
|
||||
size_t speaker_buffer_index_{0};
|
||||
size_t speaker_buffer_size_{0};
|
||||
size_t speaker_bytes_received_{0};
|
||||
bool wait_for_stream_end_{false};
|
||||
bool stream_ended_{false};
|
||||
#endif
|
||||
#ifdef USE_MEDIA_PLAYER
|
||||
media_player::MediaPlayer *media_player_{nullptr};
|
||||
std::string tts_response_url_;
|
||||
bool started_streaming_tts_{false};
|
||||
|
||||
MediaPlayerResponseState media_player_response_state_{MediaPlayerResponseState::IDLE};
|
||||
#endif
|
||||
|
||||
bool local_output_{false};
|
||||
|
||||
std::string conversation_id_;
|
||||
|
||||
std::string wake_word_;
|
||||
@@ -316,37 +324,29 @@ class VoiceAssistant final : public Component {
|
||||
// prolonged one can be detected and stopped; 0 means no imbalance is currently being timed.
|
||||
uint32_t audio_channel_stall_start_{0};
|
||||
|
||||
bool use_wake_word_;
|
||||
uint8_t noise_suppression_level_;
|
||||
uint8_t auto_gain_;
|
||||
float volume_multiplier_;
|
||||
uint32_t conversation_timeout_;
|
||||
|
||||
bool continuous_{false};
|
||||
bool silence_detection_;
|
||||
|
||||
bool continue_conversation_{false};
|
||||
|
||||
State state_{State::IDLE};
|
||||
State desired_state_{State::IDLE};
|
||||
|
||||
AudioMode audio_mode_{AUDIO_MODE_UDP};
|
||||
bool udp_socket_running_{false};
|
||||
bool start_udp_socket_();
|
||||
|
||||
Configuration config_{};
|
||||
|
||||
#ifdef USE_MICRO_WAKE_WORD
|
||||
micro_wake_word::MicroWakeWord *micro_wake_word_{nullptr};
|
||||
#endif
|
||||
|
||||
// 1 byte members grouped at the end so they share padding instead of each taking a word
|
||||
State state_{State::IDLE};
|
||||
State desired_state_{State::IDLE};
|
||||
AudioMode audio_mode_{AUDIO_MODE_UDP};
|
||||
bool has_timers_{false};
|
||||
bool timer_tick_running_{false};
|
||||
#ifdef USE_SPEAKER
|
||||
bool wait_for_stream_end_{false};
|
||||
bool stream_ended_{false};
|
||||
#endif
|
||||
#ifdef USE_MEDIA_PLAYER
|
||||
MediaPlayerResponseState media_player_response_state_{MediaPlayerResponseState::IDLE};
|
||||
bool started_streaming_tts_{false};
|
||||
#endif
|
||||
bool local_output_{false};
|
||||
bool use_wake_word_;
|
||||
uint8_t noise_suppression_level_;
|
||||
uint8_t auto_gain_;
|
||||
bool continuous_{false};
|
||||
bool silence_detection_;
|
||||
bool continue_conversation_{false};
|
||||
bool udp_socket_running_{false};
|
||||
};
|
||||
|
||||
extern VoiceAssistant *global_voice_assistant; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
esphome:
|
||||
name: test
|
||||
|
||||
esp8266:
|
||||
board: esp01_1m
|
||||
|
||||
font:
|
||||
- file: ../NotoSans-Regular.ttf
|
||||
id: noto
|
||||
size: 12
|
||||
glyphs: "AB"
|
||||
@@ -4,10 +4,8 @@ Focuses on verifying that long multi-byte (Chinese/CJK) glyph strings
|
||||
are correctly processed through the font configuration pipeline.
|
||||
"""
|
||||
|
||||
from collections.abc import Callable
|
||||
import functools
|
||||
from pathlib import Path
|
||||
import re
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
@@ -262,12 +260,15 @@ def mock_cg():
|
||||
with (
|
||||
patch("esphome.components.font.cg.add_define") as mock_define,
|
||||
patch("esphome.components.font.cg.progmem_array") as mock_progmem,
|
||||
patch("esphome.components.font.cg.static_const_array") as mock_static,
|
||||
patch("esphome.components.font.cg.new_Pvariable") as mock_new_pvar,
|
||||
):
|
||||
mock_progmem.return_value = MagicMock()
|
||||
mock_static.return_value = MagicMock()
|
||||
yield {
|
||||
"add_define": mock_define,
|
||||
"progmem_array": mock_progmem,
|
||||
"static_const_array": mock_static,
|
||||
"new_Pvariable": mock_new_pvar,
|
||||
}
|
||||
|
||||
@@ -287,13 +288,13 @@ async def test_to_code_long_latin_generates_all_glyphs(mock_cg):
|
||||
mock_cg["add_define"].assert_any_call("USE_FONT")
|
||||
|
||||
# progmem_array receives the combined bitmap data (non-empty)
|
||||
# bitmap data first, then the glyph table
|
||||
assert mock_cg["progmem_array"].call_count == 2
|
||||
bitmap_data = mock_cg["progmem_array"].call_args_list[0].args[1]
|
||||
mock_cg["progmem_array"].assert_called_once()
|
||||
bitmap_data = mock_cg["progmem_array"].call_args.args[1]
|
||||
assert len(bitmap_data) > 0
|
||||
|
||||
# the glyph table receives one entry per unique glyph
|
||||
glyph_initializer = mock_cg["progmem_array"].call_args_list[1].args[1]
|
||||
# static_const_array receives one entry per unique glyph
|
||||
mock_cg["static_const_array"].assert_called_once()
|
||||
glyph_initializer = mock_cg["static_const_array"].call_args.args[1]
|
||||
assert len(glyph_initializer) == glyph_count
|
||||
|
||||
# new_Pvariable is called with the correct glyph count
|
||||
@@ -313,7 +314,7 @@ async def test_to_code_glyph_entries_contain_expected_fields(mock_cg):
|
||||
|
||||
await to_code(config)
|
||||
|
||||
glyph_initializer = mock_cg["progmem_array"].call_args_list[1].args[1]
|
||||
glyph_initializer = mock_cg["static_const_array"].call_args.args[1]
|
||||
for entry in glyph_initializer:
|
||||
assert len(entry) == 7, f"Glyph entry should have 7 fields, got {len(entry)}"
|
||||
codepoint = entry[0]
|
||||
@@ -331,17 +332,6 @@ async def test_to_code_glyphs_sorted_by_utf8(mock_cg):
|
||||
|
||||
await to_code(config)
|
||||
|
||||
glyph_initializer = mock_cg["progmem_array"].call_args_list[1].args[1]
|
||||
glyph_initializer = mock_cg["static_const_array"].call_args.args[1]
|
||||
codepoints = [entry[0] for entry in glyph_initializer]
|
||||
assert codepoints == sorted(codepoints)
|
||||
|
||||
|
||||
def test_glyph_table_is_progmem(
|
||||
generate_main: Callable[[str | Path], str],
|
||||
component_config_path: Callable[[str], Path],
|
||||
) -> None:
|
||||
"""The constexpr glyph table goes to flash, not a RAM static const array."""
|
||||
main_cpp = generate_main(component_config_path("glyph_table.yaml"))
|
||||
|
||||
assert re.search(r"static constexpr font::Glyph \w+\[\] PROGMEM = ", main_cpp)
|
||||
assert "static const font::Glyph" not in main_cpp
|
||||
@@ -1,32 +0,0 @@
|
||||
"""Tests for the homeassistant number platform."""
|
||||
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from esphome.config import read_config
|
||||
from esphome.core import CORE
|
||||
|
||||
VALID_CONFIG = "tests/component_tests/homeassistant/test_number.yaml"
|
||||
INVALID_CONFIG = "tests/component_tests/homeassistant/test_number_invalid_domain.yaml"
|
||||
|
||||
|
||||
def test_number_and_input_number_entities(
|
||||
generate_main: Callable[[str | Path], str],
|
||||
) -> None:
|
||||
"""Both number and input_number entity IDs are accepted and passed to the C++ class."""
|
||||
main_cpp = generate_main(VALID_CONFIG)
|
||||
|
||||
assert 'ha_number->set_entity_id("number.some_number");' in main_cpp
|
||||
assert 'ha_input_number->set_entity_id("input_number.some_input");' in main_cpp
|
||||
|
||||
|
||||
def test_number_rejects_unsupported_domain(
|
||||
capsys: pytest.CaptureFixture[str],
|
||||
) -> None:
|
||||
"""An entity ID from another domain fails config validation."""
|
||||
CORE.config_path = Path(INVALID_CONFIG)
|
||||
|
||||
assert read_config({}) is None
|
||||
assert "is not supported by the number platform" in capsys.readouterr().out
|
||||
@@ -1,17 +0,0 @@
|
||||
---
|
||||
esphome:
|
||||
name: test
|
||||
|
||||
host:
|
||||
|
||||
logger:
|
||||
|
||||
api:
|
||||
|
||||
number:
|
||||
- platform: homeassistant
|
||||
entity_id: number.some_number
|
||||
id: ha_number
|
||||
- platform: homeassistant
|
||||
entity_id: input_number.some_input
|
||||
id: ha_input_number
|
||||
@@ -1,17 +0,0 @@
|
||||
---
|
||||
esphome:
|
||||
name: test
|
||||
|
||||
host:
|
||||
|
||||
logger:
|
||||
|
||||
api:
|
||||
|
||||
number:
|
||||
- platform: homeassistant
|
||||
entity_id: number.some_number
|
||||
id: ha_number
|
||||
- platform: homeassistant
|
||||
entity_id: sensor.foo
|
||||
id: ha_other_number
|
||||
@@ -1,14 +0,0 @@
|
||||
"""LVGL rejects ESP8266 at validation time."""
|
||||
|
||||
import pytest
|
||||
|
||||
from esphome.components import lvgl
|
||||
from esphome.config_validation import Invalid
|
||||
from esphome.const import PlatformFramework
|
||||
from tests.component_tests.types import SetCoreConfigCallable
|
||||
|
||||
|
||||
def test_esp8266_is_rejected(set_core_config: SetCoreConfigCallable) -> None:
|
||||
set_core_config(PlatformFramework.ESP8266_ARDUINO)
|
||||
with pytest.raises(Invalid, match="not supported on ESP8266"):
|
||||
lvgl.CONFIG_SCHEMA({})
|
||||
@@ -560,7 +560,7 @@ def test_encryption_offer_codegen(
|
||||
own_key = encrypted and "USE_OTA_ENCRYPTION_PROVISIONED" not in defines_present
|
||||
assert ("esphome_esphomeotacomponent_id->set_noise_psk(" in main_cpp) is own_key
|
||||
# The api shares the ota's array instead of emitting the same key twice
|
||||
assert main_cpp.count("noise_psk[] PROGMEM") == (1 if own_key else 0)
|
||||
assert main_cpp.count("_psk[] PROGMEM") == (1 if own_key else 0)
|
||||
assert ("set_auth_password(" in main_cpp) is ("USE_OTA_PASSWORD" in defines_present)
|
||||
# The noise transport source compiles only when the define is set
|
||||
assert FILTER_SOURCE_FILES() == ([] if encrypted else ["ota_esphome_noise.cpp"])
|
||||
|
||||
File renamed without changes.
@@ -0,0 +1,68 @@
|
||||
"""Tests for the st7701s init sequence codegen."""
|
||||
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
import re
|
||||
|
||||
_YAML = """
|
||||
esphome:
|
||||
name: st7701s-test
|
||||
esp32:
|
||||
board: esp32-s3-devkitc-1
|
||||
framework:
|
||||
type: esp-idf
|
||||
psram:
|
||||
mode: octal
|
||||
spi:
|
||||
clk_pin: 10
|
||||
mosi_pin: 11
|
||||
display:
|
||||
- platform: st7701s
|
||||
id: panel
|
||||
dimensions:
|
||||
width: 480
|
||||
height: 480
|
||||
cs_pin: 44
|
||||
de_pin: 18
|
||||
hsync_pin: 16
|
||||
vsync_pin: 17
|
||||
pclk_pin: 21
|
||||
init_sequence: {init_sequence}
|
||||
data_pins: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 13, 14, 15, 38, 39]
|
||||
"""
|
||||
|
||||
|
||||
def test_init_sequence_is_progmem_table(
|
||||
generate_main: Callable[[str | Path], str],
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
"""The init sequence is a PROGMEM table instead of a vector copied at boot."""
|
||||
yaml_file = tmp_path / "st7701s.yaml"
|
||||
yaml_file.write_text(
|
||||
_YAML.format(init_sequence="[1, [0x23, 0xA, 0xB], delay 20ms]")
|
||||
)
|
||||
|
||||
main_cpp = generate_main(yaml_file)
|
||||
|
||||
call = re.search(r"panel->set_init_sequence\((\w+), (\d+)\);", main_cpp)
|
||||
assert call is not None
|
||||
table = re.search(
|
||||
rf"static constexpr uint8_t {call.group(1)}\[\] PROGMEM = \{{([^;]*)\}};",
|
||||
main_cpp,
|
||||
)
|
||||
assert table is not None
|
||||
assert len(table.group(1).split(",")) == int(call.group(2))
|
||||
|
||||
|
||||
def test_empty_init_sequence_emits_no_table(
|
||||
generate_main: Callable[[str | Path], str],
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
"""An empty init sequence leaves the display without a table instead of a zero length array."""
|
||||
yaml_file = tmp_path / "st7701s.yaml"
|
||||
yaml_file.write_text(_YAML.format(init_sequence="[]"))
|
||||
|
||||
main_cpp = generate_main(yaml_file)
|
||||
|
||||
assert "st7701s_init_sequence" not in main_cpp
|
||||
assert "set_init_sequence" not in main_cpp
|
||||
@@ -71,9 +71,6 @@ number:
|
||||
- platform: homeassistant
|
||||
entity_id: number.hello_world
|
||||
id: ha_hello_world_number
|
||||
- platform: homeassistant
|
||||
entity_id: input_number.hello_world
|
||||
id: ha_hello_world_input_number
|
||||
|
||||
sensor:
|
||||
- platform: homeassistant
|
||||
|
||||
@@ -21,12 +21,6 @@ api:
|
||||
- number.set:
|
||||
id: ha_number
|
||||
value: 42.5
|
||||
- number.set:
|
||||
id: ha_real_number
|
||||
value: 12.5
|
||||
- number.set:
|
||||
id: ha_real_input_number
|
||||
value: 7
|
||||
- switch.turn_on: ha_switch
|
||||
- switch.turn_off: ha_switch
|
||||
- logger.log: "=== All tests completed ==="
|
||||
@@ -135,13 +129,6 @@ text_sensor:
|
||||
|
||||
# Number component for testing HA number control
|
||||
number:
|
||||
# Real homeassistant numbers: the action used depends on the entity domain
|
||||
- platform: homeassistant
|
||||
id: ha_real_number
|
||||
entity_id: number.test_real_number
|
||||
- platform: homeassistant
|
||||
id: ha_real_input_number
|
||||
entity_id: input_number.test_real_input_number
|
||||
- platform: template
|
||||
name: "HA Controlled Number"
|
||||
id: ha_number
|
||||
|
||||
@@ -86,15 +86,6 @@ async def test_api_homeassistant(
|
||||
"nonexistent.action_for_error_test": loop.create_future(), # error_test_call
|
||||
}
|
||||
|
||||
# Futures keyed by (service, entity_id) for the real homeassistant numbers
|
||||
ha_real_number_futures = {
|
||||
("number.set_value", "number.test_real_number"): loop.create_future(),
|
||||
(
|
||||
"input_number.set_value",
|
||||
"input_number.test_real_input_number",
|
||||
): loop.create_future(),
|
||||
}
|
||||
|
||||
# Future for error message test
|
||||
action_error_received_future = loop.create_future()
|
||||
|
||||
@@ -111,10 +102,6 @@ async def test_api_homeassistant(
|
||||
if not future.done():
|
||||
future.set_result(service_call)
|
||||
|
||||
key = (service_call.service, service_call.data.get("entity_id"))
|
||||
if key in ha_real_number_futures and not ha_real_number_futures[key].done():
|
||||
ha_real_number_futures[key].set_result(service_call)
|
||||
|
||||
# Immediately respond to the error test call so the test can proceed
|
||||
# This needs to happen synchronously so ESPHome receives the response
|
||||
# before logging "=== All tests completed ==="
|
||||
@@ -214,16 +201,6 @@ async def test_api_homeassistant(
|
||||
# Empty state with valid entity should work (use different entity to not interfere with test)
|
||||
client.send_home_assistant_state("sensor.edge_case_empty_state", "", "")
|
||||
|
||||
# The real homeassistant numbers reject values outside min/max, which
|
||||
# default to 0 until Home Assistant provides them
|
||||
for entity in (
|
||||
"number.test_real_number",
|
||||
"input_number.test_real_input_number",
|
||||
):
|
||||
client.send_home_assistant_state(entity, "min", "0")
|
||||
client.send_home_assistant_state(entity, "max", "100")
|
||||
client.send_home_assistant_state(entity, "step", "0.5")
|
||||
|
||||
# List entities and services
|
||||
_, services = await client.list_entities_services()
|
||||
|
||||
@@ -357,21 +334,6 @@ async def test_api_homeassistant(
|
||||
# The value might be formatted with trailing zeros
|
||||
assert float(number_call.data["value"]) == 42.5
|
||||
|
||||
# 7b. Real homeassistant numbers pick the action from the entity domain
|
||||
real_number_call = await asyncio.wait_for(
|
||||
ha_real_number_futures[("number.set_value", "number.test_real_number")],
|
||||
timeout=2.0,
|
||||
)
|
||||
assert float(real_number_call.data["value"]) == 12.5
|
||||
|
||||
real_input_number_call = await asyncio.wait_for(
|
||||
ha_real_number_futures[
|
||||
("input_number.set_value", "input_number.test_real_input_number")
|
||||
],
|
||||
timeout=2.0,
|
||||
)
|
||||
assert float(real_input_number_call.data["value"]) == 7
|
||||
|
||||
# 8. HA Switch service calls
|
||||
switch_on_call = await asyncio.wait_for(
|
||||
service_call_futures["switch.turn_on"], timeout=2.0
|
||||
|
||||
Reference in new issue
Block a user