Fork of the espurna firmware for `mhsw` switches
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.

600 lines
16 KiB

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
7 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 "settings.h"
  6. #include "terminal.h"
  7. #include <vector>
  8. #include <cstdlib>
  9. #include <ArduinoJson.h>
  10. #include "storage_eeprom.h"
  11. BrokerBind(ConfigBroker);
  12. // -----------------------------------------------------------------------------
  13. // (HACK) Embedis storage format, reverse engineered
  14. // -----------------------------------------------------------------------------
  15. unsigned long settingsSize() {
  16. unsigned pos = SPI_FLASH_SEC_SIZE - 1;
  17. while (size_t len = EEPROMr.read(pos)) {
  18. if (0xFF == len) break;
  19. pos = pos - len - 2;
  20. }
  21. return SPI_FLASH_SEC_SIZE - pos + EEPROM_DATA_END;
  22. }
  23. // --------------------------------------------------------------------------
  24. namespace settings {
  25. namespace internal {
  26. uint32_t u32fromString(const String& string, int base) {
  27. const char *ptr = string.c_str();
  28. char *value_endptr = nullptr;
  29. // invalidate the whole string when invalid chars are detected
  30. const auto value = strtoul(ptr, &value_endptr, base);
  31. if (value_endptr == ptr || value_endptr[0] != '\0') {
  32. return 0;
  33. }
  34. return value;
  35. }
  36. // --------------------------------------------------------------------------
  37. template <>
  38. float convert(const String& value) {
  39. return atof(value.c_str());
  40. }
  41. template <>
  42. double convert(const String& value) {
  43. return atof(value.c_str());
  44. }
  45. template <>
  46. int convert(const String& value) {
  47. return value.toInt();
  48. }
  49. template <>
  50. long convert(const String& value) {
  51. return value.toInt();
  52. }
  53. template <>
  54. bool convert(const String& value) {
  55. return convert<int>(value) == 1;
  56. }
  57. template <>
  58. unsigned long convert(const String& value) {
  59. if (!value.length()) {
  60. return 0;
  61. }
  62. int base = 10;
  63. if (value.length() > 2) {
  64. if (value.startsWith("0b")) {
  65. base = 2;
  66. } else if (value.startsWith("0o")) {
  67. base = 8;
  68. } else if (value.startsWith("0x")) {
  69. base = 16;
  70. }
  71. }
  72. return u32fromString((base == 10) ? value : value.substring(2), base);
  73. }
  74. template <>
  75. unsigned int convert(const String& value) {
  76. return convert<unsigned long>(value);
  77. }
  78. template <>
  79. unsigned short convert(const String& value) {
  80. return convert<unsigned long>(value);
  81. }
  82. template <>
  83. unsigned char convert(const String& value) {
  84. return convert<unsigned long>(value);
  85. }
  86. } // namespace settings::internal
  87. } // namespace settings
  88. // -----------------------------------------------------------------------------
  89. size_t settingsKeyCount() {
  90. unsigned count = 0;
  91. unsigned pos = SPI_FLASH_SEC_SIZE - 1;
  92. while (size_t len = EEPROMr.read(pos)) {
  93. if (0xFF == len) break;
  94. pos = pos - len - 2;
  95. len = EEPROMr.read(pos);
  96. pos = pos - len - 2;
  97. count ++;
  98. }
  99. return count;
  100. }
  101. String settingsKeyName(unsigned int index) {
  102. String s;
  103. unsigned count = 0;
  104. unsigned pos = SPI_FLASH_SEC_SIZE - 1;
  105. while (size_t len = EEPROMr.read(pos)) {
  106. if (0xFF == len) break;
  107. pos = pos - len - 2;
  108. if (count == index) {
  109. s.reserve(len);
  110. for (unsigned char i = 0 ; i < len; i++) {
  111. s += (char) EEPROMr.read(pos + i + 1);
  112. }
  113. break;
  114. }
  115. count++;
  116. len = EEPROMr.read(pos);
  117. pos = pos - len - 2;
  118. }
  119. return s;
  120. }
  121. /*
  122. struct SettingsKeys {
  123. struct iterator {
  124. iterator(size_t total) :
  125. total(total)
  126. {}
  127. iterator& operator++() {
  128. if (total && (current_index < (total - 1))) {
  129. ++current_index
  130. current_value = settingsKeyName(current_index);
  131. return *this;
  132. }
  133. return end();
  134. }
  135. iterator operator++(int) {
  136. iterator val = *this;
  137. ++(*this);
  138. return val;
  139. }
  140. operator String() {
  141. return (current_index < total) ? current_value : empty_value;
  142. }
  143. bool operator ==(iterator& const other) const {
  144. return (total == other.total) && (current_index == other.current_index);
  145. }
  146. bool operator !=(iterator& const other) const {
  147. return !(*this == other);
  148. }
  149. using difference_type = size_t;
  150. using value_type = size_t;
  151. using pointer = const size_t*;
  152. using reference = const size_t&;
  153. using iterator_category = std::forward_iterator_tag;
  154. const size_t total;
  155. String empty_value;
  156. String current_value;
  157. size_t current_index = 0;
  158. };
  159. iterator begin() {
  160. return iterator {total};
  161. }
  162. iterator end() {
  163. return iterator {0};
  164. }
  165. };
  166. */
  167. std::vector<String> settingsKeys() {
  168. // Get sorted list of keys
  169. std::vector<String> keys;
  170. //unsigned int size = settingsKeyCount();
  171. auto size = settingsKeyCount();
  172. for (unsigned int i=0; i<size; i++) {
  173. //String key = settingsKeyName(i);
  174. String key = settingsKeyName(i);
  175. bool inserted = false;
  176. for (unsigned char j=0; j<keys.size(); j++) {
  177. // Check if we have to insert it before the current element
  178. if (keys[j].compareTo(key) > 0) {
  179. keys.insert(keys.begin() + j, key);
  180. inserted = true;
  181. break;
  182. }
  183. }
  184. // If we could not insert it, just push it at the end
  185. if (!inserted) keys.push_back(key);
  186. }
  187. return keys;
  188. }
  189. static std::vector<settings_key_match_t> _settings_matchers;
  190. void settingsRegisterDefaults(const settings_key_match_t& matcher) {
  191. _settings_matchers.push_back(matcher);
  192. }
  193. String settingsQueryDefaults(const String& key) {
  194. for (auto& matcher : _settings_matchers) {
  195. if (matcher.match(key.c_str())) {
  196. return matcher.key(key);
  197. }
  198. }
  199. return String();
  200. }
  201. // -----------------------------------------------------------------------------
  202. // Key-value API
  203. // -----------------------------------------------------------------------------
  204. String settings_key_t::toString() const {
  205. if (index < 0) {
  206. return value;
  207. } else {
  208. return value + index;
  209. }
  210. }
  211. settings_move_key_t _moveKeys(const String& from, const String& to, unsigned char index) {
  212. return settings_move_key_t {{from, index}, {to, index}};
  213. }
  214. void moveSetting(const String& from, const String& to) {
  215. const auto value = getSetting(from);
  216. if (value.length() > 0) setSetting(to, value);
  217. delSetting(from);
  218. }
  219. void moveSetting(const String& from, const String& to, unsigned char index) {
  220. const auto keys = _moveKeys(from, to, index);
  221. const auto value = getSetting(keys.first);
  222. if (value.length() > 0) setSetting(keys.second, value);
  223. delSetting(keys.first);
  224. }
  225. void moveSettings(const String& from, const String& to) {
  226. unsigned char index = 0;
  227. while (index < 100) {
  228. const auto keys = _moveKeys(from, to, index);
  229. const auto value = getSetting(keys.first);
  230. if (value.length() == 0) break;
  231. setSetting(keys.second, value);
  232. delSetting(keys.first);
  233. ++index;
  234. }
  235. }
  236. #if 0
  237. template<typename R, settings::internal::convert_t<R> Rfunc = settings::internal::convert>
  238. R getSetting(const settings_key_t& key, R defaultValue) {
  239. String value;
  240. if (!Embedis::get(key.toString(), value)) {
  241. return defaultValue;
  242. }
  243. return Rfunc(value);
  244. }
  245. #endif
  246. template<>
  247. String getSetting(const settings_key_t& key, String defaultValue) {
  248. String value;
  249. if (!Embedis::get(key.toString(), value)) {
  250. value = defaultValue;
  251. }
  252. return value;
  253. }
  254. template
  255. bool getSetting(const settings_key_t& key, bool defaultValue);
  256. template
  257. int getSetting(const settings_key_t& key, int defaultValue);
  258. template
  259. long getSetting(const settings_key_t& key, long defaultValue);
  260. template
  261. unsigned char getSetting(const settings_key_t& key, unsigned char defaultValue);
  262. template
  263. unsigned short getSetting(const settings_key_t& key, unsigned short defaultValue);
  264. template
  265. unsigned int getSetting(const settings_key_t& key, unsigned int defaultValue);
  266. template
  267. unsigned long getSetting(const settings_key_t& key, unsigned long defaultValue);
  268. template
  269. float getSetting(const settings_key_t& key, float defaultValue);
  270. template
  271. double getSetting(const settings_key_t& key, double defaultValue);
  272. String getSetting(const settings_key_t& key) {
  273. static const String defaultValue("");
  274. return getSetting(key, defaultValue);
  275. }
  276. String getSetting(const settings_key_t& key, const char* defaultValue) {
  277. return getSetting(key, String(defaultValue));
  278. }
  279. String getSetting(const settings_key_t& key, const __FlashStringHelper* defaultValue) {
  280. return getSetting(key, String(defaultValue));
  281. }
  282. template<>
  283. bool setSetting(const settings_key_t& key, const String& value) {
  284. return Embedis::set(key.toString(), value);
  285. }
  286. bool delSetting(const settings_key_t& key) {
  287. return Embedis::del(key.toString());
  288. }
  289. bool hasSetting(const settings_key_t& key) {
  290. String value;
  291. return Embedis::get(key.toString(), value);
  292. }
  293. void saveSettings() {
  294. #if not SETTINGS_AUTOSAVE
  295. eepromCommit();
  296. #endif
  297. }
  298. void resetSettings() {
  299. for (unsigned int i = 0; i < EEPROM_SIZE; i++) {
  300. EEPROMr.write(i, 0xFF);
  301. }
  302. EEPROMr.commit();
  303. }
  304. // -----------------------------------------------------------------------------
  305. // API
  306. // -----------------------------------------------------------------------------
  307. size_t settingsMaxSize() {
  308. size_t size = EEPROM_SIZE;
  309. if (size > SPI_FLASH_SEC_SIZE) size = SPI_FLASH_SEC_SIZE;
  310. size = (size + 3) & (~3);
  311. return size;
  312. }
  313. bool settingsRestoreJson(JsonObject& data) {
  314. // Check this is an ESPurna configuration file (must have "app":"ESPURNA")
  315. const char* app = data["app"];
  316. if (!app || strcmp(app, APP_NAME) != 0) {
  317. DEBUG_MSG_P(PSTR("[SETTING] Wrong or missing 'app' key\n"));
  318. return false;
  319. }
  320. // Clear settings
  321. bool is_backup = data["backup"];
  322. if (is_backup) {
  323. for (unsigned int i = EEPROM_DATA_END; i < SPI_FLASH_SEC_SIZE; i++) {
  324. EEPROMr.write(i, 0xFF);
  325. }
  326. }
  327. // Dump settings to memory buffer
  328. for (auto element : data) {
  329. if (strcmp(element.key, "app") == 0) continue;
  330. if (strcmp(element.key, "version") == 0) continue;
  331. if (strcmp(element.key, "backup") == 0) continue;
  332. setSetting(element.key, element.value.as<char*>());
  333. }
  334. // Persist to EEPROM
  335. saveSettings();
  336. DEBUG_MSG_P(PSTR("[SETTINGS] Settings restored successfully\n"));
  337. return true;
  338. }
  339. bool settingsRestoreJson(char* json_string, size_t json_buffer_size) {
  340. // XXX: as of right now, arduinojson cannot trigger callbacks for each key individually
  341. // 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.
  342. // Current parsing method is limited only by keys (~sizeof(uintptr_t) bytes per key, data is not copied when string is non-const)
  343. DynamicJsonBuffer jsonBuffer(json_buffer_size);
  344. JsonObject& root = jsonBuffer.parseObject((char *) json_string);
  345. if (!root.success()) {
  346. DEBUG_MSG_P(PSTR("[SETTINGS] JSON parsing error\n"));
  347. return false;
  348. }
  349. return settingsRestoreJson(root);
  350. }
  351. void settingsGetJson(JsonObject& root) {
  352. // Get sorted list of keys
  353. auto keys = settingsKeys();
  354. // Add the key-values to the json object
  355. for (unsigned int i=0; i<keys.size(); i++) {
  356. String value = getSetting(keys[i]);
  357. root[keys[i]] = value;
  358. }
  359. }
  360. void settingsProcessConfig(const settings_cfg_list_t& config, settings_filter_t filter) {
  361. for (auto& entry : config) {
  362. String value = getSetting(entry.key, entry.default_value);
  363. if (filter) {
  364. value = filter(value);
  365. }
  366. if (value.equals(entry.setting)) continue;
  367. entry.setting = std::move(value);
  368. }
  369. }
  370. // -----------------------------------------------------------------------------
  371. // Initialization
  372. // -----------------------------------------------------------------------------
  373. void settingsSetup() {
  374. Embedis::dictionary( F("EEPROM"),
  375. SPI_FLASH_SEC_SIZE,
  376. [](size_t pos) -> char { return EEPROMr.read(pos); },
  377. [](size_t pos, char value) { EEPROMr.write(pos, value); },
  378. #if SETTINGS_AUTOSAVE
  379. []() { eepromCommit(); }
  380. #else
  381. []() {}
  382. #endif
  383. );
  384. terminalRegisterCommand(F("CONFIG"), [](const terminal::CommandContext& ctx) {
  385. // TODO: enough of a buffer?
  386. DynamicJsonBuffer jsonBuffer(1024);
  387. JsonObject& root = jsonBuffer.createObject();
  388. settingsGetJson(root);
  389. root.prettyPrintTo(ctx.output);
  390. terminalOK(ctx);
  391. });
  392. terminalRegisterCommand(F("KEYS"), [](const terminal::CommandContext& ctx) {
  393. // Get sorted list of keys
  394. auto keys = settingsKeys();
  395. // Write key-values
  396. ctx.output.println(F("Current settings:"));
  397. for (unsigned int i=0; i<keys.size(); i++) {
  398. const auto value = getSetting(keys[i]);
  399. ctx.output.printf("> %s => \"%s\"\n", (keys[i]).c_str(), value.c_str());
  400. }
  401. unsigned long freeEEPROM [[gnu::unused]] = SPI_FLASH_SEC_SIZE - settingsSize();
  402. ctx.output.printf("Number of keys: %u\n", keys.size());
  403. ctx.output.printf("Current EEPROM sector: %u\n", EEPROMr.current());
  404. ctx.output.printf("Free EEPROM: %lu bytes (%lu%%)\n", freeEEPROM, 100 * freeEEPROM / SPI_FLASH_SEC_SIZE);
  405. terminalOK(ctx);
  406. });
  407. terminalRegisterCommand(F("DEL"), [](const terminal::CommandContext& ctx) {
  408. if (ctx.argc != 2) {
  409. terminalError(ctx, F("del <key> [<key>...]"));
  410. return;
  411. }
  412. int result = 0;
  413. for (auto it = (ctx.argv.begin() + 1); it != ctx.argv.end(); ++it) {
  414. result += Embedis::del(*it);
  415. }
  416. if (result) {
  417. terminalOK(ctx);
  418. } else {
  419. terminalError(ctx, F("no keys were removed"));
  420. }
  421. });
  422. terminalRegisterCommand(F("SET"), [](const terminal::CommandContext& ctx) {
  423. if (ctx.argc != 3) {
  424. terminalError(ctx, F("set <key> <value>"));
  425. return;
  426. }
  427. if (Embedis::set(ctx.argv[1], ctx.argv[2])) {
  428. terminalOK(ctx);
  429. return;
  430. }
  431. terminalError(ctx, F("could not set the key"));
  432. });
  433. terminalRegisterCommand(F("GET"), [](const terminal::CommandContext& ctx) {
  434. if (ctx.argc < 2) {
  435. terminalError(ctx, F("Wrong arguments"));
  436. return;
  437. }
  438. for (auto it = (ctx.argv.begin() + 1); it != ctx.argv.end(); ++it) {
  439. const String& key = *it;
  440. String value;
  441. if (!Embedis::get(key, value)) {
  442. const auto maybeDefault = settingsQueryDefaults(key);
  443. if (maybeDefault.length()) {
  444. ctx.output.printf("> %s => %s (default)\n", key.c_str(), maybeDefault.c_str());
  445. } else {
  446. ctx.output.printf("> %s =>\n", key.c_str());
  447. }
  448. continue;
  449. }
  450. ctx.output.printf("> %s => \"%s\"\n", key.c_str(), value.c_str());
  451. }
  452. terminalOK(ctx);
  453. });
  454. terminalRegisterCommand(F("RELOAD"), [](const terminal::CommandContext&) {
  455. espurnaReload();
  456. terminalOK();
  457. });
  458. terminalRegisterCommand(F("FACTORY.RESET"), [](const terminal::CommandContext&) {
  459. resetSettings();
  460. terminalOK();
  461. });
  462. #if not SETTINGS_AUTOSAVE
  463. terminalRegisterCommand(F("SAVE"), [](const terminal::CommandContext&) {
  464. eepromCommit();
  465. terminalOK();
  466. });
  467. #endif
  468. }