1
0
mirror of https://github.com/esphome/esphome.git synced 2025-11-05 01:21:50 +00:00

Compare commits

..

8 Commits

Author SHA1 Message Date
J. Nick Koston
fcdf209ac3 wip 2025-05-08 18:45:57 -05:00
J. Nick Koston
4c1111a395 devug 2025-05-08 17:20:06 -05:00
J. Nick Koston
57425a765b devug 2025-05-08 17:18:43 -05:00
J. Nick Koston
abb09b7fee wip 2025-05-08 16:43:00 -05:00
J. Nick Koston
b69fd2762e wip 2025-05-08 16:42:26 -05:00
J. Nick Koston
a38f0067ae debug 2025-05-08 16:10:52 -05:00
J. Nick Koston
00e128bdd5 Fix heap tracing function scope issues
- Add extern \C\ linkage to heap tracing functions
- Forward declare the functions in the API server implementation
- Ensures the heap tracing functions are accessible from any namespace
2025-05-08 15:29:11 -05:00
J. Nick Koston
35238c1437 Add heap tracing capability to API component
- Add heap tracing configuration options to API component
- Implement periodic heap trace dumping (every 30 seconds)
- Configure ESP-IDF settings for heap tracing
- Add sample YAML configuration
- Useful for debugging memory reallocation overhead issues
2025-05-08 15:25:06 -05:00
244 changed files with 2242 additions and 8292 deletions

View File

@@ -57,17 +57,6 @@ jobs:
event: 'REQUEST_CHANGES',
body: 'You have altered the generated proto files but they do not match what is expected.\nPlease run "script/api_protobuf/api_protobuf.py" and commit the changes.'
})
- if: failure()
name: Show changes
run: git diff
- if: failure()
name: Archive artifacts
uses: actions/upload-artifact@v4.6.2
with:
name: generated-proto-files
path: |
esphome/components/api/api_pb2.*
esphome/components/api/api_pb2_service.*
- if: success()
name: Dismiss review
uses: actions/github-script@v7.0.1

View File

@@ -292,11 +292,6 @@ jobs:
name: Run script/clang-tidy for ESP32 IDF
options: --environment esp32-idf-tidy --grep USE_ESP_IDF
pio_cache_key: tidyesp32-idf
- id: clang-tidy
name: Run script/clang-tidy for ZEPHYR
options: --environment nrf52-tidy --grep USE_ZEPHYR
pio_cache_key: tidy-zephyr
ignore_errors: true
steps:
- name: Check out code from GitHub
@@ -336,13 +331,13 @@ jobs:
- name: Run clang-tidy
run: |
. venv/bin/activate
script/clang-tidy --all-headers --fix ${{ matrix.options }} ${{ matrix.ignore_errors && '|| true' || '' }}
script/clang-tidy --all-headers --fix ${{ matrix.options }}
env:
# Also cache libdeps, store them in a ~/.platformio subfolder
PLATFORMIO_LIBDEPS_DIR: ~/.platformio/libdeps
- name: Suggested changes
run: script/ci-suggest-changes ${{ matrix.ignore_errors && '|| true' || '' }}
run: script/ci-suggest-changes
# yamllint disable-line rule:line-length
if: always()

View File

@@ -56,14 +56,16 @@ jobs:
uses: actions/setup-python@v5.6.0
with:
python-version: "3.x"
- name: Set up python environment
env:
ESPHOME_NO_VENV: 1
run: script/setup
- name: Build
run: |-
pip3 install build
python3 -m build
- name: Publish
uses: pypa/gh-action-pypi-publish@v1.12.4
with:
skip-existing: true
deploy-docker:
name: Build ESPHome ${{ matrix.platform.arch }}
@@ -229,25 +231,3 @@ jobs:
content: description
}
})
deploy-esphome-schema:
if: github.repository == 'esphome/esphome' && needs.init.outputs.branch_build == 'false'
runs-on: ubuntu-latest
needs:
- init
- deploy-manifest
steps:
- name: Trigger Workflow
uses: actions/github-script@v7.0.1
with:
github-token: ${{ secrets.DEPLOY_ESPHOME_SCHEMA_REPO_TOKEN }}
script: |
github.rest.actions.createWorkflowDispatch({
owner: "esphome",
repo: "esphome-schema",
workflow_id: "generate-schemas.yml",
ref: "main",
inputs: {
version: "${{ needs.init.outputs.tag }}",
}
})

View File

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

View File

@@ -282,7 +282,6 @@ esphome/components/microphone/* @jesserockz @kahrendt
esphome/components/mics_4514/* @jesserockz
esphome/components/midea/* @dudanov
esphome/components/midea_ir/* @dudanov
esphome/components/mipi_spi/* @clydebarrow
esphome/components/mitsubishi/* @RubyBailey
esphome/components/mixer/speaker/* @kahrendt
esphome/components/mlx90393/* @functionpointer
@@ -399,7 +398,6 @@ esphome/components/smt100/* @piechade
esphome/components/sn74hc165/* @jesserockz
esphome/components/socket/* @esphome/core
esphome/components/sonoff_d1/* @anatoly-savchenkov
esphome/components/sound_level/* @kahrendt
esphome/components/speaker/* @jesserockz @kahrendt
esphome/components/speaker/media_player/* @kahrendt @synesthesiam
esphome/components/spi/* @clydebarrow @esphome/core

View File

@@ -34,7 +34,7 @@ AirthingsWaveBase = airthings_wave_base_ns.class_(
BASE_SCHEMA = (
cv.Schema(
sensor.SENSOR_SCHEMA.extend(
{
cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(
unit_of_measurement=UNIT_PERCENT,

View File

@@ -5,8 +5,6 @@ from esphome.components import mqtt, web_server
import esphome.config_validation as cv
from esphome.const import (
CONF_CODE,
CONF_ENTITY_CATEGORY,
CONF_ICON,
CONF_ID,
CONF_MQTT_ID,
CONF_ON_STATE,
@@ -14,7 +12,6 @@ from esphome.const import (
CONF_WEB_SERVER,
)
from esphome.core import CORE, coroutine_with_priority
from esphome.cpp_generator import MockObjClass
from esphome.cpp_helpers import setup_entity
CODEOWNERS = ["@grahambrown11", "@hwstar"]
@@ -81,11 +78,12 @@ AlarmControlPanelCondition = alarm_control_panel_ns.class_(
"AlarmControlPanelCondition", automation.Condition
)
_ALARM_CONTROL_PANEL_SCHEMA = (
ALARM_CONTROL_PANEL_SCHEMA = (
cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA)
.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA)
.extend(
{
cv.GenerateID(): cv.declare_id(AlarmControlPanel),
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(
mqtt.MQTTAlarmControlPanelComponent
),
@@ -148,33 +146,6 @@ _ALARM_CONTROL_PANEL_SCHEMA = (
)
)
def alarm_control_panel_schema(
class_: MockObjClass,
*,
entity_category: str = cv.UNDEFINED,
icon: str = cv.UNDEFINED,
) -> cv.Schema:
schema = {
cv.GenerateID(): cv.declare_id(class_),
}
for key, default, validator in [
(CONF_ENTITY_CATEGORY, entity_category, cv.entity_category),
(CONF_ICON, icon, cv.icon),
]:
if default is not cv.UNDEFINED:
schema[cv.Optional(key, default=default)] = validator
return _ALARM_CONTROL_PANEL_SCHEMA.extend(schema)
# Remove before 2025.11.0
ALARM_CONTROL_PANEL_SCHEMA = alarm_control_panel_schema(AlarmControlPanel)
ALARM_CONTROL_PANEL_SCHEMA.add_extra(
cv.deprecated_schema_constant("alarm_control_panel")
)
ALARM_CONTROL_PANEL_ACTION_SCHEMA = maybe_simple_id(
{
cv.GenerateID(): cv.use_id(AlarmControlPanel),
@@ -238,12 +209,6 @@ async def register_alarm_control_panel(var, config):
await setup_alarm_control_panel_core_(var, config)
async def new_alarm_control_panel(config, *args):
var = cg.new_Pvariable(config[CONF_ID], *args)
await register_alarm_control_panel(var, config)
return var
@automation.register_action(
"alarm_control_panel.arm_away", ArmAwayAction, ALARM_CONTROL_PANEL_ACTION_SCHEMA
)

View File

@@ -1,7 +1,7 @@
import esphome.codegen as cg
from esphome.components import ble_client, cover
import esphome.config_validation as cv
from esphome.const import CONF_PIN
from esphome.const import CONF_ID, CONF_PIN
CODEOWNERS = ["@buxtronix"]
DEPENDENCIES = ["ble_client"]
@@ -15,9 +15,9 @@ Am43Component = am43_ns.class_(
)
CONFIG_SCHEMA = (
cover.cover_schema(Am43Component)
.extend(
cover.COVER_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(Am43Component),
cv.Optional(CONF_PIN, default=8888): cv.int_range(min=0, max=0xFFFF),
cv.Optional(CONF_INVERT_POSITION, default=False): cv.boolean,
}
@@ -28,8 +28,9 @@ CONFIG_SCHEMA = (
async def to_code(config):
var = await cover.new_cover(config)
var = cg.new_Pvariable(config[CONF_ID])
cg.add(var.set_pin(config[CONF_PIN]))
cg.add(var.set_invert_position(config[CONF_INVERT_POSITION]))
await cg.register_component(var, config)
await cover.register_cover(var, config)
await ble_client.register_ble_node(var, config)

View File

@@ -1,7 +1,7 @@
import esphome.codegen as cg
from esphome.components import ble_client, climate
import esphome.config_validation as cv
from esphome.const import CONF_UNIT_OF_MEASUREMENT
from esphome.const import CONF_ID, CONF_UNIT_OF_MEASUREMENT
UNITS = {
"f": "f",
@@ -17,9 +17,9 @@ Anova = anova_ns.class_(
)
CONFIG_SCHEMA = (
climate.climate_schema(Anova)
.extend(
climate.CLIMATE_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(Anova),
cv.Required(CONF_UNIT_OF_MEASUREMENT): cv.enum(UNITS),
}
)
@@ -29,7 +29,8 @@ CONFIG_SCHEMA = (
async def to_code(config):
var = await climate.new_climate(config)
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await climate.register_climate(var, config)
await ble_client.register_ble_node(var, config)
cg.add(var.set_unit_of_measurement(config[CONF_UNIT_OF_MEASUREMENT]))

View File

@@ -1,8 +1,10 @@
import base64
import logging
from esphome import automation
from esphome.automation import Condition
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_ACTION,
@@ -23,12 +25,14 @@ from esphome.const import (
CONF_TRIGGER_ID,
CONF_VARIABLES,
)
from esphome.core import coroutine_with_priority
from esphome.core import CORE, coroutine_with_priority
DEPENDENCIES = ["network"]
AUTO_LOAD = ["socket"]
CODEOWNERS = ["@OttoWinter"]
_LOGGER = logging.getLogger(__name__)
api_ns = cg.esphome_ns.namespace("api")
APIServer = api_ns.class_("APIServer", cg.Component, cg.Controller)
HomeAssistantServiceCallAction = api_ns.class_(
@@ -49,6 +53,11 @@ SERVICE_ARG_NATIVE_TYPES = {
"string[]": cg.std_vector.template(cg.std_string),
}
CONF_ENCRYPTION = "encryption"
CONF_HEAP_TRACING = "heap_tracing"
CONF_HEAP_TRACING_STANDALONE = "standalone" # vs SYSTEM
CONF_HEAP_TRACING_RECORDS = "num_records"
CONF_HEAP_TASK_TRACKING = "task_tracking"
CONF_HEAP_TASK_MAX = "max_tasks"
def validate_encryption_key(value):
@@ -95,6 +104,22 @@ def _encryption_schema(config):
return ENCRYPTION_SCHEMA(config)
HEAP_TRACING_SCHEMA = cv.Schema(
{
cv.Optional(CONF_HEAP_TRACING_STANDALONE, default=True): cv.boolean,
cv.Optional(CONF_HEAP_TRACING_RECORDS, default=100): cv.positive_int,
cv.Optional(CONF_HEAP_TASK_TRACKING, default=True): cv.boolean,
cv.Optional(CONF_HEAP_TASK_MAX, default=10): cv.positive_int,
}
)
def _heap_tracing_schema(config):
if config is None:
config = {}
return HEAP_TRACING_SCHEMA(config)
CONFIG_SCHEMA = cv.All(
cv.Schema(
{
@@ -109,6 +134,7 @@ CONFIG_SCHEMA = cv.All(
): ACTIONS_SCHEMA,
cv.Exclusive(CONF_ACTIONS, group_of_exclusion=CONF_ACTIONS): ACTIONS_SCHEMA,
cv.Optional(CONF_ENCRYPTION): _encryption_schema,
cv.Optional(CONF_HEAP_TRACING): _heap_tracing_schema,
cv.Optional(CONF_ON_CLIENT_CONNECTED): automation.validate_automation(
single=True
),
@@ -176,6 +202,74 @@ async def to_code(config):
else:
cg.add_define("USE_API_PLAINTEXT")
# Handle heap tracing configuration if ESP32 platform and using ESP-IDF
if (heap_tracing_config := config.get(CONF_HEAP_TRACING, None)) is not None:
if CORE.using_esp_idf:
# Enable heap tracing in sdkconfig
add_idf_sdkconfig_option("CONFIG_HEAP_TRACING", True)
add_idf_sdkconfig_option("CONFIG_HEAP_TRACE_STACK_DEPTH", "30")
add_idf_sdkconfig_option("CONFIG_ESP32_APPTRACE_ENABLE", True)
# Set tracing mode (standalone or system)
if heap_tracing_config[CONF_HEAP_TRACING_STANDALONE]:
add_idf_sdkconfig_option("CONFIG_HEAP_TRACING_STANDALONE", True)
else:
add_idf_sdkconfig_option("CONFIG_HEAP_TRACING_SYSTEM", True)
# Enable runtime stats gathering for task info
if heap_tracing_config[CONF_HEAP_TASK_TRACKING]:
add_idf_sdkconfig_option(
"CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS", True
)
add_idf_sdkconfig_option(
"CONFIG_FREERTOS_USE_STATS_FORMATTING_FUNCTIONS", True
)
add_idf_sdkconfig_option("CONFIG_FREERTOS_USE_TRACE_FACILITY", True)
# Generate code to implement heap tracing
cg.add_global(cg.RawStatement('#include "esp_heap_trace.h"'))
# Define the trace record buffer
num_records = heap_tracing_config[CONF_HEAP_TRACING_RECORDS]
cg.add_global(
cg.RawStatement(
f"static heap_trace_record_t trace_record[{num_records}];"
)
)
# No additional setup needed for task tracking
# Add helper functions for heap tracing with extern "C" to make them globally accessible
cg.add_global(
cg.RawStatement(
"""
// Global heap tracing functions that can be called from any context
extern "C" void start_heap_trace() {
heap_trace_init_standalone(trace_record, """
+ str(num_records)
+ """);
heap_trace_start(HEAP_TRACE_LEAKS);
}
extern "C" void stop_and_dump_heap_trace() {
heap_trace_stop();
heap_trace_dump();
}
"""
)
)
# Add periodic heap trace dumping to the api_server.cpp file
# This will be added in C++ code
cg.add_define("USE_API_HEAP_TRACE")
else:
# Not using ESP-IDF, so we can't use heap tracing
_LOGGER.warning(
"Heap tracing is only available when using ESP-IDF. "
"Disabling heap tracing configuration."
)
cg.add_define("USE_API")
cg.add_global(api_ns.using)

View File

@@ -33,24 +33,23 @@ service APIConnection {
rpc execute_service (ExecuteServiceRequest) returns (void) {}
rpc noise_encryption_set_key (NoiseEncryptionSetKeyRequest) returns (NoiseEncryptionSetKeyResponse) {}
rpc button_command (ButtonCommandRequest) returns (void) {}
rpc camera_image (CameraImageRequest) returns (void) {}
rpc climate_command (ClimateCommandRequest) returns (void) {}
rpc cover_command (CoverCommandRequest) returns (void) {}
rpc date_command (DateCommandRequest) returns (void) {}
rpc datetime_command (DateTimeCommandRequest) returns (void) {}
rpc fan_command (FanCommandRequest) returns (void) {}
rpc light_command (LightCommandRequest) returns (void) {}
rpc lock_command (LockCommandRequest) returns (void) {}
rpc media_player_command (MediaPlayerCommandRequest) returns (void) {}
rpc number_command (NumberCommandRequest) returns (void) {}
rpc select_command (SelectCommandRequest) returns (void) {}
rpc siren_command (SirenCommandRequest) returns (void) {}
rpc switch_command (SwitchCommandRequest) returns (void) {}
rpc camera_image (CameraImageRequest) returns (void) {}
rpc climate_command (ClimateCommandRequest) returns (void) {}
rpc number_command (NumberCommandRequest) returns (void) {}
rpc text_command (TextCommandRequest) returns (void) {}
rpc time_command (TimeCommandRequest) returns (void) {}
rpc update_command (UpdateCommandRequest) returns (void) {}
rpc select_command (SelectCommandRequest) returns (void) {}
rpc button_command (ButtonCommandRequest) returns (void) {}
rpc lock_command (LockCommandRequest) returns (void) {}
rpc valve_command (ValveCommandRequest) returns (void) {}
rpc media_player_command (MediaPlayerCommandRequest) returns (void) {}
rpc date_command (DateCommandRequest) returns (void) {}
rpc time_command (TimeCommandRequest) returns (void) {}
rpc datetime_command (DateTimeCommandRequest) returns (void) {}
rpc update_command (UpdateCommandRequest) returns (void) {}
rpc subscribe_bluetooth_le_advertisements(SubscribeBluetoothLEAdvertisementsRequest) returns (void) {}
rpc bluetooth_device_request(BluetoothDeviceRequest) returns (void) {}
@@ -656,7 +655,7 @@ message SubscribeLogsResponse {
option (no_delay) = false;
LogLevel level = 1;
bytes message = 3;
string message = 3;
bool send_failed = 4;
}
@@ -912,7 +911,6 @@ message ClimateStateResponse {
float target_temperature = 4;
float target_temperature_low = 5;
float target_temperature_high = 6;
// For older peers, equal to preset == CLIMATE_PRESET_AWAY
bool unused_legacy_away = 7;
ClimateAction action = 8;
ClimateFanMode fan_mode = 9;
@@ -938,7 +936,6 @@ message ClimateCommandRequest {
float target_temperature_low = 7;
bool has_target_temperature_high = 8;
float target_temperature_high = 9;
// legacy, for older peers, newer ones should use CLIMATE_PRESET_AWAY in preset
bool unused_has_legacy_away = 10;
bool unused_legacy_away = 11;
bool has_fan_mode = 12;
@@ -1041,49 +1038,6 @@ message SelectCommandRequest {
string state = 2;
}
// ==================== SIREN ====================
message ListEntitiesSirenResponse {
option (id) = 55;
option (source) = SOURCE_SERVER;
option (ifdef) = "USE_SIREN";
string object_id = 1;
fixed32 key = 2;
string name = 3;
string unique_id = 4;
string icon = 5;
bool disabled_by_default = 6;
repeated string tones = 7;
bool supports_duration = 8;
bool supports_volume = 9;
EntityCategory entity_category = 10;
}
message SirenStateResponse {
option (id) = 56;
option (source) = SOURCE_SERVER;
option (ifdef) = "USE_SIREN";
option (no_delay) = true;
fixed32 key = 1;
bool state = 2;
}
message SirenCommandRequest {
option (id) = 57;
option (source) = SOURCE_CLIENT;
option (ifdef) = "USE_SIREN";
option (no_delay) = true;
fixed32 key = 1;
bool has_state = 2;
bool state = 3;
bool has_tone = 4;
string tone = 5;
bool has_duration = 6;
uint32 duration = 7;
bool has_volume = 8;
float volume = 9;
}
// ==================== LOCK ====================
enum LockState {
@@ -1253,8 +1207,8 @@ message SubscribeBluetoothLEAdvertisementsRequest {
message BluetoothServiceData {
string uuid = 1;
repeated uint32 legacy_data = 2 [deprecated = true]; // Removed in api version 1.7
bytes data = 3; // Added in api version 1.7
repeated uint32 legacy_data = 2 [deprecated = true];
bytes data = 3; // Changed in proto version 1.7
}
message BluetoothLEAdvertisementResponse {
option (id) = 67;
@@ -1263,7 +1217,7 @@ message BluetoothLEAdvertisementResponse {
option (no_delay) = true;
uint64 address = 1;
bytes name = 2;
string name = 2;
sint32 rssi = 3;
repeated string service_uuids = 4;
@@ -1550,7 +1504,7 @@ message BluetoothScannerSetModeRequest {
BluetoothScannerMode mode = 1;
}
// ==================== VOICE ASSISTANT ====================
// ==================== PUSH TO TALK ====================
enum VoiceAssistantSubscribeFlag {
VOICE_ASSISTANT_SUBSCRIBE_NONE = 0;
VOICE_ASSISTANT_SUBSCRIBE_API_AUDIO = 1;

View File

@@ -73,91 +73,6 @@ const char *api_error_to_str(APIError err) {
return "UNKNOWN";
}
// Common implementation for writing raw data to socket
template<typename StateEnum>
APIError APIFrameHelper::write_raw_(const struct iovec *iov, int iovcnt, socket::Socket *socket,
std::vector<uint8_t> &tx_buf, const std::string &info, StateEnum &state,
StateEnum failed_state) {
// This method writes data to socket or buffers it
// Returns APIError::OK if successful (or would block, but data has been buffered)
// Returns APIError::SOCKET_WRITE_FAILED if socket write failed, and sets state to failed_state
if (iovcnt == 0)
return APIError::OK; // Nothing to do, success
size_t total_write_len = 0;
for (int i = 0; i < iovcnt; i++) {
#ifdef HELPER_LOG_PACKETS
ESP_LOGVV(TAG, "Sending raw: %s",
format_hex_pretty(reinterpret_cast<uint8_t *>(iov[i].iov_base), iov[i].iov_len).c_str());
#endif
total_write_len += iov[i].iov_len;
}
if (!tx_buf.empty()) {
// try to empty tx_buf first
while (!tx_buf.empty()) {
ssize_t sent = socket->write(tx_buf.data(), tx_buf.size());
if (is_would_block(sent)) {
break;
} else if (sent == -1) {
ESP_LOGVV(TAG, "%s: Socket write failed with errno %d", info.c_str(), errno);
state = failed_state;
return APIError::SOCKET_WRITE_FAILED; // Socket write failed
}
// TODO: inefficient if multiple packets in txbuf
// replace with deque of buffers
tx_buf.erase(tx_buf.begin(), tx_buf.begin() + sent);
}
}
if (!tx_buf.empty()) {
// tx buf not empty, can't write now because then stream would be inconsistent
// Reserve space upfront to avoid multiple reallocations
tx_buf.reserve(tx_buf.size() + total_write_len);
for (int i = 0; i < iovcnt; i++) {
tx_buf.insert(tx_buf.end(), reinterpret_cast<uint8_t *>(iov[i].iov_base),
reinterpret_cast<uint8_t *>(iov[i].iov_base) + iov[i].iov_len);
}
return APIError::OK; // Success, data buffered
}
ssize_t sent = socket->writev(iov, iovcnt);
if (is_would_block(sent)) {
// operation would block, add buffer to tx_buf
// Reserve space upfront to avoid multiple reallocations
tx_buf.reserve(tx_buf.size() + total_write_len);
for (int i = 0; i < iovcnt; i++) {
tx_buf.insert(tx_buf.end(), reinterpret_cast<uint8_t *>(iov[i].iov_base),
reinterpret_cast<uint8_t *>(iov[i].iov_base) + iov[i].iov_len);
}
return APIError::OK; // Success, data buffered
} else if (sent == -1) {
// an error occurred
ESP_LOGVV(TAG, "%s: Socket write failed with errno %d", info.c_str(), errno);
state = failed_state;
return APIError::SOCKET_WRITE_FAILED; // Socket write failed
} else if ((size_t) sent != total_write_len) {
// partially sent, add end to tx_buf
size_t remaining = total_write_len - sent;
// Reserve space upfront to avoid multiple reallocations
tx_buf.reserve(tx_buf.size() + remaining);
size_t to_consume = sent;
for (int i = 0; i < iovcnt; i++) {
if (to_consume >= iov[i].iov_len) {
to_consume -= iov[i].iov_len;
} else {
tx_buf.insert(tx_buf.end(), reinterpret_cast<uint8_t *>(iov[i].iov_base) + to_consume,
reinterpret_cast<uint8_t *>(iov[i].iov_base) + iov[i].iov_len);
to_consume = 0;
}
}
return APIError::OK; // Success, data buffered
}
return APIError::OK; // Success, all data sent
}
#define HELPER_LOG(msg, ...) ESP_LOGVV(TAG, "%s: " msg, info_.c_str(), ##__VA_ARGS__)
// uncomment to log raw packets
//#define HELPER_LOG_PACKETS
@@ -632,6 +547,79 @@ APIError APINoiseFrameHelper::try_send_tx_buf_() {
return APIError::OK;
}
/** Write the data to the socket, or buffer it a write would block
*
* @param data The data to write
* @param len The length of data
*/
APIError APINoiseFrameHelper::write_raw_(const struct iovec *iov, int iovcnt) {
if (iovcnt == 0)
return APIError::OK;
APIError aerr;
size_t total_write_len = 0;
for (int i = 0; i < iovcnt; i++) {
#ifdef HELPER_LOG_PACKETS
ESP_LOGVV(TAG, "Sending raw: %s",
format_hex_pretty(reinterpret_cast<uint8_t *>(iov[i].iov_base), iov[i].iov_len).c_str());
#endif
total_write_len += iov[i].iov_len;
}
if (!tx_buf_.empty()) {
// try to empty tx_buf_ first
aerr = try_send_tx_buf_();
if (aerr != APIError::OK && aerr != APIError::WOULD_BLOCK)
return aerr;
}
if (!tx_buf_.empty()) {
// tx buf not empty, can't write now because then stream would be inconsistent
// Reserve space upfront to avoid multiple reallocations
tx_buf_.reserve(tx_buf_.size() + total_write_len);
for (int i = 0; i < iovcnt; i++) {
tx_buf_.insert(tx_buf_.end(), reinterpret_cast<uint8_t *>(iov[i].iov_base),
reinterpret_cast<uint8_t *>(iov[i].iov_base) + iov[i].iov_len);
}
return APIError::OK;
}
ssize_t sent = socket_->writev(iov, iovcnt);
if (is_would_block(sent)) {
// operation would block, add buffer to tx_buf
// Reserve space upfront to avoid multiple reallocations
tx_buf_.reserve(tx_buf_.size() + total_write_len);
for (int i = 0; i < iovcnt; i++) {
tx_buf_.insert(tx_buf_.end(), reinterpret_cast<uint8_t *>(iov[i].iov_base),
reinterpret_cast<uint8_t *>(iov[i].iov_base) + iov[i].iov_len);
}
return APIError::OK;
} else if (sent == -1) {
// an error occurred
state_ = State::FAILED;
HELPER_LOG("Socket write failed with errno %d", errno);
return APIError::SOCKET_WRITE_FAILED;
} else if ((size_t) sent != total_write_len) {
// partially sent, add end to tx_buf
size_t remaining = total_write_len - sent;
// Reserve space upfront to avoid multiple reallocations
tx_buf_.reserve(tx_buf_.size() + remaining);
size_t to_consume = sent;
for (int i = 0; i < iovcnt; i++) {
if (to_consume >= iov[i].iov_len) {
to_consume -= iov[i].iov_len;
} else {
tx_buf_.insert(tx_buf_.end(), reinterpret_cast<uint8_t *>(iov[i].iov_base) + to_consume,
reinterpret_cast<uint8_t *>(iov[i].iov_base) + iov[i].iov_len);
to_consume = 0;
}
}
return APIError::OK;
}
// fully sent
return APIError::OK;
}
APIError APINoiseFrameHelper::write_frame_(const uint8_t *data, size_t len) {
uint8_t header[3];
header[0] = 0x01; // indicator
@@ -765,11 +753,6 @@ void noise_rand_bytes(void *output, size_t len) {
}
}
}
// Explicit template instantiation for Noise
template APIError APIFrameHelper::write_raw_<APINoiseFrameHelper::State>(
const struct iovec *iov, int iovcnt, socket::Socket *socket, std::vector<uint8_t> &tx_buf_, const std::string &info,
APINoiseFrameHelper::State &state, APINoiseFrameHelper::State failed_state);
#endif // USE_API_NOISE
#ifdef USE_API_PLAINTEXT
@@ -994,6 +977,79 @@ APIError APIPlaintextFrameHelper::try_send_tx_buf_() {
return APIError::OK;
}
/** Write the data to the socket, or buffer it a write would block
*
* @param data The data to write
* @param len The length of data
*/
APIError APIPlaintextFrameHelper::write_raw_(const struct iovec *iov, int iovcnt) {
if (iovcnt == 0)
return APIError::OK;
APIError aerr;
size_t total_write_len = 0;
for (int i = 0; i < iovcnt; i++) {
#ifdef HELPER_LOG_PACKETS
ESP_LOGVV(TAG, "Sending raw: %s",
format_hex_pretty(reinterpret_cast<uint8_t *>(iov[i].iov_base), iov[i].iov_len).c_str());
#endif
total_write_len += iov[i].iov_len;
}
if (!tx_buf_.empty()) {
// try to empty tx_buf_ first
aerr = try_send_tx_buf_();
if (aerr != APIError::OK && aerr != APIError::WOULD_BLOCK)
return aerr;
}
if (!tx_buf_.empty()) {
// tx buf not empty, can't write now because then stream would be inconsistent
// Reserve space upfront to avoid multiple reallocations
tx_buf_.reserve(tx_buf_.size() + total_write_len);
for (int i = 0; i < iovcnt; i++) {
tx_buf_.insert(tx_buf_.end(), reinterpret_cast<uint8_t *>(iov[i].iov_base),
reinterpret_cast<uint8_t *>(iov[i].iov_base) + iov[i].iov_len);
}
return APIError::OK;
}
ssize_t sent = socket_->writev(iov, iovcnt);
if (is_would_block(sent)) {
// operation would block, add buffer to tx_buf
// Reserve space upfront to avoid multiple reallocations
tx_buf_.reserve(tx_buf_.size() + total_write_len);
for (int i = 0; i < iovcnt; i++) {
tx_buf_.insert(tx_buf_.end(), reinterpret_cast<uint8_t *>(iov[i].iov_base),
reinterpret_cast<uint8_t *>(iov[i].iov_base) + iov[i].iov_len);
}
return APIError::OK;
} else if (sent == -1) {
// an error occurred
state_ = State::FAILED;
HELPER_LOG("Socket write failed with errno %d", errno);
return APIError::SOCKET_WRITE_FAILED;
} else if ((size_t) sent != total_write_len) {
// partially sent, add end to tx_buf
size_t remaining = total_write_len - sent;
// Reserve space upfront to avoid multiple reallocations
tx_buf_.reserve(tx_buf_.size() + remaining);
size_t to_consume = sent;
for (int i = 0; i < iovcnt; i++) {
if (to_consume >= iov[i].iov_len) {
to_consume -= iov[i].iov_len;
} else {
tx_buf_.insert(tx_buf_.end(), reinterpret_cast<uint8_t *>(iov[i].iov_base) + to_consume,
reinterpret_cast<uint8_t *>(iov[i].iov_base) + iov[i].iov_len);
to_consume = 0;
}
}
return APIError::OK;
}
// fully sent
return APIError::OK;
}
APIError APIPlaintextFrameHelper::close() {
state_ = State::CLOSED;
@@ -1011,11 +1067,6 @@ APIError APIPlaintextFrameHelper::shutdown(int how) {
}
return APIError::OK;
}
// Explicit template instantiation for Plaintext
template APIError APIFrameHelper::write_raw_<APIPlaintextFrameHelper::State>(
const struct iovec *iov, int iovcnt, socket::Socket *socket, std::vector<uint8_t> &tx_buf_, const std::string &info,
APIPlaintextFrameHelper::State &state, APIPlaintextFrameHelper::State failed_state);
#endif // USE_API_PLAINTEXT
} // namespace api

View File

@@ -72,12 +72,6 @@ class APIFrameHelper {
virtual APIError shutdown(int how) = 0;
// Give this helper a name for logging
virtual void set_log_info(std::string info) = 0;
protected:
// Common implementation for writing raw data to socket
template<typename StateEnum>
APIError write_raw_(const struct iovec *iov, int iovcnt, socket::Socket *socket, std::vector<uint8_t> &tx_buf,
const std::string &info, StateEnum &state, StateEnum failed_state);
};
#ifdef USE_API_NOISE
@@ -109,9 +103,7 @@ class APINoiseFrameHelper : public APIFrameHelper {
APIError try_read_frame_(ParsedFrame *frame);
APIError try_send_tx_buf_();
APIError write_frame_(const uint8_t *data, size_t len);
inline APIError write_raw_(const struct iovec *iov, int iovcnt) {
return APIFrameHelper::write_raw_(iov, iovcnt, socket_.get(), tx_buf_, info_, state_, State::FAILED);
}
APIError write_raw_(const struct iovec *iov, int iovcnt);
APIError init_handshake_();
APIError check_handshake_finished_();
void send_explicit_handshake_reject_(const std::string &reason);
@@ -172,9 +164,7 @@ class APIPlaintextFrameHelper : public APIFrameHelper {
APIError try_read_frame_(ParsedFrame *frame);
APIError try_send_tx_buf_();
inline APIError write_raw_(const struct iovec *iov, int iovcnt) {
return APIFrameHelper::write_raw_(iov, iovcnt, socket_.get(), tx_buf_, info_, state_, State::FAILED);
}
APIError write_raw_(const struct iovec *iov, int iovcnt);
std::unique_ptr<socket::Socket> socket_;

View File

@@ -5377,307 +5377,6 @@ void SelectCommandRequest::dump_to(std::string &out) const {
out.append("}");
}
#endif
bool ListEntitiesSirenResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 6: {
this->disabled_by_default = value.as_bool();
return true;
}
case 8: {
this->supports_duration = value.as_bool();
return true;
}
case 9: {
this->supports_volume = value.as_bool();
return true;
}
case 10: {
this->entity_category = value.as_enum<enums::EntityCategory>();
return true;
}
default:
return false;
}
}
bool ListEntitiesSirenResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 1: {
this->object_id = value.as_string();
return true;
}
case 3: {
this->name = value.as_string();
return true;
}
case 4: {
this->unique_id = value.as_string();
return true;
}
case 5: {
this->icon = value.as_string();
return true;
}
case 7: {
this->tones.push_back(value.as_string());
return true;
}
default:
return false;
}
}
bool ListEntitiesSirenResponse::decode_32bit(uint32_t field_id, Proto32Bit value) {
switch (field_id) {
case 2: {
this->key = value.as_fixed32();
return true;
}
default:
return false;
}
}
void ListEntitiesSirenResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(1, this->object_id);
buffer.encode_fixed32(2, this->key);
buffer.encode_string(3, this->name);
buffer.encode_string(4, this->unique_id);
buffer.encode_string(5, this->icon);
buffer.encode_bool(6, this->disabled_by_default);
for (auto &it : this->tones) {
buffer.encode_string(7, it, true);
}
buffer.encode_bool(8, this->supports_duration);
buffer.encode_bool(9, this->supports_volume);
buffer.encode_enum<enums::EntityCategory>(10, this->entity_category);
}
void ListEntitiesSirenResponse::calculate_size(uint32_t &total_size) const {
ProtoSize::add_string_field(total_size, 1, this->object_id, false);
ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0, false);
ProtoSize::add_string_field(total_size, 1, this->name, false);
ProtoSize::add_string_field(total_size, 1, this->unique_id, false);
ProtoSize::add_string_field(total_size, 1, this->icon, false);
ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false);
if (!this->tones.empty()) {
for (const auto &it : this->tones) {
ProtoSize::add_string_field(total_size, 1, it, true);
}
}
ProtoSize::add_bool_field(total_size, 1, this->supports_duration, false);
ProtoSize::add_bool_field(total_size, 1, this->supports_volume, false);
ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesSirenResponse::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
out.append("ListEntitiesSirenResponse {\n");
out.append(" object_id: ");
out.append("'").append(this->object_id).append("'");
out.append("\n");
out.append(" key: ");
sprintf(buffer, "%" PRIu32, this->key);
out.append(buffer);
out.append("\n");
out.append(" name: ");
out.append("'").append(this->name).append("'");
out.append("\n");
out.append(" unique_id: ");
out.append("'").append(this->unique_id).append("'");
out.append("\n");
out.append(" icon: ");
out.append("'").append(this->icon).append("'");
out.append("\n");
out.append(" disabled_by_default: ");
out.append(YESNO(this->disabled_by_default));
out.append("\n");
for (const auto &it : this->tones) {
out.append(" tones: ");
out.append("'").append(it).append("'");
out.append("\n");
}
out.append(" supports_duration: ");
out.append(YESNO(this->supports_duration));
out.append("\n");
out.append(" supports_volume: ");
out.append(YESNO(this->supports_volume));
out.append("\n");
out.append(" entity_category: ");
out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
out.append("\n");
out.append("}");
}
#endif
bool SirenStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 2: {
this->state = value.as_bool();
return true;
}
default:
return false;
}
}
bool SirenStateResponse::decode_32bit(uint32_t field_id, Proto32Bit value) {
switch (field_id) {
case 1: {
this->key = value.as_fixed32();
return true;
}
default:
return false;
}
}
void SirenStateResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_fixed32(1, this->key);
buffer.encode_bool(2, this->state);
}
void SirenStateResponse::calculate_size(uint32_t &total_size) const {
ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0, false);
ProtoSize::add_bool_field(total_size, 1, this->state, false);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void SirenStateResponse::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
out.append("SirenStateResponse {\n");
out.append(" key: ");
sprintf(buffer, "%" PRIu32, this->key);
out.append(buffer);
out.append("\n");
out.append(" state: ");
out.append(YESNO(this->state));
out.append("\n");
out.append("}");
}
#endif
bool SirenCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 2: {
this->has_state = value.as_bool();
return true;
}
case 3: {
this->state = value.as_bool();
return true;
}
case 4: {
this->has_tone = value.as_bool();
return true;
}
case 6: {
this->has_duration = value.as_bool();
return true;
}
case 7: {
this->duration = value.as_uint32();
return true;
}
case 8: {
this->has_volume = value.as_bool();
return true;
}
default:
return false;
}
}
bool SirenCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 5: {
this->tone = value.as_string();
return true;
}
default:
return false;
}
}
bool SirenCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
switch (field_id) {
case 1: {
this->key = value.as_fixed32();
return true;
}
case 9: {
this->volume = value.as_float();
return true;
}
default:
return false;
}
}
void SirenCommandRequest::encode(ProtoWriteBuffer buffer) const {
buffer.encode_fixed32(1, this->key);
buffer.encode_bool(2, this->has_state);
buffer.encode_bool(3, this->state);
buffer.encode_bool(4, this->has_tone);
buffer.encode_string(5, this->tone);
buffer.encode_bool(6, this->has_duration);
buffer.encode_uint32(7, this->duration);
buffer.encode_bool(8, this->has_volume);
buffer.encode_float(9, this->volume);
}
void SirenCommandRequest::calculate_size(uint32_t &total_size) const {
ProtoSize::add_fixed_field<4>(total_size, 1, this->key != 0, false);
ProtoSize::add_bool_field(total_size, 1, this->has_state, false);
ProtoSize::add_bool_field(total_size, 1, this->state, false);
ProtoSize::add_bool_field(total_size, 1, this->has_tone, false);
ProtoSize::add_string_field(total_size, 1, this->tone, false);
ProtoSize::add_bool_field(total_size, 1, this->has_duration, false);
ProtoSize::add_uint32_field(total_size, 1, this->duration, false);
ProtoSize::add_bool_field(total_size, 1, this->has_volume, false);
ProtoSize::add_fixed_field<4>(total_size, 1, this->volume != 0.0f, false);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void SirenCommandRequest::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
out.append("SirenCommandRequest {\n");
out.append(" key: ");
sprintf(buffer, "%" PRIu32, this->key);
out.append(buffer);
out.append("\n");
out.append(" has_state: ");
out.append(YESNO(this->has_state));
out.append("\n");
out.append(" state: ");
out.append(YESNO(this->state));
out.append("\n");
out.append(" has_tone: ");
out.append(YESNO(this->has_tone));
out.append("\n");
out.append(" tone: ");
out.append("'").append(this->tone).append("'");
out.append("\n");
out.append(" has_duration: ");
out.append(YESNO(this->has_duration));
out.append("\n");
out.append(" duration: ");
sprintf(buffer, "%" PRIu32, this->duration);
out.append(buffer);
out.append("\n");
out.append(" has_volume: ");
out.append(YESNO(this->has_volume));
out.append("\n");
out.append(" volume: ");
sprintf(buffer, "%g", this->volume);
out.append(buffer);
out.append("\n");
out.append("}");
}
#endif
bool ListEntitiesLockResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 6: {

View File

@@ -1284,65 +1284,6 @@ class SelectCommandRequest : public ProtoMessage {
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
};
class ListEntitiesSirenResponse : public ProtoMessage {
public:
std::string object_id{};
uint32_t key{0};
std::string name{};
std::string unique_id{};
std::string icon{};
bool disabled_by_default{false};
std::vector<std::string> tones{};
bool supports_duration{false};
bool supports_volume{false};
enums::EntityCategory entity_category{};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class SirenStateResponse : public ProtoMessage {
public:
uint32_t key{0};
bool state{false};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class SirenCommandRequest : public ProtoMessage {
public:
uint32_t key{0};
bool has_state{false};
bool state{false};
bool has_tone{false};
std::string tone{};
bool has_duration{false};
uint32_t duration{0};
bool has_volume{false};
float volume{0.0f};
void encode(ProtoWriteBuffer buffer) const override;
void calculate_size(uint32_t &total_size) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class ListEntitiesLockResponse : public ProtoMessage {
public:
std::string object_id{};

View File

@@ -292,24 +292,6 @@ bool APIServerConnectionBase::send_select_state_response(const SelectStateRespon
#endif
#ifdef USE_SELECT
#endif
#ifdef USE_SIREN
bool APIServerConnectionBase::send_list_entities_siren_response(const ListEntitiesSirenResponse &msg) {
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "send_list_entities_siren_response: %s", msg.dump().c_str());
#endif
return this->send_message_<ListEntitiesSirenResponse>(msg, 55);
}
#endif
#ifdef USE_SIREN
bool APIServerConnectionBase::send_siren_state_response(const SirenStateResponse &msg) {
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "send_siren_state_response: %s", msg.dump().c_str());
#endif
return this->send_message_<SirenStateResponse>(msg, 56);
}
#endif
#ifdef USE_SIREN
#endif
#ifdef USE_LOCK
bool APIServerConnectionBase::send_list_entities_lock_response(const ListEntitiesLockResponse &msg) {
#ifdef HAS_PROTO_MESSAGE_DUMP
@@ -921,17 +903,6 @@ bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
ESP_LOGVV(TAG, "on_select_command_request: %s", msg.dump().c_str());
#endif
this->on_select_command_request(msg);
#endif
break;
}
case 57: {
#ifdef USE_SIREN
SirenCommandRequest msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_siren_command_request: %s", msg.dump().c_str());
#endif
this->on_siren_command_request(msg);
#endif
break;
}
@@ -1398,45 +1369,6 @@ void APIServerConnection::on_noise_encryption_set_key_request(const NoiseEncrypt
}
}
#endif
#ifdef USE_BUTTON
void APIServerConnection::on_button_command_request(const ButtonCommandRequest &msg) {
if (!this->is_connection_setup()) {
this->on_no_setup_connection();
return;
}
if (!this->is_authenticated()) {
this->on_unauthenticated_access();
return;
}
this->button_command(msg);
}
#endif
#ifdef USE_ESP32_CAMERA
void APIServerConnection::on_camera_image_request(const CameraImageRequest &msg) {
if (!this->is_connection_setup()) {
this->on_no_setup_connection();
return;
}
if (!this->is_authenticated()) {
this->on_unauthenticated_access();
return;
}
this->camera_image(msg);
}
#endif
#ifdef USE_CLIMATE
void APIServerConnection::on_climate_command_request(const ClimateCommandRequest &msg) {
if (!this->is_connection_setup()) {
this->on_no_setup_connection();
return;
}
if (!this->is_authenticated()) {
this->on_unauthenticated_access();
return;
}
this->climate_command(msg);
}
#endif
#ifdef USE_COVER
void APIServerConnection::on_cover_command_request(const CoverCommandRequest &msg) {
if (!this->is_connection_setup()) {
@@ -1450,32 +1382,6 @@ void APIServerConnection::on_cover_command_request(const CoverCommandRequest &ms
this->cover_command(msg);
}
#endif
#ifdef USE_DATETIME_DATE
void APIServerConnection::on_date_command_request(const DateCommandRequest &msg) {
if (!this->is_connection_setup()) {
this->on_no_setup_connection();
return;
}
if (!this->is_authenticated()) {
this->on_unauthenticated_access();
return;
}
this->date_command(msg);
}
#endif
#ifdef USE_DATETIME_DATETIME
void APIServerConnection::on_date_time_command_request(const DateTimeCommandRequest &msg) {
if (!this->is_connection_setup()) {
this->on_no_setup_connection();
return;
}
if (!this->is_authenticated()) {
this->on_unauthenticated_access();
return;
}
this->datetime_command(msg);
}
#endif
#ifdef USE_FAN
void APIServerConnection::on_fan_command_request(const FanCommandRequest &msg) {
if (!this->is_connection_setup()) {
@@ -1502,8 +1408,8 @@ void APIServerConnection::on_light_command_request(const LightCommandRequest &ms
this->light_command(msg);
}
#endif
#ifdef USE_LOCK
void APIServerConnection::on_lock_command_request(const LockCommandRequest &msg) {
#ifdef USE_SWITCH
void APIServerConnection::on_switch_command_request(const SwitchCommandRequest &msg) {
if (!this->is_connection_setup()) {
this->on_no_setup_connection();
return;
@@ -1512,11 +1418,11 @@ void APIServerConnection::on_lock_command_request(const LockCommandRequest &msg)
this->on_unauthenticated_access();
return;
}
this->lock_command(msg);
this->switch_command(msg);
}
#endif
#ifdef USE_MEDIA_PLAYER
void APIServerConnection::on_media_player_command_request(const MediaPlayerCommandRequest &msg) {
#ifdef USE_ESP32_CAMERA
void APIServerConnection::on_camera_image_request(const CameraImageRequest &msg) {
if (!this->is_connection_setup()) {
this->on_no_setup_connection();
return;
@@ -1525,7 +1431,20 @@ void APIServerConnection::on_media_player_command_request(const MediaPlayerComma
this->on_unauthenticated_access();
return;
}
this->media_player_command(msg);
this->camera_image(msg);
}
#endif
#ifdef USE_CLIMATE
void APIServerConnection::on_climate_command_request(const ClimateCommandRequest &msg) {
if (!this->is_connection_setup()) {
this->on_no_setup_connection();
return;
}
if (!this->is_authenticated()) {
this->on_unauthenticated_access();
return;
}
this->climate_command(msg);
}
#endif
#ifdef USE_NUMBER
@@ -1541,6 +1460,19 @@ void APIServerConnection::on_number_command_request(const NumberCommandRequest &
this->number_command(msg);
}
#endif
#ifdef USE_TEXT
void APIServerConnection::on_text_command_request(const TextCommandRequest &msg) {
if (!this->is_connection_setup()) {
this->on_no_setup_connection();
return;
}
if (!this->is_authenticated()) {
this->on_unauthenticated_access();
return;
}
this->text_command(msg);
}
#endif
#ifdef USE_SELECT
void APIServerConnection::on_select_command_request(const SelectCommandRequest &msg) {
if (!this->is_connection_setup()) {
@@ -1554,8 +1486,8 @@ void APIServerConnection::on_select_command_request(const SelectCommandRequest &
this->select_command(msg);
}
#endif
#ifdef USE_SIREN
void APIServerConnection::on_siren_command_request(const SirenCommandRequest &msg) {
#ifdef USE_BUTTON
void APIServerConnection::on_button_command_request(const ButtonCommandRequest &msg) {
if (!this->is_connection_setup()) {
this->on_no_setup_connection();
return;
@@ -1564,11 +1496,11 @@ void APIServerConnection::on_siren_command_request(const SirenCommandRequest &ms
this->on_unauthenticated_access();
return;
}
this->siren_command(msg);
this->button_command(msg);
}
#endif
#ifdef USE_SWITCH
void APIServerConnection::on_switch_command_request(const SwitchCommandRequest &msg) {
#ifdef USE_LOCK
void APIServerConnection::on_lock_command_request(const LockCommandRequest &msg) {
if (!this->is_connection_setup()) {
this->on_no_setup_connection();
return;
@@ -1577,11 +1509,11 @@ void APIServerConnection::on_switch_command_request(const SwitchCommandRequest &
this->on_unauthenticated_access();
return;
}
this->switch_command(msg);
this->lock_command(msg);
}
#endif
#ifdef USE_TEXT
void APIServerConnection::on_text_command_request(const TextCommandRequest &msg) {
#ifdef USE_VALVE
void APIServerConnection::on_valve_command_request(const ValveCommandRequest &msg) {
if (!this->is_connection_setup()) {
this->on_no_setup_connection();
return;
@@ -1590,7 +1522,33 @@ void APIServerConnection::on_text_command_request(const TextCommandRequest &msg)
this->on_unauthenticated_access();
return;
}
this->text_command(msg);
this->valve_command(msg);
}
#endif
#ifdef USE_MEDIA_PLAYER
void APIServerConnection::on_media_player_command_request(const MediaPlayerCommandRequest &msg) {
if (!this->is_connection_setup()) {
this->on_no_setup_connection();
return;
}
if (!this->is_authenticated()) {
this->on_unauthenticated_access();
return;
}
this->media_player_command(msg);
}
#endif
#ifdef USE_DATETIME_DATE
void APIServerConnection::on_date_command_request(const DateCommandRequest &msg) {
if (!this->is_connection_setup()) {
this->on_no_setup_connection();
return;
}
if (!this->is_authenticated()) {
this->on_unauthenticated_access();
return;
}
this->date_command(msg);
}
#endif
#ifdef USE_DATETIME_TIME
@@ -1606,6 +1564,19 @@ void APIServerConnection::on_time_command_request(const TimeCommandRequest &msg)
this->time_command(msg);
}
#endif
#ifdef USE_DATETIME_DATETIME
void APIServerConnection::on_date_time_command_request(const DateTimeCommandRequest &msg) {
if (!this->is_connection_setup()) {
this->on_no_setup_connection();
return;
}
if (!this->is_authenticated()) {
this->on_unauthenticated_access();
return;
}
this->datetime_command(msg);
}
#endif
#ifdef USE_UPDATE
void APIServerConnection::on_update_command_request(const UpdateCommandRequest &msg) {
if (!this->is_connection_setup()) {
@@ -1619,19 +1590,6 @@ void APIServerConnection::on_update_command_request(const UpdateCommandRequest &
this->update_command(msg);
}
#endif
#ifdef USE_VALVE
void APIServerConnection::on_valve_command_request(const ValveCommandRequest &msg) {
if (!this->is_connection_setup()) {
this->on_no_setup_connection();
return;
}
if (!this->is_authenticated()) {
this->on_unauthenticated_access();
return;
}
this->valve_command(msg);
}
#endif
#ifdef USE_BLUETOOTH_PROXY
void APIServerConnection::on_subscribe_bluetooth_le_advertisements_request(
const SubscribeBluetoothLEAdvertisementsRequest &msg) {

View File

@@ -136,15 +136,6 @@ class APIServerConnectionBase : public ProtoService {
#ifdef USE_SELECT
virtual void on_select_command_request(const SelectCommandRequest &value){};
#endif
#ifdef USE_SIREN
bool send_list_entities_siren_response(const ListEntitiesSirenResponse &msg);
#endif
#ifdef USE_SIREN
bool send_siren_state_response(const SirenStateResponse &msg);
#endif
#ifdef USE_SIREN
virtual void on_siren_command_request(const SirenCommandRequest &value){};
#endif
#ifdef USE_LOCK
bool send_list_entities_lock_response(const ListEntitiesLockResponse &msg);
#endif
@@ -373,60 +364,57 @@ class APIServerConnection : public APIServerConnectionBase {
#ifdef USE_API_NOISE
virtual NoiseEncryptionSetKeyResponse noise_encryption_set_key(const NoiseEncryptionSetKeyRequest &msg) = 0;
#endif
#ifdef USE_BUTTON
virtual void button_command(const ButtonCommandRequest &msg) = 0;
#endif
#ifdef USE_ESP32_CAMERA
virtual void camera_image(const CameraImageRequest &msg) = 0;
#endif
#ifdef USE_CLIMATE
virtual void climate_command(const ClimateCommandRequest &msg) = 0;
#endif
#ifdef USE_COVER
virtual void cover_command(const CoverCommandRequest &msg) = 0;
#endif
#ifdef USE_DATETIME_DATE
virtual void date_command(const DateCommandRequest &msg) = 0;
#endif
#ifdef USE_DATETIME_DATETIME
virtual void datetime_command(const DateTimeCommandRequest &msg) = 0;
#endif
#ifdef USE_FAN
virtual void fan_command(const FanCommandRequest &msg) = 0;
#endif
#ifdef USE_LIGHT
virtual void light_command(const LightCommandRequest &msg) = 0;
#endif
#ifdef USE_LOCK
virtual void lock_command(const LockCommandRequest &msg) = 0;
#ifdef USE_SWITCH
virtual void switch_command(const SwitchCommandRequest &msg) = 0;
#endif
#ifdef USE_MEDIA_PLAYER
virtual void media_player_command(const MediaPlayerCommandRequest &msg) = 0;
#ifdef USE_ESP32_CAMERA
virtual void camera_image(const CameraImageRequest &msg) = 0;
#endif
#ifdef USE_CLIMATE
virtual void climate_command(const ClimateCommandRequest &msg) = 0;
#endif
#ifdef USE_NUMBER
virtual void number_command(const NumberCommandRequest &msg) = 0;
#endif
#ifdef USE_TEXT
virtual void text_command(const TextCommandRequest &msg) = 0;
#endif
#ifdef USE_SELECT
virtual void select_command(const SelectCommandRequest &msg) = 0;
#endif
#ifdef USE_SIREN
virtual void siren_command(const SirenCommandRequest &msg) = 0;
#ifdef USE_BUTTON
virtual void button_command(const ButtonCommandRequest &msg) = 0;
#endif
#ifdef USE_SWITCH
virtual void switch_command(const SwitchCommandRequest &msg) = 0;
#ifdef USE_LOCK
virtual void lock_command(const LockCommandRequest &msg) = 0;
#endif
#ifdef USE_TEXT
virtual void text_command(const TextCommandRequest &msg) = 0;
#ifdef USE_VALVE
virtual void valve_command(const ValveCommandRequest &msg) = 0;
#endif
#ifdef USE_MEDIA_PLAYER
virtual void media_player_command(const MediaPlayerCommandRequest &msg) = 0;
#endif
#ifdef USE_DATETIME_DATE
virtual void date_command(const DateCommandRequest &msg) = 0;
#endif
#ifdef USE_DATETIME_TIME
virtual void time_command(const TimeCommandRequest &msg) = 0;
#endif
#ifdef USE_DATETIME_DATETIME
virtual void datetime_command(const DateTimeCommandRequest &msg) = 0;
#endif
#ifdef USE_UPDATE
virtual void update_command(const UpdateCommandRequest &msg) = 0;
#endif
#ifdef USE_VALVE
virtual void valve_command(const ValveCommandRequest &msg) = 0;
#endif
#ifdef USE_BLUETOOTH_PROXY
virtual void subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) = 0;
#endif
@@ -490,60 +478,57 @@ class APIServerConnection : public APIServerConnectionBase {
#ifdef USE_API_NOISE
void on_noise_encryption_set_key_request(const NoiseEncryptionSetKeyRequest &msg) override;
#endif
#ifdef USE_BUTTON
void on_button_command_request(const ButtonCommandRequest &msg) override;
#endif
#ifdef USE_ESP32_CAMERA
void on_camera_image_request(const CameraImageRequest &msg) override;
#endif
#ifdef USE_CLIMATE
void on_climate_command_request(const ClimateCommandRequest &msg) override;
#endif
#ifdef USE_COVER
void on_cover_command_request(const CoverCommandRequest &msg) override;
#endif
#ifdef USE_DATETIME_DATE
void on_date_command_request(const DateCommandRequest &msg) override;
#endif
#ifdef USE_DATETIME_DATETIME
void on_date_time_command_request(const DateTimeCommandRequest &msg) override;
#endif
#ifdef USE_FAN
void on_fan_command_request(const FanCommandRequest &msg) override;
#endif
#ifdef USE_LIGHT
void on_light_command_request(const LightCommandRequest &msg) override;
#endif
#ifdef USE_LOCK
void on_lock_command_request(const LockCommandRequest &msg) override;
#ifdef USE_SWITCH
void on_switch_command_request(const SwitchCommandRequest &msg) override;
#endif
#ifdef USE_MEDIA_PLAYER
void on_media_player_command_request(const MediaPlayerCommandRequest &msg) override;
#ifdef USE_ESP32_CAMERA
void on_camera_image_request(const CameraImageRequest &msg) override;
#endif
#ifdef USE_CLIMATE
void on_climate_command_request(const ClimateCommandRequest &msg) override;
#endif
#ifdef USE_NUMBER
void on_number_command_request(const NumberCommandRequest &msg) override;
#endif
#ifdef USE_TEXT
void on_text_command_request(const TextCommandRequest &msg) override;
#endif
#ifdef USE_SELECT
void on_select_command_request(const SelectCommandRequest &msg) override;
#endif
#ifdef USE_SIREN
void on_siren_command_request(const SirenCommandRequest &msg) override;
#ifdef USE_BUTTON
void on_button_command_request(const ButtonCommandRequest &msg) override;
#endif
#ifdef USE_SWITCH
void on_switch_command_request(const SwitchCommandRequest &msg) override;
#ifdef USE_LOCK
void on_lock_command_request(const LockCommandRequest &msg) override;
#endif
#ifdef USE_TEXT
void on_text_command_request(const TextCommandRequest &msg) override;
#ifdef USE_VALVE
void on_valve_command_request(const ValveCommandRequest &msg) override;
#endif
#ifdef USE_MEDIA_PLAYER
void on_media_player_command_request(const MediaPlayerCommandRequest &msg) override;
#endif
#ifdef USE_DATETIME_DATE
void on_date_command_request(const DateCommandRequest &msg) override;
#endif
#ifdef USE_DATETIME_TIME
void on_time_command_request(const TimeCommandRequest &msg) override;
#endif
#ifdef USE_DATETIME_DATETIME
void on_date_time_command_request(const DateTimeCommandRequest &msg) override;
#endif
#ifdef USE_UPDATE
void on_update_command_request(const UpdateCommandRequest &msg) override;
#endif
#ifdef USE_VALVE
void on_valve_command_request(const ValveCommandRequest &msg) override;
#endif
#ifdef USE_BLUETOOTH_PROXY
void on_subscribe_bluetooth_le_advertisements_request(const SubscribeBluetoothLEAdvertisementsRequest &msg) override;
#endif

View File

@@ -14,6 +14,96 @@
#include "esphome/components/logger/logger.h"
#endif
#ifdef USE_API_HEAP_TRACE
#include "esp_heap_trace.h"
#include "esp_heap_caps.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
// Forward declare heap tracing functions that will be used in the API class
extern "C" void start_heap_trace();
extern "C" void stop_and_dump_heap_trace();
// Task heap information tracking
extern "C" void dump_task_heap_info() {
// Get basic heap statistics
multi_heap_info_t info;
heap_caps_get_info(&info, MALLOC_CAP_INTERNAL);
ESP_LOGI("HEAP", "=== Task Heap Information ===");
ESP_LOGI("HEAP", "-------------------------------------");
ESP_LOGI("HEAP", "Total free bytes: %u", info.total_free_bytes);
ESP_LOGI("HEAP", "Total allocated bytes: %u", info.total_allocated_bytes);
ESP_LOGI("HEAP", "Minimum free bytes: %u", info.minimum_free_bytes);
ESP_LOGI("HEAP", "Largest free block: %u", info.largest_free_block);
ESP_LOGI("HEAP", "Free blocks: %u", info.free_blocks);
ESP_LOGI("HEAP", "Allocated blocks: %u", info.allocated_blocks);
ESP_LOGI("HEAP", "Total blocks: %u", info.total_blocks);
ESP_LOGI("HEAP", "-------------------------------------");
// Get information about running tasks with a much larger buffer to prevent overflow
// The FreeRTOS functions don't provide a way to check buffer size requirements in advance
static char buffer[2048];
// Zero out the buffer for safety
memset(buffer, 0, sizeof(buffer));
// Get task list
vTaskList(buffer);
// Check if buffer has valid content
if (buffer[0] != '\0') {
ESP_LOGI("HEAP", "Task Information:");
ESP_LOGI("HEAP", "Name State Priority Stack Num");
ESP_LOGI("HEAP", "-------------------------------------");
// Process the buffer line by line to add the log prefix to each line
char *line = strtok(buffer, "\n\r");
int count = 0;
while (line != nullptr && strlen(line) > 0 && count < 20) {
ESP_LOGI("HEAP", "%s", line);
line = strtok(nullptr, "\n\r");
count++;
}
} else {
ESP_LOGE("HEAP", "Could not get task information");
}
ESP_LOGI("HEAP", "-------------------------------------");
// Runtime statistics - use a separate section with a different buffer to avoid corruption
static char stats_buffer[2048];
memset(stats_buffer, 0, sizeof(stats_buffer));
// Get runtime stats
vTaskGetRunTimeStats(stats_buffer);
// Check if buffer has valid content
if (stats_buffer[0] != '\0') {
ESP_LOGI("HEAP", "Task Runtime Statistics:");
ESP_LOGI("HEAP", "Name Time Percentage");
ESP_LOGI("HEAP", "-------------------------------------");
// Process the runtime stats buffer line by line safely
char *line = strtok(stats_buffer, "\n\r");
int count = 0;
// Limit to 20 lines to prevent buffer overruns
while (line != nullptr && count < 20) {
// Skip empty lines
if (strlen(line) > 0) {
ESP_LOGI("HEAP", "%s", line);
}
line = strtok(nullptr, "\n\r");
count++;
}
} else {
ESP_LOGE("HEAP", "Could not get task runtime statistics");
}
ESP_LOGI("HEAP", "-------------------------------------");
}
#endif
#include <algorithm>
namespace esphome {
@@ -30,6 +120,11 @@ void APIServer::setup() {
ESP_LOGCONFIG(TAG, "Setting up Home Assistant API server...");
this->setup_controller();
#ifdef USE_API_HEAP_TRACE
ESP_LOGI(TAG, "Initializing heap tracing");
start_heap_trace();
#endif
#ifdef USE_API_NOISE
uint32_t hash = 88491486UL;
@@ -126,29 +221,19 @@ void APIServer::loop() {
conn->start();
}
// Process clients and remove disconnected ones in a single pass
if (!this->clients_.empty()) {
size_t client_index = 0;
while (client_index < this->clients_.size()) {
auto &client = this->clients_[client_index];
// Partition clients into remove and active
auto new_end = std::partition(this->clients_.begin(), this->clients_.end(),
[](const std::unique_ptr<APIConnection> &conn) { return !conn->remove_; });
// print disconnection messages
for (auto it = new_end; it != this->clients_.end(); ++it) {
this->client_disconnected_trigger_->trigger((*it)->client_info_, (*it)->client_peername_);
ESP_LOGV(TAG, "Removing connection to %s", (*it)->client_info_.c_str());
}
// resize vector
this->clients_.erase(new_end, this->clients_.end());
if (client->remove_) {
// Handle disconnection
this->client_disconnected_trigger_->trigger(client->client_info_, client->client_peername_);
ESP_LOGV(TAG, "Removing connection to %s", client->client_info_.c_str());
// Swap with the last element and pop (avoids expensive vector shifts)
if (client_index < this->clients_.size() - 1) {
std::swap(this->clients_[client_index], this->clients_.back());
}
this->clients_.pop_back();
// Don't increment client_index since we need to process the swapped element
} else {
// Process active client
client->loop();
client_index++; // Move to next client
}
}
for (auto &client : this->clients_) {
client->loop();
}
if (this->reboot_timeout_ != 0) {
@@ -164,6 +249,24 @@ void APIServer::loop() {
this->status_clear_warning();
}
}
#ifdef USE_API_HEAP_TRACE
// Periodically dump heap trace information (every 30 seconds)
static uint32_t last_heap_trace_dump = 0;
const uint32_t now = millis();
if (now - last_heap_trace_dump > 30000) { // 30 seconds
ESP_LOGI(TAG, "Dumping heap trace information");
stop_and_dump_heap_trace();
// Also dump task-specific heap information
dump_task_heap_info();
// Start a new trace for the next period
start_heap_trace();
last_heap_trace_dump = now;
}
#endif
}
void APIServer::dump_config() {
@@ -476,6 +579,16 @@ void APIServer::on_shutdown() {
c->send_disconnect_request(DisconnectRequest());
}
delay(10);
#ifdef USE_API_HEAP_TRACE
// Make sure to stop tracing on shutdown to get final results
ESP_LOGI(TAG, "Final heap trace dump on shutdown");
stop_and_dump_heap_trace();
// Dump final task heap information
ESP_LOGI(TAG, "Final task heap information dump on shutdown");
dump_task_heap_info();
#endif
}
} // namespace api

View File

@@ -3,6 +3,5 @@ import esphome.codegen as cg
CODEOWNERS = ["@circuitsetup", "@descipher"]
atm90e32_ns = cg.esphome_ns.namespace("atm90e32")
ATM90E32Component = atm90e32_ns.class_("ATM90E32Component", cg.Component)
CONF_ATM90E32_ID = "atm90e32_id"

View File

@@ -1,7 +1,7 @@
#include "atm90e32.h"
#include <cinttypes>
#include <cmath>
#include "atm90e32_reg.h"
#include "esphome/core/log.h"
#include <cinttypes>
namespace esphome {
namespace atm90e32 {
@@ -11,84 +11,115 @@ void ATM90E32Component::loop() {
if (this->get_publish_interval_flag_()) {
this->set_publish_interval_flag_(false);
for (uint8_t phase = 0; phase < 3; phase++) {
if (this->phase_[phase].voltage_sensor_ != nullptr)
if (this->phase_[phase].voltage_sensor_ != nullptr) {
this->phase_[phase].voltage_ = this->get_phase_voltage_(phase);
if (this->phase_[phase].current_sensor_ != nullptr)
}
}
for (uint8_t phase = 0; phase < 3; phase++) {
if (this->phase_[phase].current_sensor_ != nullptr) {
this->phase_[phase].current_ = this->get_phase_current_(phase);
if (this->phase_[phase].power_sensor_ != nullptr)
}
}
for (uint8_t phase = 0; phase < 3; phase++) {
if (this->phase_[phase].power_sensor_ != nullptr) {
this->phase_[phase].active_power_ = this->get_phase_active_power_(phase);
if (this->phase_[phase].power_factor_sensor_ != nullptr)
}
}
for (uint8_t phase = 0; phase < 3; phase++) {
if (this->phase_[phase].power_factor_sensor_ != nullptr) {
this->phase_[phase].power_factor_ = this->get_phase_power_factor_(phase);
if (this->phase_[phase].reactive_power_sensor_ != nullptr)
}
}
for (uint8_t phase = 0; phase < 3; phase++) {
if (this->phase_[phase].reactive_power_sensor_ != nullptr) {
this->phase_[phase].reactive_power_ = this->get_phase_reactive_power_(phase);
if (this->phase_[phase].apparent_power_sensor_ != nullptr)
this->phase_[phase].apparent_power_ = this->get_phase_apparent_power_(phase);
if (this->phase_[phase].forward_active_energy_sensor_ != nullptr)
}
}
for (uint8_t phase = 0; phase < 3; phase++) {
if (this->phase_[phase].forward_active_energy_sensor_ != nullptr) {
this->phase_[phase].forward_active_energy_ = this->get_phase_forward_active_energy_(phase);
if (this->phase_[phase].reverse_active_energy_sensor_ != nullptr)
}
}
for (uint8_t phase = 0; phase < 3; phase++) {
if (this->phase_[phase].reverse_active_energy_sensor_ != nullptr) {
this->phase_[phase].reverse_active_energy_ = this->get_phase_reverse_active_energy_(phase);
if (this->phase_[phase].phase_angle_sensor_ != nullptr)
}
}
for (uint8_t phase = 0; phase < 3; phase++) {
if (this->phase_[phase].phase_angle_sensor_ != nullptr) {
this->phase_[phase].phase_angle_ = this->get_phase_angle_(phase);
if (this->phase_[phase].harmonic_active_power_sensor_ != nullptr)
}
}
for (uint8_t phase = 0; phase < 3; phase++) {
if (this->phase_[phase].harmonic_active_power_sensor_ != nullptr) {
this->phase_[phase].harmonic_active_power_ = this->get_phase_harmonic_active_power_(phase);
if (this->phase_[phase].peak_current_sensor_ != nullptr)
}
}
for (uint8_t phase = 0; phase < 3; phase++) {
if (this->phase_[phase].peak_current_sensor_ != nullptr) {
this->phase_[phase].peak_current_ = this->get_phase_peak_current_(phase);
// After the local store is collected we can publish them trusting they are within +-1 hardware sampling
if (this->phase_[phase].voltage_sensor_ != nullptr)
}
}
// After the local store in collected we can publish them trusting they are withing +-1 haardware sampling
for (uint8_t phase = 0; phase < 3; phase++) {
if (this->phase_[phase].voltage_sensor_ != nullptr) {
this->phase_[phase].voltage_sensor_->publish_state(this->get_local_phase_voltage_(phase));
if (this->phase_[phase].current_sensor_ != nullptr)
}
}
for (uint8_t phase = 0; phase < 3; phase++) {
if (this->phase_[phase].current_sensor_ != nullptr) {
this->phase_[phase].current_sensor_->publish_state(this->get_local_phase_current_(phase));
if (this->phase_[phase].power_sensor_ != nullptr)
}
}
for (uint8_t phase = 0; phase < 3; phase++) {
if (this->phase_[phase].power_sensor_ != nullptr) {
this->phase_[phase].power_sensor_->publish_state(this->get_local_phase_active_power_(phase));
if (this->phase_[phase].power_factor_sensor_ != nullptr)
}
}
for (uint8_t phase = 0; phase < 3; phase++) {
if (this->phase_[phase].power_factor_sensor_ != nullptr) {
this->phase_[phase].power_factor_sensor_->publish_state(this->get_local_phase_power_factor_(phase));
if (this->phase_[phase].reactive_power_sensor_ != nullptr)
}
}
for (uint8_t phase = 0; phase < 3; phase++) {
if (this->phase_[phase].reactive_power_sensor_ != nullptr) {
this->phase_[phase].reactive_power_sensor_->publish_state(this->get_local_phase_reactive_power_(phase));
if (this->phase_[phase].apparent_power_sensor_ != nullptr)
this->phase_[phase].apparent_power_sensor_->publish_state(this->get_local_phase_apparent_power_(phase));
}
}
for (uint8_t phase = 0; phase < 3; phase++) {
if (this->phase_[phase].forward_active_energy_sensor_ != nullptr) {
this->phase_[phase].forward_active_energy_sensor_->publish_state(
this->get_local_phase_forward_active_energy_(phase));
}
}
for (uint8_t phase = 0; phase < 3; phase++) {
if (this->phase_[phase].reverse_active_energy_sensor_ != nullptr) {
this->phase_[phase].reverse_active_energy_sensor_->publish_state(
this->get_local_phase_reverse_active_energy_(phase));
}
if (this->phase_[phase].phase_angle_sensor_ != nullptr)
}
for (uint8_t phase = 0; phase < 3; phase++) {
if (this->phase_[phase].phase_angle_sensor_ != nullptr) {
this->phase_[phase].phase_angle_sensor_->publish_state(this->get_local_phase_angle_(phase));
}
}
for (uint8_t phase = 0; phase < 3; phase++) {
if (this->phase_[phase].harmonic_active_power_sensor_ != nullptr) {
this->phase_[phase].harmonic_active_power_sensor_->publish_state(
this->get_local_phase_harmonic_active_power_(phase));
}
if (this->phase_[phase].peak_current_sensor_ != nullptr)
this->phase_[phase].peak_current_sensor_->publish_state(this->get_local_phase_peak_current_(phase));
}
if (this->freq_sensor_ != nullptr)
for (uint8_t phase = 0; phase < 3; phase++) {
if (this->phase_[phase].peak_current_sensor_ != nullptr) {
this->phase_[phase].peak_current_sensor_->publish_state(this->get_local_phase_peak_current_(phase));
}
}
if (this->freq_sensor_ != nullptr) {
this->freq_sensor_->publish_state(this->get_frequency_());
if (this->chip_temperature_sensor_ != nullptr)
}
if (this->chip_temperature_sensor_ != nullptr) {
this->chip_temperature_sensor_->publish_state(this->get_chip_temperature_());
}
}
}
@@ -99,30 +130,82 @@ void ATM90E32Component::update() {
}
this->set_publish_interval_flag_(true);
this->status_clear_warning();
}
#ifdef USE_TEXT_SENSOR
this->check_phase_status();
this->check_over_current();
this->check_freq_status();
#endif
void ATM90E32Component::restore_calibrations_() {
if (enable_offset_calibration_) {
this->pref_.load(&this->offset_phase_);
}
};
void ATM90E32Component::run_offset_calibrations() {
// Run the calibrations and
// Setup voltage and current calibration offsets for PHASE A
this->offset_phase_[PHASEA].voltage_offset_ = calibrate_voltage_offset_phase(PHASEA);
this->phase_[PHASEA].voltage_offset_ = this->offset_phase_[PHASEA].voltage_offset_;
this->write16_(ATM90E32_REGISTER_UOFFSETA, this->phase_[PHASEA].voltage_offset_); // C Voltage offset
this->offset_phase_[PHASEA].current_offset_ = calibrate_current_offset_phase(PHASEA);
this->phase_[PHASEA].current_offset_ = this->offset_phase_[PHASEA].current_offset_;
this->write16_(ATM90E32_REGISTER_IOFFSETA, this->phase_[PHASEA].current_offset_); // C Current offset
// Setup voltage and current calibration offsets for PHASE B
this->offset_phase_[PHASEB].voltage_offset_ = calibrate_voltage_offset_phase(PHASEB);
this->phase_[PHASEB].voltage_offset_ = this->offset_phase_[PHASEB].voltage_offset_;
this->write16_(ATM90E32_REGISTER_UOFFSETB, this->phase_[PHASEB].voltage_offset_); // C Voltage offset
this->offset_phase_[PHASEB].current_offset_ = calibrate_current_offset_phase(PHASEB);
this->phase_[PHASEB].current_offset_ = this->offset_phase_[PHASEB].current_offset_;
this->write16_(ATM90E32_REGISTER_IOFFSETB, this->phase_[PHASEB].current_offset_); // C Current offset
// Setup voltage and current calibration offsets for PHASE C
this->offset_phase_[PHASEC].voltage_offset_ = calibrate_voltage_offset_phase(PHASEC);
this->phase_[PHASEC].voltage_offset_ = this->offset_phase_[PHASEC].voltage_offset_;
this->write16_(ATM90E32_REGISTER_UOFFSETC, this->phase_[PHASEC].voltage_offset_); // C Voltage offset
this->offset_phase_[PHASEC].current_offset_ = calibrate_current_offset_phase(PHASEC);
this->phase_[PHASEC].current_offset_ = this->offset_phase_[PHASEC].current_offset_;
this->write16_(ATM90E32_REGISTER_IOFFSETC, this->phase_[PHASEC].current_offset_); // C Current offset
this->pref_.save(&this->offset_phase_);
ESP_LOGI(TAG, "PhaseA Vo=%5d PhaseB Vo=%5d PhaseC Vo=%5d", this->offset_phase_[PHASEA].voltage_offset_,
this->offset_phase_[PHASEB].voltage_offset_, this->offset_phase_[PHASEC].voltage_offset_);
ESP_LOGI(TAG, "PhaseA Io=%5d PhaseB Io=%5d PhaseC Io=%5d", this->offset_phase_[PHASEA].current_offset_,
this->offset_phase_[PHASEB].current_offset_, this->offset_phase_[PHASEC].current_offset_);
}
void ATM90E32Component::clear_offset_calibrations() {
// Clear the calibrations and
this->offset_phase_[PHASEA].voltage_offset_ = 0;
this->phase_[PHASEA].voltage_offset_ = this->offset_phase_[PHASEA].voltage_offset_;
this->write16_(ATM90E32_REGISTER_UOFFSETA, this->phase_[PHASEA].voltage_offset_); // C Voltage offset
this->offset_phase_[PHASEA].current_offset_ = 0;
this->phase_[PHASEA].current_offset_ = this->offset_phase_[PHASEA].current_offset_;
this->write16_(ATM90E32_REGISTER_IOFFSETA, this->phase_[PHASEA].current_offset_); // C Current offset
this->offset_phase_[PHASEB].voltage_offset_ = 0;
this->phase_[PHASEB].voltage_offset_ = this->offset_phase_[PHASEB].voltage_offset_;
this->write16_(ATM90E32_REGISTER_UOFFSETB, this->phase_[PHASEB].voltage_offset_); // C Voltage offset
this->offset_phase_[PHASEB].current_offset_ = 0;
this->phase_[PHASEB].current_offset_ = this->offset_phase_[PHASEB].current_offset_;
this->write16_(ATM90E32_REGISTER_IOFFSETB, this->phase_[PHASEB].current_offset_); // C Current offset
this->offset_phase_[PHASEC].voltage_offset_ = 0;
this->phase_[PHASEC].voltage_offset_ = this->offset_phase_[PHASEC].voltage_offset_;
this->write16_(ATM90E32_REGISTER_UOFFSETC, this->phase_[PHASEC].voltage_offset_); // C Voltage offset
this->offset_phase_[PHASEC].current_offset_ = 0;
this->phase_[PHASEC].current_offset_ = this->offset_phase_[PHASEC].current_offset_;
this->write16_(ATM90E32_REGISTER_IOFFSETC, this->phase_[PHASEC].current_offset_); // C Current offset
this->pref_.save(&this->offset_phase_);
ESP_LOGI(TAG, "PhaseA Vo=%5d PhaseB Vo=%5d PhaseC Vo=%5d", this->offset_phase_[PHASEA].voltage_offset_,
this->offset_phase_[PHASEB].voltage_offset_, this->offset_phase_[PHASEC].voltage_offset_);
ESP_LOGI(TAG, "PhaseA Io=%5d PhaseB Io=%5d PhaseC Io=%5d", this->offset_phase_[PHASEA].current_offset_,
this->offset_phase_[PHASEB].current_offset_, this->offset_phase_[PHASEC].current_offset_);
}
void ATM90E32Component::setup() {
ESP_LOGCONFIG(TAG, "Setting up ATM90E32 Component...");
this->spi_setup();
if (this->enable_offset_calibration_) {
uint32_t hash = fnv1_hash(App.get_friendly_name());
this->pref_ = global_preferences->make_preference<Calibration[3]>(hash, true);
this->restore_calibrations_();
}
uint16_t mmode0 = 0x87; // 3P4W 50Hz
uint16_t high_thresh = 0;
uint16_t low_thresh = 0;
if (line_freq_ == 60) {
mmode0 |= 1 << 12; // sets 12th bit to 1, 60Hz
// for freq threshold registers
high_thresh = 6300; // 63.00 Hz
low_thresh = 5700; // 57.00 Hz
} else {
high_thresh = 5300; // 53.00 Hz
low_thresh = 4700; // 47.00 Hz
}
if (current_phases_ == 2) {
@@ -133,84 +216,34 @@ void ATM90E32Component::setup() {
this->write16_(ATM90E32_REGISTER_SOFTRESET, 0x789A); // Perform soft reset
delay(6); // Wait for the minimum 5ms + 1ms
this->write16_(ATM90E32_REGISTER_CFGREGACCEN, 0x55AA); // enable register config access
if (!this->validate_spi_read_(0x55AA, "setup()")) {
if (this->read16_(ATM90E32_REGISTER_LASTSPIDATA) != 0x55AA) {
ESP_LOGW(TAG, "Could not initialize ATM90E32 IC, check SPI settings");
this->mark_failed();
return;
}
this->write16_(ATM90E32_REGISTER_METEREN, 0x0001); // Enable Metering
this->write16_(ATM90E32_REGISTER_SAGPEAKDETCFG, 0xFF3F); // Peak Detector time (15:8) 255ms, Sag Period (7:0) 63ms
this->write16_(ATM90E32_REGISTER_SAGPEAKDETCFG, 0xFF3F); // Peak Detector time ms (15:8), Sag Period ms (7:0)
this->write16_(ATM90E32_REGISTER_PLCONSTH, 0x0861); // PL Constant MSB (default) = 140625000
this->write16_(ATM90E32_REGISTER_PLCONSTL, 0xC468); // PL Constant LSB (default)
this->write16_(ATM90E32_REGISTER_ZXCONFIG, 0xD654); // Zero crossing (ZX2, ZX1, ZX0) pin config
this->write16_(ATM90E32_REGISTER_ZXCONFIG, 0xD654); // ZX2, ZX1, ZX0 pin config
this->write16_(ATM90E32_REGISTER_MMODE0, mmode0); // Mode Config (frequency set in main program)
this->write16_(ATM90E32_REGISTER_MMODE1, pga_gain_); // PGA Gain Configuration for Current Channels
this->write16_(ATM90E32_REGISTER_FREQHITH, high_thresh); // Frequency high threshold
this->write16_(ATM90E32_REGISTER_FREQLOTH, low_thresh); // Frequency low threshold
this->write16_(ATM90E32_REGISTER_PSTARTTH, 0x1D4C); // All Active Startup Power Threshold - 0.02A/0.00032 = 7500
this->write16_(ATM90E32_REGISTER_QSTARTTH, 0x1D4C); // All Reactive Startup Power Threshold - 50%
this->write16_(ATM90E32_REGISTER_SSTARTTH, 0x1D4C); // All Reactive Startup Power Threshold - 50%
this->write16_(ATM90E32_REGISTER_PPHASETH, 0x02EE); // Each Phase Active Phase Threshold - 0.002A/0.00032 = 750
this->write16_(ATM90E32_REGISTER_QPHASETH, 0x02EE); // Each phase Reactive Phase Threshold - 10%
if (this->enable_offset_calibration_) {
// Initialize flash storage for offset calibrations
uint32_t o_hash = fnv1_hash(std::string("_offset_calibration_") + this->cs_->dump_summary());
this->offset_pref_ = global_preferences->make_preference<OffsetCalibration[3]>(o_hash, true);
this->restore_offset_calibrations_();
// Initialize flash storage for power offset calibrations
uint32_t po_hash = fnv1_hash(std::string("_power_offset_calibration_") + this->cs_->dump_summary());
this->power_offset_pref_ = global_preferences->make_preference<PowerOffsetCalibration[3]>(po_hash, true);
this->restore_power_offset_calibrations_();
} else {
ESP_LOGI(TAG, "[CALIBRATION] Power & Voltage/Current offset calibration is disabled. Using config file values.");
for (uint8_t phase = 0; phase < 3; ++phase) {
this->write16_(this->voltage_offset_registers[phase],
static_cast<uint16_t>(this->offset_phase_[phase].voltage_offset_));
this->write16_(this->current_offset_registers[phase],
static_cast<uint16_t>(this->offset_phase_[phase].current_offset_));
this->write16_(this->power_offset_registers[phase],
static_cast<uint16_t>(this->power_offset_phase_[phase].active_power_offset));
this->write16_(this->reactive_power_offset_registers[phase],
static_cast<uint16_t>(this->power_offset_phase_[phase].reactive_power_offset));
}
}
if (this->enable_gain_calibration_) {
// Initialize flash storage for gain calibration
uint32_t g_hash = fnv1_hash(std::string("_gain_calibration_") + this->cs_->dump_summary());
this->gain_calibration_pref_ = global_preferences->make_preference<GainCalibration[3]>(g_hash, true);
this->restore_gain_calibrations_();
if (this->using_saved_calibrations_) {
ESP_LOGI(TAG, "[CALIBRATION] Successfully restored gain calibration from memory.");
} else {
for (uint8_t phase = 0; phase < 3; ++phase) {
this->write16_(voltage_gain_registers[phase], this->phase_[phase].voltage_gain_);
this->write16_(current_gain_registers[phase], this->phase_[phase].ct_gain_);
}
}
} else {
ESP_LOGI(TAG, "[CALIBRATION] Gain calibration is disabled. Using config file values.");
for (uint8_t phase = 0; phase < 3; ++phase) {
this->write16_(voltage_gain_registers[phase], this->phase_[phase].voltage_gain_);
this->write16_(current_gain_registers[phase], this->phase_[phase].ct_gain_);
}
}
// Sag threshold (78%)
uint16_t sagth = calculate_voltage_threshold(line_freq_, this->phase_[0].voltage_gain_, 0.78f);
// Overvoltage threshold (122%)
uint16_t ovth = calculate_voltage_threshold(line_freq_, this->phase_[0].voltage_gain_, 1.22f);
// Write to registers
this->write16_(ATM90E32_REGISTER_SAGTH, sagth);
this->write16_(ATM90E32_REGISTER_OVTH, ovth);
this->write16_(ATM90E32_REGISTER_CFGREGACCEN, 0x0000); // end configuration
// Setup voltage and current gain for PHASE A
this->write16_(ATM90E32_REGISTER_UGAINA, this->phase_[PHASEA].voltage_gain_); // A Voltage rms gain
this->write16_(ATM90E32_REGISTER_IGAINA, this->phase_[PHASEA].ct_gain_); // A line current gain
// Setup voltage and current gain for PHASE B
this->write16_(ATM90E32_REGISTER_UGAINB, this->phase_[PHASEB].voltage_gain_); // B Voltage rms gain
this->write16_(ATM90E32_REGISTER_IGAINB, this->phase_[PHASEB].ct_gain_); // B line current gain
// Setup voltage and current gain for PHASE C
this->write16_(ATM90E32_REGISTER_UGAINC, this->phase_[PHASEC].voltage_gain_); // C Voltage rms gain
this->write16_(ATM90E32_REGISTER_IGAINC, this->phase_[PHASEC].ct_gain_); // C line current gain
this->write16_(ATM90E32_REGISTER_CFGREGACCEN, 0x0000); // end configuration
}
void ATM90E32Component::dump_config() {
@@ -224,7 +257,6 @@ void ATM90E32Component::dump_config() {
LOG_SENSOR(" ", "Current A", this->phase_[PHASEA].current_sensor_);
LOG_SENSOR(" ", "Power A", this->phase_[PHASEA].power_sensor_);
LOG_SENSOR(" ", "Reactive Power A", this->phase_[PHASEA].reactive_power_sensor_);
LOG_SENSOR(" ", "Apparent Power A", this->phase_[PHASEA].apparent_power_sensor_);
LOG_SENSOR(" ", "PF A", this->phase_[PHASEA].power_factor_sensor_);
LOG_SENSOR(" ", "Active Forward Energy A", this->phase_[PHASEA].forward_active_energy_sensor_);
LOG_SENSOR(" ", "Active Reverse Energy A", this->phase_[PHASEA].reverse_active_energy_sensor_);
@@ -235,24 +267,22 @@ void ATM90E32Component::dump_config() {
LOG_SENSOR(" ", "Current B", this->phase_[PHASEB].current_sensor_);
LOG_SENSOR(" ", "Power B", this->phase_[PHASEB].power_sensor_);
LOG_SENSOR(" ", "Reactive Power B", this->phase_[PHASEB].reactive_power_sensor_);
LOG_SENSOR(" ", "Apparent Power B", this->phase_[PHASEB].apparent_power_sensor_);
LOG_SENSOR(" ", "PF B", this->phase_[PHASEB].power_factor_sensor_);
LOG_SENSOR(" ", "Active Forward Energy B", this->phase_[PHASEB].forward_active_energy_sensor_);
LOG_SENSOR(" ", "Active Reverse Energy B", this->phase_[PHASEB].reverse_active_energy_sensor_);
LOG_SENSOR(" ", "Harmonic Power B", this->phase_[PHASEB].harmonic_active_power_sensor_);
LOG_SENSOR(" ", "Phase Angle B", this->phase_[PHASEB].phase_angle_sensor_);
LOG_SENSOR(" ", "Peak Current B", this->phase_[PHASEB].peak_current_sensor_);
LOG_SENSOR(" ", "Harmonic Power A", this->phase_[PHASEB].harmonic_active_power_sensor_);
LOG_SENSOR(" ", "Phase Angle A", this->phase_[PHASEB].phase_angle_sensor_);
LOG_SENSOR(" ", "Peak Current A", this->phase_[PHASEB].peak_current_sensor_);
LOG_SENSOR(" ", "Voltage C", this->phase_[PHASEC].voltage_sensor_);
LOG_SENSOR(" ", "Current C", this->phase_[PHASEC].current_sensor_);
LOG_SENSOR(" ", "Power C", this->phase_[PHASEC].power_sensor_);
LOG_SENSOR(" ", "Reactive Power C", this->phase_[PHASEC].reactive_power_sensor_);
LOG_SENSOR(" ", "Apparent Power C", this->phase_[PHASEC].apparent_power_sensor_);
LOG_SENSOR(" ", "PF C", this->phase_[PHASEC].power_factor_sensor_);
LOG_SENSOR(" ", "Active Forward Energy C", this->phase_[PHASEC].forward_active_energy_sensor_);
LOG_SENSOR(" ", "Active Reverse Energy C", this->phase_[PHASEC].reverse_active_energy_sensor_);
LOG_SENSOR(" ", "Harmonic Power C", this->phase_[PHASEC].harmonic_active_power_sensor_);
LOG_SENSOR(" ", "Phase Angle C", this->phase_[PHASEC].phase_angle_sensor_);
LOG_SENSOR(" ", "Peak Current C", this->phase_[PHASEC].peak_current_sensor_);
LOG_SENSOR(" ", "Harmonic Power A", this->phase_[PHASEC].harmonic_active_power_sensor_);
LOG_SENSOR(" ", "Phase Angle A", this->phase_[PHASEC].phase_angle_sensor_);
LOG_SENSOR(" ", "Peak Current A", this->phase_[PHASEC].peak_current_sensor_);
LOG_SENSOR(" ", "Frequency", this->freq_sensor_);
LOG_SENSOR(" ", "Chip Temp", this->chip_temperature_sensor_);
}
@@ -268,7 +298,7 @@ uint16_t ATM90E32Component::read16_(uint16_t a_register) {
uint8_t data[2];
uint16_t output;
this->enable();
delay_microseconds_safe(1); // min delay between CS low and first SCK is 200ns - 1ms is plenty
delay_microseconds_safe(10);
this->write_byte(addrh);
this->write_byte(addrl);
this->read_array(data, 2);
@@ -298,7 +328,8 @@ void ATM90E32Component::write16_(uint16_t a_register, uint16_t val) {
this->write_byte16(a_register);
this->write_byte16(val);
this->disable();
this->validate_spi_read_(val, "write16()");
if (this->read16_(ATM90E32_REGISTER_LASTSPIDATA) != val)
ESP_LOGW(TAG, "SPI write error 0x%04X val 0x%04X", a_register, val);
}
float ATM90E32Component::get_local_phase_voltage_(uint8_t phase) { return this->phase_[phase].voltage_; }
@@ -309,8 +340,6 @@ float ATM90E32Component::get_local_phase_active_power_(uint8_t phase) { return t
float ATM90E32Component::get_local_phase_reactive_power_(uint8_t phase) { return this->phase_[phase].reactive_power_; }
float ATM90E32Component::get_local_phase_apparent_power_(uint8_t phase) { return this->phase_[phase].apparent_power_; }
float ATM90E32Component::get_local_phase_power_factor_(uint8_t phase) { return this->phase_[phase].power_factor_; }
float ATM90E32Component::get_local_phase_forward_active_energy_(uint8_t phase) {
@@ -331,7 +360,8 @@ float ATM90E32Component::get_local_phase_peak_current_(uint8_t phase) { return t
float ATM90E32Component::get_phase_voltage_(uint8_t phase) {
const uint16_t voltage = this->read16_(ATM90E32_REGISTER_URMS + phase);
this->validate_spi_read_(voltage, "get_phase_voltage()");
if (this->read16_(ATM90E32_REGISTER_LASTSPIDATA) != voltage)
ESP_LOGW(TAG, "SPI URMS voltage register read error.");
return (float) voltage / 100;
}
@@ -341,7 +371,8 @@ float ATM90E32Component::get_phase_voltage_avg_(uint8_t phase) {
uint16_t voltage = 0;
for (uint8_t i = 0; i < reads; i++) {
voltage = this->read16_(ATM90E32_REGISTER_URMS + phase);
this->validate_spi_read_(voltage, "get_phase_voltage_avg_()");
if (this->read16_(ATM90E32_REGISTER_LASTSPIDATA) != voltage)
ESP_LOGW(TAG, "SPI URMS voltage register read error.");
accumulation += voltage;
}
voltage = accumulation / reads;
@@ -355,7 +386,8 @@ float ATM90E32Component::get_phase_current_avg_(uint8_t phase) {
uint16_t current = 0;
for (uint8_t i = 0; i < reads; i++) {
current = this->read16_(ATM90E32_REGISTER_IRMS + phase);
this->validate_spi_read_(current, "get_phase_current_avg_()");
if (this->read16_(ATM90E32_REGISTER_LASTSPIDATA) != current)
ESP_LOGW(TAG, "SPI IRMS current register read error.");
accumulation += current;
}
current = accumulation / reads;
@@ -365,7 +397,8 @@ float ATM90E32Component::get_phase_current_avg_(uint8_t phase) {
float ATM90E32Component::get_phase_current_(uint8_t phase) {
const uint16_t current = this->read16_(ATM90E32_REGISTER_IRMS + phase);
this->validate_spi_read_(current, "get_phase_current_()");
if (this->read16_(ATM90E32_REGISTER_LASTSPIDATA) != current)
ESP_LOGW(TAG, "SPI IRMS current register read error.");
return (float) current / 1000;
}
@@ -379,15 +412,11 @@ float ATM90E32Component::get_phase_reactive_power_(uint8_t phase) {
return val * 0.00032f;
}
float ATM90E32Component::get_phase_apparent_power_(uint8_t phase) {
const int val = this->read32_(ATM90E32_REGISTER_SMEAN + phase, ATM90E32_REGISTER_SMEANLSB + phase);
return val * 0.00032f;
}
float ATM90E32Component::get_phase_power_factor_(uint8_t phase) {
uint16_t powerfactor = this->read16_(ATM90E32_REGISTER_PFMEAN + phase); // unsigned to compare to lastspidata
this->validate_spi_read_(powerfactor, "get_phase_power_factor_()");
return (float) ((int16_t) powerfactor) / 1000; // make it signed again
const int16_t powerfactor = this->read16_(ATM90E32_REGISTER_PFMEAN + phase);
if (this->read16_(ATM90E32_REGISTER_LASTSPIDATA) != powerfactor)
ESP_LOGW(TAG, "SPI power factor read error.");
return (float) powerfactor / 1000;
}
float ATM90E32Component::get_phase_forward_active_energy_(uint8_t phase) {
@@ -397,19 +426,17 @@ float ATM90E32Component::get_phase_forward_active_energy_(uint8_t phase) {
} else {
this->phase_[phase].cumulative_forward_active_energy_ = val;
}
// 0.01CF resolution = 0.003125 Wh per count
return ((float) this->phase_[phase].cumulative_forward_active_energy_ * (10.0f / 3200.0f));
return ((float) this->phase_[phase].cumulative_forward_active_energy_ * 10 / 3200);
}
float ATM90E32Component::get_phase_reverse_active_energy_(uint8_t phase) {
const uint16_t val = this->read16_(ATM90E32_REGISTER_ANENERGY + phase);
const uint16_t val = this->read16_(ATM90E32_REGISTER_ANENERGY);
if (UINT32_MAX - this->phase_[phase].cumulative_reverse_active_energy_ > val) {
this->phase_[phase].cumulative_reverse_active_energy_ += val;
} else {
this->phase_[phase].cumulative_reverse_active_energy_ = val;
}
// 0.01CF resolution = 0.003125 Wh per count
return ((float) this->phase_[phase].cumulative_reverse_active_energy_ * (10.0f / 3200.0f));
return ((float) this->phase_[phase].cumulative_reverse_active_energy_ * 10 / 3200);
}
float ATM90E32Component::get_phase_harmonic_active_power_(uint8_t phase) {
@@ -419,15 +446,15 @@ float ATM90E32Component::get_phase_harmonic_active_power_(uint8_t phase) {
float ATM90E32Component::get_phase_angle_(uint8_t phase) {
uint16_t val = this->read16_(ATM90E32_REGISTER_PANGLE + phase) / 10.0;
return (val > 180) ? (float) (val - 360.0f) : (float) val;
return (float) (val > 180) ? val - 360.0 : val;
}
float ATM90E32Component::get_phase_peak_current_(uint8_t phase) {
int16_t val = (float) this->read16_(ATM90E32_REGISTER_IPEAK + phase);
if (!this->peak_current_signed_)
val = std::abs(val);
val = abs(val);
// phase register * phase current gain value / 1000 * 2^13
return (val * this->phase_[phase].ct_gain_ / 8192000.0);
return (float) (val * this->phase_[phase].ct_gain_ / 8192000.0);
}
float ATM90E32Component::get_frequency_() {
@@ -440,433 +467,29 @@ float ATM90E32Component::get_chip_temperature_() {
return (float) ctemp;
}
void ATM90E32Component::run_gain_calibrations() {
if (!this->enable_gain_calibration_) {
ESP_LOGW(TAG, "[CALIBRATION] Gain calibration is disabled! Enable it first with enable_gain_calibration: true");
return;
}
float ref_voltages[3] = {
this->get_reference_voltage(0),
this->get_reference_voltage(1),
this->get_reference_voltage(2),
};
float ref_currents[3] = {this->get_reference_current(0), this->get_reference_current(1),
this->get_reference_current(2)};
ESP_LOGI(TAG, "[CALIBRATION] ");
ESP_LOGI(TAG, "[CALIBRATION] ========================= Gain Calibration =========================");
ESP_LOGI(TAG, "[CALIBRATION] ---------------------------------------------------------------------");
ESP_LOGI(TAG,
"[CALIBRATION] | Phase | V_meas (V) | I_meas (A) | V_ref | I_ref | V_gain (old→new) | I_gain (old→new) |");
ESP_LOGI(TAG, "[CALIBRATION] ---------------------------------------------------------------------");
for (uint8_t phase = 0; phase < 3; phase++) {
float measured_voltage = this->get_phase_voltage_avg_(phase);
float measured_current = this->get_phase_current_avg_(phase);
float ref_voltage = ref_voltages[phase];
float ref_current = ref_currents[phase];
uint16_t current_voltage_gain = this->read16_(voltage_gain_registers[phase]);
uint16_t current_current_gain = this->read16_(current_gain_registers[phase]);
bool did_voltage = false;
bool did_current = false;
// Voltage calibration
if (ref_voltage <= 0.0f) {
ESP_LOGW(TAG, "[CALIBRATION] Phase %s - Skipping voltage calibration: reference voltage is 0.",
phase_labels[phase]);
} else if (measured_voltage == 0.0f) {
ESP_LOGW(TAG, "[CALIBRATION] Phase %s - Skipping voltage calibration: measured voltage is 0.",
phase_labels[phase]);
} else {
uint32_t new_voltage_gain = static_cast<uint16_t>((ref_voltage / measured_voltage) * current_voltage_gain);
if (new_voltage_gain == 0) {
ESP_LOGW(TAG, "[CALIBRATION] Phase %s - Voltage gain would be 0. Check reference and measured voltage.",
phase_labels[phase]);
} else {
if (new_voltage_gain >= 65535) {
ESP_LOGW(
TAG,
"[CALIBRATION] Phase %s - Voltage gain exceeds 65535. You may need a higher output voltage transformer.",
phase_labels[phase]);
new_voltage_gain = 65535;
}
this->gain_phase_[phase].voltage_gain = static_cast<uint16_t>(new_voltage_gain);
did_voltage = true;
}
}
// Current calibration
if (ref_current == 0.0f) {
ESP_LOGW(TAG, "[CALIBRATION] Phase %s - Skipping current calibration: reference current is 0.",
phase_labels[phase]);
} else if (measured_current == 0.0f) {
ESP_LOGW(TAG, "[CALIBRATION] Phase %s - Skipping current calibration: measured current is 0.",
phase_labels[phase]);
} else {
uint32_t new_current_gain = static_cast<uint16_t>((ref_current / measured_current) * current_current_gain);
if (new_current_gain == 0) {
ESP_LOGW(TAG, "[CALIBRATION] Phase %s - Current gain would be 0. Check reference and measured current.",
phase_labels[phase]);
} else {
if (new_current_gain >= 65535) {
ESP_LOGW(TAG, "[CALIBRATION] Phase %s - Current gain exceeds 65535. You may need to turn up pga gain.",
phase_labels[phase]);
new_current_gain = 65535;
}
this->gain_phase_[phase].current_gain = static_cast<uint16_t>(new_current_gain);
did_current = true;
}
}
// Final row output
ESP_LOGI(TAG, "[CALIBRATION] | %c | %9.2f | %9.4f | %5.2f | %6.4f | %5u → %-5u | %5u → %-5u |",
'A' + phase, measured_voltage, measured_current, ref_voltage, ref_current, current_voltage_gain,
did_voltage ? this->gain_phase_[phase].voltage_gain : current_voltage_gain, current_current_gain,
did_current ? this->gain_phase_[phase].current_gain : current_current_gain);
}
ESP_LOGI(TAG, "[CALIBRATION] =====================================================================\n");
this->save_gain_calibration_to_memory_();
this->write_gains_to_registers_();
this->verify_gain_writes_();
}
void ATM90E32Component::save_gain_calibration_to_memory_() {
bool success = this->gain_calibration_pref_.save(&this->gain_phase_);
if (success) {
this->using_saved_calibrations_ = true;
ESP_LOGI(TAG, "[CALIBRATION] Gain calibration saved to memory.");
} else {
this->using_saved_calibrations_ = false;
ESP_LOGE(TAG, "[CALIBRATION] Failed to save gain calibration to memory!");
}
}
void ATM90E32Component::run_offset_calibrations() {
if (!this->enable_offset_calibration_) {
ESP_LOGW(TAG, "[CALIBRATION] Offset calibration is disabled! Enable it first with enable_offset_calibration: true");
return;
}
for (uint8_t phase = 0; phase < 3; phase++) {
int16_t voltage_offset = calibrate_offset(phase, true);
int16_t current_offset = calibrate_offset(phase, false);
this->write_offsets_to_registers_(phase, voltage_offset, current_offset);
ESP_LOGI(TAG, "[CALIBRATION] Phase %c - offset_voltage: %d, offset_current: %d", 'A' + phase, voltage_offset,
current_offset);
}
this->offset_pref_.save(&this->offset_phase_); // Save to flash
}
void ATM90E32Component::run_power_offset_calibrations() {
if (!this->enable_offset_calibration_) {
ESP_LOGW(
TAG,
"[CALIBRATION] Offset power calibration is disabled! Enable it first with enable_offset_calibration: true");
return;
}
for (uint8_t phase = 0; phase < 3; ++phase) {
int16_t active_offset = calibrate_power_offset(phase, false);
int16_t reactive_offset = calibrate_power_offset(phase, true);
this->write_power_offsets_to_registers_(phase, active_offset, reactive_offset);
ESP_LOGI(TAG, "[CALIBRATION] Phase %c - offset_active_power: %d, offset_reactive_power: %d", 'A' + phase,
active_offset, reactive_offset);
}
this->power_offset_pref_.save(&this->power_offset_phase_); // Save to flash
}
void ATM90E32Component::write_gains_to_registers_() {
this->write16_(ATM90E32_REGISTER_CFGREGACCEN, 0x55AA);
for (int phase = 0; phase < 3; phase++) {
this->write16_(voltage_gain_registers[phase], this->gain_phase_[phase].voltage_gain);
this->write16_(current_gain_registers[phase], this->gain_phase_[phase].current_gain);
}
this->write16_(ATM90E32_REGISTER_CFGREGACCEN, 0x0000);
}
void ATM90E32Component::write_offsets_to_registers_(uint8_t phase, int16_t voltage_offset, int16_t current_offset) {
// Save to runtime
this->offset_phase_[phase].voltage_offset_ = voltage_offset;
this->phase_[phase].voltage_offset_ = voltage_offset;
// Save to flash-storable struct
this->offset_phase_[phase].current_offset_ = current_offset;
this->phase_[phase].current_offset_ = current_offset;
// Write to registers
this->write16_(ATM90E32_REGISTER_CFGREGACCEN, 0x55AA);
this->write16_(voltage_offset_registers[phase], static_cast<uint16_t>(voltage_offset));
this->write16_(current_offset_registers[phase], static_cast<uint16_t>(current_offset));
this->write16_(ATM90E32_REGISTER_CFGREGACCEN, 0x0000);
}
void ATM90E32Component::write_power_offsets_to_registers_(uint8_t phase, int16_t p_offset, int16_t q_offset) {
// Save to runtime
this->phase_[phase].active_power_offset_ = p_offset;
this->phase_[phase].reactive_power_offset_ = q_offset;
// Save to flash-storable struct
this->power_offset_phase_[phase].active_power_offset = p_offset;
this->power_offset_phase_[phase].reactive_power_offset = q_offset;
// Write to registers
this->write16_(ATM90E32_REGISTER_CFGREGACCEN, 0x55AA);
this->write16_(this->power_offset_registers[phase], static_cast<uint16_t>(p_offset));
this->write16_(this->reactive_power_offset_registers[phase], static_cast<uint16_t>(q_offset));
this->write16_(ATM90E32_REGISTER_CFGREGACCEN, 0x0000);
}
void ATM90E32Component::restore_gain_calibrations_() {
if (this->gain_calibration_pref_.load(&this->gain_phase_)) {
ESP_LOGI(TAG, "[CALIBRATION] Restoring saved gain calibrations to registers:");
for (uint8_t phase = 0; phase < 3; phase++) {
uint16_t v_gain = this->gain_phase_[phase].voltage_gain;
uint16_t i_gain = this->gain_phase_[phase].current_gain;
ESP_LOGI(TAG, "[CALIBRATION] Phase %c - Voltage Gain: %u, Current Gain: %u", 'A' + phase, v_gain, i_gain);
}
this->write_gains_to_registers_();
if (this->verify_gain_writes_()) {
this->using_saved_calibrations_ = true;
ESP_LOGI(TAG, "[CALIBRATION] Gain calibration loaded and verified successfully.");
} else {
this->using_saved_calibrations_ = false;
ESP_LOGE(TAG, "[CALIBRATION] Gain verification failed! Calibration may not be applied correctly.");
}
} else {
this->using_saved_calibrations_ = false;
ESP_LOGW(TAG, "[CALIBRATION] No stored gain calibrations found. Using config file values.");
}
}
void ATM90E32Component::restore_offset_calibrations_() {
if (this->offset_pref_.load(&this->offset_phase_)) {
ESP_LOGI(TAG, "[CALIBRATION] Successfully restored offset calibration from memory.");
for (uint8_t phase = 0; phase < 3; phase++) {
auto &offset = this->offset_phase_[phase];
write_offsets_to_registers_(phase, offset.voltage_offset_, offset.current_offset_);
ESP_LOGI(TAG, "[CALIBRATION] Phase %c - offset_voltage:: %d, offset_current: %d", 'A' + phase,
offset.voltage_offset_, offset.current_offset_);
}
} else {
ESP_LOGW(TAG, "[CALIBRATION] No stored offset calibrations found. Using default values.");
}
}
void ATM90E32Component::restore_power_offset_calibrations_() {
if (this->power_offset_pref_.load(&this->power_offset_phase_)) {
ESP_LOGI(TAG, "[CALIBRATION] Successfully restored power offset calibration from memory.");
for (uint8_t phase = 0; phase < 3; ++phase) {
auto &offset = this->power_offset_phase_[phase];
write_power_offsets_to_registers_(phase, offset.active_power_offset, offset.reactive_power_offset);
ESP_LOGI(TAG, "[CALIBRATION] Phase %c - offset_active_power: %d, offset_reactive_power: %d", 'A' + phase,
offset.active_power_offset, offset.reactive_power_offset);
}
} else {
ESP_LOGW(TAG, "[CALIBRATION] No stored power offsets found. Using default values.");
}
}
void ATM90E32Component::clear_gain_calibrations() {
ESP_LOGI(TAG, "[CALIBRATION] Clearing stored gain calibrations and restoring config-defined values...");
for (int phase = 0; phase < 3; phase++) {
gain_phase_[phase].voltage_gain = this->phase_[phase].voltage_gain_;
gain_phase_[phase].current_gain = this->phase_[phase].ct_gain_;
}
bool success = this->gain_calibration_pref_.save(&this->gain_phase_);
this->using_saved_calibrations_ = false;
if (success) {
ESP_LOGI(TAG, "[CALIBRATION] Gain calibrations cleared. Config values restored:");
for (int phase = 0; phase < 3; phase++) {
ESP_LOGI(TAG, "[CALIBRATION] Phase %c - Voltage Gain: %u, Current Gain: %u", 'A' + phase,
gain_phase_[phase].voltage_gain, gain_phase_[phase].current_gain);
}
} else {
ESP_LOGE(TAG, "[CALIBRATION] Failed to clear gain calibrations!");
}
this->write_gains_to_registers_(); // Apply them to the chip immediately
}
void ATM90E32Component::clear_offset_calibrations() {
for (uint8_t phase = 0; phase < 3; phase++) {
this->write_offsets_to_registers_(phase, 0, 0);
}
this->offset_pref_.save(&this->offset_phase_); // Save cleared values to flash memory
ESP_LOGI(TAG, "[CALIBRATION] Offsets cleared.");
}
void ATM90E32Component::clear_power_offset_calibrations() {
for (uint8_t phase = 0; phase < 3; phase++) {
this->write_power_offsets_to_registers_(phase, 0, 0);
}
this->power_offset_pref_.save(&this->power_offset_phase_);
ESP_LOGI(TAG, "[CALIBRATION] Power offsets cleared.");
}
int16_t ATM90E32Component::calibrate_offset(uint8_t phase, bool voltage) {
uint16_t ATM90E32Component::calibrate_voltage_offset_phase(uint8_t phase) {
const uint8_t num_reads = 5;
uint64_t total_value = 0;
for (uint8_t i = 0; i < num_reads; ++i) {
uint32_t reading = voltage ? this->read32_(ATM90E32_REGISTER_URMS + phase, ATM90E32_REGISTER_URMSLSB + phase)
: this->read32_(ATM90E32_REGISTER_IRMS + phase, ATM90E32_REGISTER_IRMSLSB + phase);
total_value += reading;
for (int i = 0; i < num_reads; ++i) {
const uint32_t measurement_value = read32_(ATM90E32_REGISTER_URMS + phase, ATM90E32_REGISTER_URMSLSB + phase);
total_value += measurement_value;
}
const uint32_t average_value = total_value / num_reads;
const uint32_t shifted = average_value >> 7;
const uint32_t offset = ~shifted + 1;
return static_cast<int16_t>(offset); // Takes lower 16 bits
const uint32_t shifted_value = average_value >> 7;
const uint32_t voltage_offset = ~shifted_value + 1;
return voltage_offset & 0xFFFF; // Take the lower 16 bits
}
int16_t ATM90E32Component::calibrate_power_offset(uint8_t phase, bool reactive) {
uint16_t ATM90E32Component::calibrate_current_offset_phase(uint8_t phase) {
const uint8_t num_reads = 5;
uint64_t total_value = 0;
for (uint8_t i = 0; i < num_reads; ++i) {
uint32_t reading = reactive ? this->read32_(ATM90E32_REGISTER_QMEAN + phase, ATM90E32_REGISTER_QMEANLSB + phase)
: this->read32_(ATM90E32_REGISTER_PMEAN + phase, ATM90E32_REGISTER_PMEANLSB + phase);
total_value += reading;
for (int i = 0; i < num_reads; ++i) {
const uint32_t measurement_value = read32_(ATM90E32_REGISTER_IRMS + phase, ATM90E32_REGISTER_IRMSLSB + phase);
total_value += measurement_value;
}
const uint32_t average_value = total_value / num_reads;
const uint32_t power_offset = ~average_value + 1;
return static_cast<int16_t>(power_offset); // Takes the lower 16 bits
}
bool ATM90E32Component::verify_gain_writes_() {
bool success = true;
for (uint8_t phase = 0; phase < 3; phase++) {
uint16_t read_voltage = this->read16_(voltage_gain_registers[phase]);
uint16_t read_current = this->read16_(current_gain_registers[phase]);
if (read_voltage != this->gain_phase_[phase].voltage_gain ||
read_current != this->gain_phase_[phase].current_gain) {
ESP_LOGE(TAG, "[CALIBRATION] Mismatch detected for Phase %s!", phase_labels[phase]);
success = false;
}
}
return success; // Return true if all writes were successful, false otherwise
}
#ifdef USE_TEXT_SENSOR
void ATM90E32Component::check_phase_status() {
uint16_t state0 = this->read16_(ATM90E32_REGISTER_EMMSTATE0);
uint16_t state1 = this->read16_(ATM90E32_REGISTER_EMMSTATE1);
for (int phase = 0; phase < 3; phase++) {
std::string status;
if (state0 & over_voltage_flags[phase])
status += "Over Voltage; ";
if (state1 & voltage_sag_flags[phase])
status += "Voltage Sag; ";
if (state1 & phase_loss_flags[phase])
status += "Phase Loss; ";
auto *sensor = this->phase_status_text_sensor_[phase];
const char *phase_name = sensor ? sensor->get_name().c_str() : "Unknown Phase";
if (!status.empty()) {
status.pop_back(); // remove space
status.pop_back(); // remove semicolon
ESP_LOGW(TAG, "%s: %s", phase_name, status.c_str());
if (sensor != nullptr)
sensor->publish_state(status);
} else {
if (sensor != nullptr)
sensor->publish_state("Okay");
}
}
}
void ATM90E32Component::check_freq_status() {
uint16_t state1 = this->read16_(ATM90E32_REGISTER_EMMSTATE1);
std::string freq_status;
if (state1 & ATM90E32_STATUS_S1_FREQHIST) {
freq_status = "HIGH";
} else if (state1 & ATM90E32_STATUS_S1_FREQLOST) {
freq_status = "LOW";
} else {
freq_status = "Normal";
}
ESP_LOGW(TAG, "Frequency status: %s", freq_status.c_str());
if (this->freq_status_text_sensor_ != nullptr) {
this->freq_status_text_sensor_->publish_state(freq_status);
}
}
void ATM90E32Component::check_over_current() {
constexpr float max_current_threshold = 65.53f;
for (uint8_t phase = 0; phase < 3; phase++) {
float current_val =
this->phase_[phase].current_sensor_ != nullptr ? this->phase_[phase].current_sensor_->state : 0.0f;
if (current_val > max_current_threshold) {
ESP_LOGW(TAG, "Over current detected on Phase %c: %.2f A", 'A' + phase, current_val);
ESP_LOGW(TAG, "You may need to half your gain_ct: value & multiply the current and power values by 2");
if (this->phase_status_text_sensor_[phase] != nullptr) {
this->phase_status_text_sensor_[phase]->publish_state("Over Current; ");
}
}
}
}
#endif
uint16_t ATM90E32Component::calculate_voltage_threshold(int line_freq, uint16_t ugain, float multiplier) {
// this assumes that 60Hz electrical systems use 120V mains,
// which is usually, but not always the case
float nominal_voltage = (line_freq == 60) ? 120.0f : 220.0f;
float target_voltage = nominal_voltage * multiplier;
float peak_01v = target_voltage * 100.0f * std::sqrt(2.0f); // convert RMS → peak, scale to 0.01V
float divider = (2.0f * ugain) / 32768.0f;
float threshold = peak_01v / divider;
return static_cast<uint16_t>(threshold);
}
bool ATM90E32Component::validate_spi_read_(uint16_t expected, const char *context) {
uint16_t last = this->read16_(ATM90E32_REGISTER_LASTSPIDATA);
if (last != expected) {
if (context != nullptr) {
ESP_LOGW(TAG, "[%s] SPI read mismatch: expected 0x%04X, got 0x%04X", context, expected, last);
} else {
ESP_LOGW(TAG, "SPI read mismatch: expected 0x%04X, got 0x%04X", expected, last);
}
return false;
}
return true;
const uint32_t current_offset = ~average_value + 1;
return current_offset & 0xFFFF; // Take the lower 16 bits
}
} // namespace atm90e32

View File

@@ -1,6 +1,5 @@
#pragma once
#include <unordered_map>
#include "atm90e32_reg.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/spi/spi.h"
@@ -19,26 +18,6 @@ class ATM90E32Component : public PollingComponent,
static const uint8_t PHASEA = 0;
static const uint8_t PHASEB = 1;
static const uint8_t PHASEC = 2;
const char *phase_labels[3] = {"A", "B", "C"};
// these registers are not sucessive, so we can't just do 'base + phase'
const uint16_t voltage_gain_registers[3] = {ATM90E32_REGISTER_UGAINA, ATM90E32_REGISTER_UGAINB,
ATM90E32_REGISTER_UGAINC};
const uint16_t current_gain_registers[3] = {ATM90E32_REGISTER_IGAINA, ATM90E32_REGISTER_IGAINB,
ATM90E32_REGISTER_IGAINC};
const uint16_t voltage_offset_registers[3] = {ATM90E32_REGISTER_UOFFSETA, ATM90E32_REGISTER_UOFFSETB,
ATM90E32_REGISTER_UOFFSETC};
const uint16_t current_offset_registers[3] = {ATM90E32_REGISTER_IOFFSETA, ATM90E32_REGISTER_IOFFSETB,
ATM90E32_REGISTER_IOFFSETC};
const uint16_t power_offset_registers[3] = {ATM90E32_REGISTER_POFFSETA, ATM90E32_REGISTER_POFFSETB,
ATM90E32_REGISTER_POFFSETC};
const uint16_t reactive_power_offset_registers[3] = {ATM90E32_REGISTER_QOFFSETA, ATM90E32_REGISTER_QOFFSETB,
ATM90E32_REGISTER_QOFFSETC};
const uint16_t over_voltage_flags[3] = {ATM90E32_STATUS_S0_OVPHASEAST, ATM90E32_STATUS_S0_OVPHASEBST,
ATM90E32_STATUS_S0_OVPHASECST};
const uint16_t voltage_sag_flags[3] = {ATM90E32_STATUS_S1_SAGPHASEAST, ATM90E32_STATUS_S1_SAGPHASEBST,
ATM90E32_STATUS_S1_SAGPHASECST};
const uint16_t phase_loss_flags[3] = {ATM90E32_STATUS_S1_PHASELOSSAST, ATM90E32_STATUS_S1_PHASELOSSBST,
ATM90E32_STATUS_S1_PHASELOSSCST};
void loop() override;
void setup() override;
void dump_config() override;
@@ -63,14 +42,6 @@ class ATM90E32Component : public PollingComponent,
void set_peak_current_sensor(int phase, sensor::Sensor *obj) { this->phase_[phase].peak_current_sensor_ = obj; }
void set_volt_gain(int phase, uint16_t gain) { this->phase_[phase].voltage_gain_ = gain; }
void set_ct_gain(int phase, uint16_t gain) { this->phase_[phase].ct_gain_ = gain; }
void set_voltage_offset(uint8_t phase, int16_t offset) { this->offset_phase_[phase].voltage_offset_ = offset; }
void set_current_offset(uint8_t phase, int16_t offset) { this->offset_phase_[phase].current_offset_ = offset; }
void set_active_power_offset(uint8_t phase, int16_t offset) {
this->power_offset_phase_[phase].active_power_offset = offset;
}
void set_reactive_power_offset(uint8_t phase, int16_t offset) {
this->power_offset_phase_[phase].reactive_power_offset = offset;
}
void set_freq_sensor(sensor::Sensor *freq_sensor) { freq_sensor_ = freq_sensor; }
void set_peak_current_signed(bool flag) { peak_current_signed_ = flag; }
void set_chip_temperature_sensor(sensor::Sensor *chip_temperature_sensor) {
@@ -80,104 +51,53 @@ class ATM90E32Component : public PollingComponent,
void set_current_phases(int phases) { current_phases_ = phases; }
void set_pga_gain(uint16_t gain) { pga_gain_ = gain; }
void run_offset_calibrations();
void run_power_offset_calibrations();
void clear_offset_calibrations();
void clear_power_offset_calibrations();
void clear_gain_calibrations();
void set_enable_offset_calibration(bool flag) { enable_offset_calibration_ = flag; }
void set_enable_gain_calibration(bool flag) { enable_gain_calibration_ = flag; }
int16_t calibrate_offset(uint8_t phase, bool voltage);
int16_t calibrate_power_offset(uint8_t phase, bool reactive);
void run_gain_calibrations();
#ifdef USE_NUMBER
void set_reference_voltage(uint8_t phase, number::Number *ref_voltage) { ref_voltages_[phase] = ref_voltage; }
void set_reference_current(uint8_t phase, number::Number *ref_current) { ref_currents_[phase] = ref_current; }
#endif
float get_reference_voltage(uint8_t phase) {
#ifdef USE_NUMBER
return (phase >= 0 && phase < 3 && ref_voltages_[phase]) ? ref_voltages_[phase]->state : 120.0; // Default voltage
#else
return 120.0; // Default voltage
#endif
}
float get_reference_current(uint8_t phase) {
#ifdef USE_NUMBER
return (phase >= 0 && phase < 3 && ref_currents_[phase]) ? ref_currents_[phase]->state : 5.0f; // Default current
#else
return 5.0f; // Default current
#endif
}
bool using_saved_calibrations_ = false; // Track if stored calibrations are being used
#ifdef USE_TEXT_SENSOR
void check_phase_status();
void check_freq_status();
void check_over_current();
void set_phase_status_text_sensor(uint8_t phase, text_sensor::TextSensor *sensor) {
this->phase_status_text_sensor_[phase] = sensor;
}
void set_freq_status_text_sensor(text_sensor::TextSensor *sensor) { this->freq_status_text_sensor_ = sensor; }
#endif
uint16_t calculate_voltage_threshold(int line_freq, uint16_t ugain, float multiplier);
uint16_t calibrate_voltage_offset_phase(uint8_t /*phase*/);
uint16_t calibrate_current_offset_phase(uint8_t /*phase*/);
int32_t last_periodic_millis = millis();
protected:
#ifdef USE_NUMBER
number::Number *ref_voltages_[3]{nullptr, nullptr, nullptr};
number::Number *ref_currents_[3]{nullptr, nullptr, nullptr};
#endif
uint16_t read16_(uint16_t a_register);
int read32_(uint16_t addr_h, uint16_t addr_l);
void write16_(uint16_t a_register, uint16_t val);
float get_local_phase_voltage_(uint8_t phase);
float get_local_phase_current_(uint8_t phase);
float get_local_phase_active_power_(uint8_t phase);
float get_local_phase_reactive_power_(uint8_t phase);
float get_local_phase_apparent_power_(uint8_t phase);
float get_local_phase_power_factor_(uint8_t phase);
float get_local_phase_forward_active_energy_(uint8_t phase);
float get_local_phase_reverse_active_energy_(uint8_t phase);
float get_local_phase_angle_(uint8_t phase);
float get_local_phase_harmonic_active_power_(uint8_t phase);
float get_local_phase_peak_current_(uint8_t phase);
float get_phase_voltage_(uint8_t phase);
float get_phase_voltage_avg_(uint8_t phase);
float get_phase_current_(uint8_t phase);
float get_phase_current_avg_(uint8_t phase);
float get_phase_active_power_(uint8_t phase);
float get_phase_reactive_power_(uint8_t phase);
float get_phase_apparent_power_(uint8_t phase);
float get_phase_power_factor_(uint8_t phase);
float get_phase_forward_active_energy_(uint8_t phase);
float get_phase_reverse_active_energy_(uint8_t phase);
float get_phase_angle_(uint8_t phase);
float get_phase_harmonic_active_power_(uint8_t phase);
float get_phase_peak_current_(uint8_t phase);
float get_local_phase_voltage_(uint8_t /*phase*/);
float get_local_phase_current_(uint8_t /*phase*/);
float get_local_phase_active_power_(uint8_t /*phase*/);
float get_local_phase_reactive_power_(uint8_t /*phase*/);
float get_local_phase_power_factor_(uint8_t /*phase*/);
float get_local_phase_forward_active_energy_(uint8_t /*phase*/);
float get_local_phase_reverse_active_energy_(uint8_t /*phase*/);
float get_local_phase_angle_(uint8_t /*phase*/);
float get_local_phase_harmonic_active_power_(uint8_t /*phase*/);
float get_local_phase_peak_current_(uint8_t /*phase*/);
float get_phase_voltage_(uint8_t /*phase*/);
float get_phase_voltage_avg_(uint8_t /*phase*/);
float get_phase_current_(uint8_t /*phase*/);
float get_phase_current_avg_(uint8_t /*phase*/);
float get_phase_active_power_(uint8_t /*phase*/);
float get_phase_reactive_power_(uint8_t /*phase*/);
float get_phase_power_factor_(uint8_t /*phase*/);
float get_phase_forward_active_energy_(uint8_t /*phase*/);
float get_phase_reverse_active_energy_(uint8_t /*phase*/);
float get_phase_angle_(uint8_t /*phase*/);
float get_phase_harmonic_active_power_(uint8_t /*phase*/);
float get_phase_peak_current_(uint8_t /*phase*/);
float get_frequency_();
float get_chip_temperature_();
bool get_publish_interval_flag_() { return publish_interval_flag_; };
void set_publish_interval_flag_(bool flag) { publish_interval_flag_ = flag; };
void restore_offset_calibrations_();
void restore_power_offset_calibrations_();
void restore_gain_calibrations_();
void save_gain_calibration_to_memory_();
void write_offsets_to_registers_(uint8_t phase, int16_t voltage_offset, int16_t current_offset);
void write_power_offsets_to_registers_(uint8_t phase, int16_t p_offset, int16_t q_offset);
void write_gains_to_registers_();
bool verify_gain_writes_();
bool validate_spi_read_(uint16_t expected, const char *context = nullptr);
void restore_calibrations_();
struct ATM90E32Phase {
uint16_t voltage_gain_{0};
uint16_t ct_gain_{0};
int16_t voltage_offset_{0};
int16_t current_offset_{0};
int16_t active_power_offset_{0};
int16_t reactive_power_offset_{0};
uint16_t voltage_offset_{0};
uint16_t current_offset_{0};
float voltage_{0};
float current_{0};
float active_power_{0};
float reactive_power_{0};
float apparent_power_{0};
float power_factor_{0};
float forward_active_energy_{0};
float reverse_active_energy_{0};
@@ -199,30 +119,14 @@ class ATM90E32Component : public PollingComponent,
uint32_t cumulative_reverse_active_energy_{0};
} phase_[3];
struct OffsetCalibration {
int16_t voltage_offset_{0};
int16_t current_offset_{0};
struct Calibration {
uint16_t voltage_offset_{0};
uint16_t current_offset_{0};
} offset_phase_[3];
struct PowerOffsetCalibration {
int16_t active_power_offset{0};
int16_t reactive_power_offset{0};
} power_offset_phase_[3];
struct GainCalibration {
uint16_t voltage_gain{1};
uint16_t current_gain{1};
} gain_phase_[3];
ESPPreferenceObject offset_pref_;
ESPPreferenceObject power_offset_pref_;
ESPPreferenceObject gain_calibration_pref_;
ESPPreferenceObject pref_;
sensor::Sensor *freq_sensor_{nullptr};
#ifdef USE_TEXT_SENSOR
text_sensor::TextSensor *phase_status_text_sensor_[3]{nullptr};
text_sensor::TextSensor *freq_status_text_sensor_{nullptr};
#endif
sensor::Sensor *chip_temperature_sensor_{nullptr};
uint16_t pga_gain_{0x15};
int line_freq_{60};
@@ -230,7 +134,6 @@ class ATM90E32Component : public PollingComponent,
bool publish_interval_flag_{false};
bool peak_current_signed_{false};
bool enable_offset_calibration_{false};
bool enable_gain_calibration_{false};
};
} // namespace atm90e32

View File

@@ -176,17 +176,16 @@ static const uint16_t ATM90E32_REGISTER_ANENERGYCH = 0xAF; // C Reverse Harm. E
/* POWER & P.F. REGISTERS */
static const uint16_t ATM90E32_REGISTER_PMEANT = 0xB0; // Total Mean Power (P)
static const uint16_t ATM90E32_REGISTER_PMEAN = 0xB1; // Active Power Reg Base (P)
static const uint16_t ATM90E32_REGISTER_PMEAN = 0xB1; // Mean Power Reg Base (P)
static const uint16_t ATM90E32_REGISTER_PMEANA = 0xB1; // A Mean Power (P)
static const uint16_t ATM90E32_REGISTER_PMEANB = 0xB2; // B Mean Power (P)
static const uint16_t ATM90E32_REGISTER_PMEANC = 0xB3; // C Mean Power (P)
static const uint16_t ATM90E32_REGISTER_QMEANT = 0xB4; // Total Mean Power (Q)
static const uint16_t ATM90E32_REGISTER_QMEAN = 0xB5; // Reactive Power Reg Base (Q)
static const uint16_t ATM90E32_REGISTER_QMEAN = 0xB5; // Mean Power Reg Base (Q)
static const uint16_t ATM90E32_REGISTER_QMEANA = 0xB5; // A Mean Power (Q)
static const uint16_t ATM90E32_REGISTER_QMEANB = 0xB6; // B Mean Power (Q)
static const uint16_t ATM90E32_REGISTER_QMEANC = 0xB7; // C Mean Power (Q)
static const uint16_t ATM90E32_REGISTER_SMEANT = 0xB8; // Total Mean Power (S)
static const uint16_t ATM90E32_REGISTER_SMEAN = 0xB9; // Apparent Mean Power Base (S)
static const uint16_t ATM90E32_REGISTER_SMEANA = 0xB9; // A Mean Power (S)
static const uint16_t ATM90E32_REGISTER_SMEANB = 0xBA; // B Mean Power (S)
static const uint16_t ATM90E32_REGISTER_SMEANC = 0xBB; // C Mean Power (S)
@@ -207,7 +206,6 @@ static const uint16_t ATM90E32_REGISTER_QMEANALSB = 0xC5; // Lower Word (A Rea
static const uint16_t ATM90E32_REGISTER_QMEANBLSB = 0xC6; // Lower Word (B React. Power)
static const uint16_t ATM90E32_REGISTER_QMEANCLSB = 0xC7; // Lower Word (C React. Power)
static const uint16_t ATM90E32_REGISTER_SAMEANTLSB = 0xC8; // Lower Word (Tot. App. Power)
static const uint16_t ATM90E32_REGISTER_SMEANLSB = 0xC9; // Lower Word Reg Base (Apparent Power)
static const uint16_t ATM90E32_REGISTER_SMEANALSB = 0xC9; // Lower Word (A App. Power)
static const uint16_t ATM90E32_REGISTER_SMEANBLSB = 0xCA; // Lower Word (B App. Power)
static const uint16_t ATM90E32_REGISTER_SMEANCLSB = 0xCB; // Lower Word (C App. Power)

View File

@@ -1,95 +1,43 @@
import esphome.codegen as cg
from esphome.components import button
import esphome.config_validation as cv
from esphome.const import CONF_ID, ENTITY_CATEGORY_CONFIG, ICON_SCALE
from esphome.const import CONF_ID, ENTITY_CATEGORY_CONFIG, ICON_CHIP, ICON_SCALE
from .. import atm90e32_ns
from ..sensor import ATM90E32Component
CONF_RUN_GAIN_CALIBRATION = "run_gain_calibration"
CONF_CLEAR_GAIN_CALIBRATION = "clear_gain_calibration"
CONF_RUN_OFFSET_CALIBRATION = "run_offset_calibration"
CONF_CLEAR_OFFSET_CALIBRATION = "clear_offset_calibration"
CONF_RUN_POWER_OFFSET_CALIBRATION = "run_power_offset_calibration"
CONF_CLEAR_POWER_OFFSET_CALIBRATION = "clear_power_offset_calibration"
ATM90E32GainCalibrationButton = atm90e32_ns.class_(
"ATM90E32GainCalibrationButton", button.Button
ATM90E32CalibrationButton = atm90e32_ns.class_(
"ATM90E32CalibrationButton",
button.Button,
)
ATM90E32ClearGainCalibrationButton = atm90e32_ns.class_(
"ATM90E32ClearGainCalibrationButton", button.Button
)
ATM90E32OffsetCalibrationButton = atm90e32_ns.class_(
"ATM90E32OffsetCalibrationButton", button.Button
)
ATM90E32ClearOffsetCalibrationButton = atm90e32_ns.class_(
"ATM90E32ClearOffsetCalibrationButton", button.Button
)
ATM90E32PowerOffsetCalibrationButton = atm90e32_ns.class_(
"ATM90E32PowerOffsetCalibrationButton", button.Button
)
ATM90E32ClearPowerOffsetCalibrationButton = atm90e32_ns.class_(
"ATM90E32ClearPowerOffsetCalibrationButton", button.Button
ATM90E32ClearCalibrationButton = atm90e32_ns.class_(
"ATM90E32ClearCalibrationButton",
button.Button,
)
CONFIG_SCHEMA = {
cv.GenerateID(CONF_ID): cv.use_id(ATM90E32Component),
cv.Optional(CONF_RUN_GAIN_CALIBRATION): button.button_schema(
ATM90E32GainCalibrationButton,
entity_category=ENTITY_CATEGORY_CONFIG,
icon="mdi:scale-balance",
),
cv.Optional(CONF_CLEAR_GAIN_CALIBRATION): button.button_schema(
ATM90E32ClearGainCalibrationButton,
entity_category=ENTITY_CATEGORY_CONFIG,
icon="mdi:delete",
),
cv.Optional(CONF_RUN_OFFSET_CALIBRATION): button.button_schema(
ATM90E32OffsetCalibrationButton,
ATM90E32CalibrationButton,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_SCALE,
),
cv.Optional(CONF_CLEAR_OFFSET_CALIBRATION): button.button_schema(
ATM90E32ClearOffsetCalibrationButton,
ATM90E32ClearCalibrationButton,
entity_category=ENTITY_CATEGORY_CONFIG,
icon="mdi:delete",
),
cv.Optional(CONF_RUN_POWER_OFFSET_CALIBRATION): button.button_schema(
ATM90E32PowerOffsetCalibrationButton,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_SCALE,
),
cv.Optional(CONF_CLEAR_POWER_OFFSET_CALIBRATION): button.button_schema(
ATM90E32ClearPowerOffsetCalibrationButton,
entity_category=ENTITY_CATEGORY_CONFIG,
icon="mdi:delete",
icon=ICON_CHIP,
),
}
async def to_code(config):
parent = await cg.get_variable(config[CONF_ID])
if run_gain := config.get(CONF_RUN_GAIN_CALIBRATION):
b = await button.new_button(run_gain)
await cg.register_parented(b, parent)
if clear_gain := config.get(CONF_CLEAR_GAIN_CALIBRATION):
b = await button.new_button(clear_gain)
await cg.register_parented(b, parent)
if run_offset := config.get(CONF_RUN_OFFSET_CALIBRATION):
b = await button.new_button(run_offset)
await cg.register_parented(b, parent)
if clear_offset := config.get(CONF_CLEAR_OFFSET_CALIBRATION):
b = await button.new_button(clear_offset)
await cg.register_parented(b, parent)
if run_power := config.get(CONF_RUN_POWER_OFFSET_CALIBRATION):
b = await button.new_button(run_power)
await cg.register_parented(b, parent)
if clear_power := config.get(CONF_CLEAR_POWER_OFFSET_CALIBRATION):
b = await button.new_button(clear_power)
await cg.register_parented(b, parent)

View File

@@ -1,5 +1,4 @@
#include "atm90e32_button.h"
#include "esphome/core/component.h"
#include "esphome/core/log.h"
namespace esphome {
@@ -7,73 +6,15 @@ namespace atm90e32 {
static const char *const TAG = "atm90e32.button";
void ATM90E32GainCalibrationButton::press_action() {
if (this->parent_ == nullptr) {
ESP_LOGW(TAG, "[CALIBRATION] No meters assigned to Gain Calibration button [%s]", this->get_name().c_str());
return;
}
ESP_LOGI(TAG, "%s", this->get_name().c_str());
ESP_LOGI(TAG,
"[CALIBRATION] Use gain_ct: & gain_voltage: under each phase_x: in your config file to save these values");
this->parent_->run_gain_calibrations();
}
void ATM90E32ClearGainCalibrationButton::press_action() {
if (this->parent_ == nullptr) {
ESP_LOGW(TAG, "[CALIBRATION] No meters assigned to Clear Gain button [%s]", this->get_name().c_str());
return;
}
ESP_LOGI(TAG, "%s", this->get_name().c_str());
this->parent_->clear_gain_calibrations();
}
void ATM90E32OffsetCalibrationButton::press_action() {
if (this->parent_ == nullptr) {
ESP_LOGW(TAG, "[CALIBRATION] No meters assigned to Offset Calibration button [%s]", this->get_name().c_str());
return;
}
ESP_LOGI(TAG, "%s", this->get_name().c_str());
ESP_LOGI(TAG, "[CALIBRATION] **NOTE: CTs and ACVs must be 0 during this process. USB power only**");
ESP_LOGI(TAG, "[CALIBRATION] Use offset_voltage: & offset_current: under each phase_x: in your config file to save "
"these values");
void ATM90E32CalibrationButton::press_action() {
ESP_LOGI(TAG, "Running offset calibrations, Note: CTs and ACVs must be 0 during this process...");
this->parent_->run_offset_calibrations();
}
void ATM90E32ClearOffsetCalibrationButton::press_action() {
if (this->parent_ == nullptr) {
ESP_LOGW(TAG, "[CALIBRATION] No meters assigned to Clear Offset button [%s]", this->get_name().c_str());
return;
}
ESP_LOGI(TAG, "%s", this->get_name().c_str());
void ATM90E32ClearCalibrationButton::press_action() {
ESP_LOGI(TAG, "Offset calibrations cleared.");
this->parent_->clear_offset_calibrations();
}
void ATM90E32PowerOffsetCalibrationButton::press_action() {
if (this->parent_ == nullptr) {
ESP_LOGW(TAG, "[CALIBRATION] No meters assigned to Power Calibration button [%s]", this->get_name().c_str());
return;
}
ESP_LOGI(TAG, "%s", this->get_name().c_str());
ESP_LOGI(TAG, "[CALIBRATION] **NOTE: CTs must be 0 during this process. Voltage reference should be present**");
ESP_LOGI(TAG, "[CALIBRATION] Use offset_active_power: & offset_reactive_power: under each phase_x: in your config "
"file to save these values");
this->parent_->run_power_offset_calibrations();
}
void ATM90E32ClearPowerOffsetCalibrationButton::press_action() {
if (this->parent_ == nullptr) {
ESP_LOGW(TAG, "[CALIBRATION] No meters assigned to Clear Power button [%s]", this->get_name().c_str());
return;
}
ESP_LOGI(TAG, "%s", this->get_name().c_str());
this->parent_->clear_power_offset_calibrations();
}
} // namespace atm90e32
} // namespace esphome

View File

@@ -7,49 +7,17 @@
namespace esphome {
namespace atm90e32 {
class ATM90E32GainCalibrationButton : public button::Button, public Parented<ATM90E32Component> {
class ATM90E32CalibrationButton : public button::Button, public Parented<ATM90E32Component> {
public:
ATM90E32GainCalibrationButton() = default;
ATM90E32CalibrationButton() = default;
protected:
void press_action() override;
};
class ATM90E32ClearGainCalibrationButton : public button::Button, public Parented<ATM90E32Component> {
class ATM90E32ClearCalibrationButton : public button::Button, public Parented<ATM90E32Component> {
public:
ATM90E32ClearGainCalibrationButton() = default;
protected:
void press_action() override;
};
class ATM90E32OffsetCalibrationButton : public button::Button, public Parented<ATM90E32Component> {
public:
ATM90E32OffsetCalibrationButton() = default;
protected:
void press_action() override;
};
class ATM90E32ClearOffsetCalibrationButton : public button::Button, public Parented<ATM90E32Component> {
public:
ATM90E32ClearOffsetCalibrationButton() = default;
protected:
void press_action() override;
};
class ATM90E32PowerOffsetCalibrationButton : public button::Button, public Parented<ATM90E32Component> {
public:
ATM90E32PowerOffsetCalibrationButton() = default;
protected:
void press_action() override;
};
class ATM90E32ClearPowerOffsetCalibrationButton : public button::Button, public Parented<ATM90E32Component> {
public:
ATM90E32ClearPowerOffsetCalibrationButton() = default;
ATM90E32ClearCalibrationButton() = default;
protected:
void press_action() override;

View File

@@ -1,130 +0,0 @@
import esphome.codegen as cg
from esphome.components import number
import esphome.config_validation as cv
from esphome.const import (
CONF_ID,
CONF_MAX_VALUE,
CONF_MIN_VALUE,
CONF_MODE,
CONF_PHASE_A,
CONF_PHASE_B,
CONF_PHASE_C,
CONF_REFERENCE_VOLTAGE,
CONF_STEP,
ENTITY_CATEGORY_CONFIG,
UNIT_AMPERE,
UNIT_VOLT,
)
from .. import atm90e32_ns
from ..sensor import ATM90E32Component
ATM90E32Number = atm90e32_ns.class_(
"ATM90E32Number", number.Number, cg.Parented.template(ATM90E32Component)
)
CONF_REFERENCE_CURRENT = "reference_current"
PHASE_KEYS = [CONF_PHASE_A, CONF_PHASE_B, CONF_PHASE_C]
REFERENCE_VOLTAGE_PHASE_SCHEMA = cv.All(
cv.Schema(
{
cv.Optional(CONF_MODE, default="box"): cv.string,
cv.Optional(CONF_MIN_VALUE, default=100.0): cv.float_,
cv.Optional(CONF_MAX_VALUE, default=260.0): cv.float_,
cv.Optional(CONF_STEP, default=0.1): cv.float_,
}
).extend(
number.number_schema(
class_=ATM90E32Number,
unit_of_measurement=UNIT_VOLT,
entity_category=ENTITY_CATEGORY_CONFIG,
icon="mdi:power-plug",
)
)
)
REFERENCE_CURRENT_PHASE_SCHEMA = cv.All(
cv.Schema(
{
cv.Optional(CONF_MODE, default="box"): cv.string,
cv.Optional(CONF_MIN_VALUE, default=1.0): cv.float_,
cv.Optional(CONF_MAX_VALUE, default=200.0): cv.float_,
cv.Optional(CONF_STEP, default=0.1): cv.float_,
}
).extend(
number.number_schema(
class_=ATM90E32Number,
unit_of_measurement=UNIT_AMPERE,
entity_category=ENTITY_CATEGORY_CONFIG,
icon="mdi:home-lightning-bolt",
)
)
)
REFERENCE_VOLTAGE_SCHEMA = cv.Schema(
{
cv.Optional(CONF_PHASE_A): REFERENCE_VOLTAGE_PHASE_SCHEMA,
cv.Optional(CONF_PHASE_B): REFERENCE_VOLTAGE_PHASE_SCHEMA,
cv.Optional(CONF_PHASE_C): REFERENCE_VOLTAGE_PHASE_SCHEMA,
}
)
REFERENCE_CURRENT_SCHEMA = cv.Schema(
{
cv.Optional(CONF_PHASE_A): REFERENCE_CURRENT_PHASE_SCHEMA,
cv.Optional(CONF_PHASE_B): REFERENCE_CURRENT_PHASE_SCHEMA,
cv.Optional(CONF_PHASE_C): REFERENCE_CURRENT_PHASE_SCHEMA,
}
)
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(CONF_ID): cv.use_id(ATM90E32Component),
cv.Optional(CONF_REFERENCE_VOLTAGE): REFERENCE_VOLTAGE_SCHEMA,
cv.Optional(CONF_REFERENCE_CURRENT): REFERENCE_CURRENT_SCHEMA,
}
)
async def to_code(config):
parent = await cg.get_variable(config[CONF_ID])
if voltage_cfg := config.get(CONF_REFERENCE_VOLTAGE):
voltage_objs = [None, None, None]
for i, key in enumerate(PHASE_KEYS):
if validated := voltage_cfg.get(key):
obj = await number.new_number(
validated,
min_value=validated["min_value"],
max_value=validated["max_value"],
step=validated["step"],
)
await cg.register_parented(obj, parent)
voltage_objs[i] = obj
# Inherit from A → B/C if only A defined
if voltage_objs[0] is not None:
for i in range(3):
if voltage_objs[i] is None:
voltage_objs[i] = voltage_objs[0]
for i, obj in enumerate(voltage_objs):
if obj is not None:
cg.add(parent.set_reference_voltage(i, obj))
if current_cfg := config.get(CONF_REFERENCE_CURRENT):
for i, key in enumerate(PHASE_KEYS):
if validated := current_cfg.get(key):
obj = await number.new_number(
validated,
min_value=validated["min_value"],
max_value=validated["max_value"],
step=validated["step"],
)
await cg.register_parented(obj, parent)
cg.add(parent.set_reference_current(i, obj))

View File

@@ -1,16 +0,0 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/atm90e32/atm90e32.h"
#include "esphome/components/number/number.h"
namespace esphome {
namespace atm90e32 {
class ATM90E32Number : public number::Number, public Parented<ATM90E32Component> {
public:
void control(float value) override { this->publish_state(value); }
};
} // namespace atm90e32
} // namespace esphome

View File

@@ -33,7 +33,6 @@ from esphome.const import (
UNIT_DEGREES,
UNIT_HERTZ,
UNIT_VOLT,
UNIT_VOLT_AMPS,
UNIT_VOLT_AMPS_REACTIVE,
UNIT_WATT,
UNIT_WATT_HOURS,
@@ -46,17 +45,10 @@ CONF_GAIN_PGA = "gain_pga"
CONF_CURRENT_PHASES = "current_phases"
CONF_GAIN_VOLTAGE = "gain_voltage"
CONF_GAIN_CT = "gain_ct"
CONF_OFFSET_VOLTAGE = "offset_voltage"
CONF_OFFSET_CURRENT = "offset_current"
CONF_OFFSET_ACTIVE_POWER = "offset_active_power"
CONF_OFFSET_REACTIVE_POWER = "offset_reactive_power"
CONF_HARMONIC_POWER = "harmonic_power"
CONF_PEAK_CURRENT = "peak_current"
CONF_PEAK_CURRENT_SIGNED = "peak_current_signed"
CONF_ENABLE_OFFSET_CALIBRATION = "enable_offset_calibration"
CONF_ENABLE_GAIN_CALIBRATION = "enable_gain_calibration"
CONF_PHASE_STATUS = "phase_status"
CONF_FREQUENCY_STATUS = "frequency_status"
UNIT_DEG = "degrees"
LINE_FREQS = {
"50HZ": 50,
@@ -100,11 +92,10 @@ ATM90E32_PHASE_SCHEMA = cv.Schema(
unit_of_measurement=UNIT_VOLT_AMPS_REACTIVE,
icon=ICON_LIGHTBULB,
accuracy_decimals=2,
device_class=DEVICE_CLASS_POWER,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_APPARENT_POWER): sensor.sensor_schema(
unit_of_measurement=UNIT_VOLT_AMPS,
unit_of_measurement=UNIT_WATT,
accuracy_decimals=2,
device_class=DEVICE_CLASS_POWER,
state_class=STATE_CLASS_MEASUREMENT,
@@ -146,10 +137,6 @@ ATM90E32_PHASE_SCHEMA = cv.Schema(
),
cv.Optional(CONF_GAIN_VOLTAGE, default=7305): cv.uint16_t,
cv.Optional(CONF_GAIN_CT, default=27961): cv.uint16_t,
cv.Optional(CONF_OFFSET_VOLTAGE, default=0): cv.int_,
cv.Optional(CONF_OFFSET_CURRENT, default=0): cv.int_,
cv.Optional(CONF_OFFSET_ACTIVE_POWER, default=0): cv.int_,
cv.Optional(CONF_OFFSET_REACTIVE_POWER, default=0): cv.int_,
}
)
@@ -177,10 +164,9 @@ CONFIG_SCHEMA = (
cv.Optional(CONF_CURRENT_PHASES, default="3"): cv.enum(
CURRENT_PHASES, upper=True
),
cv.Optional(CONF_GAIN_PGA, default="1X"): cv.enum(PGA_GAINS, upper=True),
cv.Optional(CONF_GAIN_PGA, default="2X"): cv.enum(PGA_GAINS, upper=True),
cv.Optional(CONF_PEAK_CURRENT_SIGNED, default=False): cv.boolean,
cv.Optional(CONF_ENABLE_OFFSET_CALIBRATION, default=False): cv.boolean,
cv.Optional(CONF_ENABLE_GAIN_CALIBRATION, default=False): cv.boolean,
}
)
.extend(cv.polling_component_schema("60s"))
@@ -199,10 +185,6 @@ async def to_code(config):
conf = config[phase]
cg.add(var.set_volt_gain(i, conf[CONF_GAIN_VOLTAGE]))
cg.add(var.set_ct_gain(i, conf[CONF_GAIN_CT]))
cg.add(var.set_voltage_offset(i, conf[CONF_OFFSET_VOLTAGE]))
cg.add(var.set_current_offset(i, conf[CONF_OFFSET_CURRENT]))
cg.add(var.set_active_power_offset(i, conf[CONF_OFFSET_ACTIVE_POWER]))
cg.add(var.set_reactive_power_offset(i, conf[CONF_OFFSET_REACTIVE_POWER]))
if voltage_config := conf.get(CONF_VOLTAGE):
sens = await sensor.new_sensor(voltage_config)
cg.add(var.set_voltage_sensor(i, sens))
@@ -236,15 +218,16 @@ async def to_code(config):
if peak_current_config := conf.get(CONF_PEAK_CURRENT):
sens = await sensor.new_sensor(peak_current_config)
cg.add(var.set_peak_current_sensor(i, sens))
if frequency_config := config.get(CONF_FREQUENCY):
sens = await sensor.new_sensor(frequency_config)
cg.add(var.set_freq_sensor(sens))
if chip_temperature_config := config.get(CONF_CHIP_TEMPERATURE):
sens = await sensor.new_sensor(chip_temperature_config)
cg.add(var.set_chip_temperature_sensor(sens))
cg.add(var.set_line_freq(config[CONF_LINE_FREQUENCY]))
cg.add(var.set_current_phases(config[CONF_CURRENT_PHASES]))
cg.add(var.set_pga_gain(config[CONF_GAIN_PGA]))
cg.add(var.set_peak_current_signed(config[CONF_PEAK_CURRENT_SIGNED]))
cg.add(var.set_enable_offset_calibration(config[CONF_ENABLE_OFFSET_CALIBRATION]))
cg.add(var.set_enable_gain_calibration(config[CONF_ENABLE_GAIN_CALIBRATION]))

View File

@@ -1,48 +0,0 @@
import esphome.codegen as cg
from esphome.components import text_sensor
import esphome.config_validation as cv
from esphome.const import CONF_ID, CONF_PHASE_A, CONF_PHASE_B, CONF_PHASE_C
from ..sensor import ATM90E32Component
CONF_PHASE_STATUS = "phase_status"
CONF_FREQUENCY_STATUS = "frequency_status"
PHASE_KEYS = [CONF_PHASE_A, CONF_PHASE_B, CONF_PHASE_C]
PHASE_STATUS_SCHEMA = cv.Schema(
{
cv.Optional(CONF_PHASE_A): text_sensor.text_sensor_schema(
icon="mdi:flash-alert"
),
cv.Optional(CONF_PHASE_B): text_sensor.text_sensor_schema(
icon="mdi:flash-alert"
),
cv.Optional(CONF_PHASE_C): text_sensor.text_sensor_schema(
icon="mdi:flash-alert"
),
}
)
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(): cv.use_id(ATM90E32Component),
cv.Optional(CONF_PHASE_STATUS): PHASE_STATUS_SCHEMA,
cv.Optional(CONF_FREQUENCY_STATUS): text_sensor.text_sensor_schema(
icon="mdi:lightbulb-alert"
),
}
)
async def to_code(config):
parent = await cg.get_variable(config[CONF_ID])
if phase_cfg := config.get(CONF_PHASE_STATUS):
for i, key in enumerate(PHASE_KEYS):
if sub_phase_cfg := phase_cfg.get(key):
sens = await text_sensor.new_text_sensor(sub_phase_cfg)
cg.add(parent.set_phase_status_text_sensor(i, sens))
if freq_status_config := config.get(CONF_FREQUENCY_STATUS):
sens = await text_sensor.new_text_sensor(freq_status_config)
cg.add(parent.set_freq_status_text_sensor(sens))

View File

@@ -37,13 +37,16 @@ AUDIO_COMPONENT_SCHEMA = cv.Schema(
)
_UNDEF = object()
def set_stream_limits(
min_bits_per_sample: int = cv.UNDEFINED,
max_bits_per_sample: int = cv.UNDEFINED,
min_channels: int = cv.UNDEFINED,
max_channels: int = cv.UNDEFINED,
min_sample_rate: int = cv.UNDEFINED,
max_sample_rate: int = cv.UNDEFINED,
min_bits_per_sample: int = _UNDEF,
max_bits_per_sample: int = _UNDEF,
min_channels: int = _UNDEF,
max_channels: int = _UNDEF,
min_sample_rate: int = _UNDEF,
max_sample_rate: int = _UNDEF,
):
"""Sets the limits for the audio stream that audio component can handle
@@ -52,17 +55,17 @@ def set_stream_limits(
"""
def set_limits_in_config(config):
if min_bits_per_sample is not cv.UNDEFINED:
if min_bits_per_sample is not _UNDEF:
config[CONF_MIN_BITS_PER_SAMPLE] = min_bits_per_sample
if max_bits_per_sample is not cv.UNDEFINED:
if max_bits_per_sample is not _UNDEF:
config[CONF_MAX_BITS_PER_SAMPLE] = max_bits_per_sample
if min_channels is not cv.UNDEFINED:
if min_channels is not _UNDEF:
config[CONF_MIN_CHANNELS] = min_channels
if max_channels is not cv.UNDEFINED:
if max_channels is not _UNDEF:
config[CONF_MAX_CHANNELS] = max_channels
if min_sample_rate is not cv.UNDEFINED:
if min_sample_rate is not _UNDEF:
config[CONF_MIN_SAMPLE_RATE] = min_sample_rate
if max_sample_rate is not cv.UNDEFINED:
if max_sample_rate is not _UNDEF:
config[CONF_MAX_SAMPLE_RATE] = max_sample_rate
return set_limits_in_config
@@ -72,10 +75,10 @@ def final_validate_audio_schema(
name: str,
*,
audio_device: str,
bits_per_sample: int = cv.UNDEFINED,
channels: int = cv.UNDEFINED,
sample_rate: int = cv.UNDEFINED,
enabled_channels: list[int] = cv.UNDEFINED,
bits_per_sample: int = _UNDEF,
channels: int = _UNDEF,
sample_rate: int = _UNDEF,
enabled_channels: list[int] = _UNDEF,
audio_device_issue: bool = False,
):
"""Validates audio compatibility when passed between different components.
@@ -98,7 +101,7 @@ def final_validate_audio_schema(
def validate_audio_compatiblity(audio_config):
audio_schema = {}
if bits_per_sample is not cv.UNDEFINED:
if bits_per_sample is not _UNDEF:
try:
cv.int_range(
min=audio_config.get(CONF_MIN_BITS_PER_SAMPLE),
@@ -111,7 +114,7 @@ def final_validate_audio_schema(
error_string = f"Invalid configuration for the {name} component. The {CONF_BITS_PER_SAMPLE} {str(exc)}"
raise cv.Invalid(error_string) from exc
if channels is not cv.UNDEFINED:
if channels is not _UNDEF:
try:
cv.int_range(
min=audio_config.get(CONF_MIN_CHANNELS),
@@ -124,7 +127,7 @@ def final_validate_audio_schema(
error_string = f"Invalid configuration for the {name} component. The {CONF_NUM_CHANNELS} {str(exc)}"
raise cv.Invalid(error_string) from exc
if sample_rate is not cv.UNDEFINED:
if sample_rate is not _UNDEF:
try:
cv.int_range(
min=audio_config.get(CONF_MIN_SAMPLE_RATE),
@@ -137,7 +140,7 @@ def final_validate_audio_schema(
error_string = f"Invalid configuration for the {name} component. The {CONF_SAMPLE_RATE} {str(exc)}"
raise cv.Invalid(error_string) from exc
if enabled_channels is not cv.UNDEFINED:
if enabled_channels is not _UNDEF:
for channel in enabled_channels:
try:
# Channels are 0-indexed
@@ -165,4 +168,4 @@ def final_validate_audio_schema(
async def to_code(config):
cg.add_library("esphome/esp-audio-libs", "1.1.4")
cg.add_library("esphome/esp-audio-libs", "1.1.3")

View File

@@ -135,7 +135,7 @@ const char *audio_file_type_to_string(AudioFileType file_type);
void scale_audio_samples(const int16_t *audio_samples, int16_t *output_buffer, int16_t scale_factor,
size_t samples_to_scale);
/// @brief Unpacks a quantized audio sample into a Q31 fixed-point number.
/// @brief Unpacks a quantized audio sample into a Q31 fixed point number.
/// @param data Pointer to uint8_t array containing the audio sample
/// @param bytes_per_sample The number of bytes per sample
/// @return Q31 sample
@@ -160,28 +160,5 @@ inline int32_t unpack_audio_sample_to_q31(const uint8_t *data, size_t bytes_per_
return sample;
}
/// @brief Packs a Q31 fixed-point number as an audio sample with the specified number of bytes per sample.
/// Packs the most significant bits - no dithering is applied.
/// @param sample Q31 fixed-point number to pack
/// @param data Pointer to data array to store
/// @param bytes_per_sample The audio data's bytes per sample
inline void pack_q31_as_audio_sample(int32_t sample, uint8_t *data, size_t bytes_per_sample) {
if (bytes_per_sample == 1) {
data[0] = static_cast<uint8_t>(sample >> 24);
} else if (bytes_per_sample == 2) {
data[0] = static_cast<uint8_t>(sample >> 16);
data[1] = static_cast<uint8_t>(sample >> 24);
} else if (bytes_per_sample == 3) {
data[0] = static_cast<uint8_t>(sample >> 8);
data[1] = static_cast<uint8_t>(sample >> 16);
data[2] = static_cast<uint8_t>(sample >> 24);
} else if (bytes_per_sample == 4) {
data[0] = static_cast<uint8_t>(sample);
data[1] = static_cast<uint8_t>(sample >> 8);
data[2] = static_cast<uint8_t>(sample >> 16);
data[3] = static_cast<uint8_t>(sample >> 24);
}
}
} // namespace audio
} // namespace esphome

View File

@@ -171,7 +171,7 @@ AudioDecoderState AudioDecoder::decode(bool stop_gracefully) {
bytes_available_before_processing = this->input_transfer_buffer_->available();
if ((this->potentially_failed_count_ > 0) && (bytes_read == 0)) {
if ((this->potentially_failed_count_ > 10) && (bytes_read == 0)) {
// Failed to decode in last attempt and there is no new data
if ((this->input_transfer_buffer_->free() == 0) && first_loop_iteration) {

View File

@@ -1,5 +1,7 @@
import esphome.codegen as cg
from esphome.components import climate_ir
import esphome.config_validation as cv
from esphome.const import CONF_ID
AUTO_LOAD = ["climate_ir"]
CODEOWNERS = ["@bazuchan"]
@@ -7,8 +9,13 @@ CODEOWNERS = ["@bazuchan"]
ballu_ns = cg.esphome_ns.namespace("ballu")
BalluClimate = ballu_ns.class_("BalluClimate", climate_ir.ClimateIR)
CONFIG_SCHEMA = climate_ir.climare_ir_with_receiver_schema(BalluClimate)
CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(BalluClimate),
}
)
async def to_code(config):
await climate_ir.new_climate_ir(config)
var = cg.new_Pvariable(config[CONF_ID])
await climate_ir.register_climate_ir(var, config)

View File

@@ -9,6 +9,7 @@ from esphome.const import (
CONF_DEFAULT_TARGET_TEMPERATURE_LOW,
CONF_HEAT_ACTION,
CONF_HUMIDITY_SENSOR,
CONF_ID,
CONF_IDLE_ACTION,
CONF_SENSOR,
)
@@ -18,9 +19,9 @@ BangBangClimate = bang_bang_ns.class_("BangBangClimate", climate.Climate, cg.Com
BangBangClimateTargetTempConfig = bang_bang_ns.struct("BangBangClimateTargetTempConfig")
CONFIG_SCHEMA = cv.All(
climate.climate_schema(BangBangClimate)
.extend(
climate.CLIMATE_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(BangBangClimate),
cv.Required(CONF_SENSOR): cv.use_id(sensor.Sensor),
cv.Optional(CONF_HUMIDITY_SENSOR): cv.use_id(sensor.Sensor),
cv.Required(CONF_DEFAULT_TARGET_TEMPERATURE_LOW): cv.temperature,
@@ -35,15 +36,15 @@ CONFIG_SCHEMA = cv.All(
}
),
}
)
.extend(cv.COMPONENT_SCHEMA),
).extend(cv.COMPONENT_SCHEMA),
cv.has_at_least_one_key(CONF_COOL_ACTION, CONF_HEAT_ACTION),
)
async def to_code(config):
var = await climate.new_climate(config)
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await climate.register_climate(var, config)
sens = await cg.get_variable(config[CONF_SENSOR])
cg.add(var.set_sensor(sens))

View File

@@ -1,8 +1,11 @@
import logging
import esphome.codegen as cg
from esphome.components import ble_client, climate
import esphome.config_validation as cv
from esphome.const import (
CONF_HEAT_MODE,
CONF_ID,
CONF_RECEIVE_TIMEOUT,
CONF_TEMPERATURE_SOURCE,
CONF_TIME_ID,
@@ -10,6 +13,7 @@ from esphome.const import (
from .. import BEDJET_CLIENT_SCHEMA, bedjet_ns, register_bedjet_child
_LOGGER = logging.getLogger(__name__)
CODEOWNERS = ["@jhansche"]
DEPENDENCIES = ["bedjet"]
@@ -26,9 +30,9 @@ BEDJET_TEMPERATURE_SOURCES = {
}
CONFIG_SCHEMA = (
climate.climate_schema(BedJetClimate)
.extend(
climate.CLIMATE_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(BedJetClimate),
cv.Optional(CONF_HEAT_MODE, default="heat"): cv.enum(
BEDJET_HEAT_MODES, lower=True
),
@@ -59,8 +63,9 @@ CONFIG_SCHEMA = (
async def to_code(config):
var = await climate.new_climate(config)
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await climate.register_climate(var, config)
await register_bedjet_child(var, config)
cg.add(var.set_heating_mode(config[CONF_HEAT_MODE]))

View File

@@ -1,22 +1,31 @@
import logging
import esphome.codegen as cg
from esphome.components import fan
import esphome.config_validation as cv
from esphome.const import CONF_ID
from .. import BEDJET_CLIENT_SCHEMA, bedjet_ns, register_bedjet_child
_LOGGER = logging.getLogger(__name__)
CODEOWNERS = ["@jhansche"]
DEPENDENCIES = ["bedjet"]
BedJetFan = bedjet_ns.class_("BedJetFan", fan.Fan, cg.PollingComponent)
CONFIG_SCHEMA = (
fan.fan_schema(BedJetFan)
fan.FAN_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(BedJetFan),
}
)
.extend(cv.polling_component_schema("60s"))
.extend(BEDJET_CLIENT_SCHEMA)
)
async def to_code(config):
var = await fan.new_fan(config)
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await fan.register_fan(var, config)
await register_bedjet_child(var, config)

View File

@@ -1,28 +1,31 @@
import esphome.codegen as cg
from esphome.components import fan, output
import esphome.config_validation as cv
from esphome.const import CONF_DIRECTION_OUTPUT, CONF_OSCILLATION_OUTPUT, CONF_OUTPUT
from esphome.const import (
CONF_DIRECTION_OUTPUT,
CONF_OSCILLATION_OUTPUT,
CONF_OUTPUT,
CONF_OUTPUT_ID,
)
from .. import binary_ns
BinaryFan = binary_ns.class_("BinaryFan", fan.Fan, cg.Component)
CONFIG_SCHEMA = (
fan.fan_schema(BinaryFan)
.extend(
{
cv.Required(CONF_OUTPUT): cv.use_id(output.BinaryOutput),
cv.Optional(CONF_DIRECTION_OUTPUT): cv.use_id(output.BinaryOutput),
cv.Optional(CONF_OSCILLATION_OUTPUT): cv.use_id(output.BinaryOutput),
}
)
.extend(cv.COMPONENT_SCHEMA)
)
CONFIG_SCHEMA = fan.FAN_SCHEMA.extend(
{
cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(BinaryFan),
cv.Required(CONF_OUTPUT): cv.use_id(output.BinaryOutput),
cv.Optional(CONF_DIRECTION_OUTPUT): cv.use_id(output.BinaryOutput),
cv.Optional(CONF_OSCILLATION_OUTPUT): cv.use_id(output.BinaryOutput),
}
).extend(cv.COMPONENT_SCHEMA)
async def to_code(config):
var = await fan.new_fan(config)
var = cg.new_Pvariable(config[CONF_OUTPUT_ID])
await cg.register_component(var, config)
await fan.register_fan(var, config)
output_ = await cg.get_variable(config[CONF_OUTPUT])
cg.add(var.set_output(output_))

View File

@@ -386,7 +386,7 @@ def validate_click_timing(value):
return value
_BINARY_SENSOR_SCHEMA = (
BINARY_SENSOR_SCHEMA = (
cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA)
.extend(cv.MQTT_COMPONENT_SCHEMA)
.extend(
@@ -458,17 +458,19 @@ _BINARY_SENSOR_SCHEMA = (
)
)
_UNDEF = object()
def binary_sensor_schema(
class_: MockObjClass = cv.UNDEFINED,
class_: MockObjClass = _UNDEF,
*,
icon: str = cv.UNDEFINED,
entity_category: str = cv.UNDEFINED,
device_class: str = cv.UNDEFINED,
icon: str = _UNDEF,
entity_category: str = _UNDEF,
device_class: str = _UNDEF,
) -> cv.Schema:
schema = {}
if class_ is not cv.UNDEFINED:
if class_ is not _UNDEF:
# Not cv.optional
schema[cv.GenerateID()] = cv.declare_id(class_)
@@ -477,15 +479,10 @@ def binary_sensor_schema(
(CONF_ENTITY_CATEGORY, entity_category, cv.entity_category),
(CONF_DEVICE_CLASS, device_class, validate_device_class),
]:
if default is not cv.UNDEFINED:
if default is not _UNDEF:
schema[cv.Optional(key, default=default)] = validator
return _BINARY_SENSOR_SCHEMA.extend(schema)
# Remove before 2025.11.0
BINARY_SENSOR_SCHEMA = binary_sensor_schema()
BINARY_SENSOR_SCHEMA.add_extra(cv.deprecated_schema_constant("binary_sensor"))
return BINARY_SENSOR_SCHEMA.extend(schema)
async def setup_binary_sensor_core_(var, config):

View File

@@ -4,6 +4,7 @@ from esphome.components import ble_client, esp32_ble_tracker, text_sensor
import esphome.config_validation as cv
from esphome.const import (
CONF_CHARACTERISTIC_UUID,
CONF_ID,
CONF_NOTIFY,
CONF_SERVICE_UUID,
CONF_TRIGGER_ID,
@@ -31,9 +32,9 @@ BLETextSensorNotifyTrigger = ble_client_ns.class_(
)
CONFIG_SCHEMA = cv.All(
text_sensor.text_sensor_schema(BLETextSensor)
.extend(
text_sensor.TEXT_SENSOR_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(BLETextSensor),
cv.Required(CONF_SERVICE_UUID): esp32_ble_tracker.bt_uuid,
cv.Required(CONF_CHARACTERISTIC_UUID): esp32_ble_tracker.bt_uuid,
cv.Optional(CONF_DESCRIPTOR_UUID): esp32_ble_tracker.bt_uuid,
@@ -53,7 +54,7 @@ CONFIG_SCHEMA = cv.All(
async def to_code(config):
var = await text_sensor.new_text_sensor(config)
var = cg.new_Pvariable(config[CONF_ID])
if len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid16_format):
cg.add(
var.set_service_uuid16(esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID]))
@@ -100,6 +101,7 @@ async def to_code(config):
await cg.register_component(var, config)
await ble_client.register_ble_node(var, config)
cg.add(var.set_enable_notify(config[CONF_NOTIFY]))
await text_sensor.register_text_sensor(var, config)
for conf in config.get(CONF_ON_NOTIFY, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await ble_client.register_ble_node(trigger, config)

View File

@@ -73,8 +73,9 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga
resp.address = this->address_;
resp.handle = param->read.handle;
resp.data.reserve(param->read.value_len);
// Use bulk insert instead of individual push_backs
resp.data.insert(resp.data.end(), param->read.value, param->read.value + param->read.value_len);
for (uint16_t i = 0; i < param->read.value_len; i++) {
resp.data.push_back(param->read.value[i]);
}
this->proxy_->get_api_connection()->send_bluetooth_gatt_read_response(resp);
break;
}
@@ -126,8 +127,9 @@ bool BluetoothConnection::gattc_event_handler(esp_gattc_cb_event_t event, esp_ga
resp.address = this->address_;
resp.handle = param->notify.handle;
resp.data.reserve(param->notify.value_len);
// Use bulk insert instead of individual push_backs
resp.data.insert(resp.data.end(), param->notify.value, param->notify.value + param->notify.value_len);
for (uint16_t i = 0; i < param->notify.value_len; i++) {
resp.data.push_back(param->notify.value[i]);
}
this->proxy_->get_api_connection()->send_bluetooth_gatt_notify_data_response(resp);
break;
}

View File

@@ -51,60 +51,33 @@ bool BluetoothProxy::parse_device(const esp32_ble_tracker::ESPBTDevice &device)
return true;
}
static constexpr size_t FLUSH_BATCH_SIZE = 8;
static std::vector<api::BluetoothLERawAdvertisement> &get_batch_buffer() {
static std::vector<api::BluetoothLERawAdvertisement> batch_buffer;
return batch_buffer;
}
bool BluetoothProxy::parse_devices(esp_ble_gap_cb_param_t::ble_scan_result_evt_param *advertisements, size_t count) {
if (!api::global_api_server->is_connected() || this->api_connection_ == nullptr || !this->raw_advertisements_)
return false;
// Get the batch buffer reference
auto &batch_buffer = get_batch_buffer();
// Reserve additional capacity if needed
size_t new_size = batch_buffer.size() + count;
if (batch_buffer.capacity() < new_size) {
batch_buffer.reserve(new_size);
}
// Add new advertisements to the batch buffer
api::BluetoothLERawAdvertisementsResponse resp;
for (size_t i = 0; i < count; i++) {
auto &result = advertisements[i];
uint8_t length = result.adv_data_len + result.scan_rsp_len;
batch_buffer.emplace_back();
auto &adv = batch_buffer.back();
api::BluetoothLERawAdvertisement adv;
adv.address = esp32_ble::ble_addr_to_uint64(result.bda);
adv.rssi = result.rssi;
adv.address_type = result.ble_addr_type;
adv.data.assign(&result.ble_adv[0], &result.ble_adv[length]);
ESP_LOGV(TAG, "Queuing raw packet from %02X:%02X:%02X:%02X:%02X:%02X, length %d. RSSI: %d dB", result.bda[0],
uint8_t length = result.adv_data_len + result.scan_rsp_len;
adv.data.reserve(length);
for (uint16_t i = 0; i < length; i++) {
adv.data.push_back(result.ble_adv[i]);
}
resp.advertisements.push_back(std::move(adv));
ESP_LOGV(TAG, "Proxying raw packet from %02X:%02X:%02X:%02X:%02X:%02X, length %d. RSSI: %d dB", result.bda[0],
result.bda[1], result.bda[2], result.bda[3], result.bda[4], result.bda[5], length, result.rssi);
}
// Only send if we've accumulated a good batch size to maximize batching efficiency
// https://github.com/esphome/backlog/issues/21
if (batch_buffer.size() >= FLUSH_BATCH_SIZE) {
this->flush_pending_advertisements();
}
ESP_LOGV(TAG, "Proxying %d packets", count);
this->api_connection_->send_bluetooth_le_raw_advertisements_response(resp);
return true;
}
void BluetoothProxy::flush_pending_advertisements() {
auto &batch_buffer = get_batch_buffer();
if (batch_buffer.empty() || !api::global_api_server->is_connected() || this->api_connection_ == nullptr)
return;
api::BluetoothLERawAdvertisementsResponse resp;
resp.advertisements.swap(batch_buffer);
this->api_connection_->send_bluetooth_le_raw_advertisements_response(resp);
}
void BluetoothProxy::send_api_packet_(const esp32_ble_tracker::ESPBTDevice &device) {
api::BluetoothLEAdvertisementResponse resp;
resp.address = device.address_uint64();
@@ -112,34 +85,21 @@ void BluetoothProxy::send_api_packet_(const esp32_ble_tracker::ESPBTDevice &devi
if (!device.get_name().empty())
resp.name = device.get_name();
resp.rssi = device.get_rssi();
// Pre-allocate vectors based on known sizes
auto service_uuids = device.get_service_uuids();
resp.service_uuids.reserve(service_uuids.size());
for (auto &uuid : service_uuids) {
resp.service_uuids.emplace_back(uuid.to_string());
for (auto uuid : device.get_service_uuids()) {
resp.service_uuids.push_back(uuid.to_string());
}
// Pre-allocate service data vector
auto service_datas = device.get_service_datas();
resp.service_data.reserve(service_datas.size());
for (auto &data : service_datas) {
resp.service_data.emplace_back();
auto &service_data = resp.service_data.back();
for (auto &data : device.get_service_datas()) {
api::BluetoothServiceData service_data;
service_data.uuid = data.uuid.to_string();
service_data.data.assign(data.data.begin(), data.data.end());
resp.service_data.push_back(std::move(service_data));
}
// Pre-allocate manufacturer data vector
auto manufacturer_datas = device.get_manufacturer_datas();
resp.manufacturer_data.reserve(manufacturer_datas.size());
for (auto &data : manufacturer_datas) {
resp.manufacturer_data.emplace_back();
auto &manufacturer_data = resp.manufacturer_data.back();
for (auto &data : device.get_manufacturer_datas()) {
api::BluetoothServiceData manufacturer_data;
manufacturer_data.uuid = data.uuid.to_string();
manufacturer_data.data.assign(data.data.begin(), data.data.end());
resp.manufacturer_data.push_back(std::move(manufacturer_data));
}
this->api_connection_->send_bluetooth_le_advertisement(resp);
}
@@ -173,18 +133,6 @@ void BluetoothProxy::loop() {
}
return;
}
// Flush any pending BLE advertisements that have been accumulated but not yet sent
if (this->raw_advertisements_) {
static uint32_t last_flush_time = 0;
uint32_t now = millis();
// Flush accumulated advertisements every 100ms
if (now - last_flush_time >= 100) {
this->flush_pending_advertisements();
last_flush_time = now;
}
}
for (auto *connection : this->connections_) {
if (connection->send_service_ == connection->service_count_) {
connection->send_service_ = DONE_SENDING_SERVICES;
@@ -213,27 +161,11 @@ void BluetoothProxy::loop() {
}
api::BluetoothGATTGetServicesResponse resp;
resp.address = connection->get_address();
resp.services.reserve(1); // Always one service per response in this implementation
api::BluetoothGATTService service_resp;
service_resp.uuid = get_128bit_uuid_vec(service_result.uuid);
service_resp.handle = service_result.start_handle;
uint16_t char_offset = 0;
esp_gattc_char_elem_t char_result;
// Get the number of characteristics directly with one call
uint16_t total_char_count = 0;
esp_gatt_status_t char_count_status = esp_ble_gattc_get_attr_count(
connection->get_gattc_if(), connection->get_conn_id(), ESP_GATT_DB_CHARACTERISTIC,
service_result.start_handle, service_result.end_handle, 0, &total_char_count);
if (char_count_status == ESP_GATT_OK && total_char_count > 0) {
// Only reserve if we successfully got a count
service_resp.characteristics.reserve(total_char_count);
} else if (char_count_status != ESP_GATT_OK) {
ESP_LOGW(TAG, "[%d] [%s] Error getting characteristic count, status=%d", connection->get_connection_index(),
connection->address_str().c_str(), char_count_status);
}
// Now process characteristics
while (true) { // characteristics
uint16_t char_count = 1;
esp_gatt_status_t char_status = esp_ble_gattc_get_all_char(
@@ -255,23 +187,6 @@ void BluetoothProxy::loop() {
characteristic_resp.handle = char_result.char_handle;
characteristic_resp.properties = char_result.properties;
char_offset++;
// Get the number of descriptors directly with one call
uint16_t total_desc_count = 0;
esp_gatt_status_t desc_count_status =
esp_ble_gattc_get_attr_count(connection->get_gattc_if(), connection->get_conn_id(), ESP_GATT_DB_DESCRIPTOR,
char_result.char_handle, service_result.end_handle, 0, &total_desc_count);
if (desc_count_status == ESP_GATT_OK && total_desc_count > 0) {
// Only reserve if we successfully got a count
characteristic_resp.descriptors.reserve(total_desc_count);
} else if (desc_count_status != ESP_GATT_OK) {
ESP_LOGW(TAG, "[%d] [%s] Error getting descriptor count for char handle %d, status=%d",
connection->get_connection_index(), connection->address_str().c_str(), char_result.char_handle,
desc_count_status);
}
// Now process descriptors
uint16_t desc_offset = 0;
esp_gattc_descr_elem_t desc_result;
while (true) { // descriptors

View File

@@ -56,7 +56,6 @@ class BluetoothProxy : public esp32_ble_tracker::ESPBTDeviceListener, public Com
void dump_config() override;
void setup() override;
void loop() override;
void flush_pending_advertisements();
esp32_ble_tracker::AdvertisementParserType get_advertisement_parser_type() override;
void register_connection(BluetoothConnection *connection) {

View File

@@ -44,7 +44,7 @@ ButtonPressTrigger = button_ns.class_(
validate_device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space="_")
_BUTTON_SCHEMA = (
BUTTON_SCHEMA = (
cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA)
.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA)
.extend(
@@ -60,13 +60,15 @@ _BUTTON_SCHEMA = (
)
)
_UNDEF = object()
def button_schema(
class_: MockObjClass,
*,
icon: str = cv.UNDEFINED,
entity_category: str = cv.UNDEFINED,
device_class: str = cv.UNDEFINED,
icon: str = _UNDEF,
entity_category: str = _UNDEF,
device_class: str = _UNDEF,
) -> cv.Schema:
schema = {cv.GenerateID(): cv.declare_id(class_)}
@@ -75,15 +77,10 @@ def button_schema(
(CONF_ENTITY_CATEGORY, entity_category, cv.entity_category),
(CONF_DEVICE_CLASS, device_class, validate_device_class),
]:
if default is not cv.UNDEFINED:
if default is not _UNDEF:
schema[cv.Optional(key, default=default)] = validator
return _BUTTON_SCHEMA.extend(schema)
# Remove before 2025.11.0
BUTTON_SCHEMA = button_schema(Button)
BUTTON_SCHEMA.add_extra(cv.deprecated_schema_constant("button"))
return BUTTON_SCHEMA.extend(schema)
async def setup_button_core_(var, config):

View File

@@ -11,11 +11,9 @@ from esphome.const import (
CONF_CURRENT_TEMPERATURE_STATE_TOPIC,
CONF_CUSTOM_FAN_MODE,
CONF_CUSTOM_PRESET,
CONF_ENTITY_CATEGORY,
CONF_FAN_MODE,
CONF_FAN_MODE_COMMAND_TOPIC,
CONF_FAN_MODE_STATE_TOPIC,
CONF_ICON,
CONF_ID,
CONF_MAX_TEMPERATURE,
CONF_MIN_TEMPERATURE,
@@ -48,7 +46,6 @@ from esphome.const import (
CONF_WEB_SERVER,
)
from esphome.core import CORE, coroutine_with_priority
from esphome.cpp_generator import MockObjClass
from esphome.cpp_helpers import setup_entity
IS_PLATFORM_COMPONENT = True
@@ -154,11 +151,12 @@ ControlTrigger = climate_ns.class_(
"ControlTrigger", automation.Trigger.template(ClimateCall.operator("ref"))
)
_CLIMATE_SCHEMA = (
CLIMATE_SCHEMA = (
cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA)
.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA)
.extend(
{
cv.GenerateID(): cv.declare_id(Climate),
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTClimateComponent),
cv.Optional(CONF_VISUAL, default={}): cv.Schema(
{
@@ -247,31 +245,6 @@ _CLIMATE_SCHEMA = (
)
def climate_schema(
class_: MockObjClass,
*,
entity_category: str = cv.UNDEFINED,
icon: str = cv.UNDEFINED,
) -> cv.Schema:
schema = {
cv.GenerateID(): cv.declare_id(class_),
}
for key, default, validator in [
(CONF_ENTITY_CATEGORY, entity_category, cv.entity_category),
(CONF_ICON, icon, cv.icon),
]:
if default is not cv.UNDEFINED:
schema[cv.Optional(key, default=default)] = validator
return _CLIMATE_SCHEMA.extend(schema)
# Remove before 2025.11.0
CLIMATE_SCHEMA = climate_schema(Climate)
CLIMATE_SCHEMA.add_extra(cv.deprecated_schema_constant("climate"))
async def setup_climate_core_(var, config):
await setup_entity(var, config)
@@ -446,12 +419,6 @@ async def register_climate(var, config):
await setup_climate_core_(var, config)
async def new_climate(config, *args):
var = cg.new_Pvariable(config[CONF_ID], *args)
await register_climate(var, config)
return var
CLIMATE_CONTROL_ACTION_SCHEMA = cv.Schema(
{
cv.Required(CONF_ID): cv.use_id(Climate),

View File

@@ -1,13 +1,7 @@
import logging
from esphome import core
import esphome.codegen as cg
from esphome.components import climate, remote_base, sensor
import esphome.config_validation as cv
from esphome.const import CONF_ID, CONF_SENSOR, CONF_SUPPORTS_COOL, CONF_SUPPORTS_HEAT
from esphome.cpp_generator import MockObjClass
_LOGGER = logging.getLogger(__name__)
from esphome.const import CONF_SENSOR, CONF_SUPPORTS_COOL, CONF_SUPPORTS_HEAT
DEPENDENCIES = ["remote_transmitter"]
AUTO_LOAD = ["sensor", "remote_base"]
@@ -22,58 +16,30 @@ ClimateIR = climate_ir_ns.class_(
remote_base.RemoteTransmittable,
)
def climate_ir_schema(
class_: MockObjClass,
) -> cv.Schema:
return (
climate.climate_schema(class_)
.extend(
{
cv.Optional(CONF_SUPPORTS_COOL, default=True): cv.boolean,
cv.Optional(CONF_SUPPORTS_HEAT, default=True): cv.boolean,
cv.Optional(CONF_SENSOR): cv.use_id(sensor.Sensor),
}
)
.extend(cv.COMPONENT_SCHEMA)
.extend(remote_base.REMOTE_TRANSMITTABLE_SCHEMA)
)
def climare_ir_with_receiver_schema(
class_: MockObjClass,
) -> cv.Schema:
return climate_ir_schema(class_).extend(
CLIMATE_IR_SCHEMA = (
climate.CLIMATE_SCHEMA.extend(
{
cv.Optional(remote_base.CONF_RECEIVER_ID): cv.use_id(
remote_base.RemoteReceiverBase
),
cv.Optional(CONF_SUPPORTS_COOL, default=True): cv.boolean,
cv.Optional(CONF_SUPPORTS_HEAT, default=True): cv.boolean,
cv.Optional(CONF_SENSOR): cv.use_id(sensor.Sensor),
}
)
.extend(cv.COMPONENT_SCHEMA)
.extend(remote_base.REMOTE_TRANSMITTABLE_SCHEMA)
)
# Remove before 2025.11.0
def deprecated_schema_constant(config):
type: str = "unknown"
if (id := config.get(CONF_ID)) is not None and isinstance(id, core.ID):
type = str(id.type).split("::", maxsplit=1)[0]
_LOGGER.warning(
"Using `climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA` is deprecated and will be removed in ESPHome 2025.11.0. "
"Please use `climate_ir.climare_ir_with_receiver_schema(...)` instead. "
"If you are seeing this, report an issue to the external_component author and ask them to update it. "
"https://developers.esphome.io/blog/2025/05/14/_schema-deprecations/. "
"Component using this schema: %s",
type,
)
return config
CLIMATE_IR_WITH_RECEIVER_SCHEMA = climare_ir_with_receiver_schema(ClimateIR)
CLIMATE_IR_WITH_RECEIVER_SCHEMA.add_extra(deprecated_schema_constant)
CLIMATE_IR_WITH_RECEIVER_SCHEMA = CLIMATE_IR_SCHEMA.extend(
{
cv.Optional(remote_base.CONF_RECEIVER_ID): cv.use_id(
remote_base.RemoteReceiverBase
),
}
)
async def register_climate_ir(var, config):
await cg.register_component(var, config)
await climate.register_climate(var, config)
await remote_base.register_transmittable(var, config)
cg.add(var.set_supports_cool(config[CONF_SUPPORTS_COOL]))
cg.add(var.set_supports_heat(config[CONF_SUPPORTS_HEAT]))
@@ -82,9 +48,3 @@ async def register_climate_ir(var, config):
if sensor_id := config.get(CONF_SENSOR):
sens = await cg.get_variable(sensor_id)
cg.add(var.set_sensor(sens))
async def new_climate_ir(config, *args):
var = await climate.new_climate(config, *args)
await register_climate_ir(var, config)
return var

View File

@@ -1,6 +1,7 @@
import esphome.codegen as cg
from esphome.components import climate_ir
import esphome.config_validation as cv
from esphome.const import CONF_ID
AUTO_LOAD = ["climate_ir"]
@@ -13,8 +14,9 @@ CONF_BIT_HIGH = "bit_high"
CONF_BIT_ONE_LOW = "bit_one_low"
CONF_BIT_ZERO_LOW = "bit_zero_low"
CONFIG_SCHEMA = climate_ir.climare_ir_with_receiver_schema(LgIrClimate).extend(
CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(LgIrClimate),
cv.Optional(
CONF_HEADER_HIGH, default="8000us"
): cv.positive_time_period_microseconds,
@@ -35,7 +37,8 @@ CONFIG_SCHEMA = climate_ir.climare_ir_with_receiver_schema(LgIrClimate).extend(
async def to_code(config):
var = await climate_ir.new_climate_ir(config)
var = cg.new_Pvariable(config[CONF_ID])
await climate_ir.register_climate_ir(var, config)
cg.add(var.set_header_high(config[CONF_HEADER_HIGH]))
cg.add(var.set_header_low(config[CONF_HEADER_LOW]))

View File

@@ -1,5 +1,7 @@
import esphome.codegen as cg
from esphome.components import climate_ir
import esphome.config_validation as cv
from esphome.const import CONF_ID
AUTO_LOAD = ["climate_ir"]
CODEOWNERS = ["@glmnet"]
@@ -7,8 +9,13 @@ CODEOWNERS = ["@glmnet"]
coolix_ns = cg.esphome_ns.namespace("coolix")
CoolixClimate = coolix_ns.class_("CoolixClimate", climate_ir.ClimateIR)
CONFIG_SCHEMA = climate_ir.climare_ir_with_receiver_schema(CoolixClimate)
CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(CoolixClimate),
}
)
async def to_code(config):
await climate_ir.new_climate_ir(config)
var = cg.new_Pvariable(config[CONF_ID])
await climate_ir.register_climate_ir(var, config)

View File

@@ -5,6 +5,7 @@ from esphome.const import (
CONF_DEVICE_CLASS,
CONF_ENTITY_CATEGORY,
CONF_ICON,
CONF_ID,
CONF_SOURCE_ID,
)
from esphome.core.entity_helpers import inherit_property_from
@@ -14,15 +15,12 @@ from .. import copy_ns
CopyCover = copy_ns.class_("CopyCover", cover.Cover, cg.Component)
CONFIG_SCHEMA = (
cover.cover_schema(CopyCover)
.extend(
{
cv.Required(CONF_SOURCE_ID): cv.use_id(cover.Cover),
}
)
.extend(cv.COMPONENT_SCHEMA)
)
CONFIG_SCHEMA = cover.COVER_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(CopyCover),
cv.Required(CONF_SOURCE_ID): cv.use_id(cover.Cover),
}
).extend(cv.COMPONENT_SCHEMA)
FINAL_VALIDATE_SCHEMA = cv.All(
inherit_property_from(CONF_ICON, CONF_SOURCE_ID),
@@ -32,7 +30,8 @@ FINAL_VALIDATE_SCHEMA = cv.All(
async def to_code(config):
var = await cover.new_cover(config)
var = cg.new_Pvariable(config[CONF_ID])
await cover.register_cover(var, config)
await cg.register_component(var, config)
source = await cg.get_variable(config[CONF_SOURCE_ID])

View File

@@ -1,7 +1,7 @@
import esphome.codegen as cg
from esphome.components import fan
import esphome.config_validation as cv
from esphome.const import CONF_ENTITY_CATEGORY, CONF_ICON, CONF_SOURCE_ID
from esphome.const import CONF_ENTITY_CATEGORY, CONF_ICON, CONF_ID, CONF_SOURCE_ID
from esphome.core.entity_helpers import inherit_property_from
from .. import copy_ns
@@ -9,15 +9,12 @@ from .. import copy_ns
CopyFan = copy_ns.class_("CopyFan", fan.Fan, cg.Component)
CONFIG_SCHEMA = (
fan.fan_schema(CopyFan)
.extend(
{
cv.Required(CONF_SOURCE_ID): cv.use_id(fan.Fan),
}
)
.extend(cv.COMPONENT_SCHEMA)
)
CONFIG_SCHEMA = fan.FAN_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(CopyFan),
cv.Required(CONF_SOURCE_ID): cv.use_id(fan.Fan),
}
).extend(cv.COMPONENT_SCHEMA)
FINAL_VALIDATE_SCHEMA = cv.All(
inherit_property_from(CONF_ICON, CONF_SOURCE_ID),
@@ -26,7 +23,8 @@ FINAL_VALIDATE_SCHEMA = cv.All(
async def to_code(config):
var = await fan.new_fan(config)
var = cg.new_Pvariable(config[CONF_ID])
await fan.register_fan(var, config)
await cg.register_component(var, config)
source = await cg.get_variable(config[CONF_SOURCE_ID])

View File

@@ -1,7 +1,7 @@
import esphome.codegen as cg
from esphome.components import lock
import esphome.config_validation as cv
from esphome.const import CONF_ENTITY_CATEGORY, CONF_ICON, CONF_SOURCE_ID
from esphome.const import CONF_ENTITY_CATEGORY, CONF_ICON, CONF_ID, CONF_SOURCE_ID
from esphome.core.entity_helpers import inherit_property_from
from .. import copy_ns
@@ -9,15 +9,12 @@ from .. import copy_ns
CopyLock = copy_ns.class_("CopyLock", lock.Lock, cg.Component)
CONFIG_SCHEMA = (
lock.lock_schema(CopyLock)
.extend(
{
cv.Required(CONF_SOURCE_ID): cv.use_id(lock.Lock),
}
)
.extend(cv.COMPONENT_SCHEMA)
)
CONFIG_SCHEMA = lock.LOCK_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(CopyLock),
cv.Required(CONF_SOURCE_ID): cv.use_id(lock.Lock),
}
).extend(cv.COMPONENT_SCHEMA)
FINAL_VALIDATE_SCHEMA = cv.All(
inherit_property_from(CONF_ICON, CONF_SOURCE_ID),
@@ -26,7 +23,8 @@ FINAL_VALIDATE_SCHEMA = cv.All(
async def to_code(config):
var = await lock.new_lock(config)
var = cg.new_Pvariable(config[CONF_ID])
await lock.register_lock(var, config)
await cg.register_component(var, config)
source = await cg.get_variable(config[CONF_SOURCE_ID])

View File

@@ -9,15 +9,12 @@ from .. import copy_ns
CopyText = copy_ns.class_("CopyText", text.Text, cg.Component)
CONFIG_SCHEMA = (
text.text_schema(CopyText)
.extend(
{
cv.Required(CONF_SOURCE_ID): cv.use_id(text.Text),
}
)
.extend(cv.COMPONENT_SCHEMA)
)
CONFIG_SCHEMA = text.TEXT_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(CopyText),
cv.Required(CONF_SOURCE_ID): cv.use_id(text.Text),
}
).extend(cv.COMPONENT_SCHEMA)
FINAL_VALIDATE_SCHEMA = cv.All(
inherit_property_from(CONF_ICON, CONF_SOURCE_ID),

View File

@@ -5,8 +5,6 @@ from esphome.components import mqtt, web_server
import esphome.config_validation as cv
from esphome.const import (
CONF_DEVICE_CLASS,
CONF_ENTITY_CATEGORY,
CONF_ICON,
CONF_ID,
CONF_MQTT_ID,
CONF_ON_OPEN,
@@ -33,7 +31,6 @@ from esphome.const import (
DEVICE_CLASS_WINDOW,
)
from esphome.core import CORE, coroutine_with_priority
from esphome.cpp_generator import MockObjClass
from esphome.cpp_helpers import setup_entity
IS_PLATFORM_COMPONENT = True
@@ -92,11 +89,12 @@ CoverClosedTrigger = cover_ns.class_(
CONF_ON_CLOSED = "on_closed"
_COVER_SCHEMA = (
COVER_SCHEMA = (
cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA)
.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA)
.extend(
{
cv.GenerateID(): cv.declare_id(Cover),
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTCoverComponent),
cv.Optional(CONF_DEVICE_CLASS): cv.one_of(*DEVICE_CLASSES, lower=True),
cv.Optional(CONF_POSITION_COMMAND_TOPIC): cv.All(
@@ -126,33 +124,6 @@ _COVER_SCHEMA = (
)
def cover_schema(
class_: MockObjClass,
*,
device_class: str = cv.UNDEFINED,
entity_category: str = cv.UNDEFINED,
icon: str = cv.UNDEFINED,
) -> cv.Schema:
schema = {
cv.GenerateID(): cv.declare_id(class_),
}
for key, default, validator in [
(CONF_DEVICE_CLASS, device_class, cv.one_of(*DEVICE_CLASSES, lower=True)),
(CONF_ENTITY_CATEGORY, entity_category, cv.entity_category),
(CONF_ICON, icon, cv.icon),
]:
if default is not cv.UNDEFINED:
schema[cv.Optional(key, default=default)] = validator
return _COVER_SCHEMA.extend(schema)
# Remove before 2025.11.0
COVER_SCHEMA = cover_schema(Cover)
COVER_SCHEMA.add_extra(cv.deprecated_schema_constant("cover"))
async def setup_cover_core_(var, config):
await setup_entity(var, config)
@@ -192,12 +163,6 @@ async def register_cover(var, config):
await setup_cover_core_(var, config)
async def new_cover(config, *args):
var = cg.new_Pvariable(config[CONF_ID], *args)
await register_cover(var, config)
return var
COVER_ACTION_SCHEMA = maybe_simple_id(
{
cv.Required(CONF_ID): cv.use_id(Cover),

View File

@@ -5,6 +5,7 @@ import esphome.config_validation as cv
from esphome.const import (
CONF_CLOSE_ACTION,
CONF_CLOSE_DURATION,
CONF_ID,
CONF_MAX_DURATION,
CONF_OPEN_ACTION,
CONF_OPEN_DURATION,
@@ -29,47 +30,45 @@ CurrentBasedCover = current_based_ns.class_(
"CurrentBasedCover", cover.Cover, cg.Component
)
CONFIG_SCHEMA = (
cover.cover_schema(CurrentBasedCover)
.extend(
{
cv.Required(CONF_STOP_ACTION): automation.validate_automation(single=True),
cv.Required(CONF_OPEN_SENSOR): cv.use_id(sensor.Sensor),
cv.Required(CONF_OPEN_MOVING_CURRENT_THRESHOLD): cv.float_range(
min=0, min_included=False
),
cv.Optional(CONF_OPEN_OBSTACLE_CURRENT_THRESHOLD): cv.float_range(
min=0, min_included=False
),
cv.Required(CONF_OPEN_ACTION): automation.validate_automation(single=True),
cv.Required(CONF_OPEN_DURATION): cv.positive_time_period_milliseconds,
cv.Required(CONF_CLOSE_SENSOR): cv.use_id(sensor.Sensor),
cv.Required(CONF_CLOSE_MOVING_CURRENT_THRESHOLD): cv.float_range(
min=0, min_included=False
),
cv.Optional(CONF_CLOSE_OBSTACLE_CURRENT_THRESHOLD): cv.float_range(
min=0, min_included=False
),
cv.Required(CONF_CLOSE_ACTION): automation.validate_automation(single=True),
cv.Required(CONF_CLOSE_DURATION): cv.positive_time_period_milliseconds,
cv.Optional(CONF_OBSTACLE_ROLLBACK, default="10%"): cv.percentage,
cv.Optional(CONF_MAX_DURATION): cv.positive_time_period_milliseconds,
cv.Optional(CONF_MALFUNCTION_DETECTION, default=True): cv.boolean,
cv.Optional(CONF_MALFUNCTION_ACTION): automation.validate_automation(
single=True
),
cv.Optional(
CONF_START_SENSING_DELAY, default="500ms"
): cv.positive_time_period_milliseconds,
}
)
.extend(cv.COMPONENT_SCHEMA)
)
CONFIG_SCHEMA = cover.COVER_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(CurrentBasedCover),
cv.Required(CONF_STOP_ACTION): automation.validate_automation(single=True),
cv.Required(CONF_OPEN_SENSOR): cv.use_id(sensor.Sensor),
cv.Required(CONF_OPEN_MOVING_CURRENT_THRESHOLD): cv.float_range(
min=0, min_included=False
),
cv.Optional(CONF_OPEN_OBSTACLE_CURRENT_THRESHOLD): cv.float_range(
min=0, min_included=False
),
cv.Required(CONF_OPEN_ACTION): automation.validate_automation(single=True),
cv.Required(CONF_OPEN_DURATION): cv.positive_time_period_milliseconds,
cv.Required(CONF_CLOSE_SENSOR): cv.use_id(sensor.Sensor),
cv.Required(CONF_CLOSE_MOVING_CURRENT_THRESHOLD): cv.float_range(
min=0, min_included=False
),
cv.Optional(CONF_CLOSE_OBSTACLE_CURRENT_THRESHOLD): cv.float_range(
min=0, min_included=False
),
cv.Required(CONF_CLOSE_ACTION): automation.validate_automation(single=True),
cv.Required(CONF_CLOSE_DURATION): cv.positive_time_period_milliseconds,
cv.Optional(CONF_OBSTACLE_ROLLBACK, default="10%"): cv.percentage,
cv.Optional(CONF_MAX_DURATION): cv.positive_time_period_milliseconds,
cv.Optional(CONF_MALFUNCTION_DETECTION, default=True): cv.boolean,
cv.Optional(CONF_MALFUNCTION_ACTION): automation.validate_automation(
single=True
),
cv.Optional(
CONF_START_SENSING_DELAY, default="500ms"
): cv.positive_time_period_milliseconds,
}
).extend(cv.COMPONENT_SCHEMA)
async def to_code(config):
var = await cover.new_cover(config)
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await cover.register_cover(var, config)
await automation.build_automation(
var.get_stop_trigger(), [], config[CONF_STOP_ACTION]

View File

@@ -1,13 +1,20 @@
import esphome.codegen as cg
from esphome.components import climate_ir
import esphome.config_validation as cv
from esphome.const import CONF_ID
AUTO_LOAD = ["climate_ir"]
daikin_ns = cg.esphome_ns.namespace("daikin")
DaikinClimate = daikin_ns.class_("DaikinClimate", climate_ir.ClimateIR)
CONFIG_SCHEMA = climate_ir.climare_ir_with_receiver_schema(DaikinClimate)
CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(DaikinClimate),
}
)
async def to_code(config):
await climate_ir.new_climate_ir(config)
var = cg.new_Pvariable(config[CONF_ID])
await climate_ir.register_climate_ir(var, config)

View File

@@ -1,13 +1,18 @@
import esphome.codegen as cg
from esphome.components import climate_ir
import esphome.config_validation as cv
from esphome.const import CONF_ID
AUTO_LOAD = ["climate_ir"]
daikin_arc_ns = cg.esphome_ns.namespace("daikin_arc")
DaikinArcClimate = daikin_arc_ns.class_("DaikinArcClimate", climate_ir.ClimateIR)
CONFIG_SCHEMA = climate_ir.climare_ir_with_receiver_schema(DaikinArcClimate)
CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend(
{cv.GenerateID(): cv.declare_id(DaikinArcClimate)}
)
async def to_code(config):
await climate_ir.new_climate_ir(config)
var = cg.new_Pvariable(config[CONF_ID])
await climate_ir.register_climate_ir(var, config)

View File

@@ -1,7 +1,7 @@
import esphome.codegen as cg
from esphome.components import climate_ir
import esphome.config_validation as cv
from esphome.const import CONF_USE_FAHRENHEIT
from esphome.const import CONF_ID, CONF_USE_FAHRENHEIT
AUTO_LOAD = ["climate_ir"]
@@ -9,13 +9,15 @@ daikin_brc_ns = cg.esphome_ns.namespace("daikin_brc")
DaikinBrcClimate = daikin_brc_ns.class_("DaikinBrcClimate", climate_ir.ClimateIR)
CONFIG_SCHEMA = climate_ir.climare_ir_with_receiver_schema(DaikinBrcClimate).extend(
CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(DaikinBrcClimate),
cv.Optional(CONF_USE_FAHRENHEIT, default=False): cv.boolean,
}
)
async def to_code(config):
var = await climate_ir.new_climate_ir(config)
var = cg.new_Pvariable(config[CONF_ID])
await climate_ir.register_climate_ir(var, config)
cg.add(var.set_fahrenheit(config[CONF_USE_FAHRENHEIT]))

View File

@@ -56,13 +56,21 @@ void DallasTemperatureSensor::update() {
});
}
void IRAM_ATTR DallasTemperatureSensor::read_scratch_pad_int_() {
for (uint8_t &i : this->scratch_pad_) {
i = this->bus_->read8();
}
}
bool DallasTemperatureSensor::read_scratch_pad_() {
bool success = this->send_command_(DALLAS_COMMAND_READ_SCRATCH_PAD);
if (success) {
for (uint8_t &i : this->scratch_pad_) {
i = this->bus_->read8();
}
} else {
bool success;
{
InterruptLock lock;
success = this->send_command_(DALLAS_COMMAND_READ_SCRATCH_PAD);
if (success)
this->read_scratch_pad_int_();
}
if (!success) {
ESP_LOGW(TAG, "'%s' - reading scratch pad failed bus reset", this->get_name().c_str());
this->status_set_warning("bus reset failed");
}
@@ -105,14 +113,17 @@ void DallasTemperatureSensor::setup() {
return;
this->scratch_pad_[4] = res;
if (this->send_command_(DALLAS_COMMAND_WRITE_SCRATCH_PAD)) {
this->bus_->write8(this->scratch_pad_[2]); // high alarm temp
this->bus_->write8(this->scratch_pad_[3]); // low alarm temp
this->bus_->write8(this->scratch_pad_[4]); // resolution
}
{
InterruptLock lock;
if (this->send_command_(DALLAS_COMMAND_WRITE_SCRATCH_PAD)) {
this->bus_->write8(this->scratch_pad_[2]); // high alarm temp
this->bus_->write8(this->scratch_pad_[3]); // low alarm temp
this->bus_->write8(this->scratch_pad_[4]); // resolution
}
// write value to EEPROM
this->send_command_(DALLAS_COMMAND_COPY_SCRATCH_PAD);
// write value to EEPROM
this->send_command_(DALLAS_COMMAND_COPY_SCRATCH_PAD);
}
}
bool DallasTemperatureSensor::check_scratch_pad_() {
@@ -127,10 +138,6 @@ bool DallasTemperatureSensor::check_scratch_pad_() {
if (!chksum_validity) {
ESP_LOGW(TAG, "'%s' - Scratch pad checksum invalid!", this->get_name().c_str());
this->status_set_warning("scratch pad checksum invalid");
ESP_LOGD(TAG, "Scratch pad: %02X.%02X.%02X.%02X.%02X.%02X.%02X.%02X.%02X (%02X)", this->scratch_pad_[0],
this->scratch_pad_[1], this->scratch_pad_[2], this->scratch_pad_[3], this->scratch_pad_[4],
this->scratch_pad_[5], this->scratch_pad_[6], this->scratch_pad_[7], this->scratch_pad_[8],
crc8(this->scratch_pad_, 8));
}
return chksum_validity;
}

View File

@@ -23,6 +23,7 @@ class DallasTemperatureSensor : public PollingComponent, public sensor::Sensor,
/// Get the number of milliseconds we have to wait for the conversion phase.
uint16_t millis_to_wait_for_conversion_() const;
bool read_scratch_pad_();
void read_scratch_pad_int_();
bool check_scratch_pad_();
float get_temp_c_();
};

View File

@@ -1,13 +1,20 @@
import esphome.codegen as cg
from esphome.components import climate_ir
import esphome.config_validation as cv
from esphome.const import CONF_ID
AUTO_LOAD = ["climate_ir"]
delonghi_ns = cg.esphome_ns.namespace("delonghi")
DelonghiClimate = delonghi_ns.class_("DelonghiClimate", climate_ir.ClimateIR)
CONFIG_SCHEMA = climate_ir.climare_ir_with_receiver_schema(DelonghiClimate)
CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(DelonghiClimate),
}
)
async def to_code(config):
await climate_ir.new_climate_ir(config)
var = cg.new_Pvariable(config[CONF_ID])
await climate_ir.register_climate_ir(var, config)

View File

@@ -17,6 +17,7 @@ from esphome.const import (
CONF_DEVICE_CLASS,
CONF_FORCE_UPDATE,
CONF_ICON,
CONF_ID,
CONF_INVERTED,
CONF_MAX_VALUE,
CONF_MIN_VALUE,
@@ -152,10 +153,9 @@ CONFIG_SCHEMA = cv.Schema(
},
],
): [
climate.climate_schema(DemoClimate)
.extend(cv.COMPONENT_SCHEMA)
.extend(
climate.CLIMATE_SCHEMA.extend(cv.COMPONENT_SCHEMA).extend(
{
cv.GenerateID(): cv.declare_id(DemoClimate),
cv.Required(CONF_TYPE): cv.enum(CLIMATE_TYPES, int=True),
}
)
@@ -183,10 +183,9 @@ CONFIG_SCHEMA = cv.Schema(
},
],
): [
cover.cover_schema(DemoCover)
.extend(cv.COMPONENT_SCHEMA)
.extend(
cover.COVER_SCHEMA.extend(cv.COMPONENT_SCHEMA).extend(
{
cv.GenerateID(): cv.declare_id(DemoCover),
cv.Required(CONF_TYPE): cv.enum(COVER_TYPES, int=True),
}
)
@@ -212,10 +211,9 @@ CONFIG_SCHEMA = cv.Schema(
},
],
): [
fan.fan_schema(DemoFan)
.extend(cv.COMPONENT_SCHEMA)
.extend(
fan.FAN_SCHEMA.extend(cv.COMPONENT_SCHEMA).extend(
{
cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(DemoFan),
cv.Required(CONF_TYPE): cv.enum(FAN_TYPES, int=True),
}
)
@@ -253,9 +251,7 @@ CONFIG_SCHEMA = cv.Schema(
},
],
): [
light.light_schema(DemoLight, light.LightType.RGB)
.extend(cv.COMPONENT_SCHEMA)
.extend(
light.RGB_LIGHT_SCHEMA.extend(cv.COMPONENT_SCHEMA).extend(
{
cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(DemoLight),
cv.Required(CONF_TYPE): cv.enum(LIGHT_TYPES, int=True),
@@ -381,33 +377,39 @@ async def to_code(config):
await cg.register_component(var, conf)
for conf in config[CONF_CLIMATES]:
var = await climate.new_climate(conf)
var = cg.new_Pvariable(conf[CONF_ID])
await cg.register_component(var, conf)
await climate.register_climate(var, conf)
cg.add(var.set_type(conf[CONF_TYPE]))
for conf in config[CONF_COVERS]:
var = await cover.new_cover(conf)
var = cg.new_Pvariable(conf[CONF_ID])
await cg.register_component(var, conf)
await cover.register_cover(var, conf)
cg.add(var.set_type(conf[CONF_TYPE]))
for conf in config[CONF_FANS]:
var = await fan.new_fan(conf)
var = cg.new_Pvariable(conf[CONF_OUTPUT_ID])
await cg.register_component(var, conf)
await fan.register_fan(var, conf)
cg.add(var.set_type(conf[CONF_TYPE]))
for conf in config[CONF_LIGHTS]:
var = await light.new_light(conf)
var = cg.new_Pvariable(conf[CONF_OUTPUT_ID])
await cg.register_component(var, conf)
await light.register_light(var, conf)
cg.add(var.set_type(conf[CONF_TYPE]))
for conf in config[CONF_NUMBERS]:
var = await number.new_number(
var = cg.new_Pvariable(conf[CONF_ID])
await cg.register_component(var, conf)
await number.register_number(
var,
conf,
min_value=conf[CONF_MIN_VALUE],
max_value=conf[CONF_MAX_VALUE],
step=conf[CONF_STEP],
)
await cg.register_component(var, conf)
cg.add(var.set_type(conf[CONF_TYPE]))
for conf in config[CONF_SENSORS]:

View File

@@ -2,7 +2,6 @@ import esphome.codegen as cg
from esphome.components import switch
import esphome.config_validation as cv
from esphome.const import CONF_TYPE, ENTITY_CATEGORY_CONFIG
from esphome.cpp_generator import MockObjClass
from .. import CONF_DFROBOT_SEN0395_ID, DfrobotSen0395Component
@@ -27,30 +26,32 @@ Sen0395StartAfterBootSwitch = dfrobot_sen0395_ns.class_(
"Sen0395StartAfterBootSwitch", DfrobotSen0395Switch
)
def _switch_schema(class_: MockObjClass) -> cv.Schema:
return (
switch.switch_schema(
class_,
entity_category=ENTITY_CATEGORY_CONFIG,
)
.extend(
{
cv.GenerateID(CONF_DFROBOT_SEN0395_ID): cv.use_id(
DfrobotSen0395Component
),
}
)
.extend(cv.COMPONENT_SCHEMA)
_SWITCH_SCHEMA = (
switch.switch_schema(
entity_category=ENTITY_CATEGORY_CONFIG,
)
.extend(
{
cv.GenerateID(CONF_DFROBOT_SEN0395_ID): cv.use_id(DfrobotSen0395Component),
}
)
.extend(cv.COMPONENT_SCHEMA)
)
CONFIG_SCHEMA = cv.typed_schema(
{
"sensor_active": _switch_schema(Sen0395PowerSwitch),
"turn_on_led": _switch_schema(Sen0395LedSwitch),
"presence_via_uart": _switch_schema(Sen0395UartPresenceSwitch),
"start_after_boot": _switch_schema(Sen0395StartAfterBootSwitch),
"sensor_active": _SWITCH_SCHEMA.extend(
{cv.GenerateID(): cv.declare_id(Sen0395PowerSwitch)}
),
"turn_on_led": _SWITCH_SCHEMA.extend(
{cv.GenerateID(): cv.declare_id(Sen0395LedSwitch)}
),
"presence_via_uart": _SWITCH_SCHEMA.extend(
{cv.GenerateID(): cv.declare_id(Sen0395UartPresenceSwitch)}
),
"start_after_boot": _SWITCH_SCHEMA.extend(
{cv.GenerateID(): cv.declare_id(Sen0395StartAfterBootSwitch)}
),
}
)

View File

@@ -1,5 +1,7 @@
import esphome.codegen as cg
from esphome.components import climate_ir
import esphome.config_validation as cv
from esphome.const import CONF_ID
CODEOWNERS = ["@E440QF"]
AUTO_LOAD = ["climate_ir"]
@@ -7,8 +9,13 @@ AUTO_LOAD = ["climate_ir"]
emmeti_ns = cg.esphome_ns.namespace("emmeti")
EmmetiClimate = emmeti_ns.class_("EmmetiClimate", climate_ir.ClimateIR)
CONFIG_SCHEMA = climate_ir.climare_ir_with_receiver_schema(EmmetiClimate)
CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(EmmetiClimate),
}
)
async def to_code(config):
await climate_ir.new_climate_ir(config)
var = cg.new_Pvariable(config[CONF_ID])
await climate_ir.register_climate_ir(var, config)

View File

@@ -6,6 +6,7 @@ from esphome.const import (
CONF_CLOSE_ACTION,
CONF_CLOSE_DURATION,
CONF_CLOSE_ENDSTOP,
CONF_ID,
CONF_MAX_DURATION,
CONF_OPEN_ACTION,
CONF_OPEN_DURATION,
@@ -16,27 +17,25 @@ from esphome.const import (
endstop_ns = cg.esphome_ns.namespace("endstop")
EndstopCover = endstop_ns.class_("EndstopCover", cover.Cover, cg.Component)
CONFIG_SCHEMA = (
cover.cover_schema(EndstopCover)
.extend(
{
cv.Required(CONF_STOP_ACTION): automation.validate_automation(single=True),
cv.Required(CONF_OPEN_ENDSTOP): cv.use_id(binary_sensor.BinarySensor),
cv.Required(CONF_OPEN_ACTION): automation.validate_automation(single=True),
cv.Required(CONF_OPEN_DURATION): cv.positive_time_period_milliseconds,
cv.Required(CONF_CLOSE_ACTION): automation.validate_automation(single=True),
cv.Required(CONF_CLOSE_ENDSTOP): cv.use_id(binary_sensor.BinarySensor),
cv.Required(CONF_CLOSE_DURATION): cv.positive_time_period_milliseconds,
cv.Optional(CONF_MAX_DURATION): cv.positive_time_period_milliseconds,
}
)
.extend(cv.COMPONENT_SCHEMA)
)
CONFIG_SCHEMA = cover.COVER_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(EndstopCover),
cv.Required(CONF_STOP_ACTION): automation.validate_automation(single=True),
cv.Required(CONF_OPEN_ENDSTOP): cv.use_id(binary_sensor.BinarySensor),
cv.Required(CONF_OPEN_ACTION): automation.validate_automation(single=True),
cv.Required(CONF_OPEN_DURATION): cv.positive_time_period_milliseconds,
cv.Required(CONF_CLOSE_ACTION): automation.validate_automation(single=True),
cv.Required(CONF_CLOSE_ENDSTOP): cv.use_id(binary_sensor.BinarySensor),
cv.Required(CONF_CLOSE_DURATION): cv.positive_time_period_milliseconds,
cv.Optional(CONF_MAX_DURATION): cv.positive_time_period_milliseconds,
}
).extend(cv.COMPONENT_SCHEMA)
async def to_code(config):
var = await cover.new_cover(config)
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await cover.register_cover(var, config)
await automation.build_automation(
var.get_stop_trigger(), [], config[CONF_STOP_ACTION]

View File

@@ -2,66 +2,42 @@
#include "gpio.h"
#include "esphome/core/log.h"
#include "driver/gpio.h"
#include "driver/rtc_io.h"
#include "hal/gpio_hal.h"
#include "soc/soc_caps.h"
#include "soc/gpio_periph.h"
#include <cinttypes>
#if (SOC_RTCIO_PIN_COUNT > 0)
#include "hal/rtc_io_hal.h"
#endif
#ifndef SOC_GPIO_SUPPORT_RTC_INDEPENDENT
#define SOC_GPIO_SUPPORT_RTC_INDEPENDENT 0 // NOLINT
#endif
namespace esphome {
namespace esp32 {
static const char *const TAG = "esp32";
static const gpio_hal_context_t GPIO_HAL = {.dev = GPIO_HAL_GET_HW(GPIO_PORT_0)};
bool ESP32InternalGPIOPin::isr_service_installed = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
static gpio_mode_t flags_to_mode(gpio::Flags flags) {
static gpio_mode_t IRAM_ATTR flags_to_mode(gpio::Flags flags) {
flags = (gpio::Flags)(flags & ~(gpio::FLAG_PULLUP | gpio::FLAG_PULLDOWN));
if (flags == gpio::FLAG_INPUT)
if (flags == gpio::FLAG_INPUT) {
return GPIO_MODE_INPUT;
if (flags == gpio::FLAG_OUTPUT)
} else if (flags == gpio::FLAG_OUTPUT) {
return GPIO_MODE_OUTPUT;
if (flags == (gpio::FLAG_OUTPUT | gpio::FLAG_OPEN_DRAIN))
} else if (flags == (gpio::FLAG_OUTPUT | gpio::FLAG_OPEN_DRAIN)) {
return GPIO_MODE_OUTPUT_OD;
if (flags == (gpio::FLAG_INPUT | gpio::FLAG_OUTPUT | gpio::FLAG_OPEN_DRAIN))
} else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_OUTPUT | gpio::FLAG_OPEN_DRAIN)) {
return GPIO_MODE_INPUT_OUTPUT_OD;
if (flags == (gpio::FLAG_INPUT | gpio::FLAG_OUTPUT))
} else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_OUTPUT)) {
return GPIO_MODE_INPUT_OUTPUT;
// unsupported or gpio::FLAG_NONE
return GPIO_MODE_DISABLE;
} else {
// unsupported or gpio::FLAG_NONE
return GPIO_MODE_DISABLE;
}
}
struct ISRPinArg {
gpio_num_t pin;
gpio::Flags flags;
bool inverted;
#if defined(USE_ESP32_VARIANT_ESP32)
bool use_rtc;
int rtc_pin;
#endif
};
ISRInternalGPIOPin ESP32InternalGPIOPin::to_isr() const {
auto *arg = new ISRPinArg{}; // NOLINT(cppcoreguidelines-owning-memory)
arg->pin = this->pin_;
arg->flags = gpio::FLAG_NONE;
arg->pin = pin_;
arg->inverted = inverted_;
#if defined(USE_ESP32_VARIANT_ESP32)
arg->use_rtc = rtc_gpio_is_valid_gpio(this->pin_);
if (arg->use_rtc)
arg->rtc_pin = rtc_io_number_get(this->pin_);
#endif
return ISRInternalGPIOPin((void *) arg);
}
@@ -114,7 +90,6 @@ void ESP32InternalGPIOPin::setup() {
if (flags_ & gpio::FLAG_OUTPUT) {
gpio_set_drive_capability(pin_, drive_strength_);
}
ESP_LOGD(TAG, "rtc: %d", SOC_GPIO_SUPPORT_RTC_INDEPENDENT);
}
void ESP32InternalGPIOPin::pin_mode(gpio::Flags flags) {
@@ -140,65 +115,28 @@ void ESP32InternalGPIOPin::detach_interrupt() const { gpio_intr_disable(pin_); }
using namespace esp32;
bool IRAM_ATTR ISRInternalGPIOPin::digital_read() {
auto *arg = reinterpret_cast<ISRPinArg *>(this->arg_);
return bool(gpio_hal_get_level(&GPIO_HAL, arg->pin)) != arg->inverted;
auto *arg = reinterpret_cast<ISRPinArg *>(arg_);
return bool(gpio_get_level(arg->pin)) != arg->inverted;
}
void IRAM_ATTR ISRInternalGPIOPin::digital_write(bool value) {
auto *arg = reinterpret_cast<ISRPinArg *>(this->arg_);
gpio_hal_set_level(&GPIO_HAL, arg->pin, value != arg->inverted);
auto *arg = reinterpret_cast<ISRPinArg *>(arg_);
gpio_set_level(arg->pin, value != arg->inverted ? 1 : 0);
}
void IRAM_ATTR ISRInternalGPIOPin::clear_interrupt() {
// not supported
}
void IRAM_ATTR ISRInternalGPIOPin::pin_mode(gpio::Flags flags) {
auto *arg = reinterpret_cast<ISRPinArg *>(arg_);
gpio::Flags diff = (gpio::Flags)(flags ^ arg->flags);
if (diff & gpio::FLAG_OUTPUT) {
if (flags & gpio::FLAG_OUTPUT) {
gpio_hal_output_enable(&GPIO_HAL, arg->pin);
if (flags & gpio::FLAG_OPEN_DRAIN)
gpio_hal_od_enable(&GPIO_HAL, arg->pin);
} else {
gpio_hal_output_disable(&GPIO_HAL, arg->pin);
}
gpio_set_direction(arg->pin, flags_to_mode(flags));
gpio_pull_mode_t pull_mode = GPIO_FLOATING;
if ((flags & gpio::FLAG_PULLUP) && (flags & gpio::FLAG_PULLDOWN)) {
pull_mode = GPIO_PULLUP_PULLDOWN;
} else if (flags & gpio::FLAG_PULLUP) {
pull_mode = GPIO_PULLUP_ONLY;
} else if (flags & gpio::FLAG_PULLDOWN) {
pull_mode = GPIO_PULLDOWN_ONLY;
}
if (diff & gpio::FLAG_INPUT) {
if (flags & gpio::FLAG_INPUT) {
gpio_hal_input_enable(&GPIO_HAL, arg->pin);
#if defined(USE_ESP32_VARIANT_ESP32)
if (arg->use_rtc) {
if (flags & gpio::FLAG_PULLUP) {
rtcio_hal_pullup_enable(arg->rtc_pin);
} else {
rtcio_hal_pullup_disable(arg->rtc_pin);
}
if (flags & gpio::FLAG_PULLDOWN) {
rtcio_hal_pulldown_enable(arg->rtc_pin);
} else {
rtcio_hal_pulldown_disable(arg->rtc_pin);
}
} else
#endif
{
if (flags & gpio::FLAG_PULLUP) {
gpio_hal_pullup_en(&GPIO_HAL, arg->pin);
} else {
gpio_hal_pullup_dis(&GPIO_HAL, arg->pin);
}
if (flags & gpio::FLAG_PULLDOWN) {
gpio_hal_pulldown_en(&GPIO_HAL, arg->pin);
} else {
gpio_hal_pulldown_dis(&GPIO_HAL, arg->pin);
}
}
} else {
gpio_hal_input_disable(&GPIO_HAL, arg->pin);
}
}
arg->flags = flags;
gpio_set_pull_mode(arg->pin, pull_mode);
}
} // namespace esphome

View File

@@ -1,6 +1,6 @@
from dataclasses import dataclass
import logging
from typing import Any, Callable
from typing import Any
from esphome import pins
import esphome.codegen as cg
@@ -64,7 +64,8 @@ def _lookup_pin(value):
def _translate_pin(value):
if isinstance(value, dict) or value is None:
raise cv.Invalid(
"This variable only supports pin numbers, not full pin schemas (with inverted and mode)."
"This variable only supports pin numbers, not full pin schemas "
"(with inverted and mode)."
)
if isinstance(value, int) and not isinstance(value, bool):
return value
@@ -81,22 +82,30 @@ def _translate_pin(value):
@dataclass
class ESP32ValidationFunctions:
pin_validation: Callable[[Any], Any]
usage_validation: Callable[[Any], Any]
pin_validation: Any
usage_validation: Any
_esp32_validations = {
VARIANT_ESP32: ESP32ValidationFunctions(
pin_validation=esp32_validate_gpio_pin, usage_validation=esp32_validate_supports
),
VARIANT_ESP32C2: ESP32ValidationFunctions(
pin_validation=esp32_c2_validate_gpio_pin,
usage_validation=esp32_c2_validate_supports,
VARIANT_ESP32S2: ESP32ValidationFunctions(
pin_validation=esp32_s2_validate_gpio_pin,
usage_validation=esp32_s2_validate_supports,
),
VARIANT_ESP32C3: ESP32ValidationFunctions(
pin_validation=esp32_c3_validate_gpio_pin,
usage_validation=esp32_c3_validate_supports,
),
VARIANT_ESP32S3: ESP32ValidationFunctions(
pin_validation=esp32_s3_validate_gpio_pin,
usage_validation=esp32_s3_validate_supports,
),
VARIANT_ESP32C2: ESP32ValidationFunctions(
pin_validation=esp32_c2_validate_gpio_pin,
usage_validation=esp32_c2_validate_supports,
),
VARIANT_ESP32C6: ESP32ValidationFunctions(
pin_validation=esp32_c6_validate_gpio_pin,
usage_validation=esp32_c6_validate_supports,
@@ -105,14 +114,6 @@ _esp32_validations = {
pin_validation=esp32_h2_validate_gpio_pin,
usage_validation=esp32_h2_validate_supports,
),
VARIANT_ESP32S2: ESP32ValidationFunctions(
pin_validation=esp32_s2_validate_gpio_pin,
usage_validation=esp32_s2_validate_supports,
),
VARIANT_ESP32S3: ESP32ValidationFunctions(
pin_validation=esp32_s3_validate_gpio_pin,
usage_validation=esp32_s3_validate_supports,
),
}

View File

@@ -31,7 +31,8 @@ def esp32_validate_gpio_pin(value):
)
if 9 <= value <= 10:
_LOGGER.warning(
"Pin %s (9-10) might already be used by the flash interface in QUAD IO flash mode.",
"Pin %s (9-10) might already be used by the "
"flash interface in QUAD IO flash mode.",
value,
)
if value in (24, 28, 29, 30, 31):

View File

@@ -22,7 +22,7 @@ def esp32_c2_validate_supports(value):
is_input = mode[CONF_INPUT]
if num < 0 or num > 20:
raise cv.Invalid(f"Invalid pin number: {num} (must be 0-20)")
raise cv.Invalid(f"Invalid pin number: {value} (must be 0-20)")
if is_input:
# All ESP32 pins support input mode

View File

@@ -35,7 +35,7 @@ def esp32_c3_validate_supports(value):
is_input = mode[CONF_INPUT]
if num < 0 or num > 21:
raise cv.Invalid(f"Invalid pin number: {num} (must be 0-21)")
raise cv.Invalid(f"Invalid pin number: {value} (must be 0-21)")
if is_input:
# All ESP32 pins support input mode

View File

@@ -36,7 +36,7 @@ def esp32_c6_validate_supports(value):
is_input = mode[CONF_INPUT]
if num < 0 or num > 23:
raise cv.Invalid(f"Invalid pin number: {num} (must be 0-23)")
raise cv.Invalid(f"Invalid pin number: {value} (must be 0-23)")
if is_input:
# All ESP32 pins support input mode
pass

View File

@@ -45,7 +45,7 @@ def esp32_h2_validate_supports(value):
is_input = mode[CONF_INPUT]
if num < 0 or num > 27:
raise cv.Invalid(f"Invalid pin number: {num} (must be 0-27)")
raise cv.Invalid(f"Invalid pin number: {value} (must be 0-27)")
if is_input:
# All ESP32 pins support input mode
pass

View File

@@ -44,7 +44,6 @@ CONF_ESP32_BLE_ID = "esp32_ble_id"
CONF_SCAN_PARAMETERS = "scan_parameters"
CONF_WINDOW = "window"
CONF_ON_SCAN_END = "on_scan_end"
CONF_SOFTWARE_COEXISTENCE = "software_coexistence"
DEFAULT_MAX_CONNECTIONS = 3
IDF_MAX_CONNECTIONS = 9
@@ -204,7 +203,6 @@ CONFIG_SCHEMA = cv.All(
cv.Optional(CONF_ON_SCAN_END): automation.validate_automation(
{cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(BLEEndOfScanTrigger)}
),
cv.OnlyWith(CONF_SOFTWARE_COEXISTENCE, "wifi", default=True): bool,
}
).extend(cv.COMPONENT_SCHEMA),
)
@@ -312,8 +310,6 @@ async def to_code(config):
if CORE.using_esp_idf:
add_idf_sdkconfig_option("CONFIG_BT_ENABLED", True)
if config.get(CONF_SOFTWARE_COEXISTENCE):
add_idf_sdkconfig_option("CONFIG_SW_COEXIST_ENABLE", True)
# https://github.com/espressif/esp-idf/issues/4101
# https://github.com/espressif/esp-idf/issues/2503
# Match arduino CONFIG_BTU_TASK_STACK_SIZE
@@ -335,8 +331,6 @@ async def to_code(config):
cg.add_define("USE_OTA_STATE_CALLBACK") # To be notified when an OTA update starts
cg.add_define("USE_ESP32_BLE_CLIENT")
if config.get(CONF_SOFTWARE_COEXISTENCE):
cg.add_define("USE_ESP32_BLE_SOFTWARE_COEXISTENCE")
ESP32_BLE_START_SCAN_ACTION_SCHEMA = cv.Schema(

View File

@@ -21,10 +21,6 @@
#include "esphome/components/ota/ota_backend.h"
#endif
#ifdef USE_ESP32_BLE_SOFTWARE_COEXISTENCE
#include <esp_coexist.h>
#endif
#ifdef USE_ARDUINO
#include <esp32-hal-bt.h>
#endif
@@ -122,7 +118,7 @@ void ESP32BLETracker::loop() {
if (this->scanner_state_ == ScannerState::RUNNING &&
this->scan_result_index_ && // if it looks like we have a scan result we will take the lock
xSemaphoreTake(this->scan_result_lock_, 0)) {
xSemaphoreTake(this->scan_result_lock_, 5L / portTICK_PERIOD_MS)) {
uint32_t index = this->scan_result_index_;
if (index >= ESP32BLETracker::SCAN_RESULT_BUFFER_SIZE) {
ESP_LOGW(TAG, "Too many BLE events to process. Some devices may not show up.");
@@ -198,17 +194,9 @@ void ESP32BLETracker::loop() {
https://github.com/espressif/esp-idf/issues/6688
*/
if (this->scanner_state_ == ScannerState::IDLE && !connecting && !disconnecting && !promote_to_connecting) {
#ifdef USE_ESP32_BLE_SOFTWARE_COEXISTENCE
if (this->coex_prefer_ble_) {
this->coex_prefer_ble_ = false;
ESP_LOGD(TAG, "Setting coexistence preference to balanced.");
esp_coex_preference_set(ESP_COEX_PREFER_BALANCE); // Reset to default
}
#endif
if (this->scan_continuous_) {
this->start_scan_(false); // first = false
}
if (this->scanner_state_ == ScannerState::IDLE && this->scan_continuous_ && !connecting && !disconnecting &&
!promote_to_connecting) {
this->start_scan_(false); // first = false
}
// If there is a discovered client and no connecting
// clients and no clients using the scanner to search for
@@ -225,13 +213,6 @@ void ESP32BLETracker::loop() {
ESP_LOGD(TAG, "Promoting client to connect...");
// We only want to promote one client at a time.
// once the scanner is fully stopped.
#ifdef USE_ESP32_BLE_SOFTWARE_COEXISTENCE
ESP_LOGD(TAG, "Setting coexistence to Bluetooth to make connection.");
if (!this->coex_prefer_ble_) {
this->coex_prefer_ble_ = true;
esp_coex_preference_set(ESP_COEX_PREFER_BT); // Prioritize Bluetooth
}
#endif
client->set_state(ClientState::READY_TO_CONNECT);
}
break;
@@ -447,7 +428,7 @@ void ESP32BLETracker::gap_scan_stop_complete_(const esp_ble_gap_cb_param_t::ble_
void ESP32BLETracker::gap_scan_result_(const esp_ble_gap_cb_param_t::ble_scan_result_evt_param &param) {
ESP_LOGV(TAG, "gap_scan_result - event %d", param.search_evt);
if (param.search_evt == ESP_GAP_SEARCH_INQ_RES_EVT) {
if (xSemaphoreTake(this->scan_result_lock_, 0)) {
if (xSemaphoreTake(this->scan_result_lock_, 0L)) {
if (this->scan_result_index_ < ESP32BLETracker::SCAN_RESULT_BUFFER_SIZE) {
this->scan_result_buffer_[this->scan_result_index_++] = param;
}

View File

@@ -290,7 +290,7 @@ class ESP32BLETracker : public Component,
#ifdef USE_PSRAM
const static u_int8_t SCAN_RESULT_BUFFER_SIZE = 32;
#else
const static u_int8_t SCAN_RESULT_BUFFER_SIZE = 20;
const static u_int8_t SCAN_RESULT_BUFFER_SIZE = 16;
#endif // USE_PSRAM
esp_ble_gap_cb_param_t::ble_scan_result_evt_param *scan_result_buffer_;
esp_bt_status_t scan_start_failed_{ESP_BT_STATUS_SUCCESS};
@@ -299,9 +299,6 @@ class ESP32BLETracker : public Component,
int discovered_{0};
int searching_{0};
int disconnecting_{0};
#ifdef USE_ESP32_BLE_SOFTWARE_COEXISTENCE
bool coex_prefer_ble_{false};
#endif
};
// NOLINTNEXTLINE

View File

@@ -8,7 +8,7 @@ namespace esp8266 {
static const char *const TAG = "esp8266";
static int flags_to_mode(gpio::Flags flags, uint8_t pin) {
static int IRAM_ATTR flags_to_mode(gpio::Flags flags, uint8_t pin) {
if (flags == gpio::FLAG_INPUT) { // NOLINT(bugprone-branch-clone)
return INPUT;
} else if (flags == gpio::FLAG_OUTPUT) {
@@ -34,36 +34,12 @@ static int flags_to_mode(gpio::Flags flags, uint8_t pin) {
struct ISRPinArg {
uint8_t pin;
bool inverted;
volatile uint32_t *in_reg;
volatile uint32_t *out_set_reg;
volatile uint32_t *out_clr_reg;
volatile uint32_t *mode_set_reg;
volatile uint32_t *mode_clr_reg;
volatile uint32_t *func_reg;
uint32_t mask;
};
ISRInternalGPIOPin ESP8266GPIOPin::to_isr() const {
auto *arg = new ISRPinArg{}; // NOLINT(cppcoreguidelines-owning-memory)
arg->pin = this->pin_;
arg->inverted = this->inverted_;
if (this->pin_ < 16) {
arg->in_reg = &GPI;
arg->out_set_reg = &GPOS;
arg->out_clr_reg = &GPOC;
arg->mode_set_reg = &GPES;
arg->mode_clr_reg = &GPEC;
arg->func_reg = &GPF(this->pin_);
arg->mask = 1 << this->pin_;
} else {
arg->in_reg = &GP16I;
arg->out_set_reg = &GP16O;
arg->out_clr_reg = nullptr;
arg->mode_set_reg = &GP16E;
arg->mode_clr_reg = nullptr;
arg->func_reg = &GPF16;
arg->mask = 1;
}
arg->pin = pin_;
arg->inverted = inverted_;
return ISRInternalGPIOPin((void *) arg);
}
@@ -112,57 +88,20 @@ void ESP8266GPIOPin::detach_interrupt() const { detachInterrupt(pin_); }
using namespace esp8266;
bool IRAM_ATTR ISRInternalGPIOPin::digital_read() {
auto *arg = reinterpret_cast<ISRPinArg *>(this->arg_);
return bool(*arg->in_reg & arg->mask) != arg->inverted;
auto *arg = reinterpret_cast<ISRPinArg *>(arg_);
return bool(digitalRead(arg->pin)) != arg->inverted; // NOLINT
}
void IRAM_ATTR ISRInternalGPIOPin::digital_write(bool value) {
auto *arg = reinterpret_cast<ISRPinArg *>(arg_);
if (arg->pin < 16) {
if (value != arg->inverted) {
*arg->out_set_reg = arg->mask;
} else {
*arg->out_clr_reg = arg->mask;
}
} else {
if (value != arg->inverted) {
*arg->out_set_reg |= 1;
} else {
*arg->out_set_reg &= ~1;
}
}
digitalWrite(arg->pin, value != arg->inverted ? 1 : 0); // NOLINT
}
void IRAM_ATTR ISRInternalGPIOPin::clear_interrupt() {
auto *arg = reinterpret_cast<ISRPinArg *>(arg_);
GPIO_REG_WRITE(GPIO_STATUS_W1TC_ADDRESS, 1UL << arg->pin);
}
void IRAM_ATTR ISRInternalGPIOPin::pin_mode(gpio::Flags flags) {
auto *arg = reinterpret_cast<ISRPinArg *>(this->arg_);
if (arg->pin < 16) {
if (flags & gpio::FLAG_OUTPUT) {
*arg->mode_set_reg = arg->mask;
} else {
*arg->mode_clr_reg = arg->mask;
}
if (flags & gpio::FLAG_PULLUP) {
*arg->func_reg |= 1 << GPFPU;
} else {
*arg->func_reg &= ~(1 << GPFPU);
}
} else {
if (flags & gpio::FLAG_OUTPUT) {
*arg->mode_set_reg |= 1;
} else {
*arg->mode_set_reg &= ~1;
}
if (flags & gpio::FLAG_PULLDOWN) {
*arg->func_reg |= 1 << GP16FPD;
} else {
*arg->func_reg &= ~(1 << GP16FPD);
}
}
auto *arg = reinterpret_cast<ISRPinArg *>(arg_);
pinMode(arg->pin, flags_to_mode(flags, arg->pin)); // NOLINT
}
} // namespace esphome

View File

@@ -41,7 +41,7 @@ EventTrigger = event_ns.class_("EventTrigger", automation.Trigger.template())
validate_device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space="_")
_EVENT_SCHEMA = (
EVENT_SCHEMA = (
cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA)
.extend(cv.MQTT_COMPONENT_SCHEMA)
.extend(
@@ -58,17 +58,19 @@ _EVENT_SCHEMA = (
)
)
_UNDEF = object()
def event_schema(
class_: MockObjClass = cv.UNDEFINED,
class_: MockObjClass = _UNDEF,
*,
icon: str = cv.UNDEFINED,
entity_category: str = cv.UNDEFINED,
device_class: str = cv.UNDEFINED,
icon: str = _UNDEF,
entity_category: str = _UNDEF,
device_class: str = _UNDEF,
) -> cv.Schema:
schema = {}
if class_ is not cv.UNDEFINED:
if class_ is not _UNDEF:
schema[cv.GenerateID()] = cv.declare_id(class_)
for key, default, validator in [
@@ -76,15 +78,10 @@ def event_schema(
(CONF_ENTITY_CATEGORY, entity_category, cv.entity_category),
(CONF_DEVICE_CLASS, device_class, validate_device_class),
]:
if default is not cv.UNDEFINED:
if default is not _UNDEF:
schema[cv.Optional(key, default=default)] = validator
return _EVENT_SCHEMA.extend(schema)
# Remove before 2025.11.0
EVENT_SCHEMA = event_schema()
EVENT_SCHEMA.add_extra(cv.deprecated_schema_constant("event"))
return EVENT_SCHEMA.extend(schema)
async def setup_event_core_(var, config, *, event_types: list[str]):

View File

@@ -1,7 +1,14 @@
import esphome.codegen as cg
from esphome.components import switch
import esphome.config_validation as cv
from esphome.const import ENTITY_CATEGORY_CONFIG, ICON_RESTART_ALERT
from esphome.const import (
CONF_ENTITY_CATEGORY,
CONF_ICON,
CONF_ID,
CONF_INVERTED,
ENTITY_CATEGORY_CONFIG,
ICON_RESTART_ALERT,
)
from .. import factory_reset_ns
@@ -9,14 +16,21 @@ FactoryResetSwitch = factory_reset_ns.class_(
"FactoryResetSwitch", switch.Switch, cg.Component
)
CONFIG_SCHEMA = switch.switch_schema(
FactoryResetSwitch,
block_inverted=True,
icon=ICON_RESTART_ALERT,
entity_category=ENTITY_CATEGORY_CONFIG,
CONFIG_SCHEMA = switch.SWITCH_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(FactoryResetSwitch),
cv.Optional(CONF_INVERTED): cv.invalid(
"Factory Reset switches do not support inverted mode!"
),
cv.Optional(CONF_ICON, default=ICON_RESTART_ALERT): cv.icon,
cv.Optional(
CONF_ENTITY_CATEGORY, default=ENTITY_CATEGORY_CONFIG
): cv.entity_category,
}
).extend(cv.COMPONENT_SCHEMA)
async def to_code(config):
var = await switch.new_switch(config)
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await switch.register_switch(var, config)

View File

@@ -5,10 +5,6 @@ from esphome.components import mqtt, web_server
import esphome.config_validation as cv
from esphome.const import (
CONF_DIRECTION,
CONF_DIRECTION_COMMAND_TOPIC,
CONF_DIRECTION_STATE_TOPIC,
CONF_ENTITY_CATEGORY,
CONF_ICON,
CONF_ID,
CONF_MQTT_ID,
CONF_OFF_SPEED_CYCLE,
@@ -84,21 +80,16 @@ FanPresetSetTrigger = fan_ns.class_(
FanIsOnCondition = fan_ns.class_("FanIsOnCondition", automation.Condition.template())
FanIsOffCondition = fan_ns.class_("FanIsOffCondition", automation.Condition.template())
_FAN_SCHEMA = (
FAN_SCHEMA = (
cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA)
.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA)
.extend(
{
cv.GenerateID(): cv.declare_id(Fan),
cv.Optional(CONF_RESTORE_MODE, default="ALWAYS_OFF"): cv.enum(
RESTORE_MODES, upper=True, space="_"
),
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTFanComponent),
cv.Optional(CONF_DIRECTION_STATE_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
),
cv.Optional(CONF_DIRECTION_COMMAND_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.subscribe_topic
),
cv.Optional(CONF_OSCILLATION_STATE_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
),
@@ -160,37 +151,6 @@ _FAN_SCHEMA = (
)
)
def fan_schema(
class_: cg.Pvariable,
*,
entity_category: str = cv.UNDEFINED,
icon: str = cv.UNDEFINED,
default_restore_mode: str = cv.UNDEFINED,
) -> cv.Schema:
schema = {
cv.GenerateID(): cv.declare_id(class_),
}
for key, default, validator in [
(CONF_ENTITY_CATEGORY, entity_category, cv.entity_category),
(CONF_ICON, icon, cv.icon),
(
CONF_RESTORE_MODE,
default_restore_mode,
cv.enum(RESTORE_MODES, upper=True, space="_"),
),
]:
if default is not cv.UNDEFINED:
schema[cv.Optional(key, default=default)] = validator
return _FAN_SCHEMA.extend(schema)
# Remove before 2025.11.0
FAN_SCHEMA = fan_schema(Fan)
FAN_SCHEMA.add_extra(cv.deprecated_schema_constant("fan"))
_PRESET_MODES_SCHEMA = cv.All(
cv.ensure_list(cv.string_strict),
cv.Length(min=1),
@@ -233,14 +193,6 @@ async def setup_fan_core_(var, config):
mqtt_ = cg.new_Pvariable(mqtt_id, var)
await mqtt.register_mqtt_component(mqtt_, config)
if (
direction_state_topic := config.get(CONF_DIRECTION_STATE_TOPIC)
) is not None:
cg.add(mqtt_.set_custom_direction_state_topic(direction_state_topic))
if (
direction_command_topic := config.get(CONF_DIRECTION_COMMAND_TOPIC)
) is not None:
cg.add(mqtt_.set_custom_direction_command_topic(direction_command_topic))
if (
oscillation_state_topic := config.get(CONF_OSCILLATION_STATE_TOPIC)
) is not None:
@@ -299,9 +251,10 @@ async def register_fan(var, config):
await setup_fan_core_(var, config)
async def new_fan(config, *args):
var = cg.new_Pvariable(config[CONF_ID], *args)
async def create_fan_state(config):
var = cg.new_Pvariable(config[CONF_ID])
await register_fan(var, config)
await cg.register_component(var, config)
return var

View File

@@ -7,6 +7,7 @@ from esphome.const import (
CONF_CLOSE_ACTION,
CONF_CLOSE_DURATION,
CONF_CLOSE_ENDSTOP,
CONF_ID,
CONF_MAX_DURATION,
CONF_OPEN_ACTION,
CONF_OPEN_DURATION,
@@ -49,43 +50,36 @@ def validate_infer_endstop(config):
return config
CONFIG_FEEDBACK_COVER_BASE_SCHEMA = (
cover.cover_schema(FeedbackCover)
.extend(
{
cv.Required(CONF_STOP_ACTION): automation.validate_automation(single=True),
cv.Required(CONF_OPEN_ACTION): automation.validate_automation(single=True),
cv.Required(CONF_OPEN_DURATION): cv.positive_time_period_milliseconds,
cv.Optional(CONF_OPEN_ENDSTOP): cv.use_id(binary_sensor.BinarySensor),
cv.Optional(CONF_OPEN_SENSOR): cv.use_id(binary_sensor.BinarySensor),
cv.Optional(CONF_OPEN_OBSTACLE_SENSOR): cv.use_id(
binary_sensor.BinarySensor
),
cv.Required(CONF_CLOSE_ACTION): automation.validate_automation(single=True),
cv.Required(CONF_CLOSE_DURATION): cv.positive_time_period_milliseconds,
cv.Optional(CONF_CLOSE_ENDSTOP): cv.use_id(binary_sensor.BinarySensor),
cv.Optional(CONF_CLOSE_SENSOR): cv.use_id(binary_sensor.BinarySensor),
cv.Optional(CONF_CLOSE_OBSTACLE_SENSOR): cv.use_id(
binary_sensor.BinarySensor
),
cv.Optional(CONF_MAX_DURATION): cv.positive_time_period_milliseconds,
cv.Optional(CONF_HAS_BUILT_IN_ENDSTOP, default=False): cv.boolean,
cv.Optional(CONF_ASSUMED_STATE): cv.boolean,
cv.Optional(
CONF_UPDATE_INTERVAL, "1000ms"
): cv.positive_time_period_milliseconds,
cv.Optional(CONF_INFER_ENDSTOP_FROM_MOVEMENT, False): cv.boolean,
cv.Optional(
CONF_DIRECTION_CHANGE_WAIT_TIME
): cv.positive_time_period_milliseconds,
cv.Optional(
CONF_ACCELERATION_WAIT_TIME, "0s"
): cv.positive_time_period_milliseconds,
cv.Optional(CONF_OBSTACLE_ROLLBACK, default="10%"): cv.percentage,
},
)
.extend(cv.COMPONENT_SCHEMA)
)
CONFIG_FEEDBACK_COVER_BASE_SCHEMA = cover.COVER_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(FeedbackCover),
cv.Required(CONF_STOP_ACTION): automation.validate_automation(single=True),
cv.Required(CONF_OPEN_ACTION): automation.validate_automation(single=True),
cv.Required(CONF_OPEN_DURATION): cv.positive_time_period_milliseconds,
cv.Optional(CONF_OPEN_ENDSTOP): cv.use_id(binary_sensor.BinarySensor),
cv.Optional(CONF_OPEN_SENSOR): cv.use_id(binary_sensor.BinarySensor),
cv.Optional(CONF_OPEN_OBSTACLE_SENSOR): cv.use_id(binary_sensor.BinarySensor),
cv.Required(CONF_CLOSE_ACTION): automation.validate_automation(single=True),
cv.Required(CONF_CLOSE_DURATION): cv.positive_time_period_milliseconds,
cv.Optional(CONF_CLOSE_ENDSTOP): cv.use_id(binary_sensor.BinarySensor),
cv.Optional(CONF_CLOSE_SENSOR): cv.use_id(binary_sensor.BinarySensor),
cv.Optional(CONF_CLOSE_OBSTACLE_SENSOR): cv.use_id(binary_sensor.BinarySensor),
cv.Optional(CONF_MAX_DURATION): cv.positive_time_period_milliseconds,
cv.Optional(CONF_HAS_BUILT_IN_ENDSTOP, default=False): cv.boolean,
cv.Optional(CONF_ASSUMED_STATE): cv.boolean,
cv.Optional(
CONF_UPDATE_INTERVAL, "1000ms"
): cv.positive_time_period_milliseconds,
cv.Optional(CONF_INFER_ENDSTOP_FROM_MOVEMENT, False): cv.boolean,
cv.Optional(
CONF_DIRECTION_CHANGE_WAIT_TIME
): cv.positive_time_period_milliseconds,
cv.Optional(
CONF_ACCELERATION_WAIT_TIME, "0s"
): cv.positive_time_period_milliseconds,
cv.Optional(CONF_OBSTACLE_ROLLBACK, default="10%"): cv.percentage,
},
).extend(cv.COMPONENT_SCHEMA)
CONFIG_SCHEMA = cv.All(
@@ -96,8 +90,9 @@ CONFIG_SCHEMA = cv.All(
async def to_code(config):
var = await cover.new_cover(config)
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await cover.register_cover(var, config)
# STOP
await automation.build_automation(

View File

@@ -1,5 +1,7 @@
import esphome.codegen as cg
from esphome.components import climate_ir
import esphome.config_validation as cv
from esphome.const import CONF_ID
AUTO_LOAD = ["climate_ir"]
@@ -8,8 +10,13 @@ FujitsuGeneralClimate = fujitsu_general_ns.class_(
"FujitsuGeneralClimate", climate_ir.ClimateIR
)
CONFIG_SCHEMA = climate_ir.climare_ir_with_receiver_schema(FujitsuGeneralClimate)
CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(FujitsuGeneralClimate),
}
)
async def to_code(config):
await climate_ir.new_climate_ir(config)
var = cg.new_Pvariable(config[CONF_ID])
await climate_ir.register_climate_ir(var, config)

View File

@@ -10,10 +10,8 @@ static const char *const TAG = "gpio.one_wire";
void GPIOOneWireBus::setup() {
ESP_LOGCONFIG(TAG, "Setting up 1-wire bus...");
this->t_pin_->setup();
this->t_pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
// clear bus with 480µs high, otherwise initial reset in search might fail
this->pin_.digital_write(true);
this->pin_.pin_mode(gpio::FLAG_OUTPUT);
this->t_pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
delayMicroseconds(480);
this->search();
}
@@ -24,49 +22,40 @@ void GPIOOneWireBus::dump_config() {
this->dump_devices_(TAG);
}
int HOT IRAM_ATTR GPIOOneWireBus::reset_int() {
InterruptLock lock;
bool HOT IRAM_ATTR GPIOOneWireBus::reset() {
// See reset here:
// https://www.maximintegrated.com/en/design/technical-documents/app-notes/1/126.html
// Wait for communication to clear (delay G)
this->pin_.pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
pin_.pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
uint8_t retries = 125;
do {
if (--retries == 0)
return -1;
return false;
delayMicroseconds(2);
} while (!this->pin_.digital_read());
} while (!pin_.digital_read());
bool r = false;
bool r;
// Send 480µs LOW TX reset pulse (drive bus low, delay H)
this->pin_.digital_write(false);
this->pin_.pin_mode(gpio::FLAG_OUTPUT);
pin_.pin_mode(gpio::FLAG_OUTPUT);
pin_.digital_write(false);
delayMicroseconds(480);
// Release the bus, delay I
this->pin_.pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
uint32_t start = micros();
delayMicroseconds(30);
while (micros() - start < 300) {
// sample bus, 0=device(s) present, 1=no device present
r = !this->pin_.digital_read();
if (r)
break;
delayMicroseconds(1);
}
pin_.pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
delayMicroseconds(70);
// sample bus, 0=device(s) present, 1=no device present
r = !pin_.digital_read();
// delay J
delayMicroseconds(start + 480 - micros());
this->pin_.digital_write(true);
this->pin_.pin_mode(gpio::FLAG_OUTPUT);
return r ? 1 : 0;
delayMicroseconds(410);
return r;
}
void HOT IRAM_ATTR GPIOOneWireBus::write_bit_(bool bit) {
// drive bus low
this->pin_.digital_write(false);
pin_.pin_mode(gpio::FLAG_OUTPUT);
pin_.digital_write(false);
// from datasheet:
// write 0 low time: t_low0: min=60µs, max=120µs
@@ -75,62 +64,72 @@ void HOT IRAM_ATTR GPIOOneWireBus::write_bit_(bool bit) {
// recovery time: t_rec: min=1µs
// ds18b20 appears to read the bus after roughly 14µs
uint32_t delay0 = bit ? 6 : 60;
uint32_t delay1 = bit ? 64 : 10;
uint32_t delay1 = bit ? 59 : 5;
// delay A/C
delayMicroseconds(delay0);
// release bus
this->pin_.digital_write(true);
pin_.digital_write(true);
// delay B/D
delayMicroseconds(delay1);
}
bool HOT IRAM_ATTR GPIOOneWireBus::read_bit_() {
// drive bus low
this->pin_.digital_write(false);
pin_.pin_mode(gpio::FLAG_OUTPUT);
pin_.digital_write(false);
// datasheet says >= 1µs
delayMicroseconds(5);
// note: for reading we'll need very accurate timing, as the
// timing for the digital_read() is tight; according to the datasheet,
// we should read at the end of 16µs starting from the bus low
// typically, the ds18b20 pulls the line high after 11µs for a logical 1
// and 29µs for a logical 0
uint32_t start = micros();
// datasheet says >1µs
delayMicroseconds(2);
// release bus, delay E
this->pin_.pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
pin_.pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
// measure from start value directly, to get best accurate timing no matter
// how long pin_mode/delayMicroseconds took
uint32_t now = micros();
if (now - start < 12)
delayMicroseconds(12 - (now - start));
delayMicroseconds(8);
// sample bus to read bit from peer
bool r = this->pin_.digital_read();
bool r = pin_.digital_read();
// read slot is at least 60µs
delayMicroseconds(50);
// read slot is at least 60µs; get as close to 60µs to spend less time with interrupts locked
now = micros();
if (now - start < 60)
delayMicroseconds(60 - (now - start));
this->pin_.digital_write(true);
this->pin_.pin_mode(gpio::FLAG_OUTPUT);
return r;
}
void IRAM_ATTR GPIOOneWireBus::write8(uint8_t val) {
InterruptLock lock;
for (uint8_t i = 0; i < 8; i++) {
this->write_bit_(bool((1u << i) & val));
}
}
void IRAM_ATTR GPIOOneWireBus::write64(uint64_t val) {
InterruptLock lock;
for (uint8_t i = 0; i < 64; i++) {
this->write_bit_(bool((1ULL << i) & val));
}
}
uint8_t IRAM_ATTR GPIOOneWireBus::read8() {
InterruptLock lock;
uint8_t ret = 0;
for (uint8_t i = 0; i < 8; i++)
for (uint8_t i = 0; i < 8; i++) {
ret |= (uint8_t(this->read_bit_()) << i);
}
return ret;
}
uint64_t IRAM_ATTR GPIOOneWireBus::read64() {
InterruptLock lock;
uint64_t ret = 0;
for (uint8_t i = 0; i < 8; i++) {
ret |= (uint64_t(this->read_bit_()) << i);
@@ -145,7 +144,6 @@ void GPIOOneWireBus::reset_search() {
}
uint64_t IRAM_ATTR GPIOOneWireBus::search_int() {
InterruptLock lock;
if (this->last_device_flag_)
return 0u;

View File

@@ -18,6 +18,7 @@ class GPIOOneWireBus : public one_wire::OneWireBus, public Component {
this->pin_ = pin->to_isr();
}
bool reset() override;
void write8(uint8_t val) override;
void write64(uint64_t val) override;
uint8_t read8() override;
@@ -30,12 +31,10 @@ class GPIOOneWireBus : public one_wire::OneWireBus, public Component {
bool last_device_flag_{false};
uint64_t address_;
int reset_int() override;
void reset_search() override;
uint64_t search_int() override;
void write_bit_(bool bit);
bool read_bit_();
bool read_bit_(uint32_t *t);
};
} // namespace gpio

View File

@@ -25,7 +25,6 @@ GPS = gps_ns.class_("GPS", cg.Component, uart.UARTDevice)
GPSListener = gps_ns.class_("GPSListener")
CONF_GPS_ID = "gps_id"
CONF_HDOP = "hdop"
MULTI_CONF = True
CONFIG_SCHEMA = cv.All(
cv.Schema(
@@ -41,7 +40,7 @@ CONFIG_SCHEMA = cv.All(
),
cv.Optional(CONF_SPEED): sensor.sensor_schema(
unit_of_measurement=UNIT_KILOMETER_PER_HOUR,
accuracy_decimals=3,
accuracy_decimals=6,
),
cv.Optional(CONF_COURSE): sensor.sensor_schema(
unit_of_measurement=UNIT_DEGREES,
@@ -49,16 +48,12 @@ CONFIG_SCHEMA = cv.All(
),
cv.Optional(CONF_ALTITUDE): sensor.sensor_schema(
unit_of_measurement=UNIT_METER,
accuracy_decimals=2,
accuracy_decimals=1,
),
cv.Optional(CONF_SATELLITES): sensor.sensor_schema(
accuracy_decimals=0,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_HDOP): sensor.sensor_schema(
accuracy_decimals=3,
state_class=STATE_CLASS_MEASUREMENT,
),
}
)
.extend(cv.polling_component_schema("20s"))
@@ -97,9 +92,5 @@ async def to_code(config):
sens = await sensor.new_sensor(config[CONF_SATELLITES])
cg.add(var.set_satellites_sensor(sens))
if hdop_config := config.get(CONF_HDOP):
sens = await sensor.new_sensor(hdop_config)
cg.add(var.set_hdop_sensor(sens))
# https://platformio.org/lib/show/1655/TinyGPSPlus
cg.add_library("mikalhart/TinyGPSPlus", "1.0.2")

View File

@@ -28,9 +28,6 @@ void GPS::update() {
if (this->satellites_sensor_ != nullptr)
this->satellites_sensor_->publish_state(this->satellites_);
if (this->hdop_sensor_ != nullptr)
this->hdop_sensor_->publish_state(this->hdop_);
}
void GPS::loop() {
@@ -47,23 +44,23 @@ void GPS::loop() {
if (tiny_gps_.speed.isUpdated()) {
this->speed_ = tiny_gps_.speed.kmph();
ESP_LOGD(TAG, "Speed: %.3f km/h", this->speed_);
ESP_LOGD(TAG, "Speed:");
ESP_LOGD(TAG, " %f km/h", this->speed_);
}
if (tiny_gps_.course.isUpdated()) {
this->course_ = tiny_gps_.course.deg();
ESP_LOGD(TAG, "Course: %.2f °", this->course_);
ESP_LOGD(TAG, "Course:");
ESP_LOGD(TAG, " %f °", this->course_);
}
if (tiny_gps_.altitude.isUpdated()) {
this->altitude_ = tiny_gps_.altitude.meters();
ESP_LOGD(TAG, "Altitude: %.2f m", this->altitude_);
ESP_LOGD(TAG, "Altitude:");
ESP_LOGD(TAG, " %f m", this->altitude_);
}
if (tiny_gps_.satellites.isUpdated()) {
this->satellites_ = tiny_gps_.satellites.value();
ESP_LOGD(TAG, "Satellites: %d", this->satellites_);
}
if (tiny_gps_.hdop.isUpdated()) {
this->hdop_ = tiny_gps_.hdop.hdop();
ESP_LOGD(TAG, "HDOP: %.3f", this->hdop_);
ESP_LOGD(TAG, "Satellites:");
ESP_LOGD(TAG, " %d", this->satellites_);
}
for (auto *listener : this->listeners_)

View File

@@ -33,7 +33,6 @@ class GPS : public PollingComponent, public uart::UARTDevice {
void set_course_sensor(sensor::Sensor *course_sensor) { course_sensor_ = course_sensor; }
void set_altitude_sensor(sensor::Sensor *altitude_sensor) { altitude_sensor_ = altitude_sensor; }
void set_satellites_sensor(sensor::Sensor *satellites_sensor) { satellites_sensor_ = satellites_sensor; }
void set_hdop_sensor(sensor::Sensor *hdop_sensor) { hdop_sensor_ = hdop_sensor; }
void register_listener(GPSListener *listener) {
listener->parent_ = this;
@@ -47,13 +46,12 @@ class GPS : public PollingComponent, public uart::UARTDevice {
TinyGPSPlus &get_tiny_gps() { return this->tiny_gps_; }
protected:
float latitude_ = NAN;
float longitude_ = NAN;
float speed_ = NAN;
float course_ = NAN;
float altitude_ = NAN;
int satellites_ = 0;
double hdop_ = NAN;
float latitude_ = -1;
float longitude_ = -1;
float speed_ = -1;
float course_ = -1;
float altitude_ = -1;
int satellites_ = -1;
sensor::Sensor *latitude_sensor_{nullptr};
sensor::Sensor *longitude_sensor_{nullptr};
@@ -61,7 +59,6 @@ class GPS : public PollingComponent, public uart::UARTDevice {
sensor::Sensor *course_sensor_{nullptr};
sensor::Sensor *altitude_sensor_{nullptr};
sensor::Sensor *satellites_sensor_{nullptr};
sensor::Sensor *hdop_sensor_{nullptr};
bool has_time_{false};
TinyGPSPlus tiny_gps_;

View File

@@ -1,7 +1,7 @@
import esphome.codegen as cg
from esphome.components import climate_ir
import esphome.config_validation as cv
from esphome.const import CONF_MODEL
from esphome.const import CONF_ID, CONF_MODEL
CODEOWNERS = ["@orestismers"]
@@ -21,13 +21,16 @@ MODELS = {
"yag": Model.GREE_YAG,
}
CONFIG_SCHEMA = climate_ir.climare_ir_with_receiver_schema(GreeClimate).extend(
CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(GreeClimate),
cv.Required(CONF_MODEL): cv.enum(MODELS),
}
)
async def to_code(config):
var = await climate_ir.new_climate_ir(config)
var = cg.new_Pvariable(config[CONF_ID])
cg.add(var.set_model(config[CONF_MODEL]))
await climate_ir.register_climate_ir(var, config)

View File

@@ -30,7 +30,6 @@ from esphome.const import (
CONF_VISUAL,
CONF_WIFI,
)
from esphome.cpp_generator import MockObjClass
import esphome.final_validate as fv
_LOGGER = logging.getLogger(__name__)
@@ -186,46 +185,42 @@ def validate_visual(config):
return config
def _base_config_schema(class_: MockObjClass) -> cv.Schema:
return (
climate.climate_schema(class_)
.extend(
{
cv.Optional(CONF_SUPPORTED_MODES): cv.ensure_list(
cv.enum(SUPPORTED_CLIMATE_MODES_OPTIONS, upper=True)
),
cv.Optional(
CONF_SUPPORTED_SWING_MODES,
default=[
"VERTICAL",
"HORIZONTAL",
"BOTH",
],
): cv.ensure_list(cv.enum(SUPPORTED_SWING_MODES_OPTIONS, upper=True)),
cv.Optional(CONF_WIFI_SIGNAL, default=False): cv.boolean,
cv.Optional(CONF_DISPLAY): cv.boolean,
cv.Optional(
CONF_ANSWER_TIMEOUT,
): cv.positive_time_period_milliseconds,
cv.Optional(CONF_ON_STATUS_MESSAGE): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
StatusMessageTrigger
),
}
),
}
)
.extend(uart.UART_DEVICE_SCHEMA)
.extend(cv.COMPONENT_SCHEMA)
BASE_CONFIG_SCHEMA = (
climate.CLIMATE_SCHEMA.extend(
{
cv.Optional(CONF_SUPPORTED_MODES): cv.ensure_list(
cv.enum(SUPPORTED_CLIMATE_MODES_OPTIONS, upper=True)
),
cv.Optional(
CONF_SUPPORTED_SWING_MODES,
default=[
"VERTICAL",
"HORIZONTAL",
"BOTH",
],
): cv.ensure_list(cv.enum(SUPPORTED_SWING_MODES_OPTIONS, upper=True)),
cv.Optional(CONF_WIFI_SIGNAL, default=False): cv.boolean,
cv.Optional(CONF_DISPLAY): cv.boolean,
cv.Optional(
CONF_ANSWER_TIMEOUT,
): cv.positive_time_period_milliseconds,
cv.Optional(CONF_ON_STATUS_MESSAGE): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(StatusMessageTrigger),
}
),
}
)
.extend(uart.UART_DEVICE_SCHEMA)
.extend(cv.COMPONENT_SCHEMA)
)
CONFIG_SCHEMA = cv.All(
cv.typed_schema(
{
PROTOCOL_SMARTAIR2: _base_config_schema(Smartair2Climate).extend(
PROTOCOL_SMARTAIR2: BASE_CONFIG_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(Smartair2Climate),
cv.Optional(
CONF_ALTERNATIVE_SWING_CONTROL, default=False
): cv.boolean,
@@ -237,8 +232,9 @@ CONFIG_SCHEMA = cv.All(
),
}
),
PROTOCOL_HON: _base_config_schema(HonClimate).extend(
PROTOCOL_HON: BASE_CONFIG_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(HonClimate),
cv.Optional(
CONF_CONTROL_METHOD, default="SET_GROUP_PARAMETERS"
): cv.ensure_list(
@@ -468,9 +464,10 @@ FINAL_VALIDATE_SCHEMA = _final_validate
async def to_code(config):
cg.add(haier_ns.init_haier_protocol_logging())
var = await climate.new_climate(config)
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await uart.register_uart_device(var, config)
await climate.register_climate(var, config)
cg.add(var.set_send_wifi(config[CONF_WIFI_SIGNAL]))
if CONF_CONTROL_METHOD in config:

View File

@@ -30,28 +30,25 @@ DECAY_MODE_OPTIONS = {
# Actions
BrakeAction = hbridge_ns.class_("BrakeAction", automation.Action)
CONFIG_SCHEMA = (
fan.fan_schema(HBridgeFan)
.extend(
{
cv.Required(CONF_PIN_A): cv.use_id(output.FloatOutput),
cv.Required(CONF_PIN_B): cv.use_id(output.FloatOutput),
cv.Optional(CONF_DECAY_MODE, default="SLOW"): cv.enum(
DECAY_MODE_OPTIONS, upper=True
),
cv.Optional(CONF_SPEED_COUNT, default=100): cv.int_range(min=1),
cv.Optional(CONF_ENABLE_PIN): cv.use_id(output.FloatOutput),
cv.Optional(CONF_PRESET_MODES): validate_preset_modes,
}
)
.extend(cv.COMPONENT_SCHEMA)
)
CONFIG_SCHEMA = fan.FAN_SCHEMA.extend(
{
cv.GenerateID(CONF_ID): cv.declare_id(HBridgeFan),
cv.Required(CONF_PIN_A): cv.use_id(output.FloatOutput),
cv.Required(CONF_PIN_B): cv.use_id(output.FloatOutput),
cv.Optional(CONF_DECAY_MODE, default="SLOW"): cv.enum(
DECAY_MODE_OPTIONS, upper=True
),
cv.Optional(CONF_SPEED_COUNT, default=100): cv.int_range(min=1),
cv.Optional(CONF_ENABLE_PIN): cv.use_id(output.FloatOutput),
cv.Optional(CONF_PRESET_MODES): validate_preset_modes,
}
).extend(cv.COMPONENT_SCHEMA)
@automation.register_action(
"fan.hbridge.brake",
BrakeAction,
maybe_simple_id({cv.GenerateID(): cv.use_id(HBridgeFan)}),
maybe_simple_id({cv.Required(CONF_ID): cv.use_id(HBridgeFan)}),
)
async def fan_hbridge_brake_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID])
@@ -59,12 +56,13 @@ async def fan_hbridge_brake_to_code(config, action_id, template_arg, args):
async def to_code(config):
var = await fan.new_fan(
config,
var = cg.new_Pvariable(
config[CONF_ID],
config[CONF_SPEED_COUNT],
config[CONF_DECAY_MODE],
)
await cg.register_component(var, config)
await fan.register_fan(var, config)
pin_a_ = await cg.get_variable(config[CONF_PIN_A])
cg.add(var.set_pin_a(pin_a_))
pin_b_ = await cg.get_variable(config[CONF_PIN_B])

View File

@@ -1,17 +1,17 @@
import esphome.codegen as cg
from esphome.components import cover, uart
import esphome.config_validation as cv
from esphome.const import CONF_CLOSE_DURATION, CONF_OPEN_DURATION
from esphome.const import CONF_CLOSE_DURATION, CONF_ID, CONF_OPEN_DURATION
he60r_ns = cg.esphome_ns.namespace("he60r")
HE60rCover = he60r_ns.class_("HE60rCover", cover.Cover, cg.Component)
CONFIG_SCHEMA = (
cover.cover_schema(HE60rCover)
.extend(uart.UART_DEVICE_SCHEMA)
cover.COVER_SCHEMA.extend(uart.UART_DEVICE_SCHEMA)
.extend(cv.COMPONENT_SCHEMA)
.extend(
{
cv.GenerateID(): cv.declare_id(HE60rCover),
cv.Optional(
CONF_OPEN_DURATION, default="15s"
): cv.positive_time_period_milliseconds,
@@ -34,8 +34,9 @@ FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema(
async def to_code(config):
var = await cover.new_cover(config)
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await cover.register_cover(var, config)
await uart.register_uart_device(var, config)
cg.add(var.set_close_duration(config[CONF_CLOSE_DURATION]))

View File

@@ -2,6 +2,7 @@ import esphome.codegen as cg
from esphome.components import climate_ir
import esphome.config_validation as cv
from esphome.const import (
CONF_ID,
CONF_MAX_TEMPERATURE,
CONF_MIN_TEMPERATURE,
CONF_PROTOCOL,
@@ -97,8 +98,9 @@ VERTICAL_DIRECTIONS = {
}
CONFIG_SCHEMA = cv.All(
climate_ir.climare_ir_with_receiver_schema(HeatpumpIRClimate).extend(
climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(HeatpumpIRClimate),
cv.Required(CONF_PROTOCOL): cv.enum(PROTOCOLS),
cv.Required(CONF_HORIZONTAL_DEFAULT): cv.enum(HORIZONTAL_DIRECTIONS),
cv.Required(CONF_VERTICAL_DEFAULT): cv.enum(VERTICAL_DIRECTIONS),
@@ -110,8 +112,8 @@ CONFIG_SCHEMA = cv.All(
)
async def to_code(config):
var = await climate_ir.new_climate_ir(config)
def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
if CONF_VISUAL not in config:
config[CONF_VISUAL] = {}
visual = config[CONF_VISUAL]
@@ -119,6 +121,7 @@ async def to_code(config):
visual[CONF_MAX_TEMPERATURE] = config[CONF_MAX_TEMPERATURE]
if CONF_MIN_TEMPERATURE not in visual:
visual[CONF_MIN_TEMPERATURE] = config[CONF_MIN_TEMPERATURE]
yield climate_ir.register_climate_ir(var, config)
cg.add(var.set_protocol(config[CONF_PROTOCOL]))
cg.add(var.set_horizontal_default(config[CONF_HORIZONTAL_DEFAULT]))
cg.add(var.set_vertical_default(config[CONF_VERTICAL_DEFAULT]))

View File

@@ -1,13 +1,20 @@
import esphome.codegen as cg
from esphome.components import climate_ir
import esphome.config_validation as cv
from esphome.const import CONF_ID
AUTO_LOAD = ["climate_ir"]
hitachi_ac344_ns = cg.esphome_ns.namespace("hitachi_ac344")
HitachiClimate = hitachi_ac344_ns.class_("HitachiClimate", climate_ir.ClimateIR)
CONFIG_SCHEMA = climate_ir.climare_ir_with_receiver_schema(HitachiClimate)
CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(HitachiClimate),
}
)
async def to_code(config):
await climate_ir.new_climate_ir(config)
var = cg.new_Pvariable(config[CONF_ID])
await climate_ir.register_climate_ir(var, config)

View File

@@ -1,13 +1,20 @@
import esphome.codegen as cg
from esphome.components import climate_ir
import esphome.config_validation as cv
from esphome.const import CONF_ID
AUTO_LOAD = ["climate_ir"]
hitachi_ac424_ns = cg.esphome_ns.namespace("hitachi_ac424")
HitachiClimate = hitachi_ac424_ns.class_("HitachiClimate", climate_ir.ClimateIR)
CONFIG_SCHEMA = climate_ir.climare_ir_with_receiver_schema(HitachiClimate)
CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(HitachiClimate),
}
)
async def to_code(config):
await climate_ir.new_climate_ir(config)
var = cg.new_Pvariable(config[CONF_ID])
await climate_ir.register_climate_ir(var, config)

View File

@@ -16,17 +16,14 @@ HttpRequestUpdate = http_request_ns.class_(
CONF_OTA_ID = "ota_id"
CONFIG_SCHEMA = (
update.update_schema(HttpRequestUpdate)
.extend(
{
cv.GenerateID(CONF_OTA_ID): cv.use_id(OtaHttpRequestComponent),
cv.GenerateID(CONF_HTTP_REQUEST_ID): cv.use_id(HttpRequestComponent),
cv.Required(CONF_SOURCE): cv.url,
}
)
.extend(cv.polling_component_schema("6h"))
)
CONFIG_SCHEMA = update.UPDATE_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(HttpRequestUpdate),
cv.GenerateID(CONF_OTA_ID): cv.use_id(OtaHttpRequestComponent),
cv.GenerateID(CONF_HTTP_REQUEST_ID): cv.use_id(HttpRequestComponent),
cv.Required(CONF_SOURCE): cv.url,
}
).extend(cv.polling_component_schema("6h"))
async def to_code(config):

View File

@@ -2,7 +2,7 @@ from esphome import pins
import esphome.codegen as cg
from esphome.components import esp32, media_player
import esphome.config_validation as cv
from esphome.const import CONF_MODE
from esphome.const import CONF_ID, CONF_MODE
from .. import (
CONF_I2S_AUDIO_ID,
@@ -57,17 +57,16 @@ def validate_esp32_variant(config):
CONFIG_SCHEMA = cv.All(
cv.typed_schema(
{
"internal": media_player.media_player_schema(I2SAudioMediaPlayer)
.extend(
"internal": media_player.MEDIA_PLAYER_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(I2SAudioMediaPlayer),
cv.GenerateID(CONF_I2S_AUDIO_ID): cv.use_id(I2SAudioComponent),
cv.Required(CONF_MODE): cv.enum(INTERNAL_DAC_OPTIONS, lower=True),
}
)
.extend(cv.COMPONENT_SCHEMA),
"external": media_player.media_player_schema(I2SAudioMediaPlayer)
.extend(
).extend(cv.COMPONENT_SCHEMA),
"external": media_player.MEDIA_PLAYER_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(I2SAudioMediaPlayer),
cv.GenerateID(CONF_I2S_AUDIO_ID): cv.use_id(I2SAudioComponent),
cv.Required(
CONF_I2S_DOUT_PIN
@@ -80,8 +79,7 @@ CONFIG_SCHEMA = cv.All(
*I2C_COMM_FMT_OPTIONS, lower=True
),
}
)
.extend(cv.COMPONENT_SCHEMA),
).extend(cv.COMPONENT_SCHEMA),
},
key=CONF_DAC_TYPE,
),
@@ -99,8 +97,9 @@ FINAL_VALIDATE_SCHEMA = _final_validate
async def to_code(config):
var = await media_player.new_media_player(config)
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await media_player.register_media_player(var, config)
await cg.register_parented(var, config[CONF_I2S_AUDIO_ID])

View File

@@ -30,7 +30,6 @@ DEPENDENCIES = ["i2s_audio"]
CONF_ADC_PIN = "adc_pin"
CONF_ADC_TYPE = "adc_type"
CONF_CORRECT_DC_OFFSET = "correct_dc_offset"
CONF_PDM = "pdm"
I2SAudioMicrophone = i2s_audio_ns.class_(
@@ -89,13 +88,10 @@ BASE_SCHEMA = microphone.MICROPHONE_SCHEMA.extend(
default_sample_rate=16000,
default_channel=CONF_RIGHT,
default_bits_per_sample="32bit",
).extend(
{
cv.Optional(CONF_CORRECT_DC_OFFSET, default=False): cv.boolean,
}
)
).extend(cv.COMPONENT_SCHEMA)
CONFIG_SCHEMA = cv.All(
cv.typed_schema(
{
@@ -144,5 +140,3 @@ async def to_code(config):
else:
cg.add(var.set_din_pin(config[CONF_I2S_DIN_PIN]))
cg.add(var.set_pdm(config[CONF_PDM]))
cg.add(var.set_correct_dc_offset(config[CONF_CORRECT_DC_OFFSET]))

View File

@@ -12,8 +12,6 @@
#include "esphome/core/hal.h"
#include "esphome/core/log.h"
#include "esphome/components/audio/audio.h"
namespace esphome {
namespace i2s_audio {
@@ -24,9 +22,6 @@ static const uint32_t READ_DURATION_MS = 16;
static const size_t TASK_STACK_SIZE = 4096;
static const ssize_t TASK_PRIORITY = 23;
// Use an exponential moving average to correct a DC offset with weight factor 1/1000
static const int32_t DC_OFFSET_MOVING_AVERAGE_COEFFICIENT_DENOMINATOR = 1000;
static const char *const TAG = "i2s_audio.microphone";
enum MicrophoneEventGroupBits : uint32_t {
@@ -75,11 +70,21 @@ void I2SAudioMicrophone::setup() {
this->mark_failed();
return;
}
this->configure_stream_settings_();
}
void I2SAudioMicrophone::configure_stream_settings_() {
void I2SAudioMicrophone::start() {
if (this->is_failed())
return;
xSemaphoreTake(this->active_listeners_semaphore_, 0);
}
bool I2SAudioMicrophone::start_driver_() {
if (!this->parent_->try_lock()) {
return false; // Waiting for another i2s to return lock
}
esp_err_t err;
uint8_t channel_count = 1;
#ifdef USE_I2S_LEGACY
uint8_t bits_per_sample = this->bits_per_sample_;
@@ -88,10 +93,10 @@ void I2SAudioMicrophone::configure_stream_settings_() {
channel_count = 2;
}
#else
uint8_t bits_per_sample = 16;
if (this->slot_bit_width_ != I2S_SLOT_BIT_WIDTH_AUTO) {
bits_per_sample = this->slot_bit_width_;
if (this->slot_bit_width_ == I2S_SLOT_BIT_WIDTH_AUTO) {
this->slot_bit_width_ = I2S_SLOT_BIT_WIDTH_16BIT;
}
uint8_t bits_per_sample = this->slot_bit_width_;
if (this->slot_mode_ == I2S_SLOT_MODE_STEREO) {
channel_count = 2;
@@ -109,26 +114,6 @@ void I2SAudioMicrophone::configure_stream_settings_() {
}
#endif
if (this->pdm_) {
bits_per_sample = 16; // PDM mics are always 16 bits per sample
}
this->audio_stream_info_ = audio::AudioStreamInfo(bits_per_sample, channel_count, this->sample_rate_);
}
void I2SAudioMicrophone::start() {
if (this->is_failed())
return;
xSemaphoreTake(this->active_listeners_semaphore_, 0);
}
bool I2SAudioMicrophone::start_driver_() {
if (!this->parent_->try_lock()) {
return false; // Waiting for another i2s to return lock
}
esp_err_t err;
#ifdef USE_I2S_LEGACY
i2s_driver_config_t config = {
.mode = (i2s_mode_t) (this->i2s_mode_ | I2S_MODE_RX),
@@ -217,6 +202,8 @@ bool I2SAudioMicrophone::start_driver_() {
i2s_std_gpio_config_t pin_config = this->parent_->get_pin_config();
#if SOC_I2S_SUPPORTS_PDM_RX
if (this->pdm_) {
bits_per_sample = 16; // PDM mics are always 16 bits per sample with the IDF 5 driver
i2s_pdm_rx_clk_config_t clk_cfg = {
.sample_rate_hz = this->sample_rate_,
.clk_src = clk_src,
@@ -290,8 +277,10 @@ bool I2SAudioMicrophone::start_driver_() {
}
#endif
this->audio_stream_info_ = audio::AudioStreamInfo(bits_per_sample, channel_count, this->sample_rate_);
this->status_clear_error();
this->configure_stream_settings_(); // redetermine the settings in case some settings were changed after compilation
return true;
}
@@ -372,12 +361,9 @@ void I2SAudioMicrophone::mic_task(void *params) {
samples.resize(bytes_to_read);
size_t bytes_read = this_microphone->read_(samples.data(), bytes_to_read, 2 * pdMS_TO_TICKS(READ_DURATION_MS));
samples.resize(bytes_read);
if (this_microphone->correct_dc_offset_) {
this_microphone->fix_dc_offset_(samples);
}
this_microphone->data_callbacks_.call(samples);
} else {
vTaskDelay(pdMS_TO_TICKS(READ_DURATION_MS));
delay(READ_DURATION_MS);
}
}
}
@@ -387,34 +373,11 @@ void I2SAudioMicrophone::mic_task(void *params) {
xEventGroupSetBits(this_microphone->event_group_, MicrophoneEventGroupBits::TASK_STOPPED);
while (true) {
// Continuously delay until the loop method deletes the task
vTaskDelay(pdMS_TO_TICKS(10));
// Continuously delay until the loop method delete the task
delay(10);
}
}
void I2SAudioMicrophone::fix_dc_offset_(std::vector<uint8_t> &data) {
const size_t bytes_per_sample = this->audio_stream_info_.samples_to_bytes(1);
const uint32_t total_samples = this->audio_stream_info_.bytes_to_samples(data.size());
if (total_samples == 0) {
return;
}
int64_t offset_accumulator = 0;
for (uint32_t sample_index = 0; sample_index < total_samples; ++sample_index) {
const uint32_t byte_index = sample_index * bytes_per_sample;
int32_t sample = audio::unpack_audio_sample_to_q31(&data[byte_index], bytes_per_sample);
offset_accumulator += sample;
sample -= this->dc_offset_;
audio::pack_q31_as_audio_sample(sample, &data[byte_index], bytes_per_sample);
}
const int32_t new_offset = offset_accumulator / total_samples;
this->dc_offset_ = new_offset / DC_OFFSET_MOVING_AVERAGE_COEFFICIENT_DENOMINATOR +
(DC_OFFSET_MOVING_AVERAGE_COEFFICIENT_DENOMINATOR - 1) * this->dc_offset_ /
DC_OFFSET_MOVING_AVERAGE_COEFFICIENT_DENOMINATOR;
}
size_t I2SAudioMicrophone::read_(uint8_t *buf, size_t len, TickType_t ticks_to_wait) {
size_t bytes_read = 0;
#ifdef USE_I2S_LEGACY

View File

@@ -7,10 +7,8 @@
#include "esphome/components/microphone/microphone.h"
#include "esphome/core/component.h"
#include <freertos/FreeRTOS.h>
#include <freertos/event_groups.h>
#include <freertos/semphr.h>
#include <freertos/task.h>
namespace esphome {
namespace i2s_audio {
@@ -22,9 +20,6 @@ class I2SAudioMicrophone : public I2SAudioIn, public microphone::Microphone, pub
void stop() override;
void loop() override;
void set_correct_dc_offset(bool correct_dc_offset) { this->correct_dc_offset_ = correct_dc_offset; }
#ifdef USE_I2S_LEGACY
void set_din_pin(int8_t pin) { this->din_pin_ = pin; }
#else
@@ -46,16 +41,8 @@ class I2SAudioMicrophone : public I2SAudioIn, public microphone::Microphone, pub
bool start_driver_();
void stop_driver_();
/// @brief Attempts to correct a microphone DC offset; e.g., a microphones silent level is offset from 0. Applies a
/// correction offset that is updated using an exponential moving average for all samples away from 0.
/// @param data
void fix_dc_offset_(std::vector<uint8_t> &data);
size_t read_(uint8_t *buf, size_t len, TickType_t ticks_to_wait);
/// @brief Sets the Microphone ``audio_stream_info_`` member variable to the configured I2S settings.
void configure_stream_settings_();
static void mic_task(void *params);
SemaphoreHandle_t active_listeners_semaphore_{nullptr};
@@ -74,9 +61,6 @@ class I2SAudioMicrophone : public I2SAudioIn, public microphone::Microphone, pub
i2s_chan_handle_t rx_handle_;
#endif
bool pdm_{false};
bool correct_dc_offset_;
int32_t dc_offset_{0};
};
} // namespace i2s_audio

View File

@@ -629,16 +629,7 @@ esp_err_t I2SAudioSpeaker::start_i2s_driver_(audio::AudioStreamInfo &audio_strea
std_slot_cfg =
I2S_STD_MSB_SLOT_DEFAULT_CONFIG((i2s_data_bit_width_t) audio_stream_info.get_bits_per_sample(), slot_mode);
}
#ifdef USE_ESP32_VARIANT_ESP32
// There seems to be a bug on the ESP32 (non-variant) platform where setting the slot bit width higher then the bits
// per sample causes the audio to play too fast. Setting the ws_width to the configured slot bit width seems to
// make it play at the correct speed while sending more bits per slot.
if (this->slot_bit_width_ != I2S_SLOT_BIT_WIDTH_AUTO) {
std_slot_cfg.ws_width = static_cast<uint32_t>(this->slot_bit_width_);
}
#else
std_slot_cfg.slot_bit_width = this->slot_bit_width_;
#endif
std_slot_cfg.slot_mask = slot_mask;
pin_config.dout = this->dout_pin_;

View File

@@ -1,5 +1,3 @@
import enum
import esphome.automation as auto
import esphome.codegen as cg
from esphome.components import mqtt, power_supply, web_server
@@ -15,18 +13,15 @@ from esphome.const import (
CONF_COLOR_TEMPERATURE,
CONF_DEFAULT_TRANSITION_LENGTH,
CONF_EFFECTS,
CONF_ENTITY_CATEGORY,
CONF_FLASH_TRANSITION_LENGTH,
CONF_GAMMA_CORRECT,
CONF_GREEN,
CONF_ICON,
CONF_ID,
CONF_INITIAL_STATE,
CONF_MQTT_ID,
CONF_ON_STATE,
CONF_ON_TURN_OFF,
CONF_ON_TURN_ON,
CONF_OUTPUT_ID,
CONF_POWER_SUPPLY,
CONF_RED,
CONF_RESTORE_MODE,
@@ -38,7 +33,6 @@ from esphome.const import (
CONF_WHITE,
)
from esphome.core import coroutine_with_priority
from esphome.cpp_generator import MockObjClass
from esphome.cpp_helpers import setup_entity
from .automation import LIGHT_STATE_SCHEMA
@@ -147,51 +141,6 @@ ADDRESSABLE_LIGHT_SCHEMA = RGB_LIGHT_SCHEMA.extend(
)
class LightType(enum.IntEnum):
"""Light type enum."""
BINARY = 0
BRIGHTNESS_ONLY = 1
RGB = 2
ADDRESSABLE = 3
def light_schema(
class_: MockObjClass,
type_: LightType,
*,
entity_category: str = cv.UNDEFINED,
icon: str = cv.UNDEFINED,
default_restore_mode: str = cv.UNDEFINED,
) -> cv.Schema:
schema = {
cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(class_),
}
for key, default, validator in [
(CONF_ENTITY_CATEGORY, entity_category, cv.entity_category),
(CONF_ICON, icon, cv.icon),
(
CONF_RESTORE_MODE,
default_restore_mode,
cv.enum(RESTORE_MODES, upper=True, space="_"),
),
]:
if default is not cv.UNDEFINED:
schema[cv.Optional(key, default=default)] = validator
if type_ == LightType.BINARY:
return BINARY_LIGHT_SCHEMA.extend(schema)
if type_ == LightType.BRIGHTNESS_ONLY:
return BRIGHTNESS_ONLY_LIGHT_SCHEMA.extend(schema)
if type_ == LightType.RGB:
return RGB_LIGHT_SCHEMA.extend(schema)
if type_ == LightType.ADDRESSABLE:
return ADDRESSABLE_LIGHT_SCHEMA.extend(schema)
raise ValueError(f"Invalid light type: {type_}")
def validate_color_temperature_channels(value):
if (
CONF_COLD_WHITE_COLOR_TEMPERATURE in value
@@ -274,12 +223,6 @@ async def register_light(output_var, config):
await setup_light_core_(light_var, output_var, config)
async def new_light(config, *args):
output_var = cg.new_Pvariable(config[CONF_OUTPUT_ID], *args)
await register_light(output_var, config)
return output_var
@coroutine_with_priority(100.0)
async def to_code(config):
cg.add_define("USE_LIGHT")

View File

@@ -4,8 +4,6 @@ import esphome.codegen as cg
from esphome.components import mqtt, web_server
import esphome.config_validation as cv
from esphome.const import (
CONF_ENTITY_CATEGORY,
CONF_ICON,
CONF_ID,
CONF_MQTT_ID,
CONF_ON_LOCK,
@@ -14,7 +12,6 @@ from esphome.const import (
CONF_WEB_SERVER,
)
from esphome.core import CORE, coroutine_with_priority
from esphome.cpp_generator import MockObjClass
from esphome.cpp_helpers import setup_entity
CODEOWNERS = ["@esphome/core"]
@@ -34,19 +31,7 @@ LockCondition = lock_ns.class_("LockCondition", Condition)
LockLockTrigger = lock_ns.class_("LockLockTrigger", automation.Trigger.template())
LockUnlockTrigger = lock_ns.class_("LockUnlockTrigger", automation.Trigger.template())
LockState = lock_ns.enum("LockState")
LOCK_STATES = {
"LOCKED": LockState.LOCK_STATE_LOCKED,
"UNLOCKED": LockState.LOCK_STATE_UNLOCKED,
"JAMMED": LockState.LOCK_STATE_JAMMED,
"LOCKING": LockState.LOCK_STATE_LOCKING,
"UNLOCKING": LockState.LOCK_STATE_UNLOCKING,
}
validate_lock_state = cv.enum(LOCK_STATES, upper=True)
_LOCK_SCHEMA = (
LOCK_SCHEMA = (
cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA)
.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA)
.extend(
@@ -67,33 +52,7 @@ _LOCK_SCHEMA = (
)
def lock_schema(
class_: MockObjClass = cv.UNDEFINED,
*,
icon: str = cv.UNDEFINED,
entity_category: str = cv.UNDEFINED,
) -> cv.Schema:
schema = {}
if class_ is not cv.UNDEFINED:
schema[cv.GenerateID()] = cv.declare_id(class_)
for key, default, validator in [
(CONF_ICON, icon, cv.icon),
(CONF_ENTITY_CATEGORY, entity_category, cv.entity_category),
]:
if default is not cv.UNDEFINED:
schema[cv.Optional(key, default=default)] = validator
return _LOCK_SCHEMA.extend(schema)
# Remove before 2025.11.0
LOCK_SCHEMA = lock_schema()
LOCK_SCHEMA.add_extra(cv.deprecated_schema_constant("lock"))
async def _setup_lock_core(var, config):
async def setup_lock_core_(var, config):
await setup_entity(var, config)
for conf in config.get(CONF_ON_LOCK, []):
@@ -115,18 +74,12 @@ async def register_lock(var, config):
if not CORE.has_id(config[CONF_ID]):
var = cg.Pvariable(config[CONF_ID], var)
cg.add(cg.App.register_lock(var))
await _setup_lock_core(var, config)
async def new_lock(config, *args):
var = cg.new_Pvariable(config[CONF_ID], *args)
await register_lock(var, config)
return var
await setup_lock_core_(var, config)
LOCK_ACTION_SCHEMA = maybe_simple_id(
{
cv.GenerateID(CONF_ID): cv.use_id(Lock),
cv.Required(CONF_ID): cv.use_id(Lock),
}
)

View File

@@ -1,8 +1,8 @@
#pragma once
#include "esphome/components/lock/lock.h"
#include "esphome/core/automation.h"
#include "esphome/core/component.h"
#include "esphome/core/automation.h"
#include "esphome/components/lock/lock.h"
namespace esphome {
namespace lock {
@@ -72,5 +72,16 @@ class LockUnlockTrigger : public Trigger<> {
}
};
template<typename... Ts> class LockPublishAction : public Action<Ts...> {
public:
LockPublishAction(Lock *a_lock) : lock_(a_lock) {}
TEMPLATABLE_VALUE(LockState, state)
void play(Ts... x) override { this->lock_->publish_state(this->state_.value(x...)); }
protected:
Lock *lock_;
};
} // namespace lock
} // namespace esphome

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