mirror of
https://github.com/esphome/esphome.git
synced 2026-01-16 23:14:52 -07:00
Compare commits
28 Commits
syslog_snp
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
798d3bd956 | ||
|
|
77df3933db | ||
|
|
19514ccdf4 | ||
|
|
2947642ca5 | ||
|
|
60e333db08 | ||
|
|
d8463f4813 | ||
|
|
e1800d2fe2 | ||
|
|
50aa4b1992 | ||
|
|
edb303e495 | ||
|
|
973fc4c5dc | ||
|
|
f88e8fc43b | ||
|
|
d830787c71 | ||
|
|
c4c31a2e8e | ||
|
|
e6790f0042 | ||
|
|
ec7f72e280 | ||
|
|
6f29dbd6f1 | ||
|
|
9caf78aa7e | ||
|
|
1f4221abfa | ||
|
|
92808a09c7 | ||
|
|
e54d5ee898 | ||
|
|
bbe1155518 | ||
|
|
69d7b6e921 | ||
|
|
510c874061 | ||
|
|
f7ad324d81 | ||
|
|
58a9e30017 | ||
|
|
52ac9e1861 | ||
|
|
c5e4a60884 | ||
|
|
a680884138 |
@@ -11,7 +11,7 @@ ci:
|
||||
repos:
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: v0.14.11
|
||||
rev: v0.14.13
|
||||
hooks:
|
||||
# Run the linter.
|
||||
- id: ruff
|
||||
|
||||
@@ -48,14 +48,14 @@ uint32_t ProtoDecodableMessage::count_repeated_field(const uint8_t *buffer, size
|
||||
}
|
||||
uint32_t field_length = res->as_uint32();
|
||||
ptr += consumed;
|
||||
if (ptr + field_length > end) {
|
||||
if (field_length > static_cast<size_t>(end - ptr)) {
|
||||
return count; // Out of bounds
|
||||
}
|
||||
ptr += field_length;
|
||||
break;
|
||||
}
|
||||
case WIRE_TYPE_FIXED32: { // 32-bit - skip 4 bytes
|
||||
if (ptr + 4 > end) {
|
||||
if (end - ptr < 4) {
|
||||
return count;
|
||||
}
|
||||
ptr += 4;
|
||||
@@ -110,7 +110,7 @@ void ProtoDecodableMessage::decode(const uint8_t *buffer, size_t length) {
|
||||
}
|
||||
uint32_t field_length = res->as_uint32();
|
||||
ptr += consumed;
|
||||
if (ptr + field_length > end) {
|
||||
if (field_length > static_cast<size_t>(end - ptr)) {
|
||||
ESP_LOGV(TAG, "Out-of-bounds Length Delimited at offset %ld", (long) (ptr - buffer));
|
||||
return;
|
||||
}
|
||||
@@ -121,7 +121,7 @@ void ProtoDecodableMessage::decode(const uint8_t *buffer, size_t length) {
|
||||
break;
|
||||
}
|
||||
case WIRE_TYPE_FIXED32: { // 32-bit
|
||||
if (ptr + 4 > end) {
|
||||
if (end - ptr < 4) {
|
||||
ESP_LOGV(TAG, "Out-of-bounds Fixed32-bit at offset %ld", (long) (ptr - buffer));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include "hmac_sha256.h"
|
||||
#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_LIBRETINY) || defined(USE_HOST)
|
||||
@@ -26,9 +25,7 @@ void HmacSHA256::calculate() { mbedtls_md_hmac_finish(&this->ctx_, this->digest_
|
||||
void HmacSHA256::get_bytes(uint8_t *output) { memcpy(output, this->digest_, SHA256_DIGEST_SIZE); }
|
||||
|
||||
void HmacSHA256::get_hex(char *output) {
|
||||
for (size_t i = 0; i < SHA256_DIGEST_SIZE; i++) {
|
||||
sprintf(output + (i * 2), "%02x", this->digest_[i]);
|
||||
}
|
||||
format_hex_to(output, SHA256_DIGEST_SIZE * 2 + 1, this->digest_, SHA256_DIGEST_SIZE);
|
||||
}
|
||||
|
||||
bool HmacSHA256::equals_bytes(const uint8_t *expected) {
|
||||
|
||||
@@ -242,9 +242,7 @@ template<typename... Ts> class HttpRequestSendAction : public Action<Ts...> {
|
||||
return;
|
||||
}
|
||||
|
||||
size_t content_length = container->content_length;
|
||||
size_t max_length = std::min(content_length, this->max_response_buffer_size_);
|
||||
|
||||
size_t max_length = this->max_response_buffer_size_;
|
||||
#ifdef USE_HTTP_REQUEST_RESPONSE
|
||||
if (this->capture_response_.value(x...)) {
|
||||
std::string response_body;
|
||||
|
||||
@@ -213,18 +213,12 @@ int HttpContainerIDF::read(uint8_t *buf, size_t max_len) {
|
||||
const uint32_t start = millis();
|
||||
watchdog::WatchdogManager wdm(this->parent_->get_watchdog_timeout());
|
||||
|
||||
int bufsize = std::min(max_len, this->content_length - this->bytes_read_);
|
||||
|
||||
if (bufsize == 0) {
|
||||
this->duration_ms += (millis() - start);
|
||||
return 0;
|
||||
this->feed_wdt();
|
||||
int read_len = esp_http_client_read(this->client_, (char *) buf, max_len);
|
||||
this->feed_wdt();
|
||||
if (read_len > 0) {
|
||||
this->bytes_read_ += read_len;
|
||||
}
|
||||
|
||||
this->feed_wdt();
|
||||
int read_len = esp_http_client_read(this->client_, (char *) buf, bufsize);
|
||||
this->feed_wdt();
|
||||
this->bytes_read_ += read_len;
|
||||
|
||||
this->duration_ms += (millis() - start);
|
||||
|
||||
return read_len;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
from esphome import automation, pins
|
||||
@@ -18,13 +19,16 @@ from esphome.const import (
|
||||
CONF_ROTATION,
|
||||
CONF_UPDATE_INTERVAL,
|
||||
)
|
||||
from esphome.core import ID
|
||||
from esphome.core import ID, EnumValue
|
||||
from esphome.cpp_generator import MockObj, TemplateArgsType
|
||||
import esphome.final_validate as fv
|
||||
from esphome.helpers import add_class_to_obj
|
||||
from esphome.types import ConfigType
|
||||
|
||||
from . import boards, hub75_ns
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DEPENDENCIES = ["esp32"]
|
||||
CODEOWNERS = ["@stuartparmenter"]
|
||||
|
||||
@@ -120,13 +124,51 @@ PANEL_LAYOUTS = {
|
||||
}
|
||||
|
||||
Hub75ScanWiring = cg.global_ns.enum("Hub75ScanWiring", is_class=True)
|
||||
SCAN_PATTERNS = {
|
||||
SCAN_WIRINGS = {
|
||||
"STANDARD_TWO_SCAN": Hub75ScanWiring.STANDARD_TWO_SCAN,
|
||||
"FOUR_SCAN_16PX_HIGH": Hub75ScanWiring.FOUR_SCAN_16PX_HIGH,
|
||||
"FOUR_SCAN_32PX_HIGH": Hub75ScanWiring.FOUR_SCAN_32PX_HIGH,
|
||||
"FOUR_SCAN_64PX_HIGH": Hub75ScanWiring.FOUR_SCAN_64PX_HIGH,
|
||||
"SCAN_1_4_16PX_HIGH": Hub75ScanWiring.SCAN_1_4_16PX_HIGH,
|
||||
"SCAN_1_8_32PX_HIGH": Hub75ScanWiring.SCAN_1_8_32PX_HIGH,
|
||||
"SCAN_1_8_40PX_HIGH": Hub75ScanWiring.SCAN_1_8_40PX_HIGH,
|
||||
"SCAN_1_8_64PX_HIGH": Hub75ScanWiring.SCAN_1_8_64PX_HIGH,
|
||||
}
|
||||
|
||||
# Deprecated scan wiring names - mapped to new names
|
||||
DEPRECATED_SCAN_WIRINGS = {
|
||||
"FOUR_SCAN_16PX_HIGH": "SCAN_1_4_16PX_HIGH",
|
||||
"FOUR_SCAN_32PX_HIGH": "SCAN_1_8_32PX_HIGH",
|
||||
"FOUR_SCAN_64PX_HIGH": "SCAN_1_8_64PX_HIGH",
|
||||
}
|
||||
|
||||
|
||||
def _validate_scan_wiring(value):
|
||||
"""Validate scan_wiring with deprecation warnings for old names."""
|
||||
value = cv.string(value).upper().replace(" ", "_")
|
||||
|
||||
# Check if using deprecated name
|
||||
# Remove deprecated names in 2026.7.0
|
||||
if value in DEPRECATED_SCAN_WIRINGS:
|
||||
new_name = DEPRECATED_SCAN_WIRINGS[value]
|
||||
_LOGGER.warning(
|
||||
"Scan wiring '%s' is deprecated and will be removed in ESPHome 2026.7.0. "
|
||||
"Please use '%s' instead.",
|
||||
value,
|
||||
new_name,
|
||||
)
|
||||
value = new_name
|
||||
|
||||
# Validate against allowed values
|
||||
if value not in SCAN_WIRINGS:
|
||||
raise cv.Invalid(
|
||||
f"Unknown scan wiring '{value}'. "
|
||||
f"Valid options are: {', '.join(sorted(SCAN_WIRINGS.keys()))}"
|
||||
)
|
||||
|
||||
# Return as EnumValue like cv.enum does
|
||||
result = add_class_to_obj(value, EnumValue)
|
||||
result.enum_value = SCAN_WIRINGS[value]
|
||||
return result
|
||||
|
||||
|
||||
Hub75ClockSpeed = cg.global_ns.enum("Hub75ClockSpeed", is_class=True)
|
||||
CLOCK_SPEEDS = {
|
||||
"8MHZ": Hub75ClockSpeed.HZ_8M,
|
||||
@@ -382,9 +424,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
cv.Optional(CONF_LAYOUT_COLS): cv.positive_int,
|
||||
cv.Optional(CONF_LAYOUT): cv.enum(PANEL_LAYOUTS, upper=True, space="_"),
|
||||
# Panel hardware configuration
|
||||
cv.Optional(CONF_SCAN_WIRING): cv.enum(
|
||||
SCAN_PATTERNS, upper=True, space="_"
|
||||
),
|
||||
cv.Optional(CONF_SCAN_WIRING): _validate_scan_wiring,
|
||||
cv.Optional(CONF_SHIFT_DRIVER): cv.enum(SHIFT_DRIVERS, upper=True),
|
||||
# Display configuration
|
||||
cv.Optional(CONF_DOUBLE_BUFFER): cv.boolean,
|
||||
@@ -547,7 +587,7 @@ def _build_config_struct(
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
add_idf_component(
|
||||
name="esphome/esp-hub75",
|
||||
ref="0.2.2",
|
||||
ref="0.3.0",
|
||||
)
|
||||
|
||||
# Set compile-time configuration via build flags (so external library sees them)
|
||||
|
||||
@@ -19,12 +19,12 @@ InfraredCall &InfraredCall::set_carrier_frequency(uint32_t frequency) {
|
||||
InfraredCall &InfraredCall::set_raw_timings(const std::vector<int32_t> &timings) {
|
||||
this->raw_timings_ = &timings;
|
||||
this->packed_data_ = nullptr;
|
||||
this->base85_ptr_ = nullptr;
|
||||
this->base64url_ptr_ = nullptr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
InfraredCall &InfraredCall::set_raw_timings_base85(const std::string &base85) {
|
||||
this->base85_ptr_ = &base85;
|
||||
InfraredCall &InfraredCall::set_raw_timings_base64url(const std::string &base64url) {
|
||||
this->base64url_ptr_ = &base64url;
|
||||
this->raw_timings_ = nullptr;
|
||||
this->packed_data_ = nullptr;
|
||||
return *this;
|
||||
@@ -35,7 +35,7 @@ InfraredCall &InfraredCall::set_raw_timings_packed(const uint8_t *data, uint16_t
|
||||
this->packed_length_ = length;
|
||||
this->packed_count_ = count;
|
||||
this->raw_timings_ = nullptr;
|
||||
this->base85_ptr_ = nullptr;
|
||||
this->base64url_ptr_ = nullptr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -101,13 +101,22 @@ void Infrared::control(const InfraredCall &call) {
|
||||
call.get_packed_count());
|
||||
ESP_LOGD(TAG, "Transmitting packed raw timings: count=%u, repeat=%u", call.get_packed_count(),
|
||||
call.get_repeat_count());
|
||||
} else if (call.is_base85()) {
|
||||
// Decode base85 directly into transmit buffer (zero heap allocations)
|
||||
if (!transmit_data->set_data_from_base85(call.get_base85_data())) {
|
||||
ESP_LOGE(TAG, "Invalid base85 data");
|
||||
} else if (call.is_base64url()) {
|
||||
// Decode base64url (URL-safe) into transmit buffer
|
||||
if (!transmit_data->set_data_from_base64url(call.get_base64url_data())) {
|
||||
ESP_LOGE(TAG, "Invalid base64url data");
|
||||
return;
|
||||
}
|
||||
ESP_LOGD(TAG, "Transmitting base85 raw timings: count=%zu, repeat=%u", transmit_data->get_data().size(),
|
||||
// Sanity check: validate timing values are within reasonable bounds
|
||||
constexpr int32_t max_timing_us = 500000; // 500ms absolute max
|
||||
for (int32_t timing : transmit_data->get_data()) {
|
||||
int32_t abs_timing = timing < 0 ? -timing : timing;
|
||||
if (abs_timing > max_timing_us) {
|
||||
ESP_LOGE(TAG, "Invalid timing value: %d µs (max %d)", timing, max_timing_us);
|
||||
return;
|
||||
}
|
||||
}
|
||||
ESP_LOGD(TAG, "Transmitting base64url raw timings: count=%zu, repeat=%u", transmit_data->get_data().size(),
|
||||
call.get_repeat_count());
|
||||
} else {
|
||||
// From vector (lambdas/automations)
|
||||
|
||||
@@ -40,11 +40,11 @@ class InfraredCall {
|
||||
/// @note Usage: Primarily for lambdas/automations where the vector is in scope.
|
||||
InfraredCall &set_raw_timings(const std::vector<int32_t> &timings);
|
||||
|
||||
/// Set the raw timings from base85-encoded int32 data
|
||||
/// Set the raw timings from base64url-encoded little-endian int32 data
|
||||
/// @note Lifetime: Stores a pointer to the string. The string must outlive perform().
|
||||
/// @note Usage: For web_server where the encoded string is on the stack.
|
||||
/// @note Usage: For web_server - base64url is fully URL-safe (uses '-' and '_').
|
||||
/// @note Decoding happens at perform() time, directly into the transmit buffer.
|
||||
InfraredCall &set_raw_timings_base85(const std::string &base85);
|
||||
InfraredCall &set_raw_timings_base64url(const std::string &base64url);
|
||||
|
||||
/// Set the raw timings from packed protobuf sint32 data (zigzag + varint encoded)
|
||||
/// @note Lifetime: Stores a pointer to the buffer. The buffer must outlive perform().
|
||||
@@ -59,18 +59,18 @@ class InfraredCall {
|
||||
|
||||
/// Get the carrier frequency
|
||||
const optional<uint32_t> &get_carrier_frequency() const { return this->carrier_frequency_; }
|
||||
/// Get the raw timings (only valid if set via set_raw_timings, not packed or base85)
|
||||
/// Get the raw timings (only valid if set via set_raw_timings)
|
||||
const std::vector<int32_t> &get_raw_timings() const { return *this->raw_timings_; }
|
||||
/// Check if raw timings have been set (vector, packed, or base85)
|
||||
/// Check if raw timings have been set (any format)
|
||||
bool has_raw_timings() const {
|
||||
return this->raw_timings_ != nullptr || this->packed_data_ != nullptr || this->base85_ptr_ != nullptr;
|
||||
return this->raw_timings_ != nullptr || this->packed_data_ != nullptr || this->base64url_ptr_ != nullptr;
|
||||
}
|
||||
/// Check if using packed data format
|
||||
bool is_packed() const { return this->packed_data_ != nullptr; }
|
||||
/// Check if using base85 data format
|
||||
bool is_base85() const { return this->base85_ptr_ != nullptr; }
|
||||
/// Get the base85 data string
|
||||
const std::string &get_base85_data() const { return *this->base85_ptr_; }
|
||||
/// Check if using base64url data format
|
||||
bool is_base64url() const { return this->base64url_ptr_ != nullptr; }
|
||||
/// Get the base64url data string
|
||||
const std::string &get_base64url_data() const { return *this->base64url_ptr_; }
|
||||
/// Get packed data (only valid if set via set_raw_timings_packed)
|
||||
const uint8_t *get_packed_data() const { return this->packed_data_; }
|
||||
uint16_t get_packed_length() const { return this->packed_length_; }
|
||||
@@ -84,8 +84,8 @@ class InfraredCall {
|
||||
optional<uint32_t> carrier_frequency_;
|
||||
// Pointer to vector-based timings (caller-owned, must outlive perform())
|
||||
const std::vector<int32_t> *raw_timings_{nullptr};
|
||||
// Pointer to base85-encoded string (caller-owned, must outlive perform())
|
||||
const std::string *base85_ptr_{nullptr};
|
||||
// Pointer to base64url-encoded string (caller-owned, must outlive perform())
|
||||
const std::string *base64url_ptr_{nullptr};
|
||||
// Pointer to packed protobuf buffer (caller-owned, must outlive perform())
|
||||
const uint8_t *packed_data_{nullptr};
|
||||
uint16_t packed_length_{0};
|
||||
|
||||
@@ -85,8 +85,8 @@ optional<AEHAData> AEHAProtocol::decode(RemoteReceiveData src) {
|
||||
std::string AEHAProtocol::format_data_(const std::vector<uint8_t> &data) {
|
||||
std::string out;
|
||||
for (uint8_t byte : data) {
|
||||
char buf[6];
|
||||
sprintf(buf, "0x%02X,", byte);
|
||||
char buf[8]; // "0x%02X," = 5 chars + null + margin
|
||||
snprintf(buf, sizeof(buf), "0x%02X,", byte);
|
||||
out += buf;
|
||||
}
|
||||
out.pop_back();
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "raw_protocol.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
@@ -8,36 +9,30 @@ static const char *const TAG = "remote.raw";
|
||||
|
||||
bool RawDumper::dump(RemoteReceiveData src) {
|
||||
char buffer[256];
|
||||
uint32_t buffer_offset = 0;
|
||||
buffer_offset += sprintf(buffer, "Received Raw: ");
|
||||
size_t pos = buf_append_printf(buffer, sizeof(buffer), 0, "Received Raw: ");
|
||||
|
||||
for (int32_t i = 0; i < src.size() - 1; i++) {
|
||||
const int32_t value = src[i];
|
||||
const uint32_t remaining_length = sizeof(buffer) - buffer_offset;
|
||||
int written;
|
||||
size_t prev_pos = pos;
|
||||
|
||||
if (i + 1 < src.size() - 1) {
|
||||
written = snprintf(buffer + buffer_offset, remaining_length, "%" PRId32 ", ", value);
|
||||
pos = buf_append_printf(buffer, sizeof(buffer), pos, "%" PRId32 ", ", value);
|
||||
} else {
|
||||
written = snprintf(buffer + buffer_offset, remaining_length, "%" PRId32, value);
|
||||
pos = buf_append_printf(buffer, sizeof(buffer), pos, "%" PRId32, value);
|
||||
}
|
||||
|
||||
if (written < 0 || written >= int(remaining_length)) {
|
||||
// write failed, flush...
|
||||
buffer[buffer_offset] = '\0';
|
||||
if (pos >= sizeof(buffer) - 1) {
|
||||
// buffer full, flush and continue
|
||||
buffer[prev_pos] = '\0';
|
||||
ESP_LOGI(TAG, "%s", buffer);
|
||||
buffer_offset = 0;
|
||||
written = sprintf(buffer, " ");
|
||||
if (i + 1 < src.size() - 1) {
|
||||
written += sprintf(buffer + written, "%" PRId32 ", ", value);
|
||||
pos = buf_append_printf(buffer, sizeof(buffer), 0, " %" PRId32 ", ", value);
|
||||
} else {
|
||||
written += sprintf(buffer + written, "%" PRId32, value);
|
||||
pos = buf_append_printf(buffer, sizeof(buffer), 0, " %" PRId32, value);
|
||||
}
|
||||
}
|
||||
|
||||
buffer_offset += written;
|
||||
}
|
||||
if (buffer_offset != 0) {
|
||||
if (pos != 0) {
|
||||
ESP_LOGI(TAG, "%s", buffer);
|
||||
}
|
||||
return true;
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
#include "remote_base.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#include <cinttypes>
|
||||
|
||||
namespace esphome {
|
||||
namespace remote_base {
|
||||
|
||||
@@ -159,8 +158,8 @@ void RemoteTransmitData::set_data_from_packed_sint32(const uint8_t *data, size_t
|
||||
}
|
||||
}
|
||||
|
||||
bool RemoteTransmitData::set_data_from_base85(const std::string &base85) {
|
||||
return base85_decode_int32_vector(base85, this->data_);
|
||||
bool RemoteTransmitData::set_data_from_base64url(const std::string &base64url) {
|
||||
return base64_decode_int32_vector(base64url, this->data_);
|
||||
}
|
||||
|
||||
/* RemoteTransmitterBase */
|
||||
@@ -169,36 +168,31 @@ void RemoteTransmitterBase::send_(uint32_t send_times, uint32_t send_wait) {
|
||||
#ifdef ESPHOME_LOG_HAS_VERY_VERBOSE
|
||||
const auto &vec = this->temp_.get_data();
|
||||
char buffer[256];
|
||||
uint32_t buffer_offset = 0;
|
||||
buffer_offset += sprintf(buffer, "Sending times=%" PRIu32 " wait=%" PRIu32 "ms: ", send_times, send_wait);
|
||||
size_t pos = buf_append_printf(buffer, sizeof(buffer), 0,
|
||||
"Sending times=%" PRIu32 " wait=%" PRIu32 "ms: ", send_times, send_wait);
|
||||
|
||||
for (size_t i = 0; i < vec.size(); i++) {
|
||||
const int32_t value = vec[i];
|
||||
const uint32_t remaining_length = sizeof(buffer) - buffer_offset;
|
||||
int written;
|
||||
size_t prev_pos = pos;
|
||||
|
||||
if (i + 1 < vec.size()) {
|
||||
written = snprintf(buffer + buffer_offset, remaining_length, "%" PRId32 ", ", value);
|
||||
pos = buf_append_printf(buffer, sizeof(buffer), pos, "%" PRId32 ", ", value);
|
||||
} else {
|
||||
written = snprintf(buffer + buffer_offset, remaining_length, "%" PRId32, value);
|
||||
pos = buf_append_printf(buffer, sizeof(buffer), pos, "%" PRId32, value);
|
||||
}
|
||||
|
||||
if (written < 0 || written >= int(remaining_length)) {
|
||||
// write failed, flush...
|
||||
buffer[buffer_offset] = '\0';
|
||||
if (pos >= sizeof(buffer) - 1) {
|
||||
// buffer full, flush and continue
|
||||
buffer[prev_pos] = '\0';
|
||||
ESP_LOGVV(TAG, "%s", buffer);
|
||||
buffer_offset = 0;
|
||||
written = sprintf(buffer, " ");
|
||||
if (i + 1 < vec.size()) {
|
||||
written += sprintf(buffer + written, "%" PRId32 ", ", value);
|
||||
pos = buf_append_printf(buffer, sizeof(buffer), 0, " %" PRId32 ", ", value);
|
||||
} else {
|
||||
written += sprintf(buffer + written, "%" PRId32, value);
|
||||
pos = buf_append_printf(buffer, sizeof(buffer), 0, " %" PRId32, value);
|
||||
}
|
||||
}
|
||||
|
||||
buffer_offset += written;
|
||||
}
|
||||
if (buffer_offset != 0) {
|
||||
if (pos != 0) {
|
||||
ESP_LOGVV(TAG, "%s", buffer);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -36,11 +36,11 @@ class RemoteTransmitData {
|
||||
/// @param len Length of the buffer in bytes
|
||||
/// @param count Number of values (for reserve optimization)
|
||||
void set_data_from_packed_sint32(const uint8_t *data, size_t len, size_t count);
|
||||
/// Set data from base85-encoded int32 values
|
||||
/// Decodes directly into internal buffer (zero heap allocations)
|
||||
/// @param base85 Base85-encoded string (5 chars per int32 value)
|
||||
/// Set data from base64url-encoded little-endian int32 values
|
||||
/// Base64url is URL-safe: uses '-' instead of '+', '_' instead of '/'
|
||||
/// @param base64url Base64url-encoded string of little-endian int32 values
|
||||
/// @return true if successful, false if decode failed or invalid size
|
||||
bool set_data_from_base85(const std::string &base85);
|
||||
bool set_data_from_base64url(const std::string &base64url);
|
||||
void reset() {
|
||||
this->data_.clear();
|
||||
this->carrier_frequency_ = 0;
|
||||
|
||||
@@ -8,17 +8,20 @@ from esphome.const import (
|
||||
CONF_ICON,
|
||||
CONF_ID,
|
||||
CONF_INDEX,
|
||||
CONF_LAMBDA,
|
||||
CONF_MODE,
|
||||
CONF_MQTT_ID,
|
||||
CONF_ON_VALUE,
|
||||
CONF_OPERATION,
|
||||
CONF_OPTION,
|
||||
CONF_OPTIONS,
|
||||
CONF_TRIGGER_ID,
|
||||
CONF_WEB_SERVER,
|
||||
)
|
||||
from esphome.core import CORE, CoroPriority, coroutine_with_priority
|
||||
from esphome.core import CORE, ID, CoroPriority, coroutine_with_priority
|
||||
from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity
|
||||
from esphome.cpp_generator import MockObjClass
|
||||
from esphome.cpp_generator import MockObjClass, TemplateArguments
|
||||
from esphome.cpp_types import global_ns
|
||||
|
||||
CODEOWNERS = ["@esphome/core"]
|
||||
IS_PLATFORM_COMPONENT = True
|
||||
@@ -38,6 +41,9 @@ SelectSetAction = select_ns.class_("SelectSetAction", automation.Action)
|
||||
SelectSetIndexAction = select_ns.class_("SelectSetIndexAction", automation.Action)
|
||||
SelectOperationAction = select_ns.class_("SelectOperationAction", automation.Action)
|
||||
|
||||
# Conditions
|
||||
SelectIsCondition = select_ns.class_("SelectIsCondition", automation.Condition)
|
||||
|
||||
# Enums
|
||||
SelectOperation = select_ns.enum("SelectOperation")
|
||||
SELECT_OPERATION_OPTIONS = {
|
||||
@@ -165,6 +171,41 @@ async def select_set_index_to_code(config, action_id, template_arg, args):
|
||||
return var
|
||||
|
||||
|
||||
@automation.register_condition(
|
||||
"select.is",
|
||||
SelectIsCondition,
|
||||
OPERATION_BASE_SCHEMA.extend(
|
||||
{
|
||||
cv.Optional(CONF_OPTIONS): cv.All(
|
||||
cv.ensure_list(cv.string_strict), cv.Length(min=1)
|
||||
),
|
||||
cv.Optional(CONF_LAMBDA): cv.returning_lambda,
|
||||
}
|
||||
).add_extra(cv.has_exactly_one_key(CONF_OPTIONS, CONF_LAMBDA)),
|
||||
)
|
||||
async def select_is_to_code(config, condition_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
if options := config.get(CONF_OPTIONS):
|
||||
# List of constant options
|
||||
# Create a constexpr and pass that with a template length
|
||||
arr_id = ID(
|
||||
f"{condition_id}_data",
|
||||
is_declaration=True,
|
||||
type=global_ns.namespace("constexpr char * const"),
|
||||
)
|
||||
arg = cg.static_const_array(arr_id, cg.ArrayInitializer(*options))
|
||||
template_arg = TemplateArguments(len(options), *template_arg)
|
||||
else:
|
||||
# Lambda
|
||||
arg = await cg.process_lambda(
|
||||
config[CONF_LAMBDA],
|
||||
[(global_ns.namespace("StringRef &").operator("const"), "current")] + args,
|
||||
return_type=cg.bool_,
|
||||
)
|
||||
template_arg = TemplateArguments(0, *template_arg)
|
||||
return cg.new_Pvariable(condition_id, template_arg, paren, arg)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"select.operation",
|
||||
SelectOperationAction,
|
||||
|
||||
@@ -66,4 +66,34 @@ template<typename... Ts> class SelectOperationAction : public Action<Ts...> {
|
||||
Select *select_;
|
||||
};
|
||||
|
||||
template<size_t N, typename... Ts> class SelectIsCondition : public Condition<Ts...> {
|
||||
public:
|
||||
SelectIsCondition(Select *parent, const char *const *option_list) : parent_(parent), option_list_(option_list) {}
|
||||
|
||||
bool check(const Ts &...x) override {
|
||||
auto current = this->parent_->current_option();
|
||||
for (size_t i = 0; i != N; i++) {
|
||||
if (current == this->option_list_[i]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected:
|
||||
Select *parent_;
|
||||
const char *const *option_list_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class SelectIsCondition<0, Ts...> : public Condition<Ts...> {
|
||||
public:
|
||||
SelectIsCondition(Select *parent, std::function<bool(const StringRef &, const Ts &...)> &&f)
|
||||
: parent_(parent), f_(f) {}
|
||||
|
||||
bool check(const Ts &...x) override { return this->f_(this->parent_->current_option(), x...); }
|
||||
|
||||
protected:
|
||||
Select *parent_;
|
||||
std::function<bool(const StringRef &, const Ts &...)> f_;
|
||||
};
|
||||
} // namespace esphome::select
|
||||
|
||||
@@ -47,27 +47,29 @@ void Syslog::log_(const int level, const char *tag, const char *message, size_t
|
||||
size_t remaining = sizeof(packet);
|
||||
|
||||
// Write PRI - abort if this fails as packet would be malformed
|
||||
offset = buf_append_printf(packet, sizeof(packet), 0, "<%d>", pri);
|
||||
if (offset == 0) {
|
||||
return; // PRI always produces at least "<0>" (3 chars), so 0 means error
|
||||
int ret = snprintf(packet, remaining, "<%d>", pri);
|
||||
if (ret <= 0 || static_cast<size_t>(ret) >= remaining) {
|
||||
return;
|
||||
}
|
||||
remaining -= offset;
|
||||
offset = ret;
|
||||
remaining -= ret;
|
||||
|
||||
// Write timestamp directly into packet (RFC 5424: use "-" if time not valid or strftime fails)
|
||||
auto now = this->time_->now();
|
||||
size_t ts_written = now.is_valid() ? now.strftime(packet + offset, remaining, "%b %e %H:%M:%S") : 0;
|
||||
if (ts_written > 0) {
|
||||
offset += ts_written;
|
||||
remaining -= ts_written;
|
||||
} else if (remaining > 0) {
|
||||
packet[offset++] = '-';
|
||||
remaining--;
|
||||
}
|
||||
|
||||
// Write hostname, tag, and message
|
||||
offset = buf_append_printf(packet, sizeof(packet), offset, " %s %s: %.*s", App.get_name().c_str(), tag, (int) len,
|
||||
message);
|
||||
// Clamp to exclude null terminator position if buffer was filled
|
||||
if (offset >= sizeof(packet)) {
|
||||
offset = sizeof(packet) - 1;
|
||||
ret = snprintf(packet + offset, remaining, " %s %s: %.*s", App.get_name().c_str(), tag, (int) len, message);
|
||||
if (ret > 0) {
|
||||
// snprintf returns chars that would be written; clamp to actual buffer space
|
||||
offset += std::min(static_cast<size_t>(ret), remaining > 0 ? remaining - 1 : 0);
|
||||
}
|
||||
|
||||
if (offset > 0) {
|
||||
|
||||
@@ -658,6 +658,24 @@ std::string WebServer::text_sensor_json_(text_sensor::TextSensor *obj, const std
|
||||
#endif
|
||||
|
||||
#ifdef USE_SWITCH
|
||||
enum SwitchAction : uint8_t { SWITCH_ACTION_NONE, SWITCH_ACTION_TOGGLE, SWITCH_ACTION_TURN_ON, SWITCH_ACTION_TURN_OFF };
|
||||
|
||||
static void execute_switch_action(switch_::Switch *obj, SwitchAction action) {
|
||||
switch (action) {
|
||||
case SWITCH_ACTION_TOGGLE:
|
||||
obj->toggle();
|
||||
break;
|
||||
case SWITCH_ACTION_TURN_ON:
|
||||
obj->turn_on();
|
||||
break;
|
||||
case SWITCH_ACTION_TURN_OFF:
|
||||
obj->turn_off();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void WebServer::on_switch_update(switch_::Switch *obj) {
|
||||
if (!this->include_internal_ && obj->is_internal())
|
||||
return;
|
||||
@@ -676,34 +694,22 @@ void WebServer::handle_switch_request(AsyncWebServerRequest *request, const UrlM
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle action methods with single defer and response
|
||||
enum SwitchAction { NONE, TOGGLE, TURN_ON, TURN_OFF };
|
||||
SwitchAction action = NONE;
|
||||
SwitchAction action = SWITCH_ACTION_NONE;
|
||||
|
||||
if (match.method_equals(ESPHOME_F("toggle"))) {
|
||||
action = TOGGLE;
|
||||
action = SWITCH_ACTION_TOGGLE;
|
||||
} else if (match.method_equals(ESPHOME_F("turn_on"))) {
|
||||
action = TURN_ON;
|
||||
action = SWITCH_ACTION_TURN_ON;
|
||||
} else if (match.method_equals(ESPHOME_F("turn_off"))) {
|
||||
action = TURN_OFF;
|
||||
action = SWITCH_ACTION_TURN_OFF;
|
||||
}
|
||||
|
||||
if (action != NONE) {
|
||||
this->defer([obj, action]() {
|
||||
switch (action) {
|
||||
case TOGGLE:
|
||||
obj->toggle();
|
||||
break;
|
||||
case TURN_ON:
|
||||
obj->turn_on();
|
||||
break;
|
||||
case TURN_OFF:
|
||||
obj->turn_off();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
if (action != SWITCH_ACTION_NONE) {
|
||||
#ifdef USE_ESP8266
|
||||
execute_switch_action(obj, action);
|
||||
#else
|
||||
this->defer([obj, action]() { execute_switch_action(obj, action); });
|
||||
#endif
|
||||
request->send(200);
|
||||
} else {
|
||||
request->send(404);
|
||||
@@ -743,7 +749,7 @@ void WebServer::handle_button_request(AsyncWebServerRequest *request, const UrlM
|
||||
std::string data = this->button_json_(obj, detail);
|
||||
request->send(200, "application/json", data.c_str());
|
||||
} else if (match.method_equals(ESPHOME_F("press"))) {
|
||||
this->defer([obj]() { obj->press(); });
|
||||
DEFER_ACTION(obj, obj->press());
|
||||
request->send(200);
|
||||
return;
|
||||
} else {
|
||||
@@ -828,7 +834,7 @@ void WebServer::handle_fan_request(AsyncWebServerRequest *request, const UrlMatc
|
||||
std::string data = this->fan_json_(obj, detail);
|
||||
request->send(200, "application/json", data.c_str());
|
||||
} else if (match.method_equals(ESPHOME_F("toggle"))) {
|
||||
this->defer([obj]() { obj->toggle().perform(); });
|
||||
DEFER_ACTION(obj, obj->toggle().perform());
|
||||
request->send(200);
|
||||
} else {
|
||||
bool is_on = match.method_equals(ESPHOME_F("turn_on"));
|
||||
@@ -859,7 +865,7 @@ void WebServer::handle_fan_request(AsyncWebServerRequest *request, const UrlMatc
|
||||
return;
|
||||
}
|
||||
}
|
||||
this->defer([call]() mutable { call.perform(); });
|
||||
DEFER_ACTION(call, call.perform());
|
||||
request->send(200);
|
||||
}
|
||||
return;
|
||||
@@ -909,7 +915,7 @@ void WebServer::handle_light_request(AsyncWebServerRequest *request, const UrlMa
|
||||
std::string data = this->light_json_(obj, detail);
|
||||
request->send(200, "application/json", data.c_str());
|
||||
} else if (match.method_equals(ESPHOME_F("toggle"))) {
|
||||
this->defer([obj]() { obj->toggle().perform(); });
|
||||
DEFER_ACTION(obj, obj->toggle().perform());
|
||||
request->send(200);
|
||||
} else {
|
||||
bool is_on = match.method_equals(ESPHOME_F("turn_on"));
|
||||
@@ -938,7 +944,7 @@ void WebServer::handle_light_request(AsyncWebServerRequest *request, const UrlMa
|
||||
parse_string_param_(request, ESPHOME_F("effect"), call, &decltype(call)::set_effect);
|
||||
}
|
||||
|
||||
this->defer([call]() mutable { call.perform(); });
|
||||
DEFER_ACTION(call, call.perform());
|
||||
request->send(200);
|
||||
}
|
||||
return;
|
||||
@@ -1027,7 +1033,7 @@ void WebServer::handle_cover_request(AsyncWebServerRequest *request, const UrlMa
|
||||
parse_float_param_(request, ESPHOME_F("position"), call, &decltype(call)::set_position);
|
||||
parse_float_param_(request, ESPHOME_F("tilt"), call, &decltype(call)::set_tilt);
|
||||
|
||||
this->defer([call]() mutable { call.perform(); });
|
||||
DEFER_ACTION(call, call.perform());
|
||||
request->send(200);
|
||||
return;
|
||||
}
|
||||
@@ -1086,7 +1092,7 @@ void WebServer::handle_number_request(AsyncWebServerRequest *request, const UrlM
|
||||
auto call = obj->make_call();
|
||||
parse_float_param_(request, ESPHOME_F("value"), call, &decltype(call)::set_value);
|
||||
|
||||
this->defer([call]() mutable { call.perform(); });
|
||||
DEFER_ACTION(call, call.perform());
|
||||
request->send(200);
|
||||
return;
|
||||
}
|
||||
@@ -1159,7 +1165,7 @@ void WebServer::handle_date_request(AsyncWebServerRequest *request, const UrlMat
|
||||
|
||||
parse_string_param_(request, ESPHOME_F("value"), call, &decltype(call)::set_date);
|
||||
|
||||
this->defer([call]() mutable { call.perform(); });
|
||||
DEFER_ACTION(call, call.perform());
|
||||
request->send(200);
|
||||
return;
|
||||
}
|
||||
@@ -1223,7 +1229,7 @@ void WebServer::handle_time_request(AsyncWebServerRequest *request, const UrlMat
|
||||
|
||||
parse_string_param_(request, ESPHOME_F("value"), call, &decltype(call)::set_time);
|
||||
|
||||
this->defer([call]() mutable { call.perform(); });
|
||||
DEFER_ACTION(call, call.perform());
|
||||
request->send(200);
|
||||
return;
|
||||
}
|
||||
@@ -1286,7 +1292,7 @@ void WebServer::handle_datetime_request(AsyncWebServerRequest *request, const Ur
|
||||
|
||||
parse_string_param_(request, ESPHOME_F("value"), call, &decltype(call)::set_datetime);
|
||||
|
||||
this->defer([call]() mutable { call.perform(); });
|
||||
DEFER_ACTION(call, call.perform());
|
||||
request->send(200);
|
||||
return;
|
||||
}
|
||||
@@ -1346,7 +1352,7 @@ void WebServer::handle_text_request(AsyncWebServerRequest *request, const UrlMat
|
||||
auto call = obj->make_call();
|
||||
parse_string_param_(request, ESPHOME_F("value"), call, &decltype(call)::set_value);
|
||||
|
||||
this->defer([call]() mutable { call.perform(); });
|
||||
DEFER_ACTION(call, call.perform());
|
||||
request->send(200);
|
||||
return;
|
||||
}
|
||||
@@ -1404,7 +1410,7 @@ void WebServer::handle_select_request(AsyncWebServerRequest *request, const UrlM
|
||||
auto call = obj->make_call();
|
||||
parse_string_param_(request, ESPHOME_F("option"), call, &decltype(call)::set_option);
|
||||
|
||||
this->defer([call]() mutable { call.perform(); });
|
||||
DEFER_ACTION(call, call.perform());
|
||||
request->send(200);
|
||||
return;
|
||||
}
|
||||
@@ -1473,7 +1479,7 @@ void WebServer::handle_climate_request(AsyncWebServerRequest *request, const Url
|
||||
parse_float_param_(request, ESPHOME_F("target_temperature_low"), call, &decltype(call)::set_target_temperature_low);
|
||||
parse_float_param_(request, ESPHOME_F("target_temperature"), call, &decltype(call)::set_target_temperature);
|
||||
|
||||
this->defer([call]() mutable { call.perform(); });
|
||||
DEFER_ACTION(call, call.perform());
|
||||
request->send(200);
|
||||
return;
|
||||
}
|
||||
@@ -1589,6 +1595,24 @@ std::string WebServer::climate_json_(climate::Climate *obj, JsonDetail start_con
|
||||
#endif
|
||||
|
||||
#ifdef USE_LOCK
|
||||
enum LockAction : uint8_t { LOCK_ACTION_NONE, LOCK_ACTION_LOCK, LOCK_ACTION_UNLOCK, LOCK_ACTION_OPEN };
|
||||
|
||||
static void execute_lock_action(lock::Lock *obj, LockAction action) {
|
||||
switch (action) {
|
||||
case LOCK_ACTION_LOCK:
|
||||
obj->lock();
|
||||
break;
|
||||
case LOCK_ACTION_UNLOCK:
|
||||
obj->unlock();
|
||||
break;
|
||||
case LOCK_ACTION_OPEN:
|
||||
obj->open();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void WebServer::on_lock_update(lock::Lock *obj) {
|
||||
if (!this->include_internal_ && obj->is_internal())
|
||||
return;
|
||||
@@ -1607,34 +1631,22 @@ void WebServer::handle_lock_request(AsyncWebServerRequest *request, const UrlMat
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle action methods with single defer and response
|
||||
enum LockAction { NONE, LOCK, UNLOCK, OPEN };
|
||||
LockAction action = NONE;
|
||||
LockAction action = LOCK_ACTION_NONE;
|
||||
|
||||
if (match.method_equals(ESPHOME_F("lock"))) {
|
||||
action = LOCK;
|
||||
action = LOCK_ACTION_LOCK;
|
||||
} else if (match.method_equals(ESPHOME_F("unlock"))) {
|
||||
action = UNLOCK;
|
||||
action = LOCK_ACTION_UNLOCK;
|
||||
} else if (match.method_equals(ESPHOME_F("open"))) {
|
||||
action = OPEN;
|
||||
action = LOCK_ACTION_OPEN;
|
||||
}
|
||||
|
||||
if (action != NONE) {
|
||||
this->defer([obj, action]() {
|
||||
switch (action) {
|
||||
case LOCK:
|
||||
obj->lock();
|
||||
break;
|
||||
case UNLOCK:
|
||||
obj->unlock();
|
||||
break;
|
||||
case OPEN:
|
||||
obj->open();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
if (action != LOCK_ACTION_NONE) {
|
||||
#ifdef USE_ESP8266
|
||||
execute_lock_action(obj, action);
|
||||
#else
|
||||
this->defer([obj, action]() { execute_lock_action(obj, action); });
|
||||
#endif
|
||||
request->send(200);
|
||||
} else {
|
||||
request->send(404);
|
||||
@@ -1717,7 +1729,7 @@ void WebServer::handle_valve_request(AsyncWebServerRequest *request, const UrlMa
|
||||
|
||||
parse_float_param_(request, ESPHOME_F("position"), call, &decltype(call)::set_position);
|
||||
|
||||
this->defer([call]() mutable { call.perform(); });
|
||||
DEFER_ACTION(call, call.perform());
|
||||
request->send(200);
|
||||
return;
|
||||
}
|
||||
@@ -1796,7 +1808,7 @@ void WebServer::handle_alarm_control_panel_request(AsyncWebServerRequest *reques
|
||||
return;
|
||||
}
|
||||
|
||||
this->defer([call]() mutable { call.perform(); });
|
||||
DEFER_ACTION(call, call.perform());
|
||||
request->send(200);
|
||||
return;
|
||||
}
|
||||
@@ -1872,7 +1884,7 @@ void WebServer::handle_water_heater_request(AsyncWebServerRequest *request, cons
|
||||
// Parse on/off parameter
|
||||
parse_bool_param_(request, ESPHOME_F("is_on"), base_call, &water_heater::WaterHeaterCall::set_on);
|
||||
|
||||
this->defer([call]() mutable { call.perform(); });
|
||||
DEFER_ACTION(call, call.perform());
|
||||
request->send(200);
|
||||
return;
|
||||
}
|
||||
@@ -2032,7 +2044,7 @@ void WebServer::handle_update_request(AsyncWebServerRequest *request, const UrlM
|
||||
return;
|
||||
}
|
||||
|
||||
this->defer([obj]() mutable { obj->perform(); });
|
||||
DEFER_ACTION(obj, obj->perform());
|
||||
request->send(200);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -42,6 +42,14 @@ using ParamNameType = const __FlashStringHelper *;
|
||||
using ParamNameType = const char *;
|
||||
#endif
|
||||
|
||||
// ESP8266 is single-threaded, so actions can execute directly in request context.
|
||||
// Multi-core platforms need to defer to main loop thread for thread safety.
|
||||
#ifdef USE_ESP8266
|
||||
#define DEFER_ACTION(capture, action) action
|
||||
#else
|
||||
#define DEFER_ACTION(capture, action) this->defer([capture]() mutable { action; })
|
||||
#endif
|
||||
|
||||
/// Result of matching a URL against an entity
|
||||
struct EntityMatchResult {
|
||||
bool matched; ///< True if entity matched the URL
|
||||
|
||||
@@ -624,53 +624,44 @@ std::vector<uint8_t> base64_decode(const std::string &encoded_string) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// Encode int32 to 5 base85 characters + null terminator
|
||||
/// Standard ASCII85 alphabet: '!' (33) = 0 through 'u' (117) = 84
|
||||
inline void base85_encode_int32(int32_t value, std::span<char, BASE85_INT32_ENCODED_SIZE> output) {
|
||||
uint32_t v = static_cast<uint32_t>(value);
|
||||
// Encode least significant digit first, then reverse
|
||||
for (int i = 4; i >= 0; i--) {
|
||||
output[i] = static_cast<char>('!' + (v % 85));
|
||||
v /= 85;
|
||||
}
|
||||
output[5] = '\0';
|
||||
}
|
||||
|
||||
/// Decode 5 base85 characters to int32
|
||||
inline bool base85_decode_int32(const char *input, int32_t &out) {
|
||||
uint8_t c0 = static_cast<uint8_t>(input[0] - '!');
|
||||
uint8_t c1 = static_cast<uint8_t>(input[1] - '!');
|
||||
uint8_t c2 = static_cast<uint8_t>(input[2] - '!');
|
||||
uint8_t c3 = static_cast<uint8_t>(input[3] - '!');
|
||||
uint8_t c4 = static_cast<uint8_t>(input[4] - '!');
|
||||
|
||||
// Each digit must be 0-84. Since uint8_t wraps, chars below '!' become > 84
|
||||
if (c0 > 84 || c1 > 84 || c2 > 84 || c3 > 84 || c4 > 84)
|
||||
return false;
|
||||
|
||||
// 85^4 = 52200625, 85^3 = 614125, 85^2 = 7225, 85^1 = 85
|
||||
out = static_cast<int32_t>(c0 * 52200625u + c1 * 614125u + c2 * 7225u + c3 * 85u + c4);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Decode base85 string directly into vector (no intermediate buffer)
|
||||
bool base85_decode_int32_vector(const std::string &base85, std::vector<int32_t> &out) {
|
||||
size_t len = base85.size();
|
||||
if (len % 5 != 0)
|
||||
return false;
|
||||
/// Decode base64/base64url string directly into vector of little-endian int32 values
|
||||
/// @param base64 Base64 or base64url encoded string (both +/ and -_ accepted)
|
||||
/// @param out Output vector (cleared and filled with decoded int32 values)
|
||||
/// @return true if successful, false if decode failed or invalid size
|
||||
bool base64_decode_int32_vector(const std::string &base64, std::vector<int32_t> &out) {
|
||||
// Decode in chunks to minimize stack usage
|
||||
constexpr size_t chunk_bytes = 48; // 12 int32 values
|
||||
constexpr size_t chunk_chars = 64; // 48 * 4/3 = 64 chars
|
||||
uint8_t chunk[chunk_bytes];
|
||||
|
||||
out.clear();
|
||||
const char *ptr = base85.data();
|
||||
const char *end = ptr + len;
|
||||
|
||||
while (ptr < end) {
|
||||
int32_t value;
|
||||
if (!base85_decode_int32(ptr, value))
|
||||
const uint8_t *input = reinterpret_cast<const uint8_t *>(base64.data());
|
||||
size_t remaining = base64.size();
|
||||
size_t pos = 0;
|
||||
|
||||
while (remaining > 0) {
|
||||
size_t chars_to_decode = std::min(remaining, chunk_chars);
|
||||
size_t decoded_len = base64_decode(input + pos, chars_to_decode, chunk, chunk_bytes);
|
||||
|
||||
if (decoded_len == 0)
|
||||
return false;
|
||||
out.push_back(value);
|
||||
ptr += 5;
|
||||
|
||||
// Parse little-endian int32 values
|
||||
for (size_t i = 0; i + 3 < decoded_len; i += 4) {
|
||||
int32_t timing = static_cast<int32_t>(encode_uint32(chunk[i + 3], chunk[i + 2], chunk[i + 1], chunk[i]));
|
||||
out.push_back(timing);
|
||||
}
|
||||
|
||||
// Check for incomplete int32 in last chunk
|
||||
if (remaining <= chunk_chars && (decoded_len % 4) != 0)
|
||||
return false;
|
||||
|
||||
pos += chars_to_decode;
|
||||
remaining -= chars_to_decode;
|
||||
}
|
||||
return true;
|
||||
|
||||
return !out.empty();
|
||||
}
|
||||
|
||||
// Colors
|
||||
|
||||
@@ -1137,13 +1137,11 @@ std::vector<uint8_t> base64_decode(const std::string &encoded_string);
|
||||
size_t base64_decode(std::string const &encoded_string, uint8_t *buf, size_t buf_len);
|
||||
size_t base64_decode(const uint8_t *encoded_data, size_t encoded_len, uint8_t *buf, size_t buf_len);
|
||||
|
||||
/// Size of buffer needed for base85 encoded int32 (5 chars + null terminator)
|
||||
static constexpr size_t BASE85_INT32_ENCODED_SIZE = 6;
|
||||
|
||||
void base85_encode_int32(int32_t value, std::span<char, BASE85_INT32_ENCODED_SIZE> output);
|
||||
|
||||
bool base85_decode_int32(const char *input, int32_t &out);
|
||||
bool base85_decode_int32_vector(const std::string &base85, std::vector<int32_t> &out);
|
||||
/// Decode base64/base64url string directly into vector of little-endian int32 values
|
||||
/// @param base64 Base64 or base64url encoded string (both +/ and -_ accepted)
|
||||
/// @param out Output vector (cleared and filled with decoded int32 values)
|
||||
/// @return true if successful, false if decode failed or invalid size
|
||||
bool base64_decode_int32_vector(const std::string &base64, std::vector<int32_t> &out);
|
||||
|
||||
///@}
|
||||
|
||||
|
||||
@@ -28,8 +28,8 @@ dependencies:
|
||||
rules:
|
||||
- if: "target in [esp32s2, esp32s3, esp32p4]"
|
||||
esphome/esp-hub75:
|
||||
version: 0.2.2
|
||||
version: 0.3.0
|
||||
rules:
|
||||
- if: "target in [esp32, esp32s2, esp32s3, esp32p4]"
|
||||
- if: "target in [esp32, esp32s2, esp32s3, esp32c6, esp32p4]"
|
||||
esp32async/asynctcp:
|
||||
version: 3.4.91
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
pylint==4.0.4
|
||||
flake8==7.3.0 # also change in .pre-commit-config.yaml when updating
|
||||
ruff==0.14.12 # also change in .pre-commit-config.yaml when updating
|
||||
ruff==0.14.13 # also change in .pre-commit-config.yaml when updating
|
||||
pyupgrade==3.21.2 # also change in .pre-commit-config.yaml when updating
|
||||
pre-commit
|
||||
|
||||
|
||||
@@ -53,6 +53,17 @@ binary_sensor:
|
||||
// Garage Door is closed.
|
||||
return false;
|
||||
}
|
||||
- platform: template
|
||||
id: select_binary_sensor
|
||||
name: Select is one or two
|
||||
condition:
|
||||
any:
|
||||
- select.is:
|
||||
id: template_select
|
||||
options: [one, two]
|
||||
- select.is:
|
||||
id: template_select
|
||||
lambda: return current == id(template_text).state;
|
||||
- platform: template
|
||||
id: other_binary_sensor
|
||||
name: "Garage Door Closed"
|
||||
@@ -320,6 +331,7 @@ valve:
|
||||
|
||||
text:
|
||||
- platform: template
|
||||
id: template_text
|
||||
name: "Template text"
|
||||
optimistic: true
|
||||
min_length: 0
|
||||
|
||||
Reference in New Issue
Block a user