Compare commits

..

1 Commits

Author SHA1 Message Date
J. Nick Koston
635cb08e63 Set ble tx power 2025-08-28 14:41:18 -05:00
169 changed files with 782 additions and 2128 deletions

View File

@@ -17,7 +17,7 @@ runs:
steps:
- name: Set up Python ${{ inputs.python-version }}
id: python
uses: actions/setup-python@v6.0.0
uses: actions/setup-python@v5.6.0
with:
python-version: ${{ inputs.python-version }}
- name: Restore Python virtual environment

View File

@@ -32,7 +32,7 @@ jobs:
private-key: ${{ secrets.ESPHOME_GITHUB_APP_PRIVATE_KEY }}
- name: Auto Label PR
uses: actions/github-script@v8.0.0
uses: actions/github-script@v7.0.1
with:
github-token: ${{ steps.generate-token.outputs.token }}
script: |

View File

@@ -23,7 +23,7 @@ jobs:
- name: Checkout
uses: actions/checkout@v5.0.0
- name: Set up Python
uses: actions/setup-python@v6.0.0
uses: actions/setup-python@v5.6.0
with:
python-version: "3.11"
@@ -47,7 +47,7 @@ jobs:
fi
- if: failure()
name: Review PR
uses: actions/github-script@v8.0.0
uses: actions/github-script@v7.0.1
with:
script: |
await github.rest.pulls.createReview({
@@ -70,7 +70,7 @@ jobs:
esphome/components/api/api_pb2_service.*
- if: success()
name: Dismiss review
uses: actions/github-script@v8.0.0
uses: actions/github-script@v7.0.1
with:
script: |
let reviews = await github.rest.pulls.listReviews({

View File

@@ -23,7 +23,7 @@ jobs:
uses: actions/checkout@v5.0.0
- name: Set up Python
uses: actions/setup-python@v6.0.0
uses: actions/setup-python@v5.6.0
with:
python-version: "3.11"
@@ -41,7 +41,7 @@ jobs:
- if: failure()
name: Request changes
uses: actions/github-script@v8.0.0
uses: actions/github-script@v7.0.1
with:
script: |
await github.rest.pulls.createReview({
@@ -54,7 +54,7 @@ jobs:
- if: success()
name: Dismiss review
uses: actions/github-script@v8.0.0
uses: actions/github-script@v7.0.1
with:
script: |
let reviews = await github.rest.pulls.listReviews({

View File

@@ -45,7 +45,7 @@ jobs:
steps:
- uses: actions/checkout@v5.0.0
- name: Set up Python
uses: actions/setup-python@v6.0.0
uses: actions/setup-python@v5.6.0
with:
python-version: "3.11"
- name: Set up Docker Buildx

View File

@@ -42,7 +42,7 @@ jobs:
run: echo key="${{ hashFiles('requirements.txt', 'requirements_test.txt', '.pre-commit-config.yaml') }}" >> $GITHUB_OUTPUT
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
id: python
uses: actions/setup-python@v6.0.0
uses: actions/setup-python@v5.6.0
with:
python-version: ${{ env.DEFAULT_PYTHON }}
- name: Restore Python virtual environment
@@ -156,7 +156,7 @@ jobs:
. venv/bin/activate
pytest -vv --cov-report=xml --tb=native -n auto tests --ignore=tests/integration/
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v5.5.1
uses: codecov/codecov-action@v5.5.0
with:
token: ${{ secrets.CODECOV_TOKEN }}
- name: Save Python virtual environment cache
@@ -217,7 +217,7 @@ jobs:
uses: actions/checkout@v5.0.0
- name: Set up Python 3.13
id: python
uses: actions/setup-python@v6.0.0
uses: actions/setup-python@v5.6.0
with:
python-version: "3.13"
- name: Restore Python virtual environment

View File

@@ -25,7 +25,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Request reviews from component codeowners
uses: actions/github-script@v8.0.0
uses: actions/github-script@v7.0.1
with:
script: |
const owner = context.repo.owner;

View File

@@ -15,7 +15,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Add external component comment
uses: actions/github-script@v8.0.0
uses: actions/github-script@v7.0.1
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |

View File

@@ -19,7 +19,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Notify codeowners for component issues
uses: actions/github-script@v8.0.0
uses: actions/github-script@v7.0.1
with:
script: |
const owner = context.repo.owner;

View File

@@ -62,7 +62,7 @@ jobs:
steps:
- uses: actions/checkout@v5.0.0
- name: Set up Python
uses: actions/setup-python@v6.0.0
uses: actions/setup-python@v5.6.0
with:
python-version: "3.x"
- name: Build
@@ -70,7 +70,7 @@ jobs:
pip3 install build
python3 -m build
- name: Publish
uses: pypa/gh-action-pypi-publish@v1.13.0
uses: pypa/gh-action-pypi-publish@v1.12.4
with:
skip-existing: true
@@ -94,7 +94,7 @@ jobs:
steps:
- uses: actions/checkout@v5.0.0
- name: Set up Python
uses: actions/setup-python@v6.0.0
uses: actions/setup-python@v5.6.0
with:
python-version: "3.11"
@@ -220,7 +220,7 @@ jobs:
- deploy-manifest
steps:
- name: Trigger Workflow
uses: actions/github-script@v8.0.0
uses: actions/github-script@v7.0.1
with:
github-token: ${{ secrets.DEPLOY_HA_ADDON_REPO_TOKEN }}
script: |
@@ -246,7 +246,7 @@ jobs:
environment: ${{ needs.init.outputs.deploy_env }}
steps:
- name: Trigger Workflow
uses: actions/github-script@v8.0.0
uses: actions/github-script@v7.0.1
with:
github-token: ${{ secrets.DEPLOY_ESPHOME_SCHEMA_REPO_TOKEN }}
script: |

View File

@@ -17,7 +17,7 @@ jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v10.0.0
- uses: actions/stale@v9.1.0
with:
days-before-pr-stale: 90
days-before-pr-close: 7
@@ -37,7 +37,7 @@ jobs:
close-issues:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v10.0.0
- uses: actions/stale@v9.1.0
with:
days-before-pr-stale: -1
days-before-pr-close: -1

View File

@@ -16,7 +16,7 @@ jobs:
- merge-after-release
steps:
- name: Check for ${{ matrix.label }} label
uses: actions/github-script@v8.0.0
uses: actions/github-script@v7.0.1
with:
script: |
const { data: labels } = await github.rest.issues.listLabelsOnIssue({

View File

@@ -22,7 +22,7 @@ jobs:
path: lib/home-assistant
- name: Setup Python
uses: actions/setup-python@v6.0.0
uses: actions/setup-python@v5.6.0
with:
python-version: 3.13

View File

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

View File

@@ -89,7 +89,6 @@ esphome/components/bp5758d/* @Cossid
esphome/components/button/* @esphome/core
esphome/components/bytebuffer/* @clydebarrow
esphome/components/camera/* @DT-art1 @bdraco
esphome/components/camera_encoder/* @DT-art1
esphome/components/canbus/* @danielschramm @mvturnho
esphome/components/cap1188/* @mreditor97
esphome/components/captive_portal/* @esphome/core

View File

@@ -61,10 +61,11 @@ void AbsoluteHumidityComponent::loop() {
ESP_LOGW(TAG, "No valid state from temperature sensor!");
}
if (no_humidity) {
ESP_LOGW(TAG, "No valid state from humidity sensor!");
ESP_LOGW(TAG, "No valid state from temperature sensor!");
}
ESP_LOGW(TAG, "Unable to calculate absolute humidity.");
this->publish_state(NAN);
this->status_set_warning("Unable to calculate absolute humidity.");
this->status_set_warning();
return;
}
@@ -86,8 +87,9 @@ void AbsoluteHumidityComponent::loop() {
es = es_wobus(temperature_c);
break;
default:
ESP_LOGE(TAG, "Invalid saturation vapor pressure equation selection!");
this->publish_state(NAN);
this->status_set_error("Invalid saturation vapor pressure equation selection!");
this->status_set_error();
return;
}
ESP_LOGD(TAG, "Saturation vapor pressure %f kPa", es);

View File

@@ -13,7 +13,7 @@ from esphome.const import (
CONF_TRIGGER_ID,
CONF_WEB_SERVER,
)
from esphome.core import CORE, CoroPriority, coroutine_with_priority
from esphome.core import CORE, coroutine_with_priority
from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity
from esphome.cpp_generator import MockObjClass
@@ -345,6 +345,6 @@ async def alarm_control_panel_is_armed_to_code(
return cg.new_Pvariable(condition_id, template_arg, paren)
@coroutine_with_priority(CoroPriority.CORE)
@coroutine_with_priority(100.0)
async def to_code(config):
cg.add_global(alarm_control_panel_ns.using)

View File

@@ -24,7 +24,7 @@ from esphome.const import (
CONF_TRIGGER_ID,
CONF_VARIABLES,
)
from esphome.core import CORE, CoroPriority, coroutine_with_priority
from esphome.core import CORE, coroutine_with_priority
DOMAIN = "api"
DEPENDENCIES = ["network"]
@@ -134,7 +134,7 @@ CONFIG_SCHEMA = cv.All(
)
@coroutine_with_priority(CoroPriority.WEB)
@coroutine_with_priority(40.0)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)

View File

@@ -1712,7 +1712,6 @@ message BluetoothScannerStateResponse {
BluetoothScannerState state = 1;
BluetoothScannerMode mode = 2;
BluetoothScannerMode configured_mode = 3;
}
message BluetoothScannerSetModeRequest {

View File

@@ -2153,12 +2153,10 @@ void BluetoothDeviceClearCacheResponse::calculate_size(ProtoSize &size) const {
void BluetoothScannerStateResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_uint32(1, static_cast<uint32_t>(this->state));
buffer.encode_uint32(2, static_cast<uint32_t>(this->mode));
buffer.encode_uint32(3, static_cast<uint32_t>(this->configured_mode));
}
void BluetoothScannerStateResponse::calculate_size(ProtoSize &size) const {
size.add_uint32(1, static_cast<uint32_t>(this->state));
size.add_uint32(1, static_cast<uint32_t>(this->mode));
size.add_uint32(1, static_cast<uint32_t>(this->configured_mode));
}
bool BluetoothScannerSetModeRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {

View File

@@ -2214,13 +2214,12 @@ class BluetoothDeviceClearCacheResponse final : public ProtoMessage {
class BluetoothScannerStateResponse final : public ProtoMessage {
public:
static constexpr uint8_t MESSAGE_TYPE = 126;
static constexpr uint8_t ESTIMATED_SIZE = 6;
static constexpr uint8_t ESTIMATED_SIZE = 4;
#ifdef HAS_PROTO_MESSAGE_DUMP
const char *message_name() const override { return "bluetooth_scanner_state_response"; }
#endif
enums::BluetoothScannerState state{};
enums::BluetoothScannerMode mode{};
enums::BluetoothScannerMode configured_mode{};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(ProtoSize &size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP

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", static_cast<bool>(it), 4);
dump_field(out, "bool_array", it, 4);
}
for (const auto &it : this->int_array) {
dump_field(out, "int_array", it, 4);
@@ -1704,7 +1704,6 @@ void BluetoothScannerStateResponse::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "BluetoothScannerStateResponse");
dump_field(out, "state", static_cast<enums::BluetoothScannerState>(this->state));
dump_field(out, "mode", static_cast<enums::BluetoothScannerMode>(this->mode));
dump_field(out, "configured_mode", static_cast<enums::BluetoothScannerMode>(this->configured_mode));
}
void BluetoothScannerSetModeRequest::dump_to(std::string &out) const {
MessageDumpHelper helper(out, "BluetoothScannerSetModeRequest");

View File

@@ -8,7 +8,7 @@ from esphome.const import (
PLATFORM_LN882X,
PLATFORM_RTL87XX,
)
from esphome.core import CORE, CoroPriority, coroutine_with_priority
from esphome.core import CORE, coroutine_with_priority
CODEOWNERS = ["@esphome/core"]
@@ -27,7 +27,7 @@ CONFIG_SCHEMA = cv.All(
)
@coroutine_with_priority(CoroPriority.NETWORK_TRANSPORT)
@coroutine_with_priority(200.0)
async def to_code(config):
if CORE.is_esp32 or CORE.is_libretiny:
# https://github.com/ESP32Async/AsyncTCP

View File

@@ -2,7 +2,7 @@ from esphome import automation
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.const import CONF_ID, CONF_MIC_GAIN
from esphome.core import CoroPriority, coroutine_with_priority
from esphome.core import coroutine_with_priority
CODEOWNERS = ["@kbx81"]
IS_PLATFORM_COMPONENT = True
@@ -35,7 +35,7 @@ async def audio_adc_set_mic_gain_to_code(config, action_id, template_arg, args):
return var
@coroutine_with_priority(CoroPriority.CORE)
@coroutine_with_priority(100.0)
async def to_code(config):
cg.add_define("USE_AUDIO_ADC")
cg.add_global(audio_adc_ns.using)

View File

@@ -3,7 +3,7 @@ from esphome.automation import maybe_simple_id
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.const import CONF_ID, CONF_VOLUME
from esphome.core import CoroPriority, coroutine_with_priority
from esphome.core import coroutine_with_priority
CODEOWNERS = ["@kbx81"]
IS_PLATFORM_COMPONENT = True
@@ -51,7 +51,7 @@ async def audio_dac_set_volume_to_code(config, action_id, template_arg, args):
return var
@coroutine_with_priority(CoroPriority.CORE)
@coroutine_with_priority(100.0)
async def to_code(config):
cg.add_define("USE_AUDIO_DAC")
cg.add_global(audio_dac_ns.using)

View File

@@ -59,7 +59,7 @@ from esphome.const import (
DEVICE_CLASS_VIBRATION,
DEVICE_CLASS_WINDOW,
)
from esphome.core import CORE, CoroPriority, coroutine_with_priority
from esphome.core import CORE, coroutine_with_priority
from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity
from esphome.cpp_generator import MockObjClass
from esphome.util import Registry
@@ -652,7 +652,7 @@ async def binary_sensor_is_off_to_code(config, condition_id, template_arg, args)
return cg.new_Pvariable(condition_id, template_arg, paren, False)
@coroutine_with_priority(CoroPriority.CORE)
@coroutine_with_priority(100.0)
async def to_code(config):
cg.add_global(binary_sensor_ns.using)

View File

@@ -15,8 +15,8 @@ void log_binary_sensor(const char *tag, const char *prefix, const char *type, Bi
ESP_LOGCONFIG(tag, "%s%s '%s'", prefix, type, obj->get_name().c_str());
if (!obj->get_device_class_ref().empty()) {
ESP_LOGCONFIG(tag, "%s Device Class: '%s'", prefix, obj->get_device_class_ref().c_str());
if (!obj->get_device_class().empty()) {
ESP_LOGCONFIG(tag, "%s Device Class: '%s'", prefix, obj->get_device_class().c_str());
}
}

View File

@@ -80,7 +80,7 @@ CONFIG_SCHEMA = cv.All(
cv.Schema(
{
cv.GenerateID(): cv.declare_id(BluetoothProxy),
cv.Optional(CONF_ACTIVE, default=True): cv.boolean,
cv.Optional(CONF_ACTIVE, default=False): cv.boolean,
cv.SplitDefault(CONF_CACHE_SERVICES, esp32_idf=True): cv.All(
cv.only_with_esp_idf, cv.boolean
),

View File

@@ -24,9 +24,6 @@ void BluetoothProxy::setup() {
this->connections_free_response_.limit = BLUETOOTH_PROXY_MAX_CONNECTIONS;
this->connections_free_response_.free = BLUETOOTH_PROXY_MAX_CONNECTIONS;
// Capture the configured scan mode from YAML before any API changes
this->configured_scan_active_ = this->parent_->get_scan_active();
this->parent_->add_scanner_state_callback([this](esp32_ble_tracker::ScannerState state) {
if (this->api_connection_ != nullptr) {
this->send_bluetooth_scanner_state_(state);
@@ -39,9 +36,6 @@ void BluetoothProxy::send_bluetooth_scanner_state_(esp32_ble_tracker::ScannerSta
resp.state = static_cast<api::enums::BluetoothScannerState>(state);
resp.mode = this->parent_->get_scan_active() ? api::enums::BluetoothScannerMode::BLUETOOTH_SCANNER_MODE_ACTIVE
: api::enums::BluetoothScannerMode::BLUETOOTH_SCANNER_MODE_PASSIVE;
resp.configured_mode = this->configured_scan_active_
? api::enums::BluetoothScannerMode::BLUETOOTH_SCANNER_MODE_ACTIVE
: api::enums::BluetoothScannerMode::BLUETOOTH_SCANNER_MODE_PASSIVE;
this->api_connection_->send_message(resp, api::BluetoothScannerStateResponse::MESSAGE_TYPE);
}
@@ -189,12 +183,6 @@ void BluetoothProxy::bluetooth_device_request(const api::BluetoothDeviceRequest
this->send_device_connection(msg.address, false);
return;
}
if (!msg.has_address_type) {
ESP_LOGE(TAG, "[%d] [%s] Missing address type in connect request", connection->get_connection_index(),
connection->address_str().c_str());
this->send_device_connection(msg.address, false);
return;
}
if (connection->state() == espbt::ClientState::CONNECTED ||
connection->state() == espbt::ClientState::ESTABLISHED) {
this->log_connection_request_ignored_(connection, connection->state());
@@ -221,9 +209,13 @@ void BluetoothProxy::bluetooth_device_request(const api::BluetoothDeviceRequest
connection->set_connection_type(espbt::ConnectionType::V3_WITHOUT_CACHE);
this->log_connection_info_(connection, "v3 without cache");
}
uint64_to_bd_addr(msg.address, connection->remote_bda_);
connection->set_remote_addr_type(static_cast<esp_ble_addr_type_t>(msg.address_type));
connection->set_state(espbt::ClientState::DISCOVERED);
if (msg.has_address_type) {
uint64_to_bd_addr(msg.address, connection->remote_bda_);
connection->set_remote_addr_type(static_cast<esp_ble_addr_type_t>(msg.address_type));
connection->set_state(espbt::ClientState::DISCOVERED);
} else {
connection->set_state(espbt::ClientState::SEARCHING);
}
this->send_connections_free();
break;
}

View File

@@ -161,8 +161,7 @@ class BluetoothProxy final : public esp32_ble_tracker::ESPBTDeviceListener, publ
// Group 4: 1-byte types grouped together
bool active_;
uint8_t connection_count_{0};
bool configured_scan_active_{false}; // Configured scan mode from YAML
// 3 bytes used, 1 byte padding
// 2 bytes used, 2 bytes padding
};
extern BluetoothProxy *global_bluetooth_proxy; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)

View File

@@ -17,7 +17,7 @@ from esphome.const import (
DEVICE_CLASS_RESTART,
DEVICE_CLASS_UPDATE,
)
from esphome.core import CORE, CoroPriority, coroutine_with_priority
from esphome.core import CORE, coroutine_with_priority
from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity
from esphome.cpp_generator import MockObjClass
@@ -134,6 +134,6 @@ async def button_press_to_code(config, action_id, template_arg, args):
return cg.new_Pvariable(action_id, template_arg, paren)
@coroutine_with_priority(CoroPriority.CORE)
@coroutine_with_priority(100.0)
async def to_code(config):
cg.add_global(button_ns.using)

View File

@@ -14,8 +14,8 @@ void log_button(const char *tag, const char *prefix, const char *type, Button *o
ESP_LOGCONFIG(tag, "%s%s '%s'", prefix, type, obj->get_name().c_str());
if (!obj->get_icon_ref().empty()) {
ESP_LOGCONFIG(tag, "%s Icon: '%s'", prefix, obj->get_icon_ref().c_str());
if (!obj->get_icon().empty()) {
ESP_LOGCONFIG(tag, "%s Icon: '%s'", prefix, obj->get_icon().c_str());
}
}

View File

@@ -1,18 +0,0 @@
#pragma once
#include <cinttypes>
#include <cstddef>
namespace esphome::camera {
/// Interface for a generic buffer that stores image data.
class Buffer {
public:
/// Returns a pointer to the buffer's data.
virtual uint8_t *get_data_buffer() = 0;
/// Returns the length of the buffer in bytes.
virtual size_t get_data_length() = 0;
virtual ~Buffer() = default;
};
} // namespace esphome::camera

View File

@@ -1,20 +0,0 @@
#include "buffer_impl.h"
namespace esphome::camera {
BufferImpl::BufferImpl(size_t size) {
this->data_ = this->allocator_.allocate(size);
this->size_ = size;
}
BufferImpl::BufferImpl(CameraImageSpec *spec) {
this->data_ = this->allocator_.allocate(spec->bytes_per_image());
this->size_ = spec->bytes_per_image();
}
BufferImpl::~BufferImpl() {
if (this->data_ != nullptr)
this->allocator_.deallocate(this->data_, this->size_);
}
} // namespace esphome::camera

View File

@@ -1,26 +0,0 @@
#pragma once
#include "buffer.h"
#include "camera.h"
namespace esphome::camera {
/// Default implementation of Buffer Interface.
/// Uses a RAMAllocator for memory reservation.
class BufferImpl : public Buffer {
public:
explicit BufferImpl(size_t size);
explicit BufferImpl(CameraImageSpec *spec);
// -------- Buffer --------
uint8_t *get_data_buffer() override { return data_; }
size_t get_data_length() override { return size_; }
// ------------------------
~BufferImpl() override;
protected:
RAMAllocator<uint8_t> allocator_;
size_t size_{};
uint8_t *data_{};
};
} // namespace esphome::camera

View File

@@ -15,26 +15,6 @@ namespace camera {
*/
enum CameraRequester : uint8_t { IDLE, API_REQUESTER, WEB_REQUESTER };
/// Enumeration of different pixel formats.
enum PixelFormat : uint8_t {
PIXEL_FORMAT_GRAYSCALE = 0, ///< 8-bit grayscale.
PIXEL_FORMAT_RGB565, ///< 16-bit RGB (5-6-5).
PIXEL_FORMAT_BGR888, ///< RGB pixel data in 8-bit format, stored as B, G, R (1 byte each).
};
/// Returns string name for a given PixelFormat.
inline const char *to_string(PixelFormat format) {
switch (format) {
case PIXEL_FORMAT_GRAYSCALE:
return "PIXEL_FORMAT_GRAYSCALE";
case PIXEL_FORMAT_RGB565:
return "PIXEL_FORMAT_RGB565";
case PIXEL_FORMAT_BGR888:
return "PIXEL_FORMAT_BGR888";
}
return "PIXEL_FORMAT_UNKNOWN";
}
/** Abstract camera image base class.
* Encapsulates the JPEG encoded data and it is shared among
* all connected clients.
@@ -63,29 +43,6 @@ class CameraImageReader {
virtual ~CameraImageReader() {}
};
/// Specification of a caputured camera image.
/// This struct defines the format and size details for images captured
/// or processed by a camera component.
struct CameraImageSpec {
uint16_t width;
uint16_t height;
PixelFormat format;
size_t bytes_per_pixel() {
switch (format) {
case PIXEL_FORMAT_GRAYSCALE:
return 1;
case PIXEL_FORMAT_RGB565:
return 2;
case PIXEL_FORMAT_BGR888:
return 3;
}
return 1;
}
size_t bytes_per_row() { return bytes_per_pixel() * width; }
size_t bytes_per_image() { return bytes_per_pixel() * width * height; }
};
/** Abstract camera base class. Collaborates with API.
* 1) API server starts and installs callback (add_image_callback)
* which is called by the camera when a new image is available.

View File

@@ -1,69 +0,0 @@
#pragma once
#include "buffer.h"
#include "camera.h"
namespace esphome::camera {
/// Result codes from the encoder used to control camera pipeline flow.
enum EncoderError : uint8_t {
ENCODER_ERROR_SUCCESS = 0, ///< Encoding succeeded, continue pipeline normally.
ENCODER_ERROR_SKIP_FRAME, ///< Skip current frame, try again on next frame.
ENCODER_ERROR_RETRY_FRAME, ///< Retry current frame, after buffer growth or for incremental encoding.
ENCODER_ERROR_CONFIGURATION ///< Fatal config error, shut down pipeline.
};
/// Converts EncoderError to string.
inline const char *to_string(EncoderError error) {
switch (error) {
case ENCODER_ERROR_SUCCESS:
return "ENCODER_ERROR_SUCCESS";
case ENCODER_ERROR_SKIP_FRAME:
return "ENCODER_ERROR_SKIP_FRAME";
case ENCODER_ERROR_RETRY_FRAME:
return "ENCODER_ERROR_RETRY_FRAME";
case ENCODER_ERROR_CONFIGURATION:
return "ENCODER_ERROR_CONFIGURATION";
}
return "ENCODER_ERROR_INVALID";
}
/// Interface for an encoder buffer supporting resizing and variable-length data.
class EncoderBuffer {
public:
/// Sets logical buffer size, reallocates if needed.
/// @param size Required size in bytes.
/// @return true on success, false on allocation failure.
virtual bool set_buffer_size(size_t size) = 0;
/// Returns a pointer to the buffer data.
virtual uint8_t *get_data() const = 0;
/// Returns number of bytes currently used.
virtual size_t get_size() const = 0;
/// Returns total allocated buffer size.
virtual size_t get_max_size() const = 0;
virtual ~EncoderBuffer() = default;
};
/// Interface for image encoders used in a camera pipeline.
class Encoder {
public:
/// Encodes pixel data from a previous camera pipeline stage.
/// @param spec Specification of the input pixel data.
/// @param pixels Image pixels in RGB or grayscale format, as specified in @p spec.
/// @return EncoderError Indicating the result of the encoding operation.
virtual EncoderError encode_pixels(CameraImageSpec *spec, Buffer *pixels) = 0;
/// Returns the encoder's output buffer.
/// @return Pointer to an EncoderBuffer containing encoded data.
virtual EncoderBuffer *get_output_buffer() = 0;
/// Prints the encoder's configuration to the log.
virtual void dump_config() = 0;
virtual ~Encoder() = default;
};
} // namespace esphome::camera

View File

@@ -1,62 +0,0 @@
import esphome.codegen as cg
from esphome.components.esp32 import add_idf_component
import esphome.config_validation as cv
from esphome.const import CONF_BUFFER_SIZE, CONF_ID, CONF_TYPE
from esphome.core import CORE
from esphome.types import ConfigType
CODEOWNERS = ["@DT-art1"]
AUTO_LOAD = ["camera"]
CONF_BUFFER_EXPAND_SIZE = "buffer_expand_size"
CONF_ENCODER_BUFFER_ID = "encoder_buffer_id"
CONF_QUALITY = "quality"
ESP32_CAMERA_ENCODER = "esp32_camera"
camera_ns = cg.esphome_ns.namespace("camera")
camera_encoder_ns = cg.esphome_ns.namespace("camera_encoder")
Encoder = camera_ns.class_("Encoder")
EncoderBufferImpl = camera_encoder_ns.class_("EncoderBufferImpl")
ESP32CameraJPEGEncoder = camera_encoder_ns.class_("ESP32CameraJPEGEncoder", Encoder)
MAX_JPEG_BUFFER_SIZE_2MB = 2 * 1024 * 1024
ESP32_CAMERA_ENCODER_SCHEMA = cv.Schema(
{
cv.GenerateID(): cv.declare_id(ESP32CameraJPEGEncoder),
cv.Optional(CONF_QUALITY, default=80): cv.int_range(1, 100),
cv.Optional(CONF_BUFFER_SIZE, default=4096): cv.int_range(
1024, MAX_JPEG_BUFFER_SIZE_2MB
),
cv.Optional(CONF_BUFFER_EXPAND_SIZE, default=1024): cv.int_range(
0, MAX_JPEG_BUFFER_SIZE_2MB
),
cv.GenerateID(CONF_ENCODER_BUFFER_ID): cv.declare_id(EncoderBufferImpl),
}
)
CONFIG_SCHEMA = cv.typed_schema(
{
ESP32_CAMERA_ENCODER: ESP32_CAMERA_ENCODER_SCHEMA,
},
default_type=ESP32_CAMERA_ENCODER,
)
async def to_code(config: ConfigType) -> None:
buffer = cg.new_Pvariable(config[CONF_ENCODER_BUFFER_ID])
cg.add(buffer.set_buffer_size(config[CONF_BUFFER_SIZE]))
if config[CONF_TYPE] == ESP32_CAMERA_ENCODER:
if CORE.using_esp_idf:
add_idf_component(name="espressif/esp32-camera", ref="2.1.0")
cg.add_build_flag("-DUSE_ESP32_CAMERA_JPEG_ENCODER")
var = cg.new_Pvariable(
config[CONF_ID],
config[CONF_QUALITY],
buffer,
)
cg.add(var.set_buffer_expand_size(config[CONF_BUFFER_EXPAND_SIZE]))

View File

@@ -1,23 +0,0 @@
#include "encoder_buffer_impl.h"
namespace esphome::camera_encoder {
bool EncoderBufferImpl::set_buffer_size(size_t size) {
if (size > this->capacity_) {
uint8_t *p = this->allocator_.reallocate(this->data_, size);
if (p == nullptr)
return false;
this->data_ = p;
this->capacity_ = size;
}
this->size_ = size;
return true;
}
EncoderBufferImpl::~EncoderBufferImpl() {
if (this->data_ != nullptr)
this->allocator_.deallocate(this->data_, this->capacity_);
}
} // namespace esphome::camera_encoder

View File

@@ -1,25 +0,0 @@
#pragma once
#include "esphome/components/camera/encoder.h"
#include "esphome/core/helpers.h"
namespace esphome::camera_encoder {
class EncoderBufferImpl : public camera::EncoderBuffer {
public:
// --- EncoderBuffer ---
bool set_buffer_size(size_t size) override;
uint8_t *get_data() const override { return this->data_; }
size_t get_size() const override { return this->size_; }
size_t get_max_size() const override { return this->capacity_; }
// ----------------------
~EncoderBufferImpl() override;
protected:
RAMAllocator<uint8_t> allocator_;
size_t capacity_{};
size_t size_{};
uint8_t *data_{};
};
} // namespace esphome::camera_encoder

View File

@@ -1,82 +0,0 @@
#ifdef USE_ESP32_CAMERA_JPEG_ENCODER
#include "esp32_camera_jpeg_encoder.h"
namespace esphome::camera_encoder {
static const char *const TAG = "camera_encoder";
ESP32CameraJPEGEncoder::ESP32CameraJPEGEncoder(uint8_t quality, camera::EncoderBuffer *output) {
this->quality_ = quality;
this->output_ = output;
}
camera::EncoderError ESP32CameraJPEGEncoder::encode_pixels(camera::CameraImageSpec *spec, camera::Buffer *pixels) {
this->bytes_written_ = 0;
this->out_of_output_memory_ = false;
bool success = fmt2jpg_cb(pixels->get_data_buffer(), pixels->get_data_length(), spec->width, spec->height,
to_internal_(spec->format), this->quality_, callback_, this);
if (!success)
return camera::ENCODER_ERROR_CONFIGURATION;
if (this->out_of_output_memory_) {
if (this->buffer_expand_size_ <= 0)
return camera::ENCODER_ERROR_SKIP_FRAME;
size_t current_size = this->output_->get_max_size();
size_t new_size = this->output_->get_max_size() + this->buffer_expand_size_;
if (!this->output_->set_buffer_size(new_size)) {
ESP_LOGE(TAG, "Failed to expand output buffer.");
this->buffer_expand_size_ = 0;
return camera::ENCODER_ERROR_SKIP_FRAME;
}
ESP_LOGD(TAG, "Output buffer expanded (%u -> %u).", current_size, this->output_->get_max_size());
return camera::ENCODER_ERROR_RETRY_FRAME;
}
this->output_->set_buffer_size(this->bytes_written_);
return camera::ENCODER_ERROR_SUCCESS;
}
void ESP32CameraJPEGEncoder::dump_config() {
ESP_LOGCONFIG(TAG,
"ESP32 Camera JPEG Encoder:\n"
" Size: %zu\n"
" Quality: %d\n"
" Expand: %d\n",
this->output_->get_max_size(), this->quality_, this->buffer_expand_size_);
}
size_t ESP32CameraJPEGEncoder::callback_(void *arg, size_t index, const void *data, size_t len) {
ESP32CameraJPEGEncoder *that = reinterpret_cast<ESP32CameraJPEGEncoder *>(arg);
uint8_t *buffer = that->output_->get_data();
size_t buffer_length = that->output_->get_max_size();
if (index + len > buffer_length) {
that->out_of_output_memory_ = true;
return 0;
}
std::memcpy(&buffer[index], data, len);
that->bytes_written_ += len;
return len;
}
pixformat_t ESP32CameraJPEGEncoder::to_internal_(camera::PixelFormat format) {
switch (format) {
case camera::PIXEL_FORMAT_GRAYSCALE:
return PIXFORMAT_GRAYSCALE;
case camera::PIXEL_FORMAT_RGB565:
return PIXFORMAT_RGB565;
// Internal representation for RGB is in byte order: B, G, R
case camera::PIXEL_FORMAT_BGR888:
return PIXFORMAT_RGB888;
}
return PIXFORMAT_GRAYSCALE;
}
} // namespace esphome::camera_encoder
#endif

View File

@@ -1,39 +0,0 @@
#pragma once
#ifdef USE_ESP32_CAMERA_JPEG_ENCODER
#include <esp_camera.h>
#include "esphome/components/camera/encoder.h"
namespace esphome::camera_encoder {
/// Encoder that uses the software-based JPEG implementation from Espressif's esp32-camera component.
class ESP32CameraJPEGEncoder : public camera::Encoder {
public:
/// Constructs a ESP32CameraJPEGEncoder instance.
/// @param quality Sets the quality of the encoded image (1-100).
/// @param output Pointer to preallocated output buffer.
ESP32CameraJPEGEncoder(uint8_t quality, camera::EncoderBuffer *output);
/// Sets the number of bytes to expand the output buffer on underflow during encoding.
/// @param buffer_expand_size Number of bytes to expand the buffer.
void set_buffer_expand_size(size_t buffer_expand_size) { this->buffer_expand_size_ = buffer_expand_size; }
// -------- Encoder --------
camera::EncoderError encode_pixels(camera::CameraImageSpec *spec, camera::Buffer *pixels) override;
camera::EncoderBuffer *get_output_buffer() override { return output_; }
void dump_config() override;
// -------------------------
protected:
static size_t callback_(void *arg, size_t index, const void *data, size_t len);
pixformat_t to_internal_(camera::PixelFormat format);
camera::EncoderBuffer *output_{};
size_t buffer_expand_size_{};
size_t bytes_written_{};
uint8_t quality_{};
bool out_of_output_memory_{};
};
} // namespace esphome::camera_encoder
#endif

View File

@@ -10,7 +10,7 @@ from esphome.const import (
PLATFORM_LN882X,
PLATFORM_RTL87XX,
)
from esphome.core import CORE, CoroPriority, coroutine_with_priority
from esphome.core import CORE, coroutine_with_priority
AUTO_LOAD = ["web_server_base", "ota.web_server"]
DEPENDENCIES = ["wifi"]
@@ -40,7 +40,7 @@ CONFIG_SCHEMA = cv.All(
)
@coroutine_with_priority(CoroPriority.COMMUNICATION)
@coroutine_with_priority(64.0)
async def to_code(config):
paren = await cg.get_variable(config[CONF_WEB_SERVER_BASE_ID])

View File

@@ -47,7 +47,7 @@ from esphome.const import (
CONF_VISUAL,
CONF_WEB_SERVER,
)
from esphome.core import CORE, CoroPriority, coroutine_with_priority
from esphome.core import CORE, coroutine_with_priority
from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity
from esphome.cpp_generator import MockObjClass
@@ -517,6 +517,6 @@ async def climate_control_to_code(config, action_id, template_arg, args):
return var
@coroutine_with_priority(CoroPriority.CORE)
@coroutine_with_priority(100.0)
async def to_code(config):
cg.add_global(climate_ns.using)

View File

@@ -32,7 +32,7 @@ from esphome.const import (
DEVICE_CLASS_SHUTTER,
DEVICE_CLASS_WINDOW,
)
from esphome.core import CORE, CoroPriority, coroutine_with_priority
from esphome.core import CORE, coroutine_with_priority
from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity
from esphome.cpp_generator import MockObjClass
@@ -263,6 +263,6 @@ async def cover_control_to_code(config, action_id, template_arg, args):
return var
@coroutine_with_priority(CoroPriority.CORE)
@coroutine_with_priority(100.0)
async def to_code(config):
cg.add_global(cover_ns.using)

View File

@@ -19,8 +19,8 @@ const extern float COVER_CLOSED;
if (traits_.get_is_assumed_state()) { \
ESP_LOGCONFIG(TAG, "%s Assumed State: YES", prefix); \
} \
if (!(obj)->get_device_class_ref().empty()) { \
ESP_LOGCONFIG(TAG, "%s Device Class: '%s'", prefix, (obj)->get_device_class_ref().c_str()); \
if (!(obj)->get_device_class().empty()) { \
ESP_LOGCONFIG(TAG, "%s Device Class: '%s'", prefix, (obj)->get_device_class().c_str()); \
} \
}

View File

@@ -21,7 +21,7 @@ from esphome.const import (
CONF_WEB_SERVER,
CONF_YEAR,
)
from esphome.core import CORE, CoroPriority, coroutine_with_priority
from esphome.core import CORE, coroutine_with_priority
from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity
from esphome.cpp_generator import MockObjClass
@@ -172,7 +172,7 @@ async def new_datetime(config, *args):
return var
@coroutine_with_priority(CoroPriority.CORE)
@coroutine_with_priority(100.0)
async def to_code(config):
cg.add_global(datetime_ns.using)

View File

@@ -16,8 +16,8 @@ namespace datetime {
#define LOG_DATETIME_DATE(prefix, type, obj) \
if ((obj) != nullptr) { \
ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, LOG_STR_LITERAL(type), (obj)->get_name().c_str()); \
if (!(obj)->get_icon_ref().empty()) { \
ESP_LOGCONFIG(TAG, "%s Icon: '%s'", prefix, (obj)->get_icon_ref().c_str()); \
if (!(obj)->get_icon().empty()) { \
ESP_LOGCONFIG(TAG, "%s Icon: '%s'", prefix, (obj)->get_icon().c_str()); \
} \
}

View File

@@ -16,8 +16,8 @@ namespace datetime {
#define LOG_DATETIME_DATETIME(prefix, type, obj) \
if ((obj) != nullptr) { \
ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, LOG_STR_LITERAL(type), (obj)->get_name().c_str()); \
if (!(obj)->get_icon_ref().empty()) { \
ESP_LOGCONFIG(TAG, "%s Icon: '%s'", prefix, (obj)->get_icon_ref().c_str()); \
if (!(obj)->get_icon().empty()) { \
ESP_LOGCONFIG(TAG, "%s Icon: '%s'", prefix, (obj)->get_icon().c_str()); \
} \
}

View File

@@ -16,8 +16,8 @@ namespace datetime {
#define LOG_DATETIME_TIME(prefix, type, obj) \
if ((obj) != nullptr) { \
ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, LOG_STR_LITERAL(type), (obj)->get_name().c_str()); \
if (!(obj)->get_icon_ref().empty()) { \
ESP_LOGCONFIG(TAG, "%s Icon: '%s'", prefix, (obj)->get_icon_ref().c_str()); \
if (!(obj)->get_icon().empty()) { \
ESP_LOGCONFIG(TAG, "%s Icon: '%s'", prefix, (obj)->get_icon().c_str()); \
} \
}

View File

@@ -15,7 +15,7 @@ from esphome.const import (
CONF_UPDATE_INTERVAL,
SCHEDULER_DONT_RUN,
)
from esphome.core import CoroPriority, coroutine_with_priority
from esphome.core import coroutine_with_priority
IS_PLATFORM_COMPONENT = True
@@ -176,7 +176,7 @@ async def display_page_show_to_code(config, action_id, template_arg, args):
DisplayPageShowNextAction,
maybe_simple_id(
{
cv.GenerateID(CONF_ID): cv.templatable(cv.use_id(Display)),
cv.Required(CONF_ID): cv.templatable(cv.use_id(Display)),
}
),
)
@@ -190,7 +190,7 @@ async def display_page_show_next_to_code(config, action_id, template_arg, args):
DisplayPageShowPrevAction,
maybe_simple_id(
{
cv.GenerateID(CONF_ID): cv.templatable(cv.use_id(Display)),
cv.Required(CONF_ID): cv.templatable(cv.use_id(Display)),
}
),
)
@@ -218,7 +218,7 @@ async def display_is_displaying_page_to_code(config, condition_id, template_arg,
return var
@coroutine_with_priority(CoroPriority.CORE)
@coroutine_with_priority(100.0)
async def to_code(config):
cg.add_global(display_ns.using)
cg.add_define("USE_DISPLAY")

View File

@@ -40,7 +40,6 @@ 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
@@ -841,9 +840,6 @@ 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 +855,11 @@ async def to_code(config):
cg.add_platformio_option("platform_packages", [conf[CONF_SOURCE]])
# platformio/toolchain-esp32ulp does not support linux_aarch64 yet and has not been updated for over 2 years
# This is espressif's own published version which is more up to date.
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
@@ -1078,11 +1079,7 @@ def _write_idf_component_yml():
contents = yaml_util.dump({"dependencies": dependencies})
else:
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()
write_file_if_changed(yml_path, contents)
# Called by writer.py

View File

@@ -3,7 +3,17 @@ import re
from esphome import automation
import esphome.codegen as cg
from esphome.components.esp32 import add_idf_sdkconfig_option, const, get_esp32_variant
from esphome.components.esp32 import (
VARIANT_ESP32C2,
VARIANT_ESP32C3,
VARIANT_ESP32C5,
VARIANT_ESP32C6,
VARIANT_ESP32H2,
VARIANT_ESP32S3,
add_idf_sdkconfig_option,
const,
get_esp32_variant,
)
import esphome.config_validation as cv
from esphome.const import (
CONF_ENABLE_ON_BOOT,
@@ -11,8 +21,10 @@ from esphome.const import (
CONF_ID,
CONF_NAME,
CONF_NAME_ADD_MAC_SUFFIX,
CONF_TX_POWER,
)
from esphome.core import CORE, TimePeriod
from esphome.cpp_types import MockObj
import esphome.final_validate as fv
DEPENDENCIES = ["esp32"]
@@ -151,7 +163,8 @@ IO_CAPABILITY = {
esp_power_level_t = cg.global_ns.enum("esp_power_level_t")
TX_POWER_LEVELS = {
# Power level mappings for code generation - ESP32 classic
TX_POWER_LEVELS_ESP32 = {
-12: esp_power_level_t.ESP_PWR_LVL_N12,
-9: esp_power_level_t.ESP_PWR_LVL_N9,
-6: esp_power_level_t.ESP_PWR_LVL_N6,
@@ -162,6 +175,53 @@ TX_POWER_LEVELS = {
9: esp_power_level_t.ESP_PWR_LVL_P9,
}
# Power level mappings for code generation - Extended variants
TX_POWER_LEVELS_EXT = {
-24: esp_power_level_t.ESP_PWR_LVL_N24,
-21: esp_power_level_t.ESP_PWR_LVL_N21,
-18: esp_power_level_t.ESP_PWR_LVL_N18,
-15: esp_power_level_t.ESP_PWR_LVL_N15,
-12: esp_power_level_t.ESP_PWR_LVL_N12,
-9: esp_power_level_t.ESP_PWR_LVL_N9,
-6: esp_power_level_t.ESP_PWR_LVL_N6,
-3: esp_power_level_t.ESP_PWR_LVL_N3,
0: esp_power_level_t.ESP_PWR_LVL_N0,
3: esp_power_level_t.ESP_PWR_LVL_P3,
6: esp_power_level_t.ESP_PWR_LVL_P6,
9: esp_power_level_t.ESP_PWR_LVL_P9,
12: esp_power_level_t.ESP_PWR_LVL_P12,
15: esp_power_level_t.ESP_PWR_LVL_P15,
18: esp_power_level_t.ESP_PWR_LVL_P18,
20: esp_power_level_t.ESP_PWR_LVL_P20,
}
def _get_tx_power_levels() -> dict[str, MockObj]:
variant = get_esp32_variant()
if variant in [
VARIANT_ESP32C2,
VARIANT_ESP32C3,
VARIANT_ESP32C5,
VARIANT_ESP32C6,
VARIANT_ESP32H2,
VARIANT_ESP32S3,
]:
return TX_POWER_LEVELS_EXT
return TX_POWER_LEVELS_ESP32
def validate_tx_power(value: int) -> int:
value = cv.decibel(value)
power_levels = _get_tx_power_levels()
if value not in power_levels:
raise cv.Invalid(
f"TX power {value}dBm is not valid. "
f"Valid values are: {', '.join(str(v) + 'dBm' for v in sorted(power_levels.keys()))}"
)
# Return just the dBm value, we'll map it to enum in to_code
return value
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(): cv.declare_id(ESP32BLE),
@@ -169,6 +229,7 @@ CONFIG_SCHEMA = cv.Schema(
cv.Optional(CONF_IO_CAPABILITY, default="none"): cv.enum(
IO_CAPABILITY, lower=True
),
cv.Optional(CONF_TX_POWER): validate_tx_power,
cv.Optional(CONF_ENABLE_ON_BOOT, default=True): cv.boolean,
cv.Optional(CONF_ADVERTISING, default=False): cv.boolean,
cv.Optional(
@@ -259,6 +320,9 @@ async def to_code(config):
cg.add(var.set_advertising_cycle_time(config[CONF_ADVERTISING_CYCLE_TIME]))
if (name := config.get(CONF_NAME)) is not None:
cg.add(var.set_name(name))
if (tx_power := config.get(CONF_TX_POWER)) is not None:
# The validation already returned the enum value
cg.add(var.set_tx_power(_get_tx_power_levels()[tx_power]))
await cg.register_component(var, config)
if CORE.using_esp_idf:

View File

@@ -212,6 +212,15 @@ bool ESP32BLE::ble_setup_() {
return false;
}
// Set TX power for all BLE operations (advertising, scanning, connections)
err = esp_ble_tx_power_set(ESP_BLE_PWR_TYPE_DEFAULT, this->tx_power_);
if (err != ESP_OK) {
ESP_LOGW(TAG, "esp_ble_tx_power_set failed: %s", esp_err_to_name(err));
// Continue anyway as this is not critical
} else {
ESP_LOGD(TAG, "BLE TX power set to level %d", this->tx_power_);
}
// BLE takes some time to be fully set up, 200ms should be more than enough
delay(200); // NOLINT
@@ -520,11 +529,106 @@ void ESP32BLE::dump_config() {
io_capability_s = "invalid";
break;
}
// Convert TX power level to dBm for display
int tx_power_dbm = 0;
#if defined(CONFIG_IDF_TARGET_ESP32)
// ESP32 classic power levels (0-7)
switch (this->tx_power_) {
case 0:
tx_power_dbm = -12;
break; // ESP_PWR_LVL_N12
case 1:
tx_power_dbm = -9;
break; // ESP_PWR_LVL_N9
case 2:
tx_power_dbm = -6;
break; // ESP_PWR_LVL_N6
case 3:
tx_power_dbm = -3;
break; // ESP_PWR_LVL_N3
case 4:
tx_power_dbm = 0;
break; // ESP_PWR_LVL_N0
case 5:
tx_power_dbm = 3;
break; // ESP_PWR_LVL_P3
case 6:
tx_power_dbm = 6;
break; // ESP_PWR_LVL_P6
case 7:
tx_power_dbm = 9;
break; // ESP_PWR_LVL_P9
default:
tx_power_dbm = 0;
break;
}
#elif defined(CONFIG_IDF_TARGET_ESP32C2) || defined(CONFIG_IDF_TARGET_ESP32C3) || \
defined(CONFIG_IDF_TARGET_ESP32C5) || defined(CONFIG_IDF_TARGET_ESP32C6) || defined(CONFIG_IDF_TARGET_ESP32H2) || \
defined(CONFIG_IDF_TARGET_ESP32S3)
// Extended power levels for C2/C3/C5/C6/H2/S3 (0-15)
switch (this->tx_power_) {
case 0:
tx_power_dbm = -24;
break; // ESP_PWR_LVL_N24
case 1:
tx_power_dbm = -21;
break; // ESP_PWR_LVL_N21
case 2:
tx_power_dbm = -18;
break; // ESP_PWR_LVL_N18
case 3:
tx_power_dbm = -15;
break; // ESP_PWR_LVL_N15
case 4:
tx_power_dbm = -12;
break; // ESP_PWR_LVL_N12
case 5:
tx_power_dbm = -9;
break; // ESP_PWR_LVL_N9
case 6:
tx_power_dbm = -6;
break; // ESP_PWR_LVL_N6
case 7:
tx_power_dbm = -3;
break; // ESP_PWR_LVL_N3
case 8:
tx_power_dbm = 0;
break; // ESP_PWR_LVL_N0
case 9:
tx_power_dbm = 3;
break; // ESP_PWR_LVL_P3
case 10:
tx_power_dbm = 6;
break; // ESP_PWR_LVL_P6
case 11:
tx_power_dbm = 9;
break; // ESP_PWR_LVL_P9
case 12:
tx_power_dbm = 12;
break; // ESP_PWR_LVL_P12
case 13:
tx_power_dbm = 15;
break; // ESP_PWR_LVL_P15
case 14:
tx_power_dbm = 18;
break; // ESP_PWR_LVL_P18
case 15:
tx_power_dbm = 20;
break; // ESP_PWR_LVL_P20
default:
tx_power_dbm = 0;
break;
}
#else
// Unknown variant
tx_power_dbm = 0;
#endif
ESP_LOGCONFIG(TAG,
"BLE:\n"
" MAC address: %s\n"
" IO Capability: %s",
format_mac_address_pretty(mac_address).c_str(), io_capability_s);
" IO Capability: %s\n"
" TX Power: %d dBm",
format_mac_address_pretty(mac_address).c_str(), io_capability_s, tx_power_dbm);
} else {
ESP_LOGCONFIG(TAG, "Bluetooth stack is not enabled");
}

View File

@@ -20,6 +20,7 @@
#ifdef USE_ESP32
#include <esp_bt.h>
#include <esp_gap_ble_api.h>
#include <esp_gattc_api.h>
#include <esp_gatts_api.h>
@@ -94,6 +95,7 @@ class BLEStatusEventHandler {
class ESP32BLE : public Component {
public:
void set_io_capability(IoCapability io_capability) { this->io_cap_ = (esp_ble_io_cap_t) io_capability; }
void set_tx_power(esp_power_level_t tx_power) { this->tx_power_ = tx_power; }
void set_advertising_cycle_time(uint32_t advertising_cycle_time) {
this->advertising_cycle_time_ = advertising_cycle_time;
@@ -172,6 +174,7 @@ class ESP32BLE : public Component {
// 1-byte aligned members (grouped together to minimize padding)
BLEComponentState state_{BLE_COMPONENT_STATE_OFF}; // 1 byte (uint8_t enum)
bool enable_on_boot_{}; // 1 byte
esp_power_level_t tx_power_{ESP_PWR_LVL_P9}; // 1 byte (default: +9 dBm)
};
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)

View File

@@ -93,7 +93,7 @@ bool BLEClientBase::parse_device(const espbt::ESPBTDevice &device) {
return false;
if (this->address_ == 0 || device.address_uint64() != this->address_)
return false;
if (this->state_ != espbt::ClientState::IDLE)
if (this->state_ != espbt::ClientState::IDLE && this->state_ != espbt::ClientState::SEARCHING)
return false;
this->log_event_("Found device");
@@ -168,7 +168,8 @@ void BLEClientBase::unconditional_disconnect() {
this->log_gattc_warning_("esp_ble_gattc_close", err);
}
if (this->state_ == espbt::ClientState::READY_TO_CONNECT || this->state_ == espbt::ClientState::DISCOVERED) {
if (this->state_ == espbt::ClientState::SEARCHING || this->state_ == espbt::ClientState::READY_TO_CONNECT ||
this->state_ == espbt::ClientState::DISCOVERED) {
this->set_address(0);
this->set_state(espbt::ClientState::IDLE);
} else {

View File

@@ -30,7 +30,7 @@ from esphome.const import (
CONF_SERVICE_UUID,
CONF_TRIGGER_ID,
)
from esphome.core import CORE, CoroPriority, coroutine_with_priority
from esphome.core import CORE, coroutine_with_priority
from esphome.enum import StrEnum
from esphome.types import ConfigType
@@ -368,7 +368,7 @@ async def to_code(config):
# This needs to be run as a job with very low priority so that all components have
# chance to call register_ble_tracker and register_client before the list is checked
# and added to the global defines list.
@coroutine_with_priority(CoroPriority.FINAL)
@coroutine_with_priority(-1000)
async def _add_ble_features():
# Add feature-specific defines based on what's needed
if BLEFeatures.ESP_BT_DEVICE in _required_features:

View File

@@ -49,6 +49,8 @@ const char *client_state_to_string(ClientState state) {
return "DISCONNECTING";
case ClientState::IDLE:
return "IDLE";
case ClientState::SEARCHING:
return "SEARCHING";
case ClientState::DISCOVERED:
return "DISCOVERED";
case ClientState::READY_TO_CONNECT:
@@ -134,8 +136,9 @@ void ESP32BLETracker::loop() {
ClientStateCounts counts = this->count_client_states_();
if (counts != this->client_state_counts_) {
this->client_state_counts_ = counts;
ESP_LOGD(TAG, "connecting: %d, discovered: %d, disconnecting: %d", this->client_state_counts_.connecting,
this->client_state_counts_.discovered, this->client_state_counts_.disconnecting);
ESP_LOGD(TAG, "connecting: %d, discovered: %d, searching: %d, disconnecting: %d",
this->client_state_counts_.connecting, this->client_state_counts_.discovered,
this->client_state_counts_.searching, this->client_state_counts_.disconnecting);
}
if (this->scanner_state_ == ScannerState::FAILED ||
@@ -155,8 +158,10 @@ void ESP32BLETracker::loop() {
https://github.com/espressif/esp-idf/issues/6688
*/
bool promote_to_connecting = counts.discovered && !counts.searching && !counts.connecting;
if (this->scanner_state_ == ScannerState::IDLE && !counts.connecting && !counts.disconnecting && !counts.discovered) {
if (this->scanner_state_ == ScannerState::IDLE && !counts.connecting && !counts.disconnecting &&
!promote_to_connecting) {
#ifdef USE_ESP32_BLE_SOFTWARE_COEXISTENCE
this->update_coex_preference_(false);
#endif
@@ -165,11 +170,12 @@ void ESP32BLETracker::loop() {
}
}
// If there is a discovered client and no connecting
// clients, then promote the discovered client to ready to connect.
// clients and no clients using the scanner to search for
// devices, then promote the discovered client to ready to connect.
// We check both RUNNING and IDLE states because:
// - RUNNING: gap_scan_event_handler initiates stop_scan_() but promotion can happen immediately
// - IDLE: Scanner has already stopped (naturally or by gap_scan_event_handler)
if (counts.discovered && !counts.connecting &&
if (promote_to_connecting &&
(this->scanner_state_ == ScannerState::RUNNING || this->scanner_state_ == ScannerState::IDLE)) {
this->try_promote_discovered_clients_();
}
@@ -301,7 +307,14 @@ void ESP32BLETracker::gap_scan_event_handler(const BLEScanResult &scan_result) {
if (scan_result.search_evt == ESP_GAP_SEARCH_INQ_RES_EVT) {
// Process the scan result immediately
this->process_scan_result_(scan_result);
bool found_discovered_client = this->process_scan_result_(scan_result);
// If we found a discovered client that needs promotion, stop scanning
// This replaces the promote_to_connecting logic from loop()
if (found_discovered_client && this->scanner_state_ == ScannerState::RUNNING) {
ESP_LOGD(TAG, "Found discovered client, stopping scan for connection");
this->stop_scan_();
}
} else if (scan_result.search_evt == ESP_GAP_SEARCH_INQ_CMPL_EVT) {
// Scan finished on its own
if (this->scanner_state_ != ScannerState::RUNNING) {
@@ -627,8 +640,9 @@ void ESP32BLETracker::dump_config() {
this->scan_duration_, this->scan_interval_ * 0.625f, this->scan_window_ * 0.625f,
this->scan_active_ ? "ACTIVE" : "PASSIVE", YESNO(this->scan_continuous_));
ESP_LOGCONFIG(TAG, " Scanner State: %s", this->scanner_state_to_string_(this->scanner_state_));
ESP_LOGCONFIG(TAG, " Connecting: %d, discovered: %d, disconnecting: %d", this->client_state_counts_.connecting,
this->client_state_counts_.discovered, this->client_state_counts_.disconnecting);
ESP_LOGCONFIG(TAG, " Connecting: %d, discovered: %d, searching: %d, disconnecting: %d",
this->client_state_counts_.connecting, this->client_state_counts_.discovered,
this->client_state_counts_.searching, this->client_state_counts_.disconnecting);
if (this->scan_start_fail_count_) {
ESP_LOGCONFIG(TAG, " Scan Start Fail Count: %d", this->scan_start_fail_count_);
}
@@ -706,9 +720,20 @@ bool ESPBTDevice::resolve_irk(const uint8_t *irk) const {
ecb_ciphertext[13] == ((addr64 >> 16) & 0xff);
}
bool ESP32BLETracker::has_connecting_clients_() const {
for (auto *client : this->clients_) {
auto state = client->state();
if (state == ClientState::CONNECTING || state == ClientState::READY_TO_CONNECT) {
return true;
}
}
return false;
}
#endif // USE_ESP32_BLE_DEVICE
void ESP32BLETracker::process_scan_result_(const BLEScanResult &scan_result) {
bool ESP32BLETracker::process_scan_result_(const BLEScanResult &scan_result) {
bool found_discovered_client = false;
// Process raw advertisements
if (this->raw_advertisements_) {
for (auto *listener : this->listeners_) {
@@ -734,6 +759,14 @@ void ESP32BLETracker::process_scan_result_(const BLEScanResult &scan_result) {
for (auto *client : this->clients_) {
if (client->parse_device(device)) {
found = true;
// Check if this client is discovered and needs promotion
if (client->state() == ClientState::DISCOVERED) {
// Only check for connecting clients if we found a discovered client
// This matches the original logic: !connecting && client->state() == DISCOVERED
if (!this->has_connecting_clients_()) {
found_discovered_client = true;
}
}
}
}
@@ -742,6 +775,8 @@ void ESP32BLETracker::process_scan_result_(const BLEScanResult &scan_result) {
}
#endif // USE_ESP32_BLE_DEVICE
}
return found_discovered_client;
}
void ESP32BLETracker::cleanup_scan_state_(bool is_stop_complete) {

View File

@@ -141,10 +141,12 @@ class ESPBTDeviceListener {
struct ClientStateCounts {
uint8_t connecting = 0;
uint8_t discovered = 0;
uint8_t searching = 0;
uint8_t disconnecting = 0;
bool operator==(const ClientStateCounts &other) const {
return connecting == other.connecting && discovered == other.discovered && disconnecting == other.disconnecting;
return connecting == other.connecting && discovered == other.discovered && searching == other.searching &&
disconnecting == other.disconnecting;
}
bool operator!=(const ClientStateCounts &other) const { return !(*this == other); }
@@ -157,6 +159,8 @@ enum class ClientState : uint8_t {
DISCONNECTING,
// Connection is idle, no device detected.
IDLE,
// Searching for device.
SEARCHING,
// Device advertisement found.
DISCOVERED,
// Device is discovered and the scanner is stopped
@@ -288,7 +292,12 @@ class ESP32BLETracker : public Component,
/// Common cleanup logic when transitioning scanner to IDLE state
void cleanup_scan_state_(bool is_stop_complete);
/// Process a single scan result immediately
void process_scan_result_(const BLEScanResult &scan_result);
/// Returns true if a discovered client needs promotion to READY_TO_CONNECT
bool process_scan_result_(const BLEScanResult &scan_result);
#ifdef USE_ESP32_BLE_DEVICE
/// Check if any clients are in connecting or ready to connect state
bool has_connecting_clients_() const;
#endif
/// Handle scanner failure states
void handle_scanner_failure_();
/// Try to promote discovered clients to ready to connect
@@ -312,6 +321,9 @@ class ESP32BLETracker : public Component,
case ClientState::DISCOVERED:
counts.discovered++;
break;
case ClientState::SEARCHING:
counts.searching++;
break;
case ClientState::CONNECTING:
case ClientState::READY_TO_CONNECT:
counts.connecting++;

View File

@@ -17,7 +17,7 @@ from esphome.const import (
PLATFORM_ESP8266,
ThreadModel,
)
from esphome.core import CORE, CoroPriority, coroutine_with_priority
from esphome.core import CORE, coroutine_with_priority
from esphome.helpers import copy_file_if_changed
from .boards import BOARDS, ESP8266_LD_SCRIPTS
@@ -176,7 +176,7 @@ CONFIG_SCHEMA = cv.All(
)
@coroutine_with_priority(CoroPriority.PLATFORM)
@coroutine_with_priority(1000)
async def to_code(config):
cg.add(esp8266_ns.setup_preferences())

View File

@@ -58,8 +58,8 @@ extern "C" void resetPins() { // NOLINT
#ifdef USE_ESP8266_EARLY_PIN_INIT
for (int i = 0; i < 16; i++) {
uint8_t mode = progmem_read_byte(&ESPHOME_ESP8266_GPIO_INITIAL_MODE[i]);
uint8_t level = progmem_read_byte(&ESPHOME_ESP8266_GPIO_INITIAL_LEVEL[i]);
uint8_t mode = ESPHOME_ESP8266_GPIO_INITIAL_MODE[i];
uint8_t level = ESPHOME_ESP8266_GPIO_INITIAL_LEVEL[i];
if (mode != 255)
pinMode(i, mode); // NOLINT
if (level != 255)

View File

@@ -17,7 +17,7 @@ from esphome.const import (
CONF_PULLUP,
PLATFORM_ESP8266,
)
from esphome.core import CORE, CoroPriority, coroutine_with_priority
from esphome.core import CORE, coroutine_with_priority
from . import boards
from .const import KEY_BOARD, KEY_ESP8266, KEY_PIN_INITIAL_STATES, esp8266_ns
@@ -188,7 +188,7 @@ async def esp8266_pin_to_code(config):
return var
@coroutine_with_priority(CoroPriority.WORKAROUNDS)
@coroutine_with_priority(-999.0)
async def add_pin_initial_states_array():
# Add includes at the very end, so that they override everything
initial_states: list[PinInitialState] = CORE.data[KEY_ESP8266][
@@ -199,11 +199,11 @@ async def add_pin_initial_states_array():
cg.add_global(
cg.RawExpression(
f"const uint8_t ESPHOME_ESP8266_GPIO_INITIAL_MODE[16] PROGMEM = {{{initial_modes_s}}}"
f"const uint8_t ESPHOME_ESP8266_GPIO_INITIAL_MODE[16] = {{{initial_modes_s}}}"
)
)
cg.add_global(
cg.RawExpression(
f"const uint8_t ESPHOME_ESP8266_GPIO_INITIAL_LEVEL[16] PROGMEM = {{{initial_levels_s}}}"
f"const uint8_t ESPHOME_ESP8266_GPIO_INITIAL_LEVEL[16] = {{{initial_levels_s}}}"
)
)

View File

@@ -1,7 +1,6 @@
#ifdef USE_ESP8266
#include <c_types.h>
#include <cinttypes>
extern "C" {
#include "spi_flash.h"
}
@@ -120,16 +119,16 @@ static bool load_from_rtc(size_t offset, uint32_t *data, size_t len) {
class ESP8266PreferenceBackend : public ESPPreferenceBackend {
public:
size_t offset = 0;
uint32_t type = 0;
uint16_t offset = 0;
uint8_t length_words = 0; // Max 255 words (1020 bytes of data)
bool in_flash = false;
size_t length_words = 0;
bool save(const uint8_t *data, size_t len) override {
if (bytes_to_words(len) != length_words) {
return false;
}
size_t buffer_size = static_cast<size_t>(length_words) + 1;
size_t buffer_size = length_words + 1;
std::unique_ptr<uint32_t[]> buffer(new uint32_t[buffer_size]()); // Note the () for zero-initialization
memcpy(buffer.get(), data, len);
buffer[length_words] = calculate_crc(buffer.get(), buffer.get() + length_words, type);
@@ -143,7 +142,7 @@ class ESP8266PreferenceBackend : public ESPPreferenceBackend {
if (bytes_to_words(len) != length_words) {
return false;
}
size_t buffer_size = static_cast<size_t>(length_words) + 1;
size_t buffer_size = length_words + 1;
std::unique_ptr<uint32_t[]> buffer(new uint32_t[buffer_size]());
bool ret = in_flash ? load_from_flash(offset, buffer.get(), buffer_size)
: load_from_rtc(offset, buffer.get(), buffer_size);
@@ -177,19 +176,15 @@ class ESP8266Preferences : public ESPPreferences {
ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash) override {
uint32_t length_words = bytes_to_words(length);
if (length_words > 255) {
ESP_LOGE(TAG, "Preference too large: %" PRIu32 " words > 255", length_words);
return {};
}
if (in_flash) {
uint32_t start = current_flash_offset;
uint32_t end = start + length_words + 1;
if (end > ESP8266_FLASH_STORAGE_SIZE)
return {};
auto *pref = new ESP8266PreferenceBackend(); // NOLINT(cppcoreguidelines-owning-memory)
pref->offset = static_cast<uint16_t>(start);
pref->offset = start;
pref->type = type;
pref->length_words = static_cast<uint8_t>(length_words);
pref->length_words = length_words;
pref->in_flash = true;
current_flash_offset = end;
return {pref};
@@ -215,9 +210,9 @@ class ESP8266Preferences : public ESPPreferences {
uint32_t rtc_offset = in_normal ? start + 32 : start - 96;
auto *pref = new ESP8266PreferenceBackend(); // NOLINT(cppcoreguidelines-owning-memory)
pref->offset = static_cast<uint16_t>(rtc_offset);
pref->offset = rtc_offset;
pref->type = type;
pref->length_words = static_cast<uint8_t>(length_words);
pref->length_words = length_words;
pref->in_flash = false;
current_offset += length_words + 1;
return pref;

View File

@@ -16,7 +16,7 @@ from esphome.const import (
CONF_SAFE_MODE,
CONF_VERSION,
)
from esphome.core import CoroPriority, coroutine_with_priority
from esphome.core import coroutine_with_priority
import esphome.final_validate as fv
_LOGGER = logging.getLogger(__name__)
@@ -121,7 +121,7 @@ CONFIG_SCHEMA = (
FINAL_VALIDATE_SCHEMA = ota_esphome_final_validate
@coroutine_with_priority(CoroPriority.COMMUNICATION)
@coroutine_with_priority(52.0)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
cg.add(var.set_port(config[CONF_PORT]))

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 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.
/// It waits for the first magic byte (0x6C) 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,7 +126,6 @@ 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
@@ -137,47 +136,34 @@ void ESPHomeOTAComponent::handle_handshake_() {
return;
}
// 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);
// Try to read first byte of magic bytes
uint8_t first_byte;
ssize_t read = this->client_->read(&first_byte, 1);
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 magic bytes");
} else {
ESP_LOGW(TAG, "Remote closed during handshake");
}
this->cleanup_connection_();
return;
}
this->magic_buf_pos_ += read;
if (read == -1 && (errno == EAGAIN || errno == EWOULDBLOCK)) {
return; // No data yet, try again next loop
}
// 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;
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");
}
// All 5 magic bytes are valid, continue with data handling
this->handle_data_();
this->cleanup_connection_();
return;
}
// 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;
}
// First byte is valid, continue with data handling
this->handle_data_();
}
void ESPHomeOTAComponent::handle_data_() {
@@ -200,6 +186,18 @@ 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;
@@ -489,7 +487,6 @@ 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,13 +41,11 @@ 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

@@ -38,12 +38,7 @@ from esphome.const import (
KEY_CORE,
KEY_FRAMEWORK_VERSION,
)
from esphome.core import (
CORE,
CoroPriority,
TimePeriodMilliseconds,
coroutine_with_priority,
)
from esphome.core import CORE, TimePeriodMilliseconds, coroutine_with_priority
import esphome.final_validate as fv
CONFLICTS_WITH = ["wifi"]
@@ -294,7 +289,7 @@ def phy_register(address: int, value: int, page: int):
)
@coroutine_with_priority(CoroPriority.COMMUNICATION)
@coroutine_with_priority(60.0)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)

View File

@@ -17,7 +17,7 @@ from esphome.const import (
DEVICE_CLASS_EMPTY,
DEVICE_CLASS_MOTION,
)
from esphome.core import CORE, CoroPriority, coroutine_with_priority
from esphome.core import CORE, coroutine_with_priority
from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity
from esphome.cpp_generator import MockObjClass
@@ -143,6 +143,6 @@ async def event_fire_to_code(config, action_id, template_arg, args):
return var
@coroutine_with_priority(CoroPriority.CORE)
@coroutine_with_priority(100.0)
async def to_code(config):
cg.add_global(event_ns.using)

View File

@@ -13,11 +13,11 @@ namespace event {
#define LOG_EVENT(prefix, type, obj) \
if ((obj) != nullptr) { \
ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, LOG_STR_LITERAL(type), (obj)->get_name().c_str()); \
if (!(obj)->get_icon_ref().empty()) { \
ESP_LOGCONFIG(TAG, "%s Icon: '%s'", prefix, (obj)->get_icon_ref().c_str()); \
if (!(obj)->get_icon().empty()) { \
ESP_LOGCONFIG(TAG, "%s Icon: '%s'", prefix, (obj)->get_icon().c_str()); \
} \
if (!(obj)->get_device_class_ref().empty()) { \
ESP_LOGCONFIG(TAG, "%s Device Class: '%s'", prefix, (obj)->get_device_class_ref().c_str()); \
if (!(obj)->get_device_class().empty()) { \
ESP_LOGCONFIG(TAG, "%s Device Class: '%s'", prefix, (obj)->get_device_class().c_str()); \
} \
}

View File

@@ -31,7 +31,7 @@ from esphome.const import (
CONF_TRIGGER_ID,
CONF_WEB_SERVER,
)
from esphome.core import CORE, CoroPriority, coroutine_with_priority
from esphome.core import CORE, coroutine_with_priority
from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity
IS_PLATFORM_COMPONENT = True
@@ -398,6 +398,6 @@ async def fan_is_on_off_to_code(config, condition_id, template_arg, args):
return cg.new_Pvariable(condition_id, template_arg, paren)
@coroutine_with_priority(CoroPriority.CORE)
@coroutine_with_priority(100.0)
async def to_code(config):
cg.add_global(fan_ns.using)

View File

@@ -8,7 +8,7 @@ from esphome.const import (
CONF_TYPE,
CONF_VALUE,
)
from esphome.core import CoroPriority, coroutine_with_priority
from esphome.core import coroutine_with_priority
CODEOWNERS = ["@esphome/core"]
globals_ns = cg.esphome_ns.namespace("globals")
@@ -35,7 +35,7 @@ CONFIG_SCHEMA = cv.Schema(
# Run with low priority so that namespaces are registered first
@coroutine_with_priority(CoroPriority.LATE)
@coroutine_with_priority(-100.0)
async def to_code(config):
type_ = cg.RawExpression(config[CONF_TYPE])
restore = config[CONF_RESTORE_VALUE]

View File

@@ -42,10 +42,9 @@ class HostPreferences : public ESPPreferences {
if (len > 255)
return false;
this->setup_();
auto it = this->data.find(key);
if (it == this->data.end())
if (this->data.count(key) == 0)
return false;
const auto &vec = it->second;
auto vec = this->data[key];
if (vec.size() != len)
return false;
memcpy(data, vec.data(), len);

View File

@@ -3,7 +3,7 @@ import esphome.codegen as cg
from esphome.components.ota import BASE_OTA_SCHEMA, OTAComponent, ota_to_code
import esphome.config_validation as cv
from esphome.const import CONF_ID, CONF_PASSWORD, CONF_URL, CONF_USERNAME
from esphome.core import CoroPriority, coroutine_with_priority
from esphome.core import coroutine_with_priority
from .. import CONF_HTTP_REQUEST_ID, HttpRequestComponent, http_request_ns
@@ -40,7 +40,7 @@ CONFIG_SCHEMA = cv.All(
)
@coroutine_with_priority(CoroPriority.COMMUNICATION)
@coroutine_with_priority(52.0)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await ota_to_code(var, config)

View File

@@ -18,7 +18,7 @@ from esphome.const import (
PLATFORM_RP2040,
PlatformFramework,
)
from esphome.core import CORE, CoroPriority, coroutine_with_priority
from esphome.core import CORE, coroutine_with_priority
import esphome.final_validate as fv
LOGGER = logging.getLogger(__name__)
@@ -74,7 +74,7 @@ CONFIG_SCHEMA = cv.All(
)
@coroutine_with_priority(CoroPriority.BUS)
@coroutine_with_priority(1.0)
async def to_code(config):
cg.add_global(i2c_ns.using)
cg.add_define("USE_I2C")

View File

@@ -47,9 +47,9 @@ ErrorCode I2CDevice::write_register(uint8_t a_register, const uint8_t *data, siz
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);
v.push_back(a_register >> 8);
v.push_back(a_register);
v.insert(v.end(), data, data + len);
return bus_->write_readv(this->address_, v.data(), v.size(), nullptr, 0);
}

View File

@@ -1,6 +1,6 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.core import CoroPriority, coroutine_with_priority
from esphome.core import coroutine_with_priority
CODEOWNERS = ["@esphome/core"]
json_ns = cg.esphome_ns.namespace("json")
@@ -10,7 +10,7 @@ CONFIG_SCHEMA = cv.All(
)
@coroutine_with_priority(CoroPriority.BUS)
@coroutine_with_priority(1.0)
async def to_code(config):
cg.add_library("bblanchon/ArduinoJson", "7.4.2")
cg.add_define("USE_JSON")

View File

@@ -37,7 +37,7 @@ from esphome.const import (
CONF_WEB_SERVER,
CONF_WHITE,
)
from esphome.core import CORE, CoroPriority, coroutine_with_priority
from esphome.core import CORE, coroutine_with_priority
from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity
from esphome.cpp_generator import MockObjClass
@@ -283,6 +283,6 @@ async def new_light(config, *args):
return output_var
@coroutine_with_priority(CoroPriority.CORE)
@coroutine_with_priority(100.0)
async def to_code(config):
cg.add_global(light_ns.using)

View File

@@ -13,7 +13,7 @@ from esphome.const import (
CONF_TRIGGER_ID,
CONF_WEB_SERVER,
)
from esphome.core import CORE, CoroPriority, coroutine_with_priority
from esphome.core import CORE, coroutine_with_priority
from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity
from esphome.cpp_generator import MockObjClass
@@ -155,6 +155,6 @@ async def lock_is_off_to_code(config, condition_id, template_arg, args):
return cg.new_Pvariable(condition_id, template_arg, paren, False)
@coroutine_with_priority(CoroPriority.CORE)
@coroutine_with_priority(100.0)
async def to_code(config):
cg.add_global(lock_ns.using)

View File

@@ -15,8 +15,8 @@ class Lock;
#define LOG_LOCK(prefix, type, obj) \
if ((obj) != nullptr) { \
ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, LOG_STR_LITERAL(type), (obj)->get_name().c_str()); \
if (!(obj)->get_icon_ref().empty()) { \
ESP_LOGCONFIG(TAG, "%s Icon: '%s'", prefix, (obj)->get_icon_ref().c_str()); \
if (!(obj)->get_icon().empty()) { \
ESP_LOGCONFIG(TAG, "%s Icon: '%s'", prefix, (obj)->get_icon().c_str()); \
} \
if ((obj)->traits.get_assumed_state()) { \
ESP_LOGCONFIG(TAG, "%s Assumed State: YES", prefix); \

View File

@@ -51,7 +51,7 @@ from esphome.const import (
PLATFORM_RTL87XX,
PlatformFramework,
)
from esphome.core import CORE, CoroPriority, Lambda, coroutine_with_priority
from esphome.core import CORE, Lambda, coroutine_with_priority
CODEOWNERS = ["@esphome/core"]
logger_ns = cg.esphome_ns.namespace("logger")
@@ -275,7 +275,7 @@ CONFIG_SCHEMA = cv.All(
)
@coroutine_with_priority(CoroPriority.DIAGNOSTICS)
@coroutine_with_priority(90.0)
async def to_code(config):
baud_rate = config[CONF_BAUD_RATE]
level = config[CONF_LEVEL]

View File

@@ -451,7 +451,6 @@ CONF_GRID_ROWS = "grid_rows"
CONF_HEADER_MODE = "header_mode"
CONF_HOME = "home"
CONF_INITIAL_FOCUS = "initial_focus"
CONF_SELECTED_DIGIT = "selected_digit"
CONF_KEY_CODE = "key_code"
CONF_KEYPADS = "keypads"
CONF_LAYOUT = "layout"

View File

@@ -4,112 +4,49 @@ from esphome.yaml_util import parse_yaml
CONFIG = """
- obj:
id: hello_world_card_
radius: 0
pad_all: 12
bg_color: white
bg_color: 0xFFFFFF
height: 100%
width: 100%
scrollable: false
widgets:
- obj:
align: top_mid
outline_width: 0
border_width: 0
pad_all: 4
scrollable: false
height: size_content
width: 100%
layout:
type: flex
flex_flow: row
flex_align_cross: center
flex_align_track: start
flex_align_main: space_between
widgets:
- button:
checkable: true
radius: 4
text_font: montserrat_20
on_click:
lvgl.label.update:
id: hello_world_label_
text: "Clicked!"
widgets:
- label:
text: "Button"
- label:
id: hello_world_title_
text: ESPHome
text_font: montserrat_20
width: 100%
text_align: center
on_boot:
lvgl.widget.refresh: hello_world_title_
hidden: !lambda |-
return lv_obj_get_width(lv_scr_act()) < 400;
- checkbox:
text: Checkbox
id: hello_world_checkbox_
on_boot:
lvgl.widget.refresh: hello_world_checkbox_
hidden: !lambda |-
return lv_obj_get_width(lv_scr_act()) < 240;
on_click:
lvgl.label.update:
id: hello_world_label_
text: "Checked!"
- obj:
id: hello_world_container_
- spinner:
id: hello_world_spinner_
align: center
indicator:
arc_color: tomato
height: 100
width: 100
spin_time: 2s
arc_length: 60deg
- label:
id: hello_world_label_
text: "Hello World!"
align: center
y: 14
pad_all: 0
outline_width: 0
border_width: 0
width: 100%
height: size_content
scrollable: false
on_click:
lvgl.spinner.update:
id: hello_world_spinner_
arc_color: springgreen
layout:
type: flex
flex_flow: row_wrap
flex_align_cross: center
flex_align_track: center
flex_align_main: space_evenly
- checkbox:
pad_all: 8
text: Checkbox
align: top_right
on_click:
lvgl.label.update:
id: hello_world_label_
text: "Checked!"
- button:
pad_all: 8
checkable: true
align: top_left
text_font: montserrat_20
on_click:
lvgl.label.update:
id: hello_world_label_
text: "Clicked!"
widgets:
- spinner:
id: hello_world_spinner_
indicator:
arc_color: tomato
height: 100
width: 100
spin_time: 2s
arc_length: 60deg
widgets:
- label:
id: hello_world_label_
text: "Hello World!"
align: center
- obj:
id: hello_world_qrcode_
outline_width: 0
border_width: 0
hidden: !lambda |-
return lv_obj_get_width(lv_scr_act()) < 300 && lv_obj_get_height(lv_scr_act()) < 400;
widgets:
- label:
text_font: montserrat_14
text: esphome.io
align: top_mid
- qrcode:
text: "https://esphome.io"
size: 80
align: bottom_mid
on_boot:
lvgl.widget.refresh: hello_world_qrcode_
- label:
text: "Button"
- slider:
width: 80%
align: bottom_mid

View File

@@ -67,6 +67,7 @@ class Widget:
self.type = wtype
self.config = config
self.scale = 1.0
self.step = 1.0
self.range_from = -sys.maxsize
self.range_to = sys.maxsize
if wtype.is_compound():

View File

@@ -11,7 +11,6 @@ from ..defines import (
CONF_ROLLOVER,
CONF_SCROLLBAR,
CONF_SELECTED,
CONF_SELECTED_DIGIT,
CONF_TEXTAREA_PLACEHOLDER,
)
from ..lv_validation import lv_bool, lv_float
@@ -39,24 +38,18 @@ def validate_spinbox(config):
min_val = -1 - max_val
range_from = int(config[CONF_RANGE_FROM])
range_to = int(config[CONF_RANGE_TO])
step = config[CONF_SELECTED_DIGIT]
digits = config[CONF_DIGITS]
step = int(config[CONF_STEP])
if (
range_from > max_val
or range_from < min_val
or range_to > max_val
or range_to < min_val
):
raise cv.Invalid("Range outside allowed limits", path=[CONF_RANGE_FROM])
if digits <= config[CONF_DECIMAL_PLACES]:
raise cv.Invalid(
"Number of digits must exceed number of decimal places", path=[CONF_DIGITS]
)
if step >= digits:
raise cv.Invalid(
"Initial selected digit must be less than number of digits",
path=[CONF_SELECTED_DIGIT],
)
raise cv.Invalid("Range outside allowed limits")
if step <= 0 or step >= (range_to - range_from) / 2:
raise cv.Invalid("Invalid step value")
if config[CONF_DIGITS] <= config[CONF_DECIMAL_PLACES]:
raise cv.Invalid("Number of digits must exceed number of decimal places")
return config
@@ -66,10 +59,7 @@ SPINBOX_SCHEMA = cv.Schema(
cv.Optional(CONF_RANGE_FROM, default=0): cv.float_,
cv.Optional(CONF_RANGE_TO, default=100): cv.float_,
cv.Optional(CONF_DIGITS, default=4): cv.int_range(1, 10),
cv.Optional(CONF_STEP): cv.invalid(
f"{CONF_STEP} has been replaced by {CONF_SELECTED_DIGIT}"
),
cv.Optional(CONF_SELECTED_DIGIT, default=0): cv.positive_int,
cv.Optional(CONF_STEP, default=1.0): cv.positive_float,
cv.Optional(CONF_DECIMAL_PLACES, default=0): cv.int_range(0, 6),
cv.Optional(CONF_ROLLOVER, default=False): lv_bool,
}
@@ -103,12 +93,13 @@ class SpinboxType(WidgetType):
scale = 10 ** config[CONF_DECIMAL_PLACES]
range_from = int(config[CONF_RANGE_FROM]) * scale
range_to = int(config[CONF_RANGE_TO]) * scale
step = config[CONF_SELECTED_DIGIT]
step = int(config[CONF_STEP]) * scale
w.scale = scale
w.step = step
w.range_to = range_to
w.range_from = range_from
lv.spinbox_set_range(w.obj, range_from, range_to)
await w.set_property("step", 10**step)
await w.set_property(CONF_STEP, step)
await w.set_property(CONF_ROLLOVER, config)
lv.spinbox_set_digit_format(
w.obj, digits, digits - config[CONF_DECIMAL_PLACES]
@@ -129,7 +120,7 @@ class SpinboxType(WidgetType):
return config[CONF_RANGE_FROM]
def get_step(self, config: dict):
return 10 ** config[CONF_SELECTED_DIGIT]
return config[CONF_STEP]
spinbox_spec = SpinboxType()

View File

@@ -10,8 +10,7 @@ from esphome.loader import get_component
CODEOWNERS = ["@clydebarrow"]
MULTI_CONF = True
mapping_ns = cg.esphome_ns.namespace("mapping")
mapping_class = mapping_ns.class_("Mapping")
map_ = cg.std_ns.class_("map")
CONF_ENTRIES = "entries"
CONF_CLASS = "class"
@@ -30,11 +29,7 @@ class IndexType:
INDEX_TYPES = {
"int": IndexType(cv.int_, cg.int_, int),
"string": IndexType(
cv.string,
cg.std_string,
str,
),
"string": IndexType(cv.string, cg.std_string, str),
}
@@ -52,7 +47,7 @@ def to_schema(value):
BASE_SCHEMA = cv.Schema(
{
cv.Required(CONF_ID): cv.declare_id(mapping_class),
cv.Required(CONF_ID): cv.declare_id(map_),
cv.Required(CONF_FROM): cv.one_of(*INDEX_TYPES, lower=True),
cv.Required(CONF_TO): cv.string,
},
@@ -128,15 +123,12 @@ async def to_code(config):
if list(entries.values())[0].op != ".":
value_type = value_type.operator("ptr")
varid = config[CONF_ID]
varid.type = mapping_class.template(
index_type,
value_type,
)
varid.type = map_.template(index_type, value_type)
var = MockObj(varid, ".")
decl = VariableDeclarationExpression(varid.type, "", varid)
add_global(decl)
CORE.register_variable(varid, var)
for key, value in entries.items():
cg.add(var.set(key, value))
cg.add(var.insert((key, value)))
return var

View File

@@ -1,69 +0,0 @@
#pragma once
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#include <map>
#include <string>
namespace esphome::mapping {
using alloc_string_t = std::basic_string<char, std::char_traits<char>, RAMAllocator<char>>;
/**
*
* Mapping class with custom allocator.
* Additionally, when std::string is used as key or value, it will be replaced with a custom string type
* that uses RAMAllocator.
* @tparam K The type of the key in the mapping.
* @tparam V The type of the value in the mapping. Should be a basic type or pointer.
*/
static const char *const TAG = "mapping";
template<typename K, typename V> class Mapping {
public:
// Constructor
Mapping() = default;
using key_t = const std::conditional_t<std::is_same_v<K, std::string>,
alloc_string_t, // if K is std::string, custom string type
K>;
using value_t = std::conditional_t<std::is_same_v<V, std::string>,
alloc_string_t, // if V is std::string, custom string type
V>;
void set(const K &key, const V &value) { this->map_[key_t{key}] = value; }
V get(const K &key) const {
auto it = this->map_.find(key_t{key});
if (it != this->map_.end()) {
return V{it->second};
}
if constexpr (std::is_pointer_v<K>) {
esph_log_e(TAG, "Key '%p' not found in mapping", key);
} else if constexpr (std::is_same_v<K, std::string>) {
esph_log_e(TAG, "Key '%s' not found in mapping", key.c_str());
} else {
esph_log_e(TAG, "Key '%s' not found in mapping", to_string(key).c_str());
}
return {};
}
// index map overload
V operator[](K key) { return this->get(key); }
// convenience function for strings to get a C-style string
template<typename T = V, std::enable_if_t<std::is_same_v<T, std::string>, int> = 0>
const char *operator[](K key) const {
auto it = this->map_.find(key_t{key});
if (it != this->map_.end()) {
return it->second.c_str(); // safe since value remains in map
}
return "";
}
protected:
std::map<key_t, value_t, std::less<key_t>, RAMAllocator<std::pair<key_t, value_t>>> map_;
};
} // namespace esphome::mapping

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_16_(reg, &buf)) {
if (!this->read_byte_16(reg, &buf)) {
this->error_code_ = MCP4461_STATUS_REGISTER_ERROR;
this->mark_failed();
return 0;
@@ -148,20 +148,6 @@ 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;
@@ -212,14 +198,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::READ);
uint8_t reg = addr | static_cast<uint8_t>(Mcp4461Commands::INCREMENT);
if (wiper_idx > 3) {
if (!this->is_eeprom_ready_for_writing_(true)) {
return 0;
}
}
uint16_t buf = 0;
if (!(this->read_16_(reg, &buf))) {
if (!(this->read_byte_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);
@@ -406,7 +392,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_16_(reg, &buf)) {
if (this->read_byte_16(reg, &buf)) {
return static_cast<uint8_t>(buf & 0x00ff);
} else {
this->error_code_ = MCP4461_STATUS_I2C_ERROR;
@@ -531,7 +517,7 @@ uint16_t Mcp4461Component::get_eeprom_value(Mcp4461EepromLocation location) {
if (!this->is_eeprom_ready_for_writing_(true)) {
return 0;
}
if (!this->read_16_(reg, &buf)) {
if (!this->read_byte_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,7 +96,6 @@ 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

@@ -11,7 +11,7 @@ from esphome.const import (
CONF_SERVICES,
PlatformFramework,
)
from esphome.core import CORE, CoroPriority, coroutine_with_priority
from esphome.core import CORE, coroutine_with_priority
CODEOWNERS = ["@esphome/core"]
DEPENDENCIES = ["network"]
@@ -72,7 +72,7 @@ def mdns_service(
)
@coroutine_with_priority(CoroPriority.COMMUNICATION)
@coroutine_with_priority(55.0)
async def to_code(config):
if config[CONF_DISABLED] is True:
return

View File

@@ -14,7 +14,7 @@ from esphome.const import (
)
from esphome.core import CORE
from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity
from esphome.coroutine import CoroPriority, coroutine_with_priority
from esphome.coroutine import coroutine_with_priority
from esphome.cpp_generator import MockObjClass
CODEOWNERS = ["@jesserockz"]
@@ -303,7 +303,7 @@ async def media_player_volume_set_action(config, action_id, template_arg, args):
return var
@coroutine_with_priority(CoroPriority.CORE)
@coroutine_with_priority(100.0)
async def to_code(config):
cg.add_global(media_player_ns.using)
cg.add_define("USE_MEDIA_PLAYER")

View File

@@ -12,7 +12,7 @@ from esphome.const import (
CONF_TRIGGER_ID,
)
from esphome.core import CORE
from esphome.coroutine import CoroPriority, coroutine_with_priority
from esphome.coroutine import coroutine_with_priority
AUTO_LOAD = ["audio"]
CODEOWNERS = ["@jesserockz", "@kahrendt"]
@@ -213,7 +213,7 @@ automation.register_condition(
)(microphone_action)
@coroutine_with_priority(CoroPriority.CORE)
@coroutine_with_priority(100.0)
async def to_code(config):
cg.add_global(microphone_ns.using)
cg.add_define("USE_MICROPHONE")

View File

@@ -2,7 +2,7 @@
# Various configuration constants for MIPI displays
# Various utility functions for MIPI DBI configuration
from typing import Any, Self
from typing import Any
from esphome.components.const import CONF_COLOR_DEPTH
from esphome.components.display import CONF_SHOW_TEST_CARD, display_ns
@@ -222,7 +222,7 @@ def delay(ms):
class DriverChip:
models: dict[str, Self] = {}
models = {}
def __init__(
self,

View File

@@ -16,6 +16,7 @@ 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

@@ -57,7 +57,7 @@ from esphome.const import (
PLATFORM_ESP8266,
PlatformFramework,
)
from esphome.core import CORE, CoroPriority, coroutine_with_priority
from esphome.core import CORE, coroutine_with_priority
DEPENDENCIES = ["network"]
@@ -321,7 +321,7 @@ def exp_mqtt_message(config):
)
@coroutine_with_priority(CoroPriority.WEB)
@coroutine_with_priority(40.0)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)

View File

@@ -2,7 +2,7 @@ import esphome.codegen as cg
from esphome.components.esp32 import add_idf_sdkconfig_option
import esphome.config_validation as cv
from esphome.const import CONF_ENABLE_IPV6, CONF_MIN_IPV6_ADDR_COUNT
from esphome.core import CORE, CoroPriority, coroutine_with_priority
from esphome.core import CORE, coroutine_with_priority
CODEOWNERS = ["@esphome/core"]
AUTO_LOAD = ["mdns"]
@@ -36,7 +36,7 @@ CONFIG_SCHEMA = cv.Schema(
)
@coroutine_with_priority(CoroPriority.NETWORK)
@coroutine_with_priority(201.0)
async def to_code(config):
cg.add_define("USE_NETWORK")
if CORE.using_arduino and CORE.is_esp32:

View File

@@ -2,13 +2,10 @@ from __future__ import annotations
from pathlib import Path
from esphome import pins
import esphome.codegen as cg
from esphome.components.zephyr import (
copy_files as zephyr_copy_files,
zephyr_add_pm_static,
zephyr_add_prj_conf,
zephyr_data,
zephyr_set_core_data,
zephyr_to_code,
)
@@ -21,8 +18,6 @@ import esphome.config_validation as cv
from esphome.const import (
CONF_BOARD,
CONF_FRAMEWORK,
CONF_ID,
CONF_RESET_PIN,
KEY_CORE,
KEY_FRAMEWORK_VERSION,
KEY_TARGET_FRAMEWORK,
@@ -30,7 +25,7 @@ from esphome.const import (
PLATFORM_NRF52,
ThreadModel,
)
from esphome.core import CORE, CoroPriority, EsphomeError, coroutine_with_priority
from esphome.core import CORE, EsphomeError, coroutine_with_priority
from esphome.storage_json import StorageJSON
from esphome.types import ConfigType
@@ -95,44 +90,19 @@ def _detect_bootloader(config: ConfigType) -> ConfigType:
return config
nrf52_ns = cg.esphome_ns.namespace("nrf52")
DeviceFirmwareUpdate = nrf52_ns.class_("DeviceFirmwareUpdate", cg.Component)
CONF_DFU = "dfu"
CONFIG_SCHEMA = cv.All(
_detect_bootloader,
set_core_data,
cv.Schema(
{
cv.Required(CONF_BOARD): cv.string_strict,
cv.Optional(KEY_BOOTLOADER): cv.one_of(*BOOTLOADERS, lower=True),
cv.Optional(CONF_DFU): cv.Schema(
{
cv.GenerateID(): cv.declare_id(DeviceFirmwareUpdate),
cv.Required(CONF_RESET_PIN): pins.gpio_output_pin_schema,
}
),
}
),
_detect_bootloader,
set_core_data,
)
def _validate_mcumgr(config):
bootloader = zephyr_data()[KEY_BOOTLOADER]
if bootloader == BOOTLOADER_MCUBOOT:
raise cv.Invalid(f"'{bootloader}' bootloader does not support DFU")
def _final_validate(config):
if CONF_DFU in config:
_validate_mcumgr(config)
FINAL_VALIDATE_SCHEMA = _final_validate
@coroutine_with_priority(CoroPriority.PLATFORM)
@coroutine_with_priority(1000)
async def to_code(config: ConfigType) -> None:
"""Convert the configuration to code."""
cg.add_platformio_option("board", config[CONF_BOARD])
@@ -166,19 +136,6 @@ async def to_code(config: ConfigType) -> None:
zephyr_to_code(config)
if dfu_config := config.get(CONF_DFU):
CORE.add_job(_dfu_to_code, dfu_config)
@coroutine_with_priority(CoroPriority.DIAGNOSTICS)
async def _dfu_to_code(dfu_config):
cg.add_define("USE_NRF52_DFU")
var = cg.new_Pvariable(dfu_config[CONF_ID])
pin = await cg.gpio_pin_expression(dfu_config[CONF_RESET_PIN])
cg.add(var.set_reset_pin(pin))
zephyr_add_prj_conf("CDC_ACM_DTE_RATE_CALLBACK_SUPPORT", True)
await cg.register_component(var, dfu_config)
def copy_files() -> None:
"""Copy files to the build directory."""

View File

@@ -2,7 +2,6 @@ BOOTLOADER_ADAFRUIT = "adafruit"
BOOTLOADER_ADAFRUIT_NRF52_SD132 = "adafruit_nrf52_sd132"
BOOTLOADER_ADAFRUIT_NRF52_SD140_V6 = "adafruit_nrf52_sd140_v6"
BOOTLOADER_ADAFRUIT_NRF52_SD140_V7 = "adafruit_nrf52_sd140_v7"
EXTRA_ADC = [
"VDD",
"VDDHDIV5",

View File

@@ -1,51 +0,0 @@
#include "dfu.h"
#ifdef USE_NRF52_DFU
#include <zephyr/device.h>
#include <zephyr/drivers/uart.h>
#include <zephyr/drivers/uart/cdc_acm.h>
#include "esphome/core/log.h"
namespace esphome {
namespace nrf52 {
static const char *const TAG = "dfu";
volatile bool goto_dfu = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
static const uint32_t DFU_DBL_RESET_MAGIC = 0x5A1AD5; // SALADS
#define DEVICE_AND_COMMA(node_id) DEVICE_DT_GET(node_id),
static void cdc_dte_rate_callback(const struct device * /*unused*/, uint32_t rate) {
if (rate == 1200) {
goto_dfu = true;
}
}
void DeviceFirmwareUpdate::setup() {
this->reset_pin_->setup();
const struct device *cdc_dev[] = {DT_FOREACH_STATUS_OKAY(zephyr_cdc_acm_uart, DEVICE_AND_COMMA)};
for (auto &idx : cdc_dev) {
cdc_acm_dte_rate_callback_set(idx, cdc_dte_rate_callback);
}
}
void DeviceFirmwareUpdate::loop() {
if (goto_dfu) {
goto_dfu = false;
volatile uint32_t *dbl_reset_mem = (volatile uint32_t *) 0x20007F7C;
(*dbl_reset_mem) = DFU_DBL_RESET_MAGIC;
this->reset_pin_->digital_write(true);
}
}
void DeviceFirmwareUpdate::dump_config() {
ESP_LOGCONFIG(TAG, "DFU:");
LOG_PIN(" RESET Pin: ", this->reset_pin_);
}
} // namespace nrf52
} // namespace esphome
#endif

View File

@@ -1,24 +0,0 @@
#pragma once
#include "esphome/core/defines.h"
#ifdef USE_NRF52_DFU
#include "esphome/core/component.h"
#include "esphome/core/gpio.h"
namespace esphome {
namespace nrf52 {
class DeviceFirmwareUpdate : public Component {
public:
void setup() override;
void loop() override;
void set_reset_pin(GPIOPin *reset) { this->reset_pin_ = reset; }
void dump_config() override;
protected:
GPIOPin *reset_pin_;
};
} // namespace nrf52
} // namespace esphome
#endif

View File

@@ -76,7 +76,7 @@ from esphome.const import (
DEVICE_CLASS_WIND_DIRECTION,
DEVICE_CLASS_WIND_SPEED,
)
from esphome.core import CORE, CoroPriority, coroutine_with_priority
from esphome.core import CORE, coroutine_with_priority
from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity
from esphome.cpp_generator import MockObjClass
@@ -321,7 +321,7 @@ async def number_in_range_to_code(config, condition_id, template_arg, args):
return var
@coroutine_with_priority(CoroPriority.CORE)
@coroutine_with_priority(100.0)
async def to_code(config):
cg.add_global(number_ns.using)

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