Compare commits
32 Commits
multi_devi
...
ld2410_arr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
357ef51beb | ||
|
|
2fc8131164 | ||
|
|
26007175be | ||
|
|
09e5aa6011 | ||
|
|
9549304007 | ||
|
|
f7ac32ceda | ||
|
|
92365f133d | ||
|
|
461e10ca3e | ||
|
|
205dc9c6f8 | ||
|
|
9daa9a6de8 | ||
|
|
f5eca9ce8d | ||
|
|
487dd6c8d1 | ||
|
|
23b1e428de | ||
|
|
67a25e56b0 | ||
|
|
a27f7cd765 | ||
|
|
f029f4f20e | ||
|
|
79e3d2b2d7 | ||
|
|
c74e5e0f04 | ||
|
|
15ef93ccc9 | ||
|
|
e017250445 | ||
|
|
17497eec43 | ||
|
|
6d0c6329ad | ||
|
|
f35be6b5cc | ||
|
|
b18ff48b4a | ||
|
|
7c28134214 | ||
|
|
16860e8a30 | ||
|
|
5362d1a89f | ||
|
|
5531296ee0 | ||
|
|
47db5e26f3 | ||
|
|
cf5197b68a | ||
|
|
9f831e91b3 | ||
|
|
2df0ebd895 |
23
.github/workflows/lock.yml
vendored
23
.github/workflows/lock.yml
vendored
@@ -1,28 +1,11 @@
|
||||
---
|
||||
name: Lock
|
||||
name: Lock closed issues and PRs
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "30 0 * * *"
|
||||
- cron: "30 0 * * *" # Run daily at 00:30 UTC
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
|
||||
concurrency:
|
||||
group: lock
|
||||
|
||||
jobs:
|
||||
lock:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: dessant/lock-threads@v5.0.1
|
||||
with:
|
||||
pr-inactive-days: "1"
|
||||
pr-lock-reason: ""
|
||||
exclude-any-pr-labels: keep-open
|
||||
|
||||
issue-inactive-days: "7"
|
||||
issue-lock-reason: ""
|
||||
exclude-any-issue-labels: keep-open
|
||||
uses: esphome/workflows/.github/workflows/lock.yml@main
|
||||
|
||||
@@ -14,8 +14,8 @@ from esphome.const import (
|
||||
CONF_WEB_SERVER,
|
||||
)
|
||||
from esphome.core import CORE, coroutine_with_priority
|
||||
from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity
|
||||
from esphome.cpp_generator import MockObjClass
|
||||
from esphome.cpp_helpers import setup_entity
|
||||
|
||||
CODEOWNERS = ["@grahambrown11", "@hwstar"]
|
||||
IS_PLATFORM_COMPONENT = True
|
||||
@@ -149,6 +149,9 @@ _ALARM_CONTROL_PANEL_SCHEMA = (
|
||||
)
|
||||
|
||||
|
||||
_ALARM_CONTROL_PANEL_SCHEMA.add_extra(entity_duplicate_validator("alarm_control_panel"))
|
||||
|
||||
|
||||
def alarm_control_panel_schema(
|
||||
class_: MockObjClass,
|
||||
*,
|
||||
@@ -190,7 +193,7 @@ ALARM_CONTROL_PANEL_CONDITION_SCHEMA = maybe_simple_id(
|
||||
|
||||
|
||||
async def setup_alarm_control_panel_core_(var, config):
|
||||
await setup_entity(var, config)
|
||||
await setup_entity(var, config, "alarm_control_panel")
|
||||
for conf in config.get(CONF_ON_STATE, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [], conf)
|
||||
|
||||
@@ -188,6 +188,17 @@ message DeviceInfoRequest {
|
||||
// Empty
|
||||
}
|
||||
|
||||
message AreaInfo {
|
||||
uint32 area_id = 1;
|
||||
string name = 2;
|
||||
}
|
||||
|
||||
message DeviceInfo {
|
||||
uint32 device_id = 1;
|
||||
string name = 2;
|
||||
uint32 area_id = 3;
|
||||
}
|
||||
|
||||
message DeviceInfoResponse {
|
||||
option (id) = 10;
|
||||
option (source) = SOURCE_SERVER;
|
||||
@@ -236,6 +247,12 @@ message DeviceInfoResponse {
|
||||
|
||||
// Supports receiving and saving api encryption key
|
||||
bool api_encryption_supported = 19;
|
||||
|
||||
repeated DeviceInfo devices = 20;
|
||||
repeated AreaInfo areas = 21;
|
||||
|
||||
// Top-level area info to phase out suggested_area
|
||||
AreaInfo area = 22;
|
||||
}
|
||||
|
||||
message ListEntitiesRequest {
|
||||
@@ -280,6 +297,7 @@ message ListEntitiesBinarySensorResponse {
|
||||
bool disabled_by_default = 7;
|
||||
string icon = 8;
|
||||
EntityCategory entity_category = 9;
|
||||
uint32 device_id = 10;
|
||||
}
|
||||
message BinarySensorStateResponse {
|
||||
option (id) = 21;
|
||||
@@ -315,6 +333,7 @@ message ListEntitiesCoverResponse {
|
||||
string icon = 10;
|
||||
EntityCategory entity_category = 11;
|
||||
bool supports_stop = 12;
|
||||
uint32 device_id = 13;
|
||||
}
|
||||
|
||||
enum LegacyCoverState {
|
||||
@@ -388,6 +407,7 @@ message ListEntitiesFanResponse {
|
||||
string icon = 10;
|
||||
EntityCategory entity_category = 11;
|
||||
repeated string supported_preset_modes = 12;
|
||||
uint32 device_id = 13;
|
||||
}
|
||||
enum FanSpeed {
|
||||
FAN_SPEED_LOW = 0;
|
||||
@@ -471,6 +491,7 @@ message ListEntitiesLightResponse {
|
||||
bool disabled_by_default = 13;
|
||||
string icon = 14;
|
||||
EntityCategory entity_category = 15;
|
||||
uint32 device_id = 16;
|
||||
}
|
||||
message LightStateResponse {
|
||||
option (id) = 24;
|
||||
@@ -563,6 +584,7 @@ message ListEntitiesSensorResponse {
|
||||
SensorLastResetType legacy_last_reset_type = 11;
|
||||
bool disabled_by_default = 12;
|
||||
EntityCategory entity_category = 13;
|
||||
uint32 device_id = 14;
|
||||
}
|
||||
message SensorStateResponse {
|
||||
option (id) = 25;
|
||||
@@ -595,6 +617,7 @@ message ListEntitiesSwitchResponse {
|
||||
bool disabled_by_default = 7;
|
||||
EntityCategory entity_category = 8;
|
||||
string device_class = 9;
|
||||
uint32 device_id = 10;
|
||||
}
|
||||
message SwitchStateResponse {
|
||||
option (id) = 26;
|
||||
@@ -632,6 +655,7 @@ message ListEntitiesTextSensorResponse {
|
||||
bool disabled_by_default = 6;
|
||||
EntityCategory entity_category = 7;
|
||||
string device_class = 8;
|
||||
uint32 device_id = 9;
|
||||
}
|
||||
message TextSensorStateResponse {
|
||||
option (id) = 27;
|
||||
@@ -814,6 +838,7 @@ message ListEntitiesCameraResponse {
|
||||
bool disabled_by_default = 5;
|
||||
string icon = 6;
|
||||
EntityCategory entity_category = 7;
|
||||
uint32 device_id = 8;
|
||||
}
|
||||
|
||||
message CameraImageResponse {
|
||||
@@ -916,6 +941,7 @@ message ListEntitiesClimateResponse {
|
||||
bool supports_target_humidity = 23;
|
||||
float visual_min_humidity = 24;
|
||||
float visual_max_humidity = 25;
|
||||
uint32 device_id = 26;
|
||||
}
|
||||
message ClimateStateResponse {
|
||||
option (id) = 47;
|
||||
@@ -999,6 +1025,7 @@ message ListEntitiesNumberResponse {
|
||||
string unit_of_measurement = 11;
|
||||
NumberMode mode = 12;
|
||||
string device_class = 13;
|
||||
uint32 device_id = 14;
|
||||
}
|
||||
message NumberStateResponse {
|
||||
option (id) = 50;
|
||||
@@ -1039,6 +1066,7 @@ message ListEntitiesSelectResponse {
|
||||
repeated string options = 6;
|
||||
bool disabled_by_default = 7;
|
||||
EntityCategory entity_category = 8;
|
||||
uint32 device_id = 9;
|
||||
}
|
||||
message SelectStateResponse {
|
||||
option (id) = 53;
|
||||
@@ -1081,6 +1109,7 @@ message ListEntitiesSirenResponse {
|
||||
bool supports_duration = 8;
|
||||
bool supports_volume = 9;
|
||||
EntityCategory entity_category = 10;
|
||||
uint32 device_id = 11;
|
||||
}
|
||||
message SirenStateResponse {
|
||||
option (id) = 56;
|
||||
@@ -1144,6 +1173,7 @@ message ListEntitiesLockResponse {
|
||||
|
||||
// Not yet implemented:
|
||||
string code_format = 11;
|
||||
uint32 device_id = 12;
|
||||
}
|
||||
message LockStateResponse {
|
||||
option (id) = 59;
|
||||
@@ -1183,6 +1213,7 @@ message ListEntitiesButtonResponse {
|
||||
bool disabled_by_default = 6;
|
||||
EntityCategory entity_category = 7;
|
||||
string device_class = 8;
|
||||
uint32 device_id = 9;
|
||||
}
|
||||
message ButtonCommandRequest {
|
||||
option (id) = 62;
|
||||
@@ -1238,6 +1269,8 @@ message ListEntitiesMediaPlayerResponse {
|
||||
bool supports_pause = 8;
|
||||
|
||||
repeated MediaPlayerSupportedFormat supported_formats = 9;
|
||||
|
||||
uint32 device_id = 10;
|
||||
}
|
||||
message MediaPlayerStateResponse {
|
||||
option (id) = 64;
|
||||
@@ -1778,6 +1811,7 @@ message ListEntitiesAlarmControlPanelResponse {
|
||||
uint32 supported_features = 8;
|
||||
bool requires_code = 9;
|
||||
bool requires_code_to_arm = 10;
|
||||
uint32 device_id = 11;
|
||||
}
|
||||
|
||||
message AlarmControlPanelStateResponse {
|
||||
@@ -1823,6 +1857,7 @@ message ListEntitiesTextResponse {
|
||||
uint32 max_length = 9;
|
||||
string pattern = 10;
|
||||
TextMode mode = 11;
|
||||
uint32 device_id = 12;
|
||||
}
|
||||
message TextStateResponse {
|
||||
option (id) = 98;
|
||||
@@ -1863,6 +1898,7 @@ message ListEntitiesDateResponse {
|
||||
string icon = 5;
|
||||
bool disabled_by_default = 6;
|
||||
EntityCategory entity_category = 7;
|
||||
uint32 device_id = 8;
|
||||
}
|
||||
message DateStateResponse {
|
||||
option (id) = 101;
|
||||
@@ -1906,6 +1942,7 @@ message ListEntitiesTimeResponse {
|
||||
string icon = 5;
|
||||
bool disabled_by_default = 6;
|
||||
EntityCategory entity_category = 7;
|
||||
uint32 device_id = 8;
|
||||
}
|
||||
message TimeStateResponse {
|
||||
option (id) = 104;
|
||||
@@ -1952,6 +1989,7 @@ message ListEntitiesEventResponse {
|
||||
string device_class = 8;
|
||||
|
||||
repeated string event_types = 9;
|
||||
uint32 device_id = 10;
|
||||
}
|
||||
message EventResponse {
|
||||
option (id) = 108;
|
||||
@@ -1983,6 +2021,7 @@ message ListEntitiesValveResponse {
|
||||
bool assumed_state = 9;
|
||||
bool supports_position = 10;
|
||||
bool supports_stop = 11;
|
||||
uint32 device_id = 12;
|
||||
}
|
||||
|
||||
enum ValveOperation {
|
||||
@@ -2029,6 +2068,7 @@ message ListEntitiesDateTimeResponse {
|
||||
string icon = 5;
|
||||
bool disabled_by_default = 6;
|
||||
EntityCategory entity_category = 7;
|
||||
uint32 device_id = 8;
|
||||
}
|
||||
message DateTimeStateResponse {
|
||||
option (id) = 113;
|
||||
@@ -2069,6 +2109,7 @@ message ListEntitiesUpdateResponse {
|
||||
bool disabled_by_default = 6;
|
||||
EntityCategory entity_category = 7;
|
||||
string device_class = 8;
|
||||
uint32 device_id = 9;
|
||||
}
|
||||
message UpdateStateResponse {
|
||||
option (id) = 117;
|
||||
|
||||
@@ -33,9 +33,14 @@ namespace api {
|
||||
// Since each message could contain multiple protobuf messages when using packet batching,
|
||||
// this limits the number of messages processed, not the number of TCP packets.
|
||||
static constexpr uint8_t MAX_MESSAGES_PER_LOOP = 5;
|
||||
static constexpr uint8_t MAX_PING_RETRIES = 60;
|
||||
static constexpr uint16_t PING_RETRY_INTERVAL = 1000;
|
||||
static constexpr uint32_t KEEPALIVE_DISCONNECT_TIMEOUT = (KEEPALIVE_TIMEOUT_MS * 5) / 2;
|
||||
|
||||
static const char *const TAG = "api.connection";
|
||||
#ifdef USE_ESP32_CAMERA
|
||||
static const int ESP32_CAMERA_STOP_STREAM = 5000;
|
||||
#endif
|
||||
|
||||
APIConnection::APIConnection(std::unique_ptr<socket::Socket> sock, APIServer *parent)
|
||||
: parent_(parent), initial_state_iterator_(this), list_entities_iterator_(this) {
|
||||
@@ -90,16 +95,6 @@ APIConnection::~APIConnection() {
|
||||
}
|
||||
|
||||
void APIConnection::loop() {
|
||||
if (this->remove_)
|
||||
return;
|
||||
|
||||
if (!network::is_connected()) {
|
||||
// when network is disconnected force disconnect immediately
|
||||
// don't wait for timeout
|
||||
this->on_fatal_error();
|
||||
ESP_LOGW(TAG, "%s: Network unavailable; disconnecting", this->get_client_combined_info().c_str());
|
||||
return;
|
||||
}
|
||||
if (this->next_close_) {
|
||||
// requested a disconnect
|
||||
this->helper_->close();
|
||||
@@ -152,20 +147,19 @@ void APIConnection::loop() {
|
||||
|
||||
// Process deferred batch if scheduled
|
||||
if (this->deferred_batch_.batch_scheduled &&
|
||||
App.get_loop_component_start_time() - this->deferred_batch_.batch_start_time >= this->get_batch_delay_ms_()) {
|
||||
now - this->deferred_batch_.batch_start_time >= this->get_batch_delay_ms_()) {
|
||||
this->process_batch_();
|
||||
}
|
||||
|
||||
if (!this->list_entities_iterator_.completed())
|
||||
if (!this->list_entities_iterator_.completed()) {
|
||||
this->list_entities_iterator_.advance();
|
||||
if (!this->initial_state_iterator_.completed() && this->list_entities_iterator_.completed())
|
||||
} else if (!this->initial_state_iterator_.completed()) {
|
||||
this->initial_state_iterator_.advance();
|
||||
}
|
||||
|
||||
static uint8_t max_ping_retries = 60;
|
||||
static uint16_t ping_retry_interval = 1000;
|
||||
if (this->sent_ping_) {
|
||||
// Disconnect if not responded within 2.5*keepalive
|
||||
if (now - this->last_traffic_ > (KEEPALIVE_TIMEOUT_MS * 5) / 2) {
|
||||
if (now - this->last_traffic_ > KEEPALIVE_DISCONNECT_TIMEOUT) {
|
||||
on_fatal_error();
|
||||
ESP_LOGW(TAG, "%s is unresponsive; disconnecting", this->get_client_combined_info().c_str());
|
||||
}
|
||||
@@ -173,17 +167,15 @@ void APIConnection::loop() {
|
||||
ESP_LOGVV(TAG, "Sending keepalive PING");
|
||||
this->sent_ping_ = this->send_message(PingRequest());
|
||||
if (!this->sent_ping_) {
|
||||
this->next_ping_retry_ = now + ping_retry_interval;
|
||||
this->next_ping_retry_ = now + PING_RETRY_INTERVAL;
|
||||
this->ping_retries_++;
|
||||
std::string warn_str = str_sprintf("%s: Sending keepalive failed %u time(s);",
|
||||
this->get_client_combined_info().c_str(), this->ping_retries_);
|
||||
if (this->ping_retries_ >= max_ping_retries) {
|
||||
if (this->ping_retries_ >= MAX_PING_RETRIES) {
|
||||
on_fatal_error();
|
||||
ESP_LOGE(TAG, "%s disconnecting", warn_str.c_str());
|
||||
ESP_LOGE(TAG, "%s: Ping failed %u times", this->get_client_combined_info().c_str(), this->ping_retries_);
|
||||
} else if (this->ping_retries_ >= 10) {
|
||||
ESP_LOGW(TAG, "%s retrying in %u ms", warn_str.c_str(), ping_retry_interval);
|
||||
ESP_LOGW(TAG, "%s: Ping retry %u", this->get_client_combined_info().c_str(), this->ping_retries_);
|
||||
} else {
|
||||
ESP_LOGD(TAG, "%s retrying in %u ms", warn_str.c_str(), ping_retry_interval);
|
||||
ESP_LOGD(TAG, "%s: Ping retry %u", this->get_client_combined_info().c_str(), this->ping_retries_);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -207,22 +199,20 @@ void APIConnection::loop() {
|
||||
// bool done = 3;
|
||||
buffer.encode_bool(3, done);
|
||||
|
||||
bool success = this->send_buffer(buffer, 44);
|
||||
bool success = this->send_buffer(buffer, CameraImageResponse::MESSAGE_TYPE);
|
||||
|
||||
if (success) {
|
||||
this->image_reader_.consume_data(to_send);
|
||||
}
|
||||
if (success && done) {
|
||||
this->image_reader_.return_image();
|
||||
if (done) {
|
||||
this->image_reader_.return_image();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (state_subs_at_ != -1) {
|
||||
if (state_subs_at_ >= 0) {
|
||||
const auto &subs = this->parent_->get_state_subs();
|
||||
if (state_subs_at_ >= (int) subs.size()) {
|
||||
state_subs_at_ = -1;
|
||||
} else {
|
||||
if (state_subs_at_ < static_cast<int>(subs.size())) {
|
||||
auto &it = subs[state_subs_at_];
|
||||
SubscribeHomeAssistantStateResponse resp;
|
||||
resp.entity_id = it.entity_id;
|
||||
@@ -231,6 +221,8 @@ void APIConnection::loop() {
|
||||
if (this->send_message(resp)) {
|
||||
state_subs_at_++;
|
||||
}
|
||||
} else {
|
||||
state_subs_at_ = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -284,6 +276,11 @@ uint16_t APIConnection::encode_message_to_buffer(ProtoMessage &msg, uint16_t mes
|
||||
// Encode directly into buffer
|
||||
msg.encode(buffer);
|
||||
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
// Log the message for VV debugging
|
||||
conn->log_send_message_(msg.message_name(), msg.dump());
|
||||
#endif
|
||||
|
||||
// Calculate actual encoded size (not including header that was already added)
|
||||
size_t actual_payload_size = shared_buf.size() - size_before_encode;
|
||||
|
||||
@@ -1440,7 +1437,7 @@ void APIConnection::alarm_control_panel_command(const AlarmControlPanelCommandRe
|
||||
|
||||
#ifdef USE_EVENT
|
||||
void APIConnection::send_event(event::Event *event, const std::string &event_type) {
|
||||
this->schedule_message_(event, MessageCreator(event_type, EventResponse::MESSAGE_TYPE), EventResponse::MESSAGE_TYPE);
|
||||
this->schedule_message_(event, MessageCreator(event_type), EventResponse::MESSAGE_TYPE);
|
||||
}
|
||||
void APIConnection::send_event_info(event::Event *event) {
|
||||
this->schedule_message_(event, &APIConnection::try_send_event_info, ListEntitiesEventResponse::MESSAGE_TYPE);
|
||||
@@ -1629,6 +1626,23 @@ DeviceInfoResponse APIConnection::device_info(const DeviceInfoRequest &msg) {
|
||||
#endif
|
||||
#ifdef USE_API_NOISE
|
||||
resp.api_encryption_supported = true;
|
||||
#endif
|
||||
#ifdef USE_DEVICES
|
||||
for (auto const &device : App.get_devices()) {
|
||||
DeviceInfo device_info;
|
||||
device_info.device_id = device->get_device_id();
|
||||
device_info.name = device->get_name();
|
||||
device_info.area_id = device->get_area_id();
|
||||
resp.devices.push_back(device_info);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_AREAS
|
||||
for (auto const &area : App.get_areas()) {
|
||||
AreaInfo area_info;
|
||||
area_info.area_id = area->get_area_id();
|
||||
area_info.name = area->get_name();
|
||||
resp.areas.push_back(area_info);
|
||||
}
|
||||
#endif
|
||||
return resp;
|
||||
}
|
||||
@@ -1778,7 +1792,8 @@ void APIConnection::process_batch_() {
|
||||
const auto &item = this->deferred_batch_.items[0];
|
||||
|
||||
// Let the creator calculate size and encode if it fits
|
||||
uint16_t payload_size = item.creator(item.entity, this, std::numeric_limits<uint16_t>::max(), true);
|
||||
uint16_t payload_size =
|
||||
item.creator(item.entity, this, std::numeric_limits<uint16_t>::max(), true, item.message_type);
|
||||
|
||||
if (payload_size > 0 &&
|
||||
this->send_buffer(ProtoWriteBuffer{&this->parent_->get_shared_buffer_ref()}, item.message_type)) {
|
||||
@@ -1828,7 +1843,7 @@ void APIConnection::process_batch_() {
|
||||
for (const auto &item : this->deferred_batch_.items) {
|
||||
// Try to encode message
|
||||
// The creator will calculate overhead to determine if the message fits
|
||||
uint16_t payload_size = item.creator(item.entity, this, remaining_size, false);
|
||||
uint16_t payload_size = item.creator(item.entity, this, remaining_size, false, item.message_type);
|
||||
|
||||
if (payload_size == 0) {
|
||||
// Message won't fit, stop processing
|
||||
@@ -1891,21 +1906,23 @@ void APIConnection::process_batch_() {
|
||||
}
|
||||
|
||||
uint16_t APIConnection::MessageCreator::operator()(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
|
||||
bool is_single) const {
|
||||
switch (message_type_) {
|
||||
case 0: // Function pointer
|
||||
return data_.ptr(entity, conn, remaining_size, is_single);
|
||||
|
||||
bool is_single, uint16_t message_type) const {
|
||||
if (has_tagged_string_ptr_()) {
|
||||
// Handle string-based messages
|
||||
switch (message_type) {
|
||||
#ifdef USE_EVENT
|
||||
case EventResponse::MESSAGE_TYPE: {
|
||||
auto *e = static_cast<event::Event *>(entity);
|
||||
return APIConnection::try_send_event_response(e, *data_.string_ptr, conn, remaining_size, is_single);
|
||||
}
|
||||
case EventResponse::MESSAGE_TYPE: {
|
||||
auto *e = static_cast<event::Event *>(entity);
|
||||
return APIConnection::try_send_event_response(e, *get_string_ptr_(), conn, remaining_size, is_single);
|
||||
}
|
||||
#endif
|
||||
|
||||
default:
|
||||
// Should not happen, return 0 to indicate no message
|
||||
return 0;
|
||||
default:
|
||||
// Should not happen, return 0 to indicate no message
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
// Function pointer case
|
||||
return data_.ptr(entity, conn, remaining_size, is_single);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -301,6 +301,9 @@ class APIConnection : public APIServerConnection {
|
||||
response.icon = entity->get_icon();
|
||||
response.disabled_by_default = entity->is_disabled_by_default();
|
||||
response.entity_category = static_cast<enums::EntityCategory>(entity->get_entity_category());
|
||||
#ifdef USE_DEVICES
|
||||
response.device_id = entity->get_device_id();
|
||||
#endif
|
||||
}
|
||||
|
||||
// Helper function to fill common entity state fields
|
||||
@@ -480,55 +483,57 @@ class APIConnection : public APIServerConnection {
|
||||
// Function pointer type for message encoding
|
||||
using MessageCreatorPtr = uint16_t (*)(EntityBase *, APIConnection *, uint32_t remaining_size, bool is_single);
|
||||
|
||||
// Optimized MessageCreator class using union dispatch
|
||||
// Optimized MessageCreator class using tagged pointer
|
||||
class MessageCreator {
|
||||
// Ensure pointer alignment allows LSB tagging
|
||||
static_assert(alignof(std::string *) > 1, "String pointer alignment must be > 1 for LSB tagging");
|
||||
|
||||
public:
|
||||
// Constructor for function pointer (message_type = 0)
|
||||
MessageCreator(MessageCreatorPtr ptr) : message_type_(0) { data_.ptr = ptr; }
|
||||
// Constructor for function pointer
|
||||
MessageCreator(MessageCreatorPtr ptr) {
|
||||
// Function pointers are always aligned, so LSB is 0
|
||||
data_.ptr = ptr;
|
||||
}
|
||||
|
||||
// Constructor for string state capture
|
||||
MessageCreator(const std::string &value, uint16_t msg_type) : message_type_(msg_type) {
|
||||
data_.string_ptr = new std::string(value);
|
||||
explicit MessageCreator(const std::string &str_value) {
|
||||
// Allocate string and tag the pointer
|
||||
auto *str = new std::string(str_value);
|
||||
// Set LSB to 1 to indicate string pointer
|
||||
data_.tagged = reinterpret_cast<uintptr_t>(str) | 1;
|
||||
}
|
||||
|
||||
// Destructor
|
||||
~MessageCreator() {
|
||||
// Clean up string data for string-based message types
|
||||
if (uses_string_data_()) {
|
||||
delete data_.string_ptr;
|
||||
if (has_tagged_string_ptr_()) {
|
||||
delete get_string_ptr_();
|
||||
}
|
||||
}
|
||||
|
||||
// Copy constructor
|
||||
MessageCreator(const MessageCreator &other) : message_type_(other.message_type_) {
|
||||
if (message_type_ == 0) {
|
||||
data_.ptr = other.data_.ptr;
|
||||
} else if (uses_string_data_()) {
|
||||
data_.string_ptr = new std::string(*other.data_.string_ptr);
|
||||
MessageCreator(const MessageCreator &other) {
|
||||
if (other.has_tagged_string_ptr_()) {
|
||||
auto *str = new std::string(*other.get_string_ptr_());
|
||||
data_.tagged = reinterpret_cast<uintptr_t>(str) | 1;
|
||||
} else {
|
||||
data_ = other.data_; // For POD types
|
||||
data_ = other.data_;
|
||||
}
|
||||
}
|
||||
|
||||
// Move constructor
|
||||
MessageCreator(MessageCreator &&other) noexcept : data_(other.data_), message_type_(other.message_type_) {
|
||||
other.message_type_ = 0; // Reset other to function pointer type
|
||||
other.data_.ptr = nullptr;
|
||||
}
|
||||
MessageCreator(MessageCreator &&other) noexcept : data_(other.data_) { other.data_.ptr = nullptr; }
|
||||
|
||||
// Assignment operators (needed for batch deduplication)
|
||||
MessageCreator &operator=(const MessageCreator &other) {
|
||||
if (this != &other) {
|
||||
// Clean up current string data if needed
|
||||
if (uses_string_data_()) {
|
||||
delete data_.string_ptr;
|
||||
if (has_tagged_string_ptr_()) {
|
||||
delete get_string_ptr_();
|
||||
}
|
||||
// Copy new data
|
||||
message_type_ = other.message_type_;
|
||||
if (other.message_type_ == 0) {
|
||||
data_.ptr = other.data_.ptr;
|
||||
} else if (other.uses_string_data_()) {
|
||||
data_.string_ptr = new std::string(*other.data_.string_ptr);
|
||||
if (other.has_tagged_string_ptr_()) {
|
||||
auto *str = new std::string(*other.get_string_ptr_());
|
||||
data_.tagged = reinterpret_cast<uintptr_t>(str) | 1;
|
||||
} else {
|
||||
data_ = other.data_;
|
||||
}
|
||||
@@ -539,30 +544,35 @@ class APIConnection : public APIServerConnection {
|
||||
MessageCreator &operator=(MessageCreator &&other) noexcept {
|
||||
if (this != &other) {
|
||||
// Clean up current string data if needed
|
||||
if (uses_string_data_()) {
|
||||
delete data_.string_ptr;
|
||||
if (has_tagged_string_ptr_()) {
|
||||
delete get_string_ptr_();
|
||||
}
|
||||
// Move data
|
||||
message_type_ = other.message_type_;
|
||||
data_ = other.data_;
|
||||
// Reset other to safe state
|
||||
other.message_type_ = 0;
|
||||
other.data_.ptr = nullptr;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Call operator
|
||||
uint16_t operator()(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single) const;
|
||||
// Call operator - now accepts message_type as parameter
|
||||
uint16_t operator()(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single,
|
||||
uint16_t message_type) const;
|
||||
|
||||
private:
|
||||
// Helper to check if this message type uses heap-allocated strings
|
||||
bool uses_string_data_() const { return message_type_ == EventResponse::MESSAGE_TYPE; }
|
||||
union CreatorData {
|
||||
MessageCreatorPtr ptr; // 8 bytes
|
||||
std::string *string_ptr; // 8 bytes
|
||||
} data_; // 8 bytes
|
||||
uint16_t message_type_; // 2 bytes (0 = function ptr, >0 = state capture)
|
||||
// Check if this contains a string pointer
|
||||
bool has_tagged_string_ptr_() const { return (data_.tagged & 1) != 0; }
|
||||
|
||||
// Get the actual string pointer (clears the tag bit)
|
||||
std::string *get_string_ptr_() const {
|
||||
// NOLINTNEXTLINE(performance-no-int-to-ptr)
|
||||
return reinterpret_cast<std::string *>(data_.tagged & ~uintptr_t(1));
|
||||
}
|
||||
|
||||
union {
|
||||
MessageCreatorPtr ptr;
|
||||
uintptr_t tagged;
|
||||
} data_; // 4 bytes on 32-bit
|
||||
};
|
||||
|
||||
// Generic batching mechanism for both state updates and entity info
|
||||
|
||||
@@ -66,6 +66,17 @@ const char *api_error_to_str(APIError err) {
|
||||
return "UNKNOWN";
|
||||
}
|
||||
|
||||
// Default implementation for loop - handles sending buffered data
|
||||
APIError APIFrameHelper::loop() {
|
||||
if (!this->tx_buf_.empty()) {
|
||||
APIError err = try_send_tx_buf_();
|
||||
if (err != APIError::OK && err != APIError::WOULD_BLOCK) {
|
||||
return err;
|
||||
}
|
||||
}
|
||||
return APIError::OK; // Convert WOULD_BLOCK to OK to avoid connection termination
|
||||
}
|
||||
|
||||
// Helper method to buffer data from IOVs
|
||||
void APIFrameHelper::buffer_data_from_iov_(const struct iovec *iov, int iovcnt, uint16_t total_write_len) {
|
||||
SendBuffer buffer;
|
||||
@@ -287,13 +298,8 @@ APIError APINoiseFrameHelper::loop() {
|
||||
}
|
||||
}
|
||||
|
||||
if (!this->tx_buf_.empty()) {
|
||||
APIError err = try_send_tx_buf_();
|
||||
if (err != APIError::OK && err != APIError::WOULD_BLOCK) {
|
||||
return err;
|
||||
}
|
||||
}
|
||||
return APIError::OK; // Convert WOULD_BLOCK to OK to avoid connection termination
|
||||
// Use base class implementation for buffer sending
|
||||
return APIFrameHelper::loop();
|
||||
}
|
||||
|
||||
/** Read a packet into the rx_buf_. If successful, stores frame data in the frame parameter
|
||||
@@ -339,17 +345,15 @@ APIError APINoiseFrameHelper::try_read_frame_(ParsedFrame *frame) {
|
||||
return APIError::WOULD_BLOCK;
|
||||
}
|
||||
|
||||
if (rx_header_buf_[0] != 0x01) {
|
||||
state_ = State::FAILED;
|
||||
HELPER_LOG("Bad indicator byte %u", rx_header_buf_[0]);
|
||||
return APIError::BAD_INDICATOR;
|
||||
}
|
||||
// header reading done
|
||||
}
|
||||
|
||||
// read body
|
||||
uint8_t indicator = rx_header_buf_[0];
|
||||
if (indicator != 0x01) {
|
||||
state_ = State::FAILED;
|
||||
HELPER_LOG("Bad indicator byte %u", indicator);
|
||||
return APIError::BAD_INDICATOR;
|
||||
}
|
||||
|
||||
uint16_t msg_size = (((uint16_t) rx_header_buf_[1]) << 8) | rx_header_buf_[2];
|
||||
|
||||
if (state_ != State::DATA && msg_size > 128) {
|
||||
@@ -595,10 +599,6 @@ APIError APINoiseFrameHelper::read_packet(ReadPacketBuffer *buffer) {
|
||||
return APIError::BAD_DATA_PACKET;
|
||||
}
|
||||
|
||||
// uint16_t type;
|
||||
// uint16_t data_len;
|
||||
// uint8_t *data;
|
||||
// uint8_t *padding; zero or more bytes to fill up the rest of the packet
|
||||
uint16_t type = (((uint16_t) msg_data[0]) << 8) | msg_data[1];
|
||||
uint16_t data_len = (((uint16_t) msg_data[2]) << 8) | msg_data[3];
|
||||
if (data_len > msg_size - 4) {
|
||||
@@ -831,18 +831,12 @@ APIError APIPlaintextFrameHelper::init() {
|
||||
state_ = State::DATA;
|
||||
return APIError::OK;
|
||||
}
|
||||
/// Not used for plaintext
|
||||
APIError APIPlaintextFrameHelper::loop() {
|
||||
if (state_ != State::DATA) {
|
||||
return APIError::BAD_STATE;
|
||||
}
|
||||
if (!this->tx_buf_.empty()) {
|
||||
APIError err = try_send_tx_buf_();
|
||||
if (err != APIError::OK && err != APIError::WOULD_BLOCK) {
|
||||
return err;
|
||||
}
|
||||
}
|
||||
return APIError::OK; // Convert WOULD_BLOCK to OK to avoid connection termination
|
||||
// Use base class implementation for buffer sending
|
||||
return APIFrameHelper::loop();
|
||||
}
|
||||
|
||||
/** Read a packet into the rx_buf_. If successful, stores frame data in the frame parameter
|
||||
|
||||
@@ -38,7 +38,7 @@ struct PacketInfo {
|
||||
: message_type(type), offset(off), payload_size(size), padding(0) {}
|
||||
};
|
||||
|
||||
enum class APIError : int {
|
||||
enum class APIError : uint16_t {
|
||||
OK = 0,
|
||||
WOULD_BLOCK = 1001,
|
||||
BAD_HANDSHAKE_PACKET_LEN = 1002,
|
||||
@@ -74,7 +74,7 @@ class APIFrameHelper {
|
||||
}
|
||||
virtual ~APIFrameHelper() = default;
|
||||
virtual APIError init() = 0;
|
||||
virtual APIError loop() = 0;
|
||||
virtual APIError loop();
|
||||
virtual APIError read_packet(ReadPacketBuffer *buffer) = 0;
|
||||
bool can_write_without_blocking() { return state_ == State::DATA && tx_buf_.empty(); }
|
||||
std::string getpeername() { return socket_->getpeername(); }
|
||||
|
||||
@@ -812,6 +812,103 @@ void PingResponse::dump_to(std::string &out) const { out.append("PingResponse {}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void DeviceInfoRequest::dump_to(std::string &out) const { out.append("DeviceInfoRequest {}"); }
|
||||
#endif
|
||||
bool AreaInfo::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
switch (field_id) {
|
||||
case 1: {
|
||||
this->area_id = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
bool AreaInfo::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
|
||||
switch (field_id) {
|
||||
case 2: {
|
||||
this->name = value.as_string();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
void AreaInfo::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_uint32(1, this->area_id);
|
||||
buffer.encode_string(2, this->name);
|
||||
}
|
||||
void AreaInfo::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_uint32_field(total_size, 1, this->area_id, false);
|
||||
ProtoSize::add_string_field(total_size, 1, this->name, false);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void AreaInfo::dump_to(std::string &out) const {
|
||||
__attribute__((unused)) char buffer[64];
|
||||
out.append("AreaInfo {\n");
|
||||
out.append(" area_id: ");
|
||||
sprintf(buffer, "%" PRIu32, this->area_id);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
out.append(" name: ");
|
||||
out.append("'").append(this->name).append("'");
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
bool DeviceInfo::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
switch (field_id) {
|
||||
case 1: {
|
||||
this->device_id = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
case 3: {
|
||||
this->area_id = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
bool DeviceInfo::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
|
||||
switch (field_id) {
|
||||
case 2: {
|
||||
this->name = value.as_string();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
void DeviceInfo::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_uint32(1, this->device_id);
|
||||
buffer.encode_string(2, this->name);
|
||||
buffer.encode_uint32(3, this->area_id);
|
||||
}
|
||||
void DeviceInfo::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_uint32_field(total_size, 1, this->device_id, false);
|
||||
ProtoSize::add_string_field(total_size, 1, this->name, false);
|
||||
ProtoSize::add_uint32_field(total_size, 1, this->area_id, false);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void DeviceInfo::dump_to(std::string &out) const {
|
||||
__attribute__((unused)) char buffer[64];
|
||||
out.append("DeviceInfo {\n");
|
||||
out.append(" device_id: ");
|
||||
sprintf(buffer, "%" PRIu32, this->device_id);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
out.append(" name: ");
|
||||
out.append("'").append(this->name).append("'");
|
||||
out.append("\n");
|
||||
|
||||
out.append(" area_id: ");
|
||||
sprintf(buffer, "%" PRIu32, this->area_id);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
bool DeviceInfoResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
switch (field_id) {
|
||||
case 1: {
|
||||
@@ -896,6 +993,18 @@ bool DeviceInfoResponse::decode_length(uint32_t field_id, ProtoLengthDelimited v
|
||||
this->bluetooth_mac_address = value.as_string();
|
||||
return true;
|
||||
}
|
||||
case 20: {
|
||||
this->devices.push_back(value.as_message<DeviceInfo>());
|
||||
return true;
|
||||
}
|
||||
case 21: {
|
||||
this->areas.push_back(value.as_message<AreaInfo>());
|
||||
return true;
|
||||
}
|
||||
case 22: {
|
||||
this->area = value.as_message<AreaInfo>();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -920,6 +1029,13 @@ void DeviceInfoResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_string(16, this->suggested_area);
|
||||
buffer.encode_string(18, this->bluetooth_mac_address);
|
||||
buffer.encode_bool(19, this->api_encryption_supported);
|
||||
for (auto &it : this->devices) {
|
||||
buffer.encode_message<DeviceInfo>(20, it, true);
|
||||
}
|
||||
for (auto &it : this->areas) {
|
||||
buffer.encode_message<AreaInfo>(21, it, true);
|
||||
}
|
||||
buffer.encode_message<AreaInfo>(22, this->area);
|
||||
}
|
||||
void DeviceInfoResponse::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_bool_field(total_size, 1, this->uses_password, false);
|
||||
@@ -941,6 +1057,9 @@ void DeviceInfoResponse::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_string_field(total_size, 2, this->suggested_area, false);
|
||||
ProtoSize::add_string_field(total_size, 2, this->bluetooth_mac_address, false);
|
||||
ProtoSize::add_bool_field(total_size, 2, this->api_encryption_supported, false);
|
||||
ProtoSize::add_repeated_message(total_size, 2, this->devices);
|
||||
ProtoSize::add_repeated_message(total_size, 2, this->areas);
|
||||
ProtoSize::add_message_object(total_size, 2, this->area, false);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void DeviceInfoResponse::dump_to(std::string &out) const {
|
||||
@@ -1026,6 +1145,22 @@ void DeviceInfoResponse::dump_to(std::string &out) const {
|
||||
out.append(" api_encryption_supported: ");
|
||||
out.append(YESNO(this->api_encryption_supported));
|
||||
out.append("\n");
|
||||
|
||||
for (const auto &it : this->devices) {
|
||||
out.append(" devices: ");
|
||||
it.dump_to(out);
|
||||
out.append("\n");
|
||||
}
|
||||
|
||||
for (const auto &it : this->areas) {
|
||||
out.append(" areas: ");
|
||||
it.dump_to(out);
|
||||
out.append("\n");
|
||||
}
|
||||
|
||||
out.append(" area: ");
|
||||
this->area.dump_to(out);
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
@@ -1052,6 +1187,10 @@ bool ListEntitiesBinarySensorResponse::decode_varint(uint32_t field_id, ProtoVar
|
||||
this->entity_category = value.as_enum<enums::EntityCategory>();
|
||||
return true;
|
||||
}
|
||||
case 10: {
|
||||
this->device_id = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -1102,6 +1241,7 @@ void ListEntitiesBinarySensorResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_bool(7, this->disabled_by_default);
|
||||
buffer.encode_string(8, this->icon);
|
||||
buffer.encode_enum<enums::EntityCategory>(9, this->entity_category);
|
||||
buffer.encode_uint32(10, this->device_id);
|
||||
}
|
||||
void ListEntitiesBinarySensorResponse::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_string_field(total_size, 1, this->object_id, false);
|
||||
@@ -1113,6 +1253,7 @@ void ListEntitiesBinarySensorResponse::calculate_size(uint32_t &total_size) cons
|
||||
ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false);
|
||||
ProtoSize::add_string_field(total_size, 1, this->icon, false);
|
||||
ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false);
|
||||
ProtoSize::add_uint32_field(total_size, 1, this->device_id, false);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void ListEntitiesBinarySensorResponse::dump_to(std::string &out) const {
|
||||
@@ -1154,6 +1295,11 @@ void ListEntitiesBinarySensorResponse::dump_to(std::string &out) const {
|
||||
out.append(" entity_category: ");
|
||||
out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" device_id: ");
|
||||
sprintf(buffer, "%" PRIu32, this->device_id);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
@@ -1236,6 +1382,10 @@ bool ListEntitiesCoverResponse::decode_varint(uint32_t field_id, ProtoVarInt val
|
||||
this->supports_stop = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
case 13: {
|
||||
this->device_id = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -1289,6 +1439,7 @@ void ListEntitiesCoverResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_string(10, this->icon);
|
||||
buffer.encode_enum<enums::EntityCategory>(11, this->entity_category);
|
||||
buffer.encode_bool(12, this->supports_stop);
|
||||
buffer.encode_uint32(13, this->device_id);
|
||||
}
|
||||
void ListEntitiesCoverResponse::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_string_field(total_size, 1, this->object_id, false);
|
||||
@@ -1303,6 +1454,7 @@ void ListEntitiesCoverResponse::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_string_field(total_size, 1, this->icon, false);
|
||||
ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false);
|
||||
ProtoSize::add_bool_field(total_size, 1, this->supports_stop, false);
|
||||
ProtoSize::add_uint32_field(total_size, 1, this->device_id, false);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void ListEntitiesCoverResponse::dump_to(std::string &out) const {
|
||||
@@ -1356,6 +1508,11 @@ void ListEntitiesCoverResponse::dump_to(std::string &out) const {
|
||||
out.append(" supports_stop: ");
|
||||
out.append(YESNO(this->supports_stop));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" device_id: ");
|
||||
sprintf(buffer, "%" PRIu32, this->device_id);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
@@ -1565,6 +1722,10 @@ bool ListEntitiesFanResponse::decode_varint(uint32_t field_id, ProtoVarInt value
|
||||
this->entity_category = value.as_enum<enums::EntityCategory>();
|
||||
return true;
|
||||
}
|
||||
case 13: {
|
||||
this->device_id = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -1620,6 +1781,7 @@ void ListEntitiesFanResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
for (auto &it : this->supported_preset_modes) {
|
||||
buffer.encode_string(12, it, true);
|
||||
}
|
||||
buffer.encode_uint32(13, this->device_id);
|
||||
}
|
||||
void ListEntitiesFanResponse::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_string_field(total_size, 1, this->object_id, false);
|
||||
@@ -1638,6 +1800,7 @@ void ListEntitiesFanResponse::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_string_field(total_size, 1, it, true);
|
||||
}
|
||||
}
|
||||
ProtoSize::add_uint32_field(total_size, 1, this->device_id, false);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void ListEntitiesFanResponse::dump_to(std::string &out) const {
|
||||
@@ -1694,6 +1857,11 @@ void ListEntitiesFanResponse::dump_to(std::string &out) const {
|
||||
out.append("'").append(it).append("'");
|
||||
out.append("\n");
|
||||
}
|
||||
|
||||
out.append(" device_id: ");
|
||||
sprintf(buffer, "%" PRIu32, this->device_id);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
@@ -1987,6 +2155,10 @@ bool ListEntitiesLightResponse::decode_varint(uint32_t field_id, ProtoVarInt val
|
||||
this->entity_category = value.as_enum<enums::EntityCategory>();
|
||||
return true;
|
||||
}
|
||||
case 16: {
|
||||
this->device_id = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -2055,6 +2227,7 @@ void ListEntitiesLightResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_bool(13, this->disabled_by_default);
|
||||
buffer.encode_string(14, this->icon);
|
||||
buffer.encode_enum<enums::EntityCategory>(15, this->entity_category);
|
||||
buffer.encode_uint32(16, this->device_id);
|
||||
}
|
||||
void ListEntitiesLightResponse::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_string_field(total_size, 1, this->object_id, false);
|
||||
@@ -2080,6 +2253,7 @@ void ListEntitiesLightResponse::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false);
|
||||
ProtoSize::add_string_field(total_size, 1, this->icon, false);
|
||||
ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false);
|
||||
ProtoSize::add_uint32_field(total_size, 2, this->device_id, false);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void ListEntitiesLightResponse::dump_to(std::string &out) const {
|
||||
@@ -2151,6 +2325,11 @@ void ListEntitiesLightResponse::dump_to(std::string &out) const {
|
||||
out.append(" entity_category: ");
|
||||
out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" device_id: ");
|
||||
sprintf(buffer, "%" PRIu32, this->device_id);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
@@ -2658,6 +2837,10 @@ bool ListEntitiesSensorResponse::decode_varint(uint32_t field_id, ProtoVarInt va
|
||||
this->entity_category = value.as_enum<enums::EntityCategory>();
|
||||
return true;
|
||||
}
|
||||
case 14: {
|
||||
this->device_id = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -2716,6 +2899,7 @@ void ListEntitiesSensorResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_enum<enums::SensorLastResetType>(11, this->legacy_last_reset_type);
|
||||
buffer.encode_bool(12, this->disabled_by_default);
|
||||
buffer.encode_enum<enums::EntityCategory>(13, this->entity_category);
|
||||
buffer.encode_uint32(14, this->device_id);
|
||||
}
|
||||
void ListEntitiesSensorResponse::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_string_field(total_size, 1, this->object_id, false);
|
||||
@@ -2731,6 +2915,7 @@ void ListEntitiesSensorResponse::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->legacy_last_reset_type), false);
|
||||
ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false);
|
||||
ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false);
|
||||
ProtoSize::add_uint32_field(total_size, 1, this->device_id, false);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void ListEntitiesSensorResponse::dump_to(std::string &out) const {
|
||||
@@ -2789,6 +2974,11 @@ void ListEntitiesSensorResponse::dump_to(std::string &out) const {
|
||||
out.append(" entity_category: ");
|
||||
out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" device_id: ");
|
||||
sprintf(buffer, "%" PRIu32, this->device_id);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
@@ -2860,6 +3050,10 @@ bool ListEntitiesSwitchResponse::decode_varint(uint32_t field_id, ProtoVarInt va
|
||||
this->entity_category = value.as_enum<enums::EntityCategory>();
|
||||
return true;
|
||||
}
|
||||
case 10: {
|
||||
this->device_id = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -2910,6 +3104,7 @@ void ListEntitiesSwitchResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_bool(7, this->disabled_by_default);
|
||||
buffer.encode_enum<enums::EntityCategory>(8, this->entity_category);
|
||||
buffer.encode_string(9, this->device_class);
|
||||
buffer.encode_uint32(10, this->device_id);
|
||||
}
|
||||
void ListEntitiesSwitchResponse::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_string_field(total_size, 1, this->object_id, false);
|
||||
@@ -2921,6 +3116,7 @@ void ListEntitiesSwitchResponse::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false);
|
||||
ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false);
|
||||
ProtoSize::add_string_field(total_size, 1, this->device_class, false);
|
||||
ProtoSize::add_uint32_field(total_size, 1, this->device_id, false);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void ListEntitiesSwitchResponse::dump_to(std::string &out) const {
|
||||
@@ -2962,6 +3158,11 @@ void ListEntitiesSwitchResponse::dump_to(std::string &out) const {
|
||||
out.append(" device_class: ");
|
||||
out.append("'").append(this->device_class).append("'");
|
||||
out.append("\n");
|
||||
|
||||
out.append(" device_id: ");
|
||||
sprintf(buffer, "%" PRIu32, this->device_id);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
@@ -3061,6 +3262,10 @@ bool ListEntitiesTextSensorResponse::decode_varint(uint32_t field_id, ProtoVarIn
|
||||
this->entity_category = value.as_enum<enums::EntityCategory>();
|
||||
return true;
|
||||
}
|
||||
case 9: {
|
||||
this->device_id = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -3110,6 +3315,7 @@ void ListEntitiesTextSensorResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_bool(6, this->disabled_by_default);
|
||||
buffer.encode_enum<enums::EntityCategory>(7, this->entity_category);
|
||||
buffer.encode_string(8, this->device_class);
|
||||
buffer.encode_uint32(9, this->device_id);
|
||||
}
|
||||
void ListEntitiesTextSensorResponse::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_string_field(total_size, 1, this->object_id, false);
|
||||
@@ -3120,6 +3326,7 @@ void ListEntitiesTextSensorResponse::calculate_size(uint32_t &total_size) const
|
||||
ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false);
|
||||
ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false);
|
||||
ProtoSize::add_string_field(total_size, 1, this->device_class, false);
|
||||
ProtoSize::add_uint32_field(total_size, 1, this->device_id, false);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void ListEntitiesTextSensorResponse::dump_to(std::string &out) const {
|
||||
@@ -3157,6 +3364,11 @@ void ListEntitiesTextSensorResponse::dump_to(std::string &out) const {
|
||||
out.append(" device_class: ");
|
||||
out.append("'").append(this->device_class).append("'");
|
||||
out.append("\n");
|
||||
|
||||
out.append(" device_id: ");
|
||||
sprintf(buffer, "%" PRIu32, this->device_id);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
@@ -3922,6 +4134,10 @@ bool ListEntitiesCameraResponse::decode_varint(uint32_t field_id, ProtoVarInt va
|
||||
this->entity_category = value.as_enum<enums::EntityCategory>();
|
||||
return true;
|
||||
}
|
||||
case 8: {
|
||||
this->device_id = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -3966,6 +4182,7 @@ void ListEntitiesCameraResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_bool(5, this->disabled_by_default);
|
||||
buffer.encode_string(6, this->icon);
|
||||
buffer.encode_enum<enums::EntityCategory>(7, this->entity_category);
|
||||
buffer.encode_uint32(8, this->device_id);
|
||||
}
|
||||
void ListEntitiesCameraResponse::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_string_field(total_size, 1, this->object_id, false);
|
||||
@@ -3975,6 +4192,7 @@ void ListEntitiesCameraResponse::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false);
|
||||
ProtoSize::add_string_field(total_size, 1, this->icon, false);
|
||||
ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false);
|
||||
ProtoSize::add_uint32_field(total_size, 1, this->device_id, false);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void ListEntitiesCameraResponse::dump_to(std::string &out) const {
|
||||
@@ -4008,6 +4226,11 @@ void ListEntitiesCameraResponse::dump_to(std::string &out) const {
|
||||
out.append(" entity_category: ");
|
||||
out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" device_id: ");
|
||||
sprintf(buffer, "%" PRIu32, this->device_id);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
@@ -4156,6 +4379,10 @@ bool ListEntitiesClimateResponse::decode_varint(uint32_t field_id, ProtoVarInt v
|
||||
this->supports_target_humidity = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
case 26: {
|
||||
this->device_id = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -4262,6 +4489,7 @@ void ListEntitiesClimateResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_bool(23, this->supports_target_humidity);
|
||||
buffer.encode_float(24, this->visual_min_humidity);
|
||||
buffer.encode_float(25, this->visual_max_humidity);
|
||||
buffer.encode_uint32(26, this->device_id);
|
||||
}
|
||||
void ListEntitiesClimateResponse::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_string_field(total_size, 1, this->object_id, false);
|
||||
@@ -4313,6 +4541,7 @@ void ListEntitiesClimateResponse::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_bool_field(total_size, 2, this->supports_target_humidity, false);
|
||||
ProtoSize::add_fixed_field<4>(total_size, 2, this->visual_min_humidity != 0.0f, false);
|
||||
ProtoSize::add_fixed_field<4>(total_size, 2, this->visual_max_humidity != 0.0f, false);
|
||||
ProtoSize::add_uint32_field(total_size, 2, this->device_id, false);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void ListEntitiesClimateResponse::dump_to(std::string &out) const {
|
||||
@@ -4436,6 +4665,11 @@ void ListEntitiesClimateResponse::dump_to(std::string &out) const {
|
||||
sprintf(buffer, "%g", this->visual_max_humidity);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
out.append(" device_id: ");
|
||||
sprintf(buffer, "%" PRIu32, this->device_id);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
@@ -4901,6 +5135,10 @@ bool ListEntitiesNumberResponse::decode_varint(uint32_t field_id, ProtoVarInt va
|
||||
this->mode = value.as_enum<enums::NumberMode>();
|
||||
return true;
|
||||
}
|
||||
case 14: {
|
||||
this->device_id = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -4971,6 +5209,7 @@ void ListEntitiesNumberResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_string(11, this->unit_of_measurement);
|
||||
buffer.encode_enum<enums::NumberMode>(12, this->mode);
|
||||
buffer.encode_string(13, this->device_class);
|
||||
buffer.encode_uint32(14, this->device_id);
|
||||
}
|
||||
void ListEntitiesNumberResponse::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_string_field(total_size, 1, this->object_id, false);
|
||||
@@ -4986,6 +5225,7 @@ void ListEntitiesNumberResponse::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_string_field(total_size, 1, this->unit_of_measurement, false);
|
||||
ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->mode), false);
|
||||
ProtoSize::add_string_field(total_size, 1, this->device_class, false);
|
||||
ProtoSize::add_uint32_field(total_size, 1, this->device_id, false);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void ListEntitiesNumberResponse::dump_to(std::string &out) const {
|
||||
@@ -5046,6 +5286,11 @@ void ListEntitiesNumberResponse::dump_to(std::string &out) const {
|
||||
out.append(" device_class: ");
|
||||
out.append("'").append(this->device_class).append("'");
|
||||
out.append("\n");
|
||||
|
||||
out.append(" device_id: ");
|
||||
sprintf(buffer, "%" PRIu32, this->device_id);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
@@ -5151,6 +5396,10 @@ bool ListEntitiesSelectResponse::decode_varint(uint32_t field_id, ProtoVarInt va
|
||||
this->entity_category = value.as_enum<enums::EntityCategory>();
|
||||
return true;
|
||||
}
|
||||
case 9: {
|
||||
this->device_id = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -5202,6 +5451,7 @@ void ListEntitiesSelectResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
}
|
||||
buffer.encode_bool(7, this->disabled_by_default);
|
||||
buffer.encode_enum<enums::EntityCategory>(8, this->entity_category);
|
||||
buffer.encode_uint32(9, this->device_id);
|
||||
}
|
||||
void ListEntitiesSelectResponse::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_string_field(total_size, 1, this->object_id, false);
|
||||
@@ -5216,6 +5466,7 @@ void ListEntitiesSelectResponse::calculate_size(uint32_t &total_size) const {
|
||||
}
|
||||
ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false);
|
||||
ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false);
|
||||
ProtoSize::add_uint32_field(total_size, 1, this->device_id, false);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void ListEntitiesSelectResponse::dump_to(std::string &out) const {
|
||||
@@ -5255,6 +5506,11 @@ void ListEntitiesSelectResponse::dump_to(std::string &out) const {
|
||||
out.append(" entity_category: ");
|
||||
out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" device_id: ");
|
||||
sprintf(buffer, "%" PRIu32, this->device_id);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
@@ -5378,6 +5634,10 @@ bool ListEntitiesSirenResponse::decode_varint(uint32_t field_id, ProtoVarInt val
|
||||
this->entity_category = value.as_enum<enums::EntityCategory>();
|
||||
return true;
|
||||
}
|
||||
case 11: {
|
||||
this->device_id = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -5431,6 +5691,7 @@ void ListEntitiesSirenResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_bool(8, this->supports_duration);
|
||||
buffer.encode_bool(9, this->supports_volume);
|
||||
buffer.encode_enum<enums::EntityCategory>(10, this->entity_category);
|
||||
buffer.encode_uint32(11, this->device_id);
|
||||
}
|
||||
void ListEntitiesSirenResponse::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_string_field(total_size, 1, this->object_id, false);
|
||||
@@ -5447,6 +5708,7 @@ void ListEntitiesSirenResponse::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_bool_field(total_size, 1, this->supports_duration, false);
|
||||
ProtoSize::add_bool_field(total_size, 1, this->supports_volume, false);
|
||||
ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false);
|
||||
ProtoSize::add_uint32_field(total_size, 1, this->device_id, false);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void ListEntitiesSirenResponse::dump_to(std::string &out) const {
|
||||
@@ -5494,6 +5756,11 @@ void ListEntitiesSirenResponse::dump_to(std::string &out) const {
|
||||
out.append(" entity_category: ");
|
||||
out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" device_id: ");
|
||||
sprintf(buffer, "%" PRIu32, this->device_id);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
@@ -5683,6 +5950,10 @@ bool ListEntitiesLockResponse::decode_varint(uint32_t field_id, ProtoVarInt valu
|
||||
this->requires_code = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
case 12: {
|
||||
this->device_id = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -5735,6 +6006,7 @@ void ListEntitiesLockResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_bool(9, this->supports_open);
|
||||
buffer.encode_bool(10, this->requires_code);
|
||||
buffer.encode_string(11, this->code_format);
|
||||
buffer.encode_uint32(12, this->device_id);
|
||||
}
|
||||
void ListEntitiesLockResponse::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_string_field(total_size, 1, this->object_id, false);
|
||||
@@ -5748,6 +6020,7 @@ void ListEntitiesLockResponse::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_bool_field(total_size, 1, this->supports_open, false);
|
||||
ProtoSize::add_bool_field(total_size, 1, this->requires_code, false);
|
||||
ProtoSize::add_string_field(total_size, 1, this->code_format, false);
|
||||
ProtoSize::add_uint32_field(total_size, 1, this->device_id, false);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void ListEntitiesLockResponse::dump_to(std::string &out) const {
|
||||
@@ -5797,6 +6070,11 @@ void ListEntitiesLockResponse::dump_to(std::string &out) const {
|
||||
out.append(" code_format: ");
|
||||
out.append("'").append(this->code_format).append("'");
|
||||
out.append("\n");
|
||||
|
||||
out.append(" device_id: ");
|
||||
sprintf(buffer, "%" PRIu32, this->device_id);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
@@ -5922,6 +6200,10 @@ bool ListEntitiesButtonResponse::decode_varint(uint32_t field_id, ProtoVarInt va
|
||||
this->entity_category = value.as_enum<enums::EntityCategory>();
|
||||
return true;
|
||||
}
|
||||
case 9: {
|
||||
this->device_id = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -5971,6 +6253,7 @@ void ListEntitiesButtonResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_bool(6, this->disabled_by_default);
|
||||
buffer.encode_enum<enums::EntityCategory>(7, this->entity_category);
|
||||
buffer.encode_string(8, this->device_class);
|
||||
buffer.encode_uint32(9, this->device_id);
|
||||
}
|
||||
void ListEntitiesButtonResponse::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_string_field(total_size, 1, this->object_id, false);
|
||||
@@ -5981,6 +6264,7 @@ void ListEntitiesButtonResponse::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false);
|
||||
ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false);
|
||||
ProtoSize::add_string_field(total_size, 1, this->device_class, false);
|
||||
ProtoSize::add_uint32_field(total_size, 1, this->device_id, false);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void ListEntitiesButtonResponse::dump_to(std::string &out) const {
|
||||
@@ -6018,6 +6302,11 @@ void ListEntitiesButtonResponse::dump_to(std::string &out) const {
|
||||
out.append(" device_class: ");
|
||||
out.append("'").append(this->device_class).append("'");
|
||||
out.append("\n");
|
||||
|
||||
out.append(" device_id: ");
|
||||
sprintf(buffer, "%" PRIu32, this->device_id);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
@@ -6135,6 +6424,10 @@ bool ListEntitiesMediaPlayerResponse::decode_varint(uint32_t field_id, ProtoVarI
|
||||
this->supports_pause = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
case 10: {
|
||||
this->device_id = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -6187,6 +6480,7 @@ void ListEntitiesMediaPlayerResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
for (auto &it : this->supported_formats) {
|
||||
buffer.encode_message<MediaPlayerSupportedFormat>(9, it, true);
|
||||
}
|
||||
buffer.encode_uint32(10, this->device_id);
|
||||
}
|
||||
void ListEntitiesMediaPlayerResponse::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_string_field(total_size, 1, this->object_id, false);
|
||||
@@ -6198,6 +6492,7 @@ void ListEntitiesMediaPlayerResponse::calculate_size(uint32_t &total_size) const
|
||||
ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false);
|
||||
ProtoSize::add_bool_field(total_size, 1, this->supports_pause, false);
|
||||
ProtoSize::add_repeated_message(total_size, 1, this->supported_formats);
|
||||
ProtoSize::add_uint32_field(total_size, 1, this->device_id, false);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void ListEntitiesMediaPlayerResponse::dump_to(std::string &out) const {
|
||||
@@ -6241,6 +6536,11 @@ void ListEntitiesMediaPlayerResponse::dump_to(std::string &out) const {
|
||||
it.dump_to(out);
|
||||
out.append("\n");
|
||||
}
|
||||
|
||||
out.append(" device_id: ");
|
||||
sprintf(buffer, "%" PRIu32, this->device_id);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
@@ -8551,6 +8851,10 @@ bool ListEntitiesAlarmControlPanelResponse::decode_varint(uint32_t field_id, Pro
|
||||
this->requires_code_to_arm = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
case 11: {
|
||||
this->device_id = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -8598,6 +8902,7 @@ void ListEntitiesAlarmControlPanelResponse::encode(ProtoWriteBuffer buffer) cons
|
||||
buffer.encode_uint32(8, this->supported_features);
|
||||
buffer.encode_bool(9, this->requires_code);
|
||||
buffer.encode_bool(10, this->requires_code_to_arm);
|
||||
buffer.encode_uint32(11, this->device_id);
|
||||
}
|
||||
void ListEntitiesAlarmControlPanelResponse::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_string_field(total_size, 1, this->object_id, false);
|
||||
@@ -8610,6 +8915,7 @@ void ListEntitiesAlarmControlPanelResponse::calculate_size(uint32_t &total_size)
|
||||
ProtoSize::add_uint32_field(total_size, 1, this->supported_features, false);
|
||||
ProtoSize::add_bool_field(total_size, 1, this->requires_code, false);
|
||||
ProtoSize::add_bool_field(total_size, 1, this->requires_code_to_arm, false);
|
||||
ProtoSize::add_uint32_field(total_size, 1, this->device_id, false);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void ListEntitiesAlarmControlPanelResponse::dump_to(std::string &out) const {
|
||||
@@ -8656,6 +8962,11 @@ void ListEntitiesAlarmControlPanelResponse::dump_to(std::string &out) const {
|
||||
out.append(" requires_code_to_arm: ");
|
||||
out.append(YESNO(this->requires_code_to_arm));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" device_id: ");
|
||||
sprintf(buffer, "%" PRIu32, this->device_id);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
@@ -8783,6 +9094,10 @@ bool ListEntitiesTextResponse::decode_varint(uint32_t field_id, ProtoVarInt valu
|
||||
this->mode = value.as_enum<enums::TextMode>();
|
||||
return true;
|
||||
}
|
||||
case 12: {
|
||||
this->device_id = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -8835,6 +9150,7 @@ void ListEntitiesTextResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_uint32(9, this->max_length);
|
||||
buffer.encode_string(10, this->pattern);
|
||||
buffer.encode_enum<enums::TextMode>(11, this->mode);
|
||||
buffer.encode_uint32(12, this->device_id);
|
||||
}
|
||||
void ListEntitiesTextResponse::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_string_field(total_size, 1, this->object_id, false);
|
||||
@@ -8848,6 +9164,7 @@ void ListEntitiesTextResponse::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_uint32_field(total_size, 1, this->max_length, false);
|
||||
ProtoSize::add_string_field(total_size, 1, this->pattern, false);
|
||||
ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->mode), false);
|
||||
ProtoSize::add_uint32_field(total_size, 1, this->device_id, false);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void ListEntitiesTextResponse::dump_to(std::string &out) const {
|
||||
@@ -8899,6 +9216,11 @@ void ListEntitiesTextResponse::dump_to(std::string &out) const {
|
||||
out.append(" mode: ");
|
||||
out.append(proto_enum_to_string<enums::TextMode>(this->mode));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" device_id: ");
|
||||
sprintf(buffer, "%" PRIu32, this->device_id);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
@@ -9014,6 +9336,10 @@ bool ListEntitiesDateResponse::decode_varint(uint32_t field_id, ProtoVarInt valu
|
||||
this->entity_category = value.as_enum<enums::EntityCategory>();
|
||||
return true;
|
||||
}
|
||||
case 8: {
|
||||
this->device_id = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -9058,6 +9384,7 @@ void ListEntitiesDateResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_string(5, this->icon);
|
||||
buffer.encode_bool(6, this->disabled_by_default);
|
||||
buffer.encode_enum<enums::EntityCategory>(7, this->entity_category);
|
||||
buffer.encode_uint32(8, this->device_id);
|
||||
}
|
||||
void ListEntitiesDateResponse::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_string_field(total_size, 1, this->object_id, false);
|
||||
@@ -9067,6 +9394,7 @@ void ListEntitiesDateResponse::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_string_field(total_size, 1, this->icon, false);
|
||||
ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false);
|
||||
ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false);
|
||||
ProtoSize::add_uint32_field(total_size, 1, this->device_id, false);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void ListEntitiesDateResponse::dump_to(std::string &out) const {
|
||||
@@ -9100,6 +9428,11 @@ void ListEntitiesDateResponse::dump_to(std::string &out) const {
|
||||
out.append(" entity_category: ");
|
||||
out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" device_id: ");
|
||||
sprintf(buffer, "%" PRIu32, this->device_id);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
@@ -9255,6 +9588,10 @@ bool ListEntitiesTimeResponse::decode_varint(uint32_t field_id, ProtoVarInt valu
|
||||
this->entity_category = value.as_enum<enums::EntityCategory>();
|
||||
return true;
|
||||
}
|
||||
case 8: {
|
||||
this->device_id = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -9299,6 +9636,7 @@ void ListEntitiesTimeResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_string(5, this->icon);
|
||||
buffer.encode_bool(6, this->disabled_by_default);
|
||||
buffer.encode_enum<enums::EntityCategory>(7, this->entity_category);
|
||||
buffer.encode_uint32(8, this->device_id);
|
||||
}
|
||||
void ListEntitiesTimeResponse::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_string_field(total_size, 1, this->object_id, false);
|
||||
@@ -9308,6 +9646,7 @@ void ListEntitiesTimeResponse::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_string_field(total_size, 1, this->icon, false);
|
||||
ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false);
|
||||
ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false);
|
||||
ProtoSize::add_uint32_field(total_size, 1, this->device_id, false);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void ListEntitiesTimeResponse::dump_to(std::string &out) const {
|
||||
@@ -9341,6 +9680,11 @@ void ListEntitiesTimeResponse::dump_to(std::string &out) const {
|
||||
out.append(" entity_category: ");
|
||||
out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" device_id: ");
|
||||
sprintf(buffer, "%" PRIu32, this->device_id);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
@@ -9496,6 +9840,10 @@ bool ListEntitiesEventResponse::decode_varint(uint32_t field_id, ProtoVarInt val
|
||||
this->entity_category = value.as_enum<enums::EntityCategory>();
|
||||
return true;
|
||||
}
|
||||
case 10: {
|
||||
this->device_id = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -9552,6 +9900,7 @@ void ListEntitiesEventResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
for (auto &it : this->event_types) {
|
||||
buffer.encode_string(9, it, true);
|
||||
}
|
||||
buffer.encode_uint32(10, this->device_id);
|
||||
}
|
||||
void ListEntitiesEventResponse::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_string_field(total_size, 1, this->object_id, false);
|
||||
@@ -9567,6 +9916,7 @@ void ListEntitiesEventResponse::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_string_field(total_size, 1, it, true);
|
||||
}
|
||||
}
|
||||
ProtoSize::add_uint32_field(total_size, 1, this->device_id, false);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void ListEntitiesEventResponse::dump_to(std::string &out) const {
|
||||
@@ -9610,6 +9960,11 @@ void ListEntitiesEventResponse::dump_to(std::string &out) const {
|
||||
out.append("'").append(it).append("'");
|
||||
out.append("\n");
|
||||
}
|
||||
|
||||
out.append(" device_id: ");
|
||||
sprintf(buffer, "%" PRIu32, this->device_id);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
@@ -9678,6 +10033,10 @@ bool ListEntitiesValveResponse::decode_varint(uint32_t field_id, ProtoVarInt val
|
||||
this->supports_stop = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
case 12: {
|
||||
this->device_id = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -9730,6 +10089,7 @@ void ListEntitiesValveResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_bool(9, this->assumed_state);
|
||||
buffer.encode_bool(10, this->supports_position);
|
||||
buffer.encode_bool(11, this->supports_stop);
|
||||
buffer.encode_uint32(12, this->device_id);
|
||||
}
|
||||
void ListEntitiesValveResponse::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_string_field(total_size, 1, this->object_id, false);
|
||||
@@ -9743,6 +10103,7 @@ void ListEntitiesValveResponse::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_bool_field(total_size, 1, this->assumed_state, false);
|
||||
ProtoSize::add_bool_field(total_size, 1, this->supports_position, false);
|
||||
ProtoSize::add_bool_field(total_size, 1, this->supports_stop, false);
|
||||
ProtoSize::add_uint32_field(total_size, 1, this->device_id, false);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void ListEntitiesValveResponse::dump_to(std::string &out) const {
|
||||
@@ -9792,6 +10153,11 @@ void ListEntitiesValveResponse::dump_to(std::string &out) const {
|
||||
out.append(" supports_stop: ");
|
||||
out.append(YESNO(this->supports_stop));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" device_id: ");
|
||||
sprintf(buffer, "%" PRIu32, this->device_id);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
@@ -9923,6 +10289,10 @@ bool ListEntitiesDateTimeResponse::decode_varint(uint32_t field_id, ProtoVarInt
|
||||
this->entity_category = value.as_enum<enums::EntityCategory>();
|
||||
return true;
|
||||
}
|
||||
case 8: {
|
||||
this->device_id = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -9967,6 +10337,7 @@ void ListEntitiesDateTimeResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_string(5, this->icon);
|
||||
buffer.encode_bool(6, this->disabled_by_default);
|
||||
buffer.encode_enum<enums::EntityCategory>(7, this->entity_category);
|
||||
buffer.encode_uint32(8, this->device_id);
|
||||
}
|
||||
void ListEntitiesDateTimeResponse::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_string_field(total_size, 1, this->object_id, false);
|
||||
@@ -9976,6 +10347,7 @@ void ListEntitiesDateTimeResponse::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_string_field(total_size, 1, this->icon, false);
|
||||
ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false);
|
||||
ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false);
|
||||
ProtoSize::add_uint32_field(total_size, 1, this->device_id, false);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void ListEntitiesDateTimeResponse::dump_to(std::string &out) const {
|
||||
@@ -10009,6 +10381,11 @@ void ListEntitiesDateTimeResponse::dump_to(std::string &out) const {
|
||||
out.append(" entity_category: ");
|
||||
out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" device_id: ");
|
||||
sprintf(buffer, "%" PRIu32, this->device_id);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
@@ -10114,6 +10491,10 @@ bool ListEntitiesUpdateResponse::decode_varint(uint32_t field_id, ProtoVarInt va
|
||||
this->entity_category = value.as_enum<enums::EntityCategory>();
|
||||
return true;
|
||||
}
|
||||
case 9: {
|
||||
this->device_id = value.as_uint32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -10163,6 +10544,7 @@ void ListEntitiesUpdateResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_bool(6, this->disabled_by_default);
|
||||
buffer.encode_enum<enums::EntityCategory>(7, this->entity_category);
|
||||
buffer.encode_string(8, this->device_class);
|
||||
buffer.encode_uint32(9, this->device_id);
|
||||
}
|
||||
void ListEntitiesUpdateResponse::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_string_field(total_size, 1, this->object_id, false);
|
||||
@@ -10173,6 +10555,7 @@ void ListEntitiesUpdateResponse::calculate_size(uint32_t &total_size) const {
|
||||
ProtoSize::add_bool_field(total_size, 1, this->disabled_by_default, false);
|
||||
ProtoSize::add_enum_field(total_size, 1, static_cast<uint32_t>(this->entity_category), false);
|
||||
ProtoSize::add_string_field(total_size, 1, this->device_class, false);
|
||||
ProtoSize::add_uint32_field(total_size, 1, this->device_id, false);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void ListEntitiesUpdateResponse::dump_to(std::string &out) const {
|
||||
@@ -10210,6 +10593,11 @@ void ListEntitiesUpdateResponse::dump_to(std::string &out) const {
|
||||
out.append(" device_class: ");
|
||||
out.append("'").append(this->device_class).append("'");
|
||||
out.append("\n");
|
||||
|
||||
out.append(" device_id: ");
|
||||
sprintf(buffer, "%" PRIu32, this->device_id);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -19,7 +19,7 @@ class APIServerConnectionBase : public ProtoService {
|
||||
|
||||
template<typename T> bool send_message(const T &msg) {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
this->log_send_message_(T::message_name(), msg.dump());
|
||||
this->log_send_message_(msg.message_name(), msg.dump());
|
||||
#endif
|
||||
return this->send_message_(msg, T::MESSAGE_TYPE);
|
||||
}
|
||||
|
||||
@@ -47,6 +47,11 @@ void APIServer::setup() {
|
||||
}
|
||||
#endif
|
||||
|
||||
// Schedule reboot if no clients connect within timeout
|
||||
if (this->reboot_timeout_ != 0) {
|
||||
this->schedule_reboot_timeout_();
|
||||
}
|
||||
|
||||
this->socket_ = socket::socket_ip_loop_monitored(SOCK_STREAM, 0); // monitored for incoming connections
|
||||
if (this->socket_ == nullptr) {
|
||||
ESP_LOGW(TAG, "Could not create socket");
|
||||
@@ -106,8 +111,6 @@ void APIServer::setup() {
|
||||
}
|
||||
#endif
|
||||
|
||||
this->last_connected_ = App.get_loop_component_start_time();
|
||||
|
||||
#ifdef USE_ESP32_CAMERA
|
||||
if (esp32_camera::global_esp32_camera != nullptr && !esp32_camera::global_esp32_camera->is_internal()) {
|
||||
esp32_camera::global_esp32_camera->add_image_callback(
|
||||
@@ -121,6 +124,16 @@ void APIServer::setup() {
|
||||
#endif
|
||||
}
|
||||
|
||||
void APIServer::schedule_reboot_timeout_() {
|
||||
this->status_set_warning();
|
||||
this->set_timeout("api_reboot", this->reboot_timeout_, []() {
|
||||
if (!global_api_server->is_connected()) {
|
||||
ESP_LOGE(TAG, "No clients; rebooting");
|
||||
App.reboot();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void APIServer::loop() {
|
||||
// Accept new clients only if the socket exists and has incoming connections
|
||||
if (this->socket_ && this->socket_->ready()) {
|
||||
@@ -130,51 +143,61 @@ void APIServer::loop() {
|
||||
auto sock = this->socket_->accept_loop_monitored((struct sockaddr *) &source_addr, &addr_len);
|
||||
if (!sock)
|
||||
break;
|
||||
ESP_LOGD(TAG, "Accepted %s", sock->getpeername().c_str());
|
||||
ESP_LOGD(TAG, "Accept %s", sock->getpeername().c_str());
|
||||
|
||||
auto *conn = new APIConnection(std::move(sock), this);
|
||||
this->clients_.emplace_back(conn);
|
||||
conn->start();
|
||||
|
||||
// Clear warning status and cancel reboot when first client connects
|
||||
if (this->clients_.size() == 1 && this->reboot_timeout_ != 0) {
|
||||
this->status_clear_warning();
|
||||
this->cancel_timeout("api_reboot");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (this->clients_.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Process clients and remove disconnected ones in a single pass
|
||||
if (!this->clients_.empty()) {
|
||||
size_t client_index = 0;
|
||||
while (client_index < this->clients_.size()) {
|
||||
auto &client = this->clients_[client_index];
|
||||
|
||||
if (client->remove_) {
|
||||
// Handle disconnection
|
||||
this->client_disconnected_trigger_->trigger(client->client_info_, client->client_peername_);
|
||||
ESP_LOGV(TAG, "Removing connection to %s", client->client_info_.c_str());
|
||||
|
||||
// Swap with the last element and pop (avoids expensive vector shifts)
|
||||
if (client_index < this->clients_.size() - 1) {
|
||||
std::swap(this->clients_[client_index], this->clients_.back());
|
||||
}
|
||||
this->clients_.pop_back();
|
||||
// Don't increment client_index since we need to process the swapped element
|
||||
} else {
|
||||
// Process active client
|
||||
client->loop();
|
||||
client_index++; // Move to next client
|
||||
}
|
||||
// Check network connectivity once for all clients
|
||||
if (!network::is_connected()) {
|
||||
// Network is down - disconnect all clients
|
||||
for (auto &client : this->clients_) {
|
||||
client->on_fatal_error();
|
||||
ESP_LOGW(TAG, "%s: Network down; disconnect", client->get_client_combined_info().c_str());
|
||||
}
|
||||
// Continue to process and clean up the clients below
|
||||
}
|
||||
|
||||
if (this->reboot_timeout_ != 0) {
|
||||
const uint32_t now = App.get_loop_component_start_time();
|
||||
if (!this->is_connected()) {
|
||||
if (now - this->last_connected_ > this->reboot_timeout_) {
|
||||
ESP_LOGE(TAG, "No client connected; rebooting");
|
||||
App.reboot();
|
||||
}
|
||||
this->status_set_warning();
|
||||
} else {
|
||||
this->last_connected_ = now;
|
||||
this->status_clear_warning();
|
||||
size_t client_index = 0;
|
||||
while (client_index < this->clients_.size()) {
|
||||
auto &client = this->clients_[client_index];
|
||||
|
||||
if (!client->remove_) {
|
||||
// Common case: process active client
|
||||
client->loop();
|
||||
client_index++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Rare case: handle disconnection
|
||||
this->client_disconnected_trigger_->trigger(client->client_info_, client->client_peername_);
|
||||
ESP_LOGV(TAG, "Remove connection %s", client->client_info_.c_str());
|
||||
|
||||
// Swap with the last element and pop (avoids expensive vector shifts)
|
||||
if (client_index < this->clients_.size() - 1) {
|
||||
std::swap(this->clients_[client_index], this->clients_.back());
|
||||
}
|
||||
this->clients_.pop_back();
|
||||
|
||||
// Schedule reboot when last client disconnects
|
||||
if (this->clients_.empty() && this->reboot_timeout_ != 0) {
|
||||
this->schedule_reboot_timeout_();
|
||||
}
|
||||
// Don't increment client_index since we need to process the swapped element
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -142,6 +142,7 @@ class APIServer : public Component, public Controller {
|
||||
}
|
||||
|
||||
protected:
|
||||
void schedule_reboot_timeout_();
|
||||
// Pointers and pointer-like types first (4 bytes each)
|
||||
std::unique_ptr<socket::Socket> socket_ = nullptr;
|
||||
Trigger<std::string, std::string> *client_connected_trigger_ = new Trigger<std::string, std::string>();
|
||||
@@ -150,7 +151,6 @@ class APIServer : public Component, public Controller {
|
||||
// 4-byte aligned types
|
||||
uint32_t reboot_timeout_{300000};
|
||||
uint32_t batch_delay_{100};
|
||||
uint32_t last_connected_{0};
|
||||
|
||||
// Vectors and strings (12 bytes each on 32-bit)
|
||||
std::vector<std::unique_ptr<APIConnection>> clients_;
|
||||
|
||||
@@ -335,6 +335,7 @@ class ProtoMessage {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
std::string dump() const;
|
||||
virtual void dump_to(std::string &out) const = 0;
|
||||
virtual const char *message_name() const { return "unknown"; }
|
||||
#endif
|
||||
|
||||
protected:
|
||||
|
||||
@@ -60,8 +60,8 @@ from esphome.const import (
|
||||
DEVICE_CLASS_WINDOW,
|
||||
)
|
||||
from esphome.core import CORE, coroutine_with_priority
|
||||
from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity
|
||||
from esphome.cpp_generator import MockObjClass
|
||||
from esphome.cpp_helpers import setup_entity
|
||||
from esphome.util import Registry
|
||||
|
||||
CODEOWNERS = ["@esphome/core"]
|
||||
@@ -148,6 +148,7 @@ BinarySensorCondition = binary_sensor_ns.class_("BinarySensorCondition", Conditi
|
||||
|
||||
# Filters
|
||||
Filter = binary_sensor_ns.class_("Filter")
|
||||
TimeoutFilter = binary_sensor_ns.class_("TimeoutFilter", Filter, cg.Component)
|
||||
DelayedOnOffFilter = binary_sensor_ns.class_("DelayedOnOffFilter", Filter, cg.Component)
|
||||
DelayedOnFilter = binary_sensor_ns.class_("DelayedOnFilter", Filter, cg.Component)
|
||||
DelayedOffFilter = binary_sensor_ns.class_("DelayedOffFilter", Filter, cg.Component)
|
||||
@@ -171,6 +172,19 @@ async def invert_filter_to_code(config, filter_id):
|
||||
return cg.new_Pvariable(filter_id)
|
||||
|
||||
|
||||
@register_filter(
|
||||
"timeout",
|
||||
TimeoutFilter,
|
||||
cv.templatable(cv.positive_time_period_milliseconds),
|
||||
)
|
||||
async def timeout_filter_to_code(config, filter_id):
|
||||
var = cg.new_Pvariable(filter_id)
|
||||
await cg.register_component(var, {})
|
||||
template_ = await cg.templatable(config, [], cg.uint32)
|
||||
cg.add(var.set_timeout_value(template_))
|
||||
return var
|
||||
|
||||
|
||||
@register_filter(
|
||||
"delayed_on_off",
|
||||
DelayedOnOffFilter,
|
||||
@@ -491,6 +505,9 @@ _BINARY_SENSOR_SCHEMA = (
|
||||
)
|
||||
|
||||
|
||||
_BINARY_SENSOR_SCHEMA.add_extra(entity_duplicate_validator("binary_sensor"))
|
||||
|
||||
|
||||
def binary_sensor_schema(
|
||||
class_: MockObjClass = cv.UNDEFINED,
|
||||
*,
|
||||
@@ -521,7 +538,7 @@ BINARY_SENSOR_SCHEMA.add_extra(cv.deprecated_schema_constant("binary_sensor"))
|
||||
|
||||
|
||||
async def setup_binary_sensor_core_(var, config):
|
||||
await setup_entity(var, config)
|
||||
await setup_entity(var, config, "binary_sensor")
|
||||
|
||||
if (device_class := config.get(CONF_DEVICE_CLASS)) is not None:
|
||||
cg.add(var.set_device_class(device_class))
|
||||
|
||||
@@ -25,6 +25,12 @@ void Filter::input(bool value) {
|
||||
}
|
||||
}
|
||||
|
||||
void TimeoutFilter::input(bool value) {
|
||||
this->set_timeout("timeout", this->timeout_delay_.value(), [this]() { this->parent_->invalidate_state(); });
|
||||
// we do not de-dup here otherwise changes from invalid to valid state will not be output
|
||||
this->output(value);
|
||||
}
|
||||
|
||||
optional<bool> DelayedOnOffFilter::new_value(bool value) {
|
||||
if (value) {
|
||||
this->set_timeout("ON_OFF", this->on_delay_.value(), [this]() { this->output(true); });
|
||||
|
||||
@@ -16,7 +16,7 @@ class Filter {
|
||||
public:
|
||||
virtual optional<bool> new_value(bool value) = 0;
|
||||
|
||||
void input(bool value);
|
||||
virtual void input(bool value);
|
||||
|
||||
void output(bool value);
|
||||
|
||||
@@ -28,6 +28,16 @@ class Filter {
|
||||
Deduplicator<bool> dedup_;
|
||||
};
|
||||
|
||||
class TimeoutFilter : public Filter, public Component {
|
||||
public:
|
||||
optional<bool> new_value(bool value) override { return value; }
|
||||
void input(bool value) override;
|
||||
template<typename T> void set_timeout_value(T timeout) { this->timeout_delay_ = timeout; }
|
||||
|
||||
protected:
|
||||
TemplatableValue<uint32_t> timeout_delay_{};
|
||||
};
|
||||
|
||||
class DelayedOnOffFilter : public Filter, public Component {
|
||||
public:
|
||||
optional<bool> new_value(bool value) override;
|
||||
|
||||
@@ -18,8 +18,8 @@ from esphome.const import (
|
||||
DEVICE_CLASS_UPDATE,
|
||||
)
|
||||
from esphome.core import CORE, coroutine_with_priority
|
||||
from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity
|
||||
from esphome.cpp_generator import MockObjClass
|
||||
from esphome.cpp_helpers import setup_entity
|
||||
|
||||
CODEOWNERS = ["@esphome/core"]
|
||||
IS_PLATFORM_COMPONENT = True
|
||||
@@ -61,6 +61,9 @@ _BUTTON_SCHEMA = (
|
||||
)
|
||||
|
||||
|
||||
_BUTTON_SCHEMA.add_extra(entity_duplicate_validator("button"))
|
||||
|
||||
|
||||
def button_schema(
|
||||
class_: MockObjClass,
|
||||
*,
|
||||
@@ -87,7 +90,7 @@ BUTTON_SCHEMA.add_extra(cv.deprecated_schema_constant("button"))
|
||||
|
||||
|
||||
async def setup_button_core_(var, config):
|
||||
await setup_entity(var, config)
|
||||
await setup_entity(var, config, "button")
|
||||
|
||||
for conf in config.get(CONF_ON_PRESS, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
|
||||
@@ -48,8 +48,8 @@ from esphome.const import (
|
||||
CONF_WEB_SERVER,
|
||||
)
|
||||
from esphome.core import CORE, coroutine_with_priority
|
||||
from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity
|
||||
from esphome.cpp_generator import MockObjClass
|
||||
from esphome.cpp_helpers import setup_entity
|
||||
|
||||
IS_PLATFORM_COMPONENT = True
|
||||
|
||||
@@ -247,6 +247,9 @@ _CLIMATE_SCHEMA = (
|
||||
)
|
||||
|
||||
|
||||
_CLIMATE_SCHEMA.add_extra(entity_duplicate_validator("climate"))
|
||||
|
||||
|
||||
def climate_schema(
|
||||
class_: MockObjClass,
|
||||
*,
|
||||
@@ -273,7 +276,7 @@ CLIMATE_SCHEMA.add_extra(cv.deprecated_schema_constant("climate"))
|
||||
|
||||
|
||||
async def setup_climate_core_(var, config):
|
||||
await setup_entity(var, config)
|
||||
await setup_entity(var, config, "climate")
|
||||
|
||||
visual = config[CONF_VISUAL]
|
||||
if (min_temp := visual.get(CONF_MIN_TEMPERATURE)) is not None:
|
||||
|
||||
@@ -33,8 +33,8 @@ from esphome.const import (
|
||||
DEVICE_CLASS_WINDOW,
|
||||
)
|
||||
from esphome.core import CORE, coroutine_with_priority
|
||||
from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity
|
||||
from esphome.cpp_generator import MockObjClass
|
||||
from esphome.cpp_helpers import setup_entity
|
||||
|
||||
IS_PLATFORM_COMPONENT = True
|
||||
|
||||
@@ -126,6 +126,9 @@ _COVER_SCHEMA = (
|
||||
)
|
||||
|
||||
|
||||
_COVER_SCHEMA.add_extra(entity_duplicate_validator("cover"))
|
||||
|
||||
|
||||
def cover_schema(
|
||||
class_: MockObjClass,
|
||||
*,
|
||||
@@ -154,7 +157,7 @@ COVER_SCHEMA.add_extra(cv.deprecated_schema_constant("cover"))
|
||||
|
||||
|
||||
async def setup_cover_core_(var, config):
|
||||
await setup_entity(var, config)
|
||||
await setup_entity(var, config, "cover")
|
||||
|
||||
if (device_class := config.get(CONF_DEVICE_CLASS)) is not None:
|
||||
cg.add(var.set_device_class(device_class))
|
||||
|
||||
@@ -22,8 +22,8 @@ from esphome.const import (
|
||||
CONF_YEAR,
|
||||
)
|
||||
from esphome.core import CORE, coroutine_with_priority
|
||||
from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity
|
||||
from esphome.cpp_generator import MockObjClass
|
||||
from esphome.cpp_helpers import setup_entity
|
||||
|
||||
CODEOWNERS = ["@rfdarter", "@jesserockz"]
|
||||
|
||||
@@ -84,6 +84,8 @@ _DATETIME_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(
|
||||
.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA)
|
||||
).add_extra(_validate_time_present)
|
||||
|
||||
_DATETIME_SCHEMA.add_extra(entity_duplicate_validator("datetime"))
|
||||
|
||||
|
||||
def date_schema(class_: MockObjClass) -> cv.Schema:
|
||||
schema = cv.Schema(
|
||||
@@ -133,7 +135,7 @@ def datetime_schema(class_: MockObjClass) -> cv.Schema:
|
||||
|
||||
|
||||
async def setup_datetime_core_(var, config):
|
||||
await setup_entity(var, config)
|
||||
await setup_entity(var, config, "datetime")
|
||||
|
||||
if (mqtt_id := config.get(CONF_MQTT_ID)) is not None:
|
||||
mqtt_ = cg.new_Pvariable(mqtt_id, var)
|
||||
|
||||
@@ -455,7 +455,7 @@ CONFIG_SCHEMA = cv.Schema(
|
||||
CONF_NAME: "Demo Plain Sensor",
|
||||
},
|
||||
{
|
||||
CONF_NAME: "Demo Temperature Sensor",
|
||||
CONF_NAME: "Demo Temperature Sensor 1",
|
||||
CONF_UNIT_OF_MEASUREMENT: UNIT_CELSIUS,
|
||||
CONF_ICON: ICON_THERMOMETER,
|
||||
CONF_ACCURACY_DECIMALS: 1,
|
||||
@@ -463,7 +463,7 @@ CONFIG_SCHEMA = cv.Schema(
|
||||
CONF_STATE_CLASS: STATE_CLASS_MEASUREMENT,
|
||||
},
|
||||
{
|
||||
CONF_NAME: "Demo Temperature Sensor",
|
||||
CONF_NAME: "Demo Temperature Sensor 2",
|
||||
CONF_UNIT_OF_MEASUREMENT: UNIT_CELSIUS,
|
||||
CONF_ICON: ICON_THERMOMETER,
|
||||
CONF_ACCURACY_DECIMALS: 1,
|
||||
|
||||
@@ -4,7 +4,7 @@ import logging
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
from esphome import git
|
||||
from esphome import yaml_util
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
@@ -23,7 +23,6 @@ from esphome.const import (
|
||||
CONF_REFRESH,
|
||||
CONF_SOURCE,
|
||||
CONF_TYPE,
|
||||
CONF_URL,
|
||||
CONF_VARIANT,
|
||||
CONF_VERSION,
|
||||
KEY_CORE,
|
||||
@@ -32,14 +31,13 @@ from esphome.const import (
|
||||
KEY_TARGET_FRAMEWORK,
|
||||
KEY_TARGET_PLATFORM,
|
||||
PLATFORM_ESP32,
|
||||
TYPE_GIT,
|
||||
TYPE_LOCAL,
|
||||
__version__,
|
||||
)
|
||||
from esphome.core import CORE, HexInt, TimePeriod
|
||||
from esphome.cpp_generator import RawExpression
|
||||
import esphome.final_validate as fv
|
||||
from esphome.helpers import copy_file_if_changed, mkdir_p, write_file_if_changed
|
||||
from esphome.types import ConfigType
|
||||
|
||||
from .boards import BOARDS
|
||||
from .const import ( # noqa
|
||||
@@ -49,10 +47,8 @@ from .const import ( # noqa
|
||||
KEY_EXTRA_BUILD_FILES,
|
||||
KEY_PATH,
|
||||
KEY_REF,
|
||||
KEY_REFRESH,
|
||||
KEY_REPO,
|
||||
KEY_SDKCONFIG_OPTIONS,
|
||||
KEY_SUBMODULES,
|
||||
KEY_VARIANT,
|
||||
VARIANT_ESP32,
|
||||
VARIANT_ESP32C2,
|
||||
@@ -235,7 +231,7 @@ def add_idf_sdkconfig_option(name: str, value: SdkconfigValueType):
|
||||
def add_idf_component(
|
||||
*,
|
||||
name: str,
|
||||
repo: str,
|
||||
repo: str = None,
|
||||
ref: str = None,
|
||||
path: str = None,
|
||||
refresh: TimePeriod = None,
|
||||
@@ -245,30 +241,27 @@ def add_idf_component(
|
||||
"""Add an esp-idf component to the project."""
|
||||
if not CORE.using_esp_idf:
|
||||
raise ValueError("Not an esp-idf project")
|
||||
if components is None:
|
||||
components = []
|
||||
if name not in CORE.data[KEY_ESP32][KEY_COMPONENTS]:
|
||||
if not repo and not ref and not path:
|
||||
raise ValueError("Requires at least one of repo, ref or path")
|
||||
if refresh or submodules or components:
|
||||
_LOGGER.warning(
|
||||
"The refresh, components and submodules parameters in add_idf_component() are "
|
||||
"deprecated and will be removed in ESPHome 2026.1. If you are seeing this, report "
|
||||
"an issue to the external_component author and ask them to update it."
|
||||
)
|
||||
if components:
|
||||
for comp in components:
|
||||
CORE.data[KEY_ESP32][KEY_COMPONENTS][comp] = {
|
||||
KEY_REPO: repo,
|
||||
KEY_REF: ref,
|
||||
KEY_PATH: f"{path}/{comp}" if path else comp,
|
||||
}
|
||||
else:
|
||||
CORE.data[KEY_ESP32][KEY_COMPONENTS][name] = {
|
||||
KEY_REPO: repo,
|
||||
KEY_REF: ref,
|
||||
KEY_PATH: path,
|
||||
KEY_REFRESH: refresh,
|
||||
KEY_COMPONENTS: components,
|
||||
KEY_SUBMODULES: submodules,
|
||||
}
|
||||
else:
|
||||
component_config = CORE.data[KEY_ESP32][KEY_COMPONENTS][name]
|
||||
if components is not None:
|
||||
component_config[KEY_COMPONENTS] = list(
|
||||
set(component_config[KEY_COMPONENTS] + components)
|
||||
)
|
||||
if submodules is not None:
|
||||
if component_config[KEY_SUBMODULES] is None:
|
||||
component_config[KEY_SUBMODULES] = submodules
|
||||
else:
|
||||
component_config[KEY_SUBMODULES] = list(
|
||||
set(component_config[KEY_SUBMODULES] + submodules)
|
||||
)
|
||||
|
||||
|
||||
def add_extra_script(stage: str, filename: str, path: str):
|
||||
@@ -575,6 +568,17 @@ CONF_ENABLE_LWIP_DHCP_SERVER = "enable_lwip_dhcp_server"
|
||||
CONF_ENABLE_LWIP_MDNS_QUERIES = "enable_lwip_mdns_queries"
|
||||
CONF_ENABLE_LWIP_BRIDGE_INTERFACE = "enable_lwip_bridge_interface"
|
||||
|
||||
|
||||
def _validate_idf_component(config: ConfigType) -> ConfigType:
|
||||
"""Validate IDF component config and warn about deprecated options."""
|
||||
if CONF_REFRESH in config:
|
||||
_LOGGER.warning(
|
||||
"The 'refresh' option for IDF components is deprecated and has no effect. "
|
||||
"It will be removed in ESPHome 2026.1. Please remove it from your configuration."
|
||||
)
|
||||
return config
|
||||
|
||||
|
||||
ESP_IDF_FRAMEWORK_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
@@ -614,15 +618,19 @@ ESP_IDF_FRAMEWORK_SCHEMA = cv.All(
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_COMPONENTS, default=[]): cv.ensure_list(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_NAME): cv.string_strict,
|
||||
cv.Required(CONF_SOURCE): cv.SOURCE_SCHEMA,
|
||||
cv.Optional(CONF_PATH): cv.string,
|
||||
cv.Optional(CONF_REFRESH, default="1d"): cv.All(
|
||||
cv.string, cv.source_refresh
|
||||
),
|
||||
}
|
||||
cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_NAME): cv.string_strict,
|
||||
cv.Optional(CONF_SOURCE): cv.git_ref,
|
||||
cv.Optional(CONF_REF): cv.string,
|
||||
cv.Optional(CONF_PATH): cv.string,
|
||||
cv.Optional(CONF_REFRESH): cv.All(
|
||||
cv.string, cv.source_refresh
|
||||
),
|
||||
}
|
||||
),
|
||||
_validate_idf_component,
|
||||
)
|
||||
),
|
||||
}
|
||||
@@ -814,18 +822,12 @@ async def to_code(config):
|
||||
add_idf_sdkconfig_option(name, RawSdkconfigValue(value))
|
||||
|
||||
for component in conf[CONF_COMPONENTS]:
|
||||
source = component[CONF_SOURCE]
|
||||
if source[CONF_TYPE] == TYPE_GIT:
|
||||
add_idf_component(
|
||||
name=component[CONF_NAME],
|
||||
repo=source[CONF_URL],
|
||||
ref=source.get(CONF_REF),
|
||||
path=component.get(CONF_PATH),
|
||||
refresh=component[CONF_REFRESH],
|
||||
)
|
||||
elif source[CONF_TYPE] == TYPE_LOCAL:
|
||||
_LOGGER.warning("Local components are not implemented yet.")
|
||||
|
||||
add_idf_component(
|
||||
name=component[CONF_NAME],
|
||||
repo=component.get(CONF_SOURCE),
|
||||
ref=component.get(CONF_REF),
|
||||
path=component.get(CONF_PATH),
|
||||
)
|
||||
elif conf[CONF_TYPE] == FRAMEWORK_ARDUINO:
|
||||
cg.add_platformio_option("framework", "arduino")
|
||||
cg.add_build_flag("-DUSE_ARDUINO")
|
||||
@@ -924,6 +926,26 @@ def _write_sdkconfig():
|
||||
write_file_if_changed(sdk_path, contents)
|
||||
|
||||
|
||||
def _write_idf_component_yml():
|
||||
yml_path = Path(CORE.relative_build_path("src/idf_component.yml"))
|
||||
if CORE.data[KEY_ESP32][KEY_COMPONENTS]:
|
||||
components: dict = CORE.data[KEY_ESP32][KEY_COMPONENTS]
|
||||
dependencies = {}
|
||||
for name, component in components.items():
|
||||
dependency = {}
|
||||
if component[KEY_REF]:
|
||||
dependency["version"] = component[KEY_REF]
|
||||
if component[KEY_REPO]:
|
||||
dependency["git"] = component[KEY_REPO]
|
||||
if component[KEY_PATH]:
|
||||
dependency["path"] = component[KEY_PATH]
|
||||
dependencies[name] = dependency
|
||||
contents = yaml_util.dump({"dependencies": dependencies})
|
||||
else:
|
||||
contents = ""
|
||||
write_file_if_changed(yml_path, contents)
|
||||
|
||||
|
||||
# Called by writer.py
|
||||
def copy_files():
|
||||
if CORE.using_arduino:
|
||||
@@ -936,6 +958,7 @@ def copy_files():
|
||||
)
|
||||
if CORE.using_esp_idf:
|
||||
_write_sdkconfig()
|
||||
_write_idf_component_yml()
|
||||
if "partitions.csv" not in CORE.data[KEY_ESP32][KEY_EXTRA_BUILD_FILES]:
|
||||
write_file_if_changed(
|
||||
CORE.relative_build_path("partitions.csv"),
|
||||
@@ -952,55 +975,6 @@ def copy_files():
|
||||
__version__,
|
||||
)
|
||||
|
||||
import shutil
|
||||
|
||||
shutil.rmtree(CORE.relative_build_path("components"), ignore_errors=True)
|
||||
|
||||
if CORE.data[KEY_ESP32][KEY_COMPONENTS]:
|
||||
components: dict = CORE.data[KEY_ESP32][KEY_COMPONENTS]
|
||||
|
||||
for name, component in components.items():
|
||||
repo_dir, _ = git.clone_or_update(
|
||||
url=component[KEY_REPO],
|
||||
ref=component[KEY_REF],
|
||||
refresh=component[KEY_REFRESH],
|
||||
domain="idf_components",
|
||||
submodules=component[KEY_SUBMODULES],
|
||||
)
|
||||
mkdir_p(CORE.relative_build_path("components"))
|
||||
component_dir = repo_dir
|
||||
if component[KEY_PATH] is not None:
|
||||
component_dir = component_dir / component[KEY_PATH]
|
||||
|
||||
if component[KEY_COMPONENTS] == ["*"]:
|
||||
shutil.copytree(
|
||||
component_dir,
|
||||
CORE.relative_build_path("components"),
|
||||
dirs_exist_ok=True,
|
||||
ignore=shutil.ignore_patterns(".git*"),
|
||||
symlinks=True,
|
||||
ignore_dangling_symlinks=True,
|
||||
)
|
||||
elif len(component[KEY_COMPONENTS]) > 0:
|
||||
for comp in component[KEY_COMPONENTS]:
|
||||
shutil.copytree(
|
||||
component_dir / comp,
|
||||
CORE.relative_build_path(f"components/{comp}"),
|
||||
dirs_exist_ok=True,
|
||||
ignore=shutil.ignore_patterns(".git*"),
|
||||
symlinks=True,
|
||||
ignore_dangling_symlinks=True,
|
||||
)
|
||||
else:
|
||||
shutil.copytree(
|
||||
component_dir,
|
||||
CORE.relative_build_path(f"components/{name}"),
|
||||
dirs_exist_ok=True,
|
||||
ignore=shutil.ignore_patterns(".git*"),
|
||||
symlinks=True,
|
||||
ignore_dangling_symlinks=True,
|
||||
)
|
||||
|
||||
for _, file in CORE.data[KEY_ESP32][KEY_EXTRA_BUILD_FILES].items():
|
||||
if file[KEY_PATH].startswith("http"):
|
||||
import requests
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "ble_event_pool.h"
|
||||
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#include <esp_bt.h>
|
||||
@@ -516,13 +517,12 @@ void ESP32BLE::dump_config() {
|
||||
break;
|
||||
}
|
||||
ESP_LOGCONFIG(TAG,
|
||||
"ESP32 BLE:\n"
|
||||
" MAC address: %02X:%02X:%02X:%02X:%02X:%02X\n"
|
||||
"BLE:\n"
|
||||
" MAC address: %s\n"
|
||||
" IO Capability: %s",
|
||||
mac_address[0], mac_address[1], mac_address[2], mac_address[3], mac_address[4], mac_address[5],
|
||||
io_capability_s);
|
||||
format_mac_address_pretty(mac_address).c_str(), io_capability_s);
|
||||
} else {
|
||||
ESP_LOGCONFIG(TAG, "ESP32 BLE: bluetooth stack is not enabled");
|
||||
ESP_LOGCONFIG(TAG, "Bluetooth stack is not enabled");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ from esphome.const import (
|
||||
CONF_VSYNC_PIN,
|
||||
)
|
||||
from esphome.core import CORE
|
||||
from esphome.cpp_helpers import setup_entity
|
||||
from esphome.core.entity_helpers import setup_entity
|
||||
|
||||
DEPENDENCIES = ["esp32"]
|
||||
|
||||
@@ -284,7 +284,7 @@ SETTERS = {
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await setup_entity(var, config)
|
||||
await setup_entity(var, config, "camera")
|
||||
await cg.register_component(var, config)
|
||||
|
||||
for key, setter in SETTERS.items():
|
||||
|
||||
0
esphome/components/esp32_hall/__init__.py
Normal file
0
esphome/components/esp32_hall/__init__.py
Normal file
5
esphome/components/esp32_hall/sensor.py
Normal file
5
esphome/components/esp32_hall/sensor.py
Normal file
@@ -0,0 +1,5 @@
|
||||
import esphome.config_validation as cv
|
||||
|
||||
CONFIG_SCHEMA = cv.invalid(
|
||||
"The esp32_hall component has been removed as of ESPHome 2025.7.0. See https://github.com/esphome/esphome/pull/9117 for details."
|
||||
)
|
||||
@@ -18,8 +18,8 @@ from esphome.const import (
|
||||
DEVICE_CLASS_MOTION,
|
||||
)
|
||||
from esphome.core import CORE, coroutine_with_priority
|
||||
from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity
|
||||
from esphome.cpp_generator import MockObjClass
|
||||
from esphome.cpp_helpers import setup_entity
|
||||
|
||||
CODEOWNERS = ["@nohat"]
|
||||
IS_PLATFORM_COMPONENT = True
|
||||
@@ -59,6 +59,9 @@ _EVENT_SCHEMA = (
|
||||
)
|
||||
|
||||
|
||||
_EVENT_SCHEMA.add_extra(entity_duplicate_validator("event"))
|
||||
|
||||
|
||||
def event_schema(
|
||||
class_: MockObjClass = cv.UNDEFINED,
|
||||
*,
|
||||
@@ -88,7 +91,7 @@ EVENT_SCHEMA.add_extra(cv.deprecated_schema_constant("event"))
|
||||
|
||||
|
||||
async def setup_event_core_(var, config, *, event_types: list[str]):
|
||||
await setup_entity(var, config)
|
||||
await setup_entity(var, config, "event")
|
||||
|
||||
for conf in config.get(CONF_ON_EVENT, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
|
||||
@@ -32,7 +32,7 @@ from esphome.const import (
|
||||
CONF_WEB_SERVER,
|
||||
)
|
||||
from esphome.core import CORE, coroutine_with_priority
|
||||
from esphome.cpp_helpers import setup_entity
|
||||
from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity
|
||||
|
||||
IS_PLATFORM_COMPONENT = True
|
||||
|
||||
@@ -161,6 +161,9 @@ _FAN_SCHEMA = (
|
||||
)
|
||||
|
||||
|
||||
_FAN_SCHEMA.add_extra(entity_duplicate_validator("fan"))
|
||||
|
||||
|
||||
def fan_schema(
|
||||
class_: cg.Pvariable,
|
||||
*,
|
||||
@@ -225,7 +228,7 @@ def validate_preset_modes(value):
|
||||
|
||||
|
||||
async def setup_fan_core_(var, config):
|
||||
await setup_entity(var, config)
|
||||
await setup_entity(var, config, "fan")
|
||||
|
||||
cg.add(var.set_restore_mode(config[CONF_RESTORE_MODE]))
|
||||
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#endif
|
||||
|
||||
#include "esphome/core/application.h"
|
||||
|
||||
#define highbyte(val) (uint8_t)((val) >> 8)
|
||||
#define lowbyte(val) (uint8_t)((val) &0xff)
|
||||
|
||||
@@ -15,8 +17,106 @@ namespace esphome {
|
||||
namespace 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 VERSION_FMT = "%u.%02X.%02X%02X%02X%02X";
|
||||
|
||||
LD2410Component::LD2410Component() {}
|
||||
// Memory-efficient lookup tables
|
||||
namespace {
|
||||
struct StringToUint8 {
|
||||
const char *str;
|
||||
uint8_t value;
|
||||
};
|
||||
|
||||
struct Uint8ToString {
|
||||
uint8_t value;
|
||||
const char *str;
|
||||
};
|
||||
|
||||
constexpr StringToUint8 BAUD_RATES[] = {
|
||||
{"9600", BAUD_RATE_9600}, {"19200", BAUD_RATE_19200}, {"38400", BAUD_RATE_38400},
|
||||
{"57600", BAUD_RATE_57600}, {"115200", BAUD_RATE_115200}, {"230400", BAUD_RATE_230400},
|
||||
{"256000", BAUD_RATE_256000}, {"460800", BAUD_RATE_460800},
|
||||
};
|
||||
|
||||
constexpr StringToUint8 DISTANCE_RESOLUTIONS[] = {
|
||||
{"0.2m", DISTANCE_RESOLUTION_0_2},
|
||||
{"0.75m", DISTANCE_RESOLUTION_0_75},
|
||||
};
|
||||
|
||||
constexpr Uint8ToString DISTANCE_RESOLUTIONS_REV[] = {
|
||||
{DISTANCE_RESOLUTION_0_2, "0.2m"},
|
||||
{DISTANCE_RESOLUTION_0_75, "0.75m"},
|
||||
};
|
||||
|
||||
constexpr StringToUint8 LIGHT_FUNCTIONS[] = {
|
||||
{"off", LIGHT_FUNCTION_OFF},
|
||||
{"below", LIGHT_FUNCTION_BELOW},
|
||||
{"above", LIGHT_FUNCTION_ABOVE},
|
||||
};
|
||||
|
||||
constexpr Uint8ToString LIGHT_FUNCTIONS_REV[] = {
|
||||
{LIGHT_FUNCTION_OFF, "off"},
|
||||
{LIGHT_FUNCTION_BELOW, "below"},
|
||||
{LIGHT_FUNCTION_ABOVE, "above"},
|
||||
};
|
||||
|
||||
constexpr StringToUint8 OUT_PIN_LEVELS[] = {
|
||||
{"low", OUT_PIN_LEVEL_LOW},
|
||||
{"high", OUT_PIN_LEVEL_HIGH},
|
||||
};
|
||||
|
||||
constexpr Uint8ToString OUT_PIN_LEVELS_REV[] = {
|
||||
{OUT_PIN_LEVEL_LOW, "low"},
|
||||
{OUT_PIN_LEVEL_HIGH, "high"},
|
||||
};
|
||||
|
||||
// Helper functions for lookups
|
||||
template<size_t N> uint8_t find_uint8(const StringToUint8 (&arr)[N], const std::string &str) {
|
||||
for (const auto &entry : arr) {
|
||||
if (str == entry.str)
|
||||
return entry.value;
|
||||
}
|
||||
return 0xFF; // Not found
|
||||
}
|
||||
|
||||
template<size_t N> const char *find_str(const Uint8ToString (&arr)[N], uint8_t value) {
|
||||
for (const auto &entry : arr) {
|
||||
if (value == entry.value)
|
||||
return entry.str;
|
||||
}
|
||||
return ""; // Not found
|
||||
}
|
||||
} // namespace
|
||||
// Commands
|
||||
static const uint8_t CMD_ENABLE_CONF = 0xFF;
|
||||
static const uint8_t CMD_DISABLE_CONF = 0xFE;
|
||||
static const uint8_t CMD_ENABLE_ENG = 0x62;
|
||||
static const uint8_t CMD_DISABLE_ENG = 0x63;
|
||||
static const uint8_t CMD_MAXDIST_DURATION = 0x60;
|
||||
static const uint8_t CMD_QUERY = 0x61;
|
||||
static const uint8_t CMD_GATE_SENS = 0x64;
|
||||
static const uint8_t CMD_VERSION = 0xA0;
|
||||
static const uint8_t CMD_QUERY_DISTANCE_RESOLUTION = 0xAB;
|
||||
static const uint8_t CMD_SET_DISTANCE_RESOLUTION = 0xAA;
|
||||
static const uint8_t CMD_QUERY_LIGHT_CONTROL = 0xAE;
|
||||
static const uint8_t CMD_SET_LIGHT_CONTROL = 0xAD;
|
||||
static const uint8_t CMD_SET_BAUD_RATE = 0xA1;
|
||||
static const uint8_t CMD_BT_PASSWORD = 0xA9;
|
||||
static const uint8_t CMD_MAC = 0xA5;
|
||||
static const uint8_t CMD_RESET = 0xA2;
|
||||
static const uint8_t CMD_RESTART = 0xA3;
|
||||
static const uint8_t CMD_BLUETOOTH = 0xA4;
|
||||
// Commands values
|
||||
static const uint8_t CMD_MAX_MOVE_VALUE = 0x00;
|
||||
static const uint8_t CMD_MAX_STILL_VALUE = 0x01;
|
||||
static const uint8_t CMD_DURATION_VALUE = 0x02;
|
||||
// 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};
|
||||
// Data Header & Footer
|
||||
static const uint8_t DATA_FRAME_HEADER[4] = {0xF4, 0xF3, 0xF2, 0xF1};
|
||||
static const uint8_t DATA_FRAME_END[4] = {0xF8, 0xF7, 0xF6, 0xF5};
|
||||
|
||||
void LD2410Component::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "LD2410:");
|
||||
@@ -73,10 +173,10 @@ void LD2410Component::dump_config() {
|
||||
#endif
|
||||
this->read_all_info();
|
||||
ESP_LOGCONFIG(TAG,
|
||||
" Throttle_ : %ums\n"
|
||||
" MAC Address : %s\n"
|
||||
" Firmware Version : %s",
|
||||
this->throttle_, const_cast<char *>(this->mac_.c_str()), const_cast<char *>(this->version_.c_str()));
|
||||
" 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() {
|
||||
@@ -153,7 +253,7 @@ void LD2410Component::handle_periodic_data_(uint8_t *buffer, int len) {
|
||||
/*
|
||||
Reduce data update rate to prevent home assistant database size grow fast
|
||||
*/
|
||||
int32_t current_millis = millis();
|
||||
int32_t current_millis = App.get_loop_component_start_time();
|
||||
if (current_millis - last_periodic_millis_ < this->throttle_)
|
||||
return;
|
||||
last_periodic_millis_ = current_millis;
|
||||
@@ -198,7 +298,7 @@ void LD2410Component::handle_periodic_data_(uint8_t *buffer, int len) {
|
||||
*/
|
||||
#ifdef USE_SENSOR
|
||||
if (this->moving_target_distance_sensor_ != nullptr) {
|
||||
int new_moving_target_distance = this->two_byte_to_int_(buffer[MOVING_TARGET_LOW], buffer[MOVING_TARGET_HIGH]);
|
||||
int new_moving_target_distance = ld2410::two_byte_to_int(buffer[MOVING_TARGET_LOW], buffer[MOVING_TARGET_HIGH]);
|
||||
if (this->moving_target_distance_sensor_->get_state() != new_moving_target_distance)
|
||||
this->moving_target_distance_sensor_->publish_state(new_moving_target_distance);
|
||||
}
|
||||
@@ -208,7 +308,7 @@ void LD2410Component::handle_periodic_data_(uint8_t *buffer, int len) {
|
||||
this->moving_target_energy_sensor_->publish_state(new_moving_target_energy);
|
||||
}
|
||||
if (this->still_target_distance_sensor_ != nullptr) {
|
||||
int new_still_target_distance = this->two_byte_to_int_(buffer[STILL_TARGET_LOW], buffer[STILL_TARGET_HIGH]);
|
||||
int new_still_target_distance = ld2410::two_byte_to_int(buffer[STILL_TARGET_LOW], buffer[STILL_TARGET_HIGH]);
|
||||
if (this->still_target_distance_sensor_->get_state() != new_still_target_distance)
|
||||
this->still_target_distance_sensor_->publish_state(new_still_target_distance);
|
||||
}
|
||||
@@ -218,7 +318,7 @@ void LD2410Component::handle_periodic_data_(uint8_t *buffer, int len) {
|
||||
this->still_target_energy_sensor_->publish_state(new_still_target_energy);
|
||||
}
|
||||
if (this->detection_distance_sensor_ != nullptr) {
|
||||
int new_detect_distance = this->two_byte_to_int_(buffer[DETECT_DISTANCE_LOW], buffer[DETECT_DISTANCE_HIGH]);
|
||||
int new_detect_distance = ld2410::two_byte_to_int(buffer[DETECT_DISTANCE_LOW], buffer[DETECT_DISTANCE_HIGH]);
|
||||
if (this->detection_distance_sensor_->get_state() != new_detect_distance)
|
||||
this->detection_distance_sensor_->publish_state(new_detect_distance);
|
||||
}
|
||||
@@ -280,40 +380,6 @@ void LD2410Component::handle_periodic_data_(uint8_t *buffer, int len) {
|
||||
#endif
|
||||
}
|
||||
|
||||
const char VERSION_FMT[] = "%u.%02X.%02X%02X%02X%02X";
|
||||
|
||||
std::string format_version(uint8_t *buffer) {
|
||||
std::string::size_type version_size = 256;
|
||||
std::string version;
|
||||
do {
|
||||
version.resize(version_size + 1);
|
||||
version_size = std::snprintf(&version[0], version.size(), VERSION_FMT, buffer[13], buffer[12], buffer[17],
|
||||
buffer[16], buffer[15], buffer[14]);
|
||||
} while (version_size + 1 > version.size());
|
||||
version.resize(version_size);
|
||||
return version;
|
||||
}
|
||||
|
||||
const char MAC_FMT[] = "%02X:%02X:%02X:%02X:%02X:%02X";
|
||||
|
||||
const std::string UNKNOWN_MAC("unknown");
|
||||
const std::string NO_MAC("08:05:04:03:02:01");
|
||||
|
||||
std::string format_mac(uint8_t *buffer) {
|
||||
std::string::size_type mac_size = 256;
|
||||
std::string mac;
|
||||
do {
|
||||
mac.resize(mac_size + 1);
|
||||
mac_size = std::snprintf(&mac[0], mac.size(), MAC_FMT, buffer[10], buffer[11], buffer[12], buffer[13], buffer[14],
|
||||
buffer[15]);
|
||||
} while (mac_size + 1 > mac.size());
|
||||
mac.resize(mac_size);
|
||||
if (mac == NO_MAC) {
|
||||
return UNKNOWN_MAC;
|
||||
}
|
||||
return mac;
|
||||
}
|
||||
|
||||
#ifdef USE_NUMBER
|
||||
std::function<void(void)> set_number_value(number::Number *n, float value) {
|
||||
float normalized_value = value * 1.0;
|
||||
@@ -328,40 +394,40 @@ std::function<void(void)> set_number_value(number::Number *n, float value) {
|
||||
bool LD2410Component::handle_ack_data_(uint8_t *buffer, int len) {
|
||||
ESP_LOGV(TAG, "Handling ACK DATA for COMMAND %02X", buffer[COMMAND]);
|
||||
if (len < 10) {
|
||||
ESP_LOGE(TAG, "Error with last command : incorrect length");
|
||||
ESP_LOGE(TAG, "Invalid length");
|
||||
return true;
|
||||
}
|
||||
if (buffer[0] != 0xFD || buffer[1] != 0xFC || buffer[2] != 0xFB || buffer[3] != 0xFA) { // check 4 frame start bytes
|
||||
ESP_LOGE(TAG, "Error with last command : incorrect Header");
|
||||
ESP_LOGE(TAG, "Invalid header");
|
||||
return true;
|
||||
}
|
||||
if (buffer[COMMAND_STATUS] != 0x01) {
|
||||
ESP_LOGE(TAG, "Error with last command : status != 0x01");
|
||||
ESP_LOGE(TAG, "Invalid status");
|
||||
return true;
|
||||
}
|
||||
if (this->two_byte_to_int_(buffer[8], buffer[9]) != 0x00) {
|
||||
ESP_LOGE(TAG, "Error with last command , last buffer was: %u , %u", buffer[8], buffer[9]);
|
||||
if (ld2410::two_byte_to_int(buffer[8], buffer[9]) != 0x00) {
|
||||
ESP_LOGE(TAG, "Invalid command: %u, %u", buffer[8], buffer[9]);
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (buffer[COMMAND]) {
|
||||
case lowbyte(CMD_ENABLE_CONF):
|
||||
ESP_LOGV(TAG, "Handled Enable conf command");
|
||||
ESP_LOGV(TAG, "Enable conf");
|
||||
break;
|
||||
case lowbyte(CMD_DISABLE_CONF):
|
||||
ESP_LOGV(TAG, "Handled Disabled conf command");
|
||||
ESP_LOGV(TAG, "Disabled conf");
|
||||
break;
|
||||
case lowbyte(CMD_SET_BAUD_RATE):
|
||||
ESP_LOGV(TAG, "Handled baud rate change command");
|
||||
ESP_LOGV(TAG, "Baud rate change");
|
||||
#ifdef USE_SELECT
|
||||
if (this->baud_rate_select_ != nullptr) {
|
||||
ESP_LOGE(TAG, "Change baud rate component config 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
|
||||
break;
|
||||
case lowbyte(CMD_VERSION):
|
||||
this->version_ = format_version(buffer);
|
||||
ESP_LOGV(TAG, "FW Version is: %s", const_cast<char *>(this->version_.c_str()));
|
||||
this->version_ = str_sprintf(VERSION_FMT, buffer[13], buffer[12], buffer[17], buffer[16], buffer[15], buffer[14]);
|
||||
ESP_LOGV(TAG, "Firmware version: %s", this->version_.c_str());
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
if (this->version_text_sensor_ != nullptr) {
|
||||
this->version_text_sensor_->publish_state(this->version_);
|
||||
@@ -370,8 +436,8 @@ bool LD2410Component::handle_ack_data_(uint8_t *buffer, int len) {
|
||||
break;
|
||||
case lowbyte(CMD_QUERY_DISTANCE_RESOLUTION): {
|
||||
std::string distance_resolution =
|
||||
DISTANCE_RESOLUTION_INT_TO_ENUM.at(this->two_byte_to_int_(buffer[10], buffer[11]));
|
||||
ESP_LOGV(TAG, "Distance resolution is: %s", const_cast<char *>(distance_resolution.c_str()));
|
||||
find_str(DISTANCE_RESOLUTIONS_REV, ld2410::two_byte_to_int(buffer[10], buffer[11]));
|
||||
ESP_LOGV(TAG, "Distance resolution: %s", distance_resolution.c_str());
|
||||
#ifdef USE_SELECT
|
||||
if (this->distance_resolution_select_ != nullptr &&
|
||||
this->distance_resolution_select_->state != distance_resolution) {
|
||||
@@ -380,12 +446,12 @@ bool LD2410Component::handle_ack_data_(uint8_t *buffer, int len) {
|
||||
#endif
|
||||
} break;
|
||||
case lowbyte(CMD_QUERY_LIGHT_CONTROL): {
|
||||
this->light_function_ = LIGHT_FUNCTION_INT_TO_ENUM.at(buffer[10]);
|
||||
this->light_function_ = find_str(LIGHT_FUNCTIONS_REV, buffer[10]);
|
||||
this->light_threshold_ = buffer[11] * 1.0;
|
||||
this->out_pin_level_ = OUT_PIN_LEVEL_INT_TO_ENUM.at(buffer[12]);
|
||||
ESP_LOGV(TAG, "Light function is: %s", const_cast<char *>(this->light_function_.c_str()));
|
||||
ESP_LOGV(TAG, "Light threshold is: %f", this->light_threshold_);
|
||||
ESP_LOGV(TAG, "Out pin level is: %s", const_cast<char *>(this->out_pin_level_.c_str()));
|
||||
this->out_pin_level_ = find_str(OUT_PIN_LEVELS_REV, buffer[12]);
|
||||
ESP_LOGV(TAG, "Light function: %s", const_cast<char *>(this->light_function_.c_str()));
|
||||
ESP_LOGV(TAG, "Light threshold: %f", this->light_threshold_);
|
||||
ESP_LOGV(TAG, "Out pin level: %s", const_cast<char *>(this->out_pin_level_.c_str()));
|
||||
#ifdef USE_SELECT
|
||||
if (this->light_function_select_ != nullptr && this->light_function_select_->state != this->light_function_) {
|
||||
this->light_function_select_->publish_state(this->light_function_);
|
||||
@@ -406,33 +472,33 @@ bool LD2410Component::handle_ack_data_(uint8_t *buffer, int len) {
|
||||
if (len < 20) {
|
||||
return false;
|
||||
}
|
||||
this->mac_ = format_mac(buffer);
|
||||
ESP_LOGV(TAG, "MAC Address is: %s", const_cast<char *>(this->mac_.c_str()));
|
||||
this->mac_ = format_mac_address_pretty(&buffer[10]);
|
||||
ESP_LOGV(TAG, "MAC address: %s", this->mac_.c_str());
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
if (this->mac_text_sensor_ != nullptr) {
|
||||
this->mac_text_sensor_->publish_state(this->mac_);
|
||||
this->mac_text_sensor_->publish_state(this->mac_ == NO_MAC ? UNKNOWN_MAC : this->mac_);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_SWITCH
|
||||
if (this->bluetooth_switch_ != nullptr) {
|
||||
this->bluetooth_switch_->publish_state(this->mac_ != UNKNOWN_MAC);
|
||||
this->bluetooth_switch_->publish_state(this->mac_ != NO_MAC);
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
case lowbyte(CMD_GATE_SENS):
|
||||
ESP_LOGV(TAG, "Handled sensitivity command");
|
||||
ESP_LOGV(TAG, "Sensitivity");
|
||||
break;
|
||||
case lowbyte(CMD_BLUETOOTH):
|
||||
ESP_LOGV(TAG, "Handled bluetooth command");
|
||||
ESP_LOGV(TAG, "Bluetooth");
|
||||
break;
|
||||
case lowbyte(CMD_SET_DISTANCE_RESOLUTION):
|
||||
ESP_LOGV(TAG, "Handled set distance resolution command");
|
||||
ESP_LOGV(TAG, "Set distance resolution");
|
||||
break;
|
||||
case lowbyte(CMD_SET_LIGHT_CONTROL):
|
||||
ESP_LOGV(TAG, "Handled set light control command");
|
||||
ESP_LOGV(TAG, "Set light control");
|
||||
break;
|
||||
case lowbyte(CMD_BT_PASSWORD):
|
||||
ESP_LOGV(TAG, "Handled set bluetooth password command");
|
||||
ESP_LOGV(TAG, "Set bluetooth password");
|
||||
break;
|
||||
case lowbyte(CMD_QUERY): // Query parameters response
|
||||
{
|
||||
@@ -461,7 +527,7 @@ bool LD2410Component::handle_ack_data_(uint8_t *buffer, int len) {
|
||||
/*
|
||||
None Duration: 33~34th bytes
|
||||
*/
|
||||
updates.push_back(set_number_value(this->timeout_number_, this->two_byte_to_int_(buffer[32], buffer[33])));
|
||||
updates.push_back(set_number_value(this->timeout_number_, ld2410::two_byte_to_int(buffer[32], buffer[33])));
|
||||
for (auto &update : updates) {
|
||||
update();
|
||||
}
|
||||
@@ -518,21 +584,21 @@ void LD2410Component::set_bluetooth(bool enable) {
|
||||
|
||||
void LD2410Component::set_distance_resolution(const std::string &state) {
|
||||
this->set_config_mode_(true);
|
||||
uint8_t cmd_value[2] = {DISTANCE_RESOLUTION_ENUM_TO_INT.at(state), 0x00};
|
||||
uint8_t cmd_value[2] = {find_uint8(DISTANCE_RESOLUTIONS, state), 0x00};
|
||||
this->send_command_(CMD_SET_DISTANCE_RESOLUTION, cmd_value, 2);
|
||||
this->set_timeout(200, [this]() { this->restart_and_read_all_info(); });
|
||||
}
|
||||
|
||||
void LD2410Component::set_baud_rate(const std::string &state) {
|
||||
this->set_config_mode_(true);
|
||||
uint8_t cmd_value[2] = {BAUD_RATE_ENUM_TO_INT.at(state), 0x00};
|
||||
uint8_t cmd_value[2] = {find_uint8(BAUD_RATES, state), 0x00};
|
||||
this->send_command_(CMD_SET_BAUD_RATE, cmd_value, 2);
|
||||
this->set_timeout(200, [this]() { this->restart_(); });
|
||||
}
|
||||
|
||||
void LD2410Component::set_bluetooth_password(const std::string &password) {
|
||||
if (password.length() != 6) {
|
||||
ESP_LOGE(TAG, "set_bluetooth_password(): invalid password length, must be exactly 6 chars '%s'", password.c_str());
|
||||
ESP_LOGE(TAG, "Password must be exactly 6 chars");
|
||||
return;
|
||||
}
|
||||
this->set_config_mode_(true);
|
||||
@@ -544,7 +610,7 @@ void LD2410Component::set_bluetooth_password(const std::string &password) {
|
||||
|
||||
void LD2410Component::set_engineering_mode(bool enable) {
|
||||
this->set_config_mode_(true);
|
||||
last_engineering_mode_change_millis_ = millis();
|
||||
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->set_config_mode_(false);
|
||||
@@ -659,9 +725,9 @@ void LD2410Component::set_light_out_control() {
|
||||
return;
|
||||
}
|
||||
this->set_config_mode_(true);
|
||||
uint8_t light_function = LIGHT_FUNCTION_ENUM_TO_INT.at(this->light_function_);
|
||||
uint8_t light_function = find_uint8(LIGHT_FUNCTIONS, this->light_function_);
|
||||
uint8_t light_threshold = static_cast<uint8_t>(this->light_threshold_);
|
||||
uint8_t out_pin_level = OUT_PIN_LEVEL_ENUM_TO_INT.at(this->out_pin_level_);
|
||||
uint8_t out_pin_level = find_uint8(OUT_PIN_LEVELS, 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
|
||||
|
||||
@@ -26,33 +26,11 @@
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
#include <map>
|
||||
|
||||
namespace esphome {
|
||||
namespace ld2410 {
|
||||
|
||||
#define CHECK_BIT(var, pos) (((var) >> (pos)) & 1)
|
||||
|
||||
// Commands
|
||||
static const uint8_t CMD_ENABLE_CONF = 0x00FF;
|
||||
static const uint8_t CMD_DISABLE_CONF = 0x00FE;
|
||||
static const uint8_t CMD_ENABLE_ENG = 0x0062;
|
||||
static const uint8_t CMD_DISABLE_ENG = 0x0063;
|
||||
static const uint8_t CMD_MAXDIST_DURATION = 0x0060;
|
||||
static const uint8_t CMD_QUERY = 0x0061;
|
||||
static const uint8_t CMD_GATE_SENS = 0x0064;
|
||||
static const uint8_t CMD_VERSION = 0x00A0;
|
||||
static const uint8_t CMD_QUERY_DISTANCE_RESOLUTION = 0x00AB;
|
||||
static const uint8_t CMD_SET_DISTANCE_RESOLUTION = 0x00AA;
|
||||
static const uint8_t CMD_QUERY_LIGHT_CONTROL = 0x00AE;
|
||||
static const uint8_t CMD_SET_LIGHT_CONTROL = 0x00AD;
|
||||
static const uint8_t CMD_SET_BAUD_RATE = 0x00A1;
|
||||
static const uint8_t CMD_BT_PASSWORD = 0x00A9;
|
||||
static const uint8_t CMD_MAC = 0x00A5;
|
||||
static const uint8_t CMD_RESET = 0x00A2;
|
||||
static const uint8_t CMD_RESTART = 0x00A3;
|
||||
static const uint8_t CMD_BLUETOOTH = 0x00A4;
|
||||
|
||||
enum BaudRateStructure : uint8_t {
|
||||
BAUD_RATE_9600 = 1,
|
||||
BAUD_RATE_19200 = 2,
|
||||
@@ -61,58 +39,25 @@ enum BaudRateStructure : uint8_t {
|
||||
BAUD_RATE_115200 = 5,
|
||||
BAUD_RATE_230400 = 6,
|
||||
BAUD_RATE_256000 = 7,
|
||||
BAUD_RATE_460800 = 8
|
||||
BAUD_RATE_460800 = 8,
|
||||
};
|
||||
|
||||
static const std::map<std::string, uint8_t> BAUD_RATE_ENUM_TO_INT{
|
||||
{"9600", BAUD_RATE_9600}, {"19200", BAUD_RATE_19200}, {"38400", BAUD_RATE_38400},
|
||||
{"57600", BAUD_RATE_57600}, {"115200", BAUD_RATE_115200}, {"230400", BAUD_RATE_230400},
|
||||
{"256000", BAUD_RATE_256000}, {"460800", BAUD_RATE_460800}};
|
||||
|
||||
enum DistanceResolutionStructure : uint8_t { DISTANCE_RESOLUTION_0_2 = 0x01, DISTANCE_RESOLUTION_0_75 = 0x00 };
|
||||
|
||||
static const std::map<std::string, uint8_t> DISTANCE_RESOLUTION_ENUM_TO_INT{{"0.2m", DISTANCE_RESOLUTION_0_2},
|
||||
{"0.75m", DISTANCE_RESOLUTION_0_75}};
|
||||
static const std::map<uint8_t, std::string> DISTANCE_RESOLUTION_INT_TO_ENUM{{DISTANCE_RESOLUTION_0_2, "0.2m"},
|
||||
{DISTANCE_RESOLUTION_0_75, "0.75m"}};
|
||||
enum DistanceResolutionStructure : uint8_t {
|
||||
DISTANCE_RESOLUTION_0_2 = 0x01,
|
||||
DISTANCE_RESOLUTION_0_75 = 0x00,
|
||||
};
|
||||
|
||||
enum LightFunctionStructure : uint8_t {
|
||||
LIGHT_FUNCTION_OFF = 0x00,
|
||||
LIGHT_FUNCTION_BELOW = 0x01,
|
||||
LIGHT_FUNCTION_ABOVE = 0x02
|
||||
LIGHT_FUNCTION_ABOVE = 0x02,
|
||||
};
|
||||
|
||||
static const std::map<std::string, uint8_t> LIGHT_FUNCTION_ENUM_TO_INT{
|
||||
{"off", LIGHT_FUNCTION_OFF}, {"below", LIGHT_FUNCTION_BELOW}, {"above", LIGHT_FUNCTION_ABOVE}};
|
||||
static const std::map<uint8_t, std::string> LIGHT_FUNCTION_INT_TO_ENUM{
|
||||
{LIGHT_FUNCTION_OFF, "off"}, {LIGHT_FUNCTION_BELOW, "below"}, {LIGHT_FUNCTION_ABOVE, "above"}};
|
||||
enum OutPinLevelStructure : uint8_t {
|
||||
OUT_PIN_LEVEL_LOW = 0x00,
|
||||
OUT_PIN_LEVEL_HIGH = 0x01,
|
||||
};
|
||||
|
||||
enum OutPinLevelStructure : uint8_t { OUT_PIN_LEVEL_LOW = 0x00, OUT_PIN_LEVEL_HIGH = 0x01 };
|
||||
|
||||
static const std::map<std::string, uint8_t> OUT_PIN_LEVEL_ENUM_TO_INT{{"low", OUT_PIN_LEVEL_LOW},
|
||||
{"high", OUT_PIN_LEVEL_HIGH}};
|
||||
static const std::map<uint8_t, std::string> OUT_PIN_LEVEL_INT_TO_ENUM{{OUT_PIN_LEVEL_LOW, "low"},
|
||||
{OUT_PIN_LEVEL_HIGH, "high"}};
|
||||
|
||||
// Commands values
|
||||
static const uint8_t CMD_MAX_MOVE_VALUE = 0x0000;
|
||||
static const uint8_t CMD_MAX_STILL_VALUE = 0x0001;
|
||||
static const uint8_t CMD_DURATION_VALUE = 0x0002;
|
||||
// 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};
|
||||
// Data Header & Footer
|
||||
static const uint8_t DATA_FRAME_HEADER[4] = {0xF4, 0xF3, 0xF2, 0xF1};
|
||||
static const uint8_t DATA_FRAME_END[4] = {0xF8, 0xF7, 0xF6, 0xF5};
|
||||
/*
|
||||
Data Type: 6th byte
|
||||
Target states: 9th byte
|
||||
Moving target distance: 10~11th bytes
|
||||
Moving target energy: 12th byte
|
||||
Still target distance: 13~14th bytes
|
||||
Still target energy: 15th byte
|
||||
Detect distance: 16~17th bytes
|
||||
*/
|
||||
enum PeriodicDataStructure : uint8_t {
|
||||
DATA_TYPES = 6,
|
||||
TARGET_STATES = 8,
|
||||
@@ -129,11 +74,20 @@ enum PeriodicDataStructure : uint8_t {
|
||||
LIGHT_SENSOR = 37,
|
||||
OUT_PIN_SENSOR = 38,
|
||||
};
|
||||
enum PeriodicDataValue : uint8_t { HEAD = 0xAA, END = 0x55, CHECK = 0x00 };
|
||||
|
||||
enum AckDataStructure : uint8_t { COMMAND = 6, COMMAND_STATUS = 7 };
|
||||
enum PeriodicDataValue : uint8_t {
|
||||
HEAD = 0xAA,
|
||||
END = 0x55,
|
||||
CHECK = 0x00,
|
||||
};
|
||||
|
||||
enum AckDataStructure : uint8_t {
|
||||
COMMAND = 6,
|
||||
COMMAND_STATUS = 7,
|
||||
};
|
||||
|
||||
static inline int two_byte_to_int(char firstbyte, char secondbyte) { return (int16_t) (secondbyte << 8) + firstbyte; }
|
||||
|
||||
// char cmd[2] = {enable ? 0xFF : 0xFE, 0x00};
|
||||
class LD2410Component : public Component, public uart::UARTDevice {
|
||||
#ifdef USE_SENSOR
|
||||
SUB_SENSOR(moving_target_distance)
|
||||
@@ -176,7 +130,6 @@ class LD2410Component : public Component, public uart::UARTDevice {
|
||||
#endif
|
||||
|
||||
public:
|
||||
LD2410Component();
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
void loop() override;
|
||||
@@ -202,7 +155,6 @@ class LD2410Component : public Component, public uart::UARTDevice {
|
||||
void factory_reset();
|
||||
|
||||
protected:
|
||||
int two_byte_to_int_(char firstbyte, char secondbyte) { return (int16_t) (secondbyte << 8) + firstbyte; }
|
||||
void send_command_(uint8_t command_str, const uint8_t *command_value, int command_value_len);
|
||||
void set_config_mode_(bool enable);
|
||||
void handle_periodic_data_(uint8_t *buffer, int len);
|
||||
@@ -215,14 +167,14 @@ class LD2410Component : public Component, public uart::UARTDevice {
|
||||
void get_light_control_();
|
||||
void restart_();
|
||||
|
||||
int32_t last_periodic_millis_ = millis();
|
||||
int32_t last_engineering_mode_change_millis_ = millis();
|
||||
int32_t last_periodic_millis_ = 0;
|
||||
int32_t last_engineering_mode_change_millis_ = 0;
|
||||
uint16_t throttle_;
|
||||
float light_threshold_ = -1;
|
||||
std::string version_;
|
||||
std::string mac_;
|
||||
std::string out_pin_level_;
|
||||
std::string light_function_;
|
||||
float light_threshold_ = -1;
|
||||
#ifdef USE_NUMBER
|
||||
std::vector<number::Number *> gate_still_threshold_numbers_ = std::vector<number::Number *>(9);
|
||||
std::vector<number::Number *> gate_move_threshold_numbers_ = std::vector<number::Number *>(9);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "ld2420.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
/*
|
||||
@@ -40,7 +41,7 @@ There are three documented parameters for modes:
|
||||
00 04 = Energy output mode
|
||||
This mode outputs detailed signal energy values for each gate and the target distance.
|
||||
The data format consist of the following.
|
||||
Header HH, Length LL, Persence PP, Distance DD, 16 Gate Energies EE, Footer FF
|
||||
Header HH, Length LL, Presence PP, Distance DD, 16 Gate Energies EE, Footer FF
|
||||
HH HH HH HH LL LL PP DD DD EE EE .. 16x .. FF FF FF FF
|
||||
F4 F3 F2 F1 23 00 00 00 00 00 00 .. .. .. .. F8 F7 F6 F5
|
||||
00 00 = debug output mode
|
||||
@@ -67,10 +68,10 @@ float LD2420Component::get_setup_priority() const { return setup_priority::BUS;
|
||||
void LD2420Component::dump_config() {
|
||||
ESP_LOGCONFIG(TAG,
|
||||
"LD2420:\n"
|
||||
" Firmware Version : %7s\n"
|
||||
"LD2420 Number:",
|
||||
" Firmware version: %7s",
|
||||
this->ld2420_firmware_ver_);
|
||||
#ifdef USE_NUMBER
|
||||
ESP_LOGCONFIG(TAG, "Number:");
|
||||
LOG_NUMBER(TAG, " Gate Timeout:", this->gate_timeout_number_);
|
||||
LOG_NUMBER(TAG, " Gate Max Distance:", this->max_gate_distance_number_);
|
||||
LOG_NUMBER(TAG, " Gate Min Distance:", this->min_gate_distance_number_);
|
||||
@@ -86,10 +87,10 @@ void LD2420Component::dump_config() {
|
||||
LOG_BUTTON(TAG, " Factory Reset:", this->factory_reset_button_);
|
||||
LOG_BUTTON(TAG, " Restart Module:", this->restart_module_button_);
|
||||
#endif
|
||||
ESP_LOGCONFIG(TAG, "LD2420 Select:");
|
||||
ESP_LOGCONFIG(TAG, "Select:");
|
||||
LOG_SELECT(TAG, " Operating Mode", this->operating_selector_);
|
||||
if (this->get_firmware_int_(ld2420_firmware_ver_) < CALIBRATE_VERSION_MIN) {
|
||||
ESP_LOGW(TAG, "LD2420 Firmware Version %s and older are only supported in Simple Mode", ld2420_firmware_ver_);
|
||||
if (LD2420Component::get_firmware_int(this->ld2420_firmware_ver_) < CALIBRATE_VERSION_MIN) {
|
||||
ESP_LOGW(TAG, "Firmware version %s and older supports Simple Mode only", this->ld2420_firmware_ver_);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,7 +103,7 @@ uint8_t LD2420Component::calc_checksum(void *data, size_t size) {
|
||||
return checksum;
|
||||
}
|
||||
|
||||
int LD2420Component::get_firmware_int_(const char *version_string) {
|
||||
int LD2420Component::get_firmware_int(const char *version_string) {
|
||||
std::string version_str = version_string;
|
||||
if (version_str[0] == 'v') {
|
||||
version_str = version_str.substr(1);
|
||||
@@ -115,7 +116,7 @@ int LD2420Component::get_firmware_int_(const char *version_string) {
|
||||
void LD2420Component::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Running setup");
|
||||
if (this->set_config_mode(true) == LD2420_ERROR_TIMEOUT) {
|
||||
ESP_LOGE(TAG, "LD2420 module has failed to respond, check baud rate and serial connections.");
|
||||
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
@@ -127,7 +128,7 @@ void LD2420Component::setup() {
|
||||
const char *pfw = this->ld2420_firmware_ver_;
|
||||
std::string fw_str(pfw);
|
||||
|
||||
for (auto &listener : listeners_) {
|
||||
for (auto &listener : this->listeners_) {
|
||||
listener->on_fw_version(fw_str);
|
||||
}
|
||||
|
||||
@@ -137,11 +138,11 @@ void LD2420Component::setup() {
|
||||
}
|
||||
|
||||
memcpy(&this->new_config, &this->current_config, sizeof(this->current_config));
|
||||
if (get_firmware_int_(ld2420_firmware_ver_) < CALIBRATE_VERSION_MIN) {
|
||||
if (LD2420Component::get_firmware_int(this->ld2420_firmware_ver_) < CALIBRATE_VERSION_MIN) {
|
||||
this->set_operating_mode(OP_SIMPLE_MODE_STRING);
|
||||
this->operating_selector_->publish_state(OP_SIMPLE_MODE_STRING);
|
||||
this->set_mode_(CMD_SYSTEM_MODE_SIMPLE);
|
||||
ESP_LOGW(TAG, "LD2420 Frimware Version %s and older are only supported in Simple Mode", ld2420_firmware_ver_);
|
||||
ESP_LOGW(TAG, "Firmware version %s and older supports Simple Mode only", this->ld2420_firmware_ver_);
|
||||
} else {
|
||||
this->set_mode_(CMD_SYSTEM_MODE_ENERGY);
|
||||
this->operating_selector_->publish_state(OP_NORMAL_MODE_STRING);
|
||||
@@ -151,18 +152,17 @@ void LD2420Component::setup() {
|
||||
#endif
|
||||
this->set_system_mode(this->system_mode_);
|
||||
this->set_config_mode(false);
|
||||
ESP_LOGCONFIG(TAG, "LD2420 setup complete.");
|
||||
}
|
||||
|
||||
void LD2420Component::apply_config_action() {
|
||||
const uint8_t checksum = calc_checksum(&this->new_config, sizeof(this->new_config));
|
||||
if (checksum == calc_checksum(&this->current_config, sizeof(this->current_config))) {
|
||||
ESP_LOGCONFIG(TAG, "No configuration change detected");
|
||||
ESP_LOGD(TAG, "No configuration change detected");
|
||||
return;
|
||||
}
|
||||
ESP_LOGCONFIG(TAG, "Reconfiguring LD2420");
|
||||
ESP_LOGD(TAG, "Reconfiguring");
|
||||
if (this->set_config_mode(true) == LD2420_ERROR_TIMEOUT) {
|
||||
ESP_LOGE(TAG, "LD2420 module has failed to respond, check baud rate and serial connections.");
|
||||
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
@@ -178,13 +178,12 @@ void LD2420Component::apply_config_action() {
|
||||
this->set_system_mode(this->system_mode_);
|
||||
this->set_config_mode(false); // Disable config mode to save new values in LD2420 nvm
|
||||
this->set_operating_mode(OP_NORMAL_MODE_STRING);
|
||||
ESP_LOGCONFIG(TAG, "LD2420 reconfig complete.");
|
||||
}
|
||||
|
||||
void LD2420Component::factory_reset_action() {
|
||||
ESP_LOGCONFIG(TAG, "Setting factory defaults");
|
||||
ESP_LOGD(TAG, "Setting factory defaults");
|
||||
if (this->set_config_mode(true) == LD2420_ERROR_TIMEOUT) {
|
||||
ESP_LOGE(TAG, "LD2420 module has failed to respond, check baud rate and serial connections.");
|
||||
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
@@ -207,18 +206,16 @@ void LD2420Component::factory_reset_action() {
|
||||
this->init_gate_config_numbers();
|
||||
this->refresh_gate_config_numbers();
|
||||
#endif
|
||||
ESP_LOGCONFIG(TAG, "LD2420 factory reset complete.");
|
||||
}
|
||||
|
||||
void LD2420Component::restart_module_action() {
|
||||
ESP_LOGCONFIG(TAG, "Restarting LD2420 module");
|
||||
ESP_LOGD(TAG, "Restarting");
|
||||
this->send_module_restart();
|
||||
this->set_timeout(250, [this]() {
|
||||
this->set_config_mode(true);
|
||||
this->set_system_mode(system_mode_);
|
||||
this->set_system_mode(this->system_mode_);
|
||||
this->set_config_mode(false);
|
||||
});
|
||||
ESP_LOGCONFIG(TAG, "LD2420 Restarted.");
|
||||
}
|
||||
|
||||
void LD2420Component::revert_config_action() {
|
||||
@@ -226,18 +223,18 @@ void LD2420Component::revert_config_action() {
|
||||
#ifdef USE_NUMBER
|
||||
this->init_gate_config_numbers();
|
||||
#endif
|
||||
ESP_LOGCONFIG(TAG, "Reverted config number edits.");
|
||||
ESP_LOGD(TAG, "Reverted config number edits");
|
||||
}
|
||||
|
||||
void LD2420Component::loop() {
|
||||
// If there is a active send command do not process it here, the send command call will handle it.
|
||||
if (!get_cmd_active_()) {
|
||||
if (!available())
|
||||
if (!this->get_cmd_active_()) {
|
||||
if (!this->available())
|
||||
return;
|
||||
static uint8_t buffer[2048];
|
||||
static uint8_t rx_data;
|
||||
while (available()) {
|
||||
rx_data = read();
|
||||
while (this->available()) {
|
||||
rx_data = this->read();
|
||||
this->readline_(rx_data, buffer, sizeof(buffer));
|
||||
}
|
||||
}
|
||||
@@ -292,7 +289,7 @@ void LD2420Component::report_gate_data() {
|
||||
|
||||
void LD2420Component::set_operating_mode(const std::string &state) {
|
||||
// If unsupported firmware ignore mode select
|
||||
if (get_firmware_int_(ld2420_firmware_ver_) >= CALIBRATE_VERSION_MIN) {
|
||||
if (LD2420Component::get_firmware_int(ld2420_firmware_ver_) >= CALIBRATE_VERSION_MIN) {
|
||||
this->current_operating_mode = OP_MODE_TO_UINT.at(state);
|
||||
// Entering Auto Calibrate we need to clear the privoiuos data collection
|
||||
this->operating_selector_->publish_state(state);
|
||||
@@ -365,13 +362,13 @@ void LD2420Component::handle_energy_mode_(uint8_t *buffer, int len) {
|
||||
}
|
||||
|
||||
// Resonable refresh rate for home assistant database size health
|
||||
const int32_t current_millis = millis();
|
||||
const int32_t current_millis = App.get_loop_component_start_time();
|
||||
if (current_millis - this->last_periodic_millis < REFRESH_RATE_MS)
|
||||
return;
|
||||
this->last_periodic_millis = current_millis;
|
||||
for (auto &listener : this->listeners_) {
|
||||
listener->on_distance(get_distance_());
|
||||
listener->on_presence(get_presence_());
|
||||
listener->on_distance(this->get_distance_());
|
||||
listener->on_presence(this->get_presence_());
|
||||
listener->on_energy(this->gate_energy_, sizeof(this->gate_energy_) / sizeof(this->gate_energy_[0]));
|
||||
}
|
||||
|
||||
@@ -392,9 +389,9 @@ void LD2420Component::handle_simple_mode_(const uint8_t *inbuf, int len) {
|
||||
char outbuf[bufsize]{0};
|
||||
while (true) {
|
||||
if (inbuf[pos - 2] == 'O' && inbuf[pos - 1] == 'F' && inbuf[pos] == 'F') {
|
||||
set_presence_(false);
|
||||
this->set_presence_(false);
|
||||
} else if (inbuf[pos - 1] == 'O' && inbuf[pos] == 'N') {
|
||||
set_presence_(true);
|
||||
this->set_presence_(true);
|
||||
}
|
||||
if (inbuf[pos] >= '0' && inbuf[pos] <= '9') {
|
||||
if (index < bufsize - 1) {
|
||||
@@ -411,18 +408,18 @@ void LD2420Component::handle_simple_mode_(const uint8_t *inbuf, int len) {
|
||||
}
|
||||
outbuf[index] = '\0';
|
||||
if (index > 1)
|
||||
set_distance_(strtol(outbuf, &endptr, 10));
|
||||
this->set_distance_(strtol(outbuf, &endptr, 10));
|
||||
|
||||
if (get_mode_() == CMD_SYSTEM_MODE_SIMPLE) {
|
||||
if (this->get_mode_() == CMD_SYSTEM_MODE_SIMPLE) {
|
||||
// Resonable refresh rate for home assistant database size health
|
||||
const int32_t current_millis = millis();
|
||||
const int32_t current_millis = App.get_loop_component_start_time();
|
||||
if (current_millis - this->last_normal_periodic_millis < REFRESH_RATE_MS)
|
||||
return;
|
||||
this->last_normal_periodic_millis = current_millis;
|
||||
for (auto &listener : this->listeners_)
|
||||
listener->on_distance(get_distance_());
|
||||
listener->on_distance(this->get_distance_());
|
||||
for (auto &listener : this->listeners_)
|
||||
listener->on_presence(get_presence_());
|
||||
listener->on_presence(this->get_presence_());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -433,10 +430,10 @@ void LD2420Component::handle_ack_data_(uint8_t *buffer, int len) {
|
||||
uint8_t data_element = 0;
|
||||
uint16_t data_pos = 0;
|
||||
if (this->cmd_reply_.length > CMD_MAX_BYTES) {
|
||||
ESP_LOGW(TAG, "LD2420 reply - received command reply frame is corrupt, length exceeds %d bytes.", CMD_MAX_BYTES);
|
||||
ESP_LOGW(TAG, "Reply frame too long");
|
||||
return;
|
||||
} else if (this->cmd_reply_.length < 2) {
|
||||
ESP_LOGW(TAG, "LD2420 reply - received command frame is corrupt, length is less than 2 bytes.");
|
||||
ESP_LOGW(TAG, "Command frame too short");
|
||||
return;
|
||||
}
|
||||
memcpy(&this->cmd_reply_.error, &buffer[CMD_ERROR_WORD], sizeof(this->cmd_reply_.error));
|
||||
@@ -447,13 +444,13 @@ void LD2420Component::handle_ack_data_(uint8_t *buffer, int len) {
|
||||
this->cmd_reply_.ack = true;
|
||||
switch ((uint16_t) this->cmd_reply_.command) {
|
||||
case (CMD_ENABLE_CONF):
|
||||
ESP_LOGD(TAG, "LD2420 reply - set config enable: CMD = %2X %s", CMD_ENABLE_CONF, result);
|
||||
ESP_LOGV(TAG, "Set config enable: CMD = %2X %s", CMD_ENABLE_CONF, result);
|
||||
break;
|
||||
case (CMD_DISABLE_CONF):
|
||||
ESP_LOGD(TAG, "LD2420 reply - set config disable: CMD = %2X %s", CMD_DISABLE_CONF, result);
|
||||
ESP_LOGV(TAG, "Set config disable: CMD = %2X %s", CMD_DISABLE_CONF, result);
|
||||
break;
|
||||
case (CMD_READ_REGISTER):
|
||||
ESP_LOGD(TAG, "LD2420 reply - read register: CMD = %2X %s", CMD_READ_REGISTER, result);
|
||||
ESP_LOGV(TAG, "Read register: CMD = %2X %s", CMD_READ_REGISTER, result);
|
||||
// TODO Read/Write register is not implemented yet, this will get flushed out to a proper header file
|
||||
data_pos = 0x0A;
|
||||
for (uint16_t index = 0; index < (CMD_REG_DATA_REPLY_SIZE * // NOLINT
|
||||
@@ -465,13 +462,13 @@ void LD2420Component::handle_ack_data_(uint8_t *buffer, int len) {
|
||||
}
|
||||
break;
|
||||
case (CMD_WRITE_REGISTER):
|
||||
ESP_LOGD(TAG, "LD2420 reply - write register: CMD = %2X %s", CMD_WRITE_REGISTER, result);
|
||||
ESP_LOGV(TAG, "Write register: CMD = %2X %s", CMD_WRITE_REGISTER, result);
|
||||
break;
|
||||
case (CMD_WRITE_ABD_PARAM):
|
||||
ESP_LOGD(TAG, "LD2420 reply - write gate parameter(s): %2X %s", CMD_WRITE_ABD_PARAM, result);
|
||||
ESP_LOGV(TAG, "Write gate parameter(s): %2X %s", CMD_WRITE_ABD_PARAM, result);
|
||||
break;
|
||||
case (CMD_READ_ABD_PARAM):
|
||||
ESP_LOGD(TAG, "LD2420 reply - read gate parameter(s): %2X %s", CMD_READ_ABD_PARAM, result);
|
||||
ESP_LOGV(TAG, "Read gate parameter(s): %2X %s", CMD_READ_ABD_PARAM, result);
|
||||
data_pos = CMD_ABD_DATA_REPLY_START;
|
||||
for (uint16_t index = 0; index < (CMD_ABD_DATA_REPLY_SIZE * // NOLINT
|
||||
((buffer[CMD_FRAME_DATA_LENGTH] - 4) / CMD_ABD_DATA_REPLY_SIZE));
|
||||
@@ -483,11 +480,11 @@ void LD2420Component::handle_ack_data_(uint8_t *buffer, int len) {
|
||||
}
|
||||
break;
|
||||
case (CMD_WRITE_SYS_PARAM):
|
||||
ESP_LOGD(TAG, "LD2420 reply - set system parameter(s): %2X %s", CMD_WRITE_SYS_PARAM, result);
|
||||
ESP_LOGV(TAG, "Set system parameter(s): %2X %s", CMD_WRITE_SYS_PARAM, result);
|
||||
break;
|
||||
case (CMD_READ_VERSION):
|
||||
memcpy(this->ld2420_firmware_ver_, &buffer[12], buffer[10]);
|
||||
ESP_LOGD(TAG, "LD2420 reply - module firmware version: %7s %s", this->ld2420_firmware_ver_, result);
|
||||
ESP_LOGV(TAG, "Firmware version: %7s %s", this->ld2420_firmware_ver_, result);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@@ -533,7 +530,7 @@ int LD2420Component::send_cmd_from_array(CmdFrameT frame) {
|
||||
}
|
||||
|
||||
while (!this->cmd_reply_.ack) {
|
||||
while (available()) {
|
||||
while (this->available()) {
|
||||
this->readline_(read(), ack_buffer, sizeof(ack_buffer));
|
||||
}
|
||||
delay_microseconds_safe(1450);
|
||||
@@ -548,7 +545,7 @@ int LD2420Component::send_cmd_from_array(CmdFrameT frame) {
|
||||
if (this->cmd_reply_.ack)
|
||||
retry = 0;
|
||||
if (this->cmd_reply_.error > 0)
|
||||
handle_cmd_error(error);
|
||||
this->handle_cmd_error(error);
|
||||
}
|
||||
return error;
|
||||
}
|
||||
@@ -563,7 +560,7 @@ uint8_t LD2420Component::set_config_mode(bool enable) {
|
||||
cmd_frame.data_length += sizeof(CMD_PROTOCOL_VER);
|
||||
}
|
||||
cmd_frame.footer = CMD_FRAME_FOOTER;
|
||||
ESP_LOGD(TAG, "Sending set config %s command: %2X", enable ? "enable" : "disable", cmd_frame.command);
|
||||
ESP_LOGV(TAG, "Sending set config %s command: %2X", enable ? "enable" : "disable", cmd_frame.command);
|
||||
return this->send_cmd_from_array(cmd_frame);
|
||||
}
|
||||
|
||||
@@ -576,7 +573,7 @@ void LD2420Component::ld2420_restart() {
|
||||
cmd_frame.header = CMD_FRAME_HEADER;
|
||||
cmd_frame.command = CMD_RESTART;
|
||||
cmd_frame.footer = CMD_FRAME_FOOTER;
|
||||
ESP_LOGD(TAG, "Sending restart command: %2X", cmd_frame.command);
|
||||
ESP_LOGV(TAG, "Sending restart command: %2X", cmd_frame.command);
|
||||
this->send_cmd_from_array(cmd_frame);
|
||||
}
|
||||
|
||||
@@ -588,7 +585,7 @@ void LD2420Component::get_reg_value_(uint16_t reg) {
|
||||
cmd_frame.data[1] = reg;
|
||||
cmd_frame.data_length += 2;
|
||||
cmd_frame.footer = CMD_FRAME_FOOTER;
|
||||
ESP_LOGD(TAG, "Sending read register %4X command: %2X", reg, cmd_frame.command);
|
||||
ESP_LOGV(TAG, "Sending read register %4X command: %2X", reg, cmd_frame.command);
|
||||
this->send_cmd_from_array(cmd_frame);
|
||||
}
|
||||
|
||||
@@ -602,11 +599,11 @@ void LD2420Component::set_reg_value(uint16_t reg, uint16_t value) {
|
||||
memcpy(&cmd_frame.data[cmd_frame.data_length], &value, sizeof(CMD_REG_DATA_REPLY_SIZE));
|
||||
cmd_frame.data_length += 2;
|
||||
cmd_frame.footer = CMD_FRAME_FOOTER;
|
||||
ESP_LOGD(TAG, "Sending write register %4X command: %2X data = %4X", reg, cmd_frame.command, value);
|
||||
ESP_LOGV(TAG, "Sending write register %4X command: %2X data = %4X", reg, cmd_frame.command, value);
|
||||
this->send_cmd_from_array(cmd_frame);
|
||||
}
|
||||
|
||||
void LD2420Component::handle_cmd_error(uint8_t error) { ESP_LOGI(TAG, "Command failed: %s", ERR_MESSAGE[error]); }
|
||||
void LD2420Component::handle_cmd_error(uint8_t error) { ESP_LOGE(TAG, "Command failed: %s", ERR_MESSAGE[error]); }
|
||||
|
||||
int LD2420Component::get_gate_threshold_(uint8_t gate) {
|
||||
uint8_t error;
|
||||
@@ -619,7 +616,7 @@ int LD2420Component::get_gate_threshold_(uint8_t gate) {
|
||||
memcpy(&cmd_frame.data[cmd_frame.data_length], &CMD_GATE_STILL_THRESH[gate], sizeof(CMD_GATE_STILL_THRESH[gate]));
|
||||
cmd_frame.data_length += 2;
|
||||
cmd_frame.footer = CMD_FRAME_FOOTER;
|
||||
ESP_LOGD(TAG, "Sending read gate %d high/low theshold command: %2X", gate, cmd_frame.command);
|
||||
ESP_LOGV(TAG, "Sending read gate %d high/low threshold command: %2X", gate, cmd_frame.command);
|
||||
error = this->send_cmd_from_array(cmd_frame);
|
||||
if (error == 0) {
|
||||
this->current_config.move_thresh[gate] = cmd_reply_.data[0];
|
||||
@@ -644,7 +641,7 @@ int LD2420Component::get_min_max_distances_timeout_() {
|
||||
sizeof(CMD_TIMEOUT_REG)); // Register: global delay time
|
||||
cmd_frame.data_length += sizeof(CMD_TIMEOUT_REG);
|
||||
cmd_frame.footer = CMD_FRAME_FOOTER;
|
||||
ESP_LOGD(TAG, "Sending read gate min max and timeout command: %2X", cmd_frame.command);
|
||||
ESP_LOGV(TAG, "Sending read gate min max and timeout command: %2X", cmd_frame.command);
|
||||
error = this->send_cmd_from_array(cmd_frame);
|
||||
if (error == 0) {
|
||||
this->current_config.min_gate = (uint16_t) cmd_reply_.data[0];
|
||||
@@ -667,9 +664,9 @@ void LD2420Component::set_system_mode(uint16_t mode) {
|
||||
memcpy(&cmd_frame.data[cmd_frame.data_length], &unknown_parm, sizeof(unknown_parm));
|
||||
cmd_frame.data_length += sizeof(unknown_parm);
|
||||
cmd_frame.footer = CMD_FRAME_FOOTER;
|
||||
ESP_LOGD(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)
|
||||
set_mode_(mode);
|
||||
this->set_mode_(mode);
|
||||
}
|
||||
|
||||
void LD2420Component::get_firmware_version_() {
|
||||
@@ -679,7 +676,7 @@ void LD2420Component::get_firmware_version_() {
|
||||
cmd_frame.command = CMD_READ_VERSION;
|
||||
cmd_frame.footer = CMD_FRAME_FOOTER;
|
||||
|
||||
ESP_LOGD(TAG, "Sending read firmware version command: %2X", cmd_frame.command);
|
||||
ESP_LOGV(TAG, "Sending read firmware version command: %2X", cmd_frame.command);
|
||||
this->send_cmd_from_array(cmd_frame);
|
||||
}
|
||||
|
||||
@@ -712,7 +709,7 @@ void LD2420Component::set_min_max_distances_timeout(uint32_t max_gate_distance,
|
||||
cmd_frame.data_length += sizeof(timeout);
|
||||
cmd_frame.footer = CMD_FRAME_FOOTER;
|
||||
|
||||
ESP_LOGD(TAG, "Sending write gate min max and timeout command: %2X", cmd_frame.command);
|
||||
ESP_LOGV(TAG, "Sending write gate min max and timeout command: %2X", cmd_frame.command);
|
||||
this->send_cmd_from_array(cmd_frame);
|
||||
}
|
||||
|
||||
@@ -738,7 +735,7 @@ void LD2420Component::set_gate_threshold(uint8_t gate) {
|
||||
sizeof(this->new_config.still_thresh[gate]));
|
||||
cmd_frame.data_length += sizeof(this->new_config.still_thresh[gate]);
|
||||
cmd_frame.footer = CMD_FRAME_FOOTER;
|
||||
ESP_LOGD(TAG, "Sending set gate %4X sensitivity command: %2X", gate, cmd_frame.command);
|
||||
ESP_LOGV(TAG, "Sending set gate %4X sensitivity command: %2X", gate, cmd_frame.command);
|
||||
this->send_cmd_from_array(cmd_frame);
|
||||
}
|
||||
|
||||
|
||||
@@ -179,7 +179,7 @@ class LD2420Component : public Component, public uart::UARTDevice {
|
||||
void set_operating_mode(const std::string &state);
|
||||
void auto_calibrate_sensitivity();
|
||||
void update_radar_data(uint16_t const *gate_energy, uint8_t sample_number);
|
||||
uint8_t calc_checksum(void *data, size_t size);
|
||||
static uint8_t calc_checksum(void *data, size_t size);
|
||||
|
||||
RegConfigT current_config;
|
||||
RegConfigT new_config;
|
||||
@@ -222,7 +222,7 @@ class LD2420Component : public Component, public uart::UARTDevice {
|
||||
volatile bool ack;
|
||||
};
|
||||
|
||||
int get_firmware_int_(const char *version_string);
|
||||
static int get_firmware_int(const char *version_string);
|
||||
void get_firmware_version_();
|
||||
int get_gate_threshold_(uint8_t gate);
|
||||
void get_reg_value_(uint16_t reg);
|
||||
|
||||
@@ -6,7 +6,9 @@
|
||||
#ifdef USE_SENSOR
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#endif
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
#define highbyte(val) (uint8_t)((val) >> 8)
|
||||
#define lowbyte(val) (uint8_t)((val) &0xff)
|
||||
@@ -15,8 +17,9 @@ namespace esphome {
|
||||
namespace 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 NO_MAC = "08:05:04:03:02:01";
|
||||
static const char *const UNKNOWN_MAC = "unknown";
|
||||
static const char *const VERSION_FMT = "%u.%02X.%02X%02X%02X%02X";
|
||||
|
||||
// LD2450 UART Serial Commands
|
||||
static const uint8_t CMD_ENABLE_CONF = 0x00FF;
|
||||
@@ -96,18 +99,6 @@ static inline std::string get_direction(int16_t speed) {
|
||||
return STATIONARY;
|
||||
}
|
||||
|
||||
static inline std::string format_mac(uint8_t *buffer) {
|
||||
return str_snprintf("%02X:%02X:%02X:%02X:%02X:%02X", 17, buffer[10], buffer[11], buffer[12], buffer[13], buffer[14],
|
||||
buffer[15]);
|
||||
}
|
||||
|
||||
static inline std::string format_version(uint8_t *buffer) {
|
||||
return str_sprintf("%u.%02X.%02X%02X%02X%02X", buffer[13], buffer[12], buffer[17], buffer[16], buffer[15],
|
||||
buffer[14]);
|
||||
}
|
||||
|
||||
LD2450Component::LD2450Component() {}
|
||||
|
||||
void LD2450Component::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Running setup");
|
||||
#ifdef USE_NUMBER
|
||||
@@ -120,7 +111,7 @@ void LD2450Component::setup() {
|
||||
}
|
||||
|
||||
void LD2450Component::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "HLK-LD2450 Human motion tracking radar module:");
|
||||
ESP_LOGCONFIG(TAG, "LD2450:");
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
LOG_BINARY_SENSOR(" ", "TargetBinarySensor", this->target_binary_sensor_);
|
||||
LOG_BINARY_SENSOR(" ", "MovingTargetBinarySensor", this->moving_target_binary_sensor_);
|
||||
@@ -189,10 +180,10 @@ void LD2450Component::dump_config() {
|
||||
LOG_NUMBER(" ", "PresenceTimeoutNumber", this->presence_timeout_number_);
|
||||
#endif
|
||||
ESP_LOGCONFIG(TAG,
|
||||
" Throttle : %ums\n"
|
||||
" MAC Address : %s\n"
|
||||
" Firmware version : %s",
|
||||
this->throttle_, const_cast<char *>(this->mac_.c_str()), const_cast<char *>(this->version_.c_str()));
|
||||
" 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() {
|
||||
@@ -266,8 +257,7 @@ bool LD2450Component::get_timeout_status_(uint32_t check_millis) {
|
||||
if (this->timeout_ == 0) {
|
||||
this->timeout_ = ld2450::convert_seconds_to_ms(DEFAULT_PRESENCE_TIMEOUT);
|
||||
}
|
||||
auto current_millis = millis();
|
||||
return current_millis - check_millis >= this->timeout_;
|
||||
return App.get_loop_component_start_time() - check_millis >= this->timeout_;
|
||||
}
|
||||
|
||||
// Extract, store and publish zone details LD2450 buffer
|
||||
@@ -354,25 +344,24 @@ void LD2450Component::send_command_(uint8_t command, const uint8_t *command_valu
|
||||
// Header Target 1 Target 2 Target 3 End
|
||||
void LD2450Component::handle_periodic_data_(uint8_t *buffer, uint8_t len) {
|
||||
if (len < 29) { // header (4 bytes) + 8 x 3 target data + footer (2 bytes)
|
||||
ESP_LOGE(TAG, "Periodic data: invalid message length");
|
||||
ESP_LOGE(TAG, "Invalid message length");
|
||||
return;
|
||||
}
|
||||
if (buffer[0] != 0xAA || buffer[1] != 0xFF || buffer[2] != 0x03 || buffer[3] != 0x00) { // header
|
||||
ESP_LOGE(TAG, "Periodic data: invalid message header");
|
||||
ESP_LOGE(TAG, "Invalid message header");
|
||||
return;
|
||||
}
|
||||
if (buffer[len - 2] != 0x55 || buffer[len - 1] != 0xCC) { // footer
|
||||
ESP_LOGE(TAG, "Periodic data: invalid message footer");
|
||||
ESP_LOGE(TAG, "Invalid message footer");
|
||||
return;
|
||||
}
|
||||
|
||||
auto current_millis = millis();
|
||||
if (current_millis - this->last_periodic_millis_ < this->throttle_) {
|
||||
if (App.get_loop_component_start_time() - this->last_periodic_millis_ < this->throttle_) {
|
||||
ESP_LOGV(TAG, "Throttling: %d", this->throttle_);
|
||||
return;
|
||||
}
|
||||
|
||||
this->last_periodic_millis_ = current_millis;
|
||||
this->last_periodic_millis_ = App.get_loop_component_start_time();
|
||||
|
||||
int16_t target_count = 0;
|
||||
int16_t still_target_count = 0;
|
||||
@@ -555,13 +544,13 @@ void LD2450Component::handle_periodic_data_(uint8_t *buffer, uint8_t len) {
|
||||
#ifdef USE_SENSOR
|
||||
// For presence timeout check
|
||||
if (target_count > 0) {
|
||||
this->presence_millis_ = millis();
|
||||
this->presence_millis_ = App.get_loop_component_start_time();
|
||||
}
|
||||
if (moving_target_count > 0) {
|
||||
this->moving_presence_millis_ = millis();
|
||||
this->moving_presence_millis_ = App.get_loop_component_start_time();
|
||||
}
|
||||
if (still_target_count > 0) {
|
||||
this->still_presence_millis_ = millis();
|
||||
this->still_presence_millis_ = App.get_loop_component_start_time();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -569,31 +558,31 @@ void LD2450Component::handle_periodic_data_(uint8_t *buffer, uint8_t len) {
|
||||
bool LD2450Component::handle_ack_data_(uint8_t *buffer, uint8_t len) {
|
||||
ESP_LOGV(TAG, "Handling ack data for command %02X", buffer[COMMAND]);
|
||||
if (len < 10) {
|
||||
ESP_LOGE(TAG, "Ack data: invalid length");
|
||||
ESP_LOGE(TAG, "Invalid ack length");
|
||||
return true;
|
||||
}
|
||||
if (buffer[0] != 0xFD || buffer[1] != 0xFC || buffer[2] != 0xFB || buffer[3] != 0xFA) { // frame header
|
||||
ESP_LOGE(TAG, "Ack data: invalid header (command %02X)", buffer[COMMAND]);
|
||||
ESP_LOGE(TAG, "Invalid ack header (command %02X)", buffer[COMMAND]);
|
||||
return true;
|
||||
}
|
||||
if (buffer[COMMAND_STATUS] != 0x01) {
|
||||
ESP_LOGE(TAG, "Ack data: invalid status");
|
||||
ESP_LOGE(TAG, "Invalid ack status");
|
||||
return true;
|
||||
}
|
||||
if (buffer[8] || buffer[9]) {
|
||||
ESP_LOGE(TAG, "Ack data: last buffer was %u, %u", buffer[8], buffer[9]);
|
||||
ESP_LOGE(TAG, "Last buffer was %u, %u", buffer[8], buffer[9]);
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (buffer[COMMAND]) {
|
||||
case lowbyte(CMD_ENABLE_CONF):
|
||||
ESP_LOGV(TAG, "Got enable conf command");
|
||||
ESP_LOGV(TAG, "Enable conf command");
|
||||
break;
|
||||
case lowbyte(CMD_DISABLE_CONF):
|
||||
ESP_LOGV(TAG, "Got disable conf command");
|
||||
ESP_LOGV(TAG, "Disable conf command");
|
||||
break;
|
||||
case lowbyte(CMD_SET_BAUD_RATE):
|
||||
ESP_LOGV(TAG, "Got baud rate change command");
|
||||
ESP_LOGV(TAG, "Baud rate change command");
|
||||
#ifdef USE_SELECT
|
||||
if (this->baud_rate_select_ != nullptr) {
|
||||
ESP_LOGV(TAG, "Change baud rate to %s", this->baud_rate_select_->state.c_str());
|
||||
@@ -601,7 +590,7 @@ bool LD2450Component::handle_ack_data_(uint8_t *buffer, uint8_t len) {
|
||||
#endif
|
||||
break;
|
||||
case lowbyte(CMD_VERSION):
|
||||
this->version_ = ld2450::format_version(buffer);
|
||||
this->version_ = str_sprintf(VERSION_FMT, buffer[13], buffer[12], buffer[17], buffer[16], buffer[15], buffer[14]);
|
||||
ESP_LOGV(TAG, "Firmware version: %s", this->version_.c_str());
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
if (this->version_text_sensor_ != nullptr) {
|
||||
@@ -613,7 +602,7 @@ bool LD2450Component::handle_ack_data_(uint8_t *buffer, uint8_t len) {
|
||||
if (len < 20) {
|
||||
return false;
|
||||
}
|
||||
this->mac_ = ld2450::format_mac(buffer);
|
||||
this->mac_ = format_mac_address_pretty(&buffer[10]);
|
||||
ESP_LOGV(TAG, "MAC address: %s", this->mac_.c_str());
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
if (this->mac_text_sensor_ != nullptr) {
|
||||
@@ -627,10 +616,10 @@ bool LD2450Component::handle_ack_data_(uint8_t *buffer, uint8_t len) {
|
||||
#endif
|
||||
break;
|
||||
case lowbyte(CMD_BLUETOOTH):
|
||||
ESP_LOGV(TAG, "Got Bluetooth command");
|
||||
ESP_LOGV(TAG, "Bluetooth command");
|
||||
break;
|
||||
case lowbyte(CMD_SINGLE_TARGET_MODE):
|
||||
ESP_LOGV(TAG, "Got single target conf command");
|
||||
ESP_LOGV(TAG, "Single target conf command");
|
||||
#ifdef USE_SWITCH
|
||||
if (this->multi_target_switch_ != nullptr) {
|
||||
this->multi_target_switch_->publish_state(false);
|
||||
@@ -638,7 +627,7 @@ bool LD2450Component::handle_ack_data_(uint8_t *buffer, uint8_t len) {
|
||||
#endif
|
||||
break;
|
||||
case lowbyte(CMD_MULTI_TARGET_MODE):
|
||||
ESP_LOGV(TAG, "Got multi target conf command");
|
||||
ESP_LOGV(TAG, "Multi target conf command");
|
||||
#ifdef USE_SWITCH
|
||||
if (this->multi_target_switch_ != nullptr) {
|
||||
this->multi_target_switch_->publish_state(true);
|
||||
@@ -646,7 +635,7 @@ bool LD2450Component::handle_ack_data_(uint8_t *buffer, uint8_t len) {
|
||||
#endif
|
||||
break;
|
||||
case lowbyte(CMD_QUERY_TARGET_MODE):
|
||||
ESP_LOGV(TAG, "Got query target tracking mode command");
|
||||
ESP_LOGV(TAG, "Query target tracking mode command");
|
||||
#ifdef USE_SWITCH
|
||||
if (this->multi_target_switch_ != nullptr) {
|
||||
this->multi_target_switch_->publish_state(buffer[10] == 0x02);
|
||||
@@ -654,7 +643,7 @@ bool LD2450Component::handle_ack_data_(uint8_t *buffer, uint8_t len) {
|
||||
#endif
|
||||
break;
|
||||
case lowbyte(CMD_QUERY_ZONE):
|
||||
ESP_LOGV(TAG, "Got query zone conf command");
|
||||
ESP_LOGV(TAG, "Query zone conf command");
|
||||
this->zone_type_ = std::stoi(std::to_string(buffer[10]), nullptr, 16);
|
||||
this->publish_zone_type();
|
||||
#ifdef USE_SELECT
|
||||
@@ -674,7 +663,7 @@ bool LD2450Component::handle_ack_data_(uint8_t *buffer, uint8_t len) {
|
||||
this->process_zone_(buffer);
|
||||
break;
|
||||
case lowbyte(CMD_SET_ZONE):
|
||||
ESP_LOGV(TAG, "Got set zone conf command");
|
||||
ESP_LOGV(TAG, "Set zone conf command");
|
||||
this->query_zone_info();
|
||||
break;
|
||||
default:
|
||||
|
||||
@@ -141,7 +141,6 @@ class LD2450Component : public Component, public uart::UARTDevice {
|
||||
#endif
|
||||
|
||||
public:
|
||||
LD2450Component();
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
void loop() override;
|
||||
@@ -197,17 +196,17 @@ class LD2450Component : public Component, public uart::UARTDevice {
|
||||
bool get_timeout_status_(uint32_t check_millis);
|
||||
uint8_t count_targets_in_zone_(const Zone &zone, bool is_moving);
|
||||
|
||||
Target target_info_[MAX_TARGETS];
|
||||
Zone zone_config_[MAX_ZONES];
|
||||
uint8_t buffer_pos_ = 0; // where to resume processing/populating buffer
|
||||
uint8_t buffer_data_[MAX_LINE_LENGTH];
|
||||
uint32_t last_periodic_millis_ = 0;
|
||||
uint32_t presence_millis_ = 0;
|
||||
uint32_t still_presence_millis_ = 0;
|
||||
uint32_t moving_presence_millis_ = 0;
|
||||
uint16_t throttle_ = 0;
|
||||
uint16_t timeout_ = 5;
|
||||
uint8_t buffer_pos_ = 0; // where to resume processing/populating buffer
|
||||
uint8_t buffer_data_[MAX_LINE_LENGTH];
|
||||
uint8_t zone_type_ = 0;
|
||||
Target target_info_[MAX_TARGETS];
|
||||
Zone zone_config_[MAX_ZONES];
|
||||
std::string version_{};
|
||||
std::string mac_{};
|
||||
#ifdef USE_NUMBER
|
||||
|
||||
@@ -38,8 +38,8 @@ from esphome.const import (
|
||||
CONF_WHITE,
|
||||
)
|
||||
from esphome.core import CORE, coroutine_with_priority
|
||||
from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity
|
||||
from esphome.cpp_generator import MockObjClass
|
||||
from esphome.cpp_helpers import setup_entity
|
||||
|
||||
from .automation import LIGHT_STATE_SCHEMA
|
||||
from .effects import (
|
||||
@@ -110,6 +110,8 @@ LIGHT_SCHEMA = (
|
||||
)
|
||||
)
|
||||
|
||||
LIGHT_SCHEMA.add_extra(entity_duplicate_validator("light"))
|
||||
|
||||
BINARY_LIGHT_SCHEMA = LIGHT_SCHEMA.extend(
|
||||
{
|
||||
cv.Optional(CONF_EFFECTS): validate_effects(BINARY_EFFECTS),
|
||||
@@ -207,7 +209,7 @@ def validate_color_temperature_channels(value):
|
||||
|
||||
|
||||
async def setup_light_core_(light_var, output_var, config):
|
||||
await setup_entity(light_var, config)
|
||||
await setup_entity(light_var, config, "light")
|
||||
|
||||
cg.add(light_var.set_restore_mode(config[CONF_RESTORE_MODE]))
|
||||
|
||||
|
||||
@@ -14,8 +14,8 @@ from esphome.const import (
|
||||
CONF_WEB_SERVER,
|
||||
)
|
||||
from esphome.core import CORE, coroutine_with_priority
|
||||
from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity
|
||||
from esphome.cpp_generator import MockObjClass
|
||||
from esphome.cpp_helpers import setup_entity
|
||||
|
||||
CODEOWNERS = ["@esphome/core"]
|
||||
IS_PLATFORM_COMPONENT = True
|
||||
@@ -67,6 +67,9 @@ _LOCK_SCHEMA = (
|
||||
)
|
||||
|
||||
|
||||
_LOCK_SCHEMA.add_extra(entity_duplicate_validator("lock"))
|
||||
|
||||
|
||||
def lock_schema(
|
||||
class_: MockObjClass = cv.UNDEFINED,
|
||||
*,
|
||||
@@ -94,7 +97,7 @@ LOCK_SCHEMA.add_extra(cv.deprecated_schema_constant("lock"))
|
||||
|
||||
|
||||
async def _setup_lock_core(var, config):
|
||||
await setup_entity(var, config)
|
||||
await setup_entity(var, config, "lock")
|
||||
|
||||
for conf in config.get(CONF_ON_LOCK, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
|
||||
@@ -48,6 +48,11 @@ void HOT Logger::log_vprintf_(uint8_t level, const char *tag, int line, const ch
|
||||
// For non-main tasks, queue the message for callbacks - but only if we have any callbacks registered
|
||||
message_sent =
|
||||
this->log_buffer_->send_message_thread_safe(level, tag, static_cast<uint16_t>(line), current_task, format, args);
|
||||
if (message_sent) {
|
||||
// Enable logger loop to process the buffered message
|
||||
// This is safe to call from any context including ISRs
|
||||
this->enable_loop_soon_any_context();
|
||||
}
|
||||
#endif // USE_ESPHOME_TASK_LOG_BUFFER
|
||||
|
||||
// Emergency console logging for non-main tasks when ring buffer is full or disabled
|
||||
@@ -139,6 +144,10 @@ Logger::Logger(uint32_t baud_rate, size_t tx_buffer_size) : baud_rate_(baud_rate
|
||||
#ifdef USE_ESPHOME_TASK_LOG_BUFFER
|
||||
void Logger::init_log_buffer(size_t total_buffer_size) {
|
||||
this->log_buffer_ = esphome::make_unique<logger::TaskLogBuffer>(total_buffer_size);
|
||||
|
||||
// Start with loop disabled when using task buffer (unless using USB CDC)
|
||||
// The loop will be enabled automatically when messages arrive
|
||||
this->disable_loop_when_buffer_empty_();
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -189,6 +198,10 @@ void Logger::loop() {
|
||||
this->write_msg_(this->tx_buffer_);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// No messages to process, disable loop if appropriate
|
||||
// This reduces overhead when there's no async logging activity
|
||||
this->disable_loop_when_buffer_empty_();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -358,6 +358,26 @@ class Logger : public Component {
|
||||
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);
|
||||
}
|
||||
|
||||
#ifdef USE_ESP32
|
||||
// Disable loop when task buffer is empty (with USB CDC check)
|
||||
inline void disable_loop_when_buffer_empty_() {
|
||||
// Thread safety note: This is safe even if another task calls enable_loop_soon_any_context()
|
||||
// concurrently. If that happens between our check and disable_loop(), the enable request
|
||||
// will be processed on the next main loop iteration since:
|
||||
// - disable_loop() takes effect immediately
|
||||
// - enable_loop_soon_any_context() sets a pending flag that's checked at loop start
|
||||
#if defined(USE_LOGGER_USB_CDC) && defined(USE_ARDUINO)
|
||||
// Only disable if not using USB CDC (which needs loop for connection detection)
|
||||
if (this->uart_ != UART_SELECTION_USB_CDC) {
|
||||
this->disable_loop();
|
||||
}
|
||||
#else
|
||||
// No USB CDC support, always safe to disable
|
||||
this->disable_loop();
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
};
|
||||
extern Logger *global_logger; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
||||
|
||||
@@ -466,7 +466,7 @@ LVGL_SCHEMA = cv.All(
|
||||
): lvalid.lv_color,
|
||||
cv.Optional(df.CONF_THEME): cv.Schema(
|
||||
{
|
||||
cv.Optional(name): obj_schema(w)
|
||||
cv.Optional(name): obj_schema(w).extend(FULL_STYLE_SCHEMA)
|
||||
for name, w in WIDGET_TYPES.items()
|
||||
}
|
||||
),
|
||||
|
||||
@@ -21,7 +21,7 @@ from esphome.core.config import StartupTrigger
|
||||
from esphome.schema_extractors import SCHEMA_EXTRACT
|
||||
|
||||
from . import defines as df, lv_validation as lvalid
|
||||
from .defines import CONF_TIME_FORMAT, LV_GRAD_DIR
|
||||
from .defines import CONF_TIME_FORMAT, LV_GRAD_DIR, TYPE_GRID
|
||||
from .helpers import add_lv_use, requires_component, validate_printf
|
||||
from .lv_validation import lv_color, lv_font, lv_gradient, lv_image, opacity
|
||||
from .lvcode import LvglComponent, lv_event_t_ptr
|
||||
@@ -349,7 +349,60 @@ def obj_schema(widget_type: WidgetType):
|
||||
)
|
||||
|
||||
|
||||
def _validate_grid_layout(config):
|
||||
layout = config[df.CONF_LAYOUT]
|
||||
rows = len(layout[df.CONF_GRID_ROWS])
|
||||
columns = len(layout[df.CONF_GRID_COLUMNS])
|
||||
used_cells = [[None] * columns for _ in range(rows)]
|
||||
for index, widget in enumerate(config[df.CONF_WIDGETS]):
|
||||
_, w = next(iter(widget.items()))
|
||||
if (df.CONF_GRID_CELL_COLUMN_POS in w) != (df.CONF_GRID_CELL_ROW_POS in w):
|
||||
# pylint: disable=raise-missing-from
|
||||
raise cv.Invalid(
|
||||
"Both row and column positions must be specified, or both omitted",
|
||||
[df.CONF_WIDGETS, index],
|
||||
)
|
||||
if df.CONF_GRID_CELL_ROW_POS in w:
|
||||
row = w[df.CONF_GRID_CELL_ROW_POS]
|
||||
column = w[df.CONF_GRID_CELL_COLUMN_POS]
|
||||
else:
|
||||
try:
|
||||
row, column = next(
|
||||
(r_idx, c_idx)
|
||||
for r_idx, row in enumerate(used_cells)
|
||||
for c_idx, value in enumerate(row)
|
||||
if value is None
|
||||
)
|
||||
except StopIteration:
|
||||
# pylint: disable=raise-missing-from
|
||||
raise cv.Invalid(
|
||||
"No free cells available in grid layout", [df.CONF_WIDGETS, index]
|
||||
)
|
||||
w[df.CONF_GRID_CELL_ROW_POS] = row
|
||||
w[df.CONF_GRID_CELL_COLUMN_POS] = column
|
||||
|
||||
for i in range(w[df.CONF_GRID_CELL_ROW_SPAN]):
|
||||
for j in range(w[df.CONF_GRID_CELL_COLUMN_SPAN]):
|
||||
if row + i >= rows or column + j >= columns:
|
||||
# pylint: disable=raise-missing-from
|
||||
raise cv.Invalid(
|
||||
f"Cell at {row}/{column} span {w[df.CONF_GRID_CELL_ROW_SPAN]}x{w[df.CONF_GRID_CELL_COLUMN_SPAN]} "
|
||||
f"exceeds grid size {rows}x{columns}",
|
||||
[df.CONF_WIDGETS, index],
|
||||
)
|
||||
if used_cells[row + i][column + j] is not None:
|
||||
# pylint: disable=raise-missing-from
|
||||
raise cv.Invalid(
|
||||
f"Cell span {row + i}/{column + j} already occupied by widget at index {used_cells[row + i][column + j]}",
|
||||
[df.CONF_WIDGETS, index],
|
||||
)
|
||||
used_cells[row + i][column + j] = index
|
||||
|
||||
return config
|
||||
|
||||
|
||||
LAYOUT_SCHEMAS = {}
|
||||
LAYOUT_VALIDATORS = {TYPE_GRID: _validate_grid_layout}
|
||||
|
||||
ALIGN_TO_SCHEMA = {
|
||||
cv.Optional(df.CONF_ALIGN_TO): cv.Schema(
|
||||
@@ -402,8 +455,8 @@ LAYOUT_SCHEMA = {
|
||||
}
|
||||
|
||||
GRID_CELL_SCHEMA = {
|
||||
cv.Required(df.CONF_GRID_CELL_ROW_POS): cv.positive_int,
|
||||
cv.Required(df.CONF_GRID_CELL_COLUMN_POS): cv.positive_int,
|
||||
cv.Optional(df.CONF_GRID_CELL_ROW_POS): cv.positive_int,
|
||||
cv.Optional(df.CONF_GRID_CELL_COLUMN_POS): cv.positive_int,
|
||||
cv.Optional(df.CONF_GRID_CELL_ROW_SPAN, default=1): cv.positive_int,
|
||||
cv.Optional(df.CONF_GRID_CELL_COLUMN_SPAN, default=1): cv.positive_int,
|
||||
cv.Optional(df.CONF_GRID_CELL_X_ALIGN): grid_alignments,
|
||||
@@ -474,7 +527,10 @@ def container_validator(schema, widget_type: WidgetType):
|
||||
result = result.extend(
|
||||
LAYOUT_SCHEMAS.get(ltype.lower(), LAYOUT_SCHEMAS[df.TYPE_NONE])
|
||||
)
|
||||
return result(value)
|
||||
value = result(value)
|
||||
if layout_validator := LAYOUT_VALIDATORS.get(ltype):
|
||||
value = layout_validator(value)
|
||||
return value
|
||||
|
||||
return validator
|
||||
|
||||
|
||||
@@ -11,9 +11,9 @@ from esphome.const import (
|
||||
CONF_VOLUME,
|
||||
)
|
||||
from esphome.core import CORE
|
||||
from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity
|
||||
from esphome.coroutine import coroutine_with_priority
|
||||
from esphome.cpp_generator import MockObjClass
|
||||
from esphome.cpp_helpers import setup_entity
|
||||
|
||||
CODEOWNERS = ["@jesserockz"]
|
||||
|
||||
@@ -81,7 +81,7 @@ IsAnnouncingCondition = media_player_ns.class_(
|
||||
|
||||
|
||||
async def setup_media_player_core_(var, config):
|
||||
await setup_entity(var, config)
|
||||
await setup_entity(var, config, "media_player")
|
||||
for conf in config.get(CONF_ON_STATE, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [], conf)
|
||||
@@ -143,6 +143,8 @@ _MEDIA_PLAYER_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(
|
||||
}
|
||||
)
|
||||
|
||||
_MEDIA_PLAYER_SCHEMA.add_extra(entity_duplicate_validator("media_player"))
|
||||
|
||||
|
||||
def media_player_schema(
|
||||
class_: MockObjClass,
|
||||
@@ -166,7 +168,6 @@ def media_player_schema(
|
||||
MEDIA_PLAYER_SCHEMA = media_player_schema(MediaPlayer)
|
||||
MEDIA_PLAYER_SCHEMA.add_extra(cv.deprecated_schema_constant("media_player"))
|
||||
|
||||
|
||||
MEDIA_PLAYER_ACTION_SCHEMA = automation.maybe_simple_id(
|
||||
cv.Schema(
|
||||
{
|
||||
|
||||
@@ -64,6 +64,14 @@ class ModbusDevice {
|
||||
this->parent_->send(this->address_, function, start_address, number_of_entities, payload_len, payload);
|
||||
}
|
||||
void send_raw(const std::vector<uint8_t> &payload) { this->parent_->send_raw(payload); }
|
||||
void send_error(uint8_t function_code, uint8_t exception_code) {
|
||||
std::vector<uint8_t> error_response;
|
||||
error_response.reserve(3);
|
||||
error_response.push_back(this->address_);
|
||||
error_response.push_back(function_code | 0x80);
|
||||
error_response.push_back(exception_code);
|
||||
this->send_raw(error_response);
|
||||
}
|
||||
// If more than one device is connected block sending a new command before a response is received
|
||||
bool waiting_for_response() { return parent_->waiting_for_response != 0; }
|
||||
|
||||
|
||||
@@ -112,6 +112,22 @@ TYPE_REGISTER_MAP = {
|
||||
"FP32_R": 2,
|
||||
}
|
||||
|
||||
CPP_TYPE_REGISTER_MAP = {
|
||||
"RAW": cg.uint16,
|
||||
"U_WORD": cg.uint16,
|
||||
"S_WORD": cg.int16,
|
||||
"U_DWORD": cg.uint32,
|
||||
"U_DWORD_R": cg.uint32,
|
||||
"S_DWORD": cg.int32,
|
||||
"S_DWORD_R": cg.int32,
|
||||
"U_QWORD": cg.uint64,
|
||||
"U_QWORD_R": cg.uint64,
|
||||
"S_QWORD": cg.int64,
|
||||
"S_QWORD_R": cg.int64,
|
||||
"FP32": cg.float_,
|
||||
"FP32_R": cg.float_,
|
||||
}
|
||||
|
||||
ModbusCommandSentTrigger = modbus_controller_ns.class_(
|
||||
"ModbusCommandSentTrigger", automation.Trigger.template(cg.int_, cg.int_)
|
||||
)
|
||||
@@ -285,21 +301,24 @@ async def to_code(config):
|
||||
cg.add(var.set_offline_skip_updates(config[CONF_OFFLINE_SKIP_UPDATES]))
|
||||
if CONF_SERVER_REGISTERS in config:
|
||||
for server_register in config[CONF_SERVER_REGISTERS]:
|
||||
server_register_var = cg.new_Pvariable(
|
||||
server_register[CONF_ID],
|
||||
server_register[CONF_ADDRESS],
|
||||
server_register[CONF_VALUE_TYPE],
|
||||
TYPE_REGISTER_MAP[server_register[CONF_VALUE_TYPE]],
|
||||
)
|
||||
cpp_type = CPP_TYPE_REGISTER_MAP[server_register[CONF_VALUE_TYPE]]
|
||||
cg.add(
|
||||
var.add_server_register(
|
||||
cg.new_Pvariable(
|
||||
server_register[CONF_ID],
|
||||
server_register[CONF_ADDRESS],
|
||||
server_register[CONF_VALUE_TYPE],
|
||||
TYPE_REGISTER_MAP[server_register[CONF_VALUE_TYPE]],
|
||||
await cg.process_lambda(
|
||||
server_register[CONF_READ_LAMBDA],
|
||||
[],
|
||||
return_type=cg.float_,
|
||||
),
|
||||
)
|
||||
server_register_var.set_read_lambda(
|
||||
cg.TemplateArguments(cpp_type),
|
||||
await cg.process_lambda(
|
||||
server_register[CONF_READ_LAMBDA],
|
||||
[(cg.uint16, "address")],
|
||||
return_type=cpp_type,
|
||||
),
|
||||
)
|
||||
)
|
||||
cg.add(var.add_server_register(server_register_var))
|
||||
await register_modbus_device(var, config)
|
||||
for conf in config.get(CONF_ON_COMMAND_SENT, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
|
||||
@@ -117,12 +117,17 @@ void ModbusController::on_modbus_read_registers(uint8_t function_code, uint16_t
|
||||
bool found = false;
|
||||
for (auto *server_register : this->server_registers_) {
|
||||
if (server_register->address == current_address) {
|
||||
float value = server_register->read_lambda();
|
||||
if (!server_register->read_lambda) {
|
||||
break;
|
||||
}
|
||||
int64_t value = server_register->read_lambda();
|
||||
ESP_LOGD(TAG, "Matched register. Address: 0x%02X. Value type: %zu. Register count: %u. Value: %s.",
|
||||
server_register->address, static_cast<size_t>(server_register->value_type),
|
||||
server_register->register_count, server_register->format_value(value).c_str());
|
||||
|
||||
ESP_LOGD(TAG, "Matched register. Address: 0x%02X. Value type: %zu. Register count: %u. Value: %0.1f.",
|
||||
server_register->address, static_cast<uint8_t>(server_register->value_type),
|
||||
server_register->register_count, value);
|
||||
std::vector<uint16_t> payload = float_to_payload(value, server_register->value_type);
|
||||
std::vector<uint16_t> payload;
|
||||
payload.reserve(server_register->register_count * 2);
|
||||
number_to_payload(payload, value, server_register->value_type);
|
||||
sixteen_bit_response.insert(sixteen_bit_response.end(), payload.cbegin(), payload.cend());
|
||||
current_address += server_register->register_count;
|
||||
found = true;
|
||||
@@ -132,11 +137,7 @@ void ModbusController::on_modbus_read_registers(uint8_t function_code, uint16_t
|
||||
|
||||
if (!found) {
|
||||
ESP_LOGW(TAG, "Could not match any register to address %02X. Sending exception response.", current_address);
|
||||
std::vector<uint8_t> error_response;
|
||||
error_response.push_back(this->address_);
|
||||
error_response.push_back(0x81);
|
||||
error_response.push_back(0x02);
|
||||
this->send_raw(error_response);
|
||||
send_error(function_code, 0x02);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,6 +63,10 @@ enum class SensorValueType : uint8_t {
|
||||
FP32_R = 0xD
|
||||
};
|
||||
|
||||
inline bool value_type_is_float(SensorValueType v) {
|
||||
return v == SensorValueType::FP32 || v == SensorValueType::FP32_R;
|
||||
}
|
||||
|
||||
inline ModbusFunctionCode modbus_register_read_function(ModbusRegisterType reg_type) {
|
||||
switch (reg_type) {
|
||||
case ModbusRegisterType::COIL:
|
||||
@@ -253,18 +257,53 @@ class SensorItem {
|
||||
};
|
||||
|
||||
class ServerRegister {
|
||||
using ReadLambda = std::function<int64_t()>;
|
||||
|
||||
public:
|
||||
ServerRegister(uint16_t address, SensorValueType value_type, uint8_t register_count,
|
||||
std::function<float()> read_lambda) {
|
||||
ServerRegister(uint16_t address, SensorValueType value_type, uint8_t register_count) {
|
||||
this->address = address;
|
||||
this->value_type = value_type;
|
||||
this->register_count = register_count;
|
||||
this->read_lambda = std::move(read_lambda);
|
||||
}
|
||||
|
||||
template<typename T> void set_read_lambda(const std::function<T(uint16_t address)> &&user_read_lambda) {
|
||||
this->read_lambda = [this, user_read_lambda]() -> int64_t {
|
||||
T user_value = user_read_lambda(this->address);
|
||||
if constexpr (std::is_same_v<T, float>) {
|
||||
return bit_cast<uint32_t>(user_value);
|
||||
} else {
|
||||
return static_cast<int64_t>(user_value);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Formats a raw value into a string representation based on the value type for debugging
|
||||
std::string format_value(int64_t value) const {
|
||||
switch (this->value_type) {
|
||||
case SensorValueType::U_WORD:
|
||||
case SensorValueType::U_DWORD:
|
||||
case SensorValueType::U_DWORD_R:
|
||||
case SensorValueType::U_QWORD:
|
||||
case SensorValueType::U_QWORD_R:
|
||||
return std::to_string(static_cast<uint64_t>(value));
|
||||
case SensorValueType::S_WORD:
|
||||
case SensorValueType::S_DWORD:
|
||||
case SensorValueType::S_DWORD_R:
|
||||
case SensorValueType::S_QWORD:
|
||||
case SensorValueType::S_QWORD_R:
|
||||
return std::to_string(value);
|
||||
case SensorValueType::FP32_R:
|
||||
case SensorValueType::FP32:
|
||||
return str_sprintf("%.1f", bit_cast<float>(static_cast<uint32_t>(value)));
|
||||
default:
|
||||
return std::to_string(value);
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t address{0};
|
||||
SensorValueType value_type{SensorValueType::RAW};
|
||||
uint8_t register_count{0};
|
||||
std::function<float()> read_lambda;
|
||||
ReadLambda read_lambda;
|
||||
};
|
||||
|
||||
// ModbusController::create_register_ranges_ tries to optimize register range
|
||||
@@ -444,7 +483,7 @@ class ModbusController : public PollingComponent, public modbus::ModbusDevice {
|
||||
void on_modbus_data(const std::vector<uint8_t> &data) override;
|
||||
/// called when a modbus error response was received
|
||||
void on_modbus_error(uint8_t function_code, uint8_t exception_code) override;
|
||||
/// called when a modbus request (function code 3 or 4) was parsed without errors
|
||||
/// called when a modbus request (function code 0x03 or 0x04) was parsed without errors
|
||||
void on_modbus_read_registers(uint8_t function_code, uint16_t start_address, uint16_t number_of_registers) final;
|
||||
/// default delegate called by process_modbus_data when a response has retrieved from the incoming queue
|
||||
void on_register_data(ModbusRegisterType register_type, uint16_t start_address, const std::vector<uint8_t> &data);
|
||||
@@ -529,7 +568,7 @@ inline float payload_to_float(const std::vector<uint8_t> &data, const SensorItem
|
||||
int64_t number = payload_to_number(data, item.sensor_value_type, item.offset, item.bitmask);
|
||||
|
||||
float float_value;
|
||||
if (item.sensor_value_type == SensorValueType::FP32 || item.sensor_value_type == SensorValueType::FP32_R) {
|
||||
if (value_type_is_float(item.sensor_value_type)) {
|
||||
float_value = bit_cast<float>(static_cast<uint32_t>(number));
|
||||
} else {
|
||||
float_value = static_cast<float>(number);
|
||||
@@ -541,7 +580,7 @@ inline float payload_to_float(const std::vector<uint8_t> &data, const SensorItem
|
||||
inline std::vector<uint16_t> float_to_payload(float value, SensorValueType value_type) {
|
||||
int64_t val;
|
||||
|
||||
if (value_type == SensorValueType::FP32 || value_type == SensorValueType::FP32_R) {
|
||||
if (value_type_is_float(value_type)) {
|
||||
val = bit_cast<uint32_t>(value);
|
||||
} else {
|
||||
val = llroundf(value);
|
||||
|
||||
@@ -17,7 +17,8 @@ enum class MQTTClientDisconnectReason : int8_t {
|
||||
MQTT_MALFORMED_CREDENTIALS = 4,
|
||||
MQTT_NOT_AUTHORIZED = 5,
|
||||
ESP8266_NOT_ENOUGH_SPACE = 6,
|
||||
TLS_BAD_FINGERPRINT = 7
|
||||
TLS_BAD_FINGERPRINT = 7,
|
||||
DNS_RESOLVE_ERROR = 8
|
||||
};
|
||||
|
||||
/// internal struct for MQTT messages.
|
||||
|
||||
@@ -229,6 +229,8 @@ void MQTTClientComponent::check_dnslookup_() {
|
||||
if (this->dns_resolve_error_) {
|
||||
ESP_LOGW(TAG, "Couldn't resolve IP address for '%s'", this->credentials_.address.c_str());
|
||||
this->state_ = MQTT_CLIENT_DISCONNECTED;
|
||||
this->disconnect_reason_ = MQTTClientDisconnectReason::DNS_RESOLVE_ERROR;
|
||||
this->on_disconnect_.call(MQTTClientDisconnectReason::DNS_RESOLVE_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -698,7 +700,9 @@ void MQTTClientComponent::set_on_connect(mqtt_on_connect_callback_t &&callback)
|
||||
}
|
||||
|
||||
void MQTTClientComponent::set_on_disconnect(mqtt_on_disconnect_callback_t &&callback) {
|
||||
auto callback_copy = callback;
|
||||
this->mqtt_backend_.set_on_disconnect(std::forward<mqtt_on_disconnect_callback_t>(callback));
|
||||
this->on_disconnect_.add(std::move(callback_copy));
|
||||
}
|
||||
|
||||
#if ASYNC_TCP_SSL_ENABLED
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "esphome/components/network/ip_address.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
#if defined(USE_ESP32)
|
||||
#include "mqtt_backend_esp32.h"
|
||||
@@ -334,6 +335,7 @@ class MQTTClientComponent : public Component {
|
||||
uint32_t connect_begin_;
|
||||
uint32_t last_connected_{0};
|
||||
optional<MQTTClientDisconnectReason> disconnect_reason_{};
|
||||
CallbackManager<MQTTBackend::on_disconnect_callback_t> on_disconnect_;
|
||||
|
||||
bool publish_nan_as_none_{false};
|
||||
bool wait_for_connection_{false};
|
||||
|
||||
@@ -76,8 +76,8 @@ from esphome.const import (
|
||||
DEVICE_CLASS_WIND_SPEED,
|
||||
)
|
||||
from esphome.core import CORE, coroutine_with_priority
|
||||
from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity
|
||||
from esphome.cpp_generator import MockObjClass
|
||||
from esphome.cpp_helpers import setup_entity
|
||||
|
||||
CODEOWNERS = ["@esphome/core"]
|
||||
DEVICE_CLASSES = [
|
||||
@@ -207,6 +207,9 @@ _NUMBER_SCHEMA = (
|
||||
)
|
||||
|
||||
|
||||
_NUMBER_SCHEMA.add_extra(entity_duplicate_validator("number"))
|
||||
|
||||
|
||||
def number_schema(
|
||||
class_: MockObjClass,
|
||||
*,
|
||||
@@ -237,7 +240,7 @@ NUMBER_SCHEMA.add_extra(cv.deprecated_schema_constant("number"))
|
||||
async def setup_number_core_(
|
||||
var, config, *, min_value: float, max_value: float, step: float
|
||||
):
|
||||
await setup_entity(var, config)
|
||||
await setup_entity(var, config, "number")
|
||||
|
||||
cg.add(var.traits.set_min_value(min_value))
|
||||
cg.add(var.traits.set_max_value(max_value))
|
||||
|
||||
@@ -17,8 +17,8 @@ from esphome.const import (
|
||||
CONF_WEB_SERVER,
|
||||
)
|
||||
from esphome.core import CORE, coroutine_with_priority
|
||||
from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity
|
||||
from esphome.cpp_generator import MockObjClass
|
||||
from esphome.cpp_helpers import setup_entity
|
||||
|
||||
CODEOWNERS = ["@esphome/core"]
|
||||
IS_PLATFORM_COMPONENT = True
|
||||
@@ -65,6 +65,9 @@ _SELECT_SCHEMA = (
|
||||
)
|
||||
|
||||
|
||||
_SELECT_SCHEMA.add_extra(entity_duplicate_validator("select"))
|
||||
|
||||
|
||||
def select_schema(
|
||||
class_: MockObjClass,
|
||||
*,
|
||||
@@ -89,7 +92,7 @@ SELECT_SCHEMA.add_extra(cv.deprecated_schema_constant("select"))
|
||||
|
||||
|
||||
async def setup_select_core_(var, config, *, options: list[str]):
|
||||
await setup_entity(var, config)
|
||||
await setup_entity(var, config, "select")
|
||||
|
||||
cg.add(var.traits.set_options(options))
|
||||
|
||||
|
||||
@@ -101,8 +101,8 @@ from esphome.const import (
|
||||
ENTITY_CATEGORY_CONFIG,
|
||||
)
|
||||
from esphome.core import CORE, coroutine_with_priority
|
||||
from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity
|
||||
from esphome.cpp_generator import MockObjClass
|
||||
from esphome.cpp_helpers import setup_entity
|
||||
from esphome.util import Registry
|
||||
|
||||
CODEOWNERS = ["@esphome/core"]
|
||||
@@ -318,6 +318,8 @@ _SENSOR_SCHEMA = (
|
||||
)
|
||||
)
|
||||
|
||||
_SENSOR_SCHEMA.add_extra(entity_duplicate_validator("sensor"))
|
||||
|
||||
|
||||
def sensor_schema(
|
||||
class_: MockObjClass = cv.UNDEFINED,
|
||||
@@ -787,7 +789,7 @@ async def build_filters(config):
|
||||
|
||||
|
||||
async def setup_sensor_core_(var, config):
|
||||
await setup_entity(var, config)
|
||||
await setup_entity(var, config, "sensor")
|
||||
|
||||
if (device_class := config.get(CONF_DEVICE_CLASS)) is not None:
|
||||
cg.add(var.set_device_class(device_class))
|
||||
|
||||
@@ -23,16 +23,22 @@ std::string state_class_to_string(StateClass state_class) {
|
||||
Sensor::Sensor() : state(NAN), raw_state(NAN) {}
|
||||
|
||||
int8_t Sensor::get_accuracy_decimals() {
|
||||
if (this->accuracy_decimals_.has_value())
|
||||
return *this->accuracy_decimals_;
|
||||
if (this->sensor_flags_.has_accuracy_override)
|
||||
return this->accuracy_decimals_;
|
||||
return 0;
|
||||
}
|
||||
void Sensor::set_accuracy_decimals(int8_t accuracy_decimals) { this->accuracy_decimals_ = accuracy_decimals; }
|
||||
void Sensor::set_accuracy_decimals(int8_t accuracy_decimals) {
|
||||
this->accuracy_decimals_ = accuracy_decimals;
|
||||
this->sensor_flags_.has_accuracy_override = true;
|
||||
}
|
||||
|
||||
void Sensor::set_state_class(StateClass state_class) { this->state_class_ = state_class; }
|
||||
void Sensor::set_state_class(StateClass state_class) {
|
||||
this->state_class_ = state_class;
|
||||
this->sensor_flags_.has_state_class_override = true;
|
||||
}
|
||||
StateClass Sensor::get_state_class() {
|
||||
if (this->state_class_.has_value())
|
||||
return *this->state_class_;
|
||||
if (this->sensor_flags_.has_state_class_override)
|
||||
return this->state_class_;
|
||||
return StateClass::STATE_CLASS_NONE;
|
||||
}
|
||||
|
||||
|
||||
@@ -80,9 +80,9 @@ class Sensor : public EntityBase, public EntityBase_DeviceClass, public EntityBa
|
||||
* state changes to the database when they are published, even if the state is the
|
||||
* same as before.
|
||||
*/
|
||||
bool get_force_update() const { return force_update_; }
|
||||
bool get_force_update() const { return sensor_flags_.force_update; }
|
||||
/// Set force update mode.
|
||||
void set_force_update(bool force_update) { force_update_ = force_update; }
|
||||
void set_force_update(bool force_update) { sensor_flags_.force_update = force_update; }
|
||||
|
||||
/// Add a filter to the filter chain. Will be appended to the back.
|
||||
void add_filter(Filter *filter);
|
||||
@@ -155,9 +155,17 @@ class Sensor : public EntityBase, public EntityBase_DeviceClass, public EntityBa
|
||||
|
||||
Filter *filter_list_{nullptr}; ///< Store all active filters.
|
||||
|
||||
optional<int8_t> accuracy_decimals_; ///< Accuracy in decimals override
|
||||
optional<StateClass> state_class_{STATE_CLASS_NONE}; ///< State class override
|
||||
bool force_update_{false}; ///< Force update mode
|
||||
// Group small members together to avoid padding
|
||||
int8_t accuracy_decimals_{-1}; ///< Accuracy in decimals (-1 = not set)
|
||||
StateClass state_class_{STATE_CLASS_NONE}; ///< State class (STATE_CLASS_NONE = not set)
|
||||
|
||||
// Bit-packed flags for sensor-specific settings
|
||||
struct SensorFlags {
|
||||
uint8_t has_accuracy_override : 1;
|
||||
uint8_t has_state_class_override : 1;
|
||||
uint8_t force_update : 1;
|
||||
uint8_t reserved : 5; // Reserved for future use
|
||||
} sensor_flags_{};
|
||||
};
|
||||
|
||||
} // namespace sensor
|
||||
|
||||
@@ -20,8 +20,8 @@ from esphome.const import (
|
||||
DEVICE_CLASS_SWITCH,
|
||||
)
|
||||
from esphome.core import CORE, coroutine_with_priority
|
||||
from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity
|
||||
from esphome.cpp_generator import MockObjClass
|
||||
from esphome.cpp_helpers import setup_entity
|
||||
|
||||
CODEOWNERS = ["@esphome/core"]
|
||||
IS_PLATFORM_COMPONENT = True
|
||||
@@ -91,6 +91,9 @@ _SWITCH_SCHEMA = (
|
||||
)
|
||||
|
||||
|
||||
_SWITCH_SCHEMA.add_extra(entity_duplicate_validator("switch"))
|
||||
|
||||
|
||||
def switch_schema(
|
||||
class_: MockObjClass,
|
||||
*,
|
||||
@@ -131,7 +134,7 @@ SWITCH_SCHEMA.add_extra(cv.deprecated_schema_constant("switch"))
|
||||
|
||||
|
||||
async def setup_switch_core_(var, config):
|
||||
await setup_entity(var, config)
|
||||
await setup_entity(var, config, "switch")
|
||||
|
||||
if (inverted := config.get(CONF_INVERTED)) is not None:
|
||||
cg.add(var.set_inverted(inverted))
|
||||
|
||||
@@ -14,8 +14,8 @@ from esphome.const import (
|
||||
CONF_WEB_SERVER,
|
||||
)
|
||||
from esphome.core import CORE, coroutine_with_priority
|
||||
from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity
|
||||
from esphome.cpp_generator import MockObjClass
|
||||
from esphome.cpp_helpers import setup_entity
|
||||
|
||||
CODEOWNERS = ["@mauritskorse"]
|
||||
IS_PLATFORM_COMPONENT = True
|
||||
@@ -58,6 +58,9 @@ _TEXT_SCHEMA = (
|
||||
)
|
||||
|
||||
|
||||
_TEXT_SCHEMA.add_extra(entity_duplicate_validator("text"))
|
||||
|
||||
|
||||
def text_schema(
|
||||
class_: MockObjClass = cv.UNDEFINED,
|
||||
*,
|
||||
@@ -94,7 +97,7 @@ async def setup_text_core_(
|
||||
max_length: int | None,
|
||||
pattern: str | None,
|
||||
):
|
||||
await setup_entity(var, config)
|
||||
await setup_entity(var, config, "text")
|
||||
|
||||
cg.add(var.traits.set_min_length(min_length))
|
||||
cg.add(var.traits.set_max_length(max_length))
|
||||
|
||||
@@ -21,8 +21,8 @@ from esphome.const import (
|
||||
DEVICE_CLASS_TIMESTAMP,
|
||||
)
|
||||
from esphome.core import CORE, coroutine_with_priority
|
||||
from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity
|
||||
from esphome.cpp_generator import MockObjClass
|
||||
from esphome.cpp_helpers import setup_entity
|
||||
from esphome.util import Registry
|
||||
|
||||
DEVICE_CLASSES = [
|
||||
@@ -153,6 +153,9 @@ _TEXT_SENSOR_SCHEMA = (
|
||||
)
|
||||
|
||||
|
||||
_TEXT_SENSOR_SCHEMA.add_extra(entity_duplicate_validator("text_sensor"))
|
||||
|
||||
|
||||
def text_sensor_schema(
|
||||
class_: MockObjClass = cv.UNDEFINED,
|
||||
*,
|
||||
@@ -186,7 +189,7 @@ async def build_filters(config):
|
||||
|
||||
|
||||
async def setup_text_sensor_core_(var, config):
|
||||
await setup_entity(var, config)
|
||||
await setup_entity(var, config, "text_sensor")
|
||||
|
||||
if (device_class := config.get(CONF_DEVICE_CLASS)) is not None:
|
||||
cg.add(var.set_device_class(device_class))
|
||||
|
||||
@@ -15,8 +15,8 @@ from esphome.const import (
|
||||
ENTITY_CATEGORY_CONFIG,
|
||||
)
|
||||
from esphome.core import CORE, coroutine_with_priority
|
||||
from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity
|
||||
from esphome.cpp_generator import MockObjClass
|
||||
from esphome.cpp_helpers import setup_entity
|
||||
|
||||
CODEOWNERS = ["@jesserockz"]
|
||||
IS_PLATFORM_COMPONENT = True
|
||||
@@ -58,6 +58,9 @@ _UPDATE_SCHEMA = (
|
||||
)
|
||||
|
||||
|
||||
_UPDATE_SCHEMA.add_extra(entity_duplicate_validator("update"))
|
||||
|
||||
|
||||
def update_schema(
|
||||
class_: MockObjClass = cv.UNDEFINED,
|
||||
*,
|
||||
@@ -87,7 +90,7 @@ UPDATE_SCHEMA.add_extra(cv.deprecated_schema_constant("update"))
|
||||
|
||||
|
||||
async def setup_update_core_(var, config):
|
||||
await setup_entity(var, config)
|
||||
await setup_entity(var, config, "update")
|
||||
|
||||
if device_class_config := config.get(CONF_DEVICE_CLASS):
|
||||
cg.add(var.set_device_class(device_class_config))
|
||||
|
||||
@@ -22,8 +22,8 @@ from esphome.const import (
|
||||
DEVICE_CLASS_WATER,
|
||||
)
|
||||
from esphome.core import CORE, coroutine_with_priority
|
||||
from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity
|
||||
from esphome.cpp_generator import MockObjClass
|
||||
from esphome.cpp_helpers import setup_entity
|
||||
|
||||
IS_PLATFORM_COMPONENT = True
|
||||
|
||||
@@ -103,6 +103,9 @@ _VALVE_SCHEMA = (
|
||||
)
|
||||
|
||||
|
||||
_VALVE_SCHEMA.add_extra(entity_duplicate_validator("valve"))
|
||||
|
||||
|
||||
def valve_schema(
|
||||
class_: MockObjClass = cv.UNDEFINED,
|
||||
*,
|
||||
@@ -132,7 +135,7 @@ VALVE_SCHEMA.add_extra(cv.deprecated_schema_constant("valve"))
|
||||
|
||||
|
||||
async def _setup_valve_core(var, config):
|
||||
await setup_entity(var, config)
|
||||
await setup_entity(var, config, "valve")
|
||||
|
||||
if device_class_config := config.get(CONF_DEVICE_CLASS):
|
||||
cg.add(var.set_device_class(device_class_config))
|
||||
|
||||
@@ -741,11 +741,6 @@ void WiFiComponent::set_power_save_mode(WiFiPowerSaveMode power_save) { this->po
|
||||
|
||||
void WiFiComponent::set_passive_scan(bool passive) { this->passive_scan_ = passive; }
|
||||
|
||||
std::string WiFiComponent::format_mac_addr(const uint8_t *mac) {
|
||||
char buf[20];
|
||||
sprintf(buf, "%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
|
||||
return buf;
|
||||
}
|
||||
bool WiFiComponent::is_captive_portal_active_() {
|
||||
#ifdef USE_CAPTIVE_PORTAL
|
||||
return captive_portal::global_captive_portal != nullptr && captive_portal::global_captive_portal->is_active();
|
||||
|
||||
@@ -321,8 +321,6 @@ class WiFiComponent : public Component {
|
||||
int32_t get_wifi_channel();
|
||||
|
||||
protected:
|
||||
static std::string format_mac_addr(const uint8_t mac[6]);
|
||||
|
||||
#ifdef USE_WIFI_AP
|
||||
void setup_ap_config_();
|
||||
#endif // USE_WIFI_AP
|
||||
|
||||
@@ -550,7 +550,7 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_
|
||||
memcpy(buf, it.ssid, it.ssid_len);
|
||||
buf[it.ssid_len] = '\0';
|
||||
ESP_LOGV(TAG, "Connected ssid='%s' bssid=" LOG_SECRET("%s") " channel=%u, authmode=%s", buf,
|
||||
format_mac_addr(it.bssid).c_str(), it.channel, get_auth_mode_str(it.authmode));
|
||||
format_mac_address_pretty(it.bssid).c_str(), it.channel, get_auth_mode_str(it.authmode));
|
||||
#if USE_NETWORK_IPV6
|
||||
this->set_timeout(100, [] { WiFi.enableIPv6(); });
|
||||
#endif /* USE_NETWORK_IPV6 */
|
||||
@@ -566,7 +566,7 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_
|
||||
ESP_LOGW(TAG, "Disconnected ssid='%s' reason='Probe Request Unsuccessful'", buf);
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Disconnected ssid='%s' bssid=" LOG_SECRET("%s") " reason='%s'", buf,
|
||||
format_mac_addr(it.bssid).c_str(), get_disconnect_reason_str(it.reason));
|
||||
format_mac_address_pretty(it.bssid).c_str(), get_disconnect_reason_str(it.reason));
|
||||
}
|
||||
|
||||
uint8_t reason = it.reason;
|
||||
@@ -636,13 +636,13 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_
|
||||
case ESPHOME_EVENT_ID_WIFI_AP_STACONNECTED: {
|
||||
auto it = info.wifi_sta_connected;
|
||||
auto &mac = it.bssid;
|
||||
ESP_LOGV(TAG, "AP client connected MAC=%s", format_mac_addr(mac).c_str());
|
||||
ESP_LOGV(TAG, "AP client connected MAC=%s", format_mac_address_pretty(mac).c_str());
|
||||
break;
|
||||
}
|
||||
case ESPHOME_EVENT_ID_WIFI_AP_STADISCONNECTED: {
|
||||
auto it = info.wifi_sta_disconnected;
|
||||
auto &mac = it.bssid;
|
||||
ESP_LOGV(TAG, "AP client disconnected MAC=%s", format_mac_addr(mac).c_str());
|
||||
ESP_LOGV(TAG, "AP client disconnected MAC=%s", format_mac_address_pretty(mac).c_str());
|
||||
break;
|
||||
}
|
||||
case ESPHOME_EVENT_ID_WIFI_AP_STAIPASSIGNED: {
|
||||
@@ -651,7 +651,7 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_
|
||||
}
|
||||
case ESPHOME_EVENT_ID_WIFI_AP_PROBEREQRECVED: {
|
||||
auto it = info.wifi_ap_probereqrecved;
|
||||
ESP_LOGVV(TAG, "AP receive Probe Request MAC=%s RSSI=%d", format_mac_addr(it.mac).c_str(), it.rssi);
|
||||
ESP_LOGVV(TAG, "AP receive Probe Request MAC=%s RSSI=%d", format_mac_address_pretty(it.mac).c_str(), it.rssi);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
|
||||
@@ -496,7 +496,8 @@ void WiFiComponent::wifi_event_callback(System_Event_t *event) {
|
||||
char buf[33];
|
||||
memcpy(buf, it.ssid, it.ssid_len);
|
||||
buf[it.ssid_len] = '\0';
|
||||
ESP_LOGV(TAG, "Connected ssid='%s' bssid=%s channel=%u", buf, format_mac_addr(it.bssid).c_str(), it.channel);
|
||||
ESP_LOGV(TAG, "Connected ssid='%s' bssid=%s channel=%u", buf, format_mac_address_pretty(it.bssid).c_str(),
|
||||
it.channel);
|
||||
s_sta_connected = true;
|
||||
break;
|
||||
}
|
||||
@@ -510,7 +511,7 @@ void WiFiComponent::wifi_event_callback(System_Event_t *event) {
|
||||
s_sta_connect_not_found = true;
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Disconnected ssid='%s' bssid=" LOG_SECRET("%s") " reason='%s'", buf,
|
||||
format_mac_addr(it.bssid).c_str(), LOG_STR_ARG(get_disconnect_reason_str(it.reason)));
|
||||
format_mac_address_pretty(it.bssid).c_str(), LOG_STR_ARG(get_disconnect_reason_str(it.reason)));
|
||||
s_sta_connect_error = true;
|
||||
}
|
||||
s_sta_connected = false;
|
||||
@@ -545,17 +546,17 @@ void WiFiComponent::wifi_event_callback(System_Event_t *event) {
|
||||
}
|
||||
case EVENT_SOFTAPMODE_STACONNECTED: {
|
||||
auto it = event->event_info.sta_connected;
|
||||
ESP_LOGV(TAG, "AP client connected MAC=%s aid=%u", format_mac_addr(it.mac).c_str(), it.aid);
|
||||
ESP_LOGV(TAG, "AP client connected MAC=%s aid=%u", format_mac_address_pretty(it.mac).c_str(), it.aid);
|
||||
break;
|
||||
}
|
||||
case EVENT_SOFTAPMODE_STADISCONNECTED: {
|
||||
auto it = event->event_info.sta_disconnected;
|
||||
ESP_LOGV(TAG, "AP client disconnected MAC=%s aid=%u", format_mac_addr(it.mac).c_str(), it.aid);
|
||||
ESP_LOGV(TAG, "AP client disconnected MAC=%s aid=%u", format_mac_address_pretty(it.mac).c_str(), it.aid);
|
||||
break;
|
||||
}
|
||||
case EVENT_SOFTAPMODE_PROBEREQRECVED: {
|
||||
auto it = event->event_info.ap_probereqrecved;
|
||||
ESP_LOGVV(TAG, "AP receive Probe Request MAC=%s RSSI=%d", format_mac_addr(it.mac).c_str(), it.rssi);
|
||||
ESP_LOGVV(TAG, "AP receive Probe Request MAC=%s RSSI=%d", format_mac_address_pretty(it.mac).c_str(), it.rssi);
|
||||
break;
|
||||
}
|
||||
#if USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 4, 0)
|
||||
@@ -567,7 +568,7 @@ void WiFiComponent::wifi_event_callback(System_Event_t *event) {
|
||||
}
|
||||
case EVENT_SOFTAPMODE_DISTRIBUTE_STA_IP: {
|
||||
auto it = event->event_info.distribute_sta_ip;
|
||||
ESP_LOGV(TAG, "AP Distribute Station IP MAC=%s IP=%s aid=%u", format_mac_addr(it.mac).c_str(),
|
||||
ESP_LOGV(TAG, "AP Distribute Station IP MAC=%s IP=%s aid=%u", format_mac_address_pretty(it.mac).c_str(),
|
||||
format_ip_addr(it.ip).c_str(), it.aid);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -691,7 +691,7 @@ void WiFiComponent::wifi_process_event_(IDFWiFiEvent *data) {
|
||||
memcpy(buf, it.ssid, it.ssid_len);
|
||||
buf[it.ssid_len] = '\0';
|
||||
ESP_LOGV(TAG, "Connected ssid='%s' bssid=" LOG_SECRET("%s") " channel=%u, authmode=%s", buf,
|
||||
format_mac_addr(it.bssid).c_str(), it.channel, get_auth_mode_str(it.authmode));
|
||||
format_mac_address_pretty(it.bssid).c_str(), it.channel, get_auth_mode_str(it.authmode));
|
||||
s_sta_connected = true;
|
||||
|
||||
} else if (data->event_base == WIFI_EVENT && data->event_id == WIFI_EVENT_STA_DISCONNECTED) {
|
||||
@@ -708,7 +708,7 @@ void WiFiComponent::wifi_process_event_(IDFWiFiEvent *data) {
|
||||
return;
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Disconnected ssid='%s' bssid=" LOG_SECRET("%s") " reason='%s'", buf,
|
||||
format_mac_addr(it.bssid).c_str(), get_disconnect_reason_str(it.reason));
|
||||
format_mac_address_pretty(it.bssid).c_str(), get_disconnect_reason_str(it.reason));
|
||||
s_sta_connect_error = true;
|
||||
}
|
||||
s_sta_connected = false;
|
||||
@@ -780,15 +780,15 @@ void WiFiComponent::wifi_process_event_(IDFWiFiEvent *data) {
|
||||
|
||||
} else if (data->event_base == WIFI_EVENT && data->event_id == WIFI_EVENT_AP_PROBEREQRECVED) {
|
||||
const auto &it = data->data.ap_probe_req_rx;
|
||||
ESP_LOGVV(TAG, "AP receive Probe Request MAC=%s RSSI=%d", format_mac_addr(it.mac).c_str(), it.rssi);
|
||||
ESP_LOGVV(TAG, "AP receive Probe Request MAC=%s RSSI=%d", format_mac_address_pretty(it.mac).c_str(), it.rssi);
|
||||
|
||||
} else if (data->event_base == WIFI_EVENT && data->event_id == WIFI_EVENT_AP_STACONNECTED) {
|
||||
const auto &it = data->data.ap_staconnected;
|
||||
ESP_LOGV(TAG, "AP client connected MAC=%s", format_mac_addr(it.mac).c_str());
|
||||
ESP_LOGV(TAG, "AP client connected MAC=%s", format_mac_address_pretty(it.mac).c_str());
|
||||
|
||||
} else if (data->event_base == WIFI_EVENT && data->event_id == WIFI_EVENT_AP_STADISCONNECTED) {
|
||||
const auto &it = data->data.ap_stadisconnected;
|
||||
ESP_LOGV(TAG, "AP client disconnected MAC=%s", format_mac_addr(it.mac).c_str());
|
||||
ESP_LOGV(TAG, "AP client disconnected MAC=%s", format_mac_address_pretty(it.mac).c_str());
|
||||
|
||||
} else if (data->event_base == IP_EVENT && data->event_id == IP_EVENT_AP_STAIPASSIGNED) {
|
||||
const auto &it = data->data.ip_ap_staipassigned;
|
||||
|
||||
@@ -281,7 +281,7 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_
|
||||
memcpy(buf, it.ssid, it.ssid_len);
|
||||
buf[it.ssid_len] = '\0';
|
||||
ESP_LOGV(TAG, "Connected ssid='%s' bssid=" LOG_SECRET("%s") " channel=%u, authmode=%s", buf,
|
||||
format_mac_addr(it.bssid).c_str(), it.channel, get_auth_mode_str(it.authmode));
|
||||
format_mac_address_pretty(it.bssid).c_str(), it.channel, get_auth_mode_str(it.authmode));
|
||||
|
||||
break;
|
||||
}
|
||||
@@ -294,7 +294,7 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_
|
||||
ESP_LOGW(TAG, "Disconnected ssid='%s' reason='Probe Request Unsuccessful'", buf);
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Disconnected ssid='%s' bssid=" LOG_SECRET("%s") " reason='%s'", buf,
|
||||
format_mac_addr(it.bssid).c_str(), get_disconnect_reason_str(it.reason));
|
||||
format_mac_address_pretty(it.bssid).c_str(), get_disconnect_reason_str(it.reason));
|
||||
}
|
||||
|
||||
uint8_t reason = it.reason;
|
||||
@@ -349,13 +349,13 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_
|
||||
case ESPHOME_EVENT_ID_WIFI_AP_STACONNECTED: {
|
||||
auto it = info.wifi_sta_connected;
|
||||
auto &mac = it.bssid;
|
||||
ESP_LOGV(TAG, "AP client connected MAC=%s", format_mac_addr(mac).c_str());
|
||||
ESP_LOGV(TAG, "AP client connected MAC=%s", format_mac_address_pretty(mac).c_str());
|
||||
break;
|
||||
}
|
||||
case ESPHOME_EVENT_ID_WIFI_AP_STADISCONNECTED: {
|
||||
auto it = info.wifi_sta_disconnected;
|
||||
auto &mac = it.bssid;
|
||||
ESP_LOGV(TAG, "AP client disconnected MAC=%s", format_mac_addr(mac).c_str());
|
||||
ESP_LOGV(TAG, "AP client disconnected MAC=%s", format_mac_address_pretty(mac).c_str());
|
||||
break;
|
||||
}
|
||||
case ESPHOME_EVENT_ID_WIFI_AP_STAIPASSIGNED: {
|
||||
@@ -364,7 +364,7 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_
|
||||
}
|
||||
case ESPHOME_EVENT_ID_WIFI_AP_PROBEREQRECVED: {
|
||||
auto it = info.wifi_ap_probereqrecved;
|
||||
ESP_LOGVV(TAG, "AP receive Probe Request MAC=%s RSSI=%d", format_mac_addr(it.mac).c_str(), it.rssi);
|
||||
ESP_LOGVV(TAG, "AP receive Probe Request MAC=%s RSSI=%d", format_mac_address_pretty(it.mac).c_str(), it.rssi);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
|
||||
@@ -320,7 +320,7 @@ bool decrypt_xiaomi_payload(std::vector<uint8_t> &raw, const uint8_t *bindkey, c
|
||||
memcpy(mac_address + 4, mac_reverse + 1, 1);
|
||||
memcpy(mac_address + 5, mac_reverse, 1);
|
||||
ESP_LOGVV(TAG, "decrypt_xiaomi_payload(): authenticated decryption failed.");
|
||||
ESP_LOGVV(TAG, " MAC address : %s", format_hex_pretty(mac_address, 6).c_str());
|
||||
ESP_LOGVV(TAG, " MAC address : %s", format_mac_address_pretty(mac_address).c_str());
|
||||
ESP_LOGVV(TAG, " Packet : %s", format_hex_pretty(raw.data(), raw.size()).c_str());
|
||||
ESP_LOGVV(TAG, " Key : %s", format_hex_pretty(vector.key, vector.keysize).c_str());
|
||||
ESP_LOGVV(TAG, " Iv : %s", format_hex_pretty(vector.iv, vector.ivsize).c_str());
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
"""Helpers for config validation using voluptuous."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from contextlib import contextmanager
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
@@ -29,6 +31,7 @@ from esphome.const import (
|
||||
CONF_COMMAND_RETAIN,
|
||||
CONF_COMMAND_TOPIC,
|
||||
CONF_DAY,
|
||||
CONF_DEVICE_ID,
|
||||
CONF_DISABLED_BY_DEFAULT,
|
||||
CONF_DISCOVERY,
|
||||
CONF_ENTITY_CATEGORY,
|
||||
@@ -355,6 +358,13 @@ def icon(value):
|
||||
)
|
||||
|
||||
|
||||
def sub_device_id(value: str | None) -> core.ID:
|
||||
# Lazy import to avoid circular imports
|
||||
from esphome.core.config import Device
|
||||
|
||||
return use_id(Device)(value)
|
||||
|
||||
|
||||
def boolean(value):
|
||||
"""Validate the given config option to be a boolean.
|
||||
|
||||
@@ -1896,6 +1906,7 @@ ENTITY_BASE_SCHEMA = Schema(
|
||||
Optional(CONF_DISABLED_BY_DEFAULT, default=False): boolean,
|
||||
Optional(CONF_ICON): icon,
|
||||
Optional(CONF_ENTITY_CATEGORY): entity_category,
|
||||
Optional(CONF_DEVICE_ID): sub_device_id,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -1964,7 +1975,7 @@ class Version:
|
||||
return f"{self.major}.{self.minor}.{self.patch}"
|
||||
|
||||
@classmethod
|
||||
def parse(cls, value: str) -> "Version":
|
||||
def parse(cls, value: str) -> Version:
|
||||
match = re.match(r"^(\d+).(\d+).(\d+)-?\w*$", value)
|
||||
if match is None:
|
||||
raise ValueError(f"Not a valid version number {value}")
|
||||
|
||||
@@ -56,6 +56,8 @@ CONF_AP = "ap"
|
||||
CONF_APPARENT_POWER = "apparent_power"
|
||||
CONF_ARDUINO_VERSION = "arduino_version"
|
||||
CONF_AREA = "area"
|
||||
CONF_AREA_ID = "area_id"
|
||||
CONF_AREAS = "areas"
|
||||
CONF_ARGS = "args"
|
||||
CONF_ASSUMED_STATE = "assumed_state"
|
||||
CONF_AT = "at"
|
||||
@@ -217,6 +219,7 @@ CONF_DEST = "dest"
|
||||
CONF_DEVICE = "device"
|
||||
CONF_DEVICE_CLASS = "device_class"
|
||||
CONF_DEVICE_FACTOR = "device_factor"
|
||||
CONF_DEVICE_ID = "device_id"
|
||||
CONF_DEVICES = "devices"
|
||||
CONF_DIELECTRIC_CONSTANT = "dielectric_constant"
|
||||
CONF_DIMENSIONS = "dimensions"
|
||||
@@ -1096,7 +1099,7 @@ UNIT_KILOMETER_PER_HOUR = "km/h"
|
||||
UNIT_KILOVOLT_AMPS = "kVA"
|
||||
UNIT_KILOVOLT_AMPS_HOURS = "kVAh"
|
||||
UNIT_KILOVOLT_AMPS_REACTIVE = "kVAR"
|
||||
UNIT_KILOVOLT_AMPS_REACTIVE_HOURS = "kVARh"
|
||||
UNIT_KILOVOLT_AMPS_REACTIVE_HOURS = "kvarh"
|
||||
UNIT_KILOWATT = "kW"
|
||||
UNIT_KILOWATT_HOURS = "kWh"
|
||||
UNIT_LITRE = "L"
|
||||
@@ -1132,7 +1135,7 @@ UNIT_VOLT = "V"
|
||||
UNIT_VOLT_AMPS = "VA"
|
||||
UNIT_VOLT_AMPS_HOURS = "VAh"
|
||||
UNIT_VOLT_AMPS_REACTIVE = "var"
|
||||
UNIT_VOLT_AMPS_REACTIVE_HOURS = "VARh"
|
||||
UNIT_VOLT_AMPS_REACTIVE_HOURS = "varh"
|
||||
UNIT_WATT = "W"
|
||||
UNIT_WATT_HOURS = "Wh"
|
||||
|
||||
|
||||
@@ -522,6 +522,9 @@ class EsphomeCore:
|
||||
# Dict to track platform entity counts for pre-allocation
|
||||
# Key: platform name (e.g. "sensor", "binary_sensor"), Value: count
|
||||
self.platform_counts: defaultdict[str, int] = defaultdict(int)
|
||||
# Track entity unique IDs to handle duplicates
|
||||
# Set of (device_id, platform, sanitized_name) tuples
|
||||
self.unique_ids: set[tuple[str, str, str]] = set()
|
||||
# Whether ESPHome was started in verbose mode
|
||||
self.verbose = False
|
||||
# Whether ESPHome was started in quiet mode
|
||||
@@ -553,6 +556,7 @@ class EsphomeCore:
|
||||
self.loaded_integrations = set()
|
||||
self.component_ids = set()
|
||||
self.platform_counts = defaultdict(int)
|
||||
self.unique_ids = set()
|
||||
PIN_SCHEMA_REGISTRY.reset()
|
||||
|
||||
@property
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "esphome/core/component.h"
|
||||
@@ -9,6 +11,13 @@
|
||||
#include "esphome/core/preferences.h"
|
||||
#include "esphome/core/scheduler.h"
|
||||
|
||||
#ifdef USE_DEVICES
|
||||
#include "esphome/core/device.h"
|
||||
#endif
|
||||
#ifdef USE_AREAS
|
||||
#include "esphome/core/area.h"
|
||||
#endif
|
||||
|
||||
#ifdef USE_SOCKET_SELECT_SUPPORT
|
||||
#include <sys/select.h>
|
||||
#endif
|
||||
@@ -87,7 +96,7 @@ static const uint32_t TEARDOWN_TIMEOUT_REBOOT_MS = 1000; // 1 second for quick
|
||||
|
||||
class Application {
|
||||
public:
|
||||
void pre_setup(const std::string &name, const std::string &friendly_name, const char *area, const char *comment,
|
||||
void pre_setup(const std::string &name, const std::string &friendly_name, const char *comment,
|
||||
const char *compilation_time, bool name_add_mac_suffix) {
|
||||
arch_init();
|
||||
this->name_add_mac_suffix_ = name_add_mac_suffix;
|
||||
@@ -102,11 +111,17 @@ class Application {
|
||||
this->name_ = name;
|
||||
this->friendly_name_ = friendly_name;
|
||||
}
|
||||
this->area_ = area;
|
||||
this->comment_ = comment;
|
||||
this->compilation_time_ = compilation_time;
|
||||
}
|
||||
|
||||
#ifdef USE_DEVICES
|
||||
void register_device(Device *device) { this->devices_.push_back(device); }
|
||||
#endif
|
||||
#ifdef USE_AREAS
|
||||
void register_area(Area *area) { this->areas_.push_back(area); }
|
||||
#endif
|
||||
|
||||
void set_current_component(Component *component) { this->current_component_ = component; }
|
||||
Component *get_current_component() { return this->current_component_; }
|
||||
|
||||
@@ -264,6 +279,12 @@ class Application {
|
||||
#ifdef USE_UPDATE
|
||||
void reserve_update(size_t count) { this->updates_.reserve(count); }
|
||||
#endif
|
||||
#ifdef USE_AREAS
|
||||
void reserve_area(size_t count) { this->areas_.reserve(count); }
|
||||
#endif
|
||||
#ifdef USE_DEVICES
|
||||
void reserve_device(size_t count) { this->devices_.reserve(count); }
|
||||
#endif
|
||||
|
||||
/// Register the component in this Application instance.
|
||||
template<class C> C *register_component(C *c) {
|
||||
@@ -285,7 +306,15 @@ class Application {
|
||||
const std::string &get_friendly_name() const { return this->friendly_name_; }
|
||||
|
||||
/// Get the area of this Application set by pre_setup().
|
||||
std::string get_area() const { return this->area_ == nullptr ? "" : this->area_; }
|
||||
const char *get_area() const {
|
||||
#ifdef USE_AREAS
|
||||
// If we have areas registered, return the name of the first one (which is the top-level area)
|
||||
if (!this->areas_.empty() && this->areas_[0] != nullptr) {
|
||||
return this->areas_[0]->get_name();
|
||||
}
|
||||
#endif
|
||||
return "";
|
||||
}
|
||||
|
||||
/// Get the comment of this Application set by pre_setup().
|
||||
std::string get_comment() const { return this->comment_; }
|
||||
@@ -308,11 +337,16 @@ class Application {
|
||||
* Each component can request a high frequency loop execution by using the HighFrequencyLoopRequester
|
||||
* helper in helpers.h
|
||||
*
|
||||
* Note: This method is not called by ESPHome core code. It is only used by lambda functions
|
||||
* in YAML configurations or by external components.
|
||||
*
|
||||
* @param loop_interval The interval in milliseconds to run the core loop at. Defaults to 16 milliseconds.
|
||||
*/
|
||||
void set_loop_interval(uint32_t loop_interval) { this->loop_interval_ = loop_interval; }
|
||||
void set_loop_interval(uint32_t loop_interval) {
|
||||
this->loop_interval_ = std::min(loop_interval, static_cast<uint32_t>(std::numeric_limits<uint16_t>::max()));
|
||||
}
|
||||
|
||||
uint32_t get_loop_interval() const { return this->loop_interval_; }
|
||||
uint32_t get_loop_interval() const { return static_cast<uint32_t>(this->loop_interval_); }
|
||||
|
||||
void schedule_dump_config() { this->dump_config_at_ = 0; }
|
||||
|
||||
@@ -334,6 +368,12 @@ class Application {
|
||||
|
||||
uint8_t get_app_state() const { return this->app_state_; }
|
||||
|
||||
#ifdef USE_DEVICES
|
||||
const std::vector<Device *> &get_devices() { return this->devices_; }
|
||||
#endif
|
||||
#ifdef USE_AREAS
|
||||
const std::vector<Area *> &get_areas() { return this->areas_; }
|
||||
#endif
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
const std::vector<binary_sensor::BinarySensor *> &get_binary_sensors() { return this->binary_sensors_; }
|
||||
binary_sensor::BinarySensor *get_binary_sensor_by_key(uint32_t key, bool include_internal = false) {
|
||||
@@ -585,6 +625,17 @@ class Application {
|
||||
/// Perform a delay while also monitoring socket file descriptors for readiness
|
||||
void yield_with_select_(uint32_t delay_ms);
|
||||
|
||||
// === Member variables ordered by size to minimize padding ===
|
||||
|
||||
// Pointer-sized members first
|
||||
Component *current_component_{nullptr};
|
||||
const char *comment_{nullptr};
|
||||
const char *compilation_time_{nullptr};
|
||||
|
||||
// size_t members
|
||||
size_t dump_config_at_{SIZE_MAX};
|
||||
|
||||
// Vectors (largest members)
|
||||
std::vector<Component *> components_{};
|
||||
|
||||
// Partitioned vector design for looping components
|
||||
@@ -604,12 +655,13 @@ class Application {
|
||||
// and active_end_ is incremented
|
||||
// - This eliminates branch mispredictions from flag checking in the hot loop
|
||||
std::vector<Component *> looping_components_{};
|
||||
uint16_t looping_components_active_end_{0};
|
||||
|
||||
// For safe reentrant modifications during iteration
|
||||
uint16_t current_loop_index_{0};
|
||||
bool in_loop_{false};
|
||||
|
||||
#ifdef USE_DEVICES
|
||||
std::vector<Device *> devices_{};
|
||||
#endif
|
||||
#ifdef USE_AREAS
|
||||
std::vector<Area *> areas_{};
|
||||
#endif
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
std::vector<binary_sensor::BinarySensor *> binary_sensors_{};
|
||||
#endif
|
||||
@@ -674,27 +726,39 @@ class Application {
|
||||
std::vector<update::UpdateEntity *> updates_{};
|
||||
#endif
|
||||
|
||||
#ifdef USE_SOCKET_SELECT_SUPPORT
|
||||
std::vector<int> socket_fds_; // Vector of all monitored socket file descriptors
|
||||
#endif
|
||||
|
||||
// String members
|
||||
std::string name_;
|
||||
std::string friendly_name_;
|
||||
const char *area_{nullptr};
|
||||
const char *comment_{nullptr};
|
||||
const char *compilation_time_{nullptr};
|
||||
bool name_add_mac_suffix_;
|
||||
|
||||
// 4-byte members
|
||||
uint32_t last_loop_{0};
|
||||
uint32_t loop_interval_{16};
|
||||
size_t dump_config_at_{SIZE_MAX};
|
||||
uint8_t app_state_{0};
|
||||
volatile bool has_pending_enable_loop_requests_{false};
|
||||
Component *current_component_{nullptr};
|
||||
uint32_t loop_component_start_time_{0};
|
||||
|
||||
#ifdef USE_SOCKET_SELECT_SUPPORT
|
||||
// Socket select management
|
||||
std::vector<int> socket_fds_; // Vector of all monitored socket file descriptors
|
||||
int max_fd_{-1}; // Highest file descriptor number for select()
|
||||
#endif
|
||||
|
||||
// 2-byte members (grouped together for alignment)
|
||||
uint16_t loop_interval_{16}; // Loop interval in ms (max 65535ms = 65.5 seconds)
|
||||
uint16_t looping_components_active_end_{0};
|
||||
uint16_t current_loop_index_{0}; // For safe reentrant modifications during iteration
|
||||
|
||||
// 1-byte members (grouped together to minimize padding)
|
||||
uint8_t app_state_{0};
|
||||
bool name_add_mac_suffix_;
|
||||
bool in_loop_{false};
|
||||
volatile bool has_pending_enable_loop_requests_{false};
|
||||
|
||||
#ifdef USE_SOCKET_SELECT_SUPPORT
|
||||
bool socket_fds_changed_{false}; // Flag to rebuild base_read_fds_ when socket_fds_ changes
|
||||
int max_fd_{-1}; // Highest file descriptor number for select()
|
||||
fd_set base_read_fds_{}; // Cached fd_set rebuilt only when socket_fds_ changes
|
||||
fd_set read_fds_{}; // Working fd_set for select(), copied from base_read_fds_
|
||||
|
||||
// Variable-sized members at end
|
||||
fd_set base_read_fds_{}; // Cached fd_set rebuilt only when socket_fds_ changes
|
||||
fd_set read_fds_{}; // Working fd_set for select(), copied from base_read_fds_
|
||||
#endif
|
||||
};
|
||||
|
||||
|
||||
19
esphome/core/area.h
Normal file
19
esphome/core/area.h
Normal file
@@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace esphome {
|
||||
|
||||
class Area {
|
||||
public:
|
||||
void set_area_id(uint32_t area_id) { this->area_id_ = area_id; }
|
||||
uint32_t get_area_id() { return this->area_id_; }
|
||||
void set_name(const char *name) { this->name_ = name; }
|
||||
const char *get_name() { return this->name_; }
|
||||
|
||||
protected:
|
||||
uint32_t area_id_{};
|
||||
const char *name_ = "";
|
||||
};
|
||||
|
||||
} // namespace esphome
|
||||
@@ -27,20 +27,67 @@ template<typename T, typename... X> class TemplatableValue {
|
||||
public:
|
||||
TemplatableValue() : type_(NONE) {}
|
||||
|
||||
template<typename F, enable_if_t<!is_invocable<F, X...>::value, int> = 0>
|
||||
TemplatableValue(F value) : type_(VALUE), value_(std::move(value)) {}
|
||||
template<typename F, enable_if_t<!is_invocable<F, X...>::value, int> = 0> TemplatableValue(F value) : type_(VALUE) {
|
||||
new (&this->value_) T(std::move(value));
|
||||
}
|
||||
|
||||
template<typename F, enable_if_t<is_invocable<F, X...>::value, int> = 0>
|
||||
TemplatableValue(F f) : type_(LAMBDA), f_(f) {}
|
||||
template<typename F, enable_if_t<is_invocable<F, X...>::value, int> = 0> TemplatableValue(F f) : type_(LAMBDA) {
|
||||
this->f_ = new std::function<T(X...)>(std::move(f));
|
||||
}
|
||||
|
||||
// Copy constructor
|
||||
TemplatableValue(const TemplatableValue &other) : type_(other.type_) {
|
||||
if (type_ == VALUE) {
|
||||
new (&this->value_) T(other.value_);
|
||||
} else if (type_ == LAMBDA) {
|
||||
this->f_ = new std::function<T(X...)>(*other.f_);
|
||||
}
|
||||
}
|
||||
|
||||
// Move constructor
|
||||
TemplatableValue(TemplatableValue &&other) noexcept : type_(other.type_) {
|
||||
if (type_ == VALUE) {
|
||||
new (&this->value_) T(std::move(other.value_));
|
||||
} else if (type_ == LAMBDA) {
|
||||
this->f_ = other.f_;
|
||||
other.f_ = nullptr;
|
||||
}
|
||||
other.type_ = NONE;
|
||||
}
|
||||
|
||||
// Assignment operators
|
||||
TemplatableValue &operator=(const TemplatableValue &other) {
|
||||
if (this != &other) {
|
||||
this->~TemplatableValue();
|
||||
new (this) TemplatableValue(other);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
TemplatableValue &operator=(TemplatableValue &&other) noexcept {
|
||||
if (this != &other) {
|
||||
this->~TemplatableValue();
|
||||
new (this) TemplatableValue(std::move(other));
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
~TemplatableValue() {
|
||||
if (type_ == VALUE) {
|
||||
this->value_.~T();
|
||||
} else if (type_ == LAMBDA) {
|
||||
delete this->f_;
|
||||
}
|
||||
}
|
||||
|
||||
bool has_value() { return this->type_ != NONE; }
|
||||
|
||||
T value(X... x) {
|
||||
if (this->type_ == LAMBDA) {
|
||||
return this->f_(x...);
|
||||
return (*this->f_)(x...);
|
||||
}
|
||||
// return value also when none
|
||||
return this->value_;
|
||||
return this->type_ == VALUE ? this->value_ : T{};
|
||||
}
|
||||
|
||||
optional<T> optional_value(X... x) {
|
||||
@@ -58,14 +105,16 @@ template<typename T, typename... X> class TemplatableValue {
|
||||
}
|
||||
|
||||
protected:
|
||||
enum {
|
||||
enum : uint8_t {
|
||||
NONE,
|
||||
VALUE,
|
||||
LAMBDA,
|
||||
} type_;
|
||||
|
||||
T value_{};
|
||||
std::function<T(X...)> f_{};
|
||||
union {
|
||||
T value_;
|
||||
std::function<T(X...)> *f_;
|
||||
};
|
||||
};
|
||||
|
||||
/** Base class for all automation conditions.
|
||||
|
||||
@@ -1,18 +1,24 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
from esphome import automation
|
||||
from esphome import automation, core
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_AREA,
|
||||
CONF_AREA_ID,
|
||||
CONF_AREAS,
|
||||
CONF_BUILD_PATH,
|
||||
CONF_COMMENT,
|
||||
CONF_COMPILE_PROCESS_LIMIT,
|
||||
CONF_DEBUG_SCHEDULER,
|
||||
CONF_DEVICES,
|
||||
CONF_ESPHOME,
|
||||
CONF_FRIENDLY_NAME,
|
||||
CONF_ID,
|
||||
CONF_INCLUDES,
|
||||
CONF_LIBRARIES,
|
||||
CONF_MIN_VERSION,
|
||||
@@ -32,7 +38,13 @@ from esphome.const import (
|
||||
__version__ as ESPHOME_VERSION,
|
||||
)
|
||||
from esphome.core import CORE, coroutine_with_priority
|
||||
from esphome.helpers import copy_file_if_changed, get_str_env, walk_files
|
||||
from esphome.helpers import (
|
||||
copy_file_if_changed,
|
||||
fnv1a_32bit_hash,
|
||||
get_str_env,
|
||||
walk_files,
|
||||
)
|
||||
from esphome.types import ConfigType
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -48,7 +60,8 @@ LoopTrigger = cg.esphome_ns.class_(
|
||||
ProjectUpdateTrigger = cg.esphome_ns.class_(
|
||||
"ProjectUpdateTrigger", cg.Component, automation.Trigger.template(cg.std_string)
|
||||
)
|
||||
|
||||
Device = cg.esphome_ns.class_("Device")
|
||||
Area = cg.esphome_ns.class_("Area")
|
||||
|
||||
VALID_INCLUDE_EXTS = {".h", ".hpp", ".tcc", ".ino", ".cpp", ".c"}
|
||||
|
||||
@@ -71,6 +84,56 @@ def validate_hostname(config):
|
||||
return config
|
||||
|
||||
|
||||
def validate_ids_and_references(config: ConfigType) -> ConfigType:
|
||||
"""Validate that there are no hash collisions between IDs and that area_id references are valid.
|
||||
|
||||
This validation is critical because we use 32-bit hashes for performance on microcontrollers.
|
||||
By detecting collisions at compile time, we prevent any runtime issues while maintaining
|
||||
optimal performance on 32-bit platforms. In practice, with typical deployments having only
|
||||
a handful of areas and devices, hash collisions are virtually impossible.
|
||||
"""
|
||||
|
||||
# Helper to check hash collisions
|
||||
def check_hash_collision(
|
||||
id_obj: core.ID,
|
||||
hash_dict: dict[int, str],
|
||||
item_type: str,
|
||||
path: list[str | int],
|
||||
) -> None:
|
||||
hash_val: int = fnv1a_32bit_hash(id_obj.id)
|
||||
if hash_val in hash_dict and hash_dict[hash_val] != id_obj.id:
|
||||
raise cv.Invalid(
|
||||
f"{item_type} ID '{id_obj.id}' with hash {hash_val} collides with "
|
||||
f"existing {item_type.lower()} ID '{hash_dict[hash_val]}'",
|
||||
path=path,
|
||||
)
|
||||
hash_dict[hash_val] = id_obj.id
|
||||
|
||||
# Collect all areas
|
||||
all_areas: list[dict[str, str | core.ID]] = []
|
||||
if CONF_AREA in config:
|
||||
all_areas.append(config[CONF_AREA])
|
||||
all_areas.extend(config[CONF_AREAS])
|
||||
|
||||
# Validate area hash collisions and collect IDs
|
||||
area_hashes: dict[int, str] = {}
|
||||
area_ids: set[str] = set()
|
||||
for area in all_areas:
|
||||
area_id: core.ID = area[CONF_ID]
|
||||
check_hash_collision(area_id, area_hashes, "Area", [CONF_AREAS, area_id.id])
|
||||
area_ids.add(area_id.id)
|
||||
|
||||
# Validate device hash collisions and area references
|
||||
device_hashes: dict[int, str] = {}
|
||||
for device in config[CONF_DEVICES]:
|
||||
device_id: core.ID = device[CONF_ID]
|
||||
check_hash_collision(
|
||||
device_id, device_hashes, "Device", [CONF_DEVICES, device_id.id]
|
||||
)
|
||||
|
||||
return config
|
||||
|
||||
|
||||
def valid_include(value):
|
||||
# Look for "<...>" includes
|
||||
if value.startswith("<") and value.endswith(">"):
|
||||
@@ -111,13 +174,32 @@ if "ESPHOME_DEFAULT_COMPILE_PROCESS_LIMIT" in os.environ:
|
||||
else:
|
||||
_compile_process_limit_default = cv.UNDEFINED
|
||||
|
||||
AREA_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(CONF_ID): cv.declare_id(Area),
|
||||
cv.Required(CONF_NAME): cv.string,
|
||||
}
|
||||
)
|
||||
|
||||
DEVICE_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(CONF_ID): cv.declare_id(Device),
|
||||
cv.Required(CONF_NAME): cv.string,
|
||||
cv.Optional(CONF_AREA_ID): cv.use_id(Area),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def validate_area_config(config: dict | str) -> dict[str, str | core.ID]:
|
||||
return cv.maybe_simple_value(AREA_SCHEMA, key=CONF_NAME)(config)
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_NAME): cv.valid_name,
|
||||
cv.Optional(CONF_FRIENDLY_NAME, ""): cv.string,
|
||||
cv.Optional(CONF_AREA, ""): cv.string,
|
||||
cv.Optional(CONF_AREA): validate_area_config,
|
||||
cv.Optional(CONF_COMMENT): cv.string,
|
||||
cv.Required(CONF_BUILD_PATH): cv.string,
|
||||
cv.Optional(CONF_PLATFORMIO_OPTIONS, default={}): cv.Schema(
|
||||
@@ -167,11 +249,17 @@ CONFIG_SCHEMA = cv.All(
|
||||
cv.Optional(
|
||||
CONF_COMPILE_PROCESS_LIMIT, default=_compile_process_limit_default
|
||||
): cv.int_range(min=1, max=get_usable_cpu_count()),
|
||||
cv.Optional(CONF_AREAS, default=[]): cv.ensure_list(AREA_SCHEMA),
|
||||
cv.Optional(CONF_DEVICES, default=[]): cv.ensure_list(DEVICE_SCHEMA),
|
||||
}
|
||||
),
|
||||
validate_hostname,
|
||||
)
|
||||
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = cv.All(validate_ids_and_references)
|
||||
|
||||
|
||||
PRELOAD_CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_NAME): cv.valid_name,
|
||||
@@ -336,7 +424,7 @@ async def _add_platform_reserves() -> None:
|
||||
|
||||
|
||||
@coroutine_with_priority(100.0)
|
||||
async def to_code(config):
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
cg.add_global(cg.global_ns.namespace("esphome").using)
|
||||
# These can be used by user lambdas, put them to default scope
|
||||
cg.add_global(cg.RawExpression("using std::isnan"))
|
||||
@@ -347,7 +435,6 @@ async def to_code(config):
|
||||
cg.App.pre_setup(
|
||||
config[CONF_NAME],
|
||||
config[CONF_FRIENDLY_NAME],
|
||||
config[CONF_AREA],
|
||||
config.get(CONF_COMMENT, ""),
|
||||
cg.RawExpression('__DATE__ ", " __TIME__'),
|
||||
config[CONF_NAME_ADD_MAC_SUFFIX],
|
||||
@@ -417,3 +504,50 @@ async def to_code(config):
|
||||
|
||||
if config[CONF_PLATFORMIO_OPTIONS]:
|
||||
CORE.add_job(_add_platformio_options, config[CONF_PLATFORMIO_OPTIONS])
|
||||
|
||||
# Process areas
|
||||
all_areas: list[dict[str, str | core.ID]] = []
|
||||
if CONF_AREA in config:
|
||||
all_areas.append(config[CONF_AREA])
|
||||
all_areas.extend(config[CONF_AREAS])
|
||||
|
||||
if all_areas:
|
||||
cg.add(cg.RawStatement(f"App.reserve_area({len(all_areas)});"))
|
||||
cg.add_define("USE_AREAS")
|
||||
|
||||
for area_conf in all_areas:
|
||||
area_id: core.ID = area_conf[CONF_ID]
|
||||
area_id_hash: int = fnv1a_32bit_hash(area_id.id)
|
||||
area_name: str = area_conf[CONF_NAME]
|
||||
|
||||
area_var = cg.new_Pvariable(area_id)
|
||||
cg.add(area_var.set_area_id(area_id_hash))
|
||||
cg.add(area_var.set_name(area_name))
|
||||
cg.add(cg.App.register_area(area_var))
|
||||
|
||||
# Process devices
|
||||
devices: list[dict[str, str | core.ID]] = config[CONF_DEVICES]
|
||||
if not devices:
|
||||
return
|
||||
|
||||
# Reserve space for devices
|
||||
cg.add(cg.RawStatement(f"App.reserve_device({len(devices)});"))
|
||||
cg.add_define("USE_DEVICES")
|
||||
|
||||
# Process each device
|
||||
for dev_conf in devices:
|
||||
device_id: core.ID = dev_conf[CONF_ID]
|
||||
device_id_hash = fnv1a_32bit_hash(device_id.id)
|
||||
device_name: str = dev_conf[CONF_NAME]
|
||||
|
||||
dev = cg.new_Pvariable(device_id)
|
||||
cg.add(dev.set_device_id(device_id_hash))
|
||||
cg.add(dev.set_name(device_name))
|
||||
|
||||
# Set area if specified
|
||||
if CONF_AREA_ID in dev_conf:
|
||||
area_id: core.ID = dev_conf[CONF_AREA_ID]
|
||||
area_id_hash = fnv1a_32bit_hash(area_id.id)
|
||||
cg.add(dev.set_area_id(area_id_hash))
|
||||
|
||||
cg.add(cg.App.register_device(dev))
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
|
||||
// Feature flags
|
||||
#define USE_ALARM_CONTROL_PANEL
|
||||
#define USE_AREAS
|
||||
#define USE_BINARY_SENSOR
|
||||
#define USE_BUTTON
|
||||
#define USE_CLIMATE
|
||||
@@ -29,6 +30,7 @@
|
||||
#define USE_DATETIME_DATETIME
|
||||
#define USE_DATETIME_TIME
|
||||
#define USE_DEEP_SLEEP
|
||||
#define USE_DEVICES
|
||||
#define USE_DISPLAY
|
||||
#define USE_ESP32_IMPROV_STATE_CALLBACK
|
||||
#define USE_EVENT
|
||||
@@ -130,6 +132,8 @@
|
||||
|
||||
// ESP32-specific feature flags
|
||||
#ifdef USE_ESP32
|
||||
#define USE_ESPHOME_TASK_LOG_BUFFER
|
||||
|
||||
#define USE_BLUETOOTH_PROXY
|
||||
#define USE_CAPTIVE_PORTAL
|
||||
#define USE_ESP32_BLE
|
||||
|
||||
20
esphome/core/device.h
Normal file
20
esphome/core/device.h
Normal file
@@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
namespace esphome {
|
||||
|
||||
class Device {
|
||||
public:
|
||||
void set_device_id(uint32_t device_id) { this->device_id_ = device_id; }
|
||||
uint32_t get_device_id() { return this->device_id_; }
|
||||
void set_name(const char *name) { this->name_ = name; }
|
||||
const char *get_name() { return this->name_; }
|
||||
void set_area_id(uint32_t area_id) { this->area_id_ = area_id; }
|
||||
uint32_t get_area_id() { return this->area_id_; }
|
||||
|
||||
protected:
|
||||
uint32_t device_id_{};
|
||||
uint32_t area_id_{};
|
||||
const char *name_ = "";
|
||||
};
|
||||
|
||||
} // namespace esphome
|
||||
@@ -11,7 +11,14 @@ const StringRef &EntityBase::get_name() const { return this->name_; }
|
||||
void EntityBase::set_name(const char *name) {
|
||||
this->name_ = StringRef(name);
|
||||
if (this->name_.empty()) {
|
||||
this->name_ = StringRef(App.get_friendly_name());
|
||||
#ifdef USE_DEVICES
|
||||
if (this->device_ != nullptr) {
|
||||
this->name_ = StringRef(this->device_->get_name());
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
this->name_ = StringRef(App.get_friendly_name());
|
||||
}
|
||||
this->flags_.has_own_name = false;
|
||||
} else {
|
||||
this->flags_.has_own_name = true;
|
||||
@@ -47,19 +54,7 @@ void EntityBase::set_object_id(const char *object_id) {
|
||||
}
|
||||
|
||||
// Calculate Object ID Hash from Entity Name
|
||||
void EntityBase::calc_object_id_() {
|
||||
// Check if `App.get_friendly_name()` is constant or dynamic.
|
||||
if (!this->flags_.has_own_name && App.is_name_add_mac_suffix_enabled()) {
|
||||
// `App.get_friendly_name()` is dynamic.
|
||||
const auto object_id = str_sanitize(str_snake_case(App.get_friendly_name()));
|
||||
// FNV-1 hash
|
||||
this->object_id_hash_ = fnv1_hash(object_id);
|
||||
} else {
|
||||
// `App.get_friendly_name()` is constant.
|
||||
// FNV-1 hash
|
||||
this->object_id_hash_ = fnv1_hash(this->object_id_c_str_);
|
||||
}
|
||||
}
|
||||
void EntityBase::calc_object_id_() { this->object_id_hash_ = fnv1_hash(this->get_object_id()); }
|
||||
|
||||
uint32_t EntityBase::get_object_id_hash() { return this->object_id_hash_; }
|
||||
|
||||
|
||||
@@ -6,6 +6,10 @@
|
||||
#include "helpers.h"
|
||||
#include "log.h"
|
||||
|
||||
#ifdef USE_DEVICES
|
||||
#include "device.h"
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
|
||||
enum EntityCategory : uint8_t {
|
||||
@@ -51,6 +55,17 @@ class EntityBase {
|
||||
std::string get_icon() const;
|
||||
void set_icon(const char *icon);
|
||||
|
||||
#ifdef USE_DEVICES
|
||||
// Get/set this entity's device id
|
||||
uint32_t get_device_id() const {
|
||||
if (this->device_ == nullptr) {
|
||||
return 0; // No device set, return 0
|
||||
}
|
||||
return this->device_->get_device_id();
|
||||
}
|
||||
void set_device(Device *device) { this->device_ = device; }
|
||||
#endif
|
||||
|
||||
// Check if this entity has state
|
||||
bool has_state() const { return this->flags_.has_state; }
|
||||
|
||||
@@ -67,6 +82,9 @@ class EntityBase {
|
||||
const char *object_id_c_str_{nullptr};
|
||||
const char *icon_c_str_{nullptr};
|
||||
uint32_t object_id_hash_{};
|
||||
#ifdef USE_DEVICES
|
||||
Device *device_{};
|
||||
#endif
|
||||
|
||||
// Bit-packed flags to save memory (1 byte instead of 5)
|
||||
struct EntityFlags {
|
||||
|
||||
@@ -1,5 +1,116 @@
|
||||
from esphome.const import CONF_ID
|
||||
from collections.abc import Callable
|
||||
import logging
|
||||
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_DEVICE_ID,
|
||||
CONF_DISABLED_BY_DEFAULT,
|
||||
CONF_ENTITY_CATEGORY,
|
||||
CONF_ICON,
|
||||
CONF_ID,
|
||||
CONF_INTERNAL,
|
||||
CONF_NAME,
|
||||
)
|
||||
from esphome.core import CORE, ID
|
||||
from esphome.cpp_generator import MockObj, add, get_variable
|
||||
import esphome.final_validate as fv
|
||||
from esphome.helpers import sanitize, snake_case
|
||||
from esphome.types import ConfigType
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def get_base_entity_object_id(
|
||||
name: str, friendly_name: str | None, device_name: str | None = None
|
||||
) -> str:
|
||||
"""Calculate the base object ID for an entity that will be set via set_object_id().
|
||||
|
||||
This function calculates what object_id_c_str_ should be set to in C++.
|
||||
|
||||
The C++ EntityBase::get_object_id() (entity_base.cpp lines 38-49) works as:
|
||||
- If !has_own_name && is_name_add_mac_suffix_enabled():
|
||||
return str_sanitize(str_snake_case(App.get_friendly_name())) // Dynamic
|
||||
- Else:
|
||||
return object_id_c_str_ ?? "" // What we set via set_object_id()
|
||||
|
||||
Since we're calculating what to pass to set_object_id(), we always need to
|
||||
generate the object_id the same way, regardless of name_add_mac_suffix setting.
|
||||
|
||||
Args:
|
||||
name: The entity name (empty string if no name)
|
||||
friendly_name: The friendly name from CORE.friendly_name
|
||||
device_name: The device name if entity is on a sub-device
|
||||
|
||||
Returns:
|
||||
The base object ID to use for duplicate checking and to pass to set_object_id()
|
||||
"""
|
||||
|
||||
if name:
|
||||
# Entity has its own name (has_own_name will be true)
|
||||
base_str = name
|
||||
elif device_name:
|
||||
# Entity has empty name and is on a sub-device
|
||||
# C++ EntityBase::set_name() uses device->get_name() when device is set
|
||||
base_str = device_name
|
||||
elif friendly_name:
|
||||
# Entity has empty name (has_own_name will be false)
|
||||
# C++ uses App.get_friendly_name() which returns friendly_name or device name
|
||||
base_str = friendly_name
|
||||
else:
|
||||
# Fallback to device name
|
||||
base_str = CORE.name
|
||||
|
||||
return sanitize(snake_case(base_str))
|
||||
|
||||
|
||||
async def setup_entity(var: MockObj, config: ConfigType, platform: str) -> None:
|
||||
"""Set up generic properties of an Entity.
|
||||
|
||||
This function sets up the common entity properties like name, icon,
|
||||
entity category, etc.
|
||||
|
||||
Args:
|
||||
var: The entity variable to set up
|
||||
config: Configuration dictionary containing entity settings
|
||||
platform: The platform name (e.g., "sensor", "binary_sensor")
|
||||
"""
|
||||
# Get device info
|
||||
device_name: str | None = None
|
||||
if CONF_DEVICE_ID in config:
|
||||
device_id_obj: ID = config[CONF_DEVICE_ID]
|
||||
device: MockObj = await get_variable(device_id_obj)
|
||||
add(var.set_device(device))
|
||||
# Get device name for object ID calculation
|
||||
device_name = device_id_obj.id
|
||||
|
||||
add(var.set_name(config[CONF_NAME]))
|
||||
|
||||
# Calculate base object_id using the same logic as C++
|
||||
# This must match the C++ behavior in esphome/core/entity_base.cpp
|
||||
base_object_id = get_base_entity_object_id(
|
||||
config[CONF_NAME], CORE.friendly_name, device_name
|
||||
)
|
||||
|
||||
if not config[CONF_NAME]:
|
||||
_LOGGER.debug(
|
||||
"Entity has empty name, using '%s' as object_id base", base_object_id
|
||||
)
|
||||
|
||||
# Set the object ID
|
||||
add(var.set_object_id(base_object_id))
|
||||
_LOGGER.debug(
|
||||
"Setting object_id '%s' for entity '%s' on platform '%s'",
|
||||
base_object_id,
|
||||
config[CONF_NAME],
|
||||
platform,
|
||||
)
|
||||
add(var.set_disabled_by_default(config[CONF_DISABLED_BY_DEFAULT]))
|
||||
if CONF_INTERNAL in config:
|
||||
add(var.set_internal(config[CONF_INTERNAL]))
|
||||
if CONF_ICON in config:
|
||||
add(var.set_icon(config[CONF_ICON]))
|
||||
if CONF_ENTITY_CATEGORY in config:
|
||||
add(var.set_entity_category(config[CONF_ENTITY_CATEGORY]))
|
||||
|
||||
|
||||
def inherit_property_from(property_to_inherit, parent_id_property, transform=None):
|
||||
@@ -54,3 +165,48 @@ def inherit_property_from(property_to_inherit, parent_id_property, transform=Non
|
||||
return config
|
||||
|
||||
return inherit_property
|
||||
|
||||
|
||||
def entity_duplicate_validator(platform: str) -> Callable[[ConfigType], ConfigType]:
|
||||
"""Create a validator function to check for duplicate entity names.
|
||||
|
||||
This validator is meant to be used with schema.add_extra() for entity base schemas.
|
||||
|
||||
Args:
|
||||
platform: The platform name (e.g., "sensor", "binary_sensor")
|
||||
|
||||
Returns:
|
||||
A validator function that checks for duplicate names
|
||||
"""
|
||||
|
||||
def validator(config: ConfigType) -> ConfigType:
|
||||
if CONF_NAME not in config:
|
||||
# No name to validate
|
||||
return config
|
||||
|
||||
# Get the entity name and device info
|
||||
entity_name = config[CONF_NAME]
|
||||
device_id = "" # Empty string for main device
|
||||
|
||||
if CONF_DEVICE_ID in config:
|
||||
device_id_obj = config[CONF_DEVICE_ID]
|
||||
# Use the device ID string directly for uniqueness
|
||||
device_id = device_id_obj.id
|
||||
|
||||
# For duplicate detection, just use the sanitized name
|
||||
name_key = sanitize(snake_case(entity_name))
|
||||
|
||||
# Check for duplicates
|
||||
unique_key = (device_id, platform, name_key)
|
||||
if unique_key in CORE.unique_ids:
|
||||
device_prefix = f" on device '{device_id}'" if device_id else ""
|
||||
raise cv.Invalid(
|
||||
f"Duplicate {platform} entity with name '{entity_name}' found{device_prefix}. "
|
||||
f"Each entity on a device must have a unique name within its platform."
|
||||
)
|
||||
|
||||
# Add to tracking set
|
||||
CORE.unique_ids.add(unique_key)
|
||||
return config
|
||||
|
||||
return validator
|
||||
|
||||
@@ -356,6 +356,10 @@ size_t parse_hex(const char *str, size_t length, uint8_t *data, size_t count) {
|
||||
return chars;
|
||||
}
|
||||
|
||||
std::string format_mac_address_pretty(const uint8_t *mac) {
|
||||
return str_snprintf("%02X:%02X:%02X:%02X:%02X:%02X", 17, mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
|
||||
}
|
||||
|
||||
static char format_hex_char(uint8_t v) { return v >= 10 ? 'a' + (v - 10) : '0' + v; }
|
||||
std::string format_hex(const uint8_t *data, size_t length) {
|
||||
std::string ret;
|
||||
@@ -732,7 +736,7 @@ std::string get_mac_address() {
|
||||
std::string get_mac_address_pretty() {
|
||||
uint8_t mac[6];
|
||||
get_mac_address_raw(mac);
|
||||
return str_snprintf("%02X:%02X:%02X:%02X:%02X:%02X", 17, mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
|
||||
return format_mac_address_pretty(mac);
|
||||
}
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
@@ -402,6 +402,8 @@ template<typename T, enable_if_t<std::is_unsigned<T>::value, int> = 0> optional<
|
||||
return parse_hex<T>(str.c_str(), str.length());
|
||||
}
|
||||
|
||||
/// Format the six-byte array \p mac into a MAC address.
|
||||
std::string format_mac_address_pretty(const uint8_t mac[6]);
|
||||
/// Format the byte array \p data of length \p len in lowercased hex.
|
||||
std::string format_hex(const uint8_t *data, size_t length);
|
||||
/// Format the vector \p data in lowercased hex.
|
||||
|
||||
@@ -1,11 +1,6 @@
|
||||
import logging
|
||||
|
||||
from esphome.const import (
|
||||
CONF_DISABLED_BY_DEFAULT,
|
||||
CONF_ENTITY_CATEGORY,
|
||||
CONF_ICON,
|
||||
CONF_INTERNAL,
|
||||
CONF_NAME,
|
||||
CONF_SAFE_MODE,
|
||||
CONF_SETUP_PRIORITY,
|
||||
CONF_TYPE_ID,
|
||||
@@ -16,7 +11,6 @@ from esphome.core import CORE, ID, coroutine
|
||||
from esphome.coroutine import FakeAwaitable
|
||||
from esphome.cpp_generator import add, get_variable
|
||||
from esphome.cpp_types import App
|
||||
from esphome.helpers import sanitize, snake_case
|
||||
from esphome.types import ConfigFragmentType, ConfigType
|
||||
from esphome.util import Registry, RegistryEntry
|
||||
|
||||
@@ -96,22 +90,6 @@ async def register_parented(var, value):
|
||||
add(var.set_parent(paren))
|
||||
|
||||
|
||||
async def setup_entity(var, config):
|
||||
"""Set up generic properties of an Entity"""
|
||||
add(var.set_name(config[CONF_NAME]))
|
||||
if not config[CONF_NAME]:
|
||||
add(var.set_object_id(sanitize(snake_case(CORE.friendly_name))))
|
||||
else:
|
||||
add(var.set_object_id(sanitize(snake_case(config[CONF_NAME]))))
|
||||
add(var.set_disabled_by_default(config[CONF_DISABLED_BY_DEFAULT]))
|
||||
if CONF_INTERNAL in config:
|
||||
add(var.set_internal(config[CONF_INTERNAL]))
|
||||
if CONF_ICON in config:
|
||||
add(var.set_icon(config[CONF_ICON]))
|
||||
if CONF_ENTITY_CATEGORY in config:
|
||||
add(var.set_entity_category(config[CONF_ENTITY_CATEGORY]))
|
||||
|
||||
|
||||
def extract_registry_entry_config(
|
||||
registry: Registry,
|
||||
full_config: ConfigType,
|
||||
|
||||
@@ -1,25 +1,9 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import unicodedata
|
||||
|
||||
from esphome.const import ALLOWED_NAME_CHARS
|
||||
from esphome.helpers import slugify
|
||||
|
||||
|
||||
def strip_accents(value):
|
||||
return "".join(
|
||||
c
|
||||
for c in unicodedata.normalize("NFD", str(value))
|
||||
if unicodedata.category(c) != "Mn"
|
||||
)
|
||||
|
||||
|
||||
def friendly_name_slugify(value):
|
||||
value = (
|
||||
strip_accents(value)
|
||||
.lower()
|
||||
.replace(" ", "-")
|
||||
.replace("_", "-")
|
||||
.replace("--", "-")
|
||||
.strip("-")
|
||||
)
|
||||
return "".join(c for c in value if c in ALLOWED_NAME_CHARS)
|
||||
def friendly_name_slugify(value: str) -> str:
|
||||
"""Convert a friendly name to a slug with dashes instead of underscores."""
|
||||
# First use the standard slugify, then convert underscores to dashes
|
||||
return slugify(value).replace("_", "-")
|
||||
|
||||
@@ -29,6 +29,53 @@ def ensure_unique_string(preferred_string, current_strings):
|
||||
return test_string
|
||||
|
||||
|
||||
def fnv1a_32bit_hash(string: str) -> int:
|
||||
"""FNV-1a 32-bit hash function.
|
||||
|
||||
Note: This uses 32-bit hash instead of 64-bit for several reasons:
|
||||
1. ESPHome targets 32-bit microcontrollers with limited RAM (often <320KB)
|
||||
2. Using 64-bit hashes would double the RAM usage for storing IDs
|
||||
3. 64-bit operations are slower on 32-bit processors
|
||||
|
||||
While there's a ~50% collision probability at ~77,000 unique IDs,
|
||||
ESPHome validates for collisions at compile time, preventing any
|
||||
runtime issues. In practice, most ESPHome installations only have
|
||||
a handful of area_ids and device_ids (typically <10 areas and <100
|
||||
devices), making collisions virtually impossible.
|
||||
"""
|
||||
hash_value = 2166136261
|
||||
for char in string:
|
||||
hash_value ^= ord(char)
|
||||
hash_value = (hash_value * 16777619) & 0xFFFFFFFF
|
||||
return hash_value
|
||||
|
||||
|
||||
def strip_accents(value: str) -> str:
|
||||
"""Remove accents from a string."""
|
||||
import unicodedata
|
||||
|
||||
return "".join(
|
||||
c
|
||||
for c in unicodedata.normalize("NFD", str(value))
|
||||
if unicodedata.category(c) != "Mn"
|
||||
)
|
||||
|
||||
|
||||
def slugify(value: str) -> str:
|
||||
"""Convert a string to a valid C++ identifier slug."""
|
||||
from esphome.const import ALLOWED_NAME_CHARS
|
||||
|
||||
value = (
|
||||
strip_accents(value)
|
||||
.lower()
|
||||
.replace(" ", "_")
|
||||
.replace("-", "_")
|
||||
.replace("__", "_")
|
||||
.strip("_")
|
||||
)
|
||||
return "".join(c for c in value if c in ALLOWED_NAME_CHARS)
|
||||
|
||||
|
||||
def indent_all_but_first_and_last(text, padding=" "):
|
||||
lines = text.splitlines(True)
|
||||
if len(lines) <= 2:
|
||||
|
||||
@@ -886,7 +886,7 @@ def build_message_type(
|
||||
public_content.append("#ifdef HAS_PROTO_MESSAGE_DUMP")
|
||||
snake_name = camel_to_snake(desc.name)
|
||||
public_content.append(
|
||||
f'static constexpr const char *message_name() {{ return "{snake_name}"; }}'
|
||||
f'const char *message_name() const override {{ return "{snake_name}"; }}'
|
||||
)
|
||||
public_content.append("#endif")
|
||||
|
||||
@@ -1356,7 +1356,7 @@ def main() -> None:
|
||||
hpp += " template<typename T>\n"
|
||||
hpp += " bool send_message(const T &msg) {\n"
|
||||
hpp += "#ifdef HAS_PROTO_MESSAGE_DUMP\n"
|
||||
hpp += " this->log_send_message_(T::message_name(), msg.dump());\n"
|
||||
hpp += " this->log_send_message_(msg.message_name(), msg.dump());\n"
|
||||
hpp += "#endif\n"
|
||||
hpp += " return this->send_message_(msg, T::MESSAGE_TYPE);\n"
|
||||
hpp += " }\n\n"
|
||||
|
||||
0
script/run-in-env.py
Normal file → Executable file
0
script/run-in-env.py
Normal file → Executable file
@@ -12,12 +12,12 @@ sensor:
|
||||
frequency: 60Hz
|
||||
phase_a:
|
||||
name: Channel A
|
||||
voltage: Voltage
|
||||
current: Current
|
||||
active_power: Active Power
|
||||
power_factor: Power Factor
|
||||
forward_active_energy: Forward Active Energy
|
||||
reverse_active_energy: Reverse Active Energy
|
||||
voltage: Channel A Voltage
|
||||
current: Channel A Current
|
||||
active_power: Channel A Active Power
|
||||
power_factor: Channel A Power Factor
|
||||
forward_active_energy: Channel A Forward Active Energy
|
||||
reverse_active_energy: Channel A Reverse Active Energy
|
||||
calibration:
|
||||
current_gain: 3116628
|
||||
voltage_gain: -757178
|
||||
@@ -25,12 +25,12 @@ sensor:
|
||||
phase_angle: 188
|
||||
phase_b:
|
||||
name: Channel B
|
||||
voltage: Voltage
|
||||
current: Current
|
||||
active_power: Active Power
|
||||
power_factor: Power Factor
|
||||
forward_active_energy: Forward Active Energy
|
||||
reverse_active_energy: Reverse Active Energy
|
||||
voltage: Channel B Voltage
|
||||
current: Channel B Current
|
||||
active_power: Channel B Active Power
|
||||
power_factor: Channel B Power Factor
|
||||
forward_active_energy: Channel B Forward Active Energy
|
||||
reverse_active_energy: Channel B Reverse Active Energy
|
||||
calibration:
|
||||
current_gain: 3133655
|
||||
voltage_gain: -755235
|
||||
@@ -38,12 +38,12 @@ sensor:
|
||||
phase_angle: 188
|
||||
phase_c:
|
||||
name: Channel C
|
||||
voltage: Voltage
|
||||
current: Current
|
||||
active_power: Active Power
|
||||
power_factor: Power Factor
|
||||
forward_active_energy: Forward Active Energy
|
||||
reverse_active_energy: Reverse Active Energy
|
||||
voltage: Channel C Voltage
|
||||
current: Channel C Current
|
||||
active_power: Channel C Active Power
|
||||
power_factor: Channel C Power Factor
|
||||
forward_active_energy: Channel C Forward Active Energy
|
||||
reverse_active_energy: Channel C Reverse Active Energy
|
||||
calibration:
|
||||
current_gain: 3111158
|
||||
voltage_gain: -743813
|
||||
@@ -51,6 +51,6 @@ sensor:
|
||||
phase_angle: 180
|
||||
neutral:
|
||||
name: Neutral
|
||||
current: Current
|
||||
current: Neutral Current
|
||||
calibration:
|
||||
current_gain: 3189
|
||||
|
||||
@@ -26,7 +26,7 @@ alarm_control_panel:
|
||||
ESP_LOGD("TEST", "State change %s", LOG_STR_ARG(alarm_control_panel_state_to_string(id(alarmcontrolpanel1)->get_state())));
|
||||
- platform: template
|
||||
id: alarmcontrolpanel2
|
||||
name: Alarm Panel
|
||||
name: Alarm Panel 2
|
||||
codes:
|
||||
- "1234"
|
||||
requires_code_to_arm: true
|
||||
|
||||
@@ -4,6 +4,31 @@ binary_sensor:
|
||||
id: some_binary_sensor
|
||||
name: "Random binary"
|
||||
lambda: return (random_uint32() & 1) == 0;
|
||||
filters:
|
||||
- invert:
|
||||
- delayed_on: 100ms
|
||||
- delayed_off: 100ms
|
||||
# Templated, delays for 1s (1000ms) only if a reed switch is active
|
||||
- delayed_on_off: !lambda "return 1000;"
|
||||
- delayed_on_off:
|
||||
time_on: 10s
|
||||
time_off: !lambda "return 1000;"
|
||||
- autorepeat:
|
||||
- delay: 1s
|
||||
time_off: 100ms
|
||||
time_on: 900ms
|
||||
- delay: 5s
|
||||
time_off: 100ms
|
||||
time_on: 400ms
|
||||
- lambda: |-
|
||||
if (id(some_binary_sensor).state) {
|
||||
return x;
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
- settle: 100ms
|
||||
- timeout: 10s
|
||||
|
||||
on_state_change:
|
||||
then:
|
||||
- logger.log:
|
||||
|
||||
@@ -26,7 +26,7 @@ binary_sensor:
|
||||
|
||||
sensor:
|
||||
- platform: binary_sensor_map
|
||||
name: Binary Sensor Map
|
||||
name: Binary Sensor Map Group
|
||||
type: group
|
||||
channels:
|
||||
- binary_sensor: bin1
|
||||
@@ -36,7 +36,7 @@ sensor:
|
||||
- binary_sensor: bin3
|
||||
value: 100.0
|
||||
- platform: binary_sensor_map
|
||||
name: Binary Sensor Map
|
||||
name: Binary Sensor Map Sum
|
||||
type: sum
|
||||
channels:
|
||||
- binary_sensor: bin1
|
||||
@@ -46,7 +46,7 @@ sensor:
|
||||
- binary_sensor: bin3
|
||||
value: 100.0
|
||||
- platform: binary_sensor_map
|
||||
name: Binary Sensor Map
|
||||
name: Binary Sensor Map Bayesian
|
||||
type: bayesian
|
||||
prior: 0.4
|
||||
observations:
|
||||
|
||||
@@ -5,7 +5,7 @@ one_wire:
|
||||
sensor:
|
||||
- platform: dallas_temp
|
||||
address: 0x1C0000031EDD2A28
|
||||
name: Dallas Temperature
|
||||
name: Dallas Temperature 1
|
||||
resolution: 9
|
||||
- platform: dallas_temp
|
||||
name: Dallas Temperature
|
||||
name: Dallas Temperature 2
|
||||
|
||||
@@ -2,7 +2,9 @@ esphome:
|
||||
debug_scheduler: true
|
||||
platformio_options:
|
||||
board_build.flash_mode: dio
|
||||
area: testing
|
||||
area:
|
||||
id: testing_area
|
||||
name: Testing Area
|
||||
on_boot:
|
||||
logger.log: on_boot
|
||||
on_shutdown:
|
||||
@@ -17,4 +19,20 @@ esphome:
|
||||
version: "1.1"
|
||||
on_update:
|
||||
logger.log: on_update
|
||||
areas:
|
||||
- id: another_area
|
||||
name: Another area
|
||||
devices:
|
||||
- id: other_device
|
||||
name: Another device
|
||||
area_id: another_area
|
||||
- id: test_device
|
||||
name: Test device in main area
|
||||
area_id: testing_area # Reference the main area (not in areas)
|
||||
- id: no_area_device
|
||||
name: Device without area # This device has no area_id
|
||||
|
||||
binary_sensor:
|
||||
- platform: template
|
||||
name: Other device sensor
|
||||
device_id: other_device
|
||||
|
||||
@@ -7,20 +7,20 @@ climate:
|
||||
protocol: mitsubishi_heavy_zm
|
||||
horizontal_default: left
|
||||
vertical_default: up
|
||||
name: HeatpumpIR Climate
|
||||
name: HeatpumpIR Climate Mitsubishi
|
||||
min_temperature: 18
|
||||
max_temperature: 30
|
||||
- platform: heatpumpir
|
||||
protocol: daikin
|
||||
horizontal_default: mleft
|
||||
vertical_default: mup
|
||||
name: HeatpumpIR Climate
|
||||
name: HeatpumpIR Climate Daikin
|
||||
min_temperature: 18
|
||||
max_temperature: 30
|
||||
- platform: heatpumpir
|
||||
protocol: panasonic_altdke
|
||||
horizontal_default: mright
|
||||
vertical_default: mdown
|
||||
name: HeatpumpIR Climate
|
||||
name: HeatpumpIR Climate Panasonic
|
||||
min_temperature: 18
|
||||
max_temperature: 30
|
||||
|
||||
@@ -114,7 +114,7 @@ light:
|
||||
warm_white_color_temperature: 500 mireds
|
||||
- platform: rgb
|
||||
id: test_rgb_light_initial_state
|
||||
name: RGB Light
|
||||
name: RGB Light Initial State
|
||||
red: test_ledc_1
|
||||
green: test_ledc_2
|
||||
blue: test_ledc_3
|
||||
|
||||
@@ -6,13 +6,13 @@ i2c:
|
||||
sensor:
|
||||
- platform: ltr390
|
||||
uv:
|
||||
name: LTR390 UV
|
||||
name: LTR390 UV 1
|
||||
uv_index:
|
||||
name: LTR390 UVI
|
||||
name: LTR390 UVI 1
|
||||
light:
|
||||
name: LTR390 Light
|
||||
name: LTR390 Light 1
|
||||
ambient_light:
|
||||
name: LTR390 ALS
|
||||
name: LTR390 ALS 1
|
||||
gain: X3
|
||||
resolution: 18
|
||||
window_correction_factor: 1.0
|
||||
@@ -20,13 +20,13 @@ sensor:
|
||||
update_interval: 60s
|
||||
- platform: ltr390
|
||||
uv:
|
||||
name: LTR390 UV
|
||||
name: LTR390 UV 2
|
||||
uv_index:
|
||||
name: LTR390 UVI
|
||||
name: LTR390 UVI 2
|
||||
light:
|
||||
name: LTR390 Light
|
||||
name: LTR390 Light 2
|
||||
ambient_light:
|
||||
name: LTR390 ALS
|
||||
name: LTR390 ALS 2
|
||||
gain:
|
||||
ambient_light: X9
|
||||
uv: X3
|
||||
|
||||
@@ -24,33 +24,33 @@ sensor:
|
||||
widget: lv_arc
|
||||
- platform: lvgl
|
||||
widget: slider_id
|
||||
name: LVGL Slider
|
||||
name: LVGL Slider Sensor
|
||||
- platform: lvgl
|
||||
widget: bar_id
|
||||
id: lvgl_bar_sensor
|
||||
name: LVGL Bar
|
||||
name: LVGL Bar Sensor
|
||||
- platform: lvgl
|
||||
widget: spinbox_id
|
||||
name: LVGL Spinbox
|
||||
name: LVGL Spinbox Sensor
|
||||
|
||||
number:
|
||||
- platform: lvgl
|
||||
widget: slider_id
|
||||
name: LVGL Slider
|
||||
name: LVGL Slider Number
|
||||
update_on_release: true
|
||||
restore_value: true
|
||||
- platform: lvgl
|
||||
widget: lv_arc
|
||||
id: lvgl_arc_number
|
||||
name: LVGL Arc
|
||||
name: LVGL Arc Number
|
||||
- platform: lvgl
|
||||
widget: bar_id
|
||||
id: lvgl_bar_number
|
||||
name: LVGL Bar
|
||||
name: LVGL Bar Number
|
||||
- platform: lvgl
|
||||
widget: spinbox_id
|
||||
id: lvgl_spinbox_number
|
||||
name: LVGL Spinbox
|
||||
name: LVGL Spinbox Number
|
||||
|
||||
light:
|
||||
- platform: lvgl
|
||||
|
||||
@@ -839,9 +839,7 @@ lvgl:
|
||||
styles: bdr_style
|
||||
grid_cell_x_align: center
|
||||
grid_cell_y_align: stretch
|
||||
grid_cell_row_pos: 0
|
||||
grid_cell_column_pos: 1
|
||||
grid_cell_column_span: 1
|
||||
grid_cell_column_span: 2
|
||||
text: "Grid cell 0/1"
|
||||
- label:
|
||||
grid_cell_x_align: end
|
||||
|
||||
@@ -170,4 +170,4 @@ switch:
|
||||
otc_active:
|
||||
name: "Boiler Outside temperature compensation active"
|
||||
ch2_active:
|
||||
name: "Boiler Central Heating 2 active"
|
||||
name: "Boiler Central Heating 2 active status"
|
||||
|
||||
@@ -5,7 +5,7 @@ packages:
|
||||
- !include package.yaml
|
||||
- github://esphome/esphome/tests/components/template/common.yaml@dev
|
||||
- url: https://github.com/esphome/esphome
|
||||
file: tests/components/binary_sensor_map/common.yaml
|
||||
file: tests/components/absolute_humidity/common.yaml
|
||||
ref: dev
|
||||
refresh: 1d
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ packages:
|
||||
shorthand: github://esphome/esphome/tests/components/template/common.yaml@dev
|
||||
github:
|
||||
url: https://github.com/esphome/esphome
|
||||
file: tests/components/binary_sensor_map/common.yaml
|
||||
file: tests/components/absolute_humidity/common.yaml
|
||||
ref: dev
|
||||
refresh: 1d
|
||||
|
||||
|
||||
@@ -115,7 +115,7 @@ button:
|
||||
address: 0x00
|
||||
command: 0x0B
|
||||
- platform: template
|
||||
name: RC5
|
||||
name: RC5 Raw
|
||||
on_press:
|
||||
remote_transmitter.transmit_raw:
|
||||
code: [1000, -1000]
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
using namespace esphome;
|
||||
|
||||
void setup() {
|
||||
App.pre_setup("livingroom", "LivingRoom", "LivingRoomArea", "comment", __DATE__ ", " __TIME__, false);
|
||||
App.pre_setup("livingroom", "LivingRoom", "comment", __DATE__ ", " __TIME__, false);
|
||||
auto *log = new logger::Logger(115200, 512); // NOLINT
|
||||
log->pre_setup();
|
||||
log->set_uart_selection(logger::UART_SELECTION_UART0);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user