Mirror of espurna firmware for wireless switches and more
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

714 lines
17 KiB

Rework settings (#2282) * wip based on early draft. todo benchmarking * fixup eraser, assume keys are unique * fix cursor copy, test removal at random * small benchmark via permutations. todo lambdas and novirtual * fix empty condition / reset * overwrite optimizations, fix move offsets overflows * ...erase using 0xff instead of 0 * test the theory with code, different length kv were bugged * try to check for out-of-bounds writes / reads * style * trying to fix mover again * clarify length, defend against reading len on edge * fix uncommited rewind change * prove space assumptions * more concise traces, fix move condition (agrh!!!) * slightly more internal knowledge (estimates API?) * make sure cursor is only valid within the range * ensure 0 does not blow things * go back up * cursor comments * comments * rewrite writes through cursor * in del too * estimate kv storage requirements, return available size * move raw erase / move into a method, allow ::set to avoid scanning storage twice * refactor naming, use in code * amend storage slicing test * fix crash handler offsets, cleanup configuration * start -> begin * eeprom readiness * dependencies * unused * SPI_FLASH constants for older Core * vtables -> templates * less include dependencies * gcov help, move estimate outside of the class * writer position can never match, use begin + offset * tweak save_crash to trigger only once in a serious crash * doh, header function should be inline * foreach api, tweak structs for public api * use test helper class * when not using foreach, move cursor reset closer to the loop using read_kv * coverage comments, fix typo in tests decltype * ensure set() does not break with offset * make codacy happy again
4 years ago
Rework settings (#2282) * wip based on early draft. todo benchmarking * fixup eraser, assume keys are unique * fix cursor copy, test removal at random * small benchmark via permutations. todo lambdas and novirtual * fix empty condition / reset * overwrite optimizations, fix move offsets overflows * ...erase using 0xff instead of 0 * test the theory with code, different length kv were bugged * try to check for out-of-bounds writes / reads * style * trying to fix mover again * clarify length, defend against reading len on edge * fix uncommited rewind change * prove space assumptions * more concise traces, fix move condition (agrh!!!) * slightly more internal knowledge (estimates API?) * make sure cursor is only valid within the range * ensure 0 does not blow things * go back up * cursor comments * comments * rewrite writes through cursor * in del too * estimate kv storage requirements, return available size * move raw erase / move into a method, allow ::set to avoid scanning storage twice * refactor naming, use in code * amend storage slicing test * fix crash handler offsets, cleanup configuration * start -> begin * eeprom readiness * dependencies * unused * SPI_FLASH constants for older Core * vtables -> templates * less include dependencies * gcov help, move estimate outside of the class * writer position can never match, use begin + offset * tweak save_crash to trigger only once in a serious crash * doh, header function should be inline * foreach api, tweak structs for public api * use test helper class * when not using foreach, move cursor reset closer to the loop using read_kv * coverage comments, fix typo in tests decltype * ensure set() does not break with offset * make codacy happy again
4 years ago
Terminal: change command-line parser (#2247) Change the underlying command line handling: - switch to a custom parser, inspired by redis / sds - update terminalRegisterCommand signature, pass only bare minimum - clean-up `help` & `commands`. update settings `set`, `get` and `del` - allow our custom test suite to run command-line tests - clean-up Stream IO to allow us to print large things into debug stream (for example, `eeprom.dump`) - send parsing errors to the debug log As a proof of concept, introduce `TERMINAL_MQTT_SUPPORT` and `TERMINAL_WEB_API_SUPPORT` - MQTT subscribes to the `<root>/cmd/set` and sends response to the `<root>/cmd`. We can't output too much, as we don't have any large-send API. - Web API listens to the `/api/cmd?apikey=...&line=...` (or PUT, params inside the body). This one is intended as a possible replacement of the `API_SUPPORT`. Internals introduce a 'task' around the AsyncWebServerRequest object that will simulate what WiFiClient does and push data into it continuously, switching between CONT and SYS. Both are experimental. We only accept a single command and not every command is updated to use Print `ctx.output` object. We are also somewhat limited by the Print / Stream overall, perhaps I am overestimating the usefulness of Arduino compatibility to such an extent :) Web API handler can also sometimes show only part of the result, whenever the command tries to yield() by itself waiting for something. Perhaps we would need to create a custom request handler for that specific use-case.
4 years ago
Rework settings (#2282) * wip based on early draft. todo benchmarking * fixup eraser, assume keys are unique * fix cursor copy, test removal at random * small benchmark via permutations. todo lambdas and novirtual * fix empty condition / reset * overwrite optimizations, fix move offsets overflows * ...erase using 0xff instead of 0 * test the theory with code, different length kv were bugged * try to check for out-of-bounds writes / reads * style * trying to fix mover again * clarify length, defend against reading len on edge * fix uncommited rewind change * prove space assumptions * more concise traces, fix move condition (agrh!!!) * slightly more internal knowledge (estimates API?) * make sure cursor is only valid within the range * ensure 0 does not blow things * go back up * cursor comments * comments * rewrite writes through cursor * in del too * estimate kv storage requirements, return available size * move raw erase / move into a method, allow ::set to avoid scanning storage twice * refactor naming, use in code * amend storage slicing test * fix crash handler offsets, cleanup configuration * start -> begin * eeprom readiness * dependencies * unused * SPI_FLASH constants for older Core * vtables -> templates * less include dependencies * gcov help, move estimate outside of the class * writer position can never match, use begin + offset * tweak save_crash to trigger only once in a serious crash * doh, header function should be inline * foreach api, tweak structs for public api * use test helper class * when not using foreach, move cursor reset closer to the loop using read_kv * coverage comments, fix typo in tests decltype * ensure set() does not break with offset * make codacy happy again
4 years ago
Terminal: change command-line parser (#2247) Change the underlying command line handling: - switch to a custom parser, inspired by redis / sds - update terminalRegisterCommand signature, pass only bare minimum - clean-up `help` & `commands`. update settings `set`, `get` and `del` - allow our custom test suite to run command-line tests - clean-up Stream IO to allow us to print large things into debug stream (for example, `eeprom.dump`) - send parsing errors to the debug log As a proof of concept, introduce `TERMINAL_MQTT_SUPPORT` and `TERMINAL_WEB_API_SUPPORT` - MQTT subscribes to the `<root>/cmd/set` and sends response to the `<root>/cmd`. We can't output too much, as we don't have any large-send API. - Web API listens to the `/api/cmd?apikey=...&line=...` (or PUT, params inside the body). This one is intended as a possible replacement of the `API_SUPPORT`. Internals introduce a 'task' around the AsyncWebServerRequest object that will simulate what WiFiClient does and push data into it continuously, switching between CONT and SYS. Both are experimental. We only accept a single command and not every command is updated to use Print `ctx.output` object. We are also somewhat limited by the Print / Stream overall, perhaps I am overestimating the usefulness of Arduino compatibility to such an extent :) Web API handler can also sometimes show only part of the result, whenever the command tries to yield() by itself waiting for something. Perhaps we would need to create a custom request handler for that specific use-case.
4 years ago
Rework settings (#2282) * wip based on early draft. todo benchmarking * fixup eraser, assume keys are unique * fix cursor copy, test removal at random * small benchmark via permutations. todo lambdas and novirtual * fix empty condition / reset * overwrite optimizations, fix move offsets overflows * ...erase using 0xff instead of 0 * test the theory with code, different length kv were bugged * try to check for out-of-bounds writes / reads * style * trying to fix mover again * clarify length, defend against reading len on edge * fix uncommited rewind change * prove space assumptions * more concise traces, fix move condition (agrh!!!) * slightly more internal knowledge (estimates API?) * make sure cursor is only valid within the range * ensure 0 does not blow things * go back up * cursor comments * comments * rewrite writes through cursor * in del too * estimate kv storage requirements, return available size * move raw erase / move into a method, allow ::set to avoid scanning storage twice * refactor naming, use in code * amend storage slicing test * fix crash handler offsets, cleanup configuration * start -> begin * eeprom readiness * dependencies * unused * SPI_FLASH constants for older Core * vtables -> templates * less include dependencies * gcov help, move estimate outside of the class * writer position can never match, use begin + offset * tweak save_crash to trigger only once in a serious crash * doh, header function should be inline * foreach api, tweak structs for public api * use test helper class * when not using foreach, move cursor reset closer to the loop using read_kv * coverage comments, fix typo in tests decltype * ensure set() does not break with offset * make codacy happy again
4 years ago
Rework settings (#2282) * wip based on early draft. todo benchmarking * fixup eraser, assume keys are unique * fix cursor copy, test removal at random * small benchmark via permutations. todo lambdas and novirtual * fix empty condition / reset * overwrite optimizations, fix move offsets overflows * ...erase using 0xff instead of 0 * test the theory with code, different length kv were bugged * try to check for out-of-bounds writes / reads * style * trying to fix mover again * clarify length, defend against reading len on edge * fix uncommited rewind change * prove space assumptions * more concise traces, fix move condition (agrh!!!) * slightly more internal knowledge (estimates API?) * make sure cursor is only valid within the range * ensure 0 does not blow things * go back up * cursor comments * comments * rewrite writes through cursor * in del too * estimate kv storage requirements, return available size * move raw erase / move into a method, allow ::set to avoid scanning storage twice * refactor naming, use in code * amend storage slicing test * fix crash handler offsets, cleanup configuration * start -> begin * eeprom readiness * dependencies * unused * SPI_FLASH constants for older Core * vtables -> templates * less include dependencies * gcov help, move estimate outside of the class * writer position can never match, use begin + offset * tweak save_crash to trigger only once in a serious crash * doh, header function should be inline * foreach api, tweak structs for public api * use test helper class * when not using foreach, move cursor reset closer to the loop using read_kv * coverage comments, fix typo in tests decltype * ensure set() does not break with offset * make codacy happy again
4 years ago
Rework settings (#2282) * wip based on early draft. todo benchmarking * fixup eraser, assume keys are unique * fix cursor copy, test removal at random * small benchmark via permutations. todo lambdas and novirtual * fix empty condition / reset * overwrite optimizations, fix move offsets overflows * ...erase using 0xff instead of 0 * test the theory with code, different length kv were bugged * try to check for out-of-bounds writes / reads * style * trying to fix mover again * clarify length, defend against reading len on edge * fix uncommited rewind change * prove space assumptions * more concise traces, fix move condition (agrh!!!) * slightly more internal knowledge (estimates API?) * make sure cursor is only valid within the range * ensure 0 does not blow things * go back up * cursor comments * comments * rewrite writes through cursor * in del too * estimate kv storage requirements, return available size * move raw erase / move into a method, allow ::set to avoid scanning storage twice * refactor naming, use in code * amend storage slicing test * fix crash handler offsets, cleanup configuration * start -> begin * eeprom readiness * dependencies * unused * SPI_FLASH constants for older Core * vtables -> templates * less include dependencies * gcov help, move estimate outside of the class * writer position can never match, use begin + offset * tweak save_crash to trigger only once in a serious crash * doh, header function should be inline * foreach api, tweak structs for public api * use test helper class * when not using foreach, move cursor reset closer to the loop using read_kv * coverage comments, fix typo in tests decltype * ensure set() does not break with offset * make codacy happy again
4 years ago
Rework settings (#2282) * wip based on early draft. todo benchmarking * fixup eraser, assume keys are unique * fix cursor copy, test removal at random * small benchmark via permutations. todo lambdas and novirtual * fix empty condition / reset * overwrite optimizations, fix move offsets overflows * ...erase using 0xff instead of 0 * test the theory with code, different length kv were bugged * try to check for out-of-bounds writes / reads * style * trying to fix mover again * clarify length, defend against reading len on edge * fix uncommited rewind change * prove space assumptions * more concise traces, fix move condition (agrh!!!) * slightly more internal knowledge (estimates API?) * make sure cursor is only valid within the range * ensure 0 does not blow things * go back up * cursor comments * comments * rewrite writes through cursor * in del too * estimate kv storage requirements, return available size * move raw erase / move into a method, allow ::set to avoid scanning storage twice * refactor naming, use in code * amend storage slicing test * fix crash handler offsets, cleanup configuration * start -> begin * eeprom readiness * dependencies * unused * SPI_FLASH constants for older Core * vtables -> templates * less include dependencies * gcov help, move estimate outside of the class * writer position can never match, use begin + offset * tweak save_crash to trigger only once in a serious crash * doh, header function should be inline * foreach api, tweak structs for public api * use test helper class * when not using foreach, move cursor reset closer to the loop using read_kv * coverage comments, fix typo in tests decltype * ensure set() does not break with offset * make codacy happy again
4 years ago
Rework settings (#2282) * wip based on early draft. todo benchmarking * fixup eraser, assume keys are unique * fix cursor copy, test removal at random * small benchmark via permutations. todo lambdas and novirtual * fix empty condition / reset * overwrite optimizations, fix move offsets overflows * ...erase using 0xff instead of 0 * test the theory with code, different length kv were bugged * try to check for out-of-bounds writes / reads * style * trying to fix mover again * clarify length, defend against reading len on edge * fix uncommited rewind change * prove space assumptions * more concise traces, fix move condition (agrh!!!) * slightly more internal knowledge (estimates API?) * make sure cursor is only valid within the range * ensure 0 does not blow things * go back up * cursor comments * comments * rewrite writes through cursor * in del too * estimate kv storage requirements, return available size * move raw erase / move into a method, allow ::set to avoid scanning storage twice * refactor naming, use in code * amend storage slicing test * fix crash handler offsets, cleanup configuration * start -> begin * eeprom readiness * dependencies * unused * SPI_FLASH constants for older Core * vtables -> templates * less include dependencies * gcov help, move estimate outside of the class * writer position can never match, use begin + offset * tweak save_crash to trigger only once in a serious crash * doh, header function should be inline * foreach api, tweak structs for public api * use test helper class * when not using foreach, move cursor reset closer to the loop using read_kv * coverage comments, fix typo in tests decltype * ensure set() does not break with offset * make codacy happy again
4 years ago
Rework settings (#2282) * wip based on early draft. todo benchmarking * fixup eraser, assume keys are unique * fix cursor copy, test removal at random * small benchmark via permutations. todo lambdas and novirtual * fix empty condition / reset * overwrite optimizations, fix move offsets overflows * ...erase using 0xff instead of 0 * test the theory with code, different length kv were bugged * try to check for out-of-bounds writes / reads * style * trying to fix mover again * clarify length, defend against reading len on edge * fix uncommited rewind change * prove space assumptions * more concise traces, fix move condition (agrh!!!) * slightly more internal knowledge (estimates API?) * make sure cursor is only valid within the range * ensure 0 does not blow things * go back up * cursor comments * comments * rewrite writes through cursor * in del too * estimate kv storage requirements, return available size * move raw erase / move into a method, allow ::set to avoid scanning storage twice * refactor naming, use in code * amend storage slicing test * fix crash handler offsets, cleanup configuration * start -> begin * eeprom readiness * dependencies * unused * SPI_FLASH constants for older Core * vtables -> templates * less include dependencies * gcov help, move estimate outside of the class * writer position can never match, use begin + offset * tweak save_crash to trigger only once in a serious crash * doh, header function should be inline * foreach api, tweak structs for public api * use test helper class * when not using foreach, move cursor reset closer to the loop using read_kv * coverage comments, fix typo in tests decltype * ensure set() does not break with offset * make codacy happy again
4 years ago
Rework settings (#2282) * wip based on early draft. todo benchmarking * fixup eraser, assume keys are unique * fix cursor copy, test removal at random * small benchmark via permutations. todo lambdas and novirtual * fix empty condition / reset * overwrite optimizations, fix move offsets overflows * ...erase using 0xff instead of 0 * test the theory with code, different length kv were bugged * try to check for out-of-bounds writes / reads * style * trying to fix mover again * clarify length, defend against reading len on edge * fix uncommited rewind change * prove space assumptions * more concise traces, fix move condition (agrh!!!) * slightly more internal knowledge (estimates API?) * make sure cursor is only valid within the range * ensure 0 does not blow things * go back up * cursor comments * comments * rewrite writes through cursor * in del too * estimate kv storage requirements, return available size * move raw erase / move into a method, allow ::set to avoid scanning storage twice * refactor naming, use in code * amend storage slicing test * fix crash handler offsets, cleanup configuration * start -> begin * eeprom readiness * dependencies * unused * SPI_FLASH constants for older Core * vtables -> templates * less include dependencies * gcov help, move estimate outside of the class * writer position can never match, use begin + offset * tweak save_crash to trigger only once in a serious crash * doh, header function should be inline * foreach api, tweak structs for public api * use test helper class * when not using foreach, move cursor reset closer to the loop using read_kv * coverage comments, fix typo in tests decltype * ensure set() does not break with offset * make codacy happy again
4 years ago
Rework settings (#2282) * wip based on early draft. todo benchmarking * fixup eraser, assume keys are unique * fix cursor copy, test removal at random * small benchmark via permutations. todo lambdas and novirtual * fix empty condition / reset * overwrite optimizations, fix move offsets overflows * ...erase using 0xff instead of 0 * test the theory with code, different length kv were bugged * try to check for out-of-bounds writes / reads * style * trying to fix mover again * clarify length, defend against reading len on edge * fix uncommited rewind change * prove space assumptions * more concise traces, fix move condition (agrh!!!) * slightly more internal knowledge (estimates API?) * make sure cursor is only valid within the range * ensure 0 does not blow things * go back up * cursor comments * comments * rewrite writes through cursor * in del too * estimate kv storage requirements, return available size * move raw erase / move into a method, allow ::set to avoid scanning storage twice * refactor naming, use in code * amend storage slicing test * fix crash handler offsets, cleanup configuration * start -> begin * eeprom readiness * dependencies * unused * SPI_FLASH constants for older Core * vtables -> templates * less include dependencies * gcov help, move estimate outside of the class * writer position can never match, use begin + offset * tweak save_crash to trigger only once in a serious crash * doh, header function should be inline * foreach api, tweak structs for public api * use test helper class * when not using foreach, move cursor reset closer to the loop using read_kv * coverage comments, fix typo in tests decltype * ensure set() does not break with offset * make codacy happy again
4 years ago
Rework settings (#2282) * wip based on early draft. todo benchmarking * fixup eraser, assume keys are unique * fix cursor copy, test removal at random * small benchmark via permutations. todo lambdas and novirtual * fix empty condition / reset * overwrite optimizations, fix move offsets overflows * ...erase using 0xff instead of 0 * test the theory with code, different length kv were bugged * try to check for out-of-bounds writes / reads * style * trying to fix mover again * clarify length, defend against reading len on edge * fix uncommited rewind change * prove space assumptions * more concise traces, fix move condition (agrh!!!) * slightly more internal knowledge (estimates API?) * make sure cursor is only valid within the range * ensure 0 does not blow things * go back up * cursor comments * comments * rewrite writes through cursor * in del too * estimate kv storage requirements, return available size * move raw erase / move into a method, allow ::set to avoid scanning storage twice * refactor naming, use in code * amend storage slicing test * fix crash handler offsets, cleanup configuration * start -> begin * eeprom readiness * dependencies * unused * SPI_FLASH constants for older Core * vtables -> templates * less include dependencies * gcov help, move estimate outside of the class * writer position can never match, use begin + offset * tweak save_crash to trigger only once in a serious crash * doh, header function should be inline * foreach api, tweak structs for public api * use test helper class * when not using foreach, move cursor reset closer to the loop using read_kv * coverage comments, fix typo in tests decltype * ensure set() does not break with offset * make codacy happy again
4 years ago
6 years ago
Terminal: change command-line parser (#2247) Change the underlying command line handling: - switch to a custom parser, inspired by redis / sds - update terminalRegisterCommand signature, pass only bare minimum - clean-up `help` & `commands`. update settings `set`, `get` and `del` - allow our custom test suite to run command-line tests - clean-up Stream IO to allow us to print large things into debug stream (for example, `eeprom.dump`) - send parsing errors to the debug log As a proof of concept, introduce `TERMINAL_MQTT_SUPPORT` and `TERMINAL_WEB_API_SUPPORT` - MQTT subscribes to the `<root>/cmd/set` and sends response to the `<root>/cmd`. We can't output too much, as we don't have any large-send API. - Web API listens to the `/api/cmd?apikey=...&line=...` (or PUT, params inside the body). This one is intended as a possible replacement of the `API_SUPPORT`. Internals introduce a 'task' around the AsyncWebServerRequest object that will simulate what WiFiClient does and push data into it continuously, switching between CONT and SYS. Both are experimental. We only accept a single command and not every command is updated to use Print `ctx.output` object. We are also somewhat limited by the Print / Stream overall, perhaps I am overestimating the usefulness of Arduino compatibility to such an extent :) Web API handler can also sometimes show only part of the result, whenever the command tries to yield() by itself waiting for something. Perhaps we would need to create a custom request handler for that specific use-case.
4 years ago
  1. /*
  2. SETTINGS MODULE
  3. Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com>
  4. */
  5. #include "espurna.h"
  6. #include "crash.h"
  7. #include "terminal.h"
  8. #include "storage_eeprom.h"
  9. #include <algorithm>
  10. #include <vector>
  11. #include <cstdlib>
  12. #include <ArduinoJson.h>
  13. // -----------------------------------------------------------------------------
  14. namespace espurna {
  15. namespace settings {
  16. namespace {
  17. // Depending on features enabled, we may end up with different left boundary
  18. // Settings are written right-to-left, so we only have issues when there are a lot of key-values
  19. // XXX: slightly hacky, because we EEPROMr.length() is 0 before we enter setup() code
  20. static kvs_type kv_store(
  21. EepromStorage{},
  22. #if DEBUG_SUPPORT
  23. EepromReservedSize + crashReservedSize(),
  24. #else
  25. EepromReservedSize,
  26. #endif
  27. EepromSize
  28. );
  29. } // namespace
  30. namespace query {
  31. const Setting* Setting::findFrom(const Setting* begin, const Setting* end, StringView key) {
  32. for (auto it = begin; it != end; ++it) {
  33. if ((*it) == key) {
  34. return it;
  35. }
  36. }
  37. return end;
  38. }
  39. String Setting::findValueFrom(const Setting* begin, const Setting* end, StringView key) {
  40. String out;
  41. const auto value = findFrom(begin, end, key);
  42. if (value != end) {
  43. out = (*value).value();
  44. }
  45. return out;
  46. }
  47. bool IndexedSetting::findSamePrefix(const IndexedSetting* begin, const IndexedSetting* end, StringView key) {
  48. for (auto it = begin; it != end; ++it) {
  49. if (samePrefix(key, (*it).prefix())) {
  50. return true;
  51. }
  52. }
  53. return false;
  54. }
  55. String IndexedSetting::findValueFrom(Iota iota, const IndexedSetting* begin, const IndexedSetting* end, StringView key) {
  56. String out;
  57. while (iota) {
  58. for (auto it = begin; it != end; ++it) {
  59. const auto expected = Key(
  60. (*it).prefix().toString(), *iota);
  61. if (key == expected.value()) {
  62. out = (*it).value(*iota);
  63. goto output;
  64. }
  65. }
  66. ++iota;
  67. }
  68. output:
  69. return out;
  70. }
  71. namespace internal {
  72. namespace {
  73. std::forward_list<Handler> handlers;
  74. } // namespace
  75. } // namespace internal
  76. String find(StringView key) {
  77. String out;
  78. for (const auto& handler : internal::handlers) {
  79. if (handler.check(key)) {
  80. out = handler.get(key);
  81. break;
  82. }
  83. }
  84. return out;
  85. }
  86. } // namespace query
  87. namespace options {
  88. bool EnumerationNumericHelper::check(const String& value) {
  89. if (value.length()) {
  90. if ((value.length() > 1) && (*value.begin() == '0')) {
  91. return false;
  92. }
  93. for (auto it = value.begin(); it != value.end(); ++it) {
  94. switch (*it) {
  95. case '0'...'9':
  96. break;
  97. default:
  98. return false;
  99. }
  100. }
  101. return true;
  102. }
  103. return false;
  104. }
  105. } // namespace options
  106. ValueResult get(const String& key) {
  107. return kv_store.get(key);
  108. }
  109. bool set(const String& key, const String& value) {
  110. return kv_store.set(key, value);
  111. }
  112. bool del(const String& key) {
  113. return kv_store.del(key);
  114. }
  115. bool has(const String& key) {
  116. return kv_store.has(key);
  117. }
  118. Keys keys() {
  119. Keys out;
  120. kv_store.foreach([&](kvs_type::KeyValueResult&& kv) {
  121. out.push_back(kv.key.read());
  122. });
  123. return out;
  124. }
  125. size_t available() {
  126. return kv_store.available();
  127. }
  128. size_t size() {
  129. return kv_store.size();
  130. }
  131. void foreach(KeyValueResultCallback&& callback) {
  132. kv_store.foreach(callback);
  133. }
  134. void foreach_prefix(PrefixResultCallback&& callback, query::StringViewIterator prefixes) {
  135. kv_store.foreach([&](kvs_type::KeyValueResult&& kv) {
  136. auto key = kv.key.read();
  137. for (auto it = prefixes.begin(); it != prefixes.end(); ++it) {
  138. if (query::samePrefix(StringView{key}, (*it))) {
  139. callback((*it), std::move(key), kv.value);
  140. }
  141. }
  142. });
  143. }
  144. // --------------------------------------------------------------------------
  145. // TODO: UI needs this to avoid showing keys in storage order
  146. std::vector<String> sorted_keys() {
  147. auto values = keys();
  148. std::sort(values.begin(), values.end(),
  149. [](const String& lhs, const String& rhs) -> bool {
  150. return rhs.compareTo(lhs) > 0;
  151. });
  152. return values;
  153. }
  154. #if TERMINAL_SUPPORT
  155. namespace terminal {
  156. namespace {
  157. void dump(const ::terminal::CommandContext& ctx, const query::Setting* begin, const query::Setting* end) {
  158. for (auto it = begin; it != end; ++it) {
  159. ctx.output.printf_P(PSTR("> %s => %s\n"),
  160. (*it).key().c_str(), (*it).value().c_str());
  161. }
  162. }
  163. void dump(const ::terminal::CommandContext& ctx, const query::IndexedSetting* begin, const query::IndexedSetting* end, size_t index) {
  164. for (auto it = begin; it != end; ++it) {
  165. ctx.output.printf_P(PSTR("> %s%u => %s\n"),
  166. (*it).prefix().c_str(), index,
  167. (*it).value(index).c_str());
  168. }
  169. }
  170. namespace commands {
  171. PROGMEM_STRING(Config, "CONFIG");
  172. void config(::terminal::CommandContext&& ctx) {
  173. DynamicJsonBuffer jsonBuffer(1024);
  174. JsonObject& root = jsonBuffer.createObject();
  175. settingsGetJson(root);
  176. root.prettyPrintTo(ctx.output);
  177. terminalOK(ctx);
  178. }
  179. PROGMEM_STRING(Keys, "KEYS");
  180. void keys(::terminal::CommandContext&& ctx) {
  181. const auto keys = settings::sorted_keys();
  182. String value;
  183. for (const auto& key : keys) {
  184. value = getSetting(key);
  185. ctx.output.printf_P(PSTR("> %s => \"%s\"\n"),
  186. key.c_str(), value.c_str());
  187. }
  188. const auto size = settings::size();
  189. if (size > 0) {
  190. const auto available = settings::available();
  191. ctx.output.printf_P(PSTR("Number of keys: %u\n"), keys.size());
  192. ctx.output.printf_P(PSTR("Available: %u bytes (%u%%)\n"),
  193. available, (100 * available) / size);
  194. }
  195. terminalOK(ctx);
  196. }
  197. PROGMEM_STRING(Gc, "GC");
  198. void gc(::terminal::CommandContext&& ctx) {
  199. struct KeyRef {
  200. String key;
  201. size_t length;
  202. };
  203. using KeyRefs = std::vector<KeyRef>;
  204. KeyRefs refs;
  205. kv_store.foreach([&](kvs_type::KeyValueResult&& result) {
  206. refs.push_back(
  207. KeyRef{
  208. .key = result.key.read(),
  209. .length = result.key.length(),
  210. });
  211. });
  212. auto is_ascii = [](const String& value) -> bool {
  213. for (const auto& c : value) {
  214. if (!isascii(c)) {
  215. return false;
  216. }
  217. }
  218. return true;
  219. };
  220. std::vector<const String*> broken;
  221. for (const auto& ref : refs) {
  222. if ((ref.length != ref.key.length()) || !is_ascii(ref.key)) {
  223. broken.push_back(&ref.key);
  224. }
  225. }
  226. size_t count = 0;
  227. for (const auto& key : broken) {
  228. settings::del(*key);
  229. ++count;
  230. }
  231. ctx.output.printf_P("deleted %zu keys\n", count);
  232. terminalOK(ctx);
  233. }
  234. PROGMEM_STRING(Del, "DEL");
  235. void del(::terminal::CommandContext&& ctx) {
  236. if (ctx.argv.size() < 2) {
  237. terminalError(ctx, F("del <key> [<key>...]"));
  238. return;
  239. }
  240. int result = 0;
  241. for (auto it = (ctx.argv.begin() + 1); it != ctx.argv.end(); ++it) {
  242. result += settings::del(*it);
  243. }
  244. if (result) {
  245. terminalOK(ctx);
  246. } else {
  247. terminalError(ctx, F("no keys were removed"));
  248. }
  249. }
  250. PROGMEM_STRING(Set, "SET");
  251. void set(::terminal::CommandContext&& ctx) {
  252. if (ctx.argv.size() != 3) {
  253. terminalError(ctx, F("set <key> <value>"));
  254. return;
  255. }
  256. if (settings::set(ctx.argv[1], ctx.argv[2])) {
  257. terminalOK(ctx);
  258. return;
  259. }
  260. terminalError(ctx, F("could not set the key"));
  261. }
  262. PROGMEM_STRING(Get, "GET");
  263. void get(::terminal::CommandContext&& ctx) {
  264. if (ctx.argv.size() < 2) {
  265. terminalError(ctx, F("get <key> [<key>...]"));
  266. return;
  267. }
  268. for (auto it = (ctx.argv.cbegin() + 1); it != ctx.argv.cend(); ++it) {
  269. auto result = settings::get(*it);
  270. if (!result) {
  271. const auto maybeValue = query::find(*it);
  272. if (maybeValue.length()) {
  273. ctx.output.printf_P(PSTR("> %s => %s (default)\n"),
  274. (*it).c_str(), maybeValue.c_str());
  275. } else {
  276. ctx.output.printf_P(PSTR("> %s =>\n"), (*it).c_str());
  277. }
  278. continue;
  279. }
  280. ctx.output.printf_P(PSTR("> %s => \"%s\"\n"), (*it).c_str(), result.c_str());
  281. }
  282. terminalOK(ctx);
  283. }
  284. PROGMEM_STRING(Reload, "RELOAD");
  285. void reload(::terminal::CommandContext&& ctx) {
  286. espurnaReload();
  287. terminalOK(ctx);
  288. }
  289. PROGMEM_STRING(FactoryReset, "FACTORY.RESET");
  290. void factory_reset(::terminal::CommandContext&& ctx) {
  291. factoryReset();
  292. terminalOK(ctx);
  293. }
  294. [[gnu::unused]]
  295. PROGMEM_STRING(Save, "SAVE");
  296. [[gnu::unused]]
  297. void save(::terminal::CommandContext&& ctx) {
  298. eepromCommit();
  299. terminalOK(ctx);
  300. }
  301. static constexpr ::terminal::Command List[] PROGMEM {
  302. {Config, commands::config},
  303. {Keys, commands::keys},
  304. {Gc, commands::gc},
  305. {Del, commands::del},
  306. {Set, commands::set},
  307. {Get, commands::get},
  308. {Reload, commands::reload},
  309. {FactoryReset, commands::factory_reset},
  310. #if not SETTINGS_AUTOSAVE
  311. {Save, commands::save},
  312. #endif
  313. };
  314. } // namespace commands
  315. void setup() {
  316. espurna::terminal::add(commands::List);
  317. }
  318. } // namespace
  319. } // namespace terminal
  320. #endif
  321. } // namespace settings
  322. } // namespace espurna
  323. // -----------------------------------------------------------------------------
  324. // Key-value API
  325. // -----------------------------------------------------------------------------
  326. size_t settingsSize() {
  327. return espurna::settings::size() - espurna::settings::available();
  328. }
  329. espurna::settings::Keys settingsKeys() {
  330. return espurna::settings::sorted_keys();
  331. }
  332. void settingsRegisterQueryHandler(espurna::settings::query::Handler handler) {
  333. espurna::settings::query::internal::handlers.push_front(handler);
  334. }
  335. String settingsQuery(espurna::StringView key) {
  336. return espurna::settings::query::find(key);
  337. }
  338. void moveSetting(const String& from, const String& to) {
  339. const auto result = espurna::settings::get(from);
  340. if (result) {
  341. setSetting(to, result.ref());
  342. delSetting(from);
  343. }
  344. }
  345. struct SettingsKeyPair {
  346. espurna::settings::Key from;
  347. espurna::settings::Key to;
  348. };
  349. void moveSetting(const String& from, const String& to, size_t index) {
  350. const auto keys = SettingsKeyPair{
  351. .from = {from, index},
  352. .to = {to, index}
  353. };
  354. const auto result = espurna::settings::get(keys.from.value());
  355. if (result) {
  356. setSetting(keys.to, result.ref());
  357. delSetting(keys.from);
  358. }
  359. }
  360. void moveSettings(const String& from, const String& to) {
  361. for (size_t index = 0; index < 100; ++index) {
  362. const auto keys = SettingsKeyPair{
  363. .from = {from, index},
  364. .to = {to, index},
  365. };
  366. const auto result = espurna::settings::get(keys.from.value());
  367. if (!result) {
  368. break;
  369. }
  370. setSetting(keys.to, result.ref());
  371. delSetting(keys.from);
  372. }
  373. }
  374. template
  375. bool getSetting(const espurna::settings::Key& key, bool defaultValue);
  376. template
  377. int getSetting(const espurna::settings::Key& key, int defaultValue);
  378. template
  379. long getSetting(const espurna::settings::Key& key, long defaultValue);
  380. template
  381. unsigned char getSetting(const espurna::settings::Key& key, unsigned char defaultValue);
  382. template
  383. unsigned short getSetting(const espurna::settings::Key& key, unsigned short defaultValue);
  384. template
  385. unsigned int getSetting(const espurna::settings::Key& key, unsigned int defaultValue);
  386. template
  387. unsigned long getSetting(const espurna::settings::Key& key, unsigned long defaultValue);
  388. template
  389. float getSetting(const espurna::settings::Key& key, float defaultValue);
  390. template
  391. double getSetting(const espurna::settings::Key& key, double defaultValue);
  392. String getSetting(const String& key) {
  393. return std::move(espurna::settings::get(key)).get();
  394. }
  395. String getSetting(const __FlashStringHelper* key) {
  396. return getSetting(espurna::settings::Key(key));
  397. }
  398. String getSetting(const char* key) {
  399. return getSetting(espurna::settings::Key(key));
  400. }
  401. String getSetting(const espurna::settings::Key& key) {
  402. return getSetting(key, espurna::StringView(""));
  403. }
  404. String getSetting(const espurna::settings::Key& key, const char* defaultValue) {
  405. return getSetting(key, espurna::StringView(defaultValue));
  406. }
  407. String getSetting(const espurna::settings::Key& key, const __FlashStringHelper* defaultValue) {
  408. return getSetting(key, espurna::StringView(defaultValue));
  409. }
  410. String getSetting(const espurna::settings::Key& key, const String& defaultValue) {
  411. auto result = espurna::settings::get(key.value());
  412. if (result) {
  413. return std::move(result).get();
  414. }
  415. return defaultValue;
  416. }
  417. String getSetting(const espurna::settings::Key& key, String&& defaultValue) {
  418. String out;
  419. auto result = espurna::settings::get(key.value());
  420. if (result) {
  421. out = std::move(result).get();
  422. } else {
  423. out = std::move(defaultValue);
  424. }
  425. return out;
  426. }
  427. String getSetting(const espurna::settings::Key& key, espurna::StringView defaultValue) {
  428. String out;
  429. auto result = espurna::settings::get(key.value());
  430. if (result) {
  431. out = std::move(result).get();
  432. } else {
  433. out = defaultValue.toString();
  434. }
  435. return out;
  436. }
  437. bool delSetting(const String& key) {
  438. return espurna::settings::del(key);
  439. }
  440. bool delSetting(const espurna::settings::Key& key) {
  441. return delSetting(key.value());
  442. }
  443. bool delSetting(const char* key) {
  444. return delSetting(String(key));
  445. }
  446. bool delSetting(const __FlashStringHelper* key) {
  447. return delSetting(String(key));
  448. }
  449. bool hasSetting(const String& key) {
  450. return espurna::settings::has(key);
  451. }
  452. bool hasSetting(const espurna::settings::Key& key) {
  453. return hasSetting(key.value());
  454. }
  455. bool hasSetting(const char* key) {
  456. return hasSetting(String(key));
  457. }
  458. bool hasSetting(const __FlashStringHelper* key) {
  459. return hasSetting(String(key));
  460. }
  461. void saveSettings() {
  462. #if not SETTINGS_AUTOSAVE
  463. eepromCommit();
  464. #endif
  465. }
  466. void autosaveSettings() {
  467. #if SETTINGS_AUTOSAVE
  468. eepromCommit();
  469. #endif
  470. }
  471. void resetSettings() {
  472. eepromClear();
  473. }
  474. // -----------------------------------------------------------------------------
  475. // API
  476. // -----------------------------------------------------------------------------
  477. bool settingsRestoreJson(JsonObject& data) {
  478. // Note: we try to match what /config generates, expect {"app":"ESPURNA",...}
  479. const auto& app = data[F("app")];
  480. if (!app.success() || !app.is<const char*>()) {
  481. DEBUG_MSG_P(PSTR("[SETTING] Missing 'app' key\n"));
  482. return false;
  483. }
  484. const auto* data_app = app.as<const char*>();
  485. const auto build_app = buildApp().name;
  486. if (build_app != data_app) {
  487. DEBUG_MSG_P(PSTR("[SETTING] Invalid 'app' key\n"));
  488. return false;
  489. }
  490. // .../config will add this key, but it is optional
  491. if (data[F("backup")].as<bool>()) {
  492. resetSettings();
  493. }
  494. // These three are just metadata, no need to actually store them
  495. for (auto element : data) {
  496. auto key = String(element.key);
  497. if (key.startsWith(F("app"))
  498. || key.startsWith(F("version"))
  499. || key.startsWith(F("backup")))
  500. {
  501. continue;
  502. }
  503. setSetting(std::move(key), String(element.value.as<String>()));
  504. }
  505. saveSettings();
  506. DEBUG_MSG_P(PSTR("[SETTINGS] Settings restored successfully\n"));
  507. return true;
  508. }
  509. bool settingsRestoreJson(char* json_string, size_t json_buffer_size) {
  510. // XXX: as of right now, arduinojson cannot trigger callbacks for each key individually
  511. // Manually separating kv pairs can allow to parse only a small chunk, since we know that there is only string type used (even with bools / ints). Can be problematic when parsing data that was not generated by us.
  512. // Current parsing method is limited only by keys (~sizeof(uintptr_t) bytes per key, data is not copied when string is non-const)
  513. DynamicJsonBuffer jsonBuffer(json_buffer_size);
  514. JsonObject& root = jsonBuffer.parseObject((char *) json_string);
  515. if (!root.success()) {
  516. DEBUG_MSG_P(PSTR("[SETTINGS] JSON parsing error\n"));
  517. return false;
  518. }
  519. return settingsRestoreJson(root);
  520. }
  521. void settingsGetJson(JsonObject& root) {
  522. auto keys = espurna::settings::sorted_keys();
  523. for (const auto& key : keys) {
  524. auto value = getSetting(key);
  525. root[key] = value;
  526. }
  527. }
  528. // -----------------------------------------------------------------------------
  529. // Initialization
  530. // -----------------------------------------------------------------------------
  531. #if TERMINAL_SUPPORT
  532. void settingsDump(const ::terminal::CommandContext& ctx,
  533. const espurna::settings::query::Setting* begin,
  534. const espurna::settings::query::Setting* end)
  535. {
  536. espurna::settings::terminal::dump(ctx, begin, end);
  537. }
  538. void settingsDump(const ::terminal::CommandContext& ctx,
  539. const espurna::settings::query::IndexedSetting* begin,
  540. const espurna::settings::query::IndexedSetting* end, size_t index)
  541. {
  542. espurna::settings::terminal::dump(ctx, begin, end, index);
  543. }
  544. namespace {
  545. } // namespace
  546. #endif
  547. void settingsSetup() {
  548. #if TERMINAL_SUPPORT
  549. espurna::settings::terminal::setup();
  550. #endif
  551. }