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.

440 lines
11 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
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
providers: relays, lights and buttons refactoring (#2414) - gpio module now tracks the known providers (right now, hardware and mcp expander) - refactored relay struct to use 'Provider' implementing setup,notify,change,boot instead of just BasePin actions - refactored button module to use gpio provider instead of referencing types itself - removed dual & stm code from buttons, migrate both to relay module - added status notify and change callbacks for relayStatus (i.e. 'notify' when relay status was called, but not changed. and 'changed' when it did) - relays runtime configuration keys - relay command now shows configured relays and current & target statuses - refactor the code using relayStatus(0, blah) under LIGHT_PROVIDER check to use lightState instead - remove rfbridge code form relay module. implement through a basic state listener in the rfbridge module, depend on RELAY_SUPPORT - allow to bind rf codes to real relays - drop tuya-specific lights provider, remove tuya code from relays and lights modules - integrate tuya via relay listeners and providers, use lights custom provider - implement channel transitions for tuya. disabled by default, and transition time and step are overridden to 2000 + 100. needs to be set to some value below the total time (i.e. `total transition time / step time == number of steps`, we need to figure out a correct time that serial comms could handle) - lights custom provider (global, not per-pin) and state listeners - remove lights code from relay module. implement through providers & listeners in the lights module, depend on RELAY_SUPPORT - lights per-channel relay provider (unused atm), depend on RELAY_SUPPORT - refactored channel transition - calculate step only once, make sure time + step values are sane, generate quick transitions with very small delay (10ms hardcoded) for transitions during OFF state i.e. we no longer waste 500ms (or whatever transition time is set to) on boot doing nothing - transition time + step parameter for the lightUpdate - report mask parameter for the lightUpdate - minor fixes across the board resolve #2222
3 years ago
providers: relays, lights and buttons refactoring (#2414) - gpio module now tracks the known providers (right now, hardware and mcp expander) - refactored relay struct to use 'Provider' implementing setup,notify,change,boot instead of just BasePin actions - refactored button module to use gpio provider instead of referencing types itself - removed dual & stm code from buttons, migrate both to relay module - added status notify and change callbacks for relayStatus (i.e. 'notify' when relay status was called, but not changed. and 'changed' when it did) - relays runtime configuration keys - relay command now shows configured relays and current & target statuses - refactor the code using relayStatus(0, blah) under LIGHT_PROVIDER check to use lightState instead - remove rfbridge code form relay module. implement through a basic state listener in the rfbridge module, depend on RELAY_SUPPORT - allow to bind rf codes to real relays - drop tuya-specific lights provider, remove tuya code from relays and lights modules - integrate tuya via relay listeners and providers, use lights custom provider - implement channel transitions for tuya. disabled by default, and transition time and step are overridden to 2000 + 100. needs to be set to some value below the total time (i.e. `total transition time / step time == number of steps`, we need to figure out a correct time that serial comms could handle) - lights custom provider (global, not per-pin) and state listeners - remove lights code from relay module. implement through providers & listeners in the lights module, depend on RELAY_SUPPORT - lights per-channel relay provider (unused atm), depend on RELAY_SUPPORT - refactored channel transition - calculate step only once, make sure time + step values are sane, generate quick transitions with very small delay (10ms hardcoded) for transitions during OFF state i.e. we no longer waste 500ms (or whatever transition time is set to) on boot doing nothing - transition time + step parameter for the lightUpdate - report mask parameter for the lightUpdate - minor fixes across the board resolve #2222
3 years ago
providers: relays, lights and buttons refactoring (#2414) - gpio module now tracks the known providers (right now, hardware and mcp expander) - refactored relay struct to use 'Provider' implementing setup,notify,change,boot instead of just BasePin actions - refactored button module to use gpio provider instead of referencing types itself - removed dual & stm code from buttons, migrate both to relay module - added status notify and change callbacks for relayStatus (i.e. 'notify' when relay status was called, but not changed. and 'changed' when it did) - relays runtime configuration keys - relay command now shows configured relays and current & target statuses - refactor the code using relayStatus(0, blah) under LIGHT_PROVIDER check to use lightState instead - remove rfbridge code form relay module. implement through a basic state listener in the rfbridge module, depend on RELAY_SUPPORT - allow to bind rf codes to real relays - drop tuya-specific lights provider, remove tuya code from relays and lights modules - integrate tuya via relay listeners and providers, use lights custom provider - implement channel transitions for tuya. disabled by default, and transition time and step are overridden to 2000 + 100. needs to be set to some value below the total time (i.e. `total transition time / step time == number of steps`, we need to figure out a correct time that serial comms could handle) - lights custom provider (global, not per-pin) and state listeners - remove lights code from relay module. implement through providers & listeners in the lights module, depend on RELAY_SUPPORT - lights per-channel relay provider (unused atm), depend on RELAY_SUPPORT - refactored channel transition - calculate step only once, make sure time + step values are sane, generate quick transitions with very small delay (10ms hardcoded) for transitions during OFF state i.e. we no longer waste 500ms (or whatever transition time is set to) on boot doing nothing - transition time + step parameter for the lightUpdate - report mask parameter for the lightUpdate - minor fixes across the board resolve #2222
3 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 <limits>
  8. #include <type_traits>
  9. #include <vector>
  10. #include "settings.h"
  11. #include "telnet.h"
  12. #include "web.h"
  13. #include "ntp.h"
  14. #include "utils.h"
  15. #include "ws.h"
  16. #if DEBUG_WEB_SUPPORT
  17. #include <ArduinoJson.h>
  18. #endif
  19. #if DEBUG_UDP_SUPPORT
  20. #include <WiFiUdp.h>
  21. WiFiUDP _udp_debug;
  22. constexpr bool _udp_syslog_enabled = (514 == DEBUG_UDP_PORT);
  23. char _udp_syslog_header[64];
  24. #endif
  25. bool _debug_enabled = false;
  26. // -----------------------------------------------------------------------------
  27. // printf-like debug methods
  28. // -----------------------------------------------------------------------------
  29. constexpr int DEBUG_SEND_STRING_BUFFER_SIZE = 128;
  30. void _debugSendInternal(const char * message, bool add_timestamp = DEBUG_ADD_TIMESTAMP);
  31. // TODO: switch to newlib vsnprintf for latest Cores to support PROGMEM args
  32. void _debugSend(const char * format, va_list args) {
  33. char temp[DEBUG_SEND_STRING_BUFFER_SIZE];
  34. int len = vsnprintf(temp, sizeof(temp), format, args);
  35. // strlen(...) + '\0' already in temp buffer, avoid using malloc when possible
  36. if (len < DEBUG_SEND_STRING_BUFFER_SIZE) {
  37. _debugSendInternal(temp);
  38. return;
  39. }
  40. len += 1;
  41. auto* buffer = static_cast<char*>(malloc(len));
  42. if (!buffer) {
  43. return;
  44. }
  45. vsnprintf(buffer, len, format, args);
  46. _debugSendInternal(buffer);
  47. free(buffer);
  48. }
  49. void debugSendRaw(const char* line, bool timestamp) {
  50. if (!_debug_enabled) return;
  51. _debugSendInternal(line, timestamp);
  52. }
  53. void debugSend(const char* format, ...) {
  54. if (!_debug_enabled) return;
  55. va_list args;
  56. va_start(args, format);
  57. _debugSend(format, args);
  58. va_end(args);
  59. }
  60. void debugSend_P(const char* format_P, ...) {
  61. if (!_debug_enabled) return;
  62. char format[strlen_P(format_P) + 1];
  63. memcpy_P(format, format_P, sizeof(format));
  64. va_list args;
  65. va_start(args, format_P);
  66. _debugSend(format, args);
  67. va_end(args);
  68. }
  69. // -----------------------------------------------------------------------------
  70. // specific debug targets
  71. // -----------------------------------------------------------------------------
  72. #if DEBUG_SERIAL_SUPPORT
  73. void _debugSendSerial(const char* prefix, const char* data) {
  74. if (prefix && (prefix[0] != '\0')) {
  75. DEBUG_PORT.print(prefix);
  76. }
  77. DEBUG_PORT.print(data);
  78. }
  79. #endif // DEBUG_SERIAL_SUPPORT
  80. #if DEBUG_LOG_BUFFER_SUPPORT
  81. std::vector<char> _debug_log_buffer;
  82. bool _debug_log_buffer_enabled = false;
  83. void _debugLogBuffer(const char* prefix, const char* data) {
  84. if (!_debug_log_buffer_enabled) return;
  85. const auto prefix_len = strlen(prefix);
  86. const auto data_len = strlen(data);
  87. const auto total_len = prefix_len + data_len;
  88. if (total_len >= std::numeric_limits<uint16_t>::max()) {
  89. return;
  90. }
  91. if ((_debug_log_buffer.capacity() - _debug_log_buffer.size()) <= (total_len + 3)) {
  92. _debug_log_buffer_enabled = false;
  93. return;
  94. }
  95. _debug_log_buffer.push_back(total_len >> 8);
  96. _debug_log_buffer.push_back(total_len & 0xff);
  97. if (prefix && (prefix[0] != '\0')) {
  98. _debug_log_buffer.insert(_debug_log_buffer.end(), prefix, prefix + prefix_len);
  99. }
  100. _debug_log_buffer.insert(_debug_log_buffer.end(), data, data + data_len);
  101. }
  102. void _debugLogBufferDump() {
  103. size_t index = 0;
  104. do {
  105. if (index >= _debug_log_buffer.size()) {
  106. break;
  107. }
  108. size_t len = _debug_log_buffer[index] << 8;
  109. len = len | _debug_log_buffer[index + 1];
  110. index += 2;
  111. auto value = _debug_log_buffer[index + len];
  112. _debug_log_buffer[index + len] = '\0';
  113. _debugSendInternal(_debug_log_buffer.data() + index, false);
  114. _debug_log_buffer[index + len] = value;
  115. index += len;
  116. } while (true);
  117. _debug_log_buffer.clear();
  118. _debug_log_buffer.shrink_to_fit();
  119. }
  120. bool debugLogBuffer() {
  121. return _debug_log_buffer_enabled;
  122. }
  123. #endif // DEBUG_LOG_BUFFER_SUPPORT
  124. // -----------------------------------------------------------------------------
  125. void _debugSendInternal(const char * message, bool add_timestamp) {
  126. const size_t msg_len = strlen(message);
  127. bool pause = false;
  128. char timestamp[10] = {0};
  129. #if DEBUG_ADD_TIMESTAMP
  130. static bool continue_timestamp = true;
  131. if (add_timestamp && continue_timestamp) {
  132. snprintf(timestamp, sizeof(timestamp), "[%06lu] ", millis() % 1000000);
  133. }
  134. continue_timestamp = add_timestamp || (message[msg_len - 1] == 10) || (message[msg_len - 1] == 13);
  135. #endif
  136. #if DEBUG_SERIAL_SUPPORT
  137. _debugSendSerial(timestamp, message);
  138. #endif
  139. #if DEBUG_UDP_SUPPORT
  140. #if SYSTEM_CHECK_ENABLED
  141. if (systemCheck()) {
  142. #endif
  143. _udp_debug.beginPacket(DEBUG_UDP_IP, DEBUG_UDP_PORT);
  144. if (_udp_syslog_enabled) {
  145. _udp_debug.write(_udp_syslog_header);
  146. }
  147. _udp_debug.write(message);
  148. pause = _udp_debug.endPacket() > 0;
  149. #if SYSTEM_CHECK_ENABLED
  150. }
  151. #endif
  152. #endif
  153. #if DEBUG_TELNET_SUPPORT
  154. pause = telnetDebugSend(timestamp, message) || pause;
  155. #endif
  156. #if DEBUG_WEB_SUPPORT
  157. pause = wsDebugSend(timestamp, message) || pause;
  158. #endif
  159. #if DEBUG_LOG_BUFFER_SUPPORT
  160. _debugLogBuffer(timestamp, message);
  161. #endif
  162. if (pause) {
  163. optimistic_yield(1000);
  164. }
  165. }
  166. // -----------------------------------------------------------------------------
  167. #if DEBUG_WEB_SUPPORT
  168. void _debugWebSocketOnAction(uint32_t client_id, const char * action, JsonObject& data) {
  169. #if TERMINAL_SUPPORT
  170. if (strcmp(action, "dbgcmd") == 0) {
  171. if (!data.containsKey("command") || !data["command"].is<const char*>()) return;
  172. const char* command = data["command"];
  173. if (command && strlen(command)) {
  174. auto command = data.get<const char*>("command");
  175. terminalInject((void*) command, strlen(command));
  176. terminalInject('\n');
  177. }
  178. }
  179. #endif
  180. }
  181. void debugWebSetup() {
  182. wsRegister()
  183. .onVisible([](JsonObject& root) { root["dbgVisible"] = 1; })
  184. .onAction(_debugWebSocketOnAction);
  185. }
  186. #endif // DEBUG_WEB_SUPPOR
  187. #if DEBUG_UDP_SUPPORT
  188. // We use the syslog header as defined in RFC5424 (The Syslog Protocol), ref:
  189. // - https://tools.ietf.org/html/rfc5424
  190. // - https://github.com/xoseperez/espurna/issues/2312/
  191. void debugUdpSyslogConfigure() {
  192. snprintf_P(
  193. _udp_syslog_header, sizeof(_udp_syslog_header),
  194. PSTR("<%u>1 - %s ESPurna - - - "), DEBUG_UDP_FAC_PRI,
  195. getSetting("hostname", getIdentifier()).c_str()
  196. );
  197. }
  198. #endif // DEBUG_UDP_SUPPORT
  199. // -----------------------------------------------------------------------------
  200. void debugSetup() {
  201. #if DEBUG_SERIAL_SUPPORT
  202. DEBUG_PORT.begin(SERIAL_BAUDRATE);
  203. #endif
  204. #if DEBUG_UDP_SUPPORT
  205. if (_udp_syslog_enabled) {
  206. debugUdpSyslogConfigure();
  207. espurnaRegisterReload(debugUdpSyslogConfigure);
  208. }
  209. #endif
  210. #if TERMINAL_SUPPORT
  211. #if DEBUG_LOG_BUFFER_SUPPORT
  212. terminalRegisterCommand(F("DEBUG.BUFFER"), [](const terminal::CommandContext&) {
  213. _debug_log_buffer_enabled = false;
  214. if (!_debug_log_buffer.size()) {
  215. DEBUG_MSG_P(PSTR("[DEBUG] Buffer is empty\n"));
  216. return;
  217. }
  218. DEBUG_MSG_P(PSTR("[DEBUG] Buffer size: %u / %u bytes\n"),
  219. _debug_log_buffer.size(),
  220. _debug_log_buffer.capacity()
  221. );
  222. _debugLogBufferDump();
  223. });
  224. #endif // DEBUG_LOG_BUFFER_SUPPORT
  225. #endif // TERMINAL_SUPPORT
  226. }
  227. namespace settings {
  228. namespace internal {
  229. String serialize(DebugLogMode value) {
  230. String result;
  231. switch (value) {
  232. case DebugLogMode::Disabled:
  233. result = "0";
  234. break;
  235. case DebugLogMode::SkipBoot:
  236. result = "2";
  237. break;
  238. default:
  239. case DebugLogMode::Enabled:
  240. result = "1";
  241. break;
  242. }
  243. return result;
  244. }
  245. template<>
  246. DebugLogMode convert(const String& value) {
  247. switch (value.toInt()) {
  248. case 0:
  249. return DebugLogMode::Disabled;
  250. case 2:
  251. return DebugLogMode::SkipBoot;
  252. case 1:
  253. default:
  254. return DebugLogMode::Enabled;
  255. }
  256. }
  257. }
  258. }
  259. void debugConfigureBoot() {
  260. static_assert(
  261. std::is_same<int, std::underlying_type<DebugLogMode>::type>::value,
  262. "should be able to match DebugLogMode with int"
  263. );
  264. const auto mode = getSetting("dbgLogMode", DEBUG_LOG_MODE);
  265. switch (mode) {
  266. case DebugLogMode::SkipBoot:
  267. schedule_function([]() {
  268. _debug_enabled = true;
  269. });
  270. // fall through
  271. case DebugLogMode::Disabled:
  272. _debug_enabled = false;
  273. break;
  274. case DebugLogMode::Enabled:
  275. _debug_enabled = true;
  276. break;
  277. }
  278. debugConfigure();
  279. }
  280. bool _debugHeartbeat(heartbeat::Mask mask) {
  281. if (mask & heartbeat::Report::Uptime)
  282. DEBUG_MSG_P(PSTR("[MAIN] Uptime: %s\n"), getUptime().c_str());
  283. if (mask & heartbeat::Report::Freeheap)
  284. infoHeapStats();
  285. if ((mask & heartbeat::Report::Vcc) && (ADC_MODE_VALUE == ADC_VCC))
  286. DEBUG_MSG_P(PSTR("[MAIN] Power: %lu mV\n"), ESP.getVcc());
  287. #if NTP_SUPPORT
  288. if ((mask & heartbeat::Report::Datetime) && (ntpSynced()))
  289. DEBUG_MSG_P(PSTR("[MAIN] Time: %s\n"), ntpDateTime().c_str());
  290. #endif
  291. return true;
  292. }
  293. void debugConfigure() {
  294. // HardwareSerial::begin() will automatically enable this when
  295. // `#if defined(DEBUG_ESP_PORT) && !defined(NDEBUG)`
  296. // Core debugging also depends on various DEBUG_ESP_... being defined
  297. {
  298. #if defined(DEBUG_ESP_PORT)
  299. #if not defined(NDEBUG)
  300. constexpr bool debug_sdk = true;
  301. #endif // !defined(NDEBUG)
  302. #else
  303. constexpr bool debug_sdk = false;
  304. #endif // defined(DEBUG_ESP_PORT)
  305. DEBUG_PORT.setDebugOutput(getSetting("dbgSDK", debug_sdk));
  306. }
  307. // Make sure other modules are aware of used GPIOs
  308. #if DEBUG_SERIAL_SUPPORT
  309. #pragma GCC diagnostic push
  310. #pragma GCC diagnostic ignored "-Wpragmas"
  311. #pragma GCC diagnostic ignored "-Wtautological-compare"
  312. if (&(DEBUG_PORT) == &Serial) {
  313. gpioLock(1);
  314. gpioLock(3);
  315. } else if (&(DEBUG_PORT) == &Serial1) {
  316. gpioLock(2);
  317. }
  318. #pragma GCC diagnostic pop
  319. #endif
  320. #if DEBUG_LOG_BUFFER_SUPPORT
  321. {
  322. const auto enabled = getSetting("dbgLogBuf", 1 == DEBUG_LOG_BUFFER_ENABLED);
  323. const auto size = getSetting("dbgLogBufSize", DEBUG_LOG_BUFFER_SIZE);
  324. if (enabled) {
  325. _debug_log_buffer_enabled = true;
  326. _debug_log_buffer.reserve(size);
  327. }
  328. }
  329. #endif // DEBUG_LOG_BUFFER
  330. systemHeartbeat(_debugHeartbeat,
  331. getSetting("dbgHbMode", heartbeat::currentMode()),
  332. getSetting("dbgHbIntvl", heartbeat::currentInterval()));
  333. }
  334. #endif // DEBUG_SUPPORT