Merge pull request #1269 from pkendall64/crsf-max-output
[ExpressLRS.git] / src / lib / WIFI / devWIFI.cpp
blob815e9356d93a93a2150f079752f7858459ff791f
1 #include "device.h"
3 #if defined(PLATFORM_ESP8266) || defined(PLATFORM_ESP32)
5 #if defined(PLATFORM_ESP32)
6 #include <WiFi.h>
7 #include <ESPmDNS.h>
8 #include <Update.h>
9 #else
10 #include <ESP8266WiFi.h>
11 #include <ESP8266mDNS.h>
12 #define wifi_mode_t WiFiMode_t
13 #endif
14 #include <DNSServer.h>
16 #include <set>
17 #include <StreamString.h>
19 #include <ESPAsyncWebServer.h>
21 #include "common.h"
22 #include "POWERMGNT.h"
23 #include "hwTimer.h"
24 #include "logging.h"
25 #include "options.h"
26 #include "helpers.h"
28 #include "WebContent.h"
30 #if defined(Regulatory_Domain_AU_915) || defined(Regulatory_Domain_EU_868) || defined(Regulatory_Domain_IN_866) || defined(Regulatory_Domain_FCC_915) || defined(Regulatory_Domain_AU_433) || defined(Regulatory_Domain_EU_433)
31 #include "SX127xDriver.h"
32 extern SX127xDriver Radio;
33 #endif
35 #if defined(Regulatory_Domain_ISM_2400)
36 #include "SX1280Driver.h"
37 extern SX1280Driver Radio;
38 #endif
40 #include "config.h"
41 #if defined(TARGET_TX)
42 extern TxConfig config;
43 #else
44 extern RxConfig config;
45 #endif
46 extern unsigned long rebootTime;
48 static bool wifiStarted = false;
49 bool webserverPreventAutoStart = false;
50 extern bool InBindingMode;
52 static wl_status_t laststatus = WL_IDLE_STATUS;
53 static volatile WiFiMode_t wifiMode = WIFI_OFF;
54 static volatile WiFiMode_t changeMode = WIFI_OFF;
55 static volatile unsigned long changeTime = 0;
57 static const byte DNS_PORT = 53;
58 static IPAddress netMsk(255, 255, 255, 0);
59 static DNSServer dnsServer;
60 static IPAddress ipAddress;
62 static AsyncWebServer server(80);
63 static bool servicesStarted = false;
65 static bool target_seen = false;
66 static uint8_t target_pos = 0;
67 static String target_found;
68 static bool target_complete = false;
69 static bool force_update = false;
70 static uint32_t totalSize;
72 /** Is this an IP? */
73 static boolean isIp(String str)
75 for (size_t i = 0; i < str.length(); i++)
77 int c = str.charAt(i);
78 if (c != '.' && (c < '0' || c > '9'))
80 return false;
83 return true;
86 /** IP to String? */
87 static String toStringIp(IPAddress ip)
89 String res = "";
90 for (int i = 0; i < 3; i++)
92 res += String((ip >> (8 * i)) & 0xFF) + ".";
94 res += String(((ip >> 8 * 3)) & 0xFF);
95 return res;
98 static bool captivePortal(AsyncWebServerRequest *request)
100 extern const char *wifi_hostname;
102 if (!isIp(request->host()) && request->host() != (String(wifi_hostname) + ".local"))
104 DBGLN("Request redirected to captive portal");
105 request->redirect(String("http://") + toStringIp(request->client()->localIP()));
106 return true;
108 return false;
111 static void WebUpdateSendCSS(AsyncWebServerRequest *request)
113 AsyncWebServerResponse *response = request->beginResponse_P(200, "text/css", (uint8_t*)CSS, sizeof(CSS));
114 response->addHeader("Content-Encoding", "gzip");
115 request->send(response);
118 static void WebUpdateSendJS(AsyncWebServerRequest *request)
120 AsyncWebServerResponse *response = request->beginResponse_P(200, "text/javascript", (uint8_t*)SCAN_JS, sizeof(SCAN_JS));
121 response->addHeader("Content-Encoding", "gzip");
122 request->send(response);
125 static void WebUpdateSendFlag(AsyncWebServerRequest *request)
127 AsyncWebServerResponse *response = request->beginResponse_P(200, "image/svg+xml", (uint8_t*)FLAG, sizeof(FLAG));
128 response->addHeader("Content-Encoding", "gzip");
129 request->send(response);
132 static void WebUpdateHandleRoot(AsyncWebServerRequest *request)
134 if (captivePortal(request))
135 { // If captive portal redirect instead of displaying the page.
136 return;
138 force_update = request->hasArg("force");
139 AsyncWebServerResponse *response = request->beginResponse_P(200, "text/html", (uint8_t*)INDEX_HTML, sizeof(INDEX_HTML));
140 response->addHeader("Cache-Control", "no-cache, no-store, must-revalidate");
141 response->addHeader("Pragma", "no-cache");
142 response->addHeader("Expires", "-1");
143 response->addHeader("Content-Encoding", "gzip");
144 request->send(response);
147 #if defined(GPIO_PIN_PWM_OUTPUTS)
148 constexpr uint8_t SERVO_PINS[] = GPIO_PIN_PWM_OUTPUTS;
149 constexpr uint8_t SERVO_COUNT = ARRAY_SIZE(SERVO_PINS);
151 static String WebGetPwmStr()
153 // Output is raw integers, the Javascript side needs to parse it
154 // ,"pwm":[49664,50688,51200] = 3 channels, 0=512, 1=512, 2=0
155 String pwmStr(",\"pwm\":[");
156 for (uint8_t ch=0; ch<SERVO_COUNT; ++ch)
158 if (ch > 0)
159 pwmStr.concat(',');
160 pwmStr.concat(config.GetPwmChannel(ch)->raw);
162 pwmStr.concat(']');
164 return pwmStr;
167 static void WebUpdatePwm(AsyncWebServerRequest *request)
169 String pwmStr = request->arg("pwm");
170 if (pwmStr.isEmpty())
172 request->send(400, "text/plain", "Empty pwm parameter");
173 return;
176 // parse out the integers representing the PWM values
177 // strtok will modify the string as it parses
178 char *token = strtok((char *)pwmStr.c_str(), ",");
179 uint8_t channel = 0;
180 while (token != nullptr && channel < SERVO_COUNT)
182 uint16_t val = atoi(token);
183 DBGLN("PWMch(%u)=%u", channel, val);
184 config.SetPwmChannelRaw(channel, val);
185 ++channel;
186 token = strtok(nullptr, ",");
188 config.Commit();
189 request->send(200, "text/plain", "PWM outputs updated");
191 #endif
193 static void WebUpdateSendMode(AsyncWebServerRequest *request)
195 String s = String("{\"ssid\":\"") + config.GetSSID() + "\",\"mode\":\"";
196 if (wifiMode == WIFI_STA) {
197 s += "STA\"";
198 } else {
199 s += "AP\"";
201 #if defined(TARGET_RX)
202 s += ",\"modelid\":" + String(config.GetModelId());
203 #endif
204 #if defined(GPIO_PIN_PWM_OUTPUTS)
205 s += WebGetPwmStr();
206 #endif
207 s += "}";
208 request->send(200, "application/json", s);
211 static void WebUpdateGetTarget(AsyncWebServerRequest *request)
213 String s = String("{\"target\":\"") + (const char *)&target_name[4] + "\",\"version\": \"" + VERSION + "\"}";
214 request->send(200, "application/json", s);
217 static void WebUpdateSendNetworks(AsyncWebServerRequest *request)
219 int numNetworks = WiFi.scanComplete();
220 if (numNetworks >= 0) {
221 DBGLN("Found %d networks", numNetworks);
222 std::set<String> vs;
223 String s="[";
224 for(int i=0 ; i<numNetworks ; i++) {
225 String w = WiFi.SSID(i);
226 DBGLN("found %s", w.c_str());
227 if (vs.find(w)==vs.end() && w.length()>0) {
228 if (!vs.empty()) s += ",";
229 s += "\"" + w + "\"";
230 vs.insert(w);
233 s+="]";
234 request->send(200, "application/json", s);
235 } else {
236 request->send(204, "application/json", "[]");
240 static void sendResponse(AsyncWebServerRequest *request, const String &msg, WiFiMode_t mode) {
241 AsyncWebServerResponse *response = request->beginResponse(200, "text/plain", msg);
242 response->addHeader("Connection", "close");
243 request->send(response);
244 request->client()->close();
245 changeTime = millis();
246 changeMode = mode;
249 static void WebUpdateAccessPoint(AsyncWebServerRequest *request)
251 DBGLN("Starting Access Point");
252 String msg = String("Access Point starting, please connect to access point '") + wifi_ap_ssid + "' with password '" + wifi_ap_password + "'";
253 sendResponse(request, msg, WIFI_AP);
256 static void WebUpdateConnect(AsyncWebServerRequest *request)
258 DBGLN("Connecting to home network");
259 String msg = String("Connecting to network '") + config.GetSSID() + "', connect to http://" +
260 wifi_hostname + ".local from a browser on that network";
261 sendResponse(request, msg, WIFI_STA);
264 static void WebUpdateSetHome(AsyncWebServerRequest *request)
266 String ssid = request->arg("network");
267 String password = request->arg("password");
269 DBGLN("Setting home network %s", ssid.c_str());
270 config.SetSSID(ssid.c_str());
271 config.SetPassword(password.c_str());
272 config.Commit();
273 WebUpdateConnect(request);
276 static void WebUpdateForget(AsyncWebServerRequest *request)
278 DBGLN("Forget home network");
279 config.SetSSID("");
280 config.SetPassword("");
281 config.Commit();
282 String msg = String("Home network forgotten, please connect to access point '") + wifi_ap_ssid + "' with password '" + wifi_ap_password + "'";
283 sendResponse(request, msg, WIFI_AP);
286 #if defined(TARGET_RX)
287 static void WebUpdateModelId(AsyncWebServerRequest *request)
289 long modelid = request->arg("modelid").toInt();
290 if (modelid < 0 || modelid > 63) modelid = 255;
291 DBGLN("Setting model match id %u", (uint8_t)modelid);
292 config.SetModelId((uint8_t)modelid);
293 config.Commit();
295 AsyncWebServerResponse *response = request->beginResponse(200, "text/plain", "Model Match updated, rebooting receiver");
296 response->addHeader("Connection", "close");
297 request->send(response);
298 request->client()->close();
299 rebootTime = millis() + 100;
301 #endif
303 static void WebUpdateHandleNotFound(AsyncWebServerRequest *request)
305 if (captivePortal(request))
306 { // If captive portal redirect instead of displaying the error page.
307 return;
309 String message = F("File Not Found\n\n");
310 message += F("URI: ");
311 message += request->url();
312 message += F("\nMethod: ");
313 message += (request->method() == HTTP_GET) ? "GET" : "POST";
314 message += F("\nArguments: ");
315 message += request->args();
316 message += F("\n");
318 for (uint8_t i = 0; i < request->args(); i++)
320 message += String(F(" ")) + request->argName(i) + F(": ") + request->arg(i) + F("\n");
322 AsyncWebServerResponse *response = request->beginResponse(404, "text/plain", message);
323 response->addHeader("Cache-Control", "no-cache, no-store, must-revalidate");
324 response->addHeader("Pragma", "no-cache");
325 response->addHeader("Expires", "-1");
326 request->send(response);
329 static void WebUploadResponseHandler(AsyncWebServerRequest *request) {
330 if (Update.hasError()) {
331 StreamString p = StreamString();
332 Update.printError(p);
333 p.trim();
334 DBGLN("Failed to upload firmware: %s", p.c_str());
335 AsyncWebServerResponse *response = request->beginResponse(200, "application/json", String("{\"status\": \"error\", \"msg\": \"") + p + "\"}");
336 response->addHeader("Connection", "close");
337 request->send(response);
338 request->client()->close();
339 } else {
340 if (target_seen) {
341 DBGLN("Update complete, rebooting");
342 AsyncWebServerResponse *response = request->beginResponse(200, "application/json", "{\"status\": \"ok\", \"msg\": \"Update complete. Please wait for LED to resume blinking before disconnecting power.\"}");
343 response->addHeader("Connection", "close");
344 request->send(response);
345 request->client()->close();
346 rebootTime = millis() + 200;
347 } else {
348 String message = String("{\"status\": \"mismatch\", \"msg\": \"<b>Current target:</b> ") + (const char *)&target_name[4] + ".<br>";
349 if (target_found.length() != 0) {
350 message += "<b>Uploaded image:</b> " + target_found + ".<br/>";
352 message += "<br/>Flashing the wrong firmware may lock or damage your device.\"}";
353 request->send(200, "application/json", message);
358 static void WebUploadDataHandler(AsyncWebServerRequest *request, const String& filename, size_t index, uint8_t *data, size_t len, bool final) {
359 if (index == 0) {
360 DBGLN("Update: %s", filename.c_str());
361 #if defined(PLATFORM_ESP8266)
362 Update.runAsync(true);
363 uint32_t maxSketchSpace = (ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000;
364 DBGLN("Free space = %u", maxSketchSpace);
365 if (!Update.begin(maxSketchSpace, U_FLASH)){//start with max available size
366 #else
367 if (!Update.begin()) { //start with max available size
368 #endif
369 Update.printError(Serial);
371 target_seen = false;
372 target_found.clear();
373 target_complete = false;
374 target_pos = 0;
375 totalSize = 0;
377 if (len) {
378 DBGVLN("writing %d", len);
379 if (Update.write(data, len) == len) {
380 if (force_update || (totalSize == 0 && *data == 0x1F))
381 target_seen = true;
382 if (!target_seen) {
383 for (size_t i=0 ; i<len ;i++) {
384 if (!target_complete && (target_pos >= 4 || target_found.length() > 0)) {
385 if (target_pos == 4) {
386 target_found.clear();
388 if (data[i] == 0 || target_found.length() > 50) {
389 target_complete = true;
391 else {
392 target_found += (char)data[i];
395 if (data[i] == target_name[target_pos]) {
396 ++target_pos;
397 if (target_pos >= target_name_size) {
398 target_seen = true;
401 else {
402 target_pos = 0; // Startover
406 totalSize += len;
409 if (final && !Update.getError()) {
410 DBGVLN("finish");
411 if (target_seen) {
412 if (Update.end(true)) { //true to set the size to the current progress
413 DBGLN("Upload Success: %ubytes\nPlease wait for LED to resume blinking before disconnecting power", totalSize);
414 } else {
415 Update.printError(Serial);
421 static void WebUploadForceUpdateHandler(AsyncWebServerRequest *request) {
422 target_seen = true;
423 if (request->arg("action").equals("confirm")) {
424 if (Update.end(true)) { //true to set the size to the current progress
425 DBGLN("Upload Success: %ubytes\nPlease wait for LED to turn resume blinking before disconnecting power", totalSize);
426 } else {
427 Update.printError(Serial);
429 WebUploadResponseHandler(request);
430 } else {
431 #if defined(PLATFORM_ESP32)
432 Update.abort();
433 #endif
434 request->send(200, "application/json", "{\"status\": \"ok\", \"msg\": \"Update cancelled\"}");
438 static size_t getFirmwareChunk(uint8_t *data, size_t len, size_t pos)
440 uint8_t *dst;
441 uint8_t alignedBuffer[7];
442 if ((uintptr_t)data % 4 != 0)
444 // If data is not aligned, read aligned byes using the local buffer and hope the next call will be aligned
445 dst = (uint8_t *)((uint32_t)alignedBuffer / 4 * 4);
446 len = 4;
448 else
450 // Otherwise just make sure len is a multiple of 4 and smaller than a sector
451 dst = data;
452 len = constrain((len / 4) * 4, 4, SPI_FLASH_SEC_SIZE);
455 ESP.flashRead(pos, (uint32_t *)dst, len);
457 // If using local stack buffer, move the 4 bytes into the passed buffer
458 // data is known to not be aligned so it is moved byte-by-byte instead of as uint32_t*
459 if ((void *)dst != (void *)data)
461 for (unsigned b=len; b>0; --b)
462 *data++ = *dst++;
464 return len;
467 static void WebUpdateGetFirmware(AsyncWebServerRequest *request) {
468 AsyncWebServerResponse *response = request->beginResponse("application/octet-stream", (size_t)ESP.getSketchSize(), &getFirmwareChunk);
469 String filename = String("attachment; filename=\"") + (const char *)&target_name[4] + "_" + VERSION + ".bin\"";
470 response->addHeader("Content-Disposition", filename);
471 request->send(response);
474 static void wifiOff()
476 wifiStarted = false;
477 WiFi.disconnect(true);
478 WiFi.mode(WIFI_OFF);
479 #if defined(PLATFORM_ESP8266)
480 WiFi.forceSleepBegin();
481 #endif
484 static void startWiFi(unsigned long now)
486 if (wifiStarted) {
487 return;
489 hwTimer::stop();
490 // Set transmit power to minimum
491 POWERMGNT::setPower(MinPower);
492 if (connectionState < FAILURE_STATES) {
493 connectionState = wifiUpdate;
496 DBGLN("Stopping Radio");
497 Radio.End();
499 DBGLN("Begin Webupdater");
501 WiFi.persistent(false);
502 WiFi.disconnect();
503 WiFi.mode(WIFI_OFF);
504 #if defined(PLATFORM_ESP8266)
505 WiFi.setOutputPower(13);
506 WiFi.setPhyMode(WIFI_PHY_MODE_11N);
507 #elif defined(PLATFORM_ESP32)
508 WiFi.setTxPower(WIFI_POWER_13dBm);
509 #endif
510 if (config.GetSSID()[0]==0 && home_wifi_ssid[0]!=0) {
511 config.SetSSID(home_wifi_ssid);
512 config.SetPassword(home_wifi_password);
513 config.Commit();
515 if (config.GetSSID()[0]==0) {
516 changeTime = now;
517 changeMode = WIFI_AP;
518 } else {
519 changeTime = now;
520 changeMode = WIFI_STA;
522 laststatus = WL_DISCONNECTED;
523 wifiStarted = true;
526 static void startMDNS()
528 if (!MDNS.begin(wifi_hostname))
530 DBGLN("Error starting mDNS");
531 return;
534 String instance = String(wifi_hostname) + "_" + WiFi.macAddress();
535 instance.replace(":", "");
536 #ifdef PLATFORM_ESP8266
537 // We have to do it differently on ESP8266 as setInstanceName has the side-effect of chainging the hostname!
538 MDNS.setInstanceName(wifi_hostname);
539 MDNSResponder::hMDNSService service = MDNS.addService(instance.c_str(), "http", "tcp", 80);
540 MDNS.addServiceTxt(service, "vendor", "elrs");
541 MDNS.addServiceTxt(service, "target", (const char *)&target_name[4]);
542 MDNS.addServiceTxt(service, "version", VERSION);
543 MDNS.addServiceTxt(service, "options", String(FPSTR(compile_options)).c_str());
544 MDNS.addServiceTxt(service, "type", "rx");
545 // If the probe result fails because there is another device on the network with the same name
546 // use our unique instance name as the hostname. A better way to do this would be to use
547 // MDNSResponder::indexDomain and change wifi_hostname as well.
548 MDNS.setHostProbeResultCallback([instance](const char* p_pcDomainName, bool p_bProbeResult) {
549 if (!p_bProbeResult) {
550 WiFi.hostname(instance);
551 MDNS.setInstanceName(instance);
554 #else
555 MDNS.setInstanceName(instance);
556 MDNS.addService("http", "tcp", 80);
557 MDNS.addServiceTxt("http", "tcp", "vendor", "elrs");
558 MDNS.addServiceTxt("http", "tcp", "target", (const char *)&target_name[4]);
559 MDNS.addServiceTxt("http", "tcp", "device", device_name);
560 MDNS.addServiceTxt("http", "tcp", "version", VERSION);
561 MDNS.addServiceTxt("http", "tcp", "options", String(FPSTR(compile_options)).c_str());
562 MDNS.addServiceTxt("http", "tcp", "type", "tx");
563 #endif
566 static void startServices()
568 if (servicesStarted) {
569 #if defined(PLATFORM_ESP32)
570 MDNS.end();
571 startMDNS();
572 #endif
573 return;
576 server.on("/", WebUpdateHandleRoot);
577 server.on("/main.css", WebUpdateSendCSS);
578 server.on("/scan.js", WebUpdateSendJS);
579 server.on("/logo.svg", WebUpdateSendFlag);
580 server.on("/mode.json", WebUpdateSendMode);
581 server.on("/networks.json", WebUpdateSendNetworks);
582 server.on("/sethome", WebUpdateSetHome);
583 server.on("/forget", WebUpdateForget);
584 server.on("/connect", WebUpdateConnect);
585 server.on("/access", WebUpdateAccessPoint);
586 server.on("/target", WebUpdateGetTarget);
587 server.on("/firmware.bin", WebUpdateGetFirmware);
589 server.on("/generate_204", WebUpdateHandleRoot); // handle Andriod phones doing shit to detect if there is 'real' internet and possibly dropping conn.
590 server.on("/gen_204", WebUpdateHandleRoot);
591 server.on("/library/test/success.html", WebUpdateHandleRoot);
592 server.on("/hotspot-detect.html", WebUpdateHandleRoot);
593 server.on("/connectivity-check.html", WebUpdateHandleRoot);
594 server.on("/check_network_status.txt", WebUpdateHandleRoot);
595 server.on("/ncsi.txt", WebUpdateHandleRoot);
596 server.on("/fwlink", WebUpdateHandleRoot);
598 server.on("/update", HTTP_POST, WebUploadResponseHandler, WebUploadDataHandler);
599 server.on("/forceupdate", WebUploadForceUpdateHandler);
601 #if defined(TARGET_RX)
602 server.on("/model", WebUpdateModelId);
603 #endif
604 #if defined(GPIO_PIN_PWM_OUTPUTS)
605 server.on("/pwm", WebUpdatePwm);
606 #endif
608 server.onNotFound(WebUpdateHandleNotFound);
610 server.begin();
612 dnsServer.start(DNS_PORT, "*", ipAddress);
613 dnsServer.setErrorReplyCode(DNSReplyCode::NoError);
615 startMDNS();
617 servicesStarted = true;
618 DBGLN("HTTPUpdateServer ready! Open http://%s.local in your browser", wifi_hostname);
621 static void HandleWebUpdate()
623 unsigned long now = millis();
624 wl_status_t status = WiFi.status();
625 if (status != laststatus && wifiMode == WIFI_STA) {
626 DBGLN("WiFi status %d", status);
627 switch(status) {
628 case WL_NO_SSID_AVAIL:
629 case WL_CONNECT_FAILED:
630 case WL_CONNECTION_LOST:
631 changeTime = now;
632 changeMode = WIFI_AP;
633 break;
634 case WL_DISCONNECTED: // try reconnection
635 changeTime = now;
636 break;
637 default:
638 break;
640 laststatus = status;
642 if (status != WL_CONNECTED && wifiMode == WIFI_STA && (now - changeTime) > 30000) {
643 changeTime = now;
644 changeMode = WIFI_AP;
645 DBGLN("Connection failed %d", status);
647 if (changeMode != wifiMode && changeMode != WIFI_OFF && (now - changeTime) > 500) {
648 switch(changeMode) {
649 case WIFI_AP:
650 DBGLN("Changing to AP mode");
651 WiFi.disconnect();
652 wifiMode = WIFI_AP;
653 #if defined(PLATFORM_ESP8266)
654 WiFi.mode(WIFI_AP_STA);
655 #else
656 WiFi.mode(WIFI_AP);
657 #endif
658 changeTime = now;
659 WiFi.softAPConfig(ipAddress, ipAddress, netMsk);
660 WiFi.softAP(wifi_ap_ssid, wifi_ap_password);
661 WiFi.scanNetworks(true);
662 startServices();
663 break;
664 case WIFI_STA:
665 DBGLN("Connecting to home network '%s'", config.GetSSID());
666 wifiMode = WIFI_STA;
667 WiFi.mode(wifiMode);
668 WiFi.setHostname(wifi_hostname); // hostname must be set after the mode is set to STA
669 changeTime = now;
670 WiFi.begin(config.GetSSID(), config.GetPassword());
671 startServices();
672 default:
673 break;
675 #if defined(PLATFORM_ESP8266)
676 MDNS.notifyAPChange();
677 #endif
678 changeMode = WIFI_OFF;
681 if (servicesStarted)
683 dnsServer.processNextRequest();
684 #if defined(PLATFORM_ESP8266)
685 MDNS.update();
686 #endif
687 // When in STA mode, a small delay reduces power use from 90mA to 30mA when idle
688 // In AP mode, it doesn't seem to make a measurable difference, but does not hurt
689 if (!Update.isRunning())
690 delay(1);
694 static int start()
696 ipAddress.fromString(wifi_ap_address);
698 #ifdef AUTO_WIFI_ON_INTERVAL
699 return AUTO_WIFI_ON_INTERVAL * 1000;
700 #else
701 return DURATION_NEVER;
702 #endif
705 static int event()
707 if (connectionState == wifiUpdate || connectionState > FAILURE_STATES)
709 if (!wifiStarted) {
710 startWiFi(millis());
711 return DURATION_IMMEDIATELY;
714 return DURATION_IGNORE;
717 static int timeout()
719 if (wifiStarted)
721 HandleWebUpdate();
722 return DURATION_IMMEDIATELY;
725 #if defined(TARGET_TX) && defined(AUTO_WIFI_ON_INTERVAL)
726 //if webupdate was requested before or AUTO_WIFI_ON_INTERVAL has been elapsed but uart is not detected
727 //start webupdate, there might be wrong configuration flashed.
728 if(webserverPreventAutoStart == false && connectionState < wifiUpdate && !wifiStarted){
729 DBGLN("No CRSF ever detected, starting WiFi");
730 connectionState = wifiUpdate;
731 return DURATION_IMMEDIATELY;
733 #elif defined(TARGET_RX) && defined(AUTO_WIFI_ON_INTERVAL)
734 if (!webserverPreventAutoStart && (connectionState == disconnected))
736 static bool pastAutoInterval = false;
737 // If InBindingMode then wait at least 60 seconds before going into wifi,
738 // regardless of if AUTO_WIFI_ON_INTERVAL is set to less
739 if (!InBindingMode || AUTO_WIFI_ON_INTERVAL >= 60 || pastAutoInterval)
741 // No need to ExitBindingMode(), the radio is about to be stopped. Need
742 // to change this before the mode change event so the LED is updated
743 InBindingMode = false;
744 connectionState = wifiUpdate;
745 return DURATION_IMMEDIATELY;
747 pastAutoInterval = true;
748 return (60 - AUTO_WIFI_ON_INTERVAL) * 1000;
750 #endif
751 return DURATION_NEVER;
754 device_t WIFI_device = {
755 .initialize = wifiOff,
756 .start = start,
757 .event = event,
758 .timeout = timeout
761 #endif