mirror of
https://github.com/esphome/esphome.git
synced 2026-09-23 10:48:04 +08:00
@@ -49,7 +49,7 @@ jobs:
|
||||
with:
|
||||
python-version: "3.10"
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3.10.0
|
||||
uses: docker/setup-buildx-action@v3.11.1
|
||||
|
||||
- name: Set TAG
|
||||
run: |
|
||||
|
||||
@@ -214,17 +214,51 @@ jobs:
|
||||
if: matrix.os == 'windows-latest'
|
||||
run: |
|
||||
./venv/Scripts/activate
|
||||
pytest -vv --cov-report=xml --tb=native -n auto tests
|
||||
pytest -vv --cov-report=xml --tb=native -n auto tests --ignore=tests/integration/
|
||||
- name: Run pytest
|
||||
if: matrix.os == 'ubuntu-latest' || matrix.os == 'macOS-latest'
|
||||
run: |
|
||||
. venv/bin/activate
|
||||
pytest -vv --cov-report=xml --tb=native -n auto tests
|
||||
pytest -vv --cov-report=xml --tb=native -n auto tests --ignore=tests/integration/
|
||||
- name: Upload coverage to Codecov
|
||||
uses: codecov/codecov-action@v5.4.3
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
integration-tests:
|
||||
name: Run integration tests
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- common
|
||||
steps:
|
||||
- name: Check out code from GitHub
|
||||
uses: actions/checkout@v4.2.2
|
||||
- name: Set up Python 3.13
|
||||
id: python
|
||||
uses: actions/setup-python@v5.6.0
|
||||
with:
|
||||
python-version: "3.13"
|
||||
- name: Restore Python virtual environment
|
||||
id: cache-venv
|
||||
uses: actions/cache@v4.2.3
|
||||
with:
|
||||
path: venv
|
||||
key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-venv-${{ needs.common.outputs.cache-key }}
|
||||
- name: Create Python virtual environment
|
||||
if: steps.cache-venv.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
python -m venv venv
|
||||
. venv/bin/activate
|
||||
python --version
|
||||
pip install -r requirements.txt -r requirements_test.txt
|
||||
pip install -e .
|
||||
- name: Register matcher
|
||||
run: echo "::add-matcher::.github/workflows/matchers/pytest.json"
|
||||
- name: Run integration tests
|
||||
run: |
|
||||
. venv/bin/activate
|
||||
pytest -vv --no-cov --tb=native -n auto tests/integration/
|
||||
|
||||
clang-format:
|
||||
name: Check clang-format
|
||||
runs-on: ubuntu-24.04
|
||||
@@ -494,6 +528,7 @@ jobs:
|
||||
- flake8
|
||||
- pylint
|
||||
- pytest
|
||||
- integration-tests
|
||||
- pyupgrade
|
||||
- clang-tidy
|
||||
- list-components
|
||||
|
||||
@@ -1,28 +1,11 @@
|
||||
---
|
||||
name: Lock
|
||||
name: Lock closed issues and PRs
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "30 0 * * *"
|
||||
- cron: "30 0 * * *" # Run daily at 00:30 UTC
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
|
||||
concurrency:
|
||||
group: lock
|
||||
|
||||
jobs:
|
||||
lock:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: dessant/lock-threads@v5.0.1
|
||||
with:
|
||||
pr-inactive-days: "1"
|
||||
pr-lock-reason: ""
|
||||
exclude-any-pr-labels: keep-open
|
||||
|
||||
issue-inactive-days: "7"
|
||||
issue-lock-reason: ""
|
||||
exclude-any-issue-labels: keep-open
|
||||
uses: esphome/workflows/.github/workflows/lock.yml@main
|
||||
|
||||
@@ -99,7 +99,7 @@ jobs:
|
||||
python-version: "3.10"
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3.10.0
|
||||
uses: docker/setup-buildx-action@v3.11.1
|
||||
|
||||
- name: Log in to docker hub
|
||||
uses: docker/login-action@v3.4.0
|
||||
@@ -178,7 +178,7 @@ jobs:
|
||||
merge-multiple: true
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3.10.0
|
||||
uses: docker/setup-buildx-action@v3.11.1
|
||||
|
||||
- name: Log in to docker hub
|
||||
if: matrix.registry == 'dockerhub'
|
||||
|
||||
+10
-2
@@ -1,10 +1,18 @@
|
||||
---
|
||||
# See https://pre-commit.com for more information
|
||||
# See https://pre-commit.com/hooks.html for more hooks
|
||||
|
||||
ci:
|
||||
autoupdate_commit_msg: 'pre-commit: autoupdate'
|
||||
autoupdate_schedule: weekly
|
||||
autofix_prs: false
|
||||
# Skip hooks that have issues in pre-commit CI environment
|
||||
skip: [pylint, yamllint]
|
||||
|
||||
repos:
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: v0.11.10
|
||||
rev: v0.12.2
|
||||
hooks:
|
||||
# Run the linter.
|
||||
- id: ruff
|
||||
@@ -12,7 +20,7 @@ repos:
|
||||
# Run the formatter.
|
||||
- id: ruff-format
|
||||
- repo: https://github.com/PyCQA/flake8
|
||||
rev: 7.2.0
|
||||
rev: 7.3.0
|
||||
hooks:
|
||||
- id: flake8
|
||||
additional_dependencies:
|
||||
|
||||
+12
@@ -87,6 +87,7 @@ esphome/components/bp1658cj/* @Cossid
|
||||
esphome/components/bp5758d/* @Cossid
|
||||
esphome/components/button/* @esphome/core
|
||||
esphome/components/bytebuffer/* @clydebarrow
|
||||
esphome/components/camera/* @DT-art1 @bdraco
|
||||
esphome/components/canbus/* @danielschramm @mvturnho
|
||||
esphome/components/cap1188/* @mreditor97
|
||||
esphome/components/captive_portal/* @OttoWinter
|
||||
@@ -124,6 +125,7 @@ esphome/components/dht/* @OttoWinter
|
||||
esphome/components/display_menu_base/* @numo68
|
||||
esphome/components/dps310/* @kbx81
|
||||
esphome/components/ds1307/* @badbadc0ffee
|
||||
esphome/components/ds2484/* @mrk-its
|
||||
esphome/components/dsmr/* @glmnet @zuidwijk
|
||||
esphome/components/duty_time/* @dudanov
|
||||
esphome/components/ee895/* @Stock-M
|
||||
@@ -146,6 +148,7 @@ esphome/components/esp32_ble_client/* @jesserockz
|
||||
esphome/components/esp32_ble_server/* @Rapsssito @clydebarrow @jesserockz
|
||||
esphome/components/esp32_camera_web_server/* @ayufan
|
||||
esphome/components/esp32_can/* @Sympatron
|
||||
esphome/components/esp32_hosted/* @swoboda1337
|
||||
esphome/components/esp32_improv/* @jesserockz
|
||||
esphome/components/esp32_rmt/* @jesserockz
|
||||
esphome/components/esp32_rmt_led_strip/* @jesserockz
|
||||
@@ -167,6 +170,7 @@ esphome/components/ft5x06/* @clydebarrow
|
||||
esphome/components/ft63x6/* @gpambrozio
|
||||
esphome/components/gcja5/* @gcormier
|
||||
esphome/components/gdk101/* @Szewcson
|
||||
esphome/components/gl_r01_i2c/* @pkejval
|
||||
esphome/components/globals/* @esphome/core
|
||||
esphome/components/gp2y1010au0f/* @zry98
|
||||
esphome/components/gp8403/* @jesserockz
|
||||
@@ -247,9 +251,11 @@ esphome/components/libretiny_pwm/* @kuba2k2
|
||||
esphome/components/light/* @esphome/core
|
||||
esphome/components/lightwaverf/* @max246
|
||||
esphome/components/lilygo_t5_47/touchscreen/* @jesserockz
|
||||
esphome/components/ln882x/* @lamauny
|
||||
esphome/components/lock/* @esphome/core
|
||||
esphome/components/logger/* @esphome/core
|
||||
esphome/components/logger/select/* @clydebarrow
|
||||
esphome/components/lps22/* @nagisa
|
||||
esphome/components/ltr390/* @latonita @sjtrny
|
||||
esphome/components/ltr501/* @latonita
|
||||
esphome/components/ltr_als_ps/* @latonita
|
||||
@@ -323,6 +329,7 @@ esphome/components/one_wire/* @ssieb
|
||||
esphome/components/online_image/* @clydebarrow @guillempages
|
||||
esphome/components/opentherm/* @olegtarasov
|
||||
esphome/components/openthread/* @mrene
|
||||
esphome/components/opt3001/* @ccutrer
|
||||
esphome/components/ota/* @esphome/core
|
||||
esphome/components/output/* @esphome/core
|
||||
esphome/components/packet_transport/* @clydebarrow
|
||||
@@ -330,6 +337,7 @@ esphome/components/pca6416a/* @Mat931
|
||||
esphome/components/pca9554/* @clydebarrow @hwstar
|
||||
esphome/components/pcf85063/* @brogon
|
||||
esphome/components/pcf8563/* @KoenBreeman
|
||||
esphome/components/pi4ioe5v6408/* @jesserockz
|
||||
esphome/components/pid/* @OttoWinter
|
||||
esphome/components/pipsolar/* @andreashergert1984
|
||||
esphome/components/pm1006/* @habbie
|
||||
@@ -436,6 +444,8 @@ esphome/components/sun/* @OttoWinter
|
||||
esphome/components/sun_gtil2/* @Mat931
|
||||
esphome/components/switch/* @esphome/core
|
||||
esphome/components/switch/binary_sensor/* @ssieb
|
||||
esphome/components/sx126x/* @swoboda1337
|
||||
esphome/components/sx127x/* @swoboda1337
|
||||
esphome/components/syslog/* @clydebarrow
|
||||
esphome/components/t6615/* @tylermenezes
|
||||
esphome/components/tc74/* @sethgirvan
|
||||
@@ -494,6 +504,7 @@ esphome/components/voice_assistant/* @jesserockz @kahrendt
|
||||
esphome/components/wake_on_lan/* @clydebarrow @willwill2will54
|
||||
esphome/components/watchdog/* @oarcher
|
||||
esphome/components/waveshare_epaper/* @clydebarrow
|
||||
esphome/components/web_server/ota/* @esphome/core
|
||||
esphome/components/web_server_base/* @OttoWinter
|
||||
esphome/components/web_server_idf/* @dentra
|
||||
esphome/components/weikai/* @DrCoolZic
|
||||
@@ -520,6 +531,7 @@ esphome/components/xiaomi_lywsd03mmc/* @ahpohl
|
||||
esphome/components/xiaomi_mhoc303/* @drug123
|
||||
esphome/components/xiaomi_mhoc401/* @vevsvevs
|
||||
esphome/components/xiaomi_rtcgq02lm/* @jesserockz
|
||||
esphome/components/xiaomi_xmwsdj04mmc/* @medusalix
|
||||
esphome/components/xl9535/* @mreditor97
|
||||
esphome/components/xpt2046/touchscreen/* @nielsnl68 @numo68
|
||||
esphome/components/xxtea/* @clydebarrow
|
||||
|
||||
@@ -48,7 +48,7 @@ PROJECT_NAME = ESPHome
|
||||
# could be handy for archiving the generated documentation or if some version
|
||||
# control system is used.
|
||||
|
||||
PROJECT_NUMBER = 2025.6.3
|
||||
PROJECT_NUMBER = 2025.7.0
|
||||
|
||||
# Using the PROJECT_BRIEF tag one can provide an optional one line description
|
||||
# for a project that appears at the top of each page and should give viewer a
|
||||
|
||||
+1
-3
@@ -34,11 +34,9 @@ from esphome.const import (
|
||||
CONF_PORT,
|
||||
CONF_SUBSTITUTIONS,
|
||||
CONF_TOPIC,
|
||||
PLATFORM_BK72XX,
|
||||
PLATFORM_ESP32,
|
||||
PLATFORM_ESP8266,
|
||||
PLATFORM_RP2040,
|
||||
PLATFORM_RTL87XX,
|
||||
SECRETS_FILES,
|
||||
)
|
||||
from esphome.core import CORE, EsphomeError, coroutine
|
||||
@@ -354,7 +352,7 @@ def upload_program(config, args, host):
|
||||
if CORE.target_platform in (PLATFORM_RP2040):
|
||||
return upload_using_platformio(config, args.device)
|
||||
|
||||
if CORE.target_platform in (PLATFORM_BK72XX, PLATFORM_RTL87XX):
|
||||
if CORE.is_libretiny:
|
||||
return upload_using_platformio(config, host)
|
||||
|
||||
return 1 # Unknown target platform
|
||||
|
||||
@@ -22,6 +22,7 @@ from esphome.cpp_generator import ( # noqa: F401
|
||||
TemplateArguments,
|
||||
add,
|
||||
add_build_flag,
|
||||
add_build_unflag,
|
||||
add_define,
|
||||
add_global,
|
||||
add_library,
|
||||
@@ -34,6 +35,7 @@ from esphome.cpp_generator import ( # noqa: F401
|
||||
process_lambda,
|
||||
progmem_array,
|
||||
safe_exp,
|
||||
set_cpp_standard,
|
||||
statement,
|
||||
static_const_array,
|
||||
templatable,
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include <cmath>
|
||||
#include <numbers>
|
||||
|
||||
#ifdef USE_ESP8266
|
||||
#include <core_esp8266_waveform.h>
|
||||
@@ -193,18 +194,17 @@ void AcDimmer::setup() {
|
||||
setTimer1Callback(&timer_interrupt);
|
||||
#endif
|
||||
#ifdef USE_ESP32
|
||||
// 80 Divider -> 1 count=1µs
|
||||
dimmer_timer = timerBegin(0, 80, true);
|
||||
timerAttachInterrupt(dimmer_timer, &AcDimmerDataStore::s_timer_intr, true);
|
||||
// timer frequency of 1mhz
|
||||
dimmer_timer = timerBegin(1000000);
|
||||
timerAttachInterrupt(dimmer_timer, &AcDimmerDataStore::s_timer_intr);
|
||||
// For ESP32, we can't use dynamic interval calculation because the timerX functions
|
||||
// are not callable from ISR (placed in flash storage).
|
||||
// Here we just use an interrupt firing every 50 µs.
|
||||
timerAlarmWrite(dimmer_timer, 50, true);
|
||||
timerAlarmEnable(dimmer_timer);
|
||||
timerAlarm(dimmer_timer, 50, true, 0);
|
||||
#endif
|
||||
}
|
||||
void AcDimmer::write_state(float state) {
|
||||
state = std::acos(1 - (2 * state)) / 3.14159; // RMS power compensation
|
||||
state = std::acos(1 - (2 * state)) / std::numbers::pi; // RMS power compensation
|
||||
auto new_value = static_cast<uint16_t>(roundf(state * 65535));
|
||||
if (new_value != 0 && this->store_.value == 0)
|
||||
this->store_.init_cycle = this->init_with_half_cycle_;
|
||||
|
||||
@@ -10,8 +10,15 @@ from esphome.components.esp32.const import (
|
||||
VARIANT_ESP32S2,
|
||||
VARIANT_ESP32S3,
|
||||
)
|
||||
from esphome.config_helpers import filter_source_files_from_platform
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ANALOG, CONF_INPUT, CONF_NUMBER, PLATFORM_ESP8266
|
||||
from esphome.const import (
|
||||
CONF_ANALOG,
|
||||
CONF_INPUT,
|
||||
CONF_NUMBER,
|
||||
PLATFORM_ESP8266,
|
||||
PlatformFramework,
|
||||
)
|
||||
from esphome.core import CORE
|
||||
|
||||
CODEOWNERS = ["@esphome/core"]
|
||||
@@ -229,3 +236,20 @@ def validate_adc_pin(value):
|
||||
)(value)
|
||||
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
FILTER_SOURCE_FILES = filter_source_files_from_platform(
|
||||
{
|
||||
"adc_sensor_esp32.cpp": {
|
||||
PlatformFramework.ESP32_ARDUINO,
|
||||
PlatformFramework.ESP32_IDF,
|
||||
},
|
||||
"adc_sensor_esp8266.cpp": {PlatformFramework.ESP8266_ARDUINO},
|
||||
"adc_sensor_rp2040.cpp": {PlatformFramework.RP2040_ARDUINO},
|
||||
"adc_sensor_libretiny.cpp": {
|
||||
PlatformFramework.BK72XX_ARDUINO,
|
||||
PlatformFramework.RTL87XX_ARDUINO,
|
||||
PlatformFramework.LN882X_ARDUINO,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
@@ -15,8 +15,7 @@ namespace adc {
|
||||
|
||||
#ifdef USE_ESP32
|
||||
// clang-format off
|
||||
#if (ESP_IDF_VERSION_MAJOR == 4 && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 4, 7)) || \
|
||||
(ESP_IDF_VERSION_MAJOR == 5 && \
|
||||
#if (ESP_IDF_VERSION_MAJOR == 5 && \
|
||||
((ESP_IDF_VERSION_MINOR == 0 && ESP_IDF_VERSION_PATCH >= 5) || \
|
||||
(ESP_IDF_VERSION_MINOR == 1 && ESP_IDF_VERSION_PATCH >= 3) || \
|
||||
(ESP_IDF_VERSION_MINOR >= 2)) \
|
||||
@@ -28,19 +27,24 @@ static const adc_atten_t ADC_ATTEN_DB_12_COMPAT = ADC_ATTEN_DB_11;
|
||||
#endif
|
||||
#endif // USE_ESP32
|
||||
|
||||
enum class SamplingMode : uint8_t { AVG = 0, MIN = 1, MAX = 2 };
|
||||
enum class SamplingMode : uint8_t {
|
||||
AVG = 0,
|
||||
MIN = 1,
|
||||
MAX = 2,
|
||||
};
|
||||
|
||||
const LogString *sampling_mode_to_str(SamplingMode mode);
|
||||
|
||||
class Aggregator {
|
||||
public:
|
||||
Aggregator(SamplingMode mode);
|
||||
void add_sample(uint32_t value);
|
||||
uint32_t aggregate();
|
||||
Aggregator(SamplingMode mode);
|
||||
|
||||
protected:
|
||||
SamplingMode mode_{SamplingMode::AVG};
|
||||
uint32_t aggr_{0};
|
||||
uint32_t samples_{0};
|
||||
SamplingMode mode_{SamplingMode::AVG};
|
||||
};
|
||||
|
||||
class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage_sampler::VoltageSampler {
|
||||
@@ -81,9 +85,9 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage
|
||||
#endif // USE_RP2040
|
||||
|
||||
protected:
|
||||
InternalGPIOPin *pin_;
|
||||
bool output_raw_{false};
|
||||
uint8_t sample_count_{1};
|
||||
bool output_raw_{false};
|
||||
InternalGPIOPin *pin_;
|
||||
SamplingMode sampling_mode_{SamplingMode::AVG};
|
||||
|
||||
#ifdef USE_RP2040
|
||||
@@ -95,11 +99,7 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage
|
||||
adc1_channel_t channel1_{ADC1_CHANNEL_MAX};
|
||||
adc2_channel_t channel2_{ADC2_CHANNEL_MAX};
|
||||
bool autorange_{false};
|
||||
#if ESP_IDF_VERSION_MAJOR >= 5
|
||||
esp_adc_cal_characteristics_t cal_characteristics_[SOC_ADC_ATTEN_NUM] = {};
|
||||
#else
|
||||
esp_adc_cal_characteristics_t cal_characteristics_[ADC_ATTEN_MAX] = {};
|
||||
#endif // ESP_IDF_VERSION_MAJOR
|
||||
#endif // USE_ESP32
|
||||
};
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@ uint32_t Aggregator::aggregate() {
|
||||
|
||||
void ADCSensor::update() {
|
||||
float value_v = this->sample();
|
||||
ESP_LOGV(TAG, "'%s': Got voltage=%.4fV", this->get_name().c_str(), value_v);
|
||||
ESP_LOGV(TAG, "'%s': Voltage=%.4fV", this->get_name().c_str(), value_v);
|
||||
this->publish_state(value_v);
|
||||
}
|
||||
|
||||
|
||||
@@ -55,32 +55,40 @@ void ADCSensor::setup() {
|
||||
}
|
||||
|
||||
void ADCSensor::dump_config() {
|
||||
static const char *const ATTEN_AUTO_STR = "auto";
|
||||
static const char *const ATTEN_0DB_STR = "0 db";
|
||||
static const char *const ATTEN_2_5DB_STR = "2.5 db";
|
||||
static const char *const ATTEN_6DB_STR = "6 db";
|
||||
static const char *const ATTEN_12DB_STR = "12 db";
|
||||
const char *atten_str = ATTEN_AUTO_STR;
|
||||
|
||||
LOG_SENSOR("", "ADC Sensor", this);
|
||||
LOG_PIN(" Pin: ", this->pin_);
|
||||
if (this->autorange_) {
|
||||
ESP_LOGCONFIG(TAG, " Attenuation: auto");
|
||||
} else {
|
||||
|
||||
if (!this->autorange_) {
|
||||
switch (this->attenuation_) {
|
||||
case ADC_ATTEN_DB_0:
|
||||
ESP_LOGCONFIG(TAG, " Attenuation: 0db");
|
||||
atten_str = ATTEN_0DB_STR;
|
||||
break;
|
||||
case ADC_ATTEN_DB_2_5:
|
||||
ESP_LOGCONFIG(TAG, " Attenuation: 2.5db");
|
||||
atten_str = ATTEN_2_5DB_STR;
|
||||
break;
|
||||
case ADC_ATTEN_DB_6:
|
||||
ESP_LOGCONFIG(TAG, " Attenuation: 6db");
|
||||
atten_str = ATTEN_6DB_STR;
|
||||
break;
|
||||
case ADC_ATTEN_DB_12_COMPAT:
|
||||
ESP_LOGCONFIG(TAG, " Attenuation: 12db");
|
||||
atten_str = ATTEN_12DB_STR;
|
||||
break;
|
||||
default: // This is to satisfy the unused ADC_ATTEN_MAX
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ESP_LOGCONFIG(TAG,
|
||||
" Attenuation: %s\n"
|
||||
" Samples: %i\n"
|
||||
" Sampling mode: %s",
|
||||
this->sample_count_, LOG_STR_ARG(sampling_mode_to_str(this->sampling_mode_)));
|
||||
atten_str, this->sample_count_, LOG_STR_ARG(sampling_mode_to_str(this->sampling_mode_)));
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
}
|
||||
|
||||
|
||||
@@ -85,8 +85,6 @@ class ADE7880 : public i2c::I2CDevice, public PollingComponent {
|
||||
|
||||
void dump_config() override;
|
||||
|
||||
float get_setup_priority() const override { return setup_priority::DATA; }
|
||||
|
||||
protected:
|
||||
ADE7880Store store_{};
|
||||
InternalGPIOPin *irq0_pin_{nullptr};
|
||||
|
||||
@@ -49,7 +49,6 @@ class ADS1115Component : public Component, public i2c::I2CDevice {
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
/// HARDWARE_LATE setup priority
|
||||
float get_setup_priority() const override { return setup_priority::DATA; }
|
||||
void set_continuous_mode(bool continuous_mode) { continuous_mode_ = continuous_mode; }
|
||||
|
||||
/// Helper method to request a measurement from a sensor.
|
||||
|
||||
@@ -34,7 +34,6 @@ class ADS1118 : public Component,
|
||||
ADS1118() = default;
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override { return setup_priority::DATA; }
|
||||
/// Helper method to request a measurement from a sensor.
|
||||
float request_measurement(ADS1118Multiplexer multiplexer, ADS1118Gain gain, bool temperature_mode);
|
||||
|
||||
|
||||
@@ -31,8 +31,6 @@ class AGS10Component : public PollingComponent, public i2c::I2CDevice {
|
||||
|
||||
void dump_config() override;
|
||||
|
||||
float get_setup_priority() const override { return setup_priority::DATA; }
|
||||
|
||||
/**
|
||||
* Modifies target address of AGS10.
|
||||
*
|
||||
|
||||
@@ -66,7 +66,6 @@ class AIC3204 : public audio_dac::AudioDac, public Component, public i2c::I2CDev
|
||||
public:
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override { return setup_priority::DATA; }
|
||||
|
||||
bool set_mute_off() override;
|
||||
bool set_mute_on() override;
|
||||
|
||||
@@ -14,8 +14,8 @@ from esphome.const import (
|
||||
CONF_WEB_SERVER,
|
||||
)
|
||||
from esphome.core import CORE, coroutine_with_priority
|
||||
from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity
|
||||
from esphome.cpp_generator import MockObjClass
|
||||
from esphome.cpp_helpers import setup_entity
|
||||
|
||||
CODEOWNERS = ["@grahambrown11", "@hwstar"]
|
||||
IS_PLATFORM_COMPONENT = True
|
||||
@@ -149,6 +149,9 @@ _ALARM_CONTROL_PANEL_SCHEMA = (
|
||||
)
|
||||
|
||||
|
||||
_ALARM_CONTROL_PANEL_SCHEMA.add_extra(entity_duplicate_validator("alarm_control_panel"))
|
||||
|
||||
|
||||
def alarm_control_panel_schema(
|
||||
class_: MockObjClass,
|
||||
*,
|
||||
@@ -190,7 +193,7 @@ ALARM_CONTROL_PANEL_CONDITION_SCHEMA = maybe_simple_id(
|
||||
|
||||
|
||||
async def setup_alarm_control_panel_core_(var, config):
|
||||
await setup_entity(var, config)
|
||||
await setup_entity(var, config, "alarm_control_panel")
|
||||
for conf in config.get(CONF_ON_STATE, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [], conf)
|
||||
|
||||
@@ -41,7 +41,6 @@ class Alpha3 : public esphome::ble_client::BLEClientNode, public PollingComponen
|
||||
void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
||||
esp_ble_gattc_cb_param_t *param) override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override { return setup_priority::DATA; }
|
||||
void set_flow_sensor(sensor::Sensor *sensor) { this->flow_sensor_ = sensor; }
|
||||
void set_head_sensor(sensor::Sensor *sensor) { this->head_sensor_ = sensor; }
|
||||
void set_power_sensor(sensor::Sensor *sensor) { this->power_sensor_ = sensor; }
|
||||
|
||||
@@ -22,7 +22,6 @@ class Am43Component : public cover::Cover, public esphome::ble_client::BLEClient
|
||||
void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
||||
esp_ble_gattc_cb_param_t *param) override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override { return setup_priority::DATA; }
|
||||
cover::CoverTraits get_traits() override;
|
||||
void set_pin(uint16_t pin) { this->pin_ = pin; }
|
||||
void set_invert_position(bool invert_position) { this->invert_position_ = invert_position; }
|
||||
|
||||
@@ -22,7 +22,6 @@ class Am43 : public esphome::ble_client::BLEClientNode, public PollingComponent
|
||||
void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
||||
esp_ble_gattc_cb_param_t *param) override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override { return setup_priority::DATA; }
|
||||
void set_battery(sensor::Sensor *battery) { battery_ = battery; }
|
||||
void set_illuminance(sensor::Sensor *illuminance) { illuminance_ = illuminance; }
|
||||
|
||||
|
||||
@@ -12,8 +12,6 @@ class AnalogThresholdBinarySensor : public Component, public binary_sensor::Bina
|
||||
void dump_config() override;
|
||||
void setup() override;
|
||||
|
||||
float get_setup_priority() const override { return setup_priority::DATA; }
|
||||
|
||||
void set_sensor(sensor::Sensor *analog_sensor);
|
||||
template<typename T> void set_upper_threshold(T upper_threshold) { this->upper_threshold_ = upper_threshold; }
|
||||
template<typename T> void set_lower_threshold(T lower_threshold) { this->lower_threshold_ = lower_threshold; }
|
||||
|
||||
@@ -17,7 +17,11 @@ void Anova::setup() {
|
||||
this->current_request_ = 0;
|
||||
}
|
||||
|
||||
void Anova::loop() {}
|
||||
void Anova::loop() {
|
||||
// Parent BLEClientNode has a loop() method, but this component uses
|
||||
// polling via update() and BLE callbacks so loop isn't needed
|
||||
this->disable_loop();
|
||||
}
|
||||
|
||||
void Anova::control(const ClimateCall &call) {
|
||||
if (call.get_mode().has_value()) {
|
||||
|
||||
@@ -26,7 +26,6 @@ class Anova : public climate::Climate, public esphome::ble_client::BLEClientNode
|
||||
void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
||||
esp_ble_gattc_cb_param_t *param) override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override { return setup_priority::DATA; }
|
||||
climate::ClimateTraits traits() override {
|
||||
auto traits = climate::ClimateTraits();
|
||||
traits.set_supports_current_temperature(true);
|
||||
|
||||
@@ -23,7 +23,7 @@ void APDS9960::setup() {
|
||||
return;
|
||||
}
|
||||
|
||||
if (id != 0xAB && id != 0x9C && id != 0xA8) { // APDS9960 all should have one of these IDs
|
||||
if (id != 0xAB && id != 0x9C && id != 0xA8 && id != 0x9E) { // APDS9960 all should have one of these IDs
|
||||
this->error_code_ = WRONG_ID;
|
||||
this->mark_failed();
|
||||
return;
|
||||
|
||||
@@ -3,6 +3,7 @@ import base64
|
||||
from esphome import automation
|
||||
from esphome.automation import Condition
|
||||
import esphome.codegen as cg
|
||||
from esphome.config_helpers import get_logger_level
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_ACTION,
|
||||
@@ -23,8 +24,9 @@ from esphome.const import (
|
||||
CONF_TRIGGER_ID,
|
||||
CONF_VARIABLES,
|
||||
)
|
||||
from esphome.core import coroutine_with_priority
|
||||
from esphome.core import CORE, coroutine_with_priority
|
||||
|
||||
DOMAIN = "api"
|
||||
DEPENDENCIES = ["network"]
|
||||
AUTO_LOAD = ["socket"]
|
||||
CODEOWNERS = ["@OttoWinter"]
|
||||
@@ -50,6 +52,7 @@ SERVICE_ARG_NATIVE_TYPES = {
|
||||
}
|
||||
CONF_ENCRYPTION = "encryption"
|
||||
CONF_BATCH_DELAY = "batch_delay"
|
||||
CONF_CUSTOM_SERVICES = "custom_services"
|
||||
|
||||
|
||||
def validate_encryption_key(value):
|
||||
@@ -110,9 +113,11 @@ 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_BATCH_DELAY, default="100ms"
|
||||
): cv.positive_time_period_milliseconds,
|
||||
cv.Optional(CONF_BATCH_DELAY, default="100ms"): cv.All(
|
||||
cv.positive_time_period_milliseconds,
|
||||
cv.Range(max=cv.TimePeriod(milliseconds=65535)),
|
||||
),
|
||||
cv.Optional(CONF_CUSTOM_SERVICES, default=False): cv.boolean,
|
||||
cv.Optional(CONF_ON_CLIENT_CONNECTED): automation.validate_automation(
|
||||
single=True
|
||||
),
|
||||
@@ -131,27 +136,35 @@ async def to_code(config):
|
||||
await cg.register_component(var, config)
|
||||
|
||||
cg.add(var.set_port(config[CONF_PORT]))
|
||||
cg.add(var.set_password(config[CONF_PASSWORD]))
|
||||
if config[CONF_PASSWORD]:
|
||||
cg.add_define("USE_API_PASSWORD")
|
||||
cg.add(var.set_password(config[CONF_PASSWORD]))
|
||||
cg.add(var.set_reboot_timeout(config[CONF_REBOOT_TIMEOUT]))
|
||||
cg.add(var.set_batch_delay(config[CONF_BATCH_DELAY]))
|
||||
|
||||
for conf in config.get(CONF_ACTIONS, []):
|
||||
template_args = []
|
||||
func_args = []
|
||||
service_arg_names = []
|
||||
for name, var_ in conf[CONF_VARIABLES].items():
|
||||
native = SERVICE_ARG_NATIVE_TYPES[var_]
|
||||
template_args.append(native)
|
||||
func_args.append((native, name))
|
||||
service_arg_names.append(name)
|
||||
templ = cg.TemplateArguments(*template_args)
|
||||
trigger = cg.new_Pvariable(
|
||||
conf[CONF_TRIGGER_ID], templ, conf[CONF_ACTION], service_arg_names
|
||||
)
|
||||
cg.add(var.register_user_service(trigger))
|
||||
await automation.build_automation(trigger, func_args, conf)
|
||||
# Set USE_API_SERVICES if any services are enabled
|
||||
if config.get(CONF_ACTIONS) or config[CONF_CUSTOM_SERVICES]:
|
||||
cg.add_define("USE_API_SERVICES")
|
||||
|
||||
if actions := config.get(CONF_ACTIONS, []):
|
||||
for conf in actions:
|
||||
template_args = []
|
||||
func_args = []
|
||||
service_arg_names = []
|
||||
for name, var_ in conf[CONF_VARIABLES].items():
|
||||
native = SERVICE_ARG_NATIVE_TYPES[var_]
|
||||
template_args.append(native)
|
||||
func_args.append((native, name))
|
||||
service_arg_names.append(name)
|
||||
templ = cg.TemplateArguments(*template_args)
|
||||
trigger = cg.new_Pvariable(
|
||||
conf[CONF_TRIGGER_ID], templ, conf[CONF_ACTION], service_arg_names
|
||||
)
|
||||
cg.add(var.register_user_service(trigger))
|
||||
await automation.build_automation(trigger, func_args, conf)
|
||||
|
||||
if CONF_ON_CLIENT_CONNECTED in config:
|
||||
cg.add_define("USE_API_CLIENT_CONNECTED_TRIGGER")
|
||||
await automation.build_automation(
|
||||
var.get_client_connected_trigger(),
|
||||
[(cg.std_string, "client_info"), (cg.std_string, "client_address")],
|
||||
@@ -159,6 +172,7 @@ async def to_code(config):
|
||||
)
|
||||
|
||||
if CONF_ON_CLIENT_DISCONNECTED in config:
|
||||
cg.add_define("USE_API_CLIENT_DISCONNECTED_TRIGGER")
|
||||
await automation.build_automation(
|
||||
var.get_client_disconnected_trigger(),
|
||||
[(cg.std_string, "client_info"), (cg.std_string, "client_address")],
|
||||
@@ -177,7 +191,7 @@ async def to_code(config):
|
||||
# and plaintext disabled. Only a factory reset can remove it.
|
||||
cg.add_define("USE_API_PLAINTEXT")
|
||||
cg.add_define("USE_API_NOISE")
|
||||
cg.add_library("esphome/noise-c", "0.1.6")
|
||||
cg.add_library("esphome/noise-c", "0.1.10")
|
||||
else:
|
||||
cg.add_define("USE_API_PLAINTEXT")
|
||||
|
||||
@@ -306,3 +320,25 @@ async def homeassistant_tag_scanned_to_code(config, action_id, template_arg, arg
|
||||
@automation.register_condition("api.connected", APIConnectedCondition, {})
|
||||
async def api_connected_to_code(config, condition_id, template_arg, args):
|
||||
return cg.new_Pvariable(condition_id, template_arg)
|
||||
|
||||
|
||||
def FILTER_SOURCE_FILES() -> list[str]:
|
||||
"""Filter out api_pb2_dump.cpp when proto message dumping is not enabled
|
||||
and user_services.cpp when no services are defined."""
|
||||
files_to_filter = []
|
||||
|
||||
# api_pb2_dump.cpp is only needed when HAS_PROTO_MESSAGE_DUMP is defined
|
||||
# This is a particularly large file that still needs to be opened and read
|
||||
# all the way to the end even when ifdef'd out
|
||||
#
|
||||
# HAS_PROTO_MESSAGE_DUMP is defined when ESPHOME_LOG_HAS_VERY_VERBOSE is set,
|
||||
# which happens when the logger level is VERY_VERBOSE
|
||||
if get_logger_level() != "VERY_VERBOSE":
|
||||
files_to_filter.append("api_pb2_dump.cpp")
|
||||
|
||||
# user_services.cpp is only needed when services are defined
|
||||
config = CORE.config.get(DOMAIN, {})
|
||||
if config and not config.get(CONF_ACTIONS) and not config[CONF_CUSTOM_SERVICES]:
|
||||
files_to_filter.append("user_services.cpp")
|
||||
|
||||
return files_to_filter
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -5,7 +5,6 @@
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "proto.h"
|
||||
#include "api_pb2_size.h"
|
||||
#include <cstring>
|
||||
#include <cinttypes>
|
||||
|
||||
@@ -66,6 +65,17 @@ const char *api_error_to_str(APIError err) {
|
||||
return "UNKNOWN";
|
||||
}
|
||||
|
||||
// Default implementation for loop - handles sending buffered data
|
||||
APIError APIFrameHelper::loop() {
|
||||
if (!this->tx_buf_.empty()) {
|
||||
APIError err = try_send_tx_buf_();
|
||||
if (err != APIError::OK && err != APIError::WOULD_BLOCK) {
|
||||
return err;
|
||||
}
|
||||
}
|
||||
return APIError::OK; // Convert WOULD_BLOCK to OK to avoid connection termination
|
||||
}
|
||||
|
||||
// Helper method to buffer data from IOVs
|
||||
void APIFrameHelper::buffer_data_from_iov_(const struct iovec *iov, int iovcnt, uint16_t total_write_len) {
|
||||
SendBuffer buffer;
|
||||
@@ -214,6 +224,22 @@ APIError APIFrameHelper::init_common_() {
|
||||
}
|
||||
|
||||
#define HELPER_LOG(msg, ...) ESP_LOGVV(TAG, "%s: " msg, this->info_.c_str(), ##__VA_ARGS__)
|
||||
|
||||
APIError APIFrameHelper::handle_socket_read_result_(ssize_t received) {
|
||||
if (received == -1) {
|
||||
if (errno == EWOULDBLOCK || errno == EAGAIN) {
|
||||
return APIError::WOULD_BLOCK;
|
||||
}
|
||||
state_ = State::FAILED;
|
||||
HELPER_LOG("Socket read failed with errno %d", errno);
|
||||
return APIError::SOCKET_READ_FAILED;
|
||||
} else if (received == 0) {
|
||||
state_ = State::FAILED;
|
||||
HELPER_LOG("Connection closed");
|
||||
return APIError::CONNECTION_CLOSED;
|
||||
}
|
||||
return APIError::OK;
|
||||
}
|
||||
// uncomment to log raw packets
|
||||
//#define HELPER_LOG_PACKETS
|
||||
|
||||
@@ -274,17 +300,21 @@ APIError APINoiseFrameHelper::init() {
|
||||
}
|
||||
/// Run through handshake messages (if in that phase)
|
||||
APIError APINoiseFrameHelper::loop() {
|
||||
APIError err = state_action_();
|
||||
if (err != APIError::OK && err != APIError::WOULD_BLOCK) {
|
||||
return err;
|
||||
}
|
||||
if (!this->tx_buf_.empty()) {
|
||||
err = try_send_tx_buf_();
|
||||
// During handshake phase, process as many actions as possible until we can't progress
|
||||
// socket_->ready() stays true until next main loop, but state_action() will return
|
||||
// WOULD_BLOCK when no more data is available to read
|
||||
while (state_ != State::DATA && this->socket_->ready()) {
|
||||
APIError err = state_action_();
|
||||
if (err != APIError::OK && err != APIError::WOULD_BLOCK) {
|
||||
return err;
|
||||
}
|
||||
if (err == APIError::WOULD_BLOCK) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return APIError::OK; // Convert WOULD_BLOCK to OK to avoid connection termination
|
||||
|
||||
// Use base class implementation for buffer sending
|
||||
return APIFrameHelper::loop();
|
||||
}
|
||||
|
||||
/** Read a packet into the rx_buf_. If successful, stores frame data in the frame parameter
|
||||
@@ -312,17 +342,9 @@ APIError APINoiseFrameHelper::try_read_frame_(ParsedFrame *frame) {
|
||||
// no header information yet
|
||||
uint8_t to_read = 3 - rx_header_buf_len_;
|
||||
ssize_t received = this->socket_->read(&rx_header_buf_[rx_header_buf_len_], to_read);
|
||||
if (received == -1) {
|
||||
if (errno == EWOULDBLOCK || errno == EAGAIN) {
|
||||
return APIError::WOULD_BLOCK;
|
||||
}
|
||||
state_ = State::FAILED;
|
||||
HELPER_LOG("Socket read failed with errno %d", errno);
|
||||
return APIError::SOCKET_READ_FAILED;
|
||||
} else if (received == 0) {
|
||||
state_ = State::FAILED;
|
||||
HELPER_LOG("Connection closed");
|
||||
return APIError::CONNECTION_CLOSED;
|
||||
APIError err = handle_socket_read_result_(received);
|
||||
if (err != APIError::OK) {
|
||||
return err;
|
||||
}
|
||||
rx_header_buf_len_ += static_cast<uint8_t>(received);
|
||||
if (static_cast<uint8_t>(received) != to_read) {
|
||||
@@ -330,17 +352,15 @@ APIError APINoiseFrameHelper::try_read_frame_(ParsedFrame *frame) {
|
||||
return APIError::WOULD_BLOCK;
|
||||
}
|
||||
|
||||
if (rx_header_buf_[0] != 0x01) {
|
||||
state_ = State::FAILED;
|
||||
HELPER_LOG("Bad indicator byte %u", rx_header_buf_[0]);
|
||||
return APIError::BAD_INDICATOR;
|
||||
}
|
||||
// header reading done
|
||||
}
|
||||
|
||||
// read body
|
||||
uint8_t indicator = rx_header_buf_[0];
|
||||
if (indicator != 0x01) {
|
||||
state_ = State::FAILED;
|
||||
HELPER_LOG("Bad indicator byte %u", indicator);
|
||||
return APIError::BAD_INDICATOR;
|
||||
}
|
||||
|
||||
uint16_t msg_size = (((uint16_t) rx_header_buf_[1]) << 8) | rx_header_buf_[2];
|
||||
|
||||
if (state_ != State::DATA && msg_size > 128) {
|
||||
@@ -359,17 +379,9 @@ APIError APINoiseFrameHelper::try_read_frame_(ParsedFrame *frame) {
|
||||
// more data to read
|
||||
uint16_t to_read = msg_size - rx_buf_len_;
|
||||
ssize_t received = this->socket_->read(&rx_buf_[rx_buf_len_], to_read);
|
||||
if (received == -1) {
|
||||
if (errno == EWOULDBLOCK || errno == EAGAIN) {
|
||||
return APIError::WOULD_BLOCK;
|
||||
}
|
||||
state_ = State::FAILED;
|
||||
HELPER_LOG("Socket read failed with errno %d", errno);
|
||||
return APIError::SOCKET_READ_FAILED;
|
||||
} else if (received == 0) {
|
||||
state_ = State::FAILED;
|
||||
HELPER_LOG("Connection closed");
|
||||
return APIError::CONNECTION_CLOSED;
|
||||
APIError err = handle_socket_read_result_(received);
|
||||
if (err != APIError::OK) {
|
||||
return err;
|
||||
}
|
||||
rx_buf_len_ += static_cast<uint16_t>(received);
|
||||
if (static_cast<uint16_t>(received) != to_read) {
|
||||
@@ -586,10 +598,6 @@ APIError APINoiseFrameHelper::read_packet(ReadPacketBuffer *buffer) {
|
||||
return APIError::BAD_DATA_PACKET;
|
||||
}
|
||||
|
||||
// uint16_t type;
|
||||
// uint16_t data_len;
|
||||
// uint8_t *data;
|
||||
// uint8_t *padding; zero or more bytes to fill up the rest of the packet
|
||||
uint16_t type = (((uint16_t) msg_data[0]) << 8) | msg_data[1];
|
||||
uint16_t data_len = (((uint16_t) msg_data[2]) << 8) | msg_data[3];
|
||||
if (data_len > msg_size - 4) {
|
||||
@@ -604,21 +612,15 @@ APIError APINoiseFrameHelper::read_packet(ReadPacketBuffer *buffer) {
|
||||
buffer->type = type;
|
||||
return APIError::OK;
|
||||
}
|
||||
APIError APINoiseFrameHelper::write_protobuf_packet(uint16_t type, ProtoWriteBuffer buffer) {
|
||||
std::vector<uint8_t> *raw_buffer = buffer.get_buffer();
|
||||
uint16_t payload_len = static_cast<uint16_t>(raw_buffer->size() - frame_header_padding_);
|
||||
|
||||
APIError APINoiseFrameHelper::write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) {
|
||||
// Resize to include MAC space (required for Noise encryption)
|
||||
raw_buffer->resize(raw_buffer->size() + frame_footer_size_);
|
||||
|
||||
// Use write_protobuf_packets with a single packet
|
||||
std::vector<PacketInfo> packets;
|
||||
packets.emplace_back(type, 0, payload_len);
|
||||
|
||||
return write_protobuf_packets(buffer, packets);
|
||||
buffer.get_buffer()->resize(buffer.get_buffer()->size() + frame_footer_size_);
|
||||
PacketInfo packet{type, 0,
|
||||
static_cast<uint16_t>(buffer.get_buffer()->size() - frame_header_padding_ - frame_footer_size_)};
|
||||
return write_protobuf_packets(buffer, std::span<const PacketInfo>(&packet, 1));
|
||||
}
|
||||
|
||||
APIError APINoiseFrameHelper::write_protobuf_packets(ProtoWriteBuffer buffer, const std::vector<PacketInfo> &packets) {
|
||||
APIError APINoiseFrameHelper::write_protobuf_packets(ProtoWriteBuffer buffer, std::span<const PacketInfo> packets) {
|
||||
APIError aerr = state_action_();
|
||||
if (aerr != APIError::OK) {
|
||||
return aerr;
|
||||
@@ -633,18 +635,15 @@ APIError APINoiseFrameHelper::write_protobuf_packets(ProtoWriteBuffer buffer, co
|
||||
}
|
||||
|
||||
std::vector<uint8_t> *raw_buffer = buffer.get_buffer();
|
||||
uint8_t *buffer_data = raw_buffer->data(); // Cache buffer pointer
|
||||
|
||||
this->reusable_iovs_.clear();
|
||||
this->reusable_iovs_.reserve(packets.size());
|
||||
|
||||
// We need to encrypt each packet in place
|
||||
for (const auto &packet : packets) {
|
||||
uint16_t type = packet.message_type;
|
||||
uint16_t offset = packet.offset;
|
||||
uint16_t payload_len = packet.payload_size;
|
||||
uint16_t msg_len = 4 + payload_len; // type(2) + data_len(2) + payload
|
||||
|
||||
// The buffer already has padding at offset
|
||||
uint8_t *buf_start = raw_buffer->data() + offset;
|
||||
uint8_t *buf_start = buffer_data + packet.offset;
|
||||
|
||||
// Write noise header
|
||||
buf_start[0] = 0x01; // indicator
|
||||
@@ -652,10 +651,10 @@ APIError APINoiseFrameHelper::write_protobuf_packets(ProtoWriteBuffer buffer, co
|
||||
|
||||
// Write message header (to be encrypted)
|
||||
const uint8_t msg_offset = 3;
|
||||
buf_start[msg_offset + 0] = (uint8_t) (type >> 8); // type high byte
|
||||
buf_start[msg_offset + 1] = (uint8_t) type; // type low byte
|
||||
buf_start[msg_offset + 2] = (uint8_t) (payload_len >> 8); // data_len high byte
|
||||
buf_start[msg_offset + 3] = (uint8_t) payload_len; // data_len low byte
|
||||
buf_start[msg_offset] = static_cast<uint8_t>(packet.message_type >> 8); // type high byte
|
||||
buf_start[msg_offset + 1] = static_cast<uint8_t>(packet.message_type); // type low byte
|
||||
buf_start[msg_offset + 2] = static_cast<uint8_t>(packet.payload_size >> 8); // data_len high byte
|
||||
buf_start[msg_offset + 3] = static_cast<uint8_t>(packet.payload_size); // data_len low byte
|
||||
// payload data is already in the buffer starting at offset + 7
|
||||
|
||||
// Make sure we have space for MAC
|
||||
@@ -664,7 +663,8 @@ APIError APINoiseFrameHelper::write_protobuf_packets(ProtoWriteBuffer buffer, co
|
||||
// Encrypt the message in place
|
||||
NoiseBuffer mbuf;
|
||||
noise_buffer_init(mbuf);
|
||||
noise_buffer_set_inout(mbuf, buf_start + msg_offset, msg_len, msg_len + frame_footer_size_);
|
||||
noise_buffer_set_inout(mbuf, buf_start + msg_offset, 4 + packet.payload_size,
|
||||
4 + packet.payload_size + frame_footer_size_);
|
||||
|
||||
int err = noise_cipherstate_encrypt(send_cipher_, &mbuf);
|
||||
if (err != 0) {
|
||||
@@ -674,14 +674,12 @@ APIError APINoiseFrameHelper::write_protobuf_packets(ProtoWriteBuffer buffer, co
|
||||
}
|
||||
|
||||
// Fill in the encrypted size
|
||||
buf_start[1] = (uint8_t) (mbuf.size >> 8);
|
||||
buf_start[2] = (uint8_t) mbuf.size;
|
||||
buf_start[1] = static_cast<uint8_t>(mbuf.size >> 8);
|
||||
buf_start[2] = static_cast<uint8_t>(mbuf.size);
|
||||
|
||||
// Add iovec for this encrypted packet
|
||||
struct iovec iov;
|
||||
iov.iov_base = buf_start;
|
||||
iov.iov_len = 3 + mbuf.size; // indicator + size + encrypted data
|
||||
this->reusable_iovs_.push_back(iov);
|
||||
this->reusable_iovs_.push_back(
|
||||
{buf_start, static_cast<size_t>(3 + mbuf.size)}); // indicator + size + encrypted data
|
||||
}
|
||||
|
||||
// Send all encrypted packets in one writev call
|
||||
@@ -822,18 +820,12 @@ APIError APIPlaintextFrameHelper::init() {
|
||||
state_ = State::DATA;
|
||||
return APIError::OK;
|
||||
}
|
||||
/// Not used for plaintext
|
||||
APIError APIPlaintextFrameHelper::loop() {
|
||||
if (state_ != State::DATA) {
|
||||
return APIError::BAD_STATE;
|
||||
}
|
||||
if (!this->tx_buf_.empty()) {
|
||||
APIError err = try_send_tx_buf_();
|
||||
if (err != APIError::OK && err != APIError::WOULD_BLOCK) {
|
||||
return err;
|
||||
}
|
||||
}
|
||||
return APIError::OK; // Convert WOULD_BLOCK to OK to avoid connection termination
|
||||
// Use base class implementation for buffer sending
|
||||
return APIFrameHelper::loop();
|
||||
}
|
||||
|
||||
/** Read a packet into the rx_buf_. If successful, stores frame data in the frame parameter
|
||||
@@ -862,17 +854,9 @@ APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) {
|
||||
// Try to get to at least 3 bytes total (indicator + 2 varint bytes), then read one byte at a time
|
||||
ssize_t received =
|
||||
this->socket_->read(&rx_header_buf_[rx_header_buf_pos_], rx_header_buf_pos_ < 3 ? 3 - rx_header_buf_pos_ : 1);
|
||||
if (received == -1) {
|
||||
if (errno == EWOULDBLOCK || errno == EAGAIN) {
|
||||
return APIError::WOULD_BLOCK;
|
||||
}
|
||||
state_ = State::FAILED;
|
||||
HELPER_LOG("Socket read failed with errno %d", errno);
|
||||
return APIError::SOCKET_READ_FAILED;
|
||||
} else if (received == 0) {
|
||||
state_ = State::FAILED;
|
||||
HELPER_LOG("Connection closed");
|
||||
return APIError::CONNECTION_CLOSED;
|
||||
APIError err = handle_socket_read_result_(received);
|
||||
if (err != APIError::OK) {
|
||||
return err;
|
||||
}
|
||||
|
||||
// If this was the first read, validate the indicator byte
|
||||
@@ -956,17 +940,9 @@ APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) {
|
||||
// more data to read
|
||||
uint16_t to_read = rx_header_parsed_len_ - rx_buf_len_;
|
||||
ssize_t received = this->socket_->read(&rx_buf_[rx_buf_len_], to_read);
|
||||
if (received == -1) {
|
||||
if (errno == EWOULDBLOCK || errno == EAGAIN) {
|
||||
return APIError::WOULD_BLOCK;
|
||||
}
|
||||
state_ = State::FAILED;
|
||||
HELPER_LOG("Socket read failed with errno %d", errno);
|
||||
return APIError::SOCKET_READ_FAILED;
|
||||
} else if (received == 0) {
|
||||
state_ = State::FAILED;
|
||||
HELPER_LOG("Connection closed");
|
||||
return APIError::CONNECTION_CLOSED;
|
||||
APIError err = handle_socket_read_result_(received);
|
||||
if (err != APIError::OK) {
|
||||
return err;
|
||||
}
|
||||
rx_buf_len_ += static_cast<uint16_t>(received);
|
||||
if (static_cast<uint16_t>(received) != to_read) {
|
||||
@@ -1025,19 +1001,12 @@ APIError APIPlaintextFrameHelper::read_packet(ReadPacketBuffer *buffer) {
|
||||
buffer->type = rx_header_parsed_type_;
|
||||
return APIError::OK;
|
||||
}
|
||||
APIError APIPlaintextFrameHelper::write_protobuf_packet(uint16_t type, ProtoWriteBuffer buffer) {
|
||||
std::vector<uint8_t> *raw_buffer = buffer.get_buffer();
|
||||
uint16_t payload_len = static_cast<uint16_t>(raw_buffer->size() - frame_header_padding_);
|
||||
|
||||
// Use write_protobuf_packets with a single packet
|
||||
std::vector<PacketInfo> packets;
|
||||
packets.emplace_back(type, 0, payload_len);
|
||||
|
||||
return write_protobuf_packets(buffer, packets);
|
||||
APIError APIPlaintextFrameHelper::write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) {
|
||||
PacketInfo packet{type, 0, static_cast<uint16_t>(buffer.get_buffer()->size() - frame_header_padding_)};
|
||||
return write_protobuf_packets(buffer, std::span<const PacketInfo>(&packet, 1));
|
||||
}
|
||||
|
||||
APIError APIPlaintextFrameHelper::write_protobuf_packets(ProtoWriteBuffer buffer,
|
||||
const std::vector<PacketInfo> &packets) {
|
||||
APIError APIPlaintextFrameHelper::write_protobuf_packets(ProtoWriteBuffer buffer, std::span<const PacketInfo> packets) {
|
||||
if (state_ != State::DATA) {
|
||||
return APIError::BAD_STATE;
|
||||
}
|
||||
@@ -1047,17 +1016,15 @@ APIError APIPlaintextFrameHelper::write_protobuf_packets(ProtoWriteBuffer buffer
|
||||
}
|
||||
|
||||
std::vector<uint8_t> *raw_buffer = buffer.get_buffer();
|
||||
uint8_t *buffer_data = raw_buffer->data(); // Cache buffer pointer
|
||||
|
||||
this->reusable_iovs_.clear();
|
||||
this->reusable_iovs_.reserve(packets.size());
|
||||
|
||||
for (const auto &packet : packets) {
|
||||
uint16_t type = packet.message_type;
|
||||
uint16_t offset = packet.offset;
|
||||
uint16_t payload_len = packet.payload_size;
|
||||
|
||||
// Calculate varint sizes for header layout
|
||||
uint8_t size_varint_len = api::ProtoSize::varint(static_cast<uint32_t>(payload_len));
|
||||
uint8_t type_varint_len = api::ProtoSize::varint(static_cast<uint32_t>(type));
|
||||
uint8_t size_varint_len = api::ProtoSize::varint(static_cast<uint32_t>(packet.payload_size));
|
||||
uint8_t type_varint_len = api::ProtoSize::varint(static_cast<uint32_t>(packet.message_type));
|
||||
uint8_t total_header_len = 1 + size_varint_len + type_varint_len;
|
||||
|
||||
// Calculate where to start writing the header
|
||||
@@ -1085,23 +1052,20 @@ APIError APIPlaintextFrameHelper::write_protobuf_packets(ProtoWriteBuffer buffer
|
||||
//
|
||||
// The message starts at offset + frame_header_padding_
|
||||
// So we write the header starting at offset + frame_header_padding_ - total_header_len
|
||||
uint8_t *buf_start = raw_buffer->data() + offset;
|
||||
uint8_t *buf_start = buffer_data + packet.offset;
|
||||
uint32_t header_offset = frame_header_padding_ - total_header_len;
|
||||
|
||||
// Write the plaintext header
|
||||
buf_start[header_offset] = 0x00; // indicator
|
||||
|
||||
// Encode size varint directly into buffer
|
||||
ProtoVarInt(payload_len).encode_to_buffer_unchecked(buf_start + header_offset + 1, size_varint_len);
|
||||
|
||||
// Encode type varint directly into buffer
|
||||
ProtoVarInt(type).encode_to_buffer_unchecked(buf_start + header_offset + 1 + size_varint_len, type_varint_len);
|
||||
// Encode varints directly into buffer
|
||||
ProtoVarInt(packet.payload_size).encode_to_buffer_unchecked(buf_start + header_offset + 1, size_varint_len);
|
||||
ProtoVarInt(packet.message_type)
|
||||
.encode_to_buffer_unchecked(buf_start + header_offset + 1 + size_varint_len, type_varint_len);
|
||||
|
||||
// Add iovec for this packet (header + payload)
|
||||
struct iovec iov;
|
||||
iov.iov_base = buf_start + header_offset;
|
||||
iov.iov_len = total_header_len + payload_len;
|
||||
this->reusable_iovs_.push_back(iov);
|
||||
this->reusable_iovs_.push_back(
|
||||
{buf_start + header_offset, static_cast<size_t>(total_header_len + packet.payload_size)});
|
||||
}
|
||||
|
||||
// Send all packets in one writev call
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include <cstdint>
|
||||
#include <deque>
|
||||
#include <limits>
|
||||
#include <span>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
@@ -29,16 +30,14 @@ struct ReadPacketBuffer {
|
||||
|
||||
// Packed packet info structure to minimize memory usage
|
||||
struct PacketInfo {
|
||||
uint16_t message_type; // 2 bytes
|
||||
uint16_t offset; // 2 bytes (sufficient for packet size ~1460 bytes)
|
||||
uint16_t payload_size; // 2 bytes (up to 65535 bytes)
|
||||
uint16_t padding; // 2 byte (for alignment)
|
||||
uint16_t offset; // Offset in buffer where message starts
|
||||
uint16_t payload_size; // Size of the message payload
|
||||
uint8_t message_type; // Message type (0-255)
|
||||
|
||||
PacketInfo(uint16_t type, uint16_t off, uint16_t size)
|
||||
: message_type(type), offset(off), payload_size(size), padding(0) {}
|
||||
PacketInfo(uint8_t type, uint16_t off, uint16_t size) : offset(off), payload_size(size), message_type(type) {}
|
||||
};
|
||||
|
||||
enum class APIError : int {
|
||||
enum class APIError : uint16_t {
|
||||
OK = 0,
|
||||
WOULD_BLOCK = 1001,
|
||||
BAD_HANDSHAKE_PACKET_LEN = 1002,
|
||||
@@ -74,7 +73,7 @@ class APIFrameHelper {
|
||||
}
|
||||
virtual ~APIFrameHelper() = default;
|
||||
virtual APIError init() = 0;
|
||||
virtual APIError loop() = 0;
|
||||
virtual APIError loop();
|
||||
virtual APIError read_packet(ReadPacketBuffer *buffer) = 0;
|
||||
bool can_write_without_blocking() { return state_ == State::DATA && tx_buf_.empty(); }
|
||||
std::string getpeername() { return socket_->getpeername(); }
|
||||
@@ -97,11 +96,11 @@ class APIFrameHelper {
|
||||
}
|
||||
// Give this helper a name for logging
|
||||
void set_log_info(std::string info) { info_ = std::move(info); }
|
||||
virtual APIError write_protobuf_packet(uint16_t type, ProtoWriteBuffer buffer) = 0;
|
||||
virtual APIError write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) = 0;
|
||||
// Write multiple protobuf packets in a single operation
|
||||
// packets contains (message_type, offset, length) for each message in the buffer
|
||||
// The buffer contains all messages with appropriate padding before each
|
||||
virtual APIError write_protobuf_packets(ProtoWriteBuffer buffer, const std::vector<PacketInfo> &packets) = 0;
|
||||
virtual APIError write_protobuf_packets(ProtoWriteBuffer buffer, std::span<const PacketInfo> packets) = 0;
|
||||
// Get the frame header padding required by this protocol
|
||||
virtual uint8_t frame_header_padding() = 0;
|
||||
// Get the frame footer size required by this protocol
|
||||
@@ -125,38 +124,6 @@ class APIFrameHelper {
|
||||
const uint8_t *current_data() const { return data.data() + offset; }
|
||||
};
|
||||
|
||||
// Queue of data buffers to be sent
|
||||
std::deque<SendBuffer> tx_buf_;
|
||||
|
||||
// Common state enum for all frame helpers
|
||||
// Note: Not all states are used by all implementations
|
||||
// - INITIALIZE: Used by both Noise and Plaintext
|
||||
// - CLIENT_HELLO, SERVER_HELLO, HANDSHAKE: Only used by Noise protocol
|
||||
// - DATA: Used by both Noise and Plaintext
|
||||
// - CLOSED: Used by both Noise and Plaintext
|
||||
// - FAILED: Used by both Noise and Plaintext
|
||||
// - EXPLICIT_REJECT: Only used by Noise protocol
|
||||
enum class State {
|
||||
INITIALIZE = 1,
|
||||
CLIENT_HELLO = 2, // Noise only
|
||||
SERVER_HELLO = 3, // Noise only
|
||||
HANDSHAKE = 4, // Noise only
|
||||
DATA = 5,
|
||||
CLOSED = 6,
|
||||
FAILED = 7,
|
||||
EXPLICIT_REJECT = 8, // Noise only
|
||||
};
|
||||
|
||||
// Current state of the frame helper
|
||||
State state_{State::INITIALIZE};
|
||||
|
||||
// Helper name for logging
|
||||
std::string info_;
|
||||
|
||||
// Socket for communication
|
||||
socket::Socket *socket_{nullptr};
|
||||
std::unique_ptr<socket::Socket> socket_owned_;
|
||||
|
||||
// Common implementation for writing raw data to socket
|
||||
APIError write_raw_(const struct iovec *iov, int iovcnt);
|
||||
|
||||
@@ -169,18 +136,47 @@ class APIFrameHelper {
|
||||
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);
|
||||
|
||||
// Pointers first (4 bytes each)
|
||||
socket::Socket *socket_{nullptr};
|
||||
std::unique_ptr<socket::Socket> socket_owned_;
|
||||
|
||||
// Common state enum for all frame helpers
|
||||
// Note: Not all states are used by all implementations
|
||||
// - INITIALIZE: Used by both Noise and Plaintext
|
||||
// - CLIENT_HELLO, SERVER_HELLO, HANDSHAKE: Only used by Noise protocol
|
||||
// - DATA: Used by both Noise and Plaintext
|
||||
// - CLOSED: Used by both Noise and Plaintext
|
||||
// - FAILED: Used by both Noise and Plaintext
|
||||
// - EXPLICIT_REJECT: Only used by Noise protocol
|
||||
enum class State : uint8_t {
|
||||
INITIALIZE = 1,
|
||||
CLIENT_HELLO = 2, // Noise only
|
||||
SERVER_HELLO = 3, // Noise only
|
||||
HANDSHAKE = 4, // Noise only
|
||||
DATA = 5,
|
||||
CLOSED = 6,
|
||||
FAILED = 7,
|
||||
EXPLICIT_REJECT = 8, // Noise only
|
||||
};
|
||||
|
||||
// Containers (size varies, but typically 12+ bytes on 32-bit)
|
||||
std::deque<SendBuffer> tx_buf_;
|
||||
std::string info_;
|
||||
std::vector<struct iovec> reusable_iovs_;
|
||||
std::vector<uint8_t> rx_buf_;
|
||||
|
||||
// Group smaller types together
|
||||
uint16_t rx_buf_len_ = 0;
|
||||
State state_{State::INITIALIZE};
|
||||
uint8_t frame_header_padding_{0};
|
||||
uint8_t frame_footer_size_{0};
|
||||
|
||||
// Reusable IOV array for write_protobuf_packets to avoid repeated allocations
|
||||
std::vector<struct iovec> reusable_iovs_;
|
||||
|
||||
// Receive buffer for reading frame data
|
||||
std::vector<uint8_t> rx_buf_;
|
||||
uint16_t rx_buf_len_ = 0;
|
||||
// 5 bytes total, 3 bytes padding
|
||||
|
||||
// Common initialization for both plaintext and noise protocols
|
||||
APIError init_common_();
|
||||
|
||||
// Helper method to handle socket read results
|
||||
APIError handle_socket_read_result_(ssize_t received);
|
||||
};
|
||||
|
||||
#ifdef USE_API_NOISE
|
||||
@@ -199,8 +195,8 @@ class APINoiseFrameHelper : public APIFrameHelper {
|
||||
APIError init() override;
|
||||
APIError loop() override;
|
||||
APIError read_packet(ReadPacketBuffer *buffer) override;
|
||||
APIError write_protobuf_packet(uint16_t type, ProtoWriteBuffer buffer) override;
|
||||
APIError write_protobuf_packets(ProtoWriteBuffer buffer, const std::vector<PacketInfo> &packets) override;
|
||||
APIError write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) override;
|
||||
APIError write_protobuf_packets(ProtoWriteBuffer buffer, std::span<const PacketInfo> packets) override;
|
||||
// Get the frame header padding required by this protocol
|
||||
uint8_t frame_header_padding() override { return frame_header_padding_; }
|
||||
// Get the frame footer size required by this protocol
|
||||
@@ -213,19 +209,28 @@ class APINoiseFrameHelper : public APIFrameHelper {
|
||||
APIError init_handshake_();
|
||||
APIError check_handshake_finished_();
|
||||
void send_explicit_handshake_reject_(const std::string &reason);
|
||||
|
||||
// Pointers first (4 bytes each)
|
||||
NoiseHandshakeState *handshake_{nullptr};
|
||||
NoiseCipherState *send_cipher_{nullptr};
|
||||
NoiseCipherState *recv_cipher_{nullptr};
|
||||
|
||||
// Shared pointer (8 bytes on 32-bit = 4 bytes control block pointer + 4 bytes object pointer)
|
||||
std::shared_ptr<APINoiseContext> ctx_;
|
||||
|
||||
// Vector (12 bytes on 32-bit)
|
||||
std::vector<uint8_t> prologue_;
|
||||
|
||||
// NoiseProtocolId (size depends on implementation)
|
||||
NoiseProtocolId nid_;
|
||||
|
||||
// Group small types together
|
||||
// Fixed-size header buffer for noise protocol:
|
||||
// 1 byte for indicator + 2 bytes for message size (16-bit value, not varint)
|
||||
// Note: Maximum message size is UINT16_MAX (65535), with a limit of 128 bytes during handshake phase
|
||||
uint8_t rx_header_buf_[3];
|
||||
uint8_t rx_header_buf_len_ = 0;
|
||||
|
||||
std::vector<uint8_t> prologue_;
|
||||
|
||||
std::shared_ptr<APINoiseContext> ctx_;
|
||||
NoiseHandshakeState *handshake_{nullptr};
|
||||
NoiseCipherState *send_cipher_{nullptr};
|
||||
NoiseCipherState *recv_cipher_{nullptr};
|
||||
NoiseProtocolId nid_;
|
||||
// 4 bytes total, no padding
|
||||
};
|
||||
#endif // USE_API_NOISE
|
||||
|
||||
@@ -244,14 +249,20 @@ class APIPlaintextFrameHelper : public APIFrameHelper {
|
||||
APIError init() override;
|
||||
APIError loop() override;
|
||||
APIError read_packet(ReadPacketBuffer *buffer) override;
|
||||
APIError write_protobuf_packet(uint16_t type, ProtoWriteBuffer buffer) override;
|
||||
APIError write_protobuf_packets(ProtoWriteBuffer buffer, const std::vector<PacketInfo> &packets) override;
|
||||
APIError write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) override;
|
||||
APIError write_protobuf_packets(ProtoWriteBuffer buffer, std::span<const PacketInfo> packets) override;
|
||||
uint8_t frame_header_padding() override { return frame_header_padding_; }
|
||||
// Get the frame footer size required by this protocol
|
||||
uint8_t frame_footer_size() override { return frame_footer_size_; }
|
||||
|
||||
protected:
|
||||
APIError try_read_frame_(ParsedFrame *frame);
|
||||
|
||||
// Group 2-byte aligned types
|
||||
uint16_t rx_header_parsed_type_ = 0;
|
||||
uint16_t rx_header_parsed_len_ = 0;
|
||||
|
||||
// Group 1-byte types together
|
||||
// Fixed-size header buffer for plaintext protocol:
|
||||
// We now store the indicator byte + the two varints.
|
||||
// To match noise protocol's maximum message size (UINT16_MAX = 65535), we need:
|
||||
@@ -263,8 +274,7 @@ class APIPlaintextFrameHelper : public APIFrameHelper {
|
||||
uint8_t rx_header_buf_[6]; // 1 byte indicator + 5 bytes for varints (3 for size + 2 for type)
|
||||
uint8_t rx_header_buf_pos_ = 0;
|
||||
bool rx_header_parsed_ = false;
|
||||
uint16_t rx_header_parsed_type_ = 0;
|
||||
uint16_t rx_header_parsed_len_ = 0;
|
||||
// 8 bytes total, no padding needed
|
||||
};
|
||||
#endif
|
||||
|
||||
|
||||
+829
-8036
File diff suppressed because it is too large
Load Diff
+532
-685
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -2,9 +2,10 @@
|
||||
// See script/api_protobuf/api_protobuf.py
|
||||
#pragma once
|
||||
|
||||
#include "api_pb2.h"
|
||||
#include "esphome/core/defines.h"
|
||||
|
||||
#include "api_pb2.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace api {
|
||||
|
||||
@@ -19,7 +20,7 @@ class APIServerConnectionBase : public ProtoService {
|
||||
|
||||
template<typename T> bool send_message(const T &msg) {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
this->log_send_message_(T::message_name(), msg.dump());
|
||||
this->log_send_message_(msg.message_name(), msg.dump());
|
||||
#endif
|
||||
return this->send_message_(msg, T::MESSAGE_TYPE);
|
||||
}
|
||||
@@ -68,9 +69,11 @@ class APIServerConnectionBase : public ProtoService {
|
||||
virtual void on_get_time_request(const GetTimeRequest &value){};
|
||||
virtual void on_get_time_response(const GetTimeResponse &value){};
|
||||
|
||||
#ifdef USE_API_SERVICES
|
||||
virtual void on_execute_service_request(const ExecuteServiceRequest &value){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_ESP32_CAMERA
|
||||
#ifdef USE_CAMERA
|
||||
virtual void on_camera_image_request(const CameraImageRequest &value){};
|
||||
#endif
|
||||
|
||||
@@ -199,7 +202,7 @@ class APIServerConnectionBase : public ProtoService {
|
||||
virtual void on_update_command_request(const UpdateCommandRequest &value){};
|
||||
#endif
|
||||
protected:
|
||||
bool read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) override;
|
||||
void read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) override;
|
||||
};
|
||||
|
||||
class APIServerConnection : public APIServerConnectionBase {
|
||||
@@ -215,14 +218,16 @@ class APIServerConnection : public APIServerConnectionBase {
|
||||
virtual void subscribe_homeassistant_services(const SubscribeHomeassistantServicesRequest &msg) = 0;
|
||||
virtual void subscribe_home_assistant_states(const SubscribeHomeAssistantStatesRequest &msg) = 0;
|
||||
virtual GetTimeResponse get_time(const GetTimeRequest &msg) = 0;
|
||||
#ifdef USE_API_SERVICES
|
||||
virtual void execute_service(const ExecuteServiceRequest &msg) = 0;
|
||||
#endif
|
||||
#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
|
||||
#ifdef USE_CAMERA
|
||||
virtual void camera_image(const CameraImageRequest &msg) = 0;
|
||||
#endif
|
||||
#ifdef USE_CLIMATE
|
||||
@@ -332,14 +337,16 @@ class APIServerConnection : public APIServerConnectionBase {
|
||||
void on_subscribe_homeassistant_services_request(const SubscribeHomeassistantServicesRequest &msg) override;
|
||||
void on_subscribe_home_assistant_states_request(const SubscribeHomeAssistantStatesRequest &msg) override;
|
||||
void on_get_time_request(const GetTimeRequest &msg) override;
|
||||
#ifdef USE_API_SERVICES
|
||||
void on_execute_service_request(const ExecuteServiceRequest &msg) override;
|
||||
#endif
|
||||
#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
|
||||
#ifdef USE_CAMERA
|
||||
void on_camera_image_request(const CameraImageRequest &msg) override;
|
||||
#endif
|
||||
#ifdef USE_CLIMATE
|
||||
|
||||
@@ -1,361 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "proto.h"
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
namespace esphome {
|
||||
namespace api {
|
||||
|
||||
class ProtoSize {
|
||||
public:
|
||||
/**
|
||||
* @brief ProtoSize class for Protocol Buffer serialization size calculation
|
||||
*
|
||||
* This class provides static methods to calculate the exact byte counts needed
|
||||
* for encoding various Protocol Buffer field types. All methods are designed to be
|
||||
* efficient for the common case where many fields have default values.
|
||||
*
|
||||
* Implements Protocol Buffer encoding size calculation according to:
|
||||
* https://protobuf.dev/programming-guides/encoding/
|
||||
*
|
||||
* Key features:
|
||||
* - Early-return optimization for zero/default values
|
||||
* - Direct total_size updates to avoid unnecessary additions
|
||||
* - Specialized handling for different field types according to protobuf spec
|
||||
* - Templated helpers for repeated fields and messages
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Calculates the size in bytes needed to encode a uint32_t value as a varint
|
||||
*
|
||||
* @param value The uint32_t value to calculate size for
|
||||
* @return The number of bytes needed to encode the value
|
||||
*/
|
||||
static inline uint32_t varint(uint32_t value) {
|
||||
// Optimized varint size calculation using leading zeros
|
||||
// Each 7 bits requires one byte in the varint encoding
|
||||
if (value < 128)
|
||||
return 1; // 7 bits, common case for small values
|
||||
|
||||
// For larger values, count bytes needed based on the position of the highest bit set
|
||||
if (value < 16384) {
|
||||
return 2; // 14 bits
|
||||
} else if (value < 2097152) {
|
||||
return 3; // 21 bits
|
||||
} else if (value < 268435456) {
|
||||
return 4; // 28 bits
|
||||
} else {
|
||||
return 5; // 32 bits (maximum for uint32_t)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Calculates the size in bytes needed to encode a uint64_t value as a varint
|
||||
*
|
||||
* @param value The uint64_t value to calculate size for
|
||||
* @return The number of bytes needed to encode the value
|
||||
*/
|
||||
static inline uint32_t varint(uint64_t value) {
|
||||
// Handle common case of values fitting in uint32_t (vast majority of use cases)
|
||||
if (value <= UINT32_MAX) {
|
||||
return varint(static_cast<uint32_t>(value));
|
||||
}
|
||||
|
||||
// For larger values, determine size based on highest bit position
|
||||
if (value < (1ULL << 35)) {
|
||||
return 5; // 35 bits
|
||||
} else if (value < (1ULL << 42)) {
|
||||
return 6; // 42 bits
|
||||
} else if (value < (1ULL << 49)) {
|
||||
return 7; // 49 bits
|
||||
} else if (value < (1ULL << 56)) {
|
||||
return 8; // 56 bits
|
||||
} else if (value < (1ULL << 63)) {
|
||||
return 9; // 63 bits
|
||||
} else {
|
||||
return 10; // 64 bits (maximum for uint64_t)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Calculates the size in bytes needed to encode an int32_t value as a varint
|
||||
*
|
||||
* Special handling is needed for negative values, which are sign-extended to 64 bits
|
||||
* in Protocol Buffers, resulting in a 10-byte varint.
|
||||
*
|
||||
* @param value The int32_t value to calculate size for
|
||||
* @return The number of bytes needed to encode the value
|
||||
*/
|
||||
static inline uint32_t varint(int32_t value) {
|
||||
// Negative values are sign-extended to 64 bits in protocol buffers,
|
||||
// which always results in a 10-byte varint for negative int32
|
||||
if (value < 0) {
|
||||
return 10; // Negative int32 is always 10 bytes long
|
||||
}
|
||||
// For non-negative values, use the uint32_t implementation
|
||||
return varint(static_cast<uint32_t>(value));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Calculates the size in bytes needed to encode an int64_t value as a varint
|
||||
*
|
||||
* @param value The int64_t value to calculate size for
|
||||
* @return The number of bytes needed to encode the value
|
||||
*/
|
||||
static inline uint32_t varint(int64_t value) {
|
||||
// For int64_t, we convert to uint64_t and calculate the size
|
||||
// This works because the bit pattern determines the encoding size,
|
||||
// and we've handled negative int32 values as a special case above
|
||||
return varint(static_cast<uint64_t>(value));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Calculates the size in bytes needed to encode a field ID and wire type
|
||||
*
|
||||
* @param field_id The field identifier
|
||||
* @param type The wire type value (from the WireType enum in the protobuf spec)
|
||||
* @return The number of bytes needed to encode the field ID and wire type
|
||||
*/
|
||||
static inline uint32_t field(uint32_t field_id, uint32_t type) {
|
||||
uint32_t tag = (field_id << 3) | (type & 0b111);
|
||||
return varint(tag);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Common parameters for all add_*_field methods
|
||||
*
|
||||
* All add_*_field methods follow these common patterns:
|
||||
*
|
||||
* @param total_size Reference to the total message size to update
|
||||
* @param field_id_size Pre-calculated size of the field ID in bytes
|
||||
* @param value The value to calculate size for (type varies)
|
||||
* @param force Whether to calculate size even if the value is default/zero/empty
|
||||
*
|
||||
* Each method follows this implementation pattern:
|
||||
* 1. Skip calculation if value is default (0, false, empty) and not forced
|
||||
* 2. Calculate the size based on the field's encoding rules
|
||||
* 3. Add the field_id_size + calculated value size to total_size
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Calculates and adds the size of an int32 field to the total message size
|
||||
*/
|
||||
static inline void add_int32_field(uint32_t &total_size, uint32_t field_id_size, int32_t value, bool force = false) {
|
||||
// Skip calculation if value is zero and not forced
|
||||
if (value == 0 && !force) {
|
||||
return; // No need to update total_size
|
||||
}
|
||||
|
||||
// Calculate and directly add to total_size
|
||||
if (value < 0) {
|
||||
// Negative values are encoded as 10-byte varints in protobuf
|
||||
total_size += field_id_size + 10;
|
||||
} else {
|
||||
// For non-negative values, use the standard varint size
|
||||
total_size += field_id_size + varint(static_cast<uint32_t>(value));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Calculates and adds the size of a uint32 field to the total message size
|
||||
*/
|
||||
static inline void add_uint32_field(uint32_t &total_size, uint32_t field_id_size, uint32_t value,
|
||||
bool force = false) {
|
||||
// Skip calculation if value is zero and not forced
|
||||
if (value == 0 && !force) {
|
||||
return; // No need to update total_size
|
||||
}
|
||||
|
||||
// Calculate and directly add to total_size
|
||||
total_size += field_id_size + varint(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Calculates and adds the size of a boolean field to the total message size
|
||||
*/
|
||||
static inline void add_bool_field(uint32_t &total_size, uint32_t field_id_size, bool value, bool force = false) {
|
||||
// Skip calculation if value is false and not forced
|
||||
if (!value && !force) {
|
||||
return; // No need to update total_size
|
||||
}
|
||||
|
||||
// Boolean fields always use 1 byte when true
|
||||
total_size += field_id_size + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Calculates and adds the size of a fixed field to the total message size
|
||||
*
|
||||
* Fixed fields always take exactly N bytes (4 for fixed32/float, 8 for fixed64/double).
|
||||
*
|
||||
* @tparam NumBytes The number of bytes for this fixed field (4 or 8)
|
||||
* @param is_nonzero Whether the value is non-zero
|
||||
*/
|
||||
template<uint32_t NumBytes>
|
||||
static inline void add_fixed_field(uint32_t &total_size, uint32_t field_id_size, bool is_nonzero,
|
||||
bool force = false) {
|
||||
// Skip calculation if value is zero and not forced
|
||||
if (!is_nonzero && !force) {
|
||||
return; // No need to update total_size
|
||||
}
|
||||
|
||||
// Fixed fields always take exactly NumBytes
|
||||
total_size += field_id_size + NumBytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Calculates and adds the size of an enum field to the total message size
|
||||
*
|
||||
* Enum fields are encoded as uint32 varints.
|
||||
*/
|
||||
static inline void add_enum_field(uint32_t &total_size, uint32_t field_id_size, uint32_t value, bool force = false) {
|
||||
// Skip calculation if value is zero and not forced
|
||||
if (value == 0 && !force) {
|
||||
return; // No need to update total_size
|
||||
}
|
||||
|
||||
// Enums are encoded as uint32
|
||||
total_size += field_id_size + varint(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Calculates and adds the size of a sint32 field to the total message size
|
||||
*
|
||||
* Sint32 fields use ZigZag encoding, which is more efficient for negative values.
|
||||
*/
|
||||
static inline void add_sint32_field(uint32_t &total_size, uint32_t field_id_size, int32_t value, bool force = false) {
|
||||
// Skip calculation if value is zero and not forced
|
||||
if (value == 0 && !force) {
|
||||
return; // No need to update total_size
|
||||
}
|
||||
|
||||
// ZigZag encoding for sint32: (n << 1) ^ (n >> 31)
|
||||
uint32_t zigzag = (static_cast<uint32_t>(value) << 1) ^ (static_cast<uint32_t>(value >> 31));
|
||||
total_size += field_id_size + varint(zigzag);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Calculates and adds the size of an int64 field to the total message size
|
||||
*/
|
||||
static inline void add_int64_field(uint32_t &total_size, uint32_t field_id_size, int64_t value, bool force = false) {
|
||||
// Skip calculation if value is zero and not forced
|
||||
if (value == 0 && !force) {
|
||||
return; // No need to update total_size
|
||||
}
|
||||
|
||||
// Calculate and directly add to total_size
|
||||
total_size += field_id_size + varint(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Calculates and adds the size of a uint64 field to the total message size
|
||||
*/
|
||||
static inline void add_uint64_field(uint32_t &total_size, uint32_t field_id_size, uint64_t value,
|
||||
bool force = false) {
|
||||
// Skip calculation if value is zero and not forced
|
||||
if (value == 0 && !force) {
|
||||
return; // No need to update total_size
|
||||
}
|
||||
|
||||
// Calculate and directly add to total_size
|
||||
total_size += field_id_size + varint(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Calculates and adds the size of a sint64 field to the total message size
|
||||
*
|
||||
* Sint64 fields use ZigZag encoding, which is more efficient for negative values.
|
||||
*/
|
||||
static inline void add_sint64_field(uint32_t &total_size, uint32_t field_id_size, int64_t value, bool force = false) {
|
||||
// Skip calculation if value is zero and not forced
|
||||
if (value == 0 && !force) {
|
||||
return; // No need to update total_size
|
||||
}
|
||||
|
||||
// ZigZag encoding for sint64: (n << 1) ^ (n >> 63)
|
||||
uint64_t zigzag = (static_cast<uint64_t>(value) << 1) ^ (static_cast<uint64_t>(value >> 63));
|
||||
total_size += field_id_size + varint(zigzag);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Calculates and adds the size of a string/bytes field to the total message size
|
||||
*/
|
||||
static inline void add_string_field(uint32_t &total_size, uint32_t field_id_size, const std::string &str,
|
||||
bool force = false) {
|
||||
// Skip calculation if string is empty and not forced
|
||||
if (str.empty() && !force) {
|
||||
return; // No need to update total_size
|
||||
}
|
||||
|
||||
// Calculate and directly add to total_size
|
||||
const uint32_t str_size = static_cast<uint32_t>(str.size());
|
||||
total_size += field_id_size + varint(str_size) + str_size;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Calculates and adds the size of a nested message field to the total message size
|
||||
*
|
||||
* This helper function directly updates the total_size reference if the nested size
|
||||
* is greater than zero or force is true.
|
||||
*
|
||||
* @param nested_size The pre-calculated size of the nested message
|
||||
*/
|
||||
static inline void add_message_field(uint32_t &total_size, uint32_t field_id_size, uint32_t nested_size,
|
||||
bool force = false) {
|
||||
// Skip calculation if nested message is empty and not forced
|
||||
if (nested_size == 0 && !force) {
|
||||
return; // No need to update total_size
|
||||
}
|
||||
|
||||
// Calculate and directly add to total_size
|
||||
// Field ID + length varint + nested message content
|
||||
total_size += field_id_size + varint(nested_size) + nested_size;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Calculates and adds the size of a nested message field to the total message size
|
||||
*
|
||||
* This templated version directly takes a message object, calculates its size internally,
|
||||
* and updates the total_size reference. This eliminates the need for a temporary variable
|
||||
* at the call site.
|
||||
*
|
||||
* @tparam MessageType The type of the nested message (inferred from parameter)
|
||||
* @param message The nested message object
|
||||
*/
|
||||
template<typename MessageType>
|
||||
static inline void add_message_object(uint32_t &total_size, uint32_t field_id_size, const MessageType &message,
|
||||
bool force = false) {
|
||||
uint32_t nested_size = 0;
|
||||
message.calculate_size(nested_size);
|
||||
|
||||
// Use the base implementation with the calculated nested_size
|
||||
add_message_field(total_size, field_id_size, nested_size, force);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Calculates and adds the sizes of all messages in a repeated field to the total message size
|
||||
*
|
||||
* This helper processes a vector of message objects, calculating the size for each message
|
||||
* and adding it to the total size.
|
||||
*
|
||||
* @tparam MessageType The type of the nested messages in the vector
|
||||
* @param messages Vector of message objects
|
||||
*/
|
||||
template<typename MessageType>
|
||||
static inline void add_repeated_message(uint32_t &total_size, uint32_t field_id_size,
|
||||
const std::vector<MessageType> &messages) {
|
||||
// Skip if the vector is empty
|
||||
if (messages.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// For repeated fields, always use force=true
|
||||
for (const auto &message : messages) {
|
||||
add_message_object(total_size, field_id_size, message, true);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace api
|
||||
} // namespace esphome
|
||||
@@ -47,6 +47,11 @@ void APIServer::setup() {
|
||||
}
|
||||
#endif
|
||||
|
||||
// Schedule reboot if no clients connect within timeout
|
||||
if (this->reboot_timeout_ != 0) {
|
||||
this->schedule_reboot_timeout_();
|
||||
}
|
||||
|
||||
this->socket_ = socket::socket_ip_loop_monitored(SOCK_STREAM, 0); // monitored for incoming connections
|
||||
if (this->socket_ == nullptr) {
|
||||
ESP_LOGW(TAG, "Could not create socket");
|
||||
@@ -91,34 +96,42 @@ void APIServer::setup() {
|
||||
|
||||
#ifdef USE_LOGGER
|
||||
if (logger::global_logger != nullptr) {
|
||||
logger::global_logger->add_on_log_callback([this](int level, const char *tag, const char *message) {
|
||||
if (this->shutting_down_) {
|
||||
// Don't try to send logs during shutdown
|
||||
// as it could result in a recursion and
|
||||
// we would be filling a buffer we are trying to clear
|
||||
return;
|
||||
}
|
||||
for (auto &c : this->clients_) {
|
||||
if (!c->remove_)
|
||||
c->try_send_log_message(level, tag, message);
|
||||
}
|
||||
});
|
||||
}
|
||||
#endif
|
||||
|
||||
this->last_connected_ = millis();
|
||||
|
||||
#ifdef USE_ESP32_CAMERA
|
||||
if (esp32_camera::global_esp32_camera != nullptr && !esp32_camera::global_esp32_camera->is_internal()) {
|
||||
esp32_camera::global_esp32_camera->add_image_callback(
|
||||
[this](const std::shared_ptr<esp32_camera::CameraImage> &image) {
|
||||
logger::global_logger->add_on_log_callback(
|
||||
[this](int level, const char *tag, const char *message, size_t message_len) {
|
||||
if (this->shutting_down_) {
|
||||
// Don't try to send logs during shutdown
|
||||
// as it could result in a recursion and
|
||||
// we would be filling a buffer we are trying to clear
|
||||
return;
|
||||
}
|
||||
for (auto &c : this->clients_) {
|
||||
if (!c->remove_)
|
||||
c->set_camera_state(image);
|
||||
if (!c->flags_.remove)
|
||||
c->try_send_log_message(level, tag, message, message_len);
|
||||
}
|
||||
});
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_CAMERA
|
||||
if (camera::Camera::instance() != nullptr && !camera::Camera::instance()->is_internal()) {
|
||||
camera::Camera::instance()->add_image_callback([this](const std::shared_ptr<camera::CameraImage> &image) {
|
||||
for (auto &c : this->clients_) {
|
||||
if (!c->flags_.remove)
|
||||
c->set_camera_state(image);
|
||||
}
|
||||
});
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void APIServer::schedule_reboot_timeout_() {
|
||||
this->status_set_warning();
|
||||
this->set_timeout("api_reboot", this->reboot_timeout_, []() {
|
||||
if (!global_api_server->is_connected()) {
|
||||
ESP_LOGE(TAG, "No clients; rebooting");
|
||||
App.reboot();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void APIServer::loop() {
|
||||
@@ -130,51 +143,63 @@ void APIServer::loop() {
|
||||
auto sock = this->socket_->accept_loop_monitored((struct sockaddr *) &source_addr, &addr_len);
|
||||
if (!sock)
|
||||
break;
|
||||
ESP_LOGD(TAG, "Accepted %s", sock->getpeername().c_str());
|
||||
ESP_LOGD(TAG, "Accept %s", sock->getpeername().c_str());
|
||||
|
||||
auto *conn = new APIConnection(std::move(sock), this);
|
||||
this->clients_.emplace_back(conn);
|
||||
conn->start();
|
||||
|
||||
// Clear warning status and cancel reboot when first client connects
|
||||
if (this->clients_.size() == 1 && this->reboot_timeout_ != 0) {
|
||||
this->status_clear_warning();
|
||||
this->cancel_timeout("api_reboot");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (this->clients_.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 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];
|
||||
|
||||
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
|
||||
}
|
||||
// Check network connectivity once for all clients
|
||||
if (!network::is_connected()) {
|
||||
// Network is down - disconnect all clients
|
||||
for (auto &client : this->clients_) {
|
||||
client->on_fatal_error();
|
||||
ESP_LOGW(TAG, "%s: Network down; disconnect", client->get_client_combined_info().c_str());
|
||||
}
|
||||
// Continue to process and clean up the clients below
|
||||
}
|
||||
|
||||
if (this->reboot_timeout_ != 0) {
|
||||
const uint32_t now = millis();
|
||||
if (!this->is_connected()) {
|
||||
if (now - this->last_connected_ > this->reboot_timeout_) {
|
||||
ESP_LOGE(TAG, "No client connected; rebooting");
|
||||
App.reboot();
|
||||
}
|
||||
this->status_set_warning();
|
||||
} else {
|
||||
this->last_connected_ = now;
|
||||
this->status_clear_warning();
|
||||
size_t client_index = 0;
|
||||
while (client_index < this->clients_.size()) {
|
||||
auto &client = this->clients_[client_index];
|
||||
|
||||
if (!client->flags_.remove) {
|
||||
// Common case: process active client
|
||||
client->loop();
|
||||
client_index++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Rare case: handle disconnection
|
||||
#ifdef USE_API_CLIENT_DISCONNECTED_TRIGGER
|
||||
this->client_disconnected_trigger_->trigger(client->client_info_, client->client_peername_);
|
||||
#endif
|
||||
ESP_LOGV(TAG, "Remove connection %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();
|
||||
|
||||
// Schedule reboot when last client disconnects
|
||||
if (this->clients_.empty() && this->reboot_timeout_ != 0) {
|
||||
this->schedule_reboot_timeout_();
|
||||
}
|
||||
// Don't increment client_index since we need to process the swapped element
|
||||
}
|
||||
}
|
||||
|
||||
@@ -193,6 +218,7 @@ void APIServer::dump_config() {
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef USE_API_PASSWORD
|
||||
bool APIServer::uses_password() const { return !this->password_.empty(); }
|
||||
|
||||
bool APIServer::check_password(const std::string &password) const {
|
||||
@@ -223,192 +249,129 @@ bool APIServer::check_password(const std::string &password) const {
|
||||
|
||||
return result == 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
void APIServer::handle_disconnect(APIConnection *conn) {}
|
||||
|
||||
// Macro for entities without extra parameters
|
||||
#define API_DISPATCH_UPDATE(entity_type, entity_name) \
|
||||
void APIServer::on_##entity_name##_update(entity_type *obj) { /* NOLINT(bugprone-macro-parentheses) */ \
|
||||
if (obj->is_internal()) \
|
||||
return; \
|
||||
for (auto &c : this->clients_) \
|
||||
c->send_##entity_name##_state(obj); \
|
||||
}
|
||||
|
||||
// Macro for entities with extra parameters (but parameters not used in send)
|
||||
#define API_DISPATCH_UPDATE_IGNORE_PARAMS(entity_type, entity_name, ...) \
|
||||
void APIServer::on_##entity_name##_update(entity_type *obj, __VA_ARGS__) { /* NOLINT(bugprone-macro-parentheses) */ \
|
||||
if (obj->is_internal()) \
|
||||
return; \
|
||||
for (auto &c : this->clients_) \
|
||||
c->send_##entity_name##_state(obj); \
|
||||
}
|
||||
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
void APIServer::on_binary_sensor_update(binary_sensor::BinarySensor *obj, bool state) {
|
||||
if (obj->is_internal())
|
||||
return;
|
||||
for (auto &c : this->clients_)
|
||||
c->send_binary_sensor_state(obj);
|
||||
}
|
||||
API_DISPATCH_UPDATE(binary_sensor::BinarySensor, binary_sensor)
|
||||
#endif
|
||||
|
||||
#ifdef USE_COVER
|
||||
void APIServer::on_cover_update(cover::Cover *obj) {
|
||||
if (obj->is_internal())
|
||||
return;
|
||||
for (auto &c : this->clients_)
|
||||
c->send_cover_state(obj);
|
||||
}
|
||||
API_DISPATCH_UPDATE(cover::Cover, cover)
|
||||
#endif
|
||||
|
||||
#ifdef USE_FAN
|
||||
void APIServer::on_fan_update(fan::Fan *obj) {
|
||||
if (obj->is_internal())
|
||||
return;
|
||||
for (auto &c : this->clients_)
|
||||
c->send_fan_state(obj);
|
||||
}
|
||||
API_DISPATCH_UPDATE(fan::Fan, fan)
|
||||
#endif
|
||||
|
||||
#ifdef USE_LIGHT
|
||||
void APIServer::on_light_update(light::LightState *obj) {
|
||||
if (obj->is_internal())
|
||||
return;
|
||||
for (auto &c : this->clients_)
|
||||
c->send_light_state(obj);
|
||||
}
|
||||
API_DISPATCH_UPDATE(light::LightState, light)
|
||||
#endif
|
||||
|
||||
#ifdef USE_SENSOR
|
||||
void APIServer::on_sensor_update(sensor::Sensor *obj, float state) {
|
||||
if (obj->is_internal())
|
||||
return;
|
||||
for (auto &c : this->clients_)
|
||||
c->send_sensor_state(obj);
|
||||
}
|
||||
API_DISPATCH_UPDATE_IGNORE_PARAMS(sensor::Sensor, sensor, float state)
|
||||
#endif
|
||||
|
||||
#ifdef USE_SWITCH
|
||||
void APIServer::on_switch_update(switch_::Switch *obj, bool state) {
|
||||
if (obj->is_internal())
|
||||
return;
|
||||
for (auto &c : this->clients_)
|
||||
c->send_switch_state(obj);
|
||||
}
|
||||
API_DISPATCH_UPDATE_IGNORE_PARAMS(switch_::Switch, switch, bool state)
|
||||
#endif
|
||||
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
void APIServer::on_text_sensor_update(text_sensor::TextSensor *obj, const std::string &state) {
|
||||
if (obj->is_internal())
|
||||
return;
|
||||
for (auto &c : this->clients_)
|
||||
c->send_text_sensor_state(obj);
|
||||
}
|
||||
API_DISPATCH_UPDATE_IGNORE_PARAMS(text_sensor::TextSensor, text_sensor, const std::string &state)
|
||||
#endif
|
||||
|
||||
#ifdef USE_CLIMATE
|
||||
void APIServer::on_climate_update(climate::Climate *obj) {
|
||||
if (obj->is_internal())
|
||||
return;
|
||||
for (auto &c : this->clients_)
|
||||
c->send_climate_state(obj);
|
||||
}
|
||||
API_DISPATCH_UPDATE(climate::Climate, climate)
|
||||
#endif
|
||||
|
||||
#ifdef USE_NUMBER
|
||||
void APIServer::on_number_update(number::Number *obj, float state) {
|
||||
if (obj->is_internal())
|
||||
return;
|
||||
for (auto &c : this->clients_)
|
||||
c->send_number_state(obj);
|
||||
}
|
||||
API_DISPATCH_UPDATE_IGNORE_PARAMS(number::Number, number, float state)
|
||||
#endif
|
||||
|
||||
#ifdef USE_DATETIME_DATE
|
||||
void APIServer::on_date_update(datetime::DateEntity *obj) {
|
||||
if (obj->is_internal())
|
||||
return;
|
||||
for (auto &c : this->clients_)
|
||||
c->send_date_state(obj);
|
||||
}
|
||||
API_DISPATCH_UPDATE(datetime::DateEntity, date)
|
||||
#endif
|
||||
|
||||
#ifdef USE_DATETIME_TIME
|
||||
void APIServer::on_time_update(datetime::TimeEntity *obj) {
|
||||
if (obj->is_internal())
|
||||
return;
|
||||
for (auto &c : this->clients_)
|
||||
c->send_time_state(obj);
|
||||
}
|
||||
API_DISPATCH_UPDATE(datetime::TimeEntity, time)
|
||||
#endif
|
||||
|
||||
#ifdef USE_DATETIME_DATETIME
|
||||
void APIServer::on_datetime_update(datetime::DateTimeEntity *obj) {
|
||||
if (obj->is_internal())
|
||||
return;
|
||||
for (auto &c : this->clients_)
|
||||
c->send_datetime_state(obj);
|
||||
}
|
||||
API_DISPATCH_UPDATE(datetime::DateTimeEntity, datetime)
|
||||
#endif
|
||||
|
||||
#ifdef USE_TEXT
|
||||
void APIServer::on_text_update(text::Text *obj, const std::string &state) {
|
||||
if (obj->is_internal())
|
||||
return;
|
||||
for (auto &c : this->clients_)
|
||||
c->send_text_state(obj);
|
||||
}
|
||||
API_DISPATCH_UPDATE_IGNORE_PARAMS(text::Text, text, const std::string &state)
|
||||
#endif
|
||||
|
||||
#ifdef USE_SELECT
|
||||
void APIServer::on_select_update(select::Select *obj, const std::string &state, size_t index) {
|
||||
if (obj->is_internal())
|
||||
return;
|
||||
for (auto &c : this->clients_)
|
||||
c->send_select_state(obj);
|
||||
}
|
||||
API_DISPATCH_UPDATE_IGNORE_PARAMS(select::Select, select, const std::string &state, size_t index)
|
||||
#endif
|
||||
|
||||
#ifdef USE_LOCK
|
||||
void APIServer::on_lock_update(lock::Lock *obj) {
|
||||
if (obj->is_internal())
|
||||
return;
|
||||
for (auto &c : this->clients_)
|
||||
c->send_lock_state(obj);
|
||||
}
|
||||
API_DISPATCH_UPDATE(lock::Lock, lock)
|
||||
#endif
|
||||
|
||||
#ifdef USE_VALVE
|
||||
void APIServer::on_valve_update(valve::Valve *obj) {
|
||||
if (obj->is_internal())
|
||||
return;
|
||||
for (auto &c : this->clients_)
|
||||
c->send_valve_state(obj);
|
||||
}
|
||||
API_DISPATCH_UPDATE(valve::Valve, valve)
|
||||
#endif
|
||||
|
||||
#ifdef USE_MEDIA_PLAYER
|
||||
void APIServer::on_media_player_update(media_player::MediaPlayer *obj) {
|
||||
if (obj->is_internal())
|
||||
return;
|
||||
for (auto &c : this->clients_)
|
||||
c->send_media_player_state(obj);
|
||||
}
|
||||
API_DISPATCH_UPDATE(media_player::MediaPlayer, media_player)
|
||||
#endif
|
||||
|
||||
#ifdef USE_EVENT
|
||||
// Event is a special case - it's the only entity that passes extra parameters to the send method
|
||||
void APIServer::on_event(event::Event *obj, const std::string &event_type) {
|
||||
if (obj->is_internal())
|
||||
return;
|
||||
for (auto &c : this->clients_)
|
||||
c->send_event(obj, event_type);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_UPDATE
|
||||
// Update is a special case - the method is called on_update, not on_update_update
|
||||
void APIServer::on_update(update::UpdateEntity *obj) {
|
||||
if (obj->is_internal())
|
||||
return;
|
||||
for (auto &c : this->clients_)
|
||||
c->send_update_state(obj);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_ALARM_CONTROL_PANEL
|
||||
void APIServer::on_alarm_control_panel_update(alarm_control_panel::AlarmControlPanel *obj) {
|
||||
if (obj->is_internal())
|
||||
return;
|
||||
for (auto &c : this->clients_)
|
||||
c->send_alarm_control_panel_state(obj);
|
||||
}
|
||||
API_DISPATCH_UPDATE(alarm_control_panel::AlarmControlPanel, alarm_control_panel)
|
||||
#endif
|
||||
|
||||
float APIServer::get_setup_priority() const { return setup_priority::AFTER_WIFI; }
|
||||
|
||||
void APIServer::set_port(uint16_t port) { this->port_ = port; }
|
||||
|
||||
#ifdef USE_API_PASSWORD
|
||||
void APIServer::set_password(const std::string &password) { this->password_ = password; }
|
||||
#endif
|
||||
|
||||
void APIServer::set_batch_delay(uint32_t batch_delay) { this->batch_delay_ = batch_delay; }
|
||||
void APIServer::set_batch_delay(uint16_t batch_delay) { this->batch_delay_ = batch_delay; }
|
||||
|
||||
void APIServer::send_homeassistant_service_call(const HomeassistantServiceResponse &call) {
|
||||
for (auto &client : this->clients_) {
|
||||
@@ -479,7 +442,7 @@ bool APIServer::save_noise_psk(psk_t psk, bool make_active) {
|
||||
#ifdef USE_HOMEASSISTANT_TIME
|
||||
void APIServer::request_time() {
|
||||
for (auto &client : this->clients_) {
|
||||
if (!client->remove_ && client->is_authenticated())
|
||||
if (!client->flags_.remove && client->is_authenticated())
|
||||
client->send_time_request();
|
||||
}
|
||||
}
|
||||
@@ -503,8 +466,9 @@ void APIServer::on_shutdown() {
|
||||
for (auto &c : this->clients_) {
|
||||
if (!c->send_message(DisconnectRequest())) {
|
||||
// If we can't send the disconnect request directly (tx_buffer full),
|
||||
// schedule it in the batch so it will be sent with the 5ms timer
|
||||
c->schedule_message_(nullptr, &APIConnection::try_send_disconnect_request, DisconnectRequest::MESSAGE_TYPE);
|
||||
// schedule it at the front of the batch so it will be sent with priority
|
||||
c->schedule_message_front_(nullptr, &APIConnection::try_send_disconnect_request, DisconnectRequest::MESSAGE_TYPE,
|
||||
DisconnectRequest::ESTIMATED_SIZE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,9 @@
|
||||
#include "esphome/core/log.h"
|
||||
#include "list_entities.h"
|
||||
#include "subscribe_state.h"
|
||||
#ifdef USE_API_SERVICES
|
||||
#include "user_services.h"
|
||||
#endif
|
||||
|
||||
#include <vector>
|
||||
|
||||
@@ -35,13 +37,15 @@ class APIServer : public Component, public Controller {
|
||||
void dump_config() override;
|
||||
void on_shutdown() override;
|
||||
bool teardown() override;
|
||||
#ifdef USE_API_PASSWORD
|
||||
bool check_password(const std::string &password) const;
|
||||
bool uses_password() const;
|
||||
void set_port(uint16_t port);
|
||||
void set_password(const std::string &password);
|
||||
#endif
|
||||
void set_port(uint16_t port);
|
||||
void set_reboot_timeout(uint32_t reboot_timeout);
|
||||
void set_batch_delay(uint32_t batch_delay);
|
||||
uint32_t get_batch_delay() const { return batch_delay_; }
|
||||
void set_batch_delay(uint16_t batch_delay);
|
||||
uint16_t get_batch_delay() const { return batch_delay_; }
|
||||
|
||||
// Get reference to shared buffer for API connections
|
||||
std::vector<uint8_t> &get_shared_buffer_ref() { return shared_write_buffer_; }
|
||||
@@ -54,7 +58,7 @@ class APIServer : public Component, public Controller {
|
||||
|
||||
void handle_disconnect(APIConnection *conn);
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
void on_binary_sensor_update(binary_sensor::BinarySensor *obj, bool state) override;
|
||||
void on_binary_sensor_update(binary_sensor::BinarySensor *obj) override;
|
||||
#endif
|
||||
#ifdef USE_COVER
|
||||
void on_cover_update(cover::Cover *obj) override;
|
||||
@@ -105,7 +109,9 @@ class APIServer : public Component, public Controller {
|
||||
void on_media_player_update(media_player::MediaPlayer *obj) override;
|
||||
#endif
|
||||
void send_homeassistant_service_call(const HomeassistantServiceResponse &call);
|
||||
#ifdef USE_API_SERVICES
|
||||
void register_user_service(UserServiceDescriptor *descriptor) { this->user_services_.push_back(descriptor); }
|
||||
#endif
|
||||
#ifdef USE_HOMEASSISTANT_TIME
|
||||
void request_time();
|
||||
#endif
|
||||
@@ -134,27 +140,49 @@ class APIServer : public Component, public Controller {
|
||||
void get_home_assistant_state(std::string entity_id, optional<std::string> attribute,
|
||||
std::function<void(std::string)> f);
|
||||
const std::vector<HomeAssistantStateSubscription> &get_state_subs() const;
|
||||
#ifdef USE_API_SERVICES
|
||||
const std::vector<UserServiceDescriptor *> &get_user_services() const { return this->user_services_; }
|
||||
#endif
|
||||
|
||||
#ifdef USE_API_CLIENT_CONNECTED_TRIGGER
|
||||
Trigger<std::string, std::string> *get_client_connected_trigger() const { return this->client_connected_trigger_; }
|
||||
#endif
|
||||
#ifdef USE_API_CLIENT_DISCONNECTED_TRIGGER
|
||||
Trigger<std::string, std::string> *get_client_disconnected_trigger() const {
|
||||
return this->client_disconnected_trigger_;
|
||||
}
|
||||
#endif
|
||||
|
||||
protected:
|
||||
bool shutting_down_ = false;
|
||||
void schedule_reboot_timeout_();
|
||||
// Pointers and pointer-like types first (4 bytes each)
|
||||
std::unique_ptr<socket::Socket> socket_ = nullptr;
|
||||
uint16_t port_{6053};
|
||||
#ifdef USE_API_CLIENT_CONNECTED_TRIGGER
|
||||
Trigger<std::string, std::string> *client_connected_trigger_ = new Trigger<std::string, std::string>();
|
||||
#endif
|
||||
#ifdef USE_API_CLIENT_DISCONNECTED_TRIGGER
|
||||
Trigger<std::string, std::string> *client_disconnected_trigger_ = new Trigger<std::string, std::string>();
|
||||
#endif
|
||||
|
||||
// 4-byte aligned types
|
||||
uint32_t reboot_timeout_{300000};
|
||||
uint32_t batch_delay_{100};
|
||||
uint32_t last_connected_{0};
|
||||
|
||||
// Vectors and strings (12 bytes each on 32-bit)
|
||||
std::vector<std::unique_ptr<APIConnection>> clients_;
|
||||
#ifdef USE_API_PASSWORD
|
||||
std::string password_;
|
||||
#endif
|
||||
std::vector<uint8_t> shared_write_buffer_; // Shared proto write buffer for all connections
|
||||
std::vector<HomeAssistantStateSubscription> state_subs_;
|
||||
#ifdef USE_API_SERVICES
|
||||
std::vector<UserServiceDescriptor *> user_services_;
|
||||
Trigger<std::string, std::string> *client_connected_trigger_ = new Trigger<std::string, std::string>();
|
||||
Trigger<std::string, std::string> *client_disconnected_trigger_ = new Trigger<std::string, std::string>();
|
||||
#endif
|
||||
|
||||
// Group smaller types together
|
||||
uint16_t port_{6053};
|
||||
uint16_t batch_delay_{100};
|
||||
bool shutting_down_ = false;
|
||||
// 5 bytes used, 3 bytes padding
|
||||
|
||||
#ifdef USE_API_NOISE
|
||||
std::shared_ptr<APINoiseContext> noise_ctx_ = std::make_shared<APINoiseContext>();
|
||||
|
||||
@@ -4,9 +4,15 @@ import asyncio
|
||||
from datetime import datetime
|
||||
import logging
|
||||
from typing import TYPE_CHECKING, Any
|
||||
import warnings
|
||||
|
||||
from aioesphomeapi import APIClient, parse_log_message
|
||||
from aioesphomeapi.log_runner import async_run
|
||||
# Suppress protobuf version warnings
|
||||
with warnings.catch_warnings():
|
||||
warnings.filterwarnings(
|
||||
"ignore", category=UserWarning, message=".*Protobuf gencode version.*"
|
||||
)
|
||||
from aioesphomeapi import APIClient, parse_log_message
|
||||
from aioesphomeapi.log_runner import async_run
|
||||
|
||||
from esphome.const import CONF_KEY, CONF_PASSWORD, CONF_PORT, __version__
|
||||
from esphome.core import CORE
|
||||
|
||||
@@ -3,10 +3,13 @@
|
||||
#include <map>
|
||||
#include "api_server.h"
|
||||
#ifdef USE_API
|
||||
#ifdef USE_API_SERVICES
|
||||
#include "user_services.h"
|
||||
#endif
|
||||
namespace esphome {
|
||||
namespace api {
|
||||
|
||||
#ifdef USE_API_SERVICES
|
||||
template<typename T, typename... Ts> class CustomAPIDeviceService : public UserServiceBase<Ts...> {
|
||||
public:
|
||||
CustomAPIDeviceService(const std::string &name, const std::array<std::string, sizeof...(Ts)> &arg_names, T *obj,
|
||||
@@ -19,6 +22,7 @@ template<typename T, typename... Ts> class CustomAPIDeviceService : public UserS
|
||||
T *obj_;
|
||||
void (T::*callback_)(Ts...);
|
||||
};
|
||||
#endif // USE_API_SERVICES
|
||||
|
||||
class CustomAPIDevice {
|
||||
public:
|
||||
@@ -46,12 +50,14 @@ class CustomAPIDevice {
|
||||
* @param name The name of the service to register.
|
||||
* @param arg_names The name of the arguments for the service, must match the arguments of the function.
|
||||
*/
|
||||
#ifdef USE_API_SERVICES
|
||||
template<typename T, typename... Ts>
|
||||
void register_service(void (T::*callback)(Ts...), const std::string &name,
|
||||
const std::array<std::string, sizeof...(Ts)> &arg_names) {
|
||||
auto *service = new CustomAPIDeviceService<T, Ts...>(name, arg_names, (T *) this, callback); // NOLINT
|
||||
global_api_server->register_user_service(service);
|
||||
}
|
||||
#endif
|
||||
|
||||
/** Register a custom native API service that will show up in Home Assistant.
|
||||
*
|
||||
@@ -71,10 +77,12 @@ class CustomAPIDevice {
|
||||
* @param callback The member function to call when the service is triggered.
|
||||
* @param name The name of the arguments for the service, must match the arguments of the function.
|
||||
*/
|
||||
#ifdef USE_API_SERVICES
|
||||
template<typename T> void register_service(void (T::*callback)(), const std::string &name) {
|
||||
auto *service = new CustomAPIDeviceService<T>(name, {}, (T *) this, callback); // NOLINT
|
||||
global_api_server->register_user_service(service);
|
||||
}
|
||||
#endif
|
||||
|
||||
/** Subscribe to the state (or attribute state) of an entity from Home Assistant.
|
||||
*
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "list_entities.h"
|
||||
#ifdef USE_API
|
||||
#include "api_connection.h"
|
||||
#include "api_pb2.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/util.h"
|
||||
@@ -8,153 +9,85 @@
|
||||
namespace esphome {
|
||||
namespace api {
|
||||
|
||||
// Generate entity handler implementations using macros
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
bool ListEntitiesIterator::on_binary_sensor(binary_sensor::BinarySensor *binary_sensor) {
|
||||
this->client_->send_binary_sensor_info(binary_sensor);
|
||||
return true;
|
||||
}
|
||||
LIST_ENTITIES_HANDLER(binary_sensor, binary_sensor::BinarySensor, ListEntitiesBinarySensorResponse)
|
||||
#endif
|
||||
#ifdef USE_COVER
|
||||
bool ListEntitiesIterator::on_cover(cover::Cover *cover) {
|
||||
this->client_->send_cover_info(cover);
|
||||
return true;
|
||||
}
|
||||
LIST_ENTITIES_HANDLER(cover, cover::Cover, ListEntitiesCoverResponse)
|
||||
#endif
|
||||
#ifdef USE_FAN
|
||||
bool ListEntitiesIterator::on_fan(fan::Fan *fan) {
|
||||
this->client_->send_fan_info(fan);
|
||||
return true;
|
||||
}
|
||||
LIST_ENTITIES_HANDLER(fan, fan::Fan, ListEntitiesFanResponse)
|
||||
#endif
|
||||
#ifdef USE_LIGHT
|
||||
bool ListEntitiesIterator::on_light(light::LightState *light) {
|
||||
this->client_->send_light_info(light);
|
||||
return true;
|
||||
}
|
||||
LIST_ENTITIES_HANDLER(light, light::LightState, ListEntitiesLightResponse)
|
||||
#endif
|
||||
#ifdef USE_SENSOR
|
||||
bool ListEntitiesIterator::on_sensor(sensor::Sensor *sensor) {
|
||||
this->client_->send_sensor_info(sensor);
|
||||
return true;
|
||||
}
|
||||
LIST_ENTITIES_HANDLER(sensor, sensor::Sensor, ListEntitiesSensorResponse)
|
||||
#endif
|
||||
#ifdef USE_SWITCH
|
||||
bool ListEntitiesIterator::on_switch(switch_::Switch *a_switch) {
|
||||
this->client_->send_switch_info(a_switch);
|
||||
return true;
|
||||
}
|
||||
LIST_ENTITIES_HANDLER(switch, switch_::Switch, ListEntitiesSwitchResponse)
|
||||
#endif
|
||||
#ifdef USE_BUTTON
|
||||
bool ListEntitiesIterator::on_button(button::Button *button) {
|
||||
this->client_->send_button_info(button);
|
||||
return true;
|
||||
}
|
||||
LIST_ENTITIES_HANDLER(button, button::Button, ListEntitiesButtonResponse)
|
||||
#endif
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
bool ListEntitiesIterator::on_text_sensor(text_sensor::TextSensor *text_sensor) {
|
||||
this->client_->send_text_sensor_info(text_sensor);
|
||||
return true;
|
||||
}
|
||||
LIST_ENTITIES_HANDLER(text_sensor, text_sensor::TextSensor, ListEntitiesTextSensorResponse)
|
||||
#endif
|
||||
#ifdef USE_LOCK
|
||||
bool ListEntitiesIterator::on_lock(lock::Lock *a_lock) {
|
||||
this->client_->send_lock_info(a_lock);
|
||||
return true;
|
||||
}
|
||||
LIST_ENTITIES_HANDLER(lock, lock::Lock, ListEntitiesLockResponse)
|
||||
#endif
|
||||
#ifdef USE_VALVE
|
||||
bool ListEntitiesIterator::on_valve(valve::Valve *valve) {
|
||||
this->client_->send_valve_info(valve);
|
||||
return true;
|
||||
}
|
||||
LIST_ENTITIES_HANDLER(valve, valve::Valve, ListEntitiesValveResponse)
|
||||
#endif
|
||||
#ifdef USE_CAMERA
|
||||
LIST_ENTITIES_HANDLER(camera, camera::Camera, ListEntitiesCameraResponse)
|
||||
#endif
|
||||
#ifdef USE_CLIMATE
|
||||
LIST_ENTITIES_HANDLER(climate, climate::Climate, ListEntitiesClimateResponse)
|
||||
#endif
|
||||
#ifdef USE_NUMBER
|
||||
LIST_ENTITIES_HANDLER(number, number::Number, ListEntitiesNumberResponse)
|
||||
#endif
|
||||
#ifdef USE_DATETIME_DATE
|
||||
LIST_ENTITIES_HANDLER(date, datetime::DateEntity, ListEntitiesDateResponse)
|
||||
#endif
|
||||
#ifdef USE_DATETIME_TIME
|
||||
LIST_ENTITIES_HANDLER(time, datetime::TimeEntity, ListEntitiesTimeResponse)
|
||||
#endif
|
||||
#ifdef USE_DATETIME_DATETIME
|
||||
LIST_ENTITIES_HANDLER(datetime, datetime::DateTimeEntity, ListEntitiesDateTimeResponse)
|
||||
#endif
|
||||
#ifdef USE_TEXT
|
||||
LIST_ENTITIES_HANDLER(text, text::Text, ListEntitiesTextResponse)
|
||||
#endif
|
||||
#ifdef USE_SELECT
|
||||
LIST_ENTITIES_HANDLER(select, select::Select, ListEntitiesSelectResponse)
|
||||
#endif
|
||||
#ifdef USE_MEDIA_PLAYER
|
||||
LIST_ENTITIES_HANDLER(media_player, media_player::MediaPlayer, ListEntitiesMediaPlayerResponse)
|
||||
#endif
|
||||
#ifdef USE_ALARM_CONTROL_PANEL
|
||||
LIST_ENTITIES_HANDLER(alarm_control_panel, alarm_control_panel::AlarmControlPanel,
|
||||
ListEntitiesAlarmControlPanelResponse)
|
||||
#endif
|
||||
#ifdef USE_EVENT
|
||||
LIST_ENTITIES_HANDLER(event, event::Event, ListEntitiesEventResponse)
|
||||
#endif
|
||||
#ifdef USE_UPDATE
|
||||
LIST_ENTITIES_HANDLER(update, update::UpdateEntity, ListEntitiesUpdateResponse)
|
||||
#endif
|
||||
|
||||
// Special cases that don't follow the pattern
|
||||
bool ListEntitiesIterator::on_end() { return this->client_->send_list_info_done(); }
|
||||
|
||||
ListEntitiesIterator::ListEntitiesIterator(APIConnection *client) : client_(client) {}
|
||||
|
||||
#ifdef USE_API_SERVICES
|
||||
bool ListEntitiesIterator::on_service(UserServiceDescriptor *service) {
|
||||
auto resp = service->encode_list_service_response();
|
||||
return this->client_->send_message(resp);
|
||||
}
|
||||
|
||||
#ifdef USE_ESP32_CAMERA
|
||||
bool ListEntitiesIterator::on_camera(esp32_camera::ESP32Camera *camera) {
|
||||
this->client_->send_camera_info(camera);
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_CLIMATE
|
||||
bool ListEntitiesIterator::on_climate(climate::Climate *climate) {
|
||||
this->client_->send_climate_info(climate);
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_NUMBER
|
||||
bool ListEntitiesIterator::on_number(number::Number *number) {
|
||||
this->client_->send_number_info(number);
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_DATETIME_DATE
|
||||
bool ListEntitiesIterator::on_date(datetime::DateEntity *date) {
|
||||
this->client_->send_date_info(date);
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_DATETIME_TIME
|
||||
bool ListEntitiesIterator::on_time(datetime::TimeEntity *time) {
|
||||
this->client_->send_time_info(time);
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_DATETIME_DATETIME
|
||||
bool ListEntitiesIterator::on_datetime(datetime::DateTimeEntity *datetime) {
|
||||
this->client_->send_datetime_info(datetime);
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_TEXT
|
||||
bool ListEntitiesIterator::on_text(text::Text *text) {
|
||||
this->client_->send_text_info(text);
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_SELECT
|
||||
bool ListEntitiesIterator::on_select(select::Select *select) {
|
||||
this->client_->send_select_info(select);
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_MEDIA_PLAYER
|
||||
bool ListEntitiesIterator::on_media_player(media_player::MediaPlayer *media_player) {
|
||||
this->client_->send_media_player_info(media_player);
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_ALARM_CONTROL_PANEL
|
||||
bool ListEntitiesIterator::on_alarm_control_panel(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) {
|
||||
this->client_->send_alarm_control_panel_info(a_alarm_control_panel);
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_EVENT
|
||||
bool ListEntitiesIterator::on_event(event::Event *event) {
|
||||
this->client_->send_event_info(event);
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_UPDATE
|
||||
bool ListEntitiesIterator::on_update(update::UpdateEntity *update) {
|
||||
this->client_->send_update_info(update);
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace api
|
||||
|
||||
@@ -9,75 +9,85 @@ namespace api {
|
||||
|
||||
class APIConnection;
|
||||
|
||||
// Macro for generating ListEntitiesIterator handlers
|
||||
// Calls schedule_message_ with try_send_*_info
|
||||
#define LIST_ENTITIES_HANDLER(entity_type, EntityClass, ResponseType) \
|
||||
bool ListEntitiesIterator::on_##entity_type(EntityClass *entity) { /* NOLINT(bugprone-macro-parentheses) */ \
|
||||
return this->client_->schedule_message_(entity, &APIConnection::try_send_##entity_type##_info, \
|
||||
ResponseType::MESSAGE_TYPE, ResponseType::ESTIMATED_SIZE); \
|
||||
}
|
||||
|
||||
class ListEntitiesIterator : public ComponentIterator {
|
||||
public:
|
||||
ListEntitiesIterator(APIConnection *client);
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
bool on_binary_sensor(binary_sensor::BinarySensor *binary_sensor) override;
|
||||
bool on_binary_sensor(binary_sensor::BinarySensor *entity) override;
|
||||
#endif
|
||||
#ifdef USE_COVER
|
||||
bool on_cover(cover::Cover *cover) override;
|
||||
bool on_cover(cover::Cover *entity) override;
|
||||
#endif
|
||||
#ifdef USE_FAN
|
||||
bool on_fan(fan::Fan *fan) override;
|
||||
bool on_fan(fan::Fan *entity) override;
|
||||
#endif
|
||||
#ifdef USE_LIGHT
|
||||
bool on_light(light::LightState *light) override;
|
||||
bool on_light(light::LightState *entity) override;
|
||||
#endif
|
||||
#ifdef USE_SENSOR
|
||||
bool on_sensor(sensor::Sensor *sensor) override;
|
||||
bool on_sensor(sensor::Sensor *entity) override;
|
||||
#endif
|
||||
#ifdef USE_SWITCH
|
||||
bool on_switch(switch_::Switch *a_switch) override;
|
||||
bool on_switch(switch_::Switch *entity) override;
|
||||
#endif
|
||||
#ifdef USE_BUTTON
|
||||
bool on_button(button::Button *button) override;
|
||||
bool on_button(button::Button *entity) override;
|
||||
#endif
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
bool on_text_sensor(text_sensor::TextSensor *text_sensor) override;
|
||||
bool on_text_sensor(text_sensor::TextSensor *entity) override;
|
||||
#endif
|
||||
#ifdef USE_API_SERVICES
|
||||
bool on_service(UserServiceDescriptor *service) override;
|
||||
#ifdef USE_ESP32_CAMERA
|
||||
bool on_camera(esp32_camera::ESP32Camera *camera) override;
|
||||
#endif
|
||||
#ifdef USE_CAMERA
|
||||
bool on_camera(camera::Camera *entity) override;
|
||||
#endif
|
||||
#ifdef USE_CLIMATE
|
||||
bool on_climate(climate::Climate *climate) override;
|
||||
bool on_climate(climate::Climate *entity) override;
|
||||
#endif
|
||||
#ifdef USE_NUMBER
|
||||
bool on_number(number::Number *number) override;
|
||||
bool on_number(number::Number *entity) override;
|
||||
#endif
|
||||
#ifdef USE_DATETIME_DATE
|
||||
bool on_date(datetime::DateEntity *date) override;
|
||||
bool on_date(datetime::DateEntity *entity) override;
|
||||
#endif
|
||||
#ifdef USE_DATETIME_TIME
|
||||
bool on_time(datetime::TimeEntity *time) override;
|
||||
bool on_time(datetime::TimeEntity *entity) override;
|
||||
#endif
|
||||
#ifdef USE_DATETIME_DATETIME
|
||||
bool on_datetime(datetime::DateTimeEntity *datetime) override;
|
||||
bool on_datetime(datetime::DateTimeEntity *entity) override;
|
||||
#endif
|
||||
#ifdef USE_TEXT
|
||||
bool on_text(text::Text *text) override;
|
||||
bool on_text(text::Text *entity) override;
|
||||
#endif
|
||||
#ifdef USE_SELECT
|
||||
bool on_select(select::Select *select) override;
|
||||
bool on_select(select::Select *entity) override;
|
||||
#endif
|
||||
#ifdef USE_LOCK
|
||||
bool on_lock(lock::Lock *a_lock) override;
|
||||
bool on_lock(lock::Lock *entity) override;
|
||||
#endif
|
||||
#ifdef USE_VALVE
|
||||
bool on_valve(valve::Valve *valve) override;
|
||||
bool on_valve(valve::Valve *entity) override;
|
||||
#endif
|
||||
#ifdef USE_MEDIA_PLAYER
|
||||
bool on_media_player(media_player::MediaPlayer *media_player) override;
|
||||
bool on_media_player(media_player::MediaPlayer *entity) override;
|
||||
#endif
|
||||
#ifdef USE_ALARM_CONTROL_PANEL
|
||||
bool on_alarm_control_panel(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) override;
|
||||
bool on_alarm_control_panel(alarm_control_panel::AlarmControlPanel *entity) override;
|
||||
#endif
|
||||
#ifdef USE_EVENT
|
||||
bool on_event(event::Event *event) override;
|
||||
bool on_event(event::Event *entity) override;
|
||||
#endif
|
||||
#ifdef USE_UPDATE
|
||||
bool on_update(update::UpdateEntity *update) override;
|
||||
bool on_update(update::UpdateEntity *entity) override;
|
||||
#endif
|
||||
bool on_end() override;
|
||||
bool completed() { return this->state_ == IteratorState::NONE; }
|
||||
|
||||
+532
-26
File diff suppressed because it is too large
Load Diff
@@ -6,73 +6,67 @@
|
||||
namespace esphome {
|
||||
namespace api {
|
||||
|
||||
// Generate entity handler implementations using macros
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
bool InitialStateIterator::on_binary_sensor(binary_sensor::BinarySensor *binary_sensor) {
|
||||
return this->client_->send_binary_sensor_state(binary_sensor);
|
||||
}
|
||||
INITIAL_STATE_HANDLER(binary_sensor, binary_sensor::BinarySensor)
|
||||
#endif
|
||||
#ifdef USE_COVER
|
||||
bool InitialStateIterator::on_cover(cover::Cover *cover) { return this->client_->send_cover_state(cover); }
|
||||
INITIAL_STATE_HANDLER(cover, cover::Cover)
|
||||
#endif
|
||||
#ifdef USE_FAN
|
||||
bool InitialStateIterator::on_fan(fan::Fan *fan) { return this->client_->send_fan_state(fan); }
|
||||
INITIAL_STATE_HANDLER(fan, fan::Fan)
|
||||
#endif
|
||||
#ifdef USE_LIGHT
|
||||
bool InitialStateIterator::on_light(light::LightState *light) { return this->client_->send_light_state(light); }
|
||||
INITIAL_STATE_HANDLER(light, light::LightState)
|
||||
#endif
|
||||
#ifdef USE_SENSOR
|
||||
bool InitialStateIterator::on_sensor(sensor::Sensor *sensor) { return this->client_->send_sensor_state(sensor); }
|
||||
INITIAL_STATE_HANDLER(sensor, sensor::Sensor)
|
||||
#endif
|
||||
#ifdef USE_SWITCH
|
||||
bool InitialStateIterator::on_switch(switch_::Switch *a_switch) { return this->client_->send_switch_state(a_switch); }
|
||||
INITIAL_STATE_HANDLER(switch, switch_::Switch)
|
||||
#endif
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
bool InitialStateIterator::on_text_sensor(text_sensor::TextSensor *text_sensor) {
|
||||
return this->client_->send_text_sensor_state(text_sensor);
|
||||
}
|
||||
INITIAL_STATE_HANDLER(text_sensor, text_sensor::TextSensor)
|
||||
#endif
|
||||
#ifdef USE_CLIMATE
|
||||
bool InitialStateIterator::on_climate(climate::Climate *climate) { return this->client_->send_climate_state(climate); }
|
||||
INITIAL_STATE_HANDLER(climate, climate::Climate)
|
||||
#endif
|
||||
#ifdef USE_NUMBER
|
||||
bool InitialStateIterator::on_number(number::Number *number) { return this->client_->send_number_state(number); }
|
||||
INITIAL_STATE_HANDLER(number, number::Number)
|
||||
#endif
|
||||
#ifdef USE_DATETIME_DATE
|
||||
bool InitialStateIterator::on_date(datetime::DateEntity *date) { return this->client_->send_date_state(date); }
|
||||
INITIAL_STATE_HANDLER(date, datetime::DateEntity)
|
||||
#endif
|
||||
#ifdef USE_DATETIME_TIME
|
||||
bool InitialStateIterator::on_time(datetime::TimeEntity *time) { return this->client_->send_time_state(time); }
|
||||
INITIAL_STATE_HANDLER(time, datetime::TimeEntity)
|
||||
#endif
|
||||
#ifdef USE_DATETIME_DATETIME
|
||||
bool InitialStateIterator::on_datetime(datetime::DateTimeEntity *datetime) {
|
||||
return this->client_->send_datetime_state(datetime);
|
||||
}
|
||||
INITIAL_STATE_HANDLER(datetime, datetime::DateTimeEntity)
|
||||
#endif
|
||||
#ifdef USE_TEXT
|
||||
bool InitialStateIterator::on_text(text::Text *text) { return this->client_->send_text_state(text); }
|
||||
INITIAL_STATE_HANDLER(text, text::Text)
|
||||
#endif
|
||||
#ifdef USE_SELECT
|
||||
bool InitialStateIterator::on_select(select::Select *select) { return this->client_->send_select_state(select); }
|
||||
INITIAL_STATE_HANDLER(select, select::Select)
|
||||
#endif
|
||||
#ifdef USE_LOCK
|
||||
bool InitialStateIterator::on_lock(lock::Lock *a_lock) { return this->client_->send_lock_state(a_lock); }
|
||||
INITIAL_STATE_HANDLER(lock, lock::Lock)
|
||||
#endif
|
||||
#ifdef USE_VALVE
|
||||
bool InitialStateIterator::on_valve(valve::Valve *valve) { return this->client_->send_valve_state(valve); }
|
||||
INITIAL_STATE_HANDLER(valve, valve::Valve)
|
||||
#endif
|
||||
#ifdef USE_MEDIA_PLAYER
|
||||
bool InitialStateIterator::on_media_player(media_player::MediaPlayer *media_player) {
|
||||
return this->client_->send_media_player_state(media_player);
|
||||
}
|
||||
INITIAL_STATE_HANDLER(media_player, media_player::MediaPlayer)
|
||||
#endif
|
||||
#ifdef USE_ALARM_CONTROL_PANEL
|
||||
bool InitialStateIterator::on_alarm_control_panel(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) {
|
||||
return this->client_->send_alarm_control_panel_state(a_alarm_control_panel);
|
||||
}
|
||||
INITIAL_STATE_HANDLER(alarm_control_panel, alarm_control_panel::AlarmControlPanel)
|
||||
#endif
|
||||
#ifdef USE_UPDATE
|
||||
bool InitialStateIterator::on_update(update::UpdateEntity *update) { return this->client_->send_update_state(update); }
|
||||
INITIAL_STATE_HANDLER(update, update::UpdateEntity)
|
||||
#endif
|
||||
|
||||
// Special cases (button and event) are already defined inline in subscribe_state.h
|
||||
|
||||
InitialStateIterator::InitialStateIterator(APIConnection *client) : client_(client) {}
|
||||
|
||||
} // namespace api
|
||||
|
||||
@@ -10,71 +10,78 @@ namespace api {
|
||||
|
||||
class APIConnection;
|
||||
|
||||
// Macro for generating InitialStateIterator handlers
|
||||
// Calls send_*_state
|
||||
#define INITIAL_STATE_HANDLER(entity_type, EntityClass) \
|
||||
bool InitialStateIterator::on_##entity_type(EntityClass *entity) { /* NOLINT(bugprone-macro-parentheses) */ \
|
||||
return this->client_->send_##entity_type##_state(entity); \
|
||||
}
|
||||
|
||||
class InitialStateIterator : public ComponentIterator {
|
||||
public:
|
||||
InitialStateIterator(APIConnection *client);
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
bool on_binary_sensor(binary_sensor::BinarySensor *binary_sensor) override;
|
||||
bool on_binary_sensor(binary_sensor::BinarySensor *entity) override;
|
||||
#endif
|
||||
#ifdef USE_COVER
|
||||
bool on_cover(cover::Cover *cover) override;
|
||||
bool on_cover(cover::Cover *entity) override;
|
||||
#endif
|
||||
#ifdef USE_FAN
|
||||
bool on_fan(fan::Fan *fan) override;
|
||||
bool on_fan(fan::Fan *entity) override;
|
||||
#endif
|
||||
#ifdef USE_LIGHT
|
||||
bool on_light(light::LightState *light) override;
|
||||
bool on_light(light::LightState *entity) override;
|
||||
#endif
|
||||
#ifdef USE_SENSOR
|
||||
bool on_sensor(sensor::Sensor *sensor) override;
|
||||
bool on_sensor(sensor::Sensor *entity) override;
|
||||
#endif
|
||||
#ifdef USE_SWITCH
|
||||
bool on_switch(switch_::Switch *a_switch) override;
|
||||
bool on_switch(switch_::Switch *entity) override;
|
||||
#endif
|
||||
#ifdef USE_BUTTON
|
||||
bool on_button(button::Button *button) override { return true; };
|
||||
#endif
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
bool on_text_sensor(text_sensor::TextSensor *text_sensor) override;
|
||||
bool on_text_sensor(text_sensor::TextSensor *entity) override;
|
||||
#endif
|
||||
#ifdef USE_CLIMATE
|
||||
bool on_climate(climate::Climate *climate) override;
|
||||
bool on_climate(climate::Climate *entity) override;
|
||||
#endif
|
||||
#ifdef USE_NUMBER
|
||||
bool on_number(number::Number *number) override;
|
||||
bool on_number(number::Number *entity) override;
|
||||
#endif
|
||||
#ifdef USE_DATETIME_DATE
|
||||
bool on_date(datetime::DateEntity *date) override;
|
||||
bool on_date(datetime::DateEntity *entity) override;
|
||||
#endif
|
||||
#ifdef USE_DATETIME_TIME
|
||||
bool on_time(datetime::TimeEntity *time) override;
|
||||
bool on_time(datetime::TimeEntity *entity) override;
|
||||
#endif
|
||||
#ifdef USE_DATETIME_DATETIME
|
||||
bool on_datetime(datetime::DateTimeEntity *datetime) override;
|
||||
bool on_datetime(datetime::DateTimeEntity *entity) override;
|
||||
#endif
|
||||
#ifdef USE_TEXT
|
||||
bool on_text(text::Text *text) override;
|
||||
bool on_text(text::Text *entity) override;
|
||||
#endif
|
||||
#ifdef USE_SELECT
|
||||
bool on_select(select::Select *select) override;
|
||||
bool on_select(select::Select *entity) override;
|
||||
#endif
|
||||
#ifdef USE_LOCK
|
||||
bool on_lock(lock::Lock *a_lock) override;
|
||||
bool on_lock(lock::Lock *entity) override;
|
||||
#endif
|
||||
#ifdef USE_VALVE
|
||||
bool on_valve(valve::Valve *valve) override;
|
||||
bool on_valve(valve::Valve *entity) override;
|
||||
#endif
|
||||
#ifdef USE_MEDIA_PLAYER
|
||||
bool on_media_player(media_player::MediaPlayer *media_player) override;
|
||||
bool on_media_player(media_player::MediaPlayer *entity) override;
|
||||
#endif
|
||||
#ifdef USE_ALARM_CONTROL_PANEL
|
||||
bool on_alarm_control_panel(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) override;
|
||||
bool on_alarm_control_panel(alarm_control_panel::AlarmControlPanel *entity) override;
|
||||
#endif
|
||||
#ifdef USE_EVENT
|
||||
bool on_event(event::Event *event) override { return true; };
|
||||
#endif
|
||||
#ifdef USE_UPDATE
|
||||
bool on_update(update::UpdateEntity *update) override;
|
||||
bool on_update(update::UpdateEntity *entity) override;
|
||||
#endif
|
||||
bool completed() { return this->state_ == IteratorState::NONE; }
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "esphome/core/automation.h"
|
||||
#include "api_pb2.h"
|
||||
|
||||
#ifdef USE_API_SERVICES
|
||||
namespace esphome {
|
||||
namespace api {
|
||||
|
||||
@@ -73,3 +74,4 @@ template<typename... Ts> class UserServiceTrigger : public UserServiceBase<Ts...
|
||||
|
||||
} // namespace api
|
||||
} // namespace esphome
|
||||
#endif // USE_API_SERVICES
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/as3935/as3935.h"
|
||||
#include "esphome/components/spi/spi.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/binary_sensor/binary_sensor.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace as3935_spi {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user