Merge branch 'posix_tz_proto' into remove_posix_tz_parser

This commit is contained in:
J. Nick Koston
2026-02-26 15:20:11 -10:00
162 changed files with 6877 additions and 5764 deletions

View File

@@ -6,8 +6,9 @@
- [ ] Bugfix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
- [ ] Developer breaking change (an API change that could break external components)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) — [policy](https://developers.esphome.io/contributing/code/#what-constitutes-a-c-breaking-change)
- [ ] Developer breaking change (an API change that could break external components) — [policy](https://developers.esphome.io/contributing/code/#what-is-considered-public-c-api)
- [ ] Undocumented C++ API change (removal or change of undocumented public methods that lambda users may depend on) — [policy](https://developers.esphome.io/contributing/code/#c-user-expectations)
- [ ] Code quality improvements to existing code or addition of tests
- [ ] Other

View File

@@ -27,6 +27,7 @@ module.exports = {
'new-feature',
'breaking-change',
'developer-breaking-change',
'undocumented-api-change',
'code-quality',
'deprecated-component'
],

View File

@@ -238,6 +238,7 @@ async function detectPRTemplateCheckboxes(context) {
{ pattern: /- \[x\] New feature \(non-breaking change which adds functionality\)/i, label: 'new-feature' },
{ pattern: /- \[x\] Breaking change \(fix or feature that would cause existing functionality to not work as expected\)/i, label: 'breaking-change' },
{ pattern: /- \[x\] Developer breaking change \(an API change that could break external components\)/i, label: 'developer-breaking-change' },
{ pattern: /- \[x\] Undocumented C\+\+ API change \(removal or change of undocumented public methods that lambda users may depend on\)/i, label: 'undocumented-api-change' },
{ pattern: /- \[x\] Code quality improvements to existing code or addition of tests/i, label: 'code-quality' }
];

View File

@@ -11,7 +11,7 @@ ci:
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.15.2
rev: v0.15.3
hooks:
# Run the linter.
- id: ruff

View File

@@ -199,12 +199,19 @@ void AcDimmer::setup() {
setTimer1Callback(&timer_interrupt);
#endif
#ifdef USE_ESP32
dimmer_timer = timer_begin(TIMER_FREQUENCY_HZ);
timer_attach_interrupt(dimmer_timer, &AcDimmerDataStore::s_timer_intr);
// For ESP32, we can't use dynamic interval calculation because the timerX functions
// are not callable from ISR (placed in flash storage).
// Here we just use an interrupt firing every 50 µs.
timer_alarm(dimmer_timer, TIMER_INTERVAL_US, true, 0);
if (dimmer_timer == nullptr) {
dimmer_timer = timer_begin(TIMER_FREQUENCY_HZ);
if (dimmer_timer == nullptr) {
ESP_LOGE(TAG, "Failed to create GPTimer for AC dimmer");
this->mark_failed();
return;
}
timer_attach_interrupt(dimmer_timer, &AcDimmerDataStore::s_timer_intr);
// For ESP32, we can't use dynamic interval calculation because the timerX functions
// are not callable from ISR (placed in flash storage).
// Here we just use an interrupt firing every 50 µs.
timer_alarm(dimmer_timer, TIMER_INTERVAL_US, true, 0);
}
#endif
}

View File

@@ -1017,6 +1017,7 @@ enum ClimateAction {
CLIMATE_ACTION_IDLE = 4;
CLIMATE_ACTION_DRYING = 5;
CLIMATE_ACTION_FAN = 6;
CLIMATE_ACTION_DEFROSTING = 7;
}
enum ClimatePreset {
CLIMATE_PRESET_NONE = 0;

View File

@@ -2,7 +2,6 @@
// See script/api_protobuf/api_protobuf.py
#pragma once
#include "esphome/core/defines.h"
#include "esphome/core/string_ref.h"
#include "proto.h"
@@ -123,6 +122,7 @@ enum ClimateAction : uint32_t {
CLIMATE_ACTION_IDLE = 4,
CLIMATE_ACTION_DRYING = 5,
CLIMATE_ACTION_FAN = 6,
CLIMATE_ACTION_DEFROSTING = 7,
};
enum ClimatePreset : uint32_t {
CLIMATE_PRESET_NONE = 0,

View File

@@ -0,0 +1,12 @@
// This file was automatically generated with a tool.
// See script/api_protobuf/api_protobuf.py
#pragma once
#include "esphome/core/defines.h"
#ifdef USE_BLUETOOTH_PROXY
#ifndef USE_API_VARINT64
#define USE_API_VARINT64
#endif
#endif
namespace esphome::api {} // namespace esphome::api

View File

@@ -335,6 +335,8 @@ template<> const char *proto_enum_to_string<enums::ClimateAction>(enums::Climate
return "CLIMATE_ACTION_DRYING";
case enums::CLIMATE_ACTION_FAN:
return "CLIMATE_ACTION_FAN";
case enums::CLIMATE_ACTION_DEFROSTING:
return "CLIMATE_ACTION_DEFROSTING";
default:
return "UNKNOWN";
}

View File

@@ -433,8 +433,8 @@ void APIServer::handle_action_response(uint32_t call_id, bool success, StringRef
#ifdef USE_API_HOMEASSISTANT_STATES
// Helper to add subscription (reduces duplication)
void APIServer::add_state_subscription_(const char *entity_id, const char *attribute, std::function<void(StringRef)> f,
bool once) {
void APIServer::add_state_subscription_(const char *entity_id, const char *attribute,
std::function<void(StringRef)> &&f, bool once) {
this->state_subs_.push_back(HomeAssistantStateSubscription{
.entity_id = entity_id, .attribute = attribute, .callback = std::move(f), .once = once,
// entity_id_dynamic_storage and attribute_dynamic_storage remain nullptr (no heap allocation)
@@ -443,7 +443,7 @@ void APIServer::add_state_subscription_(const char *entity_id, const char *attri
// Helper to add subscription with heap-allocated strings (reduces duplication)
void APIServer::add_state_subscription_(std::string entity_id, optional<std::string> attribute,
std::function<void(StringRef)> f, bool once) {
std::function<void(StringRef)> &&f, bool once) {
HomeAssistantStateSubscription sub;
// Allocate heap storage for the strings
sub.entity_id_dynamic_storage = std::make_unique<std::string>(std::move(entity_id));
@@ -463,29 +463,29 @@ void APIServer::add_state_subscription_(std::string entity_id, optional<std::str
// New const char* overload (for internal components - zero allocation)
void APIServer::subscribe_home_assistant_state(const char *entity_id, const char *attribute,
std::function<void(StringRef)> f) {
std::function<void(StringRef)> &&f) {
this->add_state_subscription_(entity_id, attribute, std::move(f), false);
}
void APIServer::get_home_assistant_state(const char *entity_id, const char *attribute,
std::function<void(StringRef)> f) {
std::function<void(StringRef)> &&f) {
this->add_state_subscription_(entity_id, attribute, std::move(f), true);
}
// std::string overload with StringRef callback (zero-allocation callback)
void APIServer::subscribe_home_assistant_state(std::string entity_id, optional<std::string> attribute,
std::function<void(StringRef)> f) {
std::function<void(StringRef)> &&f) {
this->add_state_subscription_(std::move(entity_id), std::move(attribute), std::move(f), false);
}
void APIServer::get_home_assistant_state(std::string entity_id, optional<std::string> attribute,
std::function<void(StringRef)> f) {
std::function<void(StringRef)> &&f) {
this->add_state_subscription_(std::move(entity_id), std::move(attribute), std::move(f), true);
}
// Legacy helper: wraps std::string callback and delegates to StringRef version
void APIServer::add_state_subscription_(std::string entity_id, optional<std::string> attribute,
std::function<void(const std::string &)> f, bool once) {
std::function<void(const std::string &)> &&f, bool once) {
// Wrap callback to convert StringRef -> std::string, then delegate
this->add_state_subscription_(std::move(entity_id), std::move(attribute),
std::function<void(StringRef)>([f = std::move(f)](StringRef state) { f(state.str()); }),
@@ -494,12 +494,12 @@ void APIServer::add_state_subscription_(std::string entity_id, optional<std::str
// Legacy std::string overload (for custom_api_device.h - converts StringRef to std::string)
void APIServer::subscribe_home_assistant_state(std::string entity_id, optional<std::string> attribute,
std::function<void(const std::string &)> f) {
std::function<void(const std::string &)> &&f) {
this->add_state_subscription_(std::move(entity_id), std::move(attribute), std::move(f), false);
}
void APIServer::get_home_assistant_state(std::string entity_id, optional<std::string> attribute,
std::function<void(const std::string &)> f) {
std::function<void(const std::string &)> &&f) {
this->add_state_subscription_(std::move(entity_id), std::move(attribute), std::move(f), true);
}

View File

@@ -201,20 +201,20 @@ class APIServer : public Component,
};
// New const char* overload (for internal components - zero allocation)
void subscribe_home_assistant_state(const char *entity_id, const char *attribute, std::function<void(StringRef)> f);
void get_home_assistant_state(const char *entity_id, const char *attribute, std::function<void(StringRef)> f);
void subscribe_home_assistant_state(const char *entity_id, const char *attribute, std::function<void(StringRef)> &&f);
void get_home_assistant_state(const char *entity_id, const char *attribute, std::function<void(StringRef)> &&f);
// std::string overload with StringRef callback (for custom_api_device.h with zero-allocation callback)
void subscribe_home_assistant_state(std::string entity_id, optional<std::string> attribute,
std::function<void(StringRef)> f);
std::function<void(StringRef)> &&f);
void get_home_assistant_state(std::string entity_id, optional<std::string> attribute,
std::function<void(StringRef)> f);
std::function<void(StringRef)> &&f);
// Legacy std::string overload (for custom_api_device.h - converts StringRef to std::string for callback)
void subscribe_home_assistant_state(std::string entity_id, optional<std::string> attribute,
std::function<void(const std::string &)> f);
std::function<void(const std::string &)> &&f);
void get_home_assistant_state(std::string entity_id, optional<std::string> attribute,
std::function<void(const std::string &)> f);
std::function<void(const std::string &)> &&f);
const std::vector<HomeAssistantStateSubscription> &get_state_subs() const;
#endif
@@ -241,13 +241,13 @@ class APIServer : public Component,
#endif // USE_API_NOISE
#ifdef USE_API_HOMEASSISTANT_STATES
// Helper methods to reduce code duplication
void add_state_subscription_(const char *entity_id, const char *attribute, std::function<void(StringRef)> f,
bool once);
void add_state_subscription_(std::string entity_id, optional<std::string> attribute, std::function<void(StringRef)> f,
void add_state_subscription_(const char *entity_id, const char *attribute, std::function<void(StringRef)> &&f,
bool once);
void add_state_subscription_(std::string entity_id, optional<std::string> attribute,
std::function<void(StringRef)> &&f, bool once);
// Legacy helper: wraps std::string callback and delegates to StringRef version
void add_state_subscription_(std::string entity_id, optional<std::string> attribute,
std::function<void(const std::string &)> f, bool once);
std::function<void(const std::string &)> &&f, bool once);
#endif // USE_API_HOMEASSISTANT_STATES
// No explicit close() needed — listen sockets have no active connections on
// failure/shutdown. Destructor handles fd cleanup (close or abort per platform).

View File

@@ -15,7 +15,7 @@ class APIConnection;
return this->client_->schedule_message_(entity, ResponseType::MESSAGE_TYPE, ResponseType::ESTIMATED_SIZE); \
}
class ListEntitiesIterator : public ComponentIterator {
class ListEntitiesIterator final : public ComponentIterator {
public:
ListEntitiesIterator(APIConnection *client);
#ifdef USE_BINARY_SENSOR

View File

@@ -7,6 +7,23 @@ namespace esphome::api {
static const char *const TAG = "api.proto";
#ifdef USE_API_VARINT64
optional<ProtoVarInt> ProtoVarInt::parse_wide(const uint8_t *buffer, uint32_t len, uint32_t *consumed,
uint32_t result32) {
uint64_t result64 = result32;
uint32_t limit = std::min(len, uint32_t(10));
for (uint32_t i = 4; i < limit; i++) {
uint8_t val = buffer[i];
result64 |= uint64_t(val & 0x7F) << (i * 7);
if ((val & 0x80) == 0) {
*consumed = i + 1;
return ProtoVarInt(result64);
}
}
return {};
}
#endif
uint32_t ProtoDecodableMessage::count_repeated_field(const uint8_t *buffer, size_t length, uint32_t target_field_id) {
uint32_t count = 0;
const uint8_t *ptr = buffer;

View File

@@ -1,5 +1,6 @@
#pragma once
#include "api_pb2_defines.h"
#include "esphome/core/component.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
@@ -110,59 +111,78 @@ class ProtoVarInt {
#endif
if (len == 0)
return {};
// Most common case: single-byte varint (values 0-127)
// Fast path: single-byte varints (0-127) are the most common case
// (booleans, small enums, field tags). Avoid loop overhead entirely.
if ((buffer[0] & 0x80) == 0) {
*consumed = 1;
return ProtoVarInt(buffer[0]);
}
// General case for multi-byte varints
// Since we know buffer[0]'s high bit is set, initialize with its value
uint64_t result = buffer[0] & 0x7F;
uint8_t bitpos = 7;
// A 64-bit varint is at most 10 bytes (ceil(64/7)). Reject overlong encodings
// to avoid undefined behavior from shifting uint64_t by >= 64 bits.
uint32_t max_len = std::min(len, uint32_t(10));
// Start from the second byte since we've already processed the first
for (uint32_t i = 1; i < max_len; i++) {
// 32-bit phase: process remaining bytes with native 32-bit shifts.
// Without USE_API_VARINT64: cover bytes 1-4 (shifts 7, 14, 21, 28) — the uint32_t
// shift at byte 4 (shift by 28) may lose bits 32-34, but those are always zero for valid uint32 values.
// With USE_API_VARINT64: cover bytes 1-3 (shifts 7, 14, 21) so parse_wide handles
// byte 4+ with full 64-bit arithmetic (avoids truncating values > UINT32_MAX).
uint32_t result32 = buffer[0] & 0x7F;
#ifdef USE_API_VARINT64
uint32_t limit = std::min(len, uint32_t(4));
#else
uint32_t limit = std::min(len, uint32_t(5));
#endif
for (uint32_t i = 1; i < limit; i++) {
uint8_t val = buffer[i];
result |= uint64_t(val & 0x7F) << uint64_t(bitpos);
bitpos += 7;
result32 |= uint32_t(val & 0x7F) << (i * 7);
if ((val & 0x80) == 0) {
*consumed = i + 1;
return ProtoVarInt(result);
return ProtoVarInt(result32);
}
}
return {}; // Incomplete or invalid varint
// 64-bit phase for remaining bytes (BLE addresses etc.)
#ifdef USE_API_VARINT64
return parse_wide(buffer, len, consumed, result32);
#else
return {};
#endif
}
#ifdef USE_API_VARINT64
protected:
/// Continue parsing varint bytes 4-9 with 64-bit arithmetic.
/// Separated to keep 64-bit shift code (__ashldi3 on 32-bit platforms) out of the common path.
static optional<ProtoVarInt> parse_wide(const uint8_t *buffer, uint32_t len, uint32_t *consumed, uint32_t result32)
__attribute__((noinline));
public:
#endif
constexpr uint16_t as_uint16() const { return this->value_; }
constexpr uint32_t as_uint32() const { return this->value_; }
constexpr uint64_t as_uint64() const { return this->value_; }
constexpr bool as_bool() const { return this->value_; }
constexpr int32_t as_int32() const {
// Not ZigZag encoded
return static_cast<int32_t>(this->as_int64());
}
constexpr int64_t as_int64() const {
// Not ZigZag encoded
return static_cast<int64_t>(this->value_);
return static_cast<int32_t>(this->value_);
}
constexpr int32_t as_sint32() const {
// with ZigZag encoding
return decode_zigzag32(static_cast<uint32_t>(this->value_));
}
#ifdef USE_API_VARINT64
constexpr uint64_t as_uint64() const { return this->value_; }
constexpr int64_t as_int64() const {
// Not ZigZag encoded
return static_cast<int64_t>(this->value_);
}
constexpr int64_t as_sint64() const {
// with ZigZag encoding
return decode_zigzag64(this->value_);
}
#endif
protected:
#ifdef USE_API_VARINT64
uint64_t value_;
#else
uint32_t value_;
#endif
};
// Forward declarations for decode_to_message, encode_message and encode_packed_sint32

View File

@@ -16,7 +16,7 @@ class APIConnection;
return this->client_->send_##entity_type##_state(entity); \
}
class InitialStateIterator : public ComponentIterator {
class InitialStateIterator final : public ComponentIterator {
public:
InitialStateIterator(APIConnection *client);
#ifdef USE_BINARY_SENSOR

View File

@@ -230,7 +230,7 @@ template<typename... Ts> class APIRespondAction : public Action<Ts...> {
void set_is_optional_mode(bool is_optional) { this->is_optional_mode_ = is_optional; }
#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES_JSON
void set_data(std::function<void(Ts..., JsonObject)> func) {
void set_data(std::function<void(Ts..., JsonObject)> &&func) {
this->json_builder_ = std::move(func);
this->has_data_ = true;
}

View File

@@ -52,12 +52,12 @@ void BL0942::loop() {
return;
}
if (avail < sizeof(buffer)) {
if (!this->rx_start_) {
if (!this->rx_start_.has_value()) {
this->rx_start_ = millis();
} else if (millis() > this->rx_start_ + PKT_TIMEOUT_MS) {
} else if (millis() - *this->rx_start_ > PKT_TIMEOUT_MS) {
ESP_LOGW(TAG, "Junk on wire. Throwing away partial message (%zu bytes)", avail);
this->read_array((uint8_t *) &buffer, avail);
this->rx_start_ = 0;
this->rx_start_.reset();
}
return;
}
@@ -67,7 +67,7 @@ void BL0942::loop() {
this->received_package_(&buffer);
}
}
this->rx_start_ = 0;
this->rx_start_.reset();
}
bool BL0942::validate_checksum_(DataPacket *data) {

View File

@@ -140,7 +140,7 @@ class BL0942 : public PollingComponent, public uart::UARTDevice {
uint8_t address_ = 0;
bool reset_ = false;
LineFrequency line_freq_ = LINE_FREQUENCY_50HZ;
uint32_t rx_start_ = 0;
optional<uint32_t> rx_start_{};
uint32_t prev_cf_cnt_ = 0;
bool validate_checksum_(DataPacket *data);

View File

@@ -101,7 +101,7 @@ class BLEPresenceDevice : public binary_sensor::BinarySensorInitiallyOff,
}
void loop() override {
if (this->found_ && this->last_seen_ + this->timeout_ < millis())
if (this->found_ && millis() - this->last_seen_ > this->timeout_)
this->set_found_(false);
}
void dump_config() override;

View File

@@ -242,6 +242,9 @@ void CC1101Component::begin_tx() {
if (this->gdo0_pin_ != nullptr) {
this->gdo0_pin_->pin_mode(gpio::FLAG_OUTPUT);
}
// Transition through IDLE to bypass CCA (Clear Channel Assessment) which can
// block TX entry when strobing from RX, and to ensure FS_AUTOCAL calibration
this->enter_idle_();
if (!this->enter_tx_()) {
ESP_LOGW(TAG, "Failed to enter TX state!");
}
@@ -252,6 +255,8 @@ void CC1101Component::begin_rx() {
if (this->gdo0_pin_ != nullptr) {
this->gdo0_pin_->pin_mode(gpio::FLAG_INPUT);
}
// Transition through IDLE to ensure FS_AUTOCAL calibration occurs
this->enter_idle_();
if (!this->enter_rx_()) {
ESP_LOGW(TAG, "Failed to enter RX state!");
}

View File

@@ -10,8 +10,10 @@ const LogString *climate_mode_to_string(ClimateMode mode) {
return ClimateModeStrings::get_log_str(static_cast<uint8_t>(mode), ClimateModeStrings::LAST_INDEX);
}
// Climate action strings indexed by ClimateAction enum (0,2-6): OFF, (gap), COOLING, HEATING, IDLE, DRYING, FAN
PROGMEM_STRING_TABLE(ClimateActionStrings, "OFF", "UNKNOWN", "COOLING", "HEATING", "IDLE", "DRYING", "FAN", "UNKNOWN");
// Climate action strings indexed by ClimateAction enum (0,2-7): OFF, (gap), COOLING, HEATING, IDLE, DRYING, FAN,
// DEFROSTING
PROGMEM_STRING_TABLE(ClimateActionStrings, "OFF", "UNKNOWN", "COOLING", "HEATING", "IDLE", "DRYING", "FAN",
"DEFROSTING", "UNKNOWN");
const LogString *climate_action_to_string(ClimateAction action) {
return ClimateActionStrings::get_log_str(static_cast<uint8_t>(action), ClimateActionStrings::LAST_INDEX);

View File

@@ -41,6 +41,8 @@ enum ClimateAction : uint8_t {
CLIMATE_ACTION_DRYING = 5,
/// The climate device is in fan only mode
CLIMATE_ACTION_FAN = 6,
/// The climate device is defrosting
CLIMATE_ACTION_DEFROSTING = 7,
};
/// NOTE: If adding values, update ClimateFanModeMask in climate_traits.h to use the new last value

View File

@@ -3,8 +3,7 @@
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
namespace esphome {
namespace cse7766 {
namespace esphome::cse7766 {
static const char *const TAG = "cse7766";
@@ -258,5 +257,4 @@ void CSE7766Component::dump_config() {
this->check_uart_settings(4800, 1, uart::UART_CONFIG_PARITY_EVEN);
}
} // namespace cse7766
} // namespace esphome
} // namespace esphome::cse7766

View File

@@ -5,8 +5,7 @@
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/uart/uart.h"
namespace esphome {
namespace cse7766 {
namespace esphome::cse7766 {
static constexpr size_t CSE7766_RAW_DATA_SIZE = 24;
@@ -49,5 +48,4 @@ class CSE7766Component : public Component, public uart::UARTDevice {
uint16_t cf_pulses_last_{0};
};
} // namespace cse7766
} // namespace esphome
} // namespace esphome::cse7766

View File

@@ -1,7 +1,5 @@
#include "dlms_meter.h"
#include <cmath>
#if defined(USE_ESP8266_FRAMEWORK_ARDUINO)
#include <bearssl/bearssl.h>
#elif defined(USE_ESP32)
@@ -410,7 +408,7 @@ void DlmsMeterComponent::decode_obis_(uint8_t *plaintext, uint16_t message_lengt
if (current_position + 1 < message_length) {
int8_t scaler = static_cast<int8_t>(plaintext[current_position + 1]);
if (scaler != 0) {
value *= powf(10.0f, scaler);
value *= pow10_int(scaler);
}
}

View File

@@ -790,8 +790,15 @@ def _detect_variant(value):
engineering_sample = value.get(CONF_ENGINEERING_SAMPLE)
if engineering_sample is None:
_LOGGER.warning(
"No board specified for ESP32-P4. Defaulting to production silicon (rev3). "
"If you have an early engineering sample (pre-rev3), set 'engineering_sample: true'."
"No board specified for ESP32-P4. Defaulting to production silicon (rev3).\n"
"If you have an early engineering sample (pre-rev3), add this to your config:\n"
"\n"
" esp32:\n"
" engineering_sample: true\n"
"\n"
"To check your chip revision, look for 'chip revision: vX.Y' in the boot log.\n"
"Engineering samples will show a revision below v3.0.\n"
"The 'debug:' component also reports the revision (e.g. Revision: 100 = v1.0, 300 = v3.0)."
)
elif engineering_sample:
value[CONF_BOARD] = "esp32-p4-evboard"

View File

@@ -24,6 +24,7 @@ from esphome.const import (
__version__ as ESPHOME_VERSION,
)
from esphome.core import CORE
import esphome.final_validate as fv
from esphome.schema_extractors import SCHEMA_EXTRACT
AUTO_LOAD = ["esp32_ble", "bytebuffer"]
@@ -42,6 +43,7 @@ CONF_FIRMWARE_VERSION = "firmware_version"
CONF_INDICATE = "indicate"
CONF_MANUFACTURER = "manufacturer"
CONF_MANUFACTURER_DATA = "manufacturer_data"
CONF_MAX_CLIENTS = "max_clients"
CONF_ON_WRITE = "on_write"
CONF_READ = "read"
CONF_STRING = "string"
@@ -287,6 +289,22 @@ def create_device_information_service(config):
def final_validate_config(config):
# Validate max_clients does not exceed esp32_ble max_connections
max_clients = config[CONF_MAX_CLIENTS]
if max_clients > 1:
full_config = fv.full_config.get()
ble_config = full_config.get("esp32_ble", {})
max_connections = ble_config.get(
"max_connections", esp32_ble.DEFAULT_MAX_CONNECTIONS
)
if max_clients > max_connections:
raise cv.Invalid(
f"'max_clients' ({max_clients}) cannot exceed esp32_ble "
f"'max_connections' ({max_connections}). "
f"Please set 'max_connections: {max_clients}' in the "
f"'esp32_ble' component."
)
# Check if all characteristics that require notifications have the notify property set
for char_id in CORE.data.get(DOMAIN, {}).get(KEY_NOTIFY_REQUIRED, set()):
# Look for the characteristic in the configuration
@@ -428,6 +446,7 @@ CONFIG_SCHEMA = cv.Schema(
cv.Optional(CONF_MODEL): value_schema("string", templatable=False),
cv.Optional(CONF_FIRMWARE_VERSION): value_schema("string", templatable=False),
cv.Optional(CONF_MANUFACTURER_DATA): cv.Schema([cv.uint8_t]),
cv.Optional(CONF_MAX_CLIENTS, default=1): cv.int_range(min=1, max=9),
cv.Optional(CONF_SERVICES, default=[]): cv.ensure_list(SERVICE_SCHEMA),
cv.Optional(CONF_ON_CONNECT): automation.validate_automation(single=True),
cv.Optional(CONF_ON_DISCONNECT): automation.validate_automation(single=True),
@@ -552,6 +571,7 @@ async def to_code(config):
esp32_ble.register_ble_status_event_handler(parent, var)
cg.add(var.set_parent(parent))
cg.add(parent.advertising_set_appearance(config[CONF_APPEARANCE]))
cg.add(var.set_max_clients(config[CONF_MAX_CLIENTS]))
if CONF_MANUFACTURER_DATA in config:
cg.add(var.set_manufacturer_data(config[CONF_MANUFACTURER_DATA]))
for service_config in config[CONF_SERVICES]:

View File

@@ -175,6 +175,10 @@ void BLEServer::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t ga
case ESP_GATTS_CONNECT_EVT: {
ESP_LOGD(TAG, "BLE Client connected");
this->add_client_(param->connect.conn_id);
// Resume advertising so additional clients can discover and connect
if (this->client_count_ < this->max_clients_) {
this->parent_->advertising_start();
}
this->dispatch_callbacks_(CallbackType::ON_CONNECT, param->connect.conn_id);
break;
}
@@ -241,7 +245,12 @@ void BLEServer::ble_before_disabled_event_handler() {
float BLEServer::get_setup_priority() const { return setup_priority::AFTER_BLUETOOTH + 10; }
void BLEServer::dump_config() { ESP_LOGCONFIG(TAG, "ESP32 BLE Server:"); }
void BLEServer::dump_config() {
ESP_LOGCONFIG(TAG,
"ESP32 BLE Server:\n"
" Max clients: %u",
this->max_clients_);
}
BLEServer *global_ble_server = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)

View File

@@ -39,6 +39,9 @@ class BLEServer : public Component, public GATTsEventHandler, public BLEStatusEv
this->restart_advertising_();
}
void set_max_clients(uint8_t max_clients) { this->max_clients_ = max_clients; }
uint8_t get_max_clients() const { return this->max_clients_; }
BLEService *create_service(ESPBTUUID uuid, bool advertise = false, uint16_t num_handles = 15);
void remove_service(ESPBTUUID uuid, uint8_t inst_id = 0);
BLEService *get_service(ESPBTUUID uuid, uint8_t inst_id = 0);
@@ -95,6 +98,7 @@ class BLEServer : public Component, public GATTsEventHandler, public BLEStatusEv
uint16_t clients_[USE_ESP32_BLE_MAX_CONNECTIONS]{};
uint8_t client_count_{0};
uint8_t max_clients_{1};
std::vector<ServiceEntry> services_{};
std::vector<BLEService *> services_to_start_{};
BLEService *device_information_service_{};

View File

@@ -13,22 +13,63 @@ esp_ldo_ns = cg.esphome_ns.namespace("esp_ldo")
EspLdo = esp_ldo_ns.class_("EspLdo", cg.Component)
AdjustAction = esp_ldo_ns.class_("AdjustAction", Action)
CHANNELS = (3, 4)
CHANNELS = (1, 2, 3, 4)
CHANNELS_INTERNAL = (1, 2)
CONF_ADJUSTABLE = "adjustable"
CONF_ALLOW_INTERNAL_CHANNEL = "allow_internal_channel"
CONF_PASSTHROUGH = "passthrough"
adjusted_ids = set()
def validate_ldo_voltage(value):
if isinstance(value, str) and value.lower() == CONF_PASSTHROUGH:
return CONF_PASSTHROUGH
value = cv.voltage(value)
if 0.5 <= value <= 2.7:
return value
raise cv.Invalid(
f"LDO voltage must be in range 0.5V-2.7V or 'passthrough' (bypass mode), got {value}V"
)
def validate_ldo_config(config):
channel = config[CONF_CHANNEL]
allow_internal = config[CONF_ALLOW_INTERNAL_CHANNEL]
if allow_internal and channel not in CHANNELS_INTERNAL:
raise cv.Invalid(
f"'{CONF_ALLOW_INTERNAL_CHANNEL}' is only valid for internal channels (1, 2). "
f"Channel {channel} is a user-configurable channel — its usage depends on your board schematic.",
path=[CONF_ALLOW_INTERNAL_CHANNEL],
)
if channel in CHANNELS_INTERNAL and not allow_internal:
raise cv.Invalid(
f"LDO channel {channel} is normally used internally by the chip (flash/PSRAM). "
f"Set '{CONF_ALLOW_INTERNAL_CHANNEL}: true' to confirm you know what you are doing.",
path=[CONF_CHANNEL],
)
if config[CONF_VOLTAGE] == CONF_PASSTHROUGH and config[CONF_ADJUSTABLE]:
raise cv.Invalid(
"Passthrough mode passes the supply voltage directly to the output and does not support "
"runtime voltage adjustment.",
path=[CONF_ADJUSTABLE],
)
return config
CONFIG_SCHEMA = cv.All(
cv.ensure_list(
cv.COMPONENT_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(EspLdo),
cv.Required(CONF_VOLTAGE): cv.All(
cv.voltage, cv.float_range(min=0.5, max=2.7)
),
cv.Required(CONF_CHANNEL): cv.one_of(*CHANNELS, int=True),
cv.Optional(CONF_ADJUSTABLE, default=False): cv.boolean,
}
cv.All(
cv.COMPONENT_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(EspLdo),
cv.Required(CONF_VOLTAGE): validate_ldo_voltage,
cv.Required(CONF_CHANNEL): cv.one_of(*CHANNELS, int=True),
cv.Optional(CONF_ADJUSTABLE, default=False): cv.boolean,
cv.Optional(CONF_ALLOW_INTERNAL_CHANNEL, default=False): cv.boolean,
}
),
validate_ldo_config,
)
),
cv.only_on_esp32,
@@ -40,7 +81,11 @@ async def to_code(configs):
for config in configs:
var = cg.new_Pvariable(config[CONF_ID], config[CONF_CHANNEL])
await cg.register_component(var, config)
cg.add(var.set_voltage(config[CONF_VOLTAGE]))
voltage = config[CONF_VOLTAGE]
if voltage == CONF_PASSTHROUGH:
cg.add(var.set_voltage(3300))
else:
cg.add(var.set_voltage(int(round(voltage * 1000))))
cg.add(var.set_adjustable(config[CONF_ADJUSTABLE]))

View File

@@ -10,32 +10,34 @@ static const char *const TAG = "esp_ldo";
void EspLdo::setup() {
esp_ldo_channel_config_t config{};
config.chan_id = this->channel_;
config.voltage_mv = (int) (this->voltage_ * 1000.0f);
config.voltage_mv = this->voltage_mv_;
config.flags.adjustable = this->adjustable_;
auto err = esp_ldo_acquire_channel(&config, &this->handle_);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to acquire LDO channel %d with voltage %fV", this->channel_, this->voltage_);
ESP_LOGE(TAG, "Failed to acquire LDO channel %d with voltage %dmV", this->channel_, this->voltage_mv_);
this->mark_failed(LOG_STR("Failed to acquire LDO channel"));
} else {
ESP_LOGD(TAG, "Acquired LDO channel %d with voltage %fV", this->channel_, this->voltage_);
ESP_LOGD(TAG, "Acquired LDO channel %d with voltage %dmV", this->channel_, this->voltage_mv_);
}
}
void EspLdo::dump_config() {
ESP_LOGCONFIG(TAG,
"ESP LDO Channel %d:\n"
" Voltage: %fV\n"
" Voltage: %dmV\n"
" Adjustable: %s",
this->channel_, this->voltage_, YESNO(this->adjustable_));
this->channel_, this->voltage_mv_, YESNO(this->adjustable_));
}
void EspLdo::adjust_voltage(float voltage) {
if (!std::isfinite(voltage) || voltage < 0.5f || voltage > 2.7f) {
ESP_LOGE(TAG, "Invalid voltage %fV for LDO channel %d", voltage, this->channel_);
ESP_LOGE(TAG, "Invalid voltage %fV for LDO channel %d (must be 0.5V-2.7V)", voltage, this->channel_);
return;
}
auto erro = esp_ldo_channel_adjust_voltage(this->handle_, (int) (voltage * 1000.0f));
if (erro != ESP_OK) {
ESP_LOGE(TAG, "Failed to adjust LDO channel %d to voltage %fV: %s", this->channel_, voltage, esp_err_to_name(erro));
int voltage_mv = (int) roundf(voltage * 1000.0f);
auto err = esp_ldo_channel_adjust_voltage(this->handle_, voltage_mv);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to adjust LDO channel %d to voltage %dmV: %s", this->channel_, voltage_mv,
esp_err_to_name(err));
}
}

View File

@@ -15,7 +15,7 @@ class EspLdo : public Component {
void dump_config() override;
void set_adjustable(bool adjustable) { this->adjustable_ = adjustable; }
void set_voltage(float voltage) { this->voltage_ = voltage; }
void set_voltage(int voltage_mv) { this->voltage_mv_ = voltage_mv; }
void adjust_voltage(float voltage);
float get_setup_priority() const override {
return setup_priority::BUS; // LDO setup should be done early
@@ -23,7 +23,7 @@ class EspLdo : public Component {
protected:
int channel_;
float voltage_{2.7};
int voltage_mv_{2700};
bool adjustable_{false};
esp_ldo_channel_handle_t handle_{};
};

View File

@@ -12,7 +12,7 @@
namespace esphome {
/// ESPHomeOTAComponent provides a simple way to integrate Over-the-Air updates into your app using ArduinoOTA.
class ESPHomeOTAComponent : public ota::OTAComponent {
class ESPHomeOTAComponent final : public ota::OTAComponent {
public:
enum class OTAState : uint8_t {
IDLE,

View File

@@ -19,8 +19,7 @@
#include <driver/spi_master.h>
#endif
namespace esphome {
namespace ethernet {
namespace esphome::ethernet {
#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 4, 2)
// work around IDF compile issue on P4 https://github.com/espressif/esp-idf/pull/15637
@@ -881,7 +880,6 @@ void EthernetComponent::write_phy_register_(esp_eth_mac_t *mac, PHYRegister regi
#endif
} // namespace ethernet
} // namespace esphome
} // namespace esphome::ethernet
#endif // USE_ESP32

View File

@@ -15,8 +15,7 @@
#include "esp_mac.h"
#include "esp_idf_version.h"
namespace esphome {
namespace ethernet {
namespace esphome::ethernet {
#ifdef USE_ETHERNET_IP_STATE_LISTENERS
/** Listener interface for Ethernet IP state changes.
@@ -218,7 +217,6 @@ extern EthernetComponent *global_eth_component;
extern "C" esp_eth_phy_t *esp_eth_phy_new_jl1101(const eth_phy_config_t *config);
#endif
} // namespace ethernet
} // namespace esphome
} // namespace esphome::ethernet
#endif // USE_ESP32

View File

@@ -6,12 +6,12 @@
namespace esphome {
namespace gp8403 {
enum GP8403Voltage {
enum GP8403Voltage : uint8_t {
GP8403_VOLTAGE_5V = 0x00,
GP8403_VOLTAGE_10V = 0x11,
};
enum GP8403Model {
enum GP8403Model : uint8_t {
GP8403,
GP8413,
};

View File

@@ -95,7 +95,7 @@ void HMC5883LComponent::update() {
float mg_per_bit;
switch (this->range_) {
case HMC5883L_RANGE_88_UT:
mg_per_bit = 0.073f;
mg_per_bit = 0.73f;
break;
case HMC5883L_RANGE_130_UT:
mg_per_bit = 0.92f;

View File

@@ -1,5 +1,7 @@
#include "ota_http_request.h"
#include <cctype>
#include "esphome/core/application.h"
#include "esphome/core/defines.h"
#include "esphome/core/log.h"
@@ -209,6 +211,26 @@ uint8_t OtaHttpRequestComponent::do_ota_() {
return ota::OTA_RESPONSE_OK;
}
// URL-encode characters that are not unreserved per RFC 3986 section 2.3.
// This is needed for embedding userinfo (username/password) in URLs safely.
static std::string url_encode(const std::string &str) {
std::string result;
result.reserve(str.size());
for (char c : str) {
if (std::isalnum(static_cast<unsigned char>(c)) || c == '-' || c == '_' || c == '.' || c == '~') {
result += c;
} else {
result += '%';
result += format_hex_pretty_char((static_cast<uint8_t>(c) >> 4) & 0x0F);
result += format_hex_pretty_char(static_cast<uint8_t>(c) & 0x0F);
}
}
return result;
}
void OtaHttpRequestComponent::set_password(const std::string &password) { this->password_ = url_encode(password); }
void OtaHttpRequestComponent::set_username(const std::string &username) { this->username_ = url_encode(username); }
std::string OtaHttpRequestComponent::get_url_with_auth_(const std::string &url) {
if (this->username_.empty() || this->password_.empty()) {
return url;

View File

@@ -22,16 +22,16 @@ enum OtaHttpRequestError : uint8_t {
OTA_CONNECTION_ERROR = 0x12,
};
class OtaHttpRequestComponent : public ota::OTAComponent, public Parented<HttpRequestComponent> {
class OtaHttpRequestComponent final : public ota::OTAComponent, public Parented<HttpRequestComponent> {
public:
void dump_config() override;
float get_setup_priority() const override { return setup_priority::AFTER_WIFI; }
void set_md5_url(const std::string &md5_url);
void set_md5(const std::string &md5) { this->md5_expected_ = md5; }
void set_password(const std::string &password) { this->password_ = password; }
void set_password(const std::string &password);
void set_url(const std::string &url);
void set_username(const std::string &username) { this->username_ = username; }
void set_username(const std::string &username);
std::string md5_computed() { return this->md5_computed_; }
std::string md5_expected() { return this->md5_expected_; }

View File

@@ -5,8 +5,7 @@
#include "esphome/core/log.h"
#include <memory>
namespace esphome {
namespace i2c {
namespace esphome::i2c {
static const char *const TAG = "i2c";
@@ -109,5 +108,4 @@ uint8_t I2CRegister16::get() const {
return value;
}
} // namespace i2c
} // namespace esphome
} // namespace esphome::i2c

View File

@@ -6,8 +6,7 @@
#include "esphome/core/optional.h"
#include "i2c_bus.h"
namespace esphome {
namespace i2c {
namespace esphome::i2c {
#define LOG_I2C_DEVICE(this) ESP_LOGCONFIG(TAG, " Address: 0x%02X", this->address_);
@@ -272,5 +271,4 @@ class I2CDevice {
I2CBus *bus_{nullptr}; ///< pointer to I2CBus instance
};
} // namespace i2c
} // namespace esphome
} // namespace esphome::i2c

View File

@@ -6,8 +6,7 @@
#include "esphome/core/helpers.h"
namespace esphome {
namespace i2c {
namespace esphome::i2c {
/// @brief Error codes returned by I2CBus and I2CDevice methods
enum ErrorCode {
@@ -69,5 +68,4 @@ class InternalI2CBus : public I2CBus {
virtual int get_port() const = 0;
};
} // namespace i2c
} // namespace esphome
} // namespace esphome::i2c

View File

@@ -7,8 +7,7 @@
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
namespace esphome {
namespace i2c {
namespace esphome::i2c {
static const char *const TAG = "i2c.arduino";
@@ -262,7 +261,6 @@ void ArduinoI2CBus::recover_() {
recovery_result_ = RECOVERY_COMPLETED;
}
} // namespace i2c
} // namespace esphome
} // namespace esphome::i2c
#endif // defined(USE_ARDUINO) && !defined(USE_ESP32)

View File

@@ -6,8 +6,7 @@
#include "esphome/core/component.h"
#include "i2c_bus.h"
namespace esphome {
namespace i2c {
namespace esphome::i2c {
enum RecoveryCode {
RECOVERY_FAILED_SCL_LOW,
@@ -45,7 +44,6 @@ class ArduinoI2CBus : public InternalI2CBus, public Component {
bool initialized_ = false;
};
} // namespace i2c
} // namespace esphome
} // namespace esphome::i2c
#endif // defined(USE_ARDUINO) && !defined(USE_ESP32)

View File

@@ -10,8 +10,7 @@
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
namespace esphome {
namespace i2c {
namespace esphome::i2c {
static const char *const TAG = "i2c.idf";
@@ -312,6 +311,5 @@ void IDFI2CBus::recover_() {
recovery_result_ = RECOVERY_COMPLETED;
}
} // namespace i2c
} // namespace esphome
} // namespace esphome::i2c
#endif // USE_ESP32

View File

@@ -6,8 +6,7 @@
#include "i2c_bus.h"
#include <driver/i2c_master.h>
namespace esphome {
namespace i2c {
namespace esphome::i2c {
enum RecoveryCode {
RECOVERY_FAILED_SCL_LOW,
@@ -56,7 +55,6 @@ class IDFI2CBus : public InternalI2CBus, public Component {
#endif
};
} // namespace i2c
} // namespace esphome
} // namespace esphome::i2c
#endif // USE_ESP32

View File

@@ -222,11 +222,11 @@ void KamstrupKMPComponent::parse_command_message_(uint16_t command, const uint8_
}
// Calculate exponent
float exponent = msg[6] & 0x3F;
int8_t exp_val = msg[6] & 0x3F;
if (msg[6] & 0x40) {
exponent = -exponent;
exp_val = -exp_val;
}
exponent = powf(10, exponent);
float exponent = pow10_int(exp_val);
if (msg[6] & 0x80) {
exponent = -exponent;
}

View File

@@ -45,7 +45,7 @@ void LCDDisplay::setup() {
// TODO dotsize
// Commands can only be sent 40ms after boot-up, so let's wait if we're close
const uint8_t now = millis();
const uint32_t now = millis();
if (now < 40)
delay(40u - now);

View File

@@ -560,8 +560,6 @@ void LD2420Component::read_batch_(std::span<uint8_t, MAX_LINE_LENGTH> buffer) {
void LD2420Component::handle_ack_data_(uint8_t *buffer, int len) {
this->cmd_reply_.command = buffer[CMD_FRAME_COMMAND];
this->cmd_reply_.length = buffer[CMD_FRAME_DATA_LENGTH];
uint8_t reg_element = 0;
uint8_t data_element = 0;
uint16_t data_pos = 0;
if (this->cmd_reply_.length > CMD_MAX_BYTES) {
ESP_LOGW(TAG, "Reply frame too long");
@@ -583,43 +581,44 @@ void LD2420Component::handle_ack_data_(uint8_t *buffer, int len) {
case (CMD_DISABLE_CONF):
ESP_LOGV(TAG, "Set config disable: CMD = %2X %s", CMD_DISABLE_CONF, result);
break;
case (CMD_READ_REGISTER):
case (CMD_READ_REGISTER): {
ESP_LOGV(TAG, "Read register: CMD = %2X %s", CMD_READ_REGISTER, result);
// TODO Read/Write register is not implemented yet, this will get flushed out to a proper header file
data_pos = 0x0A;
for (uint16_t index = 0; index < (CMD_REG_DATA_REPLY_SIZE * // NOLINT
((buffer[CMD_FRAME_DATA_LENGTH] - 4) / CMD_REG_DATA_REPLY_SIZE));
index += CMD_REG_DATA_REPLY_SIZE) {
memcpy(&this->cmd_reply_.data[reg_element], &buffer[data_pos + index], sizeof(CMD_REG_DATA_REPLY_SIZE));
byteswap(this->cmd_reply_.data[reg_element]);
reg_element++;
uint16_t reg_count = std::min<uint16_t>((buffer[CMD_FRAME_DATA_LENGTH] - 4) / CMD_REG_DATA_REPLY_SIZE,
sizeof(this->cmd_reply_.data) / sizeof(this->cmd_reply_.data[0]));
for (uint16_t i = 0; i < reg_count; i++) {
memcpy(&this->cmd_reply_.data[i], &buffer[data_pos + i * CMD_REG_DATA_REPLY_SIZE], CMD_REG_DATA_REPLY_SIZE);
}
break;
}
case (CMD_WRITE_REGISTER):
ESP_LOGV(TAG, "Write register: CMD = %2X %s", CMD_WRITE_REGISTER, result);
break;
case (CMD_WRITE_ABD_PARAM):
ESP_LOGV(TAG, "Write gate parameter(s): %2X %s", CMD_WRITE_ABD_PARAM, result);
break;
case (CMD_READ_ABD_PARAM):
case (CMD_READ_ABD_PARAM): {
ESP_LOGV(TAG, "Read gate parameter(s): %2X %s", CMD_READ_ABD_PARAM, result);
data_pos = CMD_ABD_DATA_REPLY_START;
for (uint16_t index = 0; index < (CMD_ABD_DATA_REPLY_SIZE * // NOLINT
((buffer[CMD_FRAME_DATA_LENGTH] - 4) / CMD_ABD_DATA_REPLY_SIZE));
index += CMD_ABD_DATA_REPLY_SIZE) {
memcpy(&this->cmd_reply_.data[data_element], &buffer[data_pos + index],
sizeof(this->cmd_reply_.data[data_element]));
byteswap(this->cmd_reply_.data[data_element]);
data_element++;
uint16_t abd_count = std::min<uint16_t>((buffer[CMD_FRAME_DATA_LENGTH] - 4) / CMD_ABD_DATA_REPLY_SIZE,
sizeof(this->cmd_reply_.data) / sizeof(this->cmd_reply_.data[0]));
for (uint16_t i = 0; i < abd_count; i++) {
memcpy(&this->cmd_reply_.data[i], &buffer[data_pos + i * CMD_ABD_DATA_REPLY_SIZE],
sizeof(this->cmd_reply_.data[i]));
}
break;
}
case (CMD_WRITE_SYS_PARAM):
ESP_LOGV(TAG, "Set system parameter(s): %2X %s", CMD_WRITE_SYS_PARAM, result);
break;
case (CMD_READ_VERSION):
memcpy(this->firmware_ver_, &buffer[12], buffer[10]);
ESP_LOGV(TAG, "Firmware version: %7s %s", this->firmware_ver_, result);
case (CMD_READ_VERSION): {
uint8_t ver_len = std::min<uint8_t>(buffer[10], sizeof(this->firmware_ver_) - 1);
memcpy(this->firmware_ver_, &buffer[12], ver_len);
this->firmware_ver_[ver_len] = '\0';
ESP_LOGV(TAG, "Firmware version: %s %s", this->firmware_ver_, result);
break;
}
default:
break;
}
@@ -729,9 +728,9 @@ void LD2420Component::set_reg_value(uint16_t reg, uint16_t value) {
cmd_frame.data_length = 0;
cmd_frame.header = CMD_FRAME_HEADER;
cmd_frame.command = CMD_WRITE_REGISTER;
memcpy(&cmd_frame.data[cmd_frame.data_length], &reg, sizeof(CMD_REG_DATA_REPLY_SIZE));
memcpy(&cmd_frame.data[cmd_frame.data_length], &reg, CMD_REG_DATA_REPLY_SIZE);
cmd_frame.data_length += 2;
memcpy(&cmd_frame.data[cmd_frame.data_length], &value, sizeof(CMD_REG_DATA_REPLY_SIZE));
memcpy(&cmd_frame.data[cmd_frame.data_length], &value, CMD_REG_DATA_REPLY_SIZE);
cmd_frame.data_length += 2;
cmd_frame.footer = CMD_FRAME_FOOTER;
ESP_LOGV(TAG, "Sending write register %4X command: %2X data = %4X", reg, cmd_frame.command, value);

View File

@@ -27,6 +27,7 @@ from esphome.storage_json import StorageJSON
from . import gpio # noqa
from .const import (
COMPONENT_BK72XX,
CONF_GPIO_RECOVER,
CONF_LOGLEVEL,
CONF_SDK_SILENT,
@@ -453,7 +454,14 @@ async def component_to_code(config):
cg.add_platformio_option("lib_ldf_mode", "off")
cg.add_platformio_option("lib_compat_mode", "soft")
# include <Arduino.h> in every file
cg.add_platformio_option("build_src_flags", "-include Arduino.h")
build_src_flags = "-include Arduino.h"
if FAMILY_COMPONENT[config[CONF_FAMILY]] == COMPONENT_BK72XX:
# LibreTiny forces -O1 globally for BK72xx because the Beken SDK
# has issues with higher optimization levels. However, ESPHome code
# works fine with -Os (used on every other platform), so override
# it for project source files only. GCC uses the last -O flag.
build_src_flags += " -Os"
cg.add_platformio_option("build_src_flags", build_src_flags)
# dummy version code
cg.add_define("USE_ARDUINO_VERSION_CODE", cg.RawExpression("VERSION_CODE(0, 0, 0)"))
# decrease web server stack size (16k words -> 4k words)

View File

@@ -4,8 +4,6 @@
#include <Arduino.h>
namespace esphome {
namespace libretiny {} // namespace libretiny
} // namespace esphome
namespace esphome::libretiny {} // namespace esphome::libretiny
#endif // USE_LIBRETINY

View File

@@ -3,8 +3,7 @@
#include "gpio_arduino.h"
#include "esphome/core/log.h"
namespace esphome {
namespace libretiny {
namespace esphome::libretiny {
static const char *const TAG = "lt.gpio";
@@ -77,7 +76,9 @@ void ArduinoInternalGPIOPin::detach_interrupt() const {
detachInterrupt(pin_); // NOLINT
}
} // namespace libretiny
} // namespace esphome::libretiny
namespace esphome {
using namespace libretiny;

View File

@@ -3,8 +3,7 @@
#ifdef USE_LIBRETINY
#include "esphome/core/hal.h"
namespace esphome {
namespace libretiny {
namespace esphome::libretiny {
class ArduinoInternalGPIOPin : public InternalGPIOPin {
public:
@@ -31,7 +30,6 @@ class ArduinoInternalGPIOPin : public InternalGPIOPin {
gpio::Flags flags_{};
};
} // namespace libretiny
} // namespace esphome
} // namespace esphome::libretiny
#endif // USE_LIBRETINY

View File

@@ -4,8 +4,7 @@
#include "esphome/core/log.h"
namespace esphome {
namespace libretiny {
namespace esphome::libretiny {
static const char *const TAG = "lt.component";
@@ -15,6 +14,9 @@ void LTComponent::dump_config() {
" Version: %s\n"
" Loglevel: %u",
LT_BANNER_STR + 10, LT_LOGLEVEL);
#if defined(__OPTIMIZE_SIZE__) && __OPTIMIZE_LEVEL__ > 0 && __OPTIMIZE_LEVEL__ <= 3
ESP_LOGCONFIG(TAG, " Optimization: -Os, SDK: -O" STRINGIFY_MACRO(__OPTIMIZE_LEVEL__));
#endif
#ifdef USE_TEXT_SENSOR
if (this->version_ != nullptr) {
@@ -25,7 +27,6 @@ void LTComponent::dump_config() {
float LTComponent::get_setup_priority() const { return setup_priority::LATE; }
} // namespace libretiny
} // namespace esphome
} // namespace esphome::libretiny
#endif // USE_LIBRETINY

View File

@@ -12,8 +12,7 @@
#include "esphome/components/text_sensor/text_sensor.h"
#endif
namespace esphome {
namespace libretiny {
namespace esphome::libretiny {
class LTComponent : public Component {
public:
@@ -30,7 +29,6 @@ class LTComponent : public Component {
#endif // USE_TEXT_SENSOR
};
} // namespace libretiny
} // namespace esphome
} // namespace esphome::libretiny
#endif // USE_LIBRETINY

View File

@@ -8,8 +8,7 @@
#include <cstring>
#include <memory>
namespace esphome {
namespace libretiny {
namespace esphome::libretiny {
static const char *const TAG = "lt.preferences";
@@ -194,7 +193,9 @@ void setup_preferences() {
global_preferences = &s_preferences;
}
} // namespace libretiny
} // namespace esphome::libretiny
namespace esphome {
ESPPreferences *global_preferences; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)

View File

@@ -2,12 +2,10 @@
#ifdef USE_LIBRETINY
namespace esphome {
namespace libretiny {
namespace esphome::libretiny {
void setup_preferences();
} // namespace libretiny
} // namespace esphome
} // namespace esphome::libretiny
#endif // USE_LIBRETINY

View File

@@ -1,27 +1,30 @@
#include "light_color_values.h"
#include <cmath>
namespace esphome::light {
// Lightweight lerp: a + t * (b - a).
// Avoids std::lerp's NaN/infinity handling which Clang doesn't optimize out,
// adding ~200 bytes per call. Safe because all values are finite floats.
static float __attribute__((noinline)) lerp_fast(float a, float b, float t) { return a + t * (b - a); }
LightColorValues LightColorValues::lerp(const LightColorValues &start, const LightColorValues &end, float completion) {
// Directly interpolate the raw values to avoid getter/setter overhead.
// This is safe because:
// - All LightColorValues have their values clamped when set via the setters
// - std::lerp guarantees output is in the same range as inputs
// - All LightColorValues except color_temperature_ have their values clamped when set via the setters
// - lerp_fast output stays in range when inputs are in range and 0 <= completion <= 1
// - Therefore the output doesn't need clamping, so we can skip the setters
LightColorValues v;
v.color_mode_ = end.color_mode_;
v.state_ = std::lerp(start.state_, end.state_, completion);
v.brightness_ = std::lerp(start.brightness_, end.brightness_, completion);
v.color_brightness_ = std::lerp(start.color_brightness_, end.color_brightness_, completion);
v.red_ = std::lerp(start.red_, end.red_, completion);
v.green_ = std::lerp(start.green_, end.green_, completion);
v.blue_ = std::lerp(start.blue_, end.blue_, completion);
v.white_ = std::lerp(start.white_, end.white_, completion);
v.color_temperature_ = std::lerp(start.color_temperature_, end.color_temperature_, completion);
v.cold_white_ = std::lerp(start.cold_white_, end.cold_white_, completion);
v.warm_white_ = std::lerp(start.warm_white_, end.warm_white_, completion);
v.state_ = lerp_fast(start.state_, end.state_, completion);
v.brightness_ = lerp_fast(start.brightness_, end.brightness_, completion);
v.color_brightness_ = lerp_fast(start.color_brightness_, end.color_brightness_, completion);
v.red_ = lerp_fast(start.red_, end.red_, completion);
v.green_ = lerp_fast(start.green_, end.green_, completion);
v.blue_ = lerp_fast(start.blue_, end.blue_, completion);
v.white_ = lerp_fast(start.white_, end.white_, completion);
v.color_temperature_ = lerp_fast(start.color_temperature_, end.color_temperature_, completion);
v.cold_white_ = lerp_fast(start.cold_white_, end.cold_white_, completion);
v.warm_white_ = lerp_fast(start.warm_white_, end.warm_white_, completion);
return v;
}

View File

@@ -44,12 +44,11 @@ class LightTransformer {
/// The progress of this transition, on a scale of 0 to 1.
float get_progress_() {
uint32_t now = esphome::millis();
if (now < this->start_time_)
return 0.0f;
if (now >= this->start_time_ + this->length_)
uint32_t elapsed = now - this->start_time_;
if (elapsed >= this->length_)
return 1.0f;
return clamp((now - this->start_time_) / float(this->length_), 0.0f, 1.0f);
return clamp(elapsed / float(this->length_), 0.0f, 1.0f);
}
uint32_t start_time_;

View File

@@ -78,7 +78,7 @@ class LightFlashTransformer : public LightTransformer {
optional<LightColorValues> apply() override {
optional<LightColorValues> result = {};
if (this->transformer_ == nullptr && millis() > this->start_time_ + this->length_ - this->transition_length_) {
if (this->transformer_ == nullptr && millis() - this->start_time_ > this->length_ - this->transition_length_) {
// second transition back to start value
this->transformer_ = this->state_.get_output()->create_default_transition();
this->transformer_->setup(this->state_.current_values, this->get_start_values(), this->transition_length_);

View File

@@ -31,13 +31,12 @@ void LightWaveRF::read_tx() {
void LightWaveRF::send_rx(const std::vector<uint8_t> &msg, uint8_t repeats, bool inverted, int u_sec) {
this->lwtx_.lwtx_setup(pin_tx_, repeats, inverted, u_sec);
uint32_t timeout = 0;
uint32_t timeout = millis();
if (this->lwtx_.lwtx_free()) {
this->lwtx_.lwtx_send(msg);
timeout = millis();
ESP_LOGD(TAG, "[%i] msg start", timeout);
}
while (!this->lwtx_.lwtx_free() && millis() < (timeout + 1000)) {
while (!this->lwtx_.lwtx_free() && millis() - timeout < 1000) {
delay(10);
}
timeout = millis() - timeout;

View File

@@ -141,7 +141,7 @@ enum UARTSelection : uint8_t {
* 2. Works with ESP-IDF's pthread implementation that uses a linked list for TLS variables
* 3. Avoids the limitations of the fixed FreeRTOS task local storage slots
*/
class Logger : public Component {
class Logger final : public Component {
public:
explicit Logger(uint32_t baud_rate);
#ifdef USE_ESPHOME_TASK_LOG_BUFFER
@@ -481,7 +481,7 @@ class Logger : public Component {
};
extern Logger *global_logger; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
class LoggerMessageTrigger : public Trigger<uint8_t, const char *, const char *> {
class LoggerMessageTrigger final : public Trigger<uint8_t, const char *, const char *> {
public:
explicit LoggerMessageTrigger(Logger *parent, uint8_t level) : level_(level) {
parent->add_log_callback(this,

View File

@@ -133,8 +133,8 @@ uint8_t MCP2515::get_status_() {
canbus::Error MCP2515::set_mode_(const CanctrlReqopMode mode) {
modify_register_(MCP_CANCTRL, CANCTRL_REQOP, mode);
uint32_t end_time = millis() + 10;
while (millis() < end_time) {
uint32_t start_time = millis();
while (millis() - start_time < 10) {
if ((read_register_(MCP_CANSTAT) & CANSTAT_OPMOD) == mode)
return canbus::ERROR_OK;
}

View File

@@ -40,7 +40,7 @@ struct MDNSService {
FixedVector<MDNSTXTRecord> txt_records;
};
class MDNSComponent : public Component {
class MDNSComponent final : public Component {
public:
void setup() override;
void dump_config() override;

View File

@@ -39,6 +39,7 @@ import esphome.config_validation as cv
from esphome.const import (
CONF_COLOR_ORDER,
CONF_DIMENSIONS,
CONF_DISABLED,
CONF_ENABLE_PIN,
CONF_ID,
CONF_INIT_SEQUENCE,
@@ -88,14 +89,17 @@ COLOR_DEPTHS = {
def model_schema(config):
model = MODELS[config[CONF_MODEL].upper()]
model.defaults[CONF_SWAP_XY] = cv.UNDEFINED
transform = cv.Schema(
{
cv.Required(CONF_MIRROR_X): cv.boolean,
cv.Required(CONF_MIRROR_Y): cv.boolean,
cv.Optional(CONF_SWAP_XY): cv.invalid(
"Axis swapping not supported by DSI displays"
),
}
transform = cv.Any(
cv.Schema(
{
cv.Required(CONF_MIRROR_X): cv.boolean,
cv.Required(CONF_MIRROR_Y): cv.boolean,
cv.Optional(CONF_SWAP_XY): cv.invalid(
"Axis swapping not supported by DSI displays"
),
}
),
cv.one_of(CONF_DISABLED, lower=True),
)
# CUSTOM model will need to provide a custom init sequence
iseqconf = (
@@ -199,9 +203,9 @@ async def to_code(config):
cg.add(var.set_vsync_pulse_width(config[CONF_VSYNC_PULSE_WIDTH]))
cg.add(var.set_vsync_back_porch(config[CONF_VSYNC_BACK_PORCH]))
cg.add(var.set_vsync_front_porch(config[CONF_VSYNC_FRONT_PORCH]))
cg.add(var.set_pclk_frequency(int(config[CONF_PCLK_FREQUENCY] / 1e6)))
cg.add(var.set_pclk_frequency(config[CONF_PCLK_FREQUENCY] / 1.0e6))
cg.add(var.set_lanes(int(config[CONF_LANES])))
cg.add(var.set_lane_bit_rate(int(config[CONF_LANE_BIT_RATE] / 1e6)))
cg.add(var.set_lane_bit_rate(config[CONF_LANE_BIT_RATE] / 1.0e6))
if reset_pin := config.get(CONF_RESET_PIN):
reset = await cg.gpio_pin_expression(reset_pin)
cg.add(var.set_reset_pin(reset))

View File

@@ -374,7 +374,7 @@ void MIPI_DSI::dump_config() {
"\n Swap X/Y: %s"
"\n Rotation: %d degrees"
"\n DSI Lanes: %u"
"\n Lane Bit Rate: %uMbps"
"\n Lane Bit Rate: %.0fMbps"
"\n HSync Pulse Width: %u"
"\n HSync Back Porch: %u"
"\n HSync Front Porch: %u"
@@ -385,7 +385,7 @@ void MIPI_DSI::dump_config() {
"\n Display Pixel Mode: %d bit"
"\n Color Order: %s"
"\n Invert Colors: %s"
"\n Pixel Clock: %dMHz",
"\n Pixel Clock: %.1fMHz",
this->model_, this->width_, this->height_, YESNO(this->madctl_ & (MADCTL_XFLIP | MADCTL_MX)),
YESNO(this->madctl_ & (MADCTL_YFLIP | MADCTL_MY)), YESNO(this->madctl_ & MADCTL_MV), this->rotation_,
this->lanes_, this->lane_bit_rate_, this->hsync_pulse_width_, this->hsync_back_porch_,

View File

@@ -47,7 +47,7 @@ class MIPI_DSI : public display::Display {
void set_reset_pin(GPIOPin *reset_pin) { this->reset_pin_ = reset_pin; }
void set_enable_pins(std::vector<GPIOPin *> enable_pins) { this->enable_pins_ = std::move(enable_pins); }
void set_pclk_frequency(uint32_t pclk_frequency) { this->pclk_frequency_ = pclk_frequency; }
void set_pclk_frequency(float pclk_frequency) { this->pclk_frequency_ = pclk_frequency; }
int get_width_internal() override { return this->width_; }
int get_height_internal() override { return this->height_; }
void set_hsync_back_porch(uint16_t hsync_back_porch) { this->hsync_back_porch_ = hsync_back_porch; }
@@ -58,7 +58,7 @@ class MIPI_DSI : public display::Display {
void set_vsync_front_porch(uint16_t vsync_front_porch) { this->vsync_front_porch_ = vsync_front_porch; }
void set_init_sequence(const std::vector<uint8_t> &init_sequence) { this->init_sequence_ = init_sequence; }
void set_model(const char *model) { this->model_ = model; }
void set_lane_bit_rate(uint16_t lane_bit_rate) { this->lane_bit_rate_ = lane_bit_rate; }
void set_lane_bit_rate(float lane_bit_rate) { this->lane_bit_rate_ = lane_bit_rate; }
void set_lanes(uint8_t lanes) { this->lanes_ = lanes; }
void set_madctl(uint8_t madctl) { this->madctl_ = madctl; }
@@ -95,9 +95,9 @@ class MIPI_DSI : public display::Display {
uint16_t vsync_front_porch_ = 10;
const char *model_{"Unknown"};
std::vector<uint8_t> init_sequence_{};
uint16_t pclk_frequency_ = 16; // in MHz
uint16_t lane_bit_rate_{1500}; // in Mbps
uint8_t lanes_{2}; // 1, 2, 3 or 4 lanes
float pclk_frequency_ = 16; // in MHz
float lane_bit_rate_{1500}; // in Mbps
uint8_t lanes_{2}; // 1, 2, 3 or 4 lanes
bool invert_colors_{};
display::ColorOrder color_mode_{display::COLOR_ORDER_BGR};

View File

@@ -2,7 +2,11 @@ from esphome.components.mipi import DriverChip
import esphome.config_validation as cv
# fmt: off
DriverChip(
# Source for parameters and initsequence:
# https://github.com/waveshareteam/Waveshare-ESP32-components/tree/master/display/lcd/esp_lcd_jd9365_10_1
# Product page: https://www.waveshare.com/wiki/ESP32-P4-Nano-StartPage
JD9365_10_1_DSI_TOUCH_A = DriverChip(
"WAVESHARE-P4-NANO-10.1",
height=1280,
width=800,
@@ -52,6 +56,15 @@ DriverChip(
],
)
# Standalone display
# Product page: https://www.waveshare.com/wiki/10.1-DSI-TOUCH-A
JD9365_10_1_DSI_TOUCH_A.extend(
"WAVESHARE-10.1-DSI-TOUCH-A",
)
# Source for parameters and initsequence:
# https://github.com/espressif/esp-iot-solution/tree/master/components/display/lcd/esp_lcd_st7703
# Product page: https://www.waveshare.com/wiki/ESP32-P4-86-Panel-ETH-2RO
DriverChip(
"WAVESHARE-P4-86-PANEL",
height=720,
@@ -95,6 +108,9 @@ DriverChip(
],
)
# Source for parameters and initsequence:
# https://github.com/espressif/esp-iot-solution/tree/master/components/display/lcd/esp_lcd_ek79007
# Product page: https://www.waveshare.com/wiki/ESP32-P4-WIFI6-Touch-LCD-7B
DriverChip(
"WAVESHARE-ESP32-P4-WIFI6-TOUCH-LCD-7B",
height=600,
@@ -121,7 +137,10 @@ DriverChip(
],
)
DriverChip(
# Source for parameters and initsequence:
# https://github.com/waveshareteam/Waveshare-ESP32-components/tree/master/display/lcd/esp_lcd_jd9365
# Product page: https://www.waveshare.com/wiki/ESP32-P4-WIFI6-Touch-LCD-3.4C
JD9365_3_4_DSI_TOUCH_C = DriverChip(
"WAVESHARE-ESP32-P4-WIFI6-TOUCH-LCD-3.4C",
height=800,
width=800,
@@ -170,7 +189,16 @@ DriverChip(
],
)
DriverChip(
# Standalone display
# Product page: https://www.waveshare.com/wiki/3.4-DSI-TOUCH-C
JD9365_3_4_DSI_TOUCH_C.extend(
"WAVESHARE-3.4-DSI-TOUCH-C",
)
# Source for parameters and initsequence:
# https://github.com/waveshareteam/Waveshare-ESP32-components/tree/master/display/lcd/esp_lcd_jd9365
# Product page: https://www.waveshare.com/wiki/ESP32-P4-WIFI6-Touch-LCD-4C
JD9365_4_DSI_TOUCH_C = DriverChip(
"WAVESHARE-ESP32-P4-WIFI6-TOUCH-LCD-4C",
height=720,
width=720,
@@ -218,3 +246,108 @@ DriverChip(
(0xE0, 0x00), # select userpage
]
)
# Standalone display
# Product page: https://www.waveshare.com/wiki/4-DSI-TOUCH-C
JD9365_4_DSI_TOUCH_C.extend(
"WAVESHARE-4-DSI-TOUCH-C",
)
# Source for parameters and initsequence:
# https://github.com/waveshareteam/Waveshare-ESP32-components/tree/master/display/lcd/esp_lcd_jd9365
# Product page: https://www.waveshare.com/wiki/8-DSI-TOUCH-A
DriverChip(
"WAVESHARE-8-DSI-TOUCH-A",
height=1280,
width=800,
hsync_back_porch=20,
hsync_pulse_width=20,
hsync_front_porch=40,
vsync_back_porch=12,
vsync_pulse_width=4,
vsync_front_porch=30,
pclk_frequency="80MHz",
lane_bit_rate="1.5Gbps",
swap_xy=cv.UNDEFINED,
color_order="RGB",
initsequence=[
(0xE0, 0x00), # select userpage
(0xE1, 0x93), (0xE2, 0x65), (0xE3, 0xF8),
(0x80, 0x01), # Select number of lanes (2)
(0xE0, 0x01), # select page 1
(0x00, 0x00), (0x01, 0x4E), (0x03, 0x00), (0x04, 0x65), (0x0C, 0x74), (0x17, 0x00), (0x18, 0xB7), (0x19, 0x00),
(0x1A, 0x00), (0x1B, 0xB7), (0x1C, 0x00), (0x24, 0xFE), (0x37, 0x19), (0x38, 0x05), (0x39, 0x00), (0x3A, 0x01),
(0x3B, 0x01), (0x3C, 0x70), (0x3D, 0xFF), (0x3E, 0xFF), (0x3F, 0xFF), (0x40, 0x06), (0x41, 0xA0), (0x43, 0x1E),
(0x44, 0x0F), (0x45, 0x28), (0x4B, 0x04), (0x55, 0x02), (0x56, 0x01), (0x57, 0xA9), (0x58, 0x0A), (0x59, 0x0A),
(0x5A, 0x37), (0x5B, 0x19), (0x5D, 0x78), (0x5E, 0x63), (0x5F, 0x54), (0x60, 0x49), (0x61, 0x45), (0x62, 0x38),
(0x63, 0x3D), (0x64, 0x28), (0x65, 0x43), (0x66, 0x41), (0x67, 0x43), (0x68, 0x62), (0x69, 0x50), (0x6A, 0x57),
(0x6B, 0x49), (0x6C, 0x44), (0x6D, 0x37), (0x6E, 0x23), (0x6F, 0x10), (0x70, 0x78), (0x71, 0x63), (0x72, 0x54),
(0x73, 0x49), (0x74, 0x45), (0x75, 0x38), (0x76, 0x3D), (0x77, 0x28), (0x78, 0x43), (0x79, 0x41), (0x7A, 0x43),
(0x7B, 0x62), (0x7C, 0x50), (0x7D, 0x57), (0x7E, 0x49), (0x7F, 0x44), (0x80, 0x37), (0x81, 0x23), (0x82, 0x10),
(0xE0, 0x02), # select page 2
(0x00, 0x47), (0x01, 0x47), (0x02, 0x45), (0x03, 0x45), (0x04, 0x4B), (0x05, 0x4B), (0x06, 0x49), (0x07, 0x49),
(0x08, 0x41), (0x09, 0x1F), (0x0A, 0x1F), (0x0B, 0x1F), (0x0C, 0x1F), (0x0D, 0x1F), (0x0E, 0x1F), (0x0F, 0x5F),
(0x10, 0x5F), (0x11, 0x57), (0x12, 0x77), (0x13, 0x35), (0x14, 0x1F), (0x15, 0x1F), (0x16, 0x46), (0x17, 0x46),
(0x18, 0x44), (0x19, 0x44), (0x1A, 0x4A), (0x1B, 0x4A), (0x1C, 0x48), (0x1D, 0x48), (0x1E, 0x40), (0x1F, 0x1F),
(0x20, 0x1F), (0x21, 0x1F), (0x22, 0x1F), (0x23, 0x1F), (0x24, 0x1F), (0x25, 0x5F), (0x26, 0x5F), (0x27, 0x57),
(0x28, 0x77), (0x29, 0x35), (0x2A, 0x1F), (0x2B, 0x1F), (0x58, 0x40), (0x59, 0x00), (0x5A, 0x00), (0x5B, 0x10),
(0x5C, 0x06), (0x5D, 0x40), (0x5E, 0x01), (0x5F, 0x02), (0x60, 0x30), (0x61, 0x01), (0x62, 0x02), (0x63, 0x03),
(0x64, 0x6B), (0x65, 0x05), (0x66, 0x0C), (0x67, 0x73), (0x68, 0x09), (0x69, 0x03), (0x6A, 0x56), (0x6B, 0x08),
(0x6C, 0x00), (0x6D, 0x04), (0x6E, 0x04), (0x6F, 0x88), (0x70, 0x00), (0x71, 0x00), (0x72, 0x06), (0x73, 0x7B),
(0x74, 0x00), (0x75, 0xF8), (0x76, 0x00), (0x77, 0xD5), (0x78, 0x2E), (0x79, 0x12), (0x7A, 0x03), (0x7B, 0x00),
(0x7C, 0x00), (0x7D, 0x03), (0x7E, 0x7B),
(0xE0, 0x04), # select page 4
(0x00, 0x0E), (0x02, 0xB3), (0x09, 0x60), (0x0E, 0x2A), (0x36, 0x59), (0x37, 0x58), (0x2B, 0x0F),
(0xE0, 0x00), # select userpage
]
)
# Source for parameters and initsequence:
# https://github.com/waveshareteam/Waveshare-ESP32-components/tree/master/display/lcd/esp_lcd_ili9881c
# Product page: https://www.waveshare.com/wiki/7-DSI-TOUCH-A
DriverChip(
"WAVESHARE-7-DSI-TOUCH-A",
height=1280,
width=720,
hsync_back_porch=239,
hsync_pulse_width=50,
hsync_front_porch=33,
vsync_back_porch=20,
vsync_pulse_width=30,
vsync_front_porch=2,
pclk_frequency="80MHz",
lane_bit_rate="1000Mbps",
no_transform=True,
color_order="RGB",
initsequence=[
(0xFF, 0x98, 0x81, 0x03),
(0x01, 0x00), (0x02, 0x00), (0x03, 0x73), (0x04, 0x00), (0x05, 0x00), (0x06, 0x0A), (0x07, 0x00), (0x08, 0x00),
(0x09, 0x61), (0x0A, 0x00), (0x0B, 0x00), (0x0C, 0x01), (0x0D, 0x00), (0x0E, 0x00), (0x0F, 0x61), (0x10, 0x61),
(0x11, 0x00), (0x12, 0x00), (0x13, 0x00), (0x14, 0x00), (0x15, 0x00), (0x16, 0x00), (0x17, 0x00), (0x18, 0x00),
(0x19, 0x00), (0x1A, 0x00), (0x1B, 0x00), (0x1C, 0x00), (0x1D, 0x00), (0x1E, 0x40), (0x1F, 0x80), (0x20, 0x06),
(0x21, 0x01), (0x22, 0x00), (0x23, 0x00), (0x24, 0x00), (0x25, 0x00), (0x26, 0x00), (0x27, 0x00), (0x28, 0x33),
(0x29, 0x03), (0x2A, 0x00), (0x2B, 0x00), (0x2C, 0x00), (0x2D, 0x00), (0x2E, 0x00), (0x2F, 0x00), (0x30, 0x00),
(0x31, 0x00), (0x32, 0x00), (0x33, 0x00), (0x34, 0x04), (0x35, 0x00), (0x36, 0x00), (0x37, 0x00), (0x38, 0x3C),
(0x39, 0x00), (0x3A, 0x00), (0x3B, 0x00), (0x3C, 0x00), (0x3D, 0x00), (0x3E, 0x00), (0x3F, 0x00), (0x40, 0x00),
(0x41, 0x00), (0x42, 0x00), (0x43, 0x00), (0x44, 0x00), (0x50, 0x10), (0x51, 0x32), (0x52, 0x54), (0x53, 0x76),
(0x54, 0x98), (0x55, 0xBA), (0x56, 0x10), (0x57, 0x32), (0x58, 0x54), (0x59, 0x76), (0x5A, 0x98), (0x5B, 0xBA),
(0x5C, 0xDC), (0x5D, 0xFE), (0x5E, 0x00), (0x5F, 0x0E), (0x60, 0x0F), (0x61, 0x0C), (0x62, 0x0D), (0x63, 0x06),
(0x64, 0x07), (0x65, 0x02), (0x66, 0x02), (0x67, 0x02), (0x68, 0x02), (0x69, 0x01), (0x6A, 0x00), (0x6B, 0x02),
(0x6C, 0x15), (0x6D, 0x14), (0x6E, 0x02), (0x6F, 0x02), (0x70, 0x02), (0x71, 0x02), (0x72, 0x02), (0x73, 0x02),
(0x74, 0x02), (0x75, 0x0E), (0x76, 0x0F), (0x77, 0x0C), (0x78, 0x0D), (0x79, 0x06), (0x7A, 0x07), (0x7B, 0x02),
(0x7C, 0x02), (0x7D, 0x02), (0x7E, 0x02), (0x7F, 0x01), (0x80, 0x00), (0x81, 0x02), (0x82, 0x14), (0x83, 0x15),
(0x84, 0x02), (0x85, 0x02), (0x86, 0x02), (0x87, 0x02), (0x88, 0x02), (0x89, 0x02), (0x8A, 0x02),
(0xFF, 0x98, 0x81, 0x04),
(0x38, 0x01), (0x39, 0x00), (0x6C, 0x15), (0x6E, 0x2A), (0x6F, 0x33), (0x3A, 0x94), (0x8D, 0x14), (0x87, 0xBA),
(0x26, 0x76), (0xB2, 0xD1), (0xB5, 0x06), (0x3B, 0x98),
(0xFF, 0x98, 0x81, 0x01),
(0x22, 0x0A), (0x31, 0x00), (0x53, 0x71), (0x55, 0x8F), (0x40, 0x33), (0x50, 0x96), (0x51, 0x96), (0x60, 0x23),
(0xA0, 0x08), (0xA1, 0x1D), (0xA2, 0x2A), (0xA3, 0x10), (0xA4, 0x15), (0xA5, 0x28), (0xA6, 0x1C), (0xA7, 0x1D),
(0xA8, 0x7E), (0xA9, 0x1D), (0xAA, 0x29), (0xAB, 0x6B), (0xAC, 0x1A), (0xAD, 0x18), (0xAE, 0x4B), (0xAF, 0x20),
(0xB0, 0x27), (0xB1, 0x50), (0xB2, 0x64), (0xB3, 0x39), (0xC0, 0x08), (0xC1, 0x1D), (0xC2, 0x2A), (0xC3, 0x10),
(0xC4, 0x15), (0xC5, 0x28), (0xC6, 0x1C), (0xC7, 0x1D), (0xC8, 0x7E), (0xC9, 0x1D), (0xCA, 0x29), (0xCB, 0x6B),
(0xCC, 0x1A), (0xCD, 0x18), (0xCE, 0x4B), (0xCF, 0x20), (0xD0, 0x27), (0xD1, 0x50), (0xD2, 0x64), (0xD3, 0x39),
(0xFF, 0x98, 0x81, 0x00),
(0x3A, 0x77), (0x36, 0x00), (0x35, 0x00), (0x35, 0x00),
],
)

View File

@@ -20,9 +20,10 @@ static ProgmemStr climate_mode_to_mqtt_str(ClimateMode mode) {
return ClimateMqttModeStrings::get_progmem_str(static_cast<uint8_t>(mode), ClimateMqttModeStrings::LAST_INDEX);
}
// Climate action MQTT strings indexed by ClimateAction enum (0,2-6): OFF, (gap), COOLING, HEATING, IDLE, DRYING, FAN
// Climate action MQTT strings indexed by ClimateAction enum (0,2-7): OFF, (gap), COOLING, HEATING, IDLE, DRYING, FAN,
// DEFROSTING
PROGMEM_STRING_TABLE(ClimateMqttActionStrings, "off", "unknown", "cooling", "heating", "idle", "drying", "fan",
"unknown");
"defrosting", "unknown");
static ProgmemStr climate_action_to_mqtt_str(ClimateAction action) {
return ClimateMqttActionStrings::get_progmem_str(static_cast<uint8_t>(action), ClimateMqttActionStrings::LAST_INDEX);

View File

@@ -37,8 +37,7 @@ using ip4_addr_t = in_addr;
#include <esp_netif.h>
#endif
namespace esphome {
namespace network {
namespace esphome::network {
/// Buffer size for IP address string (IPv6 max: 39 chars + null)
static constexpr size_t IP_ADDRESS_BUFFER_SIZE = 40;
@@ -187,6 +186,5 @@ struct IPAddress {
using IPAddresses = std::array<IPAddress, 5>;
} // namespace network
} // namespace esphome
} // namespace esphome::network
#endif

View File

@@ -17,8 +17,7 @@
#include "esphome/components/modem/modem_component.h"
#endif
namespace esphome {
namespace network {
namespace esphome::network {
// The order of the components is important: WiFi should come after any possible main interfaces (it may be used as
// an AP that use a previous interface for NAT).
@@ -109,6 +108,5 @@ const char *get_use_address() {
#endif
}
} // namespace network
} // namespace esphome
} // namespace esphome::network
#endif

View File

@@ -4,8 +4,7 @@
#include <string>
#include "ip_address.h"
namespace esphome {
namespace network {
namespace esphome::network {
/// Return whether the node is connected to the network (through wifi, eth, ...)
bool is_connected();
@@ -15,6 +14,5 @@ bool is_disabled();
const char *get_use_address();
IPAddresses get_ip_addresses();
} // namespace network
} // namespace esphome
} // namespace esphome::network
#endif

View File

@@ -39,7 +39,7 @@ std::string get_random_ha_tag_ndef() {
for (int i = 0; i < 12; i++) {
uri += ALPHANUM[random_uint32() % (sizeof(ALPHANUM) - 1)];
}
ESP_LOGD("pn7160", "Payload to be written: %s", uri.c_str());
ESP_LOGD(TAG, "Payload to be written: %s", uri.c_str());
return uri;
}

View File

@@ -7,7 +7,7 @@
namespace esphome {
namespace ota {
class ArduinoLibreTinyOTABackend : public OTABackend {
class ArduinoLibreTinyOTABackend final : public OTABackend {
public:
OTAResponseTypes begin(size_t image_size) override;
void set_update_md5(const char *md5) override;

View File

@@ -9,7 +9,7 @@
namespace esphome {
namespace ota {
class ArduinoRP2040OTABackend : public OTABackend {
class ArduinoRP2040OTABackend final : public OTABackend {
public:
OTAResponseTypes begin(size_t image_size) override;
void set_update_md5(const char *md5) override;

View File

@@ -12,7 +12,7 @@ namespace esphome::ota {
/// OTA backend for ESP8266 using native SDK functions.
/// This implementation bypasses the Arduino Updater library to save ~228 bytes of RAM
/// by not having a global Update object in .bss.
class ESP8266OTABackend : public OTABackend {
class ESP8266OTABackend final : public OTABackend {
public:
OTAResponseTypes begin(size_t image_size) override;
void set_update_md5(const char *md5) override;

View File

@@ -10,7 +10,7 @@
namespace esphome {
namespace ota {
class IDFOTABackend : public OTABackend {
class IDFOTABackend final : public OTABackend {
public:
OTAResponseTypes begin(size_t image_size) override;
void set_update_md5(const char *md5) override;

View File

@@ -7,7 +7,7 @@ namespace esphome::ota {
/// Stub OTA backend for host platform - allows compilation but does not implement OTA.
/// All operations return error codes immediately. This enables configurations with
/// OTA triggers to compile for host platform during development.
class HostOTABackend : public OTABackend {
class HostOTABackend final : public OTABackend {
public:
OTAResponseTypes begin(size_t image_size) override;
void set_update_md5(const char *md5) override;

View File

@@ -50,8 +50,8 @@ CONFIG_SCHEMA = cv.All(
cv.Optional(CONF_HEAT_OUTPUT): cv.use_id(output.FloatOutput),
cv.Optional(CONF_DEADBAND_PARAMETERS): cv.Schema(
{
cv.Required(CONF_THRESHOLD_HIGH): cv.temperature,
cv.Required(CONF_THRESHOLD_LOW): cv.temperature,
cv.Required(CONF_THRESHOLD_HIGH): cv.temperature_delta,
cv.Required(CONF_THRESHOLD_LOW): cv.temperature_delta,
cv.Optional(CONF_KP_MULTIPLIER, default=0.1): cv.float_,
cv.Optional(CONF_KI_MULTIPLIER, default=0.0): cv.float_,
cv.Optional(CONF_KD_MULTIPLIER, default=0.0): cv.float_,

View File

@@ -308,13 +308,13 @@ void PN532::send_nack_() {
enum PN532ReadReady PN532::read_ready_(bool block) {
if (this->rd_ready_ == READY) {
if (block) {
this->rd_start_time_ = 0;
this->rd_start_time_.reset();
this->rd_ready_ = WOULDBLOCK;
}
return READY;
}
if (!this->rd_start_time_) {
if (!this->rd_start_time_.has_value()) {
this->rd_start_time_ = millis();
}
@@ -324,7 +324,7 @@ enum PN532ReadReady PN532::read_ready_(bool block) {
break;
}
if (millis() - this->rd_start_time_ > 100) {
if (millis() - *this->rd_start_time_ > 100) {
ESP_LOGV(TAG, "Timed out waiting for readiness from PN532!");
this->rd_ready_ = TIMEOUT;
break;
@@ -340,7 +340,7 @@ enum PN532ReadReady PN532::read_ready_(bool block) {
auto rdy = this->rd_ready_;
if (block || rdy == TIMEOUT) {
this->rd_start_time_ = 0;
this->rd_start_time_.reset();
this->rd_ready_ = WOULDBLOCK;
}
return rdy;

View File

@@ -99,7 +99,7 @@ class PN532 : public PollingComponent {
std::vector<nfc::NfcOnTagTrigger *> triggers_ontagremoved_;
nfc::NfcTagUid current_uid_;
nfc::NdefMessage *next_task_message_to_write_;
uint32_t rd_start_time_{0};
optional<uint32_t> rd_start_time_{};
enum PN532ReadReady rd_ready_ { WOULDBLOCK };
enum NfcTask {
READ = 0,

View File

@@ -139,9 +139,10 @@ void Rtttl::loop() {
x++;
}
if (x > 0) {
int send = this->speaker_->play((uint8_t *) (&sample), x * 2);
if (send != x * 4) {
this->samples_sent_ -= (x - (send / 2));
size_t bytes_to_send = x * sizeof(SpeakerSample);
size_t send = this->speaker_->play((uint8_t *) (&sample), bytes_to_send);
if (send != bytes_to_send) {
this->samples_sent_ -= (x - (send / sizeof(SpeakerSample)));
}
return;
}
@@ -201,9 +202,9 @@ void Rtttl::loop() {
bool need_note_gap = false;
if (note) {
auto note_index = (scale - 4) * 12 + note;
if (note_index < 0 || note_index >= (int) sizeof(NOTES)) {
if (note_index < 0 || note_index >= (int) (sizeof(NOTES) / sizeof(NOTES[0]))) {
ESP_LOGE(TAG, "Note out of range (note: %d, scale: %d, index: %d, max: %d)", note, scale, note_index,
(int) sizeof(NOTES));
(int) (sizeof(NOTES) / sizeof(NOTES[0])));
this->finish_();
return;
}
@@ -221,7 +222,7 @@ void Rtttl::loop() {
#ifdef USE_OUTPUT
if (this->output_ != nullptr) {
if (need_note_gap) {
if (need_note_gap && this->note_duration_ > DOUBLE_NOTE_GAP_MS) {
this->output_->set_level(0.0);
delay(DOUBLE_NOTE_GAP_MS);
this->note_duration_ -= DOUBLE_NOTE_GAP_MS;
@@ -240,9 +241,9 @@ void Rtttl::loop() {
this->samples_sent_ = 0;
this->samples_gap_ = 0;
this->samples_per_wave_ = 0;
this->samples_count_ = (this->sample_rate_ * this->note_duration_) / 1600; //(ms);
this->samples_count_ = (this->sample_rate_ * this->note_duration_) / 1000;
if (need_note_gap) {
this->samples_gap_ = (this->sample_rate_ * DOUBLE_NOTE_GAP_MS) / 1600; //(ms);
this->samples_gap_ = (this->sample_rate_ * DOUBLE_NOTE_GAP_MS) / 1000;
}
if (this->output_freq_ != 0) {
// make sure there is enough samples to add a full last sinus.
@@ -279,7 +280,7 @@ void Rtttl::play(std::string rtttl) {
this->note_duration_ = 0;
int bpm = 63;
uint8_t num;
uint16_t num;
// Get name
this->position_ = this->rtttl_.find(':');
@@ -395,7 +396,7 @@ void Rtttl::finish_() {
sample[0].right = 0;
sample[1].left = 0;
sample[1].right = 0;
this->speaker_->play((uint8_t *) (&sample), 8);
this->speaker_->play((uint8_t *) (&sample), sizeof(sample));
this->speaker_->finish();
this->set_state_(State::STOPPING);
}

View File

@@ -46,8 +46,8 @@ class Rtttl : public Component {
}
protected:
inline uint8_t get_integer_() {
uint8_t ret = 0;
inline uint16_t get_integer_() {
uint16_t ret = 0;
while (isdigit(this->rtttl_[this->position_])) {
ret = (ret * 10) + (this->rtttl_[this->position_++] - '0');
}
@@ -87,7 +87,7 @@ class Rtttl : public Component {
#ifdef USE_OUTPUT
/// The output to write the sound to.
output::FloatOutput *output_;
output::FloatOutput *output_{nullptr};
#endif // USE_OUTPUT
#ifdef USE_SPEAKER

View File

@@ -2,6 +2,7 @@
#include "image_decoder.h"
#include "esphome/core/log.h"
#include "esphome/core/helpers.h"
#include <algorithm>
#include <cstring>
#ifdef USE_RUNTIME_IMAGE_BMP
@@ -43,6 +44,14 @@ int RuntimeImage::resize(int width, int height) {
int target_width = this->fixed_width_ ? this->fixed_width_ : width;
int target_height = this->fixed_height_ ? this->fixed_height_ : height;
// When both fixed dimensions are set, scale uniformly to preserve aspect ratio
if (this->fixed_width_ && this->fixed_height_ && width > 0 && height > 0) {
float scale =
std::min(static_cast<float>(this->fixed_width_) / width, static_cast<float>(this->fixed_height_) / height);
target_width = static_cast<int>(width * scale);
target_height = static_cast<int>(height * scale);
}
size_t result = this->resize_buffer_(target_width, target_height);
if (result > 0 && this->progressive_display_) {
// Update display dimensions for progressive display

View File

@@ -8,7 +8,7 @@
namespace esphome::safe_mode {
class SafeModeTrigger : public Trigger<> {
class SafeModeTrigger final : public Trigger<> {
public:
explicit SafeModeTrigger(SafeModeComponent *parent) {
parent->add_on_safe_mode_callback([this]() { trigger(); });

View File

@@ -15,7 +15,7 @@ namespace esphome::safe_mode {
constexpr uint32_t RTC_KEY = 233825507UL;
/// SafeModeComponent provides a safe way to recover from repeated boot failures
class SafeModeComponent : public Component {
class SafeModeComponent final : public Component {
public:
bool should_enter_safe_mode(uint8_t num_attempts, uint32_t enable_time, uint32_t boot_is_good_after);

View File

@@ -56,15 +56,6 @@ static const LogString *rht_accel_mode_to_string(RhtAccelerationMode mode) {
}
}
// This function performs an in-place conversion of the provided buffer
// from uint16_t values to big endianness
static inline const char *sensirion_convert_to_string_in_place(uint16_t *array, size_t length) {
for (size_t i = 0; i < length; i++) {
array[i] = convert_big_endian(array[i]);
}
return reinterpret_cast<const char *>(array);
}
void SEN5XComponent::setup() {
// the sensor needs 1000 ms to enter the idle state
this->set_timeout(1000, [this]() {

View File

@@ -21,6 +21,17 @@ class SensirionI2CDevice : public i2c::I2CDevice {
public:
enum CommandLen : uint8_t { ADDR_8_BIT = 1, ADDR_16_BIT = 2 };
/**
* This function performs an in-place conversion of the provided buffer
* from uint16_t values to big endianness. Useful for Sensirion strings in SEN5X and SEN6X
*/
static inline const char *sensirion_convert_to_string_in_place(uint16_t *array, size_t length) {
for (size_t i = 0; i < length; i++) {
array[i] = convert_big_endian(array[i]);
}
return reinterpret_cast<const char *>(array);
}
/** Read data words from I2C device.
* handles CRC check used by Sensirion sensors
* @param data pointer to raw result

View File

@@ -603,7 +603,7 @@ DELTA_SCHEMA = cv.Any(
def _get_delta(value):
if isinstance(value, str):
assert value.endswith("%")
return 0.0, float(value[:-1])
return 0.0, float(value[:-1]) / 100.0
return value, 0.0

View File

@@ -5,6 +5,7 @@
#include <cmath>
#include "esphome/core/application.h"
#include "esphome/core/hal.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#include "sensor.h"
@@ -240,7 +241,7 @@ ValueListFilter::ValueListFilter(std::initializer_list<TemplatableValue<float>>
bool ValueListFilter::value_matches_any_(float sensor_value) {
int8_t accuracy = this->parent_->get_accuracy_decimals();
float accuracy_mult = powf(10.0f, accuracy);
float accuracy_mult = pow10_int(accuracy);
float rounded_sensor = roundf(accuracy_mult * sensor_value);
for (auto &filter_value : this->values_) {
@@ -472,7 +473,7 @@ optional<float> ClampFilter::new_value(float value) {
RoundFilter::RoundFilter(uint8_t precision) : precision_(precision) {}
optional<float> RoundFilter::new_value(float value) {
if (std::isfinite(value)) {
float accuracy_mult = powf(10.0f, this->precision_);
float accuracy_mult = pow10_int(this->precision_);
return roundf(accuracy_mult * value) / accuracy_mult;
}
return value;

View File

@@ -149,7 +149,7 @@ stm32_err_t stm32_get_ack_timeout(const stm32_unique_ptr &stm, uint32_t timeout)
do {
yield();
if (!stream->available()) {
if (millis() < start_time + timeout)
if (millis() - start_time < timeout)
continue;
ESP_LOGD(TAG, "Failed to read ACK timeout=%i", timeout);
return STM32_ERR_UNKNOWN;
@@ -212,7 +212,7 @@ stm32_err_t stm32_resync(const stm32_unique_ptr &stm) {
static_assert(sizeof(buf) == BUFFER_SIZE, "Buf expected to be 2 bytes");
uint8_t ack;
while (t1 < t0 + STM32_RESYNC_TIMEOUT) {
while (t1 - t0 < STM32_RESYNC_TIMEOUT) {
stream->write_array(buf, BUFFER_SIZE);
stream->flush();
if (!stream->read_array(&ack, 1)) {

View File

@@ -134,6 +134,8 @@ def require_wake_loop_threadsafe() -> None:
IMPORTANT: This is for background thread context only, NOT ISR context.
Socket operations are not safe to call from ISR handlers.
On ESP32, FreeRTOS task notifications are used instead (no socket needed).
Example:
from esphome.components import socket
@@ -147,8 +149,10 @@ def require_wake_loop_threadsafe() -> None:
):
CORE.data[KEY_WAKE_LOOP_THREADSAFE_REQUIRED] = True
cg.add_define("USE_WAKE_LOOP_THREADSAFE")
# Consume 1 socket for the shared wake notification socket
consume_sockets(1, "socket.wake_loop_threadsafe", SocketType.UDP)({})
if not CORE.is_esp32:
# Only non-ESP32 platforms need a UDP socket for wake notifications.
# ESP32 uses FreeRTOS task notifications instead (no socket needed).
consume_sockets(1, "socket.wake_loop_threadsafe", SocketType.UDP)({})
CONFIG_SCHEMA = cv.Schema(

View File

@@ -71,7 +71,7 @@ class Socket {
int get_fd() const { return -1; }
#endif
/// Check if socket has data ready to read
/// Check if socket has data ready to read. Must only be called from the main loop thread.
/// For select()-based sockets: non-virtual, checks Application's select() results
/// For LWIP raw TCP sockets: virtual, checks internal buffer state
#ifdef USE_SOCKET_SELECT_SUPPORT

View File

@@ -84,32 +84,30 @@ SprinklerValveOperator::SprinklerValveOperator(SprinklerValve *valve, Sprinkler
: controller_(controller), valve_(valve) {}
void SprinklerValveOperator::loop() {
// Use wrapping subtraction so 32-bit millis() rollover is handled correctly:
// (now - start) yields the true elapsed time even across the 49.7-day boundary.
uint32_t now = App.get_loop_component_start_time();
if (now >= this->start_millis_) { // dummy check
switch (this->state_) {
case STARTING:
if (now > (this->start_millis_ + this->start_delay_)) {
this->run_(); // start_delay_ has been exceeded, so ensure both valves are on and update the state
}
break;
switch (this->state_) {
case STARTING:
if ((now - *this->start_millis_) > this->start_delay_) {
this->run_(); // start_delay_ has been exceeded, so ensure both valves are on and update the state
}
break;
case ACTIVE:
if (now > (this->start_millis_ + this->start_delay_ + this->run_duration_)) {
this->stop(); // start_delay_ + run_duration_ has been exceeded, start shutting down
}
break;
case ACTIVE:
if ((now - *this->start_millis_) > (this->start_delay_ + this->run_duration_)) {
this->stop(); // start_delay_ + run_duration_ has been exceeded, start shutting down
}
break;
case STOPPING:
if (now > (this->stop_millis_ + this->stop_delay_)) {
this->kill_(); // stop_delay_has been exceeded, ensure all valves are off
}
break;
case STOPPING:
if ((now - *this->stop_millis_) > this->stop_delay_) {
this->kill_(); // stop_delay_has been exceeded, ensure all valves are off
}
break;
default:
break;
}
} else { // perhaps millis() rolled over...or something else is horribly wrong!
this->stop(); // bail out (TODO: handle this highly unlikely situation better...)
default:
break;
}
}
@@ -124,11 +122,11 @@ void SprinklerValveOperator::set_valve(SprinklerValve *valve) {
if (this->state_ != IDLE) { // Only kill if not already idle
this->kill_(); // ensure everything is off before we let go!
}
this->state_ = IDLE; // reset state
this->run_duration_ = 0; // reset to ensure the valve isn't started without updating it
this->start_millis_ = 0; // reset because (new) valve has not been started yet
this->stop_millis_ = 0; // reset because (new) valve has not been started yet
this->valve_ = valve; // finally, set the pointer to the new valve
this->state_ = IDLE; // reset state
this->run_duration_ = 0; // reset to ensure the valve isn't started without updating it
this->start_millis_.reset(); // reset because (new) valve has not been started yet
this->stop_millis_.reset(); // reset because (new) valve has not been started yet
this->valve_ = valve; // finally, set the pointer to the new valve
}
}
@@ -162,7 +160,7 @@ void SprinklerValveOperator::start() {
} else {
this->run_(); // there is no start_delay_, so just start the pump and valve
}
this->stop_millis_ = 0;
this->stop_millis_.reset();
this->start_millis_ = millis(); // save the time the start request was made
}
@@ -189,22 +187,25 @@ void SprinklerValveOperator::stop() {
uint32_t SprinklerValveOperator::run_duration() { return this->run_duration_ / 1000; }
uint32_t SprinklerValveOperator::time_remaining() {
if (this->start_millis_ == 0) {
if (!this->start_millis_.has_value()) {
return this->run_duration(); // hasn't been started yet
}
if (this->stop_millis_) {
if (this->stop_millis_ - this->start_millis_ >= this->start_delay_ + this->run_duration_) {
if (this->stop_millis_.has_value()) {
uint32_t elapsed = *this->stop_millis_ - *this->start_millis_;
if (elapsed >= this->start_delay_ + this->run_duration_) {
return 0; // valve was active for more than its configured duration, so we are done
} else {
// we're stopped; return time remaining
return (this->run_duration_ - (this->stop_millis_ - this->start_millis_)) / 1000;
}
if (elapsed <= this->start_delay_) {
return this->run_duration_ / 1000; // stopped during start delay, full run duration remains
}
return (this->run_duration_ - (elapsed - this->start_delay_)) / 1000;
}
auto completed_millis = this->start_millis_ + this->start_delay_ + this->run_duration_;
if (completed_millis > millis()) {
return (completed_millis - millis()) / 1000; // running now
uint32_t elapsed = millis() - *this->start_millis_;
uint32_t total_duration = this->start_delay_ + this->run_duration_;
if (elapsed < total_duration) {
return (total_duration - elapsed) / 1000; // running now
}
return 0; // run completed
}
@@ -593,7 +594,7 @@ void Sprinkler::set_repeat(optional<uint32_t> repeat) {
if (this->repeat_number_ == nullptr) {
return;
}
if (this->repeat_number_->state == repeat.value()) {
if (this->repeat_number_->state == repeat.value_or(0)) {
return;
}
auto call = this->repeat_number_->make_call();
@@ -793,7 +794,7 @@ void Sprinkler::start_single_valve(const optional<size_t> valve_number, optional
void Sprinkler::queue_valve(optional<size_t> valve_number, optional<uint32_t> run_duration) {
if (valve_number.has_value()) {
if (this->is_a_valid_valve(valve_number.value()) && (this->queued_valves_.size() < this->max_queue_size_)) {
SprinklerQueueItem item{valve_number.value(), run_duration.value()};
SprinklerQueueItem item{valve_number.value(), run_duration.value_or(0)};
this->queued_valves_.insert(this->queued_valves_.begin(), item);
ESP_LOGD(TAG, "Valve %zu placed into queue with run duration of %" PRIu32 " seconds", valve_number.value_or(0),
run_duration.value_or(0));
@@ -1080,7 +1081,7 @@ uint32_t Sprinkler::total_cycle_time_enabled_incomplete_valves() {
}
}
if (incomplete_valve_count >= enabled_valve_count) {
if (incomplete_valve_count > 0 && incomplete_valve_count >= enabled_valve_count) {
incomplete_valve_count--;
}
if (incomplete_valve_count) {

View File

@@ -141,8 +141,8 @@ class SprinklerValveOperator {
uint32_t start_delay_{0};
uint32_t stop_delay_{0};
uint32_t run_duration_{0};
uint64_t start_millis_{0};
uint64_t stop_millis_{0};
optional<uint32_t> start_millis_{};
optional<uint32_t> stop_millis_{};
Sprinkler *controller_{nullptr};
SprinklerValve *valve_{nullptr};
SprinklerState state_{IDLE};

View File

@@ -1,10 +1,8 @@
#include "automation.h"
#include "esphome/core/log.h"
namespace esphome {
namespace switch_ {
namespace esphome::switch_ {
static const char *const TAG = "switch.automation";
} // namespace switch_
} // namespace esphome
} // namespace esphome::switch_

View File

@@ -4,8 +4,7 @@
#include "esphome/core/automation.h"
#include "esphome/components/switch/switch.h"
namespace esphome {
namespace switch_ {
namespace esphome::switch_ {
template<typename... Ts> class TurnOnAction : public Action<Ts...> {
public:
@@ -104,5 +103,4 @@ template<typename... Ts> class SwitchPublishAction : public Action<Ts...> {
Switch *switch_;
};
} // namespace switch_
} // namespace esphome
} // namespace esphome::switch_

View File

@@ -1,8 +1,7 @@
#include "switch_binary_sensor.h"
#include "esphome/core/log.h"
namespace esphome {
namespace switch_ {
namespace esphome::switch_ {
static const char *const TAG = "switch.binary_sensor";
@@ -13,5 +12,4 @@ void SwitchBinarySensor::setup() {
void SwitchBinarySensor::dump_config() { LOG_BINARY_SENSOR("", "Switch Binary Sensor", this); }
} // namespace switch_
} // namespace esphome
} // namespace esphome::switch_

View File

@@ -4,8 +4,7 @@
#include "esphome/core/component.h"
#include "esphome/components/binary_sensor/binary_sensor.h"
namespace esphome {
namespace switch_ {
namespace esphome::switch_ {
class SwitchBinarySensor : public binary_sensor::BinarySensor, public Component {
public:
@@ -17,5 +16,4 @@ class SwitchBinarySensor : public binary_sensor::BinarySensor, public Component
Switch *source_;
};
} // namespace switch_
} // namespace esphome
} // namespace esphome::switch_

View File

@@ -3,8 +3,7 @@
#include "esphome/core/controller_registry.h"
#include "esphome/core/log.h"
namespace esphome {
namespace switch_ {
namespace esphome::switch_ {
static const char *const TAG = "switch";
@@ -107,5 +106,4 @@ void log_switch(const char *tag, const char *prefix, const char *type, Switch *o
}
}
} // namespace switch_
} // namespace esphome
} // namespace esphome::switch_

Some files were not shown because too many files have changed in this diff Show More