mirror of
https://github.com/esphome/esphome.git
synced 2025-11-03 00:21:56 +00:00
Compare commits
26 Commits
jesserockz
...
2025.7.5
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
68c0aa4d6d | ||
|
|
d29cae9c3b | ||
|
|
532e3e370f | ||
|
|
da573a217d | ||
|
|
a9b27d1966 | ||
|
|
0aa3c9685e | ||
|
|
d6b222c370 | ||
|
|
573dad1736 | ||
|
|
3a6cc0ea3d | ||
|
|
2f9475a927 | ||
|
|
8dce7b0905 | ||
|
|
8b0ad3072f | ||
|
|
93028a4d90 | ||
|
|
c9793f3741 | ||
|
|
2b5cceda58 | ||
|
|
dc26ed9c46 | ||
|
|
8674012406 | ||
|
|
ae12deff87 | ||
|
|
cb6acfe24b | ||
|
|
fc8c5a7438 | ||
|
|
f8777d3b66 | ||
|
|
76e75f4cdc | ||
|
|
896d7f8f76 | ||
|
|
d92ee563f2 | ||
|
|
d6ff790823 | ||
|
|
7ac60c15dc |
2
Doxyfile
2
Doxyfile
@@ -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 = 2025.7.2
|
||||
PROJECT_NUMBER = 2025.7.5
|
||||
|
||||
# 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
|
||||
|
||||
@@ -657,10 +657,16 @@ class APIConnection : public APIServerConnection {
|
||||
bool send_message_smart_(EntityBase *entity, MessageCreatorPtr creator, uint8_t message_type,
|
||||
uint8_t estimated_size) {
|
||||
// Try to send immediately if:
|
||||
// 1. We should try to send immediately (should_try_send_immediately = true)
|
||||
// 2. Batch delay is 0 (user has opted in to immediate sending)
|
||||
// 3. Buffer has space available
|
||||
if (this->flags_.should_try_send_immediately && this->get_batch_delay_ms_() == 0 &&
|
||||
// 1. It's an UpdateStateResponse (always send immediately to handle cases where
|
||||
// the main loop is blocked, e.g., during OTA updates)
|
||||
// 2. OR: We should try to send immediately (should_try_send_immediately = true)
|
||||
// AND Batch delay is 0 (user has opted in to immediate sending)
|
||||
// 3. AND: Buffer has space available
|
||||
if ((
|
||||
#ifdef USE_UPDATE
|
||||
message_type == UpdateStateResponse::MESSAGE_TYPE ||
|
||||
#endif
|
||||
(this->flags_.should_try_send_immediately && this->get_batch_delay_ms_() == 0)) &&
|
||||
this->helper_->can_write_without_blocking()) {
|
||||
// Now actually encode and send
|
||||
if (creator(entity, this, MAX_BATCH_PACKET_SIZE, true) &&
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import esp32, i2c
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ID, CONF_SAMPLE_RATE, CONF_TEMPERATURE_OFFSET
|
||||
from esphome.const import CONF_ID, CONF_SAMPLE_RATE, CONF_TEMPERATURE_OFFSET, Framework
|
||||
|
||||
CODEOWNERS = ["@trvrnrth"]
|
||||
DEPENDENCIES = ["i2c"]
|
||||
@@ -56,7 +56,15 @@ CONFIG_SCHEMA = cv.All(
|
||||
): cv.positive_time_period_minutes,
|
||||
}
|
||||
).extend(i2c.i2c_device_schema(0x76)),
|
||||
cv.only_with_arduino,
|
||||
cv.only_with_framework(
|
||||
frameworks=Framework.ARDUINO,
|
||||
suggestions={
|
||||
Framework.ESP_IDF: (
|
||||
"bme68x_bsec2_i2c",
|
||||
"sensor/bme68x_bsec2",
|
||||
)
|
||||
},
|
||||
),
|
||||
cv.Any(
|
||||
cv.only_on_esp8266,
|
||||
cv.All(
|
||||
|
||||
@@ -16,6 +16,8 @@ namespace esp32_touch {
|
||||
|
||||
static const char *const TAG = "esp32_touch";
|
||||
|
||||
static const uint32_t SETUP_MODE_THRESHOLD = 0xFFFF;
|
||||
|
||||
void ESP32TouchComponent::setup() {
|
||||
// Create queue for touch events
|
||||
// Queue size calculation: children * 4 allows for burst scenarios where ISR
|
||||
@@ -44,7 +46,11 @@ void ESP32TouchComponent::setup() {
|
||||
|
||||
// Configure each touch pad
|
||||
for (auto *child : this->children_) {
|
||||
touch_pad_config(child->get_touch_pad(), child->get_threshold());
|
||||
if (this->setup_mode_) {
|
||||
touch_pad_config(child->get_touch_pad(), SETUP_MODE_THRESHOLD);
|
||||
} else {
|
||||
touch_pad_config(child->get_touch_pad(), child->get_threshold());
|
||||
}
|
||||
}
|
||||
|
||||
// Register ISR handler
|
||||
@@ -114,8 +120,8 @@ void ESP32TouchComponent::loop() {
|
||||
child->publish_state(new_state);
|
||||
// Original ESP32: ISR only fires when touched, release is detected by timeout
|
||||
// Note: ESP32 v1 uses inverted logic - touched when value < threshold
|
||||
ESP_LOGV(TAG, "Touch Pad '%s' state: ON (value: %" PRIu32 " < threshold: %" PRIu32 ")",
|
||||
child->get_name().c_str(), event.value, child->get_threshold());
|
||||
ESP_LOGV(TAG, "Touch Pad '%s' state: %s (value: %" PRIu32 " < threshold: %" PRIu32 ")",
|
||||
child->get_name().c_str(), ONOFF(new_state), event.value, child->get_threshold());
|
||||
}
|
||||
break; // Exit inner loop after processing matching pad
|
||||
}
|
||||
@@ -188,11 +194,6 @@ void IRAM_ATTR ESP32TouchComponent::touch_isr_handler(void *arg) {
|
||||
// as any pad remains touched. This allows us to detect both new touches and
|
||||
// continued touches, but releases must be detected by timeout in the main loop.
|
||||
|
||||
// IMPORTANT: ESP32 v1 touch detection logic - INVERTED compared to v2!
|
||||
// ESP32 v1: Touch is detected when capacitance INCREASES, causing the measured value to DECREASE
|
||||
// Therefore: touched = (value < threshold)
|
||||
// This is opposite to ESP32-S2/S3 v2 where touched = (value > threshold)
|
||||
|
||||
// Process all configured pads to check their current state
|
||||
// Note: ESP32 v1 doesn't tell us which specific pad triggered the interrupt,
|
||||
// so we must scan all configured pads to find which ones were touched
|
||||
@@ -211,11 +212,16 @@ void IRAM_ATTR ESP32TouchComponent::touch_isr_handler(void *arg) {
|
||||
}
|
||||
|
||||
// Skip pads that aren’t in the trigger mask
|
||||
bool is_touched = (mask >> pad) & 1;
|
||||
if (!is_touched) {
|
||||
if (((mask >> pad) & 1) == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// IMPORTANT: ESP32 v1 touch detection logic - INVERTED compared to v2!
|
||||
// ESP32 v1: Touch is detected when capacitance INCREASES, causing the measured value to DECREASE
|
||||
// Therefore: touched = (value < threshold)
|
||||
// This is opposite to ESP32-S2/S3 v2 where touched = (value > threshold)
|
||||
bool is_touched = value < child->get_threshold();
|
||||
|
||||
// Always send the current state - the main loop will filter for changes
|
||||
// We send both touched and untouched states because the ISR doesn't
|
||||
// track previous state (to keep ISR fast and simple)
|
||||
|
||||
@@ -2,7 +2,13 @@ from esphome import pins
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import fastled_base
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_CHIPSET, CONF_NUM_LEDS, CONF_PIN, CONF_RGB_ORDER
|
||||
from esphome.const import (
|
||||
CONF_CHIPSET,
|
||||
CONF_NUM_LEDS,
|
||||
CONF_PIN,
|
||||
CONF_RGB_ORDER,
|
||||
Framework,
|
||||
)
|
||||
|
||||
AUTO_LOAD = ["fastled_base"]
|
||||
|
||||
@@ -48,13 +54,22 @@ CONFIG_SCHEMA = cv.All(
|
||||
cv.Required(CONF_PIN): pins.internal_gpio_output_pin_number,
|
||||
}
|
||||
),
|
||||
_validate,
|
||||
cv.only_with_framework(
|
||||
frameworks=Framework.ARDUINO,
|
||||
suggestions={
|
||||
Framework.ESP_IDF: (
|
||||
"esp32_rmt_led_strip",
|
||||
"light/esp32_rmt_led_strip",
|
||||
)
|
||||
},
|
||||
),
|
||||
cv.require_framework_version(
|
||||
esp8266_arduino=cv.Version(2, 7, 4),
|
||||
esp32_arduino=cv.Version(99, 0, 0),
|
||||
max_version=True,
|
||||
extra_message="Please see note on documentation for FastLED",
|
||||
),
|
||||
_validate,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ from esphome.const import (
|
||||
CONF_DATA_RATE,
|
||||
CONF_NUM_LEDS,
|
||||
CONF_RGB_ORDER,
|
||||
Framework,
|
||||
)
|
||||
|
||||
AUTO_LOAD = ["fastled_base"]
|
||||
@@ -33,6 +34,15 @@ CONFIG_SCHEMA = cv.All(
|
||||
cv.Optional(CONF_DATA_RATE): cv.frequency,
|
||||
}
|
||||
),
|
||||
cv.only_with_framework(
|
||||
frameworks=Framework.ARDUINO,
|
||||
suggestions={
|
||||
Framework.ESP_IDF: (
|
||||
"spi_led_strip",
|
||||
"light/spi_led_strip",
|
||||
)
|
||||
},
|
||||
),
|
||||
cv.require_framework_version(
|
||||
esp8266_arduino=cv.Version(2, 7, 4),
|
||||
esp32_arduino=cv.Version(99, 0, 0),
|
||||
|
||||
@@ -15,6 +15,7 @@ from freetype import (
|
||||
FT_LOAD_RENDER,
|
||||
FT_LOAD_TARGET_MONO,
|
||||
Face,
|
||||
FT_Exception,
|
||||
ft_pixel_mode_mono,
|
||||
)
|
||||
import requests
|
||||
@@ -94,7 +95,14 @@ class FontCache(MutableMapping):
|
||||
return self.store[self._keytransform(item)]
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
self.store[self._keytransform(key)] = Face(str(value))
|
||||
transformed = self._keytransform(key)
|
||||
try:
|
||||
self.store[transformed] = Face(str(value))
|
||||
except FT_Exception as exc:
|
||||
file = transformed.split(":", 1)
|
||||
raise cv.Invalid(
|
||||
f"{file[0].capitalize()} {file[1]} is not a valid font file"
|
||||
) from exc
|
||||
|
||||
|
||||
FONT_CACHE = FontCache()
|
||||
|
||||
@@ -4,7 +4,13 @@ from esphome import pins
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import binary_sensor
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ID, CONF_NAME, CONF_NUMBER, CONF_PIN
|
||||
from esphome.const import (
|
||||
CONF_ALLOW_OTHER_USES,
|
||||
CONF_ID,
|
||||
CONF_NAME,
|
||||
CONF_NUMBER,
|
||||
CONF_PIN,
|
||||
)
|
||||
from esphome.core import CORE
|
||||
|
||||
from .. import gpio_ns
|
||||
@@ -76,6 +82,18 @@ async def to_code(config):
|
||||
)
|
||||
use_interrupt = False
|
||||
|
||||
# Check if pin is shared with other components (allow_other_uses)
|
||||
# When a pin is shared, interrupts can interfere with other components
|
||||
# (e.g., duty_cycle sensor) that need to monitor the pin's state changes
|
||||
if use_interrupt and config[CONF_PIN].get(CONF_ALLOW_OTHER_USES, False):
|
||||
_LOGGER.info(
|
||||
"GPIO binary_sensor '%s': Disabling interrupts because pin %s is shared with other components. "
|
||||
"The sensor will use polling mode for compatibility with other pin uses.",
|
||||
config.get(CONF_NAME, config[CONF_ID]),
|
||||
config[CONF_PIN][CONF_NUMBER],
|
||||
)
|
||||
use_interrupt = False
|
||||
|
||||
cg.add(var.set_use_interrupt(use_interrupt))
|
||||
if use_interrupt:
|
||||
cg.add(var.set_interrupt_type(config[CONF_INTERRUPT_TYPE]))
|
||||
|
||||
@@ -8,6 +8,8 @@ namespace gt911 {
|
||||
|
||||
static const char *const TAG = "gt911.touchscreen";
|
||||
|
||||
static const uint8_t PRIMARY_ADDRESS = 0x5D; // default I2C address for GT911
|
||||
static const uint8_t SECONDARY_ADDRESS = 0x14; // secondary I2C address for GT911
|
||||
static const uint8_t GET_TOUCH_STATE[2] = {0x81, 0x4E};
|
||||
static const uint8_t CLEAR_TOUCH_STATE[3] = {0x81, 0x4E, 0x00};
|
||||
static const uint8_t GET_TOUCHES[2] = {0x81, 0x4F};
|
||||
@@ -18,8 +20,7 @@ static const size_t MAX_BUTTONS = 4; // max number of buttons scanned
|
||||
|
||||
#define ERROR_CHECK(err) \
|
||||
if ((err) != i2c::ERROR_OK) { \
|
||||
ESP_LOGE(TAG, "Failed to communicate!"); \
|
||||
this->status_set_warning(); \
|
||||
this->status_set_warning("Communication failure"); \
|
||||
return; \
|
||||
}
|
||||
|
||||
@@ -30,31 +31,31 @@ void GT911Touchscreen::setup() {
|
||||
this->reset_pin_->setup();
|
||||
this->reset_pin_->digital_write(false);
|
||||
if (this->interrupt_pin_ != nullptr) {
|
||||
// The interrupt pin is used as an input during reset to select the I2C address.
|
||||
// temporarily set the interrupt pin to output to control address selection
|
||||
this->interrupt_pin_->pin_mode(gpio::FLAG_OUTPUT);
|
||||
this->interrupt_pin_->setup();
|
||||
this->interrupt_pin_->digital_write(false);
|
||||
}
|
||||
delay(2);
|
||||
this->reset_pin_->digital_write(true);
|
||||
delay(50); // NOLINT
|
||||
if (this->interrupt_pin_ != nullptr) {
|
||||
this->interrupt_pin_->pin_mode(gpio::FLAG_INPUT);
|
||||
this->interrupt_pin_->setup();
|
||||
}
|
||||
}
|
||||
if (this->interrupt_pin_ != nullptr) {
|
||||
// set pre-configured input mode
|
||||
this->interrupt_pin_->setup();
|
||||
}
|
||||
|
||||
// check the configuration of the int line.
|
||||
uint8_t data[4];
|
||||
err = this->write(GET_SWITCHES, 2);
|
||||
err = this->write(GET_SWITCHES, sizeof(GET_SWITCHES));
|
||||
if (err != i2c::ERROR_OK && this->address_ == PRIMARY_ADDRESS) {
|
||||
this->address_ = SECONDARY_ADDRESS;
|
||||
err = this->write(GET_SWITCHES, sizeof(GET_SWITCHES));
|
||||
}
|
||||
if (err == i2c::ERROR_OK) {
|
||||
err = this->read(data, 1);
|
||||
if (err == i2c::ERROR_OK) {
|
||||
ESP_LOGD(TAG, "Read from switches: 0x%02X", data[0]);
|
||||
ESP_LOGD(TAG, "Read from switches at address 0x%02X: 0x%02X", this->address_, data[0]);
|
||||
if (this->interrupt_pin_ != nullptr) {
|
||||
// datasheet says NOT to use pullup/down on the int line.
|
||||
this->interrupt_pin_->pin_mode(gpio::FLAG_INPUT);
|
||||
this->interrupt_pin_->setup();
|
||||
this->attach_interrupt_(this->interrupt_pin_,
|
||||
(data[0] & 1) ? gpio::INTERRUPT_FALLING_EDGE : gpio::INTERRUPT_RISING_EDGE);
|
||||
}
|
||||
@@ -63,7 +64,7 @@ void GT911Touchscreen::setup() {
|
||||
if (this->x_raw_max_ == 0 || this->y_raw_max_ == 0) {
|
||||
// no calibration? Attempt to read the max values from the touchscreen.
|
||||
if (err == i2c::ERROR_OK) {
|
||||
err = this->write(GET_MAX_VALUES, 2);
|
||||
err = this->write(GET_MAX_VALUES, sizeof(GET_MAX_VALUES));
|
||||
if (err == i2c::ERROR_OK) {
|
||||
err = this->read(data, sizeof(data));
|
||||
if (err == i2c::ERROR_OK) {
|
||||
@@ -75,15 +76,12 @@ void GT911Touchscreen::setup() {
|
||||
}
|
||||
}
|
||||
if (err != i2c::ERROR_OK) {
|
||||
ESP_LOGE(TAG, "Failed to read calibration values from touchscreen!");
|
||||
this->mark_failed();
|
||||
this->mark_failed("Failed to read calibration");
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (err != i2c::ERROR_OK) {
|
||||
ESP_LOGE(TAG, "Failed to communicate!");
|
||||
this->mark_failed();
|
||||
return;
|
||||
this->mark_failed("Failed to communicate");
|
||||
}
|
||||
|
||||
ESP_LOGCONFIG(TAG, "GT911 Touchscreen setup complete");
|
||||
@@ -94,7 +92,7 @@ void GT911Touchscreen::update_touches() {
|
||||
uint8_t touch_state = 0;
|
||||
uint8_t data[MAX_TOUCHES + 1][8]; // 8 bytes each for each point, plus extra space for the key byte
|
||||
|
||||
err = this->write(GET_TOUCH_STATE, sizeof(GET_TOUCH_STATE), false);
|
||||
err = this->write(GET_TOUCH_STATE, sizeof(GET_TOUCH_STATE));
|
||||
ERROR_CHECK(err);
|
||||
err = this->read(&touch_state, 1);
|
||||
ERROR_CHECK(err);
|
||||
@@ -106,7 +104,7 @@ void GT911Touchscreen::update_touches() {
|
||||
return;
|
||||
}
|
||||
|
||||
err = this->write(GET_TOUCHES, sizeof(GET_TOUCHES), false);
|
||||
err = this->write(GET_TOUCHES, sizeof(GET_TOUCHES));
|
||||
ERROR_CHECK(err);
|
||||
// num_of_touches is guaranteed to be 0..5. Also read the key data
|
||||
err = this->read(data[0], sizeof(data[0]) * num_of_touches + 1);
|
||||
@@ -132,6 +130,7 @@ void GT911Touchscreen::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "GT911 Touchscreen:");
|
||||
LOG_I2C_DEVICE(this);
|
||||
LOG_PIN(" Interrupt Pin: ", this->interrupt_pin_);
|
||||
LOG_PIN(" Reset Pin: ", this->reset_pin_);
|
||||
}
|
||||
|
||||
} // namespace gt911
|
||||
|
||||
@@ -24,9 +24,6 @@ static const uint32_t READ_DURATION_MS = 16;
|
||||
static const size_t TASK_STACK_SIZE = 4096;
|
||||
static const ssize_t TASK_PRIORITY = 23;
|
||||
|
||||
// Use an exponential moving average to correct a DC offset with weight factor 1/1000
|
||||
static const int32_t DC_OFFSET_MOVING_AVERAGE_COEFFICIENT_DENOMINATOR = 1000;
|
||||
|
||||
static const char *const TAG = "i2s_audio.microphone";
|
||||
|
||||
enum MicrophoneEventGroupBits : uint32_t {
|
||||
@@ -382,26 +379,57 @@ void I2SAudioMicrophone::mic_task(void *params) {
|
||||
}
|
||||
|
||||
void I2SAudioMicrophone::fix_dc_offset_(std::vector<uint8_t> &data) {
|
||||
/**
|
||||
* From https://www.musicdsp.org/en/latest/Filters/135-dc-filter.html:
|
||||
*
|
||||
* y(n) = x(n) - x(n-1) + R * y(n-1)
|
||||
* R = 1 - (pi * 2 * frequency / samplerate)
|
||||
*
|
||||
* From https://en.wikipedia.org/wiki/Hearing_range:
|
||||
* The human range is commonly given as 20Hz up.
|
||||
*
|
||||
* From https://en.wikipedia.org/wiki/High-resolution_audio:
|
||||
* A reasonable upper bound for sample rate seems to be 96kHz.
|
||||
*
|
||||
* Calculate R value for 20Hz on a 96kHz sample rate:
|
||||
* R = 1 - (pi * 2 * 20 / 96000)
|
||||
* R = 0.9986910031
|
||||
*
|
||||
* Transform floating point to bit-shifting approximation:
|
||||
* output = input - prev_input + R * prev_output
|
||||
* output = input - prev_input + (prev_output - (prev_output >> S))
|
||||
*
|
||||
* Approximate bit-shift value S from R:
|
||||
* R = 1 - (1 >> S)
|
||||
* R = 1 - (1 / 2^S)
|
||||
* R = 1 - 2^-S
|
||||
* 0.9986910031 = 1 - 2^-S
|
||||
* S = 9.57732 ~= 10
|
||||
*
|
||||
* Actual R from S:
|
||||
* R = 1 - 2^-10 = 0.9990234375
|
||||
*
|
||||
* Confirm this has effect outside human hearing on 96000kHz sample:
|
||||
* 0.9990234375 = 1 - (pi * 2 * f / 96000)
|
||||
* f = 14.9208Hz
|
||||
*
|
||||
* Confirm this has effect outside human hearing on PDM 16kHz sample:
|
||||
* 0.9990234375 = 1 - (pi * 2 * f / 16000)
|
||||
* f = 2.4868Hz
|
||||
*
|
||||
*/
|
||||
const uint8_t dc_filter_shift = 10;
|
||||
const size_t bytes_per_sample = this->audio_stream_info_.samples_to_bytes(1);
|
||||
const uint32_t total_samples = this->audio_stream_info_.bytes_to_samples(data.size());
|
||||
|
||||
if (total_samples == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
int64_t offset_accumulator = 0;
|
||||
for (uint32_t sample_index = 0; sample_index < total_samples; ++sample_index) {
|
||||
const uint32_t byte_index = sample_index * bytes_per_sample;
|
||||
int32_t sample = audio::unpack_audio_sample_to_q31(&data[byte_index], bytes_per_sample);
|
||||
offset_accumulator += sample;
|
||||
sample -= this->dc_offset_;
|
||||
audio::pack_q31_as_audio_sample(sample, &data[byte_index], bytes_per_sample);
|
||||
int32_t input = audio::unpack_audio_sample_to_q31(&data[byte_index], bytes_per_sample);
|
||||
int32_t output = input - this->dc_offset_prev_input_ +
|
||||
(this->dc_offset_prev_output_ - (this->dc_offset_prev_output_ >> dc_filter_shift));
|
||||
this->dc_offset_prev_input_ = input;
|
||||
this->dc_offset_prev_output_ = output;
|
||||
audio::pack_q31_as_audio_sample(output, &data[byte_index], bytes_per_sample);
|
||||
}
|
||||
|
||||
const int32_t new_offset = offset_accumulator / total_samples;
|
||||
this->dc_offset_ = new_offset / DC_OFFSET_MOVING_AVERAGE_COEFFICIENT_DENOMINATOR +
|
||||
(DC_OFFSET_MOVING_AVERAGE_COEFFICIENT_DENOMINATOR - 1) * this->dc_offset_ /
|
||||
DC_OFFSET_MOVING_AVERAGE_COEFFICIENT_DENOMINATOR;
|
||||
}
|
||||
|
||||
size_t I2SAudioMicrophone::read_(uint8_t *buf, size_t len, TickType_t ticks_to_wait) {
|
||||
|
||||
@@ -82,7 +82,8 @@ class I2SAudioMicrophone : public I2SAudioIn, public microphone::Microphone, pub
|
||||
|
||||
bool correct_dc_offset_;
|
||||
bool locked_driver_{false};
|
||||
int32_t dc_offset_{0};
|
||||
int32_t dc_offset_prev_input_{0};
|
||||
int32_t dc_offset_prev_output_{0};
|
||||
};
|
||||
|
||||
} // namespace i2s_audio
|
||||
|
||||
@@ -477,10 +477,11 @@ void LD2450Component::handle_periodic_data_() {
|
||||
// X
|
||||
start = TARGET_X + index * 8;
|
||||
is_moving = false;
|
||||
// tx is used for further calculations, so always needs to be populated
|
||||
val = ld2450::decode_coordinate(this->buffer_data_[start], this->buffer_data_[start + 1]);
|
||||
tx = val;
|
||||
sensor::Sensor *sx = this->move_x_sensors_[index];
|
||||
if (sx != nullptr) {
|
||||
val = ld2450::decode_coordinate(this->buffer_data_[start], this->buffer_data_[start + 1]);
|
||||
tx = val;
|
||||
if (this->cached_target_data_[index].x != val) {
|
||||
sx->publish_state(val);
|
||||
this->cached_target_data_[index].x = val;
|
||||
@@ -488,10 +489,11 @@ void LD2450Component::handle_periodic_data_() {
|
||||
}
|
||||
// Y
|
||||
start = TARGET_Y + index * 8;
|
||||
// ty is used for further calculations, so always needs to be populated
|
||||
val = ld2450::decode_coordinate(this->buffer_data_[start], this->buffer_data_[start + 1]);
|
||||
ty = val;
|
||||
sensor::Sensor *sy = this->move_y_sensors_[index];
|
||||
if (sy != nullptr) {
|
||||
val = ld2450::decode_coordinate(this->buffer_data_[start], this->buffer_data_[start + 1]);
|
||||
ty = val;
|
||||
if (this->cached_target_data_[index].y != val) {
|
||||
sy->publish_state(val);
|
||||
this->cached_target_data_[index].y = val;
|
||||
|
||||
@@ -400,6 +400,7 @@ CONF_LOGGER_LOG = "logger.log"
|
||||
LOGGER_LOG_ACTION_SCHEMA = cv.All(
|
||||
cv.maybe_simple_value(
|
||||
{
|
||||
cv.GenerateID(CONF_LOGGER_ID): cv.use_id(Logger),
|
||||
cv.Required(CONF_FORMAT): cv.string,
|
||||
cv.Optional(CONF_ARGS, default=list): cv.ensure_list(cv.lambda_),
|
||||
cv.Optional(CONF_LEVEL, default="DEBUG"): cv.one_of(
|
||||
|
||||
@@ -15,7 +15,7 @@ from ..defines import (
|
||||
TILE_DIRECTIONS,
|
||||
literal,
|
||||
)
|
||||
from ..lv_validation import animated, lv_int
|
||||
from ..lv_validation import animated, lv_int, lv_pct
|
||||
from ..lvcode import lv, lv_assign, lv_expr, lv_obj, lv_Pvariable
|
||||
from ..schemas import container_schema
|
||||
from ..types import LV_EVENT, LvType, ObjUpdateAction, lv_obj_t, lv_obj_t_ptr
|
||||
@@ -41,8 +41,8 @@ TILEVIEW_SCHEMA = cv.Schema(
|
||||
container_schema(
|
||||
obj_spec,
|
||||
{
|
||||
cv.Required(CONF_ROW): lv_int,
|
||||
cv.Required(CONF_COLUMN): lv_int,
|
||||
cv.Required(CONF_ROW): cv.positive_int,
|
||||
cv.Required(CONF_COLUMN): cv.positive_int,
|
||||
cv.GenerateID(): cv.declare_id(lv_tile_t),
|
||||
cv.Optional(CONF_DIR, default="ALL"): TILE_DIRECTIONS.several_of,
|
||||
},
|
||||
@@ -63,21 +63,29 @@ class TileviewType(WidgetType):
|
||||
)
|
||||
|
||||
async def to_code(self, w: Widget, config: dict):
|
||||
for tile_conf in config.get(CONF_TILES, ()):
|
||||
tiles = config[CONF_TILES]
|
||||
for tile_conf in tiles:
|
||||
w_id = tile_conf[CONF_ID]
|
||||
tile_obj = lv_Pvariable(lv_obj_t, w_id)
|
||||
tile = Widget.create(w_id, tile_obj, tile_spec, tile_conf)
|
||||
dirs = tile_conf[CONF_DIR]
|
||||
if isinstance(dirs, list):
|
||||
dirs = "|".join(dirs)
|
||||
row_pos = tile_conf[CONF_ROW]
|
||||
col_pos = tile_conf[CONF_COLUMN]
|
||||
lv_assign(
|
||||
tile_obj,
|
||||
lv_expr.tileview_add_tile(
|
||||
w.obj, tile_conf[CONF_COLUMN], tile_conf[CONF_ROW], literal(dirs)
|
||||
),
|
||||
lv_expr.tileview_add_tile(w.obj, col_pos, row_pos, literal(dirs)),
|
||||
)
|
||||
# Bugfix for LVGL 8.x
|
||||
lv_obj.set_pos(tile_obj, lv_pct(col_pos * 100), lv_pct(row_pos * 100))
|
||||
await set_obj_properties(tile, tile_conf)
|
||||
await add_widgets(tile, tile_conf)
|
||||
if tiles:
|
||||
# Set the first tile as active
|
||||
lv_obj.set_tile_id(
|
||||
w.obj, tiles[0][CONF_COLUMN], tiles[0][CONF_ROW], literal("LV_ANIM_OFF")
|
||||
)
|
||||
|
||||
|
||||
tileview_spec = TileviewType()
|
||||
|
||||
@@ -15,6 +15,7 @@ from esphome.const import (
|
||||
CONF_PIN,
|
||||
CONF_TYPE,
|
||||
CONF_VARIANT,
|
||||
Framework,
|
||||
)
|
||||
from esphome.core import CORE
|
||||
|
||||
@@ -162,7 +163,15 @@ def _validate_method(value):
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.only_with_arduino,
|
||||
cv.only_with_framework(
|
||||
frameworks=Framework.ARDUINO,
|
||||
suggestions={
|
||||
Framework.ESP_IDF: (
|
||||
"esp32_rmt_led_strip",
|
||||
"light/esp32_rmt_led_strip",
|
||||
)
|
||||
},
|
||||
),
|
||||
cv.require_framework_version(
|
||||
esp8266_arduino=cv.Version(2, 4, 0),
|
||||
esp32_arduino=cv.Version(0, 0, 0),
|
||||
|
||||
@@ -60,6 +60,20 @@ RemoteReceiverComponent = remote_receiver_ns.class_(
|
||||
)
|
||||
|
||||
|
||||
def validate_config(config):
|
||||
if CORE.is_esp32:
|
||||
variant = esp32.get_esp32_variant()
|
||||
if variant in (esp32.const.VARIANT_ESP32, esp32.const.VARIANT_ESP32S2):
|
||||
max_idle = 65535
|
||||
else:
|
||||
max_idle = 32767
|
||||
if CONF_CLOCK_RESOLUTION in config:
|
||||
max_idle = int(max_idle * 1000000 / config[CONF_CLOCK_RESOLUTION])
|
||||
if config[CONF_IDLE].total_microseconds > max_idle:
|
||||
raise cv.Invalid(f"config 'idle' exceeds the maximum value of {max_idle}us")
|
||||
return config
|
||||
|
||||
|
||||
def validate_tolerance(value):
|
||||
if isinstance(value, dict):
|
||||
return TOLERANCE_SCHEMA(value)
|
||||
@@ -136,7 +150,9 @@ CONFIG_SCHEMA = remote_base.validate_triggers(
|
||||
cv.boolean,
|
||||
),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
)
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
.add_extra(validate_config)
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -86,10 +86,9 @@ void RemoteReceiverComponent::setup() {
|
||||
|
||||
uint32_t event_size = sizeof(rmt_rx_done_event_data_t);
|
||||
uint32_t max_filter_ns = 255u * 1000 / (RMT_CLK_FREQ / 1000000);
|
||||
uint32_t max_idle_ns = 65535u * 1000;
|
||||
memset(&this->store_.config, 0, sizeof(this->store_.config));
|
||||
this->store_.config.signal_range_min_ns = std::min(this->filter_us_ * 1000, max_filter_ns);
|
||||
this->store_.config.signal_range_max_ns = std::min(this->idle_us_ * 1000, max_idle_ns);
|
||||
this->store_.config.signal_range_max_ns = this->idle_us_ * 1000;
|
||||
this->store_.filter_symbols = this->filter_symbols_;
|
||||
this->store_.receive_size = this->receive_symbols_ * sizeof(rmt_symbol_word_t);
|
||||
this->store_.buffer_size = std::max((event_size + this->store_.receive_size) * 2, this->buffer_size_);
|
||||
|
||||
@@ -48,6 +48,9 @@ void Sdl::draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *
|
||||
}
|
||||
|
||||
void Sdl::draw_pixel_at(int x, int y, Color color) {
|
||||
if (!this->get_clipping().inside(x, y))
|
||||
return;
|
||||
|
||||
SDL_Rect rect{x, y, 1, 1};
|
||||
auto data = (display::ColorUtil::color_to_565(color, display::COLOR_ORDER_RGB));
|
||||
SDL_UpdateTexture(this->texture_, &rect, &data, 2);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import fan
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_OUTPUT_ID, CONF_SPEED_COUNT, CONF_SWITCH_DATAPOINT
|
||||
from esphome.const import CONF_ID, CONF_SPEED_COUNT, CONF_SWITCH_DATAPOINT
|
||||
|
||||
from .. import CONF_TUYA_ID, Tuya, tuya_ns
|
||||
|
||||
@@ -14,9 +14,9 @@ CONF_DIRECTION_DATAPOINT = "direction_datapoint"
|
||||
TuyaFan = tuya_ns.class_("TuyaFan", cg.Component, fan.Fan)
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
fan.FAN_SCHEMA.extend(
|
||||
fan.fan_schema(TuyaFan)
|
||||
.extend(
|
||||
{
|
||||
cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(TuyaFan),
|
||||
cv.GenerateID(CONF_TUYA_ID): cv.use_id(Tuya),
|
||||
cv.Optional(CONF_OSCILLATION_DATAPOINT): cv.uint8_t,
|
||||
cv.Optional(CONF_SPEED_DATAPOINT): cv.uint8_t,
|
||||
@@ -24,7 +24,8 @@ CONFIG_SCHEMA = cv.All(
|
||||
cv.Optional(CONF_DIRECTION_DATAPOINT): cv.uint8_t,
|
||||
cv.Optional(CONF_SPEED_COUNT, default=3): cv.int_range(min=1, max=256),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA),
|
||||
)
|
||||
.extend(cv.COMPONENT_SCHEMA),
|
||||
cv.has_at_least_one_key(CONF_SPEED_DATAPOINT, CONF_SWITCH_DATAPOINT),
|
||||
)
|
||||
|
||||
@@ -32,7 +33,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
async def to_code(config):
|
||||
parent = await cg.get_variable(config[CONF_TUYA_ID])
|
||||
|
||||
var = cg.new_Pvariable(config[CONF_OUTPUT_ID], parent, config[CONF_SPEED_COUNT])
|
||||
var = cg.new_Pvariable(config[CONF_ID], parent, config[CONF_SPEED_COUNT])
|
||||
await cg.register_component(var, config)
|
||||
await fan.register_fan(var, config)
|
||||
|
||||
|
||||
@@ -76,7 +76,7 @@ void OTARequestHandler::report_ota_progress_(AsyncWebServerRequest *request) {
|
||||
percentage = (this->ota_read_length_ * 100.0f) / request->contentLength();
|
||||
ESP_LOGD(TAG, "OTA in progress: %0.1f%%", percentage);
|
||||
} else {
|
||||
ESP_LOGD(TAG, "OTA in progress: %u bytes read", this->ota_read_length_);
|
||||
ESP_LOGD(TAG, "OTA in progress: %" PRIu32 " bytes read", this->ota_read_length_);
|
||||
}
|
||||
#ifdef USE_OTA_STATE_CALLBACK
|
||||
// Report progress - use call_deferred since we're in web server task
|
||||
@@ -171,7 +171,7 @@ void OTARequestHandler::handleUpload(AsyncWebServerRequest *request, const Strin
|
||||
|
||||
// Finalize
|
||||
if (final) {
|
||||
ESP_LOGD(TAG, "OTA final chunk: index=%zu, len=%zu, total_read=%u, contentLength=%zu", index, len,
|
||||
ESP_LOGD(TAG, "OTA final chunk: index=%zu, len=%zu, total_read=%" PRIu32 ", contentLength=%zu", index, len,
|
||||
this->ota_read_length_, request->contentLength());
|
||||
|
||||
// For Arduino framework, the Update library tracks expected size from firmware header
|
||||
|
||||
@@ -73,6 +73,7 @@ from esphome.const import (
|
||||
TYPE_GIT,
|
||||
TYPE_LOCAL,
|
||||
VALID_SUBSTITUTIONS_CHARACTERS,
|
||||
Framework,
|
||||
__version__ as ESPHOME_VERSION,
|
||||
)
|
||||
from esphome.core import (
|
||||
@@ -282,6 +283,38 @@ class FinalExternalInvalid(Invalid):
|
||||
"""Represents an invalid value in the final validation phase where the path should not be prepended."""
|
||||
|
||||
|
||||
@dataclass(frozen=True, order=True)
|
||||
class Version:
|
||||
major: int
|
||||
minor: int
|
||||
patch: int
|
||||
extra: str = ""
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.major}.{self.minor}.{self.patch}"
|
||||
|
||||
@classmethod
|
||||
def parse(cls, value: str) -> Version:
|
||||
match = re.match(r"^(\d+).(\d+).(\d+)-?(\w*)$", value)
|
||||
if match is None:
|
||||
raise ValueError(f"Not a valid version number {value}")
|
||||
major = int(match[1])
|
||||
minor = int(match[2])
|
||||
patch = int(match[3])
|
||||
extra = match[4] or ""
|
||||
return Version(major=major, minor=minor, patch=patch, extra=extra)
|
||||
|
||||
@property
|
||||
def is_beta(self) -> bool:
|
||||
"""Check if this version is a beta version."""
|
||||
return self.extra.startswith("b")
|
||||
|
||||
@property
|
||||
def is_dev(self) -> bool:
|
||||
"""Check if this version is a development version."""
|
||||
return self.extra.startswith("dev")
|
||||
|
||||
|
||||
def check_not_templatable(value):
|
||||
if isinstance(value, Lambda):
|
||||
raise Invalid("This option is not templatable!")
|
||||
@@ -619,16 +652,35 @@ def only_on(platforms):
|
||||
return validator_
|
||||
|
||||
|
||||
def only_with_framework(frameworks):
|
||||
def only_with_framework(
|
||||
frameworks: Framework | str | list[Framework | str], suggestions=None
|
||||
):
|
||||
"""Validate that this option can only be specified on the given frameworks."""
|
||||
if not isinstance(frameworks, list):
|
||||
frameworks = [frameworks]
|
||||
|
||||
frameworks = [Framework(framework) for framework in frameworks]
|
||||
|
||||
if suggestions is None:
|
||||
suggestions = {}
|
||||
|
||||
version = Version.parse(ESPHOME_VERSION)
|
||||
if version.is_beta:
|
||||
docs_format = "https://beta.esphome.io/components/{path}"
|
||||
elif version.is_dev:
|
||||
docs_format = "https://next.esphome.io/components/{path}"
|
||||
else:
|
||||
docs_format = "https://esphome.io/components/{path}"
|
||||
|
||||
def validator_(obj):
|
||||
if CORE.target_framework not in frameworks:
|
||||
raise Invalid(
|
||||
f"This feature is only available with frameworks {frameworks}"
|
||||
)
|
||||
err_str = f"This feature is only available with framework(s) {', '.join([framework.value for framework in frameworks])}"
|
||||
if suggestion := suggestions.get(CORE.target_framework, None):
|
||||
(component, docs_path) = suggestion
|
||||
err_str += f"\nPlease use '{component}'"
|
||||
if docs_path:
|
||||
err_str += f": {docs_format.format(path=docs_path)}"
|
||||
raise Invalid(err_str)
|
||||
return obj
|
||||
|
||||
return validator_
|
||||
@@ -637,8 +689,8 @@ def only_with_framework(frameworks):
|
||||
only_on_esp32 = only_on(PLATFORM_ESP32)
|
||||
only_on_esp8266 = only_on(PLATFORM_ESP8266)
|
||||
only_on_rp2040 = only_on(PLATFORM_RP2040)
|
||||
only_with_arduino = only_with_framework("arduino")
|
||||
only_with_esp_idf = only_with_framework("esp-idf")
|
||||
only_with_arduino = only_with_framework(Framework.ARDUINO)
|
||||
only_with_esp_idf = only_with_framework(Framework.ESP_IDF)
|
||||
|
||||
|
||||
# Adapted from:
|
||||
@@ -1965,26 +2017,6 @@ def source_refresh(value: str):
|
||||
return positive_time_period_seconds(value)
|
||||
|
||||
|
||||
@dataclass(frozen=True, order=True)
|
||||
class Version:
|
||||
major: int
|
||||
minor: int
|
||||
patch: int
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.major}.{self.minor}.{self.patch}"
|
||||
|
||||
@classmethod
|
||||
def parse(cls, value: str) -> Version:
|
||||
match = re.match(r"^(\d+).(\d+).(\d+)-?\w*$", value)
|
||||
if match is None:
|
||||
raise ValueError(f"Not a valid version number {value}")
|
||||
major = int(match[1])
|
||||
minor = int(match[2])
|
||||
patch = int(match[3])
|
||||
return Version(major=major, minor=minor, patch=patch)
|
||||
|
||||
|
||||
def version_number(value):
|
||||
value = string_strict(value)
|
||||
try:
|
||||
|
||||
@@ -4,7 +4,7 @@ from enum import Enum
|
||||
|
||||
from esphome.enum import StrEnum
|
||||
|
||||
__version__ = "2025.7.2"
|
||||
__version__ = "2025.7.5"
|
||||
|
||||
ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
|
||||
VALID_SUBSTITUTIONS_CHARACTERS = (
|
||||
|
||||
@@ -68,8 +68,11 @@ void Application::setup() {
|
||||
|
||||
do {
|
||||
uint8_t new_app_state = STATUS_LED_WARNING;
|
||||
this->scheduler.call();
|
||||
this->feed_wdt();
|
||||
uint32_t now = millis();
|
||||
|
||||
// Process pending loop enables to handle GPIO interrupts during setup
|
||||
this->before_loop_tasks_(now);
|
||||
|
||||
for (uint32_t j = 0; j <= i; j++) {
|
||||
// Update loop_component_start_time_ right before calling each component
|
||||
this->loop_component_start_time_ = millis();
|
||||
@@ -78,6 +81,8 @@ void Application::setup() {
|
||||
this->app_state_ |= new_app_state;
|
||||
this->feed_wdt();
|
||||
}
|
||||
|
||||
this->after_loop_tasks_();
|
||||
this->app_state_ = new_app_state;
|
||||
yield();
|
||||
} while (!component->can_proceed());
|
||||
@@ -94,30 +99,10 @@ void Application::setup() {
|
||||
void Application::loop() {
|
||||
uint8_t new_app_state = 0;
|
||||
|
||||
this->scheduler.call();
|
||||
|
||||
// Get the initial loop time at the start
|
||||
uint32_t last_op_end_time = millis();
|
||||
|
||||
// Feed WDT with time
|
||||
this->feed_wdt(last_op_end_time);
|
||||
|
||||
// Process any pending enable_loop requests from ISRs
|
||||
// This must be done before marking in_loop_ = true to avoid race conditions
|
||||
if (this->has_pending_enable_loop_requests_) {
|
||||
// Clear flag BEFORE processing to avoid race condition
|
||||
// If ISR sets it during processing, we'll catch it next loop iteration
|
||||
// This is safe because:
|
||||
// 1. Each component has its own pending_enable_loop_ flag that we check
|
||||
// 2. If we can't process a component (wrong state), enable_pending_loops_()
|
||||
// will set this flag back to true
|
||||
// 3. Any new ISR requests during processing will set the flag again
|
||||
this->has_pending_enable_loop_requests_ = false;
|
||||
this->enable_pending_loops_();
|
||||
}
|
||||
|
||||
// Mark that we're in the loop for safe reentrant modifications
|
||||
this->in_loop_ = true;
|
||||
this->before_loop_tasks_(last_op_end_time);
|
||||
|
||||
for (this->current_loop_index_ = 0; this->current_loop_index_ < this->looping_components_active_end_;
|
||||
this->current_loop_index_++) {
|
||||
@@ -138,7 +123,7 @@ void Application::loop() {
|
||||
this->feed_wdt(last_op_end_time);
|
||||
}
|
||||
|
||||
this->in_loop_ = false;
|
||||
this->after_loop_tasks_();
|
||||
this->app_state_ = new_app_state;
|
||||
|
||||
// Use the last component's end time instead of calling millis() again
|
||||
@@ -400,6 +385,36 @@ void Application::enable_pending_loops_() {
|
||||
}
|
||||
}
|
||||
|
||||
void Application::before_loop_tasks_(uint32_t loop_start_time) {
|
||||
// Process scheduled tasks
|
||||
this->scheduler.call();
|
||||
|
||||
// Feed the watchdog timer
|
||||
this->feed_wdt(loop_start_time);
|
||||
|
||||
// Process any pending enable_loop requests from ISRs
|
||||
// This must be done before marking in_loop_ = true to avoid race conditions
|
||||
if (this->has_pending_enable_loop_requests_) {
|
||||
// Clear flag BEFORE processing to avoid race condition
|
||||
// If ISR sets it during processing, we'll catch it next loop iteration
|
||||
// This is safe because:
|
||||
// 1. Each component has its own pending_enable_loop_ flag that we check
|
||||
// 2. If we can't process a component (wrong state), enable_pending_loops_()
|
||||
// will set this flag back to true
|
||||
// 3. Any new ISR requests during processing will set the flag again
|
||||
this->has_pending_enable_loop_requests_ = false;
|
||||
this->enable_pending_loops_();
|
||||
}
|
||||
|
||||
// Mark that we're in the loop for safe reentrant modifications
|
||||
this->in_loop_ = true;
|
||||
}
|
||||
|
||||
void Application::after_loop_tasks_() {
|
||||
// Clear the in_loop_ flag to indicate we're done processing components
|
||||
this->in_loop_ = false;
|
||||
}
|
||||
|
||||
#ifdef USE_SOCKET_SELECT_SUPPORT
|
||||
bool Application::register_socket_fd(int fd) {
|
||||
// WARNING: This function is NOT thread-safe and must only be called from the main loop
|
||||
|
||||
@@ -504,6 +504,8 @@ class Application {
|
||||
void enable_component_loop_(Component *component);
|
||||
void enable_pending_loops_();
|
||||
void activate_looping_component_(uint16_t index);
|
||||
void before_loop_tasks_(uint32_t loop_start_time);
|
||||
void after_loop_tasks_();
|
||||
|
||||
void feed_wdt_arch_();
|
||||
|
||||
|
||||
@@ -67,7 +67,10 @@ To bit_cast(const From &src) {
|
||||
return dst;
|
||||
}
|
||||
#endif
|
||||
using std::lerp;
|
||||
|
||||
// clang-format off
|
||||
inline float lerp(float completion, float start, float end) = delete; // Please use std::lerp. Notice that it has different order on arguments!
|
||||
// clang-format on
|
||||
|
||||
// std::byteswap from C++23
|
||||
template<typename T> constexpr T byteswap(T n) {
|
||||
|
||||
@@ -6,7 +6,7 @@ set -e
|
||||
cd "$(dirname "$0")/.."
|
||||
if [ ! -n "$VIRTUAL_ENV" ]; then
|
||||
if [ -x "$(command -v uv)" ]; then
|
||||
uv venv venv
|
||||
uv venv --seed venv
|
||||
else
|
||||
python3 -m venv venv
|
||||
fi
|
||||
|
||||
@@ -4,6 +4,8 @@ esphome:
|
||||
esp32:
|
||||
board: esp32dev
|
||||
|
||||
logger:
|
||||
|
||||
text:
|
||||
- platform: template
|
||||
name: "test 1 text"
|
||||
|
||||
@@ -738,7 +738,7 @@ lvgl:
|
||||
id: bar_id
|
||||
value: !lambda return (int)((float)rand() / RAND_MAX * 100);
|
||||
start_value: !lambda return (int)((float)rand() / RAND_MAX * 100);
|
||||
mode: symmetrical
|
||||
mode: range
|
||||
- logger.log:
|
||||
format: "bar value %f"
|
||||
args: [x]
|
||||
|
||||
Reference in New Issue
Block a user