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.

383 lines
9.6 KiB

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