mirror of
https://github.com/emsesp/EMS-ESP32.git
synced 2026-06-13 19:36:26 +03:00
minor heap optimizations
This commit is contained in:
@@ -123,6 +123,11 @@ class Command {
|
|||||||
cmdfunctions_.reserve(num);
|
cmdfunctions_.reserve(num);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// release any reserved-but-unused capacity once commands have settled
|
||||||
|
static void compact() {
|
||||||
|
cmdfunctions_.shrink_to_fit();
|
||||||
|
}
|
||||||
|
|
||||||
static void show_all(uuid::console::Shell & shell);
|
static void show_all(uuid::console::Shell & shell);
|
||||||
static Command::CmdFunction * find_command(const uint8_t device_type, const uint8_t device_id, const char * cmd, const uint8_t flag);
|
static Command::CmdFunction * find_command(const uint8_t device_type, const uint8_t device_id, const char * cmd, const uint8_t flag);
|
||||||
static std::string tagged_cmd(const std::string & cmd, const uint8_t flag);
|
static std::string tagged_cmd(const std::string & cmd, const uint8_t flag);
|
||||||
|
|||||||
@@ -559,6 +559,7 @@ void EMSdevice::show_mqtt_handlers(uuid::console::Shell & shell) const {
|
|||||||
// register a callback function for a specific telegram type
|
// register a callback function for a specific telegram type
|
||||||
void EMSdevice::register_telegram_type(const uint16_t telegram_type_id, const char * telegram_type_name, bool fetch, const process_function_p f, uint8_t length) {
|
void EMSdevice::register_telegram_type(const uint16_t telegram_type_id, const char * telegram_type_name, bool fetch, const process_function_p f, uint8_t length) {
|
||||||
telegram_functions_.emplace_back(telegram_type_id, telegram_type_name, fetch, false, length, f);
|
telegram_functions_.emplace_back(telegram_type_id, telegram_type_name, fetch, false, length, f);
|
||||||
|
EMSESP::mark_entities_changed();
|
||||||
}
|
}
|
||||||
|
|
||||||
// add to device value library, also know now as a "device entity"
|
// add to device value library, also know now as a "device entity"
|
||||||
@@ -675,6 +676,7 @@ void EMSdevice::add_device_value(int8_t tag, // to b
|
|||||||
// add the device entity
|
// add the device entity
|
||||||
devicevalues_.emplace_back(
|
devicevalues_.emplace_back(
|
||||||
device_type_, tag, value_p, type, options, options_single, numeric_operator, short_name, fullname, custom_fullname, uom, has_cmd, min, max, state);
|
device_type_, tag, value_p, type, options, options_single, numeric_operator, short_name, fullname, custom_fullname, uom, has_cmd, min, max, state);
|
||||||
|
EMSESP::mark_entities_changed();
|
||||||
|
|
||||||
// add a new command if it has a function attached
|
// add a new command if it has a function attached
|
||||||
if (has_cmd) {
|
if (has_cmd) {
|
||||||
@@ -1281,9 +1283,9 @@ void EMSdevice::setCustomizationEntity(const std::string & entity_id) {
|
|||||||
|
|
||||||
// set the custom name if it has one, or clear it
|
// set the custom name if it has one, or clear it
|
||||||
if (has_custom_name) {
|
if (has_custom_name) {
|
||||||
dv.custom_fullname = entity_id.substr(custom_name_pos + 1);
|
dv.set_custom_fullname(entity_id.substr(custom_name_pos + 1));
|
||||||
} else {
|
} else {
|
||||||
dv.custom_fullname = "";
|
dv.set_custom_fullname("");
|
||||||
}
|
}
|
||||||
|
|
||||||
auto min = dv.min;
|
auto min = dv.min;
|
||||||
@@ -1322,11 +1324,11 @@ void EMSdevice::getCustomizationEntities(std::vector<std::string> & entity_ids)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!is_set && (mask || !dv.custom_fullname.empty())) {
|
if (!is_set && (mask || dv.has_custom_fullname())) {
|
||||||
if (dv.custom_fullname.empty()) {
|
if (!dv.has_custom_fullname()) {
|
||||||
entity_ids.push_back(Helpers::hextoa(mask, false) + entity_name);
|
entity_ids.push_back(Helpers::hextoa(mask, false) + entity_name);
|
||||||
} else {
|
} else {
|
||||||
entity_ids.push_back(Helpers::hextoa(mask, false) + entity_name + "|" + dv.custom_fullname);
|
entity_ids.push_back(Helpers::hextoa(mask, false) + entity_name + "|" + dv.custom_fullname());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -550,6 +550,12 @@ class EMSdevice {
|
|||||||
telegram_functions_.reserve(elements);
|
telegram_functions_.reserve(elements);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// release any reserved-but-unused capacity once the entity/telegram set has settled
|
||||||
|
void compact() {
|
||||||
|
devicevalues_.shrink_to_fit();
|
||||||
|
telegram_functions_.shrink_to_fit();
|
||||||
|
}
|
||||||
|
|
||||||
#if defined(EMSESP_STANDALONE)
|
#if defined(EMSESP_STANDALONE)
|
||||||
struct TelegramFunctionDump {
|
struct TelegramFunctionDump {
|
||||||
uint16_t type_id_;
|
uint16_t type_id_;
|
||||||
|
|||||||
@@ -53,8 +53,7 @@ DeviceValue::DeviceValue(uint8_t device_type,
|
|||||||
, uom(uom)
|
, uom(uom)
|
||||||
, has_cmd(has_cmd)
|
, has_cmd(has_cmd)
|
||||||
, min(min)
|
, min(min)
|
||||||
, max(max)
|
, max(max) {
|
||||||
, custom_fullname(custom_fullname) {
|
|
||||||
// calculate #options in options list
|
// calculate #options in options list
|
||||||
if (options_single) {
|
if (options_single) {
|
||||||
options_size = 1;
|
options_size = 1;
|
||||||
@@ -62,7 +61,12 @@ DeviceValue::DeviceValue(uint8_t device_type,
|
|||||||
options_size = Helpers::count_items(options);
|
options_size = Helpers::count_items(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
// set the min/max
|
// store the custom name on the heap, but only if one was actually provided
|
||||||
|
if (!custom_fullname.empty()) {
|
||||||
|
custom_fullname_ = std::make_unique<std::string>(custom_fullname);
|
||||||
|
}
|
||||||
|
|
||||||
|
// set the min/max (reads back the custom name set above)
|
||||||
set_custom_minmax();
|
set_custom_minmax();
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -347,11 +351,12 @@ bool DeviceValue::get_min_max(int16_t & dv_set_min, uint32_t & dv_set_max) {
|
|||||||
|
|
||||||
// extract custom min from custom_fullname
|
// extract custom min from custom_fullname
|
||||||
bool DeviceValue::get_custom_min(int16_t & val) {
|
bool DeviceValue::get_custom_min(int16_t & val) {
|
||||||
auto min_pos = custom_fullname.find('>');
|
const auto & cf = custom_fullname();
|
||||||
|
auto min_pos = cf.find('>');
|
||||||
bool has_min = (min_pos != std::string::npos);
|
bool has_min = (min_pos != std::string::npos);
|
||||||
uint8_t fahrenheit = !EMSESP::system_.fahrenheit() ? 0 : (uom == DeviceValueUOM::DEGREES) ? 2 : (uom == DeviceValueUOM::DEGREES_R) ? 1 : 0;
|
uint8_t fahrenheit = !EMSESP::system_.fahrenheit() ? 0 : (uom == DeviceValueUOM::DEGREES) ? 2 : (uom == DeviceValueUOM::DEGREES_R) ? 1 : 0;
|
||||||
if (has_min) {
|
if (has_min) {
|
||||||
int32_t v = Helpers::atoint(custom_fullname.substr(min_pos + 1).c_str());
|
int32_t v = Helpers::atoint(cf.substr(min_pos + 1).c_str());
|
||||||
if (fahrenheit) {
|
if (fahrenheit) {
|
||||||
v = (v - (32 * (fahrenheit - 1))) / 1.8; // reset to °C
|
v = (v - (32 * (fahrenheit - 1))) / 1.8; // reset to °C
|
||||||
}
|
}
|
||||||
@@ -365,11 +370,12 @@ bool DeviceValue::get_custom_min(int16_t & val) {
|
|||||||
|
|
||||||
// extract custom max from custom_fullname
|
// extract custom max from custom_fullname
|
||||||
bool DeviceValue::get_custom_max(uint32_t & val) {
|
bool DeviceValue::get_custom_max(uint32_t & val) {
|
||||||
auto max_pos = custom_fullname.find('<');
|
const auto & cf = custom_fullname();
|
||||||
|
auto max_pos = cf.find('<');
|
||||||
bool has_max = (max_pos != std::string::npos);
|
bool has_max = (max_pos != std::string::npos);
|
||||||
uint8_t fahrenheit = !EMSESP::system_.fahrenheit() ? 0 : (uom == DeviceValueUOM::DEGREES) ? 2 : (uom == DeviceValueUOM::DEGREES_R) ? 1 : 0;
|
uint8_t fahrenheit = !EMSESP::system_.fahrenheit() ? 0 : (uom == DeviceValueUOM::DEGREES) ? 2 : (uom == DeviceValueUOM::DEGREES_R) ? 1 : 0;
|
||||||
if (has_max) {
|
if (has_max) {
|
||||||
int32_t v = Helpers::atoint(custom_fullname.substr(max_pos + 1).c_str());
|
int32_t v = Helpers::atoint(cf.substr(max_pos + 1).c_str());
|
||||||
if (fahrenheit) {
|
if (fahrenheit) {
|
||||||
v = (v - (32 * (fahrenheit - 1))) / 1.8; // reset to °C
|
v = (v - (32 * (fahrenheit - 1))) / 1.8; // reset to °C
|
||||||
}
|
}
|
||||||
@@ -387,14 +393,32 @@ void DeviceValue::set_custom_minmax() {
|
|||||||
get_custom_max(max);
|
get_custom_max(max);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// raw stored custom name (empty string if none was set)
|
||||||
|
const std::string & DeviceValue::custom_fullname() const {
|
||||||
|
static const std::string empty_string;
|
||||||
|
return custom_fullname_ ? *custom_fullname_ : empty_string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// set or clear the custom name, only allocating heap when there's actually a name
|
||||||
|
void DeviceValue::set_custom_fullname(const std::string & name) {
|
||||||
|
if (name.empty()) {
|
||||||
|
custom_fullname_.reset();
|
||||||
|
} else if (custom_fullname_) {
|
||||||
|
*custom_fullname_ = name;
|
||||||
|
} else {
|
||||||
|
custom_fullname_ = std::make_unique<std::string>(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
std::string DeviceValue::get_custom_fullname() const {
|
std::string DeviceValue::get_custom_fullname() const {
|
||||||
auto min_pos = custom_fullname.find('>');
|
const auto & cf = custom_fullname();
|
||||||
auto max_pos = custom_fullname.find('<');
|
auto min_pos = cf.find('>');
|
||||||
|
auto max_pos = cf.find('<');
|
||||||
auto minmax_pos = min_pos < max_pos ? min_pos : max_pos;
|
auto minmax_pos = min_pos < max_pos ? min_pos : max_pos;
|
||||||
if (minmax_pos != std::string::npos) {
|
if (minmax_pos != std::string::npos) {
|
||||||
return custom_fullname.substr(0, minmax_pos);
|
return cf.substr(0, minmax_pos);
|
||||||
}
|
}
|
||||||
return custom_fullname;
|
return cf;
|
||||||
}
|
}
|
||||||
|
|
||||||
// returns the translated fullname or the custom fullname (if provided)
|
// returns the translated fullname or the custom fullname (if provided)
|
||||||
|
|||||||
@@ -23,6 +23,8 @@
|
|||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include <ArduinoJson.h>
|
#include <ArduinoJson.h>
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
#include "helpers.h" // for conversions
|
#include "helpers.h" // for conversions
|
||||||
#include "default_settings.h" // for enum types
|
#include "default_settings.h" // for enum types
|
||||||
|
|
||||||
@@ -188,8 +190,6 @@ class DeviceValue {
|
|||||||
// wider numeric range fields
|
// wider numeric range fields
|
||||||
int16_t min; // min range
|
int16_t min; // min range
|
||||||
uint32_t max; // max range
|
uint32_t max; // max range
|
||||||
// largest member last (cold path: only read during customization save/load and web display)
|
|
||||||
std::string custom_fullname; // optional, from customization
|
|
||||||
|
|
||||||
DeviceValue(uint8_t device_type, // EMSdevice::DeviceType
|
DeviceValue(uint8_t device_type, // EMSdevice::DeviceType
|
||||||
int8_t tag, // DeviceValueTAG::*
|
int8_t tag, // DeviceValueTAG::*
|
||||||
@@ -219,6 +219,13 @@ class DeviceValue {
|
|||||||
std::string get_fullname() const;
|
std::string get_fullname() const;
|
||||||
static std::string get_name(const std::string & entity);
|
static std::string get_name(const std::string & entity);
|
||||||
|
|
||||||
|
// raw stored custom name (including any >min<max suffix), empty if none. Stored on heap only when set.
|
||||||
|
const std::string & custom_fullname() const;
|
||||||
|
void set_custom_fullname(const std::string & name);
|
||||||
|
bool has_custom_fullname() const {
|
||||||
|
return (bool)custom_fullname_;
|
||||||
|
}
|
||||||
|
|
||||||
// dv state flags
|
// dv state flags
|
||||||
void add_state(uint8_t s) {
|
void add_state(uint8_t s) {
|
||||||
state |= s;
|
state |= s;
|
||||||
@@ -237,6 +244,11 @@ class DeviceValue {
|
|||||||
static const char * const * DeviceValueTAG_s[];
|
static const char * const * DeviceValueTAG_s[];
|
||||||
static const char * const DeviceValueTAG_mqtt[];
|
static const char * const DeviceValueTAG_mqtt[];
|
||||||
static uint8_t NUM_TAGS; // # tags
|
static uint8_t NUM_TAGS; // # tags
|
||||||
|
|
||||||
|
private:
|
||||||
|
// optional custom name from customization. Allocated on heap only when actually set,
|
||||||
|
// so unnamed entities (the vast majority) don't pay for an inline std::string.
|
||||||
|
std::unique_ptr<std::string> custom_fullname_;
|
||||||
};
|
};
|
||||||
|
|
||||||
}; // namespace emsesp
|
}; // namespace emsesp
|
||||||
|
|||||||
@@ -48,6 +48,9 @@ uint16_t EMSESP::wait_validate_ = 0;
|
|||||||
bool EMSESP::wait_km_ = false;
|
bool EMSESP::wait_km_ = false;
|
||||||
uint32_t EMSESP::last_fetch_ = 0;
|
uint32_t EMSESP::last_fetch_ = 0;
|
||||||
|
|
||||||
|
uint32_t EMSESP::last_entity_change_ = 0;
|
||||||
|
bool EMSESP::entity_compaction_pending_ = false;
|
||||||
|
|
||||||
AsyncWebServer webServer(80);
|
AsyncWebServer webServer(80);
|
||||||
|
|
||||||
#if defined(EMSESP_STANDALONE)
|
#if defined(EMSESP_STANDALONE)
|
||||||
@@ -176,6 +179,37 @@ void EMSESP::clear_all_devices() {
|
|||||||
// emsdevices.clear(); // remove entries, but doesn't delete actual devices
|
// emsdevices.clear(); // remove entries, but doesn't delete actual devices
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// called from EMSdevice/Command whenever an entity or telegram handler is registered.
|
||||||
|
// Devices reserve their value/telegram vectors generously (to avoid realloc storms while
|
||||||
|
// heating circuits etc. are discovered incrementally), so once registration settles we
|
||||||
|
// reclaim the unused capacity - see compact_entities_if_stable().
|
||||||
|
void EMSESP::mark_entities_changed() {
|
||||||
|
last_entity_change_ = uuid::get_uptime();
|
||||||
|
entity_compaction_pending_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// once the entity/telegram set has been stable for ENTITY_COMPACT_DELAY, shrink the
|
||||||
|
// per-device and command vectors to their actual size. Re-arms automatically if a new
|
||||||
|
// device/circuit appears later (which just costs a single realloc).
|
||||||
|
void EMSESP::compact_entities_if_stable() {
|
||||||
|
if (!entity_compaction_pending_) {
|
||||||
|
return; // nothing to do (cheap early-out on the hot path)
|
||||||
|
}
|
||||||
|
if ((uuid::get_uptime() - last_entity_change_) < ENTITY_COMPACT_DELAY) {
|
||||||
|
return; // still settling
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto & emsdevice : emsdevices) {
|
||||||
|
if (emsdevice) {
|
||||||
|
emsdevice->compact();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Command::compact();
|
||||||
|
|
||||||
|
entity_compaction_pending_ = false;
|
||||||
|
LOG_DEBUG("Reclaimed unused entity vector capacity");
|
||||||
|
}
|
||||||
|
|
||||||
// return total number of devices excluding the Controller
|
// return total number of devices excluding the Controller
|
||||||
uint8_t EMSESP::count_devices() {
|
uint8_t EMSESP::count_devices() {
|
||||||
if (emsdevices.empty()) {
|
if (emsdevices.empty()) {
|
||||||
@@ -1860,6 +1894,7 @@ void EMSESP::loop() {
|
|||||||
webModulesService.loop(); // loop through the external library modules
|
webModulesService.loop(); // loop through the external library modules
|
||||||
webSchedulerService.loop(); // scheduler timing logic; command execution is offloaded to WebCommandService's worker task
|
webSchedulerService.loop(); // scheduler timing logic; command execution is offloaded to WebCommandService's worker task
|
||||||
scheduled_fetch_values(); // force a query on the EMS devices to fetch latest data at a set interval (1 min)
|
scheduled_fetch_values(); // force a query on the EMS devices to fetch latest data at a set interval (1 min)
|
||||||
|
compact_entities_if_stable(); // reclaim over-reserved entity vector capacity once device discovery settles
|
||||||
}
|
}
|
||||||
// check for GPIO Errors - this is called once when booting
|
// check for GPIO Errors - this is called once when booting
|
||||||
if (EMSESP::system_.systemStatus() == SYSTEM_STATUS::SYSTEM_STATUS_INVALID_GPIO) {
|
if (EMSESP::system_.systemStatus() == SYSTEM_STATUS::SYSTEM_STATUS_INVALID_GPIO) {
|
||||||
|
|||||||
@@ -239,6 +239,10 @@ class EMSESP {
|
|||||||
static void scan_devices();
|
static void scan_devices();
|
||||||
static void clear_all_devices();
|
static void clear_all_devices();
|
||||||
|
|
||||||
|
// called whenever a device entity or telegram handler is registered, so we can
|
||||||
|
// later reclaim the (deliberately generous) reserved vector capacity once stable
|
||||||
|
static void mark_entities_changed();
|
||||||
|
|
||||||
static std::vector<std::unique_ptr<EMSdevice>, AllocatorPSRAM<std::unique_ptr<EMSdevice>>> emsdevices;
|
static std::vector<std::unique_ptr<EMSdevice>, AllocatorPSRAM<std::unique_ptr<EMSdevice>>> emsdevices;
|
||||||
// services
|
// services
|
||||||
static Mqtt mqtt_;
|
static Mqtt mqtt_;
|
||||||
@@ -275,6 +279,9 @@ class EMSESP {
|
|||||||
static void publish_response(const std::shared_ptr<const Telegram> & telegram);
|
static void publish_response(const std::shared_ptr<const Telegram> & telegram);
|
||||||
static void publish_all_loop();
|
static void publish_all_loop();
|
||||||
|
|
||||||
|
// one-time compaction of per-device/command vectors once registration has been stable
|
||||||
|
static void compact_entities_if_stable();
|
||||||
|
|
||||||
void shell_prompt();
|
void shell_prompt();
|
||||||
void start_serial_console();
|
void start_serial_console();
|
||||||
|
|
||||||
@@ -303,6 +310,11 @@ class EMSESP {
|
|||||||
static bool wait_km_;
|
static bool wait_km_;
|
||||||
static uint32_t last_fetch_;
|
static uint32_t last_fetch_;
|
||||||
|
|
||||||
|
// entity/telegram registration tracking, used to trigger a one-time vector compaction
|
||||||
|
static constexpr uint32_t ENTITY_COMPACT_DELAY = 60000; // ms of stability before compacting
|
||||||
|
static uint32_t last_entity_change_; // uptime (ms) of last registration
|
||||||
|
static bool entity_compaction_pending_; // true while a compaction is owed
|
||||||
|
|
||||||
// UUID stuff
|
// UUID stuff
|
||||||
static constexpr auto & serial_console_ = Serial;
|
static constexpr auto & serial_console_ = Serial;
|
||||||
static constexpr unsigned long SERIAL_CONSOLE_BAUD_RATE = 115200;
|
static constexpr unsigned long SERIAL_CONSOLE_BAUD_RATE = 115200;
|
||||||
|
|||||||
@@ -492,7 +492,7 @@ void WebCustomizationService::load_test_data() {
|
|||||||
for (auto & dv : emsdevice->devicevalues_) {
|
for (auto & dv : emsdevice->devicevalues_) {
|
||||||
if (strcmp(dv.short_name, "heatingactive") == 0) {
|
if (strcmp(dv.short_name, "heatingactive") == 0) {
|
||||||
dv.state = DeviceValueState::DV_FAVORITE; // set as favorite
|
dv.state = DeviceValueState::DV_FAVORITE; // set as favorite
|
||||||
dv.custom_fullname = "is my heating on?";
|
dv.set_custom_fullname("is my heating on?");
|
||||||
} else if (strcmp(dv.short_name, "tapwateractive") == 0) {
|
} else if (strcmp(dv.short_name, "tapwateractive") == 0) {
|
||||||
dv.state = DeviceValueState::DV_FAVORITE; // set as favorite
|
dv.state = DeviceValueState::DV_FAVORITE; // set as favorite
|
||||||
} else if (strcmp(dv.short_name, "selflowtemp") == 0) {
|
} else if (strcmp(dv.short_name, "selflowtemp") == 0) {
|
||||||
|
|||||||
Reference in New Issue
Block a user