1
0
mirror of https://github.com/esphome/esphome.git synced 2025-11-03 08:31:47 +00:00

Compare commits

...

30 Commits

Author SHA1 Message Date
Jesse Hills
ce5a323f91 Merge pull request #6408 from esphome/bump-2024.3.0
2024.3.0
2024-03-20 18:30:49 +13:00
Jesse Hills
9541df9d88 Bump version to 2024.3.0 2024-03-20 17:15:19 +13:00
Jesse Hills
be15122e8b Merge pull request #6407 from esphome/bump-2024.3.0b5
2024.3.0b5
2024-03-20 17:11:24 +13:00
Jesse Hills
6f7273d9cb Bump version to 2024.3.0b5 2024-03-20 16:38:15 +13:00
RFDarter
ccca545862 web_server support for v3 (#6203) 2024-03-20 16:38:15 +13:00
Clyde Stubbs
e27e342927 Show component warnings and errors in the log; (#6400) 2024-03-20 16:38:15 +13:00
Clyde Stubbs
507568db64 AHT10: Use state machine to avoid blocking delay (#6401) 2024-03-20 16:38:15 +13:00
Jesse Hills
156d2c04f9 Merge pull request #6397 from esphome/bump-2024.3.0b4
2024.3.0b4
2024-03-19 15:37:30 +13:00
Jesse Hills
855b1fd706 Bump version to 2024.3.0b4 2024-03-19 14:22:28 +13:00
Jesse Hills
c56c40cb82 Require xsrf/csrf when using a password (#6396) 2024-03-19 14:22:28 +13:00
Mike La Spina
a3bd8ad025 ld2420: Firmware v1.5.4+ bug workaround (#6168) 2024-03-19 14:22:28 +13:00
Stefan Rado
db1b187e80 Fix wrong initialization of vectors in ade7953_i2c (#6393) 2024-03-19 14:22:28 +13:00
Stefan Rado
9442f7a271 Fix sending packets to uponor_smatrix devices (#6392) 2024-03-19 14:22:28 +13:00
swoboda1337
b3aa950c60 Fix bug in remote_base conditional (#6281)
Co-authored-by: Jonathan Swoboda <jonathan.swoboda>
2024-03-19 14:22:28 +13:00
Jesse Hills
dccad040f9 Merge pull request #6390 from esphome/bump-2024.3.0b3
2024.3.0b3
2024-03-18 19:34:58 +13:00
Jesse Hills
8a8bfe01c7 Bump version to 2024.3.0b3 2024-03-18 15:54:39 +13:00
Jesse Hills
690a7d46ce Replace name and friendly name in full adopted configs (#4456) 2024-03-18 15:54:39 +13:00
Jimmy Hedman
4429e5ae56 IPv6 can't be enabled for libretiny (#6387) 2024-03-18 15:54:39 +13:00
Edward Firmo
22f427165f Shows component operation time in ms (#6388)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2024-03-18 15:54:39 +13:00
Stefan Rado
3908a9ce9d Fix compilation for uponor_smatrix without time component (#6389)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2024-03-18 15:54:39 +13:00
Kevin Ahrendt
9e378189c3 microWakeWord - add new ops and small improvements (#6360) 2024-03-18 15:54:39 +13:00
Samuel Sieb
d121fa5d05 allow negative ppm for sensair (#6385) 2024-03-18 15:54:39 +13:00
Jesse Hills
4de58559c6 Fix list-components when PR is not targeting dev (#6375) 2024-03-18 15:54:38 +13:00
Federico G. Schwindt
a5553827f1 Use AQI device class (#6376) 2024-03-18 15:54:38 +13:00
Jesse Hills
4180eae68f Merge pull request #6374 from esphome/bump-2024.3.0b2
2024.3.0b2
2024-03-15 17:06:14 +13:00
Jesse Hills
83cc7b9d48 Bump version to 2024.3.0b2 2024-03-15 14:23:12 +13:00
Jimmy Hedman
ca85a41a72 Don't try to get IPv6 addresses when disabled (#6366) 2024-03-15 14:23:12 +13:00
Samuel Sieb
92fbc61c46 fix servo restore (#6370) 2024-03-15 14:23:12 +13:00
Attila Farago
76c4bfbed3 Allow button press action in web_server to be executed via GET method (#5938) 2024-03-15 14:23:12 +13:00
Clyde Stubbs
e33e09a685 SPI: Revert clk_pin to standard output pin schema (#6368) 2024-03-15 14:23:12 +13:00
30 changed files with 343 additions and 287 deletions

View File

@@ -398,6 +398,7 @@ jobs:
runs-on: ubuntu-latest
needs:
- common
if: github.event_name == 'pull_request'
outputs:
matrix: ${{ steps.set-matrix.outputs.matrix }}
steps:
@@ -406,10 +407,14 @@ jobs:
with:
# Fetch enough history so `git merge-base refs/remotes/origin/dev HEAD` works.
fetch-depth: 500
- name: Fetch dev branch
- name: Get target branch
id: target-branch
run: |
git -c protocol.version=2 fetch --no-tags --prune --no-recurse-submodules --depth=1 origin +refs/heads/dev*:refs/remotes/origin/dev* +refs/tags/dev*:refs/tags/dev*
git merge-base refs/remotes/origin/dev HEAD
echo "branch=${{ github.event.pull_request.base.ref }}" >> $GITHUB_OUTPUT
- name: Fetch ${{ steps.target-branch.outputs.branch }} branch
run: |
git -c protocol.version=2 fetch --no-tags --prune --no-recurse-submodules --depth=1 origin +refs/heads/${{ steps.target-branch.outputs.branch }}:refs/remotes/origin/${{ steps.target-branch.outputs.branch }}
git merge-base refs/remotes/origin/${{ steps.target-branch.outputs.branch }} HEAD
- name: Restore Python
uses: ./.github/actions/restore-python
with:
@@ -419,7 +424,7 @@ jobs:
id: set-matrix
run: |
. venv/bin/activate
echo "matrix=$(script/list-components.py --changed | jq -R -s -c 'split("\n")[:-1]')" >> $GITHUB_OUTPUT
echo "matrix=$(script/list-components.py --changed --branch ${{ steps.target-branch.outputs.branch }} | jq -R -s -c 'split("\n")[:-1]')" >> $GITHUB_OUTPUT
test-build-components:
name: Component test ${{ matrix.file }}
@@ -427,7 +432,7 @@ jobs:
needs:
- common
- list-components
if: ${{ needs.list-components.outputs.matrix != '[]' && needs.list-components.outputs.matrix != '' }}
if: ${{ github.event_name == 'pull_request' && needs.list-components.outputs.matrix != '[]' && needs.list-components.outputs.matrix != '' }}
strategy:
fail-fast: false
max-parallel: 2

View File

@@ -13,29 +13,29 @@ void AdE7953I2c::dump_config() {
ade7953_base::ADE7953::dump_config();
}
bool AdE7953I2c::ade_write_8(uint16_t reg, uint8_t value) {
std::vector<uint8_t> data(3);
data.push_back(reg >> 8);
data.push_back(reg >> 0);
data.push_back(value);
return this->write(data.data(), data.size()) != i2c::ERROR_OK;
uint8_t data[3];
data[0] = reg >> 8;
data[1] = reg >> 0;
data[2] = value;
return this->write(data, 3) != i2c::ERROR_OK;
}
bool AdE7953I2c::ade_write_16(uint16_t reg, uint16_t value) {
std::vector<uint8_t> data(4);
data.push_back(reg >> 8);
data.push_back(reg >> 0);
data.push_back(value >> 8);
data.push_back(value >> 0);
return this->write(data.data(), data.size()) != i2c::ERROR_OK;
uint8_t data[4];
data[0] = reg >> 8;
data[1] = reg >> 0;
data[2] = value >> 8;
data[3] = value >> 0;
return this->write(data, 4) != i2c::ERROR_OK;
}
bool AdE7953I2c::ade_write_32(uint16_t reg, uint32_t value) {
std::vector<uint8_t> data(6);
data.push_back(reg >> 8);
data.push_back(reg >> 0);
data.push_back(value >> 24);
data.push_back(value >> 16);
data.push_back(value >> 8);
data.push_back(value >> 0);
return this->write(data.data(), data.size()) != i2c::ERROR_OK;
uint8_t data[6];
data[0] = reg >> 8;
data[1] = reg >> 0;
data[2] = value >> 24;
data[3] = value >> 16;
data[4] = value >> 8;
data[5] = value >> 0;
return this->write(data, 6) != i2c::ERROR_OK;
}
bool AdE7953I2c::ade_read_8(uint16_t reg, uint8_t *value) {
uint8_t reg_data[2];

View File

@@ -36,6 +36,7 @@ static const uint8_t AHT10_INIT_ATTEMPTS = 10;
static const uint8_t AHT10_STATUS_BUSY = 0x80;
void AHT10Component::setup() {
this->read_delay_ = this->humidity_sensor_ != nullptr ? AHT10_HUMIDITY_DELAY : AHT10_DEFAULT_DELAY;
if (this->write(AHT10_SOFTRESET_CMD, sizeof(AHT10_SOFTRESET_CMD)) != i2c::ERROR_OK) {
ESP_LOGE(TAG, "Reset AHT10 failed!");
}
@@ -83,74 +84,78 @@ void AHT10Component::setup() {
ESP_LOGV(TAG, "AHT10 initialization");
}
void AHT10Component::update() {
if (this->write(AHT10_MEASURE_CMD, sizeof(AHT10_MEASURE_CMD)) != i2c::ERROR_OK) {
ESP_LOGE(TAG, "Communication with AHT10 failed!");
this->status_set_warning();
return;
}
uint8_t data[6];
uint8_t delay_ms = AHT10_DEFAULT_DELAY;
if (this->humidity_sensor_ != nullptr)
delay_ms = AHT10_HUMIDITY_DELAY;
bool success = false;
for (int i = 0; i < AHT10_ATTEMPTS; ++i) {
ESP_LOGVV(TAG, "Attempt %d at %6" PRIu32, i, millis());
delay(delay_ms);
if (this->read(data, 6) != i2c::ERROR_OK) {
ESP_LOGD(TAG, "Communication with AHT10 failed, waiting...");
continue;
}
if ((data[0] & 0x80) == 0x80) { // Bit[7] = 0b1, device is busy
ESP_LOGD(TAG, "AHT10 is busy, waiting...");
} else if (data[1] == 0x0 && data[2] == 0x0 && (data[3] >> 4) == 0x0) {
// Unrealistic humidity (0x0)
if (this->humidity_sensor_ == nullptr) {
ESP_LOGVV(TAG, "ATH10 Unrealistic humidity (0x0), but humidity is not required");
break;
} else {
ESP_LOGD(TAG, "ATH10 Unrealistic humidity (0x0), retrying...");
if (this->write(AHT10_MEASURE_CMD, sizeof(AHT10_MEASURE_CMD)) != i2c::ERROR_OK) {
ESP_LOGE(TAG, "Communication with AHT10 failed!");
this->status_set_warning();
return;
}
}
} else {
// data is valid, we can break the loop
ESP_LOGVV(TAG, "Answer at %6" PRIu32, millis());
success = true;
break;
}
}
if (!success || (data[0] & 0x80) == 0x80) {
void AHT10Component::restart_read_() {
if (this->read_count_ == AHT10_ATTEMPTS) {
this->read_count_ = 0;
ESP_LOGE(TAG, "Measurements reading timed-out!");
this->status_set_warning();
this->status_set_error();
return;
}
this->read_count_++;
this->set_timeout(this->read_delay_, [this]() { this->read_data_(); });
}
void AHT10Component::read_data_() {
uint8_t data[6];
ESP_LOGD(TAG, "Read attempt %d at %ums", this->read_count_, (unsigned) (millis() - this->start_time_));
if (this->read(data, 6) != i2c::ERROR_OK) {
ESP_LOGD(TAG, "Communication with AHT10 failed, waiting...");
this->restart_read_();
return;
}
if ((data[0] & 0x80) == 0x80) { // Bit[7] = 0b1, device is busy
ESP_LOGD(TAG, "AHT10 is busy, waiting...");
this->restart_read_();
return;
}
if (data[1] == 0x0 && data[2] == 0x0 && (data[3] >> 4) == 0x0) {
// Unrealistic humidity (0x0)
if (this->humidity_sensor_ == nullptr) {
ESP_LOGV(TAG, "ATH10 Unrealistic humidity (0x0), but humidity is not required");
} else {
ESP_LOGD(TAG, "ATH10 Unrealistic humidity (0x0), retrying...");
if (this->write(AHT10_MEASURE_CMD, sizeof(AHT10_MEASURE_CMD)) != i2c::ERROR_OK) {
ESP_LOGE(TAG, "Communication with AHT10 failed!");
this->status_set_warning();
}
this->restart_read_();
return;
}
}
ESP_LOGD(TAG, "Success at %ums", (unsigned) (millis() - this->start_time_));
uint32_t raw_temperature = ((data[3] & 0x0F) << 16) | (data[4] << 8) | data[5];
uint32_t raw_humidity = ((data[1] << 16) | (data[2] << 8) | data[3]) >> 4;
float temperature = ((200.0f * (float) raw_temperature) / 1048576.0f) - 50.0f;
float humidity;
if (raw_humidity == 0) { // unrealistic value
humidity = NAN;
} else {
humidity = (float) raw_humidity * 100.0f / 1048576.0f;
}
if (this->temperature_sensor_ != nullptr) {
float temperature = ((200.0f * (float) raw_temperature) / 1048576.0f) - 50.0f;
this->temperature_sensor_->publish_state(temperature);
}
if (this->humidity_sensor_ != nullptr) {
float humidity;
if (raw_humidity == 0) { // unrealistic value
humidity = NAN;
} else {
humidity = (float) raw_humidity * 100.0f / 1048576.0f;
}
if (std::isnan(humidity)) {
ESP_LOGW(TAG, "Invalid humidity! Sensor reported 0%% Hum");
}
this->humidity_sensor_->publish_state(humidity);
}
this->status_clear_warning();
this->read_count_ = 0;
}
void AHT10Component::update() {
if (this->read_count_ != 0)
return;
this->start_time_ = millis();
if (this->write(AHT10_MEASURE_CMD, sizeof(AHT10_MEASURE_CMD)) != i2c::ERROR_OK) {
ESP_LOGE(TAG, "Communication with AHT10 failed!");
this->status_set_warning();
return;
}
this->restart_read_();
}
float AHT10Component::get_setup_priority() const { return setup_priority::DATA; }

View File

@@ -26,6 +26,11 @@ class AHT10Component : public PollingComponent, public i2c::I2CDevice {
sensor::Sensor *temperature_sensor_{nullptr};
sensor::Sensor *humidity_sensor_{nullptr};
AHT10Variant variant_{};
unsigned read_count_{};
unsigned read_delay_{};
void read_data_();
void restart_read_();
uint32_t start_time_{};
};
} // namespace aht10

View File

@@ -2,8 +2,10 @@ import base64
import secrets
from pathlib import Path
from typing import Optional
import re
import requests
from ruamel.yaml import YAML
import esphome.codegen as cg
import esphome.config_validation as cv
@@ -11,7 +13,6 @@ import esphome.final_validate as fv
from esphome import git
from esphome.components.packages import validate_source_shorthand
from esphome.const import CONF_REF, CONF_WIFI, CONF_ESPHOME, CONF_PROJECT
from esphome.wizard import wizard_file
from esphome.yaml_util import dump
dashboard_import_ns = cg.esphome_ns.namespace("dashboard_import")
@@ -94,75 +95,74 @@ def import_config(
if p.exists():
raise FileExistsError
if project_name == "esphome.web":
if "esp32c3" in import_url:
board = "esp32-c3-devkitm-1"
platform = "ESP32"
elif "esp32s2" in import_url:
board = "esp32-s2-saola-1"
platform = "ESP32"
elif "esp32s3" in import_url:
board = "esp32-s3-devkitc-1"
platform = "ESP32"
elif "esp32" in import_url:
board = "esp32dev"
platform = "ESP32"
elif "esp8266" in import_url:
board = "esp01_1m"
platform = "ESP8266"
elif "pico-w" in import_url:
board = "pico-w"
platform = "RP2040"
git_file = git.GitFile.from_shorthand(import_url)
kwargs = {
"name": name,
"friendly_name": friendly_name,
"platform": platform,
"board": board,
"ssid": "!secret wifi_ssid",
"psk": "!secret wifi_password",
if git_file.query and "full_config" in git_file.query:
url = git_file.raw_url
try:
req = requests.get(url, timeout=30)
req.raise_for_status()
except requests.exceptions.RequestException as e:
raise ValueError(f"Error while fetching {url}: {e}") from e
contents = req.text
yaml = YAML()
loaded_yaml = yaml.load(contents)
if (
"name_add_mac_suffix" in loaded_yaml["esphome"]
and loaded_yaml["esphome"]["name_add_mac_suffix"]
):
loaded_yaml["esphome"]["name_add_mac_suffix"] = False
name_val = loaded_yaml["esphome"]["name"]
sub_pattern = re.compile(r"\$\{?([a-zA-Z-_]+)\}?")
if match := sub_pattern.match(name_val):
name_sub = match.group(1)
if name_sub in loaded_yaml["substitutions"]:
loaded_yaml["substitutions"][name_sub] = name
else:
raise ValueError(
f"Name substitution {name_sub} not found in substitutions"
)
else:
loaded_yaml["esphome"]["name"] = name
if friendly_name is not None:
friendly_name_val = loaded_yaml["esphome"]["friendly_name"]
if match := sub_pattern.match(friendly_name_val):
friendly_name_sub = match.group(1)
if friendly_name_sub in loaded_yaml["substitutions"]:
loaded_yaml["substitutions"][friendly_name_sub] = friendly_name
else:
raise ValueError(
f"Friendly name substitution {friendly_name_sub} not found in substitutions"
)
else:
loaded_yaml["esphome"]["friendly_name"] = friendly_name
with p.open("w", encoding="utf8") as f:
yaml.dump(loaded_yaml, f)
else:
with p.open("w", encoding="utf8") as f:
f.write(contents)
else:
substitutions = {"name": name}
esphome_core = {"name": "${name}", "name_add_mac_suffix": False}
if friendly_name:
substitutions["friendly_name"] = friendly_name
esphome_core["friendly_name"] = "${friendly_name}"
config = {
"substitutions": substitutions,
"packages": {project_name: import_url},
"esphome": esphome_core,
}
if encryption:
noise_psk = secrets.token_bytes(32)
key = base64.b64encode(noise_psk).decode()
kwargs["api_encryption_key"] = key
config["api"] = {"encryption": {"key": key}}
p.write_text(
wizard_file(**kwargs),
encoding="utf8",
)
else:
git_file = git.GitFile.from_shorthand(import_url)
output = dump(config)
if git_file.query and "full_config" in git_file.query:
url = git_file.raw_url
try:
req = requests.get(url, timeout=30)
req.raise_for_status()
except requests.exceptions.RequestException as e:
raise ValueError(f"Error while fetching {url}: {e}") from e
if network == CONF_WIFI:
output += WIFI_CONFIG
p.write_text(req.text, encoding="utf8")
else:
substitutions = {"name": name}
esphome_core = {"name": "${name}", "name_add_mac_suffix": False}
if friendly_name:
substitutions["friendly_name"] = friendly_name
esphome_core["friendly_name"] = "${friendly_name}"
config = {
"substitutions": substitutions,
"packages": {project_name: import_url},
"esphome": esphome_core,
}
if encryption:
noise_psk = secrets.token_bytes(32)
key = base64.b64encode(noise_psk).decode()
config["api"] = {"encryption": {"key": key}}
output = dump(config)
if network == CONF_WIFI:
output += WIFI_CONFIG
p.write_text(output, encoding="utf8")
p.write_text(output, encoding="utf8")

View File

@@ -211,10 +211,11 @@ void LD2420Component::factory_reset_action() {
void LD2420Component::restart_module_action() {
ESP_LOGCONFIG(TAG, "Restarting LD2420 module...");
this->send_module_restart();
delay_microseconds_safe(45000);
this->set_config_mode(true);
this->set_system_mode(system_mode_);
this->set_config_mode(false);
this->set_timeout(250, [this]() {
this->set_config_mode(true);
this->set_system_mode(system_mode_);
this->set_config_mode(false);
});
ESP_LOGCONFIG(TAG, "LD2420 Restarted.");
}
@@ -527,18 +528,16 @@ int LD2420Component::send_cmd_from_array(CmdFrameT frame) {
this->write_byte(cmd_buffer[index]);
}
delay_microseconds_safe(500); // give the module a moment to process it
error = 0;
if (frame.command == CMD_RESTART) {
delay_microseconds_safe(25000); // Wait for the restart
return 0; // restart does not reply exit now
return 0; // restart does not reply exit now
}
while (!this->cmd_reply_.ack) {
while (available()) {
this->readline_(read(), ack_buffer, sizeof(ack_buffer));
}
delay_microseconds_safe(250);
delay_microseconds_safe(1450);
if (loop_count <= 0) {
error = LD2420_ERROR_TIMEOUT;
retry--;

View File

@@ -93,11 +93,18 @@ int MicroWakeWord::read_microphone_() {
return 0;
}
size_t bytes_written = this->ring_buffer_->write((void *) this->input_buffer_, bytes_read);
if (bytes_written != bytes_read) {
ESP_LOGW(TAG, "Failed to write some data to ring buffer (written=%d, expected=%d)", bytes_written, bytes_read);
size_t bytes_free = this->ring_buffer_->free();
if (bytes_free < bytes_read) {
ESP_LOGW(TAG,
"Not enough free bytes in ring buffer to store incoming audio data (free bytes=%d, incoming bytes=%d). "
"Resetting the ring buffer. Wake word detection accuracy will be reduced.",
bytes_free, bytes_read);
this->ring_buffer_->reset();
}
return bytes_written;
return this->ring_buffer_->write((void *) this->input_buffer_, bytes_read);
}
void MicroWakeWord::loop() {
@@ -206,12 +213,6 @@ bool MicroWakeWord::initialize_models() {
return false;
}
this->preprocessor_stride_buffer_ = audio_samples_allocator.allocate(HISTORY_SAMPLES_TO_KEEP);
if (this->preprocessor_stride_buffer_ == nullptr) {
ESP_LOGE(TAG, "Could not allocate the audio preprocessor's stride buffer.");
return false;
}
this->preprocessor_model_ = tflite::GetModel(G_AUDIO_PREPROCESSOR_INT8_TFLITE);
if (this->preprocessor_model_->version() != TFLITE_SCHEMA_VERSION) {
ESP_LOGE(TAG, "Wake word's audio preprocessor model's schema is not supported");
@@ -225,7 +226,7 @@ bool MicroWakeWord::initialize_models() {
}
static tflite::MicroMutableOpResolver<18> preprocessor_op_resolver;
static tflite::MicroMutableOpResolver<14> streaming_op_resolver;
static tflite::MicroMutableOpResolver<17> streaming_op_resolver;
if (!this->register_preprocessor_ops_(preprocessor_op_resolver))
return false;
@@ -329,7 +330,6 @@ bool MicroWakeWord::detect_wake_word_() {
}
// Perform inference
uint32_t streaming_size = micros();
float streaming_prob = this->perform_streaming_inference_();
// Add the most recent probability to the sliding window
@@ -357,6 +357,9 @@ bool MicroWakeWord::detect_wake_word_() {
for (auto &prob : this->recent_streaming_probabilities_) {
prob = 0;
}
ESP_LOGD(TAG, "Wake word sliding average probability is %.3f and most recent probability is %.3f",
sliding_window_average, streaming_prob);
return true;
}
@@ -371,23 +374,6 @@ void MicroWakeWord::set_sliding_window_average_size(size_t size) {
bool MicroWakeWord::slice_available_() {
size_t available = this->ring_buffer_->available();
size_t free = this->ring_buffer_->free();
if (free < NEW_SAMPLES_TO_GET * sizeof(int16_t)) {
// If the ring buffer is within one audio slice of being full, then wake word detection will have issues.
// If this is constantly occuring, then some possibilities why are
// 1) there are too many other slow components configured
// 2) the ESP32 isn't fast enough; e.g., an ESP32 is much slower than an ESP32-S3 at inferences.
// 3) the model is too large
// 4) the model uses operations that are not optimized
ESP_LOGW(TAG,
"Audio buffer is nearly full. Wake word detection may be less accurate and have slower reponse times. "
#if !defined(USE_ESP32_VARIANT_ESP32S3)
"microWakeWord is designed for the ESP32-S3. The current platform is too slow for this model."
#endif
);
}
return available > (NEW_SAMPLES_TO_GET * sizeof(int16_t));
}
@@ -396,13 +382,12 @@ bool MicroWakeWord::stride_audio_samples_(int16_t **audio_samples) {
return false;
}
// Copy 320 bytes (160 samples over 10 ms) into preprocessor_audio_buffer_ from history in
// preprocessor_stride_buffer_
memcpy((void *) (this->preprocessor_audio_buffer_), (void *) (this->preprocessor_stride_buffer_),
// Copy the last 320 bytes (160 samples over 10 ms) from the audio buffer to the start of the audio buffer
memcpy((void *) (this->preprocessor_audio_buffer_), (void *) (this->preprocessor_audio_buffer_ + NEW_SAMPLES_TO_GET),
HISTORY_SAMPLES_TO_KEEP * sizeof(int16_t));
// Copy 640 bytes (320 samples over 20 ms) from the ring buffer
// The first 320 bytes (160 samples over 10 ms) will be from history
// Copy 640 bytes (320 samples over 20 ms) from the ring buffer into the audio buffer offset 320 bytes (160 samples
// over 10 ms)
size_t bytes_read = this->ring_buffer_->read((void *) (this->preprocessor_audio_buffer_ + HISTORY_SAMPLES_TO_KEEP),
NEW_SAMPLES_TO_GET * sizeof(int16_t), pdMS_TO_TICKS(200));
@@ -415,11 +400,6 @@ bool MicroWakeWord::stride_audio_samples_(int16_t **audio_samples) {
return false;
}
// Copy the last 320 bytes (160 samples over 10 ms) from the audio buffer into history stride buffer for the next
// iteration
memcpy((void *) (this->preprocessor_stride_buffer_), (void *) (this->preprocessor_audio_buffer_ + NEW_SAMPLES_TO_GET),
HISTORY_SAMPLES_TO_KEEP * sizeof(int16_t));
*audio_samples = this->preprocessor_audio_buffer_;
return true;
}
@@ -480,7 +460,7 @@ bool MicroWakeWord::register_preprocessor_ops_(tflite::MicroMutableOpResolver<18
return true;
}
bool MicroWakeWord::register_streaming_ops_(tflite::MicroMutableOpResolver<14> &op_resolver) {
bool MicroWakeWord::register_streaming_ops_(tflite::MicroMutableOpResolver<17> &op_resolver) {
if (op_resolver.AddCallOnce() != kTfLiteOk)
return false;
if (op_resolver.AddVarHandle() != kTfLiteOk)
@@ -509,6 +489,12 @@ bool MicroWakeWord::register_streaming_ops_(tflite::MicroMutableOpResolver<14> &
return false;
if (op_resolver.AddQuantize() != kTfLiteOk)
return false;
if (op_resolver.AddDepthwiseConv2D() != kTfLiteOk)
return false;
if (op_resolver.AddAveragePool2D() != kTfLiteOk)
return false;
if (op_resolver.AddMaxPool2D() != kTfLiteOk)
return false;
return true;
}

View File

@@ -128,7 +128,6 @@ class MicroWakeWord : public Component {
// Stores audio fed into feature generator preprocessor
int16_t *preprocessor_audio_buffer_;
int16_t *preprocessor_stride_buffer_;
bool detected_{false};
@@ -181,7 +180,7 @@ class MicroWakeWord : public Component {
bool register_preprocessor_ops_(tflite::MicroMutableOpResolver<18> &op_resolver);
/// @brief Returns true if successfully registered the streaming model's TensorFlow operations
bool register_streaming_ops_(tflite::MicroMutableOpResolver<14> &op_resolver);
bool register_streaming_ops_(tflite::MicroMutableOpResolver<17> &op_resolver);
};
template<typename... Ts> class StartAction : public Action<Ts...>, public Parented<MicroWakeWord> {

View File

@@ -6,6 +6,9 @@ from esphome.components.esp32 import add_idf_sdkconfig_option
from esphome.const import (
CONF_ENABLE_IPV6,
CONF_MIN_IPV6_ADDR_COUNT,
PLATFORM_ESP32,
PLATFORM_ESP8266,
PLATFORM_RP2040,
)
CODEOWNERS = ["@esphome/core"]
@@ -16,25 +19,30 @@ IPAddress = network_ns.class_("IPAddress")
CONFIG_SCHEMA = cv.Schema(
{
cv.Optional(CONF_ENABLE_IPV6, default=False): cv.boolean,
cv.SplitDefault(CONF_ENABLE_IPV6): cv.All(
cv.boolean, cv.only_on([PLATFORM_ESP32, PLATFORM_ESP8266, PLATFORM_RP2040])
),
cv.Optional(CONF_MIN_IPV6_ADDR_COUNT, default=0): cv.positive_int,
}
)
async def to_code(config):
cg.add_define("USE_NETWORK_IPV6", config[CONF_ENABLE_IPV6])
cg.add_define("USE_NETWORK_MIN_IPV6_ADDR_COUNT", config[CONF_MIN_IPV6_ADDR_COUNT])
if CORE.using_esp_idf:
add_idf_sdkconfig_option("CONFIG_LWIP_IPV6", config[CONF_ENABLE_IPV6])
add_idf_sdkconfig_option(
"CONFIG_LWIP_IPV6_AUTOCONFIG", config[CONF_ENABLE_IPV6]
if CONF_ENABLE_IPV6 in config:
cg.add_define("USE_NETWORK_IPV6", config[CONF_ENABLE_IPV6])
cg.add_define(
"USE_NETWORK_MIN_IPV6_ADDR_COUNT", config[CONF_MIN_IPV6_ADDR_COUNT]
)
else:
if config[CONF_ENABLE_IPV6]:
cg.add_build_flag("-DCONFIG_LWIP_IPV6")
cg.add_build_flag("-DCONFIG_LWIP_IPV6_AUTOCONFIG")
if CORE.is_rp2040:
cg.add_build_flag("-DPIO_FRAMEWORK_ARDUINO_ENABLE_IPV6")
if CORE.is_esp8266:
cg.add_build_flag("-DPIO_FRAMEWORK_ARDUINO_LWIP2_IPV6_LOW_MEMORY")
if CORE.using_esp_idf:
add_idf_sdkconfig_option("CONFIG_LWIP_IPV6", config[CONF_ENABLE_IPV6])
add_idf_sdkconfig_option(
"CONFIG_LWIP_IPV6_AUTOCONFIG", config[CONF_ENABLE_IPV6]
)
else:
if config[CONF_ENABLE_IPV6]:
cg.add_build_flag("-DCONFIG_LWIP_IPV6")
cg.add_build_flag("-DCONFIG_LWIP_IPV6_AUTOCONFIG")
if CORE.is_rp2040:
cg.add_build_flag("-DPIO_FRAMEWORK_ARDUINO_ENABLE_IPV6")
if CORE.is_esp8266:
cg.add_build_flag("-DPIO_FRAMEWORK_ARDUINO_LWIP2_IPV6_LOW_MEMORY")

View File

@@ -16,7 +16,7 @@ RemoteRMTChannel::RemoteRMTChannel(uint8_t mem_block_num) : mem_block_num_(mem_b
}
void RemoteRMTChannel::config_rmt(rmt_config_t &rmt) {
if (rmt_channel_t(int(this->channel_) + this->mem_block_num_) >= RMT_CHANNEL_MAX) {
if (rmt_channel_t(int(this->channel_) + this->mem_block_num_) > RMT_CHANNEL_MAX) {
this->mem_block_num_ = int(RMT_CHANNEL_MAX) - int(this->channel_);
ESP_LOGW(TAG, "Not enough RMT memory blocks available, reduced to %i blocks.", this->mem_block_num_);
}

View File

@@ -14,13 +14,12 @@ from esphome.const import (
CONF_PM_4_0,
CONF_STORE_BASELINE,
CONF_TEMPERATURE,
DEVICE_CLASS_AQI,
DEVICE_CLASS_HUMIDITY,
DEVICE_CLASS_NITROUS_OXIDE,
DEVICE_CLASS_PM1,
DEVICE_CLASS_PM10,
DEVICE_CLASS_PM25,
DEVICE_CLASS_TEMPERATURE,
DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS,
ICON_CHEMICAL_WEAPON,
ICON_RADIATOR,
ICON_THERMOMETER,
@@ -132,13 +131,13 @@ CONFIG_SCHEMA = (
cv.Optional(CONF_VOC): sensor.sensor_schema(
icon=ICON_RADIATOR,
accuracy_decimals=0,
device_class=DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS,
device_class=DEVICE_CLASS_AQI,
state_class=STATE_CLASS_MEASUREMENT,
).extend(GAS_SENSOR),
cv.Optional(CONF_NOX): sensor.sensor_schema(
icon=ICON_RADIATOR,
accuracy_decimals=0,
device_class=DEVICE_CLASS_NITROUS_OXIDE,
device_class=DEVICE_CLASS_AQI,
state_class=STATE_CLASS_MEASUREMENT,
).extend(GAS_SENSOR),
cv.Optional(CONF_STORE_BASELINE, default=True): cv.boolean,

View File

@@ -54,9 +54,9 @@ void SenseAirComponent::update() {
this->status_clear_warning();
const uint8_t length = response[2];
const uint16_t status = (uint16_t(response[3]) << 8) | response[4];
const uint16_t ppm = (uint16_t(response[length + 1]) << 8) | response[length + 2];
const int16_t ppm = int16_t((response[length + 1] << 8) | response[length + 2]);
ESP_LOGD(TAG, "SenseAir Received CO₂=%uppm Status=0x%02X", ppm, status);
ESP_LOGD(TAG, "SenseAir Received CO₂=%dppm Status=0x%02X", ppm, status);
if (this->co2_sensor_ != nullptr)
this->co2_sensor_->publish_state(ppm);
}

View File

@@ -19,13 +19,28 @@ void Servo::dump_config() {
ESP_LOGCONFIG(TAG, " run duration: %" PRIu32 " ms", this->transition_length_);
}
void Servo::setup() {
float v;
if (this->restore_) {
this->rtc_ = global_preferences->make_preference<float>(global_servo_id);
global_servo_id++;
if (this->rtc_.load(&v)) {
this->target_value_ = v;
this->internal_write(v);
this->state_ = STATE_ATTACHED;
this->start_millis_ = millis();
return;
}
}
this->detach();
}
void Servo::loop() {
// check if auto_detach_time_ is set and servo reached target
if (this->auto_detach_time_ && this->state_ == STATE_TARGET_REACHED) {
if (millis() - this->start_millis_ > this->auto_detach_time_) {
this->detach();
this->start_millis_ = 0;
this->state_ = STATE_DETACHED;
ESP_LOGD(TAG, "Servo detached on auto_detach_time");
}
}
@@ -54,8 +69,11 @@ void Servo::loop() {
void Servo::write(float value) {
value = clamp(value, -1.0f, 1.0f);
if (this->target_value_ == value)
if ((this->state_ == STATE_DETACHED) && (this->target_value_ == value)) {
this->internal_write(value);
} else {
this->save_level_(value);
}
this->target_value_ = value;
this->source_value_ = this->current_value_;
this->state_ = STATE_ATTACHED;
@@ -72,11 +90,18 @@ void Servo::internal_write(float value) {
level = lerp(value, this->idle_level_, this->max_level_);
}
this->output_->set_level(level);
if (this->target_value_ == this->current_value_) {
this->save_level_(level);
}
this->current_value_ = value;
}
void Servo::detach() {
this->state_ = STATE_DETACHED;
this->output_->set_level(0.0f);
}
void Servo::save_level_(float v) {
if (this->restore_)
this->rtc_.save(&v);
}
} // namespace servo
} // namespace esphome

View File

@@ -17,22 +17,8 @@ class Servo : public Component {
void loop() override;
void write(float value);
void internal_write(float value);
void detach() {
this->output_->set_level(0.0f);
this->save_level_(0.0f);
}
void setup() override {
float v;
if (this->restore_) {
this->rtc_ = global_preferences->make_preference<float>(global_servo_id);
global_servo_id++;
if (this->rtc_.load(&v)) {
this->output_->set_level(v);
return;
}
}
this->detach();
}
void detach();
void setup() override;
void dump_config() override;
float get_setup_priority() const override { return setup_priority::DATA; }
void set_min_level(float min_level) { min_level_ = min_level; }
@@ -42,8 +28,10 @@ class Servo : public Component {
void set_auto_detach_time(uint32_t auto_detach_time) { auto_detach_time_ = auto_detach_time; }
void set_transition_length(uint32_t transition_length) { transition_length_ = transition_length; }
bool has_reached_target() { return this->current_value_ == this->target_value_; }
protected:
void save_level_(float v) { this->rtc_.save(&v); }
void save_level_(float v);
output::FloatOutput *output_;
float min_level_ = 0.0300f;

View File

@@ -29,7 +29,6 @@ from esphome.const import (
PLATFORM_ESP32,
PLATFORM_ESP8266,
PLATFORM_RP2040,
CONF_ALLOW_OTHER_USES,
CONF_DATA_PINS,
)
from esphome.core import (
@@ -199,8 +198,6 @@ def get_hw_spi(config, available):
def validate_spi_config(config):
available = list(range(len(get_hw_interface_list())))
for spi in config:
# map pin number to schema
spi[CONF_CLK_PIN] = pins.gpio_output_pin_schema(spi[CONF_CLK_PIN])
interface = spi[CONF_INTERFACE]
if interface == "software":
pass
@@ -257,21 +254,11 @@ def get_spi_interface(index):
return "new SPIClass(HSPI)"
# Do not use a pin schema for the number, as that will trigger a pin reuse error due to duplication of the
# clock pin in the standard and quad schemas.
clk_pin_validator = cv.maybe_simple_value(
{
cv.Required(CONF_NUMBER): cv.Any(cv.int_, cv.string),
cv.Optional(CONF_ALLOW_OTHER_USES): cv.boolean,
},
key=CONF_NUMBER,
)
SPI_SCHEMA = cv.All(
cv.Schema(
{
cv.GenerateID(): cv.declare_id(SPIComponent),
cv.Required(CONF_CLK_PIN): clk_pin_validator,
cv.Required(CONF_CLK_PIN): pins.gpio_output_pin_schema,
cv.Optional(CONF_MISO_PIN): pins.gpio_input_pin_schema,
cv.Optional(CONF_MOSI_PIN): pins.gpio_output_pin_schema,
cv.Optional(CONF_FORCE_SW): cv.invalid(
@@ -291,7 +278,7 @@ SPI_QUAD_SCHEMA = cv.All(
cv.Schema(
{
cv.GenerateID(): cv.declare_id(QuadSPIComponent),
cv.Required(CONF_CLK_PIN): clk_pin_validator,
cv.Required(CONF_CLK_PIN): pins.gpio_output_pin_schema,
cv.Required(CONF_DATA_PINS): cv.All(
cv.ensure_list(pins.internal_gpio_output_pin_number),
cv.Length(min=4, max=4),

View File

@@ -61,9 +61,11 @@ void UponorSmatrixComponent::loop() {
// Send packets during bus silence
if ((now - this->last_rx_ > 300) && (now - this->last_poll_start_ < 9500) && (now - this->last_tx_ > 200)) {
#ifdef USE_TIME
// Only build time packet when bus is silent and queue is empty to make sure we can send it right away
if (this->send_time_requested_ && this->tx_queue_.empty() && this->do_send_time_())
this->send_time_requested_ = false;
#endif
// Send the next packet in the queue
if (!this->tx_queue_.empty()) {
auto packet = std::move(this->tx_queue_.front());
@@ -171,7 +173,9 @@ bool UponorSmatrixComponent::send(uint16_t device_address, const UponorSmatrixDa
return false;
// Assemble packet for send queue. All fields are big-endian except for the little-endian checksum.
std::vector<uint8_t> packet(6 + 3 * data_len);
std::vector<uint8_t> packet;
packet.reserve(6 + 3 * data_len);
packet.push_back(this->address_ >> 8);
packet.push_back(this->address_ >> 0);
packet.push_back(device_address >> 8);

View File

@@ -4,6 +4,8 @@
#include "esphome/core/component.h"
#include "esphome/core/helpers.h"
#include "esphome/core/defines.h"
#ifdef USE_TIME
#include "esphome/components/time/real_time_clock.h"
#include "esphome/core/time.h"

View File

@@ -44,6 +44,11 @@ def default_url(config):
config[CONF_CSS_URL] = ""
if not (CONF_JS_URL in config):
config[CONF_JS_URL] = "https://oi.esphome.io/v2/www.js"
if config[CONF_VERSION] == 3:
if not (CONF_CSS_URL in config):
config[CONF_CSS_URL] = ""
if not (CONF_JS_URL in config):
config[CONF_JS_URL] = "https://oi.esphome.io/v3/www.js"
return config
@@ -64,7 +69,7 @@ CONFIG_SCHEMA = cv.All(
{
cv.GenerateID(): cv.declare_id(WebServer),
cv.Optional(CONF_PORT, default=80): cv.port,
cv.Optional(CONF_VERSION, default=2): cv.one_of(1, 2, int=True),
cv.Optional(CONF_VERSION, default=2): cv.one_of(1, 2, 3, int=True),
cv.Optional(CONF_CSS_URL): cv.string,
cv.Optional(CONF_CSS_INCLUDE): cv.file_,
cv.Optional(CONF_JS_URL): cv.string,
@@ -152,7 +157,7 @@ async def to_code(config):
cg.add_define("USE_WEBSERVER")
cg.add_define("USE_WEBSERVER_PORT", config[CONF_PORT])
cg.add_define("USE_WEBSERVER_VERSION", version)
if version == 2:
if version >= 2:
# Don't compress the index HTML as the data sizes are almost the same.
add_resource_as_progmem("INDEX_HTML", build_index_html(config), compress=False)
else:

View File

@@ -358,7 +358,7 @@ void WebServer::handle_index_request(AsyncWebServerRequest *request) {
stream->print(F("</article></body></html>"));
request->send(stream);
}
#elif USE_WEBSERVER_VERSION == 2
#elif USE_WEBSERVER_VERSION >= 2
void WebServer::handle_index_request(AsyncWebServerRequest *request) {
AsyncWebServerResponse *response =
request->beginResponse_P(200, "text/html", ESPHOME_WEBSERVER_INDEX_HTML, ESPHOME_WEBSERVER_INDEX_HTML_SIZE);
@@ -486,7 +486,7 @@ void WebServer::handle_switch_request(AsyncWebServerRequest *request, const UrlM
if (obj->get_object_id() != match.id)
continue;
if (request->method() == HTTP_GET) {
if (request->method() == HTTP_GET && match.method.empty()) {
std::string data = this->switch_json(obj, obj->state, DETAIL_STATE);
request->send(200, "application/json", data.c_str());
} else if (match.method == "toggle") {
@@ -517,7 +517,7 @@ void WebServer::handle_button_request(AsyncWebServerRequest *request, const UrlM
for (button::Button *obj : App.get_buttons()) {
if (obj->get_object_id() != match.id)
continue;
if (request->method() == HTTP_POST && match.method == "press") {
if (match.method == "press") {
this->schedule_([obj]() { obj->press(); });
request->send(200);
return;
@@ -572,7 +572,7 @@ void WebServer::handle_fan_request(AsyncWebServerRequest *request, const UrlMatc
if (obj->get_object_id() != match.id)
continue;
if (request->method() == HTTP_GET) {
if (request->method() == HTTP_GET && match.method.empty()) {
std::string data = this->fan_json(obj, DETAIL_STATE);
request->send(200, "application/json", data.c_str());
} else if (match.method == "toggle") {
@@ -630,7 +630,7 @@ void WebServer::handle_light_request(AsyncWebServerRequest *request, const UrlMa
if (obj->get_object_id() != match.id)
continue;
if (request->method() == HTTP_GET) {
if (request->method() == HTTP_GET && match.method.empty()) {
std::string data = this->light_json(obj, DETAIL_STATE);
request->send(200, "application/json", data.c_str());
} else if (match.method == "toggle") {
@@ -736,7 +736,7 @@ void WebServer::handle_cover_request(AsyncWebServerRequest *request, const UrlMa
if (obj->get_object_id() != match.id)
continue;
if (request->method() == HTTP_GET) {
if (request->method() == HTTP_GET && match.method.empty()) {
std::string data = this->cover_json(obj, DETAIL_STATE);
request->send(200, "application/json", data.c_str());
continue;
@@ -805,7 +805,7 @@ void WebServer::handle_number_request(AsyncWebServerRequest *request, const UrlM
if (obj->get_object_id() != match.id)
continue;
if (request->method() == HTTP_GET) {
if (request->method() == HTTP_GET && match.method.empty()) {
std::string data = this->number_json(obj, obj->state, DETAIL_STATE);
request->send(200, "application/json", data.c_str());
return;
@@ -910,7 +910,7 @@ void WebServer::handle_text_request(AsyncWebServerRequest *request, const UrlMat
if (obj->get_object_id() != match.id)
continue;
if (request->method() == HTTP_GET) {
if (request->method() == HTTP_GET && match.method.empty()) {
std::string data = this->text_json(obj, obj->state, DETAIL_STATE);
request->send(200, "text/json", data.c_str());
return;
@@ -961,7 +961,7 @@ void WebServer::handle_select_request(AsyncWebServerRequest *request, const UrlM
if (obj->get_object_id() != match.id)
continue;
if (request->method() == HTTP_GET) {
if (request->method() == HTTP_GET && match.method.empty()) {
auto detail = DETAIL_STATE;
auto *param = request->getParam("detail");
if (param && param->value() == "all") {
@@ -1016,7 +1016,7 @@ void WebServer::handle_climate_request(AsyncWebServerRequest *request, const Url
if (obj->get_object_id() != match.id)
continue;
if (request->method() == HTTP_GET) {
if (request->method() == HTTP_GET && match.method.empty()) {
std::string data = this->climate_json(obj, DETAIL_STATE);
request->send(200, "application/json", data.c_str());
return;
@@ -1162,7 +1162,7 @@ void WebServer::handle_lock_request(AsyncWebServerRequest *request, const UrlMat
if (obj->get_object_id() != match.id)
continue;
if (request->method() == HTTP_GET) {
if (request->method() == HTTP_GET && match.method.empty()) {
std::string data = this->lock_json(obj, obj->state, DETAIL_STATE);
request->send(200, "application/json", data.c_str());
} else if (match.method == "lock") {
@@ -1201,7 +1201,7 @@ void WebServer::handle_alarm_control_panel_request(AsyncWebServerRequest *reques
if (obj->get_object_id() != match.id)
continue;
if (request->method() == HTTP_GET) {
if (request->method() == HTTP_GET && match.method.empty()) {
std::string data = this->alarm_control_panel_json(obj, obj->get_state(), DETAIL_STATE);
request->send(200, "application/json", data.c_str());
return;
@@ -1251,7 +1251,7 @@ bool WebServer::canHandle(AsyncWebServerRequest *request) {
#endif
#ifdef USE_BUTTON
if (request->method() == HTTP_POST && match.domain == "button")
if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain == "button")
return true;
#endif

View File

@@ -13,7 +13,7 @@
#include <freertos/semphr.h>
#endif
#if USE_WEBSERVER_VERSION == 2
#if USE_WEBSERVER_VERSION >= 2
extern const uint8_t ESPHOME_WEBSERVER_INDEX_HTML[] PROGMEM;
extern const size_t ESPHOME_WEBSERVER_INDEX_HTML_SIZE;
#endif

View File

@@ -157,7 +157,7 @@ network::IPAddresses WiFiComponent::wifi_sta_ip_addresses() {
} else {
addresses[0] = network::IPAddress(&ip.ip);
}
#if LWIP_IPV6
#if USE_NETWORK_IPV6
ip6_addr_t ipv6;
err = tcpip_adapter_get_ip6_global(TCPIP_ADAPTER_IF_STA, &ipv6);
if (err != ESP_OK) {
@@ -171,7 +171,7 @@ network::IPAddresses WiFiComponent::wifi_sta_ip_addresses() {
} else {
addresses[2] = network::IPAddress(&ipv6);
}
#endif /* LWIP_IPV6 */
#endif /* USE_NETWORK_IPV6 */
return addresses;
}

View File

@@ -1,6 +1,6 @@
"""Constants used by esphome."""
__version__ = "2024.3.0b1"
__version__ = "2024.3.0"
ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
VALID_SUBSTITUTIONS_CHARACTERS = (

View File

@@ -5,6 +5,7 @@
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#include <utility>
#include <cinttypes>
namespace esphome {
@@ -140,18 +141,35 @@ bool Component::is_ready() {
(this->component_state_ & COMPONENT_STATE_MASK) == COMPONENT_STATE_SETUP;
}
bool Component::can_proceed() { return true; }
bool Component::status_has_warning() { return this->component_state_ & STATUS_LED_WARNING; }
bool Component::status_has_error() { return this->component_state_ & STATUS_LED_ERROR; }
void Component::status_set_warning() {
bool Component::status_has_warning() const { return this->component_state_ & STATUS_LED_WARNING; }
bool Component::status_has_error() const { return this->component_state_ & STATUS_LED_ERROR; }
void Component::status_set_warning(const char *message) {
// Don't spam the log. This risks missing different warning messages though.
if ((this->component_state_ & STATUS_LED_WARNING) != 0)
return;
this->component_state_ |= STATUS_LED_WARNING;
App.app_state_ |= STATUS_LED_WARNING;
ESP_LOGW(this->get_component_source(), "Warning set: %s", message);
}
void Component::status_set_error() {
void Component::status_set_error(const char *message) {
if ((this->component_state_ & STATUS_LED_ERROR) != 0)
return;
this->component_state_ |= STATUS_LED_ERROR;
App.app_state_ |= STATUS_LED_ERROR;
ESP_LOGE(this->get_component_source(), "Error set: %s", message);
}
void Component::status_clear_warning() {
if ((this->component_state_ & STATUS_LED_WARNING) == 0)
return;
this->component_state_ &= ~STATUS_LED_WARNING;
ESP_LOGW(this->get_component_source(), "Warning cleared");
}
void Component::status_clear_error() {
if ((this->component_state_ & STATUS_LED_ERROR) == 0)
return;
this->component_state_ &= ~STATUS_LED_ERROR;
ESP_LOGE(this->get_component_source(), "Error cleared");
}
void Component::status_clear_warning() { this->component_state_ &= ~STATUS_LED_WARNING; }
void Component::status_clear_error() { this->component_state_ &= ~STATUS_LED_ERROR; }
void Component::status_momentary_warning(const std::string &name, uint32_t length) {
this->status_set_warning();
this->set_timeout(name, length, [this]() { this->status_clear_warning(); });
@@ -211,8 +229,8 @@ WarnIfComponentBlockingGuard::~WarnIfComponentBlockingGuard() {
uint32_t now = millis();
if (now - started_ > 50) {
const char *src = component_ == nullptr ? "<null>" : component_->get_component_source();
ESP_LOGW(TAG, "Component %s took a long time for an operation (%.2f s).", src, (now - started_) / 1e3f);
ESP_LOGW(TAG, "Components should block for at most 20-30ms.");
ESP_LOGW(TAG, "Component %s took a long time for an operation (%" PRIu32 " ms).", src, (now - started_));
ESP_LOGW(TAG, "Components should block for at most 30 ms.");
;
}
}

View File

@@ -124,13 +124,13 @@ class Component {
virtual bool can_proceed();
bool status_has_warning();
bool status_has_warning() const;
bool status_has_error();
bool status_has_error() const;
void status_set_warning();
void status_set_warning(const char *message = "unspecified");
void status_set_error();
void status_set_error(const char *message = "unspecified");
void status_clear_warning();

View File

@@ -516,7 +516,8 @@ class ImportRequestHandler(BaseHandler):
self.set_status(500)
self.write("File already exists")
return
except ValueError:
except ValueError as e:
_LOGGER.error(e)
self.set_status(422)
self.write("Invalid package url")
return
@@ -687,6 +688,11 @@ class MainRequestHandler(BaseHandler):
@authenticated
def get(self) -> None:
begin = bool(self.get_argument("begin", False))
if settings.using_password:
# Simply accessing the xsrf_token sets the cookie for us
self.xsrf_token # pylint: disable=pointless-statement
else:
self.clear_cookie("_xsrf")
self.render(
"index.template.html",
@@ -1101,6 +1107,7 @@ def make_app(debug=get_bool_env(ENV_DEV)) -> tornado.web.Application:
"log_function": log_function,
"websocket_ping_interval": 30.0,
"template_path": get_base_frontend_path(),
"xsrf_cookies": settings.using_password,
}
rel = settings.relative_url
return tornado.web.Application(

View File

@@ -12,10 +12,11 @@ pyserial==3.5
platformio==6.1.13 # When updating platformio, also update Dockerfile
esptool==4.7.0
click==8.1.7
esphome-dashboard==20231107.0
esphome-dashboard==20240319.0
aioesphomeapi==23.1.1
zeroconf==0.131.0
python-magic==0.4.27
ruamel.yaml==0.18.6 # dashboard_import
# esp-idf requires this, but doesn't bundle it by default
# https://github.com/espressif/esp-idf/blob/220590d599e134d7a5e7f1e683cc4550349ffbf8/requirements.txt#L24

View File

@@ -70,11 +70,11 @@ def splitlines_no_ends(string):
return [s.strip() for s in string.splitlines()]
def changed_files():
def changed_files(branch="dev"):
check_remotes = ["upstream", "origin"]
check_remotes.extend(splitlines_no_ends(get_output("git", "remote")))
for remote in check_remotes:
command = ["git", "merge-base", f"refs/remotes/{remote}/dev", "HEAD"]
command = ["git", "merge-base", f"refs/remotes/{remote}/{branch}", "HEAD"]
try:
merge_base = splitlines_no_ends(get_output(*command))[0]
break

View File

@@ -120,13 +120,22 @@ def main():
parser.add_argument(
"-c", "--changed", action="store_true", help="Only run on changed files"
)
parser.add_argument(
"-b", "--branch", help="Branch to compare changed files against"
)
args = parser.parse_args()
if args.branch and not args.changed:
parser.error("--branch requires --changed")
files = git_ls_files()
files = filter(filter_component_files, files)
if args.changed:
changed = changed_files()
if args.branch:
changed = changed_files(args.branch)
else:
changed = changed_files()
files = [f for f in files if f in changed]
components = extract_component_names_array_from_files_array(files)

View File

@@ -2,7 +2,8 @@ spi:
- id: spi_id_1
type: single
clk_pin:
number: GPIO7
number: GPIO0
ignore_strapping_warning: true
allow_other_uses: false
mosi_pin: GPIO6
interface: hardware

View File

@@ -23,6 +23,9 @@ logger:
api:
reboot_timeout: 10min
web_server:
version: 3
time:
- platform: sntp