diff --git a/code/espurna/alexa.cpp b/code/espurna/alexa.cpp index e59440ab..073673e7 100644 --- a/code/espurna/alexa.cpp +++ b/code/espurna/alexa.cpp @@ -108,8 +108,8 @@ void _alexaWebSocketOnVisible(JsonObject& root) { wsPayloadModule(root, PSTR("alexa")); } -bool _alexaWebSocketOnKeyCheck(const char * key, JsonVariant&) { - return (strncmp_P(key, PSTR("alexa"), 5) == 0); +bool _alexaWebSocketOnKeyCheck(espurna::StringView key, const JsonVariant&) { + return espurna::settings::query::samePrefix(key, STRING_VIEW("alexa")); } void _alexaWebSocketOnConnected(JsonObject& root) { diff --git a/code/espurna/api_common.cpp b/code/espurna/api_common.cpp index 9f586f82..57af66ab 100644 --- a/code/espurna/api_common.cpp +++ b/code/espurna/api_common.cpp @@ -20,8 +20,8 @@ Copyright (C) 2020 by Maxim Prokhorov namespace { -bool _apiWebSocketOnKeyCheck(const char * key, JsonVariant&) { - return (strncmp_P(key, PSTR("api"), 3) == 0); +bool _apiWebSocketOnKeyCheck(espurna::StringView key, const JsonVariant&) { + return espurna::settings::query::samePrefix(key, STRING_VIEW("api")); } void _apiWebSocketOnVisible(JsonObject& root) { diff --git a/code/espurna/button.cpp b/code/espurna/button.cpp index d0e2bf68..0af69449 100644 --- a/code/espurna/button.cpp +++ b/code/espurna/button.cpp @@ -567,12 +567,12 @@ template T indexedThenGlobal(const String& prefix, size_t index, T defaultValue) { const auto key = espurna::settings::Key{prefix, index}; - auto indexed = espurna::settings::internal::get(key.value()); + const auto indexed = espurna::settings::get(key.value()); if (indexed) { return espurna::settings::internal::convert(indexed.ref()); } - auto global = espurna::settings::internal::get(prefix); + const auto global = espurna::settings::get(prefix); if (global) { return espurna::settings::internal::convert(indexed.ref()); } @@ -749,8 +749,7 @@ static constexpr espurna::settings::query::IndexedSetting IndexedSettings[] PROG }; bool checkSamePrefix(StringView key) { - alignas(4) static constexpr char Prefix[] PROGMEM = "btn"; - return espurna::settings::query::samePrefix(key, Prefix); + return espurna::settings::query::samePrefix(key, STRING_VIEW("btn")); } String findValueFrom(StringView key) { @@ -940,8 +939,8 @@ void _buttonWebSocketOnConnected(JsonObject& root) { } } -bool _buttonWebSocketOnKeyCheck(const char * key, JsonVariant&) { - return (strncmp_P(key, PSTR("btn"), 3) == 0); +bool _buttonWebSocketOnKeyCheck(espurna::StringView key, const JsonVariant&) { + return espurna::button::settings::query::checkSamePrefix(key); } } // namespace diff --git a/code/espurna/curtain_kingart.cpp b/code/espurna/curtain_kingart.cpp index 06edb2a6..2dc88f96 100644 --- a/code/espurna/curtain_kingart.cpp +++ b/code/espurna/curtain_kingart.cpp @@ -394,9 +394,8 @@ void _curtainWebSocketOnConnected(JsonObject& root) { } //------------------------------------------------------------------------------ -bool _curtainWebSocketOnKeyCheck(const char * key, JsonVariant& value) { - if (strncmp_P(key, PSTR("curtain"), __builtin_strlen("curtain")) == 0) return true; - return false; +bool _curtainWebSocketOnKeyCheck(const char * key, const JsonVariant& value) { + return espurna::settings::query::samePrefix(key, STRING_VIEW("curtain")); } //------------------------------------------------------------------------------ diff --git a/code/espurna/domoticz.cpp b/code/espurna/domoticz.cpp index 942a868b..e0baa16c 100644 --- a/code/espurna/domoticz.cpp +++ b/code/espurna/domoticz.cpp @@ -434,8 +434,8 @@ namespace { alignas(4) static constexpr char Prefix[] PROGMEM = "dcz"; -bool onKeyCheck(const char* key, JsonVariant& value) { - return (strncmp_P(key, Prefix, 3) == 0); +bool onKeyCheck(const char* key, const JsonVariant& value) { + return espurna::settings::query::samePrefix(key, Prefix); } void onVisible(JsonObject& root) { diff --git a/code/espurna/garland.cpp b/code/espurna/garland.cpp index 59b79490..0c6c9b55 100644 --- a/code/espurna/garland.cpp +++ b/code/espurna/garland.cpp @@ -65,29 +65,29 @@ namespace { #include "garland/palette.h" #include "garland/scene.h" -const char* const NAME_GARLAND_ENABLED = "garlandEnabled"; -const char* const NAME_GARLAND_BRIGHTNESS = "garlandBrightness"; -const char* const NAME_GARLAND_SPEED = "garlandSpeed"; +alignas(4) static constexpr char NAME_GARLAND_ENABLED[] = "garlandEnabled"; +alignas(4) static constexpr char NAME_GARLAND_BRIGHTNESS[] = "garlandBrightness"; +alignas(4) static constexpr char NAME_GARLAND_SPEED[] = "garlandSpeed"; -const char* const NAME_GARLAND_SWITCH = "garland_switch"; -const char* const NAME_GARLAND_SET_BRIGHTNESS = "garland_set_brightness"; -const char* const NAME_GARLAND_SET_SPEED = "garland_set_speed"; -const char* const NAME_GARLAND_SET_DEFAULT = "garland_set_default"; +alignas(4) static constexpr char NAME_GARLAND_SWITCH[] = "garland_switch"; +alignas(4) static constexpr char NAME_GARLAND_SET_BRIGHTNESS[] = "garland_set_brightness"; +alignas(4) static constexpr char NAME_GARLAND_SET_SPEED[] = "garland_set_speed"; +alignas(4) static constexpr char NAME_GARLAND_SET_DEFAULT[] = "garland_set_default"; -const char* const MQTT_TOPIC_GARLAND = "garland"; +alignas(4) static constexpr char MQTT_TOPIC_GARLAND[] = "garland"; -const char* const MQTT_PAYLOAD_COMMAND = "command"; -const char* const MQTT_PAYLOAD_ENABLE = "enable"; -const char* const MQTT_PAYLOAD_BRIGHTNESS = "brightness"; -const char* const MQTT_PAYLOAD_ANIM_SPEED = "speed"; -const char* const MQTT_PAYLOAD_ANIMATION = "animation"; -const char* const MQTT_PAYLOAD_PALETTE = "palette"; -const char* const MQTT_PAYLOAD_DURATION = "duration"; +alignas(4) static constexpr char MQTT_PAYLOAD_COMMAND[] = "command"; +alignas(4) static constexpr char MQTT_PAYLOAD_ENABLE[] = "enable"; +alignas(4) static constexpr char MQTT_PAYLOAD_BRIGHTNESS[] = "brightness"; +alignas(4) static constexpr char MQTT_PAYLOAD_ANIM_SPEED[] = "speed"; +alignas(4) static constexpr char MQTT_PAYLOAD_ANIMATION[] = "animation"; +alignas(4) static constexpr char MQTT_PAYLOAD_PALETTE[] = "palette"; +alignas(4) static constexpr char MQTT_PAYLOAD_DURATION[] = "duration"; -const char* const MQTT_COMMAND_IMMEDIATE = "immediate"; -const char* const MQTT_COMMAND_RESET = "reset"; // reset queue -const char* const MQTT_COMMAND_QUEUE = "queue"; // enqueue command payload -const char* const MQTT_COMMAND_SEQUENCE = "sequence"; // place command to sequence +alignas(4) static constexpr char MQTT_COMMAND_IMMEDIATE[] = "immediate"; +alignas(4) static constexpr char MQTT_COMMAND_RESET[] = "reset"; // reset queue +alignas(4) static constexpr char MQTT_COMMAND_QUEUE[] = "queue"; // enqueue command payload +alignas(4) static constexpr char MQTT_COMMAND_SEQUENCE[] = "sequence"; // place command to sequence #define EFFECT_UPDATE_INTERVAL_MIN 7000 // 5 sec #define EFFECT_UPDATE_INTERVAL_MAX 12000 // 10 sec @@ -219,11 +219,10 @@ void _garlandWebSocketOnConnected(JsonObject& root) { } //------------------------------------------------------------------------------ -bool _garlandWebSocketOnKeyCheck(const char* key, JsonVariant& value) { - if (strncmp(key, NAME_GARLAND_ENABLED, strlen(NAME_GARLAND_ENABLED)) == 0) return true; - if (strncmp(key, NAME_GARLAND_BRIGHTNESS, strlen(NAME_GARLAND_BRIGHTNESS)) == 0) return true; - if (strncmp(key, NAME_GARLAND_SPEED, strlen(NAME_GARLAND_SPEED)) == 0) return true; - return false; +bool _garlandWebSocketOnKeyCheck(espurna::StringView key, const JsonVariant&) { + return espurna::settings::query::samePrefix(key, NAME_GARLAND_ENABLED) + || espurna::settings::query::samePrefix(key, NAME_GARLAND_BRIGHTNESS) + || espurna::settings::query::samePrefix(key, NAME_GARLAND_SPEED); } //------------------------------------------------------------------------------ diff --git a/code/espurna/homeassistant.cpp b/code/espurna/homeassistant.cpp index f672bbef..e2c57d04 100644 --- a/code/espurna/homeassistant.cpp +++ b/code/espurna/homeassistant.cpp @@ -984,8 +984,8 @@ void onConnected(JsonObject& root) { root["haRetain"] = settings::retain(); } -bool onKeyCheck(const char* key, JsonVariant& value) { - return (strncmp(key, "ha", 2) == 0); +bool onKeyCheck(espurna::StringView key, const JsonVariant& value) { + return espurna::settings::query::samePrefix(key, STRING_VIEW("ha")); } #endif diff --git a/code/espurna/influxdb.cpp b/code/espurna/influxdb.cpp index c07581b6..bc4881db 100644 --- a/code/espurna/influxdb.cpp +++ b/code/espurna/influxdb.cpp @@ -132,8 +132,8 @@ void _idbInitClient() { // ----------------------------------------------------------------------------- -bool _idbWebSocketOnKeyCheck(const char * key, JsonVariant& value) { - return (strncmp_P(key, PSTR("idb"), 3) == 0); +bool _idbWebSocketOnKeyCheck(espurna::StringView key, const JsonVariant& value) { + return espurna::settings::query::samePrefix(key, STRING_VIEW("idb")); } void _idbWebSocketOnVisible(JsonObject& root) { diff --git a/code/espurna/ir.cpp b/code/espurna/ir.cpp index 18b45f69..a09ee23a 100644 --- a/code/espurna/ir.cpp +++ b/code/espurna/ir.cpp @@ -1560,7 +1560,7 @@ void process(rx::DecodeResult& result) { key += F("irCmd"); key += value; - auto cmd = espurna::settings::internal::get(key); + const auto cmd = espurna::settings::get(key); if (cmd) { internal::inject(cmd.ref()); } diff --git a/code/espurna/led.cpp b/code/espurna/led.cpp index 46d01987..951a4e14 100644 --- a/code/espurna/led.cpp +++ b/code/espurna/led.cpp @@ -636,8 +636,7 @@ static constexpr espurna::settings::query::IndexedSetting IndexedSettings[] PROG }; bool checkSamePrefix(StringView key) { - static constexpr char Prefix[] PROGMEM = "led"; - return espurna::settings::query::samePrefix(key, Prefix); + return espurna::settings::query::samePrefix(key, STRING_VIEW("led")); } String findValueFrom(StringView key) { @@ -998,7 +997,7 @@ void callback(unsigned int type, const char* topic, char* payload) { namespace web { namespace { -bool onKeyCheck(const char* key, JsonVariant&) { +bool onKeyCheck(StringView key, const JsonVariant&) { return settings::query::checkSamePrefix(key); } diff --git a/code/espurna/light.cpp b/code/espurna/light.cpp index 6064013f..73237a82 100644 --- a/code/espurna/light.cpp +++ b/code/espurna/light.cpp @@ -2312,10 +2312,10 @@ void _lightApiSetup() { namespace { -bool _lightWebSocketOnKeyCheck(const char* key, JsonVariant&) { - return (strncmp(key, "light", 5) == 0) - || (strncmp(key, "use", 3) == 0) - || (strncmp(key, "lt", 2) == 0); +bool _lightWebSocketOnKeyCheck(espurna::StringView key, const JsonVariant&) { + return espurna::settings::query::samePrefix(key, STRING_VIEW("light")) + || espurna::settings::query::samePrefix(key, STRING_VIEW("use")) + || espurna::settings::query::samePrefix(key, STRING_VIEW("lt")); } void _lightWebSocketStatus(JsonObject& root) { diff --git a/code/espurna/main.cpp b/code/espurna/main.cpp index 71e5e253..6802e5a3 100644 --- a/code/espurna/main.cpp +++ b/code/espurna/main.cpp @@ -307,6 +307,21 @@ void setup() { } // namespace main } // namespace + +bool StringView::compare(StringView other) const { + if (other._len == _len) { + if (inFlash(_ptr)) { + return memcmp_P(other._ptr, _ptr, _len) == 0; + } else if (inFlash(other._ptr)) { + return memcmp_P(_ptr, other._ptr, _len) == 0; + } + + return __builtin_memcmp(_ptr, other._ptr, _len); + } + + return false; +} + } // namespace espurna void espurnaRegisterLoop(LoopCallback callback) { diff --git a/code/espurna/migrate.cpp b/code/espurna/migrate.cpp index e183a221..95fb8be8 100644 --- a/code/espurna/migrate.cpp +++ b/code/espurna/migrate.cpp @@ -36,7 +36,7 @@ namespace { void deletePrefixes(query::StringViewIterator prefixes) { std::vector to_purge; - internal::foreach_prefix([&](StringView, String key, const kvs_type::ReadResult&) { + foreach_prefix([&](StringView, String key, const kvs_type::ReadResult&) { to_purge.push_back(std::move(key)); }, prefixes); diff --git a/code/espurna/mqtt.cpp b/code/espurna/mqtt.cpp index aecb6222..537ae3ab 100644 --- a/code/espurna/mqtt.cpp +++ b/code/espurna/mqtt.cpp @@ -430,8 +430,7 @@ static constexpr espurna::settings::query::Setting Settings[] PROGMEM { }; bool checkSamePrefix(espurna::StringView key) { - alignas(4) static constexpr char Prefix[] PROGMEM = "mqtt"; - return espurna::settings::query::samePrefix(key, Prefix); + return espurna::settings::query::samePrefix(key, STRING_VIEW("mqtt")); } String findValueFrom(espurna::StringView key) { @@ -907,7 +906,7 @@ namespace { #if WEB_SUPPORT -bool _mqttWebSocketOnKeyCheck(const char * key, JsonVariant&) { +bool _mqttWebSocketOnKeyCheck(espurna::StringView key, const JsonVariant&) { return mqtt::settings::query::checkSamePrefix(key); } diff --git a/code/espurna/nofuss.cpp b/code/espurna/nofuss.cpp index b4989c8d..45d72407 100644 --- a/code/espurna/nofuss.cpp +++ b/code/espurna/nofuss.cpp @@ -28,8 +28,8 @@ bool _nofussEnabled = false; #if WEB_SUPPORT -bool _nofussWebSocketOnKeyCheck(const char * key, JsonVariant& value) { - return (strncmp_P(key, PSTR("nofuss"), 6) == 0); +bool _nofussWebSocketOnKeyCheck(espurna::StringView key, const JsonVariant& value) { + return espurna::settings::query::samePrefix(key, STRING_VIEW("nofuss")); } void _nofussWebSocketOnVisible(JsonObject& root) { diff --git a/code/espurna/ntp.cpp b/code/espurna/ntp.cpp index e663251d..ee06a312 100644 --- a/code/espurna/ntp.cpp +++ b/code/espurna/ntp.cpp @@ -386,8 +386,8 @@ time_t now() { #if WEB_SUPPORT namespace web { -bool onKeyCheck(const char * key, JsonVariant&) { - return (strncmp_P(key, PSTR("ntp"), 3) == 0); +bool onKeyCheck(StringView key, const JsonVariant&) { + return espurna::settings::query::samePrefix(key, STRING_VIEW("ntp")); } void onVisible(JsonObject& root) { @@ -399,8 +399,8 @@ void onData(JsonObject& root) { } void onConnected(JsonObject& root) { - root["ntpServer"] = ::espurna::ntp::settings::server(); - root["ntpTZ"] = ::espurna::ntp::settings::tz(); + root["ntpServer"] = settings::server(); + root["ntpTZ"] = settings::tz(); } } // namespace web @@ -704,7 +704,7 @@ void convertLegacyOffsets() { bool dst { true }; int offset { 60 }; - espurna::settings::internal::foreach( + espurna::settings::foreach( [&](espurna::settings::kvs_type::KeyValueResult&& kv) { using namespace espurna::settings::internal; const auto key = kv.key.read(); diff --git a/code/espurna/relay.cpp b/code/espurna/relay.cpp index 395ed911..0c22180d 100644 --- a/code/espurna/relay.cpp +++ b/code/espurna/relay.cpp @@ -345,7 +345,7 @@ alignas(4) static constexpr char Mode[] PROGMEM = "relayPulse"; namespace { Result time(size_t index) { - auto time = espurna::settings::internal::get(espurna::settings::Key{keys::Time, index}.value()); + const auto time = espurna::settings::get(espurna::settings::Key{keys::Time, index}.value()); if (!time) { return Result(std::chrono::duration_cast(build::time(index))); } @@ -2163,8 +2163,8 @@ void _relayConfigure() { namespace { -bool _relayWebSocketOnKeyCheck(const char* key, JsonVariant&) { - return strncmp_P(key, PSTR("relay"), 5) == 0; +bool _relayWebSocketOnKeyCheck(espurna::StringView key, const JsonVariant&) { + return espurna::settings::query::samePrefix(key, STRING_VIEW("relay")); } void _relayWebSocketUpdate(JsonObject& root) { @@ -2933,7 +2933,7 @@ String findIndexedValueFrom(StringView key) { bool checkExact(StringView key) { for (const auto& setting : Settings) { - if (setting.key().compareFlash(key)) { + if (key == setting.key()) { return true; } } diff --git a/code/espurna/rfbridge.cpp b/code/espurna/rfbridge.cpp index 31b1a752..18ddcb72 100644 --- a/code/espurna/rfbridge.cpp +++ b/code/espurna/rfbridge.cpp @@ -457,8 +457,8 @@ void _rfbWebSocketOnAction(uint32_t client_id, const char* action, JsonObject& d #endif } -bool _rfbWebSocketOnKeyCheck(const char * key, JsonVariant& value) { - return strncmp_P(key, PSTR("rfb"), 3) == 0; +bool _rfbWebSocketOnKeyCheck(espurna::StringView key, const JsonVariant& value) { + return espurna::settings::query::samePrefix(key, STRING_VIEW("rfb")); } #endif // WEB_SUPPORT diff --git a/code/espurna/rfm69.cpp b/code/espurna/rfm69.cpp index 6773c780..9db2c1be 100644 --- a/code/espurna/rfm69.cpp +++ b/code/espurna/rfm69.cpp @@ -241,8 +241,8 @@ void _rfm69WebSocketOnConnected(JsonObject& root) { } } -bool _rfm69WebSocketOnKeyCheck(const char * key, JsonVariant& value) { - return (strncmp(key, "rfm69", 5) == 0); +bool _rfm69WebSocketOnKeyCheck(espurna::StringView key, const JsonVariant& value) { + return espurna::settings::query::samePrefix(key, STRING_VIEW("rfm69")); } void _rfm69WebSocketOnAction(uint32_t client_id, const char* action, JsonObject& data) { diff --git a/code/espurna/rpnrules.cpp b/code/espurna/rpnrules.cpp index c993b132..b501c156 100644 --- a/code/espurna/rpnrules.cpp +++ b/code/espurna/rpnrules.cpp @@ -354,8 +354,8 @@ void setup() { #if WEB_SUPPORT namespace web { -bool onKeyCheck(const char * key, JsonVariant& value) { - return strncmp_P(key, PSTR("rpn"), 3) == 0; +bool onKeyCheck(espurna::StringView key, const JsonVariant& value) { + return espurna::settings::query::samePrefix(key, STRING_VIEW("rpn")); } void onVisible(JsonObject& root) { diff --git a/code/espurna/scheduler.cpp b/code/espurna/scheduler.cpp index 422f9a2b..07ca8b0c 100644 --- a/code/espurna/scheduler.cpp +++ b/code/espurna/scheduler.cpp @@ -563,8 +563,8 @@ void setup() { #if WEB_SUPPORT namespace web { -bool onKey(const char* key, JsonVariant&) { - return strncmp_P(key, PSTR("sch"), 3) == 0; +bool onKey(StringView key, const JsonVariant&) { + return espurna::settings::query::samePrefix(key, STRING_VIEW("sch")); } void onVisible(JsonObject& root) { diff --git a/code/espurna/sensor.cpp b/code/espurna/sensor.cpp index 2a3b7382..d0166f93 100644 --- a/code/espurna/sensor.cpp +++ b/code/espurna/sensor.cpp @@ -3074,7 +3074,7 @@ void migrate(int version) { namespace web { namespace { -bool onKeyCheck(const char* key, JsonVariant&) { +bool onKeyCheck(StringView key, const JsonVariant&) { return settings::query::check(key); } diff --git a/code/espurna/settings.cpp b/code/espurna/settings.cpp index 66e01065..b8cd023b 100644 --- a/code/espurna/settings.cpp +++ b/code/espurna/settings.cpp @@ -131,8 +131,6 @@ bool EnumerationNumericHelper::check(const String& value) { } // namespace options -namespace internal { - ValueResult get(const String& key) { return kv_store.get(key); } @@ -183,6 +181,8 @@ void foreach_prefix(PrefixResultCallback&& callback, query::StringViewIterator p // -------------------------------------------------------------------------- +namespace internal { + template <> float convert(const String& value) { return atof(value.c_str()); @@ -261,13 +261,14 @@ unsigned char convert(const String& value) { } // namespace internal // TODO: UI needs this to avoid showing keys in storage order -std::vector keys() { - auto keys = internal::keys(); - std::sort(keys.begin(), keys.end(), [](const String& rhs, const String& lhs) -> bool { - return lhs.compareTo(rhs) > 0; - }); +std::vector sorted_keys() { + auto values = keys(); + std::sort(values.begin(), values.end(), + [](const String& lhs, const String& rhs) -> bool { + return rhs.compareTo(lhs) > 0; + }); - return keys; + return values; } #if TERMINAL_SUPPORT @@ -300,18 +301,22 @@ void config(::terminal::CommandContext&& ctx) { } void keys(::terminal::CommandContext&& ctx) { - auto keys = settingsKeys(); + const auto keys = settings::sorted_keys(); String value; - for (unsigned int i=0; i %s => \"%s\"\n"), (keys[i]).c_str(), value.c_str()); + for (const auto& key : keys) { + value = getSetting(key); + ctx.output.printf_P(PSTR("> %s => \"%s\"\n"), + key.c_str(), value.c_str()); } - auto available [[gnu::unused]] = internal::available(); - ctx.output.printf_P(PSTR("Number of keys: %u\n"), keys.size()); - ctx.output.printf_P(PSTR("Available: %u bytes (%u%%)\n"), - available, (100 * available) / internal::size()); + const auto size = settings::size(); + if (size > 0) { + const auto available = settings::available(); + ctx.output.printf_P(PSTR("Number of keys: %u\n"), keys.size()); + ctx.output.printf_P(PSTR("Available: %u bytes (%u%%)\n"), + available, (100 * available) / size); + } terminalOK(ctx); } @@ -324,7 +329,7 @@ void del(::terminal::CommandContext&& ctx) { int result = 0; for (auto it = (ctx.argv.begin() + 1); it != ctx.argv.end(); ++it) { - result += settings::internal::del(*it); + result += settings::del(*it); } if (result) { @@ -340,7 +345,7 @@ void set(::terminal::CommandContext&& ctx) { return; } - if (espurna::settings::internal::set(ctx.argv[1], ctx.argv[2])) { + if (settings::set(ctx.argv[1], ctx.argv[2])) { terminalOK(ctx); return; } @@ -355,7 +360,7 @@ void get(::terminal::CommandContext&& ctx) { } for (auto it = (ctx.argv.cbegin() + 1); it != ctx.argv.cend(); ++it) { - auto result = internal::get(*it); + auto result = settings::get(*it); if (!result) { const auto maybeValue = query::find(*it); if (maybeValue.length()) { @@ -383,6 +388,7 @@ void factory_reset(::terminal::CommandContext&& ctx) { terminalOK(ctx); } +[[gnu::unused]] void save(::terminal::CommandContext&& ctx) { eepromCommit(); terminalOK(ctx); @@ -418,11 +424,11 @@ void setup() { // ----------------------------------------------------------------------------- size_t settingsSize() { - return espurna::settings::internal::size() - espurna::settings::internal::available(); + return espurna::settings::size() - espurna::settings::available(); } -std::vector settingsKeys() { - return espurna::settings::keys(); +espurna::settings::Keys settingsKeys() { + return espurna::settings::sorted_keys(); } void settingsRegisterQueryHandler(espurna::settings::query::Handler handler) { @@ -434,36 +440,45 @@ String settingsQuery(espurna::StringView key) { } void moveSetting(const String& from, const String& to) { - auto result = espurna::settings::internal::get(from); + const auto result = espurna::settings::get(from); if (result) { setSetting(to, result.ref()); + delSetting(from); } - delSetting(from); } -using SettingsKeyPair = std::pair; +struct SettingsKeyPair { + espurna::settings::Key from; + espurna::settings::Key to; +}; void moveSetting(const String& from, const String& to, size_t index) { - const SettingsKeyPair keys = {{from, index}, {to, index}}; + const auto keys = SettingsKeyPair{ + .from = {from, index}, + .to = {to, index} + }; - auto result = espurna::settings::internal::get(keys.first.value()); + const auto result = espurna::settings::get(keys.from.value()); if (result) { - setSetting(keys.second, result.ref()); + setSetting(keys.to, result.ref()); + delSetting(keys.from); } - - delSetting(keys.first); } void moveSettings(const String& from, const String& to) { for (size_t index = 0; index < 100; ++index) { - const SettingsKeyPair keys = {{from, index}, {to, index}}; - auto result = espurna::settings::internal::get(keys.first.value()); + const auto keys = SettingsKeyPair{ + .from = {from, index}, + .to = {to, index} + }; + + const auto result = espurna::settings::get(keys.from.value()); if (!result) { break; } - setSetting(keys.second, result.ref()); - delSetting(keys.first); + setSetting(keys.to, result.ref()); + delSetting(keys.from); } } @@ -495,7 +510,7 @@ template double getSetting(const espurna::settings::Key& key, double defaultValue); String getSetting(const String& key) { - return std::move(espurna::settings::internal::get(key)).get(); + return std::move(espurna::settings::get(key)).get(); } String getSetting(const __FlashStringHelper* key) { @@ -520,7 +535,7 @@ String getSetting(const espurna::settings::Key& key, const __FlashStringHelper* } String getSetting(const espurna::settings::Key& key, const String& defaultValue) { - auto result = espurna::settings::internal::get(key.value()); + auto result = espurna::settings::get(key.value()); if (result) { return std::move(result).get(); } @@ -529,7 +544,7 @@ String getSetting(const espurna::settings::Key& key, const String& defaultValue) } String getSetting(const espurna::settings::Key& key, String&& defaultValue) { - auto result = espurna::settings::internal::get(key.value()); + auto result = espurna::settings::get(key.value()); if (result) { return std::move(result).get(); } @@ -538,7 +553,7 @@ String getSetting(const espurna::settings::Key& key, String&& defaultValue) { } bool delSetting(const String& key) { - return espurna::settings::internal::del(key); + return espurna::settings::del(key); } bool delSetting(const espurna::settings::Key& key) { @@ -554,7 +569,7 @@ bool delSetting(const __FlashStringHelper* key) { } bool hasSetting(const String& key) { - return espurna::settings::internal::has(key); + return espurna::settings::has(key); } bool hasSetting(const espurna::settings::Key& key) { @@ -636,16 +651,11 @@ bool settingsRestoreJson(char* json_string, size_t json_buffer_size) { } void settingsGetJson(JsonObject& root) { - - // Get sorted list of keys - auto keys = settingsKeys(); - - // Add the key-values to the json object - for (unsigned int i=0; i; -namespace internal { +namespace traits { -template +template using is_arduino_string = std::is_same::type>; -template +template using enable_if_arduino_string = std::enable_if::value>; -template +template using enable_if_not_arduino_string = std::enable_if::value>; +} // namespace types + ValueResult get(const String& key); bool set(const String& key, const String& value); bool del(const String& key); @@ -80,6 +82,8 @@ void foreach_prefix(PrefixResultCallback&&, settings::query::StringViewIterator) // -------------------------------------------------------------------------- +namespace internal { + template T convert(const String& value); @@ -228,22 +232,21 @@ String getSetting(const espurna::settings::Key& key, const String& defaultValue) String getSetting(const espurna::settings::Key& key, const String& defaultValue); String getSetting(const espurna::settings::Key& key, String&& defaultValue); -template ::type> +template ::type> T getSetting(const espurna::settings::Key& key, T defaultValue) { - using namespace espurna::settings::internal; - auto result = get(key.value()); + auto result = espurna::settings::get(key.value()); if (result) { - return convert(result.ref()); + return espurna::settings::internal::convert(result.ref()); } return defaultValue; } -template::type> +template ::type> bool setSetting(const espurna::settings::Key& key, T&& value) { - return espurna::settings::internal::set(key.value(), value); + return espurna::settings::set(key.value(), value); } -template::type> +template ::type> bool setSetting(const espurna::settings::Key& key, T value) { return setSetting(key, String(value)); } @@ -283,7 +286,7 @@ bool settingsRestoreJson(char* json_string, size_t json_buffer_size = 1024); bool settingsRestoreJson(JsonObject& data); size_t settingsKeyCount(); -std::vector settingsKeys(); +espurna::settings::Keys settingsKeys(); size_t settingsSize(); @@ -303,7 +306,7 @@ void migrate(); // Deprecated implementation // ----------------------------------------------------------------------------- -template +template String getSetting(const String& key, unsigned char index, T defaultValue) __attribute__((deprecated("getSetting({key, index}, default) should be used instead"))); diff --git a/code/espurna/settings_helpers.h b/code/espurna/settings_helpers.h index cd0c6be4..da89e41c 100644 --- a/code/espurna/settings_helpers.h +++ b/code/espurna/settings_helpers.h @@ -365,8 +365,8 @@ struct alignas(8) Setting { return _key == key; } - bool operator==(const StringView& key) const { - return _key.compareFlash(key); + bool operator==(StringView key) const { + return _key == key; } static String findValueFrom(const Setting* begin, const Setting* end, StringView key); diff --git a/code/espurna/system.cpp b/code/espurna/system.cpp index 7e7add05..6544c1ec 100644 --- a/code/espurna/system.cpp +++ b/code/espurna/system.cpp @@ -775,10 +775,9 @@ void onConnected(JsonObject& root) { espurna::settings::internal::serialize(heartbeat::settings::mode()); } -bool onKeyCheck(const char* key, JsonVariant&) { - const auto view = StringView(key); - return espurna::settings::query::samePrefix(view, STRING_VIEW("sys")) - || espurna::settings::query::samePrefix(view, STRING_VIEW("hb")); +bool onKeyCheck(StringView key, const JsonVariant&) { + return espurna::settings::query::samePrefix(key, STRING_VIEW("sys")) + || espurna::settings::query::samePrefix(key, STRING_VIEW("hb")); } void init() { diff --git a/code/espurna/telnet.cpp b/code/espurna/telnet.cpp index d6e1a624..4bb6782f 100644 --- a/code/espurna/telnet.cpp +++ b/code/espurna/telnet.cpp @@ -97,8 +97,8 @@ bool _telnetClientsAuth[TELNET_MAX_CLIENTS]; #if WEB_SUPPORT -bool _telnetWebSocketOnKeyCheck(const char * key, JsonVariant&) { - return strncmp_P(key, PSTR("telnet"), 6) == 0; +bool _telnetWebSocketOnKeyCheck(espurna::StringView key, const JsonVariant&) { + return espurna::settings::query::samePrefix(key, STRING_VIEW("telnet")); } void _telnetWebSocketOnVisible(JsonObject& root) { diff --git a/code/espurna/thermostat.cpp b/code/espurna/thermostat.cpp index 9b3ba8fd..d21d1709 100644 --- a/code/espurna/thermostat.cpp +++ b/code/espurna/thermostat.cpp @@ -813,18 +813,18 @@ void _thermostatWebSocketOnConnected(JsonObject& root) { } //------------------------------------------------------------------------------ -bool _thermostatWebSocketOnKeyCheck(const char * key, JsonVariant& value) { - if (strncmp(key, NAME_THERMOSTAT_ENABLED, strlen(NAME_THERMOSTAT_ENABLED)) == 0) return true; - if (strncmp(key, NAME_THERMOSTAT_MODE, strlen(NAME_THERMOSTAT_MODE)) == 0) return true; - if (strncmp(key, NAME_TEMP_RANGE_MIN, strlen(NAME_TEMP_RANGE_MIN)) == 0) return true; - if (strncmp(key, NAME_TEMP_RANGE_MAX, strlen(NAME_TEMP_RANGE_MAX)) == 0) return true; - if (strncmp(key, NAME_REMOTE_SENSOR_NAME, strlen(NAME_REMOTE_SENSOR_NAME)) == 0) return true; - if (strncmp(key, NAME_REMOTE_TEMP_MAX_WAIT, strlen(NAME_REMOTE_TEMP_MAX_WAIT)) == 0) return true; - if (strncmp(key, NAME_MAX_ON_TIME, strlen(NAME_MAX_ON_TIME)) == 0) return true; - if (strncmp(key, NAME_MIN_OFF_TIME, strlen(NAME_MIN_OFF_TIME)) == 0) return true; - if (strncmp(key, NAME_ALONE_ON_TIME, strlen(NAME_ALONE_ON_TIME)) == 0) return true; - if (strncmp(key, NAME_ALONE_OFF_TIME, strlen(NAME_ALONE_OFF_TIME)) == 0) return true; - return false; +bool _thermostatWebSocketOnKeyCheck(espurna::StringView key, const JsonVariant&) { + return key == NAME_THERMOSTAT_ENABLED + || key == NAME_THERMOSTAT_ENABLED + || key == NAME_THERMOSTAT_MODE + || key == NAME_TEMP_RANGE_MIN + || key == NAME_TEMP_RANGE_MAX + || key == NAME_REMOTE_SENSOR_NAME + || key == NAME_REMOTE_TEMP_MAX_WAIT + || key == NAME_MAX_ON_TIME + || key == NAME_MIN_OFF_TIME + || key == NAME_ALONE_ON_TIME + || key == NAME_ALONE_OFF_TIME; } //------------------------------------------------------------------------------ diff --git a/code/espurna/thingspeak.cpp b/code/espurna/thingspeak.cpp index 4cfc0d32..62c6b640 100644 --- a/code/espurna/thingspeak.cpp +++ b/code/espurna/thingspeak.cpp @@ -676,8 +676,8 @@ void loop() { namespace web { namespace { -bool onKeyCheck(const char* key, JsonVariant& value) { - return (strncmp_P(key, PSTR("tspk"), 4) == 0); +bool onKeyCheck(StringView key, const JsonVariant&) { + return espurna::settings::query::samePrefix(key, STRING_VIEW("tspk")); } void onVisible(JsonObject& root) { diff --git a/code/espurna/types.h b/code/espurna/types.h index 4e15bd23..f147ae71 100644 --- a/code/espurna/types.h +++ b/code/espurna/types.h @@ -8,6 +8,10 @@ Copyright (C) 2019-2021 by Maxim Prokhorov +#include + +// missing in our original header +extern "C" int memcmp_P(const void*, const void*, size_t); namespace espurna { @@ -88,16 +92,6 @@ struct StringView { return _len; } - bool compareRam(const StringView& other) const { - return (other._len == _len) - && (strncmp(other._ptr, _ptr, _len) == 0); - } - - bool compareFlash(const StringView& other) const { - return (other._len == _len) - && (strncmp_P(other._ptr, _ptr, _len) == 0); - } - String toString() const { String out; out.concat(_ptr, _len); @@ -108,17 +102,23 @@ struct StringView { return toString(); } + bool compare(StringView other) const; + private: + static bool inFlash(const char* ptr) { + // common comparison would use >=0x40000000 + // instead, slightly reduce the footprint by + // checking *only* for numbers below it + static constexpr uintptr_t Mask { 1 << 30 }; + return (reinterpret_cast(ptr) & Mask) > 0; + } + const char* _ptr; size_t _len; }; -inline bool operator==(const StringView& lhs, const char* rhs) { - return lhs.compareFlash(rhs); -} - -inline bool operator==(const StringView& lhs, const String& rhs) { - return lhs.compareFlash(rhs); +inline bool operator==(StringView lhs, StringView rhs) { + return lhs.compare(rhs); } inline String operator+(String&& lhs, StringView rhs) { diff --git a/code/espurna/web.cpp b/code/espurna/web.cpp index a94dd79c..798d4e5e 100644 --- a/code/espurna/web.cpp +++ b/code/espurna/web.cpp @@ -339,7 +339,7 @@ void _onGetConfig(AsyncWebServerRequest *request) { } out->concat(buffer, prefix_len); - espurna::settings::internal::foreach([&](espurna::settings::kvs_type::KeyValueResult&& kv) { + espurna::settings::foreach([&](espurna::settings::kvs_type::KeyValueResult&& kv) { auto key = kv.key.read(); auto value = kv.value.read(); diff --git a/code/espurna/wifi.cpp b/code/espurna/wifi.cpp index 03b24a38..1da49356 100644 --- a/code/espurna/wifi.cpp +++ b/code/espurna/wifi.cpp @@ -2388,10 +2388,9 @@ void onConnected(JsonObject& root) { container[F("max")] = wifi::sta::build::NetworksMax; } -bool onKeyCheck(const char* key, JsonVariant&) { - const auto key_view = StringView(key); - return wifi::settings::query::checkExactPrefix(key_view) - || wifi::settings::query::checkIndexedPrefix(key_view); +bool onKeyCheck(StringView key, const JsonVariant&) { + return wifi::settings::query::checkExactPrefix(key) + || wifi::settings::query::checkIndexedPrefix(key); } void onScan(uint32_t client_id) { diff --git a/code/espurna/ws.cpp b/code/espurna/ws.cpp index 6670b7f0..3f820e8e 100644 --- a/code/espurna/ws.cpp +++ b/code/espurna/ws.cpp @@ -427,10 +427,10 @@ namespace { // Check the existing setting before saving it // (we only care about the settings storage, don't mind the build values) -bool _wsStore(const String& key, const String& value) { - auto current = espurna::settings::internal::get(key); +bool _wsStore(String key, const String& value) { + const auto current = espurna::settings::get(key); if (!current || (current.ref() != value)) { - return espurna::settings::internal::set(key, value); + return espurna::settings::set(key, value); } return false; @@ -438,15 +438,15 @@ bool _wsStore(const String& key, const String& value) { // TODO: generate "accepted" keys in the initial phase of the connection? // TODO: is value ever used... by anything? -bool _wsCheckKey(const char* key, JsonVariant& value) { +bool _wsCheckKey(const String& key, const JsonVariant& value) { #if NTP_SUPPORT - if (strncmp_P(key, PSTR("ntpTZ"), strlen(key)) == 0) { + if (key == STRING_VIEW("ntpTZ")) { _wsResetUpdateTimer(); return true; } #endif - if (strncmp_P(key, PSTR("adminPass"), strlen(key)) == 0) { + if (key == STRING_VIEW("adminPass")) { const auto pass = getAdminPass(); return !pass.equalsConstantTime(value.as()); } @@ -579,27 +579,23 @@ void _wsParse(AsyncWebSocketClient *client, uint8_t * payload, size_t length) { // TODO: pass key as string, we always attempt to use it as such JsonObject& toAssign = settings["set"]; for (auto& kv : toAssign) { - String key = kv.key; - JsonVariant& value = kv.value; - if (!_wsCheckKey(key.c_str(), value)) { - continue; - } - - if (_wsStore(key, value.as())) { - save = true; + const String key = kv.key; + if (_wsCheckKey(key, kv.value)) { + if (_wsStore(key, kv.value.as())) { + save = true; + } } } _wsPostParse(client_id, save, reload); } -bool _wsOnKeyCheck(const char* key, JsonVariant&) { - const auto keylen = strlen(key); - return (strncmp_P(key, PSTR("ws"), 2) == 0) - || (strncmp_P(key, PSTR("adminPass"), keylen) == 0) - || (strncmp_P(key, PSTR("hostname"), keylen) == 0) - || (strncmp_P(key, PSTR("desc"), keylen) == 0) - || (strncmp_P(key, PSTR("webPort"), keylen) == 0); +bool _wsOnKeyCheck(espurna::StringView key, const JsonVariant&) { + return espurna::settings::query::samePrefix(key, STRING_VIEW("ws")) + || espurna::settings::query::samePrefix(key, STRING_VIEW("adminPass")) + || espurna::settings::query::samePrefix(key, STRING_VIEW("hostname")) + || espurna::settings::query::samePrefix(key, STRING_VIEW("desc")) + || espurna::settings::query::samePrefix(key, STRING_VIEW("webPort")); } void _wsOnConnected(JsonObject& root) { diff --git a/code/espurna/ws.h b/code/espurna/ws.h index caeb63f1..aa865c52 100644 --- a/code/espurna/ws.h +++ b/code/espurna/ws.h @@ -32,8 +32,8 @@ Copyright (C) 2019 by Maxim Prokhorov // - on_keycheck will be used to determine if we can handle specific settings keys using ws_on_send_callback_f = std::function; -using ws_on_action_callback_f = std::function; -using ws_on_keycheck_callback_f = std::function; +using ws_on_action_callback_f = std::function; +using ws_on_keycheck_callback_f = std::function; // TODO: use iterators as inputs for Post(), avoid depending on vector / any specific container using ws_on_send_callback_list_t = std::vector; @@ -49,7 +49,7 @@ struct ws_callbacks_t { using on_action_f = void(*)(uint32_t, const char*, JsonObject&); ws_callbacks_t& onAction(on_action_f); - using on_keycheck_f = bool(*)(const char*, JsonVariant&); + using on_keycheck_f = bool(*)(espurna::StringView, const JsonVariant&); ws_callbacks_t& onKeyCheck(on_keycheck_f); ws_on_send_callback_list_t on_visible;