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.

400 lines
9.9 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
7 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
7 years ago
7 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
7 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 <limits>
  8. #include <type_traits>
  9. #include <vector>
  10. #include "settings.h"
  11. #include "telnet.h"
  12. #include "web.h"
  13. #include "ws.h"
  14. #if DEBUG_UDP_SUPPORT
  15. #include <WiFiUdp.h>
  16. WiFiUDP _udp_debug;
  17. constexpr bool _udp_syslog_enabled = (514 == DEBUG_UDP_PORT);
  18. char _udp_syslog_header[64];
  19. #endif
  20. bool _debug_enabled = false;
  21. // -----------------------------------------------------------------------------
  22. // printf-like debug methods
  23. // -----------------------------------------------------------------------------
  24. constexpr int DEBUG_SEND_STRING_BUFFER_SIZE = 128;
  25. void _debugSendInternal(const char * message, bool add_timestamp = DEBUG_ADD_TIMESTAMP);
  26. // TODO: switch to newlib vsnprintf for latest Cores to support PROGMEM args
  27. void _debugSend(const char * format, va_list args) {
  28. char temp[DEBUG_SEND_STRING_BUFFER_SIZE];
  29. int len = vsnprintf(temp, sizeof(temp), format, args);
  30. // strlen(...) + '\0' already in temp buffer, avoid using malloc when possible
  31. if (len < DEBUG_SEND_STRING_BUFFER_SIZE) {
  32. _debugSendInternal(temp);
  33. return;
  34. }
  35. len += 1;
  36. auto* buffer = static_cast<char*>(malloc(len));
  37. if (!buffer) {
  38. return;
  39. }
  40. vsnprintf(buffer, len, format, args);
  41. _debugSendInternal(buffer);
  42. free(buffer);
  43. }
  44. void debugSendRaw(const char* line, bool timestamp) {
  45. if (!_debug_enabled) return;
  46. _debugSendInternal(line, timestamp);
  47. }
  48. void debugSend(const char* format, ...) {
  49. if (!_debug_enabled) return;
  50. va_list args;
  51. va_start(args, format);
  52. _debugSend(format, args);
  53. va_end(args);
  54. }
  55. void debugSend_P(const char* format_P, ...) {
  56. if (!_debug_enabled) return;
  57. char format[strlen_P(format_P) + 1];
  58. memcpy_P(format, format_P, sizeof(format));
  59. va_list args;
  60. va_start(args, format_P);
  61. _debugSend(format, args);
  62. va_end(args);
  63. }
  64. // -----------------------------------------------------------------------------
  65. // specific debug targets
  66. // -----------------------------------------------------------------------------
  67. #if DEBUG_SERIAL_SUPPORT
  68. void _debugSendSerial(const char* prefix, const char* data) {
  69. if (prefix && (prefix[0] != '\0')) {
  70. DEBUG_PORT.print(prefix);
  71. }
  72. DEBUG_PORT.print(data);
  73. }
  74. #endif // DEBUG_SERIAL_SUPPORT
  75. #if DEBUG_LOG_BUFFER_SUPPORT
  76. std::vector<char> _debug_log_buffer;
  77. bool _debug_log_buffer_enabled = false;
  78. void _debugLogBuffer(const char* prefix, const char* data) {
  79. if (!_debug_log_buffer_enabled) return;
  80. const auto prefix_len = strlen(prefix);
  81. const auto data_len = strlen(data);
  82. const auto total_len = prefix_len + data_len;
  83. if (total_len >= std::numeric_limits<uint16_t>::max()) {
  84. return;
  85. }
  86. if ((_debug_log_buffer.capacity() - _debug_log_buffer.size()) <= (total_len + 3)) {
  87. _debug_log_buffer_enabled = false;
  88. return;
  89. }
  90. _debug_log_buffer.push_back(total_len >> 8);
  91. _debug_log_buffer.push_back(total_len & 0xff);
  92. if (prefix && (prefix[0] != '\0')) {
  93. _debug_log_buffer.insert(_debug_log_buffer.end(), prefix, prefix + prefix_len);
  94. }
  95. _debug_log_buffer.insert(_debug_log_buffer.end(), data, data + data_len);
  96. }
  97. void _debugLogBufferDump() {
  98. size_t index = 0;
  99. do {
  100. if (index >= _debug_log_buffer.size()) {
  101. break;
  102. }
  103. size_t len = _debug_log_buffer[index] << 8;
  104. len = len | _debug_log_buffer[index + 1];
  105. index += 2;
  106. auto value = _debug_log_buffer[index + len];
  107. _debug_log_buffer[index + len] = '\0';
  108. _debugSendInternal(_debug_log_buffer.data() + index, false);
  109. _debug_log_buffer[index + len] = value;
  110. index += len;
  111. } while (true);
  112. _debug_log_buffer.clear();
  113. _debug_log_buffer.shrink_to_fit();
  114. }
  115. bool debugLogBuffer() {
  116. return _debug_log_buffer_enabled;
  117. }
  118. #endif // DEBUG_LOG_BUFFER_SUPPORT
  119. // -----------------------------------------------------------------------------
  120. void _debugSendInternal(const char * message, bool add_timestamp) {
  121. const size_t msg_len = strlen(message);
  122. bool pause = false;
  123. char timestamp[10] = {0};
  124. #if DEBUG_ADD_TIMESTAMP
  125. static bool continue_timestamp = true;
  126. if (add_timestamp && continue_timestamp) {
  127. snprintf(timestamp, sizeof(timestamp), "[%06lu] ", millis() % 1000000);
  128. }
  129. continue_timestamp = add_timestamp || (message[msg_len - 1] == 10) || (message[msg_len - 1] == 13);
  130. #endif
  131. #if DEBUG_SERIAL_SUPPORT
  132. _debugSendSerial(timestamp, message);
  133. #endif
  134. #if DEBUG_UDP_SUPPORT
  135. #if SYSTEM_CHECK_ENABLED
  136. if (systemCheck()) {
  137. #endif
  138. _udp_debug.beginPacket(DEBUG_UDP_IP, DEBUG_UDP_PORT);
  139. if (_udp_syslog_enabled) {
  140. _udp_debug.write(_udp_syslog_header);
  141. }
  142. _udp_debug.write(message);
  143. pause = _udp_debug.endPacket() > 0;
  144. #if SYSTEM_CHECK_ENABLED
  145. }
  146. #endif
  147. #endif
  148. #if DEBUG_TELNET_SUPPORT
  149. pause = telnetDebugSend(timestamp, message) || pause;
  150. #endif
  151. #if DEBUG_WEB_SUPPORT
  152. pause = wsDebugSend(timestamp, message) || pause;
  153. #endif
  154. #if DEBUG_LOG_BUFFER_SUPPORT
  155. _debugLogBuffer(timestamp, message);
  156. #endif
  157. if (pause) {
  158. optimistic_yield(1000);
  159. }
  160. }
  161. // -----------------------------------------------------------------------------
  162. #if DEBUG_WEB_SUPPORT
  163. void _debugWebSocketOnAction(uint32_t client_id, const char * action, JsonObject& data) {
  164. #if TERMINAL_SUPPORT
  165. if (strcmp(action, "dbgcmd") == 0) {
  166. if (!data.containsKey("command") || !data["command"].is<const char*>()) return;
  167. const char* command = data["command"];
  168. if (command && strlen(command)) {
  169. auto command = data.get<const char*>("command");
  170. terminalInject((void*) command, strlen(command));
  171. terminalInject('\n');
  172. }
  173. }
  174. #endif
  175. }
  176. void debugWebSetup() {
  177. wsRegister()
  178. .onVisible([](JsonObject& root) { root["dbgVisible"] = 1; })
  179. .onAction(_debugWebSocketOnAction);
  180. }
  181. #endif // DEBUG_WEB_SUPPOR
  182. #if DEBUG_UDP_SUPPORT
  183. // We use the syslog header as defined in RFC5424 (The Syslog Protocol), ref:
  184. // - https://tools.ietf.org/html/rfc5424
  185. // - https://github.com/xoseperez/espurna/issues/2312/
  186. void debugUdpSyslogConfigure() {
  187. snprintf_P(
  188. _udp_syslog_header, sizeof(_udp_syslog_header),
  189. PSTR("<%u>1 - %s ESPurna - - - "), DEBUG_UDP_FAC_PRI,
  190. getSetting("hostname", getIdentifier()).c_str()
  191. );
  192. }
  193. #endif // DEBUG_UDP_SUPPORT
  194. // -----------------------------------------------------------------------------
  195. void debugSetup() {
  196. #if DEBUG_SERIAL_SUPPORT
  197. DEBUG_PORT.begin(SERIAL_BAUDRATE);
  198. #endif
  199. #if DEBUG_UDP_SUPPORT
  200. if (_udp_syslog_enabled) {
  201. debugUdpSyslogConfigure();
  202. espurnaRegisterReload(debugUdpSyslogConfigure);
  203. }
  204. #endif
  205. #if TERMINAL_SUPPORT
  206. #if DEBUG_LOG_BUFFER_SUPPORT
  207. terminalRegisterCommand(F("DEBUG.BUFFER"), [](const terminal::CommandContext&) {
  208. _debug_log_buffer_enabled = false;
  209. if (!_debug_log_buffer.size()) {
  210. DEBUG_MSG_P(PSTR("[DEBUG] Buffer is empty\n"));
  211. return;
  212. }
  213. DEBUG_MSG_P(PSTR("[DEBUG] Buffer size: %u / %u bytes\n"),
  214. _debug_log_buffer.size(),
  215. _debug_log_buffer.capacity()
  216. );
  217. _debugLogBufferDump();
  218. });
  219. #endif // DEBUG_LOG_BUFFER_SUPPORT
  220. #endif // TERMINAL_SUPPORT
  221. }
  222. namespace settings {
  223. namespace internal {
  224. template<>
  225. String serialize(const DebugLogMode& value) {
  226. String result;
  227. switch (value) {
  228. case DebugLogMode::Disabled:
  229. result = "0";
  230. break;
  231. case DebugLogMode::SkipBoot:
  232. result = "2";
  233. break;
  234. default:
  235. case DebugLogMode::Enabled:
  236. result = "1";
  237. break;
  238. }
  239. return result;
  240. }
  241. template<>
  242. DebugLogMode convert(const String& value) {
  243. switch (value.toInt()) {
  244. case 0:
  245. return DebugLogMode::Disabled;
  246. case 2:
  247. return DebugLogMode::SkipBoot;
  248. case 1:
  249. default:
  250. return DebugLogMode::Enabled;
  251. }
  252. }
  253. }
  254. }
  255. void debugConfigureBoot() {
  256. static_assert(
  257. std::is_same<int, std::underlying_type<DebugLogMode>::type>::value,
  258. "should be able to match DebugLogMode with int"
  259. );
  260. const auto mode = getSetting("dbgLogMode", DEBUG_LOG_MODE);
  261. switch (mode) {
  262. case DebugLogMode::SkipBoot:
  263. schedule_function([]() {
  264. _debug_enabled = true;
  265. });
  266. // fall through
  267. case DebugLogMode::Disabled:
  268. _debug_enabled = false;
  269. break;
  270. case DebugLogMode::Enabled:
  271. _debug_enabled = true;
  272. break;
  273. }
  274. debugConfigure();
  275. }
  276. void debugConfigure() {
  277. // HardwareSerial::begin() will automatically enable this when
  278. // `#if defined(DEBUG_ESP_PORT) && !defined(NDEBUG)`
  279. // Core debugging also depends on various DEBUG_ESP_... being defined
  280. {
  281. #if defined(DEBUG_ESP_PORT)
  282. #if not defined(NDEBUG)
  283. constexpr bool debug_sdk = true;
  284. #endif // !defined(NDEBUG)
  285. #else
  286. constexpr bool debug_sdk = false;
  287. #endif // defined(DEBUG_ESP_PORT)
  288. DEBUG_PORT.setDebugOutput(getSetting("dbgSDK", debug_sdk));
  289. }
  290. #if DEBUG_LOG_BUFFER_SUPPORT
  291. {
  292. const auto enabled = getSetting("dbgBufEnabled", 1 == DEBUG_LOG_BUFFER_ENABLED);
  293. const auto size = getSetting("dbgBufSize", DEBUG_LOG_BUFFER_SIZE);
  294. if (enabled) {
  295. _debug_log_buffer_enabled = true;
  296. _debug_log_buffer.reserve(size);
  297. }
  298. }
  299. #endif // DEBUG_LOG_BUFFER
  300. }
  301. #endif // DEBUG_SUPPORT