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.

764 lines
16 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
3 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
6 years ago
6 years ago
  1. /*
  2. DEBUG MODULE
  3. Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com>
  4. */
  5. #include "espurna.h"
  6. #if DEBUG_SUPPORT
  7. #include "settings.h"
  8. #include "telnet.h"
  9. #include "ntp.h"
  10. #include <limits>
  11. #include <type_traits>
  12. #include <vector>
  13. #if WEB_SUPPORT
  14. #include "web.h"
  15. #include "ws.h"
  16. #endif
  17. #if DEBUG_WEB_SUPPORT
  18. #include <ArduinoJson.h>
  19. #endif
  20. #if DEBUG_UDP_SUPPORT
  21. #include <WiFiUdp.h>
  22. #endif
  23. namespace espurna {
  24. namespace debug {
  25. namespace settings {
  26. namespace options {
  27. namespace {
  28. using espurna::settings::options::Enumeration;
  29. PROGMEM_STRING(Disabled, "off");
  30. PROGMEM_STRING(Enabled, "on");
  31. PROGMEM_STRING(SkipBoot, "skip-boot");
  32. static constexpr Enumeration<DebugLogMode> DebugLogModeOptions[] PROGMEM {
  33. {DebugLogMode::Disabled, Disabled},
  34. {DebugLogMode::Enabled, Enabled},
  35. {DebugLogMode::SkipBoot, SkipBoot},
  36. };
  37. } // namespace
  38. } // namespace options
  39. namespace keys {
  40. namespace {
  41. PROGMEM_STRING(SdkDebug, "dbgSDK");
  42. PROGMEM_STRING(Mode, "dbgLogMode");
  43. PROGMEM_STRING(Buffer, "dbgLogBuf");
  44. PROGMEM_STRING(BufferSize, "dbgLogBufSize");
  45. PROGMEM_STRING(HeartbeatMode, "dbgHbMode");
  46. PROGMEM_STRING(HeartbeatInterval, "dbgHbIntvl");
  47. } // namespace
  48. } // namespace keys
  49. } // namespace settings
  50. } // namespace debug
  51. namespace settings {
  52. namespace internal {
  53. namespace {
  54. using espurna::debug::settings::options::DebugLogModeOptions;
  55. } // namespace
  56. String serialize(::DebugLogMode value) {
  57. return serialize(DebugLogModeOptions, value);
  58. }
  59. template<>
  60. DebugLogMode convert(const String& value) {
  61. return convert(DebugLogModeOptions, value, DebugLogMode::Enabled);
  62. }
  63. } // namespace internal
  64. } // namespace settings
  65. namespace debug {
  66. namespace {
  67. struct Timestamp {
  68. Timestamp() = default;
  69. constexpr Timestamp(bool value) :
  70. _value(value)
  71. {}
  72. constexpr explicit operator bool() const {
  73. return _value;
  74. }
  75. private:
  76. bool _value { false };
  77. };
  78. } // namespace
  79. namespace build {
  80. namespace {
  81. constexpr Timestamp AddTimestamp { 1 == DEBUG_ADD_TIMESTAMP };
  82. constexpr bool coreDebug() {
  83. #if defined(DEBUG_ESP_PORT) && !defined(NDEBUG)
  84. return true;
  85. #else
  86. return false;
  87. #endif
  88. }
  89. constexpr bool sdkDebug() {
  90. return false;
  91. }
  92. constexpr DebugLogMode mode() {
  93. return DEBUG_LOG_MODE;
  94. }
  95. constexpr bool buffer() {
  96. return 1 == DEBUG_LOG_BUFFER_ENABLED;
  97. }
  98. constexpr size_t bufferSize() {
  99. return DEBUG_LOG_BUFFER_SIZE;
  100. }
  101. } // namespace
  102. } // namespace build
  103. namespace settings {
  104. namespace {
  105. [[gnu::unused]]
  106. bool sdkDebug() {
  107. return getSetting(keys::SdkDebug, build::sdkDebug());
  108. }
  109. DebugLogMode mode() {
  110. return getSetting(keys::Mode, build::mode());
  111. }
  112. bool buffer() {
  113. return getSetting(keys::Buffer, build::buffer());
  114. }
  115. size_t bufferSize() {
  116. return getSetting(keys::BufferSize, build::bufferSize());
  117. }
  118. espurna::heartbeat::Mode heartbeatMode() {
  119. return getSetting(keys::HeartbeatMode, espurna::heartbeat::currentMode());
  120. }
  121. espurna::duration::Seconds heartbeatInterval() {
  122. return getSetting(keys::HeartbeatInterval, espurna::heartbeat::currentInterval());
  123. }
  124. } // namespace
  125. } // namespace settings
  126. namespace internal {
  127. namespace {
  128. bool enabled { false };
  129. } // namespace
  130. } // namespace internal
  131. namespace {
  132. bool enabled() {
  133. return internal::enabled;
  134. }
  135. void disable() {
  136. internal::enabled = false;
  137. }
  138. void enable() {
  139. internal::enabled = true;
  140. }
  141. void delayedEnable() {
  142. disable();
  143. ::espurnaRegisterOnce(enable);
  144. }
  145. void send(const char* message, size_t len, Timestamp);
  146. void send(const char* message, size_t len) {
  147. send(message, len, build::AddTimestamp);
  148. }
  149. void formatAndSend(const char* format, va_list args) {
  150. constexpr size_t SmallStringBufferSize { 128 };
  151. char temp[SmallStringBufferSize];
  152. int len = vsnprintf_P(temp, sizeof(temp), format, args);
  153. if (len <= 0) {
  154. return;
  155. }
  156. // strlen(...) + '\0' already in temp buffer, avoid (explicit) dynamic memory when possible
  157. // (TODO: printf might still do it anyway internally?)
  158. if (static_cast<size_t>(len) < sizeof(temp)) {
  159. send(temp, len);
  160. return;
  161. }
  162. const size_t BufferSize { static_cast<size_t>(len) + 1 };
  163. auto* buffer = new (std::nothrow) char[BufferSize];
  164. if (!buffer) {
  165. return;
  166. }
  167. vsnprintf_P(buffer, BufferSize, format, args);
  168. send(buffer, len);
  169. delete[] buffer;
  170. }
  171. namespace buffer {
  172. namespace internal {
  173. bool enabled { false };
  174. std::vector<char> storage;
  175. } // namespace internal
  176. size_t size() {
  177. return internal::storage.size();
  178. }
  179. size_t capacity() {
  180. return internal::storage.capacity();
  181. }
  182. void reserve(size_t size) {
  183. internal::storage.reserve(size);
  184. }
  185. bool enabled() {
  186. return internal::enabled;
  187. }
  188. void disable() {
  189. internal::enabled = false;
  190. }
  191. void enable() {
  192. internal::enabled = true;
  193. }
  194. void enable(size_t reserved) {
  195. enable();
  196. reserve(reserved);
  197. }
  198. template <typename T>
  199. struct Handle {
  200. Handle() = delete;
  201. explicit Handle(T& lock) :
  202. _lock(lock)
  203. {
  204. _lock.lock();
  205. }
  206. ~Handle() {
  207. _lock.unlock();
  208. }
  209. explicit operator bool() const {
  210. return _lock;
  211. }
  212. private:
  213. T& _lock;
  214. };
  215. struct Lock {
  216. Lock() = default;
  217. explicit operator bool() const {
  218. return _value;
  219. }
  220. Handle<Lock> handle() {
  221. return Handle<Lock>(*this);
  222. }
  223. bool lock() {
  224. if (!_value) {
  225. _value = true;
  226. return true;
  227. }
  228. return false;
  229. }
  230. bool unlock() {
  231. if (_value) {
  232. _value = false;
  233. return true;
  234. }
  235. return false;
  236. }
  237. private:
  238. bool _value { false };
  239. };
  240. struct DebugLock {
  241. DebugLock() {
  242. if (debug::enabled()) {
  243. _changed = true;
  244. debug::disable();
  245. }
  246. }
  247. ~DebugLock() {
  248. if (_changed) {
  249. debug::enable();
  250. }
  251. }
  252. private:
  253. bool _changed { false };
  254. };
  255. // Buffer data until we encounter line break, then flush via debug method
  256. // (which is supposed to 1-to-1 copy the data, without adding the timestamp)
  257. // TODO: abstract as `PrintLine`, so this becomes generic line buffering output for terminal as well?
  258. namespace internal {
  259. std::vector<char> line;
  260. } // namespace internal
  261. void sendBytes(const uint8_t* bytes, size_t size) {
  262. static Lock lock;
  263. if (lock) {
  264. return;
  265. }
  266. if (!size || ((size > 0) && bytes[size - 1] == '\0')) {
  267. return;
  268. }
  269. auto handle = lock.handle();
  270. if (internal::line.capacity() < (size + 2)) {
  271. internal::line.reserve(internal::line.size() + size + 2);
  272. }
  273. internal::line.insert(internal::line.end(),
  274. reinterpret_cast<const char*>(bytes),
  275. reinterpret_cast<const char*>(bytes) + size);
  276. if (internal::line.end() != std::find(internal::line.begin(), internal::line.end(), '\n')) {
  277. // TODO: ws and telnet still assume this is a c-string and will try to strlen this pointer
  278. auto len = internal::line.size();
  279. internal::line.push_back('\0');
  280. DebugLock debugLock;
  281. debug::send(internal::line.data(), len, Timestamp(false));
  282. internal::line.clear();
  283. }
  284. }
  285. // Longer recording of all log data. Stops when storage is filled, requires manual flushing.
  286. void add(const char (&prefix)[10], const char* data, size_t len) {
  287. if (len > std::numeric_limits<uint16_t>::max()) {
  288. return;
  289. }
  290. size_t total { len };
  291. bool withPrefix { prefix[0] != '\0' };
  292. if (withPrefix) {
  293. total += sizeof(prefix) - 1;
  294. }
  295. if ((internal::storage.capacity() - internal::storage.size()) <= (total + 3)) {
  296. internal::enabled = false;
  297. return;
  298. }
  299. internal::storage.push_back(total >> 8);
  300. internal::storage.push_back(total & 0xff);
  301. if (withPrefix) {
  302. internal::storage.insert(internal::storage.end(), prefix, prefix + sizeof(prefix));
  303. }
  304. internal::storage.insert(internal::storage.end(), data, data + len);
  305. }
  306. void dump(Print& out) {
  307. size_t index = 0;
  308. do {
  309. if (index >= internal::storage.size()) {
  310. break;
  311. }
  312. size_t len = internal::storage[index] << 8;
  313. len = len | internal::storage[index + 1];
  314. index += 2;
  315. auto value = internal::storage[index + len];
  316. internal::storage[index + len] = '\0';
  317. out.print(internal::storage.data() + index);
  318. internal::storage[index + len] = value;
  319. index += len;
  320. } while (true);
  321. internal::storage.clear();
  322. internal::storage.shrink_to_fit();
  323. }
  324. } // namespace buffer
  325. #if DEBUG_SERIAL_SUPPORT
  326. namespace serial {
  327. using Output = void(*)(const char (&)[10], const char*, size_t);
  328. void null_output(const char (&)[10], const char*, size_t) {
  329. }
  330. namespace internal {
  331. Print* port { nullptr };
  332. Output output { null_output };
  333. } // namespace
  334. void output(const char (&prefix)[10], const char* message, size_t len) {
  335. internal::output(prefix, message, len);
  336. }
  337. void port_output(const char (&prefix)[10], const char* message, size_t len) {
  338. if (prefix[0] != '\0') {
  339. internal::port->write(&prefix[0], sizeof(prefix) - 1);
  340. }
  341. internal::port->write(message, len);
  342. }
  343. void setup() {
  344. // HardwareSerial::begin() will automatically enable this when
  345. // `#if defined(DEBUG_ESP_PORT) && !defined(NDEBUG)`
  346. // Do not interfere when that is the case
  347. const auto port = uartPort(DEBUG_SERIAL_PORT - 1);
  348. if (!port || !port->tx) {
  349. return;
  350. }
  351. // TODO: notice that SDK accepts anything as putc / printf,
  352. // but we don't really have a good reason to wrire both
  353. // this debug output and the one from SDK
  354. // (and most of the time this is need to grab boot info from a
  355. // physically connected device)
  356. if (!build::coreDebug() && settings::sdkDebug()) {
  357. switch (port->type) {
  358. case driver::uart::Type::Uart0:
  359. uart_set_debug(0);
  360. break;
  361. case driver::uart::Type::Uart1:
  362. uart_set_debug(1);
  363. break;
  364. default:
  365. break;
  366. }
  367. }
  368. internal::port = port->stream;
  369. internal::output = port_output;
  370. }
  371. } // namespace serial
  372. #endif
  373. #if DEBUG_UDP_SUPPORT
  374. namespace syslog {
  375. namespace build {
  376. IPAddress ip() {
  377. return DEBUG_UDP_IP;
  378. }
  379. constexpr uint16_t port() {
  380. return DEBUG_UDP_PORT;
  381. };
  382. constexpr bool enabled() {
  383. return port() == 514;
  384. }
  385. } // namespace build
  386. namespace internal {
  387. size_t len { 0 };
  388. char header[128] = {0};
  389. WiFiUDP udp;
  390. } // namespace
  391. // We use the syslog header as defined in RFC5424 (The Syslog Protocol), ref:
  392. // - https://tools.ietf.org/html/rfc5424
  393. // - https://github.com/xoseperez/espurna/issues/2312/
  394. void configure() {
  395. snprintf_P(
  396. internal::header, sizeof(internal::header),
  397. PSTR("<%u>1 - %.31s ESPurna - - - "), DEBUG_UDP_FAC_PRI,
  398. systemHostname().c_str());
  399. }
  400. bool output(const char* message, size_t len) {
  401. if (build::enabled() && wifiConnected()) {
  402. internal::udp.beginPacket(build::ip(), build::port());
  403. internal::udp.write(internal::header, internal::len);
  404. internal::udp.write(message, len);
  405. return internal::udp.endPacket() > 0;
  406. }
  407. return false;
  408. }
  409. } // namespace syslog
  410. #endif
  411. void send(const char* message, size_t len, Timestamp timestamp) {
  412. if (!message || !len) {
  413. return;
  414. }
  415. char prefix[10] = {0};
  416. static bool continue_timestamp = true;
  417. if (timestamp && continue_timestamp) {
  418. snprintf(prefix, sizeof(prefix), "[%06lu] ", millis() % 1000000);
  419. }
  420. continue_timestamp = static_cast<bool>(timestamp)
  421. || (message[len - 1] == '\r')
  422. || (message[len - 1] == '\n');
  423. bool pause { false };
  424. #if DEBUG_SERIAL_SUPPORT
  425. serial::output(prefix, message, len);
  426. #endif
  427. #if DEBUG_UDP_SUPPORT
  428. pause = syslog::output(message, len);
  429. #endif
  430. #if DEBUG_TELNET_SUPPORT
  431. pause = telnetDebugSend(prefix, message) || pause;
  432. #endif
  433. #if DEBUG_WEB_SUPPORT
  434. pause = wsDebugSend(prefix, message) || pause;
  435. #endif
  436. #if DEBUG_LOG_BUFFER_SUPPORT
  437. buffer::add(prefix, message, len);
  438. #endif
  439. if (pause) {
  440. optimistic_yield(1000);
  441. }
  442. }
  443. // -----------------------------------------------------------------------------
  444. #if DEBUG_WEB_SUPPORT
  445. namespace web {
  446. void onVisible(JsonObject& root) {
  447. wsPayloadModule(root, PSTR("dbg"));
  448. }
  449. } // namespace web
  450. #endif
  451. // -----------------------------------------------------------------------------
  452. bool status(espurna::heartbeat::Mask mask) {
  453. if (mask & espurna::heartbeat::Report::Uptime) {
  454. debugSend(PSTR("[MAIN] Uptime: %s\n"), prettyDuration(systemUptime()).c_str());
  455. }
  456. if (mask & espurna::heartbeat::Report::Freeheap) {
  457. const auto stats = systemHeapStats();
  458. debugSend(PSTR("[MAIN] Heap: initial %5lu available %5lu contiguous %5lu\n"),
  459. systemInitialFreeHeap(), stats.available, stats.usable);
  460. }
  461. if ((mask & espurna::heartbeat::Report::Vcc) && (ADC_MODE_VALUE == ADC_VCC)) {
  462. debugSend(PSTR("[MAIN] VCC: %lu mV\n"), ESP.getVcc());
  463. }
  464. #if NTP_SUPPORT
  465. if ((mask & espurna::heartbeat::Report::Datetime) && ntpSynced()) {
  466. debugSend(PSTR("[MAIN] Datetime: %s\n"), ntpDateTime().c_str());
  467. }
  468. #endif
  469. return true;
  470. }
  471. void configure() {
  472. #if DEBUG_LOG_BUFFER_SUPPORT
  473. if (settings::buffer()) {
  474. debug::buffer::enable(settings::bufferSize());
  475. }
  476. #endif
  477. ::systemHeartbeat(status,
  478. settings::heartbeatMode(),
  479. settings::heartbeatInterval());
  480. }
  481. void onBoot() {
  482. static_assert(
  483. std::is_same<int, std::underlying_type<DebugLogMode>::type>::value,
  484. "should be able to match DebugLogMode with int"
  485. );
  486. switch (settings::mode()) {
  487. case DebugLogMode::SkipBoot:
  488. debug::delayedEnable();
  489. break;
  490. case DebugLogMode::Disabled:
  491. debug::disable();
  492. break;
  493. case DebugLogMode::Enabled:
  494. debug::enable();
  495. break;
  496. }
  497. #if DEBUG_SERIAL_SUPPORT
  498. espurna::debug::serial::setup();
  499. #endif
  500. configure();
  501. }
  502. #if TERMINAL_SUPPORT
  503. namespace terminal {
  504. PROGMEM_STRING(DebugBuffer, "DEBUG.BUFFER");
  505. void debug_buffer(::terminal::CommandContext&& ctx) {
  506. debug::buffer::disable();
  507. if (!debug::buffer::size()) {
  508. terminalError(ctx, F("buffer is empty\n"));
  509. return;
  510. }
  511. ctx.output.printf_P(PSTR("buffer size: %u / %u bytes\n"),
  512. debug::buffer::size(), debug::buffer::capacity());
  513. debug::buffer::dump(ctx.output);
  514. terminalOK(ctx);
  515. }
  516. static constexpr ::terminal::Command commands[] PROGMEM {
  517. {DebugBuffer, debug_buffer},
  518. };
  519. void setup() {
  520. espurna::terminal::add(commands);
  521. }
  522. } // namespace terminal
  523. #endif
  524. } // namespace
  525. } // namespace debug
  526. } // namespace espurna
  527. void debugSendBytes(const uint8_t* bytes, size_t size) {
  528. espurna::debug::buffer::sendBytes(bytes, size);
  529. }
  530. #if DEBUG_LOG_BUFFER_SUPPORT
  531. bool debugLogBuffer() {
  532. return espurna::debug::buffer::enabled();
  533. }
  534. #endif
  535. void debugSend(const char* format, ...) {
  536. if (espurna::debug::enabled()) {
  537. va_list args;
  538. va_start(args, format);
  539. espurna::debug::formatAndSend(format, args);
  540. va_end(args);
  541. }
  542. }
  543. void debugConfigureBoot() {
  544. espurna::debug::onBoot();
  545. }
  546. #if WEB_SUPPORT
  547. void debugWebSetup() {
  548. wsRegister()
  549. .onVisible(espurna::debug::web::onVisible);
  550. }
  551. #endif
  552. void debugShowBanner() {
  553. #if DEBUG_SERIAL_SUPPORT
  554. if (espurna::debug::buffer::enabled()) {
  555. return;
  556. }
  557. const auto app = buildApp();
  558. DEBUG_MSG_P(PSTR("[MAIN] %s %s built %s\n"),
  559. app.name.c_str(), app.version.c_str(),
  560. app.build_time.c_str());
  561. DEBUG_MSG_P(PSTR("[MAIN] %s\n"), app.author.c_str());
  562. DEBUG_MSG_P(PSTR("[MAIN] %s\n"), app.website.c_str());
  563. DEBUG_MSG_P(PSTR("[MAIN] CPU chip ID: %s frequency: %hhuMHz\n"),
  564. systemChipId().c_str(), system_get_cpu_freq());
  565. const auto device = systemDevice();
  566. DEBUG_MSG_P(PSTR("[MAIN] Device: %s\n"),
  567. device.c_str());
  568. #endif
  569. }
  570. void debugSetup() {
  571. #if DEBUG_UDP_SUPPORT
  572. if (espurna::debug::syslog::build::enabled()) {
  573. espurna::debug::syslog::configure();
  574. espurnaRegisterReload(espurna::debug::syslog::configure);
  575. }
  576. #endif
  577. #if DEBUG_LOG_BUFFER_SUPPORT
  578. #if TERMINAL_SUPPORT
  579. espurna::debug::terminal::setup();
  580. #endif
  581. #endif
  582. }
  583. #endif // DEBUG_SUPPORT