mirror of
https://github.com/esphome/esphome.git
synced 2026-01-14 05:57:41 -07:00
Compare commits
34 Commits
action_cal
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9c5f4e5288 | ||
|
|
c8cc29a991 | ||
|
|
8b49d465f8 | ||
|
|
47ee2f4ad9 | ||
|
|
2793e33baf | ||
|
|
5dfdd05122 | ||
|
|
be12e3667a | ||
|
|
52c631384a | ||
|
|
45e000f091 | ||
|
|
e45cad45fe | ||
|
|
3d74d1e7f0 | ||
|
|
a060d1d044 | ||
|
|
733f57da50 | ||
|
|
4d96c60696 | ||
|
|
3d40979c96 | ||
|
|
7fed9144a6 | ||
|
|
7abb374f2a | ||
|
|
5d90f170e5 | ||
|
|
6e01c4f86e | ||
|
|
f4c17e15ea | ||
|
|
d6507ce329 | ||
|
|
9504e92458 | ||
|
|
3911991de2 | ||
|
|
dede47477b | ||
|
|
dca8def0f2 | ||
|
|
a1727a8901 | ||
|
|
48f5296d24 | ||
|
|
1327776d5b | ||
|
|
df4a3e8915 | ||
|
|
6823e17b3b | ||
|
|
675103bed0 | ||
|
|
6c043be4d3 | ||
|
|
e9469cbe48 | ||
|
|
5890cdf69a |
1
.github/PULL_REQUEST_TEMPLATE.md
vendored
1
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -27,6 +27,7 @@
|
||||
- [ ] RP2040
|
||||
- [ ] BK72xx
|
||||
- [ ] RTL87xx
|
||||
- [ ] LN882x
|
||||
- [ ] nRF52840
|
||||
|
||||
## Example entry for `config.yaml`:
|
||||
|
||||
4
.github/workflows/codeql.yml
vendored
4
.github/workflows/codeql.yml
vendored
@@ -58,7 +58,7 @@ jobs:
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v4.31.9
|
||||
uses: github/codeql-action/init@cdefb33c0f6224e58673d9004f47f7cb3e328b89 # v4.31.10
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
build-mode: ${{ matrix.build-mode }}
|
||||
@@ -86,6 +86,6 @@ jobs:
|
||||
exit 1
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v4.31.9
|
||||
uses: github/codeql-action/analyze@cdefb33c0f6224e58673d9004f47f7cb3e328b89 # v4.31.10
|
||||
with:
|
||||
category: "/language:${{matrix.language}}"
|
||||
|
||||
@@ -255,6 +255,7 @@ esphome/components/inkplate/* @jesserockz @JosipKuci
|
||||
esphome/components/integration/* @OttoWinter
|
||||
esphome/components/internal_temperature/* @Mat931
|
||||
esphome/components/interval/* @esphome/core
|
||||
esphome/components/ir_rf_proxy/* @kbx81
|
||||
esphome/components/jsn_sr04t/* @Mafus1
|
||||
esphome/components/json/* @esphome/core
|
||||
esphome/components/kamstrup_kmp/* @cfeenstra1024
|
||||
|
||||
@@ -4,6 +4,7 @@ import logging
|
||||
from esphome import automation
|
||||
from esphome.automation import Condition
|
||||
import esphome.codegen as cg
|
||||
from esphome.components.logger import request_log_listener
|
||||
from esphome.config_helpers import get_logger_level
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
@@ -326,6 +327,9 @@ async def to_code(config: ConfigType) -> None:
|
||||
# Track controller registration for StaticVector sizing
|
||||
CORE.register_controller()
|
||||
|
||||
# Request a log listener slot for API log streaming
|
||||
request_log_listener()
|
||||
|
||||
cg.add(var.set_port(config[CONF_PORT]))
|
||||
cg.add(var.set_reboot_timeout(config[CONF_REBOOT_TIMEOUT]))
|
||||
cg.add(var.set_batch_delay(config[CONF_BATCH_DELAY]))
|
||||
|
||||
@@ -265,8 +265,7 @@ void APIConnection::loop() {
|
||||
// If we can't send the ping request directly (tx_buffer full),
|
||||
// schedule it at the front of the batch so it will be sent with priority
|
||||
ESP_LOGW(TAG, "Buffer full, ping queued");
|
||||
this->schedule_message_front_(nullptr, &APIConnection::try_send_ping_request, PingRequest::MESSAGE_TYPE,
|
||||
PingRequest::ESTIMATED_SIZE);
|
||||
this->schedule_message_front_(nullptr, PingRequest::MESSAGE_TYPE, PingRequest::ESTIMATED_SIZE);
|
||||
this->flags_.sent_ping = true; // Mark as sent to avoid scheduling multiple pings
|
||||
}
|
||||
}
|
||||
@@ -305,7 +304,8 @@ uint16_t APIConnection::encode_message_to_buffer(ProtoMessage &msg, uint8_t mess
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
// If in log-only mode, just log and return
|
||||
if (conn->flags_.log_only_mode) {
|
||||
conn->log_send_message_(msg.message_name(), msg.dump());
|
||||
DumpBuffer dump_buf;
|
||||
conn->log_send_message_(msg.message_name(), msg.dump_to(dump_buf));
|
||||
return 1; // Return non-zero to indicate "success" for logging
|
||||
}
|
||||
#endif
|
||||
@@ -361,8 +361,8 @@ uint16_t APIConnection::encode_message_to_buffer(ProtoMessage &msg, uint8_t mess
|
||||
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
bool APIConnection::send_binary_sensor_state(binary_sensor::BinarySensor *binary_sensor) {
|
||||
return this->send_message_smart_(binary_sensor, &APIConnection::try_send_binary_sensor_state,
|
||||
BinarySensorStateResponse::MESSAGE_TYPE, BinarySensorStateResponse::ESTIMATED_SIZE);
|
||||
return this->send_message_smart_(binary_sensor, BinarySensorStateResponse::MESSAGE_TYPE,
|
||||
BinarySensorStateResponse::ESTIMATED_SIZE);
|
||||
}
|
||||
|
||||
uint16_t APIConnection::try_send_binary_sensor_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
|
||||
@@ -388,8 +388,7 @@ uint16_t APIConnection::try_send_binary_sensor_info(EntityBase *entity, APIConne
|
||||
|
||||
#ifdef USE_COVER
|
||||
bool APIConnection::send_cover_state(cover::Cover *cover) {
|
||||
return this->send_message_smart_(cover, &APIConnection::try_send_cover_state, CoverStateResponse::MESSAGE_TYPE,
|
||||
CoverStateResponse::ESTIMATED_SIZE);
|
||||
return this->send_message_smart_(cover, CoverStateResponse::MESSAGE_TYPE, CoverStateResponse::ESTIMATED_SIZE);
|
||||
}
|
||||
uint16_t APIConnection::try_send_cover_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
|
||||
bool is_single) {
|
||||
@@ -429,8 +428,7 @@ void APIConnection::cover_command(const CoverCommandRequest &msg) {
|
||||
|
||||
#ifdef USE_FAN
|
||||
bool APIConnection::send_fan_state(fan::Fan *fan) {
|
||||
return this->send_message_smart_(fan, &APIConnection::try_send_fan_state, FanStateResponse::MESSAGE_TYPE,
|
||||
FanStateResponse::ESTIMATED_SIZE);
|
||||
return this->send_message_smart_(fan, FanStateResponse::MESSAGE_TYPE, FanStateResponse::ESTIMATED_SIZE);
|
||||
}
|
||||
uint16_t APIConnection::try_send_fan_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
|
||||
bool is_single) {
|
||||
@@ -481,8 +479,7 @@ void APIConnection::fan_command(const FanCommandRequest &msg) {
|
||||
|
||||
#ifdef USE_LIGHT
|
||||
bool APIConnection::send_light_state(light::LightState *light) {
|
||||
return this->send_message_smart_(light, &APIConnection::try_send_light_state, LightStateResponse::MESSAGE_TYPE,
|
||||
LightStateResponse::ESTIMATED_SIZE);
|
||||
return this->send_message_smart_(light, LightStateResponse::MESSAGE_TYPE, LightStateResponse::ESTIMATED_SIZE);
|
||||
}
|
||||
uint16_t APIConnection::try_send_light_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
|
||||
bool is_single) {
|
||||
@@ -568,8 +565,7 @@ void APIConnection::light_command(const LightCommandRequest &msg) {
|
||||
|
||||
#ifdef USE_SENSOR
|
||||
bool APIConnection::send_sensor_state(sensor::Sensor *sensor) {
|
||||
return this->send_message_smart_(sensor, &APIConnection::try_send_sensor_state, SensorStateResponse::MESSAGE_TYPE,
|
||||
SensorStateResponse::ESTIMATED_SIZE);
|
||||
return this->send_message_smart_(sensor, SensorStateResponse::MESSAGE_TYPE, SensorStateResponse::ESTIMATED_SIZE);
|
||||
}
|
||||
|
||||
uint16_t APIConnection::try_send_sensor_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
|
||||
@@ -597,8 +593,7 @@ uint16_t APIConnection::try_send_sensor_info(EntityBase *entity, APIConnection *
|
||||
|
||||
#ifdef USE_SWITCH
|
||||
bool APIConnection::send_switch_state(switch_::Switch *a_switch) {
|
||||
return this->send_message_smart_(a_switch, &APIConnection::try_send_switch_state, SwitchStateResponse::MESSAGE_TYPE,
|
||||
SwitchStateResponse::ESTIMATED_SIZE);
|
||||
return this->send_message_smart_(a_switch, SwitchStateResponse::MESSAGE_TYPE, SwitchStateResponse::ESTIMATED_SIZE);
|
||||
}
|
||||
|
||||
uint16_t APIConnection::try_send_switch_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
|
||||
@@ -632,8 +627,8 @@ void APIConnection::switch_command(const SwitchCommandRequest &msg) {
|
||||
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
bool APIConnection::send_text_sensor_state(text_sensor::TextSensor *text_sensor) {
|
||||
return this->send_message_smart_(text_sensor, &APIConnection::try_send_text_sensor_state,
|
||||
TextSensorStateResponse::MESSAGE_TYPE, TextSensorStateResponse::ESTIMATED_SIZE);
|
||||
return this->send_message_smart_(text_sensor, TextSensorStateResponse::MESSAGE_TYPE,
|
||||
TextSensorStateResponse::ESTIMATED_SIZE);
|
||||
}
|
||||
|
||||
uint16_t APIConnection::try_send_text_sensor_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
|
||||
@@ -657,8 +652,7 @@ uint16_t APIConnection::try_send_text_sensor_info(EntityBase *entity, APIConnect
|
||||
|
||||
#ifdef USE_CLIMATE
|
||||
bool APIConnection::send_climate_state(climate::Climate *climate) {
|
||||
return this->send_message_smart_(climate, &APIConnection::try_send_climate_state, ClimateStateResponse::MESSAGE_TYPE,
|
||||
ClimateStateResponse::ESTIMATED_SIZE);
|
||||
return this->send_message_smart_(climate, ClimateStateResponse::MESSAGE_TYPE, ClimateStateResponse::ESTIMATED_SIZE);
|
||||
}
|
||||
uint16_t APIConnection::try_send_climate_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
|
||||
bool is_single) {
|
||||
@@ -753,8 +747,7 @@ void APIConnection::climate_command(const ClimateCommandRequest &msg) {
|
||||
|
||||
#ifdef USE_NUMBER
|
||||
bool APIConnection::send_number_state(number::Number *number) {
|
||||
return this->send_message_smart_(number, &APIConnection::try_send_number_state, NumberStateResponse::MESSAGE_TYPE,
|
||||
NumberStateResponse::ESTIMATED_SIZE);
|
||||
return this->send_message_smart_(number, NumberStateResponse::MESSAGE_TYPE, NumberStateResponse::ESTIMATED_SIZE);
|
||||
}
|
||||
|
||||
uint16_t APIConnection::try_send_number_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
|
||||
@@ -788,8 +781,7 @@ void APIConnection::number_command(const NumberCommandRequest &msg) {
|
||||
|
||||
#ifdef USE_DATETIME_DATE
|
||||
bool APIConnection::send_date_state(datetime::DateEntity *date) {
|
||||
return this->send_message_smart_(date, &APIConnection::try_send_date_state, DateStateResponse::MESSAGE_TYPE,
|
||||
DateStateResponse::ESTIMATED_SIZE);
|
||||
return this->send_message_smart_(date, DateStateResponse::MESSAGE_TYPE, DateStateResponse::ESTIMATED_SIZE);
|
||||
}
|
||||
uint16_t APIConnection::try_send_date_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
|
||||
bool is_single) {
|
||||
@@ -817,8 +809,7 @@ void APIConnection::date_command(const DateCommandRequest &msg) {
|
||||
|
||||
#ifdef USE_DATETIME_TIME
|
||||
bool APIConnection::send_time_state(datetime::TimeEntity *time) {
|
||||
return this->send_message_smart_(time, &APIConnection::try_send_time_state, TimeStateResponse::MESSAGE_TYPE,
|
||||
TimeStateResponse::ESTIMATED_SIZE);
|
||||
return this->send_message_smart_(time, TimeStateResponse::MESSAGE_TYPE, TimeStateResponse::ESTIMATED_SIZE);
|
||||
}
|
||||
uint16_t APIConnection::try_send_time_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
|
||||
bool is_single) {
|
||||
@@ -846,8 +837,8 @@ void APIConnection::time_command(const TimeCommandRequest &msg) {
|
||||
|
||||
#ifdef USE_DATETIME_DATETIME
|
||||
bool APIConnection::send_datetime_state(datetime::DateTimeEntity *datetime) {
|
||||
return this->send_message_smart_(datetime, &APIConnection::try_send_datetime_state,
|
||||
DateTimeStateResponse::MESSAGE_TYPE, DateTimeStateResponse::ESTIMATED_SIZE);
|
||||
return this->send_message_smart_(datetime, DateTimeStateResponse::MESSAGE_TYPE,
|
||||
DateTimeStateResponse::ESTIMATED_SIZE);
|
||||
}
|
||||
uint16_t APIConnection::try_send_datetime_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
|
||||
bool is_single) {
|
||||
@@ -877,8 +868,7 @@ void APIConnection::datetime_command(const DateTimeCommandRequest &msg) {
|
||||
|
||||
#ifdef USE_TEXT
|
||||
bool APIConnection::send_text_state(text::Text *text) {
|
||||
return this->send_message_smart_(text, &APIConnection::try_send_text_state, TextStateResponse::MESSAGE_TYPE,
|
||||
TextStateResponse::ESTIMATED_SIZE);
|
||||
return this->send_message_smart_(text, TextStateResponse::MESSAGE_TYPE, TextStateResponse::ESTIMATED_SIZE);
|
||||
}
|
||||
|
||||
uint16_t APIConnection::try_send_text_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
|
||||
@@ -910,8 +900,7 @@ void APIConnection::text_command(const TextCommandRequest &msg) {
|
||||
|
||||
#ifdef USE_SELECT
|
||||
bool APIConnection::send_select_state(select::Select *select) {
|
||||
return this->send_message_smart_(select, &APIConnection::try_send_select_state, SelectStateResponse::MESSAGE_TYPE,
|
||||
SelectStateResponse::ESTIMATED_SIZE);
|
||||
return this->send_message_smart_(select, SelectStateResponse::MESSAGE_TYPE, SelectStateResponse::ESTIMATED_SIZE);
|
||||
}
|
||||
|
||||
uint16_t APIConnection::try_send_select_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
|
||||
@@ -955,8 +944,7 @@ void esphome::api::APIConnection::button_command(const ButtonCommandRequest &msg
|
||||
|
||||
#ifdef USE_LOCK
|
||||
bool APIConnection::send_lock_state(lock::Lock *a_lock) {
|
||||
return this->send_message_smart_(a_lock, &APIConnection::try_send_lock_state, LockStateResponse::MESSAGE_TYPE,
|
||||
LockStateResponse::ESTIMATED_SIZE);
|
||||
return this->send_message_smart_(a_lock, LockStateResponse::MESSAGE_TYPE, LockStateResponse::ESTIMATED_SIZE);
|
||||
}
|
||||
|
||||
uint16_t APIConnection::try_send_lock_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
|
||||
@@ -996,8 +984,7 @@ void APIConnection::lock_command(const LockCommandRequest &msg) {
|
||||
|
||||
#ifdef USE_VALVE
|
||||
bool APIConnection::send_valve_state(valve::Valve *valve) {
|
||||
return this->send_message_smart_(valve, &APIConnection::try_send_valve_state, ValveStateResponse::MESSAGE_TYPE,
|
||||
ValveStateResponse::ESTIMATED_SIZE);
|
||||
return this->send_message_smart_(valve, ValveStateResponse::MESSAGE_TYPE, ValveStateResponse::ESTIMATED_SIZE);
|
||||
}
|
||||
uint16_t APIConnection::try_send_valve_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
|
||||
bool is_single) {
|
||||
@@ -1031,8 +1018,8 @@ void APIConnection::valve_command(const ValveCommandRequest &msg) {
|
||||
|
||||
#ifdef USE_MEDIA_PLAYER
|
||||
bool APIConnection::send_media_player_state(media_player::MediaPlayer *media_player) {
|
||||
return this->send_message_smart_(media_player, &APIConnection::try_send_media_player_state,
|
||||
MediaPlayerStateResponse::MESSAGE_TYPE, MediaPlayerStateResponse::ESTIMATED_SIZE);
|
||||
return this->send_message_smart_(media_player, MediaPlayerStateResponse::MESSAGE_TYPE,
|
||||
MediaPlayerStateResponse::ESTIMATED_SIZE);
|
||||
}
|
||||
uint16_t APIConnection::try_send_media_player_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
|
||||
bool is_single) {
|
||||
@@ -1314,8 +1301,7 @@ void APIConnection::zwave_proxy_request(const ZWaveProxyRequest &msg) {
|
||||
|
||||
#ifdef USE_ALARM_CONTROL_PANEL
|
||||
bool APIConnection::send_alarm_control_panel_state(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) {
|
||||
return this->send_message_smart_(a_alarm_control_panel, &APIConnection::try_send_alarm_control_panel_state,
|
||||
AlarmControlPanelStateResponse::MESSAGE_TYPE,
|
||||
return this->send_message_smart_(a_alarm_control_panel, AlarmControlPanelStateResponse::MESSAGE_TYPE,
|
||||
AlarmControlPanelStateResponse::ESTIMATED_SIZE);
|
||||
}
|
||||
uint16_t APIConnection::try_send_alarm_control_panel_state(EntityBase *entity, APIConnection *conn,
|
||||
@@ -1368,8 +1354,8 @@ void APIConnection::alarm_control_panel_command(const AlarmControlPanelCommandRe
|
||||
|
||||
#ifdef USE_WATER_HEATER
|
||||
bool APIConnection::send_water_heater_state(water_heater::WaterHeater *water_heater) {
|
||||
return this->send_message_smart_(water_heater, &APIConnection::try_send_water_heater_state,
|
||||
WaterHeaterStateResponse::MESSAGE_TYPE, WaterHeaterStateResponse::ESTIMATED_SIZE);
|
||||
return this->send_message_smart_(water_heater, WaterHeaterStateResponse::MESSAGE_TYPE,
|
||||
WaterHeaterStateResponse::ESTIMATED_SIZE);
|
||||
}
|
||||
uint16_t APIConnection::try_send_water_heater_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
|
||||
bool is_single) {
|
||||
@@ -1418,10 +1404,11 @@ void APIConnection::on_water_heater_command_request(const WaterHeaterCommandRequ
|
||||
#endif
|
||||
|
||||
#ifdef USE_EVENT
|
||||
void APIConnection::send_event(event::Event *event, StringRef event_type) {
|
||||
// get_last_event_type() returns StringRef pointing to null-terminated string literals from codegen
|
||||
this->send_message_smart_(event, MessageCreator(event_type.c_str()), EventResponse::MESSAGE_TYPE,
|
||||
EventResponse::ESTIMATED_SIZE);
|
||||
// Event is a special case - unlike other entities with simple state fields,
|
||||
// events store their state in a member accessed via obj->get_last_event_type()
|
||||
void APIConnection::send_event(event::Event *event) {
|
||||
this->send_message_smart_(event, EventResponse::MESSAGE_TYPE, EventResponse::ESTIMATED_SIZE,
|
||||
event->get_last_event_type_index());
|
||||
}
|
||||
uint16_t APIConnection::try_send_event_response(event::Event *event, StringRef event_type, APIConnection *conn,
|
||||
uint32_t remaining_size, bool is_single) {
|
||||
@@ -1472,8 +1459,7 @@ uint16_t APIConnection::try_send_infrared_info(EntityBase *entity, APIConnection
|
||||
|
||||
#ifdef USE_UPDATE
|
||||
bool APIConnection::send_update_state(update::UpdateEntity *update) {
|
||||
return this->send_message_smart_(update, &APIConnection::try_send_update_state, UpdateStateResponse::MESSAGE_TYPE,
|
||||
UpdateStateResponse::ESTIMATED_SIZE);
|
||||
return this->send_message_smart_(update, UpdateStateResponse::MESSAGE_TYPE, UpdateStateResponse::ESTIMATED_SIZE);
|
||||
}
|
||||
uint16_t APIConnection::try_send_update_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
|
||||
bool is_single) {
|
||||
@@ -1896,30 +1882,31 @@ void APIConnection::on_fatal_error() {
|
||||
this->flags_.remove = true;
|
||||
}
|
||||
|
||||
void APIConnection::DeferredBatch::add_item(EntityBase *entity, MessageCreator creator, uint8_t message_type,
|
||||
uint8_t estimated_size) {
|
||||
void APIConnection::DeferredBatch::add_item(EntityBase *entity, uint8_t message_type, uint8_t estimated_size,
|
||||
uint8_t aux_data_index) {
|
||||
// Check if we already have a message of this type for this entity
|
||||
// This provides deduplication per entity/message_type combination
|
||||
// O(n) but optimized for RAM and not performance.
|
||||
for (auto &item : items) {
|
||||
if (item.entity == entity && item.message_type == message_type) {
|
||||
// Replace with new creator
|
||||
item.creator = creator;
|
||||
return;
|
||||
// Skip deduplication for events - they are edge-triggered, every occurrence matters
|
||||
#ifdef USE_EVENT
|
||||
if (message_type != EventResponse::MESSAGE_TYPE)
|
||||
#endif
|
||||
{
|
||||
for (const auto &item : items) {
|
||||
if (item.entity == entity && item.message_type == message_type)
|
||||
return; // Already queued
|
||||
}
|
||||
}
|
||||
|
||||
// No existing item found, add new one
|
||||
items.emplace_back(entity, creator, message_type, estimated_size);
|
||||
// No existing item found (or event), add new one
|
||||
items.push_back({entity, message_type, estimated_size, aux_data_index});
|
||||
}
|
||||
|
||||
void APIConnection::DeferredBatch::add_item_front(EntityBase *entity, MessageCreator creator, uint8_t message_type,
|
||||
uint8_t estimated_size) {
|
||||
void APIConnection::DeferredBatch::add_item_front(EntityBase *entity, uint8_t message_type, uint8_t estimated_size) {
|
||||
// Add high priority message and swap to front
|
||||
// This avoids expensive vector::insert which shifts all elements
|
||||
// Note: We only ever have one high-priority message at a time (ping OR disconnect)
|
||||
// If we're disconnecting, pings are blocked, so this simple swap is sufficient
|
||||
items.emplace_back(entity, creator, message_type, estimated_size);
|
||||
items.push_back({entity, message_type, estimated_size, AUX_DATA_UNUSED});
|
||||
if (items.size() > 1) {
|
||||
// Swap the new high-priority item to the front
|
||||
std::swap(items.front(), items.back());
|
||||
@@ -1958,19 +1945,17 @@ void APIConnection::process_batch_() {
|
||||
if (num_items == 1) {
|
||||
const auto &item = this->deferred_batch_[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, item.message_type);
|
||||
// Let dispatch_message_ calculate size and encode if it fits
|
||||
uint16_t payload_size = this->dispatch_message_(item, std::numeric_limits<uint16_t>::max(), true);
|
||||
|
||||
if (payload_size > 0 && this->send_buffer(ProtoWriteBuffer{&shared_buf}, item.message_type)) {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
// Log messages after send attempt for VV debugging
|
||||
// It's safe to use the buffer for logging at this point regardless of send result
|
||||
// Log message after send attempt for VV debugging
|
||||
this->log_batch_item_(item);
|
||||
#endif
|
||||
this->clear_batch_();
|
||||
} else if (payload_size == 0) {
|
||||
// Message too large
|
||||
// Message too large to fit in available space
|
||||
ESP_LOGW(TAG, "Message too large to send: type=%u", item.message_type);
|
||||
this->clear_batch_();
|
||||
}
|
||||
@@ -2015,9 +2000,9 @@ void APIConnection::process_batch_() {
|
||||
// Process items and encode directly to buffer (up to our limit)
|
||||
for (size_t i = 0; i < messages_to_process; i++) {
|
||||
const auto &item = this->deferred_batch_[i];
|
||||
// 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, item.message_type);
|
||||
// Try to encode message via dispatch
|
||||
// The dispatch function calculates overhead to determine if the message fits
|
||||
uint16_t payload_size = this->dispatch_message_(item, remaining_size, false);
|
||||
|
||||
if (payload_size == 0) {
|
||||
// Message won't fit, stop processing
|
||||
@@ -2083,18 +2068,129 @@ void APIConnection::process_batch_() {
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t APIConnection::MessageCreator::operator()(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
|
||||
bool is_single, uint8_t message_type) const {
|
||||
// Dispatch message encoding based on message_type
|
||||
// Switch assigns function pointer, single call site for smaller code size
|
||||
uint16_t APIConnection::dispatch_message_(const DeferredBatch::BatchItem &item, uint32_t remaining_size,
|
||||
bool is_single) {
|
||||
#ifdef USE_EVENT
|
||||
// Special case: EventResponse uses const char * pointer
|
||||
if (message_type == EventResponse::MESSAGE_TYPE) {
|
||||
auto *e = static_cast<event::Event *>(entity);
|
||||
return APIConnection::try_send_event_response(e, StringRef(data_.const_char_ptr), conn, remaining_size, is_single);
|
||||
// Events need aux_data_index to look up event type from entity
|
||||
if (item.message_type == EventResponse::MESSAGE_TYPE) {
|
||||
// Skip if aux_data_index is invalid (should never happen in normal operation)
|
||||
if (item.aux_data_index == DeferredBatch::AUX_DATA_UNUSED)
|
||||
return 0;
|
||||
auto *event = static_cast<event::Event *>(item.entity);
|
||||
return try_send_event_response(event, StringRef::from_maybe_nullptr(event->get_event_type(item.aux_data_index)),
|
||||
this, remaining_size, is_single);
|
||||
}
|
||||
#endif
|
||||
|
||||
// All other message types use function pointers
|
||||
return data_.function_ptr(entity, conn, remaining_size, is_single);
|
||||
// All other message types use function pointer lookup via switch
|
||||
MessageCreatorPtr func = nullptr;
|
||||
|
||||
// Macros to reduce repetitive switch cases
|
||||
#define CASE_STATE_INFO(entity_name, StateResp, InfoResp) \
|
||||
case StateResp::MESSAGE_TYPE: \
|
||||
func = &try_send_##entity_name##_state; \
|
||||
break; \
|
||||
case InfoResp::MESSAGE_TYPE: \
|
||||
func = &try_send_##entity_name##_info; \
|
||||
break;
|
||||
#define CASE_INFO_ONLY(entity_name, InfoResp) \
|
||||
case InfoResp::MESSAGE_TYPE: \
|
||||
func = &try_send_##entity_name##_info; \
|
||||
break;
|
||||
|
||||
switch (item.message_type) {
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
CASE_STATE_INFO(binary_sensor, BinarySensorStateResponse, ListEntitiesBinarySensorResponse)
|
||||
#endif
|
||||
#ifdef USE_COVER
|
||||
CASE_STATE_INFO(cover, CoverStateResponse, ListEntitiesCoverResponse)
|
||||
#endif
|
||||
#ifdef USE_FAN
|
||||
CASE_STATE_INFO(fan, FanStateResponse, ListEntitiesFanResponse)
|
||||
#endif
|
||||
#ifdef USE_LIGHT
|
||||
CASE_STATE_INFO(light, LightStateResponse, ListEntitiesLightResponse)
|
||||
#endif
|
||||
#ifdef USE_SENSOR
|
||||
CASE_STATE_INFO(sensor, SensorStateResponse, ListEntitiesSensorResponse)
|
||||
#endif
|
||||
#ifdef USE_SWITCH
|
||||
CASE_STATE_INFO(switch, SwitchStateResponse, ListEntitiesSwitchResponse)
|
||||
#endif
|
||||
#ifdef USE_BUTTON
|
||||
CASE_INFO_ONLY(button, ListEntitiesButtonResponse)
|
||||
#endif
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
CASE_STATE_INFO(text_sensor, TextSensorStateResponse, ListEntitiesTextSensorResponse)
|
||||
#endif
|
||||
#ifdef USE_CLIMATE
|
||||
CASE_STATE_INFO(climate, ClimateStateResponse, ListEntitiesClimateResponse)
|
||||
#endif
|
||||
#ifdef USE_NUMBER
|
||||
CASE_STATE_INFO(number, NumberStateResponse, ListEntitiesNumberResponse)
|
||||
#endif
|
||||
#ifdef USE_DATETIME_DATE
|
||||
CASE_STATE_INFO(date, DateStateResponse, ListEntitiesDateResponse)
|
||||
#endif
|
||||
#ifdef USE_DATETIME_TIME
|
||||
CASE_STATE_INFO(time, TimeStateResponse, ListEntitiesTimeResponse)
|
||||
#endif
|
||||
#ifdef USE_DATETIME_DATETIME
|
||||
CASE_STATE_INFO(datetime, DateTimeStateResponse, ListEntitiesDateTimeResponse)
|
||||
#endif
|
||||
#ifdef USE_TEXT
|
||||
CASE_STATE_INFO(text, TextStateResponse, ListEntitiesTextResponse)
|
||||
#endif
|
||||
#ifdef USE_SELECT
|
||||
CASE_STATE_INFO(select, SelectStateResponse, ListEntitiesSelectResponse)
|
||||
#endif
|
||||
#ifdef USE_LOCK
|
||||
CASE_STATE_INFO(lock, LockStateResponse, ListEntitiesLockResponse)
|
||||
#endif
|
||||
#ifdef USE_VALVE
|
||||
CASE_STATE_INFO(valve, ValveStateResponse, ListEntitiesValveResponse)
|
||||
#endif
|
||||
#ifdef USE_MEDIA_PLAYER
|
||||
CASE_STATE_INFO(media_player, MediaPlayerStateResponse, ListEntitiesMediaPlayerResponse)
|
||||
#endif
|
||||
#ifdef USE_ALARM_CONTROL_PANEL
|
||||
CASE_STATE_INFO(alarm_control_panel, AlarmControlPanelStateResponse, ListEntitiesAlarmControlPanelResponse)
|
||||
#endif
|
||||
#ifdef USE_WATER_HEATER
|
||||
CASE_STATE_INFO(water_heater, WaterHeaterStateResponse, ListEntitiesWaterHeaterResponse)
|
||||
#endif
|
||||
#ifdef USE_CAMERA
|
||||
CASE_INFO_ONLY(camera, ListEntitiesCameraResponse)
|
||||
#endif
|
||||
#ifdef USE_INFRARED
|
||||
CASE_INFO_ONLY(infrared, ListEntitiesInfraredResponse)
|
||||
#endif
|
||||
#ifdef USE_EVENT
|
||||
CASE_INFO_ONLY(event, ListEntitiesEventResponse)
|
||||
#endif
|
||||
#ifdef USE_UPDATE
|
||||
CASE_STATE_INFO(update, UpdateStateResponse, ListEntitiesUpdateResponse)
|
||||
#endif
|
||||
// Special messages (not entity state/info)
|
||||
case ListEntitiesDoneResponse::MESSAGE_TYPE:
|
||||
func = &try_send_list_info_done;
|
||||
break;
|
||||
case DisconnectRequest::MESSAGE_TYPE:
|
||||
func = &try_send_disconnect_request;
|
||||
break;
|
||||
case PingRequest::MESSAGE_TYPE:
|
||||
func = &try_send_ping_request;
|
||||
break;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
|
||||
#undef CASE_STATE_INFO
|
||||
#undef CASE_INFO_ONLY
|
||||
|
||||
return func(item.entity, this, remaining_size, is_single);
|
||||
}
|
||||
|
||||
uint16_t APIConnection::try_send_list_info_done(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "esphome/core/string_ref.h"
|
||||
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
#include <vector>
|
||||
|
||||
namespace esphome::api {
|
||||
@@ -38,8 +39,8 @@ class APIConnection final : public APIServerConnection {
|
||||
void loop();
|
||||
|
||||
bool send_list_info_done() {
|
||||
return this->schedule_message_(nullptr, &APIConnection::try_send_list_info_done,
|
||||
ListEntitiesDoneResponse::MESSAGE_TYPE, ListEntitiesDoneResponse::ESTIMATED_SIZE);
|
||||
return this->schedule_message_(nullptr, ListEntitiesDoneResponse::MESSAGE_TYPE,
|
||||
ListEntitiesDoneResponse::ESTIMATED_SIZE);
|
||||
}
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
bool send_binary_sensor_state(binary_sensor::BinarySensor *binary_sensor);
|
||||
@@ -178,7 +179,7 @@ class APIConnection final : public APIServerConnection {
|
||||
#endif
|
||||
|
||||
#ifdef USE_EVENT
|
||||
void send_event(event::Event *event, StringRef event_type);
|
||||
void send_event(event::Event *event);
|
||||
#endif
|
||||
|
||||
#ifdef USE_UPDATE
|
||||
@@ -540,33 +541,17 @@ class APIConnection final : public APIServerConnection {
|
||||
// Function pointer type for message encoding
|
||||
using MessageCreatorPtr = uint16_t (*)(EntityBase *, APIConnection *, uint32_t remaining_size, bool is_single);
|
||||
|
||||
class MessageCreator {
|
||||
public:
|
||||
MessageCreator(MessageCreatorPtr ptr) { data_.function_ptr = ptr; }
|
||||
explicit MessageCreator(const char *str_value) { data_.const_char_ptr = str_value; }
|
||||
|
||||
// Call operator - uses message_type to determine union type
|
||||
uint16_t operator()(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single,
|
||||
uint8_t message_type) const;
|
||||
|
||||
private:
|
||||
union Data {
|
||||
MessageCreatorPtr function_ptr;
|
||||
const char *const_char_ptr;
|
||||
} data_; // 4 bytes on 32-bit, 8 bytes on 64-bit
|
||||
};
|
||||
|
||||
// Generic batching mechanism for both state updates and entity info
|
||||
struct DeferredBatch {
|
||||
struct BatchItem {
|
||||
EntityBase *entity; // Entity pointer
|
||||
MessageCreator creator; // Function that creates the message when needed
|
||||
uint8_t message_type; // Message type for overhead calculation (max 255)
|
||||
uint8_t estimated_size; // Estimated message size (max 255 bytes)
|
||||
// Sentinel value for unused aux_data_index
|
||||
static constexpr uint8_t AUX_DATA_UNUSED = std::numeric_limits<uint8_t>::max();
|
||||
|
||||
// Constructor for creating BatchItem
|
||||
BatchItem(EntityBase *entity, MessageCreator creator, uint8_t message_type, uint8_t estimated_size)
|
||||
: entity(entity), creator(creator), message_type(message_type), estimated_size(estimated_size) {}
|
||||
struct BatchItem {
|
||||
EntityBase *entity; // 4 bytes - Entity pointer
|
||||
uint8_t message_type; // 1 byte - Message type for protocol and dispatch
|
||||
uint8_t estimated_size; // 1 byte - Estimated message size (max 255 bytes)
|
||||
uint8_t aux_data_index{AUX_DATA_UNUSED}; // 1 byte - For events: index into entity's event_types
|
||||
// 1 byte padding
|
||||
};
|
||||
|
||||
std::vector<BatchItem> items;
|
||||
@@ -575,10 +560,11 @@ class APIConnection final : public APIServerConnection {
|
||||
// No pre-allocation - log connections never use batching, and for
|
||||
// connections that do, buffers are released after initial sync anyway
|
||||
|
||||
// Add item to the batch
|
||||
void add_item(EntityBase *entity, MessageCreator creator, uint8_t message_type, uint8_t estimated_size);
|
||||
// Add item to the batch (with deduplication)
|
||||
void add_item(EntityBase *entity, uint8_t message_type, uint8_t estimated_size,
|
||||
uint8_t aux_data_index = AUX_DATA_UNUSED);
|
||||
// Add item to the front of the batch (for high priority messages like ping)
|
||||
void add_item_front(EntityBase *entity, MessageCreator creator, uint8_t message_type, uint8_t estimated_size);
|
||||
void add_item_front(EntityBase *entity, uint8_t message_type, uint8_t estimated_size);
|
||||
|
||||
// Clear all items
|
||||
void clear() {
|
||||
@@ -592,6 +578,7 @@ class APIConnection final : public APIServerConnection {
|
||||
bool empty() const { return items.empty(); }
|
||||
size_t size() const { return items.size(); }
|
||||
const BatchItem &operator[](size_t index) const { return items[index]; }
|
||||
|
||||
// Release excess capacity - only releases if items already empty
|
||||
void release_buffer() {
|
||||
// Safe to call: batch is processed before release_buffer is called,
|
||||
@@ -663,17 +650,15 @@ class APIConnection final : public APIServerConnection {
|
||||
this->flags_.batch_scheduled = false;
|
||||
}
|
||||
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
// Helper to log a proto message from a MessageCreator object
|
||||
void log_proto_message_(EntityBase *entity, const MessageCreator &creator, uint8_t message_type) {
|
||||
this->flags_.log_only_mode = true;
|
||||
creator(entity, this, MAX_BATCH_PACKET_SIZE, true, message_type);
|
||||
this->flags_.log_only_mode = false;
|
||||
}
|
||||
// Dispatch message encoding based on message_type - replaces function pointer storage
|
||||
// Switch assigns pointer, single call site for smaller code size
|
||||
uint16_t dispatch_message_(const DeferredBatch::BatchItem &item, uint32_t remaining_size, bool is_single);
|
||||
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void log_batch_item_(const DeferredBatch::BatchItem &item) {
|
||||
// Use the helper to log the message
|
||||
this->log_proto_message_(item.entity, item.creator, item.message_type);
|
||||
this->flags_.log_only_mode = true;
|
||||
this->dispatch_message_(item, MAX_BATCH_PACKET_SIZE, true);
|
||||
this->flags_.log_only_mode = false;
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -698,63 +683,31 @@ class APIConnection final : public APIServerConnection {
|
||||
// Helper method to send a message either immediately or via batching
|
||||
// Tries immediate send if should_send_immediately_() returns true and buffer has space
|
||||
// Falls back to batching if immediate send fails or isn't applicable
|
||||
bool send_message_smart_(EntityBase *entity, MessageCreatorPtr creator, uint8_t message_type,
|
||||
uint8_t estimated_size) {
|
||||
bool send_message_smart_(EntityBase *entity, uint8_t message_type, uint8_t estimated_size,
|
||||
uint8_t aux_data_index = DeferredBatch::AUX_DATA_UNUSED) {
|
||||
if (this->should_send_immediately_(message_type) && this->helper_->can_write_without_blocking()) {
|
||||
// Now actually encode and send
|
||||
if (creator(entity, this, MAX_BATCH_PACKET_SIZE, true) &&
|
||||
DeferredBatch::BatchItem item{entity, message_type, estimated_size, aux_data_index};
|
||||
if (this->dispatch_message_(item, MAX_BATCH_PACKET_SIZE, true) &&
|
||||
this->send_buffer(ProtoWriteBuffer{&this->parent_->get_shared_buffer_ref()}, message_type)) {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
// Log the message in verbose mode
|
||||
this->log_proto_message_(entity, MessageCreator(creator), message_type);
|
||||
this->log_batch_item_(item);
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
// If immediate send failed, fall through to batching
|
||||
}
|
||||
|
||||
// Fall back to scheduled batching
|
||||
return this->schedule_message_(entity, creator, message_type, estimated_size);
|
||||
}
|
||||
|
||||
// Overload for MessageCreator (used by events which need to capture event_type)
|
||||
bool send_message_smart_(EntityBase *entity, MessageCreator creator, uint8_t message_type, uint8_t estimated_size) {
|
||||
// Try to send immediately if message type should bypass batching and buffer has space
|
||||
if (this->should_send_immediately_(message_type) && this->helper_->can_write_without_blocking()) {
|
||||
// Now actually encode and send
|
||||
if (creator(entity, this, MAX_BATCH_PACKET_SIZE, true, message_type) &&
|
||||
this->send_buffer(ProtoWriteBuffer{&this->parent_->get_shared_buffer_ref()}, message_type)) {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
// Log the message in verbose mode
|
||||
this->log_proto_message_(entity, creator, message_type);
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
// If immediate send failed, fall through to batching
|
||||
}
|
||||
|
||||
// Fall back to scheduled batching
|
||||
return this->schedule_message_(entity, creator, message_type, estimated_size);
|
||||
return this->schedule_message_(entity, message_type, estimated_size, aux_data_index);
|
||||
}
|
||||
|
||||
// Helper function to schedule a deferred message with known message type
|
||||
bool schedule_message_(EntityBase *entity, MessageCreator creator, uint8_t message_type, uint8_t estimated_size) {
|
||||
this->deferred_batch_.add_item(entity, creator, message_type, estimated_size);
|
||||
bool schedule_message_(EntityBase *entity, uint8_t message_type, uint8_t estimated_size,
|
||||
uint8_t aux_data_index = DeferredBatch::AUX_DATA_UNUSED) {
|
||||
this->deferred_batch_.add_item(entity, message_type, estimated_size, aux_data_index);
|
||||
return this->schedule_batch_();
|
||||
}
|
||||
|
||||
// Overload for function pointers (for info messages and current state reads)
|
||||
bool schedule_message_(EntityBase *entity, MessageCreatorPtr function_ptr, uint8_t message_type,
|
||||
uint8_t estimated_size) {
|
||||
return schedule_message_(entity, MessageCreator(function_ptr), message_type, estimated_size);
|
||||
}
|
||||
|
||||
// Helper function to schedule a high priority message at the front of the batch
|
||||
bool schedule_message_front_(EntityBase *entity, MessageCreatorPtr function_ptr, uint8_t message_type,
|
||||
uint8_t estimated_size) {
|
||||
this->deferred_batch_.add_item_front(entity, MessageCreator(function_ptr), message_type, estimated_size);
|
||||
bool schedule_message_front_(EntityBase *entity, uint8_t message_type, uint8_t estimated_size) {
|
||||
this->deferred_batch_.add_item_front(entity, message_type, estimated_size);
|
||||
return this->schedule_batch_();
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -8,8 +8,12 @@ namespace esphome::api {
|
||||
static const char *const TAG = "api.service";
|
||||
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void APIServerConnectionBase::log_send_message_(const char *name, const std::string &dump) {
|
||||
ESP_LOGVV(TAG, "send_message %s: %s", name, dump.c_str());
|
||||
void APIServerConnectionBase::log_send_message_(const char *name, const char *dump) {
|
||||
ESP_LOGVV(TAG, "send_message %s: %s", name, dump);
|
||||
}
|
||||
void APIServerConnectionBase::log_receive_message_(const LogString *name, const ProtoMessage &msg) {
|
||||
DumpBuffer dump_buf;
|
||||
ESP_LOGVV(TAG, "%s: %s", LOG_STR_ARG(name), msg.dump_to(dump_buf));
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -19,7 +23,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
||||
HelloRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_hello_request: %s", msg.dump().c_str());
|
||||
this->log_receive_message_(LOG_STR("on_hello_request"), msg);
|
||||
#endif
|
||||
this->on_hello_request(msg);
|
||||
break;
|
||||
@@ -28,7 +32,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
||||
DisconnectRequest msg;
|
||||
// Empty message: no decode needed
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_disconnect_request: %s", msg.dump().c_str());
|
||||
this->log_receive_message_(LOG_STR("on_disconnect_request"), msg);
|
||||
#endif
|
||||
this->on_disconnect_request(msg);
|
||||
break;
|
||||
@@ -37,7 +41,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
||||
DisconnectResponse msg;
|
||||
// Empty message: no decode needed
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_disconnect_response: %s", msg.dump().c_str());
|
||||
this->log_receive_message_(LOG_STR("on_disconnect_response"), msg);
|
||||
#endif
|
||||
this->on_disconnect_response(msg);
|
||||
break;
|
||||
@@ -46,7 +50,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
||||
PingRequest msg;
|
||||
// Empty message: no decode needed
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_ping_request: %s", msg.dump().c_str());
|
||||
this->log_receive_message_(LOG_STR("on_ping_request"), msg);
|
||||
#endif
|
||||
this->on_ping_request(msg);
|
||||
break;
|
||||
@@ -55,7 +59,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
||||
PingResponse msg;
|
||||
// Empty message: no decode needed
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_ping_response: %s", msg.dump().c_str());
|
||||
this->log_receive_message_(LOG_STR("on_ping_response"), msg);
|
||||
#endif
|
||||
this->on_ping_response(msg);
|
||||
break;
|
||||
@@ -64,7 +68,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
||||
DeviceInfoRequest msg;
|
||||
// Empty message: no decode needed
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_device_info_request: %s", msg.dump().c_str());
|
||||
this->log_receive_message_(LOG_STR("on_device_info_request"), msg);
|
||||
#endif
|
||||
this->on_device_info_request(msg);
|
||||
break;
|
||||
@@ -73,7 +77,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
||||
ListEntitiesRequest msg;
|
||||
// Empty message: no decode needed
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_list_entities_request: %s", msg.dump().c_str());
|
||||
this->log_receive_message_(LOG_STR("on_list_entities_request"), msg);
|
||||
#endif
|
||||
this->on_list_entities_request(msg);
|
||||
break;
|
||||
@@ -82,7 +86,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
||||
SubscribeStatesRequest msg;
|
||||
// Empty message: no decode needed
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_subscribe_states_request: %s", msg.dump().c_str());
|
||||
this->log_receive_message_(LOG_STR("on_subscribe_states_request"), msg);
|
||||
#endif
|
||||
this->on_subscribe_states_request(msg);
|
||||
break;
|
||||
@@ -91,7 +95,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
||||
SubscribeLogsRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_subscribe_logs_request: %s", msg.dump().c_str());
|
||||
this->log_receive_message_(LOG_STR("on_subscribe_logs_request"), msg);
|
||||
#endif
|
||||
this->on_subscribe_logs_request(msg);
|
||||
break;
|
||||
@@ -101,7 +105,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
||||
CoverCommandRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_cover_command_request: %s", msg.dump().c_str());
|
||||
this->log_receive_message_(LOG_STR("on_cover_command_request"), msg);
|
||||
#endif
|
||||
this->on_cover_command_request(msg);
|
||||
break;
|
||||
@@ -112,7 +116,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
||||
FanCommandRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_fan_command_request: %s", msg.dump().c_str());
|
||||
this->log_receive_message_(LOG_STR("on_fan_command_request"), msg);
|
||||
#endif
|
||||
this->on_fan_command_request(msg);
|
||||
break;
|
||||
@@ -123,7 +127,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
||||
LightCommandRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_light_command_request: %s", msg.dump().c_str());
|
||||
this->log_receive_message_(LOG_STR("on_light_command_request"), msg);
|
||||
#endif
|
||||
this->on_light_command_request(msg);
|
||||
break;
|
||||
@@ -134,7 +138,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
||||
SwitchCommandRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_switch_command_request: %s", msg.dump().c_str());
|
||||
this->log_receive_message_(LOG_STR("on_switch_command_request"), msg);
|
||||
#endif
|
||||
this->on_switch_command_request(msg);
|
||||
break;
|
||||
@@ -145,7 +149,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
||||
SubscribeHomeassistantServicesRequest msg;
|
||||
// Empty message: no decode needed
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_subscribe_homeassistant_services_request: %s", msg.dump().c_str());
|
||||
this->log_receive_message_(LOG_STR("on_subscribe_homeassistant_services_request"), msg);
|
||||
#endif
|
||||
this->on_subscribe_homeassistant_services_request(msg);
|
||||
break;
|
||||
@@ -155,7 +159,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
||||
GetTimeResponse msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_get_time_response: %s", msg.dump().c_str());
|
||||
this->log_receive_message_(LOG_STR("on_get_time_response"), msg);
|
||||
#endif
|
||||
this->on_get_time_response(msg);
|
||||
break;
|
||||
@@ -165,7 +169,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
||||
SubscribeHomeAssistantStatesRequest msg;
|
||||
// Empty message: no decode needed
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_subscribe_home_assistant_states_request: %s", msg.dump().c_str());
|
||||
this->log_receive_message_(LOG_STR("on_subscribe_home_assistant_states_request"), msg);
|
||||
#endif
|
||||
this->on_subscribe_home_assistant_states_request(msg);
|
||||
break;
|
||||
@@ -176,7 +180,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
||||
HomeAssistantStateResponse msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_home_assistant_state_response: %s", msg.dump().c_str());
|
||||
this->log_receive_message_(LOG_STR("on_home_assistant_state_response"), msg);
|
||||
#endif
|
||||
this->on_home_assistant_state_response(msg);
|
||||
break;
|
||||
@@ -187,7 +191,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
||||
ExecuteServiceRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_execute_service_request: %s", msg.dump().c_str());
|
||||
this->log_receive_message_(LOG_STR("on_execute_service_request"), msg);
|
||||
#endif
|
||||
this->on_execute_service_request(msg);
|
||||
break;
|
||||
@@ -198,7 +202,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
||||
CameraImageRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_camera_image_request: %s", msg.dump().c_str());
|
||||
this->log_receive_message_(LOG_STR("on_camera_image_request"), msg);
|
||||
#endif
|
||||
this->on_camera_image_request(msg);
|
||||
break;
|
||||
@@ -209,7 +213,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
||||
ClimateCommandRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_climate_command_request: %s", msg.dump().c_str());
|
||||
this->log_receive_message_(LOG_STR("on_climate_command_request"), msg);
|
||||
#endif
|
||||
this->on_climate_command_request(msg);
|
||||
break;
|
||||
@@ -220,7 +224,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
||||
NumberCommandRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_number_command_request: %s", msg.dump().c_str());
|
||||
this->log_receive_message_(LOG_STR("on_number_command_request"), msg);
|
||||
#endif
|
||||
this->on_number_command_request(msg);
|
||||
break;
|
||||
@@ -231,7 +235,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
||||
SelectCommandRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_select_command_request: %s", msg.dump().c_str());
|
||||
this->log_receive_message_(LOG_STR("on_select_command_request"), msg);
|
||||
#endif
|
||||
this->on_select_command_request(msg);
|
||||
break;
|
||||
@@ -242,7 +246,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
||||
SirenCommandRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_siren_command_request: %s", msg.dump().c_str());
|
||||
this->log_receive_message_(LOG_STR("on_siren_command_request"), msg);
|
||||
#endif
|
||||
this->on_siren_command_request(msg);
|
||||
break;
|
||||
@@ -253,7 +257,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
||||
LockCommandRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_lock_command_request: %s", msg.dump().c_str());
|
||||
this->log_receive_message_(LOG_STR("on_lock_command_request"), msg);
|
||||
#endif
|
||||
this->on_lock_command_request(msg);
|
||||
break;
|
||||
@@ -264,7 +268,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
||||
ButtonCommandRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_button_command_request: %s", msg.dump().c_str());
|
||||
this->log_receive_message_(LOG_STR("on_button_command_request"), msg);
|
||||
#endif
|
||||
this->on_button_command_request(msg);
|
||||
break;
|
||||
@@ -275,7 +279,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
||||
MediaPlayerCommandRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_media_player_command_request: %s", msg.dump().c_str());
|
||||
this->log_receive_message_(LOG_STR("on_media_player_command_request"), msg);
|
||||
#endif
|
||||
this->on_media_player_command_request(msg);
|
||||
break;
|
||||
@@ -286,7 +290,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
||||
SubscribeBluetoothLEAdvertisementsRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_subscribe_bluetooth_le_advertisements_request: %s", msg.dump().c_str());
|
||||
this->log_receive_message_(LOG_STR("on_subscribe_bluetooth_le_advertisements_request"), msg);
|
||||
#endif
|
||||
this->on_subscribe_bluetooth_le_advertisements_request(msg);
|
||||
break;
|
||||
@@ -297,7 +301,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
||||
BluetoothDeviceRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_bluetooth_device_request: %s", msg.dump().c_str());
|
||||
this->log_receive_message_(LOG_STR("on_bluetooth_device_request"), msg);
|
||||
#endif
|
||||
this->on_bluetooth_device_request(msg);
|
||||
break;
|
||||
@@ -308,7 +312,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
||||
BluetoothGATTGetServicesRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_bluetooth_gatt_get_services_request: %s", msg.dump().c_str());
|
||||
this->log_receive_message_(LOG_STR("on_bluetooth_gatt_get_services_request"), msg);
|
||||
#endif
|
||||
this->on_bluetooth_gatt_get_services_request(msg);
|
||||
break;
|
||||
@@ -319,7 +323,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
||||
BluetoothGATTReadRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_bluetooth_gatt_read_request: %s", msg.dump().c_str());
|
||||
this->log_receive_message_(LOG_STR("on_bluetooth_gatt_read_request"), msg);
|
||||
#endif
|
||||
this->on_bluetooth_gatt_read_request(msg);
|
||||
break;
|
||||
@@ -330,7 +334,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
||||
BluetoothGATTWriteRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_bluetooth_gatt_write_request: %s", msg.dump().c_str());
|
||||
this->log_receive_message_(LOG_STR("on_bluetooth_gatt_write_request"), msg);
|
||||
#endif
|
||||
this->on_bluetooth_gatt_write_request(msg);
|
||||
break;
|
||||
@@ -341,7 +345,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
||||
BluetoothGATTReadDescriptorRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_bluetooth_gatt_read_descriptor_request: %s", msg.dump().c_str());
|
||||
this->log_receive_message_(LOG_STR("on_bluetooth_gatt_read_descriptor_request"), msg);
|
||||
#endif
|
||||
this->on_bluetooth_gatt_read_descriptor_request(msg);
|
||||
break;
|
||||
@@ -352,7 +356,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
||||
BluetoothGATTWriteDescriptorRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_bluetooth_gatt_write_descriptor_request: %s", msg.dump().c_str());
|
||||
this->log_receive_message_(LOG_STR("on_bluetooth_gatt_write_descriptor_request"), msg);
|
||||
#endif
|
||||
this->on_bluetooth_gatt_write_descriptor_request(msg);
|
||||
break;
|
||||
@@ -363,7 +367,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
||||
BluetoothGATTNotifyRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_bluetooth_gatt_notify_request: %s", msg.dump().c_str());
|
||||
this->log_receive_message_(LOG_STR("on_bluetooth_gatt_notify_request"), msg);
|
||||
#endif
|
||||
this->on_bluetooth_gatt_notify_request(msg);
|
||||
break;
|
||||
@@ -374,7 +378,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
||||
SubscribeBluetoothConnectionsFreeRequest msg;
|
||||
// Empty message: no decode needed
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_subscribe_bluetooth_connections_free_request: %s", msg.dump().c_str());
|
||||
this->log_receive_message_(LOG_STR("on_subscribe_bluetooth_connections_free_request"), msg);
|
||||
#endif
|
||||
this->on_subscribe_bluetooth_connections_free_request(msg);
|
||||
break;
|
||||
@@ -385,7 +389,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
||||
UnsubscribeBluetoothLEAdvertisementsRequest msg;
|
||||
// Empty message: no decode needed
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_unsubscribe_bluetooth_le_advertisements_request: %s", msg.dump().c_str());
|
||||
this->log_receive_message_(LOG_STR("on_unsubscribe_bluetooth_le_advertisements_request"), msg);
|
||||
#endif
|
||||
this->on_unsubscribe_bluetooth_le_advertisements_request(msg);
|
||||
break;
|
||||
@@ -396,7 +400,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
||||
SubscribeVoiceAssistantRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_subscribe_voice_assistant_request: %s", msg.dump().c_str());
|
||||
this->log_receive_message_(LOG_STR("on_subscribe_voice_assistant_request"), msg);
|
||||
#endif
|
||||
this->on_subscribe_voice_assistant_request(msg);
|
||||
break;
|
||||
@@ -407,7 +411,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
||||
VoiceAssistantResponse msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_voice_assistant_response: %s", msg.dump().c_str());
|
||||
this->log_receive_message_(LOG_STR("on_voice_assistant_response"), msg);
|
||||
#endif
|
||||
this->on_voice_assistant_response(msg);
|
||||
break;
|
||||
@@ -418,7 +422,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
||||
VoiceAssistantEventResponse msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_voice_assistant_event_response: %s", msg.dump().c_str());
|
||||
this->log_receive_message_(LOG_STR("on_voice_assistant_event_response"), msg);
|
||||
#endif
|
||||
this->on_voice_assistant_event_response(msg);
|
||||
break;
|
||||
@@ -429,7 +433,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
||||
AlarmControlPanelCommandRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_alarm_control_panel_command_request: %s", msg.dump().c_str());
|
||||
this->log_receive_message_(LOG_STR("on_alarm_control_panel_command_request"), msg);
|
||||
#endif
|
||||
this->on_alarm_control_panel_command_request(msg);
|
||||
break;
|
||||
@@ -440,7 +444,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
||||
TextCommandRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_text_command_request: %s", msg.dump().c_str());
|
||||
this->log_receive_message_(LOG_STR("on_text_command_request"), msg);
|
||||
#endif
|
||||
this->on_text_command_request(msg);
|
||||
break;
|
||||
@@ -451,7 +455,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
||||
DateCommandRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_date_command_request: %s", msg.dump().c_str());
|
||||
this->log_receive_message_(LOG_STR("on_date_command_request"), msg);
|
||||
#endif
|
||||
this->on_date_command_request(msg);
|
||||
break;
|
||||
@@ -462,7 +466,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
||||
TimeCommandRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_time_command_request: %s", msg.dump().c_str());
|
||||
this->log_receive_message_(LOG_STR("on_time_command_request"), msg);
|
||||
#endif
|
||||
this->on_time_command_request(msg);
|
||||
break;
|
||||
@@ -473,7 +477,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
||||
VoiceAssistantAudio msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_voice_assistant_audio: %s", msg.dump().c_str());
|
||||
this->log_receive_message_(LOG_STR("on_voice_assistant_audio"), msg);
|
||||
#endif
|
||||
this->on_voice_assistant_audio(msg);
|
||||
break;
|
||||
@@ -484,7 +488,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
||||
ValveCommandRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_valve_command_request: %s", msg.dump().c_str());
|
||||
this->log_receive_message_(LOG_STR("on_valve_command_request"), msg);
|
||||
#endif
|
||||
this->on_valve_command_request(msg);
|
||||
break;
|
||||
@@ -495,7 +499,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
||||
DateTimeCommandRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_date_time_command_request: %s", msg.dump().c_str());
|
||||
this->log_receive_message_(LOG_STR("on_date_time_command_request"), msg);
|
||||
#endif
|
||||
this->on_date_time_command_request(msg);
|
||||
break;
|
||||
@@ -506,7 +510,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
||||
VoiceAssistantTimerEventResponse msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_voice_assistant_timer_event_response: %s", msg.dump().c_str());
|
||||
this->log_receive_message_(LOG_STR("on_voice_assistant_timer_event_response"), msg);
|
||||
#endif
|
||||
this->on_voice_assistant_timer_event_response(msg);
|
||||
break;
|
||||
@@ -517,7 +521,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
||||
UpdateCommandRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_update_command_request: %s", msg.dump().c_str());
|
||||
this->log_receive_message_(LOG_STR("on_update_command_request"), msg);
|
||||
#endif
|
||||
this->on_update_command_request(msg);
|
||||
break;
|
||||
@@ -528,7 +532,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
||||
VoiceAssistantAnnounceRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_voice_assistant_announce_request: %s", msg.dump().c_str());
|
||||
this->log_receive_message_(LOG_STR("on_voice_assistant_announce_request"), msg);
|
||||
#endif
|
||||
this->on_voice_assistant_announce_request(msg);
|
||||
break;
|
||||
@@ -539,7 +543,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
||||
VoiceAssistantConfigurationRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_voice_assistant_configuration_request: %s", msg.dump().c_str());
|
||||
this->log_receive_message_(LOG_STR("on_voice_assistant_configuration_request"), msg);
|
||||
#endif
|
||||
this->on_voice_assistant_configuration_request(msg);
|
||||
break;
|
||||
@@ -550,7 +554,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
||||
VoiceAssistantSetConfiguration msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_voice_assistant_set_configuration: %s", msg.dump().c_str());
|
||||
this->log_receive_message_(LOG_STR("on_voice_assistant_set_configuration"), msg);
|
||||
#endif
|
||||
this->on_voice_assistant_set_configuration(msg);
|
||||
break;
|
||||
@@ -561,7 +565,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
||||
NoiseEncryptionSetKeyRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_noise_encryption_set_key_request: %s", msg.dump().c_str());
|
||||
this->log_receive_message_(LOG_STR("on_noise_encryption_set_key_request"), msg);
|
||||
#endif
|
||||
this->on_noise_encryption_set_key_request(msg);
|
||||
break;
|
||||
@@ -572,7 +576,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
||||
BluetoothScannerSetModeRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_bluetooth_scanner_set_mode_request: %s", msg.dump().c_str());
|
||||
this->log_receive_message_(LOG_STR("on_bluetooth_scanner_set_mode_request"), msg);
|
||||
#endif
|
||||
this->on_bluetooth_scanner_set_mode_request(msg);
|
||||
break;
|
||||
@@ -583,7 +587,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
||||
ZWaveProxyFrame msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_z_wave_proxy_frame: %s", msg.dump().c_str());
|
||||
this->log_receive_message_(LOG_STR("on_z_wave_proxy_frame"), msg);
|
||||
#endif
|
||||
this->on_z_wave_proxy_frame(msg);
|
||||
break;
|
||||
@@ -594,7 +598,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
||||
ZWaveProxyRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_z_wave_proxy_request: %s", msg.dump().c_str());
|
||||
this->log_receive_message_(LOG_STR("on_z_wave_proxy_request"), msg);
|
||||
#endif
|
||||
this->on_z_wave_proxy_request(msg);
|
||||
break;
|
||||
@@ -605,7 +609,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
||||
HomeassistantActionResponse msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_homeassistant_action_response: %s", msg.dump().c_str());
|
||||
this->log_receive_message_(LOG_STR("on_homeassistant_action_response"), msg);
|
||||
#endif
|
||||
this->on_homeassistant_action_response(msg);
|
||||
break;
|
||||
@@ -616,7 +620,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
||||
WaterHeaterCommandRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_water_heater_command_request: %s", msg.dump().c_str());
|
||||
this->log_receive_message_(LOG_STR("on_water_heater_command_request"), msg);
|
||||
#endif
|
||||
this->on_water_heater_command_request(msg);
|
||||
break;
|
||||
@@ -627,7 +631,7 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
||||
InfraredRFTransmitRawTimingsRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_infrared_rf_transmit_raw_timings_request: %s", msg.dump().c_str());
|
||||
this->log_receive_message_(LOG_STR("on_infrared_rf_transmit_raw_timings_request"), msg);
|
||||
#endif
|
||||
this->on_infrared_rf_transmit_raw_timings_request(msg);
|
||||
break;
|
||||
|
||||
@@ -12,14 +12,16 @@ class APIServerConnectionBase : public ProtoService {
|
||||
public:
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
protected:
|
||||
void log_send_message_(const char *name, const std::string &dump);
|
||||
void log_send_message_(const char *name, const char *dump);
|
||||
void log_receive_message_(const LogString *name, const ProtoMessage &msg);
|
||||
|
||||
public:
|
||||
#endif
|
||||
|
||||
bool send_message(const ProtoMessage &msg, uint8_t message_type) {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
this->log_send_message_(msg.message_name(), msg.dump());
|
||||
DumpBuffer dump_buf;
|
||||
this->log_send_message_(msg.message_name(), msg.dump_to(dump_buf));
|
||||
#endif
|
||||
return this->send_message_(msg, message_type);
|
||||
}
|
||||
|
||||
@@ -318,13 +318,11 @@ API_DISPATCH_UPDATE(water_heater::WaterHeater, water_heater)
|
||||
#endif
|
||||
|
||||
#ifdef USE_EVENT
|
||||
// Event is a special case - unlike other entities with simple state fields,
|
||||
// events store their state in a member accessed via obj->get_last_event_type()
|
||||
void APIServer::on_event(event::Event *obj) {
|
||||
if (obj->is_internal())
|
||||
return;
|
||||
for (auto &c : this->clients_)
|
||||
c->send_event(obj, obj->get_last_event_type());
|
||||
c->send_event(obj);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -615,8 +613,7 @@ void APIServer::on_shutdown() {
|
||||
if (!c->send_message(req, DisconnectRequest::MESSAGE_TYPE)) {
|
||||
// If we can't send the disconnect request directly (tx_buffer full),
|
||||
// schedule it at the front of the batch so it will be sent with priority
|
||||
c->schedule_message_front_(nullptr, &APIConnection::try_send_disconnect_request, DisconnectRequest::MESSAGE_TYPE,
|
||||
DisconnectRequest::ESTIMATED_SIZE);
|
||||
c->schedule_message_front_(nullptr, DisconnectRequest::MESSAGE_TYPE, DisconnectRequest::ESTIMATED_SIZE);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -639,14 +636,6 @@ bool APIServer::teardown() {
|
||||
#define USE_API_ACTION_CALL_TIMEOUT_MS 30000 // NOLINT
|
||||
#endif
|
||||
|
||||
// SSO-friendly action call key - hex format guarantees max 11 chars ("ac_ffffffff")
|
||||
// which fits in any std::string SSO buffer (typically 12-15 bytes)
|
||||
static inline std::string make_action_call_key(uint32_t id) {
|
||||
char buf[12];
|
||||
size_t len = snprintf(buf, sizeof(buf), "ac_%x", id);
|
||||
return std::string(buf, len);
|
||||
}
|
||||
|
||||
uint32_t APIServer::register_active_action_call(uint32_t client_call_id, APIConnection *conn) {
|
||||
uint32_t action_call_id = this->next_action_call_id_++;
|
||||
// Handle wraparound (skip 0 as it means "no call")
|
||||
@@ -656,17 +645,18 @@ uint32_t APIServer::register_active_action_call(uint32_t client_call_id, APIConn
|
||||
this->active_action_calls_.push_back({action_call_id, client_call_id, conn});
|
||||
|
||||
// Schedule automatic cleanup after timeout (client will have given up by then)
|
||||
this->set_timeout(make_action_call_key(action_call_id), USE_API_ACTION_CALL_TIMEOUT_MS, [this, action_call_id]() {
|
||||
ESP_LOGD(TAG, "Action call %u timed out", action_call_id);
|
||||
this->unregister_active_action_call(action_call_id);
|
||||
});
|
||||
this->set_timeout(str_sprintf("action_call_%u", action_call_id), USE_API_ACTION_CALL_TIMEOUT_MS,
|
||||
[this, action_call_id]() {
|
||||
ESP_LOGD(TAG, "Action call %u timed out", action_call_id);
|
||||
this->unregister_active_action_call(action_call_id);
|
||||
});
|
||||
|
||||
return action_call_id;
|
||||
}
|
||||
|
||||
void APIServer::unregister_active_action_call(uint32_t action_call_id) {
|
||||
// Cancel the timeout for this action call
|
||||
this->cancel_timeout(make_action_call_key(action_call_id));
|
||||
this->cancel_timeout(str_sprintf("action_call_%u", action_call_id));
|
||||
|
||||
// Swap-and-pop is more efficient than remove_if for unordered vectors
|
||||
for (size_t i = 0; i < this->active_action_calls_.size(); i++) {
|
||||
@@ -683,7 +673,7 @@ void APIServer::unregister_active_action_calls_for_connection(APIConnection *con
|
||||
for (size_t i = 0; i < this->active_action_calls_.size();) {
|
||||
if (this->active_action_calls_[i].connection == conn) {
|
||||
// Cancel the timeout for this action call
|
||||
this->cancel_timeout(make_action_call_key(this->active_action_calls_[i].action_call_id));
|
||||
this->cancel_timeout(str_sprintf("action_call_%u", this->active_action_calls_[i].action_call_id));
|
||||
|
||||
std::swap(this->active_action_calls_[i], this->active_action_calls_.back());
|
||||
this->active_action_calls_.pop_back();
|
||||
|
||||
@@ -9,11 +9,10 @@ namespace esphome::api {
|
||||
class APIConnection;
|
||||
|
||||
// Macro for generating ListEntitiesIterator handlers
|
||||
// Calls schedule_message_ with try_send_*_info
|
||||
// Calls schedule_message_ which dispatches to try_send_*_info
|
||||
#define LIST_ENTITIES_HANDLER(entity_type, EntityClass, ResponseType) \
|
||||
bool ListEntitiesIterator::on_##entity_type(EntityClass *entity) { /* NOLINT(bugprone-macro-parentheses) */ \
|
||||
return this->client_->schedule_message_(entity, &APIConnection::try_send_##entity_type##_info, \
|
||||
ResponseType::MESSAGE_TYPE, ResponseType::ESTIMATED_SIZE); \
|
||||
return this->client_->schedule_message_(entity, ResponseType::MESSAGE_TYPE, ResponseType::ESTIMATED_SIZE); \
|
||||
}
|
||||
|
||||
class ListEntitiesIterator : public ComponentIterator {
|
||||
|
||||
@@ -139,12 +139,4 @@ void ProtoDecodableMessage::decode(const uint8_t *buffer, size_t length) {
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
std::string ProtoMessage::dump() const {
|
||||
std::string out;
|
||||
this->dump_to(out);
|
||||
return out;
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace esphome::api
|
||||
|
||||
@@ -362,6 +362,63 @@ class ProtoWriteBuffer {
|
||||
std::vector<uint8_t> *buffer_;
|
||||
};
|
||||
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
/**
|
||||
* Fixed-size buffer for message dumps - avoids heap allocation.
|
||||
* Sized to match the logger's default tx_buffer_size (512 bytes)
|
||||
* since anything larger gets truncated anyway.
|
||||
*/
|
||||
class DumpBuffer {
|
||||
public:
|
||||
// Matches default tx_buffer_size in logger component
|
||||
static constexpr size_t CAPACITY = 512;
|
||||
|
||||
DumpBuffer() : pos_(0) { buf_[0] = '\0'; }
|
||||
|
||||
DumpBuffer &append(const char *str) {
|
||||
if (str) {
|
||||
append_impl_(str, strlen(str));
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
DumpBuffer &append(const char *str, size_t len) {
|
||||
append_impl_(str, len);
|
||||
return *this;
|
||||
}
|
||||
|
||||
DumpBuffer &append(size_t n, char c) {
|
||||
size_t space = CAPACITY - 1 - pos_;
|
||||
if (n > space)
|
||||
n = space;
|
||||
if (n > 0) {
|
||||
memset(buf_ + pos_, c, n);
|
||||
pos_ += n;
|
||||
buf_[pos_] = '\0';
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
const char *c_str() const { return buf_; }
|
||||
size_t size() const { return pos_; }
|
||||
|
||||
private:
|
||||
void append_impl_(const char *str, size_t len) {
|
||||
size_t space = CAPACITY - 1 - pos_;
|
||||
if (len > space)
|
||||
len = space;
|
||||
if (len > 0) {
|
||||
memcpy(buf_ + pos_, str, len);
|
||||
pos_ += len;
|
||||
buf_[pos_] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
char buf_[CAPACITY];
|
||||
size_t pos_;
|
||||
};
|
||||
#endif
|
||||
|
||||
class ProtoMessage {
|
||||
public:
|
||||
virtual ~ProtoMessage() = default;
|
||||
@@ -370,8 +427,7 @@ class ProtoMessage {
|
||||
// Default implementation for messages with no fields
|
||||
virtual void calculate_size(ProtoSize &size) const {}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
std::string dump() const;
|
||||
virtual void dump_to(std::string &out) const = 0;
|
||||
virtual const char *dump_to(DumpBuffer &out) const = 0;
|
||||
virtual const char *message_name() const { return "unknown"; }
|
||||
#endif
|
||||
};
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#include "bh1750.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/application.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace bh1750 {
|
||||
namespace esphome::bh1750 {
|
||||
|
||||
static const char *const TAG = "bh1750.sensor";
|
||||
|
||||
@@ -13,6 +13,31 @@ static const uint8_t BH1750_COMMAND_ONE_TIME_L = 0b00100011;
|
||||
static const uint8_t BH1750_COMMAND_ONE_TIME_H = 0b00100000;
|
||||
static const uint8_t BH1750_COMMAND_ONE_TIME_H2 = 0b00100001;
|
||||
|
||||
static constexpr uint32_t MEASUREMENT_TIMEOUT_MS = 2000;
|
||||
static constexpr float HIGH_LIGHT_THRESHOLD_LX = 7000.0f;
|
||||
|
||||
// Measurement time constants (datasheet values)
|
||||
static constexpr uint16_t MTREG_DEFAULT = 69;
|
||||
static constexpr uint16_t MTREG_MIN = 31;
|
||||
static constexpr uint16_t MTREG_MAX = 254;
|
||||
static constexpr uint16_t MEAS_TIME_L_MS = 24; // L-resolution max measurement time @ mtreg=69
|
||||
static constexpr uint16_t MEAS_TIME_H_MS = 180; // H/H2-resolution max measurement time @ mtreg=69
|
||||
|
||||
// Conversion constants (datasheet formulas)
|
||||
static constexpr float RESOLUTION_DIVISOR = 1.2f; // counts to lux conversion divisor
|
||||
static constexpr float MODE_H2_DIVISOR = 2.0f; // H2 mode has 2x higher resolution
|
||||
|
||||
// MTreg calculation constants
|
||||
static constexpr int COUNTS_TARGET = 50000; // Target counts for optimal range (avoid saturation)
|
||||
static constexpr int COUNTS_NUMERATOR = 10;
|
||||
static constexpr int COUNTS_DENOMINATOR = 12;
|
||||
|
||||
// MTreg register bit manipulation constants
|
||||
static constexpr uint8_t MTREG_HI_SHIFT = 5; // High 3 bits start at bit 5
|
||||
static constexpr uint8_t MTREG_HI_MASK = 0b111; // 3-bit mask for high bits
|
||||
static constexpr uint8_t MTREG_LO_SHIFT = 0; // Low 5 bits start at bit 0
|
||||
static constexpr uint8_t MTREG_LO_MASK = 0b11111; // 5-bit mask for low bits
|
||||
|
||||
/*
|
||||
bh1750 properties:
|
||||
|
||||
@@ -43,74 +68,7 @@ void BH1750Sensor::setup() {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void BH1750Sensor::read_lx_(BH1750Mode mode, uint8_t mtreg, const std::function<void(float)> &f) {
|
||||
// turn on (after one-shot sensor automatically powers down)
|
||||
uint8_t turn_on = BH1750_COMMAND_POWER_ON;
|
||||
if (this->write(&turn_on, 1) != i2c::ERROR_OK) {
|
||||
ESP_LOGW(TAG, "Power on failed");
|
||||
f(NAN);
|
||||
return;
|
||||
}
|
||||
|
||||
if (active_mtreg_ != mtreg) {
|
||||
// set mtreg
|
||||
uint8_t mtreg_hi = BH1750_COMMAND_MT_REG_HI | ((mtreg >> 5) & 0b111);
|
||||
uint8_t mtreg_lo = BH1750_COMMAND_MT_REG_LO | ((mtreg >> 0) & 0b11111);
|
||||
if (this->write(&mtreg_hi, 1) != i2c::ERROR_OK || this->write(&mtreg_lo, 1) != i2c::ERROR_OK) {
|
||||
ESP_LOGW(TAG, "Set measurement time failed");
|
||||
active_mtreg_ = 0;
|
||||
f(NAN);
|
||||
return;
|
||||
}
|
||||
active_mtreg_ = mtreg;
|
||||
}
|
||||
|
||||
uint8_t cmd;
|
||||
uint16_t meas_time;
|
||||
switch (mode) {
|
||||
case BH1750_MODE_L:
|
||||
cmd = BH1750_COMMAND_ONE_TIME_L;
|
||||
meas_time = 24 * mtreg / 69;
|
||||
break;
|
||||
case BH1750_MODE_H:
|
||||
cmd = BH1750_COMMAND_ONE_TIME_H;
|
||||
meas_time = 180 * mtreg / 69;
|
||||
break;
|
||||
case BH1750_MODE_H2:
|
||||
cmd = BH1750_COMMAND_ONE_TIME_H2;
|
||||
meas_time = 180 * mtreg / 69;
|
||||
break;
|
||||
default:
|
||||
f(NAN);
|
||||
return;
|
||||
}
|
||||
if (this->write(&cmd, 1) != i2c::ERROR_OK) {
|
||||
ESP_LOGW(TAG, "Start measurement failed");
|
||||
f(NAN);
|
||||
return;
|
||||
}
|
||||
|
||||
// probably not needed, but adjust for rounding
|
||||
meas_time++;
|
||||
|
||||
this->set_timeout("read", meas_time, [this, mode, mtreg, f]() {
|
||||
uint16_t raw_value;
|
||||
if (this->read(reinterpret_cast<uint8_t *>(&raw_value), 2) != i2c::ERROR_OK) {
|
||||
ESP_LOGW(TAG, "Read data failed");
|
||||
f(NAN);
|
||||
return;
|
||||
}
|
||||
raw_value = i2c::i2ctohs(raw_value);
|
||||
|
||||
float lx = float(raw_value) / 1.2f;
|
||||
lx *= 69.0f / mtreg;
|
||||
if (mode == BH1750_MODE_H2)
|
||||
lx /= 2.0f;
|
||||
|
||||
f(lx);
|
||||
});
|
||||
this->state_ = IDLE;
|
||||
}
|
||||
|
||||
void BH1750Sensor::dump_config() {
|
||||
@@ -124,45 +82,189 @@ void BH1750Sensor::dump_config() {
|
||||
}
|
||||
|
||||
void BH1750Sensor::update() {
|
||||
// first do a quick measurement in L-mode with full range
|
||||
// to find right range
|
||||
this->read_lx_(BH1750_MODE_L, 31, [this](float val) {
|
||||
if (std::isnan(val)) {
|
||||
this->status_set_warning();
|
||||
this->publish_state(NAN);
|
||||
const uint32_t now = millis();
|
||||
|
||||
// Start coarse measurement to determine optimal mode/mtreg
|
||||
if (this->state_ != IDLE) {
|
||||
// Safety timeout: reset if stuck
|
||||
if (now - this->measurement_start_time_ > MEASUREMENT_TIMEOUT_MS) {
|
||||
ESP_LOGW(TAG, "Measurement timeout, resetting state");
|
||||
this->state_ = IDLE;
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Previous measurement not complete, skipping update");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
BH1750Mode use_mode;
|
||||
uint8_t use_mtreg;
|
||||
if (val <= 7000) {
|
||||
use_mode = BH1750_MODE_H2;
|
||||
use_mtreg = 254;
|
||||
} else {
|
||||
use_mode = BH1750_MODE_H;
|
||||
// lx = counts / 1.2 * (69 / mtreg)
|
||||
// -> mtreg = counts / 1.2 * (69 / lx)
|
||||
// calculate for counts=50000 (allow some range to not saturate, but maximize mtreg)
|
||||
// -> mtreg = 50000*(10/12)*(69/lx)
|
||||
int ideal_mtreg = 50000 * 10 * 69 / (12 * (int) val);
|
||||
use_mtreg = std::min(254, std::max(31, ideal_mtreg));
|
||||
}
|
||||
ESP_LOGV(TAG, "L result: %f -> Calculated mode=%d, mtreg=%d", val, (int) use_mode, use_mtreg);
|
||||
if (!this->start_measurement_(BH1750_MODE_L, MTREG_MIN, now)) {
|
||||
this->status_set_warning();
|
||||
this->publish_state(NAN);
|
||||
return;
|
||||
}
|
||||
|
||||
this->read_lx_(use_mode, use_mtreg, [this](float val) {
|
||||
if (std::isnan(val)) {
|
||||
this->status_set_warning();
|
||||
this->publish_state(NAN);
|
||||
return;
|
||||
this->state_ = WAITING_COARSE_MEASUREMENT;
|
||||
this->enable_loop(); // Enable loop while measurement in progress
|
||||
}
|
||||
|
||||
void BH1750Sensor::loop() {
|
||||
const uint32_t now = App.get_loop_component_start_time();
|
||||
|
||||
switch (this->state_) {
|
||||
case IDLE:
|
||||
// Disable loop when idle to save cycles
|
||||
this->disable_loop();
|
||||
break;
|
||||
|
||||
case WAITING_COARSE_MEASUREMENT:
|
||||
if (now - this->measurement_start_time_ >= this->measurement_duration_) {
|
||||
this->state_ = READING_COARSE_RESULT;
|
||||
}
|
||||
ESP_LOGD(TAG, "'%s': Illuminance=%.1flx", this->get_name().c_str(), val);
|
||||
break;
|
||||
|
||||
case READING_COARSE_RESULT: {
|
||||
float lx;
|
||||
if (!this->read_measurement_(lx)) {
|
||||
this->fail_and_reset_();
|
||||
break;
|
||||
}
|
||||
|
||||
this->process_coarse_result_(lx);
|
||||
|
||||
// Start fine measurement with optimal settings
|
||||
// fetch millis() again since the read can take a bit
|
||||
if (!this->start_measurement_(this->fine_mode_, this->fine_mtreg_, millis())) {
|
||||
this->fail_and_reset_();
|
||||
break;
|
||||
}
|
||||
|
||||
this->state_ = WAITING_FINE_MEASUREMENT;
|
||||
break;
|
||||
}
|
||||
|
||||
case WAITING_FINE_MEASUREMENT:
|
||||
if (now - this->measurement_start_time_ >= this->measurement_duration_) {
|
||||
this->state_ = READING_FINE_RESULT;
|
||||
}
|
||||
break;
|
||||
|
||||
case READING_FINE_RESULT: {
|
||||
float lx;
|
||||
if (!this->read_measurement_(lx)) {
|
||||
this->fail_and_reset_();
|
||||
break;
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, "'%s': Illuminance=%.1flx", this->get_name().c_str(), lx);
|
||||
this->status_clear_warning();
|
||||
this->publish_state(val);
|
||||
});
|
||||
});
|
||||
this->publish_state(lx);
|
||||
this->state_ = IDLE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool BH1750Sensor::start_measurement_(BH1750Mode mode, uint8_t mtreg, uint32_t now) {
|
||||
// Power on
|
||||
uint8_t turn_on = BH1750_COMMAND_POWER_ON;
|
||||
if (this->write(&turn_on, 1) != i2c::ERROR_OK) {
|
||||
ESP_LOGW(TAG, "Power on failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set MTreg if changed
|
||||
if (this->active_mtreg_ != mtreg) {
|
||||
uint8_t mtreg_hi = BH1750_COMMAND_MT_REG_HI | ((mtreg >> MTREG_HI_SHIFT) & MTREG_HI_MASK);
|
||||
uint8_t mtreg_lo = BH1750_COMMAND_MT_REG_LO | ((mtreg >> MTREG_LO_SHIFT) & MTREG_LO_MASK);
|
||||
if (this->write(&mtreg_hi, 1) != i2c::ERROR_OK || this->write(&mtreg_lo, 1) != i2c::ERROR_OK) {
|
||||
ESP_LOGW(TAG, "Set measurement time failed");
|
||||
this->active_mtreg_ = 0;
|
||||
return false;
|
||||
}
|
||||
this->active_mtreg_ = mtreg;
|
||||
}
|
||||
|
||||
// Start measurement
|
||||
uint8_t cmd;
|
||||
uint16_t meas_time;
|
||||
switch (mode) {
|
||||
case BH1750_MODE_L:
|
||||
cmd = BH1750_COMMAND_ONE_TIME_L;
|
||||
meas_time = MEAS_TIME_L_MS * mtreg / MTREG_DEFAULT;
|
||||
break;
|
||||
case BH1750_MODE_H:
|
||||
cmd = BH1750_COMMAND_ONE_TIME_H;
|
||||
meas_time = MEAS_TIME_H_MS * mtreg / MTREG_DEFAULT;
|
||||
break;
|
||||
case BH1750_MODE_H2:
|
||||
cmd = BH1750_COMMAND_ONE_TIME_H2;
|
||||
meas_time = MEAS_TIME_H_MS * mtreg / MTREG_DEFAULT;
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this->write(&cmd, 1) != i2c::ERROR_OK) {
|
||||
ESP_LOGW(TAG, "Start measurement failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Store current measurement parameters
|
||||
this->current_mode_ = mode;
|
||||
this->current_mtreg_ = mtreg;
|
||||
this->measurement_start_time_ = now;
|
||||
this->measurement_duration_ = meas_time + 1; // Add 1ms for safety
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BH1750Sensor::read_measurement_(float &lx_out) {
|
||||
uint16_t raw_value;
|
||||
if (this->read(reinterpret_cast<uint8_t *>(&raw_value), 2) != i2c::ERROR_OK) {
|
||||
ESP_LOGW(TAG, "Read data failed");
|
||||
return false;
|
||||
}
|
||||
raw_value = i2c::i2ctohs(raw_value);
|
||||
|
||||
float lx = float(raw_value) / RESOLUTION_DIVISOR;
|
||||
lx *= float(MTREG_DEFAULT) / this->current_mtreg_;
|
||||
if (this->current_mode_ == BH1750_MODE_H2) {
|
||||
lx /= MODE_H2_DIVISOR;
|
||||
}
|
||||
|
||||
lx_out = lx;
|
||||
return true;
|
||||
}
|
||||
|
||||
void BH1750Sensor::process_coarse_result_(float lx) {
|
||||
if (std::isnan(lx)) {
|
||||
// Use defaults if coarse measurement failed
|
||||
this->fine_mode_ = BH1750_MODE_H2;
|
||||
this->fine_mtreg_ = MTREG_MAX;
|
||||
return;
|
||||
}
|
||||
|
||||
if (lx <= HIGH_LIGHT_THRESHOLD_LX) {
|
||||
this->fine_mode_ = BH1750_MODE_H2;
|
||||
this->fine_mtreg_ = MTREG_MAX;
|
||||
} else {
|
||||
this->fine_mode_ = BH1750_MODE_H;
|
||||
// lx = counts / 1.2 * (69 / mtreg)
|
||||
// -> mtreg = counts / 1.2 * (69 / lx)
|
||||
// calculate for counts=50000 (allow some range to not saturate, but maximize mtreg)
|
||||
// -> mtreg = 50000*(10/12)*(69/lx)
|
||||
int ideal_mtreg = COUNTS_TARGET * COUNTS_NUMERATOR * MTREG_DEFAULT / (COUNTS_DENOMINATOR * (int) lx);
|
||||
this->fine_mtreg_ = std::min((int) MTREG_MAX, std::max((int) MTREG_MIN, ideal_mtreg));
|
||||
}
|
||||
|
||||
ESP_LOGV(TAG, "L result: %.1f -> Calculated mode=%d, mtreg=%d", lx, (int) this->fine_mode_, this->fine_mtreg_);
|
||||
}
|
||||
|
||||
void BH1750Sensor::fail_and_reset_() {
|
||||
this->status_set_warning();
|
||||
this->publish_state(NAN);
|
||||
this->state_ = IDLE;
|
||||
}
|
||||
|
||||
float BH1750Sensor::get_setup_priority() const { return setup_priority::DATA; }
|
||||
|
||||
} // namespace bh1750
|
||||
} // namespace esphome
|
||||
} // namespace esphome::bh1750
|
||||
|
||||
@@ -4,10 +4,9 @@
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace bh1750 {
|
||||
namespace esphome::bh1750 {
|
||||
|
||||
enum BH1750Mode {
|
||||
enum BH1750Mode : uint8_t {
|
||||
BH1750_MODE_L,
|
||||
BH1750_MODE_H,
|
||||
BH1750_MODE_H2,
|
||||
@@ -21,13 +20,36 @@ class BH1750Sensor : public sensor::Sensor, public PollingComponent, public i2c:
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
void update() override;
|
||||
void loop() override;
|
||||
float get_setup_priority() const override;
|
||||
|
||||
protected:
|
||||
void read_lx_(BH1750Mode mode, uint8_t mtreg, const std::function<void(float)> &f);
|
||||
// State machine states
|
||||
enum State : uint8_t {
|
||||
IDLE,
|
||||
WAITING_COARSE_MEASUREMENT,
|
||||
READING_COARSE_RESULT,
|
||||
WAITING_FINE_MEASUREMENT,
|
||||
READING_FINE_RESULT,
|
||||
};
|
||||
|
||||
// 4-byte aligned members
|
||||
uint32_t measurement_start_time_{0};
|
||||
uint32_t measurement_duration_{0};
|
||||
|
||||
// 1-byte members grouped together to minimize padding
|
||||
State state_{IDLE};
|
||||
BH1750Mode current_mode_{BH1750_MODE_L};
|
||||
uint8_t current_mtreg_{31};
|
||||
BH1750Mode fine_mode_{BH1750_MODE_H2};
|
||||
uint8_t fine_mtreg_{254};
|
||||
uint8_t active_mtreg_{0};
|
||||
|
||||
// Helper methods
|
||||
bool start_measurement_(BH1750Mode mode, uint8_t mtreg, uint32_t now);
|
||||
bool read_measurement_(float &lx_out);
|
||||
void process_coarse_result_(float lx);
|
||||
void fail_and_reset_();
|
||||
};
|
||||
|
||||
} // namespace bh1750
|
||||
} // namespace esphome
|
||||
} // namespace esphome::bh1750
|
||||
|
||||
@@ -1,9 +1,23 @@
|
||||
# This file was auto-generated by libretiny/generate_components.py
|
||||
# Do not modify its contents.
|
||||
# For custom pin validators, put validate_pin() or validate_usage()
|
||||
# in gpio.py file in this directory.
|
||||
# For changing schema/pin schema, put COMPONENT_SCHEMA or COMPONENT_PIN_SCHEMA
|
||||
# in schema.py file in this directory.
|
||||
"""
|
||||
██╗ ██╗ █████╗ ██████╗ ███╗ ██╗██╗███╗ ██╗ ██████╗
|
||||
██║ ██║██╔══██╗██╔══██╗████╗ ██║██║████╗ ██║██╔════╝
|
||||
██║ █╗ ██║███████║██████╔╝██╔██╗ ██║██║██╔██╗ ██║██║ ███╗
|
||||
██║███╗██║██╔══██║██╔══██╗██║╚██╗██║██║██║╚██╗██║██║ ██║
|
||||
╚███╔███╔╝██║ ██║██║ ██║██║ ╚████║██║██║ ╚████║╚██████╔╝
|
||||
╚══╝╚══╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═══╝╚═╝╚═╝ ╚═══╝ ╚═════╝
|
||||
|
||||
AUTO-GENERATED FILE - DO NOT EDIT!
|
||||
|
||||
This file was auto-generated by libretiny/generate_components.py.
|
||||
Any manual changes WILL BE LOST on regeneration.
|
||||
|
||||
To customize this component:
|
||||
- Pin validators: Create gpio.py with validate_pin() or validate_usage()
|
||||
- Schema extensions: Create schema.py with COMPONENT_SCHEMA or COMPONENT_PIN_SCHEMA
|
||||
|
||||
Platform-specific code should be added to the main libretiny component
|
||||
(__init__.py in esphome/components/libretiny/) rather than here.
|
||||
"""
|
||||
|
||||
from esphome import pins
|
||||
from esphome.components import libretiny
|
||||
@@ -27,6 +41,7 @@ COMPONENT_DATA = LibreTinyComponent(
|
||||
board_pins=BK72XX_BOARD_PINS,
|
||||
pin_validation=None,
|
||||
usage_validation=None,
|
||||
supports_atomics=False,
|
||||
)
|
||||
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,5 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components.logger import request_log_listener
|
||||
from esphome.components.zephyr import zephyr_add_prj_conf
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ID, CONF_LOGS, CONF_TYPE
|
||||
@@ -25,5 +26,8 @@ CONFIG_SCHEMA = cv.All(
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
zephyr_add_prj_conf("BT_NUS", True)
|
||||
cg.add(var.set_expose_log(config[CONF_TYPE] == CONF_LOGS))
|
||||
expose_log = config[CONF_TYPE] == CONF_LOGS
|
||||
cg.add(var.set_expose_log(expose_log))
|
||||
if expose_log:
|
||||
request_log_listener() # Request a log listener slot for BLE NUS log streaming
|
||||
await cg.register_component(var, config)
|
||||
|
||||
@@ -50,7 +50,7 @@ TYPES = [
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(cg.Component),
|
||||
cv.GenerateID(): cv.declare_id(cg.EntityBase),
|
||||
cv.GenerateID(CONF_BME68X_BSEC2_ID): cv.use_id(BME68xBSEC2Component),
|
||||
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_CELSIUS,
|
||||
|
||||
@@ -184,6 +184,7 @@ async def to_code(config):
|
||||
height,
|
||||
init_sequence_id,
|
||||
init_sequence_length,
|
||||
*model.get_constructor_args(config),
|
||||
)
|
||||
|
||||
# Rotation is handled by setting the transform
|
||||
|
||||
@@ -54,20 +54,14 @@ void EPaperBase::setup_pins_() const {
|
||||
float EPaperBase::get_setup_priority() const { return setup_priority::PROCESSOR; }
|
||||
|
||||
void EPaperBase::command(uint8_t value) {
|
||||
this->start_command_();
|
||||
ESP_LOGV(TAG, "Command: 0x%02X", value);
|
||||
this->dc_pin_->digital_write(false);
|
||||
this->enable();
|
||||
this->write_byte(value);
|
||||
this->end_command_();
|
||||
}
|
||||
|
||||
void EPaperBase::data(uint8_t value) {
|
||||
this->start_data_();
|
||||
this->write_byte(value);
|
||||
this->end_data_();
|
||||
this->disable();
|
||||
}
|
||||
|
||||
// write a command followed by zero or more bytes of data.
|
||||
// The command is the first byte, length is the length of data only in the second byte, followed by the data.
|
||||
// [COMMAND, LENGTH, DATA...]
|
||||
void EPaperBase::cmd_data(uint8_t command, const uint8_t *ptr, size_t length) {
|
||||
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
|
||||
char hex_buf[format_hex_pretty_size(EPAPER_MAX_CMD_LOG_BYTES)];
|
||||
@@ -130,14 +124,10 @@ void EPaperBase::wait_for_idle_(bool should_wait) {
|
||||
|
||||
void EPaperBase::loop() {
|
||||
auto now = millis();
|
||||
if (this->delay_until_ != 0) {
|
||||
// using modulus arithmetic to handle wrap-around
|
||||
int diff = now - this->delay_until_;
|
||||
if (diff < 0) {
|
||||
return;
|
||||
}
|
||||
this->delay_until_ = 0;
|
||||
}
|
||||
// using modulus arithmetic to handle wrap-around
|
||||
int diff = now - this->delay_until_;
|
||||
if (diff < 0)
|
||||
return;
|
||||
if (this->waiting_for_idle_) {
|
||||
if (this->is_idle_()) {
|
||||
this->waiting_for_idle_ = false;
|
||||
@@ -192,7 +182,7 @@ void EPaperBase::process_state_() {
|
||||
this->set_state_(EPaperState::RESET);
|
||||
break;
|
||||
case EPaperState::INITIALISE:
|
||||
this->initialise_();
|
||||
this->initialise(this->update_count_ != 0);
|
||||
this->set_state_(EPaperState::TRANSFER_DATA);
|
||||
break;
|
||||
case EPaperState::TRANSFER_DATA:
|
||||
@@ -230,11 +220,11 @@ void EPaperBase::set_state_(EPaperState state, uint16_t delay) {
|
||||
ESP_LOGV(TAG, "Exit state %s", this->epaper_state_to_string_());
|
||||
this->state_ = state;
|
||||
this->wait_for_idle_(state > EPaperState::SHOULD_WAIT);
|
||||
if (delay != 0) {
|
||||
this->delay_until_ = millis() + delay;
|
||||
} else {
|
||||
this->delay_until_ = 0;
|
||||
}
|
||||
// allow subclasses to nominate delays
|
||||
if (delay == 0)
|
||||
delay = this->next_delay_;
|
||||
this->next_delay_ = 0;
|
||||
this->delay_until_ = millis() + delay;
|
||||
ESP_LOGV(TAG, "Enter state %s, delay %u, wait_for_idle=%s", this->epaper_state_to_string_(), delay,
|
||||
TRUEFALSE(this->waiting_for_idle_));
|
||||
if (state == EPaperState::IDLE) {
|
||||
@@ -242,22 +232,14 @@ void EPaperBase::set_state_(EPaperState state, uint16_t delay) {
|
||||
}
|
||||
}
|
||||
|
||||
void EPaperBase::start_command_() {
|
||||
this->dc_pin_->digital_write(false);
|
||||
this->enable();
|
||||
}
|
||||
|
||||
void EPaperBase::end_command_() { this->disable(); }
|
||||
|
||||
void EPaperBase::start_data_() {
|
||||
this->dc_pin_->digital_write(true);
|
||||
this->enable();
|
||||
}
|
||||
void EPaperBase::end_data_() { this->disable(); }
|
||||
|
||||
void EPaperBase::on_safe_shutdown() { this->deep_sleep(); }
|
||||
|
||||
void EPaperBase::initialise_() {
|
||||
void EPaperBase::initialise(bool partial) {
|
||||
size_t index = 0;
|
||||
|
||||
auto *sequence = this->init_sequence_;
|
||||
@@ -317,9 +299,8 @@ bool EPaperBase::rotate_coordinates_(int &x, int &y) {
|
||||
void HOT EPaperBase::draw_pixel_at(int x, int y, Color color) {
|
||||
if (!rotate_coordinates_(x, y))
|
||||
return;
|
||||
const size_t pixel_position = y * this->width_ + x;
|
||||
const size_t byte_position = pixel_position / 8;
|
||||
const uint8_t bit_position = pixel_position % 8;
|
||||
const size_t byte_position = y * this->row_width_ + x / 8;
|
||||
const uint8_t bit_position = x % 8;
|
||||
const uint8_t pixel_bit = 0x80 >> bit_position;
|
||||
const auto original = this->buffer_[byte_position];
|
||||
if ((color_to_bit(color) == 0)) {
|
||||
|
||||
@@ -36,14 +36,16 @@ class EPaperBase : public Display,
|
||||
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW, spi::CLOCK_PHASE_LEADING,
|
||||
spi::DATA_RATE_2MHZ> {
|
||||
public:
|
||||
EPaperBase(const char *name, uint16_t width, uint16_t height, const uint8_t *init_sequence,
|
||||
size_t init_sequence_length, DisplayType display_type = DISPLAY_TYPE_BINARY)
|
||||
EPaperBase(const char *name, uint16_t width, uint16_t height, const uint8_t *init_sequence = nullptr,
|
||||
size_t init_sequence_length = 0, DisplayType display_type = DISPLAY_TYPE_BINARY)
|
||||
: name_(name),
|
||||
width_(width),
|
||||
height_(height),
|
||||
init_sequence_(init_sequence),
|
||||
init_sequence_length_(init_sequence_length),
|
||||
display_type_(display_type) {}
|
||||
display_type_(display_type) {
|
||||
this->row_width_ = (this->width_ + 7) / 8; // width of a row in bytes
|
||||
}
|
||||
void set_dc_pin(GPIOPin *dc_pin) { dc_pin_ = dc_pin; }
|
||||
float get_setup_priority() const override;
|
||||
void set_reset_pin(GPIOPin *reset) { this->reset_pin_ = reset; }
|
||||
@@ -54,9 +56,13 @@ class EPaperBase : public Display,
|
||||
void dump_config() override;
|
||||
|
||||
void command(uint8_t value);
|
||||
void data(uint8_t value);
|
||||
void cmd_data(uint8_t command, const uint8_t *ptr, size_t length);
|
||||
|
||||
// variant with in-place initializer list
|
||||
void cmd_data(uint8_t command, std::initializer_list<uint8_t> data) {
|
||||
this->cmd_data(command, data.begin(), data.size());
|
||||
}
|
||||
|
||||
void update() override;
|
||||
void loop() override;
|
||||
|
||||
@@ -109,7 +115,7 @@ class EPaperBase : public Display,
|
||||
bool is_idle_() const;
|
||||
void setup_pins_() const;
|
||||
virtual bool reset();
|
||||
void initialise_();
|
||||
virtual void initialise(bool partial);
|
||||
void wait_for_idle_(bool should_wait);
|
||||
bool init_buffer_(size_t buffer_length);
|
||||
bool rotate_coordinates_(int &x, int &y);
|
||||
@@ -143,14 +149,12 @@ class EPaperBase : public Display,
|
||||
|
||||
void set_state_(EPaperState state, uint16_t delay = 0);
|
||||
|
||||
void start_command_();
|
||||
void end_command_();
|
||||
void start_data_();
|
||||
void end_data_();
|
||||
|
||||
// properties initialised in the constructor
|
||||
const char *name_;
|
||||
uint16_t width_;
|
||||
uint16_t row_width_; // width of a row in bytes
|
||||
uint16_t height_;
|
||||
const uint8_t *init_sequence_;
|
||||
size_t init_sequence_length_;
|
||||
@@ -163,7 +167,8 @@ class EPaperBase : public Display,
|
||||
GPIOPin *busy_pin_{};
|
||||
GPIOPin *reset_pin_{};
|
||||
bool waiting_for_idle_{};
|
||||
uint32_t delay_until_{};
|
||||
uint32_t delay_until_{}; // timestamp until which to delay processing
|
||||
uint16_t next_delay_{}; // milliseconds to delay before next state
|
||||
uint8_t transform_{};
|
||||
uint8_t update_count_{};
|
||||
// these values represent the bounds of the updated buffer. Note that x_high and y_high
|
||||
|
||||
@@ -1,25 +1,24 @@
|
||||
#include "epaper_spi_ssd1677.h"
|
||||
#include "epaper_spi_mono.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome::epaper_spi {
|
||||
static constexpr const char *const TAG = "epaper_spi.ssd1677";
|
||||
static constexpr const char *const TAG = "epaper_spi.mono";
|
||||
|
||||
void EPaperSSD1677::refresh_screen(bool partial) {
|
||||
void EPaperMono::refresh_screen(bool partial) {
|
||||
ESP_LOGV(TAG, "Refresh screen");
|
||||
this->command(0x22);
|
||||
this->data(partial ? 0xFF : 0xF7);
|
||||
this->cmd_data(0x22, {partial ? (uint8_t) 0xFF : (uint8_t) 0xF7});
|
||||
this->command(0x20);
|
||||
}
|
||||
|
||||
void EPaperSSD1677::deep_sleep() {
|
||||
void EPaperMono::deep_sleep() {
|
||||
ESP_LOGV(TAG, "Deep sleep");
|
||||
this->command(0x10);
|
||||
}
|
||||
|
||||
bool EPaperSSD1677::reset() {
|
||||
bool EPaperMono::reset() {
|
||||
if (EPaperBase::reset()) {
|
||||
this->command(0x12);
|
||||
return true;
|
||||
@@ -27,29 +26,24 @@ bool EPaperSSD1677::reset() {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool HOT EPaperSSD1677::transfer_data() {
|
||||
void EPaperMono::set_window() {
|
||||
// round x-coordinates to byte boundaries
|
||||
this->x_low_ &= ~7;
|
||||
this->x_high_ += 7;
|
||||
this->x_high_ &= ~7;
|
||||
this->cmd_data(0x44, {(uint8_t) this->x_low_, (uint8_t) (this->x_low_ / 256), (uint8_t) (this->x_high_ - 1),
|
||||
(uint8_t) ((this->x_high_ - 1) / 256)});
|
||||
this->cmd_data(0x4E, {(uint8_t) this->x_low_, (uint8_t) (this->x_low_ / 256)});
|
||||
this->cmd_data(0x45, {(uint8_t) this->y_low_, (uint8_t) (this->y_low_ / 256), (uint8_t) (this->y_high_ - 1),
|
||||
(uint8_t) ((this->y_high_ - 1) / 256)});
|
||||
this->cmd_data(0x4F, {(uint8_t) this->y_low_, (uint8_t) (this->y_low_ / 256)});
|
||||
}
|
||||
|
||||
bool HOT EPaperMono::transfer_data() {
|
||||
auto start_time = millis();
|
||||
if (this->current_data_index_ == 0) {
|
||||
uint8_t data[4]{};
|
||||
// round to byte boundaries
|
||||
this->x_low_ &= ~7;
|
||||
this->y_low_ &= ~7;
|
||||
this->x_high_ += 7;
|
||||
this->x_high_ &= ~7;
|
||||
this->y_high_ += 7;
|
||||
this->y_high_ &= ~7;
|
||||
data[0] = this->x_low_;
|
||||
data[1] = this->x_low_ / 256;
|
||||
data[2] = this->x_high_ - 1;
|
||||
data[3] = (this->x_high_ - 1) / 256;
|
||||
cmd_data(0x4E, data, 2);
|
||||
cmd_data(0x44, data, sizeof(data));
|
||||
data[0] = this->y_low_;
|
||||
data[1] = this->y_low_ / 256;
|
||||
data[2] = this->y_high_ - 1;
|
||||
data[3] = (this->y_high_ - 1) / 256;
|
||||
cmd_data(0x4F, data, 2);
|
||||
this->cmd_data(0x45, data, sizeof(data));
|
||||
this->set_window();
|
||||
// for monochrome, we still need to clear the red data buffer at least once to prevent it
|
||||
// causing dirty pixels after partial refresh.
|
||||
this->command(this->send_red_ ? 0x26 : 0x24);
|
||||
@@ -58,10 +52,10 @@ bool HOT EPaperSSD1677::transfer_data() {
|
||||
size_t row_length = (this->x_high_ - this->x_low_) / 8;
|
||||
FixedVector<uint8_t> bytes_to_send{};
|
||||
bytes_to_send.init(row_length);
|
||||
ESP_LOGV(TAG, "Writing bytes at line %zu at %ums", this->current_data_index_, (unsigned) millis());
|
||||
ESP_LOGV(TAG, "Writing %u bytes at line %zu at %ums", row_length, this->current_data_index_, (unsigned) millis());
|
||||
this->start_data_();
|
||||
while (this->current_data_index_ != this->y_high_) {
|
||||
size_t data_idx = (this->current_data_index_ * this->width_ + this->x_low_) / 8;
|
||||
size_t data_idx = this->current_data_index_ * this->row_width_ + this->x_low_ / 8;
|
||||
for (size_t i = 0; i != row_length; i++) {
|
||||
bytes_to_send[i] = this->send_red_ ? 0 : this->buffer_[data_idx++];
|
||||
}
|
||||
@@ -69,12 +63,12 @@ bool HOT EPaperSSD1677::transfer_data() {
|
||||
this->write_array(&bytes_to_send.front(), row_length); // NOLINT
|
||||
if (millis() - start_time > MAX_TRANSFER_TIME) {
|
||||
// Let the main loop run and come back next loop
|
||||
this->end_data_();
|
||||
this->disable();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
this->end_data_();
|
||||
this->disable();
|
||||
this->current_data_index_ = 0;
|
||||
if (this->send_red_) {
|
||||
this->send_red_ = false;
|
||||
@@ -3,13 +3,15 @@
|
||||
#include "epaper_spi.h"
|
||||
|
||||
namespace esphome::epaper_spi {
|
||||
|
||||
class EPaperSSD1677 : public EPaperBase {
|
||||
/**
|
||||
* A class for monochrome epaper displays.
|
||||
*/
|
||||
class EPaperMono : public EPaperBase {
|
||||
public:
|
||||
EPaperSSD1677(const char *name, uint16_t width, uint16_t height, const uint8_t *init_sequence,
|
||||
size_t init_sequence_length)
|
||||
EPaperMono(const char *name, uint16_t width, uint16_t height, const uint8_t *init_sequence,
|
||||
size_t init_sequence_length)
|
||||
: EPaperBase(name, width, height, init_sequence, init_sequence_length, DISPLAY_TYPE_BINARY) {
|
||||
this->buffer_length_ = width * height / 8; // 8 pixels per byte
|
||||
this->buffer_length_ = (width + 7) / 8 * height; // 8 pixels per byte, rounded up
|
||||
}
|
||||
|
||||
protected:
|
||||
@@ -18,6 +20,7 @@ class EPaperSSD1677 : public EPaperBase {
|
||||
void power_off() override{};
|
||||
void deep_sleep() override;
|
||||
bool reset() override;
|
||||
virtual void set_window();
|
||||
bool transfer_data() override;
|
||||
bool send_red_{true};
|
||||
};
|
||||
@@ -80,20 +80,17 @@ void EPaperSpectraE6::power_on() {
|
||||
|
||||
void EPaperSpectraE6::power_off() {
|
||||
ESP_LOGV(TAG, "Power off");
|
||||
this->command(0x02);
|
||||
this->data(0x00);
|
||||
this->cmd_data(0x02, {0x00});
|
||||
}
|
||||
|
||||
void EPaperSpectraE6::refresh_screen(bool partial) {
|
||||
ESP_LOGV(TAG, "Refresh");
|
||||
this->command(0x12);
|
||||
this->data(0x00);
|
||||
this->cmd_data(0x12, {0x00});
|
||||
}
|
||||
|
||||
void EPaperSpectraE6::deep_sleep() {
|
||||
ESP_LOGV(TAG, "Deep sleep");
|
||||
this->command(0x07);
|
||||
this->data(0xA5);
|
||||
this->cmd_data(0x07, {0xA5});
|
||||
}
|
||||
|
||||
void EPaperSpectraE6::fill(Color color) {
|
||||
@@ -143,7 +140,7 @@ bool HOT EPaperSpectraE6::transfer_data() {
|
||||
if (buf_idx == sizeof bytes_to_send) {
|
||||
this->start_data_();
|
||||
this->write_array(bytes_to_send, buf_idx);
|
||||
this->end_data_();
|
||||
this->disable();
|
||||
ESP_LOGV(TAG, "Wrote %d bytes at %ums", buf_idx, (unsigned) millis());
|
||||
buf_idx = 0;
|
||||
|
||||
@@ -157,7 +154,7 @@ bool HOT EPaperSpectraE6::transfer_data() {
|
||||
if (buf_idx != 0) {
|
||||
this->start_data_();
|
||||
this->write_array(bytes_to_send, buf_idx);
|
||||
this->end_data_();
|
||||
this->disable();
|
||||
}
|
||||
this->current_data_index_ = 0;
|
||||
return true;
|
||||
|
||||
47
esphome/components/epaper_spi/epaper_waveshare.cpp
Normal file
47
esphome/components/epaper_spi/epaper_waveshare.cpp
Normal file
@@ -0,0 +1,47 @@
|
||||
#include "epaper_waveshare.h"
|
||||
|
||||
namespace esphome::epaper_spi {
|
||||
|
||||
static const char *const TAG = "epaper_spi.waveshare";
|
||||
|
||||
void EpaperWaveshare::initialise(bool partial) {
|
||||
EPaperBase::initialise(partial);
|
||||
if (partial) {
|
||||
this->cmd_data(0x32, this->partial_lut_, this->partial_lut_length_);
|
||||
this->cmd_data(0x3C, {0x80});
|
||||
this->cmd_data(0x22, {0xC0});
|
||||
this->command(0x20);
|
||||
this->next_delay_ = 100;
|
||||
} else {
|
||||
this->cmd_data(0x32, this->lut_, this->lut_length_);
|
||||
this->cmd_data(0x3C, {0x05});
|
||||
}
|
||||
this->send_red_ = true;
|
||||
}
|
||||
|
||||
void EpaperWaveshare::set_window() {
|
||||
this->x_low_ &= ~7;
|
||||
this->x_high_ += 7;
|
||||
this->x_high_ &= ~7;
|
||||
uint16_t x_start = this->x_low_ / 8;
|
||||
uint16_t x_end = (this->x_high_ - 1) / 8;
|
||||
this->cmd_data(0x44, {(uint8_t) x_start, (uint8_t) (x_end)});
|
||||
this->cmd_data(0x4E, {(uint8_t) x_start});
|
||||
this->cmd_data(0x45, {(uint8_t) this->y_low_, (uint8_t) (this->y_low_ / 256), (uint8_t) (this->y_high_ - 1),
|
||||
(uint8_t) ((this->y_high_ - 1) / 256)});
|
||||
this->cmd_data(0x4F, {(uint8_t) this->y_low_, (uint8_t) (this->y_low_ / 256)});
|
||||
ESP_LOGV(TAG, "Set window X: %u-%u, Y: %u-%u", this->x_low_, this->x_high_, this->y_low_, this->y_high_);
|
||||
}
|
||||
|
||||
void EpaperWaveshare::refresh_screen(bool partial) {
|
||||
if (partial) {
|
||||
this->cmd_data(0x22, {0x0F});
|
||||
} else {
|
||||
this->cmd_data(0x22, {0xC7});
|
||||
}
|
||||
this->command(0x20);
|
||||
this->next_delay_ = partial ? 100 : 3000;
|
||||
}
|
||||
|
||||
void EpaperWaveshare::deep_sleep() { this->cmd_data(0x10, {0x01}); }
|
||||
} // namespace esphome::epaper_spi
|
||||
30
esphome/components/epaper_spi/epaper_waveshare.h
Normal file
30
esphome/components/epaper_spi/epaper_waveshare.h
Normal file
@@ -0,0 +1,30 @@
|
||||
#pragma once
|
||||
#include "epaper_spi.h"
|
||||
#include "epaper_spi_mono.h"
|
||||
|
||||
namespace esphome::epaper_spi {
|
||||
/**
|
||||
* An epaper display that needs LUTs to be sent to it.
|
||||
*/
|
||||
class EpaperWaveshare : public EPaperMono {
|
||||
public:
|
||||
EpaperWaveshare(const char *name, uint16_t width, uint16_t height, const uint8_t *init_sequence,
|
||||
size_t init_sequence_length, const uint8_t *lut, size_t lut_length, const uint8_t *partial_lut,
|
||||
uint16_t partial_lut_length)
|
||||
: EPaperMono(name, width, height, init_sequence, init_sequence_length),
|
||||
lut_(lut),
|
||||
lut_length_(lut_length),
|
||||
partial_lut_(partial_lut),
|
||||
partial_lut_length_(partial_lut_length) {}
|
||||
|
||||
protected:
|
||||
void initialise(bool partial) override;
|
||||
void set_window() override;
|
||||
void refresh_screen(bool partial) override;
|
||||
void deep_sleep() override;
|
||||
const uint8_t *lut_;
|
||||
size_t lut_length_;
|
||||
const uint8_t *partial_lut_;
|
||||
uint16_t partial_lut_length_;
|
||||
};
|
||||
} // namespace esphome::epaper_spi
|
||||
@@ -32,6 +32,9 @@ class EpaperModel:
|
||||
return cv.Required(name)
|
||||
return cv.Optional(name, default=self.get_default(name, fallback))
|
||||
|
||||
def get_constructor_args(self, config) -> tuple:
|
||||
return ()
|
||||
|
||||
def get_dimensions(self, config) -> tuple[int, int]:
|
||||
if CONF_DIMENSIONS in config:
|
||||
# Explicit dimensions, just use as is
|
||||
|
||||
@@ -4,10 +4,9 @@ from . import EpaperModel
|
||||
|
||||
|
||||
class SSD1677(EpaperModel):
|
||||
def __init__(self, name, class_name="EPaperSSD1677", **kwargs):
|
||||
if CONF_DATA_RATE not in kwargs:
|
||||
kwargs[CONF_DATA_RATE] = "20MHz"
|
||||
super().__init__(name, class_name, **kwargs)
|
||||
def __init__(self, name, class_name="EPaperMono", data_rate="20MHz", **defaults):
|
||||
defaults[CONF_DATA_RATE] = data_rate
|
||||
super().__init__(name, class_name, **defaults)
|
||||
|
||||
# fmt: off
|
||||
def get_init_sequence(self, config: dict):
|
||||
@@ -23,11 +22,15 @@ class SSD1677(EpaperModel):
|
||||
|
||||
ssd1677 = SSD1677("ssd1677")
|
||||
|
||||
ssd1677.extend(
|
||||
"seeed-ee04-mono-4.26",
|
||||
wave_4_26 = ssd1677.extend(
|
||||
"waveshare-4.26in",
|
||||
width=800,
|
||||
height=480,
|
||||
mirror_x=True,
|
||||
)
|
||||
|
||||
wave_4_26.extend(
|
||||
"seeed-ee04-mono-4.26",
|
||||
cs_pin=44,
|
||||
dc_pin=10,
|
||||
reset_pin=38,
|
||||
|
||||
88
esphome/components/epaper_spi/models/waveshare.py
Normal file
88
esphome/components/epaper_spi/models/waveshare.py
Normal file
@@ -0,0 +1,88 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.core import ID
|
||||
|
||||
from ..display import CONF_INIT_SEQUENCE_ID
|
||||
from . import EpaperModel
|
||||
|
||||
|
||||
class WaveshareModel(EpaperModel):
|
||||
def __init__(self, name, lut, lut_partial=None, **defaults):
|
||||
super().__init__(name, "EpaperWaveshare", **defaults)
|
||||
self.lut = lut
|
||||
self.lut_partial = lut_partial
|
||||
|
||||
def get_constructor_args(self, config) -> tuple:
|
||||
lut = (
|
||||
cg.static_const_array(
|
||||
ID(config[CONF_INIT_SEQUENCE_ID].id + "_lut", type=cg.uint8), self.lut
|
||||
),
|
||||
len(self.lut),
|
||||
)
|
||||
if self.lut_partial is None:
|
||||
lut_partial = cg.nullptr, 0
|
||||
else:
|
||||
lut_partial = (
|
||||
cg.static_const_array(
|
||||
ID(
|
||||
config[CONF_INIT_SEQUENCE_ID].id + "_lut_partial", type=cg.uint8
|
||||
),
|
||||
self.lut_partial,
|
||||
),
|
||||
len(self.lut_partial),
|
||||
)
|
||||
return *lut, *lut_partial
|
||||
|
||||
|
||||
# fmt: off
|
||||
WaveshareModel(
|
||||
"waveshare-2.13in-v3",
|
||||
width=122,
|
||||
height=250,
|
||||
initsequence=(
|
||||
(0x01, 0x27, 0x01, 0x00), # driver output control
|
||||
(0x37, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00),
|
||||
(0x11, 0x03), # Data entry mode
|
||||
(0x3F, 0x22), # Undocumented command
|
||||
(0x2C, 0x36), # write VCOM register
|
||||
(0x04, 0x41, 0x0C, 0x32), # SRC voltage
|
||||
(0x03, 0x17), # Gate voltage
|
||||
(0x21, 0x00, 0x80), # Display update control
|
||||
(0x18, 0x80), # Select internal temperature sensor
|
||||
),
|
||||
lut=(
|
||||
0x80, 0x4A, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x40, 0x4A, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x80, 0x4A, 0x40, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40, 0x4A, 0x80, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0xF, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xF, 0x0, 0x0,
|
||||
0xF, 0x0, 0x0, 0x2, 0xF, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,
|
||||
0x0, 0x0, 0x0,
|
||||
),
|
||||
lut_partial=(
|
||||
0x0, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x80, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x40, 0x40, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x80, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0xF, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,
|
||||
0x0, 0x0, 0x0,
|
||||
),
|
||||
)
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <esp_app_desc.h>
|
||||
#include <esp_hosted.h>
|
||||
#include <esp_hosted_host_fw_ver.h>
|
||||
#include <esp_ota_ops.h>
|
||||
|
||||
#ifdef USE_ESP32_HOSTED_HTTP_UPDATE
|
||||
#include "esphome/components/json/json_util.h"
|
||||
@@ -442,6 +443,12 @@ void Esp32HostedUpdate::perform(bool force) {
|
||||
this->status_clear_error();
|
||||
this->publish_state();
|
||||
|
||||
#ifdef USE_OTA_ROLLBACK
|
||||
// Mark the host partition as valid before rebooting, in case the safe mode
|
||||
// timer hasn't expired yet.
|
||||
esp_ota_mark_app_valid_cancel_rollback();
|
||||
#endif
|
||||
|
||||
// Schedule a restart to ensure everything is in sync
|
||||
ESP_LOGI(TAG, "Restarting in 1 second");
|
||||
this->set_timeout(1000, []() { App.safe_reboot(); });
|
||||
|
||||
@@ -370,12 +370,14 @@ void ESPHomeOTAComponent::handle_data_() {
|
||||
|
||||
error:
|
||||
this->write_byte_(static_cast<uint8_t>(error_code));
|
||||
this->cleanup_connection_();
|
||||
|
||||
// Abort backend before cleanup - cleanup_connection_() destroys the backend
|
||||
if (this->backend_ != nullptr && update_started) {
|
||||
this->backend_->abort();
|
||||
}
|
||||
|
||||
this->cleanup_connection_();
|
||||
|
||||
this->status_momentary_error("err", 5000);
|
||||
#ifdef USE_OTA_STATE_LISTENER
|
||||
this->notify_state_(ota::OTA_ERROR, 0.0f, static_cast<uint8_t>(error_code));
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
@@ -48,6 +49,24 @@ class Event : public EntityBase, public EntityBase_DeviceClass {
|
||||
/// Return the last triggered event type, or empty StringRef if no event triggered yet.
|
||||
StringRef get_last_event_type() const { return StringRef::from_maybe_nullptr(this->last_event_type_); }
|
||||
|
||||
/// Return event type by index, or nullptr if index is out of bounds.
|
||||
const char *get_event_type(uint8_t index) const {
|
||||
return index < this->types_.size() ? this->types_[index] : nullptr;
|
||||
}
|
||||
|
||||
/// Return index of last triggered event type, or max uint8_t if no event triggered yet.
|
||||
uint8_t get_last_event_type_index() const {
|
||||
if (this->last_event_type_ == nullptr)
|
||||
return std::numeric_limits<uint8_t>::max();
|
||||
// Most events have <3 types, uint8_t is sufficient for all reasonable scenarios
|
||||
const uint8_t size = static_cast<uint8_t>(this->types_.size());
|
||||
for (uint8_t i = 0; i < size; i++) {
|
||||
if (this->types_[i] == this->last_event_type_)
|
||||
return i;
|
||||
}
|
||||
return std::numeric_limits<uint8_t>::max();
|
||||
}
|
||||
|
||||
/// Check if an event has been triggered.
|
||||
bool has_event() const { return this->last_event_type_ != nullptr; }
|
||||
|
||||
|
||||
@@ -340,8 +340,8 @@ void I2SAudioSpeaker::speaker_task(void *params) {
|
||||
const uint32_t read_delay =
|
||||
(this_speaker->current_stream_info_.frames_to_microseconds(frames_written) / 1000) / 2;
|
||||
|
||||
uint8_t *new_data = transfer_buffer->get_buffer_end(); // track start of any newly copied bytes
|
||||
size_t bytes_read = transfer_buffer->transfer_data_from_source(pdMS_TO_TICKS(read_delay));
|
||||
uint8_t *new_data = transfer_buffer->get_buffer_end() - bytes_read;
|
||||
|
||||
if (bytes_read > 0) {
|
||||
if (this_speaker->q15_volume_factor_ < INT16_MAX) {
|
||||
|
||||
@@ -193,8 +193,12 @@ std::vector<uint8_t> ImprovSerialComponent::build_rpc_settings_response_(improv:
|
||||
#ifdef USE_WEBSERVER
|
||||
for (auto &ip : wifi::global_wifi_component->wifi_sta_ip_addresses()) {
|
||||
if (ip.is_ip4()) {
|
||||
std::string webserver_url = "http://" + ip.str() + ":" + to_string(USE_WEBSERVER_PORT);
|
||||
urls.push_back(webserver_url);
|
||||
char ip_buf[network::IP_ADDRESS_BUFFER_SIZE];
|
||||
ip.str_to(ip_buf);
|
||||
// "http://" (7) + IP (40) + ":" (1) + port (5) + null (1) = 54
|
||||
char webserver_url[7 + network::IP_ADDRESS_BUFFER_SIZE + 1 + 5 + 1];
|
||||
snprintf(webserver_url, sizeof(webserver_url), "http://%s:%u", ip_buf, USE_WEBSERVER_PORT);
|
||||
urls.emplace_back(webserver_url);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
11
esphome/components/ir_rf_proxy/__init__.py
Normal file
11
esphome/components/ir_rf_proxy/__init__.py
Normal file
@@ -0,0 +1,11 @@
|
||||
"""IR/RF Proxy component - provides remote_base backend for infrared platform."""
|
||||
|
||||
import esphome.codegen as cg
|
||||
|
||||
CODEOWNERS = ["@kbx81"]
|
||||
|
||||
# Namespace and constants exported for infrared.py platform
|
||||
ir_rf_proxy_ns = cg.esphome_ns.namespace("ir_rf_proxy")
|
||||
|
||||
CONF_REMOTE_RECEIVER_ID = "remote_receiver_id"
|
||||
CONF_REMOTE_TRANSMITTER_ID = "remote_transmitter_id"
|
||||
77
esphome/components/ir_rf_proxy/infrared.py
Normal file
77
esphome/components/ir_rf_proxy/infrared.py
Normal file
@@ -0,0 +1,77 @@
|
||||
"""Infrared platform implementation using remote_base (remote_transmitter/receiver)."""
|
||||
|
||||
from typing import Any
|
||||
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import infrared, remote_receiver, remote_transmitter
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_CARRIER_DUTY_PERCENT, CONF_FREQUENCY
|
||||
import esphome.final_validate as fv
|
||||
|
||||
from . import CONF_REMOTE_RECEIVER_ID, CONF_REMOTE_TRANSMITTER_ID, ir_rf_proxy_ns
|
||||
|
||||
CODEOWNERS = ["@kbx81"]
|
||||
DEPENDENCIES = ["infrared"]
|
||||
|
||||
IrRfProxy = ir_rf_proxy_ns.class_("IrRfProxy", infrared.Infrared)
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
infrared.infrared_schema(IrRfProxy).extend(
|
||||
{
|
||||
cv.Optional(CONF_FREQUENCY, default=0): cv.frequency,
|
||||
cv.Optional(CONF_REMOTE_RECEIVER_ID): cv.use_id(
|
||||
remote_receiver.RemoteReceiverComponent
|
||||
),
|
||||
cv.Optional(CONF_REMOTE_TRANSMITTER_ID): cv.use_id(
|
||||
remote_transmitter.RemoteTransmitterComponent
|
||||
),
|
||||
}
|
||||
),
|
||||
cv.has_exactly_one_key(CONF_REMOTE_RECEIVER_ID, CONF_REMOTE_TRANSMITTER_ID),
|
||||
)
|
||||
|
||||
|
||||
def _final_validate(config: dict[str, Any]) -> None:
|
||||
"""Validate that transmitters have a proper carrier duty cycle."""
|
||||
# Only validate if this is an infrared (not RF) configuration with a transmitter
|
||||
if config.get(CONF_FREQUENCY, 0) != 0 or CONF_REMOTE_TRANSMITTER_ID not in config:
|
||||
return
|
||||
|
||||
# Get the transmitter configuration
|
||||
transmitter_id = config[CONF_REMOTE_TRANSMITTER_ID]
|
||||
full_config = fv.full_config.get()
|
||||
transmitter_path = full_config.get_path_for_id(transmitter_id)[:-1]
|
||||
transmitter_config = full_config.get_config_for_path(transmitter_path)
|
||||
|
||||
# Check if carrier_duty_percent set to 0 or 100
|
||||
# Note: remote_transmitter schema requires this field and validates 1-100%,
|
||||
# but we double-check here for infrared to provide a helpful error message
|
||||
duty_percent = transmitter_config.get(CONF_CARRIER_DUTY_PERCENT)
|
||||
if duty_percent in {0, 100}:
|
||||
raise cv.Invalid(
|
||||
f"Transmitter '{transmitter_id}' must have '{CONF_CARRIER_DUTY_PERCENT}' configured with "
|
||||
"an intermediate value (typically 30-50%) for infrared transmission. If this is an RF "
|
||||
f"transmitter, configure this infrared with a '{CONF_FREQUENCY}' value greater than 0"
|
||||
)
|
||||
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = _final_validate
|
||||
|
||||
|
||||
async def to_code(config: dict[str, Any]) -> None:
|
||||
"""Code generation for remote_base infrared platform."""
|
||||
# Create and register the infrared entity
|
||||
var = await infrared.new_infrared(config)
|
||||
|
||||
# Set frequency / 1000; zero indicates infrared hardware
|
||||
cg.add(var.set_frequency(config[CONF_FREQUENCY] / 1000))
|
||||
|
||||
# Link transmitter if specified
|
||||
if CONF_REMOTE_TRANSMITTER_ID in config:
|
||||
transmitter = await cg.get_variable(config[CONF_REMOTE_TRANSMITTER_ID])
|
||||
cg.add(var.set_transmitter(transmitter))
|
||||
|
||||
# Link receiver if specified
|
||||
if CONF_REMOTE_RECEIVER_ID in config:
|
||||
receiver = await cg.get_variable(config[CONF_REMOTE_RECEIVER_ID])
|
||||
cg.add(var.set_receiver(receiver))
|
||||
23
esphome/components/ir_rf_proxy/ir_rf_proxy.cpp
Normal file
23
esphome/components/ir_rf_proxy/ir_rf_proxy.cpp
Normal file
@@ -0,0 +1,23 @@
|
||||
#include "ir_rf_proxy.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome::ir_rf_proxy {
|
||||
|
||||
static const char *const TAG = "ir_rf_proxy";
|
||||
|
||||
void IrRfProxy::dump_config() {
|
||||
ESP_LOGCONFIG(TAG,
|
||||
"IR/RF Proxy '%s'\n"
|
||||
" Supports Transmitter: %s\n"
|
||||
" Supports Receiver: %s",
|
||||
this->get_name().c_str(), YESNO(this->traits_.get_supports_transmitter()),
|
||||
YESNO(this->traits_.get_supports_receiver()));
|
||||
|
||||
if (this->is_rf()) {
|
||||
ESP_LOGCONFIG(TAG, " Hardware Type: RF (%.3f MHz)", this->frequency_khz_ / 1e3f);
|
||||
} else {
|
||||
ESP_LOGCONFIG(TAG, " Hardware Type: Infrared");
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace esphome::ir_rf_proxy
|
||||
32
esphome/components/ir_rf_proxy/ir_rf_proxy.h
Normal file
32
esphome/components/ir_rf_proxy/ir_rf_proxy.h
Normal file
@@ -0,0 +1,32 @@
|
||||
#pragma once
|
||||
|
||||
// WARNING: This component is EXPERIMENTAL. The API may change at any time
|
||||
// without following the normal breaking changes policy. Use at your own risk.
|
||||
// Once the API is considered stable, this warning will be removed.
|
||||
|
||||
#include "esphome/components/infrared/infrared.h"
|
||||
#include "esphome/components/remote_transmitter/remote_transmitter.h"
|
||||
#include "esphome/components/remote_receiver/remote_receiver.h"
|
||||
|
||||
namespace esphome::ir_rf_proxy {
|
||||
|
||||
/// IrRfProxy - Infrared platform implementation using remote_transmitter/receiver as backend
|
||||
class IrRfProxy : public infrared::Infrared {
|
||||
public:
|
||||
IrRfProxy() = default;
|
||||
|
||||
void dump_config() override;
|
||||
|
||||
/// Set RF frequency in kHz (0 = infrared, non-zero = RF)
|
||||
void set_frequency(uint32_t frequency_khz) { this->frequency_khz_ = frequency_khz; }
|
||||
/// Get RF frequency in kHz
|
||||
uint32_t get_frequency() const { return this->frequency_khz_; }
|
||||
/// Check if this is RF mode (non-zero frequency)
|
||||
bool is_rf() const { return this->frequency_khz_ > 0; }
|
||||
|
||||
protected:
|
||||
// RF frequency in kHz (Hz / 1000); 0 = infrared, non-zero = RF
|
||||
uint32_t frequency_khz_{0};
|
||||
};
|
||||
|
||||
} // namespace esphome::ir_rf_proxy
|
||||
@@ -5,6 +5,7 @@ from esphome.const import (
|
||||
CONF_HAS_MOVING_TARGET,
|
||||
CONF_HAS_STILL_TARGET,
|
||||
CONF_HAS_TARGET,
|
||||
CONF_ID,
|
||||
DEVICE_CLASS_MOTION,
|
||||
DEVICE_CLASS_OCCUPANCY,
|
||||
DEVICE_CLASS_PRESENCE,
|
||||
@@ -19,6 +20,7 @@ DEPENDENCIES = ["ld2410"]
|
||||
CONF_OUT_PIN_PRESENCE_STATUS = "out_pin_presence_status"
|
||||
|
||||
CONFIG_SCHEMA = {
|
||||
cv.GenerateID(CONF_ID): cv.declare_id(cg.EntityBase),
|
||||
cv.GenerateID(CONF_LD2410_ID): cv.use_id(LD2410Component),
|
||||
cv.Optional(CONF_HAS_TARGET): binary_sensor.binary_sensor_schema(
|
||||
device_class=DEVICE_CLASS_OCCUPANCY,
|
||||
|
||||
@@ -3,6 +3,7 @@ from esphome.components import button
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_FACTORY_RESET,
|
||||
CONF_ID,
|
||||
CONF_RESTART,
|
||||
DEVICE_CLASS_RESTART,
|
||||
ENTITY_CATEGORY_CONFIG,
|
||||
@@ -21,6 +22,7 @@ RestartButton = ld2410_ns.class_("RestartButton", button.Button)
|
||||
CONF_QUERY_PARAMS = "query_params"
|
||||
|
||||
CONFIG_SCHEMA = {
|
||||
cv.GenerateID(CONF_ID): cv.declare_id(cg.EntityBase),
|
||||
cv.GenerateID(CONF_LD2410_ID): cv.use_id(LD2410Component),
|
||||
cv.Optional(CONF_FACTORY_RESET): button.button_schema(
|
||||
FactoryResetButton,
|
||||
|
||||
@@ -31,6 +31,7 @@ TIMEOUT_GROUP = "timeout"
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(CONF_ID): cv.declare_id(cg.EntityBase),
|
||||
cv.GenerateID(CONF_LD2410_ID): cv.use_id(LD2410Component),
|
||||
cv.Inclusive(CONF_TIMEOUT, TIMEOUT_GROUP): number.number_schema(
|
||||
MaxDistanceTimeoutNumber,
|
||||
|
||||
@@ -3,6 +3,7 @@ from esphome.components import select
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_BAUD_RATE,
|
||||
CONF_ID,
|
||||
ENTITY_CATEGORY_CONFIG,
|
||||
ICON_LIGHTBULB,
|
||||
ICON_RULER,
|
||||
@@ -22,6 +23,7 @@ CONF_OUT_PIN_LEVEL = "out_pin_level"
|
||||
|
||||
|
||||
CONFIG_SCHEMA = {
|
||||
cv.GenerateID(CONF_ID): cv.declare_id(cg.EntityBase),
|
||||
cv.GenerateID(CONF_LD2410_ID): cv.use_id(LD2410Component),
|
||||
cv.Optional(CONF_DISTANCE_RESOLUTION): select.select_schema(
|
||||
DistanceResolutionSelect,
|
||||
|
||||
@@ -2,6 +2,7 @@ import esphome.codegen as cg
|
||||
from esphome.components import sensor
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
CONF_LIGHT,
|
||||
CONF_MOVING_DISTANCE,
|
||||
DEVICE_CLASS_DISTANCE,
|
||||
@@ -28,6 +29,7 @@ CONF_STILL_ENERGY = "still_energy"
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(CONF_ID): cv.declare_id(cg.EntityBase),
|
||||
cv.GenerateID(CONF_LD2410_ID): cv.use_id(LD2410Component),
|
||||
cv.Optional(CONF_MOVING_DISTANCE): sensor.sensor_schema(
|
||||
device_class=DEVICE_CLASS_DISTANCE,
|
||||
|
||||
@@ -3,6 +3,7 @@ from esphome.components import switch
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_BLUETOOTH,
|
||||
CONF_ID,
|
||||
DEVICE_CLASS_SWITCH,
|
||||
ENTITY_CATEGORY_CONFIG,
|
||||
ICON_BLUETOOTH,
|
||||
@@ -17,6 +18,7 @@ EngineeringModeSwitch = ld2410_ns.class_("EngineeringModeSwitch", switch.Switch)
|
||||
CONF_ENGINEERING_MODE = "engineering_mode"
|
||||
|
||||
CONFIG_SCHEMA = {
|
||||
cv.GenerateID(CONF_ID): cv.declare_id(cg.EntityBase),
|
||||
cv.GenerateID(CONF_LD2410_ID): cv.use_id(LD2410Component),
|
||||
cv.Optional(CONF_ENGINEERING_MODE): switch.switch_schema(
|
||||
EngineeringModeSwitch,
|
||||
|
||||
@@ -2,6 +2,7 @@ import esphome.codegen as cg
|
||||
from esphome.components import text_sensor
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
CONF_MAC_ADDRESS,
|
||||
CONF_VERSION,
|
||||
ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
@@ -14,6 +15,7 @@ from . import CONF_LD2410_ID, LD2410Component
|
||||
DEPENDENCIES = ["ld2410"]
|
||||
|
||||
CONFIG_SCHEMA = {
|
||||
cv.GenerateID(CONF_ID): cv.declare_id(cg.EntityBase),
|
||||
cv.GenerateID(CONF_LD2410_ID): cv.use_id(LD2410Component),
|
||||
cv.Optional(CONF_VERSION): text_sensor.text_sensor_schema(
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC, icon=ICON_CHIP
|
||||
|
||||
@@ -5,6 +5,7 @@ from esphome.const import (
|
||||
CONF_HAS_MOVING_TARGET,
|
||||
CONF_HAS_STILL_TARGET,
|
||||
CONF_HAS_TARGET,
|
||||
CONF_ID,
|
||||
DEVICE_CLASS_MOTION,
|
||||
DEVICE_CLASS_OCCUPANCY,
|
||||
DEVICE_CLASS_RUNNING,
|
||||
@@ -20,6 +21,7 @@ DEPENDENCIES = ["ld2412"]
|
||||
CONF_DYNAMIC_BACKGROUND_CORRECTION_STATUS = "dynamic_background_correction_status"
|
||||
|
||||
CONFIG_SCHEMA = {
|
||||
cv.GenerateID(CONF_ID): cv.declare_id(cg.EntityBase),
|
||||
cv.GenerateID(CONF_LD2412_ID): cv.use_id(LD2412Component),
|
||||
cv.Optional(
|
||||
CONF_DYNAMIC_BACKGROUND_CORRECTION_STATUS
|
||||
|
||||
@@ -3,6 +3,7 @@ from esphome.components import button
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_FACTORY_RESET,
|
||||
CONF_ID,
|
||||
CONF_RESTART,
|
||||
DEVICE_CLASS_RESTART,
|
||||
ENTITY_CATEGORY_CONFIG,
|
||||
@@ -26,6 +27,7 @@ CONF_QUERY_PARAMS = "query_params"
|
||||
CONF_START_DYNAMIC_BACKGROUND_CORRECTION = "start_dynamic_background_correction"
|
||||
|
||||
CONFIG_SCHEMA = {
|
||||
cv.GenerateID(CONF_ID): cv.declare_id(cg.EntityBase),
|
||||
cv.GenerateID(CONF_LD2412_ID): cv.use_id(LD2412Component),
|
||||
cv.Optional(CONF_FACTORY_RESET): button.button_schema(
|
||||
FactoryResetButton,
|
||||
|
||||
@@ -31,6 +31,7 @@ TIMEOUT_GROUP = "timeout"
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(CONF_ID): cv.declare_id(cg.EntityBase),
|
||||
cv.GenerateID(CONF_LD2412_ID): cv.use_id(LD2412Component),
|
||||
cv.Optional(CONF_LIGHT_THRESHOLD): number.number_schema(
|
||||
LightThresholdNumber,
|
||||
|
||||
@@ -3,6 +3,7 @@ from esphome.components import select
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_BAUD_RATE,
|
||||
CONF_ID,
|
||||
ENTITY_CATEGORY_CONFIG,
|
||||
ICON_LIGHTBULB,
|
||||
ICON_RULER,
|
||||
@@ -22,6 +23,7 @@ CONF_OUT_PIN_LEVEL = "out_pin_level"
|
||||
|
||||
|
||||
CONFIG_SCHEMA = {
|
||||
cv.GenerateID(CONF_ID): cv.declare_id(cg.EntityBase),
|
||||
cv.GenerateID(CONF_LD2412_ID): cv.use_id(LD2412Component),
|
||||
cv.Optional(CONF_BAUD_RATE): select.select_schema(
|
||||
BaudRateSelect,
|
||||
|
||||
@@ -2,6 +2,7 @@ import esphome.codegen as cg
|
||||
from esphome.components import sensor
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
CONF_LIGHT,
|
||||
CONF_MOVING_DISTANCE,
|
||||
DEVICE_CLASS_DISTANCE,
|
||||
@@ -28,6 +29,7 @@ CONF_STILL_ENERGY = "still_energy"
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(CONF_ID): cv.declare_id(cg.EntityBase),
|
||||
cv.GenerateID(CONF_LD2412_ID): cv.use_id(LD2412Component),
|
||||
cv.Optional(CONF_DETECTION_DISTANCE): sensor.sensor_schema(
|
||||
device_class=DEVICE_CLASS_DISTANCE,
|
||||
|
||||
@@ -3,6 +3,7 @@ from esphome.components import switch
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_BLUETOOTH,
|
||||
CONF_ID,
|
||||
DEVICE_CLASS_SWITCH,
|
||||
ENTITY_CATEGORY_CONFIG,
|
||||
ICON_BLUETOOTH,
|
||||
@@ -17,6 +18,7 @@ EngineeringModeSwitch = LD2412_ns.class_("EngineeringModeSwitch", switch.Switch)
|
||||
CONF_ENGINEERING_MODE = "engineering_mode"
|
||||
|
||||
CONFIG_SCHEMA = {
|
||||
cv.GenerateID(CONF_ID): cv.declare_id(cg.EntityBase),
|
||||
cv.GenerateID(CONF_LD2412_ID): cv.use_id(LD2412Component),
|
||||
cv.Optional(CONF_BLUETOOTH): switch.switch_schema(
|
||||
BluetoothSwitch,
|
||||
|
||||
@@ -2,6 +2,7 @@ import esphome.codegen as cg
|
||||
from esphome.components import text_sensor
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
CONF_MAC_ADDRESS,
|
||||
CONF_VERSION,
|
||||
ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
@@ -14,6 +15,7 @@ from . import CONF_LD2412_ID, LD2412Component
|
||||
DEPENDENCIES = ["ld2412"]
|
||||
|
||||
CONFIG_SCHEMA = {
|
||||
cv.GenerateID(CONF_ID): cv.declare_id(cg.EntityBase),
|
||||
cv.GenerateID(CONF_LD2412_ID): cv.use_id(LD2412Component),
|
||||
cv.Optional(CONF_VERSION): text_sensor.text_sensor_schema(
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC, icon=ICON_CHIP
|
||||
|
||||
@@ -5,6 +5,7 @@ from esphome.const import (
|
||||
CONF_HAS_MOVING_TARGET,
|
||||
CONF_HAS_STILL_TARGET,
|
||||
CONF_HAS_TARGET,
|
||||
CONF_ID,
|
||||
DEVICE_CLASS_MOTION,
|
||||
DEVICE_CLASS_OCCUPANCY,
|
||||
)
|
||||
@@ -18,6 +19,7 @@ ICON_SHIELD_ACCOUNT = "mdi:shield-account"
|
||||
ICON_TARGET_ACCOUNT = "mdi:target-account"
|
||||
|
||||
CONFIG_SCHEMA = {
|
||||
cv.GenerateID(CONF_ID): cv.declare_id(cg.EntityBase),
|
||||
cv.GenerateID(CONF_LD2450_ID): cv.use_id(LD2450Component),
|
||||
cv.Optional(CONF_HAS_TARGET): binary_sensor.binary_sensor_schema(
|
||||
device_class=DEVICE_CLASS_OCCUPANCY,
|
||||
|
||||
@@ -3,6 +3,7 @@ from esphome.components import button
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_FACTORY_RESET,
|
||||
CONF_ID,
|
||||
CONF_RESTART,
|
||||
DEVICE_CLASS_RESTART,
|
||||
ENTITY_CATEGORY_CONFIG,
|
||||
@@ -17,6 +18,7 @@ FactoryResetButton = ld2450_ns.class_("FactoryResetButton", button.Button)
|
||||
RestartButton = ld2450_ns.class_("RestartButton", button.Button)
|
||||
|
||||
CONFIG_SCHEMA = {
|
||||
cv.GenerateID(CONF_ID): cv.declare_id(cg.EntityBase),
|
||||
cv.GenerateID(CONF_LD2450_ID): cv.use_id(LD2450Component),
|
||||
cv.Optional(CONF_FACTORY_RESET): button.button_schema(
|
||||
FactoryResetButton,
|
||||
|
||||
@@ -28,6 +28,7 @@ ZoneCoordinateNumber = ld2450_ns.class_("ZoneCoordinateNumber", number.Number)
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(CONF_ID): cv.declare_id(cg.EntityBase),
|
||||
cv.GenerateID(CONF_LD2450_ID): cv.use_id(LD2450Component),
|
||||
cv.Required(CONF_PRESENCE_TIMEOUT): number.number_schema(
|
||||
PresenceTimeoutNumber,
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import select
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_BAUD_RATE, ENTITY_CATEGORY_CONFIG, ICON_THERMOMETER
|
||||
from esphome.const import (
|
||||
CONF_BAUD_RATE,
|
||||
CONF_ID,
|
||||
ENTITY_CATEGORY_CONFIG,
|
||||
ICON_THERMOMETER,
|
||||
)
|
||||
|
||||
from .. import CONF_LD2450_ID, LD2450Component, ld2450_ns
|
||||
|
||||
@@ -11,6 +16,7 @@ BaudRateSelect = ld2450_ns.class_("BaudRateSelect", select.Select)
|
||||
ZoneTypeSelect = ld2450_ns.class_("ZoneTypeSelect", select.Select)
|
||||
|
||||
CONFIG_SCHEMA = {
|
||||
cv.GenerateID(CONF_ID): cv.declare_id(cg.EntityBase),
|
||||
cv.GenerateID(CONF_LD2450_ID): cv.use_id(LD2450Component),
|
||||
cv.Optional(CONF_BAUD_RATE): select.select_schema(
|
||||
BaudRateSelect,
|
||||
|
||||
@@ -4,6 +4,7 @@ import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_ANGLE,
|
||||
CONF_DISTANCE,
|
||||
CONF_ID,
|
||||
CONF_RESOLUTION,
|
||||
CONF_SPEED,
|
||||
CONF_X,
|
||||
@@ -40,6 +41,7 @@ UNIT_MILLIMETER_PER_SECOND = "mm/s"
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(CONF_ID): cv.declare_id(cg.EntityBase),
|
||||
cv.GenerateID(CONF_LD2450_ID): cv.use_id(LD2450Component),
|
||||
cv.Optional(CONF_TARGET_COUNT): sensor.sensor_schema(
|
||||
accuracy_decimals=0,
|
||||
|
||||
@@ -3,6 +3,7 @@ from esphome.components import switch
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_BLUETOOTH,
|
||||
CONF_ID,
|
||||
DEVICE_CLASS_SWITCH,
|
||||
ENTITY_CATEGORY_CONFIG,
|
||||
ICON_BLUETOOTH,
|
||||
@@ -17,6 +18,7 @@ MultiTargetSwitch = ld2450_ns.class_("MultiTargetSwitch", switch.Switch)
|
||||
CONF_MULTI_TARGET = "multi_target"
|
||||
|
||||
CONFIG_SCHEMA = {
|
||||
cv.GenerateID(CONF_ID): cv.declare_id(cg.EntityBase),
|
||||
cv.GenerateID(CONF_LD2450_ID): cv.use_id(LD2450Component),
|
||||
cv.Optional(CONF_BLUETOOTH): switch.switch_schema(
|
||||
BluetoothSwitch,
|
||||
|
||||
@@ -3,6 +3,7 @@ from esphome.components import text_sensor
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_DIRECTION,
|
||||
CONF_ID,
|
||||
CONF_MAC_ADDRESS,
|
||||
CONF_VERSION,
|
||||
ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
@@ -20,6 +21,7 @@ MAX_TARGETS = 3
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(CONF_ID): cv.declare_id(cg.EntityBase),
|
||||
cv.GenerateID(CONF_LD2450_ID): cv.use_id(LD2450Component),
|
||||
cv.Optional(CONF_VERSION): text_sensor.text_sensor_schema(
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
|
||||
@@ -35,6 +35,7 @@ from .const import (
|
||||
FAMILY_BK7231N,
|
||||
FAMILY_COMPONENT,
|
||||
FAMILY_FRIENDLY,
|
||||
FAMILY_RTL8710B,
|
||||
KEY_BOARD,
|
||||
KEY_COMPONENT,
|
||||
KEY_COMPONENT_DATA,
|
||||
@@ -278,11 +279,23 @@ async def component_to_code(config):
|
||||
cg.add_build_flag(f"-DUSE_LIBRETINY_VARIANT_{config[CONF_FAMILY]}")
|
||||
cg.add_define("ESPHOME_BOARD", config[CONF_BOARD])
|
||||
cg.add_define("ESPHOME_VARIANT", FAMILY_FRIENDLY[config[CONF_FAMILY]])
|
||||
# LibreTiny uses MULTI_NO_ATOMICS because platforms like BK7231N (ARM968E-S) lack
|
||||
# exclusive load/store (no LDREX/STREX). std::atomic RMW operations require libatomic,
|
||||
# which is not linked to save flash (4-8KB). Even if linked, libatomic would use locks
|
||||
# (ATOMIC_INT_LOCK_FREE=1), so explicit FreeRTOS mutexes are simpler and equivalent.
|
||||
cg.add_define(ThreadModel.MULTI_NO_ATOMICS)
|
||||
# Set threading model based on chip architecture
|
||||
component: LibreTinyComponent = CORE.data[KEY_LIBRETINY][KEY_COMPONENT_DATA]
|
||||
if component.supports_atomics:
|
||||
# RTL87xx (Cortex-M4) and LN882x (Cortex-M4F) have LDREX/STREX
|
||||
cg.add_define(ThreadModel.MULTI_ATOMICS)
|
||||
else:
|
||||
# BK72xx uses ARM968E-S (ARMv5TE) which lacks LDREX/STREX.
|
||||
# std::atomic RMW operations would require libatomic (not linked to save
|
||||
# 4-8KB flash). Even if linked, it would use locks, so explicit FreeRTOS
|
||||
# mutexes are simpler and equivalent.
|
||||
cg.add_define(ThreadModel.MULTI_NO_ATOMICS)
|
||||
|
||||
# RTL8710B needs FreeRTOS 8.2.3+ for xTaskNotifyGive/ulTaskNotifyTake
|
||||
# required by AsyncTCP 3.4.3+ (https://github.com/esphome/esphome/issues/10220)
|
||||
# RTL8720C (ambz2) requires FreeRTOS 10.x so this only applies to RTL8710B
|
||||
if config[CONF_FAMILY] == FAMILY_RTL8710B:
|
||||
cg.add_platformio_option("custom_versions.freertos", "8.2.3")
|
||||
|
||||
# force using arduino framework
|
||||
cg.add_platformio_option("framework", "arduino")
|
||||
|
||||
@@ -11,6 +11,7 @@ class LibreTinyComponent:
|
||||
board_pins: dict[str, dict[str, int]]
|
||||
pin_validation: Callable[[int], int]
|
||||
usage_validation: Callable[[dict], dict]
|
||||
supports_atomics: bool = False # True for Cortex-M4(F) with LDREX/STREX
|
||||
|
||||
|
||||
CONF_LIBRETINY = "libretiny"
|
||||
|
||||
@@ -11,13 +11,27 @@ from black import FileMode, format_str
|
||||
from ltchiptool import Board, Family
|
||||
from ltchiptool.util.lvm import LVM
|
||||
|
||||
BASE_CODE_INIT = """
|
||||
# This file was auto-generated by libretiny/generate_components.py
|
||||
# Do not modify its contents.
|
||||
# For custom pin validators, put validate_pin() or validate_usage()
|
||||
# in gpio.py file in this directory.
|
||||
# For changing schema/pin schema, put COMPONENT_SCHEMA or COMPONENT_PIN_SCHEMA
|
||||
# in schema.py file in this directory.
|
||||
BASE_CODE_INIT = '''
|
||||
"""
|
||||
██╗ ██╗ █████╗ ██████╗ ███╗ ██╗██╗███╗ ██╗ ██████╗
|
||||
██║ ██║██╔══██╗██╔══██╗████╗ ██║██║████╗ ██║██╔════╝
|
||||
██║ █╗ ██║███████║██████╔╝██╔██╗ ██║██║██╔██╗ ██║██║ ███╗
|
||||
██║███╗██║██╔══██║██╔══██╗██║╚██╗██║██║██║╚██╗██║██║ ██║
|
||||
╚███╔███╔╝██║ ██║██║ ██║██║ ╚████║██║██║ ╚████║╚██████╔╝
|
||||
╚══╝╚══╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═══╝╚═╝╚═╝ ╚═══╝ ╚═════╝
|
||||
|
||||
AUTO-GENERATED FILE - DO NOT EDIT!
|
||||
|
||||
This file was auto-generated by libretiny/generate_components.py.
|
||||
Any manual changes WILL BE LOST on regeneration.
|
||||
|
||||
To customize this component:
|
||||
- Pin validators: Create gpio.py with validate_pin() or validate_usage()
|
||||
- Schema extensions: Create schema.py with COMPONENT_SCHEMA or COMPONENT_PIN_SCHEMA
|
||||
|
||||
Platform-specific code should be added to the main libretiny component
|
||||
(__init__.py in esphome/components/libretiny/) rather than here.
|
||||
"""
|
||||
|
||||
from esphome import pins
|
||||
from esphome.components import libretiny
|
||||
@@ -31,8 +45,9 @@ from esphome.core import CORE
|
||||
|
||||
{IMPORTS}
|
||||
|
||||
CODEOWNERS = ["@kuba2k2"]
|
||||
CODEOWNERS = {CODEOWNERS}
|
||||
AUTO_LOAD = ["libretiny"]
|
||||
IS_TARGET_PLATFORM = True
|
||||
|
||||
COMPONENT_DATA = LibreTinyComponent(
|
||||
name=COMPONENT_{COMPONENT},
|
||||
@@ -40,6 +55,7 @@ COMPONENT_DATA = LibreTinyComponent(
|
||||
board_pins={COMPONENT}_BOARD_PINS,
|
||||
pin_validation={PIN_VALIDATION},
|
||||
usage_validation={USAGE_VALIDATION},
|
||||
supports_atomics={SUPPORTS_ATOMICS},
|
||||
)
|
||||
|
||||
|
||||
@@ -63,11 +79,22 @@ async def to_code(config):
|
||||
@pins.PIN_SCHEMA_REGISTRY.register("{COMPONENT_LOWER}", PIN_SCHEMA)
|
||||
async def pin_to_code(config):
|
||||
return await libretiny.gpio.component_pin_to_code(config)
|
||||
"""
|
||||
'''
|
||||
|
||||
BASE_CODE_BOARDS = """
|
||||
# This file was auto-generated by libretiny/generate_components.py
|
||||
# Do not modify its contents.
|
||||
BASE_CODE_BOARDS = '''
|
||||
"""
|
||||
██╗ ██╗ █████╗ ██████╗ ███╗ ██╗██╗███╗ ██╗ ██████╗
|
||||
██║ ██║██╔══██╗██╔══██╗████╗ ██║██║████╗ ██║██╔════╝
|
||||
██║ █╗ ██║███████║██████╔╝██╔██╗ ██║██║██╔██╗ ██║██║ ███╗
|
||||
██║███╗██║██╔══██║██╔══██╗██║╚██╗██║██║██║╚██╗██║██║ ██║
|
||||
╚███╔███╔╝██║ ██║██║ ██║██║ ╚████║██║██║ ╚████║╚██████╔╝
|
||||
╚══╝╚══╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═══╝╚═╝╚═╝ ╚═══╝ ╚═════╝
|
||||
|
||||
AUTO-GENERATED FILE - DO NOT EDIT!
|
||||
|
||||
This file was auto-generated by libretiny/generate_components.py.
|
||||
Any manual changes WILL BE LOST on regeneration.
|
||||
"""
|
||||
|
||||
from esphome.components.libretiny.const import {FAMILIES}
|
||||
|
||||
@@ -76,7 +103,7 @@ from esphome.components.libretiny.const import {FAMILIES}
|
||||
{COMPONENT}_BOARD_PINS = {PINS_JSON}
|
||||
|
||||
BOARDS = {COMPONENT}_BOARDS
|
||||
"""
|
||||
'''
|
||||
|
||||
# variable names in component extension code
|
||||
VAR_SCHEMA = "COMPONENT_SCHEMA"
|
||||
@@ -97,6 +124,19 @@ COMPONENT_MAP = {
|
||||
"ln882x": "lightning-ln882x",
|
||||
}
|
||||
|
||||
# Components with Cortex-M4(F) have LDREX/STREX for native atomic support.
|
||||
# BK72xx uses ARM968E-S (ARMv5TE) which lacks these instructions.
|
||||
COMPONENT_SUPPORTS_ATOMICS = {
|
||||
"rtl87xx": True, # Cortex-M4
|
||||
"ln882x": True, # Cortex-M4F
|
||||
"bk72xx": False, # ARM968E-S
|
||||
}
|
||||
|
||||
# CODEOWNERS for each component. If not specified, defaults to @kuba2k2.
|
||||
COMPONENT_CODEOWNERS = {
|
||||
"ln882x": ["@lamauny"],
|
||||
}
|
||||
|
||||
|
||||
def subst(code: str, key: str, value: str) -> str:
|
||||
return code.replace(f"{{{key}}}", value)
|
||||
@@ -140,6 +180,7 @@ def write_component_code(
|
||||
"boards": {"{COMPONENT}_BOARDS", "{COMPONENT}_BOARD_PINS"},
|
||||
}
|
||||
# substitution values
|
||||
codeowners = COMPONENT_CODEOWNERS.get(component, ["@kuba2k2"])
|
||||
values = dict(
|
||||
COMPONENT=component.upper(),
|
||||
COMPONENT_LOWER=component.lower(),
|
||||
@@ -147,6 +188,8 @@ def write_component_code(
|
||||
PIN_SCHEMA=PIN_SCHEMA_BASE,
|
||||
PIN_VALIDATION="None",
|
||||
USAGE_VALIDATION="None",
|
||||
SUPPORTS_ATOMICS=str(COMPONENT_SUPPORTS_ATOMICS.get(component, False)),
|
||||
CODEOWNERS=repr(codeowners),
|
||||
)
|
||||
|
||||
# parse gpio.py file to find custom validators
|
||||
|
||||
@@ -1,9 +1,23 @@
|
||||
# This file was auto-generated by libretiny/generate_components.py
|
||||
# Do not modify its contents.
|
||||
# For custom pin validators, put validate_pin() or validate_usage()
|
||||
# in gpio.py file in this directory.
|
||||
# For changing schema/pin schema, put COMPONENT_SCHEMA or COMPONENT_PIN_SCHEMA
|
||||
# in schema.py file in this directory.
|
||||
"""
|
||||
██╗ ██╗ █████╗ ██████╗ ███╗ ██╗██╗███╗ ██╗ ██████╗
|
||||
██║ ██║██╔══██╗██╔══██╗████╗ ██║██║████╗ ██║██╔════╝
|
||||
██║ █╗ ██║███████║██████╔╝██╔██╗ ██║██║██╔██╗ ██║██║ ███╗
|
||||
██║███╗██║██╔══██║██╔══██╗██║╚██╗██║██║██║╚██╗██║██║ ██║
|
||||
╚███╔███╔╝██║ ██║██║ ██║██║ ╚████║██║██║ ╚████║╚██████╔╝
|
||||
╚══╝╚══╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═══╝╚═╝╚═╝ ╚═══╝ ╚═════╝
|
||||
|
||||
AUTO-GENERATED FILE - DO NOT EDIT!
|
||||
|
||||
This file was auto-generated by libretiny/generate_components.py.
|
||||
Any manual changes WILL BE LOST on regeneration.
|
||||
|
||||
To customize this component:
|
||||
- Pin validators: Create gpio.py with validate_pin() or validate_usage()
|
||||
- Schema extensions: Create schema.py with COMPONENT_SCHEMA or COMPONENT_PIN_SCHEMA
|
||||
|
||||
Platform-specific code should be added to the main libretiny component
|
||||
(__init__.py in esphome/components/libretiny/) rather than here.
|
||||
"""
|
||||
|
||||
from esphome import pins
|
||||
from esphome.components import libretiny
|
||||
@@ -27,6 +41,7 @@ COMPONENT_DATA = LibreTinyComponent(
|
||||
board_pins=LN882X_BOARD_PINS,
|
||||
pin_validation=None,
|
||||
usage_validation=None,
|
||||
supports_atomics=True,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -1,9 +1,28 @@
|
||||
# This file was auto-generated by libretiny/generate_components.py
|
||||
# Do not modify its contents.
|
||||
"""
|
||||
██╗ ██╗ █████╗ ██████╗ ███╗ ██╗██╗███╗ ██╗ ██████╗
|
||||
██║ ██║██╔══██╗██╔══██╗████╗ ██║██║████╗ ██║██╔════╝
|
||||
██║ █╗ ██║███████║██████╔╝██╔██╗ ██║██║██╔██╗ ██║██║ ███╗
|
||||
██║███╗██║██╔══██║██╔══██╗██║╚██╗██║██║██║╚██╗██║██║ ██║
|
||||
╚███╔███╔╝██║ ██║██║ ██║██║ ╚████║██║██║ ╚████║╚██████╔╝
|
||||
╚══╝╚══╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═══╝╚═╝╚═╝ ╚═══╝ ╚═════╝
|
||||
|
||||
AUTO-GENERATED FILE - DO NOT EDIT!
|
||||
|
||||
This file was auto-generated by libretiny/generate_components.py.
|
||||
Any manual changes WILL BE LOST on regeneration.
|
||||
"""
|
||||
|
||||
from esphome.components.libretiny.const import FAMILY_LN882H
|
||||
|
||||
LN882X_BOARDS = {
|
||||
"generic-ln882hki": {
|
||||
"name": "Generic - LN882HKI",
|
||||
"family": FAMILY_LN882H,
|
||||
},
|
||||
"wb02a": {
|
||||
"name": "WB02A Wi-Fi/BLE Module",
|
||||
"family": FAMILY_LN882H,
|
||||
},
|
||||
"wl2s": {
|
||||
"name": "WL2S Wi-Fi/BLE Module",
|
||||
"family": FAMILY_LN882H,
|
||||
@@ -12,13 +31,195 @@ LN882X_BOARDS = {
|
||||
"name": "LN-02 Wi-Fi/BLE Module",
|
||||
"family": FAMILY_LN882H,
|
||||
},
|
||||
"generic-ln882hki": {
|
||||
"name": "Generic - LN882HKI",
|
||||
"family": FAMILY_LN882H,
|
||||
},
|
||||
}
|
||||
|
||||
LN882X_BOARD_PINS = {
|
||||
"generic-ln882hki": {
|
||||
"WIRE0_SCL_0": 0,
|
||||
"WIRE0_SCL_1": 1,
|
||||
"WIRE0_SCL_2": 2,
|
||||
"WIRE0_SCL_3": 3,
|
||||
"WIRE0_SCL_4": 4,
|
||||
"WIRE0_SCL_5": 5,
|
||||
"WIRE0_SCL_6": 6,
|
||||
"WIRE0_SCL_7": 7,
|
||||
"WIRE0_SCL_8": 8,
|
||||
"WIRE0_SCL_9": 9,
|
||||
"WIRE0_SCL_10": 10,
|
||||
"WIRE0_SCL_11": 11,
|
||||
"WIRE0_SCL_12": 12,
|
||||
"WIRE0_SCL_13": 19,
|
||||
"WIRE0_SCL_14": 20,
|
||||
"WIRE0_SCL_15": 21,
|
||||
"WIRE0_SCL_16": 22,
|
||||
"WIRE0_SCL_17": 23,
|
||||
"WIRE0_SCL_18": 24,
|
||||
"WIRE0_SCL_19": 25,
|
||||
"WIRE0_SDA_0": 0,
|
||||
"WIRE0_SDA_1": 1,
|
||||
"WIRE0_SDA_2": 2,
|
||||
"WIRE0_SDA_3": 3,
|
||||
"WIRE0_SDA_4": 4,
|
||||
"WIRE0_SDA_5": 5,
|
||||
"WIRE0_SDA_6": 6,
|
||||
"WIRE0_SDA_7": 7,
|
||||
"WIRE0_SDA_8": 8,
|
||||
"WIRE0_SDA_9": 9,
|
||||
"WIRE0_SDA_10": 10,
|
||||
"WIRE0_SDA_11": 11,
|
||||
"WIRE0_SDA_12": 12,
|
||||
"WIRE0_SDA_13": 19,
|
||||
"WIRE0_SDA_14": 20,
|
||||
"WIRE0_SDA_15": 21,
|
||||
"WIRE0_SDA_16": 22,
|
||||
"WIRE0_SDA_17": 23,
|
||||
"WIRE0_SDA_18": 24,
|
||||
"WIRE0_SDA_19": 25,
|
||||
"SERIAL0_RX": 3,
|
||||
"SERIAL0_TX": 2,
|
||||
"SERIAL1_RX": 24,
|
||||
"SERIAL1_TX": 25,
|
||||
"ADC2": 0,
|
||||
"ADC3": 1,
|
||||
"ADC4": 4,
|
||||
"ADC5": 19,
|
||||
"ADC6": 20,
|
||||
"ADC7": 21,
|
||||
"PA00": 0,
|
||||
"PA0": 0,
|
||||
"PA01": 1,
|
||||
"PA1": 1,
|
||||
"PA02": 2,
|
||||
"PA2": 2,
|
||||
"PA03": 3,
|
||||
"PA3": 3,
|
||||
"PA04": 4,
|
||||
"PA4": 4,
|
||||
"PA05": 5,
|
||||
"PA5": 5,
|
||||
"PA06": 6,
|
||||
"PA6": 6,
|
||||
"PA07": 7,
|
||||
"PA7": 7,
|
||||
"PA08": 8,
|
||||
"PA8": 8,
|
||||
"PA09": 9,
|
||||
"PA9": 9,
|
||||
"PA10": 10,
|
||||
"PA11": 11,
|
||||
"PA12": 12,
|
||||
"PB03": 19,
|
||||
"PB3": 19,
|
||||
"PB04": 20,
|
||||
"PB4": 20,
|
||||
"PB05": 21,
|
||||
"PB5": 21,
|
||||
"PB06": 22,
|
||||
"PB6": 22,
|
||||
"PB07": 23,
|
||||
"PB7": 23,
|
||||
"PB08": 24,
|
||||
"PB8": 24,
|
||||
"PB09": 25,
|
||||
"PB9": 25,
|
||||
"RX0": 3,
|
||||
"RX1": 24,
|
||||
"TX0": 2,
|
||||
"TX1": 25,
|
||||
"D0": 0,
|
||||
"D1": 1,
|
||||
"D2": 2,
|
||||
"D3": 3,
|
||||
"D4": 4,
|
||||
"D5": 5,
|
||||
"D6": 6,
|
||||
"D7": 7,
|
||||
"D8": 8,
|
||||
"D9": 9,
|
||||
"D10": 10,
|
||||
"D11": 11,
|
||||
"D12": 12,
|
||||
"D13": 19,
|
||||
"D14": 20,
|
||||
"D15": 21,
|
||||
"D16": 22,
|
||||
"D17": 23,
|
||||
"D18": 24,
|
||||
"D19": 25,
|
||||
"A2": 0,
|
||||
"A3": 1,
|
||||
"A4": 4,
|
||||
"A5": 19,
|
||||
"A6": 20,
|
||||
"A7": 21,
|
||||
},
|
||||
"wb02a": {
|
||||
"WIRE0_SCL_0": 7,
|
||||
"WIRE0_SCL_1": 5,
|
||||
"WIRE0_SCL_2": 3,
|
||||
"WIRE0_SCL_3": 10,
|
||||
"WIRE0_SCL_4": 2,
|
||||
"WIRE0_SCL_5": 1,
|
||||
"WIRE0_SCL_6": 4,
|
||||
"WIRE0_SCL_7": 5,
|
||||
"WIRE0_SCL_8": 9,
|
||||
"WIRE0_SCL_9": 24,
|
||||
"WIRE0_SCL_10": 25,
|
||||
"WIRE0_SDA_0": 7,
|
||||
"WIRE0_SDA_1": 5,
|
||||
"WIRE0_SDA_2": 3,
|
||||
"WIRE0_SDA_3": 10,
|
||||
"WIRE0_SDA_4": 2,
|
||||
"WIRE0_SDA_5": 1,
|
||||
"WIRE0_SDA_6": 4,
|
||||
"WIRE0_SDA_7": 5,
|
||||
"WIRE0_SDA_8": 9,
|
||||
"WIRE0_SDA_9": 24,
|
||||
"WIRE0_SDA_10": 25,
|
||||
"SERIAL0_RX": 3,
|
||||
"SERIAL0_TX": 2,
|
||||
"SERIAL1_RX": 24,
|
||||
"SERIAL1_TX": 25,
|
||||
"ADC3": 1,
|
||||
"ADC4": 4,
|
||||
"PA01": 1,
|
||||
"PA1": 1,
|
||||
"PA02": 2,
|
||||
"PA2": 2,
|
||||
"PA03": 3,
|
||||
"PA3": 3,
|
||||
"PA04": 4,
|
||||
"PA4": 4,
|
||||
"PA05": 5,
|
||||
"PA5": 5,
|
||||
"PA07": 7,
|
||||
"PA7": 7,
|
||||
"PA09": 9,
|
||||
"PA9": 9,
|
||||
"PA10": 10,
|
||||
"PB08": 24,
|
||||
"PB8": 24,
|
||||
"PB09": 25,
|
||||
"PB9": 25,
|
||||
"RX0": 3,
|
||||
"RX1": 24,
|
||||
"SCL0": 25,
|
||||
"SDA0": 25,
|
||||
"TX0": 2,
|
||||
"TX1": 25,
|
||||
"D0": 7,
|
||||
"D1": 5,
|
||||
"D2": 3,
|
||||
"D3": 10,
|
||||
"D4": 2,
|
||||
"D5": 1,
|
||||
"D6": 4,
|
||||
"D7": 9,
|
||||
"D8": 24,
|
||||
"D9": 25,
|
||||
"A0": 1,
|
||||
"A1": 4,
|
||||
},
|
||||
"wl2s": {
|
||||
"WIRE0_SCL_0": 7,
|
||||
"WIRE0_SCL_1": 12,
|
||||
@@ -161,125 +362,6 @@ LN882X_BOARD_PINS = {
|
||||
"A1": 1,
|
||||
"A2": 0,
|
||||
},
|
||||
"generic-ln882hki": {
|
||||
"WIRE0_SCL_0": 0,
|
||||
"WIRE0_SCL_1": 1,
|
||||
"WIRE0_SCL_2": 2,
|
||||
"WIRE0_SCL_3": 3,
|
||||
"WIRE0_SCL_4": 4,
|
||||
"WIRE0_SCL_5": 5,
|
||||
"WIRE0_SCL_6": 6,
|
||||
"WIRE0_SCL_7": 7,
|
||||
"WIRE0_SCL_8": 8,
|
||||
"WIRE0_SCL_9": 9,
|
||||
"WIRE0_SCL_10": 10,
|
||||
"WIRE0_SCL_11": 11,
|
||||
"WIRE0_SCL_12": 12,
|
||||
"WIRE0_SCL_13": 19,
|
||||
"WIRE0_SCL_14": 20,
|
||||
"WIRE0_SCL_15": 21,
|
||||
"WIRE0_SCL_16": 22,
|
||||
"WIRE0_SCL_17": 23,
|
||||
"WIRE0_SCL_18": 24,
|
||||
"WIRE0_SCL_19": 25,
|
||||
"WIRE0_SDA_0": 0,
|
||||
"WIRE0_SDA_1": 1,
|
||||
"WIRE0_SDA_2": 2,
|
||||
"WIRE0_SDA_3": 3,
|
||||
"WIRE0_SDA_4": 4,
|
||||
"WIRE0_SDA_5": 5,
|
||||
"WIRE0_SDA_6": 6,
|
||||
"WIRE0_SDA_7": 7,
|
||||
"WIRE0_SDA_8": 8,
|
||||
"WIRE0_SDA_9": 9,
|
||||
"WIRE0_SDA_10": 10,
|
||||
"WIRE0_SDA_11": 11,
|
||||
"WIRE0_SDA_12": 12,
|
||||
"WIRE0_SDA_13": 19,
|
||||
"WIRE0_SDA_14": 20,
|
||||
"WIRE0_SDA_15": 21,
|
||||
"WIRE0_SDA_16": 22,
|
||||
"WIRE0_SDA_17": 23,
|
||||
"WIRE0_SDA_18": 24,
|
||||
"WIRE0_SDA_19": 25,
|
||||
"SERIAL0_RX": 3,
|
||||
"SERIAL0_TX": 2,
|
||||
"SERIAL1_RX": 24,
|
||||
"SERIAL1_TX": 25,
|
||||
"ADC2": 0,
|
||||
"ADC3": 1,
|
||||
"ADC4": 4,
|
||||
"ADC5": 19,
|
||||
"ADC6": 20,
|
||||
"ADC7": 21,
|
||||
"PA00": 0,
|
||||
"PA0": 0,
|
||||
"PA01": 1,
|
||||
"PA1": 1,
|
||||
"PA02": 2,
|
||||
"PA2": 2,
|
||||
"PA03": 3,
|
||||
"PA3": 3,
|
||||
"PA04": 4,
|
||||
"PA4": 4,
|
||||
"PA05": 5,
|
||||
"PA5": 5,
|
||||
"PA06": 6,
|
||||
"PA6": 6,
|
||||
"PA07": 7,
|
||||
"PA7": 7,
|
||||
"PA08": 8,
|
||||
"PA8": 8,
|
||||
"PA09": 9,
|
||||
"PA9": 9,
|
||||
"PA10": 10,
|
||||
"PA11": 11,
|
||||
"PA12": 12,
|
||||
"PB03": 19,
|
||||
"PB3": 19,
|
||||
"PB04": 20,
|
||||
"PB4": 20,
|
||||
"PB05": 21,
|
||||
"PB5": 21,
|
||||
"PB06": 22,
|
||||
"PB6": 22,
|
||||
"PB07": 23,
|
||||
"PB7": 23,
|
||||
"PB08": 24,
|
||||
"PB8": 24,
|
||||
"PB09": 25,
|
||||
"PB9": 25,
|
||||
"RX0": 3,
|
||||
"RX1": 24,
|
||||
"TX0": 2,
|
||||
"TX1": 25,
|
||||
"D0": 0,
|
||||
"D1": 1,
|
||||
"D2": 2,
|
||||
"D3": 3,
|
||||
"D4": 4,
|
||||
"D5": 5,
|
||||
"D6": 6,
|
||||
"D7": 7,
|
||||
"D8": 8,
|
||||
"D9": 9,
|
||||
"D10": 10,
|
||||
"D11": 11,
|
||||
"D12": 12,
|
||||
"D13": 19,
|
||||
"D14": 20,
|
||||
"D15": 21,
|
||||
"D16": 22,
|
||||
"D17": 23,
|
||||
"D18": 24,
|
||||
"D19": 25,
|
||||
"A2": 0,
|
||||
"A3": 1,
|
||||
"A4": 4,
|
||||
"A5": 19,
|
||||
"A6": 20,
|
||||
"A7": 21,
|
||||
},
|
||||
}
|
||||
|
||||
BOARDS = LN882X_BOARDS
|
||||
|
||||
@@ -421,6 +421,7 @@ async def to_code(config):
|
||||
await cg.register_component(log, config)
|
||||
|
||||
for conf in config.get(CONF_ON_MESSAGE, []):
|
||||
request_log_listener() # Each on_message trigger needs a listener slot
|
||||
trigger = cg.new_Pvariable(
|
||||
conf[CONF_TRIGGER_ID], log, LOG_LEVEL_SEVERITY.index(conf[CONF_LEVEL])
|
||||
)
|
||||
@@ -546,6 +547,7 @@ FILTER_SOURCE_FILES = filter_source_files_from_platform(
|
||||
# Keys for CORE.data storage
|
||||
DOMAIN = "logger"
|
||||
KEY_LEVEL_LISTENERS = "level_listeners"
|
||||
KEY_LOG_LISTENERS = "log_listeners"
|
||||
|
||||
|
||||
def request_logger_level_listeners() -> None:
|
||||
@@ -558,8 +560,26 @@ def request_logger_level_listeners() -> None:
|
||||
CORE.data.setdefault(DOMAIN, {})[KEY_LEVEL_LISTENERS] = True
|
||||
|
||||
|
||||
def request_log_listener() -> None:
|
||||
"""Request a log listener slot.
|
||||
|
||||
Components that need to receive log messages should call this function
|
||||
during their code generation. This increments the listener count used
|
||||
to size the StaticVector.
|
||||
"""
|
||||
data = CORE.data.setdefault(DOMAIN, {})
|
||||
data[KEY_LOG_LISTENERS] = data.get(KEY_LOG_LISTENERS, 0) + 1
|
||||
|
||||
|
||||
@coroutine_with_priority(CoroPriority.FINAL)
|
||||
async def final_step():
|
||||
"""Final code generation step to configure optional logger features."""
|
||||
if CORE.data.get(DOMAIN, {}).get(KEY_LEVEL_LISTENERS, False):
|
||||
domain_data = CORE.data.get(DOMAIN, {})
|
||||
if domain_data.get(KEY_LEVEL_LISTENERS, False):
|
||||
cg.add_define("USE_LOGGER_LEVEL_LISTENERS")
|
||||
|
||||
# Only generate log listener code if any component needs it
|
||||
log_listener_count = domain_data.get(KEY_LOG_LISTENERS, 0)
|
||||
if log_listener_count > 0:
|
||||
cg.add_define("USE_LOG_LISTENERS")
|
||||
cg.add_define("ESPHOME_LOG_MAX_LISTENERS", log_listener_count)
|
||||
|
||||
@@ -23,30 +23,57 @@ static const char *const TAG = "logger";
|
||||
// - Messages are serialized through main loop for proper console output
|
||||
// - Fallback to emergency console logging only if ring buffer is full
|
||||
// - WITHOUT task log buffer: Only emergency console output, no callbacks
|
||||
//
|
||||
// Optimized for the common case: 99.9% of logs come from the main thread
|
||||
void HOT Logger::log_vprintf_(uint8_t level, const char *tag, int line, const char *format, va_list args) { // NOLINT
|
||||
if (level > this->level_for(tag))
|
||||
return;
|
||||
|
||||
#if defined(USE_ESP32) || defined(USE_LIBRETINY)
|
||||
// Get task handle once - used for both main task check and passing to non-main thread handler
|
||||
TaskHandle_t current_task = xTaskGetCurrentTaskHandle();
|
||||
bool is_main_task = (current_task == main_task_);
|
||||
const bool is_main_task = (current_task == this->main_task_);
|
||||
#else // USE_HOST
|
||||
pthread_t current_thread = pthread_self();
|
||||
bool is_main_task = pthread_equal(current_thread, main_thread_);
|
||||
const bool is_main_task = pthread_equal(pthread_self(), this->main_thread_);
|
||||
#endif
|
||||
|
||||
// Check and set recursion guard - uses pthread TLS for per-thread/task state
|
||||
if (this->check_and_set_task_log_recursion_(is_main_task)) {
|
||||
return; // Recursion detected
|
||||
}
|
||||
|
||||
// Main thread/task uses the shared buffer for efficiency
|
||||
if (is_main_task) {
|
||||
// Fast path: main thread, no recursion (99.9% of all logs)
|
||||
if (is_main_task && !this->main_task_recursion_guard_) [[likely]] {
|
||||
RecursionGuard guard(this->main_task_recursion_guard_);
|
||||
// Format and send to both console and callbacks
|
||||
this->log_message_to_buffer_and_send_(level, tag, line, format, args);
|
||||
this->reset_task_log_recursion_(is_main_task);
|
||||
return;
|
||||
}
|
||||
|
||||
// Main task with recursion - silently drop to prevent infinite loop
|
||||
if (is_main_task) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Non-main thread handling (~0.1% of logs)
|
||||
#if defined(USE_ESP32) || defined(USE_LIBRETINY)
|
||||
this->log_vprintf_non_main_thread_(level, tag, line, format, args, current_task);
|
||||
#else // USE_HOST
|
||||
this->log_vprintf_non_main_thread_(level, tag, line, format, args);
|
||||
#endif
|
||||
}
|
||||
|
||||
// Handles non-main thread logging only
|
||||
// Kept separate from hot path to improve instruction cache performance
|
||||
#if defined(USE_ESP32) || defined(USE_LIBRETINY)
|
||||
void Logger::log_vprintf_non_main_thread_(uint8_t level, const char *tag, int line, const char *format, va_list args,
|
||||
TaskHandle_t current_task) {
|
||||
#else // USE_HOST
|
||||
void Logger::log_vprintf_non_main_thread_(uint8_t level, const char *tag, int line, const char *format, va_list args) {
|
||||
#endif
|
||||
// Check if already in recursion for this non-main thread/task
|
||||
if (this->is_non_main_task_recursive_()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// RAII guard - automatically resets on any return path
|
||||
auto guard = this->make_non_main_task_guard_();
|
||||
|
||||
bool message_sent = false;
|
||||
#ifdef USE_ESPHOME_TASK_LOG_BUFFER
|
||||
// For non-main threads/tasks, queue the message for callbacks
|
||||
@@ -85,21 +112,17 @@ void HOT Logger::log_vprintf_(uint8_t level, const char *tag, int line, const ch
|
||||
this->write_msg_(console_buffer, buffer_at);
|
||||
}
|
||||
|
||||
// Reset the recursion guard for this thread/task
|
||||
this->reset_task_log_recursion_(is_main_task);
|
||||
// RAII guard automatically resets on return
|
||||
}
|
||||
#else
|
||||
// Implementation for all other platforms
|
||||
// Implementation for all other platforms (single-task, no threading)
|
||||
void HOT Logger::log_vprintf_(uint8_t level, const char *tag, int line, const char *format, va_list args) { // NOLINT
|
||||
if (level > this->level_for(tag) || global_recursion_guard_)
|
||||
return;
|
||||
|
||||
global_recursion_guard_ = true;
|
||||
|
||||
RecursionGuard guard(global_recursion_guard_);
|
||||
// Format and send to both console and callbacks
|
||||
this->log_message_to_buffer_and_send_(level, tag, line, format, args);
|
||||
|
||||
global_recursion_guard_ = false;
|
||||
}
|
||||
#endif // USE_ESP32 / USE_HOST / USE_LIBRETINY
|
||||
|
||||
@@ -130,7 +153,7 @@ void Logger::log_vprintf_(uint8_t level, const char *tag, int line, const __Flas
|
||||
if (level > this->level_for(tag) || global_recursion_guard_)
|
||||
return;
|
||||
|
||||
global_recursion_guard_ = true;
|
||||
RecursionGuard guard(global_recursion_guard_);
|
||||
this->tx_buffer_at_ = 0;
|
||||
|
||||
// Copy format string from progmem
|
||||
@@ -140,9 +163,8 @@ void Logger::log_vprintf_(uint8_t level, const char *tag, int line, const __Flas
|
||||
this->tx_buffer_[this->tx_buffer_at_++] = ch = (char) progmem_read_byte(format_pgm_p++);
|
||||
}
|
||||
|
||||
// Buffer full from copying format
|
||||
// Buffer full from copying format - RAII guard handles cleanup on return
|
||||
if (this->tx_buffer_at_ >= this->tx_buffer_size_) {
|
||||
global_recursion_guard_ = false; // Make sure to reset the recursion guard before returning
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -156,13 +178,13 @@ void Logger::log_vprintf_(uint8_t level, const char *tag, int line, const __Flas
|
||||
this->tx_buffer_at_ - msg_start; // Don't subtract 1 - tx_buffer_at_ is already at the null terminator position
|
||||
|
||||
// Listeners get message first (before console write)
|
||||
#ifdef USE_LOG_LISTENERS
|
||||
for (auto *listener : this->log_listeners_)
|
||||
listener->on_log(level, tag, this->tx_buffer_ + msg_start, msg_length);
|
||||
#endif
|
||||
|
||||
// Write to console starting at the msg_start
|
||||
this->write_tx_buffer_to_console_(msg_start, &msg_length);
|
||||
|
||||
global_recursion_guard_ = false;
|
||||
}
|
||||
#endif // USE_STORE_LOG_STR_IN_FLASH
|
||||
|
||||
|
||||
@@ -212,8 +212,13 @@ class Logger : public Component {
|
||||
|
||||
inline uint8_t level_for(const char *tag);
|
||||
|
||||
#ifdef USE_LOG_LISTENERS
|
||||
/// Register a log listener to receive log messages
|
||||
void add_log_listener(LogListener *listener) { this->log_listeners_.push_back(listener); }
|
||||
#else
|
||||
/// No-op when log listeners are disabled
|
||||
void add_log_listener(LogListener *listener) {}
|
||||
#endif
|
||||
|
||||
#ifdef USE_LOGGER_LEVEL_LISTENERS
|
||||
/// Register a listener for log level changes
|
||||
@@ -229,6 +234,31 @@ class Logger : public Component {
|
||||
#endif
|
||||
|
||||
protected:
|
||||
// RAII guard for recursion flags - sets flag on construction, clears on destruction
|
||||
class RecursionGuard {
|
||||
public:
|
||||
explicit RecursionGuard(bool &flag) : flag_(flag) { flag_ = true; }
|
||||
~RecursionGuard() { flag_ = false; }
|
||||
RecursionGuard(const RecursionGuard &) = delete;
|
||||
RecursionGuard &operator=(const RecursionGuard &) = delete;
|
||||
RecursionGuard(RecursionGuard &&) = delete;
|
||||
RecursionGuard &operator=(RecursionGuard &&) = delete;
|
||||
|
||||
private:
|
||||
bool &flag_;
|
||||
};
|
||||
|
||||
#if defined(USE_ESP32) || defined(USE_HOST) || defined(USE_LIBRETINY)
|
||||
// Handles non-main thread logging only (~0.1% of calls)
|
||||
#if defined(USE_ESP32) || defined(USE_LIBRETINY)
|
||||
// ESP32/LibreTiny: Pass task handle to avoid calling xTaskGetCurrentTaskHandle() twice
|
||||
void log_vprintf_non_main_thread_(uint8_t level, const char *tag, int line, const char *format, va_list args,
|
||||
TaskHandle_t current_task);
|
||||
#else // USE_HOST
|
||||
// Host: No task handle parameter needed (not used in send_message_thread_safe)
|
||||
void log_vprintf_non_main_thread_(uint8_t level, const char *tag, int line, const char *format, va_list args);
|
||||
#endif
|
||||
#endif
|
||||
void process_messages_();
|
||||
void write_msg_(const char *msg, size_t len);
|
||||
|
||||
@@ -293,8 +323,10 @@ class Logger : public Component {
|
||||
this->tx_buffer_size_);
|
||||
|
||||
// Listeners get message WITHOUT newline (for API/MQTT/syslog)
|
||||
#ifdef USE_LOG_LISTENERS
|
||||
for (auto *listener : this->log_listeners_)
|
||||
listener->on_log(level, tag, this->tx_buffer_, this->tx_buffer_at_);
|
||||
#endif
|
||||
|
||||
// Console gets message WITH newline (if platform needs it)
|
||||
this->write_tx_buffer_to_console_();
|
||||
@@ -311,8 +343,10 @@ class Logger : public Component {
|
||||
this->write_body_to_buffer_(text, text_length, this->tx_buffer_, &this->tx_buffer_at_, this->tx_buffer_size_);
|
||||
this->write_footer_to_buffer_(this->tx_buffer_, &this->tx_buffer_at_, this->tx_buffer_size_);
|
||||
this->tx_buffer_[this->tx_buffer_at_] = '\0';
|
||||
#ifdef USE_LOG_LISTENERS
|
||||
for (auto *listener : this->log_listeners_)
|
||||
listener->on_log(level, tag, this->tx_buffer_, this->tx_buffer_at_);
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -348,10 +382,10 @@ class Logger : public Component {
|
||||
const device *uart_dev_{nullptr};
|
||||
#endif
|
||||
#if defined(USE_ESP32) || defined(USE_LIBRETINY) || defined(USE_ZEPHYR)
|
||||
void *main_task_ = nullptr; // Only used for thread name identification
|
||||
void *main_task_{nullptr}; // Main thread/task for fast path comparison
|
||||
#endif
|
||||
#ifdef USE_HOST
|
||||
pthread_t main_thread_{}; // Main thread for identification
|
||||
pthread_t main_thread_{}; // Main thread for pthread_equal() comparison
|
||||
#endif
|
||||
#ifdef USE_ESP32
|
||||
// Task-specific recursion guards:
|
||||
@@ -369,7 +403,10 @@ class Logger : public Component {
|
||||
#ifdef USE_LOGGER_RUNTIME_TAG_LEVELS
|
||||
std::map<const char *, uint8_t, CStrCompare> log_levels_{};
|
||||
#endif
|
||||
std::vector<LogListener *> log_listeners_; // Log message listeners (API, MQTT, syslog, etc.)
|
||||
#ifdef USE_LOG_LISTENERS
|
||||
StaticVector<LogListener *, ESPHOME_LOG_MAX_LISTENERS>
|
||||
log_listeners_; // Log message listeners (API, MQTT, syslog, etc.)
|
||||
#endif
|
||||
#ifdef USE_LOGGER_LEVEL_LISTENERS
|
||||
std::vector<LoggerLevelListener *> level_listeners_; // Log level change listeners
|
||||
#endif
|
||||
@@ -434,29 +471,28 @@ class Logger : public Component {
|
||||
#endif
|
||||
|
||||
#if defined(USE_ESP32) || defined(USE_HOST)
|
||||
inline bool HOT check_and_set_task_log_recursion_(bool is_main_task) {
|
||||
if (is_main_task) {
|
||||
const bool was_recursive = main_task_recursion_guard_;
|
||||
main_task_recursion_guard_ = true;
|
||||
return was_recursive;
|
||||
// RAII guard for non-main task recursion using pthread TLS
|
||||
class NonMainTaskRecursionGuard {
|
||||
public:
|
||||
explicit NonMainTaskRecursionGuard(pthread_key_t key) : key_(key) {
|
||||
pthread_setspecific(key_, reinterpret_cast<void *>(1));
|
||||
}
|
||||
~NonMainTaskRecursionGuard() { pthread_setspecific(key_, nullptr); }
|
||||
NonMainTaskRecursionGuard(const NonMainTaskRecursionGuard &) = delete;
|
||||
NonMainTaskRecursionGuard &operator=(const NonMainTaskRecursionGuard &) = delete;
|
||||
NonMainTaskRecursionGuard(NonMainTaskRecursionGuard &&) = delete;
|
||||
NonMainTaskRecursionGuard &operator=(NonMainTaskRecursionGuard &&) = delete;
|
||||
|
||||
intptr_t current = (intptr_t) pthread_getspecific(log_recursion_key_);
|
||||
if (current != 0)
|
||||
return true;
|
||||
private:
|
||||
pthread_key_t key_;
|
||||
};
|
||||
|
||||
pthread_setspecific(log_recursion_key_, (void *) 1);
|
||||
return false;
|
||||
}
|
||||
// Check if non-main task is already in recursion (via TLS)
|
||||
inline bool HOT is_non_main_task_recursive_() const { return pthread_getspecific(log_recursion_key_) != nullptr; }
|
||||
|
||||
inline void HOT reset_task_log_recursion_(bool is_main_task) {
|
||||
if (is_main_task) {
|
||||
main_task_recursion_guard_ = false;
|
||||
return;
|
||||
}
|
||||
// Create RAII guard for non-main task recursion
|
||||
inline NonMainTaskRecursionGuard make_non_main_task_guard_() { return NonMainTaskRecursionGuard(log_recursion_key_); }
|
||||
|
||||
pthread_setspecific(log_recursion_key_, (void *) 0);
|
||||
}
|
||||
#elif defined(USE_LIBRETINY)
|
||||
// LibreTiny doesn't have FreeRTOS TLS, so use a simple approach:
|
||||
// - Main task uses dedicated boolean (same as ESP32)
|
||||
@@ -466,29 +502,11 @@ class Logger : public Component {
|
||||
// - Cross-task "recursion" is prevented by the buffer mutex anyway
|
||||
// - Missing a recursive call from another task is acceptable (falls back to direct output)
|
||||
|
||||
inline bool HOT check_and_set_task_log_recursion_(bool is_main_task) {
|
||||
if (is_main_task) {
|
||||
const bool was_recursive = main_task_recursion_guard_;
|
||||
main_task_recursion_guard_ = true;
|
||||
return was_recursive;
|
||||
}
|
||||
// Check if non-main task is already in recursion
|
||||
inline bool HOT is_non_main_task_recursive_() const { return non_main_task_recursion_guard_; }
|
||||
|
||||
// For non-main tasks, use a simple shared guard
|
||||
// This may block legitimate concurrent logs from different tasks,
|
||||
// but that's acceptable - they'll fall back to direct console output
|
||||
const bool was_recursive = non_main_task_recursion_guard_;
|
||||
non_main_task_recursion_guard_ = true;
|
||||
return was_recursive;
|
||||
}
|
||||
|
||||
inline void HOT reset_task_log_recursion_(bool is_main_task) {
|
||||
if (is_main_task) {
|
||||
main_task_recursion_guard_ = false;
|
||||
return;
|
||||
}
|
||||
|
||||
non_main_task_recursion_guard_ = false;
|
||||
}
|
||||
// Create RAII guard for non-main task recursion (uses shared boolean for all non-main tasks)
|
||||
inline RecursionGuard make_non_main_task_guard_() { return RecursionGuard(non_main_task_recursion_guard_); }
|
||||
#endif
|
||||
|
||||
#ifdef USE_HOST
|
||||
|
||||
@@ -350,6 +350,7 @@ def exp_mqtt_message(config):
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
|
||||
# Add required libraries for ESP8266 and LibreTiny
|
||||
if CORE.is_esp8266 or CORE.is_libretiny:
|
||||
# https://github.com/heman/async-mqtt-client/blob/master/library.json
|
||||
@@ -432,6 +433,8 @@ async def to_code(config):
|
||||
cg.add(var.disable_log_message())
|
||||
else:
|
||||
cg.add(var.set_log_message_template(exp_mqtt_message(log_topic)))
|
||||
# Request a log listener slot only when log topic is enabled
|
||||
logger.request_log_listener()
|
||||
|
||||
if CONF_LEVEL in log_topic:
|
||||
cg.add(var.set_log_level(logger.LOG_LEVELS[log_topic[CONF_LEVEL]]))
|
||||
@@ -572,9 +575,13 @@ async def register_mqtt_component(var, config):
|
||||
if not config.get(CONF_DISCOVERY, True):
|
||||
cg.add(var.disable_discovery())
|
||||
if CONF_STATE_TOPIC in config:
|
||||
cg.add(var.set_custom_state_topic(config[CONF_STATE_TOPIC]))
|
||||
state_topic = await cg.templatable(config[CONF_STATE_TOPIC], [], cg.std_string)
|
||||
cg.add(var.set_custom_state_topic(state_topic))
|
||||
if CONF_COMMAND_TOPIC in config:
|
||||
cg.add(var.set_custom_command_topic(config[CONF_COMMAND_TOPIC]))
|
||||
command_topic = await cg.templatable(
|
||||
config[CONF_COMMAND_TOPIC], [], cg.std_string
|
||||
)
|
||||
cg.add(var.set_custom_command_topic(command_topic))
|
||||
if CONF_COMMAND_RETAIN in config:
|
||||
cg.add(var.set_command_retain(config[CONF_COMMAND_RETAIN]))
|
||||
if CONF_AVAILABILITY in config:
|
||||
|
||||
@@ -13,13 +13,13 @@ bool CustomMQTTDevice::publish(const std::string &topic, const std::string &payl
|
||||
}
|
||||
bool CustomMQTTDevice::publish(const std::string &topic, float value, int8_t number_decimals) {
|
||||
char buf[VALUE_ACCURACY_MAX_LEN];
|
||||
value_accuracy_to_buf(buf, value, number_decimals);
|
||||
return this->publish(topic, buf);
|
||||
size_t len = value_accuracy_to_buf(buf, value, number_decimals);
|
||||
return global_mqtt_client->publish(topic, buf, len);
|
||||
}
|
||||
bool CustomMQTTDevice::publish(const std::string &topic, int value) {
|
||||
char buffer[24];
|
||||
sprintf(buffer, "%d", value);
|
||||
return this->publish(topic, buffer);
|
||||
int len = snprintf(buffer, sizeof(buffer), "%d", value);
|
||||
return global_mqtt_client->publish(topic, buffer, len);
|
||||
}
|
||||
bool CustomMQTTDevice::publish_json(const std::string &topic, const json::json_build_t &f, uint8_t qos, bool retain) {
|
||||
return global_mqtt_client->publish_json(topic, f, qos, retain);
|
||||
|
||||
@@ -292,36 +292,37 @@ bool MQTTClimateComponent::publish_state_() {
|
||||
int8_t target_accuracy = traits.get_target_temperature_accuracy_decimals();
|
||||
int8_t current_accuracy = traits.get_current_temperature_accuracy_decimals();
|
||||
char payload[VALUE_ACCURACY_MAX_LEN];
|
||||
size_t len;
|
||||
if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_CURRENT_TEMPERATURE) &&
|
||||
!std::isnan(this->device_->current_temperature)) {
|
||||
value_accuracy_to_buf(payload, this->device_->current_temperature, current_accuracy);
|
||||
if (!this->publish(this->get_current_temperature_state_topic(), payload))
|
||||
len = value_accuracy_to_buf(payload, this->device_->current_temperature, current_accuracy);
|
||||
if (!this->publish(this->get_current_temperature_state_topic(), payload, len))
|
||||
success = false;
|
||||
}
|
||||
if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_TWO_POINT_TARGET_TEMPERATURE |
|
||||
climate::CLIMATE_REQUIRES_TWO_POINT_TARGET_TEMPERATURE)) {
|
||||
value_accuracy_to_buf(payload, this->device_->target_temperature_low, target_accuracy);
|
||||
if (!this->publish(this->get_target_temperature_low_state_topic(), payload))
|
||||
len = value_accuracy_to_buf(payload, this->device_->target_temperature_low, target_accuracy);
|
||||
if (!this->publish(this->get_target_temperature_low_state_topic(), payload, len))
|
||||
success = false;
|
||||
value_accuracy_to_buf(payload, this->device_->target_temperature_high, target_accuracy);
|
||||
if (!this->publish(this->get_target_temperature_high_state_topic(), payload))
|
||||
len = value_accuracy_to_buf(payload, this->device_->target_temperature_high, target_accuracy);
|
||||
if (!this->publish(this->get_target_temperature_high_state_topic(), payload, len))
|
||||
success = false;
|
||||
} else {
|
||||
value_accuracy_to_buf(payload, this->device_->target_temperature, target_accuracy);
|
||||
if (!this->publish(this->get_target_temperature_state_topic(), payload))
|
||||
len = value_accuracy_to_buf(payload, this->device_->target_temperature, target_accuracy);
|
||||
if (!this->publish(this->get_target_temperature_state_topic(), payload, len))
|
||||
success = false;
|
||||
}
|
||||
|
||||
if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_CURRENT_HUMIDITY) &&
|
||||
!std::isnan(this->device_->current_humidity)) {
|
||||
value_accuracy_to_buf(payload, this->device_->current_humidity, 0);
|
||||
if (!this->publish(this->get_current_humidity_state_topic(), payload))
|
||||
len = value_accuracy_to_buf(payload, this->device_->current_humidity, 0);
|
||||
if (!this->publish(this->get_current_humidity_state_topic(), payload, len))
|
||||
success = false;
|
||||
}
|
||||
if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_TARGET_HUMIDITY) &&
|
||||
!std::isnan(this->device_->target_humidity)) {
|
||||
value_accuracy_to_buf(payload, this->device_->target_humidity, 0);
|
||||
if (!this->publish(this->get_target_humidity_state_topic(), payload))
|
||||
len = value_accuracy_to_buf(payload, this->device_->target_humidity, 0);
|
||||
if (!this->publish(this->get_target_humidity_state_topic(), payload, len))
|
||||
success = false;
|
||||
}
|
||||
|
||||
|
||||
@@ -94,21 +94,25 @@ std::string MQTTComponent::get_default_topic_for_(const std::string &suffix) con
|
||||
}
|
||||
|
||||
std::string MQTTComponent::get_state_topic_() const {
|
||||
if (this->has_custom_state_topic_)
|
||||
return this->custom_state_topic_.str();
|
||||
if (this->custom_state_topic_.has_value())
|
||||
return this->custom_state_topic_.value();
|
||||
return this->get_default_topic_for_("state");
|
||||
}
|
||||
|
||||
std::string MQTTComponent::get_command_topic_() const {
|
||||
if (this->has_custom_command_topic_)
|
||||
return this->custom_command_topic_.str();
|
||||
if (this->custom_command_topic_.has_value())
|
||||
return this->custom_command_topic_.value();
|
||||
return this->get_default_topic_for_("command");
|
||||
}
|
||||
|
||||
bool MQTTComponent::publish(const std::string &topic, const std::string &payload) {
|
||||
return this->publish(topic, payload.data(), payload.size());
|
||||
}
|
||||
|
||||
bool MQTTComponent::publish(const std::string &topic, const char *payload, size_t payload_length) {
|
||||
if (topic.empty())
|
||||
return false;
|
||||
return global_mqtt_client->publish(topic, payload, this->qos_, this->retain_);
|
||||
return global_mqtt_client->publish(topic, payload, payload_length, this->qos_, this->retain_);
|
||||
}
|
||||
|
||||
bool MQTTComponent::publish_json(const std::string &topic, const json::json_build_t &f) {
|
||||
@@ -273,14 +277,6 @@ MQTTComponent::MQTTComponent() = default;
|
||||
|
||||
float MQTTComponent::get_setup_priority() const { return setup_priority::AFTER_CONNECTION; }
|
||||
void MQTTComponent::disable_discovery() { this->discovery_enabled_ = false; }
|
||||
void MQTTComponent::set_custom_state_topic(const char *custom_state_topic) {
|
||||
this->custom_state_topic_ = StringRef(custom_state_topic);
|
||||
this->has_custom_state_topic_ = true;
|
||||
}
|
||||
void MQTTComponent::set_custom_command_topic(const char *custom_command_topic) {
|
||||
this->custom_command_topic_ = StringRef(custom_command_topic);
|
||||
this->has_custom_command_topic_ = true;
|
||||
}
|
||||
void MQTTComponent::set_command_retain(bool command_retain) { this->command_retain_ = command_retain; }
|
||||
|
||||
void MQTTComponent::set_availability(std::string topic, std::string payload_available,
|
||||
@@ -349,13 +345,13 @@ StringRef MQTTComponent::get_default_object_id_to_(std::span<char, OBJECT_ID_MAX
|
||||
StringRef MQTTComponent::get_icon_ref_() const { return this->get_entity()->get_icon_ref(); }
|
||||
bool MQTTComponent::is_disabled_by_default_() const { return this->get_entity()->is_disabled_by_default(); }
|
||||
bool MQTTComponent::is_internal() {
|
||||
if (this->has_custom_state_topic_) {
|
||||
if (this->custom_state_topic_.has_value()) {
|
||||
// If the custom state_topic is null, return true as it is internal and should not publish
|
||||
// else, return false, as it is explicitly set to a topic, so it is not internal and should publish
|
||||
return this->get_state_topic_().empty();
|
||||
}
|
||||
|
||||
if (this->has_custom_command_topic_) {
|
||||
if (this->custom_command_topic_.has_value()) {
|
||||
// If the custom command_topic is null, return true as it is internal and should not publish
|
||||
// else, return false, as it is explicitly set to a topic, so it is not internal and should publish
|
||||
return this->get_command_topic_().empty();
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/entity_base.h"
|
||||
#include "esphome/core/string_ref.h"
|
||||
@@ -109,10 +110,13 @@ class MQTTComponent : public Component {
|
||||
/// Override this method to return the component type (e.g. "light", "sensor", ...)
|
||||
virtual const char *component_type() const = 0;
|
||||
|
||||
/// Set a custom state topic. Set to "" for default behavior.
|
||||
void set_custom_state_topic(const char *custom_state_topic);
|
||||
/// Set a custom command topic. Set to "" for default behavior.
|
||||
void set_custom_command_topic(const char *custom_command_topic);
|
||||
/// Set a custom state topic. Do not set for default behavior.
|
||||
template<typename T> void set_custom_state_topic(T &&custom_state_topic) {
|
||||
this->custom_state_topic_ = std::forward<T>(custom_state_topic);
|
||||
}
|
||||
template<typename T> void set_custom_command_topic(T &&custom_command_topic) {
|
||||
this->custom_command_topic_ = std::forward<T>(custom_command_topic);
|
||||
}
|
||||
/// Set whether command message should be retained.
|
||||
void set_command_retain(bool command_retain);
|
||||
|
||||
@@ -136,6 +140,14 @@ class MQTTComponent : public Component {
|
||||
*/
|
||||
bool publish(const std::string &topic, const std::string &payload);
|
||||
|
||||
/** Send a MQTT message.
|
||||
*
|
||||
* @param topic The topic.
|
||||
* @param payload The payload buffer.
|
||||
* @param payload_length The length of the payload.
|
||||
*/
|
||||
bool publish(const std::string &topic, const char *payload, size_t payload_length);
|
||||
|
||||
/** Construct and send a JSON MQTT message.
|
||||
*
|
||||
* @param topic The topic.
|
||||
@@ -203,14 +215,11 @@ class MQTTComponent : public Component {
|
||||
/// Get the object ID for this MQTT component, writing to the provided buffer.
|
||||
StringRef get_default_object_id_to_(std::span<char, OBJECT_ID_MAX_LEN> buf) const;
|
||||
|
||||
StringRef custom_state_topic_{};
|
||||
StringRef custom_command_topic_{};
|
||||
TemplatableValue<std::string> custom_state_topic_{};
|
||||
TemplatableValue<std::string> custom_command_topic_{};
|
||||
|
||||
std::unique_ptr<Availability> availability_;
|
||||
|
||||
bool has_custom_state_topic_{false};
|
||||
bool has_custom_command_topic_{false};
|
||||
|
||||
bool command_retain_{false};
|
||||
bool retain_{true};
|
||||
uint8_t qos_{0};
|
||||
|
||||
@@ -99,14 +99,14 @@ bool MQTTCoverComponent::publish_state() {
|
||||
bool success = true;
|
||||
if (traits.get_supports_position()) {
|
||||
char pos[VALUE_ACCURACY_MAX_LEN];
|
||||
value_accuracy_to_buf(pos, roundf(this->cover_->position * 100), 0);
|
||||
if (!this->publish(this->get_position_state_topic(), pos))
|
||||
size_t len = value_accuracy_to_buf(pos, roundf(this->cover_->position * 100), 0);
|
||||
if (!this->publish(this->get_position_state_topic(), pos, len))
|
||||
success = false;
|
||||
}
|
||||
if (traits.get_supports_tilt()) {
|
||||
char pos[VALUE_ACCURACY_MAX_LEN];
|
||||
value_accuracy_to_buf(pos, roundf(this->cover_->tilt * 100), 0);
|
||||
if (!this->publish(this->get_tilt_state_topic(), pos))
|
||||
size_t len = value_accuracy_to_buf(pos, roundf(this->cover_->tilt * 100), 0);
|
||||
if (!this->publish(this->get_tilt_state_topic(), pos, len))
|
||||
success = false;
|
||||
}
|
||||
const char *state_s = this->cover_->current_operation == COVER_OPERATION_OPENING ? "opening"
|
||||
|
||||
@@ -174,8 +174,9 @@ bool MQTTFanComponent::publish_state() {
|
||||
}
|
||||
auto traits = this->state_->get_traits();
|
||||
if (traits.supports_speed()) {
|
||||
std::string payload = to_string(this->state_->speed);
|
||||
bool success = this->publish(this->get_speed_level_state_topic(), payload);
|
||||
char buf[12];
|
||||
int len = snprintf(buf, sizeof(buf), "%d", this->state_->speed);
|
||||
bool success = this->publish(this->get_speed_level_state_topic(), buf, len);
|
||||
failed = failed || !success;
|
||||
}
|
||||
return !failed;
|
||||
|
||||
@@ -80,11 +80,11 @@ bool MQTTSensorComponent::send_initial_state() {
|
||||
}
|
||||
bool MQTTSensorComponent::publish_state(float value) {
|
||||
if (mqtt::global_mqtt_client->is_publish_nan_as_none() && std::isnan(value))
|
||||
return this->publish(this->get_state_topic_(), "None");
|
||||
return this->publish(this->get_state_topic_(), "None", 4);
|
||||
int8_t accuracy = this->sensor_->get_accuracy_decimals();
|
||||
char buf[VALUE_ACCURACY_MAX_LEN];
|
||||
value_accuracy_to_buf(buf, value, accuracy);
|
||||
return this->publish(this->get_state_topic_(), buf);
|
||||
size_t len = value_accuracy_to_buf(buf, value, accuracy);
|
||||
return this->publish(this->get_state_topic_(), buf, len);
|
||||
}
|
||||
|
||||
} // namespace esphome::mqtt
|
||||
|
||||
@@ -74,8 +74,8 @@ bool MQTTValveComponent::publish_state() {
|
||||
bool success = true;
|
||||
if (traits.get_supports_position()) {
|
||||
char pos[VALUE_ACCURACY_MAX_LEN];
|
||||
value_accuracy_to_buf(pos, roundf(this->valve_->position * 100), 0);
|
||||
if (!this->publish(this->get_position_state_topic(), pos))
|
||||
size_t len = value_accuracy_to_buf(pos, roundf(this->valve_->position * 100), 0);
|
||||
if (!this->publish(this->get_position_state_topic(), pos, len))
|
||||
success = false;
|
||||
}
|
||||
const char *state_s = this->valve_->current_operation == VALVE_OPERATION_OPENING ? "opening"
|
||||
|
||||
@@ -18,9 +18,9 @@ char *format_bytes_to(char *buffer, const std::vector<uint8_t> &bytes) {
|
||||
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
||||
std::string format_uid(const std::vector<uint8_t> &uid) { return format_hex_pretty(uid, '-', false); }
|
||||
|
||||
std::string format_bytes(const std::vector<uint8_t> &bytes) { return format_hex_pretty(bytes, ' ', false); }
|
||||
// Deprecated wrappers intentionally use heap-allocating version for backward compatibility
|
||||
std::string format_uid(const std::vector<uint8_t> &uid) { return format_hex_pretty(uid, '-', false); } // NOLINT
|
||||
std::string format_bytes(const std::vector<uint8_t> &bytes) { return format_hex_pretty(bytes, ' ', false); } // NOLINT
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
uint8_t guess_tag_type(uint8_t uid_length) {
|
||||
|
||||
@@ -14,6 +14,13 @@ namespace ota {
|
||||
std::unique_ptr<ota::OTABackend> make_ota_backend() { return make_unique<ota::IDFOTABackend>(); }
|
||||
|
||||
OTAResponseTypes IDFOTABackend::begin(size_t image_size) {
|
||||
#ifdef USE_OTA_ROLLBACK
|
||||
// If we're starting an OTA, the current boot is good enough - mark it valid
|
||||
// to prevent rollback and allow the OTA to proceed even if the safe mode
|
||||
// timer hasn't expired yet.
|
||||
esp_ota_mark_app_valid_cancel_rollback();
|
||||
#endif
|
||||
|
||||
this->partition_ = esp_ota_get_next_update_partition(nullptr);
|
||||
if (this->partition_ == nullptr) {
|
||||
return OTA_RESPONSE_ERROR_NO_UPDATE_PARTITION;
|
||||
|
||||
@@ -220,7 +220,7 @@ void RC522::loop() {
|
||||
|
||||
std::vector<uint8_t> rfid_uid(std::begin(uid_buffer_), std::begin(uid_buffer_) + uid_idx_);
|
||||
uid_idx_ = 0;
|
||||
// ESP_LOGD(TAG, "Processing '%s'", format_hex_pretty(rfid_uid, '-', false).c_str());
|
||||
// ESP_LOGD(TAG, "Processing '%s'", format_hex_pretty(rfid_uid, '-', false).c_str()); // NOLINT
|
||||
pcd_antenna_off_();
|
||||
state_ = STATE_INIT; // scan again on next update
|
||||
bool report = true;
|
||||
|
||||
@@ -31,7 +31,7 @@ class MideaData {
|
||||
bool is_compliment(const MideaData &rhs) const;
|
||||
/// @deprecated Allocates heap memory. Use to_str() instead. Removed in 2026.7.0.
|
||||
ESPDEPRECATED("Allocates heap memory. Use to_str() instead. Removed in 2026.7.0.", "2026.1.0")
|
||||
std::string to_string() const { return format_hex_pretty(this->data_.data(), this->data_.size()); }
|
||||
std::string to_string() const { return format_hex_pretty(this->data_.data(), this->data_.size()); } // NOLINT
|
||||
/// Buffer size for to_str(): 6 bytes = "AA.BB.CC.DD.EE.FF\0"
|
||||
static constexpr size_t TO_STR_BUFFER_SIZE = format_hex_pretty_size(6);
|
||||
/// Format to buffer, returns pointer to buffer
|
||||
|
||||
@@ -1,18 +1,29 @@
|
||||
# This file was auto-generated by libretiny/generate_components.py
|
||||
# Do not modify its contents.
|
||||
# For custom pin validators, put validate_pin() or validate_usage()
|
||||
# in gpio.py file in this directory.
|
||||
# For changing schema/pin schema, put COMPONENT_SCHEMA or COMPONENT_PIN_SCHEMA
|
||||
# in schema.py file in this directory.
|
||||
"""
|
||||
██╗ ██╗ █████╗ ██████╗ ███╗ ██╗██╗███╗ ██╗ ██████╗
|
||||
██║ ██║██╔══██╗██╔══██╗████╗ ██║██║████╗ ██║██╔════╝
|
||||
██║ █╗ ██║███████║██████╔╝██╔██╗ ██║██║██╔██╗ ██║██║ ███╗
|
||||
██║███╗██║██╔══██║██╔══██╗██║╚██╗██║██║██║╚██╗██║██║ ██║
|
||||
╚███╔███╔╝██║ ██║██║ ██║██║ ╚████║██║██║ ╚████║╚██████╔╝
|
||||
╚══╝╚══╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═══╝╚═╝╚═╝ ╚═══╝ ╚═════╝
|
||||
|
||||
AUTO-GENERATED FILE - DO NOT EDIT!
|
||||
|
||||
This file was auto-generated by libretiny/generate_components.py.
|
||||
Any manual changes WILL BE LOST on regeneration.
|
||||
|
||||
To customize this component:
|
||||
- Pin validators: Create gpio.py with validate_pin() or validate_usage()
|
||||
- Schema extensions: Create schema.py with COMPONENT_SCHEMA or COMPONENT_PIN_SCHEMA
|
||||
|
||||
Platform-specific code should be added to the main libretiny component
|
||||
(__init__.py in esphome/components/libretiny/) rather than here.
|
||||
"""
|
||||
|
||||
from esphome import pins
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import libretiny
|
||||
from esphome.components.libretiny.const import (
|
||||
COMPONENT_RTL87XX,
|
||||
FAMILY_RTL8710B,
|
||||
KEY_COMPONENT_DATA,
|
||||
KEY_FAMILY,
|
||||
KEY_LIBRETINY,
|
||||
LibreTinyComponent,
|
||||
)
|
||||
@@ -24,13 +35,13 @@ CODEOWNERS = ["@kuba2k2"]
|
||||
AUTO_LOAD = ["libretiny"]
|
||||
IS_TARGET_PLATFORM = True
|
||||
|
||||
|
||||
COMPONENT_DATA = LibreTinyComponent(
|
||||
name=COMPONENT_RTL87XX,
|
||||
boards=RTL87XX_BOARDS,
|
||||
board_pins=RTL87XX_BOARD_PINS,
|
||||
pin_validation=None,
|
||||
usage_validation=None,
|
||||
supports_atomics=True,
|
||||
)
|
||||
|
||||
|
||||
@@ -48,11 +59,6 @@ CONFIG_SCHEMA.prepend_extra(_set_core_data)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
# Use FreeRTOS 8.2.3+ for xTaskNotifyGive/ulTaskNotifyTake required by AsyncTCP 3.4.3+
|
||||
# https://github.com/esphome/esphome/issues/10220
|
||||
# Only for RTL8710B (ambz) - RTL8720C (ambz2) requires FreeRTOS 10.x
|
||||
if CORE.data[KEY_LIBRETINY][KEY_FAMILY] == FAMILY_RTL8710B:
|
||||
cg.add_platformio_option("custom_versions.freertos", "8.2.3")
|
||||
return await libretiny.component_to_code(config)
|
||||
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -79,7 +79,13 @@ class BSDSocketImpl final : public Socket {
|
||||
return ::setsockopt(this->fd_, level, optname, optval, optlen);
|
||||
}
|
||||
int listen(int backlog) override { return ::listen(this->fd_, backlog); }
|
||||
ssize_t read(void *buf, size_t len) override { return ::read(this->fd_, buf, len); }
|
||||
ssize_t read(void *buf, size_t len) override {
|
||||
#ifdef USE_ESP32
|
||||
return ::lwip_read(this->fd_, buf, len);
|
||||
#else
|
||||
return ::read(this->fd_, buf, len);
|
||||
#endif
|
||||
}
|
||||
ssize_t recvfrom(void *buf, size_t len, sockaddr *addr, socklen_t *addr_len) override {
|
||||
#if defined(USE_ESP32) || defined(USE_HOST)
|
||||
return ::recvfrom(this->fd_, buf, len, 0, addr, addr_len);
|
||||
@@ -94,7 +100,13 @@ class BSDSocketImpl final : public Socket {
|
||||
return ::readv(this->fd_, iov, iovcnt);
|
||||
#endif
|
||||
}
|
||||
ssize_t write(const void *buf, size_t len) override { return ::write(this->fd_, buf, len); }
|
||||
ssize_t write(const void *buf, size_t len) override {
|
||||
#ifdef USE_ESP32
|
||||
return ::lwip_write(this->fd_, buf, len);
|
||||
#else
|
||||
return ::write(this->fd_, buf, len);
|
||||
#endif
|
||||
}
|
||||
ssize_t send(void *buf, size_t len, int flags) { return ::send(this->fd_, buf, len, flags); }
|
||||
ssize_t writev(const struct iovec *iov, int iovcnt) override {
|
||||
#if defined(USE_ESP32)
|
||||
|
||||
@@ -32,6 +32,8 @@ static const uint8_t SSD1306_COMMAND_PAGE_ADDRESS = 0x22;
|
||||
static const uint8_t SSD1306_COMMAND_NORMAL_DISPLAY = 0xA6;
|
||||
static const uint8_t SSD1306_COMMAND_INVERSE_DISPLAY = 0xA7;
|
||||
|
||||
static const uint8_t SSD1306B_COMMAND_SELECT_IREF = 0xAD;
|
||||
|
||||
static const uint8_t SSD1305_COMMAND_SET_BRIGHTNESS = 0x82;
|
||||
static const uint8_t SSD1305_COMMAND_SET_AREA_COLOR = 0xD8;
|
||||
|
||||
@@ -95,6 +97,12 @@ void SSD1306::setup() {
|
||||
this->command(0x8B);
|
||||
}
|
||||
} else {
|
||||
if (this->is_ssd1306b_()) {
|
||||
// Select external or internal Iref (0xAD)
|
||||
this->command(SSD1306B_COMMAND_SELECT_IREF);
|
||||
// Enable internal Iref and change from 19ua (POR) to 30uA
|
||||
this->command(0x20 | 0x10);
|
||||
}
|
||||
// Enable charge pump (0x8D)
|
||||
this->command(SSD1306_COMMAND_CHARGE_PUMP);
|
||||
if (this->external_vcc_) {
|
||||
@@ -226,6 +234,8 @@ bool SSD1306::is_sh1107_() const { return this->model_ == SH1107_MODEL_128_64 ||
|
||||
bool SSD1306::is_ssd1305_() const {
|
||||
return this->model_ == SSD1305_MODEL_128_64 || this->model_ == SSD1305_MODEL_128_32;
|
||||
}
|
||||
bool SSD1306::is_ssd1306b_() const { return this->model_ == SSD1306_MODEL_72_40; }
|
||||
|
||||
void SSD1306::update() {
|
||||
this->do_update_();
|
||||
this->display();
|
||||
|
||||
@@ -63,6 +63,7 @@ class SSD1306 : public display::DisplayBuffer {
|
||||
bool is_sh1106_() const;
|
||||
bool is_sh1107_() const;
|
||||
bool is_ssd1305_() const;
|
||||
bool is_ssd1306b_() const;
|
||||
|
||||
void draw_absolute_pixel_internal(int x, int y, Color color) override;
|
||||
|
||||
|
||||
@@ -62,9 +62,9 @@ void HOT I2CSSD1306::write_display_data() {
|
||||
}
|
||||
} else {
|
||||
size_t block_size = 16;
|
||||
if ((this->get_buffer_length_() & 8) == 8) {
|
||||
// use smaller block size for e.g. 72x40 displays where buffer size is multiple of 8, not 16
|
||||
block_size = 8;
|
||||
if ((this->get_buffer_length_() % 24) == 0) {
|
||||
// use 24 byte block size for e.g. 72x40 displays where buffer size is multiple of 24, not 16
|
||||
block_size = 24;
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < this->get_buffer_length_();) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from esphome import automation
|
||||
from esphome.automation import Condition, maybe_simple_id
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import mqtt, web_server
|
||||
from esphome.components import mqtt, web_server, zigbee
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_DEVICE_CLASS,
|
||||
@@ -74,6 +74,7 @@ validate_device_class = cv.one_of(*DEVICE_CLASSES, lower=True)
|
||||
_SWITCH_SCHEMA = (
|
||||
cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA)
|
||||
.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA)
|
||||
.extend(zigbee.SWITCH_SCHEMA)
|
||||
.extend(
|
||||
{
|
||||
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTSwitchComponent),
|
||||
@@ -103,6 +104,7 @@ _SWITCH_SCHEMA = (
|
||||
|
||||
|
||||
_SWITCH_SCHEMA.add_extra(entity_duplicate_validator("switch"))
|
||||
_SWITCH_SCHEMA.add_extra(zigbee.validate_switch)
|
||||
|
||||
|
||||
def switch_schema(
|
||||
@@ -165,6 +167,7 @@ async def setup_switch_core_(var, config):
|
||||
cg.add(var.set_device_class(device_class))
|
||||
|
||||
cg.add(var.set_restore_mode(config[CONF_RESTORE_MODE]))
|
||||
await zigbee.setup_switch(var, config)
|
||||
|
||||
|
||||
async def register_switch(var, config):
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import udp
|
||||
from esphome.components.logger import LOG_LEVELS, is_log_level
|
||||
from esphome.components.logger import LOG_LEVELS, is_log_level, request_log_listener
|
||||
from esphome.components.time import RealTimeClock
|
||||
from esphome.components.udp import CONF_UDP_ID
|
||||
import esphome.config_validation as cv
|
||||
@@ -36,6 +36,7 @@ async def to_code(config):
|
||||
level = LOG_LEVELS[config[CONF_LEVEL]]
|
||||
var = cg.new_Pvariable(config[CONF_ID], level, time)
|
||||
await cg.register_component(var, config)
|
||||
request_log_listener() # Request a log listener slot for syslog
|
||||
await cg.register_parented(var, parent)
|
||||
cg.add(var.set_strip(config[CONF_STRIP]))
|
||||
cg.add(var.set_facility(config[CONF_FACILITY]))
|
||||
|
||||
@@ -74,3 +74,4 @@ async def to_code(config: ConfigType) -> None:
|
||||
add_idf_sdkconfig_option(
|
||||
"CONFIG_TINYUSB_CDC_TX_BUFSIZE", config[CONF_TX_BUFFER_SIZE]
|
||||
)
|
||||
cg.add_define("ESPHOME_MAX_USB_CDC_INSTANCES", num_interfaces)
|
||||
|
||||
@@ -3,181 +3,17 @@
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#include <sys/param.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/ringbuf.h"
|
||||
#include "freertos/task.h"
|
||||
#include "esp_log.h"
|
||||
|
||||
#include "tusb.h"
|
||||
#include "tusb_cdc_acm.h"
|
||||
|
||||
namespace esphome::usb_cdc_acm {
|
||||
|
||||
static const char *TAG = "usb_cdc_acm";
|
||||
|
||||
// Maximum bytes to log in very verbose hex output (168 * 3 = 504, under TX buffer size of 512)
|
||||
static constexpr size_t USB_CDC_MAX_LOG_BYTES = 168;
|
||||
|
||||
static constexpr size_t USB_TX_TASK_STACK_SIZE = 4096;
|
||||
static constexpr size_t USB_TX_TASK_STACK_SIZE_VV = 8192;
|
||||
static const char *const TAG = "usb_cdc_acm";
|
||||
|
||||
// Global component instance for managing USB device
|
||||
USBCDCACMComponent *global_usb_cdc_component = nullptr;
|
||||
|
||||
static USBCDCACMInstance *get_instance_by_itf(int itf) {
|
||||
if (global_usb_cdc_component == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
return global_usb_cdc_component->get_interface_by_number(itf);
|
||||
}
|
||||
|
||||
static void tinyusb_cdc_rx_callback(int itf, cdcacm_event_t *event) {
|
||||
USBCDCACMInstance *instance = get_instance_by_itf(itf);
|
||||
if (instance == nullptr) {
|
||||
ESP_LOGE(TAG, "RX callback: invalid interface %d", itf);
|
||||
return;
|
||||
}
|
||||
|
||||
size_t rx_size = 0;
|
||||
static uint8_t rx_buf[CONFIG_TINYUSB_CDC_RX_BUFSIZE] = {0};
|
||||
|
||||
// read from USB
|
||||
esp_err_t ret =
|
||||
tinyusb_cdcacm_read(static_cast<tinyusb_cdcacm_itf_t>(itf), rx_buf, CONFIG_TINYUSB_CDC_RX_BUFSIZE, &rx_size);
|
||||
ESP_LOGV(TAG, "tinyusb_cdc_rx_callback itf=%d (size: %u)", itf, rx_size);
|
||||
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE
|
||||
char rx_hex_buf[format_hex_pretty_size(USB_CDC_MAX_LOG_BYTES)];
|
||||
#endif
|
||||
ESP_LOGVV(TAG, "rx_buf = %s", format_hex_pretty_to(rx_hex_buf, rx_buf, rx_size));
|
||||
|
||||
if (ret == ESP_OK && rx_size > 0) {
|
||||
RingbufHandle_t rx_ringbuf = instance->get_rx_ringbuf();
|
||||
if (rx_ringbuf != nullptr) {
|
||||
BaseType_t send_res = xRingbufferSend(rx_ringbuf, rx_buf, rx_size, 0);
|
||||
if (send_res != pdTRUE) {
|
||||
ESP_LOGE(TAG, "USB RX itf=%d: buffer full, %u bytes lost", itf, rx_size);
|
||||
} else {
|
||||
ESP_LOGV(TAG, "USB RX itf=%d: queued %u bytes", itf, rx_size);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void tinyusb_cdc_line_state_changed_callback(int itf, cdcacm_event_t *event) {
|
||||
USBCDCACMInstance *instance = get_instance_by_itf(itf);
|
||||
if (instance == nullptr) {
|
||||
ESP_LOGE(TAG, "Line state callback: invalid interface %d", itf);
|
||||
return;
|
||||
}
|
||||
|
||||
int dtr = event->line_state_changed_data.dtr;
|
||||
int rts = event->line_state_changed_data.rts;
|
||||
ESP_LOGV(TAG, "Line state itf=%d: DTR=%d, RTS=%d", itf, dtr, rts);
|
||||
|
||||
// Queue event for processing in main loop
|
||||
instance->queue_line_state_event(dtr != 0, rts != 0);
|
||||
}
|
||||
|
||||
static void tinyusb_cdc_line_coding_changed_callback(int itf, cdcacm_event_t *event) {
|
||||
USBCDCACMInstance *instance = get_instance_by_itf(itf);
|
||||
if (instance == nullptr) {
|
||||
ESP_LOGE(TAG, "Line coding callback: invalid interface %d", itf);
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t bit_rate = event->line_coding_changed_data.p_line_coding->bit_rate;
|
||||
uint8_t stop_bits = event->line_coding_changed_data.p_line_coding->stop_bits;
|
||||
uint8_t parity = event->line_coding_changed_data.p_line_coding->parity;
|
||||
uint8_t data_bits = event->line_coding_changed_data.p_line_coding->data_bits;
|
||||
ESP_LOGV(TAG, "Line coding itf=%d: bit_rate=%" PRIu32 " stop_bits=%u parity=%u data_bits=%u", itf, bit_rate,
|
||||
stop_bits, parity, data_bits);
|
||||
|
||||
// Queue event for processing in main loop
|
||||
instance->queue_line_coding_event(bit_rate, stop_bits, parity, data_bits);
|
||||
}
|
||||
|
||||
static esp_err_t ringbuf_read_bytes(RingbufHandle_t ring_buf, uint8_t *out_buf, size_t out_buf_sz, size_t *rx_data_size,
|
||||
TickType_t xTicksToWait) {
|
||||
size_t read_sz;
|
||||
uint8_t *buf = static_cast<uint8_t *>(xRingbufferReceiveUpTo(ring_buf, &read_sz, xTicksToWait, out_buf_sz));
|
||||
|
||||
if (buf == nullptr) {
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
memcpy(out_buf, buf, read_sz);
|
||||
vRingbufferReturnItem(ring_buf, (void *) buf);
|
||||
*rx_data_size = read_sz;
|
||||
|
||||
// Buffer's data can be wrapped, in which case we should perform another read
|
||||
buf = static_cast<uint8_t *>(xRingbufferReceiveUpTo(ring_buf, &read_sz, 0, out_buf_sz - *rx_data_size));
|
||||
if (buf != nullptr) {
|
||||
memcpy(out_buf + *rx_data_size, buf, read_sz);
|
||||
vRingbufferReturnItem(ring_buf, (void *) buf);
|
||||
*rx_data_size += read_sz;
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
USBCDCACMComponent *global_usb_cdc_component = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
||||
//==============================================================================
|
||||
// USBCDCACMInstance Implementation
|
||||
//==============================================================================
|
||||
|
||||
void USBCDCACMInstance::setup() {
|
||||
this->usb_tx_ringbuf_ = xRingbufferCreate(CONFIG_TINYUSB_CDC_TX_BUFSIZE, RINGBUF_TYPE_BYTEBUF);
|
||||
if (this->usb_tx_ringbuf_ == nullptr) {
|
||||
ESP_LOGE(TAG, "USB TX buffer creation error for itf %d", this->itf_);
|
||||
this->parent_->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
this->usb_rx_ringbuf_ = xRingbufferCreate(CONFIG_TINYUSB_CDC_RX_BUFSIZE, RINGBUF_TYPE_BYTEBUF);
|
||||
if (this->usb_rx_ringbuf_ == nullptr) {
|
||||
ESP_LOGE(TAG, "USB RX buffer creation error for itf %d", this->itf_);
|
||||
this->parent_->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
// Configure this CDC interface
|
||||
const tinyusb_config_cdcacm_t acm_cfg = {
|
||||
.usb_dev = TINYUSB_USBDEV_0,
|
||||
.cdc_port = this->itf_,
|
||||
.callback_rx = &tinyusb_cdc_rx_callback,
|
||||
.callback_rx_wanted_char = NULL,
|
||||
.callback_line_state_changed = &tinyusb_cdc_line_state_changed_callback,
|
||||
.callback_line_coding_changed = &tinyusb_cdc_line_coding_changed_callback,
|
||||
};
|
||||
|
||||
esp_err_t result = tusb_cdc_acm_init(&acm_cfg);
|
||||
if (result != ESP_OK) {
|
||||
ESP_LOGE(TAG, "tusb_cdc_acm_init failed: %d", result);
|
||||
this->parent_->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
// Use a larger stack size for (very) verbose logging
|
||||
const size_t stack_size = esp_log_level_get(TAG) > ESP_LOG_DEBUG ? USB_TX_TASK_STACK_SIZE_VV : USB_TX_TASK_STACK_SIZE;
|
||||
|
||||
// Create a simple, unique task name per interface
|
||||
char task_name[] = "usb_tx_0";
|
||||
task_name[sizeof(task_name) - 1] = format_hex_char(static_cast<char>(this->itf_));
|
||||
xTaskCreate(usb_tx_task_fn, task_name, stack_size, this, 4, &this->usb_tx_task_handle_);
|
||||
|
||||
if (this->usb_tx_task_handle_ == nullptr) {
|
||||
ESP_LOGE(TAG, "Failed to create USB TX task for itf %d", this->itf_);
|
||||
this->parent_->mark_failed();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void USBCDCACMInstance::loop() {
|
||||
// Process events from the lock-free queue
|
||||
this->process_events_();
|
||||
}
|
||||
|
||||
void USBCDCACMInstance::queue_line_state_event(bool dtr, bool rts) {
|
||||
// Allocate event from pool
|
||||
CDCEvent *event = this->event_pool_.allocate();
|
||||
@@ -288,170 +124,6 @@ void USBCDCACMInstance::process_events_() {
|
||||
}
|
||||
}
|
||||
|
||||
void USBCDCACMInstance::usb_tx_task_fn(void *arg) {
|
||||
auto *instance = static_cast<USBCDCACMInstance *>(arg);
|
||||
instance->usb_tx_task();
|
||||
}
|
||||
|
||||
void USBCDCACMInstance::usb_tx_task() {
|
||||
uint8_t data[CONFIG_TINYUSB_CDC_TX_BUFSIZE] = {0};
|
||||
size_t tx_data_size = 0;
|
||||
|
||||
while (1) {
|
||||
// Wait for a notification from the bridge component
|
||||
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
|
||||
|
||||
// When we do wake up, we can be sure there is data in the ring buffer
|
||||
esp_err_t ret = ringbuf_read_bytes(this->usb_tx_ringbuf_, data, CONFIG_TINYUSB_CDC_TX_BUFSIZE, &tx_data_size, 0);
|
||||
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "USB TX itf=%d: RingBuf read failed", this->itf_);
|
||||
continue;
|
||||
} else if (tx_data_size == 0) {
|
||||
ESP_LOGD(TAG, "USB TX itf=%d: RingBuf empty, skipping", this->itf_);
|
||||
continue;
|
||||
}
|
||||
|
||||
ESP_LOGV(TAG, "USB TX itf=%d: Read %d bytes from buffer", this->itf_, tx_data_size);
|
||||
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE
|
||||
char tx_hex_buf[format_hex_pretty_size(USB_CDC_MAX_LOG_BYTES)];
|
||||
#endif
|
||||
ESP_LOGVV(TAG, "data = %s", format_hex_pretty_to(tx_hex_buf, data, tx_data_size));
|
||||
|
||||
// Serial data will be split up into 64 byte chunks to be sent over USB so this
|
||||
// usually will take multiple iterations
|
||||
uint8_t *data_head = &data[0];
|
||||
|
||||
while (tx_data_size > 0) {
|
||||
size_t queued = tinyusb_cdcacm_write_queue(this->itf_, data_head, tx_data_size);
|
||||
ESP_LOGV(TAG, "USB TX itf=%d: enqueued: size=%d, queued=%u", this->itf_, tx_data_size, queued);
|
||||
|
||||
tx_data_size -= queued;
|
||||
data_head += queued;
|
||||
|
||||
ESP_LOGV(TAG, "USB TX itf=%d: waiting 10ms for flush", this->itf_);
|
||||
esp_err_t flush_ret = tinyusb_cdcacm_write_flush(this->itf_, pdMS_TO_TICKS(10));
|
||||
|
||||
if (flush_ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "USB TX itf=%d: flush failed", this->itf_);
|
||||
tud_cdc_n_write_clear(this->itf_);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
// UARTComponent Interface Implementation
|
||||
//==============================================================================
|
||||
|
||||
void USBCDCACMInstance::write_array(const uint8_t *data, size_t len) {
|
||||
if (len == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Write data to TX ring buffer
|
||||
BaseType_t send_res = xRingbufferSend(this->usb_tx_ringbuf_, data, len, 0);
|
||||
if (send_res != pdTRUE) {
|
||||
ESP_LOGW(TAG, "USB TX itf=%d: buffer full, %u bytes dropped", this->itf_, len);
|
||||
return;
|
||||
}
|
||||
|
||||
// Notify TX task that data is available
|
||||
if (this->usb_tx_task_handle_ != nullptr) {
|
||||
xTaskNotifyGive(this->usb_tx_task_handle_);
|
||||
}
|
||||
}
|
||||
|
||||
bool USBCDCACMInstance::peek_byte(uint8_t *data) {
|
||||
if (this->has_peek_) {
|
||||
*data = this->peek_buffer_;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this->read_byte(&this->peek_buffer_)) {
|
||||
*data = this->peek_buffer_;
|
||||
this->has_peek_ = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool USBCDCACMInstance::read_array(uint8_t *data, size_t len) {
|
||||
if (len == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t original_len = len;
|
||||
size_t bytes_read = 0;
|
||||
|
||||
// First, use the peek buffer if available
|
||||
if (this->has_peek_) {
|
||||
data[0] = this->peek_buffer_;
|
||||
this->has_peek_ = false;
|
||||
bytes_read = 1;
|
||||
data++;
|
||||
if (--len == 0) { // Decrement len first, then check it...
|
||||
return true; // No more to read
|
||||
}
|
||||
}
|
||||
|
||||
// Read remaining bytes from RX ring buffer
|
||||
size_t rx_size = 0;
|
||||
uint8_t *buf = static_cast<uint8_t *>(xRingbufferReceiveUpTo(this->usb_rx_ringbuf_, &rx_size, 0, len));
|
||||
if (buf == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
memcpy(data, buf, rx_size);
|
||||
vRingbufferReturnItem(this->usb_rx_ringbuf_, (void *) buf);
|
||||
bytes_read += rx_size;
|
||||
data += rx_size;
|
||||
len -= rx_size;
|
||||
if (len == 0) {
|
||||
return true; // No more to read
|
||||
}
|
||||
|
||||
// Buffer's data may wrap around, in which case we should perform another read
|
||||
buf = static_cast<uint8_t *>(xRingbufferReceiveUpTo(this->usb_rx_ringbuf_, &rx_size, 0, len));
|
||||
if (buf == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
memcpy(data, buf, rx_size);
|
||||
vRingbufferReturnItem(this->usb_rx_ringbuf_, (void *) buf);
|
||||
bytes_read += rx_size;
|
||||
|
||||
return bytes_read == original_len;
|
||||
}
|
||||
|
||||
int USBCDCACMInstance::available() {
|
||||
UBaseType_t waiting = 0;
|
||||
if (this->usb_rx_ringbuf_ != nullptr) {
|
||||
vRingbufferGetInfo(this->usb_rx_ringbuf_, nullptr, nullptr, nullptr, nullptr, &waiting);
|
||||
}
|
||||
return static_cast<int>(waiting) + (this->has_peek_ ? 1 : 0);
|
||||
}
|
||||
|
||||
void USBCDCACMInstance::flush() {
|
||||
// Wait for TX ring buffer to be empty
|
||||
if (this->usb_tx_ringbuf_ == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
UBaseType_t waiting = 1;
|
||||
while (waiting > 0) {
|
||||
vRingbufferGetInfo(this->usb_tx_ringbuf_, nullptr, nullptr, nullptr, nullptr, &waiting);
|
||||
if (waiting > 0) {
|
||||
vTaskDelay(pdMS_TO_TICKS(1));
|
||||
}
|
||||
}
|
||||
|
||||
// Also wait for USB to finish transmitting
|
||||
tinyusb_cdcacm_write_flush(this->itf_, pdMS_TO_TICKS(100));
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
// USBCDCACMComponent Implementation
|
||||
//==============================================================================
|
||||
@@ -460,7 +132,7 @@ USBCDCACMComponent::USBCDCACMComponent() { global_usb_cdc_component = this; }
|
||||
|
||||
void USBCDCACMComponent::setup() {
|
||||
// Setup all registered interfaces
|
||||
for (auto interface : this->interfaces_) {
|
||||
for (auto *interface : this->interfaces_) {
|
||||
if (interface != nullptr) {
|
||||
interface->setup();
|
||||
}
|
||||
@@ -469,7 +141,7 @@ void USBCDCACMComponent::setup() {
|
||||
|
||||
void USBCDCACMComponent::loop() {
|
||||
// Call loop() on all registered interfaces to process events
|
||||
for (auto interface : this->interfaces_) {
|
||||
for (auto *interface : this->interfaces_) {
|
||||
if (interface != nullptr) {
|
||||
interface->loop();
|
||||
}
|
||||
@@ -480,21 +152,28 @@ void USBCDCACMComponent::dump_config() {
|
||||
ESP_LOGCONFIG(TAG,
|
||||
"USB CDC-ACM:\n"
|
||||
" Number of Interfaces: %d",
|
||||
this->interfaces_[MAX_USB_CDC_INSTANCES - 1] != nullptr ? MAX_USB_CDC_INSTANCES : 1);
|
||||
ESPHOME_MAX_USB_CDC_INSTANCES);
|
||||
for (uint8_t i = 0; i < ESPHOME_MAX_USB_CDC_INSTANCES; ++i) {
|
||||
if (this->interfaces_[i] != nullptr) {
|
||||
this->interfaces_[i]->dump_config();
|
||||
} else {
|
||||
ESP_LOGCONFIG(TAG, " Interface %u is disabled", i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void USBCDCACMComponent::add_interface(USBCDCACMInstance *interface) {
|
||||
uint8_t itf_num = static_cast<uint8_t>(interface->get_itf());
|
||||
if (itf_num < MAX_USB_CDC_INSTANCES) {
|
||||
if (itf_num < ESPHOME_MAX_USB_CDC_INSTANCES) {
|
||||
this->interfaces_[itf_num] = interface;
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Interface number must be less than %u", MAX_USB_CDC_INSTANCES);
|
||||
ESP_LOGE(TAG, "Interface number must be less than %u", ESPHOME_MAX_USB_CDC_INSTANCES);
|
||||
}
|
||||
}
|
||||
|
||||
USBCDCACMInstance *USBCDCACMComponent::get_interface_by_number(uint8_t itf) {
|
||||
for (auto interface : this->interfaces_) {
|
||||
if ((interface != nullptr) && (interface->get_itf() == static_cast<tinyusb_cdcacm_itf_t>(itf))) {
|
||||
for (auto *interface : this->interfaces_) {
|
||||
if ((interface != nullptr) && (interface->get_itf() == itf)) {
|
||||
return interface;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
namespace esphome::usb_cdc_acm {
|
||||
|
||||
static const uint8_t EVENT_QUEUE_SIZE = 12;
|
||||
static const uint8_t MAX_USB_CDC_INSTANCES = 2;
|
||||
|
||||
// Callback types for line coding and line state changes
|
||||
using LineCodingCallback = std::function<void(uint32_t bit_rate, uint8_t stop_bits, uint8_t parity, uint8_t data_bits)>;
|
||||
@@ -53,14 +52,13 @@ class USBCDCACMComponent;
|
||||
/// Represents a single CDC ACM interface instance
|
||||
class USBCDCACMInstance : public uart::UARTComponent, public Parented<USBCDCACMComponent> {
|
||||
public:
|
||||
void set_interface_number(uint8_t itf) { this->itf_ = static_cast<tinyusb_cdcacm_itf_t>(itf); }
|
||||
|
||||
void setup();
|
||||
void loop();
|
||||
void dump_config();
|
||||
|
||||
void set_interface_number(uint8_t itf) { this->itf_ = itf; }
|
||||
// Get the CDC port number for this instance
|
||||
tinyusb_cdcacm_itf_t get_itf() const { return this->itf_; }
|
||||
|
||||
uint8_t get_itf() const { return this->itf_; }
|
||||
// Ring buffer accessors for bridge components
|
||||
RingbufHandle_t get_tx_ringbuf() const { return this->usb_tx_ringbuf_; }
|
||||
RingbufHandle_t get_rx_ringbuf() const { return this->usb_rx_ringbuf_; }
|
||||
@@ -72,7 +70,7 @@ class USBCDCACMInstance : public uart::UARTComponent, public Parented<USBCDCACMC
|
||||
void set_line_coding_callback(LineCodingCallback callback) { this->line_coding_callback_ = std::move(callback); }
|
||||
void set_line_state_callback(LineStateCallback callback) { this->line_state_callback_ = std::move(callback); }
|
||||
|
||||
// Called from TinyUSB task context (SPSC producer) - queues event for processing in main loop
|
||||
// Called from USB core task context queues event for processing in main loop
|
||||
void queue_line_coding_event(uint32_t bit_rate, uint8_t stop_bits, uint8_t parity, uint8_t data_bits);
|
||||
void queue_line_state_event(bool dtr, bool rts);
|
||||
|
||||
@@ -87,17 +85,18 @@ class USBCDCACMInstance : public uart::UARTComponent, public Parented<USBCDCACMC
|
||||
void flush() override;
|
||||
|
||||
protected:
|
||||
void check_logger_conflict() override {}
|
||||
void check_logger_conflict() override;
|
||||
|
||||
// Process queued events and invoke callbacks (called from main loop)
|
||||
void process_events_();
|
||||
|
||||
TaskHandle_t usb_tx_task_handle_{nullptr};
|
||||
tinyusb_cdcacm_itf_t itf_{TINYUSB_CDC_ACM_0};
|
||||
|
||||
RingbufHandle_t usb_tx_ringbuf_{nullptr};
|
||||
RingbufHandle_t usb_rx_ringbuf_{nullptr};
|
||||
|
||||
// RX buffer for peek functionality
|
||||
uint8_t peek_buffer_{0};
|
||||
bool has_peek_{false};
|
||||
uint8_t itf_{0};
|
||||
// User-registered callbacks (called from main loop)
|
||||
LineCodingCallback line_coding_callback_{nullptr};
|
||||
LineStateCallback line_state_callback_{nullptr};
|
||||
@@ -105,10 +104,6 @@ class USBCDCACMInstance : public uart::UARTComponent, public Parented<USBCDCACMC
|
||||
// Lock-free queue and event pool for cross-task event passing
|
||||
EventPool<CDCEvent, EVENT_QUEUE_SIZE> event_pool_;
|
||||
LockFreeQueue<CDCEvent, EVENT_QUEUE_SIZE> event_queue_;
|
||||
|
||||
// RX buffer for peek functionality
|
||||
uint8_t peek_buffer_{0};
|
||||
bool has_peek_{false};
|
||||
};
|
||||
|
||||
/// Main USB CDC ACM component that manages the USB device and all CDC interfaces
|
||||
@@ -126,7 +121,7 @@ class USBCDCACMComponent : public Component {
|
||||
USBCDCACMInstance *get_interface_by_number(uint8_t itf);
|
||||
|
||||
protected:
|
||||
std::array<USBCDCACMInstance *, MAX_USB_CDC_INSTANCES> interfaces_{nullptr, nullptr};
|
||||
std::array<USBCDCACMInstance *, ESPHOME_MAX_USB_CDC_INSTANCES> interfaces_{};
|
||||
};
|
||||
|
||||
extern USBCDCACMComponent *global_usb_cdc_component; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
||||
349
esphome/components/usb_cdc_acm/usb_cdc_acm_esp32.cpp
Normal file
349
esphome/components/usb_cdc_acm/usb_cdc_acm_esp32.cpp
Normal file
@@ -0,0 +1,349 @@
|
||||
#if defined(USE_ESP32_VARIANT_ESP32P4) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
|
||||
#include "usb_cdc_acm.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <sys/param.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/ringbuf.h"
|
||||
#include "freertos/task.h"
|
||||
#include "esp_log.h"
|
||||
|
||||
#include "tusb.h"
|
||||
#include "tusb_cdc_acm.h"
|
||||
|
||||
namespace esphome::usb_cdc_acm {
|
||||
|
||||
static const char *const TAG = "usb_cdc_acm";
|
||||
|
||||
// Maximum bytes to log in very verbose hex output (168 * 3 = 504, under TX buffer size of 512)
|
||||
static constexpr size_t USB_CDC_MAX_LOG_BYTES = 168;
|
||||
|
||||
static constexpr size_t USB_TX_TASK_STACK_SIZE = 4096;
|
||||
static constexpr size_t USB_TX_TASK_STACK_SIZE_VV = 8192;
|
||||
|
||||
static USBCDCACMInstance *get_instance_by_itf(int itf) {
|
||||
if (global_usb_cdc_component == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
return global_usb_cdc_component->get_interface_by_number(itf);
|
||||
}
|
||||
|
||||
static void tinyusb_cdc_rx_callback(int itf, cdcacm_event_t *event) {
|
||||
USBCDCACMInstance *instance = get_instance_by_itf(itf);
|
||||
if (instance == nullptr) {
|
||||
ESP_LOGE(TAG, "RX callback: invalid interface %d", itf);
|
||||
return;
|
||||
}
|
||||
|
||||
size_t rx_size = 0;
|
||||
static uint8_t rx_buf[CONFIG_TINYUSB_CDC_RX_BUFSIZE] = {0};
|
||||
|
||||
// read from USB
|
||||
esp_err_t ret =
|
||||
tinyusb_cdcacm_read(static_cast<tinyusb_cdcacm_itf_t>(itf), rx_buf, CONFIG_TINYUSB_CDC_RX_BUFSIZE, &rx_size);
|
||||
ESP_LOGV(TAG, "tinyusb_cdc_rx_callback itf=%d (size: %u)", itf, rx_size);
|
||||
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE
|
||||
char rx_hex_buf[format_hex_pretty_size(USB_CDC_MAX_LOG_BYTES)];
|
||||
#endif
|
||||
ESP_LOGVV(TAG, "rx_buf = %s", format_hex_pretty_to(rx_hex_buf, rx_buf, rx_size));
|
||||
|
||||
if (ret == ESP_OK && rx_size > 0) {
|
||||
RingbufHandle_t rx_ringbuf = instance->get_rx_ringbuf();
|
||||
if (rx_ringbuf != nullptr) {
|
||||
BaseType_t send_res = xRingbufferSend(rx_ringbuf, rx_buf, rx_size, 0);
|
||||
if (send_res != pdTRUE) {
|
||||
ESP_LOGE(TAG, "USB RX itf=%d: buffer full, %u bytes lost", itf, rx_size);
|
||||
} else {
|
||||
ESP_LOGV(TAG, "USB RX itf=%d: queued %u bytes", itf, rx_size);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void tinyusb_cdc_line_state_changed_callback(int itf, cdcacm_event_t *event) {
|
||||
USBCDCACMInstance *instance = get_instance_by_itf(itf);
|
||||
if (instance == nullptr) {
|
||||
ESP_LOGE(TAG, "Line state callback: invalid interface %d", itf);
|
||||
return;
|
||||
}
|
||||
|
||||
int dtr = event->line_state_changed_data.dtr;
|
||||
int rts = event->line_state_changed_data.rts;
|
||||
ESP_LOGV(TAG, "Line state itf=%d: DTR=%d, RTS=%d", itf, dtr, rts);
|
||||
|
||||
// Queue event for processing in main loop
|
||||
instance->queue_line_state_event(dtr != 0, rts != 0);
|
||||
}
|
||||
|
||||
static void tinyusb_cdc_line_coding_changed_callback(int itf, cdcacm_event_t *event) {
|
||||
USBCDCACMInstance *instance = get_instance_by_itf(itf);
|
||||
if (instance == nullptr) {
|
||||
ESP_LOGE(TAG, "Line coding callback: invalid interface %d", itf);
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t bit_rate = event->line_coding_changed_data.p_line_coding->bit_rate;
|
||||
uint8_t stop_bits = event->line_coding_changed_data.p_line_coding->stop_bits;
|
||||
uint8_t parity = event->line_coding_changed_data.p_line_coding->parity;
|
||||
uint8_t data_bits = event->line_coding_changed_data.p_line_coding->data_bits;
|
||||
ESP_LOGV(TAG, "Line coding itf=%d: bit_rate=%" PRIu32 " stop_bits=%u parity=%u data_bits=%u", itf, bit_rate,
|
||||
stop_bits, parity, data_bits);
|
||||
|
||||
// Queue event for processing in main loop
|
||||
instance->queue_line_coding_event(bit_rate, stop_bits, parity, data_bits);
|
||||
}
|
||||
|
||||
static esp_err_t ringbuf_read_bytes(RingbufHandle_t ring_buf, uint8_t *out_buf, size_t out_buf_sz, size_t *rx_data_size,
|
||||
TickType_t xTicksToWait) {
|
||||
size_t read_sz;
|
||||
uint8_t *buf = static_cast<uint8_t *>(xRingbufferReceiveUpTo(ring_buf, &read_sz, xTicksToWait, out_buf_sz));
|
||||
|
||||
if (buf == nullptr) {
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
memcpy(out_buf, buf, read_sz);
|
||||
vRingbufferReturnItem(ring_buf, (void *) buf);
|
||||
*rx_data_size = read_sz;
|
||||
|
||||
// Buffer's data can be wrapped, in which case we should perform another read
|
||||
buf = static_cast<uint8_t *>(xRingbufferReceiveUpTo(ring_buf, &read_sz, 0, out_buf_sz - *rx_data_size));
|
||||
if (buf != nullptr) {
|
||||
memcpy(out_buf + *rx_data_size, buf, read_sz);
|
||||
vRingbufferReturnItem(ring_buf, (void *) buf);
|
||||
*rx_data_size += read_sz;
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
// USBCDCACMInstance Implementation
|
||||
//==============================================================================
|
||||
|
||||
void USBCDCACMInstance::setup() {
|
||||
this->usb_tx_ringbuf_ = xRingbufferCreate(CONFIG_TINYUSB_CDC_TX_BUFSIZE, RINGBUF_TYPE_BYTEBUF);
|
||||
if (this->usb_tx_ringbuf_ == nullptr) {
|
||||
ESP_LOGE(TAG, "USB TX buffer creation error for itf %d", this->itf_);
|
||||
this->parent_->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
this->usb_rx_ringbuf_ = xRingbufferCreate(CONFIG_TINYUSB_CDC_RX_BUFSIZE, RINGBUF_TYPE_BYTEBUF);
|
||||
if (this->usb_rx_ringbuf_ == nullptr) {
|
||||
ESP_LOGE(TAG, "USB RX buffer creation error for itf %d", this->itf_);
|
||||
this->parent_->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
// Configure this CDC interface
|
||||
const tinyusb_config_cdcacm_t acm_cfg = {
|
||||
.usb_dev = TINYUSB_USBDEV_0,
|
||||
.cdc_port = static_cast<tinyusb_cdcacm_itf_t>(this->itf_),
|
||||
.callback_rx = &tinyusb_cdc_rx_callback,
|
||||
.callback_rx_wanted_char = NULL,
|
||||
.callback_line_state_changed = &tinyusb_cdc_line_state_changed_callback,
|
||||
.callback_line_coding_changed = &tinyusb_cdc_line_coding_changed_callback,
|
||||
};
|
||||
|
||||
esp_err_t result = tusb_cdc_acm_init(&acm_cfg);
|
||||
if (result != ESP_OK) {
|
||||
ESP_LOGE(TAG, "tusb_cdc_acm_init failed: %d", result);
|
||||
this->parent_->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
// Use a larger stack size for (very) verbose logging
|
||||
const size_t stack_size = esp_log_level_get(TAG) > ESP_LOG_DEBUG ? USB_TX_TASK_STACK_SIZE_VV : USB_TX_TASK_STACK_SIZE;
|
||||
|
||||
// Create a simple, unique task name per interface
|
||||
char task_name[] = "usb_tx_0";
|
||||
task_name[sizeof(task_name) - 1] = format_hex_char(static_cast<char>(this->itf_));
|
||||
xTaskCreate(usb_tx_task_fn, task_name, stack_size, this, 4, &this->usb_tx_task_handle_);
|
||||
|
||||
if (this->usb_tx_task_handle_ == nullptr) {
|
||||
ESP_LOGE(TAG, "Failed to create USB TX task for itf %d", this->itf_);
|
||||
this->parent_->mark_failed();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void USBCDCACMInstance::loop() {
|
||||
// Process events from the lock-free queue
|
||||
this->process_events_();
|
||||
}
|
||||
|
||||
void USBCDCACMInstance::dump_config() {}
|
||||
|
||||
void USBCDCACMInstance::usb_tx_task_fn(void *arg) {
|
||||
auto *instance = static_cast<USBCDCACMInstance *>(arg);
|
||||
instance->usb_tx_task();
|
||||
}
|
||||
|
||||
void USBCDCACMInstance::usb_tx_task() {
|
||||
uint8_t data[CONFIG_TINYUSB_CDC_TX_BUFSIZE] = {0};
|
||||
size_t tx_data_size = 0;
|
||||
|
||||
while (1) {
|
||||
// Wait for a notification from the bridge component
|
||||
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
|
||||
|
||||
// When we do wake up, we can be sure there is data in the ring buffer
|
||||
esp_err_t ret = ringbuf_read_bytes(this->usb_tx_ringbuf_, data, CONFIG_TINYUSB_CDC_TX_BUFSIZE, &tx_data_size, 0);
|
||||
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "USB TX itf=%d: RingBuf read failed", this->itf_);
|
||||
continue;
|
||||
} else if (tx_data_size == 0) {
|
||||
ESP_LOGD(TAG, "USB TX itf=%d: RingBuf empty, skipping", this->itf_);
|
||||
continue;
|
||||
}
|
||||
|
||||
ESP_LOGV(TAG, "USB TX itf=%d: Read %d bytes from buffer", this->itf_, tx_data_size);
|
||||
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE
|
||||
char tx_hex_buf[format_hex_pretty_size(USB_CDC_MAX_LOG_BYTES)];
|
||||
#endif
|
||||
ESP_LOGVV(TAG, "data = %s", format_hex_pretty_to(tx_hex_buf, data, tx_data_size));
|
||||
|
||||
// Serial data will be split up into 64 byte chunks to be sent over USB so this
|
||||
// usually will take multiple iterations
|
||||
uint8_t *data_head = &data[0];
|
||||
|
||||
while (tx_data_size > 0) {
|
||||
size_t queued =
|
||||
tinyusb_cdcacm_write_queue(static_cast<tinyusb_cdcacm_itf_t>(this->itf_), data_head, tx_data_size);
|
||||
ESP_LOGV(TAG, "USB TX itf=%d: enqueued: size=%d, queued=%u", this->itf_, tx_data_size, queued);
|
||||
|
||||
tx_data_size -= queued;
|
||||
data_head += queued;
|
||||
|
||||
ESP_LOGV(TAG, "USB TX itf=%d: waiting 10ms for flush", this->itf_);
|
||||
esp_err_t flush_ret =
|
||||
tinyusb_cdcacm_write_flush(static_cast<tinyusb_cdcacm_itf_t>(this->itf_), pdMS_TO_TICKS(10));
|
||||
|
||||
if (flush_ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "USB TX itf=%d: flush failed", this->itf_);
|
||||
tud_cdc_n_write_clear(this->itf_);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
// UARTComponent Interface Implementation
|
||||
//==============================================================================
|
||||
|
||||
void USBCDCACMInstance::write_array(const uint8_t *data, size_t len) {
|
||||
if (len == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Write data to TX ring buffer
|
||||
BaseType_t send_res = xRingbufferSend(this->usb_tx_ringbuf_, data, len, 0);
|
||||
if (send_res != pdTRUE) {
|
||||
ESP_LOGW(TAG, "USB TX itf=%d: buffer full, %u bytes dropped", this->itf_, len);
|
||||
return;
|
||||
}
|
||||
|
||||
// Notify TX task that data is available
|
||||
if (this->usb_tx_task_handle_ != nullptr) {
|
||||
xTaskNotifyGive(this->usb_tx_task_handle_);
|
||||
}
|
||||
}
|
||||
|
||||
bool USBCDCACMInstance::peek_byte(uint8_t *data) {
|
||||
if (this->has_peek_) {
|
||||
*data = this->peek_buffer_;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this->read_byte(&this->peek_buffer_)) {
|
||||
*data = this->peek_buffer_;
|
||||
this->has_peek_ = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool USBCDCACMInstance::read_array(uint8_t *data, size_t len) {
|
||||
if (len == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t original_len = len;
|
||||
size_t bytes_read = 0;
|
||||
|
||||
// First, use the peek buffer if available
|
||||
if (this->has_peek_) {
|
||||
data[0] = this->peek_buffer_;
|
||||
this->has_peek_ = false;
|
||||
bytes_read = 1;
|
||||
data++;
|
||||
if (--len == 0) { // Decrement len first, then check it...
|
||||
return true; // No more to read
|
||||
}
|
||||
}
|
||||
|
||||
// Read remaining bytes from RX ring buffer
|
||||
size_t rx_size = 0;
|
||||
uint8_t *buf = static_cast<uint8_t *>(xRingbufferReceiveUpTo(this->usb_rx_ringbuf_, &rx_size, 0, len));
|
||||
if (buf == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
memcpy(data, buf, rx_size);
|
||||
vRingbufferReturnItem(this->usb_rx_ringbuf_, (void *) buf);
|
||||
bytes_read += rx_size;
|
||||
data += rx_size;
|
||||
len -= rx_size;
|
||||
if (len == 0) {
|
||||
return true; // No more to read
|
||||
}
|
||||
|
||||
// Buffer's data may wrap around, in which case we should perform another read
|
||||
buf = static_cast<uint8_t *>(xRingbufferReceiveUpTo(this->usb_rx_ringbuf_, &rx_size, 0, len));
|
||||
if (buf == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
memcpy(data, buf, rx_size);
|
||||
vRingbufferReturnItem(this->usb_rx_ringbuf_, (void *) buf);
|
||||
bytes_read += rx_size;
|
||||
|
||||
return bytes_read == original_len;
|
||||
}
|
||||
|
||||
int USBCDCACMInstance::available() {
|
||||
UBaseType_t waiting = 0;
|
||||
if (this->usb_rx_ringbuf_ != nullptr) {
|
||||
vRingbufferGetInfo(this->usb_rx_ringbuf_, nullptr, nullptr, nullptr, nullptr, &waiting);
|
||||
}
|
||||
return static_cast<int>(waiting) + (this->has_peek_ ? 1 : 0);
|
||||
}
|
||||
|
||||
void USBCDCACMInstance::flush() {
|
||||
// Wait for TX ring buffer to be empty
|
||||
if (this->usb_tx_ringbuf_ == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
UBaseType_t waiting = 1;
|
||||
while (waiting > 0) {
|
||||
vRingbufferGetInfo(this->usb_tx_ringbuf_, nullptr, nullptr, nullptr, nullptr, &waiting);
|
||||
if (waiting > 0) {
|
||||
vTaskDelay(pdMS_TO_TICKS(1));
|
||||
}
|
||||
}
|
||||
|
||||
// Also wait for USB to finish transmitting
|
||||
tinyusb_cdcacm_write_flush(static_cast<tinyusb_cdcacm_itf_t>(this->itf_), pdMS_TO_TICKS(100));
|
||||
}
|
||||
|
||||
void USBCDCACMInstance::check_logger_conflict() {}
|
||||
|
||||
} // namespace esphome::usb_cdc_acm
|
||||
#endif
|
||||
@@ -4,6 +4,7 @@ import gzip
|
||||
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import web_server_base
|
||||
from esphome.components.logger import request_log_listener
|
||||
from esphome.components.web_server_base import CONF_WEB_SERVER_BASE_ID
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
@@ -313,6 +314,8 @@ async def to_code(config):
|
||||
if config.get(CONF_OTA) is False:
|
||||
cg.add_define("USE_WEBSERVER_OTA_DISABLED")
|
||||
cg.add(var.set_expose_log(config[CONF_LOG]))
|
||||
if config[CONF_LOG]:
|
||||
request_log_listener() # Request a log listener slot for web server log streaming
|
||||
if config[CONF_ENABLE_PRIVATE_NETWORK_ACCESS]:
|
||||
cg.add_define("USE_WEBSERVER_PRIVATE_NETWORK_ACCESS")
|
||||
if CONF_AUTH in config:
|
||||
|
||||
@@ -624,7 +624,11 @@ async def wifi_disable_to_code(config, action_id, template_arg, args):
|
||||
|
||||
KEEP_SCAN_RESULTS_KEY = "wifi_keep_scan_results"
|
||||
RUNTIME_POWER_SAVE_KEY = "wifi_runtime_power_save"
|
||||
WIFI_LISTENERS_KEY = "wifi_listeners"
|
||||
# Keys for listener counts
|
||||
IP_STATE_LISTENERS_KEY = "wifi_ip_state_listeners"
|
||||
SCAN_RESULTS_LISTENERS_KEY = "wifi_scan_results_listeners"
|
||||
CONNECT_STATE_LISTENERS_KEY = "wifi_connect_state_listeners"
|
||||
POWER_SAVE_LISTENERS_KEY = "wifi_power_save_listeners"
|
||||
|
||||
|
||||
def request_wifi_scan_results():
|
||||
@@ -650,15 +654,28 @@ def enable_runtime_power_save_control():
|
||||
CORE.data[RUNTIME_POWER_SAVE_KEY] = True
|
||||
|
||||
|
||||
def request_wifi_listeners() -> None:
|
||||
"""Request that WiFi state listeners be compiled in.
|
||||
def request_wifi_ip_state_listener() -> None:
|
||||
"""Request an IP state listener slot."""
|
||||
CORE.data[IP_STATE_LISTENERS_KEY] = CORE.data.get(IP_STATE_LISTENERS_KEY, 0) + 1
|
||||
|
||||
Components that need to be notified about WiFi state changes (IP address changes,
|
||||
scan results, connection state) should call this function during their code generation.
|
||||
This enables the add_ip_state_listener(), add_scan_results_listener(),
|
||||
and add_connect_state_listener() APIs.
|
||||
"""
|
||||
CORE.data[WIFI_LISTENERS_KEY] = True
|
||||
|
||||
def request_wifi_scan_results_listener() -> None:
|
||||
"""Request a scan results listener slot."""
|
||||
CORE.data[SCAN_RESULTS_LISTENERS_KEY] = (
|
||||
CORE.data.get(SCAN_RESULTS_LISTENERS_KEY, 0) + 1
|
||||
)
|
||||
|
||||
|
||||
def request_wifi_connect_state_listener() -> None:
|
||||
"""Request a connect state listener slot."""
|
||||
CORE.data[CONNECT_STATE_LISTENERS_KEY] = (
|
||||
CORE.data.get(CONNECT_STATE_LISTENERS_KEY, 0) + 1
|
||||
)
|
||||
|
||||
|
||||
def request_wifi_power_save_listener() -> None:
|
||||
"""Request a power save listener slot."""
|
||||
CORE.data[POWER_SAVE_LISTENERS_KEY] = CORE.data.get(POWER_SAVE_LISTENERS_KEY, 0) + 1
|
||||
|
||||
|
||||
@coroutine_with_priority(CoroPriority.FINAL)
|
||||
@@ -670,8 +687,25 @@ async def final_step():
|
||||
)
|
||||
if CORE.data.get(RUNTIME_POWER_SAVE_KEY, False):
|
||||
cg.add_define("USE_WIFI_RUNTIME_POWER_SAVE")
|
||||
if CORE.data.get(WIFI_LISTENERS_KEY, False):
|
||||
cg.add_define("USE_WIFI_LISTENERS")
|
||||
|
||||
# Generate listener defines - each listener type has its own #ifdef
|
||||
ip_state_count = CORE.data.get(IP_STATE_LISTENERS_KEY, 0)
|
||||
scan_results_count = CORE.data.get(SCAN_RESULTS_LISTENERS_KEY, 0)
|
||||
connect_state_count = CORE.data.get(CONNECT_STATE_LISTENERS_KEY, 0)
|
||||
power_save_count = CORE.data.get(POWER_SAVE_LISTENERS_KEY, 0)
|
||||
|
||||
if ip_state_count:
|
||||
cg.add_define("USE_WIFI_IP_STATE_LISTENERS")
|
||||
cg.add_define("ESPHOME_WIFI_IP_STATE_LISTENERS", ip_state_count)
|
||||
if scan_results_count:
|
||||
cg.add_define("USE_WIFI_SCAN_RESULTS_LISTENERS")
|
||||
cg.add_define("ESPHOME_WIFI_SCAN_RESULTS_LISTENERS", scan_results_count)
|
||||
if connect_state_count:
|
||||
cg.add_define("USE_WIFI_CONNECT_STATE_LISTENERS")
|
||||
cg.add_define("ESPHOME_WIFI_CONNECT_STATE_LISTENERS", connect_state_count)
|
||||
if power_save_count:
|
||||
cg.add_define("USE_WIFI_POWER_SAVE_LISTENERS")
|
||||
cg.add_define("ESPHOME_WIFI_POWER_SAVE_LISTENERS", power_save_count)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
|
||||
@@ -275,6 +275,9 @@ struct LTWiFiEvent;
|
||||
*
|
||||
* Components can implement this interface to receive IP address updates
|
||||
* without the overhead of std::function callbacks.
|
||||
*
|
||||
* @note Components must call wifi.request_wifi_ip_state_listener() in their
|
||||
* Python to_code() to register for this listener type.
|
||||
*/
|
||||
class WiFiIPStateListener {
|
||||
public:
|
||||
@@ -286,6 +289,9 @@ class WiFiIPStateListener {
|
||||
*
|
||||
* Components can implement this interface to receive scan results
|
||||
* without the overhead of std::function callbacks.
|
||||
*
|
||||
* @note Components must call wifi.request_wifi_scan_results_listener() in their
|
||||
* Python to_code() to register for this listener type.
|
||||
*/
|
||||
class WiFiScanResultsListener {
|
||||
public:
|
||||
@@ -296,6 +302,9 @@ class WiFiScanResultsListener {
|
||||
*
|
||||
* Components can implement this interface to receive connection updates
|
||||
* without the overhead of std::function callbacks.
|
||||
*
|
||||
* @note Components must call wifi.request_wifi_connect_state_listener() in their
|
||||
* Python to_code() to register for this listener type.
|
||||
*/
|
||||
class WiFiConnectStateListener {
|
||||
public:
|
||||
@@ -306,6 +315,9 @@ class WiFiConnectStateListener {
|
||||
*
|
||||
* Components can implement this interface to receive power save mode updates
|
||||
* without the overhead of std::function callbacks.
|
||||
*
|
||||
* @note Components must call wifi.request_wifi_power_save_listener() in their
|
||||
* Python to_code() to register for this listener type.
|
||||
*/
|
||||
class WiFiPowerSaveListener {
|
||||
public:
|
||||
@@ -444,26 +456,32 @@ class WiFiComponent : public Component {
|
||||
|
||||
int32_t get_wifi_channel();
|
||||
|
||||
#ifdef USE_WIFI_LISTENERS
|
||||
#ifdef USE_WIFI_IP_STATE_LISTENERS
|
||||
/** Add a listener for IP state changes.
|
||||
* Listener receives: IP addresses, DNS address 1, DNS address 2
|
||||
*/
|
||||
void add_ip_state_listener(WiFiIPStateListener *listener) { this->ip_state_listeners_.push_back(listener); }
|
||||
#endif // USE_WIFI_IP_STATE_LISTENERS
|
||||
#ifdef USE_WIFI_SCAN_RESULTS_LISTENERS
|
||||
/// Add a listener for WiFi scan results
|
||||
void add_scan_results_listener(WiFiScanResultsListener *listener) {
|
||||
this->scan_results_listeners_.push_back(listener);
|
||||
}
|
||||
#endif // USE_WIFI_SCAN_RESULTS_LISTENERS
|
||||
#ifdef USE_WIFI_CONNECT_STATE_LISTENERS
|
||||
/** Add a listener for WiFi connection state changes.
|
||||
* Listener receives: SSID, BSSID
|
||||
*/
|
||||
void add_connect_state_listener(WiFiConnectStateListener *listener) {
|
||||
this->connect_state_listeners_.push_back(listener);
|
||||
}
|
||||
#endif // USE_WIFI_CONNECT_STATE_LISTENERS
|
||||
#ifdef USE_WIFI_POWER_SAVE_LISTENERS
|
||||
/** Add a listener for WiFi power save mode changes.
|
||||
* Listener receives: WiFiPowerSaveMode
|
||||
*/
|
||||
void add_power_save_listener(WiFiPowerSaveListener *listener) { this->power_save_listeners_.push_back(listener); }
|
||||
#endif // USE_WIFI_LISTENERS
|
||||
#endif // USE_WIFI_POWER_SAVE_LISTENERS
|
||||
|
||||
#ifdef USE_WIFI_RUNTIME_POWER_SAVE
|
||||
/** Request high-performance mode (no power saving) for improved WiFi latency.
|
||||
@@ -628,12 +646,18 @@ class WiFiComponent : public Component {
|
||||
WiFiAP ap_;
|
||||
#endif
|
||||
float output_power_{NAN};
|
||||
#ifdef USE_WIFI_LISTENERS
|
||||
std::vector<WiFiIPStateListener *> ip_state_listeners_;
|
||||
std::vector<WiFiScanResultsListener *> scan_results_listeners_;
|
||||
std::vector<WiFiConnectStateListener *> connect_state_listeners_;
|
||||
std::vector<WiFiPowerSaveListener *> power_save_listeners_;
|
||||
#endif // USE_WIFI_LISTENERS
|
||||
#ifdef USE_WIFI_IP_STATE_LISTENERS
|
||||
StaticVector<WiFiIPStateListener *, ESPHOME_WIFI_IP_STATE_LISTENERS> ip_state_listeners_;
|
||||
#endif
|
||||
#ifdef USE_WIFI_SCAN_RESULTS_LISTENERS
|
||||
StaticVector<WiFiScanResultsListener *, ESPHOME_WIFI_SCAN_RESULTS_LISTENERS> scan_results_listeners_;
|
||||
#endif
|
||||
#ifdef USE_WIFI_CONNECT_STATE_LISTENERS
|
||||
StaticVector<WiFiConnectStateListener *, ESPHOME_WIFI_CONNECT_STATE_LISTENERS> connect_state_listeners_;
|
||||
#endif
|
||||
#ifdef USE_WIFI_POWER_SAVE_LISTENERS
|
||||
StaticVector<WiFiPowerSaveListener *, ESPHOME_WIFI_POWER_SAVE_LISTENERS> power_save_listeners_;
|
||||
#endif
|
||||
ESPPreferenceObject pref_;
|
||||
#ifdef USE_WIFI_FAST_CONNECT
|
||||
ESPPreferenceObject fast_connect_pref_;
|
||||
|
||||
@@ -105,7 +105,7 @@ bool WiFiComponent::wifi_apply_power_save_() {
|
||||
}
|
||||
wifi_fpm_auto_sleep_set_in_null_mode(1);
|
||||
bool success = wifi_set_sleep_type(power_save);
|
||||
#ifdef USE_WIFI_LISTENERS
|
||||
#ifdef USE_WIFI_POWER_SAVE_LISTENERS
|
||||
if (success) {
|
||||
for (auto *listener : this->power_save_listeners_) {
|
||||
listener->on_wifi_power_save(this->power_save_);
|
||||
@@ -511,12 +511,13 @@ void WiFiComponent::wifi_event_callback(System_Event_t *event) {
|
||||
it.channel);
|
||||
#endif
|
||||
s_sta_connected = true;
|
||||
#ifdef USE_WIFI_LISTENERS
|
||||
#ifdef USE_WIFI_CONNECT_STATE_LISTENERS
|
||||
for (auto *listener : global_wifi_component->connect_state_listeners_) {
|
||||
listener->on_wifi_connect_state(StringRef(it.ssid, it.ssid_len), it.bssid);
|
||||
}
|
||||
#endif
|
||||
// For static IP configurations, GOT_IP event may not fire, so notify IP listeners here
|
||||
#ifdef USE_WIFI_MANUAL_IP
|
||||
#if defined(USE_WIFI_IP_STATE_LISTENERS) && defined(USE_WIFI_MANUAL_IP)
|
||||
if (const WiFiAP *config = global_wifi_component->get_selected_sta_();
|
||||
config && config->get_manual_ip().has_value()) {
|
||||
for (auto *listener : global_wifi_component->ip_state_listeners_) {
|
||||
@@ -524,7 +525,6 @@ void WiFiComponent::wifi_event_callback(System_Event_t *event) {
|
||||
global_wifi_component->get_dns_address(0), global_wifi_component->get_dns_address(1));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
@@ -543,7 +543,12 @@ void WiFiComponent::wifi_event_callback(System_Event_t *event) {
|
||||
}
|
||||
s_sta_connected = false;
|
||||
s_sta_connecting = false;
|
||||
#ifdef USE_WIFI_LISTENERS
|
||||
// IMPORTANT: Set error flag BEFORE notifying listeners.
|
||||
// This ensures is_connected() returns false during listener callbacks,
|
||||
// which is critical for proper reconnection logic (e.g., roaming).
|
||||
global_wifi_component->error_from_callback_ = true;
|
||||
#ifdef USE_WIFI_CONNECT_STATE_LISTENERS
|
||||
// Notify listeners AFTER setting error flag so they see correct state
|
||||
static constexpr uint8_t EMPTY_BSSID[6] = {};
|
||||
for (auto *listener : global_wifi_component->connect_state_listeners_) {
|
||||
listener->on_wifi_connect_state(StringRef(), EMPTY_BSSID);
|
||||
@@ -573,7 +578,7 @@ void WiFiComponent::wifi_event_callback(System_Event_t *event) {
|
||||
ESP_LOGV(TAG, "static_ip=%s gateway=%s netmask=%s", network::IPAddress(&it.ip).str_to(ip_buf),
|
||||
network::IPAddress(&it.gw).str_to(gw_buf), network::IPAddress(&it.mask).str_to(mask_buf));
|
||||
s_sta_got_ip = true;
|
||||
#ifdef USE_WIFI_LISTENERS
|
||||
#ifdef USE_WIFI_IP_STATE_LISTENERS
|
||||
for (auto *listener : global_wifi_component->ip_state_listeners_) {
|
||||
listener->on_ip_state(global_wifi_component->wifi_sta_ip_addresses(), global_wifi_component->get_dns_address(0),
|
||||
global_wifi_component->get_dns_address(1));
|
||||
@@ -635,10 +640,6 @@ void WiFiComponent::wifi_event_callback(System_Event_t *event) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (event->event == EVENT_STAMODE_DISCONNECTED) {
|
||||
global_wifi_component->error_from_callback_ = true;
|
||||
}
|
||||
|
||||
WiFiMockClass::_event_callback(event);
|
||||
}
|
||||
|
||||
@@ -770,7 +771,7 @@ void WiFiComponent::wifi_scan_done_callback_(void *arg, STATUS status) {
|
||||
it->is_hidden != 0);
|
||||
}
|
||||
this->scan_done_ = true;
|
||||
#ifdef USE_WIFI_LISTENERS
|
||||
#ifdef USE_WIFI_SCAN_RESULTS_LISTENERS
|
||||
for (auto *listener : global_wifi_component->scan_results_listeners_) {
|
||||
listener->on_wifi_scan_results(global_wifi_component->scan_result_);
|
||||
}
|
||||
|
||||
@@ -281,7 +281,7 @@ bool WiFiComponent::wifi_apply_power_save_() {
|
||||
break;
|
||||
}
|
||||
bool success = esp_wifi_set_ps(power_save) == ESP_OK;
|
||||
#ifdef USE_WIFI_LISTENERS
|
||||
#ifdef USE_WIFI_POWER_SAVE_LISTENERS
|
||||
if (success) {
|
||||
for (auto *listener : this->power_save_listeners_) {
|
||||
listener->on_wifi_power_save(this->power_save_);
|
||||
@@ -741,18 +741,18 @@ void WiFiComponent::wifi_process_event_(IDFWiFiEvent *data) {
|
||||
(const char *) it.ssid, bssid_buf, it.channel, get_auth_mode_str(it.authmode));
|
||||
#endif
|
||||
s_sta_connected = true;
|
||||
#ifdef USE_WIFI_LISTENERS
|
||||
#ifdef USE_WIFI_CONNECT_STATE_LISTENERS
|
||||
for (auto *listener : this->connect_state_listeners_) {
|
||||
listener->on_wifi_connect_state(StringRef(it.ssid, it.ssid_len), it.bssid);
|
||||
}
|
||||
#endif
|
||||
// For static IP configurations, GOT_IP event may not fire, so notify IP listeners here
|
||||
#ifdef USE_WIFI_MANUAL_IP
|
||||
#if defined(USE_WIFI_IP_STATE_LISTENERS) && defined(USE_WIFI_MANUAL_IP)
|
||||
if (const WiFiAP *config = this->get_selected_sta_(); config && config->get_manual_ip().has_value()) {
|
||||
for (auto *listener : this->ip_state_listeners_) {
|
||||
listener->on_ip_state(this->wifi_sta_ip_addresses(), this->get_dns_address(0), this->get_dns_address(1));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
} else if (data->event_base == WIFI_EVENT && data->event_id == WIFI_EVENT_STA_DISCONNECTED) {
|
||||
@@ -774,7 +774,7 @@ void WiFiComponent::wifi_process_event_(IDFWiFiEvent *data) {
|
||||
s_sta_connected = false;
|
||||
s_sta_connecting = false;
|
||||
error_from_callback_ = true;
|
||||
#ifdef USE_WIFI_LISTENERS
|
||||
#ifdef USE_WIFI_CONNECT_STATE_LISTENERS
|
||||
static constexpr uint8_t EMPTY_BSSID[6] = {};
|
||||
for (auto *listener : this->connect_state_listeners_) {
|
||||
listener->on_wifi_connect_state(StringRef(), EMPTY_BSSID);
|
||||
@@ -788,7 +788,7 @@ void WiFiComponent::wifi_process_event_(IDFWiFiEvent *data) {
|
||||
#endif /* USE_NETWORK_IPV6 */
|
||||
ESP_LOGV(TAG, "static_ip=" IPSTR " gateway=" IPSTR, IP2STR(&it.ip_info.ip), IP2STR(&it.ip_info.gw));
|
||||
this->got_ipv4_address_ = true;
|
||||
#ifdef USE_WIFI_LISTENERS
|
||||
#ifdef USE_WIFI_IP_STATE_LISTENERS
|
||||
for (auto *listener : this->ip_state_listeners_) {
|
||||
listener->on_ip_state(this->wifi_sta_ip_addresses(), this->get_dns_address(0), this->get_dns_address(1));
|
||||
}
|
||||
@@ -799,7 +799,7 @@ void WiFiComponent::wifi_process_event_(IDFWiFiEvent *data) {
|
||||
const auto &it = data->data.ip_got_ip6;
|
||||
ESP_LOGV(TAG, "IPv6 address=" IPV6STR, IPV62STR(it.ip6_info.ip));
|
||||
this->num_ipv6_addresses_++;
|
||||
#ifdef USE_WIFI_LISTENERS
|
||||
#ifdef USE_WIFI_IP_STATE_LISTENERS
|
||||
for (auto *listener : this->ip_state_listeners_) {
|
||||
listener->on_ip_state(this->wifi_sta_ip_addresses(), this->get_dns_address(0), this->get_dns_address(1));
|
||||
}
|
||||
@@ -843,7 +843,7 @@ void WiFiComponent::wifi_process_event_(IDFWiFiEvent *data) {
|
||||
scan_result_.emplace_back(bssid, ssid, record.primary, record.rssi, record.authmode != WIFI_AUTH_OPEN,
|
||||
ssid.empty());
|
||||
}
|
||||
#ifdef USE_WIFI_LISTENERS
|
||||
#ifdef USE_WIFI_SCAN_RESULTS_LISTENERS
|
||||
for (auto *listener : this->scan_results_listeners_) {
|
||||
listener->on_wifi_scan_results(this->scan_result_);
|
||||
}
|
||||
|
||||
@@ -144,7 +144,7 @@ bool WiFiComponent::wifi_sta_pre_setup_() {
|
||||
}
|
||||
bool WiFiComponent::wifi_apply_power_save_() {
|
||||
bool success = WiFi.setSleep(this->power_save_ != WIFI_POWER_SAVE_NONE);
|
||||
#ifdef USE_WIFI_LISTENERS
|
||||
#ifdef USE_WIFI_POWER_SAVE_LISTENERS
|
||||
if (success) {
|
||||
for (auto *listener : this->power_save_listeners_) {
|
||||
listener->on_wifi_power_save(this->power_save_);
|
||||
@@ -455,19 +455,19 @@ void WiFiComponent::wifi_process_event_(LTWiFiEvent *event) {
|
||||
// Note: We don't set CONNECTED state here yet - wait for GOT_IP
|
||||
// This matches ESP32 IDF behavior where s_sta_connected is set but
|
||||
// wifi_sta_connect_status_() also checks got_ipv4_address_
|
||||
#ifdef USE_WIFI_LISTENERS
|
||||
#ifdef USE_WIFI_CONNECT_STATE_LISTENERS
|
||||
for (auto *listener : this->connect_state_listeners_) {
|
||||
listener->on_wifi_connect_state(StringRef(it.ssid, it.ssid_len), it.bssid);
|
||||
}
|
||||
#endif
|
||||
// For static IP configurations, GOT_IP event may not fire, so notify IP listeners here
|
||||
#ifdef USE_WIFI_MANUAL_IP
|
||||
#if defined(USE_WIFI_IP_STATE_LISTENERS) && defined(USE_WIFI_MANUAL_IP)
|
||||
if (const WiFiAP *config = this->get_selected_sta_(); config && config->get_manual_ip().has_value()) {
|
||||
s_sta_state = LTWiFiSTAState::CONNECTED;
|
||||
for (auto *listener : this->ip_state_listeners_) {
|
||||
listener->on_ip_state(this->wifi_sta_ip_addresses(), this->get_dns_address(0), this->get_dns_address(1));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
@@ -521,7 +521,7 @@ void WiFiComponent::wifi_process_event_(LTWiFiEvent *event) {
|
||||
this->error_from_callback_ = true;
|
||||
}
|
||||
|
||||
#ifdef USE_WIFI_LISTENERS
|
||||
#ifdef USE_WIFI_CONNECT_STATE_LISTENERS
|
||||
static constexpr uint8_t EMPTY_BSSID[6] = {};
|
||||
for (auto *listener : this->connect_state_listeners_) {
|
||||
listener->on_wifi_connect_state(StringRef(), EMPTY_BSSID);
|
||||
@@ -547,7 +547,7 @@ void WiFiComponent::wifi_process_event_(LTWiFiEvent *event) {
|
||||
ESP_LOGV(TAG, "static_ip=%s gateway=%s", network::IPAddress(WiFi.localIP()).str_to(ip_buf),
|
||||
network::IPAddress(WiFi.gatewayIP()).str_to(gw_buf));
|
||||
s_sta_state = LTWiFiSTAState::CONNECTED;
|
||||
#ifdef USE_WIFI_LISTENERS
|
||||
#ifdef USE_WIFI_IP_STATE_LISTENERS
|
||||
for (auto *listener : this->ip_state_listeners_) {
|
||||
listener->on_ip_state(this->wifi_sta_ip_addresses(), this->get_dns_address(0), this->get_dns_address(1));
|
||||
}
|
||||
@@ -556,7 +556,7 @@ void WiFiComponent::wifi_process_event_(LTWiFiEvent *event) {
|
||||
}
|
||||
case ESPHOME_EVENT_ID_WIFI_STA_GOT_IP6: {
|
||||
ESP_LOGV(TAG, "Got IPv6");
|
||||
#ifdef USE_WIFI_LISTENERS
|
||||
#ifdef USE_WIFI_IP_STATE_LISTENERS
|
||||
for (auto *listener : this->ip_state_listeners_) {
|
||||
listener->on_ip_state(this->wifi_sta_ip_addresses(), this->get_dns_address(0), this->get_dns_address(1));
|
||||
}
|
||||
@@ -677,7 +677,7 @@ void WiFiComponent::wifi_scan_done_callback_() {
|
||||
ssid.length() == 0);
|
||||
}
|
||||
WiFi.scanDelete();
|
||||
#ifdef USE_WIFI_LISTENERS
|
||||
#ifdef USE_WIFI_SCAN_RESULTS_LISTENERS
|
||||
for (auto *listener : this->scan_results_listeners_) {
|
||||
listener->on_wifi_scan_results(this->scan_result_);
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user