mirror of
https://github.com/esphome/esphome.git
synced 2026-01-19 01:26:24 -07:00
Compare commits
51 Commits
web_server
...
action_cal
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b22a8b00bf | ||
|
|
297f05d600 | ||
|
|
54fc10714d | ||
|
|
889886909b | ||
|
|
655e2b43cb | ||
|
|
81e639a6ba | ||
|
|
f9ffd134df | ||
|
|
c50bf45496 | ||
|
|
9f9341a700 | ||
|
|
71d532a349 | ||
|
|
61a89a97d7 | ||
|
|
0c3433d056 | ||
|
|
7e1cda8f9f | ||
|
|
7f0e4eaa84 | ||
|
|
8cccfa5369 | ||
|
|
7ea6bcef88 | ||
|
|
353daa97d0 | ||
|
|
6c68ebe86e | ||
|
|
29cef3bc5d | ||
|
|
83eebdf15d | ||
|
|
595217786c | ||
|
|
912f94d1e8 | ||
|
|
ea8ae2ae60 | ||
|
|
e1aac7601d | ||
|
|
f1b11b1855 | ||
|
|
23f9f70b71 | ||
|
|
eeeae53f76 | ||
|
|
45c0796e40 | ||
|
|
38e2e4a56d | ||
|
|
52132ea3bc | ||
|
|
ace3ff2170 | ||
|
|
26e90b4ca6 | ||
|
|
684790c2ab | ||
|
|
6a3737bac3 | ||
|
|
723ca57617 | ||
|
|
909bd1074a | ||
|
|
68064dc974 | ||
|
|
742d724e65 | ||
|
|
5ae46a4369 | ||
|
|
a1395af763 | ||
|
|
6222fae907 | ||
|
|
e34532f283 | ||
|
|
f2eb61a767 | ||
|
|
5725a4840e | ||
|
|
de82f96ccb | ||
|
|
6c981d8b71 | ||
|
|
c03faf2d9a | ||
|
|
da7680f7d9 | ||
|
|
cea2878b55 | ||
|
|
e0ff7fdaa1 | ||
|
|
3c9b300c46 |
@@ -293,6 +293,12 @@ This document provides essential context for AI models interacting with this pro
|
||||
* **Configuration Design:** Aim for simplicity with sensible defaults, while allowing for advanced customization.
|
||||
* **Embedded Systems Optimization:** ESPHome targets resource-constrained microcontrollers. Be mindful of flash size and RAM usage.
|
||||
|
||||
**Why Heap Allocation Matters:**
|
||||
|
||||
ESP devices run for months with small heaps shared between Wi-Fi, BLE, LWIP, and application code. Over time, repeated allocations of different sizes fragment the heap. Failures happen when the largest contiguous block shrinks, even if total free heap is still large. We have seen field crashes caused by this.
|
||||
|
||||
**Heap allocation after `setup()` should be avoided unless absolutely unavoidable.** Every allocation/deallocation cycle contributes to fragmentation. ESPHome treats runtime heap allocation as a long-term reliability bug, not a performance issue. Helpers that hide allocation (`std::string`, `std::to_string`, string-returning helpers) are being deprecated and replaced with buffer and view based APIs.
|
||||
|
||||
**STL Container Guidelines:**
|
||||
|
||||
ESPHome runs on embedded systems with limited resources. Choose containers carefully:
|
||||
@@ -322,15 +328,15 @@ This document provides essential context for AI models interacting with this pro
|
||||
std::array<uint8_t, 256> buffer;
|
||||
```
|
||||
|
||||
2. **Compile-time-known fixed sizes with vector-like API:** Use `StaticVector` from `esphome/core/helpers.h` for fixed-size stack allocation with `push_back()` interface.
|
||||
2. **Compile-time-known fixed sizes with vector-like API:** Use `StaticVector` from `esphome/core/helpers.h` for compile-time fixed size with `push_back()` interface (no dynamic allocation).
|
||||
```cpp
|
||||
// Bad - generates STL realloc code (_M_realloc_insert)
|
||||
std::vector<ServiceRecord> services;
|
||||
services.reserve(5); // Still includes reallocation machinery
|
||||
|
||||
// Good - compile-time fixed size, stack allocated, no reallocation machinery
|
||||
StaticVector<ServiceRecord, MAX_SERVICES> services; // Allocates all MAX_SERVICES on stack
|
||||
services.push_back(record1); // Tracks count but all slots allocated
|
||||
// Good - compile-time fixed size, no dynamic allocation
|
||||
StaticVector<ServiceRecord, MAX_SERVICES> services;
|
||||
services.push_back(record1);
|
||||
```
|
||||
Use `cg.add_define("MAX_SERVICES", count)` to set the size from Python configuration.
|
||||
Like `std::array` but with vector-like API (`push_back()`, `size()`) and no STL reallocation code.
|
||||
@@ -372,22 +378,21 @@ This document provides essential context for AI models interacting with this pro
|
||||
```
|
||||
Linear search on small datasets (1-16 elements) is often faster than hashing/tree overhead, but this depends on lookup frequency and access patterns. For frequent lookups in hot code paths, the O(1) vs O(n) complexity difference may still matter even for small datasets. `std::vector` with simple structs is usually fine—it's the heavy containers (`map`, `set`, `unordered_map`) that should be avoided for small datasets unless profiling shows otherwise.
|
||||
|
||||
5. **Detection:** Look for these patterns in compiler output:
|
||||
5. **Avoid `std::deque`:** It allocates in 512-byte blocks regardless of element size, guaranteeing at least 512 bytes of RAM usage immediately. This is a major source of crashes on memory-constrained devices.
|
||||
|
||||
6. **Detection:** Look for these patterns in compiler output:
|
||||
- Large code sections with STL symbols (vector, map, set)
|
||||
- `alloc`, `realloc`, `dealloc` in symbol names
|
||||
- `_M_realloc_insert`, `_M_default_append` (vector reallocation)
|
||||
- Red-black tree code (`rb_tree`, `_Rb_tree`)
|
||||
- Hash table infrastructure (`unordered_map`, `hash`)
|
||||
|
||||
**When to optimize:**
|
||||
**Prioritize optimization effort for:**
|
||||
- Core components (API, network, logger)
|
||||
- Widely-used components (mdns, wifi, ble)
|
||||
- Components causing flash size complaints
|
||||
|
||||
**When not to optimize:**
|
||||
- Single-use niche components
|
||||
- Code where readability matters more than bytes
|
||||
- Already using appropriate containers
|
||||
Note: Avoiding heap allocation after `setup()` is always required regardless of component type. The prioritization above is about the effort spent on container optimization (e.g., migrating from `std::vector` to `StaticVector`).
|
||||
|
||||
* **State Management:** Use `CORE.data` for component state that needs to persist during configuration generation. Avoid module-level mutable globals.
|
||||
|
||||
|
||||
@@ -249,6 +249,7 @@ esphome/components/ina260/* @mreditor97
|
||||
esphome/components/ina2xx_base/* @latonita
|
||||
esphome/components/ina2xx_i2c/* @latonita
|
||||
esphome/components/ina2xx_spi/* @latonita
|
||||
esphome/components/infrared/* @kbx81
|
||||
esphome/components/inkbird_ibsth1_mini/* @fkirill
|
||||
esphome/components/inkplate/* @jesserockz @JosipKuci
|
||||
esphome/components/integration/* @OttoWinter
|
||||
|
||||
@@ -66,6 +66,8 @@ service APIConnection {
|
||||
|
||||
rpc zwave_proxy_frame(ZWaveProxyFrame) returns (void) {}
|
||||
rpc zwave_proxy_request(ZWaveProxyRequest) returns (void) {}
|
||||
|
||||
rpc infrared_rf_transmit_raw_timings(InfraredRFTransmitRawTimingsRequest) returns (void) {}
|
||||
}
|
||||
|
||||
|
||||
@@ -763,7 +765,7 @@ message SubscribeHomeassistantServicesRequest {
|
||||
|
||||
message HomeassistantServiceMap {
|
||||
string key = 1;
|
||||
string value = 2 [(no_zero_copy) = true];
|
||||
string value = 2;
|
||||
}
|
||||
|
||||
message HomeassistantActionRequest {
|
||||
@@ -779,7 +781,7 @@ message HomeassistantActionRequest {
|
||||
bool is_event = 5;
|
||||
uint32 call_id = 6 [(field_ifdef) = "USE_API_HOMEASSISTANT_ACTION_RESPONSES"];
|
||||
bool wants_response = 7 [(field_ifdef) = "USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON"];
|
||||
string response_template = 8 [(no_zero_copy) = true, (field_ifdef) = "USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON"];
|
||||
string response_template = 8 [(field_ifdef) = "USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON"];
|
||||
}
|
||||
|
||||
// Message sent by Home Assistant to ESPHome with service call response data
|
||||
@@ -2437,3 +2439,49 @@ message ZWaveProxyRequest {
|
||||
ZWaveProxyRequestType type = 1;
|
||||
bytes data = 2;
|
||||
}
|
||||
|
||||
// ==================== INFRARED ====================
|
||||
// Note: Feature and capability flag enums are defined in
|
||||
// esphome/components/infrared/infrared.h
|
||||
|
||||
// Listing of infrared instances
|
||||
message ListEntitiesInfraredResponse {
|
||||
option (id) = 135;
|
||||
option (base_class) = "InfoResponseProtoMessage";
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_INFRARED";
|
||||
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
string name = 3;
|
||||
string icon = 4 [(field_ifdef) = "USE_ENTITY_ICON"];
|
||||
bool disabled_by_default = 5;
|
||||
EntityCategory entity_category = 6;
|
||||
uint32 device_id = 7 [(field_ifdef) = "USE_DEVICES"];
|
||||
uint32 capabilities = 8; // Bitfield of InfraredCapabilityFlags
|
||||
}
|
||||
|
||||
// Command to transmit infrared/RF data using raw timings
|
||||
message InfraredRFTransmitRawTimingsRequest {
|
||||
option (id) = 136;
|
||||
option (source) = SOURCE_CLIENT;
|
||||
option (ifdef) = "USE_IR_RF";
|
||||
|
||||
uint32 device_id = 1 [(field_ifdef) = "USE_DEVICES"];
|
||||
fixed32 key = 2; // Key identifying the transmitter instance
|
||||
uint32 carrier_frequency = 3; // Carrier frequency in Hz
|
||||
uint32 repeat_count = 4; // Number of times to transmit (1 = once, 2 = twice, etc.)
|
||||
repeated sint32 timings = 5 [packed = true, (packed_buffer) = true]; // Raw timings in microseconds (zigzag-encoded): positive = mark (LED/TX on), negative = space (LED/TX off)
|
||||
}
|
||||
|
||||
// Event message for received infrared/RF data
|
||||
message InfraredRFReceiveEvent {
|
||||
option (id) = 137;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_IR_RF";
|
||||
option (no_delay) = true;
|
||||
|
||||
uint32 device_id = 1 [(field_ifdef) = "USE_DEVICES"];
|
||||
fixed32 key = 2; // Key identifying the receiver instance
|
||||
repeated sint32 timings = 3 [packed = true, (container_pointer_no_template) = "std::vector<int32_t>"]; // Raw timings in microseconds (zigzag-encoded): alternating mark/space periods
|
||||
}
|
||||
|
||||
@@ -46,6 +46,9 @@
|
||||
#ifdef USE_WATER_HEATER
|
||||
#include "esphome/components/water_heater/water_heater.h"
|
||||
#endif
|
||||
#ifdef USE_INFRARED
|
||||
#include "esphome/components/infrared/infrared.h"
|
||||
#endif
|
||||
|
||||
namespace esphome::api {
|
||||
|
||||
@@ -443,7 +446,7 @@ uint16_t APIConnection::try_send_fan_state(EntityBase *entity, APIConnection *co
|
||||
if (traits.supports_direction())
|
||||
msg.direction = static_cast<enums::FanDirection>(fan->direction);
|
||||
if (traits.supports_preset_modes() && fan->has_preset_mode())
|
||||
msg.preset_mode = StringRef(fan->get_preset_mode());
|
||||
msg.preset_mode = fan->get_preset_mode();
|
||||
return fill_and_encode_entity_state(fan, msg, FanStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
|
||||
}
|
||||
uint16_t APIConnection::try_send_fan_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
|
||||
@@ -499,7 +502,7 @@ uint16_t APIConnection::try_send_light_state(EntityBase *entity, APIConnection *
|
||||
resp.cold_white = values.get_cold_white();
|
||||
resp.warm_white = values.get_warm_white();
|
||||
if (light->supports_effects()) {
|
||||
resp.effect = light->get_effect_name_ref();
|
||||
resp.effect = light->get_effect_name();
|
||||
}
|
||||
return fill_and_encode_entity_state(light, resp, LightStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
|
||||
}
|
||||
@@ -522,7 +525,8 @@ uint16_t APIConnection::try_send_light_info(EntityBase *entity, APIConnection *c
|
||||
effects_list.init(light_effects.size() + 1);
|
||||
effects_list.push_back("None");
|
||||
for (auto *effect : light_effects) {
|
||||
effects_list.push_back(effect->get_name());
|
||||
// c_str() is safe as effect names are null-terminated strings from codegen
|
||||
effects_list.push_back(effect->get_name().c_str());
|
||||
}
|
||||
}
|
||||
msg.effects = &effects_list;
|
||||
@@ -675,13 +679,13 @@ uint16_t APIConnection::try_send_climate_state(EntityBase *entity, APIConnection
|
||||
if (traits.get_supports_fan_modes() && climate->fan_mode.has_value())
|
||||
resp.fan_mode = static_cast<enums::ClimateFanMode>(climate->fan_mode.value());
|
||||
if (!traits.get_supported_custom_fan_modes().empty() && climate->has_custom_fan_mode()) {
|
||||
resp.custom_fan_mode = StringRef(climate->get_custom_fan_mode());
|
||||
resp.custom_fan_mode = climate->get_custom_fan_mode();
|
||||
}
|
||||
if (traits.get_supports_presets() && climate->preset.has_value()) {
|
||||
resp.preset = static_cast<enums::ClimatePreset>(climate->preset.value());
|
||||
}
|
||||
if (!traits.get_supported_custom_presets().empty() && climate->has_custom_preset()) {
|
||||
resp.custom_preset = StringRef(climate->get_custom_preset());
|
||||
resp.custom_preset = climate->get_custom_preset();
|
||||
}
|
||||
if (traits.get_supports_swing_modes())
|
||||
resp.swing_mode = static_cast<enums::ClimateSwingMode>(climate->swing_mode);
|
||||
@@ -914,7 +918,7 @@ uint16_t APIConnection::try_send_select_state(EntityBase *entity, APIConnection
|
||||
bool is_single) {
|
||||
auto *select = static_cast<select::Select *>(entity);
|
||||
SelectStateResponse resp;
|
||||
resp.state = StringRef(select->current_option());
|
||||
resp.state = select->current_option();
|
||||
resp.missing_state = !select->has_state();
|
||||
return fill_and_encode_entity_state(select, resp, SelectStateResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
|
||||
}
|
||||
@@ -1414,14 +1418,15 @@ void APIConnection::on_water_heater_command_request(const WaterHeaterCommandRequ
|
||||
#endif
|
||||
|
||||
#ifdef USE_EVENT
|
||||
void APIConnection::send_event(event::Event *event, const char *event_type) {
|
||||
this->send_message_smart_(event, MessageCreator(event_type), EventResponse::MESSAGE_TYPE,
|
||||
void APIConnection::send_event(event::Event *event, StringRef event_type) {
|
||||
// get_last_event_type() returns StringRef pointing to null-terminated string literals from codegen
|
||||
this->send_message_smart_(event, MessageCreator(event_type.c_str()), EventResponse::MESSAGE_TYPE,
|
||||
EventResponse::ESTIMATED_SIZE);
|
||||
}
|
||||
uint16_t APIConnection::try_send_event_response(event::Event *event, const char *event_type, APIConnection *conn,
|
||||
uint16_t APIConnection::try_send_event_response(event::Event *event, StringRef event_type, APIConnection *conn,
|
||||
uint32_t remaining_size, bool is_single) {
|
||||
EventResponse resp;
|
||||
resp.event_type = StringRef(event_type);
|
||||
resp.event_type = event_type;
|
||||
return fill_and_encode_entity_state(event, resp, EventResponse::MESSAGE_TYPE, conn, remaining_size, is_single);
|
||||
}
|
||||
|
||||
@@ -1436,6 +1441,35 @@ uint16_t APIConnection::try_send_event_info(EntityBase *entity, APIConnection *c
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_IR_RF
|
||||
void APIConnection::infrared_rf_transmit_raw_timings(const InfraredRFTransmitRawTimingsRequest &msg) {
|
||||
// TODO: When RF is implemented, add a field to the message to distinguish IR vs RF
|
||||
// and dispatch to the appropriate entity type based on that field.
|
||||
#ifdef USE_INFRARED
|
||||
ENTITY_COMMAND_MAKE_CALL(infrared::Infrared, infrared, infrared)
|
||||
call.set_carrier_frequency(msg.carrier_frequency);
|
||||
call.set_raw_timings_packed(msg.timings_data_, msg.timings_length_, msg.timings_count_);
|
||||
call.set_repeat_count(msg.repeat_count);
|
||||
call.perform();
|
||||
#endif
|
||||
}
|
||||
|
||||
void APIConnection::send_infrared_rf_receive_event(const InfraredRFReceiveEvent &msg) {
|
||||
this->send_message(msg, InfraredRFReceiveEvent::MESSAGE_TYPE);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_INFRARED
|
||||
uint16_t APIConnection::try_send_infrared_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
|
||||
bool is_single) {
|
||||
auto *infrared = static_cast<infrared::Infrared *>(entity);
|
||||
ListEntitiesInfraredResponse msg;
|
||||
msg.capabilities = infrared->get_capability_flags();
|
||||
return fill_and_encode_entity_info(infrared, msg, ListEntitiesInfraredResponse::MESSAGE_TYPE, conn, remaining_size,
|
||||
is_single);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_UPDATE
|
||||
bool APIConnection::send_update_state(update::UpdateEntity *update) {
|
||||
return this->send_message_smart_(update, &APIConnection::try_send_update_state, UpdateStateResponse::MESSAGE_TYPE,
|
||||
@@ -2055,7 +2089,7 @@ uint16_t APIConnection::MessageCreator::operator()(EntityBase *entity, APIConnec
|
||||
// Special case: EventResponse uses const char * pointer
|
||||
if (message_type == EventResponse::MESSAGE_TYPE) {
|
||||
auto *e = static_cast<event::Event *>(entity);
|
||||
return APIConnection::try_send_event_response(e, data_.const_char_ptr, conn, remaining_size, is_single);
|
||||
return APIConnection::try_send_event_response(e, StringRef(data_.const_char_ptr), conn, remaining_size, is_single);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
@@ -172,8 +172,13 @@ class APIConnection final : public APIServerConnection {
|
||||
void on_water_heater_command_request(const WaterHeaterCommandRequest &msg) override;
|
||||
#endif
|
||||
|
||||
#ifdef USE_IR_RF
|
||||
void infrared_rf_transmit_raw_timings(const InfraredRFTransmitRawTimingsRequest &msg) override;
|
||||
void send_infrared_rf_receive_event(const InfraredRFReceiveEvent &msg);
|
||||
#endif
|
||||
|
||||
#ifdef USE_EVENT
|
||||
void send_event(event::Event *event, const char *event_type);
|
||||
void send_event(event::Event *event, StringRef event_type);
|
||||
#endif
|
||||
|
||||
#ifdef USE_UPDATE
|
||||
@@ -468,8 +473,12 @@ class APIConnection final : public APIServerConnection {
|
||||
static uint16_t try_send_water_heater_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
|
||||
bool is_single);
|
||||
#endif
|
||||
#ifdef USE_INFRARED
|
||||
static uint16_t try_send_infrared_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size,
|
||||
bool is_single);
|
||||
#endif
|
||||
#ifdef USE_EVENT
|
||||
static uint16_t try_send_event_response(event::Event *event, const char *event_type, APIConnection *conn,
|
||||
static uint16_t try_send_event_response(event::Event *event, StringRef event_type, APIConnection *conn,
|
||||
uint32_t remaining_size, bool is_single);
|
||||
static uint16_t try_send_event_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single);
|
||||
#endif
|
||||
|
||||
@@ -27,7 +27,6 @@ extend google.protobuf.MessageOptions {
|
||||
extend google.protobuf.FieldOptions {
|
||||
optional string field_ifdef = 1042;
|
||||
optional uint32 fixed_array_size = 50007;
|
||||
optional bool no_zero_copy = 50008 [default=false];
|
||||
optional bool fixed_array_skip_zero = 50009 [default=false];
|
||||
optional string fixed_array_size_define = 50010;
|
||||
optional string fixed_array_with_length_define = 50011;
|
||||
@@ -80,4 +79,15 @@ extend google.protobuf.FieldOptions {
|
||||
// Example: [(container_pointer_no_template) = "light::ColorModeMask"]
|
||||
// generates: const light::ColorModeMask *supported_color_modes{};
|
||||
optional string container_pointer_no_template = 50014;
|
||||
|
||||
// packed_buffer: Expose raw packed buffer instead of decoding into container
|
||||
// When set on a packed repeated field, the generated code stores a pointer
|
||||
// to the raw protobuf buffer instead of decoding values. This enables
|
||||
// zero-copy passthrough when the consumer can decode on-demand.
|
||||
// The field must be a packed repeated field (packed=true).
|
||||
// Generates three fields:
|
||||
// - const uint8_t *<field>_data_{nullptr};
|
||||
// - uint16_t <field>_length_{0};
|
||||
// - uint16_t <field>_count_{0};
|
||||
optional bool packed_buffer = 50015 [default=false];
|
||||
}
|
||||
|
||||
@@ -3347,5 +3347,98 @@ void ZWaveProxyRequest::calculate_size(ProtoSize &size) const {
|
||||
size.add_length(1, this->data_len);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_INFRARED
|
||||
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);
|
||||
#ifdef USE_ENTITY_ICON
|
||||
buffer.encode_string(4, this->icon);
|
||||
#endif
|
||||
buffer.encode_bool(5, this->disabled_by_default);
|
||||
buffer.encode_uint32(6, static_cast<uint32_t>(this->entity_category));
|
||||
#ifdef USE_DEVICES
|
||||
buffer.encode_uint32(7, this->device_id);
|
||||
#endif
|
||||
buffer.encode_uint32(8, this->capabilities);
|
||||
}
|
||||
void ListEntitiesInfraredResponse::calculate_size(ProtoSize &size) const {
|
||||
size.add_length(1, this->object_id.size());
|
||||
size.add_fixed32(1, this->key);
|
||||
size.add_length(1, this->name.size());
|
||||
#ifdef USE_ENTITY_ICON
|
||||
size.add_length(1, this->icon.size());
|
||||
#endif
|
||||
size.add_bool(1, this->disabled_by_default);
|
||||
size.add_uint32(1, static_cast<uint32_t>(this->entity_category));
|
||||
#ifdef USE_DEVICES
|
||||
size.add_uint32(1, this->device_id);
|
||||
#endif
|
||||
size.add_uint32(1, this->capabilities);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_IR_RF
|
||||
bool InfraredRFTransmitRawTimingsRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
switch (field_id) {
|
||||
#ifdef USE_DEVICES
|
||||
case 1:
|
||||
this->device_id = value.as_uint32();
|
||||
break;
|
||||
#endif
|
||||
case 3:
|
||||
this->carrier_frequency = value.as_uint32();
|
||||
break;
|
||||
case 4:
|
||||
this->repeat_count = value.as_uint32();
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
bool InfraredRFTransmitRawTimingsRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
|
||||
switch (field_id) {
|
||||
case 5: {
|
||||
this->timings_data_ = value.data();
|
||||
this->timings_length_ = value.size();
|
||||
this->timings_count_ = count_packed_varints(value.data(), value.size());
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
bool InfraredRFTransmitRawTimingsRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
|
||||
switch (field_id) {
|
||||
case 2:
|
||||
this->key = value.as_fixed32();
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
void InfraredRFReceiveEvent::encode(ProtoWriteBuffer buffer) const {
|
||||
#ifdef USE_DEVICES
|
||||
buffer.encode_uint32(1, this->device_id);
|
||||
#endif
|
||||
buffer.encode_fixed32(2, this->key);
|
||||
for (const auto &it : *this->timings) {
|
||||
buffer.encode_sint32(3, it, true);
|
||||
}
|
||||
}
|
||||
void InfraredRFReceiveEvent::calculate_size(ProtoSize &size) const {
|
||||
#ifdef USE_DEVICES
|
||||
size.add_uint32(1, this->device_id);
|
||||
#endif
|
||||
size.add_fixed32(1, this->key);
|
||||
if (!this->timings->empty()) {
|
||||
for (const auto &it : *this->timings) {
|
||||
size.add_sint32_force(1, it);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace esphome::api
|
||||
|
||||
@@ -1053,7 +1053,7 @@ class SubscribeHomeassistantServicesRequest final : public ProtoMessage {
|
||||
class HomeassistantServiceMap final : public ProtoMessage {
|
||||
public:
|
||||
StringRef key{};
|
||||
std::string value{};
|
||||
StringRef value{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
@@ -1081,7 +1081,7 @@ class HomeassistantActionRequest final : public ProtoMessage {
|
||||
bool wants_response{false};
|
||||
#endif
|
||||
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
|
||||
std::string response_template{};
|
||||
StringRef response_template{};
|
||||
#endif
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
@@ -3049,5 +3049,70 @@ class ZWaveProxyRequest final : public ProtoDecodableMessage {
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
#endif
|
||||
#ifdef USE_INFRARED
|
||||
class ListEntitiesInfraredResponse final : public InfoResponseProtoMessage {
|
||||
public:
|
||||
static constexpr uint8_t MESSAGE_TYPE = 135;
|
||||
static constexpr uint8_t ESTIMATED_SIZE = 44;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *message_name() const override { return "list_entities_infrared_response"; }
|
||||
#endif
|
||||
uint32_t capabilities{0};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
};
|
||||
#endif
|
||||
#ifdef USE_IR_RF
|
||||
class InfraredRFTransmitRawTimingsRequest final : public ProtoDecodableMessage {
|
||||
public:
|
||||
static constexpr uint8_t MESSAGE_TYPE = 136;
|
||||
static constexpr uint8_t ESTIMATED_SIZE = 220;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *message_name() const override { return "infrared_rf_transmit_raw_timings_request"; }
|
||||
#endif
|
||||
#ifdef USE_DEVICES
|
||||
uint32_t device_id{0};
|
||||
#endif
|
||||
uint32_t key{0};
|
||||
uint32_t carrier_frequency{0};
|
||||
uint32_t repeat_count{0};
|
||||
const uint8_t *timings_data_{nullptr};
|
||||
uint16_t timings_length_{0};
|
||||
uint16_t timings_count_{0};
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class InfraredRFReceiveEvent final : public ProtoMessage {
|
||||
public:
|
||||
static constexpr uint8_t MESSAGE_TYPE = 137;
|
||||
static constexpr uint8_t ESTIMATED_SIZE = 17;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
const char *message_name() const override { return "infrared_rf_receive_event"; }
|
||||
#endif
|
||||
#ifdef USE_DEVICES
|
||||
uint32_t device_id{0};
|
||||
#endif
|
||||
uint32_t key{0};
|
||||
const std::vector<int32_t> *timings{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void calculate_size(ProtoSize &size) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
};
|
||||
#endif
|
||||
|
||||
} // namespace esphome::api
|
||||
|
||||
@@ -100,6 +100,16 @@ template<typename T> static void dump_field(std::string &out, const char *field_
|
||||
out.append("\n");
|
||||
}
|
||||
|
||||
// Helper for bytes fields - uses stack buffer to avoid heap allocation
|
||||
// Buffer sized for 160 bytes of data (480 chars with separators) to fit typical log buffer
|
||||
static void dump_bytes_field(std::string &out, const char *field_name, const uint8_t *data, size_t len,
|
||||
int indent = 2) {
|
||||
char hex_buf[format_hex_pretty_size(160)];
|
||||
append_field_prefix(out, field_name, indent);
|
||||
format_hex_pretty_to(hex_buf, data, len);
|
||||
append_with_newline(out, hex_buf);
|
||||
}
|
||||
|
||||
template<> const char *proto_enum_to_string<enums::EntityCategory>(enums::EntityCategory value) {
|
||||
switch (value) {
|
||||
case enums::ENTITY_CATEGORY_NONE:
|
||||
@@ -1127,16 +1137,12 @@ void SubscribeLogsRequest::dump_to(std::string &out) const {
|
||||
void SubscribeLogsResponse::dump_to(std::string &out) const {
|
||||
MessageDumpHelper helper(out, "SubscribeLogsResponse");
|
||||
dump_field(out, "level", static_cast<enums::LogLevel>(this->level));
|
||||
out.append(" message: ");
|
||||
out.append(format_hex_pretty(this->message_ptr_, this->message_len_));
|
||||
out.append("\n");
|
||||
dump_bytes_field(out, "message", this->message_ptr_, this->message_len_);
|
||||
}
|
||||
#ifdef USE_API_NOISE
|
||||
void NoiseEncryptionSetKeyRequest::dump_to(std::string &out) const {
|
||||
MessageDumpHelper helper(out, "NoiseEncryptionSetKeyRequest");
|
||||
out.append(" key: ");
|
||||
out.append(format_hex_pretty(this->key, this->key_len));
|
||||
out.append("\n");
|
||||
dump_bytes_field(out, "key", this->key, this->key_len);
|
||||
}
|
||||
void NoiseEncryptionSetKeyResponse::dump_to(std::string &out) const {
|
||||
MessageDumpHelper helper(out, "NoiseEncryptionSetKeyResponse");
|
||||
@@ -1189,9 +1195,7 @@ void HomeassistantActionResponse::dump_to(std::string &out) const {
|
||||
dump_field(out, "success", this->success);
|
||||
dump_field(out, "error_message", this->error_message);
|
||||
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
|
||||
out.append(" response_data: ");
|
||||
out.append(format_hex_pretty(this->response_data, this->response_data_len));
|
||||
out.append("\n");
|
||||
dump_bytes_field(out, "response_data", this->response_data, this->response_data_len);
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
@@ -1278,9 +1282,7 @@ void ExecuteServiceResponse::dump_to(std::string &out) const {
|
||||
dump_field(out, "success", this->success);
|
||||
dump_field(out, "error_message", this->error_message);
|
||||
#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES_JSON
|
||||
out.append(" response_data: ");
|
||||
out.append(format_hex_pretty(this->response_data, this->response_data_len));
|
||||
out.append("\n");
|
||||
dump_bytes_field(out, "response_data", this->response_data, this->response_data_len);
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
@@ -1302,9 +1304,7 @@ void ListEntitiesCameraResponse::dump_to(std::string &out) const {
|
||||
void CameraImageResponse::dump_to(std::string &out) const {
|
||||
MessageDumpHelper helper(out, "CameraImageResponse");
|
||||
dump_field(out, "key", this->key);
|
||||
out.append(" data: ");
|
||||
out.append(format_hex_pretty(this->data_ptr_, this->data_len_));
|
||||
out.append("\n");
|
||||
dump_bytes_field(out, "data", this->data_ptr_, this->data_len_);
|
||||
dump_field(out, "done", this->done);
|
||||
#ifdef USE_DEVICES
|
||||
dump_field(out, "device_id", this->device_id);
|
||||
@@ -1705,9 +1705,7 @@ void BluetoothLERawAdvertisement::dump_to(std::string &out) const {
|
||||
dump_field(out, "address", this->address);
|
||||
dump_field(out, "rssi", this->rssi);
|
||||
dump_field(out, "address_type", this->address_type);
|
||||
out.append(" data: ");
|
||||
out.append(format_hex_pretty(this->data, this->data_len));
|
||||
out.append("\n");
|
||||
dump_bytes_field(out, "data", this->data, this->data_len);
|
||||
}
|
||||
void BluetoothLERawAdvertisementsResponse::dump_to(std::string &out) const {
|
||||
MessageDumpHelper helper(out, "BluetoothLERawAdvertisementsResponse");
|
||||
@@ -1792,18 +1790,14 @@ void BluetoothGATTReadResponse::dump_to(std::string &out) const {
|
||||
MessageDumpHelper helper(out, "BluetoothGATTReadResponse");
|
||||
dump_field(out, "address", this->address);
|
||||
dump_field(out, "handle", this->handle);
|
||||
out.append(" data: ");
|
||||
out.append(format_hex_pretty(this->data_ptr_, this->data_len_));
|
||||
out.append("\n");
|
||||
dump_bytes_field(out, "data", this->data_ptr_, this->data_len_);
|
||||
}
|
||||
void BluetoothGATTWriteRequest::dump_to(std::string &out) const {
|
||||
MessageDumpHelper helper(out, "BluetoothGATTWriteRequest");
|
||||
dump_field(out, "address", this->address);
|
||||
dump_field(out, "handle", this->handle);
|
||||
dump_field(out, "response", this->response);
|
||||
out.append(" data: ");
|
||||
out.append(format_hex_pretty(this->data, this->data_len));
|
||||
out.append("\n");
|
||||
dump_bytes_field(out, "data", this->data, this->data_len);
|
||||
}
|
||||
void BluetoothGATTReadDescriptorRequest::dump_to(std::string &out) const {
|
||||
MessageDumpHelper helper(out, "BluetoothGATTReadDescriptorRequest");
|
||||
@@ -1814,9 +1808,7 @@ void BluetoothGATTWriteDescriptorRequest::dump_to(std::string &out) const {
|
||||
MessageDumpHelper helper(out, "BluetoothGATTWriteDescriptorRequest");
|
||||
dump_field(out, "address", this->address);
|
||||
dump_field(out, "handle", this->handle);
|
||||
out.append(" data: ");
|
||||
out.append(format_hex_pretty(this->data, this->data_len));
|
||||
out.append("\n");
|
||||
dump_bytes_field(out, "data", this->data, this->data_len);
|
||||
}
|
||||
void BluetoothGATTNotifyRequest::dump_to(std::string &out) const {
|
||||
MessageDumpHelper helper(out, "BluetoothGATTNotifyRequest");
|
||||
@@ -1828,9 +1820,7 @@ void BluetoothGATTNotifyDataResponse::dump_to(std::string &out) const {
|
||||
MessageDumpHelper helper(out, "BluetoothGATTNotifyDataResponse");
|
||||
dump_field(out, "address", this->address);
|
||||
dump_field(out, "handle", this->handle);
|
||||
out.append(" data: ");
|
||||
out.append(format_hex_pretty(this->data_ptr_, this->data_len_));
|
||||
out.append("\n");
|
||||
dump_bytes_field(out, "data", this->data_ptr_, this->data_len_);
|
||||
}
|
||||
void SubscribeBluetoothConnectionsFreeRequest::dump_to(std::string &out) const {
|
||||
out.append("SubscribeBluetoothConnectionsFreeRequest {}");
|
||||
@@ -1934,9 +1924,7 @@ void VoiceAssistantEventResponse::dump_to(std::string &out) const {
|
||||
}
|
||||
void VoiceAssistantAudio::dump_to(std::string &out) const {
|
||||
MessageDumpHelper helper(out, "VoiceAssistantAudio");
|
||||
out.append(" data: ");
|
||||
out.append(format_hex_pretty(this->data, this->data_len));
|
||||
out.append("\n");
|
||||
dump_bytes_field(out, "data", this->data, this->data_len);
|
||||
dump_field(out, "end", this->end);
|
||||
}
|
||||
void VoiceAssistantTimerEventResponse::dump_to(std::string &out) const {
|
||||
@@ -2297,16 +2285,56 @@ void UpdateCommandRequest::dump_to(std::string &out) const {
|
||||
#ifdef USE_ZWAVE_PROXY
|
||||
void ZWaveProxyFrame::dump_to(std::string &out) const {
|
||||
MessageDumpHelper helper(out, "ZWaveProxyFrame");
|
||||
out.append(" data: ");
|
||||
out.append(format_hex_pretty(this->data, this->data_len));
|
||||
out.append("\n");
|
||||
dump_bytes_field(out, "data", this->data, this->data_len);
|
||||
}
|
||||
void ZWaveProxyRequest::dump_to(std::string &out) const {
|
||||
MessageDumpHelper helper(out, "ZWaveProxyRequest");
|
||||
dump_field(out, "type", static_cast<enums::ZWaveProxyRequestType>(this->type));
|
||||
out.append(" data: ");
|
||||
out.append(format_hex_pretty(this->data, this->data_len));
|
||||
out.append("\n");
|
||||
dump_bytes_field(out, "data", this->data, this->data_len);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_INFRARED
|
||||
void ListEntitiesInfraredResponse::dump_to(std::string &out) const {
|
||||
MessageDumpHelper helper(out, "ListEntitiesInfraredResponse");
|
||||
dump_field(out, "object_id", this->object_id);
|
||||
dump_field(out, "key", this->key);
|
||||
dump_field(out, "name", this->name);
|
||||
#ifdef USE_ENTITY_ICON
|
||||
dump_field(out, "icon", this->icon);
|
||||
#endif
|
||||
dump_field(out, "disabled_by_default", this->disabled_by_default);
|
||||
dump_field(out, "entity_category", static_cast<enums::EntityCategory>(this->entity_category));
|
||||
#ifdef USE_DEVICES
|
||||
dump_field(out, "device_id", this->device_id);
|
||||
#endif
|
||||
dump_field(out, "capabilities", this->capabilities);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_IR_RF
|
||||
void InfraredRFTransmitRawTimingsRequest::dump_to(std::string &out) const {
|
||||
MessageDumpHelper helper(out, "InfraredRFTransmitRawTimingsRequest");
|
||||
#ifdef USE_DEVICES
|
||||
dump_field(out, "device_id", this->device_id);
|
||||
#endif
|
||||
dump_field(out, "key", this->key);
|
||||
dump_field(out, "carrier_frequency", this->carrier_frequency);
|
||||
dump_field(out, "repeat_count", this->repeat_count);
|
||||
out.append(" timings: ");
|
||||
out.append("packed buffer [");
|
||||
out.append(std::to_string(this->timings_count_));
|
||||
out.append(" values, ");
|
||||
out.append(std::to_string(this->timings_length_));
|
||||
out.append(" bytes]\n");
|
||||
}
|
||||
void InfraredRFReceiveEvent::dump_to(std::string &out) const {
|
||||
MessageDumpHelper helper(out, "InfraredRFReceiveEvent");
|
||||
#ifdef USE_DEVICES
|
||||
dump_field(out, "device_id", this->device_id);
|
||||
#endif
|
||||
dump_field(out, "key", this->key);
|
||||
for (const auto &it : *this->timings) {
|
||||
dump_field(out, "timings", it, 4);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
@@ -621,6 +621,17 @@ void APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
||||
this->on_water_heater_command_request(msg);
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_IR_RF
|
||||
case InfraredRFTransmitRawTimingsRequest::MESSAGE_TYPE: {
|
||||
InfraredRFTransmitRawTimingsRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_infrared_rf_transmit_raw_timings_request: %s", msg.dump().c_str());
|
||||
#endif
|
||||
this->on_infrared_rf_transmit_raw_timings_request(msg);
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
default:
|
||||
break;
|
||||
@@ -819,6 +830,11 @@ void APIServerConnection::on_z_wave_proxy_frame(const ZWaveProxyFrame &msg) { th
|
||||
#ifdef USE_ZWAVE_PROXY
|
||||
void APIServerConnection::on_z_wave_proxy_request(const ZWaveProxyRequest &msg) { this->zwave_proxy_request(msg); }
|
||||
#endif
|
||||
#ifdef USE_IR_RF
|
||||
void APIServerConnection::on_infrared_rf_transmit_raw_timings_request(const InfraredRFTransmitRawTimingsRequest &msg) {
|
||||
this->infrared_rf_transmit_raw_timings(msg);
|
||||
}
|
||||
#endif
|
||||
|
||||
void APIServerConnection::read_message(uint32_t msg_size, uint32_t msg_type, const uint8_t *msg_data) {
|
||||
// Check authentication/connection requirements for messages
|
||||
|
||||
@@ -217,6 +217,11 @@ class APIServerConnectionBase : public ProtoService {
|
||||
#ifdef USE_ZWAVE_PROXY
|
||||
virtual void on_z_wave_proxy_request(const ZWaveProxyRequest &value){};
|
||||
#endif
|
||||
|
||||
#ifdef USE_IR_RF
|
||||
virtual void on_infrared_rf_transmit_raw_timings_request(const InfraredRFTransmitRawTimingsRequest &value){};
|
||||
#endif
|
||||
|
||||
protected:
|
||||
void read_message(uint32_t msg_size, uint32_t msg_type, const uint8_t *msg_data) override;
|
||||
};
|
||||
@@ -347,6 +352,9 @@ class APIServerConnection : public APIServerConnectionBase {
|
||||
#endif
|
||||
#ifdef USE_ZWAVE_PROXY
|
||||
virtual void zwave_proxy_request(const ZWaveProxyRequest &msg) = 0;
|
||||
#endif
|
||||
#ifdef USE_IR_RF
|
||||
virtual void infrared_rf_transmit_raw_timings(const InfraredRFTransmitRawTimingsRequest &msg) = 0;
|
||||
#endif
|
||||
protected:
|
||||
void on_hello_request(const HelloRequest &msg) override;
|
||||
@@ -473,6 +481,9 @@ class APIServerConnection : public APIServerConnectionBase {
|
||||
#endif
|
||||
#ifdef USE_ZWAVE_PROXY
|
||||
void on_z_wave_proxy_request(const ZWaveProxyRequest &msg) override;
|
||||
#endif
|
||||
#ifdef USE_IR_RF
|
||||
void on_infrared_rf_transmit_raw_timings_request(const InfraredRFTransmitRawTimingsRequest &msg) override;
|
||||
#endif
|
||||
void read_message(uint32_t msg_size, uint32_t msg_type, const uint8_t *msg_data) override;
|
||||
};
|
||||
|
||||
@@ -347,6 +347,21 @@ void APIServer::on_zwave_proxy_request(const esphome::api::ProtoMessage &msg) {
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_IR_RF
|
||||
void APIServer::send_infrared_rf_receive_event([[maybe_unused]] uint32_t device_id, uint32_t key,
|
||||
const std::vector<int32_t> *timings) {
|
||||
InfraredRFReceiveEvent resp{};
|
||||
#ifdef USE_DEVICES
|
||||
resp.device_id = device_id;
|
||||
#endif
|
||||
resp.key = key;
|
||||
resp.timings = timings;
|
||||
|
||||
for (auto &c : this->clients_)
|
||||
c->send_infrared_rf_receive_event(resp);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_ALARM_CONTROL_PANEL
|
||||
API_DISPATCH_UPDATE(alarm_control_panel::AlarmControlPanel, alarm_control_panel)
|
||||
#endif
|
||||
@@ -624,6 +639,14 @@ bool APIServer::teardown() {
|
||||
#define USE_API_ACTION_CALL_TIMEOUT_MS 30000 // NOLINT
|
||||
#endif
|
||||
|
||||
// SSO-friendly action call key - hex format guarantees max 11 chars ("ac_ffffffff")
|
||||
// which fits in any std::string SSO buffer (typically 12-15 bytes)
|
||||
static inline std::string make_action_call_key(uint32_t id) {
|
||||
char buf[12];
|
||||
size_t len = snprintf(buf, sizeof(buf), "ac_%x", id);
|
||||
return std::string(buf, len);
|
||||
}
|
||||
|
||||
uint32_t APIServer::register_active_action_call(uint32_t client_call_id, APIConnection *conn) {
|
||||
uint32_t action_call_id = this->next_action_call_id_++;
|
||||
// Handle wraparound (skip 0 as it means "no call")
|
||||
@@ -633,18 +656,17 @@ uint32_t APIServer::register_active_action_call(uint32_t client_call_id, APIConn
|
||||
this->active_action_calls_.push_back({action_call_id, client_call_id, conn});
|
||||
|
||||
// Schedule automatic cleanup after timeout (client will have given up by then)
|
||||
this->set_timeout(str_sprintf("action_call_%u", action_call_id), USE_API_ACTION_CALL_TIMEOUT_MS,
|
||||
[this, action_call_id]() {
|
||||
ESP_LOGD(TAG, "Action call %u timed out", action_call_id);
|
||||
this->unregister_active_action_call(action_call_id);
|
||||
});
|
||||
this->set_timeout(make_action_call_key(action_call_id), USE_API_ACTION_CALL_TIMEOUT_MS, [this, action_call_id]() {
|
||||
ESP_LOGD(TAG, "Action call %u timed out", action_call_id);
|
||||
this->unregister_active_action_call(action_call_id);
|
||||
});
|
||||
|
||||
return action_call_id;
|
||||
}
|
||||
|
||||
void APIServer::unregister_active_action_call(uint32_t action_call_id) {
|
||||
// Cancel the timeout for this action call
|
||||
this->cancel_timeout(str_sprintf("action_call_%u", action_call_id));
|
||||
this->cancel_timeout(make_action_call_key(action_call_id));
|
||||
|
||||
// Swap-and-pop is more efficient than remove_if for unordered vectors
|
||||
for (size_t i = 0; i < this->active_action_calls_.size(); i++) {
|
||||
@@ -661,7 +683,7 @@ void APIServer::unregister_active_action_calls_for_connection(APIConnection *con
|
||||
for (size_t i = 0; i < this->active_action_calls_.size();) {
|
||||
if (this->active_action_calls_[i].connection == conn) {
|
||||
// Cancel the timeout for this action call
|
||||
this->cancel_timeout(str_sprintf("action_call_%u", this->active_action_calls_[i].action_call_id));
|
||||
this->cancel_timeout(make_action_call_key(this->active_action_calls_[i].action_call_id));
|
||||
|
||||
std::swap(this->active_action_calls_[i], this->active_action_calls_.back());
|
||||
this->active_action_calls_.pop_back();
|
||||
|
||||
@@ -185,6 +185,9 @@ class APIServer : public Component,
|
||||
#ifdef USE_ZWAVE_PROXY
|
||||
void on_zwave_proxy_request(const esphome::api::ProtoMessage &msg);
|
||||
#endif
|
||||
#ifdef USE_IR_RF
|
||||
void send_infrared_rf_receive_event(uint32_t device_id, uint32_t key, const std::vector<int32_t> *timings);
|
||||
#endif
|
||||
|
||||
bool is_connected(bool state_subscription_only = false) const;
|
||||
|
||||
|
||||
@@ -265,7 +265,7 @@ class CustomAPIDevice {
|
||||
for (auto &it : data) {
|
||||
auto &kv = resp.data.emplace_back();
|
||||
kv.key = StringRef(it.first);
|
||||
kv.value = it.second; // value is std::string (no_zero_copy), assign directly
|
||||
kv.value = StringRef(it.second); // data map lives until send completes
|
||||
}
|
||||
global_api_server->send_homeassistant_action(resp);
|
||||
}
|
||||
@@ -308,7 +308,7 @@ class CustomAPIDevice {
|
||||
for (auto &it : data) {
|
||||
auto &kv = resp.data.emplace_back();
|
||||
kv.key = StringRef(it.first);
|
||||
kv.value = it.second; // value is std::string (no_zero_copy), assign directly
|
||||
kv.value = StringRef(it.second); // data map lives until send completes
|
||||
}
|
||||
global_api_server->send_homeassistant_action(resp);
|
||||
}
|
||||
|
||||
@@ -149,11 +149,21 @@ template<typename... Ts> class HomeAssistantServiceCallAction : public Action<Ts
|
||||
std::string service_value = this->service_.value(x...);
|
||||
resp.service = StringRef(service_value);
|
||||
resp.is_event = this->flags_.is_event;
|
||||
this->populate_service_map(resp.data, this->data_, x...);
|
||||
this->populate_service_map(resp.data_template, this->data_template_, x...);
|
||||
this->populate_service_map(resp.variables, this->variables_, x...);
|
||||
|
||||
// Local storage for lambda-evaluated strings - lives until after send
|
||||
FixedVector<std::string> data_storage;
|
||||
FixedVector<std::string> data_template_storage;
|
||||
FixedVector<std::string> variables_storage;
|
||||
|
||||
this->populate_service_map(resp.data, this->data_, data_storage, x...);
|
||||
this->populate_service_map(resp.data_template, this->data_template_, data_template_storage, x...);
|
||||
this->populate_service_map(resp.variables, this->variables_, variables_storage, x...);
|
||||
|
||||
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
|
||||
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
|
||||
// IMPORTANT: Declare at outer scope so it lives until send_homeassistant_action returns.
|
||||
std::string response_template_value;
|
||||
#endif
|
||||
if (this->flags_.wants_status) {
|
||||
// Generate a unique call ID for this service call
|
||||
static uint32_t call_id_counter = 1;
|
||||
@@ -164,8 +174,8 @@ template<typename... Ts> class HomeAssistantServiceCallAction : public Action<Ts
|
||||
resp.wants_response = true;
|
||||
// Set response template if provided
|
||||
if (this->flags_.has_response_template) {
|
||||
std::string response_template_value = this->response_template_.value(x...);
|
||||
resp.response_template = response_template_value;
|
||||
response_template_value = this->response_template_.value(x...);
|
||||
resp.response_template = StringRef(response_template_value);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -205,12 +215,31 @@ template<typename... Ts> class HomeAssistantServiceCallAction : public Action<Ts
|
||||
}
|
||||
|
||||
template<typename VectorType, typename SourceType>
|
||||
static void populate_service_map(VectorType &dest, SourceType &source, Ts... x) {
|
||||
static void populate_service_map(VectorType &dest, SourceType &source, FixedVector<std::string> &value_storage,
|
||||
Ts... x) {
|
||||
dest.init(source.size());
|
||||
|
||||
// Count non-static strings to allocate exact storage needed
|
||||
size_t lambda_count = 0;
|
||||
for (const auto &it : source) {
|
||||
if (!it.value.is_static_string()) {
|
||||
lambda_count++;
|
||||
}
|
||||
}
|
||||
value_storage.init(lambda_count);
|
||||
|
||||
for (auto &it : source) {
|
||||
auto &kv = dest.emplace_back();
|
||||
kv.key = StringRef(it.key);
|
||||
kv.value = it.value.value(x...);
|
||||
|
||||
if (it.value.is_static_string()) {
|
||||
// Static string from YAML - zero allocation
|
||||
kv.value = StringRef(it.value.get_static_string());
|
||||
} else {
|
||||
// Lambda evaluation - store result, reference it
|
||||
value_storage.push_back(it.value.value(x...));
|
||||
kv.value = StringRef(value_storage.back());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -76,6 +76,9 @@ LIST_ENTITIES_HANDLER(alarm_control_panel, alarm_control_panel::AlarmControlPane
|
||||
#ifdef USE_WATER_HEATER
|
||||
LIST_ENTITIES_HANDLER(water_heater, water_heater::WaterHeater, ListEntitiesWaterHeaterResponse)
|
||||
#endif
|
||||
#ifdef USE_INFRARED
|
||||
LIST_ENTITIES_HANDLER(infrared, infrared::Infrared, ListEntitiesInfraredResponse)
|
||||
#endif
|
||||
#ifdef USE_EVENT
|
||||
LIST_ENTITIES_HANDLER(event, event::Event, ListEntitiesEventResponse)
|
||||
#endif
|
||||
|
||||
@@ -85,6 +85,9 @@ class ListEntitiesIterator : public ComponentIterator {
|
||||
#ifdef USE_WATER_HEATER
|
||||
bool on_water_heater(water_heater::WaterHeater *entity) override;
|
||||
#endif
|
||||
#ifdef USE_INFRARED
|
||||
bool on_infrared(infrared::Infrared *entity) override;
|
||||
#endif
|
||||
#ifdef USE_EVENT
|
||||
bool on_event(event::Event *entity) override;
|
||||
#endif
|
||||
|
||||
@@ -79,6 +79,9 @@ class InitialStateIterator : public ComponentIterator {
|
||||
#ifdef USE_WATER_HEATER
|
||||
bool on_water_heater(water_heater::WaterHeater *entity) override;
|
||||
#endif
|
||||
#ifdef USE_INFRARED
|
||||
bool on_infrared(infrared::Infrared *infrared) override { return true; };
|
||||
#endif
|
||||
#ifdef USE_EVENT
|
||||
bool on_event(event::Event *event) override { return true; };
|
||||
#endif
|
||||
|
||||
@@ -6,7 +6,7 @@ namespace esphome::aqi {
|
||||
|
||||
class AbstractAQICalculator {
|
||||
public:
|
||||
virtual uint16_t get_aqi(uint16_t pm2_5_value, uint16_t pm10_0_value) = 0;
|
||||
virtual uint16_t get_aqi(float pm2_5_value, float pm10_0_value) = 0;
|
||||
};
|
||||
|
||||
} // namespace esphome::aqi
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <climits>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include "abstract_aqi_calculator.h"
|
||||
|
||||
// https://document.airnow.gov/technical-assistance-document-for-the-reporting-of-daily-air-quailty.pdf
|
||||
@@ -9,11 +10,11 @@ namespace esphome::aqi {
|
||||
|
||||
class AQICalculator : public AbstractAQICalculator {
|
||||
public:
|
||||
uint16_t get_aqi(uint16_t pm2_5_value, uint16_t pm10_0_value) override {
|
||||
int pm2_5_index = calculate_index(pm2_5_value, PM2_5_GRID);
|
||||
int pm10_0_index = calculate_index(pm10_0_value, PM10_0_GRID);
|
||||
uint16_t get_aqi(float pm2_5_value, float pm10_0_value) override {
|
||||
float pm2_5_index = calculate_index(pm2_5_value, PM2_5_GRID);
|
||||
float pm10_0_index = calculate_index(pm10_0_value, PM10_0_GRID);
|
||||
|
||||
return (pm2_5_index < pm10_0_index) ? pm10_0_index : pm2_5_index;
|
||||
return static_cast<uint16_t>(std::round((pm2_5_index < pm10_0_index) ? pm10_0_index : pm2_5_index));
|
||||
}
|
||||
|
||||
protected:
|
||||
@@ -21,25 +22,28 @@ class AQICalculator : public AbstractAQICalculator {
|
||||
|
||||
static constexpr int INDEX_GRID[NUM_LEVELS][2] = {{0, 50}, {51, 100}, {101, 150}, {151, 200}, {201, 300}, {301, 500}};
|
||||
|
||||
static constexpr int PM2_5_GRID[NUM_LEVELS][2] = {{0, 9}, {10, 35}, {36, 55}, {56, 125}, {126, 225}, {226, INT_MAX}};
|
||||
static constexpr float PM2_5_GRID[NUM_LEVELS][2] = {{0.0f, 9.0f}, {9.1f, 35.4f},
|
||||
{35.5f, 55.4f}, {55.5f, 125.4f},
|
||||
{125.5f, 225.4f}, {225.5f, std::numeric_limits<float>::max()}};
|
||||
|
||||
static constexpr int PM10_0_GRID[NUM_LEVELS][2] = {{0, 54}, {55, 154}, {155, 254},
|
||||
{255, 354}, {355, 424}, {425, INT_MAX}};
|
||||
static constexpr float PM10_0_GRID[NUM_LEVELS][2] = {{0.0f, 54.0f}, {55.0f, 154.0f},
|
||||
{155.0f, 254.0f}, {255.0f, 354.0f},
|
||||
{355.0f, 424.0f}, {425.0f, std::numeric_limits<float>::max()}};
|
||||
|
||||
static int calculate_index(uint16_t value, const int array[NUM_LEVELS][2]) {
|
||||
static float calculate_index(float value, const float array[NUM_LEVELS][2]) {
|
||||
int grid_index = get_grid_index(value, array);
|
||||
if (grid_index == -1) {
|
||||
return -1;
|
||||
return -1.0f;
|
||||
}
|
||||
int aqi_lo = INDEX_GRID[grid_index][0];
|
||||
int aqi_hi = INDEX_GRID[grid_index][1];
|
||||
int conc_lo = array[grid_index][0];
|
||||
int conc_hi = array[grid_index][1];
|
||||
float aqi_lo = INDEX_GRID[grid_index][0];
|
||||
float aqi_hi = INDEX_GRID[grid_index][1];
|
||||
float conc_lo = array[grid_index][0];
|
||||
float conc_hi = array[grid_index][1];
|
||||
|
||||
return (value - conc_lo) * (aqi_hi - aqi_lo) / (conc_hi - conc_lo) + aqi_lo;
|
||||
}
|
||||
|
||||
static int get_grid_index(uint16_t value, const int array[NUM_LEVELS][2]) {
|
||||
static int get_grid_index(float value, const float array[NUM_LEVELS][2]) {
|
||||
for (int i = 0; i < NUM_LEVELS; i++) {
|
||||
if (value >= array[i][0] && value <= array[i][1]) {
|
||||
return i;
|
||||
|
||||
@@ -44,8 +44,7 @@ void AQISensor::calculate_aqi_() {
|
||||
return;
|
||||
}
|
||||
|
||||
uint16_t aqi =
|
||||
calculator->get_aqi(static_cast<uint16_t>(this->pm_2_5_value_), static_cast<uint16_t>(this->pm_10_0_value_));
|
||||
uint16_t aqi = calculator->get_aqi(this->pm_2_5_value_, this->pm_10_0_value_);
|
||||
this->publish_state(aqi);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include "abstract_aqi_calculator.h"
|
||||
|
||||
namespace esphome::aqi {
|
||||
|
||||
class CAQICalculator : public AbstractAQICalculator {
|
||||
public:
|
||||
uint16_t get_aqi(uint16_t pm2_5_value, uint16_t pm10_0_value) override {
|
||||
int pm2_5_index = calculate_index(pm2_5_value, PM2_5_GRID);
|
||||
int pm10_0_index = calculate_index(pm10_0_value, PM10_0_GRID);
|
||||
uint16_t get_aqi(float pm2_5_value, float pm10_0_value) override {
|
||||
float pm2_5_index = calculate_index(pm2_5_value, PM2_5_GRID);
|
||||
float pm10_0_index = calculate_index(pm10_0_value, PM10_0_GRID);
|
||||
|
||||
return (pm2_5_index < pm10_0_index) ? pm10_0_index : pm2_5_index;
|
||||
return static_cast<uint16_t>(std::round((pm2_5_index < pm10_0_index) ? pm10_0_index : pm2_5_index));
|
||||
}
|
||||
|
||||
protected:
|
||||
@@ -18,25 +20,27 @@ class CAQICalculator : public AbstractAQICalculator {
|
||||
|
||||
static constexpr int INDEX_GRID[NUM_LEVELS][2] = {{0, 25}, {26, 50}, {51, 75}, {76, 100}, {101, 400}};
|
||||
|
||||
static constexpr int PM2_5_GRID[NUM_LEVELS][2] = {{0, 15}, {16, 30}, {31, 55}, {56, 110}, {111, 400}};
|
||||
static constexpr float PM2_5_GRID[NUM_LEVELS][2] = {
|
||||
{0.0f, 15.0f}, {15.1f, 30.0f}, {30.1f, 55.0f}, {55.1f, 110.0f}, {110.1f, std::numeric_limits<float>::max()}};
|
||||
|
||||
static constexpr int PM10_0_GRID[NUM_LEVELS][2] = {{0, 25}, {26, 50}, {51, 90}, {91, 180}, {181, 400}};
|
||||
static constexpr float PM10_0_GRID[NUM_LEVELS][2] = {
|
||||
{0.0f, 25.0f}, {25.1f, 50.0f}, {50.1f, 90.0f}, {90.1f, 180.0f}, {180.1f, std::numeric_limits<float>::max()}};
|
||||
|
||||
static int calculate_index(uint16_t value, const int array[NUM_LEVELS][2]) {
|
||||
static float calculate_index(float value, const float array[NUM_LEVELS][2]) {
|
||||
int grid_index = get_grid_index(value, array);
|
||||
if (grid_index == -1) {
|
||||
return -1;
|
||||
return -1.0f;
|
||||
}
|
||||
|
||||
int aqi_lo = INDEX_GRID[grid_index][0];
|
||||
int aqi_hi = INDEX_GRID[grid_index][1];
|
||||
int conc_lo = array[grid_index][0];
|
||||
int conc_hi = array[grid_index][1];
|
||||
float aqi_lo = INDEX_GRID[grid_index][0];
|
||||
float aqi_hi = INDEX_GRID[grid_index][1];
|
||||
float conc_lo = array[grid_index][0];
|
||||
float conc_hi = array[grid_index][1];
|
||||
|
||||
return (value - conc_lo) * (aqi_hi - aqi_lo) / (conc_hi - conc_lo) + aqi_lo;
|
||||
}
|
||||
|
||||
static int get_grid_index(uint16_t value, const int array[NUM_LEVELS][2]) {
|
||||
static int get_grid_index(float value, const float array[NUM_LEVELS][2]) {
|
||||
for (int i = 0; i < NUM_LEVELS; i++) {
|
||||
if (value >= array[i][0] && value <= array[i][1]) {
|
||||
return i;
|
||||
|
||||
@@ -164,21 +164,21 @@ void BedJetClimate::control(const ClimateCall &call) {
|
||||
return;
|
||||
}
|
||||
} else if (call.has_custom_preset()) {
|
||||
const char *preset = call.get_custom_preset();
|
||||
auto preset = call.get_custom_preset();
|
||||
bool result;
|
||||
|
||||
if (strcmp(preset, "M1") == 0) {
|
||||
if (preset == "M1") {
|
||||
result = this->parent_->button_memory1();
|
||||
} else if (strcmp(preset, "M2") == 0) {
|
||||
} else if (preset == "M2") {
|
||||
result = this->parent_->button_memory2();
|
||||
} else if (strcmp(preset, "M3") == 0) {
|
||||
} else if (preset == "M3") {
|
||||
result = this->parent_->button_memory3();
|
||||
} else if (strcmp(preset, "LTD HT") == 0) {
|
||||
} else if (preset == "LTD HT") {
|
||||
result = this->parent_->button_heat();
|
||||
} else if (strcmp(preset, "EXT HT") == 0) {
|
||||
} else if (preset == "EXT HT") {
|
||||
result = this->parent_->button_ext_heat();
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Unsupported preset: %s", preset);
|
||||
ESP_LOGW(TAG, "Unsupported preset: %.*s", (int) preset.size(), preset.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -208,10 +208,11 @@ void BedJetClimate::control(const ClimateCall &call) {
|
||||
this->set_fan_mode_(fan_mode);
|
||||
}
|
||||
} else if (call.has_custom_fan_mode()) {
|
||||
const char *fan_mode = call.get_custom_fan_mode();
|
||||
auto fan_index = bedjet_fan_speed_to_step(fan_mode);
|
||||
auto fan_mode = call.get_custom_fan_mode();
|
||||
auto fan_index = bedjet_fan_speed_to_step(fan_mode.c_str());
|
||||
if (fan_index <= 19) {
|
||||
ESP_LOGV(TAG, "[%s] Converted fan mode %s to bedjet fan step %d", this->get_name().c_str(), fan_mode, fan_index);
|
||||
ESP_LOGV(TAG, "[%s] Converted fan mode %.*s to bedjet fan step %d", this->get_name().c_str(),
|
||||
(int) fan_mode.size(), fan_mode.c_str(), fan_index);
|
||||
bool result = this->parent_->set_fan_index(fan_index);
|
||||
if (result) {
|
||||
this->set_custom_fan_mode_(fan_mode);
|
||||
|
||||
@@ -7,83 +7,83 @@ namespace esphome::captive_portal {
|
||||
|
||||
#ifdef USE_CAPTIVE_PORTAL_GZIP
|
||||
const uint8_t INDEX_GZ[] PROGMEM = {
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0x95, 0x56, 0xeb, 0x6f, 0xd4, 0x38, 0x10, 0xff, 0xce,
|
||||
0x5f, 0xe1, 0x33, 0x8f, 0x26, 0xd0, 0x3c, 0xb7, 0xdb, 0x96, 0x6c, 0x12, 0x04, 0xdc, 0x21, 0x90, 0x28, 0x20, 0xb5,
|
||||
0x70, 0x1f, 0x10, 0x52, 0xbd, 0xc9, 0x64, 0x63, 0x9a, 0x38, 0x39, 0xdb, 0xfb, 0x62, 0xb5, 0xf7, 0xb7, 0xdf, 0x38,
|
||||
0xc9, 0x6e, 0xb7, 0x15, 0x9c, 0xee, 0x5a, 0x35, 0x1d, 0xdb, 0xf3, 0xf8, 0xcd, 0x78, 0x1e, 0x8e, 0x7f, 0xcb, 0x9b,
|
||||
0x4c, 0xaf, 0x5b, 0x20, 0xa5, 0xae, 0xab, 0x34, 0x36, 0x5f, 0x52, 0x31, 0x31, 0x4b, 0x40, 0xe0, 0x0a, 0x58, 0x9e,
|
||||
0xc6, 0x35, 0x68, 0x46, 0xb2, 0x92, 0x49, 0x05, 0x3a, 0xf9, 0x7c, 0xf5, 0xc6, 0x39, 0x4f, 0xe3, 0x8a, 0x8b, 0x1b,
|
||||
0x22, 0xa1, 0x4a, 0x78, 0xd6, 0x08, 0x52, 0x4a, 0x28, 0x92, 0x9c, 0x69, 0x16, 0xf1, 0x9a, 0xcd, 0x60, 0x10, 0x11,
|
||||
0xac, 0x86, 0x64, 0xc1, 0x61, 0xd9, 0x36, 0x52, 0x13, 0xe4, 0xd3, 0x20, 0x74, 0x42, 0x97, 0x3c, 0xd7, 0x65, 0x92,
|
||||
0xc3, 0x82, 0x67, 0xe0, 0x74, 0x8b, 0x63, 0x2e, 0xb8, 0xe6, 0xac, 0x72, 0x54, 0xc6, 0x2a, 0x48, 0x82, 0xe3, 0xb9,
|
||||
0x02, 0xd9, 0x2d, 0xd8, 0x14, 0xd7, 0xa2, 0xa1, 0x69, 0xac, 0x32, 0xc9, 0x5b, 0x4d, 0x0c, 0xd4, 0xa4, 0x6e, 0xf2,
|
||||
0x79, 0x05, 0xa9, 0xe7, 0x31, 0x85, 0x90, 0x94, 0xc7, 0x45, 0x0e, 0x2b, 0x77, 0xea, 0x67, 0x99, 0x0f, 0xe7, 0xe7,
|
||||
0xee, 0x77, 0xf5, 0x00, 0x9d, 0x9a, 0xd7, 0x68, 0xcd, 0xad, 0x9a, 0x8c, 0x69, 0xde, 0x08, 0x57, 0x01, 0x93, 0x59,
|
||||
0x99, 0x24, 0x09, 0x7d, 0xa1, 0xd8, 0x02, 0xe8, 0x93, 0x27, 0xd6, 0x9e, 0x69, 0x06, 0xfa, 0x8f, 0x0a, 0x0c, 0xa9,
|
||||
0x5e, 0xad, 0xaf, 0xd8, 0xec, 0x03, 0x02, 0xb7, 0x28, 0x53, 0x3c, 0x07, 0x6a, 0x7f, 0xf5, 0xbf, 0xb9, 0x4a, 0xaf,
|
||||
0x2b, 0x70, 0x73, 0xae, 0xda, 0x8a, 0xad, 0x13, 0x3a, 0x45, 0xad, 0x37, 0xd4, 0x9e, 0x14, 0x73, 0x91, 0x19, 0xe5,
|
||||
0x44, 0x59, 0x60, 0x6f, 0x2a, 0x40, 0x78, 0xc9, 0x05, 0xd3, 0xa5, 0x5b, 0xb3, 0x95, 0xd5, 0x13, 0x5c, 0x58, 0xe1,
|
||||
0x53, 0x0b, 0x9e, 0x05, 0xbe, 0x6f, 0x1f, 0x77, 0x1f, 0xdf, 0xf6, 0xf0, 0xff, 0x44, 0x82, 0x9e, 0x4b, 0x41, 0x98,
|
||||
0x75, 0x1d, 0xb7, 0xc8, 0x49, 0xf2, 0x84, 0x5e, 0x04, 0x21, 0x09, 0x9e, 0xbb, 0xe1, 0xf8, 0xbd, 0x7b, 0x46, 0x4e,
|
||||
0xf0, 0x7f, 0x76, 0xe6, 0x8c, 0x49, 0x70, 0x82, 0x9f, 0x30, 0x74, 0xc7, 0xc4, 0xff, 0x41, 0x49, 0xc1, 0xab, 0x2a,
|
||||
0xa1, 0xa2, 0x11, 0x40, 0x89, 0xd2, 0xb2, 0xb9, 0x81, 0x84, 0x66, 0x73, 0x29, 0x11, 0xfb, 0xeb, 0xa6, 0x6a, 0x24,
|
||||
0xf5, 0xd2, 0x07, 0xff, 0x4b, 0xa1, 0x96, 0x4c, 0xa8, 0xa2, 0x91, 0x75, 0x42, 0xbb, 0xe8, 0x5b, 0x8f, 0x36, 0x7a,
|
||||
0x4b, 0xcc, 0xc7, 0x3e, 0x38, 0x74, 0x1a, 0xc9, 0x67, 0x5c, 0x24, 0xd4, 0x68, 0x3c, 0x47, 0x23, 0xd7, 0xf6, 0x76,
|
||||
0xef, 0x3d, 0x33, 0xde, 0x0f, 0xfe, 0x34, 0xd6, 0xd7, 0xeb, 0x58, 0x2d, 0x66, 0x64, 0x55, 0x57, 0x42, 0x25, 0xb4,
|
||||
0xd4, 0xba, 0x8d, 0x3c, 0x6f, 0xb9, 0x5c, 0xba, 0xcb, 0x91, 0xdb, 0xc8, 0x99, 0x17, 0xfa, 0xbe, 0xef, 0x21, 0x07,
|
||||
0x25, 0x7d, 0x22, 0xd0, 0xf0, 0x84, 0x92, 0x12, 0xf8, 0xac, 0xd4, 0x1d, 0x9d, 0x3e, 0xda, 0xc0, 0x36, 0x36, 0x1c,
|
||||
0xe9, 0xf5, 0xb7, 0x03, 0x2b, 0xfc, 0xc0, 0x0a, 0xbc, 0x60, 0x16, 0xdd, 0xb9, 0x79, 0xd4, 0xb9, 0x79, 0xc6, 0x42,
|
||||
0x12, 0x12, 0xbf, 0xfb, 0x0d, 0x1d, 0x43, 0x0f, 0x2b, 0xe7, 0xde, 0x8a, 0x1c, 0xac, 0x0c, 0x55, 0x9f, 0x3a, 0xcf,
|
||||
0xf7, 0xb2, 0x81, 0xd9, 0x59, 0x04, 0xfe, 0xed, 0x86, 0x11, 0x78, 0x7b, 0x7a, 0xb8, 0x76, 0xc2, 0x2f, 0x87, 0x0c,
|
||||
0xc6, 0x5a, 0x19, 0x7c, 0x39, 0x65, 0x63, 0x32, 0x1e, 0x76, 0xc6, 0x8e, 0xa1, 0xf7, 0x2b, 0x32, 0x5e, 0x20, 0x47,
|
||||
0xed, 0x9c, 0x3a, 0x63, 0x36, 0x22, 0xa3, 0x01, 0x08, 0x52, 0xb8, 0x7d, 0x8a, 0x82, 0x07, 0x7b, 0xce, 0xe8, 0xc7,
|
||||
0x91, 0x97, 0x52, 0x3b, 0xa2, 0xf4, 0xd6, 0xf3, 0xe6, 0xd0, 0x73, 0xf7, 0x7b, 0x83, 0x39, 0x45, 0x29, 0x46, 0x06,
|
||||
0x74, 0x56, 0x5a, 0xd4, 0xc3, 0xc2, 0x2a, 0xf8, 0x0c, 0xb3, 0xbe, 0x11, 0xd4, 0x76, 0x75, 0x09, 0xc2, 0xda, 0x89,
|
||||
0x1a, 0x41, 0xe8, 0x4e, 0xac, 0xfb, 0x27, 0xda, 0xde, 0xec, 0xf3, 0x5f, 0x73, 0x8d, 0x65, 0xa6, 0x5d, 0x53, 0xb0,
|
||||
0xc7, 0xfb, 0xdd, 0x69, 0x93, 0xaf, 0x7f, 0x51, 0x1a, 0x65, 0xd0, 0xd7, 0x05, 0x17, 0x02, 0xe4, 0x15, 0xac, 0xf0,
|
||||
0xe6, 0x2e, 0x5e, 0xbe, 0x26, 0x2f, 0xf3, 0x5c, 0x82, 0x52, 0x11, 0xa1, 0xcf, 0x34, 0xd6, 0x40, 0xf6, 0xdf, 0x75,
|
||||
0x05, 0x77, 0x74, 0xfd, 0xc9, 0xdf, 0x70, 0xf2, 0x01, 0xf4, 0xb2, 0x91, 0x37, 0x83, 0x36, 0x03, 0x6d, 0x62, 0x2a,
|
||||
0x4c, 0x22, 0x4e, 0xd6, 0x2a, 0x57, 0x55, 0xd8, 0x3e, 0xac, 0xc0, 0x46, 0x3b, 0xed, 0xad, 0x57, 0x62, 0x17, 0xa8,
|
||||
0xeb, 0x38, 0xe7, 0x0b, 0x92, 0x55, 0xd8, 0x21, 0xb0, 0x5c, 0x7a, 0x55, 0x94, 0x3c, 0x20, 0xdd, 0x4f, 0x23, 0x32,
|
||||
0x94, 0xbe, 0x49, 0xe8, 0x4f, 0x3a, 0xc0, 0xab, 0xf5, 0xbb, 0xdc, 0x3a, 0x52, 0x58, 0xfb, 0x47, 0xb6, 0xbb, 0x60,
|
||||
0xd5, 0x1c, 0x48, 0x42, 0x74, 0xc9, 0xd5, 0x2d, 0xc0, 0xc9, 0x2f, 0xc5, 0x5a, 0x75, 0x83, 0x52, 0x05, 0x1e, 0x2b,
|
||||
0xcb, 0xa6, 0xe9, 0x60, 0x2e, 0x66, 0x7d, 0x83, 0xa4, 0x0f, 0xe9, 0x3d, 0x44, 0x4e, 0x05, 0x85, 0xde, 0xf3, 0x11,
|
||||
0x2c, 0x3b, 0x65, 0x09, 0x57, 0xa2, 0x75, 0x7b, 0xbb, 0xdf, 0x8c, 0x55, 0xcb, 0xc4, 0x7d, 0x41, 0x03, 0xd0, 0x94,
|
||||
0x0a, 0x36, 0x36, 0xa4, 0x4c, 0xbd, 0x20, 0xd3, 0xde, 0xa0, 0xc7, 0x76, 0xe4, 0xa3, 0x0d, 0x47, 0x8d, 0xa6, 0x5f,
|
||||
0xed, 0x35, 0xc6, 0x1e, 0x86, 0x26, 0xbd, 0xde, 0xda, 0xb7, 0x7e, 0xfc, 0x35, 0x07, 0xb9, 0xbe, 0x84, 0x0a, 0x32,
|
||||
0xdd, 0x48, 0x8b, 0x3e, 0x44, 0x2b, 0x98, 0x4a, 0x9d, 0xc3, 0x6f, 0xaf, 0x2e, 0xde, 0x27, 0x8d, 0x25, 0xed, 0xe3,
|
||||
0x5f, 0x71, 0x9b, 0x51, 0xf0, 0x15, 0x47, 0xc1, 0xdf, 0xc9, 0x91, 0x19, 0x06, 0x47, 0xdf, 0x50, 0xb4, 0xf3, 0xf7,
|
||||
0xfa, 0x76, 0x22, 0x98, 0x72, 0x7e, 0x86, 0x2d, 0xe1, 0xd8, 0x78, 0xe8, 0x9c, 0x8e, 0xed, 0x2d, 0xda, 0x47, 0x04,
|
||||
0x88, 0xbb, 0xeb, 0xeb, 0xd8, 0xdf, 0x4d, 0x8b, 0x4d, 0x9f, 0x6e, 0xa6, 0xcd, 0xca, 0x51, 0xfc, 0x07, 0x17, 0xb3,
|
||||
0x88, 0x8b, 0x12, 0x24, 0xd7, 0x5b, 0x84, 0x8b, 0x13, 0xa2, 0x9d, 0xeb, 0x4d, 0xcb, 0xf2, 0xdc, 0x9c, 0x8c, 0xdb,
|
||||
0xd5, 0xa4, 0xc0, 0x79, 0x62, 0x38, 0x21, 0x0a, 0xa0, 0xde, 0xf6, 0xe7, 0x5d, 0x47, 0x89, 0x9e, 0x8f, 0x1f, 0x6f,
|
||||
0x4d, 0xc2, 0x6d, 0x34, 0x5e, 0x96, 0xc3, 0x2a, 0x3e, 0x13, 0x51, 0x86, 0xc0, 0x41, 0xf6, 0x42, 0x05, 0xab, 0x79,
|
||||
0xb5, 0x8e, 0x14, 0xf6, 0x36, 0x07, 0x07, 0x0d, 0x2f, 0xb6, 0xd3, 0xb9, 0xd6, 0x8d, 0x40, 0xdb, 0x32, 0x07, 0x19,
|
||||
0xf9, 0x93, 0x9e, 0x70, 0x24, 0xcb, 0xf9, 0x5c, 0x45, 0xee, 0x48, 0x42, 0x3d, 0x99, 0xb2, 0xec, 0x66, 0x26, 0x9b,
|
||||
0xb9, 0xc8, 0x9d, 0xcc, 0x74, 0xda, 0xe8, 0x61, 0x50, 0xb0, 0x11, 0x64, 0x93, 0x61, 0x55, 0x14, 0xc5, 0x04, 0x43,
|
||||
0x01, 0x4e, 0xdf, 0xcb, 0xa2, 0xd0, 0x3d, 0x31, 0x62, 0x07, 0x30, 0xdd, 0xd0, 0x6c, 0xf4, 0x18, 0x71, 0x04, 0x3c,
|
||||
0x9e, 0xec, 0xdc, 0xf1, 0x27, 0xd8, 0xc2, 0x15, 0x2a, 0x69, 0xb1, 0xb6, 0x11, 0xe6, 0xb6, 0x66, 0x5c, 0x1c, 0xa2,
|
||||
0x37, 0x69, 0x32, 0x19, 0xc6, 0x0f, 0x86, 0xa5, 0x33, 0xd3, 0x0d, 0xa1, 0x09, 0x0e, 0x98, 0x7e, 0x86, 0x46, 0xe1,
|
||||
0xa9, 0xdf, 0xae, 0xb6, 0xee, 0x90, 0x20, 0x9b, 0x1d, 0x77, 0x51, 0xc1, 0x6a, 0xf2, 0x7d, 0xae, 0x34, 0x2f, 0xd6,
|
||||
0xce, 0x30, 0x83, 0x23, 0x4c, 0x16, 0x9c, 0xbd, 0x53, 0x64, 0x05, 0x10, 0x93, 0xce, 0x86, 0xc3, 0x35, 0xd4, 0x6a,
|
||||
0x88, 0xd3, 0x5e, 0x4d, 0x97, 0xa0, 0x77, 0x75, 0xfd, 0x1b, 0xb7, 0xc9, 0xc5, 0x4d, 0xcd, 0x24, 0x8e, 0x0a, 0x67,
|
||||
0xda, 0x60, 0x4c, 0xeb, 0xc8, 0x39, 0xc3, 0xbb, 0x1a, 0xb6, 0x8c, 0x32, 0xf4, 0x1c, 0x61, 0x76, 0xb3, 0x75, 0x17,
|
||||
0xef, 0xa0, 0x5d, 0x11, 0xd5, 0x54, 0x3c, 0x1f, 0xf8, 0x3a, 0x16, 0xe2, 0xef, 0xc3, 0x13, 0xe0, 0x75, 0x13, 0xb3,
|
||||
0xb7, 0x0b, 0xf5, 0x49, 0x71, 0xce, 0x02, 0xff, 0x27, 0x37, 0x92, 0x17, 0x45, 0x38, 0x2d, 0xf6, 0x91, 0x32, 0x63,
|
||||
0xd2, 0x94, 0x46, 0x97, 0x5a, 0xb1, 0xd7, 0xbf, 0x66, 0x4c, 0x66, 0xe0, 0x03, 0x05, 0x23, 0x8c, 0xef, 0x9b, 0x80,
|
||||
0xf0, 0x3c, 0xc1, 0x4e, 0x95, 0x1e, 0xb4, 0x2f, 0x64, 0x0c, 0x76, 0x47, 0x48, 0xdd, 0x69, 0x46, 0xfd, 0x59, 0x87,
|
||||
0x3e, 0x7d, 0xdd, 0x60, 0x7d, 0x60, 0xdb, 0x11, 0x33, 0xa2, 0x1b, 0x32, 0x84, 0xc0, 0x75, 0xdd, 0x78, 0x2a, 0xd3,
|
||||
0x4f, 0x15, 0x30, 0x05, 0x64, 0xc9, 0xb8, 0x76, 0xb1, 0x1a, 0x3b, 0xfe, 0xbe, 0x8e, 0x51, 0x29, 0xb2, 0xa6, 0x43,
|
||||
0xc1, 0xc6, 0xe5, 0xa8, 0x37, 0x70, 0x09, 0xda, 0x68, 0x32, 0x06, 0x46, 0x69, 0x6c, 0x46, 0x2e, 0x61, 0x5d, 0x4b,
|
||||
0x4b, 0xbc, 0x25, 0x2f, 0xb8, 0x79, 0xb2, 0xa4, 0x71, 0x97, 0xe4, 0x46, 0x83, 0x89, 0x73, 0xff, 0xbc, 0xea, 0xa8,
|
||||
0x0a, 0xc4, 0x0c, 0x27, 0xe9, 0x28, 0x24, 0xe8, 0x76, 0x06, 0x65, 0x53, 0x61, 0x58, 0x93, 0xcb, 0xcb, 0x77, 0xbf,
|
||||
0xa7, 0x06, 0xcc, 0xad, 0x1c, 0xf6, 0xa7, 0x5e, 0xcc, 0x10, 0x83, 0xd4, 0xe9, 0x49, 0xff, 0xa8, 0x6a, 0xb1, 0xbf,
|
||||
0xa0, 0x07, 0xf9, 0x1d, 0x1d, 0x9f, 0x86, 0xcd, 0x5e, 0x4f, 0xf7, 0xd7, 0x95, 0x4a, 0x7a, 0x89, 0x80, 0x62, 0x6f,
|
||||
0x58, 0xc4, 0x9e, 0x01, 0xdc, 0x9f, 0x97, 0x03, 0x1f, 0xc6, 0xe9, 0xe3, 0xd5, 0x4b, 0xf2, 0xb9, 0xc5, 0x26, 0x00,
|
||||
0x7d, 0xd8, 0x3a, 0xaf, 0xf0, 0x65, 0x58, 0x36, 0x79, 0xf2, 0xe9, 0xe3, 0xe5, 0xd5, 0xde, 0xc3, 0x79, 0xc7, 0x44,
|
||||
0x40, 0x64, 0xfd, 0xf3, 0x6e, 0x5e, 0x69, 0xde, 0x32, 0xa9, 0x3b, 0xb5, 0x8e, 0xe9, 0x22, 0x3b, 0x1f, 0xba, 0x73,
|
||||
0x7c, 0x03, 0x41, 0xef, 0x46, 0x2f, 0x98, 0x92, 0x1d, 0xaa, 0x9d, 0xb5, 0x7b, 0xb8, 0xbc, 0xfe, 0xb6, 0xbd, 0xfe,
|
||||
0xea, 0xbd, 0xee, 0xa5, 0xfb, 0x0f, 0x53, 0x58, 0x46, 0xb2, 0xf9, 0x0a, 0x00, 0x00};
|
||||
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,
|
||||
0x7e, 0xa0, 0x24, 0x7b, 0x9d, 0x45, 0x73, 0xb8, 0xb3, 0x60, 0x61, 0x38, 0xef, 0x19, 0xcd, 0x83, 0xc5, 0xdf, 0x2a,
|
||||
0xc5, 0xec, 0xbe, 0x03, 0xd4, 0xd8, 0x56, 0x94, 0x85, 0x7b, 0x23, 0x41, 0xe5, 0x8a, 0x80, 0x2c, 0x8b, 0x06, 0x68,
|
||||
0x55, 0x16, 0x2d, 0x58, 0x8a, 0x58, 0x43, 0xb5, 0x01, 0x4b, 0xfe, 0xbc, 0xff, 0x39, 0xb8, 0x2d, 0x0b, 0xc1, 0xe5,
|
||||
0x1a, 0x69, 0x10, 0x84, 0x33, 0x25, 0x51, 0xa3, 0xa1, 0x26, 0x15, 0xb5, 0x34, 0xe3, 0x2d, 0x5d, 0xc1, 0x24, 0x22,
|
||||
0x69, 0x0b, 0x64, 0xc3, 0x61, 0xdb, 0x29, 0x6d, 0x11, 0x53, 0xd2, 0x82, 0xb4, 0x04, 0x6f, 0x79, 0x65, 0x1b, 0x52,
|
||||
0xc1, 0x86, 0x33, 0x08, 0x86, 0xc3, 0x35, 0x97, 0xdc, 0x72, 0x2a, 0x02, 0xc3, 0xa8, 0x00, 0x92, 0x5c, 0xf7, 0x06,
|
||||
0xf4, 0x70, 0xa0, 0x4b, 0x01, 0x44, 0x2a, 0x5c, 0x16, 0x86, 0x69, 0xde, 0x59, 0xe4, 0x5c, 0x25, 0xad, 0xaa, 0x7a,
|
||||
0x01, 0x65, 0x14, 0x51, 0x63, 0xc0, 0x9a, 0x88, 0xcb, 0x0a, 0x76, 0xe1, 0x32, 0x66, 0x2c, 0x86, 0xdb, 0xdb, 0xf0,
|
||||
0xb3, 0x79, 0x56, 0x29, 0xd6, 0xb7, 0x20, 0x6d, 0x28, 0x14, 0xa3, 0x96, 0x2b, 0x19, 0x1a, 0xa0, 0x9a, 0x35, 0x84,
|
||||
0x10, 0xfc, 0xa3, 0xa1, 0x1b, 0xc0, 0xdf, 0x7f, 0xef, 0x9d, 0x99, 0x56, 0x60, 0xff, 0x21, 0xc0, 0x81, 0xe6, 0xa7,
|
||||
0xfd, 0x3d, 0x5d, 0xfd, 0x4e, 0x5b, 0xf0, 0x30, 0x35, 0xbc, 0x02, 0xec, 0x7f, 0x8c, 0x3f, 0x85, 0xc6, 0xee, 0x05,
|
||||
0x84, 0x15, 0x37, 0x9d, 0xa0, 0x7b, 0x82, 0x97, 0x42, 0xb1, 0x35, 0xf6, 0xf3, 0xba, 0x97, 0xcc, 0x29, 0x47, 0xc6,
|
||||
0x03, 0xff, 0x20, 0xc0, 0x22, 0x4b, 0xde, 0x51, 0xdb, 0x84, 0x2d, 0xdd, 0x79, 0x23, 0xc0, 0xa5, 0x97, 0xfe, 0xe0,
|
||||
0xc1, 0xcb, 0x24, 0x8e, 0xfd, 0xeb, 0xe1, 0x15, 0xfb, 0x51, 0x12, 0xc7, 0xb9, 0x06, 0xdb, 0x6b, 0x89, 0xa8, 0xf7,
|
||||
0x50, 0x74, 0xd4, 0x36, 0xa8, 0x22, 0xf8, 0x5d, 0x92, 0xa2, 0xe4, 0x75, 0x98, 0xce, 0x7f, 0x0b, 0x5f, 0xa1, 0x9b,
|
||||
0x30, 0x9d, 0xb3, 0x57, 0xc1, 0x1c, 0x25, 0x37, 0xc1, 0x1c, 0xa5, 0x69, 0x38, 0x47, 0xf1, 0x17, 0x8c, 0x6a, 0x2e,
|
||||
0x04, 0xc1, 0x52, 0x49, 0xc0, 0xc8, 0x58, 0xad, 0xd6, 0x40, 0x30, 0xeb, 0xb5, 0x06, 0x69, 0xdf, 0x2a, 0xa1, 0x34,
|
||||
0x8e, 0xca, 0x67, 0xff, 0x97, 0x42, 0xab, 0xa9, 0x34, 0xb5, 0xd2, 0x2d, 0xc1, 0x43, 0xf6, 0xbd, 0x17, 0x07, 0x7b,
|
||||
0x44, 0xee, 0xe5, 0x5f, 0x10, 0x03, 0xa5, 0xf9, 0x8a, 0x4b, 0x82, 0x9d, 0xc6, 0x5b, 0x1c, 0x95, 0x0f, 0xfe, 0xf1,
|
||||
0x1c, 0x3d, 0x75, 0xd1, 0x4f, 0xf1, 0x28, 0xef, 0xe3, 0x43, 0x61, 0x36, 0x2b, 0xb4, 0x6b, 0x85, 0x34, 0x04, 0x37,
|
||||
0xd6, 0x76, 0x59, 0x14, 0x6d, 0xb7, 0xdb, 0x70, 0x3b, 0x0b, 0x95, 0x5e, 0x45, 0x69, 0x1c, 0xc7, 0x91, 0xd9, 0xac,
|
||||
0x30, 0x1a, 0x0b, 0x01, 0xa7, 0x37, 0x18, 0x35, 0xc0, 0x57, 0x8d, 0x1d, 0xe0, 0xf2, 0xc5, 0x01, 0x8e, 0x85, 0xe3,
|
||||
0x28, 0x1f, 0x3e, 0x5d, 0x58, 0xe1, 0x17, 0x56, 0xe0, 0x47, 0xea, 0xe1, 0x53, 0x98, 0x57, 0x43, 0x98, 0xaf, 0x68,
|
||||
0x8a, 0x52, 0x14, 0x0f, 0x4f, 0x1a, 0x38, 0x78, 0x3a, 0x05, 0x4f, 0x4e, 0xe8, 0xe2, 0xe4, 0xa0, 0x76, 0x11, 0xbc,
|
||||
0x3e, 0xcb, 0x26, 0x0e, 0xb3, 0x49, 0xe2, 0x47, 0x84, 0x13, 0xf8, 0x65, 0x71, 0x79, 0x0e, 0xd2, 0x0f, 0x97, 0x0c,
|
||||
0xce, 0x5a, 0x93, 0x7c, 0x58, 0xd0, 0x39, 0x9a, 0x4f, 0x98, 0x79, 0xe0, 0xe0, 0xf3, 0x09, 0xcd, 0x37, 0x69, 0x93,
|
||||
0xb4, 0xc1, 0x22, 0x98, 0xd3, 0x19, 0x9a, 0x4d, 0x8e, 0xcc, 0xd0, 0x6c, 0x93, 0x36, 0x8b, 0x0f, 0x8b, 0x4b, 0x5c,
|
||||
0x30, 0xfb, 0x72, 0x15, 0x95, 0xd8, 0xcf, 0x30, 0x7e, 0x8c, 0x5c, 0x5d, 0x46, 0x1e, 0x7e, 0x56, 0x5c, 0x7a, 0x18,
|
||||
0xfb, 0xc7, 0x1a, 0x2c, 0x6b, 0x3c, 0x1c, 0x31, 0x25, 0x6b, 0xbe, 0x0a, 0x3f, 0x1b, 0x25, 0xb1, 0x1f, 0xda, 0x06,
|
||||
0xa4, 0x77, 0x12, 0x75, 0x82, 0x30, 0x50, 0xbc, 0xa7, 0x14, 0xeb, 0x1f, 0xce, 0xf5, 0x6f, 0xb9, 0x15, 0x40, 0x6c,
|
||||
0xe8, 0x1a, 0xf6, 0xfa, 0x8c, 0x5d, 0xaa, 0x6a, 0xff, 0x8d, 0xd6, 0x68, 0x92, 0xb1, 0x2f, 0xb8, 0x94, 0xa0, 0xef,
|
||||
0x61, 0x67, 0x09, 0x7e, 0xf7, 0xe6, 0x2d, 0x7a, 0x53, 0x55, 0x1a, 0x8c, 0xc9, 0x10, 0x7e, 0x69, 0xc3, 0x96, 0xb2,
|
||||
0xff, 0x5d, 0x57, 0xf2, 0x95, 0xae, 0x7f, 0xf2, 0x9f, 0x39, 0xfa, 0x1d, 0xec, 0x56, 0xe9, 0xf5, 0xa4, 0xcd, 0xb9,
|
||||
0x96, 0xbb, 0x0e, 0xd3, 0xc4, 0x86, 0xb4, 0x33, 0xa1, 0x11, 0x9c, 0x81, 0x97, 0xf8, 0x61, 0x4b, 0xbb, 0xc7, 0xa8,
|
||||
0xe4, 0x29, 0x51, 0x0f, 0x45, 0xc5, 0x37, 0x88, 0x09, 0x6a, 0x0c, 0xc1, 0x72, 0x54, 0x85, 0xd1, 0x33, 0x34, 0xfc,
|
||||
0x94, 0x64, 0x82, 0xb3, 0x35, 0xc1, 0x7f, 0x31, 0x01, 0x7e, 0xda, 0xff, 0x5a, 0x79, 0x57, 0xc6, 0xf0, 0xea, 0xca,
|
||||
0x0f, 0x37, 0x54, 0xf4, 0x80, 0x08, 0xb2, 0x0d, 0x37, 0x8f, 0x0e, 0xe6, 0xdf, 0x14, 0xeb, 0xcc, 0xfa, 0xca, 0x0f,
|
||||
0x6b, 0xc5, 0x7a, 0xe3, 0xf9, 0xb8, 0x9c, 0xcc, 0x15, 0x74, 0x1c, 0x90, 0xf8, 0x39, 0x7e, 0xe2, 0x51, 0x20, 0xa0,
|
||||
0xb6, 0x67, 0x3e, 0x84, 0x5e, 0x1c, 0x8c, 0x27, 0x43, 0x6d, 0x0c, 0xf7, 0x8f, 0x67, 0x64, 0x61, 0x3a, 0x2a, 0x9f,
|
||||
0x0a, 0x3a, 0x07, 0x5d, 0xab, 0xc8, 0xd0, 0x41, 0xae, 0x5f, 0x3a, 0x2a, 0xcf, 0x06, 0x23, 0x7a, 0x02, 0x5f, 0x1c,
|
||||
0xb8, 0x27, 0xdd, 0x14, 0x5c, 0x9f, 0x35, 0x16, 0x51, 0xc5, 0x37, 0xe5, 0xc3, 0xd1, 0x7f, 0x8c, 0xe3, 0x5f, 0x3d,
|
||||
0xe8, 0xfd, 0x1d, 0x08, 0x60, 0x56, 0x69, 0x0f, 0x3f, 0x97, 0x60, 0xb1, 0x3f, 0x06, 0xfc, 0xcb, 0xfd, 0xbb, 0xdf,
|
||||
0x88, 0xf2, 0xb4, 0x7f, 0xfd, 0x2d, 0x6e, 0xb7, 0x0a, 0x3e, 0x6a, 0x10, 0xff, 0x26, 0x57, 0x6e, 0x19, 0x5c, 0x7d,
|
||||
0xc2, 0x7e, 0x38, 0xc4, 0xfb, 0xf0, 0xb8, 0x11, 0x5c, 0x3b, 0xbf, 0xdc, 0xb5, 0xe2, 0xda, 0x45, 0x18, 0x2c, 0xe6,
|
||||
0xfe, 0xf1, 0xe1, 0xe8, 0x1f, 0xfd, 0xbc, 0x88, 0xc6, 0xb9, 0x5e, 0x16, 0xc3, 0x88, 0x2d, 0x7f, 0x38, 0x2c, 0xd5,
|
||||
0x2e, 0x30, 0xfc, 0x0b, 0x97, 0xab, 0x8c, 0xcb, 0x06, 0x34, 0xb7, 0xc7, 0x8a, 0x6f, 0xae, 0xb9, 0xec, 0x7a, 0x7b,
|
||||
0xe8, 0x68, 0x55, 0x39, 0xca, 0xbc, 0xdb, 0xe5, 0xb5, 0x92, 0xd6, 0x71, 0x42, 0x96, 0x40, 0x7b, 0x1c, 0xe9, 0xc3,
|
||||
0x44, 0xc9, 0x5e, 0xcf, 0xbf, 0x3b, 0xba, 0x82, 0x3b, 0x58, 0xd8, 0xd9, 0x80, 0x0a, 0xbe, 0x92, 0x19, 0x03, 0x69,
|
||||
0x41, 0x8f, 0x42, 0x35, 0x6d, 0xb9, 0xd8, 0x67, 0x86, 0x4a, 0x13, 0x18, 0xd0, 0xbc, 0x3e, 0x2e, 0x7b, 0x6b, 0x95,
|
||||
0x3c, 0x2c, 0x95, 0xae, 0x40, 0x67, 0x71, 0x3e, 0x02, 0x81, 0xa6, 0x15, 0xef, 0x4d, 0x16, 0xce, 0x34, 0xb4, 0xf9,
|
||||
0x92, 0xb2, 0xf5, 0x4a, 0xab, 0x5e, 0x56, 0x01, 0x73, 0x93, 0x36, 0x7b, 0x9e, 0xd4, 0x74, 0x06, 0x2c, 0x9f, 0x4e,
|
||||
0x75, 0x5d, 0xe7, 0x82, 0x4b, 0x08, 0xc6, 0x59, 0x96, 0xa5, 0xe1, 0x8d, 0x13, 0xbb, 0x70, 0x33, 0x4c, 0x1d, 0x62,
|
||||
0xf4, 0x31, 0x89, 0xe3, 0xef, 0xf2, 0x53, 0x38, 0x71, 0xce, 0x7a, 0x6d, 0x94, 0xce, 0x3a, 0xc5, 0x9d, 0x9b, 0xc7,
|
||||
0x96, 0x72, 0x79, 0xe9, 0xbd, 0x2b, 0x93, 0x7c, 0x5a, 0x3f, 0x19, 0x97, 0x83, 0x99, 0x61, 0x09, 0xe5, 0x2d, 0x97,
|
||||
0xe3, 0x0e, 0xcd, 0xd2, 0x45, 0xdc, 0xed, 0x8e, 0xe1, 0x54, 0x20, 0x87, 0x13, 0x77, 0x2d, 0x60, 0x97, 0x7f, 0xee,
|
||||
0x8d, 0xe5, 0xf5, 0x3e, 0x98, 0x76, 0x70, 0x66, 0x3a, 0xca, 0x20, 0x58, 0x82, 0xdd, 0x02, 0xc8, 0x7c, 0xb0, 0x11,
|
||||
0x70, 0x0b, 0xad, 0x99, 0xf2, 0x74, 0x56, 0x33, 0x14, 0xe8, 0xd7, 0xba, 0xfe, 0x1b, 0xb7, 0xab, 0xc5, 0x43, 0x4b,
|
||||
0xf5, 0x8a, 0xcb, 0x60, 0xa9, 0xac, 0x55, 0x6d, 0x16, 0xbc, 0xea, 0x76, 0xf9, 0x84, 0x72, 0xca, 0xb2, 0xc4, 0xb9,
|
||||
0x39, 0xec, 0xd6, 0x53, 0xbe, 0x93, 0x6e, 0x87, 0x8c, 0x12, 0xbc, 0x9a, 0xf8, 0x06, 0x16, 0x14, 0x9f, 0xd3, 0x93,
|
||||
0xcc, 0xbb, 0x1d, 0x72, 0xb8, 0x53, 0xaa, 0x6f, 0xea, 0x5b, 0x9a, 0xc4, 0x7f, 0xf1, 0x45, 0xaa, 0xba, 0x4e, 0x97,
|
||||
0xf5, 0x39, 0x53, 0x6e, 0x4d, 0xba, 0xd6, 0x18, 0x4a, 0xab, 0x88, 0xc6, 0xdb, 0x8c, 0xab, 0x8c, 0xb2, 0x70, 0x19,
|
||||
0x2e, 0x8b, 0x26, 0x41, 0xbc, 0x22, 0x2d, 0x65, 0xe5, 0xc5, 0xf8, 0x2a, 0xa2, 0x26, 0x39, 0x91, 0x9a, 0xa4, 0xfc,
|
||||
0x6a, 0x18, 0x8d, 0xb4, 0xc1, 0xfb, 0xf2, 0xad, 0x92, 0x12, 0x98, 0xe5, 0x72, 0x85, 0xac, 0x42, 0x53, 0x0a, 0xc2,
|
||||
0x30, 0x2c, 0x96, 0xba, 0x7c, 0x2f, 0x80, 0x1a, 0x40, 0x5b, 0xca, 0x6d, 0x58, 0x44, 0x23, 0xff, 0xd8, 0xc7, 0xbc,
|
||||
0x22, 0x12, 0x6c, 0x39, 0x35, 0x6c, 0xd1, 0xcc, 0x46, 0x03, 0x77, 0x60, 0x9d, 0x26, 0x67, 0x60, 0x56, 0x16, 0x6e,
|
||||
0xe5, 0x22, 0x3a, 0x8c, 0x34, 0x12, 0x6d, 0x79, 0xcd, 0xdd, 0x95, 0xa5, 0x2c, 0x86, 0x22, 0x77, 0x1a, 0x5c, 0x9e,
|
||||
0xc7, 0xeb, 0xd5, 0x00, 0x09, 0x90, 0x2b, 0xdb, 0x90, 0x59, 0x8a, 0x3a, 0x41, 0x19, 0x34, 0x4a, 0x54, 0xa0, 0xc9,
|
||||
0xdd, 0xdd, 0xaf, 0x7f, 0x2f, 0x9d, 0x33, 0x8f, 0x72, 0x9d, 0x59, 0x8f, 0x62, 0x0e, 0x98, 0xa4, 0x16, 0x37, 0xe3,
|
||||
0xa5, 0xaa, 0xa3, 0xc6, 0x6c, 0x95, 0xae, 0xbe, 0xd2, 0xf1, 0x7e, 0x42, 0x8e, 0x7a, 0x86, 0xff, 0xd0, 0x2a, 0xe5,
|
||||
0x1d, 0xdd, 0x40, 0x11, 0x4d, 0x87, 0x22, 0x72, 0x0e, 0x8f, 0xf4, 0x66, 0xe2, 0x6b, 0x92, 0xf2, 0x8f, 0xfb, 0x37,
|
||||
0xe8, 0xcf, 0xae, 0xa2, 0x16, 0xc6, 0xb4, 0x0d, 0x51, 0xb5, 0x60, 0x1b, 0x55, 0x91, 0xf7, 0x7f, 0xdc, 0xdd, 0x9f,
|
||||
0x23, 0xec, 0x07, 0x26, 0x04, 0x92, 0x8d, 0xd7, 0xbb, 0x5e, 0x58, 0xde, 0x51, 0x6d, 0x07, 0xb5, 0x81, 0x9b, 0x22,
|
||||
0xa7, 0x18, 0x06, 0x7a, 0xcd, 0x05, 0x8c, 0x61, 0x8c, 0x82, 0x25, 0x3a, 0x79, 0x75, 0xb2, 0xf6, 0xc4, 0xaf, 0x68,
|
||||
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 = {
|
||||
|
||||
@@ -93,7 +93,9 @@ bool CH422GComponent::read_inputs_() {
|
||||
bool CH422GComponent::write_reg_(uint8_t reg, uint8_t value) {
|
||||
auto err = this->bus_->write_readv(reg, &value, 1, nullptr, 0);
|
||||
if (err != i2c::ERROR_OK) {
|
||||
this->status_set_warning(str_sprintf("write failed for register 0x%X, error %d", reg, err).c_str());
|
||||
char buf[64];
|
||||
snprintf(buf, sizeof(buf), "write failed for register 0x%X, error %d", reg, err);
|
||||
this->status_set_warning(buf);
|
||||
return false;
|
||||
}
|
||||
this->status_clear_warning();
|
||||
@@ -104,7 +106,9 @@ uint8_t CH422GComponent::read_reg_(uint8_t reg) {
|
||||
uint8_t value;
|
||||
auto err = this->bus_->write_readv(reg, nullptr, 0, &value, 1);
|
||||
if (err != i2c::ERROR_OK) {
|
||||
this->status_set_warning(str_sprintf("read failed for register 0x%X, error %d", reg, err).c_str());
|
||||
char buf[64];
|
||||
snprintf(buf, sizeof(buf), "read failed for register 0x%X, error %d", reg, err);
|
||||
this->status_set_warning(buf);
|
||||
return 0;
|
||||
}
|
||||
this->status_clear_warning();
|
||||
|
||||
@@ -682,19 +682,19 @@ bool Climate::set_fan_mode_(ClimateFanMode mode) {
|
||||
return set_primary_mode(this->fan_mode, this->custom_fan_mode_, mode);
|
||||
}
|
||||
|
||||
bool Climate::set_custom_fan_mode_(const char *mode) {
|
||||
bool Climate::set_custom_fan_mode_(const char *mode, size_t len) {
|
||||
auto traits = this->get_traits();
|
||||
return set_custom_mode<ClimateFanMode>(this->custom_fan_mode_, this->fan_mode, traits.find_custom_fan_mode_(mode),
|
||||
this->has_custom_fan_mode());
|
||||
return set_custom_mode<ClimateFanMode>(this->custom_fan_mode_, this->fan_mode,
|
||||
traits.find_custom_fan_mode_(mode, len), this->has_custom_fan_mode());
|
||||
}
|
||||
|
||||
void Climate::clear_custom_fan_mode_() { this->custom_fan_mode_ = nullptr; }
|
||||
|
||||
bool Climate::set_preset_(ClimatePreset preset) { return set_primary_mode(this->preset, this->custom_preset_, preset); }
|
||||
|
||||
bool Climate::set_custom_preset_(const char *preset) {
|
||||
bool Climate::set_custom_preset_(const char *preset, size_t len) {
|
||||
auto traits = this->get_traits();
|
||||
return set_custom_mode<ClimatePreset>(this->custom_preset_, this->preset, traits.find_custom_preset_(preset),
|
||||
return set_custom_mode<ClimatePreset>(this->custom_preset_, this->preset, traits.find_custom_preset_(preset, len),
|
||||
this->has_custom_preset());
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/preferences.h"
|
||||
#include "esphome/core/string_ref.h"
|
||||
#include "climate_mode.h"
|
||||
#include "climate_traits.h"
|
||||
|
||||
@@ -110,8 +111,8 @@ class ClimateCall {
|
||||
const optional<ClimateFanMode> &get_fan_mode() const;
|
||||
const optional<ClimateSwingMode> &get_swing_mode() const;
|
||||
const optional<ClimatePreset> &get_preset() const;
|
||||
const char *get_custom_fan_mode() const { return this->custom_fan_mode_; }
|
||||
const char *get_custom_preset() const { return this->custom_preset_; }
|
||||
StringRef get_custom_fan_mode() const { return StringRef::from_maybe_nullptr(this->custom_fan_mode_); }
|
||||
StringRef get_custom_preset() const { return StringRef::from_maybe_nullptr(this->custom_preset_); }
|
||||
bool has_custom_fan_mode() const { return this->custom_fan_mode_ != nullptr; }
|
||||
bool has_custom_preset() const { return this->custom_preset_ != nullptr; }
|
||||
|
||||
@@ -266,11 +267,11 @@ class Climate : public EntityBase {
|
||||
/// The active swing mode of the climate device.
|
||||
ClimateSwingMode swing_mode{CLIMATE_SWING_OFF};
|
||||
|
||||
/// Get the active custom fan mode (read-only access).
|
||||
const char *get_custom_fan_mode() const { return this->custom_fan_mode_; }
|
||||
/// Get the active custom fan mode (read-only access). Returns StringRef.
|
||||
StringRef get_custom_fan_mode() const { return StringRef::from_maybe_nullptr(this->custom_fan_mode_); }
|
||||
|
||||
/// Get the active custom preset (read-only access).
|
||||
const char *get_custom_preset() const { return this->custom_preset_; }
|
||||
/// Get the active custom preset (read-only access). Returns StringRef.
|
||||
StringRef get_custom_preset() const { return StringRef::from_maybe_nullptr(this->custom_preset_); }
|
||||
|
||||
protected:
|
||||
friend ClimateCall;
|
||||
@@ -280,7 +281,9 @@ class Climate : public EntityBase {
|
||||
bool set_fan_mode_(ClimateFanMode mode);
|
||||
|
||||
/// Set custom fan mode. Reset primary fan mode. Return true if fan mode has been changed.
|
||||
bool set_custom_fan_mode_(const char *mode);
|
||||
bool set_custom_fan_mode_(const char *mode) { return this->set_custom_fan_mode_(mode, strlen(mode)); }
|
||||
bool set_custom_fan_mode_(const char *mode, size_t len);
|
||||
bool set_custom_fan_mode_(StringRef mode) { return this->set_custom_fan_mode_(mode.c_str(), mode.size()); }
|
||||
/// Clear custom fan mode.
|
||||
void clear_custom_fan_mode_();
|
||||
|
||||
@@ -288,7 +291,9 @@ class Climate : public EntityBase {
|
||||
bool set_preset_(ClimatePreset preset);
|
||||
|
||||
/// Set custom preset. Reset primary preset. Return true if preset has been changed.
|
||||
bool set_custom_preset_(const char *preset);
|
||||
bool set_custom_preset_(const char *preset) { return this->set_custom_preset_(preset, strlen(preset)); }
|
||||
bool set_custom_preset_(const char *preset, size_t len);
|
||||
bool set_custom_preset_(StringRef preset) { return this->set_custom_preset_(preset.c_str(), preset.size()); }
|
||||
/// Clear custom preset.
|
||||
void clear_custom_preset_();
|
||||
|
||||
|
||||
@@ -8,20 +8,24 @@ static const char *const TAG = "copy.fan";
|
||||
|
||||
void CopyFan::setup() {
|
||||
source_->add_on_state_callback([this]() {
|
||||
this->state = source_->state;
|
||||
this->oscillating = source_->oscillating;
|
||||
this->speed = source_->speed;
|
||||
this->direction = source_->direction;
|
||||
this->set_preset_mode_(source_->get_preset_mode());
|
||||
this->copy_state_from_source_();
|
||||
this->publish_state();
|
||||
});
|
||||
|
||||
this->copy_state_from_source_();
|
||||
this->publish_state();
|
||||
}
|
||||
|
||||
void CopyFan::copy_state_from_source_() {
|
||||
this->state = source_->state;
|
||||
this->oscillating = source_->oscillating;
|
||||
this->speed = source_->speed;
|
||||
this->direction = source_->direction;
|
||||
this->set_preset_mode_(source_->get_preset_mode());
|
||||
this->publish_state();
|
||||
if (source_->has_preset_mode()) {
|
||||
this->set_preset_mode_(source_->get_preset_mode());
|
||||
} else {
|
||||
this->clear_preset_mode_();
|
||||
}
|
||||
}
|
||||
|
||||
void CopyFan::dump_config() { LOG_FAN("", "Copy Fan", this); }
|
||||
|
||||
@@ -16,7 +16,7 @@ class CopyFan : public fan::Fan, public Component {
|
||||
|
||||
protected:
|
||||
void control(const fan::FanCall &call) override;
|
||||
;
|
||||
void copy_state_from_source_();
|
||||
|
||||
fan::Fan *source_;
|
||||
};
|
||||
|
||||
@@ -322,6 +322,8 @@ size_t DebugComponent::get_device_info_(std::span<char, DEVICE_INFO_BUFFER_SIZE>
|
||||
return "Unspecified";
|
||||
};
|
||||
|
||||
char mac_pretty[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
|
||||
get_mac_address_pretty_into_buffer(mac_pretty);
|
||||
ESP_LOGD(TAG,
|
||||
"Code page size: %u, code size: %u, device id: 0x%08x%08x\n"
|
||||
"Encryption root: 0x%08x%08x%08x%08x, Identity Root: 0x%08x%08x%08x%08x\n"
|
||||
@@ -330,10 +332,10 @@ size_t DebugComponent::get_device_info_(std::span<char, DEVICE_INFO_BUFFER_SIZE>
|
||||
"RAM: %ukB, Flash: %ukB, production test: %sdone",
|
||||
NRF_FICR->CODEPAGESIZE, NRF_FICR->CODESIZE, NRF_FICR->DEVICEID[1], NRF_FICR->DEVICEID[0], NRF_FICR->ER[0],
|
||||
NRF_FICR->ER[1], NRF_FICR->ER[2], NRF_FICR->ER[3], NRF_FICR->IR[0], NRF_FICR->IR[1], NRF_FICR->IR[2],
|
||||
NRF_FICR->IR[3], (NRF_FICR->DEVICEADDRTYPE & 0x1 ? "Random" : "Public"), get_mac_address_pretty().c_str(),
|
||||
NRF_FICR->INFO.PART, NRF_FICR->INFO.VARIANT >> 24 & 0xFF, NRF_FICR->INFO.VARIANT >> 16 & 0xFF,
|
||||
NRF_FICR->INFO.VARIANT >> 8 & 0xFF, NRF_FICR->INFO.VARIANT & 0xFF, package(NRF_FICR->INFO.PACKAGE),
|
||||
NRF_FICR->INFO.RAM, NRF_FICR->INFO.FLASH, (NRF_FICR->PRODTEST[0] == 0xBB42319F ? "" : "not "));
|
||||
NRF_FICR->IR[3], (NRF_FICR->DEVICEADDRTYPE & 0x1 ? "Random" : "Public"), mac_pretty, NRF_FICR->INFO.PART,
|
||||
NRF_FICR->INFO.VARIANT >> 24 & 0xFF, NRF_FICR->INFO.VARIANT >> 16 & 0xFF, NRF_FICR->INFO.VARIANT >> 8 & 0xFF,
|
||||
NRF_FICR->INFO.VARIANT & 0xFF, package(NRF_FICR->INFO.PACKAGE), NRF_FICR->INFO.RAM, NRF_FICR->INFO.FLASH,
|
||||
(NRF_FICR->PRODTEST[0] == 0xBB42319F ? "" : "not "));
|
||||
bool n_reset_enabled = NRF_UICR->PSELRESET[0] == NRF_UICR->PSELRESET[1] &&
|
||||
(NRF_UICR->PSELRESET[0] & UICR_PSELRESET_CONNECT_Msk) == UICR_PSELRESET_CONNECT_Connected
|
||||
<< UICR_PSELRESET_CONNECT_Pos;
|
||||
|
||||
@@ -26,7 +26,7 @@ namespace deep_sleep {
|
||||
// - ext0: Single pin wakeup using RTC GPIO (esp_sleep_enable_ext0_wakeup)
|
||||
// - ext1: Multiple pin wakeup (esp_sleep_enable_ext1_wakeup)
|
||||
// - Touch: Touch pad wakeup (esp_sleep_enable_touchpad_wakeup)
|
||||
// - GPIO wakeup: GPIO wakeup for non-RTC pins (esp_deep_sleep_enable_gpio_wakeup)
|
||||
// - GPIO wakeup: GPIO wakeup for RTC pins (esp_deep_sleep_enable_gpio_wakeup)
|
||||
|
||||
static const char *const TAG = "deep_sleep";
|
||||
|
||||
@@ -127,22 +127,14 @@ void DeepSleepComponent::deep_sleep_() {
|
||||
defined(USE_ESP32_VARIANT_ESP32C61)
|
||||
if (this->wakeup_pin_ != nullptr) {
|
||||
const auto gpio_pin = gpio_num_t(this->wakeup_pin_->get_pin());
|
||||
if (this->wakeup_pin_->get_flags() & gpio::FLAG_PULLUP) {
|
||||
gpio_sleep_set_pull_mode(gpio_pin, GPIO_PULLUP_ONLY);
|
||||
} else if (this->wakeup_pin_->get_flags() & gpio::FLAG_PULLDOWN) {
|
||||
gpio_sleep_set_pull_mode(gpio_pin, GPIO_PULLDOWN_ONLY);
|
||||
}
|
||||
gpio_sleep_set_direction(gpio_pin, GPIO_MODE_INPUT);
|
||||
gpio_hold_en(gpio_pin);
|
||||
#if !SOC_GPIO_SUPPORT_HOLD_SINGLE_IO_IN_DSLP
|
||||
// Some ESP32 variants support holding a single GPIO during deep sleep without this function
|
||||
// For those variants, gpio_hold_en() is sufficient to hold the pin state during deep sleep
|
||||
gpio_deep_sleep_hold_en();
|
||||
#endif
|
||||
// Make sure GPIO is in input mode, not all RTC GPIO pins are input by default
|
||||
gpio_set_direction(gpio_pin, GPIO_MODE_INPUT);
|
||||
bool level = !this->wakeup_pin_->is_inverted();
|
||||
if (this->wakeup_pin_mode_ == WAKEUP_PIN_MODE_INVERT_WAKEUP && this->wakeup_pin_->digital_read()) {
|
||||
level = !level;
|
||||
}
|
||||
// Internal pullup/pulldown resistors are enabled automatically, when
|
||||
// ESP_SLEEP_GPIO_ENABLE_INTERNAL_RESISTORS is set (by default it is)
|
||||
esp_deep_sleep_enable_gpio_wakeup(1 << this->wakeup_pin_->get_pin(),
|
||||
static_cast<esp_deepsleep_gpio_wake_up_mode_t>(level));
|
||||
}
|
||||
|
||||
@@ -42,7 +42,8 @@ std::string MenuItemSelect::get_value_text() const {
|
||||
result = this->value_getter_.value()(this);
|
||||
} else {
|
||||
if (this->select_var_ != nullptr) {
|
||||
result = this->select_var_->current_option();
|
||||
auto option = this->select_var_->current_option();
|
||||
result.assign(option.c_str(), option.size());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -82,8 +82,9 @@ void E131Component::add_effect(E131AddressableLightEffect *light_effect) {
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, "Registering '%s' for universes %d-%d.", light_effect->get_name(), light_effect->get_first_universe(),
|
||||
light_effect->get_last_universe());
|
||||
auto effect_name = light_effect->get_name();
|
||||
ESP_LOGD(TAG, "Registering '%.*s' for universes %d-%d.", (int) effect_name.size(), effect_name.c_str(),
|
||||
light_effect->get_first_universe(), light_effect->get_last_universe());
|
||||
|
||||
light_effects_.push_back(light_effect);
|
||||
|
||||
@@ -98,8 +99,9 @@ void E131Component::remove_effect(E131AddressableLightEffect *light_effect) {
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, "Unregistering '%s' for universes %d-%d.", light_effect->get_name(), light_effect->get_first_universe(),
|
||||
light_effect->get_last_universe());
|
||||
auto effect_name = light_effect->get_name();
|
||||
ESP_LOGD(TAG, "Unregistering '%.*s' for universes %d-%d.", (int) effect_name.size(), effect_name.c_str(),
|
||||
light_effect->get_first_universe(), light_effect->get_last_universe());
|
||||
|
||||
// Swap with last element and pop for O(1) removal (order doesn't matter)
|
||||
*it = light_effects_.back();
|
||||
|
||||
@@ -58,8 +58,9 @@ bool E131AddressableLightEffect::process_(int universe, const E131Packet &packet
|
||||
std::min(it->size(), std::min(output_offset + get_lights_per_universe(), output_offset + packet.count - 1));
|
||||
auto *input_data = packet.values + 1;
|
||||
|
||||
ESP_LOGV(TAG, "Applying data for '%s' on %d universe, for %" PRId32 "-%d.", get_name(), universe, output_offset,
|
||||
output_end);
|
||||
auto effect_name = get_name();
|
||||
ESP_LOGV(TAG, "Applying data for '%.*s' on %d universe, for %" PRId32 "-%d.", (int) effect_name.size(),
|
||||
effect_name.c_str(), universe, output_offset, output_end);
|
||||
|
||||
switch (channels_) {
|
||||
case E131_MONO:
|
||||
|
||||
@@ -19,6 +19,7 @@ from esphome.components.esp32 import (
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
CONF_MODE,
|
||||
CONF_RX_PIN,
|
||||
CONF_RX_QUEUE_LEN,
|
||||
CONF_TX_PIN,
|
||||
@@ -33,6 +34,13 @@ CONF_TX_ENQUEUE_TIMEOUT = "tx_enqueue_timeout"
|
||||
esp32_can_ns = cg.esphome_ns.namespace("esp32_can")
|
||||
esp32_can = esp32_can_ns.class_("ESP32Can", CanbusComponent)
|
||||
|
||||
# Mode options - consistent with MCP2515 component
|
||||
CanMode = esp32_can_ns.enum("CanMode")
|
||||
CAN_MODES = {
|
||||
"NORMAL": CanMode.CAN_MODE_NORMAL,
|
||||
"LISTENONLY": CanMode.CAN_MODE_LISTEN_ONLY,
|
||||
}
|
||||
|
||||
# Currently the driver only supports a subset of the bit rates defined in canbus
|
||||
# The supported bit rates differ between ESP32 variants.
|
||||
# See ESP-IDF Programming Guide --> API Reference --> Two-Wire Automotive Interface (TWAI)
|
||||
@@ -95,6 +103,7 @@ CONFIG_SCHEMA = canbus.CANBUS_SCHEMA.extend(
|
||||
cv.Optional(CONF_BIT_RATE, default="125KBPS"): validate_bit_rate,
|
||||
cv.Required(CONF_RX_PIN): pins.internal_gpio_input_pin_number,
|
||||
cv.Required(CONF_TX_PIN): pins.internal_gpio_output_pin_number,
|
||||
cv.Optional(CONF_MODE, default="NORMAL"): cv.enum(CAN_MODES, upper=True),
|
||||
cv.Optional(CONF_RX_QUEUE_LEN): cv.uint32_t,
|
||||
cv.Optional(CONF_TX_QUEUE_LEN): cv.uint32_t,
|
||||
cv.Optional(CONF_TX_ENQUEUE_TIMEOUT): cv.positive_time_period_milliseconds,
|
||||
@@ -117,6 +126,7 @@ async def to_code(config):
|
||||
|
||||
cg.add(var.set_rx(config[CONF_RX_PIN]))
|
||||
cg.add(var.set_tx(config[CONF_TX_PIN]))
|
||||
cg.add(var.set_mode(config[CONF_MODE]))
|
||||
if (rx_queue_len := config.get(CONF_RX_QUEUE_LEN)) is not None:
|
||||
cg.add(var.set_rx_queue_len(rx_queue_len))
|
||||
if (tx_queue_len := config.get(CONF_TX_QUEUE_LEN)) is not None:
|
||||
|
||||
@@ -75,8 +75,15 @@ bool ESP32Can::setup_internal() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Select TWAI mode based on configuration
|
||||
twai_mode_t twai_mode = (this->mode_ == CAN_MODE_LISTEN_ONLY) ? TWAI_MODE_LISTEN_ONLY : TWAI_MODE_NORMAL;
|
||||
|
||||
if (this->mode_ == CAN_MODE_LISTEN_ONLY) {
|
||||
ESP_LOGI(TAG, "CAN bus configured in LISTEN_ONLY mode (passive, no ACKs)");
|
||||
}
|
||||
|
||||
twai_general_config_t g_config =
|
||||
TWAI_GENERAL_CONFIG_DEFAULT((gpio_num_t) this->tx_, (gpio_num_t) this->rx_, TWAI_MODE_NORMAL);
|
||||
TWAI_GENERAL_CONFIG_DEFAULT((gpio_num_t) this->tx_, (gpio_num_t) this->rx_, twai_mode);
|
||||
g_config.controller_id = next_twai_ctrl_num++;
|
||||
if (this->tx_queue_len_.has_value()) {
|
||||
g_config.tx_queue_len = this->tx_queue_len_.value();
|
||||
@@ -111,6 +118,12 @@ bool ESP32Can::setup_internal() {
|
||||
}
|
||||
|
||||
canbus::Error ESP32Can::send_message(struct canbus::CanFrame *frame) {
|
||||
// In listen-only mode, we cannot transmit
|
||||
if (this->mode_ == CAN_MODE_LISTEN_ONLY) {
|
||||
ESP_LOGW(TAG, "Cannot send messages in LISTEN_ONLY mode");
|
||||
return canbus::ERROR_FAIL;
|
||||
}
|
||||
|
||||
if (this->twai_handle_ == nullptr) {
|
||||
// not setup yet or setup failed
|
||||
return canbus::ERROR_FAIL;
|
||||
|
||||
@@ -10,10 +10,16 @@
|
||||
namespace esphome {
|
||||
namespace esp32_can {
|
||||
|
||||
enum CanMode : uint8_t {
|
||||
CAN_MODE_NORMAL = 0,
|
||||
CAN_MODE_LISTEN_ONLY = 1,
|
||||
};
|
||||
|
||||
class ESP32Can : public canbus::Canbus {
|
||||
public:
|
||||
void set_rx(int rx) { rx_ = rx; }
|
||||
void set_tx(int tx) { tx_ = tx; }
|
||||
void set_mode(CanMode mode) { mode_ = mode; }
|
||||
void set_tx_queue_len(uint32_t tx_queue_len) { this->tx_queue_len_ = tx_queue_len; }
|
||||
void set_rx_queue_len(uint32_t rx_queue_len) { this->rx_queue_len_ = rx_queue_len; }
|
||||
void set_tx_enqueue_timeout_ms(uint32_t tx_enqueue_timeout_ms) {
|
||||
@@ -28,6 +34,7 @@ class ESP32Can : public canbus::Canbus {
|
||||
|
||||
int rx_{-1};
|
||||
int tx_{-1};
|
||||
CanMode mode_{CAN_MODE_NORMAL};
|
||||
TickType_t tx_enqueue_timeout_ticks_{};
|
||||
optional<uint32_t> tx_queue_len_{};
|
||||
optional<uint32_t> rx_queue_len_{};
|
||||
|
||||
@@ -93,9 +93,9 @@ async def to_code(config):
|
||||
framework_ver: cv.Version = CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION]
|
||||
os.environ["ESP_IDF_VERSION"] = f"{framework_ver.major}.{framework_ver.minor}"
|
||||
if framework_ver >= cv.Version(5, 5, 0):
|
||||
esp32.add_idf_component(name="espressif/esp_wifi_remote", ref="1.2.2")
|
||||
esp32.add_idf_component(name="espressif/eppp_link", ref="1.1.3")
|
||||
esp32.add_idf_component(name="espressif/esp_hosted", ref="2.7.0")
|
||||
esp32.add_idf_component(name="espressif/esp_wifi_remote", ref="1.2.4")
|
||||
esp32.add_idf_component(name="espressif/eppp_link", ref="1.1.4")
|
||||
esp32.add_idf_component(name="espressif/esp_hosted", ref="2.9.3")
|
||||
else:
|
||||
esp32.add_idf_component(name="espressif/esp_wifi_remote", ref="0.13.0")
|
||||
esp32.add_idf_component(name="espressif/eppp_link", ref="0.2.0")
|
||||
|
||||
@@ -4,18 +4,24 @@ from typing import Any
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import esp32, update
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_PATH, CONF_RAW_DATA_ID
|
||||
from esphome.core import CORE, HexInt
|
||||
from esphome.const import CONF_ID, CONF_PATH, CONF_SOURCE, CONF_TYPE
|
||||
from esphome.core import CORE, ID, HexInt
|
||||
|
||||
CODEOWNERS = ["@swoboda1337"]
|
||||
AUTO_LOAD = ["sha256", "watchdog"]
|
||||
AUTO_LOAD = ["sha256", "watchdog", "json"]
|
||||
DEPENDENCIES = ["esp32_hosted"]
|
||||
|
||||
CONF_SHA256 = "sha256"
|
||||
CONF_HTTP_REQUEST_ID = "http_request_id"
|
||||
|
||||
TYPE_EMBEDDED = "embedded"
|
||||
TYPE_HTTP = "http"
|
||||
|
||||
esp32_hosted_ns = cg.esphome_ns.namespace("esp32_hosted")
|
||||
http_request_ns = cg.esphome_ns.namespace("http_request")
|
||||
HttpRequestComponent = http_request_ns.class_("HttpRequestComponent", cg.Component)
|
||||
Esp32HostedUpdate = esp32_hosted_ns.class_(
|
||||
"Esp32HostedUpdate", update.UpdateEntity, cg.Component
|
||||
"Esp32HostedUpdate", update.UpdateEntity, cg.PollingComponent
|
||||
)
|
||||
|
||||
|
||||
@@ -30,12 +36,29 @@ def _validate_sha256(value: Any) -> str:
|
||||
return value
|
||||
|
||||
|
||||
BASE_SCHEMA = update.update_schema(Esp32HostedUpdate, device_class="firmware").extend(
|
||||
cv.polling_component_schema("6h")
|
||||
)
|
||||
|
||||
EMBEDDED_SCHEMA = BASE_SCHEMA.extend(
|
||||
{
|
||||
cv.Required(CONF_PATH): cv.file_,
|
||||
cv.Required(CONF_SHA256): _validate_sha256,
|
||||
}
|
||||
)
|
||||
|
||||
HTTP_SCHEMA = BASE_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(CONF_HTTP_REQUEST_ID): cv.use_id(HttpRequestComponent),
|
||||
cv.Required(CONF_SOURCE): cv.url,
|
||||
}
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
update.update_schema(Esp32HostedUpdate, device_class="firmware").extend(
|
||||
cv.typed_schema(
|
||||
{
|
||||
cv.GenerateID(CONF_RAW_DATA_ID): cv.declare_id(cg.uint8),
|
||||
cv.Required(CONF_PATH): cv.file_,
|
||||
cv.Required(CONF_SHA256): _validate_sha256,
|
||||
TYPE_EMBEDDED: EMBEDDED_SCHEMA,
|
||||
TYPE_HTTP: HTTP_SCHEMA,
|
||||
}
|
||||
),
|
||||
esp32.only_on_variant(
|
||||
@@ -48,6 +71,9 @@ CONFIG_SCHEMA = cv.All(
|
||||
|
||||
|
||||
def _validate_firmware(config: dict[str, Any]) -> None:
|
||||
if config[CONF_TYPE] != TYPE_EMBEDDED:
|
||||
return
|
||||
|
||||
path = CORE.relative_config_path(config[CONF_PATH])
|
||||
with open(path, "rb") as f:
|
||||
firmware_data = f.read()
|
||||
@@ -65,14 +91,22 @@ FINAL_VALIDATE_SCHEMA = _validate_firmware
|
||||
async def to_code(config: dict[str, Any]) -> None:
|
||||
var = await update.new_update(config)
|
||||
|
||||
path = config[CONF_PATH]
|
||||
with open(CORE.relative_config_path(path), "rb") as f:
|
||||
firmware_data = f.read()
|
||||
rhs = [HexInt(x) for x in firmware_data]
|
||||
prog_arr = cg.progmem_array(config[CONF_RAW_DATA_ID], rhs)
|
||||
if config[CONF_TYPE] == TYPE_EMBEDDED:
|
||||
path = config[CONF_PATH]
|
||||
with open(CORE.relative_config_path(path), "rb") as f:
|
||||
firmware_data = f.read()
|
||||
rhs = [HexInt(x) for x in firmware_data]
|
||||
arr_id = ID(f"{config[CONF_ID]}_data", is_declaration=True, type=cg.uint8)
|
||||
prog_arr = cg.progmem_array(arr_id, rhs)
|
||||
|
||||
sha256_bytes = bytes.fromhex(config[CONF_SHA256])
|
||||
cg.add(var.set_firmware_sha256([HexInt(b) for b in sha256_bytes]))
|
||||
cg.add(var.set_firmware_data(prog_arr))
|
||||
cg.add(var.set_firmware_size(len(firmware_data)))
|
||||
else:
|
||||
http_request_var = await cg.get_variable(config[CONF_HTTP_REQUEST_ID])
|
||||
cg.add(var.set_http_request_parent(http_request_var))
|
||||
cg.add(var.set_source_url(config[CONF_SOURCE]))
|
||||
cg.add_define("USE_ESP32_HOSTED_HTTP_UPDATE")
|
||||
|
||||
sha256_bytes = bytes.fromhex(config[CONF_SHA256])
|
||||
cg.add(var.set_firmware_sha256([HexInt(b) for b in sha256_bytes]))
|
||||
cg.add(var.set_firmware_data(prog_arr))
|
||||
cg.add(var.set_firmware_size(len(firmware_data)))
|
||||
await cg.register_component(var, config)
|
||||
|
||||
@@ -7,6 +7,12 @@
|
||||
#include <esp_image_format.h>
|
||||
#include <esp_app_desc.h>
|
||||
#include <esp_hosted.h>
|
||||
#include <esp_hosted_host_fw_ver.h>
|
||||
|
||||
#ifdef USE_ESP32_HOSTED_HTTP_UPDATE
|
||||
#include "esphome/components/json/json_util.h"
|
||||
#include "esphome/components/network/util.h"
|
||||
#endif
|
||||
|
||||
extern "C" {
|
||||
#include <esp_hosted_ota.h>
|
||||
@@ -16,18 +22,50 @@ namespace esphome::esp32_hosted {
|
||||
|
||||
static const char *const TAG = "esp32_hosted.update";
|
||||
|
||||
// older coprocessor firmware versions have a 1500-byte limit per RPC call
|
||||
// Older coprocessor firmware versions have a 1500-byte limit per RPC call
|
||||
constexpr size_t CHUNK_SIZE = 1500;
|
||||
|
||||
// Compile-time version string from esp_hosted_host_fw_ver.h macros
|
||||
#define STRINGIFY_(x) #x
|
||||
#define STRINGIFY(x) STRINGIFY_(x)
|
||||
static const char *const ESP_HOSTED_VERSION_STR = STRINGIFY(ESP_HOSTED_VERSION_MAJOR_1) "." STRINGIFY(
|
||||
ESP_HOSTED_VERSION_MINOR_1) "." STRINGIFY(ESP_HOSTED_VERSION_PATCH_1);
|
||||
|
||||
#ifdef USE_ESP32_HOSTED_HTTP_UPDATE
|
||||
// Parse version string "major.minor.patch" into components
|
||||
// Returns true if parsing succeeded
|
||||
static bool parse_version(const std::string &version_str, int &major, int &minor, int &patch) {
|
||||
major = minor = patch = 0;
|
||||
if (sscanf(version_str.c_str(), "%d.%d.%d", &major, &minor, &patch) >= 2) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Compare two versions, returns:
|
||||
// -1 if v1 < v2
|
||||
// 0 if v1 == v2
|
||||
// 1 if v1 > v2
|
||||
static int compare_versions(int major1, int minor1, int patch1, int major2, int minor2, int patch2) {
|
||||
if (major1 != major2)
|
||||
return major1 < major2 ? -1 : 1;
|
||||
if (minor1 != minor2)
|
||||
return minor1 < minor2 ? -1 : 1;
|
||||
if (patch1 != patch2)
|
||||
return patch1 < patch2 ? -1 : 1;
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
void Esp32HostedUpdate::setup() {
|
||||
this->update_info_.title = "ESP32 Hosted Coprocessor";
|
||||
|
||||
// if wifi is not present, connect to the coprocessor
|
||||
#ifndef USE_WIFI
|
||||
// If WiFi is not present, connect to the coprocessor
|
||||
esp_hosted_connect_to_slave(); // NOLINT
|
||||
#endif
|
||||
|
||||
// get coprocessor version
|
||||
// Get coprocessor version
|
||||
esp_hosted_coprocessor_fwver_t ver_info;
|
||||
if (esp_hosted_get_coprocessor_fwversion(&ver_info) == ESP_OK) {
|
||||
this->update_info_.current_version = str_sprintf("%d.%d.%d", ver_info.major1, ver_info.minor1, ver_info.patch1);
|
||||
@@ -36,7 +74,8 @@ void Esp32HostedUpdate::setup() {
|
||||
}
|
||||
ESP_LOGD(TAG, "Coprocessor version: %s", this->update_info_.current_version.c_str());
|
||||
|
||||
// get image version
|
||||
#ifndef USE_ESP32_HOSTED_HTTP_UPDATE
|
||||
// Embedded mode: get image version from embedded firmware
|
||||
const int app_desc_offset = sizeof(esp_image_header_t) + sizeof(esp_image_segment_header_t);
|
||||
if (this->firmware_size_ >= app_desc_offset + sizeof(esp_app_desc_t)) {
|
||||
esp_app_desc_t *app_desc = (esp_app_desc_t *) (this->firmware_data_ + app_desc_offset);
|
||||
@@ -64,58 +103,272 @@ void Esp32HostedUpdate::setup() {
|
||||
this->state_ = update::UPDATE_STATE_NO_UPDATE;
|
||||
}
|
||||
|
||||
// publish state
|
||||
// Publish state
|
||||
this->status_clear_error();
|
||||
this->publish_state();
|
||||
#else
|
||||
// HTTP mode: retry initial check every 10s until network is ready (max 6 attempts)
|
||||
// Only if update interval is > 1 minute to avoid redundant checks
|
||||
if (this->get_update_interval() > 60000) {
|
||||
this->set_retry("initial_check", 10000, 6, [this](uint8_t) {
|
||||
if (!network::is_connected()) {
|
||||
return RetryResult::RETRY;
|
||||
}
|
||||
this->check();
|
||||
return RetryResult::DONE;
|
||||
});
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void Esp32HostedUpdate::dump_config() {
|
||||
ESP_LOGCONFIG(TAG,
|
||||
"ESP32 Hosted Update:\n"
|
||||
" Current Version: %s\n"
|
||||
" Latest Version: %s\n"
|
||||
" Latest Size: %zu bytes",
|
||||
this->update_info_.current_version.c_str(), this->update_info_.latest_version.c_str(),
|
||||
" Host Library Version: %s\n"
|
||||
" Coprocessor Version: %s\n"
|
||||
" Latest Version: %s",
|
||||
ESP_HOSTED_VERSION_STR, this->update_info_.current_version.c_str(),
|
||||
this->update_info_.latest_version.c_str());
|
||||
#ifdef USE_ESP32_HOSTED_HTTP_UPDATE
|
||||
ESP_LOGCONFIG(TAG,
|
||||
" Mode: HTTP\n"
|
||||
" Source URL: %s",
|
||||
this->source_url_.c_str());
|
||||
#else
|
||||
ESP_LOGCONFIG(TAG,
|
||||
" Mode: Embedded\n"
|
||||
" Firmware Size: %zu bytes",
|
||||
this->firmware_size_);
|
||||
#endif
|
||||
}
|
||||
|
||||
void Esp32HostedUpdate::perform(bool force) {
|
||||
if (this->state_ != update::UPDATE_STATE_AVAILABLE && !force) {
|
||||
ESP_LOGW(TAG, "Update not available");
|
||||
void Esp32HostedUpdate::check() {
|
||||
#ifdef USE_ESP32_HOSTED_HTTP_UPDATE
|
||||
if (!network::is_connected()) {
|
||||
ESP_LOGD(TAG, "Network not connected, skipping update check");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this->fetch_manifest_()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Compare versions
|
||||
if (this->update_info_.latest_version.empty() ||
|
||||
this->update_info_.latest_version == this->update_info_.current_version) {
|
||||
this->state_ = update::UPDATE_STATE_NO_UPDATE;
|
||||
} else {
|
||||
this->state_ = update::UPDATE_STATE_AVAILABLE;
|
||||
}
|
||||
|
||||
this->update_info_.has_progress = false;
|
||||
this->update_info_.progress = 0.0f;
|
||||
this->status_clear_error();
|
||||
this->publish_state();
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef USE_ESP32_HOSTED_HTTP_UPDATE
|
||||
bool Esp32HostedUpdate::fetch_manifest_() {
|
||||
ESP_LOGD(TAG, "Fetching manifest");
|
||||
|
||||
auto container = this->http_request_parent_->get(this->source_url_);
|
||||
if (container == nullptr || container->status_code != 200) {
|
||||
ESP_LOGE(TAG, "Failed to fetch manifest from %s", this->source_url_.c_str());
|
||||
this->status_set_error(LOG_STR("Failed to fetch manifest"));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Read manifest JSON into string (manifest is small, ~1KB max)
|
||||
std::string json_str;
|
||||
json_str.reserve(container->content_length);
|
||||
uint8_t buf[256];
|
||||
while (container->get_bytes_read() < container->content_length) {
|
||||
int read = container->read(buf, sizeof(buf));
|
||||
if (read > 0) {
|
||||
json_str.append(reinterpret_cast<char *>(buf), read);
|
||||
}
|
||||
yield();
|
||||
}
|
||||
container->end();
|
||||
|
||||
// Parse JSON manifest
|
||||
// Format: {"versions": [{"version": "2.7.0", "url": "...", "sha256": "..."}]}
|
||||
// Only consider versions <= host library version to avoid compatibility issues
|
||||
bool valid = json::parse_json(json_str, [this](JsonObject root) -> bool {
|
||||
if (!root["versions"].is<JsonArray>()) {
|
||||
ESP_LOGE(TAG, "Manifest does not contain 'versions' array");
|
||||
return false;
|
||||
}
|
||||
|
||||
JsonArray versions = root["versions"].as<JsonArray>();
|
||||
if (versions.size() == 0) {
|
||||
ESP_LOGE(TAG, "Manifest 'versions' array is empty");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Find the highest version that is compatible with the host library
|
||||
// (version <= host version to avoid upgrading coprocessor ahead of host)
|
||||
int best_major = -1, best_minor = -1, best_patch = -1;
|
||||
std::string best_version, best_url, best_sha256;
|
||||
|
||||
for (JsonObject entry : versions) {
|
||||
if (!entry["version"].is<const char *>() || !entry["url"].is<const char *>() ||
|
||||
!entry["sha256"].is<const char *>()) {
|
||||
continue; // Skip malformed entries
|
||||
}
|
||||
|
||||
std::string ver_str = entry["version"].as<std::string>();
|
||||
int major, minor, patch;
|
||||
if (!parse_version(ver_str, major, minor, patch)) {
|
||||
ESP_LOGW(TAG, "Failed to parse version: %s", ver_str.c_str());
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if this version is compatible (not newer than host)
|
||||
if (compare_versions(major, minor, patch, ESP_HOSTED_VERSION_MAJOR_1, ESP_HOSTED_VERSION_MINOR_1,
|
||||
ESP_HOSTED_VERSION_PATCH_1) > 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if this is better than our current best
|
||||
if (best_major < 0 || compare_versions(major, minor, patch, best_major, best_minor, best_patch) > 0) {
|
||||
best_major = major;
|
||||
best_minor = minor;
|
||||
best_patch = patch;
|
||||
best_version = ver_str;
|
||||
best_url = entry["url"].as<std::string>();
|
||||
best_sha256 = entry["sha256"].as<std::string>();
|
||||
}
|
||||
}
|
||||
|
||||
if (best_major < 0) {
|
||||
ESP_LOGW(TAG, "No compatible firmware version found (host is %s)", ESP_HOSTED_VERSION_STR);
|
||||
return false;
|
||||
}
|
||||
|
||||
this->update_info_.latest_version = best_version;
|
||||
this->firmware_url_ = best_url;
|
||||
|
||||
// Parse SHA256 hex string to bytes
|
||||
if (!parse_hex(best_sha256, this->firmware_sha256_.data(), 32)) {
|
||||
ESP_LOGE(TAG, "Invalid SHA256: %s", best_sha256.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, "Best compatible version: %s", this->update_info_.latest_version.c_str());
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
if (!valid) {
|
||||
ESP_LOGE(TAG, "Failed to parse manifest JSON");
|
||||
this->status_set_error(LOG_STR("Failed to parse manifest"));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Esp32HostedUpdate::stream_firmware_to_coprocessor_() {
|
||||
ESP_LOGI(TAG, "Downloading firmware");
|
||||
|
||||
auto container = this->http_request_parent_->get(this->firmware_url_);
|
||||
if (container == nullptr || container->status_code != 200) {
|
||||
ESP_LOGE(TAG, "Failed to fetch firmware");
|
||||
this->status_set_error(LOG_STR("Failed to fetch firmware"));
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t total_size = container->content_length;
|
||||
ESP_LOGI(TAG, "Firmware size: %zu bytes", total_size);
|
||||
|
||||
// Begin OTA on coprocessor
|
||||
esp_err_t err = esp_hosted_slave_ota_begin(); // NOLINT
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to begin OTA: %s", esp_err_to_name(err));
|
||||
container->end();
|
||||
this->status_set_error(LOG_STR("Failed to begin OTA"));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Stream firmware to coprocessor while computing SHA256
|
||||
// Hardware SHA acceleration requires 32-byte alignment on some chips (ESP32-S3 with IDF 5.5.x+)
|
||||
alignas(32) sha256::SHA256 hasher;
|
||||
hasher.init();
|
||||
|
||||
uint8_t buffer[CHUNK_SIZE];
|
||||
while (container->get_bytes_read() < total_size) {
|
||||
int read = container->read(buffer, sizeof(buffer));
|
||||
|
||||
// Feed watchdog and give other tasks a chance to run
|
||||
App.feed_wdt();
|
||||
yield();
|
||||
|
||||
// Exit loop if no data available (stream closed or end of data)
|
||||
if (read <= 0) {
|
||||
if (read < 0) {
|
||||
ESP_LOGE(TAG, "Stream closed with error");
|
||||
esp_hosted_slave_ota_end(); // NOLINT
|
||||
container->end();
|
||||
this->status_set_error(LOG_STR("Download failed"));
|
||||
return false;
|
||||
}
|
||||
// read == 0: no more data available, exit loop
|
||||
break;
|
||||
}
|
||||
|
||||
hasher.add(buffer, read);
|
||||
err = esp_hosted_slave_ota_write(buffer, read); // NOLINT
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to write OTA data: %s", esp_err_to_name(err));
|
||||
esp_hosted_slave_ota_end(); // NOLINT
|
||||
container->end();
|
||||
this->status_set_error(LOG_STR("Failed to write OTA data"));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
container->end();
|
||||
|
||||
// Verify SHA256
|
||||
hasher.calculate();
|
||||
if (!hasher.equals_bytes(this->firmware_sha256_.data())) {
|
||||
ESP_LOGE(TAG, "SHA256 mismatch");
|
||||
esp_hosted_slave_ota_end(); // NOLINT
|
||||
this->status_set_error(LOG_STR("SHA256 verification failed"));
|
||||
return false;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "SHA256 verified successfully");
|
||||
return true;
|
||||
}
|
||||
#else
|
||||
bool Esp32HostedUpdate::write_embedded_firmware_to_coprocessor_() {
|
||||
if (this->firmware_data_ == nullptr || this->firmware_size_ == 0) {
|
||||
ESP_LOGE(TAG, "No firmware data available");
|
||||
return;
|
||||
this->status_set_error(LOG_STR("No firmware data available"));
|
||||
return false;
|
||||
}
|
||||
|
||||
// ESP32-S3 hardware SHA acceleration requires 32-byte DMA alignment (IDF 5.5.x+)
|
||||
// Verify SHA256 before writing
|
||||
// Hardware SHA acceleration requires 32-byte alignment on some chips (ESP32-S3 with IDF 5.5.x+)
|
||||
alignas(32) sha256::SHA256 hasher;
|
||||
hasher.init();
|
||||
hasher.add(this->firmware_data_, this->firmware_size_);
|
||||
hasher.calculate();
|
||||
if (!hasher.equals_bytes(this->firmware_sha256_.data())) {
|
||||
ESP_LOGE(TAG, "SHA256 mismatch");
|
||||
this->status_set_error(LOG_STR("SHA256 verification failed"));
|
||||
this->publish_state();
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Starting OTA update (%zu bytes)", this->firmware_size_);
|
||||
|
||||
watchdog::WatchdogManager watchdog(20000);
|
||||
update::UpdateState prev_state = this->state_;
|
||||
this->state_ = update::UPDATE_STATE_INSTALLING;
|
||||
this->update_info_.has_progress = false;
|
||||
this->publish_state();
|
||||
|
||||
esp_err_t err = esp_hosted_slave_ota_begin(); // NOLINT
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to begin OTA: %s", esp_err_to_name(err));
|
||||
this->state_ = prev_state;
|
||||
this->status_set_error(LOG_STR("Failed to begin OTA"));
|
||||
this->publish_state();
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t chunk[CHUNK_SIZE];
|
||||
@@ -128,42 +381,68 @@ void Esp32HostedUpdate::perform(bool force) {
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to write OTA data: %s", esp_err_to_name(err));
|
||||
esp_hosted_slave_ota_end(); // NOLINT
|
||||
this->state_ = prev_state;
|
||||
this->status_set_error(LOG_STR("Failed to write OTA data"));
|
||||
this->publish_state();
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
data_ptr += chunk_size;
|
||||
remaining -= chunk_size;
|
||||
App.feed_wdt();
|
||||
}
|
||||
|
||||
err = esp_hosted_slave_ota_end(); // NOLINT
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to end OTA: %s", esp_err_to_name(err));
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
void Esp32HostedUpdate::perform(bool force) {
|
||||
if (this->state_ != update::UPDATE_STATE_AVAILABLE && !force) {
|
||||
ESP_LOGW(TAG, "Update not available");
|
||||
return;
|
||||
}
|
||||
|
||||
update::UpdateState prev_state = this->state_;
|
||||
this->state_ = update::UPDATE_STATE_INSTALLING;
|
||||
this->update_info_.has_progress = false;
|
||||
this->publish_state();
|
||||
|
||||
watchdog::WatchdogManager watchdog(60000);
|
||||
|
||||
#ifdef USE_ESP32_HOSTED_HTTP_UPDATE
|
||||
if (!this->stream_firmware_to_coprocessor_())
|
||||
#else
|
||||
if (!this->write_embedded_firmware_to_coprocessor_())
|
||||
#endif
|
||||
{
|
||||
this->state_ = prev_state;
|
||||
this->publish_state();
|
||||
return;
|
||||
}
|
||||
|
||||
// End OTA and activate new firmware
|
||||
esp_err_t end_err = esp_hosted_slave_ota_end(); // NOLINT
|
||||
if (end_err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to end OTA: %s", esp_err_to_name(end_err));
|
||||
this->state_ = prev_state;
|
||||
this->status_set_error(LOG_STR("Failed to end OTA"));
|
||||
this->publish_state();
|
||||
return;
|
||||
}
|
||||
|
||||
// activate new firmware
|
||||
err = esp_hosted_slave_ota_activate(); // NOLINT
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to activate OTA: %s", esp_err_to_name(err));
|
||||
esp_err_t activate_err = esp_hosted_slave_ota_activate(); // NOLINT
|
||||
if (activate_err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to activate OTA: %s", esp_err_to_name(activate_err));
|
||||
this->state_ = prev_state;
|
||||
this->status_set_error(LOG_STR("Failed to activate OTA"));
|
||||
this->publish_state();
|
||||
return;
|
||||
}
|
||||
|
||||
// update state
|
||||
// Update state
|
||||
ESP_LOGI(TAG, "OTA update successful");
|
||||
this->state_ = update::UPDATE_STATE_NO_UPDATE;
|
||||
this->status_clear_error();
|
||||
this->publish_state();
|
||||
|
||||
// schedule a restart to ensure everything is in sync
|
||||
// Schedule a restart to ensure everything is in sync
|
||||
ESP_LOGI(TAG, "Restarting in 1 second");
|
||||
this->set_timeout(1000, []() { App.safe_reboot(); });
|
||||
}
|
||||
|
||||
@@ -5,26 +5,55 @@
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/update/update_entity.h"
|
||||
#include <array>
|
||||
#include <string>
|
||||
|
||||
#ifdef USE_ESP32_HOSTED_HTTP_UPDATE
|
||||
#include "esphome/components/http_request/http_request.h"
|
||||
#endif
|
||||
|
||||
namespace esphome::esp32_hosted {
|
||||
|
||||
class Esp32HostedUpdate : public update::UpdateEntity, public Component {
|
||||
class Esp32HostedUpdate : public update::UpdateEntity, public PollingComponent {
|
||||
public:
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override { return setup_priority::AFTER_WIFI; }
|
||||
|
||||
void update() override { this->check(); } // PollingComponent - delegates to check()
|
||||
void perform(bool force) override;
|
||||
void check() override {}
|
||||
void check() override;
|
||||
|
||||
#ifdef USE_ESP32_HOSTED_HTTP_UPDATE
|
||||
// HTTP mode setters
|
||||
void set_source_url(const std::string &url) { this->source_url_ = url; }
|
||||
void set_http_request_parent(http_request::HttpRequestComponent *parent) { this->http_request_parent_ = parent; }
|
||||
#else
|
||||
// Embedded mode setters
|
||||
void set_firmware_data(const uint8_t *data) { this->firmware_data_ = data; }
|
||||
void set_firmware_size(size_t size) { this->firmware_size_ = size; }
|
||||
void set_firmware_sha256(const std::array<uint8_t, 32> &sha256) { this->firmware_sha256_ = sha256; }
|
||||
#endif
|
||||
|
||||
protected:
|
||||
#ifdef USE_ESP32_HOSTED_HTTP_UPDATE
|
||||
// HTTP mode members
|
||||
http_request::HttpRequestComponent *http_request_parent_{nullptr};
|
||||
std::string source_url_;
|
||||
std::string firmware_url_;
|
||||
|
||||
// HTTP mode helpers
|
||||
bool fetch_manifest_();
|
||||
bool stream_firmware_to_coprocessor_();
|
||||
#else
|
||||
// Embedded mode members
|
||||
const uint8_t *firmware_data_{nullptr};
|
||||
size_t firmware_size_{0};
|
||||
std::array<uint8_t, 32> firmware_sha256_;
|
||||
|
||||
// Embedded mode helper
|
||||
bool write_embedded_firmware_to_coprocessor_();
|
||||
#endif
|
||||
|
||||
std::array<uint8_t, 32> firmware_sha256_{};
|
||||
};
|
||||
|
||||
} // namespace esphome::esp32_hosted
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/entity_base.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/string_ref.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace event {
|
||||
@@ -44,8 +45,11 @@ class Event : public EntityBase, public EntityBase_DeviceClass {
|
||||
/// Return the event types supported by this event.
|
||||
const FixedVector<const char *> &get_event_types() const { return this->types_; }
|
||||
|
||||
/// Return the last triggered event type (pointer to string in types_), or nullptr if no event triggered yet.
|
||||
const char *get_last_event_type() const { return this->last_event_type_; }
|
||||
/// Return the last triggered event type, or empty StringRef if no event triggered yet.
|
||||
StringRef get_last_event_type() const { return StringRef::from_maybe_nullptr(this->last_event_type_); }
|
||||
|
||||
/// Check if an event has been triggered.
|
||||
bool has_event() const { return this->last_event_type_ != nullptr; }
|
||||
|
||||
void add_on_event_callback(std::function<void(const std::string &event_type)> &&callback);
|
||||
|
||||
|
||||
@@ -212,19 +212,18 @@ class FanPresetSetTrigger : public Trigger<std::string> {
|
||||
public:
|
||||
FanPresetSetTrigger(Fan *state) {
|
||||
state->add_on_state_callback([this, state]() {
|
||||
const auto *preset_mode = state->get_preset_mode();
|
||||
auto preset_mode = state->get_preset_mode();
|
||||
auto should_trigger = preset_mode != this->last_preset_mode_;
|
||||
this->last_preset_mode_ = preset_mode;
|
||||
if (should_trigger) {
|
||||
// Trigger with empty string when nullptr to maintain backward compatibility
|
||||
this->trigger(preset_mode != nullptr ? preset_mode : "");
|
||||
this->trigger(std::string(preset_mode));
|
||||
}
|
||||
});
|
||||
this->last_preset_mode_ = state->get_preset_mode();
|
||||
}
|
||||
|
||||
protected:
|
||||
const char *last_preset_mode_{nullptr};
|
||||
StringRef last_preset_mode_{};
|
||||
};
|
||||
|
||||
} // namespace fan
|
||||
|
||||
@@ -61,7 +61,7 @@ void FanCall::perform() {
|
||||
if (this->direction_.has_value()) {
|
||||
ESP_LOGD(TAG, " Direction: %s", LOG_STR_ARG(fan_direction_to_string(*this->direction_)));
|
||||
}
|
||||
if (this->has_preset_mode()) {
|
||||
if (this->preset_mode_ != nullptr) {
|
||||
ESP_LOGD(TAG, " Preset Mode: %s", this->preset_mode_);
|
||||
}
|
||||
this->parent_.control(*this);
|
||||
@@ -83,7 +83,7 @@ void FanCall::validate_() {
|
||||
*this->binary_state_
|
||||
// ..,and no preset mode will be active...
|
||||
&& !this->has_preset_mode() &&
|
||||
this->parent_.get_preset_mode() == nullptr
|
||||
!this->parent_.has_preset_mode()
|
||||
// ...and neither current nor new speed is available...
|
||||
&& traits.supports_speed() && this->parent_.speed == 0 && !this->speed_.has_value()) {
|
||||
// ...set speed to 100%
|
||||
@@ -154,16 +154,16 @@ const char *Fan::find_preset_mode_(const char *preset_mode, size_t len) {
|
||||
return this->get_traits().find_preset_mode(preset_mode, len);
|
||||
}
|
||||
|
||||
bool Fan::set_preset_mode_(const char *preset_mode) {
|
||||
if (preset_mode == nullptr) {
|
||||
// Treat nullptr as clearing the preset mode
|
||||
bool Fan::set_preset_mode_(const char *preset_mode, size_t len) {
|
||||
if (preset_mode == nullptr || len == 0) {
|
||||
// Treat nullptr or empty string as clearing the preset mode (no valid preset is "")
|
||||
if (this->preset_mode_ == nullptr) {
|
||||
return false; // No change
|
||||
}
|
||||
this->clear_preset_mode_();
|
||||
return true;
|
||||
}
|
||||
const char *validated = this->find_preset_mode_(preset_mode);
|
||||
const char *validated = this->find_preset_mode_(preset_mode, len);
|
||||
if (validated == nullptr || this->preset_mode_ == validated) {
|
||||
return false; // Preset mode not supported or no change
|
||||
}
|
||||
@@ -171,10 +171,31 @@ bool Fan::set_preset_mode_(const char *preset_mode) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Fan::set_preset_mode_(const std::string &preset_mode) { return this->set_preset_mode_(preset_mode.c_str()); }
|
||||
bool Fan::set_preset_mode_(const char *preset_mode) {
|
||||
return this->set_preset_mode_(preset_mode, preset_mode ? strlen(preset_mode) : 0);
|
||||
}
|
||||
|
||||
bool Fan::set_preset_mode_(const std::string &preset_mode) {
|
||||
return this->set_preset_mode_(preset_mode.data(), preset_mode.size());
|
||||
}
|
||||
|
||||
bool Fan::set_preset_mode_(StringRef preset_mode) {
|
||||
// Safe: find_preset_mode_ only uses the input for comparison and returns
|
||||
// a pointer from traits, so the input StringRef's lifetime doesn't matter.
|
||||
return this->set_preset_mode_(preset_mode.c_str(), preset_mode.size());
|
||||
}
|
||||
|
||||
void Fan::clear_preset_mode_() { this->preset_mode_ = nullptr; }
|
||||
|
||||
void Fan::apply_preset_mode_(const FanCall &call) {
|
||||
if (call.has_preset_mode()) {
|
||||
this->set_preset_mode_(call.get_preset_mode());
|
||||
} else if (call.get_speed().has_value()) {
|
||||
// Manually setting speed clears preset (per Home Assistant convention)
|
||||
this->clear_preset_mode_();
|
||||
}
|
||||
}
|
||||
|
||||
void Fan::add_on_state_callback(std::function<void()> &&callback) { this->state_callback_.add(std::move(callback)); }
|
||||
void Fan::publish_state() {
|
||||
auto traits = this->get_traits();
|
||||
@@ -192,9 +213,8 @@ void Fan::publish_state() {
|
||||
if (traits.supports_direction()) {
|
||||
ESP_LOGD(TAG, " Direction: %s", LOG_STR_ARG(fan_direction_to_string(this->direction)));
|
||||
}
|
||||
const char *preset = this->get_preset_mode();
|
||||
if (preset != nullptr) {
|
||||
ESP_LOGD(TAG, " Preset Mode: %s", preset);
|
||||
if (this->preset_mode_ != nullptr) {
|
||||
ESP_LOGD(TAG, " Preset Mode: %s", this->preset_mode_);
|
||||
}
|
||||
this->state_callback_.call();
|
||||
#if defined(USE_FAN) && defined(USE_CONTROLLER_REGISTRY)
|
||||
@@ -249,12 +269,11 @@ void Fan::save_state_() {
|
||||
state.speed = this->speed;
|
||||
state.direction = this->direction;
|
||||
|
||||
const char *preset = this->get_preset_mode();
|
||||
if (preset != nullptr) {
|
||||
if (this->has_preset_mode()) {
|
||||
const auto &preset_modes = traits.supported_preset_modes();
|
||||
// Find index of current preset mode (pointer comparison is safe since preset is from traits)
|
||||
for (size_t i = 0; i < preset_modes.size(); i++) {
|
||||
if (preset_modes[i] == preset) {
|
||||
if (preset_modes[i] == this->preset_mode_) {
|
||||
state.preset_mode = i;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/optional.h"
|
||||
#include "esphome/core/preferences.h"
|
||||
#include "esphome/core/string_ref.h"
|
||||
#include "fan_traits.h"
|
||||
|
||||
namespace esphome {
|
||||
@@ -128,8 +129,11 @@ class Fan : public EntityBase {
|
||||
/// Set the restore mode of this fan.
|
||||
void set_restore_mode(FanRestoreMode restore_mode) { this->restore_mode_ = restore_mode; }
|
||||
|
||||
/// Get the current preset mode (returns pointer to string stored in traits, or nullptr if not set)
|
||||
const char *get_preset_mode() const { return this->preset_mode_; }
|
||||
/// Get the current preset mode.
|
||||
/// Returns a StringRef of the string stored in traits, or empty ref if not set.
|
||||
/// The returned ref points to string literals from codegen (static storage).
|
||||
/// Traits are set once at startup and valid for the lifetime of the program.
|
||||
StringRef get_preset_mode() const { return StringRef::from_maybe_nullptr(this->preset_mode_); }
|
||||
|
||||
/// Check if a preset mode is currently active
|
||||
bool has_preset_mode() const { return this->preset_mode_ != nullptr; }
|
||||
@@ -146,11 +150,15 @@ class Fan : public EntityBase {
|
||||
void dump_traits_(const char *tag, const char *prefix);
|
||||
|
||||
/// Set the preset mode (finds and stores pointer from traits). Returns true if changed.
|
||||
/// Passing nullptr or empty string clears the preset mode.
|
||||
bool set_preset_mode_(const char *preset_mode, size_t len);
|
||||
bool set_preset_mode_(const char *preset_mode);
|
||||
/// Set the preset mode (finds and stores pointer from traits). Returns true if changed.
|
||||
bool set_preset_mode_(const std::string &preset_mode);
|
||||
bool set_preset_mode_(StringRef preset_mode);
|
||||
/// Clear the preset mode
|
||||
void clear_preset_mode_();
|
||||
/// Apply preset mode from a FanCall (handles speed-clears-preset convention)
|
||||
void apply_preset_mode_(const FanCall &call);
|
||||
/// Find and return the matching preset mode pointer from traits, or nullptr if not found.
|
||||
const char *find_preset_mode_(const char *preset_mode);
|
||||
const char *find_preset_mode_(const char *preset_mode, size_t len);
|
||||
|
||||
@@ -232,17 +232,19 @@ void GraphLegend::init(Graph *g) {
|
||||
ESP_LOGI(TAGL, " %s %d %d", txtstr.c_str(), fw, fh);
|
||||
|
||||
if (this->values_ != VALUE_POSITION_TYPE_NONE) {
|
||||
std::string valstr =
|
||||
value_accuracy_to_string(trace->sensor_->get_state(), trace->sensor_->get_accuracy_decimals());
|
||||
char valstr[VALUE_ACCURACY_MAX_LEN];
|
||||
if (this->units_) {
|
||||
valstr += trace->sensor_->get_unit_of_measurement_ref();
|
||||
value_accuracy_with_uom_to_buf(valstr, trace->sensor_->get_state(), trace->sensor_->get_accuracy_decimals(),
|
||||
trace->sensor_->get_unit_of_measurement_ref());
|
||||
} else {
|
||||
value_accuracy_to_buf(valstr, trace->sensor_->get_state(), trace->sensor_->get_accuracy_decimals());
|
||||
}
|
||||
this->font_value_->measure(valstr.c_str(), &fw, &fos, &fbl, &fh);
|
||||
this->font_value_->measure(valstr, &fw, &fos, &fbl, &fh);
|
||||
if (fw > valw)
|
||||
valw = fw;
|
||||
if (fh > valh)
|
||||
valh = fh;
|
||||
ESP_LOGI(TAGL, " %s %d %d", valstr.c_str(), fw, fh);
|
||||
ESP_LOGI(TAGL, " %s %d %d", valstr, fw, fh);
|
||||
}
|
||||
}
|
||||
// Add extra margin
|
||||
@@ -368,13 +370,15 @@ void Graph::draw_legend(display::Display *buff, uint16_t x_offset, uint16_t y_of
|
||||
if (legend_->values_ != VALUE_POSITION_TYPE_NONE) {
|
||||
int xv = x + legend_->xv_;
|
||||
int yv = y + legend_->yv_;
|
||||
std::string valstr =
|
||||
value_accuracy_to_string(trace->sensor_->get_state(), trace->sensor_->get_accuracy_decimals());
|
||||
char valstr[VALUE_ACCURACY_MAX_LEN];
|
||||
if (legend_->units_) {
|
||||
valstr += trace->sensor_->get_unit_of_measurement_ref();
|
||||
value_accuracy_with_uom_to_buf(valstr, trace->sensor_->get_state(), trace->sensor_->get_accuracy_decimals(),
|
||||
trace->sensor_->get_unit_of_measurement_ref());
|
||||
} else {
|
||||
value_accuracy_to_buf(valstr, trace->sensor_->get_state(), trace->sensor_->get_accuracy_decimals());
|
||||
}
|
||||
buff->printf(xv, yv, legend_->font_value_, trace->get_line_color(), TextAlign::TOP_CENTER, "%s", valstr.c_str());
|
||||
ESP_LOGV(TAG, " value: %s", valstr.c_str());
|
||||
buff->printf(xv, yv, legend_->font_value_, trace->get_line_color(), TextAlign::TOP_CENTER, "%s", valstr);
|
||||
ESP_LOGV(TAG, " value: %s", valstr);
|
||||
}
|
||||
x += legend_->xs_;
|
||||
y += legend_->ys_;
|
||||
|
||||
@@ -57,7 +57,7 @@ void HBridgeFan::control(const fan::FanCall &call) {
|
||||
this->oscillating = *call.get_oscillating();
|
||||
if (call.get_direction().has_value())
|
||||
this->direction = *call.get_direction();
|
||||
this->set_preset_mode_(call.get_preset_mode());
|
||||
this->apply_preset_mode_(call);
|
||||
|
||||
this->write_state_();
|
||||
this->publish_state();
|
||||
|
||||
@@ -91,11 +91,14 @@ void HomeassistantNumber::control(float value) {
|
||||
resp.data.init(2);
|
||||
auto &entity_id = resp.data.emplace_back();
|
||||
entity_id.key = ENTITY_ID_KEY;
|
||||
entity_id.value = this->entity_id_;
|
||||
entity_id.value = StringRef(this->entity_id_);
|
||||
|
||||
auto &entity_value = resp.data.emplace_back();
|
||||
entity_value.key = VALUE_KEY;
|
||||
entity_value.value = to_string(value);
|
||||
// Stack buffer - no heap allocation; %g produces shortest representation
|
||||
char value_buf[16];
|
||||
snprintf(value_buf, sizeof(value_buf), "%g", value);
|
||||
entity_value.value = StringRef(value_buf);
|
||||
|
||||
api::global_api_server->send_homeassistant_action(resp);
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ void HomeassistantSwitch::write_state(bool state) {
|
||||
resp.data.init(1);
|
||||
auto &entity_id_kv = resp.data.emplace_back();
|
||||
entity_id_kv.key = ENTITY_ID_KEY;
|
||||
entity_id_kv.value = this->entity_id_;
|
||||
entity_id_kv.value = StringRef(this->entity_id_);
|
||||
|
||||
api::global_api_server->send_homeassistant_action(resp);
|
||||
}
|
||||
|
||||
@@ -267,8 +267,10 @@ bool ImprovSerialComponent::parse_improv_payload_(improv::ImprovCommand &command
|
||||
if (std::find(networks.begin(), networks.end(), ssid) != networks.end())
|
||||
continue;
|
||||
// Send each ssid separately to avoid overflowing the buffer
|
||||
std::vector<uint8_t> data = improv::build_rpc_response(
|
||||
improv::GET_WIFI_NETWORKS, {ssid, str_sprintf("%d", scan.get_rssi()), YESNO(scan.get_with_auth())}, false);
|
||||
char rssi_buf[5]; // int8_t: -128 to 127, max 4 chars + null
|
||||
*int8_to_str(rssi_buf, scan.get_rssi()) = '\0';
|
||||
std::vector<uint8_t> data =
|
||||
improv::build_rpc_response(improv::GET_WIFI_NETWORKS, {ssid, rssi_buf, YESNO(scan.get_with_auth())}, false);
|
||||
this->send_response_(data);
|
||||
networks.push_back(ssid);
|
||||
}
|
||||
|
||||
76
esphome/components/infrared/__init__.py
Normal file
76
esphome/components/infrared/__init__.py
Normal file
@@ -0,0 +1,76 @@
|
||||
"""
|
||||
Infrared component for ESPHome.
|
||||
|
||||
WARNING: This component is EXPERIMENTAL. The API (both Python configuration
|
||||
and C++ interfaces) may change at any time without following the normal
|
||||
breaking changes policy. Use at your own risk.
|
||||
|
||||
Once the API is considered stable, this warning will be removed.
|
||||
"""
|
||||
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ID
|
||||
from esphome.core import CORE, coroutine_with_priority
|
||||
from esphome.core.entity_helpers import setup_entity
|
||||
from esphome.coroutine import CoroPriority
|
||||
from esphome.types import ConfigType
|
||||
|
||||
CODEOWNERS = ["@kbx81"]
|
||||
AUTO_LOAD = ["remote_base"]
|
||||
|
||||
IS_PLATFORM_COMPONENT = True
|
||||
|
||||
infrared_ns = cg.esphome_ns.namespace("infrared")
|
||||
Infrared = infrared_ns.class_("Infrared", cg.EntityBase, cg.Component)
|
||||
InfraredCall = infrared_ns.class_("InfraredCall")
|
||||
InfraredTraits = infrared_ns.class_("InfraredTraits")
|
||||
|
||||
CONF_INFRARED_ID = "infrared_id"
|
||||
CONF_SUPPORTS_TRANSMITTER = "supports_transmitter"
|
||||
CONF_SUPPORTS_RECEIVER = "supports_receiver"
|
||||
|
||||
|
||||
def infrared_schema(class_: type[cg.MockObjClass]) -> cv.Schema:
|
||||
"""Create a schema for an infrared platform.
|
||||
|
||||
:param class_: The infrared class to use for this schema.
|
||||
:return: An extended schema for infrared configuration.
|
||||
"""
|
||||
entity_schema = cv.ENTITY_BASE_SCHEMA.extend(cv.COMPONENT_SCHEMA)
|
||||
return entity_schema.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(class_),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def setup_infrared_core_(var: cg.Pvariable, config: ConfigType) -> None:
|
||||
"""Set up core infrared configuration."""
|
||||
await setup_entity(var, config, "infrared")
|
||||
|
||||
|
||||
async def register_infrared(var: cg.Pvariable, config: ConfigType) -> None:
|
||||
"""Register an infrared device with the core."""
|
||||
cg.add_define("USE_IR_RF")
|
||||
await cg.register_component(var, config)
|
||||
await setup_infrared_core_(var, config)
|
||||
cg.add(cg.App.register_infrared(var))
|
||||
CORE.register_platform_component("infrared", var)
|
||||
|
||||
|
||||
async def new_infrared(config: ConfigType, *args) -> cg.Pvariable:
|
||||
"""Create a new Infrared instance.
|
||||
|
||||
:param config: Configuration dictionary.
|
||||
:param args: Additional arguments to pass to new_Pvariable.
|
||||
:return: The created Infrared instance.
|
||||
"""
|
||||
var = cg.new_Pvariable(config[CONF_ID], *args)
|
||||
await register_infrared(var, config)
|
||||
return var
|
||||
|
||||
|
||||
@coroutine_with_priority(CoroPriority.CORE)
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
cg.add_global(infrared_ns.using)
|
||||
138
esphome/components/infrared/infrared.cpp
Normal file
138
esphome/components/infrared/infrared.cpp
Normal file
@@ -0,0 +1,138 @@
|
||||
#include "infrared.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#ifdef USE_API
|
||||
#include "esphome/components/api/api_server.h"
|
||||
#endif
|
||||
|
||||
namespace esphome::infrared {
|
||||
|
||||
static const char *const TAG = "infrared";
|
||||
|
||||
// ========== InfraredCall ==========
|
||||
|
||||
InfraredCall &InfraredCall::set_carrier_frequency(uint32_t frequency) {
|
||||
this->carrier_frequency_ = frequency;
|
||||
return *this;
|
||||
}
|
||||
|
||||
InfraredCall &InfraredCall::set_raw_timings(const std::vector<int32_t> &timings) {
|
||||
this->raw_timings_ = &timings;
|
||||
this->packed_data_ = nullptr; // Clear packed if vector is set
|
||||
return *this;
|
||||
}
|
||||
|
||||
InfraredCall &InfraredCall::set_raw_timings_packed(const uint8_t *data, uint16_t length, uint16_t count) {
|
||||
this->packed_data_ = data;
|
||||
this->packed_length_ = length;
|
||||
this->packed_count_ = count;
|
||||
this->raw_timings_ = nullptr; // Clear vector if packed is set
|
||||
return *this;
|
||||
}
|
||||
|
||||
InfraredCall &InfraredCall::set_repeat_count(uint32_t count) {
|
||||
this->repeat_count_ = count;
|
||||
return *this;
|
||||
}
|
||||
|
||||
void InfraredCall::perform() {
|
||||
if (this->parent_ != nullptr) {
|
||||
this->parent_->control(*this);
|
||||
}
|
||||
}
|
||||
|
||||
// ========== Infrared ==========
|
||||
|
||||
void Infrared::setup() {
|
||||
// Set up traits based on configuration
|
||||
this->traits_.set_supports_transmitter(this->has_transmitter());
|
||||
this->traits_.set_supports_receiver(this->has_receiver());
|
||||
|
||||
// Register as listener for received IR data
|
||||
if (this->receiver_ != nullptr) {
|
||||
this->receiver_->register_listener(this);
|
||||
}
|
||||
}
|
||||
|
||||
void Infrared::dump_config() {
|
||||
ESP_LOGCONFIG(TAG,
|
||||
"Infrared '%s'\n"
|
||||
" Supports Transmitter: %s\n"
|
||||
" Supports Receiver: %s",
|
||||
this->get_name().c_str(), YESNO(this->traits_.get_supports_transmitter()),
|
||||
YESNO(this->traits_.get_supports_receiver()));
|
||||
}
|
||||
|
||||
InfraredCall Infrared::make_call() { return InfraredCall(this); }
|
||||
|
||||
void Infrared::control(const InfraredCall &call) {
|
||||
if (this->transmitter_ == nullptr) {
|
||||
ESP_LOGW(TAG, "No transmitter configured");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!call.has_raw_timings()) {
|
||||
ESP_LOGE(TAG, "No raw timings provided");
|
||||
return;
|
||||
}
|
||||
|
||||
// Create transmit data object
|
||||
auto transmit_call = this->transmitter_->transmit();
|
||||
auto *transmit_data = transmit_call.get_data();
|
||||
|
||||
// Set carrier frequency
|
||||
if (call.get_carrier_frequency().has_value()) {
|
||||
transmit_data->set_carrier_frequency(call.get_carrier_frequency().value());
|
||||
}
|
||||
|
||||
// Set timings based on format
|
||||
if (call.is_packed()) {
|
||||
// Zero-copy from packed protobuf data
|
||||
transmit_data->set_data_from_packed_sint32(call.get_packed_data(), call.get_packed_length(),
|
||||
call.get_packed_count());
|
||||
ESP_LOGD(TAG, "Transmitting packed raw timings: count=%u, repeat=%u", call.get_packed_count(),
|
||||
call.get_repeat_count());
|
||||
} else {
|
||||
// From vector (lambdas/automations)
|
||||
transmit_data->set_data(call.get_raw_timings());
|
||||
ESP_LOGD(TAG, "Transmitting raw timings: count=%zu, repeat=%u", call.get_raw_timings().size(),
|
||||
call.get_repeat_count());
|
||||
}
|
||||
|
||||
// Set repeat count
|
||||
if (call.get_repeat_count() > 0) {
|
||||
transmit_call.set_send_times(call.get_repeat_count());
|
||||
}
|
||||
|
||||
// Perform transmission
|
||||
transmit_call.perform();
|
||||
}
|
||||
|
||||
uint32_t Infrared::get_capability_flags() const {
|
||||
uint32_t flags = 0;
|
||||
|
||||
// Add transmit/receive capability based on traits
|
||||
if (this->traits_.get_supports_transmitter())
|
||||
flags |= InfraredCapability::CAPABILITY_TRANSMITTER;
|
||||
if (this->traits_.get_supports_receiver())
|
||||
flags |= InfraredCapability::CAPABILITY_RECEIVER;
|
||||
|
||||
return flags;
|
||||
}
|
||||
|
||||
bool Infrared::on_receive(remote_base::RemoteReceiveData data) {
|
||||
// Forward received IR data to API server
|
||||
#if defined(USE_API) && defined(USE_IR_RF)
|
||||
if (api::global_api_server != nullptr) {
|
||||
#ifdef USE_DEVICES
|
||||
uint32_t device_id = this->get_device_id();
|
||||
#else
|
||||
uint32_t device_id = 0;
|
||||
#endif
|
||||
api::global_api_server->send_infrared_rf_receive_event(device_id, this->get_object_id_hash(), &data.get_raw_data());
|
||||
}
|
||||
#endif
|
||||
return false; // Don't consume the event, allow other listeners to process it
|
||||
}
|
||||
|
||||
} // namespace esphome::infrared
|
||||
130
esphome/components/infrared/infrared.h
Normal file
130
esphome/components/infrared/infrared.h
Normal file
@@ -0,0 +1,130 @@
|
||||
#pragma once
|
||||
|
||||
// WARNING: This component is EXPERIMENTAL. The API may change at any time
|
||||
// without following the normal breaking changes policy. Use at your own risk.
|
||||
// Once the API is considered stable, this warning will be removed.
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/entity_base.h"
|
||||
#include "esphome/components/remote_base/remote_base.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace esphome::infrared {
|
||||
|
||||
/// Capability flags for individual infrared instances
|
||||
enum InfraredCapability : uint32_t {
|
||||
CAPABILITY_TRANSMITTER = 1 << 0, // Can transmit signals
|
||||
CAPABILITY_RECEIVER = 1 << 1, // Can receive signals
|
||||
};
|
||||
|
||||
/// Forward declarations
|
||||
class Infrared;
|
||||
|
||||
/// InfraredCall - Builder pattern for transmitting infrared signals
|
||||
class InfraredCall {
|
||||
public:
|
||||
explicit InfraredCall(Infrared *parent) : parent_(parent) {}
|
||||
|
||||
/// Set the carrier frequency in Hz
|
||||
InfraredCall &set_carrier_frequency(uint32_t frequency);
|
||||
/// Set the raw timings (positive = mark, negative = space)
|
||||
/// Note: The timings vector must outlive the InfraredCall (zero-copy reference)
|
||||
InfraredCall &set_raw_timings(const std::vector<int32_t> &timings);
|
||||
/// Set the raw timings from packed protobuf sint32 data (zero-copy from wire)
|
||||
/// Note: The data must outlive the InfraredCall
|
||||
InfraredCall &set_raw_timings_packed(const uint8_t *data, uint16_t length, uint16_t count);
|
||||
/// Set the number of times to repeat transmission (1 = transmit once, 2 = transmit twice, etc.)
|
||||
InfraredCall &set_repeat_count(uint32_t count);
|
||||
|
||||
/// Perform the transmission
|
||||
void perform();
|
||||
|
||||
/// Get the carrier frequency
|
||||
const optional<uint32_t> &get_carrier_frequency() const { return this->carrier_frequency_; }
|
||||
/// Get the raw timings (only valid if set via set_raw_timings, not packed)
|
||||
const std::vector<int32_t> &get_raw_timings() const { return *this->raw_timings_; }
|
||||
/// Check if raw timings have been set (either vector or packed)
|
||||
bool has_raw_timings() const { return this->raw_timings_ != nullptr || this->packed_data_ != nullptr; }
|
||||
/// Check if using packed data format
|
||||
bool is_packed() const { return this->packed_data_ != nullptr; }
|
||||
/// Get packed data (only valid if set via set_raw_timings_packed)
|
||||
const uint8_t *get_packed_data() const { return this->packed_data_; }
|
||||
uint16_t get_packed_length() const { return this->packed_length_; }
|
||||
uint16_t get_packed_count() const { return this->packed_count_; }
|
||||
/// Get the repeat count
|
||||
uint32_t get_repeat_count() const { return this->repeat_count_; }
|
||||
|
||||
protected:
|
||||
uint32_t repeat_count_{1};
|
||||
Infrared *parent_;
|
||||
optional<uint32_t> carrier_frequency_;
|
||||
// Vector-based timings (for lambdas/automations)
|
||||
const std::vector<int32_t> *raw_timings_{nullptr};
|
||||
// Packed protobuf timings (for API zero-copy)
|
||||
const uint8_t *packed_data_{nullptr};
|
||||
uint16_t packed_length_{0};
|
||||
uint16_t packed_count_{0};
|
||||
};
|
||||
|
||||
/// InfraredTraits - Describes the capabilities of an infrared implementation
|
||||
class InfraredTraits {
|
||||
public:
|
||||
bool get_supports_transmitter() const { return this->supports_transmitter_; }
|
||||
void set_supports_transmitter(bool supports) { this->supports_transmitter_ = supports; }
|
||||
|
||||
bool get_supports_receiver() const { return this->supports_receiver_; }
|
||||
void set_supports_receiver(bool supports) { this->supports_receiver_ = supports; }
|
||||
|
||||
protected:
|
||||
bool supports_transmitter_{false};
|
||||
bool supports_receiver_{false};
|
||||
};
|
||||
|
||||
/// Infrared - Base class for infrared remote control implementations
|
||||
class Infrared : public Component, public EntityBase, public remote_base::RemoteReceiverListener {
|
||||
public:
|
||||
Infrared() = default;
|
||||
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override { return setup_priority::AFTER_CONNECTION; }
|
||||
|
||||
/// Set the remote receiver component
|
||||
void set_receiver(remote_base::RemoteReceiverBase *receiver) { this->receiver_ = receiver; }
|
||||
/// Set the remote transmitter component
|
||||
void set_transmitter(remote_base::RemoteTransmitterBase *transmitter) { this->transmitter_ = transmitter; }
|
||||
|
||||
/// Check if this infrared has a transmitter configured
|
||||
bool has_transmitter() const { return this->transmitter_ != nullptr; }
|
||||
/// Check if this infrared has a receiver configured
|
||||
bool has_receiver() const { return this->receiver_ != nullptr; }
|
||||
|
||||
/// Get the traits for this infrared implementation
|
||||
InfraredTraits &get_traits() { return this->traits_; }
|
||||
const InfraredTraits &get_traits() const { return this->traits_; }
|
||||
|
||||
/// Create a call object for transmitting
|
||||
InfraredCall make_call();
|
||||
|
||||
/// Get capability flags for this infrared instance
|
||||
uint32_t get_capability_flags() const;
|
||||
|
||||
/// Called when IR data is received (from RemoteReceiverListener)
|
||||
bool on_receive(remote_base::RemoteReceiveData data) override;
|
||||
|
||||
protected:
|
||||
friend class InfraredCall;
|
||||
|
||||
/// Perform the actual transmission (called by InfraredCall)
|
||||
virtual void control(const InfraredCall &call);
|
||||
|
||||
// Underlying hardware components
|
||||
remote_base::RemoteReceiverBase *receiver_{nullptr};
|
||||
remote_base::RemoteTransmitterBase *transmitter_{nullptr};
|
||||
|
||||
// Traits describing capabilities
|
||||
InfraredTraits traits_;
|
||||
};
|
||||
|
||||
} // namespace esphome::infrared
|
||||
@@ -183,11 +183,14 @@ uint8_t Lc709203f::get_register_(uint8_t register_to_read, uint16_t *register_va
|
||||
return_code = this->read_register(register_to_read, &read_buffer[3], 3);
|
||||
if (return_code != i2c::NO_ERROR) {
|
||||
// Error on the i2c bus
|
||||
this->status_set_warning(
|
||||
str_sprintf("Error code %d when reading from register 0x%02X", return_code, register_to_read).c_str());
|
||||
char buf[64];
|
||||
snprintf(buf, sizeof(buf), "Error code %d when reading from register 0x%02X", return_code, register_to_read);
|
||||
this->status_set_warning(buf);
|
||||
} else if (crc8(read_buffer, 5, 0x00, 0x07, true) != read_buffer[5]) {
|
||||
// I2C indicated OK, but the CRC of the data does not matcth.
|
||||
this->status_set_warning(str_sprintf("CRC error reading from register 0x%02X", register_to_read).c_str());
|
||||
char buf[64];
|
||||
snprintf(buf, sizeof(buf), "CRC error reading from register 0x%02X", register_to_read);
|
||||
this->status_set_warning(buf);
|
||||
} else {
|
||||
*register_value = ((uint16_t) read_buffer[4] << 8) | (uint16_t) read_buffer[3];
|
||||
return i2c::NO_ERROR;
|
||||
@@ -225,8 +228,9 @@ uint8_t Lc709203f::set_register_(uint8_t register_to_set, uint16_t value_to_set)
|
||||
if (return_code == i2c::NO_ERROR) {
|
||||
return return_code;
|
||||
} else {
|
||||
this->status_set_warning(
|
||||
str_sprintf("Error code %d when writing to register 0x%02X", return_code, register_to_set).c_str());
|
||||
char buf[64];
|
||||
snprintf(buf, sizeof(buf), "Error code %d when writing to register 0x%02X", return_code, register_to_set);
|
||||
this->status_set_warning(buf);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -442,7 +442,8 @@ bool LD2410Component::handle_ack_data_() {
|
||||
ESP_LOGV(TAG, "Baud rate change");
|
||||
#ifdef USE_SELECT
|
||||
if (this->baud_rate_select_ != nullptr) {
|
||||
ESP_LOGE(TAG, "Change baud rate to %s and reinstall", this->baud_rate_select_->current_option());
|
||||
auto baud = this->baud_rate_select_->current_option();
|
||||
ESP_LOGE(TAG, "Change baud rate to %.*s and reinstall", (int) baud.size(), baud.c_str());
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
@@ -766,10 +767,10 @@ void LD2410Component::set_light_out_control() {
|
||||
#endif
|
||||
#ifdef USE_SELECT
|
||||
if (this->light_function_select_ != nullptr && this->light_function_select_->has_state()) {
|
||||
this->light_function_ = find_uint8(LIGHT_FUNCTIONS_BY_STR, this->light_function_select_->current_option());
|
||||
this->light_function_ = find_uint8(LIGHT_FUNCTIONS_BY_STR, this->light_function_select_->current_option().c_str());
|
||||
}
|
||||
if (this->out_pin_level_select_ != nullptr && this->out_pin_level_select_->has_state()) {
|
||||
this->out_pin_level_ = find_uint8(OUT_PIN_LEVELS_BY_STR, this->out_pin_level_select_->current_option());
|
||||
this->out_pin_level_ = find_uint8(OUT_PIN_LEVELS_BY_STR, this->out_pin_level_select_->current_option().c_str());
|
||||
}
|
||||
#endif
|
||||
this->set_config_mode_(true);
|
||||
|
||||
@@ -486,7 +486,8 @@ bool LD2412Component::handle_ack_data_() {
|
||||
ESP_LOGV(TAG, "Baud rate change");
|
||||
#ifdef USE_SELECT
|
||||
if (this->baud_rate_select_ != nullptr) {
|
||||
ESP_LOGW(TAG, "Change baud rate to %s and reinstall", this->baud_rate_select_->current_option());
|
||||
auto baud = this->baud_rate_select_->current_option();
|
||||
ESP_LOGW(TAG, "Change baud rate to %.*s and reinstall", (int) baud.size(), baud.c_str());
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
@@ -790,7 +791,7 @@ void LD2412Component::set_basic_config() {
|
||||
1, TOTAL_GATES, DEFAULT_PRESENCE_TIMEOUT, 0,
|
||||
#endif
|
||||
#ifdef USE_SELECT
|
||||
find_uint8(OUT_PIN_LEVELS_BY_STR, this->out_pin_level_select_->current_option()),
|
||||
find_uint8(OUT_PIN_LEVELS_BY_STR, this->out_pin_level_select_->current_option().c_str()),
|
||||
#else
|
||||
0x01, // Default value if not using select
|
||||
#endif
|
||||
@@ -844,7 +845,7 @@ void LD2412Component::set_light_out_control() {
|
||||
#endif
|
||||
#ifdef USE_SELECT
|
||||
if (this->light_function_select_ != nullptr && this->light_function_select_->has_state()) {
|
||||
this->light_function_ = find_uint8(LIGHT_FUNCTIONS_BY_STR, this->light_function_select_->current_option());
|
||||
this->light_function_ = find_uint8(LIGHT_FUNCTIONS_BY_STR, this->light_function_select_->current_option().c_str());
|
||||
}
|
||||
#endif
|
||||
uint8_t value[2] = {this->light_function_, this->light_threshold_};
|
||||
|
||||
@@ -637,7 +637,8 @@ bool LD2450Component::handle_ack_data_() {
|
||||
ESP_LOGV(TAG, "Baud rate change");
|
||||
#ifdef USE_SELECT
|
||||
if (this->baud_rate_select_ != nullptr) {
|
||||
ESP_LOGE(TAG, "Change baud rate to %s and reinstall", this->baud_rate_select_->current_option());
|
||||
auto baud = this->baud_rate_select_->current_option();
|
||||
ESP_LOGE(TAG, "Change baud rate to %.*s and reinstall", (int) baud.size(), baud.c_str());
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
@@ -718,7 +719,8 @@ bool LD2450Component::handle_ack_data_() {
|
||||
this->publish_zone_type();
|
||||
#ifdef USE_SELECT
|
||||
if (this->zone_type_select_ != nullptr) {
|
||||
ESP_LOGV(TAG, "Change zone type to: %s", this->zone_type_select_->current_option());
|
||||
auto zone = this->zone_type_select_->current_option();
|
||||
ESP_LOGV(TAG, "Change zone type to: %.*s", (int) zone.size(), zone.c_str());
|
||||
}
|
||||
#endif
|
||||
if (this->buffer_data_[10] == 0x00) {
|
||||
|
||||
@@ -32,6 +32,7 @@ from .const import (
|
||||
CONF_SDK_SILENT,
|
||||
CONF_UART_PORT,
|
||||
FAMILIES,
|
||||
FAMILY_BK7231N,
|
||||
FAMILY_COMPONENT,
|
||||
FAMILY_FRIENDLY,
|
||||
KEY_BOARD,
|
||||
@@ -50,6 +51,22 @@ CODEOWNERS = ["@kuba2k2"]
|
||||
AUTO_LOAD = ["preferences"]
|
||||
IS_TARGET_PLATFORM = True
|
||||
|
||||
# BK7231N SDK options to disable unused features.
|
||||
# Disabling BLE saves ~21KB RAM and ~200KB Flash because BLE init code is
|
||||
# called unconditionally by the SDK. ESPHome doesn't use BLE on LibreTiny.
|
||||
#
|
||||
# This only works on BK7231N (BLE 5.x). Other BK72XX chips using BLE 4.2
|
||||
# (BK7231T, BK7231Q, BK7251; BK7252 boards use the BK7251 family) have a bug
|
||||
# where the BLE library still links and references undefined symbols when
|
||||
# CFG_SUPPORT_BLE=0.
|
||||
#
|
||||
# Other options like CFG_TX_EVM_TEST, CFG_RX_SENSITIVITY_TEST, CFG_SUPPORT_BKREG,
|
||||
# CFG_SUPPORT_OTA_HTTP, and CFG_USE_SPI_SLAVE were evaluated but provide no # NOLINT
|
||||
# measurable benefit - the linker already strips unreferenced code via -gc-sections.
|
||||
_BK7231N_SYS_CONFIG_OPTIONS = [
|
||||
"CFG_SUPPORT_BLE=0",
|
||||
]
|
||||
|
||||
|
||||
def _detect_variant(value):
|
||||
if KEY_LIBRETINY not in CORE.data:
|
||||
@@ -346,4 +363,10 @@ async def component_to_code(config):
|
||||
cg.add_platformio_option("custom_fw_name", "esphome")
|
||||
cg.add_platformio_option("custom_fw_version", __version__)
|
||||
|
||||
# Apply chip-specific SDK options to save RAM/Flash
|
||||
if config[CONF_FAMILY] == FAMILY_BK7231N:
|
||||
cg.add_platformio_option(
|
||||
"custom_options.sys_config#h", _BK7231N_SYS_CONFIG_OPTIONS
|
||||
)
|
||||
|
||||
await cg.register_component(var, config)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include <cinttypes>
|
||||
|
||||
#include "light_call.h"
|
||||
#include "light_state.h"
|
||||
#include "esphome/core/log.h"
|
||||
@@ -153,15 +154,15 @@ void LightCall::perform() {
|
||||
|
||||
} else if (this->has_effect_()) {
|
||||
// EFFECT
|
||||
const char *effect_s;
|
||||
StringRef effect_s;
|
||||
if (this->effect_ == 0u) {
|
||||
effect_s = "None";
|
||||
effect_s = StringRef::from_lit("None");
|
||||
} else {
|
||||
effect_s = this->parent_->effects_[this->effect_ - 1]->get_name();
|
||||
}
|
||||
|
||||
if (publish) {
|
||||
ESP_LOGD(TAG, " Effect: '%s'", effect_s);
|
||||
ESP_LOGD(TAG, " Effect: '%.*s'", (int) effect_s.size(), effect_s.c_str());
|
||||
}
|
||||
|
||||
this->parent_->start_effect_(this->effect_);
|
||||
@@ -511,11 +512,9 @@ LightCall &LightCall::set_effect(const char *effect, size_t len) {
|
||||
}
|
||||
|
||||
bool found = false;
|
||||
StringRef effect_ref(effect, len);
|
||||
for (uint32_t i = 0; i < this->parent_->effects_.size(); i++) {
|
||||
LightEffect *e = this->parent_->effects_[i];
|
||||
const char *name = e->get_name();
|
||||
|
||||
if (strncasecmp(effect, name, len) == 0 && name[len] == '\0') {
|
||||
if (str_equals_case_insensitive(effect_ref, this->parent_->effects_[i]->get_name())) {
|
||||
this->set_effect(i + 1);
|
||||
found = true;
|
||||
break;
|
||||
|
||||
28
esphome/components/light/light_color_values.cpp
Normal file
28
esphome/components/light/light_color_values.cpp
Normal file
@@ -0,0 +1,28 @@
|
||||
#include "light_color_values.h"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
namespace esphome::light {
|
||||
|
||||
LightColorValues LightColorValues::lerp(const LightColorValues &start, const LightColorValues &end, float completion) {
|
||||
// Directly interpolate the raw values to avoid getter/setter overhead.
|
||||
// This is safe because:
|
||||
// - All LightColorValues have their values clamped when set via the setters
|
||||
// - std::lerp guarantees output is in the same range as inputs
|
||||
// - Therefore the output doesn't need clamping, so we can skip the setters
|
||||
LightColorValues v;
|
||||
v.color_mode_ = end.color_mode_;
|
||||
v.state_ = std::lerp(start.state_, end.state_, completion);
|
||||
v.brightness_ = std::lerp(start.brightness_, end.brightness_, completion);
|
||||
v.color_brightness_ = std::lerp(start.color_brightness_, end.color_brightness_, completion);
|
||||
v.red_ = std::lerp(start.red_, end.red_, completion);
|
||||
v.green_ = std::lerp(start.green_, end.green_, completion);
|
||||
v.blue_ = std::lerp(start.blue_, end.blue_, completion);
|
||||
v.white_ = std::lerp(start.white_, end.white_, completion);
|
||||
v.color_temperature_ = std::lerp(start.color_temperature_, end.color_temperature_, completion);
|
||||
v.cold_white_ = std::lerp(start.cold_white_, end.cold_white_, completion);
|
||||
v.warm_white_ = std::lerp(start.warm_white_, end.warm_white_, completion);
|
||||
return v;
|
||||
}
|
||||
|
||||
} // namespace esphome::light
|
||||
@@ -82,26 +82,7 @@ class LightColorValues {
|
||||
* @param completion The completion value. 0 -> start, 1 -> end.
|
||||
* @return The linearly interpolated LightColorValues.
|
||||
*/
|
||||
static LightColorValues lerp(const LightColorValues &start, const LightColorValues &end, float completion) {
|
||||
// Directly interpolate the raw values to avoid getter/setter overhead.
|
||||
// This is safe because:
|
||||
// - All LightColorValues have their values clamped when set via the setters
|
||||
// - std::lerp guarantees output is in the same range as inputs
|
||||
// - Therefore the output doesn't need clamping, so we can skip the setters
|
||||
LightColorValues v;
|
||||
v.color_mode_ = end.color_mode_;
|
||||
v.state_ = std::lerp(start.state_, end.state_, completion);
|
||||
v.brightness_ = std::lerp(start.brightness_, end.brightness_, completion);
|
||||
v.color_brightness_ = std::lerp(start.color_brightness_, end.color_brightness_, completion);
|
||||
v.red_ = std::lerp(start.red_, end.red_, completion);
|
||||
v.green_ = std::lerp(start.green_, end.green_, completion);
|
||||
v.blue_ = std::lerp(start.blue_, end.blue_, completion);
|
||||
v.white_ = std::lerp(start.white_, end.white_, completion);
|
||||
v.color_temperature_ = std::lerp(start.color_temperature_, end.color_temperature_, completion);
|
||||
v.cold_white_ = std::lerp(start.cold_white_, end.cold_white_, completion);
|
||||
v.warm_white_ = std::lerp(start.warm_white_, end.warm_white_, completion);
|
||||
return v;
|
||||
}
|
||||
static LightColorValues lerp(const LightColorValues &start, const LightColorValues &end, float completion);
|
||||
|
||||
/** Normalize the color (RGB/W) component.
|
||||
*
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/string_ref.h"
|
||||
|
||||
namespace esphome::light {
|
||||
|
||||
@@ -23,9 +24,9 @@ class LightEffect {
|
||||
|
||||
/**
|
||||
* Returns the name of this effect.
|
||||
* The returned pointer is valid for the lifetime of the program and must not be freed.
|
||||
* The underlying data is valid for the lifetime of the program (static string from codegen).
|
||||
*/
|
||||
const char *get_name() const { return this->name_; }
|
||||
StringRef get_name() const { return StringRef(this->name_); }
|
||||
|
||||
/// Internal method called by the LightState when this light effect is registered in it.
|
||||
virtual void init() {}
|
||||
|
||||
@@ -36,7 +36,7 @@ static const char *get_color_mode_json_str(ColorMode mode) {
|
||||
void LightJSONSchema::dump_json(LightState &state, JsonObject root) {
|
||||
// NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
|
||||
if (state.supports_effects()) {
|
||||
root[ESPHOME_F("effect")] = state.get_effect_name_ref();
|
||||
root[ESPHOME_F("effect")] = state.get_effect_name().c_str();
|
||||
root[ESPHOME_F("effect_index")] = state.get_current_effect_index();
|
||||
root[ESPHOME_F("effect_count")] = state.get_effect_count();
|
||||
}
|
||||
|
||||
@@ -162,20 +162,12 @@ void LightState::publish_state() {
|
||||
|
||||
LightOutput *LightState::get_output() const { return this->output_; }
|
||||
|
||||
static constexpr const char *EFFECT_NONE = "None";
|
||||
static constexpr auto EFFECT_NONE_REF = StringRef::from_lit("None");
|
||||
|
||||
std::string LightState::get_effect_name() {
|
||||
StringRef LightState::get_effect_name() {
|
||||
if (this->active_effect_index_ > 0) {
|
||||
return this->effects_[this->active_effect_index_ - 1]->get_name();
|
||||
}
|
||||
return EFFECT_NONE;
|
||||
}
|
||||
|
||||
StringRef LightState::get_effect_name_ref() {
|
||||
if (this->active_effect_index_ > 0) {
|
||||
return StringRef(this->effects_[this->active_effect_index_ - 1]->get_name());
|
||||
}
|
||||
return EFFECT_NONE_REF;
|
||||
}
|
||||
|
||||
|
||||
@@ -140,9 +140,7 @@ class LightState : public EntityBase, public Component {
|
||||
LightOutput *get_output() const;
|
||||
|
||||
/// Return the name of the current effect, or if no effect is active "None".
|
||||
std::string get_effect_name();
|
||||
/// Return the name of the current effect as StringRef (for API usage)
|
||||
StringRef get_effect_name_ref();
|
||||
StringRef get_effect_name();
|
||||
|
||||
/** Add a listener for remote values changes.
|
||||
* Listener is notified when the light's remote values change (state, brightness, color, etc.)
|
||||
@@ -191,11 +189,11 @@ class LightState : public EntityBase, public Component {
|
||||
|
||||
/// Get effect index by name. Returns 0 if effect not found.
|
||||
uint32_t get_effect_index(const std::string &effect_name) const {
|
||||
if (strcasecmp(effect_name.c_str(), "none") == 0) {
|
||||
if (str_equals_case_insensitive(effect_name, "none")) {
|
||||
return 0;
|
||||
}
|
||||
for (size_t i = 0; i < this->effects_.size(); i++) {
|
||||
if (strcasecmp(effect_name.c_str(), this->effects_[i]->get_name()) == 0) {
|
||||
if (str_equals_case_insensitive(effect_name, this->effects_[i]->get_name())) {
|
||||
return i + 1; // Effects are 1-indexed in active_effect_index_
|
||||
}
|
||||
}
|
||||
@@ -218,7 +216,7 @@ class LightState : public EntityBase, public Component {
|
||||
if (index > this->effects_.size()) {
|
||||
return ""; // Invalid index
|
||||
}
|
||||
return this->effects_[index - 1]->get_name();
|
||||
return std::string(this->effects_[index - 1]->get_name());
|
||||
}
|
||||
|
||||
/// The result of all the current_values_as_* methods have gamma correction applied.
|
||||
|
||||
@@ -16,7 +16,6 @@ from esphome.const import (
|
||||
CONF_REPEAT,
|
||||
CONF_TRIGGER_ID,
|
||||
CONF_TYPE,
|
||||
DEVICE_CLASS_DISTANCE,
|
||||
DEVICE_CLASS_ILLUMINANCE,
|
||||
ICON_BRIGHTNESS_5,
|
||||
ICON_BRIGHTNESS_6,
|
||||
@@ -169,7 +168,6 @@ CONFIG_SCHEMA = cv.All(
|
||||
unit_of_measurement=UNIT_COUNTS,
|
||||
icon=ICON_BRIGHTNESS_5,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_ILLUMINANCE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
key=CONF_NAME,
|
||||
@@ -179,7 +177,6 @@ CONFIG_SCHEMA = cv.All(
|
||||
unit_of_measurement=UNIT_COUNTS,
|
||||
icon=ICON_BRIGHTNESS_7,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_ILLUMINANCE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
key=CONF_NAME,
|
||||
@@ -189,7 +186,6 @@ CONFIG_SCHEMA = cv.All(
|
||||
unit_of_measurement=UNIT_COUNTS,
|
||||
icon=ICON_PROXIMITY,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_DISTANCE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
key=CONF_NAME,
|
||||
@@ -198,7 +194,6 @@ CONFIG_SCHEMA = cv.All(
|
||||
sensor.sensor_schema(
|
||||
icon=ICON_GAIN,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_ILLUMINANCE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
key=CONF_NAME,
|
||||
|
||||
@@ -65,12 +65,14 @@ void AirConditioner::control(const ClimateCall &call) {
|
||||
if (call.get_preset().has_value()) {
|
||||
ctrl.preset = Converters::to_midea_preset(call.get_preset().value());
|
||||
} else if (call.has_custom_preset()) {
|
||||
ctrl.preset = Converters::to_midea_preset(call.get_custom_preset());
|
||||
// get_custom_preset() returns StringRef pointing to null-terminated string literals from codegen
|
||||
ctrl.preset = Converters::to_midea_preset(call.get_custom_preset().c_str());
|
||||
}
|
||||
if (call.get_fan_mode().has_value()) {
|
||||
ctrl.fanMode = Converters::to_midea_fan_mode(call.get_fan_mode().value());
|
||||
} else if (call.has_custom_fan_mode()) {
|
||||
ctrl.fanMode = Converters::to_midea_fan_mode(call.get_custom_fan_mode());
|
||||
// get_custom_fan_mode() returns StringRef pointing to null-terminated string literals from codegen
|
||||
ctrl.fanMode = Converters::to_midea_fan_mode(call.get_custom_fan_mode().c_str());
|
||||
}
|
||||
this->base_.control(ctrl);
|
||||
}
|
||||
|
||||
@@ -12,8 +12,9 @@ bool CustomMQTTDevice::publish(const std::string &topic, const std::string &payl
|
||||
return global_mqtt_client->publish(topic, payload, qos, retain);
|
||||
}
|
||||
bool CustomMQTTDevice::publish(const std::string &topic, float value, int8_t number_decimals) {
|
||||
auto str = value_accuracy_to_string(value, number_decimals);
|
||||
return this->publish(topic, str);
|
||||
char buf[VALUE_ACCURACY_MAX_LEN];
|
||||
value_accuracy_to_buf(buf, value, number_decimals);
|
||||
return this->publish(topic, buf);
|
||||
}
|
||||
bool CustomMQTTDevice::publish(const std::string &topic, int value) {
|
||||
char buffer[24];
|
||||
|
||||
@@ -28,8 +28,9 @@ static const char *const TAG = "mqtt";
|
||||
|
||||
MQTTClientComponent::MQTTClientComponent() {
|
||||
global_mqtt_client = this;
|
||||
const std::string mac_addr = get_mac_address();
|
||||
this->credentials_.client_id = make_name_with_suffix(App.get_name(), '-', mac_addr.c_str(), mac_addr.size());
|
||||
char mac_addr[MAC_ADDRESS_BUFFER_SIZE];
|
||||
get_mac_address_into_buffer(mac_addr);
|
||||
this->credentials_.client_id = make_name_with_suffix(App.get_name(), '-', mac_addr, MAC_ADDRESS_BUFFER_SIZE - 1);
|
||||
}
|
||||
|
||||
// Connection
|
||||
@@ -102,7 +103,9 @@ void MQTTClientComponent::send_device_info_() {
|
||||
root[ESPHOME_F("port")] = api::global_api_server->get_port();
|
||||
#endif
|
||||
root[ESPHOME_F("version")] = ESPHOME_VERSION;
|
||||
root[ESPHOME_F("mac")] = get_mac_address();
|
||||
char mac_buf[MAC_ADDRESS_BUFFER_SIZE];
|
||||
get_mac_address_into_buffer(mac_buf);
|
||||
root[ESPHOME_F("mac")] = mac_buf;
|
||||
|
||||
#ifdef USE_ESP8266
|
||||
root[ESPHOME_F("platform")] = ESPHOME_F("ESP8266");
|
||||
|
||||
@@ -291,35 +291,36 @@ bool MQTTClimateComponent::publish_state_() {
|
||||
success = false;
|
||||
int8_t target_accuracy = traits.get_target_temperature_accuracy_decimals();
|
||||
int8_t current_accuracy = traits.get_current_temperature_accuracy_decimals();
|
||||
char payload[VALUE_ACCURACY_MAX_LEN];
|
||||
if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_CURRENT_TEMPERATURE) &&
|
||||
!std::isnan(this->device_->current_temperature)) {
|
||||
std::string payload = value_accuracy_to_string(this->device_->current_temperature, current_accuracy);
|
||||
value_accuracy_to_buf(payload, this->device_->current_temperature, current_accuracy);
|
||||
if (!this->publish(this->get_current_temperature_state_topic(), payload))
|
||||
success = false;
|
||||
}
|
||||
if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_TWO_POINT_TARGET_TEMPERATURE |
|
||||
climate::CLIMATE_REQUIRES_TWO_POINT_TARGET_TEMPERATURE)) {
|
||||
std::string payload = value_accuracy_to_string(this->device_->target_temperature_low, target_accuracy);
|
||||
value_accuracy_to_buf(payload, this->device_->target_temperature_low, target_accuracy);
|
||||
if (!this->publish(this->get_target_temperature_low_state_topic(), payload))
|
||||
success = false;
|
||||
payload = value_accuracy_to_string(this->device_->target_temperature_high, target_accuracy);
|
||||
value_accuracy_to_buf(payload, this->device_->target_temperature_high, target_accuracy);
|
||||
if (!this->publish(this->get_target_temperature_high_state_topic(), payload))
|
||||
success = false;
|
||||
} else {
|
||||
std::string payload = value_accuracy_to_string(this->device_->target_temperature, target_accuracy);
|
||||
value_accuracy_to_buf(payload, this->device_->target_temperature, target_accuracy);
|
||||
if (!this->publish(this->get_target_temperature_state_topic(), payload))
|
||||
success = false;
|
||||
}
|
||||
|
||||
if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_CURRENT_HUMIDITY) &&
|
||||
!std::isnan(this->device_->current_humidity)) {
|
||||
std::string payload = value_accuracy_to_string(this->device_->current_humidity, 0);
|
||||
value_accuracy_to_buf(payload, this->device_->current_humidity, 0);
|
||||
if (!this->publish(this->get_current_humidity_state_topic(), payload))
|
||||
success = false;
|
||||
}
|
||||
if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_TARGET_HUMIDITY) &&
|
||||
!std::isnan(this->device_->target_humidity)) {
|
||||
std::string payload = value_accuracy_to_string(this->device_->target_humidity, 0);
|
||||
value_accuracy_to_buf(payload, this->device_->target_humidity, 0);
|
||||
if (!this->publish(this->get_target_humidity_state_topic(), payload))
|
||||
success = false;
|
||||
}
|
||||
@@ -357,7 +358,7 @@ bool MQTTClimateComponent::publish_state_() {
|
||||
}
|
||||
}
|
||||
if (this->device_->has_custom_preset())
|
||||
payload = this->device_->get_custom_preset();
|
||||
payload = this->device_->get_custom_preset().c_str();
|
||||
if (!this->publish(this->get_preset_state_topic(), payload))
|
||||
success = false;
|
||||
}
|
||||
@@ -429,7 +430,7 @@ bool MQTTClimateComponent::publish_state_() {
|
||||
}
|
||||
}
|
||||
if (this->device_->has_custom_fan_mode())
|
||||
payload = this->device_->get_custom_fan_mode();
|
||||
payload = this->device_->get_custom_fan_mode().c_str();
|
||||
if (!this->publish(this->get_fan_mode_state_topic(), payload))
|
||||
success = false;
|
||||
}
|
||||
|
||||
@@ -187,7 +187,13 @@ bool MQTTComponent::send_discovery_() {
|
||||
char friendly_name_hash[9];
|
||||
sprintf(friendly_name_hash, "%08" PRIx32, fnv1_hash(this->friendly_name_()));
|
||||
friendly_name_hash[8] = 0; // ensure the hash-string ends with null
|
||||
root[MQTT_UNIQUE_ID] = get_mac_address() + "-" + this->component_type() + "-" + friendly_name_hash;
|
||||
// Format: mac-component_type-hash (e.g. "aabbccddeeff-sensor-12345678")
|
||||
// MAC (12) + "-" (1) + domain (max 20) + "-" (1) + hash (8) + null (1) = 43
|
||||
char unique_id[MAC_ADDRESS_BUFFER_SIZE + ESPHOME_DOMAIN_MAX_LEN + 11];
|
||||
char mac_buf[MAC_ADDRESS_BUFFER_SIZE];
|
||||
get_mac_address_into_buffer(mac_buf);
|
||||
snprintf(unique_id, sizeof(unique_id), "%s-%s-%s", mac_buf, this->component_type(), friendly_name_hash);
|
||||
root[MQTT_UNIQUE_ID] = unique_id;
|
||||
} else {
|
||||
// default to almost-unique ID. It's a hack but the only way to get that
|
||||
// gorgeous device registry view.
|
||||
@@ -203,7 +209,8 @@ bool MQTTComponent::send_discovery_() {
|
||||
std::string node_area = App.get_area();
|
||||
|
||||
JsonObject device_info = root[MQTT_DEVICE].to<JsonObject>();
|
||||
const auto mac = get_mac_address();
|
||||
char mac[MAC_ADDRESS_BUFFER_SIZE];
|
||||
get_mac_address_into_buffer(mac);
|
||||
device_info[MQTT_DEVICE_IDENTIFIERS] = mac;
|
||||
device_info[MQTT_DEVICE_NAME] = node_friendly_name;
|
||||
#ifdef ESPHOME_PROJECT_NAME
|
||||
|
||||
@@ -98,12 +98,14 @@ bool MQTTCoverComponent::publish_state() {
|
||||
auto traits = this->cover_->get_traits();
|
||||
bool success = true;
|
||||
if (traits.get_supports_position()) {
|
||||
std::string pos = value_accuracy_to_string(roundf(this->cover_->position * 100), 0);
|
||||
char pos[VALUE_ACCURACY_MAX_LEN];
|
||||
value_accuracy_to_buf(pos, roundf(this->cover_->position * 100), 0);
|
||||
if (!this->publish(this->get_position_state_topic(), pos))
|
||||
success = false;
|
||||
}
|
||||
if (traits.get_supports_tilt()) {
|
||||
std::string pos = value_accuracy_to_string(roundf(this->cover_->tilt * 100), 0);
|
||||
char pos[VALUE_ACCURACY_MAX_LEN];
|
||||
value_accuracy_to_buf(pos, roundf(this->cover_->tilt * 100), 0);
|
||||
if (!this->publish(this->get_tilt_state_topic(), pos))
|
||||
success = false;
|
||||
}
|
||||
|
||||
@@ -80,8 +80,10 @@ void MQTTJSONLightComponent::send_discovery(JsonObject root, mqtt::SendDiscovery
|
||||
if (this->state_->supports_effects()) {
|
||||
root[ESPHOME_F("effect")] = true;
|
||||
JsonArray effect_list = root[MQTT_EFFECT_LIST].to<JsonArray>();
|
||||
for (auto *effect : this->state_->get_effects())
|
||||
effect_list.add(effect->get_name());
|
||||
for (auto *effect : this->state_->get_effects()) {
|
||||
// c_str() is safe as effect names are null-terminated strings from codegen
|
||||
effect_list.add(effect->get_name().c_str());
|
||||
}
|
||||
effect_list.add(ESPHOME_F("None"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,7 +43,8 @@ void MQTTSelectComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryCon
|
||||
}
|
||||
bool MQTTSelectComponent::send_initial_state() {
|
||||
if (this->select_->has_state()) {
|
||||
return this->publish_state(this->select_->current_option());
|
||||
auto option = this->select_->current_option();
|
||||
return this->publish_state(std::string(option.c_str(), option.size()));
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -82,7 +82,9 @@ bool MQTTSensorComponent::publish_state(float value) {
|
||||
if (mqtt::global_mqtt_client->is_publish_nan_as_none() && std::isnan(value))
|
||||
return this->publish(this->get_state_topic_(), "None");
|
||||
int8_t accuracy = this->sensor_->get_accuracy_decimals();
|
||||
return this->publish(this->get_state_topic_(), value_accuracy_to_string(value, accuracy));
|
||||
char buf[VALUE_ACCURACY_MAX_LEN];
|
||||
value_accuracy_to_buf(buf, value, accuracy);
|
||||
return this->publish(this->get_state_topic_(), buf);
|
||||
}
|
||||
|
||||
} // namespace esphome::mqtt
|
||||
|
||||
@@ -73,7 +73,8 @@ bool MQTTValveComponent::publish_state() {
|
||||
auto traits = this->valve_->get_traits();
|
||||
bool success = true;
|
||||
if (traits.get_supports_position()) {
|
||||
std::string pos = value_accuracy_to_string(roundf(this->valve_->position * 100), 0);
|
||||
char pos[VALUE_ACCURACY_MAX_LEN];
|
||||
value_accuracy_to_buf(pos, roundf(this->valve_->position * 100), 0);
|
||||
if (!this->publish(this->get_position_state_topic(), pos))
|
||||
success = false;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
#include "automation.h"
|
||||
#include "nfc.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace nfc {
|
||||
|
||||
void NfcOnTagTrigger::process(const std::unique_ptr<NfcTag> &tag) { this->trigger(format_uid(tag->get_uid()), *tag); }
|
||||
void NfcOnTagTrigger::process(const std::unique_ptr<NfcTag> &tag) {
|
||||
char uid_buf[FORMAT_UID_BUFFER_SIZE];
|
||||
this->trigger(std::string(format_uid_to(uid_buf, tag->get_uid())), *tag);
|
||||
}
|
||||
|
||||
} // namespace nfc
|
||||
} // namespace esphome
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "nfc_binary_sensor.h"
|
||||
#include "../nfc.h"
|
||||
#include "../nfc_helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
@@ -24,7 +25,8 @@ void NfcTagBinarySensor::dump_config() {
|
||||
return;
|
||||
}
|
||||
if (!this->uid_.empty()) {
|
||||
ESP_LOGCONFIG(TAG, " Tag UID: %s", format_bytes(this->uid_).c_str());
|
||||
char uid_buf[FORMAT_BYTES_BUFFER_SIZE];
|
||||
ESP_LOGCONFIG(TAG, " Tag UID: %s", format_bytes_to(uid_buf, this->uid_));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,9 +8,20 @@ namespace nfc {
|
||||
|
||||
static const char *const TAG = "nfc";
|
||||
|
||||
char *format_uid_to(char *buffer, const std::vector<uint8_t> &uid) {
|
||||
return format_hex_pretty_to(buffer, FORMAT_UID_BUFFER_SIZE, uid.data(), uid.size(), '-');
|
||||
}
|
||||
|
||||
char *format_bytes_to(char *buffer, const std::vector<uint8_t> &bytes) {
|
||||
return format_hex_pretty_to(buffer, FORMAT_BYTES_BUFFER_SIZE, bytes.data(), bytes.size(), ' ');
|
||||
}
|
||||
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
||||
std::string format_uid(const std::vector<uint8_t> &uid) { return format_hex_pretty(uid, '-', false); }
|
||||
|
||||
std::string format_bytes(const std::vector<uint8_t> &bytes) { return format_hex_pretty(bytes, ' ', false); }
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
uint8_t guess_tag_type(uint8_t uid_length) {
|
||||
if (uid_length == 4) {
|
||||
|
||||
@@ -53,7 +53,21 @@ static const uint8_t DEFAULT_KEY[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
|
||||
static const uint8_t NDEF_KEY[6] = {0xD3, 0xF7, 0xD3, 0xF7, 0xD3, 0xF7};
|
||||
static const uint8_t MAD_KEY[6] = {0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5};
|
||||
|
||||
/// Max UID size is 10 bytes, formatted as "XX-XX-XX-XX-XX-XX-XX-XX-XX-XX\0" = 30 chars
|
||||
static constexpr size_t FORMAT_UID_BUFFER_SIZE = 30;
|
||||
/// Format UID to buffer with '-' separator (e.g., "04-11-22-33"). Returns buffer for inline use.
|
||||
char *format_uid_to(char *buffer, const std::vector<uint8_t> &uid);
|
||||
|
||||
/// Buffer size for format_bytes_to (64 bytes max = 192 chars with space separator)
|
||||
static constexpr size_t FORMAT_BYTES_BUFFER_SIZE = 192;
|
||||
/// Format bytes to buffer with ' ' separator (e.g., "04 11 22 33"). Returns buffer for inline use.
|
||||
char *format_bytes_to(char *buffer, const std::vector<uint8_t> &bytes);
|
||||
|
||||
// Remove before 2026.6.0
|
||||
ESPDEPRECATED("Use format_uid_to() with stack buffer instead. Removed in 2026.6.0", "2025.12.0")
|
||||
std::string format_uid(const std::vector<uint8_t> &uid);
|
||||
// Remove before 2026.6.0
|
||||
ESPDEPRECATED("Use format_bytes_to() with stack buffer instead. Removed in 2026.6.0", "2025.12.0")
|
||||
std::string format_bytes(const std::vector<uint8_t> &bytes);
|
||||
|
||||
uint8_t guess_tag_type(uint8_t uid_length);
|
||||
|
||||
@@ -8,6 +8,7 @@ from esphome import pins
|
||||
import esphome.codegen as cg
|
||||
from esphome.components.zephyr import (
|
||||
copy_files as zephyr_copy_files,
|
||||
zephyr_add_overlay,
|
||||
zephyr_add_pm_static,
|
||||
zephyr_add_prj_conf,
|
||||
zephyr_data,
|
||||
@@ -26,6 +27,7 @@ from esphome.const import (
|
||||
CONF_FRAMEWORK,
|
||||
CONF_ID,
|
||||
CONF_RESET_PIN,
|
||||
CONF_VERSION,
|
||||
CONF_VOLTAGE,
|
||||
KEY_CORE,
|
||||
KEY_FRAMEWORK_VERSION,
|
||||
@@ -59,7 +61,6 @@ def set_core_data(config: ConfigType) -> ConfigType:
|
||||
zephyr_set_core_data(config)
|
||||
CORE.data[KEY_CORE][KEY_TARGET_PLATFORM] = PLATFORM_NRF52
|
||||
CORE.data[KEY_CORE][KEY_TARGET_FRAMEWORK] = KEY_ZEPHYR
|
||||
CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] = cv.Version(2, 6, 1)
|
||||
|
||||
if config[KEY_BOOTLOADER] in BOOTLOADER_CONFIG:
|
||||
zephyr_add_pm_static(BOOTLOADER_CONFIG[config[KEY_BOOTLOADER]])
|
||||
@@ -67,6 +68,12 @@ def set_core_data(config: ConfigType) -> ConfigType:
|
||||
return config
|
||||
|
||||
|
||||
def set_framework(config: ConfigType) -> ConfigType:
|
||||
version = cv.Version.parse(cv.version_number(config[CONF_FRAMEWORK][CONF_VERSION]))
|
||||
CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] = version
|
||||
return config
|
||||
|
||||
|
||||
BOOTLOADERS = [
|
||||
BOOTLOADER_ADAFRUIT,
|
||||
BOOTLOADER_ADAFRUIT_NRF52_SD132,
|
||||
@@ -133,8 +140,14 @@ CONFIG_SCHEMA = cv.All(
|
||||
cv.Optional(CONF_UICR_ERASE, default=False): cv.boolean,
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_FRAMEWORK, default={CONF_VERSION: "2.6.1-7"}): cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_VERSION): cv.string_strict,
|
||||
}
|
||||
),
|
||||
}
|
||||
),
|
||||
set_framework,
|
||||
)
|
||||
|
||||
|
||||
@@ -173,7 +186,7 @@ async def to_code(config: ConfigType) -> None:
|
||||
cg.add_platformio_option(
|
||||
"platform_packages",
|
||||
[
|
||||
"platformio/framework-zephyr@https://github.com/tomaszduda23/framework-sdk-nrf/archive/refs/tags/v2.6.1-7.zip",
|
||||
f"platformio/framework-zephyr@https://github.com/tomaszduda23/framework-sdk-nrf/archive/refs/tags/v{CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION]}.zip",
|
||||
"platformio/toolchain-gccarmnoneeabi@https://github.com/tomaszduda23/toolchain-sdk-ng/archive/refs/tags/v0.17.4-0.zip",
|
||||
],
|
||||
)
|
||||
@@ -200,7 +213,17 @@ async def to_code(config: ConfigType) -> None:
|
||||
|
||||
if dfu_config := config.get(CONF_DFU):
|
||||
CORE.add_job(_dfu_to_code, dfu_config)
|
||||
zephyr_add_prj_conf("BOARD_ENABLE_DCDC", config[CONF_DCDC])
|
||||
framework_ver: cv.Version = CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION]
|
||||
if framework_ver < cv.Version(2, 9, 2):
|
||||
zephyr_add_prj_conf("BOARD_ENABLE_DCDC", config[CONF_DCDC])
|
||||
else:
|
||||
zephyr_add_overlay(
|
||||
f"""
|
||||
®1 {{
|
||||
regulator-initial-mode = <{"NRF5X_REG_MODE_DCDC" if config[CONF_DCDC] else "NRF5X_REG_MODE_LDO"}>;
|
||||
}};
|
||||
"""
|
||||
)
|
||||
|
||||
if reg0_config := config.get(CONF_REG0):
|
||||
value = VOLTAGE_LEVELS.index(reg0_config[CONF_VOLTAGE])
|
||||
@@ -209,8 +232,12 @@ async def to_code(config: ConfigType) -> None:
|
||||
cg.add_define("USE_NRF52_UICR_ERASE")
|
||||
|
||||
# c++ support
|
||||
zephyr_add_prj_conf("CPLUSPLUS", True)
|
||||
zephyr_add_prj_conf("LIB_CPLUSPLUS", True)
|
||||
if framework_ver < cv.Version(2, 9, 2):
|
||||
zephyr_add_prj_conf("CPLUSPLUS", True)
|
||||
zephyr_add_prj_conf("LIB_CPLUSPLUS", True)
|
||||
else:
|
||||
zephyr_add_prj_conf("CPP", True)
|
||||
zephyr_add_prj_conf("REQUIRES_FULL_LIBCPP", True)
|
||||
# watchdog
|
||||
zephyr_add_prj_conf("WATCHDOG", True)
|
||||
zephyr_add_prj_conf("WDT_DISABLE_AT_BOOT", False)
|
||||
@@ -218,7 +245,16 @@ async def to_code(config: ConfigType) -> None:
|
||||
zephyr_add_prj_conf("UART_CONSOLE", False)
|
||||
zephyr_add_prj_conf("CONSOLE", False)
|
||||
# use NFC pins as GPIO
|
||||
zephyr_add_prj_conf("NFCT_PINS_AS_GPIOS", True)
|
||||
if framework_ver < cv.Version(2, 9, 2):
|
||||
zephyr_add_prj_conf("NFCT_PINS_AS_GPIOS", True)
|
||||
else:
|
||||
zephyr_add_overlay(
|
||||
"""
|
||||
&uicr {
|
||||
nfct-pins-as-gpios;
|
||||
};
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
@coroutine_with_priority(CoroPriority.DIAGNOSTICS)
|
||||
|
||||
@@ -6,8 +6,10 @@ namespace one_wire {
|
||||
static const char *const TAG = "one_wire";
|
||||
|
||||
const std::string &OneWireDevice::get_address_name() {
|
||||
if (this->address_name_.empty())
|
||||
this->address_name_ = std::string("0x") + format_hex(this->address_);
|
||||
if (this->address_name_.empty()) {
|
||||
char hex_buf[19]; // "0x" + 16 hex chars + null
|
||||
this->address_name_ = format_hex_prefixed_to(hex_buf, this->address_);
|
||||
}
|
||||
return this->address_name_;
|
||||
}
|
||||
|
||||
|
||||
@@ -274,7 +274,7 @@ void PacketTransport::flush_() {
|
||||
|
||||
void PacketTransport::add_binary_data_(uint8_t key, const char *id, bool data) {
|
||||
auto len = 1 + 1 + 1 + strlen(id);
|
||||
if (len + this->header_.size() + this->data_.size() > this->get_max_packet_size()) {
|
||||
if (round4(this->header_.size()) + round4(this->data_.size() + len) > this->get_max_packet_size()) {
|
||||
this->flush_();
|
||||
this->init_data_();
|
||||
}
|
||||
@@ -289,7 +289,7 @@ void PacketTransport::add_data_(uint8_t key, const char *id, float data) {
|
||||
|
||||
void PacketTransport::add_data_(uint8_t key, const char *id, uint32_t data) {
|
||||
auto len = 4 + 1 + 1 + strlen(id);
|
||||
if (len + this->header_.size() + this->data_.size() > this->get_max_packet_size()) {
|
||||
if (round4(this->header_.size()) + round4(this->data_.size() + len) > this->get_max_packet_size()) {
|
||||
this->flush_();
|
||||
this->init_data_();
|
||||
}
|
||||
|
||||
@@ -197,7 +197,8 @@ void PN532::loop() {
|
||||
trigger->process(tag);
|
||||
|
||||
if (report) {
|
||||
ESP_LOGD(TAG, "Found new tag '%s'", nfc::format_uid(nfcid).c_str());
|
||||
char uid_buf[nfc::FORMAT_UID_BUFFER_SIZE];
|
||||
ESP_LOGD(TAG, "Found new tag '%s'", nfc::format_uid_to(uid_buf, nfcid));
|
||||
if (tag->has_ndef_message()) {
|
||||
const auto &message = tag->get_ndef_message();
|
||||
const auto &records = message->get_records();
|
||||
|
||||
@@ -77,7 +77,8 @@ bool PN532::read_mifare_classic_block_(uint8_t block_num, std::vector<uint8_t> &
|
||||
}
|
||||
data.erase(data.begin());
|
||||
|
||||
ESP_LOGVV(TAG, " Block %d: %s", block_num, nfc::format_bytes(data).c_str());
|
||||
char data_buf[nfc::FORMAT_BYTES_BUFFER_SIZE];
|
||||
ESP_LOGVV(TAG, " Block %d: %s", block_num, nfc::format_bytes_to(data_buf, data));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -71,7 +71,8 @@ bool PN532::read_mifare_ultralight_bytes_(uint8_t start_page, uint16_t num_bytes
|
||||
}
|
||||
}
|
||||
|
||||
ESP_LOGVV(TAG, "Data read: %s", nfc::format_bytes(data).c_str());
|
||||
char data_buf[nfc::FORMAT_BYTES_BUFFER_SIZE];
|
||||
ESP_LOGVV(TAG, "Data read: %s", nfc::format_bytes_to(data_buf, data));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -203,7 +203,8 @@ uint8_t PN7150::set_test_mode(const TestMode test_mode, const std::vector<uint8_
|
||||
result = rx.get_message();
|
||||
result.erase(result.begin(), result.begin() + 4); // remove NCI header
|
||||
if (!result.empty()) {
|
||||
ESP_LOGW(TAG, "Test results: %s", nfc::format_bytes(result).c_str());
|
||||
char buf[nfc::FORMAT_BYTES_BUFFER_SIZE];
|
||||
ESP_LOGW(TAG, "Test results: %s", nfc::format_bytes_to(buf, result));
|
||||
}
|
||||
}
|
||||
return status;
|
||||
@@ -229,14 +230,16 @@ uint8_t PN7150::reset_core_(const bool reset_config, const bool power) {
|
||||
}
|
||||
|
||||
if (!rx.simple_status_response_is(nfc::STATUS_OK)) {
|
||||
ESP_LOGE(TAG, "Invalid reset response: %s", nfc::format_bytes(rx.get_message()).c_str());
|
||||
char buf[nfc::FORMAT_BYTES_BUFFER_SIZE];
|
||||
ESP_LOGE(TAG, "Invalid reset response: %s", nfc::format_bytes_to(buf, rx.get_message()));
|
||||
return rx.get_simple_status_response();
|
||||
}
|
||||
// verify reset response
|
||||
if ((!rx.message_type_is(nfc::NCI_PKT_MT_CTRL_RESPONSE)) || (!rx.message_length_is(3)) ||
|
||||
(rx.get_message()[nfc::NCI_PKT_PAYLOAD_OFFSET + 1] != 0x11) ||
|
||||
(rx.get_message()[nfc::NCI_PKT_PAYLOAD_OFFSET + 2] != (uint8_t) reset_config)) {
|
||||
ESP_LOGE(TAG, "Reset response was malformed: %s", nfc::format_bytes(rx.get_message()).c_str());
|
||||
char buf[nfc::FORMAT_BYTES_BUFFER_SIZE];
|
||||
ESP_LOGE(TAG, "Reset response was malformed: %s", nfc::format_bytes_to(buf, rx.get_message()));
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
|
||||
@@ -259,7 +262,8 @@ uint8_t PN7150::init_core_() {
|
||||
}
|
||||
|
||||
if (!rx.simple_status_response_is(nfc::STATUS_OK)) {
|
||||
ESP_LOGE(TAG, "Invalid initialise response: %s", nfc::format_bytes(rx.get_message()).c_str());
|
||||
char buf[nfc::FORMAT_BYTES_BUFFER_SIZE];
|
||||
ESP_LOGE(TAG, "Invalid initialise response: %s", nfc::format_bytes_to(buf, rx.get_message()));
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
|
||||
@@ -574,7 +578,8 @@ void PN7150::erase_tag_(const uint8_t tag_index) {
|
||||
for (auto *listener : this->tag_listeners_) {
|
||||
listener->tag_off(*this->discovered_endpoint_[tag_index].tag);
|
||||
}
|
||||
ESP_LOGI(TAG, "Tag %s removed", nfc::format_uid(this->discovered_endpoint_[tag_index].tag->get_uid()).c_str());
|
||||
char uid_buf[nfc::FORMAT_UID_BUFFER_SIZE];
|
||||
ESP_LOGI(TAG, "Tag %s removed", nfc::format_uid_to(uid_buf, this->discovered_endpoint_[tag_index].tag->get_uid()));
|
||||
this->discovered_endpoint_.erase(this->discovered_endpoint_.begin() + tag_index);
|
||||
}
|
||||
}
|
||||
@@ -772,26 +777,33 @@ void PN7150::process_message_() {
|
||||
ESP_LOGV(TAG, "Unimplemented NCI Core OID received: 0x%02X", rx.get_oid());
|
||||
}
|
||||
} else {
|
||||
ESP_LOGV(TAG, "Unimplemented notification: %s", nfc::format_bytes(rx.get_message()).c_str());
|
||||
char buf[nfc::FORMAT_BYTES_BUFFER_SIZE];
|
||||
ESP_LOGV(TAG, "Unimplemented notification: %s", nfc::format_bytes_to(buf, rx.get_message()));
|
||||
}
|
||||
break;
|
||||
|
||||
case nfc::NCI_PKT_MT_CTRL_RESPONSE:
|
||||
case nfc::NCI_PKT_MT_CTRL_RESPONSE: {
|
||||
char buf[nfc::FORMAT_BYTES_BUFFER_SIZE];
|
||||
ESP_LOGV(TAG, "Unimplemented GID: 0x%02X OID: 0x%02X Full response: %s", rx.get_gid(), rx.get_oid(),
|
||||
nfc::format_bytes(rx.get_message()).c_str());
|
||||
nfc::format_bytes_to(buf, rx.get_message()));
|
||||
break;
|
||||
}
|
||||
|
||||
case nfc::NCI_PKT_MT_CTRL_COMMAND:
|
||||
ESP_LOGV(TAG, "Unimplemented command: %s", nfc::format_bytes(rx.get_message()).c_str());
|
||||
case nfc::NCI_PKT_MT_CTRL_COMMAND: {
|
||||
char buf[nfc::FORMAT_BYTES_BUFFER_SIZE];
|
||||
ESP_LOGV(TAG, "Unimplemented command: %s", nfc::format_bytes_to(buf, rx.get_message()));
|
||||
break;
|
||||
}
|
||||
|
||||
case nfc::NCI_PKT_MT_DATA:
|
||||
this->process_data_message_(rx);
|
||||
break;
|
||||
|
||||
default:
|
||||
ESP_LOGV(TAG, "Unimplemented message type: %s", nfc::format_bytes(rx.get_message()).c_str());
|
||||
default: {
|
||||
char buf[nfc::FORMAT_BYTES_BUFFER_SIZE];
|
||||
ESP_LOGV(TAG, "Unimplemented message type: %s", nfc::format_bytes_to(buf, rx.get_message()));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -872,8 +884,9 @@ void PN7150::process_rf_intf_activated_oid_(nfc::NciMessage &rx) { // an endpoi
|
||||
case EP_READ:
|
||||
default:
|
||||
if (!working_endpoint.trig_called) {
|
||||
char uid_buf[nfc::FORMAT_UID_BUFFER_SIZE];
|
||||
ESP_LOGI(TAG, "Read tag type %s with UID %s", working_endpoint.tag->get_tag_type().c_str(),
|
||||
nfc::format_uid(working_endpoint.tag->get_uid()).c_str());
|
||||
nfc::format_uid_to(uid_buf, working_endpoint.tag->get_uid()));
|
||||
if (this->read_endpoint_data_(*working_endpoint.tag) != nfc::STATUS_OK) {
|
||||
ESP_LOGW(TAG, " Unable to read NDEF record(s)");
|
||||
} else if (working_endpoint.tag->has_ndef_message()) {
|
||||
@@ -964,7 +977,8 @@ void PN7150::process_rf_deactivate_oid_(nfc::NciMessage &rx) {
|
||||
}
|
||||
|
||||
void PN7150::process_data_message_(nfc::NciMessage &rx) {
|
||||
ESP_LOGVV(TAG, "Received data message: %s", nfc::format_bytes(rx.get_message()).c_str());
|
||||
char buf[nfc::FORMAT_BYTES_BUFFER_SIZE];
|
||||
ESP_LOGVV(TAG, "Received data message: %s", nfc::format_bytes_to(buf, rx.get_message()));
|
||||
|
||||
std::vector<uint8_t> ndef_response;
|
||||
this->card_emu_t4t_get_response_(rx.get_message(), ndef_response);
|
||||
@@ -978,7 +992,7 @@ void PN7150::process_data_message_(nfc::NciMessage &rx) {
|
||||
uint8_t(ndef_response_size & 0x00FF)};
|
||||
tx_msg.insert(tx_msg.end(), ndef_response.begin(), ndef_response.end());
|
||||
nfc::NciMessage tx(tx_msg);
|
||||
ESP_LOGVV(TAG, "Sending data message: %s", nfc::format_bytes(tx.get_message()).c_str());
|
||||
ESP_LOGVV(TAG, "Sending data message: %s", nfc::format_bytes_to(buf, tx.get_message()));
|
||||
if (this->transceive_(tx, rx, NFCC_DEFAULT_TIMEOUT, false) != nfc::STATUS_OK) {
|
||||
ESP_LOGE(TAG, "Sending reply for card emulation failed");
|
||||
}
|
||||
@@ -1031,7 +1045,8 @@ void PN7150::card_emu_t4t_get_response_(std::vector<uint8_t> &response, std::vec
|
||||
uint16_t offset = (response[nfc::NCI_PKT_HEADER_SIZE + 2] << 8) + response[nfc::NCI_PKT_HEADER_SIZE + 3];
|
||||
uint8_t length = response[nfc::NCI_PKT_HEADER_SIZE + 4];
|
||||
|
||||
ESP_LOGVV(TAG, "Encoded NDEF message: %s", nfc::format_bytes(ndef_message).c_str());
|
||||
char ndef_buf[nfc::FORMAT_BYTES_BUFFER_SIZE];
|
||||
ESP_LOGVV(TAG, "Encoded NDEF message: %s", nfc::format_bytes_to(ndef_buf, ndef_message));
|
||||
|
||||
if (length <= (ndef_msg_size + offset + 2)) {
|
||||
if (offset == 0) {
|
||||
@@ -1070,7 +1085,8 @@ void PN7150::card_emu_t4t_get_response_(std::vector<uint8_t> &response, std::vec
|
||||
|
||||
ndef_msg_written.insert(ndef_msg_written.end(), response.begin() + nfc::NCI_PKT_HEADER_SIZE + 5,
|
||||
response.begin() + nfc::NCI_PKT_HEADER_SIZE + 5 + length);
|
||||
ESP_LOGD(TAG, "Received %u-byte NDEF message: %s", length, nfc::format_bytes(ndef_msg_written).c_str());
|
||||
char ndef_buf[nfc::FORMAT_BYTES_BUFFER_SIZE];
|
||||
ESP_LOGD(TAG, "Received %u-byte NDEF message: %s", length, nfc::format_bytes_to(ndef_buf, ndef_msg_written));
|
||||
ndef_response.insert(ndef_response.end(), std::begin(CARD_EMU_T4T_OK), std::end(CARD_EMU_T4T_OK));
|
||||
}
|
||||
}
|
||||
@@ -1079,6 +1095,7 @@ void PN7150::card_emu_t4t_get_response_(std::vector<uint8_t> &response, std::vec
|
||||
uint8_t PN7150::transceive_(nfc::NciMessage &tx, nfc::NciMessage &rx, const uint16_t timeout,
|
||||
const bool expect_notification) {
|
||||
uint8_t retries = NFCC_MAX_COMM_FAILS;
|
||||
char buf[nfc::FORMAT_BYTES_BUFFER_SIZE];
|
||||
|
||||
while (retries) {
|
||||
// first, send the message we need to send
|
||||
@@ -1086,7 +1103,7 @@ uint8_t PN7150::transceive_(nfc::NciMessage &tx, nfc::NciMessage &rx, const uint
|
||||
ESP_LOGE(TAG, "Error sending message");
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
ESP_LOGVV(TAG, "Wrote: %s", nfc::format_bytes(tx.get_message()).c_str());
|
||||
ESP_LOGVV(TAG, "Wrote: %s", nfc::format_bytes_to(buf, tx.get_message()));
|
||||
// next, the NFCC should send back a response
|
||||
if (this->read_nfcc(rx, timeout) != nfc::STATUS_OK) {
|
||||
ESP_LOGW(TAG, "Error receiving message");
|
||||
@@ -1098,24 +1115,24 @@ uint8_t PN7150::transceive_(nfc::NciMessage &tx, nfc::NciMessage &rx, const uint
|
||||
break;
|
||||
}
|
||||
}
|
||||
ESP_LOGVV(TAG, "Read: %s", nfc::format_bytes(rx.get_message()).c_str());
|
||||
ESP_LOGVV(TAG, "Read: %s", nfc::format_bytes_to(buf, rx.get_message()));
|
||||
// validate the response based on the message type that was sent (command vs. data)
|
||||
if (!tx.message_type_is(nfc::NCI_PKT_MT_DATA)) {
|
||||
// for commands, the GID and OID should match and the status should be OK
|
||||
if ((rx.get_gid() != tx.get_gid()) || (rx.get_oid()) != tx.get_oid()) {
|
||||
ESP_LOGE(TAG, "Incorrect response to command: %s", nfc::format_bytes(rx.get_message()).c_str());
|
||||
ESP_LOGE(TAG, "Incorrect response to command: %s", nfc::format_bytes_to(buf, rx.get_message()));
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
|
||||
if (!rx.simple_status_response_is(nfc::STATUS_OK)) {
|
||||
ESP_LOGE(TAG, "Error in response to command: %s", nfc::format_bytes(rx.get_message()).c_str());
|
||||
ESP_LOGE(TAG, "Error in response to command: %s", nfc::format_bytes_to(buf, rx.get_message()));
|
||||
}
|
||||
return rx.get_simple_status_response();
|
||||
} else {
|
||||
// when requesting data from the endpoint, the first response is from the NFCC; we must validate this, first
|
||||
if ((!rx.message_type_is(nfc::NCI_PKT_MT_CTRL_NOTIFICATION)) || (!rx.gid_is(nfc::NCI_CORE_GID)) ||
|
||||
(!rx.oid_is(nfc::NCI_CORE_CONN_CREDITS_OID)) || (!rx.message_length_is(3))) {
|
||||
ESP_LOGE(TAG, "Incorrect response to data message: %s", nfc::format_bytes(rx.get_message()).c_str());
|
||||
ESP_LOGE(TAG, "Incorrect response to data message: %s", nfc::format_bytes_to(buf, rx.get_message()));
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
|
||||
@@ -1125,7 +1142,7 @@ uint8_t PN7150::transceive_(nfc::NciMessage &tx, nfc::NciMessage &rx, const uint
|
||||
ESP_LOGE(TAG, "Error receiving data from endpoint");
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
ESP_LOGVV(TAG, "Read: %s", nfc::format_bytes(rx.get_message()).c_str());
|
||||
ESP_LOGVV(TAG, "Read: %s", nfc::format_bytes_to(buf, rx.get_message()));
|
||||
}
|
||||
|
||||
return nfc::STATUS_OK;
|
||||
|
||||
@@ -70,7 +70,8 @@ uint8_t PN7150::read_mifare_classic_block_(uint8_t block_num, std::vector<uint8_
|
||||
nfc::NciMessage rx;
|
||||
nfc::NciMessage tx(nfc::NCI_PKT_MT_DATA, {XCHG_DATA_OID, nfc::MIFARE_CMD_READ, block_num});
|
||||
|
||||
ESP_LOGVV(TAG, "Read XCHG_DATA_REQ: %s", nfc::format_bytes(tx.get_message()).c_str());
|
||||
char buf[nfc::FORMAT_BYTES_BUFFER_SIZE];
|
||||
ESP_LOGVV(TAG, "Read XCHG_DATA_REQ: %s", nfc::format_bytes_to(buf, tx.get_message()));
|
||||
if (this->transceive_(tx, rx) != nfc::STATUS_OK) {
|
||||
ESP_LOGE(TAG, "Timeout reading tag data");
|
||||
return nfc::STATUS_FAILED;
|
||||
@@ -79,13 +80,13 @@ uint8_t PN7150::read_mifare_classic_block_(uint8_t block_num, std::vector<uint8_
|
||||
if ((!rx.message_type_is(nfc::NCI_PKT_MT_DATA)) || (!rx.simple_status_response_is(XCHG_DATA_OID)) ||
|
||||
(!rx.message_length_is(18))) {
|
||||
ESP_LOGE(TAG, "MFC read block failed - block 0x%02x", block_num);
|
||||
ESP_LOGV(TAG, "Read response: %s", nfc::format_bytes(rx.get_message()).c_str());
|
||||
ESP_LOGV(TAG, "Read response: %s", nfc::format_bytes_to(buf, rx.get_message()));
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
|
||||
data.insert(data.begin(), rx.get_message().begin() + 4, rx.get_message().end() - 1);
|
||||
|
||||
ESP_LOGVV(TAG, " Block %u: %s", block_num, nfc::format_bytes(data).c_str());
|
||||
ESP_LOGVV(TAG, " Block %u: %s", block_num, nfc::format_bytes_to(buf, data));
|
||||
return nfc::STATUS_OK;
|
||||
}
|
||||
|
||||
@@ -111,7 +112,8 @@ uint8_t PN7150::auth_mifare_classic_block_(uint8_t block_num, uint8_t key_num, c
|
||||
tx.get_message().insert(tx.get_message().end(), key, key + 6);
|
||||
}
|
||||
|
||||
ESP_LOGVV(TAG, "MFC_AUTHENTICATE_REQ: %s", nfc::format_bytes(tx.get_message()).c_str());
|
||||
char buf[nfc::FORMAT_BYTES_BUFFER_SIZE];
|
||||
ESP_LOGVV(TAG, "MFC_AUTHENTICATE_REQ: %s", nfc::format_bytes_to(buf, tx.get_message()));
|
||||
if (this->transceive_(tx, rx) != nfc::STATUS_OK) {
|
||||
ESP_LOGE(TAG, "Sending MFC_AUTHENTICATE_REQ failed");
|
||||
return nfc::STATUS_FAILED;
|
||||
@@ -119,7 +121,7 @@ uint8_t PN7150::auth_mifare_classic_block_(uint8_t block_num, uint8_t key_num, c
|
||||
if ((!rx.message_type_is(nfc::NCI_PKT_MT_DATA)) || (!rx.simple_status_response_is(MFC_AUTHENTICATE_OID)) ||
|
||||
(rx.get_message()[4] != nfc::STATUS_OK)) {
|
||||
ESP_LOGE(TAG, "MFC authentication failed - block 0x%02x", block_num);
|
||||
ESP_LOGVV(TAG, "MFC_AUTHENTICATE_RSP: %s", nfc::format_bytes(rx.get_message()).c_str());
|
||||
ESP_LOGVV(TAG, "MFC_AUTHENTICATE_RSP: %s", nfc::format_bytes_to(buf, rx.get_message()));
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
|
||||
@@ -238,7 +240,8 @@ uint8_t PN7150::write_mifare_classic_block_(uint8_t block_num, std::vector<uint8
|
||||
nfc::NciMessage rx;
|
||||
nfc::NciMessage tx(nfc::NCI_PKT_MT_DATA, {XCHG_DATA_OID, nfc::MIFARE_CMD_WRITE, block_num});
|
||||
|
||||
ESP_LOGVV(TAG, "Write XCHG_DATA_REQ 1: %s", nfc::format_bytes(tx.get_message()).c_str());
|
||||
char buf[nfc::FORMAT_BYTES_BUFFER_SIZE];
|
||||
ESP_LOGVV(TAG, "Write XCHG_DATA_REQ 1: %s", nfc::format_bytes_to(buf, tx.get_message()));
|
||||
if (this->transceive_(tx, rx) != nfc::STATUS_OK) {
|
||||
ESP_LOGE(TAG, "Sending XCHG_DATA_REQ failed");
|
||||
return nfc::STATUS_FAILED;
|
||||
@@ -247,7 +250,7 @@ uint8_t PN7150::write_mifare_classic_block_(uint8_t block_num, std::vector<uint8
|
||||
tx.set_payload({XCHG_DATA_OID});
|
||||
tx.get_message().insert(tx.get_message().end(), write_data.begin(), write_data.end());
|
||||
|
||||
ESP_LOGVV(TAG, "Write XCHG_DATA_REQ 2: %s", nfc::format_bytes(tx.get_message()).c_str());
|
||||
ESP_LOGVV(TAG, "Write XCHG_DATA_REQ 2: %s", nfc::format_bytes_to(buf, tx.get_message()));
|
||||
if (this->transceive_(tx, rx, NFCC_TAG_WRITE_TIMEOUT) != nfc::STATUS_OK) {
|
||||
ESP_LOGE(TAG, "MFC XCHG_DATA timed out waiting for XCHG_DATA_RSP during block write");
|
||||
return nfc::STATUS_FAILED;
|
||||
@@ -256,7 +259,7 @@ uint8_t PN7150::write_mifare_classic_block_(uint8_t block_num, std::vector<uint8
|
||||
if ((!rx.message_type_is(nfc::NCI_PKT_MT_DATA)) || (!rx.simple_status_response_is(XCHG_DATA_OID)) ||
|
||||
(rx.get_message()[4] != nfc::MIFARE_CMD_ACK)) {
|
||||
ESP_LOGE(TAG, "MFC write block failed - block 0x%02x", block_num);
|
||||
ESP_LOGV(TAG, "Write response: %s", nfc::format_bytes(rx.get_message()).c_str());
|
||||
ESP_LOGV(TAG, "Write response: %s", nfc::format_bytes_to(buf, rx.get_message()));
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
|
||||
@@ -310,7 +313,8 @@ uint8_t PN7150::halt_mifare_classic_tag_() {
|
||||
nfc::NciMessage rx;
|
||||
nfc::NciMessage tx(nfc::NCI_PKT_MT_DATA, {XCHG_DATA_OID, nfc::MIFARE_CMD_HALT, 0});
|
||||
|
||||
ESP_LOGVV(TAG, "Halt XCHG_DATA_REQ: %s", nfc::format_bytes(tx.get_message()).c_str());
|
||||
char buf[nfc::FORMAT_BYTES_BUFFER_SIZE];
|
||||
ESP_LOGVV(TAG, "Halt XCHG_DATA_REQ: %s", nfc::format_bytes_to(buf, tx.get_message()));
|
||||
if (this->transceive_(tx, rx, NFCC_TAG_WRITE_TIMEOUT) != nfc::STATUS_OK) {
|
||||
ESP_LOGE(TAG, "Sending halt XCHG_DATA_REQ failed");
|
||||
return nfc::STATUS_FAILED;
|
||||
|
||||
@@ -72,7 +72,8 @@ uint8_t PN7150::read_mifare_ultralight_bytes_(uint8_t start_page, uint16_t num_b
|
||||
}
|
||||
}
|
||||
|
||||
ESP_LOGVV(TAG, "Data read: %s", nfc::format_bytes(data).c_str());
|
||||
char buf[nfc::FORMAT_BYTES_BUFFER_SIZE];
|
||||
ESP_LOGVV(TAG, "Data read: %s", nfc::format_bytes_to(buf, data));
|
||||
|
||||
return nfc::STATUS_OK;
|
||||
}
|
||||
|
||||
@@ -215,7 +215,8 @@ uint8_t PN7160::set_test_mode(const TestMode test_mode, const std::vector<uint8_
|
||||
result = rx.get_message();
|
||||
result.erase(result.begin(), result.begin() + 4); // remove NCI header
|
||||
if (!result.empty()) {
|
||||
ESP_LOGW(TAG, "Test results: %s", nfc::format_bytes(result).c_str());
|
||||
char buf[nfc::FORMAT_BYTES_BUFFER_SIZE];
|
||||
ESP_LOGW(TAG, "Test results: %s", nfc::format_bytes_to(buf, result));
|
||||
}
|
||||
}
|
||||
return status;
|
||||
@@ -246,7 +247,8 @@ uint8_t PN7160::reset_core_(const bool reset_config, const bool power) {
|
||||
}
|
||||
|
||||
if (!rx.simple_status_response_is(nfc::STATUS_OK)) {
|
||||
ESP_LOGE(TAG, "Invalid reset response: %s", nfc::format_bytes(rx.get_message()).c_str());
|
||||
char buf[nfc::FORMAT_BYTES_BUFFER_SIZE];
|
||||
ESP_LOGE(TAG, "Invalid reset response: %s", nfc::format_bytes_to(buf, rx.get_message()));
|
||||
return rx.get_simple_status_response();
|
||||
}
|
||||
// read reset notification
|
||||
@@ -258,7 +260,8 @@ uint8_t PN7160::reset_core_(const bool reset_config, const bool power) {
|
||||
if ((!rx.message_type_is(nfc::NCI_PKT_MT_CTRL_NOTIFICATION)) || (!rx.message_length_is(9)) ||
|
||||
(rx.get_message()[nfc::NCI_PKT_PAYLOAD_OFFSET] != 0x02) ||
|
||||
(rx.get_message()[nfc::NCI_PKT_PAYLOAD_OFFSET + 1] != (uint8_t) reset_config)) {
|
||||
ESP_LOGE(TAG, "Reset notification was malformed: %s", nfc::format_bytes(rx.get_message()).c_str());
|
||||
char buf[nfc::FORMAT_BYTES_BUFFER_SIZE];
|
||||
ESP_LOGE(TAG, "Reset notification was malformed: %s", nfc::format_bytes_to(buf, rx.get_message()));
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
|
||||
@@ -269,7 +272,8 @@ uint8_t PN7160::reset_core_(const bool reset_config, const bool power) {
|
||||
rx.get_message()[4] ? "reset" : "retained", rx.get_message()[5] == 0x20 ? "2.0" : "1.0",
|
||||
rx.get_message()[6]);
|
||||
rx.get_message().erase(rx.get_message().begin(), rx.get_message().begin() + 8);
|
||||
ESP_LOGD(TAG, "Manufacturer info: %s", nfc::format_bytes(rx.get_message()).c_str());
|
||||
char mfr_buf[nfc::FORMAT_BYTES_BUFFER_SIZE];
|
||||
ESP_LOGD(TAG, "Manufacturer info: %s", nfc::format_bytes_to(mfr_buf, rx.get_message()));
|
||||
|
||||
return nfc::STATUS_OK;
|
||||
}
|
||||
@@ -284,7 +288,8 @@ uint8_t PN7160::init_core_() {
|
||||
}
|
||||
|
||||
if (!rx.simple_status_response_is(nfc::STATUS_OK)) {
|
||||
ESP_LOGE(TAG, "Invalid initialise response: %s", nfc::format_bytes(rx.get_message()).c_str());
|
||||
char buf[nfc::FORMAT_BYTES_BUFFER_SIZE];
|
||||
ESP_LOGE(TAG, "Invalid initialise response: %s", nfc::format_bytes_to(buf, rx.get_message()));
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
|
||||
@@ -294,13 +299,15 @@ uint8_t PN7160::init_core_() {
|
||||
uint8_t flash_minor_version = rx.get_message()[20 + rx.get_message()[8]];
|
||||
std::vector<uint8_t> features(rx.get_message().begin() + 4, rx.get_message().begin() + 8);
|
||||
|
||||
char feat_buf[nfc::FORMAT_BYTES_BUFFER_SIZE];
|
||||
ESP_LOGD(TAG,
|
||||
"Hardware version: %u\n"
|
||||
"ROM code version: %u\n"
|
||||
"FLASH major version: %u\n"
|
||||
"FLASH minor version: %u\n"
|
||||
"Features: %s",
|
||||
hw_version, rom_code_version, flash_major_version, flash_minor_version, nfc::format_bytes(features).c_str());
|
||||
hw_version, rom_code_version, flash_major_version, flash_minor_version,
|
||||
nfc::format_bytes_to(feat_buf, features));
|
||||
|
||||
return rx.get_simple_status_response();
|
||||
}
|
||||
@@ -599,7 +606,8 @@ void PN7160::erase_tag_(const uint8_t tag_index) {
|
||||
for (auto *listener : this->tag_listeners_) {
|
||||
listener->tag_off(*this->discovered_endpoint_[tag_index].tag);
|
||||
}
|
||||
ESP_LOGI(TAG, "Tag %s removed", nfc::format_uid(this->discovered_endpoint_[tag_index].tag->get_uid()).c_str());
|
||||
char uid_buf[nfc::FORMAT_UID_BUFFER_SIZE];
|
||||
ESP_LOGI(TAG, "Tag %s removed", nfc::format_uid_to(uid_buf, this->discovered_endpoint_[tag_index].tag->get_uid()));
|
||||
this->discovered_endpoint_.erase(this->discovered_endpoint_.begin() + tag_index);
|
||||
}
|
||||
}
|
||||
@@ -796,26 +804,33 @@ void PN7160::process_message_() {
|
||||
ESP_LOGV(TAG, "Unimplemented NCI Core OID received: 0x%02X", rx.get_oid());
|
||||
}
|
||||
} else {
|
||||
ESP_LOGV(TAG, "Unimplemented notification: %s", nfc::format_bytes(rx.get_message()).c_str());
|
||||
char buf[nfc::FORMAT_BYTES_BUFFER_SIZE];
|
||||
ESP_LOGV(TAG, "Unimplemented notification: %s", nfc::format_bytes_to(buf, rx.get_message()));
|
||||
}
|
||||
break;
|
||||
|
||||
case nfc::NCI_PKT_MT_CTRL_RESPONSE:
|
||||
case nfc::NCI_PKT_MT_CTRL_RESPONSE: {
|
||||
char buf[nfc::FORMAT_BYTES_BUFFER_SIZE];
|
||||
ESP_LOGV(TAG, "Unimplemented GID: 0x%02X OID: 0x%02X Full response: %s", rx.get_gid(), rx.get_oid(),
|
||||
nfc::format_bytes(rx.get_message()).c_str());
|
||||
nfc::format_bytes_to(buf, rx.get_message()));
|
||||
break;
|
||||
}
|
||||
|
||||
case nfc::NCI_PKT_MT_CTRL_COMMAND:
|
||||
ESP_LOGV(TAG, "Unimplemented command: %s", nfc::format_bytes(rx.get_message()).c_str());
|
||||
case nfc::NCI_PKT_MT_CTRL_COMMAND: {
|
||||
char buf[nfc::FORMAT_BYTES_BUFFER_SIZE];
|
||||
ESP_LOGV(TAG, "Unimplemented command: %s", nfc::format_bytes_to(buf, rx.get_message()));
|
||||
break;
|
||||
}
|
||||
|
||||
case nfc::NCI_PKT_MT_DATA:
|
||||
this->process_data_message_(rx);
|
||||
break;
|
||||
|
||||
default:
|
||||
ESP_LOGV(TAG, "Unimplemented message type: %s", nfc::format_bytes(rx.get_message()).c_str());
|
||||
default: {
|
||||
char buf[nfc::FORMAT_BYTES_BUFFER_SIZE];
|
||||
ESP_LOGV(TAG, "Unimplemented message type: %s", nfc::format_bytes_to(buf, rx.get_message()));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -896,8 +911,9 @@ void PN7160::process_rf_intf_activated_oid_(nfc::NciMessage &rx) { // an endpoi
|
||||
case EP_READ:
|
||||
default:
|
||||
if (!working_endpoint.trig_called) {
|
||||
char uid_buf[nfc::FORMAT_UID_BUFFER_SIZE];
|
||||
ESP_LOGI(TAG, "Read tag type %s with UID %s", working_endpoint.tag->get_tag_type().c_str(),
|
||||
nfc::format_uid(working_endpoint.tag->get_uid()).c_str());
|
||||
nfc::format_uid_to(uid_buf, working_endpoint.tag->get_uid()));
|
||||
if (this->read_endpoint_data_(*working_endpoint.tag) != nfc::STATUS_OK) {
|
||||
ESP_LOGW(TAG, " Unable to read NDEF record(s)");
|
||||
} else if (working_endpoint.tag->has_ndef_message()) {
|
||||
@@ -988,7 +1004,8 @@ void PN7160::process_rf_deactivate_oid_(nfc::NciMessage &rx) {
|
||||
}
|
||||
|
||||
void PN7160::process_data_message_(nfc::NciMessage &rx) {
|
||||
ESP_LOGVV(TAG, "Received data message: %s", nfc::format_bytes(rx.get_message()).c_str());
|
||||
char buf[nfc::FORMAT_BYTES_BUFFER_SIZE];
|
||||
ESP_LOGVV(TAG, "Received data message: %s", nfc::format_bytes_to(buf, rx.get_message()));
|
||||
|
||||
std::vector<uint8_t> ndef_response;
|
||||
this->card_emu_t4t_get_response_(rx.get_message(), ndef_response);
|
||||
@@ -1002,7 +1019,7 @@ void PN7160::process_data_message_(nfc::NciMessage &rx) {
|
||||
uint8_t(ndef_response_size & 0x00FF)};
|
||||
tx_msg.insert(tx_msg.end(), ndef_response.begin(), ndef_response.end());
|
||||
nfc::NciMessage tx(tx_msg);
|
||||
ESP_LOGVV(TAG, "Sending data message: %s", nfc::format_bytes(tx.get_message()).c_str());
|
||||
ESP_LOGVV(TAG, "Sending data message: %s", nfc::format_bytes_to(buf, tx.get_message()));
|
||||
if (this->transceive_(tx, rx, NFCC_DEFAULT_TIMEOUT, false) != nfc::STATUS_OK) {
|
||||
ESP_LOGE(TAG, "Sending reply for card emulation failed");
|
||||
}
|
||||
@@ -1055,7 +1072,8 @@ void PN7160::card_emu_t4t_get_response_(std::vector<uint8_t> &response, std::vec
|
||||
uint16_t offset = (response[nfc::NCI_PKT_HEADER_SIZE + 2] << 8) + response[nfc::NCI_PKT_HEADER_SIZE + 3];
|
||||
uint8_t length = response[nfc::NCI_PKT_HEADER_SIZE + 4];
|
||||
|
||||
ESP_LOGVV(TAG, "Encoded NDEF message: %s", nfc::format_bytes(ndef_message).c_str());
|
||||
char ndef_buf[nfc::FORMAT_BYTES_BUFFER_SIZE];
|
||||
ESP_LOGVV(TAG, "Encoded NDEF message: %s", nfc::format_bytes_to(ndef_buf, ndef_message));
|
||||
|
||||
if (length <= (ndef_msg_size + offset + 2)) {
|
||||
if (offset == 0) {
|
||||
@@ -1094,7 +1112,8 @@ void PN7160::card_emu_t4t_get_response_(std::vector<uint8_t> &response, std::vec
|
||||
|
||||
ndef_msg_written.insert(ndef_msg_written.end(), response.begin() + nfc::NCI_PKT_HEADER_SIZE + 5,
|
||||
response.begin() + nfc::NCI_PKT_HEADER_SIZE + 5 + length);
|
||||
ESP_LOGD(TAG, "Received %u-byte NDEF message: %s", length, nfc::format_bytes(ndef_msg_written).c_str());
|
||||
char write_buf[nfc::FORMAT_BYTES_BUFFER_SIZE];
|
||||
ESP_LOGD(TAG, "Received %u-byte NDEF message: %s", length, nfc::format_bytes_to(write_buf, ndef_msg_written));
|
||||
ndef_response.insert(ndef_response.end(), std::begin(CARD_EMU_T4T_OK), std::end(CARD_EMU_T4T_OK));
|
||||
}
|
||||
}
|
||||
@@ -1103,6 +1122,7 @@ void PN7160::card_emu_t4t_get_response_(std::vector<uint8_t> &response, std::vec
|
||||
uint8_t PN7160::transceive_(nfc::NciMessage &tx, nfc::NciMessage &rx, const uint16_t timeout,
|
||||
const bool expect_notification) {
|
||||
uint8_t retries = NFCC_MAX_COMM_FAILS;
|
||||
char buf[nfc::FORMAT_BYTES_BUFFER_SIZE];
|
||||
|
||||
while (retries) {
|
||||
// first, send the message we need to send
|
||||
@@ -1110,7 +1130,7 @@ uint8_t PN7160::transceive_(nfc::NciMessage &tx, nfc::NciMessage &rx, const uint
|
||||
ESP_LOGE(TAG, "Error sending message");
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
ESP_LOGVV(TAG, "Wrote: %s", nfc::format_bytes(tx.get_message()).c_str());
|
||||
ESP_LOGVV(TAG, "Wrote: %s", nfc::format_bytes_to(buf, tx.get_message()));
|
||||
// next, the NFCC should send back a response
|
||||
if (this->read_nfcc(rx, timeout) != nfc::STATUS_OK) {
|
||||
ESP_LOGW(TAG, "Error receiving message");
|
||||
@@ -1122,24 +1142,24 @@ uint8_t PN7160::transceive_(nfc::NciMessage &tx, nfc::NciMessage &rx, const uint
|
||||
break;
|
||||
}
|
||||
}
|
||||
ESP_LOGVV(TAG, "Read: %s", nfc::format_bytes(rx.get_message()).c_str());
|
||||
ESP_LOGVV(TAG, "Read: %s", nfc::format_bytes_to(buf, rx.get_message()));
|
||||
// validate the response based on the message type that was sent (command vs. data)
|
||||
if (!tx.message_type_is(nfc::NCI_PKT_MT_DATA)) {
|
||||
// for commands, the GID and OID should match and the status should be OK
|
||||
if ((rx.get_gid() != tx.get_gid()) || (rx.get_oid()) != tx.get_oid()) {
|
||||
ESP_LOGE(TAG, "Incorrect response to command: %s", nfc::format_bytes(rx.get_message()).c_str());
|
||||
ESP_LOGE(TAG, "Incorrect response to command: %s", nfc::format_bytes_to(buf, rx.get_message()));
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
|
||||
if (!rx.simple_status_response_is(nfc::STATUS_OK)) {
|
||||
ESP_LOGE(TAG, "Error in response to command: %s", nfc::format_bytes(rx.get_message()).c_str());
|
||||
ESP_LOGE(TAG, "Error in response to command: %s", nfc::format_bytes_to(buf, rx.get_message()));
|
||||
}
|
||||
return rx.get_simple_status_response();
|
||||
} else {
|
||||
// when requesting data from the endpoint, the first response is from the NFCC; we must validate this, first
|
||||
if ((!rx.message_type_is(nfc::NCI_PKT_MT_CTRL_NOTIFICATION)) || (!rx.gid_is(nfc::NCI_CORE_GID)) ||
|
||||
(!rx.oid_is(nfc::NCI_CORE_CONN_CREDITS_OID)) || (!rx.message_length_is(3))) {
|
||||
ESP_LOGE(TAG, "Incorrect response to data message: %s", nfc::format_bytes(rx.get_message()).c_str());
|
||||
ESP_LOGE(TAG, "Incorrect response to data message: %s", nfc::format_bytes_to(buf, rx.get_message()));
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
|
||||
@@ -1149,7 +1169,7 @@ uint8_t PN7160::transceive_(nfc::NciMessage &tx, nfc::NciMessage &rx, const uint
|
||||
ESP_LOGE(TAG, "Error receiving data from endpoint");
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
ESP_LOGVV(TAG, "Read: %s", nfc::format_bytes(rx.get_message()).c_str());
|
||||
ESP_LOGVV(TAG, "Read: %s", nfc::format_bytes_to(buf, rx.get_message()));
|
||||
}
|
||||
|
||||
return nfc::STATUS_OK;
|
||||
|
||||
@@ -69,8 +69,9 @@ uint8_t PN7160::read_mifare_classic_tag_(nfc::NfcTag &tag) {
|
||||
uint8_t PN7160::read_mifare_classic_block_(uint8_t block_num, std::vector<uint8_t> &data) {
|
||||
nfc::NciMessage rx;
|
||||
nfc::NciMessage tx(nfc::NCI_PKT_MT_DATA, {XCHG_DATA_OID, nfc::MIFARE_CMD_READ, block_num});
|
||||
char buf[nfc::FORMAT_BYTES_BUFFER_SIZE];
|
||||
|
||||
ESP_LOGVV(TAG, "Read XCHG_DATA_REQ: %s", nfc::format_bytes(tx.get_message()).c_str());
|
||||
ESP_LOGVV(TAG, "Read XCHG_DATA_REQ: %s", nfc::format_bytes_to(buf, tx.get_message()));
|
||||
if (this->transceive_(tx, rx) != nfc::STATUS_OK) {
|
||||
ESP_LOGE(TAG, "Timeout reading tag data");
|
||||
return nfc::STATUS_FAILED;
|
||||
@@ -79,13 +80,13 @@ uint8_t PN7160::read_mifare_classic_block_(uint8_t block_num, std::vector<uint8_
|
||||
if ((!rx.message_type_is(nfc::NCI_PKT_MT_DATA)) || (!rx.simple_status_response_is(XCHG_DATA_OID)) ||
|
||||
(!rx.message_length_is(18))) {
|
||||
ESP_LOGE(TAG, "MFC read block failed - block 0x%02x", block_num);
|
||||
ESP_LOGV(TAG, "Read response: %s", nfc::format_bytes(rx.get_message()).c_str());
|
||||
ESP_LOGV(TAG, "Read response: %s", nfc::format_bytes_to(buf, rx.get_message()));
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
|
||||
data.insert(data.begin(), rx.get_message().begin() + 4, rx.get_message().end() - 1);
|
||||
|
||||
ESP_LOGVV(TAG, " Block %u: %s", block_num, nfc::format_bytes(data).c_str());
|
||||
ESP_LOGVV(TAG, " Block %u: %s", block_num, nfc::format_bytes_to(buf, data));
|
||||
return nfc::STATUS_OK;
|
||||
}
|
||||
|
||||
@@ -111,7 +112,8 @@ uint8_t PN7160::auth_mifare_classic_block_(uint8_t block_num, uint8_t key_num, c
|
||||
tx.get_message().insert(tx.get_message().end(), key, key + 6);
|
||||
}
|
||||
|
||||
ESP_LOGVV(TAG, "MFC_AUTHENTICATE_REQ: %s", nfc::format_bytes(tx.get_message()).c_str());
|
||||
char buf[nfc::FORMAT_BYTES_BUFFER_SIZE];
|
||||
ESP_LOGVV(TAG, "MFC_AUTHENTICATE_REQ: %s", nfc::format_bytes_to(buf, tx.get_message()));
|
||||
if (this->transceive_(tx, rx) != nfc::STATUS_OK) {
|
||||
ESP_LOGE(TAG, "Sending MFC_AUTHENTICATE_REQ failed");
|
||||
return nfc::STATUS_FAILED;
|
||||
@@ -119,7 +121,7 @@ uint8_t PN7160::auth_mifare_classic_block_(uint8_t block_num, uint8_t key_num, c
|
||||
if ((!rx.message_type_is(nfc::NCI_PKT_MT_DATA)) || (!rx.simple_status_response_is(MFC_AUTHENTICATE_OID)) ||
|
||||
(rx.get_message()[4] != nfc::STATUS_OK)) {
|
||||
ESP_LOGE(TAG, "MFC authentication failed - block 0x%02x", block_num);
|
||||
ESP_LOGVV(TAG, "MFC_AUTHENTICATE_RSP: %s", nfc::format_bytes(rx.get_message()).c_str());
|
||||
ESP_LOGVV(TAG, "MFC_AUTHENTICATE_RSP: %s", nfc::format_bytes_to(buf, rx.get_message()));
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
|
||||
@@ -237,8 +239,9 @@ uint8_t PN7160::format_mifare_classic_ndef_() {
|
||||
uint8_t PN7160::write_mifare_classic_block_(uint8_t block_num, std::vector<uint8_t> &write_data) {
|
||||
nfc::NciMessage rx;
|
||||
nfc::NciMessage tx(nfc::NCI_PKT_MT_DATA, {XCHG_DATA_OID, nfc::MIFARE_CMD_WRITE, block_num});
|
||||
char buf[nfc::FORMAT_BYTES_BUFFER_SIZE];
|
||||
|
||||
ESP_LOGVV(TAG, "Write XCHG_DATA_REQ 1: %s", nfc::format_bytes(tx.get_message()).c_str());
|
||||
ESP_LOGVV(TAG, "Write XCHG_DATA_REQ 1: %s", nfc::format_bytes_to(buf, tx.get_message()));
|
||||
if (this->transceive_(tx, rx) != nfc::STATUS_OK) {
|
||||
ESP_LOGE(TAG, "Sending XCHG_DATA_REQ failed");
|
||||
return nfc::STATUS_FAILED;
|
||||
@@ -247,7 +250,7 @@ uint8_t PN7160::write_mifare_classic_block_(uint8_t block_num, std::vector<uint8
|
||||
tx.set_payload({XCHG_DATA_OID});
|
||||
tx.get_message().insert(tx.get_message().end(), write_data.begin(), write_data.end());
|
||||
|
||||
ESP_LOGVV(TAG, "Write XCHG_DATA_REQ 2: %s", nfc::format_bytes(tx.get_message()).c_str());
|
||||
ESP_LOGVV(TAG, "Write XCHG_DATA_REQ 2: %s", nfc::format_bytes_to(buf, tx.get_message()));
|
||||
if (this->transceive_(tx, rx, NFCC_TAG_WRITE_TIMEOUT) != nfc::STATUS_OK) {
|
||||
ESP_LOGE(TAG, "MFC XCHG_DATA timed out waiting for XCHG_DATA_RSP during block write");
|
||||
return nfc::STATUS_FAILED;
|
||||
@@ -256,7 +259,7 @@ uint8_t PN7160::write_mifare_classic_block_(uint8_t block_num, std::vector<uint8
|
||||
if ((!rx.message_type_is(nfc::NCI_PKT_MT_DATA)) || (!rx.simple_status_response_is(XCHG_DATA_OID)) ||
|
||||
(rx.get_message()[4] != nfc::MIFARE_CMD_ACK)) {
|
||||
ESP_LOGE(TAG, "MFC write block failed - block 0x%02x", block_num);
|
||||
ESP_LOGV(TAG, "Write response: %s", nfc::format_bytes(rx.get_message()).c_str());
|
||||
ESP_LOGV(TAG, "Write response: %s", nfc::format_bytes_to(buf, rx.get_message()));
|
||||
return nfc::STATUS_FAILED;
|
||||
}
|
||||
|
||||
@@ -310,7 +313,8 @@ uint8_t PN7160::halt_mifare_classic_tag_() {
|
||||
nfc::NciMessage rx;
|
||||
nfc::NciMessage tx(nfc::NCI_PKT_MT_DATA, {XCHG_DATA_OID, nfc::MIFARE_CMD_HALT, 0});
|
||||
|
||||
ESP_LOGVV(TAG, "Halt XCHG_DATA_REQ: %s", nfc::format_bytes(tx.get_message()).c_str());
|
||||
char buf[nfc::FORMAT_BYTES_BUFFER_SIZE];
|
||||
ESP_LOGVV(TAG, "Halt XCHG_DATA_REQ: %s", nfc::format_bytes_to(buf, tx.get_message()));
|
||||
if (this->transceive_(tx, rx, NFCC_TAG_WRITE_TIMEOUT) != nfc::STATUS_OK) {
|
||||
ESP_LOGE(TAG, "Sending halt XCHG_DATA_REQ failed");
|
||||
return nfc::STATUS_FAILED;
|
||||
|
||||
@@ -72,7 +72,8 @@ uint8_t PN7160::read_mifare_ultralight_bytes_(uint8_t start_page, uint16_t num_b
|
||||
}
|
||||
}
|
||||
|
||||
ESP_LOGVV(TAG, "Data read: %s", nfc::format_bytes(data).c_str());
|
||||
char buf[nfc::FORMAT_BYTES_BUFFER_SIZE];
|
||||
ESP_LOGVV(TAG, "Data read: %s", nfc::format_bytes_to(buf, data));
|
||||
|
||||
return nfc::STATUS_OK;
|
||||
}
|
||||
|
||||
@@ -194,7 +194,9 @@ void PrometheusHandler::sensor_row_(AsyncResponseStream *stream, sensor::Sensor
|
||||
stream->print(ESPHOME_F("\",unit=\""));
|
||||
stream->print(obj->get_unit_of_measurement_ref().c_str());
|
||||
stream->print(ESPHOME_F("\"} "));
|
||||
stream->print(value_accuracy_to_string(obj->state, obj->get_accuracy_decimals()).c_str());
|
||||
char value_buf[VALUE_ACCURACY_MAX_LEN];
|
||||
value_accuracy_to_buf(value_buf, obj->state, obj->get_accuracy_decimals());
|
||||
stream->print(value_buf);
|
||||
stream->print(ESPHOME_F("\n"));
|
||||
} else {
|
||||
// Invalid state
|
||||
@@ -363,13 +365,14 @@ void PrometheusHandler::light_row_(AsyncResponseStream *stream, light::LightStat
|
||||
// Skip effect metrics if light has no effects
|
||||
if (!obj->get_effects().empty()) {
|
||||
// Effect
|
||||
std::string effect = obj->get_effect_name();
|
||||
StringRef effect = obj->get_effect_name();
|
||||
print_metric_labels_(stream, ESPHOME_F("esphome_light_effect_active"), obj, area, node, friendly_name);
|
||||
stream->print(ESPHOME_F("\",effect=\""));
|
||||
// Only vary based on effect
|
||||
if (effect == "None") {
|
||||
stream->print(ESPHOME_F("None\"} 0\n"));
|
||||
} else {
|
||||
// c_str() is safe as effect names are null-terminated strings from codegen
|
||||
stream->print(effect.c_str());
|
||||
stream->print(ESPHOME_F("\"} 1\n"));
|
||||
}
|
||||
@@ -599,7 +602,7 @@ void PrometheusHandler::event_row_(AsyncResponseStream *stream, event::Event *ob
|
||||
std::string &friendly_name) {
|
||||
if (obj->is_internal() && !this->include_internal_)
|
||||
return;
|
||||
if (obj->get_last_event_type() != nullptr) {
|
||||
if (obj->has_event()) {
|
||||
// We have a valid event type, output this value
|
||||
stream->print(ESPHOME_F("esphome_event_failed{id=\""));
|
||||
stream->print(relabel_id_(obj).c_str());
|
||||
@@ -618,7 +621,8 @@ void PrometheusHandler::event_row_(AsyncResponseStream *stream, event::Event *ob
|
||||
stream->print(ESPHOME_F("\",name=\""));
|
||||
stream->print(relabel_name_(obj).c_str());
|
||||
stream->print(ESPHOME_F("\",last_event_type=\""));
|
||||
stream->print(obj->get_last_event_type());
|
||||
// get_last_event_type() returns StringRef (null-terminated)
|
||||
stream->print(obj->get_last_event_type().c_str());
|
||||
stream->print(ESPHOME_F("\"} "));
|
||||
stream->print(ESPHOME_F("1.0"));
|
||||
stream->print(ESPHOME_F("\n"));
|
||||
@@ -709,7 +713,8 @@ void PrometheusHandler::select_row_(AsyncResponseStream *stream, select::Select
|
||||
stream->print(ESPHOME_F("\",name=\""));
|
||||
stream->print(relabel_name_(obj).c_str());
|
||||
stream->print(ESPHOME_F("\",value=\""));
|
||||
stream->print(obj->current_option());
|
||||
// c_str() is safe as option values are null-terminated strings from codegen
|
||||
stream->print(obj->current_option().c_str());
|
||||
stream->print(ESPHOME_F("\"} "));
|
||||
stream->print(ESPHOME_F("1.0"));
|
||||
stream->print(ESPHOME_F("\n"));
|
||||
@@ -951,7 +956,7 @@ void PrometheusHandler::climate_setting_row_(AsyncResponseStream *stream, climat
|
||||
|
||||
void PrometheusHandler::climate_value_row_(AsyncResponseStream *stream, climate::Climate *obj, std::string &area,
|
||||
std::string &node, std::string &friendly_name, std::string &category,
|
||||
std::string &climate_value) {
|
||||
const char *climate_value) {
|
||||
stream->print(ESPHOME_F("esphome_climate_value{id=\""));
|
||||
stream->print(relabel_id_(obj).c_str());
|
||||
add_area_label_(stream, area);
|
||||
@@ -962,7 +967,7 @@ void PrometheusHandler::climate_value_row_(AsyncResponseStream *stream, climate:
|
||||
stream->print(ESPHOME_F("\",category=\""));
|
||||
stream->print(category.c_str());
|
||||
stream->print(ESPHOME_F("\"} "));
|
||||
stream->print(climate_value.c_str());
|
||||
stream->print(climate_value);
|
||||
stream->print(ESPHOME_F("\n"));
|
||||
}
|
||||
|
||||
@@ -1000,14 +1005,15 @@ void PrometheusHandler::climate_row_(AsyncResponseStream *stream, climate::Clima
|
||||
// Now see if traits is supported
|
||||
int8_t target_accuracy = traits.get_target_temperature_accuracy_decimals();
|
||||
int8_t current_accuracy = traits.get_current_temperature_accuracy_decimals();
|
||||
char value_buf[VALUE_ACCURACY_MAX_LEN];
|
||||
// max temp
|
||||
std::string max_temp = "maximum_temperature";
|
||||
auto max_temp_value = value_accuracy_to_string(traits.get_visual_max_temperature(), target_accuracy);
|
||||
climate_value_row_(stream, obj, area, node, friendly_name, max_temp, max_temp_value);
|
||||
// max temp
|
||||
std::string min_temp = "mininum_temperature";
|
||||
auto min_temp_value = value_accuracy_to_string(traits.get_visual_min_temperature(), target_accuracy);
|
||||
climate_value_row_(stream, obj, area, node, friendly_name, min_temp, min_temp_value);
|
||||
value_accuracy_to_buf(value_buf, traits.get_visual_max_temperature(), target_accuracy);
|
||||
climate_value_row_(stream, obj, area, node, friendly_name, max_temp, value_buf);
|
||||
// min temp
|
||||
std::string min_temp = "minimum_temperature";
|
||||
value_accuracy_to_buf(value_buf, traits.get_visual_min_temperature(), target_accuracy);
|
||||
climate_value_row_(stream, obj, area, node, friendly_name, min_temp, value_buf);
|
||||
// now check optional traits
|
||||
if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_CURRENT_TEMPERATURE)) {
|
||||
std::string current_temp = "current_temperature";
|
||||
@@ -1015,8 +1021,8 @@ void PrometheusHandler::climate_row_(AsyncResponseStream *stream, climate::Clima
|
||||
climate_failed_row_(stream, obj, area, node, friendly_name, current_temp, true);
|
||||
any_failures = true;
|
||||
} else {
|
||||
auto current_temp_value = value_accuracy_to_string(obj->current_temperature, current_accuracy);
|
||||
climate_value_row_(stream, obj, area, node, friendly_name, current_temp, current_temp_value);
|
||||
value_accuracy_to_buf(value_buf, obj->current_temperature, current_accuracy);
|
||||
climate_value_row_(stream, obj, area, node, friendly_name, current_temp, value_buf);
|
||||
climate_failed_row_(stream, obj, area, node, friendly_name, current_temp, false);
|
||||
}
|
||||
}
|
||||
@@ -1026,8 +1032,8 @@ void PrometheusHandler::climate_row_(AsyncResponseStream *stream, climate::Clima
|
||||
climate_failed_row_(stream, obj, area, node, friendly_name, current_humidity, true);
|
||||
any_failures = true;
|
||||
} else {
|
||||
auto current_humidity_value = value_accuracy_to_string(obj->current_humidity, 0);
|
||||
climate_value_row_(stream, obj, area, node, friendly_name, current_humidity, current_humidity_value);
|
||||
value_accuracy_to_buf(value_buf, obj->current_humidity, 0);
|
||||
climate_value_row_(stream, obj, area, node, friendly_name, current_humidity, value_buf);
|
||||
climate_failed_row_(stream, obj, area, node, friendly_name, current_humidity, false);
|
||||
}
|
||||
}
|
||||
@@ -1037,23 +1043,23 @@ void PrometheusHandler::climate_row_(AsyncResponseStream *stream, climate::Clima
|
||||
climate_failed_row_(stream, obj, area, node, friendly_name, target_humidity, true);
|
||||
any_failures = true;
|
||||
} else {
|
||||
auto target_humidity_value = value_accuracy_to_string(obj->target_humidity, 0);
|
||||
climate_value_row_(stream, obj, area, node, friendly_name, target_humidity, target_humidity_value);
|
||||
value_accuracy_to_buf(value_buf, obj->target_humidity, 0);
|
||||
climate_value_row_(stream, obj, area, node, friendly_name, target_humidity, value_buf);
|
||||
climate_failed_row_(stream, obj, area, node, friendly_name, target_humidity, false);
|
||||
}
|
||||
}
|
||||
if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_TWO_POINT_TARGET_TEMPERATURE |
|
||||
climate::CLIMATE_REQUIRES_TWO_POINT_TARGET_TEMPERATURE)) {
|
||||
std::string target_temp_low = "target_temperature_low";
|
||||
auto target_temp_low_value = value_accuracy_to_string(obj->target_temperature_low, target_accuracy);
|
||||
climate_value_row_(stream, obj, area, node, friendly_name, target_temp_low, target_temp_low_value);
|
||||
value_accuracy_to_buf(value_buf, obj->target_temperature_low, target_accuracy);
|
||||
climate_value_row_(stream, obj, area, node, friendly_name, target_temp_low, value_buf);
|
||||
std::string target_temp_high = "target_temperature_high";
|
||||
auto target_temp_high_value = value_accuracy_to_string(obj->target_temperature_high, target_accuracy);
|
||||
climate_value_row_(stream, obj, area, node, friendly_name, target_temp_high, target_temp_high_value);
|
||||
value_accuracy_to_buf(value_buf, obj->target_temperature_high, target_accuracy);
|
||||
climate_value_row_(stream, obj, area, node, friendly_name, target_temp_high, value_buf);
|
||||
} else {
|
||||
std::string target_temp = "target_temperature";
|
||||
auto target_temp_value = value_accuracy_to_string(obj->target_temperature, target_accuracy);
|
||||
climate_value_row_(stream, obj, area, node, friendly_name, target_temp, target_temp_value);
|
||||
value_accuracy_to_buf(value_buf, obj->target_temperature, target_accuracy);
|
||||
climate_value_row_(stream, obj, area, node, friendly_name, target_temp, value_buf);
|
||||
}
|
||||
if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_ACTION)) {
|
||||
std::string climate_trait_category = "action";
|
||||
|
||||
@@ -207,7 +207,7 @@ class PrometheusHandler : public AsyncWebHandler, public Component {
|
||||
void climate_setting_row_(AsyncResponseStream *stream, climate::Climate *obj, std::string &area, std::string &node,
|
||||
std::string &friendly_name, std::string &setting, const LogString *setting_value);
|
||||
void climate_value_row_(AsyncResponseStream *stream, climate::Climate *obj, std::string &area, std::string &node,
|
||||
std::string &friendly_name, std::string &category, std::string &climate_value);
|
||||
std::string &friendly_name, std::string &category, const char *climate_value);
|
||||
#endif
|
||||
|
||||
web_server_base::WebServerBase *base_;
|
||||
|
||||
@@ -105,7 +105,9 @@ void QMC5883LComponent::update() {
|
||||
if (ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_DEBUG) {
|
||||
err = this->read_register(QMC5883L_REGISTER_STATUS, &status, 1);
|
||||
if (err != i2c::ERROR_OK) {
|
||||
this->status_set_warning(str_sprintf("status read failed (%d)", err).c_str());
|
||||
char buf[32];
|
||||
snprintf(buf, sizeof(buf), "status read failed (%d)", err);
|
||||
this->status_set_warning(buf);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -127,7 +129,9 @@ void QMC5883LComponent::update() {
|
||||
}
|
||||
err = this->read_bytes_16_le_(start, &raw[dest], 3 - dest);
|
||||
if (err != i2c::ERROR_OK) {
|
||||
this->status_set_warning(str_sprintf("mag read failed (%d)", err).c_str());
|
||||
char buf[32];
|
||||
snprintf(buf, sizeof(buf), "mag read failed (%d)", err);
|
||||
this->status_set_warning(buf);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -155,7 +159,9 @@ void QMC5883LComponent::update() {
|
||||
uint16_t raw_temp;
|
||||
err = this->read_bytes_16_le_(QMC5883L_REGISTER_TEMPERATURE_LSB, &raw_temp);
|
||||
if (err != i2c::ERROR_OK) {
|
||||
this->status_set_warning(str_sprintf("temp read failed (%d)", err).c_str());
|
||||
char buf[32];
|
||||
snprintf(buf, sizeof(buf), "temp read failed (%d)", err);
|
||||
this->status_set_warning(buf);
|
||||
return;
|
||||
}
|
||||
temp = int16_t(raw_temp) * 0.01f;
|
||||
|
||||
@@ -492,7 +492,10 @@ bool RC522BinarySensor::process(std::vector<uint8_t> &data) {
|
||||
this->found_ = result;
|
||||
return result;
|
||||
}
|
||||
void RC522Trigger::process(std::vector<uint8_t> &data) { this->trigger(format_hex_pretty(data, '-', false)); }
|
||||
void RC522Trigger::process(std::vector<uint8_t> &data) {
|
||||
char uid_buf[format_hex_pretty_size(RC522_MAX_UID_SIZE)];
|
||||
this->trigger(format_hex_pretty_to(uid_buf, data.data(), data.size(), '-'));
|
||||
}
|
||||
|
||||
} // namespace rc522
|
||||
} // namespace esphome
|
||||
|
||||
@@ -21,6 +21,11 @@ static constexpr uint8_t CMD_FRAME_FOOTER[] = {0x04, 0x03, 0x02, 0x01};
|
||||
static constexpr uint16_t CMD_SINGLE_TARGET = 0x0080;
|
||||
static constexpr uint16_t CMD_MULTI_TARGET = 0x0090;
|
||||
|
||||
// Speed sentinel values (cm/s) - radar outputs these when no valid Doppler measurement
|
||||
// FMCW radars detect motion via Doppler shift; targets with these speeds are likely noise
|
||||
static constexpr int16_t SPEED_SENTINEL_248 = 248;
|
||||
static constexpr int16_t SPEED_SENTINEL_256 = 256;
|
||||
|
||||
// Decode coordinate/speed value from RD-03D format
|
||||
// Per datasheet: MSB=1 means positive, MSB=0 means negative
|
||||
static constexpr int16_t decode_value(uint8_t low_byte, uint8_t high_byte) {
|
||||
@@ -31,6 +36,13 @@ static constexpr int16_t decode_value(uint8_t low_byte, uint8_t high_byte) {
|
||||
return value;
|
||||
}
|
||||
|
||||
// Check if speed value indicates a valid Doppler measurement
|
||||
// Zero, ±248, or ±256 cm/s are sentinel values from the radar firmware
|
||||
static constexpr bool is_speed_valid(int16_t speed) {
|
||||
int16_t abs_speed = speed < 0 ? -speed : speed;
|
||||
return speed != 0 && abs_speed != SPEED_SENTINEL_248 && abs_speed != SPEED_SENTINEL_256;
|
||||
}
|
||||
|
||||
void RD03DComponent::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up RD-03D...");
|
||||
this->set_timeout(SETUP_TIMEOUT_MS, [this]() { this->apply_config_(); });
|
||||
@@ -136,8 +148,12 @@ void RD03DComponent::process_frame_() {
|
||||
int16_t speed = decode_value(speed_low, speed_high);
|
||||
uint16_t resolution = (res_high << 8) | res_low;
|
||||
|
||||
// Check if target is present (non-zero coordinates)
|
||||
bool target_present = (x != 0 || y != 0);
|
||||
// Check if target is present
|
||||
// Requires non-zero coordinates AND valid speed (not a sentinel value)
|
||||
// FMCW radars detect motion via Doppler; sentinel speed indicates no real target
|
||||
bool has_position = (x != 0 || y != 0);
|
||||
bool has_valid_speed = is_speed_valid(speed);
|
||||
bool target_present = has_position && has_valid_speed;
|
||||
if (target_present) {
|
||||
target_count++;
|
||||
}
|
||||
@@ -169,20 +185,21 @@ void RD03DComponent::process_frame_() {
|
||||
#ifdef USE_SENSOR
|
||||
void RD03DComponent::publish_target_(uint8_t target_num, int16_t x, int16_t y, int16_t speed, uint16_t resolution) {
|
||||
TargetSensor &target = this->targets_[target_num];
|
||||
bool valid = is_speed_valid(speed);
|
||||
|
||||
// Publish X coordinate (mm)
|
||||
// Publish X coordinate (mm) - NaN if target invalid
|
||||
if (target.x != nullptr) {
|
||||
target.x->publish_state(x);
|
||||
target.x->publish_state(valid ? static_cast<float>(x) : NAN);
|
||||
}
|
||||
|
||||
// Publish Y coordinate (mm)
|
||||
// Publish Y coordinate (mm) - NaN if target invalid
|
||||
if (target.y != nullptr) {
|
||||
target.y->publish_state(y);
|
||||
target.y->publish_state(valid ? static_cast<float>(y) : NAN);
|
||||
}
|
||||
|
||||
// Publish speed (convert from cm/s to mm/s)
|
||||
// Publish speed (convert from cm/s to mm/s) - NaN if target invalid
|
||||
if (target.speed != nullptr) {
|
||||
target.speed->publish_state(static_cast<float>(speed) * 10.0f);
|
||||
target.speed->publish_state(valid ? static_cast<float>(speed) * 10.0f : NAN);
|
||||
}
|
||||
|
||||
// Publish resolution (mm)
|
||||
@@ -190,20 +207,23 @@ void RD03DComponent::publish_target_(uint8_t target_num, int16_t x, int16_t y, i
|
||||
target.resolution->publish_state(resolution);
|
||||
}
|
||||
|
||||
// Calculate and publish distance (mm)
|
||||
// Calculate and publish distance (mm) - NaN if target invalid
|
||||
if (target.distance != nullptr) {
|
||||
float distance = std::hypot(static_cast<float>(x), static_cast<float>(y));
|
||||
target.distance->publish_state(distance);
|
||||
if (valid) {
|
||||
target.distance->publish_state(std::hypot(static_cast<float>(x), static_cast<float>(y)));
|
||||
} else {
|
||||
target.distance->publish_state(NAN);
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate and publish angle (degrees)
|
||||
// Calculate and publish angle (degrees) - NaN if target invalid
|
||||
// Angle is measured from the Y axis (radar forward direction)
|
||||
if (target.angle != nullptr) {
|
||||
if (x == 0 && y == 0) {
|
||||
target.angle->publish_state(0);
|
||||
} else {
|
||||
if (valid) {
|
||||
float angle = std::atan2(static_cast<float>(x), static_cast<float>(y)) * 180.0f / M_PI;
|
||||
target.angle->publish_state(angle);
|
||||
} else {
|
||||
target.angle->publish_state(NAN);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,8 @@ class MideaData {
|
||||
bool is_valid() const { return this->data_[OFFSET_CS] == this->calc_cs_(); }
|
||||
void finalize() { this->data_[OFFSET_CS] = this->calc_cs_(); }
|
||||
bool is_compliment(const MideaData &rhs) const;
|
||||
/// @deprecated Allocates heap memory. Use to_str() instead. Removed in 2026.7.0.
|
||||
ESPDEPRECATED("Allocates heap memory. Use to_str() instead. Removed in 2026.7.0.", "2026.1.0")
|
||||
std::string to_string() const { return format_hex_pretty(this->data_.data(), this->data_.size()); }
|
||||
/// Buffer size for to_str(): 6 bytes = "AA.BB.CC.DD.EE.FF\0"
|
||||
static constexpr size_t TO_STR_BUFFER_SIZE = format_hex_pretty_size(6);
|
||||
|
||||
@@ -40,13 +40,10 @@ void RemoteTransmitterComponent::await_target_time_() {
|
||||
if (this->target_time_ == 0) {
|
||||
this->target_time_ = current_time;
|
||||
} else if ((int32_t) (this->target_time_ - current_time) > 0) {
|
||||
#if defined(USE_LIBRETINY) || defined(USE_RP2040)
|
||||
// busy loop is required for libretiny and rp2040 as interrupts are disabled
|
||||
// busy loop is required as interrupts are disabled and delayMicroseconds()
|
||||
// may not work correctly in interrupt-disabled contexts on all platforms
|
||||
while ((int32_t) (this->target_time_ - micros()) > 0)
|
||||
;
|
||||
#else
|
||||
delayMicroseconds(this->target_time_ - current_time);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ void RuntimeStatsCollector::record_component_time(Component *component, uint32_t
|
||||
void RuntimeStatsCollector::log_stats_() {
|
||||
ESP_LOGI(TAG,
|
||||
"Component Runtime Statistics\n"
|
||||
"Period stats (last %" PRIu32 "ms):",
|
||||
" Period stats (last %" PRIu32 "ms):",
|
||||
this->log_interval_);
|
||||
|
||||
// First collect stats we want to display
|
||||
@@ -55,7 +55,7 @@ void RuntimeStatsCollector::log_stats_() {
|
||||
}
|
||||
|
||||
// Log total stats since boot
|
||||
ESP_LOGI(TAG, "Total stats (since boot):");
|
||||
ESP_LOGI(TAG, " Total stats (since boot):");
|
||||
|
||||
// Re-sort by total runtime for all-time stats
|
||||
std::sort(stats_to_display.begin(), stats_to_display.end(),
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user