1
0
mirror of https://github.com/esphome/esphome.git synced 2025-10-07 12:23:47 +01:00

Merge branch 'webserver_string_construction_reduce' into integration

This commit is contained in:
J. Nick Koston
2025-10-02 22:14:02 +02:00
3 changed files with 71 additions and 47 deletions

View File

@@ -381,11 +381,14 @@ void WebServer::handle_js_request(AsyncWebServerRequest *request) {
#endif #endif
// Helper functions to reduce code size by avoiding macro expansion // Helper functions to reduce code size by avoiding macro expansion
static void set_json_id(JsonObject &root, EntityBase *obj, const std::string &id, JsonDetail start_config) { static void set_json_id(JsonObject &root, EntityBase *obj, const char *prefix, JsonDetail start_config) {
root["id"] = id; char id_buf[160]; // object_id can be up to 128 chars + prefix + dash + null
const auto &object_id = obj->get_object_id();
snprintf(id_buf, sizeof(id_buf), "%s-%s", prefix, object_id.c_str());
root["id"] = id_buf;
if (start_config == DETAIL_ALL) { if (start_config == DETAIL_ALL) {
root["name"] = obj->get_name(); root["name"] = obj->get_name();
root["icon"] = obj->get_icon(); root["icon"] = obj->get_icon_ref();
root["entity_category"] = obj->get_entity_category(); root["entity_category"] = obj->get_entity_category();
bool is_disabled = obj->is_disabled_by_default(); bool is_disabled = obj->is_disabled_by_default();
if (is_disabled) if (is_disabled)
@@ -393,17 +396,19 @@ static void set_json_id(JsonObject &root, EntityBase *obj, const std::string &id
} }
} }
// Keep as separate function even though only used once: reduces code size by ~48 bytes
// by allowing compiler to share code between template instantiations (bool, float, etc.)
template<typename T> template<typename T>
static void set_json_value(JsonObject &root, EntityBase *obj, const std::string &id, const T &value, static void set_json_value(JsonObject &root, EntityBase *obj, const char *prefix, const T &value,
JsonDetail start_config) { JsonDetail start_config) {
set_json_id(root, obj, id, start_config); set_json_id(root, obj, prefix, start_config);
root["value"] = value; root["value"] = value;
} }
template<typename T> template<typename T>
static void set_json_icon_state_value(JsonObject &root, EntityBase *obj, const std::string &id, static void set_json_icon_state_value(JsonObject &root, EntityBase *obj, const char *prefix, const std::string &state,
const std::string &state, const T &value, JsonDetail start_config) { const T &value, JsonDetail start_config) {
set_json_value(root, obj, id, value, start_config); set_json_value(root, obj, prefix, value, start_config);
root["state"] = state; root["state"] = state;
} }
@@ -442,20 +447,20 @@ std::string WebServer::sensor_json(sensor::Sensor *obj, float value, JsonDetail
json::JsonBuilder builder; json::JsonBuilder builder;
JsonObject root = builder.root(); JsonObject root = builder.root();
const auto uom_ref = obj->get_unit_of_measurement_ref();
// Build JSON directly inline // Build JSON directly inline
std::string state; std::string state;
if (std::isnan(value)) { if (std::isnan(value)) {
state = "NA"; state = "NA";
} else { } else {
state = value_accuracy_to_string(value, obj->get_accuracy_decimals()); state = value_accuracy_with_uom_to_string(value, obj->get_accuracy_decimals(), uom_ref);
if (!obj->get_unit_of_measurement().empty())
state += " " + obj->get_unit_of_measurement();
} }
set_json_icon_state_value(root, obj, "sensor-" + obj->get_object_id(), state, value, start_config); set_json_icon_state_value(root, obj, "sensor", state, value, start_config);
if (start_config == DETAIL_ALL) { if (start_config == DETAIL_ALL) {
this->add_sorting_info_(root, obj); this->add_sorting_info_(root, obj);
if (!obj->get_unit_of_measurement().empty()) if (!uom_ref.empty())
root["uom"] = obj->get_unit_of_measurement(); root["uom"] = uom_ref;
} }
return builder.serialize(); return builder.serialize();
@@ -494,7 +499,7 @@ std::string WebServer::text_sensor_json(text_sensor::TextSensor *obj, const std:
json::JsonBuilder builder; json::JsonBuilder builder;
JsonObject root = builder.root(); JsonObject root = builder.root();
set_json_icon_state_value(root, obj, "text_sensor-" + obj->get_object_id(), value, value, start_config); set_json_icon_state_value(root, obj, "text_sensor", value, value, start_config);
if (start_config == DETAIL_ALL) { if (start_config == DETAIL_ALL) {
this->add_sorting_info_(root, obj); this->add_sorting_info_(root, obj);
} }
@@ -567,7 +572,7 @@ std::string WebServer::switch_json(switch_::Switch *obj, bool value, JsonDetail
json::JsonBuilder builder; json::JsonBuilder builder;
JsonObject root = builder.root(); JsonObject root = builder.root();
set_json_icon_state_value(root, obj, "switch-" + obj->get_object_id(), value ? "ON" : "OFF", value, start_config); set_json_icon_state_value(root, obj, "switch", value ? "ON" : "OFF", value, start_config);
if (start_config == DETAIL_ALL) { if (start_config == DETAIL_ALL) {
root["assumed_state"] = obj->assumed_state(); root["assumed_state"] = obj->assumed_state();
this->add_sorting_info_(root, obj); this->add_sorting_info_(root, obj);
@@ -607,7 +612,7 @@ std::string WebServer::button_json(button::Button *obj, JsonDetail start_config)
json::JsonBuilder builder; json::JsonBuilder builder;
JsonObject root = builder.root(); JsonObject root = builder.root();
set_json_id(root, obj, "button-" + obj->get_object_id(), start_config); set_json_id(root, obj, "button", start_config);
if (start_config == DETAIL_ALL) { if (start_config == DETAIL_ALL) {
this->add_sorting_info_(root, obj); this->add_sorting_info_(root, obj);
} }
@@ -647,8 +652,7 @@ std::string WebServer::binary_sensor_json(binary_sensor::BinarySensor *obj, bool
json::JsonBuilder builder; json::JsonBuilder builder;
JsonObject root = builder.root(); JsonObject root = builder.root();
set_json_icon_state_value(root, obj, "binary_sensor-" + obj->get_object_id(), value ? "ON" : "OFF", value, set_json_icon_state_value(root, obj, "binary_sensor", value ? "ON" : "OFF", value, start_config);
start_config);
if (start_config == DETAIL_ALL) { if (start_config == DETAIL_ALL) {
this->add_sorting_info_(root, obj); this->add_sorting_info_(root, obj);
} }
@@ -717,8 +721,7 @@ std::string WebServer::fan_json(fan::Fan *obj, JsonDetail start_config) {
json::JsonBuilder builder; json::JsonBuilder builder;
JsonObject root = builder.root(); JsonObject root = builder.root();
set_json_icon_state_value(root, obj, "fan-" + obj->get_object_id(), obj->state ? "ON" : "OFF", obj->state, set_json_icon_state_value(root, obj, "fan", obj->state ? "ON" : "OFF", obj->state, start_config);
start_config);
const auto traits = obj->get_traits(); const auto traits = obj->get_traits();
if (traits.supports_speed()) { if (traits.supports_speed()) {
root["speed_level"] = obj->speed; root["speed_level"] = obj->speed;
@@ -793,7 +796,7 @@ std::string WebServer::light_json(light::LightState *obj, JsonDetail start_confi
json::JsonBuilder builder; json::JsonBuilder builder;
JsonObject root = builder.root(); JsonObject root = builder.root();
set_json_id(root, obj, "light-" + obj->get_object_id(), start_config); set_json_id(root, obj, "light", start_config);
root["state"] = obj->remote_values.is_on() ? "ON" : "OFF"; root["state"] = obj->remote_values.is_on() ? "ON" : "OFF";
light::LightJSONSchema::dump_json(*obj, root); light::LightJSONSchema::dump_json(*obj, root);
@@ -881,8 +884,8 @@ std::string WebServer::cover_json(cover::Cover *obj, JsonDetail start_config) {
json::JsonBuilder builder; json::JsonBuilder builder;
JsonObject root = builder.root(); JsonObject root = builder.root();
set_json_icon_state_value(root, obj, "cover-" + obj->get_object_id(), obj->is_fully_closed() ? "CLOSED" : "OPEN", set_json_icon_state_value(root, obj, "cover", obj->is_fully_closed() ? "CLOSED" : "OPEN", obj->position,
obj->position, start_config); start_config);
root["current_operation"] = cover::cover_operation_to_str(obj->current_operation); root["current_operation"] = cover::cover_operation_to_str(obj->current_operation);
if (obj->get_traits().get_supports_position()) if (obj->get_traits().get_supports_position())
@@ -939,7 +942,9 @@ std::string WebServer::number_json(number::Number *obj, float value, JsonDetail
json::JsonBuilder builder; json::JsonBuilder builder;
JsonObject root = builder.root(); JsonObject root = builder.root();
set_json_id(root, obj, "number-" + obj->get_object_id(), start_config); const auto uom_ref = obj->traits.get_unit_of_measurement_ref();
set_json_id(root, obj, "number", start_config);
if (start_config == DETAIL_ALL) { if (start_config == DETAIL_ALL) {
root["min_value"] = root["min_value"] =
value_accuracy_to_string(obj->traits.get_min_value(), step_to_accuracy_decimals(obj->traits.get_step())); value_accuracy_to_string(obj->traits.get_min_value(), step_to_accuracy_decimals(obj->traits.get_step()));
@@ -947,8 +952,8 @@ std::string WebServer::number_json(number::Number *obj, float value, JsonDetail
value_accuracy_to_string(obj->traits.get_max_value(), step_to_accuracy_decimals(obj->traits.get_step())); value_accuracy_to_string(obj->traits.get_max_value(), step_to_accuracy_decimals(obj->traits.get_step()));
root["step"] = value_accuracy_to_string(obj->traits.get_step(), step_to_accuracy_decimals(obj->traits.get_step())); root["step"] = value_accuracy_to_string(obj->traits.get_step(), step_to_accuracy_decimals(obj->traits.get_step()));
root["mode"] = (int) obj->traits.get_mode(); root["mode"] = (int) obj->traits.get_mode();
if (!obj->traits.get_unit_of_measurement().empty()) if (!uom_ref.empty())
root["uom"] = obj->traits.get_unit_of_measurement(); root["uom"] = uom_ref;
this->add_sorting_info_(root, obj); this->add_sorting_info_(root, obj);
} }
if (std::isnan(value)) { if (std::isnan(value)) {
@@ -956,10 +961,8 @@ std::string WebServer::number_json(number::Number *obj, float value, JsonDetail
root["state"] = "NA"; root["state"] = "NA";
} else { } else {
root["value"] = value_accuracy_to_string(value, step_to_accuracy_decimals(obj->traits.get_step())); root["value"] = value_accuracy_to_string(value, step_to_accuracy_decimals(obj->traits.get_step()));
std::string state = value_accuracy_to_string(value, step_to_accuracy_decimals(obj->traits.get_step())); root["state"] =
if (!obj->traits.get_unit_of_measurement().empty()) value_accuracy_with_uom_to_string(value, step_to_accuracy_decimals(obj->traits.get_step()), uom_ref);
state += " " + obj->traits.get_unit_of_measurement();
root["state"] = state;
} }
return builder.serialize(); return builder.serialize();
@@ -1013,7 +1016,7 @@ std::string WebServer::date_json(datetime::DateEntity *obj, JsonDetail start_con
json::JsonBuilder builder; json::JsonBuilder builder;
JsonObject root = builder.root(); JsonObject root = builder.root();
set_json_id(root, obj, "date-" + obj->get_object_id(), start_config); set_json_id(root, obj, "date", start_config);
std::string value = str_sprintf("%d-%02d-%02d", obj->year, obj->month, obj->day); std::string value = str_sprintf("%d-%02d-%02d", obj->year, obj->month, obj->day);
root["value"] = value; root["value"] = value;
root["state"] = value; root["state"] = value;
@@ -1071,7 +1074,7 @@ std::string WebServer::time_json(datetime::TimeEntity *obj, JsonDetail start_con
json::JsonBuilder builder; json::JsonBuilder builder;
JsonObject root = builder.root(); JsonObject root = builder.root();
set_json_id(root, obj, "time-" + obj->get_object_id(), start_config); set_json_id(root, obj, "time", start_config);
std::string value = str_sprintf("%02d:%02d:%02d", obj->hour, obj->minute, obj->second); std::string value = str_sprintf("%02d:%02d:%02d", obj->hour, obj->minute, obj->second);
root["value"] = value; root["value"] = value;
root["state"] = value; root["state"] = value;
@@ -1129,7 +1132,7 @@ std::string WebServer::datetime_json(datetime::DateTimeEntity *obj, JsonDetail s
json::JsonBuilder builder; json::JsonBuilder builder;
JsonObject root = builder.root(); JsonObject root = builder.root();
set_json_id(root, obj, "datetime-" + obj->get_object_id(), start_config); set_json_id(root, obj, "datetime", start_config);
std::string value = std::string value =
str_sprintf("%d-%02d-%02d %02d:%02d:%02d", obj->year, obj->month, obj->day, obj->hour, obj->minute, obj->second); str_sprintf("%d-%02d-%02d %02d:%02d:%02d", obj->year, obj->month, obj->day, obj->hour, obj->minute, obj->second);
root["value"] = value; root["value"] = value;
@@ -1184,7 +1187,7 @@ std::string WebServer::text_json(text::Text *obj, const std::string &value, Json
json::JsonBuilder builder; json::JsonBuilder builder;
JsonObject root = builder.root(); JsonObject root = builder.root();
set_json_id(root, obj, "text-" + obj->get_object_id(), start_config); set_json_id(root, obj, "text", start_config);
root["min_length"] = obj->traits.get_min_length(); root["min_length"] = obj->traits.get_min_length();
root["max_length"] = obj->traits.get_max_length(); root["max_length"] = obj->traits.get_max_length();
root["pattern"] = obj->traits.get_pattern(); root["pattern"] = obj->traits.get_pattern();
@@ -1245,7 +1248,7 @@ std::string WebServer::select_json(select::Select *obj, const std::string &value
json::JsonBuilder builder; json::JsonBuilder builder;
JsonObject root = builder.root(); JsonObject root = builder.root();
set_json_icon_state_value(root, obj, "select-" + obj->get_object_id(), value, value, start_config); set_json_icon_state_value(root, obj, "select", value, value, start_config);
if (start_config == DETAIL_ALL) { if (start_config == DETAIL_ALL) {
JsonArray opt = root["option"].to<JsonArray>(); JsonArray opt = root["option"].to<JsonArray>();
for (auto &option : obj->traits.get_options()) { for (auto &option : obj->traits.get_options()) {
@@ -1314,7 +1317,7 @@ std::string WebServer::climate_json(climate::Climate *obj, JsonDetail start_conf
// NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson // NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
json::JsonBuilder builder; json::JsonBuilder builder;
JsonObject root = builder.root(); JsonObject root = builder.root();
set_json_id(root, obj, "climate-" + obj->get_object_id(), start_config); set_json_id(root, obj, "climate", start_config);
const auto traits = obj->get_traits(); const auto traits = obj->get_traits();
int8_t target_accuracy = traits.get_target_temperature_accuracy_decimals(); int8_t target_accuracy = traits.get_target_temperature_accuracy_decimals();
int8_t current_accuracy = traits.get_current_temperature_accuracy_decimals(); int8_t current_accuracy = traits.get_current_temperature_accuracy_decimals();
@@ -1467,8 +1470,7 @@ std::string WebServer::lock_json(lock::Lock *obj, lock::LockState value, JsonDet
json::JsonBuilder builder; json::JsonBuilder builder;
JsonObject root = builder.root(); JsonObject root = builder.root();
set_json_icon_state_value(root, obj, "lock-" + obj->get_object_id(), lock::lock_state_to_string(value), value, set_json_icon_state_value(root, obj, "lock", lock::lock_state_to_string(value), value, start_config);
start_config);
if (start_config == DETAIL_ALL) { if (start_config == DETAIL_ALL) {
this->add_sorting_info_(root, obj); this->add_sorting_info_(root, obj);
} }
@@ -1546,8 +1548,8 @@ std::string WebServer::valve_json(valve::Valve *obj, JsonDetail start_config) {
json::JsonBuilder builder; json::JsonBuilder builder;
JsonObject root = builder.root(); JsonObject root = builder.root();
set_json_icon_state_value(root, obj, "valve-" + obj->get_object_id(), obj->is_fully_closed() ? "CLOSED" : "OPEN", set_json_icon_state_value(root, obj, "valve", obj->is_fully_closed() ? "CLOSED" : "OPEN", obj->position,
obj->position, start_config); start_config);
root["current_operation"] = valve::valve_operation_to_str(obj->current_operation); root["current_operation"] = valve::valve_operation_to_str(obj->current_operation);
if (obj->get_traits().get_supports_position()) if (obj->get_traits().get_supports_position())
@@ -1630,8 +1632,8 @@ std::string WebServer::alarm_control_panel_json(alarm_control_panel::AlarmContro
JsonObject root = builder.root(); JsonObject root = builder.root();
char buf[16]; char buf[16];
set_json_icon_state_value(root, obj, "alarm-control-panel-" + obj->get_object_id(), set_json_icon_state_value(root, obj, "alarm-control-panel", PSTR_LOCAL(alarm_control_panel_state_to_string(value)),
PSTR_LOCAL(alarm_control_panel_state_to_string(value)), value, start_config); value, start_config);
if (start_config == DETAIL_ALL) { if (start_config == DETAIL_ALL) {
this->add_sorting_info_(root, obj); this->add_sorting_info_(root, obj);
} }
@@ -1676,7 +1678,7 @@ std::string WebServer::event_json(event::Event *obj, const std::string &event_ty
json::JsonBuilder builder; json::JsonBuilder builder;
JsonObject root = builder.root(); JsonObject root = builder.root();
set_json_id(root, obj, "event-" + obj->get_object_id(), start_config); set_json_id(root, obj, "event", start_config);
if (!event_type.empty()) { if (!event_type.empty()) {
root["event_type"] = event_type; root["event_type"] = event_type;
} }
@@ -1685,7 +1687,7 @@ std::string WebServer::event_json(event::Event *obj, const std::string &event_ty
for (auto const &event_type : obj->get_event_types()) { for (auto const &event_type : obj->get_event_types()) {
event_types.add(event_type); event_types.add(event_type);
} }
root["device_class"] = obj->get_device_class(); root["device_class"] = obj->get_device_class_ref();
this->add_sorting_info_(root, obj); this->add_sorting_info_(root, obj);
} }
@@ -1748,7 +1750,7 @@ std::string WebServer::update_json(update::UpdateEntity *obj, JsonDetail start_c
json::JsonBuilder builder; json::JsonBuilder builder;
JsonObject root = builder.root(); JsonObject root = builder.root();
set_json_id(root, obj, "update-" + obj->get_object_id(), start_config); set_json_id(root, obj, "update", start_config);
root["value"] = obj->update_info.latest_version; root["value"] = obj->update_info.latest_version;
root["state"] = update_state_to_string(obj->state); root["state"] = update_state_to_string(obj->state);
if (start_config == DETAIL_ALL) { if (start_config == DETAIL_ALL) {

View File

@@ -3,6 +3,7 @@
#include "esphome/core/defines.h" #include "esphome/core/defines.h"
#include "esphome/core/hal.h" #include "esphome/core/hal.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
#include "esphome/core/string_ref.h"
#include <strings.h> #include <strings.h>
#include <algorithm> #include <algorithm>
@@ -348,17 +349,32 @@ ParseOnOffState parse_on_off(const char *str, const char *on, const char *off) {
return PARSE_NONE; return PARSE_NONE;
} }
std::string value_accuracy_to_string(float value, int8_t accuracy_decimals) { static inline void normalize_accuracy_decimals(float &value, int8_t &accuracy_decimals) {
if (accuracy_decimals < 0) { if (accuracy_decimals < 0) {
auto multiplier = powf(10.0f, accuracy_decimals); auto multiplier = powf(10.0f, accuracy_decimals);
value = roundf(value * multiplier) / multiplier; value = roundf(value * multiplier) / multiplier;
accuracy_decimals = 0; accuracy_decimals = 0;
} }
}
std::string value_accuracy_to_string(float value, int8_t accuracy_decimals) {
normalize_accuracy_decimals(value, accuracy_decimals);
char tmp[32]; // should be enough, but we should maybe improve this at some point. char tmp[32]; // should be enough, but we should maybe improve this at some point.
snprintf(tmp, sizeof(tmp), "%.*f", accuracy_decimals, value); snprintf(tmp, sizeof(tmp), "%.*f", accuracy_decimals, value);
return std::string(tmp); return std::string(tmp);
} }
std::string value_accuracy_with_uom_to_string(float value, int8_t accuracy_decimals, StringRef unit_of_measurement) {
normalize_accuracy_decimals(value, accuracy_decimals);
char tmp[64]; // Increased to accommodate unit of measurement
if (unit_of_measurement.empty()) {
snprintf(tmp, sizeof(tmp), "%.*f", accuracy_decimals, value);
} else {
snprintf(tmp, sizeof(tmp), "%.*f %s", accuracy_decimals, value, unit_of_measurement.c_str());
}
return std::string(tmp);
}
int8_t step_to_accuracy_decimals(float step) { int8_t step_to_accuracy_decimals(float step) {
// use printf %g to find number of digits based on temperature step // use printf %g to find number of digits based on temperature step
char buf[32]; char buf[32];

View File

@@ -14,6 +14,10 @@
#include "esphome/core/optional.h" #include "esphome/core/optional.h"
namespace esphome {
class StringRef;
}
#ifdef USE_ESP8266 #ifdef USE_ESP8266
#include <Esp.h> #include <Esp.h>
#endif #endif
@@ -603,6 +607,8 @@ ParseOnOffState parse_on_off(const char *str, const char *on = nullptr, const ch
/// Create a string from a value and an accuracy in decimals. /// Create a string from a value and an accuracy in decimals.
std::string value_accuracy_to_string(float value, int8_t accuracy_decimals); std::string value_accuracy_to_string(float value, int8_t accuracy_decimals);
/// Create a string from a value, an accuracy in decimals, and a unit of measurement.
std::string value_accuracy_with_uom_to_string(float value, int8_t accuracy_decimals, StringRef unit_of_measurement);
/// Derive accuracy in decimals from an increment step. /// Derive accuracy in decimals from an increment step.
int8_t step_to_accuracy_decimals(float step); int8_t step_to_accuracy_decimals(float step);