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.

593 lines
15 KiB

6 years ago
6 years ago
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
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
  1. /*
  2. SYSTEM MODULE
  3. Copyright (C) 2019 by Xose Pérez <xose dot perez at gmail dot com>
  4. */
  5. #include "espurna.h"
  6. #include <Ticker.h>
  7. #include <Schedule.h>
  8. #include "rtcmem.h"
  9. #include "ws.h"
  10. #include "ntp.h"
  11. #include <cstdint>
  12. #include <forward_list>
  13. #include <vector>
  14. #include "libs/TypeChecks.h"
  15. // -----------------------------------------------------------------------------
  16. // This method is called by the SDK early on boot to know where to connect the ADC
  17. int __get_adc_mode() {
  18. return (int) (ADC_MODE_VALUE);
  19. }
  20. // -----------------------------------------------------------------------------
  21. namespace settings {
  22. namespace internal {
  23. template <>
  24. heartbeat::Mode convert(const String& value) {
  25. auto len = value.length();
  26. if (len == 1) {
  27. switch (*value.c_str()) {
  28. case '0':
  29. return heartbeat::Mode::None;
  30. case '1':
  31. return heartbeat::Mode::Once;
  32. case '2':
  33. return heartbeat::Mode::Repeat;
  34. }
  35. } else if (len > 1) {
  36. if (value == F("none")) {
  37. return heartbeat::Mode::None;
  38. } else if (value == F("once")) {
  39. return heartbeat::Mode::Once;
  40. } else if (value == F("repeat")) {
  41. return heartbeat::Mode::Repeat;
  42. }
  43. }
  44. return heartbeat::Mode::Repeat;
  45. }
  46. template <>
  47. heartbeat::Seconds convert(const String& value) {
  48. return heartbeat::Seconds(convert<unsigned long>(value));
  49. }
  50. template <>
  51. heartbeat::Milliseconds convert(const String& value) {
  52. return heartbeat::Milliseconds(convert<unsigned long>(value));
  53. }
  54. } // namespace internal
  55. } // namespace settings
  56. String systemHeartbeatModeToPayload(heartbeat::Mode mode) {
  57. const __FlashStringHelper* ptr { nullptr };
  58. switch (mode) {
  59. case heartbeat::Mode::None:
  60. ptr = F("none");
  61. break;
  62. case heartbeat::Mode::Once:
  63. ptr = F("once");
  64. break;
  65. case heartbeat::Mode::Repeat:
  66. ptr = F("repeat");
  67. break;
  68. }
  69. return String(ptr);
  70. }
  71. // -----------------------------------------------------------------------------
  72. unsigned long systemFreeStack() {
  73. return ESP.getFreeContStack();
  74. }
  75. HeapStats systemHeapStats() {
  76. HeapStats stats;
  77. ESP.getHeapStats(&stats.available, &stats.usable, &stats.frag_pct);
  78. return stats;
  79. }
  80. void systemHeapStats(HeapStats& stats) {
  81. stats = systemHeapStats();
  82. }
  83. unsigned long systemFreeHeap() {
  84. return ESP.getFreeHeap();
  85. }
  86. unsigned long systemInitialFreeHeap() {
  87. static unsigned long value { 0ul };
  88. if (!value) {
  89. value = systemFreeHeap();
  90. }
  91. return value;
  92. }
  93. //--------------------------------------------------------------------------------
  94. union system_rtcmem_t {
  95. struct {
  96. uint8_t stability_counter;
  97. uint8_t reset_reason;
  98. uint16_t _reserved_;
  99. } packed;
  100. uint32_t value;
  101. };
  102. uint8_t systemStabilityCounter() {
  103. system_rtcmem_t data;
  104. data.value = Rtcmem->sys;
  105. return data.packed.stability_counter;
  106. }
  107. void systemStabilityCounter(uint8_t count) {
  108. system_rtcmem_t data;
  109. data.value = Rtcmem->sys;
  110. data.packed.stability_counter = count;
  111. Rtcmem->sys = data.value;
  112. }
  113. CustomResetReason _systemRtcmemResetReason() {
  114. system_rtcmem_t data;
  115. data.value = Rtcmem->sys;
  116. return static_cast<CustomResetReason>(data.packed.reset_reason);
  117. }
  118. void _systemRtcmemResetReason(CustomResetReason reason) {
  119. system_rtcmem_t data;
  120. data.value = Rtcmem->sys;
  121. data.packed.reset_reason = static_cast<uint8_t>(reason);
  122. Rtcmem->sys = data.value;
  123. }
  124. #if SYSTEM_CHECK_ENABLED
  125. // Call this method on boot with start=true to increase the crash counter
  126. // Call it again once the system is stable to decrease the counter
  127. // If the counter reaches SYSTEM_CHECK_MAX then the system is flagged as unstable
  128. // setting _systemOK = false;
  129. //
  130. // An unstable system will only have serial access, WiFi in AP mode and OTA
  131. bool _systemStable = true;
  132. void systemCheck(bool stable) {
  133. uint8_t value = 0;
  134. if (stable) {
  135. value = 0;
  136. DEBUG_MSG_P(PSTR("[MAIN] System OK\n"));
  137. } else {
  138. if (!rtcmemStatus()) {
  139. systemStabilityCounter(1);
  140. return;
  141. }
  142. value = systemStabilityCounter();
  143. if (++value > SYSTEM_CHECK_MAX) {
  144. _systemStable = false;
  145. value = SYSTEM_CHECK_MAX;
  146. DEBUG_MSG_P(PSTR("[MAIN] System UNSTABLE\n"));
  147. }
  148. }
  149. systemStabilityCounter(value);
  150. }
  151. bool systemCheck() {
  152. return _systemStable;
  153. }
  154. void _systemCheckLoop() {
  155. static bool checked = false;
  156. if (!checked && (millis() > SYSTEM_CHECK_TIME)) {
  157. // Flag system as stable
  158. systemCheck(true);
  159. checked = true;
  160. }
  161. }
  162. #endif
  163. // -----------------------------------------------------------------------------
  164. // Reset
  165. // -----------------------------------------------------------------------------
  166. Ticker _defer_reset;
  167. auto _reset_reason = CustomResetReason::None;
  168. String customResetReasonToPayload(CustomResetReason reason) {
  169. const __FlashStringHelper* ptr { nullptr };
  170. switch (reason) {
  171. case CustomResetReason::None:
  172. ptr = F("None");
  173. break;
  174. case CustomResetReason::Button:
  175. ptr = F("Hardware button");
  176. break;
  177. case CustomResetReason::Factory:
  178. ptr = F("Factory reset");
  179. break;
  180. case CustomResetReason::Hardware:
  181. ptr = F("Reboot from a Hardware request");
  182. break;
  183. case CustomResetReason::Mqtt:
  184. ptr = F("Reboot from MQTT");
  185. break;
  186. case CustomResetReason::Ota:
  187. ptr = F("Reboot after a successful OTA update");
  188. break;
  189. case CustomResetReason::Rpc:
  190. ptr = F("Reboot from a RPC action");
  191. break;
  192. case CustomResetReason::Rule:
  193. ptr = F("Reboot from an automation rule");
  194. break;
  195. case CustomResetReason::Scheduler:
  196. ptr = F("Reboot from a scheduler action");
  197. break;
  198. case CustomResetReason::Terminal:
  199. ptr = F("Reboot from a terminal command");
  200. break;
  201. case CustomResetReason::Web:
  202. ptr = F("Reboot from web interface");
  203. break;
  204. }
  205. return String(ptr);
  206. }
  207. // system_get_rst_info() result is cached by the Core init for internal use
  208. uint32_t systemResetReason() {
  209. return resetInfo.reason;
  210. }
  211. void customResetReason(CustomResetReason reason) {
  212. _reset_reason = reason;
  213. _systemRtcmemResetReason(reason);
  214. }
  215. CustomResetReason customResetReason() {
  216. bool once { true };
  217. static auto reason = CustomResetReason::None;
  218. if (once) {
  219. once = false;
  220. if (rtcmemStatus()) {
  221. reason = _systemRtcmemResetReason();
  222. }
  223. customResetReason(CustomResetReason::None);
  224. }
  225. return reason;
  226. }
  227. void reset() {
  228. ESP.restart();
  229. }
  230. bool eraseSDKConfig() {
  231. return ESP.eraseConfig();
  232. }
  233. void deferredReset(unsigned long delay, CustomResetReason reason) {
  234. _defer_reset.once_ms(delay, customResetReason, reason);
  235. }
  236. void factoryReset() {
  237. DEBUG_MSG_P(PSTR("\n\nFACTORY RESET\n\n"));
  238. resetSettings();
  239. deferredReset(100, CustomResetReason::Factory);
  240. }
  241. bool checkNeedsReset() {
  242. return _reset_reason != CustomResetReason::None;
  243. }
  244. // -----------------------------------------------------------------------------
  245. // Calculated load average as a percentage
  246. unsigned char _load_average { 0u };
  247. unsigned char systemLoadAverage() {
  248. return _load_average;
  249. }
  250. // -----------------------------------------------------------------------------
  251. namespace heartbeat {
  252. constexpr Mode defaultMode() {
  253. return HEARTBEAT_MODE;
  254. }
  255. constexpr Seconds defaultInterval() {
  256. return Seconds(HEARTBEAT_INTERVAL);
  257. }
  258. constexpr Mask defaultValue() {
  259. return (Report::Status * (HEARTBEAT_REPORT_STATUS))
  260. | (Report::Ssid * (HEARTBEAT_REPORT_SSID))
  261. | (Report::Ip * (HEARTBEAT_REPORT_IP))
  262. | (Report::Mac * (HEARTBEAT_REPORT_MAC))
  263. | (Report::Rssi * (HEARTBEAT_REPORT_RSSI))
  264. | (Report::Uptime * (HEARTBEAT_REPORT_UPTIME))
  265. | (Report::Datetime * (HEARTBEAT_REPORT_DATETIME))
  266. | (Report::Freeheap * (HEARTBEAT_REPORT_FREEHEAP))
  267. | (Report::Vcc * (HEARTBEAT_REPORT_VCC))
  268. | (Report::Relay * (HEARTBEAT_REPORT_RELAY))
  269. | (Report::Light * (HEARTBEAT_REPORT_LIGHT))
  270. | (Report::Hostname * (HEARTBEAT_REPORT_HOSTNAME))
  271. | (Report::Description * (HEARTBEAT_REPORT_DESCRIPTION))
  272. | (Report::App * (HEARTBEAT_REPORT_APP))
  273. | (Report::Version * (HEARTBEAT_REPORT_VERSION))
  274. | (Report::Board * (HEARTBEAT_REPORT_BOARD))
  275. | (Report::Loadavg * (HEARTBEAT_REPORT_LOADAVG))
  276. | (Report::Interval * (HEARTBEAT_REPORT_INTERVAL))
  277. | (Report::Range * (HEARTBEAT_REPORT_RANGE))
  278. | (Report::RemoteTemp * (HEARTBEAT_REPORT_REMOTE_TEMP))
  279. | (Report::Bssid * (HEARTBEAT_REPORT_BSSID));
  280. }
  281. Mask currentValue() {
  282. // because we start shifting from 1, we could use the
  283. // first bit as a flag to enable all of the messages
  284. auto value = getSetting("hbReport", defaultValue());
  285. if (value == 1) {
  286. value = std::numeric_limits<Mask>::max();
  287. }
  288. return value;
  289. }
  290. Mode currentMode() {
  291. return getSetting("hbMode", defaultMode());
  292. }
  293. Seconds currentInterval() {
  294. return getSetting("hbInterval", defaultInterval());
  295. }
  296. Milliseconds currentIntervalMs() {
  297. return Milliseconds(currentInterval());
  298. }
  299. Ticker timer;
  300. struct CallbackRunner {
  301. Callback callback;
  302. Mode mode;
  303. heartbeat::Milliseconds interval;
  304. heartbeat::Milliseconds last;
  305. };
  306. std::vector<CallbackRunner> runners;
  307. } // namespace heartbeat
  308. void _systemHeartbeat();
  309. void systemStopHeartbeat(heartbeat::Callback callback) {
  310. using namespace heartbeat;
  311. auto found = std::remove_if(runners.begin(), runners.end(),
  312. [&](const CallbackRunner& runner) {
  313. return callback == runner.callback;
  314. });
  315. runners.erase(found, runners.end());
  316. }
  317. void systemHeartbeat(heartbeat::Callback callback, heartbeat::Mode mode, heartbeat::Seconds interval) {
  318. if (mode == heartbeat::Mode::None) {
  319. return;
  320. }
  321. auto msec = heartbeat::Milliseconds(interval);
  322. if (!msec.count()) {
  323. return;
  324. }
  325. auto offset = heartbeat::Milliseconds(millis() - 1ul);
  326. heartbeat::runners.push_back({
  327. callback, mode,
  328. msec,
  329. offset - msec
  330. });
  331. heartbeat::timer.detach();
  332. static bool scheduled { false };
  333. if (!scheduled) {
  334. scheduled = true;
  335. schedule_function([]() {
  336. scheduled = false;
  337. _systemHeartbeat();
  338. });
  339. }
  340. }
  341. void systemHeartbeat(heartbeat::Callback callback, heartbeat::Mode mode) {
  342. systemHeartbeat(callback, mode, heartbeat::currentInterval());
  343. }
  344. void systemHeartbeat(heartbeat::Callback callback) {
  345. systemHeartbeat(callback, heartbeat::currentMode(), heartbeat::currentInterval());
  346. }
  347. heartbeat::Seconds systemHeartbeatInterval() {
  348. heartbeat::Milliseconds result(0ul);
  349. for (auto& runner : heartbeat::runners) {
  350. result = heartbeat::Milliseconds(result.count()
  351. ? std::min(result, runner.interval) : runner.interval);
  352. }
  353. return std::chrono::duration_cast<heartbeat::Seconds>(result);
  354. }
  355. void _systemHeartbeat() {
  356. using namespace heartbeat;
  357. constexpr Milliseconds BeatMin { 1000ul };
  358. constexpr Milliseconds BeatMax { BeatMin * 10 };
  359. auto next = Milliseconds(currentInterval());
  360. auto ts = Milliseconds(millis());
  361. if (runners.size()) {
  362. auto mask = currentValue();
  363. auto it = runners.begin();
  364. auto end = runners.end();
  365. while (it != end) {
  366. auto diff = ts - (*it).last;
  367. if (diff > (*it).interval) {
  368. auto result = (*it).callback(mask);
  369. if (result && ((*it).mode == Mode::Once)) {
  370. it = runners.erase(it);
  371. end = runners.end();
  372. continue;
  373. }
  374. if (result) {
  375. (*it).last = ts;
  376. } else if (diff < ((*it).interval + BeatMax)) {
  377. next = BeatMin;
  378. }
  379. next = std::min(next, (*it).interval);
  380. } else {
  381. next = std::min(next, (*it).interval - diff);
  382. }
  383. ++it;
  384. }
  385. }
  386. if (next < BeatMin) {
  387. next = BeatMin;
  388. }
  389. timer.once_ms_scheduled(next.count(), _systemHeartbeat);
  390. }
  391. void systemScheduleHeartbeat() {
  392. auto ts = heartbeat::Milliseconds(millis());
  393. for (auto& runner : heartbeat::runners) {
  394. runner.last = ts - runner.interval - heartbeat::Milliseconds(1ul);
  395. }
  396. schedule_function(_systemHeartbeat);
  397. }
  398. void _systemUpdateLoadAverage() {
  399. static unsigned long last_loadcheck = 0;
  400. static unsigned long load_counter_temp = 0;
  401. load_counter_temp++;
  402. if (millis() - last_loadcheck > LOADAVG_INTERVAL) {
  403. static unsigned long load_counter = 0;
  404. static unsigned long load_counter_max = 1;
  405. load_counter = load_counter_temp;
  406. load_counter_temp = 0;
  407. if (load_counter > load_counter_max) {
  408. load_counter_max = load_counter;
  409. }
  410. _load_average = 100u - (100u * load_counter / load_counter_max);
  411. last_loadcheck = millis();
  412. }
  413. }
  414. #if WEB_SUPPORT
  415. uint8_t _systemHeartbeatModeToId(heartbeat::Mode mode) {
  416. return static_cast<uint8_t>(mode);
  417. }
  418. bool _systemWebSocketOnKeyCheck(const char * key, JsonVariant& value) {
  419. if (strncmp(key, "sys", 3) == 0) return true;
  420. if (strncmp(key, "hb", 2) == 0) return true;
  421. return false;
  422. }
  423. void _systemWebSocketOnConnected(JsonObject& root) {
  424. root["hbReport"] = heartbeat::currentValue();
  425. root["hbInterval"] = getSetting("hbInterval", heartbeat::defaultInterval()).count();
  426. root["hbMode"] = _systemHeartbeatModeToId(getSetting("hbMode", heartbeat::defaultMode()));
  427. }
  428. #endif
  429. void systemLoop() {
  430. if (checkNeedsReset()) {
  431. reset();
  432. return;
  433. }
  434. #if SYSTEM_CHECK_ENABLED
  435. _systemCheckLoop();
  436. #endif
  437. _systemUpdateLoadAverage();
  438. }
  439. void _systemSetupSpecificHardware() {
  440. #if defined(MANCAVEMADE_ESPLIVE)
  441. // The ESPLive has an ADC MUX which needs to be configured.
  442. // Default CT input (pin B, solder jumper B)
  443. pinMode(16, OUTPUT);
  444. digitalWrite(16, HIGH);
  445. #endif
  446. }
  447. unsigned long systemUptime() {
  448. static unsigned long last = 0;
  449. static unsigned char overflows = 0;
  450. if (millis() < last) {
  451. ++overflows;
  452. }
  453. last = millis();
  454. unsigned long seconds = static_cast<unsigned long>(overflows)
  455. * (std::numeric_limits<unsigned long>::max() / 1000ul) + (last / 1000ul);
  456. return seconds;
  457. }
  458. void systemSetup() {
  459. #if SPIFFS_SUPPORT
  460. SPIFFS.begin();
  461. #endif
  462. // Question system stability
  463. #if SYSTEM_CHECK_ENABLED
  464. systemCheck(false);
  465. #endif
  466. #if WEB_SUPPORT
  467. wsRegister()
  468. .onConnected(_systemWebSocketOnConnected)
  469. .onKeyCheck(_systemWebSocketOnKeyCheck);
  470. #endif
  471. _systemSetupSpecificHardware();
  472. espurnaRegisterLoop(systemLoop);
  473. schedule_function(_systemHeartbeat);
  474. }