mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-29 22:24:26 +00:00 
			
		
		
		
	Merge branch 'webserver_string_construction_reduce' into integration
This commit is contained in:
		| @@ -381,11 +381,14 @@ void WebServer::handle_js_request(AsyncWebServerRequest *request) { | ||||
| #endif | ||||
|  | ||||
| // 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) { | ||||
|   root["id"] = id; | ||||
| static void set_json_id(JsonObject &root, EntityBase *obj, const char *prefix, JsonDetail start_config) { | ||||
|   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) { | ||||
|     root["name"] = obj->get_name(); | ||||
|     root["icon"] = obj->get_icon(); | ||||
|     root["icon"] = obj->get_icon_ref(); | ||||
|     root["entity_category"] = obj->get_entity_category(); | ||||
|     bool is_disabled = obj->is_disabled_by_default(); | ||||
|     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> | ||||
| 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) { | ||||
|   set_json_id(root, obj, id, start_config); | ||||
|   set_json_id(root, obj, prefix, start_config); | ||||
|   root["value"] = value; | ||||
| } | ||||
|  | ||||
| template<typename T> | ||||
| static void set_json_icon_state_value(JsonObject &root, EntityBase *obj, const std::string &id, | ||||
|                                       const std::string &state, const T &value, JsonDetail start_config) { | ||||
|   set_json_value(root, obj, id, value, start_config); | ||||
| static void set_json_icon_state_value(JsonObject &root, EntityBase *obj, const char *prefix, const std::string &state, | ||||
|                                       const T &value, JsonDetail start_config) { | ||||
|   set_json_value(root, obj, prefix, value, start_config); | ||||
|   root["state"] = state; | ||||
| } | ||||
|  | ||||
| @@ -442,20 +447,20 @@ std::string WebServer::sensor_json(sensor::Sensor *obj, float value, JsonDetail | ||||
|   json::JsonBuilder builder; | ||||
|   JsonObject root = builder.root(); | ||||
|  | ||||
|   const auto uom_ref = obj->get_unit_of_measurement_ref(); | ||||
|  | ||||
|   // Build JSON directly inline | ||||
|   std::string state; | ||||
|   if (std::isnan(value)) { | ||||
|     state = "NA"; | ||||
|   } else { | ||||
|     state = value_accuracy_to_string(value, obj->get_accuracy_decimals()); | ||||
|     if (!obj->get_unit_of_measurement().empty()) | ||||
|       state += " " + obj->get_unit_of_measurement(); | ||||
|     state = value_accuracy_with_uom_to_string(value, obj->get_accuracy_decimals(), uom_ref); | ||||
|   } | ||||
|   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) { | ||||
|     this->add_sorting_info_(root, obj); | ||||
|     if (!obj->get_unit_of_measurement().empty()) | ||||
|       root["uom"] = obj->get_unit_of_measurement(); | ||||
|     if (!uom_ref.empty()) | ||||
|       root["uom"] = uom_ref; | ||||
|   } | ||||
|  | ||||
|   return builder.serialize(); | ||||
| @@ -494,7 +499,7 @@ std::string WebServer::text_sensor_json(text_sensor::TextSensor *obj, const std: | ||||
|   json::JsonBuilder builder; | ||||
|   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) { | ||||
|     this->add_sorting_info_(root, obj); | ||||
|   } | ||||
| @@ -567,7 +572,7 @@ std::string WebServer::switch_json(switch_::Switch *obj, bool value, JsonDetail | ||||
|   json::JsonBuilder builder; | ||||
|   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) { | ||||
|     root["assumed_state"] = obj->assumed_state(); | ||||
|     this->add_sorting_info_(root, obj); | ||||
| @@ -607,7 +612,7 @@ std::string WebServer::button_json(button::Button *obj, JsonDetail start_config) | ||||
|   json::JsonBuilder builder; | ||||
|   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) { | ||||
|     this->add_sorting_info_(root, obj); | ||||
|   } | ||||
| @@ -647,8 +652,7 @@ std::string WebServer::binary_sensor_json(binary_sensor::BinarySensor *obj, bool | ||||
|   json::JsonBuilder builder; | ||||
|   JsonObject root = builder.root(); | ||||
|  | ||||
|   set_json_icon_state_value(root, obj, "binary_sensor-" + obj->get_object_id(), value ? "ON" : "OFF", value, | ||||
|                             start_config); | ||||
|   set_json_icon_state_value(root, obj, "binary_sensor", value ? "ON" : "OFF", value, start_config); | ||||
|   if (start_config == DETAIL_ALL) { | ||||
|     this->add_sorting_info_(root, obj); | ||||
|   } | ||||
| @@ -717,8 +721,7 @@ std::string WebServer::fan_json(fan::Fan *obj, JsonDetail start_config) { | ||||
|   json::JsonBuilder builder; | ||||
|   JsonObject root = builder.root(); | ||||
|  | ||||
|   set_json_icon_state_value(root, obj, "fan-" + obj->get_object_id(), obj->state ? "ON" : "OFF", obj->state, | ||||
|                             start_config); | ||||
|   set_json_icon_state_value(root, obj, "fan", obj->state ? "ON" : "OFF", obj->state, start_config); | ||||
|   const auto traits = obj->get_traits(); | ||||
|   if (traits.supports_speed()) { | ||||
|     root["speed_level"] = obj->speed; | ||||
| @@ -793,7 +796,7 @@ std::string WebServer::light_json(light::LightState *obj, JsonDetail start_confi | ||||
|   json::JsonBuilder builder; | ||||
|   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"; | ||||
|  | ||||
|   light::LightJSONSchema::dump_json(*obj, root); | ||||
| @@ -881,8 +884,8 @@ std::string WebServer::cover_json(cover::Cover *obj, JsonDetail start_config) { | ||||
|   json::JsonBuilder builder; | ||||
|   JsonObject root = builder.root(); | ||||
|  | ||||
|   set_json_icon_state_value(root, obj, "cover-" + obj->get_object_id(), obj->is_fully_closed() ? "CLOSED" : "OPEN", | ||||
|                             obj->position, start_config); | ||||
|   set_json_icon_state_value(root, obj, "cover", obj->is_fully_closed() ? "CLOSED" : "OPEN", obj->position, | ||||
|                             start_config); | ||||
|   root["current_operation"] = cover::cover_operation_to_str(obj->current_operation); | ||||
|  | ||||
|   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; | ||||
|   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) { | ||||
|     root["min_value"] = | ||||
|         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())); | ||||
|     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(); | ||||
|     if (!obj->traits.get_unit_of_measurement().empty()) | ||||
|       root["uom"] = obj->traits.get_unit_of_measurement(); | ||||
|     if (!uom_ref.empty()) | ||||
|       root["uom"] = uom_ref; | ||||
|     this->add_sorting_info_(root, obj); | ||||
|   } | ||||
|   if (std::isnan(value)) { | ||||
| @@ -956,10 +961,8 @@ std::string WebServer::number_json(number::Number *obj, float value, JsonDetail | ||||
|     root["state"] = "NA"; | ||||
|   } else { | ||||
|     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())); | ||||
|     if (!obj->traits.get_unit_of_measurement().empty()) | ||||
|       state += " " + obj->traits.get_unit_of_measurement(); | ||||
|     root["state"] = state; | ||||
|     root["state"] = | ||||
|         value_accuracy_with_uom_to_string(value, step_to_accuracy_decimals(obj->traits.get_step()), uom_ref); | ||||
|   } | ||||
|  | ||||
|   return builder.serialize(); | ||||
| @@ -1013,7 +1016,7 @@ std::string WebServer::date_json(datetime::DateEntity *obj, JsonDetail start_con | ||||
|   json::JsonBuilder builder; | ||||
|   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); | ||||
|   root["value"] = value; | ||||
|   root["state"] = value; | ||||
| @@ -1071,7 +1074,7 @@ std::string WebServer::time_json(datetime::TimeEntity *obj, JsonDetail start_con | ||||
|   json::JsonBuilder builder; | ||||
|   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); | ||||
|   root["value"] = value; | ||||
|   root["state"] = value; | ||||
| @@ -1129,7 +1132,7 @@ std::string WebServer::datetime_json(datetime::DateTimeEntity *obj, JsonDetail s | ||||
|   json::JsonBuilder builder; | ||||
|   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 = | ||||
|       str_sprintf("%d-%02d-%02d %02d:%02d:%02d", obj->year, obj->month, obj->day, obj->hour, obj->minute, obj->second); | ||||
|   root["value"] = value; | ||||
| @@ -1184,7 +1187,7 @@ std::string WebServer::text_json(text::Text *obj, const std::string &value, Json | ||||
|   json::JsonBuilder builder; | ||||
|   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["max_length"] = obj->traits.get_max_length(); | ||||
|   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; | ||||
|   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) { | ||||
|     JsonArray opt = root["option"].to<JsonArray>(); | ||||
|     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 | ||||
|   json::JsonBuilder builder; | ||||
|   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(); | ||||
|   int8_t target_accuracy = traits.get_target_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; | ||||
|   JsonObject root = builder.root(); | ||||
|  | ||||
|   set_json_icon_state_value(root, obj, "lock-" + obj->get_object_id(), lock::lock_state_to_string(value), value, | ||||
|                             start_config); | ||||
|   set_json_icon_state_value(root, obj, "lock", lock::lock_state_to_string(value), value, start_config); | ||||
|   if (start_config == DETAIL_ALL) { | ||||
|     this->add_sorting_info_(root, obj); | ||||
|   } | ||||
| @@ -1546,8 +1548,8 @@ std::string WebServer::valve_json(valve::Valve *obj, JsonDetail start_config) { | ||||
|   json::JsonBuilder builder; | ||||
|   JsonObject root = builder.root(); | ||||
|  | ||||
|   set_json_icon_state_value(root, obj, "valve-" + obj->get_object_id(), obj->is_fully_closed() ? "CLOSED" : "OPEN", | ||||
|                             obj->position, start_config); | ||||
|   set_json_icon_state_value(root, obj, "valve", obj->is_fully_closed() ? "CLOSED" : "OPEN", obj->position, | ||||
|                             start_config); | ||||
|   root["current_operation"] = valve::valve_operation_to_str(obj->current_operation); | ||||
|  | ||||
|   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(); | ||||
|  | ||||
|   char buf[16]; | ||||
|   set_json_icon_state_value(root, obj, "alarm-control-panel-" + obj->get_object_id(), | ||||
|                             PSTR_LOCAL(alarm_control_panel_state_to_string(value)), value, start_config); | ||||
|   set_json_icon_state_value(root, obj, "alarm-control-panel", PSTR_LOCAL(alarm_control_panel_state_to_string(value)), | ||||
|                             value, start_config); | ||||
|   if (start_config == DETAIL_ALL) { | ||||
|     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; | ||||
|   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()) { | ||||
|     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()) { | ||||
|       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); | ||||
|   } | ||||
|  | ||||
| @@ -1748,7 +1750,7 @@ std::string WebServer::update_json(update::UpdateEntity *obj, JsonDetail start_c | ||||
|   json::JsonBuilder builder; | ||||
|   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["state"] = update_state_to_string(obj->state); | ||||
|   if (start_config == DETAIL_ALL) { | ||||
|   | ||||
| @@ -3,6 +3,7 @@ | ||||
| #include "esphome/core/defines.h" | ||||
| #include "esphome/core/hal.h" | ||||
| #include "esphome/core/log.h" | ||||
| #include "esphome/core/string_ref.h" | ||||
|  | ||||
| #include <strings.h> | ||||
| #include <algorithm> | ||||
| @@ -348,17 +349,32 @@ ParseOnOffState parse_on_off(const char *str, const char *on, const char *off) { | ||||
|   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) { | ||||
|     auto multiplier = powf(10.0f, accuracy_decimals); | ||||
|     value = roundf(value * multiplier) / multiplier; | ||||
|     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. | ||||
|   snprintf(tmp, sizeof(tmp), "%.*f", accuracy_decimals, value); | ||||
|   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) { | ||||
|   // use printf %g to find number of digits based on temperature step | ||||
|   char buf[32]; | ||||
|   | ||||
| @@ -14,6 +14,10 @@ | ||||
|  | ||||
| #include "esphome/core/optional.h" | ||||
|  | ||||
| namespace esphome { | ||||
| class StringRef; | ||||
| } | ||||
|  | ||||
| #ifdef USE_ESP8266 | ||||
| #include <Esp.h> | ||||
| #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. | ||||
| 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. | ||||
| int8_t step_to_accuracy_decimals(float step); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user