mirror of
https://github.com/esphome/esphome.git
synced 2026-02-11 10:12:38 +00:00
Compare commits
29 Commits
api-server
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
38bba3f5a2 | ||
|
|
225c13326a | ||
|
|
5281fd3273 | ||
|
|
e3bafc1b45 | ||
|
|
42bc0994f1 | ||
|
|
58659e4893 | ||
|
|
b4707344d3 | ||
|
|
548b7e5dab | ||
|
|
b9c2be8228 | ||
|
|
fb2f0ce62f | ||
|
|
d152438335 | ||
|
|
868a2151e3 | ||
|
|
c65d3a0072 | ||
|
|
e2fad9a6c9 | ||
|
|
5365faa877 | ||
|
|
86feb4e27a | ||
|
|
2a6d9d6325 | ||
|
|
727bb27611 | ||
|
|
c03abcdb86 | ||
|
|
a99f75ca71 | ||
|
|
4168e8c30d | ||
|
|
1a6c67f92e | ||
|
|
1f761902b6 | ||
|
|
0b047c334d | ||
|
|
a5dc4b0fce | ||
|
|
c1455ccc29 | ||
|
|
438a0c4289 | ||
|
|
9eee4c9924 | ||
|
|
eea7e9edff |
@@ -1 +1 @@
|
|||||||
8dc4dae0acfa22f26c7cde87fc24e60b27f29a73300e02189b78f0315e5d0695
|
74867fc82764102ce1275ea2bc43e3aeee7619679537c6db61114a33342bb4c7
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ RUN if command -v apk > /dev/null; then \
|
|||||||
|
|
||||||
ENV PIP_DISABLE_PIP_VERSION_CHECK=1
|
ENV PIP_DISABLE_PIP_VERSION_CHECK=1
|
||||||
|
|
||||||
RUN pip install --no-cache-dir -U pip uv==0.6.14
|
RUN pip install --no-cache-dir -U pip uv==0.10.1
|
||||||
|
|
||||||
COPY requirements.txt /
|
COPY requirements.txt /
|
||||||
|
|
||||||
|
|||||||
@@ -219,35 +219,8 @@ void APIConnection::loop() {
|
|||||||
this->process_batch_();
|
this->process_batch_();
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (this->active_iterator_) {
|
if (this->active_iterator_ != ActiveIterator::NONE) {
|
||||||
case ActiveIterator::LIST_ENTITIES:
|
this->process_active_iterator_();
|
||||||
if (this->iterator_storage_.list_entities.completed()) {
|
|
||||||
this->destroy_active_iterator_();
|
|
||||||
if (this->flags_.state_subscription) {
|
|
||||||
this->begin_iterator_(ActiveIterator::INITIAL_STATE);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this->process_iterator_batch_(this->iterator_storage_.list_entities);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case ActiveIterator::INITIAL_STATE:
|
|
||||||
if (this->iterator_storage_.initial_state.completed()) {
|
|
||||||
this->destroy_active_iterator_();
|
|
||||||
// Process any remaining batched messages immediately
|
|
||||||
if (!this->deferred_batch_.empty()) {
|
|
||||||
this->process_batch_();
|
|
||||||
}
|
|
||||||
// Now that everything is sent, enable immediate sending for future state changes
|
|
||||||
this->flags_.should_try_send_immediately = true;
|
|
||||||
// Release excess memory from buffers that grew during initial sync
|
|
||||||
this->deferred_batch_.release_buffer();
|
|
||||||
this->helper_->release_buffers();
|
|
||||||
} else {
|
|
||||||
this->process_iterator_batch_(this->iterator_storage_.initial_state);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case ActiveIterator::NONE:
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this->flags_.sent_ping) {
|
if (this->flags_.sent_ping) {
|
||||||
@@ -283,6 +256,49 @@ void APIConnection::loop() {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void APIConnection::process_active_iterator_() {
|
||||||
|
// Caller ensures active_iterator_ != NONE
|
||||||
|
if (this->active_iterator_ == ActiveIterator::LIST_ENTITIES) {
|
||||||
|
if (this->iterator_storage_.list_entities.completed()) {
|
||||||
|
this->destroy_active_iterator_();
|
||||||
|
if (this->flags_.state_subscription) {
|
||||||
|
this->begin_iterator_(ActiveIterator::INITIAL_STATE);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this->process_iterator_batch_(this->iterator_storage_.list_entities);
|
||||||
|
}
|
||||||
|
} else { // INITIAL_STATE
|
||||||
|
if (this->iterator_storage_.initial_state.completed()) {
|
||||||
|
this->destroy_active_iterator_();
|
||||||
|
// Process any remaining batched messages immediately
|
||||||
|
if (!this->deferred_batch_.empty()) {
|
||||||
|
this->process_batch_();
|
||||||
|
}
|
||||||
|
// Now that everything is sent, enable immediate sending for future state changes
|
||||||
|
this->flags_.should_try_send_immediately = true;
|
||||||
|
// Release excess memory from buffers that grew during initial sync
|
||||||
|
this->deferred_batch_.release_buffer();
|
||||||
|
this->helper_->release_buffers();
|
||||||
|
} else {
|
||||||
|
this->process_iterator_batch_(this->iterator_storage_.initial_state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void APIConnection::process_iterator_batch_(ComponentIterator &iterator) {
|
||||||
|
size_t initial_size = this->deferred_batch_.size();
|
||||||
|
size_t max_batch = this->get_max_batch_size_();
|
||||||
|
while (!iterator.completed() && (this->deferred_batch_.size() - initial_size) < max_batch) {
|
||||||
|
iterator.advance();
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the batch is full, process it immediately
|
||||||
|
// Note: iterator.advance() already calls schedule_batch_() via schedule_message_()
|
||||||
|
if (this->deferred_batch_.size() >= max_batch) {
|
||||||
|
this->process_batch_();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool APIConnection::send_disconnect_response_() {
|
bool APIConnection::send_disconnect_response_() {
|
||||||
// remote initiated disconnect_client
|
// remote initiated disconnect_client
|
||||||
// don't close yet, we still need to send the disconnect response
|
// don't close yet, we still need to send the disconnect response
|
||||||
|
|||||||
@@ -15,6 +15,10 @@
|
|||||||
#include <limits>
|
#include <limits>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
class ComponentIterator;
|
||||||
|
} // namespace esphome
|
||||||
|
|
||||||
namespace esphome::api {
|
namespace esphome::api {
|
||||||
|
|
||||||
// Keepalive timeout in milliseconds
|
// Keepalive timeout in milliseconds
|
||||||
@@ -366,20 +370,13 @@ class APIConnection final : public APIServerConnectionBase {
|
|||||||
return this->client_supports_api_version(1, 14) ? MAX_INITIAL_PER_BATCH : MAX_INITIAL_PER_BATCH_LEGACY;
|
return this->client_supports_api_version(1, 14) ? MAX_INITIAL_PER_BATCH : MAX_INITIAL_PER_BATCH_LEGACY;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper method to process multiple entities from an iterator in a batch
|
// Process active iterator (list_entities/initial_state) during connection setup.
|
||||||
template<typename Iterator> void process_iterator_batch_(Iterator &iterator) {
|
// Extracted from loop() — only runs during initial handshake, NONE in steady state.
|
||||||
size_t initial_size = this->deferred_batch_.size();
|
void __attribute__((noinline)) process_active_iterator_();
|
||||||
size_t max_batch = this->get_max_batch_size_();
|
|
||||||
while (!iterator.completed() && (this->deferred_batch_.size() - initial_size) < max_batch) {
|
|
||||||
iterator.advance();
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the batch is full, process it immediately
|
// Helper method to process multiple entities from an iterator in a batch.
|
||||||
// Note: iterator.advance() already calls schedule_batch_() via schedule_message_()
|
// Takes ComponentIterator base class reference to avoid duplicate template instantiations.
|
||||||
if (this->deferred_batch_.size() >= max_batch) {
|
void process_iterator_batch_(ComponentIterator &iterator);
|
||||||
this->process_batch_();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef USE_BINARY_SENSOR
|
#ifdef USE_BINARY_SENSOR
|
||||||
static uint16_t try_send_binary_sensor_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size);
|
static uint16_t try_send_binary_sensor_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size);
|
||||||
|
|||||||
@@ -94,7 +94,6 @@ class ListEntitiesIterator : public ComponentIterator {
|
|||||||
bool on_update(update::UpdateEntity *entity) override;
|
bool on_update(update::UpdateEntity *entity) override;
|
||||||
#endif
|
#endif
|
||||||
bool on_end() override;
|
bool on_end() override;
|
||||||
bool completed() { return this->state_ == IteratorState::NONE; }
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
APIConnection *client_;
|
APIConnection *client_;
|
||||||
|
|||||||
@@ -88,7 +88,6 @@ class InitialStateIterator : public ComponentIterator {
|
|||||||
#ifdef USE_UPDATE
|
#ifdef USE_UPDATE
|
||||||
bool on_update(update::UpdateEntity *entity) override;
|
bool on_update(update::UpdateEntity *entity) override;
|
||||||
#endif
|
#endif
|
||||||
bool completed() { return this->state_ == IteratorState::NONE; }
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
APIConnection *client_;
|
APIConnection *client_;
|
||||||
|
|||||||
@@ -159,6 +159,10 @@ BK72XX_BOARD_PINS = {
|
|||||||
"A0": 23,
|
"A0": 23,
|
||||||
},
|
},
|
||||||
"cbu": {
|
"cbu": {
|
||||||
|
"SPI0_CS": 15,
|
||||||
|
"SPI0_MISO": 17,
|
||||||
|
"SPI0_MOSI": 16,
|
||||||
|
"SPI0_SCK": 14,
|
||||||
"WIRE1_SCL": 20,
|
"WIRE1_SCL": 20,
|
||||||
"WIRE1_SDA": 21,
|
"WIRE1_SDA": 21,
|
||||||
"WIRE2_SCL": 0,
|
"WIRE2_SCL": 0,
|
||||||
@@ -227,6 +231,10 @@ BK72XX_BOARD_PINS = {
|
|||||||
"A0": 23,
|
"A0": 23,
|
||||||
},
|
},
|
||||||
"generic-bk7231t-qfn32-tuya": {
|
"generic-bk7231t-qfn32-tuya": {
|
||||||
|
"SPI0_CS": 15,
|
||||||
|
"SPI0_MISO": 17,
|
||||||
|
"SPI0_MOSI": 16,
|
||||||
|
"SPI0_SCK": 14,
|
||||||
"WIRE1_SCL": 20,
|
"WIRE1_SCL": 20,
|
||||||
"WIRE1_SDA": 21,
|
"WIRE1_SDA": 21,
|
||||||
"WIRE2_SCL": 0,
|
"WIRE2_SCL": 0,
|
||||||
@@ -295,6 +303,10 @@ BK72XX_BOARD_PINS = {
|
|||||||
"A0": 23,
|
"A0": 23,
|
||||||
},
|
},
|
||||||
"generic-bk7231n-qfn32-tuya": {
|
"generic-bk7231n-qfn32-tuya": {
|
||||||
|
"SPI0_CS": 15,
|
||||||
|
"SPI0_MISO": 17,
|
||||||
|
"SPI0_MOSI": 16,
|
||||||
|
"SPI0_SCK": 14,
|
||||||
"WIRE1_SCL": 20,
|
"WIRE1_SCL": 20,
|
||||||
"WIRE1_SDA": 21,
|
"WIRE1_SDA": 21,
|
||||||
"WIRE2_SCL": 0,
|
"WIRE2_SCL": 0,
|
||||||
@@ -485,8 +497,7 @@ BK72XX_BOARD_PINS = {
|
|||||||
},
|
},
|
||||||
"cb3s": {
|
"cb3s": {
|
||||||
"WIRE1_SCL": 20,
|
"WIRE1_SCL": 20,
|
||||||
"WIRE1_SDA_0": 21,
|
"WIRE1_SDA": 21,
|
||||||
"WIRE1_SDA_1": 21,
|
|
||||||
"SERIAL1_RX": 10,
|
"SERIAL1_RX": 10,
|
||||||
"SERIAL1_TX": 11,
|
"SERIAL1_TX": 11,
|
||||||
"SERIAL2_TX": 0,
|
"SERIAL2_TX": 0,
|
||||||
@@ -647,6 +658,10 @@ BK72XX_BOARD_PINS = {
|
|||||||
"A0": 23,
|
"A0": 23,
|
||||||
},
|
},
|
||||||
"generic-bk7252": {
|
"generic-bk7252": {
|
||||||
|
"SPI0_CS": 15,
|
||||||
|
"SPI0_MISO": 17,
|
||||||
|
"SPI0_MOSI": 16,
|
||||||
|
"SPI0_SCK": 14,
|
||||||
"WIRE1_SCL": 20,
|
"WIRE1_SCL": 20,
|
||||||
"WIRE1_SDA": 21,
|
"WIRE1_SDA": 21,
|
||||||
"WIRE2_SCL": 0,
|
"WIRE2_SCL": 0,
|
||||||
@@ -1096,6 +1111,10 @@ BK72XX_BOARD_PINS = {
|
|||||||
"A0": 23,
|
"A0": 23,
|
||||||
},
|
},
|
||||||
"cb3se": {
|
"cb3se": {
|
||||||
|
"SPI0_CS": 15,
|
||||||
|
"SPI0_MISO": 17,
|
||||||
|
"SPI0_MOSI": 16,
|
||||||
|
"SPI0_SCK": 14,
|
||||||
"WIRE2_SCL": 0,
|
"WIRE2_SCL": 0,
|
||||||
"WIRE2_SDA": 1,
|
"WIRE2_SDA": 1,
|
||||||
"SERIAL1_RX": 10,
|
"SERIAL1_RX": 10,
|
||||||
|
|||||||
@@ -6,8 +6,9 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "bmp3xx_base.h"
|
#include "bmp3xx_base.h"
|
||||||
#include "esphome/core/log.h"
|
|
||||||
#include "esphome/core/hal.h"
|
#include "esphome/core/hal.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
#include "esphome/core/progmem.h"
|
||||||
#include <cinttypes>
|
#include <cinttypes>
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
@@ -26,46 +27,18 @@ static const LogString *chip_type_to_str(uint8_t chip_type) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Oversampling strings indexed by Oversampling enum (0-5): NONE, X2, X4, X8, X16, X32
|
||||||
|
PROGMEM_STRING_TABLE(OversamplingStrings, "None", "2x", "4x", "8x", "16x", "32x", "");
|
||||||
|
|
||||||
static const LogString *oversampling_to_str(Oversampling oversampling) {
|
static const LogString *oversampling_to_str(Oversampling oversampling) {
|
||||||
switch (oversampling) {
|
return OversamplingStrings::get_log_str(static_cast<uint8_t>(oversampling), OversamplingStrings::LAST_INDEX);
|
||||||
case Oversampling::OVERSAMPLING_NONE:
|
|
||||||
return LOG_STR("None");
|
|
||||||
case Oversampling::OVERSAMPLING_X2:
|
|
||||||
return LOG_STR("2x");
|
|
||||||
case Oversampling::OVERSAMPLING_X4:
|
|
||||||
return LOG_STR("4x");
|
|
||||||
case Oversampling::OVERSAMPLING_X8:
|
|
||||||
return LOG_STR("8x");
|
|
||||||
case Oversampling::OVERSAMPLING_X16:
|
|
||||||
return LOG_STR("16x");
|
|
||||||
case Oversampling::OVERSAMPLING_X32:
|
|
||||||
return LOG_STR("32x");
|
|
||||||
default:
|
|
||||||
return LOG_STR("");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IIR filter strings indexed by IIRFilter enum (0-7): OFF, 2, 4, 8, 16, 32, 64, 128
|
||||||
|
PROGMEM_STRING_TABLE(IIRFilterStrings, "OFF", "2x", "4x", "8x", "16x", "32x", "64x", "128x", "");
|
||||||
|
|
||||||
static const LogString *iir_filter_to_str(IIRFilter filter) {
|
static const LogString *iir_filter_to_str(IIRFilter filter) {
|
||||||
switch (filter) {
|
return IIRFilterStrings::get_log_str(static_cast<uint8_t>(filter), IIRFilterStrings::LAST_INDEX);
|
||||||
case IIRFilter::IIR_FILTER_OFF:
|
|
||||||
return LOG_STR("OFF");
|
|
||||||
case IIRFilter::IIR_FILTER_2:
|
|
||||||
return LOG_STR("2x");
|
|
||||||
case IIRFilter::IIR_FILTER_4:
|
|
||||||
return LOG_STR("4x");
|
|
||||||
case IIRFilter::IIR_FILTER_8:
|
|
||||||
return LOG_STR("8x");
|
|
||||||
case IIRFilter::IIR_FILTER_16:
|
|
||||||
return LOG_STR("16x");
|
|
||||||
case IIRFilter::IIR_FILTER_32:
|
|
||||||
return LOG_STR("32x");
|
|
||||||
case IIRFilter::IIR_FILTER_64:
|
|
||||||
return LOG_STR("64x");
|
|
||||||
case IIRFilter::IIR_FILTER_128:
|
|
||||||
return LOG_STR("128x");
|
|
||||||
default:
|
|
||||||
return LOG_STR("");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void BMP3XXComponent::setup() {
|
void BMP3XXComponent::setup() {
|
||||||
|
|||||||
@@ -11,57 +11,26 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "bmp581_base.h"
|
#include "bmp581_base.h"
|
||||||
#include "esphome/core/log.h"
|
|
||||||
#include "esphome/core/hal.h"
|
#include "esphome/core/hal.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
#include "esphome/core/progmem.h"
|
||||||
|
|
||||||
namespace esphome::bmp581_base {
|
namespace esphome::bmp581_base {
|
||||||
|
|
||||||
static const char *const TAG = "bmp581";
|
static const char *const TAG = "bmp581";
|
||||||
|
|
||||||
|
// Oversampling strings indexed by Oversampling enum (0-7): NONE, X2, X4, X8, X16, X32, X64, X128
|
||||||
|
PROGMEM_STRING_TABLE(OversamplingStrings, "None", "2x", "4x", "8x", "16x", "32x", "64x", "128x", "");
|
||||||
|
|
||||||
static const LogString *oversampling_to_str(Oversampling oversampling) {
|
static const LogString *oversampling_to_str(Oversampling oversampling) {
|
||||||
switch (oversampling) {
|
return OversamplingStrings::get_log_str(static_cast<uint8_t>(oversampling), OversamplingStrings::LAST_INDEX);
|
||||||
case Oversampling::OVERSAMPLING_NONE:
|
|
||||||
return LOG_STR("None");
|
|
||||||
case Oversampling::OVERSAMPLING_X2:
|
|
||||||
return LOG_STR("2x");
|
|
||||||
case Oversampling::OVERSAMPLING_X4:
|
|
||||||
return LOG_STR("4x");
|
|
||||||
case Oversampling::OVERSAMPLING_X8:
|
|
||||||
return LOG_STR("8x");
|
|
||||||
case Oversampling::OVERSAMPLING_X16:
|
|
||||||
return LOG_STR("16x");
|
|
||||||
case Oversampling::OVERSAMPLING_X32:
|
|
||||||
return LOG_STR("32x");
|
|
||||||
case Oversampling::OVERSAMPLING_X64:
|
|
||||||
return LOG_STR("64x");
|
|
||||||
case Oversampling::OVERSAMPLING_X128:
|
|
||||||
return LOG_STR("128x");
|
|
||||||
default:
|
|
||||||
return LOG_STR("");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IIR filter strings indexed by IIRFilter enum (0-7): OFF, 2, 4, 8, 16, 32, 64, 128
|
||||||
|
PROGMEM_STRING_TABLE(IIRFilterStrings, "OFF", "2x", "4x", "8x", "16x", "32x", "64x", "128x", "");
|
||||||
|
|
||||||
static const LogString *iir_filter_to_str(IIRFilter filter) {
|
static const LogString *iir_filter_to_str(IIRFilter filter) {
|
||||||
switch (filter) {
|
return IIRFilterStrings::get_log_str(static_cast<uint8_t>(filter), IIRFilterStrings::LAST_INDEX);
|
||||||
case IIRFilter::IIR_FILTER_OFF:
|
|
||||||
return LOG_STR("OFF");
|
|
||||||
case IIRFilter::IIR_FILTER_2:
|
|
||||||
return LOG_STR("2x");
|
|
||||||
case IIRFilter::IIR_FILTER_4:
|
|
||||||
return LOG_STR("4x");
|
|
||||||
case IIRFilter::IIR_FILTER_8:
|
|
||||||
return LOG_STR("8x");
|
|
||||||
case IIRFilter::IIR_FILTER_16:
|
|
||||||
return LOG_STR("16x");
|
|
||||||
case IIRFilter::IIR_FILTER_32:
|
|
||||||
return LOG_STR("32x");
|
|
||||||
case IIRFilter::IIR_FILTER_64:
|
|
||||||
return LOG_STR("64x");
|
|
||||||
case IIRFilter::IIR_FILTER_128:
|
|
||||||
return LOG_STR("128x");
|
|
||||||
default:
|
|
||||||
return LOG_STR("");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void BMP581Component::dump_config() {
|
void BMP581Component::dump_config() {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#include "debug_component.h"
|
#include "debug_component.h"
|
||||||
#ifdef USE_ESP8266
|
#ifdef USE_ESP8266
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
#include "esphome/core/progmem.h"
|
||||||
#include <Esp.h>
|
#include <Esp.h>
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
@@ -19,27 +20,38 @@ namespace debug {
|
|||||||
|
|
||||||
static const char *const TAG = "debug";
|
static const char *const TAG = "debug";
|
||||||
|
|
||||||
|
// PROGMEM string table for reset reasons, indexed by reason code (0-6), with "Unknown" as fallback
|
||||||
|
// clang-format off
|
||||||
|
PROGMEM_STRING_TABLE(ResetReasonStrings,
|
||||||
|
"Power On", // 0 = REASON_DEFAULT_RST
|
||||||
|
"Hardware Watchdog", // 1 = REASON_WDT_RST
|
||||||
|
"Exception", // 2 = REASON_EXCEPTION_RST
|
||||||
|
"Software Watchdog", // 3 = REASON_SOFT_WDT_RST
|
||||||
|
"Software/System restart", // 4 = REASON_SOFT_RESTART
|
||||||
|
"Deep-Sleep Wake", // 5 = REASON_DEEP_SLEEP_AWAKE
|
||||||
|
"External System", // 6 = REASON_EXT_SYS_RST
|
||||||
|
"Unknown" // 7 = fallback
|
||||||
|
);
|
||||||
|
// clang-format on
|
||||||
|
static_assert(REASON_DEFAULT_RST == 0, "Reset reason enum values must match table indices");
|
||||||
|
static_assert(REASON_WDT_RST == 1, "Reset reason enum values must match table indices");
|
||||||
|
static_assert(REASON_EXCEPTION_RST == 2, "Reset reason enum values must match table indices");
|
||||||
|
static_assert(REASON_SOFT_WDT_RST == 3, "Reset reason enum values must match table indices");
|
||||||
|
static_assert(REASON_SOFT_RESTART == 4, "Reset reason enum values must match table indices");
|
||||||
|
static_assert(REASON_DEEP_SLEEP_AWAKE == 5, "Reset reason enum values must match table indices");
|
||||||
|
static_assert(REASON_EXT_SYS_RST == 6, "Reset reason enum values must match table indices");
|
||||||
|
|
||||||
|
// PROGMEM string table for flash chip modes, indexed by mode code (0-3), with "UNKNOWN" as fallback
|
||||||
|
PROGMEM_STRING_TABLE(FlashModeStrings, "QIO", "QOUT", "DIO", "DOUT", "UNKNOWN");
|
||||||
|
static_assert(FM_QIO == 0, "Flash mode enum values must match table indices");
|
||||||
|
static_assert(FM_QOUT == 1, "Flash mode enum values must match table indices");
|
||||||
|
static_assert(FM_DIO == 2, "Flash mode enum values must match table indices");
|
||||||
|
static_assert(FM_DOUT == 3, "Flash mode enum values must match table indices");
|
||||||
|
|
||||||
// Get reset reason string from reason code (no heap allocation)
|
// Get reset reason string from reason code (no heap allocation)
|
||||||
// Returns LogString* pointing to flash (PROGMEM) on ESP8266
|
// Returns LogString* pointing to flash (PROGMEM) on ESP8266
|
||||||
static const LogString *get_reset_reason_str(uint32_t reason) {
|
static const LogString *get_reset_reason_str(uint32_t reason) {
|
||||||
switch (reason) {
|
return ResetReasonStrings::get_log_str(static_cast<uint8_t>(reason), ResetReasonStrings::LAST_INDEX);
|
||||||
case REASON_DEFAULT_RST:
|
|
||||||
return LOG_STR("Power On");
|
|
||||||
case REASON_WDT_RST:
|
|
||||||
return LOG_STR("Hardware Watchdog");
|
|
||||||
case REASON_EXCEPTION_RST:
|
|
||||||
return LOG_STR("Exception");
|
|
||||||
case REASON_SOFT_WDT_RST:
|
|
||||||
return LOG_STR("Software Watchdog");
|
|
||||||
case REASON_SOFT_RESTART:
|
|
||||||
return LOG_STR("Software/System restart");
|
|
||||||
case REASON_DEEP_SLEEP_AWAKE:
|
|
||||||
return LOG_STR("Deep-Sleep Wake");
|
|
||||||
case REASON_EXT_SYS_RST:
|
|
||||||
return LOG_STR("External System");
|
|
||||||
default:
|
|
||||||
return LOG_STR("Unknown");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Size for core version hex buffer
|
// Size for core version hex buffer
|
||||||
@@ -92,23 +104,9 @@ size_t DebugComponent::get_device_info_(std::span<char, DEVICE_INFO_BUFFER_SIZE>
|
|||||||
constexpr size_t size = DEVICE_INFO_BUFFER_SIZE;
|
constexpr size_t size = DEVICE_INFO_BUFFER_SIZE;
|
||||||
char *buf = buffer.data();
|
char *buf = buffer.data();
|
||||||
|
|
||||||
const LogString *flash_mode;
|
const LogString *flash_mode = FlashModeStrings::get_log_str(
|
||||||
switch (ESP.getFlashChipMode()) { // NOLINT(readability-static-accessed-through-instance)
|
static_cast<uint8_t>(ESP.getFlashChipMode()), // NOLINT(readability-static-accessed-through-instance)
|
||||||
case FM_QIO:
|
FlashModeStrings::LAST_INDEX);
|
||||||
flash_mode = LOG_STR("QIO");
|
|
||||||
break;
|
|
||||||
case FM_QOUT:
|
|
||||||
flash_mode = LOG_STR("QOUT");
|
|
||||||
break;
|
|
||||||
case FM_DIO:
|
|
||||||
flash_mode = LOG_STR("DIO");
|
|
||||||
break;
|
|
||||||
case FM_DOUT:
|
|
||||||
flash_mode = LOG_STR("DOUT");
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
flash_mode = LOG_STR("UNKNOWN");
|
|
||||||
}
|
|
||||||
uint32_t flash_size = ESP.getFlashChipSize() / 1024; // NOLINT(readability-static-accessed-through-instance)
|
uint32_t flash_size = ESP.getFlashChipSize() / 1024; // NOLINT(readability-static-accessed-through-instance)
|
||||||
uint32_t flash_speed = ESP.getFlashChipSpeed() / 1000000; // NOLINT(readability-static-accessed-through-instance)
|
uint32_t flash_speed = ESP.getFlashChipSpeed() / 1000000; // NOLINT(readability-static-accessed-through-instance)
|
||||||
ESP_LOGD(TAG, "Flash Chip: Size=%" PRIu32 "kB Speed=%" PRIu32 "MHz Mode=%s", flash_size, flash_speed,
|
ESP_LOGD(TAG, "Flash Chip: Size=%" PRIu32 "kB Speed=%" PRIu32 "MHz Mode=%s", flash_size, flash_speed,
|
||||||
|
|||||||
@@ -1436,10 +1436,6 @@ async def to_code(config):
|
|||||||
CORE.relative_internal_path(".espressif")
|
CORE.relative_internal_path(".espressif")
|
||||||
)
|
)
|
||||||
|
|
||||||
# Set the uv cache inside the data dir so "Clean All" clears it.
|
|
||||||
# Avoids persistent corrupted cache from mid-stream download failures.
|
|
||||||
os.environ["UV_CACHE_DIR"] = str(CORE.relative_internal_path(".uv_cache"))
|
|
||||||
|
|
||||||
if conf[CONF_TYPE] == FRAMEWORK_ESP_IDF:
|
if conf[CONF_TYPE] == FRAMEWORK_ESP_IDF:
|
||||||
cg.add_build_flag("-DUSE_ESP_IDF")
|
cg.add_build_flag("-DUSE_ESP_IDF")
|
||||||
cg.add_build_flag("-DUSE_ESP32_FRAMEWORK_ESP_IDF")
|
cg.add_build_flag("-DUSE_ESP32_FRAMEWORK_ESP_IDF")
|
||||||
|
|||||||
@@ -369,42 +369,9 @@ bool ESP32BLE::ble_dismantle_() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ESP32BLE::loop() {
|
void ESP32BLE::loop() {
|
||||||
switch (this->state_) {
|
if (this->state_ != BLE_COMPONENT_STATE_ACTIVE) {
|
||||||
case BLE_COMPONENT_STATE_OFF:
|
this->loop_handle_state_transition_not_active_();
|
||||||
case BLE_COMPONENT_STATE_DISABLED:
|
return;
|
||||||
return;
|
|
||||||
case BLE_COMPONENT_STATE_DISABLE: {
|
|
||||||
ESP_LOGD(TAG, "Disabling");
|
|
||||||
|
|
||||||
#ifdef ESPHOME_ESP32_BLE_BLE_STATUS_EVENT_HANDLER_COUNT
|
|
||||||
for (auto *ble_event_handler : this->ble_status_event_handlers_) {
|
|
||||||
ble_event_handler->ble_before_disabled_event_handler();
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if (!ble_dismantle_()) {
|
|
||||||
ESP_LOGE(TAG, "Could not be dismantled");
|
|
||||||
this->mark_failed();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this->state_ = BLE_COMPONENT_STATE_DISABLED;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
case BLE_COMPONENT_STATE_ENABLE: {
|
|
||||||
ESP_LOGD(TAG, "Enabling");
|
|
||||||
this->state_ = BLE_COMPONENT_STATE_OFF;
|
|
||||||
|
|
||||||
if (!ble_setup_()) {
|
|
||||||
ESP_LOGE(TAG, "Could not be set up");
|
|
||||||
this->mark_failed();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this->state_ = BLE_COMPONENT_STATE_ACTIVE;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
case BLE_COMPONENT_STATE_ACTIVE:
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
BLEEvent *ble_event = this->ble_events_.pop();
|
BLEEvent *ble_event = this->ble_events_.pop();
|
||||||
@@ -520,6 +487,37 @@ void ESP32BLE::loop() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ESP32BLE::loop_handle_state_transition_not_active_() {
|
||||||
|
// Caller ensures state_ != ACTIVE
|
||||||
|
if (this->state_ == BLE_COMPONENT_STATE_DISABLE) {
|
||||||
|
ESP_LOGD(TAG, "Disabling");
|
||||||
|
|
||||||
|
#ifdef ESPHOME_ESP32_BLE_BLE_STATUS_EVENT_HANDLER_COUNT
|
||||||
|
for (auto *ble_event_handler : this->ble_status_event_handlers_) {
|
||||||
|
ble_event_handler->ble_before_disabled_event_handler();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (!ble_dismantle_()) {
|
||||||
|
ESP_LOGE(TAG, "Could not be dismantled");
|
||||||
|
this->mark_failed();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this->state_ = BLE_COMPONENT_STATE_DISABLED;
|
||||||
|
} else if (this->state_ == BLE_COMPONENT_STATE_ENABLE) {
|
||||||
|
ESP_LOGD(TAG, "Enabling");
|
||||||
|
this->state_ = BLE_COMPONENT_STATE_OFF;
|
||||||
|
|
||||||
|
if (!ble_setup_()) {
|
||||||
|
ESP_LOGE(TAG, "Could not be set up");
|
||||||
|
this->mark_failed();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this->state_ = BLE_COMPONENT_STATE_ACTIVE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Helper function to load new event data based on type
|
// Helper function to load new event data based on type
|
||||||
void load_ble_event(BLEEvent *event, esp_gap_ble_cb_event_t e, esp_ble_gap_cb_param_t *p) {
|
void load_ble_event(BLEEvent *event, esp_gap_ble_cb_event_t e, esp_ble_gap_cb_param_t *p) {
|
||||||
event->load_gap_event(e, p);
|
event->load_gap_event(e, p);
|
||||||
|
|||||||
@@ -155,6 +155,10 @@ class ESP32BLE : public Component {
|
|||||||
#endif
|
#endif
|
||||||
static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param);
|
static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param);
|
||||||
|
|
||||||
|
// Handle DISABLE and ENABLE transitions when not in the ACTIVE state.
|
||||||
|
// Other non-ACTIVE states (e.g. OFF, DISABLED) are currently treated as no-ops.
|
||||||
|
void __attribute__((noinline)) loop_handle_state_transition_not_active_();
|
||||||
|
|
||||||
bool ble_setup_();
|
bool ble_setup_();
|
||||||
bool ble_dismantle_();
|
bool ble_dismantle_();
|
||||||
bool ble_pre_setup_();
|
bool ble_pre_setup_();
|
||||||
|
|||||||
@@ -90,16 +90,14 @@ void HttpRequestUpdate::update_task(void *params) {
|
|||||||
UPDATE_RETURN;
|
UPDATE_RETURN;
|
||||||
}
|
}
|
||||||
size_t read_index = container->get_bytes_read();
|
size_t read_index = container->get_bytes_read();
|
||||||
|
size_t content_length = container->content_length;
|
||||||
|
|
||||||
|
container->end();
|
||||||
|
container.reset(); // Release ownership of the container's shared_ptr
|
||||||
|
|
||||||
bool valid = false;
|
bool valid = false;
|
||||||
{ // Ensures the response string falls out of scope and deallocates before the task ends
|
{ // Scope to ensure JsonDocument is destroyed before deallocating buffer
|
||||||
std::string response((char *) data, read_index);
|
valid = json::parse_json(data, read_index, [this_update](JsonObject root) -> bool {
|
||||||
allocator.deallocate(data, container->content_length);
|
|
||||||
|
|
||||||
container->end();
|
|
||||||
container.reset(); // Release ownership of the container's shared_ptr
|
|
||||||
|
|
||||||
valid = json::parse_json(response, [this_update](JsonObject root) -> bool {
|
|
||||||
if (!root[ESPHOME_F("name")].is<const char *>() || !root[ESPHOME_F("version")].is<const char *>() ||
|
if (!root[ESPHOME_F("name")].is<const char *>() || !root[ESPHOME_F("version")].is<const char *>() ||
|
||||||
!root[ESPHOME_F("builds")].is<JsonArray>()) {
|
!root[ESPHOME_F("builds")].is<JsonArray>()) {
|
||||||
ESP_LOGE(TAG, "Manifest does not contain required fields");
|
ESP_LOGE(TAG, "Manifest does not contain required fields");
|
||||||
@@ -137,6 +135,7 @@ void HttpRequestUpdate::update_task(void *params) {
|
|||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
allocator.deallocate(data, content_length);
|
||||||
|
|
||||||
if (!valid) {
|
if (!valid) {
|
||||||
ESP_LOGE(TAG, "Failed to parse JSON from %s", this_update->source_url_.c_str());
|
ESP_LOGE(TAG, "Failed to parse JSON from %s", this_update->source_url_.c_str());
|
||||||
@@ -157,17 +156,12 @@ void HttpRequestUpdate::update_task(void *params) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
{ // Ensures the current version string falls out of scope and deallocates before the task ends
|
|
||||||
std::string current_version;
|
|
||||||
#ifdef ESPHOME_PROJECT_VERSION
|
#ifdef ESPHOME_PROJECT_VERSION
|
||||||
current_version = ESPHOME_PROJECT_VERSION;
|
this_update->update_info_.current_version = ESPHOME_PROJECT_VERSION;
|
||||||
#else
|
#else
|
||||||
current_version = ESPHOME_VERSION;
|
this_update->update_info_.current_version = ESPHOME_VERSION;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
this_update->update_info_.current_version = current_version;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool trigger_update_available = false;
|
bool trigger_update_available = false;
|
||||||
|
|
||||||
if (this_update->update_info_.latest_version.empty() ||
|
if (this_update->update_info_.latest_version.empty() ||
|
||||||
|
|||||||
@@ -25,8 +25,13 @@ std::string build_json(const json_build_t &f) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool parse_json(const std::string &data, const json_parse_t &f) {
|
bool parse_json(const std::string &data, const json_parse_t &f) {
|
||||||
|
// NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
|
||||||
|
return parse_json(reinterpret_cast<const uint8_t *>(data.c_str()), data.size(), f);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool parse_json(const uint8_t *data, size_t len, const json_parse_t &f) {
|
||||||
// NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
|
// NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
|
||||||
JsonDocument doc = parse_json(reinterpret_cast<const uint8_t *>(data.c_str()), data.size());
|
JsonDocument doc = parse_json(data, len);
|
||||||
if (doc.overflowed() || doc.isNull())
|
if (doc.overflowed() || doc.isNull())
|
||||||
return false;
|
return false;
|
||||||
return f(doc.as<JsonObject>());
|
return f(doc.as<JsonObject>());
|
||||||
|
|||||||
@@ -50,6 +50,8 @@ std::string build_json(const json_build_t &f);
|
|||||||
|
|
||||||
/// Parse a JSON string and run the provided json parse function if it's valid.
|
/// Parse a JSON string and run the provided json parse function if it's valid.
|
||||||
bool parse_json(const std::string &data, const json_parse_t &f);
|
bool parse_json(const std::string &data, const json_parse_t &f);
|
||||||
|
/// Parse JSON from raw bytes and run the provided json parse function if it's valid.
|
||||||
|
bool parse_json(const uint8_t *data, size_t len, const json_parse_t &f);
|
||||||
|
|
||||||
/// Parse a JSON string and return the root JsonDocument (or an unbound object on error)
|
/// Parse a JSON string and return the root JsonDocument (or an unbound object on error)
|
||||||
JsonDocument parse_json(const uint8_t *data, size_t len);
|
JsonDocument parse_json(const uint8_t *data, size_t len);
|
||||||
|
|||||||
@@ -193,14 +193,14 @@ def _notify_old_style(config):
|
|||||||
# The dev and latest branches will be at *least* this version, which is what matters.
|
# The dev and latest branches will be at *least* this version, which is what matters.
|
||||||
# Use GitHub releases directly to avoid PlatformIO moderation delays.
|
# Use GitHub releases directly to avoid PlatformIO moderation delays.
|
||||||
ARDUINO_VERSIONS = {
|
ARDUINO_VERSIONS = {
|
||||||
"dev": (cv.Version(1, 11, 0), "https://github.com/libretiny-eu/libretiny.git"),
|
"dev": (cv.Version(1, 12, 1), "https://github.com/libretiny-eu/libretiny.git"),
|
||||||
"latest": (
|
"latest": (
|
||||||
cv.Version(1, 11, 0),
|
cv.Version(1, 12, 1),
|
||||||
"https://github.com/libretiny-eu/libretiny.git#v1.11.0",
|
"https://github.com/libretiny-eu/libretiny.git#v1.12.1",
|
||||||
),
|
),
|
||||||
"recommended": (
|
"recommended": (
|
||||||
cv.Version(1, 11, 0),
|
cv.Version(1, 12, 1),
|
||||||
"https://github.com/libretiny-eu/libretiny.git#v1.11.0",
|
"https://github.com/libretiny-eu/libretiny.git#v1.12.1",
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -154,28 +154,26 @@ LN882X_BOARD_PINS = {
|
|||||||
"A7": 21,
|
"A7": 21,
|
||||||
},
|
},
|
||||||
"wb02a": {
|
"wb02a": {
|
||||||
"WIRE0_SCL_0": 7,
|
"WIRE0_SCL_0": 1,
|
||||||
"WIRE0_SCL_1": 5,
|
"WIRE0_SCL_1": 2,
|
||||||
"WIRE0_SCL_2": 3,
|
"WIRE0_SCL_2": 3,
|
||||||
"WIRE0_SCL_3": 10,
|
"WIRE0_SCL_3": 4,
|
||||||
"WIRE0_SCL_4": 2,
|
"WIRE0_SCL_4": 5,
|
||||||
"WIRE0_SCL_5": 1,
|
"WIRE0_SCL_5": 7,
|
||||||
"WIRE0_SCL_6": 4,
|
"WIRE0_SCL_6": 9,
|
||||||
"WIRE0_SCL_7": 5,
|
"WIRE0_SCL_7": 10,
|
||||||
"WIRE0_SCL_8": 9,
|
"WIRE0_SCL_8": 24,
|
||||||
"WIRE0_SCL_9": 24,
|
"WIRE0_SCL_9": 25,
|
||||||
"WIRE0_SCL_10": 25,
|
"WIRE0_SDA_0": 1,
|
||||||
"WIRE0_SDA_0": 7,
|
"WIRE0_SDA_1": 2,
|
||||||
"WIRE0_SDA_1": 5,
|
|
||||||
"WIRE0_SDA_2": 3,
|
"WIRE0_SDA_2": 3,
|
||||||
"WIRE0_SDA_3": 10,
|
"WIRE0_SDA_3": 4,
|
||||||
"WIRE0_SDA_4": 2,
|
"WIRE0_SDA_4": 5,
|
||||||
"WIRE0_SDA_5": 1,
|
"WIRE0_SDA_5": 7,
|
||||||
"WIRE0_SDA_6": 4,
|
"WIRE0_SDA_6": 9,
|
||||||
"WIRE0_SDA_7": 5,
|
"WIRE0_SDA_7": 10,
|
||||||
"WIRE0_SDA_8": 9,
|
"WIRE0_SDA_8": 24,
|
||||||
"WIRE0_SDA_9": 24,
|
"WIRE0_SDA_9": 25,
|
||||||
"WIRE0_SDA_10": 25,
|
|
||||||
"SERIAL0_RX": 3,
|
"SERIAL0_RX": 3,
|
||||||
"SERIAL0_TX": 2,
|
"SERIAL0_TX": 2,
|
||||||
"SERIAL1_RX": 24,
|
"SERIAL1_RX": 24,
|
||||||
@@ -221,32 +219,32 @@ LN882X_BOARD_PINS = {
|
|||||||
"A1": 4,
|
"A1": 4,
|
||||||
},
|
},
|
||||||
"wl2s": {
|
"wl2s": {
|
||||||
"WIRE0_SCL_0": 7,
|
"WIRE0_SCL_0": 0,
|
||||||
"WIRE0_SCL_1": 12,
|
"WIRE0_SCL_1": 1,
|
||||||
"WIRE0_SCL_2": 3,
|
"WIRE0_SCL_2": 2,
|
||||||
"WIRE0_SCL_3": 10,
|
"WIRE0_SCL_3": 3,
|
||||||
"WIRE0_SCL_4": 2,
|
"WIRE0_SCL_4": 5,
|
||||||
"WIRE0_SCL_5": 0,
|
"WIRE0_SCL_5": 7,
|
||||||
"WIRE0_SCL_6": 19,
|
"WIRE0_SCL_6": 9,
|
||||||
"WIRE0_SCL_7": 11,
|
"WIRE0_SCL_7": 10,
|
||||||
"WIRE0_SCL_8": 9,
|
"WIRE0_SCL_8": 11,
|
||||||
"WIRE0_SCL_9": 24,
|
"WIRE0_SCL_9": 12,
|
||||||
"WIRE0_SCL_10": 25,
|
"WIRE0_SCL_10": 19,
|
||||||
"WIRE0_SCL_11": 5,
|
"WIRE0_SCL_11": 24,
|
||||||
"WIRE0_SCL_12": 1,
|
"WIRE0_SCL_12": 25,
|
||||||
"WIRE0_SDA_0": 7,
|
"WIRE0_SDA_0": 0,
|
||||||
"WIRE0_SDA_1": 12,
|
"WIRE0_SDA_1": 1,
|
||||||
"WIRE0_SDA_2": 3,
|
"WIRE0_SDA_2": 2,
|
||||||
"WIRE0_SDA_3": 10,
|
"WIRE0_SDA_3": 3,
|
||||||
"WIRE0_SDA_4": 2,
|
"WIRE0_SDA_4": 5,
|
||||||
"WIRE0_SDA_5": 0,
|
"WIRE0_SDA_5": 7,
|
||||||
"WIRE0_SDA_6": 19,
|
"WIRE0_SDA_6": 9,
|
||||||
"WIRE0_SDA_7": 11,
|
"WIRE0_SDA_7": 10,
|
||||||
"WIRE0_SDA_8": 9,
|
"WIRE0_SDA_8": 11,
|
||||||
"WIRE0_SDA_9": 24,
|
"WIRE0_SDA_9": 12,
|
||||||
"WIRE0_SDA_10": 25,
|
"WIRE0_SDA_10": 19,
|
||||||
"WIRE0_SDA_11": 5,
|
"WIRE0_SDA_11": 24,
|
||||||
"WIRE0_SDA_12": 1,
|
"WIRE0_SDA_12": 25,
|
||||||
"SERIAL0_RX": 3,
|
"SERIAL0_RX": 3,
|
||||||
"SERIAL0_TX": 2,
|
"SERIAL0_TX": 2,
|
||||||
"SERIAL1_RX": 24,
|
"SERIAL1_RX": 24,
|
||||||
@@ -301,24 +299,24 @@ LN882X_BOARD_PINS = {
|
|||||||
"A2": 1,
|
"A2": 1,
|
||||||
},
|
},
|
||||||
"ln-02": {
|
"ln-02": {
|
||||||
"WIRE0_SCL_0": 11,
|
"WIRE0_SCL_0": 0,
|
||||||
"WIRE0_SCL_1": 19,
|
"WIRE0_SCL_1": 1,
|
||||||
"WIRE0_SCL_2": 3,
|
"WIRE0_SCL_2": 2,
|
||||||
"WIRE0_SCL_3": 24,
|
"WIRE0_SCL_3": 3,
|
||||||
"WIRE0_SCL_4": 2,
|
"WIRE0_SCL_4": 9,
|
||||||
"WIRE0_SCL_5": 25,
|
"WIRE0_SCL_5": 11,
|
||||||
"WIRE0_SCL_6": 1,
|
"WIRE0_SCL_6": 19,
|
||||||
"WIRE0_SCL_7": 0,
|
"WIRE0_SCL_7": 24,
|
||||||
"WIRE0_SCL_8": 9,
|
"WIRE0_SCL_8": 25,
|
||||||
"WIRE0_SDA_0": 11,
|
"WIRE0_SDA_0": 0,
|
||||||
"WIRE0_SDA_1": 19,
|
"WIRE0_SDA_1": 1,
|
||||||
"WIRE0_SDA_2": 3,
|
"WIRE0_SDA_2": 2,
|
||||||
"WIRE0_SDA_3": 24,
|
"WIRE0_SDA_3": 3,
|
||||||
"WIRE0_SDA_4": 2,
|
"WIRE0_SDA_4": 9,
|
||||||
"WIRE0_SDA_5": 25,
|
"WIRE0_SDA_5": 11,
|
||||||
"WIRE0_SDA_6": 1,
|
"WIRE0_SDA_6": 19,
|
||||||
"WIRE0_SDA_7": 0,
|
"WIRE0_SDA_7": 24,
|
||||||
"WIRE0_SDA_8": 9,
|
"WIRE0_SDA_8": 25,
|
||||||
"SERIAL0_RX": 3,
|
"SERIAL0_RX": 3,
|
||||||
"SERIAL0_TX": 2,
|
"SERIAL0_TX": 2,
|
||||||
"SERIAL1_RX": 24,
|
"SERIAL1_RX": 24,
|
||||||
|
|||||||
@@ -45,9 +45,28 @@ class MDNSComponent : public Component {
|
|||||||
void setup() override;
|
void setup() override;
|
||||||
void dump_config() override;
|
void dump_config() override;
|
||||||
|
|
||||||
#if (defined(USE_ESP8266) || defined(USE_RP2040)) && defined(USE_ARDUINO)
|
// Polling interval for MDNS.update() on platforms that require it (ESP8266, RP2040).
|
||||||
void loop() override;
|
//
|
||||||
#endif
|
// On these platforms, MDNS.update() calls _process(true) which only manages timer-driven
|
||||||
|
// state machines (probe/announce timeouts and service query cache TTLs). Incoming mDNS
|
||||||
|
// packets are handled independently via the lwIP onRx UDP callback and are NOT affected
|
||||||
|
// by how often update() is called.
|
||||||
|
//
|
||||||
|
// The shortest internal timer is the 250ms probe interval (RFC 6762 Section 8.1).
|
||||||
|
// Announcement intervals are 1000ms and cache TTL checks are on the order of seconds
|
||||||
|
// to minutes. A 50ms polling interval provides sufficient resolution for all timers
|
||||||
|
// while completely removing mDNS from the per-iteration loop list.
|
||||||
|
//
|
||||||
|
// In steady state (after the ~8 second boot probe/announce phase completes), update()
|
||||||
|
// checks timers that are set to never expire, making every call pure overhead.
|
||||||
|
//
|
||||||
|
// Tasmota uses a 50ms main loop cycle with mDNS working correctly, confirming this
|
||||||
|
// interval is safe in production.
|
||||||
|
//
|
||||||
|
// By using set_interval() instead of overriding loop(), the component is excluded from
|
||||||
|
// the main loop list via has_overridden_loop(), eliminating all per-iteration overhead
|
||||||
|
// including virtual dispatch.
|
||||||
|
static constexpr uint32_t MDNS_UPDATE_INTERVAL_MS = 50;
|
||||||
float get_setup_priority() const override { return setup_priority::AFTER_CONNECTION; }
|
float get_setup_priority() const override { return setup_priority::AFTER_CONNECTION; }
|
||||||
|
|
||||||
#ifdef USE_MDNS_EXTRA_SERVICES
|
#ifdef USE_MDNS_EXTRA_SERVICES
|
||||||
|
|||||||
@@ -36,9 +36,14 @@ static void register_esp8266(MDNSComponent *, StaticVector<MDNSService, MDNS_SER
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MDNSComponent::setup() { this->setup_buffers_and_register_(register_esp8266); }
|
void MDNSComponent::setup() {
|
||||||
|
this->setup_buffers_and_register_(register_esp8266);
|
||||||
void MDNSComponent::loop() { MDNS.update(); }
|
// Schedule MDNS.update() via set_interval() instead of overriding loop().
|
||||||
|
// This removes the component from the per-iteration loop list entirely,
|
||||||
|
// eliminating virtual dispatch overhead on every main loop cycle.
|
||||||
|
// See MDNS_UPDATE_INTERVAL_MS comment in mdns_component.h for safety analysis.
|
||||||
|
this->set_interval(MDNS_UPDATE_INTERVAL_MS, []() { MDNS.update(); });
|
||||||
|
}
|
||||||
|
|
||||||
void MDNSComponent::on_shutdown() {
|
void MDNSComponent::on_shutdown() {
|
||||||
MDNS.close();
|
MDNS.close();
|
||||||
|
|||||||
@@ -35,9 +35,14 @@ static void register_rp2040(MDNSComponent *, StaticVector<MDNSService, MDNS_SERV
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MDNSComponent::setup() { this->setup_buffers_and_register_(register_rp2040); }
|
void MDNSComponent::setup() {
|
||||||
|
this->setup_buffers_and_register_(register_rp2040);
|
||||||
void MDNSComponent::loop() { MDNS.update(); }
|
// Schedule MDNS.update() via set_interval() instead of overriding loop().
|
||||||
|
// This removes the component from the per-iteration loop list entirely,
|
||||||
|
// eliminating virtual dispatch overhead on every main loop cycle.
|
||||||
|
// See MDNS_UPDATE_INTERVAL_MS comment in mdns_component.h for safety analysis.
|
||||||
|
this->set_interval(MDNS_UPDATE_INTERVAL_MS, []() { MDNS.update(); });
|
||||||
|
}
|
||||||
|
|
||||||
void MDNSComponent::on_shutdown() {
|
void MDNSComponent::on_shutdown() {
|
||||||
MDNS.close();
|
MDNS.close();
|
||||||
|
|||||||
@@ -170,10 +170,8 @@ void MQTTClientComponent::send_device_info_() {
|
|||||||
void MQTTClientComponent::on_log(uint8_t level, const char *tag, const char *message, size_t message_len) {
|
void MQTTClientComponent::on_log(uint8_t level, const char *tag, const char *message, size_t message_len) {
|
||||||
(void) tag;
|
(void) tag;
|
||||||
if (level <= this->log_level_ && this->is_connected()) {
|
if (level <= this->log_level_ && this->is_connected()) {
|
||||||
this->publish({.topic = this->log_message_.topic,
|
this->publish(this->log_message_.topic.c_str(), message, message_len, this->log_message_.qos,
|
||||||
.payload = std::string(message, message_len),
|
this->log_message_.retain);
|
||||||
.qos = this->log_message_.qos,
|
|
||||||
.retain = this->log_message_.retain});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -300,9 +300,11 @@ const EntityBase *MQTTClimateComponent::get_entity() const { return this->device
|
|||||||
|
|
||||||
bool MQTTClimateComponent::publish_state_() {
|
bool MQTTClimateComponent::publish_state_() {
|
||||||
auto traits = this->device_->get_traits();
|
auto traits = this->device_->get_traits();
|
||||||
|
// Reusable stack buffer for topic construction (avoids heap allocation per publish)
|
||||||
|
char topic_buf[MQTT_DEFAULT_TOPIC_MAX_LEN];
|
||||||
// mode
|
// mode
|
||||||
bool success = true;
|
bool success = true;
|
||||||
if (!this->publish(this->get_mode_state_topic(), climate_mode_to_mqtt_str(this->device_->mode)))
|
if (!this->publish(this->get_mode_state_topic_to(topic_buf), climate_mode_to_mqtt_str(this->device_->mode)))
|
||||||
success = false;
|
success = false;
|
||||||
int8_t target_accuracy = traits.get_target_temperature_accuracy_decimals();
|
int8_t target_accuracy = traits.get_target_temperature_accuracy_decimals();
|
||||||
int8_t current_accuracy = traits.get_current_temperature_accuracy_decimals();
|
int8_t current_accuracy = traits.get_current_temperature_accuracy_decimals();
|
||||||
@@ -311,68 +313,70 @@ bool MQTTClimateComponent::publish_state_() {
|
|||||||
if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_CURRENT_TEMPERATURE) &&
|
if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_CURRENT_TEMPERATURE) &&
|
||||||
!std::isnan(this->device_->current_temperature)) {
|
!std::isnan(this->device_->current_temperature)) {
|
||||||
len = value_accuracy_to_buf(payload, this->device_->current_temperature, current_accuracy);
|
len = value_accuracy_to_buf(payload, this->device_->current_temperature, current_accuracy);
|
||||||
if (!this->publish(this->get_current_temperature_state_topic(), payload, len))
|
if (!this->publish(this->get_current_temperature_state_topic_to(topic_buf), payload, len))
|
||||||
success = false;
|
success = false;
|
||||||
}
|
}
|
||||||
if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_TWO_POINT_TARGET_TEMPERATURE |
|
if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_TWO_POINT_TARGET_TEMPERATURE |
|
||||||
climate::CLIMATE_REQUIRES_TWO_POINT_TARGET_TEMPERATURE)) {
|
climate::CLIMATE_REQUIRES_TWO_POINT_TARGET_TEMPERATURE)) {
|
||||||
len = value_accuracy_to_buf(payload, this->device_->target_temperature_low, target_accuracy);
|
len = value_accuracy_to_buf(payload, this->device_->target_temperature_low, target_accuracy);
|
||||||
if (!this->publish(this->get_target_temperature_low_state_topic(), payload, len))
|
if (!this->publish(this->get_target_temperature_low_state_topic_to(topic_buf), payload, len))
|
||||||
success = false;
|
success = false;
|
||||||
len = value_accuracy_to_buf(payload, this->device_->target_temperature_high, target_accuracy);
|
len = value_accuracy_to_buf(payload, this->device_->target_temperature_high, target_accuracy);
|
||||||
if (!this->publish(this->get_target_temperature_high_state_topic(), payload, len))
|
if (!this->publish(this->get_target_temperature_high_state_topic_to(topic_buf), payload, len))
|
||||||
success = false;
|
success = false;
|
||||||
} else {
|
} else {
|
||||||
len = value_accuracy_to_buf(payload, this->device_->target_temperature, target_accuracy);
|
len = value_accuracy_to_buf(payload, this->device_->target_temperature, target_accuracy);
|
||||||
if (!this->publish(this->get_target_temperature_state_topic(), payload, len))
|
if (!this->publish(this->get_target_temperature_state_topic_to(topic_buf), payload, len))
|
||||||
success = false;
|
success = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_CURRENT_HUMIDITY) &&
|
if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_CURRENT_HUMIDITY) &&
|
||||||
!std::isnan(this->device_->current_humidity)) {
|
!std::isnan(this->device_->current_humidity)) {
|
||||||
len = value_accuracy_to_buf(payload, this->device_->current_humidity, 0);
|
len = value_accuracy_to_buf(payload, this->device_->current_humidity, 0);
|
||||||
if (!this->publish(this->get_current_humidity_state_topic(), payload, len))
|
if (!this->publish(this->get_current_humidity_state_topic_to(topic_buf), payload, len))
|
||||||
success = false;
|
success = false;
|
||||||
}
|
}
|
||||||
if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_TARGET_HUMIDITY) &&
|
if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_TARGET_HUMIDITY) &&
|
||||||
!std::isnan(this->device_->target_humidity)) {
|
!std::isnan(this->device_->target_humidity)) {
|
||||||
len = value_accuracy_to_buf(payload, this->device_->target_humidity, 0);
|
len = value_accuracy_to_buf(payload, this->device_->target_humidity, 0);
|
||||||
if (!this->publish(this->get_target_humidity_state_topic(), payload, len))
|
if (!this->publish(this->get_target_humidity_state_topic_to(topic_buf), payload, len))
|
||||||
success = false;
|
success = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (traits.get_supports_presets() || !traits.get_supported_custom_presets().empty()) {
|
if (traits.get_supports_presets() || !traits.get_supported_custom_presets().empty()) {
|
||||||
if (this->device_->has_custom_preset()) {
|
if (this->device_->has_custom_preset()) {
|
||||||
if (!this->publish(this->get_preset_state_topic(), this->device_->get_custom_preset()))
|
if (!this->publish(this->get_preset_state_topic_to(topic_buf), this->device_->get_custom_preset().c_str()))
|
||||||
success = false;
|
success = false;
|
||||||
} else if (this->device_->preset.has_value()) {
|
} else if (this->device_->preset.has_value()) {
|
||||||
if (!this->publish(this->get_preset_state_topic(), climate_preset_to_mqtt_str(this->device_->preset.value())))
|
if (!this->publish(this->get_preset_state_topic_to(topic_buf),
|
||||||
|
climate_preset_to_mqtt_str(this->device_->preset.value())))
|
||||||
success = false;
|
success = false;
|
||||||
} else if (!this->publish(this->get_preset_state_topic(), "")) {
|
} else if (!this->publish(this->get_preset_state_topic_to(topic_buf), "")) {
|
||||||
success = false;
|
success = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_ACTION)) {
|
if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_ACTION)) {
|
||||||
if (!this->publish(this->get_action_state_topic(), climate_action_to_mqtt_str(this->device_->action)))
|
if (!this->publish(this->get_action_state_topic_to(topic_buf), climate_action_to_mqtt_str(this->device_->action)))
|
||||||
success = false;
|
success = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (traits.get_supports_fan_modes()) {
|
if (traits.get_supports_fan_modes()) {
|
||||||
if (this->device_->has_custom_fan_mode()) {
|
if (this->device_->has_custom_fan_mode()) {
|
||||||
if (!this->publish(this->get_fan_mode_state_topic(), this->device_->get_custom_fan_mode()))
|
if (!this->publish(this->get_fan_mode_state_topic_to(topic_buf), this->device_->get_custom_fan_mode().c_str()))
|
||||||
success = false;
|
success = false;
|
||||||
} else if (this->device_->fan_mode.has_value()) {
|
} else if (this->device_->fan_mode.has_value()) {
|
||||||
if (!this->publish(this->get_fan_mode_state_topic(),
|
if (!this->publish(this->get_fan_mode_state_topic_to(topic_buf),
|
||||||
climate_fan_mode_to_mqtt_str(this->device_->fan_mode.value())))
|
climate_fan_mode_to_mqtt_str(this->device_->fan_mode.value())))
|
||||||
success = false;
|
success = false;
|
||||||
} else if (!this->publish(this->get_fan_mode_state_topic(), "")) {
|
} else if (!this->publish(this->get_fan_mode_state_topic_to(topic_buf), "")) {
|
||||||
success = false;
|
success = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (traits.get_supports_swing_modes()) {
|
if (traits.get_supports_swing_modes()) {
|
||||||
if (!this->publish(this->get_swing_mode_state_topic(), climate_swing_mode_to_mqtt_str(this->device_->swing_mode)))
|
if (!this->publish(this->get_swing_mode_state_topic_to(topic_buf),
|
||||||
|
climate_swing_mode_to_mqtt_str(this->device_->swing_mode)))
|
||||||
success = false;
|
success = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -59,6 +59,11 @@ void log_mqtt_component(const char *tag, MQTTComponent *obj, bool state_topic, b
|
|||||||
\
|
\
|
||||||
public: \
|
public: \
|
||||||
void set_custom_##name##_##type##_topic(const std::string &topic) { this->custom_##name##_##type##_topic_ = topic; } \
|
void set_custom_##name##_##type##_topic(const std::string &topic) { this->custom_##name##_##type##_topic_ = topic; } \
|
||||||
|
StringRef get_##name##_##type##_topic_to(std::span<char, MQTT_DEFAULT_TOPIC_MAX_LEN> buf) const { \
|
||||||
|
if (!this->custom_##name##_##type##_topic_.empty()) \
|
||||||
|
return StringRef(this->custom_##name##_##type##_topic_.data(), this->custom_##name##_##type##_topic_.size()); \
|
||||||
|
return this->get_default_topic_for_to_(buf, #name "/" #type, sizeof(#name "/" #type) - 1); \
|
||||||
|
} \
|
||||||
std::string get_##name##_##type##_topic() const { \
|
std::string get_##name##_##type##_topic() const { \
|
||||||
if (this->custom_##name##_##type##_topic_.empty()) \
|
if (this->custom_##name##_##type##_topic_.empty()) \
|
||||||
return this->get_default_topic_for_(#name "/" #type); \
|
return this->get_default_topic_for_(#name "/" #type); \
|
||||||
|
|||||||
@@ -112,19 +112,19 @@ bool MQTTCoverComponent::send_initial_state() { return this->publish_state(); }
|
|||||||
bool MQTTCoverComponent::publish_state() {
|
bool MQTTCoverComponent::publish_state() {
|
||||||
auto traits = this->cover_->get_traits();
|
auto traits = this->cover_->get_traits();
|
||||||
bool success = true;
|
bool success = true;
|
||||||
|
char topic_buf[MQTT_DEFAULT_TOPIC_MAX_LEN];
|
||||||
if (traits.get_supports_position()) {
|
if (traits.get_supports_position()) {
|
||||||
char pos[VALUE_ACCURACY_MAX_LEN];
|
char pos[VALUE_ACCURACY_MAX_LEN];
|
||||||
size_t len = value_accuracy_to_buf(pos, roundf(this->cover_->position * 100), 0);
|
size_t len = value_accuracy_to_buf(pos, roundf(this->cover_->position * 100), 0);
|
||||||
if (!this->publish(this->get_position_state_topic(), pos, len))
|
if (!this->publish(this->get_position_state_topic_to(topic_buf), pos, len))
|
||||||
success = false;
|
success = false;
|
||||||
}
|
}
|
||||||
if (traits.get_supports_tilt()) {
|
if (traits.get_supports_tilt()) {
|
||||||
char pos[VALUE_ACCURACY_MAX_LEN];
|
char pos[VALUE_ACCURACY_MAX_LEN];
|
||||||
size_t len = value_accuracy_to_buf(pos, roundf(this->cover_->tilt * 100), 0);
|
size_t len = value_accuracy_to_buf(pos, roundf(this->cover_->tilt * 100), 0);
|
||||||
if (!this->publish(this->get_tilt_state_topic(), pos, len))
|
if (!this->publish(this->get_tilt_state_topic_to(topic_buf), pos, len))
|
||||||
success = false;
|
success = false;
|
||||||
}
|
}
|
||||||
char topic_buf[MQTT_DEFAULT_TOPIC_MAX_LEN];
|
|
||||||
if (!this->publish(this->get_state_topic_to_(topic_buf),
|
if (!this->publish(this->get_state_topic_to_(topic_buf),
|
||||||
cover_state_to_mqtt_str(this->cover_->current_operation, this->cover_->position,
|
cover_state_to_mqtt_str(this->cover_->current_operation, this->cover_->position,
|
||||||
traits.get_supports_position())))
|
traits.get_supports_position())))
|
||||||
|
|||||||
@@ -173,19 +173,20 @@ bool MQTTFanComponent::publish_state() {
|
|||||||
this->publish(this->get_state_topic_to_(topic_buf), state_s);
|
this->publish(this->get_state_topic_to_(topic_buf), state_s);
|
||||||
bool failed = false;
|
bool failed = false;
|
||||||
if (this->state_->get_traits().supports_direction()) {
|
if (this->state_->get_traits().supports_direction()) {
|
||||||
bool success = this->publish(this->get_direction_state_topic(), fan_direction_to_mqtt_str(this->state_->direction));
|
bool success = this->publish(this->get_direction_state_topic_to(topic_buf),
|
||||||
|
fan_direction_to_mqtt_str(this->state_->direction));
|
||||||
failed = failed || !success;
|
failed = failed || !success;
|
||||||
}
|
}
|
||||||
if (this->state_->get_traits().supports_oscillation()) {
|
if (this->state_->get_traits().supports_oscillation()) {
|
||||||
bool success =
|
bool success = this->publish(this->get_oscillation_state_topic_to(topic_buf),
|
||||||
this->publish(this->get_oscillation_state_topic(), fan_oscillation_to_mqtt_str(this->state_->oscillating));
|
fan_oscillation_to_mqtt_str(this->state_->oscillating));
|
||||||
failed = failed || !success;
|
failed = failed || !success;
|
||||||
}
|
}
|
||||||
auto traits = this->state_->get_traits();
|
auto traits = this->state_->get_traits();
|
||||||
if (traits.supports_speed()) {
|
if (traits.supports_speed()) {
|
||||||
char buf[12];
|
char buf[12];
|
||||||
size_t len = buf_append_printf(buf, sizeof(buf), 0, "%d", this->state_->speed);
|
size_t len = buf_append_printf(buf, sizeof(buf), 0, "%d", this->state_->speed);
|
||||||
bool success = this->publish(this->get_speed_level_state_topic(), buf, len);
|
bool success = this->publish(this->get_speed_level_state_topic_to(topic_buf), buf, len);
|
||||||
failed = failed || !success;
|
failed = failed || !success;
|
||||||
}
|
}
|
||||||
return !failed;
|
return !failed;
|
||||||
|
|||||||
@@ -87,13 +87,13 @@ bool MQTTValveComponent::send_initial_state() { return this->publish_state(); }
|
|||||||
bool MQTTValveComponent::publish_state() {
|
bool MQTTValveComponent::publish_state() {
|
||||||
auto traits = this->valve_->get_traits();
|
auto traits = this->valve_->get_traits();
|
||||||
bool success = true;
|
bool success = true;
|
||||||
|
char topic_buf[MQTT_DEFAULT_TOPIC_MAX_LEN];
|
||||||
if (traits.get_supports_position()) {
|
if (traits.get_supports_position()) {
|
||||||
char pos[VALUE_ACCURACY_MAX_LEN];
|
char pos[VALUE_ACCURACY_MAX_LEN];
|
||||||
size_t len = value_accuracy_to_buf(pos, roundf(this->valve_->position * 100), 0);
|
size_t len = value_accuracy_to_buf(pos, roundf(this->valve_->position * 100), 0);
|
||||||
if (!this->publish(this->get_position_state_topic(), pos, len))
|
if (!this->publish(this->get_position_state_topic_to(topic_buf), pos, len))
|
||||||
success = false;
|
success = false;
|
||||||
}
|
}
|
||||||
char topic_buf[MQTT_DEFAULT_TOPIC_MAX_LEN];
|
|
||||||
if (!this->publish(this->get_state_topic_to_(topic_buf),
|
if (!this->publish(this->get_state_topic_to_(topic_buf),
|
||||||
valve_state_to_mqtt_str(this->valve_->current_operation, this->valve_->position,
|
valve_state_to_mqtt_str(this->valve_->current_operation, this->valve_->position,
|
||||||
traits.get_supports_position())))
|
traits.get_supports_position())))
|
||||||
|
|||||||
@@ -71,6 +71,10 @@ RTL87XX_BOARDS = {
|
|||||||
"name": "WR3L Wi-Fi Module",
|
"name": "WR3L Wi-Fi Module",
|
||||||
"family": FAMILY_RTL8710B,
|
"family": FAMILY_RTL8710B,
|
||||||
},
|
},
|
||||||
|
"wbru": {
|
||||||
|
"name": "WBRU Wi-Fi Module",
|
||||||
|
"family": FAMILY_RTL8720C,
|
||||||
|
},
|
||||||
"wr2le": {
|
"wr2le": {
|
||||||
"name": "WR2LE Wi-Fi Module",
|
"name": "WR2LE Wi-Fi Module",
|
||||||
"family": FAMILY_RTL8710B,
|
"family": FAMILY_RTL8710B,
|
||||||
@@ -83,6 +87,14 @@ RTL87XX_BOARDS = {
|
|||||||
"name": "T103_V1.0",
|
"name": "T103_V1.0",
|
||||||
"family": FAMILY_RTL8710B,
|
"family": FAMILY_RTL8710B,
|
||||||
},
|
},
|
||||||
|
"cr3l": {
|
||||||
|
"name": "CR3L Wi-Fi Module",
|
||||||
|
"family": FAMILY_RTL8720C,
|
||||||
|
},
|
||||||
|
"generic-rtl8720cm-4mb-1712k": {
|
||||||
|
"name": "Generic - RTL8720CM (4M/1712k)",
|
||||||
|
"family": FAMILY_RTL8720C,
|
||||||
|
},
|
||||||
"generic-rtl8720cf-2mb-896k": {
|
"generic-rtl8720cf-2mb-896k": {
|
||||||
"name": "Generic - RTL8720CF (2M/896k)",
|
"name": "Generic - RTL8720CF (2M/896k)",
|
||||||
"family": FAMILY_RTL8720C,
|
"family": FAMILY_RTL8720C,
|
||||||
@@ -103,6 +115,10 @@ RTL87XX_BOARDS = {
|
|||||||
"name": "WR2L Wi-Fi Module",
|
"name": "WR2L Wi-Fi Module",
|
||||||
"family": FAMILY_RTL8710B,
|
"family": FAMILY_RTL8710B,
|
||||||
},
|
},
|
||||||
|
"wbr1": {
|
||||||
|
"name": "WBR1 Wi-Fi Module",
|
||||||
|
"family": FAMILY_RTL8720C,
|
||||||
|
},
|
||||||
"wr1": {
|
"wr1": {
|
||||||
"name": "WR1 Wi-Fi Module",
|
"name": "WR1 Wi-Fi Module",
|
||||||
"family": FAMILY_RTL8710B,
|
"family": FAMILY_RTL8710B,
|
||||||
@@ -119,10 +135,10 @@ RTL87XX_BOARD_PINS = {
|
|||||||
"SPI1_MISO": 22,
|
"SPI1_MISO": 22,
|
||||||
"SPI1_MOSI": 23,
|
"SPI1_MOSI": 23,
|
||||||
"SPI1_SCK": 18,
|
"SPI1_SCK": 18,
|
||||||
"WIRE0_SCL_0": 29,
|
"WIRE0_SCL_0": 22,
|
||||||
"WIRE0_SCL_1": 22,
|
"WIRE0_SCL_1": 29,
|
||||||
"WIRE0_SDA_0": 30,
|
"WIRE0_SDA_0": 19,
|
||||||
"WIRE0_SDA_1": 19,
|
"WIRE0_SDA_1": 30,
|
||||||
"WIRE1_SCL": 18,
|
"WIRE1_SCL": 18,
|
||||||
"WIRE1_SDA": 23,
|
"WIRE1_SDA": 23,
|
||||||
"SERIAL0_CTS": 19,
|
"SERIAL0_CTS": 19,
|
||||||
@@ -230,10 +246,10 @@ RTL87XX_BOARD_PINS = {
|
|||||||
"A1": 41,
|
"A1": 41,
|
||||||
},
|
},
|
||||||
"wbr3": {
|
"wbr3": {
|
||||||
"WIRE0_SCL_0": 11,
|
"WIRE0_SCL_0": 2,
|
||||||
"WIRE0_SCL_1": 2,
|
"WIRE0_SCL_1": 11,
|
||||||
"WIRE0_SCL_2": 19,
|
"WIRE0_SCL_2": 15,
|
||||||
"WIRE0_SCL_3": 15,
|
"WIRE0_SCL_3": 19,
|
||||||
"WIRE0_SDA_0": 3,
|
"WIRE0_SDA_0": 3,
|
||||||
"WIRE0_SDA_1": 12,
|
"WIRE0_SDA_1": 12,
|
||||||
"WIRE0_SDA_2": 16,
|
"WIRE0_SDA_2": 16,
|
||||||
@@ -242,10 +258,10 @@ RTL87XX_BOARD_PINS = {
|
|||||||
"SERIAL0_TX_0": 11,
|
"SERIAL0_TX_0": 11,
|
||||||
"SERIAL0_TX_1": 14,
|
"SERIAL0_TX_1": 14,
|
||||||
"SERIAL1_CTS": 4,
|
"SERIAL1_CTS": 4,
|
||||||
"SERIAL1_RX_0": 2,
|
"SERIAL1_RX_0": 0,
|
||||||
"SERIAL1_RX_1": 0,
|
"SERIAL1_RX_1": 2,
|
||||||
"SERIAL1_TX_0": 3,
|
"SERIAL1_TX_0": 1,
|
||||||
"SERIAL1_TX_1": 1,
|
"SERIAL1_TX_1": 3,
|
||||||
"SERIAL2_CTS": 19,
|
"SERIAL2_CTS": 19,
|
||||||
"SERIAL2_RX": 15,
|
"SERIAL2_RX": 15,
|
||||||
"SERIAL2_TX": 16,
|
"SERIAL2_TX": 16,
|
||||||
@@ -296,6 +312,12 @@ RTL87XX_BOARD_PINS = {
|
|||||||
},
|
},
|
||||||
"generic-rtl8710bn-2mb-468k": {
|
"generic-rtl8710bn-2mb-468k": {
|
||||||
"SPI0_CS": 19,
|
"SPI0_CS": 19,
|
||||||
|
"SPI0_FCS": 6,
|
||||||
|
"SPI0_FD0": 9,
|
||||||
|
"SPI0_FD1": 7,
|
||||||
|
"SPI0_FD2": 8,
|
||||||
|
"SPI0_FD3": 11,
|
||||||
|
"SPI0_FSCK": 10,
|
||||||
"SPI0_MISO": 22,
|
"SPI0_MISO": 22,
|
||||||
"SPI0_MOSI": 23,
|
"SPI0_MOSI": 23,
|
||||||
"SPI0_SCK": 18,
|
"SPI0_SCK": 18,
|
||||||
@@ -396,10 +418,10 @@ RTL87XX_BOARD_PINS = {
|
|||||||
"SPI1_MISO": 22,
|
"SPI1_MISO": 22,
|
||||||
"SPI1_MOSI": 23,
|
"SPI1_MOSI": 23,
|
||||||
"SPI1_SCK": 18,
|
"SPI1_SCK": 18,
|
||||||
"WIRE0_SCL_0": 29,
|
"WIRE0_SCL_0": 22,
|
||||||
"WIRE0_SCL_1": 22,
|
"WIRE0_SCL_1": 29,
|
||||||
"WIRE0_SDA_0": 30,
|
"WIRE0_SDA_0": 19,
|
||||||
"WIRE0_SDA_1": 19,
|
"WIRE0_SDA_1": 30,
|
||||||
"WIRE1_SCL": 18,
|
"WIRE1_SCL": 18,
|
||||||
"WIRE1_SDA": 23,
|
"WIRE1_SDA": 23,
|
||||||
"SERIAL0_CTS": 19,
|
"SERIAL0_CTS": 19,
|
||||||
@@ -463,10 +485,10 @@ RTL87XX_BOARD_PINS = {
|
|||||||
"SPI1_MISO": 22,
|
"SPI1_MISO": 22,
|
||||||
"SPI1_MOSI": 23,
|
"SPI1_MOSI": 23,
|
||||||
"SPI1_SCK": 18,
|
"SPI1_SCK": 18,
|
||||||
"WIRE0_SCL_0": 29,
|
"WIRE0_SCL_0": 22,
|
||||||
"WIRE0_SCL_1": 22,
|
"WIRE0_SCL_1": 29,
|
||||||
"WIRE0_SDA_0": 30,
|
"WIRE0_SDA_0": 19,
|
||||||
"WIRE0_SDA_1": 19,
|
"WIRE0_SDA_1": 30,
|
||||||
"WIRE1_SCL": 18,
|
"WIRE1_SCL": 18,
|
||||||
"WIRE1_SDA": 23,
|
"WIRE1_SDA": 23,
|
||||||
"SERIAL0_CTS": 19,
|
"SERIAL0_CTS": 19,
|
||||||
@@ -714,6 +736,12 @@ RTL87XX_BOARD_PINS = {
|
|||||||
},
|
},
|
||||||
"generic-rtl8710bn-2mb-788k": {
|
"generic-rtl8710bn-2mb-788k": {
|
||||||
"SPI0_CS": 19,
|
"SPI0_CS": 19,
|
||||||
|
"SPI0_FCS": 6,
|
||||||
|
"SPI0_FD0": 9,
|
||||||
|
"SPI0_FD1": 7,
|
||||||
|
"SPI0_FD2": 8,
|
||||||
|
"SPI0_FD3": 11,
|
||||||
|
"SPI0_FSCK": 10,
|
||||||
"SPI0_MISO": 22,
|
"SPI0_MISO": 22,
|
||||||
"SPI0_MOSI": 23,
|
"SPI0_MOSI": 23,
|
||||||
"SPI0_SCK": 18,
|
"SPI0_SCK": 18,
|
||||||
@@ -807,6 +835,12 @@ RTL87XX_BOARD_PINS = {
|
|||||||
},
|
},
|
||||||
"generic-rtl8710bx-4mb-980k": {
|
"generic-rtl8710bx-4mb-980k": {
|
||||||
"SPI0_CS": 19,
|
"SPI0_CS": 19,
|
||||||
|
"SPI0_FCS": 6,
|
||||||
|
"SPI0_FD0": 9,
|
||||||
|
"SPI0_FD1": 7,
|
||||||
|
"SPI0_FD2": 8,
|
||||||
|
"SPI0_FD3": 11,
|
||||||
|
"SPI0_FSCK": 10,
|
||||||
"SPI0_MISO": 22,
|
"SPI0_MISO": 22,
|
||||||
"SPI0_MOSI": 23,
|
"SPI0_MOSI": 23,
|
||||||
"SPI0_SCK": 18,
|
"SPI0_SCK": 18,
|
||||||
@@ -957,8 +991,8 @@ RTL87XX_BOARD_PINS = {
|
|||||||
"SPI1_MISO": 22,
|
"SPI1_MISO": 22,
|
||||||
"SPI1_MOSI": 23,
|
"SPI1_MOSI": 23,
|
||||||
"SPI1_SCK": 18,
|
"SPI1_SCK": 18,
|
||||||
"WIRE0_SCL_0": 29,
|
"WIRE0_SCL_0": 22,
|
||||||
"WIRE0_SCL_1": 22,
|
"WIRE0_SCL_1": 29,
|
||||||
"WIRE0_SDA_0": 19,
|
"WIRE0_SDA_0": 19,
|
||||||
"WIRE0_SDA_1": 30,
|
"WIRE0_SDA_1": 30,
|
||||||
"WIRE1_SCL": 18,
|
"WIRE1_SCL": 18,
|
||||||
@@ -1088,6 +1122,99 @@ RTL87XX_BOARD_PINS = {
|
|||||||
"A0": 19,
|
"A0": 19,
|
||||||
"A1": 41,
|
"A1": 41,
|
||||||
},
|
},
|
||||||
|
"wbru": {
|
||||||
|
"SPI0_CS_0": 2,
|
||||||
|
"SPI0_CS_1": 7,
|
||||||
|
"SPI0_CS_2": 15,
|
||||||
|
"SPI0_MISO_0": 10,
|
||||||
|
"SPI0_MISO_1": 20,
|
||||||
|
"SPI0_MOSI_0": 4,
|
||||||
|
"SPI0_MOSI_1": 9,
|
||||||
|
"SPI0_MOSI_2": 19,
|
||||||
|
"SPI0_SCK_0": 3,
|
||||||
|
"SPI0_SCK_1": 8,
|
||||||
|
"SPI0_SCK_2": 16,
|
||||||
|
"WIRE0_SCL_0": 2,
|
||||||
|
"WIRE0_SCL_1": 11,
|
||||||
|
"WIRE0_SCL_2": 15,
|
||||||
|
"WIRE0_SCL_3": 19,
|
||||||
|
"WIRE0_SDA_0": 3,
|
||||||
|
"WIRE0_SDA_1": 12,
|
||||||
|
"WIRE0_SDA_2": 16,
|
||||||
|
"WIRE0_SDA_3": 20,
|
||||||
|
"SERIAL0_CTS": 10,
|
||||||
|
"SERIAL0_RTS": 9,
|
||||||
|
"SERIAL0_RX_0": 12,
|
||||||
|
"SERIAL0_RX_1": 13,
|
||||||
|
"SERIAL0_TX_0": 11,
|
||||||
|
"SERIAL0_TX_1": 14,
|
||||||
|
"SERIAL1_CTS": 4,
|
||||||
|
"SERIAL1_RX_0": 0,
|
||||||
|
"SERIAL1_RX_1": 2,
|
||||||
|
"SERIAL1_TX": 3,
|
||||||
|
"SERIAL2_CTS": 19,
|
||||||
|
"SERIAL2_RTS": 20,
|
||||||
|
"SERIAL2_RX": 15,
|
||||||
|
"SERIAL2_TX": 16,
|
||||||
|
"CS0": 7,
|
||||||
|
"CTS0": 10,
|
||||||
|
"CTS1": 4,
|
||||||
|
"CTS2": 19,
|
||||||
|
"MOSI0": 19,
|
||||||
|
"PA00": 0,
|
||||||
|
"PA0": 0,
|
||||||
|
"PA02": 2,
|
||||||
|
"PA2": 2,
|
||||||
|
"PA03": 3,
|
||||||
|
"PA3": 3,
|
||||||
|
"PA04": 4,
|
||||||
|
"PA4": 4,
|
||||||
|
"PA07": 7,
|
||||||
|
"PA7": 7,
|
||||||
|
"PA08": 8,
|
||||||
|
"PA8": 8,
|
||||||
|
"PA09": 9,
|
||||||
|
"PA9": 9,
|
||||||
|
"PA10": 10,
|
||||||
|
"PA11": 11,
|
||||||
|
"PA12": 12,
|
||||||
|
"PA13": 13,
|
||||||
|
"PA14": 14,
|
||||||
|
"PA15": 15,
|
||||||
|
"PA16": 16,
|
||||||
|
"PA17": 17,
|
||||||
|
"PA18": 18,
|
||||||
|
"PA19": 19,
|
||||||
|
"PA20": 20,
|
||||||
|
"PWM0": 0,
|
||||||
|
"PWM1": 12,
|
||||||
|
"PWM5": 17,
|
||||||
|
"PWM6": 18,
|
||||||
|
"RTS0": 9,
|
||||||
|
"RTS2": 20,
|
||||||
|
"RX2": 15,
|
||||||
|
"SCK0": 16,
|
||||||
|
"TX1": 3,
|
||||||
|
"TX2": 16,
|
||||||
|
"D0": 8,
|
||||||
|
"D1": 9,
|
||||||
|
"D2": 2,
|
||||||
|
"D3": 3,
|
||||||
|
"D4": 4,
|
||||||
|
"D5": 15,
|
||||||
|
"D6": 16,
|
||||||
|
"D7": 11,
|
||||||
|
"D8": 12,
|
||||||
|
"D9": 17,
|
||||||
|
"D10": 18,
|
||||||
|
"D11": 19,
|
||||||
|
"D12": 14,
|
||||||
|
"D13": 13,
|
||||||
|
"D14": 20,
|
||||||
|
"D15": 0,
|
||||||
|
"D16": 10,
|
||||||
|
"D17": 7,
|
||||||
|
},
|
||||||
"wr2le": {
|
"wr2le": {
|
||||||
"MISO0": 22,
|
"MISO0": 22,
|
||||||
"MISO1": 22,
|
"MISO1": 22,
|
||||||
@@ -1116,21 +1243,21 @@ RTL87XX_BOARD_PINS = {
|
|||||||
"SPI0_MISO": 20,
|
"SPI0_MISO": 20,
|
||||||
"SPI0_MOSI_0": 4,
|
"SPI0_MOSI_0": 4,
|
||||||
"SPI0_MOSI_1": 19,
|
"SPI0_MOSI_1": 19,
|
||||||
"SPI0_SCK_0": 16,
|
"SPI0_SCK_0": 3,
|
||||||
"SPI0_SCK_1": 3,
|
"SPI0_SCK_1": 16,
|
||||||
"WIRE0_SCL_0": 2,
|
"WIRE0_SCL_0": 2,
|
||||||
"WIRE0_SCL_1": 15,
|
"WIRE0_SCL_1": 15,
|
||||||
"WIRE0_SCL_2": 19,
|
"WIRE0_SCL_2": 19,
|
||||||
"WIRE0_SDA_0": 20,
|
"WIRE0_SDA_0": 3,
|
||||||
"WIRE0_SDA_1": 16,
|
"WIRE0_SDA_1": 16,
|
||||||
"WIRE0_SDA_2": 3,
|
"WIRE0_SDA_2": 20,
|
||||||
"SERIAL0_RX": 13,
|
"SERIAL0_RX": 13,
|
||||||
"SERIAL0_TX": 14,
|
"SERIAL0_TX": 14,
|
||||||
"SERIAL1_CTS": 4,
|
"SERIAL1_CTS": 4,
|
||||||
"SERIAL1_RX_0": 2,
|
"SERIAL1_RX_0": 0,
|
||||||
"SERIAL1_RX_1": 0,
|
"SERIAL1_RX_1": 2,
|
||||||
"SERIAL1_TX_0": 3,
|
"SERIAL1_TX_0": 1,
|
||||||
"SERIAL1_TX_1": 1,
|
"SERIAL1_TX_1": 3,
|
||||||
"SERIAL2_CTS": 19,
|
"SERIAL2_CTS": 19,
|
||||||
"SERIAL2_RTS": 20,
|
"SERIAL2_RTS": 20,
|
||||||
"SERIAL2_RX": 15,
|
"SERIAL2_RX": 15,
|
||||||
@@ -1251,6 +1378,168 @@ RTL87XX_BOARD_PINS = {
|
|||||||
"A0": 19,
|
"A0": 19,
|
||||||
"A1": 41,
|
"A1": 41,
|
||||||
},
|
},
|
||||||
|
"cr3l": {
|
||||||
|
"SPI0_CS_0": 2,
|
||||||
|
"SPI0_CS_1": 15,
|
||||||
|
"SPI0_MISO": 20,
|
||||||
|
"SPI0_MOSI_0": 4,
|
||||||
|
"SPI0_MOSI_1": 19,
|
||||||
|
"SPI0_SCK_0": 3,
|
||||||
|
"SPI0_SCK_1": 16,
|
||||||
|
"WIRE0_SCL_0": 2,
|
||||||
|
"WIRE0_SCL_1": 15,
|
||||||
|
"WIRE0_SCL_2": 19,
|
||||||
|
"WIRE0_SDA_0": 3,
|
||||||
|
"WIRE0_SDA_1": 16,
|
||||||
|
"WIRE0_SDA_2": 20,
|
||||||
|
"SERIAL0_RX": 13,
|
||||||
|
"SERIAL0_TX": 14,
|
||||||
|
"SERIAL1_CTS": 4,
|
||||||
|
"SERIAL1_RX": 2,
|
||||||
|
"SERIAL1_TX": 3,
|
||||||
|
"SERIAL2_CTS": 19,
|
||||||
|
"SERIAL2_RTS": 20,
|
||||||
|
"SERIAL2_RX": 15,
|
||||||
|
"SERIAL2_TX": 16,
|
||||||
|
"CTS1": 4,
|
||||||
|
"CTS2": 19,
|
||||||
|
"MISO0": 20,
|
||||||
|
"PA02": 2,
|
||||||
|
"PA2": 2,
|
||||||
|
"PA03": 3,
|
||||||
|
"PA3": 3,
|
||||||
|
"PA04": 4,
|
||||||
|
"PA4": 4,
|
||||||
|
"PA13": 13,
|
||||||
|
"PA14": 14,
|
||||||
|
"PA15": 15,
|
||||||
|
"PA16": 16,
|
||||||
|
"PA17": 17,
|
||||||
|
"PA18": 18,
|
||||||
|
"PA19": 19,
|
||||||
|
"PA20": 20,
|
||||||
|
"PWM0": 20,
|
||||||
|
"PWM5": 17,
|
||||||
|
"PWM6": 18,
|
||||||
|
"RTS2": 20,
|
||||||
|
"RX0": 13,
|
||||||
|
"RX1": 2,
|
||||||
|
"RX2": 15,
|
||||||
|
"SCL0": 19,
|
||||||
|
"SDA0": 16,
|
||||||
|
"TX0": 14,
|
||||||
|
"TX1": 3,
|
||||||
|
"TX2": 16,
|
||||||
|
"D0": 20,
|
||||||
|
"D1": 2,
|
||||||
|
"D2": 3,
|
||||||
|
"D3": 4,
|
||||||
|
"D4": 15,
|
||||||
|
"D5": 16,
|
||||||
|
"D6": 17,
|
||||||
|
"D7": 18,
|
||||||
|
"D8": 19,
|
||||||
|
"D9": 13,
|
||||||
|
"D10": 14,
|
||||||
|
},
|
||||||
|
"generic-rtl8720cm-4mb-1712k": {
|
||||||
|
"SPI0_CS_0": 2,
|
||||||
|
"SPI0_CS_1": 7,
|
||||||
|
"SPI0_CS_2": 15,
|
||||||
|
"SPI0_MISO_0": 10,
|
||||||
|
"SPI0_MISO_1": 20,
|
||||||
|
"SPI0_MOSI_0": 4,
|
||||||
|
"SPI0_MOSI_1": 9,
|
||||||
|
"SPI0_MOSI_2": 19,
|
||||||
|
"SPI0_SCK_0": 3,
|
||||||
|
"SPI0_SCK_1": 8,
|
||||||
|
"SPI0_SCK_2": 16,
|
||||||
|
"WIRE0_SCL_0": 2,
|
||||||
|
"WIRE0_SCL_1": 11,
|
||||||
|
"WIRE0_SCL_2": 15,
|
||||||
|
"WIRE0_SCL_3": 19,
|
||||||
|
"WIRE0_SDA_0": 3,
|
||||||
|
"WIRE0_SDA_1": 12,
|
||||||
|
"WIRE0_SDA_2": 16,
|
||||||
|
"WIRE0_SDA_3": 20,
|
||||||
|
"SERIAL0_CTS": 10,
|
||||||
|
"SERIAL0_RTS": 9,
|
||||||
|
"SERIAL0_RX_0": 12,
|
||||||
|
"SERIAL0_RX_1": 13,
|
||||||
|
"SERIAL0_TX_0": 11,
|
||||||
|
"SERIAL0_TX_1": 14,
|
||||||
|
"SERIAL1_CTS": 4,
|
||||||
|
"SERIAL1_RX_0": 0,
|
||||||
|
"SERIAL1_RX_1": 2,
|
||||||
|
"SERIAL1_TX_0": 1,
|
||||||
|
"SERIAL1_TX_1": 3,
|
||||||
|
"SERIAL2_CTS": 19,
|
||||||
|
"SERIAL2_RTS": 20,
|
||||||
|
"SERIAL2_RX": 15,
|
||||||
|
"SERIAL2_TX": 16,
|
||||||
|
"CS0": 15,
|
||||||
|
"CTS0": 10,
|
||||||
|
"CTS1": 4,
|
||||||
|
"CTS2": 19,
|
||||||
|
"MOSI0": 19,
|
||||||
|
"PA00": 0,
|
||||||
|
"PA0": 0,
|
||||||
|
"PA01": 1,
|
||||||
|
"PA1": 1,
|
||||||
|
"PA02": 2,
|
||||||
|
"PA2": 2,
|
||||||
|
"PA03": 3,
|
||||||
|
"PA3": 3,
|
||||||
|
"PA04": 4,
|
||||||
|
"PA4": 4,
|
||||||
|
"PA07": 7,
|
||||||
|
"PA7": 7,
|
||||||
|
"PA08": 8,
|
||||||
|
"PA8": 8,
|
||||||
|
"PA09": 9,
|
||||||
|
"PA9": 9,
|
||||||
|
"PA10": 10,
|
||||||
|
"PA11": 11,
|
||||||
|
"PA12": 12,
|
||||||
|
"PA13": 13,
|
||||||
|
"PA14": 14,
|
||||||
|
"PA15": 15,
|
||||||
|
"PA16": 16,
|
||||||
|
"PA17": 17,
|
||||||
|
"PA18": 18,
|
||||||
|
"PA19": 19,
|
||||||
|
"PA20": 20,
|
||||||
|
"PA23": 23,
|
||||||
|
"PWM0": 20,
|
||||||
|
"PWM5": 17,
|
||||||
|
"PWM6": 18,
|
||||||
|
"PWM7": 23,
|
||||||
|
"RTS0": 9,
|
||||||
|
"RTS2": 20,
|
||||||
|
"RX2": 15,
|
||||||
|
"SCK0": 16,
|
||||||
|
"TX2": 16,
|
||||||
|
"D0": 0,
|
||||||
|
"D1": 1,
|
||||||
|
"D2": 2,
|
||||||
|
"D3": 3,
|
||||||
|
"D4": 4,
|
||||||
|
"D5": 7,
|
||||||
|
"D6": 8,
|
||||||
|
"D7": 9,
|
||||||
|
"D8": 10,
|
||||||
|
"D9": 11,
|
||||||
|
"D10": 12,
|
||||||
|
"D11": 13,
|
||||||
|
"D12": 14,
|
||||||
|
"D13": 15,
|
||||||
|
"D14": 16,
|
||||||
|
"D15": 17,
|
||||||
|
"D16": 18,
|
||||||
|
"D17": 19,
|
||||||
|
"D18": 20,
|
||||||
|
"D19": 23,
|
||||||
|
},
|
||||||
"generic-rtl8720cf-2mb-896k": {
|
"generic-rtl8720cf-2mb-896k": {
|
||||||
"SPI0_CS_0": 2,
|
"SPI0_CS_0": 2,
|
||||||
"SPI0_CS_1": 7,
|
"SPI0_CS_1": 7,
|
||||||
@@ -1456,8 +1745,8 @@ RTL87XX_BOARD_PINS = {
|
|||||||
"SPI1_MISO": 22,
|
"SPI1_MISO": 22,
|
||||||
"SPI1_MOSI": 23,
|
"SPI1_MOSI": 23,
|
||||||
"SPI1_SCK": 18,
|
"SPI1_SCK": 18,
|
||||||
"WIRE0_SCL_0": 29,
|
"WIRE0_SCL_0": 22,
|
||||||
"WIRE0_SCL_1": 22,
|
"WIRE0_SCL_1": 29,
|
||||||
"WIRE0_SDA_0": 19,
|
"WIRE0_SDA_0": 19,
|
||||||
"WIRE0_SDA_1": 30,
|
"WIRE0_SDA_1": 30,
|
||||||
"WIRE1_SCL": 18,
|
"WIRE1_SCL": 18,
|
||||||
@@ -1585,6 +1874,65 @@ RTL87XX_BOARD_PINS = {
|
|||||||
"D4": 12,
|
"D4": 12,
|
||||||
"A0": 19,
|
"A0": 19,
|
||||||
},
|
},
|
||||||
|
"wbr1": {
|
||||||
|
"WIRE0_SCL_0": 2,
|
||||||
|
"WIRE0_SCL_1": 11,
|
||||||
|
"WIRE0_SCL_2": 15,
|
||||||
|
"WIRE0_SDA_0": 3,
|
||||||
|
"WIRE0_SDA_1": 12,
|
||||||
|
"WIRE0_SDA_2": 16,
|
||||||
|
"SERIAL0_RX_0": 12,
|
||||||
|
"SERIAL0_RX_1": 13,
|
||||||
|
"SERIAL0_TX_0": 11,
|
||||||
|
"SERIAL0_TX_1": 14,
|
||||||
|
"SERIAL1_CTS": 4,
|
||||||
|
"SERIAL1_RX_0": 0,
|
||||||
|
"SERIAL1_RX_1": 2,
|
||||||
|
"SERIAL1_TX_0": 1,
|
||||||
|
"SERIAL1_TX_1": 3,
|
||||||
|
"SERIAL2_RX": 15,
|
||||||
|
"SERIAL2_TX": 16,
|
||||||
|
"CTS1": 4,
|
||||||
|
"MOSI0": 4,
|
||||||
|
"PA00": 0,
|
||||||
|
"PA0": 0,
|
||||||
|
"PA01": 1,
|
||||||
|
"PA1": 1,
|
||||||
|
"PA02": 2,
|
||||||
|
"PA2": 2,
|
||||||
|
"PA03": 3,
|
||||||
|
"PA3": 3,
|
||||||
|
"PA04": 4,
|
||||||
|
"PA4": 4,
|
||||||
|
"PA11": 11,
|
||||||
|
"PA12": 12,
|
||||||
|
"PA13": 13,
|
||||||
|
"PA14": 14,
|
||||||
|
"PA15": 15,
|
||||||
|
"PA16": 16,
|
||||||
|
"PA17": 17,
|
||||||
|
"PA18": 18,
|
||||||
|
"PWM5": 17,
|
||||||
|
"PWM6": 18,
|
||||||
|
"PWM7": 13,
|
||||||
|
"RX2": 15,
|
||||||
|
"SCL0": 15,
|
||||||
|
"SDA0": 12,
|
||||||
|
"TX2": 16,
|
||||||
|
"D0": 14,
|
||||||
|
"D1": 13,
|
||||||
|
"D2": 2,
|
||||||
|
"D3": 3,
|
||||||
|
"D4": 16,
|
||||||
|
"D5": 4,
|
||||||
|
"D6": 11,
|
||||||
|
"D7": 15,
|
||||||
|
"D8": 12,
|
||||||
|
"D9": 17,
|
||||||
|
"D10": 18,
|
||||||
|
"D11": 0,
|
||||||
|
"D12": 1,
|
||||||
|
},
|
||||||
"wr1": {
|
"wr1": {
|
||||||
"SPI0_CS": 19,
|
"SPI0_CS": 19,
|
||||||
"SPI0_MISO": 22,
|
"SPI0_MISO": 22,
|
||||||
@@ -1594,10 +1942,10 @@ RTL87XX_BOARD_PINS = {
|
|||||||
"SPI1_MISO": 22,
|
"SPI1_MISO": 22,
|
||||||
"SPI1_MOSI": 23,
|
"SPI1_MOSI": 23,
|
||||||
"SPI1_SCK": 18,
|
"SPI1_SCK": 18,
|
||||||
"WIRE0_SCL_0": 29,
|
"WIRE0_SCL_0": 22,
|
||||||
"WIRE0_SCL_1": 22,
|
"WIRE0_SCL_1": 29,
|
||||||
"WIRE0_SDA_0": 30,
|
"WIRE0_SDA_0": 19,
|
||||||
"WIRE0_SDA_1": 19,
|
"WIRE0_SDA_1": 30,
|
||||||
"WIRE1_SCL": 18,
|
"WIRE1_SCL": 18,
|
||||||
"WIRE1_SDA": 23,
|
"WIRE1_SDA": 23,
|
||||||
"SERIAL0_CTS": 19,
|
"SERIAL0_CTS": 19,
|
||||||
|
|||||||
@@ -2,29 +2,74 @@
|
|||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include "esphome/core/hal.h"
|
#include "esphome/core/hal.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
#include "esphome/core/progmem.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome::rtttl {
|
||||||
namespace rtttl {
|
|
||||||
|
|
||||||
static const char *const TAG = "rtttl";
|
static const char *const TAG = "rtttl";
|
||||||
|
|
||||||
static const uint32_t DOUBLE_NOTE_GAP_MS = 10;
|
|
||||||
|
|
||||||
// These values can also be found as constants in the Tone library (Tone.h)
|
// These values can also be found as constants in the Tone library (Tone.h)
|
||||||
static const uint16_t NOTES[] = {0, 262, 277, 294, 311, 330, 349, 370, 392, 415, 440, 466, 494,
|
static const uint16_t NOTES[] = {0, 262, 277, 294, 311, 330, 349, 370, 392, 415, 440, 466, 494,
|
||||||
523, 554, 587, 622, 659, 698, 740, 784, 831, 880, 932, 988, 1047,
|
523, 554, 587, 622, 659, 698, 740, 784, 831, 880, 932, 988, 1047,
|
||||||
1109, 1175, 1245, 1319, 1397, 1480, 1568, 1661, 1760, 1865, 1976, 2093, 2217,
|
1109, 1175, 1245, 1319, 1397, 1480, 1568, 1661, 1760, 1865, 1976, 2093, 2217,
|
||||||
2349, 2489, 2637, 2794, 2960, 3136, 3322, 3520, 3729, 3951};
|
2349, 2489, 2637, 2794, 2960, 3136, 3322, 3520, 3729, 3951};
|
||||||
|
|
||||||
static const uint16_t I2S_SPEED = 1000;
|
#if defined(USE_OUTPUT) || defined(USE_SPEAKER)
|
||||||
|
static const uint32_t DOUBLE_NOTE_GAP_MS = 10;
|
||||||
|
#endif // USE_OUTPUT || USE_SPEAKER
|
||||||
|
|
||||||
#undef HALF_PI
|
#ifdef USE_SPEAKER
|
||||||
static const double HALF_PI = 1.5707963267948966192313216916398;
|
static const size_t SAMPLE_BUFFER_SIZE = 2048;
|
||||||
|
|
||||||
|
struct SpeakerSample {
|
||||||
|
int8_t left{0};
|
||||||
|
int8_t right{0};
|
||||||
|
};
|
||||||
|
|
||||||
inline double deg2rad(double degrees) {
|
inline double deg2rad(double degrees) {
|
||||||
static const double PI_ON_180 = 4.0 * atan(1.0) / 180.0;
|
static const double PI_ON_180 = 4.0 * atan(1.0) / 180.0;
|
||||||
return degrees * PI_ON_180;
|
return degrees * PI_ON_180;
|
||||||
}
|
}
|
||||||
|
#endif // USE_SPEAKER
|
||||||
|
|
||||||
|
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
|
||||||
|
// RTTTL state strings indexed by State enum (0-4): STOPPED, INIT, STARTING, RUNNING, STOPPING, plus UNKNOWN fallback
|
||||||
|
PROGMEM_STRING_TABLE(RtttlStateStrings, "State::STOPPED", "State::INIT", "State::STARTING", "State::RUNNING",
|
||||||
|
"State::STOPPING", "UNKNOWN");
|
||||||
|
|
||||||
|
static const LogString *state_to_string(State state) {
|
||||||
|
return RtttlStateStrings::get_log_str(static_cast<uint8_t>(state), RtttlStateStrings::LAST_INDEX);
|
||||||
|
}
|
||||||
|
#endif // ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
|
||||||
|
|
||||||
|
static uint8_t note_index_from_char(char note) {
|
||||||
|
switch (note) {
|
||||||
|
case 'c':
|
||||||
|
return 1;
|
||||||
|
// 'c#': 2
|
||||||
|
case 'd':
|
||||||
|
return 3;
|
||||||
|
// 'd#': 4
|
||||||
|
case 'e':
|
||||||
|
return 5;
|
||||||
|
case 'f':
|
||||||
|
return 6;
|
||||||
|
// 'f#': 7
|
||||||
|
case 'g':
|
||||||
|
return 8;
|
||||||
|
// 'g#': 9
|
||||||
|
case 'a':
|
||||||
|
return 10;
|
||||||
|
// 'a#': 11
|
||||||
|
// Support both 'b' (English notation for B natural) and 'h' (German notation for B natural)
|
||||||
|
case 'b':
|
||||||
|
case 'h':
|
||||||
|
return 12;
|
||||||
|
case 'p':
|
||||||
|
default:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Rtttl::dump_config() {
|
void Rtttl::dump_config() {
|
||||||
ESP_LOGCONFIG(TAG,
|
ESP_LOGCONFIG(TAG,
|
||||||
@@ -33,161 +78,34 @@ void Rtttl::dump_config() {
|
|||||||
this->gain_);
|
this->gain_);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Rtttl::play(std::string rtttl) {
|
|
||||||
if (this->state_ != State::STATE_STOPPED && this->state_ != State::STATE_STOPPING) {
|
|
||||||
size_t pos = this->rtttl_.find(':');
|
|
||||||
size_t len = (pos != std::string::npos) ? pos : this->rtttl_.length();
|
|
||||||
ESP_LOGW(TAG, "Already playing: %.*s", (int) len, this->rtttl_.c_str());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this->rtttl_ = std::move(rtttl);
|
|
||||||
|
|
||||||
this->default_duration_ = 4;
|
|
||||||
this->default_octave_ = 6;
|
|
||||||
this->note_duration_ = 0;
|
|
||||||
|
|
||||||
int bpm = 63;
|
|
||||||
uint8_t num;
|
|
||||||
|
|
||||||
// Get name
|
|
||||||
this->position_ = this->rtttl_.find(':');
|
|
||||||
|
|
||||||
// it's somewhat documented to be up to 10 characters but let's be a bit flexible here
|
|
||||||
if (this->position_ == std::string::npos || this->position_ > 15) {
|
|
||||||
ESP_LOGE(TAG, "Unable to determine name; missing ':'");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ESP_LOGD(TAG, "Playing song %.*s", (int) this->position_, this->rtttl_.c_str());
|
|
||||||
|
|
||||||
// get default duration
|
|
||||||
this->position_ = this->rtttl_.find("d=", this->position_);
|
|
||||||
if (this->position_ == std::string::npos) {
|
|
||||||
ESP_LOGE(TAG, "Missing 'd='");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this->position_ += 2;
|
|
||||||
num = this->get_integer_();
|
|
||||||
if (num > 0)
|
|
||||||
this->default_duration_ = num;
|
|
||||||
|
|
||||||
// get default octave
|
|
||||||
this->position_ = this->rtttl_.find("o=", this->position_);
|
|
||||||
if (this->position_ == std::string::npos) {
|
|
||||||
ESP_LOGE(TAG, "Missing 'o=");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this->position_ += 2;
|
|
||||||
num = get_integer_();
|
|
||||||
if (num >= 3 && num <= 7)
|
|
||||||
this->default_octave_ = num;
|
|
||||||
|
|
||||||
// get BPM
|
|
||||||
this->position_ = this->rtttl_.find("b=", this->position_);
|
|
||||||
if (this->position_ == std::string::npos) {
|
|
||||||
ESP_LOGE(TAG, "Missing b=");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this->position_ += 2;
|
|
||||||
num = get_integer_();
|
|
||||||
if (num != 0)
|
|
||||||
bpm = num;
|
|
||||||
|
|
||||||
this->position_ = this->rtttl_.find(':', this->position_);
|
|
||||||
if (this->position_ == std::string::npos) {
|
|
||||||
ESP_LOGE(TAG, "Missing second ':'");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this->position_++;
|
|
||||||
|
|
||||||
// BPM usually expresses the number of quarter notes per minute
|
|
||||||
this->wholenote_ = 60 * 1000L * 4 / bpm; // this is the time for whole note (in milliseconds)
|
|
||||||
|
|
||||||
this->output_freq_ = 0;
|
|
||||||
this->last_note_ = millis();
|
|
||||||
this->note_duration_ = 1;
|
|
||||||
|
|
||||||
#ifdef USE_SPEAKER
|
|
||||||
if (this->speaker_ != nullptr) {
|
|
||||||
this->set_state_(State::STATE_INIT);
|
|
||||||
this->samples_sent_ = 0;
|
|
||||||
this->samples_count_ = 0;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
#ifdef USE_OUTPUT
|
|
||||||
if (this->output_ != nullptr) {
|
|
||||||
this->set_state_(State::STATE_RUNNING);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
void Rtttl::stop() {
|
|
||||||
#ifdef USE_OUTPUT
|
|
||||||
if (this->output_ != nullptr) {
|
|
||||||
this->output_->set_level(0.0);
|
|
||||||
this->set_state_(STATE_STOPPED);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
#ifdef USE_SPEAKER
|
|
||||||
if (this->speaker_ != nullptr) {
|
|
||||||
if (this->speaker_->is_running()) {
|
|
||||||
this->speaker_->stop();
|
|
||||||
}
|
|
||||||
this->set_state_(STATE_STOPPING);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
this->position_ = this->rtttl_.length();
|
|
||||||
this->note_duration_ = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Rtttl::finish_() {
|
|
||||||
ESP_LOGV(TAG, "Rtttl::finish_()");
|
|
||||||
#ifdef USE_OUTPUT
|
|
||||||
if (this->output_ != nullptr) {
|
|
||||||
this->output_->set_level(0.0);
|
|
||||||
this->set_state_(State::STATE_STOPPED);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
#ifdef USE_SPEAKER
|
|
||||||
if (this->speaker_ != nullptr) {
|
|
||||||
SpeakerSample sample[2];
|
|
||||||
sample[0].left = 0;
|
|
||||||
sample[0].right = 0;
|
|
||||||
sample[1].left = 0;
|
|
||||||
sample[1].right = 0;
|
|
||||||
this->speaker_->play((uint8_t *) (&sample), 8);
|
|
||||||
this->speaker_->finish();
|
|
||||||
this->set_state_(State::STATE_STOPPING);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
// Ensure no more notes are played in case finish_() is called for an error.
|
|
||||||
this->position_ = this->rtttl_.length();
|
|
||||||
this->note_duration_ = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Rtttl::loop() {
|
void Rtttl::loop() {
|
||||||
if (this->state_ == State::STATE_STOPPED) {
|
if (this->state_ == State::STOPPED) {
|
||||||
this->disable_loop();
|
this->disable_loop();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef USE_OUTPUT
|
||||||
|
if (this->output_ != nullptr && millis() - this->last_note_ < this->note_duration_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#endif // USE_OUTPUT
|
||||||
|
|
||||||
#ifdef USE_SPEAKER
|
#ifdef USE_SPEAKER
|
||||||
if (this->speaker_ != nullptr) {
|
if (this->speaker_ != nullptr) {
|
||||||
if (this->state_ == State::STATE_STOPPING) {
|
if (this->state_ == State::STOPPING) {
|
||||||
if (this->speaker_->is_stopped()) {
|
if (this->speaker_->is_stopped()) {
|
||||||
this->set_state_(State::STATE_STOPPED);
|
this->set_state_(State::STOPPED);
|
||||||
} else {
|
} else {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else if (this->state_ == State::STATE_INIT) {
|
} else if (this->state_ == State::INIT) {
|
||||||
if (this->speaker_->is_stopped()) {
|
if (this->speaker_->is_stopped()) {
|
||||||
this->speaker_->start();
|
this->speaker_->start();
|
||||||
this->set_state_(State::STATE_STARTING);
|
this->set_state_(State::STARTING);
|
||||||
}
|
}
|
||||||
} else if (this->state_ == State::STATE_STARTING) {
|
} else if (this->state_ == State::STARTING) {
|
||||||
if (this->speaker_->is_running()) {
|
if (this->speaker_->is_running()) {
|
||||||
this->set_state_(State::STATE_RUNNING);
|
this->set_state_(State::RUNNING);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!this->speaker_->is_running()) {
|
if (!this->speaker_->is_running()) {
|
||||||
@@ -229,19 +147,17 @@ void Rtttl::loop() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif // USE_SPEAKER
|
||||||
#ifdef USE_OUTPUT
|
|
||||||
if (this->output_ != nullptr && millis() - this->last_note_ < this->note_duration_)
|
|
||||||
return;
|
|
||||||
#endif
|
|
||||||
if (this->position_ >= this->rtttl_.length()) {
|
if (this->position_ >= this->rtttl_.length()) {
|
||||||
this->finish_();
|
this->finish_();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// align to note: most rtttl's out there does not add and space after the ',' separator but just in case...
|
// align to note: most rtttl's out there does not add and space after the ',' separator but just in case...
|
||||||
while (this->rtttl_[this->position_] == ',' || this->rtttl_[this->position_] == ' ')
|
while (this->rtttl_[this->position_] == ',' || this->rtttl_[this->position_] == ' ') {
|
||||||
this->position_++;
|
this->position_++;
|
||||||
|
}
|
||||||
|
|
||||||
// first, get note duration, if available
|
// first, get note duration, if available
|
||||||
uint8_t num = this->get_integer_();
|
uint8_t num = this->get_integer_();
|
||||||
@@ -253,35 +169,8 @@ void Rtttl::loop() {
|
|||||||
this->wholenote_ / this->default_duration_; // we will need to check if we are a dotted note after
|
this->wholenote_ / this->default_duration_; // we will need to check if we are a dotted note after
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t note;
|
uint8_t note = note_index_from_char(this->rtttl_[this->position_]);
|
||||||
|
|
||||||
switch (this->rtttl_[this->position_]) {
|
|
||||||
case 'c':
|
|
||||||
note = 1;
|
|
||||||
break;
|
|
||||||
case 'd':
|
|
||||||
note = 3;
|
|
||||||
break;
|
|
||||||
case 'e':
|
|
||||||
note = 5;
|
|
||||||
break;
|
|
||||||
case 'f':
|
|
||||||
note = 6;
|
|
||||||
break;
|
|
||||||
case 'g':
|
|
||||||
note = 8;
|
|
||||||
break;
|
|
||||||
case 'a':
|
|
||||||
note = 10;
|
|
||||||
break;
|
|
||||||
case 'h':
|
|
||||||
case 'b':
|
|
||||||
note = 12;
|
|
||||||
break;
|
|
||||||
case 'p':
|
|
||||||
default:
|
|
||||||
note = 0;
|
|
||||||
}
|
|
||||||
this->position_++;
|
this->position_++;
|
||||||
|
|
||||||
// now, get optional '#' sharp
|
// now, get optional '#' sharp
|
||||||
@@ -291,7 +180,7 @@ void Rtttl::loop() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// now, get scale
|
// now, get scale
|
||||||
uint8_t scale = get_integer_();
|
uint8_t scale = this->get_integer_();
|
||||||
if (scale == 0) {
|
if (scale == 0) {
|
||||||
scale = this->default_octave_;
|
scale = this->default_octave_;
|
||||||
}
|
}
|
||||||
@@ -344,7 +233,8 @@ void Rtttl::loop() {
|
|||||||
this->output_->set_level(0.0);
|
this->output_->set_level(0.0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif // USE_OUTPUT
|
||||||
|
|
||||||
#ifdef USE_SPEAKER
|
#ifdef USE_SPEAKER
|
||||||
if (this->speaker_ != nullptr) {
|
if (this->speaker_ != nullptr) {
|
||||||
this->samples_sent_ = 0;
|
this->samples_sent_ = 0;
|
||||||
@@ -369,29 +259,152 @@ void Rtttl::loop() {
|
|||||||
}
|
}
|
||||||
// Convert from frequency in Hz to high and low samples in fixed point
|
// Convert from frequency in Hz to high and low samples in fixed point
|
||||||
}
|
}
|
||||||
#endif
|
#endif // USE_SPEAKER
|
||||||
|
|
||||||
this->last_note_ = millis();
|
this->last_note_ = millis();
|
||||||
}
|
}
|
||||||
|
|
||||||
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
|
void Rtttl::play(std::string rtttl) {
|
||||||
static const LogString *state_to_string(State state) {
|
if (this->state_ != State::STOPPED && this->state_ != State::STOPPING) {
|
||||||
switch (state) {
|
size_t pos = this->rtttl_.find(':');
|
||||||
case STATE_STOPPED:
|
size_t len = (pos != std::string::npos) ? pos : this->rtttl_.length();
|
||||||
return LOG_STR("STATE_STOPPED");
|
ESP_LOGW(TAG, "Already playing: %.*s", (int) len, this->rtttl_.c_str());
|
||||||
case STATE_STARTING:
|
return;
|
||||||
return LOG_STR("STATE_STARTING");
|
|
||||||
case STATE_RUNNING:
|
|
||||||
return LOG_STR("STATE_RUNNING");
|
|
||||||
case STATE_STOPPING:
|
|
||||||
return LOG_STR("STATE_STOPPING");
|
|
||||||
case STATE_INIT:
|
|
||||||
return LOG_STR("STATE_INIT");
|
|
||||||
default:
|
|
||||||
return LOG_STR("UNKNOWN");
|
|
||||||
}
|
}
|
||||||
};
|
|
||||||
#endif
|
this->rtttl_ = std::move(rtttl);
|
||||||
|
|
||||||
|
this->default_duration_ = 4;
|
||||||
|
this->default_octave_ = 6;
|
||||||
|
this->note_duration_ = 0;
|
||||||
|
|
||||||
|
int bpm = 63;
|
||||||
|
uint8_t num;
|
||||||
|
|
||||||
|
// Get name
|
||||||
|
this->position_ = this->rtttl_.find(':');
|
||||||
|
|
||||||
|
// it's somewhat documented to be up to 10 characters but let's be a bit flexible here
|
||||||
|
if (this->position_ == std::string::npos || this->position_ > 15) {
|
||||||
|
ESP_LOGE(TAG, "Unable to determine name; missing ':'");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ESP_LOGD(TAG, "Playing song %.*s", (int) this->position_, this->rtttl_.c_str());
|
||||||
|
|
||||||
|
// get default duration
|
||||||
|
this->position_ = this->rtttl_.find("d=", this->position_);
|
||||||
|
if (this->position_ == std::string::npos) {
|
||||||
|
ESP_LOGE(TAG, "Missing 'd='");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this->position_ += 2;
|
||||||
|
num = this->get_integer_();
|
||||||
|
if (num > 0) {
|
||||||
|
this->default_duration_ = num;
|
||||||
|
}
|
||||||
|
|
||||||
|
// get default octave
|
||||||
|
this->position_ = this->rtttl_.find("o=", this->position_);
|
||||||
|
if (this->position_ == std::string::npos) {
|
||||||
|
ESP_LOGE(TAG, "Missing 'o=");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this->position_ += 2;
|
||||||
|
num = this->get_integer_();
|
||||||
|
if (num >= 3 && num <= 7) {
|
||||||
|
this->default_octave_ = num;
|
||||||
|
}
|
||||||
|
|
||||||
|
// get BPM
|
||||||
|
this->position_ = this->rtttl_.find("b=", this->position_);
|
||||||
|
if (this->position_ == std::string::npos) {
|
||||||
|
ESP_LOGE(TAG, "Missing b=");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this->position_ += 2;
|
||||||
|
num = this->get_integer_();
|
||||||
|
if (num != 0) {
|
||||||
|
bpm = num;
|
||||||
|
}
|
||||||
|
|
||||||
|
this->position_ = this->rtttl_.find(':', this->position_);
|
||||||
|
if (this->position_ == std::string::npos) {
|
||||||
|
ESP_LOGE(TAG, "Missing second ':'");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this->position_++;
|
||||||
|
|
||||||
|
// BPM usually expresses the number of quarter notes per minute
|
||||||
|
this->wholenote_ = 60 * 1000L * 4 / bpm; // this is the time for whole note (in milliseconds)
|
||||||
|
|
||||||
|
this->output_freq_ = 0;
|
||||||
|
this->last_note_ = millis();
|
||||||
|
this->note_duration_ = 1;
|
||||||
|
|
||||||
|
#ifdef USE_OUTPUT
|
||||||
|
if (this->output_ != nullptr) {
|
||||||
|
this->set_state_(State::RUNNING);
|
||||||
|
}
|
||||||
|
#endif // USE_OUTPUT
|
||||||
|
|
||||||
|
#ifdef USE_SPEAKER
|
||||||
|
if (this->speaker_ != nullptr) {
|
||||||
|
this->set_state_(State::INIT);
|
||||||
|
this->samples_sent_ = 0;
|
||||||
|
this->samples_count_ = 0;
|
||||||
|
}
|
||||||
|
#endif // USE_SPEAKER
|
||||||
|
}
|
||||||
|
|
||||||
|
void Rtttl::stop() {
|
||||||
|
#ifdef USE_OUTPUT
|
||||||
|
if (this->output_ != nullptr) {
|
||||||
|
this->output_->set_level(0.0);
|
||||||
|
this->set_state_(State::STOPPED);
|
||||||
|
}
|
||||||
|
#endif // USE_OUTPUT
|
||||||
|
|
||||||
|
#ifdef USE_SPEAKER
|
||||||
|
if (this->speaker_ != nullptr) {
|
||||||
|
if (this->speaker_->is_running()) {
|
||||||
|
this->speaker_->stop();
|
||||||
|
}
|
||||||
|
this->set_state_(State::STOPPING);
|
||||||
|
}
|
||||||
|
#endif // USE_SPEAKER
|
||||||
|
|
||||||
|
this->position_ = this->rtttl_.length();
|
||||||
|
this->note_duration_ = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Rtttl::finish_() {
|
||||||
|
ESP_LOGV(TAG, "Rtttl::finish_()");
|
||||||
|
|
||||||
|
#ifdef USE_OUTPUT
|
||||||
|
if (this->output_ != nullptr) {
|
||||||
|
this->output_->set_level(0.0);
|
||||||
|
this->set_state_(State::STOPPED);
|
||||||
|
}
|
||||||
|
#endif // USE_OUTPUT
|
||||||
|
|
||||||
|
#ifdef USE_SPEAKER
|
||||||
|
if (this->speaker_ != nullptr) {
|
||||||
|
SpeakerSample sample[2];
|
||||||
|
sample[0].left = 0;
|
||||||
|
sample[0].right = 0;
|
||||||
|
sample[1].left = 0;
|
||||||
|
sample[1].right = 0;
|
||||||
|
this->speaker_->play((uint8_t *) (&sample), 8);
|
||||||
|
this->speaker_->finish();
|
||||||
|
this->set_state_(State::STOPPING);
|
||||||
|
}
|
||||||
|
#endif // USE_SPEAKER
|
||||||
|
|
||||||
|
// Ensure no more notes are played in case finish_() is called for an error.
|
||||||
|
this->position_ = this->rtttl_.length();
|
||||||
|
this->note_duration_ = 0;
|
||||||
|
}
|
||||||
|
|
||||||
void Rtttl::set_state_(State state) {
|
void Rtttl::set_state_(State state) {
|
||||||
State old_state = this->state_;
|
State old_state = this->state_;
|
||||||
@@ -399,15 +412,14 @@ void Rtttl::set_state_(State state) {
|
|||||||
ESP_LOGV(TAG, "State changed from %s to %s", LOG_STR_ARG(state_to_string(old_state)),
|
ESP_LOGV(TAG, "State changed from %s to %s", LOG_STR_ARG(state_to_string(old_state)),
|
||||||
LOG_STR_ARG(state_to_string(state)));
|
LOG_STR_ARG(state_to_string(state)));
|
||||||
|
|
||||||
// Clear loop_done when transitioning from STOPPED to any other state
|
// Clear loop_done when transitioning from `State::STOPPED` to any other state
|
||||||
if (state == State::STATE_STOPPED) {
|
if (state == State::STOPPED) {
|
||||||
this->disable_loop();
|
this->disable_loop();
|
||||||
this->on_finished_playback_callback_.call();
|
this->on_finished_playback_callback_.call();
|
||||||
ESP_LOGD(TAG, "Playback finished");
|
ESP_LOGD(TAG, "Playback finished");
|
||||||
} else if (old_state == State::STATE_STOPPED) {
|
} else if (old_state == State::STOPPED) {
|
||||||
this->enable_loop();
|
this->enable_loop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace rtttl
|
} // namespace esphome::rtttl
|
||||||
} // namespace esphome
|
|
||||||
|
|||||||
@@ -5,48 +5,41 @@
|
|||||||
|
|
||||||
#ifdef USE_OUTPUT
|
#ifdef USE_OUTPUT
|
||||||
#include "esphome/components/output/float_output.h"
|
#include "esphome/components/output/float_output.h"
|
||||||
#endif
|
#endif // USE_OUTPUT
|
||||||
|
|
||||||
#ifdef USE_SPEAKER
|
#ifdef USE_SPEAKER
|
||||||
#include "esphome/components/speaker/speaker.h"
|
#include "esphome/components/speaker/speaker.h"
|
||||||
#endif
|
#endif // USE_SPEAKER
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome::rtttl {
|
||||||
namespace rtttl {
|
|
||||||
|
|
||||||
enum State : uint8_t {
|
enum class State : uint8_t {
|
||||||
STATE_STOPPED = 0,
|
STOPPED = 0,
|
||||||
STATE_INIT,
|
INIT,
|
||||||
STATE_STARTING,
|
STARTING,
|
||||||
STATE_RUNNING,
|
RUNNING,
|
||||||
STATE_STOPPING,
|
STOPPING,
|
||||||
};
|
};
|
||||||
|
|
||||||
#ifdef USE_SPEAKER
|
|
||||||
static const size_t SAMPLE_BUFFER_SIZE = 2048;
|
|
||||||
|
|
||||||
struct SpeakerSample {
|
|
||||||
int8_t left{0};
|
|
||||||
int8_t right{0};
|
|
||||||
};
|
|
||||||
#endif
|
|
||||||
|
|
||||||
class Rtttl : public Component {
|
class Rtttl : public Component {
|
||||||
public:
|
public:
|
||||||
#ifdef USE_OUTPUT
|
#ifdef USE_OUTPUT
|
||||||
void set_output(output::FloatOutput *output) { this->output_ = output; }
|
void set_output(output::FloatOutput *output) { this->output_ = output; }
|
||||||
#endif
|
#endif // USE_OUTPUT
|
||||||
|
|
||||||
#ifdef USE_SPEAKER
|
#ifdef USE_SPEAKER
|
||||||
void set_speaker(speaker::Speaker *speaker) { this->speaker_ = speaker; }
|
void set_speaker(speaker::Speaker *speaker) { this->speaker_ = speaker; }
|
||||||
#endif
|
#endif // USE_SPEAKER
|
||||||
float get_gain() { return gain_; }
|
|
||||||
void set_gain(float gain) { this->gain_ = clamp(gain, 0.0f, 1.0f); }
|
void dump_config() override;
|
||||||
|
void loop() override;
|
||||||
void play(std::string rtttl);
|
void play(std::string rtttl);
|
||||||
void stop();
|
void stop();
|
||||||
void dump_config() override;
|
|
||||||
|
|
||||||
bool is_playing() { return this->state_ != State::STATE_STOPPED; }
|
float get_gain() { return this->gain_; }
|
||||||
void loop() override;
|
void set_gain(float gain) { this->gain_ = clamp(gain, 0.0f, 1.0f); }
|
||||||
|
|
||||||
|
bool is_playing() { return this->state_ != State::STOPPED; }
|
||||||
|
|
||||||
void add_on_finished_playback_callback(std::function<void()> callback) {
|
void add_on_finished_playback_callback(std::function<void()> callback) {
|
||||||
this->on_finished_playback_callback_.add(std::move(callback));
|
this->on_finished_playback_callback_.add(std::move(callback));
|
||||||
@@ -90,12 +83,12 @@ class Rtttl : public Component {
|
|||||||
/// The gain of the output.
|
/// The gain of the output.
|
||||||
float gain_{0.6f};
|
float gain_{0.6f};
|
||||||
/// The current state of the RTTTL player.
|
/// The current state of the RTTTL player.
|
||||||
State state_{State::STATE_STOPPED};
|
State state_{State::STOPPED};
|
||||||
|
|
||||||
#ifdef USE_OUTPUT
|
#ifdef USE_OUTPUT
|
||||||
/// The output to write the sound to.
|
/// The output to write the sound to.
|
||||||
output::FloatOutput *output_;
|
output::FloatOutput *output_;
|
||||||
#endif
|
#endif // USE_OUTPUT
|
||||||
|
|
||||||
#ifdef USE_SPEAKER
|
#ifdef USE_SPEAKER
|
||||||
/// The speaker to write the sound to.
|
/// The speaker to write the sound to.
|
||||||
@@ -110,8 +103,7 @@ class Rtttl : public Component {
|
|||||||
int samples_count_{0};
|
int samples_count_{0};
|
||||||
/// The number of samples for the gap between notes.
|
/// The number of samples for the gap between notes.
|
||||||
int samples_gap_{0};
|
int samples_gap_{0};
|
||||||
|
#endif // USE_SPEAKER
|
||||||
#endif
|
|
||||||
|
|
||||||
/// The callback to call when playback is finished.
|
/// The callback to call when playback is finished.
|
||||||
CallbackManager<void()> on_finished_playback_callback_;
|
CallbackManager<void()> on_finished_playback_callback_;
|
||||||
@@ -145,5 +137,4 @@ class FinishedPlaybackTrigger : public Trigger<> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace rtttl
|
} // namespace esphome::rtttl
|
||||||
} // namespace esphome
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
#include "esphome/core/application.h"
|
#include "esphome/core/application.h"
|
||||||
#include "esphome/core/helpers.h"
|
#include "esphome/core/helpers.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
#include "esphome/core/progmem.h"
|
||||||
#include <cinttypes>
|
#include <cinttypes>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
@@ -1544,42 +1545,19 @@ void Sprinkler::log_multiplier_zero_warning_(const LogString *method_name) {
|
|||||||
ESP_LOGW(TAG, "%s called but multiplier is set to zero; no action taken", LOG_STR_ARG(method_name));
|
ESP_LOGW(TAG, "%s called but multiplier is set to zero; no action taken", LOG_STR_ARG(method_name));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Request origin strings indexed by SprinklerValveRunRequestOrigin enum (0-2): USER, CYCLE, QUEUE
|
||||||
|
PROGMEM_STRING_TABLE(SprinklerRequestOriginStrings, "USER", "CYCLE", "QUEUE", "UNKNOWN");
|
||||||
|
|
||||||
const LogString *Sprinkler::req_as_str_(SprinklerValveRunRequestOrigin origin) {
|
const LogString *Sprinkler::req_as_str_(SprinklerValveRunRequestOrigin origin) {
|
||||||
switch (origin) {
|
return SprinklerRequestOriginStrings::get_log_str(static_cast<uint8_t>(origin),
|
||||||
case USER:
|
SprinklerRequestOriginStrings::LAST_INDEX);
|
||||||
return LOG_STR("USER");
|
|
||||||
|
|
||||||
case CYCLE:
|
|
||||||
return LOG_STR("CYCLE");
|
|
||||||
|
|
||||||
case QUEUE:
|
|
||||||
return LOG_STR("QUEUE");
|
|
||||||
|
|
||||||
default:
|
|
||||||
return LOG_STR("UNKNOWN");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sprinkler state strings indexed by SprinklerState enum (0-4): IDLE, STARTING, ACTIVE, STOPPING, BYPASS
|
||||||
|
PROGMEM_STRING_TABLE(SprinklerStateStrings, "IDLE", "STARTING", "ACTIVE", "STOPPING", "BYPASS", "UNKNOWN");
|
||||||
|
|
||||||
const LogString *Sprinkler::state_as_str_(SprinklerState state) {
|
const LogString *Sprinkler::state_as_str_(SprinklerState state) {
|
||||||
switch (state) {
|
return SprinklerStateStrings::get_log_str(static_cast<uint8_t>(state), SprinklerStateStrings::LAST_INDEX);
|
||||||
case IDLE:
|
|
||||||
return LOG_STR("IDLE");
|
|
||||||
|
|
||||||
case STARTING:
|
|
||||||
return LOG_STR("STARTING");
|
|
||||||
|
|
||||||
case ACTIVE:
|
|
||||||
return LOG_STR("ACTIVE");
|
|
||||||
|
|
||||||
case STOPPING:
|
|
||||||
return LOG_STR("STOPPING");
|
|
||||||
|
|
||||||
case BYPASS:
|
|
||||||
return LOG_STR("BYPASS");
|
|
||||||
|
|
||||||
default:
|
|
||||||
return LOG_STR("UNKNOWN");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Sprinkler::start_timer_(const SprinklerTimerIndex timer_index) {
|
void Sprinkler::start_timer_(const SprinklerTimerIndex timer_index) {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#include "ssd1306_base.h"
|
#include "ssd1306_base.h"
|
||||||
#include "esphome/core/helpers.h"
|
#include "esphome/core/helpers.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
#include "esphome/core/progmem.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace ssd1306_base {
|
namespace ssd1306_base {
|
||||||
@@ -40,6 +41,55 @@ static const uint8_t SSD1305_COMMAND_SET_AREA_COLOR = 0xD8;
|
|||||||
static const uint8_t SH1107_COMMAND_SET_START_LINE = 0xDC;
|
static const uint8_t SH1107_COMMAND_SET_START_LINE = 0xDC;
|
||||||
static const uint8_t SH1107_COMMAND_CHARGE_PUMP = 0xAD;
|
static const uint8_t SH1107_COMMAND_CHARGE_PUMP = 0xAD;
|
||||||
|
|
||||||
|
// Verify first enum value and table sizes match SSD1306_MODEL_COUNT
|
||||||
|
static_assert(SSD1306_MODEL_128_32 == 0, "SSD1306Model enum must start at 0");
|
||||||
|
|
||||||
|
// PROGMEM lookup table indexed by SSD1306Model enum (width, height per model)
|
||||||
|
struct ModelDimensions {
|
||||||
|
uint8_t width;
|
||||||
|
uint8_t height;
|
||||||
|
};
|
||||||
|
static const ModelDimensions MODEL_DIMS[] PROGMEM = {
|
||||||
|
{128, 32}, // SSD1306_MODEL_128_32
|
||||||
|
{128, 64}, // SSD1306_MODEL_128_64
|
||||||
|
{96, 16}, // SSD1306_MODEL_96_16
|
||||||
|
{64, 48}, // SSD1306_MODEL_64_48
|
||||||
|
{64, 32}, // SSD1306_MODEL_64_32
|
||||||
|
{72, 40}, // SSD1306_MODEL_72_40
|
||||||
|
{128, 32}, // SH1106_MODEL_128_32
|
||||||
|
{128, 64}, // SH1106_MODEL_128_64
|
||||||
|
{96, 16}, // SH1106_MODEL_96_16
|
||||||
|
{64, 48}, // SH1106_MODEL_64_48
|
||||||
|
{64, 128}, // SH1107_MODEL_128_64 (note: width is 64, height is 128)
|
||||||
|
{128, 128}, // SH1107_MODEL_128_128
|
||||||
|
{128, 32}, // SSD1305_MODEL_128_32
|
||||||
|
{128, 64}, // SSD1305_MODEL_128_64
|
||||||
|
};
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
PROGMEM_STRING_TABLE(ModelStrings,
|
||||||
|
"SSD1306 128x32", // SSD1306_MODEL_128_32
|
||||||
|
"SSD1306 128x64", // SSD1306_MODEL_128_64
|
||||||
|
"SSD1306 96x16", // SSD1306_MODEL_96_16
|
||||||
|
"SSD1306 64x48", // SSD1306_MODEL_64_48
|
||||||
|
"SSD1306 64x32", // SSD1306_MODEL_64_32
|
||||||
|
"SSD1306 72x40", // SSD1306_MODEL_72_40
|
||||||
|
"SH1106 128x32", // SH1106_MODEL_128_32
|
||||||
|
"SH1106 128x64", // SH1106_MODEL_128_64
|
||||||
|
"SH1106 96x16", // SH1106_MODEL_96_16
|
||||||
|
"SH1106 64x48", // SH1106_MODEL_64_48
|
||||||
|
"SH1107 128x64", // SH1107_MODEL_128_64
|
||||||
|
"SH1107 128x128", // SH1107_MODEL_128_128
|
||||||
|
"SSD1305 128x32", // SSD1305_MODEL_128_32
|
||||||
|
"SSD1305 128x64", // SSD1305_MODEL_128_64
|
||||||
|
"Unknown" // fallback
|
||||||
|
);
|
||||||
|
// clang-format on
|
||||||
|
static_assert(sizeof(MODEL_DIMS) / sizeof(MODEL_DIMS[0]) == SSD1306_MODEL_COUNT,
|
||||||
|
"MODEL_DIMS must have one entry per SSD1306Model");
|
||||||
|
static_assert(ModelStrings::COUNT == SSD1306_MODEL_COUNT + 1,
|
||||||
|
"ModelStrings must have one entry per SSD1306Model plus fallback");
|
||||||
|
|
||||||
void SSD1306::setup() {
|
void SSD1306::setup() {
|
||||||
this->init_internal_(this->get_buffer_length_());
|
this->init_internal_(this->get_buffer_length_());
|
||||||
|
|
||||||
@@ -146,6 +196,7 @@ void SSD1306::setup() {
|
|||||||
break;
|
break;
|
||||||
case SH1107_MODEL_128_64:
|
case SH1107_MODEL_128_64:
|
||||||
case SH1107_MODEL_128_128:
|
case SH1107_MODEL_128_128:
|
||||||
|
case SSD1306_MODEL_COUNT:
|
||||||
// Not used, but prevents build warning
|
// Not used, but prevents build warning
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -274,54 +325,14 @@ void SSD1306::turn_off() {
|
|||||||
this->is_on_ = false;
|
this->is_on_ = false;
|
||||||
}
|
}
|
||||||
int SSD1306::get_height_internal() {
|
int SSD1306::get_height_internal() {
|
||||||
switch (this->model_) {
|
if (this->model_ >= SSD1306_MODEL_COUNT)
|
||||||
case SH1107_MODEL_128_64:
|
return 0;
|
||||||
case SH1107_MODEL_128_128:
|
return progmem_read_byte(&MODEL_DIMS[this->model_].height);
|
||||||
return 128;
|
|
||||||
case SSD1306_MODEL_128_32:
|
|
||||||
case SSD1306_MODEL_64_32:
|
|
||||||
case SH1106_MODEL_128_32:
|
|
||||||
case SSD1305_MODEL_128_32:
|
|
||||||
return 32;
|
|
||||||
case SSD1306_MODEL_128_64:
|
|
||||||
case SH1106_MODEL_128_64:
|
|
||||||
case SSD1305_MODEL_128_64:
|
|
||||||
return 64;
|
|
||||||
case SSD1306_MODEL_96_16:
|
|
||||||
case SH1106_MODEL_96_16:
|
|
||||||
return 16;
|
|
||||||
case SSD1306_MODEL_64_48:
|
|
||||||
case SH1106_MODEL_64_48:
|
|
||||||
return 48;
|
|
||||||
case SSD1306_MODEL_72_40:
|
|
||||||
return 40;
|
|
||||||
default:
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
int SSD1306::get_width_internal() {
|
int SSD1306::get_width_internal() {
|
||||||
switch (this->model_) {
|
if (this->model_ >= SSD1306_MODEL_COUNT)
|
||||||
case SSD1306_MODEL_128_32:
|
return 0;
|
||||||
case SH1106_MODEL_128_32:
|
return progmem_read_byte(&MODEL_DIMS[this->model_].width);
|
||||||
case SSD1306_MODEL_128_64:
|
|
||||||
case SH1106_MODEL_128_64:
|
|
||||||
case SSD1305_MODEL_128_32:
|
|
||||||
case SSD1305_MODEL_128_64:
|
|
||||||
case SH1107_MODEL_128_128:
|
|
||||||
return 128;
|
|
||||||
case SSD1306_MODEL_96_16:
|
|
||||||
case SH1106_MODEL_96_16:
|
|
||||||
return 96;
|
|
||||||
case SSD1306_MODEL_64_48:
|
|
||||||
case SSD1306_MODEL_64_32:
|
|
||||||
case SH1106_MODEL_64_48:
|
|
||||||
case SH1107_MODEL_128_64:
|
|
||||||
return 64;
|
|
||||||
case SSD1306_MODEL_72_40:
|
|
||||||
return 72;
|
|
||||||
default:
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
size_t SSD1306::get_buffer_length_() {
|
size_t SSD1306::get_buffer_length_() {
|
||||||
return size_t(this->get_width_internal()) * size_t(this->get_height_internal()) / 8u;
|
return size_t(this->get_width_internal()) * size_t(this->get_height_internal()) / 8u;
|
||||||
@@ -361,37 +372,8 @@ void SSD1306::init_reset_() {
|
|||||||
this->reset_pin_->digital_write(true);
|
this->reset_pin_->digital_write(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const char *SSD1306::model_str_() {
|
const LogString *SSD1306::model_str_() {
|
||||||
switch (this->model_) {
|
return ModelStrings::get_log_str(static_cast<uint8_t>(this->model_), ModelStrings::LAST_INDEX);
|
||||||
case SSD1306_MODEL_128_32:
|
|
||||||
return "SSD1306 128x32";
|
|
||||||
case SSD1306_MODEL_128_64:
|
|
||||||
return "SSD1306 128x64";
|
|
||||||
case SSD1306_MODEL_64_32:
|
|
||||||
return "SSD1306 64x32";
|
|
||||||
case SSD1306_MODEL_96_16:
|
|
||||||
return "SSD1306 96x16";
|
|
||||||
case SSD1306_MODEL_64_48:
|
|
||||||
return "SSD1306 64x48";
|
|
||||||
case SSD1306_MODEL_72_40:
|
|
||||||
return "SSD1306 72x40";
|
|
||||||
case SH1106_MODEL_128_32:
|
|
||||||
return "SH1106 128x32";
|
|
||||||
case SH1106_MODEL_128_64:
|
|
||||||
return "SH1106 128x64";
|
|
||||||
case SH1106_MODEL_96_16:
|
|
||||||
return "SH1106 96x16";
|
|
||||||
case SH1106_MODEL_64_48:
|
|
||||||
return "SH1106 64x48";
|
|
||||||
case SH1107_MODEL_128_64:
|
|
||||||
return "SH1107 128x64";
|
|
||||||
case SSD1305_MODEL_128_32:
|
|
||||||
return "SSD1305 128x32";
|
|
||||||
case SSD1305_MODEL_128_64:
|
|
||||||
return "SSD1305 128x64";
|
|
||||||
default:
|
|
||||||
return "Unknown";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace ssd1306_base
|
} // namespace ssd1306_base
|
||||||
|
|||||||
@@ -22,6 +22,9 @@ enum SSD1306Model {
|
|||||||
SH1107_MODEL_128_128,
|
SH1107_MODEL_128_128,
|
||||||
SSD1305_MODEL_128_32,
|
SSD1305_MODEL_128_32,
|
||||||
SSD1305_MODEL_128_64,
|
SSD1305_MODEL_128_64,
|
||||||
|
// When adding a new model, add it before SSD1306_MODEL_COUNT and update
|
||||||
|
// MODEL_DIMS and ModelStrings tables in ssd1306_base.cpp
|
||||||
|
SSD1306_MODEL_COUNT, // must be last
|
||||||
};
|
};
|
||||||
|
|
||||||
class SSD1306 : public display::DisplayBuffer {
|
class SSD1306 : public display::DisplayBuffer {
|
||||||
@@ -70,7 +73,7 @@ class SSD1306 : public display::DisplayBuffer {
|
|||||||
int get_height_internal() override;
|
int get_height_internal() override;
|
||||||
int get_width_internal() override;
|
int get_width_internal() override;
|
||||||
size_t get_buffer_length_();
|
size_t get_buffer_length_();
|
||||||
const char *model_str_();
|
const LogString *model_str_();
|
||||||
|
|
||||||
SSD1306Model model_{SSD1306_MODEL_128_64};
|
SSD1306Model model_{SSD1306_MODEL_128_64};
|
||||||
GPIOPin *reset_pin_{nullptr};
|
GPIOPin *reset_pin_{nullptr};
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ void I2CSSD1306::dump_config() {
|
|||||||
" Offset X: %d\n"
|
" Offset X: %d\n"
|
||||||
" Offset Y: %d\n"
|
" Offset Y: %d\n"
|
||||||
" Inverted Color: %s",
|
" Inverted Color: %s",
|
||||||
this->model_str_(), YESNO(this->external_vcc_), YESNO(this->flip_x_), YESNO(this->flip_y_),
|
LOG_STR_ARG(this->model_str_()), YESNO(this->external_vcc_), YESNO(this->flip_x_), YESNO(this->flip_y_),
|
||||||
this->offset_x_, this->offset_y_, YESNO(this->invert_));
|
this->offset_x_, this->offset_y_, YESNO(this->invert_));
|
||||||
LOG_I2C_DEVICE(this);
|
LOG_I2C_DEVICE(this);
|
||||||
LOG_PIN(" Reset Pin: ", this->reset_pin_);
|
LOG_PIN(" Reset Pin: ", this->reset_pin_);
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ void SPISSD1306::dump_config() {
|
|||||||
" Offset X: %d\n"
|
" Offset X: %d\n"
|
||||||
" Offset Y: %d\n"
|
" Offset Y: %d\n"
|
||||||
" Inverted Color: %s",
|
" Inverted Color: %s",
|
||||||
this->model_str_(), YESNO(this->external_vcc_), YESNO(this->flip_x_), YESNO(this->flip_y_),
|
LOG_STR_ARG(this->model_str_()), YESNO(this->external_vcc_), YESNO(this->flip_x_), YESNO(this->flip_y_),
|
||||||
this->offset_x_, this->offset_y_, YESNO(this->invert_));
|
this->offset_x_, this->offset_y_, YESNO(this->invert_));
|
||||||
LOG_PIN(" CS Pin: ", this->cs_);
|
LOG_PIN(" CS Pin: ", this->cs_);
|
||||||
LOG_PIN(" DC Pin: ", this->dc_pin_);
|
LOG_PIN(" DC Pin: ", this->dc_pin_);
|
||||||
|
|||||||
@@ -344,14 +344,15 @@ bool AsyncWebServerRequest::authenticate(const char *username, const char *passw
|
|||||||
memcpy(user_info + user_len + 1, password, pass_len);
|
memcpy(user_info + user_len + 1, password, pass_len);
|
||||||
user_info[user_info_len] = '\0';
|
user_info[user_info_len] = '\0';
|
||||||
|
|
||||||
size_t n = 0, out;
|
// Base64 output size is ceil(input_len * 4/3) + 1, with input bounded to 256 bytes
|
||||||
esp_crypto_base64_encode(nullptr, 0, &n, reinterpret_cast<const uint8_t *>(user_info), user_info_len);
|
// max output is ceil(256 * 4/3) + 1 = 343 bytes, use 350 for safety
|
||||||
|
constexpr size_t max_digest_len = 350;
|
||||||
auto digest = std::unique_ptr<char[]>(new char[n + 1]);
|
char digest[max_digest_len];
|
||||||
esp_crypto_base64_encode(reinterpret_cast<uint8_t *>(digest.get()), n, &out,
|
size_t out;
|
||||||
|
esp_crypto_base64_encode(reinterpret_cast<uint8_t *>(digest), max_digest_len, &out,
|
||||||
reinterpret_cast<const uint8_t *>(user_info), user_info_len);
|
reinterpret_cast<const uint8_t *>(user_info), user_info_len);
|
||||||
|
|
||||||
return strcmp(digest.get(), auth_str + auth_prefix_len) == 0;
|
return strcmp(digest, auth_str + auth_prefix_len) == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AsyncWebServerRequest::requestAuthentication(const char *realm) const {
|
void AsyncWebServerRequest::requestAuthentication(const char *realm) const {
|
||||||
@@ -861,12 +862,12 @@ esp_err_t AsyncWebServer::handle_multipart_upload_(httpd_req_t *r, const char *c
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Process data
|
// Process data - use stack buffer to avoid heap allocation
|
||||||
std::unique_ptr<char[]> buffer(new char[MULTIPART_CHUNK_SIZE]);
|
char buffer[MULTIPART_CHUNK_SIZE];
|
||||||
size_t bytes_since_yield = 0;
|
size_t bytes_since_yield = 0;
|
||||||
|
|
||||||
for (size_t remaining = r->content_len; remaining > 0;) {
|
for (size_t remaining = r->content_len; remaining > 0;) {
|
||||||
int recv_len = httpd_req_recv(r, buffer.get(), std::min(remaining, MULTIPART_CHUNK_SIZE));
|
int recv_len = httpd_req_recv(r, buffer, std::min(remaining, MULTIPART_CHUNK_SIZE));
|
||||||
|
|
||||||
if (recv_len <= 0) {
|
if (recv_len <= 0) {
|
||||||
httpd_resp_send_err(r, recv_len == HTTPD_SOCK_ERR_TIMEOUT ? HTTPD_408_REQ_TIMEOUT : HTTPD_400_BAD_REQUEST,
|
httpd_resp_send_err(r, recv_len == HTTPD_SOCK_ERR_TIMEOUT ? HTTPD_408_REQ_TIMEOUT : HTTPD_400_BAD_REQUEST,
|
||||||
@@ -874,7 +875,7 @@ esp_err_t AsyncWebServer::handle_multipart_upload_(httpd_req_t *r, const char *c
|
|||||||
return recv_len == HTTPD_SOCK_ERR_TIMEOUT ? ESP_ERR_TIMEOUT : ESP_FAIL;
|
return recv_len == HTTPD_SOCK_ERR_TIMEOUT ? ESP_ERR_TIMEOUT : ESP_FAIL;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (reader->parse(buffer.get(), recv_len) != static_cast<size_t>(recv_len)) {
|
if (reader->parse(buffer, recv_len) != static_cast<size_t>(recv_len)) {
|
||||||
ESP_LOGW(TAG, "Multipart parser error");
|
ESP_LOGW(TAG, "Multipart parser error");
|
||||||
httpd_resp_send_err(r, HTTPD_400_BAD_REQUEST, nullptr);
|
httpd_resp_send_err(r, HTTPD_400_BAD_REQUEST, nullptr);
|
||||||
return ESP_FAIL;
|
return ESP_FAIL;
|
||||||
|
|||||||
@@ -204,36 +204,40 @@ void Application::loop() {
|
|||||||
this->last_loop_ = last_op_end_time;
|
this->last_loop_ = last_op_end_time;
|
||||||
|
|
||||||
if (this->dump_config_at_ < this->components_.size()) {
|
if (this->dump_config_at_ < this->components_.size()) {
|
||||||
if (this->dump_config_at_ == 0) {
|
this->process_dump_config_();
|
||||||
char build_time_str[Application::BUILD_TIME_STR_SIZE];
|
}
|
||||||
this->get_build_time_string(build_time_str);
|
}
|
||||||
ESP_LOGI(TAG, "ESPHome version " ESPHOME_VERSION " compiled on %s", build_time_str);
|
|
||||||
|
void Application::process_dump_config_() {
|
||||||
|
if (this->dump_config_at_ == 0) {
|
||||||
|
char build_time_str[Application::BUILD_TIME_STR_SIZE];
|
||||||
|
this->get_build_time_string(build_time_str);
|
||||||
|
ESP_LOGI(TAG, "ESPHome version " ESPHOME_VERSION " compiled on %s", build_time_str);
|
||||||
#ifdef ESPHOME_PROJECT_NAME
|
#ifdef ESPHOME_PROJECT_NAME
|
||||||
ESP_LOGI(TAG, "Project " ESPHOME_PROJECT_NAME " version " ESPHOME_PROJECT_VERSION);
|
ESP_LOGI(TAG, "Project " ESPHOME_PROJECT_NAME " version " ESPHOME_PROJECT_VERSION);
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_ESP32
|
#ifdef USE_ESP32
|
||||||
esp_chip_info_t chip_info;
|
esp_chip_info_t chip_info;
|
||||||
esp_chip_info(&chip_info);
|
esp_chip_info(&chip_info);
|
||||||
ESP_LOGI(TAG, "ESP32 Chip: %s rev%d.%d, %d core(s)", ESPHOME_VARIANT, chip_info.revision / 100,
|
ESP_LOGI(TAG, "ESP32 Chip: %s rev%d.%d, %d core(s)", ESPHOME_VARIANT, chip_info.revision / 100,
|
||||||
chip_info.revision % 100, chip_info.cores);
|
chip_info.revision % 100, chip_info.cores);
|
||||||
#if defined(USE_ESP32_VARIANT_ESP32) && !defined(USE_ESP32_MIN_CHIP_REVISION_SET)
|
#if defined(USE_ESP32_VARIANT_ESP32) && !defined(USE_ESP32_MIN_CHIP_REVISION_SET)
|
||||||
// Suggest optimization for chips that don't need the PSRAM cache workaround
|
// Suggest optimization for chips that don't need the PSRAM cache workaround
|
||||||
if (chip_info.revision >= 300) {
|
if (chip_info.revision >= 300) {
|
||||||
#ifdef USE_PSRAM
|
#ifdef USE_PSRAM
|
||||||
ESP_LOGW(TAG, "Set minimum_chip_revision: \"%d.%d\" to save ~10KB IRAM", chip_info.revision / 100,
|
ESP_LOGW(TAG, "Set minimum_chip_revision: \"%d.%d\" to save ~10KB IRAM", chip_info.revision / 100,
|
||||||
chip_info.revision % 100);
|
chip_info.revision % 100);
|
||||||
#else
|
#else
|
||||||
ESP_LOGW(TAG, "Set minimum_chip_revision: \"%d.%d\" to reduce binary size", chip_info.revision / 100,
|
ESP_LOGW(TAG, "Set minimum_chip_revision: \"%d.%d\" to reduce binary size", chip_info.revision / 100,
|
||||||
chip_info.revision % 100);
|
chip_info.revision % 100);
|
||||||
#endif
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
this->components_[this->dump_config_at_]->call_dump_config();
|
#endif
|
||||||
this->dump_config_at_++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this->components_[this->dump_config_at_]->call_dump_config();
|
||||||
|
this->dump_config_at_++;
|
||||||
}
|
}
|
||||||
|
|
||||||
void IRAM_ATTR HOT Application::feed_wdt(uint32_t time) {
|
void IRAM_ATTR HOT Application::feed_wdt(uint32_t time) {
|
||||||
|
|||||||
@@ -519,6 +519,11 @@ class Application {
|
|||||||
void before_loop_tasks_(uint32_t loop_start_time);
|
void before_loop_tasks_(uint32_t loop_start_time);
|
||||||
void after_loop_tasks_();
|
void after_loop_tasks_();
|
||||||
|
|
||||||
|
/// Process dump_config output one component per loop iteration.
|
||||||
|
/// Extracted from loop() to keep cold startup/reconnect logging out of the hot path.
|
||||||
|
/// Caller must ensure dump_config_at_ < components_.size().
|
||||||
|
void __attribute__((noinline)) process_dump_config_();
|
||||||
|
|
||||||
void feed_wdt_arch_();
|
void feed_wdt_arch_();
|
||||||
|
|
||||||
/// Perform a delay while also monitoring socket file descriptors for readiness
|
/// Perform a delay while also monitoring socket file descriptors for readiness
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ class ComponentIterator {
|
|||||||
public:
|
public:
|
||||||
void begin(bool include_internal = false);
|
void begin(bool include_internal = false);
|
||||||
void advance();
|
void advance();
|
||||||
|
bool completed() const { return this->state_ == IteratorState::NONE; }
|
||||||
virtual bool on_begin();
|
virtual bool on_begin();
|
||||||
#ifdef USE_BINARY_SENSOR
|
#ifdef USE_BINARY_SENSOR
|
||||||
virtual bool on_binary_sensor(binary_sensor::BinarySensor *binary_sensor) = 0;
|
virtual bool on_binary_sensor(binary_sensor::BinarySensor *binary_sensor) = 0;
|
||||||
|
|||||||
@@ -107,6 +107,24 @@ static void validate_static_string(const char *name) {
|
|||||||
// iterating over them from the loop task is fine; but iterating from any other context requires the lock to be held to
|
// iterating over them from the loop task is fine; but iterating from any other context requires the lock to be held to
|
||||||
// avoid the main thread modifying the list while it is being accessed.
|
// avoid the main thread modifying the list while it is being accessed.
|
||||||
|
|
||||||
|
// Calculate random offset for interval timers
|
||||||
|
// Extracted from set_timer_common_ to reduce code size - float math + random_float()
|
||||||
|
// only needed for intervals, not timeouts
|
||||||
|
uint32_t Scheduler::calculate_interval_offset_(uint32_t delay) {
|
||||||
|
return static_cast<uint32_t>(std::min(delay / 2, MAX_INTERVAL_DELAY) * random_float());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if a retry was already cancelled in items_ or to_add_
|
||||||
|
// Extracted from set_timer_common_ to reduce code size - retry path is cold and deprecated
|
||||||
|
// Remove before 2026.8.0 along with all retry code
|
||||||
|
bool Scheduler::is_retry_cancelled_locked_(Component *component, NameType name_type, const char *static_name,
|
||||||
|
uint32_t hash_or_id) {
|
||||||
|
return has_cancelled_timeout_in_container_locked_(this->items_, component, name_type, static_name, hash_or_id,
|
||||||
|
/* match_retry= */ true) ||
|
||||||
|
has_cancelled_timeout_in_container_locked_(this->to_add_, component, name_type, static_name, hash_or_id,
|
||||||
|
/* match_retry= */ true);
|
||||||
|
}
|
||||||
|
|
||||||
// Common implementation for both timeout and interval
|
// Common implementation for both timeout and interval
|
||||||
// name_type determines storage type: STATIC_STRING uses static_name, others use hash_or_id
|
// name_type determines storage type: STATIC_STRING uses static_name, others use hash_or_id
|
||||||
void HOT Scheduler::set_timer_common_(Component *component, SchedulerItem::Type type, NameType name_type,
|
void HOT Scheduler::set_timer_common_(Component *component, SchedulerItem::Type type, NameType name_type,
|
||||||
@@ -130,84 +148,66 @@ void HOT Scheduler::set_timer_common_(Component *component, SchedulerItem::Type
|
|||||||
// Create and populate the scheduler item
|
// Create and populate the scheduler item
|
||||||
auto item = this->get_item_from_pool_locked_();
|
auto item = this->get_item_from_pool_locked_();
|
||||||
item->component = component;
|
item->component = component;
|
||||||
switch (name_type) {
|
item->set_name(name_type, static_name, hash_or_id);
|
||||||
case NameType::STATIC_STRING:
|
|
||||||
item->set_static_name(static_name);
|
|
||||||
break;
|
|
||||||
case NameType::HASHED_STRING:
|
|
||||||
item->set_hashed_name(hash_or_id);
|
|
||||||
break;
|
|
||||||
case NameType::NUMERIC_ID:
|
|
||||||
item->set_numeric_id(hash_or_id);
|
|
||||||
break;
|
|
||||||
case NameType::NUMERIC_ID_INTERNAL:
|
|
||||||
item->set_internal_id(hash_or_id);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
item->type = type;
|
item->type = type;
|
||||||
item->callback = std::move(func);
|
item->callback = std::move(func);
|
||||||
// Reset remove flag - recycled items may have been cancelled (remove=true) in previous use
|
// Reset remove flag - recycled items may have been cancelled (remove=true) in previous use
|
||||||
this->set_item_removed_(item.get(), false);
|
this->set_item_removed_(item.get(), false);
|
||||||
item->is_retry = is_retry;
|
item->is_retry = is_retry;
|
||||||
|
|
||||||
|
// Determine target container: defer_queue_ for deferred items, to_add_ for everything else.
|
||||||
|
// Using a pointer lets both paths share the cancel + push_back epilogue.
|
||||||
|
auto *target = &this->to_add_;
|
||||||
|
|
||||||
#ifndef ESPHOME_THREAD_SINGLE
|
#ifndef ESPHOME_THREAD_SINGLE
|
||||||
// Special handling for defer() (delay = 0, type = TIMEOUT)
|
// Special handling for defer() (delay = 0, type = TIMEOUT)
|
||||||
// Single-core platforms don't need thread-safe defer handling
|
// Single-core platforms don't need thread-safe defer handling
|
||||||
if (delay == 0 && type == SchedulerItem::TIMEOUT) {
|
if (delay == 0 && type == SchedulerItem::TIMEOUT) {
|
||||||
// Put in defer queue for guaranteed FIFO execution
|
// Put in defer queue for guaranteed FIFO execution
|
||||||
if (!skip_cancel) {
|
target = &this->defer_queue_;
|
||||||
this->cancel_item_locked_(component, name_type, static_name, hash_or_id, type);
|
} else
|
||||||
}
|
|
||||||
this->defer_queue_.push_back(std::move(item));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
#endif /* not ESPHOME_THREAD_SINGLE */
|
#endif /* not ESPHOME_THREAD_SINGLE */
|
||||||
|
{
|
||||||
// Type-specific setup
|
// Type-specific setup
|
||||||
if (type == SchedulerItem::INTERVAL) {
|
if (type == SchedulerItem::INTERVAL) {
|
||||||
item->interval = delay;
|
item->interval = delay;
|
||||||
// first execution happens immediately after a random smallish offset
|
// first execution happens immediately after a random smallish offset
|
||||||
// Calculate random offset (0 to min(interval/2, 5s))
|
uint32_t offset = this->calculate_interval_offset_(delay);
|
||||||
uint32_t offset = (uint32_t) (std::min(delay / 2, MAX_INTERVAL_DELAY) * random_float());
|
item->set_next_execution(now + offset);
|
||||||
item->set_next_execution(now + offset);
|
|
||||||
#ifdef ESPHOME_LOG_HAS_VERBOSE
|
#ifdef ESPHOME_LOG_HAS_VERBOSE
|
||||||
SchedulerNameLog name_log;
|
SchedulerNameLog name_log;
|
||||||
ESP_LOGV(TAG, "Scheduler interval for %s is %" PRIu32 "ms, offset %" PRIu32 "ms",
|
ESP_LOGV(TAG, "Scheduler interval for %s is %" PRIu32 "ms, offset %" PRIu32 "ms",
|
||||||
name_log.format(name_type, static_name, hash_or_id), delay, offset);
|
name_log.format(name_type, static_name, hash_or_id), delay, offset);
|
||||||
#endif
|
#endif
|
||||||
} else {
|
} else {
|
||||||
item->interval = 0;
|
item->interval = 0;
|
||||||
item->set_next_execution(now + delay);
|
item->set_next_execution(now + delay);
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef ESPHOME_DEBUG_SCHEDULER
|
#ifdef ESPHOME_DEBUG_SCHEDULER
|
||||||
this->debug_log_timer_(item.get(), name_type, static_name, hash_or_id, type, delay, now);
|
this->debug_log_timer_(item.get(), name_type, static_name, hash_or_id, type, delay, now);
|
||||||
#endif /* ESPHOME_DEBUG_SCHEDULER */
|
#endif /* ESPHOME_DEBUG_SCHEDULER */
|
||||||
|
|
||||||
// For retries, check if there's a cancelled timeout first
|
// For retries, check if there's a cancelled timeout first
|
||||||
// Skip check for anonymous retries (STATIC_STRING with nullptr) - they can't be cancelled by name
|
// Skip check for anonymous retries (STATIC_STRING with nullptr) - they can't be cancelled by name
|
||||||
if (is_retry && (name_type != NameType::STATIC_STRING || static_name != nullptr) && type == SchedulerItem::TIMEOUT &&
|
if (is_retry && (name_type != NameType::STATIC_STRING || static_name != nullptr) &&
|
||||||
(has_cancelled_timeout_in_container_locked_(this->items_, component, name_type, static_name, hash_or_id,
|
type == SchedulerItem::TIMEOUT &&
|
||||||
/* match_retry= */ true) ||
|
this->is_retry_cancelled_locked_(component, name_type, static_name, hash_or_id)) {
|
||||||
has_cancelled_timeout_in_container_locked_(this->to_add_, component, name_type, static_name, hash_or_id,
|
// Skip scheduling - the retry was cancelled
|
||||||
/* match_retry= */ true))) {
|
|
||||||
// Skip scheduling - the retry was cancelled
|
|
||||||
#ifdef ESPHOME_DEBUG_SCHEDULER
|
#ifdef ESPHOME_DEBUG_SCHEDULER
|
||||||
SchedulerNameLog skip_name_log;
|
SchedulerNameLog skip_name_log;
|
||||||
ESP_LOGD(TAG, "Skipping retry '%s' - found cancelled item",
|
ESP_LOGD(TAG, "Skipping retry '%s' - found cancelled item",
|
||||||
skip_name_log.format(name_type, static_name, hash_or_id));
|
skip_name_log.format(name_type, static_name, hash_or_id));
|
||||||
#endif
|
#endif
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If name is provided, do atomic cancel-and-add (unless skip_cancel is true)
|
// Common epilogue: atomic cancel-and-add (unless skip_cancel is true)
|
||||||
// Cancel existing items
|
|
||||||
if (!skip_cancel) {
|
if (!skip_cancel) {
|
||||||
this->cancel_item_locked_(component, name_type, static_name, hash_or_id, type);
|
this->cancel_item_locked_(component, name_type, static_name, hash_or_id, type);
|
||||||
}
|
}
|
||||||
// Add new item directly to to_add_
|
target->push_back(std::move(item));
|
||||||
// since we have the lock held
|
|
||||||
this->to_add_.push_back(std::move(item));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void HOT Scheduler::set_timeout(Component *component, const char *name, uint32_t timeout, std::function<void()> func) {
|
void HOT Scheduler::set_timeout(Component *component, const char *name, uint32_t timeout, std::function<void()> func) {
|
||||||
|
|||||||
@@ -219,28 +219,15 @@ class Scheduler {
|
|||||||
// Helper to get the name type
|
// Helper to get the name type
|
||||||
NameType get_name_type() const { return name_type_; }
|
NameType get_name_type() const { return name_type_; }
|
||||||
|
|
||||||
// Helper to set a static string name (no allocation)
|
// Set name storage: for STATIC_STRING stores the pointer, for all other types stores hash_or_id.
|
||||||
void set_static_name(const char *name) {
|
// Both union members occupy the same offset, so only one store is needed.
|
||||||
name_.static_name = name;
|
void set_name(NameType type, const char *static_name, uint32_t hash_or_id) {
|
||||||
name_type_ = NameType::STATIC_STRING;
|
if (type == NameType::STATIC_STRING) {
|
||||||
}
|
name_.static_name = static_name;
|
||||||
|
} else {
|
||||||
// Helper to set a hashed string name (hash computed from std::string)
|
name_.hash_or_id = hash_or_id;
|
||||||
void set_hashed_name(uint32_t hash) {
|
}
|
||||||
name_.hash_or_id = hash;
|
name_type_ = type;
|
||||||
name_type_ = NameType::HASHED_STRING;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper to set a numeric ID name
|
|
||||||
void set_numeric_id(uint32_t id) {
|
|
||||||
name_.hash_or_id = id;
|
|
||||||
name_type_ = NameType::NUMERIC_ID;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper to set an internal numeric ID (separate namespace from NUMERIC_ID)
|
|
||||||
void set_internal_id(uint32_t id) {
|
|
||||||
name_.hash_or_id = id;
|
|
||||||
name_type_ = NameType::NUMERIC_ID_INTERNAL;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool cmp(const std::unique_ptr<SchedulerItem> &a, const std::unique_ptr<SchedulerItem> &b);
|
static bool cmp(const std::unique_ptr<SchedulerItem> &a, const std::unique_ptr<SchedulerItem> &b);
|
||||||
@@ -355,6 +342,17 @@ class Scheduler {
|
|||||||
// Helper to perform full cleanup when too many items are cancelled
|
// Helper to perform full cleanup when too many items are cancelled
|
||||||
void full_cleanup_removed_items_();
|
void full_cleanup_removed_items_();
|
||||||
|
|
||||||
|
// Helper to calculate random offset for interval timers - extracted to reduce code size of set_timer_common_
|
||||||
|
// IMPORTANT: Must not be inlined - called only for intervals, keeping it out of the hot path saves flash.
|
||||||
|
uint32_t __attribute__((noinline)) calculate_interval_offset_(uint32_t delay);
|
||||||
|
|
||||||
|
// Helper to check if a retry was already cancelled - extracted to reduce code size of set_timer_common_
|
||||||
|
// Remove before 2026.8.0 along with all retry code.
|
||||||
|
// IMPORTANT: Must not be inlined - retry path is cold and deprecated.
|
||||||
|
// IMPORTANT: Caller must hold the scheduler lock before calling this function.
|
||||||
|
bool __attribute__((noinline))
|
||||||
|
is_retry_cancelled_locked_(Component *component, NameType name_type, const char *static_name, uint32_t hash_or_id);
|
||||||
|
|
||||||
#ifdef ESPHOME_DEBUG_SCHEDULER
|
#ifdef ESPHOME_DEBUG_SCHEDULER
|
||||||
// Helper for debug logging in set_timer_common_ - extracted to reduce code size
|
// Helper for debug logging in set_timer_common_ - extracted to reduce code size
|
||||||
void debug_log_timer_(const SchedulerItem *item, NameType name_type, const char *static_name, uint32_t hash_or_id,
|
void debug_log_timer_(const SchedulerItem *item, NameType name_type, const char *static_name, uint32_t hash_or_id,
|
||||||
|
|||||||
@@ -133,6 +133,8 @@ def run_platformio_cli(*args, **kwargs) -> str | int:
|
|||||||
)
|
)
|
||||||
# Suppress Python syntax warnings from third-party scripts during compilation
|
# Suppress Python syntax warnings from third-party scripts during compilation
|
||||||
os.environ.setdefault("PYTHONWARNINGS", "ignore::SyntaxWarning")
|
os.environ.setdefault("PYTHONWARNINGS", "ignore::SyntaxWarning")
|
||||||
|
# Increase uv retry count to handle transient network errors (default is 3)
|
||||||
|
os.environ.setdefault("UV_HTTP_RETRIES", "10")
|
||||||
cmd = ["platformio"] + list(args)
|
cmd = ["platformio"] + list(args)
|
||||||
|
|
||||||
if not CORE.verbose:
|
if not CORE.verbose:
|
||||||
|
|||||||
@@ -214,7 +214,7 @@ build_unflags =
|
|||||||
; This are common settings for the LibreTiny (all variants) using Arduino.
|
; This are common settings for the LibreTiny (all variants) using Arduino.
|
||||||
[common:libretiny-arduino]
|
[common:libretiny-arduino]
|
||||||
extends = common:arduino
|
extends = common:arduino
|
||||||
platform = https://github.com/libretiny-eu/libretiny.git#v1.11.0
|
platform = https://github.com/libretiny-eu/libretiny.git#v1.12.1
|
||||||
framework = arduino
|
framework = arduino
|
||||||
lib_compat_mode = soft
|
lib_compat_mode = soft
|
||||||
lib_deps =
|
lib_deps =
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ esp32:
|
|||||||
type: esp-idf
|
type: esp-idf
|
||||||
components:
|
components:
|
||||||
- espressif/mdns^1.8.2
|
- espressif/mdns^1.8.2
|
||||||
- name: espressif/esp_hosted
|
- name: espressif/button
|
||||||
ref: 2.7.0
|
ref: 4.1.5
|
||||||
advanced:
|
advanced:
|
||||||
enable_idf_experimental_features: yes
|
enable_idf_experimental_features: yes
|
||||||
disable_debug_stubs: true
|
disable_debug_stubs: true
|
||||||
|
|||||||
Reference in New Issue
Block a user