Compare commits

..

70 Commits

Author SHA1 Message Date
Jonathan Swoboda
cfd88376b9 Merge pull request #12266 from esphome/bump-2025.11.3
2025.11.3
2025-12-03 11:36:57 -05:00
Jonathan Swoboda
577a6b2941 Bump version to 2025.11.3 2025-12-03 10:50:28 -05:00
Jonathan Swoboda
de68b56c4a [rtl87xx] Fix FreeRTOS version for RTL8720C boards (#12261)
Co-authored-by: Claude <noreply@anthropic.com>
2025-12-03 10:50:28 -05:00
Jonathan Swoboda
ccd23e692b [analog_threshold] Fix oscillation when using invert filter (#12251)
Co-authored-by: Claude <noreply@anthropic.com>
2025-12-03 10:50:28 -05:00
Jonathan Swoboda
1f5a44be3d [rtl87xx] Fix AsyncTCP compilation by upgrading FreeRTOS to 8.2.3 (#12230)
Co-authored-by: Claude <noreply@anthropic.com>
2025-12-03 10:50:28 -05:00
Jonathan Swoboda
1d1e47c757 [core] Fix clean all windows (#12217)
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2025-12-03 10:50:28 -05:00
3fbed1fa79 [ade7953] Apply voltage_gain setting to both channels (#12180) 2025-12-03 10:50:28 -05:00
Jonathan Swoboda
5c71520635 [mopeka_pro_check] Fix negative temperatures (#12198)
Co-authored-by: Claude <noreply@anthropic.com>
2025-12-03 10:50:28 -05:00
J. Nick Koston
9d6c81ec23 [hlk_fm22x] Fix Action::play method signatures (#12192) 2025-12-03 10:50:28 -05:00
Clyde Stubbs
73fa9230e6 [helpers] Add conversion from FixedVector to std::vector (#12179) 2025-12-03 10:50:28 -05:00
J. Nick Koston
48caff13c9 [espnow] Initialize LwIP stack when running without WiFi component (#12169) 2025-12-03 10:50:28 -05:00
J. Nick Koston
71bb94524e [usb_uart] Wake main loop immediately when USB data arrives (#12148) 2025-12-03 10:50:28 -05:00
Clyde Stubbs
a3199792c6 [build] Don't clear pio cache unless requested (#11966) 2025-12-03 10:50:28 -05:00
Jonathan Swoboda
50c1720c16 Merge pull request #12149 from esphome/bump-2025.11.2
2025.11.2
2025-11-27 18:19:05 -05:00
Jonathan Swoboda
4115dd7222 Bump version to 2025.11.2 2025-11-27 17:23:28 -05:00
J. Nick Koston
d5e2543751 [scheduler] Fix use-after-move crash in heap operations (#12124) 2025-11-27 17:23:28 -05:00
Clyde Stubbs
b4b34aee13 [wifi] Restore blocking setup until connected for RP2040 (#12142) 2025-11-27 17:23:28 -05:00
Jonathan Swoboda
6645994700 [esp32] Fix hosted update when there is no wifi (#12123) 2025-11-27 17:23:28 -05:00
Clyde Stubbs
ae140f52e3 [lvgl] Fix position of errors in widget config (#12111)
Co-authored-by: J. Nick Koston <nick@koston.org>
2025-11-27 17:23:28 -05:00
Clyde Stubbs
46ae6d35a2 [lvgl] Allow multiple widgets per grid cell (#12091) 2025-11-27 17:23:27 -05:00
J. Nick Koston
278f12fb99 [script] Fix script.wait hanging when triggered from on_boot (#12102) 2025-11-27 17:23:27 -05:00
Jonathan Swoboda
acdcd56395 [esp32] Fix platformio flash size print (#12099)
Co-authored-by: J. Nick Koston <nick+github@koston.org>
2025-11-27 17:23:27 -05:00
Edward Firmo
9289fc36f7 [nextion] Do not set alternative baud rate when not specified or <= 0 (#12097) 2025-11-27 17:23:27 -05:00
Jonathan Swoboda
3775b54554 Merge pull request #12086 from esphome/bump-2025.11.1
2025.11.1
2025-11-24 17:29:53 -05:00
Jonathan Swoboda
9186144dcd Bump version to 2025.11.1 2025-11-24 16:24:38 -05:00
Jesse Hills
25bcd0ea25 [online_image] Fix some large PNGs causing watchdog timeout (#12025)
Co-authored-by: guillempages <guillempages@users.noreply.github.com>
2025-11-24 16:24:38 -05:00
J. Nick Koston
50d08a2eba [esp_ldo,mipi_dsi,mipi_rgb] Fix dangling pointer bugs in mark_failed() (#12077) 2025-11-24 16:24:38 -05:00
J. Nick Koston
3a7a0c66ab [script][wait_until] Fix FIFO ordering and reentrancy bugs (#12049)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-24 16:24:38 -05:00
Jonathan Swoboda
83525b7a92 [core] Add support for passing yaml files to clean-all (#12039) 2025-11-24 16:24:38 -05:00
Jonathan Swoboda
f31f023c89 [esp32] Fix C2 builds (#12050) 2025-11-24 16:24:37 -05:00
J. Nick Koston
f8efefffaa [cst816][http_request] Fix status_set_error() dangling pointer bugs (#12033) 2025-11-24 16:24:37 -05:00
Jonathan Swoboda
d698083ede [jsn_sr04t] Fix model AJ_SR04M (#11992) 2025-11-24 16:24:37 -05:00
Jonathan Swoboda
11ba6440d7 [cst816][packet_transport][udp][wake_on_lan] Fix error messages (#12019) 2025-11-24 16:24:37 -05:00
Jonathan Swoboda
89ee37a2d5 [ltr501][ltr_als_ps] Rename enum to avoid collision with lwip defines (#12017) 2025-11-24 16:24:37 -05:00
J. Nick Koston
45b8c1e267 [network] Fix IPAddress constructor causing comparison failures and garbage output (#12005) 2025-11-24 16:24:37 -05:00
Jonathan Swoboda
fbe091f167 [graph] Fix legend border (#12000) 2025-11-24 16:24:37 -05:00
Jonathan Swoboda
625172e07d Merge pull request #12004 from esphome/bump-2025.11.0
2025.11.0
2025-11-19 17:37:42 -05:00
Jonathan Swoboda
1e9c7d3c6d Bump version to 2025.11.0 2025-11-19 16:02:52 -05:00
Jonathan Swoboda
c2bc7b3cdc Merge pull request #12003 from esphome/bump-2025.11.0b5
2025.11.0b5
2025-11-19 15:06:44 -05:00
Jonathan Swoboda
c75abfb894 Bump version to 2025.11.0b5 2025-11-19 14:17:03 -05:00
Jesse Hills
1157b4aee8 [epaper_spi] Add basic 7.3in-Spectra-E6 model (#12001) 2025-11-19 14:17:03 -05:00
J. Nick Koston
71dc2d374d [web_server_idf] Fix pbuf_free crash by moving shutdown before close (#11995) 2025-11-19 14:17:03 -05:00
Jonathan Swoboda
0a224f919b [wifi] Fix positive RSSI values on 8266 (#11994) 2025-11-19 14:17:03 -05:00
Jonathan Swoboda
7ef4b4f3d9 [text_sensor] Fix infinite loop in substitute filter (#11989)
Co-authored-by: J. Nick Koston <nick@koston.org>
2025-11-19 14:17:03 -05:00
J. Nick Koston
13b875c763 [tests] Fix SNTP time ID conflicts in component tests for grouped testing (#11990) 2025-11-19 14:17:03 -05:00
Jesse Hills
dfd614c00c Merge pull request #11980 from esphome/bump-2025.11.0b4
2025.11.0b4
2025-11-19 13:22:09 +13:00
Jesse Hills
2681a14d05 Bump version to 2025.11.0b4 2025-11-19 09:17:33 +13:00
J. Nick Koston
f436f6ee2e [wifi] Fix captive portal unusable when WiFi credentials are wrong (#11965) 2025-11-19 09:17:33 +13:00
Jonathan Swoboda
f18bc62690 [sfa30] Fix negative temperature values (#11973) 2025-11-19 09:17:33 +13:00
J. Nick Koston
6db73df649 [scheduler] Add defensive nullptr checks and explicit locking requirements (#11974) 2025-11-19 09:17:33 +13:00
Jonathan Swoboda
93215f1737 [esp32] Fix Arduino build on some ESP32 S2 boards (#11972) 2025-11-19 09:17:33 +13:00
Clyde Stubbs
70aa94b8a4 [lvgl] Apply scale to spinbox value (#11946) 2025-11-19 09:17:33 +13:00
strange_v
e8998a79c7 [mipi_rgb] Fix GUITION-4848S040 colors (#11709) 2025-11-19 09:17:33 +13:00
Jonathan Swoboda
3b25fdbc5f [core] Add support for setting environment variables (#11953) 2025-11-19 09:17:33 +13:00
J. Nick Koston
6c8577678c [captive_portal] Warn when enabled without WiFi AP configured (#11856) 2025-11-19 09:17:33 +13:00
Jesse Hills
70366d2124 Merge pull request #11944 from esphome/bump-2025.11.0b3
2025.11.0b3
2025-11-17 17:41:11 +13:00
Jesse Hills
a38c4e0c6e Bump version to 2025.11.0b3 2025-11-17 15:32:09 +13:00
Anton Sergunov
6c6b03bda0 [uart] Setup uart pins only if flags are set (#11914)
Co-authored-by: J. Nick Koston <nick+github@koston.org>
2025-11-17 15:32:09 +13:00
J. Nick Koston
9e02e31917 [web_server_idf] Fix lwIP assertion crash by shutting down sockets on connection close (#11937) 2025-11-17 15:32:09 +13:00
J. Nick Koston
3fd58f1a91 [web_server.ota] Merge multiple instances to prevent undefined behavior (#11905) 2025-11-17 15:32:09 +13:00
J. Nick Koston
9151489481 [sntp] Merge multiple instances to fix crash and undefined behavior (#11904) 2025-11-17 15:32:09 +13:00
J. Nick Koston
f19296ac7f [analyze-memory] Show all core symbols > 100 B instead of top 15 (#11909) 2025-11-17 15:32:09 +13:00
J. Nick Koston
36868ee7b1 [scheduler] Fix timing breakage after 49 days of uptime on ESP8266/RP2040 (#11924) 2025-11-17 15:32:09 +13:00
J. Nick Koston
d559f9f52e [ld2410] Add timeout filter to prevent stuck targets (#11920) 2025-11-17 15:32:09 +13:00
J. Nick Koston
6440b5fbf5 [ld2412] Fix stuck targets by adding timeout filter (#11919) 2025-11-17 15:32:09 +13:00
Jonathan Swoboda
97c4914573 [uart] Improve error handling and validate buffer size (#11895)
Co-authored-by: J. Nick Koston <nick+github@koston.org>
2025-11-17 15:32:09 +13:00
Edward Firmo
7ce94c27fe [wifi] Allow use_psram with Arduino (#11902) 2025-11-17 15:32:09 +13:00
Edward Firmo
eb54c0026d [light] Fix missing ColorMode::BRIGHTNESS case in logging (#11836) 2025-11-17 15:32:09 +13:00
Clyde Stubbs
fe00e209ff [esp32] Add sdkconfig flag to make OTA work for 32MB flash (#11883)
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2025-11-17 15:32:08 +13:00
Clyde Stubbs
aed80732f9 [esp32] Make esp-idf default framework for P4 (#11884) 2025-11-17 15:32:08 +13:00
166 changed files with 1897 additions and 2433 deletions

View File

@@ -58,7 +58,7 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@014f16e7ab1402f30e7c3329d33797e7948572db # v4.31.3
uses: github/codeql-action/init@0499de31b99561a6d14a36a5f662c2a54f91beee # v4.31.2
with:
languages: ${{ matrix.language }}
build-mode: ${{ matrix.build-mode }}
@@ -86,6 +86,6 @@ jobs:
exit 1
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@014f16e7ab1402f30e7c3329d33797e7948572db # v4.31.3
uses: github/codeql-action/analyze@0499de31b99561a6d14a36a5f662c2a54f91beee # v4.31.2
with:
category: "/language:${{matrix.language}}"

View File

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

View File

@@ -48,7 +48,7 @@ PROJECT_NAME = ESPHome
# could be handy for archiving the generated documentation or if some version
# control system is used.
PROJECT_NUMBER = 2025.12.0-dev
PROJECT_NUMBER = 2025.11.3
# Using the PROJECT_BRIEF tag one can provide an optional one line description
# for a project that appears at the top of each page and should give viewer a

View File

@@ -741,13 +741,6 @@ def command_vscode(args: ArgsProtocol) -> int | None:
def command_compile(args: ArgsProtocol, config: ConfigType) -> int | None:
# Set memory analysis options in config
if args.analyze_memory:
config.setdefault(CONF_ESPHOME, {})["analyze_memory"] = True
if args.memory_report:
config.setdefault(CONF_ESPHOME, {})["memory_report_file"] = args.memory_report
exit_code = write_cpp(config)
if exit_code != 0:
return exit_code
@@ -1209,17 +1202,6 @@ def parse_args(argv):
help="Only generate source code, do not compile.",
action="store_true",
)
parser_compile.add_argument(
"--analyze-memory",
help="Analyze and display memory usage by component after compilation.",
action="store_true",
)
parser_compile.add_argument(
"--memory-report",
help="Save memory analysis report to a file (supports .json or .txt).",
type=str,
metavar="FILE",
)
parser_upload = subparsers.add_parser(
"upload",
@@ -1337,7 +1319,7 @@ def parse_args(argv):
"clean-all", help="Clean all build and platform files."
)
parser_clean_all.add_argument(
"configuration", help="Your YAML configuration directory.", nargs="*"
"configuration", help="Your YAML file or configuration directory.", nargs="*"
)
parser_dashboard = subparsers.add_parser(

View File

@@ -1,7 +1,6 @@
"""CLI interface for memory analysis with report generation."""
from collections import defaultdict
import json
import sys
from . import (
@@ -298,28 +297,6 @@ class MemoryAnalyzerCLI(MemoryAnalyzer):
return "\n".join(lines)
def to_json(self) -> str:
"""Export analysis results as JSON."""
data = {
"components": {
name: {
"text": mem.text_size,
"rodata": mem.rodata_size,
"data": mem.data_size,
"bss": mem.bss_size,
"flash_total": mem.flash_total,
"ram_total": mem.ram_total,
"symbol_count": mem.symbol_count,
}
for name, mem in self.components.items()
},
"totals": {
"flash": sum(c.flash_total for c in self.components.values()),
"ram": sum(c.ram_total for c in self.components.values()),
},
}
return json.dumps(data, indent=2)
def dump_uncategorized_symbols(self, output_file: str | None = None) -> None:
"""Dump uncategorized symbols for analysis."""
# Sort by size descending

View File

@@ -25,7 +25,8 @@ void ADE7953::setup() {
this->ade_write_8(PGA_V_8, pga_v_);
this->ade_write_8(PGA_IA_8, pga_ia_);
this->ade_write_8(PGA_IB_8, pga_ib_);
this->ade_write_32(AVGAIN_32, vgain_);
this->ade_write_32(AVGAIN_32, avgain_);
this->ade_write_32(BVGAIN_32, bvgain_);
this->ade_write_32(AIGAIN_32, aigain_);
this->ade_write_32(BIGAIN_32, bigain_);
this->ade_write_32(AWGAIN_32, awgain_);
@@ -34,7 +35,8 @@ void ADE7953::setup() {
this->ade_read_8(PGA_V_8, &pga_v_);
this->ade_read_8(PGA_IA_8, &pga_ia_);
this->ade_read_8(PGA_IB_8, &pga_ib_);
this->ade_read_32(AVGAIN_32, &vgain_);
this->ade_read_32(AVGAIN_32, &avgain_);
this->ade_read_32(BVGAIN_32, &bvgain_);
this->ade_read_32(AIGAIN_32, &aigain_);
this->ade_read_32(BIGAIN_32, &bigain_);
this->ade_read_32(AWGAIN_32, &awgain_);
@@ -63,13 +65,14 @@ void ADE7953::dump_config() {
" PGA_V_8: 0x%X\n"
" PGA_IA_8: 0x%X\n"
" PGA_IB_8: 0x%X\n"
" VGAIN_32: 0x%08jX\n"
" AVGAIN_32: 0x%08jX\n"
" BVGAIN_32: 0x%08jX\n"
" AIGAIN_32: 0x%08jX\n"
" BIGAIN_32: 0x%08jX\n"
" AWGAIN_32: 0x%08jX\n"
" BWGAIN_32: 0x%08jX",
this->use_acc_energy_regs_, pga_v_, pga_ia_, pga_ib_, (uintmax_t) vgain_, (uintmax_t) aigain_,
(uintmax_t) bigain_, (uintmax_t) awgain_, (uintmax_t) bwgain_);
this->use_acc_energy_regs_, pga_v_, pga_ia_, pga_ib_, (uintmax_t) avgain_, (uintmax_t) bvgain_,
(uintmax_t) aigain_, (uintmax_t) bigain_, (uintmax_t) awgain_, (uintmax_t) bwgain_);
}
#define ADE_PUBLISH_(name, val, factor) \

View File

@@ -46,7 +46,12 @@ class ADE7953 : public PollingComponent, public sensor::Sensor {
void set_pga_ib(uint8_t pga_ib) { pga_ib_ = pga_ib; }
// Set input gains
void set_vgain(uint32_t vgain) { vgain_ = vgain; }
void set_vgain(uint32_t vgain) {
// Datasheet says: "to avoid discrepancies in other registers,
// if AVGAIN is set then BVGAIN should be set to the same value."
avgain_ = vgain;
bvgain_ = vgain;
}
void set_aigain(uint32_t aigain) { aigain_ = aigain; }
void set_bigain(uint32_t bigain) { bigain_ = bigain; }
void set_awgain(uint32_t awgain) { awgain_ = awgain; }
@@ -100,7 +105,8 @@ class ADE7953 : public PollingComponent, public sensor::Sensor {
uint8_t pga_v_;
uint8_t pga_ia_;
uint8_t pga_ib_;
uint32_t vgain_;
uint32_t avgain_;
uint32_t bvgain_;
uint32_t aigain_;
uint32_t bigain_;
uint32_t awgain_;

View File

@@ -12,10 +12,11 @@ void AnalogThresholdBinarySensor::setup() {
// TRUE state is defined to be when sensor is >= threshold
// so when undefined sensor value initialize to FALSE
if (std::isnan(sensor_value)) {
this->raw_state_ = false;
this->publish_initial_state(false);
} else {
this->publish_initial_state(sensor_value >=
(this->lower_threshold_.value() + this->upper_threshold_.value()) / 2.0f);
this->raw_state_ = sensor_value >= (this->lower_threshold_.value() + this->upper_threshold_.value()) / 2.0f;
this->publish_initial_state(this->raw_state_);
}
}
@@ -25,8 +26,10 @@ void AnalogThresholdBinarySensor::set_sensor(sensor::Sensor *analog_sensor) {
this->sensor_->add_on_state_callback([this](float sensor_value) {
// if there is an invalid sensor reading, ignore the change and keep the current state
if (!std::isnan(sensor_value)) {
this->publish_state(sensor_value >=
(this->state ? this->lower_threshold_.value() : this->upper_threshold_.value()));
// Use raw_state_ for hysteresis logic, not this->state which is post-filter
this->raw_state_ =
sensor_value >= (this->raw_state_ ? this->lower_threshold_.value() : this->upper_threshold_.value());
this->publish_state(this->raw_state_);
}
});
}

View File

@@ -20,6 +20,7 @@ class AnalogThresholdBinarySensor : public Component, public binary_sensor::Bina
sensor::Sensor *sensor_{nullptr};
TemplatableValue<float> upper_threshold_{};
TemplatableValue<float> lower_threshold_{};
bool raw_state_{false}; // Pre-filter state for hysteresis logic
};
} // namespace analog_threshold

View File

@@ -51,14 +51,13 @@ template<typename... Ts> class UserServiceBase : public UserServiceDescriptor {
return false;
if (req.args.size() != sizeof...(Ts))
return false;
this->execute_(req.args, std::make_index_sequence<sizeof...(Ts)>{});
this->execute_(req.args, typename gens<sizeof...(Ts)>::type());
return true;
}
protected:
virtual void execute(Ts... x) = 0;
template<typename ArgsContainer, size_t... S>
void execute_(const ArgsContainer &args, std::index_sequence<S...> type) {
template<typename ArgsContainer, int... S> void execute_(const ArgsContainer &args, seq<S...> type) {
this->execute((get_execute_arg_value<Ts>(args[S]))...);
}
@@ -96,14 +95,13 @@ template<typename... Ts> class UserServiceDynamic : public UserServiceDescriptor
return false;
if (req.args.size() != sizeof...(Ts))
return false;
this->execute_(req.args, std::make_index_sequence<sizeof...(Ts)>{});
this->execute_(req.args, typename gens<sizeof...(Ts)>::type());
return true;
}
protected:
virtual void execute(Ts... x) = 0;
template<typename ArgsContainer, size_t... S>
void execute_(const ArgsContainer &args, std::index_sequence<S...> type) {
template<typename ArgsContainer, int... S> void execute_(const ArgsContainer &args, seq<S...> type) {
this->execute((get_execute_arg_value<Ts>(args[S]))...);
}

View File

@@ -1,8 +1,8 @@
#include "bh1750.h"
#include "esphome/core/log.h"
#include "esphome/core/application.h"
namespace esphome::bh1750 {
namespace esphome {
namespace bh1750 {
static const char *const TAG = "bh1750.sensor";
@@ -13,31 +13,6 @@ static const uint8_t BH1750_COMMAND_ONE_TIME_L = 0b00100011;
static const uint8_t BH1750_COMMAND_ONE_TIME_H = 0b00100000;
static const uint8_t BH1750_COMMAND_ONE_TIME_H2 = 0b00100001;
static constexpr uint32_t MEASUREMENT_TIMEOUT_MS = 2000;
static constexpr float HIGH_LIGHT_THRESHOLD_LX = 7000.0f;
// Measurement time constants (datasheet values)
static constexpr uint16_t MTREG_DEFAULT = 69;
static constexpr uint16_t MTREG_MIN = 31;
static constexpr uint16_t MTREG_MAX = 254;
static constexpr uint16_t MEAS_TIME_L_MS = 24; // L-resolution max measurement time @ mtreg=69
static constexpr uint16_t MEAS_TIME_H_MS = 180; // H/H2-resolution max measurement time @ mtreg=69
// Conversion constants (datasheet formulas)
static constexpr float RESOLUTION_DIVISOR = 1.2f; // counts to lux conversion divisor
static constexpr float MODE_H2_DIVISOR = 2.0f; // H2 mode has 2x higher resolution
// MTreg calculation constants
static constexpr int COUNTS_TARGET = 50000; // Target counts for optimal range (avoid saturation)
static constexpr int COUNTS_NUMERATOR = 10;
static constexpr int COUNTS_DENOMINATOR = 12;
// MTreg register bit manipulation constants
static constexpr uint8_t MTREG_HI_SHIFT = 5; // High 3 bits start at bit 5
static constexpr uint8_t MTREG_HI_MASK = 0b111; // 3-bit mask for high bits
static constexpr uint8_t MTREG_LO_SHIFT = 0; // Low 5 bits start at bit 0
static constexpr uint8_t MTREG_LO_MASK = 0b11111; // 5-bit mask for low bits
/*
bh1750 properties:
@@ -68,7 +43,74 @@ void BH1750Sensor::setup() {
this->mark_failed();
return;
}
this->state_ = IDLE;
}
void BH1750Sensor::read_lx_(BH1750Mode mode, uint8_t mtreg, const std::function<void(float)> &f) {
// turn on (after one-shot sensor automatically powers down)
uint8_t turn_on = BH1750_COMMAND_POWER_ON;
if (this->write(&turn_on, 1) != i2c::ERROR_OK) {
ESP_LOGW(TAG, "Power on failed");
f(NAN);
return;
}
if (active_mtreg_ != mtreg) {
// set mtreg
uint8_t mtreg_hi = BH1750_COMMAND_MT_REG_HI | ((mtreg >> 5) & 0b111);
uint8_t mtreg_lo = BH1750_COMMAND_MT_REG_LO | ((mtreg >> 0) & 0b11111);
if (this->write(&mtreg_hi, 1) != i2c::ERROR_OK || this->write(&mtreg_lo, 1) != i2c::ERROR_OK) {
ESP_LOGW(TAG, "Set measurement time failed");
active_mtreg_ = 0;
f(NAN);
return;
}
active_mtreg_ = mtreg;
}
uint8_t cmd;
uint16_t meas_time;
switch (mode) {
case BH1750_MODE_L:
cmd = BH1750_COMMAND_ONE_TIME_L;
meas_time = 24 * mtreg / 69;
break;
case BH1750_MODE_H:
cmd = BH1750_COMMAND_ONE_TIME_H;
meas_time = 180 * mtreg / 69;
break;
case BH1750_MODE_H2:
cmd = BH1750_COMMAND_ONE_TIME_H2;
meas_time = 180 * mtreg / 69;
break;
default:
f(NAN);
return;
}
if (this->write(&cmd, 1) != i2c::ERROR_OK) {
ESP_LOGW(TAG, "Start measurement failed");
f(NAN);
return;
}
// probably not needed, but adjust for rounding
meas_time++;
this->set_timeout("read", meas_time, [this, mode, mtreg, f]() {
uint16_t raw_value;
if (this->read(reinterpret_cast<uint8_t *>(&raw_value), 2) != i2c::ERROR_OK) {
ESP_LOGW(TAG, "Read data failed");
f(NAN);
return;
}
raw_value = i2c::i2ctohs(raw_value);
float lx = float(raw_value) / 1.2f;
lx *= 69.0f / mtreg;
if (mode == BH1750_MODE_H2)
lx /= 2.0f;
f(lx);
});
}
void BH1750Sensor::dump_config() {
@@ -82,188 +124,45 @@ void BH1750Sensor::dump_config() {
}
void BH1750Sensor::update() {
const uint32_t now = millis();
// Start coarse measurement to determine optimal mode/mtreg
if (this->state_ != IDLE) {
// Safety timeout: reset if stuck
if (now - this->measurement_start_time_ > MEASUREMENT_TIMEOUT_MS) {
ESP_LOGW(TAG, "Measurement timeout, resetting state");
this->state_ = IDLE;
} else {
ESP_LOGW(TAG, "Previous measurement not complete, skipping update");
// first do a quick measurement in L-mode with full range
// to find right range
this->read_lx_(BH1750_MODE_L, 31, [this](float val) {
if (std::isnan(val)) {
this->status_set_warning();
this->publish_state(NAN);
return;
}
}
if (!this->start_measurement_(BH1750_MODE_L, MTREG_MIN, now)) {
this->status_set_warning();
this->publish_state(NAN);
return;
}
this->state_ = WAITING_COARSE_MEASUREMENT;
this->enable_loop(); // Enable loop while measurement in progress
}
void BH1750Sensor::loop() {
const uint32_t now = App.get_loop_component_start_time();
switch (this->state_) {
case IDLE:
// Disable loop when idle to save cycles
this->disable_loop();
break;
case WAITING_COARSE_MEASUREMENT:
if (now - this->measurement_start_time_ >= this->measurement_duration_) {
this->state_ = READING_COARSE_RESULT;
}
break;
case READING_COARSE_RESULT: {
float lx;
if (!this->read_measurement_(lx)) {
this->fail_and_reset_();
break;
}
this->process_coarse_result_(lx);
// Start fine measurement with optimal settings
if (!this->start_measurement_(this->fine_mode_, this->fine_mtreg_, now)) {
this->fail_and_reset_();
break;
}
this->state_ = WAITING_FINE_MEASUREMENT;
break;
BH1750Mode use_mode;
uint8_t use_mtreg;
if (val <= 7000) {
use_mode = BH1750_MODE_H2;
use_mtreg = 254;
} else {
use_mode = BH1750_MODE_H;
// lx = counts / 1.2 * (69 / mtreg)
// -> mtreg = counts / 1.2 * (69 / lx)
// calculate for counts=50000 (allow some range to not saturate, but maximize mtreg)
// -> mtreg = 50000*(10/12)*(69/lx)
int ideal_mtreg = 50000 * 10 * 69 / (12 * (int) val);
use_mtreg = std::min(254, std::max(31, ideal_mtreg));
}
ESP_LOGV(TAG, "L result: %f -> Calculated mode=%d, mtreg=%d", val, (int) use_mode, use_mtreg);
case WAITING_FINE_MEASUREMENT:
if (now - this->measurement_start_time_ >= this->measurement_duration_) {
this->state_ = READING_FINE_RESULT;
this->read_lx_(use_mode, use_mtreg, [this](float val) {
if (std::isnan(val)) {
this->status_set_warning();
this->publish_state(NAN);
return;
}
break;
case READING_FINE_RESULT: {
float lx;
if (!this->read_measurement_(lx)) {
this->fail_and_reset_();
break;
}
ESP_LOGD(TAG, "'%s': Illuminance=%.1flx", this->get_name().c_str(), lx);
ESP_LOGD(TAG, "'%s': Illuminance=%.1flx", this->get_name().c_str(), val);
this->status_clear_warning();
this->publish_state(lx);
this->state_ = IDLE;
break;
}
}
}
bool BH1750Sensor::start_measurement_(BH1750Mode mode, uint8_t mtreg, uint32_t now) {
// Power on
uint8_t turn_on = BH1750_COMMAND_POWER_ON;
if (this->write(&turn_on, 1) != i2c::ERROR_OK) {
ESP_LOGW(TAG, "Power on failed");
return false;
}
// Set MTreg if changed
if (this->active_mtreg_ != mtreg) {
uint8_t mtreg_hi = BH1750_COMMAND_MT_REG_HI | ((mtreg >> MTREG_HI_SHIFT) & MTREG_HI_MASK);
uint8_t mtreg_lo = BH1750_COMMAND_MT_REG_LO | ((mtreg >> MTREG_LO_SHIFT) & MTREG_LO_MASK);
if (this->write(&mtreg_hi, 1) != i2c::ERROR_OK || this->write(&mtreg_lo, 1) != i2c::ERROR_OK) {
ESP_LOGW(TAG, "Set measurement time failed");
this->active_mtreg_ = 0;
return false;
}
this->active_mtreg_ = mtreg;
}
// Start measurement
uint8_t cmd;
uint16_t meas_time;
switch (mode) {
case BH1750_MODE_L:
cmd = BH1750_COMMAND_ONE_TIME_L;
meas_time = MEAS_TIME_L_MS * mtreg / MTREG_DEFAULT;
break;
case BH1750_MODE_H:
cmd = BH1750_COMMAND_ONE_TIME_H;
meas_time = MEAS_TIME_H_MS * mtreg / MTREG_DEFAULT;
break;
case BH1750_MODE_H2:
cmd = BH1750_COMMAND_ONE_TIME_H2;
meas_time = MEAS_TIME_H_MS * mtreg / MTREG_DEFAULT;
break;
default:
return false;
}
if (this->write(&cmd, 1) != i2c::ERROR_OK) {
ESP_LOGW(TAG, "Start measurement failed");
return false;
}
// Store current measurement parameters
this->current_mode_ = mode;
this->current_mtreg_ = mtreg;
this->measurement_start_time_ = now;
this->measurement_duration_ = meas_time + 1; // Add 1ms for safety
return true;
}
bool BH1750Sensor::read_measurement_(float &lx_out) {
uint16_t raw_value;
if (this->read(reinterpret_cast<uint8_t *>(&raw_value), 2) != i2c::ERROR_OK) {
ESP_LOGW(TAG, "Read data failed");
return false;
}
raw_value = i2c::i2ctohs(raw_value);
float lx = float(raw_value) / RESOLUTION_DIVISOR;
lx *= float(MTREG_DEFAULT) / this->current_mtreg_;
if (this->current_mode_ == BH1750_MODE_H2) {
lx /= MODE_H2_DIVISOR;
}
lx_out = lx;
return true;
}
void BH1750Sensor::process_coarse_result_(float lx) {
if (std::isnan(lx)) {
// Use defaults if coarse measurement failed
this->fine_mode_ = BH1750_MODE_H2;
this->fine_mtreg_ = MTREG_MAX;
return;
}
if (lx <= HIGH_LIGHT_THRESHOLD_LX) {
this->fine_mode_ = BH1750_MODE_H2;
this->fine_mtreg_ = MTREG_MAX;
} else {
this->fine_mode_ = BH1750_MODE_H;
// lx = counts / 1.2 * (69 / mtreg)
// -> mtreg = counts / 1.2 * (69 / lx)
// calculate for counts=50000 (allow some range to not saturate, but maximize mtreg)
// -> mtreg = 50000*(10/12)*(69/lx)
int ideal_mtreg = COUNTS_TARGET * COUNTS_NUMERATOR * MTREG_DEFAULT / (COUNTS_DENOMINATOR * (int) lx);
this->fine_mtreg_ = std::min((int) MTREG_MAX, std::max((int) MTREG_MIN, ideal_mtreg));
}
ESP_LOGV(TAG, "L result: %.1f -> Calculated mode=%d, mtreg=%d", lx, (int) this->fine_mode_, this->fine_mtreg_);
}
void BH1750Sensor::fail_and_reset_() {
this->status_set_warning();
this->publish_state(NAN);
this->state_ = IDLE;
this->publish_state(val);
});
});
}
float BH1750Sensor::get_setup_priority() const { return setup_priority::DATA; }
} // namespace esphome::bh1750
} // namespace bh1750
} // namespace esphome

View File

@@ -4,9 +4,10 @@
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/i2c/i2c.h"
namespace esphome::bh1750 {
namespace esphome {
namespace bh1750 {
enum BH1750Mode : uint8_t {
enum BH1750Mode {
BH1750_MODE_L,
BH1750_MODE_H,
BH1750_MODE_H2,
@@ -20,36 +21,13 @@ class BH1750Sensor : public sensor::Sensor, public PollingComponent, public i2c:
void setup() override;
void dump_config() override;
void update() override;
void loop() override;
float get_setup_priority() const override;
protected:
// State machine states
enum State : uint8_t {
IDLE,
WAITING_COARSE_MEASUREMENT,
READING_COARSE_RESULT,
WAITING_FINE_MEASUREMENT,
READING_FINE_RESULT,
};
void read_lx_(BH1750Mode mode, uint8_t mtreg, const std::function<void(float)> &f);
// 4-byte aligned members
uint32_t measurement_start_time_{0};
uint32_t measurement_duration_{0};
// 1-byte members grouped together to minimize padding
State state_{IDLE};
BH1750Mode current_mode_{BH1750_MODE_L};
uint8_t current_mtreg_{31};
BH1750Mode fine_mode_{BH1750_MODE_H2};
uint8_t fine_mtreg_{254};
uint8_t active_mtreg_{0};
// Helper methods
bool start_measurement_(BH1750Mode mode, uint8_t mtreg, uint32_t now);
bool read_measurement_(float &lx_out);
void process_coarse_result_(float lx);
void fail_and_reset_();
};
} // namespace esphome::bh1750
} // namespace bh1750
} // namespace esphome

View File

@@ -1,11 +1,12 @@
#include "automation.h"
#include "esphome/core/log.h"
namespace esphome::binary_sensor {
namespace esphome {
namespace binary_sensor {
static const char *const TAG = "binary_sensor.automation";
void MultiClickTrigger::on_state_(bool state) {
void binary_sensor::MultiClickTrigger::on_state_(bool state) {
// Handle duplicate events
if (state == this->last_state_) {
return;
@@ -66,7 +67,7 @@ void MultiClickTrigger::on_state_(bool state) {
*this->at_index_ = *this->at_index_ + 1;
}
void MultiClickTrigger::schedule_cooldown_() {
void binary_sensor::MultiClickTrigger::schedule_cooldown_() {
ESP_LOGV(TAG, "Multi Click: Invalid length of press, starting cooldown of %" PRIu32 " ms", this->invalid_cooldown_);
this->is_in_cooldown_ = true;
this->set_timeout("cooldown", this->invalid_cooldown_, [this]() {
@@ -78,7 +79,7 @@ void MultiClickTrigger::schedule_cooldown_() {
this->cancel_timeout("is_valid");
this->cancel_timeout("is_not_valid");
}
void MultiClickTrigger::schedule_is_valid_(uint32_t min_length) {
void binary_sensor::MultiClickTrigger::schedule_is_valid_(uint32_t min_length) {
if (min_length == 0) {
this->is_valid_ = true;
return;
@@ -89,19 +90,19 @@ void MultiClickTrigger::schedule_is_valid_(uint32_t min_length) {
this->is_valid_ = true;
});
}
void MultiClickTrigger::schedule_is_not_valid_(uint32_t max_length) {
void binary_sensor::MultiClickTrigger::schedule_is_not_valid_(uint32_t max_length) {
this->set_timeout("is_not_valid", max_length, [this]() {
ESP_LOGV(TAG, "Multi Click: You waited too long to %s.", this->parent_->state ? "RELEASE" : "PRESS");
this->is_valid_ = false;
this->schedule_cooldown_();
});
}
void MultiClickTrigger::cancel() {
void binary_sensor::MultiClickTrigger::cancel() {
ESP_LOGV(TAG, "Multi Click: Sequence explicitly cancelled.");
this->is_valid_ = false;
this->schedule_cooldown_();
}
void MultiClickTrigger::trigger_() {
void binary_sensor::MultiClickTrigger::trigger_() {
ESP_LOGV(TAG, "Multi Click: Hooray, multi click is valid. Triggering!");
this->at_index_.reset();
this->cancel_timeout("trigger");
@@ -117,4 +118,5 @@ bool match_interval(uint32_t min_length, uint32_t max_length, uint32_t length) {
return length >= min_length && length <= max_length;
}
}
} // namespace esphome::binary_sensor
} // namespace binary_sensor
} // namespace esphome

View File

@@ -9,7 +9,8 @@
#include "esphome/core/helpers.h"
#include "esphome/components/binary_sensor/binary_sensor.h"
namespace esphome::binary_sensor {
namespace esphome {
namespace binary_sensor {
struct MultiClickTriggerEvent {
bool state;
@@ -171,4 +172,5 @@ template<typename... Ts> class BinarySensorInvalidateAction : public Action<Ts..
BinarySensor *sensor_;
};
} // namespace esphome::binary_sensor
} // namespace binary_sensor
} // namespace esphome

View File

@@ -3,7 +3,9 @@
#include "esphome/core/controller_registry.h"
#include "esphome/core/log.h"
namespace esphome::binary_sensor {
namespace esphome {
namespace binary_sensor {
static const char *const TAG = "binary_sensor";
@@ -61,4 +63,6 @@ void BinarySensor::add_filters(std::initializer_list<Filter *> filters) {
}
bool BinarySensor::is_status_binary_sensor() const { return false; }
} // namespace esphome::binary_sensor
} // namespace binary_sensor
} // namespace esphome

View File

@@ -6,7 +6,9 @@
#include <initializer_list>
namespace esphome::binary_sensor {
namespace esphome {
namespace binary_sensor {
class BinarySensor;
void log_binary_sensor(const char *tag, const char *prefix, const char *type, BinarySensor *obj);
@@ -68,4 +70,5 @@ class BinarySensorInitiallyOff : public BinarySensor {
bool has_state() const override { return true; }
};
} // namespace esphome::binary_sensor
} // namespace binary_sensor
} // namespace esphome

View File

@@ -2,7 +2,9 @@
#include "binary_sensor.h"
namespace esphome::binary_sensor {
namespace esphome {
namespace binary_sensor {
static const char *const TAG = "sensor.filter";
@@ -130,4 +132,6 @@ optional<bool> SettleFilter::new_value(bool value) {
float SettleFilter::get_setup_priority() const { return setup_priority::HARDWARE; }
} // namespace esphome::binary_sensor
} // namespace binary_sensor
} // namespace esphome

View File

@@ -4,7 +4,9 @@
#include "esphome/core/component.h"
#include "esphome/core/helpers.h"
namespace esphome::binary_sensor {
namespace esphome {
namespace binary_sensor {
class BinarySensor;
@@ -137,4 +139,6 @@ class SettleFilter : public Filter, public Component {
bool steady_{true};
};
} // namespace esphome::binary_sensor
} // namespace binary_sensor
} // namespace esphome

View File

@@ -122,19 +122,16 @@ template<typename... Ts> class BLEClientWriteAction : public Action<Ts...>, publ
void play_complex(const Ts &...x) override {
this->num_running_++;
this->var_ = std::make_tuple(x...);
bool result;
std::vector<uint8_t> value;
if (this->len_ >= 0) {
// Static mode: write directly from flash pointer
result = this->write(this->value_.data, this->len_);
// Static mode: copy from flash to vector
value.assign(this->value_.data, this->value_.data + this->len_);
} else {
// Template mode: call function and write the vector
std::vector<uint8_t> value = this->value_.func(x...);
result = this->write(value);
// Template mode: call function
value = this->value_.func(x...);
}
// on write failure, continue the automation chain rather than stopping so that e.g. disconnect can work.
if (!result)
if (!write(value))
this->play_next_(x...);
}
@@ -147,15 +144,15 @@ template<typename... Ts> class BLEClientWriteAction : public Action<Ts...>, publ
* errors.
*/
// initiate the write. Return true if all went well, will be followed by a WRITE_CHAR event.
bool write(const uint8_t *data, size_t len) {
bool write(const std::vector<uint8_t> &value) {
if (this->node_state != espbt::ClientState::ESTABLISHED) {
esph_log_w(Automation::TAG, "Cannot write to BLE characteristic - not connected");
return false;
}
esph_log_vv(Automation::TAG, "Will write %d bytes: %s", len, format_hex_pretty(data, len).c_str());
esp_err_t err =
esp_ble_gattc_write_char(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), this->char_handle_, len,
const_cast<uint8_t *>(data), this->write_type_, ESP_GATT_AUTH_REQ_NONE);
esph_log_vv(Automation::TAG, "Will write %d bytes: %s", value.size(), format_hex_pretty(value).c_str());
esp_err_t err = esp_ble_gattc_write_char(this->parent()->get_gattc_if(), this->parent()->get_conn_id(),
this->char_handle_, value.size(), const_cast<uint8_t *>(value.data()),
this->write_type_, ESP_GATT_AUTH_REQ_NONE);
if (err != ESP_OK) {
esph_log_e(Automation::TAG, "Error writing to characteristic: %s!", esp_err_to_name(err));
return false;
@@ -163,8 +160,6 @@ template<typename... Ts> class BLEClientWriteAction : public Action<Ts...>, publ
return true;
}
bool write(const std::vector<uint8_t> &value) { return this->write(value.data(), value.size()); }
void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
esp_ble_gattc_cb_param_t *param) override {
switch (event) {

View File

@@ -72,6 +72,16 @@ def _final_validate(config: ConfigType) -> ConfigType:
"Add 'ap:' to your WiFi configuration to enable the captive portal."
)
# Register socket needs for DNS server and additional HTTP connections
# - 1 UDP socket for DNS server
# - 3 additional TCP sockets for captive portal detection probes + configuration requests
# OS captive portal detection makes multiple probe requests that stay in TIME_WAIT.
# Need headroom for actual user configuration requests.
# LRU purging will reclaim idle sockets to prevent exhaustion from repeated attempts.
from esphome.components import socket
socket.consume_sockets(4, "captive_portal")(config)
return config

View File

@@ -50,8 +50,8 @@ void CaptivePortal::handle_wifisave(AsyncWebServerRequest *request) {
ESP_LOGI(TAG, "Requested WiFi Settings Change:");
ESP_LOGI(TAG, " SSID='%s'", ssid.c_str());
ESP_LOGI(TAG, " Password=" LOG_SECRET("'%s'"), psk.c_str());
wifi::global_wifi_component->save_wifi_sta(ssid, psk);
wifi::global_wifi_component->start_scanning();
// Defer save to main loop thread to avoid NVS operations from HTTP thread
this->defer([ssid, psk]() { wifi::global_wifi_component->save_wifi_sta(ssid, psk); });
request->redirect(ESPHOME_F("/?save"));
}
@@ -63,6 +63,12 @@ void CaptivePortal::start() {
this->base_->init();
if (!this->initialized_) {
this->base_->add_handler(this);
#ifdef USE_ESP32
// Enable LRU socket purging to handle captive portal detection probe bursts
// OS captive portal detection makes many simultaneous HTTP requests which can
// exhaust sockets. LRU purging automatically closes oldest idle connections.
this->base_->get_server()->set_lru_purge_enable(true);
#endif
}
network::IPAddress ip = wifi::global_wifi_component->wifi_soft_ap_ip();

View File

@@ -40,6 +40,10 @@ class CaptivePortal : public AsyncWebHandler, public Component {
void end() {
this->active_ = false;
this->disable_loop(); // Stop processing DNS requests
#ifdef USE_ESP32
// Disable LRU socket purging now that captive portal is done
this->base_->get_server()->set_lru_purge_enable(false);
#endif
this->base_->deinit();
if (this->dns_server_ != nullptr) {
this->dns_server_->stop();

View File

@@ -4,7 +4,8 @@
#include "esphome/core/automation.h"
#include "cover.h"
namespace esphome::cover {
namespace esphome {
namespace cover {
template<typename... Ts> class OpenAction : public Action<Ts...> {
public:
@@ -130,4 +131,5 @@ class CoverClosedTrigger : public Trigger<> {
}
};
} // namespace esphome::cover
} // namespace cover
} // namespace esphome

View File

@@ -6,7 +6,8 @@
#include "esphome/core/log.h"
namespace esphome::cover {
namespace esphome {
namespace cover {
static const char *const TAG = "cover";
@@ -211,4 +212,5 @@ void CoverRestoreState::apply(Cover *cover) {
cover->publish_state();
}
} // namespace esphome::cover
} // namespace cover
} // namespace esphome

View File

@@ -7,7 +7,8 @@
#include "cover_traits.h"
namespace esphome::cover {
namespace esphome {
namespace cover {
const extern float COVER_OPEN;
const extern float COVER_CLOSED;
@@ -156,4 +157,5 @@ class Cover : public EntityBase, public EntityBase_DeviceClass {
ESPPreferenceObject rtc_;
};
} // namespace esphome::cover
} // namespace cover
} // namespace esphome

View File

@@ -1,6 +1,7 @@
#pragma once
namespace esphome::cover {
namespace esphome {
namespace cover {
class CoverTraits {
public:
@@ -25,4 +26,5 @@ class CoverTraits {
bool supports_stop_{false};
};
} // namespace esphome::cover
} // namespace cover
} // namespace esphome

View File

@@ -19,8 +19,9 @@ void CST816Touchscreen::continue_setup_() {
case CST816T_CHIP_ID:
break;
default:
ESP_LOGE(TAG, "Unknown chip ID: 0x%02X", this->chip_id_);
this->status_set_error("Unknown chip ID");
this->mark_failed();
this->status_set_error(str_sprintf("Unknown chip ID 0x%02X", this->chip_id_).c_str());
return;
}
this->write_byte(REG_IRQ_CTL, IRQ_EN_MOTION);

View File

@@ -3,10 +3,10 @@
namespace esphome {
namespace dashboard_import {
static const char *g_package_import_url = ""; // NOLINT
static std::string g_package_import_url; // NOLINT
const char *get_package_import_url() { return g_package_import_url; }
void set_package_import_url(const char *url) { g_package_import_url = url; }
const std::string &get_package_import_url() { return g_package_import_url; }
void set_package_import_url(std::string url) { g_package_import_url = std::move(url); }
} // namespace dashboard_import
} // namespace esphome

View File

@@ -1,10 +1,12 @@
#pragma once
#include <string>
namespace esphome {
namespace dashboard_import {
const char *get_package_import_url();
void set_package_import_url(const char *url);
const std::string &get_package_import_url();
void set_package_import_url(std::string url);
} // namespace dashboard_import
} // namespace esphome

View File

@@ -102,7 +102,7 @@ def customise_schema(config):
"""
config = cv.Schema(
{
cv.Required(CONF_MODEL): cv.one_of(*MODELS, upper=True),
cv.Required(CONF_MODEL): cv.one_of(*MODELS, upper=True, space="-"),
},
extra=cv.ALLOW_EXTRA,
)(config)

View File

@@ -32,11 +32,15 @@ class SpectraE6(EpaperModel):
spectra_e6 = SpectraE6("spectra-e6")
spectra_e6.extend(
"Seeed-reTerminal-E1002",
spectra_e6_7p3 = spectra_e6.extend(
"7.3in-Spectra-E6",
width=800,
height=480,
data_rate="20MHz",
)
spectra_e6_7p3.extend(
"Seeed-reTerminal-E1002",
cs_pin=10,
dc_pin=11,
reset_pin=12,

View File

@@ -854,6 +854,10 @@ def _configure_lwip_max_sockets(conf: dict) -> None:
async def to_code(config):
cg.add_platformio_option("board", config[CONF_BOARD])
cg.add_platformio_option("board_upload.flash_size", config[CONF_FLASH_SIZE])
cg.add_platformio_option(
"board_upload.maximum_size",
int(config[CONF_FLASH_SIZE].removesuffix("MB")) * 1024 * 1024,
)
cg.set_cpp_standard("gnu++20")
cg.add_build_flag("-DUSE_ESP32")
cg.add_define("ESPHOME_BOARD", config[CONF_BOARD])
@@ -883,6 +887,12 @@ async def to_code(config):
CORE.relative_internal_path(".espressif")
)
add_extra_script(
"pre",
"pre_build.py",
Path(__file__).parent / "pre_build.py.script",
)
add_extra_script(
"post",
"post_build.py",
@@ -931,6 +941,12 @@ async def to_code(config):
add_idf_sdkconfig_option("CONFIG_MBEDTLS_CERTIFICATE_BUNDLE", True)
add_idf_sdkconfig_option("CONFIG_ESP_PHY_REDUCE_TX_POWER", True)
# ESP32-S2 Arduino: Disable USB Serial on boot to avoid TinyUSB dependency
if get_esp32_variant() == VARIANT_ESP32S2:
cg.add_build_unflag("-DARDUINO_USB_CDC_ON_BOOT=1")
cg.add_build_unflag("-DARDUINO_USB_CDC_ON_BOOT=0")
cg.add_build_flag("-DARDUINO_USB_CDC_ON_BOOT=0")
cg.add_build_flag("-Wno-nonnull-compare")
add_idf_sdkconfig_option(f"CONFIG_IDF_TARGET_{variant}", True)

View File

@@ -0,0 +1,9 @@
Import("env") # noqa: F821
# Remove custom_sdkconfig from the board config as it causes
# pioarduino to enable some strange hybrid build mode that breaks IDF
board = env.BoardConfig()
if "espidf.custom_sdkconfig" in board:
del board._manifest["espidf"]["custom_sdkconfig"]
if not board._manifest["espidf"]:
del board._manifest["espidf"]

View File

@@ -96,10 +96,6 @@ void ESP32BLE::advertising_set_service_data(const std::vector<uint8_t> &data) {
}
void ESP32BLE::advertising_set_manufacturer_data(const std::vector<uint8_t> &data) {
this->advertising_set_manufacturer_data(std::span<const uint8_t>(data));
}
void ESP32BLE::advertising_set_manufacturer_data(std::span<const uint8_t> data) {
this->advertising_init_();
this->advertising_->set_manufacturer_data(data);
this->advertising_start();
@@ -638,13 +634,11 @@ void ESP32BLE::dump_config() {
io_capability_s = "invalid";
break;
}
char mac_s[18];
format_mac_addr_upper(mac_address, mac_s);
ESP_LOGCONFIG(TAG,
"BLE:\n"
" MAC address: %s\n"
" IO Capability: %s",
mac_s, io_capability_s);
format_mac_address_pretty(mac_address).c_str(), io_capability_s);
} else {
ESP_LOGCONFIG(TAG, "Bluetooth stack is not enabled");
}

View File

@@ -118,7 +118,6 @@ class ESP32BLE : public Component {
void advertising_start();
void advertising_set_service_data(const std::vector<uint8_t> &data);
void advertising_set_manufacturer_data(const std::vector<uint8_t> &data);
void advertising_set_manufacturer_data(std::span<const uint8_t> data);
void advertising_set_appearance(uint16_t appearance) { this->appearance_ = appearance; }
void advertising_set_service_data_and_name(std::span<const uint8_t> data, bool include_name);
void advertising_add_service_uuid(ESPBTUUID uuid);

View File

@@ -59,10 +59,6 @@ void BLEAdvertising::set_service_data(const std::vector<uint8_t> &data) {
}
void BLEAdvertising::set_manufacturer_data(const std::vector<uint8_t> &data) {
this->set_manufacturer_data(std::span<const uint8_t>(data));
}
void BLEAdvertising::set_manufacturer_data(std::span<const uint8_t> data) {
delete[] this->advertising_data_.p_manufacturer_data;
this->advertising_data_.p_manufacturer_data = nullptr;
this->advertising_data_.manufacturer_len = data.size();

View File

@@ -37,7 +37,6 @@ class BLEAdvertising {
void set_scan_response(bool scan_response) { this->scan_response_ = scan_response; }
void set_min_preferred_interval(uint16_t interval) { this->advertising_data_.min_interval = interval; }
void set_manufacturer_data(const std::vector<uint8_t> &data);
void set_manufacturer_data(std::span<const uint8_t> data);
void set_appearance(uint16_t appearance) { this->advertising_data_.appearance = appearance; }
void set_service_data(const std::vector<uint8_t> &data);
void set_service_data(std::span<const uint8_t> data);

View File

@@ -1,6 +1,5 @@
#include "esp32_ble_beacon.h"
#include "esphome/core/log.h"
#include "esphome/core/helpers.h"
#ifdef USE_ESP32

View File

@@ -15,10 +15,7 @@ Trigger<std::vector<uint8_t>, uint16_t> *BLETriggers::create_characteristic_on_w
Trigger<std::vector<uint8_t>, uint16_t> *on_write_trigger = // NOLINT(cppcoreguidelines-owning-memory)
new Trigger<std::vector<uint8_t>, uint16_t>();
characteristic->on_write([on_write_trigger](std::span<const uint8_t> data, uint16_t id) {
// Convert span to vector for trigger - copy is necessary because:
// 1. Trigger stores the data for use in automation actions that execute later
// 2. The span is only valid during this callback (points to temporary BLE stack data)
// 3. User lambdas in automations need persistent data they can access asynchronously
// Convert span to vector for trigger
on_write_trigger->trigger(std::vector<uint8_t>(data.begin(), data.end()), id);
});
return on_write_trigger;
@@ -30,10 +27,7 @@ Trigger<std::vector<uint8_t>, uint16_t> *BLETriggers::create_descriptor_on_write
Trigger<std::vector<uint8_t>, uint16_t> *on_write_trigger = // NOLINT(cppcoreguidelines-owning-memory)
new Trigger<std::vector<uint8_t>, uint16_t>();
descriptor->on_write([on_write_trigger](std::span<const uint8_t> data, uint16_t id) {
// Convert span to vector for trigger - copy is necessary because:
// 1. Trigger stores the data for use in automation actions that execute later
// 2. The span is only valid during this callback (points to temporary BLE stack data)
// 3. User lambdas in automations need persistent data they can access asynchronously
// Convert span to vector for trigger
on_write_trigger->trigger(std::vector<uint8_t>(data.begin(), data.end()), id);
});
return on_write_trigger;

View File

@@ -22,6 +22,11 @@ constexpr size_t CHUNK_SIZE = 1500;
void Esp32HostedUpdate::setup() {
this->update_info_.title = "ESP32 Hosted Coprocessor";
// if wifi is not present, connect to the coprocessor
#ifndef USE_WIFI
esp_hosted_connect_to_slave(); // NOLINT
#endif
// get coprocessor version
esp_hosted_coprocessor_fwver_t ver_info;
if (esp_hosted_get_coprocessor_fwversion(&ver_info) == ESP_OK) {

View File

@@ -20,6 +20,10 @@ CONF_ON_STOP = "on_stop"
CONF_STATUS_INDICATOR = "status_indicator"
CONF_WIFI_TIMEOUT = "wifi_timeout"
# Default WiFi timeout - aligned with WiFi component ap_timeout
# Allows sufficient time to try all BSSIDs before starting provisioning mode
DEFAULT_WIFI_TIMEOUT = "90s"
improv_ns = cg.esphome_ns.namespace("improv")
Error = improv_ns.enum("Error")
@@ -59,7 +63,7 @@ CONFIG_SCHEMA = (
CONF_AUTHORIZED_DURATION, default="1min"
): cv.positive_time_period_milliseconds,
cv.Optional(
CONF_WIFI_TIMEOUT, default="1min"
CONF_WIFI_TIMEOUT, default=DEFAULT_WIFI_TIMEOUT
): cv.positive_time_period_milliseconds,
cv.Optional(CONF_ON_PROVISIONED): automation.validate_automation(
{

View File

@@ -127,6 +127,7 @@ void ESP32ImprovComponent::loop() {
// Set initial state based on whether we have an authorizer
this->set_state_(this->get_initial_state_(), false);
this->set_error_(improv::ERROR_NONE);
this->should_start_ = false; // Clear flag after starting
ESP_LOGD(TAG, "Service started!");
}
}

View File

@@ -45,6 +45,7 @@ class ESP32ImprovComponent : public Component, public improv_base::ImprovBase {
void start();
void stop();
bool is_active() const { return this->state_ != improv::STATE_STOPPED; }
bool should_start() const { return this->should_start_; }
#ifdef USE_ESP32_IMPROV_STATE_CALLBACK
void add_on_state_callback(std::function<void(improv::State, improv::Error)> &&callback) {

View File

@@ -14,8 +14,8 @@ void EspLdo::setup() {
config.flags.adjustable = this->adjustable_;
auto err = esp_ldo_acquire_channel(&config, &this->handle_);
if (err != ESP_OK) {
auto msg = str_sprintf("Failed to acquire LDO channel %d with voltage %fV", this->channel_, this->voltage_);
this->mark_failed(msg.c_str());
ESP_LOGE(TAG, "Failed to acquire LDO channel %d with voltage %fV", this->channel_, this->voltage_);
this->mark_failed("Failed to acquire LDO channel");
} else {
ESP_LOGD(TAG, "Acquired LDO channel %d with voltage %fV", this->channel_, this->voltage_);
}

View File

@@ -10,6 +10,7 @@
#include <esp_event.h>
#include <esp_mac.h>
#include <esp_netif.h>
#include <esp_now.h>
#include <esp_random.h>
#include <esp_wifi.h>
@@ -157,6 +158,12 @@ bool ESPNowComponent::is_wifi_enabled() {
}
void ESPNowComponent::setup() {
#ifndef USE_WIFI
// Initialize LwIP stack for wake_loop_threadsafe() socket support
// When WiFi component is present, it handles esp_netif_init()
ESP_ERROR_CHECK(esp_netif_init());
#endif
if (this->enable_on_boot_) {
this->enable_();
} else {

View File

@@ -383,7 +383,6 @@ async def to_code(config):
cg.add(var.set_use_address(config[CONF_USE_ADDRESS]))
if CONF_MANUAL_IP in config:
cg.add_define("USE_ETHERNET_MANUAL_IP")
cg.add(var.set_manual_ip(manual_ip(config[CONF_MANUAL_IP])))
# Add compile-time define for PHY types with specific code

View File

@@ -553,14 +553,11 @@ void EthernetComponent::start_connect_() {
}
esp_netif_ip_info_t info;
#ifdef USE_ETHERNET_MANUAL_IP
if (this->manual_ip_.has_value()) {
info.ip = this->manual_ip_->static_ip;
info.gw = this->manual_ip_->gateway;
info.netmask = this->manual_ip_->subnet;
} else
#endif
{
} else {
info.ip.addr = 0;
info.gw.addr = 0;
info.netmask.addr = 0;
@@ -581,7 +578,6 @@ void EthernetComponent::start_connect_() {
err = esp_netif_set_ip_info(this->eth_netif_, &info);
ESPHL_ERROR_CHECK(err, "DHCPC set IP info error");
#ifdef USE_ETHERNET_MANUAL_IP
if (this->manual_ip_.has_value()) {
LwIPLock lock;
if (this->manual_ip_->dns1.is_set()) {
@@ -594,9 +590,7 @@ void EthernetComponent::start_connect_() {
d = this->manual_ip_->dns2;
dns_setserver(1, &d);
}
} else
#endif
{
} else {
err = esp_netif_dhcpc_start(this->eth_netif_);
if (err != ESP_ERR_ESP_NETIF_DHCP_ALREADY_STARTED) {
ESPHL_ERROR_CHECK(err, "DHCPC start error");
@@ -694,9 +688,7 @@ void EthernetComponent::set_clk_mode(emac_rmii_clock_mode_t clk_mode) { this->cl
void EthernetComponent::add_phy_register(PHYRegister register_value) { this->phy_registers_.push_back(register_value); }
#endif
void EthernetComponent::set_type(EthernetType type) { this->type_ = type; }
#ifdef USE_ETHERNET_MANUAL_IP
void EthernetComponent::set_manual_ip(const ManualIP &manual_ip) { this->manual_ip_ = manual_ip; }
#endif
// set_use_address() is guaranteed to be called during component setup by Python code generation,
// so use_address_ will always be valid when get_use_address() is called - no fallback needed.

View File

@@ -82,9 +82,7 @@ class EthernetComponent : public Component {
void add_phy_register(PHYRegister register_value);
#endif
void set_type(EthernetType type);
#ifdef USE_ETHERNET_MANUAL_IP
void set_manual_ip(const ManualIP &manual_ip);
#endif
void set_fixed_mac(const std::array<uint8_t, 6> &mac) { this->fixed_mac_ = mac; }
network::IPAddresses get_ip_addresses();
@@ -139,9 +137,7 @@ class EthernetComponent : public Component {
uint8_t mdc_pin_{23};
uint8_t mdio_pin_{18};
#endif
#ifdef USE_ETHERNET_MANUAL_IP
optional<ManualIP> manual_ip_{};
#endif
uint32_t connect_begin_;
// Group all uint8_t types together (enums and bools)

View File

@@ -36,6 +36,7 @@ from esphome.const import (
CONF_WEIGHT,
)
from esphome.core import CORE, HexInt
from esphome.helpers import cpp_string_escape
from esphome.types import ConfigType
_LOGGER = logging.getLogger(__name__)
@@ -49,6 +50,7 @@ font_ns = cg.esphome_ns.namespace("font")
Font = font_ns.class_("Font")
Glyph = font_ns.class_("Glyph")
GlyphData = font_ns.struct("GlyphData")
CONF_BPP = "bpp"
CONF_EXTRAS = "extras"
@@ -461,7 +463,7 @@ FONT_SCHEMA = cv.Schema(
)
),
cv.GenerateID(CONF_RAW_DATA_ID): cv.declare_id(cg.uint8),
cv.GenerateID(CONF_RAW_GLYPH_ID): cv.declare_id(Glyph),
cv.GenerateID(CONF_RAW_GLYPH_ID): cv.declare_id(GlyphData),
},
)
@@ -486,8 +488,6 @@ class GlyphInfo:
def glyph_to_glyphinfo(glyph, font, size, bpp):
# Convert to 32 bit unicode codepoint
glyph = ord(glyph)
scale = 256 // (1 << bpp)
if not font.is_scalable:
sizes = [pt_to_px(x.size) for x in font.available_sizes]
@@ -583,15 +583,22 @@ async def to_code(config):
# Create the glyph table that points to data in the above array.
glyph_initializer = [
[
x.glyph,
prog_arr + (y - len(x.bitmap_data)),
x.advance,
x.offset_x,
x.offset_y,
x.width,
x.height,
]
cg.StructInitializer(
GlyphData,
(
"a_char",
cg.RawExpression(f"(const uint8_t *){cpp_string_escape(x.glyph)}"),
),
(
"data",
cg.RawExpression(f"{str(prog_arr)} + {str(y - len(x.bitmap_data))}"),
),
("advance", x.advance),
("offset_x", x.offset_x),
("offset_y", x.offset_y),
("width", x.width),
("height", x.height),
)
for (x, y) in zip(
glyph_args, list(accumulate([len(x.bitmap_data) for x in glyph_args]))
)

View File

@@ -6,245 +6,133 @@
namespace esphome {
namespace font {
static const char *const TAG = "font";
#ifdef USE_LVGL_FONT
const uint8_t *Font::get_glyph_bitmap(const lv_font_t *font, uint32_t unicode_letter) {
auto *fe = (Font *) font->dsc;
const auto *gd = fe->get_glyph_data_(unicode_letter);
if (gd == nullptr) {
return nullptr;
const uint8_t *Glyph::get_char() const { return this->glyph_data_->a_char; }
// Compare the char at the string position with this char.
// Return true if this char is less than or equal the other.
bool Glyph::compare_to(const uint8_t *str) const {
// 1 -> this->char_
// 2 -> str
for (uint32_t i = 0;; i++) {
if (this->glyph_data_->a_char[i] == '\0')
return true;
if (str[i] == '\0')
return false;
if (this->glyph_data_->a_char[i] > str[i])
return false;
if (this->glyph_data_->a_char[i] < str[i])
return true;
}
return gd->data;
// this should not happen
return false;
}
int Glyph::match_length(const uint8_t *str) const {
for (uint32_t i = 0;; i++) {
if (this->glyph_data_->a_char[i] == '\0')
return i;
if (str[i] != this->glyph_data_->a_char[i])
return 0;
}
// this should not happen
return 0;
}
void Glyph::scan_area(int *x1, int *y1, int *width, int *height) const {
*x1 = this->glyph_data_->offset_x;
*y1 = this->glyph_data_->offset_y;
*width = this->glyph_data_->width;
*height = this->glyph_data_->height;
}
bool Font::get_glyph_dsc_cb(const lv_font_t *font, lv_font_glyph_dsc_t *dsc, uint32_t unicode_letter, uint32_t next) {
auto *fe = (Font *) font->dsc;
const auto *gd = fe->get_glyph_data_(unicode_letter);
if (gd == nullptr) {
return false;
}
dsc->adv_w = gd->advance;
dsc->ofs_x = gd->offset_x;
dsc->ofs_y = fe->height_ - gd->height - gd->offset_y - fe->lv_font_.base_line;
dsc->box_w = gd->width;
dsc->box_h = gd->height;
dsc->is_placeholder = 0;
dsc->bpp = fe->get_bpp();
return true;
}
const Glyph *Font::get_glyph_data_(uint32_t unicode_letter) {
if (unicode_letter == this->last_letter_ && this->last_letter_ != 0)
return this->last_data_;
auto *glyph = this->find_glyph(unicode_letter);
if (glyph == nullptr) {
return nullptr;
}
this->last_data_ = glyph;
this->last_letter_ = unicode_letter;
return glyph;
}
#endif
/**
* Attempt to extract a 32 bit Unicode codepoint from a UTF-8 string.
* If successful, return the codepoint and set the length to the number of bytes read.
* If the end of the string has been reached and a valid codepoint has not been found, return 0 and set the length to
* 0.
*
* @param utf8_str The input string
* @param length Pointer to length storage
* @return The extracted code point
*/
static uint32_t extract_unicode_codepoint(const char *utf8_str, size_t *length) {
// Safely cast to uint8_t* for correct bitwise operations on bytes
const uint8_t *current = reinterpret_cast<const uint8_t *>(utf8_str);
uint32_t code_point = 0;
uint8_t c1 = *current++;
// check for end of string
if (c1 == 0) {
*length = 0;
return 0;
}
// --- 1-Byte Sequence: 0xxxxxxx (ASCII) ---
if (c1 < 0x80) {
// Valid ASCII byte.
code_point = c1;
// Optimization: No need to check for continuation bytes.
}
// --- 2-Byte Sequence: 110xxxxx 10xxxxxx ---
else if ((c1 & 0xE0) == 0xC0) {
uint8_t c2 = *current++;
// Error Check 1: Check if c2 is a valid continuation byte (10xxxxxx)
if ((c2 & 0xC0) != 0x80) {
*length = 0;
return 0;
}
code_point = (c1 & 0x1F) << 6;
code_point |= (c2 & 0x3F);
// Error Check 2: Overlong check (2-byte must be > 0x7F)
if (code_point <= 0x7F) {
*length = 0;
return 0;
}
}
// --- 3-Byte Sequence: 1110xxxx 10xxxxxx 10xxxxxx ---
else if ((c1 & 0xF0) == 0xE0) {
uint8_t c2 = *current++;
uint8_t c3 = *current++;
// Error Check 1: Check continuation bytes
if (((c2 & 0xC0) != 0x80) || ((c3 & 0xC0) != 0x80)) {
*length = 0;
return 0;
}
code_point = (c1 & 0x0F) << 12;
code_point |= (c2 & 0x3F) << 6;
code_point |= (c3 & 0x3F);
// Error Check 2: Overlong check (3-byte must be > 0x7FF)
// Also check for surrogates (0xD800-0xDFFF)
if (code_point <= 0x7FF || (code_point >= 0xD800 && code_point <= 0xDFFF)) {
*length = 0;
return 0;
}
}
// --- 4-Byte Sequence: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx ---
else if ((c1 & 0xF8) == 0xF0) {
uint8_t c2 = *current++;
uint8_t c3 = *current++;
uint8_t c4 = *current++;
// Error Check 1: Check continuation bytes
if (((c2 & 0xC0) != 0x80) || ((c3 & 0xC0) != 0x80) || ((c4 & 0xC0) != 0x80)) {
*length = 0;
return 0;
}
code_point = (c1 & 0x07) << 18;
code_point |= (c2 & 0x3F) << 12;
code_point |= (c3 & 0x3F) << 6;
code_point |= (c4 & 0x3F);
// Error Check 2: Overlong check (4-byte must be > 0xFFFF)
// Also check for valid Unicode range (must be <= 0x10FFFF)
if (code_point <= 0xFFFF || code_point > 0x10FFFF) {
*length = 0;
return 0;
}
}
// --- Invalid leading byte (e.g., 10xxxxxx or 11111xxx) ---
else {
*length = 0;
return 0;
}
*length = current - reinterpret_cast<const uint8_t *>(utf8_str);
return code_point;
}
Font::Font(const Glyph *data, int data_nr, int baseline, int height, int descender, int xheight, int capheight,
Font::Font(const GlyphData *data, int data_nr, int baseline, int height, int descender, int xheight, int capheight,
uint8_t bpp)
: glyphs_(ConstVector(data, data_nr)),
baseline_(baseline),
: baseline_(baseline),
height_(height),
descender_(descender),
linegap_(height - baseline - descender),
xheight_(xheight),
capheight_(capheight),
bpp_(bpp) {
#ifdef USE_LVGL_FONT
this->lv_font_.dsc = this;
this->lv_font_.line_height = this->get_height();
this->lv_font_.base_line = this->lv_font_.line_height - this->get_baseline();
this->lv_font_.get_glyph_dsc = get_glyph_dsc_cb;
this->lv_font_.get_glyph_bitmap = get_glyph_bitmap;
this->lv_font_.subpx = LV_FONT_SUBPX_NONE;
this->lv_font_.underline_position = -1;
this->lv_font_.underline_thickness = 1;
#endif
glyphs_.reserve(data_nr);
for (int i = 0; i < data_nr; ++i)
glyphs_.emplace_back(&data[i]);
}
const Glyph *Font::find_glyph(uint32_t codepoint) const {
int Font::match_next_glyph(const uint8_t *str, int *match_length) {
int lo = 0;
int hi = this->glyphs_.size() - 1;
while (lo != hi) {
int mid = (lo + hi + 1) / 2;
if (this->glyphs_[mid].is_less_or_equal(codepoint)) {
if (this->glyphs_[mid].compare_to(str)) {
lo = mid;
} else {
hi = mid - 1;
}
}
auto *result = &this->glyphs_[lo];
if (result->code_point == codepoint)
return result;
return nullptr;
*match_length = this->glyphs_[lo].match_length(str);
if (*match_length <= 0)
return -1;
return lo;
}
#ifdef USE_DISPLAY
void Font::measure(const char *str, int *width, int *x_offset, int *baseline, int *height) {
*baseline = this->baseline_;
*height = this->height_;
int i = 0;
int min_x = 0;
bool has_char = false;
int x = 0;
for (;;) {
size_t length;
auto code_point = extract_unicode_codepoint(str, &length);
if (length == 0)
break;
str += length;
auto *glyph = this->find_glyph(code_point);
if (glyph == nullptr) {
while (str[i] != '\0') {
int match_length;
int glyph_n = this->match_next_glyph((const uint8_t *) str + i, &match_length);
if (glyph_n < 0) {
// Unknown char, skip
if (!this->glyphs_.empty())
x += this->glyphs_[0].advance;
if (!this->get_glyphs().empty())
x += this->get_glyphs()[0].glyph_data_->advance;
i++;
continue;
}
const Glyph &glyph = this->glyphs_[glyph_n];
if (!has_char) {
min_x = glyph->offset_x;
min_x = glyph.glyph_data_->offset_x;
} else {
min_x = std::min(min_x, x + glyph->offset_x);
min_x = std::min(min_x, x + glyph.glyph_data_->offset_x);
}
x += glyph->advance;
x += glyph.glyph_data_->advance;
i += match_length;
has_char = true;
}
*x_offset = min_x;
*width = x - min_x;
}
void Font::print(int x_start, int y_start, display::Display *display, Color color, const char *text, Color background) {
int i = 0;
int x_at = x_start;
for (;;) {
size_t length;
auto code_point = extract_unicode_codepoint(text, &length);
if (length == 0)
break;
text += length;
auto *glyph = this->find_glyph(code_point);
if (glyph == nullptr) {
int scan_x1, scan_y1, scan_width, scan_height;
while (text[i] != '\0') {
int match_length;
int glyph_n = this->match_next_glyph((const uint8_t *) text + i, &match_length);
if (glyph_n < 0) {
// Unknown char, skip
ESP_LOGW(TAG, "Codepoint 0x%08" PRIx32 " not found in font", code_point);
if (!this->glyphs_.empty()) {
uint8_t glyph_width = this->glyphs_[0].advance;
display->rectangle(x_at, y_start, glyph_width, this->height_, color);
ESP_LOGW(TAG, "Encountered character without representation in font: '%c'", text[i]);
if (!this->get_glyphs().empty()) {
uint8_t glyph_width = this->get_glyphs()[0].glyph_data_->advance;
display->filled_rectangle(x_at, y_start, glyph_width, this->height_, color);
x_at += glyph_width;
}
i++;
continue;
}
const uint8_t *data = glyph->data;
const int max_x = x_at + glyph->offset_x + glyph->width;
const int max_y = y_start + glyph->offset_y + glyph->height;
const Glyph &glyph = this->get_glyphs()[glyph_n];
glyph.scan_area(&scan_x1, &scan_y1, &scan_width, &scan_height);
const uint8_t *data = glyph.glyph_data_->data;
const int max_x = x_at + scan_x1 + scan_width;
const int max_y = y_start + scan_y1 + scan_height;
uint8_t bitmask = 0;
uint8_t pixel_data = 0;
@@ -257,10 +145,10 @@ void Font::print(int x_start, int y_start, display::Display *display, Color colo
auto b_g = (float) background.g;
auto b_b = (float) background.b;
auto b_w = (float) background.w;
for (int glyph_y = y_start + glyph->offset_y; glyph_y != max_y; glyph_y++) {
for (int glyph_x = x_at + glyph->offset_x; glyph_x != max_x; glyph_x++) {
for (int glyph_y = y_start + scan_y1; glyph_y != max_y; glyph_y++) {
for (int glyph_x = x_at + scan_x1; glyph_x != max_x; glyph_x++) {
uint8_t pixel = 0;
for (uint8_t bit_num = 0; bit_num != this->bpp_; bit_num++) {
for (int bit_num = 0; bit_num != this->bpp_; bit_num++) {
if (bitmask == 0) {
pixel_data = progmem_read_byte(data++);
bitmask = 0x80;
@@ -280,9 +168,12 @@ void Font::print(int x_start, int y_start, display::Display *display, Color colo
}
}
}
x_at += glyph->advance;
x_at += glyph.glyph_data_->advance;
i += match_length;
}
}
#endif
} // namespace font
} // namespace esphome

View File

@@ -6,30 +6,14 @@
#ifdef USE_DISPLAY
#include "esphome/components/display/display.h"
#endif
#ifdef USE_LVGL_FONT
#include <lvgl.h>
#endif
namespace esphome {
namespace font {
class Font;
class Glyph {
public:
constexpr Glyph(uint32_t code_point, const uint8_t *data, int advance, int offset_x, int offset_y, int width,
int height)
: code_point(code_point),
data(data),
advance(advance),
offset_x(offset_x),
offset_y(offset_y),
width(width),
height(height) {}
bool is_less_or_equal(uint32_t other) const { return this->code_point <= other; }
const uint32_t code_point;
struct GlyphData {
const uint8_t *a_char;
const uint8_t *data;
int advance;
int offset_x;
@@ -38,6 +22,26 @@ class Glyph {
int height;
};
class Glyph {
public:
Glyph(const GlyphData *data) : glyph_data_(data) {}
const uint8_t *get_char() const;
bool compare_to(const uint8_t *str) const;
int match_length(const uint8_t *str) const;
void scan_area(int *x1, int *y1, int *width, int *height) const;
const GlyphData *get_glyph_data() const { return this->glyph_data_; }
protected:
friend Font;
const GlyphData *glyph_data_;
};
class Font
#ifdef USE_DISPLAY
: public display::BaseFont
@@ -46,8 +50,8 @@ class Font
public:
/** Construct the font with the given glyphs.
*
* @param data A list of glyphs, must be sorted lexicographically.
* @param data_nr The number of glyphs
* @param data A vector of glyphs, must be sorted lexicographically.
* @param data_nr The number of glyphs in data.
* @param baseline The y-offset from the top of the text to the baseline.
* @param height The y-offset from the top of the text to the bottom.
* @param descender The y-offset from the baseline to the lowest stroke in the font (e.g. from letters like g or p).
@@ -55,10 +59,10 @@ class Font
* @param capheight The height of capital letters, usually measured at the "X" glyph.
* @param bpp The bits per pixel used for this font. Used to read data out of the glyph bitmaps.
*/
Font(const Glyph *data, int data_nr, int baseline, int height, int descender, int xheight, int capheight,
Font(const GlyphData *data, int data_nr, int baseline, int height, int descender, int xheight, int capheight,
uint8_t bpp = 1);
const Glyph *find_glyph(uint32_t codepoint) const;
int match_next_glyph(const uint8_t *str, int *match_length);
#ifdef USE_DISPLAY
void print(int x_start, int y_start, display::Display *display, Color color, const char *text,
@@ -73,14 +77,11 @@ class Font
inline int get_xheight() { return this->xheight_; }
inline int get_capheight() { return this->capheight_; }
inline int get_bpp() { return this->bpp_; }
#ifdef USE_LVGL_FONT
const lv_font_t *get_lv_font() const { return &this->lv_font_; }
#endif
const ConstVector<Glyph> &get_glyphs() const { return glyphs_; }
const std::vector<Glyph, RAMAllocator<Glyph>> &get_glyphs() const { return glyphs_; }
protected:
ConstVector<Glyph> glyphs_;
std::vector<Glyph, RAMAllocator<Glyph>> glyphs_;
int baseline_;
int height_;
int descender_;
@@ -88,14 +89,6 @@ class Font
int xheight_;
int capheight_;
uint8_t bpp_; // bits per pixel
#ifdef USE_LVGL_FONT
lv_font_t lv_font_{};
static const uint8_t *get_glyph_bitmap(const lv_font_t *font, uint32_t unicode_letter);
static bool get_glyph_dsc_cb(const lv_font_t *font, lv_font_glyph_dsc_t *dsc, uint32_t unicode_letter, uint32_t next);
const Glyph *get_glyph_data_(uint32_t unicode_letter);
uint32_t last_letter_{};
const Glyph *last_data_{};
#endif
};
} // namespace font

View File

@@ -337,7 +337,7 @@ void Graph::draw_legend(display::Display *buff, uint16_t x_offset, uint16_t y_of
return;
/// Plot border
if (this->border_) {
if (legend_->border_) {
int w = legend_->width_;
int h = legend_->height_;
buff->horizontal_line(x_offset, y_offset, w, color);

View File

@@ -189,7 +189,7 @@ template<typename... Ts> class EnrollmentAction : public Action<Ts...>, public P
TEMPLATABLE_VALUE(std::string, name)
TEMPLATABLE_VALUE(uint8_t, direction)
void play(Ts... x) override {
void play(const Ts &...x) override {
auto name = this->name_.value(x...);
auto direction = (HlkFm22xFaceDirection) this->direction_.value(x...);
this->parent_->enroll_face(name, direction);
@@ -200,7 +200,7 @@ template<typename... Ts> class DeleteAction : public Action<Ts...>, public Paren
public:
TEMPLATABLE_VALUE(int16_t, face_id)
void play(Ts... x) override {
void play(const Ts &...x) override {
auto face_id = this->face_id_.value(x...);
this->parent_->delete_face(face_id);
}
@@ -208,17 +208,17 @@ template<typename... Ts> class DeleteAction : public Action<Ts...>, public Paren
template<typename... Ts> class DeleteAllAction : public Action<Ts...>, public Parented<HlkFm22xComponent> {
public:
void play(Ts... x) override { this->parent_->delete_all_faces(); }
void play(const Ts &...x) override { this->parent_->delete_all_faces(); }
};
template<typename... Ts> class ScanAction : public Action<Ts...>, public Parented<HlkFm22xComponent> {
public:
void play(Ts... x) override { this->parent_->scan_face(); }
void play(const Ts &...x) override { this->parent_->scan_face(); }
};
template<typename... Ts> class ResetAction : public Action<Ts...>, public Parented<HlkFm22xComponent> {
public:
void play(Ts... x) override { this->parent_->reset(); }
void play(const Ts &...x) override { this->parent_->reset(); }
};
} // namespace esphome::hlk_fm22x

View File

@@ -49,18 +49,18 @@ void HttpRequestUpdate::update_task(void *params) {
auto container = this_update->request_parent_->get(this_update->source_url_);
if (container == nullptr || container->status_code != HTTP_STATUS_OK) {
std::string msg = str_sprintf("Failed to fetch manifest from %s", this_update->source_url_.c_str());
ESP_LOGE(TAG, "Failed to fetch manifest from %s", this_update->source_url_.c_str());
// Defer to main loop to avoid race condition on component_state_ read-modify-write
this_update->defer([this_update, msg]() { this_update->status_set_error(msg.c_str()); });
this_update->defer([this_update]() { this_update->status_set_error("Failed to fetch manifest"); });
UPDATE_RETURN;
}
RAMAllocator<uint8_t> allocator;
uint8_t *data = allocator.allocate(container->content_length);
if (data == nullptr) {
std::string msg = str_sprintf("Failed to allocate %zu bytes for manifest", container->content_length);
ESP_LOGE(TAG, "Failed to allocate %zu bytes for manifest", container->content_length);
// Defer to main loop to avoid race condition on component_state_ read-modify-write
this_update->defer([this_update, msg]() { this_update->status_set_error(msg.c_str()); });
this_update->defer([this_update]() { this_update->status_set_error("Failed to allocate memory for manifest"); });
container->end();
UPDATE_RETURN;
}
@@ -121,9 +121,9 @@ void HttpRequestUpdate::update_task(void *params) {
}
if (!valid) {
std::string msg = str_sprintf("Failed to parse JSON from %s", this_update->source_url_.c_str());
ESP_LOGE(TAG, "Failed to parse JSON from %s", this_update->source_url_.c_str());
// Defer to main loop to avoid race condition on component_state_ read-modify-write
this_update->defer([this_update, msg]() { this_update->status_set_error(msg.c_str()); });
this_update->defer([this_update]() { this_update->status_set_error("Failed to parse manifest JSON"); });
UPDATE_RETURN;
}

View File

@@ -107,7 +107,7 @@ void IDFI2CBus::dump_config() {
if (s.second) {
ESP_LOGCONFIG(TAG, "Found device at address 0x%02X", s.first);
} else {
ESP_LOGCONFIG(TAG, "Unknown error at address 0x%02X", s.first);
ESP_LOGE(TAG, "Unknown error at address 0x%02X", s.first);
}
}
}

View File

@@ -10,7 +10,7 @@ namespace jsn_sr04t {
static const char *const TAG = "jsn_sr04t.sensor";
void Jsnsr04tComponent::update() {
this->write_byte(0x55);
this->write_byte((this->model_ == AJ_SR04M) ? 0x01 : 0x55);
ESP_LOGV(TAG, "Request read out from sensor");
}
@@ -31,19 +31,10 @@ void Jsnsr04tComponent::loop() {
}
void Jsnsr04tComponent::check_buffer_() {
uint8_t checksum = 0;
switch (this->model_) {
case JSN_SR04T:
checksum = this->buffer_[0] + this->buffer_[1] + this->buffer_[2];
break;
case AJ_SR04M:
checksum = this->buffer_[1] + this->buffer_[2];
break;
}
uint8_t checksum = this->buffer_[0] + this->buffer_[1] + this->buffer_[2];
if (this->buffer_[3] == checksum) {
uint16_t distance = encode_uint16(this->buffer_[1], this->buffer_[2]);
if (distance > 250) {
if (distance > ((this->model_ == AJ_SR04M) ? 200 : 250)) {
float meters = distance / 1000.0f;
ESP_LOGV(TAG, "Distance from sensor: %umm, %.3fm", distance, meters);
this->publish_state(meters);

View File

@@ -24,9 +24,6 @@ void LightState::setup() {
effect->init_internal(this);
}
// Start with loop disabled if idle - respects any effects/transitions set up during initialization
this->disable_loop_if_idle_();
// When supported color temperature range is known, initialize color temperature setting within bounds.
auto traits = this->get_traits();
float min_mireds = traits.get_min_mireds();
@@ -129,9 +126,6 @@ void LightState::loop() {
this->is_transformer_active_ = false;
this->transformer_ = nullptr;
this->target_state_reached_callback_.call();
// Disable loop if idle (no transformer and no effect)
this->disable_loop_if_idle_();
}
}
@@ -139,8 +133,6 @@ void LightState::loop() {
if (this->next_write_) {
this->next_write_ = false;
this->output_->write_state(this);
// Disable loop if idle (no transformer and no effect)
this->disable_loop_if_idle_();
}
}
@@ -236,8 +228,6 @@ void LightState::start_effect_(uint32_t effect_index) {
this->active_effect_index_ = effect_index;
auto *effect = this->get_active_effect_();
effect->start_internal();
// Enable loop while effect is active
this->enable_loop();
}
LightEffect *LightState::get_active_effect_() {
if (this->active_effect_index_ == 0) {
@@ -252,8 +242,6 @@ void LightState::stop_effect_() {
effect->stop();
}
this->active_effect_index_ = 0;
// Disable loop if idle (no effect and no transformer)
this->disable_loop_if_idle_();
}
void LightState::start_transition_(const LightColorValues &target, uint32_t length, bool set_remote_values) {
@@ -263,8 +251,6 @@ void LightState::start_transition_(const LightColorValues &target, uint32_t leng
if (set_remote_values) {
this->remote_values = target;
}
// Enable loop while transition is active
this->enable_loop();
}
void LightState::start_flash_(const LightColorValues &target, uint32_t length, bool set_remote_values) {
@@ -280,8 +266,6 @@ void LightState::start_flash_(const LightColorValues &target, uint32_t length, b
if (set_remote_values) {
this->remote_values = target;
};
// Enable loop while flash is active
this->enable_loop();
}
void LightState::set_immediately_(const LightColorValues &target, bool set_remote_values) {
@@ -293,14 +277,6 @@ void LightState::set_immediately_(const LightColorValues &target, bool set_remot
}
this->output_->update_state(this);
this->next_write_ = true;
this->enable_loop();
}
void LightState::disable_loop_if_idle_() {
// Only disable loop if both transformer and effect are inactive, and no pending writes
if (this->transformer_ == nullptr && this->get_active_effect_() == nullptr && !this->next_write_) {
this->disable_loop();
}
}
void LightState::save_remote_values_() {

View File

@@ -256,9 +256,6 @@ class LightState : public EntityBase, public Component {
/// Internal method to save the current remote_values to the preferences
void save_remote_values_();
/// Disable loop if neither transformer nor effect is active
void disable_loop_if_idle_();
/// Store the output to allow effects to have more access.
LightOutput *output_;
/// The currently active transformer for this light (transition/flash).

View File

@@ -365,10 +365,8 @@ async def to_code(config):
if CORE.is_esp32:
if config[CONF_HARDWARE_UART] == USB_CDC:
add_idf_sdkconfig_option("CONFIG_ESP_CONSOLE_USB_CDC", True)
cg.add_define("USE_LOGGER_UART_SELECTION_USB_CDC")
elif config[CONF_HARDWARE_UART] == USB_SERIAL_JTAG:
add_idf_sdkconfig_option("CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG", True)
cg.add_define("USE_LOGGER_UART_SELECTION_USB_SERIAL_JTAG")
try:
uart_selection(USB_SERIAL_JTAG)
cg.add_define("USE_LOGGER_USB_SERIAL_JTAG")

View File

@@ -65,9 +65,7 @@ void HOT Logger::log_vprintf_(uint8_t level, const char *tag, int line, const ch
uint16_t buffer_at = 0; // Initialize buffer position
this->format_log_to_buffer_with_terminator_(level, tag, line, format, args, console_buffer, &buffer_at,
MAX_CONSOLE_LOG_MSG_SIZE);
// Add newline if platform needs it (ESP32 doesn't add via write_msg_)
this->add_newline_to_buffer_if_needed_(console_buffer, &buffer_at, MAX_CONSOLE_LOG_MSG_SIZE);
this->write_msg_(console_buffer, buffer_at);
this->write_msg_(console_buffer);
}
// Reset the recursion guard for this task
@@ -133,19 +131,18 @@ void Logger::log_vprintf_(uint8_t level, const char *tag, int line, const __Flas
// Save the offset before calling format_log_to_buffer_with_terminator_
// since it will increment tx_buffer_at_ to the end of the formatted string
uint16_t msg_start = this->tx_buffer_at_;
uint32_t msg_start = this->tx_buffer_at_;
this->format_log_to_buffer_with_terminator_(level, tag, line, this->tx_buffer_, args, this->tx_buffer_,
&this->tx_buffer_at_, this->tx_buffer_size_);
uint16_t msg_length =
// Write to console and send callback starting at the msg_start
if (this->baud_rate_ > 0) {
this->write_msg_(this->tx_buffer_ + msg_start);
}
size_t msg_length =
this->tx_buffer_at_ - msg_start; // Don't subtract 1 - tx_buffer_at_ is already at the null terminator position
// Callbacks get message first (before console write)
this->log_callback_.call(level, tag, this->tx_buffer_ + msg_start, msg_length);
// Write to console starting at the msg_start
this->write_tx_buffer_to_console_(msg_start, &msg_length);
global_recursion_guard_ = false;
}
#endif // USE_STORE_LOG_STR_IN_FLASH
@@ -212,7 +209,9 @@ void Logger::process_messages_() {
// This ensures all log messages appear on the console in a clean, serialized manner
// Note: Messages may appear slightly out of order due to async processing, but
// this is preferred over corrupted/interleaved console output
this->write_tx_buffer_to_console_();
if (this->baud_rate_ > 0) {
this->write_msg_(this->tx_buffer_);
}
}
} else {
// No messages to process, disable loop if appropriate

View File

@@ -71,17 +71,6 @@ static constexpr uint16_t MAX_HEADER_SIZE = 128;
// "0x" + 2 hex digits per byte + '\0'
static constexpr size_t MAX_POINTER_REPRESENTATION = 2 + sizeof(void *) * 2 + 1;
// Platform-specific: does write_msg_ add its own newline?
// false: Caller must add newline to buffer before calling write_msg_ (ESP32, ESP8266, LibreTiny)
// Allows single write call with newline included for efficiency
// true: write_msg_ adds newline itself via puts()/println() (other platforms)
// Newline should NOT be added to buffer
#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_LIBRETINY)
static constexpr bool WRITE_MSG_ADDS_NEWLINE = false;
#else
static constexpr bool WRITE_MSG_ADDS_NEWLINE = true;
#endif
#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_LIBRETINY) || defined(USE_ZEPHYR)
/** Enum for logging UART selection
*
@@ -184,7 +173,7 @@ class Logger : public Component {
protected:
void process_messages_();
void write_msg_(const char *msg, size_t len);
void write_msg_(const char *msg);
// Format a log message with printf-style arguments and write it to a buffer with header, footer, and null terminator
// It's the caller's responsibility to initialize buffer_at (typically to 0)
@@ -211,35 +200,6 @@ class Logger : public Component {
}
}
// Helper to add newline to buffer for platforms that need it
// Modifies buffer_at to include the newline
inline void HOT add_newline_to_buffer_if_needed_(char *buffer, uint16_t *buffer_at, uint16_t buffer_size) {
if constexpr (!WRITE_MSG_ADDS_NEWLINE) {
// Add newline - don't need to maintain null termination
// write_msg_ now always receives explicit length, so we can safely overwrite the null terminator
// This is safe because:
// 1. Callbacks already received the message (before we add newline)
// 2. write_msg_ receives the length explicitly (doesn't need null terminator)
if (*buffer_at < buffer_size) {
buffer[(*buffer_at)++] = '\n';
} else if (buffer_size > 0) {
// Buffer was full - replace last char with newline to ensure it's visible
buffer[buffer_size - 1] = '\n';
*buffer_at = buffer_size;
}
}
}
// Helper to write tx_buffer_ to console if logging is enabled
// INTERNAL USE ONLY - offset > 0 requires length parameter to be non-null
inline void HOT write_tx_buffer_to_console_(uint16_t offset = 0, uint16_t *length = nullptr) {
if (this->baud_rate_ > 0) {
uint16_t *len_ptr = length ? length : &this->tx_buffer_at_;
this->add_newline_to_buffer_if_needed_(this->tx_buffer_ + offset, len_ptr, this->tx_buffer_size_ - offset);
this->write_msg_(this->tx_buffer_ + offset, *len_ptr);
}
}
// Helper to format and send a log message to both console and callbacks
inline void HOT log_message_to_buffer_and_send_(uint8_t level, const char *tag, int line, const char *format,
va_list args) {
@@ -248,11 +208,10 @@ class Logger : public Component {
this->format_log_to_buffer_with_terminator_(level, tag, line, format, args, this->tx_buffer_, &this->tx_buffer_at_,
this->tx_buffer_size_);
// Callbacks get message WITHOUT newline (for API/MQTT/syslog)
if (this->baud_rate_ > 0) {
this->write_msg_(this->tx_buffer_); // If logging is enabled, write to console
}
this->log_callback_.call(level, tag, this->tx_buffer_, this->tx_buffer_at_);
// Console gets message WITH newline (if platform needs it)
this->write_tx_buffer_to_console_();
}
// Write the body of the log message to the buffer
@@ -466,9 +425,7 @@ class Logger : public Component {
}
// Update buffer_at with the formatted length (handle truncation)
// When vsnprintf truncates (ret >= remaining), it writes (remaining - 1) chars + null terminator
// When it doesn't truncate (ret < remaining), it writes ret chars + null terminator
uint16_t formatted_len = (ret >= remaining) ? (remaining - 1) : ret;
uint16_t formatted_len = (ret >= remaining) ? remaining : ret;
*buffer_at += formatted_len;
// Remove all trailing newlines right after formatting

View File

@@ -121,23 +121,25 @@ void Logger::pre_setup() {
ESP_LOGI(TAG, "Log initialized");
}
void HOT Logger::write_msg_(const char *msg, size_t len) {
// Length is now always passed explicitly - no strlen() fallback needed
#if defined(USE_LOGGER_UART_SELECTION_USB_CDC) || defined(USE_LOGGER_UART_SELECTION_USB_SERIAL_JTAG)
// USB CDC/JTAG - single write including newline (already in buffer)
// Use fwrite to stdout which goes through VFS to USB console
//
// Note: These defines indicate the user's YAML configuration choice (hardware_uart: USB_CDC/USB_SERIAL_JTAG).
// They are ONLY defined when the user explicitly selects USB as the logger output in their config.
// This is compile-time selection, not runtime detection - if USB is configured, it's always used.
// There is no fallback to regular UART if "USB isn't connected" - that's the user's responsibility
// to configure correctly for their hardware. This approach eliminates runtime overhead.
fwrite(msg, 1, len, stdout);
void HOT Logger::write_msg_(const char *msg) {
if (
#if defined(USE_LOGGER_USB_CDC) && !defined(USE_LOGGER_USB_SERIAL_JTAG)
this->uart_ == UART_SELECTION_USB_CDC
#elif defined(USE_LOGGER_USB_SERIAL_JTAG) && !defined(USE_LOGGER_USB_CDC)
this->uart_ == UART_SELECTION_USB_SERIAL_JTAG
#elif defined(USE_LOGGER_USB_CDC) && defined(USE_LOGGER_USB_SERIAL_JTAG)
this->uart_ == UART_SELECTION_USB_CDC || this->uart_ == UART_SELECTION_USB_SERIAL_JTAG
#else
// Regular UART - single write including newline (already in buffer)
uart_write_bytes(this->uart_num_, msg, len);
/* DISABLES CODE */ (false) // NOLINT
#endif
) {
puts(msg);
} else {
// Use tx_buffer_at_ if msg points to tx_buffer_, otherwise fall back to strlen
size_t len = (msg == this->tx_buffer_) ? this->tx_buffer_at_ : strlen(msg);
uart_write_bytes(this->uart_num_, msg, len);
uart_write_bytes(this->uart_num_, "\n", 1);
}
}
const LogString *Logger::get_uart_selection_() {

View File

@@ -33,10 +33,7 @@ void Logger::pre_setup() {
ESP_LOGI(TAG, "Log initialized");
}
void HOT Logger::write_msg_(const char *msg, size_t len) {
// Single write with newline already in buffer (added by caller)
this->hw_serial_->write(msg, len);
}
void HOT Logger::write_msg_(const char *msg) { this->hw_serial_->println(msg); }
const LogString *Logger::get_uart_selection_() {
switch (this->uart_) {

View File

@@ -3,7 +3,7 @@
namespace esphome::logger {
void HOT Logger::write_msg_(const char *msg, size_t) {
void HOT Logger::write_msg_(const char *msg) {
time_t rawtime;
struct tm *timeinfo;
char buffer[80];

View File

@@ -49,7 +49,7 @@ void Logger::pre_setup() {
ESP_LOGI(TAG, "Log initialized");
}
void HOT Logger::write_msg_(const char *msg, size_t len) { this->hw_serial_->write(msg, len); }
void HOT Logger::write_msg_(const char *msg) { this->hw_serial_->println(msg); }
const LogString *Logger::get_uart_selection_() {
switch (this->uart_) {

View File

@@ -27,7 +27,7 @@ void Logger::pre_setup() {
ESP_LOGI(TAG, "Log initialized");
}
void HOT Logger::write_msg_(const char *msg, size_t) { this->hw_serial_->println(msg); }
void HOT Logger::write_msg_(const char *msg) { this->hw_serial_->println(msg); }
const LogString *Logger::get_uart_selection_() {
switch (this->uart_) {

View File

@@ -62,7 +62,7 @@ void Logger::pre_setup() {
ESP_LOGI(TAG, "Log initialized");
}
void HOT Logger::write_msg_(const char *msg, size_t) {
void HOT Logger::write_msg_(const char *msg) {
#ifdef CONFIG_PRINTK
printk("%s\n", msg);
#endif

View File

@@ -174,7 +174,7 @@ void LTRAlsPs501Component::loop() {
break;
case State::WAITING_FOR_DATA:
if (this->is_als_data_ready_(this->als_readings_) == DataAvail::DATA_OK) {
if (this->is_als_data_ready_(this->als_readings_) == LtrDataAvail::LTR_DATA_OK) {
tries = 0;
ESP_LOGV(TAG, "Reading sensor data assuming gain = %.0fx, time = %d ms",
get_gain_coeff(this->als_readings_.gain), get_itime_ms(this->als_readings_.integration_time));
@@ -379,18 +379,18 @@ void LTRAlsPs501Component::configure_integration_time_(IntegrationTime501 time)
}
}
DataAvail LTRAlsPs501Component::is_als_data_ready_(AlsReadings &data) {
LtrDataAvail LTRAlsPs501Component::is_als_data_ready_(AlsReadings &data) {
AlsPsStatusRegister als_status{0};
als_status.raw = this->reg((uint8_t) CommandRegisters::ALS_PS_STATUS).get();
if (!als_status.als_new_data)
return DataAvail::NO_DATA;
return LtrDataAvail::LTR_NO_DATA;
ESP_LOGV(TAG, "Data ready, reported gain is %.0fx", get_gain_coeff(als_status.gain));
if (data.gain != als_status.gain) {
ESP_LOGW(TAG, "Actual gain differs from requested (%.0f)", get_gain_coeff(data.gain));
return DataAvail::BAD_DATA;
return LtrDataAvail::LTR_BAD_DATA;
}
data.gain = als_status.gain;
return DataAvail::DATA_OK;
return LtrDataAvail::LTR_DATA_OK;
}
void LTRAlsPs501Component::read_sensor_data_(AlsReadings &data) {

View File

@@ -11,7 +11,7 @@
namespace esphome {
namespace ltr501 {
enum DataAvail : uint8_t { NO_DATA, BAD_DATA, DATA_OK };
enum LtrDataAvail : uint8_t { LTR_NO_DATA, LTR_BAD_DATA, LTR_DATA_OK };
enum LtrType : uint8_t {
LTR_TYPE_UNKNOWN = 0,
@@ -106,7 +106,7 @@ class LTRAlsPs501Component : public PollingComponent, public i2c::I2CDevice {
void configure_als_();
void configure_integration_time_(IntegrationTime501 time);
void configure_gain_(AlsGain501 gain);
DataAvail is_als_data_ready_(AlsReadings &data);
LtrDataAvail is_als_data_ready_(AlsReadings &data);
void read_sensor_data_(AlsReadings &data);
bool are_adjustments_required_(AlsReadings &data);
void apply_lux_calculation_(AlsReadings &data);

View File

@@ -165,7 +165,7 @@ void LTRAlsPsComponent::loop() {
break;
case State::WAITING_FOR_DATA:
if (this->is_als_data_ready_(this->als_readings_) == DataAvail::DATA_OK) {
if (this->is_als_data_ready_(this->als_readings_) == LtrDataAvail::LTR_DATA_OK) {
tries = 0;
ESP_LOGV(TAG, "Reading sensor data having gain = %.0fx, time = %d ms", get_gain_coeff(this->als_readings_.gain),
get_itime_ms(this->als_readings_.integration_time));
@@ -376,23 +376,23 @@ void LTRAlsPsComponent::configure_integration_time_(IntegrationTime time) {
}
}
DataAvail LTRAlsPsComponent::is_als_data_ready_(AlsReadings &data) {
LtrDataAvail LTRAlsPsComponent::is_als_data_ready_(AlsReadings &data) {
AlsPsStatusRegister als_status{0};
als_status.raw = this->reg((uint8_t) CommandRegisters::ALS_PS_STATUS).get();
if (!als_status.als_new_data)
return DataAvail::NO_DATA;
return LtrDataAvail::LTR_NO_DATA;
if (als_status.data_invalid) {
ESP_LOGW(TAG, "Data available but not valid");
return DataAvail::BAD_DATA;
return LtrDataAvail::LTR_BAD_DATA;
}
ESP_LOGV(TAG, "Data ready, reported gain is %.0f", get_gain_coeff(als_status.gain));
if (data.gain != als_status.gain) {
ESP_LOGW(TAG, "Actual gain differs from requested (%.0f)", get_gain_coeff(data.gain));
return DataAvail::BAD_DATA;
return LtrDataAvail::LTR_BAD_DATA;
}
return DataAvail::DATA_OK;
return LtrDataAvail::LTR_DATA_OK;
}
void LTRAlsPsComponent::read_sensor_data_(AlsReadings &data) {

View File

@@ -11,7 +11,7 @@
namespace esphome {
namespace ltr_als_ps {
enum DataAvail : uint8_t { NO_DATA, BAD_DATA, DATA_OK };
enum LtrDataAvail : uint8_t { LTR_NO_DATA, LTR_BAD_DATA, LTR_DATA_OK };
enum LtrType : uint8_t {
LTR_TYPE_UNKNOWN = 0,
@@ -106,7 +106,7 @@ class LTRAlsPsComponent : public PollingComponent, public i2c::I2CDevice {
void configure_als_();
void configure_integration_time_(IntegrationTime time);
void configure_gain_(AlsGain gain);
DataAvail is_als_data_ready_(AlsReadings &data);
LtrDataAvail is_als_data_ready_(AlsReadings &data);
void read_sensor_data_(AlsReadings &data);
bool are_adjustments_required_(AlsReadings &data);
void apply_lux_calculation_(AlsReadings &data);

View File

@@ -52,7 +52,15 @@ from .schemas import (
from .styles import add_top_layer, styles_to_code, theme_to_code
from .touchscreens import touchscreen_schema, touchscreens_to_code
from .trigger import add_on_boot_triggers, generate_triggers
from .types import IdleTrigger, PlainTrigger, lv_font_t, lv_group_t, lv_style_t, lvgl_ns
from .types import (
FontEngine,
IdleTrigger,
PlainTrigger,
lv_font_t,
lv_group_t,
lv_style_t,
lvgl_ns,
)
from .widgets import (
LvScrActType,
Widget,
@@ -236,6 +244,7 @@ async def to_code(configs):
cg.add_global(lvgl_ns.using)
for font in helpers.esphome_fonts_used:
await cg.get_variable(font)
cg.new_Pvariable(ID(f"{font}_engine", True, type=FontEngine), MockObj(font))
default_font = config_0[df.CONF_DEFAULT_FONT]
if not lvalid.is_lv_font(default_font):
add_define(
@@ -247,8 +256,7 @@ async def to_code(configs):
type=lv_font_t.operator("ptr").operator("const"),
)
cg.new_variable(
globfont_id,
MockObj(await lvalid.lv_font.process(default_font), "->").get_lv_font(),
globfont_id, MockObj(await lvalid.lv_font.process(default_font))
)
add_define("LV_FONT_DEFAULT", df.DEFAULT_ESPHOME_FONT)
else:

View File

@@ -0,0 +1,76 @@
#include "lvgl_esphome.h"
#ifdef USE_LVGL_FONT
namespace esphome {
namespace lvgl {
static const uint8_t *get_glyph_bitmap(const lv_font_t *font, uint32_t unicode_letter) {
auto *fe = (FontEngine *) font->dsc;
const auto *gd = fe->get_glyph_data(unicode_letter);
if (gd == nullptr)
return nullptr;
// esph_log_d(TAG, "Returning bitmap @ %X", (uint32_t)gd->data);
return gd->data;
}
static bool get_glyph_dsc_cb(const lv_font_t *font, lv_font_glyph_dsc_t *dsc, uint32_t unicode_letter, uint32_t next) {
auto *fe = (FontEngine *) font->dsc;
const auto *gd = fe->get_glyph_data(unicode_letter);
if (gd == nullptr)
return false;
dsc->adv_w = gd->advance;
dsc->ofs_x = gd->offset_x;
dsc->ofs_y = fe->height - gd->height - gd->offset_y - fe->baseline;
dsc->box_w = gd->width;
dsc->box_h = gd->height;
dsc->is_placeholder = 0;
dsc->bpp = fe->bpp;
return true;
}
FontEngine::FontEngine(font::Font *esp_font) : font_(esp_font) {
this->bpp = esp_font->get_bpp();
this->lv_font_.dsc = this;
this->lv_font_.line_height = this->height = esp_font->get_height();
this->lv_font_.base_line = this->baseline = this->lv_font_.line_height - esp_font->get_baseline();
this->lv_font_.get_glyph_dsc = get_glyph_dsc_cb;
this->lv_font_.get_glyph_bitmap = get_glyph_bitmap;
this->lv_font_.subpx = LV_FONT_SUBPX_NONE;
this->lv_font_.underline_position = -1;
this->lv_font_.underline_thickness = 1;
}
const lv_font_t *FontEngine::get_lv_font() { return &this->lv_font_; }
const font::GlyphData *FontEngine::get_glyph_data(uint32_t unicode_letter) {
if (unicode_letter == last_letter_)
return this->last_data_;
uint8_t unicode[5];
memset(unicode, 0, sizeof unicode);
if (unicode_letter > 0xFFFF) {
unicode[0] = 0xF0 + ((unicode_letter >> 18) & 0x7);
unicode[1] = 0x80 + ((unicode_letter >> 12) & 0x3F);
unicode[2] = 0x80 + ((unicode_letter >> 6) & 0x3F);
unicode[3] = 0x80 + (unicode_letter & 0x3F);
} else if (unicode_letter > 0x7FF) {
unicode[0] = 0xE0 + ((unicode_letter >> 12) & 0xF);
unicode[1] = 0x80 + ((unicode_letter >> 6) & 0x3F);
unicode[2] = 0x80 + (unicode_letter & 0x3F);
} else if (unicode_letter > 0x7F) {
unicode[0] = 0xC0 + ((unicode_letter >> 6) & 0x1F);
unicode[1] = 0x80 + (unicode_letter & 0x3F);
} else {
unicode[0] = unicode_letter;
}
int match_length;
int glyph_n = this->font_->match_next_glyph(unicode, &match_length);
if (glyph_n < 0)
return nullptr;
this->last_data_ = this->font_->get_glyphs()[glyph_n].get_glyph_data();
this->last_letter_ = unicode_letter;
return this->last_data_;
}
} // namespace lvgl
} // namespace esphome
#endif // USES_LVGL_FONT

View File

@@ -36,6 +36,8 @@ from .defines import (
)
from .lv_validation import padding, size
CONF_MULTIPLE_WIDGETS_PER_CELL = "multiple_widgets_per_cell"
cell_alignments = LV_CELL_ALIGNMENTS.one_of
grid_alignments = LV_GRID_ALIGNMENTS.one_of
flex_alignments = LV_FLEX_ALIGNMENTS.one_of
@@ -220,6 +222,7 @@ class GridLayout(Layout):
cv.Optional(CONF_GRID_ROW_ALIGN): grid_alignments,
cv.Optional(CONF_PAD_ROW): padding,
cv.Optional(CONF_PAD_COLUMN): padding,
cv.Optional(CONF_MULTIPLE_WIDGETS_PER_CELL, default=False): cv.boolean,
},
{
cv.Optional(CONF_GRID_CELL_ROW_POS): cv.positive_int,
@@ -263,6 +266,7 @@ class GridLayout(Layout):
# should be guaranteed to be a dict at this point
assert isinstance(layout, dict)
assert layout.get(CONF_TYPE).lower() == TYPE_GRID
allow_multiple = layout.get(CONF_MULTIPLE_WIDGETS_PER_CELL, False)
rows = len(layout[CONF_GRID_ROWS])
columns = len(layout[CONF_GRID_COLUMNS])
used_cells = [[None] * columns for _ in range(rows)]
@@ -299,7 +303,10 @@ class GridLayout(Layout):
f"exceeds grid size {rows}x{columns}",
[CONF_WIDGETS, index],
)
if used_cells[row + i][column + j] is not None:
if (
not allow_multiple
and used_cells[row + i][column + j] is not None
):
raise cv.Invalid(
f"Cell span {row + i}/{column + j} already occupied by widget at index {used_cells[row + i][column + j]}",
[CONF_WIDGETS, index],

View File

@@ -493,7 +493,6 @@ class LvFont(LValidator):
return LV_FONTS
if is_lv_font(value):
return lv_builtin_font(value)
add_lv_use("font")
fontval = cv.use_id(Font)(value)
esphome_fonts_used.add(fontval)
return requires_component("font")(fontval)
@@ -503,9 +502,7 @@ class LvFont(LValidator):
async def process(self, value, args=()):
if is_lv_font(value):
return literal(f"&lv_font_{value}")
if isinstance(value, str):
return literal(f"{value}")
return await super().process(value, args)
return literal(f"{value}_engine->get_lv_font()")
lv_font = LvFont()

View File

@@ -50,14 +50,6 @@ static const display::ColorBitness LV_BITNESS = display::ColorBitness::COLOR_BIT
static const display::ColorBitness LV_BITNESS = display::ColorBitness::COLOR_BITNESS_332;
#endif // LV_COLOR_DEPTH
#ifdef USE_LVGL_FONT
inline void lv_obj_set_style_text_font(lv_obj_t *obj, const font::Font *font, lv_style_selector_t part) {
lv_obj_set_style_text_font(obj, font->get_lv_font(), part);
}
inline void lv_style_set_text_font(lv_style_t *style, const font::Font *font) {
lv_style_set_text_font(style, font->get_lv_font());
}
#endif
#ifdef USE_LVGL_IMAGE
// Shortcut / overload, so that the source of an image can easily be updated
// from within a lambda.
@@ -142,6 +134,24 @@ template<typename... Ts> class ObjUpdateAction : public Action<Ts...> {
protected:
std::function<void(Ts...)> lamb_;
};
#ifdef USE_LVGL_FONT
class FontEngine {
public:
FontEngine(font::Font *esp_font);
const lv_font_t *get_lv_font();
const font::GlyphData *get_glyph_data(uint32_t unicode_letter);
uint16_t baseline{};
uint16_t height{};
uint8_t bpp{};
protected:
font::Font *font_{};
uint32_t last_letter_{};
const font::GlyphData *last_data_{};
lv_font_t lv_font_{};
};
#endif // USE_LVGL_FONT
#ifdef USE_LVGL_ANIMIMG
void lv_animimg_stop(lv_obj_t *obj);
#endif // USE_LVGL_ANIMIMG

View File

@@ -1,6 +1,7 @@
from esphome import config_validation as cv
from esphome.automation import Trigger, validate_automation
from esphome.components.time import RealTimeClock
from esphome.config_validation import prepend_path
from esphome.const import (
CONF_ARGS,
CONF_FORMAT,
@@ -422,7 +423,10 @@ def any_widget_schema(extras=None):
def validator(value):
if isinstance(value, dict):
# Convert to list
is_dict = True
value = [{k: v} for k, v in value.items()]
else:
is_dict = False
if not isinstance(value, list):
raise cv.Invalid("Expected a list of widgets")
result = []
@@ -443,7 +447,9 @@ def any_widget_schema(extras=None):
)
# Apply custom validation
value = widget_type.validate(value or {})
result.append({key: container_validator(value)})
path = [key] if is_dict else [index, key]
with prepend_path(path):
result.append({key: container_validator(value)})
return result
return validator

View File

@@ -45,6 +45,7 @@ lv_coord_t = cg.global_ns.namespace("lv_coord_t")
lv_event_code_t = cg.global_ns.enum("lv_event_code_t")
lv_indev_type_t = cg.global_ns.enum("lv_indev_type_t")
lv_key_t = cg.global_ns.enum("lv_key_t")
FontEngine = lvgl_ns.class_("FontEngine")
PlainTrigger = esphome_ns.class_("Trigger<>", automation.Trigger.template())
DrawEndTrigger = esphome_ns.class_(
"Trigger<uint32_t, uint32_t>", automation.Trigger.template(cg.uint32, cg.uint32)

View File

@@ -1,6 +1,7 @@
from esphome import automation
import esphome.config_validation as cv
from esphome.const import CONF_ID, CONF_RANGE_FROM, CONF_RANGE_TO, CONF_STEP, CONF_VALUE
from esphome.cpp_generator import MockObj
from ..automation import action_to_code
from ..defines import (
@@ -114,7 +115,9 @@ class SpinboxType(WidgetType):
w.obj, digits, digits - config[CONF_DECIMAL_PLACES]
)
if (value := config.get(CONF_VALUE)) is not None:
lv.spinbox_set_value(w.obj, await lv_float.process(value))
lv.spinbox_set_value(
w.obj, MockObj(await lv_float.process(value)) * w.get_scale()
)
def get_scale(self, config):
return 10 ** config[CONF_DECIMAL_PLACES]

View File

@@ -56,7 +56,7 @@ void MCP23016::pin_mode(uint8_t pin, gpio::Flags flags) {
this->update_reg_(pin, false, iodir);
}
}
float MCP23016::get_setup_priority() const { return setup_priority::IO; }
float MCP23016::get_setup_priority() const { return setup_priority::HARDWARE; }
bool MCP23016::read_reg_(uint8_t reg, uint8_t *value) {
if (this->is_failed())
return false;

View File

@@ -135,7 +135,8 @@ void MDNSComponent::compile_records_(StaticVector<MDNSService, MDNS_SERVICE_COUN
#ifdef USE_DASHBOARD_IMPORT
MDNS_STATIC_CONST_CHAR(TXT_PACKAGE_IMPORT_URL, "package_import_url");
txt_records.push_back({MDNS_STR(TXT_PACKAGE_IMPORT_URL), MDNS_STR(dashboard_import::get_package_import_url())});
txt_records.push_back(
{MDNS_STR(TXT_PACKAGE_IMPORT_URL), MDNS_STR(dashboard_import::get_package_import_url().c_str())});
#endif
}
#endif // USE_API

View File

@@ -11,6 +11,12 @@ static bool notify_refresh_ready(esp_lcd_panel_handle_t panel, esp_lcd_dpi_panel
xSemaphoreGiveFromISR(sem, &need_yield);
return (need_yield == pdTRUE);
}
void MIPI_DSI::smark_failed(const char *message, esp_err_t err) {
ESP_LOGE(TAG, "%s: %s", message, esp_err_to_name(err));
this->mark_failed(message);
}
void MIPI_DSI::setup() {
ESP_LOGCONFIG(TAG, "Running Setup");

View File

@@ -62,10 +62,7 @@ class MIPI_DSI : public display::Display {
void set_lanes(uint8_t lanes) { this->lanes_ = lanes; }
void set_madctl(uint8_t madctl) { this->madctl_ = madctl; }
void smark_failed(const char *message, esp_err_t err) {
auto str = str_sprintf("Setup failed: %s: %s", message, esp_err_to_name(err));
this->mark_failed(str.c_str());
}
void smark_failed(const char *message, esp_err_t err);
void update() override;

View File

@@ -164,8 +164,8 @@ void MipiRgb::common_setup_() {
if (err == ESP_OK)
err = esp_lcd_panel_init(this->handle_);
if (err != ESP_OK) {
auto msg = str_sprintf("lcd setup failed: %s", esp_err_to_name(err));
this->mark_failed(msg.c_str());
ESP_LOGE(TAG, "lcd setup failed: %s", esp_err_to_name(err));
this->mark_failed("lcd setup failed");
}
ESP_LOGCONFIG(TAG, "MipiRgb setup complete");
}
@@ -350,6 +350,7 @@ void MipiRgb::dump_config() {
"\n Width: %u"
"\n Height: %u"
"\n Rotation: %d degrees"
"\n PCLK Inverted: %s"
"\n HSync Pulse Width: %u"
"\n HSync Back Porch: %u"
"\n HSync Front Porch: %u"
@@ -357,18 +358,18 @@ void MipiRgb::dump_config() {
"\n VSync Back Porch: %u"
"\n VSync Front Porch: %u"
"\n Invert Colors: %s"
"\n Pixel Clock: %dMHz"
"\n Pixel Clock: %uMHz"
"\n Reset Pin: %s"
"\n DE Pin: %s"
"\n PCLK Pin: %s"
"\n HSYNC Pin: %s"
"\n VSYNC Pin: %s",
this->model_, this->width_, this->height_, this->rotation_, this->hsync_pulse_width_,
this->hsync_back_porch_, this->hsync_front_porch_, this->vsync_pulse_width_, this->vsync_back_porch_,
this->vsync_front_porch_, YESNO(this->invert_colors_), this->pclk_frequency_ / 1000000,
get_pin_name(this->reset_pin_).c_str(), get_pin_name(this->de_pin_).c_str(),
get_pin_name(this->pclk_pin_).c_str(), get_pin_name(this->hsync_pin_).c_str(),
get_pin_name(this->vsync_pin_).c_str());
this->model_, this->width_, this->height_, this->rotation_, YESNO(this->pclk_inverted_),
this->hsync_pulse_width_, this->hsync_back_porch_, this->hsync_front_porch_, this->vsync_pulse_width_,
this->vsync_back_porch_, this->vsync_front_porch_, YESNO(this->invert_colors_),
(unsigned) (this->pclk_frequency_ / 1000000), get_pin_name(this->reset_pin_).c_str(),
get_pin_name(this->de_pin_).c_str(), get_pin_name(this->pclk_pin_).c_str(),
get_pin_name(this->hsync_pin_).c_str(), get_pin_name(this->vsync_pin_).c_str());
if (this->madctl_ & MADCTL_BGR) {
this->dump_pins_(8, 13, "Blue", 0);

View File

@@ -11,6 +11,7 @@ st7701s.extend(
vsync_pin=17,
pclk_pin=21,
pclk_frequency="12MHz",
pclk_inverted=False,
pixel_mode="18bit",
mirror_x=True,
mirror_y=True,

View File

@@ -116,7 +116,7 @@ bool MopekaProCheck::parse_device(const esp32_ble_tracker::ESPBTDevice &device)
// Get temperature of sensor
if (this->temperature_ != nullptr) {
uint8_t temp_in_c = this->parse_temperature_(manu_data.data);
int8_t temp_in_c = this->parse_temperature_(manu_data.data);
this->temperature_->publish_state(temp_in_c);
}
@@ -145,7 +145,7 @@ uint32_t MopekaProCheck::parse_distance_(const std::vector<uint8_t> &message) {
(MOPEKA_LPG_COEF[0] + MOPEKA_LPG_COEF[1] * raw_t + MOPEKA_LPG_COEF[2] * raw_t * raw_t));
}
uint8_t MopekaProCheck::parse_temperature_(const std::vector<uint8_t> &message) { return (message[2] & 0x7F) - 40; }
int8_t MopekaProCheck::parse_temperature_(const std::vector<uint8_t> &message) { return (message[2] & 0x7F) - 40; }
SensorReadQuality MopekaProCheck::parse_read_quality_(const std::vector<uint8_t> &message) {
// Since a 8 bit value is being shifted and truncated to 2 bits all possible values are defined as enumeration

View File

@@ -61,7 +61,7 @@ class MopekaProCheck : public Component, public esp32_ble_tracker::ESPBTDeviceLi
uint8_t parse_battery_level_(const std::vector<uint8_t> &message);
uint32_t parse_distance_(const std::vector<uint8_t> &message);
uint8_t parse_temperature_(const std::vector<uint8_t> &message);
int8_t parse_temperature_(const std::vector<uint8_t> &message);
SensorReadQuality parse_read_quality_(const std::vector<uint8_t> &message);
};

View File

@@ -81,7 +81,12 @@ struct IPAddress {
ip_addr_.type = IPADDR_TYPE_V6;
}
#endif /* LWIP_IPV6 */
IPAddress(esp_ip4_addr_t *other_ip) { memcpy((void *) &ip_addr_, (void *) other_ip, sizeof(esp_ip4_addr_t)); }
IPAddress(esp_ip4_addr_t *other_ip) {
memcpy((void *) &ip_addr_, (void *) other_ip, sizeof(esp_ip4_addr_t));
#if LWIP_IPV6
ip_addr_.type = IPADDR_TYPE_V4;
#endif
}
IPAddress(esp_ip_addr_t *other_ip) {
#if LWIP_IPV6
memcpy((void *) &ip_addr_, (void *) other_ip, sizeof(ip_addr_));
@@ -118,10 +123,10 @@ struct IPAddress {
operator arduino_ns::IPAddress() const { return ip_addr_get_ip4_u32(&ip_addr_); }
#endif
bool is_set() const { return !ip_addr_isany(&ip_addr_); } // NOLINT(readability-simplify-boolean-expr)
bool is_ip4() const { return IP_IS_V4(&ip_addr_); }
bool is_ip6() const { return IP_IS_V6(&ip_addr_); }
bool is_multicast() const { return ip_addr_ismulticast(&ip_addr_); }
bool is_set() { return !ip_addr_isany(&ip_addr_); } // NOLINT(readability-simplify-boolean-expr)
bool is_ip4() { return IP_IS_V4(&ip_addr_); }
bool is_ip6() { return IP_IS_V6(&ip_addr_); }
bool is_multicast() { return ip_addr_ismulticast(&ip_addr_); }
std::string str() const { return str_lower_case(ipaddr_ntoa(&ip_addr_)); }
bool operator==(const IPAddress &other) const { return ip_addr_cmp(&ip_addr_, &other.ip_addr_); }
bool operator!=(const IPAddress &other) const { return !ip_addr_cmp(&ip_addr_, &other.ip_addr_); }

View File

@@ -174,6 +174,9 @@ bool Nextion::upload_tft(uint32_t baud_rate, bool exit_reparse) {
// Check if baud rate is supported
this->original_baud_rate_ = this->parent_->get_baud_rate();
if (baud_rate <= 0) {
baud_rate = this->original_baud_rate_;
}
ESP_LOGD(TAG, "Baud rate: %" PRIu32, baud_rate);
// Define the configuration for the HTTP client

View File

@@ -177,6 +177,9 @@ bool Nextion::upload_tft(uint32_t baud_rate, bool exit_reparse) {
// Check if baud rate is supported
this->original_baud_rate_ = this->parent_->get_baud_rate();
if (baud_rate <= 0) {
baud_rate = this->original_baud_rate_;
}
ESP_LOGD(TAG, "Baud rate: %" PRIu32, baud_rate);
// Define the configuration for the HTTP client

View File

@@ -103,7 +103,6 @@ nrf52_ns = cg.esphome_ns.namespace("nrf52")
DeviceFirmwareUpdate = nrf52_ns.class_("DeviceFirmwareUpdate", cg.Component)
CONF_DFU = "dfu"
CONF_DCDC = "dcdc"
CONF_REG0 = "reg0"
CONF_UICR_ERASE = "uicr_erase"
@@ -122,7 +121,6 @@ CONFIG_SCHEMA = cv.All(
cv.Required(CONF_RESET_PIN): pins.gpio_output_pin_schema,
}
),
cv.Optional(CONF_DCDC, default=True): cv.boolean,
cv.Optional(CONF_REG0): cv.Schema(
{
cv.Required(CONF_VOLTAGE): cv.All(
@@ -198,7 +196,6 @@ async def to_code(config: ConfigType) -> None:
if dfu_config := config.get(CONF_DFU):
CORE.add_job(_dfu_to_code, dfu_config)
zephyr_add_prj_conf("BOARD_ENABLE_DCDC", config[CONF_DCDC])
if reg0_config := config.get(CONF_REG0):
value = VOLTAGE_LEVELS.index(reg0_config[CONF_VOLTAGE])

View File

@@ -1,7 +1,8 @@
#include "automation.h"
#include "esphome/core/log.h"
namespace esphome::number {
namespace esphome {
namespace number {
static const char *const TAG = "number.automation";
@@ -51,4 +52,5 @@ void ValueRangeTrigger::on_state_(float state) {
this->rtc_.save(&in_range);
}
} // namespace esphome::number
} // namespace number
} // namespace esphome

View File

@@ -4,7 +4,8 @@
#include "esphome/core/automation.h"
#include "esphome/core/component.h"
namespace esphome::number {
namespace esphome {
namespace number {
class NumberStateTrigger : public Trigger<float> {
public:
@@ -90,4 +91,5 @@ template<typename... Ts> class NumberInRangeCondition : public Condition<Ts...>
float max_{NAN};
};
} // namespace esphome::number
} // namespace number
} // namespace esphome

View File

@@ -3,7 +3,8 @@
#include "esphome/core/controller_registry.h"
#include "esphome/core/log.h"
namespace esphome::number {
namespace esphome {
namespace number {
static const char *const TAG = "number";
@@ -42,4 +43,5 @@ void Number::add_on_state_callback(std::function<void(float)> &&callback) {
this->state_callback_.add(std::move(callback));
}
} // namespace esphome::number
} // namespace number
} // namespace esphome

View File

@@ -6,7 +6,8 @@
#include "number_call.h"
#include "number_traits.h"
namespace esphome::number {
namespace esphome {
namespace number {
class Number;
void log_number(const char *tag, const char *prefix, const char *type, Number *obj);
@@ -52,4 +53,5 @@ class Number : public EntityBase {
CallbackManager<void(float)> state_callback_;
};
} // namespace esphome::number
} // namespace number
} // namespace esphome

View File

@@ -2,7 +2,8 @@
#include "number.h"
#include "esphome/core/log.h"
namespace esphome::number {
namespace esphome {
namespace number {
static const char *const TAG = "number";
@@ -124,4 +125,5 @@ void NumberCall::perform() {
this->parent_->control(target_value);
}
} // namespace esphome::number
} // namespace number
} // namespace esphome

View File

@@ -4,7 +4,8 @@
#include "esphome/core/log.h"
#include "number_traits.h"
namespace esphome::number {
namespace esphome {
namespace number {
class Number;
@@ -43,4 +44,5 @@ class NumberCall {
bool cycle_;
};
} // namespace esphome::number
} // namespace number
} // namespace esphome

View File

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

View File

@@ -3,7 +3,8 @@
#include "esphome/core/entity_base.h"
#include "esphome/core/helpers.h"
namespace esphome::number {
namespace esphome {
namespace number {
enum NumberMode : uint8_t {
NUMBER_MODE_AUTO = 0,
@@ -34,4 +35,5 @@ class NumberTraits : public EntityBase_DeviceClass, public EntityBase_UnitOfMeas
NumberMode mode_{NUMBER_MODE_AUTO};
};
} // namespace esphome::number
} // namespace number
} // namespace esphome

View File

@@ -2,6 +2,7 @@
#ifdef USE_ONLINE_IMAGE_PNG_SUPPORT
#include "esphome/components/display/display_buffer.h"
#include "esphome/core/application.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
@@ -38,6 +39,14 @@ static void draw_callback(pngle_t *pngle, uint32_t x, uint32_t y, uint32_t w, ui
PngDecoder *decoder = (PngDecoder *) pngle_get_user_data(pngle);
Color color(rgba[0], rgba[1], rgba[2], rgba[3]);
decoder->draw(x, y, w, h, color);
// Feed watchdog periodically to avoid triggering during long decode operations.
// Feed every 1024 pixels to balance efficiency and responsiveness.
uint32_t pixels = w * h;
decoder->increment_pixels_decoded(pixels);
if ((decoder->get_pixels_decoded() % 1024) < pixels) {
App.feed_wdt();
}
}
PngDecoder::PngDecoder(OnlineImage *image) : ImageDecoder(image) {

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