Merge remote-tracking branch 'upstream/dev' into rp2040-upload-improvements

This commit is contained in:
J. Nick Koston
2026-03-05 13:11:02 -10:00
29 changed files with 225 additions and 177 deletions
+1 -1
View File
@@ -1 +1 @@
b97e16a84153b2a4cfc51137cd6121db3c32374504b2bea55144413b3e573052
b6f8c16c1ddd222134bf4a71910b4c832e764e23caf49f9bce3280b079955fcf
+2 -13
View File
@@ -173,19 +173,8 @@ float ADS1115Component::request_measurement(ADS1115Multiplexer multiplexer, ADS1
}
if (resolution == ADS1015_12_BITS) {
bool negative = (raw_conversion >> 15) == 1;
// shift raw_conversion as it's only 12-bits, left justified
raw_conversion = raw_conversion >> (16 - ADS1015_12_BITS);
// check if number was negative in order to keep the sign
if (negative) {
// the number was negative
// 1) set the negative bit back
raw_conversion |= 0x8000;
// 2) reset the former (shifted) negative bit
raw_conversion &= 0xF7FF;
}
// ADS1015 returns 12-bit value left-justified in 16 bits; shift right and sign-extend
raw_conversion = static_cast<uint16_t>(static_cast<int16_t>(raw_conversion) >> (16 - ADS1015_12_BITS));
}
auto signed_conversion = static_cast<int16_t>(raw_conversion);
+56
View File
@@ -1,5 +1,9 @@
#include "audio.h"
#include "esphome/core/helpers.h"
#include <cstring>
namespace esphome {
namespace audio {
@@ -58,6 +62,58 @@ const char *audio_file_type_to_string(AudioFileType file_type) {
}
}
AudioFileType detect_audio_file_type(const char *content_type, const char *url) {
// Try Content-Type header first
if (content_type != nullptr && content_type[0] != '\0') {
#ifdef USE_AUDIO_MP3_SUPPORT
if (strcasecmp(content_type, "mp3") == 0 || strcasecmp(content_type, "audio/mp3") == 0 ||
strcasecmp(content_type, "audio/mpeg") == 0) {
return AudioFileType::MP3;
}
#endif
if (strcasecmp(content_type, "audio/wav") == 0) {
return AudioFileType::WAV;
}
#ifdef USE_AUDIO_FLAC_SUPPORT
if (strcasecmp(content_type, "audio/flac") == 0 || strcasecmp(content_type, "audio/x-flac") == 0) {
return AudioFileType::FLAC;
}
#endif
#ifdef USE_AUDIO_OPUS_SUPPORT
// Match "audio/ogg" with a codecs parameter containing "opus"
// Valid forms: audio/ogg;codecs=opus, audio/ogg; codecs="opus", etc.
// Plain "audio/ogg" without opus is not matched (almost always Ogg Vorbis)
if (strncasecmp(content_type, "audio/ogg", 9) == 0 && strcasestr(content_type + 9, "opus") != nullptr) {
return AudioFileType::OPUS;
}
#endif
}
// Fallback to URL extension
if (url != nullptr && url[0] != '\0') {
if (str_endswith_ignore_case(url, ".wav")) {
return AudioFileType::WAV;
}
#ifdef USE_AUDIO_MP3_SUPPORT
if (str_endswith_ignore_case(url, ".mp3")) {
return AudioFileType::MP3;
}
#endif
#ifdef USE_AUDIO_FLAC_SUPPORT
if (str_endswith_ignore_case(url, ".flac")) {
return AudioFileType::FLAC;
}
#endif
#ifdef USE_AUDIO_OPUS_SUPPORT
if (str_endswith_ignore_case(url, ".opus")) {
return AudioFileType::OPUS;
}
#endif
}
return AudioFileType::NONE;
}
void scale_audio_samples(const int16_t *audio_samples, int16_t *output_buffer, int16_t scale_factor,
size_t samples_to_scale) {
// Note the assembly dsps_mulc function has audio glitches if the input and output buffers are the same.
+7
View File
@@ -130,6 +130,13 @@ struct AudioFile {
/// @return const char pointer to the readable file type
const char *audio_file_type_to_string(AudioFileType file_type);
/// @brief Detect audio file type from a Content-Type header value and/or URL extension.
/// Tries Content-Type first, then falls back to URL extension. Either parameter may be null.
/// @param content_type Content-Type header value (may be null or empty)
/// @param url URL to inspect for file extension (may be null or empty)
/// @return The detected AudioFileType, or NONE if unknown
AudioFileType detect_audio_file_type(const char *content_type, const char *url);
/// @brief Scales Q15 fixed point audio samples. Scales in place if audio_samples == output_buffer.
/// @param audio_samples PCM int16 audio samples
/// @param output_buffer Buffer to store the scaled samples
+3 -47
View File
@@ -185,26 +185,8 @@ esp_err_t AudioReader::start(const std::string &uri, AudioFileType &file_type) {
return err;
}
if (str_endswith_ignore_case(url, ".wav")) {
file_type = AudioFileType::WAV;
}
#ifdef USE_AUDIO_MP3_SUPPORT
else if (str_endswith_ignore_case(url, ".mp3")) {
file_type = AudioFileType::MP3;
}
#endif
#ifdef USE_AUDIO_FLAC_SUPPORT
else if (str_endswith_ignore_case(url, ".flac")) {
file_type = AudioFileType::FLAC;
}
#endif
#ifdef USE_AUDIO_OPUS_SUPPORT
else if (str_endswith_ignore_case(url, ".opus")) {
file_type = AudioFileType::OPUS;
}
#endif
else {
file_type = AudioFileType::NONE;
file_type = detect_audio_file_type(nullptr, url);
if (file_type == AudioFileType::NONE) {
this->cleanup_connection_();
return ESP_ERR_NOT_SUPPORTED;
}
@@ -232,32 +214,6 @@ AudioReaderState AudioReader::read() {
return AudioReaderState::FAILED;
}
AudioFileType AudioReader::get_audio_type(const char *content_type) {
#ifdef USE_AUDIO_MP3_SUPPORT
if (strcasecmp(content_type, "mp3") == 0 || strcasecmp(content_type, "audio/mp3") == 0 ||
strcasecmp(content_type, "audio/mpeg") == 0) {
return AudioFileType::MP3;
}
#endif
if (strcasecmp(content_type, "audio/wav") == 0) {
return AudioFileType::WAV;
}
#ifdef USE_AUDIO_FLAC_SUPPORT
if (strcasecmp(content_type, "audio/flac") == 0 || strcasecmp(content_type, "audio/x-flac") == 0) {
return AudioFileType::FLAC;
}
#endif
#ifdef USE_AUDIO_OPUS_SUPPORT
// Match "audio/ogg" with a codecs parameter containing "opus"
// Valid forms: audio/ogg;codecs=opus, audio/ogg; codecs="opus", etc.
// Plain "audio/ogg" without a codecs parameter is not matched, as those are almost always Ogg Vorbis streams
if (strncasecmp(content_type, "audio/ogg", 9) == 0 && strcasestr(content_type + 9, "opus") != nullptr) {
return AudioFileType::OPUS;
}
#endif
return AudioFileType::NONE;
}
esp_err_t AudioReader::http_event_handler(esp_http_client_event_t *evt) {
// Based on https://github.com/maroc81/WeatherLily/tree/main/main/net accessed 20241224
AudioReader *this_reader = (AudioReader *) evt->user_data;
@@ -265,7 +221,7 @@ esp_err_t AudioReader::http_event_handler(esp_http_client_event_t *evt) {
switch (evt->event_id) {
case HTTP_EVENT_ON_HEADER:
if (strcasecmp(evt->header_key, "Content-Type") == 0) {
this_reader->audio_file_type_ = get_audio_type(evt->header_value);
this_reader->audio_file_type_ = detect_audio_file_type(evt->header_value, nullptr);
}
break;
default:
-5
View File
@@ -58,11 +58,6 @@ class AudioReader {
/// @brief Monitors the http client events to attempt determining the file type from the Content-Type header
static esp_err_t http_event_handler(esp_http_client_event_t *evt);
/// @brief Determines the audio file type from the http header's Content-Type key
/// @param content_type string with the Content-Type key
/// @return AudioFileType of the url, if it can be determined. If not, return AudioFileType::NONE.
static AudioFileType get_audio_type(const char *content_type);
AudioReaderState file_read_();
AudioReaderState http_read_();
+7 -3
View File
@@ -147,13 +147,17 @@ uint32_t CSE7761Component::read_(uint8_t reg, uint8_t size) {
}
uint32_t CSE7761Component::coefficient_by_unit_(uint32_t unit) {
uint32_t coeff = 0;
switch (unit) {
case RMS_UC:
return 0x400000 * 100 / this->data_.coefficient[RMS_UC];
coeff = this->data_.coefficient[RMS_UC];
return coeff ? 0x400000 * 100 / coeff : 0;
case RMS_IAC:
return (0x800000 * 100 / this->data_.coefficient[RMS_IAC]) * 10; // Stay within 32 bits
coeff = this->data_.coefficient[RMS_IAC];
return coeff ? (0x800000 * 100 / coeff) * 10 : 0; // Stay within 32 bits
case POWER_PAC:
return 0x80000000 / this->data_.coefficient[POWER_PAC];
coeff = this->data_.coefficient[POWER_PAC];
return coeff ? 0x80000000 / coeff : 0;
}
return 0;
}
@@ -437,10 +437,15 @@ void FeedbackCover::recompute_position_() {
}
// check if we have an acceleration_wait_time, and remove from position computation
if (now > (this->start_dir_time_ + this->acceleration_wait_time_)) {
this->position +=
dir * (now - std::max(this->start_dir_time_ + this->acceleration_wait_time_, this->last_recompute_time_)) /
(action_dur - this->acceleration_wait_time_);
if (now - this->start_dir_time_ > this->acceleration_wait_time_) {
uint32_t accel_end_time = this->start_dir_time_ + this->acceleration_wait_time_;
uint32_t effective_start;
if (static_cast<int32_t>(accel_end_time - this->last_recompute_time_) >= 0) {
effective_start = accel_end_time;
} else {
effective_start = this->last_recompute_time_;
}
this->position += dir * (now - effective_start) / (action_dur - this->acceleration_wait_time_);
this->position = clamp(this->position, min_pos, max_pos);
}
this->last_recompute_time_ = now;
+10 -6
View File
@@ -24,12 +24,16 @@ void HC8Component::setup() {
}
void HC8Component::update() {
uint32_t now_ms = App.get_loop_component_start_time();
uint32_t warmup_ms = this->warmup_seconds_ * 1000;
if (now_ms < warmup_ms) {
ESP_LOGW(TAG, "HC8 warming up, %" PRIu32 " s left", (warmup_ms - now_ms) / 1000);
this->status_set_warning();
return;
if (!this->warmup_complete_) {
uint32_t now_ms = App.get_loop_component_start_time();
uint32_t warmup_ms = this->warmup_seconds_ * 1000;
if (now_ms < warmup_ms) {
ESP_LOGW(TAG, "HC8 warming up, %" PRIu32 " s left", (warmup_ms - now_ms) / 1000);
this->status_set_warning();
return;
}
this->warmup_complete_ = true;
this->status_clear_warning();
}
while (this->available())
+1
View File
@@ -23,6 +23,7 @@ class HC8Component : public PollingComponent, public uart::UARTDevice {
protected:
sensor::Sensor *co2_sensor_{nullptr};
uint32_t warmup_seconds_{0};
bool warmup_complete_{false};
};
template<typename... Ts> class HC8CalibrateAction : public Action<Ts...>, public Parented<HC8Component> {
+1 -1
View File
@@ -239,7 +239,7 @@ void HE60rCover::recompute_position_() {
return;
const uint32_t now = millis();
if (now > this->last_recompute_time_) {
if (now != this->last_recompute_time_) {
auto diff = (unsigned) (now - last_recompute_time_);
float delta;
switch (this->current_operation) {
@@ -61,7 +61,7 @@ void MatrixKeypad::loop() {
ESP_LOGD(TAG, "key @ row %d, col %d released", row, col);
for (auto &listener : this->listeners_)
listener->button_released(row, col);
if (!this->keys_.empty()) {
if (this->pressed_key_ < (int) this->keys_.size()) {
uint8_t keycode = this->keys_[this->pressed_key_];
ESP_LOGD(TAG, "key '%c' released", keycode);
for (auto &listener : this->listeners_)
@@ -84,7 +84,7 @@ void MatrixKeypad::loop() {
ESP_LOGD(TAG, "key @ row %d, col %d pressed", row, col);
for (auto &listener : this->listeners_)
listener->button_pressed(row, col);
if (!this->keys_.empty()) {
if (key < (int) this->keys_.size()) {
uint8_t keycode = this->keys_[key];
ESP_LOGD(TAG, "key '%c' pressed", keycode);
for (auto &trigger : this->key_triggers_)
+3 -3
View File
@@ -337,7 +337,7 @@ void Nextion::loop() {
this->started_ms_ = App.get_loop_component_start_time();
if (this->startup_override_ms_ > 0 &&
this->started_ms_ + this->startup_override_ms_ < App.get_loop_component_start_time()) {
App.get_loop_component_start_time() - this->started_ms_ > this->startup_override_ms_) {
ESP_LOGV(TAG, "Manual ready set");
this->connection_state_.nextion_reports_is_setup_ = true;
}
@@ -853,10 +853,10 @@ void Nextion::process_nextion_commands_() {
const uint32_t ms = App.get_loop_component_start_time();
if (this->max_q_age_ms_ > 0 && !this->nextion_queue_.empty() &&
this->nextion_queue_.front()->queue_time + this->max_q_age_ms_ < ms) {
ms - this->nextion_queue_.front()->queue_time > this->max_q_age_ms_) {
for (size_t i = 0; i < this->nextion_queue_.size(); i++) {
NextionComponentBase *component = this->nextion_queue_[i]->component;
if (this->nextion_queue_[i]->queue_time + this->max_q_age_ms_ < ms) {
if (ms - this->nextion_queue_[i]->queue_time > this->max_q_age_ms_) {
if (this->nextion_queue_[i]->queue_time == 0) {
ESP_LOGD(TAG, "Remove old queue '%s':'%s' (t=0)", component->get_queue_type_string().c_str(),
component->get_variable_name().c_str());
@@ -330,15 +330,16 @@ void PacketTransport::update() {
if (!this->ping_pong_enable_) {
return;
}
auto now = millis() / 1000;
if (this->last_key_time_ + this->ping_pong_recyle_time_ < now) {
uint32_t now = millis();
uint32_t ping_request_age = now - this->last_key_time_;
if (ping_request_age > this->ping_pong_recyle_time_ * 1000u) {
this->resend_ping_key_ = this->ping_pong_enable_;
ESP_LOGV(TAG, "Ping request, age %" PRIu32, now - this->last_key_time_);
ESP_LOGV(TAG, "Ping request, age %" PRIu32, ping_request_age);
this->last_key_time_ = now;
}
for (const auto &provider : this->providers_) {
uint32_t key_response_age = now - provider.second.last_key_response_time;
if (key_response_age > (this->ping_pong_recyle_time_ * 2u)) {
if (key_response_age > (this->ping_pong_recyle_time_ * 2000u)) {
#ifdef USE_STATUS_SENSOR
if (provider.second.status_sensor != nullptr && provider.second.status_sensor->state) {
ESP_LOGI(TAG, "Ping status for %s timeout at %" PRIu32 " with age %" PRIu32, provider.first.c_str(), now,
@@ -496,7 +497,7 @@ void PacketTransport::process_(std::span<const uint8_t> data) {
if (decoder.decode(PING_KEY, key) == DECODE_OK) {
if (key == this->ping_key_) {
ping_key_seen = true;
provider.last_key_response_time = millis() / 1000;
provider.last_key_response_time = millis();
ESP_LOGV(TAG, "Found good ping key %X at timestamp %" PRIu32, (unsigned) key, provider.last_key_response_time);
} else {
ESP_LOGV(TAG, "Unknown ping key %X", (unsigned) key);
+3 -3
View File
@@ -91,7 +91,7 @@ def _parse_platform_version(value):
# The default/recommended arduino framework version
# - https://github.com/earlephilhower/arduino-pico/releases
# - https://api.registry.platformio.org/v3/packages/earlephilhower/tool/framework-arduinopico
RECOMMENDED_ARDUINO_FRAMEWORK_VERSION = cv.Version(5, 5, 0)
RECOMMENDED_ARDUINO_FRAMEWORK_VERSION = cv.Version(5, 5, 1)
# The raspberrypi platform version to use for arduino frameworks
# - https://github.com/maxgerhardt/platform-raspberrypi/tags
@@ -101,8 +101,8 @@ RECOMMENDED_ARDUINO_PLATFORM_VERSION = "v1.4.0-gcc14-arduinopico460"
def _arduino_check_versions(value):
value = value.copy()
lookups = {
"dev": (cv.Version(5, 5, 0), "https://github.com/earlephilhower/arduino-pico"),
"latest": (cv.Version(5, 5, 0), None),
"dev": (cv.Version(5, 5, 1), "https://github.com/earlephilhower/arduino-pico"),
"latest": (cv.Version(5, 5, 1), None),
"recommended": (RECOMMENDED_ARDUINO_FRAMEWORK_VERSION, None),
}
@@ -9,21 +9,16 @@ namespace esphome {
namespace runtime_stats {
RuntimeStatsCollector::RuntimeStatsCollector() : log_interval_(60000), next_log_time_(0) {
RuntimeStatsCollector::RuntimeStatsCollector() : log_interval_(60000), next_log_time_(60000) {
global_runtime_stats = this;
}
void RuntimeStatsCollector::record_component_time(Component *component, uint32_t duration_us, uint32_t current_time) {
void RuntimeStatsCollector::record_component_time(Component *component, uint32_t duration_us) {
if (component == nullptr)
return;
// Record stats using component pointer as key
this->component_stats_[component].record_time(duration_us);
if (this->next_log_time_ == 0) {
this->next_log_time_ = current_time + this->log_interval_;
return;
}
}
void RuntimeStatsCollector::log_stats_() {
@@ -88,10 +83,7 @@ void RuntimeStatsCollector::log_stats_() {
}
void RuntimeStatsCollector::process_pending_stats(uint32_t current_time) {
if (this->next_log_time_ == 0)
return;
if (current_time >= this->next_log_time_) {
if ((int32_t) (current_time - this->next_log_time_) >= 0) {
this->log_stats_();
this->reset_stats_();
this->next_log_time_ = current_time + this->log_interval_;
@@ -7,6 +7,7 @@
#include <map>
#include <cstdint>
#include <cstring>
#include "esphome/core/hal.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
@@ -80,10 +81,13 @@ class RuntimeStatsCollector {
public:
RuntimeStatsCollector();
void set_log_interval(uint32_t log_interval) { this->log_interval_ = log_interval; }
void set_log_interval(uint32_t log_interval) {
this->log_interval_ = log_interval;
this->next_log_time_ = millis() + log_interval;
}
uint32_t get_log_interval() const { return this->log_interval_; }
void record_component_time(Component *component, uint32_t duration_us, uint32_t current_time);
void record_component_time(Component *component, uint32_t duration_us);
// Process any pending stats printing (should be called after component loop)
void process_pending_stats(uint32_t current_time);
@@ -101,7 +105,7 @@ class RuntimeStatsCollector {
// We use Component* as the key since each component is unique
std::map<Component *, ComponentRuntimeStats> component_stats_;
uint32_t log_interval_;
uint32_t next_log_time_;
uint32_t next_log_time_{0};
};
} // namespace runtime_stats
@@ -177,10 +177,14 @@ void MR60BHA2Component::process_frame_(uint16_t frame_id, uint16_t frame_type, c
uint16_t has_target_int = encode_uint16(data[1], data[0]);
this->has_target_binary_sensor_->publish_state(has_target_int);
if (has_target_int == 0) {
this->breath_rate_sensor_->publish_state(0.0);
this->heart_rate_sensor_->publish_state(0.0);
this->distance_sensor_->publish_state(0.0);
this->num_targets_sensor_->publish_state(0);
if (this->breath_rate_sensor_ != nullptr)
this->breath_rate_sensor_->publish_state(0.0);
if (this->heart_rate_sensor_ != nullptr)
this->heart_rate_sensor_->publish_state(0.0);
if (this->distance_sensor_ != nullptr)
this->distance_sensor_->publish_state(0.0);
if (this->num_targets_sensor_ != nullptr)
this->num_targets_sensor_->publish_state(0);
}
}
break;
+1 -13
View File
@@ -466,7 +466,7 @@ void HOT ST7735::write_display_data_() {
}
void ST7735::spi_master_write_addr_(uint16_t addr1, uint16_t addr2) {
static uint8_t byte[4];
uint8_t byte[4];
byte[0] = (addr1 >> 8) & 0xFF;
byte[1] = addr1 & 0xFF;
byte[2] = (addr2 >> 8) & 0xFF;
@@ -476,17 +476,5 @@ void ST7735::spi_master_write_addr_(uint16_t addr1, uint16_t addr2) {
this->write_array(byte, 4);
}
void ST7735::spi_master_write_color_(uint16_t color, uint16_t size) {
static uint8_t byte[1024];
int index = 0;
for (int i = 0; i < size; i++) {
byte[index++] = (color >> 8) & 0xFF;
byte[index++] = color & 0xFF;
}
this->dc_pin_->digital_write(true);
write_array(byte, size * 2);
}
} // namespace st7735
} // namespace esphome
-1
View File
@@ -68,7 +68,6 @@ class ST7735 : public display::DisplayBuffer,
void set_addr_window_(uint16_t x, uint16_t y, uint16_t w, uint16_t h);
void draw_absolute_pixel_internal(int x, int y, Color color) override;
void spi_master_write_addr_(uint16_t addr1, uint16_t addr2);
void spi_master_write_color_(uint16_t color, uint16_t size);
int get_width_internal() override;
int get_height_internal() override;
+19 -10
View File
@@ -1,11 +1,16 @@
#include "st7789v.h"
#include "esphome/core/log.h"
#include <algorithm>
namespace esphome {
namespace st7789v {
static const char *const TAG = "st7789v";
static const size_t TEMP_BUFFER_SIZE = 128;
#ifdef USE_ESP32
static constexpr size_t TEMP_BUFFER_SIZE = 1024;
#else
static constexpr size_t TEMP_BUFFER_SIZE = 512;
#endif
void ST7789V::setup() {
#ifdef USE_POWER_SUPPLY
@@ -236,7 +241,7 @@ void ST7789V::write_data_(uint8_t value) {
}
void ST7789V::write_addr_(uint16_t addr1, uint16_t addr2) {
static uint8_t byte[4];
uint8_t byte[4];
byte[0] = (addr1 >> 8) & 0xFF;
byte[1] = addr1 & 0xFF;
byte[2] = (addr2 >> 8) & 0xFF;
@@ -247,15 +252,19 @@ void ST7789V::write_addr_(uint16_t addr1, uint16_t addr2) {
}
void ST7789V::write_color_(uint16_t color, uint16_t size) {
static uint8_t byte[1024];
int index = 0;
for (int i = 0; i < size; i++) {
byte[index++] = (color >> 8) & 0xFF;
byte[index++] = color & 0xFF;
}
uint8_t byte[TEMP_BUFFER_SIZE];
uint16_t remaining = size;
this->dc_pin_->digital_write(true);
write_array(byte, size * 2);
while (remaining > 0) {
uint16_t batch = std::min(remaining, static_cast<uint16_t>(sizeof(byte) / 2));
int index = 0;
for (int i = 0; i < batch; i++) {
byte[index++] = (color >> 8) & 0xFF;
byte[index++] = color & 0xFF;
}
this->write_array(byte, batch * 2);
remaining -= batch;
}
}
size_t ST7789V::get_buffer_length_() {
+9 -6
View File
@@ -72,16 +72,19 @@ void ST7920::goto_xy_(uint16_t x, uint16_t y) {
}
void HOT ST7920::write_display_data() {
uint8_t i, j, b;
for (j = 0; j < (uint8_t) (this->get_height_internal() / 2); j++) {
int i, j;
uint8_t b;
int width_bytes = this->get_width_internal() / 8;
int half_height = this->get_height_internal() / 2;
for (j = 0; j < half_height; j++) {
this->goto_xy_(0, j);
this->enable();
for (i = 0; i < 16; i++) { // 16 bytes from line #0+
b = this->buffer_[i + j * 16];
for (i = 0; i < width_bytes; i++) {
b = this->buffer_[i + j * width_bytes];
this->send_(LCD_DATA, b);
}
for (i = 0; i < 16; i++) { // 16 bytes from line #32+
b = this->buffer_[i + (j + 32) * 16];
for (i = 0; i < width_bytes; i++) {
b = this->buffer_[i + (j + half_height) * width_bytes];
this->send_(LCD_DATA, b);
}
this->disable();
+2 -2
View File
@@ -118,8 +118,8 @@ void TMP1075Sensor::send_alert_limit_high_() {
}
static uint16_t temp2regvalue(const float temp) {
const uint16_t regvalue = temp / 0.0625f;
return regvalue << 4;
const int16_t regvalue = static_cast<int16_t>(temp / 0.0625f);
return static_cast<uint16_t>(regvalue << 4);
}
static float regvalue2temp(const uint16_t regvalue) {
+2 -2
View File
@@ -171,8 +171,8 @@ void USBUartChannel::flush() {
// Safe to call from the main loop only.
// The 100 ms timeout guards against a device that stops responding mid-flush;
// in that case the main loop is blocked for the full duration.
uint32_t deadline = millis() + 100; // 100 ms safety timeout
while ((!this->output_queue_.empty() || this->output_started_.load()) && millis() < deadline) {
uint32_t start = millis(); // 100 ms safety timeout
while ((!this->output_queue_.empty() || this->output_started_.load()) && millis() - start < 100) {
// Kick start_output() in case data arrived but no transfer is in flight yet.
this->parent_->start_output(this);
yield();
+1 -1
View File
@@ -48,7 +48,7 @@ class WhirlpoolClimate : public climate_ir::ClimateIR {
/// Handle received IR Buffer
bool on_receive(remote_base::RemoteReceiveData data) override;
/// Set the time of the last transmission.
int32_t last_transmit_time_{};
uint32_t last_transmit_time_{};
bool send_swing_cmd_{false};
Model model_;
+7 -1
View File
@@ -210,7 +210,13 @@ WIFI_NETWORK_AP = WIFI_NETWORK_BASE.extend(
def wifi_network_ap(value):
if value is None:
value = {}
return WIFI_NETWORK_AP(value)
config = WIFI_NETWORK_AP(value)
if CONF_MANUAL_IP in config and CORE.is_rp2040:
raise cv.Invalid(
"Manual AP IP configuration is not supported on RP2040. "
"The AP uses the default IP 192.168.4.1"
)
return config
WIFI_NETWORK_STA = WIFI_NETWORK_BASE.extend(
@@ -18,6 +18,25 @@ namespace esphome::wifi {
static const char *const TAG = "wifi_pico_w";
// Check if STA is fully connected (WiFi joined + has IP address).
// Do NOT use WiFi.status() or WiFi.connected() for this — in AP-only mode they
// unconditionally return true regardless of STA state, causing false positives
// when the fallback AP is active.
static bool wifi_sta_connected() {
int link = cyw43_wifi_link_status(&cyw43_state, CYW43_ITF_STA);
IPAddress local = WiFi.localIP();
if (link == CYW43_LINK_JOIN && local.isSet()) {
// Verify the IP is a real STA IP, not the AP's IP leaking through
IPAddress ap_ip = WiFi.softAPIP();
if (local == ap_ip) {
ESP_LOGV(TAG, "wifi_sta_connected: localIP %s matches AP IP, ignoring", local.toString().c_str());
return false;
}
return true;
}
return false;
}
// Track previous state for detecting changes
static bool s_sta_was_connected = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
static bool s_sta_had_ip = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
@@ -27,17 +46,21 @@ bool WiFiComponent::wifi_mode_(optional<bool> sta, optional<bool> ap) {
if (sta.has_value()) {
if (sta.value()) {
cyw43_wifi_set_up(&cyw43_state, CYW43_ITF_STA, true, CYW43_COUNTRY_WORLDWIDE);
} else {
// Leave the STA network so the radio is free for scanning.
// Use cyw43_wifi_leave directly to avoid corrupting Arduino framework state.
cyw43_wifi_leave(&cyw43_state, CYW43_ITF_STA);
}
}
bool ap_state = false;
if (ap.has_value()) {
if (ap.value()) {
cyw43_wifi_set_up(&cyw43_state, CYW43_ITF_AP, true, CYW43_COUNTRY_WORLDWIDE);
ap_state = true;
} else {
cyw43_wifi_set_up(&cyw43_state, CYW43_ITF_AP, false, CYW43_COUNTRY_WORLDWIDE);
}
this->ap_started_ = ap.value();
}
this->ap_started_ = ap_state;
return true;
}
@@ -129,8 +152,8 @@ WiFiSTAConnectStatus WiFiComponent::wifi_sta_connect_status_() const {
int status = cyw43_wifi_link_status(&cyw43_state, CYW43_ITF_STA);
switch (status) {
case CYW43_LINK_JOIN:
// WiFi joined, check if we have an IP address via the Arduino framework's WiFi class
if (WiFi.status() == WL_CONNECTED) {
// WiFi joined, check if STA has an IP address via wifi_sta_connected()
if (wifi_sta_connected()) {
return WiFiSTAConnectStatus::CONNECTED;
}
return WiFiSTAConnectStatus::CONNECTING;
@@ -188,19 +211,9 @@ bool WiFiComponent::wifi_scan_start_(bool passive) {
#ifdef USE_WIFI_AP
bool WiFiComponent::wifi_ap_ip_config_(const optional<ManualIP> &manual_ip) {
esphome::network::IPAddress ip_address, gateway, subnet, dns;
if (manual_ip.has_value()) {
ip_address = manual_ip->static_ip;
gateway = manual_ip->gateway;
subnet = manual_ip->subnet;
dns = manual_ip->static_ip;
} else {
ip_address = network::IPAddress(192, 168, 4, 1);
gateway = network::IPAddress(192, 168, 4, 1);
subnet = network::IPAddress(255, 255, 255, 0);
dns = network::IPAddress(192, 168, 4, 1);
}
WiFi.config(ip_address, dns, gateway, subnet);
// AP IP is configured by WiFi.beginAP() internally using defaults (192.168.4.1).
// Manual AP IP has never worked on RP2040 — WiFi.config() configures the STA
// interface, not the AP. This is now rejected at config validation time.
return true;
}
@@ -219,18 +232,25 @@ bool WiFiComponent::wifi_start_ap_(const WiFiAP &ap) {
}
#endif
WiFi.beginAP(ap.ssid_.c_str(), ap.password_.c_str(), ap.has_channel() ? ap.get_channel() : 1);
// Pass nullptr for empty password — CYW43 uses the password pointer (not length)
// to choose between OPEN and WPA2 auth mode.
const char *ap_password = ap.password_.empty() ? nullptr : ap.password_.c_str();
WiFi.beginAP(ap.ssid_.c_str(), ap_password, ap.has_channel() ? ap.get_channel() : 1);
return true;
}
network::IPAddress WiFiComponent::wifi_soft_ap_ip() { return {(const ip_addr_t *) WiFi.localIP()}; }
network::IPAddress WiFiComponent::wifi_soft_ap_ip() { return {(const ip_addr_t *) WiFi.softAPIP()}; }
#endif // USE_WIFI_AP
bool WiFiComponent::wifi_disconnect_() {
// Use Arduino WiFi.disconnect() instead of raw cyw43_wifi_leave() to properly
// clean up the lwIP netif, DHCP client, and internal Arduino state.
WiFi.disconnect();
// Use cyw43_wifi_leave() directly instead of WiFi.disconnect().
// WiFi.disconnect() sets _wifiHWInitted=false in the Arduino framework. beginAP()
// uses _wifiHWInitted to determine AP+STA vs AP-only mode — with it false,
// beginAP() enters AP-only mode (IP 192.168.42.1) instead of AP_STA mode
// (IP 192.168.4.1). In AP-only mode, _beginInternal() redirects all subsequent
// STA connect attempts to beginAP(), creating an infinite loop.
cyw43_wifi_leave(&cyw43_state, CYW43_ITF_STA);
return true;
}
@@ -251,14 +271,21 @@ const char *WiFiComponent::wifi_ssid_to(std::span<char, SSID_BUFFER_SIZE> buffer
buffer[len] = '\0';
return buffer.data();
}
int8_t WiFiComponent::wifi_rssi() { return WiFi.status() == WL_CONNECTED ? WiFi.RSSI() : WIFI_RSSI_DISCONNECTED; }
int8_t WiFiComponent::wifi_rssi() { return this->is_connected_() ? WiFi.RSSI() : WIFI_RSSI_DISCONNECTED; }
int32_t WiFiComponent::get_wifi_channel() { return WiFi.channel(); }
network::IPAddresses WiFiComponent::wifi_sta_ip_addresses() {
network::IPAddresses addresses;
uint8_t index = 0;
// Filter out AP interface addresses — addrList includes all lwIP netifs.
// The AP netif IP lingers even after the AP radio is disabled.
IPAddress ap_ip = WiFi.softAPIP();
for (auto addr : addrList) {
addresses[index++] = addr.ipFromNetifNum();
IPAddress ip(addr.ipFromNetifNum());
if (ip == ap_ip) {
continue;
}
addresses[index++] = ip;
}
return addresses;
}
@@ -288,9 +315,7 @@ void WiFiComponent::wifi_loop_() {
// Poll for connection state changes
// The arduino-pico WiFi library doesn't have event callbacks like ESP8266/ESP32,
// so we need to poll the link status to detect state changes.
// Use WiFi.connected() which checks both the WiFi link and IP address via the
// Arduino framework's own netif (not the SDK's uninitialized one).
bool is_connected = WiFi.connected();
bool is_connected = wifi_sta_connected();
// Detect connection state change
if (is_connected && !s_sta_was_connected) {
+1 -1
View File
@@ -534,7 +534,7 @@ uint32_t WarnIfComponentBlockingGuard::finish() {
// 1ms granularity, so results were essentially random noise.
if (global_runtime_stats != nullptr) {
uint32_t duration_us = micros() - this->started_us_;
global_runtime_stats->record_component_time(this->component_, duration_us, curr_time);
global_runtime_stats->record_component_time(this->component_, duration_us);
}
#endif
if (blocking_time > WARN_IF_BLOCKING_OVER_MS) {
+1 -1
View File
@@ -196,7 +196,7 @@ board_build.filesystem_size = 0.5m
platform = https://github.com/maxgerhardt/platform-raspberrypi.git#v1.4.0-gcc14-arduinopico460
platform_packages =
; earlephilhower/framework-arduinopico@~1.20602.0 ; Cannot use the platformio package until old releases stop getting deleted
earlephilhower/framework-arduinopico@https://github.com/earlephilhower/arduino-pico/releases/download/5.5.0/rp2040-5.5.0.zip
earlephilhower/framework-arduinopico@https://github.com/earlephilhower/arduino-pico/releases/download/5.5.1/rp2040-5.5.1.zip
framework = arduino
lib_deps =