1
0
mirror of https://github.com/esphome/esphome.git synced 2025-11-13 13:25:50 +00:00

Compare commits

..

1 Commits

Author SHA1 Message Date
J. Nick Koston
ab9287e959 Reduce modbus heap alloc 2025-07-04 21:51:52 -05:00
355 changed files with 8628 additions and 13129 deletions

View File

@@ -214,51 +214,17 @@ jobs:
if: matrix.os == 'windows-latest' if: matrix.os == 'windows-latest'
run: | run: |
./venv/Scripts/activate ./venv/Scripts/activate
pytest -vv --cov-report=xml --tb=native -n auto tests --ignore=tests/integration/ pytest -vv --cov-report=xml --tb=native -n auto tests
- name: Run pytest - name: Run pytest
if: matrix.os == 'ubuntu-latest' || matrix.os == 'macOS-latest' if: matrix.os == 'ubuntu-latest' || matrix.os == 'macOS-latest'
run: | run: |
. venv/bin/activate . venv/bin/activate
pytest -vv --cov-report=xml --tb=native -n auto tests --ignore=tests/integration/ pytest -vv --cov-report=xml --tb=native -n auto tests
- name: Upload coverage to Codecov - name: Upload coverage to Codecov
uses: codecov/codecov-action@v5.4.3 uses: codecov/codecov-action@v5.4.3
with: with:
token: ${{ secrets.CODECOV_TOKEN }} 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: clang-format:
name: Check clang-format name: Check clang-format
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
@@ -528,7 +494,6 @@ jobs:
- flake8 - flake8
- pylint - pylint
- pytest - pytest
- integration-tests
- pyupgrade - pyupgrade
- clang-tidy - clang-tidy
- list-components - list-components

View File

@@ -1,14 +1,6 @@
--- ---
# See https://pre-commit.com for more information # See https://pre-commit.com for more information
# See https://pre-commit.com/hooks.html for more hooks # 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: repos:
- repo: https://github.com/astral-sh/ruff-pre-commit - repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version. # Ruff version.

View File

@@ -87,7 +87,6 @@ esphome/components/bp1658cj/* @Cossid
esphome/components/bp5758d/* @Cossid esphome/components/bp5758d/* @Cossid
esphome/components/button/* @esphome/core esphome/components/button/* @esphome/core
esphome/components/bytebuffer/* @clydebarrow esphome/components/bytebuffer/* @clydebarrow
esphome/components/camera/* @DT-art1 @bdraco
esphome/components/canbus/* @danielschramm @mvturnho esphome/components/canbus/* @danielschramm @mvturnho
esphome/components/cap1188/* @mreditor97 esphome/components/cap1188/* @mreditor97
esphome/components/captive_portal/* @OttoWinter esphome/components/captive_portal/* @OttoWinter
@@ -170,7 +169,6 @@ esphome/components/ft5x06/* @clydebarrow
esphome/components/ft63x6/* @gpambrozio esphome/components/ft63x6/* @gpambrozio
esphome/components/gcja5/* @gcormier esphome/components/gcja5/* @gcormier
esphome/components/gdk101/* @Szewcson esphome/components/gdk101/* @Szewcson
esphome/components/gl_r01_i2c/* @pkejval
esphome/components/globals/* @esphome/core esphome/components/globals/* @esphome/core
esphome/components/gp2y1010au0f/* @zry98 esphome/components/gp2y1010au0f/* @zry98
esphome/components/gp8403/* @jesserockz esphome/components/gp8403/* @jesserockz
@@ -255,7 +253,6 @@ esphome/components/ln882x/* @lamauny
esphome/components/lock/* @esphome/core esphome/components/lock/* @esphome/core
esphome/components/logger/* @esphome/core esphome/components/logger/* @esphome/core
esphome/components/logger/select/* @clydebarrow esphome/components/logger/select/* @clydebarrow
esphome/components/lps22/* @nagisa
esphome/components/ltr390/* @latonita @sjtrny esphome/components/ltr390/* @latonita @sjtrny
esphome/components/ltr501/* @latonita esphome/components/ltr501/* @latonita
esphome/components/ltr_als_ps/* @latonita esphome/components/ltr_als_ps/* @latonita
@@ -444,7 +441,6 @@ esphome/components/sun/* @OttoWinter
esphome/components/sun_gtil2/* @Mat931 esphome/components/sun_gtil2/* @Mat931
esphome/components/switch/* @esphome/core esphome/components/switch/* @esphome/core
esphome/components/switch/binary_sensor/* @ssieb esphome/components/switch/binary_sensor/* @ssieb
esphome/components/sx126x/* @swoboda1337
esphome/components/sx127x/* @swoboda1337 esphome/components/sx127x/* @swoboda1337
esphome/components/syslog/* @clydebarrow esphome/components/syslog/* @clydebarrow
esphome/components/t6615/* @tylermenezes esphome/components/t6615/* @tylermenezes

View File

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

View File

@@ -10,15 +10,8 @@ from esphome.components.esp32.const import (
VARIANT_ESP32S2, VARIANT_ESP32S2,
VARIANT_ESP32S3, VARIANT_ESP32S3,
) )
from esphome.config_helpers import filter_source_files_from_platform
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.const import ( from esphome.const import CONF_ANALOG, CONF_INPUT, CONF_NUMBER, PLATFORM_ESP8266
CONF_ANALOG,
CONF_INPUT,
CONF_NUMBER,
PLATFORM_ESP8266,
PlatformFramework,
)
from esphome.core import CORE from esphome.core import CORE
CODEOWNERS = ["@esphome/core"] CODEOWNERS = ["@esphome/core"]
@@ -236,20 +229,3 @@ def validate_adc_pin(value):
)(value) )(value)
raise NotImplementedError 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,
},
}
)

View File

@@ -23,7 +23,7 @@ void APDS9960::setup() {
return; return;
} }
if (id != 0xAB && id != 0x9C && id != 0xA8 && id != 0x9E) { // APDS9960 all should have one of these IDs if (id != 0xAB && id != 0x9C && id != 0xA8) { // APDS9960 all should have one of these IDs
this->error_code_ = WRONG_ID; this->error_code_ = WRONG_ID;
this->mark_failed(); this->mark_failed();
return; return;

View File

@@ -3,7 +3,6 @@ import base64
from esphome import automation from esphome import automation
from esphome.automation import Condition from esphome.automation import Condition
import esphome.codegen as cg import esphome.codegen as cg
from esphome.config_helpers import get_logger_level
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.const import ( from esphome.const import (
CONF_ACTION, CONF_ACTION,
@@ -24,9 +23,8 @@ from esphome.const import (
CONF_TRIGGER_ID, CONF_TRIGGER_ID,
CONF_VARIABLES, CONF_VARIABLES,
) )
from esphome.core import CORE, coroutine_with_priority from esphome.core import coroutine_with_priority
DOMAIN = "api"
DEPENDENCIES = ["network"] DEPENDENCIES = ["network"]
AUTO_LOAD = ["socket"] AUTO_LOAD = ["socket"]
CODEOWNERS = ["@OttoWinter"] CODEOWNERS = ["@OttoWinter"]
@@ -52,7 +50,6 @@ SERVICE_ARG_NATIVE_TYPES = {
} }
CONF_ENCRYPTION = "encryption" CONF_ENCRYPTION = "encryption"
CONF_BATCH_DELAY = "batch_delay" CONF_BATCH_DELAY = "batch_delay"
CONF_CUSTOM_SERVICES = "custom_services"
def validate_encryption_key(value): def validate_encryption_key(value):
@@ -117,7 +114,6 @@ CONFIG_SCHEMA = cv.All(
cv.positive_time_period_milliseconds, cv.positive_time_period_milliseconds,
cv.Range(max=cv.TimePeriod(milliseconds=65535)), 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( cv.Optional(CONF_ON_CLIENT_CONNECTED): automation.validate_automation(
single=True single=True
), ),
@@ -142,11 +138,8 @@ async def to_code(config):
cg.add(var.set_reboot_timeout(config[CONF_REBOOT_TIMEOUT])) cg.add(var.set_reboot_timeout(config[CONF_REBOOT_TIMEOUT]))
cg.add(var.set_batch_delay(config[CONF_BATCH_DELAY])) cg.add(var.set_batch_delay(config[CONF_BATCH_DELAY]))
# 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, []): if actions := config.get(CONF_ACTIONS, []):
cg.add_define("USE_API_YAML_SERVICES")
for conf in actions: for conf in actions:
template_args = [] template_args = []
func_args = [] func_args = []
@@ -320,25 +313,3 @@ async def homeassistant_tag_scanned_to_code(config, action_id, template_arg, arg
@automation.register_condition("api.connected", APIConnectedCondition, {}) @automation.register_condition("api.connected", APIConnectedCondition, {})
async def api_connected_to_code(config, condition_id, template_arg, args): async def api_connected_to_code(config, condition_id, template_arg, args):
return cg.new_Pvariable(condition_id, template_arg) 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

View File

@@ -374,7 +374,6 @@ message CoverCommandRequest {
option (source) = SOURCE_CLIENT; option (source) = SOURCE_CLIENT;
option (ifdef) = "USE_COVER"; option (ifdef) = "USE_COVER";
option (no_delay) = true; option (no_delay) = true;
option (base_class) = "CommandProtoMessage";
fixed32 key = 1; fixed32 key = 1;
@@ -388,7 +387,6 @@ message CoverCommandRequest {
bool has_tilt = 6; bool has_tilt = 6;
float tilt = 7; float tilt = 7;
bool stop = 8; bool stop = 8;
uint32 device_id = 9;
} }
// ==================== FAN ==================== // ==================== FAN ====================
@@ -443,7 +441,6 @@ message FanCommandRequest {
option (source) = SOURCE_CLIENT; option (source) = SOURCE_CLIENT;
option (ifdef) = "USE_FAN"; option (ifdef) = "USE_FAN";
option (no_delay) = true; option (no_delay) = true;
option (base_class) = "CommandProtoMessage";
fixed32 key = 1; fixed32 key = 1;
bool has_state = 2; bool has_state = 2;
@@ -458,7 +455,6 @@ message FanCommandRequest {
int32 speed_level = 11; int32 speed_level = 11;
bool has_preset_mode = 12; bool has_preset_mode = 12;
string preset_mode = 13; string preset_mode = 13;
uint32 device_id = 14;
} }
// ==================== LIGHT ==================== // ==================== LIGHT ====================
@@ -527,7 +523,6 @@ message LightCommandRequest {
option (source) = SOURCE_CLIENT; option (source) = SOURCE_CLIENT;
option (ifdef) = "USE_LIGHT"; option (ifdef) = "USE_LIGHT";
option (no_delay) = true; option (no_delay) = true;
option (base_class) = "CommandProtoMessage";
fixed32 key = 1; fixed32 key = 1;
bool has_state = 2; bool has_state = 2;
@@ -556,7 +551,6 @@ message LightCommandRequest {
uint32 flash_length = 17; uint32 flash_length = 17;
bool has_effect = 18; bool has_effect = 18;
string effect = 19; string effect = 19;
uint32 device_id = 28;
} }
// ==================== SENSOR ==================== // ==================== SENSOR ====================
@@ -646,11 +640,9 @@ message SwitchCommandRequest {
option (source) = SOURCE_CLIENT; option (source) = SOURCE_CLIENT;
option (ifdef) = "USE_SWITCH"; option (ifdef) = "USE_SWITCH";
option (no_delay) = true; option (no_delay) = true;
option (base_class) = "CommandProtoMessage";
fixed32 key = 1; fixed32 key = 1;
bool state = 2; bool state = 2;
uint32 device_id = 3;
} }
// ==================== TEXT SENSOR ==================== // ==================== TEXT SENSOR ====================
@@ -807,21 +799,18 @@ enum ServiceArgType {
SERVICE_ARG_TYPE_STRING_ARRAY = 7; SERVICE_ARG_TYPE_STRING_ARRAY = 7;
} }
message ListEntitiesServicesArgument { message ListEntitiesServicesArgument {
option (ifdef) = "USE_API_SERVICES";
string name = 1; string name = 1;
ServiceArgType type = 2; ServiceArgType type = 2;
} }
message ListEntitiesServicesResponse { message ListEntitiesServicesResponse {
option (id) = 41; option (id) = 41;
option (source) = SOURCE_SERVER; option (source) = SOURCE_SERVER;
option (ifdef) = "USE_API_SERVICES";
string name = 1; string name = 1;
fixed32 key = 2; fixed32 key = 2;
repeated ListEntitiesServicesArgument args = 3; repeated ListEntitiesServicesArgument args = 3;
} }
message ExecuteServiceArgument { message ExecuteServiceArgument {
option (ifdef) = "USE_API_SERVICES";
bool bool_ = 1; bool bool_ = 1;
int32 legacy_int = 2; int32 legacy_int = 2;
float float_ = 3; float float_ = 3;
@@ -837,7 +826,6 @@ message ExecuteServiceRequest {
option (id) = 42; option (id) = 42;
option (source) = SOURCE_CLIENT; option (source) = SOURCE_CLIENT;
option (no_delay) = true; option (no_delay) = true;
option (ifdef) = "USE_API_SERVICES";
fixed32 key = 1; fixed32 key = 1;
repeated ExecuteServiceArgument args = 2; repeated ExecuteServiceArgument args = 2;
@@ -848,7 +836,7 @@ message ListEntitiesCameraResponse {
option (id) = 43; option (id) = 43;
option (base_class) = "InfoResponseProtoMessage"; option (base_class) = "InfoResponseProtoMessage";
option (source) = SOURCE_SERVER; option (source) = SOURCE_SERVER;
option (ifdef) = "USE_CAMERA"; option (ifdef) = "USE_ESP32_CAMERA";
string object_id = 1; string object_id = 1;
fixed32 key = 2; fixed32 key = 2;
@@ -862,19 +850,17 @@ message ListEntitiesCameraResponse {
message CameraImageResponse { message CameraImageResponse {
option (id) = 44; option (id) = 44;
option (base_class) = "StateResponseProtoMessage";
option (source) = SOURCE_SERVER; option (source) = SOURCE_SERVER;
option (ifdef) = "USE_CAMERA"; option (ifdef) = "USE_ESP32_CAMERA";
fixed32 key = 1; fixed32 key = 1;
bytes data = 2; bytes data = 2;
bool done = 3; bool done = 3;
uint32 device_id = 4;
} }
message CameraImageRequest { message CameraImageRequest {
option (id) = 45; option (id) = 45;
option (source) = SOURCE_CLIENT; option (source) = SOURCE_CLIENT;
option (ifdef) = "USE_CAMERA"; option (ifdef) = "USE_ESP32_CAMERA";
option (no_delay) = true; option (no_delay) = true;
bool single = 1; bool single = 1;
@@ -994,7 +980,6 @@ message ClimateCommandRequest {
option (source) = SOURCE_CLIENT; option (source) = SOURCE_CLIENT;
option (ifdef) = "USE_CLIMATE"; option (ifdef) = "USE_CLIMATE";
option (no_delay) = true; option (no_delay) = true;
option (base_class) = "CommandProtoMessage";
fixed32 key = 1; fixed32 key = 1;
bool has_mode = 2; bool has_mode = 2;
@@ -1020,7 +1005,6 @@ message ClimateCommandRequest {
string custom_preset = 21; string custom_preset = 21;
bool has_target_humidity = 22; bool has_target_humidity = 22;
float target_humidity = 23; float target_humidity = 23;
uint32 device_id = 24;
} }
// ==================== NUMBER ==================== // ==================== NUMBER ====================
@@ -1070,11 +1054,9 @@ message NumberCommandRequest {
option (source) = SOURCE_CLIENT; option (source) = SOURCE_CLIENT;
option (ifdef) = "USE_NUMBER"; option (ifdef) = "USE_NUMBER";
option (no_delay) = true; option (no_delay) = true;
option (base_class) = "CommandProtoMessage";
fixed32 key = 1; fixed32 key = 1;
float state = 2; float state = 2;
uint32 device_id = 3;
} }
// ==================== SELECT ==================== // ==================== SELECT ====================
@@ -1114,11 +1096,9 @@ message SelectCommandRequest {
option (source) = SOURCE_CLIENT; option (source) = SOURCE_CLIENT;
option (ifdef) = "USE_SELECT"; option (ifdef) = "USE_SELECT";
option (no_delay) = true; option (no_delay) = true;
option (base_class) = "CommandProtoMessage";
fixed32 key = 1; fixed32 key = 1;
string state = 2; string state = 2;
uint32 device_id = 3;
} }
// ==================== SIREN ==================== // ==================== SIREN ====================
@@ -1157,7 +1137,6 @@ message SirenCommandRequest {
option (source) = SOURCE_CLIENT; option (source) = SOURCE_CLIENT;
option (ifdef) = "USE_SIREN"; option (ifdef) = "USE_SIREN";
option (no_delay) = true; option (no_delay) = true;
option (base_class) = "CommandProtoMessage";
fixed32 key = 1; fixed32 key = 1;
bool has_state = 2; bool has_state = 2;
@@ -1168,7 +1147,6 @@ message SirenCommandRequest {
uint32 duration = 7; uint32 duration = 7;
bool has_volume = 8; bool has_volume = 8;
float volume = 9; float volume = 9;
uint32 device_id = 10;
} }
// ==================== LOCK ==================== // ==================== LOCK ====================
@@ -1223,14 +1201,12 @@ message LockCommandRequest {
option (source) = SOURCE_CLIENT; option (source) = SOURCE_CLIENT;
option (ifdef) = "USE_LOCK"; option (ifdef) = "USE_LOCK";
option (no_delay) = true; option (no_delay) = true;
option (base_class) = "CommandProtoMessage";
fixed32 key = 1; fixed32 key = 1;
LockCommand command = 2; LockCommand command = 2;
// Not yet implemented: // Not yet implemented:
bool has_code = 3; bool has_code = 3;
string code = 4; string code = 4;
uint32 device_id = 5;
} }
// ==================== BUTTON ==================== // ==================== BUTTON ====================
@@ -1256,10 +1232,8 @@ message ButtonCommandRequest {
option (source) = SOURCE_CLIENT; option (source) = SOURCE_CLIENT;
option (ifdef) = "USE_BUTTON"; option (ifdef) = "USE_BUTTON";
option (no_delay) = true; option (no_delay) = true;
option (base_class) = "CommandProtoMessage";
fixed32 key = 1; fixed32 key = 1;
uint32 device_id = 2;
} }
// ==================== MEDIA PLAYER ==================== // ==================== MEDIA PLAYER ====================
@@ -1327,7 +1301,6 @@ message MediaPlayerCommandRequest {
option (source) = SOURCE_CLIENT; option (source) = SOURCE_CLIENT;
option (ifdef) = "USE_MEDIA_PLAYER"; option (ifdef) = "USE_MEDIA_PLAYER";
option (no_delay) = true; option (no_delay) = true;
option (base_class) = "CommandProtoMessage";
fixed32 key = 1; fixed32 key = 1;
@@ -1342,7 +1315,6 @@ message MediaPlayerCommandRequest {
bool has_announcement = 8; bool has_announcement = 8;
bool announcement = 9; bool announcement = 9;
uint32 device_id = 10;
} }
// ==================== BLUETOOTH ==================== // ==================== BLUETOOTH ====================
@@ -1871,11 +1843,9 @@ message AlarmControlPanelCommandRequest {
option (source) = SOURCE_CLIENT; option (source) = SOURCE_CLIENT;
option (ifdef) = "USE_ALARM_CONTROL_PANEL"; option (ifdef) = "USE_ALARM_CONTROL_PANEL";
option (no_delay) = true; option (no_delay) = true;
option (base_class) = "CommandProtoMessage";
fixed32 key = 1; fixed32 key = 1;
AlarmControlPanelStateCommand command = 2; AlarmControlPanelStateCommand command = 2;
string code = 3; string code = 3;
uint32 device_id = 4;
} }
// ===================== TEXT ===================== // ===================== TEXT =====================
@@ -1922,11 +1892,9 @@ message TextCommandRequest {
option (source) = SOURCE_CLIENT; option (source) = SOURCE_CLIENT;
option (ifdef) = "USE_TEXT"; option (ifdef) = "USE_TEXT";
option (no_delay) = true; option (no_delay) = true;
option (base_class) = "CommandProtoMessage";
fixed32 key = 1; fixed32 key = 1;
string state = 2; string state = 2;
uint32 device_id = 3;
} }
@@ -1968,13 +1936,11 @@ message DateCommandRequest {
option (source) = SOURCE_CLIENT; option (source) = SOURCE_CLIENT;
option (ifdef) = "USE_DATETIME_DATE"; option (ifdef) = "USE_DATETIME_DATE";
option (no_delay) = true; option (no_delay) = true;
option (base_class) = "CommandProtoMessage";
fixed32 key = 1; fixed32 key = 1;
uint32 year = 2; uint32 year = 2;
uint32 month = 3; uint32 month = 3;
uint32 day = 4; uint32 day = 4;
uint32 device_id = 5;
} }
// ==================== DATETIME TIME ==================== // ==================== DATETIME TIME ====================
@@ -2015,13 +1981,11 @@ message TimeCommandRequest {
option (source) = SOURCE_CLIENT; option (source) = SOURCE_CLIENT;
option (ifdef) = "USE_DATETIME_TIME"; option (ifdef) = "USE_DATETIME_TIME";
option (no_delay) = true; option (no_delay) = true;
option (base_class) = "CommandProtoMessage";
fixed32 key = 1; fixed32 key = 1;
uint32 hour = 2; uint32 hour = 2;
uint32 minute = 3; uint32 minute = 3;
uint32 second = 4; uint32 second = 4;
uint32 device_id = 5;
} }
// ==================== EVENT ==================== // ==================== EVENT ====================
@@ -2101,13 +2065,11 @@ message ValveCommandRequest {
option (source) = SOURCE_CLIENT; option (source) = SOURCE_CLIENT;
option (ifdef) = "USE_VALVE"; option (ifdef) = "USE_VALVE";
option (no_delay) = true; option (no_delay) = true;
option (base_class) = "CommandProtoMessage";
fixed32 key = 1; fixed32 key = 1;
bool has_position = 2; bool has_position = 2;
float position = 3; float position = 3;
bool stop = 4; bool stop = 4;
uint32 device_id = 5;
} }
// ==================== DATETIME DATETIME ==================== // ==================== DATETIME DATETIME ====================
@@ -2146,11 +2108,9 @@ message DateTimeCommandRequest {
option (source) = SOURCE_CLIENT; option (source) = SOURCE_CLIENT;
option (ifdef) = "USE_DATETIME_DATETIME"; option (ifdef) = "USE_DATETIME_DATETIME";
option (no_delay) = true; option (no_delay) = true;
option (base_class) = "CommandProtoMessage";
fixed32 key = 1; fixed32 key = 1;
fixed32 epoch_seconds = 2; fixed32 epoch_seconds = 2;
uint32 device_id = 3;
} }
// ==================== UPDATE ==================== // ==================== UPDATE ====================
@@ -2200,9 +2160,7 @@ message UpdateCommandRequest {
option (source) = SOURCE_CLIENT; option (source) = SOURCE_CLIENT;
option (ifdef) = "USE_UPDATE"; option (ifdef) = "USE_UPDATE";
option (no_delay) = true; option (no_delay) = true;
option (base_class) = "CommandProtoMessage";
fixed32 key = 1; fixed32 key = 1;
UpdateCommand command = 2; UpdateCommand command = 2;
uint32 device_id = 3;
} }

View File

@@ -38,23 +38,10 @@ static constexpr uint16_t PING_RETRY_INTERVAL = 1000;
static constexpr uint32_t KEEPALIVE_DISCONNECT_TIMEOUT = (KEEPALIVE_TIMEOUT_MS * 5) / 2; static constexpr uint32_t KEEPALIVE_DISCONNECT_TIMEOUT = (KEEPALIVE_TIMEOUT_MS * 5) / 2;
static const char *const TAG = "api.connection"; static const char *const TAG = "api.connection";
#ifdef USE_CAMERA #ifdef USE_ESP32_CAMERA
static const int CAMERA_STOP_STREAM = 5000; static const int ESP32_CAMERA_STOP_STREAM = 5000;
#endif #endif
// Helper macro for entity command handlers - gets entity by key, returns if not found, and creates call object
#define ENTITY_COMMAND_MAKE_CALL(entity_type, entity_var, getter_name) \
entity_type *entity_var = App.get_##getter_name##_by_key(msg.key); \
if ((entity_var) == nullptr) \
return; \
auto call = (entity_var)->make_call();
// Helper macro for entity command handlers that don't use make_call() - gets entity by key and returns if not found
#define ENTITY_COMMAND_GET(entity_type, entity_var, getter_name) \
entity_type *entity_var = App.get_##getter_name##_by_key(msg.key); \
if ((entity_var) == nullptr) \
return;
APIConnection::APIConnection(std::unique_ptr<socket::Socket> sock, APIServer *parent) APIConnection::APIConnection(std::unique_ptr<socket::Socket> sock, APIServer *parent)
: parent_(parent), initial_state_iterator_(this), list_entities_iterator_(this) { : parent_(parent), initial_state_iterator_(this), list_entities_iterator_(this) {
#if defined(USE_API_PLAINTEXT) && defined(USE_API_NOISE) #if defined(USE_API_PLAINTEXT) && defined(USE_API_NOISE)
@@ -71,11 +58,6 @@ APIConnection::APIConnection(std::unique_ptr<socket::Socket> sock, APIServer *pa
#else #else
#error "No frame helper defined" #error "No frame helper defined"
#endif #endif
#ifdef USE_CAMERA
if (camera::Camera::instance() != nullptr) {
this->image_reader_ = std::unique_ptr<camera::CameraImageReader>{camera::Camera::instance()->create_image_reader()};
}
#endif
} }
uint32_t APIConnection::get_batch_delay_ms_() const { return this->parent_->get_batch_delay(); } uint32_t APIConnection::get_batch_delay_ms_() const { return this->parent_->get_batch_delay(); }
@@ -193,16 +175,15 @@ void APIConnection::loop() {
// If we can't send the ping request directly (tx_buffer full), // If we can't send the ping request directly (tx_buffer full),
// schedule it at the front of the batch so it will be sent with priority // schedule it at the front of the batch so it will be sent with priority
ESP_LOGW(TAG, "Buffer full, ping queued"); ESP_LOGW(TAG, "Buffer full, ping queued");
this->schedule_message_front_(nullptr, &APIConnection::try_send_ping_request, PingRequest::MESSAGE_TYPE, this->schedule_message_front_(nullptr, &APIConnection::try_send_ping_request, PingRequest::MESSAGE_TYPE);
PingRequest::ESTIMATED_SIZE);
this->flags_.sent_ping = true; // Mark as sent to avoid scheduling multiple pings this->flags_.sent_ping = true; // Mark as sent to avoid scheduling multiple pings
} }
} }
#ifdef USE_CAMERA #ifdef USE_ESP32_CAMERA
if (this->image_reader_ && this->image_reader_->available() && this->helper_->can_write_without_blocking()) { if (this->image_reader_.available() && this->helper_->can_write_without_blocking()) {
uint32_t to_send = std::min((size_t) MAX_BATCH_PACKET_SIZE, this->image_reader_->available()); uint32_t to_send = std::min((size_t) MAX_PACKET_SIZE, this->image_reader_.available());
bool done = this->image_reader_->available() == to_send; bool done = this->image_reader_.available() == to_send;
uint32_t msg_size = 0; uint32_t msg_size = 0;
ProtoSize::add_fixed_field<4>(msg_size, 1, true); ProtoSize::add_fixed_field<4>(msg_size, 1, true);
// partial message size calculated manually since its a special case // partial message size calculated manually since its a special case
@@ -212,18 +193,18 @@ void APIConnection::loop() {
auto buffer = this->create_buffer(msg_size); auto buffer = this->create_buffer(msg_size);
// fixed32 key = 1; // fixed32 key = 1;
buffer.encode_fixed32(1, camera::Camera::instance()->get_object_id_hash()); buffer.encode_fixed32(1, esp32_camera::global_esp32_camera->get_object_id_hash());
// bytes data = 2; // bytes data = 2;
buffer.encode_bytes(2, this->image_reader_->peek_data_buffer(), to_send); buffer.encode_bytes(2, this->image_reader_.peek_data_buffer(), to_send);
// bool done = 3; // bool done = 3;
buffer.encode_bool(3, done); buffer.encode_bool(3, done);
bool success = this->send_buffer(buffer, CameraImageResponse::MESSAGE_TYPE); bool success = this->send_buffer(buffer, CameraImageResponse::MESSAGE_TYPE);
if (success) { if (success) {
this->image_reader_->consume_data(to_send); this->image_reader_.consume_data(to_send);
if (done) { if (done) {
this->image_reader_->return_image(); this->image_reader_.return_image();
} }
} }
} }
@@ -266,7 +247,7 @@ void APIConnection::on_disconnect_response(const DisconnectResponse &value) {
// Encodes a message to the buffer and returns the total number of bytes used, // Encodes a message to the buffer and returns the total number of bytes used,
// including header and footer overhead. Returns 0 if the message doesn't fit. // including header and footer overhead. Returns 0 if the message doesn't fit.
uint16_t APIConnection::encode_message_to_buffer(ProtoMessage &msg, uint8_t message_type, APIConnection *conn, uint16_t APIConnection::encode_message_to_buffer(ProtoMessage &msg, uint16_t message_type, APIConnection *conn,
uint32_t remaining_size, bool is_single) { uint32_t remaining_size, bool is_single) {
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
// If in log-only mode, just log and return // If in log-only mode, just log and return
@@ -317,7 +298,7 @@ uint16_t APIConnection::encode_message_to_buffer(ProtoMessage &msg, uint8_t mess
#ifdef USE_BINARY_SENSOR #ifdef USE_BINARY_SENSOR
bool APIConnection::send_binary_sensor_state(binary_sensor::BinarySensor *binary_sensor) { bool APIConnection::send_binary_sensor_state(binary_sensor::BinarySensor *binary_sensor) {
return this->send_message_smart_(binary_sensor, &APIConnection::try_send_binary_sensor_state, return this->send_message_smart_(binary_sensor, &APIConnection::try_send_binary_sensor_state,
BinarySensorStateResponse::MESSAGE_TYPE, BinarySensorStateResponse::ESTIMATED_SIZE); BinarySensorStateResponse::MESSAGE_TYPE);
} }
uint16_t APIConnection::try_send_binary_sensor_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, uint16_t APIConnection::try_send_binary_sensor_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
@@ -344,8 +325,7 @@ uint16_t APIConnection::try_send_binary_sensor_info(EntityBase *entity, APIConne
#ifdef USE_COVER #ifdef USE_COVER
bool APIConnection::send_cover_state(cover::Cover *cover) { bool APIConnection::send_cover_state(cover::Cover *cover) {
return this->send_message_smart_(cover, &APIConnection::try_send_cover_state, CoverStateResponse::MESSAGE_TYPE, return this->send_message_smart_(cover, &APIConnection::try_send_cover_state, CoverStateResponse::MESSAGE_TYPE);
CoverStateResponse::ESTIMATED_SIZE);
} }
uint16_t APIConnection::try_send_cover_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, uint16_t APIConnection::try_send_cover_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single) { bool is_single) {
@@ -376,7 +356,11 @@ uint16_t APIConnection::try_send_cover_info(EntityBase *entity, APIConnection *c
return encode_message_to_buffer(msg, ListEntitiesCoverResponse::MESSAGE_TYPE, conn, remaining_size, is_single); return encode_message_to_buffer(msg, ListEntitiesCoverResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
} }
void APIConnection::cover_command(const CoverCommandRequest &msg) { void APIConnection::cover_command(const CoverCommandRequest &msg) {
ENTITY_COMMAND_MAKE_CALL(cover::Cover, cover, cover) cover::Cover *cover = App.get_cover_by_key(msg.key);
if (cover == nullptr)
return;
auto call = cover->make_call();
if (msg.has_legacy_command) { if (msg.has_legacy_command) {
switch (msg.legacy_command) { switch (msg.legacy_command) {
case enums::LEGACY_COVER_COMMAND_OPEN: case enums::LEGACY_COVER_COMMAND_OPEN:
@@ -402,8 +386,7 @@ void APIConnection::cover_command(const CoverCommandRequest &msg) {
#ifdef USE_FAN #ifdef USE_FAN
bool APIConnection::send_fan_state(fan::Fan *fan) { bool APIConnection::send_fan_state(fan::Fan *fan) {
return this->send_message_smart_(fan, &APIConnection::try_send_fan_state, FanStateResponse::MESSAGE_TYPE, return this->send_message_smart_(fan, &APIConnection::try_send_fan_state, FanStateResponse::MESSAGE_TYPE);
FanStateResponse::ESTIMATED_SIZE);
} }
uint16_t APIConnection::try_send_fan_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, uint16_t APIConnection::try_send_fan_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single) { bool is_single) {
@@ -439,7 +422,11 @@ uint16_t APIConnection::try_send_fan_info(EntityBase *entity, APIConnection *con
return encode_message_to_buffer(msg, ListEntitiesFanResponse::MESSAGE_TYPE, conn, remaining_size, is_single); return encode_message_to_buffer(msg, ListEntitiesFanResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
} }
void APIConnection::fan_command(const FanCommandRequest &msg) { void APIConnection::fan_command(const FanCommandRequest &msg) {
ENTITY_COMMAND_MAKE_CALL(fan::Fan, fan, fan) fan::Fan *fan = App.get_fan_by_key(msg.key);
if (fan == nullptr)
return;
auto call = fan->make_call();
if (msg.has_state) if (msg.has_state)
call.set_state(msg.state); call.set_state(msg.state);
if (msg.has_oscillating) if (msg.has_oscillating)
@@ -458,8 +445,7 @@ void APIConnection::fan_command(const FanCommandRequest &msg) {
#ifdef USE_LIGHT #ifdef USE_LIGHT
bool APIConnection::send_light_state(light::LightState *light) { bool APIConnection::send_light_state(light::LightState *light) {
return this->send_message_smart_(light, &APIConnection::try_send_light_state, LightStateResponse::MESSAGE_TYPE, return this->send_message_smart_(light, &APIConnection::try_send_light_state, LightStateResponse::MESSAGE_TYPE);
LightStateResponse::ESTIMATED_SIZE);
} }
uint16_t APIConnection::try_send_light_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, uint16_t APIConnection::try_send_light_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single) { bool is_single) {
@@ -513,7 +499,11 @@ uint16_t APIConnection::try_send_light_info(EntityBase *entity, APIConnection *c
return encode_message_to_buffer(msg, ListEntitiesLightResponse::MESSAGE_TYPE, conn, remaining_size, is_single); return encode_message_to_buffer(msg, ListEntitiesLightResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
} }
void APIConnection::light_command(const LightCommandRequest &msg) { void APIConnection::light_command(const LightCommandRequest &msg) {
ENTITY_COMMAND_MAKE_CALL(light::LightState, light, light) light::LightState *light = App.get_light_by_key(msg.key);
if (light == nullptr)
return;
auto call = light->make_call();
if (msg.has_state) if (msg.has_state)
call.set_state(msg.state); call.set_state(msg.state);
if (msg.has_brightness) if (msg.has_brightness)
@@ -547,8 +537,7 @@ void APIConnection::light_command(const LightCommandRequest &msg) {
#ifdef USE_SENSOR #ifdef USE_SENSOR
bool APIConnection::send_sensor_state(sensor::Sensor *sensor) { bool APIConnection::send_sensor_state(sensor::Sensor *sensor) {
return this->send_message_smart_(sensor, &APIConnection::try_send_sensor_state, SensorStateResponse::MESSAGE_TYPE, return this->send_message_smart_(sensor, &APIConnection::try_send_sensor_state, SensorStateResponse::MESSAGE_TYPE);
SensorStateResponse::ESTIMATED_SIZE);
} }
uint16_t APIConnection::try_send_sensor_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, uint16_t APIConnection::try_send_sensor_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
@@ -580,8 +569,7 @@ uint16_t APIConnection::try_send_sensor_info(EntityBase *entity, APIConnection *
#ifdef USE_SWITCH #ifdef USE_SWITCH
bool APIConnection::send_switch_state(switch_::Switch *a_switch) { bool APIConnection::send_switch_state(switch_::Switch *a_switch) {
return this->send_message_smart_(a_switch, &APIConnection::try_send_switch_state, SwitchStateResponse::MESSAGE_TYPE, return this->send_message_smart_(a_switch, &APIConnection::try_send_switch_state, SwitchStateResponse::MESSAGE_TYPE);
SwitchStateResponse::ESTIMATED_SIZE);
} }
uint16_t APIConnection::try_send_switch_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, uint16_t APIConnection::try_send_switch_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
@@ -604,7 +592,9 @@ uint16_t APIConnection::try_send_switch_info(EntityBase *entity, APIConnection *
return encode_message_to_buffer(msg, ListEntitiesSwitchResponse::MESSAGE_TYPE, conn, remaining_size, is_single); return encode_message_to_buffer(msg, ListEntitiesSwitchResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
} }
void APIConnection::switch_command(const SwitchCommandRequest &msg) { void APIConnection::switch_command(const SwitchCommandRequest &msg) {
ENTITY_COMMAND_GET(switch_::Switch, a_switch, switch) switch_::Switch *a_switch = App.get_switch_by_key(msg.key);
if (a_switch == nullptr)
return;
if (msg.state) { if (msg.state) {
a_switch->turn_on(); a_switch->turn_on();
@@ -617,7 +607,7 @@ void APIConnection::switch_command(const SwitchCommandRequest &msg) {
#ifdef USE_TEXT_SENSOR #ifdef USE_TEXT_SENSOR
bool APIConnection::send_text_sensor_state(text_sensor::TextSensor *text_sensor) { bool APIConnection::send_text_sensor_state(text_sensor::TextSensor *text_sensor) {
return this->send_message_smart_(text_sensor, &APIConnection::try_send_text_sensor_state, return this->send_message_smart_(text_sensor, &APIConnection::try_send_text_sensor_state,
TextSensorStateResponse::MESSAGE_TYPE, TextSensorStateResponse::ESTIMATED_SIZE); TextSensorStateResponse::MESSAGE_TYPE);
} }
uint16_t APIConnection::try_send_text_sensor_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, uint16_t APIConnection::try_send_text_sensor_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
@@ -644,8 +634,7 @@ uint16_t APIConnection::try_send_text_sensor_info(EntityBase *entity, APIConnect
#ifdef USE_CLIMATE #ifdef USE_CLIMATE
bool APIConnection::send_climate_state(climate::Climate *climate) { bool APIConnection::send_climate_state(climate::Climate *climate) {
return this->send_message_smart_(climate, &APIConnection::try_send_climate_state, ClimateStateResponse::MESSAGE_TYPE, return this->send_message_smart_(climate, &APIConnection::try_send_climate_state, ClimateStateResponse::MESSAGE_TYPE);
ClimateStateResponse::ESTIMATED_SIZE);
} }
uint16_t APIConnection::try_send_climate_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, uint16_t APIConnection::try_send_climate_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single) { bool is_single) {
@@ -714,7 +703,11 @@ uint16_t APIConnection::try_send_climate_info(EntityBase *entity, APIConnection
return encode_message_to_buffer(msg, ListEntitiesClimateResponse::MESSAGE_TYPE, conn, remaining_size, is_single); return encode_message_to_buffer(msg, ListEntitiesClimateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
} }
void APIConnection::climate_command(const ClimateCommandRequest &msg) { void APIConnection::climate_command(const ClimateCommandRequest &msg) {
ENTITY_COMMAND_MAKE_CALL(climate::Climate, climate, climate) climate::Climate *climate = App.get_climate_by_key(msg.key);
if (climate == nullptr)
return;
auto call = climate->make_call();
if (msg.has_mode) if (msg.has_mode)
call.set_mode(static_cast<climate::ClimateMode>(msg.mode)); call.set_mode(static_cast<climate::ClimateMode>(msg.mode));
if (msg.has_target_temperature) if (msg.has_target_temperature)
@@ -741,8 +734,7 @@ void APIConnection::climate_command(const ClimateCommandRequest &msg) {
#ifdef USE_NUMBER #ifdef USE_NUMBER
bool APIConnection::send_number_state(number::Number *number) { bool APIConnection::send_number_state(number::Number *number) {
return this->send_message_smart_(number, &APIConnection::try_send_number_state, NumberStateResponse::MESSAGE_TYPE, return this->send_message_smart_(number, &APIConnection::try_send_number_state, NumberStateResponse::MESSAGE_TYPE);
NumberStateResponse::ESTIMATED_SIZE);
} }
uint16_t APIConnection::try_send_number_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, uint16_t APIConnection::try_send_number_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
@@ -770,7 +762,11 @@ uint16_t APIConnection::try_send_number_info(EntityBase *entity, APIConnection *
return encode_message_to_buffer(msg, ListEntitiesNumberResponse::MESSAGE_TYPE, conn, remaining_size, is_single); return encode_message_to_buffer(msg, ListEntitiesNumberResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
} }
void APIConnection::number_command(const NumberCommandRequest &msg) { void APIConnection::number_command(const NumberCommandRequest &msg) {
ENTITY_COMMAND_MAKE_CALL(number::Number, number, number) number::Number *number = App.get_number_by_key(msg.key);
if (number == nullptr)
return;
auto call = number->make_call();
call.set_value(msg.state); call.set_value(msg.state);
call.perform(); call.perform();
} }
@@ -778,8 +774,7 @@ void APIConnection::number_command(const NumberCommandRequest &msg) {
#ifdef USE_DATETIME_DATE #ifdef USE_DATETIME_DATE
bool APIConnection::send_date_state(datetime::DateEntity *date) { bool APIConnection::send_date_state(datetime::DateEntity *date) {
return this->send_message_smart_(date, &APIConnection::try_send_date_state, DateStateResponse::MESSAGE_TYPE, return this->send_message_smart_(date, &APIConnection::try_send_date_state, DateStateResponse::MESSAGE_TYPE);
DateStateResponse::ESTIMATED_SIZE);
} }
uint16_t APIConnection::try_send_date_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, uint16_t APIConnection::try_send_date_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single) { bool is_single) {
@@ -801,7 +796,11 @@ uint16_t APIConnection::try_send_date_info(EntityBase *entity, APIConnection *co
return encode_message_to_buffer(msg, ListEntitiesDateResponse::MESSAGE_TYPE, conn, remaining_size, is_single); return encode_message_to_buffer(msg, ListEntitiesDateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
} }
void APIConnection::date_command(const DateCommandRequest &msg) { void APIConnection::date_command(const DateCommandRequest &msg) {
ENTITY_COMMAND_MAKE_CALL(datetime::DateEntity, date, date) datetime::DateEntity *date = App.get_date_by_key(msg.key);
if (date == nullptr)
return;
auto call = date->make_call();
call.set_date(msg.year, msg.month, msg.day); call.set_date(msg.year, msg.month, msg.day);
call.perform(); call.perform();
} }
@@ -809,8 +808,7 @@ void APIConnection::date_command(const DateCommandRequest &msg) {
#ifdef USE_DATETIME_TIME #ifdef USE_DATETIME_TIME
bool APIConnection::send_time_state(datetime::TimeEntity *time) { bool APIConnection::send_time_state(datetime::TimeEntity *time) {
return this->send_message_smart_(time, &APIConnection::try_send_time_state, TimeStateResponse::MESSAGE_TYPE, return this->send_message_smart_(time, &APIConnection::try_send_time_state, TimeStateResponse::MESSAGE_TYPE);
TimeStateResponse::ESTIMATED_SIZE);
} }
uint16_t APIConnection::try_send_time_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, uint16_t APIConnection::try_send_time_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single) { bool is_single) {
@@ -832,7 +830,11 @@ uint16_t APIConnection::try_send_time_info(EntityBase *entity, APIConnection *co
return encode_message_to_buffer(msg, ListEntitiesTimeResponse::MESSAGE_TYPE, conn, remaining_size, is_single); return encode_message_to_buffer(msg, ListEntitiesTimeResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
} }
void APIConnection::time_command(const TimeCommandRequest &msg) { void APIConnection::time_command(const TimeCommandRequest &msg) {
ENTITY_COMMAND_MAKE_CALL(datetime::TimeEntity, time, time) datetime::TimeEntity *time = App.get_time_by_key(msg.key);
if (time == nullptr)
return;
auto call = time->make_call();
call.set_time(msg.hour, msg.minute, msg.second); call.set_time(msg.hour, msg.minute, msg.second);
call.perform(); call.perform();
} }
@@ -841,7 +843,7 @@ void APIConnection::time_command(const TimeCommandRequest &msg) {
#ifdef USE_DATETIME_DATETIME #ifdef USE_DATETIME_DATETIME
bool APIConnection::send_datetime_state(datetime::DateTimeEntity *datetime) { bool APIConnection::send_datetime_state(datetime::DateTimeEntity *datetime) {
return this->send_message_smart_(datetime, &APIConnection::try_send_datetime_state, return this->send_message_smart_(datetime, &APIConnection::try_send_datetime_state,
DateTimeStateResponse::MESSAGE_TYPE, DateTimeStateResponse::ESTIMATED_SIZE); DateTimeStateResponse::MESSAGE_TYPE);
} }
uint16_t APIConnection::try_send_datetime_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, uint16_t APIConnection::try_send_datetime_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single) { bool is_single) {
@@ -864,7 +866,11 @@ uint16_t APIConnection::try_send_datetime_info(EntityBase *entity, APIConnection
return encode_message_to_buffer(msg, ListEntitiesDateTimeResponse::MESSAGE_TYPE, conn, remaining_size, is_single); return encode_message_to_buffer(msg, ListEntitiesDateTimeResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
} }
void APIConnection::datetime_command(const DateTimeCommandRequest &msg) { void APIConnection::datetime_command(const DateTimeCommandRequest &msg) {
ENTITY_COMMAND_MAKE_CALL(datetime::DateTimeEntity, datetime, datetime) datetime::DateTimeEntity *datetime = App.get_datetime_by_key(msg.key);
if (datetime == nullptr)
return;
auto call = datetime->make_call();
call.set_datetime(msg.epoch_seconds); call.set_datetime(msg.epoch_seconds);
call.perform(); call.perform();
} }
@@ -872,8 +878,7 @@ void APIConnection::datetime_command(const DateTimeCommandRequest &msg) {
#ifdef USE_TEXT #ifdef USE_TEXT
bool APIConnection::send_text_state(text::Text *text) { bool APIConnection::send_text_state(text::Text *text) {
return this->send_message_smart_(text, &APIConnection::try_send_text_state, TextStateResponse::MESSAGE_TYPE, return this->send_message_smart_(text, &APIConnection::try_send_text_state, TextStateResponse::MESSAGE_TYPE);
TextStateResponse::ESTIMATED_SIZE);
} }
uint16_t APIConnection::try_send_text_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, uint16_t APIConnection::try_send_text_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
@@ -899,7 +904,11 @@ uint16_t APIConnection::try_send_text_info(EntityBase *entity, APIConnection *co
return encode_message_to_buffer(msg, ListEntitiesTextResponse::MESSAGE_TYPE, conn, remaining_size, is_single); return encode_message_to_buffer(msg, ListEntitiesTextResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
} }
void APIConnection::text_command(const TextCommandRequest &msg) { void APIConnection::text_command(const TextCommandRequest &msg) {
ENTITY_COMMAND_MAKE_CALL(text::Text, text, text) text::Text *text = App.get_text_by_key(msg.key);
if (text == nullptr)
return;
auto call = text->make_call();
call.set_value(msg.state); call.set_value(msg.state);
call.perform(); call.perform();
} }
@@ -907,8 +916,7 @@ void APIConnection::text_command(const TextCommandRequest &msg) {
#ifdef USE_SELECT #ifdef USE_SELECT
bool APIConnection::send_select_state(select::Select *select) { bool APIConnection::send_select_state(select::Select *select) {
return this->send_message_smart_(select, &APIConnection::try_send_select_state, SelectStateResponse::MESSAGE_TYPE, return this->send_message_smart_(select, &APIConnection::try_send_select_state, SelectStateResponse::MESSAGE_TYPE);
SelectStateResponse::ESTIMATED_SIZE);
} }
uint16_t APIConnection::try_send_select_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, uint16_t APIConnection::try_send_select_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
@@ -932,7 +940,11 @@ uint16_t APIConnection::try_send_select_info(EntityBase *entity, APIConnection *
return encode_message_to_buffer(msg, ListEntitiesSelectResponse::MESSAGE_TYPE, conn, remaining_size, is_single); return encode_message_to_buffer(msg, ListEntitiesSelectResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
} }
void APIConnection::select_command(const SelectCommandRequest &msg) { void APIConnection::select_command(const SelectCommandRequest &msg) {
ENTITY_COMMAND_MAKE_CALL(select::Select, select, select) select::Select *select = App.get_select_by_key(msg.key);
if (select == nullptr)
return;
auto call = select->make_call();
call.set_option(msg.state); call.set_option(msg.state);
call.perform(); call.perform();
} }
@@ -949,15 +961,17 @@ uint16_t APIConnection::try_send_button_info(EntityBase *entity, APIConnection *
return encode_message_to_buffer(msg, ListEntitiesButtonResponse::MESSAGE_TYPE, conn, remaining_size, is_single); return encode_message_to_buffer(msg, ListEntitiesButtonResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
} }
void esphome::api::APIConnection::button_command(const ButtonCommandRequest &msg) { void esphome::api::APIConnection::button_command(const ButtonCommandRequest &msg) {
ENTITY_COMMAND_GET(button::Button, button, button) button::Button *button = App.get_button_by_key(msg.key);
if (button == nullptr)
return;
button->press(); button->press();
} }
#endif #endif
#ifdef USE_LOCK #ifdef USE_LOCK
bool APIConnection::send_lock_state(lock::Lock *a_lock) { bool APIConnection::send_lock_state(lock::Lock *a_lock) {
return this->send_message_smart_(a_lock, &APIConnection::try_send_lock_state, LockStateResponse::MESSAGE_TYPE, return this->send_message_smart_(a_lock, &APIConnection::try_send_lock_state, LockStateResponse::MESSAGE_TYPE);
LockStateResponse::ESTIMATED_SIZE);
} }
uint16_t APIConnection::try_send_lock_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, uint16_t APIConnection::try_send_lock_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
@@ -981,7 +995,9 @@ uint16_t APIConnection::try_send_lock_info(EntityBase *entity, APIConnection *co
return encode_message_to_buffer(msg, ListEntitiesLockResponse::MESSAGE_TYPE, conn, remaining_size, is_single); return encode_message_to_buffer(msg, ListEntitiesLockResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
} }
void APIConnection::lock_command(const LockCommandRequest &msg) { void APIConnection::lock_command(const LockCommandRequest &msg) {
ENTITY_COMMAND_GET(lock::Lock, a_lock, lock) lock::Lock *a_lock = App.get_lock_by_key(msg.key);
if (a_lock == nullptr)
return;
switch (msg.command) { switch (msg.command) {
case enums::LOCK_UNLOCK: case enums::LOCK_UNLOCK:
@@ -999,8 +1015,7 @@ void APIConnection::lock_command(const LockCommandRequest &msg) {
#ifdef USE_VALVE #ifdef USE_VALVE
bool APIConnection::send_valve_state(valve::Valve *valve) { bool APIConnection::send_valve_state(valve::Valve *valve) {
return this->send_message_smart_(valve, &APIConnection::try_send_valve_state, ValveStateResponse::MESSAGE_TYPE, return this->send_message_smart_(valve, &APIConnection::try_send_valve_state, ValveStateResponse::MESSAGE_TYPE);
ValveStateResponse::ESTIMATED_SIZE);
} }
uint16_t APIConnection::try_send_valve_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, uint16_t APIConnection::try_send_valve_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single) { bool is_single) {
@@ -1025,7 +1040,11 @@ uint16_t APIConnection::try_send_valve_info(EntityBase *entity, APIConnection *c
return encode_message_to_buffer(msg, ListEntitiesValveResponse::MESSAGE_TYPE, conn, remaining_size, is_single); return encode_message_to_buffer(msg, ListEntitiesValveResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
} }
void APIConnection::valve_command(const ValveCommandRequest &msg) { void APIConnection::valve_command(const ValveCommandRequest &msg) {
ENTITY_COMMAND_MAKE_CALL(valve::Valve, valve, valve) valve::Valve *valve = App.get_valve_by_key(msg.key);
if (valve == nullptr)
return;
auto call = valve->make_call();
if (msg.has_position) if (msg.has_position)
call.set_position(msg.position); call.set_position(msg.position);
if (msg.stop) if (msg.stop)
@@ -1037,7 +1056,7 @@ void APIConnection::valve_command(const ValveCommandRequest &msg) {
#ifdef USE_MEDIA_PLAYER #ifdef USE_MEDIA_PLAYER
bool APIConnection::send_media_player_state(media_player::MediaPlayer *media_player) { bool APIConnection::send_media_player_state(media_player::MediaPlayer *media_player) {
return this->send_message_smart_(media_player, &APIConnection::try_send_media_player_state, return this->send_message_smart_(media_player, &APIConnection::try_send_media_player_state,
MediaPlayerStateResponse::MESSAGE_TYPE, MediaPlayerStateResponse::ESTIMATED_SIZE); MediaPlayerStateResponse::MESSAGE_TYPE);
} }
uint16_t APIConnection::try_send_media_player_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, uint16_t APIConnection::try_send_media_player_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single) { bool is_single) {
@@ -1072,7 +1091,11 @@ uint16_t APIConnection::try_send_media_player_info(EntityBase *entity, APIConnec
return encode_message_to_buffer(msg, ListEntitiesMediaPlayerResponse::MESSAGE_TYPE, conn, remaining_size, is_single); return encode_message_to_buffer(msg, ListEntitiesMediaPlayerResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
} }
void APIConnection::media_player_command(const MediaPlayerCommandRequest &msg) { void APIConnection::media_player_command(const MediaPlayerCommandRequest &msg) {
ENTITY_COMMAND_MAKE_CALL(media_player::MediaPlayer, media_player, media_player) media_player::MediaPlayer *media_player = App.get_media_player_by_key(msg.key);
if (media_player == nullptr)
return;
auto call = media_player->make_call();
if (msg.has_command) { if (msg.has_command) {
call.set_command(static_cast<media_player::MediaPlayerCommand>(msg.command)); call.set_command(static_cast<media_player::MediaPlayerCommand>(msg.command));
} }
@@ -1089,36 +1112,36 @@ void APIConnection::media_player_command(const MediaPlayerCommandRequest &msg) {
} }
#endif #endif
#ifdef USE_CAMERA #ifdef USE_ESP32_CAMERA
void APIConnection::set_camera_state(std::shared_ptr<camera::CameraImage> image) { void APIConnection::set_camera_state(std::shared_ptr<esp32_camera::CameraImage> image) {
if (!this->flags_.state_subscription) if (!this->flags_.state_subscription)
return; return;
if (!this->image_reader_) if (this->image_reader_.available())
return; return;
if (this->image_reader_->available()) if (image->was_requested_by(esphome::esp32_camera::API_REQUESTER) ||
return; image->was_requested_by(esphome::esp32_camera::IDLE))
if (image->was_requested_by(esphome::camera::API_REQUESTER) || image->was_requested_by(esphome::camera::IDLE)) this->image_reader_.set_image(std::move(image));
this->image_reader_->set_image(std::move(image));
} }
uint16_t APIConnection::try_send_camera_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, uint16_t APIConnection::try_send_camera_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single) { bool is_single) {
auto *camera = static_cast<camera::Camera *>(entity); auto *camera = static_cast<esp32_camera::ESP32Camera *>(entity);
ListEntitiesCameraResponse msg; ListEntitiesCameraResponse msg;
msg.unique_id = get_default_unique_id("camera", camera); msg.unique_id = get_default_unique_id("camera", camera);
fill_entity_info_base(camera, msg); fill_entity_info_base(camera, msg);
return encode_message_to_buffer(msg, ListEntitiesCameraResponse::MESSAGE_TYPE, conn, remaining_size, is_single); return encode_message_to_buffer(msg, ListEntitiesCameraResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
} }
void APIConnection::camera_image(const CameraImageRequest &msg) { void APIConnection::camera_image(const CameraImageRequest &msg) {
if (camera::Camera::instance() == nullptr) if (esp32_camera::global_esp32_camera == nullptr)
return; return;
if (msg.single) if (msg.single)
camera::Camera::instance()->request_image(esphome::camera::API_REQUESTER); esp32_camera::global_esp32_camera->request_image(esphome::esp32_camera::API_REQUESTER);
if (msg.stream) { if (msg.stream) {
camera::Camera::instance()->start_stream(esphome::camera::API_REQUESTER); esp32_camera::global_esp32_camera->start_stream(esphome::esp32_camera::API_REQUESTER);
App.scheduler.set_timeout(this->parent_, "api_camera_stop_stream", CAMERA_STOP_STREAM, App.scheduler.set_timeout(this->parent_, "api_esp32_camera_stop_stream", ESP32_CAMERA_STOP_STREAM, []() {
[]() { camera::Camera::instance()->stop_stream(esphome::camera::API_REQUESTER); }); esp32_camera::global_esp32_camera->stop_stream(esphome::esp32_camera::API_REQUESTER);
});
} }
} }
#endif #endif
@@ -1190,53 +1213,66 @@ void APIConnection::bluetooth_scanner_set_mode(const BluetoothScannerSetModeRequ
#endif #endif
#ifdef USE_VOICE_ASSISTANT #ifdef USE_VOICE_ASSISTANT
bool APIConnection::check_voice_assistant_api_connection_() const {
return voice_assistant::global_voice_assistant != nullptr &&
voice_assistant::global_voice_assistant->get_api_connection() == this;
}
void APIConnection::subscribe_voice_assistant(const SubscribeVoiceAssistantRequest &msg) { void APIConnection::subscribe_voice_assistant(const SubscribeVoiceAssistantRequest &msg) {
if (voice_assistant::global_voice_assistant != nullptr) { if (voice_assistant::global_voice_assistant != nullptr) {
voice_assistant::global_voice_assistant->client_subscription(this, msg.subscribe); voice_assistant::global_voice_assistant->client_subscription(this, msg.subscribe);
} }
} }
void APIConnection::on_voice_assistant_response(const VoiceAssistantResponse &msg) { void APIConnection::on_voice_assistant_response(const VoiceAssistantResponse &msg) {
if (!this->check_voice_assistant_api_connection_()) { if (voice_assistant::global_voice_assistant != nullptr) {
return; if (voice_assistant::global_voice_assistant->get_api_connection() != this) {
} return;
}
if (msg.error) { if (msg.error) {
voice_assistant::global_voice_assistant->failed_to_start(); voice_assistant::global_voice_assistant->failed_to_start();
return; return;
} }
if (msg.port == 0) { if (msg.port == 0) {
// Use API Audio // Use API Audio
voice_assistant::global_voice_assistant->start_streaming(); voice_assistant::global_voice_assistant->start_streaming();
} else { } else {
struct sockaddr_storage storage; struct sockaddr_storage storage;
socklen_t len = sizeof(storage); socklen_t len = sizeof(storage);
this->helper_->getpeername((struct sockaddr *) &storage, &len); this->helper_->getpeername((struct sockaddr *) &storage, &len);
voice_assistant::global_voice_assistant->start_streaming(&storage, msg.port); voice_assistant::global_voice_assistant->start_streaming(&storage, msg.port);
}
} }
}; };
void APIConnection::on_voice_assistant_event_response(const VoiceAssistantEventResponse &msg) { void APIConnection::on_voice_assistant_event_response(const VoiceAssistantEventResponse &msg) {
if (this->check_voice_assistant_api_connection_()) { if (voice_assistant::global_voice_assistant != nullptr) {
if (voice_assistant::global_voice_assistant->get_api_connection() != this) {
return;
}
voice_assistant::global_voice_assistant->on_event(msg); voice_assistant::global_voice_assistant->on_event(msg);
} }
} }
void APIConnection::on_voice_assistant_audio(const VoiceAssistantAudio &msg) { void APIConnection::on_voice_assistant_audio(const VoiceAssistantAudio &msg) {
if (this->check_voice_assistant_api_connection_()) { if (voice_assistant::global_voice_assistant != nullptr) {
if (voice_assistant::global_voice_assistant->get_api_connection() != this) {
return;
}
voice_assistant::global_voice_assistant->on_audio(msg); voice_assistant::global_voice_assistant->on_audio(msg);
} }
}; };
void APIConnection::on_voice_assistant_timer_event_response(const VoiceAssistantTimerEventResponse &msg) { void APIConnection::on_voice_assistant_timer_event_response(const VoiceAssistantTimerEventResponse &msg) {
if (this->check_voice_assistant_api_connection_()) { if (voice_assistant::global_voice_assistant != nullptr) {
if (voice_assistant::global_voice_assistant->get_api_connection() != this) {
return;
}
voice_assistant::global_voice_assistant->on_timer_event(msg); voice_assistant::global_voice_assistant->on_timer_event(msg);
} }
}; };
void APIConnection::on_voice_assistant_announce_request(const VoiceAssistantAnnounceRequest &msg) { void APIConnection::on_voice_assistant_announce_request(const VoiceAssistantAnnounceRequest &msg) {
if (this->check_voice_assistant_api_connection_()) { if (voice_assistant::global_voice_assistant != nullptr) {
if (voice_assistant::global_voice_assistant->get_api_connection() != this) {
return;
}
voice_assistant::global_voice_assistant->on_announce(msg); voice_assistant::global_voice_assistant->on_announce(msg);
} }
} }
@@ -1244,29 +1280,35 @@ void APIConnection::on_voice_assistant_announce_request(const VoiceAssistantAnno
VoiceAssistantConfigurationResponse APIConnection::voice_assistant_get_configuration( VoiceAssistantConfigurationResponse APIConnection::voice_assistant_get_configuration(
const VoiceAssistantConfigurationRequest &msg) { const VoiceAssistantConfigurationRequest &msg) {
VoiceAssistantConfigurationResponse resp; VoiceAssistantConfigurationResponse resp;
if (!this->check_voice_assistant_api_connection_()) { if (voice_assistant::global_voice_assistant != nullptr) {
return resp; if (voice_assistant::global_voice_assistant->get_api_connection() != this) {
} return resp;
auto &config = voice_assistant::global_voice_assistant->get_configuration();
for (auto &wake_word : config.available_wake_words) {
VoiceAssistantWakeWord resp_wake_word;
resp_wake_word.id = wake_word.id;
resp_wake_word.wake_word = wake_word.wake_word;
for (const auto &lang : wake_word.trained_languages) {
resp_wake_word.trained_languages.push_back(lang);
} }
resp.available_wake_words.push_back(std::move(resp_wake_word));
auto &config = voice_assistant::global_voice_assistant->get_configuration();
for (auto &wake_word : config.available_wake_words) {
VoiceAssistantWakeWord resp_wake_word;
resp_wake_word.id = wake_word.id;
resp_wake_word.wake_word = wake_word.wake_word;
for (const auto &lang : wake_word.trained_languages) {
resp_wake_word.trained_languages.push_back(lang);
}
resp.available_wake_words.push_back(std::move(resp_wake_word));
}
for (auto &wake_word_id : config.active_wake_words) {
resp.active_wake_words.push_back(wake_word_id);
}
resp.max_active_wake_words = config.max_active_wake_words;
} }
for (auto &wake_word_id : config.active_wake_words) {
resp.active_wake_words.push_back(wake_word_id);
}
resp.max_active_wake_words = config.max_active_wake_words;
return resp; return resp;
} }
void APIConnection::voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &msg) { void APIConnection::voice_assistant_set_configuration(const VoiceAssistantSetConfiguration &msg) {
if (this->check_voice_assistant_api_connection_()) { if (voice_assistant::global_voice_assistant != nullptr) {
if (voice_assistant::global_voice_assistant->get_api_connection() != this) {
return;
}
voice_assistant::global_voice_assistant->on_set_configuration(msg.active_wake_words); voice_assistant::global_voice_assistant->on_set_configuration(msg.active_wake_words);
} }
} }
@@ -1276,8 +1318,7 @@ void APIConnection::voice_assistant_set_configuration(const VoiceAssistantSetCon
#ifdef USE_ALARM_CONTROL_PANEL #ifdef USE_ALARM_CONTROL_PANEL
bool APIConnection::send_alarm_control_panel_state(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) { bool APIConnection::send_alarm_control_panel_state(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) {
return this->send_message_smart_(a_alarm_control_panel, &APIConnection::try_send_alarm_control_panel_state, return this->send_message_smart_(a_alarm_control_panel, &APIConnection::try_send_alarm_control_panel_state,
AlarmControlPanelStateResponse::MESSAGE_TYPE, AlarmControlPanelStateResponse::MESSAGE_TYPE);
AlarmControlPanelStateResponse::ESTIMATED_SIZE);
} }
uint16_t APIConnection::try_send_alarm_control_panel_state(EntityBase *entity, APIConnection *conn, uint16_t APIConnection::try_send_alarm_control_panel_state(EntityBase *entity, APIConnection *conn,
uint32_t remaining_size, bool is_single) { uint32_t remaining_size, bool is_single) {
@@ -1300,7 +1341,11 @@ uint16_t APIConnection::try_send_alarm_control_panel_info(EntityBase *entity, AP
is_single); is_single);
} }
void APIConnection::alarm_control_panel_command(const AlarmControlPanelCommandRequest &msg) { void APIConnection::alarm_control_panel_command(const AlarmControlPanelCommandRequest &msg) {
ENTITY_COMMAND_MAKE_CALL(alarm_control_panel::AlarmControlPanel, a_alarm_control_panel, alarm_control_panel) alarm_control_panel::AlarmControlPanel *a_alarm_control_panel = App.get_alarm_control_panel_by_key(msg.key);
if (a_alarm_control_panel == nullptr)
return;
auto call = a_alarm_control_panel->make_call();
switch (msg.command) { switch (msg.command) {
case enums::ALARM_CONTROL_PANEL_DISARM: case enums::ALARM_CONTROL_PANEL_DISARM:
call.disarm(); call.disarm();
@@ -1331,8 +1376,7 @@ void APIConnection::alarm_control_panel_command(const AlarmControlPanelCommandRe
#ifdef USE_EVENT #ifdef USE_EVENT
void APIConnection::send_event(event::Event *event, const std::string &event_type) { void APIConnection::send_event(event::Event *event, const std::string &event_type) {
this->schedule_message_(event, MessageCreator(event_type), EventResponse::MESSAGE_TYPE, this->schedule_message_(event, MessageCreator(event_type), EventResponse::MESSAGE_TYPE);
EventResponse::ESTIMATED_SIZE);
} }
uint16_t APIConnection::try_send_event_response(event::Event *event, const std::string &event_type, APIConnection *conn, uint16_t APIConnection::try_send_event_response(event::Event *event, const std::string &event_type, APIConnection *conn,
uint32_t remaining_size, bool is_single) { uint32_t remaining_size, bool is_single) {
@@ -1357,8 +1401,7 @@ uint16_t APIConnection::try_send_event_info(EntityBase *entity, APIConnection *c
#ifdef USE_UPDATE #ifdef USE_UPDATE
bool APIConnection::send_update_state(update::UpdateEntity *update) { bool APIConnection::send_update_state(update::UpdateEntity *update) {
return this->send_message_smart_(update, &APIConnection::try_send_update_state, UpdateStateResponse::MESSAGE_TYPE, return this->send_message_smart_(update, &APIConnection::try_send_update_state, UpdateStateResponse::MESSAGE_TYPE);
UpdateStateResponse::ESTIMATED_SIZE);
} }
uint16_t APIConnection::try_send_update_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, uint16_t APIConnection::try_send_update_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single) { bool is_single) {
@@ -1390,7 +1433,9 @@ uint16_t APIConnection::try_send_update_info(EntityBase *entity, APIConnection *
return encode_message_to_buffer(msg, ListEntitiesUpdateResponse::MESSAGE_TYPE, conn, remaining_size, is_single); return encode_message_to_buffer(msg, ListEntitiesUpdateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
} }
void APIConnection::update_command(const UpdateCommandRequest &msg) { void APIConnection::update_command(const UpdateCommandRequest &msg) {
ENTITY_COMMAND_GET(update::UpdateEntity, update, update) update::UpdateEntity *update = App.get_update_by_key(msg.key);
if (update == nullptr)
return;
switch (msg.command) { switch (msg.command) {
case enums::UPDATE_COMMAND_UPDATE: case enums::UPDATE_COMMAND_UPDATE:
@@ -1409,11 +1454,12 @@ void APIConnection::update_command(const UpdateCommandRequest &msg) {
} }
#endif #endif
bool APIConnection::try_send_log_message(int level, const char *tag, const char *line, size_t message_len) { bool APIConnection::try_send_log_message(int level, const char *tag, const char *line) {
if (this->flags_.log_subscription < level) if (this->flags_.log_subscription < level)
return false; return false;
// Pre-calculate message size to avoid reallocations // Pre-calculate message size to avoid reallocations
const size_t line_length = strlen(line);
uint32_t msg_size = 0; uint32_t msg_size = 0;
// Add size for level field (field ID 1, varint type) // Add size for level field (field ID 1, varint type)
@@ -1422,14 +1468,14 @@ bool APIConnection::try_send_log_message(int level, const char *tag, const char
// Add size for string field (field ID 3, string type) // Add size for string field (field ID 3, string type)
// 1 byte for field tag + size of length varint + string length // 1 byte for field tag + size of length varint + string length
msg_size += 1 + api::ProtoSize::varint(static_cast<uint32_t>(message_len)) + message_len; msg_size += 1 + api::ProtoSize::varint(static_cast<uint32_t>(line_length)) + line_length;
// Create a pre-sized buffer // Create a pre-sized buffer
auto buffer = this->create_buffer(msg_size); auto buffer = this->create_buffer(msg_size);
// Encode the message (SubscribeLogsResponse) // Encode the message (SubscribeLogsResponse)
buffer.encode_uint32(1, static_cast<uint32_t>(level)); // LogLevel level = 1 buffer.encode_uint32(1, static_cast<uint32_t>(level)); // LogLevel level = 1
buffer.encode_string(3, line, message_len); // string message = 3 buffer.encode_string(3, line, line_length); // string message = 3
// SubscribeLogsResponse - 29 // SubscribeLogsResponse - 29
return this->send_buffer(buffer, SubscribeLogsResponse::MESSAGE_TYPE); return this->send_buffer(buffer, SubscribeLogsResponse::MESSAGE_TYPE);
@@ -1551,7 +1597,6 @@ void APIConnection::on_home_assistant_state_response(const HomeAssistantStateRes
} }
} }
} }
#ifdef USE_API_SERVICES
void APIConnection::execute_service(const ExecuteServiceRequest &msg) { void APIConnection::execute_service(const ExecuteServiceRequest &msg) {
bool found = false; bool found = false;
for (auto *service : this->parent_->get_user_services()) { for (auto *service : this->parent_->get_user_services()) {
@@ -1563,7 +1608,6 @@ void APIConnection::execute_service(const ExecuteServiceRequest &msg) {
ESP_LOGV(TAG, "Could not find service"); ESP_LOGV(TAG, "Could not find service");
} }
} }
#endif
#ifdef USE_API_NOISE #ifdef USE_API_NOISE
NoiseEncryptionSetKeyResponse APIConnection::noise_encryption_set_key(const NoiseEncryptionSetKeyRequest &msg) { NoiseEncryptionSetKeyResponse APIConnection::noise_encryption_set_key(const NoiseEncryptionSetKeyRequest &msg) {
psk_t psk{}; psk_t psk{};
@@ -1607,7 +1651,7 @@ bool APIConnection::try_to_clear_buffer(bool log_out_of_space) {
} }
return false; return false;
} }
bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint8_t message_type) { bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint16_t message_type) {
if (!this->try_to_clear_buffer(message_type != SubscribeLogsResponse::MESSAGE_TYPE)) { // SubscribeLogsResponse if (!this->try_to_clear_buffer(message_type != SubscribeLogsResponse::MESSAGE_TYPE)) { // SubscribeLogsResponse
return false; return false;
} }
@@ -1641,8 +1685,7 @@ void APIConnection::on_fatal_error() {
this->flags_.remove = true; this->flags_.remove = true;
} }
void APIConnection::DeferredBatch::add_item(EntityBase *entity, MessageCreator creator, uint8_t message_type, void APIConnection::DeferredBatch::add_item(EntityBase *entity, MessageCreator creator, uint16_t message_type) {
uint8_t estimated_size) {
// Check if we already have a message of this type for this entity // Check if we already have a message of this type for this entity
// This provides deduplication per entity/message_type combination // This provides deduplication per entity/message_type combination
// O(n) but optimized for RAM and not performance. // O(n) but optimized for RAM and not performance.
@@ -1657,13 +1700,12 @@ void APIConnection::DeferredBatch::add_item(EntityBase *entity, MessageCreator c
} }
// No existing item found, add new one // No existing item found, add new one
items.emplace_back(entity, std::move(creator), message_type, estimated_size); items.emplace_back(entity, std::move(creator), message_type);
} }
void APIConnection::DeferredBatch::add_item_front(EntityBase *entity, MessageCreator creator, uint8_t message_type, void APIConnection::DeferredBatch::add_item_front(EntityBase *entity, MessageCreator creator, uint16_t message_type) {
uint8_t estimated_size) {
// Insert at front for high priority messages (no deduplication check) // Insert at front for high priority messages (no deduplication check)
items.insert(items.begin(), BatchItem(entity, std::move(creator), message_type, estimated_size)); items.insert(items.begin(), BatchItem(entity, std::move(creator), message_type));
} }
bool APIConnection::schedule_batch_() { bool APIConnection::schedule_batch_() {
@@ -1735,7 +1777,7 @@ void APIConnection::process_batch_() {
uint32_t total_estimated_size = 0; uint32_t total_estimated_size = 0;
for (size_t i = 0; i < this->deferred_batch_.size(); i++) { for (size_t i = 0; i < this->deferred_batch_.size(); i++) {
const auto &item = this->deferred_batch_[i]; const auto &item = this->deferred_batch_[i];
total_estimated_size += item.estimated_size; total_estimated_size += get_estimated_message_size(item.message_type);
} }
// Calculate total overhead for all messages // Calculate total overhead for all messages
@@ -1773,9 +1815,9 @@ void APIConnection::process_batch_() {
// Update tracking variables // Update tracking variables
items_processed++; items_processed++;
// After first message, set remaining size to MAX_BATCH_PACKET_SIZE to avoid fragmentation // After first message, set remaining size to MAX_PACKET_SIZE to avoid fragmentation
if (items_processed == 1) { if (items_processed == 1) {
remaining_size = MAX_BATCH_PACKET_SIZE; remaining_size = MAX_PACKET_SIZE;
} }
remaining_size -= payload_size; remaining_size -= payload_size;
// Calculate where the next message's header padding will start // Calculate where the next message's header padding will start
@@ -1829,7 +1871,7 @@ void APIConnection::process_batch_() {
} }
uint16_t APIConnection::MessageCreator::operator()(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, uint16_t APIConnection::MessageCreator::operator()(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single, uint8_t message_type) const { bool is_single, uint16_t message_type) const {
#ifdef USE_EVENT #ifdef USE_EVENT
// Special case: EventResponse uses string pointer // Special case: EventResponse uses string pointer
if (message_type == EventResponse::MESSAGE_TYPE) { if (message_type == EventResponse::MESSAGE_TYPE) {
@@ -1860,6 +1902,149 @@ uint16_t APIConnection::try_send_ping_request(EntityBase *entity, APIConnection
return encode_message_to_buffer(req, PingRequest::MESSAGE_TYPE, conn, remaining_size, is_single); return encode_message_to_buffer(req, PingRequest::MESSAGE_TYPE, conn, remaining_size, is_single);
} }
uint16_t APIConnection::get_estimated_message_size(uint16_t message_type) {
// Use generated ESTIMATED_SIZE constants from each message type
switch (message_type) {
#ifdef USE_BINARY_SENSOR
case BinarySensorStateResponse::MESSAGE_TYPE:
return BinarySensorStateResponse::ESTIMATED_SIZE;
case ListEntitiesBinarySensorResponse::MESSAGE_TYPE:
return ListEntitiesBinarySensorResponse::ESTIMATED_SIZE;
#endif
#ifdef USE_SENSOR
case SensorStateResponse::MESSAGE_TYPE:
return SensorStateResponse::ESTIMATED_SIZE;
case ListEntitiesSensorResponse::MESSAGE_TYPE:
return ListEntitiesSensorResponse::ESTIMATED_SIZE;
#endif
#ifdef USE_SWITCH
case SwitchStateResponse::MESSAGE_TYPE:
return SwitchStateResponse::ESTIMATED_SIZE;
case ListEntitiesSwitchResponse::MESSAGE_TYPE:
return ListEntitiesSwitchResponse::ESTIMATED_SIZE;
#endif
#ifdef USE_TEXT_SENSOR
case TextSensorStateResponse::MESSAGE_TYPE:
return TextSensorStateResponse::ESTIMATED_SIZE;
case ListEntitiesTextSensorResponse::MESSAGE_TYPE:
return ListEntitiesTextSensorResponse::ESTIMATED_SIZE;
#endif
#ifdef USE_NUMBER
case NumberStateResponse::MESSAGE_TYPE:
return NumberStateResponse::ESTIMATED_SIZE;
case ListEntitiesNumberResponse::MESSAGE_TYPE:
return ListEntitiesNumberResponse::ESTIMATED_SIZE;
#endif
#ifdef USE_TEXT
case TextStateResponse::MESSAGE_TYPE:
return TextStateResponse::ESTIMATED_SIZE;
case ListEntitiesTextResponse::MESSAGE_TYPE:
return ListEntitiesTextResponse::ESTIMATED_SIZE;
#endif
#ifdef USE_SELECT
case SelectStateResponse::MESSAGE_TYPE:
return SelectStateResponse::ESTIMATED_SIZE;
case ListEntitiesSelectResponse::MESSAGE_TYPE:
return ListEntitiesSelectResponse::ESTIMATED_SIZE;
#endif
#ifdef USE_LOCK
case LockStateResponse::MESSAGE_TYPE:
return LockStateResponse::ESTIMATED_SIZE;
case ListEntitiesLockResponse::MESSAGE_TYPE:
return ListEntitiesLockResponse::ESTIMATED_SIZE;
#endif
#ifdef USE_EVENT
case EventResponse::MESSAGE_TYPE:
return EventResponse::ESTIMATED_SIZE;
case ListEntitiesEventResponse::MESSAGE_TYPE:
return ListEntitiesEventResponse::ESTIMATED_SIZE;
#endif
#ifdef USE_COVER
case CoverStateResponse::MESSAGE_TYPE:
return CoverStateResponse::ESTIMATED_SIZE;
case ListEntitiesCoverResponse::MESSAGE_TYPE:
return ListEntitiesCoverResponse::ESTIMATED_SIZE;
#endif
#ifdef USE_FAN
case FanStateResponse::MESSAGE_TYPE:
return FanStateResponse::ESTIMATED_SIZE;
case ListEntitiesFanResponse::MESSAGE_TYPE:
return ListEntitiesFanResponse::ESTIMATED_SIZE;
#endif
#ifdef USE_LIGHT
case LightStateResponse::MESSAGE_TYPE:
return LightStateResponse::ESTIMATED_SIZE;
case ListEntitiesLightResponse::MESSAGE_TYPE:
return ListEntitiesLightResponse::ESTIMATED_SIZE;
#endif
#ifdef USE_CLIMATE
case ClimateStateResponse::MESSAGE_TYPE:
return ClimateStateResponse::ESTIMATED_SIZE;
case ListEntitiesClimateResponse::MESSAGE_TYPE:
return ListEntitiesClimateResponse::ESTIMATED_SIZE;
#endif
#ifdef USE_ESP32_CAMERA
case ListEntitiesCameraResponse::MESSAGE_TYPE:
return ListEntitiesCameraResponse::ESTIMATED_SIZE;
#endif
#ifdef USE_BUTTON
case ListEntitiesButtonResponse::MESSAGE_TYPE:
return ListEntitiesButtonResponse::ESTIMATED_SIZE;
#endif
#ifdef USE_MEDIA_PLAYER
case MediaPlayerStateResponse::MESSAGE_TYPE:
return MediaPlayerStateResponse::ESTIMATED_SIZE;
case ListEntitiesMediaPlayerResponse::MESSAGE_TYPE:
return ListEntitiesMediaPlayerResponse::ESTIMATED_SIZE;
#endif
#ifdef USE_ALARM_CONTROL_PANEL
case AlarmControlPanelStateResponse::MESSAGE_TYPE:
return AlarmControlPanelStateResponse::ESTIMATED_SIZE;
case ListEntitiesAlarmControlPanelResponse::MESSAGE_TYPE:
return ListEntitiesAlarmControlPanelResponse::ESTIMATED_SIZE;
#endif
#ifdef USE_DATETIME_DATE
case DateStateResponse::MESSAGE_TYPE:
return DateStateResponse::ESTIMATED_SIZE;
case ListEntitiesDateResponse::MESSAGE_TYPE:
return ListEntitiesDateResponse::ESTIMATED_SIZE;
#endif
#ifdef USE_DATETIME_TIME
case TimeStateResponse::MESSAGE_TYPE:
return TimeStateResponse::ESTIMATED_SIZE;
case ListEntitiesTimeResponse::MESSAGE_TYPE:
return ListEntitiesTimeResponse::ESTIMATED_SIZE;
#endif
#ifdef USE_DATETIME_DATETIME
case DateTimeStateResponse::MESSAGE_TYPE:
return DateTimeStateResponse::ESTIMATED_SIZE;
case ListEntitiesDateTimeResponse::MESSAGE_TYPE:
return ListEntitiesDateTimeResponse::ESTIMATED_SIZE;
#endif
#ifdef USE_VALVE
case ValveStateResponse::MESSAGE_TYPE:
return ValveStateResponse::ESTIMATED_SIZE;
case ListEntitiesValveResponse::MESSAGE_TYPE:
return ListEntitiesValveResponse::ESTIMATED_SIZE;
#endif
#ifdef USE_UPDATE
case UpdateStateResponse::MESSAGE_TYPE:
return UpdateStateResponse::ESTIMATED_SIZE;
case ListEntitiesUpdateResponse::MESSAGE_TYPE:
return ListEntitiesUpdateResponse::ESTIMATED_SIZE;
#endif
case ListEntitiesServicesResponse::MESSAGE_TYPE:
return ListEntitiesServicesResponse::ESTIMATED_SIZE;
case ListEntitiesDoneResponse::MESSAGE_TYPE:
return ListEntitiesDoneResponse::ESTIMATED_SIZE;
case DisconnectRequest::MESSAGE_TYPE:
return DisconnectRequest::ESTIMATED_SIZE;
default:
// Fallback for unknown message types
return 24;
}
}
} // namespace api } // namespace api
} // namespace esphome } // namespace esphome
#endif #endif

View File

@@ -33,7 +33,7 @@ class APIConnection : public APIServerConnection {
bool send_list_info_done() { bool send_list_info_done() {
return this->schedule_message_(nullptr, &APIConnection::try_send_list_info_done, return this->schedule_message_(nullptr, &APIConnection::try_send_list_info_done,
ListEntitiesDoneResponse::MESSAGE_TYPE, ListEntitiesDoneResponse::ESTIMATED_SIZE); ListEntitiesDoneResponse::MESSAGE_TYPE);
} }
#ifdef USE_BINARY_SENSOR #ifdef USE_BINARY_SENSOR
bool send_binary_sensor_state(binary_sensor::BinarySensor *binary_sensor); bool send_binary_sensor_state(binary_sensor::BinarySensor *binary_sensor);
@@ -60,8 +60,8 @@ class APIConnection : public APIServerConnection {
#ifdef USE_TEXT_SENSOR #ifdef USE_TEXT_SENSOR
bool send_text_sensor_state(text_sensor::TextSensor *text_sensor); bool send_text_sensor_state(text_sensor::TextSensor *text_sensor);
#endif #endif
#ifdef USE_CAMERA #ifdef USE_ESP32_CAMERA
void set_camera_state(std::shared_ptr<camera::CameraImage> image); void set_camera_state(std::shared_ptr<esp32_camera::CameraImage> image);
void camera_image(const CameraImageRequest &msg) override; void camera_image(const CameraImageRequest &msg) override;
#endif #endif
#ifdef USE_CLIMATE #ifdef USE_CLIMATE
@@ -107,7 +107,7 @@ class APIConnection : public APIServerConnection {
bool send_media_player_state(media_player::MediaPlayer *media_player); bool send_media_player_state(media_player::MediaPlayer *media_player);
void media_player_command(const MediaPlayerCommandRequest &msg) override; void media_player_command(const MediaPlayerCommandRequest &msg) override;
#endif #endif
bool try_send_log_message(int level, const char *tag, const char *line, size_t message_len); bool try_send_log_message(int level, const char *tag, const char *line);
void send_homeassistant_service_call(const HomeassistantServiceResponse &call) { void send_homeassistant_service_call(const HomeassistantServiceResponse &call) {
if (!this->flags_.service_call_subscription) if (!this->flags_.service_call_subscription)
return; return;
@@ -195,9 +195,7 @@ class APIConnection : public APIServerConnection {
// TODO // TODO
return {}; return {};
} }
#ifdef USE_API_SERVICES
void execute_service(const ExecuteServiceRequest &msg) override; void execute_service(const ExecuteServiceRequest &msg) override;
#endif
#ifdef USE_API_NOISE #ifdef USE_API_NOISE
NoiseEncryptionSetKeyResponse noise_encryption_set_key(const NoiseEncryptionSetKeyRequest &msg) override; NoiseEncryptionSetKeyResponse noise_encryption_set_key(const NoiseEncryptionSetKeyRequest &msg) override;
#endif #endif
@@ -258,7 +256,7 @@ class APIConnection : public APIServerConnection {
} }
bool try_to_clear_buffer(bool log_out_of_space); bool try_to_clear_buffer(bool log_out_of_space);
bool send_buffer(ProtoWriteBuffer buffer, uint8_t message_type) override; bool send_buffer(ProtoWriteBuffer buffer, uint16_t message_type) override;
std::string get_client_combined_info() const { std::string get_client_combined_info() const {
if (this->client_info_ == this->client_peername_) { if (this->client_info_ == this->client_peername_) {
@@ -300,14 +298,9 @@ class APIConnection : public APIServerConnection {
} }
// Non-template helper to encode any ProtoMessage // Non-template helper to encode any ProtoMessage
static uint16_t encode_message_to_buffer(ProtoMessage &msg, uint8_t message_type, APIConnection *conn, static uint16_t encode_message_to_buffer(ProtoMessage &msg, uint16_t message_type, APIConnection *conn,
uint32_t remaining_size, bool is_single); uint32_t remaining_size, bool is_single);
#ifdef USE_VOICE_ASSISTANT
// Helper to check voice assistant validity and connection ownership
inline bool check_voice_assistant_api_connection_() const;
#endif
// Helper method to process multiple entities from an iterator in a batch // Helper method to process multiple entities from an iterator in a batch
template<typename Iterator> void process_iterator_batch_(Iterator &iterator) { template<typename Iterator> void process_iterator_batch_(Iterator &iterator) {
size_t initial_size = this->deferred_batch_.size(); size_t initial_size = this->deferred_batch_.size();
@@ -432,7 +425,7 @@ class APIConnection : public APIServerConnection {
static uint16_t try_send_update_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, static uint16_t try_send_update_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single); bool is_single);
#endif #endif
#ifdef USE_CAMERA #ifdef USE_ESP32_CAMERA
static uint16_t try_send_camera_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, static uint16_t try_send_camera_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single); bool is_single);
#endif #endif
@@ -445,6 +438,9 @@ class APIConnection : public APIServerConnection {
static uint16_t try_send_disconnect_request(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, static uint16_t try_send_disconnect_request(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single); bool is_single);
// Helper function to get estimated message size for buffer pre-allocation
static uint16_t get_estimated_message_size(uint16_t message_type);
// Batch message method for ping requests // Batch message method for ping requests
static uint16_t try_send_ping_request(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, static uint16_t try_send_ping_request(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
bool is_single); bool is_single);
@@ -459,8 +455,8 @@ class APIConnection : public APIServerConnection {
// These contain vectors/pointers internally, so putting them early ensures good alignment // These contain vectors/pointers internally, so putting them early ensures good alignment
InitialStateIterator initial_state_iterator_; InitialStateIterator initial_state_iterator_;
ListEntitiesIterator list_entities_iterator_; ListEntitiesIterator list_entities_iterator_;
#ifdef USE_CAMERA #ifdef USE_ESP32_CAMERA
std::unique_ptr<camera::CameraImageReader> image_reader_; esp32_camera::CameraImageReader image_reader_;
#endif #endif
// Group 3: Strings (12 bytes each on 32-bit, 4-byte aligned) // Group 3: Strings (12 bytes each on 32-bit, 4-byte aligned)
@@ -504,10 +500,10 @@ class APIConnection : public APIServerConnection {
// Call operator - uses message_type to determine union type // Call operator - uses message_type to determine union type
uint16_t operator()(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single, uint16_t operator()(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single,
uint8_t message_type) const; uint16_t message_type) const;
// Manual cleanup method - must be called before destruction for string types // Manual cleanup method - must be called before destruction for string types
void cleanup(uint8_t message_type) { void cleanup(uint16_t message_type) {
#ifdef USE_EVENT #ifdef USE_EVENT
if (message_type == EventResponse::MESSAGE_TYPE && data_.string_ptr != nullptr) { if (message_type == EventResponse::MESSAGE_TYPE && data_.string_ptr != nullptr) {
delete data_.string_ptr; delete data_.string_ptr;
@@ -528,12 +524,11 @@ class APIConnection : public APIServerConnection {
struct BatchItem { struct BatchItem {
EntityBase *entity; // Entity pointer EntityBase *entity; // Entity pointer
MessageCreator creator; // Function that creates the message when needed MessageCreator creator; // Function that creates the message when needed
uint8_t message_type; // Message type for overhead calculation (max 255) uint16_t message_type; // Message type for overhead calculation
uint8_t estimated_size; // Estimated message size (max 255 bytes)
// Constructor for creating BatchItem // Constructor for creating BatchItem
BatchItem(EntityBase *entity, MessageCreator creator, uint8_t message_type, uint8_t estimated_size) BatchItem(EntityBase *entity, MessageCreator creator, uint16_t message_type)
: entity(entity), creator(std::move(creator)), message_type(message_type), estimated_size(estimated_size) {} : entity(entity), creator(std::move(creator)), message_type(message_type) {}
}; };
std::vector<BatchItem> items; std::vector<BatchItem> items;
@@ -559,9 +554,9 @@ class APIConnection : public APIServerConnection {
} }
// Add item to the batch // Add item to the batch
void add_item(EntityBase *entity, MessageCreator creator, uint8_t message_type, uint8_t estimated_size); void add_item(EntityBase *entity, MessageCreator creator, uint16_t message_type);
// Add item to the front of the batch (for high priority messages like ping) // Add item to the front of the batch (for high priority messages like ping)
void add_item_front(EntityBase *entity, MessageCreator creator, uint8_t message_type, uint8_t estimated_size); void add_item_front(EntityBase *entity, MessageCreator creator, uint16_t message_type);
// Clear all items with proper cleanup // Clear all items with proper cleanup
void clear() { void clear() {
@@ -630,7 +625,7 @@ class APIConnection : public APIServerConnection {
// to send in one go. This is the maximum size of a single packet // to send in one go. This is the maximum size of a single packet
// that can be sent over the network. // that can be sent over the network.
// This is to avoid fragmentation of the packet. // This is to avoid fragmentation of the packet.
static constexpr size_t MAX_BATCH_PACKET_SIZE = 1390; // MTU static constexpr size_t MAX_PACKET_SIZE = 1390; // MTU
bool schedule_batch_(); bool schedule_batch_();
void process_batch_(); void process_batch_();
@@ -641,9 +636,9 @@ class APIConnection : public APIServerConnection {
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
// Helper to log a proto message from a MessageCreator object // Helper to log a proto message from a MessageCreator object
void log_proto_message_(EntityBase *entity, const MessageCreator &creator, uint8_t message_type) { void log_proto_message_(EntityBase *entity, const MessageCreator &creator, uint16_t message_type) {
this->flags_.log_only_mode = true; this->flags_.log_only_mode = true;
creator(entity, this, MAX_BATCH_PACKET_SIZE, true, message_type); creator(entity, this, MAX_PACKET_SIZE, true, message_type);
this->flags_.log_only_mode = false; this->flags_.log_only_mode = false;
} }
@@ -654,8 +649,7 @@ class APIConnection : public APIServerConnection {
#endif #endif
// Helper method to send a message either immediately or via batching // Helper method to send a message either immediately or via batching
bool send_message_smart_(EntityBase *entity, MessageCreatorPtr creator, uint8_t message_type, bool send_message_smart_(EntityBase *entity, MessageCreatorPtr creator, uint16_t message_type) {
uint8_t estimated_size) {
// Try to send immediately if: // Try to send immediately if:
// 1. We should try to send immediately (should_try_send_immediately = true) // 1. We should try to send immediately (should_try_send_immediately = true)
// 2. Batch delay is 0 (user has opted in to immediate sending) // 2. Batch delay is 0 (user has opted in to immediate sending)
@@ -663,7 +657,7 @@ class APIConnection : public APIServerConnection {
if (this->flags_.should_try_send_immediately && this->get_batch_delay_ms_() == 0 && if (this->flags_.should_try_send_immediately && this->get_batch_delay_ms_() == 0 &&
this->helper_->can_write_without_blocking()) { this->helper_->can_write_without_blocking()) {
// Now actually encode and send // Now actually encode and send
if (creator(entity, this, MAX_BATCH_PACKET_SIZE, true) && if (creator(entity, this, MAX_PACKET_SIZE, true) &&
this->send_buffer(ProtoWriteBuffer{&this->parent_->get_shared_buffer_ref()}, message_type)) { this->send_buffer(ProtoWriteBuffer{&this->parent_->get_shared_buffer_ref()}, message_type)) {
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
// Log the message in verbose mode // Log the message in verbose mode
@@ -676,25 +670,23 @@ class APIConnection : public APIServerConnection {
} }
// Fall back to scheduled batching // Fall back to scheduled batching
return this->schedule_message_(entity, creator, message_type, estimated_size); return this->schedule_message_(entity, creator, message_type);
} }
// Helper function to schedule a deferred message with known message type // Helper function to schedule a deferred message with known message type
bool schedule_message_(EntityBase *entity, MessageCreator creator, uint8_t message_type, uint8_t estimated_size) { bool schedule_message_(EntityBase *entity, MessageCreator creator, uint16_t message_type) {
this->deferred_batch_.add_item(entity, std::move(creator), message_type, estimated_size); this->deferred_batch_.add_item(entity, std::move(creator), message_type);
return this->schedule_batch_(); return this->schedule_batch_();
} }
// Overload for function pointers (for info messages and current state reads) // Overload for function pointers (for info messages and current state reads)
bool schedule_message_(EntityBase *entity, MessageCreatorPtr function_ptr, uint8_t message_type, bool schedule_message_(EntityBase *entity, MessageCreatorPtr function_ptr, uint16_t message_type) {
uint8_t estimated_size) { return schedule_message_(entity, MessageCreator(function_ptr), message_type);
return schedule_message_(entity, MessageCreator(function_ptr), message_type, estimated_size);
} }
// Helper function to schedule a high priority message at the front of the batch // Helper function to schedule a high priority message at the front of the batch
bool schedule_message_front_(EntityBase *entity, MessageCreatorPtr function_ptr, uint8_t message_type, bool schedule_message_front_(EntityBase *entity, MessageCreatorPtr function_ptr, uint16_t message_type) {
uint8_t estimated_size) { this->deferred_batch_.add_item_front(entity, MessageCreator(function_ptr), message_type);
this->deferred_batch_.add_item_front(entity, MessageCreator(function_ptr), message_type, estimated_size);
return this->schedule_batch_(); return this->schedule_batch_();
} }
}; };

View File

@@ -5,6 +5,7 @@
#include "esphome/core/helpers.h" #include "esphome/core/helpers.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
#include "proto.h" #include "proto.h"
#include "api_pb2_size.h"
#include <cstring> #include <cstring>
#include <cinttypes> #include <cinttypes>
@@ -224,22 +225,6 @@ APIError APIFrameHelper::init_common_() {
} }
#define HELPER_LOG(msg, ...) ESP_LOGVV(TAG, "%s: " msg, this->info_.c_str(), ##__VA_ARGS__) #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 // uncomment to log raw packets
//#define HELPER_LOG_PACKETS //#define HELPER_LOG_PACKETS
@@ -342,9 +327,17 @@ APIError APINoiseFrameHelper::try_read_frame_(ParsedFrame *frame) {
// no header information yet // no header information yet
uint8_t to_read = 3 - rx_header_buf_len_; uint8_t to_read = 3 - rx_header_buf_len_;
ssize_t received = this->socket_->read(&rx_header_buf_[rx_header_buf_len_], to_read); ssize_t received = this->socket_->read(&rx_header_buf_[rx_header_buf_len_], to_read);
APIError err = handle_socket_read_result_(received); if (received == -1) {
if (err != APIError::OK) { if (errno == EWOULDBLOCK || errno == EAGAIN) {
return err; 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;
} }
rx_header_buf_len_ += static_cast<uint8_t>(received); rx_header_buf_len_ += static_cast<uint8_t>(received);
if (static_cast<uint8_t>(received) != to_read) { if (static_cast<uint8_t>(received) != to_read) {
@@ -379,9 +372,17 @@ APIError APINoiseFrameHelper::try_read_frame_(ParsedFrame *frame) {
// more data to read // more data to read
uint16_t to_read = msg_size - rx_buf_len_; uint16_t to_read = msg_size - rx_buf_len_;
ssize_t received = this->socket_->read(&rx_buf_[rx_buf_len_], to_read); ssize_t received = this->socket_->read(&rx_buf_[rx_buf_len_], to_read);
APIError err = handle_socket_read_result_(received); if (received == -1) {
if (err != APIError::OK) { if (errno == EWOULDBLOCK || errno == EAGAIN) {
return err; 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;
} }
rx_buf_len_ += static_cast<uint16_t>(received); rx_buf_len_ += static_cast<uint16_t>(received);
if (static_cast<uint16_t>(received) != to_read) { if (static_cast<uint16_t>(received) != to_read) {
@@ -612,15 +613,21 @@ APIError APINoiseFrameHelper::read_packet(ReadPacketBuffer *buffer) {
buffer->type = type; buffer->type = type;
return APIError::OK; return APIError::OK;
} }
APIError APINoiseFrameHelper::write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) { 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_);
// Resize to include MAC space (required for Noise encryption) // Resize to include MAC space (required for Noise encryption)
buffer.get_buffer()->resize(buffer.get_buffer()->size() + frame_footer_size_); raw_buffer->resize(raw_buffer->size() + frame_footer_size_);
PacketInfo packet{type, 0,
static_cast<uint16_t>(buffer.get_buffer()->size() - frame_header_padding_ - frame_footer_size_)}; // Use write_protobuf_packets with a single packet
return write_protobuf_packets(buffer, std::span<const PacketInfo>(&packet, 1)); std::vector<PacketInfo> packets;
packets.emplace_back(type, 0, payload_len);
return write_protobuf_packets(buffer, packets);
} }
APIError APINoiseFrameHelper::write_protobuf_packets(ProtoWriteBuffer buffer, std::span<const PacketInfo> packets) { APIError APINoiseFrameHelper::write_protobuf_packets(ProtoWriteBuffer buffer, const std::vector<PacketInfo> &packets) {
APIError aerr = state_action_(); APIError aerr = state_action_();
if (aerr != APIError::OK) { if (aerr != APIError::OK) {
return aerr; return aerr;
@@ -635,15 +642,18 @@ APIError APINoiseFrameHelper::write_protobuf_packets(ProtoWriteBuffer buffer, st
} }
std::vector<uint8_t> *raw_buffer = buffer.get_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_.clear();
this->reusable_iovs_.reserve(packets.size()); this->reusable_iovs_.reserve(packets.size());
// We need to encrypt each packet in place // We need to encrypt each packet in place
for (const auto &packet : packets) { 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 // The buffer already has padding at offset
uint8_t *buf_start = buffer_data + packet.offset; uint8_t *buf_start = raw_buffer->data() + offset;
// Write noise header // Write noise header
buf_start[0] = 0x01; // indicator buf_start[0] = 0x01; // indicator
@@ -651,10 +661,10 @@ APIError APINoiseFrameHelper::write_protobuf_packets(ProtoWriteBuffer buffer, st
// Write message header (to be encrypted) // Write message header (to be encrypted)
const uint8_t msg_offset = 3; const uint8_t msg_offset = 3;
buf_start[msg_offset] = static_cast<uint8_t>(packet.message_type >> 8); // type high byte buf_start[msg_offset + 0] = (uint8_t) (type >> 8); // type high byte
buf_start[msg_offset + 1] = static_cast<uint8_t>(packet.message_type); // type low byte buf_start[msg_offset + 1] = (uint8_t) 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 + 2] = (uint8_t) (payload_len >> 8); // data_len high byte
buf_start[msg_offset + 3] = static_cast<uint8_t>(packet.payload_size); // data_len low byte buf_start[msg_offset + 3] = (uint8_t) payload_len; // data_len low byte
// payload data is already in the buffer starting at offset + 7 // payload data is already in the buffer starting at offset + 7
// Make sure we have space for MAC // Make sure we have space for MAC
@@ -663,8 +673,7 @@ APIError APINoiseFrameHelper::write_protobuf_packets(ProtoWriteBuffer buffer, st
// Encrypt the message in place // Encrypt the message in place
NoiseBuffer mbuf; NoiseBuffer mbuf;
noise_buffer_init(mbuf); noise_buffer_init(mbuf);
noise_buffer_set_inout(mbuf, buf_start + msg_offset, 4 + packet.payload_size, noise_buffer_set_inout(mbuf, buf_start + msg_offset, msg_len, msg_len + frame_footer_size_);
4 + packet.payload_size + frame_footer_size_);
int err = noise_cipherstate_encrypt(send_cipher_, &mbuf); int err = noise_cipherstate_encrypt(send_cipher_, &mbuf);
if (err != 0) { if (err != 0) {
@@ -674,12 +683,14 @@ APIError APINoiseFrameHelper::write_protobuf_packets(ProtoWriteBuffer buffer, st
} }
// Fill in the encrypted size // Fill in the encrypted size
buf_start[1] = static_cast<uint8_t>(mbuf.size >> 8); buf_start[1] = (uint8_t) (mbuf.size >> 8);
buf_start[2] = static_cast<uint8_t>(mbuf.size); buf_start[2] = (uint8_t) mbuf.size;
// Add iovec for this encrypted packet // Add iovec for this encrypted packet
this->reusable_iovs_.push_back( struct iovec iov;
{buf_start, static_cast<size_t>(3 + mbuf.size)}); // indicator + size + encrypted data iov.iov_base = buf_start;
iov.iov_len = 3 + mbuf.size; // indicator + size + encrypted data
this->reusable_iovs_.push_back(iov);
} }
// Send all encrypted packets in one writev call // Send all encrypted packets in one writev call
@@ -854,9 +865,17 @@ 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 // Try to get to at least 3 bytes total (indicator + 2 varint bytes), then read one byte at a time
ssize_t received = ssize_t received =
this->socket_->read(&rx_header_buf_[rx_header_buf_pos_], rx_header_buf_pos_ < 3 ? 3 - rx_header_buf_pos_ : 1); this->socket_->read(&rx_header_buf_[rx_header_buf_pos_], rx_header_buf_pos_ < 3 ? 3 - rx_header_buf_pos_ : 1);
APIError err = handle_socket_read_result_(received); if (received == -1) {
if (err != APIError::OK) { if (errno == EWOULDBLOCK || errno == EAGAIN) {
return err; 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;
} }
// If this was the first read, validate the indicator byte // If this was the first read, validate the indicator byte
@@ -940,9 +959,17 @@ APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) {
// more data to read // more data to read
uint16_t to_read = rx_header_parsed_len_ - rx_buf_len_; uint16_t to_read = rx_header_parsed_len_ - rx_buf_len_;
ssize_t received = this->socket_->read(&rx_buf_[rx_buf_len_], to_read); ssize_t received = this->socket_->read(&rx_buf_[rx_buf_len_], to_read);
APIError err = handle_socket_read_result_(received); if (received == -1) {
if (err != APIError::OK) { if (errno == EWOULDBLOCK || errno == EAGAIN) {
return err; 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;
} }
rx_buf_len_ += static_cast<uint16_t>(received); rx_buf_len_ += static_cast<uint16_t>(received);
if (static_cast<uint16_t>(received) != to_read) { if (static_cast<uint16_t>(received) != to_read) {
@@ -1001,12 +1028,19 @@ APIError APIPlaintextFrameHelper::read_packet(ReadPacketBuffer *buffer) {
buffer->type = rx_header_parsed_type_; buffer->type = rx_header_parsed_type_;
return APIError::OK; return APIError::OK;
} }
APIError APIPlaintextFrameHelper::write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) { APIError APIPlaintextFrameHelper::write_protobuf_packet(uint16_t type, ProtoWriteBuffer buffer) {
PacketInfo packet{type, 0, static_cast<uint16_t>(buffer.get_buffer()->size() - frame_header_padding_)}; std::vector<uint8_t> *raw_buffer = buffer.get_buffer();
return write_protobuf_packets(buffer, std::span<const PacketInfo>(&packet, 1)); 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_packets(ProtoWriteBuffer buffer, std::span<const PacketInfo> packets) { APIError APIPlaintextFrameHelper::write_protobuf_packets(ProtoWriteBuffer buffer,
const std::vector<PacketInfo> &packets) {
if (state_ != State::DATA) { if (state_ != State::DATA) {
return APIError::BAD_STATE; return APIError::BAD_STATE;
} }
@@ -1016,15 +1050,17 @@ APIError APIPlaintextFrameHelper::write_protobuf_packets(ProtoWriteBuffer buffer
} }
std::vector<uint8_t> *raw_buffer = buffer.get_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_.clear();
this->reusable_iovs_.reserve(packets.size()); this->reusable_iovs_.reserve(packets.size());
for (const auto &packet : packets) { 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 // Calculate varint sizes for header layout
uint8_t size_varint_len = api::ProtoSize::varint(static_cast<uint32_t>(packet.payload_size)); 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>(packet.message_type)); uint8_t type_varint_len = api::ProtoSize::varint(static_cast<uint32_t>(type));
uint8_t total_header_len = 1 + size_varint_len + type_varint_len; uint8_t total_header_len = 1 + size_varint_len + type_varint_len;
// Calculate where to start writing the header // Calculate where to start writing the header
@@ -1052,20 +1088,23 @@ APIError APIPlaintextFrameHelper::write_protobuf_packets(ProtoWriteBuffer buffer
// //
// The message starts at offset + frame_header_padding_ // The message starts at offset + frame_header_padding_
// So we write the header starting at offset + frame_header_padding_ - total_header_len // So we write the header starting at offset + frame_header_padding_ - total_header_len
uint8_t *buf_start = buffer_data + packet.offset; uint8_t *buf_start = raw_buffer->data() + offset;
uint32_t header_offset = frame_header_padding_ - total_header_len; uint32_t header_offset = frame_header_padding_ - total_header_len;
// Write the plaintext header // Write the plaintext header
buf_start[header_offset] = 0x00; // indicator buf_start[header_offset] = 0x00; // indicator
// Encode varints directly into buffer // Encode size varint directly into buffer
ProtoVarInt(packet.payload_size).encode_to_buffer_unchecked(buf_start + header_offset + 1, size_varint_len); ProtoVarInt(payload_len).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); // Encode type varint directly into buffer
ProtoVarInt(type).encode_to_buffer_unchecked(buf_start + header_offset + 1 + size_varint_len, type_varint_len);
// Add iovec for this packet (header + payload) // Add iovec for this packet (header + payload)
this->reusable_iovs_.push_back( struct iovec iov;
{buf_start + header_offset, static_cast<size_t>(total_header_len + packet.payload_size)}); iov.iov_base = buf_start + header_offset;
iov.iov_len = total_header_len + payload_len;
this->reusable_iovs_.push_back(iov);
} }
// Send all packets in one writev call // Send all packets in one writev call

View File

@@ -2,7 +2,6 @@
#include <cstdint> #include <cstdint>
#include <deque> #include <deque>
#include <limits> #include <limits>
#include <span>
#include <utility> #include <utility>
#include <vector> #include <vector>
@@ -30,11 +29,13 @@ struct ReadPacketBuffer {
// Packed packet info structure to minimize memory usage // Packed packet info structure to minimize memory usage
struct PacketInfo { struct PacketInfo {
uint16_t offset; // Offset in buffer where message starts uint16_t message_type; // 2 bytes
uint16_t payload_size; // Size of the message payload uint16_t offset; // 2 bytes (sufficient for packet size ~1460 bytes)
uint8_t message_type; // Message type (0-255) uint16_t payload_size; // 2 bytes (up to 65535 bytes)
uint16_t padding; // 2 byte (for alignment)
PacketInfo(uint8_t type, uint16_t off, uint16_t size) : offset(off), payload_size(size), message_type(type) {} PacketInfo(uint16_t type, uint16_t off, uint16_t size)
: message_type(type), offset(off), payload_size(size), padding(0) {}
}; };
enum class APIError : uint16_t { enum class APIError : uint16_t {
@@ -96,11 +97,11 @@ class APIFrameHelper {
} }
// Give this helper a name for logging // Give this helper a name for logging
void set_log_info(std::string info) { info_ = std::move(info); } void set_log_info(std::string info) { info_ = std::move(info); }
virtual APIError write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) = 0; virtual APIError write_protobuf_packet(uint16_t type, ProtoWriteBuffer buffer) = 0;
// Write multiple protobuf packets in a single operation // Write multiple protobuf packets in a single operation
// packets contains (message_type, offset, length) for each message in the buffer // packets contains (message_type, offset, length) for each message in the buffer
// The buffer contains all messages with appropriate padding before each // The buffer contains all messages with appropriate padding before each
virtual APIError write_protobuf_packets(ProtoWriteBuffer buffer, std::span<const PacketInfo> packets) = 0; virtual APIError write_protobuf_packets(ProtoWriteBuffer buffer, const std::vector<PacketInfo> &packets) = 0;
// Get the frame header padding required by this protocol // Get the frame header padding required by this protocol
virtual uint8_t frame_header_padding() = 0; virtual uint8_t frame_header_padding() = 0;
// Get the frame footer size required by this protocol // Get the frame footer size required by this protocol
@@ -174,9 +175,6 @@ class APIFrameHelper {
// Common initialization for both plaintext and noise protocols // Common initialization for both plaintext and noise protocols
APIError init_common_(); APIError init_common_();
// Helper method to handle socket read results
APIError handle_socket_read_result_(ssize_t received);
}; };
#ifdef USE_API_NOISE #ifdef USE_API_NOISE
@@ -195,8 +193,8 @@ class APINoiseFrameHelper : public APIFrameHelper {
APIError init() override; APIError init() override;
APIError loop() override; APIError loop() override;
APIError read_packet(ReadPacketBuffer *buffer) override; APIError read_packet(ReadPacketBuffer *buffer) override;
APIError write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) override; APIError write_protobuf_packet(uint16_t type, ProtoWriteBuffer buffer) override;
APIError write_protobuf_packets(ProtoWriteBuffer buffer, std::span<const PacketInfo> packets) override; APIError write_protobuf_packets(ProtoWriteBuffer buffer, const std::vector<PacketInfo> &packets) override;
// Get the frame header padding required by this protocol // Get the frame header padding required by this protocol
uint8_t frame_header_padding() override { return frame_header_padding_; } uint8_t frame_header_padding() override { return frame_header_padding_; }
// Get the frame footer size required by this protocol // Get the frame footer size required by this protocol
@@ -249,8 +247,8 @@ class APIPlaintextFrameHelper : public APIFrameHelper {
APIError init() override; APIError init() override;
APIError loop() override; APIError loop() override;
APIError read_packet(ReadPacketBuffer *buffer) override; APIError read_packet(ReadPacketBuffer *buffer) override;
APIError write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) override; APIError write_protobuf_packet(uint16_t type, ProtoWriteBuffer buffer) override;
APIError write_protobuf_packets(ProtoWriteBuffer buffer, std::span<const PacketInfo> packets) override; APIError write_protobuf_packets(ProtoWriteBuffer buffer, const std::vector<PacketInfo> &packets) override;
uint8_t frame_header_padding() override { return frame_header_padding_; } uint8_t frame_header_padding() override { return frame_header_padding_; }
// Get the frame footer size required by this protocol // Get the frame footer size required by this protocol
uint8_t frame_footer_size() override { return frame_footer_size_; } uint8_t frame_footer_size() override { return frame_footer_size_; }

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

View File

@@ -195,7 +195,6 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
this->on_home_assistant_state_response(msg); this->on_home_assistant_state_response(msg);
break; break;
} }
#ifdef USE_API_SERVICES
case 42: { case 42: {
ExecuteServiceRequest msg; ExecuteServiceRequest msg;
msg.decode(msg_data, msg_size); msg.decode(msg_data, msg_size);
@@ -205,8 +204,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
this->on_execute_service_request(msg); this->on_execute_service_request(msg);
break; break;
} }
#endif #ifdef USE_ESP32_CAMERA
#ifdef USE_CAMERA
case 45: { case 45: {
CameraImageRequest msg; CameraImageRequest msg;
msg.decode(msg_data, msg_size); msg.decode(msg_data, msg_size);
@@ -662,13 +660,11 @@ void APIServerConnection::on_get_time_request(const GetTimeRequest &msg) {
} }
} }
} }
#ifdef USE_API_SERVICES
void APIServerConnection::on_execute_service_request(const ExecuteServiceRequest &msg) { void APIServerConnection::on_execute_service_request(const ExecuteServiceRequest &msg) {
if (this->check_authenticated_()) { if (this->check_authenticated_()) {
this->execute_service(msg); this->execute_service(msg);
} }
} }
#endif
#ifdef USE_API_NOISE #ifdef USE_API_NOISE
void APIServerConnection::on_noise_encryption_set_key_request(const NoiseEncryptionSetKeyRequest &msg) { void APIServerConnection::on_noise_encryption_set_key_request(const NoiseEncryptionSetKeyRequest &msg) {
if (this->check_authenticated_()) { if (this->check_authenticated_()) {
@@ -686,7 +682,7 @@ void APIServerConnection::on_button_command_request(const ButtonCommandRequest &
} }
} }
#endif #endif
#ifdef USE_CAMERA #ifdef USE_ESP32_CAMERA
void APIServerConnection::on_camera_image_request(const CameraImageRequest &msg) { void APIServerConnection::on_camera_image_request(const CameraImageRequest &msg) {
if (this->check_authenticated_()) { if (this->check_authenticated_()) {
this->camera_image(msg); this->camera_image(msg);

View File

@@ -69,11 +69,9 @@ class APIServerConnectionBase : public ProtoService {
virtual void on_get_time_request(const GetTimeRequest &value){}; virtual void on_get_time_request(const GetTimeRequest &value){};
virtual void on_get_time_response(const GetTimeResponse &value){}; virtual void on_get_time_response(const GetTimeResponse &value){};
#ifdef USE_API_SERVICES
virtual void on_execute_service_request(const ExecuteServiceRequest &value){}; virtual void on_execute_service_request(const ExecuteServiceRequest &value){};
#endif
#ifdef USE_CAMERA #ifdef USE_ESP32_CAMERA
virtual void on_camera_image_request(const CameraImageRequest &value){}; virtual void on_camera_image_request(const CameraImageRequest &value){};
#endif #endif
@@ -218,16 +216,14 @@ class APIServerConnection : public APIServerConnectionBase {
virtual void subscribe_homeassistant_services(const SubscribeHomeassistantServicesRequest &msg) = 0; virtual void subscribe_homeassistant_services(const SubscribeHomeassistantServicesRequest &msg) = 0;
virtual void subscribe_home_assistant_states(const SubscribeHomeAssistantStatesRequest &msg) = 0; virtual void subscribe_home_assistant_states(const SubscribeHomeAssistantStatesRequest &msg) = 0;
virtual GetTimeResponse get_time(const GetTimeRequest &msg) = 0; virtual GetTimeResponse get_time(const GetTimeRequest &msg) = 0;
#ifdef USE_API_SERVICES
virtual void execute_service(const ExecuteServiceRequest &msg) = 0; virtual void execute_service(const ExecuteServiceRequest &msg) = 0;
#endif
#ifdef USE_API_NOISE #ifdef USE_API_NOISE
virtual NoiseEncryptionSetKeyResponse noise_encryption_set_key(const NoiseEncryptionSetKeyRequest &msg) = 0; virtual NoiseEncryptionSetKeyResponse noise_encryption_set_key(const NoiseEncryptionSetKeyRequest &msg) = 0;
#endif #endif
#ifdef USE_BUTTON #ifdef USE_BUTTON
virtual void button_command(const ButtonCommandRequest &msg) = 0; virtual void button_command(const ButtonCommandRequest &msg) = 0;
#endif #endif
#ifdef USE_CAMERA #ifdef USE_ESP32_CAMERA
virtual void camera_image(const CameraImageRequest &msg) = 0; virtual void camera_image(const CameraImageRequest &msg) = 0;
#endif #endif
#ifdef USE_CLIMATE #ifdef USE_CLIMATE
@@ -337,16 +333,14 @@ class APIServerConnection : public APIServerConnectionBase {
void on_subscribe_homeassistant_services_request(const SubscribeHomeassistantServicesRequest &msg) override; void on_subscribe_homeassistant_services_request(const SubscribeHomeassistantServicesRequest &msg) override;
void on_subscribe_home_assistant_states_request(const SubscribeHomeAssistantStatesRequest &msg) override; void on_subscribe_home_assistant_states_request(const SubscribeHomeAssistantStatesRequest &msg) override;
void on_get_time_request(const GetTimeRequest &msg) override; void on_get_time_request(const GetTimeRequest &msg) override;
#ifdef USE_API_SERVICES
void on_execute_service_request(const ExecuteServiceRequest &msg) override; void on_execute_service_request(const ExecuteServiceRequest &msg) override;
#endif
#ifdef USE_API_NOISE #ifdef USE_API_NOISE
void on_noise_encryption_set_key_request(const NoiseEncryptionSetKeyRequest &msg) override; void on_noise_encryption_set_key_request(const NoiseEncryptionSetKeyRequest &msg) override;
#endif #endif
#ifdef USE_BUTTON #ifdef USE_BUTTON
void on_button_command_request(const ButtonCommandRequest &msg) override; void on_button_command_request(const ButtonCommandRequest &msg) override;
#endif #endif
#ifdef USE_CAMERA #ifdef USE_ESP32_CAMERA
void on_camera_image_request(const CameraImageRequest &msg) override; void on_camera_image_request(const CameraImageRequest &msg) override;
#endif #endif
#ifdef USE_CLIMATE #ifdef USE_CLIMATE

View File

@@ -0,0 +1,359 @@
#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 version takes a ProtoMessage object, calculates its size internally,
* and updates the total_size reference. This eliminates the need for a temporary variable
* at the call site.
*
* @param message The nested message object
*/
static inline void add_message_object(uint32_t &total_size, uint32_t field_id_size, const ProtoMessage &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

View File

@@ -96,30 +96,30 @@ void APIServer::setup() {
#ifdef USE_LOGGER #ifdef USE_LOGGER
if (logger::global_logger != nullptr) { if (logger::global_logger != nullptr) {
logger::global_logger->add_on_log_callback( logger::global_logger->add_on_log_callback([this](int level, const char *tag, const char *message) {
[this](int level, const char *tag, const char *message, size_t message_len) { if (this->shutting_down_) {
if (this->shutting_down_) { // Don't try to send logs during shutdown
// Don't try to send logs during shutdown // as it could result in a recursion and
// as it could result in a recursion and // we would be filling a buffer we are trying to clear
// we would be filling a buffer we are trying to clear return;
return; }
} for (auto &c : this->clients_) {
for (auto &c : this->clients_) { if (!c->flags_.remove)
if (!c->flags_.remove) c->try_send_log_message(level, tag, message);
c->try_send_log_message(level, tag, message, message_len); }
} });
});
} }
#endif #endif
#ifdef USE_CAMERA #ifdef USE_ESP32_CAMERA
if (camera::Camera::instance() != nullptr && !camera::Camera::instance()->is_internal()) { if (esp32_camera::global_esp32_camera != nullptr && !esp32_camera::global_esp32_camera->is_internal()) {
camera::Camera::instance()->add_image_callback([this](const std::shared_ptr<camera::CameraImage> &image) { esp32_camera::global_esp32_camera->add_image_callback(
for (auto &c : this->clients_) { [this](const std::shared_ptr<esp32_camera::CameraImage> &image) {
if (!c->flags_.remove) for (auto &c : this->clients_) {
c->set_camera_state(image); if (!c->flags_.remove)
} c->set_camera_state(image);
}); }
});
} }
#endif #endif
} }
@@ -253,114 +253,180 @@ bool APIServer::check_password(const std::string &password) const {
void APIServer::handle_disconnect(APIConnection *conn) {} 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 #ifdef USE_BINARY_SENSOR
API_DISPATCH_UPDATE(binary_sensor::BinarySensor, binary_sensor) void APIServer::on_binary_sensor_update(binary_sensor::BinarySensor *obj) {
if (obj->is_internal())
return;
for (auto &c : this->clients_)
c->send_binary_sensor_state(obj);
}
#endif #endif
#ifdef USE_COVER #ifdef USE_COVER
API_DISPATCH_UPDATE(cover::Cover, cover) void APIServer::on_cover_update(cover::Cover *obj) {
if (obj->is_internal())
return;
for (auto &c : this->clients_)
c->send_cover_state(obj);
}
#endif #endif
#ifdef USE_FAN #ifdef USE_FAN
API_DISPATCH_UPDATE(fan::Fan, fan) void APIServer::on_fan_update(fan::Fan *obj) {
if (obj->is_internal())
return;
for (auto &c : this->clients_)
c->send_fan_state(obj);
}
#endif #endif
#ifdef USE_LIGHT #ifdef USE_LIGHT
API_DISPATCH_UPDATE(light::LightState, light) void APIServer::on_light_update(light::LightState *obj) {
if (obj->is_internal())
return;
for (auto &c : this->clients_)
c->send_light_state(obj);
}
#endif #endif
#ifdef USE_SENSOR #ifdef USE_SENSOR
API_DISPATCH_UPDATE_IGNORE_PARAMS(sensor::Sensor, sensor, float state) 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);
}
#endif #endif
#ifdef USE_SWITCH #ifdef USE_SWITCH
API_DISPATCH_UPDATE_IGNORE_PARAMS(switch_::Switch, switch, bool state) 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);
}
#endif #endif
#ifdef USE_TEXT_SENSOR #ifdef USE_TEXT_SENSOR
API_DISPATCH_UPDATE_IGNORE_PARAMS(text_sensor::TextSensor, text_sensor, const std::string &state) 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);
}
#endif #endif
#ifdef USE_CLIMATE #ifdef USE_CLIMATE
API_DISPATCH_UPDATE(climate::Climate, climate) void APIServer::on_climate_update(climate::Climate *obj) {
if (obj->is_internal())
return;
for (auto &c : this->clients_)
c->send_climate_state(obj);
}
#endif #endif
#ifdef USE_NUMBER #ifdef USE_NUMBER
API_DISPATCH_UPDATE_IGNORE_PARAMS(number::Number, number, float state) 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);
}
#endif #endif
#ifdef USE_DATETIME_DATE #ifdef USE_DATETIME_DATE
API_DISPATCH_UPDATE(datetime::DateEntity, date) void APIServer::on_date_update(datetime::DateEntity *obj) {
if (obj->is_internal())
return;
for (auto &c : this->clients_)
c->send_date_state(obj);
}
#endif #endif
#ifdef USE_DATETIME_TIME #ifdef USE_DATETIME_TIME
API_DISPATCH_UPDATE(datetime::TimeEntity, time) void APIServer::on_time_update(datetime::TimeEntity *obj) {
if (obj->is_internal())
return;
for (auto &c : this->clients_)
c->send_time_state(obj);
}
#endif #endif
#ifdef USE_DATETIME_DATETIME #ifdef USE_DATETIME_DATETIME
API_DISPATCH_UPDATE(datetime::DateTimeEntity, datetime) void APIServer::on_datetime_update(datetime::DateTimeEntity *obj) {
if (obj->is_internal())
return;
for (auto &c : this->clients_)
c->send_datetime_state(obj);
}
#endif #endif
#ifdef USE_TEXT #ifdef USE_TEXT
API_DISPATCH_UPDATE_IGNORE_PARAMS(text::Text, text, const std::string &state) 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);
}
#endif #endif
#ifdef USE_SELECT #ifdef USE_SELECT
API_DISPATCH_UPDATE_IGNORE_PARAMS(select::Select, select, const std::string &state, size_t index) 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);
}
#endif #endif
#ifdef USE_LOCK #ifdef USE_LOCK
API_DISPATCH_UPDATE(lock::Lock, lock) void APIServer::on_lock_update(lock::Lock *obj) {
if (obj->is_internal())
return;
for (auto &c : this->clients_)
c->send_lock_state(obj);
}
#endif #endif
#ifdef USE_VALVE #ifdef USE_VALVE
API_DISPATCH_UPDATE(valve::Valve, valve) void APIServer::on_valve_update(valve::Valve *obj) {
if (obj->is_internal())
return;
for (auto &c : this->clients_)
c->send_valve_state(obj);
}
#endif #endif
#ifdef USE_MEDIA_PLAYER #ifdef USE_MEDIA_PLAYER
API_DISPATCH_UPDATE(media_player::MediaPlayer, 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);
}
#endif #endif
#ifdef USE_EVENT #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) { void APIServer::on_event(event::Event *obj, const std::string &event_type) {
if (obj->is_internal())
return;
for (auto &c : this->clients_) for (auto &c : this->clients_)
c->send_event(obj, event_type); c->send_event(obj, event_type);
} }
#endif #endif
#ifdef USE_UPDATE #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) { void APIServer::on_update(update::UpdateEntity *obj) {
if (obj->is_internal())
return;
for (auto &c : this->clients_) for (auto &c : this->clients_)
c->send_update_state(obj); c->send_update_state(obj);
} }
#endif #endif
#ifdef USE_ALARM_CONTROL_PANEL #ifdef USE_ALARM_CONTROL_PANEL
API_DISPATCH_UPDATE(alarm_control_panel::AlarmControlPanel, 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);
}
#endif #endif
float APIServer::get_setup_priority() const { return setup_priority::AFTER_WIFI; } float APIServer::get_setup_priority() const { return setup_priority::AFTER_WIFI; }
@@ -467,8 +533,7 @@ void APIServer::on_shutdown() {
if (!c->send_message(DisconnectRequest())) { if (!c->send_message(DisconnectRequest())) {
// If we can't send the disconnect request directly (tx_buffer full), // If we can't send the disconnect request directly (tx_buffer full),
// schedule it at the front of the batch so it will be sent with priority // 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, c->schedule_message_front_(nullptr, &APIConnection::try_send_disconnect_request, DisconnectRequest::MESSAGE_TYPE);
DisconnectRequest::ESTIMATED_SIZE);
} }
} }
} }

View File

@@ -12,9 +12,7 @@
#include "esphome/core/log.h" #include "esphome/core/log.h"
#include "list_entities.h" #include "list_entities.h"
#include "subscribe_state.h" #include "subscribe_state.h"
#ifdef USE_API_SERVICES
#include "user_services.h" #include "user_services.h"
#endif
#include <vector> #include <vector>
@@ -109,9 +107,18 @@ class APIServer : public Component, public Controller {
void on_media_player_update(media_player::MediaPlayer *obj) override; void on_media_player_update(media_player::MediaPlayer *obj) override;
#endif #endif
void send_homeassistant_service_call(const HomeassistantServiceResponse &call); void send_homeassistant_service_call(const HomeassistantServiceResponse &call);
#ifdef USE_API_SERVICES void register_user_service(UserServiceDescriptor *descriptor) {
void register_user_service(UserServiceDescriptor *descriptor) { this->user_services_.push_back(descriptor); } #ifdef USE_API_YAML_SERVICES
// Vector is pre-allocated when services are defined in YAML
this->user_services_.push_back(descriptor);
#else
// Lazy allocate vector on first use for CustomAPIDevice
if (!this->user_services_) {
this->user_services_ = std::make_unique<std::vector<UserServiceDescriptor *>>();
}
this->user_services_->push_back(descriptor);
#endif #endif
}
#ifdef USE_HOMEASSISTANT_TIME #ifdef USE_HOMEASSISTANT_TIME
void request_time(); void request_time();
#endif #endif
@@ -140,9 +147,14 @@ class APIServer : public Component, public Controller {
void get_home_assistant_state(std::string entity_id, optional<std::string> attribute, void get_home_assistant_state(std::string entity_id, optional<std::string> attribute,
std::function<void(std::string)> f); std::function<void(std::string)> f);
const std::vector<HomeAssistantStateSubscription> &get_state_subs() const; const std::vector<HomeAssistantStateSubscription> &get_state_subs() const;
#ifdef USE_API_SERVICES const std::vector<UserServiceDescriptor *> &get_user_services() const {
const std::vector<UserServiceDescriptor *> &get_user_services() const { return this->user_services_; } #ifdef USE_API_YAML_SERVICES
return this->user_services_;
#else
static const std::vector<UserServiceDescriptor *> EMPTY;
return this->user_services_ ? *this->user_services_ : EMPTY;
#endif #endif
}
#ifdef USE_API_CLIENT_CONNECTED_TRIGGER #ifdef USE_API_CLIENT_CONNECTED_TRIGGER
Trigger<std::string, std::string> *get_client_connected_trigger() const { return this->client_connected_trigger_; } Trigger<std::string, std::string> *get_client_connected_trigger() const { return this->client_connected_trigger_; }
@@ -174,8 +186,14 @@ class APIServer : public Component, public Controller {
#endif #endif
std::vector<uint8_t> shared_write_buffer_; // Shared proto write buffer for all connections std::vector<uint8_t> shared_write_buffer_; // Shared proto write buffer for all connections
std::vector<HomeAssistantStateSubscription> state_subs_; std::vector<HomeAssistantStateSubscription> state_subs_;
#ifdef USE_API_SERVICES #ifdef USE_API_YAML_SERVICES
// When services are defined in YAML, we know at compile time that services will be registered
std::vector<UserServiceDescriptor *> user_services_; std::vector<UserServiceDescriptor *> user_services_;
#else
// Services can still be registered at runtime by CustomAPIDevice components even when not
// defined in YAML. Using unique_ptr allows lazy allocation, saving 12 bytes in the common
// case where no services (YAML or custom) are used.
std::unique_ptr<std::vector<UserServiceDescriptor *>> user_services_;
#endif #endif
// Group smaller types together // Group smaller types together

View File

@@ -3,13 +3,10 @@
#include <map> #include <map>
#include "api_server.h" #include "api_server.h"
#ifdef USE_API #ifdef USE_API
#ifdef USE_API_SERVICES
#include "user_services.h" #include "user_services.h"
#endif
namespace esphome { namespace esphome {
namespace api { namespace api {
#ifdef USE_API_SERVICES
template<typename T, typename... Ts> class CustomAPIDeviceService : public UserServiceBase<Ts...> { template<typename T, typename... Ts> class CustomAPIDeviceService : public UserServiceBase<Ts...> {
public: public:
CustomAPIDeviceService(const std::string &name, const std::array<std::string, sizeof...(Ts)> &arg_names, T *obj, CustomAPIDeviceService(const std::string &name, const std::array<std::string, sizeof...(Ts)> &arg_names, T *obj,
@@ -22,7 +19,6 @@ template<typename T, typename... Ts> class CustomAPIDeviceService : public UserS
T *obj_; T *obj_;
void (T::*callback_)(Ts...); void (T::*callback_)(Ts...);
}; };
#endif // USE_API_SERVICES
class CustomAPIDevice { class CustomAPIDevice {
public: public:
@@ -50,14 +46,12 @@ class CustomAPIDevice {
* @param name The name of the service to register. * @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. * @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> template<typename T, typename... Ts>
void register_service(void (T::*callback)(Ts...), const std::string &name, void register_service(void (T::*callback)(Ts...), const std::string &name,
const std::array<std::string, sizeof...(Ts)> &arg_names) { const std::array<std::string, sizeof...(Ts)> &arg_names) {
auto *service = new CustomAPIDeviceService<T, Ts...>(name, arg_names, (T *) this, callback); // NOLINT auto *service = new CustomAPIDeviceService<T, Ts...>(name, arg_names, (T *) this, callback); // NOLINT
global_api_server->register_user_service(service); global_api_server->register_user_service(service);
} }
#endif
/** Register a custom native API service that will show up in Home Assistant. /** Register a custom native API service that will show up in Home Assistant.
* *
@@ -77,12 +71,10 @@ class CustomAPIDevice {
* @param callback The member function to call when the service is triggered. * @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. * @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) { template<typename T> void register_service(void (T::*callback)(), const std::string &name) {
auto *service = new CustomAPIDeviceService<T>(name, {}, (T *) this, callback); // NOLINT auto *service = new CustomAPIDeviceService<T>(name, {}, (T *) this, callback); // NOLINT
global_api_server->register_user_service(service); global_api_server->register_user_service(service);
} }
#endif
/** Subscribe to the state (or attribute state) of an entity from Home Assistant. /** Subscribe to the state (or attribute state) of an entity from Home Assistant.
* *

View File

@@ -40,8 +40,8 @@ LIST_ENTITIES_HANDLER(lock, lock::Lock, ListEntitiesLockResponse)
#ifdef USE_VALVE #ifdef USE_VALVE
LIST_ENTITIES_HANDLER(valve, valve::Valve, ListEntitiesValveResponse) LIST_ENTITIES_HANDLER(valve, valve::Valve, ListEntitiesValveResponse)
#endif #endif
#ifdef USE_CAMERA #ifdef USE_ESP32_CAMERA
LIST_ENTITIES_HANDLER(camera, camera::Camera, ListEntitiesCameraResponse) LIST_ENTITIES_HANDLER(camera, esp32_camera::ESP32Camera, ListEntitiesCameraResponse)
#endif #endif
#ifdef USE_CLIMATE #ifdef USE_CLIMATE
LIST_ENTITIES_HANDLER(climate, climate::Climate, ListEntitiesClimateResponse) LIST_ENTITIES_HANDLER(climate, climate::Climate, ListEntitiesClimateResponse)
@@ -83,12 +83,10 @@ bool ListEntitiesIterator::on_end() { return this->client_->send_list_info_done(
ListEntitiesIterator::ListEntitiesIterator(APIConnection *client) : client_(client) {} ListEntitiesIterator::ListEntitiesIterator(APIConnection *client) : client_(client) {}
#ifdef USE_API_SERVICES
bool ListEntitiesIterator::on_service(UserServiceDescriptor *service) { bool ListEntitiesIterator::on_service(UserServiceDescriptor *service) {
auto resp = service->encode_list_service_response(); auto resp = service->encode_list_service_response();
return this->client_->send_message(resp); return this->client_->send_message(resp);
} }
#endif
} // namespace api } // namespace api
} // namespace esphome } // namespace esphome

View File

@@ -14,7 +14,7 @@ class APIConnection;
#define LIST_ENTITIES_HANDLER(entity_type, EntityClass, ResponseType) \ #define LIST_ENTITIES_HANDLER(entity_type, EntityClass, ResponseType) \
bool ListEntitiesIterator::on_##entity_type(EntityClass *entity) { /* NOLINT(bugprone-macro-parentheses) */ \ bool ListEntitiesIterator::on_##entity_type(EntityClass *entity) { /* NOLINT(bugprone-macro-parentheses) */ \
return this->client_->schedule_message_(entity, &APIConnection::try_send_##entity_type##_info, \ return this->client_->schedule_message_(entity, &APIConnection::try_send_##entity_type##_info, \
ResponseType::MESSAGE_TYPE, ResponseType::ESTIMATED_SIZE); \ ResponseType::MESSAGE_TYPE); \
} }
class ListEntitiesIterator : public ComponentIterator { class ListEntitiesIterator : public ComponentIterator {
@@ -44,11 +44,9 @@ class ListEntitiesIterator : public ComponentIterator {
#ifdef USE_TEXT_SENSOR #ifdef USE_TEXT_SENSOR
bool on_text_sensor(text_sensor::TextSensor *entity) override; bool on_text_sensor(text_sensor::TextSensor *entity) override;
#endif #endif
#ifdef USE_API_SERVICES
bool on_service(UserServiceDescriptor *service) override; bool on_service(UserServiceDescriptor *service) override;
#endif #ifdef USE_ESP32_CAMERA
#ifdef USE_CAMERA bool on_camera(esp32_camera::ESP32Camera *entity) override;
bool on_camera(camera::Camera *entity) override;
#endif #endif
#ifdef USE_CLIMATE #ifdef USE_CLIMATE
bool on_climate(climate::Climate *entity) override; bool on_climate(climate::Climate *entity) override;

View File

@@ -4,7 +4,6 @@
#include "esphome/core/helpers.h" #include "esphome/core/helpers.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
#include <cassert>
#include <vector> #include <vector>
#ifdef ESPHOME_LOG_HAS_VERY_VERBOSE #ifdef ESPHOME_LOG_HAS_VERY_VERBOSE
@@ -60,6 +59,7 @@ class ProtoVarInt {
uint32_t as_uint32() const { return this->value_; } uint32_t as_uint32() const { return this->value_; }
uint64_t as_uint64() const { return this->value_; } uint64_t as_uint64() const { return this->value_; }
bool as_bool() const { return this->value_; } bool as_bool() const { return this->value_; }
template<typename T> T as_enum() const { return static_cast<T>(this->as_uint32()); }
int32_t as_int32() const { int32_t as_int32() const {
// Not ZigZag encoded // Not ZigZag encoded
return static_cast<int32_t>(this->as_int64()); return static_cast<int32_t>(this->as_int64());
@@ -133,24 +133,15 @@ class ProtoVarInt {
uint64_t value_; uint64_t value_;
}; };
// Forward declaration for decode_to_message and encode_to_writer
class ProtoMessage;
class ProtoLengthDelimited { class ProtoLengthDelimited {
public: public:
explicit ProtoLengthDelimited(const uint8_t *value, size_t length) : value_(value), length_(length) {} explicit ProtoLengthDelimited(const uint8_t *value, size_t length) : value_(value), length_(length) {}
std::string as_string() const { return std::string(reinterpret_cast<const char *>(this->value_), this->length_); } std::string as_string() const { return std::string(reinterpret_cast<const char *>(this->value_), this->length_); }
template<class C> C as_message() const {
/** auto msg = C();
* Decode the length-delimited data into an existing ProtoMessage instance. msg.decode(this->value_, this->length_);
* return msg;
* This method allows decoding without templates, enabling use in contexts }
* where the message type is not known at compile time. The ProtoMessage's
* decode() method will be called with the raw data and length.
*
* @param msg The ProtoMessage instance to decode into
*/
void decode_to_message(ProtoMessage &msg) const;
protected: protected:
const uint8_t *const value_; const uint8_t *const value_;
@@ -272,6 +263,9 @@ class ProtoWriteBuffer {
this->write((value >> 48) & 0xFF); this->write((value >> 48) & 0xFF);
this->write((value >> 56) & 0xFF); this->write((value >> 56) & 0xFF);
} }
template<typename T> void encode_enum(uint32_t field_id, T value, bool force = false) {
this->encode_uint32(field_id, static_cast<uint32_t>(value), force);
}
void encode_float(uint32_t field_id, float value, bool force = false) { void encode_float(uint32_t field_id, float value, bool force = false) {
if (value == 0.0f && !force) if (value == 0.0f && !force)
return; return;
@@ -312,7 +306,18 @@ class ProtoWriteBuffer {
} }
this->encode_uint64(field_id, uvalue, force); this->encode_uint64(field_id, uvalue, force);
} }
void encode_message(uint32_t field_id, const ProtoMessage &value, bool force = false); template<class C> void encode_message(uint32_t field_id, const C &value, bool force = false) {
this->encode_field_raw(field_id, 2); // type 2: Length-delimited message
size_t begin = this->buffer_->size();
value.encode(*this);
const uint32_t nested_length = this->buffer_->size() - begin;
// add size varint
std::vector<uint8_t> var;
ProtoVarInt(nested_length).encode(var);
this->buffer_->insert(this->buffer_->begin() + begin, var.begin(), var.end());
}
std::vector<uint8_t> *get_buffer() const { return buffer_; } std::vector<uint8_t> *get_buffer() const { return buffer_; }
protected: protected:
@@ -340,494 +345,6 @@ class ProtoMessage {
virtual bool decode_64bit(uint32_t field_id, Proto64Bit value) { return false; } virtual bool decode_64bit(uint32_t field_id, Proto64Bit value) { return false; }
}; };
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) {
// Skip calculation if value is zero
if (value == 0) {
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 an int32 field to the total message size (repeated field version)
*/
static inline void add_int32_field_repeated(uint32_t &total_size, uint32_t field_id_size, int32_t value) {
// Always calculate size for repeated fields
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) {
// Skip calculation if value is zero
if (value == 0) {
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 uint32 field to the total message size (repeated field version)
*/
static inline void add_uint32_field_repeated(uint32_t &total_size, uint32_t field_id_size, uint32_t value) {
// Always calculate size for repeated fields
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) {
// Skip calculation if value is false
if (!value) {
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 boolean field to the total message size (repeated field version)
*/
static inline void add_bool_field_repeated(uint32_t &total_size, uint32_t field_id_size, bool value) {
// Always calculate size for repeated fields
// Boolean fields always use 1 byte
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) {
// Skip calculation if value is zero
if (!is_nonzero) {
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) {
// Skip calculation if value is zero
if (value == 0) {
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 an enum field to the total message size (repeated field version)
*
* Enum fields are encoded as uint32 varints.
*/
static inline void add_enum_field_repeated(uint32_t &total_size, uint32_t field_id_size, uint32_t value) {
// Always calculate size for repeated fields
// 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) {
// Skip calculation if value is zero
if (value == 0) {
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 a sint32 field to the total message size (repeated field version)
*
* Sint32 fields use ZigZag encoding, which is more efficient for negative values.
*/
static inline void add_sint32_field_repeated(uint32_t &total_size, uint32_t field_id_size, int32_t value) {
// Always calculate size for repeated fields
// 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) {
// Skip calculation if value is zero
if (value == 0) {
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 an int64 field to the total message size (repeated field version)
*/
static inline void add_int64_field_repeated(uint32_t &total_size, uint32_t field_id_size, int64_t value) {
// Always calculate size for repeated fields
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) {
// Skip calculation if value is zero
if (value == 0) {
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 (repeated field version)
*/
static inline void add_uint64_field_repeated(uint32_t &total_size, uint32_t field_id_size, uint64_t value) {
// Always calculate size for repeated fields
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) {
// Skip calculation if value is zero
if (value == 0) {
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 sint64 field to the total message size (repeated field version)
*
* Sint64 fields use ZigZag encoding, which is more efficient for negative values.
*/
static inline void add_sint64_field_repeated(uint32_t &total_size, uint32_t field_id_size, int64_t value) {
// Always calculate size for repeated fields
// 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) {
// Skip calculation if string is empty
if (str.empty()) {
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 string/bytes field to the total message size (repeated field version)
*/
static inline void add_string_field_repeated(uint32_t &total_size, uint32_t field_id_size, const std::string &str) {
// Always calculate size for repeated fields
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.
*
* @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) {
// Skip calculation if nested message is empty
if (nested_size == 0) {
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 (repeated field version)
*
* @param nested_size The pre-calculated size of the nested message
*/
static inline void add_message_field_repeated(uint32_t &total_size, uint32_t field_id_size, uint32_t nested_size) {
// Always calculate size for repeated fields
// 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 version takes a ProtoMessage object, calculates its size internally,
* and updates the total_size reference. This eliminates the need for a temporary variable
* at the call site.
*
* @param message The nested message object
*/
static inline void add_message_object(uint32_t &total_size, uint32_t field_id_size, const ProtoMessage &message) {
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);
}
/**
* @brief Calculates and adds the size of a nested message field to the total message size (repeated field version)
*
* @param message The nested message object
*/
static inline void add_message_object_repeated(uint32_t &total_size, uint32_t field_id_size,
const ProtoMessage &message) {
uint32_t nested_size = 0;
message.calculate_size(nested_size);
// Use the base implementation with the calculated nested_size
add_message_field_repeated(total_size, field_id_size, nested_size);
}
/**
* @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;
}
// Use the repeated field version for all messages
for (const auto &message : messages) {
add_message_object_repeated(total_size, field_id_size, message);
}
}
};
// Implementation of encode_message - must be after ProtoMessage is defined
inline void ProtoWriteBuffer::encode_message(uint32_t field_id, const ProtoMessage &value, bool force) {
this->encode_field_raw(field_id, 2); // type 2: Length-delimited message
// Calculate the message size first
uint32_t msg_length_bytes = 0;
value.calculate_size(msg_length_bytes);
// Calculate how many bytes the length varint needs
uint32_t varint_length_bytes = ProtoSize::varint(msg_length_bytes);
// Reserve exact space for the length varint
size_t begin = this->buffer_->size();
this->buffer_->resize(this->buffer_->size() + varint_length_bytes);
// Write the length varint directly
ProtoVarInt(msg_length_bytes).encode_to_buffer_unchecked(this->buffer_->data() + begin, varint_length_bytes);
// Now encode the message content - it will append to the buffer
value.encode(*this);
// Verify that the encoded size matches what we calculated
assert(this->buffer_->size() == begin + varint_length_bytes + msg_length_bytes);
}
// Implementation of decode_to_message - must be after ProtoMessage is defined
inline void ProtoLengthDelimited::decode_to_message(ProtoMessage &msg) const {
msg.decode(this->value_, this->length_);
}
template<typename T> const char *proto_enum_to_string(T value); template<typename T> const char *proto_enum_to_string(T value);
class ProtoService { class ProtoService {
@@ -846,11 +363,11 @@ class ProtoService {
* @return A ProtoWriteBuffer object with the reserved size. * @return A ProtoWriteBuffer object with the reserved size.
*/ */
virtual ProtoWriteBuffer create_buffer(uint32_t reserve_size) = 0; virtual ProtoWriteBuffer create_buffer(uint32_t reserve_size) = 0;
virtual bool send_buffer(ProtoWriteBuffer buffer, uint8_t message_type) = 0; virtual bool send_buffer(ProtoWriteBuffer buffer, uint16_t message_type) = 0;
virtual void read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) = 0; virtual void read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) = 0;
// Optimized method that pre-allocates buffer based on message size // Optimized method that pre-allocates buffer based on message size
bool send_message_(const ProtoMessage &msg, uint8_t message_type) { bool send_message_(const ProtoMessage &msg, uint16_t message_type) {
uint32_t msg_size = 0; uint32_t msg_size = 0;
msg.calculate_size(msg_size); msg.calculate_size(msg_size);

View File

@@ -7,7 +7,6 @@
#include "esphome/core/automation.h" #include "esphome/core/automation.h"
#include "api_pb2.h" #include "api_pb2.h"
#ifdef USE_API_SERVICES
namespace esphome { namespace esphome {
namespace api { namespace api {
@@ -74,4 +73,3 @@ template<typename... Ts> class UserServiceTrigger : public UserServiceBase<Ts...
} // namespace api } // namespace api
} // namespace esphome } // namespace esphome
#endif // USE_API_SERVICES

View File

@@ -3,6 +3,8 @@
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/components/as3935/as3935.h" #include "esphome/components/as3935/as3935.h"
#include "esphome/components/spi/spi.h" #include "esphome/components/spi/spi.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/binary_sensor/binary_sensor.h"
namespace esphome { namespace esphome {
namespace as3935_spi { namespace as3935_spi {

View File

@@ -31,7 +31,7 @@ CONFIG_SCHEMA = cv.All(
async def to_code(config): async def to_code(config):
if CORE.is_esp32 or CORE.is_libretiny: if CORE.is_esp32 or CORE.is_libretiny:
# https://github.com/ESP32Async/AsyncTCP # https://github.com/ESP32Async/AsyncTCP
cg.add_library("ESP32Async/AsyncTCP", "3.4.5") cg.add_library("ESP32Async/AsyncTCP", "3.4.4")
elif CORE.is_esp8266: elif CORE.is_esp8266:
# https://github.com/ESP32Async/ESPAsyncTCP # https://github.com/ESP32Async/ESPAsyncTCP
cg.add_library("ESP32Async/ESPAsyncTCP", "2.0.0") cg.add_library("ESP32Async/ESPAsyncTCP", "2.0.0")

View File

@@ -52,21 +52,11 @@ bool BluetoothProxy::parse_device(const esp32_ble_tracker::ESPBTDevice &device)
return true; return true;
} }
// Batch size for BLE advertisements to maximize WiFi efficiency static constexpr size_t FLUSH_BATCH_SIZE = 8;
// Each advertisement is up to 80 bytes when packaged (including protocol overhead) static std::vector<api::BluetoothLERawAdvertisement> &get_batch_buffer() {
// Most advertisements are 20-30 bytes, allowing even more to fit per packet static std::vector<api::BluetoothLERawAdvertisement> batch_buffer;
// 16 advertisements × 80 bytes (worst case) = 1280 bytes out of ~1320 bytes usable payload return batch_buffer;
// This achieves ~97% WiFi MTU utilization while staying under the limit }
static constexpr size_t FLUSH_BATCH_SIZE = 16;
namespace {
// Batch buffer in anonymous namespace to avoid guard variable (saves 8 bytes)
// This is initialized at program startup before any threads
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
std::vector<api::BluetoothLERawAdvertisement> batch_buffer;
} // namespace
static std::vector<api::BluetoothLERawAdvertisement> &get_batch_buffer() { return batch_buffer; }
bool BluetoothProxy::parse_devices(const esp32_ble::BLEScanResult *scan_results, size_t count) { bool BluetoothProxy::parse_devices(const esp32_ble::BLEScanResult *scan_results, size_t count) {
if (!api::global_api_server->is_connected() || this->api_connection_ == nullptr || !this->raw_advertisements_) if (!api::global_api_server->is_connected() || this->api_connection_ == nullptr || !this->raw_advertisements_)
@@ -180,7 +170,7 @@ int BluetoothProxy::get_bluetooth_connections_free() {
void BluetoothProxy::loop() { void BluetoothProxy::loop() {
if (!api::global_api_server->is_connected() || this->api_connection_ == nullptr) { if (!api::global_api_server->is_connected() || this->api_connection_ == nullptr) {
for (auto *connection : this->connections_) { for (auto *connection : this->connections_) {
if (connection->get_address() != 0 && !connection->disconnect_pending()) { if (connection->get_address() != 0) {
connection->disconnect(); connection->disconnect();
} }
} }

View File

@@ -1 +0,0 @@
CODEOWNERS = ["@DT-art1", "@bdraco"]

View File

@@ -1,22 +0,0 @@
#include "camera.h"
namespace esphome {
namespace camera {
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
Camera *Camera::global_camera = nullptr;
Camera::Camera() {
if (global_camera != nullptr) {
this->status_set_error("Multiple cameras are configured, but only one is supported.");
this->mark_failed();
return;
}
global_camera = this;
}
Camera *Camera::instance() { return global_camera; }
} // namespace camera
} // namespace esphome

View File

@@ -1,80 +0,0 @@
#pragma once
#include "esphome/core/automation.h"
#include "esphome/core/component.h"
#include "esphome/core/entity_base.h"
#include "esphome/core/helpers.h"
namespace esphome {
namespace camera {
/** Different sources for filtering.
* IDLE: Camera requests to send an image to the API.
* API_REQUESTER: API requests a new image.
* WEB_REQUESTER: ESP32 web server request an image. Ignored by API.
*/
enum CameraRequester : uint8_t { IDLE, API_REQUESTER, WEB_REQUESTER };
/** Abstract camera image base class.
* Encapsulates the JPEG encoded data and it is shared among
* all connected clients.
*/
class CameraImage {
public:
virtual uint8_t *get_data_buffer() = 0;
virtual size_t get_data_length() = 0;
virtual bool was_requested_by(CameraRequester requester) const = 0;
virtual ~CameraImage() {}
};
/** Abstract image reader base class.
* Keeps track of the data offset of the camera image and
* how many bytes are remaining to read. When the image
* is returned, the shared_ptr is reset and the camera can
* reuse the memory of the camera image.
*/
class CameraImageReader {
public:
virtual void set_image(std::shared_ptr<CameraImage> image) = 0;
virtual size_t available() const = 0;
virtual uint8_t *peek_data_buffer() = 0;
virtual void consume_data(size_t consumed) = 0;
virtual void return_image() = 0;
virtual ~CameraImageReader() {}
};
/** Abstract camera base class. Collaborates with API.
* 1) API server starts and installs callback (add_image_callback)
* which is called by the camera when a new image is available.
* 2) New API client connects and creates a new image reader (create_image_reader).
* 3) API connection receives protobuf CameraImageRequest and calls request_image.
* 3.a) API connection receives protobuf CameraImageRequest and calls start_stream.
* 4) Camera implementation provides JPEG data in the CameraImage and calls callback.
* 5) API connection sets the image in the image reader.
* 6) API connection consumes data from the image reader and returns the image when finished.
* 7.a) Camera captures a new image and continues with 4) until start_stream is called.
*/
class Camera : public EntityBase, public Component {
public:
Camera();
// Camera implementation invokes callback to publish a new image.
virtual void add_image_callback(std::function<void(std::shared_ptr<CameraImage>)> &&callback) = 0;
/// Returns a new camera image reader that keeps track of the JPEG data in the camera image.
virtual CameraImageReader *create_image_reader() = 0;
// Connection, camera or web server requests one new JPEG image.
virtual void request_image(CameraRequester requester) = 0;
// Connection, camera or web server requests a stream of images.
virtual void start_stream(CameraRequester requester) = 0;
// Connection or web server stops the previously started stream.
virtual void stop_stream(CameraRequester requester) = 0;
virtual ~Camera() {}
/// The singleton instance of the camera implementation.
static Camera *instance();
protected:
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
static Camera *global_camera;
};
} // namespace camera
} // namespace esphome

View File

@@ -2,7 +2,6 @@
CODEOWNERS = ["@esphome/core"] CODEOWNERS = ["@esphome/core"]
CONF_BYTE_ORDER = "byte_order"
CONF_DRAW_ROUNDING = "draw_rounding" CONF_DRAW_ROUNDING = "draw_rounding"
CONF_ON_STATE_CHANGE = "on_state_change" CONF_ON_STATE_CHANGE = "on_state_change"
CONF_REQUEST_HEADERS = "request_headers" CONF_REQUEST_HEADERS = "request_headers"

View File

@@ -1,5 +1,4 @@
import esphome.codegen as cg import esphome.codegen as cg
from esphome.config_helpers import filter_source_files_from_platform
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.const import ( from esphome.const import (
CONF_BLOCK, CONF_BLOCK,
@@ -8,7 +7,6 @@ from esphome.const import (
CONF_FREE, CONF_FREE,
CONF_ID, CONF_ID,
CONF_LOOP_TIME, CONF_LOOP_TIME,
PlatformFramework,
) )
CODEOWNERS = ["@OttoWinter"] CODEOWNERS = ["@OttoWinter"]
@@ -46,21 +44,3 @@ CONFIG_SCHEMA = cv.All(
async def to_code(config): async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID]) var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config) await cg.register_component(var, config)
FILTER_SOURCE_FILES = filter_source_files_from_platform(
{
"debug_esp32.cpp": {
PlatformFramework.ESP32_ARDUINO,
PlatformFramework.ESP32_IDF,
},
"debug_esp8266.cpp": {PlatformFramework.ESP8266_ARDUINO},
"debug_host.cpp": {PlatformFramework.HOST_NATIVE},
"debug_rp2040.cpp": {PlatformFramework.RP2040_ARDUINO},
"debug_libretiny.cpp": {
PlatformFramework.BK72XX_ARDUINO,
PlatformFramework.RTL87XX_ARDUINO,
PlatformFramework.LN882X_ARDUINO,
},
}
)

View File

@@ -53,7 +53,6 @@ void DebugComponent::on_shutdown() {
auto pref = global_preferences->make_preference(REBOOT_MAX_LEN, fnv1_hash(REBOOT_KEY + App.get_name())); auto pref = global_preferences->make_preference(REBOOT_MAX_LEN, fnv1_hash(REBOOT_KEY + App.get_name()));
if (component != nullptr) { if (component != nullptr) {
strncpy(buffer, component->get_component_source(), REBOOT_MAX_LEN - 1); strncpy(buffer, component->get_component_source(), REBOOT_MAX_LEN - 1);
buffer[REBOOT_MAX_LEN - 1] = '\0';
} }
ESP_LOGD(TAG, "Storing reboot source: %s", buffer); ESP_LOGD(TAG, "Storing reboot source: %s", buffer);
pref.save(&buffer); pref.save(&buffer);
@@ -69,7 +68,6 @@ std::string DebugComponent::get_reset_reason_() {
auto pref = global_preferences->make_preference(REBOOT_MAX_LEN, fnv1_hash(REBOOT_KEY + App.get_name())); auto pref = global_preferences->make_preference(REBOOT_MAX_LEN, fnv1_hash(REBOOT_KEY + App.get_name()));
char buffer[REBOOT_MAX_LEN]{}; char buffer[REBOOT_MAX_LEN]{};
if (pref.load(&buffer)) { if (pref.load(&buffer)) {
buffer[REBOOT_MAX_LEN - 1] = '\0';
reset_reason = "Reboot request from " + std::string(buffer); reset_reason = "Reboot request from " + std::string(buffer);
} }
} }

View File

@@ -1,6 +1,6 @@
from esphome import automation, pins from esphome import automation, pins
import esphome.codegen as cg import esphome.codegen as cg
from esphome.components import esp32, time from esphome.components import time
from esphome.components.esp32 import get_esp32_variant from esphome.components.esp32 import get_esp32_variant
from esphome.components.esp32.const import ( from esphome.components.esp32.const import (
VARIANT_ESP32, VARIANT_ESP32,
@@ -11,7 +11,6 @@ from esphome.components.esp32.const import (
VARIANT_ESP32S2, VARIANT_ESP32S2,
VARIANT_ESP32S3, VARIANT_ESP32S3,
) )
from esphome.config_helpers import filter_source_files_from_platform
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.const import ( from esphome.const import (
CONF_DEFAULT, CONF_DEFAULT,
@@ -28,7 +27,6 @@ from esphome.const import (
CONF_WAKEUP_PIN, CONF_WAKEUP_PIN,
PLATFORM_ESP32, PLATFORM_ESP32,
PLATFORM_ESP8266, PLATFORM_ESP8266,
PlatformFramework,
) )
WAKEUP_PINS = { WAKEUP_PINS = {
@@ -116,20 +114,12 @@ def validate_pin_number(value):
return value return value
def _validate_ex1_wakeup_mode(value): def validate_config(config):
if value == "ALL_LOW": if get_esp32_variant() == VARIANT_ESP32C3 and CONF_ESP32_EXT1_WAKEUP in config:
esp32.only_on_variant(supported=[VARIANT_ESP32], msg_prefix="ALL_LOW")(value) raise cv.Invalid("ESP32-C3 does not support wakeup from touch.")
if value == "ANY_LOW": if get_esp32_variant() == VARIANT_ESP32C3 and CONF_TOUCH_WAKEUP in config:
esp32.only_on_variant( raise cv.Invalid("ESP32-C3 does not support wakeup from ext1")
supported=[ return config
VARIANT_ESP32S2,
VARIANT_ESP32S3,
VARIANT_ESP32C6,
VARIANT_ESP32H2,
],
msg_prefix="ANY_LOW",
)(value)
return value
deep_sleep_ns = cg.esphome_ns.namespace("deep_sleep") deep_sleep_ns = cg.esphome_ns.namespace("deep_sleep")
@@ -156,7 +146,6 @@ WAKEUP_PIN_MODES = {
esp_sleep_ext1_wakeup_mode_t = cg.global_ns.enum("esp_sleep_ext1_wakeup_mode_t") esp_sleep_ext1_wakeup_mode_t = cg.global_ns.enum("esp_sleep_ext1_wakeup_mode_t")
Ext1Wakeup = deep_sleep_ns.struct("Ext1Wakeup") Ext1Wakeup = deep_sleep_ns.struct("Ext1Wakeup")
EXT1_WAKEUP_MODES = { EXT1_WAKEUP_MODES = {
"ANY_LOW": esp_sleep_ext1_wakeup_mode_t.ESP_EXT1_WAKEUP_ANY_LOW,
"ALL_LOW": esp_sleep_ext1_wakeup_mode_t.ESP_EXT1_WAKEUP_ALL_LOW, "ALL_LOW": esp_sleep_ext1_wakeup_mode_t.ESP_EXT1_WAKEUP_ALL_LOW,
"ANY_HIGH": esp_sleep_ext1_wakeup_mode_t.ESP_EXT1_WAKEUP_ANY_HIGH, "ANY_HIGH": esp_sleep_ext1_wakeup_mode_t.ESP_EXT1_WAKEUP_ANY_HIGH,
} }
@@ -196,28 +185,16 @@ CONFIG_SCHEMA = cv.All(
), ),
cv.Optional(CONF_ESP32_EXT1_WAKEUP): cv.All( cv.Optional(CONF_ESP32_EXT1_WAKEUP): cv.All(
cv.only_on_esp32, cv.only_on_esp32,
esp32.only_on_variant(
unsupported=[VARIANT_ESP32C3], msg_prefix="Wakeup from ext1"
),
cv.Schema( cv.Schema(
{ {
cv.Required(CONF_PINS): cv.ensure_list( cv.Required(CONF_PINS): cv.ensure_list(
pins.internal_gpio_input_pin_schema, validate_pin_number pins.internal_gpio_input_pin_schema, validate_pin_number
), ),
cv.Required(CONF_MODE): cv.All( cv.Required(CONF_MODE): cv.enum(EXT1_WAKEUP_MODES, upper=True),
cv.enum(EXT1_WAKEUP_MODES, upper=True),
_validate_ex1_wakeup_mode,
),
} }
), ),
), ),
cv.Optional(CONF_TOUCH_WAKEUP): cv.All( cv.Optional(CONF_TOUCH_WAKEUP): cv.All(cv.only_on_esp32, cv.boolean),
cv.only_on_esp32,
esp32.only_on_variant(
unsupported=[VARIANT_ESP32C3], msg_prefix="Wakeup from touch"
),
cv.boolean,
),
} }
).extend(cv.COMPONENT_SCHEMA), ).extend(cv.COMPONENT_SCHEMA),
cv.only_on([PLATFORM_ESP32, PLATFORM_ESP8266]), cv.only_on([PLATFORM_ESP32, PLATFORM_ESP8266]),
@@ -336,14 +313,3 @@ async def deep_sleep_action_to_code(config, action_id, template_arg, args):
var = cg.new_Pvariable(action_id, template_arg) var = cg.new_Pvariable(action_id, template_arg)
await cg.register_parented(var, config[CONF_ID]) await cg.register_parented(var, config[CONF_ID])
return var return var
FILTER_SOURCE_FILES = filter_source_files_from_platform(
{
"deep_sleep_esp32.cpp": {
PlatformFramework.ESP32_ARDUINO,
PlatformFramework.ESP32_IDF,
},
"deep_sleep_esp8266.cpp": {PlatformFramework.ESP8266_ARDUINO},
}
)

View File

@@ -189,7 +189,7 @@ def get_download_types(storage_json):
] ]
def only_on_variant(*, supported=None, unsupported=None, msg_prefix="This feature"): def only_on_variant(*, supported=None, unsupported=None):
"""Config validator for features only available on some ESP32 variants.""" """Config validator for features only available on some ESP32 variants."""
if supported is not None and not isinstance(supported, list): if supported is not None and not isinstance(supported, list):
supported = [supported] supported = [supported]
@@ -200,11 +200,11 @@ def only_on_variant(*, supported=None, unsupported=None, msg_prefix="This featur
variant = get_esp32_variant() variant = get_esp32_variant()
if supported is not None and variant not in supported: if supported is not None and variant not in supported:
raise cv.Invalid( raise cv.Invalid(
f"{msg_prefix} is only available on {', '.join(supported)}" f"This feature is only available on {', '.join(supported)}"
) )
if unsupported is not None and variant in unsupported: if unsupported is not None and variant in unsupported:
raise cv.Invalid( raise cv.Invalid(
f"{msg_prefix} is not available on {', '.join(unsupported)}" f"This feature is not available on {', '.join(unsupported)}"
) )
return obj return obj
@@ -707,7 +707,6 @@ async def to_code(config):
cg.add_define("ESPHOME_VARIANT", VARIANT_FRIENDLY[config[CONF_VARIANT]]) cg.add_define("ESPHOME_VARIANT", VARIANT_FRIENDLY[config[CONF_VARIANT]])
cg.add_platformio_option("lib_ldf_mode", "off") cg.add_platformio_option("lib_ldf_mode", "off")
cg.add_platformio_option("lib_compat_mode", "strict")
framework_ver: cv.Version = CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] framework_ver: cv.Version = CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION]

View File

@@ -1,69 +0,0 @@
#include "esphome/core/helpers.h"
#ifdef USE_ESP32
#include "esp_efuse.h"
#include "esp_efuse_table.h"
#include "esp_mac.h"
#include <freertos/FreeRTOS.h>
#include <freertos/portmacro.h>
#include "esp_random.h"
#include "esp_system.h"
namespace esphome {
uint32_t random_uint32() { return esp_random(); }
bool random_bytes(uint8_t *data, size_t len) {
esp_fill_random(data, len);
return true;
}
Mutex::Mutex() { handle_ = xSemaphoreCreateMutex(); }
Mutex::~Mutex() {}
void Mutex::lock() { xSemaphoreTake(this->handle_, portMAX_DELAY); }
bool Mutex::try_lock() { return xSemaphoreTake(this->handle_, 0) == pdTRUE; }
void Mutex::unlock() { xSemaphoreGive(this->handle_); }
// only affects the executing core
// so should not be used as a mutex lock, only to get accurate timing
IRAM_ATTR InterruptLock::InterruptLock() { portDISABLE_INTERRUPTS(); }
IRAM_ATTR InterruptLock::~InterruptLock() { portENABLE_INTERRUPTS(); }
void get_mac_address_raw(uint8_t *mac) { // NOLINT(readability-non-const-parameter)
#if defined(CONFIG_SOC_IEEE802154_SUPPORTED)
// When CONFIG_SOC_IEEE802154_SUPPORTED is defined, esp_efuse_mac_get_default
// returns the 802.15.4 EUI-64 address, so we read directly from eFuse instead.
if (has_custom_mac_address()) {
esp_efuse_read_field_blob(ESP_EFUSE_MAC_CUSTOM, mac, 48);
} else {
esp_efuse_read_field_blob(ESP_EFUSE_MAC_FACTORY, mac, 48);
}
#else
if (has_custom_mac_address()) {
esp_efuse_mac_get_custom(mac);
} else {
esp_efuse_mac_get_default(mac);
}
#endif
}
void set_mac_address(uint8_t *mac) { esp_base_mac_addr_set(mac); }
bool has_custom_mac_address() {
#if !defined(USE_ESP32_IGNORE_EFUSE_CUSTOM_MAC)
uint8_t mac[6];
// do not use 'esp_efuse_mac_get_custom(mac)' because it drops an error in the logs whenever it fails
#ifndef USE_ESP32_VARIANT_ESP32
return (esp_efuse_read_field_blob(ESP_EFUSE_USER_DATA_MAC_CUSTOM, mac, 48) == ESP_OK) && mac_address_is_valid(mac);
#else
return (esp_efuse_read_field_blob(ESP_EFUSE_MAC_CUSTOM, mac, 48) == ESP_OK) && mac_address_is_valid(mac);
#endif
#else
return false;
#endif
}
} // namespace esphome
#endif // USE_ESP32

View File

@@ -25,15 +25,10 @@ namespace esphome {
namespace esp32_ble { namespace esp32_ble {
// Maximum number of BLE scan results to buffer // Maximum number of BLE scan results to buffer
// Sized to handle bursts of advertisements while allowing for processing delays
// With 16 advertisements per batch and some safety margin:
// - Without PSRAM: 24 entries (1.5× batch size)
// - With PSRAM: 36 entries (2.25× batch size)
// The reduced structure size (~80 bytes vs ~400 bytes) allows for larger buffers
#ifdef USE_PSRAM #ifdef USE_PSRAM
static constexpr uint8_t SCAN_RESULT_BUFFER_SIZE = 36; static constexpr uint8_t SCAN_RESULT_BUFFER_SIZE = 32;
#else #else
static constexpr uint8_t SCAN_RESULT_BUFFER_SIZE = 24; static constexpr uint8_t SCAN_RESULT_BUFFER_SIZE = 20;
#endif #endif
// Maximum size of the BLE event queue - must be power of 2 for lock-free queue // Maximum size of the BLE event queue - must be power of 2 for lock-free queue
@@ -56,7 +51,7 @@ enum IoCapability {
IO_CAP_KBDISP = ESP_IO_CAP_KBDISP, IO_CAP_KBDISP = ESP_IO_CAP_KBDISP,
}; };
enum BLEComponentState : uint8_t { enum BLEComponentState {
/** Nothing has been initialized yet. */ /** Nothing has been initialized yet. */
BLE_COMPONENT_STATE_OFF = 0, BLE_COMPONENT_STATE_OFF = 0,
/** BLE should be disabled on next loop. */ /** BLE should be disabled on next loop. */
@@ -146,31 +141,21 @@ class ESP32BLE : public Component {
private: private:
template<typename... Args> friend void enqueue_ble_event(Args... args); template<typename... Args> friend void enqueue_ble_event(Args... args);
// Vectors (12 bytes each on 32-bit, naturally aligned to 4 bytes)
std::vector<GAPEventHandler *> gap_event_handlers_; std::vector<GAPEventHandler *> gap_event_handlers_;
std::vector<GAPScanEventHandler *> gap_scan_event_handlers_; std::vector<GAPScanEventHandler *> gap_scan_event_handlers_;
std::vector<GATTcEventHandler *> gattc_event_handlers_; std::vector<GATTcEventHandler *> gattc_event_handlers_;
std::vector<GATTsEventHandler *> gatts_event_handlers_; std::vector<GATTsEventHandler *> gatts_event_handlers_;
std::vector<BLEStatusEventHandler *> ble_status_event_handlers_; std::vector<BLEStatusEventHandler *> ble_status_event_handlers_;
BLEComponentState state_{BLE_COMPONENT_STATE_OFF};
// Large objects (size depends on template parameters, but typically aligned to 4 bytes)
esphome::LockFreeQueue<BLEEvent, MAX_BLE_QUEUE_SIZE> ble_events_; esphome::LockFreeQueue<BLEEvent, MAX_BLE_QUEUE_SIZE> ble_events_;
esphome::EventPool<BLEEvent, MAX_BLE_QUEUE_SIZE> ble_event_pool_; esphome::EventPool<BLEEvent, MAX_BLE_QUEUE_SIZE> ble_event_pool_;
BLEAdvertising *advertising_{};
// optional<string> (typically 16+ bytes on 32-bit, aligned to 4 bytes) esp_ble_io_cap_t io_cap_{ESP_IO_CAP_NONE};
uint32_t advertising_cycle_time_{};
bool enable_on_boot_{};
optional<std::string> name_; optional<std::string> name_;
uint16_t appearance_{0};
// 4-byte aligned members
BLEAdvertising *advertising_{}; // 4 bytes (pointer)
esp_ble_io_cap_t io_cap_{ESP_IO_CAP_NONE}; // 4 bytes (enum)
uint32_t advertising_cycle_time_{}; // 4 bytes
// 2-byte aligned members
uint16_t appearance_{0}; // 2 bytes
// 1-byte aligned members (grouped together to minimize padding)
BLEComponentState state_{BLE_COMPONENT_STATE_OFF}; // 1 byte (uint8_t enum)
bool enable_on_boot_{}; // 1 byte
}; };
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)

View File

@@ -23,7 +23,7 @@ from esphome.core.entity_helpers import setup_entity
DEPENDENCIES = ["esp32"] DEPENDENCIES = ["esp32"]
AUTO_LOAD = ["camera", "psram"] AUTO_LOAD = ["psram"]
esp32_camera_ns = cg.esphome_ns.namespace("esp32_camera") esp32_camera_ns = cg.esphome_ns.namespace("esp32_camera")
ESP32Camera = esp32_camera_ns.class_("ESP32Camera", cg.PollingComponent, cg.EntityBase) ESP32Camera = esp32_camera_ns.class_("ESP32Camera", cg.PollingComponent, cg.EntityBase)
@@ -283,7 +283,6 @@ SETTERS = {
async def to_code(config): async def to_code(config):
cg.add_define("USE_CAMERA")
var = cg.new_Pvariable(config[CONF_ID]) var = cg.new_Pvariable(config[CONF_ID])
await setup_entity(var, config, "camera") await setup_entity(var, config, "camera")
await cg.register_component(var, config) await cg.register_component(var, config)
@@ -308,7 +307,7 @@ async def to_code(config):
cg.add(var.set_frame_buffer_count(config[CONF_FRAME_BUFFER_COUNT])) cg.add(var.set_frame_buffer_count(config[CONF_FRAME_BUFFER_COUNT]))
cg.add(var.set_frame_size(config[CONF_RESOLUTION])) cg.add(var.set_frame_size(config[CONF_RESOLUTION]))
cg.add_define("USE_CAMERA") cg.add_define("USE_ESP32_CAMERA")
if CORE.using_esp_idf: if CORE.using_esp_idf:
add_idf_component(name="espressif/esp32-camera", ref="2.0.15") add_idf_component(name="espressif/esp32-camera", ref="2.0.15")

View File

@@ -14,6 +14,8 @@ static const char *const TAG = "esp32_camera";
/* ---------------- public API (derivated) ---------------- */ /* ---------------- public API (derivated) ---------------- */
void ESP32Camera::setup() { void ESP32Camera::setup() {
global_esp32_camera = this;
#ifdef USE_I2C #ifdef USE_I2C
if (this->i2c_bus_ != nullptr) { if (this->i2c_bus_ != nullptr) {
this->config_.sccb_i2c_port = this->i2c_bus_->get_port(); this->config_.sccb_i2c_port = this->i2c_bus_->get_port();
@@ -41,7 +43,7 @@ void ESP32Camera::setup() {
xTaskCreatePinnedToCore(&ESP32Camera::framebuffer_task, xTaskCreatePinnedToCore(&ESP32Camera::framebuffer_task,
"framebuffer_task", // name "framebuffer_task", // name
1024, // stack size 1024, // stack size
this, // task pv params nullptr, // task pv params
1, // priority 1, // priority
nullptr, // handle nullptr, // handle
1 // core 1 // core
@@ -174,7 +176,7 @@ void ESP32Camera::loop() {
const uint32_t now = App.get_loop_component_start_time(); const uint32_t now = App.get_loop_component_start_time();
if (this->idle_update_interval_ != 0 && now - this->last_idle_request_ > this->idle_update_interval_) { if (this->idle_update_interval_ != 0 && now - this->last_idle_request_ > this->idle_update_interval_) {
this->last_idle_request_ = now; this->last_idle_request_ = now;
this->request_image(camera::IDLE); this->request_image(IDLE);
} }
// Check if we should fetch a new image // Check if we should fetch a new image
@@ -200,7 +202,7 @@ void ESP32Camera::loop() {
xQueueSend(this->framebuffer_return_queue_, &fb, portMAX_DELAY); xQueueSend(this->framebuffer_return_queue_, &fb, portMAX_DELAY);
return; return;
} }
this->current_image_ = std::make_shared<ESP32CameraImage>(fb, this->single_requesters_ | this->stream_requesters_); this->current_image_ = std::make_shared<CameraImage>(fb, this->single_requesters_ | this->stream_requesters_);
ESP_LOGD(TAG, "Got Image: len=%u", fb->len); ESP_LOGD(TAG, "Got Image: len=%u", fb->len);
this->new_image_callback_.call(this->current_image_); this->new_image_callback_.call(this->current_image_);
@@ -223,6 +225,8 @@ ESP32Camera::ESP32Camera() {
this->config_.fb_count = 1; this->config_.fb_count = 1;
this->config_.grab_mode = CAMERA_GRAB_WHEN_EMPTY; this->config_.grab_mode = CAMERA_GRAB_WHEN_EMPTY;
this->config_.fb_location = CAMERA_FB_IN_PSRAM; this->config_.fb_location = CAMERA_FB_IN_PSRAM;
global_esp32_camera = this;
} }
/* ---------------- setters ---------------- */ /* ---------------- setters ---------------- */
@@ -352,7 +356,7 @@ void ESP32Camera::set_frame_buffer_count(uint8_t fb_count) {
} }
/* ---------------- public API (specific) ---------------- */ /* ---------------- public API (specific) ---------------- */
void ESP32Camera::add_image_callback(std::function<void(std::shared_ptr<camera::CameraImage>)> &&callback) { void ESP32Camera::add_image_callback(std::function<void(std::shared_ptr<CameraImage>)> &&callback) {
this->new_image_callback_.add(std::move(callback)); this->new_image_callback_.add(std::move(callback));
} }
void ESP32Camera::add_stream_start_callback(std::function<void()> &&callback) { void ESP32Camera::add_stream_start_callback(std::function<void()> &&callback) {
@@ -361,16 +365,15 @@ void ESP32Camera::add_stream_start_callback(std::function<void()> &&callback) {
void ESP32Camera::add_stream_stop_callback(std::function<void()> &&callback) { void ESP32Camera::add_stream_stop_callback(std::function<void()> &&callback) {
this->stream_stop_callback_.add(std::move(callback)); this->stream_stop_callback_.add(std::move(callback));
} }
void ESP32Camera::start_stream(camera::CameraRequester requester) { void ESP32Camera::start_stream(CameraRequester requester) {
this->stream_start_callback_.call(); this->stream_start_callback_.call();
this->stream_requesters_ |= (1U << requester); this->stream_requesters_ |= (1U << requester);
} }
void ESP32Camera::stop_stream(camera::CameraRequester requester) { void ESP32Camera::stop_stream(CameraRequester requester) {
this->stream_stop_callback_.call(); this->stream_stop_callback_.call();
this->stream_requesters_ &= ~(1U << requester); this->stream_requesters_ &= ~(1U << requester);
} }
void ESP32Camera::request_image(camera::CameraRequester requester) { this->single_requesters_ |= (1U << requester); } void ESP32Camera::request_image(CameraRequester requester) { this->single_requesters_ |= (1U << requester); }
camera::CameraImageReader *ESP32Camera::create_image_reader() { return new ESP32CameraImageReader; }
void ESP32Camera::update_camera_parameters() { void ESP32Camera::update_camera_parameters() {
sensor_t *s = esp_camera_sensor_get(); sensor_t *s = esp_camera_sensor_get();
/* update image */ /* update image */
@@ -399,39 +402,39 @@ void ESP32Camera::update_camera_parameters() {
bool ESP32Camera::has_requested_image_() const { return this->single_requesters_ || this->stream_requesters_; } bool ESP32Camera::has_requested_image_() const { return this->single_requesters_ || this->stream_requesters_; }
bool ESP32Camera::can_return_image_() const { return this->current_image_.use_count() == 1; } bool ESP32Camera::can_return_image_() const { return this->current_image_.use_count() == 1; }
void ESP32Camera::framebuffer_task(void *pv) { void ESP32Camera::framebuffer_task(void *pv) {
ESP32Camera *that = (ESP32Camera *) pv;
while (true) { while (true) {
camera_fb_t *framebuffer = esp_camera_fb_get(); camera_fb_t *framebuffer = esp_camera_fb_get();
xQueueSend(that->framebuffer_get_queue_, &framebuffer, portMAX_DELAY); xQueueSend(global_esp32_camera->framebuffer_get_queue_, &framebuffer, portMAX_DELAY);
// return is no-op for config with 1 fb // return is no-op for config with 1 fb
xQueueReceive(that->framebuffer_return_queue_, &framebuffer, portMAX_DELAY); xQueueReceive(global_esp32_camera->framebuffer_return_queue_, &framebuffer, portMAX_DELAY);
esp_camera_fb_return(framebuffer); esp_camera_fb_return(framebuffer);
} }
} }
/* ---------------- ESP32CameraImageReader class ----------- */ ESP32Camera *global_esp32_camera; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
void ESP32CameraImageReader::set_image(std::shared_ptr<camera::CameraImage> image) {
this->image_ = std::static_pointer_cast<ESP32CameraImage>(image); /* ---------------- CameraImageReader class ---------------- */
void CameraImageReader::set_image(std::shared_ptr<CameraImage> image) {
this->image_ = std::move(image);
this->offset_ = 0; this->offset_ = 0;
} }
size_t ESP32CameraImageReader::available() const { size_t CameraImageReader::available() const {
if (!this->image_) if (!this->image_)
return 0; return 0;
return this->image_->get_data_length() - this->offset_; return this->image_->get_data_length() - this->offset_;
} }
void ESP32CameraImageReader::return_image() { this->image_.reset(); } void CameraImageReader::return_image() { this->image_.reset(); }
void ESP32CameraImageReader::consume_data(size_t consumed) { this->offset_ += consumed; } void CameraImageReader::consume_data(size_t consumed) { this->offset_ += consumed; }
uint8_t *ESP32CameraImageReader::peek_data_buffer() { return this->image_->get_data_buffer() + this->offset_; } uint8_t *CameraImageReader::peek_data_buffer() { return this->image_->get_data_buffer() + this->offset_; }
/* ---------------- ESP32CameraImage class ----------- */ /* ---------------- CameraImage class ---------------- */
ESP32CameraImage::ESP32CameraImage(camera_fb_t *buffer, uint8_t requesters) CameraImage::CameraImage(camera_fb_t *buffer, uint8_t requesters) : buffer_(buffer), requesters_(requesters) {}
: buffer_(buffer), requesters_(requesters) {}
camera_fb_t *ESP32CameraImage::get_raw_buffer() { return this->buffer_; } camera_fb_t *CameraImage::get_raw_buffer() { return this->buffer_; }
uint8_t *ESP32CameraImage::get_data_buffer() { return this->buffer_->buf; } uint8_t *CameraImage::get_data_buffer() { return this->buffer_->buf; }
size_t ESP32CameraImage::get_data_length() { return this->buffer_->len; } size_t CameraImage::get_data_length() { return this->buffer_->len; }
bool ESP32CameraImage::was_requested_by(camera::CameraRequester requester) const { bool CameraImage::was_requested_by(CameraRequester requester) const {
return (this->requesters_ & (1 << requester)) != 0; return (this->requesters_ & (1 << requester)) != 0;
} }

View File

@@ -7,7 +7,7 @@
#include <freertos/queue.h> #include <freertos/queue.h>
#include "esphome/core/automation.h" #include "esphome/core/automation.h"
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/components/camera/camera.h" #include "esphome/core/entity_base.h"
#include "esphome/core/helpers.h" #include "esphome/core/helpers.h"
#ifdef USE_I2C #ifdef USE_I2C
@@ -19,6 +19,9 @@ namespace esp32_camera {
class ESP32Camera; class ESP32Camera;
/* ---------------- enum classes ---------------- */
enum CameraRequester { IDLE, API_REQUESTER, WEB_REQUESTER };
enum ESP32CameraFrameSize { enum ESP32CameraFrameSize {
ESP32_CAMERA_SIZE_160X120, // QQVGA ESP32_CAMERA_SIZE_160X120, // QQVGA
ESP32_CAMERA_SIZE_176X144, // QCIF ESP32_CAMERA_SIZE_176X144, // QCIF
@@ -74,13 +77,13 @@ enum ESP32SpecialEffect {
}; };
/* ---------------- CameraImage class ---------------- */ /* ---------------- CameraImage class ---------------- */
class ESP32CameraImage : public camera::CameraImage { class CameraImage {
public: public:
ESP32CameraImage(camera_fb_t *buffer, uint8_t requester); CameraImage(camera_fb_t *buffer, uint8_t requester);
camera_fb_t *get_raw_buffer(); camera_fb_t *get_raw_buffer();
uint8_t *get_data_buffer() override; uint8_t *get_data_buffer();
size_t get_data_length() override; size_t get_data_length();
bool was_requested_by(camera::CameraRequester requester) const override; bool was_requested_by(CameraRequester requester) const;
protected: protected:
camera_fb_t *buffer_; camera_fb_t *buffer_;
@@ -93,21 +96,21 @@ struct CameraImageData {
}; };
/* ---------------- CameraImageReader class ---------------- */ /* ---------------- CameraImageReader class ---------------- */
class ESP32CameraImageReader : public camera::CameraImageReader { class CameraImageReader {
public: public:
void set_image(std::shared_ptr<camera::CameraImage> image) override; void set_image(std::shared_ptr<CameraImage> image);
size_t available() const override; size_t available() const;
uint8_t *peek_data_buffer() override; uint8_t *peek_data_buffer();
void consume_data(size_t consumed) override; void consume_data(size_t consumed);
void return_image() override; void return_image();
protected: protected:
std::shared_ptr<ESP32CameraImage> image_; std::shared_ptr<CameraImage> image_;
size_t offset_{0}; size_t offset_{0};
}; };
/* ---------------- ESP32Camera class ---------------- */ /* ---------------- ESP32Camera class ---------------- */
class ESP32Camera : public camera::Camera { class ESP32Camera : public EntityBase, public Component {
public: public:
ESP32Camera(); ESP32Camera();
@@ -159,15 +162,14 @@ class ESP32Camera : public camera::Camera {
void dump_config() override; void dump_config() override;
float get_setup_priority() const override; float get_setup_priority() const override;
/* public API (specific) */ /* public API (specific) */
void start_stream(camera::CameraRequester requester) override; void start_stream(CameraRequester requester);
void stop_stream(camera::CameraRequester requester) override; void stop_stream(CameraRequester requester);
void request_image(camera::CameraRequester requester) override; void request_image(CameraRequester requester);
void update_camera_parameters(); void update_camera_parameters();
void add_image_callback(std::function<void(std::shared_ptr<camera::CameraImage>)> &&callback) override; void add_image_callback(std::function<void(std::shared_ptr<CameraImage>)> &&callback);
void add_stream_start_callback(std::function<void()> &&callback); void add_stream_start_callback(std::function<void()> &&callback);
void add_stream_stop_callback(std::function<void()> &&callback); void add_stream_stop_callback(std::function<void()> &&callback);
camera::CameraImageReader *create_image_reader() override;
protected: protected:
/* internal methods */ /* internal methods */
@@ -204,12 +206,12 @@ class ESP32Camera : public camera::Camera {
uint32_t idle_update_interval_{15000}; uint32_t idle_update_interval_{15000};
esp_err_t init_error_{ESP_OK}; esp_err_t init_error_{ESP_OK};
std::shared_ptr<ESP32CameraImage> current_image_; std::shared_ptr<CameraImage> current_image_;
uint8_t single_requesters_{0}; uint8_t single_requesters_{0};
uint8_t stream_requesters_{0}; uint8_t stream_requesters_{0};
QueueHandle_t framebuffer_get_queue_; QueueHandle_t framebuffer_get_queue_;
QueueHandle_t framebuffer_return_queue_; QueueHandle_t framebuffer_return_queue_;
CallbackManager<void(std::shared_ptr<camera::CameraImage>)> new_image_callback_{}; CallbackManager<void(std::shared_ptr<CameraImage>)> new_image_callback_{};
CallbackManager<void()> stream_start_callback_{}; CallbackManager<void()> stream_start_callback_{};
CallbackManager<void()> stream_stop_callback_{}; CallbackManager<void()> stream_stop_callback_{};
@@ -220,10 +222,13 @@ class ESP32Camera : public camera::Camera {
#endif // USE_I2C #endif // USE_I2C
}; };
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
extern ESP32Camera *global_esp32_camera;
class ESP32CameraImageTrigger : public Trigger<CameraImageData> { class ESP32CameraImageTrigger : public Trigger<CameraImageData> {
public: public:
explicit ESP32CameraImageTrigger(ESP32Camera *parent) { explicit ESP32CameraImageTrigger(ESP32Camera *parent) {
parent->add_image_callback([this](const std::shared_ptr<camera::CameraImage> &image) { parent->add_image_callback([this](const std::shared_ptr<esp32_camera::CameraImage> &image) {
CameraImageData camera_image_data{}; CameraImageData camera_image_data{};
camera_image_data.length = image->get_data_length(); camera_image_data.length = image->get_data_length();
camera_image_data.data = image->get_data_buffer(); camera_image_data.data = image->get_data_buffer();

View File

@@ -3,8 +3,7 @@ import esphome.config_validation as cv
from esphome.const import CONF_ID, CONF_MODE, CONF_PORT from esphome.const import CONF_ID, CONF_MODE, CONF_PORT
CODEOWNERS = ["@ayufan"] CODEOWNERS = ["@ayufan"]
AUTO_LOAD = ["camera"] DEPENDENCIES = ["esp32_camera", "network"]
DEPENDENCIES = ["network"]
MULTI_CONF = True MULTI_CONF = True
esp32_camera_web_server_ns = cg.esphome_ns.namespace("esp32_camera_web_server") esp32_camera_web_server_ns = cg.esphome_ns.namespace("esp32_camera_web_server")

View File

@@ -40,7 +40,7 @@ CameraWebServer::CameraWebServer() {}
CameraWebServer::~CameraWebServer() {} CameraWebServer::~CameraWebServer() {}
void CameraWebServer::setup() { void CameraWebServer::setup() {
if (!camera::Camera::instance() || camera::Camera::instance()->is_failed()) { if (!esp32_camera::global_esp32_camera || esp32_camera::global_esp32_camera->is_failed()) {
this->mark_failed(); this->mark_failed();
return; return;
} }
@@ -67,8 +67,8 @@ void CameraWebServer::setup() {
httpd_register_uri_handler(this->httpd_, &uri); httpd_register_uri_handler(this->httpd_, &uri);
camera::Camera::instance()->add_image_callback([this](std::shared_ptr<camera::CameraImage> image) { esp32_camera::global_esp32_camera->add_image_callback([this](std::shared_ptr<esp32_camera::CameraImage> image) {
if (this->running_ && image->was_requested_by(camera::WEB_REQUESTER)) { if (this->running_ && image->was_requested_by(esp32_camera::WEB_REQUESTER)) {
this->image_ = std::move(image); this->image_ = std::move(image);
xSemaphoreGive(this->semaphore_); xSemaphoreGive(this->semaphore_);
} }
@@ -108,8 +108,8 @@ void CameraWebServer::loop() {
} }
} }
std::shared_ptr<esphome::camera::CameraImage> CameraWebServer::wait_for_image_() { std::shared_ptr<esphome::esp32_camera::CameraImage> CameraWebServer::wait_for_image_() {
std::shared_ptr<esphome::camera::CameraImage> image; std::shared_ptr<esphome::esp32_camera::CameraImage> image;
image.swap(this->image_); image.swap(this->image_);
if (!image) { if (!image) {
@@ -172,7 +172,7 @@ esp_err_t CameraWebServer::streaming_handler_(struct httpd_req *req) {
uint32_t last_frame = millis(); uint32_t last_frame = millis();
uint32_t frames = 0; uint32_t frames = 0;
camera::Camera::instance()->start_stream(esphome::camera::WEB_REQUESTER); esp32_camera::global_esp32_camera->start_stream(esphome::esp32_camera::WEB_REQUESTER);
while (res == ESP_OK && this->running_) { while (res == ESP_OK && this->running_) {
auto image = this->wait_for_image_(); auto image = this->wait_for_image_();
@@ -205,7 +205,7 @@ esp_err_t CameraWebServer::streaming_handler_(struct httpd_req *req) {
res = httpd_send_all(req, STREAM_ERROR, strlen(STREAM_ERROR)); res = httpd_send_all(req, STREAM_ERROR, strlen(STREAM_ERROR));
} }
camera::Camera::instance()->stop_stream(esphome::camera::WEB_REQUESTER); esp32_camera::global_esp32_camera->stop_stream(esphome::esp32_camera::WEB_REQUESTER);
ESP_LOGI(TAG, "STREAM: closed. Frames: %" PRIu32, frames); ESP_LOGI(TAG, "STREAM: closed. Frames: %" PRIu32, frames);
@@ -215,7 +215,7 @@ esp_err_t CameraWebServer::streaming_handler_(struct httpd_req *req) {
esp_err_t CameraWebServer::snapshot_handler_(struct httpd_req *req) { esp_err_t CameraWebServer::snapshot_handler_(struct httpd_req *req) {
esp_err_t res = ESP_OK; esp_err_t res = ESP_OK;
camera::Camera::instance()->request_image(esphome::camera::WEB_REQUESTER); esp32_camera::global_esp32_camera->request_image(esphome::esp32_camera::WEB_REQUESTER);
auto image = this->wait_for_image_(); auto image = this->wait_for_image_();

View File

@@ -6,7 +6,7 @@
#include <freertos/FreeRTOS.h> #include <freertos/FreeRTOS.h>
#include <freertos/semphr.h> #include <freertos/semphr.h>
#include "esphome/components/camera/camera.h" #include "esphome/components/esp32_camera/esp32_camera.h"
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/core/helpers.h" #include "esphome/core/helpers.h"
#include "esphome/core/preferences.h" #include "esphome/core/preferences.h"
@@ -32,7 +32,7 @@ class CameraWebServer : public Component {
void loop() override; void loop() override;
protected: protected:
std::shared_ptr<camera::CameraImage> wait_for_image_(); std::shared_ptr<esphome::esp32_camera::CameraImage> wait_for_image_();
esp_err_t handler_(struct httpd_req *req); esp_err_t handler_(struct httpd_req *req);
esp_err_t streaming_handler_(struct httpd_req *req); esp_err_t streaming_handler_(struct httpd_req *req);
esp_err_t snapshot_handler_(struct httpd_req *req); esp_err_t snapshot_handler_(struct httpd_req *req);
@@ -40,7 +40,7 @@ class CameraWebServer : public Component {
uint16_t port_{0}; uint16_t port_{0};
void *httpd_{nullptr}; void *httpd_{nullptr};
SemaphoreHandle_t semaphore_; SemaphoreHandle_t semaphore_;
std::shared_ptr<camera::CameraImage> image_; std::shared_ptr<esphome::esp32_camera::CameraImage> image_;
bool running_{false}; bool running_{false};
Mode mode_{STREAM}; Mode mode_{STREAM};
}; };

View File

@@ -109,7 +109,6 @@ void ESP32TouchComponent::loop() {
// Only publish if state changed - this filters out repeated events // Only publish if state changed - this filters out repeated events
if (new_state != child->last_state_) { if (new_state != child->last_state_) {
child->initial_state_published_ = true;
child->last_state_ = new_state; child->last_state_ = new_state;
child->publish_state(new_state); child->publish_state(new_state);
// Original ESP32: ISR only fires when touched, release is detected by timeout // Original ESP32: ISR only fires when touched, release is detected by timeout
@@ -176,9 +175,6 @@ void ESP32TouchComponent::on_shutdown() {
void IRAM_ATTR ESP32TouchComponent::touch_isr_handler(void *arg) { void IRAM_ATTR ESP32TouchComponent::touch_isr_handler(void *arg) {
ESP32TouchComponent *component = static_cast<ESP32TouchComponent *>(arg); ESP32TouchComponent *component = static_cast<ESP32TouchComponent *>(arg);
uint32_t mask = 0;
touch_ll_read_trigger_status_mask(&mask);
touch_ll_clear_trigger_status_mask();
touch_pad_clear_status(); touch_pad_clear_status();
// INTERRUPT BEHAVIOR: On ESP32 v1 hardware, the interrupt fires when ANY configured // INTERRUPT BEHAVIOR: On ESP32 v1 hardware, the interrupt fires when ANY configured
@@ -188,11 +184,6 @@ void IRAM_ATTR ESP32TouchComponent::touch_isr_handler(void *arg) {
// as any pad remains touched. This allows us to detect both new touches and // as any pad remains touched. This allows us to detect both new touches and
// continued touches, but releases must be detected by timeout in the main loop. // continued touches, but releases must be detected by timeout in the main loop.
// IMPORTANT: ESP32 v1 touch detection logic - INVERTED compared to v2!
// ESP32 v1: Touch is detected when capacitance INCREASES, causing the measured value to DECREASE
// Therefore: touched = (value < threshold)
// This is opposite to ESP32-S2/S3 v2 where touched = (value > threshold)
// Process all configured pads to check their current state // Process all configured pads to check their current state
// Note: ESP32 v1 doesn't tell us which specific pad triggered the interrupt, // Note: ESP32 v1 doesn't tell us which specific pad triggered the interrupt,
// so we must scan all configured pads to find which ones were touched // so we must scan all configured pads to find which ones were touched
@@ -210,12 +201,19 @@ void IRAM_ATTR ESP32TouchComponent::touch_isr_handler(void *arg) {
value = touch_ll_read_raw_data(pad); value = touch_ll_read_raw_data(pad);
} }
// Skip pads that arent in the trigger mask // Skip pads with 0 value - they haven't been measured in this cycle
bool is_touched = (mask >> pad) & 1; // This is important: not all pads are measured every interrupt cycle,
if (!is_touched) { // only those that the hardware has updated
if (value == 0) {
continue; continue;
} }
// IMPORTANT: ESP32 v1 touch detection logic - INVERTED compared to v2!
// ESP32 v1: Touch is detected when capacitance INCREASES, causing the measured value to DECREASE
// Therefore: touched = (value < threshold)
// This is opposite to ESP32-S2/S3 v2 where touched = (value > threshold)
bool is_touched = value < child->get_threshold();
// Always send the current state - the main loop will filter for changes // Always send the current state - the main loop will filter for changes
// We send both touched and untouched states because the ISR doesn't // We send both touched and untouched states because the ISR doesn't
// track previous state (to keep ISR fast and simple) // track previous state (to keep ISR fast and simple)

View File

@@ -180,7 +180,6 @@ async def to_code(config):
cg.add(esp8266_ns.setup_preferences()) cg.add(esp8266_ns.setup_preferences())
cg.add_platformio_option("lib_ldf_mode", "off") cg.add_platformio_option("lib_ldf_mode", "off")
cg.add_platformio_option("lib_compat_mode", "strict")
cg.add_platformio_option("board", config[CONF_BOARD]) cg.add_platformio_option("board", config[CONF_BOARD])
cg.add_build_flag("-DUSE_ESP8266") cg.add_build_flag("-DUSE_ESP8266")

View File

@@ -1,31 +0,0 @@
#include "esphome/core/helpers.h"
#ifdef USE_ESP8266
#include <osapi.h>
#include <user_interface.h>
// for xt_rsil()/xt_wsr_ps()
#include <Arduino.h>
namespace esphome {
uint32_t random_uint32() { return os_random(); }
bool random_bytes(uint8_t *data, size_t len) { return os_get_random(data, len) == 0; }
// ESP8266 doesn't have mutexes, but that shouldn't be an issue as it's single-core and non-preemptive OS.
Mutex::Mutex() {}
Mutex::~Mutex() {}
void Mutex::lock() {}
bool Mutex::try_lock() { return true; }
void Mutex::unlock() {}
IRAM_ATTR InterruptLock::InterruptLock() { state_ = xt_rsil(15); }
IRAM_ATTR InterruptLock::~InterruptLock() { xt_wsr_ps(state_); }
void get_mac_address_raw(uint8_t *mac) { // NOLINT(readability-non-const-parameter)
wifi_get_macaddr(STATION_IF, mac);
}
} // namespace esphome
#endif // USE_ESP8266

View File

@@ -20,16 +20,14 @@ adjusted_ids = set()
CONFIG_SCHEMA = cv.All( CONFIG_SCHEMA = cv.All(
cv.ensure_list( cv.ensure_list(
cv.COMPONENT_SCHEMA.extend( {
{ cv.GenerateID(): cv.declare_id(EspLdo),
cv.GenerateID(): cv.declare_id(EspLdo), cv.Required(CONF_VOLTAGE): cv.All(
cv.Required(CONF_VOLTAGE): cv.All( cv.voltage, cv.float_range(min=0.5, max=2.7)
cv.voltage, cv.float_range(min=0.5, max=2.7) ),
), cv.Required(CONF_CHANNEL): cv.one_of(*CHANNELS, int=True),
cv.Required(CONF_CHANNEL): cv.one_of(*CHANNELS, int=True), cv.Optional(CONF_ADJUSTABLE, default=False): cv.boolean,
cv.Optional(CONF_ADJUSTABLE, default=False): cv.boolean, }
}
)
), ),
cv.only_with_esp_idf, cv.only_with_esp_idf,
only_on_variant(supported=[VARIANT_ESP32P4]), only_on_variant(supported=[VARIANT_ESP32P4]),

View File

@@ -17,9 +17,6 @@ class EspLdo : public Component {
void set_adjustable(bool adjustable) { this->adjustable_ = adjustable; } void set_adjustable(bool adjustable) { this->adjustable_ = adjustable; }
void set_voltage(float voltage) { this->voltage_ = voltage; } void set_voltage(float voltage) { this->voltage_ = voltage; }
void adjust_voltage(float voltage); void adjust_voltage(float voltage);
float get_setup_priority() const override {
return setup_priority::BUS; // LDO setup should be done early
}
protected: protected:
int channel_; int channel_;

View File

@@ -177,10 +177,6 @@ optional<FanRestoreState> Fan::restore_state_() {
return {}; return {};
} }
void Fan::save_state_() { void Fan::save_state_() {
if (this->restore_mode_ == FanRestoreMode::NO_RESTORE) {
return;
}
FanRestoreState state{}; FanRestoreState state{};
state.state = this->state; state.state = this->state;
state.oscillating = this->oscillating; state.oscillating = this->oscillating;

View File

@@ -1,68 +0,0 @@
#include "esphome/core/log.h"
#include "esphome/core/hal.h"
#include "gl_r01_i2c.h"
namespace esphome {
namespace gl_r01_i2c {
static const char *const TAG = "gl_r01_i2c";
// Register definitions from datasheet
static const uint8_t REG_VERSION = 0x00;
static const uint8_t REG_DISTANCE = 0x02;
static const uint8_t REG_TRIGGER = 0x10;
static const uint8_t CMD_TRIGGER = 0xB0;
static const uint8_t RESTART_CMD1 = 0x5A;
static const uint8_t RESTART_CMD2 = 0xA5;
static const uint8_t READ_DELAY = 40; // minimum milliseconds from datasheet to safely read measurement result
void GLR01I2CComponent::setup() {
ESP_LOGCONFIG(TAG, "Setting up GL-R01 I2C...");
// Verify sensor presence
if (!this->read_byte_16(REG_VERSION, &this->version_)) {
ESP_LOGE(TAG, "Failed to communicate with GL-R01 I2C sensor!");
this->mark_failed();
return;
}
ESP_LOGD(TAG, "Found GL-R01 I2C with version 0x%04X", this->version_);
}
void GLR01I2CComponent::dump_config() {
ESP_LOGCONFIG(TAG, "GL-R01 I2C:");
ESP_LOGCONFIG(TAG, " Firmware Version: 0x%04X", this->version_);
LOG_I2C_DEVICE(this);
LOG_SENSOR(" ", "Distance", this);
}
void GLR01I2CComponent::update() {
// Trigger a new measurement
if (!this->write_byte(REG_TRIGGER, CMD_TRIGGER)) {
ESP_LOGE(TAG, "Failed to trigger measurement!");
this->status_set_warning();
return;
}
// Schedule reading the result after the read delay
this->set_timeout(READ_DELAY, [this]() { this->read_distance_(); });
}
void GLR01I2CComponent::read_distance_() {
uint16_t distance = 0;
if (!this->read_byte_16(REG_DISTANCE, &distance)) {
ESP_LOGE(TAG, "Failed to read distance value!");
this->status_set_warning();
return;
}
if (distance == 0xFFFF) {
ESP_LOGW(TAG, "Invalid measurement received!");
this->status_set_warning();
} else {
ESP_LOGV(TAG, "Distance: %umm", distance);
this->publish_state(distance);
this->status_clear_warning();
}
}
} // namespace gl_r01_i2c
} // namespace esphome

View File

@@ -1,22 +0,0 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/i2c/i2c.h"
namespace esphome {
namespace gl_r01_i2c {
class GLR01I2CComponent : public sensor::Sensor, public i2c::I2CDevice, public PollingComponent {
public:
void setup() override;
void dump_config() override;
void update() override;
protected:
void read_distance_();
uint16_t version_{0};
};
} // namespace gl_r01_i2c
} // namespace esphome

View File

@@ -1,36 +0,0 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import i2c, sensor
from esphome.const import (
CONF_ID,
DEVICE_CLASS_DISTANCE,
STATE_CLASS_MEASUREMENT,
UNIT_MILLIMETER,
)
CODEOWNERS = ["@pkejval"]
DEPENDENCIES = ["i2c"]
gl_r01_i2c_ns = cg.esphome_ns.namespace("gl_r01_i2c")
GLR01I2CComponent = gl_r01_i2c_ns.class_(
"GLR01I2CComponent", i2c.I2CDevice, cg.PollingComponent
)
CONFIG_SCHEMA = (
sensor.sensor_schema(
GLR01I2CComponent,
unit_of_measurement=UNIT_MILLIMETER,
accuracy_decimals=0,
device_class=DEVICE_CLASS_DISTANCE,
state_class=STATE_CLASS_MEASUREMENT,
)
.extend(cv.polling_component_schema("60s"))
.extend(i2c.i2c_device_schema(0x74))
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await sensor.register_sensor(var, config)
await i2c.register_i2c_device(var, config)

View File

@@ -1,16 +1,11 @@
import logging
from esphome import pins from esphome import pins
import esphome.codegen as cg import esphome.codegen as cg
from esphome.components import binary_sensor from esphome.components import binary_sensor
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.const import CONF_ID, CONF_NAME, CONF_NUMBER, CONF_PIN from esphome.const import CONF_PIN
from esphome.core import CORE
from .. import gpio_ns from .. import gpio_ns
_LOGGER = logging.getLogger(__name__)
GPIOBinarySensor = gpio_ns.class_( GPIOBinarySensor = gpio_ns.class_(
"GPIOBinarySensor", binary_sensor.BinarySensor, cg.Component "GPIOBinarySensor", binary_sensor.BinarySensor, cg.Component
) )
@@ -46,22 +41,6 @@ async def to_code(config):
pin = await cg.gpio_pin_expression(config[CONF_PIN]) pin = await cg.gpio_pin_expression(config[CONF_PIN])
cg.add(var.set_pin(pin)) cg.add(var.set_pin(pin))
# Check for ESP8266 GPIO16 interrupt limitation cg.add(var.set_use_interrupt(config[CONF_USE_INTERRUPT]))
# GPIO16 on ESP8266 is a special pin that doesn't support interrupts through if config[CONF_USE_INTERRUPT]:
# the Arduino attachInterrupt() function. This is the only known GPIO pin
# across all supported platforms that has this limitation, so we handle it
# here instead of in the platform-specific code.
use_interrupt = config[CONF_USE_INTERRUPT]
if use_interrupt and CORE.is_esp8266 and config[CONF_PIN][CONF_NUMBER] == 16:
_LOGGER.warning(
"GPIO binary_sensor '%s': GPIO16 on ESP8266 doesn't support interrupts. "
"Falling back to polling mode (same as in ESPHome <2025.7). "
"The sensor will work exactly as before, but other pins have better "
"performance with interrupts.",
config.get(CONF_NAME, config[CONF_ID]),
)
use_interrupt = False
cg.add(var.set_use_interrupt(use_interrupt))
if use_interrupt:
cg.add(var.set_interrupt_type(config[CONF_INTERRUPT_TYPE])) cg.add(var.set_interrupt_type(config[CONF_INTERRUPT_TYPE]))

View File

@@ -45,4 +45,3 @@ async def to_code(config):
cg.add_define("ESPHOME_BOARD", "host") cg.add_define("ESPHOME_BOARD", "host")
cg.add_platformio_option("platform", "platformio/native") cg.add_platformio_option("platform", "platformio/native")
cg.add_platformio_option("lib_ldf_mode", "off") cg.add_platformio_option("lib_ldf_mode", "off")
cg.add_platformio_option("lib_compat_mode", "strict")

View File

@@ -1,57 +0,0 @@
#include "esphome/core/helpers.h"
#ifdef USE_HOST
#ifndef _WIN32
#include <net/if.h>
#include <netinet/in.h>
#include <sys/ioctl.h>
#endif
#include <unistd.h>
#include <limits>
#include <random>
#include "esphome/core/defines.h"
#include "esphome/core/log.h"
namespace esphome {
static const char *const TAG = "helpers.host";
uint32_t random_uint32() {
std::random_device dev;
std::mt19937 rng(dev());
std::uniform_int_distribution<uint32_t> dist(0, std::numeric_limits<uint32_t>::max());
return dist(rng);
}
bool random_bytes(uint8_t *data, size_t len) {
FILE *fp = fopen("/dev/urandom", "r");
if (fp == nullptr) {
ESP_LOGW(TAG, "Could not open /dev/urandom, errno=%d", errno);
exit(1);
}
size_t read = fread(data, 1, len, fp);
if (read != len) {
ESP_LOGW(TAG, "Not enough data from /dev/urandom");
exit(1);
}
fclose(fp);
return true;
}
// Host platform uses std::mutex for proper thread synchronization
Mutex::Mutex() { handle_ = new std::mutex(); }
Mutex::~Mutex() { delete static_cast<std::mutex *>(handle_); }
void Mutex::lock() { static_cast<std::mutex *>(handle_)->lock(); }
bool Mutex::try_lock() { return static_cast<std::mutex *>(handle_)->try_lock(); }
void Mutex::unlock() { static_cast<std::mutex *>(handle_)->unlock(); }
void get_mac_address_raw(uint8_t *mac) { // NOLINT(readability-non-const-parameter)
static const uint8_t esphome_host_mac_address[6] = USE_ESPHOME_HOST_MAC_ADDRESS;
memcpy(mac, esphome_host_mac_address, sizeof(esphome_host_mac_address));
}
} // namespace esphome
#endif // USE_HOST

View File

@@ -2,7 +2,6 @@ from esphome import automation
import esphome.codegen as cg import esphome.codegen as cg
from esphome.components import esp32 from esphome.components import esp32
from esphome.components.const import CONF_REQUEST_HEADERS from esphome.components.const import CONF_REQUEST_HEADERS
from esphome.config_helpers import filter_source_files_from_platform
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.const import ( from esphome.const import (
CONF_ESP8266_DISABLE_SSL_SUPPORT, CONF_ESP8266_DISABLE_SSL_SUPPORT,
@@ -14,7 +13,6 @@ from esphome.const import (
CONF_URL, CONF_URL,
CONF_WATCHDOG_TIMEOUT, CONF_WATCHDOG_TIMEOUT,
PLATFORM_HOST, PLATFORM_HOST,
PlatformFramework,
__version__, __version__,
) )
from esphome.core import CORE, Lambda from esphome.core import CORE, Lambda
@@ -321,19 +319,3 @@ async def http_request_action_to_code(config, action_id, template_arg, args):
await automation.build_automation(trigger, [], conf) await automation.build_automation(trigger, [], conf)
return var return var
FILTER_SOURCE_FILES = filter_source_files_from_platform(
{
"http_request_host.cpp": {PlatformFramework.HOST_NATIVE},
"http_request_arduino.cpp": {
PlatformFramework.ESP32_ARDUINO,
PlatformFramework.ESP8266_ARDUINO,
PlatformFramework.RP2040_ARDUINO,
PlatformFramework.BK72XX_ARDUINO,
PlatformFramework.RTL87XX_ARDUINO,
PlatformFramework.LN882X_ARDUINO,
},
"http_request_idf.cpp": {PlatformFramework.ESP32_IDF},
}
)

View File

@@ -50,8 +50,7 @@ void HttpRequestUpdate::update_task(void *params) {
if (container == nullptr || container->status_code != HTTP_STATUS_OK) { if (container == nullptr || container->status_code != HTTP_STATUS_OK) {
std::string msg = str_sprintf("Failed to fetch manifest from %s", this_update->source_url_.c_str()); std::string msg = str_sprintf("Failed to fetch manifest from %s", this_update->source_url_.c_str());
// Defer to main loop to avoid race condition on component_state_ read-modify-write this_update->status_set_error(msg.c_str());
this_update->defer([this_update, msg]() { this_update->status_set_error(msg.c_str()); });
UPDATE_RETURN; UPDATE_RETURN;
} }
@@ -59,8 +58,7 @@ void HttpRequestUpdate::update_task(void *params) {
uint8_t *data = allocator.allocate(container->content_length); uint8_t *data = allocator.allocate(container->content_length);
if (data == nullptr) { if (data == nullptr) {
std::string msg = str_sprintf("Failed to allocate %zu bytes for manifest", container->content_length); std::string msg = str_sprintf("Failed to allocate %zu bytes for manifest", container->content_length);
// Defer to main loop to avoid race condition on component_state_ read-modify-write this_update->status_set_error(msg.c_str());
this_update->defer([this_update, msg]() { this_update->status_set_error(msg.c_str()); });
container->end(); container->end();
UPDATE_RETURN; UPDATE_RETURN;
} }
@@ -83,7 +81,7 @@ void HttpRequestUpdate::update_task(void *params) {
container.reset(); // Release ownership of the container's shared_ptr container.reset(); // Release ownership of the container's shared_ptr
valid = json::parse_json(response, [this_update](JsonObject root) -> bool { valid = json::parse_json(response, [this_update](JsonObject root) -> bool {
if (!root["name"].is<const char *>() || !root["version"].is<const char *>() || !root["builds"].is<JsonArray>()) { if (!root.containsKey("name") || !root.containsKey("version") || !root.containsKey("builds")) {
ESP_LOGE(TAG, "Manifest does not contain required fields"); ESP_LOGE(TAG, "Manifest does not contain required fields");
return false; return false;
} }
@@ -91,26 +89,26 @@ void HttpRequestUpdate::update_task(void *params) {
this_update->update_info_.latest_version = root["version"].as<std::string>(); this_update->update_info_.latest_version = root["version"].as<std::string>();
for (auto build : root["builds"].as<JsonArray>()) { for (auto build : root["builds"].as<JsonArray>()) {
if (!build["chipFamily"].is<const char *>()) { if (!build.containsKey("chipFamily")) {
ESP_LOGE(TAG, "Manifest does not contain required fields"); ESP_LOGE(TAG, "Manifest does not contain required fields");
return false; return false;
} }
if (build["chipFamily"] == ESPHOME_VARIANT) { if (build["chipFamily"] == ESPHOME_VARIANT) {
if (!build["ota"].is<JsonObject>()) { if (!build.containsKey("ota")) {
ESP_LOGE(TAG, "Manifest does not contain required fields"); ESP_LOGE(TAG, "Manifest does not contain required fields");
return false; return false;
} }
JsonObject ota = build["ota"].as<JsonObject>(); auto ota = build["ota"];
if (!ota["path"].is<const char *>() || !ota["md5"].is<const char *>()) { if (!ota.containsKey("path") || !ota.containsKey("md5")) {
ESP_LOGE(TAG, "Manifest does not contain required fields"); ESP_LOGE(TAG, "Manifest does not contain required fields");
return false; return false;
} }
this_update->update_info_.firmware_url = ota["path"].as<std::string>(); this_update->update_info_.firmware_url = ota["path"].as<std::string>();
this_update->update_info_.md5 = ota["md5"].as<std::string>(); this_update->update_info_.md5 = ota["md5"].as<std::string>();
if (ota["summary"].is<const char *>()) if (ota.containsKey("summary"))
this_update->update_info_.summary = ota["summary"].as<std::string>(); this_update->update_info_.summary = ota["summary"].as<std::string>();
if (ota["release_url"].is<const char *>()) if (ota.containsKey("release_url"))
this_update->update_info_.release_url = ota["release_url"].as<std::string>(); this_update->update_info_.release_url = ota["release_url"].as<std::string>();
return true; return true;
@@ -122,8 +120,7 @@ void HttpRequestUpdate::update_task(void *params) {
if (!valid) { if (!valid) {
std::string msg = str_sprintf("Failed to parse JSON from %s", this_update->source_url_.c_str()); std::string msg = str_sprintf("Failed to parse JSON from %s", this_update->source_url_.c_str());
// Defer to main loop to avoid race condition on component_state_ read-modify-write this_update->status_set_error(msg.c_str());
this_update->defer([this_update, msg]() { this_update->status_set_error(msg.c_str()); });
UPDATE_RETURN; UPDATE_RETURN;
} }
@@ -150,34 +147,18 @@ void HttpRequestUpdate::update_task(void *params) {
this_update->update_info_.current_version = current_version; this_update->update_info_.current_version = current_version;
} }
bool trigger_update_available = false;
if (this_update->update_info_.latest_version.empty() || if (this_update->update_info_.latest_version.empty() ||
this_update->update_info_.latest_version == this_update->update_info_.current_version) { this_update->update_info_.latest_version == this_update->update_info_.current_version) {
this_update->state_ = update::UPDATE_STATE_NO_UPDATE; this_update->state_ = update::UPDATE_STATE_NO_UPDATE;
} else { } else {
if (this_update->state_ != update::UPDATE_STATE_AVAILABLE) {
trigger_update_available = true;
}
this_update->state_ = update::UPDATE_STATE_AVAILABLE; this_update->state_ = update::UPDATE_STATE_AVAILABLE;
} }
// Defer to main loop to ensure thread-safe execution of: this_update->update_info_.has_progress = false;
// - status_clear_error() performs non-atomic read-modify-write on component_state_ this_update->update_info_.progress = 0.0f;
// - publish_state() triggers API callbacks that write to the shared protobuf buffer
// which can be corrupted if accessed concurrently from task and main loop threads
// - update_available trigger to ensure consistent state when the trigger fires
this_update->defer([this_update, trigger_update_available]() {
this_update->update_info_.has_progress = false;
this_update->update_info_.progress = 0.0f;
this_update->status_clear_error(); this_update->status_clear_error();
this_update->publish_state(); this_update->publish_state();
if (trigger_update_available) {
this_update->get_update_available_trigger()->trigger(this_update->update_info_);
}
});
UPDATE_RETURN; UPDATE_RETURN;
} }

View File

@@ -111,8 +111,8 @@ CONFIG_SCHEMA = cv.All(
cv.Optional(CONF_MOISTURE): sensor.sensor_schema( cv.Optional(CONF_MOISTURE): sensor.sensor_schema(
unit_of_measurement=UNIT_INTENSITY, unit_of_measurement=UNIT_INTENSITY,
accuracy_decimals=0, accuracy_decimals=0,
device_class=DEVICE_CLASS_PRECIPITATION_INTENSITY,
state_class=STATE_CLASS_MEASUREMENT, state_class=STATE_CLASS_MEASUREMENT,
icon="mdi:weather-rainy",
), ),
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
unit_of_measurement=UNIT_CELSIUS, unit_of_measurement=UNIT_CELSIUS,

View File

@@ -3,7 +3,6 @@ import logging
from esphome import pins from esphome import pins
import esphome.codegen as cg import esphome.codegen as cg
from esphome.components import esp32 from esphome.components import esp32
from esphome.config_helpers import filter_source_files_from_platform
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.const import ( from esphome.const import (
CONF_ADDRESS, CONF_ADDRESS,
@@ -19,7 +18,6 @@ from esphome.const import (
PLATFORM_ESP32, PLATFORM_ESP32,
PLATFORM_ESP8266, PLATFORM_ESP8266,
PLATFORM_RP2040, PLATFORM_RP2040,
PlatformFramework,
) )
from esphome.core import CORE, coroutine_with_priority from esphome.core import CORE, coroutine_with_priority
import esphome.final_validate as fv import esphome.final_validate as fv
@@ -207,18 +205,3 @@ def final_validate_device_schema(
{cv.Required(CONF_I2C_ID): fv.id_declaration_match_schema(hub_schema)}, {cv.Required(CONF_I2C_ID): fv.id_declaration_match_schema(hub_schema)},
extra=cv.ALLOW_EXTRA, extra=cv.ALLOW_EXTRA,
) )
FILTER_SOURCE_FILES = filter_source_files_from_platform(
{
"i2c_bus_arduino.cpp": {
PlatformFramework.ESP32_ARDUINO,
PlatformFramework.ESP8266_ARDUINO,
PlatformFramework.RP2040_ARDUINO,
PlatformFramework.BK72XX_ARDUINO,
PlatformFramework.RTL87XX_ARDUINO,
PlatformFramework.LN882X_ARDUINO,
},
"i2c_bus_esp_idf.cpp": {PlatformFramework.ESP32_IDF},
}
)

View File

@@ -180,7 +180,7 @@ async def to_code(config):
await speaker.register_speaker(var, config) await speaker.register_speaker(var, config)
if config[CONF_DAC_TYPE] == "internal": if config[CONF_DAC_TYPE] == "internal":
cg.add(var.set_internal_dac_mode(config[CONF_MODE])) cg.add(var.set_internal_dac_mode(config[CONF_CHANNEL]))
else: else:
cg.add(var.set_dout_pin(config[CONF_I2S_DOUT_PIN])) cg.add(var.set_dout_pin(config[CONF_I2S_DOUT_PIN]))
if use_legacy(): if use_legacy():

View File

@@ -10,10 +10,8 @@ from PIL import Image, UnidentifiedImageError
from esphome import core, external_files from esphome import core, external_files
import esphome.codegen as cg import esphome.codegen as cg
from esphome.components.const import CONF_BYTE_ORDER
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.const import ( from esphome.const import (
CONF_DEFAULTS,
CONF_DITHER, CONF_DITHER,
CONF_FILE, CONF_FILE,
CONF_ICON, CONF_ICON,
@@ -40,7 +38,6 @@ CONF_OPAQUE = "opaque"
CONF_CHROMA_KEY = "chroma_key" CONF_CHROMA_KEY = "chroma_key"
CONF_ALPHA_CHANNEL = "alpha_channel" CONF_ALPHA_CHANNEL = "alpha_channel"
CONF_INVERT_ALPHA = "invert_alpha" CONF_INVERT_ALPHA = "invert_alpha"
CONF_IMAGES = "images"
TRANSPARENCY_TYPES = ( TRANSPARENCY_TYPES = (
CONF_OPAQUE, CONF_OPAQUE,
@@ -191,10 +188,6 @@ class ImageRGB565(ImageEncoder):
dither, dither,
invert_alpha, invert_alpha,
) )
self.big_endian = True
def set_big_endian(self, big_endian: bool) -> None:
self.big_endian = big_endian
def convert(self, image, path): def convert(self, image, path):
return image.convert("RGBA") return image.convert("RGBA")
@@ -212,16 +205,10 @@ class ImageRGB565(ImageEncoder):
g = 1 g = 1
b = 0 b = 0
rgb = (r << 11) | (g << 5) | b rgb = (r << 11) | (g << 5) | b
if self.big_endian: self.data[self.index] = rgb >> 8
self.data[self.index] = rgb >> 8 self.index += 1
self.index += 1 self.data[self.index] = rgb & 0xFF
self.data[self.index] = rgb & 0xFF self.index += 1
self.index += 1
else:
self.data[self.index] = rgb & 0xFF
self.index += 1
self.data[self.index] = rgb >> 8
self.index += 1
if self.transparency == CONF_ALPHA_CHANNEL: if self.transparency == CONF_ALPHA_CHANNEL:
if self.invert_alpha: if self.invert_alpha:
a ^= 0xFF a ^= 0xFF
@@ -377,7 +364,7 @@ def validate_file_shorthand(value):
value = cv.string_strict(value) value = cv.string_strict(value)
parts = value.strip().split(":") parts = value.strip().split(":")
if len(parts) == 2 and parts[0] in MDI_SOURCES: if len(parts) == 2 and parts[0] in MDI_SOURCES:
match = re.match(r"^[a-zA-Z0-9\-]+$", parts[1]) match = re.match(r"[a-zA-Z0-9\-]+", parts[1])
if match is None: if match is None:
raise cv.Invalid(f"Could not parse mdi icon name from '{value}'.") raise cv.Invalid(f"Could not parse mdi icon name from '{value}'.")
return download_gh_svg(parts[1], parts[0]) return download_gh_svg(parts[1], parts[0])
@@ -447,29 +434,20 @@ def validate_type(image_types):
def validate_settings(value): def validate_settings(value):
""" type = value[CONF_TYPE]
Validate the settings for a single image configuration.
"""
conf_type = value[CONF_TYPE]
type_class = IMAGE_TYPE[conf_type]
transparency = value[CONF_TRANSPARENCY].lower() transparency = value[CONF_TRANSPARENCY].lower()
if transparency not in type_class.allow_config: allow_config = IMAGE_TYPE[type].allow_config
if transparency not in allow_config:
raise cv.Invalid( raise cv.Invalid(
f"Image format '{conf_type}' cannot have transparency: {transparency}" f"Image format '{type}' cannot have transparency: {transparency}"
) )
invert_alpha = value.get(CONF_INVERT_ALPHA, False) invert_alpha = value.get(CONF_INVERT_ALPHA, False)
if ( if (
invert_alpha invert_alpha
and transparency != CONF_ALPHA_CHANNEL and transparency != CONF_ALPHA_CHANNEL
and CONF_INVERT_ALPHA not in type_class.allow_config and CONF_INVERT_ALPHA not in allow_config
): ):
raise cv.Invalid("No alpha channel to invert") raise cv.Invalid("No alpha channel to invert")
if value.get(CONF_BYTE_ORDER) is not None and not callable(
getattr(type_class, "set_big_endian", None)
):
raise cv.Invalid(
f"Image format '{conf_type}' does not support byte order configuration"
)
if file := value.get(CONF_FILE): if file := value.get(CONF_FILE):
file = Path(file) file = Path(file)
if is_svg_file(file): if is_svg_file(file):
@@ -478,82 +456,31 @@ def validate_settings(value):
try: try:
Image.open(file) Image.open(file)
except UnidentifiedImageError as exc: except UnidentifiedImageError as exc:
raise cv.Invalid( raise cv.Invalid(f"File can't be opened as image: {file}") from exc
f"File can't be opened as image: {file.absolute()}"
) from exc
return value return value
IMAGE_ID_SCHEMA = {
cv.Required(CONF_ID): cv.declare_id(Image_),
cv.Required(CONF_FILE): cv.Any(validate_file_shorthand, TYPED_FILE_SCHEMA),
cv.GenerateID(CONF_RAW_DATA_ID): cv.declare_id(cg.uint8),
}
OPTIONS_SCHEMA = {
cv.Optional(CONF_RESIZE): cv.dimensions,
cv.Optional(CONF_DITHER, default="NONE"): cv.one_of(
"NONE", "FLOYDSTEINBERG", upper=True
),
cv.Optional(CONF_INVERT_ALPHA, default=False): cv.boolean,
cv.Optional(CONF_BYTE_ORDER): cv.one_of("BIG_ENDIAN", "LITTLE_ENDIAN", upper=True),
cv.Optional(CONF_TRANSPARENCY, default=CONF_OPAQUE): validate_transparency(),
cv.Optional(CONF_TYPE): validate_type(IMAGE_TYPE),
}
OPTIONS = [key.schema for key in OPTIONS_SCHEMA]
# image schema with no defaults, used with `CONF_IMAGES` in the config
IMAGE_SCHEMA_NO_DEFAULTS = {
**IMAGE_ID_SCHEMA,
**{cv.Optional(key): OPTIONS_SCHEMA[key] for key in OPTIONS},
}
BASE_SCHEMA = cv.Schema( BASE_SCHEMA = cv.Schema(
{ {
**IMAGE_ID_SCHEMA, cv.Required(CONF_ID): cv.declare_id(Image_),
**OPTIONS_SCHEMA, cv.Required(CONF_FILE): cv.Any(validate_file_shorthand, TYPED_FILE_SCHEMA),
cv.Optional(CONF_RESIZE): cv.dimensions,
cv.Optional(CONF_DITHER, default="NONE"): cv.one_of(
"NONE", "FLOYDSTEINBERG", upper=True
),
cv.Optional(CONF_INVERT_ALPHA, default=False): cv.boolean,
cv.GenerateID(CONF_RAW_DATA_ID): cv.declare_id(cg.uint8),
} }
).add_extra(validate_settings) ).add_extra(validate_settings)
IMAGE_SCHEMA = BASE_SCHEMA.extend( IMAGE_SCHEMA = BASE_SCHEMA.extend(
{ {
cv.Required(CONF_TYPE): validate_type(IMAGE_TYPE), cv.Required(CONF_TYPE): validate_type(IMAGE_TYPE),
cv.Optional(CONF_TRANSPARENCY, default=CONF_OPAQUE): validate_transparency(),
} }
) )
def validate_defaults(value):
"""
Validate the options for images with defaults
"""
defaults = value[CONF_DEFAULTS]
result = []
for index, image in enumerate(value[CONF_IMAGES]):
type = image.get(CONF_TYPE, defaults.get(CONF_TYPE))
if type is None:
raise cv.Invalid(
"Type is required either in the image config or in the defaults",
path=[CONF_IMAGES, index],
)
type_class = IMAGE_TYPE[type]
# A default byte order should be simply ignored if the type does not support it
available_options = [*OPTIONS]
if (
not callable(getattr(type_class, "set_big_endian", None))
and CONF_BYTE_ORDER not in image
):
available_options.remove(CONF_BYTE_ORDER)
config = {
**{key: image.get(key, defaults.get(key)) for key in available_options},
**{key.schema: image[key.schema] for key in IMAGE_ID_SCHEMA},
}
validate_settings(config)
result.append(config)
return result
def typed_image_schema(image_type): def typed_image_schema(image_type):
""" """
Construct a schema for a specific image type, allowing transparency options Construct a schema for a specific image type, allowing transparency options
@@ -596,33 +523,10 @@ def typed_image_schema(image_type):
# The config schema can be a (possibly empty) single list of images, # The config schema can be a (possibly empty) single list of images,
# or a dictionary of image types each with a list of images # or a dictionary of image types each with a list of images
# or a dictionary with keys `defaults:` and `images:` CONFIG_SCHEMA = cv.Any(
cv.Schema({cv.Optional(t.lower()): typed_image_schema(t) for t in IMAGE_TYPE}),
cv.ensure_list(IMAGE_SCHEMA),
def _config_schema(config): )
if isinstance(config, list):
return cv.Schema([IMAGE_SCHEMA])(config)
if not isinstance(config, dict):
raise cv.Invalid(
"Badly formed image configuration, expected a list or a dictionary"
)
if CONF_DEFAULTS in config or CONF_IMAGES in config:
return validate_defaults(
cv.Schema(
{
cv.Required(CONF_DEFAULTS): OPTIONS_SCHEMA,
cv.Required(CONF_IMAGES): cv.ensure_list(IMAGE_SCHEMA_NO_DEFAULTS),
}
)(config)
)
if CONF_ID in config or CONF_FILE in config:
return cv.ensure_list(IMAGE_SCHEMA)([config])
return cv.Schema(
{cv.Optional(t.lower()): typed_image_schema(t) for t in IMAGE_TYPE}
)(config)
CONFIG_SCHEMA = _config_schema
async def write_image(config, all_frames=False): async def write_image(config, all_frames=False):
@@ -681,9 +585,6 @@ async def write_image(config, all_frames=False):
total_rows = height * frame_count total_rows = height * frame_count
encoder = IMAGE_TYPE[type](width, total_rows, transparency, dither, invert_alpha) encoder = IMAGE_TYPE[type](width, total_rows, transparency, dither, invert_alpha)
if byte_order := config.get(CONF_BYTE_ORDER):
# Check for valid type has already been done in validate_settings
encoder.set_big_endian(byte_order == "BIG_ENDIAN")
for frame_index in range(frame_count): for frame_index in range(frame_count):
image.seek(frame_index) image.seek(frame_index)
pixels = encoder.convert(image.resize((width, height)), path).getdata() pixels = encoder.convert(image.resize((width, height)), path).getdata()

View File

@@ -1,7 +1,6 @@
from esphome import pins from esphome import pins
import esphome.codegen as cg import esphome.codegen as cg
from esphome.components import display, i2c from esphome.components import display, i2c
from esphome.components.esp32 import CONF_CPU_FREQUENCY
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.const import ( from esphome.const import (
CONF_FULL_UPDATE_EVERY, CONF_FULL_UPDATE_EVERY,
@@ -14,9 +13,7 @@ from esphome.const import (
CONF_PAGES, CONF_PAGES,
CONF_TRANSFORM, CONF_TRANSFORM,
CONF_WAKEUP_PIN, CONF_WAKEUP_PIN,
PLATFORM_ESP32,
) )
import esphome.final_validate as fv
DEPENDENCIES = ["i2c", "esp32"] DEPENDENCIES = ["i2c", "esp32"]
AUTO_LOAD = ["psram"] AUTO_LOAD = ["psram"]
@@ -123,18 +120,6 @@ CONFIG_SCHEMA = cv.All(
) )
def _validate_cpu_frequency(config):
esp32_config = fv.full_config.get()[PLATFORM_ESP32]
if esp32_config[CONF_CPU_FREQUENCY] != "240MHZ":
raise cv.Invalid(
"Inkplate requires 240MHz CPU frequency (set in esp32 component)"
)
return config
FINAL_VALIDATE_SCHEMA = _validate_cpu_frequency
async def to_code(config): async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID]) var = cg.new_Pvariable(config[CONF_ID])

View File

@@ -12,6 +12,6 @@ CONFIG_SCHEMA = cv.All(
@coroutine_with_priority(1.0) @coroutine_with_priority(1.0)
async def to_code(config): async def to_code(config):
cg.add_library("bblanchon/ArduinoJson", "7.4.2") cg.add_library("bblanchon/ArduinoJson", "6.18.5")
cg.add_define("USE_JSON") cg.add_define("USE_JSON")
cg.add_global(json_ns.using) cg.add_global(json_ns.using)

View File

@@ -1,76 +1,83 @@
#include "json_util.h" #include "json_util.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
// ArduinoJson::Allocator is included via ArduinoJson.h in json_util.h
namespace esphome { namespace esphome {
namespace json { namespace json {
static const char *const TAG = "json"; static const char *const TAG = "json";
// Build an allocator for the JSON Library using the RAMAllocator class static std::vector<char> global_json_build_buffer; // NOLINT
struct SpiRamAllocator : ArduinoJson::Allocator { static const auto ALLOCATOR = RAMAllocator<uint8_t>(RAMAllocator<uint8_t>::ALLOC_INTERNAL);
void *allocate(size_t size) override { return this->allocator_.allocate(size); }
void deallocate(void *pointer) override {
// ArduinoJson's Allocator interface doesn't provide the size parameter in deallocate.
// RAMAllocator::deallocate() requires the size, which we don't have access to here.
// RAMAllocator::deallocate implementation just calls free() regardless of whether
// the memory was allocated with heap_caps_malloc or malloc.
// This is safe because ESP-IDF's heap implementation internally tracks the memory region
// and routes free() to the appropriate heap.
free(pointer); // NOLINT(cppcoreguidelines-owning-memory,cppcoreguidelines-no-malloc)
}
void *reallocate(void *ptr, size_t new_size) override {
return this->allocator_.reallocate(static_cast<uint8_t *>(ptr), new_size);
}
protected:
RAMAllocator<uint8_t> allocator_{RAMAllocator<uint8_t>(RAMAllocator<uint8_t>::NONE)};
};
std::string build_json(const json_build_t &f) { std::string build_json(const json_build_t &f) {
// NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson // Here we are allocating up to 5kb of memory,
auto doc_allocator = SpiRamAllocator(); // with the heap size minus 2kb to be safe if less than 5kb
JsonDocument json_document(&doc_allocator); // as we can not have a true dynamic sized document.
if (json_document.overflowed()) { // The excess memory is freed below with `shrinkToFit()`
ESP_LOGE(TAG, "Could not allocate memory for JSON document!"); auto free_heap = ALLOCATOR.get_max_free_block_size();
return "{}"; size_t request_size = std::min(free_heap, (size_t) 512);
while (true) {
ESP_LOGV(TAG, "Attempting to allocate %zu bytes for JSON serialization", request_size);
DynamicJsonDocument json_document(request_size);
if (json_document.capacity() == 0) {
ESP_LOGE(TAG, "Could not allocate memory for document! Requested %zu bytes, largest free heap block: %zu bytes",
request_size, free_heap);
return "{}";
}
JsonObject root = json_document.to<JsonObject>();
f(root);
if (json_document.overflowed()) {
if (request_size == free_heap) {
ESP_LOGE(TAG, "Could not allocate memory for document! Overflowed largest free heap block: %zu bytes",
free_heap);
return "{}";
}
request_size = std::min(request_size * 2, free_heap);
continue;
}
json_document.shrinkToFit();
ESP_LOGV(TAG, "Size after shrink %zu bytes", json_document.capacity());
std::string output;
serializeJson(json_document, output);
return output;
} }
JsonObject root = json_document.to<JsonObject>();
f(root);
if (json_document.overflowed()) {
ESP_LOGE(TAG, "Could not allocate memory for JSON document!");
return "{}";
}
std::string output;
serializeJson(json_document, output);
return output;
// NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks)
} }
bool parse_json(const std::string &data, const json_parse_t &f) { bool parse_json(const std::string &data, const json_parse_t &f) {
// NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson // Here we are allocating 1.5 times the data size,
auto doc_allocator = SpiRamAllocator(); // with the heap size minus 2kb to be safe if less than that
JsonDocument json_document(&doc_allocator); // as we can not have a true dynamic sized document.
if (json_document.overflowed()) { // The excess memory is freed below with `shrinkToFit()`
ESP_LOGE(TAG, "Could not allocate memory for JSON document!"); auto free_heap = ALLOCATOR.get_max_free_block_size();
return false; size_t request_size = std::min(free_heap, (size_t) (data.size() * 1.5));
} while (true) {
DeserializationError err = deserializeJson(json_document, data); DynamicJsonDocument json_document(request_size);
if (json_document.capacity() == 0) {
ESP_LOGE(TAG, "Could not allocate memory for document! Requested %zu bytes, free heap: %zu", request_size,
free_heap);
return false;
}
DeserializationError err = deserializeJson(json_document, data);
json_document.shrinkToFit();
JsonObject root = json_document.as<JsonObject>(); JsonObject root = json_document.as<JsonObject>();
if (err == DeserializationError::Ok) { if (err == DeserializationError::Ok) {
return f(root); return f(root);
} else if (err == DeserializationError::NoMemory) { } else if (err == DeserializationError::NoMemory) {
ESP_LOGE(TAG, "Can not allocate more memory for deserialization. Consider making source string smaller"); if (request_size * 2 >= free_heap) {
return false; ESP_LOGE(TAG, "Can not allocate more memory for deserialization. Consider making source string smaller");
} return false;
ESP_LOGE(TAG, "Parse error: %s", err.c_str()); }
ESP_LOGV(TAG, "Increasing memory allocation.");
request_size *= 2;
continue;
} else {
ESP_LOGE(TAG, "Parse error: %s", err.c_str());
return false;
}
};
return false; return false;
// NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks)
} }
} // namespace json } // namespace json

View File

@@ -14,8 +14,8 @@ from esphome.const import (
from .. import CONF_LD2410_ID, LD2410Component, ld2410_ns from .. import CONF_LD2410_ID, LD2410Component, ld2410_ns
FactoryResetButton = ld2410_ns.class_("FactoryResetButton", button.Button)
QueryButton = ld2410_ns.class_("QueryButton", button.Button) QueryButton = ld2410_ns.class_("QueryButton", button.Button)
ResetButton = ld2410_ns.class_("ResetButton", button.Button)
RestartButton = ld2410_ns.class_("RestartButton", button.Button) RestartButton = ld2410_ns.class_("RestartButton", button.Button)
CONF_QUERY_PARAMS = "query_params" CONF_QUERY_PARAMS = "query_params"
@@ -23,7 +23,7 @@ CONF_QUERY_PARAMS = "query_params"
CONFIG_SCHEMA = { CONFIG_SCHEMA = {
cv.GenerateID(CONF_LD2410_ID): cv.use_id(LD2410Component), cv.GenerateID(CONF_LD2410_ID): cv.use_id(LD2410Component),
cv.Optional(CONF_FACTORY_RESET): button.button_schema( cv.Optional(CONF_FACTORY_RESET): button.button_schema(
FactoryResetButton, ResetButton,
device_class=DEVICE_CLASS_RESTART, device_class=DEVICE_CLASS_RESTART,
entity_category=ENTITY_CATEGORY_CONFIG, entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_RESTART_ALERT, icon=ICON_RESTART_ALERT,
@@ -47,7 +47,7 @@ async def to_code(config):
if factory_reset_config := config.get(CONF_FACTORY_RESET): if factory_reset_config := config.get(CONF_FACTORY_RESET):
b = await button.new_button(factory_reset_config) b = await button.new_button(factory_reset_config)
await cg.register_parented(b, config[CONF_LD2410_ID]) await cg.register_parented(b, config[CONF_LD2410_ID])
cg.add(ld2410_component.set_factory_reset_button(b)) cg.add(ld2410_component.set_reset_button(b))
if restart_config := config.get(CONF_RESTART): if restart_config := config.get(CONF_RESTART):
b = await button.new_button(restart_config) b = await button.new_button(restart_config)
await cg.register_parented(b, config[CONF_LD2410_ID]) await cg.register_parented(b, config[CONF_LD2410_ID])

View File

@@ -1,9 +0,0 @@
#include "factory_reset_button.h"
namespace esphome {
namespace ld2410 {
void FactoryResetButton::press_action() { this->parent_->factory_reset(); }
} // namespace ld2410
} // namespace esphome

View File

@@ -0,0 +1,9 @@
#include "reset_button.h"
namespace esphome {
namespace ld2410 {
void ResetButton::press_action() { this->parent_->factory_reset(); }
} // namespace ld2410
} // namespace esphome

View File

@@ -6,9 +6,9 @@
namespace esphome { namespace esphome {
namespace ld2410 { namespace ld2410 {
class FactoryResetButton : public button::Button, public Parented<LD2410Component> { class ResetButton : public button::Button, public Parented<LD2410Component> {
public: public:
FactoryResetButton() = default; ResetButton() = default;
protected: protected:
void press_action() override; void press_action() override;

View File

@@ -18,10 +18,11 @@ namespace esphome {
namespace ld2410 { namespace ld2410 {
static const char *const TAG = "ld2410"; static const char *const TAG = "ld2410";
static const char *const NO_MAC = "08:05:04:03:02:01";
static const char *const UNKNOWN_MAC = "unknown"; static const char *const UNKNOWN_MAC = "unknown";
static const char *const VERSION_FMT = "%u.%02X.%02X%02X%02X%02X"; static const char *const VERSION_FMT = "%u.%02X.%02X%02X%02X%02X";
enum BaudRate : uint8_t { enum BaudRateStructure : uint8_t {
BAUD_RATE_9600 = 1, BAUD_RATE_9600 = 1,
BAUD_RATE_19200 = 2, BAUD_RATE_19200 = 2,
BAUD_RATE_38400 = 3, BAUD_RATE_38400 = 3,
@@ -32,23 +33,23 @@ enum BaudRate : uint8_t {
BAUD_RATE_460800 = 8, BAUD_RATE_460800 = 8,
}; };
enum DistanceResolution : uint8_t { enum DistanceResolutionStructure : uint8_t {
DISTANCE_RESOLUTION_0_2 = 0x01, DISTANCE_RESOLUTION_0_2 = 0x01,
DISTANCE_RESOLUTION_0_75 = 0x00, DISTANCE_RESOLUTION_0_75 = 0x00,
}; };
enum LightFunction : uint8_t { enum LightFunctionStructure : uint8_t {
LIGHT_FUNCTION_OFF = 0x00, LIGHT_FUNCTION_OFF = 0x00,
LIGHT_FUNCTION_BELOW = 0x01, LIGHT_FUNCTION_BELOW = 0x01,
LIGHT_FUNCTION_ABOVE = 0x02, LIGHT_FUNCTION_ABOVE = 0x02,
}; };
enum OutPinLevel : uint8_t { enum OutPinLevelStructure : uint8_t {
OUT_PIN_LEVEL_LOW = 0x00, OUT_PIN_LEVEL_LOW = 0x00,
OUT_PIN_LEVEL_HIGH = 0x01, OUT_PIN_LEVEL_HIGH = 0x01,
}; };
enum PeriodicData : uint8_t { enum PeriodicDataStructure : uint8_t {
DATA_TYPES = 6, DATA_TYPES = 6,
TARGET_STATES = 8, TARGET_STATES = 8,
MOVING_TARGET_LOW = 9, MOVING_TARGET_LOW = 9,
@@ -66,12 +67,12 @@ enum PeriodicData : uint8_t {
}; };
enum PeriodicDataValue : uint8_t { enum PeriodicDataValue : uint8_t {
HEADER = 0xAA, HEAD = 0xAA,
FOOTER = 0x55, END = 0x55,
CHECK = 0x00, CHECK = 0x00,
}; };
enum AckData : uint8_t { enum AckDataStructure : uint8_t {
COMMAND = 6, COMMAND = 6,
COMMAND_STATUS = 7, COMMAND_STATUS = 7,
}; };
@@ -79,11 +80,11 @@ enum AckData : uint8_t {
// Memory-efficient lookup tables // Memory-efficient lookup tables
struct StringToUint8 { struct StringToUint8 {
const char *str; const char *str;
const uint8_t value; uint8_t value;
}; };
struct Uint8ToString { struct Uint8ToString {
const uint8_t value; uint8_t value;
const char *str; const char *str;
}; };
@@ -143,114 +144,96 @@ template<size_t N> const char *find_str(const Uint8ToString (&arr)[N], uint8_t v
} }
// Commands // Commands
static constexpr uint8_t CMD_ENABLE_CONF = 0xFF; static const uint8_t CMD_ENABLE_CONF = 0xFF;
static constexpr uint8_t CMD_DISABLE_CONF = 0xFE; static const uint8_t CMD_DISABLE_CONF = 0xFE;
static constexpr uint8_t CMD_ENABLE_ENG = 0x62; static const uint8_t CMD_ENABLE_ENG = 0x62;
static constexpr uint8_t CMD_DISABLE_ENG = 0x63; static const uint8_t CMD_DISABLE_ENG = 0x63;
static constexpr uint8_t CMD_MAXDIST_DURATION = 0x60; static const uint8_t CMD_MAXDIST_DURATION = 0x60;
static constexpr uint8_t CMD_QUERY = 0x61; static const uint8_t CMD_QUERY = 0x61;
static constexpr uint8_t CMD_GATE_SENS = 0x64; static const uint8_t CMD_GATE_SENS = 0x64;
static constexpr uint8_t CMD_QUERY_VERSION = 0xA0; static const uint8_t CMD_VERSION = 0xA0;
static constexpr uint8_t CMD_QUERY_DISTANCE_RESOLUTION = 0xAB; static const uint8_t CMD_QUERY_DISTANCE_RESOLUTION = 0xAB;
static constexpr uint8_t CMD_SET_DISTANCE_RESOLUTION = 0xAA; static const uint8_t CMD_SET_DISTANCE_RESOLUTION = 0xAA;
static constexpr uint8_t CMD_QUERY_LIGHT_CONTROL = 0xAE; static const uint8_t CMD_QUERY_LIGHT_CONTROL = 0xAE;
static constexpr uint8_t CMD_SET_LIGHT_CONTROL = 0xAD; static const uint8_t CMD_SET_LIGHT_CONTROL = 0xAD;
static constexpr uint8_t CMD_SET_BAUD_RATE = 0xA1; static const uint8_t CMD_SET_BAUD_RATE = 0xA1;
static constexpr uint8_t CMD_BT_PASSWORD = 0xA9; static const uint8_t CMD_BT_PASSWORD = 0xA9;
static constexpr uint8_t CMD_QUERY_MAC_ADDRESS = 0xA5; static const uint8_t CMD_MAC = 0xA5;
static constexpr uint8_t CMD_RESET = 0xA2; static const uint8_t CMD_RESET = 0xA2;
static constexpr uint8_t CMD_RESTART = 0xA3; static const uint8_t CMD_RESTART = 0xA3;
static constexpr uint8_t CMD_BLUETOOTH = 0xA4; static const uint8_t CMD_BLUETOOTH = 0xA4;
// Commands values // Commands values
static constexpr uint8_t CMD_MAX_MOVE_VALUE = 0x00; static const uint8_t CMD_MAX_MOVE_VALUE = 0x00;
static constexpr uint8_t CMD_MAX_STILL_VALUE = 0x01; static const uint8_t CMD_MAX_STILL_VALUE = 0x01;
static constexpr uint8_t CMD_DURATION_VALUE = 0x02; static const uint8_t CMD_DURATION_VALUE = 0x02;
// Header & Footer size
static constexpr uint8_t HEADER_FOOTER_SIZE = 4;
// Command Header & Footer // Command Header & Footer
static constexpr uint8_t CMD_FRAME_HEADER[HEADER_FOOTER_SIZE] = {0xFD, 0xFC, 0xFB, 0xFA}; static const uint8_t CMD_FRAME_HEADER[4] = {0xFD, 0xFC, 0xFB, 0xFA};
static constexpr uint8_t CMD_FRAME_FOOTER[HEADER_FOOTER_SIZE] = {0x04, 0x03, 0x02, 0x01}; static const uint8_t CMD_FRAME_END[4] = {0x04, 0x03, 0x02, 0x01};
// Data Header & Footer // Data Header & Footer
static constexpr uint8_t DATA_FRAME_HEADER[HEADER_FOOTER_SIZE] = {0xF4, 0xF3, 0xF2, 0xF1}; static const uint8_t DATA_FRAME_HEADER[4] = {0xF4, 0xF3, 0xF2, 0xF1};
static constexpr uint8_t DATA_FRAME_FOOTER[HEADER_FOOTER_SIZE] = {0xF8, 0xF7, 0xF6, 0xF5}; static const uint8_t DATA_FRAME_END[4] = {0xF8, 0xF7, 0xF6, 0xF5};
// MAC address the module uses when Bluetooth is disabled
static constexpr uint8_t NO_MAC[] = {0x08, 0x05, 0x04, 0x03, 0x02, 0x01};
static inline int two_byte_to_int(char firstbyte, char secondbyte) { return (int16_t) (secondbyte << 8) + firstbyte; } static inline int two_byte_to_int(char firstbyte, char secondbyte) { return (int16_t) (secondbyte << 8) + firstbyte; }
static inline bool validate_header_footer(const uint8_t *header_footer, const uint8_t *buffer) {
return std::memcmp(header_footer, buffer, HEADER_FOOTER_SIZE) == 0;
}
void LD2410Component::dump_config() { void LD2410Component::dump_config() {
std::string mac_str = ESP_LOGCONFIG(TAG, "LD2410:");
mac_address_is_valid(this->mac_address_) ? format_mac_address_pretty(this->mac_address_) : UNKNOWN_MAC;
std::string version = str_sprintf(VERSION_FMT, this->version_[1], this->version_[0], this->version_[5],
this->version_[4], this->version_[3], this->version_[2]);
ESP_LOGCONFIG(TAG,
"LD2410:\n"
" Firmware version: %s\n"
" MAC address: %s\n"
" Throttle: %u ms",
version.c_str(), mac_str.c_str(), this->throttle_);
#ifdef USE_BINARY_SENSOR #ifdef USE_BINARY_SENSOR
ESP_LOGCONFIG(TAG, "Binary Sensors:"); LOG_BINARY_SENSOR(" ", "TargetBinarySensor", this->target_binary_sensor_);
LOG_BINARY_SENSOR(" ", "Target", this->target_binary_sensor_); LOG_BINARY_SENSOR(" ", "MovingTargetBinarySensor", this->moving_target_binary_sensor_);
LOG_BINARY_SENSOR(" ", "MovingTarget", this->moving_target_binary_sensor_); LOG_BINARY_SENSOR(" ", "StillTargetBinarySensor", this->still_target_binary_sensor_);
LOG_BINARY_SENSOR(" ", "StillTarget", this->still_target_binary_sensor_); LOG_BINARY_SENSOR(" ", "OutPinPresenceStatusBinarySensor", this->out_pin_presence_status_binary_sensor_);
LOG_BINARY_SENSOR(" ", "OutPinPresenceStatus", this->out_pin_presence_status_binary_sensor_); #endif
#ifdef USE_SWITCH
LOG_SWITCH(" ", "EngineeringModeSwitch", this->engineering_mode_switch_);
LOG_SWITCH(" ", "BluetoothSwitch", this->bluetooth_switch_);
#endif
#ifdef USE_BUTTON
LOG_BUTTON(" ", "ResetButton", this->reset_button_);
LOG_BUTTON(" ", "RestartButton", this->restart_button_);
LOG_BUTTON(" ", "QueryButton", this->query_button_);
#endif #endif
#ifdef USE_SENSOR #ifdef USE_SENSOR
ESP_LOGCONFIG(TAG, "Sensors:"); LOG_SENSOR(" ", "LightSensor", this->light_sensor_);
LOG_SENSOR(" ", "Light", this->light_sensor_); LOG_SENSOR(" ", "MovingTargetDistanceSensor", this->moving_target_distance_sensor_);
LOG_SENSOR(" ", "DetectionDistance", this->detection_distance_sensor_); LOG_SENSOR(" ", "StillTargetDistanceSensor", this->still_target_distance_sensor_);
LOG_SENSOR(" ", "MovingTargetDistance", this->moving_target_distance_sensor_); LOG_SENSOR(" ", "MovingTargetEnergySensor", this->moving_target_energy_sensor_);
LOG_SENSOR(" ", "MovingTargetEnergy", this->moving_target_energy_sensor_); LOG_SENSOR(" ", "StillTargetEnergySensor", this->still_target_energy_sensor_);
LOG_SENSOR(" ", "StillTargetDistance", this->still_target_distance_sensor_); LOG_SENSOR(" ", "DetectionDistanceSensor", this->detection_distance_sensor_);
LOG_SENSOR(" ", "StillTargetEnergy", this->still_target_energy_sensor_);
for (sensor::Sensor *s : this->gate_move_sensors_) {
LOG_SENSOR(" ", "GateMove", s);
}
for (sensor::Sensor *s : this->gate_still_sensors_) { for (sensor::Sensor *s : this->gate_still_sensors_) {
LOG_SENSOR(" ", "GateStill", s); LOG_SENSOR(" ", "NthGateStillSesnsor", s);
}
for (sensor::Sensor *s : this->gate_move_sensors_) {
LOG_SENSOR(" ", "NthGateMoveSesnsor", s);
} }
#endif #endif
#ifdef USE_TEXT_SENSOR #ifdef USE_TEXT_SENSOR
ESP_LOGCONFIG(TAG, "Text Sensors:"); LOG_TEXT_SENSOR(" ", "VersionTextSensor", this->version_text_sensor_);
LOG_TEXT_SENSOR(" ", "Mac", this->mac_text_sensor_); LOG_TEXT_SENSOR(" ", "MacTextSensor", this->mac_text_sensor_);
LOG_TEXT_SENSOR(" ", "Version", this->version_text_sensor_);
#endif
#ifdef USE_NUMBER
ESP_LOGCONFIG(TAG, "Numbers:");
LOG_NUMBER(" ", "LightThreshold", this->light_threshold_number_);
LOG_NUMBER(" ", "MaxMoveDistanceGate", this->max_move_distance_gate_number_);
LOG_NUMBER(" ", "MaxStillDistanceGate", this->max_still_distance_gate_number_);
LOG_NUMBER(" ", "Timeout", this->timeout_number_);
for (number::Number *n : this->gate_move_threshold_numbers_) {
LOG_NUMBER(" ", "MoveThreshold", n);
}
for (number::Number *n : this->gate_still_threshold_numbers_) {
LOG_NUMBER(" ", "StillThreshold", n);
}
#endif #endif
#ifdef USE_SELECT #ifdef USE_SELECT
ESP_LOGCONFIG(TAG, "Selects:"); LOG_SELECT(" ", "LightFunctionSelect", this->light_function_select_);
LOG_SELECT(" ", "BaudRate", this->baud_rate_select_); LOG_SELECT(" ", "OutPinLevelSelect", this->out_pin_level_select_);
LOG_SELECT(" ", "DistanceResolution", this->distance_resolution_select_); LOG_SELECT(" ", "DistanceResolutionSelect", this->distance_resolution_select_);
LOG_SELECT(" ", "LightFunction", this->light_function_select_); LOG_SELECT(" ", "BaudRateSelect", this->baud_rate_select_);
LOG_SELECT(" ", "OutPinLevel", this->out_pin_level_select_);
#endif #endif
#ifdef USE_SWITCH #ifdef USE_NUMBER
ESP_LOGCONFIG(TAG, "Switches:"); LOG_NUMBER(" ", "LightThresholdNumber", this->light_threshold_number_);
LOG_SWITCH(" ", "Bluetooth", this->bluetooth_switch_); LOG_NUMBER(" ", "MaxStillDistanceGateNumber", this->max_still_distance_gate_number_);
LOG_SWITCH(" ", "EngineeringMode", this->engineering_mode_switch_); LOG_NUMBER(" ", "MaxMoveDistanceGateNumber", this->max_move_distance_gate_number_);
#endif LOG_NUMBER(" ", "TimeoutNumber", this->timeout_number_);
#ifdef USE_BUTTON for (number::Number *n : this->gate_still_threshold_numbers_) {
ESP_LOGCONFIG(TAG, "Buttons:"); LOG_NUMBER(" ", "Still Thresholds Number", n);
LOG_BUTTON(" ", "FactoryReset", this->factory_reset_button_); }
LOG_BUTTON(" ", "Query", this->query_button_); for (number::Number *n : this->gate_move_threshold_numbers_) {
LOG_BUTTON(" ", "Restart", this->restart_button_); LOG_NUMBER(" ", "Move Thresholds Number", n);
}
#endif #endif
this->read_all_info();
ESP_LOGCONFIG(TAG,
" Throttle: %ums\n"
" MAC address: %s\n"
" Firmware version: %s",
this->throttle_, this->mac_ == NO_MAC ? UNKNOWN_MAC : this->mac_.c_str(), this->version_.c_str());
} }
void LD2410Component::setup() { void LD2410Component::setup() {
@@ -263,12 +246,12 @@ void LD2410Component::read_all_info() {
this->get_version_(); this->get_version_();
this->get_mac_(); this->get_mac_();
this->get_distance_resolution_(); this->get_distance_resolution_();
this->query_light_control_(); this->get_light_control_();
this->query_parameters_(); this->query_parameters_();
this->set_config_mode_(false); this->set_config_mode_(false);
#ifdef USE_SELECT #ifdef USE_SELECT
const auto baud_rate = std::to_string(this->parent_->get_baud_rate()); const auto baud_rate = std::to_string(this->parent_->get_baud_rate());
if (this->baud_rate_select_ != nullptr) { if (this->baud_rate_select_ != nullptr && this->baud_rate_select_->state != baud_rate) {
this->baud_rate_select_->publish_state(baud_rate); this->baud_rate_select_->publish_state(baud_rate);
} }
#endif #endif
@@ -281,57 +264,66 @@ void LD2410Component::restart_and_read_all_info() {
} }
void LD2410Component::loop() { void LD2410Component::loop() {
while (this->available()) { const int max_line_length = 80;
this->readline_(this->read()); static uint8_t buffer[max_line_length];
while (available()) {
this->readline_(read(), buffer, max_line_length);
} }
} }
void LD2410Component::send_command_(uint8_t command, const uint8_t *command_value, uint8_t command_value_len) { void LD2410Component::send_command_(uint8_t command, const uint8_t *command_value, int command_value_len) {
ESP_LOGV(TAG, "Sending COMMAND %02X", command); ESP_LOGV(TAG, "Sending COMMAND %02X", command);
// frame header bytes // frame start bytes
this->write_array(CMD_FRAME_HEADER, sizeof(CMD_FRAME_HEADER)); this->write_array(CMD_FRAME_HEADER, 4);
// length bytes // length bytes
uint8_t len = 2; int len = 2;
if (command_value != nullptr) { if (command_value != nullptr)
len += command_value_len; len += command_value_len;
} this->write_byte(lowbyte(len));
// 2 length bytes (low, high) + 2 command bytes (low, high) this->write_byte(highbyte(len));
uint8_t len_cmd[] = {len, 0x00, command, 0x00};
this->write_array(len_cmd, sizeof(len_cmd)); // command
this->write_byte(lowbyte(command));
this->write_byte(highbyte(command));
// command value bytes // command value bytes
if (command_value != nullptr) { if (command_value != nullptr) {
this->write_array(command_value, command_value_len); for (int i = 0; i < command_value_len; i++) {
this->write_byte(command_value[i]);
}
} }
// frame footer bytes // frame end bytes
this->write_array(CMD_FRAME_FOOTER, sizeof(CMD_FRAME_FOOTER)); this->write_array(CMD_FRAME_END, 4);
// FIXME to remove // FIXME to remove
delay(50); // NOLINT delay(50); // NOLINT
} }
void LD2410Component::handle_periodic_data_() { void LD2410Component::handle_periodic_data_(uint8_t *buffer, int len) {
// Reduce data update rate to reduce home assistant database growth if (len < 12)
// Check this first to prevent unnecessary processing done in later checks/parsing return; // 4 frame start bytes + 2 length bytes + 1 data end byte + 1 crc byte + 4 frame end bytes
if (App.get_loop_component_start_time() - this->last_periodic_millis_ < this->throttle_) { if (buffer[0] != 0xF4 || buffer[1] != 0xF3 || buffer[2] != 0xF2 || buffer[3] != 0xF1) // check 4 frame start bytes
return; return;
} if (buffer[7] != HEAD || buffer[len - 6] != END || buffer[len - 5] != CHECK) // Check constant values
// 4 frame header bytes + 2 length bytes + 1 data end byte + 1 crc byte + 4 frame footer bytes return; // data head=0xAA, data end=0x55, crc=0x00
// data header=0xAA, data footer=0x55, crc=0x00
if (this->buffer_pos_ < 12 || !ld2410::validate_header_footer(DATA_FRAME_HEADER, this->buffer_data_) || /*
this->buffer_data_[7] != HEADER || this->buffer_data_[this->buffer_pos_ - 6] != FOOTER || Reduce data update rate to prevent home assistant database size grow fast
this->buffer_data_[this->buffer_pos_ - 5] != CHECK) { */
int32_t current_millis = App.get_loop_component_start_time();
if (current_millis - last_periodic_millis_ < this->throttle_)
return; return;
} last_periodic_millis_ = current_millis;
// Save the timestamp after validating the frame so, if invalid, we'll take the next frame immediately
this->last_periodic_millis_ = App.get_loop_component_start_time();
/* /*
Data Type: 7th Data Type: 7th
0x01: Engineering mode 0x01: Engineering mode
0x02: Normal mode 0x02: Normal mode
*/ */
bool engineering_mode = this->buffer_data_[DATA_TYPES] == 0x01; bool engineering_mode = buffer[DATA_TYPES] == 0x01;
#ifdef USE_SWITCH #ifdef USE_SWITCH
if (this->engineering_mode_switch_ != nullptr) { if (this->engineering_mode_switch_ != nullptr &&
current_millis - last_engineering_mode_change_millis_ > this->throttle_) {
this->engineering_mode_switch_->publish_state(engineering_mode); this->engineering_mode_switch_->publish_state(engineering_mode);
} }
#endif #endif
@@ -343,7 +335,7 @@ void LD2410Component::handle_periodic_data_() {
0x02 = Still targets 0x02 = Still targets
0x03 = Moving+Still targets 0x03 = Moving+Still targets
*/ */
char target_state = this->buffer_data_[TARGET_STATES]; char target_state = buffer[TARGET_STATES];
if (this->target_binary_sensor_ != nullptr) { if (this->target_binary_sensor_ != nullptr) {
this->target_binary_sensor_->publish_state(target_state != 0x00); this->target_binary_sensor_->publish_state(target_state != 0x00);
} }
@@ -363,30 +355,27 @@ void LD2410Component::handle_periodic_data_() {
*/ */
#ifdef USE_SENSOR #ifdef USE_SENSOR
if (this->moving_target_distance_sensor_ != nullptr) { if (this->moving_target_distance_sensor_ != nullptr) {
int new_moving_target_distance = int new_moving_target_distance = ld2410::two_byte_to_int(buffer[MOVING_TARGET_LOW], buffer[MOVING_TARGET_HIGH]);
ld2410::two_byte_to_int(this->buffer_data_[MOVING_TARGET_LOW], this->buffer_data_[MOVING_TARGET_HIGH]);
if (this->moving_target_distance_sensor_->get_state() != new_moving_target_distance) if (this->moving_target_distance_sensor_->get_state() != new_moving_target_distance)
this->moving_target_distance_sensor_->publish_state(new_moving_target_distance); this->moving_target_distance_sensor_->publish_state(new_moving_target_distance);
} }
if (this->moving_target_energy_sensor_ != nullptr) { if (this->moving_target_energy_sensor_ != nullptr) {
int new_moving_target_energy = this->buffer_data_[MOVING_ENERGY]; int new_moving_target_energy = buffer[MOVING_ENERGY];
if (this->moving_target_energy_sensor_->get_state() != new_moving_target_energy) if (this->moving_target_energy_sensor_->get_state() != new_moving_target_energy)
this->moving_target_energy_sensor_->publish_state(new_moving_target_energy); this->moving_target_energy_sensor_->publish_state(new_moving_target_energy);
} }
if (this->still_target_distance_sensor_ != nullptr) { if (this->still_target_distance_sensor_ != nullptr) {
int new_still_target_distance = int new_still_target_distance = ld2410::two_byte_to_int(buffer[STILL_TARGET_LOW], buffer[STILL_TARGET_HIGH]);
ld2410::two_byte_to_int(this->buffer_data_[STILL_TARGET_LOW], this->buffer_data_[STILL_TARGET_HIGH]);
if (this->still_target_distance_sensor_->get_state() != new_still_target_distance) if (this->still_target_distance_sensor_->get_state() != new_still_target_distance)
this->still_target_distance_sensor_->publish_state(new_still_target_distance); this->still_target_distance_sensor_->publish_state(new_still_target_distance);
} }
if (this->still_target_energy_sensor_ != nullptr) { if (this->still_target_energy_sensor_ != nullptr) {
int new_still_target_energy = this->buffer_data_[STILL_ENERGY]; int new_still_target_energy = buffer[STILL_ENERGY];
if (this->still_target_energy_sensor_->get_state() != new_still_target_energy) if (this->still_target_energy_sensor_->get_state() != new_still_target_energy)
this->still_target_energy_sensor_->publish_state(new_still_target_energy); this->still_target_energy_sensor_->publish_state(new_still_target_energy);
} }
if (this->detection_distance_sensor_ != nullptr) { if (this->detection_distance_sensor_ != nullptr) {
int new_detect_distance = int new_detect_distance = ld2410::two_byte_to_int(buffer[DETECT_DISTANCE_LOW], buffer[DETECT_DISTANCE_HIGH]);
ld2410::two_byte_to_int(this->buffer_data_[DETECT_DISTANCE_LOW], this->buffer_data_[DETECT_DISTANCE_HIGH]);
if (this->detection_distance_sensor_->get_state() != new_detect_distance) if (this->detection_distance_sensor_->get_state() != new_detect_distance)
this->detection_distance_sensor_->publish_state(new_detect_distance); this->detection_distance_sensor_->publish_state(new_detect_distance);
} }
@@ -394,12 +383,12 @@ void LD2410Component::handle_periodic_data_() {
/* /*
Moving distance range: 18th byte Moving distance range: 18th byte
Still distance range: 19th byte Still distance range: 19th byte
Moving energy: 20~28th bytes Moving enery: 20~28th bytes
*/ */
for (std::vector<sensor::Sensor *>::size_type i = 0; i != this->gate_move_sensors_.size(); i++) { for (std::vector<sensor::Sensor *>::size_type i = 0; i != this->gate_move_sensors_.size(); i++) {
sensor::Sensor *s = this->gate_move_sensors_[i]; sensor::Sensor *s = this->gate_move_sensors_[i];
if (s != nullptr) { if (s != nullptr) {
s->publish_state(this->buffer_data_[MOVING_SENSOR_START + i]); s->publish_state(buffer[MOVING_SENSOR_START + i]);
} }
} }
/* /*
@@ -408,17 +397,16 @@ void LD2410Component::handle_periodic_data_() {
for (std::vector<sensor::Sensor *>::size_type i = 0; i != this->gate_still_sensors_.size(); i++) { for (std::vector<sensor::Sensor *>::size_type i = 0; i != this->gate_still_sensors_.size(); i++) {
sensor::Sensor *s = this->gate_still_sensors_[i]; sensor::Sensor *s = this->gate_still_sensors_[i];
if (s != nullptr) { if (s != nullptr) {
s->publish_state(this->buffer_data_[STILL_SENSOR_START + i]); s->publish_state(buffer[STILL_SENSOR_START + i]);
} }
} }
/* /*
Light sensor: 38th bytes Light sensor: 38th bytes
*/ */
if (this->light_sensor_ != nullptr) { if (this->light_sensor_ != nullptr) {
int new_light_sensor = this->buffer_data_[LIGHT_SENSOR]; int new_light_sensor = buffer[LIGHT_SENSOR];
if (this->light_sensor_->get_state() != new_light_sensor) { if (this->light_sensor_->get_state() != new_light_sensor)
this->light_sensor_->publish_state(new_light_sensor); this->light_sensor_->publish_state(new_light_sensor);
}
} }
} else { } else {
for (auto *s : this->gate_move_sensors_) { for (auto *s : this->gate_move_sensors_) {
@@ -439,7 +427,7 @@ void LD2410Component::handle_periodic_data_() {
#ifdef USE_BINARY_SENSOR #ifdef USE_BINARY_SENSOR
if (engineering_mode) { if (engineering_mode) {
if (this->out_pin_presence_status_binary_sensor_ != nullptr) { if (this->out_pin_presence_status_binary_sensor_ != nullptr) {
this->out_pin_presence_status_binary_sensor_->publish_state(this->buffer_data_[OUT_PIN_SENSOR] == 0x01); this->out_pin_presence_status_binary_sensor_->publish_state(buffer[OUT_PIN_SENSOR] == 0x01);
} }
} else { } else {
if (this->out_pin_presence_status_binary_sensor_ != nullptr) { if (this->out_pin_presence_status_binary_sensor_ != nullptr) {
@@ -451,149 +439,127 @@ void LD2410Component::handle_periodic_data_() {
#ifdef USE_NUMBER #ifdef USE_NUMBER
std::function<void(void)> set_number_value(number::Number *n, float value) { std::function<void(void)> set_number_value(number::Number *n, float value) {
if (n != nullptr && (!n->has_state() || n->state != value)) { float normalized_value = value * 1.0;
n->state = value; if (n != nullptr && (!n->has_state() || n->state != normalized_value)) {
return [n, value]() { n->publish_state(value); }; n->state = normalized_value;
return [n, normalized_value]() { n->publish_state(normalized_value); };
} }
return []() {}; return []() {};
} }
#endif #endif
bool LD2410Component::handle_ack_data_() { bool LD2410Component::handle_ack_data_(uint8_t *buffer, int len) {
ESP_LOGV(TAG, "Handling ACK DATA for COMMAND %02X", this->buffer_data_[COMMAND]); ESP_LOGV(TAG, "Handling ACK DATA for COMMAND %02X", buffer[COMMAND]);
if (this->buffer_pos_ < 10) { if (len < 10) {
ESP_LOGE(TAG, "Invalid length"); ESP_LOGE(TAG, "Invalid length");
return true; return true;
} }
if (!ld2410::validate_header_footer(CMD_FRAME_HEADER, this->buffer_data_)) { if (buffer[0] != 0xFD || buffer[1] != 0xFC || buffer[2] != 0xFB || buffer[3] != 0xFA) { // check 4 frame start bytes
ESP_LOGW(TAG, "Invalid header: %s", format_hex_pretty(this->buffer_data_, HEADER_FOOTER_SIZE).c_str()); ESP_LOGE(TAG, "Invalid header");
return true; return true;
} }
if (this->buffer_data_[COMMAND_STATUS] != 0x01) { if (buffer[COMMAND_STATUS] != 0x01) {
ESP_LOGE(TAG, "Invalid status"); ESP_LOGE(TAG, "Invalid status");
return true; return true;
} }
if (this->buffer_data_[8] || this->buffer_data_[9]) { if (ld2410::two_byte_to_int(buffer[8], buffer[9]) != 0x00) {
ESP_LOGW(TAG, "Invalid command: %02X, %02X", this->buffer_data_[8], this->buffer_data_[9]); ESP_LOGE(TAG, "Invalid command: %u, %u", buffer[8], buffer[9]);
return true; return true;
} }
switch (this->buffer_data_[COMMAND]) { switch (buffer[COMMAND]) {
case CMD_ENABLE_CONF: case lowbyte(CMD_ENABLE_CONF):
ESP_LOGV(TAG, "Enable conf"); ESP_LOGV(TAG, "Enable conf");
break; break;
case lowbyte(CMD_DISABLE_CONF):
case CMD_DISABLE_CONF:
ESP_LOGV(TAG, "Disabled conf"); ESP_LOGV(TAG, "Disabled conf");
break; break;
case lowbyte(CMD_SET_BAUD_RATE):
case CMD_SET_BAUD_RATE:
ESP_LOGV(TAG, "Baud rate change"); ESP_LOGV(TAG, "Baud rate change");
#ifdef USE_SELECT #ifdef USE_SELECT
if (this->baud_rate_select_ != nullptr) { if (this->baud_rate_select_ != nullptr) {
ESP_LOGE(TAG, "Change baud rate to %s and reinstall", this->baud_rate_select_->state.c_str()); ESP_LOGE(TAG, "Configure baud rate to %s and reinstall", this->baud_rate_select_->state.c_str());
} }
#endif #endif
break; break;
case lowbyte(CMD_VERSION):
case CMD_QUERY_VERSION: { this->version_ = str_sprintf(VERSION_FMT, buffer[13], buffer[12], buffer[17], buffer[16], buffer[15], buffer[14]);
std::memcpy(this->version_, &this->buffer_data_[12], sizeof(this->version_)); ESP_LOGV(TAG, "Firmware version: %s", this->version_.c_str());
std::string version = str_sprintf(VERSION_FMT, this->version_[1], this->version_[0], this->version_[5],
this->version_[4], this->version_[3], this->version_[2]);
ESP_LOGV(TAG, "Firmware version: %s", version.c_str());
#ifdef USE_TEXT_SENSOR #ifdef USE_TEXT_SENSOR
if (this->version_text_sensor_ != nullptr) { if (this->version_text_sensor_ != nullptr) {
this->version_text_sensor_->publish_state(version); this->version_text_sensor_->publish_state(this->version_);
} }
#endif #endif
break; break;
} case lowbyte(CMD_QUERY_DISTANCE_RESOLUTION): {
std::string distance_resolution =
case CMD_QUERY_DISTANCE_RESOLUTION: { find_str(DISTANCE_RESOLUTIONS_BY_UINT, ld2410::two_byte_to_int(buffer[10], buffer[11]));
const auto *distance_resolution = find_str(DISTANCE_RESOLUTIONS_BY_UINT, this->buffer_data_[10]); ESP_LOGV(TAG, "Distance resolution: %s", distance_resolution.c_str());
ESP_LOGV(TAG, "Distance resolution: %s", distance_resolution);
#ifdef USE_SELECT #ifdef USE_SELECT
if (this->distance_resolution_select_ != nullptr) { if (this->distance_resolution_select_ != nullptr &&
this->distance_resolution_select_->state != distance_resolution) {
this->distance_resolution_select_->publish_state(distance_resolution); this->distance_resolution_select_->publish_state(distance_resolution);
} }
#endif #endif
break; } break;
} case lowbyte(CMD_QUERY_LIGHT_CONTROL): {
this->light_function_ = find_str(LIGHT_FUNCTIONS_BY_UINT, buffer[10]);
case CMD_QUERY_LIGHT_CONTROL: { this->light_threshold_ = buffer[11] * 1.0;
this->light_function_ = this->buffer_data_[10]; this->out_pin_level_ = find_str(OUT_PIN_LEVELS_BY_UINT, buffer[12]);
this->light_threshold_ = this->buffer_data_[11]; ESP_LOGV(TAG, "Light function: %s", const_cast<char *>(this->light_function_.c_str()));
this->out_pin_level_ = this->buffer_data_[12]; ESP_LOGV(TAG, "Light threshold: %f", this->light_threshold_);
const auto *light_function_str = find_str(LIGHT_FUNCTIONS_BY_UINT, this->light_function_); ESP_LOGV(TAG, "Out pin level: %s", const_cast<char *>(this->out_pin_level_.c_str()));
const auto *out_pin_level_str = find_str(OUT_PIN_LEVELS_BY_UINT, this->out_pin_level_);
ESP_LOGV(TAG,
"Light function: %s\n"
"Light threshold: %u\n"
"Out pin level: %s",
light_function_str, this->light_threshold_, out_pin_level_str);
#ifdef USE_SELECT #ifdef USE_SELECT
if (this->light_function_select_ != nullptr) { if (this->light_function_select_ != nullptr && this->light_function_select_->state != this->light_function_) {
this->light_function_select_->publish_state(light_function_str); this->light_function_select_->publish_state(this->light_function_);
} }
if (this->out_pin_level_select_ != nullptr) { if (this->out_pin_level_select_ != nullptr && this->out_pin_level_select_->state != this->out_pin_level_) {
this->out_pin_level_select_->publish_state(out_pin_level_str); this->out_pin_level_select_->publish_state(this->out_pin_level_);
} }
#endif #endif
#ifdef USE_NUMBER #ifdef USE_NUMBER
if (this->light_threshold_number_ != nullptr) { if (this->light_threshold_number_ != nullptr &&
this->light_threshold_number_->publish_state(static_cast<float>(this->light_threshold_)); (!this->light_threshold_number_->has_state() ||
this->light_threshold_number_->state != this->light_threshold_)) {
this->light_threshold_number_->publish_state(this->light_threshold_);
} }
#endif #endif
break; } break;
} case lowbyte(CMD_MAC):
case CMD_QUERY_MAC_ADDRESS: { if (len < 20) {
if (this->buffer_pos_ < 20) {
return false; return false;
} }
this->mac_ = format_mac_address_pretty(&buffer[10]);
this->bluetooth_on_ = std::memcmp(&this->buffer_data_[10], NO_MAC, sizeof(NO_MAC)) != 0; ESP_LOGV(TAG, "MAC address: %s", this->mac_.c_str());
if (this->bluetooth_on_) {
std::memcpy(this->mac_address_, &this->buffer_data_[10], sizeof(this->mac_address_));
}
std::string mac_str =
mac_address_is_valid(this->mac_address_) ? format_mac_address_pretty(this->mac_address_) : UNKNOWN_MAC;
ESP_LOGV(TAG, "MAC address: %s", mac_str.c_str());
#ifdef USE_TEXT_SENSOR #ifdef USE_TEXT_SENSOR
if (this->mac_text_sensor_ != nullptr) { if (this->mac_text_sensor_ != nullptr) {
this->mac_text_sensor_->publish_state(mac_str); this->mac_text_sensor_->publish_state(this->mac_ == NO_MAC ? UNKNOWN_MAC : this->mac_);
} }
#endif #endif
#ifdef USE_SWITCH #ifdef USE_SWITCH
if (this->bluetooth_switch_ != nullptr) { if (this->bluetooth_switch_ != nullptr) {
this->bluetooth_switch_->publish_state(this->bluetooth_on_); this->bluetooth_switch_->publish_state(this->mac_ != NO_MAC);
} }
#endif #endif
break; break;
} case lowbyte(CMD_GATE_SENS):
case CMD_GATE_SENS:
ESP_LOGV(TAG, "Sensitivity"); ESP_LOGV(TAG, "Sensitivity");
break; break;
case lowbyte(CMD_BLUETOOTH):
case CMD_BLUETOOTH:
ESP_LOGV(TAG, "Bluetooth"); ESP_LOGV(TAG, "Bluetooth");
break; break;
case lowbyte(CMD_SET_DISTANCE_RESOLUTION):
case CMD_SET_DISTANCE_RESOLUTION:
ESP_LOGV(TAG, "Set distance resolution"); ESP_LOGV(TAG, "Set distance resolution");
break; break;
case lowbyte(CMD_SET_LIGHT_CONTROL):
case CMD_SET_LIGHT_CONTROL:
ESP_LOGV(TAG, "Set light control"); ESP_LOGV(TAG, "Set light control");
break; break;
case lowbyte(CMD_BT_PASSWORD):
case CMD_BT_PASSWORD:
ESP_LOGV(TAG, "Set bluetooth password"); ESP_LOGV(TAG, "Set bluetooth password");
break; break;
case lowbyte(CMD_QUERY): // Query parameters response
case CMD_QUERY: { // Query parameters response {
if (this->buffer_data_[10] != HEADER) if (buffer[10] != 0xAA)
return true; // value head=0xAA return true; // value head=0xAA
#ifdef USE_NUMBER #ifdef USE_NUMBER
/* /*
@@ -601,31 +567,29 @@ bool LD2410Component::handle_ack_data_() {
Still distance range: 14th byte Still distance range: 14th byte
*/ */
std::vector<std::function<void(void)>> updates; std::vector<std::function<void(void)>> updates;
updates.push_back(set_number_value(this->max_move_distance_gate_number_, this->buffer_data_[12])); updates.push_back(set_number_value(this->max_move_distance_gate_number_, buffer[12]));
updates.push_back(set_number_value(this->max_still_distance_gate_number_, this->buffer_data_[13])); updates.push_back(set_number_value(this->max_still_distance_gate_number_, buffer[13]));
/* /*
Moving Sensitivities: 15~23th bytes Moving Sensitivities: 15~23th bytes
*/ */
for (std::vector<number::Number *>::size_type i = 0; i != this->gate_move_threshold_numbers_.size(); i++) { for (std::vector<number::Number *>::size_type i = 0; i != this->gate_move_threshold_numbers_.size(); i++) {
updates.push_back(set_number_value(this->gate_move_threshold_numbers_[i], this->buffer_data_[14 + i])); updates.push_back(set_number_value(this->gate_move_threshold_numbers_[i], buffer[14 + i]));
} }
/* /*
Still Sensitivities: 24~32th bytes Still Sensitivities: 24~32th bytes
*/ */
for (std::vector<number::Number *>::size_type i = 0; i != this->gate_still_threshold_numbers_.size(); i++) { for (std::vector<number::Number *>::size_type i = 0; i != this->gate_still_threshold_numbers_.size(); i++) {
updates.push_back(set_number_value(this->gate_still_threshold_numbers_[i], this->buffer_data_[23 + i])); updates.push_back(set_number_value(this->gate_still_threshold_numbers_[i], buffer[23 + i]));
} }
/* /*
None Duration: 33~34th bytes None Duration: 33~34th bytes
*/ */
updates.push_back(set_number_value(this->timeout_number_, updates.push_back(set_number_value(this->timeout_number_, ld2410::two_byte_to_int(buffer[32], buffer[33])));
ld2410::two_byte_to_int(this->buffer_data_[32], this->buffer_data_[33])));
for (auto &update : updates) { for (auto &update : updates) {
update(); update();
} }
#endif #endif
break; } break;
}
default: default:
break; break;
} }
@@ -633,60 +597,59 @@ bool LD2410Component::handle_ack_data_() {
return true; return true;
} }
void LD2410Component::readline_(int readch) { void LD2410Component::readline_(int readch, uint8_t *buffer, int len) {
if (readch < 0) { static int pos = 0;
return; // No data available
}
if (this->buffer_pos_ < MAX_LINE_LENGTH - 1) { if (readch >= 0) {
this->buffer_data_[this->buffer_pos_++] = readch; if (pos < len - 1) {
this->buffer_data_[this->buffer_pos_] = 0; buffer[pos++] = readch;
} else { buffer[pos] = 0;
// We should never get here, but just in case...
ESP_LOGW(TAG, "Max command length exceeded; ignoring");
this->buffer_pos_ = 0;
}
if (this->buffer_pos_ < 4) {
return; // Not enough data to process yet
}
if (ld2410::validate_header_footer(DATA_FRAME_FOOTER, &this->buffer_data_[this->buffer_pos_ - 4])) {
ESP_LOGV(TAG, "Handling Periodic Data: %s", format_hex_pretty(this->buffer_data_, this->buffer_pos_).c_str());
this->handle_periodic_data_();
this->buffer_pos_ = 0; // Reset position index for next message
} else if (ld2410::validate_header_footer(CMD_FRAME_FOOTER, &this->buffer_data_[this->buffer_pos_ - 4])) {
ESP_LOGV(TAG, "Handling Ack Data: %s", format_hex_pretty(this->buffer_data_, this->buffer_pos_).c_str());
if (this->handle_ack_data_()) {
this->buffer_pos_ = 0; // Reset position index for next message
} else { } else {
ESP_LOGV(TAG, "Ack Data incomplete"); pos = 0;
}
if (pos >= 4) {
if (buffer[pos - 4] == 0xF8 && buffer[pos - 3] == 0xF7 && buffer[pos - 2] == 0xF6 && buffer[pos - 1] == 0xF5) {
ESP_LOGV(TAG, "Will handle Periodic Data");
this->handle_periodic_data_(buffer, pos);
pos = 0; // Reset position index ready for next time
} else if (buffer[pos - 4] == 0x04 && buffer[pos - 3] == 0x03 && buffer[pos - 2] == 0x02 &&
buffer[pos - 1] == 0x01) {
ESP_LOGV(TAG, "Will handle ACK Data");
if (this->handle_ack_data_(buffer, pos)) {
pos = 0; // Reset position index ready for next time
} else {
ESP_LOGV(TAG, "ACK Data incomplete");
}
}
} }
} }
} }
void LD2410Component::set_config_mode_(bool enable) { void LD2410Component::set_config_mode_(bool enable) {
const uint8_t cmd = enable ? CMD_ENABLE_CONF : CMD_DISABLE_CONF; uint8_t cmd = enable ? CMD_ENABLE_CONF : CMD_DISABLE_CONF;
const uint8_t cmd_value[2] = {0x01, 0x00}; uint8_t cmd_value[2] = {0x01, 0x00};
this->send_command_(cmd, enable ? cmd_value : nullptr, sizeof(cmd_value)); this->send_command_(cmd, enable ? cmd_value : nullptr, 2);
} }
void LD2410Component::set_bluetooth(bool enable) { void LD2410Component::set_bluetooth(bool enable) {
this->set_config_mode_(true); this->set_config_mode_(true);
const uint8_t cmd_value[2] = {enable ? (uint8_t) 0x01 : (uint8_t) 0x00, 0x00}; uint8_t enable_cmd_value[2] = {0x01, 0x00};
this->send_command_(CMD_BLUETOOTH, cmd_value, sizeof(cmd_value)); uint8_t disable_cmd_value[2] = {0x00, 0x00};
this->send_command_(CMD_BLUETOOTH, enable ? enable_cmd_value : disable_cmd_value, 2);
this->set_timeout(200, [this]() { this->restart_and_read_all_info(); }); this->set_timeout(200, [this]() { this->restart_and_read_all_info(); });
} }
void LD2410Component::set_distance_resolution(const std::string &state) { void LD2410Component::set_distance_resolution(const std::string &state) {
this->set_config_mode_(true); this->set_config_mode_(true);
const uint8_t cmd_value[2] = {find_uint8(DISTANCE_RESOLUTIONS_BY_STR, state), 0x00}; uint8_t cmd_value[2] = {find_uint8(DISTANCE_RESOLUTIONS_BY_STR, state), 0x00};
this->send_command_(CMD_SET_DISTANCE_RESOLUTION, cmd_value, sizeof(cmd_value)); this->send_command_(CMD_SET_DISTANCE_RESOLUTION, cmd_value, 2);
this->set_timeout(200, [this]() { this->restart_and_read_all_info(); }); this->set_timeout(200, [this]() { this->restart_and_read_all_info(); });
} }
void LD2410Component::set_baud_rate(const std::string &state) { void LD2410Component::set_baud_rate(const std::string &state) {
this->set_config_mode_(true); this->set_config_mode_(true);
const uint8_t cmd_value[2] = {find_uint8(BAUD_RATES_BY_STR, state), 0x00}; uint8_t cmd_value[2] = {find_uint8(BAUD_RATES_BY_STR, state), 0x00};
this->send_command_(CMD_SET_BAUD_RATE, cmd_value, sizeof(cmd_value)); this->send_command_(CMD_SET_BAUD_RATE, cmd_value, 2);
this->set_timeout(200, [this]() { this->restart_(); }); this->set_timeout(200, [this]() { this->restart_(); });
} }
@@ -698,13 +661,14 @@ void LD2410Component::set_bluetooth_password(const std::string &password) {
this->set_config_mode_(true); this->set_config_mode_(true);
uint8_t cmd_value[6]; uint8_t cmd_value[6];
std::copy(password.begin(), password.end(), std::begin(cmd_value)); std::copy(password.begin(), password.end(), std::begin(cmd_value));
this->send_command_(CMD_BT_PASSWORD, cmd_value, sizeof(cmd_value)); this->send_command_(CMD_BT_PASSWORD, cmd_value, 6);
this->set_config_mode_(false); this->set_config_mode_(false);
} }
void LD2410Component::set_engineering_mode(bool enable) { void LD2410Component::set_engineering_mode(bool enable) {
const uint8_t cmd = enable ? CMD_ENABLE_ENG : CMD_DISABLE_ENG;
this->set_config_mode_(true); this->set_config_mode_(true);
last_engineering_mode_change_millis_ = App.get_loop_component_start_time();
uint8_t cmd = enable ? CMD_ENABLE_ENG : CMD_DISABLE_ENG;
this->send_command_(cmd, nullptr, 0); this->send_command_(cmd, nullptr, 0);
this->set_config_mode_(false); this->set_config_mode_(false);
} }
@@ -718,17 +682,14 @@ void LD2410Component::factory_reset() {
void LD2410Component::restart_() { this->send_command_(CMD_RESTART, nullptr, 0); } void LD2410Component::restart_() { this->send_command_(CMD_RESTART, nullptr, 0); }
void LD2410Component::query_parameters_() { this->send_command_(CMD_QUERY, nullptr, 0); } void LD2410Component::query_parameters_() { this->send_command_(CMD_QUERY, nullptr, 0); }
void LD2410Component::get_version_() { this->send_command_(CMD_VERSION, nullptr, 0); }
void LD2410Component::get_version_() { this->send_command_(CMD_QUERY_VERSION, nullptr, 0); }
void LD2410Component::get_mac_() { void LD2410Component::get_mac_() {
const uint8_t cmd_value[2] = {0x01, 0x00}; uint8_t cmd_value[2] = {0x01, 0x00};
this->send_command_(CMD_QUERY_MAC_ADDRESS, cmd_value, sizeof(cmd_value)); this->send_command_(CMD_MAC, cmd_value, 2);
} }
void LD2410Component::get_distance_resolution_() { this->send_command_(CMD_QUERY_DISTANCE_RESOLUTION, nullptr, 0); } void LD2410Component::get_distance_resolution_() { this->send_command_(CMD_QUERY_DISTANCE_RESOLUTION, nullptr, 0); }
void LD2410Component::query_light_control_() { this->send_command_(CMD_QUERY_LIGHT_CONTROL, nullptr, 0); } void LD2410Component::get_light_control_() { this->send_command_(CMD_QUERY_LIGHT_CONTROL, nullptr, 0); }
#ifdef USE_NUMBER #ifdef USE_NUMBER
void LD2410Component::set_max_distances_timeout() { void LD2410Component::set_max_distances_timeout() {
@@ -758,7 +719,8 @@ void LD2410Component::set_max_distances_timeout() {
0x00, 0x00,
0x00}; 0x00};
this->set_config_mode_(true); this->set_config_mode_(true);
this->send_command_(CMD_MAXDIST_DURATION, value, sizeof(value)); this->send_command_(CMD_MAXDIST_DURATION, value, 18);
delay(50); // NOLINT
this->query_parameters_(); this->query_parameters_();
this->set_timeout(200, [this]() { this->restart_and_read_all_info(); }); this->set_timeout(200, [this]() { this->restart_and_read_all_info(); });
this->set_config_mode_(false); this->set_config_mode_(false);
@@ -787,16 +749,17 @@ void LD2410Component::set_gate_threshold(uint8_t gate) {
uint8_t value[18] = {0x00, 0x00, lowbyte(gate), highbyte(gate), 0x00, 0x00, uint8_t value[18] = {0x00, 0x00, lowbyte(gate), highbyte(gate), 0x00, 0x00,
0x01, 0x00, lowbyte(motion), highbyte(motion), 0x00, 0x00, 0x01, 0x00, lowbyte(motion), highbyte(motion), 0x00, 0x00,
0x02, 0x00, lowbyte(still), highbyte(still), 0x00, 0x00}; 0x02, 0x00, lowbyte(still), highbyte(still), 0x00, 0x00};
this->send_command_(CMD_GATE_SENS, value, sizeof(value)); this->send_command_(CMD_GATE_SENS, value, 18);
delay(50); // NOLINT
this->query_parameters_(); this->query_parameters_();
this->set_config_mode_(false); this->set_config_mode_(false);
} }
void LD2410Component::set_gate_still_threshold_number(uint8_t gate, number::Number *n) { void LD2410Component::set_gate_still_threshold_number(int gate, number::Number *n) {
this->gate_still_threshold_numbers_[gate] = n; this->gate_still_threshold_numbers_[gate] = n;
} }
void LD2410Component::set_gate_move_threshold_number(uint8_t gate, number::Number *n) { void LD2410Component::set_gate_move_threshold_number(int gate, number::Number *n) {
this->gate_move_threshold_numbers_[gate] = n; this->gate_move_threshold_numbers_[gate] = n;
} }
#endif #endif
@@ -804,28 +767,35 @@ void LD2410Component::set_gate_move_threshold_number(uint8_t gate, number::Numbe
void LD2410Component::set_light_out_control() { void LD2410Component::set_light_out_control() {
#ifdef USE_NUMBER #ifdef USE_NUMBER
if (this->light_threshold_number_ != nullptr && this->light_threshold_number_->has_state()) { if (this->light_threshold_number_ != nullptr && this->light_threshold_number_->has_state()) {
this->light_threshold_ = static_cast<uint8_t>(this->light_threshold_number_->state); this->light_threshold_ = this->light_threshold_number_->state;
} }
#endif #endif
#ifdef USE_SELECT #ifdef USE_SELECT
if (this->light_function_select_ != nullptr && this->light_function_select_->has_state()) { if (this->light_function_select_ != nullptr && this->light_function_select_->has_state()) {
this->light_function_ = find_uint8(LIGHT_FUNCTIONS_BY_STR, this->light_function_select_->state); this->light_function_ = this->light_function_select_->state;
} }
if (this->out_pin_level_select_ != nullptr && this->out_pin_level_select_->has_state()) { if (this->out_pin_level_select_ != nullptr && this->out_pin_level_select_->has_state()) {
this->out_pin_level_ = find_uint8(OUT_PIN_LEVELS_BY_STR, this->out_pin_level_select_->state); this->out_pin_level_ = this->out_pin_level_select_->state;
} }
#endif #endif
if (this->light_function_.empty() || this->out_pin_level_.empty() || this->light_threshold_ < 0) {
return;
}
this->set_config_mode_(true); this->set_config_mode_(true);
uint8_t value[4] = {this->light_function_, this->light_threshold_, this->out_pin_level_, 0x00}; uint8_t light_function = find_uint8(LIGHT_FUNCTIONS_BY_STR, this->light_function_);
this->send_command_(CMD_SET_LIGHT_CONTROL, value, sizeof(value)); uint8_t light_threshold = static_cast<uint8_t>(this->light_threshold_);
this->query_light_control_(); uint8_t out_pin_level = find_uint8(OUT_PIN_LEVELS_BY_STR, this->out_pin_level_);
uint8_t value[4] = {light_function, light_threshold, out_pin_level, 0x00};
this->send_command_(CMD_SET_LIGHT_CONTROL, value, 4);
delay(50); // NOLINT
this->get_light_control_();
this->set_timeout(200, [this]() { this->restart_and_read_all_info(); }); this->set_timeout(200, [this]() { this->restart_and_read_all_info(); });
this->set_config_mode_(false); this->set_config_mode_(false);
} }
#ifdef USE_SENSOR #ifdef USE_SENSOR
void LD2410Component::set_gate_move_sensor(uint8_t gate, sensor::Sensor *s) { this->gate_move_sensors_[gate] = s; } void LD2410Component::set_gate_move_sensor(int gate, sensor::Sensor *s) { this->gate_move_sensors_[gate] = s; }
void LD2410Component::set_gate_still_sensor(uint8_t gate, sensor::Sensor *s) { this->gate_still_sensors_[gate] = s; } void LD2410Component::set_gate_still_sensor(int gate, sensor::Sensor *s) { this->gate_still_sensors_[gate] = s; }
#endif #endif
} // namespace ld2410 } // namespace ld2410

View File

@@ -29,48 +29,45 @@
namespace esphome { namespace esphome {
namespace ld2410 { namespace ld2410 {
static const uint8_t MAX_LINE_LENGTH = 46; // Max characters for serial buffer
static const uint8_t TOTAL_GATES = 9; // Total number of gates supported by the LD2410
class LD2410Component : public Component, public uart::UARTDevice { class LD2410Component : public Component, public uart::UARTDevice {
#ifdef USE_BINARY_SENSOR
SUB_BINARY_SENSOR(out_pin_presence_status)
SUB_BINARY_SENSOR(moving_target)
SUB_BINARY_SENSOR(still_target)
SUB_BINARY_SENSOR(target)
#endif
#ifdef USE_SENSOR #ifdef USE_SENSOR
SUB_SENSOR(moving_target_distance)
SUB_SENSOR(still_target_distance)
SUB_SENSOR(moving_target_energy)
SUB_SENSOR(still_target_energy)
SUB_SENSOR(light) SUB_SENSOR(light)
SUB_SENSOR(detection_distance) SUB_SENSOR(detection_distance)
SUB_SENSOR(moving_target_distance) #endif
SUB_SENSOR(moving_target_energy) #ifdef USE_BINARY_SENSOR
SUB_SENSOR(still_target_distance) SUB_BINARY_SENSOR(target)
SUB_SENSOR(still_target_energy) SUB_BINARY_SENSOR(moving_target)
SUB_BINARY_SENSOR(still_target)
SUB_BINARY_SENSOR(out_pin_presence_status)
#endif #endif
#ifdef USE_TEXT_SENSOR #ifdef USE_TEXT_SENSOR
SUB_TEXT_SENSOR(version) SUB_TEXT_SENSOR(version)
SUB_TEXT_SENSOR(mac) SUB_TEXT_SENSOR(mac)
#endif #endif
#ifdef USE_NUMBER
SUB_NUMBER(light_threshold)
SUB_NUMBER(max_move_distance_gate)
SUB_NUMBER(max_still_distance_gate)
SUB_NUMBER(timeout)
#endif
#ifdef USE_SELECT #ifdef USE_SELECT
SUB_SELECT(baud_rate)
SUB_SELECT(distance_resolution) SUB_SELECT(distance_resolution)
SUB_SELECT(baud_rate)
SUB_SELECT(light_function) SUB_SELECT(light_function)
SUB_SELECT(out_pin_level) SUB_SELECT(out_pin_level)
#endif #endif
#ifdef USE_SWITCH #ifdef USE_SWITCH
SUB_SWITCH(bluetooth)
SUB_SWITCH(engineering_mode) SUB_SWITCH(engineering_mode)
SUB_SWITCH(bluetooth)
#endif #endif
#ifdef USE_BUTTON #ifdef USE_BUTTON
SUB_BUTTON(factory_reset) SUB_BUTTON(reset)
SUB_BUTTON(query)
SUB_BUTTON(restart) SUB_BUTTON(restart)
SUB_BUTTON(query)
#endif
#ifdef USE_NUMBER
SUB_NUMBER(max_still_distance_gate)
SUB_NUMBER(max_move_distance_gate)
SUB_NUMBER(timeout)
SUB_NUMBER(light_threshold)
#endif #endif
public: public:
@@ -79,14 +76,14 @@ class LD2410Component : public Component, public uart::UARTDevice {
void loop() override; void loop() override;
void set_light_out_control(); void set_light_out_control();
#ifdef USE_NUMBER #ifdef USE_NUMBER
void set_gate_still_threshold_number(uint8_t gate, number::Number *n); void set_gate_still_threshold_number(int gate, number::Number *n);
void set_gate_move_threshold_number(uint8_t gate, number::Number *n); void set_gate_move_threshold_number(int gate, number::Number *n);
void set_max_distances_timeout(); void set_max_distances_timeout();
void set_gate_threshold(uint8_t gate); void set_gate_threshold(uint8_t gate);
#endif #endif
#ifdef USE_SENSOR #ifdef USE_SENSOR
void set_gate_move_sensor(uint8_t gate, sensor::Sensor *s); void set_gate_move_sensor(int gate, sensor::Sensor *s);
void set_gate_still_sensor(uint8_t gate, sensor::Sensor *s); void set_gate_still_sensor(int gate, sensor::Sensor *s);
#endif #endif
void set_throttle(uint16_t value) { this->throttle_ = value; }; void set_throttle(uint16_t value) { this->throttle_ = value; };
void set_bluetooth_password(const std::string &password); void set_bluetooth_password(const std::string &password);
@@ -99,35 +96,33 @@ class LD2410Component : public Component, public uart::UARTDevice {
void factory_reset(); void factory_reset();
protected: protected:
void send_command_(uint8_t command_str, const uint8_t *command_value, uint8_t command_value_len); void send_command_(uint8_t command_str, const uint8_t *command_value, int command_value_len);
void set_config_mode_(bool enable); void set_config_mode_(bool enable);
void handle_periodic_data_(); void handle_periodic_data_(uint8_t *buffer, int len);
bool handle_ack_data_(); bool handle_ack_data_(uint8_t *buffer, int len);
void readline_(int readch); void readline_(int readch, uint8_t *buffer, int len);
void query_parameters_(); void query_parameters_();
void get_version_(); void get_version_();
void get_mac_(); void get_mac_();
void get_distance_resolution_(); void get_distance_resolution_();
void query_light_control_(); void get_light_control_();
void restart_(); void restart_();
uint32_t last_periodic_millis_ = 0; int32_t last_periodic_millis_ = 0;
uint16_t throttle_ = 0; int32_t last_engineering_mode_change_millis_ = 0;
uint8_t light_function_ = 0; uint16_t throttle_;
uint8_t light_threshold_ = 0; float light_threshold_ = -1;
uint8_t out_pin_level_ = 0; std::string version_;
uint8_t buffer_pos_ = 0; // where to resume processing/populating buffer std::string mac_;
uint8_t buffer_data_[MAX_LINE_LENGTH]; std::string out_pin_level_;
uint8_t mac_address_[6] = {0, 0, 0, 0, 0, 0}; std::string light_function_;
uint8_t version_[6] = {0, 0, 0, 0, 0, 0};
bool bluetooth_on_{false};
#ifdef USE_NUMBER #ifdef USE_NUMBER
std::vector<number::Number *> gate_move_threshold_numbers_ = std::vector<number::Number *>(TOTAL_GATES); std::vector<number::Number *> gate_still_threshold_numbers_ = std::vector<number::Number *>(9);
std::vector<number::Number *> gate_still_threshold_numbers_ = std::vector<number::Number *>(TOTAL_GATES); std::vector<number::Number *> gate_move_threshold_numbers_ = std::vector<number::Number *>(9);
#endif #endif
#ifdef USE_SENSOR #ifdef USE_SENSOR
std::vector<sensor::Sensor *> gate_move_sensors_ = std::vector<sensor::Sensor *>(TOTAL_GATES); std::vector<sensor::Sensor *> gate_still_sensors_ = std::vector<sensor::Sensor *>(9);
std::vector<sensor::Sensor *> gate_still_sensors_ = std::vector<sensor::Sensor *>(TOTAL_GATES); std::vector<sensor::Sensor *> gate_move_sensors_ = std::vector<sensor::Sensor *>(9);
#endif #endif
}; };

View File

@@ -5,10 +5,10 @@
namespace esphome { namespace esphome {
namespace ld2420 { namespace ld2420 {
static const char *const TAG = "ld2420.binary_sensor"; static const char *const TAG = "LD2420.binary_sensor";
void LD2420BinarySensor::dump_config() { void LD2420BinarySensor::dump_config() {
ESP_LOGCONFIG(TAG, "Binary Sensor:"); ESP_LOGCONFIG(TAG, "LD2420 BinarySensor:");
LOG_BINARY_SENSOR(" ", "Presence", this->presence_bsensor_); LOG_BINARY_SENSOR(" ", "Presence", this->presence_bsensor_);
} }

View File

@@ -2,7 +2,7 @@
#include "esphome/core/helpers.h" #include "esphome/core/helpers.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
static const char *const TAG = "ld2420.button"; static const char *const TAG = "LD2420.button";
namespace esphome { namespace esphome {
namespace ld2420 { namespace ld2420 {

View File

@@ -137,7 +137,7 @@ static const std::string OP_SIMPLE_MODE_STRING = "Simple";
// Memory-efficient lookup tables // Memory-efficient lookup tables
struct StringToUint8 { struct StringToUint8 {
const char *str; const char *str;
const uint8_t value; uint8_t value;
}; };
static constexpr StringToUint8 OP_MODE_BY_STR[] = { static constexpr StringToUint8 OP_MODE_BY_STR[] = {
@@ -155,9 +155,8 @@ static constexpr const char *ERR_MESSAGE[] = {
// Helper function for lookups // Helper function for lookups
template<size_t N> uint8_t find_uint8(const StringToUint8 (&arr)[N], const std::string &str) { template<size_t N> uint8_t find_uint8(const StringToUint8 (&arr)[N], const std::string &str) {
for (const auto &entry : arr) { for (const auto &entry : arr) {
if (str == entry.str) { if (str == entry.str)
return entry.value; return entry.value;
}
} }
return 0xFF; // Not found return 0xFF; // Not found
} }
@@ -327,8 +326,15 @@ void LD2420Component::revert_config_action() {
void LD2420Component::loop() { void LD2420Component::loop() {
// If there is a active send command do not process it here, the send command call will handle it. // If there is a active send command do not process it here, the send command call will handle it.
while (!this->cmd_active_ && this->available()) { if (!this->get_cmd_active_()) {
this->readline_(this->read(), this->buffer_data_, MAX_LINE_LENGTH); if (!this->available())
return;
static uint8_t buffer[2048];
static uint8_t rx_data;
while (this->available()) {
rx_data = this->read();
this->readline_(rx_data, buffer, sizeof(buffer));
}
} }
} }
@@ -359,9 +365,8 @@ void LD2420Component::auto_calibrate_sensitivity() {
// Store average and peak values // Store average and peak values
this->gate_avg[gate] = sum / CALIBRATE_SAMPLES; this->gate_avg[gate] = sum / CALIBRATE_SAMPLES;
if (this->gate_peak[gate] < peak) { if (this->gate_peak[gate] < peak)
this->gate_peak[gate] = peak; this->gate_peak[gate] = peak;
}
uint32_t calculated_value = uint32_t calculated_value =
(static_cast<uint32_t>(this->gate_peak[gate]) + (move_factor * static_cast<uint32_t>(this->gate_peak[gate]))); (static_cast<uint32_t>(this->gate_peak[gate]) + (move_factor * static_cast<uint32_t>(this->gate_peak[gate])));
@@ -398,9 +403,8 @@ void LD2420Component::set_operating_mode(const std::string &state) {
} }
} else { } else {
// Set the current data back so we don't have new data that can be applied in error. // Set the current data back so we don't have new data that can be applied in error.
if (this->get_calibration_()) { if (this->get_calibration_())
memcpy(&this->new_config, &this->current_config, sizeof(this->current_config)); memcpy(&this->new_config, &this->current_config, sizeof(this->current_config));
}
this->set_calibration_(false); this->set_calibration_(false);
} }
} else { } else {
@@ -410,32 +414,30 @@ void LD2420Component::set_operating_mode(const std::string &state) {
} }
void LD2420Component::readline_(int rx_data, uint8_t *buffer, int len) { void LD2420Component::readline_(int rx_data, uint8_t *buffer, int len) {
if (rx_data < 0) { static int pos = 0;
return; // No data available
} if (rx_data >= 0) {
if (this->buffer_pos_ < len - 1) { if (pos < len - 1) {
buffer[this->buffer_pos_++] = rx_data; buffer[pos++] = rx_data;
buffer[this->buffer_pos_] = 0; buffer[pos] = 0;
} else { } else {
// We should never get here, but just in case... pos = 0;
ESP_LOGW(TAG, "Max command length exceeded; ignoring"); }
this->buffer_pos_ = 0; if (pos >= 4) {
} if (memcmp(&buffer[pos - 4], &CMD_FRAME_FOOTER, sizeof(CMD_FRAME_FOOTER)) == 0) {
if (this->buffer_pos_ < 4) { this->set_cmd_active_(false); // Set command state to inactive after responce.
return; // Not enough data to process yet this->handle_ack_data_(buffer, pos);
} pos = 0;
if (memcmp(&buffer[this->buffer_pos_ - 4], &CMD_FRAME_FOOTER, sizeof(CMD_FRAME_FOOTER)) == 0) { } else if ((buffer[pos - 2] == 0x0D && buffer[pos - 1] == 0x0A) &&
this->cmd_active_ = false; // Set command state to inactive after response (this->get_mode_() == CMD_SYSTEM_MODE_SIMPLE)) {
this->handle_ack_data_(buffer, this->buffer_pos_); this->handle_simple_mode_(buffer, pos);
this->buffer_pos_ = 0; pos = 0;
} else if ((buffer[this->buffer_pos_ - 2] == 0x0D && buffer[this->buffer_pos_ - 1] == 0x0A) && } else if ((memcmp(&buffer[pos - 4], &ENERGY_FRAME_FOOTER, sizeof(ENERGY_FRAME_FOOTER)) == 0) &&
(this->get_mode_() == CMD_SYSTEM_MODE_SIMPLE)) { (this->get_mode_() == CMD_SYSTEM_MODE_ENERGY)) {
this->handle_simple_mode_(buffer, this->buffer_pos_); this->handle_energy_mode_(buffer, pos);
this->buffer_pos_ = 0; pos = 0;
} else if ((memcmp(&buffer[this->buffer_pos_ - 4], &ENERGY_FRAME_FOOTER, sizeof(ENERGY_FRAME_FOOTER)) == 0) && }
(this->get_mode_() == CMD_SYSTEM_MODE_ENERGY)) { }
this->handle_energy_mode_(buffer, this->buffer_pos_);
this->buffer_pos_ = 0;
} }
} }
@@ -460,9 +462,8 @@ void LD2420Component::handle_energy_mode_(uint8_t *buffer, int len) {
// Resonable refresh rate for home assistant database size health // Resonable refresh rate for home assistant database size health
const int32_t current_millis = App.get_loop_component_start_time(); const int32_t current_millis = App.get_loop_component_start_time();
if (current_millis - this->last_periodic_millis < REFRESH_RATE_MS) { if (current_millis - this->last_periodic_millis < REFRESH_RATE_MS)
return; return;
}
this->last_periodic_millis = current_millis; this->last_periodic_millis = current_millis;
for (auto &listener : this->listeners_) { for (auto &listener : this->listeners_) {
listener->on_distance(this->get_distance_()); listener->on_distance(this->get_distance_());
@@ -505,16 +506,14 @@ void LD2420Component::handle_simple_mode_(const uint8_t *inbuf, int len) {
} }
} }
outbuf[index] = '\0'; outbuf[index] = '\0';
if (index > 1) { if (index > 1)
this->set_distance_(strtol(outbuf, &endptr, 10)); this->set_distance_(strtol(outbuf, &endptr, 10));
}
if (this->get_mode_() == CMD_SYSTEM_MODE_SIMPLE) { if (this->get_mode_() == CMD_SYSTEM_MODE_SIMPLE) {
// Resonable refresh rate for home assistant database size health // Resonable refresh rate for home assistant database size health
const int32_t current_millis = App.get_loop_component_start_time(); const int32_t current_millis = App.get_loop_component_start_time();
if (current_millis - this->last_normal_periodic_millis < REFRESH_RATE_MS) { if (current_millis - this->last_normal_periodic_millis < REFRESH_RATE_MS)
return; return;
}
this->last_normal_periodic_millis = current_millis; this->last_normal_periodic_millis = current_millis;
for (auto &listener : this->listeners_) for (auto &listener : this->listeners_)
listener->on_distance(this->get_distance_()); listener->on_distance(this->get_distance_());
@@ -594,12 +593,11 @@ void LD2420Component::handle_ack_data_(uint8_t *buffer, int len) {
int LD2420Component::send_cmd_from_array(CmdFrameT frame) { int LD2420Component::send_cmd_from_array(CmdFrameT frame) {
uint32_t start_millis = millis(); uint32_t start_millis = millis();
uint8_t error = 0; uint8_t error = 0;
uint8_t ack_buffer[MAX_LINE_LENGTH]; uint8_t ack_buffer[64];
uint8_t cmd_buffer[MAX_LINE_LENGTH]; uint8_t cmd_buffer[64];
this->cmd_reply_.ack = false; this->cmd_reply_.ack = false;
if (frame.command != CMD_RESTART) { if (frame.command != CMD_RESTART)
this->cmd_active_ = true; this->set_cmd_active_(true); // Restart does not reply, thus no ack state required.
} // Restart does not reply, thus no ack state required
uint8_t retry = 3; uint8_t retry = 3;
while (retry) { while (retry) {
frame.length = 0; frame.length = 0;
@@ -621,7 +619,9 @@ int LD2420Component::send_cmd_from_array(CmdFrameT frame) {
memcpy(cmd_buffer + frame.length, &frame.footer, sizeof(frame.footer)); memcpy(cmd_buffer + frame.length, &frame.footer, sizeof(frame.footer));
frame.length += sizeof(frame.footer); frame.length += sizeof(frame.footer);
this->write_array(cmd_buffer, frame.length); for (uint16_t index = 0; index < frame.length; index++) {
this->write_byte(cmd_buffer[index]);
}
error = 0; error = 0;
if (frame.command == CMD_RESTART) { if (frame.command == CMD_RESTART) {
@@ -630,7 +630,7 @@ int LD2420Component::send_cmd_from_array(CmdFrameT frame) {
while (!this->cmd_reply_.ack) { while (!this->cmd_reply_.ack) {
while (this->available()) { while (this->available()) {
this->readline_(this->read(), ack_buffer, sizeof(ack_buffer)); this->readline_(read(), ack_buffer, sizeof(ack_buffer));
} }
delay_microseconds_safe(1450); delay_microseconds_safe(1450);
// Wait on an Rx from the LD2420 for up to 3 1 second loops, otherwise it could trigger a WDT. // Wait on an Rx from the LD2420 for up to 3 1 second loops, otherwise it could trigger a WDT.
@@ -641,12 +641,10 @@ int LD2420Component::send_cmd_from_array(CmdFrameT frame) {
break; break;
} }
} }
if (this->cmd_reply_.ack) { if (this->cmd_reply_.ack)
retry = 0; retry = 0;
} if (this->cmd_reply_.error > 0)
if (this->cmd_reply_.error > 0) {
this->handle_cmd_error(error); this->handle_cmd_error(error);
}
} }
return error; return error;
} }
@@ -766,9 +764,8 @@ void LD2420Component::set_system_mode(uint16_t mode) {
cmd_frame.data_length += sizeof(unknown_parm); cmd_frame.data_length += sizeof(unknown_parm);
cmd_frame.footer = CMD_FRAME_FOOTER; cmd_frame.footer = CMD_FRAME_FOOTER;
ESP_LOGV(TAG, "Sending write system mode command: %2X", cmd_frame.command); ESP_LOGV(TAG, "Sending write system mode command: %2X", cmd_frame.command);
if (this->send_cmd_from_array(cmd_frame) == 0) { if (this->send_cmd_from_array(cmd_frame) == 0)
this->set_mode_(mode); this->set_mode_(mode);
}
} }
void LD2420Component::get_firmware_version_() { void LD2420Component::get_firmware_version_() {
@@ -843,24 +840,18 @@ void LD2420Component::set_gate_threshold(uint8_t gate) {
#ifdef USE_NUMBER #ifdef USE_NUMBER
void LD2420Component::init_gate_config_numbers() { void LD2420Component::init_gate_config_numbers() {
if (this->gate_timeout_number_ != nullptr) { if (this->gate_timeout_number_ != nullptr)
this->gate_timeout_number_->publish_state(static_cast<uint16_t>(this->current_config.timeout)); this->gate_timeout_number_->publish_state(static_cast<uint16_t>(this->current_config.timeout));
} if (this->gate_select_number_ != nullptr)
if (this->gate_select_number_ != nullptr) {
this->gate_select_number_->publish_state(0); this->gate_select_number_->publish_state(0);
} if (this->min_gate_distance_number_ != nullptr)
if (this->min_gate_distance_number_ != nullptr) {
this->min_gate_distance_number_->publish_state(static_cast<uint16_t>(this->current_config.min_gate)); this->min_gate_distance_number_->publish_state(static_cast<uint16_t>(this->current_config.min_gate));
} if (this->max_gate_distance_number_ != nullptr)
if (this->max_gate_distance_number_ != nullptr) {
this->max_gate_distance_number_->publish_state(static_cast<uint16_t>(this->current_config.max_gate)); this->max_gate_distance_number_->publish_state(static_cast<uint16_t>(this->current_config.max_gate));
} if (this->gate_move_sensitivity_factor_number_ != nullptr)
if (this->gate_move_sensitivity_factor_number_ != nullptr) {
this->gate_move_sensitivity_factor_number_->publish_state(this->gate_move_sensitivity_factor); this->gate_move_sensitivity_factor_number_->publish_state(this->gate_move_sensitivity_factor);
} if (this->gate_still_sensitivity_factor_number_ != nullptr)
if (this->gate_still_sensitivity_factor_number_ != nullptr) {
this->gate_still_sensitivity_factor_number_->publish_state(this->gate_still_sensitivity_factor); this->gate_still_sensitivity_factor_number_->publish_state(this->gate_still_sensitivity_factor);
}
for (uint8_t gate = 0; gate < TOTAL_GATES; gate++) { for (uint8_t gate = 0; gate < TOTAL_GATES; gate++) {
if (this->gate_still_threshold_numbers_[gate] != nullptr) { if (this->gate_still_threshold_numbers_[gate] != nullptr) {
this->gate_still_threshold_numbers_[gate]->publish_state( this->gate_still_threshold_numbers_[gate]->publish_state(

View File

@@ -20,9 +20,8 @@
namespace esphome { namespace esphome {
namespace ld2420 { namespace ld2420 {
static const uint8_t CALIBRATE_SAMPLES = 64;
static const uint8_t MAX_LINE_LENGTH = 46; // Max characters for serial buffer
static const uint8_t TOTAL_GATES = 16; static const uint8_t TOTAL_GATES = 16;
static const uint8_t CALIBRATE_SAMPLES = 64;
enum OpMode : uint8_t { enum OpMode : uint8_t {
OP_NORMAL_MODE = 1, OP_NORMAL_MODE = 1,
@@ -119,10 +118,10 @@ class LD2420Component : public Component, public uart::UARTDevice {
float gate_move_sensitivity_factor{0.5}; float gate_move_sensitivity_factor{0.5};
float gate_still_sensitivity_factor{0.5}; float gate_still_sensitivity_factor{0.5};
int32_t last_periodic_millis{0}; int32_t last_periodic_millis = millis();
int32_t report_periodic_millis{0}; int32_t report_periodic_millis = millis();
int32_t monitor_periodic_millis{0}; int32_t monitor_periodic_millis = millis();
int32_t last_normal_periodic_millis{0}; int32_t last_normal_periodic_millis = millis();
uint16_t radar_data[TOTAL_GATES][CALIBRATE_SAMPLES]; uint16_t radar_data[TOTAL_GATES][CALIBRATE_SAMPLES];
uint16_t gate_avg[TOTAL_GATES]; uint16_t gate_avg[TOTAL_GATES];
uint16_t gate_peak[TOTAL_GATES]; uint16_t gate_peak[TOTAL_GATES];
@@ -162,6 +161,8 @@ class LD2420Component : public Component, public uart::UARTDevice {
void set_presence_(bool presence) { this->presence_ = presence; }; void set_presence_(bool presence) { this->presence_ = presence; };
uint16_t get_distance_() { return this->distance_; }; uint16_t get_distance_() { return this->distance_; };
void set_distance_(uint16_t distance) { this->distance_ = distance; }; void set_distance_(uint16_t distance) { this->distance_ = distance; };
bool get_cmd_active_() { return this->cmd_active_; };
void set_cmd_active_(bool active) { this->cmd_active_ = active; };
void handle_simple_mode_(const uint8_t *inbuf, int len); void handle_simple_mode_(const uint8_t *inbuf, int len);
void handle_energy_mode_(uint8_t *buffer, int len); void handle_energy_mode_(uint8_t *buffer, int len);
void handle_ack_data_(uint8_t *buffer, int len); void handle_ack_data_(uint8_t *buffer, int len);
@@ -180,11 +181,12 @@ class LD2420Component : public Component, public uart::UARTDevice {
std::vector<number::Number *> gate_move_threshold_numbers_ = std::vector<number::Number *>(16); std::vector<number::Number *> gate_move_threshold_numbers_ = std::vector<number::Number *>(16);
#endif #endif
uint16_t distance_{0}; uint32_t max_distance_gate_;
uint32_t min_distance_gate_;
uint16_t system_mode_; uint16_t system_mode_;
uint16_t gate_energy_[TOTAL_GATES]; uint16_t gate_energy_[TOTAL_GATES];
uint8_t buffer_pos_{0}; // where to resume processing/populating buffer uint16_t distance_{0};
uint8_t buffer_data_[MAX_LINE_LENGTH]; uint8_t config_checksum_{0};
char firmware_ver_[8]{"v0.0.0"}; char firmware_ver_[8]{"v0.0.0"};
bool cmd_active_{false}; bool cmd_active_{false};
bool presence_{false}; bool presence_{false};

View File

@@ -2,7 +2,7 @@
#include "esphome/core/helpers.h" #include "esphome/core/helpers.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
static const char *const TAG = "ld2420.number"; static const char *const TAG = "LD2420.number";
namespace esphome { namespace esphome {
namespace ld2420 { namespace ld2420 {

View File

@@ -5,7 +5,7 @@
namespace esphome { namespace esphome {
namespace ld2420 { namespace ld2420 {
static const char *const TAG = "ld2420.select"; static const char *const TAG = "LD2420.select";
void LD2420Select::control(const std::string &value) { void LD2420Select::control(const std::string &value) {
this->publish_state(value); this->publish_state(value);

View File

@@ -5,10 +5,10 @@
namespace esphome { namespace esphome {
namespace ld2420 { namespace ld2420 {
static const char *const TAG = "ld2420.sensor"; static const char *const TAG = "LD2420.sensor";
void LD2420Sensor::dump_config() { void LD2420Sensor::dump_config() {
ESP_LOGCONFIG(TAG, "Sensor:"); ESP_LOGCONFIG(TAG, "LD2420 Sensor:");
LOG_SENSOR(" ", "Distance", this->distance_sensor_); LOG_SENSOR(" ", "Distance", this->distance_sensor_);
} }

View File

@@ -5,10 +5,10 @@
namespace esphome { namespace esphome {
namespace ld2420 { namespace ld2420 {
static const char *const TAG = "ld2420.text_sensor"; static const char *const TAG = "LD2420.text_sensor";
void LD2420TextSensor::dump_config() { void LD2420TextSensor::dump_config() {
ESP_LOGCONFIG(TAG, "Text Sensor:"); ESP_LOGCONFIG(TAG, "LD2420 TextSensor:");
LOG_TEXT_SENSOR(" ", "Firmware", this->fw_version_text_sensor_); LOG_TEXT_SENSOR(" ", "Firmware", this->fw_version_text_sensor_);
} }

View File

@@ -13,13 +13,13 @@ from esphome.const import (
from .. import CONF_LD2450_ID, LD2450Component, ld2450_ns from .. import CONF_LD2450_ID, LD2450Component, ld2450_ns
FactoryResetButton = ld2450_ns.class_("FactoryResetButton", button.Button) ResetButton = ld2450_ns.class_("ResetButton", button.Button)
RestartButton = ld2450_ns.class_("RestartButton", button.Button) RestartButton = ld2450_ns.class_("RestartButton", button.Button)
CONFIG_SCHEMA = { CONFIG_SCHEMA = {
cv.GenerateID(CONF_LD2450_ID): cv.use_id(LD2450Component), cv.GenerateID(CONF_LD2450_ID): cv.use_id(LD2450Component),
cv.Optional(CONF_FACTORY_RESET): button.button_schema( cv.Optional(CONF_FACTORY_RESET): button.button_schema(
FactoryResetButton, ResetButton,
device_class=DEVICE_CLASS_RESTART, device_class=DEVICE_CLASS_RESTART,
entity_category=ENTITY_CATEGORY_CONFIG, entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_RESTART_ALERT, icon=ICON_RESTART_ALERT,
@@ -38,7 +38,7 @@ async def to_code(config):
if factory_reset_config := config.get(CONF_FACTORY_RESET): if factory_reset_config := config.get(CONF_FACTORY_RESET):
b = await button.new_button(factory_reset_config) b = await button.new_button(factory_reset_config)
await cg.register_parented(b, config[CONF_LD2450_ID]) await cg.register_parented(b, config[CONF_LD2450_ID])
cg.add(ld2450_component.set_factory_reset_button(b)) cg.add(ld2450_component.set_reset_button(b))
if restart_config := config.get(CONF_RESTART): if restart_config := config.get(CONF_RESTART):
b = await button.new_button(restart_config) b = await button.new_button(restart_config)
await cg.register_parented(b, config[CONF_LD2450_ID]) await cg.register_parented(b, config[CONF_LD2450_ID])

View File

@@ -1,9 +0,0 @@
#include "factory_reset_button.h"
namespace esphome {
namespace ld2450 {
void FactoryResetButton::press_action() { this->parent_->factory_reset(); }
} // namespace ld2450
} // namespace esphome

View File

@@ -0,0 +1,9 @@
#include "reset_button.h"
namespace esphome {
namespace ld2450 {
void ResetButton::press_action() { this->parent_->factory_reset(); }
} // namespace ld2450
} // namespace esphome

View File

@@ -6,9 +6,9 @@
namespace esphome { namespace esphome {
namespace ld2450 { namespace ld2450 {
class FactoryResetButton : public button::Button, public Parented<LD2450Component> { class ResetButton : public button::Button, public Parented<LD2450Component> {
public: public:
FactoryResetButton() = default; ResetButton() = default;
protected: protected:
void press_action() override; void press_action() override;

View File

@@ -1,6 +1,5 @@
#include "ld2450.h" #include "ld2450.h"
#include <utility> #include <utility>
#include <cmath>
#ifdef USE_NUMBER #ifdef USE_NUMBER
#include "esphome/components/number/number.h" #include "esphome/components/number/number.h"
#endif #endif
@@ -18,10 +17,11 @@ namespace esphome {
namespace ld2450 { namespace ld2450 {
static const char *const TAG = "ld2450"; static const char *const TAG = "ld2450";
static const char *const NO_MAC = "08:05:04:03:02:01";
static const char *const UNKNOWN_MAC = "unknown"; static const char *const UNKNOWN_MAC = "unknown";
static const char *const VERSION_FMT = "%u.%02X.%02X%02X%02X%02X"; static const char *const VERSION_FMT = "%u.%02X.%02X%02X%02X%02X";
enum BaudRate : uint8_t { enum BaudRateStructure : uint8_t {
BAUD_RATE_9600 = 1, BAUD_RATE_9600 = 1,
BAUD_RATE_19200 = 2, BAUD_RATE_19200 = 2,
BAUD_RATE_38400 = 3, BAUD_RATE_38400 = 3,
@@ -32,13 +32,14 @@ enum BaudRate : uint8_t {
BAUD_RATE_460800 = 8 BAUD_RATE_460800 = 8
}; };
enum ZoneType : uint8_t { // Zone type struct
enum ZoneTypeStructure : uint8_t {
ZONE_DISABLED = 0, ZONE_DISABLED = 0,
ZONE_DETECTION = 1, ZONE_DETECTION = 1,
ZONE_FILTER = 2, ZONE_FILTER = 2,
}; };
enum PeriodicData : uint8_t { enum PeriodicDataStructure : uint8_t {
TARGET_X = 4, TARGET_X = 4,
TARGET_Y = 6, TARGET_Y = 6,
TARGET_SPEED = 8, TARGET_SPEED = 8,
@@ -46,12 +47,12 @@ enum PeriodicData : uint8_t {
}; };
enum PeriodicDataValue : uint8_t { enum PeriodicDataValue : uint8_t {
HEADER = 0xAA, HEAD = 0xAA,
FOOTER = 0x55, END = 0x55,
CHECK = 0x00, CHECK = 0x00,
}; };
enum AckData : uint8_t { enum AckDataStructure : uint8_t {
COMMAND = 6, COMMAND = 6,
COMMAND_STATUS = 7, COMMAND_STATUS = 7,
}; };
@@ -59,11 +60,11 @@ enum AckData : uint8_t {
// Memory-efficient lookup tables // Memory-efficient lookup tables
struct StringToUint8 { struct StringToUint8 {
const char *str; const char *str;
const uint8_t value; uint8_t value;
}; };
struct Uint8ToString { struct Uint8ToString {
const uint8_t value; uint8_t value;
const char *str; const char *str;
}; };
@@ -73,13 +74,6 @@ constexpr StringToUint8 BAUD_RATES_BY_STR[] = {
{"256000", BAUD_RATE_256000}, {"460800", BAUD_RATE_460800}, {"256000", BAUD_RATE_256000}, {"460800", BAUD_RATE_460800},
}; };
constexpr Uint8ToString DIRECTION_BY_UINT[] = {
{DIRECTION_APPROACHING, "Approaching"},
{DIRECTION_MOVING_AWAY, "Moving away"},
{DIRECTION_STATIONARY, "Stationary"},
{DIRECTION_NA, "NA"},
};
constexpr Uint8ToString ZONE_TYPE_BY_UINT[] = { constexpr Uint8ToString ZONE_TYPE_BY_UINT[] = {
{ZONE_DISABLED, "Disabled"}, {ZONE_DISABLED, "Disabled"},
{ZONE_DETECTION, "Detection"}, {ZONE_DETECTION, "Detection"},
@@ -109,38 +103,36 @@ template<size_t N> const char *find_str(const Uint8ToString (&arr)[N], uint8_t v
return ""; // Not found return ""; // Not found
} }
// LD2450 serial command header & footer
static const uint8_t CMD_FRAME_HEADER[4] = {0xFD, 0xFC, 0xFB, 0xFA};
static const uint8_t CMD_FRAME_END[4] = {0x04, 0x03, 0x02, 0x01};
// LD2450 UART Serial Commands // LD2450 UART Serial Commands
static constexpr uint8_t CMD_ENABLE_CONF = 0xFF; static const uint8_t CMD_ENABLE_CONF = 0xFF;
static constexpr uint8_t CMD_DISABLE_CONF = 0xFE; static const uint8_t CMD_DISABLE_CONF = 0xFE;
static constexpr uint8_t CMD_QUERY_VERSION = 0xA0; static const uint8_t CMD_VERSION = 0xA0;
static constexpr uint8_t CMD_QUERY_MAC_ADDRESS = 0xA5; static const uint8_t CMD_MAC = 0xA5;
static constexpr uint8_t CMD_RESET = 0xA2; static const uint8_t CMD_RESET = 0xA2;
static constexpr uint8_t CMD_RESTART = 0xA3; static const uint8_t CMD_RESTART = 0xA3;
static constexpr uint8_t CMD_BLUETOOTH = 0xA4; static const uint8_t CMD_BLUETOOTH = 0xA4;
static constexpr uint8_t CMD_SINGLE_TARGET_MODE = 0x80; static const uint8_t CMD_SINGLE_TARGET_MODE = 0x80;
static constexpr uint8_t CMD_MULTI_TARGET_MODE = 0x90; static const uint8_t CMD_MULTI_TARGET_MODE = 0x90;
static constexpr uint8_t CMD_QUERY_TARGET_MODE = 0x91; static const uint8_t CMD_QUERY_TARGET_MODE = 0x91;
static constexpr uint8_t CMD_SET_BAUD_RATE = 0xA1; static const uint8_t CMD_SET_BAUD_RATE = 0xA1;
static constexpr uint8_t CMD_QUERY_ZONE = 0xC1; static const uint8_t CMD_QUERY_ZONE = 0xC1;
static constexpr uint8_t CMD_SET_ZONE = 0xC2; static const uint8_t CMD_SET_ZONE = 0xC2;
// Header & Footer size
static constexpr uint8_t HEADER_FOOTER_SIZE = 4;
// Command Header & Footer
static constexpr uint8_t CMD_FRAME_HEADER[HEADER_FOOTER_SIZE] = {0xFD, 0xFC, 0xFB, 0xFA};
static constexpr uint8_t CMD_FRAME_FOOTER[HEADER_FOOTER_SIZE] = {0x04, 0x03, 0x02, 0x01};
// Data Header & Footer
static constexpr uint8_t DATA_FRAME_HEADER[HEADER_FOOTER_SIZE] = {0xAA, 0xFF, 0x03, 0x00};
static constexpr uint8_t DATA_FRAME_FOOTER[2] = {0x55, 0xCC};
// MAC address the module uses when Bluetooth is disabled
static constexpr uint8_t NO_MAC[] = {0x08, 0x05, 0x04, 0x03, 0x02, 0x01};
static inline uint16_t convert_seconds_to_ms(uint16_t value) { return value * 1000; }; static inline uint16_t convert_seconds_to_ms(uint16_t value) { return value * 1000; };
static inline std::string convert_signed_int_to_hex(int value) {
auto value_as_str = str_snprintf("%04x", 4, value & 0xFFFF);
return value_as_str;
}
static inline void convert_int_values_to_hex(const int *values, uint8_t *bytes) { static inline void convert_int_values_to_hex(const int *values, uint8_t *bytes) {
for (uint8_t i = 0; i < 4; i++) { for (int i = 0; i < 4; i++) {
uint16_t val = values[i] & 0xFFFF; std::string temp_hex = convert_signed_int_to_hex(values[i]);
bytes[i * 2] = val & 0xFF; // Store low byte first (little-endian) bytes[i * 2] = std::stoi(temp_hex.substr(2, 2), nullptr, 16); // Store high byte
bytes[i * 2 + 1] = (val >> 8) & 0xFF; // Store high byte second bytes[i * 2 + 1] = std::stoi(temp_hex.substr(0, 2), nullptr, 16); // Store low byte
} }
} }
@@ -178,13 +170,18 @@ static inline float calculate_angle(float base, float hypotenuse) {
return angle_degrees; return angle_degrees;
} }
static bool validate_header_footer(const uint8_t *header_footer, const uint8_t *buffer) { static inline std::string get_direction(int16_t speed) {
for (uint8_t i = 0; i < HEADER_FOOTER_SIZE; i++) { static const char *const APPROACHING = "Approaching";
if (header_footer[i] != buffer[i]) { static const char *const MOVING_AWAY = "Moving away";
return false; // Mismatch in header/footer static const char *const STATIONARY = "Stationary";
}
if (speed > 0) {
return MOVING_AWAY;
} }
return true; // Valid header/footer if (speed < 0) {
return APPROACHING;
}
return STATIONARY;
} }
void LD2450Component::setup() { void LD2450Component::setup() {
@@ -199,93 +196,84 @@ void LD2450Component::setup() {
} }
void LD2450Component::dump_config() { void LD2450Component::dump_config() {
std::string mac_str = ESP_LOGCONFIG(TAG, "LD2450:");
mac_address_is_valid(this->mac_address_) ? format_mac_address_pretty(this->mac_address_) : UNKNOWN_MAC;
std::string version = str_sprintf(VERSION_FMT, this->version_[1], this->version_[0], this->version_[5],
this->version_[4], this->version_[3], this->version_[2]);
ESP_LOGCONFIG(TAG,
"LD2450:\n"
" Firmware version: %s\n"
" MAC address: %s\n"
" Throttle: %u ms",
version.c_str(), mac_str.c_str(), this->throttle_);
#ifdef USE_BINARY_SENSOR #ifdef USE_BINARY_SENSOR
ESP_LOGCONFIG(TAG, "Binary Sensors:"); LOG_BINARY_SENSOR(" ", "TargetBinarySensor", this->target_binary_sensor_);
LOG_BINARY_SENSOR(" ", "MovingTarget", this->moving_target_binary_sensor_); LOG_BINARY_SENSOR(" ", "MovingTargetBinarySensor", this->moving_target_binary_sensor_);
LOG_BINARY_SENSOR(" ", "StillTarget", this->still_target_binary_sensor_); LOG_BINARY_SENSOR(" ", "StillTargetBinarySensor", this->still_target_binary_sensor_);
LOG_BINARY_SENSOR(" ", "Target", this->target_binary_sensor_); #endif
#ifdef USE_SWITCH
LOG_SWITCH(" ", "BluetoothSwitch", this->bluetooth_switch_);
LOG_SWITCH(" ", "MultiTargetSwitch", this->multi_target_switch_);
#endif
#ifdef USE_BUTTON
LOG_BUTTON(" ", "ResetButton", this->reset_button_);
LOG_BUTTON(" ", "RestartButton", this->restart_button_);
#endif #endif
#ifdef USE_SENSOR #ifdef USE_SENSOR
ESP_LOGCONFIG(TAG, "Sensors:"); LOG_SENSOR(" ", "TargetCountSensor", this->target_count_sensor_);
LOG_SENSOR(" ", "MovingTargetCount", this->moving_target_count_sensor_); LOG_SENSOR(" ", "StillTargetCountSensor", this->still_target_count_sensor_);
LOG_SENSOR(" ", "StillTargetCount", this->still_target_count_sensor_); LOG_SENSOR(" ", "MovingTargetCountSensor", this->moving_target_count_sensor_);
LOG_SENSOR(" ", "TargetCount", this->target_count_sensor_);
for (sensor::Sensor *s : this->move_x_sensors_) { for (sensor::Sensor *s : this->move_x_sensors_) {
LOG_SENSOR(" ", "TargetX", s); LOG_SENSOR(" ", "NthTargetXSensor", s);
} }
for (sensor::Sensor *s : this->move_y_sensors_) { for (sensor::Sensor *s : this->move_y_sensors_) {
LOG_SENSOR(" ", "TargetY", s); LOG_SENSOR(" ", "NthTargetYSensor", s);
}
for (sensor::Sensor *s : this->move_angle_sensors_) {
LOG_SENSOR(" ", "TargetAngle", s);
}
for (sensor::Sensor *s : this->move_distance_sensors_) {
LOG_SENSOR(" ", "TargetDistance", s);
}
for (sensor::Sensor *s : this->move_resolution_sensors_) {
LOG_SENSOR(" ", "TargetResolution", s);
} }
for (sensor::Sensor *s : this->move_speed_sensors_) { for (sensor::Sensor *s : this->move_speed_sensors_) {
LOG_SENSOR(" ", "TargetSpeed", s); LOG_SENSOR(" ", "NthTargetSpeedSensor", s);
}
for (sensor::Sensor *s : this->move_angle_sensors_) {
LOG_SENSOR(" ", "NthTargetAngleSensor", s);
}
for (sensor::Sensor *s : this->move_distance_sensors_) {
LOG_SENSOR(" ", "NthTargetDistanceSensor", s);
}
for (sensor::Sensor *s : this->move_resolution_sensors_) {
LOG_SENSOR(" ", "NthTargetResolutionSensor", s);
} }
for (sensor::Sensor *s : this->zone_target_count_sensors_) { for (sensor::Sensor *s : this->zone_target_count_sensors_) {
LOG_SENSOR(" ", "ZoneTargetCount", s); LOG_SENSOR(" ", "NthZoneTargetCountSensor", s);
}
for (sensor::Sensor *s : this->zone_moving_target_count_sensors_) {
LOG_SENSOR(" ", "ZoneMovingTargetCount", s);
} }
for (sensor::Sensor *s : this->zone_still_target_count_sensors_) { for (sensor::Sensor *s : this->zone_still_target_count_sensors_) {
LOG_SENSOR(" ", "ZoneStillTargetCount", s); LOG_SENSOR(" ", "NthZoneStillTargetCountSensor", s);
}
for (sensor::Sensor *s : this->zone_moving_target_count_sensors_) {
LOG_SENSOR(" ", "NthZoneMovingTargetCountSensor", s);
} }
#endif #endif
#ifdef USE_TEXT_SENSOR #ifdef USE_TEXT_SENSOR
ESP_LOGCONFIG(TAG, "Text Sensors:"); LOG_TEXT_SENSOR(" ", "VersionTextSensor", this->version_text_sensor_);
LOG_TEXT_SENSOR(" ", "Version", this->version_text_sensor_); LOG_TEXT_SENSOR(" ", "MacTextSensor", this->mac_text_sensor_);
LOG_TEXT_SENSOR(" ", "Mac", this->mac_text_sensor_);
for (text_sensor::TextSensor *s : this->direction_text_sensors_) { for (text_sensor::TextSensor *s : this->direction_text_sensors_) {
LOG_TEXT_SENSOR(" ", "Direction", s); LOG_TEXT_SENSOR(" ", "NthDirectionTextSensor", s);
} }
#endif #endif
#ifdef USE_NUMBER #ifdef USE_NUMBER
ESP_LOGCONFIG(TAG, "Numbers:");
LOG_NUMBER(" ", "PresenceTimeout", this->presence_timeout_number_);
for (auto n : this->zone_numbers_) { for (auto n : this->zone_numbers_) {
LOG_NUMBER(" ", "ZoneX1", n.x1); LOG_NUMBER(" ", "ZoneX1Number", n.x1);
LOG_NUMBER(" ", "ZoneY1", n.y1); LOG_NUMBER(" ", "ZoneY1Number", n.y1);
LOG_NUMBER(" ", "ZoneX2", n.x2); LOG_NUMBER(" ", "ZoneX2Number", n.x2);
LOG_NUMBER(" ", "ZoneY2", n.y2); LOG_NUMBER(" ", "ZoneY2Number", n.y2);
} }
#endif #endif
#ifdef USE_SELECT #ifdef USE_SELECT
ESP_LOGCONFIG(TAG, "Selects:"); LOG_SELECT(" ", "BaudRateSelect", this->baud_rate_select_);
LOG_SELECT(" ", "BaudRate", this->baud_rate_select_); LOG_SELECT(" ", "ZoneTypeSelect", this->zone_type_select_);
LOG_SELECT(" ", "ZoneType", this->zone_type_select_);
#endif #endif
#ifdef USE_SWITCH #ifdef USE_NUMBER
ESP_LOGCONFIG(TAG, "Switches:"); LOG_NUMBER(" ", "PresenceTimeoutNumber", this->presence_timeout_number_);
LOG_SWITCH(" ", "Bluetooth", this->bluetooth_switch_);
LOG_SWITCH(" ", "MultiTarget", this->multi_target_switch_);
#endif
#ifdef USE_BUTTON
ESP_LOGCONFIG(TAG, "Buttons:");
LOG_BUTTON(" ", "FactoryReset", this->factory_reset_button_);
LOG_BUTTON(" ", "Restart", this->restart_button_);
#endif #endif
ESP_LOGCONFIG(TAG,
" Throttle: %ums\n"
" MAC Address: %s\n"
" Firmware version: %s",
this->throttle_, this->mac_ == NO_MAC ? UNKNOWN_MAC : this->mac_.c_str(), this->version_.c_str());
} }
void LD2450Component::loop() { void LD2450Component::loop() {
while (this->available()) { while (this->available()) {
this->readline_(this->read()); this->readline_(read(), this->buffer_data_, MAX_LINE_LENGTH);
} }
} }
@@ -320,7 +308,7 @@ void LD2450Component::set_radar_zone(int32_t zone_type, int32_t zone1_x1, int32_
this->zone_type_ = zone_type; this->zone_type_ = zone_type;
int zone_parameters[12] = {zone1_x1, zone1_y1, zone1_x2, zone1_y2, zone2_x1, zone2_y1, int zone_parameters[12] = {zone1_x1, zone1_y1, zone1_x2, zone1_y2, zone2_x1, zone2_y1,
zone2_x2, zone2_y2, zone3_x1, zone3_y1, zone3_x2, zone3_y2}; zone2_x2, zone2_y2, zone3_x1, zone3_y1, zone3_x2, zone3_y2};
for (uint8_t i = 0; i < MAX_ZONES; i++) { for (int i = 0; i < MAX_ZONES; i++) {
this->zone_config_[i].x1 = zone_parameters[i * 4]; this->zone_config_[i].x1 = zone_parameters[i * 4];
this->zone_config_[i].y1 = zone_parameters[i * 4 + 1]; this->zone_config_[i].y1 = zone_parameters[i * 4 + 1];
this->zone_config_[i].x2 = zone_parameters[i * 4 + 2]; this->zone_config_[i].x2 = zone_parameters[i * 4 + 2];
@@ -334,15 +322,15 @@ void LD2450Component::send_set_zone_command_() {
uint8_t cmd_value[26] = {}; uint8_t cmd_value[26] = {};
uint8_t zone_type_bytes[2] = {static_cast<uint8_t>(this->zone_type_), 0x00}; uint8_t zone_type_bytes[2] = {static_cast<uint8_t>(this->zone_type_), 0x00};
uint8_t area_config[24] = {}; uint8_t area_config[24] = {};
for (uint8_t i = 0; i < MAX_ZONES; i++) { for (int i = 0; i < MAX_ZONES; i++) {
int values[4] = {this->zone_config_[i].x1, this->zone_config_[i].y1, this->zone_config_[i].x2, int values[4] = {this->zone_config_[i].x1, this->zone_config_[i].y1, this->zone_config_[i].x2,
this->zone_config_[i].y2}; this->zone_config_[i].y2};
ld2450::convert_int_values_to_hex(values, area_config + (i * 8)); ld2450::convert_int_values_to_hex(values, area_config + (i * 8));
} }
std::memcpy(cmd_value, zone_type_bytes, sizeof(zone_type_bytes)); std::memcpy(cmd_value, zone_type_bytes, 2);
std::memcpy(cmd_value + 2, area_config, sizeof(area_config)); std::memcpy(cmd_value + 2, area_config, 24);
this->set_config_mode_(true); this->set_config_mode_(true);
this->send_command_(CMD_SET_ZONE, cmd_value, sizeof(cmd_value)); this->send_command_(CMD_SET_ZONE, cmd_value, 26);
this->set_config_mode_(false); this->set_config_mode_(false);
} }
@@ -358,14 +346,14 @@ bool LD2450Component::get_timeout_status_(uint32_t check_millis) {
} }
// Extract, store and publish zone details LD2450 buffer // Extract, store and publish zone details LD2450 buffer
void LD2450Component::process_zone_() { void LD2450Component::process_zone_(uint8_t *buffer) {
uint8_t index, start; uint8_t index, start;
for (index = 0; index < MAX_ZONES; index++) { for (index = 0; index < MAX_ZONES; index++) {
start = 12 + index * 8; start = 12 + index * 8;
this->zone_config_[index].x1 = ld2450::hex_to_signed_int(this->buffer_data_, start); this->zone_config_[index].x1 = ld2450::hex_to_signed_int(buffer, start);
this->zone_config_[index].y1 = ld2450::hex_to_signed_int(this->buffer_data_, start + 2); this->zone_config_[index].y1 = ld2450::hex_to_signed_int(buffer, start + 2);
this->zone_config_[index].x2 = ld2450::hex_to_signed_int(this->buffer_data_, start + 4); this->zone_config_[index].x2 = ld2450::hex_to_signed_int(buffer, start + 4);
this->zone_config_[index].y2 = ld2450::hex_to_signed_int(this->buffer_data_, start + 6); this->zone_config_[index].y2 = ld2450::hex_to_signed_int(buffer, start + 6);
#ifdef USE_NUMBER #ifdef USE_NUMBER
// only one null check as all coordinates are required for a single zone // only one null check as all coordinates are required for a single zone
if (this->zone_numbers_[index].x1 != nullptr) { if (this->zone_numbers_[index].x1 != nullptr) {
@@ -411,25 +399,27 @@ void LD2450Component::restart_and_read_all_info() {
// Send command with values to LD2450 // Send command with values to LD2450
void LD2450Component::send_command_(uint8_t command, const uint8_t *command_value, uint8_t command_value_len) { void LD2450Component::send_command_(uint8_t command, const uint8_t *command_value, uint8_t command_value_len) {
ESP_LOGV(TAG, "Sending COMMAND %02X", command); ESP_LOGV(TAG, "Sending command %02X", command);
// frame header bytes // frame header
this->write_array(CMD_FRAME_HEADER, sizeof(CMD_FRAME_HEADER)); this->write_array(CMD_FRAME_HEADER, 4);
// length bytes // length bytes
uint8_t len = 2; int len = 2;
if (command_value != nullptr) { if (command_value != nullptr) {
len += command_value_len; len += command_value_len;
} }
uint8_t len_cmd[] = {lowbyte(len), highbyte(len), command, 0x00}; this->write_byte(lowbyte(len));
this->write_array(len_cmd, sizeof(len_cmd)); this->write_byte(highbyte(len));
// command
this->write_byte(lowbyte(command));
this->write_byte(highbyte(command));
// command value bytes // command value bytes
if (command_value != nullptr) { if (command_value != nullptr) {
for (uint8_t i = 0; i < command_value_len; i++) { for (int i = 0; i < command_value_len; i++) {
this->write_byte(command_value[i]); this->write_byte(command_value[i]);
} }
} }
// frame footer bytes // footer
this->write_array(CMD_FRAME_FOOTER, sizeof(CMD_FRAME_FOOTER)); this->write_array(CMD_FRAME_END, 4);
// FIXME to remove // FIXME to remove
delay(50); // NOLINT delay(50); // NOLINT
} }
@@ -437,23 +427,25 @@ void LD2450Component::send_command_(uint8_t command, const uint8_t *command_valu
// LD2450 Radar data message: // LD2450 Radar data message:
// [AA FF 03 00] [0E 03 B1 86 10 00 40 01] [00 00 00 00 00 00 00 00] [00 00 00 00 00 00 00 00] [55 CC] // [AA FF 03 00] [0E 03 B1 86 10 00 40 01] [00 00 00 00 00 00 00 00] [00 00 00 00 00 00 00 00] [55 CC]
// Header Target 1 Target 2 Target 3 End // Header Target 1 Target 2 Target 3 End
void LD2450Component::handle_periodic_data_() { void LD2450Component::handle_periodic_data_(uint8_t *buffer, uint8_t len) {
// Early throttle check - moved before any processing to save CPU cycles if (len < 29) { // header (4 bytes) + 8 x 3 target data + footer (2 bytes)
if (App.get_loop_component_start_time() - this->last_periodic_millis_ < this->throttle_) { ESP_LOGE(TAG, "Invalid message length");
return;
}
if (buffer[0] != 0xAA || buffer[1] != 0xFF || buffer[2] != 0x03 || buffer[3] != 0x00) { // header
ESP_LOGE(TAG, "Invalid message header");
return;
}
if (buffer[len - 2] != 0x55 || buffer[len - 1] != 0xCC) { // footer
ESP_LOGE(TAG, "Invalid message footer");
return; return;
} }
if (this->buffer_pos_ < 29) { // header (4 bytes) + 8 x 3 target data + footer (2 bytes) if (App.get_loop_component_start_time() - this->last_periodic_millis_ < this->throttle_) {
ESP_LOGE(TAG, "Invalid length"); ESP_LOGV(TAG, "Throttling: %d", this->throttle_);
return; return;
} }
if (!ld2450::validate_header_footer(DATA_FRAME_HEADER, this->buffer_data_) ||
this->buffer_data_[this->buffer_pos_ - 2] != DATA_FRAME_FOOTER[0] ||
this->buffer_data_[this->buffer_pos_ - 1] != DATA_FRAME_FOOTER[1]) {
ESP_LOGE(TAG, "Invalid header/footer");
return;
}
// Save the timestamp after validating the frame so, if invalid, we'll take the next frame immediately
this->last_periodic_millis_ = App.get_loop_component_start_time(); this->last_periodic_millis_ = App.get_loop_component_start_time();
int16_t target_count = 0; int16_t target_count = 0;
@@ -461,13 +453,13 @@ void LD2450Component::handle_periodic_data_() {
int16_t moving_target_count = 0; int16_t moving_target_count = 0;
int16_t start = 0; int16_t start = 0;
int16_t val = 0; int16_t val = 0;
uint8_t index = 0;
int16_t tx = 0; int16_t tx = 0;
int16_t ty = 0; int16_t ty = 0;
int16_t td = 0; int16_t td = 0;
int16_t ts = 0; int16_t ts = 0;
int16_t angle = 0; int16_t angle = 0;
uint8_t index = 0; std::string direction{};
Direction direction{DIRECTION_UNDEFINED};
bool is_moving = false; bool is_moving = false;
#if defined(USE_BINARY_SENSOR) || defined(USE_SENSOR) || defined(USE_TEXT_SENSOR) #if defined(USE_BINARY_SENSOR) || defined(USE_SENSOR) || defined(USE_TEXT_SENSOR)
@@ -479,38 +471,29 @@ void LD2450Component::handle_periodic_data_() {
is_moving = false; is_moving = false;
sensor::Sensor *sx = this->move_x_sensors_[index]; sensor::Sensor *sx = this->move_x_sensors_[index];
if (sx != nullptr) { if (sx != nullptr) {
val = ld2450::decode_coordinate(this->buffer_data_[start], this->buffer_data_[start + 1]); val = ld2450::decode_coordinate(buffer[start], buffer[start + 1]);
tx = val; tx = val;
if (this->cached_target_data_[index].x != val) { sx->publish_state(val);
sx->publish_state(val);
this->cached_target_data_[index].x = val;
}
} }
// Y // Y
start = TARGET_Y + index * 8; start = TARGET_Y + index * 8;
sensor::Sensor *sy = this->move_y_sensors_[index]; sensor::Sensor *sy = this->move_y_sensors_[index];
if (sy != nullptr) { if (sy != nullptr) {
val = ld2450::decode_coordinate(this->buffer_data_[start], this->buffer_data_[start + 1]); val = ld2450::decode_coordinate(buffer[start], buffer[start + 1]);
ty = val; ty = val;
if (this->cached_target_data_[index].y != val) { sy->publish_state(val);
sy->publish_state(val);
this->cached_target_data_[index].y = val;
}
} }
// RESOLUTION // RESOLUTION
start = TARGET_RESOLUTION + index * 8; start = TARGET_RESOLUTION + index * 8;
sensor::Sensor *sr = this->move_resolution_sensors_[index]; sensor::Sensor *sr = this->move_resolution_sensors_[index];
if (sr != nullptr) { if (sr != nullptr) {
val = (this->buffer_data_[start + 1] << 8) | this->buffer_data_[start]; val = (buffer[start + 1] << 8) | buffer[start];
if (this->cached_target_data_[index].resolution != val) { sr->publish_state(val);
sr->publish_state(val);
this->cached_target_data_[index].resolution = val;
}
} }
#endif #endif
// SPEED // SPEED
start = TARGET_SPEED + index * 8; start = TARGET_SPEED + index * 8;
val = ld2450::decode_speed(this->buffer_data_[start], this->buffer_data_[start + 1]); val = ld2450::decode_speed(buffer[start], buffer[start + 1]);
ts = val; ts = val;
if (val) { if (val) {
is_moving = true; is_moving = true;
@@ -519,17 +502,13 @@ void LD2450Component::handle_periodic_data_() {
#ifdef USE_SENSOR #ifdef USE_SENSOR
sensor::Sensor *ss = this->move_speed_sensors_[index]; sensor::Sensor *ss = this->move_speed_sensors_[index];
if (ss != nullptr) { if (ss != nullptr) {
if (this->cached_target_data_[index].speed != val) { ss->publish_state(val);
ss->publish_state(val);
this->cached_target_data_[index].speed = val;
}
} }
#endif #endif
// DISTANCE // DISTANCE
// Optimized: use already decoded tx and ty values, replace pow() with multiplication val = (uint16_t) sqrt(
int32_t x_squared = (int32_t) tx * tx; pow(ld2450::decode_coordinate(buffer[TARGET_X + index * 8], buffer[(TARGET_X + index * 8) + 1]), 2) +
int32_t y_squared = (int32_t) ty * ty; pow(ld2450::decode_coordinate(buffer[TARGET_Y + index * 8], buffer[(TARGET_Y + index * 8) + 1]), 2));
val = (uint16_t) sqrt(x_squared + y_squared);
td = val; td = val;
if (val > 0) { if (val > 0) {
target_count++; target_count++;
@@ -537,42 +516,27 @@ void LD2450Component::handle_periodic_data_() {
#ifdef USE_SENSOR #ifdef USE_SENSOR
sensor::Sensor *sd = this->move_distance_sensors_[index]; sensor::Sensor *sd = this->move_distance_sensors_[index];
if (sd != nullptr) { if (sd != nullptr) {
if (this->cached_target_data_[index].distance != val) { sd->publish_state(val);
sd->publish_state(val);
this->cached_target_data_[index].distance = val;
}
} }
// ANGLE // ANGLE
angle = ld2450::calculate_angle(static_cast<float>(ty), static_cast<float>(td)); angle = calculate_angle(static_cast<float>(ty), static_cast<float>(td));
if (tx > 0) { if (tx > 0) {
angle = angle * -1; angle = angle * -1;
} }
sensor::Sensor *sa = this->move_angle_sensors_[index]; sensor::Sensor *sa = this->move_angle_sensors_[index];
if (sa != nullptr) { if (sa != nullptr) {
if (std::isnan(this->cached_target_data_[index].angle) || sa->publish_state(angle);
std::abs(this->cached_target_data_[index].angle - angle) > 0.1f) {
sa->publish_state(angle);
this->cached_target_data_[index].angle = angle;
}
} }
#endif #endif
#ifdef USE_TEXT_SENSOR #ifdef USE_TEXT_SENSOR
// DIRECTION // DIRECTION
direction = get_direction(ts);
if (td == 0) { if (td == 0) {
direction = DIRECTION_NA; direction = "NA";
} else if (ts > 0) {
direction = DIRECTION_MOVING_AWAY;
} else if (ts < 0) {
direction = DIRECTION_APPROACHING;
} else {
direction = DIRECTION_STATIONARY;
} }
text_sensor::TextSensor *tsd = this->direction_text_sensors_[index]; text_sensor::TextSensor *tsd = this->direction_text_sensors_[index];
if (tsd != nullptr) { if (tsd != nullptr) {
if (this->cached_target_data_[index].direction != direction) { tsd->publish_state(direction);
tsd->publish_state(find_str(ld2450::DIRECTION_BY_UINT, direction));
this->cached_target_data_[index].direction = direction;
}
} }
#endif #endif
@@ -599,50 +563,32 @@ void LD2450Component::handle_periodic_data_() {
// Publish Still Target Count in Zones // Publish Still Target Count in Zones
sensor::Sensor *szstc = this->zone_still_target_count_sensors_[index]; sensor::Sensor *szstc = this->zone_still_target_count_sensors_[index];
if (szstc != nullptr) { if (szstc != nullptr) {
if (this->cached_zone_data_[index].still_count != zone_still_targets) { szstc->publish_state(zone_still_targets);
szstc->publish_state(zone_still_targets);
this->cached_zone_data_[index].still_count = zone_still_targets;
}
} }
// Publish Moving Target Count in Zones // Publish Moving Target Count in Zones
sensor::Sensor *szmtc = this->zone_moving_target_count_sensors_[index]; sensor::Sensor *szmtc = this->zone_moving_target_count_sensors_[index];
if (szmtc != nullptr) { if (szmtc != nullptr) {
if (this->cached_zone_data_[index].moving_count != zone_moving_targets) { szmtc->publish_state(zone_moving_targets);
szmtc->publish_state(zone_moving_targets);
this->cached_zone_data_[index].moving_count = zone_moving_targets;
}
} }
// Publish All Target Count in Zones // Publish All Target Count in Zones
sensor::Sensor *sztc = this->zone_target_count_sensors_[index]; sensor::Sensor *sztc = this->zone_target_count_sensors_[index];
if (sztc != nullptr) { if (sztc != nullptr) {
if (this->cached_zone_data_[index].total_count != zone_all_targets) { sztc->publish_state(zone_all_targets);
sztc->publish_state(zone_all_targets);
this->cached_zone_data_[index].total_count = zone_all_targets;
}
} }
} // End loop thru zones } // End loop thru zones
// Target Count // Target Count
if (this->target_count_sensor_ != nullptr) { if (this->target_count_sensor_ != nullptr) {
if (this->cached_global_data_.target_count != target_count) { this->target_count_sensor_->publish_state(target_count);
this->target_count_sensor_->publish_state(target_count);
this->cached_global_data_.target_count = target_count;
}
} }
// Still Target Count // Still Target Count
if (this->still_target_count_sensor_ != nullptr) { if (this->still_target_count_sensor_ != nullptr) {
if (this->cached_global_data_.still_count != still_target_count) { this->still_target_count_sensor_->publish_state(still_target_count);
this->still_target_count_sensor_->publish_state(still_target_count);
this->cached_global_data_.still_count = still_target_count;
}
} }
// Moving Target Count // Moving Target Count
if (this->moving_target_count_sensor_ != nullptr) { if (this->moving_target_count_sensor_ != nullptr) {
if (this->cached_global_data_.moving_count != moving_target_count) { this->moving_target_count_sensor_->publish_state(moving_target_count);
this->moving_target_count_sensor_->publish_state(moving_target_count);
this->cached_global_data_.moving_count = moving_target_count;
}
} }
#endif #endif
@@ -694,139 +640,117 @@ void LD2450Component::handle_periodic_data_() {
#endif #endif
} }
bool LD2450Component::handle_ack_data_() { bool LD2450Component::handle_ack_data_(uint8_t *buffer, uint8_t len) {
ESP_LOGV(TAG, "Handling ACK DATA for COMMAND %02X", this->buffer_data_[COMMAND]); ESP_LOGV(TAG, "Handling ack data for command %02X", buffer[COMMAND]);
if (this->buffer_pos_ < 10) { if (len < 10) {
ESP_LOGE(TAG, "Invalid length"); ESP_LOGE(TAG, "Invalid ack length");
return true; return true;
} }
if (!ld2450::validate_header_footer(CMD_FRAME_HEADER, this->buffer_data_)) { if (buffer[0] != 0xFD || buffer[1] != 0xFC || buffer[2] != 0xFB || buffer[3] != 0xFA) { // frame header
ESP_LOGW(TAG, "Invalid header: %s", format_hex_pretty(this->buffer_data_, HEADER_FOOTER_SIZE).c_str()); ESP_LOGE(TAG, "Invalid ack header (command %02X)", buffer[COMMAND]);
return true; return true;
} }
if (this->buffer_data_[COMMAND_STATUS] != 0x01) { if (buffer[COMMAND_STATUS] != 0x01) {
ESP_LOGE(TAG, "Invalid status"); ESP_LOGE(TAG, "Invalid ack status");
return true; return true;
} }
if (this->buffer_data_[8] || this->buffer_data_[9]) { if (buffer[8] || buffer[9]) {
ESP_LOGW(TAG, "Invalid command: %02X, %02X", this->buffer_data_[8], this->buffer_data_[9]); ESP_LOGE(TAG, "Last buffer was %u, %u", buffer[8], buffer[9]);
return true; return true;
} }
switch (this->buffer_data_[COMMAND]) { switch (buffer[COMMAND]) {
case CMD_ENABLE_CONF: case lowbyte(CMD_ENABLE_CONF):
ESP_LOGV(TAG, "Enable conf"); ESP_LOGV(TAG, "Enable conf command");
break; break;
case lowbyte(CMD_DISABLE_CONF):
case CMD_DISABLE_CONF: ESP_LOGV(TAG, "Disable conf command");
ESP_LOGV(TAG, "Disabled conf");
break; break;
case lowbyte(CMD_SET_BAUD_RATE):
case CMD_SET_BAUD_RATE: ESP_LOGV(TAG, "Baud rate change command");
ESP_LOGV(TAG, "Baud rate change");
#ifdef USE_SELECT #ifdef USE_SELECT
if (this->baud_rate_select_ != nullptr) { if (this->baud_rate_select_ != nullptr) {
ESP_LOGE(TAG, "Change baud rate to %s and reinstall", this->baud_rate_select_->state.c_str()); ESP_LOGV(TAG, "Change baud rate to %s", this->baud_rate_select_->state.c_str());
} }
#endif #endif
break; break;
case lowbyte(CMD_VERSION):
case CMD_QUERY_VERSION: { this->version_ = str_sprintf(VERSION_FMT, buffer[13], buffer[12], buffer[17], buffer[16], buffer[15], buffer[14]);
std::memcpy(this->version_, &this->buffer_data_[12], sizeof(this->version_)); ESP_LOGV(TAG, "Firmware version: %s", this->version_.c_str());
std::string version = str_sprintf(VERSION_FMT, this->version_[1], this->version_[0], this->version_[5],
this->version_[4], this->version_[3], this->version_[2]);
ESP_LOGV(TAG, "Firmware version: %s", version.c_str());
#ifdef USE_TEXT_SENSOR #ifdef USE_TEXT_SENSOR
if (this->version_text_sensor_ != nullptr) { if (this->version_text_sensor_ != nullptr) {
this->version_text_sensor_->publish_state(version); this->version_text_sensor_->publish_state(this->version_);
} }
#endif #endif
break; break;
} case lowbyte(CMD_MAC):
if (len < 20) {
case CMD_QUERY_MAC_ADDRESS: {
if (this->buffer_pos_ < 20) {
return false; return false;
} }
this->mac_ = format_mac_address_pretty(&buffer[10]);
this->bluetooth_on_ = std::memcmp(&this->buffer_data_[10], NO_MAC, sizeof(NO_MAC)) != 0; ESP_LOGV(TAG, "MAC address: %s", this->mac_.c_str());
if (this->bluetooth_on_) {
std::memcpy(this->mac_address_, &this->buffer_data_[10], sizeof(this->mac_address_));
}
std::string mac_str =
mac_address_is_valid(this->mac_address_) ? format_mac_address_pretty(this->mac_address_) : UNKNOWN_MAC;
ESP_LOGV(TAG, "MAC address: %s", mac_str.c_str());
#ifdef USE_TEXT_SENSOR #ifdef USE_TEXT_SENSOR
if (this->mac_text_sensor_ != nullptr) { if (this->mac_text_sensor_ != nullptr) {
this->mac_text_sensor_->publish_state(mac_str); this->mac_text_sensor_->publish_state(this->mac_ == NO_MAC ? UNKNOWN_MAC : this->mac_);
} }
#endif #endif
#ifdef USE_SWITCH #ifdef USE_SWITCH
if (this->bluetooth_switch_ != nullptr) { if (this->bluetooth_switch_ != nullptr) {
this->bluetooth_switch_->publish_state(this->bluetooth_on_); this->bluetooth_switch_->publish_state(this->mac_ != NO_MAC);
} }
#endif #endif
break; break;
} case lowbyte(CMD_BLUETOOTH):
ESP_LOGV(TAG, "Bluetooth command");
case CMD_BLUETOOTH:
ESP_LOGV(TAG, "Bluetooth");
break; break;
case lowbyte(CMD_SINGLE_TARGET_MODE):
case CMD_SINGLE_TARGET_MODE: ESP_LOGV(TAG, "Single target conf command");
ESP_LOGV(TAG, "Single target conf");
#ifdef USE_SWITCH #ifdef USE_SWITCH
if (this->multi_target_switch_ != nullptr) { if (this->multi_target_switch_ != nullptr) {
this->multi_target_switch_->publish_state(false); this->multi_target_switch_->publish_state(false);
} }
#endif #endif
break; break;
case lowbyte(CMD_MULTI_TARGET_MODE):
case CMD_MULTI_TARGET_MODE: ESP_LOGV(TAG, "Multi target conf command");
ESP_LOGV(TAG, "Multi target conf");
#ifdef USE_SWITCH #ifdef USE_SWITCH
if (this->multi_target_switch_ != nullptr) { if (this->multi_target_switch_ != nullptr) {
this->multi_target_switch_->publish_state(true); this->multi_target_switch_->publish_state(true);
} }
#endif #endif
break; break;
case lowbyte(CMD_QUERY_TARGET_MODE):
case CMD_QUERY_TARGET_MODE: ESP_LOGV(TAG, "Query target tracking mode command");
ESP_LOGV(TAG, "Query target tracking mode");
#ifdef USE_SWITCH #ifdef USE_SWITCH
if (this->multi_target_switch_ != nullptr) { if (this->multi_target_switch_ != nullptr) {
this->multi_target_switch_->publish_state(this->buffer_data_[10] == 0x02); this->multi_target_switch_->publish_state(buffer[10] == 0x02);
} }
#endif #endif
break; break;
case lowbyte(CMD_QUERY_ZONE):
case CMD_QUERY_ZONE: ESP_LOGV(TAG, "Query zone conf command");
ESP_LOGV(TAG, "Query zone conf"); this->zone_type_ = std::stoi(std::to_string(buffer[10]), nullptr, 16);
this->zone_type_ = std::stoi(std::to_string(this->buffer_data_[10]), nullptr, 16);
this->publish_zone_type(); this->publish_zone_type();
#ifdef USE_SELECT #ifdef USE_SELECT
if (this->zone_type_select_ != nullptr) { if (this->zone_type_select_ != nullptr) {
ESP_LOGV(TAG, "Change zone type to: %s", this->zone_type_select_->state.c_str()); ESP_LOGV(TAG, "Change zone type to: %s", this->zone_type_select_->state.c_str());
} }
#endif #endif
if (this->buffer_data_[10] == 0x00) { if (buffer[10] == 0x00) {
ESP_LOGV(TAG, "Zone: Disabled"); ESP_LOGV(TAG, "Zone: Disabled");
} }
if (this->buffer_data_[10] == 0x01) { if (buffer[10] == 0x01) {
ESP_LOGV(TAG, "Zone: Area detection"); ESP_LOGV(TAG, "Zone: Area detection");
} }
if (this->buffer_data_[10] == 0x02) { if (buffer[10] == 0x02) {
ESP_LOGV(TAG, "Zone: Area filter"); ESP_LOGV(TAG, "Zone: Area filter");
} }
this->process_zone_(); this->process_zone_(buffer);
break; break;
case lowbyte(CMD_SET_ZONE):
case CMD_SET_ZONE: ESP_LOGV(TAG, "Set zone conf command");
ESP_LOGV(TAG, "Set zone conf");
this->query_zone_info(); this->query_zone_info();
break; break;
default: default:
break; break;
} }
@@ -834,57 +758,55 @@ bool LD2450Component::handle_ack_data_() {
} }
// Read LD2450 buffer data // Read LD2450 buffer data
void LD2450Component::readline_(int readch) { void LD2450Component::readline_(int readch, uint8_t *buffer, uint8_t len) {
if (readch < 0) { if (readch < 0) {
return; // No data available return;
} }
if (this->buffer_pos_ < len - 1) {
if (this->buffer_pos_ < MAX_LINE_LENGTH - 1) { buffer[this->buffer_pos_++] = readch;
this->buffer_data_[this->buffer_pos_++] = readch; buffer[this->buffer_pos_] = 0;
this->buffer_data_[this->buffer_pos_] = 0;
} else { } else {
// We should never get here, but just in case...
ESP_LOGW(TAG, "Max command length exceeded; ignoring");
this->buffer_pos_ = 0; this->buffer_pos_ = 0;
} }
if (this->buffer_pos_ < 4) { if (this->buffer_pos_ < 4) {
return; // Not enough data to process yet return;
} }
if (this->buffer_data_[this->buffer_pos_ - 2] == DATA_FRAME_FOOTER[0] && if (buffer[this->buffer_pos_ - 2] == 0x55 && buffer[this->buffer_pos_ - 1] == 0xCC) {
this->buffer_data_[this->buffer_pos_ - 1] == DATA_FRAME_FOOTER[1]) { ESP_LOGV(TAG, "Handle periodic radar data");
ESP_LOGV(TAG, "Handling Periodic Data: %s", format_hex_pretty(this->buffer_data_, this->buffer_pos_).c_str()); this->handle_periodic_data_(buffer, this->buffer_pos_);
this->handle_periodic_data_();
this->buffer_pos_ = 0; // Reset position index for next frame this->buffer_pos_ = 0; // Reset position index for next frame
} else if (ld2450::validate_header_footer(CMD_FRAME_FOOTER, &this->buffer_data_[this->buffer_pos_ - 4])) { } else if (buffer[this->buffer_pos_ - 4] == 0x04 && buffer[this->buffer_pos_ - 3] == 0x03 &&
ESP_LOGV(TAG, "Handling Ack Data: %s", format_hex_pretty(this->buffer_data_, this->buffer_pos_).c_str()); buffer[this->buffer_pos_ - 2] == 0x02 && buffer[this->buffer_pos_ - 1] == 0x01) {
if (this->handle_ack_data_()) { ESP_LOGV(TAG, "Handle command ack data");
this->buffer_pos_ = 0; // Reset position index for next message if (this->handle_ack_data_(buffer, this->buffer_pos_)) {
this->buffer_pos_ = 0; // Reset position index for next frame
} else { } else {
ESP_LOGV(TAG, "Ack Data incomplete"); ESP_LOGV(TAG, "Command ack data invalid");
} }
} }
} }
// Set Config Mode - Pre-requisite sending commands // Set Config Mode - Pre-requisite sending commands
void LD2450Component::set_config_mode_(bool enable) { void LD2450Component::set_config_mode_(bool enable) {
const uint8_t cmd = enable ? CMD_ENABLE_CONF : CMD_DISABLE_CONF; uint8_t cmd = enable ? CMD_ENABLE_CONF : CMD_DISABLE_CONF;
const uint8_t cmd_value[2] = {0x01, 0x00}; uint8_t cmd_value[2] = {0x01, 0x00};
this->send_command_(cmd, enable ? cmd_value : nullptr, sizeof(cmd_value)); this->send_command_(cmd, enable ? cmd_value : nullptr, 2);
} }
// Set Bluetooth Enable/Disable // Set Bluetooth Enable/Disable
void LD2450Component::set_bluetooth(bool enable) { void LD2450Component::set_bluetooth(bool enable) {
this->set_config_mode_(true); this->set_config_mode_(true);
const uint8_t cmd_value[2] = {enable ? (uint8_t) 0x01 : (uint8_t) 0x00, 0x00}; uint8_t enable_cmd_value[2] = {0x01, 0x00};
this->send_command_(CMD_BLUETOOTH, cmd_value, sizeof(cmd_value)); uint8_t disable_cmd_value[2] = {0x00, 0x00};
this->send_command_(CMD_BLUETOOTH, enable ? enable_cmd_value : disable_cmd_value, 2);
this->set_timeout(200, [this]() { this->restart_and_read_all_info(); }); this->set_timeout(200, [this]() { this->restart_and_read_all_info(); });
} }
// Set Baud rate // Set Baud rate
void LD2450Component::set_baud_rate(const std::string &state) { void LD2450Component::set_baud_rate(const std::string &state) {
this->set_config_mode_(true); this->set_config_mode_(true);
const uint8_t cmd_value[2] = {find_uint8(BAUD_RATES_BY_STR, state), 0x00}; uint8_t cmd_value[2] = {find_uint8(BAUD_RATES_BY_STR, state), 0x00};
this->send_command_(CMD_SET_BAUD_RATE, cmd_value, sizeof(cmd_value)); this->send_command_(CMD_SET_BAUD_RATE, cmd_value, 2);
this->set_timeout(200, [this]() { this->restart_(); }); this->set_timeout(200, [this]() { this->restart_(); });
} }
@@ -925,12 +847,12 @@ void LD2450Component::factory_reset() {
void LD2450Component::restart_() { this->send_command_(CMD_RESTART, nullptr, 0); } void LD2450Component::restart_() { this->send_command_(CMD_RESTART, nullptr, 0); }
// Get LD2450 firmware version // Get LD2450 firmware version
void LD2450Component::get_version_() { this->send_command_(CMD_QUERY_VERSION, nullptr, 0); } void LD2450Component::get_version_() { this->send_command_(CMD_VERSION, nullptr, 0); }
// Get LD2450 mac address // Get LD2450 mac address
void LD2450Component::get_mac_() { void LD2450Component::get_mac_() {
uint8_t cmd_value[2] = {0x01, 0x00}; uint8_t cmd_value[2] = {0x01, 0x00};
this->send_command_(CMD_QUERY_MAC_ADDRESS, cmd_value, 2); this->send_command_(CMD_MAC, cmd_value, 2);
} }
// Query for target tracking mode // Query for target tracking mode

View File

@@ -5,8 +5,6 @@
#include "esphome/core/defines.h" #include "esphome/core/defines.h"
#include "esphome/core/helpers.h" #include "esphome/core/helpers.h"
#include "esphome/core/preferences.h" #include "esphome/core/preferences.h"
#include <limits>
#include <cmath>
#ifdef USE_SENSOR #ifdef USE_SENSOR
#include "esphome/components/sensor/sensor.h" #include "esphome/components/sensor/sensor.h"
#endif #endif
@@ -38,18 +36,10 @@ namespace ld2450 {
// Constants // Constants
static const uint8_t DEFAULT_PRESENCE_TIMEOUT = 5; // Timeout to reset presense status 5 sec. static const uint8_t DEFAULT_PRESENCE_TIMEOUT = 5; // Timeout to reset presense status 5 sec.
static const uint8_t MAX_LINE_LENGTH = 41; // Max characters for serial buffer static const uint8_t MAX_LINE_LENGTH = 60; // Max characters for serial buffer
static const uint8_t MAX_TARGETS = 3; // Max 3 Targets in LD2450 static const uint8_t MAX_TARGETS = 3; // Max 3 Targets in LD2450
static const uint8_t MAX_ZONES = 3; // Max 3 Zones in LD2450 static const uint8_t MAX_ZONES = 3; // Max 3 Zones in LD2450
enum Direction : uint8_t {
DIRECTION_APPROACHING = 0,
DIRECTION_MOVING_AWAY = 1,
DIRECTION_STATIONARY = 2,
DIRECTION_NA = 3,
DIRECTION_UNDEFINED = 4,
};
// Target coordinate struct // Target coordinate struct
struct Target { struct Target {
int16_t x; int16_t x;
@@ -75,22 +65,19 @@ struct ZoneOfNumbers {
#endif #endif
class LD2450Component : public Component, public uart::UARTDevice { class LD2450Component : public Component, public uart::UARTDevice {
#ifdef USE_SENSOR
SUB_SENSOR(target_count)
SUB_SENSOR(still_target_count)
SUB_SENSOR(moving_target_count)
#endif
#ifdef USE_BINARY_SENSOR #ifdef USE_BINARY_SENSOR
SUB_BINARY_SENSOR(target)
SUB_BINARY_SENSOR(moving_target) SUB_BINARY_SENSOR(moving_target)
SUB_BINARY_SENSOR(still_target) SUB_BINARY_SENSOR(still_target)
SUB_BINARY_SENSOR(target)
#endif
#ifdef USE_SENSOR
SUB_SENSOR(moving_target_count)
SUB_SENSOR(still_target_count)
SUB_SENSOR(target_count)
#endif #endif
#ifdef USE_TEXT_SENSOR #ifdef USE_TEXT_SENSOR
SUB_TEXT_SENSOR(mac)
SUB_TEXT_SENSOR(version) SUB_TEXT_SENSOR(version)
#endif SUB_TEXT_SENSOR(mac)
#ifdef USE_NUMBER
SUB_NUMBER(presence_timeout)
#endif #endif
#ifdef USE_SELECT #ifdef USE_SELECT
SUB_SELECT(baud_rate) SUB_SELECT(baud_rate)
@@ -101,16 +88,19 @@ class LD2450Component : public Component, public uart::UARTDevice {
SUB_SWITCH(multi_target) SUB_SWITCH(multi_target)
#endif #endif
#ifdef USE_BUTTON #ifdef USE_BUTTON
SUB_BUTTON(factory_reset) SUB_BUTTON(reset)
SUB_BUTTON(restart) SUB_BUTTON(restart)
#endif #endif
#ifdef USE_NUMBER
SUB_NUMBER(presence_timeout)
#endif
public: public:
void setup() override; void setup() override;
void dump_config() override; void dump_config() override;
void loop() override; void loop() override;
void set_presence_timeout(); void set_presence_timeout();
void set_throttle(uint16_t value) { this->throttle_ = value; } void set_throttle(uint16_t value) { this->throttle_ = value; };
void read_all_info(); void read_all_info();
void query_zone_info(); void query_zone_info();
void restart_and_read_all_info(); void restart_and_read_all_info();
@@ -146,10 +136,10 @@ class LD2450Component : public Component, public uart::UARTDevice {
protected: protected:
void send_command_(uint8_t command_str, const uint8_t *command_value, uint8_t command_value_len); void send_command_(uint8_t command_str, const uint8_t *command_value, uint8_t command_value_len);
void set_config_mode_(bool enable); void set_config_mode_(bool enable);
void handle_periodic_data_(); void handle_periodic_data_(uint8_t *buffer, uint8_t len);
bool handle_ack_data_(); bool handle_ack_data_(uint8_t *buffer, uint8_t len);
void process_zone_(); void process_zone_(uint8_t *buffer);
void readline_(int readch); void readline_(int readch, uint8_t *buffer, uint8_t len);
void get_version_(); void get_version_();
void get_mac_(); void get_mac_();
void query_target_tracking_mode_(); void query_target_tracking_mode_();
@@ -167,40 +157,13 @@ class LD2450Component : public Component, public uart::UARTDevice {
uint32_t moving_presence_millis_ = 0; uint32_t moving_presence_millis_ = 0;
uint16_t throttle_ = 0; uint16_t throttle_ = 0;
uint16_t timeout_ = 5; uint16_t timeout_ = 5;
uint8_t buffer_data_[MAX_LINE_LENGTH];
uint8_t mac_address_[6] = {0, 0, 0, 0, 0, 0};
uint8_t version_[6] = {0, 0, 0, 0, 0, 0};
uint8_t buffer_pos_ = 0; // where to resume processing/populating buffer uint8_t buffer_pos_ = 0; // where to resume processing/populating buffer
uint8_t buffer_data_[MAX_LINE_LENGTH];
uint8_t zone_type_ = 0; uint8_t zone_type_ = 0;
bool bluetooth_on_{false};
Target target_info_[MAX_TARGETS]; Target target_info_[MAX_TARGETS];
Zone zone_config_[MAX_ZONES]; Zone zone_config_[MAX_ZONES];
std::string version_{};
// Change detection - cache previous values to avoid redundant publishes std::string mac_{};
// All values are initialized to sentinel values that are outside the valid sensor ranges
// to ensure the first real measurement is always published
struct CachedTargetData {
int16_t x = std::numeric_limits<int16_t>::min(); // -32768, outside range of -4860 to 4860
int16_t y = std::numeric_limits<int16_t>::min(); // -32768, outside range of 0 to 7560
int16_t speed = std::numeric_limits<int16_t>::min(); // -32768, outside practical sensor range
uint16_t resolution = std::numeric_limits<uint16_t>::max(); // 65535, unlikely resolution value
uint16_t distance = std::numeric_limits<uint16_t>::max(); // 65535, outside range of 0 to ~8990
Direction direction = DIRECTION_UNDEFINED; // Undefined, will differ from any real direction
float angle = NAN; // NAN, safe sentinel for floats
} cached_target_data_[MAX_TARGETS];
struct CachedZoneData {
uint8_t still_count = std::numeric_limits<uint8_t>::max(); // 255, unlikely zone count
uint8_t moving_count = std::numeric_limits<uint8_t>::max(); // 255, unlikely zone count
uint8_t total_count = std::numeric_limits<uint8_t>::max(); // 255, unlikely zone count
} cached_zone_data_[MAX_ZONES];
struct CachedGlobalData {
uint8_t target_count = std::numeric_limits<uint8_t>::max(); // 255, max 3 targets possible
uint8_t still_count = std::numeric_limits<uint8_t>::max(); // 255, max 3 targets possible
uint8_t moving_count = std::numeric_limits<uint8_t>::max(); // 255, max 3 targets possible
} cached_global_data_;
#ifdef USE_NUMBER #ifdef USE_NUMBER
ESPPreferenceObject pref_; // only used when numbers are in use ESPPreferenceObject pref_; // only used when numbers are in use
ZoneOfNumbers zone_numbers_[MAX_ZONES]; ZoneOfNumbers zone_numbers_[MAX_ZONES];

View File

@@ -268,7 +268,6 @@ async def component_to_code(config):
# disable library compatibility checks # disable library compatibility checks
cg.add_platformio_option("lib_ldf_mode", "off") cg.add_platformio_option("lib_ldf_mode", "off")
cg.add_platformio_option("lib_compat_mode", "soft")
# include <Arduino.h> in every file # include <Arduino.h> in every file
cg.add_platformio_option("build_src_flags", "-include Arduino.h") cg.add_platformio_option("build_src_flags", "-include Arduino.h")
# dummy version code # dummy version code

View File

@@ -1,35 +0,0 @@
#include "esphome/core/helpers.h"
#ifdef USE_LIBRETINY
#include "esphome/core/hal.h"
#include <WiFi.h> // for macAddress()
namespace esphome {
uint32_t random_uint32() { return rand(); }
bool random_bytes(uint8_t *data, size_t len) {
lt_rand_bytes(data, len);
return true;
}
Mutex::Mutex() { handle_ = xSemaphoreCreateMutex(); }
Mutex::~Mutex() {}
void Mutex::lock() { xSemaphoreTake(this->handle_, portMAX_DELAY); }
bool Mutex::try_lock() { return xSemaphoreTake(this->handle_, 0) == pdTRUE; }
void Mutex::unlock() { xSemaphoreGive(this->handle_); }
// only affects the executing core
// so should not be used as a mutex lock, only to get accurate timing
IRAM_ATTR InterruptLock::InterruptLock() { portDISABLE_INTERRUPTS(); }
IRAM_ATTR InterruptLock::~InterruptLock() { portENABLE_INTERRUPTS(); }
void get_mac_address_raw(uint8_t *mac) { // NOLINT(readability-non-const-parameter)
WiFi.macAddress(mac);
}
} // namespace esphome
#endif // USE_LIBRETINY

View File

@@ -97,12 +97,12 @@ class AddressableLight : public LightOutput, public Component {
} }
virtual ESPColorView get_view_internal(int32_t index) const = 0; virtual ESPColorView get_view_internal(int32_t index) const = 0;
bool effect_active_{false};
ESPColorCorrection correction_{}; ESPColorCorrection correction_{};
LightState *state_parent_{nullptr};
#ifdef USE_POWER_SUPPLY #ifdef USE_POWER_SUPPLY
power_supply::PowerSupplyRequester power_; power_supply::PowerSupplyRequester power_;
#endif #endif
bool effect_active_{false}; LightState *state_parent_{nullptr};
}; };
class AddressableLightTransformer : public LightTransitionTransformer { class AddressableLightTransformer : public LightTransitionTransformer {
@@ -114,9 +114,9 @@ class AddressableLightTransformer : public LightTransitionTransformer {
protected: protected:
AddressableLight &light_; AddressableLight &light_;
Color target_color_{};
float last_transition_progress_{0.0f}; float last_transition_progress_{0.0f};
float accumulated_alpha_{0.0f}; float accumulated_alpha_{0.0f};
Color target_color_{};
}; };
} // namespace light } // namespace light

View File

@@ -69,8 +69,8 @@ class ESPColorCorrection {
protected: protected:
uint8_t gamma_table_[256]; uint8_t gamma_table_[256];
uint8_t gamma_reverse_table_[256]; uint8_t gamma_reverse_table_[256];
Color max_brightness_;
uint8_t local_brightness_{255}; uint8_t local_brightness_{255};
Color max_brightness_;
}; };
} // namespace light } // namespace light

View File

@@ -2,28 +2,12 @@
#include "light_call.h" #include "light_call.h"
#include "light_state.h" #include "light_state.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
#include "esphome/core/optional.h"
namespace esphome { namespace esphome {
namespace light { namespace light {
static const char *const TAG = "light"; static const char *const TAG = "light";
// Macro to reduce repetitive setter code
#define IMPLEMENT_LIGHT_CALL_SETTER(name, type, flag) \
LightCall &LightCall::set_##name(optional<type>(name)) { \
if ((name).has_value()) { \
this->name##_ = (name).value(); \
} \
this->set_flag_(flag, (name).has_value()); \
return *this; \
} \
LightCall &LightCall::set_##name(type name) { \
this->name##_ = name; \
this->set_flag_(flag, true); \
return *this; \
}
static const LogString *color_mode_to_human(ColorMode color_mode) { static const LogString *color_mode_to_human(ColorMode color_mode) {
if (color_mode == ColorMode::UNKNOWN) if (color_mode == ColorMode::UNKNOWN)
return LOG_STR("Unknown"); return LOG_STR("Unknown");
@@ -48,43 +32,41 @@ void LightCall::perform() {
const char *name = this->parent_->get_name().c_str(); const char *name = this->parent_->get_name().c_str();
LightColorValues v = this->validate_(); LightColorValues v = this->validate_();
if (this->get_publish_()) { if (this->publish_) {
ESP_LOGD(TAG, "'%s' Setting:", name); ESP_LOGD(TAG, "'%s' Setting:", name);
// Only print color mode when it's being changed // Only print color mode when it's being changed
ColorMode current_color_mode = this->parent_->remote_values.get_color_mode(); ColorMode current_color_mode = this->parent_->remote_values.get_color_mode();
ColorMode target_color_mode = this->has_color_mode() ? this->color_mode_ : current_color_mode; if (this->color_mode_.value_or(current_color_mode) != current_color_mode) {
if (target_color_mode != current_color_mode) {
ESP_LOGD(TAG, " Color mode: %s", LOG_STR_ARG(color_mode_to_human(v.get_color_mode()))); ESP_LOGD(TAG, " Color mode: %s", LOG_STR_ARG(color_mode_to_human(v.get_color_mode())));
} }
// Only print state when it's being changed // Only print state when it's being changed
bool current_state = this->parent_->remote_values.is_on(); bool current_state = this->parent_->remote_values.is_on();
bool target_state = this->has_state() ? this->state_ : current_state; if (this->state_.value_or(current_state) != current_state) {
if (target_state != current_state) {
ESP_LOGD(TAG, " State: %s", ONOFF(v.is_on())); ESP_LOGD(TAG, " State: %s", ONOFF(v.is_on()));
} }
if (this->has_brightness()) { if (this->brightness_.has_value()) {
ESP_LOGD(TAG, " Brightness: %.0f%%", v.get_brightness() * 100.0f); ESP_LOGD(TAG, " Brightness: %.0f%%", v.get_brightness() * 100.0f);
} }
if (this->has_color_brightness()) { if (this->color_brightness_.has_value()) {
ESP_LOGD(TAG, " Color brightness: %.0f%%", v.get_color_brightness() * 100.0f); ESP_LOGD(TAG, " Color brightness: %.0f%%", v.get_color_brightness() * 100.0f);
} }
if (this->has_red() || this->has_green() || this->has_blue()) { if (this->red_.has_value() || this->green_.has_value() || this->blue_.has_value()) {
ESP_LOGD(TAG, " Red: %.0f%%, Green: %.0f%%, Blue: %.0f%%", v.get_red() * 100.0f, v.get_green() * 100.0f, ESP_LOGD(TAG, " Red: %.0f%%, Green: %.0f%%, Blue: %.0f%%", v.get_red() * 100.0f, v.get_green() * 100.0f,
v.get_blue() * 100.0f); v.get_blue() * 100.0f);
} }
if (this->has_white()) { if (this->white_.has_value()) {
ESP_LOGD(TAG, " White: %.0f%%", v.get_white() * 100.0f); ESP_LOGD(TAG, " White: %.0f%%", v.get_white() * 100.0f);
} }
if (this->has_color_temperature()) { if (this->color_temperature_.has_value()) {
ESP_LOGD(TAG, " Color temperature: %.1f mireds", v.get_color_temperature()); ESP_LOGD(TAG, " Color temperature: %.1f mireds", v.get_color_temperature());
} }
if (this->has_cold_white() || this->has_warm_white()) { if (this->cold_white_.has_value() || this->warm_white_.has_value()) {
ESP_LOGD(TAG, " Cold white: %.0f%%, warm white: %.0f%%", v.get_cold_white() * 100.0f, ESP_LOGD(TAG, " Cold white: %.0f%%, warm white: %.0f%%", v.get_cold_white() * 100.0f,
v.get_warm_white() * 100.0f); v.get_warm_white() * 100.0f);
} }
@@ -92,57 +74,58 @@ void LightCall::perform() {
if (this->has_flash_()) { if (this->has_flash_()) {
// FLASH // FLASH
if (this->get_publish_()) { if (this->publish_) {
ESP_LOGD(TAG, " Flash length: %.1fs", this->flash_length_ / 1e3f); ESP_LOGD(TAG, " Flash length: %.1fs", *this->flash_length_ / 1e3f);
} }
this->parent_->start_flash_(v, this->flash_length_, this->get_publish_()); this->parent_->start_flash_(v, *this->flash_length_, this->publish_);
} else if (this->has_transition_()) { } else if (this->has_transition_()) {
// TRANSITION // TRANSITION
if (this->get_publish_()) { if (this->publish_) {
ESP_LOGD(TAG, " Transition length: %.1fs", this->transition_length_ / 1e3f); ESP_LOGD(TAG, " Transition length: %.1fs", *this->transition_length_ / 1e3f);
} }
// Special case: Transition and effect can be set when turning off // Special case: Transition and effect can be set when turning off
if (this->has_effect_()) { if (this->has_effect_()) {
if (this->get_publish_()) { if (this->publish_) {
ESP_LOGD(TAG, " Effect: 'None'"); ESP_LOGD(TAG, " Effect: 'None'");
} }
this->parent_->stop_effect_(); this->parent_->stop_effect_();
} }
this->parent_->start_transition_(v, this->transition_length_, this->get_publish_()); this->parent_->start_transition_(v, *this->transition_length_, this->publish_);
} else if (this->has_effect_()) { } else if (this->has_effect_()) {
// EFFECT // EFFECT
auto effect = this->effect_;
const char *effect_s; const char *effect_s;
if (this->effect_ == 0u) { if (effect == 0u) {
effect_s = "None"; effect_s = "None";
} else { } else {
effect_s = this->parent_->effects_[this->effect_ - 1]->get_name().c_str(); effect_s = this->parent_->effects_[*this->effect_ - 1]->get_name().c_str();
} }
if (this->get_publish_()) { if (this->publish_) {
ESP_LOGD(TAG, " Effect: '%s'", effect_s); ESP_LOGD(TAG, " Effect: '%s'", effect_s);
} }
this->parent_->start_effect_(this->effect_); this->parent_->start_effect_(*this->effect_);
// Also set light color values when starting an effect // Also set light color values when starting an effect
// For example to turn off the light // For example to turn off the light
this->parent_->set_immediately_(v, true); this->parent_->set_immediately_(v, true);
} else { } else {
// INSTANT CHANGE // INSTANT CHANGE
this->parent_->set_immediately_(v, this->get_publish_()); this->parent_->set_immediately_(v, this->publish_);
} }
if (!this->has_transition_()) { if (!this->has_transition_()) {
this->parent_->target_state_reached_callback_.call(); this->parent_->target_state_reached_callback_.call();
} }
if (this->get_publish_()) { if (this->publish_) {
this->parent_->publish_state(); this->parent_->publish_state();
} }
if (this->get_save_()) { if (this->save_) {
this->parent_->save_remote_values_(); this->parent_->save_remote_values_();
} }
} }
@@ -152,80 +135,82 @@ LightColorValues LightCall::validate_() {
auto traits = this->parent_->get_traits(); auto traits = this->parent_->get_traits();
// Color mode check // Color mode check
if (this->has_color_mode() && !traits.supports_color_mode(this->color_mode_)) { if (this->color_mode_.has_value() && !traits.supports_color_mode(this->color_mode_.value())) {
ESP_LOGW(TAG, "'%s' does not support color mode %s", name, LOG_STR_ARG(color_mode_to_human(this->color_mode_))); ESP_LOGW(TAG, "'%s' does not support color mode %s", name,
this->set_flag_(FLAG_HAS_COLOR_MODE, false); LOG_STR_ARG(color_mode_to_human(this->color_mode_.value())));
this->color_mode_.reset();
} }
// Ensure there is always a color mode set // Ensure there is always a color mode set
if (!this->has_color_mode()) { if (!this->color_mode_.has_value()) {
this->color_mode_ = this->compute_color_mode_(); this->color_mode_ = this->compute_color_mode_();
this->set_flag_(FLAG_HAS_COLOR_MODE, true);
} }
auto color_mode = this->color_mode_; auto color_mode = *this->color_mode_;
// Transform calls that use non-native parameters for the current mode. // Transform calls that use non-native parameters for the current mode.
this->transform_parameters_(); this->transform_parameters_();
// Brightness exists check // Brightness exists check
if (this->has_brightness() && this->brightness_ > 0.0f && !(color_mode & ColorCapability::BRIGHTNESS)) { if (this->brightness_.has_value() && *this->brightness_ > 0.0f && !(color_mode & ColorCapability::BRIGHTNESS)) {
ESP_LOGW(TAG, "'%s': setting brightness not supported", name); ESP_LOGW(TAG, "'%s': setting brightness not supported", name);
this->set_flag_(FLAG_HAS_BRIGHTNESS, false); this->brightness_.reset();
} }
// Transition length possible check // Transition length possible check
if (this->has_transition_() && this->transition_length_ != 0 && !(color_mode & ColorCapability::BRIGHTNESS)) { if (this->transition_length_.has_value() && *this->transition_length_ != 0 &&
!(color_mode & ColorCapability::BRIGHTNESS)) {
ESP_LOGW(TAG, "'%s': transitions not supported", name); ESP_LOGW(TAG, "'%s': transitions not supported", name);
this->set_flag_(FLAG_HAS_TRANSITION, false); this->transition_length_.reset();
} }
// Color brightness exists check // Color brightness exists check
if (this->has_color_brightness() && this->color_brightness_ > 0.0f && !(color_mode & ColorCapability::RGB)) { if (this->color_brightness_.has_value() && *this->color_brightness_ > 0.0f && !(color_mode & ColorCapability::RGB)) {
ESP_LOGW(TAG, "'%s': color mode does not support setting RGB brightness", name); ESP_LOGW(TAG, "'%s': color mode does not support setting RGB brightness", name);
this->set_flag_(FLAG_HAS_COLOR_BRIGHTNESS, false); this->color_brightness_.reset();
} }
// RGB exists check // RGB exists check
if ((this->has_red() && this->red_ > 0.0f) || (this->has_green() && this->green_ > 0.0f) || if ((this->red_.has_value() && *this->red_ > 0.0f) || (this->green_.has_value() && *this->green_ > 0.0f) ||
(this->has_blue() && this->blue_ > 0.0f)) { (this->blue_.has_value() && *this->blue_ > 0.0f)) {
if (!(color_mode & ColorCapability::RGB)) { if (!(color_mode & ColorCapability::RGB)) {
ESP_LOGW(TAG, "'%s': color mode does not support setting RGB color", name); ESP_LOGW(TAG, "'%s': color mode does not support setting RGB color", name);
this->set_flag_(FLAG_HAS_RED, false); this->red_.reset();
this->set_flag_(FLAG_HAS_GREEN, false); this->green_.reset();
this->set_flag_(FLAG_HAS_BLUE, false); this->blue_.reset();
} }
} }
// White value exists check // White value exists check
if (this->has_white() && this->white_ > 0.0f && if (this->white_.has_value() && *this->white_ > 0.0f &&
!(color_mode & ColorCapability::WHITE || color_mode & ColorCapability::COLD_WARM_WHITE)) { !(color_mode & ColorCapability::WHITE || color_mode & ColorCapability::COLD_WARM_WHITE)) {
ESP_LOGW(TAG, "'%s': color mode does not support setting white value", name); ESP_LOGW(TAG, "'%s': color mode does not support setting white value", name);
this->set_flag_(FLAG_HAS_WHITE, false); this->white_.reset();
} }
// Color temperature exists check // Color temperature exists check
if (this->has_color_temperature() && if (this->color_temperature_.has_value() &&
!(color_mode & ColorCapability::COLOR_TEMPERATURE || color_mode & ColorCapability::COLD_WARM_WHITE)) { !(color_mode & ColorCapability::COLOR_TEMPERATURE || color_mode & ColorCapability::COLD_WARM_WHITE)) {
ESP_LOGW(TAG, "'%s': color mode does not support setting color temperature", name); ESP_LOGW(TAG, "'%s': color mode does not support setting color temperature", name);
this->set_flag_(FLAG_HAS_COLOR_TEMPERATURE, false); this->color_temperature_.reset();
} }
// Cold/warm white value exists check // Cold/warm white value exists check
if ((this->has_cold_white() && this->cold_white_ > 0.0f) || (this->has_warm_white() && this->warm_white_ > 0.0f)) { if ((this->cold_white_.has_value() && *this->cold_white_ > 0.0f) ||
(this->warm_white_.has_value() && *this->warm_white_ > 0.0f)) {
if (!(color_mode & ColorCapability::COLD_WARM_WHITE)) { if (!(color_mode & ColorCapability::COLD_WARM_WHITE)) {
ESP_LOGW(TAG, "'%s': color mode does not support setting cold/warm white value", name); ESP_LOGW(TAG, "'%s': color mode does not support setting cold/warm white value", name);
this->set_flag_(FLAG_HAS_COLD_WHITE, false); this->cold_white_.reset();
this->set_flag_(FLAG_HAS_WARM_WHITE, false); this->warm_white_.reset();
} }
} }
#define VALIDATE_RANGE_(name_, upper_name, min, max) \ #define VALIDATE_RANGE_(name_, upper_name, min, max) \
if (this->has_##name_()) { \ if (name_##_.has_value()) { \
auto val = this->name_##_; \ auto val = *name_##_; \
if (val < (min) || val > (max)) { \ if (val < (min) || val > (max)) { \
ESP_LOGW(TAG, "'%s': %s value %.2f is out of range [%.1f - %.1f]", name, LOG_STR_LITERAL(upper_name), val, \ ESP_LOGW(TAG, "'%s': %s value %.2f is out of range [%.1f - %.1f]", name, LOG_STR_LITERAL(upper_name), val, \
(min), (max)); \ (min), (max)); \
this->name_##_ = clamp(val, (min), (max)); \ name_##_ = clamp(val, (min), (max)); \
} \ } \
} }
#define VALIDATE_RANGE(name, upper_name) VALIDATE_RANGE_(name, upper_name, 0.0f, 1.0f) #define VALIDATE_RANGE(name, upper_name) VALIDATE_RANGE_(name, upper_name, 0.0f, 1.0f)
@@ -242,116 +227,110 @@ LightColorValues LightCall::validate_() {
VALIDATE_RANGE_(color_temperature, "Color temperature", traits.get_min_mireds(), traits.get_max_mireds()) VALIDATE_RANGE_(color_temperature, "Color temperature", traits.get_min_mireds(), traits.get_max_mireds())
// Flag whether an explicit turn off was requested, in which case we'll also stop the effect. // Flag whether an explicit turn off was requested, in which case we'll also stop the effect.
bool explicit_turn_off_request = this->has_state() && !this->state_; bool explicit_turn_off_request = this->state_.has_value() && !*this->state_;
// Turn off when brightness is set to zero, and reset brightness (so that it has nonzero brightness when turned on). // Turn off when brightness is set to zero, and reset brightness (so that it has nonzero brightness when turned on).
if (this->has_brightness() && this->brightness_ == 0.0f) { if (this->brightness_.has_value() && *this->brightness_ == 0.0f) {
this->state_ = false; this->state_ = optional<float>(false);
this->set_flag_(FLAG_HAS_STATE, true); this->brightness_ = optional<float>(1.0f);
this->brightness_ = 1.0f;
} }
// Set color brightness to 100% if currently zero and a color is set. // Set color brightness to 100% if currently zero and a color is set.
if (this->has_red() || this->has_green() || this->has_blue()) { if (this->red_.has_value() || this->green_.has_value() || this->blue_.has_value()) {
if (!this->has_color_brightness() && this->parent_->remote_values.get_color_brightness() == 0.0f) { if (!this->color_brightness_.has_value() && this->parent_->remote_values.get_color_brightness() == 0.0f)
this->color_brightness_ = 1.0f; this->color_brightness_ = optional<float>(1.0f);
this->set_flag_(FLAG_HAS_COLOR_BRIGHTNESS, true);
}
} }
// Create color values for the light with this call applied. // Create color values for the light with this call applied.
auto v = this->parent_->remote_values; auto v = this->parent_->remote_values;
if (this->has_color_mode()) if (this->color_mode_.has_value())
v.set_color_mode(this->color_mode_); v.set_color_mode(*this->color_mode_);
if (this->has_state()) if (this->state_.has_value())
v.set_state(this->state_); v.set_state(*this->state_);
if (this->has_brightness()) if (this->brightness_.has_value())
v.set_brightness(this->brightness_); v.set_brightness(*this->brightness_);
if (this->has_color_brightness()) if (this->color_brightness_.has_value())
v.set_color_brightness(this->color_brightness_); v.set_color_brightness(*this->color_brightness_);
if (this->has_red()) if (this->red_.has_value())
v.set_red(this->red_); v.set_red(*this->red_);
if (this->has_green()) if (this->green_.has_value())
v.set_green(this->green_); v.set_green(*this->green_);
if (this->has_blue()) if (this->blue_.has_value())
v.set_blue(this->blue_); v.set_blue(*this->blue_);
if (this->has_white()) if (this->white_.has_value())
v.set_white(this->white_); v.set_white(*this->white_);
if (this->has_color_temperature()) if (this->color_temperature_.has_value())
v.set_color_temperature(this->color_temperature_); v.set_color_temperature(*this->color_temperature_);
if (this->has_cold_white()) if (this->cold_white_.has_value())
v.set_cold_white(this->cold_white_); v.set_cold_white(*this->cold_white_);
if (this->has_warm_white()) if (this->warm_white_.has_value())
v.set_warm_white(this->warm_white_); v.set_warm_white(*this->warm_white_);
v.normalize_color(); v.normalize_color();
// Flash length check // Flash length check
if (this->has_flash_() && this->flash_length_ == 0) { if (this->has_flash_() && *this->flash_length_ == 0) {
ESP_LOGW(TAG, "'%s': flash length must be greater than zero", name); ESP_LOGW(TAG, "'%s': flash length must be greater than zero", name);
this->set_flag_(FLAG_HAS_FLASH, false); this->flash_length_.reset();
} }
// validate transition length/flash length/effect not used at the same time // validate transition length/flash length/effect not used at the same time
bool supports_transition = color_mode & ColorCapability::BRIGHTNESS; bool supports_transition = color_mode & ColorCapability::BRIGHTNESS;
// If effect is already active, remove effect start // If effect is already active, remove effect start
if (this->has_effect_() && this->effect_ == this->parent_->active_effect_index_) { if (this->has_effect_() && *this->effect_ == this->parent_->active_effect_index_) {
this->set_flag_(FLAG_HAS_EFFECT, false); this->effect_.reset();
} }
// validate effect index // validate effect index
if (this->has_effect_() && this->effect_ > this->parent_->effects_.size()) { if (this->has_effect_() && *this->effect_ > this->parent_->effects_.size()) {
ESP_LOGW(TAG, "'%s': invalid effect index %" PRIu32, name, this->effect_); ESP_LOGW(TAG, "'%s': invalid effect index %" PRIu32, name, *this->effect_);
this->set_flag_(FLAG_HAS_EFFECT, false); this->effect_.reset();
} }
if (this->has_effect_() && (this->has_transition_() || this->has_flash_())) { if (this->has_effect_() && (this->has_transition_() || this->has_flash_())) {
ESP_LOGW(TAG, "'%s': effect cannot be used with transition/flash", name); ESP_LOGW(TAG, "'%s': effect cannot be used with transition/flash", name);
this->set_flag_(FLAG_HAS_TRANSITION, false); this->transition_length_.reset();
this->set_flag_(FLAG_HAS_FLASH, false); this->flash_length_.reset();
} }
if (this->has_flash_() && this->has_transition_()) { if (this->has_flash_() && this->has_transition_()) {
ESP_LOGW(TAG, "'%s': flash cannot be used with transition", name); ESP_LOGW(TAG, "'%s': flash cannot be used with transition", name);
this->set_flag_(FLAG_HAS_TRANSITION, false); this->transition_length_.reset();
} }
if (!this->has_transition_() && !this->has_flash_() && (!this->has_effect_() || this->effect_ == 0) && if (!this->has_transition_() && !this->has_flash_() && (!this->has_effect_() || *this->effect_ == 0) &&
supports_transition) { supports_transition) {
// nothing specified and light supports transitions, set default transition length // nothing specified and light supports transitions, set default transition length
this->transition_length_ = this->parent_->default_transition_length_; this->transition_length_ = this->parent_->default_transition_length_;
this->set_flag_(FLAG_HAS_TRANSITION, true);
} }
if (this->has_transition_() && this->transition_length_ == 0) { if (this->transition_length_.value_or(0) == 0) {
// 0 transition is interpreted as no transition (instant change) // 0 transition is interpreted as no transition (instant change)
this->set_flag_(FLAG_HAS_TRANSITION, false); this->transition_length_.reset();
} }
if (this->has_transition_() && !supports_transition) { if (this->has_transition_() && !supports_transition) {
ESP_LOGW(TAG, "'%s': transitions not supported", name); ESP_LOGW(TAG, "'%s': transitions not supported", name);
this->set_flag_(FLAG_HAS_TRANSITION, false); this->transition_length_.reset();
} }
// If not a flash and turning the light off, then disable the light // If not a flash and turning the light off, then disable the light
// Do not use light color values directly, so that effects can set 0% brightness // Do not use light color values directly, so that effects can set 0% brightness
// Reason: When user turns off the light in frontend, the effect should also stop // Reason: When user turns off the light in frontend, the effect should also stop
bool target_state = this->has_state() ? this->state_ : v.is_on(); if (!this->has_flash_() && !this->state_.value_or(v.is_on())) {
if (!this->has_flash_() && !target_state) {
if (this->has_effect_()) { if (this->has_effect_()) {
ESP_LOGW(TAG, "'%s': cannot start effect when turning off", name); ESP_LOGW(TAG, "'%s': cannot start effect when turning off", name);
this->set_flag_(FLAG_HAS_EFFECT, false); this->effect_.reset();
} else if (this->parent_->active_effect_index_ != 0 && explicit_turn_off_request) { } else if (this->parent_->active_effect_index_ != 0 && explicit_turn_off_request) {
// Auto turn off effect // Auto turn off effect
this->effect_ = 0; this->effect_ = 0;
this->set_flag_(FLAG_HAS_EFFECT, true);
} }
} }
// Disable saving for flashes // Disable saving for flashes
if (this->has_flash_()) if (this->has_flash_())
this->set_flag_(FLAG_SAVE, false); this->save_ = false;
return v; return v;
} }
@@ -364,27 +343,24 @@ void LightCall::transform_parameters_() {
// - RGBWW lights with color_interlock=true, which also sets "brightness" and // - RGBWW lights with color_interlock=true, which also sets "brightness" and
// "color_temperature" (without color_interlock, CW/WW are set directly) // "color_temperature" (without color_interlock, CW/WW are set directly)
// - Legacy Home Assistant (pre-colormode), which sets "white" and "color_temperature" // - Legacy Home Assistant (pre-colormode), which sets "white" and "color_temperature"
if (((this->has_white() && this->white_ > 0.0f) || this->has_color_temperature()) && // if (((this->white_.has_value() && *this->white_ > 0.0f) || this->color_temperature_.has_value()) && //
(this->color_mode_ & ColorCapability::COLD_WARM_WHITE) && // (*this->color_mode_ & ColorCapability::COLD_WARM_WHITE) && //
!(this->color_mode_ & ColorCapability::WHITE) && // !(*this->color_mode_ & ColorCapability::WHITE) && //
!(this->color_mode_ & ColorCapability::COLOR_TEMPERATURE) && // !(*this->color_mode_ & ColorCapability::COLOR_TEMPERATURE) && //
traits.get_min_mireds() > 0.0f && traits.get_max_mireds() > 0.0f) { traits.get_min_mireds() > 0.0f && traits.get_max_mireds() > 0.0f) {
ESP_LOGD(TAG, "'%s': setting cold/warm white channels using white/color temperature values", ESP_LOGD(TAG, "'%s': setting cold/warm white channels using white/color temperature values",
this->parent_->get_name().c_str()); this->parent_->get_name().c_str());
if (this->has_color_temperature()) { if (this->color_temperature_.has_value()) {
const float color_temp = clamp(this->color_temperature_, traits.get_min_mireds(), traits.get_max_mireds()); const float color_temp = clamp(*this->color_temperature_, traits.get_min_mireds(), traits.get_max_mireds());
const float ww_fraction = const float ww_fraction =
(color_temp - traits.get_min_mireds()) / (traits.get_max_mireds() - traits.get_min_mireds()); (color_temp - traits.get_min_mireds()) / (traits.get_max_mireds() - traits.get_min_mireds());
const float cw_fraction = 1.0f - ww_fraction; const float cw_fraction = 1.0f - ww_fraction;
const float max_cw_ww = std::max(ww_fraction, cw_fraction); const float max_cw_ww = std::max(ww_fraction, cw_fraction);
this->cold_white_ = gamma_uncorrect(cw_fraction / max_cw_ww, this->parent_->get_gamma_correct()); this->cold_white_ = gamma_uncorrect(cw_fraction / max_cw_ww, this->parent_->get_gamma_correct());
this->warm_white_ = gamma_uncorrect(ww_fraction / max_cw_ww, this->parent_->get_gamma_correct()); this->warm_white_ = gamma_uncorrect(ww_fraction / max_cw_ww, this->parent_->get_gamma_correct());
this->set_flag_(FLAG_HAS_COLD_WHITE, true);
this->set_flag_(FLAG_HAS_WARM_WHITE, true);
} }
if (this->has_white()) { if (this->white_.has_value()) {
this->brightness_ = this->white_; this->brightness_ = *this->white_;
this->set_flag_(FLAG_HAS_BRIGHTNESS, true);
} }
} }
} }
@@ -402,7 +378,7 @@ ColorMode LightCall::compute_color_mode_() {
// Don't change if the light is being turned off. // Don't change if the light is being turned off.
ColorMode current_mode = this->parent_->remote_values.get_color_mode(); ColorMode current_mode = this->parent_->remote_values.get_color_mode();
if (this->has_state() && !this->state_) if (this->state_.has_value() && !*this->state_)
return current_mode; return current_mode;
// If no color mode is specified, we try to guess the color mode. This is needed for backward compatibility to // If no color mode is specified, we try to guess the color mode. This is needed for backward compatibility to
@@ -435,12 +411,12 @@ ColorMode LightCall::compute_color_mode_() {
return color_mode; return color_mode;
} }
std::set<ColorMode> LightCall::get_suitable_color_modes_() { std::set<ColorMode> LightCall::get_suitable_color_modes_() {
bool has_white = this->has_white() && this->white_ > 0.0f; bool has_white = this->white_.has_value() && *this->white_ > 0.0f;
bool has_ct = this->has_color_temperature(); bool has_ct = this->color_temperature_.has_value();
bool has_cwww = bool has_cwww = (this->cold_white_.has_value() && *this->cold_white_ > 0.0f) ||
(this->has_cold_white() && this->cold_white_ > 0.0f) || (this->has_warm_white() && this->warm_white_ > 0.0f); (this->warm_white_.has_value() && *this->warm_white_ > 0.0f);
bool has_rgb = (this->has_color_brightness() && this->color_brightness_ > 0.0f) || bool has_rgb = (this->color_brightness_.has_value() && *this->color_brightness_ > 0.0f) ||
(this->has_red() || this->has_green() || this->has_blue()); (this->red_.has_value() || this->green_.has_value() || this->blue_.has_value());
#define KEY(white, ct, cwww, rgb) ((white) << 0 | (ct) << 1 | (cwww) << 2 | (rgb) << 3) #define KEY(white, ct, cwww, rgb) ((white) << 0 | (ct) << 1 | (cwww) << 2 | (rgb) << 3)
#define ENTRY(white, ct, cwww, rgb, ...) \ #define ENTRY(white, ct, cwww, rgb, ...) \
@@ -515,7 +491,7 @@ LightCall &LightCall::from_light_color_values(const LightColorValues &values) {
return *this; return *this;
} }
ColorMode LightCall::get_active_color_mode_() { ColorMode LightCall::get_active_color_mode_() {
return this->has_color_mode() ? this->color_mode_ : this->parent_->remote_values.get_color_mode(); return this->color_mode_.value_or(this->parent_->remote_values.get_color_mode());
} }
LightCall &LightCall::set_transition_length_if_supported(uint32_t transition_length) { LightCall &LightCall::set_transition_length_if_supported(uint32_t transition_length) {
if (this->get_active_color_mode_() & ColorCapability::BRIGHTNESS) if (this->get_active_color_mode_() & ColorCapability::BRIGHTNESS)
@@ -529,7 +505,7 @@ LightCall &LightCall::set_brightness_if_supported(float brightness) {
} }
LightCall &LightCall::set_color_mode_if_supported(ColorMode color_mode) { LightCall &LightCall::set_color_mode_if_supported(ColorMode color_mode) {
if (this->parent_->get_traits().supports_color_mode(color_mode)) if (this->parent_->get_traits().supports_color_mode(color_mode))
this->set_color_mode(color_mode); this->color_mode_ = color_mode;
return *this; return *this;
} }
LightCall &LightCall::set_color_brightness_if_supported(float brightness) { LightCall &LightCall::set_color_brightness_if_supported(float brightness) {
@@ -573,19 +549,110 @@ LightCall &LightCall::set_warm_white_if_supported(float warm_white) {
this->set_warm_white(warm_white); this->set_warm_white(warm_white);
return *this; return *this;
} }
IMPLEMENT_LIGHT_CALL_SETTER(state, bool, FLAG_HAS_STATE) LightCall &LightCall::set_state(optional<bool> state) {
IMPLEMENT_LIGHT_CALL_SETTER(transition_length, uint32_t, FLAG_HAS_TRANSITION) this->state_ = state;
IMPLEMENT_LIGHT_CALL_SETTER(flash_length, uint32_t, FLAG_HAS_FLASH) return *this;
IMPLEMENT_LIGHT_CALL_SETTER(brightness, float, FLAG_HAS_BRIGHTNESS) }
IMPLEMENT_LIGHT_CALL_SETTER(color_mode, ColorMode, FLAG_HAS_COLOR_MODE) LightCall &LightCall::set_state(bool state) {
IMPLEMENT_LIGHT_CALL_SETTER(color_brightness, float, FLAG_HAS_COLOR_BRIGHTNESS) this->state_ = state;
IMPLEMENT_LIGHT_CALL_SETTER(red, float, FLAG_HAS_RED) return *this;
IMPLEMENT_LIGHT_CALL_SETTER(green, float, FLAG_HAS_GREEN) }
IMPLEMENT_LIGHT_CALL_SETTER(blue, float, FLAG_HAS_BLUE) LightCall &LightCall::set_transition_length(optional<uint32_t> transition_length) {
IMPLEMENT_LIGHT_CALL_SETTER(white, float, FLAG_HAS_WHITE) this->transition_length_ = transition_length;
IMPLEMENT_LIGHT_CALL_SETTER(color_temperature, float, FLAG_HAS_COLOR_TEMPERATURE) return *this;
IMPLEMENT_LIGHT_CALL_SETTER(cold_white, float, FLAG_HAS_COLD_WHITE) }
IMPLEMENT_LIGHT_CALL_SETTER(warm_white, float, FLAG_HAS_WARM_WHITE) LightCall &LightCall::set_transition_length(uint32_t transition_length) {
this->transition_length_ = transition_length;
return *this;
}
LightCall &LightCall::set_flash_length(optional<uint32_t> flash_length) {
this->flash_length_ = flash_length;
return *this;
}
LightCall &LightCall::set_flash_length(uint32_t flash_length) {
this->flash_length_ = flash_length;
return *this;
}
LightCall &LightCall::set_brightness(optional<float> brightness) {
this->brightness_ = brightness;
return *this;
}
LightCall &LightCall::set_brightness(float brightness) {
this->brightness_ = brightness;
return *this;
}
LightCall &LightCall::set_color_mode(optional<ColorMode> color_mode) {
this->color_mode_ = color_mode;
return *this;
}
LightCall &LightCall::set_color_mode(ColorMode color_mode) {
this->color_mode_ = color_mode;
return *this;
}
LightCall &LightCall::set_color_brightness(optional<float> brightness) {
this->color_brightness_ = brightness;
return *this;
}
LightCall &LightCall::set_color_brightness(float brightness) {
this->color_brightness_ = brightness;
return *this;
}
LightCall &LightCall::set_red(optional<float> red) {
this->red_ = red;
return *this;
}
LightCall &LightCall::set_red(float red) {
this->red_ = red;
return *this;
}
LightCall &LightCall::set_green(optional<float> green) {
this->green_ = green;
return *this;
}
LightCall &LightCall::set_green(float green) {
this->green_ = green;
return *this;
}
LightCall &LightCall::set_blue(optional<float> blue) {
this->blue_ = blue;
return *this;
}
LightCall &LightCall::set_blue(float blue) {
this->blue_ = blue;
return *this;
}
LightCall &LightCall::set_white(optional<float> white) {
this->white_ = white;
return *this;
}
LightCall &LightCall::set_white(float white) {
this->white_ = white;
return *this;
}
LightCall &LightCall::set_color_temperature(optional<float> color_temperature) {
this->color_temperature_ = color_temperature;
return *this;
}
LightCall &LightCall::set_color_temperature(float color_temperature) {
this->color_temperature_ = color_temperature;
return *this;
}
LightCall &LightCall::set_cold_white(optional<float> cold_white) {
this->cold_white_ = cold_white;
return *this;
}
LightCall &LightCall::set_cold_white(float cold_white) {
this->cold_white_ = cold_white;
return *this;
}
LightCall &LightCall::set_warm_white(optional<float> warm_white) {
this->warm_white_ = warm_white;
return *this;
}
LightCall &LightCall::set_warm_white(float warm_white) {
this->warm_white_ = warm_white;
return *this;
}
LightCall &LightCall::set_effect(optional<std::string> effect) { LightCall &LightCall::set_effect(optional<std::string> effect) {
if (effect.has_value()) if (effect.has_value())
this->set_effect(*effect); this->set_effect(*effect);
@@ -593,22 +660,18 @@ LightCall &LightCall::set_effect(optional<std::string> effect) {
} }
LightCall &LightCall::set_effect(uint32_t effect_number) { LightCall &LightCall::set_effect(uint32_t effect_number) {
this->effect_ = effect_number; this->effect_ = effect_number;
this->set_flag_(FLAG_HAS_EFFECT, true);
return *this; return *this;
} }
LightCall &LightCall::set_effect(optional<uint32_t> effect_number) { LightCall &LightCall::set_effect(optional<uint32_t> effect_number) {
if (effect_number.has_value()) { this->effect_ = effect_number;
this->effect_ = effect_number.value();
}
this->set_flag_(FLAG_HAS_EFFECT, effect_number.has_value());
return *this; return *this;
} }
LightCall &LightCall::set_publish(bool publish) { LightCall &LightCall::set_publish(bool publish) {
this->set_flag_(FLAG_PUBLISH, publish); this->publish_ = publish;
return *this; return *this;
} }
LightCall &LightCall::set_save(bool save) { LightCall &LightCall::set_save(bool save) {
this->set_flag_(FLAG_SAVE, save); this->save_ = save;
return *this; return *this;
} }
LightCall &LightCall::set_rgb(float red, float green, float blue) { LightCall &LightCall::set_rgb(float red, float green, float blue) {

View File

@@ -1,5 +1,6 @@
#pragma once #pragma once
#include "esphome/core/optional.h"
#include "light_color_values.h" #include "light_color_values.h"
#include <set> #include <set>
@@ -9,11 +10,6 @@ namespace light {
class LightState; class LightState;
/** This class represents a requested change in a light state. /** This class represents a requested change in a light state.
*
* Light state changes are tracked using a bitfield flags_ to minimize memory usage.
* Each possible light property has a flag indicating whether it has been set.
* This design keeps LightCall at ~56 bytes to minimize heap fragmentation on
* ESP8266 and other memory-constrained devices.
*/ */
class LightCall { class LightCall {
public: public:
@@ -135,19 +131,6 @@ class LightCall {
/// Set whether this light call should trigger a save state to recover them at startup.. /// Set whether this light call should trigger a save state to recover them at startup..
LightCall &set_save(bool save); LightCall &set_save(bool save);
// Getter methods to check if values are set
bool has_state() const { return (flags_ & FLAG_HAS_STATE) != 0; }
bool has_brightness() const { return (flags_ & FLAG_HAS_BRIGHTNESS) != 0; }
bool has_color_brightness() const { return (flags_ & FLAG_HAS_COLOR_BRIGHTNESS) != 0; }
bool has_red() const { return (flags_ & FLAG_HAS_RED) != 0; }
bool has_green() const { return (flags_ & FLAG_HAS_GREEN) != 0; }
bool has_blue() const { return (flags_ & FLAG_HAS_BLUE) != 0; }
bool has_white() const { return (flags_ & FLAG_HAS_WHITE) != 0; }
bool has_color_temperature() const { return (flags_ & FLAG_HAS_COLOR_TEMPERATURE) != 0; }
bool has_cold_white() const { return (flags_ & FLAG_HAS_COLD_WHITE) != 0; }
bool has_warm_white() const { return (flags_ & FLAG_HAS_WARM_WHITE) != 0; }
bool has_color_mode() const { return (flags_ & FLAG_HAS_COLOR_MODE) != 0; }
/** Set the RGB color of the light by RGB values. /** Set the RGB color of the light by RGB values.
* *
* Please note that this only changes the color of the light, not the brightness. * Please note that this only changes the color of the light, not the brightness.
@@ -187,62 +170,27 @@ class LightCall {
/// Some color modes also can be set using non-native parameters, transform those calls. /// Some color modes also can be set using non-native parameters, transform those calls.
void transform_parameters_(); void transform_parameters_();
// Bitfield flags - each flag indicates whether a corresponding value has been set. bool has_transition_() { return this->transition_length_.has_value(); }
enum FieldFlags : uint16_t { bool has_flash_() { return this->flash_length_.has_value(); }
FLAG_HAS_STATE = 1 << 0, bool has_effect_() { return this->effect_.has_value(); }
FLAG_HAS_TRANSITION = 1 << 1,
FLAG_HAS_FLASH = 1 << 2,
FLAG_HAS_EFFECT = 1 << 3,
FLAG_HAS_BRIGHTNESS = 1 << 4,
FLAG_HAS_COLOR_BRIGHTNESS = 1 << 5,
FLAG_HAS_RED = 1 << 6,
FLAG_HAS_GREEN = 1 << 7,
FLAG_HAS_BLUE = 1 << 8,
FLAG_HAS_WHITE = 1 << 9,
FLAG_HAS_COLOR_TEMPERATURE = 1 << 10,
FLAG_HAS_COLD_WHITE = 1 << 11,
FLAG_HAS_WARM_WHITE = 1 << 12,
FLAG_HAS_COLOR_MODE = 1 << 13,
FLAG_PUBLISH = 1 << 14,
FLAG_SAVE = 1 << 15,
};
bool has_transition_() { return (this->flags_ & FLAG_HAS_TRANSITION) != 0; }
bool has_flash_() { return (this->flags_ & FLAG_HAS_FLASH) != 0; }
bool has_effect_() { return (this->flags_ & FLAG_HAS_EFFECT) != 0; }
bool get_publish_() { return (this->flags_ & FLAG_PUBLISH) != 0; }
bool get_save_() { return (this->flags_ & FLAG_SAVE) != 0; }
// Helper to set flag
void set_flag_(FieldFlags flag, bool value) {
if (value) {
this->flags_ |= flag;
} else {
this->flags_ &= ~flag;
}
}
LightState *parent_; LightState *parent_;
optional<bool> state_;
// Light state values - use flags_ to check if a value has been set. optional<uint32_t> transition_length_;
// Group 4-byte aligned members first optional<uint32_t> flash_length_;
uint32_t transition_length_; optional<ColorMode> color_mode_;
uint32_t flash_length_; optional<float> brightness_;
uint32_t effect_; optional<float> color_brightness_;
float brightness_; optional<float> red_;
float color_brightness_; optional<float> green_;
float red_; optional<float> blue_;
float green_; optional<float> white_;
float blue_; optional<float> color_temperature_;
float white_; optional<float> cold_white_;
float color_temperature_; optional<float> warm_white_;
float cold_white_; optional<uint32_t> effect_;
float warm_white_; bool publish_{true};
bool save_{true};
// Smaller members at the end for better packing
uint16_t flags_{FLAG_PUBLISH | FLAG_SAVE}; // Tracks which values are set
ColorMode color_mode_;
bool state_;
}; };
} // namespace light } // namespace light

View File

@@ -46,7 +46,8 @@ class LightColorValues {
public: public:
/// Construct the LightColorValues with all attributes enabled, but state set to off. /// Construct the LightColorValues with all attributes enabled, but state set to off.
LightColorValues() LightColorValues()
: state_(0.0f), : color_mode_(ColorMode::UNKNOWN),
state_(0.0f),
brightness_(1.0f), brightness_(1.0f),
color_brightness_(1.0f), color_brightness_(1.0f),
red_(1.0f), red_(1.0f),
@@ -55,8 +56,7 @@ class LightColorValues {
white_(1.0f), white_(1.0f),
color_temperature_{0.0f}, color_temperature_{0.0f},
cold_white_{1.0f}, cold_white_{1.0f},
warm_white_{1.0f}, warm_white_{1.0f} {}
color_mode_(ColorMode::UNKNOWN) {}
LightColorValues(ColorMode color_mode, float state, float brightness, float color_brightness, float red, float green, LightColorValues(ColorMode color_mode, float state, float brightness, float color_brightness, float red, float green,
float blue, float white, float color_temperature, float cold_white, float warm_white) { float blue, float white, float color_temperature, float cold_white, float warm_white) {
@@ -292,6 +292,7 @@ class LightColorValues {
void set_warm_white(float warm_white) { this->warm_white_ = clamp(warm_white, 0.0f, 1.0f); } void set_warm_white(float warm_white) { this->warm_white_ = clamp(warm_white, 0.0f, 1.0f); }
protected: protected:
ColorMode color_mode_;
float state_; ///< ON / OFF, float for transition float state_; ///< ON / OFF, float for transition
float brightness_; float brightness_;
float color_brightness_; float color_brightness_;
@@ -302,7 +303,6 @@ class LightColorValues {
float color_temperature_; ///< Color Temperature in Mired float color_temperature_; ///< Color Temperature in Mired
float cold_white_; float cold_white_;
float warm_white_; float warm_white_;
ColorMode color_mode_;
}; };
} // namespace light } // namespace light

View File

@@ -9,7 +9,6 @@ namespace light {
// See https://www.home-assistant.io/integrations/light.mqtt/#json-schema for documentation on the schema // See https://www.home-assistant.io/integrations/light.mqtt/#json-schema for documentation on the schema
void LightJSONSchema::dump_json(LightState &state, JsonObject root) { void LightJSONSchema::dump_json(LightState &state, JsonObject root) {
// NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
if (state.supports_effects()) if (state.supports_effects())
root["effect"] = state.get_effect_name(); root["effect"] = state.get_effect_name();
@@ -53,7 +52,7 @@ void LightJSONSchema::dump_json(LightState &state, JsonObject root) {
if (values.get_color_mode() & ColorCapability::BRIGHTNESS) if (values.get_color_mode() & ColorCapability::BRIGHTNESS)
root["brightness"] = uint8_t(values.get_brightness() * 255); root["brightness"] = uint8_t(values.get_brightness() * 255);
JsonObject color = root["color"].to<JsonObject>(); JsonObject color = root.createNestedObject("color");
if (values.get_color_mode() & ColorCapability::RGB) { if (values.get_color_mode() & ColorCapability::RGB) {
color["r"] = uint8_t(values.get_color_brightness() * values.get_red() * 255); color["r"] = uint8_t(values.get_color_brightness() * values.get_red() * 255);
color["g"] = uint8_t(values.get_color_brightness() * values.get_green() * 255); color["g"] = uint8_t(values.get_color_brightness() * values.get_green() * 255);
@@ -74,7 +73,7 @@ void LightJSONSchema::dump_json(LightState &state, JsonObject root) {
} }
void LightJSONSchema::parse_color_json(LightState &state, LightCall &call, JsonObject root) { void LightJSONSchema::parse_color_json(LightState &state, LightCall &call, JsonObject root) {
if (root["state"].is<const char *>()) { if (root.containsKey("state")) {
auto val = parse_on_off(root["state"]); auto val = parse_on_off(root["state"]);
switch (val) { switch (val) {
case PARSE_ON: case PARSE_ON:
@@ -91,40 +90,40 @@ void LightJSONSchema::parse_color_json(LightState &state, LightCall &call, JsonO
} }
} }
if (root["brightness"].is<uint8_t>()) { if (root.containsKey("brightness")) {
call.set_brightness(float(root["brightness"]) / 255.0f); call.set_brightness(float(root["brightness"]) / 255.0f);
} }
if (root["color"].is<JsonObject>()) { if (root.containsKey("color")) {
JsonObject color = root["color"]; JsonObject color = root["color"];
// HA also encodes brightness information in the r, g, b values, so extract that and set it as color brightness. // HA also encodes brightness information in the r, g, b values, so extract that and set it as color brightness.
float max_rgb = 0.0f; float max_rgb = 0.0f;
if (color["r"].is<uint8_t>()) { if (color.containsKey("r")) {
float r = float(color["r"]) / 255.0f; float r = float(color["r"]) / 255.0f;
max_rgb = fmaxf(max_rgb, r); max_rgb = fmaxf(max_rgb, r);
call.set_red(r); call.set_red(r);
} }
if (color["g"].is<uint8_t>()) { if (color.containsKey("g")) {
float g = float(color["g"]) / 255.0f; float g = float(color["g"]) / 255.0f;
max_rgb = fmaxf(max_rgb, g); max_rgb = fmaxf(max_rgb, g);
call.set_green(g); call.set_green(g);
} }
if (color["b"].is<uint8_t>()) { if (color.containsKey("b")) {
float b = float(color["b"]) / 255.0f; float b = float(color["b"]) / 255.0f;
max_rgb = fmaxf(max_rgb, b); max_rgb = fmaxf(max_rgb, b);
call.set_blue(b); call.set_blue(b);
} }
if (color["r"].is<uint8_t>() || color["g"].is<uint8_t>() || color["b"].is<uint8_t>()) { if (color.containsKey("r") || color.containsKey("g") || color.containsKey("b")) {
call.set_color_brightness(max_rgb); call.set_color_brightness(max_rgb);
} }
if (color["c"].is<uint8_t>()) { if (color.containsKey("c")) {
call.set_cold_white(float(color["c"]) / 255.0f); call.set_cold_white(float(color["c"]) / 255.0f);
} }
if (color["w"].is<uint8_t>()) { if (color.containsKey("w")) {
// the HA scheme is ambiguous here, the same key is used for white channel in RGBW and warm // the HA scheme is ambiguous here, the same key is used for white channel in RGBW and warm
// white channel in RGBWW. // white channel in RGBWW.
if (color["c"].is<uint8_t>()) { if (color.containsKey("c")) {
call.set_warm_white(float(color["w"]) / 255.0f); call.set_warm_white(float(color["w"]) / 255.0f);
} else { } else {
call.set_white(float(color["w"]) / 255.0f); call.set_white(float(color["w"]) / 255.0f);
@@ -132,11 +131,11 @@ void LightJSONSchema::parse_color_json(LightState &state, LightCall &call, JsonO
} }
} }
if (root["white_value"].is<uint8_t>()) { // legacy API if (root.containsKey("white_value")) { // legacy API
call.set_white(float(root["white_value"]) / 255.0f); call.set_white(float(root["white_value"]) / 255.0f);
} }
if (root["color_temp"].is<uint16_t>()) { if (root.containsKey("color_temp")) {
call.set_color_temperature(float(root["color_temp"])); call.set_color_temperature(float(root["color_temp"]));
} }
} }
@@ -144,17 +143,17 @@ void LightJSONSchema::parse_color_json(LightState &state, LightCall &call, JsonO
void LightJSONSchema::parse_json(LightState &state, LightCall &call, JsonObject root) { void LightJSONSchema::parse_json(LightState &state, LightCall &call, JsonObject root) {
LightJSONSchema::parse_color_json(state, call, root); LightJSONSchema::parse_color_json(state, call, root);
if (root["flash"].is<uint32_t>()) { if (root.containsKey("flash")) {
auto length = uint32_t(float(root["flash"]) * 1000); auto length = uint32_t(float(root["flash"]) * 1000);
call.set_flash_length(length); call.set_flash_length(length);
} }
if (root["transition"].is<uint16_t>()) { if (root.containsKey("transition")) {
auto length = uint32_t(float(root["transition"]) * 1000); auto length = uint32_t(float(root["transition"]) * 1000);
call.set_transition_length(length); call.set_transition_length(length);
} }
if (root["effect"].is<const char *>()) { if (root.containsKey("effect")) {
const char *effect = root["effect"]; const char *effect = root["effect"];
call.set_effect(effect); call.set_effect(effect);
} }

View File

@@ -31,7 +31,9 @@ enum LightRestoreMode : uint8_t {
struct LightStateRTCState { struct LightStateRTCState {
LightStateRTCState(ColorMode color_mode, bool state, float brightness, float color_brightness, float red, float green, LightStateRTCState(ColorMode color_mode, bool state, float brightness, float color_brightness, float red, float green,
float blue, float white, float color_temp, float cold_white, float warm_white) float blue, float white, float color_temp, float cold_white, float warm_white)
: brightness(brightness), : color_mode(color_mode),
state(state),
brightness(brightness),
color_brightness(color_brightness), color_brightness(color_brightness),
red(red), red(red),
green(green), green(green),
@@ -39,12 +41,10 @@ struct LightStateRTCState {
white(white), white(white),
color_temp(color_temp), color_temp(color_temp),
cold_white(cold_white), cold_white(cold_white),
warm_white(warm_white), warm_white(warm_white) {}
effect(0),
color_mode(color_mode),
state(state) {}
LightStateRTCState() = default; LightStateRTCState() = default;
// Group 4-byte aligned members first ColorMode color_mode{ColorMode::UNKNOWN};
bool state{false};
float brightness{1.0f}; float brightness{1.0f};
float color_brightness{1.0f}; float color_brightness{1.0f};
float red{1.0f}; float red{1.0f};
@@ -55,9 +55,6 @@ struct LightStateRTCState {
float cold_white{1.0f}; float cold_white{1.0f};
float warm_white{1.0f}; float warm_white{1.0f};
uint32_t effect{0}; uint32_t effect{0};
// Group smaller members at the end
ColorMode color_mode{ColorMode::UNKNOWN};
bool state{false};
}; };
/** This class represents the communication layer between the front-end MQTT layer and the /** This class represents the communication layer between the front-end MQTT layer and the
@@ -219,8 +216,6 @@ class LightState : public EntityBase, public Component {
std::unique_ptr<LightTransformer> transformer_{nullptr}; std::unique_ptr<LightTransformer> transformer_{nullptr};
/// List of effects for this light. /// List of effects for this light.
std::vector<LightEffect *> effects_; std::vector<LightEffect *> effects_;
/// Object used to store the persisted values of the light.
ESPPreferenceObject rtc_;
/// Value for storing the index of the currently active effect. 0 if no effect is active /// Value for storing the index of the currently active effect. 0 if no effect is active
uint32_t active_effect_index_{}; uint32_t active_effect_index_{};
/// Default transition length for all transitions in ms. /// Default transition length for all transitions in ms.
@@ -229,11 +224,15 @@ class LightState : public EntityBase, public Component {
uint32_t flash_transition_length_{}; uint32_t flash_transition_length_{};
/// Gamma correction factor for the light. /// Gamma correction factor for the light.
float gamma_correct_{}; float gamma_correct_{};
/// Whether the light value should be written in the next cycle. /// Whether the light value should be written in the next cycle.
bool next_write_{true}; bool next_write_{true};
// for effects, true if a transformer (transition) is active. // for effects, true if a transformer (transition) is active.
bool is_transformer_active_ = false; bool is_transformer_active_ = false;
/// Object used to store the persisted values of the light.
ESPPreferenceObject rtc_;
/** Callback to call when new values for the frontend are available. /** Callback to call when new values for the frontend are available.
* *
* "Remote values" are light color values that are reported to the frontend and have a lower * "Remote values" are light color values that are reported to the frontend and have a lower

View File

@@ -59,9 +59,9 @@ class LightTransitionTransformer : public LightTransformer {
// transition from 0 to 1 on x = [0, 1] // transition from 0 to 1 on x = [0, 1]
static float smoothed_progress(float x) { return x * x * x * (x * (x * 6.0f - 15.0f) + 10.0f); } static float smoothed_progress(float x) { return x * x * x * (x * (x * 6.0f - 15.0f) + 10.0f); }
bool changing_color_mode_{false};
LightColorValues end_values_{}; LightColorValues end_values_{};
LightColorValues intermediate_values_{}; LightColorValues intermediate_values_{};
bool changing_color_mode_{false};
}; };
class LightFlashTransformer : public LightTransformer { class LightFlashTransformer : public LightTransformer {
@@ -117,8 +117,8 @@ class LightFlashTransformer : public LightTransformer {
protected: protected:
LightState &state_; LightState &state_;
std::unique_ptr<LightTransformer> transformer_{nullptr};
uint32_t transition_length_; uint32_t transition_length_;
std::unique_ptr<LightTransformer> transformer_{nullptr};
bool begun_lightstate_restore_; bool begun_lightstate_restore_;
}; };

View File

@@ -21,7 +21,6 @@ from esphome.components.libretiny.const import (
COMPONENT_LN882X, COMPONENT_LN882X,
COMPONENT_RTL87XX, COMPONENT_RTL87XX,
) )
from esphome.config_helpers import filter_source_files_from_platform
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.const import ( from esphome.const import (
CONF_ARGS, CONF_ARGS,
@@ -43,7 +42,6 @@ from esphome.const import (
PLATFORM_LN882X, PLATFORM_LN882X,
PLATFORM_RP2040, PLATFORM_RP2040,
PLATFORM_RTL87XX, PLATFORM_RTL87XX,
PlatformFramework,
) )
from esphome.core import CORE, Lambda, coroutine_with_priority from esphome.core import CORE, Lambda, coroutine_with_priority
@@ -446,25 +444,3 @@ async def logger_set_level_to_code(config, action_id, template_arg, args):
lambda_ = await cg.process_lambda(Lambda(text), args, return_type=cg.void) lambda_ = await cg.process_lambda(Lambda(text), args, return_type=cg.void)
return cg.new_Pvariable(action_id, template_arg, lambda_) return cg.new_Pvariable(action_id, template_arg, lambda_)
FILTER_SOURCE_FILES = filter_source_files_from_platform(
{
"logger_esp32.cpp": {
PlatformFramework.ESP32_ARDUINO,
PlatformFramework.ESP32_IDF,
},
"logger_esp8266.cpp": {PlatformFramework.ESP8266_ARDUINO},
"logger_host.cpp": {PlatformFramework.HOST_NATIVE},
"logger_rp2040.cpp": {PlatformFramework.RP2040_ARDUINO},
"logger_libretiny.cpp": {
PlatformFramework.BK72XX_ARDUINO,
PlatformFramework.RTL87XX_ARDUINO,
PlatformFramework.LN882X_ARDUINO,
},
"task_log_buffer.cpp": {
PlatformFramework.ESP32_ARDUINO,
PlatformFramework.ESP32_IDF,
},
}
)

View File

@@ -90,25 +90,6 @@ void HOT Logger::log_vprintf_(uint8_t level, const char *tag, int line, const ch
#ifdef USE_STORE_LOG_STR_IN_FLASH #ifdef USE_STORE_LOG_STR_IN_FLASH
// Implementation for ESP8266 with flash string support. // Implementation for ESP8266 with flash string support.
// Note: USE_STORE_LOG_STR_IN_FLASH is only defined for ESP8266. // Note: USE_STORE_LOG_STR_IN_FLASH is only defined for ESP8266.
//
// This function handles format strings stored in flash memory (PROGMEM) to save RAM.
// The buffer is used in a special way to avoid allocating extra memory:
//
// Memory layout during execution:
// Step 1: Copy format string from flash to buffer
// tx_buffer_: [format_string][null][.....................]
// tx_buffer_at_: ------------------^
// msg_start: saved here -----------^
//
// Step 2: format_log_to_buffer_with_terminator_ reads format string from beginning
// and writes formatted output starting at msg_start position
// tx_buffer_: [format_string][null][formatted_message][null]
// tx_buffer_at_: -------------------------------------^
//
// Step 3: Output the formatted message (starting at msg_start)
// write_msg_ and callbacks receive: this->tx_buffer_ + msg_start
// which points to: [formatted_message][null]
//
void Logger::log_vprintf_(uint8_t level, const char *tag, int line, const __FlashStringHelper *format, void Logger::log_vprintf_(uint8_t level, const char *tag, int line, const __FlashStringHelper *format,
va_list args) { // NOLINT va_list args) { // NOLINT
if (level > this->level_for(tag) || global_recursion_guard_) if (level > this->level_for(tag) || global_recursion_guard_)
@@ -140,9 +121,7 @@ void Logger::log_vprintf_(uint8_t level, const char *tag, int line, const __Flas
if (this->baud_rate_ > 0) { if (this->baud_rate_ > 0) {
this->write_msg_(this->tx_buffer_ + msg_start); this->write_msg_(this->tx_buffer_ + msg_start);
} }
size_t msg_length = this->log_callback_.call(level, tag, this->tx_buffer_ + msg_start);
this->tx_buffer_at_ - msg_start; // Don't subtract 1 - tx_buffer_at_ is already at the null terminator position
this->log_callback_.call(level, tag, this->tx_buffer_ + msg_start, msg_length);
global_recursion_guard_ = false; global_recursion_guard_ = false;
} }
@@ -206,8 +185,7 @@ void Logger::loop() {
this->tx_buffer_size_); this->tx_buffer_size_);
this->write_footer_to_buffer_(this->tx_buffer_, &this->tx_buffer_at_, this->tx_buffer_size_); this->write_footer_to_buffer_(this->tx_buffer_, &this->tx_buffer_at_, this->tx_buffer_size_);
this->tx_buffer_[this->tx_buffer_at_] = '\0'; this->tx_buffer_[this->tx_buffer_at_] = '\0';
size_t msg_len = this->tx_buffer_at_; // We already know the length from tx_buffer_at_ this->log_callback_.call(message->level, message->tag, this->tx_buffer_);
this->log_callback_.call(message->level, message->tag, this->tx_buffer_, msg_len);
// At this point all the data we need from message has been transferred to the tx_buffer // At this point all the data we need from message has been transferred to the tx_buffer
// so we can release the message to allow other tasks to use it as soon as possible. // so we can release the message to allow other tasks to use it as soon as possible.
this->log_buffer_->release_message_main_loop(received_token); this->log_buffer_->release_message_main_loop(received_token);
@@ -236,7 +214,7 @@ void Logger::set_log_level(const std::string &tag, uint8_t log_level) { this->lo
UARTSelection Logger::get_uart() const { return this->uart_; } UARTSelection Logger::get_uart() const { return this->uart_; }
#endif #endif
void Logger::add_on_log_callback(std::function<void(uint8_t, const char *, const char *, size_t)> &&callback) { void Logger::add_on_log_callback(std::function<void(uint8_t, const char *, const char *)> &&callback) {
this->log_callback_.add(std::move(callback)); this->log_callback_.add(std::move(callback));
} }
float Logger::get_setup_priority() const { return setup_priority::BUS + 500.0f; } float Logger::get_setup_priority() const { return setup_priority::BUS + 500.0f; }

View File

@@ -143,7 +143,7 @@ class Logger : public Component {
inline uint8_t level_for(const char *tag); inline uint8_t level_for(const char *tag);
/// Register a callback that will be called for every log message sent /// Register a callback that will be called for every log message sent
void add_on_log_callback(std::function<void(uint8_t, const char *, const char *, size_t)> &&callback); void add_on_log_callback(std::function<void(uint8_t, const char *, const char *)> &&callback);
// add a listener for log level changes // add a listener for log level changes
void add_listener(std::function<void(uint8_t)> &&callback) { this->level_callback_.add(std::move(callback)); } void add_listener(std::function<void(uint8_t)> &&callback) { this->level_callback_.add(std::move(callback)); }
@@ -192,7 +192,7 @@ class Logger : public Component {
if (this->baud_rate_ > 0) { if (this->baud_rate_ > 0) {
this->write_msg_(this->tx_buffer_); // If logging is enabled, write to console this->write_msg_(this->tx_buffer_); // If logging is enabled, write to console
} }
this->log_callback_.call(level, tag, this->tx_buffer_, this->tx_buffer_at_); this->log_callback_.call(level, tag, this->tx_buffer_);
} }
// Write the body of the log message to the buffer // Write the body of the log message to the buffer
@@ -246,7 +246,7 @@ class Logger : public Component {
// Large objects (internally aligned) // Large objects (internally aligned)
std::map<std::string, uint8_t> log_levels_{}; std::map<std::string, uint8_t> log_levels_{};
CallbackManager<void(uint8_t, const char *, const char *, size_t)> log_callback_{}; CallbackManager<void(uint8_t, const char *, const char *)> log_callback_{};
CallbackManager<void(uint8_t)> level_callback_{}; CallbackManager<void(uint8_t)> level_callback_{};
#ifdef USE_ESPHOME_TASK_LOG_BUFFER #ifdef USE_ESPHOME_TASK_LOG_BUFFER
std::unique_ptr<logger::TaskLogBuffer> log_buffer_; // Will be initialized with init_log_buffer std::unique_ptr<logger::TaskLogBuffer> log_buffer_; // Will be initialized with init_log_buffer
@@ -355,7 +355,7 @@ class Logger : public Component {
} }
inline void HOT write_footer_to_buffer_(char *buffer, uint16_t *buffer_at, uint16_t buffer_size) { inline void HOT write_footer_to_buffer_(char *buffer, uint16_t *buffer_at, uint16_t buffer_size) {
static constexpr uint16_t RESET_COLOR_LEN = sizeof(ESPHOME_LOG_RESET_COLOR) - 1; static const uint16_t RESET_COLOR_LEN = strlen(ESPHOME_LOG_RESET_COLOR);
this->write_body_to_buffer_(ESPHOME_LOG_RESET_COLOR, RESET_COLOR_LEN, buffer, buffer_at, buffer_size); this->write_body_to_buffer_(ESPHOME_LOG_RESET_COLOR, RESET_COLOR_LEN, buffer, buffer_at, buffer_size);
} }
@@ -385,7 +385,7 @@ class LoggerMessageTrigger : public Trigger<uint8_t, const char *, const char *>
public: public:
explicit LoggerMessageTrigger(Logger *parent, uint8_t level) { explicit LoggerMessageTrigger(Logger *parent, uint8_t level) {
this->level_ = level; this->level_ = level;
parent->add_on_log_callback([this](uint8_t level, const char *tag, const char *message, size_t message_len) { parent->add_on_log_callback([this](uint8_t level, const char *tag, const char *message) {
if (level <= this->level_) { if (level <= this->level_) {
this->trigger(level, tag, message); this->trigger(level, tag, message);
} }

View File

@@ -184,9 +184,7 @@ void HOT Logger::write_msg_(const char *msg) {
) { ) {
puts(msg); puts(msg);
} else { } else {
// Use tx_buffer_at_ if msg points to tx_buffer_, otherwise fall back to strlen uart_write_bytes(this->uart_num_, msg, strlen(msg));
size_t len = (msg == this->tx_buffer_) ? this->tx_buffer_at_ : strlen(msg);
uart_write_bytes(this->uart_num_, msg, len);
uart_write_bytes(this->uart_num_, "\n", 1); uart_write_bytes(this->uart_num_, "\n", 1);
} }
} }

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