Compare commits

..

81 Commits

Author SHA1 Message Date
J. Nick Koston
df2936f686 Merge branch 'dev' into parition_callbacks 2025-12-17 16:40:30 -07:00
Jonathan Swoboda
2b337aa306 [esp32_camera] Fix I2C driver conflict with other components (#12533)
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
2025-12-17 17:37:59 -05:00
Jonathan Swoboda
4ddaff4027 [esp32] Dynamically embed managed component server certificates (#12509)
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
2025-12-17 17:26:56 -05:00
J. Nick Koston
91c504061b [select] Eliminate string allocation in state callbacks (#12505)
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2025-12-17 12:19:26 -10:00
Jonathan Swoboda
dc8f7abce2 [bme68x_bsec2_i2c] Add MULTI_CONF support for multiple sensors (#12535)
Co-authored-by: Claude <noreply@anthropic.com>
2025-12-17 17:07:42 -05:00
Jonathan Swoboda
3d673ac55e [ci] Check changed headers in clang-tidy when using --changed (#12540)
Co-authored-by: Claude <noreply@anthropic.com>
2025-12-17 11:13:18 -10:00
Jack Wilsdon
b02696edc0 [pm1006] Fix "never" update interval detection (#12529) 2025-12-18 07:40:31 +11:00
Anna Oake
f9720026d0 [cc1101] Fix default frequencies (#12539) 2025-12-17 14:19:18 -05:00
Jonathan Swoboda
d7b04a3d18 [nextion] Fix clang-tidy error on Zephyr for HTTPClient (#12538)
Co-authored-by: Claude <noreply@anthropic.com>
2025-12-17 13:59:49 -05:00
Jonathan Swoboda
0e71fa97a7 [spi] Add SPIInterface stub for clang-tidy on unsupported platforms (#12532)
Co-authored-by: Claude <noreply@anthropic.com>
2025-12-17 12:18:25 -05:00
J. Nick Koston
42e061c9ae [text] Avoid string copies in callbacks by passing const ref (#12504) 2025-12-17 12:00:19 -05:00
J. Nick Koston
94763ebdab [libretiny] Store preference keys as uint32_t, convert to string only at FlashDB boundary (#12500) 2025-12-17 11:59:40 -05:00
J. Nick Koston
f32bb618ac [esp32] Store preference keys as uint32_t, convert to string only at NVS boundary (#12494) 2025-12-17 11:59:35 -05:00
Edward Firmo
0707f383a6 [nextion] Use ESP-IDF for ESP32 Arduino (#9429)
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2025-12-17 11:45:17 -05:00
Piotr Szulc
e91c6a79ea [deep_sleep] Deep sleep for BK72xx (#12267)
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2025-12-17 11:45:05 -05:00
J. Nick Koston
63fc8b4e5a [core] Refactor str_snake_case and str_sanitize to use constexpr helpers (#12454) 2025-12-17 11:30:12 -05:00
J. Nick Koston
ab73ed76b8 [esphome] Improve OTA field alignment to save 4 bytes on 32-bit (#12461) 2025-12-17 11:29:58 -05:00
J. Nick Koston
bf6a03d1cf [factory_reset] Optimize memory by storing interval as uint16_t seconds (#12462) 2025-12-17 11:29:51 -05:00
J. Nick Koston
9928ab09cf [time] Convert to C++17 nested namespace syntax (#12463) 2025-12-17 11:29:43 -05:00
Thomas Rupprecht
56c1691d72 [pca9685,sx126x,sx127x] Use frequency/float_range check (#12490)
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2025-12-16 22:52:28 -05:00
Roger Fachini
a065990ab9 [update] Add check action to trigger update checks (#12415) 2025-12-16 22:20:12 -05:00
Stuart Parmenter
084f517a20 [hub75] Add set_brightness action (#12521) 2025-12-16 22:12:33 -05:00
Jonathan Swoboda
1122ec354f [esp32] Add OTA rollback support (#12460)
Co-authored-by: Claude <noreply@anthropic.com>
2025-12-16 20:07:57 -05:00
Jonathan Swoboda
431183eebc [ledc,mqtt,resampler] Remove unnecessary ESP-IDF framework restrictions (#12442)
Co-authored-by: Claude <noreply@anthropic.com>
2025-12-16 20:07:09 -05:00
Jonathan Swoboda
08beaf8750 [esp32] Remove Arduino-specific code from core.cpp (#12501)
Co-authored-by: Claude <noreply@anthropic.com>
2025-12-16 20:06:12 -05:00
Jonathan Swoboda
18814f12dc [http_request] Use ESP-IDF for ESP32 Arduino (#12428)
Co-authored-by: Claude <noreply@anthropic.com>
2025-12-16 19:44:14 -05:00
Jonathan Swoboda
9cd888cef6 [spi] Use ESP-IDF driver for ESP32 Arduino (#12420)
Co-authored-by: Claude <noreply@anthropic.com>
2025-12-16 19:44:01 -05:00
Thomas Rupprecht
9727c7135c [openthread] channel range, fix typo, use C++17 nested namespace syntax (#12422) 2025-12-16 19:43:18 -05:00
Thomas Rupprecht
93621d85b0 [climate] Improve temperature unit regex (#12032) 2025-12-16 19:43:10 -05:00
Thomas Rupprecht
046ea922e8 [esp32] improve types and variable naming (#12423) 2025-12-16 19:42:52 -05:00
Jeff Zigler
fab4efb469 [esp32] Fix serial logging on h2, c2 & c61 (#12522)
Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
2025-12-16 19:42:12 -05:00
Jonathan Swoboda
efc5672567 Merge branch 'release' into dev 2025-12-16 18:57:37 -05:00
Jonathan Swoboda
0ea5f2fd81 Merge pull request #12525 from esphome/bump-2025.12.0
2025.12.0
2025-12-16 18:57:20 -05:00
Jonathan Swoboda
fa3d998c3d Bump version to 2025.12.0 2025-12-16 17:15:50 -05:00
Jonathan Swoboda
5e630e9255 Merge branch 'beta' into dev 2025-12-16 11:26:08 -05:00
Jonathan Swoboda
864aaeec01 Merge pull request #12520 from esphome/bump-2025.12.0b5
2025.12.0b5
2025-12-16 11:25:57 -05:00
Jonathan Swoboda
9c88e44300 Bump version to 2025.12.0b5 2025-12-16 10:35:31 -05:00
Jonathan Swoboda
4d6a93f92d [uart] Fix UART on default UART0 pins for ESP-IDF (#12519)
Co-authored-by: Claude <noreply@anthropic.com>
2025-12-16 10:35:31 -05:00
J. Nick Koston
7216120bfd [socket] Fix getpeername() returning local address instead of remote in LWIP raw TCP (#12475) 2025-12-16 10:35:31 -05:00
Jonathan Swoboda
1897551b28 [uart] Fix UART on default UART0 pins for ESP-IDF (#12519)
Co-authored-by: Claude <noreply@anthropic.com>
2025-12-16 10:17:17 -05:00
J. Nick Koston
ead60bc5c4 [socket] Fix getpeername() returning local address instead of remote in LWIP raw TCP (#12475) 2025-12-16 00:48:30 -06:00
Jonathan Swoboda
7fe8e53f82 Merge branch 'beta' into dev 2025-12-15 19:01:12 -05:00
Jonathan Swoboda
8cf0ee38a3 Merge pull request #12513 from esphome/bump-2025.12.0b4
2025.12.0b4
2025-12-15 19:01:02 -05:00
Jonathan Swoboda
4c926cca60 Bump version to 2025.12.0b4 2025-12-15 18:09:42 -05:00
Pascal Vizeli
57634b612a [http_request] Fix infinite loop when server doesn't send Content-Length header (#12480)
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-15 18:09:42 -05:00
Jonathan Swoboda
8dff7ee746 [esp32] Support all IDF component version operators in shorthand syntax (#12499)
Co-authored-by: Claude <noreply@anthropic.com>
2025-12-15 18:09:42 -05:00
Jonathan Swoboda
803bb742c9 [remote_base] Fix crash when ABBWelcome action has no data field (#12493)
Co-authored-by: Claude <noreply@anthropic.com>
2025-12-15 18:09:42 -05:00
David Woodhouse
839139df36 Add FNV-1a hash functions (#12502) 2025-12-15 20:23:54 +00:00
dependabot[bot]
24d7e9dd23 Bump tornado from 6.5.3 to 6.5.4 (#12508)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-15 20:08:16 +00:00
dependabot[bot]
1214bb6bad Bump aioesphomeapi from 43.2.1 to 43.3.0 (#12507)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-15 20:07:20 +00:00
J. Nick Koston
ab0ca3006a Merge branch 'dev' into parition_callbacks 2025-12-15 12:20:54 -06:00
Pascal Vizeli
260ffba2a5 [http_request] Fix infinite loop when server doesn't send Content-Length header (#12480)
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-15 12:54:12 -05:00
Jonathan Swoboda
2e899dd010 [esp32] Support all IDF component version operators in shorthand syntax (#12499)
Co-authored-by: Claude <noreply@anthropic.com>
2025-12-15 12:07:02 -05:00
David Woodhouse
61cbd07e1d Add hmac-sha256 support (#12437)
Co-authored-by: J. Nick Koston <nick+github@koston.org>
2025-12-15 10:55:03 -06:00
Jonathan Swoboda
450962850a [remote_base] Fix crash when ABBWelcome action has no data field (#12493)
Co-authored-by: Claude <noreply@anthropic.com>
2025-12-15 09:29:51 -05:00
Jonathan Swoboda
6b088caf5d Merge branch 'beta' into dev 2025-12-14 19:18:10 -05:00
Jonathan Swoboda
3e6a65e7dc Merge pull request #12488 from esphome/bump-2025.12.0b3
2025.12.0b3
2025-12-14 19:17:58 -05:00
Jonathan Swoboda
3a101d8886 Bump version to 2025.12.0b3 2025-12-14 18:17:00 -05:00
J. Nick Koston
fa0f07bfe9 [wifi] Fix WiFi recovery after failed connection attempts (#12483) 2025-12-14 18:17:00 -05:00
mbohdal
fffa16e4d8 [ethernet] fix used pins validation in configuration of RMII pins (#12486) 2025-12-14 18:17:00 -05:00
guillempages
734710d22a [core] Use Arduino string macros only on ESP8266 (#12471) 2025-12-14 18:17:00 -05:00
J. Nick Koston
3a1be6822e [ota] Match client timeout to device timeout to prevent premature failures (#12484) 2025-12-14 18:17:00 -05:00
J. Nick Koston
c85b1b8609 [web_server_idf] Always enable LRU purge to prevent socket exhaustion (#12481) 2025-12-14 18:17:00 -05:00
J. Nick Koston
2e9ddd967c [wifi_signal] Skip publishing disconnected RSSI value (#12482) 2025-12-14 18:17:00 -05:00
J. Nick Koston
078afe9656 [dashboard] Add ESPHOME_TRUSTED_DOMAINS support to events WebSocket (#12479) 2025-12-14 18:17:00 -05:00
Jonathan Swoboda
46574fcbec [cc1101] Add packet mode support (#12474)
Co-authored-by: Claude <noreply@anthropic.com>
2025-12-14 18:17:00 -05:00
Jonathan Swoboda
359f45400f [core] Fix polling_component_schema and type consistency (#12478)
Co-authored-by: Claude <noreply@anthropic.com>
2025-12-14 18:16:59 -05:00
Clyde Stubbs
4da95ccd7e [packet_transport] Ensure retransmission at update intervals (#12472)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-12-14 18:16:59 -05:00
J. Nick Koston
c69d58273a [core] Fix CORE.raw_config not updated after package merge (#12456) 2025-12-14 18:16:59 -05:00
J. Nick Koston
ffce80f96c [wifi] Fix WiFi recovery after failed connection attempts (#12483) 2025-12-14 16:26:34 -06:00
mbohdal
fa5b14fad4 [ethernet] fix used pins validation in configuration of RMII pins (#12486) 2025-12-14 16:40:08 -05:00
guillempages
cee532a1e3 [core] Use Arduino string macros only on ESP8266 (#12471) 2025-12-15 07:15:19 +11:00
J. Nick Koston
8524b894d6 [ota] Match client timeout to device timeout to prevent premature failures (#12484) 2025-12-14 13:47:11 -06:00
J. Nick Koston
3a5e708c13 [web_server_idf] Always enable LRU purge to prevent socket exhaustion (#12481) 2025-12-14 13:31:19 -06:00
J. Nick Koston
96e418a8ca [wifi_signal] Skip publishing disconnected RSSI value (#12482) 2025-12-14 13:31:07 -06:00
J. Nick Koston
780a407b10 [dashboard] Add ESPHOME_TRUSTED_DOMAINS support to events WebSocket (#12479) 2025-12-14 13:30:55 -06:00
Jonathan Swoboda
cfc0d8bdfc [cc1101] Add packet mode support (#12474)
Co-authored-by: Claude <noreply@anthropic.com>
2025-12-14 13:22:55 -05:00
Jonathan Swoboda
786d7266f5 [core] Fix polling_component_schema and type consistency (#12478)
Co-authored-by: Claude <noreply@anthropic.com>
2025-12-14 12:47:52 -05:00
Clyde Stubbs
ede64a9f47 [packet_transport] Ensure retransmission at update intervals (#12472)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-12-14 12:47:15 -05:00
J. Nick Koston
e0ce66e011 [core] Fix CORE.raw_config not updated after package merge (#12456) 2025-12-13 07:38:31 -06:00
David Woodhouse
6fce0a6104 Add host platform support to MD5 component (#12458) 2025-12-13 02:50:34 +00:00
202 changed files with 2376 additions and 2011 deletions

View File

@@ -1 +1 @@
766420905c06eeb6c5f360f68fd965e5ddd9c4a5db6b823263d3ad3accb64a07
6857423aecf90accd0a8bf584d36ee094a4938f872447a4efc05a2efc6dc6481

View File

@@ -215,6 +215,7 @@ esphome/components/hlk_fm22x/* @OnFreund
esphome/components/hlw8032/* @rici4kubicek
esphome/components/hm3301/* @freekode
esphome/components/hmac_md5/* @dwmw2
esphome/components/hmac_sha256/* @dwmw2
esphome/components/homeassistant/* @esphome/core @OttoWinter
esphome/components/homeassistant/number/* @landonr
esphome/components/homeassistant/switch/* @Links2004

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
@@ -1227,17 +1220,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",

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

@@ -227,7 +227,7 @@ CONFIG_SCHEMA = cv.All(
{
cv.GenerateID(): cv.declare_id(ADE7880),
cv.Optional(CONF_FREQUENCY, default="50Hz"): cv.All(
cv.frequency, cv.Range(min=45.0, max=66.0)
cv.frequency, cv.float_range(min=45.0, max=66.0)
),
cv.Optional(CONF_IRQ0_PIN): pins.internal_gpio_input_pin_schema,
cv.Required(CONF_IRQ1_PIN): pins.internal_gpio_input_pin_schema,

View File

@@ -35,12 +35,26 @@ void AlarmControlPanel::publish_state(AlarmControlPanelState state) {
ESP_LOGD(TAG, "Set state to: %s, previous: %s", LOG_STR_ARG(alarm_control_panel_state_to_string(state)),
LOG_STR_ARG(alarm_control_panel_state_to_string(prev_state)));
this->current_state_ = state;
// Single state callback - triggers check get_state() for specific states
this->state_callback_.call();
#if defined(USE_ALARM_CONTROL_PANEL) && defined(USE_CONTROLLER_REGISTRY)
ControllerRegistry::notify_alarm_control_panel_update(this);
#endif
// Cleared fires when leaving TRIGGERED state
if (state == ACP_STATE_TRIGGERED) {
this->triggered_callback_.call();
} else if (state == ACP_STATE_ARMING) {
this->arming_callback_.call();
} else if (state == ACP_STATE_PENDING) {
this->pending_callback_.call();
} else if (state == ACP_STATE_ARMED_HOME) {
this->armed_home_callback_.call();
} else if (state == ACP_STATE_ARMED_NIGHT) {
this->armed_night_callback_.call();
} else if (state == ACP_STATE_ARMED_AWAY) {
this->armed_away_callback_.call();
} else if (state == ACP_STATE_DISARMED) {
this->disarmed_callback_.call();
}
if (prev_state == ACP_STATE_TRIGGERED) {
this->cleared_callback_.call();
}
@@ -55,6 +69,34 @@ void AlarmControlPanel::add_on_state_callback(std::function<void()> &&callback)
this->state_callback_.add(std::move(callback));
}
void AlarmControlPanel::add_on_triggered_callback(std::function<void()> &&callback) {
this->triggered_callback_.add(std::move(callback));
}
void AlarmControlPanel::add_on_arming_callback(std::function<void()> &&callback) {
this->arming_callback_.add(std::move(callback));
}
void AlarmControlPanel::add_on_armed_home_callback(std::function<void()> &&callback) {
this->armed_home_callback_.add(std::move(callback));
}
void AlarmControlPanel::add_on_armed_night_callback(std::function<void()> &&callback) {
this->armed_night_callback_.add(std::move(callback));
}
void AlarmControlPanel::add_on_armed_away_callback(std::function<void()> &&callback) {
this->armed_away_callback_.add(std::move(callback));
}
void AlarmControlPanel::add_on_pending_callback(std::function<void()> &&callback) {
this->pending_callback_.add(std::move(callback));
}
void AlarmControlPanel::add_on_disarmed_callback(std::function<void()> &&callback) {
this->disarmed_callback_.add(std::move(callback));
}
void AlarmControlPanel::add_on_cleared_callback(std::function<void()> &&callback) {
this->cleared_callback_.add(std::move(callback));
}

View File

@@ -35,13 +35,54 @@ class AlarmControlPanel : public EntityBase {
*/
void publish_state(AlarmControlPanelState state);
/** Add a callback for when the state of the alarm_control_panel changes.
* Triggers can check get_state() to determine the new state.
/** Add a callback for when the state of the alarm_control_panel changes
*
* @param callback The callback function
*/
void add_on_state_callback(std::function<void()> &&callback);
/** Add a callback for when the state of the alarm_control_panel chanes to triggered
*
* @param callback The callback function
*/
void add_on_triggered_callback(std::function<void()> &&callback);
/** Add a callback for when the state of the alarm_control_panel chanes to arming
*
* @param callback The callback function
*/
void add_on_arming_callback(std::function<void()> &&callback);
/** Add a callback for when the state of the alarm_control_panel changes to pending
*
* @param callback The callback function
*/
void add_on_pending_callback(std::function<void()> &&callback);
/** Add a callback for when the state of the alarm_control_panel changes to armed_home
*
* @param callback The callback function
*/
void add_on_armed_home_callback(std::function<void()> &&callback);
/** Add a callback for when the state of the alarm_control_panel changes to armed_night
*
* @param callback The callback function
*/
void add_on_armed_night_callback(std::function<void()> &&callback);
/** Add a callback for when the state of the alarm_control_panel changes to armed_away
*
* @param callback The callback function
*/
void add_on_armed_away_callback(std::function<void()> &&callback);
/** Add a callback for when the state of the alarm_control_panel changes to disarmed
*
* @param callback The callback function
*/
void add_on_disarmed_callback(std::function<void()> &&callback);
/** Add a callback for when the state of the alarm_control_panel clears from triggered
*
* @param callback The callback function
@@ -131,9 +172,23 @@ class AlarmControlPanel : public EntityBase {
uint32_t last_update_;
// the call control function
virtual void control(const AlarmControlPanelCall &call) = 0;
// state callback - triggers check get_state() for specific state
// state callback
CallbackManager<void()> state_callback_{};
// clear callback - fires when leaving TRIGGERED state
// trigger callback
CallbackManager<void()> triggered_callback_{};
// arming callback
CallbackManager<void()> arming_callback_{};
// pending callback
CallbackManager<void()> pending_callback_{};
// armed_home callback
CallbackManager<void()> armed_home_callback_{};
// armed_night callback
CallbackManager<void()> armed_night_callback_{};
// armed_away callback
CallbackManager<void()> armed_away_callback_{};
// disarmed callback
CallbackManager<void()> disarmed_callback_{};
// clear callback
CallbackManager<void()> cleared_callback_{};
// chime callback
CallbackManager<void()> chime_callback_{};

View File

@@ -6,7 +6,6 @@
namespace esphome {
namespace alarm_control_panel {
/// Trigger on any state change
class StateTrigger : public Trigger<> {
public:
explicit StateTrigger(AlarmControlPanel *alarm_control_panel) {
@@ -14,30 +13,55 @@ class StateTrigger : public Trigger<> {
}
};
/// Template trigger that fires when entering a specific state
template<AlarmControlPanelState State> class StateEnterTrigger : public Trigger<> {
class TriggeredTrigger : public Trigger<> {
public:
explicit StateEnterTrigger(AlarmControlPanel *alarm_control_panel) : alarm_control_panel_(alarm_control_panel) {
alarm_control_panel->add_on_state_callback([this]() {
if (this->alarm_control_panel_->get_state() == State)
this->trigger();
});
explicit TriggeredTrigger(AlarmControlPanel *alarm_control_panel) {
alarm_control_panel->add_on_triggered_callback([this]() { this->trigger(); });
}
protected:
AlarmControlPanel *alarm_control_panel_;
};
// Type aliases for state-specific triggers
using TriggeredTrigger = StateEnterTrigger<ACP_STATE_TRIGGERED>;
using ArmingTrigger = StateEnterTrigger<ACP_STATE_ARMING>;
using PendingTrigger = StateEnterTrigger<ACP_STATE_PENDING>;
using ArmedHomeTrigger = StateEnterTrigger<ACP_STATE_ARMED_HOME>;
using ArmedNightTrigger = StateEnterTrigger<ACP_STATE_ARMED_NIGHT>;
using ArmedAwayTrigger = StateEnterTrigger<ACP_STATE_ARMED_AWAY>;
using DisarmedTrigger = StateEnterTrigger<ACP_STATE_DISARMED>;
class ArmingTrigger : public Trigger<> {
public:
explicit ArmingTrigger(AlarmControlPanel *alarm_control_panel) {
alarm_control_panel->add_on_arming_callback([this]() { this->trigger(); });
}
};
class PendingTrigger : public Trigger<> {
public:
explicit PendingTrigger(AlarmControlPanel *alarm_control_panel) {
alarm_control_panel->add_on_pending_callback([this]() { this->trigger(); });
}
};
class ArmedHomeTrigger : public Trigger<> {
public:
explicit ArmedHomeTrigger(AlarmControlPanel *alarm_control_panel) {
alarm_control_panel->add_on_armed_home_callback([this]() { this->trigger(); });
}
};
class ArmedNightTrigger : public Trigger<> {
public:
explicit ArmedNightTrigger(AlarmControlPanel *alarm_control_panel) {
alarm_control_panel->add_on_armed_night_callback([this]() { this->trigger(); });
}
};
class ArmedAwayTrigger : public Trigger<> {
public:
explicit ArmedAwayTrigger(AlarmControlPanel *alarm_control_panel) {
alarm_control_panel->add_on_armed_away_callback([this]() { this->trigger(); });
}
};
class DisarmedTrigger : public Trigger<> {
public:
explicit DisarmedTrigger(AlarmControlPanel *alarm_control_panel) {
alarm_control_panel->add_on_disarmed_callback([this]() { this->trigger(); });
}
};
/// Trigger when leaving TRIGGERED state (alarm cleared)
class ClearedTrigger : public Trigger<> {
public:
explicit ClearedTrigger(AlarmControlPanel *alarm_control_panel) {
@@ -45,7 +69,6 @@ class ClearedTrigger : public Trigger<> {
}
};
/// Trigger on chime event (zone opened while disarmed)
class ChimeTrigger : public Trigger<> {
public:
explicit ChimeTrigger(AlarmControlPanel *alarm_control_panel) {
@@ -53,7 +76,6 @@ class ChimeTrigger : public Trigger<> {
}
};
/// Trigger on ready state change
class ReadyTrigger : public Trigger<> {
public:
explicit ReadyTrigger(AlarmControlPanel *alarm_control_panel) {

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,189 +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
// fetch millis() again since the read can take a bit
if (!this->start_measurement_(this->fine_mode_, this->fine_mtreg_, millis())) {
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

@@ -11,6 +11,7 @@ CODEOWNERS = ["@neffs", "@kbx81"]
AUTO_LOAD = ["bme68x_bsec2"]
DEPENDENCIES = ["i2c"]
MULTI_CONF = True
bme68x_bsec2_i2c_ns = cg.esphome_ns.namespace("bme68x_bsec2_i2c")
BME68xBSEC2I2CComponent = bme68x_bsec2_i2c_ns.class_(

View File

@@ -65,12 +65,6 @@ 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,10 +40,6 @@ 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

@@ -1,9 +1,17 @@
from esphome import automation
from esphome import automation, pins
from esphome.automation import maybe_simple_id
import esphome.codegen as cg
from esphome.components import spi
from esphome.components.const import CONF_CRC_ENABLE, CONF_ON_PACKET
import esphome.config_validation as cv
from esphome.const import CONF_CHANNEL, CONF_FREQUENCY, CONF_ID, CONF_WAIT_TIME
from esphome.const import (
CONF_CHANNEL,
CONF_DATA,
CONF_FREQUENCY,
CONF_ID,
CONF_WAIT_TIME,
)
from esphome.core import ID
CODEOWNERS = ["@lygris", "@gabest11"]
DEPENDENCIES = ["spi"]
@@ -29,7 +37,6 @@ CONF_MANCHESTER = "manchester"
CONF_NUM_PREAMBLE = "num_preamble"
CONF_SYNC1 = "sync1"
CONF_SYNC0 = "sync0"
CONF_PKTLEN = "pktlen"
CONF_MAGN_TARGET = "magn_target"
CONF_MAX_LNA_GAIN = "max_lna_gain"
CONF_MAX_DVGA_GAIN = "max_dvga_gain"
@@ -41,6 +48,12 @@ CONF_FILTER_LENGTH_ASK_OOK = "filter_length_ask_ook"
CONF_FREEZE = "freeze"
CONF_HYST_LEVEL = "hyst_level"
# Packet mode config keys
CONF_PACKET_MODE = "packet_mode"
CONF_PACKET_LENGTH = "packet_length"
CONF_WHITENING = "whitening"
CONF_GDO0_PIN = "gdo0_pin"
# Enums
SyncMode = ns.enum("SyncMode", True)
SYNC_MODE = {
@@ -152,7 +165,7 @@ CONFIG_MAP = {
CONF_OUTPUT_POWER: cv.float_range(min=-30.0, max=11.0),
CONF_RX_ATTENUATION: cv.enum(RX_ATTENUATION, upper=False),
CONF_DC_BLOCKING_FILTER: cv.boolean,
CONF_FREQUENCY: cv.All(cv.frequency, cv.float_range(min=300000000, max=928000000)),
CONF_FREQUENCY: cv.All(cv.frequency, cv.float_range(min=300.0e6, max=928.0e6)),
CONF_IF_FREQUENCY: cv.All(cv.frequency, cv.float_range(min=25000, max=788000)),
CONF_FILTER_BANDWIDTH: cv.All(cv.frequency, cv.float_range(min=58000, max=812000)),
CONF_CHANNEL: cv.uint8_t,
@@ -167,7 +180,6 @@ CONFIG_MAP = {
CONF_NUM_PREAMBLE: cv.int_range(min=0, max=7),
CONF_SYNC1: cv.hex_uint8_t,
CONF_SYNC0: cv.hex_uint8_t,
CONF_PKTLEN: cv.uint8_t,
CONF_MAGN_TARGET: cv.enum(MAGN_TARGET, upper=False),
CONF_MAX_LNA_GAIN: cv.enum(MAX_LNA_GAIN, upper=False),
CONF_MAX_DVGA_GAIN: cv.enum(MAX_DVGA_GAIN, upper=False),
@@ -179,13 +191,36 @@ CONFIG_MAP = {
CONF_FREEZE: cv.enum(FREEZE, upper=False),
CONF_WAIT_TIME: cv.enum(WAIT_TIME, upper=False),
CONF_HYST_LEVEL: cv.enum(HYST_LEVEL, upper=False),
CONF_PACKET_MODE: cv.boolean,
CONF_PACKET_LENGTH: cv.uint8_t,
CONF_CRC_ENABLE: cv.boolean,
CONF_WHITENING: cv.boolean,
}
CONFIG_SCHEMA = (
cv.Schema({cv.GenerateID(): cv.declare_id(CC1101Component)})
def _validate_packet_mode(config):
if config.get(CONF_PACKET_MODE, False):
if CONF_GDO0_PIN not in config:
raise cv.Invalid("gdo0_pin is required when packet_mode is enabled")
if CONF_PACKET_LENGTH not in config:
raise cv.Invalid("packet_length is required when packet_mode is enabled")
if config[CONF_PACKET_LENGTH] > 64:
raise cv.Invalid("packet_length must be <= 64 (FIFO size)")
return config
CONFIG_SCHEMA = cv.All(
cv.Schema(
{
cv.GenerateID(): cv.declare_id(CC1101Component),
cv.Optional(CONF_GDO0_PIN): pins.internal_gpio_input_pin_schema,
cv.Optional(CONF_ON_PACKET): automation.validate_automation(single=True),
}
)
.extend({cv.Optional(key): validator for key, validator in CONFIG_MAP.items()})
.extend(cv.COMPONENT_SCHEMA)
.extend(spi.spi_device_schema(cs_pin_required=True))
.extend(spi.spi_device_schema(cs_pin_required=True)),
_validate_packet_mode,
)
@@ -198,12 +233,29 @@ async def to_code(config):
if key in config:
cg.add(getattr(var, f"set_{key}")(config[key]))
if CONF_GDO0_PIN in config:
gdo0_pin = await cg.gpio_pin_expression(config[CONF_GDO0_PIN])
cg.add(var.set_gdo0_pin(gdo0_pin))
if CONF_ON_PACKET in config:
await automation.build_automation(
var.get_packet_trigger(),
[
(cg.std_vector.template(cg.uint8), "x"),
(cg.float_, "rssi"),
(cg.uint8, "lqi"),
],
config[CONF_ON_PACKET],
)
# Actions
BeginTxAction = ns.class_("BeginTxAction", automation.Action)
BeginRxAction = ns.class_("BeginRxAction", automation.Action)
ResetAction = ns.class_("ResetAction", automation.Action)
SetIdleAction = ns.class_("SetIdleAction", automation.Action)
SendPacketAction = ns.class_(
"SendPacketAction", automation.Action, cg.Parented.template(CC1101Component)
)
CC1101_ACTION_SCHEMA = cv.Schema(
maybe_simple_id({cv.GenerateID(CONF_ID): cv.use_id(CC1101Component)})
@@ -218,3 +270,42 @@ async def cc1101_action_to_code(config, action_id, template_arg, args):
var = cg.new_Pvariable(action_id, template_arg)
await cg.register_parented(var, config[CONF_ID])
return var
def validate_raw_data(value):
if isinstance(value, str):
return value.encode("utf-8")
if isinstance(value, list):
return cv.Schema([cv.hex_uint8_t])(value)
raise cv.Invalid(
"data must either be a string wrapped in quotes or a list of bytes"
)
SEND_PACKET_ACTION_SCHEMA = cv.maybe_simple_value(
{
cv.GenerateID(): cv.use_id(CC1101Component),
cv.Required(CONF_DATA): cv.templatable(validate_raw_data),
},
key=CONF_DATA,
)
@automation.register_action(
"cc1101.send_packet", SendPacketAction, SEND_PACKET_ACTION_SCHEMA
)
async def send_packet_action_to_code(config, action_id, template_arg, args):
var = cg.new_Pvariable(action_id, template_arg)
await cg.register_parented(var, config[CONF_ID])
data = config[CONF_DATA]
if isinstance(data, bytes):
data = list(data)
if cg.is_template(data):
templ = await cg.templatable(data, args, cg.std_vector.template(cg.uint8))
cg.add(var.set_data_template(templ))
else:
# Generate static array in flash to avoid RAM copy
arr_id = ID(f"{action_id}_data", is_declaration=True, type=cg.uint8)
arr = cg.static_const_array(arr_id, cg.ArrayInitializer(*data))
cg.add(var.set_data_static(arr, len(data)))
return var

View File

@@ -99,11 +99,11 @@ CC1101Component::CC1101Component() {
this->state_.FS_AUTOCAL = 1;
// Default Settings
this->set_frequency(433920);
this->set_if_frequency(153);
this->set_filter_bandwidth(203);
this->set_frequency(433920000);
this->set_if_frequency(153000);
this->set_filter_bandwidth(203000);
this->set_channel(0);
this->set_channel_spacing(200);
this->set_channel_spacing(200000);
this->set_symbol_rate(5000);
this->set_sync_mode(SyncMode::SYNC_MODE_NONE);
this->set_carrier_sense_above_threshold(true);
@@ -143,6 +143,11 @@ void CC1101Component::setup() {
return;
}
// Setup GDO0 pin if configured
if (this->gdo0_pin_ != nullptr) {
this->gdo0_pin_->setup();
}
this->initialized_ = true;
for (uint8_t i = 0; i <= static_cast<uint8_t>(Register::TEST0); i++) {
@@ -151,8 +156,69 @@ void CC1101Component::setup() {
}
this->write_(static_cast<Register>(i));
}
this->write_(Register::PATABLE, this->pa_table_, sizeof(this->pa_table_));
this->set_output_power(this->output_power_requested_);
this->strobe_(Command::RX);
// Defer pin mode setup until after all components have completed setup()
// This handles the case where remote_transmitter runs after CC1101 and changes pin mode
if (this->gdo0_pin_ != nullptr) {
this->defer([this]() { this->gdo0_pin_->pin_mode(gpio::FLAG_INPUT); });
}
}
void CC1101Component::loop() {
if (this->state_.PKT_FORMAT != static_cast<uint8_t>(PacketFormat::PACKET_FORMAT_FIFO) || this->gdo0_pin_ == nullptr ||
!this->gdo0_pin_->digital_read()) {
return;
}
// Read state
this->read_(Register::RXBYTES);
uint8_t rx_bytes = this->state_.NUM_RXBYTES;
bool overflow = this->state_.RXFIFO_OVERFLOW;
if (overflow || rx_bytes == 0) {
ESP_LOGW(TAG, "RX FIFO overflow, flushing");
this->enter_idle_();
this->strobe_(Command::FRX);
this->strobe_(Command::RX);
this->wait_for_state_(State::RX);
return;
}
// Read packet
uint8_t payload_length;
if (this->state_.LENGTH_CONFIG == static_cast<uint8_t>(LengthConfig::LENGTH_CONFIG_VARIABLE)) {
this->read_(Register::FIFO, &payload_length, 1);
} else {
payload_length = this->state_.PKTLEN;
}
if (payload_length == 0 || payload_length > 64) {
ESP_LOGW(TAG, "Invalid payload length: %u", payload_length);
this->enter_idle_();
this->strobe_(Command::FRX);
this->strobe_(Command::RX);
this->wait_for_state_(State::RX);
return;
}
this->packet_.resize(payload_length);
this->read_(Register::FIFO, this->packet_.data(), payload_length);
// Read status and trigger
uint8_t status[2];
this->read_(Register::FIFO, status, 2);
int8_t rssi_raw = static_cast<int8_t>(status[0]);
float rssi = (rssi_raw * RSSI_STEP) - RSSI_OFFSET;
bool crc_ok = (status[1] & STATUS_CRC_OK_MASK) != 0;
uint8_t lqi = status[1] & STATUS_LQI_MASK;
if (this->state_.CRC_EN == 0 || crc_ok) {
this->packet_trigger_->trigger(this->packet_, rssi, lqi);
}
// Return to rx
this->enter_idle_();
this->strobe_(Command::FRX);
this->strobe_(Command::RX);
this->wait_for_state_(State::RX);
}
void CC1101Component::dump_config() {
@@ -177,9 +243,12 @@ void CC1101Component::dump_config() {
}
void CC1101Component::begin_tx() {
// Ensure Packet Format is 3 (Async Serial), use GDO0 as input during TX
// Ensure Packet Format is 3 (Async Serial)
this->write_(Register::PKTCTRL0, 0x32);
ESP_LOGV(TAG, "Beginning TX sequence");
if (this->gdo0_pin_ != nullptr) {
this->gdo0_pin_->pin_mode(gpio::FLAG_OUTPUT);
}
this->strobe_(Command::TX);
if (!this->wait_for_state_(State::TX, 50)) {
ESP_LOGW(TAG, "Timed out waiting for TX state!");
@@ -188,6 +257,9 @@ void CC1101Component::begin_tx() {
void CC1101Component::begin_rx() {
ESP_LOGV(TAG, "Beginning RX sequence");
if (this->gdo0_pin_ != nullptr) {
this->gdo0_pin_->pin_mode(gpio::FLAG_INPUT);
}
this->strobe_(Command::RX);
}
@@ -201,20 +273,6 @@ void CC1101Component::set_idle() {
this->enter_idle_();
}
void CC1101Component::set_gdo0_config(uint8_t value) {
this->state_.GDO0_CFG = value;
if (this->initialized_) {
this->write_(Register::IOCFG0);
}
}
void CC1101Component::set_gdo2_config(uint8_t value) {
this->state_.GDO2_CFG = value;
if (this->initialized_) {
this->write_(Register::IOCFG2);
}
}
bool CC1101Component::wait_for_state_(State target_state, uint32_t timeout_ms) {
uint32_t start = millis();
while (millis() - start < timeout_ms) {
@@ -282,6 +340,33 @@ void CC1101Component::read_(Register reg, uint8_t *buffer, size_t length) {
this->disable();
}
CC1101Error CC1101Component::transmit_packet(const std::vector<uint8_t> &packet) {
if (this->state_.PKT_FORMAT != static_cast<uint8_t>(PacketFormat::PACKET_FORMAT_FIFO)) {
return CC1101Error::PARAMS;
}
// Write packet
this->enter_idle_();
this->strobe_(Command::FTX);
if (this->state_.LENGTH_CONFIG == static_cast<uint8_t>(LengthConfig::LENGTH_CONFIG_VARIABLE)) {
this->write_(Register::FIFO, static_cast<uint8_t>(packet.size()));
}
this->write_(Register::FIFO, packet.data(), packet.size());
this->strobe_(Command::TX);
if (!this->wait_for_state_(State::IDLE, 1000)) {
ESP_LOGW(TAG, "TX timeout");
this->enter_idle_();
this->strobe_(Command::RX);
this->wait_for_state_(State::RX);
return CC1101Error::TIMEOUT;
}
// Return to rx
this->strobe_(Command::RX);
this->wait_for_state_(State::RX);
return CC1101Error::NONE;
}
// Setters
void CC1101Component::set_output_power(float value) {
this->output_power_requested_ = value;
@@ -428,6 +513,7 @@ void CC1101Component::set_modulation_type(Modulation value) {
this->state_.PA_POWER = value == Modulation::MODULATION_ASK_OOK ? 1 : 0;
if (this->initialized_) {
this->enter_idle_();
this->set_output_power(this->output_power_requested_);
this->write_(Register::MDMCFG2);
this->write_(Register::FREND0);
this->strobe_(Command::RX);
@@ -462,13 +548,6 @@ void CC1101Component::set_sync0(uint8_t value) {
}
}
void CC1101Component::set_pktlen(uint8_t value) {
this->state_.PKTLEN = value;
if (this->initialized_) {
this->write_(Register::PKTLEN);
}
}
void CC1101Component::set_magn_target(MagnTarget value) {
this->state_.MAGN_TARGET = static_cast<uint8_t>(value);
if (this->initialized_) {
@@ -546,4 +625,50 @@ void CC1101Component::set_hyst_level(HystLevel value) {
}
}
void CC1101Component::set_packet_mode(bool value) {
this->state_.PKT_FORMAT =
static_cast<uint8_t>(value ? PacketFormat::PACKET_FORMAT_FIFO : PacketFormat::PACKET_FORMAT_ASYNC_SERIAL);
if (value) {
// Configure GDO0 for FIFO status (asserts on RX FIFO threshold or end of packet)
this->state_.GDO0_CFG = 0x01;
// Set max RX FIFO threshold to ensure we only trigger on end-of-packet
this->state_.FIFO_THR = 15;
} else {
// Configure GDO0 for serial data (async serial mode)
this->state_.GDO0_CFG = 0x0D;
}
if (this->initialized_) {
this->write_(Register::PKTCTRL0);
this->write_(Register::IOCFG0);
this->write_(Register::FIFOTHR);
}
}
void CC1101Component::set_packet_length(uint8_t value) {
if (value == 0) {
this->state_.LENGTH_CONFIG = static_cast<uint8_t>(LengthConfig::LENGTH_CONFIG_VARIABLE);
} else {
this->state_.LENGTH_CONFIG = static_cast<uint8_t>(LengthConfig::LENGTH_CONFIG_FIXED);
this->state_.PKTLEN = value;
}
if (this->initialized_) {
this->write_(Register::PKTCTRL0);
this->write_(Register::PKTLEN);
}
}
void CC1101Component::set_crc_enable(bool value) {
this->state_.CRC_EN = value ? 1 : 0;
if (this->initialized_) {
this->write_(Register::PKTCTRL0);
}
}
void CC1101Component::set_whitening(bool value) {
this->state_.WHITE_DATA = value ? 1 : 0;
if (this->initialized_) {
this->write_(Register::PKTCTRL0);
}
}
} // namespace esphome::cc1101

View File

@@ -5,9 +5,12 @@
#include "esphome/components/spi/spi.h"
#include "esphome/core/automation.h"
#include "cc1101defs.h"
#include <vector>
namespace esphome::cc1101 {
enum class CC1101Error { NONE = 0, TIMEOUT, PARAMS, CRC_ERROR, FIFO_OVERFLOW };
class CC1101Component : public Component,
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW,
spi::CLOCK_PHASE_LEADING, spi::DATA_RATE_1MHZ> {
@@ -15,6 +18,7 @@ class CC1101Component : public Component,
CC1101Component();
void setup() override;
void loop() override;
void dump_config() override;
// Actions
@@ -24,8 +28,7 @@ class CC1101Component : public Component,
void set_idle();
// GDO Pin Configuration
void set_gdo0_config(uint8_t value);
void set_gdo2_config(uint8_t value);
void set_gdo0_pin(InternalGPIOPin *pin) { this->gdo0_pin_ = pin; }
// Configuration Setters
void set_output_power(float value);
@@ -48,7 +51,6 @@ class CC1101Component : public Component,
void set_num_preamble(uint8_t value);
void set_sync1(uint8_t value);
void set_sync0(uint8_t value);
void set_pktlen(uint8_t value);
// AGC settings
void set_magn_target(MagnTarget value);
@@ -63,6 +65,16 @@ class CC1101Component : public Component,
void set_wait_time(WaitTime value);
void set_hyst_level(HystLevel value);
// Packet mode settings
void set_packet_mode(bool value);
void set_packet_length(uint8_t value);
void set_crc_enable(bool value);
void set_whitening(bool value);
// Packet mode operations
CC1101Error transmit_packet(const std::vector<uint8_t> &packet);
Trigger<std::vector<uint8_t>, float, uint8_t> *get_packet_trigger() const { return this->packet_trigger_; }
protected:
uint16_t chip_id_{0};
bool initialized_{false};
@@ -73,6 +85,13 @@ class CC1101Component : public Component,
CC1101State state_;
// GDO pin for packet reception
InternalGPIOPin *gdo0_pin_{nullptr};
// Packet handling
Trigger<std::vector<uint8_t>, float, uint8_t> *packet_trigger_{new Trigger<std::vector<uint8_t>, float, uint8_t>()};
std::vector<uint8_t> packet_;
// Low-level Helpers
uint8_t strobe_(Command cmd);
void write_(Register reg);
@@ -107,4 +126,28 @@ template<typename... Ts> class SetIdleAction : public Action<Ts...>, public Pare
void play(const Ts &...x) override { this->parent_->set_idle(); }
};
template<typename... Ts> class SendPacketAction : public Action<Ts...>, public Parented<CC1101Component> {
public:
void set_data_template(std::function<std::vector<uint8_t>(Ts...)> func) { this->data_func_ = func; }
void set_data_static(const uint8_t *data, size_t len) {
this->data_static_ = data;
this->data_static_len_ = len;
}
void play(const Ts &...x) override {
if (this->data_func_) {
auto data = this->data_func_(x...);
this->parent_->transmit_packet(data);
} else if (this->data_static_ != nullptr) {
std::vector<uint8_t> data(this->data_static_, this->data_static_ + this->data_static_len_);
this->parent_->transmit_packet(data);
}
}
protected:
std::function<std::vector<uint8_t>(Ts...)> data_func_{};
const uint8_t *data_static_{nullptr};
size_t data_static_len_{0};
};
} // namespace esphome::cc1101

View File

@@ -6,6 +6,12 @@ namespace esphome::cc1101 {
static constexpr float XTAL_FREQUENCY = 26000000;
static constexpr float RSSI_OFFSET = 74.0f;
static constexpr float RSSI_STEP = 0.5f;
static constexpr uint8_t STATUS_CRC_OK_MASK = 0x80;
static constexpr uint8_t STATUS_LQI_MASK = 0x7F;
static constexpr uint8_t BUS_BURST = 0x40;
static constexpr uint8_t BUS_READ = 0x80;
static constexpr uint8_t BUS_WRITE = 0x00;
@@ -134,6 +140,10 @@ enum class SyncMode : uint8_t {
SYNC_MODE_15_16,
SYNC_MODE_16_16,
SYNC_MODE_30_32,
SYNC_MODE_NONE_CS,
SYNC_MODE_15_16_CS,
SYNC_MODE_16_16_CS,
SYNC_MODE_30_32_CS,
};
enum class Modulation : uint8_t {
@@ -218,6 +228,19 @@ enum class HystLevel : uint8_t {
HYST_LEVEL_HIGH,
};
enum class PacketFormat : uint8_t {
PACKET_FORMAT_FIFO,
PACKET_FORMAT_SYNC_SERIAL,
PACKET_FORMAT_RANDOM_TX,
PACKET_FORMAT_ASYNC_SERIAL,
};
enum class LengthConfig : uint8_t {
LENGTH_CONFIG_FIXED,
LENGTH_CONFIG_VARIABLE,
LENGTH_CONFIG_INFINITE,
};
struct __attribute__((packed)) CC1101State {
// Byte array accessors for bulk SPI transfers
uint8_t *regs() { return reinterpret_cast<uint8_t *>(this); }

View File

@@ -117,9 +117,7 @@ CONF_MIN_HUMIDITY = "min_humidity"
CONF_MAX_HUMIDITY = "max_humidity"
CONF_TARGET_HUMIDITY = "target_humidity"
visual_temperature = cv.float_with_unit(
"visual_temperature", "(°C|° C|°|C|°K|° K|K|°F|° F|F)?"
)
visual_temperature = cv.float_with_unit("visual_temperature", "(°|(° ?)?[CKF])?")
VISUAL_TEMPERATURE_STEP_SCHEMA = cv.Schema(

View File

@@ -7,9 +7,11 @@ BYTE_ORDER_LITTLE = "little_endian"
BYTE_ORDER_BIG = "big_endian"
CONF_COLOR_DEPTH = "color_depth"
CONF_CRC_ENABLE = "crc_enable"
CONF_DRAW_ROUNDING = "draw_rounding"
CONF_ENABLED = "enabled"
CONF_IGNORE_NOT_FOUND = "ignore_not_found"
CONF_ON_PACKET = "on_packet"
CONF_ON_RECEIVE = "on_receive"
CONF_ON_STATE_CHANGE = "on_state_change"
CONF_REQUEST_HEADERS = "request_headers"

View File

@@ -7,7 +7,7 @@ namespace copy {
static const char *const TAG = "copy.select";
void CopySelect::setup() {
source_->add_on_state_callback([this](const std::string &value, size_t index) { this->publish_state(index); });
source_->add_on_state_callback([this](size_t index) { this->publish_state(index); });
traits.set_options(source_->traits.get_options());

View File

@@ -1,4 +1,4 @@
from esphome import automation, pins
from esphome import automation, core, pins
import esphome.codegen as cg
from esphome.components import esp32, time
from esphome.components.esp32 import (
@@ -23,16 +23,20 @@ from esphome.const import (
CONF_MINUTE,
CONF_MODE,
CONF_NUMBER,
CONF_PIN,
CONF_PINS,
CONF_RUN_DURATION,
CONF_SECOND,
CONF_SLEEP_DURATION,
CONF_TIME_ID,
CONF_WAKEUP_PIN,
PLATFORM_BK72XX,
PLATFORM_ESP32,
PLATFORM_ESP8266,
PlatformFramework,
)
from esphome.core import CORE
from esphome.types import ConfigType
WAKEUP_PINS = {
VARIANT_ESP32: [
@@ -113,7 +117,7 @@ WAKEUP_PINS = {
}
def validate_pin_number(value):
def validate_pin_number_esp32(value: ConfigType) -> ConfigType:
valid_pins = WAKEUP_PINS.get(get_esp32_variant(), WAKEUP_PINS[VARIANT_ESP32])
if value[CONF_NUMBER] not in valid_pins:
raise cv.Invalid(
@@ -122,6 +126,51 @@ def validate_pin_number(value):
return value
def validate_pin_number(value: ConfigType) -> ConfigType:
if not CORE.is_esp32:
return value
return validate_pin_number_esp32(value)
def validate_wakeup_pin(
value: ConfigType | list[ConfigType],
) -> list[ConfigType]:
if not isinstance(value, list):
processed_pins: list[ConfigType] = [{CONF_PIN: value}]
else:
processed_pins = list(value)
for i, pin_config in enumerate(processed_pins):
# now validate each item
validated_pin = WAKEUP_PIN_SCHEMA(pin_config)
validate_pin_number(validated_pin[CONF_PIN])
processed_pins[i] = validated_pin
return processed_pins
def validate_config(config: ConfigType) -> ConfigType:
# right now only BK72XX supports the list format for wakeup pins
if CORE.is_bk72xx:
if CONF_WAKEUP_PIN_MODE in config:
wakeup_pins = config.get(CONF_WAKEUP_PIN, [])
if len(wakeup_pins) > 1:
raise cv.Invalid(
"You need to remove the global wakeup_pin_mode and define it per pin"
)
if wakeup_pins:
wakeup_pins[0][CONF_WAKEUP_PIN_MODE] = config.pop(CONF_WAKEUP_PIN_MODE)
elif (
isinstance(config.get(CONF_WAKEUP_PIN), list)
and len(config[CONF_WAKEUP_PIN]) > 1
):
raise cv.Invalid(
"Your platform does not support providing multiple entries in wakeup_pin"
)
return config
def _validate_ex1_wakeup_mode(value):
if value == "ALL_LOW":
esp32.only_on_variant(supported=[VARIANT_ESP32], msg_prefix="ALL_LOW")(value)
@@ -141,6 +190,15 @@ def _validate_ex1_wakeup_mode(value):
return value
def _validate_sleep_duration(value: core.TimePeriod) -> core.TimePeriod:
if not CORE.is_bk72xx:
return value
max_duration = core.TimePeriod(hours=36)
if value > max_duration:
raise cv.Invalid("sleep duration cannot be more than 36 hours on BK72XX")
return value
deep_sleep_ns = cg.esphome_ns.namespace("deep_sleep")
DeepSleepComponent = deep_sleep_ns.class_("DeepSleepComponent", cg.Component)
EnterDeepSleepAction = deep_sleep_ns.class_("EnterDeepSleepAction", automation.Action)
@@ -186,6 +244,13 @@ WAKEUP_CAUSES_SCHEMA = cv.Schema(
}
)
WAKEUP_PIN_SCHEMA = cv.Schema(
{
cv.Required(CONF_PIN): pins.internal_gpio_input_pin_schema,
cv.Optional(CONF_WAKEUP_PIN_MODE): cv.enum(WAKEUP_PIN_MODES, upper=True),
}
)
CONFIG_SCHEMA = cv.All(
cv.Schema(
{
@@ -194,14 +259,15 @@ CONFIG_SCHEMA = cv.All(
cv.All(cv.only_on_esp32, WAKEUP_CAUSES_SCHEMA),
cv.positive_time_period_milliseconds,
),
cv.Optional(CONF_SLEEP_DURATION): cv.positive_time_period_milliseconds,
cv.Optional(CONF_WAKEUP_PIN): cv.All(
cv.only_on_esp32,
pins.internal_gpio_input_pin_schema,
validate_pin_number,
cv.Optional(CONF_SLEEP_DURATION): cv.All(
cv.positive_time_period_milliseconds,
_validate_sleep_duration,
),
cv.Optional(CONF_WAKEUP_PIN): validate_wakeup_pin,
cv.Optional(CONF_WAKEUP_PIN_MODE): cv.All(
cv.only_on_esp32, cv.enum(WAKEUP_PIN_MODES), upper=True
cv.only_on([PLATFORM_ESP32, PLATFORM_BK72XX]),
cv.enum(WAKEUP_PIN_MODES),
upper=True,
),
cv.Optional(CONF_ESP32_EXT1_WAKEUP): cv.All(
cv.only_on_esp32,
@@ -212,7 +278,8 @@ CONFIG_SCHEMA = cv.All(
cv.Schema(
{
cv.Required(CONF_PINS): cv.ensure_list(
pins.internal_gpio_input_pin_schema, validate_pin_number
pins.internal_gpio_input_pin_schema,
validate_pin_number_esp32,
),
cv.Required(CONF_MODE): cv.All(
cv.enum(EXT1_WAKEUP_MODES, upper=True),
@@ -238,7 +305,8 @@ CONFIG_SCHEMA = cv.All(
),
}
).extend(cv.COMPONENT_SCHEMA),
cv.only_on([PLATFORM_ESP32, PLATFORM_ESP8266]),
cv.only_on([PLATFORM_ESP32, PLATFORM_ESP8266, PLATFORM_BK72XX]),
validate_config,
)
@@ -249,8 +317,21 @@ async def to_code(config):
if CONF_SLEEP_DURATION in config:
cg.add(var.set_sleep_duration(config[CONF_SLEEP_DURATION]))
if CONF_WAKEUP_PIN in config:
pin = await cg.gpio_pin_expression(config[CONF_WAKEUP_PIN])
cg.add(var.set_wakeup_pin(pin))
pins_as_list = config.get(CONF_WAKEUP_PIN, [])
if CORE.is_bk72xx:
cg.add(var.init_wakeup_pins_(len(pins_as_list)))
for item in pins_as_list:
cg.add(
var.add_wakeup_pin(
await cg.gpio_pin_expression(item[CONF_PIN]),
item.get(
CONF_WAKEUP_PIN_MODE, WakeupPinMode.WAKEUP_PIN_MODE_IGNORE
),
)
)
else:
pin = await cg.gpio_pin_expression(pins_as_list[0][CONF_PIN])
cg.add(var.set_wakeup_pin(pin))
if CONF_WAKEUP_PIN_MODE in config:
cg.add(var.set_wakeup_pin_mode(config[CONF_WAKEUP_PIN_MODE]))
if CONF_RUN_DURATION in config:
@@ -305,7 +386,10 @@ DEEP_SLEEP_ENTER_SCHEMA = cv.All(
cv.Schema(
{
cv.Exclusive(CONF_SLEEP_DURATION, "time"): cv.templatable(
cv.positive_time_period_milliseconds
cv.All(
cv.positive_time_period_milliseconds,
_validate_sleep_duration,
)
),
# Only on ESP32 due to how long the RTC on ESP8266 can stay asleep
cv.Exclusive(CONF_UNTIL, "time"): cv.All(
@@ -363,5 +447,6 @@ FILTER_SOURCE_FILES = filter_source_files_from_platform(
PlatformFramework.ESP32_IDF,
},
"deep_sleep_esp8266.cpp": {PlatformFramework.ESP8266_ARDUINO},
"deep_sleep_bk72xx.cpp": {PlatformFramework.BK72XX_ARDUINO},
}
)

View File

@@ -0,0 +1,64 @@
#ifdef USE_BK72XX
#include "deep_sleep_component.h"
#include "esphome/core/log.h"
namespace esphome::deep_sleep {
static const char *const TAG = "deep_sleep.bk72xx";
optional<uint32_t> DeepSleepComponent::get_run_duration_() const { return this->run_duration_; }
void DeepSleepComponent::dump_config_platform_() {
for (const WakeUpPinItem &item : this->wakeup_pins_) {
LOG_PIN(" Wakeup Pin: ", item.wakeup_pin);
}
}
bool DeepSleepComponent::pin_prevents_sleep_(WakeUpPinItem &pinItem) const {
return (pinItem.wakeup_pin_mode == WAKEUP_PIN_MODE_KEEP_AWAKE && pinItem.wakeup_pin != nullptr &&
!this->sleep_duration_.has_value() && (pinItem.wakeup_level == get_real_pin_state_(*pinItem.wakeup_pin)));
}
bool DeepSleepComponent::prepare_to_sleep_() {
if (wakeup_pins_.size() > 0) {
for (WakeUpPinItem &item : this->wakeup_pins_) {
if (pin_prevents_sleep_(item)) {
// Defer deep sleep until inactive
if (!this->next_enter_deep_sleep_) {
this->status_set_warning();
ESP_LOGV(TAG, "Waiting for pin to switch state to enter deep sleep...");
}
this->next_enter_deep_sleep_ = true;
return false;
}
}
}
return true;
}
void DeepSleepComponent::deep_sleep_() {
for (WakeUpPinItem &item : this->wakeup_pins_) {
if (item.wakeup_pin_mode == WAKEUP_PIN_MODE_INVERT_WAKEUP) {
if (item.wakeup_level == get_real_pin_state_(*item.wakeup_pin)) {
item.wakeup_level = !item.wakeup_level;
}
}
ESP_LOGI(TAG, "Wake-up on P%u %s (%d)", item.wakeup_pin->get_pin(), item.wakeup_level ? "HIGH" : "LOW",
static_cast<int32_t>(item.wakeup_pin_mode));
}
if (this->sleep_duration_.has_value())
lt_deep_sleep_config_timer((*this->sleep_duration_ / 1000) & 0xFFFFFFFF);
for (WakeUpPinItem &item : this->wakeup_pins_) {
lt_deep_sleep_config_gpio(1 << item.wakeup_pin->get_pin(), item.wakeup_level);
lt_deep_sleep_keep_floating_gpio(1 << item.wakeup_pin->get_pin(), true);
}
lt_deep_sleep_enter();
}
} // namespace esphome::deep_sleep
#endif // USE_BK72XX

View File

@@ -19,7 +19,7 @@
namespace esphome {
namespace deep_sleep {
#ifdef USE_ESP32
#if defined(USE_ESP32) || defined(USE_BK72XX)
/** The values of this enum define what should be done if deep sleep is set up with a wakeup pin on the ESP32
* and the scenario occurs that the wakeup pin is already in the wakeup state.
@@ -33,7 +33,17 @@ enum WakeupPinMode {
*/
WAKEUP_PIN_MODE_INVERT_WAKEUP,
};
#endif
#if defined(USE_BK72XX)
struct WakeUpPinItem {
InternalGPIOPin *wakeup_pin;
WakeupPinMode wakeup_pin_mode;
bool wakeup_level;
};
#endif // USE_BK72XX
#ifdef USE_ESP32
#if defined(USE_ESP32) && !defined(USE_ESP32_VARIANT_ESP32C2) && !defined(USE_ESP32_VARIANT_ESP32C3)
struct Ext1Wakeup {
uint64_t mask;
@@ -75,6 +85,13 @@ class DeepSleepComponent : public Component {
void set_wakeup_pin_mode(WakeupPinMode wakeup_pin_mode);
#endif // USE_ESP32
#if defined(USE_BK72XX)
void init_wakeup_pins_(size_t capacity) { this->wakeup_pins_.init(capacity); }
void add_wakeup_pin(InternalGPIOPin *wakeup_pin, WakeupPinMode wakeup_pin_mode) {
this->wakeup_pins_.emplace_back(WakeUpPinItem{wakeup_pin, wakeup_pin_mode, !wakeup_pin->is_inverted()});
}
#endif // USE_BK72XX
#if defined(USE_ESP32)
#if !defined(USE_ESP32_VARIANT_ESP32C2) && !defined(USE_ESP32_VARIANT_ESP32C3)
void set_ext1_wakeup(Ext1Wakeup ext1_wakeup);
@@ -114,7 +131,17 @@ class DeepSleepComponent : public Component {
bool prepare_to_sleep_();
void deep_sleep_();
#ifdef USE_BK72XX
bool pin_prevents_sleep_(WakeUpPinItem &pinItem) const;
bool get_real_pin_state_(InternalGPIOPin &pin) const { return (pin.digital_read() ^ pin.is_inverted()); }
#endif // USE_BK72XX
optional<uint64_t> sleep_duration_;
#ifdef USE_BK72XX
FixedVector<WakeUpPinItem> wakeup_pins_;
#endif // USE_BK72XX
#ifdef USE_ESP32
InternalGPIOPin *wakeup_pin_;
WakeupPinMode wakeup_pin_mode_{WAKEUP_PIN_MODE_IGNORE};
@@ -124,8 +151,10 @@ class DeepSleepComponent : public Component {
#endif
optional<bool> touch_wakeup_;
optional<WakeupCauseToRunDuration> wakeup_cause_to_run_duration_;
#endif // USE_ESP32
optional<uint32_t> run_duration_;
bool next_enter_deep_sleep_{false};
bool prevent_{false};

View File

@@ -4,6 +4,7 @@ import itertools
import logging
import os
from pathlib import Path
import re
from esphome import yaml_util
import esphome.codegen as cg
@@ -12,6 +13,7 @@ from esphome.const import (
CONF_ADVANCED,
CONF_BOARD,
CONF_COMPONENTS,
CONF_DISABLED,
CONF_ESPHOME,
CONF_FRAMEWORK,
CONF_IGNORE_EFUSE_CUSTOM_MAC,
@@ -23,6 +25,7 @@ from esphome.const import (
CONF_PLATFORMIO_OPTIONS,
CONF_REF,
CONF_REFRESH,
CONF_SAFE_MODE,
CONF_SOURCE,
CONF_TYPE,
CONF_VARIANT,
@@ -80,6 +83,7 @@ CONF_ASSERTION_LEVEL = "assertion_level"
CONF_COMPILER_OPTIMIZATION = "compiler_optimization"
CONF_ENABLE_IDF_EXPERIMENTAL_FEATURES = "enable_idf_experimental_features"
CONF_ENABLE_LWIP_ASSERT = "enable_lwip_assert"
CONF_ENABLE_OTA_ROLLBACK = "enable_ota_rollback"
CONF_EXECUTE_FROM_PSRAM = "execute_from_psram"
CONF_RELEASE = "release"
@@ -117,8 +121,8 @@ ARDUINO_ALLOWED_VARIANTS = [
]
def get_cpu_frequencies(*frequencies):
return [str(x) + "MHZ" for x in frequencies]
def get_cpu_frequencies(*frequencies: int) -> list[str]:
return [f"{frequency}MHZ" for frequency in frequencies]
CPU_FREQUENCIES = {
@@ -135,7 +139,7 @@ CPU_FREQUENCIES = {
}
# Make sure not missed here if a new variant added.
assert all(v in CPU_FREQUENCIES for v in VARIANTS)
assert all(variant in CPU_FREQUENCIES for variant in VARIANTS)
FULL_CPU_FREQUENCIES = set(itertools.chain.from_iterable(CPU_FREQUENCIES.values()))
@@ -249,10 +253,10 @@ def add_idf_sdkconfig_option(name: str, value: SdkconfigValueType):
def add_idf_component(
*,
name: str,
repo: str = None,
ref: str = None,
path: str = None,
refresh: TimePeriod = None,
repo: str | None = None,
ref: str | None = None,
path: str | None = None,
refresh: TimePeriod | None = None,
components: list[str] | None = None,
submodules: list[str] | None = None,
):
@@ -333,7 +337,7 @@ def _format_framework_espidf_version(ver: cv.Version, release: str) -> str:
return f"pioarduino/framework-espidf@https://github.com/pioarduino/esp-idf/releases/download/v{str(ver)}/esp-idf-v{str(ver)}.{ext}"
def _is_framework_url(source: str) -> str:
def _is_framework_url(source: str) -> bool:
# platformio accepts many URL schemes for framework repositories and archives including http, https, git, file, and symlink
import urllib.parse
@@ -570,6 +574,13 @@ def final_validate(config):
path=[CONF_FLASH_SIZE],
)
)
if advanced[CONF_ENABLE_OTA_ROLLBACK]:
safe_mode_config = full_config.get(CONF_SAFE_MODE)
if safe_mode_config is None or safe_mode_config.get(CONF_DISABLED, False):
_LOGGER.warning(
"OTA rollback requires safe_mode, disabling rollback support"
)
advanced[CONF_ENABLE_OTA_ROLLBACK] = False
if errs:
raise cv.MultipleInvalid(errs)
@@ -595,9 +606,6 @@ CONF_LOOP_TASK_STACK_SIZE = "loop_task_stack_size"
KEY_VFS_SELECT_REQUIRED = "vfs_select_required"
KEY_VFS_DIR_REQUIRED = "vfs_dir_required"
# Ring buffer IRAM requirement tracking
KEY_RINGBUF_IN_IRAM = "ringbuf_in_iram"
def require_vfs_select() -> None:
"""Mark that VFS select support is required by a component.
@@ -617,23 +625,15 @@ def require_vfs_dir() -> None:
CORE.data[KEY_VFS_DIR_REQUIRED] = True
def enable_ringbuf_in_iram() -> None:
"""Keep ring buffer functions in IRAM instead of moving them to flash.
Call this from components that use esphome/core/ring_buffer.cpp and need
the ring buffer functions to remain in IRAM for performance reasons
(e.g., voice assistants, audio components).
This prevents CONFIG_RINGBUF_PLACE_FUNCTIONS_INTO_FLASH from being enabled.
"""
CORE.data[KEY_RINGBUF_IN_IRAM] = True
def _parse_idf_component(value: str) -> ConfigType:
"""Parse IDF component shorthand syntax like 'owner/component^version'"""
if "^" not in value:
raise cv.Invalid(f"Invalid IDF component shorthand '{value}'")
name, ref = value.split("^", 1)
return {CONF_NAME: name, CONF_REF: ref}
# Match operator followed by version-like string (digit or *)
if match := re.search(r"(~=|>=|<=|==|!=|>|<|\^|~)(\d|\*)", value):
return {CONF_NAME: value[: match.start()], CONF_REF: value[match.start() :]}
raise cv.Invalid(
f"Invalid IDF component shorthand '{value}'. "
f"Expected format: 'owner/component<op>version' where <op> is one of: ^, ~, ~=, ==, !=, >=, >, <=, <"
)
def _validate_idf_component(config: ConfigType) -> ConfigType:
@@ -701,6 +701,7 @@ FRAMEWORK_SCHEMA = cv.Schema(
cv.Optional(CONF_LOOP_TASK_STACK_SIZE, default=8192): cv.int_range(
min=8192, max=32768
),
cv.Optional(CONF_ENABLE_OTA_ROLLBACK, default=True): cv.boolean,
}
),
cv.Optional(CONF_COMPONENTS, default=[]): cv.ensure_list(
@@ -981,29 +982,14 @@ async def to_code(config):
cg.add_platformio_option("framework", "arduino, espidf")
cg.add_build_flag("-DUSE_ARDUINO")
cg.add_build_flag("-DUSE_ESP32_FRAMEWORK_ARDUINO")
cg.add_platformio_option(
"board_build.embed_txtfiles",
[
"managed_components/espressif__esp_insights/server_certs/https_server.crt",
"managed_components/espressif__esp_rainmaker/server_certs/rmaker_mqtt_server.crt",
"managed_components/espressif__esp_rainmaker/server_certs/rmaker_claim_service_server.crt",
"managed_components/espressif__esp_rainmaker/server_certs/rmaker_ota_server.crt",
],
)
cg.add_define(
"USE_ARDUINO_VERSION_CODE",
cg.RawExpression(
f"VERSION_CODE({framework_ver.major}, {framework_ver.minor}, {framework_ver.patch})"
),
)
add_idf_sdkconfig_option(
"CONFIG_ARDUINO_LOOP_STACK_SIZE",
conf[CONF_ADVANCED][CONF_LOOP_TASK_STACK_SIZE],
)
add_idf_sdkconfig_option("CONFIG_AUTOSTART_ARDUINO", True)
add_idf_sdkconfig_option("CONFIG_MBEDTLS_PSK_MODES", True)
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:
@@ -1041,18 +1027,14 @@ async def to_code(config):
add_idf_sdkconfig_option("CONFIG_FREERTOS_PLACE_FUNCTIONS_INTO_FLASH", True)
# Place ring buffer functions into flash instead of IRAM by default
# This saves IRAM but may impact performance for audio/voice components.
# Components that need ring buffer in IRAM call enable_ringbuf_in_iram().
# Users can also set ringbuf_in_iram: true to force IRAM placement.
# In ESP-IDF 6.0 flash placement becomes the default.
if conf[CONF_ADVANCED][CONF_RINGBUF_IN_IRAM] or CORE.data.get(
KEY_RINGBUF_IN_IRAM, False
):
# User config or component requires ring buffer in IRAM for performance
# This saves IRAM. In ESP-IDF 6.0 flash placement becomes the default.
# Users can set ringbuf_in_iram: true as an escape hatch if they encounter issues.
if conf[CONF_ADVANCED][CONF_RINGBUF_IN_IRAM]:
# User requests ring buffer in IRAM
# IDF 6.0+: will need CONFIG_RINGBUF_PLACE_ISR_FUNCTIONS_INTO_FLASH=n
add_idf_sdkconfig_option("CONFIG_RINGBUF_PLACE_ISR_FUNCTIONS_INTO_FLASH", False)
else:
# No component needs it - place in flash to save IRAM
# Place in flash to save IRAM (default)
add_idf_sdkconfig_option("CONFIG_RINGBUF_PLACE_FUNCTIONS_INTO_FLASH", True)
# Setup watchdog
@@ -1178,6 +1160,11 @@ async def to_code(config):
"CONFIG_BOOTLOADER_CACHE_32BIT_ADDR_QUAD_FLASH", True
)
# Enable OTA rollback support
if advanced[CONF_ENABLE_OTA_ROLLBACK]:
add_idf_sdkconfig_option("CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE", True)
cg.add_define("USE_OTA_ROLLBACK")
cg.add_define("ESPHOME_LOOP_TASK_STACK_SIZE", advanced[CONF_LOOP_TASK_STACK_SIZE])
cg.add_define(
@@ -1207,7 +1194,7 @@ APP_PARTITION_SIZES = {
}
def get_arduino_partition_csv(flash_size):
def get_arduino_partition_csv(flash_size: str):
app_partition_size = APP_PARTITION_SIZES[flash_size]
eeprom_partition_size = 0x1000 # 4 KB
spiffs_partition_size = 0xF000 # 60 KB
@@ -1227,7 +1214,7 @@ spiffs, data, spiffs, 0x{spiffs_partition_start:X}, 0x{spiffs_partition_size:
"""
def get_idf_partition_csv(flash_size):
def get_idf_partition_csv(flash_size: str):
app_partition_size = APP_PARTITION_SIZES[flash_size]
return f"""\

View File

@@ -4,25 +4,20 @@
#include "esphome/core/hal.h"
#include "esphome/core/helpers.h"
#include "preferences.h"
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <esp_clk_tree.h>
#include <esp_cpu.h>
#include <esp_idf_version.h>
#include <esp_ota_ops.h>
#include <esp_task_wdt.h>
#include <esp_timer.h>
#include <soc/rtc.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <hal/cpu_hal.h>
void setup(); // NOLINT(readability-redundant-declaration)
void loop(); // NOLINT(readability-redundant-declaration)
#ifdef USE_ARDUINO
#include <Esp.h>
#else
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 1, 0)
#include <esp_clk_tree.h>
#endif
void setup();
void loop();
#endif
// Weak stub for initArduino - overridden when the Arduino component is present
extern "C" __attribute__((weak)) void initArduino() {}
namespace esphome {
@@ -41,29 +36,13 @@ void arch_restart() {
void arch_init() {
// Enable the task watchdog only on the loop task (from which we're currently running)
#if defined(USE_ESP_IDF)
esp_task_wdt_add(nullptr);
// Idle task watchdog is disabled on ESP-IDF
#elif defined(USE_ARDUINO)
enableLoopWDT();
// Disable idle task watchdog on the core we're using (Arduino pins the task to a core)
#if defined(CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0) && CONFIG_ARDUINO_RUNNING_CORE == 0
disableCore0WDT();
#endif
#if defined(CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU1) && CONFIG_ARDUINO_RUNNING_CORE == 1
disableCore1WDT();
#endif
#endif
// If the bootloader was compiled with CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE the current
// partition will get rolled back unless it is marked as valid.
esp_ota_img_states_t state;
const esp_partition_t *running = esp_ota_get_running_partition();
if (esp_ota_get_state_partition(running, &state) == ESP_OK) {
if (state == ESP_OTA_IMG_PENDING_VERIFY) {
esp_ota_mark_app_valid_cancel_rollback();
}
}
// Handle OTA rollback: mark partition valid immediately unless USE_OTA_ROLLBACK is enabled,
// in which case safe_mode will mark it valid after confirming successful boot.
#ifndef USE_OTA_ROLLBACK
esp_ota_mark_app_valid_cancel_rollback();
#endif
}
void IRAM_ATTR HOT arch_feed_wdt() { esp_task_wdt_reset(); }
@@ -71,21 +50,10 @@ uint8_t progmem_read_byte(const uint8_t *addr) { return *addr; }
uint32_t arch_get_cpu_cycle_count() { return esp_cpu_get_cycle_count(); }
uint32_t arch_get_cpu_freq_hz() {
uint32_t freq = 0;
#ifdef USE_ESP_IDF
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 1, 0)
esp_clk_tree_src_get_freq_hz(SOC_MOD_CLK_CPU, ESP_CLK_TREE_SRC_FREQ_PRECISION_CACHED, &freq);
#else
rtc_cpu_freq_config_t config;
rtc_clk_cpu_freq_get_config(&config);
freq = config.freq_mhz * 1000000U;
#endif
#elif defined(USE_ARDUINO)
freq = ESP.getCpuFreqMHz() * 1000000;
#endif
return freq;
}
#ifdef USE_ESP_IDF
TaskHandle_t loop_task_handle = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
void loop_task(void *pv_params) {
@@ -96,6 +64,7 @@ void loop_task(void *pv_params) {
}
extern "C" void app_main() {
initArduino();
esp32::setup_preferences();
#if CONFIG_FREERTOS_UNICORE
xTaskCreate(loop_task, "loopTask", ESPHOME_LOOP_TASK_STACK_SIZE, nullptr, 1, &loop_task_handle);
@@ -103,11 +72,6 @@ extern "C" void app_main() {
xTaskCreatePinnedToCore(loop_task, "loopTask", ESPHOME_LOOP_TASK_STACK_SIZE, nullptr, 1, &loop_task_handle, 1);
#endif
}
#endif // USE_ESP_IDF
#ifdef USE_ARDUINO
extern "C" void init() { esp32::setup_preferences(); }
#endif // USE_ARDUINO
} // namespace esphome

View File

@@ -85,6 +85,7 @@ void ESP32InternalGPIOPin::attach_interrupt(void (*func)(void *), void *arg, gpi
break;
}
gpio_set_intr_type(this->get_pin_num(), idf_type);
gpio_intr_enable(this->get_pin_num());
if (!isr_service_installed) {
auto res = gpio_install_isr_service(ESP_INTR_FLAG_LEVEL3);
if (res != ESP_OK) {
@@ -94,7 +95,6 @@ void ESP32InternalGPIOPin::attach_interrupt(void (*func)(void *), void *arg, gpi
isr_service_installed = true;
}
gpio_isr_handler_add(this->get_pin_num(), func, arg);
gpio_intr_enable(this->get_pin_num());
}
std::string ESP32InternalGPIOPin::dump_summary() const {

View File

@@ -5,6 +5,7 @@ import json # noqa: E402
import os # noqa: E402
import pathlib # noqa: E402
import shutil # noqa: E402
from glob import glob # noqa: E402
def merge_factory_bin(source, target, env):
@@ -126,3 +127,14 @@ def esp32_copy_ota_bin(source, target, env):
# Run merge first, then ota copy second
env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", merge_factory_bin) # noqa: F821
env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", esp32_copy_ota_bin) # noqa: F821
# Find server certificates in managed components and generate .S files.
# Workaround for PlatformIO not processing target_add_binary_data() from managed component CMakeLists.
project_dir = env.subst("$PROJECT_DIR")
managed_components = os.path.join(project_dir, "managed_components")
if os.path.isdir(managed_components):
for cert_file in glob(os.path.join(managed_components, "**/server_certs/*.crt"), recursive=True):
try:
env.FileToAsm(cert_file, FILE_TYPE="TEXT")
except Exception as e:
print(f"Error processing {os.path.basename(cert_file)}: {e}")

View File

@@ -4,26 +4,28 @@
#include "esphome/core/log.h"
#include "esphome/core/preferences.h"
#include <nvs_flash.h>
#include <cstring>
#include <cinttypes>
#include <vector>
#include <string>
#include <cstring>
#include <memory>
#include <vector>
namespace esphome {
namespace esp32 {
static const char *const TAG = "esp32.preferences";
// Buffer size for converting uint32_t to string: max "4294967295" (10 chars) + null terminator + 1 padding
static constexpr size_t KEY_BUFFER_SIZE = 12;
struct NVSData {
std::string key;
uint32_t key;
std::unique_ptr<uint8_t[]> data;
size_t len;
void set_data(const uint8_t *src, size_t size) {
data = std::make_unique<uint8_t[]>(size);
memcpy(data.get(), src, size);
len = size;
this->data = std::make_unique<uint8_t[]>(size);
memcpy(this->data.get(), src, size);
this->len = size;
}
};
@@ -31,27 +33,27 @@ static std::vector<NVSData> s_pending_save; // NOLINT(cppcoreguidelines-avoid-n
class ESP32PreferenceBackend : public ESPPreferenceBackend {
public:
std::string key;
uint32_t key;
uint32_t nvs_handle;
bool save(const uint8_t *data, size_t len) override {
// try find in pending saves and update that
for (auto &obj : s_pending_save) {
if (obj.key == key) {
if (obj.key == this->key) {
obj.set_data(data, len);
return true;
}
}
NVSData save{};
save.key = key;
save.key = this->key;
save.set_data(data, len);
s_pending_save.emplace_back(std::move(save));
ESP_LOGVV(TAG, "s_pending_save: key: %s, len: %zu", key.c_str(), len);
ESP_LOGVV(TAG, "s_pending_save: key: %" PRIu32 ", len: %zu", this->key, len);
return true;
}
bool load(uint8_t *data, size_t len) override {
// try find in pending saves and load from that
for (auto &obj : s_pending_save) {
if (obj.key == key) {
if (obj.key == this->key) {
if (obj.len != len) {
// size mismatch
return false;
@@ -61,22 +63,24 @@ class ESP32PreferenceBackend : public ESPPreferenceBackend {
}
}
char key_str[KEY_BUFFER_SIZE];
snprintf(key_str, sizeof(key_str), "%" PRIu32, this->key);
size_t actual_len;
esp_err_t err = nvs_get_blob(nvs_handle, key.c_str(), nullptr, &actual_len);
esp_err_t err = nvs_get_blob(this->nvs_handle, key_str, nullptr, &actual_len);
if (err != 0) {
ESP_LOGV(TAG, "nvs_get_blob('%s'): %s - the key might not be set yet", key.c_str(), esp_err_to_name(err));
ESP_LOGV(TAG, "nvs_get_blob('%s'): %s - the key might not be set yet", key_str, esp_err_to_name(err));
return false;
}
if (actual_len != len) {
ESP_LOGVV(TAG, "NVS length does not match (%zu!=%zu)", actual_len, len);
return false;
}
err = nvs_get_blob(nvs_handle, key.c_str(), data, &len);
err = nvs_get_blob(this->nvs_handle, key_str, data, &len);
if (err != 0) {
ESP_LOGV(TAG, "nvs_get_blob('%s') failed: %s", key.c_str(), esp_err_to_name(err));
ESP_LOGV(TAG, "nvs_get_blob('%s') failed: %s", key_str, esp_err_to_name(err));
return false;
} else {
ESP_LOGVV(TAG, "nvs_get_blob: key: %s, len: %zu", key.c_str(), len);
ESP_LOGVV(TAG, "nvs_get_blob: key: %s, len: %zu", key_str, len);
}
return true;
}
@@ -103,14 +107,12 @@ class ESP32Preferences : public ESPPreferences {
}
}
ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash) override {
return make_preference(length, type);
return this->make_preference(length, type);
}
ESPPreferenceObject make_preference(size_t length, uint32_t type) override {
auto *pref = new ESP32PreferenceBackend(); // NOLINT(cppcoreguidelines-owning-memory)
pref->nvs_handle = nvs_handle;
uint32_t keyval = type;
pref->key = str_sprintf("%" PRIu32, keyval);
pref->nvs_handle = this->nvs_handle;
pref->key = type;
return ESPPreferenceObject(pref);
}
@@ -123,17 +125,19 @@ class ESP32Preferences : public ESPPreferences {
// goal try write all pending saves even if one fails
int cached = 0, written = 0, failed = 0;
esp_err_t last_err = ESP_OK;
std::string last_key{};
uint32_t last_key = 0;
// go through vector from back to front (makes erase easier/more efficient)
for (ssize_t i = s_pending_save.size() - 1; i >= 0; i--) {
const auto &save = s_pending_save[i];
ESP_LOGVV(TAG, "Checking if NVS data %s has changed", save.key.c_str());
if (is_changed(nvs_handle, save)) {
esp_err_t err = nvs_set_blob(nvs_handle, save.key.c_str(), save.data.get(), save.len);
ESP_LOGV(TAG, "sync: key: %s, len: %zu", save.key.c_str(), save.len);
ESP_LOGVV(TAG, "Checking if NVS data %" PRIu32 " has changed", save.key);
if (this->is_changed(this->nvs_handle, save)) {
char key_str[KEY_BUFFER_SIZE];
snprintf(key_str, sizeof(key_str), "%" PRIu32, save.key);
esp_err_t err = nvs_set_blob(this->nvs_handle, key_str, save.data.get(), save.len);
ESP_LOGV(TAG, "sync: key: %s, len: %zu", key_str, save.len);
if (err != 0) {
ESP_LOGV(TAG, "nvs_set_blob('%s', len=%zu) failed: %s", save.key.c_str(), save.len, esp_err_to_name(err));
ESP_LOGV(TAG, "nvs_set_blob('%s', len=%zu) failed: %s", key_str, save.len, esp_err_to_name(err));
failed++;
last_err = err;
last_key = save.key;
@@ -141,7 +145,7 @@ class ESP32Preferences : public ESPPreferences {
}
written++;
} else {
ESP_LOGV(TAG, "NVS data not changed skipping %s len=%zu", save.key.c_str(), save.len);
ESP_LOGV(TAG, "NVS data not changed skipping %" PRIu32 " len=%zu", save.key, save.len);
cached++;
}
s_pending_save.erase(s_pending_save.begin() + i);
@@ -149,12 +153,12 @@ class ESP32Preferences : public ESPPreferences {
ESP_LOGD(TAG, "Writing %d items: %d cached, %d written, %d failed", cached + written + failed, cached, written,
failed);
if (failed > 0) {
ESP_LOGE(TAG, "Writing %d items failed. Last error=%s for key=%s", failed, esp_err_to_name(last_err),
last_key.c_str());
ESP_LOGE(TAG, "Writing %d items failed. Last error=%s for key=%" PRIu32, failed, esp_err_to_name(last_err),
last_key);
}
// note: commit on esp-idf currently is a no-op, nvs_set_blob always writes
esp_err_t err = nvs_commit(nvs_handle);
esp_err_t err = nvs_commit(this->nvs_handle);
if (err != 0) {
ESP_LOGV(TAG, "nvs_commit() failed: %s", esp_err_to_name(err));
return false;
@@ -163,10 +167,13 @@ class ESP32Preferences : public ESPPreferences {
return failed == 0;
}
bool is_changed(const uint32_t nvs_handle, const NVSData &to_save) {
char key_str[KEY_BUFFER_SIZE];
snprintf(key_str, sizeof(key_str), "%" PRIu32, to_save.key);
size_t actual_len;
esp_err_t err = nvs_get_blob(nvs_handle, to_save.key.c_str(), nullptr, &actual_len);
esp_err_t err = nvs_get_blob(nvs_handle, key_str, nullptr, &actual_len);
if (err != 0) {
ESP_LOGV(TAG, "nvs_get_blob('%s'): %s - the key might not be set yet", to_save.key.c_str(), esp_err_to_name(err));
ESP_LOGV(TAG, "nvs_get_blob('%s'): %s - the key might not be set yet", key_str, esp_err_to_name(err));
return true;
}
// Check size first before allocating memory
@@ -174,9 +181,9 @@ class ESP32Preferences : public ESPPreferences {
return true;
}
auto stored_data = std::make_unique<uint8_t[]>(actual_len);
err = nvs_get_blob(nvs_handle, to_save.key.c_str(), stored_data.get(), &actual_len);
err = nvs_get_blob(nvs_handle, key_str, stored_data.get(), &actual_len);
if (err != 0) {
ESP_LOGV(TAG, "nvs_get_blob('%s') failed: %s", to_save.key.c_str(), esp_err_to_name(err));
ESP_LOGV(TAG, "nvs_get_blob('%s') failed: %s", key_str, esp_err_to_name(err));
return true;
}
return memcmp(to_save.data.get(), stored_data.get(), to_save.len) != 0;

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();

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

@@ -5,7 +5,7 @@ import logging
from esphome import automation
import esphome.codegen as cg
from esphome.components import esp32_ble, ota
from esphome.components import esp32_ble
from esphome.components.esp32 import add_idf_sdkconfig_option
from esphome.components.esp32_ble import (
IDF_MAX_CONNECTIONS,
@@ -328,7 +328,7 @@ async def to_code(config):
# Note: CONFIG_BT_ACL_CONNECTIONS and CONFIG_BTDM_CTRL_BLE_MAX_CONN are now
# configured in esp32_ble component based on max_connections setting
ota.request_ota_state_listeners() # To be notified when an OTA update starts
cg.add_define("USE_OTA_STATE_CALLBACK") # To be notified when an OTA update starts
cg.add_define("USE_ESP32_BLE_CLIENT")
CORE.add_job(_add_ble_features)

View File

@@ -71,23 +71,20 @@ void ESP32BLETracker::setup() {
global_esp32_ble_tracker = this;
#ifdef USE_OTA_STATE_LISTENER
ota::get_global_ota_callback()->add_global_state_listener(this);
#endif
}
#ifdef USE_OTA_STATE_LISTENER
void ESP32BLETracker::on_ota_global_state(ota::OTAState state, float progress, uint8_t error, ota::OTAComponent *comp) {
if (state == ota::OTA_STARTED) {
this->stop_scan();
#ifdef USE_OTA
ota::get_global_ota_callback()->add_on_state_callback(
[this](ota::OTAState state, float progress, uint8_t error, ota::OTAComponent *comp) {
if (state == ota::OTA_STARTED) {
this->stop_scan();
#ifdef ESPHOME_ESP32_BLE_TRACKER_CLIENT_COUNT
for (auto *client : this->clients_) {
client->disconnect();
}
for (auto *client : this->clients_) {
client->disconnect();
}
#endif
}
});
#endif
}
}
#endif
void ESP32BLETracker::loop() {
if (!this->parent_->is_active()) {

View File

@@ -22,10 +22,6 @@
#include "esphome/components/esp32_ble/ble_uuid.h"
#include "esphome/components/esp32_ble/ble_scan_result.h"
#ifdef USE_OTA_STATE_LISTENER
#include "esphome/components/ota/ota_backend.h"
#endif
namespace esphome::esp32_ble_tracker {
using namespace esp32_ble;
@@ -245,9 +241,6 @@ class ESP32BLETracker : public Component,
public GAPScanEventHandler,
public GATTcEventHandler,
public BLEStatusEventHandler,
#ifdef USE_OTA_STATE_LISTENER
public ota::OTAGlobalStateListener,
#endif
public Parented<ESP32BLE> {
public:
void set_scan_duration(uint32_t scan_duration) { scan_duration_ = scan_duration; }
@@ -281,10 +274,6 @@ class ESP32BLETracker : public Component,
void gap_scan_event_handler(const BLEScanResult &scan_result) override;
void ble_before_disabled_event_handler() override;
#ifdef USE_OTA_STATE_LISTENER
void on_ota_global_state(ota::OTAState state, float progress, uint8_t error, ota::OTAComponent *comp) override;
#endif
/// Add a listener for scanner state changes
void add_scanner_state_listener(BLEScannerStateListener *listener) {
this->scanner_state_listeners_.push_back(listener);

View File

@@ -3,7 +3,7 @@ import logging
from esphome import automation, pins
import esphome.codegen as cg
from esphome.components import i2c
from esphome.components.esp32 import add_idf_component
from esphome.components.esp32 import add_idf_component, add_idf_sdkconfig_option
from esphome.components.psram import DOMAIN as psram_domain
import esphome.config_validation as cv
from esphome.const import (
@@ -186,7 +186,7 @@ CONFIG_SCHEMA = cv.All(
{
cv.Required(CONF_PIN): pins.internal_gpio_input_pin_number,
cv.Optional(CONF_FREQUENCY, default="20MHz"): cv.All(
cv.frequency, cv.Range(min=8e6, max=20e6)
cv.frequency, cv.float_range(min=8e6, max=20e6)
),
}
),
@@ -352,6 +352,8 @@ async def to_code(config):
cg.add_define("USE_CAMERA")
add_idf_component(name="espressif/esp32-camera", ref="2.1.1")
add_idf_sdkconfig_option("CONFIG_SCCB_HARDWARE_I2C_DRIVER_NEW", True)
add_idf_sdkconfig_option("CONFIG_SCCB_HARDWARE_I2C_DRIVER_LEGACY", False)
for conf in config.get(CONF_ON_STREAM_START, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)

View File

@@ -16,7 +16,7 @@ def valid_pwm_pin(value):
esp8266_pwm_ns = cg.esphome_ns.namespace("esp8266_pwm")
ESP8266PWM = esp8266_pwm_ns.class_("ESP8266PWM", output.FloatOutput, cg.Component)
SetFrequencyAction = esp8266_pwm_ns.class_("SetFrequencyAction", automation.Action)
validate_frequency = cv.All(cv.frequency, cv.Range(min=1.0e-6))
validate_frequency = cv.All(cv.frequency, cv.float_range(min=1.0e-6))
CONFIG_SCHEMA = cv.All(
output.FLOAT_OUTPUT_SCHEMA.extend(

View File

@@ -41,6 +41,10 @@ static constexpr size_t SHA256_HEX_SIZE = 64; // SHA256 hash as hex string (32
#endif // USE_OTA_PASSWORD
void ESPHomeOTAComponent::setup() {
#ifdef USE_OTA_STATE_CALLBACK
ota::register_ota_platform(this);
#endif
this->server_ = socket::socket_ip_loop_monitored(SOCK_STREAM, 0); // monitored for incoming connections
if (this->server_ == nullptr) {
this->log_socket_error_(LOG_STR("creation"));
@@ -293,8 +297,8 @@ void ESPHomeOTAComponent::handle_data_() {
// accidentally trigger the update process.
this->log_start_(LOG_STR("update"));
this->status_set_warning();
#ifdef USE_OTA_STATE_LISTENER
this->notify_state_(ota::OTA_STARTED, 0.0f, 0);
#ifdef USE_OTA_STATE_CALLBACK
this->state_callback_.call(ota::OTA_STARTED, 0.0f, 0);
#endif
// This will block for a few seconds as it locks flash
@@ -353,8 +357,8 @@ void ESPHomeOTAComponent::handle_data_() {
last_progress = now;
float percentage = (total * 100.0f) / ota_size;
ESP_LOGD(TAG, "Progress: %0.1f%%", percentage);
#ifdef USE_OTA_STATE_LISTENER
this->notify_state_(ota::OTA_IN_PROGRESS, percentage, 0);
#ifdef USE_OTA_STATE_CALLBACK
this->state_callback_.call(ota::OTA_IN_PROGRESS, percentage, 0);
#endif
// feed watchdog and give other tasks a chance to run
this->yield_and_feed_watchdog_();
@@ -383,8 +387,8 @@ void ESPHomeOTAComponent::handle_data_() {
delay(10);
ESP_LOGI(TAG, "Update complete");
this->status_clear_warning();
#ifdef USE_OTA_STATE_LISTENER
this->notify_state_(ota::OTA_COMPLETED, 100.0f, 0);
#ifdef USE_OTA_STATE_CALLBACK
this->state_callback_.call(ota::OTA_COMPLETED, 100.0f, 0);
#endif
delay(100); // NOLINT
App.safe_reboot();
@@ -398,8 +402,8 @@ error:
}
this->status_momentary_error("err", 5000);
#ifdef USE_OTA_STATE_LISTENER
this->notify_state_(ota::OTA_ERROR, 0.0f, static_cast<uint8_t>(error_code));
#ifdef USE_OTA_STATE_CALLBACK
this->state_callback_.call(ota::OTA_ERROR, 0.0f, static_cast<uint8_t>(error_code));
#endif
}

View File

@@ -80,6 +80,7 @@ class ESPHomeOTAComponent : public ota::OTAComponent {
#ifdef USE_OTA_PASSWORD
std::string password_;
std::unique_ptr<uint8_t[]> auth_buf_;
#endif // USE_OTA_PASSWORD
std::unique_ptr<socket::Socket> server_;
@@ -93,7 +94,6 @@ class ESPHomeOTAComponent : public ota::OTAComponent {
uint8_t handshake_buf_pos_{0};
uint8_t ota_features_{0};
#ifdef USE_OTA_PASSWORD
std::unique_ptr<uint8_t[]> auth_buf_;
uint8_t auth_buf_pos_{0};
uint8_t auth_type_{0}; // Store auth type to know which hasher to use
#endif // USE_OTA_PASSWORD

View File

@@ -13,7 +13,7 @@ static const char *const TAG = "espnow.transport";
bool ESPNowTransport::should_send() { return this->parent_ != nullptr && !this->parent_->is_failed(); }
void ESPNowTransport::setup() {
packet_transport::PacketTransport::setup();
PacketTransport::setup();
if (this->parent_ == nullptr) {
ESP_LOGE(TAG, "ESPNow component not set");
@@ -26,15 +26,10 @@ void ESPNowTransport::setup() {
this->peer_address_[2], this->peer_address_[3], this->peer_address_[4], this->peer_address_[5]);
// Register received handler
this->parent_->register_received_handler(static_cast<ESPNowReceivedPacketHandler *>(this));
this->parent_->register_received_handler(this);
// Register broadcasted handler
this->parent_->register_broadcasted_handler(static_cast<ESPNowBroadcastedHandler *>(this));
}
void ESPNowTransport::update() {
packet_transport::PacketTransport::update();
this->updated_ = true;
this->parent_->register_broadcasted_handler(this);
}
void ESPNowTransport::send_packet(const std::vector<uint8_t> &buf) const {

View File

@@ -18,7 +18,6 @@ class ESPNowTransport : public packet_transport::PacketTransport,
public ESPNowBroadcastedHandler {
public:
void setup() override;
void update() override;
float get_setup_priority() const override { return setup_priority::AFTER_WIFI; }
void set_peer_address(peer_address_t address) {

View File

@@ -434,10 +434,13 @@ def _final_validate_rmii_pins(config: ConfigType) -> None:
# Check all used pins against RMII reserved pins
for pin_list in pins.PIN_SCHEMA_REGISTRY.pins_used.values():
for pin_path, _, pin_config in pin_list:
for pin_path, pin_device, pin_config in pin_list:
pin_num = pin_config.get(CONF_NUMBER)
if pin_num not in rmii_pins:
continue
# Skip if pin is not directly on ESP, but at some expander (device set to something else than 'None')
if pin_device is not None:
continue
# Found a conflict - show helpful error message
pin_function = rmii_pins[pin_num]
component_path = ".".join(str(p) for p in pin_path)

View File

@@ -50,7 +50,9 @@ CONFIG_SCHEMA = cv.All(
cv.GenerateID(): cv.declare_id(FactoryResetComponent),
cv.Optional(CONF_MAX_DELAY, default="10s"): cv.All(
cv.positive_time_period_seconds,
cv.Range(min=cv.TimePeriod(milliseconds=1000)),
cv.Range(
min=cv.TimePeriod(seconds=1), max=cv.TimePeriod(seconds=65535)
),
),
cv.Optional(CONF_RESETS_REQUIRED): cv.positive_not_null_int,
cv.Optional(CONF_ON_INCREMENT): validate_automation(
@@ -82,7 +84,7 @@ async def to_code(config):
var = cg.new_Pvariable(
config[CONF_ID],
reset_count,
config[CONF_MAX_DELAY].total_milliseconds,
config[CONF_MAX_DELAY].total_seconds,
)
await cg.register_component(var, config)
for conf in config.get(CONF_ON_INCREMENT, []):

View File

@@ -8,8 +8,7 @@
#if !defined(USE_RP2040) && !defined(USE_HOST)
namespace esphome {
namespace factory_reset {
namespace esphome::factory_reset {
static const char *const TAG = "factory_reset";
static const uint32_t POWER_CYCLES_KEY = 0xFA5C0DE;
@@ -33,10 +32,10 @@ void FactoryResetComponent::dump_config() {
this->flash_.load(&count);
ESP_LOGCONFIG(TAG, "Factory Reset by Reset:");
ESP_LOGCONFIG(TAG,
" Max interval between resets %" PRIu32 " seconds\n"
" Max interval between resets: %u seconds\n"
" Current count: %u\n"
" Factory reset after %u resets",
this->max_interval_ / 1000, count, this->required_count_);
this->max_interval_, count, this->required_count_);
}
void FactoryResetComponent::save_(uint8_t count) {
@@ -61,8 +60,8 @@ void FactoryResetComponent::setup() {
}
this->save_(count);
ESP_LOGD(TAG, "Power on reset detected, incremented count to %u", count);
this->set_timeout(this->max_interval_, [this]() {
ESP_LOGD(TAG, "No reset in the last %" PRIu32 " seconds, resetting count", this->max_interval_ / 1000);
this->set_timeout(static_cast<uint32_t>(this->max_interval_) * 1000, [this]() {
ESP_LOGD(TAG, "No reset in the last %u seconds, resetting count", this->max_interval_);
this->save_(0); // reset count
});
} else {
@@ -70,7 +69,6 @@ void FactoryResetComponent::setup() {
}
}
} // namespace factory_reset
} // namespace esphome
} // namespace esphome::factory_reset
#endif // !defined(USE_RP2040) && !defined(USE_HOST)

View File

@@ -9,12 +9,11 @@
#include <esp_system.h>
#endif
namespace esphome {
namespace factory_reset {
namespace esphome::factory_reset {
class FactoryResetComponent : public Component {
public:
FactoryResetComponent(uint8_t required_count, uint32_t max_interval)
: required_count_(required_count), max_interval_(max_interval) {}
FactoryResetComponent(uint8_t required_count, uint16_t max_interval)
: max_interval_(max_interval), required_count_(required_count) {}
void dump_config() override;
void setup() override;
@@ -26,9 +25,9 @@ class FactoryResetComponent : public Component {
~FactoryResetComponent() = default;
void save_(uint8_t count);
ESPPreferenceObject flash_{}; // saves the number of fast power cycles
uint8_t required_count_; // The number of boot attempts before fast boot is enabled
uint32_t max_interval_; // max interval between power cycles
CallbackManager<void(uint8_t, uint8_t)> increment_callback_{};
uint16_t max_interval_; // max interval between power cycles in seconds
uint8_t required_count_; // The number of boot attempts before fast boot is enabled
};
class FastBootTrigger : public Trigger<uint8_t, uint8_t> {
@@ -37,7 +36,6 @@ class FastBootTrigger : public Trigger<uint8_t, uint8_t> {
parent->add_increment_callback([this](uint8_t current, uint8_t target) { this->trigger(current, target); });
}
};
} // namespace factory_reset
} // namespace esphome
} // namespace esphome::factory_reset
#endif // !defined(USE_RP2040) && !defined(USE_HOST)

View File

@@ -65,7 +65,7 @@ void FanCall::validate_() {
auto traits = this->parent_.get_traits();
if (this->speed_.has_value()) {
this->speed_ = clamp(*this->speed_, 1, static_cast<int>(traits.supported_speed_count()));
this->speed_ = clamp(*this->speed_, 1, traits.supported_speed_count());
// https://developers.home-assistant.io/docs/core/entity/fan/#preset-modes
// "Manually setting a speed must disable any set preset mode"

View File

@@ -11,7 +11,7 @@ namespace fan {
class FanTraits {
public:
FanTraits() = default;
FanTraits(bool oscillation, bool speed, bool direction, uint8_t speed_count)
FanTraits(bool oscillation, bool speed, bool direction, int speed_count)
: oscillation_(oscillation), speed_(speed), direction_(direction), speed_count_(speed_count) {}
/// Return if this fan supports oscillation.
@@ -23,9 +23,9 @@ class FanTraits {
/// Set whether this fan supports speed levels.
void set_speed(bool speed) { this->speed_ = speed; }
/// Return how many speed levels the fan has
uint8_t supported_speed_count() const { return this->speed_count_; }
int supported_speed_count() const { return this->speed_count_; }
/// Set how many speed levels this fan has.
void set_supported_speed_count(uint8_t speed_count) { this->speed_count_ = speed_count; }
void set_supported_speed_count(int speed_count) { this->speed_count_ = speed_count; }
/// Return if this fan supports changing direction
bool supports_direction() const { return this->direction_; }
/// Set whether this fan supports changing direction
@@ -61,7 +61,7 @@ class FanTraits {
bool oscillation_{false};
bool speed_{false};
bool direction_{false};
uint8_t speed_count_{};
int speed_count_{};
std::vector<const char *> preset_modes_{};
};

View File

@@ -39,7 +39,7 @@ CONFIG_SCHEMA = (
cv.Optional(CONF_DECAY_MODE, default="SLOW"): cv.enum(
DECAY_MODE_OPTIONS, upper=True
),
cv.Optional(CONF_SPEED_COUNT, default=100): cv.int_range(min=1, max=255),
cv.Optional(CONF_SPEED_COUNT, default=100): cv.int_range(min=1),
cv.Optional(CONF_ENABLE_PIN): cv.use_id(output.FloatOutput),
cv.Optional(CONF_PRESET_MODES): validate_preset_modes,
}

View File

@@ -15,7 +15,7 @@ enum DecayMode {
class HBridgeFan : public Component, public fan::Fan {
public:
HBridgeFan(uint8_t speed_count, DecayMode decay_mode) : speed_count_(speed_count), decay_mode_(decay_mode) {}
HBridgeFan(int speed_count, DecayMode decay_mode) : speed_count_(speed_count), decay_mode_(decay_mode) {}
void set_pin_a(output::FloatOutput *pin_a) { pin_a_ = pin_a; }
void set_pin_b(output::FloatOutput *pin_b) { pin_b_ = pin_b; }
@@ -33,7 +33,7 @@ class HBridgeFan : public Component, public fan::Fan {
output::FloatOutput *pin_b_;
output::FloatOutput *enable_{nullptr};
output::BinaryOutput *oscillating_{nullptr};
uint8_t speed_count_{};
int speed_count_{};
DecayMode decay_mode_{DECAY_MODE_SLOW};
fan::FanTraits traits_;
std::vector<const char *> preset_modes_{};

View File

@@ -0,0 +1,6 @@
import esphome.config_validation as cv
AUTO_LOAD = ["sha256"]
CODEOWNERS = ["@dwmw2"]
CONFIG_SCHEMA = cv.Schema({})

View File

@@ -0,0 +1,102 @@
#include <cstdio>
#include <cstring>
#include "hmac_sha256.h"
#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_LIBRETINY) || defined(USE_HOST)
#include "esphome/core/helpers.h"
namespace esphome::hmac_sha256 {
constexpr size_t SHA256_DIGEST_SIZE = 32;
#if defined(USE_ESP32) || defined(USE_LIBRETINY)
HmacSHA256::~HmacSHA256() { mbedtls_md_free(&this->ctx_); }
void HmacSHA256::init(const uint8_t *key, size_t len) {
mbedtls_md_init(&this->ctx_);
const mbedtls_md_info_t *md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA256);
mbedtls_md_setup(&this->ctx_, md_info, 1); // 1 = HMAC mode
mbedtls_md_hmac_starts(&this->ctx_, key, len);
}
void HmacSHA256::add(const uint8_t *data, size_t len) { mbedtls_md_hmac_update(&this->ctx_, data, len); }
void HmacSHA256::calculate() { mbedtls_md_hmac_finish(&this->ctx_, this->digest_); }
void HmacSHA256::get_bytes(uint8_t *output) { memcpy(output, this->digest_, SHA256_DIGEST_SIZE); }
void HmacSHA256::get_hex(char *output) {
for (size_t i = 0; i < SHA256_DIGEST_SIZE; i++) {
sprintf(output + (i * 2), "%02x", this->digest_[i]);
}
}
bool HmacSHA256::equals_bytes(const uint8_t *expected) {
return memcmp(this->digest_, expected, SHA256_DIGEST_SIZE) == 0;
}
bool HmacSHA256::equals_hex(const char *expected) {
char hex_output[SHA256_DIGEST_SIZE * 2 + 1];
this->get_hex(hex_output);
hex_output[SHA256_DIGEST_SIZE * 2] = '\0';
return strncmp(hex_output, expected, SHA256_DIGEST_SIZE * 2) == 0;
}
#else
HmacSHA256::~HmacSHA256() = default;
// HMAC block size for SHA256 (RFC 2104)
constexpr size_t HMAC_BLOCK_SIZE = 64;
void HmacSHA256::init(const uint8_t *key, size_t len) {
uint8_t ipad[HMAC_BLOCK_SIZE], opad[HMAC_BLOCK_SIZE];
memset(ipad, 0, sizeof(ipad));
if (len > HMAC_BLOCK_SIZE) {
sha256::SHA256 keysha256;
keysha256.init();
keysha256.add(key, len);
keysha256.calculate();
keysha256.get_bytes(ipad);
} else {
memcpy(ipad, key, len);
}
memcpy(opad, ipad, sizeof(opad));
for (size_t i = 0; i < HMAC_BLOCK_SIZE; i++) {
ipad[i] ^= 0x36;
opad[i] ^= 0x5c;
}
this->ihash_.init();
this->ihash_.add(ipad, sizeof(ipad));
this->ohash_.init();
this->ohash_.add(opad, sizeof(opad));
}
void HmacSHA256::add(const uint8_t *data, size_t len) { this->ihash_.add(data, len); }
void HmacSHA256::calculate() {
uint8_t ibytes[32];
this->ihash_.calculate();
this->ihash_.get_bytes(ibytes);
this->ohash_.add(ibytes, sizeof(ibytes));
this->ohash_.calculate();
}
void HmacSHA256::get_bytes(uint8_t *output) { this->ohash_.get_bytes(output); }
void HmacSHA256::get_hex(char *output) { this->ohash_.get_hex(output); }
bool HmacSHA256::equals_bytes(const uint8_t *expected) { return this->ohash_.equals_bytes(expected); }
bool HmacSHA256::equals_hex(const char *expected) { return this->ohash_.equals_hex(expected); }
#endif // USE_ESP32 || USE_LIBRETINY
} // namespace esphome::hmac_sha256
#endif

View File

@@ -0,0 +1,59 @@
#pragma once
#include "esphome/core/defines.h"
#if defined(USE_ESP32) || defined(USE_ESP8266) || defined(USE_RP2040) || defined(USE_LIBRETINY) || defined(USE_HOST)
#include <string>
#if defined(USE_ESP32) || defined(USE_LIBRETINY)
#include "mbedtls/md.h"
#else
#include "esphome/components/sha256/sha256.h"
#endif
namespace esphome::hmac_sha256 {
class HmacSHA256 {
public:
HmacSHA256() = default;
~HmacSHA256();
/// Initialize a new HMAC-SHA256 digest computation.
void init(const uint8_t *key, size_t len);
void init(const char *key, size_t len) { this->init((const uint8_t *) key, len); }
void init(const std::string &key) { this->init(key.c_str(), key.length()); }
/// Add bytes of data for the digest.
void add(const uint8_t *data, size_t len);
void add(const char *data, size_t len) { this->add((const uint8_t *) data, len); }
/// Compute the digest, based on the provided data.
void calculate();
/// Retrieve the HMAC-SHA256 digest as bytes.
/// The output must be able to hold 32 bytes or more.
void get_bytes(uint8_t *output);
/// Retrieve the HMAC-SHA256 digest as hex characters.
/// The output must be able to hold 64 bytes or more.
void get_hex(char *output);
/// Compare the digest against a provided byte-encoded digest (32 bytes).
bool equals_bytes(const uint8_t *expected);
/// Compare the digest against a provided hex-encoded digest (64 bytes).
bool equals_hex(const char *expected);
protected:
#if defined(USE_ESP32) || defined(USE_LIBRETINY)
static constexpr size_t SHA256_DIGEST_SIZE = 32;
mbedtls_md_context_t ctx_{};
uint8_t digest_[SHA256_DIGEST_SIZE]{};
#else
sha256::SHA256 ihash_;
sha256::SHA256 ohash_;
#endif
};
} // namespace esphome::hmac_sha256
#endif

View File

@@ -69,9 +69,6 @@ def validate_url(value):
def validate_ssl_verification(config):
error_message = ""
if CORE.is_esp32 and not CORE.using_esp_idf and config[CONF_VERIFY_SSL]:
error_message = "ESPHome supports certificate verification only via ESP-IDF"
if CORE.is_rp2040 and config[CONF_VERIFY_SSL]:
error_message = "ESPHome does not support certificate verification on RP2040"
@@ -93,9 +90,9 @@ def validate_ssl_verification(config):
def _declare_request_class(value):
if CORE.is_host:
return cv.declare_id(HttpRequestHost)(value)
if CORE.using_esp_idf:
if CORE.is_esp32:
return cv.declare_id(HttpRequestIDF)(value)
if CORE.is_esp8266 or CORE.is_esp32 or CORE.is_rp2040:
if CORE.is_esp8266 or CORE.is_rp2040:
return cv.declare_id(HttpRequestArduino)(value)
return NotImplementedError
@@ -121,11 +118,11 @@ CONFIG_SCHEMA = cv.All(
cv.positive_not_null_time_period,
cv.positive_time_period_milliseconds,
),
cv.SplitDefault(CONF_BUFFER_SIZE_RX, esp32_idf=512): cv.All(
cv.uint16_t, cv.only_with_esp_idf
cv.SplitDefault(CONF_BUFFER_SIZE_RX, esp32=512): cv.All(
cv.uint16_t, cv.only_on_esp32
),
cv.SplitDefault(CONF_BUFFER_SIZE_TX, esp32_idf=512): cv.All(
cv.uint16_t, cv.only_with_esp_idf
cv.SplitDefault(CONF_BUFFER_SIZE_TX, esp32=512): cv.All(
cv.uint16_t, cv.only_on_esp32
),
cv.Optional(CONF_CA_CERTIFICATE_PATH): cv.All(
cv.file_,
@@ -158,25 +155,20 @@ async def to_code(config):
cg.add(var.set_watchdog_timeout(timeout_ms))
if CORE.is_esp32:
if CORE.using_esp_idf:
cg.add(var.set_buffer_size_rx(config[CONF_BUFFER_SIZE_RX]))
cg.add(var.set_buffer_size_tx(config[CONF_BUFFER_SIZE_TX]))
cg.add(var.set_buffer_size_rx(config[CONF_BUFFER_SIZE_RX]))
cg.add(var.set_buffer_size_tx(config[CONF_BUFFER_SIZE_TX]))
esp32.add_idf_sdkconfig_option(
"CONFIG_MBEDTLS_CERTIFICATE_BUNDLE",
config.get(CONF_VERIFY_SSL),
)
esp32.add_idf_sdkconfig_option(
"CONFIG_ESP_TLS_INSECURE",
not config.get(CONF_VERIFY_SSL),
)
esp32.add_idf_sdkconfig_option(
"CONFIG_ESP_TLS_SKIP_SERVER_CERT_VERIFY",
not config.get(CONF_VERIFY_SSL),
)
else:
cg.add_library("NetworkClientSecure", None)
cg.add_library("HTTPClient", None)
if config.get(CONF_VERIFY_SSL):
esp32.add_idf_sdkconfig_option("CONFIG_MBEDTLS_CERTIFICATE_BUNDLE", True)
esp32.add_idf_sdkconfig_option(
"CONFIG_ESP_TLS_INSECURE",
not config.get(CONF_VERIFY_SSL),
)
esp32.add_idf_sdkconfig_option(
"CONFIG_ESP_TLS_SKIP_SERVER_CERT_VERIFY",
not config.get(CONF_VERIFY_SSL),
)
if CORE.is_esp8266:
cg.add_library("ESP8266HTTPClient", None)
if CORE.is_rp2040 and CORE.using_arduino:
@@ -327,13 +319,15 @@ FILTER_SOURCE_FILES = filter_source_files_from_platform(
{
"http_request_host.cpp": {PlatformFramework.HOST_NATIVE},
"http_request_arduino.cpp": {
PlatformFramework.ESP32_ARDUINO,
PlatformFramework.ESP8266_ARDUINO,
PlatformFramework.RP2040_ARDUINO,
PlatformFramework.BK72XX_ARDUINO,
PlatformFramework.RTL87XX_ARDUINO,
PlatformFramework.LN882X_ARDUINO,
},
"http_request_idf.cpp": {PlatformFramework.ESP32_IDF},
"http_request_idf.cpp": {
PlatformFramework.ESP32_ARDUINO,
PlatformFramework.ESP32_IDF,
},
}
)

View File

@@ -4,8 +4,7 @@
#include <cinttypes>
namespace esphome {
namespace http_request {
namespace esphome::http_request {
static const char *const TAG = "http_request";
@@ -42,5 +41,4 @@ std::string HttpContainer::get_response_header(const std::string &header_name) {
}
}
} // namespace http_request
} // namespace esphome
} // namespace esphome::http_request

View File

@@ -15,8 +15,7 @@
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
namespace esphome {
namespace http_request {
namespace esphome::http_request {
struct Header {
std::string name;
@@ -255,6 +254,9 @@ template<typename... Ts> class HttpRequestSendAction : public Action<Ts...> {
size_t read_index = 0;
while (container->get_bytes_read() < max_length) {
int read = container->read(buf + read_index, std::min<size_t>(max_length - read_index, 512));
if (read <= 0) {
break;
}
App.feed_wdt();
yield();
read_index += read;
@@ -302,5 +304,4 @@ template<typename... Ts> class HttpRequestSendAction : public Action<Ts...> {
size_t max_response_buffer_size_{SIZE_MAX};
};
} // namespace http_request
} // namespace esphome
} // namespace esphome::http_request

View File

@@ -1,6 +1,6 @@
#include "http_request_arduino.h"
#ifdef USE_ARDUINO
#if defined(USE_ARDUINO) && !defined(USE_ESP32)
#include "esphome/components/network/util.h"
#include "esphome/components/watchdog/watchdog.h"
@@ -9,8 +9,7 @@
#include "esphome/core/defines.h"
#include "esphome/core/log.h"
namespace esphome {
namespace http_request {
namespace esphome::http_request {
static const char *const TAG = "http_request.arduino";
@@ -75,8 +74,6 @@ std::shared_ptr<HttpContainer> HttpRequestArduino::perform(const std::string &ur
container->client_.setInsecure();
}
bool status = container->client_.begin(url.c_str());
#elif defined(USE_ESP32)
bool status = container->client_.begin(url.c_str());
#endif
App.feed_wdt();
@@ -90,9 +87,6 @@ std::shared_ptr<HttpContainer> HttpRequestArduino::perform(const std::string &ur
container->client_.setReuse(true);
container->client_.setTimeout(this->timeout_);
#if defined(USE_ESP32)
container->client_.setConnectTimeout(this->timeout_);
#endif
if (this->useragent_ != nullptr) {
container->client_.setUserAgent(this->useragent_);
@@ -177,7 +171,6 @@ void HttpContainerArduino::end() {
this->client_.end();
}
} // namespace http_request
} // namespace esphome
} // namespace esphome::http_request
#endif // USE_ARDUINO
#endif // USE_ARDUINO && !USE_ESP32

View File

@@ -2,9 +2,9 @@
#include "http_request.h"
#ifdef USE_ARDUINO
#if defined(USE_ARDUINO) && !defined(USE_ESP32)
#if defined(USE_ESP32) || defined(USE_RP2040)
#if defined(USE_RP2040)
#include <HTTPClient.h>
#include <WiFiClient.h>
#endif
@@ -15,8 +15,7 @@
#endif
#endif
namespace esphome {
namespace http_request {
namespace esphome::http_request {
class HttpRequestArduino;
class HttpContainerArduino : public HttpContainer {
@@ -36,7 +35,6 @@ class HttpRequestArduino : public HttpRequestComponent {
const std::set<std::string> &collect_headers) override;
};
} // namespace http_request
} // namespace esphome
} // namespace esphome::http_request
#endif // USE_ARDUINO
#endif // USE_ARDUINO && !USE_ESP32

View File

@@ -12,8 +12,7 @@
#include "esphome/core/application.h"
#include "esphome/core/log.h"
namespace esphome {
namespace http_request {
namespace esphome::http_request {
static const char *const TAG = "http_request.host";
@@ -139,7 +138,6 @@ void HttpContainerHost::end() {
this->bytes_read_ = 0;
}
} // namespace http_request
} // namespace esphome
} // namespace esphome::http_request
#endif // USE_HOST

View File

@@ -2,8 +2,8 @@
#ifdef USE_HOST
#include "http_request.h"
namespace esphome {
namespace http_request {
namespace esphome::http_request {
class HttpRequestHost;
class HttpContainerHost : public HttpContainer {
@@ -27,7 +27,6 @@ class HttpRequestHost : public HttpRequestComponent {
const char *ca_path_{};
};
} // namespace http_request
} // namespace esphome
} // namespace esphome::http_request
#endif // USE_HOST

View File

@@ -1,6 +1,6 @@
#include "http_request_idf.h"
#ifdef USE_ESP_IDF
#ifdef USE_ESP32
#include "esphome/components/network/util.h"
#include "esphome/components/watchdog/watchdog.h"
@@ -14,8 +14,7 @@
#include "esp_task_wdt.h"
namespace esphome {
namespace http_request {
namespace esphome::http_request {
static const char *const TAG = "http_request.idf";
@@ -245,7 +244,6 @@ void HttpContainerIDF::feed_wdt() {
}
}
} // namespace http_request
} // namespace esphome
} // namespace esphome::http_request
#endif // USE_ESP_IDF
#endif // USE_ESP32

View File

@@ -2,15 +2,14 @@
#include "http_request.h"
#ifdef USE_ESP_IDF
#ifdef USE_ESP32
#include <esp_event.h>
#include <esp_http_client.h>
#include <esp_netif.h>
#include <esp_tls.h>
namespace esphome {
namespace http_request {
namespace esphome::http_request {
class HttpContainerIDF : public HttpContainer {
public:
@@ -48,7 +47,6 @@ class HttpRequestIDF : public HttpRequestComponent {
static esp_err_t http_event_handler(esp_http_client_event_t *evt);
};
} // namespace http_request
} // namespace esphome
} // namespace esphome::http_request
#endif // USE_ESP_IDF
#endif // USE_ESP32

View File

@@ -16,7 +16,11 @@ namespace http_request {
static const char *const TAG = "http_request.ota";
void OtaHttpRequestComponent::setup() {}
void OtaHttpRequestComponent::setup() {
#ifdef USE_OTA_STATE_CALLBACK
ota::register_ota_platform(this);
#endif
}
void OtaHttpRequestComponent::dump_config() { ESP_LOGCONFIG(TAG, "Over-The-Air updates via HTTP request"); };
@@ -44,24 +48,24 @@ void OtaHttpRequestComponent::flash() {
}
ESP_LOGI(TAG, "Starting update");
#ifdef USE_OTA_STATE_LISTENER
this->notify_state_(ota::OTA_STARTED, 0.0f, 0);
#ifdef USE_OTA_STATE_CALLBACK
this->state_callback_.call(ota::OTA_STARTED, 0.0f, 0);
#endif
auto ota_status = this->do_ota_();
switch (ota_status) {
case ota::OTA_RESPONSE_OK:
#ifdef USE_OTA_STATE_LISTENER
this->notify_state_(ota::OTA_COMPLETED, 100.0f, ota_status);
#ifdef USE_OTA_STATE_CALLBACK
this->state_callback_.call(ota::OTA_COMPLETED, 100.0f, ota_status);
#endif
delay(10);
App.safe_reboot();
break;
default:
#ifdef USE_OTA_STATE_LISTENER
this->notify_state_(ota::OTA_ERROR, 0.0f, ota_status);
#ifdef USE_OTA_STATE_CALLBACK
this->state_callback_.call(ota::OTA_ERROR, 0.0f, ota_status);
#endif
this->md5_computed_.clear(); // will be reset at next attempt
this->md5_expected_.clear(); // will be reset at next attempt
@@ -128,11 +132,18 @@ uint8_t OtaHttpRequestComponent::do_ota_() {
App.feed_wdt();
yield();
if (bufsize < 0) {
ESP_LOGE(TAG, "Stream closed");
this->cleanup_(std::move(backend), container);
return OTA_CONNECTION_ERROR;
} else if (bufsize > 0 && bufsize <= OtaHttpRequestComponent::HTTP_RECV_BUFFER) {
// Exit loop if no data available (stream closed or end of data)
if (bufsize <= 0) {
if (bufsize < 0) {
ESP_LOGE(TAG, "Stream closed with error");
this->cleanup_(std::move(backend), container);
return OTA_CONNECTION_ERROR;
}
// bufsize == 0: no more data available, exit loop
break;
}
if (bufsize <= OtaHttpRequestComponent::HTTP_RECV_BUFFER) {
// add read bytes to MD5
md5_receive.add(buf, bufsize);
@@ -154,8 +165,8 @@ uint8_t OtaHttpRequestComponent::do_ota_() {
last_progress = now;
float percentage = container->get_bytes_read() * 100.0f / container->content_length;
ESP_LOGD(TAG, "Progress: %0.1f%%", percentage);
#ifdef USE_OTA_STATE_LISTENER
this->notify_state_(ota::OTA_IN_PROGRESS, percentage, 0);
#ifdef USE_OTA_STATE_CALLBACK
this->state_callback_.call(ota::OTA_IN_PROGRESS, percentage, 0);
#endif
}
} // while
@@ -243,6 +254,9 @@ bool OtaHttpRequestComponent::http_get_md5_() {
int read_len = 0;
while (container->get_bytes_read() < MD5_SIZE) {
read_len = container->read((uint8_t *) this->md5_expected_.data(), MD5_SIZE);
if (read_len <= 0) {
break;
}
App.feed_wdt();
yield();
}

View File

@@ -1,5 +1,5 @@
import esphome.codegen as cg
from esphome.components import ota, update
from esphome.components import update
import esphome.config_validation as cv
from esphome.const import CONF_SOURCE
@@ -38,6 +38,6 @@ async def to_code(config):
cg.add(var.set_source_url(config[CONF_SOURCE]))
ota.request_ota_state_listeners()
cg.add_define("USE_OTA_STATE_CALLBACK")
await cg.register_component(var, config)

View File

@@ -20,19 +20,19 @@ static const char *const TAG = "http_request.update";
static const size_t MAX_READ_SIZE = 256;
void HttpRequestUpdate::setup() { this->ota_parent_->add_state_listener(this); }
void HttpRequestUpdate::on_ota_state(ota::OTAState state, float progress, uint8_t error) {
if (state == ota::OTAState::OTA_IN_PROGRESS) {
this->state_ = update::UPDATE_STATE_INSTALLING;
this->update_info_.has_progress = true;
this->update_info_.progress = progress;
this->publish_state();
} else if (state == ota::OTAState::OTA_ABORT || state == ota::OTAState::OTA_ERROR) {
this->state_ = update::UPDATE_STATE_AVAILABLE;
this->status_set_error(LOG_STR("Failed to install firmware"));
this->publish_state();
}
void HttpRequestUpdate::setup() {
this->ota_parent_->add_on_state_callback([this](ota::OTAState state, float progress, uint8_t err) {
if (state == ota::OTAState::OTA_IN_PROGRESS) {
this->state_ = update::UPDATE_STATE_INSTALLING;
this->update_info_.has_progress = true;
this->update_info_.progress = progress;
this->publish_state();
} else if (state == ota::OTAState::OTA_ABORT || state == ota::OTAState::OTA_ERROR) {
this->state_ = update::UPDATE_STATE_AVAILABLE;
this->status_set_error(LOG_STR("Failed to install firmware"));
this->publish_state();
}
});
}
void HttpRequestUpdate::update() {
@@ -76,6 +76,11 @@ void HttpRequestUpdate::update_task(void *params) {
yield();
if (read_bytes <= 0) {
// Network error or connection closed - break to avoid infinite loop
break;
}
read_index += read_bytes;
}

View File

@@ -14,7 +14,7 @@
namespace esphome {
namespace http_request {
class HttpRequestUpdate final : public update::UpdateEntity, public PollingComponent, public ota::OTAStateListener {
class HttpRequestUpdate : public update::UpdateEntity, public PollingComponent {
public:
void setup() override;
void update() override;
@@ -29,8 +29,6 @@ class HttpRequestUpdate final : public update::UpdateEntity, public PollingCompo
float get_setup_priority() const override { return setup_priority::AFTER_WIFI; }
void on_ota_state(ota::OTAState state, float progress, uint8_t error) override;
protected:
HttpRequestComponent *request_parent_;
OtaHttpRequestComponent *ota_parent_;

View File

@@ -1,6 +1,6 @@
from typing import Any
from esphome import pins
from esphome import automation, pins
import esphome.codegen as cg
from esphome.components import display
from esphome.components.esp32 import add_idf_component
@@ -17,6 +17,8 @@ from esphome.const import (
CONF_OE_PIN,
CONF_UPDATE_INTERVAL,
)
from esphome.core import ID
from esphome.cpp_generator import MockObj, TemplateArgsType
import esphome.final_validate as fv
from esphome.types import ConfigType
@@ -135,6 +137,7 @@ CLOCK_SPEEDS = {
HUB75Display = hub75_ns.class_("HUB75Display", cg.PollingComponent, display.Display)
Hub75Config = cg.global_ns.struct("Hub75Config")
Hub75Pins = cg.global_ns.struct("Hub75Pins")
SetBrightnessAction = hub75_ns.class_("SetBrightnessAction", automation.Action)
def _merge_board_pins(config: ConfigType) -> ConfigType:
@@ -576,3 +579,27 @@ async def to_code(config: ConfigType) -> None:
config[CONF_LAMBDA], [(display.DisplayRef, "it")], return_type=cg.void
)
cg.add(var.set_writer(lambda_))
@automation.register_action(
"hub75.set_brightness",
SetBrightnessAction,
cv.maybe_simple_value(
{
cv.GenerateID(): cv.use_id(HUB75Display),
cv.Required(CONF_BRIGHTNESS): cv.templatable(cv.int_range(min=0, max=255)),
},
key=CONF_BRIGHTNESS,
),
)
async def hub75_set_brightness_to_code(
config: ConfigType,
action_id: ID,
template_arg: cg.TemplateArguments,
args: TemplateArgsType,
) -> MockObj:
var = cg.new_Pvariable(action_id, template_arg)
await cg.register_parented(var, config[CONF_ID])
template_ = await cg.templatable(config[CONF_BRIGHTNESS], args, cg.uint8)
cg.add(var.set_brightness(template_))
return var

View File

@@ -179,7 +179,7 @@ void HOT HUB75Display::draw_pixels_at(int x_start, int y_start, int w, int h, co
}
}
void HUB75Display::set_brightness(int brightness) {
void HUB75Display::set_brightness(uint8_t brightness) {
this->brightness_ = brightness;
this->enabled_ = (brightness > 0);
if (this->driver_ != nullptr) {

View File

@@ -5,6 +5,7 @@
#include <utility>
#include "esphome/components/display/display_buffer.h"
#include "esphome/core/automation.h"
#include "esphome/core/component.h"
#include "esphome/core/hal.h"
#include "esphome/core/log.h"
@@ -34,7 +35,7 @@ class HUB75Display : public display::Display {
display::ColorBitness bitness, bool big_endian, int x_offset, int y_offset, int x_pad) override;
// Brightness control (runtime mutable)
void set_brightness(int brightness);
void set_brightness(uint8_t brightness);
protected:
// Display internal methods
@@ -46,10 +47,17 @@ class HUB75Display : public display::Display {
Hub75Config config_; // Immutable configuration
// Runtime state (mutable)
int brightness_{128};
uint8_t brightness_{128};
bool enabled_{false};
};
template<typename... Ts> class SetBrightnessAction : public Action<Ts...>, public Parented<HUB75Display> {
public:
TEMPLATABLE_VALUE(uint8_t, brightness)
void play(const Ts &...x) override { this->parent_->set_brightness(this->brightness_.value(x...)); }
};
} // namespace esphome::hub75
#endif

View File

@@ -121,7 +121,7 @@ CONFIG_SCHEMA = cv.All(
nrf52="100kHz",
): cv.All(
cv.frequency,
cv.Range(min=0, min_included=False),
cv.float_range(min=0, min_included=False),
),
cv.Optional(CONF_TIMEOUT): cv.All(
cv.only_with_framework(["arduino", "esp-idf"]),

View File

@@ -116,7 +116,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

@@ -1,11 +1,6 @@
from esphome import pins
import esphome.codegen as cg
from esphome.components.esp32 import (
add_idf_sdkconfig_option,
enable_ringbuf_in_iram,
get_esp32_variant,
)
from esphome.components.esp32.const import (
VARIANT_ESP32,
VARIANT_ESP32C3,
VARIANT_ESP32C5,
@@ -15,6 +10,8 @@ from esphome.components.esp32.const import (
VARIANT_ESP32P4,
VARIANT_ESP32S2,
VARIANT_ESP32S3,
add_idf_sdkconfig_option,
get_esp32_variant,
)
import esphome.config_validation as cv
from esphome.const import CONF_BITS_PER_SAMPLE, CONF_CHANNEL, CONF_ID, CONF_SAMPLE_RATE
@@ -280,9 +277,6 @@ async def to_code(config):
# Helps avoid callbacks being skipped due to processor load
add_idf_sdkconfig_option("CONFIG_I2S_ISR_IRAM_SAFE", True)
# Keep ring buffer functions in IRAM for audio performance
enable_ringbuf_in_iram()
cg.add(var.set_lrclk_pin(config[CONF_I2S_LRCLK_PIN]))
if CONF_I2S_BCLK_PIN in config:
cg.add(var.set_bclk_pin(config[CONF_I2S_BCLK_PIN]))

View File

@@ -45,10 +45,12 @@ CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend(
{
cv.Required(CONF_ID): cv.declare_id(LEDCOutput),
cv.Required(CONF_PIN): pins.internal_gpio_output_pin_schema,
cv.Optional(CONF_FREQUENCY, default="1kHz"): cv.frequency,
cv.Optional(CONF_FREQUENCY, default="1kHz"): cv.All(
cv.frequency, cv.float_range(min=0, min_included=False)
),
cv.Optional(CONF_CHANNEL): cv.int_range(min=0, max=15),
cv.Optional(CONF_PHASE_ANGLE): cv.All(
cv.only_with_esp_idf, cv.angle, cv.float_range(min=0.0, max=360.0)
cv.angle, cv.float_range(min=0.0, max=360.0)
),
}
).extend(cv.COMPONENT_SCHEMA)

View File

@@ -4,24 +4,27 @@
#include "esphome/core/log.h"
#include "esphome/core/preferences.h"
#include <flashdb.h>
#include <cinttypes>
#include <cstring>
#include <memory>
#include <string>
namespace esphome {
namespace libretiny {
static const char *const TAG = "lt.preferences";
// Buffer size for converting uint32_t to string: max "4294967295" (10 chars) + null terminator + 1 padding
static constexpr size_t KEY_BUFFER_SIZE = 12;
struct NVSData {
std::string key;
uint32_t key;
std::unique_ptr<uint8_t[]> data;
size_t len;
void set_data(const uint8_t *src, size_t size) {
data = std::make_unique<uint8_t[]>(size);
memcpy(data.get(), src, size);
len = size;
this->data = std::make_unique<uint8_t[]>(size);
memcpy(this->data.get(), src, size);
this->len = size;
}
};
@@ -29,30 +32,30 @@ static std::vector<NVSData> s_pending_save; // NOLINT(cppcoreguidelines-avoid-n
class LibreTinyPreferenceBackend : public ESPPreferenceBackend {
public:
std::string key;
uint32_t key;
fdb_kvdb_t db;
fdb_blob_t blob;
bool save(const uint8_t *data, size_t len) override {
// try find in pending saves and update that
for (auto &obj : s_pending_save) {
if (obj.key == key) {
if (obj.key == this->key) {
obj.set_data(data, len);
return true;
}
}
NVSData save{};
save.key = key;
save.key = this->key;
save.set_data(data, len);
s_pending_save.emplace_back(std::move(save));
ESP_LOGVV(TAG, "s_pending_save: key: %s, len: %zu", key.c_str(), len);
ESP_LOGVV(TAG, "s_pending_save: key: %" PRIu32 ", len: %zu", this->key, len);
return true;
}
bool load(uint8_t *data, size_t len) override {
// try find in pending saves and load from that
for (auto &obj : s_pending_save) {
if (obj.key == key) {
if (obj.key == this->key) {
if (obj.len != len) {
// size mismatch
return false;
@@ -62,13 +65,15 @@ class LibreTinyPreferenceBackend : public ESPPreferenceBackend {
}
}
fdb_blob_make(blob, data, len);
size_t actual_len = fdb_kv_get_blob(db, key.c_str(), blob);
char key_str[KEY_BUFFER_SIZE];
snprintf(key_str, sizeof(key_str), "%" PRIu32, this->key);
fdb_blob_make(this->blob, data, len);
size_t actual_len = fdb_kv_get_blob(this->db, key_str, this->blob);
if (actual_len != len) {
ESP_LOGVV(TAG, "NVS length does not match (%zu!=%zu)", actual_len, len);
return false;
} else {
ESP_LOGVV(TAG, "fdb_kv_get_blob: key: %s, len: %zu", key.c_str(), len);
ESP_LOGVV(TAG, "fdb_kv_get_blob: key: %s, len: %zu", key_str, len);
}
return true;
}
@@ -90,16 +95,14 @@ class LibreTinyPreferences : public ESPPreferences {
}
ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash) override {
return make_preference(length, type);
return this->make_preference(length, type);
}
ESPPreferenceObject make_preference(size_t length, uint32_t type) override {
auto *pref = new LibreTinyPreferenceBackend(); // NOLINT(cppcoreguidelines-owning-memory)
pref->db = &db;
pref->blob = &blob;
uint32_t keyval = type;
pref->key = str_sprintf("%u", keyval);
pref->db = &this->db;
pref->blob = &this->blob;
pref->key = type;
return ESPPreferenceObject(pref);
}
@@ -112,18 +115,20 @@ class LibreTinyPreferences : public ESPPreferences {
// goal try write all pending saves even if one fails
int cached = 0, written = 0, failed = 0;
fdb_err_t last_err = FDB_NO_ERR;
std::string last_key{};
uint32_t last_key = 0;
// go through vector from back to front (makes erase easier/more efficient)
for (ssize_t i = s_pending_save.size() - 1; i >= 0; i--) {
const auto &save = s_pending_save[i];
ESP_LOGVV(TAG, "Checking if FDB data %s has changed", save.key.c_str());
if (is_changed(&db, save)) {
ESP_LOGV(TAG, "sync: key: %s, len: %zu", save.key.c_str(), save.len);
fdb_blob_make(&blob, save.data.get(), save.len);
fdb_err_t err = fdb_kv_set_blob(&db, save.key.c_str(), &blob);
ESP_LOGVV(TAG, "Checking if FDB data %" PRIu32 " has changed", save.key);
if (this->is_changed(&this->db, save)) {
char key_str[KEY_BUFFER_SIZE];
snprintf(key_str, sizeof(key_str), "%" PRIu32, save.key);
ESP_LOGV(TAG, "sync: key: %s, len: %zu", key_str, save.len);
fdb_blob_make(&this->blob, save.data.get(), save.len);
fdb_err_t err = fdb_kv_set_blob(&this->db, key_str, &this->blob);
if (err != FDB_NO_ERR) {
ESP_LOGV(TAG, "fdb_kv_set_blob('%s', len=%zu) failed: %d", save.key.c_str(), save.len, err);
ESP_LOGV(TAG, "fdb_kv_set_blob('%s', len=%zu) failed: %d", key_str, save.len, err);
failed++;
last_err = err;
last_key = save.key;
@@ -131,7 +136,7 @@ class LibreTinyPreferences : public ESPPreferences {
}
written++;
} else {
ESP_LOGD(TAG, "FDB data not changed; skipping %s len=%zu", save.key.c_str(), save.len);
ESP_LOGD(TAG, "FDB data not changed; skipping %" PRIu32 " len=%zu", save.key, save.len);
cached++;
}
s_pending_save.erase(s_pending_save.begin() + i);
@@ -139,17 +144,20 @@ class LibreTinyPreferences : public ESPPreferences {
ESP_LOGD(TAG, "Writing %d items: %d cached, %d written, %d failed", cached + written + failed, cached, written,
failed);
if (failed > 0) {
ESP_LOGE(TAG, "Writing %d items failed. Last error=%d for key=%s", failed, last_err, last_key.c_str());
ESP_LOGE(TAG, "Writing %d items failed. Last error=%d for key=%" PRIu32, failed, last_err, last_key);
}
return failed == 0;
}
bool is_changed(const fdb_kvdb_t db, const NVSData &to_save) {
char key_str[KEY_BUFFER_SIZE];
snprintf(key_str, sizeof(key_str), "%" PRIu32, to_save.key);
struct fdb_kv kv;
fdb_kv_t kvp = fdb_kv_get_obj(db, to_save.key.c_str(), &kv);
fdb_kv_t kvp = fdb_kv_get_obj(db, key_str, &kv);
if (kvp == nullptr) {
ESP_LOGV(TAG, "fdb_kv_get_obj('%s'): nullptr - the key might not be set yet", to_save.key.c_str());
ESP_LOGV(TAG, "fdb_kv_get_obj('%s'): nullptr - the key might not be set yet", key_str);
return true;
}
@@ -160,10 +168,10 @@ class LibreTinyPreferences : public ESPPreferences {
// Allocate buffer on heap to avoid stack allocation for large data
auto stored_data = std::make_unique<uint8_t[]>(kv.value_len);
fdb_blob_make(&blob, stored_data.get(), kv.value_len);
size_t actual_len = fdb_kv_get_blob(db, to_save.key.c_str(), &blob);
fdb_blob_make(&this->blob, stored_data.get(), kv.value_len);
size_t actual_len = fdb_kv_get_blob(db, key_str, &this->blob);
if (actual_len != kv.value_len) {
ESP_LOGV(TAG, "fdb_kv_get_blob('%s') len mismatch: %u != %u", to_save.key.c_str(), actual_len, kv.value_len);
ESP_LOGV(TAG, "fdb_kv_get_blob('%s') len mismatch: %u != %u", key_str, actual_len, kv.value_len);
return true;
}

View File

@@ -14,7 +14,9 @@ CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend(
{
cv.Required(CONF_ID): cv.declare_id(LibreTinyPWM),
cv.Required(CONF_PIN): pins.internal_gpio_output_pin_schema,
cv.Optional(CONF_FREQUENCY, default="1kHz"): cv.frequency,
cv.Optional(CONF_FREQUENCY, default="1kHz"): cv.All(
cv.frequency, cv.float_range(min=0, min_included=False)
),
}
).extend(cv.COMPONENT_SCHEMA)

View File

@@ -241,9 +241,12 @@ CONFIG_SCHEMA = cv.All(
CONF_HARDWARE_UART,
esp8266=UART0,
esp32=UART0,
esp32_c2=UART0,
esp32_c3=USB_SERIAL_JTAG,
esp32_c5=USB_SERIAL_JTAG,
esp32_c6=USB_SERIAL_JTAG,
esp32_c61=USB_SERIAL_JTAG,
esp32_h2=USB_SERIAL_JTAG,
esp32_p4=USB_SERIAL_JTAG,
esp32_s2=USB_CDC,
esp32_s3=USB_SERIAL_JTAG,

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

@@ -1,7 +1,17 @@
import esphome.codegen as cg
from esphome.core import CORE
from esphome.helpers import IS_MACOS
CODEOWNERS = ["@esphome/core"]
async def to_code(config):
cg.add_define("USE_MD5")
# Add OpenSSL library for host platform
if CORE.is_host:
if IS_MACOS:
# macOS needs special handling for Homebrew OpenSSL
cg.add_build_flag("-I/opt/homebrew/opt/openssl/include")
cg.add_build_flag("-L/opt/homebrew/opt/openssl/lib")
cg.add_build_flag("-lcrypto")

View File

@@ -39,6 +39,44 @@ void MD5Digest::add(const uint8_t *data, size_t len) { br_md5_update(&this->ctx_
void MD5Digest::calculate() { br_md5_out(&this->ctx_, this->digest_); }
#endif // USE_RP2040
#ifdef USE_HOST
MD5Digest::~MD5Digest() {
if (this->ctx_) {
EVP_MD_CTX_free(this->ctx_);
}
}
void MD5Digest::init() {
if (this->ctx_) {
EVP_MD_CTX_free(this->ctx_);
}
this->ctx_ = EVP_MD_CTX_new();
EVP_DigestInit_ex(this->ctx_, EVP_md5(), nullptr);
this->calculated_ = false;
memset(this->digest_, 0, 16);
}
void MD5Digest::add(const uint8_t *data, size_t len) {
if (!this->ctx_) {
this->init();
}
EVP_DigestUpdate(this->ctx_, data, len);
}
void MD5Digest::calculate() {
if (!this->ctx_) {
this->init();
}
if (!this->calculated_) {
unsigned int len = 16;
EVP_DigestFinal_ex(this->ctx_, this->digest_, &len);
this->calculated_ = true;
}
}
#else
MD5Digest::~MD5Digest() = default;
#endif // USE_HOST
} // namespace md5
} // namespace esphome
#endif

View File

@@ -5,6 +5,10 @@
#include "esphome/core/hash_base.h"
#ifdef USE_HOST
#include <openssl/evp.h>
#endif
#ifdef USE_ESP32
#include "esp_rom_md5.h"
#define MD5_CTX_TYPE md5_context_t
@@ -31,7 +35,7 @@ namespace md5 {
class MD5Digest : public HashBase {
public:
MD5Digest() = default;
~MD5Digest() override = default;
~MD5Digest() override;
/// Initialize a new MD5 digest computation.
void init() override;
@@ -47,7 +51,12 @@ class MD5Digest : public HashBase {
size_t get_size() const override { return 16; }
protected:
#ifdef USE_HOST
EVP_MD_CTX *ctx_{nullptr};
bool calculated_{false};
#else
MD5_CTX_TYPE ctx_{};
#endif
};
} // namespace md5

View File

@@ -7,7 +7,7 @@ from urllib.parse import urljoin
from esphome import automation, external_files, git
from esphome.automation import register_action, register_condition
import esphome.codegen as cg
from esphome.components import esp32, microphone, ota, socket
from esphome.components import esp32, microphone, socket
import esphome.config_validation as cv
from esphome.const import (
CONF_FILE,
@@ -448,14 +448,11 @@ async def to_code(config):
# The inference task queues detection events that need immediate processing
socket.require_wake_loop_threadsafe()
# Keep ring buffer functions in IRAM for audio performance
esp32.enable_ringbuf_in_iram()
mic_source = await microphone.microphone_source_to_code(config[CONF_MICROPHONE])
cg.add(var.set_microphone_source(mic_source))
cg.add_define("USE_MICRO_WAKE_WORD")
ota.request_ota_state_listeners()
cg.add_define("USE_OTA_STATE_CALLBACK")
esp32.add_idf_component(name="espressif/esp-tflite-micro", ref="1.3.3~1")

View File

@@ -119,21 +119,18 @@ void MicroWakeWord::setup() {
}
});
#ifdef USE_OTA_STATE_LISTENER
ota::get_global_ota_callback()->add_global_state_listener(this);
#ifdef USE_OTA
ota::get_global_ota_callback()->add_on_state_callback(
[this](ota::OTAState state, float progress, uint8_t error, ota::OTAComponent *comp) {
if (state == ota::OTA_STARTED) {
this->suspend_task_();
} else if (state == ota::OTA_ERROR) {
this->resume_task_();
}
});
#endif
}
#ifdef USE_OTA_STATE_LISTENER
void MicroWakeWord::on_ota_global_state(ota::OTAState state, float progress, uint8_t error, ota::OTAComponent *comp) {
if (state == ota::OTA_STARTED) {
this->suspend_task_();
} else if (state == ota::OTA_ERROR) {
this->resume_task_();
}
}
#endif
void MicroWakeWord::inference_task(void *params) {
MicroWakeWord *this_mww = (MicroWakeWord *) params;

View File

@@ -9,13 +9,8 @@
#include "esphome/core/automation.h"
#include "esphome/core/component.h"
#include "esphome/core/defines.h"
#include "esphome/core/ring_buffer.h"
#ifdef USE_OTA_STATE_LISTENER
#include "esphome/components/ota/ota_backend.h"
#endif
#include <freertos/event_groups.h>
#include <frontend.h>
@@ -31,22 +26,13 @@ enum State {
STOPPED,
};
class MicroWakeWord : public Component
#ifdef USE_OTA_STATE_LISTENER
,
public ota::OTAGlobalStateListener
#endif
{
class MicroWakeWord : public Component {
public:
void setup() override;
void loop() override;
float get_setup_priority() const override;
void dump_config() override;
#ifdef USE_OTA_STATE_LISTENER
void on_ota_global_state(ota::OTAState state, float progress, uint8_t error, ota::OTAComponent *comp) override;
#endif
void start();
void stop();

View File

@@ -233,11 +233,11 @@ CONFIG_SCHEMA = cv.All(
cv.Optional(CONF_PASSWORD, default=""): cv.string,
cv.Optional(CONF_CLEAN_SESSION, default=False): cv.boolean,
cv.Optional(CONF_CLIENT_ID): cv.string,
cv.SplitDefault(CONF_IDF_SEND_ASYNC, esp32_idf=False): cv.All(
cv.boolean, cv.only_with_esp_idf
cv.SplitDefault(CONF_IDF_SEND_ASYNC, esp32=False): cv.All(
cv.boolean, cv.only_on_esp32
),
cv.Optional(CONF_CERTIFICATE_AUTHORITY): cv.All(
cv.string, cv.only_with_esp_idf
cv.string, cv.only_on_esp32
),
cv.Inclusive(CONF_CLIENT_CERTIFICATE, "cert-key-pair"): cv.All(
cv.string, cv.only_on_esp32
@@ -245,8 +245,8 @@ CONFIG_SCHEMA = cv.All(
cv.Inclusive(CONF_CLIENT_CERTIFICATE_KEY, "cert-key-pair"): cv.All(
cv.string, cv.only_on_esp32
),
cv.SplitDefault(CONF_SKIP_CERT_CN_CHECK, esp32_idf=False): cv.All(
cv.boolean, cv.only_with_esp_idf
cv.SplitDefault(CONF_SKIP_CERT_CN_CHECK, esp32=False): cv.All(
cv.boolean, cv.only_on_esp32
),
cv.Optional(CONF_DISCOVERY, default=True): cv.Any(
cv.boolean, cv.one_of("CLEAN", upper=True)

View File

@@ -21,8 +21,7 @@ void MQTTSelectComponent::setup() {
call.set_option(state);
call.perform();
});
this->select_->add_on_state_callback(
[this](const std::string &state, size_t index) { this->publish_state(this->select_->option_at(index)); });
this->select_->add_on_state_callback([this](size_t index) { this->publish_state(this->select_->option_at(index)); });
}
void MQTTSelectComponent::dump_config() {

View File

@@ -13,14 +13,16 @@ CONF_SEND_TO_NEXTION = "send_to_nextion"
FILTER_SOURCE_FILES = filter_source_files_from_platform(
{
"nextion_upload_arduino.cpp": {
"nextion_upload_esp32.cpp": {
PlatformFramework.ESP32_ARDUINO,
PlatformFramework.ESP32_IDF,
},
"nextion_upload_arduino.cpp": {
PlatformFramework.ESP8266_ARDUINO,
PlatformFramework.RP2040_ARDUINO,
PlatformFramework.BK72XX_ARDUINO,
PlatformFramework.RTL87XX_ARDUINO,
PlatformFramework.LN882X_ARDUINO,
},
"nextion_upload_idf.cpp": {PlatformFramework.ESP32_IDF},
}
)

View File

@@ -154,14 +154,11 @@ async def to_code(config):
cg.add_define("USE_NEXTION_TFT_UPLOAD")
cg.add(var.set_tft_url(config[CONF_TFT_URL]))
if CORE.is_esp32:
if CORE.using_arduino:
cg.add_library("NetworkClientSecure", None)
cg.add_library("HTTPClient", None)
esp32.add_idf_sdkconfig_option("CONFIG_ESP_TLS_INSECURE", True)
esp32.add_idf_sdkconfig_option(
"CONFIG_ESP_TLS_SKIP_SERVER_CERT_VERIFY", True
)
elif CORE.is_esp8266 and CORE.using_arduino:
elif CORE.is_esp8266:
cg.add_library("ESP8266HTTPClient", None)
if CONF_TOUCH_SLEEP_TIMEOUT in config:

View File

@@ -13,17 +13,12 @@
#include "esphome/components/display/display_color_utils.h"
#ifdef USE_NEXTION_TFT_UPLOAD
#ifdef USE_ARDUINO
#ifdef USE_ESP32
#include <HTTPClient.h>
#endif // USE_ESP32
#ifdef USE_ESP8266
#include <esp_http_client.h>
#elif defined(USE_ESP8266)
#include <ESP8266HTTPClient.h>
#include <WiFiClientSecure.h>
#endif // USE_ESP8266
#elif defined(USE_ESP_IDF)
#include <esp_http_client.h>
#endif // ARDUINO vs USE_ESP_IDF
#endif // USE_ESP32 vs USE_ESP8266
#endif // USE_NEXTION_TFT_UPLOAD
namespace esphome {
@@ -1078,7 +1073,7 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe
#ifdef USE_NEXTION_TFT_UPLOAD
/**
* Set the tft file URL. https seems problematic with Arduino..
* Set the tft file URL.
*/
void set_tft_url(const std::string &tft_url) { this->tft_url_ = tft_url; }
@@ -1422,16 +1417,7 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe
uint32_t original_baud_rate_ = 0;
bool upload_first_chunk_sent_ = false;
#ifdef USE_ARDUINO
/**
* will request chunk_size chunks from the web server
* and send each to the nextion
* @param HTTPClient http_client HTTP client handler.
* @param int range_start Position of next byte to transfer.
* @return position of last byte transferred, -1 for failure.
*/
int upload_by_chunks_(HTTPClient &http_client, uint32_t &range_start);
#elif defined(USE_ESP_IDF)
#ifdef USE_ESP32
/**
* will request 4096 bytes chunks from the web server
* and send each to Nextion
@@ -1440,7 +1426,16 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe
* @return position of last byte transferred, -1 for failure.
*/
int upload_by_chunks_(esp_http_client_handle_t http_client, uint32_t &range_start);
#endif // USE_ARDUINO vs USE_ESP_IDF
#elif defined(USE_ARDUINO)
/**
* will request chunk_size chunks from the web server
* and send each to the nextion
* @param HTTPClient http_client HTTP client handler.
* @param int range_start Position of next byte to transfer.
* @return position of last byte transferred, -1 for failure.
*/
int upload_by_chunks_(HTTPClient &http_client, uint32_t &range_start);
#endif // USE_ESP32 vs USE_ARDUINO
/**
* Ends the upload process, restart Nextion and, if successful,
@@ -1450,12 +1445,6 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe
*/
bool upload_end_(bool successful);
/**
* Returns the ESP Free Heap memory. This is framework independent.
* @return Free Heap in bytes.
*/
uint32_t get_free_heap_();
#endif // USE_NEXTION_TFT_UPLOAD
bool check_connect_();

View File

@@ -1,7 +1,7 @@
#include "nextion.h"
#ifdef USE_NEXTION_TFT_UPLOAD
#ifdef USE_ARDUINO
#ifndef USE_ESP32
#include <cinttypes>
#include "esphome/components/network/util.h"
@@ -10,10 +10,6 @@
#include "esphome/core/log.h"
#include "esphome/core/util.h"
#ifdef USE_ESP32
#include <esp_heap_caps.h>
#endif
namespace esphome {
namespace nextion {
static const char *const TAG = "nextion.upload.arduino";
@@ -21,23 +17,17 @@ static const char *const TAG = "nextion.upload.arduino";
// Followed guide
// https://unofficialnextion.com/t/nextion-upload-protocol-v1-2-the-fast-one/1044/2
inline uint32_t Nextion::get_free_heap_() {
#if defined(USE_ESP32)
return heap_caps_get_free_size(MALLOC_CAP_INTERNAL);
#elif defined(USE_ESP8266)
return EspClass::getFreeHeap();
#endif // USE_ESP32 vs USE_ESP8266
}
int Nextion::upload_by_chunks_(HTTPClient &http_client, uint32_t &range_start) {
uint32_t range_size = this->tft_size_ - range_start;
ESP_LOGV(TAG, "Heap: %" PRIu32, this->get_free_heap_());
ESP_LOGV(TAG, "Heap: %" PRIu32, EspClass::getFreeHeap());
uint32_t range_end = ((upload_first_chunk_sent_ or this->tft_size_ < 4096) ? this->tft_size_ : 4096) - 1;
ESP_LOGD(TAG, "Range start: %" PRIu32, range_start);
if (range_size <= 0 or range_end <= range_start) {
ESP_LOGD(TAG, "Range end: %" PRIu32, range_end);
ESP_LOGD(TAG, "Range size: %" PRIu32, range_size);
ESP_LOGE(TAG, "Invalid range");
ESP_LOGD(TAG,
"Range end: %" PRIu32 "\n"
"Range size: %" PRIu32,
range_end, range_size);
return -1;
}
@@ -95,14 +85,8 @@ int Nextion::upload_by_chunks_(HTTPClient &http_client, uint32_t &range_start) {
this->recv_ret_string_(recv_string, upload_first_chunk_sent_ ? 500 : 5000, true);
this->content_length_ -= read_len;
const float upload_percentage = 100.0f * (this->tft_size_ - this->content_length_) / this->tft_size_;
#if defined(USE_ESP32) && defined(USE_PSRAM)
ESP_LOGD(TAG, "Upload: %0.2f%% (%" PRIu32 " left, heap: %" PRIu32 "+%" PRIu32 ")", upload_percentage,
this->content_length_, static_cast<uint32_t>(heap_caps_get_free_size(MALLOC_CAP_INTERNAL)),
static_cast<uint32_t>(heap_caps_get_free_size(MALLOC_CAP_SPIRAM)));
#else
ESP_LOGD(TAG, "Upload: %0.2f%% (%" PRIu32 " left, heap: %" PRIu32 ")", upload_percentage, this->content_length_,
this->get_free_heap_());
#endif
EspClass::getFreeHeap());
upload_first_chunk_sent_ = true;
if (recv_string[0] == 0x08 && recv_string.size() == 5) { // handle partial upload request
ESP_LOGD(TAG, "Recv: [%s]",
@@ -148,9 +132,11 @@ int Nextion::upload_by_chunks_(HTTPClient &http_client, uint32_t &range_start) {
}
bool Nextion::upload_tft(uint32_t baud_rate, bool exit_reparse) {
ESP_LOGD(TAG, "TFT upload requested");
ESP_LOGD(TAG, "Exit reparse: %s", YESNO(exit_reparse));
ESP_LOGD(TAG, "URL: %s", this->tft_url_.c_str());
ESP_LOGD(TAG,
"TFT upload requested\n"
"Exit reparse: %s\n"
"URL: %s",
YESNO(exit_reparse), this->tft_url_.c_str());
if (this->connection_state_.is_updating_) {
ESP_LOGW(TAG, "Upload in progress");
@@ -180,15 +166,14 @@ bool Nextion::upload_tft(uint32_t baud_rate, bool exit_reparse) {
ESP_LOGD(TAG, "Baud rate: %" PRIu32, baud_rate);
// Define the configuration for the HTTP client
ESP_LOGV(TAG, "Init HTTP client");
ESP_LOGV(TAG, "Heap: %" PRIu32, this->get_free_heap_());
ESP_LOGV(TAG,
"Init HTTP client\n"
"Heap: %" PRIu32,
EspClass::getFreeHeap());
HTTPClient http_client;
http_client.setTimeout(15000); // Yes 15 seconds.... Helps 8266s along
bool begin_status = false;
#ifdef USE_ESP32
begin_status = http_client.begin(this->tft_url_.c_str());
#endif
#ifdef USE_ESP8266
#if USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 7, 0)
http_client.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
@@ -256,22 +241,24 @@ bool Nextion::upload_tft(uint32_t baud_rate, bool exit_reparse) {
this->send_command_("sleep=0");
this->send_command_("dim=100");
delay(250); // NOLINT
ESP_LOGV(TAG, "Heap: %" PRIu32, this->get_free_heap_());
ESP_LOGV(TAG, "Heap: %" PRIu32, EspClass::getFreeHeap());
App.feed_wdt();
char command[128];
// Tells the Nextion the content length of the tft file and baud rate it will be sent at
// Once the Nextion accepts the command it will wait until the file is successfully uploaded
// If it fails for any reason a power cycle of the display will be needed
sprintf(command, "whmi-wris %d,%d,1", this->content_length_, baud_rate);
snprintf(command, sizeof(command), "whmi-wris %" PRIu32 ",%" PRIu32 ",1", this->content_length_, baud_rate);
// Clear serial receive buffer
ESP_LOGV(TAG, "Clear RX buffer");
this->reset_(false);
delay(250); // NOLINT
ESP_LOGV(TAG, "Heap: %" PRIu32, this->get_free_heap_());
ESP_LOGV(TAG, "Upload cmd: %s", command);
ESP_LOGV(TAG,
"Heap: %" PRIu32 "\n"
"Upload cmd: %s",
EspClass::getFreeHeap(), command);
this->send_command_(command);
if (baud_rate != this->original_baud_rate_) {
@@ -290,7 +277,7 @@ bool Nextion::upload_tft(uint32_t baud_rate, bool exit_reparse) {
ESP_LOGD(TAG, "Upload resp: [%s] %zu B",
format_hex_pretty(reinterpret_cast<const uint8_t *>(response.data()), response.size()).c_str(),
response.length());
ESP_LOGV(TAG, "Heap: %" PRIu32, this->get_free_heap_());
ESP_LOGV(TAG, "Heap: %" PRIu32, EspClass::getFreeHeap());
if (response.find(0x05) != std::string::npos) {
ESP_LOGV(TAG, "Upload prep done");
@@ -302,10 +289,12 @@ bool Nextion::upload_tft(uint32_t baud_rate, bool exit_reparse) {
return this->upload_end_(false);
}
ESP_LOGD(TAG, "Upload TFT:");
ESP_LOGD(TAG, " URL: %s", this->tft_url_.c_str());
ESP_LOGD(TAG, " Size: %d bytes", this->content_length_);
ESP_LOGD(TAG, " Heap: %" PRIu32, this->get_free_heap_());
ESP_LOGD(TAG,
"Upload TFT:\n"
" URL: %s\n"
" Size: %d bytes\n"
" Heap: %" PRIu32,
this->tft_url_.c_str(), this->content_length_, EspClass::getFreeHeap());
// Proceed with the content download as before
@@ -322,7 +311,7 @@ bool Nextion::upload_tft(uint32_t baud_rate, bool exit_reparse) {
return this->upload_end_(false);
}
App.feed_wdt();
ESP_LOGV(TAG, "Heap: %" PRIu32 " left: %" PRIu32, this->get_free_heap_(), this->content_length_);
ESP_LOGV(TAG, "Heap: %" PRIu32 " left: %" PRIu32, EspClass::getFreeHeap(), this->content_length_);
}
ESP_LOGD(TAG, "Upload complete");
@@ -356,5 +345,5 @@ WiFiClient *Nextion::get_wifi_client_() {
} // namespace nextion
} // namespace esphome
#endif // USE_ARDUINO
#endif // NOT USE_ESP32
#endif // USE_NEXTION_TFT_UPLOAD

View File

@@ -1,7 +1,7 @@
#include "nextion.h"
#ifdef USE_NEXTION_TFT_UPLOAD
#ifdef USE_ESP_IDF
#ifdef USE_ESP32
#include <esp_heap_caps.h>
#include <esp_http_client.h>
@@ -14,7 +14,7 @@
namespace esphome {
namespace nextion {
static const char *const TAG = "nextion.upload.idf";
static const char *const TAG = "nextion.upload.esp32";
// Followed guide
// https://unofficialnextion.com/t/nextion-upload-protocol-v1-2-the-fast-one/1044/2
@@ -25,8 +25,10 @@ int Nextion::upload_by_chunks_(esp_http_client_handle_t http_client, uint32_t &r
uint32_t range_end = ((upload_first_chunk_sent_ or this->tft_size_ < 4096) ? this->tft_size_ : 4096) - 1;
ESP_LOGD(TAG, "Range start: %" PRIu32, range_start);
if (range_size <= 0 or range_end <= range_start) {
ESP_LOGD(TAG, "Range end: %" PRIu32, range_end);
ESP_LOGD(TAG, "Range size: %" PRIu32, range_size);
ESP_LOGD(TAG,
"Range end: %" PRIu32 "\n"
"Range size: %" PRIu32,
range_end, range_size);
ESP_LOGE(TAG, "Invalid range");
return -1;
}
@@ -151,9 +153,11 @@ int Nextion::upload_by_chunks_(esp_http_client_handle_t http_client, uint32_t &r
}
bool Nextion::upload_tft(uint32_t baud_rate, bool exit_reparse) {
ESP_LOGD(TAG, "TFT upload requested");
ESP_LOGD(TAG, "Exit reparse: %s", YESNO(exit_reparse));
ESP_LOGD(TAG, "URL: %s", this->tft_url_.c_str());
ESP_LOGD(TAG,
"TFT upload requested\n"
"Exit reparse: %s\n"
"URL: %s",
YESNO(exit_reparse), this->tft_url_.c_str());
if (this->connection_state_.is_updating_) {
ESP_LOGW(TAG, "Upload in progress");
@@ -183,8 +187,10 @@ bool Nextion::upload_tft(uint32_t baud_rate, bool exit_reparse) {
ESP_LOGD(TAG, "Baud rate: %" PRIu32, baud_rate);
// Define the configuration for the HTTP client
ESP_LOGV(TAG, "Init HTTP client");
ESP_LOGV(TAG, "Heap: %" PRIu32, esp_get_free_heap_size());
ESP_LOGV(TAG,
"Init HTTP client\n"
"Heap: %" PRIu32,
esp_get_free_heap_size());
esp_http_client_config_t config = {
.url = this->tft_url_.c_str(),
.cert_pem = nullptr,
@@ -208,8 +214,10 @@ bool Nextion::upload_tft(uint32_t baud_rate, bool exit_reparse) {
}
// Perform the HTTP request
ESP_LOGV(TAG, "Check connection");
ESP_LOGV(TAG, "Heap: %" PRIu32, esp_get_free_heap_size());
ESP_LOGV(TAG,
"Check connection\n"
"Heap: %" PRIu32,
esp_get_free_heap_size());
err = esp_http_client_perform(http_client);
if (err != ESP_OK) {
ESP_LOGE(TAG, "HTTP failed: %s", esp_err_to_name(err));
@@ -218,8 +226,10 @@ bool Nextion::upload_tft(uint32_t baud_rate, bool exit_reparse) {
}
// Check the HTTP Status Code
ESP_LOGV(TAG, "Check status");
ESP_LOGV(TAG, "Heap: %" PRIu32, esp_get_free_heap_size());
ESP_LOGV(TAG,
"Check status\n"
"Heap: %" PRIu32,
esp_get_free_heap_size());
int status_code = esp_http_client_get_status_code(http_client);
if (status_code != 200 && status_code != 206) {
return this->upload_end_(false);
@@ -255,7 +265,7 @@ bool Nextion::upload_tft(uint32_t baud_rate, bool exit_reparse) {
// Tells the Nextion the content length of the tft file and baud rate it will be sent at
// Once the Nextion accepts the command it will wait until the file is successfully uploaded
// If it fails for any reason a power cycle of the display will be needed
sprintf(command, "whmi-wris %" PRIu32 ",%" PRIu32 ",1", this->content_length_, baud_rate);
snprintf(command, sizeof(command), "whmi-wris %" PRIu32 ",%" PRIu32 ",1", this->content_length_, baud_rate);
// Clear serial receive buffer
ESP_LOGV(TAG, "Clear RX buffer");
@@ -300,10 +310,12 @@ bool Nextion::upload_tft(uint32_t baud_rate, bool exit_reparse) {
return this->upload_end_(false);
}
ESP_LOGD(TAG, "Uploading TFT:");
ESP_LOGD(TAG, " URL: %s", this->tft_url_.c_str());
ESP_LOGD(TAG, " Size: %" PRIu32 " bytes", this->content_length_);
ESP_LOGD(TAG, " Heap: %" PRIu32, esp_get_free_heap_size());
ESP_LOGD(TAG,
"Uploading TFT:\n"
" URL: %s\n"
" Size: %" PRIu32 " bytes\n"
" Heap: %" PRIu32,
this->tft_url_.c_str(), this->content_length_, esp_get_free_heap_size());
// Proceed with the content download as before
@@ -324,9 +336,8 @@ bool Nextion::upload_tft(uint32_t baud_rate, bool exit_reparse) {
ESP_LOGV(TAG, "Heap: %" PRIu32 " left: %" PRIu32, esp_get_free_heap_size(), this->content_length_);
}
ESP_LOGD(TAG, "TFT upload complete");
ESP_LOGD(TAG, "Close HTTP");
ESP_LOGD(TAG, "TFT upload complete\n"
"Close HTTP");
esp_http_client_close(http_client);
esp_http_client_cleanup(http_client);
ESP_LOGV(TAG, "Connection closed");
@@ -336,5 +347,5 @@ bool Nextion::upload_tft(uint32_t baud_rate, bool exit_reparse) {
} // namespace nextion
} // namespace esphome
#endif // USE_ESP_IDF
#endif // USE_ESP32
#endif // USE_NEXTION_TFT_UPLOAD

View File

@@ -91,7 +91,7 @@ def set_sdkconfig_options(config):
add_idf_sdkconfig_option("CONFIG_OPENTHREAD_SRP_CLIENT", True)
add_idf_sdkconfig_option("CONFIG_OPENTHREAD_SRP_CLIENT_MAX_SERVICES", 5)
# TODO: Add suport for synchronized sleepy end devices (SSED)
# TODO: Add support for synchronized sleepy end devices (SSED)
add_idf_sdkconfig_option(f"CONFIG_OPENTHREAD_{config.get(CONF_DEVICE_TYPE)}", True)
@@ -102,7 +102,7 @@ OpenThreadSrpComponent = openthread_ns.class_("OpenThreadSrpComponent", cg.Compo
_CONNECTION_SCHEMA = cv.Schema(
{
cv.Optional(CONF_PAN_ID): cv.hex_int,
cv.Optional(CONF_CHANNEL): cv.int_,
cv.Optional(CONF_CHANNEL): cv.int_range(min=11, max=26),
cv.Optional(CONF_NETWORK_KEY): cv.hex_int,
cv.Optional(CONF_EXT_PAN_ID): cv.hex_int,
cv.Optional(CONF_NETWORK_NAME): cv.string_strict,

View File

@@ -21,8 +21,7 @@
static const char *const TAG = "openthread";
namespace esphome {
namespace openthread {
namespace esphome::openthread {
OpenThreadComponent *global_openthread_component = // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
@@ -275,7 +274,5 @@ const char *OpenThreadComponent::get_use_address() const { return this->use_addr
void OpenThreadComponent::set_use_address(const char *use_address) { this->use_address_ = use_address; }
} // namespace openthread
} // namespace esphome
} // namespace esphome::openthread
#endif

View File

@@ -13,8 +13,7 @@
#include <optional>
#include <vector>
namespace esphome {
namespace openthread {
namespace esphome::openthread {
class InstanceLock;
@@ -91,6 +90,5 @@ class InstanceLock {
InstanceLock() {}
};
} // namespace openthread
} // namespace esphome
} // namespace esphome::openthread
#endif

View File

@@ -24,8 +24,7 @@
static const char *const TAG = "openthread";
namespace esphome {
namespace openthread {
namespace esphome::openthread {
void OpenThreadComponent::setup() {
// Used eventfds:
@@ -209,6 +208,5 @@ otInstance *InstanceLock::get_instance() { return esp_openthread_get_instance();
InstanceLock::~InstanceLock() { esp_openthread_lock_release(); }
} // namespace openthread
} // namespace esphome
} // namespace esphome::openthread
#endif

View File

@@ -3,8 +3,7 @@
#ifdef USE_OPENTHREAD
#include "esphome/core/log.h"
namespace esphome {
namespace openthread_info {
namespace esphome::openthread_info {
static const char *const TAG = "openthread_info";
@@ -19,6 +18,5 @@ void NetworkKeyOpenThreadInfo::dump_config() { LOG_TEXT_SENSOR("", "Network Key"
void PanIdOpenThreadInfo::dump_config() { LOG_TEXT_SENSOR("", "PAN ID", this); }
void ExtPanIdOpenThreadInfo::dump_config() { LOG_TEXT_SENSOR("", "Extended PAN ID", this); }
} // namespace openthread_info
} // namespace esphome
} // namespace esphome::openthread_info
#endif

View File

@@ -5,8 +5,7 @@
#include "esphome/core/component.h"
#ifdef USE_OPENTHREAD
namespace esphome {
namespace openthread_info {
namespace esphome::openthread_info {
using esphome::openthread::InstanceLock;
@@ -213,6 +212,5 @@ class ExtPanIdOpenThreadInfo : public DatasetOpenThreadInfo, public text_sensor:
std::array<uint8_t, 8> last_extpanid_{};
};
} // namespace openthread_info
} // namespace esphome
} // namespace esphome::openthread_info
#endif

View File

@@ -13,8 +13,6 @@ from esphome.const import (
from esphome.core import CORE, coroutine_with_priority
from esphome.coroutine import CoroPriority
OTA_STATE_LISTENER_KEY = "ota_state_listener"
CODEOWNERS = ["@esphome/core"]
AUTO_LOAD = ["md5", "safe_mode"]
@@ -88,7 +86,6 @@ BASE_OTA_SCHEMA = cv.Schema(
@coroutine_with_priority(CoroPriority.OTA_UPDATES)
async def to_code(config):
cg.add_define("USE_OTA")
CORE.add_job(final_step)
if CORE.is_rp2040 and CORE.using_arduino:
cg.add_library("Updater", None)
@@ -122,24 +119,7 @@ async def ota_to_code(var, config):
await automation.build_automation(trigger, [(cg.uint8, "x")], conf)
use_state_callback = True
if use_state_callback:
request_ota_state_listeners()
def request_ota_state_listeners() -> None:
"""Request that OTA state listeners be compiled in.
Components that need to be notified about OTA state changes (start, progress,
complete, error) should call this function during their code generation.
This enables the add_state_listener() API on OTAComponent.
"""
CORE.data[OTA_STATE_LISTENER_KEY] = True
@coroutine_with_priority(CoroPriority.FINAL)
async def final_step():
"""Final code generation step to configure optional OTA features."""
if CORE.data.get(OTA_STATE_LISTENER_KEY, False):
cg.add_define("USE_OTA_STATE_LISTENER")
cg.add_define("USE_OTA_STATE_CALLBACK")
FILTER_SOURCE_FILES = filter_source_files_from_platform(

View File

@@ -1,5 +1,5 @@
#pragma once
#ifdef USE_OTA_STATE_LISTENER
#ifdef USE_OTA_STATE_CALLBACK
#include "ota_backend.h"
#include "esphome/core/automation.h"
@@ -7,64 +7,70 @@
namespace esphome {
namespace ota {
class OTAStateChangeTrigger final : public Trigger<OTAState>, public OTAStateListener {
class OTAStateChangeTrigger : public Trigger<OTAState> {
public:
explicit OTAStateChangeTrigger(OTAComponent *parent) : parent_(parent) { parent->add_state_listener(this); }
void on_ota_state(OTAState state, float progress, uint8_t error) override {
if (!this->parent_->is_failed()) {
this->trigger(state);
}
explicit OTAStateChangeTrigger(OTAComponent *parent) {
parent->add_on_state_callback([this, parent](OTAState state, float progress, uint8_t error) {
if (!parent->is_failed()) {
trigger(state);
}
});
}
protected:
OTAComponent *parent_;
};
template<OTAState State> class OTAStateTrigger final : public Trigger<>, public OTAStateListener {
class OTAStartTrigger : public Trigger<> {
public:
explicit OTAStateTrigger(OTAComponent *parent) : parent_(parent) { parent->add_state_listener(this); }
void on_ota_state(OTAState state, float progress, uint8_t error) override {
if (state == State && !this->parent_->is_failed()) {
this->trigger();
}
explicit OTAStartTrigger(OTAComponent *parent) {
parent->add_on_state_callback([this, parent](OTAState state, float progress, uint8_t error) {
if (state == OTA_STARTED && !parent->is_failed()) {
trigger();
}
});
}
protected:
OTAComponent *parent_;
};
using OTAStartTrigger = OTAStateTrigger<OTA_STARTED>;
using OTAEndTrigger = OTAStateTrigger<OTA_COMPLETED>;
using OTAAbortTrigger = OTAStateTrigger<OTA_ABORT>;
class OTAProgressTrigger final : public Trigger<float>, public OTAStateListener {
class OTAProgressTrigger : public Trigger<float> {
public:
explicit OTAProgressTrigger(OTAComponent *parent) : parent_(parent) { parent->add_state_listener(this); }
void on_ota_state(OTAState state, float progress, uint8_t error) override {
if (state == OTA_IN_PROGRESS && !this->parent_->is_failed()) {
this->trigger(progress);
}
explicit OTAProgressTrigger(OTAComponent *parent) {
parent->add_on_state_callback([this, parent](OTAState state, float progress, uint8_t error) {
if (state == OTA_IN_PROGRESS && !parent->is_failed()) {
trigger(progress);
}
});
}
protected:
OTAComponent *parent_;
};
class OTAErrorTrigger final : public Trigger<uint8_t>, public OTAStateListener {
class OTAEndTrigger : public Trigger<> {
public:
explicit OTAErrorTrigger(OTAComponent *parent) : parent_(parent) { parent->add_state_listener(this); }
void on_ota_state(OTAState state, float progress, uint8_t error) override {
if (state == OTA_ERROR && !this->parent_->is_failed()) {
this->trigger(error);
}
explicit OTAEndTrigger(OTAComponent *parent) {
parent->add_on_state_callback([this, parent](OTAState state, float progress, uint8_t error) {
if (state == OTA_COMPLETED && !parent->is_failed()) {
trigger();
}
});
}
};
protected:
OTAComponent *parent_;
class OTAAbortTrigger : public Trigger<> {
public:
explicit OTAAbortTrigger(OTAComponent *parent) {
parent->add_on_state_callback([this, parent](OTAState state, float progress, uint8_t error) {
if (state == OTA_ABORT && !parent->is_failed()) {
trigger();
}
});
}
};
class OTAErrorTrigger : public Trigger<uint8_t> {
public:
explicit OTAErrorTrigger(OTAComponent *parent) {
parent->add_on_state_callback([this, parent](OTAState state, float progress, uint8_t error) {
if (state == OTA_ERROR && !parent->is_failed()) {
trigger(error);
}
});
}
};
} // namespace ota

View File

@@ -3,7 +3,7 @@
namespace esphome {
namespace ota {
#ifdef USE_OTA_STATE_LISTENER
#ifdef USE_OTA_STATE_CALLBACK
OTAGlobalCallback *global_ota_callback{nullptr}; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
OTAGlobalCallback *get_global_ota_callback() {
@@ -13,12 +13,7 @@ OTAGlobalCallback *get_global_ota_callback() {
return global_ota_callback;
}
void OTAComponent::notify_state_(OTAState state, float progress, uint8_t error) {
for (auto *listener : this->state_listeners_) {
listener->on_ota_state(state, progress, error);
}
get_global_ota_callback()->notify_ota_state(state, progress, error, this);
}
void register_ota_platform(OTAComponent *ota_caller) { get_global_ota_callback()->register_ota(ota_caller); }
#endif
} // namespace ota

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