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.

289 lines
8.2 KiB

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
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
  1. /*
  2. HTTP(s) OTA MODULE
  3. Copyright (C) 2019 by Maxim Prokhorov <prokhorov dot max at outlook dot com>
  4. */
  5. // -----------------------------------------------------------------------------
  6. // OTA by using Core's HTTP(s) updater
  7. // -----------------------------------------------------------------------------
  8. #include "ota.h"
  9. #if OTA_CLIENT == OTA_CLIENT_HTTPUPDATE
  10. #include <memory>
  11. #include "mqtt.h"
  12. #include "system.h"
  13. #include "terminal.h"
  14. #include <ESP8266HTTPClient.h>
  15. #include <ESP8266httpUpdate.h>
  16. #include "libs/URL.h"
  17. #include "libs/TypeChecks.h"
  18. #include "libs/SecureClientHelpers.h"
  19. #if SECURE_CLIENT != SECURE_CLIENT_NONE
  20. #include <WiFiClientSecure.h>
  21. #if OTA_SECURE_CLIENT_INCLUDE_CA
  22. #include "static/ota_client_trusted_root_ca.h"
  23. #else
  24. #include "static/digicert_evroot_pem.h"
  25. #define _ota_client_trusted_root_ca _ssl_digicert_ev_root_ca
  26. #endif
  27. #endif // SECURE_CLIENT != SECURE_CLIENT_NONE
  28. // -----------------------------------------------------------------------------
  29. // Configuration templates
  30. // -----------------------------------------------------------------------------
  31. template <typename T>
  32. t_httpUpdate_return _otaClientUpdate(const std::true_type&, T& instance, WiFiClient* client, const String& url) {
  33. return instance.update(*client, url);
  34. }
  35. template <typename T>
  36. t_httpUpdate_return _otaClientUpdate(const std::false_type&, T& instance, WiFiClient*, const String& url) {
  37. return instance.update(url);
  38. }
  39. namespace ota {
  40. template <typename T>
  41. using has_WiFiClient_argument_t = decltype(std::declval<T>().update(std::declval<WiFiClient&>(), std::declval<const String&>()));
  42. template <typename T>
  43. using has_WiFiClient_argument = is_detected<has_WiFiClient_argument_t, T>;
  44. }
  45. // -----------------------------------------------------------------------------
  46. // Settings helper
  47. // -----------------------------------------------------------------------------
  48. #if SECURE_CLIENT == SECURE_CLIENT_AXTLS
  49. SecureClientConfig _ota_sc_config {
  50. "OTA",
  51. []() -> String {
  52. return String(); // NOTE: unused
  53. },
  54. []() -> int {
  55. return getSetting("otaScCheck", OTA_SECURE_CLIENT_CHECK);
  56. },
  57. []() -> String {
  58. return getSetting("otaFP", OTA_FINGERPRINT);
  59. },
  60. true
  61. };
  62. #endif
  63. #if SECURE_CLIENT == SECURE_CLIENT_BEARSSL
  64. SecureClientConfig _ota_sc_config {
  65. "OTA",
  66. []() -> int {
  67. return getSetting("otaScCheck", OTA_SECURE_CLIENT_CHECK);
  68. },
  69. []() -> PGM_P {
  70. return _ota_client_trusted_root_ca;
  71. },
  72. []() -> String {
  73. return getSetting("otaFP", OTA_FINGERPRINT);
  74. },
  75. []() -> uint16_t {
  76. return getSetting("otaScMFLN", OTA_SECURE_CLIENT_MFLN);
  77. },
  78. true
  79. };
  80. #endif
  81. // -----------------------------------------------------------------------------
  82. // Generic update methods
  83. // -----------------------------------------------------------------------------
  84. void _otaClientRunUpdater(__attribute__((unused)) WiFiClient* client, const String& url, __attribute__((unused)) const String& fp = "") {
  85. // Disabling EEPROM rotation to prevent writing to EEPROM after the upgrade
  86. eepromRotate(false);
  87. DEBUG_MSG_P(PSTR("[OTA] Downloading %s ...\n"), url.c_str());
  88. #if not defined(ARDUINO_ESP8266_RELEASE_2_3_0)
  89. ESPhttpUpdate.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
  90. #endif
  91. ESPhttpUpdate.rebootOnUpdate(false);
  92. t_httpUpdate_return result = HTTP_UPDATE_NO_UPDATES;
  93. // We expect both .update(url, "", String_fp) and .update(url) to survice until axTLS is removed from the Core
  94. #if (SECURE_CLIENT == SECURE_CLIENT_AXTLS)
  95. if (url.startsWith("https://")) {
  96. result = ESPhttpUpdate.update(url, "", fp);
  97. } else {
  98. result = ESPhttpUpdate.update(url);
  99. }
  100. #else
  101. // TODO: support currentVersion (string arg after 'url')
  102. // TODO: implement through callbacks?
  103. // see https://github.com/esp8266/Arduino/pull/6796
  104. result = _otaClientUpdate(ota::has_WiFiClient_argument<decltype(ESPhttpUpdate)>{}, ESPhttpUpdate, client, url);
  105. #endif
  106. switch (result) {
  107. case HTTP_UPDATE_FAILED:
  108. DEBUG_MSG_P(PSTR("[OTA] Update failed (error %d): %s\n"), ESPhttpUpdate.getLastError(), ESPhttpUpdate.getLastErrorString().c_str());
  109. eepromRotate(true);
  110. break;
  111. case HTTP_UPDATE_NO_UPDATES:
  112. DEBUG_MSG_P(PSTR("[OTA] No updates"));
  113. eepromRotate(true);
  114. break;
  115. case HTTP_UPDATE_OK:
  116. DEBUG_MSG_P(PSTR("[OTA] Done, restarting..."));
  117. deferredReset(500, CustomResetReason::Ota); // wait a bit more than usual
  118. break;
  119. }
  120. }
  121. void _otaClientFromHttp(const String& url) {
  122. std::unique_ptr<WiFiClient> client(nullptr);
  123. if (ota::has_WiFiClient_argument<decltype(ESPhttpUpdate)>{}) {
  124. client = std::make_unique<WiFiClient>();
  125. }
  126. _otaClientRunUpdater(client.get(), url, "");
  127. }
  128. #if SECURE_CLIENT == SECURE_CLIENT_BEARSSL
  129. void _otaClientFromHttps(const String& url) {
  130. // Check for NTP early to avoid constructing SecureClient prematurely
  131. const int check = _ota_sc_config.on_check();
  132. if (!ntpSynced() && (check == SECURE_CLIENT_CHECK_CA)) {
  133. DEBUG_MSG_P(PSTR("[OTA] Time not synced! Cannot use CA validation\n"));
  134. return;
  135. }
  136. // unique_ptr self-destructs after exiting function scope
  137. // create the client on heap to use less stack space
  138. auto client = std::make_unique<SecureClient>(_ota_sc_config);
  139. if (!client->beforeConnected()) {
  140. return;
  141. }
  142. _otaClientRunUpdater(&client->get(), url);
  143. }
  144. #endif // SECURE_CLIENT_BEARSSL
  145. #if SECURE_CLIENT == SECURE_CLIENT_AXTLS
  146. void _otaClientFromHttps(const String& url) {
  147. // Note: this being the legacy option, only supporting legacy methods on ESPHttpUpdate itself
  148. // no way to know when it is connected, so no afterConnected
  149. const int check = _ota_sc_config.on_check();
  150. const String fp_string = _ota_sc_config.on_fingerprint();
  151. if (check == SECURE_CLIENT_CHECK_FINGERPRINT) {
  152. if (!fp_string.length() || !sslCheckFingerPrint(fp_string.c_str())) {
  153. DEBUG_MSG_P(PSTR("[OTA] Wrong fingerprint\n"));
  154. return;
  155. }
  156. }
  157. _otaClientRunUpdater(nullptr, url, fp_string);
  158. }
  159. #endif // SECURE_CLIENT_AXTLS
  160. void _otaClientFrom(const String& url) {
  161. if (url.startsWith("http://")) {
  162. _otaClientFromHttp(url);
  163. return;
  164. }
  165. #if SECURE_CLIENT != SECURE_CLIENT_NONE
  166. if (url.startsWith("https://")) {
  167. _otaClientFromHttps(url);
  168. return;
  169. }
  170. #endif
  171. DEBUG_MSG_P(PSTR("[OTA] Incorrect URL specified\n"));
  172. }
  173. #if TERMINAL_SUPPORT
  174. void _otaClientInitCommands() {
  175. terminalRegisterCommand(F("OTA"), [](const terminal::CommandContext& ctx) {
  176. if (ctx.argc < 2) {
  177. terminalError(F("OTA <url>"));
  178. } else {
  179. _otaClientFrom(ctx.argv[1]);
  180. terminalOK();
  181. }
  182. });
  183. }
  184. #endif // TERMINAL_SUPPORT
  185. #if (MQTT_SUPPORT && OTA_MQTT_SUPPORT)
  186. bool _ota_do_update = false;
  187. void _otaClientMqttCallback(unsigned int type, const char * topic, const char * payload) {
  188. if (type == MQTT_CONNECT_EVENT) {
  189. mqttSubscribe(MQTT_TOPIC_OTA);
  190. }
  191. if (type == MQTT_MESSAGE_EVENT) {
  192. const String t = mqttMagnitude((char *) topic);
  193. if (!_ota_do_update && t.equals(MQTT_TOPIC_OTA)) {
  194. DEBUG_MSG_P(PSTR("[OTA] Queuing from URL: %s\n"), payload);
  195. // TODO: c++14 support is required for `[_payload = String(payload)]() { ... }`
  196. // c++11 also supports basic `std::bind(func, arg)`, but we need to reset the lock
  197. _ota_do_update = true;
  198. const String _payload(payload);
  199. schedule_function([_payload]() {
  200. _otaClientFrom(_payload);
  201. _ota_do_update = false;
  202. });
  203. }
  204. }
  205. }
  206. #endif // MQTT_SUPPORT
  207. // -----------------------------------------------------------------------------
  208. void otaClientSetup() {
  209. // Backwards compatibility
  210. moveSetting("otafp", "otaFP");
  211. #if TERMINAL_SUPPORT
  212. _otaClientInitCommands();
  213. #endif
  214. #if (MQTT_SUPPORT && OTA_MQTT_SUPPORT)
  215. mqttRegister(_otaClientMqttCallback);
  216. #endif
  217. }
  218. #endif // OTA_CLIENT == OTA_CLIENT_HTTPUPDATE