mirror of
https://github.com/esphome/esphome.git
synced 2026-09-27 15:00:24 +00:00
Merge branch 'dev' into api-stringref-user-services
This commit is contained in:
+1
-1
@@ -1 +1 @@
|
||||
ce05c28e9dc0b12c4f6e7454986ffea5123ac974a949da841be698c535f2083e
|
||||
5eb1e5852765114ad06533220d3160b6c23f5ccefc4de41828699de5dfff5ad6
|
||||
|
||||
@@ -58,7 +58,7 @@ jobs:
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@45cbd0c69e560cd9e7cd7f8c32362050c9b7ded2 # v4.32.2
|
||||
uses: github/codeql-action/init@89a39a4e59826350b863aa6b6252a07ad50cf83e # v4.32.4
|
||||
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@45cbd0c69e560cd9e7cd7f8c32362050c9b7ded2 # v4.32.2
|
||||
uses: github/codeql-action/analyze@89a39a4e59826350b863aa6b6252a07ad50cf83e # v4.32.4
|
||||
with:
|
||||
category: "/language:${{matrix.language}}"
|
||||
|
||||
@@ -19,7 +19,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Stale
|
||||
uses: actions/stale@997185467fa4f803885201cee163a9f38240193d # v10.1.1
|
||||
uses: actions/stale@b5d41d4e1d5dceea10e7104786b73624c18a190f # v10.2.0
|
||||
with:
|
||||
debug-only: ${{ github.ref != 'refs/heads/dev' }} # Dry-run when not run on dev branch
|
||||
remove-stale-when-updated: true
|
||||
|
||||
@@ -11,7 +11,7 @@ ci:
|
||||
repos:
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: v0.15.0
|
||||
rev: v0.15.2
|
||||
hooks:
|
||||
# Run the linter.
|
||||
- id: ruff
|
||||
|
||||
@@ -411,6 +411,7 @@ esphome/components/rp2040_pwm/* @jesserockz
|
||||
esphome/components/rpi_dpi_rgb/* @clydebarrow
|
||||
esphome/components/rtl87xx/* @kuba2k2
|
||||
esphome/components/rtttl/* @glmnet
|
||||
esphome/components/runtime_image/* @clydebarrow @guillempages @kahrendt
|
||||
esphome/components/runtime_stats/* @bdraco
|
||||
esphome/components/rx8130/* @beormund
|
||||
esphome/components/safe_mode/* @jsuanet @kbx81 @paulmonigatti
|
||||
|
||||
@@ -944,12 +944,6 @@ def command_clean_all(args: ArgsProtocol) -> int | None:
|
||||
return 0
|
||||
|
||||
|
||||
def command_mqtt_fingerprint(args: ArgsProtocol, config: ConfigType) -> int | None:
|
||||
from esphome import mqtt
|
||||
|
||||
return mqtt.get_fingerprint(config)
|
||||
|
||||
|
||||
def command_version(args: ArgsProtocol) -> int | None:
|
||||
safe_print(f"Version: {const.__version__}")
|
||||
return 0
|
||||
@@ -1237,7 +1231,6 @@ POST_CONFIG_ACTIONS = {
|
||||
"run": command_run,
|
||||
"clean": command_clean,
|
||||
"clean-mqtt": command_clean_mqtt,
|
||||
"mqtt-fingerprint": command_mqtt_fingerprint,
|
||||
"idedata": command_idedata,
|
||||
"rename": command_rename,
|
||||
"discover": command_discover,
|
||||
@@ -1451,13 +1444,6 @@ def parse_args(argv):
|
||||
)
|
||||
parser_wizard.add_argument("configuration", help="Your YAML configuration file.")
|
||||
|
||||
parser_fingerprint = subparsers.add_parser(
|
||||
"mqtt-fingerprint", help="Get the SSL fingerprint from a MQTT broker."
|
||||
)
|
||||
parser_fingerprint.add_argument(
|
||||
"configuration", help="Your YAML configuration file(s).", nargs="+"
|
||||
)
|
||||
|
||||
subparsers.add_parser("version", help="Print the ESPHome version and exit.")
|
||||
|
||||
parser_clean = subparsers.add_parser(
|
||||
|
||||
@@ -60,6 +60,11 @@ static constexpr uint8_t MAX_MESSAGES_PER_LOOP = 5;
|
||||
static constexpr uint8_t MAX_PING_RETRIES = 60;
|
||||
static constexpr uint16_t PING_RETRY_INTERVAL = 1000;
|
||||
static constexpr uint32_t KEEPALIVE_DISCONNECT_TIMEOUT = (KEEPALIVE_TIMEOUT_MS * 5) / 2;
|
||||
// Timeout for completing the handshake (Noise transport + HelloRequest).
|
||||
// A stalled handshake from a buggy client or network glitch holds a connection
|
||||
// slot, which can prevent legitimate clients from reconnecting. Also hardens
|
||||
// against the less likely case of intentional connection slot exhaustion.
|
||||
static constexpr uint32_t HANDSHAKE_TIMEOUT_MS = 15000;
|
||||
|
||||
static constexpr auto ESPHOME_VERSION_REF = StringRef::from_lit(ESPHOME_VERSION);
|
||||
|
||||
@@ -205,7 +210,12 @@ void APIConnection::loop() {
|
||||
this->fatal_error_with_log_(LOG_STR("Reading failed"), err);
|
||||
return;
|
||||
} else {
|
||||
this->last_traffic_ = now;
|
||||
// Only update last_traffic_ after authentication to ensure the
|
||||
// handshake timeout is an absolute deadline from connection start.
|
||||
// Pre-auth messages (e.g. PingRequest) must not reset the timer.
|
||||
if (this->is_authenticated()) {
|
||||
this->last_traffic_ = now;
|
||||
}
|
||||
// read a packet
|
||||
this->read_message(buffer.data_len, buffer.type, buffer.data);
|
||||
if (this->flags_.remove)
|
||||
@@ -223,6 +233,15 @@ void APIConnection::loop() {
|
||||
this->process_active_iterator_();
|
||||
}
|
||||
|
||||
// Disconnect clients that haven't completed the handshake in time.
|
||||
// Stale half-open connections from buggy clients or network issues can
|
||||
// accumulate and block legitimate clients from reconnecting.
|
||||
if (!this->is_authenticated() && now - this->last_traffic_ > HANDSHAKE_TIMEOUT_MS) {
|
||||
this->on_fatal_error();
|
||||
this->log_client_(ESPHOME_LOG_LEVEL_WARN, LOG_STR("handshake timeout; disconnecting"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (this->flags_.sent_ping) {
|
||||
// Disconnect if not responded within 2.5*keepalive
|
||||
if (now - this->last_traffic_ > KEEPALIVE_DISCONNECT_TIMEOUT) {
|
||||
@@ -328,9 +347,7 @@ uint16_t APIConnection::encode_message_to_buffer(ProtoMessage &msg, uint8_t mess
|
||||
#endif
|
||||
|
||||
// Calculate size
|
||||
ProtoSize size_calc;
|
||||
msg.calculate_size(size_calc);
|
||||
uint32_t calculated_size = size_calc.get_size();
|
||||
uint32_t calculated_size = msg.calculated_size();
|
||||
|
||||
// Cache frame sizes to avoid repeated virtual calls
|
||||
const uint8_t header_padding = conn->helper_->frame_header_padding();
|
||||
@@ -358,19 +375,14 @@ uint16_t APIConnection::encode_message_to_buffer(ProtoMessage &msg, uint8_t mess
|
||||
shared_buf.resize(current_size + footer_size + header_padding);
|
||||
}
|
||||
|
||||
// Encode directly into buffer
|
||||
size_t size_before_encode = shared_buf.size();
|
||||
msg.encode({&shared_buf});
|
||||
// Pre-resize buffer to include payload, then encode through raw pointer
|
||||
size_t write_start = shared_buf.size();
|
||||
shared_buf.resize(write_start + calculated_size);
|
||||
ProtoWriteBuffer buffer{&shared_buf, write_start};
|
||||
msg.encode(buffer);
|
||||
|
||||
// Calculate actual encoded size (not including header that was already added)
|
||||
size_t actual_payload_size = shared_buf.size() - size_before_encode;
|
||||
|
||||
// Return actual total size (header + actual payload + footer)
|
||||
size_t actual_total_size = header_padding + actual_payload_size + footer_size;
|
||||
|
||||
// Verify that calculate_size() returned the correct value
|
||||
assert(calculated_size == actual_payload_size);
|
||||
return static_cast<uint16_t>(actual_total_size);
|
||||
// Return total size (header + payload + footer)
|
||||
return static_cast<uint16_t>(header_padding + calculated_size + footer_size);
|
||||
}
|
||||
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
@@ -1484,6 +1496,8 @@ void APIConnection::complete_authentication_() {
|
||||
}
|
||||
|
||||
this->flags_.connection_state = static_cast<uint8_t>(ConnectionState::AUTHENTICATED);
|
||||
// Reset traffic timer so keepalive starts from authentication, not connection start
|
||||
this->last_traffic_ = App.get_loop_component_start_time();
|
||||
this->log_client_(ESPHOME_LOG_LEVEL_DEBUG, LOG_STR("connected"));
|
||||
#ifdef USE_API_CLIENT_CONNECTED_TRIGGER
|
||||
{
|
||||
@@ -1513,6 +1527,12 @@ bool APIConnection::send_hello_response_(const HelloRequest &msg) {
|
||||
ESP_LOGV(TAG, "Hello from client: '%s' | %s | API Version %" PRIu16 ".%" PRIu16, this->helper_->get_client_name(),
|
||||
this->helper_->get_peername_to(peername), this->client_api_version_major_, this->client_api_version_minor_);
|
||||
|
||||
// TODO: Remove before 2026.8.0 (one version after get_object_id backward compat removal)
|
||||
if (!this->client_supports_api_version(1, 14)) {
|
||||
ESP_LOGW(TAG, "'%s' using outdated API %" PRIu16 ".%" PRIu16 ", update to 1.14+", this->helper_->get_client_name(),
|
||||
this->client_api_version_major_, this->client_api_version_minor_);
|
||||
}
|
||||
|
||||
HelloResponse resp;
|
||||
resp.api_version_major = 1;
|
||||
resp.api_version_minor = 14;
|
||||
@@ -1814,12 +1834,14 @@ bool APIConnection::try_to_clear_buffer(bool log_out_of_space) {
|
||||
return false;
|
||||
}
|
||||
bool APIConnection::send_message_impl(const ProtoMessage &msg, uint8_t message_type) {
|
||||
ProtoSize size;
|
||||
msg.calculate_size(size);
|
||||
uint32_t payload_size = msg.calculated_size();
|
||||
std::vector<uint8_t> &shared_buf = this->parent_->get_shared_buffer_ref();
|
||||
this->prepare_first_message_buffer(shared_buf, size.get_size());
|
||||
msg.encode({&shared_buf});
|
||||
return this->send_buffer({&shared_buf}, message_type);
|
||||
this->prepare_first_message_buffer(shared_buf, payload_size);
|
||||
size_t write_start = shared_buf.size();
|
||||
shared_buf.resize(write_start + payload_size);
|
||||
ProtoWriteBuffer buffer{&shared_buf, write_start};
|
||||
msg.encode(buffer);
|
||||
return this->send_buffer(ProtoWriteBuffer{&shared_buf}, message_type);
|
||||
}
|
||||
bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint8_t message_type) {
|
||||
const bool is_log_message = (message_type == SubscribeLogsResponse::MESSAGE_TYPE);
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace esphome::api {
|
||||
|
||||
static const char *const TAG = "api.noise";
|
||||
#ifdef USE_ESP8266
|
||||
static const char PROLOGUE_INIT[] PROGMEM = "NoiseAPIInit";
|
||||
static constexpr char PROLOGUE_INIT[] PROGMEM = "NoiseAPIInit";
|
||||
#else
|
||||
static const char *const PROLOGUE_INIT = "NoiseAPIInit";
|
||||
#endif
|
||||
@@ -475,7 +475,7 @@ APIError APINoiseFrameHelper::write_protobuf_messages(ProtoWriteBuffer buffer, s
|
||||
// buf_start[1], buf_start[2] to be set after encryption
|
||||
|
||||
// Write message header (to be encrypted)
|
||||
const uint8_t msg_offset = 3;
|
||||
constexpr uint8_t msg_offset = 3;
|
||||
buf_start[msg_offset] = static_cast<uint8_t>(msg.message_type >> 8); // type high byte
|
||||
buf_start[msg_offset + 1] = static_cast<uint8_t>(msg.message_type); // type low byte
|
||||
buf_start[msg_offset + 2] = static_cast<uint8_t>(msg.payload_size >> 8); // data_len high byte
|
||||
|
||||
@@ -31,7 +31,7 @@ bool HelloRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value)
|
||||
}
|
||||
return true;
|
||||
}
|
||||
void HelloResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
void HelloResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_uint32(1, this->api_version_major);
|
||||
buffer.encode_uint32(2, this->api_version_minor);
|
||||
buffer.encode_string(3, this->server_info);
|
||||
@@ -44,7 +44,7 @@ void HelloResponse::calculate_size(ProtoSize &size) const {
|
||||
size.add_length(1, this->name.size());
|
||||
}
|
||||
#ifdef USE_AREAS
|
||||
void AreaInfo::encode(ProtoWriteBuffer buffer) const {
|
||||
void AreaInfo::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_uint32(1, this->area_id);
|
||||
buffer.encode_string(2, this->name);
|
||||
}
|
||||
@@ -54,7 +54,7 @@ void AreaInfo::calculate_size(ProtoSize &size) const {
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_DEVICES
|
||||
void DeviceInfo::encode(ProtoWriteBuffer buffer) const {
|
||||
void DeviceInfo::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_uint32(1, this->device_id);
|
||||
buffer.encode_string(2, this->name);
|
||||
buffer.encode_uint32(3, this->area_id);
|
||||
@@ -65,7 +65,7 @@ void DeviceInfo::calculate_size(ProtoSize &size) const {
|
||||
size.add_uint32(1, this->area_id);
|
||||
}
|
||||
#endif
|
||||
void DeviceInfoResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
void DeviceInfoResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_string(2, this->name);
|
||||
buffer.encode_string(3, this->mac_address);
|
||||
buffer.encode_string(4, this->esphome_version);
|
||||
@@ -111,7 +111,7 @@ void DeviceInfoResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_AREAS
|
||||
buffer.encode_message(22, this->area);
|
||||
buffer.encode_message(22, this->area, false);
|
||||
#endif
|
||||
#ifdef USE_ZWAVE_PROXY
|
||||
buffer.encode_uint32(23, this->zwave_proxy_feature_flags);
|
||||
@@ -176,7 +176,7 @@ void DeviceInfoResponse::calculate_size(ProtoSize &size) const {
|
||||
#endif
|
||||
}
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
void ListEntitiesBinarySensorResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
void ListEntitiesBinarySensorResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_string(1, this->object_id);
|
||||
buffer.encode_fixed32(2, this->key);
|
||||
buffer.encode_string(3, this->name);
|
||||
@@ -206,7 +206,7 @@ void ListEntitiesBinarySensorResponse::calculate_size(ProtoSize &size) const {
|
||||
size.add_uint32(1, this->device_id);
|
||||
#endif
|
||||
}
|
||||
void BinarySensorStateResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
void BinarySensorStateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_fixed32(1, this->key);
|
||||
buffer.encode_bool(2, this->state);
|
||||
buffer.encode_bool(3, this->missing_state);
|
||||
@@ -224,7 +224,7 @@ void BinarySensorStateResponse::calculate_size(ProtoSize &size) const {
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_COVER
|
||||
void ListEntitiesCoverResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
void ListEntitiesCoverResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_string(1, this->object_id);
|
||||
buffer.encode_fixed32(2, this->key);
|
||||
buffer.encode_string(3, this->name);
|
||||
@@ -260,7 +260,7 @@ void ListEntitiesCoverResponse::calculate_size(ProtoSize &size) const {
|
||||
size.add_uint32(1, this->device_id);
|
||||
#endif
|
||||
}
|
||||
void CoverStateResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
void CoverStateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_fixed32(1, this->key);
|
||||
buffer.encode_float(3, this->position);
|
||||
buffer.encode_float(4, this->tilt);
|
||||
@@ -317,7 +317,7 @@ bool CoverCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_FAN
|
||||
void ListEntitiesFanResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
void ListEntitiesFanResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_string(1, this->object_id);
|
||||
buffer.encode_fixed32(2, this->key);
|
||||
buffer.encode_string(3, this->name);
|
||||
@@ -359,7 +359,7 @@ void ListEntitiesFanResponse::calculate_size(ProtoSize &size) const {
|
||||
size.add_uint32(1, this->device_id);
|
||||
#endif
|
||||
}
|
||||
void FanStateResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
void FanStateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_fixed32(1, this->key);
|
||||
buffer.encode_bool(2, this->state);
|
||||
buffer.encode_bool(3, this->oscillating);
|
||||
@@ -443,7 +443,7 @@ bool FanCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_LIGHT
|
||||
void ListEntitiesLightResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
void ListEntitiesLightResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_string(1, this->object_id);
|
||||
buffer.encode_fixed32(2, this->key);
|
||||
buffer.encode_string(3, this->name);
|
||||
@@ -489,7 +489,7 @@ void ListEntitiesLightResponse::calculate_size(ProtoSize &size) const {
|
||||
size.add_uint32(2, this->device_id);
|
||||
#endif
|
||||
}
|
||||
void LightStateResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
void LightStateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_fixed32(1, this->key);
|
||||
buffer.encode_bool(2, this->state);
|
||||
buffer.encode_float(3, this->brightness);
|
||||
@@ -635,7 +635,7 @@ bool LightCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_SENSOR
|
||||
void ListEntitiesSensorResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
void ListEntitiesSensorResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_string(1, this->object_id);
|
||||
buffer.encode_fixed32(2, this->key);
|
||||
buffer.encode_string(3, this->name);
|
||||
@@ -671,7 +671,7 @@ void ListEntitiesSensorResponse::calculate_size(ProtoSize &size) const {
|
||||
size.add_uint32(1, this->device_id);
|
||||
#endif
|
||||
}
|
||||
void SensorStateResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
void SensorStateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_fixed32(1, this->key);
|
||||
buffer.encode_float(2, this->state);
|
||||
buffer.encode_bool(3, this->missing_state);
|
||||
@@ -689,7 +689,7 @@ void SensorStateResponse::calculate_size(ProtoSize &size) const {
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_SWITCH
|
||||
void ListEntitiesSwitchResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
void ListEntitiesSwitchResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_string(1, this->object_id);
|
||||
buffer.encode_fixed32(2, this->key);
|
||||
buffer.encode_string(3, this->name);
|
||||
@@ -719,7 +719,7 @@ void ListEntitiesSwitchResponse::calculate_size(ProtoSize &size) const {
|
||||
size.add_uint32(1, this->device_id);
|
||||
#endif
|
||||
}
|
||||
void SwitchStateResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
void SwitchStateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_fixed32(1, this->key);
|
||||
buffer.encode_bool(2, this->state);
|
||||
#ifdef USE_DEVICES
|
||||
@@ -760,7 +760,7 @@ bool SwitchCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
void ListEntitiesTextSensorResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
void ListEntitiesTextSensorResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_string(1, this->object_id);
|
||||
buffer.encode_fixed32(2, this->key);
|
||||
buffer.encode_string(3, this->name);
|
||||
@@ -788,7 +788,7 @@ void ListEntitiesTextSensorResponse::calculate_size(ProtoSize &size) const {
|
||||
size.add_uint32(1, this->device_id);
|
||||
#endif
|
||||
}
|
||||
void TextSensorStateResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
void TextSensorStateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_fixed32(1, this->key);
|
||||
buffer.encode_string(2, this->state);
|
||||
buffer.encode_bool(3, this->missing_state);
|
||||
@@ -818,7 +818,7 @@ bool SubscribeLogsRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
}
|
||||
return true;
|
||||
}
|
||||
void SubscribeLogsResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
void SubscribeLogsResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_uint32(1, static_cast<uint32_t>(this->level));
|
||||
buffer.encode_bytes(3, this->message_ptr_, this->message_len_);
|
||||
}
|
||||
@@ -839,11 +839,11 @@ bool NoiseEncryptionSetKeyRequest::decode_length(uint32_t field_id, ProtoLengthD
|
||||
}
|
||||
return true;
|
||||
}
|
||||
void NoiseEncryptionSetKeyResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(1, this->success); }
|
||||
void NoiseEncryptionSetKeyResponse::encode(ProtoWriteBuffer &buffer) const { buffer.encode_bool(1, this->success); }
|
||||
void NoiseEncryptionSetKeyResponse::calculate_size(ProtoSize &size) const { size.add_bool(1, this->success); }
|
||||
#endif
|
||||
#ifdef USE_API_HOMEASSISTANT_SERVICES
|
||||
void HomeassistantServiceMap::encode(ProtoWriteBuffer buffer) const {
|
||||
void HomeassistantServiceMap::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_string(1, this->key);
|
||||
buffer.encode_string(2, this->value);
|
||||
}
|
||||
@@ -851,7 +851,7 @@ void HomeassistantServiceMap::calculate_size(ProtoSize &size) const {
|
||||
size.add_length(1, this->key.size());
|
||||
size.add_length(1, this->value.size());
|
||||
}
|
||||
void HomeassistantActionRequest::encode(ProtoWriteBuffer buffer) const {
|
||||
void HomeassistantActionRequest::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_string(1, this->service);
|
||||
for (auto &it : this->data) {
|
||||
buffer.encode_message(2, it);
|
||||
@@ -924,7 +924,7 @@ bool HomeassistantActionResponse::decode_length(uint32_t field_id, ProtoLengthDe
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_API_HOMEASSISTANT_STATES
|
||||
void SubscribeHomeAssistantStateResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
void SubscribeHomeAssistantStateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_string(1, this->entity_id);
|
||||
buffer.encode_string(2, this->attribute);
|
||||
buffer.encode_bool(3, this->once);
|
||||
@@ -982,7 +982,7 @@ bool GetTimeResponse::decode_32bit(uint32_t field_id, Proto32Bit value) {
|
||||
return true;
|
||||
}
|
||||
#ifdef USE_API_USER_DEFINED_ACTIONS
|
||||
void ListEntitiesServicesArgument::encode(ProtoWriteBuffer buffer) const {
|
||||
void ListEntitiesServicesArgument::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_string(1, this->name);
|
||||
buffer.encode_uint32(2, static_cast<uint32_t>(this->type));
|
||||
}
|
||||
@@ -990,7 +990,7 @@ void ListEntitiesServicesArgument::calculate_size(ProtoSize &size) const {
|
||||
size.add_length(1, this->name.size());
|
||||
size.add_uint32(1, static_cast<uint32_t>(this->type));
|
||||
}
|
||||
void ListEntitiesServicesResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
void ListEntitiesServicesResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_string(1, this->name);
|
||||
buffer.encode_fixed32(2, this->key);
|
||||
for (auto &it : this->args) {
|
||||
@@ -1112,7 +1112,7 @@ void ExecuteServiceRequest::decode(const uint8_t *buffer, size_t length) {
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES
|
||||
void ExecuteServiceResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
void ExecuteServiceResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_uint32(1, this->call_id);
|
||||
buffer.encode_bool(2, this->success);
|
||||
buffer.encode_string(3, this->error_message);
|
||||
@@ -1130,7 +1130,7 @@ void ExecuteServiceResponse::calculate_size(ProtoSize &size) const {
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_CAMERA
|
||||
void ListEntitiesCameraResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
void ListEntitiesCameraResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_string(1, this->object_id);
|
||||
buffer.encode_fixed32(2, this->key);
|
||||
buffer.encode_string(3, this->name);
|
||||
@@ -1156,7 +1156,7 @@ void ListEntitiesCameraResponse::calculate_size(ProtoSize &size) const {
|
||||
size.add_uint32(1, this->device_id);
|
||||
#endif
|
||||
}
|
||||
void CameraImageResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
void CameraImageResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_fixed32(1, this->key);
|
||||
buffer.encode_bytes(2, this->data_ptr_, this->data_len_);
|
||||
buffer.encode_bool(3, this->done);
|
||||
@@ -1187,7 +1187,7 @@ bool CameraImageRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_CLIMATE
|
||||
void ListEntitiesClimateResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
void ListEntitiesClimateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_string(1, this->object_id);
|
||||
buffer.encode_fixed32(2, this->key);
|
||||
buffer.encode_string(3, this->name);
|
||||
@@ -1285,7 +1285,7 @@ void ListEntitiesClimateResponse::calculate_size(ProtoSize &size) const {
|
||||
#endif
|
||||
size.add_uint32(2, this->feature_flags);
|
||||
}
|
||||
void ClimateStateResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
void ClimateStateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_fixed32(1, this->key);
|
||||
buffer.encode_uint32(2, static_cast<uint32_t>(this->mode));
|
||||
buffer.encode_float(3, this->current_temperature);
|
||||
@@ -1416,7 +1416,7 @@ bool ClimateCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_WATER_HEATER
|
||||
void ListEntitiesWaterHeaterResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
void ListEntitiesWaterHeaterResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_string(1, this->object_id);
|
||||
buffer.encode_fixed32(2, this->key);
|
||||
buffer.encode_string(3, this->name);
|
||||
@@ -1458,7 +1458,7 @@ void ListEntitiesWaterHeaterResponse::calculate_size(ProtoSize &size) const {
|
||||
}
|
||||
size.add_uint32(1, this->supported_features);
|
||||
}
|
||||
void WaterHeaterStateResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
void WaterHeaterStateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_fixed32(1, this->key);
|
||||
buffer.encode_float(2, this->current_temperature);
|
||||
buffer.encode_float(3, this->target_temperature);
|
||||
@@ -1524,7 +1524,7 @@ bool WaterHeaterCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_NUMBER
|
||||
void ListEntitiesNumberResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
void ListEntitiesNumberResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_string(1, this->object_id);
|
||||
buffer.encode_fixed32(2, this->key);
|
||||
buffer.encode_string(3, this->name);
|
||||
@@ -1562,7 +1562,7 @@ void ListEntitiesNumberResponse::calculate_size(ProtoSize &size) const {
|
||||
size.add_uint32(1, this->device_id);
|
||||
#endif
|
||||
}
|
||||
void NumberStateResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
void NumberStateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_fixed32(1, this->key);
|
||||
buffer.encode_float(2, this->state);
|
||||
buffer.encode_bool(3, this->missing_state);
|
||||
@@ -1605,7 +1605,7 @@ bool NumberCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_SELECT
|
||||
void ListEntitiesSelectResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
void ListEntitiesSelectResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_string(1, this->object_id);
|
||||
buffer.encode_fixed32(2, this->key);
|
||||
buffer.encode_string(3, this->name);
|
||||
@@ -1639,7 +1639,7 @@ void ListEntitiesSelectResponse::calculate_size(ProtoSize &size) const {
|
||||
size.add_uint32(1, this->device_id);
|
||||
#endif
|
||||
}
|
||||
void SelectStateResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
void SelectStateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_fixed32(1, this->key);
|
||||
buffer.encode_string(2, this->state);
|
||||
buffer.encode_bool(3, this->missing_state);
|
||||
@@ -1690,7 +1690,7 @@ bool SelectCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_SIREN
|
||||
void ListEntitiesSirenResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
void ListEntitiesSirenResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_string(1, this->object_id);
|
||||
buffer.encode_fixed32(2, this->key);
|
||||
buffer.encode_string(3, this->name);
|
||||
@@ -1728,7 +1728,7 @@ void ListEntitiesSirenResponse::calculate_size(ProtoSize &size) const {
|
||||
size.add_uint32(1, this->device_id);
|
||||
#endif
|
||||
}
|
||||
void SirenStateResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
void SirenStateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_fixed32(1, this->key);
|
||||
buffer.encode_bool(2, this->state);
|
||||
#ifdef USE_DEVICES
|
||||
@@ -1798,7 +1798,7 @@ bool SirenCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_LOCK
|
||||
void ListEntitiesLockResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
void ListEntitiesLockResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_string(1, this->object_id);
|
||||
buffer.encode_fixed32(2, this->key);
|
||||
buffer.encode_string(3, this->name);
|
||||
@@ -1832,7 +1832,7 @@ void ListEntitiesLockResponse::calculate_size(ProtoSize &size) const {
|
||||
size.add_uint32(1, this->device_id);
|
||||
#endif
|
||||
}
|
||||
void LockStateResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
void LockStateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_fixed32(1, this->key);
|
||||
buffer.encode_uint32(2, static_cast<uint32_t>(this->state));
|
||||
#ifdef USE_DEVICES
|
||||
@@ -1887,7 +1887,7 @@ bool LockCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_BUTTON
|
||||
void ListEntitiesButtonResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
void ListEntitiesButtonResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_string(1, this->object_id);
|
||||
buffer.encode_fixed32(2, this->key);
|
||||
buffer.encode_string(3, this->name);
|
||||
@@ -1939,7 +1939,7 @@ bool ButtonCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_MEDIA_PLAYER
|
||||
void MediaPlayerSupportedFormat::encode(ProtoWriteBuffer buffer) const {
|
||||
void MediaPlayerSupportedFormat::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_string(1, this->format);
|
||||
buffer.encode_uint32(2, this->sample_rate);
|
||||
buffer.encode_uint32(3, this->num_channels);
|
||||
@@ -1953,7 +1953,7 @@ void MediaPlayerSupportedFormat::calculate_size(ProtoSize &size) const {
|
||||
size.add_uint32(1, static_cast<uint32_t>(this->purpose));
|
||||
size.add_uint32(1, this->sample_bytes);
|
||||
}
|
||||
void ListEntitiesMediaPlayerResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
void ListEntitiesMediaPlayerResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_string(1, this->object_id);
|
||||
buffer.encode_fixed32(2, this->key);
|
||||
buffer.encode_string(3, this->name);
|
||||
@@ -1987,7 +1987,7 @@ void ListEntitiesMediaPlayerResponse::calculate_size(ProtoSize &size) const {
|
||||
#endif
|
||||
size.add_uint32(1, this->feature_flags);
|
||||
}
|
||||
void MediaPlayerStateResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
void MediaPlayerStateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_fixed32(1, this->key);
|
||||
buffer.encode_uint32(2, static_cast<uint32_t>(this->state));
|
||||
buffer.encode_float(3, this->volume);
|
||||
@@ -2071,7 +2071,7 @@ bool SubscribeBluetoothLEAdvertisementsRequest::decode_varint(uint32_t field_id,
|
||||
}
|
||||
return true;
|
||||
}
|
||||
void BluetoothLERawAdvertisement::encode(ProtoWriteBuffer buffer) const {
|
||||
void BluetoothLERawAdvertisement::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_uint64(1, this->address);
|
||||
buffer.encode_sint32(2, this->rssi);
|
||||
buffer.encode_uint32(3, this->address_type);
|
||||
@@ -2083,7 +2083,7 @@ void BluetoothLERawAdvertisement::calculate_size(ProtoSize &size) const {
|
||||
size.add_uint32(1, this->address_type);
|
||||
size.add_length(1, this->data_len);
|
||||
}
|
||||
void BluetoothLERawAdvertisementsResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
void BluetoothLERawAdvertisementsResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
for (uint16_t i = 0; i < this->advertisements_len; i++) {
|
||||
buffer.encode_message(1, this->advertisements[i]);
|
||||
}
|
||||
@@ -2112,7 +2112,7 @@ bool BluetoothDeviceRequest::decode_varint(uint32_t field_id, ProtoVarInt value)
|
||||
}
|
||||
return true;
|
||||
}
|
||||
void BluetoothDeviceConnectionResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
void BluetoothDeviceConnectionResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_uint64(1, this->address);
|
||||
buffer.encode_bool(2, this->connected);
|
||||
buffer.encode_uint32(3, this->mtu);
|
||||
@@ -2134,7 +2134,7 @@ bool BluetoothGATTGetServicesRequest::decode_varint(uint32_t field_id, ProtoVarI
|
||||
}
|
||||
return true;
|
||||
}
|
||||
void BluetoothGATTDescriptor::encode(ProtoWriteBuffer buffer) const {
|
||||
void BluetoothGATTDescriptor::encode(ProtoWriteBuffer &buffer) const {
|
||||
if (this->uuid[0] != 0 || this->uuid[1] != 0) {
|
||||
buffer.encode_uint64(1, this->uuid[0], true);
|
||||
buffer.encode_uint64(1, this->uuid[1], true);
|
||||
@@ -2150,7 +2150,7 @@ void BluetoothGATTDescriptor::calculate_size(ProtoSize &size) const {
|
||||
size.add_uint32(1, this->handle);
|
||||
size.add_uint32(1, this->short_uuid);
|
||||
}
|
||||
void BluetoothGATTCharacteristic::encode(ProtoWriteBuffer buffer) const {
|
||||
void BluetoothGATTCharacteristic::encode(ProtoWriteBuffer &buffer) const {
|
||||
if (this->uuid[0] != 0 || this->uuid[1] != 0) {
|
||||
buffer.encode_uint64(1, this->uuid[0], true);
|
||||
buffer.encode_uint64(1, this->uuid[1], true);
|
||||
@@ -2172,7 +2172,7 @@ void BluetoothGATTCharacteristic::calculate_size(ProtoSize &size) const {
|
||||
size.add_repeated_message(1, this->descriptors);
|
||||
size.add_uint32(1, this->short_uuid);
|
||||
}
|
||||
void BluetoothGATTService::encode(ProtoWriteBuffer buffer) const {
|
||||
void BluetoothGATTService::encode(ProtoWriteBuffer &buffer) const {
|
||||
if (this->uuid[0] != 0 || this->uuid[1] != 0) {
|
||||
buffer.encode_uint64(1, this->uuid[0], true);
|
||||
buffer.encode_uint64(1, this->uuid[1], true);
|
||||
@@ -2192,7 +2192,7 @@ void BluetoothGATTService::calculate_size(ProtoSize &size) const {
|
||||
size.add_repeated_message(1, this->characteristics);
|
||||
size.add_uint32(1, this->short_uuid);
|
||||
}
|
||||
void BluetoothGATTGetServicesResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
void BluetoothGATTGetServicesResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_uint64(1, this->address);
|
||||
for (auto &it : this->services) {
|
||||
buffer.encode_message(2, it);
|
||||
@@ -2202,7 +2202,7 @@ void BluetoothGATTGetServicesResponse::calculate_size(ProtoSize &size) const {
|
||||
size.add_uint64(1, this->address);
|
||||
size.add_repeated_message(1, this->services);
|
||||
}
|
||||
void BluetoothGATTGetServicesDoneResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
void BluetoothGATTGetServicesDoneResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_uint64(1, this->address);
|
||||
}
|
||||
void BluetoothGATTGetServicesDoneResponse::calculate_size(ProtoSize &size) const { size.add_uint64(1, this->address); }
|
||||
@@ -2219,7 +2219,7 @@ bool BluetoothGATTReadRequest::decode_varint(uint32_t field_id, ProtoVarInt valu
|
||||
}
|
||||
return true;
|
||||
}
|
||||
void BluetoothGATTReadResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
void BluetoothGATTReadResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_uint64(1, this->address);
|
||||
buffer.encode_uint32(2, this->handle);
|
||||
buffer.encode_bytes(3, this->data_ptr_, this->data_len_);
|
||||
@@ -2311,7 +2311,7 @@ bool BluetoothGATTNotifyRequest::decode_varint(uint32_t field_id, ProtoVarInt va
|
||||
}
|
||||
return true;
|
||||
}
|
||||
void BluetoothGATTNotifyDataResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
void BluetoothGATTNotifyDataResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_uint64(1, this->address);
|
||||
buffer.encode_uint32(2, this->handle);
|
||||
buffer.encode_bytes(3, this->data_ptr_, this->data_len_);
|
||||
@@ -2321,7 +2321,7 @@ void BluetoothGATTNotifyDataResponse::calculate_size(ProtoSize &size) const {
|
||||
size.add_uint32(1, this->handle);
|
||||
size.add_length(1, this->data_len_);
|
||||
}
|
||||
void BluetoothConnectionsFreeResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
void BluetoothConnectionsFreeResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_uint32(1, this->free);
|
||||
buffer.encode_uint32(2, this->limit);
|
||||
for (const auto &it : this->allocated) {
|
||||
@@ -2339,7 +2339,7 @@ void BluetoothConnectionsFreeResponse::calculate_size(ProtoSize &size) const {
|
||||
}
|
||||
}
|
||||
}
|
||||
void BluetoothGATTErrorResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
void BluetoothGATTErrorResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_uint64(1, this->address);
|
||||
buffer.encode_uint32(2, this->handle);
|
||||
buffer.encode_int32(3, this->error);
|
||||
@@ -2349,7 +2349,7 @@ void BluetoothGATTErrorResponse::calculate_size(ProtoSize &size) const {
|
||||
size.add_uint32(1, this->handle);
|
||||
size.add_int32(1, this->error);
|
||||
}
|
||||
void BluetoothGATTWriteResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
void BluetoothGATTWriteResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_uint64(1, this->address);
|
||||
buffer.encode_uint32(2, this->handle);
|
||||
}
|
||||
@@ -2357,7 +2357,7 @@ void BluetoothGATTWriteResponse::calculate_size(ProtoSize &size) const {
|
||||
size.add_uint64(1, this->address);
|
||||
size.add_uint32(1, this->handle);
|
||||
}
|
||||
void BluetoothGATTNotifyResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
void BluetoothGATTNotifyResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_uint64(1, this->address);
|
||||
buffer.encode_uint32(2, this->handle);
|
||||
}
|
||||
@@ -2365,7 +2365,7 @@ void BluetoothGATTNotifyResponse::calculate_size(ProtoSize &size) const {
|
||||
size.add_uint64(1, this->address);
|
||||
size.add_uint32(1, this->handle);
|
||||
}
|
||||
void BluetoothDevicePairingResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
void BluetoothDevicePairingResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_uint64(1, this->address);
|
||||
buffer.encode_bool(2, this->paired);
|
||||
buffer.encode_int32(3, this->error);
|
||||
@@ -2375,7 +2375,7 @@ void BluetoothDevicePairingResponse::calculate_size(ProtoSize &size) const {
|
||||
size.add_bool(1, this->paired);
|
||||
size.add_int32(1, this->error);
|
||||
}
|
||||
void BluetoothDeviceUnpairingResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
void BluetoothDeviceUnpairingResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_uint64(1, this->address);
|
||||
buffer.encode_bool(2, this->success);
|
||||
buffer.encode_int32(3, this->error);
|
||||
@@ -2385,7 +2385,7 @@ void BluetoothDeviceUnpairingResponse::calculate_size(ProtoSize &size) const {
|
||||
size.add_bool(1, this->success);
|
||||
size.add_int32(1, this->error);
|
||||
}
|
||||
void BluetoothDeviceClearCacheResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
void BluetoothDeviceClearCacheResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_uint64(1, this->address);
|
||||
buffer.encode_bool(2, this->success);
|
||||
buffer.encode_int32(3, this->error);
|
||||
@@ -2395,7 +2395,7 @@ void BluetoothDeviceClearCacheResponse::calculate_size(ProtoSize &size) const {
|
||||
size.add_bool(1, this->success);
|
||||
size.add_int32(1, this->error);
|
||||
}
|
||||
void BluetoothScannerStateResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
void BluetoothScannerStateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_uint32(1, static_cast<uint32_t>(this->state));
|
||||
buffer.encode_uint32(2, static_cast<uint32_t>(this->mode));
|
||||
buffer.encode_uint32(3, static_cast<uint32_t>(this->configured_mode));
|
||||
@@ -2430,7 +2430,7 @@ bool SubscribeVoiceAssistantRequest::decode_varint(uint32_t field_id, ProtoVarIn
|
||||
}
|
||||
return true;
|
||||
}
|
||||
void VoiceAssistantAudioSettings::encode(ProtoWriteBuffer buffer) const {
|
||||
void VoiceAssistantAudioSettings::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_uint32(1, this->noise_suppression_level);
|
||||
buffer.encode_uint32(2, this->auto_gain);
|
||||
buffer.encode_float(3, this->volume_multiplier);
|
||||
@@ -2440,11 +2440,11 @@ void VoiceAssistantAudioSettings::calculate_size(ProtoSize &size) const {
|
||||
size.add_uint32(1, this->auto_gain);
|
||||
size.add_float(1, this->volume_multiplier);
|
||||
}
|
||||
void VoiceAssistantRequest::encode(ProtoWriteBuffer buffer) const {
|
||||
void VoiceAssistantRequest::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_bool(1, this->start);
|
||||
buffer.encode_string(2, this->conversation_id);
|
||||
buffer.encode_uint32(3, this->flags);
|
||||
buffer.encode_message(4, this->audio_settings);
|
||||
buffer.encode_message(4, this->audio_settings, false);
|
||||
buffer.encode_string(5, this->wake_word_phrase);
|
||||
}
|
||||
void VoiceAssistantRequest::calculate_size(ProtoSize &size) const {
|
||||
@@ -2525,7 +2525,7 @@ bool VoiceAssistantAudio::decode_length(uint32_t field_id, ProtoLengthDelimited
|
||||
}
|
||||
return true;
|
||||
}
|
||||
void VoiceAssistantAudio::encode(ProtoWriteBuffer buffer) const {
|
||||
void VoiceAssistantAudio::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_bytes(1, this->data, this->data_len);
|
||||
buffer.encode_bool(2, this->end);
|
||||
}
|
||||
@@ -2596,9 +2596,9 @@ bool VoiceAssistantAnnounceRequest::decode_length(uint32_t field_id, ProtoLength
|
||||
}
|
||||
return true;
|
||||
}
|
||||
void VoiceAssistantAnnounceFinished::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(1, this->success); }
|
||||
void VoiceAssistantAnnounceFinished::encode(ProtoWriteBuffer &buffer) const { buffer.encode_bool(1, this->success); }
|
||||
void VoiceAssistantAnnounceFinished::calculate_size(ProtoSize &size) const { size.add_bool(1, this->success); }
|
||||
void VoiceAssistantWakeWord::encode(ProtoWriteBuffer buffer) const {
|
||||
void VoiceAssistantWakeWord::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_string(1, this->id);
|
||||
buffer.encode_string(2, this->wake_word);
|
||||
for (auto &it : this->trained_languages) {
|
||||
@@ -2665,7 +2665,7 @@ bool VoiceAssistantConfigurationRequest::decode_length(uint32_t field_id, ProtoL
|
||||
}
|
||||
return true;
|
||||
}
|
||||
void VoiceAssistantConfigurationResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
void VoiceAssistantConfigurationResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
for (auto &it : this->available_wake_words) {
|
||||
buffer.encode_message(1, it);
|
||||
}
|
||||
@@ -2695,7 +2695,7 @@ bool VoiceAssistantSetConfiguration::decode_length(uint32_t field_id, ProtoLengt
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_ALARM_CONTROL_PANEL
|
||||
void ListEntitiesAlarmControlPanelResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
void ListEntitiesAlarmControlPanelResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_string(1, this->object_id);
|
||||
buffer.encode_fixed32(2, this->key);
|
||||
buffer.encode_string(3, this->name);
|
||||
@@ -2727,7 +2727,7 @@ void ListEntitiesAlarmControlPanelResponse::calculate_size(ProtoSize &size) cons
|
||||
size.add_uint32(1, this->device_id);
|
||||
#endif
|
||||
}
|
||||
void AlarmControlPanelStateResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
void AlarmControlPanelStateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_fixed32(1, this->key);
|
||||
buffer.encode_uint32(2, static_cast<uint32_t>(this->state));
|
||||
#ifdef USE_DEVICES
|
||||
@@ -2779,7 +2779,7 @@ bool AlarmControlPanelCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_TEXT
|
||||
void ListEntitiesTextResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
void ListEntitiesTextResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_string(1, this->object_id);
|
||||
buffer.encode_fixed32(2, this->key);
|
||||
buffer.encode_string(3, this->name);
|
||||
@@ -2813,7 +2813,7 @@ void ListEntitiesTextResponse::calculate_size(ProtoSize &size) const {
|
||||
size.add_uint32(1, this->device_id);
|
||||
#endif
|
||||
}
|
||||
void TextStateResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
void TextStateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_fixed32(1, this->key);
|
||||
buffer.encode_string(2, this->state);
|
||||
buffer.encode_bool(3, this->missing_state);
|
||||
@@ -2864,7 +2864,7 @@ bool TextCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_DATETIME_DATE
|
||||
void ListEntitiesDateResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
void ListEntitiesDateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_string(1, this->object_id);
|
||||
buffer.encode_fixed32(2, this->key);
|
||||
buffer.encode_string(3, this->name);
|
||||
@@ -2890,7 +2890,7 @@ void ListEntitiesDateResponse::calculate_size(ProtoSize &size) const {
|
||||
size.add_uint32(1, this->device_id);
|
||||
#endif
|
||||
}
|
||||
void DateStateResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
void DateStateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_fixed32(1, this->key);
|
||||
buffer.encode_bool(2, this->missing_state);
|
||||
buffer.encode_uint32(3, this->year);
|
||||
@@ -2943,7 +2943,7 @@ bool DateCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_DATETIME_TIME
|
||||
void ListEntitiesTimeResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
void ListEntitiesTimeResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_string(1, this->object_id);
|
||||
buffer.encode_fixed32(2, this->key);
|
||||
buffer.encode_string(3, this->name);
|
||||
@@ -2969,7 +2969,7 @@ void ListEntitiesTimeResponse::calculate_size(ProtoSize &size) const {
|
||||
size.add_uint32(1, this->device_id);
|
||||
#endif
|
||||
}
|
||||
void TimeStateResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
void TimeStateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_fixed32(1, this->key);
|
||||
buffer.encode_bool(2, this->missing_state);
|
||||
buffer.encode_uint32(3, this->hour);
|
||||
@@ -3022,7 +3022,7 @@ bool TimeCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_EVENT
|
||||
void ListEntitiesEventResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
void ListEntitiesEventResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_string(1, this->object_id);
|
||||
buffer.encode_fixed32(2, this->key);
|
||||
buffer.encode_string(3, this->name);
|
||||
@@ -3058,7 +3058,7 @@ void ListEntitiesEventResponse::calculate_size(ProtoSize &size) const {
|
||||
size.add_uint32(1, this->device_id);
|
||||
#endif
|
||||
}
|
||||
void EventResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
void EventResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_fixed32(1, this->key);
|
||||
buffer.encode_string(2, this->event_type);
|
||||
#ifdef USE_DEVICES
|
||||
@@ -3074,7 +3074,7 @@ void EventResponse::calculate_size(ProtoSize &size) const {
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_VALVE
|
||||
void ListEntitiesValveResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
void ListEntitiesValveResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_string(1, this->object_id);
|
||||
buffer.encode_fixed32(2, this->key);
|
||||
buffer.encode_string(3, this->name);
|
||||
@@ -3108,7 +3108,7 @@ void ListEntitiesValveResponse::calculate_size(ProtoSize &size) const {
|
||||
size.add_uint32(1, this->device_id);
|
||||
#endif
|
||||
}
|
||||
void ValveStateResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
void ValveStateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_fixed32(1, this->key);
|
||||
buffer.encode_float(2, this->position);
|
||||
buffer.encode_uint32(3, static_cast<uint32_t>(this->current_operation));
|
||||
@@ -3157,7 +3157,7 @@ bool ValveCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_DATETIME_DATETIME
|
||||
void ListEntitiesDateTimeResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
void ListEntitiesDateTimeResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_string(1, this->object_id);
|
||||
buffer.encode_fixed32(2, this->key);
|
||||
buffer.encode_string(3, this->name);
|
||||
@@ -3183,7 +3183,7 @@ void ListEntitiesDateTimeResponse::calculate_size(ProtoSize &size) const {
|
||||
size.add_uint32(1, this->device_id);
|
||||
#endif
|
||||
}
|
||||
void DateTimeStateResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
void DateTimeStateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_fixed32(1, this->key);
|
||||
buffer.encode_bool(2, this->missing_state);
|
||||
buffer.encode_fixed32(3, this->epoch_seconds);
|
||||
@@ -3226,7 +3226,7 @@ bool DateTimeCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_UPDATE
|
||||
void ListEntitiesUpdateResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
void ListEntitiesUpdateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_string(1, this->object_id);
|
||||
buffer.encode_fixed32(2, this->key);
|
||||
buffer.encode_string(3, this->name);
|
||||
@@ -3254,7 +3254,7 @@ void ListEntitiesUpdateResponse::calculate_size(ProtoSize &size) const {
|
||||
size.add_uint32(1, this->device_id);
|
||||
#endif
|
||||
}
|
||||
void UpdateStateResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
void UpdateStateResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_fixed32(1, this->key);
|
||||
buffer.encode_bool(2, this->missing_state);
|
||||
buffer.encode_bool(3, this->in_progress);
|
||||
@@ -3323,7 +3323,7 @@ bool ZWaveProxyFrame::decode_length(uint32_t field_id, ProtoLengthDelimited valu
|
||||
}
|
||||
return true;
|
||||
}
|
||||
void ZWaveProxyFrame::encode(ProtoWriteBuffer buffer) const { buffer.encode_bytes(1, this->data, this->data_len); }
|
||||
void ZWaveProxyFrame::encode(ProtoWriteBuffer &buffer) const { buffer.encode_bytes(1, this->data, this->data_len); }
|
||||
void ZWaveProxyFrame::calculate_size(ProtoSize &size) const { size.add_length(1, this->data_len); }
|
||||
bool ZWaveProxyRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
switch (field_id) {
|
||||
@@ -3347,7 +3347,7 @@ bool ZWaveProxyRequest::decode_length(uint32_t field_id, ProtoLengthDelimited va
|
||||
}
|
||||
return true;
|
||||
}
|
||||
void ZWaveProxyRequest::encode(ProtoWriteBuffer buffer) const {
|
||||
void ZWaveProxyRequest::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_uint32(1, static_cast<uint32_t>(this->type));
|
||||
buffer.encode_bytes(2, this->data, this->data_len);
|
||||
}
|
||||
@@ -3357,7 +3357,7 @@ void ZWaveProxyRequest::calculate_size(ProtoSize &size) const {
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_INFRARED
|
||||
void ListEntitiesInfraredResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
void ListEntitiesInfraredResponse::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_string(1, this->object_id);
|
||||
buffer.encode_fixed32(2, this->key);
|
||||
buffer.encode_string(3, this->name);
|
||||
@@ -3428,7 +3428,7 @@ bool InfraredRFTransmitRawTimingsRequest::decode_32bit(uint32_t field_id, Proto3
|
||||
}
|
||||
return true;
|
||||
}
|
||||
void InfraredRFReceiveEvent::encode(ProtoWriteBuffer buffer) const {
|
||||
void InfraredRFReceiveEvent::encode(ProtoWriteBuffer &buffer) const {
|
||||
#ifdef USE_DEVICES
|
||||
buffer.encode_uint32(1, this->device_id);
|
||||
#endif
|
||||
|
||||
@@ -382,7 +382,7 @@ class HelloResponse final : public ProtoMessage {
|
||||
uint32_t api_version_minor{0};
|
||||
StringRef server_info{};
|
||||
StringRef name{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void encode(ProtoWriteBuffer &buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *dump_to(DumpBuffer &out) const override;
|
||||
@@ -447,7 +447,7 @@ class AreaInfo final : public ProtoMessage {
|
||||
public:
|
||||
uint32_t area_id{0};
|
||||
StringRef name{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void encode(ProtoWriteBuffer &buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *dump_to(DumpBuffer &out) const override;
|
||||
@@ -462,7 +462,7 @@ class DeviceInfo final : public ProtoMessage {
|
||||
uint32_t device_id{0};
|
||||
StringRef name{};
|
||||
uint32_t area_id{0};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void encode(ProtoWriteBuffer &buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *dump_to(DumpBuffer &out) const override;
|
||||
@@ -527,7 +527,7 @@ class DeviceInfoResponse final : public ProtoMessage {
|
||||
#ifdef USE_ZWAVE_PROXY
|
||||
uint32_t zwave_home_id{0};
|
||||
#endif
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void encode(ProtoWriteBuffer &buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *dump_to(DumpBuffer &out) const override;
|
||||
@@ -558,7 +558,7 @@ class ListEntitiesBinarySensorResponse final : public InfoResponseProtoMessage {
|
||||
#endif
|
||||
StringRef device_class{};
|
||||
bool is_status_binary_sensor{false};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void encode(ProtoWriteBuffer &buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *dump_to(DumpBuffer &out) const override;
|
||||
@@ -575,7 +575,7 @@ class BinarySensorStateResponse final : public StateResponseProtoMessage {
|
||||
#endif
|
||||
bool state{false};
|
||||
bool missing_state{false};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void encode(ProtoWriteBuffer &buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *dump_to(DumpBuffer &out) const override;
|
||||
@@ -597,7 +597,7 @@ class ListEntitiesCoverResponse final : public InfoResponseProtoMessage {
|
||||
bool supports_tilt{false};
|
||||
StringRef device_class{};
|
||||
bool supports_stop{false};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void encode(ProtoWriteBuffer &buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *dump_to(DumpBuffer &out) const override;
|
||||
@@ -615,7 +615,7 @@ class CoverStateResponse final : public StateResponseProtoMessage {
|
||||
float position{0.0f};
|
||||
float tilt{0.0f};
|
||||
enums::CoverOperation current_operation{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void encode(ProtoWriteBuffer &buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *dump_to(DumpBuffer &out) const override;
|
||||
@@ -657,7 +657,7 @@ class ListEntitiesFanResponse final : public InfoResponseProtoMessage {
|
||||
bool supports_direction{false};
|
||||
int32_t supported_speed_count{0};
|
||||
const std::vector<const char *> *supported_preset_modes{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void encode(ProtoWriteBuffer &buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *dump_to(DumpBuffer &out) const override;
|
||||
@@ -677,7 +677,7 @@ class FanStateResponse final : public StateResponseProtoMessage {
|
||||
enums::FanDirection direction{};
|
||||
int32_t speed_level{0};
|
||||
StringRef preset_mode{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void encode(ProtoWriteBuffer &buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *dump_to(DumpBuffer &out) const override;
|
||||
@@ -724,7 +724,7 @@ class ListEntitiesLightResponse final : public InfoResponseProtoMessage {
|
||||
float min_mireds{0.0f};
|
||||
float max_mireds{0.0f};
|
||||
const FixedVector<const char *> *effects{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void encode(ProtoWriteBuffer &buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *dump_to(DumpBuffer &out) const override;
|
||||
@@ -751,7 +751,7 @@ class LightStateResponse final : public StateResponseProtoMessage {
|
||||
float cold_white{0.0f};
|
||||
float warm_white{0.0f};
|
||||
StringRef effect{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void encode(ProtoWriteBuffer &buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *dump_to(DumpBuffer &out) const override;
|
||||
@@ -815,7 +815,7 @@ class ListEntitiesSensorResponse final : public InfoResponseProtoMessage {
|
||||
bool force_update{false};
|
||||
StringRef device_class{};
|
||||
enums::SensorStateClass state_class{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void encode(ProtoWriteBuffer &buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *dump_to(DumpBuffer &out) const override;
|
||||
@@ -832,7 +832,7 @@ class SensorStateResponse final : public StateResponseProtoMessage {
|
||||
#endif
|
||||
float state{0.0f};
|
||||
bool missing_state{false};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void encode(ProtoWriteBuffer &buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *dump_to(DumpBuffer &out) const override;
|
||||
@@ -851,7 +851,7 @@ class ListEntitiesSwitchResponse final : public InfoResponseProtoMessage {
|
||||
#endif
|
||||
bool assumed_state{false};
|
||||
StringRef device_class{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void encode(ProtoWriteBuffer &buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *dump_to(DumpBuffer &out) const override;
|
||||
@@ -867,7 +867,7 @@ class SwitchStateResponse final : public StateResponseProtoMessage {
|
||||
const char *message_name() const override { return "switch_state_response"; }
|
||||
#endif
|
||||
bool state{false};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void encode(ProtoWriteBuffer &buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *dump_to(DumpBuffer &out) const override;
|
||||
@@ -901,7 +901,7 @@ class ListEntitiesTextSensorResponse final : public InfoResponseProtoMessage {
|
||||
const char *message_name() const override { return "list_entities_text_sensor_response"; }
|
||||
#endif
|
||||
StringRef device_class{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void encode(ProtoWriteBuffer &buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *dump_to(DumpBuffer &out) const override;
|
||||
@@ -918,7 +918,7 @@ class TextSensorStateResponse final : public StateResponseProtoMessage {
|
||||
#endif
|
||||
StringRef state{};
|
||||
bool missing_state{false};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void encode(ProtoWriteBuffer &buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *dump_to(DumpBuffer &out) const override;
|
||||
@@ -957,7 +957,7 @@ class SubscribeLogsResponse final : public ProtoMessage {
|
||||
this->message_ptr_ = data;
|
||||
this->message_len_ = len;
|
||||
}
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void encode(ProtoWriteBuffer &buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *dump_to(DumpBuffer &out) const override;
|
||||
@@ -990,7 +990,7 @@ class NoiseEncryptionSetKeyResponse final : public ProtoMessage {
|
||||
const char *message_name() const override { return "noise_encryption_set_key_response"; }
|
||||
#endif
|
||||
bool success{false};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void encode(ProtoWriteBuffer &buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *dump_to(DumpBuffer &out) const override;
|
||||
@@ -1004,7 +1004,7 @@ class HomeassistantServiceMap final : public ProtoMessage {
|
||||
public:
|
||||
StringRef key{};
|
||||
StringRef value{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void encode(ProtoWriteBuffer &buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *dump_to(DumpBuffer &out) const override;
|
||||
@@ -1033,7 +1033,7 @@ class HomeassistantActionRequest final : public ProtoMessage {
|
||||
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
|
||||
StringRef response_template{};
|
||||
#endif
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void encode(ProtoWriteBuffer &buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *dump_to(DumpBuffer &out) const override;
|
||||
@@ -1077,7 +1077,7 @@ class SubscribeHomeAssistantStateResponse final : public ProtoMessage {
|
||||
StringRef entity_id{};
|
||||
StringRef attribute{};
|
||||
bool once{false};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void encode(ProtoWriteBuffer &buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *dump_to(DumpBuffer &out) const override;
|
||||
@@ -1139,7 +1139,7 @@ class ListEntitiesServicesArgument final : public ProtoMessage {
|
||||
public:
|
||||
StringRef name{};
|
||||
enums::ServiceArgType type{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void encode(ProtoWriteBuffer &buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *dump_to(DumpBuffer &out) const override;
|
||||
@@ -1158,7 +1158,7 @@ class ListEntitiesServicesResponse final : public ProtoMessage {
|
||||
uint32_t key{0};
|
||||
FixedVector<ListEntitiesServicesArgument> args{};
|
||||
enums::SupportsResponseType supports_response{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void encode(ProtoWriteBuffer &buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *dump_to(DumpBuffer &out) const override;
|
||||
@@ -1228,7 +1228,7 @@ class ExecuteServiceResponse final : public ProtoMessage {
|
||||
const uint8_t *response_data{nullptr};
|
||||
uint16_t response_data_len{0};
|
||||
#endif
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void encode(ProtoWriteBuffer &buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *dump_to(DumpBuffer &out) const override;
|
||||
@@ -1245,7 +1245,7 @@ class ListEntitiesCameraResponse final : public InfoResponseProtoMessage {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *message_name() const override { return "list_entities_camera_response"; }
|
||||
#endif
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void encode(ProtoWriteBuffer &buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *dump_to(DumpBuffer &out) const override;
|
||||
@@ -1267,7 +1267,7 @@ class CameraImageResponse final : public StateResponseProtoMessage {
|
||||
this->data_len_ = len;
|
||||
}
|
||||
bool done{false};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void encode(ProtoWriteBuffer &buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *dump_to(DumpBuffer &out) const override;
|
||||
@@ -1318,7 +1318,7 @@ class ListEntitiesClimateResponse final : public InfoResponseProtoMessage {
|
||||
float visual_min_humidity{0.0f};
|
||||
float visual_max_humidity{0.0f};
|
||||
uint32_t feature_flags{0};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void encode(ProtoWriteBuffer &buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *dump_to(DumpBuffer &out) const override;
|
||||
@@ -1346,7 +1346,7 @@ class ClimateStateResponse final : public StateResponseProtoMessage {
|
||||
StringRef custom_preset{};
|
||||
float current_humidity{0.0f};
|
||||
float target_humidity{0.0f};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void encode(ProtoWriteBuffer &buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *dump_to(DumpBuffer &out) const override;
|
||||
@@ -1404,7 +1404,7 @@ class ListEntitiesWaterHeaterResponse final : public InfoResponseProtoMessage {
|
||||
float target_temperature_step{0.0f};
|
||||
const water_heater::WaterHeaterModeMask *supported_modes{};
|
||||
uint32_t supported_features{0};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void encode(ProtoWriteBuffer &buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *dump_to(DumpBuffer &out) const override;
|
||||
@@ -1425,7 +1425,7 @@ class WaterHeaterStateResponse final : public StateResponseProtoMessage {
|
||||
uint32_t state{0};
|
||||
float target_temperature_low{0.0f};
|
||||
float target_temperature_high{0.0f};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void encode(ProtoWriteBuffer &buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *dump_to(DumpBuffer &out) const override;
|
||||
@@ -1469,7 +1469,7 @@ class ListEntitiesNumberResponse final : public InfoResponseProtoMessage {
|
||||
StringRef unit_of_measurement{};
|
||||
enums::NumberMode mode{};
|
||||
StringRef device_class{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void encode(ProtoWriteBuffer &buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *dump_to(DumpBuffer &out) const override;
|
||||
@@ -1486,7 +1486,7 @@ class NumberStateResponse final : public StateResponseProtoMessage {
|
||||
#endif
|
||||
float state{0.0f};
|
||||
bool missing_state{false};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void encode(ProtoWriteBuffer &buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *dump_to(DumpBuffer &out) const override;
|
||||
@@ -1520,7 +1520,7 @@ class ListEntitiesSelectResponse final : public InfoResponseProtoMessage {
|
||||
const char *message_name() const override { return "list_entities_select_response"; }
|
||||
#endif
|
||||
const FixedVector<const char *> *options{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void encode(ProtoWriteBuffer &buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *dump_to(DumpBuffer &out) const override;
|
||||
@@ -1537,7 +1537,7 @@ class SelectStateResponse final : public StateResponseProtoMessage {
|
||||
#endif
|
||||
StringRef state{};
|
||||
bool missing_state{false};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void encode(ProtoWriteBuffer &buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *dump_to(DumpBuffer &out) const override;
|
||||
@@ -1574,7 +1574,7 @@ class ListEntitiesSirenResponse final : public InfoResponseProtoMessage {
|
||||
const FixedVector<const char *> *tones{};
|
||||
bool supports_duration{false};
|
||||
bool supports_volume{false};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void encode(ProtoWriteBuffer &buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *dump_to(DumpBuffer &out) const override;
|
||||
@@ -1590,7 +1590,7 @@ class SirenStateResponse final : public StateResponseProtoMessage {
|
||||
const char *message_name() const override { return "siren_state_response"; }
|
||||
#endif
|
||||
bool state{false};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void encode(ProtoWriteBuffer &buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *dump_to(DumpBuffer &out) const override;
|
||||
@@ -1635,7 +1635,7 @@ class ListEntitiesLockResponse final : public InfoResponseProtoMessage {
|
||||
bool supports_open{false};
|
||||
bool requires_code{false};
|
||||
StringRef code_format{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void encode(ProtoWriteBuffer &buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *dump_to(DumpBuffer &out) const override;
|
||||
@@ -1651,7 +1651,7 @@ class LockStateResponse final : public StateResponseProtoMessage {
|
||||
const char *message_name() const override { return "lock_state_response"; }
|
||||
#endif
|
||||
enums::LockState state{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void encode(ProtoWriteBuffer &buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *dump_to(DumpBuffer &out) const override;
|
||||
@@ -1688,7 +1688,7 @@ class ListEntitiesButtonResponse final : public InfoResponseProtoMessage {
|
||||
const char *message_name() const override { return "list_entities_button_response"; }
|
||||
#endif
|
||||
StringRef device_class{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void encode(ProtoWriteBuffer &buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *dump_to(DumpBuffer &out) const override;
|
||||
@@ -1720,7 +1720,7 @@ class MediaPlayerSupportedFormat final : public ProtoMessage {
|
||||
uint32_t num_channels{0};
|
||||
enums::MediaPlayerFormatPurpose purpose{};
|
||||
uint32_t sample_bytes{0};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void encode(ProtoWriteBuffer &buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *dump_to(DumpBuffer &out) const override;
|
||||
@@ -1738,7 +1738,7 @@ class ListEntitiesMediaPlayerResponse final : public InfoResponseProtoMessage {
|
||||
bool supports_pause{false};
|
||||
std::vector<MediaPlayerSupportedFormat> supported_formats{};
|
||||
uint32_t feature_flags{0};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void encode(ProtoWriteBuffer &buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *dump_to(DumpBuffer &out) const override;
|
||||
@@ -1756,7 +1756,7 @@ class MediaPlayerStateResponse final : public StateResponseProtoMessage {
|
||||
enums::MediaPlayerState state{};
|
||||
float volume{0.0f};
|
||||
bool muted{false};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void encode(ProtoWriteBuffer &buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *dump_to(DumpBuffer &out) const override;
|
||||
@@ -1812,7 +1812,7 @@ class BluetoothLERawAdvertisement final : public ProtoMessage {
|
||||
uint32_t address_type{0};
|
||||
uint8_t data[62]{};
|
||||
uint8_t data_len{0};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void encode(ProtoWriteBuffer &buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *dump_to(DumpBuffer &out) const override;
|
||||
@@ -1829,7 +1829,7 @@ class BluetoothLERawAdvertisementsResponse final : public ProtoMessage {
|
||||
#endif
|
||||
std::array<BluetoothLERawAdvertisement, BLUETOOTH_PROXY_ADVERTISEMENT_BATCH_SIZE> advertisements{};
|
||||
uint16_t advertisements_len{0};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void encode(ProtoWriteBuffer &buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *dump_to(DumpBuffer &out) const override;
|
||||
@@ -1866,7 +1866,7 @@ class BluetoothDeviceConnectionResponse final : public ProtoMessage {
|
||||
bool connected{false};
|
||||
uint32_t mtu{0};
|
||||
int32_t error{0};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void encode(ProtoWriteBuffer &buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *dump_to(DumpBuffer &out) const override;
|
||||
@@ -1894,7 +1894,7 @@ class BluetoothGATTDescriptor final : public ProtoMessage {
|
||||
std::array<uint64_t, 2> uuid{};
|
||||
uint32_t handle{0};
|
||||
uint32_t short_uuid{0};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void encode(ProtoWriteBuffer &buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *dump_to(DumpBuffer &out) const override;
|
||||
@@ -1909,7 +1909,7 @@ class BluetoothGATTCharacteristic final : public ProtoMessage {
|
||||
uint32_t properties{0};
|
||||
FixedVector<BluetoothGATTDescriptor> descriptors{};
|
||||
uint32_t short_uuid{0};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void encode(ProtoWriteBuffer &buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *dump_to(DumpBuffer &out) const override;
|
||||
@@ -1923,7 +1923,7 @@ class BluetoothGATTService final : public ProtoMessage {
|
||||
uint32_t handle{0};
|
||||
FixedVector<BluetoothGATTCharacteristic> characteristics{};
|
||||
uint32_t short_uuid{0};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void encode(ProtoWriteBuffer &buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *dump_to(DumpBuffer &out) const override;
|
||||
@@ -1940,7 +1940,7 @@ class BluetoothGATTGetServicesResponse final : public ProtoMessage {
|
||||
#endif
|
||||
uint64_t address{0};
|
||||
std::vector<BluetoothGATTService> services{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void encode(ProtoWriteBuffer &buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *dump_to(DumpBuffer &out) const override;
|
||||
@@ -1956,7 +1956,7 @@ class BluetoothGATTGetServicesDoneResponse final : public ProtoMessage {
|
||||
const char *message_name() const override { return "bluetooth_gatt_get_services_done_response"; }
|
||||
#endif
|
||||
uint64_t address{0};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void encode(ProtoWriteBuffer &buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *dump_to(DumpBuffer &out) const override;
|
||||
@@ -1995,7 +1995,7 @@ class BluetoothGATTReadResponse final : public ProtoMessage {
|
||||
this->data_ptr_ = data;
|
||||
this->data_len_ = len;
|
||||
}
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void encode(ProtoWriteBuffer &buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *dump_to(DumpBuffer &out) const override;
|
||||
@@ -2090,7 +2090,7 @@ class BluetoothGATTNotifyDataResponse final : public ProtoMessage {
|
||||
this->data_ptr_ = data;
|
||||
this->data_len_ = len;
|
||||
}
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void encode(ProtoWriteBuffer &buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *dump_to(DumpBuffer &out) const override;
|
||||
@@ -2108,7 +2108,7 @@ class BluetoothConnectionsFreeResponse final : public ProtoMessage {
|
||||
uint32_t free{0};
|
||||
uint32_t limit{0};
|
||||
std::array<uint64_t, BLUETOOTH_PROXY_MAX_CONNECTIONS> allocated{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void encode(ProtoWriteBuffer &buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *dump_to(DumpBuffer &out) const override;
|
||||
@@ -2126,7 +2126,7 @@ class BluetoothGATTErrorResponse final : public ProtoMessage {
|
||||
uint64_t address{0};
|
||||
uint32_t handle{0};
|
||||
int32_t error{0};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void encode(ProtoWriteBuffer &buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *dump_to(DumpBuffer &out) const override;
|
||||
@@ -2143,7 +2143,7 @@ class BluetoothGATTWriteResponse final : public ProtoMessage {
|
||||
#endif
|
||||
uint64_t address{0};
|
||||
uint32_t handle{0};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void encode(ProtoWriteBuffer &buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *dump_to(DumpBuffer &out) const override;
|
||||
@@ -2160,7 +2160,7 @@ class BluetoothGATTNotifyResponse final : public ProtoMessage {
|
||||
#endif
|
||||
uint64_t address{0};
|
||||
uint32_t handle{0};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void encode(ProtoWriteBuffer &buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *dump_to(DumpBuffer &out) const override;
|
||||
@@ -2178,7 +2178,7 @@ class BluetoothDevicePairingResponse final : public ProtoMessage {
|
||||
uint64_t address{0};
|
||||
bool paired{false};
|
||||
int32_t error{0};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void encode(ProtoWriteBuffer &buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *dump_to(DumpBuffer &out) const override;
|
||||
@@ -2196,7 +2196,7 @@ class BluetoothDeviceUnpairingResponse final : public ProtoMessage {
|
||||
uint64_t address{0};
|
||||
bool success{false};
|
||||
int32_t error{0};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void encode(ProtoWriteBuffer &buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *dump_to(DumpBuffer &out) const override;
|
||||
@@ -2214,7 +2214,7 @@ class BluetoothDeviceClearCacheResponse final : public ProtoMessage {
|
||||
uint64_t address{0};
|
||||
bool success{false};
|
||||
int32_t error{0};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void encode(ProtoWriteBuffer &buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *dump_to(DumpBuffer &out) const override;
|
||||
@@ -2232,7 +2232,7 @@ class BluetoothScannerStateResponse final : public ProtoMessage {
|
||||
enums::BluetoothScannerState state{};
|
||||
enums::BluetoothScannerMode mode{};
|
||||
enums::BluetoothScannerMode configured_mode{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void encode(ProtoWriteBuffer &buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *dump_to(DumpBuffer &out) const override;
|
||||
@@ -2278,7 +2278,7 @@ class VoiceAssistantAudioSettings final : public ProtoMessage {
|
||||
uint32_t noise_suppression_level{0};
|
||||
uint32_t auto_gain{0};
|
||||
float volume_multiplier{0.0f};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void encode(ProtoWriteBuffer &buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *dump_to(DumpBuffer &out) const override;
|
||||
@@ -2298,7 +2298,7 @@ class VoiceAssistantRequest final : public ProtoMessage {
|
||||
uint32_t flags{0};
|
||||
VoiceAssistantAudioSettings audio_settings{};
|
||||
StringRef wake_word_phrase{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void encode(ProtoWriteBuffer &buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *dump_to(DumpBuffer &out) const override;
|
||||
@@ -2360,7 +2360,7 @@ class VoiceAssistantAudio final : public ProtoDecodableMessage {
|
||||
const uint8_t *data{nullptr};
|
||||
uint16_t data_len{0};
|
||||
bool end{false};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void encode(ProtoWriteBuffer &buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *dump_to(DumpBuffer &out) const override;
|
||||
@@ -2418,7 +2418,7 @@ class VoiceAssistantAnnounceFinished final : public ProtoMessage {
|
||||
const char *message_name() const override { return "voice_assistant_announce_finished"; }
|
||||
#endif
|
||||
bool success{false};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void encode(ProtoWriteBuffer &buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *dump_to(DumpBuffer &out) const override;
|
||||
@@ -2431,7 +2431,7 @@ class VoiceAssistantWakeWord final : public ProtoMessage {
|
||||
StringRef id{};
|
||||
StringRef wake_word{};
|
||||
std::vector<std::string> trained_languages{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void encode(ProtoWriteBuffer &buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *dump_to(DumpBuffer &out) const override;
|
||||
@@ -2481,7 +2481,7 @@ class VoiceAssistantConfigurationResponse final : public ProtoMessage {
|
||||
std::vector<VoiceAssistantWakeWord> available_wake_words{};
|
||||
const std::vector<std::string> *active_wake_words{};
|
||||
uint32_t max_active_wake_words{0};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void encode(ProtoWriteBuffer &buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *dump_to(DumpBuffer &out) const override;
|
||||
@@ -2516,7 +2516,7 @@ class ListEntitiesAlarmControlPanelResponse final : public InfoResponseProtoMess
|
||||
uint32_t supported_features{0};
|
||||
bool requires_code{false};
|
||||
bool requires_code_to_arm{false};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void encode(ProtoWriteBuffer &buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *dump_to(DumpBuffer &out) const override;
|
||||
@@ -2532,7 +2532,7 @@ class AlarmControlPanelStateResponse final : public StateResponseProtoMessage {
|
||||
const char *message_name() const override { return "alarm_control_panel_state_response"; }
|
||||
#endif
|
||||
enums::AlarmControlPanelState state{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void encode(ProtoWriteBuffer &buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *dump_to(DumpBuffer &out) const override;
|
||||
@@ -2571,7 +2571,7 @@ class ListEntitiesTextResponse final : public InfoResponseProtoMessage {
|
||||
uint32_t max_length{0};
|
||||
StringRef pattern{};
|
||||
enums::TextMode mode{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void encode(ProtoWriteBuffer &buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *dump_to(DumpBuffer &out) const override;
|
||||
@@ -2588,7 +2588,7 @@ class TextStateResponse final : public StateResponseProtoMessage {
|
||||
#endif
|
||||
StringRef state{};
|
||||
bool missing_state{false};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void encode(ProtoWriteBuffer &buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *dump_to(DumpBuffer &out) const override;
|
||||
@@ -2622,7 +2622,7 @@ class ListEntitiesDateResponse final : public InfoResponseProtoMessage {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *message_name() const override { return "list_entities_date_response"; }
|
||||
#endif
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void encode(ProtoWriteBuffer &buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *dump_to(DumpBuffer &out) const override;
|
||||
@@ -2641,7 +2641,7 @@ class DateStateResponse final : public StateResponseProtoMessage {
|
||||
uint32_t year{0};
|
||||
uint32_t month{0};
|
||||
uint32_t day{0};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void encode(ProtoWriteBuffer &buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *dump_to(DumpBuffer &out) const override;
|
||||
@@ -2676,7 +2676,7 @@ class ListEntitiesTimeResponse final : public InfoResponseProtoMessage {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *message_name() const override { return "list_entities_time_response"; }
|
||||
#endif
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void encode(ProtoWriteBuffer &buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *dump_to(DumpBuffer &out) const override;
|
||||
@@ -2695,7 +2695,7 @@ class TimeStateResponse final : public StateResponseProtoMessage {
|
||||
uint32_t hour{0};
|
||||
uint32_t minute{0};
|
||||
uint32_t second{0};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void encode(ProtoWriteBuffer &buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *dump_to(DumpBuffer &out) const override;
|
||||
@@ -2732,7 +2732,7 @@ class ListEntitiesEventResponse final : public InfoResponseProtoMessage {
|
||||
#endif
|
||||
StringRef device_class{};
|
||||
const FixedVector<const char *> *event_types{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void encode(ProtoWriteBuffer &buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *dump_to(DumpBuffer &out) const override;
|
||||
@@ -2748,7 +2748,7 @@ class EventResponse final : public StateResponseProtoMessage {
|
||||
const char *message_name() const override { return "event_response"; }
|
||||
#endif
|
||||
StringRef event_type{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void encode(ProtoWriteBuffer &buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *dump_to(DumpBuffer &out) const override;
|
||||
@@ -2769,7 +2769,7 @@ class ListEntitiesValveResponse final : public InfoResponseProtoMessage {
|
||||
bool assumed_state{false};
|
||||
bool supports_position{false};
|
||||
bool supports_stop{false};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void encode(ProtoWriteBuffer &buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *dump_to(DumpBuffer &out) const override;
|
||||
@@ -2786,7 +2786,7 @@ class ValveStateResponse final : public StateResponseProtoMessage {
|
||||
#endif
|
||||
float position{0.0f};
|
||||
enums::ValveOperation current_operation{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void encode(ProtoWriteBuffer &buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *dump_to(DumpBuffer &out) const override;
|
||||
@@ -2821,7 +2821,7 @@ class ListEntitiesDateTimeResponse final : public InfoResponseProtoMessage {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *message_name() const override { return "list_entities_date_time_response"; }
|
||||
#endif
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void encode(ProtoWriteBuffer &buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *dump_to(DumpBuffer &out) const override;
|
||||
@@ -2838,7 +2838,7 @@ class DateTimeStateResponse final : public StateResponseProtoMessage {
|
||||
#endif
|
||||
bool missing_state{false};
|
||||
uint32_t epoch_seconds{0};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void encode(ProtoWriteBuffer &buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *dump_to(DumpBuffer &out) const override;
|
||||
@@ -2872,7 +2872,7 @@ class ListEntitiesUpdateResponse final : public InfoResponseProtoMessage {
|
||||
const char *message_name() const override { return "list_entities_update_response"; }
|
||||
#endif
|
||||
StringRef device_class{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void encode(ProtoWriteBuffer &buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *dump_to(DumpBuffer &out) const override;
|
||||
@@ -2896,7 +2896,7 @@ class UpdateStateResponse final : public StateResponseProtoMessage {
|
||||
StringRef title{};
|
||||
StringRef release_summary{};
|
||||
StringRef release_url{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void encode(ProtoWriteBuffer &buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *dump_to(DumpBuffer &out) const override;
|
||||
@@ -2931,7 +2931,7 @@ class ZWaveProxyFrame final : public ProtoDecodableMessage {
|
||||
#endif
|
||||
const uint8_t *data{nullptr};
|
||||
uint16_t data_len{0};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void encode(ProtoWriteBuffer &buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *dump_to(DumpBuffer &out) const override;
|
||||
@@ -2950,7 +2950,7 @@ class ZWaveProxyRequest final : public ProtoDecodableMessage {
|
||||
enums::ZWaveProxyRequestType type{};
|
||||
const uint8_t *data{nullptr};
|
||||
uint16_t data_len{0};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void encode(ProtoWriteBuffer &buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *dump_to(DumpBuffer &out) const override;
|
||||
@@ -2970,7 +2970,7 @@ class ListEntitiesInfraredResponse final : public InfoResponseProtoMessage {
|
||||
const char *message_name() const override { return "list_entities_infrared_response"; }
|
||||
#endif
|
||||
uint32_t capabilities{0};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void encode(ProtoWriteBuffer &buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *dump_to(DumpBuffer &out) const override;
|
||||
@@ -3017,7 +3017,7 @@ class InfraredRFReceiveEvent final : public ProtoMessage {
|
||||
#endif
|
||||
uint32_t key{0};
|
||||
const std::vector<int32_t> *timings{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void encode(ProtoWriteBuffer &buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *dump_to(DumpBuffer &out) const override;
|
||||
|
||||
@@ -28,10 +28,12 @@ static const char *const TAG = "api";
|
||||
// APIServer
|
||||
APIServer *global_api_server = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
||||
APIServer::APIServer() {
|
||||
global_api_server = this;
|
||||
// Pre-allocate shared write buffer
|
||||
shared_write_buffer_.reserve(64);
|
||||
APIServer::APIServer() { global_api_server = this; }
|
||||
|
||||
void APIServer::socket_failed_(const LogString *msg) {
|
||||
ESP_LOGW(TAG, "Socket %s: errno %d", LOG_STR_ARG(msg), errno);
|
||||
this->destroy_socket_();
|
||||
this->mark_failed();
|
||||
}
|
||||
|
||||
void APIServer::setup() {
|
||||
@@ -52,22 +54,20 @@ void APIServer::setup() {
|
||||
#endif
|
||||
#endif
|
||||
|
||||
this->socket_ = socket::socket_ip_loop_monitored(SOCK_STREAM, 0); // monitored for incoming connections
|
||||
this->socket_ = socket::socket_ip_loop_monitored(SOCK_STREAM, 0).release(); // monitored for incoming connections
|
||||
if (this->socket_ == nullptr) {
|
||||
ESP_LOGW(TAG, "Could not create socket");
|
||||
this->mark_failed();
|
||||
this->socket_failed_(LOG_STR("creation"));
|
||||
return;
|
||||
}
|
||||
int enable = 1;
|
||||
int err = this->socket_->setsockopt(SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int));
|
||||
if (err != 0) {
|
||||
ESP_LOGW(TAG, "Socket unable to set reuseaddr: errno %d", err);
|
||||
ESP_LOGW(TAG, "Socket reuseaddr: errno %d", errno);
|
||||
// we can still continue
|
||||
}
|
||||
err = this->socket_->setblocking(false);
|
||||
if (err != 0) {
|
||||
ESP_LOGW(TAG, "Socket unable to set nonblocking mode: errno %d", err);
|
||||
this->mark_failed();
|
||||
this->socket_failed_(LOG_STR("nonblocking"));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -75,28 +75,28 @@ void APIServer::setup() {
|
||||
|
||||
socklen_t sl = socket::set_sockaddr_any((struct sockaddr *) &server, sizeof(server), this->port_);
|
||||
if (sl == 0) {
|
||||
ESP_LOGW(TAG, "Socket unable to set sockaddr: errno %d", errno);
|
||||
this->mark_failed();
|
||||
this->socket_failed_(LOG_STR("set sockaddr"));
|
||||
return;
|
||||
}
|
||||
|
||||
err = this->socket_->bind((struct sockaddr *) &server, sl);
|
||||
if (err != 0) {
|
||||
ESP_LOGW(TAG, "Socket unable to bind: errno %d", errno);
|
||||
this->mark_failed();
|
||||
this->socket_failed_(LOG_STR("bind"));
|
||||
return;
|
||||
}
|
||||
|
||||
err = this->socket_->listen(this->listen_backlog_);
|
||||
if (err != 0) {
|
||||
ESP_LOGW(TAG, "Socket unable to listen: errno %d", errno);
|
||||
this->mark_failed();
|
||||
this->socket_failed_(LOG_STR("listen"));
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef USE_LOGGER
|
||||
if (logger::global_logger != nullptr) {
|
||||
logger::global_logger->add_log_listener(this);
|
||||
logger::global_logger->add_log_callback(
|
||||
this, [](void *self, uint8_t level, const char *tag, const char *message, size_t message_len) {
|
||||
static_cast<APIServer *>(self)->on_log(level, tag, message, message_len);
|
||||
});
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -623,10 +623,7 @@ void APIServer::on_shutdown() {
|
||||
this->shutting_down_ = true;
|
||||
|
||||
// Close the listening socket to prevent new connections
|
||||
if (this->socket_) {
|
||||
this->socket_->close();
|
||||
this->socket_ = nullptr;
|
||||
}
|
||||
this->destroy_socket_();
|
||||
|
||||
// Change batch delay to 5ms for quick flushing during shutdown
|
||||
this->batch_delay_ = 5;
|
||||
|
||||
@@ -37,10 +37,6 @@ struct SavedNoisePsk {
|
||||
|
||||
class APIServer : public Component,
|
||||
public Controller
|
||||
#ifdef USE_LOGGER
|
||||
,
|
||||
public logger::LogListener
|
||||
#endif
|
||||
#ifdef USE_CAMERA
|
||||
,
|
||||
public camera::CameraListener
|
||||
@@ -56,7 +52,7 @@ class APIServer : public Component,
|
||||
void on_shutdown() override;
|
||||
bool teardown() override;
|
||||
#ifdef USE_LOGGER
|
||||
void on_log(uint8_t level, const char *tag, const char *message, size_t message_len) override;
|
||||
void on_log(uint8_t level, const char *tag, const char *message, size_t message_len);
|
||||
#endif
|
||||
#ifdef USE_CAMERA
|
||||
void on_camera_image(const std::shared_ptr<camera::CameraImage> &image) override;
|
||||
@@ -253,8 +249,15 @@ class APIServer : public Component,
|
||||
void add_state_subscription_(std::string entity_id, optional<std::string> attribute,
|
||||
std::function<void(const std::string &)> f, bool once);
|
||||
#endif // USE_API_HOMEASSISTANT_STATES
|
||||
// No explicit close() needed — listen sockets have no active connections on
|
||||
// failure/shutdown. Destructor handles fd cleanup (close or abort per platform).
|
||||
inline void destroy_socket_() {
|
||||
delete this->socket_;
|
||||
this->socket_ = nullptr;
|
||||
}
|
||||
void socket_failed_(const LogString *msg);
|
||||
// Pointers and pointer-like types first (4 bytes each)
|
||||
std::unique_ptr<socket::Socket> socket_ = nullptr;
|
||||
socket::Socket *socket_{nullptr};
|
||||
#ifdef USE_API_CLIENT_CONNECTED_TRIGGER
|
||||
Trigger<std::string, std::string> client_connected_trigger_;
|
||||
#endif
|
||||
@@ -268,7 +271,11 @@ class APIServer : public Component,
|
||||
|
||||
// Vectors and strings (12 bytes each on 32-bit)
|
||||
std::vector<std::unique_ptr<APIConnection>> clients_;
|
||||
std::vector<uint8_t> shared_write_buffer_; // Shared proto write buffer for all connections
|
||||
// Shared proto write buffer for all connections.
|
||||
// Not pre-allocated: all send paths call prepare_first_message_buffer() which
|
||||
// reserves the exact needed size. Pre-allocating here would cause heap fragmentation
|
||||
// since the buffer would almost always reallocate on first use.
|
||||
std::vector<uint8_t> shared_write_buffer_;
|
||||
#ifdef USE_API_HOMEASSISTANT_STATES
|
||||
std::vector<HomeAssistantStateSubscription> state_subs_;
|
||||
#endif
|
||||
|
||||
@@ -70,6 +70,21 @@ uint32_t ProtoDecodableMessage::count_repeated_field(const uint8_t *buffer, size
|
||||
return count;
|
||||
}
|
||||
|
||||
#ifdef ESPHOME_DEBUG_API
|
||||
void ProtoWriteBuffer::debug_check_bounds_(size_t bytes, const char *caller) {
|
||||
if (this->pos_ + bytes > this->buffer_->data() + this->buffer_->size()) {
|
||||
ESP_LOGE(TAG, "ProtoWriteBuffer bounds check failed in %s: bytes=%zu offset=%td buf_size=%zu", caller, bytes,
|
||||
this->pos_ - this->buffer_->data(), this->buffer_->size());
|
||||
abort();
|
||||
}
|
||||
}
|
||||
void ProtoWriteBuffer::debug_check_encode_size_(uint32_t field_id, uint32_t expected, ptrdiff_t actual) {
|
||||
ESP_LOGE(TAG, "encode_message: size mismatch for field %" PRIu32 ": calculated=%" PRIu32 " actual=%td", field_id,
|
||||
expected, actual);
|
||||
abort();
|
||||
}
|
||||
#endif
|
||||
|
||||
void ProtoDecodableMessage::decode(const uint8_t *buffer, size_t length) {
|
||||
const uint8_t *ptr = buffer;
|
||||
const uint8_t *end = buffer + length;
|
||||
|
||||
@@ -217,21 +217,26 @@ class Proto32Bit {
|
||||
|
||||
class ProtoWriteBuffer {
|
||||
public:
|
||||
ProtoWriteBuffer(std::vector<uint8_t> *buffer) : buffer_(buffer) {}
|
||||
void write(uint8_t value) { this->buffer_->push_back(value); }
|
||||
ProtoWriteBuffer(std::vector<uint8_t> *buffer) : buffer_(buffer), pos_(buffer->data() + buffer->size()) {}
|
||||
ProtoWriteBuffer(std::vector<uint8_t> *buffer, size_t write_pos)
|
||||
: buffer_(buffer), pos_(buffer->data() + write_pos) {}
|
||||
void encode_varint_raw(uint32_t value) {
|
||||
while (value > 0x7F) {
|
||||
this->buffer_->push_back(static_cast<uint8_t>(value | 0x80));
|
||||
this->debug_check_bounds_(1);
|
||||
*this->pos_++ = static_cast<uint8_t>(value | 0x80);
|
||||
value >>= 7;
|
||||
}
|
||||
this->buffer_->push_back(static_cast<uint8_t>(value));
|
||||
this->debug_check_bounds_(1);
|
||||
*this->pos_++ = static_cast<uint8_t>(value);
|
||||
}
|
||||
void encode_varint_raw_64(uint64_t value) {
|
||||
while (value > 0x7F) {
|
||||
this->buffer_->push_back(static_cast<uint8_t>(value | 0x80));
|
||||
this->debug_check_bounds_(1);
|
||||
*this->pos_++ = static_cast<uint8_t>(value | 0x80);
|
||||
value >>= 7;
|
||||
}
|
||||
this->buffer_->push_back(static_cast<uint8_t>(value));
|
||||
this->debug_check_bounds_(1);
|
||||
*this->pos_++ = static_cast<uint8_t>(value);
|
||||
}
|
||||
/**
|
||||
* Encode a field key (tag/wire type combination).
|
||||
@@ -245,23 +250,18 @@ class ProtoWriteBuffer {
|
||||
*
|
||||
* Following https://protobuf.dev/programming-guides/encoding/#structure
|
||||
*/
|
||||
void encode_field_raw(uint32_t field_id, uint32_t type) {
|
||||
uint32_t val = (field_id << 3) | (type & WIRE_TYPE_MASK);
|
||||
this->encode_varint_raw(val);
|
||||
}
|
||||
void encode_field_raw(uint32_t field_id, uint32_t type) { this->encode_varint_raw((field_id << 3) | type); }
|
||||
void encode_string(uint32_t field_id, const char *string, size_t len, bool force = false) {
|
||||
if (len == 0 && !force)
|
||||
return;
|
||||
|
||||
this->encode_field_raw(field_id, 2); // type 2: Length-delimited string
|
||||
this->encode_varint_raw(len);
|
||||
|
||||
// Using resize + memcpy instead of insert provides significant performance improvement:
|
||||
// ~10-11x faster for 16-32 byte strings, ~3x faster for 64-byte strings
|
||||
// as it avoids iterator checks and potential element moves that insert performs
|
||||
size_t old_size = this->buffer_->size();
|
||||
this->buffer_->resize(old_size + len);
|
||||
std::memcpy(this->buffer_->data() + old_size, string, len);
|
||||
// Direct memcpy into pre-sized buffer — avoids push_back() per-byte capacity checks
|
||||
// and vector::insert() iterator overhead. ~10-11x faster for 16-32 byte strings.
|
||||
this->debug_check_bounds_(len);
|
||||
std::memcpy(this->pos_, string, len);
|
||||
this->pos_ += len;
|
||||
}
|
||||
void encode_string(uint32_t field_id, const std::string &value, bool force = false) {
|
||||
this->encode_string(field_id, value.data(), value.size(), force);
|
||||
@@ -288,17 +288,26 @@ class ProtoWriteBuffer {
|
||||
if (!value && !force)
|
||||
return;
|
||||
this->encode_field_raw(field_id, 0); // type 0: Varint - bool
|
||||
this->buffer_->push_back(value ? 0x01 : 0x00);
|
||||
this->debug_check_bounds_(1);
|
||||
*this->pos_++ = value ? 0x01 : 0x00;
|
||||
}
|
||||
void encode_fixed32(uint32_t field_id, uint32_t value, bool force = false) {
|
||||
// noinline: 51 call sites; inlining causes net code growth vs a single out-of-line copy
|
||||
__attribute__((noinline)) void encode_fixed32(uint32_t field_id, uint32_t value, bool force = false) {
|
||||
if (value == 0 && !force)
|
||||
return;
|
||||
|
||||
this->encode_field_raw(field_id, 5); // type 5: 32-bit fixed32
|
||||
this->write((value >> 0) & 0xFF);
|
||||
this->write((value >> 8) & 0xFF);
|
||||
this->write((value >> 16) & 0xFF);
|
||||
this->write((value >> 24) & 0xFF);
|
||||
this->debug_check_bounds_(4);
|
||||
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
|
||||
// Protobuf fixed32 is little-endian, so direct copy works
|
||||
std::memcpy(this->pos_, &value, 4);
|
||||
this->pos_ += 4;
|
||||
#else
|
||||
*this->pos_++ = (value >> 0) & 0xFF;
|
||||
*this->pos_++ = (value >> 8) & 0xFF;
|
||||
*this->pos_++ = (value >> 16) & 0xFF;
|
||||
*this->pos_++ = (value >> 24) & 0xFF;
|
||||
#endif
|
||||
}
|
||||
// NOTE: Wire type 1 (64-bit fixed: double, fixed64, sfixed64) is intentionally
|
||||
// not supported to reduce overhead on embedded systems. All ESPHome devices are
|
||||
@@ -334,11 +343,20 @@ class ProtoWriteBuffer {
|
||||
}
|
||||
/// Encode a packed repeated sint32 field (zero-copy from vector)
|
||||
void encode_packed_sint32(uint32_t field_id, const std::vector<int32_t> &values);
|
||||
void encode_message(uint32_t field_id, const ProtoMessage &value);
|
||||
/// Encode a nested message field (force=true for repeated, false for singular)
|
||||
void encode_message(uint32_t field_id, const ProtoMessage &value, bool force = true);
|
||||
std::vector<uint8_t> *get_buffer() const { return buffer_; }
|
||||
|
||||
protected:
|
||||
#ifdef ESPHOME_DEBUG_API
|
||||
void debug_check_bounds_(size_t bytes, const char *caller = __builtin_FUNCTION());
|
||||
void debug_check_encode_size_(uint32_t field_id, uint32_t expected, ptrdiff_t actual);
|
||||
#else
|
||||
void debug_check_bounds_([[maybe_unused]] size_t bytes) {}
|
||||
#endif
|
||||
|
||||
std::vector<uint8_t> *buffer_;
|
||||
uint8_t *pos_;
|
||||
};
|
||||
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
@@ -416,9 +434,11 @@ class ProtoMessage {
|
||||
public:
|
||||
virtual ~ProtoMessage() = default;
|
||||
// Default implementation for messages with no fields
|
||||
virtual void encode(ProtoWriteBuffer buffer) const {}
|
||||
virtual void encode(ProtoWriteBuffer &buffer) const {}
|
||||
// Default implementation for messages with no fields
|
||||
virtual void calculate_size(ProtoSize &size) const {}
|
||||
// Convenience: calculate and return size directly (defined after ProtoSize)
|
||||
uint32_t calculated_size() const;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
virtual const char *dump_to(DumpBuffer &out) const = 0;
|
||||
virtual const char *message_name() const { return "unknown"; }
|
||||
@@ -877,6 +897,14 @@ class ProtoSize {
|
||||
}
|
||||
};
|
||||
|
||||
// Implementation of methods that depend on ProtoSize being fully defined
|
||||
|
||||
inline uint32_t ProtoMessage::calculated_size() const {
|
||||
ProtoSize size;
|
||||
this->calculate_size(size);
|
||||
return size.get_size();
|
||||
}
|
||||
|
||||
// Implementation of encode_packed_sint32 - must be after ProtoSize is defined
|
||||
inline void ProtoWriteBuffer::encode_packed_sint32(uint32_t field_id, const std::vector<int32_t> &values) {
|
||||
if (values.empty())
|
||||
@@ -897,30 +925,30 @@ inline void ProtoWriteBuffer::encode_packed_sint32(uint32_t field_id, const std:
|
||||
}
|
||||
|
||||
// Implementation of encode_message - must be after ProtoMessage is defined
|
||||
inline void ProtoWriteBuffer::encode_message(uint32_t field_id, const ProtoMessage &value) {
|
||||
this->encode_field_raw(field_id, 2); // type 2: Length-delimited message
|
||||
|
||||
inline void ProtoWriteBuffer::encode_message(uint32_t field_id, const ProtoMessage &value, bool force) {
|
||||
// Calculate the message size first
|
||||
ProtoSize msg_size;
|
||||
value.calculate_size(msg_size);
|
||||
uint32_t msg_length_bytes = msg_size.get_size();
|
||||
|
||||
// Calculate how many bytes the length varint needs
|
||||
uint32_t varint_length_bytes = ProtoSize::varint(msg_length_bytes);
|
||||
// Skip empty singular messages (matches add_message_field which skips when nested_size == 0)
|
||||
// Repeated messages (force=true) are always encoded since an empty item is meaningful
|
||||
if (msg_length_bytes == 0 && !force)
|
||||
return;
|
||||
|
||||
// Reserve exact space for the length varint
|
||||
size_t begin = this->buffer_->size();
|
||||
this->buffer_->resize(this->buffer_->size() + varint_length_bytes);
|
||||
this->encode_field_raw(field_id, 2); // type 2: Length-delimited message
|
||||
|
||||
// Write the length varint directly
|
||||
encode_varint_to_buffer(msg_length_bytes, this->buffer_->data() + begin);
|
||||
|
||||
// Now encode the message content - it will append to the buffer
|
||||
value.encode(*this);
|
||||
// Write the length varint directly through pos_
|
||||
this->encode_varint_raw(msg_length_bytes);
|
||||
|
||||
// Encode nested message - pos_ advances directly through the reference
|
||||
#ifdef ESPHOME_DEBUG_API
|
||||
// Verify that the encoded size matches what we calculated
|
||||
assert(this->buffer_->size() == begin + varint_length_bytes + msg_length_bytes);
|
||||
uint8_t *start = this->pos_;
|
||||
value.encode(*this);
|
||||
if (static_cast<uint32_t>(this->pos_ - start) != msg_length_bytes)
|
||||
this->debug_check_encode_size_(field_id, msg_length_bytes, this->pos_ - start);
|
||||
#else
|
||||
value.encode(*this);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -264,9 +264,9 @@ template<typename... Ts> class APIRespondAction : public Action<Ts...> {
|
||||
// Build and send JSON response
|
||||
json::JsonBuilder builder;
|
||||
this->json_builder_(x..., builder.root());
|
||||
std::string json_str = builder.serialize();
|
||||
auto json_buf = builder.serialize();
|
||||
this->parent_->send_action_response(call_id, success, StringRef(error_message),
|
||||
reinterpret_cast<const uint8_t *>(json_str.data()), json_str.size());
|
||||
reinterpret_cast<const uint8_t *>(json_buf.data()), json_buf.size());
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
import esphome.codegen as cg
|
||||
from esphome.components.esp32 import add_idf_component, include_builtin_idf_component
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_BITS_PER_SAMPLE, CONF_NUM_CHANNELS, CONF_SAMPLE_RATE
|
||||
from esphome.core import CORE
|
||||
import esphome.final_validate as fv
|
||||
|
||||
CODEOWNERS = ["@kahrendt"]
|
||||
DOMAIN = "audio"
|
||||
audio_ns = cg.esphome_ns.namespace("audio")
|
||||
|
||||
AudioFile = audio_ns.struct("AudioFile")
|
||||
@@ -14,9 +18,38 @@ AUDIO_FILE_TYPE_ENUM = {
|
||||
"WAV": AudioFileType.WAV,
|
||||
"MP3": AudioFileType.MP3,
|
||||
"FLAC": AudioFileType.FLAC,
|
||||
"OPUS": AudioFileType.OPUS,
|
||||
}
|
||||
|
||||
|
||||
@dataclass
|
||||
class AudioData:
|
||||
flac_support: bool = False
|
||||
mp3_support: bool = False
|
||||
opus_support: bool = False
|
||||
|
||||
|
||||
def _get_data() -> AudioData:
|
||||
if DOMAIN not in CORE.data:
|
||||
CORE.data[DOMAIN] = AudioData()
|
||||
return CORE.data[DOMAIN]
|
||||
|
||||
|
||||
def request_flac_support() -> None:
|
||||
"""Request FLAC codec support for audio decoding."""
|
||||
_get_data().flac_support = True
|
||||
|
||||
|
||||
def request_mp3_support() -> None:
|
||||
"""Request MP3 codec support for audio decoding."""
|
||||
_get_data().mp3_support = True
|
||||
|
||||
|
||||
def request_opus_support() -> None:
|
||||
"""Request Opus codec support for audio decoding."""
|
||||
_get_data().opus_support = True
|
||||
|
||||
|
||||
CONF_MIN_BITS_PER_SAMPLE = "min_bits_per_sample"
|
||||
CONF_MAX_BITS_PER_SAMPLE = "max_bits_per_sample"
|
||||
CONF_MIN_CHANNELS = "min_channels"
|
||||
@@ -173,3 +206,12 @@ async def to_code(config):
|
||||
name="esphome/esp-audio-libs",
|
||||
ref="2.0.3",
|
||||
)
|
||||
|
||||
data = _get_data()
|
||||
if data.flac_support:
|
||||
cg.add_define("USE_AUDIO_FLAC_SUPPORT")
|
||||
if data.mp3_support:
|
||||
cg.add_define("USE_AUDIO_MP3_SUPPORT")
|
||||
if data.opus_support:
|
||||
cg.add_define("USE_AUDIO_OPUS_SUPPORT")
|
||||
add_idf_component(name="esphome/micro-opus", ref="0.3.3")
|
||||
|
||||
@@ -46,6 +46,10 @@ const char *audio_file_type_to_string(AudioFileType file_type) {
|
||||
#ifdef USE_AUDIO_MP3_SUPPORT
|
||||
case AudioFileType::MP3:
|
||||
return "MP3";
|
||||
#endif
|
||||
#ifdef USE_AUDIO_OPUS_SUPPORT
|
||||
case AudioFileType::OPUS:
|
||||
return "OPUS";
|
||||
#endif
|
||||
case AudioFileType::WAV:
|
||||
return "WAV";
|
||||
|
||||
@@ -112,6 +112,9 @@ enum class AudioFileType : uint8_t {
|
||||
#endif
|
||||
#ifdef USE_AUDIO_MP3_SUPPORT
|
||||
MP3,
|
||||
#endif
|
||||
#ifdef USE_AUDIO_OPUS_SUPPORT
|
||||
OPUS,
|
||||
#endif
|
||||
WAV,
|
||||
};
|
||||
|
||||
@@ -3,17 +3,20 @@
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace audio {
|
||||
|
||||
static const char *const TAG = "audio.decoder";
|
||||
|
||||
static const uint32_t DECODING_TIMEOUT_MS = 50; // The decode function will yield after this duration
|
||||
static const uint32_t READ_WRITE_TIMEOUT_MS = 20; // Timeout for transferring audio data
|
||||
|
||||
static const uint32_t MAX_POTENTIALLY_FAILED_COUNT = 10;
|
||||
|
||||
AudioDecoder::AudioDecoder(size_t input_buffer_size, size_t output_buffer_size) {
|
||||
this->input_transfer_buffer_ = AudioSourceTransferBuffer::create(input_buffer_size);
|
||||
AudioDecoder::AudioDecoder(size_t input_buffer_size, size_t output_buffer_size)
|
||||
: input_buffer_size_(input_buffer_size) {
|
||||
this->output_transfer_buffer_ = AudioSinkTransferBuffer::create(output_buffer_size);
|
||||
}
|
||||
|
||||
@@ -26,11 +29,20 @@ AudioDecoder::~AudioDecoder() {
|
||||
}
|
||||
|
||||
esp_err_t AudioDecoder::add_source(std::weak_ptr<RingBuffer> &input_ring_buffer) {
|
||||
if (this->input_transfer_buffer_ != nullptr) {
|
||||
this->input_transfer_buffer_->set_source(input_ring_buffer);
|
||||
return ESP_OK;
|
||||
auto source = AudioSourceTransferBuffer::create(this->input_buffer_size_);
|
||||
if (source == nullptr) {
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
return ESP_ERR_NO_MEM;
|
||||
source->set_source(input_ring_buffer);
|
||||
this->input_buffer_ = std::move(source);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t AudioDecoder::add_source(const uint8_t *data_pointer, size_t length) {
|
||||
auto source = make_unique<ConstAudioSourceBuffer>();
|
||||
source->set_data(data_pointer, length);
|
||||
this->input_buffer_ = std::move(source);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t AudioDecoder::add_sink(std::weak_ptr<RingBuffer> &output_ring_buffer) {
|
||||
@@ -51,8 +63,16 @@ esp_err_t AudioDecoder::add_sink(speaker::Speaker *speaker) {
|
||||
}
|
||||
#endif
|
||||
|
||||
esp_err_t AudioDecoder::add_sink(AudioSinkCallback *callback) {
|
||||
if (this->output_transfer_buffer_ != nullptr) {
|
||||
this->output_transfer_buffer_->set_sink(callback);
|
||||
return ESP_OK;
|
||||
}
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
esp_err_t AudioDecoder::start(AudioFileType audio_file_type) {
|
||||
if ((this->input_transfer_buffer_ == nullptr) || (this->output_transfer_buffer_ == nullptr)) {
|
||||
if (this->output_transfer_buffer_ == nullptr) {
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
@@ -65,6 +85,10 @@ esp_err_t AudioDecoder::start(AudioFileType audio_file_type) {
|
||||
#ifdef USE_AUDIO_FLAC_SUPPORT
|
||||
case AudioFileType::FLAC:
|
||||
this->flac_decoder_ = make_unique<esp_audio_libs::flac::FLACDecoder>();
|
||||
// CRC check slows down decoding by 15-20% on an ESP32-S3. FLAC sources in ESPHome are either from an http source
|
||||
// or built into the firmware, so the data integrity is already verified by the time it gets to the decoder,
|
||||
// making the CRC check unnecessary.
|
||||
this->flac_decoder_->set_crc_check_enabled(false);
|
||||
this->free_buffer_required_ =
|
||||
this->output_transfer_buffer_->capacity(); // Adjusted and reallocated after reading the header
|
||||
break;
|
||||
@@ -79,6 +103,14 @@ esp_err_t AudioDecoder::start(AudioFileType audio_file_type) {
|
||||
// Always reallocate the output transfer buffer to the smallest necessary size
|
||||
this->output_transfer_buffer_->reallocate(this->free_buffer_required_);
|
||||
break;
|
||||
#endif
|
||||
#ifdef USE_AUDIO_OPUS_SUPPORT
|
||||
case AudioFileType::OPUS:
|
||||
this->opus_decoder_ = make_unique<micro_opus::OggOpusDecoder>();
|
||||
this->free_buffer_required_ =
|
||||
this->output_transfer_buffer_->capacity(); // Adjusted and reallocated after reading the header
|
||||
this->decoder_buffers_internally_ = true;
|
||||
break;
|
||||
#endif
|
||||
case AudioFileType::WAV:
|
||||
this->wav_decoder_ = make_unique<esp_audio_libs::wav_decoder::WAVDecoder>();
|
||||
@@ -101,6 +133,10 @@ esp_err_t AudioDecoder::start(AudioFileType audio_file_type) {
|
||||
}
|
||||
|
||||
AudioDecoderState AudioDecoder::decode(bool stop_gracefully) {
|
||||
if (this->input_buffer_ == nullptr) {
|
||||
return AudioDecoderState::FAILED;
|
||||
}
|
||||
|
||||
if (stop_gracefully) {
|
||||
if (this->output_transfer_buffer_->available() == 0) {
|
||||
if (this->end_of_file_) {
|
||||
@@ -108,7 +144,7 @@ AudioDecoderState AudioDecoder::decode(bool stop_gracefully) {
|
||||
return AudioDecoderState::FINISHED;
|
||||
}
|
||||
|
||||
if (!this->input_transfer_buffer_->has_buffered_data()) {
|
||||
if (!this->input_buffer_->has_buffered_data()) {
|
||||
// If all the internal buffers are empty, the decoding is done
|
||||
return AudioDecoderState::FINISHED;
|
||||
}
|
||||
@@ -158,10 +194,11 @@ AudioDecoderState AudioDecoder::decode(bool stop_gracefully) {
|
||||
// Decode more audio
|
||||
|
||||
// Only shift data on the first loop iteration to avoid unnecessary, slow moves
|
||||
size_t bytes_read = this->input_transfer_buffer_->transfer_data_from_source(pdMS_TO_TICKS(READ_WRITE_TIMEOUT_MS),
|
||||
first_loop_iteration);
|
||||
// If the decoder buffers internally, then never shift
|
||||
size_t bytes_read = this->input_buffer_->fill(pdMS_TO_TICKS(READ_WRITE_TIMEOUT_MS),
|
||||
first_loop_iteration && !this->decoder_buffers_internally_);
|
||||
|
||||
if (!first_loop_iteration && (this->input_transfer_buffer_->available() < bytes_processed)) {
|
||||
if (!first_loop_iteration && (this->input_buffer_->available() < bytes_processed)) {
|
||||
// Less data is available than what was processed in last iteration, so don't attempt to decode.
|
||||
// This attempts to avoid the decoder from consistently trying to decode an incomplete frame. The transfer buffer
|
||||
// will shift the remaining data to the start and copy more from the source the next time the decode function is
|
||||
@@ -169,19 +206,21 @@ AudioDecoderState AudioDecoder::decode(bool stop_gracefully) {
|
||||
break;
|
||||
}
|
||||
|
||||
bytes_available_before_processing = this->input_transfer_buffer_->available();
|
||||
bytes_available_before_processing = this->input_buffer_->available();
|
||||
|
||||
if ((this->potentially_failed_count_ > 0) && (bytes_read == 0)) {
|
||||
// Failed to decode in last attempt and there is no new data
|
||||
|
||||
if ((this->input_transfer_buffer_->free() == 0) && first_loop_iteration) {
|
||||
// The input buffer is full. Since it previously failed on the exact same data, we can never recover
|
||||
if ((this->input_buffer_->free() == 0) && first_loop_iteration) {
|
||||
// The input buffer is full (or read-only, e.g. const flash source). Since it previously failed on the exact
|
||||
// same data, we can never recover. For const sources this is correct: the entire file is already available, so
|
||||
// a decode failure is genuine, not a transient out-of-data condition.
|
||||
state = FileDecoderState::FAILED;
|
||||
} else {
|
||||
// Attempt to get more data next time
|
||||
state = FileDecoderState::IDLE;
|
||||
}
|
||||
} else if (this->input_transfer_buffer_->available() == 0) {
|
||||
} else if (this->input_buffer_->available() == 0) {
|
||||
// No data to decode, attempt to get more data next time
|
||||
state = FileDecoderState::IDLE;
|
||||
} else {
|
||||
@@ -195,6 +234,11 @@ AudioDecoderState AudioDecoder::decode(bool stop_gracefully) {
|
||||
case AudioFileType::MP3:
|
||||
state = this->decode_mp3_();
|
||||
break;
|
||||
#endif
|
||||
#ifdef USE_AUDIO_OPUS_SUPPORT
|
||||
case AudioFileType::OPUS:
|
||||
state = this->decode_opus_();
|
||||
break;
|
||||
#endif
|
||||
case AudioFileType::WAV:
|
||||
state = this->decode_wav_();
|
||||
@@ -207,7 +251,7 @@ AudioDecoderState AudioDecoder::decode(bool stop_gracefully) {
|
||||
}
|
||||
|
||||
first_loop_iteration = false;
|
||||
bytes_processed = bytes_available_before_processing - this->input_transfer_buffer_->available();
|
||||
bytes_processed = bytes_available_before_processing - this->input_buffer_->available();
|
||||
|
||||
if (state == FileDecoderState::POTENTIALLY_FAILED) {
|
||||
++this->potentially_failed_count_;
|
||||
@@ -226,8 +270,7 @@ AudioDecoderState AudioDecoder::decode(bool stop_gracefully) {
|
||||
FileDecoderState AudioDecoder::decode_flac_() {
|
||||
if (!this->audio_stream_info_.has_value()) {
|
||||
// Header hasn't been read
|
||||
auto result = this->flac_decoder_->read_header(this->input_transfer_buffer_->get_buffer_start(),
|
||||
this->input_transfer_buffer_->available());
|
||||
auto result = this->flac_decoder_->read_header(this->input_buffer_->data(), this->input_buffer_->available());
|
||||
|
||||
if (result > esp_audio_libs::flac::FLAC_DECODER_HEADER_OUT_OF_DATA) {
|
||||
// Serrious error reading FLAC header, there is no recovery
|
||||
@@ -235,7 +278,7 @@ FileDecoderState AudioDecoder::decode_flac_() {
|
||||
}
|
||||
|
||||
size_t bytes_consumed = this->flac_decoder_->get_bytes_index();
|
||||
this->input_transfer_buffer_->decrease_buffer_length(bytes_consumed);
|
||||
this->input_buffer_->consume(bytes_consumed);
|
||||
|
||||
if (result == esp_audio_libs::flac::FLAC_DECODER_HEADER_OUT_OF_DATA) {
|
||||
return FileDecoderState::MORE_TO_PROCESS;
|
||||
@@ -256,8 +299,7 @@ FileDecoderState AudioDecoder::decode_flac_() {
|
||||
}
|
||||
|
||||
uint32_t output_samples = 0;
|
||||
auto result = this->flac_decoder_->decode_frame(this->input_transfer_buffer_->get_buffer_start(),
|
||||
this->input_transfer_buffer_->available(),
|
||||
auto result = this->flac_decoder_->decode_frame(this->input_buffer_->data(), this->input_buffer_->available(),
|
||||
this->output_transfer_buffer_->get_buffer_end(), &output_samples);
|
||||
|
||||
if (result == esp_audio_libs::flac::FLAC_DECODER_ERROR_OUT_OF_DATA) {
|
||||
@@ -266,7 +308,7 @@ FileDecoderState AudioDecoder::decode_flac_() {
|
||||
}
|
||||
|
||||
size_t bytes_consumed = this->flac_decoder_->get_bytes_index();
|
||||
this->input_transfer_buffer_->decrease_buffer_length(bytes_consumed);
|
||||
this->input_buffer_->consume(bytes_consumed);
|
||||
|
||||
if (result > esp_audio_libs::flac::FLAC_DECODER_ERROR_OUT_OF_DATA) {
|
||||
// Corrupted frame, don't retry with current buffer content, wait for new sync
|
||||
@@ -288,26 +330,25 @@ FileDecoderState AudioDecoder::decode_flac_() {
|
||||
#ifdef USE_AUDIO_MP3_SUPPORT
|
||||
FileDecoderState AudioDecoder::decode_mp3_() {
|
||||
// Look for the next sync word
|
||||
int buffer_length = (int) this->input_transfer_buffer_->available();
|
||||
int32_t offset =
|
||||
esp_audio_libs::helix_decoder::MP3FindSyncWord(this->input_transfer_buffer_->get_buffer_start(), buffer_length);
|
||||
int buffer_length = (int) this->input_buffer_->available();
|
||||
int32_t offset = esp_audio_libs::helix_decoder::MP3FindSyncWord(this->input_buffer_->data(), buffer_length);
|
||||
|
||||
if (offset < 0) {
|
||||
// New data may have the sync word
|
||||
this->input_transfer_buffer_->decrease_buffer_length(buffer_length);
|
||||
this->input_buffer_->consume(buffer_length);
|
||||
return FileDecoderState::POTENTIALLY_FAILED;
|
||||
}
|
||||
|
||||
// Advance read pointer to match the offset for the syncword
|
||||
this->input_transfer_buffer_->decrease_buffer_length(offset);
|
||||
const uint8_t *buffer_start = this->input_transfer_buffer_->get_buffer_start();
|
||||
this->input_buffer_->consume(offset);
|
||||
const uint8_t *buffer_start = this->input_buffer_->data();
|
||||
|
||||
buffer_length = (int) this->input_transfer_buffer_->available();
|
||||
buffer_length = (int) this->input_buffer_->available();
|
||||
int err = esp_audio_libs::helix_decoder::MP3Decode(this->mp3_decoder_, &buffer_start, &buffer_length,
|
||||
(int16_t *) this->output_transfer_buffer_->get_buffer_end(), 0);
|
||||
|
||||
size_t consumed = this->input_transfer_buffer_->available() - buffer_length;
|
||||
this->input_transfer_buffer_->decrease_buffer_length(consumed);
|
||||
size_t consumed = this->input_buffer_->available() - buffer_length;
|
||||
this->input_buffer_->consume(consumed);
|
||||
|
||||
if (err) {
|
||||
switch (err) {
|
||||
@@ -339,15 +380,53 @@ FileDecoderState AudioDecoder::decode_mp3_() {
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_AUDIO_OPUS_SUPPORT
|
||||
FileDecoderState AudioDecoder::decode_opus_() {
|
||||
bool processed_header = this->opus_decoder_->is_initialized();
|
||||
|
||||
size_t bytes_consumed, samples_decoded;
|
||||
|
||||
micro_opus::OggOpusResult result = this->opus_decoder_->decode(
|
||||
this->input_buffer_->data(), this->input_buffer_->available(), this->output_transfer_buffer_->get_buffer_end(),
|
||||
this->output_transfer_buffer_->free(), bytes_consumed, samples_decoded);
|
||||
|
||||
if (result == micro_opus::OGG_OPUS_OK) {
|
||||
if (!processed_header && this->opus_decoder_->is_initialized()) {
|
||||
// Header processed and stream info is available
|
||||
this->audio_stream_info_ =
|
||||
audio::AudioStreamInfo(this->opus_decoder_->get_bit_depth(), this->opus_decoder_->get_channels(),
|
||||
this->opus_decoder_->get_sample_rate());
|
||||
}
|
||||
if (samples_decoded > 0 && this->audio_stream_info_.has_value()) {
|
||||
// Some audio was processed
|
||||
this->output_transfer_buffer_->increase_buffer_length(
|
||||
this->audio_stream_info_.value().frames_to_bytes(samples_decoded));
|
||||
}
|
||||
this->input_buffer_->consume(bytes_consumed);
|
||||
} else if (result == micro_opus::OGG_OPUS_OUTPUT_BUFFER_TOO_SMALL) {
|
||||
// Reallocate to decode the packet on the next call
|
||||
this->free_buffer_required_ = this->opus_decoder_->get_required_output_buffer_size();
|
||||
if (!this->output_transfer_buffer_->reallocate(this->free_buffer_required_)) {
|
||||
// Couldn't reallocate output buffer
|
||||
return FileDecoderState::FAILED;
|
||||
}
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Opus decoder failed: %" PRId8, result);
|
||||
return FileDecoderState::POTENTIALLY_FAILED;
|
||||
}
|
||||
return FileDecoderState::MORE_TO_PROCESS;
|
||||
}
|
||||
#endif
|
||||
|
||||
FileDecoderState AudioDecoder::decode_wav_() {
|
||||
if (!this->audio_stream_info_.has_value()) {
|
||||
// Header hasn't been processed
|
||||
|
||||
esp_audio_libs::wav_decoder::WAVDecoderResult result = this->wav_decoder_->decode_header(
|
||||
this->input_transfer_buffer_->get_buffer_start(), this->input_transfer_buffer_->available());
|
||||
esp_audio_libs::wav_decoder::WAVDecoderResult result =
|
||||
this->wav_decoder_->decode_header(this->input_buffer_->data(), this->input_buffer_->available());
|
||||
|
||||
if (result == esp_audio_libs::wav_decoder::WAV_DECODER_SUCCESS_IN_DATA) {
|
||||
this->input_transfer_buffer_->decrease_buffer_length(this->wav_decoder_->bytes_processed());
|
||||
this->input_buffer_->consume(this->wav_decoder_->bytes_processed());
|
||||
|
||||
this->audio_stream_info_ = audio::AudioStreamInfo(
|
||||
this->wav_decoder_->bits_per_sample(), this->wav_decoder_->num_channels(), this->wav_decoder_->sample_rate());
|
||||
@@ -363,7 +442,7 @@ FileDecoderState AudioDecoder::decode_wav_() {
|
||||
}
|
||||
} else {
|
||||
if (!this->wav_has_known_end_ || (this->wav_bytes_left_ > 0)) {
|
||||
size_t bytes_to_copy = this->input_transfer_buffer_->available();
|
||||
size_t bytes_to_copy = this->input_buffer_->available();
|
||||
|
||||
if (this->wav_has_known_end_) {
|
||||
bytes_to_copy = std::min(bytes_to_copy, this->wav_bytes_left_);
|
||||
@@ -372,9 +451,8 @@ FileDecoderState AudioDecoder::decode_wav_() {
|
||||
bytes_to_copy = std::min(bytes_to_copy, this->output_transfer_buffer_->free());
|
||||
|
||||
if (bytes_to_copy > 0) {
|
||||
std::memcpy(this->output_transfer_buffer_->get_buffer_end(), this->input_transfer_buffer_->get_buffer_start(),
|
||||
bytes_to_copy);
|
||||
this->input_transfer_buffer_->decrease_buffer_length(bytes_to_copy);
|
||||
std::memcpy(this->output_transfer_buffer_->get_buffer_end(), this->input_buffer_->data(), bytes_to_copy);
|
||||
this->input_buffer_->consume(bytes_to_copy);
|
||||
this->output_transfer_buffer_->increase_buffer_length(bytes_to_copy);
|
||||
if (this->wav_has_known_end_) {
|
||||
this->wav_bytes_left_ -= bytes_to_copy;
|
||||
|
||||
@@ -24,6 +24,11 @@
|
||||
#endif
|
||||
#include <wav_decoder.h>
|
||||
|
||||
// micro-opus
|
||||
#ifdef USE_AUDIO_OPUS_SUPPORT
|
||||
#include <micro_opus/ogg_opus_decoder.h>
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
namespace audio {
|
||||
|
||||
@@ -45,17 +50,17 @@ enum class FileDecoderState : uint8_t {
|
||||
class AudioDecoder {
|
||||
/*
|
||||
* @brief Class that facilitates decoding an audio file.
|
||||
* The audio file is read from a ring buffer source, decoded, and sent to an audio sink (ring buffer or speaker
|
||||
* component).
|
||||
* Supports wav, flac, and mp3 formats.
|
||||
* The audio file is read from a source (ring buffer or const data pointer), decoded, and sent to an audio sink
|
||||
* (ring buffer, speaker component, or callback).
|
||||
* Supports wav, flac, mp3, and ogg opus formats.
|
||||
*/
|
||||
public:
|
||||
/// @brief Allocates the input and output transfer buffers
|
||||
/// @brief Allocates the output transfer buffer and stores the input buffer size for later use by add_source()
|
||||
/// @param input_buffer_size Size of the input transfer buffer in bytes.
|
||||
/// @param output_buffer_size Size of the output transfer buffer in bytes.
|
||||
AudioDecoder(size_t input_buffer_size, size_t output_buffer_size);
|
||||
|
||||
/// @brief Deallocates the MP3 decoder (the flac and wav decoders are deallocated automatically)
|
||||
/// @brief Deallocates the MP3 decoder (the flac, opus, and wav decoders are deallocated automatically)
|
||||
~AudioDecoder();
|
||||
|
||||
/// @brief Adds a source ring buffer for raw file data. Takes ownership of the ring buffer in a shared_ptr.
|
||||
@@ -75,6 +80,17 @@ class AudioDecoder {
|
||||
esp_err_t add_sink(speaker::Speaker *speaker);
|
||||
#endif
|
||||
|
||||
/// @brief Adds a const data pointer as the source for raw file data. Does not allocate a transfer buffer.
|
||||
/// @param data_pointer Pointer to the const audio data (e.g., stored in flash memory)
|
||||
/// @param length Size of the data in bytes
|
||||
/// @return ESP_OK
|
||||
esp_err_t add_source(const uint8_t *data_pointer, size_t length);
|
||||
|
||||
/// @brief Adds a callback as the sink for decoded audio.
|
||||
/// @param callback Pointer to the AudioSinkCallback implementation
|
||||
/// @return ESP_OK if successful, ESP_ERR_NO_MEM if the transfer buffer wasn't allocated
|
||||
esp_err_t add_sink(AudioSinkCallback *callback);
|
||||
|
||||
/// @brief Sets up decoding the file
|
||||
/// @param audio_file_type AudioFileType of the file
|
||||
/// @return ESP_OK if successful, ESP_ERR_NO_MEM if the transfer buffers fail to allocate, or ESP_ERR_NOT_SUPPORTED if
|
||||
@@ -108,26 +124,33 @@ class AudioDecoder {
|
||||
#ifdef USE_AUDIO_MP3_SUPPORT
|
||||
FileDecoderState decode_mp3_();
|
||||
esp_audio_libs::helix_decoder::HMP3Decoder mp3_decoder_;
|
||||
#endif
|
||||
#ifdef USE_AUDIO_OPUS_SUPPORT
|
||||
FileDecoderState decode_opus_();
|
||||
std::unique_ptr<micro_opus::OggOpusDecoder> opus_decoder_;
|
||||
#endif
|
||||
FileDecoderState decode_wav_();
|
||||
|
||||
std::unique_ptr<AudioSourceTransferBuffer> input_transfer_buffer_;
|
||||
std::unique_ptr<AudioReadableBuffer> input_buffer_;
|
||||
std::unique_ptr<AudioSinkTransferBuffer> output_transfer_buffer_;
|
||||
|
||||
AudioFileType audio_file_type_{AudioFileType::NONE};
|
||||
optional<AudioStreamInfo> audio_stream_info_{};
|
||||
|
||||
size_t input_buffer_size_{0};
|
||||
size_t free_buffer_required_{0};
|
||||
size_t wav_bytes_left_{0};
|
||||
|
||||
uint32_t potentially_failed_count_{0};
|
||||
uint32_t accumulated_frames_written_{0};
|
||||
uint32_t playback_ms_{0};
|
||||
|
||||
bool end_of_file_{false};
|
||||
bool wav_has_known_end_{false};
|
||||
|
||||
bool pause_output_{false};
|
||||
bool decoder_buffers_internally_{false};
|
||||
|
||||
uint32_t accumulated_frames_written_{0};
|
||||
uint32_t playback_ms_{0};
|
||||
bool pause_output_{false};
|
||||
};
|
||||
} // namespace audio
|
||||
} // namespace esphome
|
||||
|
||||
@@ -197,6 +197,11 @@ esp_err_t AudioReader::start(const std::string &uri, AudioFileType &file_type) {
|
||||
else if (str_endswith_ignore_case(url, ".flac")) {
|
||||
file_type = AudioFileType::FLAC;
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_AUDIO_OPUS_SUPPORT
|
||||
else if (str_endswith_ignore_case(url, ".opus")) {
|
||||
file_type = AudioFileType::OPUS;
|
||||
}
|
||||
#endif
|
||||
else {
|
||||
file_type = AudioFileType::NONE;
|
||||
@@ -241,6 +246,14 @@ AudioFileType AudioReader::get_audio_type(const char *content_type) {
|
||||
if (strcasecmp(content_type, "audio/flac") == 0 || strcasecmp(content_type, "audio/x-flac") == 0) {
|
||||
return AudioFileType::FLAC;
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_AUDIO_OPUS_SUPPORT
|
||||
// Match "audio/ogg" with a codecs parameter containing "opus"
|
||||
// Valid forms: audio/ogg;codecs=opus, audio/ogg; codecs="opus", etc.
|
||||
// Plain "audio/ogg" without a codecs parameter is not matched, as those are almost always Ogg Vorbis streams
|
||||
if (strncasecmp(content_type, "audio/ogg", 9) == 0 && strcasestr(content_type + 9, "opus") != nullptr) {
|
||||
return AudioFileType::OPUS;
|
||||
}
|
||||
#endif
|
||||
return AudioFileType::NONE;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
namespace esphome {
|
||||
@@ -75,12 +77,32 @@ bool AudioTransferBuffer::has_buffered_data() const {
|
||||
}
|
||||
|
||||
bool AudioTransferBuffer::reallocate(size_t new_buffer_size) {
|
||||
if (this->buffer_length_ > 0) {
|
||||
// Buffer currently has data, so reallocation is impossible
|
||||
if (this->buffer_ == nullptr) {
|
||||
return this->allocate_buffer_(new_buffer_size);
|
||||
}
|
||||
|
||||
if (new_buffer_size < this->buffer_length_) {
|
||||
// New size is too small to hold existing data
|
||||
return false;
|
||||
}
|
||||
this->deallocate_buffer_();
|
||||
return this->allocate_buffer_(new_buffer_size);
|
||||
|
||||
// Shift existing data to the start of the buffer so realloc preserves it
|
||||
if ((this->buffer_length_ > 0) && (this->data_start_ != this->buffer_)) {
|
||||
std::memmove(this->buffer_, this->data_start_, this->buffer_length_);
|
||||
this->data_start_ = this->buffer_;
|
||||
}
|
||||
|
||||
RAMAllocator<uint8_t> allocator;
|
||||
uint8_t *new_buffer = allocator.reallocate(this->buffer_, new_buffer_size);
|
||||
if (new_buffer == nullptr) {
|
||||
// Reallocation failed, but the original buffer is still valid
|
||||
return false;
|
||||
}
|
||||
|
||||
this->buffer_ = new_buffer;
|
||||
this->data_start_ = this->buffer_;
|
||||
this->buffer_size_ = new_buffer_size;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AudioTransferBuffer::allocate_buffer_(size_t buffer_size) {
|
||||
@@ -115,12 +137,12 @@ size_t AudioSourceTransferBuffer::transfer_data_from_source(TickType_t ticks_to_
|
||||
if (pre_shift) {
|
||||
// Shift data in buffer to start
|
||||
if (this->buffer_length_ > 0) {
|
||||
memmove(this->buffer_, this->data_start_, this->buffer_length_);
|
||||
std::memmove(this->buffer_, this->data_start_, this->buffer_length_);
|
||||
}
|
||||
this->data_start_ = this->buffer_;
|
||||
}
|
||||
|
||||
size_t bytes_to_read = this->free();
|
||||
size_t bytes_to_read = AudioTransferBuffer::free();
|
||||
size_t bytes_read = 0;
|
||||
if (bytes_to_read > 0) {
|
||||
if (this->ring_buffer_.use_count() > 0) {
|
||||
@@ -143,6 +165,8 @@ size_t AudioSinkTransferBuffer::transfer_data_to_sink(TickType_t ticks_to_wait,
|
||||
if (this->ring_buffer_.use_count() > 0) {
|
||||
bytes_written =
|
||||
this->ring_buffer_->write_without_replacement((void *) this->data_start_, this->available(), ticks_to_wait);
|
||||
} else if (this->sink_callback_ != nullptr) {
|
||||
bytes_written = this->sink_callback_->audio_sink_write(this->data_start_, this->available(), ticks_to_wait);
|
||||
}
|
||||
|
||||
this->decrease_buffer_length(bytes_written);
|
||||
@@ -150,7 +174,7 @@ size_t AudioSinkTransferBuffer::transfer_data_to_sink(TickType_t ticks_to_wait,
|
||||
|
||||
if (post_shift) {
|
||||
// Shift unwritten data to the start of the buffer
|
||||
memmove(this->buffer_, this->data_start_, this->buffer_length_);
|
||||
std::memmove(this->buffer_, this->data_start_, this->buffer_length_);
|
||||
this->data_start_ = this->buffer_;
|
||||
}
|
||||
|
||||
@@ -169,6 +193,21 @@ bool AudioSinkTransferBuffer::has_buffered_data() const {
|
||||
return (this->available() > 0);
|
||||
}
|
||||
|
||||
size_t AudioSourceTransferBuffer::free() const { return AudioTransferBuffer::free(); }
|
||||
|
||||
bool AudioSourceTransferBuffer::has_buffered_data() const { return AudioTransferBuffer::has_buffered_data(); }
|
||||
|
||||
void ConstAudioSourceBuffer::set_data(const uint8_t *data, size_t length) {
|
||||
this->data_start_ = data;
|
||||
this->length_ = length;
|
||||
}
|
||||
|
||||
void ConstAudioSourceBuffer::consume(size_t bytes) {
|
||||
bytes = std::min(bytes, this->length_);
|
||||
this->length_ -= bytes;
|
||||
this->data_start_ += bytes;
|
||||
}
|
||||
|
||||
} // namespace audio
|
||||
} // namespace esphome
|
||||
|
||||
|
||||
@@ -15,6 +15,12 @@
|
||||
namespace esphome {
|
||||
namespace audio {
|
||||
|
||||
/// @brief Abstract interface for writing decoded audio data to a sink.
|
||||
class AudioSinkCallback {
|
||||
public:
|
||||
virtual size_t audio_sink_write(uint8_t *data, size_t length, TickType_t ticks_to_wait) = 0;
|
||||
};
|
||||
|
||||
class AudioTransferBuffer {
|
||||
/*
|
||||
* @brief Class that facilitates tranferring data between a buffer and an audio source or sink.
|
||||
@@ -26,7 +32,7 @@ class AudioTransferBuffer {
|
||||
/// @brief Destructor that deallocates the transfer buffer
|
||||
~AudioTransferBuffer();
|
||||
|
||||
/// @brief Returns a pointer to the start of the transfer buffer where available() bytes of exisiting data can be read
|
||||
/// @brief Returns a pointer to the start of the transfer buffer where available() bytes of existing data can be read
|
||||
uint8_t *get_buffer_start() const { return this->data_start_; }
|
||||
|
||||
/// @brief Returns a pointer to the end of the transfer buffer where free() bytes of new data can be written
|
||||
@@ -56,6 +62,9 @@ class AudioTransferBuffer {
|
||||
/// @return True if there is data, false otherwise.
|
||||
virtual bool has_buffered_data() const;
|
||||
|
||||
/// @brief Reallocates the transfer buffer, preserving any existing data.
|
||||
/// @param new_buffer_size The new size in bytes. Must be at least as large as available().
|
||||
/// @return True if successful, false otherwise. On failure, the original buffer remains valid.
|
||||
bool reallocate(size_t new_buffer_size);
|
||||
|
||||
protected:
|
||||
@@ -105,6 +114,10 @@ class AudioSinkTransferBuffer : public AudioTransferBuffer {
|
||||
void set_sink(speaker::Speaker *speaker) { this->speaker_ = speaker; }
|
||||
#endif
|
||||
|
||||
/// @brief Adds a callback as the transfer buffer's sink.
|
||||
/// @param callback Pointer to the AudioSinkCallback implementation
|
||||
void set_sink(AudioSinkCallback *callback) { this->sink_callback_ = callback; }
|
||||
|
||||
void clear_buffered_data() override;
|
||||
|
||||
bool has_buffered_data() const override;
|
||||
@@ -113,12 +126,44 @@ class AudioSinkTransferBuffer : public AudioTransferBuffer {
|
||||
#ifdef USE_SPEAKER
|
||||
speaker::Speaker *speaker_{nullptr};
|
||||
#endif
|
||||
AudioSinkCallback *sink_callback_{nullptr};
|
||||
};
|
||||
|
||||
class AudioSourceTransferBuffer : public AudioTransferBuffer {
|
||||
/// @brief Abstract interface for reading audio data from a buffer.
|
||||
/// Provides a common read interface for both mutable transfer buffers and read-only const buffers.
|
||||
class AudioReadableBuffer {
|
||||
public:
|
||||
virtual ~AudioReadableBuffer() = default;
|
||||
|
||||
/// @brief Returns a pointer to the start of readable data
|
||||
virtual const uint8_t *data() const = 0;
|
||||
|
||||
/// @brief Returns the number of bytes available to read
|
||||
virtual size_t available() const = 0;
|
||||
|
||||
/// @brief Returns the number of free bytes available to write. Defaults to 0 for read-only buffers.
|
||||
virtual size_t free() const { return 0; }
|
||||
|
||||
/// @brief Advances past consumed data
|
||||
/// @param bytes Number of bytes consumed
|
||||
virtual void consume(size_t bytes) = 0;
|
||||
|
||||
/// @brief Tests if there is any buffered data
|
||||
virtual bool has_buffered_data() const = 0;
|
||||
|
||||
/// @brief Refills the buffer from its source. No-op by default for read-only buffers.
|
||||
/// @param ticks_to_wait FreeRTOS ticks to block while waiting for data
|
||||
/// @param pre_shift If true, shifts existing data to the start of the buffer before reading
|
||||
/// @return Number of bytes read
|
||||
virtual size_t fill(TickType_t ticks_to_wait, bool pre_shift) { return 0; }
|
||||
size_t fill(TickType_t ticks_to_wait) { return this->fill(ticks_to_wait, true); }
|
||||
};
|
||||
|
||||
class AudioSourceTransferBuffer : public AudioTransferBuffer, public AudioReadableBuffer {
|
||||
/*
|
||||
* @brief A class that implements a transfer buffer for audio sources.
|
||||
* Supports reading audio data from a ring buffer into the transfer buffer for processing.
|
||||
* Implements AudioReadableBuffer for use by consumers that only need read access.
|
||||
*/
|
||||
public:
|
||||
/// @brief Creates a new source transfer buffer.
|
||||
@@ -126,7 +171,7 @@ class AudioSourceTransferBuffer : public AudioTransferBuffer {
|
||||
/// @return unique_ptr if successfully allocated, nullptr otherwise
|
||||
static std::unique_ptr<AudioSourceTransferBuffer> create(size_t buffer_size);
|
||||
|
||||
/// @brief Reads any available data from the sink into the transfer buffer.
|
||||
/// @brief Reads any available data from the source into the transfer buffer.
|
||||
/// @param ticks_to_wait FreeRTOS ticks to block while waiting for the source to have enough data
|
||||
/// @param pre_shift If true, any unwritten data is moved to the start of the buffer before transferring from the
|
||||
/// source. Defaults to true.
|
||||
@@ -136,6 +181,36 @@ class AudioSourceTransferBuffer : public AudioTransferBuffer {
|
||||
/// @brief Adds a ring buffer as the transfer buffer's source.
|
||||
/// @param ring_buffer weak_ptr to the allocated ring buffer
|
||||
void set_source(const std::weak_ptr<RingBuffer> &ring_buffer) { this->ring_buffer_ = ring_buffer.lock(); };
|
||||
|
||||
// AudioReadableBuffer interface
|
||||
const uint8_t *data() const override { return this->data_start_; }
|
||||
size_t available() const override { return this->buffer_length_; }
|
||||
size_t free() const override;
|
||||
void consume(size_t bytes) override { this->decrease_buffer_length(bytes); }
|
||||
bool has_buffered_data() const override;
|
||||
size_t fill(TickType_t ticks_to_wait, bool pre_shift) override {
|
||||
return this->transfer_data_from_source(ticks_to_wait, pre_shift);
|
||||
}
|
||||
};
|
||||
|
||||
/// @brief A lightweight read-only audio buffer for const data sources (e.g., flash memory).
|
||||
/// Does not allocate memory or transfer data from external sources.
|
||||
class ConstAudioSourceBuffer : public AudioReadableBuffer {
|
||||
public:
|
||||
/// @brief Sets the data pointer and length for the buffer
|
||||
/// @param data Pointer to the const audio data
|
||||
/// @param length Size of the data in bytes
|
||||
void set_data(const uint8_t *data, size_t length);
|
||||
|
||||
// AudioReadableBuffer interface
|
||||
const uint8_t *data() const override { return this->data_start_; }
|
||||
size_t available() const override { return this->length_; }
|
||||
void consume(size_t bytes) override;
|
||||
bool has_buffered_data() const override { return this->length_ > 0; }
|
||||
|
||||
protected:
|
||||
const uint8_t *data_start_{nullptr};
|
||||
size_t length_{0};
|
||||
};
|
||||
|
||||
} // namespace audio
|
||||
|
||||
@@ -87,7 +87,10 @@ void BLENUS::setup() {
|
||||
global_ble_nus = this;
|
||||
#ifdef USE_LOGGER
|
||||
if (logger::global_logger != nullptr && this->expose_log_) {
|
||||
logger::global_logger->add_log_listener(this);
|
||||
logger::global_logger->add_log_callback(
|
||||
this, [](void *self, uint8_t level, const char *tag, const char *message, size_t message_len) {
|
||||
static_cast<BLENUS *>(self)->on_log(level, tag, message, message_len);
|
||||
});
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -10,12 +10,7 @@
|
||||
|
||||
namespace esphome::ble_nus {
|
||||
|
||||
class BLENUS : public Component
|
||||
#ifdef USE_LOGGER
|
||||
,
|
||||
public logger::LogListener
|
||||
#endif
|
||||
{
|
||||
class BLENUS : public Component {
|
||||
enum TxStatus {
|
||||
TX_DISABLED,
|
||||
TX_ENABLED,
|
||||
@@ -29,7 +24,7 @@ class BLENUS : public Component
|
||||
size_t write_array(const uint8_t *data, size_t len);
|
||||
void set_expose_log(bool expose_log) { this->expose_log_ = expose_log; }
|
||||
#ifdef USE_LOGGER
|
||||
void on_log(uint8_t level, const char *tag, const char *message, size_t message_len) override;
|
||||
void on_log(uint8_t level, const char *tag, const char *message, size_t message_len);
|
||||
#endif
|
||||
|
||||
protected:
|
||||
|
||||
@@ -23,9 +23,9 @@
|
||||
|
||||
namespace esphome::bluetooth_proxy {
|
||||
|
||||
static const esp_err_t ESP_GATT_NOT_CONNECTED = -1;
|
||||
static const int DONE_SENDING_SERVICES = -2;
|
||||
static const int INIT_SENDING_SERVICES = -3;
|
||||
static constexpr esp_err_t ESP_GATT_NOT_CONNECTED = -1;
|
||||
static constexpr int DONE_SENDING_SERVICES = -2;
|
||||
static constexpr int INIT_SENDING_SERVICES = -3;
|
||||
|
||||
using namespace esp32_ble_client;
|
||||
|
||||
@@ -35,8 +35,8 @@ using namespace esp32_ble_client;
|
||||
// Version 3: New connection API
|
||||
// Version 4: Pairing support
|
||||
// Version 5: Cache clear support
|
||||
static const uint32_t LEGACY_ACTIVE_CONNECTIONS_VERSION = 5;
|
||||
static const uint32_t LEGACY_PASSIVE_ONLY_VERSION = 1;
|
||||
static constexpr uint32_t LEGACY_ACTIVE_CONNECTIONS_VERSION = 5;
|
||||
static constexpr uint32_t LEGACY_PASSIVE_ONLY_VERSION = 1;
|
||||
|
||||
enum BluetoothProxyFeature : uint32_t {
|
||||
FEATURE_PASSIVE_SCAN = 1 << 0,
|
||||
|
||||
@@ -22,11 +22,11 @@ static const uint8_t BME680_REGISTER_CHIPID = 0xD0;
|
||||
|
||||
static const uint8_t BME680_REGISTER_FIELD0 = 0x1D;
|
||||
|
||||
const float BME680_GAS_LOOKUP_TABLE_1[16] PROGMEM = {0.0, 0.0, 0.0, 0.0, 0.0, -1.0, 0.0, -0.8,
|
||||
0.0, 0.0, -0.2, -0.5, 0.0, -1.0, 0.0, 0.0};
|
||||
constexpr float BME680_GAS_LOOKUP_TABLE_1[16] PROGMEM = {0.0, 0.0, 0.0, 0.0, 0.0, -1.0, 0.0, -0.8,
|
||||
0.0, 0.0, -0.2, -0.5, 0.0, -1.0, 0.0, 0.0};
|
||||
|
||||
const float BME680_GAS_LOOKUP_TABLE_2[16] PROGMEM = {0.0, 0.0, 0.0, 0.0, 0.1, 0.7, 0.0, -0.8,
|
||||
-0.1, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0};
|
||||
constexpr float BME680_GAS_LOOKUP_TABLE_2[16] PROGMEM = {0.0, 0.0, 0.0, 0.0, 0.1, 0.7, 0.0, -0.8,
|
||||
-0.1, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0};
|
||||
|
||||
[[maybe_unused]] static const char *oversampling_to_str(BME680Oversampling oversampling) {
|
||||
switch (oversampling) {
|
||||
|
||||
@@ -3,18 +3,22 @@
|
||||
namespace esphome::camera {
|
||||
|
||||
BufferImpl::BufferImpl(size_t size) {
|
||||
this->data_ = this->allocator_.allocate(size);
|
||||
RAMAllocator<uint8_t> allocator;
|
||||
this->data_ = allocator.allocate(size);
|
||||
this->size_ = size;
|
||||
}
|
||||
|
||||
BufferImpl::BufferImpl(CameraImageSpec *spec) {
|
||||
this->data_ = this->allocator_.allocate(spec->bytes_per_image());
|
||||
RAMAllocator<uint8_t> allocator;
|
||||
this->data_ = allocator.allocate(spec->bytes_per_image());
|
||||
this->size_ = spec->bytes_per_image();
|
||||
}
|
||||
|
||||
BufferImpl::~BufferImpl() {
|
||||
if (this->data_ != nullptr)
|
||||
this->allocator_.deallocate(this->data_, this->size_);
|
||||
if (this->data_ != nullptr) {
|
||||
RAMAllocator<uint8_t> allocator;
|
||||
allocator.deallocate(this->data_, this->size_);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace esphome::camera
|
||||
|
||||
@@ -18,7 +18,6 @@ class BufferImpl : public Buffer {
|
||||
~BufferImpl() override;
|
||||
|
||||
protected:
|
||||
RAMAllocator<uint8_t> allocator_;
|
||||
size_t size_{};
|
||||
uint8_t *data_{};
|
||||
};
|
||||
|
||||
@@ -4,7 +4,8 @@ namespace esphome::camera_encoder {
|
||||
|
||||
bool EncoderBufferImpl::set_buffer_size(size_t size) {
|
||||
if (size > this->capacity_) {
|
||||
uint8_t *p = this->allocator_.reallocate(this->data_, size);
|
||||
RAMAllocator<uint8_t> allocator;
|
||||
uint8_t *p = allocator.reallocate(this->data_, size);
|
||||
if (p == nullptr)
|
||||
return false;
|
||||
|
||||
@@ -16,8 +17,10 @@ bool EncoderBufferImpl::set_buffer_size(size_t size) {
|
||||
}
|
||||
|
||||
EncoderBufferImpl::~EncoderBufferImpl() {
|
||||
if (this->data_ != nullptr)
|
||||
this->allocator_.deallocate(this->data_, this->capacity_);
|
||||
if (this->data_ != nullptr) {
|
||||
RAMAllocator<uint8_t> allocator;
|
||||
allocator.deallocate(this->data_, this->capacity_);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace esphome::camera_encoder
|
||||
|
||||
@@ -16,7 +16,6 @@ class EncoderBufferImpl : public camera::EncoderBuffer {
|
||||
~EncoderBufferImpl() override;
|
||||
|
||||
protected:
|
||||
RAMAllocator<uint8_t> allocator_;
|
||||
size_t capacity_{};
|
||||
size_t size_{};
|
||||
uint8_t *data_{};
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
namespace esphome::captive_portal {
|
||||
|
||||
#ifdef USE_CAPTIVE_PORTAL_GZIP
|
||||
const uint8_t INDEX_GZ[] PROGMEM = {
|
||||
constexpr uint8_t INDEX_GZ[] PROGMEM = {
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x95, 0x16, 0x6b, 0x8f, 0xdb, 0x36, 0xf2, 0x7b, 0x7e,
|
||||
0x05, 0x8f, 0x49, 0xbb, 0x52, 0xb3, 0x7a, 0x7a, 0xed, 0x6c, 0x24, 0x51, 0x45, 0x9a, 0xbb, 0xa2, 0x05, 0x9a, 0x36,
|
||||
0xc0, 0x6e, 0x73, 0x1f, 0x82, 0x00, 0x4b, 0x53, 0x23, 0x8b, 0x31, 0x45, 0xea, 0x48, 0xca, 0x8f, 0x18, 0xbe, 0xdf,
|
||||
@@ -86,7 +86,7 @@ const uint8_t INDEX_GZ[] PROGMEM = {
|
||||
0xfc, 0xda, 0xd1, 0xf8, 0xe9, 0xa3, 0xe1, 0xa6, 0xfb, 0x1f, 0x53, 0x58, 0x46, 0xb2, 0xf9, 0x0a, 0x00, 0x00};
|
||||
|
||||
#else // Brotli (default, smaller)
|
||||
const uint8_t INDEX_BR[] PROGMEM = {
|
||||
constexpr uint8_t INDEX_BR[] PROGMEM = {
|
||||
0x1b, 0xf8, 0x0a, 0x00, 0x64, 0x5a, 0xd3, 0xfa, 0xe7, 0xf3, 0x62, 0xd8, 0x06, 0x1b, 0xe9, 0x6a, 0x8a, 0x81, 0x2b,
|
||||
0xb5, 0x49, 0x14, 0x37, 0xdc, 0x9e, 0x1a, 0xcb, 0x56, 0x87, 0xfb, 0xff, 0xf7, 0x73, 0x75, 0x12, 0x0a, 0xd6, 0x48,
|
||||
0x84, 0xc6, 0x21, 0xa4, 0x6d, 0xb5, 0x71, 0xef, 0x13, 0xbe, 0x4e, 0x54, 0xf1, 0x64, 0x8f, 0x3f, 0xcc, 0x9a, 0x78,
|
||||
|
||||
@@ -53,7 +53,7 @@ void DNSServer::start(const network::IPAddress &ip) {
|
||||
#endif
|
||||
|
||||
// Create loop-monitored UDP socket
|
||||
this->socket_ = socket::socket_ip_loop_monitored(SOCK_DGRAM, IPPROTO_UDP);
|
||||
this->socket_ = socket::socket_ip_loop_monitored(SOCK_DGRAM, IPPROTO_UDP).release();
|
||||
if (this->socket_ == nullptr) {
|
||||
ESP_LOGE(TAG, "Socket create failed");
|
||||
return;
|
||||
@@ -70,17 +70,14 @@ void DNSServer::start(const network::IPAddress &ip) {
|
||||
int err = this->socket_->bind((struct sockaddr *) &server_addr, addr_len);
|
||||
if (err != 0) {
|
||||
ESP_LOGE(TAG, "Bind failed: %d", errno);
|
||||
this->socket_ = nullptr;
|
||||
this->destroy_socket_();
|
||||
return;
|
||||
}
|
||||
ESP_LOGV(TAG, "Bound to port %d", DNS_PORT);
|
||||
}
|
||||
|
||||
void DNSServer::stop() {
|
||||
if (this->socket_ != nullptr) {
|
||||
this->socket_->close();
|
||||
this->socket_ = nullptr;
|
||||
}
|
||||
this->destroy_socket_();
|
||||
ESP_LOGV(TAG, "Stopped");
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#pragma once
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include <memory>
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/components/network/ip_address.h"
|
||||
#include "esphome/components/socket/socket.h"
|
||||
@@ -15,9 +14,15 @@ class DNSServer {
|
||||
void process_next_request();
|
||||
|
||||
protected:
|
||||
// No explicit close() needed — listen sockets have no active connections on
|
||||
// failure/shutdown. Destructor handles fd cleanup (close or abort per platform).
|
||||
inline void destroy_socket_() {
|
||||
delete this->socket_;
|
||||
this->socket_ = nullptr;
|
||||
}
|
||||
static constexpr size_t DNS_BUFFER_SIZE = 192;
|
||||
|
||||
std::unique_ptr<socket::Socket> socket_{nullptr};
|
||||
socket::Socket *socket_{nullptr};
|
||||
network::IPAddress server_ip_;
|
||||
uint8_t buffer_[DNS_BUFFER_SIZE];
|
||||
};
|
||||
|
||||
@@ -9,6 +9,7 @@ from esphome.const import (
|
||||
CONF_DATA,
|
||||
CONF_FREQUENCY,
|
||||
CONF_ID,
|
||||
CONF_VALUE,
|
||||
CONF_WAIT_TIME,
|
||||
)
|
||||
from esphome.core import ID
|
||||
@@ -333,3 +334,94 @@ async def send_packet_action_to_code(config, action_id, template_arg, args):
|
||||
arr = cg.static_const_array(arr_id, cg.ArrayInitializer(*data))
|
||||
cg.add(var.set_data_static(arr, len(data)))
|
||||
return var
|
||||
|
||||
|
||||
# Setter action definitions: (setter_name, validator, template_type, enum_map)
|
||||
_SETTER_ACTIONS = [
|
||||
(
|
||||
"set_frequency",
|
||||
cv.All(cv.frequency, cv.float_range(min=300.0e6, max=928.0e6)),
|
||||
float,
|
||||
None,
|
||||
),
|
||||
("set_output_power", cv.float_range(min=-30.0, max=11.0), float, None),
|
||||
("set_modulation_type", cv.enum(MODULATION, upper=False), Modulation, MODULATION),
|
||||
("set_symbol_rate", cv.float_range(min=600, max=500000), float, None),
|
||||
(
|
||||
"set_rx_attenuation",
|
||||
cv.enum(RX_ATTENUATION, upper=False),
|
||||
RxAttenuation,
|
||||
RX_ATTENUATION,
|
||||
),
|
||||
("set_dc_blocking_filter", cv.boolean, bool, None),
|
||||
("set_manchester", cv.boolean, bool, None),
|
||||
(
|
||||
"set_filter_bandwidth",
|
||||
cv.All(cv.frequency, cv.float_range(min=58000, max=812000)),
|
||||
float,
|
||||
None,
|
||||
),
|
||||
(
|
||||
"set_fsk_deviation",
|
||||
cv.All(cv.frequency, cv.float_range(min=1500, max=381000)),
|
||||
float,
|
||||
None,
|
||||
),
|
||||
("set_msk_deviation", cv.int_range(min=1, max=8), cg.uint8, None),
|
||||
("set_channel", cv.uint8_t, cg.uint8, None),
|
||||
(
|
||||
"set_channel_spacing",
|
||||
cv.All(cv.frequency, cv.float_range(min=25000, max=405000)),
|
||||
float,
|
||||
None,
|
||||
),
|
||||
(
|
||||
"set_if_frequency",
|
||||
cv.All(cv.frequency, cv.float_range(min=25000, max=788000)),
|
||||
float,
|
||||
None,
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
def _register_setter_actions():
|
||||
for setter_name, validator, templ_type, enum_map in _SETTER_ACTIONS:
|
||||
class_name = (
|
||||
"".join(word.capitalize() for word in setter_name.split("_")) + "Action"
|
||||
)
|
||||
action_cls = ns.class_(
|
||||
class_name, automation.Action, cg.Parented.template(CC1101Component)
|
||||
)
|
||||
schema = cv.maybe_simple_value(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(CC1101Component),
|
||||
cv.Required(CONF_VALUE): cv.templatable(validator),
|
||||
},
|
||||
key=CONF_VALUE,
|
||||
)
|
||||
|
||||
async def _setter_action_to_code(
|
||||
config,
|
||||
action_id,
|
||||
template_arg,
|
||||
args,
|
||||
_setter=setter_name,
|
||||
_type=templ_type,
|
||||
_map=enum_map,
|
||||
):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
data = config[CONF_VALUE]
|
||||
if cg.is_template(data):
|
||||
templ_ = await cg.templatable(data, args, _type)
|
||||
cg.add(getattr(var, _setter)(templ_))
|
||||
else:
|
||||
cg.add(getattr(var, _setter)(_map[data] if _map else data))
|
||||
return var
|
||||
|
||||
automation.register_action(f"cc1101.{setter_name}", action_cls, schema)(
|
||||
_setter_action_to_code
|
||||
)
|
||||
|
||||
|
||||
_register_setter_actions()
|
||||
|
||||
@@ -161,4 +161,82 @@ template<typename... Ts> class SendPacketAction : public Action<Ts...>, public P
|
||||
size_t data_static_len_{0};
|
||||
};
|
||||
|
||||
template<typename... Ts> class SetSymbolRateAction : public Action<Ts...>, public Parented<CC1101Component> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(float, symbol_rate)
|
||||
void play(const Ts &...x) override { this->parent_->set_symbol_rate(this->symbol_rate_.value(x...)); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class SetFrequencyAction : public Action<Ts...>, public Parented<CC1101Component> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(float, frequency)
|
||||
void play(const Ts &...x) override { this->parent_->set_frequency(this->frequency_.value(x...)); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class SetOutputPowerAction : public Action<Ts...>, public Parented<CC1101Component> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(float, output_power)
|
||||
void play(const Ts &...x) override { this->parent_->set_output_power(this->output_power_.value(x...)); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class SetModulationTypeAction : public Action<Ts...>, public Parented<CC1101Component> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(Modulation, modulation_type)
|
||||
void play(const Ts &...x) override { this->parent_->set_modulation_type(this->modulation_type_.value(x...)); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class SetRxAttenuationAction : public Action<Ts...>, public Parented<CC1101Component> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(RxAttenuation, rx_attenuation)
|
||||
void play(const Ts &...x) override { this->parent_->set_rx_attenuation(this->rx_attenuation_.value(x...)); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class SetDcBlockingFilterAction : public Action<Ts...>, public Parented<CC1101Component> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(bool, dc_blocking_filter)
|
||||
void play(const Ts &...x) override { this->parent_->set_dc_blocking_filter(this->dc_blocking_filter_.value(x...)); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class SetManchesterAction : public Action<Ts...>, public Parented<CC1101Component> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(bool, manchester)
|
||||
void play(const Ts &...x) override { this->parent_->set_manchester(this->manchester_.value(x...)); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class SetFilterBandwidthAction : public Action<Ts...>, public Parented<CC1101Component> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(float, filter_bandwidth)
|
||||
void play(const Ts &...x) override { this->parent_->set_filter_bandwidth(this->filter_bandwidth_.value(x...)); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class SetFskDeviationAction : public Action<Ts...>, public Parented<CC1101Component> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(float, fsk_deviation)
|
||||
void play(const Ts &...x) override { this->parent_->set_fsk_deviation(this->fsk_deviation_.value(x...)); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class SetMskDeviationAction : public Action<Ts...>, public Parented<CC1101Component> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(uint8_t, msk_deviation)
|
||||
void play(const Ts &...x) override { this->parent_->set_msk_deviation(this->msk_deviation_.value(x...)); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class SetChannelAction : public Action<Ts...>, public Parented<CC1101Component> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(uint8_t, channel)
|
||||
void play(const Ts &...x) override { this->parent_->set_channel(this->channel_.value(x...)); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class SetChannelSpacingAction : public Action<Ts...>, public Parented<CC1101Component> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(float, channel_spacing)
|
||||
void play(const Ts &...x) override { this->parent_->set_channel_spacing(this->channel_spacing_.value(x...)); }
|
||||
};
|
||||
|
||||
template<typename... Ts> class SetIfFrequencyAction : public Action<Ts...>, public Parented<CC1101Component> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(float, if_frequency)
|
||||
void play(const Ts &...x) override { this->parent_->set_if_frequency(this->if_frequency_.value(x...)); }
|
||||
};
|
||||
|
||||
} // namespace esphome::cc1101
|
||||
|
||||
@@ -126,7 +126,7 @@ void LinearCombinationComponent::setup() {
|
||||
}
|
||||
|
||||
void LinearCombinationComponent::handle_new_value(float value) {
|
||||
// Multiplies each sensor state by a configured coeffecient and then sums
|
||||
// Multiplies each sensor state by a configured coefficient and then sums
|
||||
|
||||
if (!std::isfinite(value))
|
||||
return;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import logging
|
||||
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import sensor
|
||||
import esphome.config_validation as cv
|
||||
@@ -15,6 +17,8 @@ from esphome.const import (
|
||||
)
|
||||
from esphome.core.entity_helpers import inherit_property_from
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
CODEOWNERS = ["@Cat-Ion", "@kahrendt"]
|
||||
|
||||
combination_ns = cg.esphome_ns.namespace("combination")
|
||||
@@ -47,7 +51,8 @@ SumCombinationComponent = combination_ns.class_(
|
||||
"SumCombinationComponent", cg.Component, sensor.Sensor
|
||||
)
|
||||
|
||||
CONF_COEFFECIENT = "coeffecient"
|
||||
CONF_COEFFICIENT = "coefficient"
|
||||
CONF_COEFFECIENT = "coeffecient" # Deprecated, remove before 2026.12.0
|
||||
CONF_ERROR = "error"
|
||||
CONF_KALMAN = "kalman"
|
||||
CONF_LINEAR = "linear"
|
||||
@@ -68,11 +73,34 @@ KALMAN_SOURCE_SCHEMA = cv.Schema(
|
||||
}
|
||||
)
|
||||
|
||||
LINEAR_SOURCE_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_SOURCE): cv.use_id(sensor.Sensor),
|
||||
cv.Required(CONF_COEFFECIENT): cv.templatable(cv.float_),
|
||||
}
|
||||
|
||||
def _migrate_coeffecient(config):
|
||||
"""Migrate deprecated 'coeffecient' spelling to 'coefficient'."""
|
||||
if CONF_COEFFECIENT in config:
|
||||
if CONF_COEFFICIENT in config:
|
||||
raise cv.Invalid(
|
||||
f"Cannot specify both '{CONF_COEFFICIENT}' and '{CONF_COEFFECIENT}'"
|
||||
)
|
||||
_LOGGER.warning(
|
||||
"'%s' is deprecated, use '%s' instead. Will be removed in 2026.12.0",
|
||||
CONF_COEFFECIENT,
|
||||
CONF_COEFFICIENT,
|
||||
)
|
||||
config[CONF_COEFFICIENT] = config.pop(CONF_COEFFECIENT)
|
||||
elif CONF_COEFFICIENT not in config:
|
||||
raise cv.Invalid(f"'{CONF_COEFFICIENT}' is a required option")
|
||||
return config
|
||||
|
||||
|
||||
LINEAR_SOURCE_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_SOURCE): cv.use_id(sensor.Sensor),
|
||||
cv.Optional(CONF_COEFFICIENT): cv.templatable(cv.float_),
|
||||
cv.Optional(CONF_COEFFECIENT): cv.templatable(cv.float_),
|
||||
}
|
||||
),
|
||||
_migrate_coeffecient,
|
||||
)
|
||||
|
||||
SENSOR_ONLY_SOURCE_SCHEMA = cv.Schema(
|
||||
@@ -162,12 +190,12 @@ async def to_code(config):
|
||||
)
|
||||
cg.add(var.add_source(source, error))
|
||||
elif config[CONF_TYPE] == CONF_LINEAR:
|
||||
coeffecient = await cg.templatable(
|
||||
source_conf[CONF_COEFFECIENT],
|
||||
coefficient = await cg.templatable(
|
||||
source_conf[CONF_COEFFICIENT],
|
||||
[(float, "x")],
|
||||
cg.float_,
|
||||
)
|
||||
cg.add(var.add_source(source, coeffecient))
|
||||
cg.add(var.add_source(source, coefficient))
|
||||
else:
|
||||
cg.add(var.add_source(source))
|
||||
|
||||
|
||||
@@ -15,29 +15,29 @@ static const char *const TAG = "cse7761";
|
||||
* https://github.com/arendst/Tasmota/blob/development/tasmota/xnrg_19_cse7761.ino
|
||||
\*********************************************************************************************/
|
||||
|
||||
static const int CSE7761_UREF = 42563; // RmsUc
|
||||
static const int CSE7761_IREF = 52241; // RmsIAC
|
||||
static const int CSE7761_PREF = 44513; // PowerPAC
|
||||
static constexpr int CSE7761_UREF = 42563; // RmsUc
|
||||
static constexpr int CSE7761_IREF = 52241; // RmsIAC
|
||||
static constexpr int CSE7761_PREF = 44513; // PowerPAC
|
||||
|
||||
static const uint8_t CSE7761_REG_SYSCON = 0x00; // (2) System Control Register (0x0A04)
|
||||
static const uint8_t CSE7761_REG_EMUCON = 0x01; // (2) Metering control register (0x0000)
|
||||
static const uint8_t CSE7761_REG_EMUCON2 = 0x13; // (2) Metering control register 2 (0x0001)
|
||||
static const uint8_t CSE7761_REG_PULSE1SEL = 0x1D; // (2) Pin function output select register (0x3210)
|
||||
static constexpr uint8_t CSE7761_REG_SYSCON = 0x00; // (2) System Control Register (0x0A04)
|
||||
static constexpr uint8_t CSE7761_REG_EMUCON = 0x01; // (2) Metering control register (0x0000)
|
||||
static constexpr uint8_t CSE7761_REG_EMUCON2 = 0x13; // (2) Metering control register 2 (0x0001)
|
||||
static constexpr uint8_t CSE7761_REG_PULSE1SEL = 0x1D; // (2) Pin function output select register (0x3210)
|
||||
|
||||
static const uint8_t CSE7761_REG_RMSIA = 0x24; // (3) The effective value of channel A current (0x000000)
|
||||
static const uint8_t CSE7761_REG_RMSIB = 0x25; // (3) The effective value of channel B current (0x000000)
|
||||
static const uint8_t CSE7761_REG_RMSU = 0x26; // (3) Voltage RMS (0x000000)
|
||||
static const uint8_t CSE7761_REG_POWERPA = 0x2C; // (4) Channel A active power, update rate 27.2Hz (0x00000000)
|
||||
static const uint8_t CSE7761_REG_POWERPB = 0x2D; // (4) Channel B active power, update rate 27.2Hz (0x00000000)
|
||||
static const uint8_t CSE7761_REG_SYSSTATUS = 0x43; // (1) System status register
|
||||
static constexpr uint8_t CSE7761_REG_RMSIA = 0x24; // (3) The effective value of channel A current (0x000000)
|
||||
static constexpr uint8_t CSE7761_REG_RMSIB = 0x25; // (3) The effective value of channel B current (0x000000)
|
||||
static constexpr uint8_t CSE7761_REG_RMSU = 0x26; // (3) Voltage RMS (0x000000)
|
||||
static constexpr uint8_t CSE7761_REG_POWERPA = 0x2C; // (4) Channel A active power, update rate 27.2Hz (0x00000000)
|
||||
static constexpr uint8_t CSE7761_REG_POWERPB = 0x2D; // (4) Channel B active power, update rate 27.2Hz (0x00000000)
|
||||
static constexpr uint8_t CSE7761_REG_SYSSTATUS = 0x43; // (1) System status register
|
||||
|
||||
static const uint8_t CSE7761_REG_COEFFCHKSUM = 0x6F; // (2) Coefficient checksum
|
||||
static const uint8_t CSE7761_REG_RMSIAC = 0x70; // (2) Channel A effective current conversion coefficient
|
||||
static constexpr uint8_t CSE7761_REG_COEFFCHKSUM = 0x6F; // (2) Coefficient checksum
|
||||
static constexpr uint8_t CSE7761_REG_RMSIAC = 0x70; // (2) Channel A effective current conversion coefficient
|
||||
|
||||
static const uint8_t CSE7761_SPECIAL_COMMAND = 0xEA; // Start special command
|
||||
static const uint8_t CSE7761_CMD_RESET = 0x96; // Reset command, after receiving the command, the chip resets
|
||||
static const uint8_t CSE7761_CMD_CLOSE_WRITE = 0xDC; // Close write operation
|
||||
static const uint8_t CSE7761_CMD_ENABLE_WRITE = 0xE5; // Enable write operation
|
||||
static constexpr uint8_t CSE7761_SPECIAL_COMMAND = 0xEA; // Start special command
|
||||
static constexpr uint8_t CSE7761_CMD_RESET = 0x96; // Reset command, after receiving the command, the chip resets
|
||||
static constexpr uint8_t CSE7761_CMD_CLOSE_WRITE = 0xDC; // Close write operation
|
||||
static constexpr uint8_t CSE7761_CMD_ENABLE_WRITE = 0xE5; // Enable write operation
|
||||
|
||||
enum CSE7761 { RMS_IAC, RMS_IBC, RMS_UC, POWER_PAC, POWER_PBC, POWER_SC, ENERGY_AC, ENERGY_BC };
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#ifdef USE_ZEPHYR
|
||||
#include <climits>
|
||||
#include "esphome/core/log.h"
|
||||
#include <esphome/components/zephyr/reset_reason.h>
|
||||
#include <zephyr/drivers/hwinfo.h>
|
||||
#include <hal/nrf_power.h>
|
||||
#include <cstdint>
|
||||
@@ -15,16 +16,6 @@ static const char *const TAG = "debug";
|
||||
constexpr std::uintptr_t MBR_PARAM_PAGE_ADDR = 0xFFC;
|
||||
constexpr std::uintptr_t MBR_BOOTLOADER_ADDR = 0xFF8;
|
||||
|
||||
static size_t append_reset_reason(char *buf, size_t size, size_t pos, bool set, const char *reason) {
|
||||
if (!set) {
|
||||
return pos;
|
||||
}
|
||||
if (pos > 0) {
|
||||
pos = buf_append_printf(buf, size, pos, ", ");
|
||||
}
|
||||
return buf_append_printf(buf, size, pos, "%s", reason);
|
||||
}
|
||||
|
||||
static inline uint32_t read_mem_u32(uintptr_t addr) {
|
||||
return *reinterpret_cast<volatile uint32_t *>(addr); // NOLINT(performance-no-int-to-ptr)
|
||||
}
|
||||
@@ -57,39 +48,7 @@ static inline uint32_t sd_version_get() {
|
||||
}
|
||||
|
||||
const char *DebugComponent::get_reset_reason_(std::span<char, RESET_REASON_BUFFER_SIZE> buffer) {
|
||||
char *buf = buffer.data();
|
||||
const size_t size = RESET_REASON_BUFFER_SIZE;
|
||||
|
||||
uint32_t cause;
|
||||
auto ret = hwinfo_get_reset_cause(&cause);
|
||||
if (ret) {
|
||||
ESP_LOGE(TAG, "Unable to get reset cause: %d", ret);
|
||||
buf[0] = '\0';
|
||||
return buf;
|
||||
}
|
||||
size_t pos = 0;
|
||||
|
||||
pos = append_reset_reason(buf, size, pos, cause & RESET_PIN, "External pin");
|
||||
pos = append_reset_reason(buf, size, pos, cause & RESET_SOFTWARE, "Software reset");
|
||||
pos = append_reset_reason(buf, size, pos, cause & RESET_BROWNOUT, "Brownout (drop in voltage)");
|
||||
pos = append_reset_reason(buf, size, pos, cause & RESET_POR, "Power-on reset (POR)");
|
||||
pos = append_reset_reason(buf, size, pos, cause & RESET_WATCHDOG, "Watchdog timer expiration");
|
||||
pos = append_reset_reason(buf, size, pos, cause & RESET_DEBUG, "Debug event");
|
||||
pos = append_reset_reason(buf, size, pos, cause & RESET_SECURITY, "Security violation");
|
||||
pos = append_reset_reason(buf, size, pos, cause & RESET_LOW_POWER_WAKE, "Waking up from low power mode");
|
||||
pos = append_reset_reason(buf, size, pos, cause & RESET_CPU_LOCKUP, "CPU lock-up detected");
|
||||
pos = append_reset_reason(buf, size, pos, cause & RESET_PARITY, "Parity error");
|
||||
pos = append_reset_reason(buf, size, pos, cause & RESET_PLL, "PLL error");
|
||||
pos = append_reset_reason(buf, size, pos, cause & RESET_CLOCK, "Clock error");
|
||||
pos = append_reset_reason(buf, size, pos, cause & RESET_HARDWARE, "Hardware reset");
|
||||
pos = append_reset_reason(buf, size, pos, cause & RESET_USER, "User reset");
|
||||
pos = append_reset_reason(buf, size, pos, cause & RESET_TEMPERATURE, "Temperature reset");
|
||||
|
||||
// Ensure null termination if nothing was written
|
||||
if (pos == 0) {
|
||||
buf[0] = '\0';
|
||||
}
|
||||
|
||||
const char *buf = zephyr::get_reset_reason(buffer);
|
||||
ESP_LOGD(TAG, "Reset Reason: %s", buf);
|
||||
return buf;
|
||||
}
|
||||
|
||||
@@ -9,8 +9,7 @@ namespace esphome {
|
||||
namespace display {
|
||||
static const char *const TAG = "display";
|
||||
|
||||
const Color COLOR_OFF(0, 0, 0, 0);
|
||||
const Color COLOR_ON(255, 255, 255, 255);
|
||||
// COLOR_OFF and COLOR_ON are now inline constexpr in display.h
|
||||
|
||||
void Display::fill(Color color) { this->filled_rectangle(0, 0, this->get_width(), this->get_height(), color); }
|
||||
void Display::clear() { this->fill(COLOR_OFF); }
|
||||
@@ -811,9 +810,9 @@ bool Display::clamp_y_(int y, int h, int &min_y, int &max_y) {
|
||||
return min_y < max_y;
|
||||
}
|
||||
|
||||
const uint8_t TESTCARD_FONT[3][8] PROGMEM = {{0x41, 0x7F, 0x7F, 0x09, 0x19, 0x7F, 0x66, 0x00}, // 'R'
|
||||
{0x1C, 0x3E, 0x63, 0x41, 0x51, 0x73, 0x72, 0x00}, // 'G'
|
||||
{0x41, 0x7F, 0x7F, 0x49, 0x49, 0x7F, 0x36, 0x00}}; // 'B'
|
||||
constexpr uint8_t TESTCARD_FONT[3][8] PROGMEM = {{0x41, 0x7F, 0x7F, 0x09, 0x19, 0x7F, 0x66, 0x00}, // 'R'
|
||||
{0x1C, 0x3E, 0x63, 0x41, 0x51, 0x73, 0x72, 0x00}, // 'G'
|
||||
{0x41, 0x7F, 0x7F, 0x49, 0x49, 0x7F, 0x36, 0x00}}; // 'B'
|
||||
|
||||
void Display::test_card() {
|
||||
int w = get_width(), h = get_height(), image_w, image_h;
|
||||
|
||||
@@ -298,9 +298,9 @@ using display_writer_t = DisplayWriter<Display>;
|
||||
}
|
||||
|
||||
/// Turn the pixel OFF.
|
||||
extern const Color COLOR_OFF;
|
||||
inline constexpr Color COLOR_OFF(0, 0, 0, 0);
|
||||
/// Turn the pixel ON.
|
||||
extern const Color COLOR_ON;
|
||||
inline constexpr Color COLOR_ON(255, 255, 255, 255);
|
||||
|
||||
class BaseImage {
|
||||
public:
|
||||
|
||||
@@ -64,6 +64,9 @@ class Dsmr : public Component, public uart::UARTDevice {
|
||||
void dump_config() override;
|
||||
|
||||
void set_decryption_key(const char *decryption_key);
|
||||
// Remove before 2026.8.0
|
||||
ESPDEPRECATED("Pass .c_str() - e.g. set_decryption_key(key.c_str()). Removed in 2026.8.0", "2026.2.0")
|
||||
void set_decryption_key(const std::string &decryption_key) { this->set_decryption_key(decryption_key.c_str()); }
|
||||
void set_max_telegram_length(size_t length) { this->max_telegram_len_ = length; }
|
||||
void set_request_pin(GPIOPin *request_pin) { this->request_pin_ = request_pin; }
|
||||
void set_request_interval(uint32_t interval) { this->request_interval_ = interval; }
|
||||
|
||||
@@ -14,12 +14,17 @@ static const int PORT = 5568;
|
||||
E131Component::E131Component() {}
|
||||
|
||||
E131Component::~E131Component() {
|
||||
#if defined(USE_SOCKET_IMPL_BSD_SOCKETS) || defined(USE_SOCKET_IMPL_LWIP_SOCKETS)
|
||||
if (this->socket_) {
|
||||
this->socket_->close();
|
||||
}
|
||||
#elif defined(USE_SOCKET_IMPL_LWIP_TCP)
|
||||
this->udp_.stop();
|
||||
#endif
|
||||
}
|
||||
|
||||
void E131Component::setup() {
|
||||
#if defined(USE_SOCKET_IMPL_BSD_SOCKETS) || defined(USE_SOCKET_IMPL_LWIP_SOCKETS)
|
||||
this->socket_ = socket::socket_ip(SOCK_DGRAM, IPPROTO_IP);
|
||||
|
||||
int enable = 1;
|
||||
@@ -50,6 +55,13 @@ void E131Component::setup() {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
#elif defined(USE_SOCKET_IMPL_LWIP_TCP)
|
||||
if (!this->udp_.begin(PORT)) {
|
||||
ESP_LOGW(TAG, "Cannot bind E1.31 to port %d.", PORT);
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
join_igmp_groups_();
|
||||
}
|
||||
@@ -58,19 +70,20 @@ void E131Component::loop() {
|
||||
E131Packet packet;
|
||||
int universe = 0;
|
||||
uint8_t buf[1460];
|
||||
ssize_t len;
|
||||
|
||||
ssize_t len = this->socket_->read(buf, sizeof(buf));
|
||||
if (len == -1) {
|
||||
return;
|
||||
}
|
||||
// Drain all queued packets so multi-universe frames are applied
|
||||
// atomically before the light writes. Without this, each universe
|
||||
// packet would trigger a separate full-strip write causing tearing.
|
||||
while ((len = this->read_(buf, sizeof(buf))) > 0) {
|
||||
if (!this->packet_(buf, (size_t) len, universe, packet)) {
|
||||
ESP_LOGV(TAG, "Invalid packet received of size %d.", (int) len);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!this->packet_(buf, (size_t) len, universe, packet)) {
|
||||
ESP_LOGV(TAG, "Invalid packet received of size %zd.", len);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this->process_(universe, packet)) {
|
||||
ESP_LOGV(TAG, "Ignored packet for %d universe of size %d.", universe, packet.count);
|
||||
if (!this->process_(universe, packet)) {
|
||||
ESP_LOGV(TAG, "Ignored packet for %d universe of size %d.", universe, packet.count);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
#pragma once
|
||||
#include "esphome/core/defines.h"
|
||||
#ifdef USE_NETWORK
|
||||
#if defined(USE_SOCKET_IMPL_BSD_SOCKETS) || defined(USE_SOCKET_IMPL_LWIP_SOCKETS)
|
||||
#include "esphome/components/socket/socket.h"
|
||||
#elif defined(USE_SOCKET_IMPL_LWIP_TCP)
|
||||
#include <WiFiUdp.h>
|
||||
#endif
|
||||
#include "esphome/core/component.h"
|
||||
|
||||
#include <cinttypes>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
@@ -23,6 +26,11 @@ struct E131Packet {
|
||||
uint8_t values[E131_MAX_PROPERTY_VALUES_COUNT];
|
||||
};
|
||||
|
||||
struct UniverseConsumer {
|
||||
uint16_t universe;
|
||||
uint16_t consumers;
|
||||
};
|
||||
|
||||
class E131Component : public esphome::Component {
|
||||
public:
|
||||
E131Component();
|
||||
@@ -38,16 +46,30 @@ class E131Component : public esphome::Component {
|
||||
void set_method(E131ListenMethod listen_method) { this->listen_method_ = listen_method; }
|
||||
|
||||
protected:
|
||||
inline ssize_t read_(uint8_t *buf, size_t len) {
|
||||
#if defined(USE_SOCKET_IMPL_BSD_SOCKETS) || defined(USE_SOCKET_IMPL_LWIP_SOCKETS)
|
||||
return this->socket_->read(buf, len);
|
||||
#elif defined(USE_SOCKET_IMPL_LWIP_TCP)
|
||||
if (!this->udp_.parsePacket())
|
||||
return -1;
|
||||
return this->udp_.read(buf, len);
|
||||
#endif
|
||||
}
|
||||
bool packet_(const uint8_t *data, size_t len, int &universe, E131Packet &packet);
|
||||
bool process_(int universe, const E131Packet &packet);
|
||||
bool join_igmp_groups_();
|
||||
UniverseConsumer *find_universe_(int universe);
|
||||
void join_(int universe);
|
||||
void leave_(int universe);
|
||||
|
||||
E131ListenMethod listen_method_{E131_MULTICAST};
|
||||
#if defined(USE_SOCKET_IMPL_BSD_SOCKETS) || defined(USE_SOCKET_IMPL_LWIP_SOCKETS)
|
||||
std::unique_ptr<socket::Socket> socket_;
|
||||
#elif defined(USE_SOCKET_IMPL_LWIP_TCP)
|
||||
WiFiUDP udp_;
|
||||
#endif
|
||||
std::vector<E131AddressableLightEffect *> light_effects_;
|
||||
std::map<int, int> universe_consumers_;
|
||||
std::vector<UniverseConsumer> universe_consumers_;
|
||||
};
|
||||
|
||||
} // namespace e131
|
||||
|
||||
@@ -60,17 +60,19 @@ union E131RawPacket {
|
||||
const size_t E131_MIN_PACKET_SIZE = reinterpret_cast<size_t>(&((E131RawPacket *) nullptr)->property_values[1]);
|
||||
|
||||
bool E131Component::join_igmp_groups_() {
|
||||
if (listen_method_ != E131_MULTICAST)
|
||||
if (this->listen_method_ != E131_MULTICAST)
|
||||
return false;
|
||||
#if defined(USE_SOCKET_IMPL_BSD_SOCKETS) || defined(USE_SOCKET_IMPL_LWIP_SOCKETS)
|
||||
if (this->socket_ == nullptr)
|
||||
return false;
|
||||
#endif
|
||||
|
||||
for (auto universe : universe_consumers_) {
|
||||
if (!universe.second)
|
||||
for (auto &entry : this->universe_consumers_) {
|
||||
if (!entry.consumers)
|
||||
continue;
|
||||
|
||||
ip4_addr_t multicast_addr =
|
||||
network::IPAddress(239, 255, ((universe.first >> 8) & 0xff), ((universe.first >> 0) & 0xff));
|
||||
network::IPAddress(239, 255, ((entry.universe >> 8) & 0xff), ((entry.universe >> 0) & 0xff));
|
||||
|
||||
err_t err;
|
||||
{
|
||||
@@ -79,34 +81,47 @@ bool E131Component::join_igmp_groups_() {
|
||||
}
|
||||
|
||||
if (err) {
|
||||
ESP_LOGW(TAG, "IGMP join for %d universe of E1.31 failed. Multicast might not work.", universe.first);
|
||||
ESP_LOGW(TAG, "IGMP join for %d universe of E1.31 failed. Multicast might not work.", entry.universe);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
UniverseConsumer *E131Component::find_universe_(int universe) {
|
||||
for (auto &entry : this->universe_consumers_) {
|
||||
if (entry.universe == universe)
|
||||
return &entry;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void E131Component::join_(int universe) {
|
||||
// store only latest received packet for the given universe
|
||||
auto consumers = ++universe_consumers_[universe];
|
||||
|
||||
if (consumers > 1) {
|
||||
return; // we already joined before
|
||||
auto *consumer = this->find_universe_(universe);
|
||||
if (consumer != nullptr) {
|
||||
if (consumer->consumers++ > 0) {
|
||||
return; // we already joined before
|
||||
}
|
||||
} else {
|
||||
this->universe_consumers_.push_back({static_cast<uint16_t>(universe), 1});
|
||||
}
|
||||
|
||||
if (join_igmp_groups_()) {
|
||||
if (this->join_igmp_groups_()) {
|
||||
ESP_LOGD(TAG, "Joined %d universe for E1.31.", universe);
|
||||
}
|
||||
}
|
||||
|
||||
void E131Component::leave_(int universe) {
|
||||
auto consumers = --universe_consumers_[universe];
|
||||
auto *consumer = this->find_universe_(universe);
|
||||
if (consumer == nullptr)
|
||||
return;
|
||||
|
||||
if (consumers > 0) {
|
||||
if (--consumer->consumers > 0) {
|
||||
return; // we have other consumers of the given universe
|
||||
}
|
||||
|
||||
if (listen_method_ == E131_MULTICAST) {
|
||||
if (this->listen_method_ == E131_MULTICAST) {
|
||||
ip4_addr_t multicast_addr = network::IPAddress(239, 255, ((universe >> 8) & 0xff), ((universe >> 0) & 0xff));
|
||||
|
||||
LwIPLock lock;
|
||||
|
||||
@@ -49,10 +49,6 @@ EPaperBase = epaper_spi_ns.class_(
|
||||
)
|
||||
Transform = epaper_spi_ns.enum("Transform")
|
||||
|
||||
EPaperSpectraE6 = epaper_spi_ns.class_("EPaperSpectraE6", EPaperBase)
|
||||
EPaper7p3InSpectraE6 = epaper_spi_ns.class_("EPaper7p3InSpectraE6", EPaperSpectraE6)
|
||||
|
||||
|
||||
# Import all models dynamically from the models package
|
||||
for module_info in pkgutil.iter_modules(models.__path__):
|
||||
importlib.import_module(f".models.{module_info.name}", package=__package__)
|
||||
|
||||
@@ -76,7 +76,7 @@ class EPaperBase : public Display,
|
||||
static uint8_t color_to_bit(Color color) {
|
||||
// It's always a shade of gray. Map to BLACK or WHITE.
|
||||
// We split the luminance at a suitable point
|
||||
if ((static_cast<int>(color.r) + color.g + color.b) > 512) {
|
||||
if ((color.r + color.g + color.b) >= 382) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
|
||||
@@ -0,0 +1,244 @@
|
||||
#include "epaper_weact_3c.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome::epaper_spi {
|
||||
|
||||
static constexpr const char *const TAG = "epaper_weact_3c";
|
||||
|
||||
enum class BwrState : uint8_t {
|
||||
BWR_BLACK,
|
||||
BWR_WHITE,
|
||||
BWR_RED,
|
||||
};
|
||||
|
||||
static BwrState color_to_bwr(Color color) {
|
||||
if (color.r > color.g + color.b && color.r > 127) {
|
||||
return BwrState::BWR_RED;
|
||||
}
|
||||
if (color.r + color.g + color.b >= 382) {
|
||||
return BwrState::BWR_WHITE;
|
||||
}
|
||||
return BwrState::BWR_BLACK;
|
||||
}
|
||||
// SSD1680 3-color display notes:
|
||||
// - Buffer uses 1 bit per pixel, 8 pixels per byte
|
||||
// - Buffer first half (black_offset): Black/White plane (0=black, 1=white)
|
||||
// - Buffer second half (red_offset): Red plane (1=red, 0=no red)
|
||||
// - Total buffer: width * height / 4 bytes = 2 * (width * height / 8)
|
||||
// - For 128x296: 128*296/4 = 9472 bytes total (4736 per color)
|
||||
|
||||
void EPaperWeAct3C::draw_pixel_at(int x, int y, Color color) {
|
||||
if (!this->rotate_coordinates_(x, y))
|
||||
return;
|
||||
|
||||
// Calculate position in the 1-bit buffer
|
||||
const uint32_t pos = (x / 8) + (y * this->row_width_);
|
||||
const uint8_t bit = 0x80 >> (x & 0x07);
|
||||
const uint32_t red_offset = this->buffer_length_ / 2u;
|
||||
|
||||
// Use luminance threshold for B/W mapping
|
||||
// Split at halfway point (382 = (255*3)/2)
|
||||
auto bwr = color_to_bwr(color);
|
||||
|
||||
// Update black/white plane (first half of buffer)
|
||||
if (bwr == BwrState::BWR_WHITE) {
|
||||
// White pixel - set bit in black plane
|
||||
this->buffer_[pos] |= bit;
|
||||
} else {
|
||||
// Black pixel - clear bit in black plane
|
||||
this->buffer_[pos] &= ~bit;
|
||||
}
|
||||
|
||||
// Update red plane (second half of buffer)
|
||||
// Red if red component is dominant (r > g+b)
|
||||
if (bwr == BwrState::BWR_RED) {
|
||||
// Red pixel - set bit in red plane
|
||||
this->buffer_[red_offset + pos] |= bit;
|
||||
} else {
|
||||
// Not red - clear bit in red plane
|
||||
this->buffer_[red_offset + pos] &= ~bit;
|
||||
}
|
||||
}
|
||||
|
||||
void EPaperWeAct3C::fill(Color color) {
|
||||
// For 3-color e-paper with 1-bit buffer format:
|
||||
// - Black buffer: 1=black, 0=white
|
||||
// - Red buffer: 1=red, 0=no red
|
||||
// The buffer is stored as two halves: [black plane][red plane]
|
||||
const size_t half_buffer = this->buffer_length_ / 2u;
|
||||
|
||||
// Use luminance threshold for B/W mapping
|
||||
auto bits = color_to_bwr(color);
|
||||
|
||||
// Fill both planes
|
||||
if (bits == BwrState::BWR_BLACK) {
|
||||
// Black - both planes = 0x00
|
||||
this->buffer_.fill(0x00);
|
||||
} else if (bits == BwrState::BWR_RED) {
|
||||
// Red - black plane = 0x00, red plane = 0xFF
|
||||
for (size_t i = 0; i < half_buffer; i++)
|
||||
this->buffer_[i] = 0x00;
|
||||
for (size_t i = 0; i < half_buffer; i++)
|
||||
this->buffer_[half_buffer + i] = 0xFF;
|
||||
} else {
|
||||
// White - black plane = 0xFF, red plane = 0x00
|
||||
for (size_t i = 0; i < half_buffer; i++)
|
||||
this->buffer_[i] = 0xFF;
|
||||
for (size_t i = 0; i < half_buffer; i++)
|
||||
this->buffer_[half_buffer + i] = 0x00;
|
||||
}
|
||||
}
|
||||
|
||||
void EPaperWeAct3C::clear() {
|
||||
// Clear buffer to white, just like real paper.
|
||||
this->fill(COLOR_ON);
|
||||
}
|
||||
|
||||
void EPaperWeAct3C::set_window_() {
|
||||
// For full screen refresh, we always start from (0,0)
|
||||
// The y_low_/y_high_ values track the dirty region for optimization,
|
||||
// but for display refresh we need to write from the beginning
|
||||
uint16_t x_start = 0;
|
||||
uint16_t x_end = this->width_ - 1;
|
||||
uint16_t y_start = 0;
|
||||
uint16_t y_end = this->height_ - 1; // height = 296 for 2.9" display
|
||||
|
||||
// Set RAM X address boundaries (0x44)
|
||||
// X coordinates are byte-aligned (divided by 8)
|
||||
this->cmd_data(0x44, {(uint8_t) (x_start / 8), (uint8_t) (x_end / 8)});
|
||||
|
||||
// Set RAM Y address boundaries (0x45)
|
||||
// Format: Y start (LSB, MSB), Y end (LSB, MSB)
|
||||
this->cmd_data(0x45, {(uint8_t) y_start, (uint8_t) (y_start >> 8), (uint8_t) (y_end & 0xFF), (uint8_t) (y_end >> 8)});
|
||||
|
||||
// Reset RAM X counter to start (0x4E) - 1 byte
|
||||
this->cmd_data(0x4E, {(uint8_t) (x_start / 8)});
|
||||
|
||||
// Reset RAM Y counter to start (0x4F) - 2 bytes (LSB, MSB)
|
||||
this->cmd_data(0x4F, {(uint8_t) y_start, (uint8_t) (y_start >> 8)});
|
||||
}
|
||||
|
||||
bool HOT EPaperWeAct3C::transfer_data() {
|
||||
const uint32_t start_time = millis();
|
||||
const size_t buffer_length = this->buffer_length_;
|
||||
const size_t half_buffer = buffer_length / 2u;
|
||||
|
||||
ESP_LOGV(TAG, "transfer_data: buffer_length=%u, half_buffer=%u", buffer_length, half_buffer);
|
||||
|
||||
// Use a local buffer for SPI transfers
|
||||
uint8_t bytes_to_send[MAX_TRANSFER_SIZE];
|
||||
|
||||
// First, send the RED buffer (0x26 = WRITE_COLOR)
|
||||
// The red plane is in the second half of our buffer
|
||||
// NOTE: Must set RAM window first to reset address counters!
|
||||
if (this->current_data_index_ < half_buffer) {
|
||||
if (this->current_data_index_ == 0) {
|
||||
ESP_LOGV(TAG, "transfer_data: sending RED buffer (0x26)");
|
||||
this->set_window_(); // Reset RAM X/Y counters to start position
|
||||
this->command(0x26);
|
||||
}
|
||||
|
||||
this->start_data_();
|
||||
size_t red_offset = half_buffer;
|
||||
while (this->current_data_index_ < half_buffer) {
|
||||
size_t bytes_to_copy = std::min(MAX_TRANSFER_SIZE, half_buffer - this->current_data_index_);
|
||||
|
||||
for (size_t i = 0; i < bytes_to_copy; i++) {
|
||||
bytes_to_send[i] = this->buffer_[red_offset + this->current_data_index_ + i];
|
||||
}
|
||||
|
||||
this->write_array(bytes_to_send, bytes_to_copy);
|
||||
|
||||
this->current_data_index_ += bytes_to_copy;
|
||||
|
||||
if (millis() - start_time > MAX_TRANSFER_TIME) {
|
||||
// Let the main loop run and come back next loop
|
||||
this->disable();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
this->disable();
|
||||
}
|
||||
|
||||
// Finished the red buffer, now send the BLACK buffer (0x24 = WRITE_BLACK)
|
||||
// The black plane is in the first half of our buffer
|
||||
if (this->current_data_index_ < buffer_length) {
|
||||
if (this->current_data_index_ == half_buffer) {
|
||||
ESP_LOGV(TAG, "transfer_data: finished red buffer, sending BLACK buffer (0x24)");
|
||||
|
||||
// Do NOT reset RAM counters here for WeAct displays (Reference implementation behavior)
|
||||
// this->set_window();
|
||||
this->command(0x24);
|
||||
// Continue using current_data_index_, but we need to map it to the start of the buffer
|
||||
}
|
||||
|
||||
this->start_data_();
|
||||
while (this->current_data_index_ < buffer_length) {
|
||||
size_t remaining = buffer_length - this->current_data_index_;
|
||||
size_t bytes_to_copy = std::min(MAX_TRANSFER_SIZE, remaining);
|
||||
|
||||
// Calculate offset into the BLACK buffer (which is at the start of this->buffer_)
|
||||
// current_data_index_ goes from half_buffer to buffer_length
|
||||
size_t buffer_offset = this->current_data_index_ - half_buffer;
|
||||
|
||||
for (size_t i = 0; i < bytes_to_copy; i++) {
|
||||
bytes_to_send[i] = this->buffer_[buffer_offset + i];
|
||||
}
|
||||
|
||||
this->write_array(bytes_to_send, bytes_to_copy);
|
||||
|
||||
this->current_data_index_ += bytes_to_copy;
|
||||
|
||||
if (millis() - start_time > MAX_TRANSFER_TIME) {
|
||||
// Let the main loop run and come back next loop
|
||||
this->disable();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
this->disable();
|
||||
}
|
||||
|
||||
this->current_data_index_ = 0;
|
||||
ESP_LOGV(TAG, "transfer_data: completed (red=%u, black=%u bytes)", half_buffer, half_buffer);
|
||||
return true;
|
||||
}
|
||||
|
||||
void EPaperWeAct3C::refresh_screen(bool partial) {
|
||||
// SSD1680 refresh sequence:
|
||||
// Reset RAM X/Y address counters to 0,0 so display reads from start
|
||||
// 0x4E: RAM X counter - 1 byte (X / 8)
|
||||
// 0x4F: RAM Y counter - 2 bytes (Y LSB, Y MSB)
|
||||
this->cmd_data(0x4E, {0x00}); // RAM X counter = 0 (1 byte)
|
||||
this->cmd_data(0x4F, {0x00, 0x00}); // RAM Y counter = 0 (2 bytes)
|
||||
|
||||
// Send UPDATE_FULL command (0x22) with display update control parameter
|
||||
// Both WeAct and waveshare reference use 0xF7: {0x22, 0xF7}
|
||||
// 0xF7 = Display update: Load temperature, Load LUT, Enable RAM content
|
||||
this->cmd_data(0x22, {0xF7}); // Command 0x22 with parameter 0xF7
|
||||
this->command(0x20); // Activate display update
|
||||
|
||||
// COMMAND TERMINATE FRAME READ WRITE (required by SSD1680)
|
||||
// Removed 0xFF based on working reference implementation
|
||||
// this->command(0xFF);
|
||||
}
|
||||
|
||||
void EPaperWeAct3C::power_on() {
|
||||
// Power on sequence - send command to turn on power
|
||||
// According to SSD1680 spec: 0x22, 0xF8 powers on the display
|
||||
this->cmd_data(0x22, {0xF8}); // Power on
|
||||
this->command(0x20); // Activate
|
||||
}
|
||||
|
||||
void EPaperWeAct3C::power_off() {
|
||||
// Power off sequence - send command to turn off power
|
||||
// According to SSD1680 spec: 0x22, 0x83 powers off the display
|
||||
this->cmd_data(0x22, {0x83}); // Power off
|
||||
this->command(0x20); // Activate
|
||||
}
|
||||
|
||||
void EPaperWeAct3C::deep_sleep() {
|
||||
// Deep sleep sequence
|
||||
this->cmd_data(0x10, {0x01}); // Deep sleep mode
|
||||
}
|
||||
|
||||
} // namespace esphome::epaper_spi
|
||||
@@ -0,0 +1,39 @@
|
||||
#pragma once
|
||||
|
||||
#include "epaper_spi.h"
|
||||
|
||||
namespace esphome::epaper_spi {
|
||||
|
||||
/**
|
||||
* WeAct 3-color e-paper displays (SSD1683 controller).
|
||||
* Supports multiple sizes: 2.9" (128x296), 4.2" (400x300), etc.
|
||||
*
|
||||
* Color scheme: Black, White, Red (BWR)
|
||||
* Buffer layout: 1 bit per pixel, separate planes
|
||||
* - Buffer first half: Black/White plane (1=black, 0=white)
|
||||
* - Buffer second half: Red plane (1=red, 0=no red)
|
||||
* - Total buffer: width * height / 4 bytes (2 * width * height / 8)
|
||||
*/
|
||||
class EPaperWeAct3C : public EPaperBase {
|
||||
public:
|
||||
EPaperWeAct3C(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_ = this->row_width_ * height * 2;
|
||||
}
|
||||
|
||||
void fill(Color color) override;
|
||||
void clear() override;
|
||||
|
||||
protected:
|
||||
void set_window_();
|
||||
void refresh_screen(bool partial) override;
|
||||
void power_on() override;
|
||||
void power_off() override;
|
||||
void deep_sleep() override;
|
||||
void draw_pixel_at(int x, int y, Color color) override;
|
||||
|
||||
bool transfer_data() override;
|
||||
};
|
||||
|
||||
} // namespace esphome::epaper_spi
|
||||
@@ -84,3 +84,35 @@ jd79660.extend(
|
||||
(0xA5, 0x00,),
|
||||
),
|
||||
)
|
||||
|
||||
# Waveshare 7.5-H
|
||||
#
|
||||
# Vendor init derived from vendor sample code
|
||||
# <https://github.com/waveshareteam/e-Paper/blob/master/E-paper_Separate_Program/7in5_e-Paper_H/ESP32/EPD_7in5h.cpp>
|
||||
# Compatible MIT license, see esphome/LICENSE file.
|
||||
#
|
||||
# Note: busy pin uses LOW=busy, HIGH=idle. Configure with inverted: true in YAML.
|
||||
#
|
||||
# fmt: off
|
||||
jd79660.extend(
|
||||
"Waveshare-7.5in-H",
|
||||
width=800,
|
||||
height=480,
|
||||
|
||||
initsequence=(
|
||||
(0x00, 0x0F, 0x29,),
|
||||
(0x06, 0x0F, 0x8B, 0x93, 0xA1,),
|
||||
(0x41, 0x00,),
|
||||
(0x50, 0x37,),
|
||||
(0x60, 0x02, 0x02,),
|
||||
(0x61, 800 // 256, 800 % 256, 480 // 256, 480 % 256,), # RES: 800x480
|
||||
(0x62, 0x98, 0x98, 0x98, 0x75, 0xCA, 0xB2, 0x98, 0x7E,),
|
||||
(0x65, 0x00, 0x00, 0x00, 0x00,),
|
||||
(0xE7, 0x1C,),
|
||||
(0xE3, 0x00,),
|
||||
(0xE9, 0x01,),
|
||||
(0x30, 0x08,),
|
||||
# Power On (0x04): Must be early part of init seq = Disabled later!
|
||||
(0x04,),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
"""WeAct Black/White/Red e-paper displays using SSD1683 controller.
|
||||
|
||||
Supported models:
|
||||
- weact-2.13in-3c: 122x250 pixels (2.13" display)
|
||||
- weact-2.9in-3c: 128x296 pixels (2.9" display)
|
||||
- weact-4.2in-3c: 400x300 pixels (4.2" display)
|
||||
|
||||
These displays use SSD1680 or SSD1683 controller and require a specific initialization
|
||||
sequence. The DRV_OUT_CTL command is calculated from the display height.
|
||||
"""
|
||||
|
||||
from . import EpaperModel
|
||||
|
||||
|
||||
class WeActBWR(EpaperModel):
|
||||
"""Base EpaperModel class for WeAct Black/White/Red displays using SSD1683 controller."""
|
||||
|
||||
def __init__(self, name, **defaults):
|
||||
super().__init__(name, "EPaperWeAct3C", **defaults)
|
||||
|
||||
def get_init_sequence(self, config):
|
||||
"""Generate initialization sequence for WeAct BWR displays.
|
||||
|
||||
The initialization sequence is based on SSD1680 and SSD1683 controller datasheet
|
||||
and the WeAct display specifications.
|
||||
"""
|
||||
_, height = self.get_dimensions(config)
|
||||
# DRV_OUT_CTL: MSB of (height-1), LSB of (height-1), gate setting (0x00)
|
||||
height_minus_1 = height - 1
|
||||
msb = height_minus_1 >> 8
|
||||
lsb = height_minus_1 & 0xFF
|
||||
return (
|
||||
# Step 1: Software Reset (0x12) - REQUIRED per SSD1680, but works without it as well, so it's commented out for now
|
||||
# (0x12,),
|
||||
# Step 2: Wait 10ms after SWRESET (?) not sure how to implement wht waiting for 10ms after SWRESET, so it's commented out for now
|
||||
# Step 3: DRV_OUT_CTL - driver output control (height-dependent)
|
||||
# Format: (command, LSB, MSB, gate setting)
|
||||
(0x01, lsb, msb, 0x00),
|
||||
# Step 4: DATA_ENTRY - data entry mode (0x03 = decrement Y, increment X)
|
||||
(0x11, 0x03),
|
||||
# Step 5: BORDER_FULL - border waveform control
|
||||
(0x3C, 0x05),
|
||||
# Step 6: TEMP_SENS - internal temperature sensor
|
||||
(0x18, 0x80),
|
||||
# Step 7: DISPLAY_UPDATE - display update control
|
||||
(0x21, 0x00, 0x80),
|
||||
)
|
||||
|
||||
|
||||
# Model: WeAct 2.9" 3C - 128x296 pixels, SSD1680 controller
|
||||
weact_2p9in3c = WeActBWR(
|
||||
"weact-2.9in-3c",
|
||||
width=128,
|
||||
height=296,
|
||||
data_rate="10MHz",
|
||||
minimum_update_interval="1s",
|
||||
)
|
||||
|
||||
# Model: WeAct 2.13" 3C - 122x250 pixels, SSD1680 controller
|
||||
weact_2p13in3c = WeActBWR(
|
||||
"weact-2.13in-3c",
|
||||
width=122,
|
||||
height=250,
|
||||
data_rate="10MHz",
|
||||
minimum_update_interval="1s",
|
||||
)
|
||||
|
||||
# Model: WeAct 4.2" 3C - 400x300 pixels, SSD1683 controller
|
||||
weact_4p2in3c = WeActBWR(
|
||||
"weact-4.2in-3c",
|
||||
width=400,
|
||||
height=300,
|
||||
data_rate="10MHz",
|
||||
minimum_update_interval="10s",
|
||||
)
|
||||
@@ -25,7 +25,6 @@ from esphome.const import (
|
||||
CONF_PLATFORM_VERSION,
|
||||
CONF_PLATFORMIO_OPTIONS,
|
||||
CONF_REF,
|
||||
CONF_REFRESH,
|
||||
CONF_SAFE_MODE,
|
||||
CONF_SOURCE,
|
||||
CONF_TYPE,
|
||||
@@ -41,12 +40,12 @@ from esphome.const import (
|
||||
ThreadModel,
|
||||
__version__,
|
||||
)
|
||||
from esphome.core import CORE, HexInt, TimePeriod
|
||||
from esphome.core import CORE, HexInt
|
||||
from esphome.coroutine import CoroPriority, coroutine_with_priority
|
||||
import esphome.final_validate as fv
|
||||
from esphome.helpers import copy_file_if_changed, write_file_if_changed
|
||||
from esphome.helpers import copy_file_if_changed, rmtree, write_file_if_changed
|
||||
from esphome.types import ConfigType
|
||||
from esphome.writer import clean_cmake_cache, rmtree
|
||||
from esphome.writer import clean_cmake_cache
|
||||
|
||||
from .boards import BOARDS, STANDARD_BOARDS
|
||||
from .const import ( # noqa
|
||||
@@ -88,6 +87,7 @@ IS_TARGET_PLATFORM = True
|
||||
CONF_ASSERTION_LEVEL = "assertion_level"
|
||||
CONF_COMPILER_OPTIMIZATION = "compiler_optimization"
|
||||
CONF_ENABLE_IDF_EXPERIMENTAL_FEATURES = "enable_idf_experimental_features"
|
||||
CONF_ENGINEERING_SAMPLE = "engineering_sample"
|
||||
CONF_INCLUDE_BUILTIN_IDF_COMPONENTS = "include_builtin_idf_components"
|
||||
CONF_ENABLE_LWIP_ASSERT = "enable_lwip_assert"
|
||||
CONF_ENABLE_OTA_ROLLBACK = "enable_ota_rollback"
|
||||
@@ -499,49 +499,24 @@ def add_idf_component(
|
||||
repo: str | None = None,
|
||||
ref: str | None = None,
|
||||
path: str | None = None,
|
||||
refresh: TimePeriod | None = None,
|
||||
components: list[str] | None = None,
|
||||
submodules: list[str] | None = None,
|
||||
):
|
||||
"""Add an esp-idf component to the project."""
|
||||
if not repo and not ref and not path:
|
||||
raise ValueError("Requires at least one of repo, ref or path")
|
||||
if refresh or submodules or components:
|
||||
_LOGGER.warning(
|
||||
"The refresh, components and submodules parameters in add_idf_component() are "
|
||||
"deprecated and will be removed in ESPHome 2026.1. If you are seeing this, report "
|
||||
"an issue to the external_component author and ask them to update it."
|
||||
)
|
||||
components_registry = CORE.data[KEY_ESP32][KEY_COMPONENTS]
|
||||
if components:
|
||||
for comp in components:
|
||||
existing = components_registry.get(comp)
|
||||
if existing and existing.get(KEY_REF) != ref:
|
||||
_LOGGER.warning(
|
||||
"IDF component %s version conflict %s replaced by %s",
|
||||
comp,
|
||||
existing.get(KEY_REF),
|
||||
ref,
|
||||
)
|
||||
components_registry[comp] = {
|
||||
KEY_REPO: repo,
|
||||
KEY_REF: ref,
|
||||
KEY_PATH: f"{path}/{comp}" if path else comp,
|
||||
}
|
||||
else:
|
||||
existing = components_registry.get(name)
|
||||
if existing and existing.get(KEY_REF) != ref:
|
||||
_LOGGER.warning(
|
||||
"IDF component %s version conflict %s replaced by %s",
|
||||
name,
|
||||
existing.get(KEY_REF),
|
||||
ref,
|
||||
)
|
||||
components_registry[name] = {
|
||||
KEY_REPO: repo,
|
||||
KEY_REF: ref,
|
||||
KEY_PATH: path,
|
||||
}
|
||||
existing = components_registry.get(name)
|
||||
if existing and existing.get(KEY_REF) != ref:
|
||||
_LOGGER.warning(
|
||||
"IDF component %s version conflict %s replaced by %s",
|
||||
name,
|
||||
existing.get(KEY_REF),
|
||||
ref,
|
||||
)
|
||||
components_registry[name] = {
|
||||
KEY_REPO: repo,
|
||||
KEY_REF: ref,
|
||||
KEY_PATH: path,
|
||||
}
|
||||
|
||||
|
||||
def exclude_builtin_idf_component(name: str) -> None:
|
||||
@@ -613,16 +588,22 @@ def _format_framework_arduino_version(ver: cv.Version) -> str:
|
||||
return f"{ARDUINO_FRAMEWORK_PKG}@https://github.com/espressif/arduino-esp32/releases/download/{ver}/{filename}"
|
||||
|
||||
|
||||
def _format_framework_espidf_version(ver: cv.Version, release: str) -> str:
|
||||
def _format_framework_espidf_version(
|
||||
ver: cv.Version, release: str | None = None
|
||||
) -> str:
|
||||
# format the given espidf (https://github.com/pioarduino/esp-idf/releases) version to
|
||||
# a PIO platformio/framework-espidf value
|
||||
if ver == cv.Version(5, 4, 3) or ver >= cv.Version(5, 5, 1):
|
||||
ext = "tar.xz"
|
||||
else:
|
||||
ext = "zip"
|
||||
# Build version string with dot-separated extra (e.g., "5.5.3.1" not "5.5.3-1")
|
||||
ver_str = f"{ver.major}.{ver.minor}.{ver.patch}"
|
||||
if ver.extra:
|
||||
ver_str += f".{ver.extra}"
|
||||
if release:
|
||||
return f"pioarduino/framework-espidf@https://github.com/pioarduino/esp-idf/releases/download/v{str(ver)}.{release}/esp-idf-v{str(ver)}.{ext}"
|
||||
return f"pioarduino/framework-espidf@https://github.com/pioarduino/esp-idf/releases/download/v{str(ver)}/esp-idf-v{str(ver)}.{ext}"
|
||||
return f"pioarduino/framework-espidf@https://github.com/pioarduino/esp-idf/releases/download/v{ver_str}.{release}/esp-idf-v{ver_str}.{ext}"
|
||||
return f"pioarduino/framework-espidf@https://github.com/pioarduino/esp-idf/releases/download/v{ver_str}/esp-idf-v{ver_str}.{ext}"
|
||||
|
||||
|
||||
def _is_framework_url(source: str) -> bool:
|
||||
@@ -669,7 +650,7 @@ ARDUINO_PLATFORM_VERSION_LOOKUP = {
|
||||
# These versions correspond to pioarduino/esp-idf releases
|
||||
# See: https://github.com/pioarduino/esp-idf/releases
|
||||
ARDUINO_IDF_VERSION_LOOKUP = {
|
||||
cv.Version(3, 3, 7): cv.Version(5, 5, 2),
|
||||
cv.Version(3, 3, 7): cv.Version(5, 5, 3, "1"),
|
||||
cv.Version(3, 3, 6): cv.Version(5, 5, 2),
|
||||
cv.Version(3, 3, 5): cv.Version(5, 5, 2),
|
||||
cv.Version(3, 3, 4): cv.Version(5, 5, 1),
|
||||
@@ -688,11 +669,13 @@ ARDUINO_IDF_VERSION_LOOKUP = {
|
||||
# The default/recommended esp-idf framework version
|
||||
# - https://github.com/espressif/esp-idf/releases
|
||||
ESP_IDF_FRAMEWORK_VERSION_LOOKUP = {
|
||||
"recommended": cv.Version(5, 5, 2),
|
||||
"latest": cv.Version(5, 5, 2),
|
||||
"dev": cv.Version(5, 5, 2),
|
||||
"recommended": cv.Version(5, 5, 3, "1"),
|
||||
"latest": cv.Version(5, 5, 3, "1"),
|
||||
"dev": cv.Version(5, 5, 3, "1"),
|
||||
}
|
||||
ESP_IDF_PLATFORM_VERSION_LOOKUP = {
|
||||
cv.Version(5, 5, 3, "1"): cv.Version(55, 3, 37),
|
||||
cv.Version(5, 5, 3): cv.Version(55, 3, 37),
|
||||
cv.Version(5, 5, 2): cv.Version(55, 3, 37),
|
||||
cv.Version(5, 5, 1): cv.Version(55, 3, 31, "2"),
|
||||
cv.Version(5, 5, 0): cv.Version(55, 3, 31, "2"),
|
||||
@@ -755,7 +738,7 @@ def _check_versions(config):
|
||||
platform_lookup = ESP_IDF_PLATFORM_VERSION_LOOKUP.get(version)
|
||||
value[CONF_SOURCE] = value.get(
|
||||
CONF_SOURCE,
|
||||
_format_framework_espidf_version(version, value.get(CONF_RELEASE, None)),
|
||||
_format_framework_espidf_version(version, value.get(CONF_RELEASE)),
|
||||
)
|
||||
if _is_framework_url(value[CONF_SOURCE]):
|
||||
value[CONF_SOURCE] = f"pioarduino/framework-espidf@{value[CONF_SOURCE]}"
|
||||
@@ -803,6 +786,15 @@ def _detect_variant(value):
|
||||
# variant has already been validated against the known set
|
||||
value = value.copy()
|
||||
value[CONF_BOARD] = STANDARD_BOARDS[variant]
|
||||
if variant == VARIANT_ESP32P4:
|
||||
engineering_sample = value.get(CONF_ENGINEERING_SAMPLE)
|
||||
if engineering_sample is None:
|
||||
_LOGGER.warning(
|
||||
"No board specified for ESP32-P4. Defaulting to production silicon (rev3). "
|
||||
"If you have an early engineering sample (pre-rev3), set 'engineering_sample: true'."
|
||||
)
|
||||
elif engineering_sample:
|
||||
value[CONF_BOARD] = "esp32-p4-evboard"
|
||||
elif board in BOARDS:
|
||||
variant = variant or BOARDS[board][KEY_VARIANT]
|
||||
if variant != BOARDS[board][KEY_VARIANT]:
|
||||
@@ -866,6 +858,30 @@ def final_validate(config):
|
||||
path=[CONF_FRAMEWORK, CONF_ADVANCED, CONF_MINIMUM_CHIP_REVISION],
|
||||
)
|
||||
)
|
||||
if (
|
||||
config[CONF_VARIANT] != VARIANT_ESP32P4
|
||||
and config.get(CONF_ENGINEERING_SAMPLE) is not None
|
||||
):
|
||||
errs.append(
|
||||
cv.Invalid(
|
||||
f"'{CONF_ENGINEERING_SAMPLE}' is only supported on {VARIANT_ESP32P4}",
|
||||
path=[CONF_ENGINEERING_SAMPLE],
|
||||
)
|
||||
)
|
||||
if (
|
||||
config[CONF_VARIANT] == VARIANT_ESP32P4
|
||||
and config.get(CONF_ENGINEERING_SAMPLE) is not None
|
||||
):
|
||||
board_is_es = BOARDS.get(config[CONF_BOARD], {}).get(
|
||||
"engineering_sample", False
|
||||
)
|
||||
if config[CONF_ENGINEERING_SAMPLE] != board_is_es:
|
||||
errs.append(
|
||||
cv.Invalid(
|
||||
f"'{CONF_ENGINEERING_SAMPLE}' does not match board '{config[CONF_BOARD]}'",
|
||||
path=[CONF_ENGINEERING_SAMPLE],
|
||||
)
|
||||
)
|
||||
if advanced[CONF_EXECUTE_FROM_PSRAM]:
|
||||
if config[CONF_VARIANT] != VARIANT_ESP32S3:
|
||||
errs.append(
|
||||
@@ -1037,16 +1053,6 @@ def _parse_idf_component(value: str) -> ConfigType:
|
||||
)
|
||||
|
||||
|
||||
def _validate_idf_component(config: ConfigType) -> ConfigType:
|
||||
"""Validate IDF component config and warn about deprecated options."""
|
||||
if CONF_REFRESH in config:
|
||||
_LOGGER.warning(
|
||||
"The 'refresh' option for IDF components is deprecated and has no effect. "
|
||||
"It will be removed in ESPHome 2026.1. Please remove it from your configuration."
|
||||
)
|
||||
return config
|
||||
|
||||
|
||||
FRAMEWORK_ESP_IDF = "esp-idf"
|
||||
FRAMEWORK_ARDUINO = "arduino"
|
||||
FRAMEWORK_SCHEMA = cv.Schema(
|
||||
@@ -1135,13 +1141,9 @@ FRAMEWORK_SCHEMA = cv.Schema(
|
||||
cv.Optional(CONF_SOURCE): cv.git_ref,
|
||||
cv.Optional(CONF_REF): cv.string,
|
||||
cv.Optional(CONF_PATH): cv.string,
|
||||
cv.Optional(CONF_REFRESH): cv.All(
|
||||
cv.string, cv.source_refresh
|
||||
),
|
||||
}
|
||||
),
|
||||
),
|
||||
_validate_idf_component,
|
||||
)
|
||||
),
|
||||
}
|
||||
@@ -1229,6 +1231,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
cv.Optional(CONF_CPU_FREQUENCY): cv.one_of(
|
||||
*FULL_CPU_FREQUENCIES, upper=True
|
||||
),
|
||||
cv.Optional(CONF_ENGINEERING_SAMPLE): cv.boolean,
|
||||
cv.Optional(CONF_FLASH_SIZE, default="4MB"): cv.one_of(
|
||||
*FLASH_SIZES, upper=True
|
||||
),
|
||||
@@ -1467,7 +1470,7 @@ async def to_code(config):
|
||||
if (idf_ver := ARDUINO_IDF_VERSION_LOOKUP.get(framework_ver)) is not None:
|
||||
cg.add_platformio_option(
|
||||
"platform_packages",
|
||||
[_format_framework_espidf_version(idf_ver, None)],
|
||||
[_format_framework_espidf_version(idf_ver)],
|
||||
)
|
||||
# Use stub package to skip downloading precompiled libs
|
||||
stubs_dir = CORE.relative_build_path("arduino_libs_stub")
|
||||
@@ -1511,6 +1514,16 @@ async def to_code(config):
|
||||
f"CONFIG_ESPTOOLPY_FLASHSIZE_{config[CONF_FLASH_SIZE]}", True
|
||||
)
|
||||
|
||||
# ESP32-P4: ESP-IDF 5.5.3 changed the default of ESP32P4_SELECTS_REV_LESS_V3
|
||||
# from y to n. PlatformIO uses sections.ld.in (for rev <3) or
|
||||
# sections.rev3.ld.in (for rev >=3) based on board definition.
|
||||
# Set the sdkconfig option to match the board's chip revision.
|
||||
if variant == VARIANT_ESP32P4:
|
||||
is_eng_sample = BOARDS.get(config[CONF_BOARD], {}).get(
|
||||
"engineering_sample", False
|
||||
)
|
||||
add_idf_sdkconfig_option("CONFIG_ESP32P4_SELECTS_REV_LESS_V3", is_eng_sample)
|
||||
|
||||
# Set minimum chip revision for ESP32 variant
|
||||
# Setting this to 3.0 or higher reduces flash size by excluding workaround code,
|
||||
# and for PSRAM users saves significant IRAM by keeping C library functions in ROM.
|
||||
|
||||
@@ -20,7 +20,7 @@ STANDARD_BOARDS = {
|
||||
VARIANT_ESP32C6: "esp32-c6-devkitm-1",
|
||||
VARIANT_ESP32C61: "esp32-c61-devkitc1-n8r2",
|
||||
VARIANT_ESP32H2: "esp32-h2-devkitm-1",
|
||||
VARIANT_ESP32P4: "esp32-p4-evboard",
|
||||
VARIANT_ESP32P4: "esp32-p4_r3-evboard",
|
||||
VARIANT_ESP32S2: "esp32-s2-kaluga-1",
|
||||
VARIANT_ESP32S3: "esp32-s3-devkitc-1",
|
||||
}
|
||||
@@ -1713,10 +1713,12 @@ BOARDS = {
|
||||
"esp32-p4": {
|
||||
"name": "Espressif ESP32-P4 ES (pre rev.300) generic",
|
||||
"variant": VARIANT_ESP32P4,
|
||||
"engineering_sample": True,
|
||||
},
|
||||
"esp32-p4-evboard": {
|
||||
"name": "Espressif ESP32-P4 Function EV Board (ES pre rev.300)",
|
||||
"variant": VARIANT_ESP32P4,
|
||||
"engineering_sample": True,
|
||||
},
|
||||
"esp32-p4_r3": {
|
||||
"name": "Espressif ESP32-P4 rev.300 generic",
|
||||
@@ -2141,6 +2143,7 @@ BOARDS = {
|
||||
"m5stack-tab5-p4": {
|
||||
"name": "M5STACK Tab5 esp32-p4 Board (ES pre rev.300)",
|
||||
"variant": VARIANT_ESP32P4,
|
||||
"engineering_sample": True,
|
||||
},
|
||||
"m5stack-timer-cam": {
|
||||
"name": "M5Stack Timer CAM",
|
||||
|
||||
@@ -21,9 +21,9 @@ extern "C" __attribute__((weak)) void initArduino() {}
|
||||
|
||||
namespace esphome {
|
||||
|
||||
void IRAM_ATTR HOT yield() { vPortYield(); }
|
||||
void HOT yield() { vPortYield(); }
|
||||
uint32_t IRAM_ATTR HOT millis() { return (uint32_t) (esp_timer_get_time() / 1000ULL); }
|
||||
void IRAM_ATTR HOT delay(uint32_t ms) { vTaskDelay(ms / portTICK_PERIOD_MS); }
|
||||
void HOT delay(uint32_t ms) { vTaskDelay(ms / portTICK_PERIOD_MS); }
|
||||
uint32_t IRAM_ATTR HOT micros() { return (uint32_t) esp_timer_get_time(); }
|
||||
void IRAM_ATTR HOT delayMicroseconds(uint32_t us) { delay_microseconds_safe(us); }
|
||||
void arch_restart() {
|
||||
@@ -44,7 +44,7 @@ void arch_init() {
|
||||
esp_ota_mark_app_valid_cancel_rollback();
|
||||
#endif
|
||||
}
|
||||
void IRAM_ATTR HOT arch_feed_wdt() { esp_task_wdt_reset(); }
|
||||
void HOT arch_feed_wdt() { esp_task_wdt_reset(); }
|
||||
|
||||
uint8_t progmem_read_byte(const uint8_t *addr) { return *addr; }
|
||||
uint32_t arch_get_cpu_cycle_count() { return esp_cpu_get_cycle_count(); }
|
||||
|
||||
@@ -9,6 +9,7 @@ from esphome import automation
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import socket
|
||||
from esphome.components.esp32 import add_idf_sdkconfig_option, const, get_esp32_variant
|
||||
from esphome.components.esp32.const import VARIANT_ESP32C2
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_ENABLE_ON_BOOT,
|
||||
@@ -387,6 +388,15 @@ def final_validation(config):
|
||||
f"Name '{name}' is too long, maximum length is {max_length} characters"
|
||||
)
|
||||
|
||||
# ESP32-C2 has very limited RAM (~272KB). Without releasing BLE IRAM,
|
||||
# esp_bt_controller_init fails with ESP_ERR_NO_MEM.
|
||||
# CONFIG_BT_RELEASE_IRAM changes the memory layout so IRAM and DRAM share
|
||||
# space more flexibly, giving the BT controller enough contiguous memory.
|
||||
# This requires CONFIG_ESP_SYSTEM_PMP_IDRAM_SPLIT to be disabled.
|
||||
if get_esp32_variant() == VARIANT_ESP32C2:
|
||||
add_idf_sdkconfig_option("CONFIG_BT_RELEASE_IRAM", True)
|
||||
add_idf_sdkconfig_option("CONFIG_ESP_SYSTEM_PMP_IDRAM_SPLIT", False)
|
||||
|
||||
# Set GATT Client/Server sdkconfig options based on which components are loaded
|
||||
full_config = fv.full_config.get()
|
||||
|
||||
@@ -403,16 +413,16 @@ def final_validation(config):
|
||||
add_idf_sdkconfig_option("CONFIG_ESP_HOSTED_ENABLE_BT_BLUEDROID", True)
|
||||
add_idf_sdkconfig_option("CONFIG_ESP_HOSTED_BLUEDROID_HCI_VHCI", True)
|
||||
|
||||
# Check if BLE Server is needed
|
||||
has_ble_server = "esp32_ble_server" in full_config
|
||||
|
||||
# Check if BLE Client is needed (via esp32_ble_tracker or esp32_ble_client)
|
||||
has_ble_client = (
|
||||
"esp32_ble_tracker" in full_config or "esp32_ble_client" in full_config
|
||||
)
|
||||
|
||||
# Check if BLE Server is needed
|
||||
has_ble_server = "esp32_ble_server" in full_config
|
||||
|
||||
# ESP-IDF BLE stack requires GATT Server to be enabled when GATT Client is enabled
|
||||
# This is an internal dependency in the Bluedroid stack (tested ESP-IDF 5.4.2-5.5.1)
|
||||
# This is an internal dependency in the Bluedroid stack
|
||||
# See: https://github.com/espressif/esp-idf/issues/17724
|
||||
add_idf_sdkconfig_option("CONFIG_BT_GATTS_ENABLE", has_ble_server or has_ble_client)
|
||||
add_idf_sdkconfig_option("CONFIG_BT_GATTC_ENABLE", has_ble_client)
|
||||
|
||||
@@ -16,17 +16,17 @@ static const char *const TAG = "esp32_ble_client";
|
||||
// Intermediate connection parameters for standard operation
|
||||
// ESP-IDF defaults (12.5-15ms) are too slow for stable connections through WiFi-based BLE proxies,
|
||||
// causing disconnections. These medium parameters balance responsiveness with bandwidth usage.
|
||||
static const uint16_t MEDIUM_MIN_CONN_INTERVAL = 0x07; // 7 * 1.25ms = 8.75ms
|
||||
static const uint16_t MEDIUM_MAX_CONN_INTERVAL = 0x09; // 9 * 1.25ms = 11.25ms
|
||||
static constexpr uint16_t MEDIUM_MIN_CONN_INTERVAL = 0x07; // 7 * 1.25ms = 8.75ms
|
||||
static constexpr uint16_t MEDIUM_MAX_CONN_INTERVAL = 0x09; // 9 * 1.25ms = 11.25ms
|
||||
// The timeout value was increased from 6s to 8s to address stability issues observed
|
||||
// in certain BLE devices when operating through WiFi-based BLE proxies. The longer
|
||||
// timeout reduces the likelihood of disconnections during periods of high latency.
|
||||
static const uint16_t MEDIUM_CONN_TIMEOUT = 800; // 800 * 10ms = 8s
|
||||
static constexpr uint16_t MEDIUM_CONN_TIMEOUT = 800; // 800 * 10ms = 8s
|
||||
|
||||
// Fastest connection parameters for devices with short discovery timeouts
|
||||
static const uint16_t FAST_MIN_CONN_INTERVAL = 0x06; // 6 * 1.25ms = 7.5ms (BLE minimum)
|
||||
static const uint16_t FAST_MAX_CONN_INTERVAL = 0x06; // 6 * 1.25ms = 7.5ms
|
||||
static const uint16_t FAST_CONN_TIMEOUT = 1000; // 1000 * 10ms = 10s
|
||||
static constexpr uint16_t FAST_MIN_CONN_INTERVAL = 0x06; // 6 * 1.25ms = 7.5ms (BLE minimum)
|
||||
static constexpr uint16_t FAST_MAX_CONN_INTERVAL = 0x06; // 6 * 1.25ms = 7.5ms
|
||||
static constexpr uint16_t FAST_CONN_TIMEOUT = 1000; // 1000 * 10ms = 10s
|
||||
static const esp_bt_uuid_t NOTIFY_DESC_UUID = {
|
||||
.len = ESP_UUID_LEN_16,
|
||||
.uuid =
|
||||
|
||||
@@ -527,7 +527,7 @@ async def to_code_characteristic(service_var, char_conf):
|
||||
action_conf,
|
||||
char_conf[CONF_CHAR_VALUE_ACTION_ID_],
|
||||
cg.TemplateArguments(),
|
||||
{},
|
||||
[],
|
||||
)
|
||||
cg.add(value_action.play())
|
||||
else:
|
||||
|
||||
@@ -246,9 +246,27 @@ void BLECharacteristic::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt
|
||||
if (this->handle_ != param->write.handle)
|
||||
break;
|
||||
|
||||
esp_gatt_status_t status = ESP_GATT_OK;
|
||||
|
||||
if (param->write.is_prep) {
|
||||
this->value_.insert(this->value_.end(), param->write.value, param->write.value + param->write.len);
|
||||
this->write_event_ = true;
|
||||
const size_t offset = param->write.offset;
|
||||
const size_t write_len = param->write.len;
|
||||
const size_t new_size = offset + write_len;
|
||||
// Clean the buffer on the first prepared write event
|
||||
if (offset == 0) {
|
||||
this->value_.clear();
|
||||
}
|
||||
|
||||
if (offset != this->value_.size()) {
|
||||
status = ESP_GATT_INVALID_OFFSET;
|
||||
} else if (new_size > ESP_GATT_MAX_ATTR_LEN) {
|
||||
status = ESP_GATT_INVALID_ATTR_LEN;
|
||||
} else {
|
||||
if (this->value_.size() < new_size) {
|
||||
this->value_.resize(new_size);
|
||||
}
|
||||
memcpy(this->value_.data() + offset, param->write.value, write_len);
|
||||
}
|
||||
} else {
|
||||
this->set_value(ByteBuffer::wrap(param->write.value, param->write.len));
|
||||
}
|
||||
@@ -263,7 +281,7 @@ void BLECharacteristic::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt
|
||||
memcpy(response.attr_value.value, param->write.value, param->write.len);
|
||||
|
||||
esp_err_t err =
|
||||
esp_ble_gatts_send_response(gatts_if, param->write.conn_id, param->write.trans_id, ESP_GATT_OK, &response);
|
||||
esp_ble_gatts_send_response(gatts_if, param->write.conn_id, param->write.trans_id, status, &response);
|
||||
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "esp_ble_gatts_send_response failed: %d", err);
|
||||
@@ -280,9 +298,9 @@ void BLECharacteristic::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt
|
||||
}
|
||||
|
||||
case ESP_GATTS_EXEC_WRITE_EVT: {
|
||||
if (!this->write_event_)
|
||||
// BLE stack will guarantee that ESP_GATTS_EXEC_WRITE_EVT is only received after prepared writes
|
||||
if (this->value_.empty())
|
||||
break;
|
||||
this->write_event_ = false;
|
||||
if (param->exec_write.exec_write_flag == ESP_GATT_PREP_WRITE_EXEC) {
|
||||
if (this->on_write_callback_) {
|
||||
(*this->on_write_callback_)(this->value_, param->exec_write.conn_id);
|
||||
|
||||
@@ -57,12 +57,12 @@ class BLECharacteristic {
|
||||
ESPBTUUID get_uuid() { return this->uuid_; }
|
||||
std::vector<uint8_t> &get_value() { return this->value_; }
|
||||
|
||||
static const uint32_t PROPERTY_READ = 1 << 0;
|
||||
static const uint32_t PROPERTY_WRITE = 1 << 1;
|
||||
static const uint32_t PROPERTY_NOTIFY = 1 << 2;
|
||||
static const uint32_t PROPERTY_BROADCAST = 1 << 3;
|
||||
static const uint32_t PROPERTY_INDICATE = 1 << 4;
|
||||
static const uint32_t PROPERTY_WRITE_NR = 1 << 5;
|
||||
static constexpr uint32_t PROPERTY_READ = 1 << 0;
|
||||
static constexpr uint32_t PROPERTY_WRITE = 1 << 1;
|
||||
static constexpr uint32_t PROPERTY_NOTIFY = 1 << 2;
|
||||
static constexpr uint32_t PROPERTY_BROADCAST = 1 << 3;
|
||||
static constexpr uint32_t PROPERTY_INDICATE = 1 << 4;
|
||||
static constexpr uint32_t PROPERTY_WRITE_NR = 1 << 5;
|
||||
|
||||
bool is_created();
|
||||
bool is_failed();
|
||||
@@ -77,7 +77,6 @@ class BLECharacteristic {
|
||||
}
|
||||
|
||||
protected:
|
||||
bool write_event_{false};
|
||||
BLEService *service_{};
|
||||
ESPBTUUID uuid_;
|
||||
esp_gatt_char_prop_t properties_;
|
||||
|
||||
@@ -22,8 +22,10 @@ from esphome.const import (
|
||||
CONF_TRIGGER_ID,
|
||||
CONF_VSYNC_PIN,
|
||||
)
|
||||
from esphome.core import CORE
|
||||
from esphome.core.entity_helpers import setup_entity
|
||||
import esphome.final_validate as fv
|
||||
from esphome.types import ConfigType
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -84,6 +86,18 @@ FRAME_SIZES = {
|
||||
"2560X1920": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_2560X1920,
|
||||
"QSXGA": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_2560X1920,
|
||||
}
|
||||
ESP32CameraPixelFormat = esp32_camera_ns.enum("ESP32CameraPixelFormat")
|
||||
PIXEL_FORMATS = {
|
||||
"RGB565": ESP32CameraPixelFormat.ESP32_PIXEL_FORMAT_RGB565,
|
||||
"YUV422": ESP32CameraPixelFormat.ESP32_PIXEL_FORMAT_YUV422,
|
||||
"YUV420": ESP32CameraPixelFormat.ESP32_PIXEL_FORMAT_YUV420,
|
||||
"GRAYSCALE": ESP32CameraPixelFormat.ESP32_PIXEL_FORMAT_GRAYSCALE,
|
||||
"JPEG": ESP32CameraPixelFormat.ESP32_PIXEL_FORMAT_JPEG,
|
||||
"RGB888": ESP32CameraPixelFormat.ESP32_PIXEL_FORMAT_RGB888,
|
||||
"RAW": ESP32CameraPixelFormat.ESP32_PIXEL_FORMAT_RAW,
|
||||
"RGB444": ESP32CameraPixelFormat.ESP32_PIXEL_FORMAT_RGB444,
|
||||
"RGB555": ESP32CameraPixelFormat.ESP32_PIXEL_FORMAT_RGB555,
|
||||
}
|
||||
ESP32GainControlMode = esp32_camera_ns.enum("ESP32GainControlMode")
|
||||
ENUM_GAIN_CONTROL_MODE = {
|
||||
"MANUAL": ESP32GainControlMode.ESP32_GC_MODE_MANU,
|
||||
@@ -131,6 +145,7 @@ CONF_EXTERNAL_CLOCK = "external_clock"
|
||||
CONF_I2C_PINS = "i2c_pins"
|
||||
CONF_POWER_DOWN_PIN = "power_down_pin"
|
||||
# image
|
||||
CONF_PIXEL_FORMAT = "pixel_format"
|
||||
CONF_JPEG_QUALITY = "jpeg_quality"
|
||||
CONF_VERTICAL_FLIP = "vertical_flip"
|
||||
CONF_HORIZONTAL_MIRROR = "horizontal_mirror"
|
||||
@@ -171,6 +186,21 @@ def validate_fb_location_(value):
|
||||
return validator(value)
|
||||
|
||||
|
||||
def validate_jpeg_quality(config: ConfigType) -> ConfigType:
|
||||
quality = config.get(CONF_JPEG_QUALITY)
|
||||
pixel_format = config.get(CONF_PIXEL_FORMAT, "JPEG")
|
||||
|
||||
if quality == 0:
|
||||
# Set default JPEG quality if not specified for backwards compatibility
|
||||
if pixel_format == "JPEG":
|
||||
config[CONF_JPEG_QUALITY] = 10
|
||||
# For pixel formats other than JPEG, the valid 0 means no conversion
|
||||
elif quality < 6 or quality > 63:
|
||||
raise cv.Invalid(f"jpeg_quality must be between 6 and 63, got {quality}")
|
||||
|
||||
return config
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.ENTITY_BASE_SCHEMA.extend(
|
||||
{
|
||||
@@ -206,7 +236,12 @@ CONFIG_SCHEMA = cv.All(
|
||||
cv.Optional(CONF_RESOLUTION, default="640X480"): cv.enum(
|
||||
FRAME_SIZES, upper=True
|
||||
),
|
||||
cv.Optional(CONF_JPEG_QUALITY, default=10): cv.int_range(min=6, max=63),
|
||||
cv.Optional(CONF_PIXEL_FORMAT, default="JPEG"): cv.enum(
|
||||
PIXEL_FORMATS, upper=True
|
||||
),
|
||||
cv.Optional(CONF_JPEG_QUALITY, default=0): cv.Any(
|
||||
cv.one_of(0), cv.int_range(min=6, max=63)
|
||||
),
|
||||
cv.Optional(CONF_CONTRAST, default=0): camera_range_param,
|
||||
cv.Optional(CONF_BRIGHTNESS, default=0): camera_range_param,
|
||||
cv.Optional(CONF_SATURATION, default=0): camera_range_param,
|
||||
@@ -270,11 +305,21 @@ CONFIG_SCHEMA = cv.All(
|
||||
),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA),
|
||||
validate_jpeg_quality,
|
||||
cv.has_exactly_one_key(CONF_I2C_PINS, CONF_I2C_ID),
|
||||
)
|
||||
|
||||
|
||||
def _final_validate(config):
|
||||
# Check psram requirement for non-JPEG formats
|
||||
if (
|
||||
config.get(CONF_PIXEL_FORMAT, "JPEG") != "JPEG"
|
||||
and psram_domain not in CORE.loaded_integrations
|
||||
):
|
||||
raise cv.Invalid(
|
||||
f"Non-JPEG pixel formats require the '{psram_domain}' component for JPEG conversion"
|
||||
)
|
||||
|
||||
if CONF_I2C_PINS not in config:
|
||||
return
|
||||
fconf = fv.full_config.get()
|
||||
@@ -298,6 +343,7 @@ SETTERS = {
|
||||
CONF_RESET_PIN: "set_reset_pin",
|
||||
CONF_POWER_DOWN_PIN: "set_power_down_pin",
|
||||
# image
|
||||
CONF_PIXEL_FORMAT: "set_pixel_format",
|
||||
CONF_JPEG_QUALITY: "set_jpeg_quality",
|
||||
CONF_VERTICAL_FLIP: "set_vertical_flip",
|
||||
CONF_HORIZONTAL_MIRROR: "set_horizontal_mirror",
|
||||
@@ -351,6 +397,8 @@ async def to_code(config):
|
||||
cg.add(var.set_frame_size(config[CONF_RESOLUTION]))
|
||||
|
||||
cg.add_define("USE_CAMERA")
|
||||
if config[CONF_JPEG_QUALITY] != 0 and config[CONF_PIXEL_FORMAT] != "JPEG":
|
||||
cg.add_define("USE_ESP32_CAMERA_JPEG_CONVERSION")
|
||||
|
||||
add_idf_component(name="espressif/esp32-camera", ref="2.1.1")
|
||||
add_idf_sdkconfig_option("CONFIG_SCCB_HARDWARE_I2C_DRIVER_NEW", True)
|
||||
|
||||
@@ -16,6 +16,74 @@ static constexpr size_t FRAMEBUFFER_TASK_STACK_SIZE = 1792;
|
||||
static constexpr uint32_t FRAME_LOG_INTERVAL_MS = 60000;
|
||||
#endif
|
||||
|
||||
static const char *frame_size_to_str(framesize_t size) {
|
||||
switch (size) {
|
||||
case FRAMESIZE_QQVGA:
|
||||
return "160x120 (QQVGA)";
|
||||
case FRAMESIZE_QCIF:
|
||||
return "176x155 (QCIF)";
|
||||
case FRAMESIZE_HQVGA:
|
||||
return "240x176 (HQVGA)";
|
||||
case FRAMESIZE_QVGA:
|
||||
return "320x240 (QVGA)";
|
||||
case FRAMESIZE_CIF:
|
||||
return "400x296 (CIF)";
|
||||
case FRAMESIZE_VGA:
|
||||
return "640x480 (VGA)";
|
||||
case FRAMESIZE_SVGA:
|
||||
return "800x600 (SVGA)";
|
||||
case FRAMESIZE_XGA:
|
||||
return "1024x768 (XGA)";
|
||||
case FRAMESIZE_SXGA:
|
||||
return "1280x1024 (SXGA)";
|
||||
case FRAMESIZE_UXGA:
|
||||
return "1600x1200 (UXGA)";
|
||||
case FRAMESIZE_FHD:
|
||||
return "1920x1080 (FHD)";
|
||||
case FRAMESIZE_P_HD:
|
||||
return "720x1280 (P_HD)";
|
||||
case FRAMESIZE_P_3MP:
|
||||
return "864x1536 (P_3MP)";
|
||||
case FRAMESIZE_QXGA:
|
||||
return "2048x1536 (QXGA)";
|
||||
case FRAMESIZE_QHD:
|
||||
return "2560x1440 (QHD)";
|
||||
case FRAMESIZE_WQXGA:
|
||||
return "2560x1600 (WQXGA)";
|
||||
case FRAMESIZE_P_FHD:
|
||||
return "1080x1920 (P_FHD)";
|
||||
case FRAMESIZE_QSXGA:
|
||||
return "2560x1920 (QSXGA)";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
static const char *pixel_format_to_str(pixformat_t format) {
|
||||
switch (format) {
|
||||
case PIXFORMAT_RGB565:
|
||||
return "RGB565";
|
||||
case PIXFORMAT_YUV422:
|
||||
return "YUV422";
|
||||
case PIXFORMAT_YUV420:
|
||||
return "YUV420";
|
||||
case PIXFORMAT_GRAYSCALE:
|
||||
return "GRAYSCALE";
|
||||
case PIXFORMAT_JPEG:
|
||||
return "JPEG";
|
||||
case PIXFORMAT_RGB888:
|
||||
return "RGB888";
|
||||
case PIXFORMAT_RAW:
|
||||
return "RAW";
|
||||
case PIXFORMAT_RGB444:
|
||||
return "RGB444";
|
||||
case PIXFORMAT_RGB555:
|
||||
return "RGB555";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------------- public API (derivated) ---------------- */
|
||||
void ESP32Camera::setup() {
|
||||
#ifdef USE_I2C
|
||||
@@ -68,64 +136,9 @@ void ESP32Camera::dump_config() {
|
||||
this->name_.c_str(), YESNO(this->is_internal()), conf.pin_d0, conf.pin_d1, conf.pin_d2, conf.pin_d3,
|
||||
conf.pin_d4, conf.pin_d5, conf.pin_d6, conf.pin_d7, conf.pin_vsync, conf.pin_href, conf.pin_pclk,
|
||||
conf.pin_xclk, conf.xclk_freq_hz, conf.pin_sccb_sda, conf.pin_sccb_scl, conf.pin_reset);
|
||||
switch (this->config_.frame_size) {
|
||||
case FRAMESIZE_QQVGA:
|
||||
ESP_LOGCONFIG(TAG, " Resolution: 160x120 (QQVGA)");
|
||||
break;
|
||||
case FRAMESIZE_QCIF:
|
||||
ESP_LOGCONFIG(TAG, " Resolution: 176x155 (QCIF)");
|
||||
break;
|
||||
case FRAMESIZE_HQVGA:
|
||||
ESP_LOGCONFIG(TAG, " Resolution: 240x176 (HQVGA)");
|
||||
break;
|
||||
case FRAMESIZE_QVGA:
|
||||
ESP_LOGCONFIG(TAG, " Resolution: 320x240 (QVGA)");
|
||||
break;
|
||||
case FRAMESIZE_CIF:
|
||||
ESP_LOGCONFIG(TAG, " Resolution: 400x296 (CIF)");
|
||||
break;
|
||||
case FRAMESIZE_VGA:
|
||||
ESP_LOGCONFIG(TAG, " Resolution: 640x480 (VGA)");
|
||||
break;
|
||||
case FRAMESIZE_SVGA:
|
||||
ESP_LOGCONFIG(TAG, " Resolution: 800x600 (SVGA)");
|
||||
break;
|
||||
case FRAMESIZE_XGA:
|
||||
ESP_LOGCONFIG(TAG, " Resolution: 1024x768 (XGA)");
|
||||
break;
|
||||
case FRAMESIZE_SXGA:
|
||||
ESP_LOGCONFIG(TAG, " Resolution: 1280x1024 (SXGA)");
|
||||
break;
|
||||
case FRAMESIZE_UXGA:
|
||||
ESP_LOGCONFIG(TAG, " Resolution: 1600x1200 (UXGA)");
|
||||
break;
|
||||
case FRAMESIZE_FHD:
|
||||
ESP_LOGCONFIG(TAG, " Resolution: 1920x1080 (FHD)");
|
||||
break;
|
||||
case FRAMESIZE_P_HD:
|
||||
ESP_LOGCONFIG(TAG, " Resolution: 720x1280 (P_HD)");
|
||||
break;
|
||||
case FRAMESIZE_P_3MP:
|
||||
ESP_LOGCONFIG(TAG, " Resolution: 864x1536 (P_3MP)");
|
||||
break;
|
||||
case FRAMESIZE_QXGA:
|
||||
ESP_LOGCONFIG(TAG, " Resolution: 2048x1536 (QXGA)");
|
||||
break;
|
||||
case FRAMESIZE_QHD:
|
||||
ESP_LOGCONFIG(TAG, " Resolution: 2560x1440 (QHD)");
|
||||
break;
|
||||
case FRAMESIZE_WQXGA:
|
||||
ESP_LOGCONFIG(TAG, " Resolution: 2560x1600 (WQXGA)");
|
||||
break;
|
||||
case FRAMESIZE_P_FHD:
|
||||
ESP_LOGCONFIG(TAG, " Resolution: 1080x1920 (P_FHD)");
|
||||
break;
|
||||
case FRAMESIZE_QSXGA:
|
||||
ESP_LOGCONFIG(TAG, " Resolution: 2560x1920 (QSXGA)");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
ESP_LOGCONFIG(TAG, " Resolution: %s", frame_size_to_str(this->config_.frame_size));
|
||||
ESP_LOGCONFIG(TAG, " Pixel Format: %s", pixel_format_to_str(this->config_.pixel_format));
|
||||
|
||||
if (this->is_failed()) {
|
||||
ESP_LOGE(TAG, " Setup Failed: %s", esp_err_to_name(this->init_error_));
|
||||
@@ -184,8 +197,19 @@ void ESP32Camera::loop() {
|
||||
// check if we can return the image
|
||||
if (this->can_return_image_()) {
|
||||
// return image
|
||||
auto *fb = this->current_image_->get_raw_buffer();
|
||||
xQueueSend(this->framebuffer_return_queue_, &fb, portMAX_DELAY);
|
||||
#ifdef USE_ESP32_CAMERA_JPEG_CONVERSION
|
||||
if (this->config_.pixel_format != PIXFORMAT_JPEG && this->config_.jpeg_quality > 0) {
|
||||
// for non-JPEG format, we need to free the data and raw buffer
|
||||
auto *jpg_buf = this->current_image_->get_data_buffer();
|
||||
free(jpg_buf); // NOLINT(cppcoreguidelines-no-malloc)
|
||||
auto *fb = this->current_image_->get_raw_buffer();
|
||||
this->fb_allocator_.deallocate(fb, 1);
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
auto *fb = this->current_image_->get_raw_buffer();
|
||||
xQueueSend(this->framebuffer_return_queue_, &fb, portMAX_DELAY);
|
||||
}
|
||||
this->current_image_.reset();
|
||||
}
|
||||
|
||||
@@ -212,6 +236,38 @@ void ESP32Camera::loop() {
|
||||
xQueueSend(this->framebuffer_return_queue_, &fb, portMAX_DELAY);
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef USE_ESP32_CAMERA_JPEG_CONVERSION
|
||||
if (this->config_.pixel_format != PIXFORMAT_JPEG && this->config_.jpeg_quality > 0) {
|
||||
// for non-JPEG format, we need to convert the frame to JPEG
|
||||
uint8_t *jpg_buf;
|
||||
size_t jpg_buf_len;
|
||||
size_t width = fb->width;
|
||||
size_t height = fb->height;
|
||||
struct timeval timestamp = fb->timestamp;
|
||||
bool ok = frame2jpg(fb, 100 - this->config_.jpeg_quality, &jpg_buf, &jpg_buf_len);
|
||||
// return the original frame buffer to the queue
|
||||
xQueueSend(this->framebuffer_return_queue_, &fb, portMAX_DELAY);
|
||||
if (!ok) {
|
||||
ESP_LOGE(TAG, "Failed to convert frame to JPEG!");
|
||||
return;
|
||||
}
|
||||
// create a new camera_fb_t for the JPEG data
|
||||
fb = this->fb_allocator_.allocate(1);
|
||||
if (fb == nullptr) {
|
||||
ESP_LOGE(TAG, "Failed to allocate memory for camera frame buffer!");
|
||||
free(jpg_buf); // NOLINT(cppcoreguidelines-no-malloc)
|
||||
return;
|
||||
}
|
||||
memset(fb, 0, sizeof(camera_fb_t));
|
||||
fb->buf = jpg_buf;
|
||||
fb->len = jpg_buf_len;
|
||||
fb->width = width;
|
||||
fb->height = height;
|
||||
fb->format = PIXFORMAT_JPEG;
|
||||
fb->timestamp = timestamp;
|
||||
}
|
||||
#endif
|
||||
this->current_image_ = std::make_shared<ESP32CameraImage>(fb, this->single_requesters_ | this->stream_requesters_);
|
||||
|
||||
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
|
||||
@@ -342,6 +398,37 @@ void ESP32Camera::set_frame_size(ESP32CameraFrameSize size) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
void ESP32Camera::set_pixel_format(ESP32CameraPixelFormat format) {
|
||||
switch (format) {
|
||||
case ESP32_PIXEL_FORMAT_RGB565:
|
||||
this->config_.pixel_format = PIXFORMAT_RGB565;
|
||||
break;
|
||||
case ESP32_PIXEL_FORMAT_YUV422:
|
||||
this->config_.pixel_format = PIXFORMAT_YUV422;
|
||||
break;
|
||||
case ESP32_PIXEL_FORMAT_YUV420:
|
||||
this->config_.pixel_format = PIXFORMAT_YUV420;
|
||||
break;
|
||||
case ESP32_PIXEL_FORMAT_GRAYSCALE:
|
||||
this->config_.pixel_format = PIXFORMAT_GRAYSCALE;
|
||||
break;
|
||||
case ESP32_PIXEL_FORMAT_JPEG:
|
||||
this->config_.pixel_format = PIXFORMAT_JPEG;
|
||||
break;
|
||||
case ESP32_PIXEL_FORMAT_RGB888:
|
||||
this->config_.pixel_format = PIXFORMAT_RGB888;
|
||||
break;
|
||||
case ESP32_PIXEL_FORMAT_RAW:
|
||||
this->config_.pixel_format = PIXFORMAT_RAW;
|
||||
break;
|
||||
case ESP32_PIXEL_FORMAT_RGB444:
|
||||
this->config_.pixel_format = PIXFORMAT_RGB444;
|
||||
break;
|
||||
case ESP32_PIXEL_FORMAT_RGB555:
|
||||
this->config_.pixel_format = PIXFORMAT_RGB555;
|
||||
break;
|
||||
}
|
||||
}
|
||||
void ESP32Camera::set_jpeg_quality(uint8_t quality) { this->config_.jpeg_quality = quality; }
|
||||
void ESP32Camera::set_vertical_flip(bool vertical_flip) { this->vertical_flip_ = vertical_flip; }
|
||||
void ESP32Camera::set_horizontal_mirror(bool horizontal_mirror) { this->horizontal_mirror_ = horizontal_mirror; }
|
||||
|
||||
@@ -41,6 +41,18 @@ enum ESP32CameraFrameSize {
|
||||
ESP32_CAMERA_SIZE_2560X1920, // QSXGA
|
||||
};
|
||||
|
||||
enum ESP32CameraPixelFormat {
|
||||
ESP32_PIXEL_FORMAT_RGB565,
|
||||
ESP32_PIXEL_FORMAT_YUV422,
|
||||
ESP32_PIXEL_FORMAT_YUV420,
|
||||
ESP32_PIXEL_FORMAT_GRAYSCALE,
|
||||
ESP32_PIXEL_FORMAT_JPEG,
|
||||
ESP32_PIXEL_FORMAT_RGB888,
|
||||
ESP32_PIXEL_FORMAT_RAW,
|
||||
ESP32_PIXEL_FORMAT_RGB444,
|
||||
ESP32_PIXEL_FORMAT_RGB555,
|
||||
};
|
||||
|
||||
enum ESP32AgcGainCeiling {
|
||||
ESP32_GAINCEILING_2X = GAINCEILING_2X,
|
||||
ESP32_GAINCEILING_4X = GAINCEILING_4X,
|
||||
@@ -126,6 +138,7 @@ class ESP32Camera : public camera::Camera {
|
||||
void set_reset_pin(uint8_t pin);
|
||||
void set_power_down_pin(uint8_t pin);
|
||||
/* -- image */
|
||||
void set_pixel_format(ESP32CameraPixelFormat format);
|
||||
void set_frame_size(ESP32CameraFrameSize size);
|
||||
void set_jpeg_quality(uint8_t quality);
|
||||
void set_vertical_flip(bool vertical_flip);
|
||||
@@ -220,6 +233,7 @@ class ESP32Camera : public camera::Camera {
|
||||
#ifdef USE_I2C
|
||||
i2c::InternalI2CBus *i2c_bus_{nullptr};
|
||||
#endif // USE_I2C
|
||||
RAMAllocator<camera_fb_t> fb_allocator_{RAMAllocator<camera_fb_t>::ALLOC_INTERNAL};
|
||||
};
|
||||
|
||||
class ESP32CameraImageTrigger : public Trigger<CameraImageData>, public camera::CameraListener {
|
||||
|
||||
@@ -1,8 +1,30 @@
|
||||
from esphome.components import esp32
|
||||
import esphome.config_validation as cv
|
||||
from esphome.core import CORE
|
||||
|
||||
CODEOWNERS = ["@jesserockz"]
|
||||
|
||||
VARIANTS_NO_RMT = {esp32.VARIANT_ESP32C2, esp32.VARIANT_ESP32C61}
|
||||
|
||||
|
||||
def validate_rmt_not_supported(rmt_only_keys):
|
||||
"""Validate that RMT-only config keys are not used on variants without RMT hardware."""
|
||||
rmt_only_keys = set(rmt_only_keys)
|
||||
|
||||
def _validator(config):
|
||||
if CORE.is_esp32:
|
||||
variant = esp32.get_esp32_variant()
|
||||
if variant in VARIANTS_NO_RMT:
|
||||
unsupported = rmt_only_keys.intersection(config)
|
||||
if unsupported:
|
||||
keys = ", ".join(sorted(f"'{k}'" for k in unsupported))
|
||||
raise cv.Invalid(
|
||||
f"{keys} not available on {variant} (no RMT hardware)"
|
||||
)
|
||||
return config
|
||||
|
||||
return _validator
|
||||
|
||||
|
||||
def validate_clock_resolution():
|
||||
def _validator(value):
|
||||
|
||||
@@ -3,7 +3,7 @@ import logging
|
||||
|
||||
from esphome import pins
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import esp32, light
|
||||
from esphome.components import esp32, esp32_rmt, light
|
||||
from esphome.components.const import CONF_USE_PSRAM
|
||||
from esphome.components.esp32 import include_builtin_idf_component
|
||||
import esphome.config_validation as cv
|
||||
@@ -71,6 +71,10 @@ CONF_RESET_LOW = "reset_low"
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
esp32.only_on_variant(
|
||||
unsupported=list(esp32_rmt.VARIANTS_NO_RMT),
|
||||
msg_prefix="ESP32 RMT LED strip",
|
||||
),
|
||||
light.ADDRESSABLE_LIGHT_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(ESP32RMTLEDStripLightOutput),
|
||||
|
||||
@@ -14,9 +14,9 @@ extern "C" {
|
||||
|
||||
namespace esphome {
|
||||
|
||||
void IRAM_ATTR HOT yield() { ::yield(); }
|
||||
void HOT yield() { ::yield(); }
|
||||
uint32_t IRAM_ATTR HOT millis() { return ::millis(); }
|
||||
void IRAM_ATTR HOT delay(uint32_t ms) { ::delay(ms); }
|
||||
void HOT delay(uint32_t ms) { ::delay(ms); }
|
||||
uint32_t IRAM_ATTR HOT micros() { return ::micros(); }
|
||||
void IRAM_ATTR HOT delayMicroseconds(uint32_t us) { delay_microseconds_safe(us); }
|
||||
void arch_restart() {
|
||||
@@ -27,7 +27,7 @@ void arch_restart() {
|
||||
}
|
||||
}
|
||||
void arch_init() {}
|
||||
void IRAM_ATTR HOT arch_feed_wdt() { system_soft_wdt_feed(); }
|
||||
void HOT arch_feed_wdt() { system_soft_wdt_feed(); }
|
||||
|
||||
uint8_t progmem_read_byte(const uint8_t *addr) {
|
||||
return pgm_read_byte(addr); // NOLINT
|
||||
|
||||
@@ -202,11 +202,11 @@ async def add_pin_initial_states_array():
|
||||
|
||||
cg.add_global(
|
||||
cg.RawExpression(
|
||||
f"const uint8_t ESPHOME_ESP8266_GPIO_INITIAL_MODE[16] PROGMEM = {{{initial_modes_s}}}"
|
||||
f"constexpr uint8_t ESPHOME_ESP8266_GPIO_INITIAL_MODE[16] PROGMEM = {{{initial_modes_s}}}"
|
||||
)
|
||||
)
|
||||
cg.add_global(
|
||||
cg.RawExpression(
|
||||
f"const uint8_t ESPHOME_ESP8266_GPIO_INITIAL_LEVEL[16] PROGMEM = {{{initial_levels_s}}}"
|
||||
f"constexpr uint8_t ESPHOME_ESP8266_GPIO_INITIAL_LEVEL[16] PROGMEM = {{{initial_levels_s}}}"
|
||||
)
|
||||
)
|
||||
|
||||
@@ -28,10 +28,9 @@ static constexpr uint32_t OTA_SOCKET_TIMEOUT_HANDSHAKE = 20000; // milliseconds
|
||||
static constexpr uint32_t OTA_SOCKET_TIMEOUT_DATA = 90000; // milliseconds for data transfer
|
||||
|
||||
void ESPHomeOTAComponent::setup() {
|
||||
this->server_ = socket::socket_ip_loop_monitored(SOCK_STREAM, 0); // monitored for incoming connections
|
||||
this->server_ = socket::socket_ip_loop_monitored(SOCK_STREAM, 0).release(); // monitored for incoming connections
|
||||
if (this->server_ == nullptr) {
|
||||
this->log_socket_error_(LOG_STR("creation"));
|
||||
this->mark_failed();
|
||||
this->server_failed_(LOG_STR("creation"));
|
||||
return;
|
||||
}
|
||||
int enable = 1;
|
||||
@@ -42,8 +41,7 @@ void ESPHomeOTAComponent::setup() {
|
||||
}
|
||||
err = this->server_->setblocking(false);
|
||||
if (err != 0) {
|
||||
this->log_socket_error_(LOG_STR("non-blocking"));
|
||||
this->mark_failed();
|
||||
this->server_failed_(LOG_STR("nonblocking"));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -51,22 +49,19 @@ void ESPHomeOTAComponent::setup() {
|
||||
|
||||
socklen_t sl = socket::set_sockaddr_any((struct sockaddr *) &server, sizeof(server), this->port_);
|
||||
if (sl == 0) {
|
||||
this->log_socket_error_(LOG_STR("set sockaddr"));
|
||||
this->mark_failed();
|
||||
this->server_failed_(LOG_STR("set sockaddr"));
|
||||
return;
|
||||
}
|
||||
|
||||
err = this->server_->bind((struct sockaddr *) &server, sizeof(server));
|
||||
if (err != 0) {
|
||||
this->log_socket_error_(LOG_STR("bind"));
|
||||
this->mark_failed();
|
||||
this->server_failed_(LOG_STR("bind"));
|
||||
return;
|
||||
}
|
||||
|
||||
err = this->server_->listen(1); // Only one client at a time
|
||||
if (err != 0) {
|
||||
this->log_socket_error_(LOG_STR("listen"));
|
||||
this->mark_failed();
|
||||
this->server_failed_(LOG_STR("listen"));
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -455,6 +450,15 @@ void ESPHomeOTAComponent::log_remote_closed_(const LogString *during) {
|
||||
ESP_LOGW(TAG, "Remote closed at %s", LOG_STR_ARG(during));
|
||||
}
|
||||
|
||||
void ESPHomeOTAComponent::server_failed_(const LogString *msg) {
|
||||
this->log_socket_error_(msg);
|
||||
// No explicit close() needed — listen sockets have no active connections on
|
||||
// failure/shutdown. Destructor handles fd cleanup (close or abort per platform).
|
||||
delete this->server_;
|
||||
this->server_ = nullptr;
|
||||
this->mark_failed();
|
||||
}
|
||||
|
||||
bool ESPHomeOTAComponent::handle_read_error_(ssize_t read, const LogString *desc) {
|
||||
if (read == -1 && this->would_block_(errno)) {
|
||||
return false; // No data yet, try again next loop
|
||||
|
||||
@@ -66,6 +66,7 @@ class ESPHomeOTAComponent : public ota::OTAComponent {
|
||||
this->handshake_buf_pos_ = 0; // Reset buffer position for next state
|
||||
}
|
||||
|
||||
void server_failed_(const LogString *msg);
|
||||
void log_socket_error_(const LogString *msg);
|
||||
void log_read_error_(const LogString *what);
|
||||
void log_start_(const LogString *phase);
|
||||
@@ -83,7 +84,7 @@ class ESPHomeOTAComponent : public ota::OTAComponent {
|
||||
std::unique_ptr<uint8_t[]> auth_buf_;
|
||||
#endif // USE_OTA_PASSWORD
|
||||
|
||||
std::unique_ptr<socket::Socket> server_;
|
||||
socket::Socket *server_{nullptr};
|
||||
std::unique_ptr<socket::Socket> client_;
|
||||
std::unique_ptr<ota::OTABackend> backend_;
|
||||
|
||||
|
||||
@@ -218,12 +218,19 @@ def _validate(config):
|
||||
)
|
||||
elif config[CONF_TYPE] != "OPENETH":
|
||||
if CONF_CLK_MODE in config:
|
||||
mode, pin = CLK_MODES_DEPRECATED[config[CONF_CLK_MODE]]
|
||||
LOGGER.warning(
|
||||
"[ethernet] The 'clk_mode' option is deprecated and will be removed in ESPHome 2026.1. "
|
||||
"Please update your configuration to use 'clk' instead."
|
||||
"[ethernet] The 'clk_mode' option is deprecated. "
|
||||
"Please replace 'clk_mode: %s' with:\n"
|
||||
" clk:\n"
|
||||
" mode: %s\n"
|
||||
" pin: %s\n"
|
||||
"Removal scheduled for 2026.7.0.",
|
||||
config[CONF_CLK_MODE],
|
||||
mode,
|
||||
pin,
|
||||
)
|
||||
mode = CLK_MODES_DEPRECATED[config[CONF_CLK_MODE]]
|
||||
config[CONF_CLK] = CLK_SCHEMA({CONF_MODE: mode[0], CONF_PIN: mode[1]})
|
||||
config[CONF_CLK] = CLK_SCHEMA({CONF_MODE: mode, CONF_PIN: pin})
|
||||
del config[CONF_CLK_MODE]
|
||||
elif CONF_CLK not in config:
|
||||
raise cv.Invalid("'clk' is a required option for [ethernet].")
|
||||
|
||||
@@ -221,12 +221,17 @@ void Fan::publish_state() {
|
||||
}
|
||||
|
||||
// Random 32-bit value, change this every time the layout of the FanRestoreState struct changes.
|
||||
constexpr uint32_t RESTORE_STATE_VERSION = 0x71700ABA;
|
||||
constexpr uint32_t RESTORE_STATE_VERSION = 0x71700ABB;
|
||||
optional<FanRestoreState> Fan::restore_state_() {
|
||||
FanRestoreState recovered{};
|
||||
this->rtc_ = this->make_entity_preference<FanRestoreState>(RESTORE_STATE_VERSION);
|
||||
bool restored = this->rtc_.load(&recovered);
|
||||
|
||||
if (!restored) {
|
||||
// No valid saved data; ensure preset_mode sentinel is set
|
||||
recovered.preset_mode = FanRestoreState::NO_PRESET;
|
||||
}
|
||||
|
||||
switch (this->restore_mode_) {
|
||||
case FanRestoreMode::NO_RESTORE:
|
||||
return {};
|
||||
@@ -264,6 +269,7 @@ void Fan::save_state_() {
|
||||
state.oscillating = this->oscillating;
|
||||
state.speed = this->speed;
|
||||
state.direction = this->direction;
|
||||
state.preset_mode = FanRestoreState::NO_PRESET;
|
||||
|
||||
if (this->has_preset_mode()) {
|
||||
const auto &preset_modes = traits.supported_preset_modes();
|
||||
|
||||
@@ -91,11 +91,13 @@ class FanCall {
|
||||
};
|
||||
|
||||
struct FanRestoreState {
|
||||
static constexpr uint8_t NO_PRESET = UINT8_MAX;
|
||||
|
||||
bool state;
|
||||
int speed;
|
||||
bool oscillating;
|
||||
FanDirection direction;
|
||||
uint8_t preset_mode;
|
||||
uint8_t preset_mode{NO_PRESET};
|
||||
|
||||
/// Convert this struct to a fan call that can be performed.
|
||||
FanCall to_call(Fan &fan);
|
||||
|
||||
@@ -28,15 +28,15 @@ fan::FanCall HBridgeFan::brake() {
|
||||
}
|
||||
|
||||
void HBridgeFan::setup() {
|
||||
// Construct traits before restore so preset modes can be looked up by index
|
||||
this->traits_ = fan::FanTraits(this->oscillating_ != nullptr, true, true, this->speed_count_);
|
||||
this->traits_.set_supported_preset_modes(this->preset_modes_);
|
||||
|
||||
auto restore = this->restore_state_();
|
||||
if (restore.has_value()) {
|
||||
restore->apply(*this);
|
||||
this->write_state_();
|
||||
}
|
||||
|
||||
// Construct traits
|
||||
this->traits_ = fan::FanTraits(this->oscillating_ != nullptr, true, true, this->speed_count_);
|
||||
this->traits_.set_supported_preset_modes(this->preset_modes_);
|
||||
}
|
||||
|
||||
void HBridgeFan::dump_config() {
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
namespace esphome {
|
||||
|
||||
void IRAM_ATTR HOT yield() { ::sched_yield(); }
|
||||
void HOT yield() { ::sched_yield(); }
|
||||
uint32_t IRAM_ATTR HOT millis() {
|
||||
struct timespec spec;
|
||||
clock_gettime(CLOCK_MONOTONIC, &spec);
|
||||
@@ -19,7 +19,7 @@ uint32_t IRAM_ATTR HOT millis() {
|
||||
uint32_t ms = round(spec.tv_nsec / 1e6);
|
||||
return ((uint32_t) seconds) * 1000U + ms;
|
||||
}
|
||||
void IRAM_ATTR HOT delay(uint32_t ms) {
|
||||
void HOT delay(uint32_t ms) {
|
||||
struct timespec ts;
|
||||
ts.tv_sec = ms / 1000;
|
||||
ts.tv_nsec = (ms % 1000) * 1000000;
|
||||
@@ -48,7 +48,7 @@ void arch_restart() { exit(0); }
|
||||
void arch_init() {
|
||||
// pass
|
||||
}
|
||||
void IRAM_ATTR HOT arch_feed_wdt() {
|
||||
void HOT arch_feed_wdt() {
|
||||
// pass
|
||||
}
|
||||
|
||||
|
||||
@@ -302,15 +302,19 @@ async def http_request_action_to_code(config, action_id, template_arg, args):
|
||||
lambda_ = await cg.process_lambda(json_, args_, return_type=cg.void)
|
||||
cg.add(var.set_json(lambda_))
|
||||
else:
|
||||
cg.add(var.init_json(len(json_)))
|
||||
for key in json_:
|
||||
template_ = await cg.templatable(json_[key], args, cg.std_string)
|
||||
cg.add(var.add_json(key, template_))
|
||||
for key, value in config.get(CONF_REQUEST_HEADERS, {}).items():
|
||||
request_headers = config.get(CONF_REQUEST_HEADERS, {})
|
||||
if request_headers:
|
||||
cg.add(var.init_request_headers(len(request_headers)))
|
||||
for key, value in request_headers.items():
|
||||
template_ = await cg.templatable(value, args, cg.const_char_ptr)
|
||||
cg.add(var.add_request_header(key, template_))
|
||||
|
||||
for value in config.get(CONF_COLLECT_HEADERS, []):
|
||||
cg.add(var.add_collect_header(value))
|
||||
cg.add(var.add_collect_header(value.lower()))
|
||||
|
||||
if response_conf := config.get(CONF_ON_RESPONSE):
|
||||
if capture_response:
|
||||
|
||||
@@ -22,23 +22,15 @@ void HttpRequestComponent::dump_config() {
|
||||
}
|
||||
|
||||
std::string HttpContainer::get_response_header(const std::string &header_name) {
|
||||
auto response_headers = this->get_response_headers();
|
||||
auto header_name_lower_case = str_lower_case(header_name);
|
||||
if (response_headers.count(header_name_lower_case) == 0) {
|
||||
ESP_LOGW(TAG, "No header with name %s found", header_name_lower_case.c_str());
|
||||
return "";
|
||||
} else {
|
||||
auto values = response_headers[header_name_lower_case];
|
||||
if (values.empty()) {
|
||||
ESP_LOGE(TAG, "header with name %s returned an empty list, this shouldn't happen",
|
||||
header_name_lower_case.c_str());
|
||||
return "";
|
||||
} else {
|
||||
auto header_value = values.front();
|
||||
ESP_LOGD(TAG, "Header with name %s found with value %s", header_name_lower_case.c_str(), header_value.c_str());
|
||||
return header_value;
|
||||
auto lower = str_lower_case(header_name);
|
||||
for (const auto &entry : this->response_headers_) {
|
||||
if (entry.name == lower) {
|
||||
ESP_LOGD(TAG, "Header with name %s found with value %s", lower.c_str(), entry.value.c_str());
|
||||
return entry.value;
|
||||
}
|
||||
}
|
||||
ESP_LOGW(TAG, "No header with name %s found", lower.c_str());
|
||||
return "";
|
||||
}
|
||||
|
||||
} // namespace esphome::http_request
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <utility>
|
||||
@@ -80,6 +79,16 @@ inline bool is_redirect(int const status) {
|
||||
*/
|
||||
inline bool is_success(int const status) { return status >= HTTP_STATUS_OK && status < HTTP_STATUS_MULTIPLE_CHOICES; }
|
||||
|
||||
/// Check if a header name should be collected (linear scan, fine for small lists)
|
||||
inline bool should_collect_header(const std::vector<std::string> &lower_case_collect_headers,
|
||||
const std::string &lower_header_name) {
|
||||
for (const auto &h : lower_case_collect_headers) {
|
||||
if (h == lower_header_name)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* HTTP Container Read Semantics
|
||||
* =============================
|
||||
@@ -258,20 +267,13 @@ class HttpContainer : public Parented<HttpRequestComponent> {
|
||||
return !this->is_chunked_ && this->bytes_read_ >= this->content_length;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get response headers.
|
||||
*
|
||||
* @return The key is the lower case response header name, the value is the header value.
|
||||
*/
|
||||
std::map<std::string, std::list<std::string>> get_response_headers() { return this->response_headers_; }
|
||||
|
||||
std::string get_response_header(const std::string &header_name);
|
||||
|
||||
protected:
|
||||
size_t bytes_read_{0};
|
||||
bool secure_{false};
|
||||
bool is_chunked_{false}; ///< True if response uses chunked transfer encoding
|
||||
std::map<std::string, std::list<std::string>> response_headers_{};
|
||||
std::vector<Header> response_headers_{};
|
||||
};
|
||||
|
||||
/// Read data from HTTP container into buffer with timeout handling
|
||||
@@ -333,8 +335,8 @@ class HttpRequestComponent : public Component {
|
||||
return this->start(url, "GET", "", request_headers);
|
||||
}
|
||||
std::shared_ptr<HttpContainer> get(const std::string &url, const std::list<Header> &request_headers,
|
||||
const std::set<std::string> &collect_headers) {
|
||||
return this->start(url, "GET", "", request_headers, collect_headers);
|
||||
const std::vector<std::string> &lower_case_collect_headers) {
|
||||
return this->start(url, "GET", "", request_headers, lower_case_collect_headers);
|
||||
}
|
||||
std::shared_ptr<HttpContainer> post(const std::string &url, const std::string &body) {
|
||||
return this->start(url, "POST", body, {});
|
||||
@@ -345,29 +347,40 @@ class HttpRequestComponent : public Component {
|
||||
}
|
||||
std::shared_ptr<HttpContainer> post(const std::string &url, const std::string &body,
|
||||
const std::list<Header> &request_headers,
|
||||
const std::set<std::string> &collect_headers) {
|
||||
return this->start(url, "POST", body, request_headers, collect_headers);
|
||||
const std::vector<std::string> &lower_case_collect_headers) {
|
||||
return this->start(url, "POST", body, request_headers, lower_case_collect_headers);
|
||||
}
|
||||
|
||||
std::shared_ptr<HttpContainer> start(const std::string &url, const std::string &method, const std::string &body,
|
||||
const std::list<Header> &request_headers) {
|
||||
return this->start(url, method, body, request_headers, {});
|
||||
// Call perform() directly to avoid ambiguity with the std::set overload
|
||||
return this->perform(url, method, body, request_headers, {});
|
||||
}
|
||||
|
||||
// Remove before 2027.1.0
|
||||
ESPDEPRECATED("Pass collect_headers as std::vector<std::string> instead of std::set. Removed in 2027.1.0.",
|
||||
"2026.7.0")
|
||||
std::shared_ptr<HttpContainer> start(const std::string &url, const std::string &method, const std::string &body,
|
||||
const std::list<Header> &request_headers,
|
||||
const std::set<std::string> &collect_headers) {
|
||||
std::vector<std::string> lower;
|
||||
lower.reserve(collect_headers.size());
|
||||
for (const auto &h : collect_headers) {
|
||||
lower.push_back(str_lower_case(h));
|
||||
}
|
||||
return this->perform(url, method, body, request_headers, lower);
|
||||
}
|
||||
|
||||
std::shared_ptr<HttpContainer> start(const std::string &url, const std::string &method, const std::string &body,
|
||||
const std::list<Header> &request_headers,
|
||||
const std::set<std::string> &collect_headers) {
|
||||
std::set<std::string> lower_case_collect_headers;
|
||||
for (const std::string &collect_header : collect_headers) {
|
||||
lower_case_collect_headers.insert(str_lower_case(collect_header));
|
||||
}
|
||||
const std::vector<std::string> &lower_case_collect_headers) {
|
||||
return this->perform(url, method, body, request_headers, lower_case_collect_headers);
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual std::shared_ptr<HttpContainer> perform(const std::string &url, const std::string &method,
|
||||
const std::string &body, const std::list<Header> &request_headers,
|
||||
const std::set<std::string> &collect_headers) = 0;
|
||||
const std::vector<std::string> &lower_case_collect_headers) = 0;
|
||||
const char *useragent_{nullptr};
|
||||
bool follow_redirects_{};
|
||||
uint16_t redirect_limit_{};
|
||||
@@ -385,13 +398,15 @@ template<typename... Ts> class HttpRequestSendAction : public Action<Ts...> {
|
||||
TEMPLATABLE_VALUE(bool, capture_response)
|
||||
#endif
|
||||
|
||||
void init_request_headers(size_t count) { this->request_headers_.init(count); }
|
||||
void add_request_header(const char *key, TemplatableValue<const char *, Ts...> value) {
|
||||
this->request_headers_.insert({key, value});
|
||||
this->request_headers_.push_back({key, value});
|
||||
}
|
||||
|
||||
void add_collect_header(const char *value) { this->collect_headers_.insert(value); }
|
||||
void add_collect_header(const char *value) { this->lower_case_collect_headers_.push_back(value); }
|
||||
|
||||
void add_json(const char *key, TemplatableValue<std::string, Ts...> value) { this->json_.insert({key, value}); }
|
||||
void init_json(size_t count) { this->json_.init(count); }
|
||||
void add_json(const char *key, TemplatableValue<std::string, Ts...> value) { this->json_.push_back({key, value}); }
|
||||
|
||||
void set_json(std::function<void(Ts..., JsonObject)> json_func) { this->json_func_ = json_func; }
|
||||
|
||||
@@ -431,7 +446,7 @@ template<typename... Ts> class HttpRequestSendAction : public Action<Ts...> {
|
||||
}
|
||||
|
||||
auto container = this->parent_->start(this->url_.value(x...), this->method_.value(x...), body, request_headers,
|
||||
this->collect_headers_);
|
||||
this->lower_case_collect_headers_);
|
||||
|
||||
auto captured_args = std::make_tuple(x...);
|
||||
|
||||
@@ -493,9 +508,9 @@ template<typename... Ts> class HttpRequestSendAction : public Action<Ts...> {
|
||||
}
|
||||
void encode_json_func_(Ts... x, JsonObject root) { this->json_func_(x..., root); }
|
||||
HttpRequestComponent *parent_;
|
||||
std::map<const char *, TemplatableValue<const char *, Ts...>> request_headers_{};
|
||||
std::set<std::string> collect_headers_{"content-type", "content-length"};
|
||||
std::map<const char *, TemplatableValue<std::string, Ts...>> json_{};
|
||||
FixedVector<std::pair<const char *, TemplatableValue<const char *, Ts...>>> request_headers_{};
|
||||
std::vector<std::string> lower_case_collect_headers_{"content-type", "content-length"};
|
||||
FixedVector<std::pair<const char *, TemplatableValue<std::string, Ts...>>> json_{};
|
||||
std::function<void(Ts..., JsonObject)> json_func_{nullptr};
|
||||
#ifdef USE_HTTP_REQUEST_RESPONSE
|
||||
Trigger<std::shared_ptr<HttpContainer>, std::string &, Ts...> success_trigger_with_response_;
|
||||
|
||||
@@ -9,14 +9,25 @@
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
// Include BearSSL error constants for TLS failure diagnostics
|
||||
#ifdef USE_ESP8266
|
||||
#include <bearssl/bearssl_ssl.h>
|
||||
#endif
|
||||
|
||||
namespace esphome::http_request {
|
||||
|
||||
static const char *const TAG = "http_request.arduino";
|
||||
#ifdef USE_ESP8266
|
||||
static constexpr int RX_BUFFER_SIZE = 512;
|
||||
static constexpr int TX_BUFFER_SIZE = 512;
|
||||
// ESP8266 Arduino core (WiFiClientSecureBearSSL.cpp) returns -1000 on OOM
|
||||
static constexpr int ESP8266_SSL_ERR_OOM = -1000;
|
||||
#endif
|
||||
|
||||
std::shared_ptr<HttpContainer> HttpRequestArduino::perform(const std::string &url, const std::string &method,
|
||||
const std::string &body,
|
||||
const std::list<Header> &request_headers,
|
||||
const std::set<std::string> &collect_headers) {
|
||||
const std::vector<std::string> &lower_case_collect_headers) {
|
||||
if (!network::is_connected()) {
|
||||
this->status_momentary_error("failed", 1000);
|
||||
ESP_LOGW(TAG, "HTTP Request failed; Not connected to network");
|
||||
@@ -47,7 +58,7 @@ std::shared_ptr<HttpContainer> HttpRequestArduino::perform(const std::string &ur
|
||||
ESP_LOGV(TAG, "ESP8266 HTTPS connection with WiFiClientSecure");
|
||||
stream_ptr = std::make_unique<WiFiClientSecure>();
|
||||
WiFiClientSecure *secure_client = static_cast<WiFiClientSecure *>(stream_ptr.get());
|
||||
secure_client->setBufferSizes(512, 512);
|
||||
secure_client->setBufferSizes(RX_BUFFER_SIZE, TX_BUFFER_SIZE);
|
||||
secure_client->setInsecure();
|
||||
} else {
|
||||
stream_ptr = std::make_unique<WiFiClient>();
|
||||
@@ -96,9 +107,9 @@ std::shared_ptr<HttpContainer> HttpRequestArduino::perform(const std::string &ur
|
||||
}
|
||||
|
||||
// returned needed headers must be collected before the requests
|
||||
const char *header_keys[collect_headers.size()];
|
||||
const char *header_keys[lower_case_collect_headers.size()];
|
||||
int index = 0;
|
||||
for (auto const &header_name : collect_headers) {
|
||||
for (auto const &header_name : lower_case_collect_headers) {
|
||||
header_keys[index++] = header_name.c_str();
|
||||
}
|
||||
container->client_.collectHeaders(header_keys, index);
|
||||
@@ -107,27 +118,56 @@ std::shared_ptr<HttpContainer> HttpRequestArduino::perform(const std::string &ur
|
||||
container->status_code = container->client_.sendRequest(method.c_str(), body.c_str());
|
||||
App.feed_wdt();
|
||||
if (container->status_code < 0) {
|
||||
#if defined(USE_ESP8266) && defined(USE_HTTP_REQUEST_ESP8266_HTTPS)
|
||||
if (secure) {
|
||||
WiFiClientSecure *secure_client = static_cast<WiFiClientSecure *>(stream_ptr.get());
|
||||
int last_error = secure_client->getLastSSLError();
|
||||
|
||||
if (last_error != 0) {
|
||||
const LogString *error_msg;
|
||||
switch (last_error) {
|
||||
case ESP8266_SSL_ERR_OOM:
|
||||
error_msg = LOG_STR("Unable to allocate buffer memory");
|
||||
break;
|
||||
case BR_ERR_TOO_LARGE:
|
||||
error_msg = LOG_STR("Incoming TLS record does not fit in receive buffer (BR_ERR_TOO_LARGE)");
|
||||
break;
|
||||
default:
|
||||
error_msg = LOG_STR("Unknown SSL error");
|
||||
break;
|
||||
}
|
||||
ESP_LOGW(TAG, "SSL failure: %s (Code: %d)", LOG_STR_ARG(error_msg), last_error);
|
||||
if (last_error == ESP8266_SSL_ERR_OOM) {
|
||||
ESP_LOGW(TAG, "Heap free: %u bytes, configured buffer sizes: %u bytes", ESP.getFreeHeap(),
|
||||
static_cast<unsigned int>(RX_BUFFER_SIZE + TX_BUFFER_SIZE));
|
||||
}
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Connection failure with no error code");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
ESP_LOGW(TAG, "HTTP Request failed; URL: %s; Error: %s", url.c_str(),
|
||||
HTTPClient::errorToString(container->status_code).c_str());
|
||||
|
||||
this->status_momentary_error("failed", 1000);
|
||||
container->end();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!is_success(container->status_code)) {
|
||||
ESP_LOGE(TAG, "HTTP Request failed; URL: %s; Code: %d", url.c_str(), container->status_code);
|
||||
this->status_momentary_error("failed", 1000);
|
||||
// Still return the container, so it can be used to get the status code and error message
|
||||
}
|
||||
|
||||
container->response_headers_ = {};
|
||||
container->response_headers_.clear();
|
||||
auto header_count = container->client_.headers();
|
||||
for (int i = 0; i < header_count; i++) {
|
||||
const std::string header_name = str_lower_case(container->client_.headerName(i).c_str());
|
||||
if (collect_headers.count(header_name) > 0) {
|
||||
if (should_collect_header(lower_case_collect_headers, header_name)) {
|
||||
std::string header_value = container->client_.header(i).c_str();
|
||||
ESP_LOGD(TAG, "Received response header, name: %s, value: %s", header_name.c_str(), header_value.c_str());
|
||||
container->response_headers_[header_name].push_back(header_value);
|
||||
container->response_headers_.push_back({header_name, header_value});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ class HttpRequestArduino : public HttpRequestComponent {
|
||||
protected:
|
||||
std::shared_ptr<HttpContainer> perform(const std::string &url, const std::string &method, const std::string &body,
|
||||
const std::list<Header> &request_headers,
|
||||
const std::set<std::string> &collect_headers) override;
|
||||
const std::vector<std::string> &lower_case_collect_headers) override;
|
||||
};
|
||||
|
||||
} // namespace esphome::http_request
|
||||
|
||||
@@ -19,7 +19,7 @@ static const char *const TAG = "http_request.host";
|
||||
std::shared_ptr<HttpContainer> HttpRequestHost::perform(const std::string &url, const std::string &method,
|
||||
const std::string &body,
|
||||
const std::list<Header> &request_headers,
|
||||
const std::set<std::string> &response_headers) {
|
||||
const std::vector<std::string> &lower_case_collect_headers) {
|
||||
if (!network::is_connected()) {
|
||||
this->status_momentary_error("failed", 1000);
|
||||
ESP_LOGW(TAG, "HTTP Request failed; Not connected to network");
|
||||
@@ -116,8 +116,8 @@ std::shared_ptr<HttpContainer> HttpRequestHost::perform(const std::string &url,
|
||||
for (auto header : response.headers) {
|
||||
ESP_LOGD(TAG, "Header: %s: %s", header.first.c_str(), header.second.c_str());
|
||||
auto lower_name = str_lower_case(header.first);
|
||||
if (response_headers.find(lower_name) != response_headers.end()) {
|
||||
container->response_headers_[lower_name].emplace_back(header.second);
|
||||
if (should_collect_header(lower_case_collect_headers, lower_name)) {
|
||||
container->response_headers_.push_back({lower_name, header.second});
|
||||
}
|
||||
}
|
||||
container->duration_ms = millis() - start;
|
||||
|
||||
@@ -20,7 +20,7 @@ class HttpRequestHost : public HttpRequestComponent {
|
||||
public:
|
||||
std::shared_ptr<HttpContainer> perform(const std::string &url, const std::string &method, const std::string &body,
|
||||
const std::list<Header> &request_headers,
|
||||
const std::set<std::string> &response_headers) override;
|
||||
const std::vector<std::string> &lower_case_collect_headers) override;
|
||||
void set_ca_path(const char *ca_path) { this->ca_path_ = ca_path; }
|
||||
|
||||
protected:
|
||||
|
||||
@@ -19,8 +19,8 @@ namespace esphome::http_request {
|
||||
static const char *const TAG = "http_request.idf";
|
||||
|
||||
struct UserData {
|
||||
const std::set<std::string> &collect_headers;
|
||||
std::map<std::string, std::list<std::string>> response_headers;
|
||||
const std::vector<std::string> &lower_case_collect_headers;
|
||||
std::vector<Header> &response_headers;
|
||||
};
|
||||
|
||||
void HttpRequestIDF::dump_config() {
|
||||
@@ -38,10 +38,10 @@ esp_err_t HttpRequestIDF::http_event_handler(esp_http_client_event_t *evt) {
|
||||
switch (evt->event_id) {
|
||||
case HTTP_EVENT_ON_HEADER: {
|
||||
const std::string header_name = str_lower_case(evt->header_key);
|
||||
if (user_data->collect_headers.count(header_name)) {
|
||||
if (should_collect_header(user_data->lower_case_collect_headers, header_name)) {
|
||||
const std::string header_value = evt->header_value;
|
||||
ESP_LOGD(TAG, "Received response header, name: %s, value: %s", header_name.c_str(), header_value.c_str());
|
||||
user_data->response_headers[header_name].push_back(header_value);
|
||||
user_data->response_headers.push_back({header_name, header_value});
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -55,7 +55,7 @@ esp_err_t HttpRequestIDF::http_event_handler(esp_http_client_event_t *evt) {
|
||||
std::shared_ptr<HttpContainer> HttpRequestIDF::perform(const std::string &url, const std::string &method,
|
||||
const std::string &body,
|
||||
const std::list<Header> &request_headers,
|
||||
const std::set<std::string> &collect_headers) {
|
||||
const std::vector<std::string> &lower_case_collect_headers) {
|
||||
if (!network::is_connected()) {
|
||||
this->status_momentary_error("failed", 1000);
|
||||
ESP_LOGE(TAG, "HTTP Request failed; Not connected to network");
|
||||
@@ -110,8 +110,6 @@ std::shared_ptr<HttpContainer> HttpRequestIDF::perform(const std::string &url, c
|
||||
watchdog::WatchdogManager wdm(this->get_watchdog_timeout());
|
||||
|
||||
config.event_handler = http_event_handler;
|
||||
auto user_data = UserData{collect_headers, {}};
|
||||
config.user_data = static_cast<void *>(&user_data);
|
||||
|
||||
esp_http_client_handle_t client = esp_http_client_init(&config);
|
||||
|
||||
@@ -120,6 +118,9 @@ std::shared_ptr<HttpContainer> HttpRequestIDF::perform(const std::string &url, c
|
||||
|
||||
container->set_secure(secure);
|
||||
|
||||
auto user_data = UserData{lower_case_collect_headers, container->response_headers_};
|
||||
esp_http_client_set_user_data(client, static_cast<void *>(&user_data));
|
||||
|
||||
for (const auto &header : request_headers) {
|
||||
esp_http_client_set_header(client, header.name.c_str(), header.value.c_str());
|
||||
}
|
||||
@@ -164,7 +165,6 @@ std::shared_ptr<HttpContainer> HttpRequestIDF::perform(const std::string &url, c
|
||||
container->feed_wdt();
|
||||
container->status_code = esp_http_client_get_status_code(client);
|
||||
container->feed_wdt();
|
||||
container->set_response_headers(user_data.response_headers);
|
||||
container->duration_ms = millis() - start;
|
||||
if (is_success(container->status_code)) {
|
||||
return container;
|
||||
|
||||
@@ -21,11 +21,8 @@ class HttpContainerIDF : public HttpContainer {
|
||||
/// @brief Feeds the watchdog timer if the executing task has one attached
|
||||
void feed_wdt();
|
||||
|
||||
void set_response_headers(std::map<std::string, std::list<std::string>> &response_headers) {
|
||||
this->response_headers_ = std::move(response_headers);
|
||||
}
|
||||
|
||||
protected:
|
||||
friend class HttpRequestIDF;
|
||||
esp_http_client_handle_t client_;
|
||||
};
|
||||
|
||||
@@ -41,7 +38,7 @@ class HttpRequestIDF : public HttpRequestComponent {
|
||||
protected:
|
||||
std::shared_ptr<HttpContainer> perform(const std::string &url, const std::string &method, const std::string &body,
|
||||
const std::list<Header> &request_headers,
|
||||
const std::set<std::string> &collect_headers) override;
|
||||
const std::vector<std::string> &lower_case_collect_headers) override;
|
||||
// if zero ESP-IDF will use DEFAULT_HTTP_BUF_SIZE
|
||||
uint16_t buffer_size_rx_{};
|
||||
uint16_t buffer_size_tx_{};
|
||||
|
||||
@@ -267,37 +267,6 @@ class I2CDevice {
|
||||
|
||||
bool write_byte_16(uint8_t a_register, uint16_t data) const { return write_bytes_16(a_register, &data, 1); }
|
||||
|
||||
// Deprecated functions
|
||||
|
||||
ESPDEPRECATED("The stop argument is no longer used. This will be removed from ESPHome 2026.3.0", "2025.9.0")
|
||||
ErrorCode read_register(uint8_t a_register, uint8_t *data, size_t len, bool stop) {
|
||||
return this->read_register(a_register, data, len);
|
||||
}
|
||||
|
||||
ESPDEPRECATED("The stop argument is no longer used. This will be removed from ESPHome 2026.3.0", "2025.9.0")
|
||||
ErrorCode read_register16(uint16_t a_register, uint8_t *data, size_t len, bool stop) {
|
||||
return this->read_register16(a_register, data, len);
|
||||
}
|
||||
|
||||
ESPDEPRECATED("The stop argument is no longer used; use write_read() for consecutive write and read. This will be "
|
||||
"removed from ESPHome 2026.3.0",
|
||||
"2025.9.0")
|
||||
ErrorCode write(const uint8_t *data, size_t len, bool stop) const { return this->write(data, len); }
|
||||
|
||||
ESPDEPRECATED("The stop argument is no longer used; use write_read() for consecutive write and read. This will be "
|
||||
"removed from ESPHome 2026.3.0",
|
||||
"2025.9.0")
|
||||
ErrorCode write_register(uint8_t a_register, const uint8_t *data, size_t len, bool stop) const {
|
||||
return this->write_register(a_register, data, len);
|
||||
}
|
||||
|
||||
ESPDEPRECATED("The stop argument is no longer used; use write_read() for consecutive write and read. This will be "
|
||||
"removed from ESPHome 2026.3.0",
|
||||
"2025.9.0")
|
||||
ErrorCode write_register16(uint16_t a_register, const uint8_t *data, size_t len, bool stop) const {
|
||||
return this->write_register16(a_register, data, len);
|
||||
}
|
||||
|
||||
protected:
|
||||
uint8_t address_{0x00}; ///< store the address of the device on the bus
|
||||
I2CBus *bus_{nullptr}; ///< pointer to I2CBus instance
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
#pragma once
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
@@ -24,18 +22,6 @@ enum ErrorCode {
|
||||
ERROR_CRC = 7, ///< bytes received with a CRC error
|
||||
};
|
||||
|
||||
/// @brief the ReadBuffer structure stores a pointer to a read buffer and its length
|
||||
struct ReadBuffer {
|
||||
uint8_t *data; ///< pointer to the read buffer
|
||||
size_t len; ///< length of the buffer
|
||||
};
|
||||
|
||||
/// @brief the WriteBuffer structure stores a pointer to a write buffer and its length
|
||||
struct WriteBuffer {
|
||||
const uint8_t *data; ///< pointer to the write buffer
|
||||
size_t len; ///< length of the buffer
|
||||
};
|
||||
|
||||
/// @brief This Class provides the methods to read and write bytes from an I2CBus.
|
||||
/// @note The I2CBus virtual class follows a *Factory design pattern* that provides all the interfaces methods required
|
||||
/// by clients while deferring the actual implementation of these methods to a subclasses. I2C-bus specification and
|
||||
@@ -68,50 +54,6 @@ class I2CBus {
|
||||
return this->write_readv(address, buffer, len, nullptr, 0);
|
||||
}
|
||||
|
||||
ESPDEPRECATED("This method is deprecated and will be removed in ESPHome 2026.3.0. Use write_readv() instead.",
|
||||
"2025.9.0")
|
||||
ErrorCode readv(uint8_t address, ReadBuffer *read_buffers, size_t count) {
|
||||
size_t total_len = 0;
|
||||
for (size_t i = 0; i != count; i++) {
|
||||
total_len += read_buffers[i].len;
|
||||
}
|
||||
|
||||
SmallBufferWithHeapFallback<128> buffer_alloc(total_len); // Most I2C reads are small
|
||||
uint8_t *buffer = buffer_alloc.get();
|
||||
|
||||
auto err = this->write_readv(address, nullptr, 0, buffer, total_len);
|
||||
if (err != ERROR_OK)
|
||||
return err;
|
||||
size_t pos = 0;
|
||||
for (size_t i = 0; i != count; i++) {
|
||||
if (read_buffers[i].len != 0) {
|
||||
std::memcpy(read_buffers[i].data, buffer + pos, read_buffers[i].len);
|
||||
pos += read_buffers[i].len;
|
||||
}
|
||||
}
|
||||
return ERROR_OK;
|
||||
}
|
||||
|
||||
ESPDEPRECATED("This method is deprecated and will be removed in ESPHome 2026.3.0. Use write_readv() instead.",
|
||||
"2025.9.0")
|
||||
ErrorCode writev(uint8_t address, const WriteBuffer *write_buffers, size_t count, bool stop = true) {
|
||||
size_t total_len = 0;
|
||||
for (size_t i = 0; i != count; i++) {
|
||||
total_len += write_buffers[i].len;
|
||||
}
|
||||
|
||||
SmallBufferWithHeapFallback<128> buffer_alloc(total_len); // Most I2C writes are small
|
||||
uint8_t *buffer = buffer_alloc.get();
|
||||
|
||||
size_t pos = 0;
|
||||
for (size_t i = 0; i != count; i++) {
|
||||
std::memcpy(buffer + pos, write_buffers[i].data, write_buffers[i].len);
|
||||
pos += write_buffers[i].len;
|
||||
}
|
||||
|
||||
return this->write_readv(address, buffer, total_len, nullptr, 0);
|
||||
}
|
||||
|
||||
protected:
|
||||
/// @brief Scans the I2C bus for devices. Devices presence is kept in an array of std::pair
|
||||
/// that contains the address and the corresponding bool presence flag.
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace esphome {
|
||||
namespace ili9xxx {
|
||||
|
||||
// clang-format off
|
||||
static const uint8_t PROGMEM INITCMD_M5STACK[] = {
|
||||
static constexpr uint8_t PROGMEM INITCMD_M5STACK[] = {
|
||||
0xEF, 3, 0x03, 0x80, 0x02,
|
||||
0xCF, 3, 0x00, 0xC1, 0x30,
|
||||
0xED, 4, 0x64, 0x03, 0x12, 0x81,
|
||||
@@ -37,7 +37,7 @@ static const uint8_t PROGMEM INITCMD_M5STACK[] = {
|
||||
0x00 // End of list
|
||||
};
|
||||
|
||||
static const uint8_t PROGMEM INITCMD_M5CORE[] = {
|
||||
static constexpr uint8_t PROGMEM INITCMD_M5CORE[] = {
|
||||
ILI9XXX_SETEXTC, 3, 0xFF,0x93,0x42, // Turn on the external command
|
||||
ILI9XXX_PWCTR1 , 2, 0x12, 0x12,
|
||||
ILI9XXX_PWCTR2 , 1, 0x03,
|
||||
@@ -56,7 +56,7 @@ static const uint8_t PROGMEM INITCMD_M5CORE[] = {
|
||||
|
||||
|
||||
|
||||
static const uint8_t PROGMEM INITCMD_ILI9341[] = {
|
||||
static constexpr uint8_t PROGMEM INITCMD_ILI9341[] = {
|
||||
0xEF, 3, 0x03, 0x80, 0x02,
|
||||
0xCF, 3, 0x00, 0xC1, 0x30,
|
||||
0xED, 4, 0x64, 0x03, 0x12, 0x81,
|
||||
@@ -86,7 +86,7 @@ static const uint8_t PROGMEM INITCMD_ILI9341[] = {
|
||||
0x00 // End of list
|
||||
};
|
||||
|
||||
static const uint8_t PROGMEM INITCMD_ILI9481[] = {
|
||||
static constexpr uint8_t PROGMEM INITCMD_ILI9481[] = {
|
||||
ILI9XXX_SLPOUT , 0x80, // Exit sleep mode
|
||||
ILI9XXX_PWSET , 3, 0x07, 0x41, 0x1D,
|
||||
ILI9XXX_VMCTR , 3, 0x00, 0x1C, 0x1F,
|
||||
@@ -105,7 +105,7 @@ static const uint8_t PROGMEM INITCMD_ILI9481[] = {
|
||||
0x00 // end
|
||||
};
|
||||
|
||||
static const uint8_t PROGMEM INITCMD_ILI9481_18[] = {
|
||||
static constexpr uint8_t PROGMEM INITCMD_ILI9481_18[] = {
|
||||
ILI9XXX_SLPOUT , 0x80, // Exit sleep mode
|
||||
ILI9XXX_PWSET , 3, 0x07, 0x41, 0x1D,
|
||||
ILI9XXX_VMCTR , 3, 0x00, 0x1C, 0x1F,
|
||||
@@ -124,7 +124,7 @@ static const uint8_t PROGMEM INITCMD_ILI9481_18[] = {
|
||||
0x00 // end
|
||||
};
|
||||
|
||||
static const uint8_t PROGMEM INITCMD_ILI9486[] = {
|
||||
static constexpr uint8_t PROGMEM INITCMD_ILI9486[] = {
|
||||
ILI9XXX_SLPOUT, 0x80,
|
||||
ILI9XXX_PIXFMT, 1, 0x55,
|
||||
ILI9XXX_PWCTR3, 1, 0x44,
|
||||
@@ -173,7 +173,7 @@ static const uint8_t INITCMD_WAVESHARE_RES_3_5[] = {
|
||||
0x00 // End of list
|
||||
};
|
||||
|
||||
static const uint8_t PROGMEM INITCMD_ILI9488_A[] = {
|
||||
static constexpr uint8_t PROGMEM INITCMD_ILI9488_A[] = {
|
||||
ILI9XXX_GMCTRP1,15, 0x00, 0x03, 0x09, 0x08, 0x16, 0x0A, 0x3F, 0x78, 0x4C, 0x09, 0x0A, 0x08, 0x16, 0x1A, 0x0F,
|
||||
ILI9XXX_GMCTRN1,15, 0x00, 0x16, 0x19, 0x03, 0x0F, 0x05, 0x32, 0x45, 0x46, 0x04, 0x0E, 0x0D, 0x35, 0x37, 0x0F,
|
||||
|
||||
@@ -206,7 +206,7 @@ static const uint8_t PROGMEM INITCMD_ILI9488_A[] = {
|
||||
0x00 // end
|
||||
};
|
||||
|
||||
static const uint8_t PROGMEM INITCMD_ST7796[] = {
|
||||
static constexpr uint8_t PROGMEM INITCMD_ST7796[] = {
|
||||
// This ST7796S initilization routine was copied from https://github.com/prenticedavid/Adafruit_ST7796S_kbv/blob/master/Adafruit_ST7796S_kbv.cpp
|
||||
ILI9XXX_SWRESET, 0x80, // Soft reset, then delay 150 ms
|
||||
ILI9XXX_CSCON, 1, 0xC3, // ?? Unlock Manufacturer
|
||||
@@ -226,7 +226,7 @@ static const uint8_t PROGMEM INITCMD_ST7796[] = {
|
||||
0x00 // End of list
|
||||
};
|
||||
|
||||
static const uint8_t PROGMEM INITCMD_S3BOX[] = {
|
||||
static constexpr uint8_t PROGMEM INITCMD_S3BOX[] = {
|
||||
0xEF, 3, 0x03, 0x80, 0x02,
|
||||
0xCF, 3, 0x00, 0xC1, 0x30,
|
||||
0xED, 4, 0x64, 0x03, 0x12, 0x81,
|
||||
@@ -256,7 +256,7 @@ static const uint8_t PROGMEM INITCMD_S3BOX[] = {
|
||||
0x00 // End of list
|
||||
};
|
||||
|
||||
static const uint8_t PROGMEM INITCMD_S3BOXLITE[] = {
|
||||
static constexpr uint8_t PROGMEM INITCMD_S3BOXLITE[] = {
|
||||
0xEF, 3, 0x03, 0x80, 0x02,
|
||||
0xCF, 3, 0x00, 0xC1, 0x30,
|
||||
0xED, 4, 0x64, 0x03, 0x12, 0x81,
|
||||
@@ -286,7 +286,7 @@ static const uint8_t PROGMEM INITCMD_S3BOXLITE[] = {
|
||||
0x00 // End of list
|
||||
};
|
||||
|
||||
static const uint8_t PROGMEM INITCMD_ST7789V[] = {
|
||||
static constexpr uint8_t PROGMEM INITCMD_ST7789V[] = {
|
||||
ILI9XXX_SLPOUT , 0x80, // Exit Sleep
|
||||
ILI9XXX_DISPON , 0x80, // Display on
|
||||
ILI9XXX_MADCTL , 1, 0x08, // Memory Access Control, BGR
|
||||
@@ -313,7 +313,7 @@ static const uint8_t PROGMEM INITCMD_ST7789V[] = {
|
||||
0x00 // End of list
|
||||
};
|
||||
|
||||
static const uint8_t PROGMEM INITCMD_GC9A01A[] = {
|
||||
static constexpr uint8_t PROGMEM INITCMD_GC9A01A[] = {
|
||||
0xEF, 0,
|
||||
0xEB, 1, 0x14, // ?
|
||||
0xFE, 0,
|
||||
@@ -367,7 +367,7 @@ static const uint8_t PROGMEM INITCMD_GC9A01A[] = {
|
||||
0x00 // End of list
|
||||
};
|
||||
|
||||
static const uint8_t PROGMEM INITCMD_GC9D01N[] = {
|
||||
static constexpr uint8_t PROGMEM INITCMD_GC9D01N[] = {
|
||||
// Enable Inter_command
|
||||
0xFE, 0, // Inter Register Enable 1 (FEh)
|
||||
0xEF, 0, // Inter Register Enable 2 (EFh)
|
||||
@@ -426,7 +426,7 @@ static const uint8_t PROGMEM INITCMD_GC9D01N[] = {
|
||||
0x00 // End of list
|
||||
};
|
||||
|
||||
static const uint8_t PROGMEM INITCMD_ST7735[] = {
|
||||
static constexpr uint8_t PROGMEM INITCMD_ST7735[] = {
|
||||
ILI9XXX_SWRESET, 0, // Soft reset, then delay 10ms
|
||||
ILI9XXX_DELAY(10),
|
||||
ILI9XXX_SLPOUT , 0, // Exit Sleep, delay
|
||||
|
||||
@@ -15,7 +15,7 @@ static const char *const TAG = "json";
|
||||
static SpiRamAllocator global_json_allocator;
|
||||
#endif
|
||||
|
||||
std::string build_json(const json_build_t &f) {
|
||||
SerializationBuffer<> build_json(const json_build_t &f) {
|
||||
// NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
|
||||
JsonBuilder builder;
|
||||
JsonObject root = builder.root();
|
||||
@@ -66,14 +66,83 @@ JsonDocument parse_json(const uint8_t *data, size_t len) {
|
||||
// NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks)
|
||||
}
|
||||
|
||||
std::string JsonBuilder::serialize() {
|
||||
SerializationBuffer<> JsonBuilder::serialize() {
|
||||
// ===========================================================================================
|
||||
// CRITICAL: NRVO (Named Return Value Optimization) - DO NOT REFACTOR WITHOUT UNDERSTANDING
|
||||
// ===========================================================================================
|
||||
//
|
||||
// This function is carefully structured to enable NRVO. The compiler constructs `result`
|
||||
// directly in the caller's stack frame, eliminating the move constructor call entirely.
|
||||
//
|
||||
// WITHOUT NRVO: Each return would trigger SerializationBuffer's move constructor, which
|
||||
// must memcpy up to 512 bytes of stack buffer content. This happens on EVERY JSON
|
||||
// serialization (sensor updates, web server responses, MQTT publishes, etc.).
|
||||
//
|
||||
// WITH NRVO: Zero memcpy, zero move constructor overhead. The buffer lives directly
|
||||
// where the caller needs it.
|
||||
//
|
||||
// Requirements for NRVO to work:
|
||||
// 1. Single named variable (`result`) returned from ALL paths
|
||||
// 2. All paths must return the SAME variable (not different variables)
|
||||
// 3. No std::move() on the return statement
|
||||
//
|
||||
// If you must modify this function:
|
||||
// - Keep a single `result` variable declared at the top
|
||||
// - All code paths must return `result` (not a different variable)
|
||||
// - Verify NRVO still works by checking the disassembly for move constructor calls
|
||||
// - Test: objdump -d -C firmware.elf | grep "SerializationBuffer.*SerializationBuffer"
|
||||
// Should show only destructor, NOT move constructor
|
||||
//
|
||||
// Try stack buffer first. 640 bytes covers 99.9% of JSON payloads (sensors ~200B,
|
||||
// lights ~170B, climate ~500-700B). Only entities with 40+ options exceed this.
|
||||
//
|
||||
// IMPORTANT: ArduinoJson's serializeJson() with a bounded buffer returns the actual
|
||||
// bytes written (truncated count), NOT the would-be size like snprintf(). When the
|
||||
// payload exceeds the buffer, the return value equals the buffer capacity. The heap
|
||||
// fallback doubles the buffer size until the payload fits. This avoids instantiating
|
||||
// measureJson()'s DummyWriter templates (~736 bytes flash) at the cost of temporarily
|
||||
// over-allocating heap (at most 2x) for the rare payloads that exceed 640 bytes.
|
||||
//
|
||||
// ===========================================================================================
|
||||
constexpr size_t buf_size = SerializationBuffer<>::BUFFER_SIZE;
|
||||
SerializationBuffer<> result(buf_size - 1); // Max content size (reserve 1 for null)
|
||||
|
||||
if (doc_.overflowed()) {
|
||||
ESP_LOGE(TAG, "JSON document overflow");
|
||||
return "{}";
|
||||
auto *buf = result.data_writable_();
|
||||
buf[0] = '{';
|
||||
buf[1] = '}';
|
||||
buf[2] = '\0';
|
||||
result.set_size_(2);
|
||||
return result;
|
||||
}
|
||||
std::string output;
|
||||
serializeJson(doc_, output);
|
||||
return output;
|
||||
|
||||
size_t size = serializeJson(doc_, result.data_writable_(), buf_size);
|
||||
if (size < buf_size) {
|
||||
// Fits in stack buffer - update size to actual length
|
||||
result.set_size_(size);
|
||||
return result;
|
||||
}
|
||||
|
||||
// Payload exceeded stack buffer. Double the buffer and retry until it fits.
|
||||
// In practice, one iteration (1024 bytes) covers all known entity types.
|
||||
// Payloads exceeding 1024 bytes are not known to exist in real configurations.
|
||||
// Cap at 5120 as a safety limit to prevent runaway allocation.
|
||||
constexpr size_t max_heap_size = 5120;
|
||||
size_t heap_size = buf_size * 2;
|
||||
while (heap_size <= max_heap_size) {
|
||||
result.reallocate_heap_(heap_size - 1);
|
||||
size = serializeJson(doc_, result.data_writable_(), heap_size);
|
||||
if (size < heap_size) {
|
||||
result.set_size_(size);
|
||||
return result;
|
||||
}
|
||||
heap_size *= 2;
|
||||
}
|
||||
// Payload exceeds 5120 bytes - return truncated result
|
||||
ESP_LOGW(TAG, "JSON payload too large, truncated to %zu bytes", size);
|
||||
result.set_size_(size);
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace json
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "esphome/core/defines.h"
|
||||
@@ -14,11 +16,116 @@
|
||||
namespace esphome {
|
||||
namespace json {
|
||||
|
||||
/// Buffer for JSON serialization that uses stack allocation for small payloads.
|
||||
/// Template parameter STACK_SIZE specifies the stack buffer size (default 512 bytes).
|
||||
/// Supports move semantics for efficient return-by-value.
|
||||
template<size_t STACK_SIZE = 640> class SerializationBuffer {
|
||||
public:
|
||||
static constexpr size_t BUFFER_SIZE = STACK_SIZE; ///< Stack buffer size for this instantiation
|
||||
|
||||
/// Construct with known size (typically from measureJson)
|
||||
explicit SerializationBuffer(size_t size) : size_(size) {
|
||||
if (size + 1 <= STACK_SIZE) {
|
||||
buffer_ = stack_buffer_;
|
||||
} else {
|
||||
heap_buffer_ = new char[size + 1];
|
||||
buffer_ = heap_buffer_;
|
||||
}
|
||||
buffer_[0] = '\0';
|
||||
}
|
||||
|
||||
~SerializationBuffer() { delete[] heap_buffer_; }
|
||||
|
||||
// Move constructor - works with same template instantiation
|
||||
SerializationBuffer(SerializationBuffer &&other) noexcept : heap_buffer_(other.heap_buffer_), size_(other.size_) {
|
||||
if (other.buffer_ == other.stack_buffer_) {
|
||||
// Stack buffer - must copy content
|
||||
std::memcpy(stack_buffer_, other.stack_buffer_, size_ + 1);
|
||||
buffer_ = stack_buffer_;
|
||||
} else {
|
||||
// Heap buffer - steal ownership
|
||||
buffer_ = heap_buffer_;
|
||||
other.heap_buffer_ = nullptr;
|
||||
}
|
||||
// Leave moved-from object in valid empty state
|
||||
other.stack_buffer_[0] = '\0';
|
||||
other.buffer_ = other.stack_buffer_;
|
||||
other.size_ = 0;
|
||||
}
|
||||
|
||||
// Move assignment
|
||||
SerializationBuffer &operator=(SerializationBuffer &&other) noexcept {
|
||||
if (this != &other) {
|
||||
delete[] heap_buffer_;
|
||||
heap_buffer_ = other.heap_buffer_;
|
||||
size_ = other.size_;
|
||||
if (other.buffer_ == other.stack_buffer_) {
|
||||
std::memcpy(stack_buffer_, other.stack_buffer_, size_ + 1);
|
||||
buffer_ = stack_buffer_;
|
||||
} else {
|
||||
buffer_ = heap_buffer_;
|
||||
other.heap_buffer_ = nullptr;
|
||||
}
|
||||
// Leave moved-from object in valid empty state
|
||||
other.stack_buffer_[0] = '\0';
|
||||
other.buffer_ = other.stack_buffer_;
|
||||
other.size_ = 0;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Delete copy operations
|
||||
SerializationBuffer(const SerializationBuffer &) = delete;
|
||||
SerializationBuffer &operator=(const SerializationBuffer &) = delete;
|
||||
|
||||
/// Get null-terminated C string
|
||||
const char *c_str() const { return buffer_; }
|
||||
/// Get data pointer
|
||||
const char *data() const { return buffer_; }
|
||||
/// Get string length (excluding null terminator)
|
||||
size_t size() const { return size_; }
|
||||
|
||||
/// Implicit conversion to std::string for backward compatibility
|
||||
/// WARNING: This allocates a new std::string on the heap. Prefer using
|
||||
/// c_str() or data()/size() directly when possible to avoid allocation.
|
||||
operator std::string() const { return std::string(buffer_, size_); } // NOLINT(google-explicit-constructor)
|
||||
|
||||
private:
|
||||
friend class JsonBuilder; ///< Allows JsonBuilder::serialize() to call private methods
|
||||
|
||||
/// Get writable buffer (for serialization)
|
||||
char *data_writable_() { return buffer_; }
|
||||
/// Set actual size after serialization (must not exceed allocated size)
|
||||
/// Also ensures null termination for c_str() safety
|
||||
void set_size_(size_t size) {
|
||||
size_ = size;
|
||||
buffer_[size] = '\0';
|
||||
}
|
||||
|
||||
/// Reallocate to heap buffer with new size (for when stack buffer is too small)
|
||||
/// This invalidates any previous buffer content. Used by JsonBuilder::serialize().
|
||||
void reallocate_heap_(size_t size) {
|
||||
delete[] heap_buffer_;
|
||||
heap_buffer_ = new char[size + 1];
|
||||
buffer_ = heap_buffer_;
|
||||
size_ = size;
|
||||
buffer_[0] = '\0';
|
||||
}
|
||||
|
||||
char stack_buffer_[STACK_SIZE];
|
||||
char *heap_buffer_{nullptr};
|
||||
char *buffer_;
|
||||
size_t size_;
|
||||
};
|
||||
|
||||
#ifdef USE_PSRAM
|
||||
// Build an allocator for the JSON Library using the RAMAllocator class
|
||||
// This is only compiled when PSRAM is enabled
|
||||
struct SpiRamAllocator : ArduinoJson::Allocator {
|
||||
void *allocate(size_t size) override { return allocator_.allocate(size); }
|
||||
void *allocate(size_t size) override {
|
||||
RAMAllocator<uint8_t> allocator;
|
||||
return allocator.allocate(size);
|
||||
}
|
||||
|
||||
void deallocate(void *ptr) override {
|
||||
// ArduinoJson's Allocator interface doesn't provide the size parameter in deallocate.
|
||||
@@ -31,11 +138,9 @@ struct SpiRamAllocator : ArduinoJson::Allocator {
|
||||
}
|
||||
|
||||
void *reallocate(void *ptr, size_t new_size) override {
|
||||
return allocator_.reallocate(static_cast<uint8_t *>(ptr), new_size);
|
||||
RAMAllocator<uint8_t> allocator;
|
||||
return allocator.reallocate(static_cast<uint8_t *>(ptr), new_size);
|
||||
}
|
||||
|
||||
protected:
|
||||
RAMAllocator<uint8_t> allocator_{RAMAllocator<uint8_t>::NONE};
|
||||
};
|
||||
#endif
|
||||
|
||||
@@ -46,7 +151,8 @@ using json_parse_t = std::function<bool(JsonObject)>;
|
||||
using json_build_t = std::function<void(JsonObject)>;
|
||||
|
||||
/// Build a JSON string with the provided json build function.
|
||||
std::string build_json(const json_build_t &f);
|
||||
/// Returns SerializationBuffer for stack-first allocation; implicitly converts to std::string.
|
||||
SerializationBuffer<> build_json(const json_build_t &f);
|
||||
|
||||
/// Parse a JSON string and run the provided json parse function if it's valid.
|
||||
bool parse_json(const std::string &data, const json_parse_t &f);
|
||||
@@ -71,7 +177,9 @@ class JsonBuilder {
|
||||
return root_;
|
||||
}
|
||||
|
||||
std::string serialize();
|
||||
/// Serialize the JSON document to a SerializationBuffer (stack-first allocation)
|
||||
/// Uses 512-byte stack buffer by default, falls back to heap for larger JSON
|
||||
SerializationBuffer<> serialize();
|
||||
|
||||
private:
|
||||
#ifdef USE_PSRAM
|
||||
|
||||
@@ -608,8 +608,9 @@ void LD2410Component::readline_(int readch) {
|
||||
// We should never get here, but just in case...
|
||||
ESP_LOGW(TAG, "Max command length exceeded; ignoring");
|
||||
this->buffer_pos_ = 0;
|
||||
return;
|
||||
}
|
||||
if (this->buffer_pos_ < 4) {
|
||||
if (this->buffer_pos_ < HEADER_FOOTER_SIZE) {
|
||||
return; // Not enough data to process yet
|
||||
}
|
||||
if (ld2410::validate_header_footer(DATA_FRAME_FOOTER, &this->buffer_data_[this->buffer_pos_ - 4])) {
|
||||
|
||||
@@ -33,8 +33,10 @@ namespace esphome::ld2410 {
|
||||
|
||||
using namespace ld24xx;
|
||||
|
||||
static constexpr uint8_t MAX_LINE_LENGTH = 46; // Max characters for serial buffer
|
||||
static constexpr uint8_t TOTAL_GATES = 9; // Total number of gates supported by the LD2410
|
||||
// Engineering data frame is 45 bytes; +1 for null terminator, +4 so that a frame footer always
|
||||
// lands inside the buffer during footer-based resynchronization after losing sync.
|
||||
static constexpr uint8_t MAX_LINE_LENGTH = 50;
|
||||
static constexpr uint8_t TOTAL_GATES = 9; // Total number of gates supported by the LD2410
|
||||
|
||||
class LD2410Component : public Component, public uart::UARTDevice {
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
|
||||
@@ -63,73 +63,73 @@ namespace esphome::ld2420 {
|
||||
static const char *const TAG = "ld2420";
|
||||
|
||||
// Local const's
|
||||
static const uint16_t REFRESH_RATE_MS = 1000;
|
||||
static constexpr uint16_t REFRESH_RATE_MS = 1000;
|
||||
|
||||
// Command sets
|
||||
static const uint16_t CMD_DISABLE_CONF = 0x00FE;
|
||||
static const uint16_t CMD_ENABLE_CONF = 0x00FF;
|
||||
static const uint16_t CMD_PARM_HIGH_TRESH = 0x0012;
|
||||
static const uint16_t CMD_PARM_LOW_TRESH = 0x0021;
|
||||
static const uint16_t CMD_PROTOCOL_VER = 0x0002;
|
||||
static const uint16_t CMD_READ_ABD_PARAM = 0x0008;
|
||||
static const uint16_t CMD_READ_REG_ADDR = 0x0020;
|
||||
static const uint16_t CMD_READ_REGISTER = 0x0002;
|
||||
static const uint16_t CMD_READ_SERIAL_NUM = 0x0011;
|
||||
static const uint16_t CMD_READ_SYS_PARAM = 0x0013;
|
||||
static const uint16_t CMD_READ_VERSION = 0x0000;
|
||||
static const uint16_t CMD_RESTART = 0x0068;
|
||||
static const uint16_t CMD_SYSTEM_MODE = 0x0000;
|
||||
static const uint16_t CMD_SYSTEM_MODE_GR = 0x0003;
|
||||
static const uint16_t CMD_SYSTEM_MODE_MTT = 0x0001;
|
||||
static const uint16_t CMD_SYSTEM_MODE_SIMPLE = 0x0064;
|
||||
static const uint16_t CMD_SYSTEM_MODE_DEBUG = 0x0000;
|
||||
static const uint16_t CMD_SYSTEM_MODE_ENERGY = 0x0004;
|
||||
static const uint16_t CMD_SYSTEM_MODE_VS = 0x0002;
|
||||
static const uint16_t CMD_WRITE_ABD_PARAM = 0x0007;
|
||||
static const uint16_t CMD_WRITE_REGISTER = 0x0001;
|
||||
static const uint16_t CMD_WRITE_SYS_PARAM = 0x0012;
|
||||
static constexpr uint16_t CMD_DISABLE_CONF = 0x00FE;
|
||||
static constexpr uint16_t CMD_ENABLE_CONF = 0x00FF;
|
||||
static constexpr uint16_t CMD_PARM_HIGH_TRESH = 0x0012;
|
||||
static constexpr uint16_t CMD_PARM_LOW_TRESH = 0x0021;
|
||||
static constexpr uint16_t CMD_PROTOCOL_VER = 0x0002;
|
||||
static constexpr uint16_t CMD_READ_ABD_PARAM = 0x0008;
|
||||
static constexpr uint16_t CMD_READ_REG_ADDR = 0x0020;
|
||||
static constexpr uint16_t CMD_READ_REGISTER = 0x0002;
|
||||
static constexpr uint16_t CMD_READ_SERIAL_NUM = 0x0011;
|
||||
static constexpr uint16_t CMD_READ_SYS_PARAM = 0x0013;
|
||||
static constexpr uint16_t CMD_READ_VERSION = 0x0000;
|
||||
static constexpr uint16_t CMD_RESTART = 0x0068;
|
||||
static constexpr uint16_t CMD_SYSTEM_MODE = 0x0000;
|
||||
static constexpr uint16_t CMD_SYSTEM_MODE_GR = 0x0003;
|
||||
static constexpr uint16_t CMD_SYSTEM_MODE_MTT = 0x0001;
|
||||
static constexpr uint16_t CMD_SYSTEM_MODE_SIMPLE = 0x0064;
|
||||
static constexpr uint16_t CMD_SYSTEM_MODE_DEBUG = 0x0000;
|
||||
static constexpr uint16_t CMD_SYSTEM_MODE_ENERGY = 0x0004;
|
||||
static constexpr uint16_t CMD_SYSTEM_MODE_VS = 0x0002;
|
||||
static constexpr uint16_t CMD_WRITE_ABD_PARAM = 0x0007;
|
||||
static constexpr uint16_t CMD_WRITE_REGISTER = 0x0001;
|
||||
static constexpr uint16_t CMD_WRITE_SYS_PARAM = 0x0012;
|
||||
|
||||
static const uint8_t CMD_ABD_DATA_REPLY_SIZE = 0x04;
|
||||
static const uint8_t CMD_ABD_DATA_REPLY_START = 0x0A;
|
||||
static const uint8_t CMD_MAX_BYTES = 0x64;
|
||||
static const uint8_t CMD_REG_DATA_REPLY_SIZE = 0x02;
|
||||
static constexpr uint8_t CMD_ABD_DATA_REPLY_SIZE = 0x04;
|
||||
static constexpr uint8_t CMD_ABD_DATA_REPLY_START = 0x0A;
|
||||
static constexpr uint8_t CMD_MAX_BYTES = 0x64;
|
||||
static constexpr uint8_t CMD_REG_DATA_REPLY_SIZE = 0x02;
|
||||
|
||||
static const uint8_t LD2420_ERROR_NONE = 0x00;
|
||||
static const uint8_t LD2420_ERROR_TIMEOUT = 0x02;
|
||||
static const uint8_t LD2420_ERROR_UNKNOWN = 0x01;
|
||||
static constexpr uint8_t LD2420_ERROR_NONE = 0x00;
|
||||
static constexpr uint8_t LD2420_ERROR_TIMEOUT = 0x02;
|
||||
static constexpr uint8_t LD2420_ERROR_UNKNOWN = 0x01;
|
||||
|
||||
// Register address values
|
||||
static const uint16_t CMD_MIN_GATE_REG = 0x0000;
|
||||
static const uint16_t CMD_MAX_GATE_REG = 0x0001;
|
||||
static const uint16_t CMD_TIMEOUT_REG = 0x0004;
|
||||
static const uint16_t CMD_GATE_MOVE_THRESH[TOTAL_GATES] = {0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015,
|
||||
0x0016, 0x0017, 0x0018, 0x0019, 0x001A, 0x001B,
|
||||
0x001C, 0x001D, 0x001E, 0x001F};
|
||||
static const uint16_t CMD_GATE_STILL_THRESH[TOTAL_GATES] = {0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025,
|
||||
0x0026, 0x0027, 0x0028, 0x0029, 0x002A, 0x002B,
|
||||
0x002C, 0x002D, 0x002E, 0x002F};
|
||||
static const uint32_t FACTORY_MOVE_THRESH[TOTAL_GATES] = {60000, 30000, 400, 250, 250, 250, 250, 250,
|
||||
250, 250, 250, 250, 250, 250, 250, 250};
|
||||
static const uint32_t FACTORY_STILL_THRESH[TOTAL_GATES] = {40000, 20000, 200, 200, 200, 200, 200, 150,
|
||||
150, 100, 100, 100, 100, 100, 100, 100};
|
||||
static const uint16_t FACTORY_TIMEOUT = 120;
|
||||
static const uint16_t FACTORY_MIN_GATE = 1;
|
||||
static const uint16_t FACTORY_MAX_GATE = 12;
|
||||
static constexpr uint16_t CMD_MIN_GATE_REG = 0x0000;
|
||||
static constexpr uint16_t CMD_MAX_GATE_REG = 0x0001;
|
||||
static constexpr uint16_t CMD_TIMEOUT_REG = 0x0004;
|
||||
static constexpr uint16_t CMD_GATE_MOVE_THRESH[TOTAL_GATES] = {0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015,
|
||||
0x0016, 0x0017, 0x0018, 0x0019, 0x001A, 0x001B,
|
||||
0x001C, 0x001D, 0x001E, 0x001F};
|
||||
static constexpr uint16_t CMD_GATE_STILL_THRESH[TOTAL_GATES] = {0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025,
|
||||
0x0026, 0x0027, 0x0028, 0x0029, 0x002A, 0x002B,
|
||||
0x002C, 0x002D, 0x002E, 0x002F};
|
||||
static constexpr uint32_t FACTORY_MOVE_THRESH[TOTAL_GATES] = {60000, 30000, 400, 250, 250, 250, 250, 250,
|
||||
250, 250, 250, 250, 250, 250, 250, 250};
|
||||
static constexpr uint32_t FACTORY_STILL_THRESH[TOTAL_GATES] = {40000, 20000, 200, 200, 200, 200, 200, 150,
|
||||
150, 100, 100, 100, 100, 100, 100, 100};
|
||||
static constexpr uint16_t FACTORY_TIMEOUT = 120;
|
||||
static constexpr uint16_t FACTORY_MIN_GATE = 1;
|
||||
static constexpr uint16_t FACTORY_MAX_GATE = 12;
|
||||
|
||||
// COMMAND_BYTE Header & Footer
|
||||
static const uint32_t CMD_FRAME_FOOTER = 0x01020304;
|
||||
static const uint32_t CMD_FRAME_HEADER = 0xFAFBFCFD;
|
||||
static const uint32_t DEBUG_FRAME_FOOTER = 0xFAFBFCFD;
|
||||
static const uint32_t DEBUG_FRAME_HEADER = 0x1410BFAA;
|
||||
static const uint32_t ENERGY_FRAME_FOOTER = 0xF5F6F7F8;
|
||||
static const uint32_t ENERGY_FRAME_HEADER = 0xF1F2F3F4;
|
||||
static const int CALIBRATE_VERSION_MIN = 154;
|
||||
static const uint8_t CMD_FRAME_COMMAND = 6;
|
||||
static const uint8_t CMD_FRAME_DATA_LENGTH = 4;
|
||||
static const uint8_t CMD_FRAME_STATUS = 7;
|
||||
static const uint8_t CMD_ERROR_WORD = 8;
|
||||
static const uint8_t ENERGY_SENSOR_START = 9;
|
||||
static const uint8_t CALIBRATE_REPORT_INTERVAL = 4;
|
||||
static constexpr uint32_t CMD_FRAME_FOOTER = 0x01020304;
|
||||
static constexpr uint32_t CMD_FRAME_HEADER = 0xFAFBFCFD;
|
||||
static constexpr uint32_t DEBUG_FRAME_FOOTER = 0xFAFBFCFD;
|
||||
static constexpr uint32_t DEBUG_FRAME_HEADER = 0x1410BFAA;
|
||||
static constexpr uint32_t ENERGY_FRAME_FOOTER = 0xF5F6F7F8;
|
||||
static constexpr uint32_t ENERGY_FRAME_HEADER = 0xF1F2F3F4;
|
||||
static constexpr int CALIBRATE_VERSION_MIN = 154;
|
||||
static constexpr uint8_t CMD_FRAME_COMMAND = 6;
|
||||
static constexpr uint8_t CMD_FRAME_DATA_LENGTH = 4;
|
||||
static constexpr uint8_t CMD_FRAME_STATUS = 7;
|
||||
static constexpr uint8_t CMD_ERROR_WORD = 8;
|
||||
static constexpr uint8_t ENERGY_SENSOR_START = 9;
|
||||
static constexpr uint8_t CALIBRATE_REPORT_INTERVAL = 4;
|
||||
static const char *const OP_NORMAL_MODE_STRING = "Normal";
|
||||
static const char *const OP_SIMPLE_MODE_STRING = "Simple";
|
||||
|
||||
|
||||
@@ -20,9 +20,11 @@
|
||||
|
||||
namespace esphome::ld2420 {
|
||||
|
||||
static const uint8_t CALIBRATE_SAMPLES = 64;
|
||||
static const uint8_t MAX_LINE_LENGTH = 46; // Max characters for serial buffer
|
||||
static const uint8_t TOTAL_GATES = 16;
|
||||
static constexpr uint8_t CALIBRATE_SAMPLES = 64;
|
||||
// Energy frame is 45 bytes; +1 for null terminator, +4 so that a frame footer always lands
|
||||
// inside the buffer during footer-based resynchronization after losing sync.
|
||||
static constexpr uint8_t MAX_LINE_LENGTH = 50;
|
||||
static constexpr uint8_t TOTAL_GATES = 16;
|
||||
|
||||
enum OpMode : uint8_t {
|
||||
OP_NORMAL_MODE = 1,
|
||||
|
||||
@@ -776,8 +776,9 @@ void LD2450Component::readline_(int readch) {
|
||||
// We should never get here, but just in case...
|
||||
ESP_LOGW(TAG, "Max command length exceeded; ignoring");
|
||||
this->buffer_pos_ = 0;
|
||||
return;
|
||||
}
|
||||
if (this->buffer_pos_ < 4) {
|
||||
if (this->buffer_pos_ < HEADER_FOOTER_SIZE) {
|
||||
return; // Not enough data to process yet
|
||||
}
|
||||
if (this->buffer_data_[this->buffer_pos_ - 2] == DATA_FRAME_FOOTER[0] &&
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/component.h"
|
||||
#ifdef USE_SENSOR
|
||||
@@ -37,9 +38,11 @@ using namespace ld24xx;
|
||||
|
||||
// Constants
|
||||
static constexpr uint8_t DEFAULT_PRESENCE_TIMEOUT = 5; // Timeout to reset presense status 5 sec.
|
||||
static constexpr uint8_t MAX_LINE_LENGTH = 41; // Max characters for serial buffer
|
||||
static constexpr uint8_t MAX_TARGETS = 3; // Max 3 Targets in LD2450
|
||||
static constexpr uint8_t MAX_ZONES = 3; // Max 3 Zones in LD2450
|
||||
// Zone query response is 40 bytes; +1 for null terminator, +4 so that a frame footer always
|
||||
// lands inside the buffer during footer-based resynchronization after losing sync.
|
||||
static constexpr uint8_t MAX_LINE_LENGTH = 45;
|
||||
static constexpr uint8_t MAX_TARGETS = 3; // Max 3 Targets in LD2450
|
||||
static constexpr uint8_t MAX_ZONES = 3; // Max 3 Zones in LD2450
|
||||
|
||||
enum Direction : uint8_t {
|
||||
DIRECTION_APPROACHING = 0,
|
||||
|
||||
@@ -11,10 +11,10 @@ void loop();
|
||||
|
||||
namespace esphome {
|
||||
|
||||
void IRAM_ATTR HOT yield() { ::yield(); }
|
||||
void HOT yield() { ::yield(); }
|
||||
uint32_t IRAM_ATTR HOT millis() { return ::millis(); }
|
||||
uint32_t IRAM_ATTR HOT micros() { return ::micros(); }
|
||||
void IRAM_ATTR HOT delay(uint32_t ms) { ::delay(ms); }
|
||||
void HOT delay(uint32_t ms) { ::delay(ms); }
|
||||
void IRAM_ATTR HOT delayMicroseconds(uint32_t us) { ::delayMicroseconds(us); }
|
||||
|
||||
void arch_init() {
|
||||
@@ -30,7 +30,7 @@ void arch_restart() {
|
||||
while (1) {
|
||||
}
|
||||
}
|
||||
void IRAM_ATTR HOT arch_feed_wdt() { lt_wdt_feed(); }
|
||||
void HOT arch_feed_wdt() { lt_wdt_feed(); }
|
||||
uint32_t arch_get_cpu_cycle_count() { return lt_cpu_get_cycle_count(); }
|
||||
uint32_t arch_get_cpu_freq_hz() { return lt_cpu_get_freq(); }
|
||||
uint8_t progmem_read_byte(const uint8_t *addr) { return *addr; }
|
||||
|
||||
@@ -101,6 +101,8 @@ CONF_INITIAL_LEVEL = "initial_level"
|
||||
CONF_LOGGER_ID = "logger_id"
|
||||
CONF_RUNTIME_TAG_LEVELS = "runtime_tag_levels"
|
||||
CONF_TASK_LOG_BUFFER_SIZE = "task_log_buffer_size"
|
||||
CONF_WAIT_FOR_CDC = "wait_for_cdc"
|
||||
CONF_EARLY_MESSAGE = "early_message"
|
||||
|
||||
UART_SELECTION_ESP32 = {
|
||||
VARIANT_ESP32: [UART0, UART1, UART2],
|
||||
@@ -208,6 +210,12 @@ def validate_initial_no_higher_than_global(config):
|
||||
return config
|
||||
|
||||
|
||||
def validate_wait_for_cdc(config):
|
||||
if config.get(CONF_WAIT_FOR_CDC) and config.get(CONF_HARDWARE_UART) != USB_CDC:
|
||||
raise cv.Invalid("wait_for_cdc requires hardware_uart: USB_CDC")
|
||||
return config
|
||||
|
||||
|
||||
Logger = logger_ns.class_("Logger", cg.Component)
|
||||
LoggerMessageTrigger = logger_ns.class_(
|
||||
"LoggerMessageTrigger",
|
||||
@@ -300,10 +308,18 @@ CONFIG_SCHEMA = cv.All(
|
||||
cv.SplitDefault(
|
||||
CONF_ESP8266_STORE_LOG_STRINGS_IN_FLASH, esp8266=True
|
||||
): cv.All(cv.only_on_esp8266, cv.boolean),
|
||||
cv.SplitDefault(CONF_WAIT_FOR_CDC, nrf52=False): cv.All(
|
||||
cv.only_on(PLATFORM_NRF52),
|
||||
cv.boolean,
|
||||
),
|
||||
cv.SplitDefault(CONF_EARLY_MESSAGE, nrf52=False): cv.All(
|
||||
cv.only_on(PLATFORM_NRF52), cv.boolean
|
||||
),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA),
|
||||
validate_local_no_higher_than_global,
|
||||
validate_initial_no_higher_than_global,
|
||||
validate_wait_for_cdc,
|
||||
)
|
||||
|
||||
|
||||
@@ -425,13 +441,21 @@ async def to_code(config):
|
||||
except cv.Invalid:
|
||||
pass
|
||||
|
||||
if config.get(CONF_WAIT_FOR_CDC):
|
||||
cg.add_define("USE_LOGGER_WAIT_FOR_CDC")
|
||||
if config.get(CONF_EARLY_MESSAGE):
|
||||
cg.add_define("USE_LOGGER_EARLY_MESSAGE")
|
||||
|
||||
if CORE.is_nrf52:
|
||||
# esphome implement own fatal error handler which save PC/LR before reset
|
||||
zephyr_add_prj_conf("RESET_ON_FATAL_ERROR", False)
|
||||
zephyr_add_prj_conf("THREAD_LOCAL_STORAGE", True)
|
||||
if config[CONF_HARDWARE_UART] == UART0:
|
||||
zephyr_add_overlay("""&uart0 { status = "okay";};""")
|
||||
if config[CONF_HARDWARE_UART] == UART1:
|
||||
zephyr_add_overlay("""&uart1 { status = "okay";};""")
|
||||
if config[CONF_HARDWARE_UART] == USB_CDC:
|
||||
cg.add_define("USE_LOGGER_UART_SELECTION_USB_CDC")
|
||||
zephyr_add_prj_conf("UART_LINE_CTRL", True)
|
||||
zephyr_add_cdc_acm(config, 0)
|
||||
|
||||
|
||||
@@ -170,19 +170,19 @@ void Logger::init_log_buffer(size_t total_buffer_size) {
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-owning-memory) - allocated once, never freed
|
||||
this->log_buffer_ = new logger::TaskLogBuffer(total_buffer_size);
|
||||
|
||||
// Zephyr needs loop working to check when CDC port is open
|
||||
#if !(defined(USE_ZEPHYR) || defined(USE_LOGGER_USB_CDC))
|
||||
// Start with loop disabled when using task buffer (unless using USB CDC on ESP32)
|
||||
#if !(defined(USE_ZEPHYR) && defined(USE_LOGGER_UART_SELECTION_USB_CDC))
|
||||
// Start with loop disabled when using task buffer
|
||||
// The loop will be enabled automatically when messages arrive
|
||||
// Zephyr with USB CDC needs loop active to poll port readiness via cdc_loop_()
|
||||
this->disable_loop_when_buffer_empty_();
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(USE_ESPHOME_TASK_LOG_BUFFER) || (defined(USE_ZEPHYR) && defined(USE_LOGGER_USB_CDC))
|
||||
#if defined(USE_ESPHOME_TASK_LOG_BUFFER) || (defined(USE_ZEPHYR) && defined(USE_LOGGER_UART_SELECTION_USB_CDC))
|
||||
void Logger::loop() {
|
||||
this->process_messages_();
|
||||
#if defined(USE_ZEPHYR) && defined(USE_LOGGER_USB_CDC)
|
||||
#if defined(USE_ZEPHYR) && defined(USE_LOGGER_UART_SELECTION_USB_CDC)
|
||||
this->cdc_loop_();
|
||||
#endif
|
||||
}
|
||||
@@ -204,8 +204,7 @@ void Logger::process_messages_() {
|
||||
this->write_log_buffer_to_console_(buf);
|
||||
}
|
||||
}
|
||||
// Zephyr needs loop working to check when CDC port is open
|
||||
#if !(defined(USE_ZEPHYR) || defined(USE_LOGGER_USB_CDC))
|
||||
#if !(defined(USE_ZEPHYR) && defined(USE_LOGGER_UART_SELECTION_USB_CDC))
|
||||
else {
|
||||
// No messages to process, disable loop if appropriate
|
||||
// This reduces overhead when there's no async logging activity
|
||||
@@ -261,6 +260,9 @@ void Logger::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, " Level for '%s': %s", it.first, LOG_STR_ARG(get_log_level_str(it.second)));
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_ZEPHYR
|
||||
dump_crash_();
|
||||
#endif
|
||||
}
|
||||
|
||||
void Logger::set_log_level(uint8_t level) {
|
||||
|
||||
@@ -40,26 +40,25 @@ struct device;
|
||||
|
||||
namespace esphome::logger {
|
||||
|
||||
/** Interface for receiving log messages without std::function overhead.
|
||||
/** Lightweight callback for receiving log messages without virtual dispatch overhead.
|
||||
*
|
||||
* Components can implement this interface instead of using lambdas with std::function
|
||||
* to reduce flash usage from std::function type erasure machinery.
|
||||
* Replaces the former LogListener virtual interface to eliminate per-implementer
|
||||
* vtable sub-tables and thunk code (~39 bytes saved per class that used LogListener).
|
||||
*
|
||||
* Usage:
|
||||
* class MyComponent : public Component, public LogListener {
|
||||
* public:
|
||||
* void setup() override {
|
||||
* if (logger::global_logger != nullptr)
|
||||
* logger::global_logger->add_log_listener(this);
|
||||
* }
|
||||
* void on_log(uint8_t level, const char *tag, const char *message, size_t message_len) override {
|
||||
* // Handle log message
|
||||
* }
|
||||
* };
|
||||
* // In your component's setup():
|
||||
* if (logger::global_logger != nullptr)
|
||||
* logger::global_logger->add_log_callback(
|
||||
* this, [](void *self, uint8_t level, const char *tag, const char *message, size_t message_len) {
|
||||
* static_cast<MyComponent *>(self)->on_log(level, tag, message, message_len);
|
||||
* });
|
||||
*/
|
||||
class LogListener {
|
||||
public:
|
||||
virtual void on_log(uint8_t level, const char *tag, const char *message, size_t message_len) = 0;
|
||||
struct LogCallback {
|
||||
void *instance;
|
||||
void (*fn)(void *, uint8_t, const char *, const char *, size_t);
|
||||
void invoke(uint8_t level, const char *tag, const char *message, size_t message_len) const {
|
||||
this->fn(this->instance, level, tag, message, message_len);
|
||||
}
|
||||
};
|
||||
|
||||
#ifdef USE_LOGGER_LEVEL_LISTENERS
|
||||
@@ -148,7 +147,7 @@ class Logger : public Component {
|
||||
#ifdef USE_ESPHOME_TASK_LOG_BUFFER
|
||||
void init_log_buffer(size_t total_buffer_size);
|
||||
#endif
|
||||
#if defined(USE_ESPHOME_TASK_LOG_BUFFER) || (defined(USE_ZEPHYR) && defined(USE_LOGGER_USB_CDC))
|
||||
#if defined(USE_ESPHOME_TASK_LOG_BUFFER) || (defined(USE_ZEPHYR) && defined(USE_LOGGER_UART_SELECTION_USB_CDC))
|
||||
void loop() override;
|
||||
#endif
|
||||
/// Manually set the baud rate for serial, set to 0 to disable.
|
||||
@@ -187,11 +186,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); }
|
||||
/// Register a log callback to receive log messages
|
||||
void add_log_callback(void *instance, void (*fn)(void *, uint8_t, const char *, const char *, size_t)) {
|
||||
this->log_callbacks_.push_back(LogCallback{instance, fn});
|
||||
}
|
||||
#else
|
||||
/// No-op when log listeners are disabled
|
||||
void add_log_listener(LogListener *listener) {}
|
||||
void add_log_callback(void *instance, void (*fn)(void *, uint8_t, const char *, const char *, size_t)) {}
|
||||
#endif
|
||||
|
||||
#ifdef USE_LOGGER_LEVEL_LISTENERS
|
||||
@@ -228,7 +229,7 @@ class Logger : public Component {
|
||||
void log_vprintf_non_main_thread_(uint8_t level, const char *tag, int line, const char *format, va_list args,
|
||||
const char *thread_name);
|
||||
#endif
|
||||
#if defined(USE_ZEPHYR) && defined(USE_LOGGER_USB_CDC)
|
||||
#if defined(USE_ZEPHYR) && defined(USE_LOGGER_UART_SELECTION_USB_CDC)
|
||||
void cdc_loop_();
|
||||
#endif
|
||||
void process_messages_();
|
||||
@@ -253,11 +254,11 @@ class Logger : public Component {
|
||||
}
|
||||
#endif
|
||||
|
||||
// Helper to notify log listeners
|
||||
// Helper to notify log callbacks
|
||||
inline void HOT notify_listeners_(uint8_t level, const char *tag, const LogBuffer &buf) {
|
||||
#ifdef USE_LOG_LISTENERS
|
||||
for (auto *listener : this->log_listeners_)
|
||||
listener->on_log(level, tag, buf.data, buf.pos);
|
||||
for (auto &cb : this->log_callbacks_)
|
||||
cb.invoke(level, tag, buf.data, buf.pos);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -316,6 +317,7 @@ class Logger : public Component {
|
||||
Stream *hw_serial_{nullptr};
|
||||
#endif
|
||||
#if defined(USE_ZEPHYR)
|
||||
void dump_crash_();
|
||||
const device *uart_dev_{nullptr};
|
||||
#endif
|
||||
#if defined(USE_ESP32) || defined(USE_LIBRETINY) || defined(USE_ZEPHYR)
|
||||
@@ -341,8 +343,8 @@ class Logger : public Component {
|
||||
std::map<const char *, uint8_t, CStrCompare> log_levels_{};
|
||||
#endif
|
||||
#ifdef USE_LOG_LISTENERS
|
||||
StaticVector<LogListener *, ESPHOME_LOG_MAX_LISTENERS>
|
||||
log_listeners_; // Log message listeners (API, MQTT, syslog, etc.)
|
||||
StaticVector<LogCallback, ESPHOME_LOG_MAX_LISTENERS>
|
||||
log_callbacks_; // Log message callbacks (API, MQTT, syslog, etc.)
|
||||
#endif
|
||||
#ifdef USE_LOGGER_LEVEL_LISTENERS
|
||||
std::vector<LoggerLevelListener *> level_listeners_; // Log level change listeners
|
||||
@@ -463,9 +465,9 @@ class Logger : public Component {
|
||||
inline RecursionGuard make_non_main_task_guard_() { return RecursionGuard(non_main_task_recursion_guard_); }
|
||||
#endif
|
||||
|
||||
// Zephyr needs loop working to check when CDC port is open
|
||||
#if defined(USE_ESPHOME_TASK_LOG_BUFFER) && !(defined(USE_ZEPHYR) || defined(USE_LOGGER_USB_CDC))
|
||||
// Disable loop when task buffer is empty (with USB CDC check on ESP32)
|
||||
#if defined(USE_ESPHOME_TASK_LOG_BUFFER) && !(defined(USE_ZEPHYR) && defined(USE_LOGGER_UART_SELECTION_USB_CDC))
|
||||
// Disable loop when task buffer is empty
|
||||
// Zephyr with USB CDC needs loop active to poll port readiness via cdc_loop_()
|
||||
inline void disable_loop_when_buffer_empty_() {
|
||||
// Thread safety note: This is safe even if another task calls enable_loop_soon_any_context()
|
||||
// concurrently. If that happens between our check and disable_loop(), the enable request
|
||||
@@ -478,15 +480,16 @@ class Logger : public Component {
|
||||
};
|
||||
extern Logger *global_logger; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
||||
class LoggerMessageTrigger : public Trigger<uint8_t, const char *, const char *>, public LogListener {
|
||||
class LoggerMessageTrigger : public Trigger<uint8_t, const char *, const char *> {
|
||||
public:
|
||||
explicit LoggerMessageTrigger(Logger *parent, uint8_t level) : level_(level) { parent->add_log_listener(this); }
|
||||
|
||||
void on_log(uint8_t level, const char *tag, const char *message, size_t message_len) override {
|
||||
(void) message_len;
|
||||
if (level <= this->level_) {
|
||||
this->trigger(level, tag, message);
|
||||
}
|
||||
explicit LoggerMessageTrigger(Logger *parent, uint8_t level) : level_(level) {
|
||||
parent->add_log_callback(this,
|
||||
[](void *self, uint8_t level, const char *tag, const char *message, size_t message_len) {
|
||||
auto *trigger = static_cast<LoggerMessageTrigger *>(self);
|
||||
if (level <= trigger->level_) {
|
||||
trigger->trigger(level, tag, message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
@@ -77,9 +77,10 @@ void init_uart(uart_port_t uart_num, uint32_t baud_rate, int tx_buffer_size) {
|
||||
uart_config.flow_ctrl = UART_HW_FLOWCTRL_DISABLE;
|
||||
uart_config.source_clk = UART_SCLK_DEFAULT;
|
||||
uart_param_config(uart_num, &uart_config);
|
||||
const int uart_buffer_size = tx_buffer_size;
|
||||
// Install UART driver using an event queue here
|
||||
uart_driver_install(uart_num, uart_buffer_size, uart_buffer_size, 10, nullptr, 0);
|
||||
// The logger only writes to UART, never reads, so use the minimum RX buffer.
|
||||
// ESP-IDF requires rx_buffer_size > UART_HW_FIFO_LEN (128 bytes).
|
||||
const int min_rx_buffer_size = UART_HW_FIFO_LEN(uart_num) + 1;
|
||||
uart_driver_install(uart_num, min_rx_buffer_size, tx_buffer_size, 0, nullptr, 0);
|
||||
}
|
||||
|
||||
void Logger::pre_setup() {
|
||||
|
||||
@@ -8,12 +8,33 @@
|
||||
#include <zephyr/drivers/uart.h>
|
||||
#include <zephyr/sys/printk.h>
|
||||
#include <zephyr/usb/usb_device.h>
|
||||
#ifdef USE_LOGGER_EARLY_MESSAGE
|
||||
#include <esphome/components/zephyr/reset_reason.h>
|
||||
#endif
|
||||
|
||||
namespace esphome::zephyr_coredump {
|
||||
|
||||
__attribute__((weak)) void print_coredump() {}
|
||||
|
||||
} // namespace esphome::zephyr_coredump
|
||||
|
||||
namespace esphome::logger {
|
||||
|
||||
static const uint32_t CRASH_MAGIC = 0xDEADBEEF;
|
||||
|
||||
__attribute__((section(".noinit"))) struct {
|
||||
uint32_t magic;
|
||||
uint32_t reason;
|
||||
uint32_t pc;
|
||||
uint32_t lr;
|
||||
#if defined(CONFIG_THREAD_NAME)
|
||||
char thread[CONFIG_THREAD_MAX_NAME_LEN];
|
||||
#endif
|
||||
} crash_buf; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
||||
static const char *const TAG = "logger";
|
||||
|
||||
#ifdef USE_LOGGER_USB_CDC
|
||||
#ifdef USE_LOGGER_UART_SELECTION_USB_CDC
|
||||
void Logger::cdc_loop_() {
|
||||
if (this->uart_ != UART_SELECTION_USB_CDC || this->uart_dev_ == nullptr) {
|
||||
return;
|
||||
@@ -57,10 +78,26 @@ void Logger::pre_setup() {
|
||||
ESP_LOGE(TAG, "%s is not ready.", LOG_STR_ARG(get_uart_selection_()));
|
||||
} else {
|
||||
this->uart_dev_ = uart_dev;
|
||||
#if defined(USE_LOGGER_WAIT_FOR_CDC) && defined(USE_LOGGER_UART_SELECTION_USB_CDC)
|
||||
uint32_t dtr = 0;
|
||||
uint32_t count = (10 * 100); // wait 10 sec for USB CDC to have early logs
|
||||
while (dtr == 0 && count-- != 0) {
|
||||
uart_line_ctrl_get(this->uart_dev_, UART_LINE_CTRL_DTR, &dtr);
|
||||
delay(10);
|
||||
arch_feed_wdt();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
global_logger = this;
|
||||
ESP_LOGI(TAG, "Log initialized");
|
||||
#ifdef USE_LOGGER_EARLY_MESSAGE
|
||||
char reason_buffer[zephyr::RESET_REASON_BUFFER_SIZE];
|
||||
const char *reset_reason = zephyr::get_reset_reason(std::span<char, zephyr::RESET_REASON_BUFFER_SIZE>(reason_buffer));
|
||||
ESP_LOGI(TAG, "Reset reason: %s", reset_reason);
|
||||
dump_crash_();
|
||||
zephyr_coredump::print_coredump();
|
||||
#endif
|
||||
}
|
||||
|
||||
void HOT Logger::write_msg_(const char *msg, uint16_t len) {
|
||||
@@ -93,6 +130,66 @@ const LogString *Logger::get_uart_selection_() {
|
||||
}
|
||||
}
|
||||
|
||||
static const uint8_t REASON_BUF_SIZE = 32;
|
||||
|
||||
static const char *reason_to_str(unsigned int reason, char *buf) {
|
||||
switch (reason) {
|
||||
case K_ERR_CPU_EXCEPTION:
|
||||
return "CPU exception";
|
||||
case K_ERR_SPURIOUS_IRQ:
|
||||
return "Unhandled interrupt";
|
||||
case K_ERR_STACK_CHK_FAIL:
|
||||
return "Stack overflow";
|
||||
case K_ERR_KERNEL_OOPS:
|
||||
return "Kernel oops";
|
||||
case K_ERR_KERNEL_PANIC:
|
||||
return "Kernel panic";
|
||||
default:
|
||||
snprintf(buf, REASON_BUF_SIZE, "Unknown error (%u)", reason);
|
||||
return buf;
|
||||
}
|
||||
}
|
||||
|
||||
void Logger::dump_crash_() {
|
||||
ESP_LOGD(TAG, "Crash buffer address %p", &crash_buf);
|
||||
if (crash_buf.magic == CRASH_MAGIC) {
|
||||
char reason_buf[REASON_BUF_SIZE];
|
||||
ESP_LOGE(TAG, "Last crash:");
|
||||
ESP_LOGE(TAG, "Reason=%s PC=0x%08x LR=0x%08x", reason_to_str(crash_buf.reason, reason_buf), crash_buf.pc,
|
||||
crash_buf.lr);
|
||||
#if defined(CONFIG_THREAD_NAME)
|
||||
ESP_LOGE(TAG, "Thread: %s", crash_buf.thread);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void k_sys_fatal_error_handler(unsigned int reason, const z_arch_esf_t *esf) {
|
||||
crash_buf.magic = CRASH_MAGIC;
|
||||
crash_buf.reason = reason;
|
||||
if (esf) {
|
||||
crash_buf.pc = esf->basic.pc;
|
||||
crash_buf.lr = esf->basic.lr;
|
||||
}
|
||||
#if defined(CONFIG_THREAD_NAME)
|
||||
auto thread = k_current_get();
|
||||
const char *name = k_thread_name_get(thread);
|
||||
if (name) {
|
||||
strncpy(crash_buf.thread, name, sizeof(crash_buf.thread) - 1);
|
||||
crash_buf.thread[sizeof(crash_buf.thread) - 1] = '\0';
|
||||
} else {
|
||||
crash_buf.thread[0] = '\0';
|
||||
}
|
||||
#endif
|
||||
arch_restart();
|
||||
}
|
||||
|
||||
} // namespace esphome::logger
|
||||
|
||||
extern "C" {
|
||||
|
||||
void k_sys_fatal_error_handler(unsigned int reason, const z_arch_esf_t *esf) {
|
||||
esphome::logger::k_sys_fatal_error_handler(reason, esf);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user