1
0
mirror of https://github.com/esphome/esphome.git synced 2025-11-01 15:41:52 +00:00

Compare commits

...

77 Commits

Author SHA1 Message Date
Jesse Hills
d3b7a9687b Merge pull request #10664 from esphome/bump-2025.8.4
2025.8.4
2025-09-10 17:03:37 +12:00
Jesse Hills
9d7fc11108 Bump version to 2025.8.4 2025-09-10 13:56:50 +12:00
tomaszduda23
7969627d3e [light] add missing header (#10590) 2025-09-10 13:56:50 +12:00
Clyde Stubbs
82d2e367d4 [kmeteriso] Fix i2c call (#10618) 2025-09-10 13:56:50 +12:00
Keith Burzinski
972aa691e4 [sen5x] Fix initialization (#10603) 2025-09-10 13:56:50 +12:00
Jesse Hills
c5b2a9e24b Merge pull request #10558 from esphome/bump-2025.8.3
2025.8.3
2025-09-04 22:09:37 +12:00
Jesse Hills
2d3cdf60ba Bump version to 2025.8.3 2025-09-04 09:06:00 +12:00
J. Nick Koston
a29fef166b [api] Fix VERY_VERBOSE logging compilation error with bool arrays (#10539) 2025-09-04 09:06:00 +12:00
Jonathan Swoboda
9fe94f1201 [esp32] Clear IDF environment variables (#10527)
Co-authored-by: J. Nick Koston <nick+github@koston.org>
2025-09-04 09:06:00 +12:00
Anton Viktorov
1b8978a89a [i2c] Fix bug write_register16 (#10547) 2025-09-04 09:06:00 +12:00
Jonathan Swoboda
6f188d1284 [esp32] Rebuild when idf_component.yml changes (#10540) 2025-09-04 09:06:00 +12:00
Clyde Stubbs
a1a336783e [mcp4461] Fix read transaction (#10465)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-09-04 09:06:00 +12:00
Clyde Stubbs
c55bc93f70 [mipi_dsi] Fix config for Guition screen (#10464) 2025-09-04 09:06:00 +12:00
J. Nick Koston
de998f2f39 Fix incorrect entity count due to undefined execution order with globals (#10497) 2025-09-04 09:06:00 +12:00
Oliver Kleinecke
950299e52b Update mcp4461.cpp (#10479) 2025-09-04 09:06:00 +12:00
Jesse Hills
6a20e6f9ad Merge pull request #10485 from esphome/bump-2025.8.2
2025.8.2
2025-08-29 12:38:45 +12:00
Jesse Hills
07875a8b1e Bump version to 2025.8.2 2025-08-29 10:16:19 +12:00
J. Nick Koston
ba4789970c [esphome] Fix OTA watchdog resets by validating all magic bytes before blocking (#10401) 2025-08-29 10:16:19 +12:00
Vinicius Fortuna
015977cfdf [rtttl] Fix RTTTL for speakers (#10381) 2025-08-29 10:16:19 +12:00
J. Nick Koston
e513c0f004 Fix AttributeError when uploading OTA to offline OpenThread devices (#10459) 2025-08-29 10:16:19 +12:00
Clyde Stubbs
a11970aee0 [wifi] Fix retry with hidden networks. (#10445) 2025-08-29 10:16:19 +12:00
Clyde Stubbs
4ab37b069b [i2c] Perform register reads as single transactions (#10389)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-08-29 10:16:08 +12:00
Clyde Stubbs
b6bb6699d1 [mipi_spi] Fix dimensions (#10443) 2025-08-29 10:15:30 +12:00
J. Nick Koston
078eaff9a8 [wifi] Fix reconnection failures after adapter restart by not clearing netif pointers (#10458) 2025-08-29 10:15:30 +12:00
Jesse Hills
4dc11f05a7 Merge pull request #10427 from esphome/bump-2025.8.1
2025.8.1
2025-08-26 08:48:10 +12:00
Jonathan Rascher
2aceb56606 Merge commit from fork
Ensures auth check doesn't pass erroneously when the client-supplied
digest is shorter than the correct digest, but happens to match a
prefix of the correct value (e.g., same username + certain substrings of
the password).
2025-08-25 16:00:04 +12:00
Jesse Hills
d071a074ef Bump version to 2025.8.1 2025-08-25 15:59:35 +12:00
Clyde Stubbs
7a459c8c20 [web_server] Use oi.esphome.io for css and js assets (#10296) 2025-08-25 15:59:35 +12:00
J. Nick Koston
aebd21958a [test] Add integration test for light effect memory corruption fix (#10417) 2025-08-25 15:59:35 +12:00
J. Nick Koston
c542db8bfe [esp32_ble_tracker] Fix on_scan_end trigger compilation without USE_ESP32_BLE_DEVICE (#10399) 2025-08-25 15:59:35 +12:00
Clyde Stubbs
d9dcfe66ec [lvgl] Fix meter rotation (#10342) 2025-08-25 15:59:35 +12:00
J. Nick Koston
8517c2e903 [esp32_ble_client] Reduce log level for harmless BLE timeout race conditions (#10339)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-08-25 15:59:34 +12:00
J. Nick Koston
684384892a [deep_sleep] Fix ESP32-C6 compilation error with gpio_deep_sleep_hold_en() (#10345) 2025-08-25 15:59:34 +12:00
J. Nick Koston
d560831d79 [script] Fix parallel mode scripts with delays cancelling each other (#10324) 2025-08-25 15:59:34 +12:00
J. Nick Koston
fcc3c8e1b6 [esp32_ble] Increase GATT connection retry count to use full timeout window (#10376) 2025-08-25 15:59:34 +12:00
J. Nick Koston
959ffde60e [esp32_ble_client] Optimize BLE connection parameters for different connection types (#10356) 2025-08-25 15:59:34 +12:00
J. Nick Koston
07715dd50f [pvvx_mithermometer] Fix race condition with BLE authentication (#10327) 2025-08-25 15:59:34 +12:00
J. Nick Koston
03836ee2d2 [core] Improve error reporting for entity name conflicts with non-ASCII characters (#10329) 2025-08-25 15:59:34 +12:00
Clyde Stubbs
50408d9abb [http_request] Fix for host after ArduinoJson library bump (#10348) 2025-08-25 15:59:34 +12:00
Jesse Hills
0de7259428 [api] Add `USE_API_HOMEASSISTANT_SERVICES if using tag_scanned` action (#10316) 2025-08-25 15:59:34 +12:00
J. Nick Koston
d054709c2d [esp32_ble_client] Add log helper functions to reduce flash usage by 120 bytes (#10243) 2025-08-25 15:59:34 +12:00
J. Nick Koston
da16887915 [api] Add zero-copy StringRef methods for compilation_time and effect_name (#10257) 2025-08-25 15:59:34 +12:00
Jesse Hills
2adb993242 Merge pull request #10309 from esphome/bump-2025.8.0
2025.8.0
2025-08-20 19:58:01 +12:00
Jesse Hills
8e67df8059 Bump version to 2025.8.0 2025-08-20 10:45:57 +12:00
Jesse Hills
c5b2c8d971 Merge pull request #10308 from esphome/bump-2025.8.0b4
2025.8.0b4
2025-08-20 10:30:37 +12:00
Jesse Hills
104906ca11 Bump version to 2025.8.0b4 2025-08-20 09:40:19 +12:00
J. Nick Koston
ad5f6f0cfe [bluetooth_proxy] Fix connection slot race by deferring slot release until GATT close (#10303) 2025-08-20 09:40:19 +12:00
Patrick
8356f7fcd3 [pipsolar] fix faults_present, fix update interval (#10289) 2025-08-20 09:40:19 +12:00
Ben Winslow
225de226b0 [atm90e32] Only read 1 register per SPI transaction per datasheet. (#10258) 2025-08-20 09:40:19 +12:00
Jesse Hills
fd07e1d979 Merge pull request #10298 from esphome/bump-2025.8.0b3
2025.8.0b3
2025-08-19 20:40:12 +12:00
Jesse Hills
23554cda06 Bump version to 2025.8.0b3 2025-08-19 13:09:22 +12:00
Ben Winslow
064385eac6 [nextion] Don't include terminating NUL in nextion text_sensor states (#10273) 2025-08-19 13:09:22 +12:00
Jesse Hills
6502ed70de [esp32] Write variant to sdkconfig file (#10267) 2025-08-19 13:09:22 +12:00
J. Nick Koston
bb894c3e32 [core] Fix scheduler race condition where cancelled items still execute (#10268) 2025-08-19 13:09:22 +12:00
Ben Winslow
c5858b7032 [core] Fix post-OTA logs display when using esphome run and MQTT (#10274) 2025-08-19 13:09:22 +12:00
Ben Winslow
99f57ecb73 [senseair] Discard 0 ppm readings with "Out Of Range" bit set. (#10275) 2025-08-19 13:09:22 +12:00
J. Nick Koston
cc6c892678 [esp32_ble] Store GATTC/GATTS param and small data inline to nearly eliminate heap allocations (#10249)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-08-19 13:09:22 +12:00
RFDarter
07a98d2525 [web_server] fix cover_all_json_generator wrong detail (#10252) 2025-08-19 13:09:22 +12:00
J. Nick Koston
e80f616366 [esp32_ble] Optimize BLE event memory usage by eliminating std::vector overhead (#10247)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-08-19 13:09:22 +12:00
J. Nick Koston
46be877594 [bluetooth_proxy] Remove redundant connection type check after V1 removal (#10208) 2025-08-19 13:09:21 +12:00
J. Nick Koston
ac8b48a53c [core] Trigger clean build when components are removed from configuration (#10235)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-08-19 13:09:21 +12:00
J. Nick Koston
7fdbd8528a [wifi] Automatically disable Enterprise WiFi support when EAP is not configured (#10242) 2025-08-19 13:09:21 +12:00
Katherine Whitlock
80970f972b Improve error reporting for add_library (#10226)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-08-19 13:09:21 +12:00
Jesse Hills
3c7865cd6f [esp32_ble] Add `USE_ESP32_BLE_UUID` when advertising is desired (#10230) 2025-08-19 13:09:21 +12:00
Jesse Hills
0a3ee7d84e Merge pull request #10228 from esphome/bump-2025.8.0b2
2025.8.0b2
2025-08-15 08:46:15 +12:00
Jesse Hills
8d61b1e8df Bump version to 2025.8.0b2 2025-08-14 14:00:27 +12:00
dependabot[bot]
9c897993bb Bump esphome-dashboard from 20250514.0 to 20250814.0 (#10227)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-14 14:00:26 +12:00
dependabot[bot]
93f9475105 Bump aioesphomeapi from 38.2.1 to 39.0.0 (#10222)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-14 14:00:26 +12:00
Samuel Sieb
95cd224e3e [psram] allow disabling (#10224)
Co-authored-by: Samuel Sieb <samuel@sieb.net>
2025-08-14 14:00:26 +12:00
Jesse Hills
b7afeafda9 [espnow] Set state to enabled before adding initial peers (#10225) 2025-08-14 14:00:26 +12:00
Jesse Hills
7922462bcf [entity] Allow `device_id` to be blank on entities (#10217) 2025-08-14 14:00:26 +12:00
Jesse Hills
1c2e1ab3e5 Merge pull request #10214 from esphome/bump-2025.8.0b1
2025.8.0b1
2025-08-13 23:56:34 +12:00
J. Nick Koston
68ddd98f5f [CI] Fix CI job failures for PRs with >300 changed files (#10215) 2025-08-13 15:49:38 +12:00
Jesse Hills
6b7ced1970 Bump version to 2025.8.0b1 2025-08-13 14:46:50 +12:00
J. Nick Koston
ed2b76050b [bluetooth_proxy] Remove ESPBTUUID dependency to save 296 bytes of flash (#10213) 2025-08-13 14:18:53 +12:00
Samuel Sieb
113813617d [bme280_base, bmp280_base] add reasons to the fails, clean up logging (#10209)
Co-authored-by: Keith Burzinski <kbx81x@gmail.com>
2025-08-13 02:05:22 +00:00
Keith Burzinski
c3a209d3f4 [ld2450] Replace `throttle` with native filters (#10196)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2025-08-12 19:35:19 -05:00
132 changed files with 2189 additions and 1099 deletions

View File

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

View File

@@ -132,14 +132,17 @@ def choose_upload_log_host(
]
resolved.append(choose_prompt(options, purpose=purpose))
elif device == "OTA":
if (show_ota and "ota" in CORE.config) or (
show_api and "api" in CORE.config
if CORE.address and (
(show_ota and "ota" in CORE.config)
or (show_api and "api" in CORE.config)
):
resolved.append(CORE.address)
elif show_mqtt and has_mqtt_logging():
resolved.append("MQTT")
else:
resolved.append(device)
if not resolved:
_LOGGER.error("All specified devices: %s could not be resolved.", defaults)
return resolved
# No devices specified, show interactive chooser
@@ -476,7 +479,7 @@ def show_logs(config: ConfigType, args: ArgsProtocol, devices: list[str]) -> int
from esphome.components.api.client import run_logs
return run_logs(config, addresses_to_use)
if get_port_type(port) == "MQTT" and "mqtt" in config:
if get_port_type(port) in ("NETWORK", "MQTT") and "mqtt" in config:
from esphome import mqtt
return mqtt.show_logs(

View File

@@ -321,6 +321,7 @@ HOMEASSISTANT_TAG_SCANNED_ACTION_SCHEMA = cv.maybe_simple_value(
HOMEASSISTANT_TAG_SCANNED_ACTION_SCHEMA,
)
async def homeassistant_tag_scanned_to_code(config, action_id, template_arg, args):
cg.add_define("USE_API_HOMEASSISTANT_SERVICES")
serv = await cg.get_variable(config[CONF_ID])
var = cg.new_Pvariable(action_id, template_arg, serv, True)
cg.add(var.set_service("esphome.tag_scanned"))

View File

@@ -455,9 +455,7 @@ uint16_t APIConnection::try_send_light_state(EntityBase *entity, APIConnection *
resp.cold_white = values.get_cold_white();
resp.warm_white = values.get_warm_white();
if (light->supports_effects()) {
// get_effect_name() returns temporary std::string - must store it
std::string effect_name = light->get_effect_name();
resp.set_effect(StringRef(effect_name));
resp.set_effect(light->get_effect_name_ref());
}
return fill_and_encode_entity_state(light, resp, LightStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
}
@@ -1415,9 +1413,7 @@ bool APIConnection::send_device_info_response(const DeviceInfoRequest &msg) {
static constexpr auto ESPHOME_VERSION_REF = StringRef::from_lit(ESPHOME_VERSION);
resp.set_esphome_version(ESPHOME_VERSION_REF);
// get_compilation_time() returns temporary std::string - must store it
std::string compilation_time = App.get_compilation_time();
resp.set_compilation_time(StringRef(compilation_time));
resp.set_compilation_time(App.get_compilation_time_ref());
// Compile-time StringRef constants for manufacturers
#if defined(USE_ESP8266) || defined(USE_ESP32)

View File

@@ -1135,7 +1135,7 @@ void ExecuteServiceArgument::dump_to(std::string &out) const {
dump_field(out, "string_", this->string_);
dump_field(out, "int_", this->int_);
for (const auto it : this->bool_array) {
dump_field(out, "bool_array", it, 4);
dump_field(out, "bool_array", static_cast<bool>(it), 4);
}
for (const auto &it : this->int_array) {
dump_field(out, "int_array", it, 4);

View File

@@ -382,20 +382,15 @@ float ATM90E32Component::get_setup_priority() const { return setup_priority::IO;
// R/C registers can conly be cleared after the LastSPIData register is updated (register 78H)
// Peakdetect period: 05H. Bit 15:8 are PeakDet_period in ms. 7:0 are Sag_period
// Default is 143FH (20ms, 63ms)
uint16_t ATM90E32Component::read16_transaction_(uint16_t a_register) {
uint16_t ATM90E32Component::read16_(uint16_t a_register) {
this->enable();
delay_microseconds_safe(1); // min delay between CS low and first SCK is 200ns - 1us is plenty
uint8_t addrh = (1 << 7) | ((a_register >> 8) & 0x03);
uint8_t addrl = (a_register & 0xFF);
uint8_t data[4] = {addrh, addrl, 0x00, 0x00};
this->transfer_array(data, 4);
uint16_t output = encode_uint16(data[2], data[3]);
ESP_LOGVV(TAG, "read16_ 0x%04" PRIX16 " output 0x%04" PRIX16, a_register, output);
return output;
}
uint16_t ATM90E32Component::read16_(uint16_t a_register) {
this->enable();
delay_microseconds_safe(1); // min delay between CS low and first SCK is 200ns - 1us is plenty
uint16_t output = this->read16_transaction_(a_register);
delay_microseconds_safe(1); // allow the last clock to propagate before releasing CS
this->disable();
delay_microseconds_safe(1); // meet minimum CS high time before next transaction
@@ -403,14 +398,8 @@ uint16_t ATM90E32Component::read16_(uint16_t a_register) {
}
int ATM90E32Component::read32_(uint16_t addr_h, uint16_t addr_l) {
this->enable();
delay_microseconds_safe(1);
const uint16_t val_h = this->read16_transaction_(addr_h);
delay_microseconds_safe(1);
const uint16_t val_l = this->read16_transaction_(addr_l);
delay_microseconds_safe(1);
this->disable();
delay_microseconds_safe(1);
const uint16_t val_h = this->read16_(addr_h);
const uint16_t val_l = this->read16_(addr_l);
const int32_t val = (val_h << 16) | val_l;
ESP_LOGVV(TAG,

View File

@@ -140,7 +140,6 @@ class ATM90E32Component : public PollingComponent,
number::Number *ref_currents_[3]{nullptr, nullptr, nullptr};
#endif
uint16_t read16_(uint16_t a_register);
uint16_t read16_transaction_(uint16_t a_register);
int read32_(uint16_t addr_h, uint16_t addr_l);
void write16_(uint16_t a_register, uint16_t val, bool validate = true);
float get_local_phase_voltage_(uint8_t phase);

View File

@@ -41,7 +41,7 @@ void AXS15231Touchscreen::update_touches() {
i2c::ErrorCode err;
uint8_t data[8]{};
err = this->write(AXS_READ_TOUCHPAD, sizeof(AXS_READ_TOUCHPAD), false);
err = this->write(AXS_READ_TOUCHPAD, sizeof(AXS_READ_TOUCHPAD));
ERROR_CHECK(err);
err = this->read(data, sizeof(data));
ERROR_CHECK(err);

View File

@@ -286,6 +286,7 @@ async def remove_bond_to_code(config, action_id, template_arg, args):
async def to_code(config):
# Register the loggers this component needs
esp32_ble.register_bt_logger(BTLoggers.GATT, BTLoggers.SMP)
cg.add_define("USE_ESP32_BLE_UUID")
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)

View File

@@ -12,16 +12,30 @@ namespace esphome::bluetooth_proxy {
static const char *const TAG = "bluetooth_proxy.connection";
// This function is allocation-free and directly packs UUIDs into the output array
// using precalculated constants for the Bluetooth base UUID
static void fill_128bit_uuid_array(std::array<uint64_t, 2> &out, esp_bt_uuid_t uuid_source) {
esp_bt_uuid_t uuid = espbt::ESPBTUUID::from_uuid(uuid_source).as_128bit().get_uuid();
out[0] = ((uint64_t) uuid.uuid.uuid128[15] << 56) | ((uint64_t) uuid.uuid.uuid128[14] << 48) |
((uint64_t) uuid.uuid.uuid128[13] << 40) | ((uint64_t) uuid.uuid.uuid128[12] << 32) |
((uint64_t) uuid.uuid.uuid128[11] << 24) | ((uint64_t) uuid.uuid.uuid128[10] << 16) |
((uint64_t) uuid.uuid.uuid128[9] << 8) | ((uint64_t) uuid.uuid.uuid128[8]);
out[1] = ((uint64_t) uuid.uuid.uuid128[7] << 56) | ((uint64_t) uuid.uuid.uuid128[6] << 48) |
((uint64_t) uuid.uuid.uuid128[5] << 40) | ((uint64_t) uuid.uuid.uuid128[4] << 32) |
((uint64_t) uuid.uuid.uuid128[3] << 24) | ((uint64_t) uuid.uuid.uuid128[2] << 16) |
((uint64_t) uuid.uuid.uuid128[1] << 8) | ((uint64_t) uuid.uuid.uuid128[0]);
// Bluetooth base UUID: 00000000-0000-1000-8000-00805F9B34FB
// out[0] = bytes 8-15 (big-endian)
// - For 128-bit UUIDs: use bytes 8-15 as-is
// - For 16/32-bit UUIDs: insert into bytes 12-15, use 0x00001000 for bytes 8-11
out[0] = uuid_source.len == ESP_UUID_LEN_128
? (((uint64_t) uuid_source.uuid.uuid128[15] << 56) | ((uint64_t) uuid_source.uuid.uuid128[14] << 48) |
((uint64_t) uuid_source.uuid.uuid128[13] << 40) | ((uint64_t) uuid_source.uuid.uuid128[12] << 32) |
((uint64_t) uuid_source.uuid.uuid128[11] << 24) | ((uint64_t) uuid_source.uuid.uuid128[10] << 16) |
((uint64_t) uuid_source.uuid.uuid128[9] << 8) | ((uint64_t) uuid_source.uuid.uuid128[8]))
: (((uint64_t) (uuid_source.len == ESP_UUID_LEN_16 ? uuid_source.uuid.uuid16 : uuid_source.uuid.uuid32)
<< 32) |
0x00001000ULL); // Base UUID bytes 8-11
// out[1] = bytes 0-7 (big-endian)
// - For 128-bit UUIDs: use bytes 0-7 as-is
// - For 16/32-bit UUIDs: use precalculated base UUID constant
out[1] = uuid_source.len == ESP_UUID_LEN_128
? ((uint64_t) uuid_source.uuid.uuid128[7] << 56) | ((uint64_t) uuid_source.uuid.uuid128[6] << 48) |
((uint64_t) uuid_source.uuid.uuid128[5] << 40) | ((uint64_t) uuid_source.uuid.uuid128[4] << 32) |
((uint64_t) uuid_source.uuid.uuid128[3] << 24) | ((uint64_t) uuid_source.uuid.uuid128[2] << 16) |
((uint64_t) uuid_source.uuid.uuid128[1] << 8) | ((uint64_t) uuid_source.uuid.uuid128[0])
: 0x800000805F9B34FBULL; // Base UUID bytes 0-7: 80-00-00-80-5F-9B-34-FB
}
// Helper to fill UUID in the appropriate format based on client support and UUID type
@@ -119,7 +133,7 @@ void BluetoothConnection::loop() {
// Check if we should disable the loop
// - For V3_WITH_CACHE: Services are never sent, disable after INIT state
// - For other connections: Disable only after service discovery is complete
// - For V3_WITHOUT_CACHE: Disable only after service discovery is complete
// (send_service_ == DONE_SENDING_SERVICES, which is only set after services are sent)
if (this->state_ != espbt::ClientState::INIT && (this->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE ||
this->send_service_ == DONE_SENDING_SERVICES)) {
@@ -146,10 +160,7 @@ void BluetoothConnection::send_service_for_discovery_() {
if (this->send_service_ >= this->service_count_) {
this->send_service_ = DONE_SENDING_SERVICES;
this->proxy_->send_gatt_services_done(this->address_);
if (this->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE ||
this->connection_type_ == espbt::ConnectionType::V3_WITHOUT_CACHE) {
this->release_services();
}
this->release_services();
return;
}
@@ -364,10 +375,19 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga
switch (event) {
case ESP_GATTC_DISCONNECT_EVT: {
this->reset_connection_(param->disconnect.reason);
// Don't reset connection yet - wait for CLOSE_EVT to ensure controller has freed resources
// This prevents race condition where we mark slot as free before controller cleanup is complete
ESP_LOGD(TAG, "[%d] [%s] Disconnect, reason=0x%02x", this->connection_index_, this->address_str_.c_str(),
param->disconnect.reason);
// Send disconnection notification but don't free the slot yet
this->proxy_->send_device_connection(this->address_, false, 0, param->disconnect.reason);
break;
}
case ESP_GATTC_CLOSE_EVT: {
ESP_LOGD(TAG, "[%d] [%s] Close, reason=0x%02x, freeing slot", this->connection_index_, this->address_str_.c_str(),
param->close.reason);
// Now the GATT connection is fully closed and controller resources are freed
// Safe to mark the connection slot as available
this->reset_connection_(param->close.reason);
break;
}

View File

@@ -7,6 +7,8 @@
#include <esphome/components/sensor/sensor.h>
#include <esphome/core/component.h>
#define BME280_ERROR_WRONG_CHIP_ID "Wrong chip ID"
namespace esphome {
namespace bme280_base {
@@ -98,18 +100,18 @@ void BME280Component::setup() {
if (!this->read_byte(BME280_REGISTER_CHIPID, &chip_id)) {
this->error_code_ = COMMUNICATION_FAILED;
this->mark_failed();
this->mark_failed(ESP_LOG_MSG_COMM_FAIL);
return;
}
if (chip_id != 0x60) {
this->error_code_ = WRONG_CHIP_ID;
this->mark_failed();
this->mark_failed(BME280_ERROR_WRONG_CHIP_ID);
return;
}
// Send a soft reset.
if (!this->write_byte(BME280_REGISTER_RESET, BME280_SOFT_RESET)) {
this->mark_failed();
this->mark_failed("Reset failed");
return;
}
// Wait until the NVM data has finished loading.
@@ -118,14 +120,12 @@ void BME280Component::setup() {
do { // NOLINT
delay(2);
if (!this->read_byte(BME280_REGISTER_STATUS, &status)) {
ESP_LOGW(TAG, "Error reading status register.");
this->mark_failed();
this->mark_failed("Error reading status register");
return;
}
} while ((status & BME280_STATUS_IM_UPDATE) && (--retry));
if (status & BME280_STATUS_IM_UPDATE) {
ESP_LOGW(TAG, "Timeout loading NVM.");
this->mark_failed();
this->mark_failed("Timeout loading NVM");
return;
}
@@ -153,26 +153,26 @@ void BME280Component::setup() {
uint8_t humid_control_val = 0;
if (!this->read_byte(BME280_REGISTER_CONTROLHUMID, &humid_control_val)) {
this->mark_failed();
this->mark_failed("Read humidity control");
return;
}
humid_control_val &= ~0b00000111;
humid_control_val |= this->humidity_oversampling_ & 0b111;
if (!this->write_byte(BME280_REGISTER_CONTROLHUMID, humid_control_val)) {
this->mark_failed();
this->mark_failed("Write humidity control");
return;
}
uint8_t config_register = 0;
if (!this->read_byte(BME280_REGISTER_CONFIG, &config_register)) {
this->mark_failed();
this->mark_failed("Read config");
return;
}
config_register &= ~0b11111100;
config_register |= 0b101 << 5; // 1000 ms standby time
config_register |= (this->iir_filter_ & 0b111) << 2;
if (!this->write_byte(BME280_REGISTER_CONFIG, config_register)) {
this->mark_failed();
this->mark_failed("Write config");
return;
}
}
@@ -183,7 +183,7 @@ void BME280Component::dump_config() {
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
break;
case WRONG_CHIP_ID:
ESP_LOGE(TAG, "BME280 has wrong chip ID! Is it a BME280?");
ESP_LOGE(TAG, BME280_ERROR_WRONG_CHIP_ID);
break;
case NONE:
default:
@@ -223,21 +223,21 @@ void BME280Component::update() {
this->set_timeout("data", uint32_t(ceilf(meas_time)), [this]() {
uint8_t data[8];
if (!this->read_bytes(BME280_REGISTER_MEASUREMENTS, data, 8)) {
ESP_LOGW(TAG, "Error reading registers.");
ESP_LOGW(TAG, "Error reading registers");
this->status_set_warning();
return;
}
int32_t t_fine = 0;
float const temperature = this->read_temperature_(data, &t_fine);
if (std::isnan(temperature)) {
ESP_LOGW(TAG, "Invalid temperature, cannot read pressure & humidity values.");
ESP_LOGW(TAG, "Invalid temperature");
this->status_set_warning();
return;
}
float const pressure = this->read_pressure_(data, t_fine);
float const humidity = this->read_humidity_(data, t_fine);
ESP_LOGV(TAG, "Got temperature=%.1f°C pressure=%.1fhPa humidity=%.1f%%", temperature, pressure, humidity);
ESP_LOGV(TAG, "Temperature=%.1f°C Pressure=%.1fhPa Humidity=%.1f%%", temperature, pressure, humidity);
if (this->temperature_sensor_ != nullptr)
this->temperature_sensor_->publish_state(temperature);
if (this->pressure_sensor_ != nullptr)

View File

@@ -203,7 +203,7 @@ void BMI160Component::dump_config() {
i2c::ErrorCode BMI160Component::read_le_int16_(uint8_t reg, int16_t *value, uint8_t len) {
uint8_t raw_data[len * 2];
// read using read_register because we have little-endian data, and read_bytes_16 will swap it
i2c::ErrorCode err = this->read_register(reg, raw_data, len * 2, true);
i2c::ErrorCode err = this->read_register(reg, raw_data, len * 2);
if (err != i2c::ERROR_OK) {
return err;
}

View File

@@ -2,6 +2,8 @@
#include "esphome/core/hal.h"
#include "esphome/core/log.h"
#define BMP280_ERROR_WRONG_CHIP_ID "Wrong chip ID"
namespace esphome {
namespace bmp280_base {
@@ -61,25 +63,25 @@ void BMP280Component::setup() {
// Read the chip id twice, to work around a bug where the first read is 0.
// https://community.st.com/t5/stm32-mcus-products/issue-with-reading-bmp280-chip-id-using-spi/td-p/691855
if (!this->read_byte(0xD0, &chip_id)) {
if (!this->bmp_read_byte(0xD0, &chip_id)) {
this->error_code_ = COMMUNICATION_FAILED;
this->mark_failed();
this->mark_failed(ESP_LOG_MSG_COMM_FAIL);
return;
}
if (!this->read_byte(0xD0, &chip_id)) {
if (!this->bmp_read_byte(0xD0, &chip_id)) {
this->error_code_ = COMMUNICATION_FAILED;
this->mark_failed();
this->mark_failed(ESP_LOG_MSG_COMM_FAIL);
return;
}
if (chip_id != 0x58) {
this->error_code_ = WRONG_CHIP_ID;
this->mark_failed();
this->mark_failed(BMP280_ERROR_WRONG_CHIP_ID);
return;
}
// Send a soft reset.
if (!this->write_byte(BMP280_REGISTER_RESET, BMP280_SOFT_RESET)) {
this->mark_failed();
if (!this->bmp_write_byte(BMP280_REGISTER_RESET, BMP280_SOFT_RESET)) {
this->mark_failed("Reset failed");
return;
}
// Wait until the NVM data has finished loading.
@@ -87,15 +89,13 @@ void BMP280Component::setup() {
uint8_t retry = 5;
do {
delay(2);
if (!this->read_byte(BMP280_REGISTER_STATUS, &status)) {
ESP_LOGW(TAG, "Error reading status register.");
this->mark_failed();
if (!this->bmp_read_byte(BMP280_REGISTER_STATUS, &status)) {
this->mark_failed("Error reading status register");
return;
}
} while ((status & BMP280_STATUS_IM_UPDATE) && (--retry));
if (status & BMP280_STATUS_IM_UPDATE) {
ESP_LOGW(TAG, "Timeout loading NVM.");
this->mark_failed();
this->mark_failed("Timeout loading NVM");
return;
}
@@ -115,15 +115,15 @@ void BMP280Component::setup() {
this->calibration_.p9 = this->read_s16_le_(0x9E);
uint8_t config_register = 0;
if (!this->read_byte(BMP280_REGISTER_CONFIG, &config_register)) {
this->mark_failed();
if (!this->bmp_read_byte(BMP280_REGISTER_CONFIG, &config_register)) {
this->mark_failed("Read config");
return;
}
config_register &= ~0b11111100;
config_register |= 0b000 << 5; // 0.5 ms standby time
config_register |= (this->iir_filter_ & 0b111) << 2;
if (!this->write_byte(BMP280_REGISTER_CONFIG, config_register)) {
this->mark_failed();
if (!this->bmp_write_byte(BMP280_REGISTER_CONFIG, config_register)) {
this->mark_failed("Write config");
return;
}
}
@@ -134,7 +134,7 @@ void BMP280Component::dump_config() {
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
break;
case WRONG_CHIP_ID:
ESP_LOGE(TAG, "BMP280 has wrong chip ID! Is it a BME280?");
ESP_LOGE(TAG, BMP280_ERROR_WRONG_CHIP_ID);
break;
case NONE:
default:
@@ -159,7 +159,7 @@ void BMP280Component::update() {
meas_value |= (this->temperature_oversampling_ & 0b111) << 5;
meas_value |= (this->pressure_oversampling_ & 0b111) << 2;
meas_value |= 0b01; // Forced mode
if (!this->write_byte(BMP280_REGISTER_CONTROL, meas_value)) {
if (!this->bmp_write_byte(BMP280_REGISTER_CONTROL, meas_value)) {
this->status_set_warning();
return;
}
@@ -172,13 +172,13 @@ void BMP280Component::update() {
int32_t t_fine = 0;
float temperature = this->read_temperature_(&t_fine);
if (std::isnan(temperature)) {
ESP_LOGW(TAG, "Invalid temperature, cannot read pressure values.");
ESP_LOGW(TAG, "Invalid temperature");
this->status_set_warning();
return;
}
float pressure = this->read_pressure_(t_fine);
ESP_LOGD(TAG, "Got temperature=%.1f°C pressure=%.1fhPa", temperature, pressure);
ESP_LOGV(TAG, "Temperature=%.1f°C Pressure=%.1fhPa", temperature, pressure);
if (this->temperature_sensor_ != nullptr)
this->temperature_sensor_->publish_state(temperature);
if (this->pressure_sensor_ != nullptr)
@@ -188,9 +188,10 @@ void BMP280Component::update() {
}
float BMP280Component::read_temperature_(int32_t *t_fine) {
uint8_t data[3];
if (!this->read_bytes(BMP280_REGISTER_TEMPDATA, data, 3))
uint8_t data[3]{};
if (!this->bmp_read_bytes(BMP280_REGISTER_TEMPDATA, data, 3))
return NAN;
ESP_LOGV(TAG, "Read temperature data, raw: %02X %02X %02X", data[0], data[1], data[2]);
int32_t adc = ((data[0] & 0xFF) << 16) | ((data[1] & 0xFF) << 8) | (data[2] & 0xFF);
adc >>= 4;
if (adc == 0x80000) {
@@ -212,7 +213,7 @@ float BMP280Component::read_temperature_(int32_t *t_fine) {
float BMP280Component::read_pressure_(int32_t t_fine) {
uint8_t data[3];
if (!this->read_bytes(BMP280_REGISTER_PRESSUREDATA, data, 3))
if (!this->bmp_read_bytes(BMP280_REGISTER_PRESSUREDATA, data, 3))
return NAN;
int32_t adc = ((data[0] & 0xFF) << 16) | ((data[1] & 0xFF) << 8) | (data[2] & 0xFF);
adc >>= 4;
@@ -258,12 +259,12 @@ void BMP280Component::set_pressure_oversampling(BMP280Oversampling pressure_over
void BMP280Component::set_iir_filter(BMP280IIRFilter iir_filter) { this->iir_filter_ = iir_filter; }
uint8_t BMP280Component::read_u8_(uint8_t a_register) {
uint8_t data = 0;
this->read_byte(a_register, &data);
this->bmp_read_byte(a_register, &data);
return data;
}
uint16_t BMP280Component::read_u16_le_(uint8_t a_register) {
uint16_t data = 0;
this->read_byte_16(a_register, &data);
this->bmp_read_byte_16(a_register, &data);
return (data >> 8) | (data << 8);
}
int16_t BMP280Component::read_s16_le_(uint8_t a_register) { return this->read_u16_le_(a_register); }

View File

@@ -67,12 +67,12 @@ class BMP280Component : public PollingComponent {
float get_setup_priority() const override;
void update() override;
virtual bool read_byte(uint8_t a_register, uint8_t *data) = 0;
virtual bool write_byte(uint8_t a_register, uint8_t data) = 0;
virtual bool read_bytes(uint8_t a_register, uint8_t *data, size_t len) = 0;
virtual bool read_byte_16(uint8_t a_register, uint16_t *data) = 0;
protected:
virtual bool bmp_read_byte(uint8_t a_register, uint8_t *data) = 0;
virtual bool bmp_write_byte(uint8_t a_register, uint8_t data) = 0;
virtual bool bmp_read_bytes(uint8_t a_register, uint8_t *data, size_t len) = 0;
virtual bool bmp_read_byte_16(uint8_t a_register, uint16_t *data) = 0;
/// Read the temperature value and store the calculated ambient temperature in t_fine.
float read_temperature_(int32_t *t_fine);
/// Read the pressure value in hPa using the provided t_fine value.

View File

@@ -5,19 +5,6 @@
namespace esphome {
namespace bmp280_i2c {
bool BMP280I2CComponent::read_byte(uint8_t a_register, uint8_t *data) {
return I2CDevice::read_byte(a_register, data);
};
bool BMP280I2CComponent::write_byte(uint8_t a_register, uint8_t data) {
return I2CDevice::write_byte(a_register, data);
};
bool BMP280I2CComponent::read_bytes(uint8_t a_register, uint8_t *data, size_t len) {
return I2CDevice::read_bytes(a_register, data, len);
};
bool BMP280I2CComponent::read_byte_16(uint8_t a_register, uint16_t *data) {
return I2CDevice::read_byte_16(a_register, data);
};
void BMP280I2CComponent::dump_config() {
LOG_I2C_DEVICE(this);
BMP280Component::dump_config();

View File

@@ -11,10 +11,12 @@ static const char *const TAG = "bmp280_i2c.sensor";
/// This class implements support for the BMP280 Temperature+Pressure i2c sensor.
class BMP280I2CComponent : public esphome::bmp280_base::BMP280Component, public i2c::I2CDevice {
public:
bool read_byte(uint8_t a_register, uint8_t *data) override;
bool write_byte(uint8_t a_register, uint8_t data) override;
bool read_bytes(uint8_t a_register, uint8_t *data, size_t len) override;
bool read_byte_16(uint8_t a_register, uint16_t *data) override;
bool bmp_read_byte(uint8_t a_register, uint8_t *data) override { return read_byte(a_register, data); }
bool bmp_write_byte(uint8_t a_register, uint8_t data) override { return write_byte(a_register, data); }
bool bmp_read_bytes(uint8_t a_register, uint8_t *data, size_t len) override {
return read_bytes(a_register, data, len);
}
bool bmp_read_byte_16(uint8_t a_register, uint16_t *data) override { return read_byte_16(a_register, data); }
void dump_config() override;
};

View File

@@ -28,7 +28,7 @@ void BMP280SPIComponent::setup() {
// 0x77 is transferred, for read access, the byte 0xF7 is transferred.
// https://www.bosch-sensortec.com/media/boschsensortec/downloads/datasheets/bst-bmp280-ds001.pdf
bool BMP280SPIComponent::read_byte(uint8_t a_register, uint8_t *data) {
bool BMP280SPIComponent::bmp_read_byte(uint8_t a_register, uint8_t *data) {
this->enable();
this->transfer_byte(set_bit(a_register, 7));
*data = this->transfer_byte(0);
@@ -36,7 +36,7 @@ bool BMP280SPIComponent::read_byte(uint8_t a_register, uint8_t *data) {
return true;
}
bool BMP280SPIComponent::write_byte(uint8_t a_register, uint8_t data) {
bool BMP280SPIComponent::bmp_write_byte(uint8_t a_register, uint8_t data) {
this->enable();
this->transfer_byte(clear_bit(a_register, 7));
this->transfer_byte(data);
@@ -44,7 +44,7 @@ bool BMP280SPIComponent::write_byte(uint8_t a_register, uint8_t data) {
return true;
}
bool BMP280SPIComponent::read_bytes(uint8_t a_register, uint8_t *data, size_t len) {
bool BMP280SPIComponent::bmp_read_bytes(uint8_t a_register, uint8_t *data, size_t len) {
this->enable();
this->transfer_byte(set_bit(a_register, 7));
this->read_array(data, len);
@@ -52,7 +52,7 @@ bool BMP280SPIComponent::read_bytes(uint8_t a_register, uint8_t *data, size_t le
return true;
}
bool BMP280SPIComponent::read_byte_16(uint8_t a_register, uint16_t *data) {
bool BMP280SPIComponent::bmp_read_byte_16(uint8_t a_register, uint16_t *data) {
this->enable();
this->transfer_byte(set_bit(a_register, 7));
((uint8_t *) data)[1] = this->transfer_byte(0);

View File

@@ -10,10 +10,10 @@ class BMP280SPIComponent : public esphome::bmp280_base::BMP280Component,
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW,
spi::CLOCK_PHASE_LEADING, spi::DATA_RATE_200KHZ> {
void setup() override;
bool read_byte(uint8_t a_register, uint8_t *data) override;
bool write_byte(uint8_t a_register, uint8_t data) override;
bool read_bytes(uint8_t a_register, uint8_t *data, size_t len) override;
bool read_byte_16(uint8_t a_register, uint16_t *data) override;
bool bmp_read_byte(uint8_t a_register, uint8_t *data) override;
bool bmp_write_byte(uint8_t a_register, uint8_t data) override;
bool bmp_read_bytes(uint8_t a_register, uint8_t *data, size_t len) override;
bool bmp_read_byte_16(uint8_t a_register, uint16_t *data) override;
};
} // namespace bmp280_spi

View File

@@ -91,7 +91,7 @@ bool CH422GComponent::read_inputs_() {
// Write a register. Can't use the standard write_byte() method because there is no single pre-configured i2c address.
bool CH422GComponent::write_reg_(uint8_t reg, uint8_t value) {
auto err = this->bus_->write(reg, &value, 1);
auto err = this->bus_->write_readv(reg, &value, 1, nullptr, 0);
if (err != i2c::ERROR_OK) {
this->status_set_warning(str_sprintf("write failed for register 0x%X, error %d", reg, err).c_str());
return false;
@@ -102,7 +102,7 @@ bool CH422GComponent::write_reg_(uint8_t reg, uint8_t value) {
uint8_t CH422GComponent::read_reg_(uint8_t reg) {
uint8_t value;
auto err = this->bus_->read(reg, &value, 1);
auto err = this->bus_->write_readv(reg, nullptr, 0, &value, 1);
if (err != i2c::ERROR_OK) {
this->status_set_warning(str_sprintf("read failed for register 0x%X, error %d", reg, err).c_str());
return 0;

View File

@@ -1,4 +1,5 @@
#ifdef USE_ESP32
#include "soc/soc_caps.h"
#include "driver/gpio.h"
#include "deep_sleep_component.h"
#include "esphome/core/log.h"
@@ -83,7 +84,11 @@ void DeepSleepComponent::deep_sleep_() {
}
gpio_sleep_set_direction(gpio_pin, GPIO_MODE_INPUT);
gpio_hold_en(gpio_pin);
#if !SOC_GPIO_SUPPORT_HOLD_SINGLE_IO_IN_DSLP
// Some ESP32 variants support holding a single GPIO during deep sleep without this function
// For those variants, gpio_hold_en() is sufficient to hold the pin state during deep sleep
gpio_deep_sleep_hold_en();
#endif
bool level = !this->wakeup_pin_->is_inverted();
if (this->wakeup_pin_mode_ == WAKEUP_PIN_MODE_INVERT_WAKEUP && this->wakeup_pin_->digital_read()) {
level = !level;
@@ -120,7 +125,11 @@ void DeepSleepComponent::deep_sleep_() {
}
gpio_sleep_set_direction(gpio_pin, GPIO_MODE_INPUT);
gpio_hold_en(gpio_pin);
#if !SOC_GPIO_SUPPORT_HOLD_SINGLE_IO_IN_DSLP
// Some ESP32 variants support holding a single GPIO during deep sleep without this function
// For those variants, gpio_hold_en() is sufficient to hold the pin state during deep sleep
gpio_deep_sleep_hold_en();
#endif
bool level = !this->wakeup_pin_->is_inverted();
if (this->wakeup_pin_mode_ == WAKEUP_PIN_MODE_INVERT_WAKEUP && this->wakeup_pin_->digital_read()) {
level = !level;

View File

@@ -83,7 +83,7 @@ void EE895Component::write_command_(uint16_t addr, uint16_t reg_cnt) {
crc16 = calc_crc16_(address, 6);
address[5] = crc16 & 0xFF;
address[6] = (crc16 >> 8) & 0xFF;
this->write(address, 7, true);
this->write(address, 7);
}
float EE895Component::read_float_() {

View File

@@ -40,6 +40,7 @@ from esphome.cpp_generator import RawExpression
import esphome.final_validate as fv
from esphome.helpers import copy_file_if_changed, mkdir_p, write_file_if_changed
from esphome.types import ConfigType
from esphome.writer import clean_cmake_cache
from .boards import BOARDS, STANDARD_BOARDS
from .const import ( # noqa
@@ -824,8 +825,9 @@ async def to_code(config):
cg.set_cpp_standard("gnu++20")
cg.add_build_flag("-DUSE_ESP32")
cg.add_define("ESPHOME_BOARD", config[CONF_BOARD])
cg.add_build_flag(f"-DUSE_ESP32_VARIANT_{config[CONF_VARIANT]}")
cg.add_define("ESPHOME_VARIANT", VARIANT_FRIENDLY[config[CONF_VARIANT]])
variant = config[CONF_VARIANT]
cg.add_build_flag(f"-DUSE_ESP32_VARIANT_{variant}")
cg.add_define("ESPHOME_VARIANT", VARIANT_FRIENDLY[variant])
cg.add_define(ThreadModel.MULTI_ATOMICS)
cg.add_platformio_option("lib_ldf_mode", "off")
@@ -839,6 +841,9 @@ async def to_code(config):
if conf[CONF_ADVANCED][CONF_IGNORE_EFUSE_CUSTOM_MAC]:
cg.add_define("USE_ESP32_IGNORE_EFUSE_CUSTOM_MAC")
for clean_var in ("IDF_PATH", "IDF_TOOLS_PATH"):
os.environ.pop(clean_var, None)
add_extra_script(
"post",
"post_build.py",
@@ -859,6 +864,7 @@ async def to_code(config):
cg.add_platformio_option(
"platform_packages", ["espressif/toolchain-esp32ulp@2.35.0-20220830"]
)
add_idf_sdkconfig_option(f"CONFIG_IDF_TARGET_{variant}", True)
add_idf_sdkconfig_option(
f"CONFIG_ESPTOOLPY_FLASHSIZE_{config[CONF_FLASH_SIZE]}", True
)
@@ -1077,7 +1083,11 @@ def _write_idf_component_yml():
contents = yaml_util.dump({"dependencies": dependencies})
else:
contents = ""
write_file_if_changed(yml_path, contents)
if write_file_if_changed(yml_path, contents):
dependencies_lock = CORE.relative_build_path("dependencies.lock")
if os.path.isfile(dependencies_lock):
os.remove(dependencies_lock)
clean_cmake_cache()
# Called by writer.py

View File

@@ -280,6 +280,10 @@ async def to_code(config):
add_idf_sdkconfig_option(
"CONFIG_BT_BLE_ESTAB_LINK_CONN_TOUT", timeout_seconds
)
# Increase GATT client connection retry count for problematic devices
# Default in ESP-IDF is 3, we increase to 10 for better reliability with
# low-power/timing-sensitive devices
add_idf_sdkconfig_option("CONFIG_BT_GATTC_CONNECT_RETRY_COUNT", 10)
# Set the maximum number of notification registrations
# This controls how many BLE characteristics can have notifications enabled
@@ -294,6 +298,7 @@ async def to_code(config):
if config[CONF_ADVERTISING]:
cg.add_define("USE_ESP32_BLE_ADVERTISING")
cg.add_define("USE_ESP32_BLE_UUID")
@automation.register_condition("ble.enabled", BLEEnabledCondition, cv.Schema({}))

View File

@@ -306,7 +306,7 @@ void ESP32BLE::loop() {
case BLEEvent::GATTS: {
esp_gatts_cb_event_t event = ble_event->event_.gatts.gatts_event;
esp_gatt_if_t gatts_if = ble_event->event_.gatts.gatts_if;
esp_ble_gatts_cb_param_t *param = ble_event->event_.gatts.gatts_param;
esp_ble_gatts_cb_param_t *param = &ble_event->event_.gatts.gatts_param;
ESP_LOGV(TAG, "gatts_event [esp_gatt_if: %d] - %d", gatts_if, event);
for (auto *gatts_handler : this->gatts_event_handlers_) {
gatts_handler->gatts_event_handler(event, gatts_if, param);
@@ -316,7 +316,7 @@ void ESP32BLE::loop() {
case BLEEvent::GATTC: {
esp_gattc_cb_event_t event = ble_event->event_.gattc.gattc_event;
esp_gatt_if_t gattc_if = ble_event->event_.gattc.gattc_if;
esp_ble_gattc_cb_param_t *param = ble_event->event_.gattc.gattc_param;
esp_ble_gattc_cb_param_t *param = &ble_event->event_.gattc.gattc_param;
ESP_LOGV(TAG, "gattc_event [esp_gatt_if: %d] - %d", gattc_if, event);
for (auto *gattc_handler : this->gattc_event_handlers_) {
gattc_handler->gattc_event_handler(event, gattc_if, param);

View File

@@ -3,8 +3,7 @@
#ifdef USE_ESP32
#include <cstddef> // for offsetof
#include <vector>
#include <cstring> // for memcpy
#include <esp_gap_ble_api.h>
#include <esp_gattc_api.h>
#include <esp_gatts_api.h>
@@ -62,10 +61,24 @@ static_assert(offsetof(esp_ble_gap_cb_param_t, read_rssi_cmpl.rssi) == sizeof(es
static_assert(offsetof(esp_ble_gap_cb_param_t, read_rssi_cmpl.remote_addr) == sizeof(esp_bt_status_t) + sizeof(int8_t),
"remote_addr must follow rssi in read_rssi_cmpl");
// Param struct sizes on ESP32
static constexpr size_t GATTC_PARAM_SIZE = 28;
static constexpr size_t GATTS_PARAM_SIZE = 32;
// Maximum size for inline storage of data
// GATTC: 80 - 28 (param) - 8 (other fields) = 44 bytes for data
// GATTS: 80 - 32 (param) - 8 (other fields) = 40 bytes for data
static constexpr size_t GATTC_INLINE_DATA_SIZE = 44;
static constexpr size_t GATTS_INLINE_DATA_SIZE = 40;
// Verify param struct sizes
static_assert(sizeof(esp_ble_gattc_cb_param_t) == GATTC_PARAM_SIZE, "GATTC param size unexpected");
static_assert(sizeof(esp_ble_gatts_cb_param_t) == GATTS_PARAM_SIZE, "GATTS param size unexpected");
// Received GAP, GATTC and GATTS events are only queued, and get processed in the main loop().
// This class stores each event with minimal memory usage.
// GAP events (99% of traffic) don't have the vector overhead.
// GATTC/GATTS events use heap allocation for their param and data.
// GAP events (99% of traffic) don't have the heap allocation overhead.
// GATTC/GATTS events use heap allocation for their param and inline storage for small data.
//
// Event flow:
// 1. ESP-IDF BLE stack calls our static handlers in the BLE task context
@@ -112,21 +125,21 @@ class BLEEvent {
this->init_gap_data_(e, p);
}
// Constructor for GATTC events - uses heap allocation
// IMPORTANT: The heap allocation is REQUIRED and must not be removed as an optimization.
// The param pointer from ESP-IDF is only valid during the callback execution.
// Since BLE events are processed asynchronously in the main loop, we must create
// our own copy to ensure the data remains valid until the event is processed.
// Constructor for GATTC events - param stored inline, data may use heap
// IMPORTANT: We MUST copy the param struct because the pointer from ESP-IDF
// is only valid during the callback execution. Since BLE events are processed
// asynchronously in the main loop, we store our own copy inline to ensure
// the data remains valid until the event is processed.
BLEEvent(esp_gattc_cb_event_t e, esp_gatt_if_t i, esp_ble_gattc_cb_param_t *p) {
this->type_ = GATTC;
this->init_gattc_data_(e, i, p);
}
// Constructor for GATTS events - uses heap allocation
// IMPORTANT: The heap allocation is REQUIRED and must not be removed as an optimization.
// The param pointer from ESP-IDF is only valid during the callback execution.
// Since BLE events are processed asynchronously in the main loop, we must create
// our own copy to ensure the data remains valid until the event is processed.
// Constructor for GATTS events - param stored inline, data may use heap
// IMPORTANT: We MUST copy the param struct because the pointer from ESP-IDF
// is only valid during the callback execution. Since BLE events are processed
// asynchronously in the main loop, we store our own copy inline to ensure
// the data remains valid until the event is processed.
BLEEvent(esp_gatts_cb_event_t e, esp_gatt_if_t i, esp_ble_gatts_cb_param_t *p) {
this->type_ = GATTS;
this->init_gatts_data_(e, i, p);
@@ -136,25 +149,32 @@ class BLEEvent {
~BLEEvent() { this->release(); }
// Default constructor for pre-allocation in pool
BLEEvent() : type_(GAP) {}
BLEEvent() : event_{}, type_(GAP) {}
// Invoked on return to EventPool - clean up any heap-allocated data
void release() {
if (this->type_ == GAP) {
return;
}
if (this->type_ == GATTC) {
delete this->event_.gattc.gattc_param;
delete this->event_.gattc.data;
this->event_.gattc.gattc_param = nullptr;
this->event_.gattc.data = nullptr;
return;
}
if (this->type_ == GATTS) {
delete this->event_.gatts.gatts_param;
delete this->event_.gatts.data;
this->event_.gatts.gatts_param = nullptr;
this->event_.gatts.data = nullptr;
switch (this->type_) {
case GAP:
// GAP events don't have heap allocations
break;
case GATTC:
// Param is now stored inline, only delete heap data if it was heap-allocated
if (!this->event_.gattc.is_inline && this->event_.gattc.data.heap_data != nullptr) {
delete[] this->event_.gattc.data.heap_data;
}
// Clear critical fields to prevent issues if type changes
this->event_.gattc.is_inline = false;
this->event_.gattc.data.heap_data = nullptr;
break;
case GATTS:
// Param is now stored inline, only delete heap data if it was heap-allocated
if (!this->event_.gatts.is_inline && this->event_.gatts.data.heap_data != nullptr) {
delete[] this->event_.gatts.data.heap_data;
}
// Clear critical fields to prevent issues if type changes
this->event_.gatts.is_inline = false;
this->event_.gatts.data.heap_data = nullptr;
break;
}
}
@@ -206,20 +226,30 @@ class BLEEvent {
// NOLINTNEXTLINE(readability-identifier-naming)
struct gattc_event {
esp_gattc_cb_event_t gattc_event;
esp_gatt_if_t gattc_if;
esp_ble_gattc_cb_param_t *gattc_param; // Heap-allocated
std::vector<uint8_t> *data; // Heap-allocated
} gattc; // 16 bytes (pointers only)
esp_ble_gattc_cb_param_t gattc_param; // Stored inline (28 bytes)
esp_gattc_cb_event_t gattc_event; // 4 bytes
union {
uint8_t *heap_data; // 4 bytes when heap-allocated
uint8_t inline_data[GATTC_INLINE_DATA_SIZE]; // 44 bytes when stored inline
} data; // 44 bytes total
uint16_t data_len; // 2 bytes
esp_gatt_if_t gattc_if; // 1 byte
bool is_inline; // 1 byte - true when data is stored inline
} gattc; // Total: 80 bytes
// NOLINTNEXTLINE(readability-identifier-naming)
struct gatts_event {
esp_gatts_cb_event_t gatts_event;
esp_gatt_if_t gatts_if;
esp_ble_gatts_cb_param_t *gatts_param; // Heap-allocated
std::vector<uint8_t> *data; // Heap-allocated
} gatts; // 16 bytes (pointers only)
} event_; // 80 bytes
esp_ble_gatts_cb_param_t gatts_param; // Stored inline (32 bytes)
esp_gatts_cb_event_t gatts_event; // 4 bytes
union {
uint8_t *heap_data; // 4 bytes when heap-allocated
uint8_t inline_data[GATTS_INLINE_DATA_SIZE]; // 40 bytes when stored inline
} data; // 40 bytes total
uint16_t data_len; // 2 bytes
esp_gatt_if_t gatts_if; // 1 byte
bool is_inline; // 1 byte - true when data is stored inline
} gatts; // Total: 80 bytes
} event_; // 80 bytes
ble_event_t type_;
@@ -233,6 +263,29 @@ class BLEEvent {
const esp_ble_sec_t &security() const { return event_.gap.security; }
private:
// Helper to copy data with inline storage optimization
template<typename EventStruct, size_t InlineSize>
void copy_data_with_inline_storage_(EventStruct &event, const uint8_t *src_data, uint16_t len,
uint8_t **param_value_ptr) {
event.data_len = len;
if (len > 0) {
if (len <= InlineSize) {
event.is_inline = true;
memcpy(event.data.inline_data, src_data, len);
*param_value_ptr = event.data.inline_data;
} else {
event.is_inline = false;
event.data.heap_data = new uint8_t[len];
memcpy(event.data.heap_data, src_data, len);
*param_value_ptr = event.data.heap_data;
}
} else {
event.is_inline = false;
event.data.heap_data = nullptr;
*param_value_ptr = nullptr;
}
}
// Initialize GAP event data
void init_gap_data_(esp_gap_ble_cb_event_t e, esp_ble_gap_cb_param_t *p) {
this->event_.gap.gap_event = e;
@@ -317,35 +370,38 @@ class BLEEvent {
this->event_.gattc.gattc_if = i;
if (p == nullptr) {
this->event_.gattc.gattc_param = nullptr;
this->event_.gattc.data = nullptr;
// Zero out the param struct when null
memset(&this->event_.gattc.gattc_param, 0, sizeof(this->event_.gattc.gattc_param));
this->event_.gattc.is_inline = false;
this->event_.gattc.data.heap_data = nullptr;
this->event_.gattc.data_len = 0;
return; // Invalid event, but we can't log in header file
}
// Heap-allocate param and data
// Heap allocation is used because GATTC/GATTS events are rare (<1% of events)
// while GAP events (99%) are stored inline to minimize memory usage
// IMPORTANT: This heap allocation provides clear ownership semantics:
// - The BLEEvent owns the allocated memory for its lifetime
// - The data remains valid from the BLE callback context until processed in the main loop
// - Without this copy, we'd have use-after-free bugs as ESP-IDF reuses the callback memory
this->event_.gattc.gattc_param = new esp_ble_gattc_cb_param_t(*p);
// Copy param struct inline (no heap allocation!)
// GATTC/GATTS events are rare (<1% of events) but we can still store them inline
// along with small data payloads, eliminating all heap allocations for typical BLE operations
// CRITICAL: This copy is REQUIRED for memory safety - the ESP-IDF param pointer
// is only valid during the callback and will be reused/freed after we return
this->event_.gattc.gattc_param = *p;
// Copy data for events that need it
// The param struct contains pointers (e.g., notify.value) that point to temporary buffers.
// We must copy this data to ensure it remains valid when the event is processed later.
switch (e) {
case ESP_GATTC_NOTIFY_EVT:
this->event_.gattc.data = new std::vector<uint8_t>(p->notify.value, p->notify.value + p->notify.value_len);
this->event_.gattc.gattc_param->notify.value = this->event_.gattc.data->data();
copy_data_with_inline_storage_<decltype(this->event_.gattc), GATTC_INLINE_DATA_SIZE>(
this->event_.gattc, p->notify.value, p->notify.value_len, &this->event_.gattc.gattc_param.notify.value);
break;
case ESP_GATTC_READ_CHAR_EVT:
case ESP_GATTC_READ_DESCR_EVT:
this->event_.gattc.data = new std::vector<uint8_t>(p->read.value, p->read.value + p->read.value_len);
this->event_.gattc.gattc_param->read.value = this->event_.gattc.data->data();
copy_data_with_inline_storage_<decltype(this->event_.gattc), GATTC_INLINE_DATA_SIZE>(
this->event_.gattc, p->read.value, p->read.value_len, &this->event_.gattc.gattc_param.read.value);
break;
default:
this->event_.gattc.data = nullptr;
this->event_.gattc.is_inline = false;
this->event_.gattc.data.heap_data = nullptr;
this->event_.gattc.data_len = 0;
break;
}
}
@@ -356,30 +412,33 @@ class BLEEvent {
this->event_.gatts.gatts_if = i;
if (p == nullptr) {
this->event_.gatts.gatts_param = nullptr;
this->event_.gatts.data = nullptr;
// Zero out the param struct when null
memset(&this->event_.gatts.gatts_param, 0, sizeof(this->event_.gatts.gatts_param));
this->event_.gatts.is_inline = false;
this->event_.gatts.data.heap_data = nullptr;
this->event_.gatts.data_len = 0;
return; // Invalid event, but we can't log in header file
}
// Heap-allocate param and data
// Heap allocation is used because GATTC/GATTS events are rare (<1% of events)
// while GAP events (99%) are stored inline to minimize memory usage
// IMPORTANT: This heap allocation provides clear ownership semantics:
// - The BLEEvent owns the allocated memory for its lifetime
// - The data remains valid from the BLE callback context until processed in the main loop
// - Without this copy, we'd have use-after-free bugs as ESP-IDF reuses the callback memory
this->event_.gatts.gatts_param = new esp_ble_gatts_cb_param_t(*p);
// Copy param struct inline (no heap allocation!)
// GATTC/GATTS events are rare (<1% of events) but we can still store them inline
// along with small data payloads, eliminating all heap allocations for typical BLE operations
// CRITICAL: This copy is REQUIRED for memory safety - the ESP-IDF param pointer
// is only valid during the callback and will be reused/freed after we return
this->event_.gatts.gatts_param = *p;
// Copy data for events that need it
// The param struct contains pointers (e.g., write.value) that point to temporary buffers.
// We must copy this data to ensure it remains valid when the event is processed later.
switch (e) {
case ESP_GATTS_WRITE_EVT:
this->event_.gatts.data = new std::vector<uint8_t>(p->write.value, p->write.value + p->write.len);
this->event_.gatts.gatts_param->write.value = this->event_.gatts.data->data();
copy_data_with_inline_storage_<decltype(this->event_.gatts), GATTS_INLINE_DATA_SIZE>(
this->event_.gatts, p->write.value, p->write.len, &this->event_.gatts.gatts_param.write.value);
break;
default:
this->event_.gatts.data = nullptr;
this->event_.gatts.is_inline = false;
this->event_.gatts.data.heap_data = nullptr;
this->event_.gatts.data_len = 0;
break;
}
}
@@ -389,6 +448,15 @@ class BLEEvent {
// The gap member in the union should be 80 bytes (including the gap_event enum)
static_assert(sizeof(decltype(((BLEEvent *) nullptr)->event_.gap)) <= 80, "gap_event struct has grown beyond 80 bytes");
// Verify GATTC and GATTS structs don't exceed GAP struct size
// This ensures the union size is determined by GAP (the most common event type)
static_assert(sizeof(decltype(((BLEEvent *) nullptr)->event_.gattc)) <=
sizeof(decltype(((BLEEvent *) nullptr)->event_.gap)),
"gattc_event struct exceeds gap_event size - union size would increase");
static_assert(sizeof(decltype(((BLEEvent *) nullptr)->event_.gatts)) <=
sizeof(decltype(((BLEEvent *) nullptr)->event_.gap)),
"gatts_event struct exceeds gap_event size - union size would increase");
// Verify esp_ble_sec_t fits within our union
static_assert(sizeof(esp_ble_sec_t) <= 73, "esp_ble_sec_t is larger than BLEScanResult");

View File

@@ -1,6 +1,7 @@
#include "ble_uuid.h"
#ifdef USE_ESP32
#ifdef USE_ESP32_BLE_UUID
#include <cstring>
#include <cstdio>
@@ -190,4 +191,5 @@ std::string ESPBTUUID::to_string() const {
} // namespace esphome::esp32_ble
#endif
#endif // USE_ESP32_BLE_UUID
#endif // USE_ESP32

View File

@@ -1,9 +1,11 @@
#pragma once
#include "esphome/core/defines.h"
#include "esphome/core/hal.h"
#include "esphome/core/helpers.h"
#ifdef USE_ESP32
#ifdef USE_ESP32_BLE_UUID
#include <string>
#include <esp_bt_defs.h>
@@ -42,4 +44,5 @@ class ESPBTUUID {
} // namespace esphome::esp32_ble
#endif
#endif // USE_ESP32_BLE_UUID
#endif // USE_ESP32

View File

@@ -65,6 +65,8 @@ FINAL_VALIDATE_SCHEMA = esp32_ble.validate_variant
async def to_code(config):
cg.add_define("USE_ESP32_BLE_UUID")
uuid = config[CONF_UUID].hex
uuid_arr = [
cg.RawExpression(f"0x{uuid[i : i + 2]}") for i in range(0, len(uuid), 2)

View File

@@ -7,6 +7,7 @@
#include <esp_gap_ble_api.h>
#include <esp_gatt_defs.h>
#include <esp_gattc_api.h>
namespace esphome::esp32_ble_client {
@@ -111,43 +112,19 @@ void BLEClientBase::connect() {
this->remote_addr_type_);
this->paired_ = false;
// Set preferred connection parameters before connecting
// Use FAST for all V3 connections (better latency and reliability)
// Use MEDIUM for V1/legacy connections (balanced performance)
uint16_t min_interval, max_interval, timeout;
const char *param_type;
if (this->connection_type_ == espbt::ConnectionType::V3_WITHOUT_CACHE ||
this->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE) {
min_interval = FAST_MIN_CONN_INTERVAL;
max_interval = FAST_MAX_CONN_INTERVAL;
timeout = FAST_CONN_TIMEOUT;
param_type = "fast";
} else {
min_interval = MEDIUM_MIN_CONN_INTERVAL;
max_interval = MEDIUM_MAX_CONN_INTERVAL;
timeout = MEDIUM_CONN_TIMEOUT;
param_type = "medium";
// Determine connection parameters based on connection type
if (this->connection_type_ == espbt::ConnectionType::V3_WITHOUT_CACHE) {
// V3 without cache needs fast params for service discovery
this->set_conn_params_(FAST_MIN_CONN_INTERVAL, FAST_MAX_CONN_INTERVAL, 0, FAST_CONN_TIMEOUT, "fast");
} else if (this->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE) {
// V3 with cache can use medium params
this->set_conn_params_(MEDIUM_MIN_CONN_INTERVAL, MEDIUM_MAX_CONN_INTERVAL, 0, MEDIUM_CONN_TIMEOUT, "medium");
}
// For V1/Legacy, don't set params - use ESP-IDF defaults
auto param_ret = esp_ble_gap_set_prefer_conn_params(this->remote_bda_, min_interval, max_interval,
0, // latency: 0
timeout);
if (param_ret != ESP_OK) {
ESP_LOGW(TAG, "[%d] [%s] esp_ble_gap_set_prefer_conn_params failed: %d", this->connection_index_,
this->address_str_.c_str(), param_ret);
} else {
this->log_connection_params_(param_type);
}
// Now open the connection
// Open the connection
auto ret = esp_ble_gattc_open(this->gattc_if_, this->remote_bda_, this->remote_addr_type_, true);
if (ret) {
this->log_gattc_warning_("esp_ble_gattc_open", ret);
this->set_state(espbt::ClientState::IDLE);
} else {
this->set_state(espbt::ClientState::CONNECTING);
}
this->handle_connection_result_(ret);
}
esp_err_t BLEClientBase::pair() { return esp_ble_set_encryption(this->remote_bda_, ESP_BLE_SEC_ENCRYPT); }
@@ -159,7 +136,7 @@ void BLEClientBase::disconnect() {
return;
}
if (this->state_ == espbt::ClientState::CONNECTING || this->conn_id_ == UNSET_CONN_ID) {
ESP_LOGW(TAG, "[%d] [%s] Disconnecting before connected, disconnect scheduled.", this->connection_index_,
ESP_LOGD(TAG, "[%d] [%s] Disconnect before connected, disconnect scheduled", this->connection_index_,
this->address_str_.c_str());
this->want_disconnect_ = true;
return;
@@ -172,13 +149,11 @@ void BLEClientBase::unconditional_disconnect() {
ESP_LOGI(TAG, "[%d] [%s] Disconnecting (conn_id: %d).", this->connection_index_, this->address_str_.c_str(),
this->conn_id_);
if (this->state_ == espbt::ClientState::DISCONNECTING) {
ESP_LOGE(TAG, "[%d] [%s] Tried to disconnect while already disconnecting.", this->connection_index_,
this->address_str_.c_str());
this->log_error_("Already disconnecting");
return;
}
if (this->conn_id_ == UNSET_CONN_ID) {
ESP_LOGE(TAG, "[%d] [%s] No connection ID set, cannot disconnect.", this->connection_index_,
this->address_str_.c_str());
this->log_error_("conn id unset, cannot disconnect");
return;
}
auto err = esp_ble_gattc_close(this->gattc_if_, this->conn_id_);
@@ -234,17 +209,51 @@ void BLEClientBase::log_connection_params_(const char *param_type) {
ESP_LOGD(TAG, "[%d] [%s] %s conn params", this->connection_index_, this->address_str_.c_str(), param_type);
}
void BLEClientBase::restore_medium_conn_params_() {
// Restore to medium connection parameters after initial connection phase
// This balances performance with bandwidth usage for normal operation
void BLEClientBase::handle_connection_result_(esp_err_t ret) {
if (ret) {
this->log_gattc_warning_("esp_ble_gattc_open", ret);
this->set_state(espbt::ClientState::IDLE);
} else {
this->set_state(espbt::ClientState::CONNECTING);
}
}
void BLEClientBase::log_error_(const char *message) {
ESP_LOGE(TAG, "[%d] [%s] %s", this->connection_index_, this->address_str_.c_str(), message);
}
void BLEClientBase::log_error_(const char *message, int code) {
ESP_LOGE(TAG, "[%d] [%s] %s=%d", this->connection_index_, this->address_str_.c_str(), message, code);
}
void BLEClientBase::log_warning_(const char *message) {
ESP_LOGW(TAG, "[%d] [%s] %s", this->connection_index_, this->address_str_.c_str(), message);
}
void BLEClientBase::update_conn_params_(uint16_t min_interval, uint16_t max_interval, uint16_t latency,
uint16_t timeout, const char *param_type) {
esp_ble_conn_update_params_t conn_params = {{0}};
memcpy(conn_params.bda, this->remote_bda_, sizeof(esp_bd_addr_t));
conn_params.min_int = MEDIUM_MIN_CONN_INTERVAL;
conn_params.max_int = MEDIUM_MAX_CONN_INTERVAL;
conn_params.latency = 0;
conn_params.timeout = MEDIUM_CONN_TIMEOUT;
this->log_connection_params_("medium");
esp_ble_gap_update_conn_params(&conn_params);
conn_params.min_int = min_interval;
conn_params.max_int = max_interval;
conn_params.latency = latency;
conn_params.timeout = timeout;
this->log_connection_params_(param_type);
esp_err_t err = esp_ble_gap_update_conn_params(&conn_params);
if (err != ESP_OK) {
this->log_gattc_warning_("esp_ble_gap_update_conn_params", err);
}
}
void BLEClientBase::set_conn_params_(uint16_t min_interval, uint16_t max_interval, uint16_t latency, uint16_t timeout,
const char *param_type) {
// Set preferred connection parameters before connecting
// These will be used when establishing the connection
this->log_connection_params_(param_type);
esp_err_t err = esp_ble_gap_set_prefer_conn_params(this->remote_bda_, min_interval, max_interval, latency, timeout);
if (err != ESP_OK) {
this->log_gattc_warning_("esp_ble_gap_set_prefer_conn_params", err);
}
}
bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t esp_gattc_if,
@@ -264,8 +273,7 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
this->app_id);
this->gattc_if_ = esp_gattc_if;
} else {
ESP_LOGE(TAG, "[%d] [%s] gattc app registration failed id=%d code=%d", this->connection_index_,
this->address_str_.c_str(), param->reg.app_id, param->reg.status);
this->log_error_("gattc app registration failed status", param->reg.status);
this->status_ = param->reg.status;
this->mark_failed();
}
@@ -277,11 +285,21 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
this->log_gattc_event_("OPEN");
// conn_id was already set in ESP_GATTC_CONNECT_EVT
this->service_count_ = 0;
// ESP-IDF's BLE stack may send ESP_GATTC_OPEN_EVT after esp_ble_gattc_open() returns an
// error, if the error occurred at the BTA/GATT layer. This can result in the event
// arriving after we've already transitioned to IDLE state.
if (this->state_ == espbt::ClientState::IDLE) {
ESP_LOGD(TAG, "[%d] [%s] ESP_GATTC_OPEN_EVT in IDLE state (status=%d), ignoring", this->connection_index_,
this->address_str_.c_str(), param->open.status);
break;
}
if (this->state_ != espbt::ClientState::CONNECTING) {
// This should not happen but lets log it in case it does
// because it means we have a bad assumption about how the
// ESP BT stack works.
ESP_LOGE(TAG, "[%d] [%s] Got ESP_GATTC_OPEN_EVT while in %s state, status=%d", this->connection_index_,
ESP_LOGE(TAG, "[%d] [%s] ESP_GATTC_OPEN_EVT in %s state (status=%d)", this->connection_index_,
this->address_str_.c_str(), espbt::client_state_to_string(this->state_), param->open.status);
}
if (param->open.status != ESP_GATT_OK && param->open.status != ESP_GATT_ALREADY_OPEN) {
@@ -301,13 +319,14 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
this->set_state(espbt::ClientState::CONNECTED);
ESP_LOGI(TAG, "[%d] [%s] Connection open", this->connection_index_, this->address_str_.c_str());
if (this->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE) {
// Restore to medium connection parameters for cached connections too
this->restore_medium_conn_params_();
// Cached connections already connected with medium parameters, no update needed
// only set our state, subclients might have more stuff to do yet.
this->state_ = espbt::ClientState::ESTABLISHED;
break;
}
ESP_LOGD(TAG, "[%d] [%s] Searching for services", this->connection_index_, this->address_str_.c_str());
// For V3_WITHOUT_CACHE, we already set fast params before connecting
// No need to update them again here
this->log_event_("Searching for services");
esp_ble_gattc_search_service(esp_gattc_if, param->cfg_mtu.conn_id, nullptr);
break;
}
@@ -332,8 +351,7 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
// Check if we were disconnected while waiting for service discovery
if (param->disconnect.reason == ESP_GATT_CONN_TERMINATE_PEER_USER &&
this->state_ == espbt::ClientState::CONNECTED) {
ESP_LOGW(TAG, "[%d] [%s] Disconnected by remote during service discovery", this->connection_index_,
this->address_str_.c_str());
this->log_warning_("Remote closed during discovery");
} else {
ESP_LOGD(TAG, "[%d] [%s] ESP_GATTC_DISCONNECT_EVT, reason 0x%02x", this->connection_index_,
this->address_str_.c_str(), param->disconnect.reason);
@@ -389,12 +407,11 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
if (this->conn_id_ != param->search_cmpl.conn_id)
return false;
this->log_gattc_event_("SEARCH_CMPL");
// For V3 connections, restore to medium connection parameters after service discovery
// For V3_WITHOUT_CACHE, switch back to medium connection parameters after service discovery
// This balances performance with bandwidth usage after the critical discovery phase
if (this->connection_type_ == espbt::ConnectionType::V3_WITHOUT_CACHE ||
this->connection_type_ == espbt::ConnectionType::V3_WITH_CACHE) {
this->restore_medium_conn_params_();
} else {
if (this->connection_type_ == espbt::ConnectionType::V3_WITHOUT_CACHE) {
this->update_conn_params_(MEDIUM_MIN_CONN_INTERVAL, MEDIUM_MAX_CONN_INTERVAL, 0, MEDIUM_CONN_TIMEOUT, "medium");
} else if (this->connection_type_ != espbt::ConnectionType::V3_WITH_CACHE) {
#ifdef USE_ESP32_BLE_DEVICE
for (auto &svc : this->services_) {
ESP_LOGV(TAG, "[%d] [%s] Service UUID: %s", this->connection_index_, this->address_str_.c_str(),
@@ -506,16 +523,14 @@ void BLEClientBase::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_
return;
esp_bd_addr_t bd_addr;
memcpy(bd_addr, param->ble_security.auth_cmpl.bd_addr, sizeof(esp_bd_addr_t));
ESP_LOGI(TAG, "[%d] [%s] auth complete. remote BD_ADDR: %s", this->connection_index_, this->address_str_.c_str(),
ESP_LOGI(TAG, "[%d] [%s] auth complete addr: %s", this->connection_index_, this->address_str_.c_str(),
format_hex(bd_addr, 6).c_str());
if (!param->ble_security.auth_cmpl.success) {
ESP_LOGE(TAG, "[%d] [%s] auth fail reason = 0x%x", this->connection_index_, this->address_str_.c_str(),
param->ble_security.auth_cmpl.fail_reason);
this->log_error_("auth fail reason", param->ble_security.auth_cmpl.fail_reason);
} else {
this->paired_ = true;
ESP_LOGD(TAG, "[%d] [%s] auth success. address type = %d auth mode = %d", this->connection_index_,
this->address_str_.c_str(), param->ble_security.auth_cmpl.addr_type,
param->ble_security.auth_cmpl.auth_mode);
ESP_LOGD(TAG, "[%d] [%s] auth success type = %d mode = %d", this->connection_index_, this->address_str_.c_str(),
param->ble_security.auth_cmpl.addr_type, param->ble_security.auth_cmpl.auth_mode);
}
break;

View File

@@ -133,10 +133,18 @@ class BLEClientBase : public espbt::ESPBTClient, public Component {
void log_event_(const char *name);
void log_gattc_event_(const char *name);
void restore_medium_conn_params_();
void update_conn_params_(uint16_t min_interval, uint16_t max_interval, uint16_t latency, uint16_t timeout,
const char *param_type);
void set_conn_params_(uint16_t min_interval, uint16_t max_interval, uint16_t latency, uint16_t timeout,
const char *param_type);
void log_gattc_warning_(const char *operation, esp_gatt_status_t status);
void log_gattc_warning_(const char *operation, esp_err_t err);
void log_connection_params_(const char *param_type);
void handle_connection_result_(esp_err_t ret);
// Compact error logging helpers to reduce flash usage
void log_error_(const char *message);
void log_error_(const char *message, int code);
void log_warning_(const char *message);
};
} // namespace esphome::esp32_ble_client

View File

@@ -529,6 +529,7 @@ async def to_code_characteristic(service_var, char_conf):
async def to_code(config):
# Register the loggers this component needs
esp32_ble.register_bt_logger(BTLoggers.GATT, BTLoggers.SMP)
cg.add_define("USE_ESP32_BLE_UUID")
var = cg.new_Pvariable(config[CONF_ID])

View File

@@ -373,6 +373,7 @@ async def _add_ble_features():
# Add feature-specific defines based on what's needed
if BLEFeatures.ESP_BT_DEVICE in _required_features:
cg.add_define("USE_ESP32_BLE_DEVICE")
cg.add_define("USE_ESP32_BLE_UUID")
ESP32_BLE_START_SCAN_ACTION_SCHEMA = cv.Schema(

View File

@@ -80,14 +80,17 @@ class BLEManufacturerDataAdvertiseTrigger : public Trigger<const adv_data_t &>,
ESPBTUUID uuid_;
};
#endif // USE_ESP32_BLE_DEVICE
class BLEEndOfScanTrigger : public Trigger<>, public ESPBTDeviceListener {
public:
explicit BLEEndOfScanTrigger(ESP32BLETracker *parent) { parent->register_listener(this); }
#ifdef USE_ESP32_BLE_DEVICE
bool parse_device(const ESPBTDevice &device) override { return false; }
#endif
void on_scan_end() override { this->trigger(); }
};
#endif // USE_ESP32_BLE_DEVICE
template<typename... Ts> class ESP32BLEStartScanAction : public Action<Ts...> {
public:

View File

@@ -33,10 +33,12 @@ enum AdvertisementParserType {
RAW_ADVERTISEMENTS,
};
#ifdef USE_ESP32_BLE_UUID
struct ServiceData {
ESPBTUUID uuid;
adv_data_t data;
};
#endif
#ifdef USE_ESP32_BLE_DEVICE
class ESPBLEiBeacon {

View File

@@ -100,8 +100,8 @@ void ESPHomeOTAComponent::handle_handshake_() {
/// Handle the initial OTA handshake.
///
/// This method is non-blocking and will return immediately if no data is available.
/// It waits for the first magic byte (0x6C) before proceeding to handle_data_().
/// A 10-second timeout is enforced from initial connection.
/// It reads all 5 magic bytes (0x6C, 0x26, 0xF7, 0x5C, 0x45) non-blocking
/// before proceeding to handle_data_(). A 10-second timeout is enforced from initial connection.
if (this->client_ == nullptr) {
// We already checked server_->ready() in loop(), so we can accept directly
@@ -126,6 +126,7 @@ void ESPHomeOTAComponent::handle_handshake_() {
}
this->log_start_("handshake");
this->client_connect_time_ = App.get_loop_component_start_time();
this->magic_buf_pos_ = 0; // Reset magic buffer position
}
// Check for handshake timeout
@@ -136,34 +137,47 @@ void ESPHomeOTAComponent::handle_handshake_() {
return;
}
// Try to read first byte of magic bytes
uint8_t first_byte;
ssize_t read = this->client_->read(&first_byte, 1);
// Try to read remaining magic bytes
if (this->magic_buf_pos_ < 5) {
// Read as many bytes as available
uint8_t bytes_to_read = 5 - this->magic_buf_pos_;
ssize_t read = this->client_->read(this->magic_buf_ + this->magic_buf_pos_, bytes_to_read);
if (read == -1 && (errno == EAGAIN || errno == EWOULDBLOCK)) {
return; // No data yet, try again next loop
}
if (read <= 0) {
// Error or connection closed
if (read == -1) {
this->log_socket_error_("reading first byte");
} else {
ESP_LOGW(TAG, "Remote closed during handshake");
if (read == -1 && (errno == EAGAIN || errno == EWOULDBLOCK)) {
return; // No data yet, try again next loop
}
this->cleanup_connection_();
return;
if (read <= 0) {
// Error or connection closed
if (read == -1) {
this->log_socket_error_("reading magic bytes");
} else {
ESP_LOGW(TAG, "Remote closed during handshake");
}
this->cleanup_connection_();
return;
}
this->magic_buf_pos_ += read;
}
// Got first byte, check if it's the magic byte
if (first_byte != 0x6C) {
ESP_LOGW(TAG, "Invalid initial byte: 0x%02X", first_byte);
this->cleanup_connection_();
return;
}
// Check if we have all 5 magic bytes
if (this->magic_buf_pos_ == 5) {
// Validate magic bytes
static const uint8_t MAGIC_BYTES[5] = {0x6C, 0x26, 0xF7, 0x5C, 0x45};
if (memcmp(this->magic_buf_, MAGIC_BYTES, 5) != 0) {
ESP_LOGW(TAG, "Magic bytes mismatch! 0x%02X-0x%02X-0x%02X-0x%02X-0x%02X", this->magic_buf_[0],
this->magic_buf_[1], this->magic_buf_[2], this->magic_buf_[3], this->magic_buf_[4]);
// Send error response (non-blocking, best effort)
uint8_t error = static_cast<uint8_t>(ota::OTA_RESPONSE_ERROR_MAGIC);
this->client_->write(&error, 1);
this->cleanup_connection_();
return;
}
// First byte is valid, continue with data handling
this->handle_data_();
// All 5 magic bytes are valid, continue with data handling
this->handle_data_();
}
}
void ESPHomeOTAComponent::handle_data_() {
@@ -186,18 +200,6 @@ void ESPHomeOTAComponent::handle_data_() {
size_t size_acknowledged = 0;
#endif
// Read remaining 4 bytes of magic (we already read the first byte 0x6C in handle_handshake_)
if (!this->readall_(buf, 4)) {
this->log_read_error_("magic bytes");
goto error; // NOLINT(cppcoreguidelines-avoid-goto)
}
// Check remaining magic bytes: 0x26, 0xF7, 0x5C, 0x45
if (buf[0] != 0x26 || buf[1] != 0xF7 || buf[2] != 0x5C || buf[3] != 0x45) {
ESP_LOGW(TAG, "Magic bytes mismatch! 0x6C-0x%02X-0x%02X-0x%02X-0x%02X", buf[0], buf[1], buf[2], buf[3]);
error_code = ota::OTA_RESPONSE_ERROR_MAGIC;
goto error; // NOLINT(cppcoreguidelines-avoid-goto)
}
// Send OK and version - 2 bytes
buf[0] = ota::OTA_RESPONSE_OK;
buf[1] = USE_OTA_VERSION;
@@ -487,6 +489,7 @@ void ESPHomeOTAComponent::cleanup_connection_() {
this->client_->close();
this->client_ = nullptr;
this->client_connect_time_ = 0;
this->magic_buf_pos_ = 0;
}
void ESPHomeOTAComponent::yield_and_feed_watchdog_() {

View File

@@ -41,11 +41,13 @@ class ESPHomeOTAComponent : public ota::OTAComponent {
std::string password_;
#endif // USE_OTA_PASSWORD
uint16_t port_;
uint32_t client_connect_time_{0};
std::unique_ptr<socket::Socket> server_;
std::unique_ptr<socket::Socket> client_;
uint32_t client_connect_time_{0};
uint16_t port_;
uint8_t magic_buf_[5];
uint8_t magic_buf_pos_{0};
};
} // namespace esphome

View File

@@ -208,11 +208,11 @@ void ESPNowComponent::enable_() {
esp_wifi_connectionless_module_set_wake_interval(CONFIG_ESPNOW_WAKE_INTERVAL);
#endif
this->state_ = ESPNOW_STATE_ENABLED;
for (auto peer : this->peers_) {
this->add_peer(peer.address);
}
this->state_ = ESPNOW_STATE_ENABLED;
}
void ESPNowComponent::disable() {
@@ -407,7 +407,7 @@ esp_err_t ESPNowComponent::add_peer(const uint8_t *peer) {
}
if (memcmp(peer, this->own_address_, ESP_NOW_ETH_ALEN) == 0) {
this->mark_failed();
this->status_momentary_warning("peer-add-failed");
return ESP_ERR_INVALID_MAC;
}

View File

@@ -9,9 +9,8 @@ static const char *const TAG = "hte501";
void HTE501Component::setup() {
uint8_t address[] = {0x70, 0x29};
this->write(address, 2, false);
uint8_t identification[9];
this->read(identification, 9);
this->write_read(address, sizeof address, identification, sizeof identification);
if (identification[8] != calc_crc8_(identification, 0, 7)) {
this->error_code_ = CRC_CHECK_FAILED;
this->mark_failed();
@@ -42,7 +41,7 @@ void HTE501Component::dump_config() {
float HTE501Component::get_setup_priority() const { return setup_priority::DATA; }
void HTE501Component::update() {
uint8_t address_1[] = {0x2C, 0x1B};
this->write(address_1, 2, true);
this->write(address_1, 2);
this->set_timeout(50, [this]() {
uint8_t i2c_response[6];
this->read(i2c_response, 6);

View File

@@ -1,7 +1,10 @@
#include "http_request_host.h"
#ifdef USE_HOST
#define USE_HTTP_REQUEST_HOST_H
#define CPPHTTPLIB_NO_EXCEPTIONS
#include "httplib.h"
#include "http_request_host.h"
#include <regex>
#include "esphome/components/network/util.h"
#include "esphome/components/watchdog/watchdog.h"

View File

@@ -1,11 +1,7 @@
#pragma once
#include "http_request.h"
#ifdef USE_HOST
#define CPPHTTPLIB_NO_EXCEPTIONS
#include "httplib.h"
#include "http_request.h"
namespace esphome {
namespace http_request {

View File

@@ -3,12 +3,10 @@
/**
* NOTE: This is a copy of httplib.h from https://github.com/yhirose/cpp-httplib
*
* It has been modified only to add ifdefs for USE_HOST. While it contains many functions unused in ESPHome,
* It has been modified to add ifdefs for USE_HOST. While it contains many functions unused in ESPHome,
* it was considered preferable to use it with as few changes as possible, to facilitate future updates.
*/
#include "esphome/core/defines.h"
//
// httplib.h
//
@@ -17,6 +15,11 @@
//
#ifdef USE_HOST
// Prevent this code being included in main.cpp
#ifdef USE_HTTP_REQUEST_HOST_H
#include "esphome/core/defines.h"
#ifndef CPPHTTPLIB_HTTPLIB_H
#define CPPHTTPLIB_HTTPLIB_H
@@ -9687,5 +9690,6 @@ inline SSL_CTX *Client::ssl_context() const {
#endif
#endif // CPPHTTPLIB_HTTPLIB_H
#endif // USE_HTTP_REQUEST_HOST_H
#endif

View File

@@ -2,7 +2,6 @@ import logging
from esphome import pins
import esphome.codegen as cg
from esphome.components import esp32
from esphome.config_helpers import filter_source_files_from_platform
import esphome.config_validation as cv
from esphome.const import (
@@ -14,8 +13,6 @@ from esphome.const import (
CONF_SCL,
CONF_SDA,
CONF_TIMEOUT,
KEY_CORE,
KEY_FRAMEWORK_VERSION,
PLATFORM_ESP32,
PLATFORM_ESP8266,
PLATFORM_RP2040,
@@ -48,28 +45,8 @@ def _bus_declare_type(value):
def validate_config(config):
if (
config[CONF_SCAN]
and CORE.is_esp32
and CORE.using_esp_idf
and esp32.get_esp32_variant()
in [
esp32.const.VARIANT_ESP32C5,
esp32.const.VARIANT_ESP32C6,
esp32.const.VARIANT_ESP32P4,
]
):
version: cv.Version = CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION]
if version.major == 5 and (
(version.minor == 3 and version.patch <= 3)
or (version.minor == 4 and version.patch <= 1)
):
LOGGER.warning(
"There is a bug in esp-idf version %s that breaks I2C scan, I2C scan "
"has been disabled, see https://github.com/esphome/issues/issues/7128",
str(version),
)
config[CONF_SCAN] = False
if CORE.using_esp_idf:
return cv.require_framework_version(esp_idf=cv.Version(5, 4, 2))(config)
return config

View File

@@ -1,4 +1,6 @@
#include "i2c.h"
#include "esphome/core/defines.h"
#include "esphome/core/log.h"
#include <memory>
@@ -7,38 +9,48 @@ namespace i2c {
static const char *const TAG = "i2c";
ErrorCode I2CDevice::read_register(uint8_t a_register, uint8_t *data, size_t len, bool stop) {
ErrorCode err = this->write(&a_register, 1, stop);
if (err != ERROR_OK)
return err;
return bus_->read(address_, data, len);
void I2CBus::i2c_scan_() {
// suppress logs from the IDF I2C library during the scan
#if defined(USE_ESP32) && defined(USE_LOGGER)
auto previous = esp_log_level_get("*");
esp_log_level_set("*", ESP_LOG_NONE);
#endif
for (uint8_t address = 8; address != 120; address++) {
auto err = write_readv(address, nullptr, 0, nullptr, 0);
if (err == ERROR_OK) {
scan_results_.emplace_back(address, true);
} else if (err == ERROR_UNKNOWN) {
scan_results_.emplace_back(address, false);
}
}
#if defined(USE_ESP32) && defined(USE_LOGGER)
esp_log_level_set("*", previous);
#endif
}
ErrorCode I2CDevice::read_register16(uint16_t a_register, uint8_t *data, size_t len, bool stop) {
ErrorCode I2CDevice::read_register(uint8_t a_register, uint8_t *data, size_t len) {
return bus_->write_readv(this->address_, &a_register, 1, data, len);
}
ErrorCode I2CDevice::read_register16(uint16_t a_register, uint8_t *data, size_t len) {
a_register = convert_big_endian(a_register);
ErrorCode const err = this->write(reinterpret_cast<const uint8_t *>(&a_register), 2, stop);
if (err != ERROR_OK)
return err;
return bus_->read(address_, data, len);
return bus_->write_readv(this->address_, reinterpret_cast<const uint8_t *>(&a_register), 2, data, len);
}
ErrorCode I2CDevice::write_register(uint8_t a_register, const uint8_t *data, size_t len, bool stop) {
WriteBuffer buffers[2];
buffers[0].data = &a_register;
buffers[0].len = 1;
buffers[1].data = data;
buffers[1].len = len;
return bus_->writev(address_, buffers, 2, stop);
ErrorCode I2CDevice::write_register(uint8_t a_register, const uint8_t *data, size_t len) const {
std::vector<uint8_t> v{};
v.push_back(a_register);
v.insert(v.end(), data, data + len);
return bus_->write_readv(this->address_, v.data(), v.size(), nullptr, 0);
}
ErrorCode I2CDevice::write_register16(uint16_t a_register, const uint8_t *data, size_t len, bool stop) {
a_register = convert_big_endian(a_register);
WriteBuffer buffers[2];
buffers[0].data = reinterpret_cast<const uint8_t *>(&a_register);
buffers[0].len = 2;
buffers[1].data = data;
buffers[1].len = len;
return bus_->writev(address_, buffers, 2, stop);
ErrorCode I2CDevice::write_register16(uint16_t a_register, const uint8_t *data, size_t len) const {
std::vector<uint8_t> v(len + 2);
v[0] = a_register >> 8;
v[1] = a_register;
std::copy(data, data + len, v.begin() + 2);
return bus_->write_readv(this->address_, v.data(), v.size(), nullptr, 0);
}
bool I2CDevice::read_bytes_16(uint8_t a_register, uint16_t *data, uint8_t len) {
@@ -49,7 +61,7 @@ bool I2CDevice::read_bytes_16(uint8_t a_register, uint16_t *data, uint8_t len) {
return true;
}
bool I2CDevice::write_bytes_16(uint8_t a_register, const uint16_t *data, uint8_t len) {
bool I2CDevice::write_bytes_16(uint8_t a_register, const uint16_t *data, uint8_t len) const {
// we have to copy in order to be able to change byte order
std::unique_ptr<uint16_t[]> temp{new uint16_t[len]};
for (size_t i = 0; i < len; i++)

View File

@@ -1,10 +1,10 @@
#pragma once
#include "i2c_bus.h"
#include "esphome/core/helpers.h"
#include "esphome/core/optional.h"
#include <array>
#include <vector>
#include "esphome/core/helpers.h"
#include "esphome/core/optional.h"
#include "i2c_bus.h"
namespace esphome {
namespace i2c {
@@ -161,51 +161,53 @@ class I2CDevice {
/// @param data pointer to an array to store the bytes
/// @param len length of the buffer = number of bytes to read
/// @return an i2c::ErrorCode
ErrorCode read(uint8_t *data, size_t len) { return bus_->read(address_, data, len); }
ErrorCode read(uint8_t *data, size_t len) const { return bus_->write_readv(this->address_, nullptr, 0, data, len); }
/// @brief reads an array of bytes from a specific register in the I²C device
/// @param a_register an 8 bits internal address of the I²C register to read from
/// @param data pointer to an array to store the bytes
/// @param len length of the buffer = number of bytes to read
/// @param stop (true/false): True will send a stop message, releasing the bus after
/// transmission. False will send a restart, keeping the connection active.
/// @return an i2c::ErrorCode
ErrorCode read_register(uint8_t a_register, uint8_t *data, size_t len, bool stop = true);
ErrorCode read_register(uint8_t a_register, uint8_t *data, size_t len);
/// @brief reads an array of bytes from a specific register in the I²C device
/// @param a_register the 16 bits internal address of the I²C register to read from
/// @param data pointer to an array of bytes to store the information
/// @param len length of the buffer = number of bytes to read
/// @param stop (true/false): True will send a stop message, releasing the bus after
/// transmission. False will send a restart, keeping the connection active.
/// @return an i2c::ErrorCode
ErrorCode read_register16(uint16_t a_register, uint8_t *data, size_t len, bool stop = true);
ErrorCode read_register16(uint16_t a_register, uint8_t *data, size_t len);
/// @brief writes an array of bytes to a device using an I2CBus
/// @param data pointer to an array that contains the bytes to send
/// @param len length of the buffer = number of bytes to write
/// @param stop (true/false): True will send a stop message, releasing the bus after
/// transmission. False will send a restart, keeping the connection active.
/// @return an i2c::ErrorCode
ErrorCode write(const uint8_t *data, size_t len, bool stop = true) { return bus_->write(address_, data, len, stop); }
ErrorCode write(const uint8_t *data, size_t len) const {
return bus_->write_readv(this->address_, data, len, nullptr, 0);
}
/// @brief writes an array of bytes to a device, then reads an array, as a single transaction
/// @param write_data pointer to an array that contains the bytes to send
/// @param write_len length of the buffer = number of bytes to write
/// @param read_data pointer to an array to store the bytes read
/// @param read_len length of the buffer = number of bytes to read
/// @return an i2c::ErrorCode
ErrorCode write_read(const uint8_t *write_data, size_t write_len, uint8_t *read_data, size_t read_len) const {
return bus_->write_readv(this->address_, write_data, write_len, read_data, read_len);
}
/// @brief writes an array of bytes to a specific register in the I²C device
/// @param a_register the internal address of the register to read from
/// @param data pointer to an array to store the bytes
/// @param len length of the buffer = number of bytes to read
/// @param stop (true/false): True will send a stop message, releasing the bus after
/// transmission. False will send a restart, keeping the connection active.
/// @return an i2c::ErrorCode
ErrorCode write_register(uint8_t a_register, const uint8_t *data, size_t len, bool stop = true);
ErrorCode write_register(uint8_t a_register, const uint8_t *data, size_t len) const;
/// @brief write an array of bytes to a specific register in the I²C device
/// @param a_register the 16 bits internal address of the register to read from
/// @param data pointer to an array to store the bytes
/// @param len length of the buffer = number of bytes to read
/// @param stop (true/false): True will send a stop message, releasing the bus after
/// transmission. False will send a restart, keeping the connection active.
/// @return an i2c::ErrorCode
ErrorCode write_register16(uint16_t a_register, const uint8_t *data, size_t len, bool stop = true);
ErrorCode write_register16(uint16_t a_register, const uint8_t *data, size_t len) const;
///
/// Compat APIs
@@ -217,7 +219,7 @@ class I2CDevice {
return read_register(a_register, data, len) == ERROR_OK;
}
bool read_bytes_raw(uint8_t *data, uint8_t len) { return read(data, len) == ERROR_OK; }
bool read_bytes_raw(uint8_t *data, uint8_t len) const { return read(data, len) == ERROR_OK; }
template<size_t N> optional<std::array<uint8_t, N>> read_bytes(uint8_t a_register) {
std::array<uint8_t, N> res;
@@ -236,9 +238,7 @@ class I2CDevice {
bool read_bytes_16(uint8_t a_register, uint16_t *data, uint8_t len);
bool read_byte(uint8_t a_register, uint8_t *data, bool stop = true) {
return read_register(a_register, data, 1, stop) == ERROR_OK;
}
bool read_byte(uint8_t a_register, uint8_t *data) { return read_register(a_register, data, 1) == ERROR_OK; }
optional<uint8_t> read_byte(uint8_t a_register) {
uint8_t data;
@@ -249,11 +249,11 @@ class I2CDevice {
bool read_byte_16(uint8_t a_register, uint16_t *data) { return read_bytes_16(a_register, data, 1); }
bool write_bytes(uint8_t a_register, const uint8_t *data, uint8_t len, bool stop = true) {
return write_register(a_register, data, len, stop) == ERROR_OK;
bool write_bytes(uint8_t a_register, const uint8_t *data, uint8_t len) const {
return write_register(a_register, data, len) == ERROR_OK;
}
bool write_bytes(uint8_t a_register, const std::vector<uint8_t> &data) {
bool write_bytes(uint8_t a_register, const std::vector<uint8_t> &data) const {
return write_bytes(a_register, data.data(), data.size());
}
@@ -261,13 +261,42 @@ class I2CDevice {
return write_bytes(a_register, data.data(), data.size());
}
bool write_bytes_16(uint8_t a_register, const uint16_t *data, uint8_t len);
bool write_bytes_16(uint8_t a_register, const uint16_t *data, uint8_t len) const;
bool write_byte(uint8_t a_register, uint8_t data, bool stop = true) {
return write_bytes(a_register, &data, 1, stop);
bool write_byte(uint8_t a_register, uint8_t data) const { return write_bytes(a_register, &data, 1); }
bool write_byte_16(uint8_t a_register, uint16_t data) const { return write_bytes_16(a_register, &data, 1); }
// Deprecated functions
ESPDEPRECATED("The stop argument is no longer used. This will be removed from ESPHome 2026.3.0", "2025.9.0")
ErrorCode read_register(uint8_t a_register, uint8_t *data, size_t len, bool stop) {
return this->read_register(a_register, data, len);
}
bool write_byte_16(uint8_t a_register, uint16_t data) { return write_bytes_16(a_register, &data, 1); }
ESPDEPRECATED("The stop argument is no longer used. This will be removed from ESPHome 2026.3.0", "2025.9.0")
ErrorCode read_register16(uint16_t a_register, uint8_t *data, size_t len, bool stop) {
return this->read_register16(a_register, data, len);
}
ESPDEPRECATED("The stop argument is no longer used; use write_read() for consecutive write and read. This will be "
"removed from ESPHome 2026.3.0",
"2025.9.0")
ErrorCode write(const uint8_t *data, size_t len, bool stop) const { return this->write(data, len); }
ESPDEPRECATED("The stop argument is no longer used; use write_read() for consecutive write and read. This will be "
"removed from ESPHome 2026.3.0",
"2025.9.0")
ErrorCode write_register(uint8_t a_register, const uint8_t *data, size_t len, bool stop) const {
return this->write_register(a_register, data, len);
}
ESPDEPRECATED("The stop argument is no longer used; use write_read() for consecutive write and read. This will be "
"removed from ESPHome 2026.3.0",
"2025.9.0")
ErrorCode write_register16(uint16_t a_register, const uint8_t *data, size_t len, bool stop) const {
return this->write_register16(a_register, data, len);
}
protected:
uint8_t address_{0x00}; ///< store the address of the device on the bus

View File

@@ -1,9 +1,12 @@
#pragma once
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <utility>
#include <vector>
#include "esphome/core/helpers.h"
namespace esphome {
namespace i2c {
@@ -39,71 +42,66 @@ struct WriteBuffer {
/// note https://www.nxp.com/docs/en/application-note/AN10216.pdf
class I2CBus {
public:
/// @brief Creates a ReadBuffer and calls the virtual readv() method to read bytes into this buffer
/// @param address address of the I²C component on the i2c bus
/// @param buffer pointer to an array of bytes that will be used to store the data received
/// @param len length of the buffer = number of bytes to read
/// @return an i2c::ErrorCode
virtual ErrorCode read(uint8_t address, uint8_t *buffer, size_t len) {
ReadBuffer buf;
buf.data = buffer;
buf.len = len;
return readv(address, &buf, 1);
}
virtual ~I2CBus() = default;
/// @brief This virtual method reads bytes from an I2CBus into an array of ReadBuffer.
/// @param address address of the I²C component on the i2c bus
/// @param buffers pointer to an array of ReadBuffer
/// @param count number of ReadBuffer to read
/// @return an i2c::ErrorCode
/// @details This is a pure virtual method that must be implemented in a subclass.
virtual ErrorCode readv(uint8_t address, ReadBuffer *buffers, size_t count) = 0;
virtual ErrorCode write(uint8_t address, const uint8_t *buffer, size_t len) {
return write(address, buffer, len, true);
}
/// @brief Creates a WriteBuffer and calls the writev() method to send the bytes from this buffer
/// @param address address of the I²C component on the i2c bus
/// @param buffer pointer to an array of bytes that contains the data to be sent
/// @param len length of the buffer = number of bytes to write
/// @param stop true or false: True will send a stop message, releasing the bus after
/// transmission. False will send a restart, keeping the connection active.
/// @return an i2c::ErrorCode
virtual ErrorCode write(uint8_t address, const uint8_t *buffer, size_t len, bool stop) {
WriteBuffer buf;
buf.data = buffer;
buf.len = len;
return writev(address, &buf, 1, stop);
}
virtual ErrorCode writev(uint8_t address, WriteBuffer *buffers, size_t cnt) {
return writev(address, buffers, cnt, true);
}
/// @brief This virtual method writes bytes to an I2CBus from an array of WriteBuffer.
/// @param address address of the I²C component on the i2c bus
/// @param buffers pointer to an array of WriteBuffer
/// @param count number of WriteBuffer to write
/// @param stop true or false: True will send a stop message, releasing the bus after
/// @brief This virtual method writes bytes to an I2CBus from an array,
/// then reads bytes into an array of ReadBuffer.
/// @param address address of the I²C device on the i2c bus
/// @param write_buffer pointer to data
/// @param write_count number of bytes to write
/// @param read_buffer pointer to an array to receive data
/// @param read_count number of bytes to read
/// transmission. False will send a restart, keeping the connection active.
/// @return an i2c::ErrorCode
/// @details This is a pure virtual method that must be implemented in the subclass.
virtual ErrorCode writev(uint8_t address, WriteBuffer *buffers, size_t count, bool stop) = 0;
virtual ErrorCode write_readv(uint8_t address, const uint8_t *write_buffer, size_t write_count, uint8_t *read_buffer,
size_t read_count) = 0;
// Legacy functions for compatibility
ErrorCode read(uint8_t address, uint8_t *buffer, size_t len) {
return this->write_readv(address, nullptr, 0, buffer, len);
}
ErrorCode write(uint8_t address, const uint8_t *buffer, size_t len, bool stop = true) {
return this->write_readv(address, buffer, len, nullptr, 0);
}
ESPDEPRECATED("This method is deprecated and will be removed in ESPHome 2026.3.0. Use write_readv() instead.",
"2025.9.0")
ErrorCode readv(uint8_t address, ReadBuffer *read_buffers, size_t count) {
size_t total_len = 0;
for (size_t i = 0; i != count; i++) {
total_len += read_buffers[i].len;
}
std::vector<uint8_t> buffer(total_len);
auto err = this->write_readv(address, nullptr, 0, buffer.data(), total_len);
if (err != ERROR_OK)
return err;
size_t pos = 0;
for (size_t i = 0; i != count; i++) {
if (read_buffers[i].len != 0) {
std::memcpy(read_buffers[i].data, buffer.data() + pos, read_buffers[i].len);
pos += read_buffers[i].len;
}
}
return ERROR_OK;
}
ESPDEPRECATED("This method is deprecated and will be removed in ESPHome 2026.3.0. Use write_readv() instead.",
"2025.9.0")
ErrorCode writev(uint8_t address, const WriteBuffer *write_buffers, size_t count, bool stop = true) {
std::vector<uint8_t> buffer{};
for (size_t i = 0; i != count; i++) {
buffer.insert(buffer.end(), write_buffers[i].data, write_buffers[i].data + write_buffers[i].len);
}
return this->write_readv(address, buffer.data(), buffer.size(), nullptr, 0);
}
protected:
/// @brief Scans the I2C bus for devices. Devices presence is kept in an array of std::pair
/// that contains the address and the corresponding bool presence flag.
virtual void i2c_scan() {
for (uint8_t address = 8; address < 120; address++) {
auto err = writev(address, nullptr, 0);
if (err == ERROR_OK) {
scan_results_.emplace_back(address, true);
} else if (err == ERROR_UNKNOWN) {
scan_results_.emplace_back(address, false);
}
}
}
void i2c_scan_();
std::vector<std::pair<uint8_t, bool>> scan_results_; ///< array containing scan results
bool scan_{false}; ///< Should we scan ? Can be set in the yaml
};

View File

@@ -41,7 +41,7 @@ void ArduinoI2CBus::setup() {
this->initialized_ = true;
if (this->scan_) {
ESP_LOGV(TAG, "Scanning bus for active devices");
this->i2c_scan();
this->i2c_scan_();
}
}
@@ -111,88 +111,37 @@ void ArduinoI2CBus::dump_config() {
}
}
ErrorCode ArduinoI2CBus::readv(uint8_t address, ReadBuffer *buffers, size_t cnt) {
ErrorCode ArduinoI2CBus::write_readv(uint8_t address, const uint8_t *write_buffer, size_t write_count,
uint8_t *read_buffer, size_t read_count) {
#if defined(USE_ESP8266)
this->set_pins_and_clock_(); // reconfigure Wire global state in case there are multiple instances
#endif
// logging is only enabled with vv level, if warnings are shown the caller
// should log them
if (!initialized_) {
ESP_LOGVV(TAG, "i2c bus not initialized!");
return ERROR_NOT_INITIALIZED;
}
size_t to_request = 0;
for (size_t i = 0; i < cnt; i++)
to_request += buffers[i].len;
size_t ret = wire_->requestFrom(address, to_request, true);
if (ret != to_request) {
ESP_LOGVV(TAG, "RX %u from %02X failed with error %u", to_request, address, ret);
return ERROR_TIMEOUT;
}
for (size_t i = 0; i < cnt; i++) {
const auto &buf = buffers[i];
for (size_t j = 0; j < buf.len; j++)
buf.data[j] = wire_->read();
}
#ifdef ESPHOME_LOG_HAS_VERY_VERBOSE
char debug_buf[4];
std::string debug_hex;
for (size_t i = 0; i < cnt; i++) {
const auto &buf = buffers[i];
for (size_t j = 0; j < buf.len; j++) {
snprintf(debug_buf, sizeof(debug_buf), "%02X", buf.data[j]);
debug_hex += debug_buf;
}
}
ESP_LOGVV(TAG, "0x%02X RX %s", address, debug_hex.c_str());
#endif
return ERROR_OK;
}
ErrorCode ArduinoI2CBus::writev(uint8_t address, WriteBuffer *buffers, size_t cnt, bool stop) {
#if defined(USE_ESP8266)
this->set_pins_and_clock_(); // reconfigure Wire global state in case there are multiple instances
#endif
// logging is only enabled with vv level, if warnings are shown the caller
// should log them
if (!initialized_) {
ESP_LOGVV(TAG, "i2c bus not initialized!");
ESP_LOGD(TAG, "i2c bus not initialized!");
return ERROR_NOT_INITIALIZED;
}
#ifdef ESPHOME_LOG_HAS_VERY_VERBOSE
char debug_buf[4];
std::string debug_hex;
ESP_LOGV(TAG, "0x%02X TX %s", address, format_hex_pretty(write_buffer, write_count).c_str());
for (size_t i = 0; i < cnt; i++) {
const auto &buf = buffers[i];
for (size_t j = 0; j < buf.len; j++) {
snprintf(debug_buf, sizeof(debug_buf), "%02X", buf.data[j]);
debug_hex += debug_buf;
}
}
ESP_LOGVV(TAG, "0x%02X TX %s", address, debug_hex.c_str());
#endif
wire_->beginTransmission(address);
size_t written = 0;
for (size_t i = 0; i < cnt; i++) {
const auto &buf = buffers[i];
if (buf.len == 0)
continue;
size_t ret = wire_->write(buf.data, buf.len);
written += ret;
if (ret != buf.len) {
ESP_LOGVV(TAG, "TX failed at %u", written);
uint8_t status = 0;
if (write_count != 0 || read_count == 0) {
wire_->beginTransmission(address);
size_t ret = wire_->write(write_buffer, write_count);
if (ret != write_count) {
ESP_LOGV(TAG, "TX failed");
return ERROR_UNKNOWN;
}
status = wire_->endTransmission(read_count == 0);
}
if (status == 0 && read_count != 0) {
size_t ret2 = wire_->requestFrom(address, read_count, true);
if (ret2 != read_count) {
ESP_LOGVV(TAG, "RX %u from %02X failed with error %u", read_count, address, ret2);
return ERROR_TIMEOUT;
}
for (size_t j = 0; j != read_count; j++)
read_buffer[j] = wire_->read();
}
uint8_t status = wire_->endTransmission(stop);
switch (status) {
case 0:
return ERROR_OK;

View File

@@ -19,8 +19,8 @@ class ArduinoI2CBus : public InternalI2CBus, public Component {
public:
void setup() override;
void dump_config() override;
ErrorCode readv(uint8_t address, ReadBuffer *buffers, size_t cnt) override;
ErrorCode writev(uint8_t address, WriteBuffer *buffers, size_t cnt, bool stop) override;
ErrorCode write_readv(uint8_t address, const uint8_t *write_buffer, size_t write_count, uint8_t *read_buffer,
size_t read_count) override;
float get_setup_priority() const override { return setup_priority::BUS; }
void set_scan(bool scan) { scan_ = scan; }

View File

@@ -1,6 +1,7 @@
#ifdef USE_ESP_IDF
#include "i2c_bus_esp_idf.h"
#include <driver/gpio.h>
#include <cinttypes>
#include <cstring>
@@ -9,10 +10,6 @@
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 3, 0)
#define SOC_HP_I2C_NUM SOC_I2C_NUM
#endif
namespace esphome {
namespace i2c {
@@ -34,7 +31,6 @@ void IDFI2CBus::setup() {
this->recover_();
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 4, 2)
next_port = (i2c_port_t) (next_port + 1);
i2c_master_bus_config_t bus_conf{};
@@ -77,56 +73,8 @@ void IDFI2CBus::setup() {
if (this->scan_) {
ESP_LOGV(TAG, "Scanning for devices");
this->i2c_scan();
this->i2c_scan_();
}
#else
#if SOC_HP_I2C_NUM > 1
next_port = (next_port == I2C_NUM_0) ? I2C_NUM_1 : I2C_NUM_MAX;
#else
next_port = I2C_NUM_MAX;
#endif
i2c_config_t conf{};
memset(&conf, 0, sizeof(conf));
conf.mode = I2C_MODE_MASTER;
conf.sda_io_num = sda_pin_;
conf.sda_pullup_en = sda_pullup_enabled_;
conf.scl_io_num = scl_pin_;
conf.scl_pullup_en = scl_pullup_enabled_;
conf.master.clk_speed = frequency_;
#ifdef USE_ESP32_VARIANT_ESP32S2
// workaround for https://github.com/esphome/issues/issues/6718
conf.clk_flags = I2C_SCLK_SRC_FLAG_AWARE_DFS;
#endif
esp_err_t err = i2c_param_config(port_, &conf);
if (err != ESP_OK) {
ESP_LOGW(TAG, "i2c_param_config failed: %s", esp_err_to_name(err));
this->mark_failed();
return;
}
if (timeout_ > 0) {
err = i2c_set_timeout(port_, timeout_ * 80); // unit: APB 80MHz clock cycle
if (err != ESP_OK) {
ESP_LOGW(TAG, "i2c_set_timeout failed: %s", esp_err_to_name(err));
this->mark_failed();
return;
} else {
ESP_LOGV(TAG, "i2c_timeout set to %" PRIu32 " ticks (%" PRIu32 " us)", timeout_ * 80, timeout_);
}
}
err = i2c_driver_install(port_, I2C_MODE_MASTER, 0, 0, 0);
if (err != ESP_OK) {
ESP_LOGW(TAG, "i2c_driver_install failed: %s", esp_err_to_name(err));
this->mark_failed();
return;
}
initialized_ = true;
if (this->scan_) {
ESP_LOGV(TAG, "Scanning bus for active devices");
this->i2c_scan();
}
#endif
}
void IDFI2CBus::dump_config() {
@@ -166,267 +114,73 @@ void IDFI2CBus::dump_config() {
}
}
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 4, 2)
void IDFI2CBus::i2c_scan() {
for (uint8_t address = 8; address < 120; address++) {
auto err = i2c_master_probe(this->bus_, address, 20);
if (err == ESP_OK) {
this->scan_results_.emplace_back(address, true);
}
}
}
#endif
ErrorCode IDFI2CBus::readv(uint8_t address, ReadBuffer *buffers, size_t cnt) {
// logging is only enabled with vv level, if warnings are shown the caller
ErrorCode IDFI2CBus::write_readv(uint8_t address, const uint8_t *write_buffer, size_t write_count, uint8_t *read_buffer,
size_t read_count) {
// logging is only enabled with v level, if warnings are shown the caller
// should log them
if (!initialized_) {
ESP_LOGVV(TAG, "i2c bus not initialized!");
ESP_LOGW(TAG, "i2c bus not initialized!");
return ERROR_NOT_INITIALIZED;
}
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 4, 2)
i2c_operation_job_t jobs[cnt + 4];
uint8_t read = (address << 1) | I2C_MASTER_READ;
size_t last = 0, num = 0;
jobs[num].command = I2C_MASTER_CMD_START;
num++;
jobs[num].command = I2C_MASTER_CMD_WRITE;
jobs[num].write.ack_check = true;
jobs[num].write.data = &read;
jobs[num].write.total_bytes = 1;
num++;
// find the last valid index
for (size_t i = 0; i < cnt; i++) {
const auto &buf = buffers[i];
if (buf.len == 0) {
continue;
i2c_operation_job_t jobs[8]{};
size_t num_jobs = 0;
uint8_t write_addr = (address << 1) | I2C_MASTER_WRITE;
uint8_t read_addr = (address << 1) | I2C_MASTER_READ;
ESP_LOGV(TAG, "Writing %zu bytes, reading %zu bytes", write_count, read_count);
if (read_count == 0 && write_count == 0) {
// basically just a bus probe. Send a start, address and stop
ESP_LOGV(TAG, "0x%02X BUS PROBE", address);
jobs[num_jobs++].command = I2C_MASTER_CMD_START;
jobs[num_jobs].command = I2C_MASTER_CMD_WRITE;
jobs[num_jobs].write.ack_check = true;
jobs[num_jobs].write.data = &write_addr;
jobs[num_jobs++].write.total_bytes = 1;
} else {
if (write_count != 0) {
ESP_LOGV(TAG, "0x%02X TX %s", address, format_hex_pretty(write_buffer, write_count).c_str());
jobs[num_jobs++].command = I2C_MASTER_CMD_START;
jobs[num_jobs].command = I2C_MASTER_CMD_WRITE;
jobs[num_jobs].write.ack_check = true;
jobs[num_jobs].write.data = &write_addr;
jobs[num_jobs++].write.total_bytes = 1;
jobs[num_jobs].command = I2C_MASTER_CMD_WRITE;
jobs[num_jobs].write.ack_check = true;
jobs[num_jobs].write.data = (uint8_t *) write_buffer;
jobs[num_jobs++].write.total_bytes = write_count;
}
last = i;
}
for (size_t i = 0; i < cnt; i++) {
const auto &buf = buffers[i];
if (buf.len == 0) {
continue;
}
if (i == last) {
// the last byte read before stop should always be a nack,
// split the last read if len is larger than 1
if (buf.len > 1) {
jobs[num].command = I2C_MASTER_CMD_READ;
jobs[num].read.ack_value = I2C_ACK_VAL;
jobs[num].read.data = (uint8_t *) buf.data;
jobs[num].read.total_bytes = buf.len - 1;
num++;
if (read_count != 0) {
ESP_LOGV(TAG, "0x%02X RX bytes %zu", address, read_count);
jobs[num_jobs++].command = I2C_MASTER_CMD_START;
jobs[num_jobs].command = I2C_MASTER_CMD_WRITE;
jobs[num_jobs].write.ack_check = true;
jobs[num_jobs].write.data = &read_addr;
jobs[num_jobs++].write.total_bytes = 1;
if (read_count > 1) {
jobs[num_jobs].command = I2C_MASTER_CMD_READ;
jobs[num_jobs].read.ack_value = I2C_ACK_VAL;
jobs[num_jobs].read.data = read_buffer;
jobs[num_jobs++].read.total_bytes = read_count - 1;
}
jobs[num].command = I2C_MASTER_CMD_READ;
jobs[num].read.ack_value = I2C_NACK_VAL;
jobs[num].read.data = (uint8_t *) buf.data + buf.len - 1;
jobs[num].read.total_bytes = 1;
num++;
} else {
jobs[num].command = I2C_MASTER_CMD_READ;
jobs[num].read.ack_value = I2C_ACK_VAL;
jobs[num].read.data = (uint8_t *) buf.data;
jobs[num].read.total_bytes = buf.len;
num++;
jobs[num_jobs].command = I2C_MASTER_CMD_READ;
jobs[num_jobs].read.ack_value = I2C_NACK_VAL;
jobs[num_jobs].read.data = read_buffer + read_count - 1;
jobs[num_jobs++].read.total_bytes = 1;
}
}
jobs[num].command = I2C_MASTER_CMD_STOP;
num++;
esp_err_t err = i2c_master_execute_defined_operations(this->dev_, jobs, num, 20);
jobs[num_jobs++].command = I2C_MASTER_CMD_STOP;
ESP_LOGV(TAG, "Sending %zu jobs", num_jobs);
esp_err_t err = i2c_master_execute_defined_operations(this->dev_, jobs, num_jobs, 20);
if (err == ESP_ERR_INVALID_STATE) {
ESP_LOGVV(TAG, "RX from %02X failed: not acked", address);
ESP_LOGV(TAG, "TX to %02X failed: not acked", address);
return ERROR_NOT_ACKNOWLEDGED;
} else if (err == ESP_ERR_TIMEOUT) {
ESP_LOGVV(TAG, "RX from %02X failed: timeout", address);
ESP_LOGV(TAG, "TX to %02X failed: timeout", address);
return ERROR_TIMEOUT;
} else if (err != ESP_OK) {
ESP_LOGVV(TAG, "RX from %02X failed: %s", address, esp_err_to_name(err));
ESP_LOGV(TAG, "TX to %02X failed: %s", address, esp_err_to_name(err));
return ERROR_UNKNOWN;
}
#else
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
esp_err_t err = i2c_master_start(cmd);
if (err != ESP_OK) {
ESP_LOGVV(TAG, "RX from %02X master start failed: %s", address, esp_err_to_name(err));
i2c_cmd_link_delete(cmd);
return ERROR_UNKNOWN;
}
err = i2c_master_write_byte(cmd, (address << 1) | I2C_MASTER_READ, true);
if (err != ESP_OK) {
ESP_LOGVV(TAG, "RX from %02X address write failed: %s", address, esp_err_to_name(err));
i2c_cmd_link_delete(cmd);
return ERROR_UNKNOWN;
}
for (size_t i = 0; i < cnt; i++) {
const auto &buf = buffers[i];
if (buf.len == 0)
continue;
err = i2c_master_read(cmd, buf.data, buf.len, i == cnt - 1 ? I2C_MASTER_LAST_NACK : I2C_MASTER_ACK);
if (err != ESP_OK) {
ESP_LOGVV(TAG, "RX from %02X data read failed: %s", address, esp_err_to_name(err));
i2c_cmd_link_delete(cmd);
return ERROR_UNKNOWN;
}
}
err = i2c_master_stop(cmd);
if (err != ESP_OK) {
ESP_LOGVV(TAG, "RX from %02X stop failed: %s", address, esp_err_to_name(err));
i2c_cmd_link_delete(cmd);
return ERROR_UNKNOWN;
}
err = i2c_master_cmd_begin(port_, cmd, 20 / portTICK_PERIOD_MS);
// i2c_master_cmd_begin() will block for a whole second if no ack:
// https://github.com/espressif/esp-idf/issues/4999
i2c_cmd_link_delete(cmd);
if (err == ESP_FAIL) {
// transfer not acked
ESP_LOGVV(TAG, "RX from %02X failed: not acked", address);
return ERROR_NOT_ACKNOWLEDGED;
} else if (err == ESP_ERR_TIMEOUT) {
ESP_LOGVV(TAG, "RX from %02X failed: timeout", address);
return ERROR_TIMEOUT;
} else if (err != ESP_OK) {
ESP_LOGVV(TAG, "RX from %02X failed: %s", address, esp_err_to_name(err));
return ERROR_UNKNOWN;
}
#endif
#ifdef ESPHOME_LOG_HAS_VERY_VERBOSE
char debug_buf[4];
std::string debug_hex;
for (size_t i = 0; i < cnt; i++) {
const auto &buf = buffers[i];
for (size_t j = 0; j < buf.len; j++) {
snprintf(debug_buf, sizeof(debug_buf), "%02X", buf.data[j]);
debug_hex += debug_buf;
}
}
ESP_LOGVV(TAG, "0x%02X RX %s", address, debug_hex.c_str());
#endif
return ERROR_OK;
}
ErrorCode IDFI2CBus::writev(uint8_t address, WriteBuffer *buffers, size_t cnt, bool stop) {
// logging is only enabled with vv level, if warnings are shown the caller
// should log them
if (!initialized_) {
ESP_LOGVV(TAG, "i2c bus not initialized!");
return ERROR_NOT_INITIALIZED;
}
#ifdef ESPHOME_LOG_HAS_VERY_VERBOSE
char debug_buf[4];
std::string debug_hex;
for (size_t i = 0; i < cnt; i++) {
const auto &buf = buffers[i];
for (size_t j = 0; j < buf.len; j++) {
snprintf(debug_buf, sizeof(debug_buf), "%02X", buf.data[j]);
debug_hex += debug_buf;
}
}
ESP_LOGVV(TAG, "0x%02X TX %s", address, debug_hex.c_str());
#endif
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 4, 2)
i2c_operation_job_t jobs[cnt + 3];
uint8_t write = (address << 1) | I2C_MASTER_WRITE;
size_t num = 0;
jobs[num].command = I2C_MASTER_CMD_START;
num++;
jobs[num].command = I2C_MASTER_CMD_WRITE;
jobs[num].write.ack_check = true;
jobs[num].write.data = &write;
jobs[num].write.total_bytes = 1;
num++;
for (size_t i = 0; i < cnt; i++) {
const auto &buf = buffers[i];
if (buf.len == 0) {
continue;
}
jobs[num].command = I2C_MASTER_CMD_WRITE;
jobs[num].write.ack_check = true;
jobs[num].write.data = (uint8_t *) buf.data;
jobs[num].write.total_bytes = buf.len;
num++;
}
if (stop) {
jobs[num].command = I2C_MASTER_CMD_STOP;
num++;
}
esp_err_t err = i2c_master_execute_defined_operations(this->dev_, jobs, num, 20);
if (err == ESP_ERR_INVALID_STATE) {
ESP_LOGVV(TAG, "TX to %02X failed: not acked", address);
return ERROR_NOT_ACKNOWLEDGED;
} else if (err == ESP_ERR_TIMEOUT) {
ESP_LOGVV(TAG, "TX to %02X failed: timeout", address);
return ERROR_TIMEOUT;
} else if (err != ESP_OK) {
ESP_LOGVV(TAG, "TX to %02X failed: %s", address, esp_err_to_name(err));
return ERROR_UNKNOWN;
}
#else
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
esp_err_t err = i2c_master_start(cmd);
if (err != ESP_OK) {
ESP_LOGVV(TAG, "TX to %02X master start failed: %s", address, esp_err_to_name(err));
i2c_cmd_link_delete(cmd);
return ERROR_UNKNOWN;
}
err = i2c_master_write_byte(cmd, (address << 1) | I2C_MASTER_WRITE, true);
if (err != ESP_OK) {
ESP_LOGVV(TAG, "TX to %02X address write failed: %s", address, esp_err_to_name(err));
i2c_cmd_link_delete(cmd);
return ERROR_UNKNOWN;
}
for (size_t i = 0; i < cnt; i++) {
const auto &buf = buffers[i];
if (buf.len == 0)
continue;
err = i2c_master_write(cmd, buf.data, buf.len, true);
if (err != ESP_OK) {
ESP_LOGVV(TAG, "TX to %02X data write failed: %s", address, esp_err_to_name(err));
i2c_cmd_link_delete(cmd);
return ERROR_UNKNOWN;
}
}
if (stop) {
err = i2c_master_stop(cmd);
if (err != ESP_OK) {
ESP_LOGVV(TAG, "TX to %02X master stop failed: %s", address, esp_err_to_name(err));
i2c_cmd_link_delete(cmd);
return ERROR_UNKNOWN;
}
}
err = i2c_master_cmd_begin(port_, cmd, 20 / portTICK_PERIOD_MS);
i2c_cmd_link_delete(cmd);
if (err == ESP_FAIL) {
// transfer not acked
ESP_LOGVV(TAG, "TX to %02X failed: not acked", address);
return ERROR_NOT_ACKNOWLEDGED;
} else if (err == ESP_ERR_TIMEOUT) {
ESP_LOGVV(TAG, "TX to %02X failed: timeout", address);
return ERROR_TIMEOUT;
} else if (err != ESP_OK) {
ESP_LOGVV(TAG, "TX to %02X failed: %s", address, esp_err_to_name(err));
return ERROR_UNKNOWN;
}
#endif
return ERROR_OK;
}
@@ -436,8 +190,8 @@ ErrorCode IDFI2CBus::writev(uint8_t address, WriteBuffer *buffers, size_t cnt, b
void IDFI2CBus::recover_() {
ESP_LOGI(TAG, "Performing bus recovery");
const gpio_num_t scl_pin = static_cast<gpio_num_t>(scl_pin_);
const gpio_num_t sda_pin = static_cast<gpio_num_t>(sda_pin_);
const auto scl_pin = static_cast<gpio_num_t>(scl_pin_);
const auto sda_pin = static_cast<gpio_num_t>(sda_pin_);
// For the upcoming operations, target for a 60kHz toggle frequency.
// 1000kHz is the maximum frequency for I2C running in standard-mode,
@@ -545,5 +299,4 @@ void IDFI2CBus::recover_() {
} // namespace i2c
} // namespace esphome
#endif // USE_ESP_IDF

View File

@@ -2,14 +2,9 @@
#ifdef USE_ESP_IDF
#include "esp_idf_version.h"
#include "esphome/core/component.h"
#include "i2c_bus.h"
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 4, 2)
#include <driver/i2c_master.h>
#else
#include <driver/i2c.h>
#endif
namespace esphome {
namespace i2c {
@@ -24,36 +19,33 @@ class IDFI2CBus : public InternalI2CBus, public Component {
public:
void setup() override;
void dump_config() override;
ErrorCode readv(uint8_t address, ReadBuffer *buffers, size_t cnt) override;
ErrorCode writev(uint8_t address, WriteBuffer *buffers, size_t cnt, bool stop) override;
ErrorCode write_readv(uint8_t address, const uint8_t *write_buffer, size_t write_count, uint8_t *read_buffer,
size_t read_count) override;
float get_setup_priority() const override { return setup_priority::BUS; }
void set_scan(bool scan) { scan_ = scan; }
void set_sda_pin(uint8_t sda_pin) { sda_pin_ = sda_pin; }
void set_sda_pullup_enabled(bool sda_pullup_enabled) { sda_pullup_enabled_ = sda_pullup_enabled; }
void set_scl_pin(uint8_t scl_pin) { scl_pin_ = scl_pin; }
void set_scl_pullup_enabled(bool scl_pullup_enabled) { scl_pullup_enabled_ = scl_pullup_enabled; }
void set_frequency(uint32_t frequency) { frequency_ = frequency; }
void set_timeout(uint32_t timeout) { timeout_ = timeout; }
void set_scan(bool scan) { this->scan_ = scan; }
void set_sda_pin(uint8_t sda_pin) { this->sda_pin_ = sda_pin; }
void set_sda_pullup_enabled(bool sda_pullup_enabled) { this->sda_pullup_enabled_ = sda_pullup_enabled; }
void set_scl_pin(uint8_t scl_pin) { this->scl_pin_ = scl_pin; }
void set_scl_pullup_enabled(bool scl_pullup_enabled) { this->scl_pullup_enabled_ = scl_pullup_enabled; }
void set_frequency(uint32_t frequency) { this->frequency_ = frequency; }
void set_timeout(uint32_t timeout) { this->timeout_ = timeout; }
int get_port() const override { return static_cast<int>(this->port_); }
int get_port() const override { return this->port_; }
private:
void recover_();
RecoveryCode recovery_result_;
RecoveryCode recovery_result_{};
protected:
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 4, 2)
i2c_master_dev_handle_t dev_;
i2c_master_bus_handle_t bus_;
void i2c_scan() override;
#endif
i2c_port_t port_;
uint8_t sda_pin_;
bool sda_pullup_enabled_;
uint8_t scl_pin_;
bool scl_pullup_enabled_;
uint32_t frequency_;
i2c_master_dev_handle_t dev_{};
i2c_master_bus_handle_t bus_{};
i2c_port_t port_{};
uint8_t sda_pin_{};
bool sda_pullup_enabled_{};
uint8_t scl_pin_{};
bool scl_pullup_enabled_{};
uint32_t frequency_{};
uint32_t timeout_ = 0;
bool initialized_ = false;
};

View File

@@ -35,7 +35,7 @@ void IAQCore::setup() {
void IAQCore::update() {
uint8_t buffer[sizeof(SensorData)];
if (this->read_register(0xB5, buffer, sizeof(buffer), false) != i2c::ERROR_OK) {
if (this->read_register(0xB5, buffer, sizeof(buffer)) != i2c::ERROR_OK) {
ESP_LOGD(TAG, "Read failed");
this->status_set_warning();
this->publish_nans_();

View File

@@ -21,7 +21,7 @@ void INA2XXI2C::dump_config() {
}
bool INA2XXI2C::read_ina_register(uint8_t reg, uint8_t *data, size_t len) {
auto ret = this->read_register(reg, data, len, false);
auto ret = this->read_register(reg, data, len);
if (ret != i2c::ERROR_OK) {
ESP_LOGE(TAG, "read_ina_register_ failed. Reg=0x%02X Err=%d", reg, ret);
}

View File

@@ -22,7 +22,7 @@ void KMeterISOComponent::setup() {
this->reset_to_construction_state();
}
auto err = this->bus_->writev(this->address_, nullptr, 0);
auto err = this->bus_->write_readv(this->address_, nullptr, 0, nullptr, 0);
if (err == esphome::i2c::ERROR_OK) {
ESP_LOGCONFIG(TAG, "Could write to the address %d.", this->address_);
} else {

View File

@@ -184,7 +184,7 @@ uint8_t Lc709203f::get_register_(uint8_t register_to_read, uint16_t *register_va
// function will send a stop between the read and the write portion of the I2C
// transaction. This is bad in this case and will result in reading nothing but 0xFFFF
// from the registers.
return_code = this->read_register(register_to_read, &read_buffer[3], 3, false);
return_code = this->read_register(register_to_read, &read_buffer[3], 3);
if (return_code != i2c::NO_ERROR) {
// Error on the i2c bus
this->status_set_warning(
@@ -225,7 +225,7 @@ uint8_t Lc709203f::set_register_(uint8_t register_to_set, uint16_t value_to_set)
for (uint8_t i = 0; i <= LC709203F_I2C_RETRY_COUNT; i++) {
// Note: we don't write the first byte of the write buffer to the device.
// This is done automatically by the write() function.
return_code = this->write(&write_buffer[1], 4, true);
return_code = this->write(&write_buffer[1], 4);
if (return_code == i2c::NO_ERROR) {
return return_code;
} else {

View File

@@ -17,9 +17,8 @@ CONFIG_SCHEMA = cv.All(
cv.Schema(
{
cv.GenerateID(): cv.declare_id(LD2450Component),
cv.Optional(CONF_THROTTLE, default="1000ms"): cv.All(
cv.positive_time_period_milliseconds,
cv.Range(min=cv.TimePeriod(milliseconds=1)),
cv.Optional(CONF_THROTTLE): cv.invalid(
f"{CONF_THROTTLE} has been removed; use per-sensor filters, instead"
),
}
)
@@ -46,4 +45,3 @@ async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await uart.register_uart_device(var, config)
cg.add(var.set_throttle(config[CONF_THROTTLE]))

View File

@@ -21,14 +21,17 @@ CONFIG_SCHEMA = {
cv.GenerateID(CONF_LD2450_ID): cv.use_id(LD2450Component),
cv.Optional(CONF_HAS_TARGET): binary_sensor.binary_sensor_schema(
device_class=DEVICE_CLASS_OCCUPANCY,
filters=[{"settle": cv.TimePeriod(milliseconds=1000)}],
icon=ICON_SHIELD_ACCOUNT,
),
cv.Optional(CONF_HAS_MOVING_TARGET): binary_sensor.binary_sensor_schema(
device_class=DEVICE_CLASS_MOTION,
filters=[{"settle": cv.TimePeriod(milliseconds=1000)}],
icon=ICON_TARGET_ACCOUNT,
),
cv.Optional(CONF_HAS_STILL_TARGET): binary_sensor.binary_sensor_schema(
device_class=DEVICE_CLASS_OCCUPANCY,
filters=[{"settle": cv.TimePeriod(milliseconds=1000)}],
icon=ICON_MEDITATION,
),
}

View File

@@ -199,9 +199,8 @@ void LD2450Component::dump_config() {
ESP_LOGCONFIG(TAG,
"LD2450:\n"
" Firmware version: %s\n"
" MAC address: %s\n"
" Throttle: %u ms",
version.c_str(), mac_str.c_str(), this->throttle_);
" MAC address: %s",
version.c_str(), mac_str.c_str());
#ifdef USE_BINARY_SENSOR
ESP_LOGCONFIG(TAG, "Binary Sensors:");
LOG_BINARY_SENSOR(" ", "MovingTarget", this->moving_target_binary_sensor_);
@@ -431,11 +430,6 @@ void LD2450Component::send_command_(uint8_t command, const uint8_t *command_valu
// [AA FF 03 00] [0E 03 B1 86 10 00 40 01] [00 00 00 00 00 00 00 00] [00 00 00 00 00 00 00 00] [55 CC]
// Header Target 1 Target 2 Target 3 End
void LD2450Component::handle_periodic_data_() {
// Early throttle check - moved before any processing to save CPU cycles
if (App.get_loop_component_start_time() - this->last_periodic_millis_ < this->throttle_) {
return;
}
if (this->buffer_pos_ < 29) { // header (4 bytes) + 8 x 3 target data + footer (2 bytes)
ESP_LOGE(TAG, "Invalid length");
return;
@@ -446,8 +440,6 @@ void LD2450Component::handle_periodic_data_() {
ESP_LOGE(TAG, "Invalid header/footer");
return;
}
// Save the timestamp after validating the frame so, if invalid, we'll take the next frame immediately
this->last_periodic_millis_ = App.get_loop_component_start_time();
int16_t target_count = 0;
int16_t still_target_count = 0;

View File

@@ -110,7 +110,6 @@ class LD2450Component : public Component, public uart::UARTDevice {
void dump_config() override;
void loop() override;
void set_presence_timeout();
void set_throttle(uint16_t value) { this->throttle_ = value; }
void read_all_info();
void query_zone_info();
void restart_and_read_all_info();
@@ -161,11 +160,9 @@ class LD2450Component : public Component, public uart::UARTDevice {
bool get_timeout_status_(uint32_t check_millis);
uint8_t count_targets_in_zone_(const Zone &zone, bool is_moving);
uint32_t last_periodic_millis_ = 0;
uint32_t presence_millis_ = 0;
uint32_t still_presence_millis_ = 0;
uint32_t moving_presence_millis_ = 0;
uint16_t throttle_ = 0;
uint16_t timeout_ = 5;
uint8_t buffer_data_[MAX_LINE_LENGTH];
uint8_t mac_address_[6] = {0, 0, 0, 0, 0, 0};

View File

@@ -42,16 +42,43 @@ CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(CONF_LD2450_ID): cv.use_id(LD2450Component),
cv.Optional(CONF_TARGET_COUNT): sensor.sensor_schema(
icon=ICON_ACCOUNT_GROUP,
accuracy_decimals=0,
filters=[
{
"timeout": {
"timeout": cv.TimePeriod(milliseconds=1000),
"value": "last",
}
},
{"throttle_with_priority": cv.TimePeriod(milliseconds=1000)},
],
icon=ICON_ACCOUNT_GROUP,
),
cv.Optional(CONF_STILL_TARGET_COUNT): sensor.sensor_schema(
icon=ICON_HUMAN_GREETING_PROXIMITY,
accuracy_decimals=0,
filters=[
{
"timeout": {
"timeout": cv.TimePeriod(milliseconds=1000),
"value": "last",
}
},
{"throttle_with_priority": cv.TimePeriod(milliseconds=1000)},
],
icon=ICON_HUMAN_GREETING_PROXIMITY,
),
cv.Optional(CONF_MOVING_TARGET_COUNT): sensor.sensor_schema(
icon=ICON_ACCOUNT_SWITCH,
accuracy_decimals=0,
filters=[
{
"timeout": {
"timeout": cv.TimePeriod(milliseconds=1000),
"value": "last",
}
},
{"throttle_with_priority": cv.TimePeriod(milliseconds=1000)},
],
icon=ICON_ACCOUNT_SWITCH,
),
}
)
@@ -62,32 +89,86 @@ CONFIG_SCHEMA = CONFIG_SCHEMA.extend(
{
cv.Optional(CONF_X): sensor.sensor_schema(
device_class=DEVICE_CLASS_DISTANCE,
unit_of_measurement=UNIT_MILLIMETER,
filters=[
{
"timeout": {
"timeout": cv.TimePeriod(milliseconds=1000),
"value": "last",
}
},
{"throttle_with_priority": cv.TimePeriod(milliseconds=1000)},
],
icon=ICON_ALPHA_X_BOX_OUTLINE,
unit_of_measurement=UNIT_MILLIMETER,
),
cv.Optional(CONF_Y): sensor.sensor_schema(
device_class=DEVICE_CLASS_DISTANCE,
unit_of_measurement=UNIT_MILLIMETER,
filters=[
{
"timeout": {
"timeout": cv.TimePeriod(milliseconds=1000),
"value": "last",
}
},
{"throttle_with_priority": cv.TimePeriod(milliseconds=1000)},
],
icon=ICON_ALPHA_Y_BOX_OUTLINE,
unit_of_measurement=UNIT_MILLIMETER,
),
cv.Optional(CONF_SPEED): sensor.sensor_schema(
device_class=DEVICE_CLASS_SPEED,
unit_of_measurement=UNIT_MILLIMETER_PER_SECOND,
filters=[
{
"timeout": {
"timeout": cv.TimePeriod(milliseconds=1000),
"value": "last",
}
},
{"throttle_with_priority": cv.TimePeriod(milliseconds=1000)},
],
icon=ICON_SPEEDOMETER_SLOW,
unit_of_measurement=UNIT_MILLIMETER_PER_SECOND,
),
cv.Optional(CONF_ANGLE): sensor.sensor_schema(
unit_of_measurement=UNIT_DEGREES,
filters=[
{
"timeout": {
"timeout": cv.TimePeriod(milliseconds=1000),
"value": "last",
}
},
{"throttle_with_priority": cv.TimePeriod(milliseconds=1000)},
],
icon=ICON_FORMAT_TEXT_ROTATION_ANGLE_UP,
unit_of_measurement=UNIT_DEGREES,
),
cv.Optional(CONF_DISTANCE): sensor.sensor_schema(
device_class=DEVICE_CLASS_DISTANCE,
unit_of_measurement=UNIT_MILLIMETER,
filters=[
{
"timeout": {
"timeout": cv.TimePeriod(milliseconds=1000),
"value": "last",
}
},
{"throttle_with_priority": cv.TimePeriod(milliseconds=1000)},
],
icon=ICON_MAP_MARKER_DISTANCE,
unit_of_measurement=UNIT_MILLIMETER,
),
cv.Optional(CONF_RESOLUTION): sensor.sensor_schema(
device_class=DEVICE_CLASS_DISTANCE,
unit_of_measurement=UNIT_MILLIMETER,
filters=[
{
"timeout": {
"timeout": cv.TimePeriod(milliseconds=1000),
"value": "last",
}
},
{"throttle_with_priority": cv.TimePeriod(milliseconds=1000)},
],
icon=ICON_RELATION_ZERO_OR_ONE_TO_ZERO_OR_ONE,
unit_of_measurement=UNIT_MILLIMETER,
),
}
)
@@ -97,16 +178,43 @@ CONFIG_SCHEMA = CONFIG_SCHEMA.extend(
cv.Optional(f"zone_{n + 1}"): cv.Schema(
{
cv.Optional(CONF_TARGET_COUNT): sensor.sensor_schema(
icon=ICON_MAP_MARKER_ACCOUNT,
accuracy_decimals=0,
filters=[
{
"timeout": {
"timeout": cv.TimePeriod(milliseconds=1000),
"value": "last",
}
},
{"throttle_with_priority": cv.TimePeriod(milliseconds=1000)},
],
icon=ICON_MAP_MARKER_ACCOUNT,
),
cv.Optional(CONF_STILL_TARGET_COUNT): sensor.sensor_schema(
icon=ICON_MAP_MARKER_ACCOUNT,
accuracy_decimals=0,
filters=[
{
"timeout": {
"timeout": cv.TimePeriod(milliseconds=1000),
"value": "last",
}
},
{"throttle_with_priority": cv.TimePeriod(milliseconds=1000)},
],
icon=ICON_MAP_MARKER_ACCOUNT,
),
cv.Optional(CONF_MOVING_TARGET_COUNT): sensor.sensor_schema(
icon=ICON_MAP_MARKER_ACCOUNT,
accuracy_decimals=0,
filters=[
{
"timeout": {
"timeout": cv.TimePeriod(milliseconds=1000),
"value": "last",
}
},
{"throttle_with_priority": cv.TimePeriod(milliseconds=1000)},
],
icon=ICON_MAP_MARKER_ACCOUNT,
),
}
)

View File

@@ -140,12 +140,22 @@ float LightState::get_setup_priority() const { return setup_priority::HARDWARE -
void LightState::publish_state() { this->remote_values_callback_.call(); }
LightOutput *LightState::get_output() const { return this->output_; }
static constexpr const char *EFFECT_NONE = "None";
static constexpr auto EFFECT_NONE_REF = StringRef::from_lit("None");
std::string LightState::get_effect_name() {
if (this->active_effect_index_ > 0) {
return this->effects_[this->active_effect_index_ - 1]->get_name();
} else {
return "None";
}
return EFFECT_NONE;
}
StringRef LightState::get_effect_name_ref() {
if (this->active_effect_index_ > 0) {
return StringRef(this->effects_[this->active_effect_index_ - 1]->get_name());
}
return EFFECT_NONE_REF;
}
void LightState::add_new_remote_values_callback(std::function<void()> &&send_callback) {

View File

@@ -4,6 +4,7 @@
#include "esphome/core/entity_base.h"
#include "esphome/core/optional.h"
#include "esphome/core/preferences.h"
#include "esphome/core/string_ref.h"
#include "light_call.h"
#include "light_color_values.h"
#include "light_effect.h"
@@ -11,6 +12,7 @@
#include "light_transformer.h"
#include <vector>
#include <strings.h>
namespace esphome {
namespace light {
@@ -116,6 +118,8 @@ class LightState : public EntityBase, public Component {
/// Return the name of the current effect, or if no effect is active "None".
std::string get_effect_name();
/// Return the name of the current effect as StringRef (for API usage)
StringRef get_effect_name_ref();
/**
* This lets front-end components subscribe to light change events. This callback is called once

View File

@@ -24,7 +24,7 @@ from ..defines import (
literal,
)
from ..lv_validation import (
lv_angle,
lv_angle_degrees,
lv_bool,
lv_color,
lv_image,
@@ -395,15 +395,15 @@ ARC_PROPS = {
DRAW_OPA_SCHEMA.extend(
{
cv.Required(CONF_RADIUS): pixels,
cv.Required(CONF_START_ANGLE): lv_angle,
cv.Required(CONF_END_ANGLE): lv_angle,
cv.Required(CONF_START_ANGLE): lv_angle_degrees,
cv.Required(CONF_END_ANGLE): lv_angle_degrees,
}
).extend({cv.Optional(prop): validator for prop, validator in ARC_PROPS.items()}),
)
async def canvas_draw_arc(config, action_id, template_arg, args):
radius = await size.process(config[CONF_RADIUS])
start_angle = await lv_angle.process(config[CONF_START_ANGLE])
end_angle = await lv_angle.process(config[CONF_END_ANGLE])
start_angle = await lv_angle_degrees.process(config[CONF_START_ANGLE])
end_angle = await lv_angle_degrees.process(config[CONF_END_ANGLE])
async def do_draw_arc(w: Widget, x, y, dsc_addr):
lv.canvas_draw_arc(w.obj, x, y, radius, start_angle, end_angle, dsc_addr)

View File

@@ -14,7 +14,6 @@ from esphome.const import (
CONF_VALUE,
CONF_WIDTH,
)
from esphome.cpp_generator import IntLiteral
from ..automation import action_to_code
from ..defines import (
@@ -32,7 +31,7 @@ from ..helpers import add_lv_use, lvgl_components_required
from ..lv_validation import (
get_end_value,
get_start_value,
lv_angle,
lv_angle_degrees,
lv_bool,
lv_color,
lv_float,
@@ -163,7 +162,7 @@ SCALE_SCHEMA = cv.Schema(
cv.Optional(CONF_RANGE_FROM, default=0.0): cv.float_,
cv.Optional(CONF_RANGE_TO, default=100.0): cv.float_,
cv.Optional(CONF_ANGLE_RANGE, default=270): cv.int_range(0, 360),
cv.Optional(CONF_ROTATION): lv_angle,
cv.Optional(CONF_ROTATION): lv_angle_degrees,
cv.Optional(CONF_INDICATORS): cv.ensure_list(INDICATOR_SCHEMA),
}
)
@@ -188,9 +187,7 @@ class MeterType(WidgetType):
for scale_conf in config.get(CONF_SCALES, ()):
rotation = 90 + (360 - scale_conf[CONF_ANGLE_RANGE]) / 2
if CONF_ROTATION in scale_conf:
rotation = await lv_angle.process(scale_conf[CONF_ROTATION])
if isinstance(rotation, IntLiteral):
rotation = int(str(rotation)) // 10
rotation = await lv_angle_degrees.process(scale_conf[CONF_ROTATION])
with LocalVariable(
"meter_var", "lv_meter_scale_t", lv_expr.meter_add_scale(var)
) as meter_var:

View File

@@ -122,7 +122,7 @@ uint8_t Mcp4461Component::get_status_register_() {
uint8_t addr = static_cast<uint8_t>(Mcp4461Addresses::MCP4461_STATUS);
uint8_t reg = addr | static_cast<uint8_t>(Mcp4461Commands::READ);
uint16_t buf;
if (!this->read_byte_16(reg, &buf)) {
if (!this->read_16_(reg, &buf)) {
this->error_code_ = MCP4461_STATUS_REGISTER_ERROR;
this->mark_failed();
return 0;
@@ -148,6 +148,20 @@ void Mcp4461Component::read_status_register_to_log() {
((status_register_value >> 3) & 0x01), ((status_register_value >> 2) & 0x01),
((status_register_value >> 1) & 0x01), ((status_register_value >> 0) & 0x01));
}
bool Mcp4461Component::read_16_(uint8_t address, uint16_t *buf) {
// read 16 bits and convert from big endian to host,
// Do this as two separate operations to ensure a stop condition between the write and read
i2c::ErrorCode err = this->write(&address, 1);
if (err != i2c::ERROR_OK) {
return false;
}
err = this->read(reinterpret_cast<uint8_t *>(buf), 2);
if (err != i2c::ERROR_OK) {
return false;
}
*buf = convert_big_endian(*buf);
return true;
}
uint8_t Mcp4461Component::get_wiper_address_(uint8_t wiper) {
uint8_t addr;
@@ -198,14 +212,14 @@ uint16_t Mcp4461Component::get_wiper_level_(Mcp4461WiperIdx wiper) {
uint16_t Mcp4461Component::read_wiper_level_(uint8_t wiper_idx) {
uint8_t addr = this->get_wiper_address_(wiper_idx);
uint8_t reg = addr | static_cast<uint8_t>(Mcp4461Commands::INCREMENT);
uint8_t reg = addr | static_cast<uint8_t>(Mcp4461Commands::READ);
if (wiper_idx > 3) {
if (!this->is_eeprom_ready_for_writing_(true)) {
return 0;
}
}
uint16_t buf = 0;
if (!(this->read_byte_16(reg, &buf))) {
if (!(this->read_16_(reg, &buf))) {
this->error_code_ = MCP4461_STATUS_I2C_ERROR;
this->status_set_warning();
ESP_LOGW(TAG, "Error fetching %swiper %u value", (wiper_idx > 3) ? "nonvolatile " : "", wiper_idx);
@@ -328,7 +342,7 @@ bool Mcp4461Component::increase_wiper_(Mcp4461WiperIdx wiper) {
ESP_LOGV(TAG, "Increasing wiper %u", wiper_idx);
uint8_t addr = this->get_wiper_address_(wiper_idx);
uint8_t reg = addr | static_cast<uint8_t>(Mcp4461Commands::INCREMENT);
auto err = this->write(&this->address_, reg, sizeof(reg));
auto err = this->write(&this->address_, reg);
if (err != i2c::ERROR_OK) {
this->error_code_ = MCP4461_STATUS_I2C_ERROR;
this->status_set_warning();
@@ -359,7 +373,7 @@ bool Mcp4461Component::decrease_wiper_(Mcp4461WiperIdx wiper) {
ESP_LOGV(TAG, "Decreasing wiper %u", wiper_idx);
uint8_t addr = this->get_wiper_address_(wiper_idx);
uint8_t reg = addr | static_cast<uint8_t>(Mcp4461Commands::DECREMENT);
auto err = this->write(&this->address_, reg, sizeof(reg));
auto err = this->write(&this->address_, reg);
if (err != i2c::ERROR_OK) {
this->error_code_ = MCP4461_STATUS_I2C_ERROR;
this->status_set_warning();
@@ -392,7 +406,7 @@ uint8_t Mcp4461Component::get_terminal_register_(Mcp4461TerminalIdx terminal_con
: static_cast<uint8_t>(Mcp4461Addresses::MCP4461_TCON1);
reg |= static_cast<uint8_t>(Mcp4461Commands::READ);
uint16_t buf;
if (this->read_byte_16(reg, &buf)) {
if (this->read_16_(reg, &buf)) {
return static_cast<uint8_t>(buf & 0x00ff);
} else {
this->error_code_ = MCP4461_STATUS_I2C_ERROR;
@@ -517,7 +531,7 @@ uint16_t Mcp4461Component::get_eeprom_value(Mcp4461EepromLocation location) {
if (!this->is_eeprom_ready_for_writing_(true)) {
return 0;
}
if (!this->read_byte_16(reg, &buf)) {
if (!this->read_16_(reg, &buf)) {
this->error_code_ = MCP4461_STATUS_I2C_ERROR;
this->status_set_warning();
ESP_LOGW(TAG, "Error fetching EEPROM location value");

View File

@@ -96,6 +96,7 @@ class Mcp4461Component : public Component, public i2c::I2CDevice {
protected:
friend class Mcp4461Wiper;
bool read_16_(uint8_t address, uint16_t *buf);
void update_write_protection_status_();
uint8_t get_wiper_address_(uint8_t wiper);
uint16_t read_wiper_level_(uint8_t wiper);

View File

@@ -309,8 +309,12 @@ class DriverChip:
CONF_NATIVE_HEIGHT, height + offset_height * 2
)
offset_height = native_height - height - offset_height
# Swap default dimensions if swap_xy is set
if transform[CONF_SWAP_XY] is True:
# Swap default dimensions if swap_xy is set, or if rotation is 90/270 and we are not using a buffer
rotated = not requires_buffer(config) and config.get(CONF_ROTATION, 0) in (
90,
270,
)
if transform[CONF_SWAP_XY] is True or rotated:
width, height = height, width
offset_height, offset_width = offset_width, offset_height
return width, height, offset_width, offset_height

View File

@@ -16,7 +16,6 @@ DriverChip(
lane_bit_rate="750Mbps",
swap_xy=cv.UNDEFINED,
color_order="RGB",
reset_pin=27,
initsequence=[
(0x30, 0x00), (0xF7, 0x49, 0x61, 0x02, 0x00), (0x30, 0x01), (0x04, 0x0C), (0x05, 0x00), (0x06, 0x00),
(0x0B, 0x11), (0x17, 0x00), (0x20, 0x04), (0x1F, 0x05), (0x23, 0x00), (0x25, 0x19), (0x28, 0x18), (0x29, 0x04), (0x2A, 0x01),

View File

@@ -90,18 +90,18 @@ float MLX90614Component::get_setup_priority() const { return setup_priority::DAT
void MLX90614Component::update() {
uint8_t emissivity[3];
if (this->read_register(MLX90614_EMISSIVITY, emissivity, 3, false) != i2c::ERROR_OK) {
if (this->read_register(MLX90614_EMISSIVITY, emissivity, 3) != i2c::ERROR_OK) {
this->status_set_warning();
return;
}
uint8_t raw_object[3];
if (this->read_register(MLX90614_TEMPERATURE_OBJECT_1, raw_object, 3, false) != i2c::ERROR_OK) {
if (this->read_register(MLX90614_TEMPERATURE_OBJECT_1, raw_object, 3) != i2c::ERROR_OK) {
this->status_set_warning();
return;
}
uint8_t raw_ambient[3];
if (this->read_register(MLX90614_TEMPERATURE_AMBIENT, raw_ambient, 3, false) != i2c::ERROR_OK) {
if (this->read_register(MLX90614_TEMPERATURE_AMBIENT, raw_ambient, 3) != i2c::ERROR_OK) {
this->status_set_warning();
return;
}

View File

@@ -10,7 +10,7 @@ static const char *const TAG = "mpl3115a2";
void MPL3115A2Component::setup() {
uint8_t whoami = 0xFF;
if (!this->read_byte(MPL3115A2_WHOAMI, &whoami, false)) {
if (!this->read_byte(MPL3115A2_WHOAMI, &whoami)) {
this->error_code_ = COMMUNICATION_FAILED;
this->mark_failed();
return;
@@ -54,24 +54,24 @@ void MPL3115A2Component::dump_config() {
void MPL3115A2Component::update() {
uint8_t mode = MPL3115A2_CTRL_REG1_OS128;
this->write_byte(MPL3115A2_CTRL_REG1, mode, true);
this->write_byte(MPL3115A2_CTRL_REG1, mode);
// Trigger a new reading
mode |= MPL3115A2_CTRL_REG1_OST;
if (this->altitude_ != nullptr)
mode |= MPL3115A2_CTRL_REG1_ALT;
this->write_byte(MPL3115A2_CTRL_REG1, mode, true);
this->write_byte(MPL3115A2_CTRL_REG1, mode);
// Wait until status shows reading available
uint8_t status = 0;
if (!this->read_byte(MPL3115A2_REGISTER_STATUS, &status, false) || (status & MPL3115A2_REGISTER_STATUS_PDR) == 0) {
if (!this->read_byte(MPL3115A2_REGISTER_STATUS, &status) || (status & MPL3115A2_REGISTER_STATUS_PDR) == 0) {
delay(10);
if (!this->read_byte(MPL3115A2_REGISTER_STATUS, &status, false) || (status & MPL3115A2_REGISTER_STATUS_PDR) == 0) {
if (!this->read_byte(MPL3115A2_REGISTER_STATUS, &status) || (status & MPL3115A2_REGISTER_STATUS_PDR) == 0) {
return;
}
}
uint8_t buffer[5] = {0, 0, 0, 0, 0};
this->read_register(MPL3115A2_REGISTER_PRESSURE_MSB, buffer, 5, false);
this->read_register(MPL3115A2_REGISTER_PRESSURE_MSB, buffer, 5);
float altitude = 0, pressure = 0;
if (this->altitude_ != nullptr) {

View File

@@ -764,7 +764,8 @@ void Nextion::process_nextion_commands_() {
variable_name = to_process.substr(0, index);
++index;
text_value = to_process.substr(index);
// Get variable value without terminating NUL byte. Length check above ensures substr len >= 0.
text_value = to_process.substr(index, to_process_length - index - 1);
ESP_LOGN(TAG, "Text sensor: %s='%s'", variable_name.c_str(), text_value.c_str());

View File

@@ -33,7 +33,7 @@ float NPI19Component::get_setup_priority() const { return setup_priority::DATA;
i2c::ErrorCode NPI19Component::read_(uint16_t &raw_temperature, uint16_t &raw_pressure) {
// initiate data read from device
i2c::ErrorCode w_err = write(&READ_COMMAND, sizeof(READ_COMMAND), true);
i2c::ErrorCode w_err = write(&READ_COMMAND, sizeof(READ_COMMAND));
if (w_err != i2c::ERROR_OK) {
return w_err;
}

View File

@@ -72,7 +72,7 @@ void OPT3001Sensor::read_lx_(const std::function<void(float)> &f) {
}
this->set_timeout("read", OPT3001_CONVERSION_TIME_800, [this, f]() {
if (this->write(&OPT3001_REG_CONFIGURATION, 1, true) != i2c::ERROR_OK) {
if (this->write(&OPT3001_REG_CONFIGURATION, 1) != i2c::ERROR_OK) {
ESP_LOGW(TAG, "Starting configuration register read failed");
f(NAN);
return;

View File

@@ -33,7 +33,7 @@ void PCA6416AComponent::setup() {
}
// Test to see if the device supports pull-up resistors
if (this->read_register(PCAL6416A_PULL_EN0, &value, 1, true) == i2c::ERROR_OK) {
if (this->read_register(PCAL6416A_PULL_EN0, &value, 1) == i2c::ERROR_OK) {
this->has_pullup_ = true;
}
@@ -105,7 +105,7 @@ bool PCA6416AComponent::read_register_(uint8_t reg, uint8_t *value) {
return false;
}
this->last_error_ = this->read_register(reg, value, 1, true);
this->last_error_ = this->read_register(reg, value, 1);
if (this->last_error_ != i2c::ERROR_OK) {
this->status_set_warning();
ESP_LOGE(TAG, "read_register_(): I2C I/O error: %d", (int) this->last_error_);
@@ -122,7 +122,7 @@ bool PCA6416AComponent::write_register_(uint8_t reg, uint8_t value) {
return false;
}
this->last_error_ = this->write_register(reg, &value, 1, true);
this->last_error_ = this->write_register(reg, &value, 1);
if (this->last_error_ != i2c::ERROR_OK) {
this->status_set_warning();
ESP_LOGE(TAG, "write_register_(): I2C I/O error: %d", (int) this->last_error_);

View File

@@ -96,7 +96,7 @@ bool PCA9554Component::read_inputs_() {
return false;
}
this->last_error_ = this->read_register(INPUT_REG * this->reg_width_, inputs, this->reg_width_, true);
this->last_error_ = this->read_register(INPUT_REG * this->reg_width_, inputs, this->reg_width_);
if (this->last_error_ != i2c::ERROR_OK) {
this->status_set_warning();
ESP_LOGE(TAG, "read_register_(): I2C I/O error: %d", (int) this->last_error_);
@@ -114,7 +114,7 @@ bool PCA9554Component::write_register_(uint8_t reg, uint16_t value) {
uint8_t outputs[2];
outputs[0] = (uint8_t) value;
outputs[1] = (uint8_t) (value >> 8);
this->last_error_ = this->write_register(reg * this->reg_width_, outputs, this->reg_width_, true);
this->last_error_ = this->write_register(reg * this->reg_width_, outputs, this->reg_width_);
if (this->last_error_ != i2c::ERROR_OK) {
this->status_set_warning();
ESP_LOGE(TAG, "write_register_(): I2C I/O error: %d", (int) this->last_error_);

View File

@@ -23,20 +23,18 @@ void Pipsolar::loop() {
// Read message
if (this->state_ == STATE_IDLE) {
this->empty_uart_buffer_();
switch (this->send_next_command_()) {
case 0:
// no command send (empty queue) time to poll
if (millis() - this->last_poll_ > this->update_interval_) {
this->send_next_poll_();
this->last_poll_ = millis();
}
return;
break;
case 1:
// command send
return;
break;
if (this->send_next_command_()) {
// command sent
return;
}
if (this->send_next_poll_()) {
// poll sent
return;
}
return;
}
if (this->state_ == STATE_COMMAND_COMPLETE) {
if (this->check_incoming_length_(4)) {
@@ -530,7 +528,7 @@ void Pipsolar::loop() {
// '(00000000000000000000000000000000'
// iterate over all available flag (as not all models have all flags, but at least in the same order)
this->value_warnings_present_ = false;
this->value_faults_present_ = true;
this->value_faults_present_ = false;
for (size_t i = 1; i < strlen(tmp); i++) {
enabled = tmp[i] == '1';
@@ -708,6 +706,7 @@ void Pipsolar::loop() {
return;
}
// crc ok
this->used_polling_commands_[this->last_polling_command_].needs_update = false;
this->state_ = STATE_POLL_CHECKED;
return;
} else {
@@ -788,7 +787,7 @@ uint8_t Pipsolar::check_incoming_crc_() {
}
// send next command used
uint8_t Pipsolar::send_next_command_() {
bool Pipsolar::send_next_command_() {
uint16_t crc16;
if (!this->command_queue_[this->command_queue_position_].empty()) {
const char *command = this->command_queue_[this->command_queue_position_].c_str();
@@ -809,37 +808,43 @@ uint8_t Pipsolar::send_next_command_() {
// end Byte
this->write(0x0D);
ESP_LOGD(TAG, "Sending command from queue: %s with length %d", command, length);
return 1;
return true;
}
return 0;
return false;
}
void Pipsolar::send_next_poll_() {
bool Pipsolar::send_next_poll_() {
uint16_t crc16;
this->last_polling_command_ = (this->last_polling_command_ + 1) % 15;
if (this->used_polling_commands_[this->last_polling_command_].length == 0) {
this->last_polling_command_ = 0;
for (uint8_t i = 0; i < POLLING_COMMANDS_MAX; i++) {
this->last_polling_command_ = (this->last_polling_command_ + 1) % POLLING_COMMANDS_MAX;
if (this->used_polling_commands_[this->last_polling_command_].length == 0) {
// not enabled
continue;
}
if (!this->used_polling_commands_[this->last_polling_command_].needs_update) {
// no update requested
continue;
}
this->state_ = STATE_POLL;
this->command_start_millis_ = millis();
this->empty_uart_buffer_();
this->read_pos_ = 0;
crc16 = this->pipsolar_crc_(this->used_polling_commands_[this->last_polling_command_].command,
this->used_polling_commands_[this->last_polling_command_].length);
this->write_array(this->used_polling_commands_[this->last_polling_command_].command,
this->used_polling_commands_[this->last_polling_command_].length);
// checksum
this->write(((uint8_t) ((crc16) >> 8))); // highbyte
this->write(((uint8_t) ((crc16) &0xff))); // lowbyte
// end Byte
this->write(0x0D);
ESP_LOGD(TAG, "Sending polling command : %s with length %d",
this->used_polling_commands_[this->last_polling_command_].command,
this->used_polling_commands_[this->last_polling_command_].length);
return true;
}
if (this->used_polling_commands_[this->last_polling_command_].length == 0) {
// no command specified
return;
}
this->state_ = STATE_POLL;
this->command_start_millis_ = millis();
this->empty_uart_buffer_();
this->read_pos_ = 0;
crc16 = this->pipsolar_crc_(this->used_polling_commands_[this->last_polling_command_].command,
this->used_polling_commands_[this->last_polling_command_].length);
this->write_array(this->used_polling_commands_[this->last_polling_command_].command,
this->used_polling_commands_[this->last_polling_command_].length);
// checksum
this->write(((uint8_t) ((crc16) >> 8))); // highbyte
this->write(((uint8_t) ((crc16) &0xff))); // lowbyte
// end Byte
this->write(0x0D);
ESP_LOGD(TAG, "Sending polling command : %s with length %d",
this->used_polling_commands_[this->last_polling_command_].command,
this->used_polling_commands_[this->last_polling_command_].length);
return false;
}
void Pipsolar::queue_command_(const char *command, uint8_t length) {
@@ -869,7 +874,13 @@ void Pipsolar::dump_config() {
}
}
}
void Pipsolar::update() {}
void Pipsolar::update() {
for (auto &used_polling_command : this->used_polling_commands_) {
if (used_polling_command.length != 0) {
used_polling_command.needs_update = true;
}
}
}
void Pipsolar::add_polling_command_(const char *command, ENUMPollingCommand polling_command) {
for (auto &used_polling_command : this->used_polling_commands_) {
@@ -891,6 +902,7 @@ void Pipsolar::add_polling_command_(const char *command, ENUMPollingCommand poll
used_polling_command.errors = 0;
used_polling_command.identifier = polling_command;
used_polling_command.length = length - 1;
used_polling_command.needs_update = true;
return;
}
}

View File

@@ -25,6 +25,7 @@ struct PollingCommand {
uint8_t length = 0;
uint8_t errors;
ENUMPollingCommand identifier;
bool needs_update;
};
#define PIPSOLAR_VALUED_ENTITY_(type, name, polling_command, value_type) \
@@ -189,14 +190,14 @@ class Pipsolar : public uart::UARTDevice, public PollingComponent {
static const size_t PIPSOLAR_READ_BUFFER_LENGTH = 110; // maximum supported answer length
static const size_t COMMAND_QUEUE_LENGTH = 10;
static const size_t COMMAND_TIMEOUT = 5000;
uint32_t last_poll_ = 0;
static const size_t POLLING_COMMANDS_MAX = 15;
void add_polling_command_(const char *command, ENUMPollingCommand polling_command);
void empty_uart_buffer_();
uint8_t check_incoming_crc_();
uint8_t check_incoming_length_(uint8_t length);
uint16_t pipsolar_crc_(uint8_t *msg, uint8_t len);
uint8_t send_next_command_();
void send_next_poll_();
bool send_next_command_();
bool send_next_poll_();
void queue_command_(const char *command, uint8_t length);
std::string command_queue_[COMMAND_QUEUE_LENGTH];
uint8_t command_queue_position_ = 0;
@@ -216,7 +217,7 @@ class Pipsolar : public uart::UARTDevice, public PollingComponent {
};
uint8_t last_polling_command_ = 0;
PollingCommand used_polling_commands_[15];
PollingCommand used_polling_commands_[POLLING_COMMANDS_MAX];
};
} // namespace pipsolar

View File

@@ -16,6 +16,7 @@ from esphome.components.esp32.const import (
import esphome.config_validation as cv
from esphome.const import (
CONF_ADVANCED,
CONF_DISABLED,
CONF_FRAMEWORK,
CONF_ID,
CONF_MODE,
@@ -102,6 +103,7 @@ def get_config_schema(config):
cv.Optional(CONF_MODE, default=modes[0]): cv.one_of(*modes, lower=True),
cv.Optional(CONF_ENABLE_ECC, default=False): cv.boolean,
cv.Optional(CONF_SPEED, default=speeds[0]): cv.one_of(*speeds, upper=True),
cv.Optional(CONF_DISABLED, default=False): cv.boolean,
}
)(config)
@@ -112,6 +114,8 @@ FINAL_VALIDATE_SCHEMA = validate_psram_mode
async def to_code(config):
if config[CONF_DISABLED]:
return
if CORE.using_arduino:
cg.add_build_flag("-DBOARD_HAS_PSRAM")
if config[CONF_MODE] == TYPE_OCTAL:

View File

@@ -46,10 +46,32 @@ void PVVXDisplay::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t
}
this->connection_established_ = true;
this->char_handle_ = chr->handle;
#ifdef USE_TIME
this->sync_time_();
#endif
this->display();
// Attempt to write immediately
// For devices without security, this will work
// For devices with security that are already paired, this will work
// For devices that need pairing, the write will be retried after auth completes
this->sync_time_and_display_();
break;
}
default:
break;
}
}
void PVVXDisplay::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) {
switch (event) {
case ESP_GAP_BLE_AUTH_CMPL_EVT: {
if (!this->parent_->check_addr(param->ble_security.auth_cmpl.bd_addr))
return;
if (param->ble_security.auth_cmpl.success) {
ESP_LOGD(TAG, "[%s] Authentication successful, performing writes.", this->parent_->address_str().c_str());
// Now that pairing is complete, perform the pending writes
this->sync_time_and_display_();
} else {
ESP_LOGW(TAG, "[%s] Authentication failed.", this->parent_->address_str().c_str());
}
break;
}
default:
@@ -127,6 +149,13 @@ void PVVXDisplay::delayed_disconnect_() {
this->set_timeout("disconnect", this->disconnect_delay_ms_, [this]() { this->parent_->set_enabled(false); });
}
void PVVXDisplay::sync_time_and_display_() {
#ifdef USE_TIME
this->sync_time_();
#endif
this->display();
}
#ifdef USE_TIME
void PVVXDisplay::sync_time_() {
if (this->time_ == nullptr)

View File

@@ -43,6 +43,7 @@ class PVVXDisplay : public ble_client::BLEClientNode, public PollingComponent {
void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
esp_ble_gattc_cb_param_t *param) override;
void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override;
/// Set validity period of the display information in seconds (1..65535)
void set_validity_period(uint16_t validity_period) { this->validity_period_ = validity_period; }
@@ -112,6 +113,7 @@ class PVVXDisplay : public ble_client::BLEClientNode, public PollingComponent {
void setcfgbit_(uint8_t bit, bool value);
void send_to_setup_char_(uint8_t *blk, size_t size);
void delayed_disconnect_();
void sync_time_and_display_();
#ifdef USE_TIME
void sync_time_();
time::RealTimeClock *time_{nullptr};

View File

@@ -138,11 +138,37 @@ void Rtttl::stop() {
this->set_state_(STATE_STOPPING);
}
#endif
this->position_ = this->rtttl_.length();
this->note_duration_ = 0;
}
void Rtttl::finish_() {
ESP_LOGV(TAG, "Rtttl::finish_()");
#ifdef USE_OUTPUT
if (this->output_ != nullptr) {
this->output_->set_level(0.0);
this->set_state_(State::STATE_STOPPED);
}
#endif
#ifdef USE_SPEAKER
if (this->speaker_ != nullptr) {
SpeakerSample sample[2];
sample[0].left = 0;
sample[0].right = 0;
sample[1].left = 0;
sample[1].right = 0;
this->speaker_->play((uint8_t *) (&sample), 8);
this->speaker_->finish();
this->set_state_(State::STATE_STOPPING);
}
#endif
// Ensure no more notes are played in case finish_() is called for an error.
this->position_ = this->rtttl_.length();
this->note_duration_ = 0;
}
void Rtttl::loop() {
if (this->note_duration_ == 0 || this->state_ == State::STATE_STOPPED) {
if (this->state_ == State::STATE_STOPPED) {
this->disable_loop();
return;
}
@@ -152,6 +178,8 @@ void Rtttl::loop() {
if (this->state_ == State::STATE_STOPPING) {
if (this->speaker_->is_stopped()) {
this->set_state_(State::STATE_STOPPED);
} else {
return;
}
} else if (this->state_ == State::STATE_INIT) {
if (this->speaker_->is_stopped()) {
@@ -207,7 +235,7 @@ void Rtttl::loop() {
if (this->output_ != nullptr && millis() - this->last_note_ < this->note_duration_)
return;
#endif
if (!this->rtttl_[this->position_]) {
if (this->position_ >= this->rtttl_.length()) {
this->finish_();
return;
}
@@ -346,31 +374,6 @@ void Rtttl::loop() {
this->last_note_ = millis();
}
void Rtttl::finish_() {
#ifdef USE_OUTPUT
if (this->output_ != nullptr) {
this->output_->set_level(0.0);
this->set_state_(State::STATE_STOPPED);
}
#endif
#ifdef USE_SPEAKER
if (this->speaker_ != nullptr) {
SpeakerSample sample[2];
sample[0].left = 0;
sample[0].right = 0;
sample[1].left = 0;
sample[1].right = 0;
this->speaker_->play((uint8_t *) (&sample), 8);
this->speaker_->finish();
this->set_state_(State::STATE_STOPPING);
}
#endif
this->note_duration_ = 0;
this->on_finished_playback_callback_.call();
ESP_LOGD(TAG, "Playback finished");
}
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_DEBUG
static const LogString *state_to_string(State state) {
switch (state) {
@@ -397,7 +400,11 @@ void Rtttl::set_state_(State state) {
LOG_STR_ARG(state_to_string(state)));
// Clear loop_done when transitioning from STOPPED to any other state
if (old_state == State::STATE_STOPPED && state != State::STATE_STOPPED) {
if (state == State::STATE_STOPPED) {
this->disable_loop();
this->on_finished_playback_callback_.call();
ESP_LOGD(TAG, "Playback finished");
} else if (old_state == State::STATE_STOPPED) {
this->enable_loop();
}
}

View File

@@ -60,35 +60,60 @@ class Rtttl : public Component {
}
return ret;
}
/**
* @brief Finalizes the playback of the RTTTL string.
*
* This method is called internally when the end of the RTTTL string is reached
* or when a parsing error occurs. It stops the output, sets the component state,
* and triggers the on_finished_playback_callback_.
*/
void finish_();
void set_state_(State state);
/// The RTTTL string to play.
std::string rtttl_{""};
/// The current position in the RTTTL string.
size_t position_{0};
/// The duration of a whole note in milliseconds.
uint16_t wholenote_;
/// The default duration of a note (e.g. 4 for a quarter note).
uint16_t default_duration_;
/// The default octave for a note.
uint16_t default_octave_;
/// The time the last note was started.
uint32_t last_note_;
/// The duration of the current note in milliseconds.
uint16_t note_duration_;
/// The frequency of the current note in Hz.
uint32_t output_freq_;
/// The gain of the output.
float gain_{0.6f};
/// The current state of the RTTTL player.
State state_{State::STATE_STOPPED};
#ifdef USE_OUTPUT
/// The output to write the sound to.
output::FloatOutput *output_;
#endif
#ifdef USE_SPEAKER
/// The speaker to write the sound to.
speaker::Speaker *speaker_{nullptr};
/// The sample rate of the speaker.
int sample_rate_{16000};
/// The number of samples for one full cycle of a note's waveform, in Q10 fixed-point format.
int samples_per_wave_{0};
/// The number of samples sent.
int samples_sent_{0};
/// The total number of samples to send.
int samples_count_{0};
/// The number of samples for the gap between notes.
int samples_gap_{0};
#endif
/// The callback to call when playback is finished.
CallbackManager<void()> on_finished_playback_callback_;
};

View File

@@ -38,6 +38,7 @@ void SEN5XComponent::setup() {
this->mark_failed();
return;
}
delay(20); // per datasheet
uint16_t raw_read_status;
if (!this->read_data(raw_read_status)) {

View File

@@ -53,10 +53,14 @@ void SenseAirComponent::update() {
this->status_clear_warning();
const uint8_t length = response[2];
const uint16_t status = (uint16_t(response[3]) << 8) | response[4];
const int16_t ppm = int16_t((response[length + 1] << 8) | response[length + 2]);
const uint16_t status = encode_uint16(response[3], response[4]);
const uint16_t ppm = encode_uint16(response[length + 1], response[length + 2]);
ESP_LOGD(TAG, "SenseAir Received CO₂=%dppm Status=0x%02X", ppm, status);
ESP_LOGD(TAG, "SenseAir Received CO₂=%uppm Status=0x%02X", ppm, status);
if (ppm == 0 && (status & SenseAirStatus::OUT_OF_RANGE_ERROR) != 0) {
ESP_LOGD(TAG, "Discarding 0 ppm reading with out-of-range status.");
return;
}
if (this->co2_sensor_ != nullptr)
this->co2_sensor_->publish_state(ppm);
}

View File

@@ -8,6 +8,17 @@
namespace esphome {
namespace senseair {
enum SenseAirStatus : uint8_t {
FATAL_ERROR = 1 << 0,
OFFSET_ERROR = 1 << 1,
ALGORITHM_ERROR = 1 << 2,
OUTPUT_ERROR = 1 << 3,
SELF_DIAGNOSTIC_ERROR = 1 << 4,
OUT_OF_RANGE_ERROR = 1 << 5,
MEMORY_ERROR = 1 << 6,
RESERVED = 1 << 7
};
class SenseAirComponent : public PollingComponent, public uart::UARTDevice {
public:
void set_co2_sensor(sensor::Sensor *co2_sensor) { co2_sensor_ = co2_sensor; }

View File

@@ -51,8 +51,7 @@ void HOT I2CST7567::write_display_data() {
static const size_t BLOCK_SIZE = 64;
for (uint8_t x = 0; x < (uint8_t) this->get_width_internal(); x += BLOCK_SIZE) {
this->write_register(esphome::st7567_base::ST7567_SET_START_LINE, &buffer_[y * this->get_width_internal() + x],
this->get_width_internal() - x > BLOCK_SIZE ? BLOCK_SIZE : this->get_width_internal() - x,
true);
this->get_width_internal() - x > BLOCK_SIZE ? BLOCK_SIZE : this->get_width_internal() - x);
}
}
}

View File

@@ -6,23 +6,15 @@ namespace tca9548a {
static const char *const TAG = "tca9548a";
i2c::ErrorCode TCA9548AChannel::readv(uint8_t address, i2c::ReadBuffer *buffers, size_t cnt) {
i2c::ErrorCode TCA9548AChannel::write_readv(uint8_t address, const uint8_t *write_buffer, size_t write_count,
uint8_t *read_buffer, size_t read_count) {
auto err = this->parent_->switch_to_channel(channel_);
if (err != i2c::ERROR_OK)
return err;
err = this->parent_->bus_->readv(address, buffers, cnt);
err = this->parent_->bus_->write_readv(address, write_buffer, write_count, read_buffer, read_count);
this->parent_->disable_all_channels();
return err;
}
i2c::ErrorCode TCA9548AChannel::writev(uint8_t address, i2c::WriteBuffer *buffers, size_t cnt, bool stop) {
auto err = this->parent_->switch_to_channel(channel_);
if (err != i2c::ERROR_OK)
return err;
err = this->parent_->bus_->writev(address, buffers, cnt, stop);
this->parent_->disable_all_channels();
return err;
}
void TCA9548AComponent::setup() {
uint8_t status = 0;
if (this->read(&status, 1) != i2c::ERROR_OK) {

View File

@@ -14,8 +14,8 @@ class TCA9548AChannel : public i2c::I2CBus {
void set_channel(uint8_t channel) { channel_ = channel; }
void set_parent(TCA9548AComponent *parent) { parent_ = parent; }
i2c::ErrorCode readv(uint8_t address, i2c::ReadBuffer *buffers, size_t cnt) override;
i2c::ErrorCode writev(uint8_t address, i2c::WriteBuffer *buffers, size_t cnt, bool stop) override;
i2c::ErrorCode write_readv(uint8_t address, const uint8_t *write_buffer, size_t write_count, uint8_t *read_buffer,
size_t read_count) override;
protected:
uint8_t channel_;

View File

@@ -9,9 +9,9 @@ static const char *const TAG = "tee501";
void TEE501Component::setup() {
uint8_t address[] = {0x70, 0x29};
this->write(address, 2, false);
uint8_t identification[9];
this->read(identification, 9);
this->write_read(address, sizeof address, identification, sizeof identification);
if (identification[8] != calc_crc8_(identification, 0, 7)) {
this->error_code_ = CRC_CHECK_FAILED;
this->mark_failed();
@@ -41,7 +41,7 @@ void TEE501Component::dump_config() {
float TEE501Component::get_setup_priority() const { return setup_priority::DATA; }
void TEE501Component::update() {
uint8_t address_1[] = {0x2C, 0x1B};
this->write(address_1, 2, true);
this->write(address_1, 2);
this->set_timeout(50, [this]() {
uint8_t i2c_response[3];
this->read(i2c_response, 3);

View File

@@ -74,7 +74,8 @@ void TLC59208FOutput::setup() {
ESP_LOGV(TAG, " Resetting all devices on the bus");
// Reset all devices on the bus
if (this->bus_->write(TLC59208F_SWRST_ADDR >> 1, TLC59208F_SWRST_SEQ, 2) != i2c::ERROR_OK) {
if (this->bus_->write_readv(TLC59208F_SWRST_ADDR >> 1, TLC59208F_SWRST_SEQ, sizeof TLC59208F_SWRST_SEQ, nullptr, 0) !=
i2c::ERROR_OK) {
ESP_LOGE(TAG, "RESET failed");
this->mark_failed();
return;

View File

@@ -14,14 +14,12 @@ void VEML3235Sensor::setup() {
this->mark_failed();
return;
}
if ((this->write(&ID_REG, 1, false) != i2c::ERROR_OK) || !this->read_bytes_raw(device_id, 2)) {
if ((this->read_register(ID_REG, device_id, sizeof device_id) != i2c::ERROR_OK)) {
ESP_LOGE(TAG, "Unable to read ID");
this->mark_failed();
return;
} else if (device_id[0] != DEVICE_ID) {
ESP_LOGE(TAG, "Incorrect device ID - expected 0x%.2x, read 0x%.2x", DEVICE_ID, device_id[0]);
this->mark_failed();
return;
}
}
@@ -49,7 +47,7 @@ float VEML3235Sensor::read_lx_() {
}
uint8_t als_regs[] = {0, 0};
if ((this->write(&ALS_REG, 1, false) != i2c::ERROR_OK) || !this->read_bytes_raw(als_regs, 2)) {
if ((this->read_register(ALS_REG, als_regs, sizeof als_regs) != i2c::ERROR_OK)) {
this->status_set_warning();
return NAN;
}

View File

@@ -279,20 +279,18 @@ ErrorCode VEML7700Component::reconfigure_time_and_gain_(IntegrationTime time, Ga
}
ErrorCode VEML7700Component::read_sensor_output_(Readings &data) {
auto als_err =
this->read_register((uint8_t) CommandRegisters::ALS, (uint8_t *) &data.als_counts, VEML_REG_SIZE, false);
auto als_err = this->read_register((uint8_t) CommandRegisters::ALS, (uint8_t *) &data.als_counts, VEML_REG_SIZE);
if (als_err != i2c::ERROR_OK) {
ESP_LOGW(TAG, "Error reading ALS register, err = %d", als_err);
}
auto white_err =
this->read_register((uint8_t) CommandRegisters::WHITE, (uint8_t *) &data.white_counts, VEML_REG_SIZE, false);
this->read_register((uint8_t) CommandRegisters::WHITE, (uint8_t *) &data.white_counts, VEML_REG_SIZE);
if (white_err != i2c::ERROR_OK) {
ESP_LOGW(TAG, "Error reading WHITE register, err = %d", white_err);
}
ConfigurationRegister conf{0};
auto err =
this->read_register((uint8_t) CommandRegisters::ALS_CONF_0, (uint8_t *) conf.raw_bytes, VEML_REG_SIZE, false);
auto err = this->read_register((uint8_t) CommandRegisters::ALS_CONF_0, (uint8_t *) conf.raw_bytes, VEML_REG_SIZE);
if (err != i2c::ERROR_OK) {
ESP_LOGW(TAG, "Error reading ALS_CONF_0 register, err = %d", white_err);
}

View File

@@ -3,7 +3,6 @@
#include "esphome/components/i2c/i2c.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/core/component.h"
#include "esphome/core/optional.h"
namespace esphome {
namespace veml7700 {

View File

@@ -52,9 +52,9 @@ def default_url(config: ConfigType) -> ConfigType:
config = config.copy()
if config[CONF_VERSION] == 1:
if CONF_CSS_URL not in config:
config[CONF_CSS_URL] = "https://esphome.io/_static/webserver-v1.min.css"
config[CONF_CSS_URL] = "https://oi.esphome.io/v1/webserver-v1.min.css"
if CONF_JS_URL not in config:
config[CONF_JS_URL] = "https://esphome.io/_static/webserver-v1.min.js"
config[CONF_JS_URL] = "https://oi.esphome.io/v1/webserver-v1.min.js"
if config[CONF_VERSION] == 2:
if CONF_CSS_URL not in config:
config[CONF_CSS_URL] = ""

View File

@@ -813,7 +813,7 @@ std::string WebServer::cover_state_json_generator(WebServer *web_server, void *s
return web_server->cover_json((cover::Cover *) (source), DETAIL_STATE);
}
std::string WebServer::cover_all_json_generator(WebServer *web_server, void *source) {
return web_server->cover_json((cover::Cover *) (source), DETAIL_STATE);
return web_server->cover_json((cover::Cover *) (source), DETAIL_ALL);
}
std::string WebServer::cover_json(cover::Cover *obj, JsonDetail start_config) {
return json::build_json([this, obj, start_config](JsonObject root) {

View File

@@ -173,14 +173,14 @@ class WebServer : public Controller, public Component, public AsyncWebHandler {
#if USE_WEBSERVER_VERSION == 1
/** Set the URL to the CSS <link> that's sent to each client. Defaults to
* https://esphome.io/_static/webserver-v1.min.css
* https://oi.esphome.io/v1/webserver-v1.min.css
*
* @param css_url The url to the web server stylesheet.
*/
void set_css_url(const char *css_url);
/** Set the URL to the script that's embedded in the index page. Defaults to
* https://esphome.io/_static/webserver-v1.min.js
* https://oi.esphome.io/v1/webserver-v1.min.js
*
* @param js_url The url to the web server script.
*/

View File

@@ -253,7 +253,7 @@ bool AsyncWebServerRequest::authenticate(const char *username, const char *passw
esp_crypto_base64_encode(reinterpret_cast<uint8_t *>(digest.get()), n, &out,
reinterpret_cast<const uint8_t *>(user_info.c_str()), user_info.size());
return strncmp(digest.get(), auth_str + auth_prefix_len, auth.value().size() - auth_prefix_len) == 0;
return strcmp(digest.get(), auth_str + auth_prefix_len) == 0;
}
void AsyncWebServerRequest::requestAuthentication(const char *realm) const {

View File

@@ -375,11 +375,16 @@ async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
cg.add(var.set_use_address(config[CONF_USE_ADDRESS]))
# Track if any network uses Enterprise authentication
has_eap = False
def add_sta(ap, network):
ip_config = network.get(CONF_MANUAL_IP, config.get(CONF_MANUAL_IP))
cg.add(var.add_sta(wifi_network(network, ap, ip_config)))
for network in config.get(CONF_NETWORKS, []):
if CONF_EAP in network:
has_eap = True
cg.with_local_variable(network[CONF_ID], WiFiAP(), add_sta, network)
if CONF_AP in config:
@@ -396,6 +401,10 @@ async def to_code(config):
add_idf_sdkconfig_option("CONFIG_ESP_WIFI_SOFTAP_SUPPORT", False)
add_idf_sdkconfig_option("CONFIG_LWIP_DHCPS", False)
# Disable Enterprise WiFi support if no EAP is configured
if CORE.is_esp32 and CORE.using_esp_idf and not has_eap:
add_idf_sdkconfig_option("CONFIG_ESP_WIFI_ENTERPRISE_SUPPORT", False)
cg.add(var.set_reboot_timeout(config[CONF_REBOOT_TIMEOUT]))
cg.add(var.set_power_save_mode(config[CONF_POWER_SAVE_MODE]))
cg.add(var.set_fast_connect(config[CONF_FAST_CONNECT]))

View File

@@ -151,6 +151,8 @@ void WiFiComponent::loop() {
this->status_set_warning("waiting to reconnect");
if (millis() - this->action_started_ > 5000) {
if (this->fast_connect_ || this->retry_hidden_) {
if (!this->selected_ap_.get_bssid().has_value())
this->selected_ap_ = this->sta_[0];
this->start_connecting(this->selected_ap_, false);
} else {
this->start_scanning();
@@ -670,10 +672,12 @@ void WiFiComponent::check_connecting_finished() {
return;
}
ESP_LOGI(TAG, "Connected");
// We won't retry hidden networks unless a reconnect fails more than three times again
if (this->retry_hidden_ && !this->selected_ap_.get_hidden())
ESP_LOGW(TAG, "Network '%s' should be marked as hidden", this->selected_ap_.get_ssid().c_str());
this->retry_hidden_ = false;
ESP_LOGI(TAG, "Connected");
this->print_connect_params_();
if (this->has_ap()) {

View File

@@ -547,8 +547,6 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_
}
case ESPHOME_EVENT_ID_WIFI_STA_STOP: {
ESP_LOGV(TAG, "STA stop");
// Clear the STA interface handle to prevent use-after-free
s_sta_netif = nullptr;
break;
}
case ESPHOME_EVENT_ID_WIFI_STA_CONNECTED: {
@@ -638,10 +636,6 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_
}
case ESPHOME_EVENT_ID_WIFI_AP_STOP: {
ESP_LOGV(TAG, "AP stop");
#ifdef USE_WIFI_AP
// Clear the AP interface handle to prevent use-after-free
s_ap_netif = nullptr;
#endif
break;
}
case ESPHOME_EVENT_ID_WIFI_AP_STACONNECTED: {

View File

@@ -697,8 +697,6 @@ void WiFiComponent::wifi_process_event_(IDFWiFiEvent *data) {
} else if (data->event_base == WIFI_EVENT && data->event_id == WIFI_EVENT_STA_STOP) {
ESP_LOGV(TAG, "STA stop");
s_sta_started = false;
// Clear the STA interface handle to prevent use-after-free
s_sta_netif = nullptr;
} else if (data->event_base == WIFI_EVENT && data->event_id == WIFI_EVENT_STA_AUTHMODE_CHANGE) {
const auto &it = data->data.sta_authmode_change;
@@ -797,10 +795,6 @@ void WiFiComponent::wifi_process_event_(IDFWiFiEvent *data) {
} else if (data->event_base == WIFI_EVENT && data->event_id == WIFI_EVENT_AP_STOP) {
ESP_LOGV(TAG, "AP stop");
s_ap_started = false;
#ifdef USE_WIFI_AP
// Clear the AP interface handle to prevent use-after-free
s_ap_netif = nullptr;
#endif
} else if (data->event_base == WIFI_EVENT && data->event_id == WIFI_EVENT_AP_PROBEREQRECVED) {
const auto &it = data->data.ap_probe_req_rx;

View File

@@ -393,10 +393,13 @@ def icon(value):
)
def sub_device_id(value: str | None) -> core.ID:
def sub_device_id(value: str | None) -> core.ID | None:
# Lazy import to avoid circular imports
from esphome.core.config import Device
if not value:
return None
return use_id(Device)(value)

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