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.

581 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::None;
  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 Mask defaultValue() {
  253. return (Report::Status * (HEARTBEAT_REPORT_STATUS))
  254. | (Report::Ssid * (HEARTBEAT_REPORT_SSID))
  255. | (Report::Ip * (HEARTBEAT_REPORT_IP))
  256. | (Report::Mac * (HEARTBEAT_REPORT_MAC))
  257. | (Report::Rssi * (HEARTBEAT_REPORT_RSSI))
  258. | (Report::Uptime * (HEARTBEAT_REPORT_UPTIME))
  259. | (Report::Datetime * (HEARTBEAT_REPORT_DATETIME))
  260. | (Report::Freeheap * (HEARTBEAT_REPORT_FREEHEAP))
  261. | (Report::Vcc * (HEARTBEAT_REPORT_VCC))
  262. | (Report::Relay * (HEARTBEAT_REPORT_RELAY))
  263. | (Report::Light * (HEARTBEAT_REPORT_LIGHT))
  264. | (Report::Hostname * (HEARTBEAT_REPORT_HOSTNAME))
  265. | (Report::Description * (HEARTBEAT_REPORT_DESCRIPTION))
  266. | (Report::App * (HEARTBEAT_REPORT_APP))
  267. | (Report::Version * (HEARTBEAT_REPORT_VERSION))
  268. | (Report::Board * (HEARTBEAT_REPORT_BOARD))
  269. | (Report::Loadavg * (HEARTBEAT_REPORT_LOADAVG))
  270. | (Report::Interval * (HEARTBEAT_REPORT_INTERVAL))
  271. | (Report::Range * (HEARTBEAT_REPORT_RANGE))
  272. | (Report::RemoteTemp * (HEARTBEAT_REPORT_REMOTE_TEMP))
  273. | (Report::Bssid * (HEARTBEAT_REPORT_BSSID));
  274. }
  275. Mask currentValue() {
  276. // because we start shifting from 1, we could use the
  277. // first bit as a flag to enable all of the messages
  278. auto value = getSetting("hbReport", defaultValue());
  279. if (value == 1) {
  280. value = std::numeric_limits<Mask>::max();
  281. }
  282. return value;
  283. }
  284. Mode currentMode() {
  285. return getSetting("hbMode", HEARTBEAT_MODE);
  286. }
  287. Seconds currentInterval() {
  288. return getSetting("hbInterval", HEARTBEAT_INTERVAL);
  289. }
  290. Milliseconds currentIntervalMs() {
  291. return Milliseconds(currentInterval());
  292. }
  293. Ticker timer;
  294. struct CallbackRunner {
  295. Callback callback;
  296. Mode mode;
  297. heartbeat::Milliseconds interval;
  298. heartbeat::Milliseconds last;
  299. };
  300. std::vector<CallbackRunner> runners;
  301. } // namespace heartbeat
  302. void _systemHeartbeat();
  303. void systemStopHeartbeat(heartbeat::Callback callback) {
  304. using namespace heartbeat;
  305. auto found = std::remove_if(runners.begin(), runners.end(),
  306. [&](const CallbackRunner& runner) {
  307. return callback == runner.callback;
  308. });
  309. runners.erase(found, runners.end());
  310. }
  311. void systemHeartbeat(heartbeat::Callback callback, heartbeat::Mode mode, heartbeat::Seconds interval) {
  312. if (mode == heartbeat::Mode::None) {
  313. return;
  314. }
  315. auto msec = heartbeat::Milliseconds(interval);
  316. if (!msec.count()) {
  317. return;
  318. }
  319. auto offset = heartbeat::Milliseconds(millis() - 1ul);
  320. heartbeat::runners.push_back({
  321. callback, mode,
  322. msec,
  323. offset - msec
  324. });
  325. heartbeat::timer.detach();
  326. static bool scheduled { false };
  327. if (!scheduled) {
  328. scheduled = true;
  329. schedule_function([]() {
  330. scheduled = false;
  331. _systemHeartbeat();
  332. });
  333. }
  334. }
  335. void systemHeartbeat(heartbeat::Callback callback, heartbeat::Mode mode) {
  336. systemHeartbeat(callback, mode, heartbeat::currentInterval());
  337. }
  338. void systemHeartbeat(heartbeat::Callback callback) {
  339. systemHeartbeat(callback, heartbeat::currentMode(), heartbeat::currentInterval());
  340. }
  341. heartbeat::Seconds systemHeartbeatInterval() {
  342. heartbeat::Milliseconds result(0ul);
  343. for (auto& runner : heartbeat::runners) {
  344. result = heartbeat::Milliseconds(result.count()
  345. ? std::min(result, runner.interval) : runner.interval);
  346. }
  347. return std::chrono::duration_cast<heartbeat::Seconds>(result);
  348. }
  349. void _systemHeartbeat() {
  350. using namespace heartbeat;
  351. constexpr Milliseconds BeatMin { 1000ul };
  352. constexpr Milliseconds BeatMax { BeatMin * 10 };
  353. auto next = Milliseconds(currentInterval());
  354. auto ts = Milliseconds(millis());
  355. if (runners.size()) {
  356. auto mask = currentValue();
  357. auto it = runners.begin();
  358. auto end = runners.end();
  359. while (it != end) {
  360. auto diff = ts - (*it).last;
  361. if (diff > (*it).interval) {
  362. auto result = (*it).callback(mask);
  363. if (result && ((*it).mode == Mode::Once)) {
  364. it = runners.erase(it);
  365. end = runners.end();
  366. continue;
  367. }
  368. if (result) {
  369. (*it).last = ts;
  370. } else if (diff < ((*it).interval + BeatMax)) {
  371. next = BeatMin;
  372. }
  373. next = std::min(next, (*it).interval);
  374. } else {
  375. next = std::min(next, (*it).interval - diff);
  376. }
  377. ++it;
  378. }
  379. }
  380. if (next < BeatMin) {
  381. next = BeatMin;
  382. }
  383. timer.once_ms_scheduled(next.count(), _systemHeartbeat);
  384. }
  385. void systemScheduleHeartbeat() {
  386. auto ts = heartbeat::Milliseconds(millis());
  387. for (auto& runner : heartbeat::runners) {
  388. runner.last = ts - runner.interval - heartbeat::Milliseconds(1ul);
  389. }
  390. schedule_function(_systemHeartbeat);
  391. }
  392. void _systemUpdateLoadAverage() {
  393. static unsigned long last_loadcheck = 0;
  394. static unsigned long load_counter_temp = 0;
  395. load_counter_temp++;
  396. if (millis() - last_loadcheck > LOADAVG_INTERVAL) {
  397. static unsigned long load_counter = 0;
  398. static unsigned long load_counter_max = 1;
  399. load_counter = load_counter_temp;
  400. load_counter_temp = 0;
  401. if (load_counter > load_counter_max) {
  402. load_counter_max = load_counter;
  403. }
  404. _load_average = 100u - (100u * load_counter / load_counter_max);
  405. last_loadcheck = millis();
  406. }
  407. }
  408. #if WEB_SUPPORT
  409. bool _systemWebSocketOnKeyCheck(const char * key, JsonVariant& value) {
  410. if (strncmp(key, "sys", 3) == 0) return true;
  411. if (strncmp(key, "hb", 2) == 0) return true;
  412. return false;
  413. }
  414. void _systemWebSocketOnConnected(JsonObject& root) {
  415. root["hbReport"] = heartbeat::currentValue();
  416. root["hbInterval"] = getSetting("hbInterval", HEARTBEAT_INTERVAL).count();
  417. root["hbMode"] = static_cast<uint8_t>(getSetting("hbMode", HEARTBEAT_MODE));
  418. }
  419. #endif
  420. void systemLoop() {
  421. if (checkNeedsReset()) {
  422. reset();
  423. return;
  424. }
  425. #if SYSTEM_CHECK_ENABLED
  426. _systemCheckLoop();
  427. #endif
  428. _systemUpdateLoadAverage();
  429. }
  430. void _systemSetupSpecificHardware() {
  431. #if defined(MANCAVEMADE_ESPLIVE)
  432. // The ESPLive has an ADC MUX which needs to be configured.
  433. // Default CT input (pin B, solder jumper B)
  434. pinMode(16, OUTPUT);
  435. digitalWrite(16, HIGH);
  436. #endif
  437. }
  438. unsigned long systemUptime() {
  439. static unsigned long last = 0;
  440. static unsigned char overflows = 0;
  441. if (millis() < last) {
  442. ++overflows;
  443. }
  444. last = millis();
  445. unsigned long seconds = static_cast<unsigned long>(overflows)
  446. * (std::numeric_limits<unsigned long>::max() / 1000ul) + (last / 1000ul);
  447. return seconds;
  448. }
  449. void systemSetup() {
  450. #if SPIFFS_SUPPORT
  451. SPIFFS.begin();
  452. #endif
  453. // Question system stability
  454. #if SYSTEM_CHECK_ENABLED
  455. systemCheck(false);
  456. #endif
  457. #if WEB_SUPPORT
  458. wsRegister()
  459. .onConnected(_systemWebSocketOnConnected)
  460. .onKeyCheck(_systemWebSocketOnKeyCheck);
  461. #endif
  462. _systemSetupSpecificHardware();
  463. espurnaRegisterLoop(systemLoop);
  464. schedule_function(_systemHeartbeat);
  465. }