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.

590 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
  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. constexpr unsigned char _systemCheckMin() {
  132. return 0u;
  133. }
  134. constexpr unsigned char _systemCheckMax() {
  135. return SYSTEM_CHECK_MAX;
  136. }
  137. constexpr unsigned long _systemCheckTime() {
  138. return SYSTEM_CHECK_TIME;
  139. }
  140. static_assert(_systemCheckMax() > 0, "");
  141. Ticker _system_stable_timer;
  142. bool _system_stable { true };
  143. void _systemStabilityInit() {
  144. auto count = rtcmemStatus() ? systemStabilityCounter() : 1u;
  145. _system_stable = (count < _systemCheckMax());
  146. DEBUG_MSG_P(PSTR("[MAIN] System %s\n"), _system_stable ? "OK" : "UNSTABLE");
  147. _system_stable_timer.once_ms_scheduled(_systemCheckTime(), []() {
  148. DEBUG_MSG_P(PSTR("[MAIN] System stability counter %hhu / %hhu\n"),
  149. _systemCheckMin(), _systemCheckMax());
  150. systemStabilityCounter(_systemCheckMin());
  151. });
  152. auto next = count + 1u;
  153. count = next > _systemCheckMax()
  154. ? count
  155. : next;
  156. systemStabilityCounter(count);
  157. }
  158. bool systemCheck() {
  159. return _system_stable;
  160. }
  161. #endif
  162. // -----------------------------------------------------------------------------
  163. // Reset
  164. // -----------------------------------------------------------------------------
  165. Ticker _defer_reset;
  166. auto _reset_reason = CustomResetReason::None;
  167. String customResetReasonToPayload(CustomResetReason reason) {
  168. const __FlashStringHelper* ptr { nullptr };
  169. switch (reason) {
  170. case CustomResetReason::None:
  171. ptr = F("None");
  172. break;
  173. case CustomResetReason::Button:
  174. ptr = F("Hardware button");
  175. break;
  176. case CustomResetReason::Factory:
  177. ptr = F("Factory reset");
  178. break;
  179. case CustomResetReason::Hardware:
  180. ptr = F("Reboot from a Hardware request");
  181. break;
  182. case CustomResetReason::Mqtt:
  183. ptr = F("Reboot from MQTT");
  184. break;
  185. case CustomResetReason::Ota:
  186. ptr = F("Reboot after a successful OTA update");
  187. break;
  188. case CustomResetReason::Rpc:
  189. ptr = F("Reboot from a RPC action");
  190. break;
  191. case CustomResetReason::Rule:
  192. ptr = F("Reboot from an automation rule");
  193. break;
  194. case CustomResetReason::Scheduler:
  195. ptr = F("Reboot from a scheduler action");
  196. break;
  197. case CustomResetReason::Terminal:
  198. ptr = F("Reboot from a terminal command");
  199. break;
  200. case CustomResetReason::Web:
  201. ptr = F("Reboot from web interface");
  202. break;
  203. }
  204. return String(ptr);
  205. }
  206. // system_get_rst_info() result is cached by the Core init for internal use
  207. uint32_t systemResetReason() {
  208. return resetInfo.reason;
  209. }
  210. void customResetReason(CustomResetReason reason) {
  211. _reset_reason = reason;
  212. _systemRtcmemResetReason(reason);
  213. }
  214. CustomResetReason customResetReason() {
  215. bool once { true };
  216. static auto reason = CustomResetReason::None;
  217. if (once) {
  218. once = false;
  219. if (rtcmemStatus()) {
  220. reason = _systemRtcmemResetReason();
  221. }
  222. customResetReason(CustomResetReason::None);
  223. }
  224. return reason;
  225. }
  226. void reset() {
  227. ESP.restart();
  228. }
  229. bool eraseSDKConfig() {
  230. return ESP.eraseConfig();
  231. }
  232. void deferredReset(unsigned long delay, CustomResetReason reason) {
  233. _defer_reset.once_ms(delay, customResetReason, reason);
  234. }
  235. void factoryReset() {
  236. DEBUG_MSG_P(PSTR("\n\nFACTORY RESET\n\n"));
  237. resetSettings();
  238. deferredReset(100, CustomResetReason::Factory);
  239. }
  240. bool checkNeedsReset() {
  241. return _reset_reason != CustomResetReason::None;
  242. }
  243. // -----------------------------------------------------------------------------
  244. // Calculated load average as a percentage
  245. unsigned char _load_average { 0u };
  246. unsigned char systemLoadAverage() {
  247. return _load_average;
  248. }
  249. // -----------------------------------------------------------------------------
  250. namespace heartbeat {
  251. constexpr Mode defaultMode() {
  252. return HEARTBEAT_MODE;
  253. }
  254. constexpr Seconds defaultInterval() {
  255. return Seconds(HEARTBEAT_INTERVAL);
  256. }
  257. constexpr Mask defaultValue() {
  258. return (Report::Status * (HEARTBEAT_REPORT_STATUS))
  259. | (Report::Ssid * (HEARTBEAT_REPORT_SSID))
  260. | (Report::Ip * (HEARTBEAT_REPORT_IP))
  261. | (Report::Mac * (HEARTBEAT_REPORT_MAC))
  262. | (Report::Rssi * (HEARTBEAT_REPORT_RSSI))
  263. | (Report::Uptime * (HEARTBEAT_REPORT_UPTIME))
  264. | (Report::Datetime * (HEARTBEAT_REPORT_DATETIME))
  265. | (Report::Freeheap * (HEARTBEAT_REPORT_FREEHEAP))
  266. | (Report::Vcc * (HEARTBEAT_REPORT_VCC))
  267. | (Report::Relay * (HEARTBEAT_REPORT_RELAY))
  268. | (Report::Light * (HEARTBEAT_REPORT_LIGHT))
  269. | (Report::Hostname * (HEARTBEAT_REPORT_HOSTNAME))
  270. | (Report::Description * (HEARTBEAT_REPORT_DESCRIPTION))
  271. | (Report::App * (HEARTBEAT_REPORT_APP))
  272. | (Report::Version * (HEARTBEAT_REPORT_VERSION))
  273. | (Report::Board * (HEARTBEAT_REPORT_BOARD))
  274. | (Report::Loadavg * (HEARTBEAT_REPORT_LOADAVG))
  275. | (Report::Interval * (HEARTBEAT_REPORT_INTERVAL))
  276. | (Report::Range * (HEARTBEAT_REPORT_RANGE))
  277. | (Report::RemoteTemp * (HEARTBEAT_REPORT_REMOTE_TEMP))
  278. | (Report::Bssid * (HEARTBEAT_REPORT_BSSID));
  279. }
  280. Mask currentValue() {
  281. // because we start shifting from 1, we could use the
  282. // first bit as a flag to enable all of the messages
  283. auto value = getSetting("hbReport", defaultValue());
  284. if (value == 1) {
  285. value = std::numeric_limits<Mask>::max();
  286. }
  287. return value;
  288. }
  289. Mode currentMode() {
  290. return getSetting("hbMode", defaultMode());
  291. }
  292. Seconds currentInterval() {
  293. return getSetting("hbInterval", defaultInterval());
  294. }
  295. Milliseconds currentIntervalMs() {
  296. return Milliseconds(currentInterval());
  297. }
  298. Ticker timer;
  299. struct CallbackRunner {
  300. Callback callback;
  301. Mode mode;
  302. heartbeat::Milliseconds interval;
  303. heartbeat::Milliseconds last;
  304. };
  305. std::vector<CallbackRunner> runners;
  306. } // namespace heartbeat
  307. void _systemHeartbeat();
  308. void systemStopHeartbeat(heartbeat::Callback callback) {
  309. using namespace heartbeat;
  310. auto found = std::remove_if(runners.begin(), runners.end(),
  311. [&](const CallbackRunner& runner) {
  312. return callback == runner.callback;
  313. });
  314. runners.erase(found, runners.end());
  315. }
  316. void systemHeartbeat(heartbeat::Callback callback, heartbeat::Mode mode, heartbeat::Seconds interval) {
  317. if (mode == heartbeat::Mode::None) {
  318. return;
  319. }
  320. auto msec = heartbeat::Milliseconds(interval);
  321. if (!msec.count()) {
  322. return;
  323. }
  324. auto offset = heartbeat::Milliseconds(millis() - 1ul);
  325. heartbeat::runners.push_back({
  326. callback, mode,
  327. msec,
  328. offset - msec
  329. });
  330. heartbeat::timer.detach();
  331. static bool scheduled { false };
  332. if (!scheduled) {
  333. scheduled = true;
  334. schedule_function([]() {
  335. scheduled = false;
  336. _systemHeartbeat();
  337. });
  338. }
  339. }
  340. void systemHeartbeat(heartbeat::Callback callback, heartbeat::Mode mode) {
  341. systemHeartbeat(callback, mode, heartbeat::currentInterval());
  342. }
  343. void systemHeartbeat(heartbeat::Callback callback) {
  344. systemHeartbeat(callback, heartbeat::currentMode(), heartbeat::currentInterval());
  345. }
  346. heartbeat::Seconds systemHeartbeatInterval() {
  347. heartbeat::Milliseconds result(0ul);
  348. for (auto& runner : heartbeat::runners) {
  349. result = heartbeat::Milliseconds(result.count()
  350. ? std::min(result, runner.interval) : runner.interval);
  351. }
  352. return std::chrono::duration_cast<heartbeat::Seconds>(result);
  353. }
  354. void _systemHeartbeat() {
  355. using namespace heartbeat;
  356. constexpr Milliseconds BeatMin { 1000ul };
  357. constexpr Milliseconds BeatMax { BeatMin * 10 };
  358. auto next = Milliseconds(currentInterval());
  359. auto ts = Milliseconds(millis());
  360. if (runners.size()) {
  361. auto mask = currentValue();
  362. auto it = runners.begin();
  363. auto end = runners.end();
  364. while (it != end) {
  365. auto diff = ts - (*it).last;
  366. if (diff > (*it).interval) {
  367. auto result = (*it).callback(mask);
  368. if (result && ((*it).mode == Mode::Once)) {
  369. it = runners.erase(it);
  370. end = runners.end();
  371. continue;
  372. }
  373. if (result) {
  374. (*it).last = ts;
  375. } else if (diff < ((*it).interval + BeatMax)) {
  376. next = BeatMin;
  377. }
  378. next = std::min(next, (*it).interval);
  379. } else {
  380. next = std::min(next, (*it).interval - diff);
  381. }
  382. ++it;
  383. }
  384. }
  385. if (next < BeatMin) {
  386. next = BeatMin;
  387. }
  388. timer.once_ms_scheduled(next.count(), _systemHeartbeat);
  389. }
  390. void systemScheduleHeartbeat() {
  391. auto ts = heartbeat::Milliseconds(millis());
  392. for (auto& runner : heartbeat::runners) {
  393. runner.last = ts - runner.interval - heartbeat::Milliseconds(1ul);
  394. }
  395. schedule_function(_systemHeartbeat);
  396. }
  397. void _systemUpdateLoadAverage() {
  398. static unsigned long last_loadcheck = 0;
  399. static unsigned long load_counter_temp = 0;
  400. load_counter_temp++;
  401. if (millis() - last_loadcheck > LOADAVG_INTERVAL) {
  402. static unsigned long load_counter = 0;
  403. static unsigned long load_counter_max = 1;
  404. load_counter = load_counter_temp;
  405. load_counter_temp = 0;
  406. if (load_counter > load_counter_max) {
  407. load_counter_max = load_counter;
  408. }
  409. _load_average = 100u - (100u * load_counter / load_counter_max);
  410. last_loadcheck = millis();
  411. }
  412. }
  413. #if WEB_SUPPORT
  414. uint8_t _systemHeartbeatModeToId(heartbeat::Mode mode) {
  415. return static_cast<uint8_t>(mode);
  416. }
  417. bool _systemWebSocketOnKeyCheck(const char * key, JsonVariant& value) {
  418. if (strncmp(key, "sys", 3) == 0) return true;
  419. if (strncmp(key, "hb", 2) == 0) return true;
  420. return false;
  421. }
  422. void _systemWebSocketOnConnected(JsonObject& root) {
  423. root["hbReport"] = heartbeat::currentValue();
  424. root["hbInterval"] = getSetting("hbInterval", heartbeat::defaultInterval()).count();
  425. root["hbMode"] = _systemHeartbeatModeToId(getSetting("hbMode", heartbeat::defaultMode()));
  426. }
  427. #endif
  428. void systemLoop() {
  429. if (checkNeedsReset()) {
  430. reset();
  431. return;
  432. }
  433. _systemUpdateLoadAverage();
  434. }
  435. void _systemSetupSpecificHardware() {
  436. #if defined(MANCAVEMADE_ESPLIVE)
  437. // The ESPLive has an ADC MUX which needs to be configured.
  438. // Default CT input (pin B, solder jumper B)
  439. pinMode(16, OUTPUT);
  440. digitalWrite(16, HIGH);
  441. #endif
  442. }
  443. unsigned long systemUptime() {
  444. static unsigned long last = 0;
  445. static unsigned char overflows = 0;
  446. if (millis() < last) {
  447. ++overflows;
  448. }
  449. last = millis();
  450. unsigned long seconds = static_cast<unsigned long>(overflows)
  451. * (std::numeric_limits<unsigned long>::max() / 1000ul) + (last / 1000ul);
  452. return seconds;
  453. }
  454. void systemSetup() {
  455. #if SPIFFS_SUPPORT
  456. SPIFFS.begin();
  457. #endif
  458. #if SYSTEM_CHECK_ENABLED
  459. _systemStabilityInit();
  460. #endif
  461. #if WEB_SUPPORT
  462. wsRegister()
  463. .onConnected(_systemWebSocketOnConnected)
  464. .onKeyCheck(_systemWebSocketOnKeyCheck);
  465. #endif
  466. _systemSetupSpecificHardware();
  467. espurnaRegisterLoop(systemLoop);
  468. schedule_function(_systemHeartbeat);
  469. }