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.

275 lines
7.1 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. ASYNC CLIENT OTA MODULE
  3. Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com>
  4. */
  5. #include "ota.h"
  6. #if OTA_CLIENT == OTA_CLIENT_ASYNCTCP
  7. // -----------------------------------------------------------------------------
  8. // Terminal and MQTT OTA command handlers
  9. // -----------------------------------------------------------------------------
  10. #include <Arduino.h>
  11. #include "espurna.h"
  12. #if TERMINAL_SUPPORT || OTA_MQTT_SUPPORT
  13. #include <Schedule.h>
  14. #include "mqtt.h"
  15. #include "system.h"
  16. #include "settings.h"
  17. #include "terminal.h"
  18. #include "libs/URL.h"
  19. #include <Updater.h>
  20. #include <ESPAsyncTCP.h>
  21. const char OTA_REQUEST_TEMPLATE[] PROGMEM =
  22. "GET %s HTTP/1.1\r\n"
  23. "Host: %s\r\n"
  24. "User-Agent: ESPurna\r\n"
  25. "Connection: close\r\n\r\n";
  26. struct ota_client_t {
  27. enum state_t {
  28. HEADERS,
  29. DATA,
  30. END
  31. };
  32. ota_client_t() = delete;
  33. ota_client_t(const ota_client_t&) = delete;
  34. ota_client_t(URL&& url);
  35. bool connect();
  36. state_t state = HEADERS;
  37. size_t size = 0;
  38. const URL url;
  39. AsyncClient client;
  40. };
  41. std::unique_ptr<ota_client_t> _ota_client = nullptr;
  42. // -----------------------------------------------------------------------------
  43. void _otaClientDisconnect() {
  44. DEBUG_MSG_P(PSTR("[OTA] Disconnected\n"));
  45. _ota_client = nullptr;
  46. }
  47. void _otaClientOnDisconnect(void* arg, AsyncClient* client) {
  48. DEBUG_MSG_P(PSTR("\n"));
  49. otaFinalize(reinterpret_cast<ota_client_t*>(arg)->size, CUSTOM_RESET_OTA, true);
  50. schedule_function(_otaClientDisconnect);
  51. }
  52. void _otaClientOnTimeout(void*, AsyncClient * client, uint32_t) {
  53. client->close(true);
  54. }
  55. void _otaClientOnError(void*, AsyncClient* client, err_t error) {
  56. DEBUG_MSG_P(PSTR("[OTA] ERROR: %s\n"), client->errorToString(error));
  57. }
  58. void _otaClientOnData(void* arg, AsyncClient* client, void* data, size_t len) {
  59. ota_client_t* ota_client = reinterpret_cast<ota_client_t*>(arg);
  60. auto* ptr = (char *) data;
  61. // TODO: check status
  62. // TODO: check for 3xx, discover `Location:` header and schedule
  63. // another _otaClientFrom(location_header_url)
  64. if (_ota_client->state == ota_client_t::HEADERS) {
  65. ptr = (char *) strnstr((char *) data, "\r\n\r\n", len);
  66. if (!ptr) {
  67. return;
  68. }
  69. auto diff = ptr - ((char *) data);
  70. _ota_client->state = ota_client_t::DATA;
  71. len -= diff + 4;
  72. if (!len) {
  73. return;
  74. }
  75. ptr += 4;
  76. }
  77. if (ota_client->state == ota_client_t::DATA) {
  78. if (!ota_client->size) {
  79. // Check header before anything is written to the flash
  80. if (!otaVerifyHeader((uint8_t *) ptr, len)) {
  81. DEBUG_MSG_P(PSTR("[OTA] ERROR: No magic byte / invalid flash config"));
  82. client->close(true);
  83. ota_client->state = ota_client_t::END;
  84. return;
  85. }
  86. // XXX: In case of non-chunked response, really parse headers and specify size via content-length value
  87. Update.runAsync(true);
  88. if (!Update.begin((ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000)) {
  89. otaPrintError();
  90. client->close(true);
  91. return;
  92. }
  93. }
  94. // We can enter this callback even after client->close()
  95. if (!Update.isRunning()) {
  96. return;
  97. }
  98. if (Update.write((uint8_t *) ptr, len) != len) {
  99. otaPrintError();
  100. client->close(true);
  101. ota_client->state = ota_client_t::END;
  102. return;
  103. }
  104. ota_client->size += len;
  105. otaProgress(ota_client->size);
  106. delay(0);
  107. }
  108. }
  109. void _otaClientOnConnect(void* arg, AsyncClient* client) {
  110. ota_client_t* ota_client = reinterpret_cast<ota_client_t*>(arg);
  111. #if ASYNC_TCP_SSL_ENABLED
  112. const auto check = getSetting("otaScCheck", OTA_SECURE_CLIENT_CHECK);
  113. if ((check == SECURE_CLIENT_CHECK_FINGERPRINT) && (443 == ota_client->url.port)) {
  114. uint8_t fp[20] = {0};
  115. sslFingerPrintArray(getSetting("otaFP", OTA_FINGERPRINT).c_str(), fp);
  116. SSL * ssl = client->getSSL();
  117. if (ssl_match_fingerprint(ssl, fp) != SSL_OK) {
  118. DEBUG_MSG_P(PSTR("[OTA] Warning: certificate fingerpint doesn't match\n"));
  119. client->close(true);
  120. return;
  121. }
  122. }
  123. #endif
  124. // Disabling EEPROM rotation to prevent writing to EEPROM after the upgrade
  125. eepromRotate(false);
  126. DEBUG_MSG_P(PSTR("[OTA] Downloading %s\n"), ota_client->url.path.c_str());
  127. char buffer[strlen_P(OTA_REQUEST_TEMPLATE) + ota_client->url.path.length() + ota_client->url.host.length()];
  128. snprintf_P(buffer, sizeof(buffer), OTA_REQUEST_TEMPLATE, ota_client->url.path.c_str(), ota_client->url.host.c_str());
  129. client->write(buffer);
  130. }
  131. ota_client_t::ota_client_t(URL&& url) :
  132. url(std::move(url))
  133. {
  134. client.setRxTimeout(5);
  135. client.onError(_otaClientOnError, nullptr);
  136. client.onTimeout(_otaClientOnTimeout, nullptr);
  137. client.onDisconnect(_otaClientOnDisconnect, this);
  138. client.onData(_otaClientOnData, this);
  139. client.onConnect(_otaClientOnConnect, this);
  140. }
  141. bool ota_client_t::connect() {
  142. #if ASYNC_TCP_SSL_ENABLED
  143. return client.connect(url.host.c_str(), url.port, 443 == url.port);
  144. #else
  145. return client.connect(url.host.c_str(), url.port);
  146. #endif
  147. }
  148. // -----------------------------------------------------------------------------
  149. void _otaClientFrom(const String& url) {
  150. if (_ota_client) {
  151. DEBUG_MSG_P(PSTR("[OTA] Already connected\n"));
  152. return;
  153. }
  154. URL _url(url);
  155. if (!_url.protocol.equals("http") && !_url.protocol.equals("https")) {
  156. DEBUG_MSG_P(PSTR("[OTA] Incorrect URL specified\n"));
  157. return;
  158. }
  159. _ota_client = std::make_unique<ota_client_t>(std::move(_url));
  160. if (!_ota_client->connect()) {
  161. DEBUG_MSG_P(PSTR("[OTA] Connection failed\n"));
  162. }
  163. }
  164. #endif // TERMINAL_SUPPORT || OTA_MQTT_SUPPORT
  165. #if TERMINAL_SUPPORT
  166. void _otaClientInitCommands() {
  167. terminalRegisterCommand(F("OTA"), [](const terminal::CommandContext& ctx) {
  168. if (ctx.argc < 2) {
  169. terminalError(F("OTA <url>"));
  170. } else {
  171. _otaClientFrom(ctx.argv[1]);
  172. terminalOK();
  173. }
  174. });
  175. }
  176. #endif // TERMINAL_SUPPORT
  177. #if OTA_MQTT_SUPPORT
  178. void _otaClientMqttCallback(unsigned int type, const char * topic, const char * payload) {
  179. if (type == MQTT_CONNECT_EVENT) {
  180. mqttSubscribe(MQTT_TOPIC_OTA);
  181. }
  182. if (type == MQTT_MESSAGE_EVENT) {
  183. String t = mqttMagnitude((char *) topic);
  184. if (t.equals(MQTT_TOPIC_OTA)) {
  185. DEBUG_MSG_P(PSTR("[OTA] Initiating from URL: %s\n"), payload);
  186. _otaClientFrom(payload);
  187. }
  188. }
  189. }
  190. #endif // OTA_MQTT_SUPPORT
  191. // -----------------------------------------------------------------------------
  192. void otaClientSetup() {
  193. // Backwards compatibility
  194. moveSetting("otafp", "otaFP");
  195. #if TERMINAL_SUPPORT
  196. _otaClientInitCommands();
  197. #endif
  198. #if (MQTT_SUPPORT && OTA_MQTT_SUPPORT)
  199. mqttRegister(_otaClientMqttCallback);
  200. #endif
  201. }
  202. #endif // OTA_CLIENT == OTA_CLIENT_ASYNCTCP