Merge branch 'master' into 3.5.2-merge
[ExpressLRS.git] / src / lib / WIFI / devWIFI.cpp
blob7acc8fba772652285895507f2fc4328966937472
1 #include "device.h"
3 #if defined(PLATFORM_ESP8266) || defined(PLATFORM_ESP32)
5 #include "deferred.h"
7 #include <AsyncJson.h>
8 #include <ArduinoJson.h>
9 #if defined(PLATFORM_ESP8266)
10 #include <FS.h>
11 #else
12 #include <SPIFFS.h>
13 #endif
15 #if defined(PLATFORM_ESP32)
16 #include <WiFi.h>
17 #include <ESPmDNS.h>
18 #include <Update.h>
19 #include <esp_partition.h>
20 #include <esp_ota_ops.h>
21 #include <soc/uart_pins.h>
22 #else
23 #include <ESP8266WiFi.h>
24 #include <ESP8266mDNS.h>
25 #define wifi_mode_t WiFiMode_t
26 #endif
27 #include <DNSServer.h>
29 #include <set>
30 #include <StreamString.h>
32 #include "ArduinoJson.h"
33 #include "AsyncJson.h"
34 #include <ESPAsyncWebServer.h>
36 #include "common.h"
37 #include "POWERMGNT.h"
38 #include "FHSS.h"
39 #include "hwTimer.h"
40 #include "logging.h"
41 #include "options.h"
42 #include "helpers.h"
43 #include "devButton.h"
44 #if defined(TARGET_RX) && defined(PLATFORM_ESP32)
45 #include "devVTXSPI.h"
46 #endif
48 #include "WebContent.h"
50 #include "config.h"
52 #if defined(RADIO_LR1121)
53 #include "lr1121.h"
54 #endif
56 #if defined(TARGET_TX)
57 #include "wifiJoystick.h"
59 extern TxConfig config;
60 extern void setButtonColors(uint8_t b1, uint8_t b2);
61 #else
62 extern RxConfig config;
63 #endif
65 extern unsigned long rebootTime;
67 static char station_ssid[33];
68 static char station_password[65];
70 static bool wifiStarted = false;
71 bool webserverPreventAutoStart = false;
73 static wl_status_t laststatus = WL_IDLE_STATUS;
74 volatile WiFiMode_t wifiMode = WIFI_OFF;
75 static volatile WiFiMode_t changeMode = WIFI_OFF;
76 static volatile unsigned long changeTime = 0;
78 static const byte DNS_PORT = 53;
79 static IPAddress netMsk(255, 255, 255, 0);
80 static DNSServer dnsServer;
81 static IPAddress ipAddress;
83 #if defined(USE_MSP_WIFI) && defined(TARGET_RX) //MSP2WIFI in enabled only for RX only at the moment
84 #include "crsf2msp.h"
85 #include "msp2crsf.h"
87 #include "tcpsocket.h"
88 TCPSOCKET wifi2tcp(5761); //port 5761 as used by BF configurator
89 #endif
91 #if defined(PLATFORM_ESP8266)
92 static bool scanComplete = false;
93 #endif
95 static AsyncWebServer server(80);
96 static bool servicesStarted = false;
97 static constexpr uint32_t STALE_WIFI_SCAN = 20000;
98 static uint32_t lastScanTimeMS = 0;
100 static bool target_seen = false;
101 static uint8_t target_pos = 0;
102 static String target_found;
103 static bool target_complete = false;
104 static bool force_update = false;
105 static uint32_t totalSize;
107 void setWifiUpdateMode()
109 // No need to ExitBindingMode(), the radio will be stopped stopped when start the Wifi service.
110 // Need to change this before the mode change event so the LED is updated
111 InBindingMode = false;
112 connectionState = wifiUpdate;
115 /** Is this an IP? */
116 static boolean isIp(String str)
118 for (size_t i = 0; i < str.length(); i++)
120 int c = str.charAt(i);
121 if (c != '.' && (c < '0' || c > '9'))
123 return false;
126 return true;
129 /** IP to String? */
130 static String toStringIp(IPAddress ip)
132 String res = "";
133 for (int i = 0; i < 3; i++)
135 res += String((ip >> (8 * i)) & 0xFF) + ".";
137 res += String(((ip >> 8 * 3)) & 0xFF);
138 return res;
141 static bool captivePortal(AsyncWebServerRequest *request)
143 extern const char *wifi_hostname;
145 if (!isIp(request->host()) && request->host() != (String(wifi_hostname) + ".local"))
147 DBGLN("Request redirected to captive portal");
148 request->redirect(String("http://") + toStringIp(request->client()->localIP()));
149 return true;
151 return false;
154 static struct {
155 const char *url;
156 const char *contentType;
157 const uint8_t* content;
158 const size_t size;
159 } files[] = {
160 {"/scan.js", "text/javascript", (uint8_t *)SCAN_JS, sizeof(SCAN_JS)},
161 {"/mui.js", "text/javascript", (uint8_t *)MUI_JS, sizeof(MUI_JS)},
162 {"/elrs.css", "text/css", (uint8_t *)ELRS_CSS, sizeof(ELRS_CSS)},
163 {"/hardware.html", "text/html", (uint8_t *)HARDWARE_HTML, sizeof(HARDWARE_HTML)},
164 {"/hardware.js", "text/javascript", (uint8_t *)HARDWARE_JS, sizeof(HARDWARE_JS)},
165 {"/cw.html", "text/html", (uint8_t *)CW_HTML, sizeof(CW_HTML)},
166 {"/cw.js", "text/javascript", (uint8_t *)CW_JS, sizeof(CW_JS)},
167 #if defined(RADIO_LR1121)
168 {"/lr1121.html", "text/html", (uint8_t *)LR1121_HTML, sizeof(LR1121_HTML)},
169 {"/lr1121.js", "text/javascript", (uint8_t *)LR1121_JS, sizeof(LR1121_JS)},
170 #endif
173 static void WebUpdateSendContent(AsyncWebServerRequest *request)
175 for (size_t i=0 ; i<ARRAY_SIZE(files) ; i++) {
176 if (request->url().equals(files[i].url)) {
177 AsyncWebServerResponse *response = request->beginResponse_P(200, files[i].contentType, files[i].content, files[i].size);
178 response->addHeader("Content-Encoding", "gzip");
179 request->send(response);
180 return;
183 request->send(404, "text/plain", "File not found");
186 static void WebUpdateHandleRoot(AsyncWebServerRequest *request)
188 if (captivePortal(request))
189 { // If captive portal redirect instead of displaying the page.
190 return;
192 force_update = request->hasArg("force");
193 AsyncWebServerResponse *response;
194 if (connectionState == hardwareUndefined)
196 response = request->beginResponse_P(200, "text/html", (uint8_t*)HARDWARE_HTML, sizeof(HARDWARE_HTML));
198 else
200 response = request->beginResponse_P(200, "text/html", (uint8_t*)INDEX_HTML, sizeof(INDEX_HTML));
202 response->addHeader("Content-Encoding", "gzip");
203 response->addHeader("Cache-Control", "no-cache, no-store, must-revalidate");
204 response->addHeader("Pragma", "no-cache");
205 response->addHeader("Expires", "-1");
206 request->send(response);
209 static void putFile(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total)
211 static File file;
212 static size_t bytes;
213 if (!file || request->url() != file.name()) {
214 file = SPIFFS.open(request->url(), "w");
215 bytes = 0;
217 file.write(data, len);
218 bytes += len;
219 if (bytes == total) {
220 file.close();
224 static void getFile(AsyncWebServerRequest *request)
226 if (request->url() == "/options.json") {
227 request->send(200, "application/json", getOptions());
228 } else if (request->url() == "/hardware.json") {
229 request->send(200, "application/json", getHardware());
230 } else {
231 request->send(SPIFFS, request->url().c_str(), "text/plain", true);
235 static void HandleReboot(AsyncWebServerRequest *request)
237 AsyncWebServerResponse *response = request->beginResponse(200, "application/json", "Kill -9, no more CPU time!");
238 response->addHeader("Connection", "close");
239 request->send(response);
240 request->client()->close();
241 rebootTime = millis() + 100;
244 static void HandleReset(AsyncWebServerRequest *request)
246 if (request->hasArg("hardware")) {
247 SPIFFS.remove("/hardware.json");
249 if (request->hasArg("options")) {
250 SPIFFS.remove("/options.json");
252 if (request->hasArg("model") || request->hasArg("config")) {
253 config.SetDefaults(true);
255 AsyncWebServerResponse *response = request->beginResponse(200, "application/json", "Reset complete, rebooting...");
256 response->addHeader("Connection", "close");
257 request->send(response);
258 request->client()->close();
259 rebootTime = millis() + 100;
262 static void UpdateSettings(AsyncWebServerRequest *request, JsonVariant &json)
264 if (firmwareOptions.flash_discriminator != json["flash-discriminator"].as<uint32_t>()) {
265 request->send(409, "text/plain", "Mismatched device identifier, refresh the page and try again.");
266 return;
269 File file = SPIFFS.open("/options.json", "w");
270 serializeJson(json, file);
271 request->send(200);
274 static const char *GetConfigUidType(const JsonObject json)
276 #if defined(TARGET_RX)
277 if (config.GetBindStorage() == BINDSTORAGE_VOLATILE)
278 return "Volatile";
279 if (config.GetBindStorage() == BINDSTORAGE_RETURNABLE && config.IsOnLoan())
280 return "Loaned";
281 if (config.GetIsBound())
282 return "Bound";
283 return "Not Bound";
284 #else
285 if (firmwareOptions.hasUID)
287 if (json["options"]["customised"] | false)
288 return "Overridden";
289 else
290 return "Flashed";
292 return "Not set (using MAC address)";
293 #endif
296 static void GetConfiguration(AsyncWebServerRequest *request)
298 bool exportMode = request->hasArg("export");
299 AsyncJsonResponse *response = new AsyncJsonResponse();
300 JsonObject json = response->getRoot();
302 if (!exportMode)
304 JsonDocument options;
305 deserializeJson(options, getOptions());
306 json["options"] = options;
309 JsonArray uid = json["config"]["uid"].to<JsonArray>();
310 copyArray(UID, UID_LEN, uid);
312 #if defined(TARGET_TX)
313 int button_count = 0;
314 if (GPIO_PIN_BUTTON != UNDEF_PIN)
315 button_count = 1;
316 if (GPIO_PIN_BUTTON2 != UNDEF_PIN)
317 button_count = 2;
318 for (int button=0 ; button<button_count ; button++)
320 const tx_button_color_t *buttonColor = config.GetButtonActions(button);
321 if (hardware_int(button == 0 ? HARDWARE_button_led_index : HARDWARE_button2_led_index) != -1) {
322 json["config"]["button-actions"][button]["color"] = buttonColor->val.color;
324 for (int pos=0 ; pos<button_GetActionCnt() ; pos++)
326 json["config"]["button-actions"][button]["action"][pos]["is-long-press"] = buttonColor->val.actions[pos].pressType ? true : false;
327 json["config"]["button-actions"][button]["action"][pos]["count"] = buttonColor->val.actions[pos].count;
328 json["config"]["button-actions"][button]["action"][pos]["action"] = buttonColor->val.actions[pos].action;
331 if (exportMode)
333 json["config"]["fan-mode"] = config.GetFanMode();
334 json["config"]["power-fan-threshold"] = config.GetPowerFanThreshold();
336 json["config"]["motion-mode"] = config.GetMotionMode();
338 json["config"]["vtx-admin"]["band"] = config.GetVtxBand();
339 json["config"]["vtx-admin"]["channel"] = config.GetVtxChannel();
340 json["config"]["vtx-admin"]["pitmode"] = config.GetVtxPitmode();
341 json["config"]["vtx-admin"]["power"] = config.GetVtxPower();
342 json["config"]["backpack"]["dvr-start-delay"] = config.GetDvrStartDelay();
343 json["config"]["backpack"]["dvr-stop-delay"] = config.GetDvrStopDelay();
344 json["config"]["backpack"]["dvr-aux-channel"] = config.GetDvrAux();
346 for (int model = 0 ; model < CONFIG_TX_MODEL_CNT ; model++)
348 const model_config_t &modelConfig = config.GetModelConfig(model);
349 String strModel(model);
350 JsonObject modelJson = json["config"]["model"][strModel].to<JsonObject>();
351 modelJson["packet-rate"] = modelConfig.rate;
352 modelJson["telemetry-ratio"] = modelConfig.tlm;
353 modelJson["switch-mode"] = modelConfig.switchMode;
354 modelJson["power"]["max-power"] = modelConfig.power;
355 modelJson["power"]["dynamic-power"] = modelConfig.dynamicPower;
356 modelJson["power"]["boost-channel"] = modelConfig.boostChannel;
357 modelJson["model-match"] = modelConfig.modelMatch;
358 modelJson["tx-antenna"] = modelConfig.txAntenna;
361 #endif /* TARGET_TX */
363 if (!exportMode)
365 json["config"]["ssid"] = station_ssid;
366 json["config"]["mode"] = wifiMode == WIFI_STA ? "STA" : "AP";
367 #if defined(TARGET_RX)
368 json["config"]["serial-protocol"] = config.GetSerialProtocol();
369 #if defined(PLATFORM_ESP32)
370 json["config"]["serial1-protocol"] = config.GetSerial1Protocol();
371 #endif
372 json["config"]["sbus-failsafe"] = config.GetFailsafeMode();
373 json["config"]["modelid"] = config.GetModelId();
374 json["config"]["force-tlm"] = config.GetForceTlmOff();
375 json["config"]["vbind"] = config.GetBindStorage();
376 #if defined(GPIO_PIN_PWM_OUTPUTS)
377 for (int ch=0; ch<GPIO_PIN_PWM_OUTPUTS_COUNT; ++ch)
379 json["config"]["pwm"][ch]["config"] = config.GetPwmChannel(ch)->raw;
380 json["config"]["pwm"][ch]["pin"] = GPIO_PIN_PWM_OUTPUTS[ch];
381 uint8_t features = 0;
382 auto pin = GPIO_PIN_PWM_OUTPUTS[ch];
383 if (pin == U0TXD_GPIO_NUM) features |= 1; // SerialTX supported
384 else if (pin == U0RXD_GPIO_NUM) features |= 2; // SerialRX supported
385 else if (pin == GPIO_PIN_SCL) features |= 4; // I2C SCL supported (only on this pin)
386 else if (pin == GPIO_PIN_SDA) features |= 8; // I2C SCL supported (only on this pin)
387 else if (GPIO_PIN_SCL == UNDEF_PIN || GPIO_PIN_SDA == UNDEF_PIN) features |= 12; // Both I2C SCL/SDA supported (on any pin)
388 #if defined(PLATFORM_ESP32)
389 if (pin != 0) features |= 16; // DShot supported on all pins but GPIO0
390 if (pin == GPIO_PIN_SERIAL1_RX) features |= 32; // SERIAL1 RX supported (only on this pin)
391 else if (pin == GPIO_PIN_SERIAL1_TX) features |= 64; // SERIAL1 TX supported (only on this pin)
392 else if ((GPIO_PIN_SERIAL1_RX == UNDEF_PIN || GPIO_PIN_SERIAL1_TX == UNDEF_PIN) &&
393 (!(features & 1) && !(features & 2))) features |= 96; // Both Serial1 RX/TX supported (on any pin if not already featured for Serial 1)
394 #endif
395 json["config"]["pwm"][ch]["features"] = features;
397 #endif
398 #endif
399 json["config"]["product_name"] = product_name;
400 json["config"]["lua_name"] = device_name;
401 json["config"]["reg_domain"] = FHSSgetRegulatoryDomain();
402 json["config"]["uidtype"] = GetConfigUidType(json);
405 response->setLength();
406 request->send(response);
409 #if defined(TARGET_TX)
410 static void UpdateConfiguration(AsyncWebServerRequest *request, JsonVariant &json)
412 if (json.containsKey("button-actions")) {
413 const JsonArray &array = json["button-actions"].as<JsonArray>();
414 for (size_t button=0 ; button<array.size() ; button++)
416 tx_button_color_t action;
417 for (int pos=0 ; pos<button_GetActionCnt() ; pos++)
419 action.val.actions[pos].pressType = array[button]["action"][pos]["is-long-press"];
420 action.val.actions[pos].count = array[button]["action"][pos]["count"];
421 action.val.actions[pos].action = array[button]["action"][pos]["action"];
423 action.val.color = array[button]["color"];
424 config.SetButtonActions(button, &action);
427 config.Commit();
428 request->send(200, "text/plain", "Import/update complete");
431 static void ImportConfiguration(AsyncWebServerRequest *request, JsonVariant &json)
433 if (json.containsKey("config"))
435 json = json["config"];
438 if (json.containsKey("fan-mode")) config.SetFanMode(json["fan-mode"]);
439 if (json.containsKey("power-fan-threshold")) config.SetPowerFanThreshold(json["power-fan-threshold"]);
440 if (json.containsKey("motion-mode")) config.SetMotionMode(json["motion-mode"]);
442 if (json.containsKey("vtx-admin"))
444 if (json["vtx-admin"].containsKey("band")) config.SetVtxBand(json["vtx-admin"]["band"]);
445 if (json["vtx-admin"].containsKey("channel")) config.SetVtxChannel(json["vtx-admin"]["channel"]);
446 if (json["vtx-admin"].containsKey("pitmode")) config.SetVtxPitmode(json["vtx-admin"]["pitmode"]);
447 if (json["vtx-admin"].containsKey("power")) config.SetVtxPower(json["vtx-admin"]["power"]);
450 if (json.containsKey("backpack"))
452 if (json["backpack"].containsKey("dvr-start-delay")) config.SetDvrStartDelay(json["backpack"]["dvr-start-delay"]);
453 if (json["backpack"].containsKey("dvr-stop-delay")) config.SetDvrStopDelay(json["backpack"]["dvr-stop-delay"]);
454 if (json["backpack"].containsKey("dvr-aux-channel")) config.SetDvrAux(json["backpack"]["dvr-aux-channel"]);
457 if (json.containsKey("model"))
459 for(JsonPair kv : json["model"].as<JsonObject>())
461 uint8_t model = atoi(kv.key().c_str());
462 JsonObject modelJson = kv.value();
464 config.SetModelId(model);
465 if (modelJson.containsKey("packet-rate")) config.SetRate(modelJson["packet-rate"]);
466 if (modelJson.containsKey("telemetry-ratio")) config.SetTlm(modelJson["telemetry-ratio"]);
467 if (modelJson.containsKey("switch-mode")) config.SetSwitchMode(modelJson["switch-mode"]);
468 if (modelJson.containsKey("power"))
470 if (modelJson["power"].containsKey("max-power")) config.SetPower(modelJson["power"]["max-power"]);
471 if (modelJson["power"].containsKey("dynamic-power")) config.SetDynamicPower(modelJson["power"]["dynamic-power"]);
472 if (modelJson["power"].containsKey("boost-channel")) config.SetBoostChannel(modelJson["power"]["boost-channel"]);
474 if (modelJson.containsKey("model-match")) config.SetModelMatch(modelJson["model-match"]);
475 // if (modelJson.containsKey("tx-antenna")) config.SetTxAntenna(modelJson["tx-antenna"]);
476 // have to commmit after each model is updated
477 config.Commit();
481 UpdateConfiguration(request, json);
484 static void WebUpdateButtonColors(AsyncWebServerRequest *request, JsonVariant &json)
486 int button1Color = json[0].as<int>();
487 int button2Color = json[1].as<int>();
488 DBGLN("%d %d", button1Color, button2Color);
489 setButtonColors(button1Color, button2Color);
490 request->send(200);
492 #else
494 * @brief: Copy uid to config if changed
496 static void JsonUidToConfig(JsonVariant &json)
498 JsonArray juid = json["uid"].as<JsonArray>();
499 size_t juidLen = constrain(juid.size(), 0, UID_LEN);
500 uint8_t newUid[UID_LEN] = { 0 };
502 // Copy only as many bytes as were included, right-justified
503 // This supports 6-digit UID as well as 4-digit (OTA bound) UID
504 copyArray(juid, &newUid[UID_LEN-juidLen], juidLen);
506 if (memcmp(newUid, config.GetUID(), UID_LEN) != 0)
508 config.SetUID(newUid);
509 config.Commit();
510 // Also copy it to the global UID in case the page is reloaded
511 memcpy(UID, newUid, UID_LEN);
514 static void UpdateConfiguration(AsyncWebServerRequest *request, JsonVariant &json)
516 uint8_t protocol = json["serial-protocol"] | 0;
517 config.SetSerialProtocol((eSerialProtocol)protocol);
519 #if defined(PLATFORM_ESP32)
520 uint8_t protocol1 = json["serial1-protocol"] | 0;
521 config.SetSerial1Protocol((eSerial1Protocol)protocol1);
522 #endif
524 uint8_t failsafe = json["sbus-failsafe"] | 0;
525 config.SetFailsafeMode((eFailsafeMode)failsafe);
527 long modelid = json["modelid"] | 255;
528 if (modelid < 0 || modelid > 63) modelid = 255;
529 config.SetModelId((uint8_t)modelid);
531 long forceTlm = json["force-tlm"] | 0;
532 config.SetForceTlmOff(forceTlm != 0);
534 config.SetBindStorage((rx_config_bindstorage_t)(json["vbind"] | 0));
535 JsonUidToConfig(json);
537 #if defined(GPIO_PIN_PWM_OUTPUTS)
538 JsonArray pwm = json["pwm"].as<JsonArray>();
539 for(uint32_t channel = 0 ; channel < pwm.size() ; channel++)
541 uint32_t val = pwm[channel];
542 //DBGLN("PWMch(%u)=%u", channel, val);
543 config.SetPwmChannelRaw(channel, val);
545 #endif
547 config.Commit();
548 request->send(200, "text/plain", "Configuration updated");
550 #endif
552 static void WebUpdateGetTarget(AsyncWebServerRequest *request)
554 JsonDocument json;
555 json["target"] = &target_name[4];
556 json["version"] = VERSION;
557 json["product_name"] = product_name;
558 json["lua_name"] = device_name;
559 json["reg_domain"] = FHSSgetRegulatoryDomain();
560 json["git-commit"] = commit;
561 #if defined(TARGET_TX)
562 json["module-type"] = "TX";
563 #endif
564 #if defined(TARGET_RX)
565 json["module-type"] = "RX";
566 #endif
567 #if defined(RADIO_SX128X)
568 json["radio-type"] = "SX128X";
569 json["has-sub-ghz"] = false;
570 #endif
571 #if defined(RADIO_SX127X)
572 json["radio-type"] = "SX127X";
573 json["has-sub-ghz"] = true;
574 #endif
575 #if defined(RADIO_LR1121)
576 json["radio-type"] = "LR1121";
577 json["has-sub-ghz"] = true;
578 #endif
580 AsyncResponseStream *response = request->beginResponseStream("application/json");
581 serializeJson(json, *response);
582 request->send(response);
585 static void WebUpdateSendNetworks(AsyncWebServerRequest *request)
587 int numNetworks = WiFi.scanComplete();
588 if (numNetworks >= 0 && millis() - lastScanTimeMS < STALE_WIFI_SCAN) {
589 DBGLN("Found %d networks", numNetworks);
590 std::set<String> vs;
591 String s="[";
592 for(int i=0 ; i<numNetworks ; i++) {
593 String w = WiFi.SSID(i);
594 DBGLN("found %s", w.c_str());
595 if (vs.find(w)==vs.end() && w.length()>0) {
596 if (!vs.empty()) s += ",";
597 s += "\"" + w + "\"";
598 vs.insert(w);
601 s+="]";
602 request->send(200, "application/json", s);
603 } else {
604 if (WiFi.scanComplete() != WIFI_SCAN_RUNNING)
606 #if defined(PLATFORM_ESP8266)
607 scanComplete = false;
608 WiFi.scanNetworksAsync([](int){
609 scanComplete = true;
611 #else
612 WiFi.scanNetworks(true);
613 #endif
614 lastScanTimeMS = millis();
616 request->send(204, "application/json", "[]");
620 static void sendResponse(AsyncWebServerRequest *request, const String &msg, WiFiMode_t mode) {
621 AsyncWebServerResponse *response = request->beginResponse(200, "text/plain", msg);
622 response->addHeader("Connection", "close");
623 request->send(response);
624 request->client()->close();
625 changeTime = millis();
626 changeMode = mode;
629 static void WebUpdateAccessPoint(AsyncWebServerRequest *request)
631 DBGLN("Starting Access Point");
632 String msg = String("Access Point starting, please connect to access point '") + wifi_ap_ssid + "' with password '" + wifi_ap_password + "'";
633 sendResponse(request, msg, WIFI_AP);
636 static void WebUpdateConnect(AsyncWebServerRequest *request)
638 DBGLN("Connecting to network");
639 String msg = String("Connecting to network '") + station_ssid + "', connect to http://" +
640 wifi_hostname + ".local from a browser on that network";
641 sendResponse(request, msg, WIFI_STA);
644 static void WebUpdateSetHome(AsyncWebServerRequest *request)
646 String ssid = request->arg("network");
647 String password = request->arg("password");
649 DBGLN("Setting network %s", ssid.c_str());
650 strcpy(station_ssid, ssid.c_str());
651 strcpy(station_password, password.c_str());
652 if (request->hasArg("save")) {
653 strlcpy(firmwareOptions.home_wifi_ssid, ssid.c_str(), sizeof(firmwareOptions.home_wifi_ssid));
654 strlcpy(firmwareOptions.home_wifi_password, password.c_str(), sizeof(firmwareOptions.home_wifi_password));
655 saveOptions();
657 WebUpdateConnect(request);
660 static void WebUpdateForget(AsyncWebServerRequest *request)
662 DBGLN("Forget network");
663 firmwareOptions.home_wifi_ssid[0] = 0;
664 firmwareOptions.home_wifi_password[0] = 0;
665 saveOptions();
666 station_ssid[0] = 0;
667 station_password[0] = 0;
668 String msg = String("Home network forgotten, please connect to access point '") + wifi_ap_ssid + "' with password '" + wifi_ap_password + "'";
669 sendResponse(request, msg, WIFI_AP);
672 static void WebUpdateHandleNotFound(AsyncWebServerRequest *request)
674 if (captivePortal(request))
675 { // If captive portal redirect instead of displaying the error page.
676 return;
678 String message = F("File Not Found\n\n");
679 message += F("URI: ");
680 message += request->url();
681 message += F("\nMethod: ");
682 message += (request->method() == HTTP_GET) ? "GET" : "POST";
683 message += F("\nArguments: ");
684 message += request->args();
685 message += F("\n");
687 for (uint8_t i = 0; i < request->args(); i++)
689 message += String(F(" ")) + request->argName(i) + F(": ") + request->arg(i) + F("\n");
691 AsyncWebServerResponse *response = request->beginResponse(404, "text/plain", message);
692 response->addHeader("Cache-Control", "no-cache, no-store, must-revalidate");
693 response->addHeader("Pragma", "no-cache");
694 response->addHeader("Expires", "-1");
695 request->send(response);
698 static void corsPreflightResponse(AsyncWebServerRequest *request) {
699 AsyncWebServerResponse *response = request->beginResponse(204, "text/plain");
700 request->send(response);
703 static void WebUploadResponseHandler(AsyncWebServerRequest *request) {
704 if (target_seen || Update.hasError()) {
705 String msg;
706 if (!Update.hasError() && Update.end()) {
707 DBGLN("Update complete, rebooting");
708 msg = String("{\"status\": \"ok\", \"msg\": \"Update complete. ");
709 #if defined(TARGET_RX)
710 msg += "Please wait for the LED to resume blinking before disconnecting power.\"}";
711 #else
712 msg += "Please wait for a few seconds while the device reboots.\"}";
713 #endif
714 rebootTime = millis() + 200;
715 } else {
716 StreamString p = StreamString();
717 if (Update.hasError()) {
718 Update.printError(p);
719 } else {
720 p.println("Not enough data uploaded!");
722 p.trim();
723 DBGLN("Failed to upload firmware: %s", p.c_str());
724 msg = String("{\"status\": \"error\", \"msg\": \"") + p + "\"}";
726 AsyncWebServerResponse *response = request->beginResponse(200, "application/json", msg);
727 response->addHeader("Connection", "close");
728 request->send(response);
729 request->client()->close();
730 } else {
731 String message = String("{\"status\": \"mismatch\", \"msg\": \"<b>Current target:</b> ") + (const char *)&target_name[4] + ".<br>";
732 if (target_found.length() != 0) {
733 message += "<b>Uploaded image:</b> " + target_found + ".<br/>";
735 message += "<br/>It looks like you are flashing firmware with a different name to the current firmware. This sometimes happens because the hardware was flashed from the factory with an early version that has a different name. Or it may have even changed between major releases.";
736 message += "<br/><br/>Please double check you are uploading the correct target, then proceed with 'Flash Anyway'.\"}";
737 request->send(200, "application/json", message);
741 static void WebUploadDataHandler(AsyncWebServerRequest *request, const String& filename, size_t index, uint8_t *data, size_t len, bool final) {
742 force_update = force_update || request->hasArg("force");
743 if (index == 0) {
744 #if defined(TARGET_TX) && defined(PLATFORM_ESP32)
745 WifiJoystick::StopJoystickService();
746 #endif
748 size_t filesize = request->header("X-FileSize").toInt();
749 DBGLN("Update: '%s' size %u", filename.c_str(), filesize);
750 #if defined(PLATFORM_ESP8266)
751 Update.runAsync(true);
752 uint32_t maxSketchSpace = (ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000;
753 DBGLN("Free space = %u", maxSketchSpace);
754 UNUSED(maxSketchSpace); // for warning
755 #endif
756 if (!Update.begin(filesize, U_FLASH)) { // pass the size provided
757 Update.printError(LOGGING_UART);
759 target_seen = false;
760 target_found.clear();
761 target_complete = false;
762 target_pos = 0;
763 totalSize = 0;
765 if (len) {
766 DBGVLN("writing %d", len);
767 if (Update.write(data, len) == len) {
768 if (force_update || (totalSize == 0 && *data == 0x1F))
769 target_seen = true;
770 if (!target_seen) {
771 for (size_t i=0 ; i<len ;i++) {
772 if (!target_complete && (target_pos >= 4 || target_found.length() > 0)) {
773 if (target_pos == 4) {
774 target_found.clear();
776 if (data[i] == 0 || target_found.length() > 50) {
777 target_complete = true;
779 else {
780 target_found += (char)data[i];
783 if (data[i] == target_name[target_pos]) {
784 ++target_pos;
785 if (target_pos >= target_name_size) {
786 target_seen = true;
789 else {
790 target_pos = 0; // Startover
794 totalSize += len;
795 } else {
796 DBGLN("write failed to write %d", len);
801 static void WebUploadForceUpdateHandler(AsyncWebServerRequest *request) {
802 target_seen = true;
803 if (request->arg("action").equals("confirm")) {
804 WebUploadResponseHandler(request);
805 } else {
806 #if defined(PLATFORM_ESP32)
807 Update.abort();
808 #endif
809 request->send(200, "application/json", "{\"status\": \"ok\", \"msg\": \"Update cancelled\"}");
813 #if defined(TARGET_TX) && defined(PLATFORM_ESP32)
814 static void WebUdpControl(AsyncWebServerRequest *request)
816 const String &action = request->arg("action");
817 if (action.equals("joystick_begin"))
819 WifiJoystick::StartSending(request->client()->remoteIP(),
820 request->arg("interval").toInt(), request->arg("channels").toInt());
821 request->send(200, "text/plain", "ok");
823 else if (action.equals("joystick_end"))
825 WifiJoystick::StopSending();
826 request->send(200, "text/plain", "ok");
829 #endif
831 static size_t firmwareOffset = 0;
832 static size_t getFirmwareChunk(uint8_t *data, size_t len, size_t pos)
834 uint8_t *dst;
835 uint8_t alignedBuffer[7];
836 if ((uintptr_t)data % 4 != 0)
838 // If data is not aligned, read aligned byes using the local buffer and hope the next call will be aligned
839 dst = (uint8_t *)((uint32_t)alignedBuffer / 4 * 4);
840 len = 4;
842 else
844 // Otherwise just make sure len is a multiple of 4 and smaller than a sector
845 dst = data;
846 len = constrain((len / 4) * 4, 4, SPI_FLASH_SEC_SIZE);
849 ESP.flashRead(firmwareOffset + pos, (uint32_t *)dst, len);
851 // If using local stack buffer, move the 4 bytes into the passed buffer
852 // data is known to not be aligned so it is moved byte-by-byte instead of as uint32_t*
853 if ((void *)dst != (void *)data)
855 for (unsigned b=len; b>0; --b)
856 *data++ = *dst++;
858 return len;
861 static void WebUpdateGetFirmware(AsyncWebServerRequest *request) {
862 #if defined(PLATFORM_ESP32)
863 const esp_partition_t *running = esp_ota_get_running_partition();
864 if (running) {
865 firmwareOffset = running->address;
867 #endif
868 const size_t firmwareTrailerSize = 4096; // max number of bytes for the options/hardware layout json
869 AsyncWebServerResponse *response = request->beginResponse("application/octet-stream", (size_t)ESP.getSketchSize() + firmwareTrailerSize, &getFirmwareChunk);
870 String filename = String("attachment; filename=\"") + (const char *)&target_name[4] + "_" + VERSION + ".bin\"";
871 response->addHeader("Content-Disposition", filename);
872 request->send(response);
875 static void HandleContinuousWave(AsyncWebServerRequest *request) {
876 if (request->hasArg("radio")) {
877 SX12XX_Radio_Number_t radio = request->arg("radio").toInt() == 1 ? SX12XX_Radio_1 : SX12XX_Radio_2;
879 #if defined(RADIO_LR1121)
880 bool setSubGHz = false;
881 setSubGHz = request->arg("subGHz").toInt() == 1;
882 #endif
884 AsyncWebServerResponse *response = request->beginResponse(204);
885 response->addHeader("Connection", "close");
886 request->send(response);
887 request->client()->close();
889 Radio.TXdoneCallback = [](){};
890 Radio.Begin(FHSSgetMinimumFreq(), FHSSgetMaximumFreq());
892 POWERMGNT::init();
893 POWERMGNT::setPower(POWERMGNT::getMinPower());
895 #if defined(RADIO_LR1121)
896 Radio.startCWTest(setSubGHz ? FHSSconfig->freq_center : FHSSconfigDualBand->freq_center, radio);
897 #else
898 Radio.startCWTest(FHSSconfig->freq_center, radio);
899 #if defined(RADIO_SX127X)
900 deferExecutionMillis(50, [radio](){ Radio.cwRepeat(radio); });
901 #endif
902 #endif
903 } else {
904 int radios = (GPIO_PIN_NSS_2 == UNDEF_PIN) ? 1 : 2;
905 request->send(200, "application/json", String("{\"radios\": ") + radios + ", \"center\": "+ FHSSconfig->freq_center +
906 #if defined(RADIO_LR1121)
907 ", \"center2\": "+ FHSSconfigDualBand->freq_center +
908 #endif
909 "}");
913 static void initialize()
915 wifiStarted = false;
916 WiFi.disconnect(true);
917 WiFi.mode(WIFI_OFF);
918 #if defined(PLATFORM_ESP8266)
919 WiFi.forceSleepBegin();
920 #endif
921 registerButtonFunction(ACTION_START_WIFI, [](){
922 setWifiUpdateMode();
926 static void startWiFi(unsigned long now)
928 if (wifiStarted) {
929 return;
932 if (connectionState < FAILURE_STATES) {
933 hwTimer::stop();
934 #if defined(TARGET_RX) && defined(PLATFORM_ESP32)
935 disableVTxSpi();
936 #endif
938 // Set transmit power to minimum
939 POWERMGNT::setPower(MinPower);
941 setWifiUpdateMode();
943 DBGLN("Stopping Radio");
944 Radio.End();
947 DBGLN("Begin Webupdater");
949 WiFi.persistent(false);
950 WiFi.disconnect();
951 WiFi.mode(WIFI_OFF);
952 strcpy(station_ssid, firmwareOptions.home_wifi_ssid);
953 strcpy(station_password, firmwareOptions.home_wifi_password);
954 if (station_ssid[0] == 0) {
955 changeTime = now;
956 changeMode = WIFI_AP;
958 else {
959 changeTime = now;
960 changeMode = WIFI_STA;
962 laststatus = WL_DISCONNECTED;
963 wifiStarted = true;
966 static void startMDNS()
968 if (!MDNS.begin(wifi_hostname))
970 DBGLN("Error starting mDNS");
971 return;
974 String options = "-DAUTO_WIFI_ON_INTERVAL=" + (firmwareOptions.wifi_auto_on_interval == -1 ? "-1" : String(firmwareOptions.wifi_auto_on_interval / 1000));
976 #if defined(TARGET_TX)
977 if (firmwareOptions.unlock_higher_power)
979 options += " -DUNLOCK_HIGHER_POWER";
981 options += " -DTLM_REPORT_INTERVAL_MS=" + String(firmwareOptions.tlm_report_interval);
982 options += " -DFAN_MIN_RUNTIME=" + String(firmwareOptions.fan_min_runtime);
983 #endif
985 #if defined(TARGET_RX)
986 if (firmwareOptions.lock_on_first_connection)
988 options += " -DLOCK_ON_FIRST_CONNECTION";
990 options += " -DRCVR_UART_BAUD=" + String(firmwareOptions.uart_baud);
991 #endif
993 String instance = String(wifi_hostname) + "_" + WiFi.macAddress();
994 instance.replace(":", "");
995 #if defined(PLATFORM_ESP8266)
996 // We have to do it differently on ESP8266 as setInstanceName has the side-effect of chainging the hostname!
997 MDNS.setInstanceName(wifi_hostname);
998 MDNSResponder::hMDNSService service = MDNS.addService(instance.c_str(), "http", "tcp", 80);
999 MDNS.addServiceTxt(service, "vendor", "elrs");
1000 MDNS.addServiceTxt(service, "target", (const char *)&target_name[4]);
1001 MDNS.addServiceTxt(service, "device", (const char *)device_name);
1002 MDNS.addServiceTxt(service, "product", (const char *)product_name);
1003 MDNS.addServiceTxt(service, "version", VERSION);
1004 MDNS.addServiceTxt(service, "options", options.c_str());
1005 MDNS.addServiceTxt(service, "type", "rx");
1006 // If the probe result fails because there is another device on the network with the same name
1007 // use our unique instance name as the hostname. A better way to do this would be to use
1008 // MDNSResponder::indexDomain and change wifi_hostname as well.
1009 MDNS.setHostProbeResultCallback([instance](const char* p_pcDomainName, bool p_bProbeResult) {
1010 if (!p_bProbeResult) {
1011 WiFi.hostname(instance);
1012 MDNS.setInstanceName(instance);
1015 #else
1016 MDNS.setInstanceName(instance);
1017 MDNS.addService("http", "tcp", 80);
1018 MDNS.addServiceTxt("http", "tcp", "vendor", "elrs");
1019 MDNS.addServiceTxt("http", "tcp", "target", (const char *)&target_name[4]);
1020 MDNS.addServiceTxt("http", "tcp", "device", (const char *)device_name);
1021 MDNS.addServiceTxt("http", "tcp", "product", (const char *)product_name);
1022 MDNS.addServiceTxt("http", "tcp", "version", VERSION);
1023 MDNS.addServiceTxt("http", "tcp", "options", options.c_str());
1024 #if defined(TARGET_TX)
1025 MDNS.addServiceTxt("http", "tcp", "type", "tx");
1026 #else
1027 MDNS.addServiceTxt("http", "tcp", "type", "rx");
1028 #endif
1029 #endif
1031 #if defined(TARGET_TX) && defined(PLATFORM_ESP32)
1032 MDNS.addService("elrs", "udp", JOYSTICK_PORT);
1033 MDNS.addServiceTxt("elrs", "udp", "device", (const char *)device_name);
1034 MDNS.addServiceTxt("elrs", "udp", "version", String(JOYSTICK_VERSION).c_str());
1035 #endif
1038 static void addCaptivePortalHandlers()
1040 // windows 11 captive portal workaround
1041 server.on("/connecttest.txt", [](AsyncWebServerRequest *request) { request->redirect("http://logout.net"); });
1042 // A 404 stops win 10 keep calling this repeatedly and panicking the esp32
1043 server.on("/wpad.dat", [](AsyncWebServerRequest *request) { request->send(404); });
1045 server.on("/generate_204", WebUpdateHandleRoot); // Android
1046 server.on("/gen_204", WebUpdateHandleRoot); // Android
1047 server.on("/library/test/success.html", WebUpdateHandleRoot); // apple call home
1048 server.on("/hotspot-detect.html", WebUpdateHandleRoot); // apple call home
1049 server.on("/connectivity-check.html", WebUpdateHandleRoot); // ubuntu
1050 server.on("/check_network_status.txt", WebUpdateHandleRoot); // ubuntu
1051 server.on("/ncsi.txt", WebUpdateHandleRoot); // windows call home
1052 server.on("/canonical.html", WebUpdateHandleRoot); // firefox captive portal call home
1053 server.on("/fwlink", WebUpdateHandleRoot);
1054 server.on("/redirect", WebUpdateHandleRoot); // microsoft redirect
1055 server.on("/success.txt", [](AsyncWebServerRequest *request) { request->send(200); }); // firefox captive portal call home
1058 static void startServices()
1060 if (servicesStarted) {
1061 #if defined(PLATFORM_ESP32)
1062 MDNS.end();
1063 startMDNS();
1064 #endif
1065 return;
1068 server.on("/", WebUpdateHandleRoot);
1069 server.on("/elrs.css", WebUpdateSendContent);
1070 server.on("/mui.js", WebUpdateSendContent);
1071 server.on("/scan.js", WebUpdateSendContent);
1072 server.on("/networks.json", WebUpdateSendNetworks);
1073 server.on("/sethome", WebUpdateSetHome);
1074 server.on("/forget", WebUpdateForget);
1075 server.on("/connect", WebUpdateConnect);
1076 server.on("/config", HTTP_GET, GetConfiguration);
1077 server.on("/access", WebUpdateAccessPoint);
1078 server.on("/target", WebUpdateGetTarget);
1079 server.on("/firmware.bin", WebUpdateGetFirmware);
1081 server.on("/update", HTTP_POST, WebUploadResponseHandler, WebUploadDataHandler);
1082 server.on("/update", HTTP_OPTIONS, corsPreflightResponse);
1083 server.on("/forceupdate", WebUploadForceUpdateHandler);
1084 server.on("/forceupdate", HTTP_OPTIONS, corsPreflightResponse);
1085 server.on("/cw.html", WebUpdateSendContent);
1086 server.on("/cw.js", WebUpdateSendContent);
1087 server.on("/cw", HandleContinuousWave);
1089 DefaultHeaders::Instance().addHeader("Access-Control-Allow-Origin", "*");
1090 DefaultHeaders::Instance().addHeader("Access-Control-Max-Age", "600");
1091 DefaultHeaders::Instance().addHeader("Access-Control-Allow-Methods", "POST,GET,OPTIONS");
1092 DefaultHeaders::Instance().addHeader("Access-Control-Allow-Headers", "*");
1094 server.on("/hardware.html", WebUpdateSendContent);
1095 server.on("/hardware.js", WebUpdateSendContent);
1096 server.on("/hardware.json", getFile).onBody(putFile);
1097 server.on("/options.json", HTTP_GET, getFile);
1098 server.on("/reboot", HandleReboot);
1099 server.on("/reset", HandleReset);
1100 #if defined(TARGET_TX) && defined(PLATFORM_ESP32)
1101 server.on("/udpcontrol", HTTP_POST, WebUdpControl);
1102 #endif
1104 server.addHandler(new AsyncCallbackJsonWebHandler("/config", UpdateConfiguration));
1105 server.addHandler(new AsyncCallbackJsonWebHandler("/options.json", UpdateSettings));
1106 #if defined(TARGET_TX)
1107 server.addHandler(new AsyncCallbackJsonWebHandler("/buttons", WebUpdateButtonColors));
1108 server.addHandler(new AsyncCallbackJsonWebHandler("/import", ImportConfiguration, 32768U));
1109 #endif
1111 #if defined(RADIO_LR1121)
1112 server.on("/lr1121.html", WebUpdateSendContent);
1113 server.on("/lr1121.js", WebUpdateSendContent);
1114 server.on("/lr1121", HTTP_OPTIONS, corsPreflightResponse);
1115 addLR1121Handlers(server);
1116 #endif
1118 addCaptivePortalHandlers();
1120 server.onNotFound(WebUpdateHandleNotFound);
1122 server.begin();
1124 dnsServer.start(DNS_PORT, "*", ipAddress);
1125 dnsServer.setErrorReplyCode(DNSReplyCode::NoError);
1127 startMDNS();
1129 #if defined(TARGET_TX) && defined(PLATFORM_ESP32)
1130 WifiJoystick::StartJoystickService();
1131 #endif
1133 servicesStarted = true;
1134 DBGLN("HTTPUpdateServer ready! Open http://%s.local in your browser", wifi_hostname);
1135 #if defined(USE_MSP_WIFI) && defined(TARGET_RX)
1136 wifi2tcp.begin();
1137 #endif
1140 static void HandleWebUpdate()
1142 unsigned long now = millis();
1143 wl_status_t status = WiFi.status();
1145 if (status != laststatus && wifiMode == WIFI_STA) {
1146 DBGLN("WiFi status %d", status);
1147 switch(status) {
1148 case WL_NO_SSID_AVAIL:
1149 case WL_CONNECT_FAILED:
1150 case WL_CONNECTION_LOST:
1151 changeTime = now;
1152 changeMode = WIFI_AP;
1153 break;
1154 case WL_DISCONNECTED: // try reconnection
1155 changeTime = now;
1156 break;
1157 default:
1158 break;
1160 laststatus = status;
1162 if (status != WL_CONNECTED && wifiMode == WIFI_STA && (now - changeTime) > 30000) {
1163 changeTime = now;
1164 changeMode = WIFI_AP;
1165 DBGLN("Connection failed %d", status);
1167 if (changeMode != wifiMode && changeMode != WIFI_OFF && (now - changeTime) > 500) {
1168 switch(changeMode) {
1169 case WIFI_AP:
1170 DBGLN("Changing to AP mode");
1171 WiFi.disconnect();
1172 wifiMode = WIFI_AP;
1173 #if defined(PLATFORM_ESP32)
1174 WiFi.setHostname(wifi_hostname); // hostname must be set before the mode is set to STA
1175 #endif
1176 WiFi.mode(wifiMode);
1177 #if defined(PLATFORM_ESP8266)
1178 WiFi.setHostname(wifi_hostname); // hostname must be set before the mode is set to STA
1179 #endif
1180 changeTime = now;
1181 #if defined(PLATFORM_ESP8266)
1182 WiFi.setOutputPower(13.5);
1183 WiFi.setPhyMode(WIFI_PHY_MODE_11N);
1184 #elif defined(PLATFORM_ESP32)
1185 WiFi.setTxPower(WIFI_POWER_19_5dBm);
1186 #endif
1187 WiFi.softAPConfig(ipAddress, ipAddress, netMsk);
1188 WiFi.softAP(wifi_ap_ssid, wifi_ap_password);
1189 startServices();
1190 break;
1191 case WIFI_STA:
1192 DBGLN("Connecting to network '%s'", station_ssid);
1193 wifiMode = WIFI_STA;
1194 #if defined(PLATFORM_ESP32)
1195 WiFi.setHostname(wifi_hostname); // hostname must be set before the mode is set to STA
1196 #endif
1197 WiFi.mode(wifiMode);
1198 #if defined(PLATFORM_ESP8266)
1199 WiFi.setHostname(wifi_hostname); // hostname must be set after the mode is set to STA
1200 #endif
1201 changeTime = now;
1202 #if defined(PLATFORM_ESP8266)
1203 WiFi.setOutputPower(13.5);
1204 WiFi.setPhyMode(WIFI_PHY_MODE_11N);
1205 #elif defined(PLATFORM_ESP32)
1206 WiFi.setTxPower(WIFI_POWER_19_5dBm);
1207 WiFi.setSortMethod(WIFI_CONNECT_AP_BY_SIGNAL);
1208 WiFi.setScanMethod(WIFI_ALL_CHANNEL_SCAN);
1209 #endif
1210 WiFi.begin(station_ssid, station_password);
1211 startServices();
1212 default:
1213 break;
1215 #if defined(PLATFORM_ESP8266)
1216 MDNS.notifyAPChange();
1217 #endif
1218 changeMode = WIFI_OFF;
1221 #if defined(PLATFORM_ESP8266)
1222 if (scanComplete)
1224 WiFi.mode(wifiMode);
1225 scanComplete = false;
1227 #endif
1229 if (servicesStarted)
1231 dnsServer.processNextRequest();
1232 #if defined(PLATFORM_ESP8266)
1233 MDNS.update();
1234 #endif
1236 #if defined(TARGET_TX) && defined(PLATFORM_ESP32)
1237 WifiJoystick::Loop(now);
1238 #endif
1242 void HandleMSP2WIFI()
1244 #if defined(USE_MSP_WIFI) && defined(TARGET_RX)
1245 // check is there is any data to write out
1246 if (crsf2msp.FIFOout.peekSize() > 0)
1248 const uint16_t len = crsf2msp.FIFOout.popSize();
1249 uint8_t data[len];
1250 crsf2msp.FIFOout.popBytes(data, len);
1251 wifi2tcp.write(data, len);
1254 // check if there is any data to read in
1255 const uint16_t bytesReady = wifi2tcp.bytesReady();
1256 if (bytesReady > 0)
1258 uint8_t data[bytesReady];
1259 wifi2tcp.read(data);
1260 msp2crsf.parse(data, bytesReady);
1263 wifi2tcp.handle();
1264 #endif
1267 static int start()
1269 ipAddress.fromString(wifi_ap_address);
1270 return firmwareOptions.wifi_auto_on_interval;
1273 static int event()
1275 if (connectionState == wifiUpdate || connectionState > FAILURE_STATES)
1277 if (!wifiStarted) {
1278 startWiFi(millis());
1279 return DURATION_IMMEDIATELY;
1282 else if (wifiStarted)
1284 wifiStarted = false;
1285 WiFi.disconnect(true);
1286 WiFi.mode(WIFI_OFF);
1287 #if defined(PLATFORM_ESP8266)
1288 WiFi.forceSleepBegin();
1289 #endif
1291 return DURATION_IGNORE;
1294 static int timeout()
1296 if (wifiStarted)
1298 HandleWebUpdate();
1299 HandleMSP2WIFI();
1300 #if defined(PLATFORM_ESP8266)
1301 // When in STA mode, a small delay reduces power use from 90mA to 30mA when idle
1302 // In AP mode, it doesn't seem to make a measurable difference, but does not hurt
1303 // Only done on 8266 as the ESP32 runs a throttled task
1304 if (!Update.isRunning())
1305 delay(1);
1306 return DURATION_IMMEDIATELY;
1307 #else
1308 // All the web traffic is async apart from changing modes and MSP2WIFI
1309 // No need to run balls-to-the-wall; the wifi runs on this core too (0)
1310 return 2;
1311 #endif
1314 #if defined(TARGET_TX)
1315 // if webupdate was requested before or .wifi_auto_on_interval has elapsed but uart is not detected
1316 // start webupdate, there might be wrong configuration flashed.
1317 if(firmwareOptions.wifi_auto_on_interval != -1 && webserverPreventAutoStart == false && connectionState < wifiUpdate && !wifiStarted){
1318 DBGLN("No CRSF ever detected, starting WiFi");
1319 setWifiUpdateMode();
1320 return DURATION_IMMEDIATELY;
1322 #elif defined(TARGET_RX)
1323 if (firmwareOptions.wifi_auto_on_interval != -1 && !webserverPreventAutoStart && (connectionState == disconnected))
1325 static bool pastAutoInterval = false;
1326 // If InBindingMode then wait at least 60 seconds before going into wifi,
1327 // regardless of if .wifi_auto_on_interval is set to less
1328 if (!InBindingMode || firmwareOptions.wifi_auto_on_interval >= 60000 || pastAutoInterval)
1330 setWifiUpdateMode();
1331 return DURATION_IMMEDIATELY;
1333 pastAutoInterval = true;
1334 return (60000 - firmwareOptions.wifi_auto_on_interval);
1336 #endif
1337 return DURATION_NEVER;
1340 device_t WIFI_device = {
1341 .initialize = initialize,
1342 .start = start,
1343 .event = event,
1344 .timeout = timeout
1347 #endif