3 #if defined(PLATFORM_ESP8266) || defined(PLATFORM_ESP32)
8 #include <ArduinoJson.h>
9 #if defined(PLATFORM_ESP8266)
15 #if defined(PLATFORM_ESP32)
19 #include <esp_partition.h>
20 #include <esp_ota_ops.h>
21 #include <soc/uart_pins.h>
23 #include <ESP8266WiFi.h>
24 #include <ESP8266mDNS.h>
25 #define wifi_mode_t WiFiMode_t
27 #include <DNSServer.h>
30 #include <StreamString.h>
32 #include "ArduinoJson.h"
33 #include "AsyncJson.h"
34 #include <ESPAsyncWebServer.h>
37 #include "POWERMGNT.h"
43 #include "devButton.h"
44 #if defined(TARGET_RX) && defined(PLATFORM_ESP32)
45 #include "devVTXSPI.h"
48 #include "WebContent.h"
52 #if defined(RADIO_LR1121)
56 #if defined(TARGET_TX)
57 #include "wifiJoystick.h"
59 extern TxConfig config
;
60 extern void setButtonColors(uint8_t b1
, uint8_t b2
);
62 extern RxConfig config
;
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
87 #include "tcpsocket.h"
88 TCPSOCKET
wifi2tcp(5761); //port 5761 as used by BF configurator
91 #if defined(PLATFORM_ESP8266)
92 static bool scanComplete
= false;
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'))
130 static String
toStringIp(IPAddress ip
)
133 for (int i
= 0; i
< 3; i
++)
135 res
+= String((ip
>> (8 * i
)) & 0xFF) + ".";
137 res
+= String(((ip
>> 8 * 3)) & 0xFF);
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()));
156 const char *contentType
;
157 const uint8_t* content
;
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
)},
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
);
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.
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
));
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
)
213 if (!file
|| request
->url() != file
.name()) {
214 file
= SPIFFS
.open(request
->url(), "w");
217 file
.write(data
, len
);
219 if (bytes
== total
) {
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());
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.");
269 File file
= SPIFFS
.open("/options.json", "w");
270 serializeJson(json
, file
);
274 static const char *GetConfigUidType(const JsonObject json
)
276 #if defined(TARGET_RX)
277 if (config
.GetBindStorage() == BINDSTORAGE_VOLATILE
)
279 if (config
.GetBindStorage() == BINDSTORAGE_RETURNABLE
&& config
.IsOnLoan())
281 if (config
.GetIsBound())
285 if (firmwareOptions
.hasUID
)
287 if (json
["options"]["customised"] | false)
292 return "Not set (using MAC address)";
296 static void GetConfiguration(AsyncWebServerRequest
*request
)
298 bool exportMode
= request
->hasArg("export");
299 AsyncJsonResponse
*response
= new AsyncJsonResponse();
300 JsonObject json
= response
->getRoot();
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
)
316 if (GPIO_PIN_BUTTON2
!= UNDEF_PIN
)
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
;
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 */
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();
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)
395 json
["config"]["pwm"][ch
]["features"] = features
;
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
);
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
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
);
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
);
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
);
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
);
548 request
->send(200, "text/plain", "Configuration updated");
552 static void WebUpdateGetTarget(AsyncWebServerRequest
*request
)
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";
564 #if defined(TARGET_RX)
565 json
["module-type"] = "RX";
567 #if defined(RADIO_SX128X)
568 json
["radio-type"] = "SX128X";
569 json
["has-sub-ghz"] = false;
571 #if defined(RADIO_SX127X)
572 json
["radio-type"] = "SX127X";
573 json
["has-sub-ghz"] = true;
575 #if defined(RADIO_LR1121)
576 json
["radio-type"] = "LR1121";
577 json
["has-sub-ghz"] = true;
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
);
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
+ "\"";
602 request
->send(200, "application/json", s
);
604 if (WiFi
.scanComplete() != WIFI_SCAN_RUNNING
)
606 #if defined(PLATFORM_ESP8266)
607 scanComplete
= false;
608 WiFi
.scanNetworksAsync([](int){
612 WiFi
.scanNetworks(true);
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();
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
));
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;
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.
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();
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()) {
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.\"}";
712 msg
+= "Please wait for a few seconds while the device reboots.\"}";
714 rebootTime
= millis() + 200;
716 StreamString p
= StreamString();
717 if (Update
.hasError()) {
718 Update
.printError(p
);
720 p
.println("Not enough data uploaded!");
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();
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");
744 #if defined(TARGET_TX) && defined(PLATFORM_ESP32)
745 WifiJoystick::StopJoystickService();
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
756 if (!Update
.begin(filesize
, U_FLASH
)) { // pass the size provided
757 Update
.printError(LOGGING_UART
);
760 target_found
.clear();
761 target_complete
= false;
766 DBGVLN("writing %d", len
);
767 if (Update
.write(data
, len
) == len
) {
768 if (force_update
|| (totalSize
== 0 && *data
== 0x1F))
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;
780 target_found
+= (char)data
[i
];
783 if (data
[i
] == target_name
[target_pos
]) {
785 if (target_pos
>= target_name_size
) {
790 target_pos
= 0; // Startover
796 DBGLN("write failed to write %d", len
);
801 static void WebUploadForceUpdateHandler(AsyncWebServerRequest
*request
) {
803 if (request
->arg("action").equals("confirm")) {
804 WebUploadResponseHandler(request
);
806 #if defined(PLATFORM_ESP32)
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");
831 static size_t firmwareOffset
= 0;
832 static size_t getFirmwareChunk(uint8_t *data
, size_t len
, size_t pos
)
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);
844 // Otherwise just make sure len is a multiple of 4 and smaller than a sector
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
)
861 static void WebUpdateGetFirmware(AsyncWebServerRequest
*request
) {
862 #if defined(PLATFORM_ESP32)
863 const esp_partition_t
*running
= esp_ota_get_running_partition();
865 firmwareOffset
= running
->address
;
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;
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());
893 POWERMGNT::setPower(POWERMGNT::getMinPower());
895 #if defined(RADIO_LR1121)
896 Radio
.startCWTest(setSubGHz
? FHSSconfig
->freq_center
: FHSSconfigDualBand
->freq_center
, radio
);
898 Radio
.startCWTest(FHSSconfig
->freq_center
, radio
);
899 #if defined(RADIO_SX127X)
900 deferExecutionMillis(50, [radio
](){ Radio
.cwRepeat(radio
); });
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
+
913 static void initialize()
916 WiFi
.disconnect(true);
918 #if defined(PLATFORM_ESP8266)
919 WiFi
.forceSleepBegin();
921 registerButtonFunction(ACTION_START_WIFI
, [](){
926 static void startWiFi(unsigned long now
)
932 if (connectionState
< FAILURE_STATES
) {
934 #if defined(TARGET_RX) && defined(PLATFORM_ESP32)
938 // Set transmit power to minimum
939 POWERMGNT::setPower(MinPower
);
943 DBGLN("Stopping Radio");
947 DBGLN("Begin Webupdater");
949 WiFi
.persistent(false);
952 strcpy(station_ssid
, firmwareOptions
.home_wifi_ssid
);
953 strcpy(station_password
, firmwareOptions
.home_wifi_password
);
954 if (station_ssid
[0] == 0) {
956 changeMode
= WIFI_AP
;
960 changeMode
= WIFI_STA
;
962 laststatus
= WL_DISCONNECTED
;
966 static void startMDNS()
968 if (!MDNS
.begin(wifi_hostname
))
970 DBGLN("Error starting mDNS");
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
);
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
);
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
);
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");
1027 MDNS
.addServiceTxt("http", "tcp", "type", "rx");
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());
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)
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
);
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));
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
);
1118 addCaptivePortalHandlers();
1120 server
.onNotFound(WebUpdateHandleNotFound
);
1124 dnsServer
.start(DNS_PORT
, "*", ipAddress
);
1125 dnsServer
.setErrorReplyCode(DNSReplyCode::NoError
);
1129 #if defined(TARGET_TX) && defined(PLATFORM_ESP32)
1130 WifiJoystick::StartJoystickService();
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)
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
);
1148 case WL_NO_SSID_AVAIL
:
1149 case WL_CONNECT_FAILED
:
1150 case WL_CONNECTION_LOST
:
1152 changeMode
= WIFI_AP
;
1154 case WL_DISCONNECTED
: // try reconnection
1160 laststatus
= status
;
1162 if (status
!= WL_CONNECTED
&& wifiMode
== WIFI_STA
&& (now
- changeTime
) > 30000) {
1164 changeMode
= WIFI_AP
;
1165 DBGLN("Connection failed %d", status
);
1167 if (changeMode
!= wifiMode
&& changeMode
!= WIFI_OFF
&& (now
- changeTime
) > 500) {
1168 switch(changeMode
) {
1170 DBGLN("Changing to AP mode");
1173 #if defined(PLATFORM_ESP32)
1174 WiFi
.setHostname(wifi_hostname
); // hostname must be set before the mode is set to STA
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
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
);
1187 WiFi
.softAPConfig(ipAddress
, ipAddress
, netMsk
);
1188 WiFi
.softAP(wifi_ap_ssid
, wifi_ap_password
);
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
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
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
);
1210 WiFi
.begin(station_ssid
, station_password
);
1215 #if defined(PLATFORM_ESP8266)
1216 MDNS
.notifyAPChange();
1218 changeMode
= WIFI_OFF
;
1221 #if defined(PLATFORM_ESP8266)
1224 WiFi
.mode(wifiMode
);
1225 scanComplete
= false;
1229 if (servicesStarted
)
1231 dnsServer
.processNextRequest();
1232 #if defined(PLATFORM_ESP8266)
1236 #if defined(TARGET_TX) && defined(PLATFORM_ESP32)
1237 WifiJoystick::Loop(now
);
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();
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();
1258 uint8_t data
[bytesReady
];
1259 wifi2tcp
.read(data
);
1260 msp2crsf
.parse(data
, bytesReady
);
1269 ipAddress
.fromString(wifi_ap_address
);
1270 return firmwareOptions
.wifi_auto_on_interval
;
1275 if (connectionState
== wifiUpdate
|| connectionState
> FAILURE_STATES
)
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();
1291 return DURATION_IGNORE
;
1294 static int timeout()
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())
1306 return DURATION_IMMEDIATELY
;
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)
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
);
1337 return DURATION_NEVER
;
1340 device_t WIFI_device
= {
1341 .initialize
= initialize
,