mirror of
https://github.com/esphome/esphome.git
synced 2026-09-18 18:48:39 +00:00
Merge remote-tracking branch 'upstream/esp8266_web_server_defer' into integration
This commit is contained in:
@@ -18,7 +18,15 @@ InfraredCall &InfraredCall::set_carrier_frequency(uint32_t frequency) {
|
||||
|
||||
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
|
||||
this->packed_data_ = nullptr;
|
||||
this->base85_ptr_ = nullptr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
InfraredCall &InfraredCall::set_raw_timings_base85(const std::string &base85) {
|
||||
this->base85_ptr_ = &base85;
|
||||
this->raw_timings_ = nullptr;
|
||||
this->packed_data_ = nullptr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -26,7 +34,8 @@ InfraredCall &InfraredCall::set_raw_timings_packed(const uint8_t *data, uint16_t
|
||||
this->packed_data_ = data;
|
||||
this->packed_length_ = length;
|
||||
this->packed_count_ = count;
|
||||
this->raw_timings_ = nullptr; // Clear vector if packed is set
|
||||
this->raw_timings_ = nullptr;
|
||||
this->base85_ptr_ = nullptr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -92,6 +101,14 @@ void Infrared::control(const InfraredCall &call) {
|
||||
call.get_packed_count());
|
||||
ESP_LOGD(TAG, "Transmitting packed raw timings: count=%u, repeat=%u", call.get_packed_count(),
|
||||
call.get_repeat_count());
|
||||
} else if (call.is_base85()) {
|
||||
// Decode base85 directly into transmit buffer (zero heap allocations)
|
||||
if (!transmit_data->set_data_from_base85(call.get_base85_data())) {
|
||||
ESP_LOGE(TAG, "Invalid base85 data");
|
||||
return;
|
||||
}
|
||||
ESP_LOGD(TAG, "Transmitting base85 raw timings: count=%zu, repeat=%u", transmit_data->get_data().size(),
|
||||
call.get_repeat_count());
|
||||
} else {
|
||||
// From vector (lambdas/automations)
|
||||
transmit_data->set_data(call.get_raw_timings());
|
||||
|
||||
@@ -28,12 +28,29 @@ class InfraredCall {
|
||||
|
||||
/// 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)
|
||||
|
||||
// ===== Raw Timings Methods =====
|
||||
// All set_raw_timings_* methods store pointers/references to external data.
|
||||
// The referenced data must remain valid until perform() completes.
|
||||
// Safe pattern: call.set_raw_timings_xxx(data); call.perform(); // synchronous
|
||||
// Unsafe pattern: call.set_raw_timings_xxx(data); defer([call]() { call.perform(); }); // data may be gone!
|
||||
|
||||
/// Set the raw timings from a vector (positive = mark, negative = space)
|
||||
/// @note Lifetime: Stores a pointer to the vector. The vector must outlive perform().
|
||||
/// @note Usage: Primarily for lambdas/automations where the vector is in scope.
|
||||
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
|
||||
|
||||
/// Set the raw timings from base85-encoded int32 data
|
||||
/// @note Lifetime: Stores a pointer to the string. The string must outlive perform().
|
||||
/// @note Usage: For web_server where the encoded string is on the stack.
|
||||
/// @note Decoding happens at perform() time, directly into the transmit buffer.
|
||||
InfraredCall &set_raw_timings_base85(const std::string &base85);
|
||||
|
||||
/// Set the raw timings from packed protobuf sint32 data (zigzag + varint encoded)
|
||||
/// @note Lifetime: Stores a pointer to the buffer. The buffer must outlive perform().
|
||||
/// @note Usage: For API component where data comes directly from the protobuf message.
|
||||
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);
|
||||
|
||||
@@ -42,12 +59,18 @@ class InfraredCall {
|
||||
|
||||
/// 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)
|
||||
/// Get the raw timings (only valid if set via set_raw_timings, not packed or base85)
|
||||
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 raw timings have been set (vector, packed, or base85)
|
||||
bool has_raw_timings() const {
|
||||
return this->raw_timings_ != nullptr || this->packed_data_ != nullptr || this->base85_ptr_ != nullptr;
|
||||
}
|
||||
/// Check if using packed data format
|
||||
bool is_packed() const { return this->packed_data_ != nullptr; }
|
||||
/// Check if using base85 data format
|
||||
bool is_base85() const { return this->base85_ptr_ != nullptr; }
|
||||
/// Get the base85 data string
|
||||
const std::string &get_base85_data() const { return *this->base85_ptr_; }
|
||||
/// 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_; }
|
||||
@@ -59,9 +82,11 @@ class InfraredCall {
|
||||
uint32_t repeat_count_{1};
|
||||
Infrared *parent_;
|
||||
optional<uint32_t> carrier_frequency_;
|
||||
// Vector-based timings (for lambdas/automations)
|
||||
// Pointer to vector-based timings (caller-owned, must outlive perform())
|
||||
const std::vector<int32_t> *raw_timings_{nullptr};
|
||||
// Packed protobuf timings (for API zero-copy)
|
||||
// Pointer to base85-encoded string (caller-owned, must outlive perform())
|
||||
const std::string *base85_ptr_{nullptr};
|
||||
// Pointer to packed protobuf buffer (caller-owned, must outlive perform())
|
||||
const uint8_t *packed_data_{nullptr};
|
||||
uint16_t packed_length_{0};
|
||||
uint16_t packed_count_{0};
|
||||
|
||||
@@ -159,6 +159,10 @@ void RemoteTransmitData::set_data_from_packed_sint32(const uint8_t *data, size_t
|
||||
}
|
||||
}
|
||||
|
||||
bool RemoteTransmitData::set_data_from_base85(const std::string &base85) {
|
||||
return base85_decode_int32_vector(base85, this->data_);
|
||||
}
|
||||
|
||||
/* RemoteTransmitterBase */
|
||||
|
||||
void RemoteTransmitterBase::send_(uint32_t send_times, uint32_t send_wait) {
|
||||
|
||||
@@ -36,6 +36,11 @@ class RemoteTransmitData {
|
||||
/// @param len Length of the buffer in bytes
|
||||
/// @param count Number of values (for reserve optimization)
|
||||
void set_data_from_packed_sint32(const uint8_t *data, size_t len, size_t count);
|
||||
/// Set data from base85-encoded int32 values
|
||||
/// Decodes directly into internal buffer (zero heap allocations)
|
||||
/// @param base85 Base85-encoded string (5 chars per int32 value)
|
||||
/// @return true if successful, false if decode failed or invalid size
|
||||
bool set_data_from_base85(const std::string &base85);
|
||||
void reset() {
|
||||
this->data_.clear();
|
||||
this->carrier_frequency_ = 0;
|
||||
|
||||
@@ -658,6 +658,24 @@ std::string WebServer::text_sensor_json_(text_sensor::TextSensor *obj, const std
|
||||
#endif
|
||||
|
||||
#ifdef USE_SWITCH
|
||||
enum SwitchAction : uint8_t { SWITCH_ACTION_NONE, SWITCH_ACTION_TOGGLE, SWITCH_ACTION_TURN_ON, SWITCH_ACTION_TURN_OFF };
|
||||
|
||||
static void execute_switch_action(switch_::Switch *obj, SwitchAction action) {
|
||||
switch (action) {
|
||||
case SWITCH_ACTION_TOGGLE:
|
||||
obj->toggle();
|
||||
break;
|
||||
case SWITCH_ACTION_TURN_ON:
|
||||
obj->turn_on();
|
||||
break;
|
||||
case SWITCH_ACTION_TURN_OFF:
|
||||
obj->turn_off();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void WebServer::on_switch_update(switch_::Switch *obj) {
|
||||
if (!this->include_internal_ && obj->is_internal())
|
||||
return;
|
||||
@@ -676,34 +694,22 @@ void WebServer::handle_switch_request(AsyncWebServerRequest *request, const UrlM
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle action methods with single defer and response
|
||||
enum SwitchAction { NONE, TOGGLE, TURN_ON, TURN_OFF };
|
||||
SwitchAction action = NONE;
|
||||
SwitchAction action = SWITCH_ACTION_NONE;
|
||||
|
||||
if (match.method_equals(ESPHOME_F("toggle"))) {
|
||||
action = TOGGLE;
|
||||
action = SWITCH_ACTION_TOGGLE;
|
||||
} else if (match.method_equals(ESPHOME_F("turn_on"))) {
|
||||
action = TURN_ON;
|
||||
action = SWITCH_ACTION_TURN_ON;
|
||||
} else if (match.method_equals(ESPHOME_F("turn_off"))) {
|
||||
action = TURN_OFF;
|
||||
action = SWITCH_ACTION_TURN_OFF;
|
||||
}
|
||||
|
||||
if (action != NONE) {
|
||||
this->defer([obj, action]() {
|
||||
switch (action) {
|
||||
case TOGGLE:
|
||||
obj->toggle();
|
||||
break;
|
||||
case TURN_ON:
|
||||
obj->turn_on();
|
||||
break;
|
||||
case TURN_OFF:
|
||||
obj->turn_off();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
if (action != SWITCH_ACTION_NONE) {
|
||||
#ifdef USE_ESP8266
|
||||
execute_switch_action(obj, action);
|
||||
#else
|
||||
this->defer([obj, action]() { execute_switch_action(obj, action); });
|
||||
#endif
|
||||
request->send(200);
|
||||
} else {
|
||||
request->send(404);
|
||||
@@ -743,7 +749,7 @@ void WebServer::handle_button_request(AsyncWebServerRequest *request, const UrlM
|
||||
std::string data = this->button_json_(obj, detail);
|
||||
request->send(200, "application/json", data.c_str());
|
||||
} else if (match.method_equals(ESPHOME_F("press"))) {
|
||||
this->defer([obj]() { obj->press(); });
|
||||
DEFER_ACTION(obj, obj->press());
|
||||
request->send(200);
|
||||
return;
|
||||
} else {
|
||||
@@ -828,7 +834,7 @@ void WebServer::handle_fan_request(AsyncWebServerRequest *request, const UrlMatc
|
||||
std::string data = this->fan_json_(obj, detail);
|
||||
request->send(200, "application/json", data.c_str());
|
||||
} else if (match.method_equals(ESPHOME_F("toggle"))) {
|
||||
this->defer([obj]() { obj->toggle().perform(); });
|
||||
DEFER_ACTION(obj, obj->toggle().perform());
|
||||
request->send(200);
|
||||
} else {
|
||||
bool is_on = match.method_equals(ESPHOME_F("turn_on"));
|
||||
@@ -859,7 +865,7 @@ void WebServer::handle_fan_request(AsyncWebServerRequest *request, const UrlMatc
|
||||
return;
|
||||
}
|
||||
}
|
||||
this->defer([call]() mutable { call.perform(); });
|
||||
DEFER_ACTION(call, call.perform());
|
||||
request->send(200);
|
||||
}
|
||||
return;
|
||||
@@ -909,7 +915,7 @@ void WebServer::handle_light_request(AsyncWebServerRequest *request, const UrlMa
|
||||
std::string data = this->light_json_(obj, detail);
|
||||
request->send(200, "application/json", data.c_str());
|
||||
} else if (match.method_equals(ESPHOME_F("toggle"))) {
|
||||
this->defer([obj]() { obj->toggle().perform(); });
|
||||
DEFER_ACTION(obj, obj->toggle().perform());
|
||||
request->send(200);
|
||||
} else {
|
||||
bool is_on = match.method_equals(ESPHOME_F("turn_on"));
|
||||
@@ -938,7 +944,7 @@ void WebServer::handle_light_request(AsyncWebServerRequest *request, const UrlMa
|
||||
parse_string_param_(request, ESPHOME_F("effect"), call, &decltype(call)::set_effect);
|
||||
}
|
||||
|
||||
this->defer([call]() mutable { call.perform(); });
|
||||
DEFER_ACTION(call, call.perform());
|
||||
request->send(200);
|
||||
}
|
||||
return;
|
||||
@@ -1027,7 +1033,7 @@ void WebServer::handle_cover_request(AsyncWebServerRequest *request, const UrlMa
|
||||
parse_float_param_(request, ESPHOME_F("position"), call, &decltype(call)::set_position);
|
||||
parse_float_param_(request, ESPHOME_F("tilt"), call, &decltype(call)::set_tilt);
|
||||
|
||||
this->defer([call]() mutable { call.perform(); });
|
||||
DEFER_ACTION(call, call.perform());
|
||||
request->send(200);
|
||||
return;
|
||||
}
|
||||
@@ -1086,7 +1092,7 @@ void WebServer::handle_number_request(AsyncWebServerRequest *request, const UrlM
|
||||
auto call = obj->make_call();
|
||||
parse_float_param_(request, ESPHOME_F("value"), call, &decltype(call)::set_value);
|
||||
|
||||
this->defer([call]() mutable { call.perform(); });
|
||||
DEFER_ACTION(call, call.perform());
|
||||
request->send(200);
|
||||
return;
|
||||
}
|
||||
@@ -1159,7 +1165,7 @@ void WebServer::handle_date_request(AsyncWebServerRequest *request, const UrlMat
|
||||
|
||||
parse_string_param_(request, ESPHOME_F("value"), call, &decltype(call)::set_date);
|
||||
|
||||
this->defer([call]() mutable { call.perform(); });
|
||||
DEFER_ACTION(call, call.perform());
|
||||
request->send(200);
|
||||
return;
|
||||
}
|
||||
@@ -1223,7 +1229,7 @@ void WebServer::handle_time_request(AsyncWebServerRequest *request, const UrlMat
|
||||
|
||||
parse_string_param_(request, ESPHOME_F("value"), call, &decltype(call)::set_time);
|
||||
|
||||
this->defer([call]() mutable { call.perform(); });
|
||||
DEFER_ACTION(call, call.perform());
|
||||
request->send(200);
|
||||
return;
|
||||
}
|
||||
@@ -1286,7 +1292,7 @@ void WebServer::handle_datetime_request(AsyncWebServerRequest *request, const Ur
|
||||
|
||||
parse_string_param_(request, ESPHOME_F("value"), call, &decltype(call)::set_datetime);
|
||||
|
||||
this->defer([call]() mutable { call.perform(); });
|
||||
DEFER_ACTION(call, call.perform());
|
||||
request->send(200);
|
||||
return;
|
||||
}
|
||||
@@ -1346,7 +1352,7 @@ void WebServer::handle_text_request(AsyncWebServerRequest *request, const UrlMat
|
||||
auto call = obj->make_call();
|
||||
parse_string_param_(request, ESPHOME_F("value"), call, &decltype(call)::set_value);
|
||||
|
||||
this->defer([call]() mutable { call.perform(); });
|
||||
DEFER_ACTION(call, call.perform());
|
||||
request->send(200);
|
||||
return;
|
||||
}
|
||||
@@ -1404,7 +1410,7 @@ void WebServer::handle_select_request(AsyncWebServerRequest *request, const UrlM
|
||||
auto call = obj->make_call();
|
||||
parse_string_param_(request, ESPHOME_F("option"), call, &decltype(call)::set_option);
|
||||
|
||||
this->defer([call]() mutable { call.perform(); });
|
||||
DEFER_ACTION(call, call.perform());
|
||||
request->send(200);
|
||||
return;
|
||||
}
|
||||
@@ -1473,7 +1479,7 @@ void WebServer::handle_climate_request(AsyncWebServerRequest *request, const Url
|
||||
parse_float_param_(request, ESPHOME_F("target_temperature_low"), call, &decltype(call)::set_target_temperature_low);
|
||||
parse_float_param_(request, ESPHOME_F("target_temperature"), call, &decltype(call)::set_target_temperature);
|
||||
|
||||
this->defer([call]() mutable { call.perform(); });
|
||||
DEFER_ACTION(call, call.perform());
|
||||
request->send(200);
|
||||
return;
|
||||
}
|
||||
@@ -1589,6 +1595,24 @@ std::string WebServer::climate_json_(climate::Climate *obj, JsonDetail start_con
|
||||
#endif
|
||||
|
||||
#ifdef USE_LOCK
|
||||
enum LockAction : uint8_t { LOCK_ACTION_NONE, LOCK_ACTION_LOCK, LOCK_ACTION_UNLOCK, LOCK_ACTION_OPEN };
|
||||
|
||||
static void execute_lock_action(lock::Lock *obj, LockAction action) {
|
||||
switch (action) {
|
||||
case LOCK_ACTION_LOCK:
|
||||
obj->lock();
|
||||
break;
|
||||
case LOCK_ACTION_UNLOCK:
|
||||
obj->unlock();
|
||||
break;
|
||||
case LOCK_ACTION_OPEN:
|
||||
obj->open();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void WebServer::on_lock_update(lock::Lock *obj) {
|
||||
if (!this->include_internal_ && obj->is_internal())
|
||||
return;
|
||||
@@ -1607,34 +1631,22 @@ void WebServer::handle_lock_request(AsyncWebServerRequest *request, const UrlMat
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle action methods with single defer and response
|
||||
enum LockAction { NONE, LOCK, UNLOCK, OPEN };
|
||||
LockAction action = NONE;
|
||||
LockAction action = LOCK_ACTION_NONE;
|
||||
|
||||
if (match.method_equals(ESPHOME_F("lock"))) {
|
||||
action = LOCK;
|
||||
action = LOCK_ACTION_LOCK;
|
||||
} else if (match.method_equals(ESPHOME_F("unlock"))) {
|
||||
action = UNLOCK;
|
||||
action = LOCK_ACTION_UNLOCK;
|
||||
} else if (match.method_equals(ESPHOME_F("open"))) {
|
||||
action = OPEN;
|
||||
action = LOCK_ACTION_OPEN;
|
||||
}
|
||||
|
||||
if (action != NONE) {
|
||||
this->defer([obj, action]() {
|
||||
switch (action) {
|
||||
case LOCK:
|
||||
obj->lock();
|
||||
break;
|
||||
case UNLOCK:
|
||||
obj->unlock();
|
||||
break;
|
||||
case OPEN:
|
||||
obj->open();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
if (action != LOCK_ACTION_NONE) {
|
||||
#ifdef USE_ESP8266
|
||||
execute_lock_action(obj, action);
|
||||
#else
|
||||
this->defer([obj, action]() { execute_lock_action(obj, action); });
|
||||
#endif
|
||||
request->send(200);
|
||||
} else {
|
||||
request->send(404);
|
||||
@@ -1717,7 +1729,7 @@ void WebServer::handle_valve_request(AsyncWebServerRequest *request, const UrlMa
|
||||
|
||||
parse_float_param_(request, ESPHOME_F("position"), call, &decltype(call)::set_position);
|
||||
|
||||
this->defer([call]() mutable { call.perform(); });
|
||||
DEFER_ACTION(call, call.perform());
|
||||
request->send(200);
|
||||
return;
|
||||
}
|
||||
@@ -1796,7 +1808,7 @@ void WebServer::handle_alarm_control_panel_request(AsyncWebServerRequest *reques
|
||||
return;
|
||||
}
|
||||
|
||||
this->defer([call]() mutable { call.perform(); });
|
||||
DEFER_ACTION(call, call.perform());
|
||||
request->send(200);
|
||||
return;
|
||||
}
|
||||
@@ -1872,7 +1884,7 @@ void WebServer::handle_water_heater_request(AsyncWebServerRequest *request, cons
|
||||
// Parse on/off parameter
|
||||
parse_bool_param_(request, ESPHOME_F("is_on"), base_call, &water_heater::WaterHeaterCall::set_on);
|
||||
|
||||
this->defer([call]() mutable { call.perform(); });
|
||||
DEFER_ACTION(call, call.perform());
|
||||
request->send(200);
|
||||
return;
|
||||
}
|
||||
@@ -2032,7 +2044,7 @@ void WebServer::handle_update_request(AsyncWebServerRequest *request, const UrlM
|
||||
return;
|
||||
}
|
||||
|
||||
this->defer([obj]() mutable { obj->perform(); });
|
||||
DEFER_ACTION(obj, obj->perform());
|
||||
request->send(200);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -42,6 +42,14 @@ using ParamNameType = const __FlashStringHelper *;
|
||||
using ParamNameType = const char *;
|
||||
#endif
|
||||
|
||||
// ESP8266 is single-threaded, so actions can execute directly in request context.
|
||||
// Multi-core platforms need to defer to main loop thread for thread safety.
|
||||
#ifdef USE_ESP8266
|
||||
#define DEFER_ACTION(capture, action) action
|
||||
#else
|
||||
#define DEFER_ACTION(capture, action) this->defer([capture]() mutable { action; })
|
||||
#endif
|
||||
|
||||
/// Result of matching a URL against an entity
|
||||
struct EntityMatchResult {
|
||||
bool matched; ///< True if entity matched the URL
|
||||
|
||||
@@ -645,6 +645,55 @@ std::vector<uint8_t> base64_decode(const std::string &encoded_string) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// Encode int32 to 5 base85 characters + null terminator
|
||||
/// Standard ASCII85 alphabet: '!' (33) = 0 through 'u' (117) = 84
|
||||
inline void base85_encode_int32(int32_t value, std::span<char, BASE85_INT32_ENCODED_SIZE> output) {
|
||||
uint32_t v = static_cast<uint32_t>(value);
|
||||
// Encode least significant digit first, then reverse
|
||||
for (int i = 4; i >= 0; i--) {
|
||||
output[i] = static_cast<char>('!' + (v % 85));
|
||||
v /= 85;
|
||||
}
|
||||
output[5] = '\0';
|
||||
}
|
||||
|
||||
/// Decode 5 base85 characters to int32
|
||||
inline bool base85_decode_int32(const char *input, int32_t &out) {
|
||||
uint8_t c0 = static_cast<uint8_t>(input[0] - '!');
|
||||
uint8_t c1 = static_cast<uint8_t>(input[1] - '!');
|
||||
uint8_t c2 = static_cast<uint8_t>(input[2] - '!');
|
||||
uint8_t c3 = static_cast<uint8_t>(input[3] - '!');
|
||||
uint8_t c4 = static_cast<uint8_t>(input[4] - '!');
|
||||
|
||||
// Each digit must be 0-84. Since uint8_t wraps, chars below '!' become > 84
|
||||
if (c0 > 84 || c1 > 84 || c2 > 84 || c3 > 84 || c4 > 84)
|
||||
return false;
|
||||
|
||||
// 85^4 = 52200625, 85^3 = 614125, 85^2 = 7225, 85^1 = 85
|
||||
out = static_cast<int32_t>(c0 * 52200625u + c1 * 614125u + c2 * 7225u + c3 * 85u + c4);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Decode base85 string directly into vector (no intermediate buffer)
|
||||
bool base85_decode_int32_vector(const std::string &base85, std::vector<int32_t> &out) {
|
||||
size_t len = base85.size();
|
||||
if (len % 5 != 0)
|
||||
return false;
|
||||
|
||||
out.clear();
|
||||
const char *ptr = base85.data();
|
||||
const char *end = ptr + len;
|
||||
|
||||
while (ptr < end) {
|
||||
int32_t value;
|
||||
if (!base85_decode_int32(ptr, value))
|
||||
return false;
|
||||
out.push_back(value);
|
||||
ptr += 5;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Colors
|
||||
|
||||
float gamma_correct(float value, float gamma) {
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <cstdarg>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <functional>
|
||||
#include <iterator>
|
||||
@@ -18,6 +21,7 @@
|
||||
|
||||
#ifdef USE_ESP8266
|
||||
#include <Esp.h>
|
||||
#include <pgmspace.h>
|
||||
#endif
|
||||
|
||||
#ifdef USE_RP2040
|
||||
@@ -661,6 +665,53 @@ std::string __attribute__((format(printf, 1, 3))) str_snprintf(const char *fmt,
|
||||
/// @warning Allocates heap memory. Use snprintf() with a stack buffer instead.
|
||||
std::string __attribute__((format(printf, 1, 2))) str_sprintf(const char *fmt, ...);
|
||||
|
||||
#ifdef USE_ESP8266
|
||||
// ESP8266: Use vsnprintf_P to keep format strings in flash (PROGMEM)
|
||||
// Format strings must be wrapped with PSTR() macro
|
||||
/// Safely append formatted string to buffer, returning new position (capped at size).
|
||||
/// @param buf Output buffer
|
||||
/// @param size Total buffer size
|
||||
/// @param pos Current position in buffer
|
||||
/// @param fmt Format string (must be in PROGMEM on ESP8266)
|
||||
/// @return New position after appending (capped at size on overflow)
|
||||
inline size_t buf_append_printf_p(char *buf, size_t size, size_t pos, PGM_P fmt, ...) {
|
||||
if (pos >= size) {
|
||||
return size;
|
||||
}
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
int written = vsnprintf_P(buf + pos, size - pos, fmt, args);
|
||||
va_end(args);
|
||||
if (written < 0) {
|
||||
return pos; // encoding error
|
||||
}
|
||||
return std::min(pos + static_cast<size_t>(written), size);
|
||||
}
|
||||
#define buf_append_printf(buf, size, pos, fmt, ...) buf_append_printf_p(buf, size, pos, PSTR(fmt), ##__VA_ARGS__)
|
||||
#else
|
||||
/// Safely append formatted string to buffer, returning new position (capped at size).
|
||||
/// Handles snprintf edge cases: negative returns (encoding errors) and truncation.
|
||||
/// @param buf Output buffer
|
||||
/// @param size Total buffer size
|
||||
/// @param pos Current position in buffer
|
||||
/// @param fmt printf-style format string
|
||||
/// @return New position after appending (capped at size on overflow)
|
||||
__attribute__((format(printf, 4, 5))) inline size_t buf_append_printf(char *buf, size_t size, size_t pos,
|
||||
const char *fmt, ...) {
|
||||
if (pos >= size) {
|
||||
return size;
|
||||
}
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
int written = vsnprintf(buf + pos, size - pos, fmt, args);
|
||||
va_end(args);
|
||||
if (written < 0) {
|
||||
return pos; // encoding error
|
||||
}
|
||||
return std::min(pos + static_cast<size_t>(written), size);
|
||||
}
|
||||
#endif
|
||||
|
||||
/// Concatenate a name with a separator and suffix using an efficient stack-based approach.
|
||||
/// This avoids multiple heap allocations during string construction.
|
||||
/// Maximum name length supported is 120 characters for friendly names.
|
||||
@@ -1240,6 +1291,14 @@ std::vector<uint8_t> base64_decode(const std::string &encoded_string);
|
||||
size_t base64_decode(std::string const &encoded_string, uint8_t *buf, size_t buf_len);
|
||||
size_t base64_decode(const uint8_t *encoded_data, size_t encoded_len, uint8_t *buf, size_t buf_len);
|
||||
|
||||
/// Size of buffer needed for base85 encoded int32 (5 chars + null terminator)
|
||||
static constexpr size_t BASE85_INT32_ENCODED_SIZE = 6;
|
||||
|
||||
void base85_encode_int32(int32_t value, std::span<char, BASE85_INT32_ENCODED_SIZE> output);
|
||||
|
||||
bool base85_decode_int32(const char *input, int32_t &out);
|
||||
bool base85_decode_int32_vector(const std::string &base85, std::vector<int32_t> &out);
|
||||
|
||||
///@}
|
||||
|
||||
/// @name Colors
|
||||
|
||||
Reference in New Issue
Block a user