mirror of
https://github.com/esphome/esphome.git
synced 2026-02-28 01:44:20 -07:00
Merge branch 'posix_tz_proto' into remove_posix_tz_parser
This commit is contained in:
5
.github/PULL_REQUEST_TEMPLATE.md
vendored
5
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -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
|
||||
|
||||
|
||||
1
.github/scripts/auto-label-pr/constants.js
vendored
1
.github/scripts/auto-label-pr/constants.js
vendored
@@ -27,6 +27,7 @@ module.exports = {
|
||||
'new-feature',
|
||||
'breaking-change',
|
||||
'developer-breaking-change',
|
||||
'undocumented-api-change',
|
||||
'code-quality',
|
||||
'deprecated-component'
|
||||
],
|
||||
|
||||
1
.github/scripts/auto-label-pr/detectors.js
vendored
1
.github/scripts/auto-label-pr/detectors.js
vendored
@@ -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' }
|
||||
];
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
12
esphome/components/api/api_pb2_defines.h
Normal file
12
esphome/components/api/api_pb2_defines.h
Normal 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
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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!");
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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]:
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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_{};
|
||||
|
||||
@@ -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]))
|
||||
|
||||
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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_{};
|
||||
};
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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_; }
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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], ®, sizeof(CMD_REG_DATA_REPLY_SIZE));
|
||||
memcpy(&cmd_frame.data[cmd_frame.data_length], ®, 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);
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -4,8 +4,6 @@
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
namespace esphome {
|
||||
namespace libretiny {} // namespace libretiny
|
||||
} // namespace esphome
|
||||
namespace esphome::libretiny {} // namespace esphome::libretiny
|
||||
|
||||
#endif // USE_LIBRETINY
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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_;
|
||||
|
||||
@@ -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_);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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_,
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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),
|
||||
],
|
||||
)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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_,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(); });
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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]() {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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_
|
||||
|
||||
@@ -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_
|
||||
|
||||
@@ -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_
|
||||
|
||||
@@ -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_
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user