diff --git a/TEF6686_ESP32.ino b/TEF6686_ESP32.ino index 5685f1a..f8cbfcd 100644 --- a/TEF6686_ESP32.ino +++ b/TEF6686_ESP32.ino @@ -1,7 +1,11 @@ +#include #include #include -#include // https://github.com/Bodmer/TFT_eSPI -#include // https://github.com/PaulStoffregen/Time +#include // https://github.com/Bodmer/TFT_eSPI +#include // https://github.com/PaulStoffregen/Time +#include // https://github.com/bbx10/Hash_tng +#include "src/WiFiConnect.h" +#include "src/WiFiConnectParam.h" #include "src/font.h" #include "src/TEF6686.h" #include "src/constants.h" @@ -13,8 +17,8 @@ #define FONT7 &Aura2Pro_Light7pt8b #define FONTDEC &Digital7num36pt7b -#define GUI2_FONT8 &Aura2Regular8pt8b -#define GUI2_FONT12 &Aura2Regular12pt8b +//#define GUI2_FONT8 &Aura2Regular8pt8b +//#define GUI2_FONT12 &Aura2Regular12pt8b #define TFT_GREYOUT 0x38E7 #define ROTARY_PIN_A 34 @@ -39,6 +43,7 @@ TFT_eSPI tft = TFT_eSPI(240, 320); #endif bool edgebeep; +bool RDSSpy; bool BWreset; bool change2; bool cleanup; @@ -68,12 +73,18 @@ bool tuned; bool USBstatus; bool USBmode = 1; bool XDRMute; +bool XDRGTKdata; +bool wifi; +bool wificonnected; +bool XDRGTKTCP; byte region; byte regionold; byte language; byte theme; byte tunemode; byte memorypos; +String salt; +String saltkey = " "; byte memoryposold; byte menupage = 1; byte menupagestotal = 2; @@ -146,9 +157,11 @@ String PSold; String PTYold; String rds_clock; String rds_clockold; +String cryptedpassword; String RDSSPYRDS; String RDSSPYRDSold; String RTold; +String XDRGTK_key; String XDRGTKRDS; String XDRGTKRDSold; uint16_t BW; @@ -174,12 +187,15 @@ unsigned long rtticker; TEF6686 radio; TFT_eSprite sprite = TFT_eSprite(&tft); +WiFiConnect wc; +WiFiServer Server(7373); +WiFiClient RemoteClient; void setup() { setupmode = true; - EEPROM.begin(234); - if (EEPROM.readByte(43) != 22) { - EEPROM.writeByte(43, 22); + EEPROM.begin(244); + if (EEPROM.readByte(43) != 23) { + EEPROM.writeByte(43, 23); EEPROM.writeUInt(0, 10000); EEPROM.writeInt(4, 0); EEPROM.writeUInt(8, 0); @@ -210,11 +226,14 @@ void setup() { EEPROM.writeByte(52, 0); EEPROM.writeByte(53, 0); EEPROM.writeByte(54, 0); + EEPROM.writeByte(55, 0); for (int i = 0; i < 30; i++) EEPROM.writeByte(i + 60, 0); for (int i = 0; i < 30; i++) EEPROM.writeUInt((i * 4) + 100, 8750); EEPROM.writeUInt(221, 180); EEPROM.writeUInt(225, 540); EEPROM.writeUInt(229, 1800); + EEPROM.writeString(233, "password"); + EEPROM.commit(); } @@ -248,9 +267,11 @@ void setup() { region = EEPROM.readByte(52); radio.rds.underscore = EEPROM.readByte(53); USBmode = EEPROM.readByte(54); + wifi = EEPROM.readByte(55); frequency_LW = EEPROM.readUInt(221); frequency_MW = EEPROM.readUInt(225); frequency_SW = EEPROM.readUInt(229); + XDRGTK_key = EEPROM.readString(233); LWLowEdgeSet = FREQ_LW_LOW_EDGE_MIN; // later will read from flash LWHighEdgeSet = FREQ_LW_HIGH_EDGE_MAX; // later will read from flash @@ -436,6 +457,11 @@ void setup() { Wire.endTransmission(); } + if (wifi == true) { + tryWiFi(); + delay(2000); + } + SelectBand(); ShowSignalLevel(); ShowBW(); @@ -528,6 +554,7 @@ void loop() { } if (USBmode) RDSSpyRoutine(); else XDRGTKRoutine(); + if (wifi) WiFihandler(); if (menu == true && menuopen == true && menupage == 1 && menuoption == 110) { if (band == BAND_FM) radio.getStatus(SStatus, USN, WAM, OStatus, BW, MStatus); else radio.getStatusAM(SStatus, USN, WAM, OStatus, BW, MStatus); @@ -816,6 +843,7 @@ void ModeButtonPress() { EEPROM.writeByte(52, region); EEPROM.writeByte(53, radio.rds.underscore); EEPROM.writeByte(54, USBmode); + EEPROM.writeByte(55, wifi); EEPROM.commit(); Serial.end(); if (USBmode) Serial.begin(19200); else Serial.begin(115200); @@ -1093,9 +1121,42 @@ void ButtonPress() { tft.setTextColor(TFT_YELLOW); if (USBmode) tft.drawCentreString("RDS Spy", 155, 110, GFXFF); else tft.drawCentreString("XDR-GTK", 155, 110, GFXFF); break; + + case 190: + tft.setTextColor(TFT_WHITE); + tft.drawCentreString(myLanguage[language][51], 155, 70, GFXFF); + tft.setTextColor(TFT_YELLOW); + if (wifi) tft.drawCentreString(myLanguage[language][42], 155, 110, GFXFF); else tft.drawCentreString(myLanguage[language][30], 155, 110, GFXFF); + break; + + case 210: + tft.setTextColor(TFT_WHITE); + tft.drawCentreString(myLanguage[language][53], 155, 50, GFXFF); + tft.drawCentreString("ESP_" + String(ESP_getChipId()), 155, 90, GFXFF); + tft.drawCentreString(myLanguage[language][54], 155, 130, GFXFF); + char key [9]; + XDRGTK_key.toCharArray(key, 9); + WiFiConnectParam XDRGTK_key_text("Set XDRGTK Password: (max 8 characters)"); + WiFiConnectParam XDRGTK_key_input("XDRGTK_key", "Password", key, 9); + wc.addParameter(&XDRGTK_key_text); + wc.addParameter(&XDRGTK_key_input); + wc.startConfigurationPortal(AP_WAIT); + XDRGTK_key = XDRGTK_key_input.getValue(); + EEPROM.writeString(233, XDRGTK_key); + EEPROM.commit(); + tryWiFi(); + delay(2000); + BuildMenu(); + break; } } } else { + Serial.println(menupage); + Serial.println(menuoption); + if (menupage == 2 && menuoption == 190 && wifi == true) { + tryWiFi(); + delay(2000); + } menuopen = false; BuildMenu(); } @@ -1335,6 +1396,13 @@ void KeyUp() { if (USBmode) tft.drawCentreString("RDS Spy", 155, 110, GFXFF); else tft.drawCentreString("XDR-GTK", 155, 110, GFXFF); break; + case 190: + tft.setTextColor(TFT_BLACK); + if (wifi) tft.drawCentreString(myLanguage[language][42], 155, 110, GFXFF); else tft.drawCentreString(myLanguage[language][30], 155, 110, GFXFF); + if (wifi) wifi = false; else wifi = true; + tft.setTextColor(TFT_YELLOW); + if (wifi) tft.drawCentreString(myLanguage[language][42], 155, 110, GFXFF); else tft.drawCentreString(myLanguage[language][30], 155, 110, GFXFF); + break; } } } @@ -1574,6 +1642,14 @@ void KeyDown() { tft.setTextColor(TFT_YELLOW); if (USBmode) tft.drawCentreString("RDS Spy", 155, 110, GFXFF); else tft.drawCentreString("XDR-GTK", 155, 110, GFXFF); break; + + case 190: + tft.setTextColor(TFT_BLACK); + if (wifi) tft.drawCentreString(myLanguage[language][42], 155, 110, GFXFF); else tft.drawCentreString(myLanguage[language][30], 155, 110, GFXFF); + if (wifi) wifi = false; else wifi = true; + tft.setTextColor(TFT_YELLOW); + if (wifi) tft.drawCentreString(myLanguage[language][42], 155, 110, GFXFF); else tft.drawCentreString(myLanguage[language][30], 155, 110, GFXFF); + break; } } } @@ -1869,6 +1945,8 @@ void BuildMenu() { tft.drawString(myLanguage[language][46], 14, 130, GFXFF); tft.drawString(myLanguage[language][49], 14, 150, GFXFF); tft.drawString(myLanguage[language][50], 14, 170, GFXFF); + if (wifi) tft.drawString(String(myLanguage[language][51]) + " IP: " + String(WiFi.localIP().toString()), 14, 190, GFXFF); else tft.drawString(myLanguage[language][51], 14, 190, GFXFF); + tft.drawString(myLanguage[language][52], 14, 210, GFXFF); tft.setTextColor(TFT_YELLOW); tft.drawRightString(myLanguage[language][0], 305, 30, GFXFF); if (showrdserrors) tft.drawRightString(myLanguage[language][42], 305, 50, GFXFF); else tft.drawRightString(myLanguage[language][30], 305, 50, GFXFF); @@ -1879,6 +1957,8 @@ void BuildMenu() { if (region == 1) tft.drawRightString(myLanguage[language][48], 305, 130, GFXFF); if (radio.rds.underscore) tft.drawRightString(myLanguage[language][42], 305, 150, GFXFF); else tft.drawRightString(myLanguage[language][30], 305, 150, GFXFF); if (USBmode) tft.drawRightString("RDS Spy", 305, 170, GFXFF); else tft.drawRightString("XDR-GTK", 305, 170, GFXFF); + if (wifi) tft.drawRightString(myLanguage[language][42], 305, 190, GFXFF); else tft.drawRightString(myLanguage[language][30], 305, 190, GFXFF); + tft.drawRightString("→", 305, 210, GFXFF); break; } analogWrite(SMETERPIN, 0); @@ -1982,7 +2062,8 @@ void BuildDisplay() { // WORKING ON THIS! - if (theme == 1) { + /* + if (theme == 1) { tft.setFreeFont(FONT7); tft.fillScreen(TFT_BLACK); tft.fillRect(12, 105, 2, 50, TFT_RED); @@ -2083,8 +2164,8 @@ void BuildDisplay() { tft.drawString("20 50 70 100 120", 204, 220, GFXFF); for (;;); - } - + } + */ RDSstatusold = false; Stereostatusold = false; ShowFreq(0); @@ -2771,320 +2852,346 @@ void RDSSpyRoutine() { } } -void XDRGTKRoutine() { - if (Serial.available()) { - buff[buff_pos] = Serial.read(); - if (buff[buff_pos] != '\n' && buff_pos != 16 - 1) { - buff_pos++; - } else { - buff[buff_pos] = 0; - buff_pos = 0; +void XDRGTKprint(String string) { + if (USBmode == 0 && USBstatus == true) Serial.print(string); + if (XDRGTKTCP) RemoteClient.print(string); +} - switch (buff[0]) { - case 'x': - Serial.println("OK"); +void XDRGTKRoutine() { + if (!USBmode) { + if (Serial.available()) + { + buff[buff_pos] = Serial.read(); + if (buff[buff_pos] != '\n' && buff_pos != 16 - 1) + { + buff_pos++; + } else { + buff[buff_pos] = 0; + buff_pos = 0; + XDRGTKdata = true; + } + } + } + + if (XDRGTKTCP) { + if (RemoteClient.available() > 0) { + buff[buff_pos] = RemoteClient.read(); + if (buff[buff_pos] != '\n' && buff_pos != 16 - 1) + { + buff_pos++; + } else { + buff[buff_pos] = 0; + buff_pos = 0; + XDRGTKdata = true; + } + } + } + + if (XDRGTKdata) { + switch (buff[0]) { + case 'x': + XDRGTKprint("OK\n"); + if (band != BAND_FM) { + band = BAND_FM; + SelectBand(); + } + XDRGTKprint("T" + String(frequency * 10) + "A0\nD0\nG00\n"); + USBstatus = true; + ShowUSBstatus(); + if (menu == true) ModeButtonPress(); + if (Squelch != Squelchold) { + if (screenmute == false) { + tft.setFreeFont(FONT7); + tft.setTextColor(TFT_BLACK); + if (Squelchold == -100) tft.drawCentreString(myLanguage[language][33], 224, 167, GFXFF); else if (Squelchold > 920) tft.drawCentreString("ST", 224, 167, GFXFF); else tft.drawCentreString(String(Squelchold / 10), 224, 167, GFXFF); + } + } + break; + + case 'A': + AGC = atol(buff + 1); + XDRGTKprint("A" + String(AGC) + "\n"); + radio.setAGC(AGC); + break; + + case 'C': + byte scanmethod; + scanmethod = atol(buff + 1); + if (seek == false) { + if (scanmethod == 1) { + XDRGTKprint("C1\n"); + direction = true; + seek = true; + Seek(direction); + } + if (scanmethod == 2) { + XDRGTKprint("C2\n"); + direction = false; + seek = true; + Seek(direction); + } + } else { + seek = false; + } + XDRGTKprint("C0\n"); + break; + + case 'N': + doStereoToggle(); + break; + + case 'D': + DeEmphasis = atol(buff + 1); + XDRGTKprint("D" + String(DeEmphasis) + "\n"); + radio.setDeemphasis(DeEmphasis); + break; + + case 'F': + XDRBWset = atol(buff + 1); + if (XDRBWset < 16) { + XDRBWsetold = XDRBWset; + BWset = XDRBWset + 1; + } else { + XDRBWset = XDRBWsetold; + } + doBW(); + XDRGTKprint("F" + String(XDRBWset) + "\n"); + break; + + case 'G': + LevelOffset = atol(buff + 1); + if (LevelOffset == 0) { + MuteScreen(0); + LowLevelSet = EEPROM.readInt(47); + XDRGTKprint("G00\n"); + } + if (LevelOffset == 10) { + MuteScreen(1); + LowLevelSet = EEPROM.readInt(47); + XDRGTKprint("G10\n"); + } + if (LevelOffset == 1) { + MuteScreen(0); + LowLevelSet = 120; + XDRGTKprint("G01\n"); + } + if (LevelOffset == 11) { + LowLevelSet = 120; + MuteScreen(1); + XDRGTKprint("G11\n"); + } + break; + + case 'M': + byte XDRband; + XDRband = atol(buff + 1); + if (XDRband == BAND_FM) { // here XDRGTK need add four bands switch too + band = BAND_FM; + SelectBand(); + XDRGTKprint("M0\nT" + String(frequency * 10) + "\n"); + } else if (XDRband == BAND_LW) { + band = BAND_LW; + SelectBand(); + XDRGTKprint("M1\nT" + String(frequency_AM) + "\n"); + } else if (XDRband == BAND_MW) { + band = BAND_MW; + SelectBand(); + XDRGTKprint("M2\nT" + String(frequency_AM) + "\n"); + } else if (XDRband == BAND_SW) { + band = BAND_SW; + SelectBand(); + XDRGTKprint("M3\nT" + String(frequency_AM) + "\n"); + } + break; + + case 'T': + unsigned int freqtemp; + freqtemp = atoi(buff + 1); + if (seek == true) seek = false; + if (freqtemp >= LWLowEdgeSet && freqtemp <= LWHighEdgeSet) { + frequency_AM = freqtemp; + if (band != BAND_LW) { + band = BAND_LW; + SelectBand(); + } else { + radio.SetFreqAM(frequency_AM); + } + XDRGTKprint("M1\n"); + } else if (freqtemp >= MWLowEdgeSet && freqtemp <= MWHighEdgeSet) { + frequency_AM = freqtemp; + if (band != BAND_MW) { + band = BAND_MW; + SelectBand(); + } else { + radio.SetFreqAM(frequency_AM); + } + XDRGTKprint("M2\n"); + } else if (freqtemp >= SWLowEdgeSet && freqtemp <= SWHighEdgeSet) { + frequency_AM = freqtemp; + if (band != BAND_SW) { + band = BAND_SW; + SelectBand(); + } else { + radio.SetFreqAM(frequency_AM); + } + XDRGTKprint("M3\n"); + } else if (freqtemp >= FREQ_FM_START && freqtemp < FREQ_FM_END) { + frequency = freqtemp / 10; if (band != BAND_FM) { band = BAND_FM; SelectBand(); - } - Serial.print("T" + String(frequency * 10) + "A0\nD0\nG00\n"); - USBstatus = true; - ShowUSBstatus(); - if (menu == true) ModeButtonPress(); - if (Squelch != Squelchold) { - if (screenmute == false) { - tft.setFreeFont(FONT7); - tft.setTextColor(TFT_BLACK); - if (Squelchold == -100) tft.drawCentreString(myLanguage[language][33], 224, 167, GFXFF); else if (Squelchold > 920) tft.drawCentreString("ST", 224, 167, GFXFF); else tft.drawCentreString(String(Squelchold / 10), 224, 167, GFXFF); - } - } - break; - - case 'A': - AGC = atol(buff + 1); - Serial.print("A" + String(AGC) + "\n"); - radio.setAGC(AGC); - break; - - case 'C': - byte scanmethod; - scanmethod = atol(buff + 1); - if (seek == false) { - if (scanmethod == 1) { - Serial.print("C1\n"); - direction = true; - seek = true; - Seek(direction); - } - if (scanmethod == 2) { - Serial.print("C2\n"); - direction = false; - seek = true; - Seek(direction); - } + XDRGTKprint("M0\n"); } else { - seek = false; + radio.SetFreq(frequency); } - Serial.print("C0\n"); - break; + } + if (band == BAND_FM) XDRGTKprint("T" + String(frequency * 10) + "\n"); else XDRGTKprint("T" + String(frequency_AM) + "\n"); + radio.clearRDS(fullsearchrds); + RDSstatus = 0; + ShowFreq(0); + break; - case 'N': - doStereoToggle(); - break; - - case 'D': - DeEmphasis = atol(buff + 1); - Serial.print("D" + String(DeEmphasis) + "\n"); - radio.setDeemphasis(DeEmphasis); - break; - - case 'F': - XDRBWset = atol(buff + 1); - if (XDRBWset < 16) { - XDRBWsetold = XDRBWset; - BWset = XDRBWset + 1; - } else { - XDRBWset = XDRBWsetold; + case 'S': + if (buff[1] == 'a') { + scanner_start = (atol(buff + 2) + 5) / 10; + } else if (buff[1] == 'b') { + scanner_end = (atol(buff + 2) + 5) / 10; + } else if (buff[1] == 'c') { + scanner_step = (atol(buff + 2) + 5) / 10; + } else if (buff[1] == 'f') { + scanner_filter = atol(buff + 2); + } else if (scanner_start > 0 && scanner_end > 0 && scanner_step > 0 && scanner_filter >= 0) { + frequencyold = frequency; + radio.SetFreq(scanner_start); + XDRGTKprint("U"); + if (scanner_filter < 0) { + BWset = 0; + } else if (scanner_filter == 0) { + BWset = 1; + } else if (scanner_filter == 26) { + BWset = 2; + } else if (scanner_filter == 1) { + BWset = 3; + } else if (scanner_filter == 28) { + BWset = 4; + } else if (scanner_filter == 29) { + BWset = 5; + } else if (scanner_filter == 3) { + BWset = 6; + } else if (scanner_filter == 4) { + BWset = 7; + } else if (scanner_filter == 5) { + BWset = 8; + } else if (scanner_filter == 7) { + BWset = 9; + } else if (scanner_filter == 8) { + BWset = 10; + } else if (scanner_filter == 9) { + BWset = 11; + } else if (scanner_filter == 10) { + BWset = 12; + } else if (scanner_filter == 11) { + BWset = 13; + } else if (scanner_filter == 12) { + BWset = 14; + } else if (scanner_filter == 13) { + BWset = 15; + } else if (scanner_filter == 15) { + BWset = 16; } doBW(); - Serial.print("F" + String(XDRBWset) + "\n"); - break; + if (screenmute == false) { + ShowFreq(1); + tft.setTextColor(TFT_WHITE, TFT_BLACK); + tft.setCursor (90, 60); + tft.setFreeFont(FONT14); + tft.drawCentreString(myLanguage[language][34], 140, 60, GFXFF); + } + frequencyold = frequency / 10; + for (freq_scan = scanner_start; freq_scan <= scanner_end; freq_scan += scanner_step) { + radio.SetFreq(freq_scan); + XDRGTKprint(String(freq_scan * 10, DEC)); + XDRGTKprint("="); + delay(10); + if (band == BAND_FM) radio.getStatus(SStatus, USN, WAM, OStatus, BW, MStatus); else radio.getStatusAM(SStatus, USN, WAM, OStatus, BW, MStatus); + XDRGTKprint(String((SStatus / 10) + 10, DEC)); + XDRGTKprint(","); + } + XDRGTKprint("\n"); + if (screenmute == false) { + tft.setTextColor(TFT_BLACK); + tft.drawCentreString(myLanguage[language][34], 140, 60, GFXFF); + } + radio.SetFreq(frequencyold); + if (screenmute == false) ShowFreq(0); + radio.setFMABandw(); + BWset = 0; + } + break; - case 'G': - LevelOffset = atol(buff + 1); - if (LevelOffset == 0) { - MuteScreen(0); - LowLevelSet = EEPROM.readInt(47); - Serial.print("G00\n"); - } - if (LevelOffset == 10) { - MuteScreen(1); - LowLevelSet = EEPROM.readInt(47); - Serial.print("G10\n"); - } - if (LevelOffset == 1) { - MuteScreen(0); - LowLevelSet = 120; - Serial.print("G01\n"); - } - if (LevelOffset == 11) { - LowLevelSet = 120; - MuteScreen(1); - Serial.print("G11\n"); - } - break; + case 'Y': + VolSet = atoi(buff + 1); + if (VolSet == 0) { + radio.setMute(); + XDRMute = true; + SQ = true; + } else { + radio.setVolume((VolSet - 70) / 10); + XDRMute = false; + } + XDRGTKprint("Y" + String(VolSet) + "\n"); + break; - case 'M': - byte XDRband; - XDRband = atol(buff + 1); - if (XDRband == BAND_FM) { // here XDRGTK need add four bands switch too - band = BAND_FM; - SelectBand(); - Serial.print("M0\nT" + String(frequency * 10) + "\n"); - } else if (XDRband == BAND_LW) { - band = BAND_LW; - SelectBand(); - Serial.print("M1\nT" + String(frequency_AM) + "\n"); - } else if (XDRband == BAND_MW) { - band = BAND_MW; - SelectBand(); - Serial.print("M2\nT" + String(frequency_AM) + "\n"); - } else if (XDRband == BAND_SW) { - band = BAND_SW; - SelectBand(); - Serial.print("M3\nT" + String(frequency_AM) + "\n"); - } - break; + case 'X': + XDRGTKprint("X\n"); + ESP.restart(); + break; - case 'T': - unsigned int freqtemp; - freqtemp = atoi(buff + 1); - if (seek == true) seek = false; - if (freqtemp >= LWLowEdgeSet && freqtemp <= LWHighEdgeSet) { - frequency_AM = freqtemp; - if (band != BAND_LW) { - band = BAND_LW; - SelectBand(); - } else { - radio.SetFreqAM(frequency_AM); - } - Serial.print("M1\n"); - } else if (freqtemp >= MWLowEdgeSet && freqtemp <= MWHighEdgeSet) { - frequency_AM = freqtemp; - if (band != BAND_MW) { - band = BAND_MW; - SelectBand(); - } else { - radio.SetFreqAM(frequency_AM); - } - Serial.print("M2\n"); - } else if (freqtemp >= SWLowEdgeSet && freqtemp <= SWHighEdgeSet) { - frequency_AM = freqtemp; - if (band != BAND_SW) { - band = BAND_SW; - SelectBand(); - } else { - radio.SetFreqAM(frequency_AM); - } - Serial.print("M3\n"); - } else if (freqtemp >= FREQ_FM_START && freqtemp < FREQ_FM_END) { - frequency = freqtemp / 10; - if (band != BAND_FM) { - band = BAND_FM; - SelectBand(); - Serial.print("M0\n"); - } else { - radio.SetFreq(frequency); - } - } - if (band == BAND_FM) Serial.print("T" + String(frequency * 10) + "\n"); else Serial.print("T" + String(frequency_AM) + "\n"); - radio.clearRDS(fullsearchrds); - RDSstatus = 0; - ShowFreq(0); - break; - - case 'S': - if (buff[1] == 'a') { - scanner_start = (atol(buff + 2) + 5) / 10; - } else if (buff[1] == 'b') { - scanner_end = (atol(buff + 2) + 5) / 10; - } else if (buff[1] == 'c') { - scanner_step = (atol(buff + 2) + 5) / 10; - } else if (buff[1] == 'f') { - scanner_filter = atol(buff + 2); - } else if (scanner_start > 0 && scanner_end > 0 && scanner_step > 0 && scanner_filter >= 0) { - frequencyold = frequency; - radio.SetFreq(scanner_start); - Serial.print('U'); - if (scanner_filter < 0) { - BWset = 0; - } else if (scanner_filter == 0) { - BWset = 1; - } else if (scanner_filter == 26) { - BWset = 2; - } else if (scanner_filter == 1) { - BWset = 3; - } else if (scanner_filter == 28) { - BWset = 4; - } else if (scanner_filter == 29) { - BWset = 5; - } else if (scanner_filter == 3) { - BWset = 6; - } else if (scanner_filter == 4) { - BWset = 7; - } else if (scanner_filter == 5) { - BWset = 8; - } else if (scanner_filter == 7) { - BWset = 9; - } else if (scanner_filter == 8) { - BWset = 10; - } else if (scanner_filter == 9) { - BWset = 11; - } else if (scanner_filter == 10) { - BWset = 12; - } else if (scanner_filter == 11) { - BWset = 13; - } else if (scanner_filter == 12) { - BWset = 14; - } else if (scanner_filter == 13) { - BWset = 15; - } else if (scanner_filter == 15) { - BWset = 16; - } - doBW(); - if (screenmute == false) { - ShowFreq(1); - tft.setTextColor(TFT_WHITE, TFT_BLACK); - tft.setCursor (90, 60); - tft.setFreeFont(FONT14); - tft.drawCentreString(myLanguage[language][34], 140, 60, GFXFF); - } - frequencyold = frequency / 10; - for (freq_scan = scanner_start; freq_scan <= scanner_end; freq_scan += scanner_step) { - radio.SetFreq(freq_scan); - Serial.print(freq_scan * 10, DEC); - Serial.print('='); - delay(10); - if (band == BAND_FM) radio.getStatus(SStatus, USN, WAM, OStatus, BW, MStatus); else radio.getStatusAM(SStatus, USN, WAM, OStatus, BW, MStatus); - Serial.print((SStatus / 10) + 10, DEC); - Serial.print(','); - } - Serial.print('\n'); - if (screenmute == false) { - tft.setTextColor(TFT_BLACK); - tft.drawCentreString(myLanguage[language][34], 140, 60, GFXFF); - } - radio.SetFreq(frequencyold); - if (screenmute == false) ShowFreq(0); - radio.setFMABandw(); - BWset = 0; - } - break; - - case 'Y': - VolSet = atoi(buff + 1); - if (VolSet == 0) { - radio.setMute(); - XDRMute = true; - SQ = true; - } else { - radio.setVolume((VolSet - 70) / 10); - XDRMute = false; - } - Serial.print("Y" + String(VolSet) + "\n"); - break; - - case 'X': - Serial.print("X\n"); - ESP.restart(); - break; - - case 'Z': - byte iMSEQX; - iMSEQX = atol(buff + 1); - if (iMSEQX == 0) { - iMSset = 1; - EQset = 1; - iMSEQ = 2; - } - if (iMSEQX == 1) { - iMSset = 0; - EQset = 1; - iMSEQ = 3; - } - if (iMSEQX == 2) { - iMSset = 1; - EQset = 0; - iMSEQ = 4; - } - if (iMSEQX == 3) { - iMSset = 0; - EQset = 0; - iMSEQ = 1; - } - updateiMS(); - updateEQ(); - Serial.print("Z" + String(iMSEQX) + "\n"); - break; - } + case 'Z': + byte iMSEQX; + iMSEQX = atol(buff + 1); + if (iMSEQX == 0) { + iMSset = 1; + EQset = 1; + iMSEQ = 2; + } + if (iMSEQX == 1) { + iMSset = 0; + EQset = 1; + iMSEQ = 3; + } + if (iMSEQX == 2) { + iMSset = 1; + EQset = 0; + iMSEQ = 4; + } + if (iMSEQX == 3) { + iMSset = 0; + EQset = 0; + iMSEQ = 1; + } + updateiMS(); + updateEQ(); + XDRGTKprint("Z" + String(iMSEQX) + "\n"); + break; } } if (USBstatus == true) { Stereostatus = radio.getStereoStatus(); if (StereoToggle == false) { - Serial.print("SS"); + XDRGTKprint("SS"); } else if (Stereostatus == true && band == BAND_FM) { - Serial.print("Ss"); + XDRGTKprint("Ss"); } else { - Serial.print("Sm"); + XDRGTKprint("Sm"); } if (SStatus > (SStatusold + 10) || SStatus < (SStatusold - 10)) { - Serial.print(String(((SStatus * 100) + 10875) / 1000) + "." + String(((SStatus * 100) + 10875) / 100 % 10)); + XDRGTKprint(String(((SStatus * 100) + 10875) / 1000) + "." + String(((SStatus * 100) + 10875) / 100 % 10)); } else { - Serial.print(String(((SStatusold * 100) + 10875) / 1000) + "." + String(((SStatus * 100) + 10875) / 100 % 10)); + XDRGTKprint(String(((SStatusold * 100) + 10875) / 1000) + "." + String(((SStatus * 100) + 10875) / 100 % 10)); } - Serial.print("," + String(WAM / 10, DEC) + "," + String(SNR, DEC) + "\n"); + XDRGTKprint("," + String(WAM / 10, DEC) + "," + String(SNR, DEC) + "\n"); } } @@ -3290,3 +3397,89 @@ void read_encoder() { } } } + +void tryWiFi() { + tft.drawRoundRect(1, 60, 319, 140, 5, TFT_WHITE); + tft.fillRoundRect(3, 62, 315, 136, 5, TFT_BLACK); + tft.setFreeFont(FONT14); + tft.setTextColor(TFT_WHITE); + tft.drawCentreString(myLanguage[language][55], 155, 80, GFXFF); + if (wc.autoConnect()) { + Server.begin(); + tft.setTextColor(TFT_GREEN); + tft.drawCentreString(myLanguage[language][57], 155, 120, GFXFF); + wifi = true; + } else { + tft.setTextColor(TFT_RED); + tft.drawCentreString(myLanguage[language][56], 155, 120, GFXFF); + wifi = false; + } +} + +void WiFihandler() { + if (Server.hasClient()) + { + if (!RemoteClient.connected()) + { + // Server.available().stop(); + // } else { + wificonnected = true; + RemoteClient = Server.available(); + passwordcrypt(); + RemoteClient.print(saltkey + "\n"); + } + } else { + if (Server.hasClient()) Server.available().stop(); + } + + if (wificonnected == true && !RemoteClient.connected()) { + wificonnected = false; + XDRGTKTCP = false; + } + + if (XDRGTKTCP == false && wificonnected == true && RemoteClient.available()) { + String data_str = RemoteClient.readStringUntil('\n'); + int data = data_str.toInt(); + if (data_str.length() > 30 && data_str.equals(cryptedpassword)) + { + if (band != BAND_FM) { + band = BAND_FM; + SelectBand(); + } + XDRGTKTCP = true; + RemoteClient.print("o1,0\n"); + } else if (RDSSpy == false && XDRGTKTCP == false && data_str.length() < 5 && data_str == ("*R?F")) + { + RDSSpy = true; + } else if (RDSSpy == true) { + int symPos = data_str.indexOf("*F"); + if (symPos >= 5) { + String freq = data_str.substring(0, symPos); + freq = freq.substring(0, freq.length() - 1); + frequency = freq.toInt(); + radio.SetFreq(frequency); + radio.clearRDS(fullsearchrds); + if (band != BAND_FM) { + band = BAND_FM; + SelectBand(); + } + } + } else { + RemoteClient.print("a0\n"); + } + } +} + +void passwordcrypt() { + int generated = 0; + while (generated < 16) + { + byte randomValue = random(0, 26); + char letter = randomValue + 'a'; + if (randomValue > 26) letter = (randomValue - 26); + saltkey.setCharAt(generated, letter); + generated ++; + } + salt = saltkey + XDRGTK_key; + cryptedpassword = String(sha1(salt)); +} diff --git a/src/WC_AP_HTML.h b/src/WC_AP_HTML.h new file mode 100644 index 0000000..ceabd50 --- /dev/null +++ b/src/WC_AP_HTML.h @@ -0,0 +1,77 @@ +/*! + * @file WC_AP_HTML.h + * + * HTML snippets to build the Access Point portal and the Parameters Portal. + * + * Written by Stuart Blair + * + * GNU General Public License v3.0 licence, all text here must be included in any redistribution and you should receive a copy of the license file. + * + */ + +#ifndef WC_AP_HTML +#define WC_AP_HTML ///< Define to stop re-inclusion +/*! \def char AP_HTTP_HEAD[] PROGMEM +Start of HTML output + */ +const char AP_HTTP_HEAD[] PROGMEM = "{v}"; +/*! \def AP_HTTP_STYLE[] PROGMEM +Style for our access point +*/ +const char AP_HTTP_STYLE[] PROGMEM = ""; +/** Scripts for our page */ +const char AP_HTTP_SCRIPT[] PROGMEM = ""; +/** End of the header section and beginning of the body */ +const char AP_HTTP_HEAD_END[] PROGMEM = "
"; +/** Start of our HTMl configuration Form */ +const char AP_HTTP_PORTAL_OPTIONS[] PROGMEM = "
Connect this device to a WiFi network. Select the option to find a WiFi network.

"; +/** HTML snippet for wifi scanning */ +const char AP_HTTP_ITEM[] PROGMEM = "
{v} {r}%
"; +/** HTML form for saving wifi connection details */ +const char AP_HTTP_FORM_START[] PROGMEM ="

";; +/** HTML snippet for our custom parameters */ +const char AP_HTTP_FORM_PARAM[] PROGMEM = "
"; +/** The end of our HTML Form */ +const char AP_HTTP_FORM_END[] PROGMEM = "
"; +/** HTML snippet to recan for networks */ +const char AP_HTTP_SCAN_LINK[] PROGMEM = "
"; +/** HTML snippet for saved confirmation */ +// https://stackoverflow.com/questions/20760635/why-does-setting-xmlhttprequest-responsetype-before-calling-open-throw +// https://esprima.org/demo/validate.html +// https://javascript-minifier.com/ + +/* Use JavaScript to ping the ESP periodically @ the AP IP address. + * If it comes back as an AP again then we know the connection to the WiFi didn't work. + * We wait about 30 seconds to determine this outcome. This isn't 100% foolproof, but should be good enough. +*/ +/* + +*/ +const char AP_HTTP_SAVED[] PROGMEM = "
Credentials Saved.
Attempting to connect to WiFi network. Please wait....
Connected to {ap} !
You may now close this window.
Failed to connect to {ap}!
Click here to try again.
"; +/** End of the HTML page */ +const char AP_HTTP_END[] PROGMEM = "
"; +/** HTML snippet for our custom parameters portal form */ +const char AP_HTTP_PORTAL_PARAM_OPTIONS[] PROGMEM = "


"; +/** HTML snippet for our custom parameters save */ +const char AP_HTTP_FORM_PARAM_START[] PROGMEM ="
"; + +#endif \ No newline at end of file diff --git a/src/WiFiConnect.cpp b/src/WiFiConnect.cpp new file mode 100644 index 0000000..d1e3a3e --- /dev/null +++ b/src/WiFiConnect.cpp @@ -0,0 +1,1337 @@ +/*! + @file WiFiConnect.cpp + + @mainpage WiFi Connection Manager with Captive Portal + + @section intro_sec Introduction + + This is the documentation for WiFiConnect for the Arduino platform https://github.com/smurf0969/WiFiConnect. + Further documentation can be found in the [Wiki](https://github.com/smurf0969/WiFiConnect/wiki) or in the project examples folder on GitHub. + + It is a WiFi connection manager for use with the popular ESP8266 and ESP32 chips. + + It contains a captive portal to allow easy connection and changing of WiFi netwoks + via a web based interface and allows for additional user parameters. + + It can also display messages via a OLED screen see WiFiConnectOLED class. + + ![alt text](https://github.com/smurf0969/WiFiConnect/raw/master/extras/screenshots.png "Screenshots") + + This is a heavily customised version from the origional WiFiManager + developed by https://github.com/tzapu . + + @section dependencies Dependencies + + This library depends on + ESP8266 Arduino Core or ESP32 Arduino Core depending on what chip you are using being present on your system. + Please make sure you have installed the latest version before using this library. + + @section arduino_libraries Arduino Libraries + + - ArduinoJson 6.9.1 [https://github.com/bblanchon/ArduinoJson.git](https://github.com/bblanchon/ArduinoJson.git) or [https://blog.benoitblanchon.fr](https://blog.benoitblanchon.fr) + - ESP8266 OLED SSD1306 ([Custom version](https://github.com/smurf0969/esp8266-oled-ssd1306/tree/Allow-overriding-default-font)) + + @section author Author + + Written by Stuart Blair. + + @section license License + + GNU General Public License v3.0 licence, all text here must be included in any redistribution and you should receive a copy of the license file. + +*/ +#include "WiFiConnect.h" + +WiFiConnect::WiFiConnect() { + //_params = (WiFiConnectParam**)malloc(WiFiConnect_MAX_PARAMS * sizeof(WiFiConnectParam*)); + _apName[0]='\0'; + _apPassword[0]='\0'; +} +/**************************************************************************/ +/*! + @brief This function gets overridden by WiFiConnectOLED, + please view its documentation. +*/ +/**************************************************************************/ +void WiFiConnect::displayConnecting(int attempt, int totalAttempts) { + /*dummy*/ +} +/**************************************************************************/ +/*! + @brief This function gets overridden by WiFiConnectOLED, + please view its documentation. +*/ +/**************************************************************************/ +void WiFiConnect::displayConnected() { + /*dummy*/ +} +/**************************************************************************/ +/*! + @brief This function gets overridden by WiFiConnectOLED, + please view its documentation. +*/ +/**************************************************************************/ +void WiFiConnect::displayAP() { + /*dummy*/ +} +/**************************************************************************/ +/*! + @brief This function gets overridden by WiFiConnectOLED, + please view its documentation. +*/ +/**************************************************************************/ +void WiFiConnect::displayParams() { + /*dummy*/ +} +/**************************************************************************/ +/*! + @brief This function gets overridden by WiFiConnectOLED, + please view its documentation. +*/ +/**************************************************************************/ +void WiFiConnect::displayManualReset() { + /*dummy*/ +} +/**************************************************************************/ +/*! + @brief This function gets overridden by WiFiConnectOLED, + please view its documentation. +*/ +/**************************************************************************/ +void WiFiConnect::displayTurnOFF(int ms) { + /*dummy*/ +} +/**************************************************************************/ +/*! + @brief This function gets overridden by WiFiConnectOLED, + please view its documentation. +*/ +/**************************************************************************/ +void WiFiConnect::displayLoop() { + /*dummy*/ +} +/**************************************************************************/ +/*! + @brief This function gets overridden by WiFiConnectOLED, + please view its documentation. +*/ +/**************************************************************************/ +void WiFiConnect::displayON() { + /*dummy*/ +} +/**************************************************************************/ +/*! + @brief Sets the timeout for when the Access Point is idle. + @param mins + The number of minutes to before exiting an idle access point. +*/ +/**************************************************************************/ +void WiFiConnect::setAPModeTimeoutMins(int mins) { + if (mins > 0) { + _apTimeoutMins = mins; + } +} +/**************************************************************************/ +/*! + @brief Sets the name for the Access Point, setting a default based on + the ESP chip id if needed. + @param apName + The new name for the access point. +*/ +/**************************************************************************/ +void WiFiConnect::setAPName(const char *apName) { + if(strlen(apName)>32){return;} + if (strlen(apName)==0||(apName == NULL)) { + String ssid = "ESP_" + String(ESP_getChipId()); + //_apName = ssid.c_str(); + strcpy(_apName,ssid.c_str()); + + } else if (apName != NULL && strlen(apName)>0) { + // _apName = apName; + strcpy(_apName,apName); + } +} +/**************************************************************************/ +/*! + @brief Gets the Access Point name or the default based on the + ESP chip id if not already set. + @return The name to be given to the access point. +*/ +/**************************************************************************/ +const char* WiFiConnect::getAPName() { + if ((_apName == NULL ) || strlen(_apName)==0) { + setAPName(NULL); + } + return _apName; +} +/**************************************************************************/ +/*! + @brief Adds a custom parameter to be displayed in the accesspoint and + parameters portals. + Custom parameters can be used to save information that you do + not want to hard code i.e. IP address, Port number that can be + saved to the file system. + @param p + The custom parameter to add. +*/ +/**************************************************************************/ +void WiFiConnect::addParameter(WiFiConnectParam *p) { + _params[_paramsCount] = p; + _paramsCount++; + DEBUG_WC("Adding parameter"); + DEBUG_WC(p->getID()); +} +/**************************************************************************/ +/*! + @brief Starts the custom Parameters Portal using default + access point name and password. + @return Returns wether WiFi is connected to a network +*/ +/**************************************************************************/ +boolean WiFiConnect::startParamsPortal() { + return startParamsPortal(AP_NONE, _apName, _apPassword); +} +/**************************************************************************/ +/*! + @brief Starts the custom Parameters Portal using default + access point name and password, but allows for specifying how + to continue. + @param apcontinue + What should the access point do if there is still no connection. + AP_NONE - continue running code, + AP_LOOP - stay in an infinate loop, + AP_RESET - Restart the ESP + AP_WAIT - Keep AP alive waiting for input + @return Returns wether WiFi is connected to a network if + AP_NONE is passed as a parameter +*/ +/**************************************************************************/ +boolean WiFiConnect::startParamsPortal(AP_Continue apcontinue) { + return startParamsPortal(apcontinue, _apName, _apPassword); +} +/**************************************************************************/ +/*! + @brief Starts the custom Parameters Portal. + @param apcontinue + What should the access point do if there is still no connection. + AP_NONE - continue running code, + AP_LOOP - stay in an infinate loop, + AP_RESET - Restart the ESP + AP_WAIT - Keep AP alive waiting for input + @param apName + The Name to use for the access point + @param apPassword + The optional Password to protect the access point + @return Returns wether WiFi is connected to a network if + AP_NONE is passed as a parameter +*/ +/**************************************************************************/ +boolean WiFiConnect::startParamsPortal(AP_Continue apcontinue, const char *apName, const char *apPassword) { + + // Wrapper + return startConfigurationPortal(apcontinue, apName, apPassword, true); +} +/**************************************************************************/ +/*! + @brief Starts the Access Portal for setting SSID and Password for + connecting to a network and any custom user parameters. + Uses the default access point name and password. + @return Returns wether WiFi is connected to a network if + AP_NONE is passed as a parameter +*/ +/**************************************************************************/ +boolean WiFiConnect::startConfigurationPortal() { + return startConfigurationPortal(AP_NONE, _apName, _apPassword); +} +/**************************************************************************/ +/*! + @brief Starts the Access Portal for setting SSID and Password for + connecting to a network and any custom user parameters. + Uses the default access point name and password, but + allows for specifying how to continue if network not connected. + @param apcontinue + What should the access point do if there is still no connection. + AP_NONE - continue running code, + AP_LOOP - stay in an infinate loop, + AP_RESET - Restart the ESP + AP_WAIT - Keep AP alive waiting for input + @return Returns wether WiFi is connected to a network if + AP_NONE is passed as a parameter +*/ +/**************************************************************************/ +boolean WiFiConnect::startConfigurationPortal(AP_Continue apcontinue) { + return startConfigurationPortal(apcontinue, _apName, _apPassword); +} +/**************************************************************************/ +/*! + @brief Starts the Access Portal for setting SSID and Password for + connecting to a network and any custom user parameters. + @param apcontinue + What should the access point do if there is still no connection. + AP_NONE - continue running code, + AP_LOOP - stay in an infinate loop, + AP_RESET - Restart the ESP, handy for sensors if you network + goes down, do they keep trying to connect. + AP_WAIT - Keep AP alive waiting for input + @param apName + The Name to use for the access point + @param apPassword + The optional Password to protect the access point + @return Returns wether WiFi is connected to a network if + AP_NONE is passed as a parameter +*/ +/**************************************************************************/ +boolean WiFiConnect::startConfigurationPortal(AP_Continue apcontinue, const char *apName, const char *apPassword, bool paramsMode) { + + if (paramsMode) { + DEBUG_WC(F("WiFi AP STA - Parameters Portal")); + } else { + DEBUG_WC(F("WiFi AP STA - Configuration Portal")); + } + _lastAPPage = millis(); + // WiFi.mode(WIFI_AP);//Fix for scan bug when switching from STA to AP STA + // delay(1); + // WiFi.mode(WIFI_AP_STA); + // delay(1); + +#ifdef ARDUINO_ARCH_ESP8266 + WiFi.setSleepMode(WIFI_NONE_SLEEP); +#endif + + delay(50); + + if (WiFi.status() != WL_CONNECTED) { + if (paramsMode) { + // Can't show the parameters portal if not connected to a WiFi network. + startConfigurationPortal(AP_WAIT, apName, apPassword, false); + } else { + DEBUG_WC("startConfigurationPortal(): WiFi.mode(WIFI_AP)"); + WiFi.mode(WIFI_AP); + } + } else { + WiFi.mode(WIFI_AP_STA); // start an access point on the same channel we're already connected to. + } + dnsServer.reset(new DNSServer()); +#ifdef ESP8266 + server.reset(new ESP8266WebServer(80)); +#else + server.reset(new WebServer(80)); +#endif + setAPName(apName); + DEBUG_WC(_apName); + + if (strlen(apPassword)>0){ + if (strlen(apPassword) < 8 || strlen(apPassword) > 63) { + // fail passphrase to short or long! + DEBUG_WC(F("Invalid AccessPoint password. Ignoring")); + apPassword = NULL; + } + //_apPassword = apPassword; + strcpy(_apPassword,apPassword); + DEBUG_WC(_apPassword); + } + + //optional soft ip config + if (_ap_static_ip) { + DEBUG_WC(F("Custom AP IP/GW/Subnet")); + WiFi.softAPConfig(_ap_static_ip, _ap_static_gw, _ap_static_sn); + } + + //if (_apPassword != NULL && _apPassword!="") { + if(strlen(apPassword)>0){ + WiFi.softAP(_apName, _apPassword);//password option + } else { + WiFi.softAP(_apName); + DEBUG_WC("startConfigurationPortal(): WiFi.softAP(_apName)"); + } + + delay(500); // Without delay I've seen the IP address blank + DEBUG_WC(F("AP IP address: ")); + DEBUG_WC(WiFi.softAPIP()); + + // Show on OLED depending on the mode + if (!paramsMode) { displayAP(); } else { displayParams(); } + + /* Setup the DNS server redirecting all the domains to the apIP */ + dnsServer->setErrorReplyCode(DNSReplyCode::NoError); + dnsServer->start(DNS_PORT, "*", WiFi.softAPIP()); + + if (paramsMode) + { + + server->on("/", std::bind(&WiFiConnect::handleParamRoot, this)); + server->on("/param", std::bind(&WiFiConnect::handleParams, this)); + server->on("/params", std::bind(&WiFiConnect::handleParams, this)); + server->on("/wifisave", std::bind(&WiFiConnect::handleWifiSave, this)); + server->on("/i", std::bind(&WiFiConnect::handleInfo, this)); + //server->on("/r", std::bind(&WiFiConnect::handleReset, this)); // doesn't work - causes boot loop + //server->on("/generate_204", std::bind(&WiFiConnect::handle204, this)); //Android/Chrome OS captive portal check. + server->on("/fwlink", std::bind(&WiFiConnect::handleParamRoot, this)); //Microsoft captive portal. Maybe not needed. Might be handled by notFound handler. + server->on("/hotspot_detect.html", std::bind(&WiFiConnect::handleParamRoot, this)); + + } else { // Config portal mode + + /* Setup web pages: root, wifi config pages, SO captive portal detectors and not found. */ + server->on("/", std::bind(&WiFiConnect::handleRoot, this)); + server->on("/wifi", std::bind(&WiFiConnect::handleWifi, this, true)); // Auto Scan of APs + server->on("/0wifi", std::bind(&WiFiConnect::handleWifi, this, false)); // Manual entry form only + server->on("/wifisave", std::bind(&WiFiConnect::handleWifiSave, this)); + server->on("/i", std::bind(&WiFiConnect::handleInfo, this)); // Not of interest - commented out in static HTML + server->on("/r", std::bind(&WiFiConnect::handleReset, this)); + //server->on("/generate_204", std::bind(&WiFiConnect::handle204, this)); //Android/Chrome OS captive portal check. + server->on("/fwlink", std::bind(&WiFiConnect::handleRoot, this)); //Microsoft captive portal. Maybe not needed. Might be handled by notFound handler. + server->on("/hotspot_detect.html", std::bind(&WiFiConnect::handleRoot, this)); + + } // config portal + + server->onNotFound (std::bind(&WiFiConnect::handleNotFound, this)); + server->begin(); // Web server start + DEBUG_WC(F("HTTP server started")); + + //notify we entered AP mode + if ( _apcallback != NULL) { + _apcallback(this); + } + + if (apcontinue == AP_WAIT) + DEBUG_WC(F("AP to stay alive indefinitely waiting for configuration.")); + + + _lastAPPage = millis(); + _readyToConnect = false; + while (true) { + + if (apcontinue != AP_WAIT) // Stay here if the AP_LOOP enabled + if (millis() - (unsigned long int)_lastAPPage >= ( (unsigned int)_apTimeoutMins * 60 * 1000)) { + break; + } + + dnsServer->processNextRequest(); + server->handleClient(); + if (_readyToConnect) { + _readyToConnect = false; + + DEBUG_WC(F("startConfigurationPortal(): Calling: autoConnect(_ssid.c_str(), _password.c_str(), WIFI_AP_STA)")); + + if (autoConnect(_ssid.c_str(), _password.c_str(), WIFI_AP_STA)) { + + DEBUG_WC(F("Connection was a success, so changing to WIFI_STA mode only.")); + WiFi.mode(WIFI_STA); + delay(500); + //WiFi.reconnect(); + //delay(500); + if (_savecallback != NULL) { + _savecallback(); + } + break; // we connected! + } + else + { + DEBUG_WC(F("Connection was a failure. We keep waiting.")); + } + } // ready to connect + + yield(); + } + + //teardown?? + DEBUG_WC(F("Exiting AP (or AP Params) Mode")); + server->close(); + server.reset(); + + dnsServer.reset(); + boolean con = (WiFi.status() == WL_CONNECTED); + if (!con) { + switch (apcontinue) { + case AP_WAIT: + break; + case AP_NONE: + DEBUG_WC(F("No AP continue action")); + break; + case AP_LOOP: + DEBUG_WC(F("AP to turn off and stay in loop lockup not being accessible.")); + displayManualReset(); + displayTurnOFF(5 * 60 * 100); //5mins + WiFi.mode(WIFI_OFF); + while (true) { + displayLoop(); + delay(1000); + yield(); + } + + break; + case AP_RESTART: + case AP_RESET: + displayManualReset(); + DEBUG_WC(F("AP restart chip")); + delay(1000); +#if defined(ESP8266) + ESP.reset(); +#else + ESP.restart(); +#endif + delay(2000); + } + WiFi.mode(WIFI_STA); + } + return con; +} +/**************************************************************************/ +/*! + @brief Tries to connect to the last connected WiFi network. + @return Returns wether WiFi is connected + + @section example_startConfigurationPortal Example + + The return value can be used to automatically start the access point + so you may change networks if required. + + @code + if(!wificonnect.autoConnect()){ + //Start the access point + wificonnect.startConfigurationPortal(); + } + @endcode +*/ +/**************************************************************************/ +boolean WiFiConnect::autoConnect() { + return autoConnect(NULL, NULL, WIFI_STA); +} +/**************************************************************************/ +/*! + @brief Tries to connect to the specified WiFi network. + @param ssidName + The SSID of the netqork you want to connect to. + @param ssidPassword + The password for a secure network. + @param WiFiMode + The WiFi mode + @return Returns wether WiFi is connected +*/ +/**************************************************************************/ +boolean WiFiConnect::autoConnect(char const *ssidName, char const *ssidPassword, WiFiMode_t acWiFiMode) { +/* + #ifdef ESP32 + WiFi.persistent(false); // disable persistent for esp32 after esp_wifi_start or else saves wont work (from WiFiManager) + #endif +*/ + + DEBUG_WC(F("Auto Connect")); + WiFi.mode(acWiFiMode); + DEBUG_WC("autoConnect():WiFi.mode(acWiFiMode)"); + if (WiFi.status() == WL_CONNECTED) { + DEBUG_WC(F("Already Connected")); + DEBUG_WC(WiFi.localIP()); + return true; + } + if (_sta_static_ip) { + DEBUG_WC(F("Custom STA IP/GW/Subnet")); + WiFi.config(_sta_static_ip, _sta_static_gw, _sta_static_sn); + DEBUG_WC(WiFi.localIP()); + } + + //fix for auto connect racing issue + /* + if (WiFi.status() == WL_CONNECTED && (WiFi.SSID() == String(ssidName))) { + DEBUG_WC(F("Already connected. Bailing out.")); + DEBUG_WC(WiFi.localIP()); + return true; + //return WL_CONNECTED; + } +*/ + + int c = 0; + while (c < _retryAttempts) { + displayConnecting(c + 1, _retryAttempts); + long ms = millis(); + if (ssidName == NULL || strlen(ssidName)==0) { + + #ifdef ESP8266 + if (WiFi.SSID() == "") + { + DEBUG_WC(F("No ESP8266 WiFi SSID configuration stored. Bailing.")); + return false; + } + DEBUG_WC(F("Stored SSID:")); DEBUG_WC(WiFi.SSID()); + #endif + + #ifdef ESP32 + // WiFi.SSID() Always returns null on ESP32.... Arduino framework bug? + // So use lower-level IDF function to pull persistent configuration. + wifi_config_t conf; + esp_wifi_get_config(WIFI_IF_STA, &conf); + + String stored_ssid = String(reinterpret_cast(conf.sta.ssid)); + if (stored_ssid == "") + { + DEBUG_WC(F("No ESP32 WiFi SSID configuration stored. Bailing.")); + return false; + } + + DEBUG_WC(F("Stored SSID:")); DEBUG_WC(stored_ssid); + #endif + + + WiFi.begin(); // persistence is on by default, so if this WiFi connection should happen automatically + DEBUG_WC("autoConnect(): WiFi.begin()"); + +/* + #ifdef ESP32 + //https://forum.arduino.cc/index.php?topic=652513.0 + int wifi_retry = 0; // COUNTER SOLVES ESP32-BUG WITH CERTAIN ROUTERS: CONNECTION ONLY ESTABLISHED EVERY SECOND TIME + + while (WiFi.waitForConnectResult() != WL_CONNECTED && wifi_retry < 3) { + DEBUG_WC("WLAN Not yet connected on ESP32...retrying"); + WiFi.begin(); + delay(250); + wifi_retry++; + } + + #endif +*/ + + } else { + DEBUG_WC(F("Connecting with SSID & Password")); + DEBUG_WC(ssidName); + DEBUG_WC(ssidPassword); + WiFi.begin(ssidName, ssidPassword); + DEBUG_WC("autoConnect(): WiFi.begin(ssidName, ssidPassword)"); + } + + + while (millis() - (unsigned long int)ms < ( (unsigned int)_connectionTimeoutSecs * 1000)) { + int ws = WiFi.status(); + if (ws == WL_CONNECTED) { + DEBUG_WC(F("WIFI_CONNECTED")); + delay(500);//wait for ip to refresh + DEBUG_WC(WiFi.localIP()); + displayConnected(); + return true; + } else if (ws == WL_CONNECT_FAILED) { + DEBUG_WC(F("WIFI_CONNECT_FAILED")); + delay(500); + //return false; + } else { + delay(200); + yield(); + } + } + DEBUG_WC(statusToString(WiFi.status())); + c++; + } // attemps + DEBUG_WC(statusToString(WiFi.status())); + return false; +} +/**************************************************************************/ +/*! + @brief Sets how many times we should try to connect to the WiFi network. + @param attempts + How may attampts. +*/ +/**************************************************************************/ +void WiFiConnect::setRetryAttempts(int attempts) { + if (attempts >= 1) { + _retryAttempts = attempts; + } +} +/**************************************************************************/ +/*! + @brief Sets how long to wait for the connection before failing. + @param timeout + How may seconds to wait for the connection to be made. +*/ +/**************************************************************************/ +void WiFiConnect::setConnectionTimeoutSecs(int timeout) { + if (timeout >= 1) { + _connectionTimeoutSecs = timeout; + } +} +/**************************************************************************/ +/*! + @brief A helper to clear the settings for the last connected network. + Remember to comment out or remove and re-upload your code + otherwise chip will constantly restart. +*/ +/**************************************************************************/ +void WiFiConnect::resetSettings() { + DEBUG_WC(F("Clearing Settings for:")); + DEBUG_WC(WiFi.SSID()); +#if defined(ESP8266) + WiFi.disconnect(true); + delay(1000); + ESP.reset(); +#else + WiFi.disconnect(true);//doesnt seem to work + esp_wifi_restore();//doesnt seem to work + WiFi.begin("0", "0"); //HACK + delay(1000); + ESP.restart(); +#endif + delay(2000); +} +/**************************************************************************/ +/*! + @brief A helper function to return the string version of a + wifi network state. + @param state + The state that we want the string representation of. + @return WiFi state as a string. +*/ +/**************************************************************************/ +const char* WiFiConnect::statusToString(int state) { + switch (state) { + case WL_CONNECTED: + return "WL_CONNECTED"; + case WL_CONNECT_FAILED: + return "WL_CONNECT_FAILED"; + case WL_CONNECTION_LOST: + return "WL_CONNECTION_LOST"; + case WL_DISCONNECTED: + return "WL_DISCONNECTED"; + case WL_SCAN_COMPLETED: + return "WL_SCAN_COMPLETED"; + case WL_NO_SSID_AVAIL: + return "WL_NO_SSID_AVAIL"; + case WL_IDLE_STATUS: + return "WL_IDLE_STATUS"; + case WL_NO_SHIELD: + return "WL_NO_SHIELD"; + default: + return "UNKNOWN CODE"; + } +} +/**************************************************************************/ +/*! + @brief Outputs debug messages + @param text + The text to be outputted +*/ +/**************************************************************************/ +template +void WiFiConnect::DEBUG_WC(Generic text) { + if (_debug) { + Serial.print("*WC: "); + Serial.println(text); + } +} +/**************************************************************************/ +/*! + @brief Handles the web request for root in the Parameters Portal and + sends back the html menu +*/ +/**************************************************************************/ +void WiFiConnect::handleParamRoot() { + if (captivePortal()) { // If captive portal redirect instead of displaying the page. + return; + } + _lastAPPage = millis(); + DEBUG_WC("Sending Root"); + String page = FPSTR(AP_HTTP_HEAD); + page.replace("{v}", "Options"); + page += FPSTR(AP_HTTP_SCRIPT); + page += FPSTR(AP_HTTP_STYLE); + page += FPSTR(AP_HTTP_HEAD_END); + page += String(F("

")); + page += _apName; + page += String(F("

")); +// page += String(F("

WiFiConnect

")); + page += FPSTR(AP_HTTP_PORTAL_PARAM_OPTIONS); + page += FPSTR(AP_HTTP_END); + + server->sendHeader("Content-Length", String(page.length())); + server->send(200, "text/html", page); + +} +/**************************************************************************/ +/*! + @brief Handles the web request for displaying custom parameters + in the Parameters Portal and sends back the html +*/ +/**************************************************************************/ +void WiFiConnect::handleParams() { + _lastAPPage = millis(); + DEBUG_WC(F("Sending Params")); + + String page = FPSTR(AP_HTTP_HEAD); + page.replace("{v}", "Config Params"); + page += FPSTR(AP_HTTP_SCRIPT); + page += FPSTR(AP_HTTP_STYLE); + page += FPSTR(AP_HTTP_HEAD_END); + page += FPSTR(AP_HTTP_FORM_PARAM_START); + char parLength[4]; + // add the extra parameters to the form + for (int i = 0; i < _paramsCount; i++) { + if (_params[i] == NULL) { + break; + } + + String pitem = FPSTR(AP_HTTP_FORM_PARAM); + if (_params[i]->getID() != NULL) { + pitem.replace("{i}", _params[i]->getID()); + pitem.replace("{n}", _params[i]->getID()); + pitem.replace("{p}", _params[i]->getPlaceholder()); + snprintf(parLength, 4, "%d", _params[i]->getValueLength()); + pitem.replace("{l}", parLength); + pitem.replace("{v}", _params[i]->getValue()); + pitem.replace("{c}", _params[i]->getCustomHTML()); + } else { + pitem = _params[i]->getCustomHTML(); + } + + page += pitem; + } + if (_params[0] != NULL) { + page += "
"; + } + + page += FPSTR(AP_HTTP_FORM_END); + + page += FPSTR(AP_HTTP_END); + + server->sendHeader("Content-Length", String(page.length())); + server->send(200, "text/html", page); +} +/**************************************************************************/ +/*! + @brief Handles the web request for displaying the menu in the + Access Point portal. +*/ +/**************************************************************************/ +void WiFiConnect::handleRoot() { + if (captivePortal()) { // If captive portal redirect instead of displaying the page. + return; + } + _lastAPPage = millis(); + DEBUG_WC("Sending Root"); + String page = FPSTR(AP_HTTP_HEAD); + page.replace("{v}", "Options"); + page += FPSTR(AP_HTTP_SCRIPT); + page += FPSTR(AP_HTTP_STYLE); + page += FPSTR(AP_HTTP_HEAD_END); + page += String(F("

")); + page += _apName; + page += String(F("

")); + //page += String(F("

WiFiConnect

")); + page += FPSTR(AP_HTTP_PORTAL_OPTIONS); + page += FPSTR(AP_HTTP_END); + + server->sendHeader("Content-Length", String(page.length())); + server->send(200, "text/html", page); + + +} + +/**************************************************************************/ +/*! + @brief Handles the web request for entering the SSID and Password of + the network you want to connect too. + Optionally scans and shows available networks. + @param scan + Whether to scan for networks or not. +*/ +/**************************************************************************/ +void WiFiConnect::handleWifi(boolean scan) { + + _lastAPPage = millis(); + DEBUG_WC(F("Sending WiFi")); + + String page = FPSTR(AP_HTTP_HEAD); + page.replace("{v}", "Select WiFi"); + page += FPSTR(AP_HTTP_SCRIPT); + page += FPSTR(AP_HTTP_STYLE); + page += FPSTR(AP_HTTP_HEAD_END); + + if (scan) { + int n = WiFi.scanNetworks(); + DEBUG_WC(F("Scan done")); + if (n == 0) { + DEBUG_WC(F("No networks found")); + page += F("No networks found. Refresh to scan again."); + } else { + + //sort networks + int indices[n]; + for (int i = 0; i < n; i++) { + indices[i] = i; + } + + // RSSI SORT + + // old sort + for (int i = 0; i < n; i++) { + for (int j = i + 1; j < n; j++) { + if (WiFi.RSSI(indices[j]) > WiFi.RSSI(indices[i])) { + std::swap(indices[i], indices[j]); + } + } + } + + /*std::sort(indices, indices + n, [](const int & a, const int & b) -> bool + { + return WiFi.RSSI(a) > WiFi.RSSI(b); + });*/ + + // remove duplicates ( must be RSSI sorted ) + if (_removeDuplicateAPs) { + String cssid; + for (int i = 0; i < n; i++) { + if (indices[i] == -1) continue; + cssid = WiFi.SSID(indices[i]); + for (int j = i + 1; j < n; j++) { + if (cssid == WiFi.SSID(indices[j])) { + DEBUG_WC("DUP AP: " + WiFi.SSID(indices[j])); + indices[j] = -1; // set dup aps to index -1 + } + } + } + } + + //display networks in page + for (int i = 0; i < n; i++) { + if (indices[i] == -1) continue; // skip dups + DEBUG_WC(WiFi.SSID(indices[i])); + DEBUG_WC(WiFi.RSSI(indices[i])); + int quality = getRSSIasQuality(WiFi.RSSI(indices[i])); + + if (_minimumQuality == -1 || _minimumQuality < quality) { + String item = FPSTR(AP_HTTP_ITEM); + String rssiQ; + rssiQ += quality; + item.replace("{v}", WiFi.SSID(indices[i])); + item.replace("{r}", rssiQ); +#if defined(ESP8266) + if (WiFi.encryptionType(indices[i]) != ENC_TYPE_NONE) { +#else + if (WiFi.encryptionType(indices[i]) != WIFI_AUTH_OPEN) { +#endif + item.replace("{i}", "l"); + } else { + item.replace("{i}", ""); + } + //DEBUG_WM(item); + page += item; + delay(0); + } else { + DEBUG_WC(F("Skipping due to quality")); + } + + } + page += "
"; + } + } + + page += FPSTR(AP_HTTP_FORM_START); + char parLength[4]; + // add the extra parameters to the form + for (int i = 0; i < _paramsCount; i++) { + if (_params[i] == NULL) { + break; + } + + String pitem = FPSTR(AP_HTTP_FORM_PARAM); + if (_params[i]->getID() != NULL) { + pitem.replace("{i}", _params[i]->getID()); + pitem.replace("{n}", _params[i]->getID()); + pitem.replace("{p}", _params[i]->getPlaceholder()); + snprintf(parLength, 4, "%d", _params[i]->getValueLength()); + pitem.replace("{l}", parLength); + pitem.replace("{v}", _params[i]->getValue()); + pitem.replace("{c}", _params[i]->getCustomHTML()); + } else { + pitem = _params[i]->getCustomHTML(); + } + + page += pitem; + } + if (_params[0] != NULL) { + page += "
"; + } + + if (_sta_static_ip) { + + String item = FPSTR(AP_HTTP_FORM_PARAM); + item.replace("{i}", "ip"); + item.replace("{n}", "ip"); + item.replace("{p}", "Static IP"); + item.replace("{l}", "15"); + item.replace("{v}", _sta_static_ip.toString()); + + page += item; + + item = FPSTR(AP_HTTP_FORM_PARAM); + item.replace("{i}", "gw"); + item.replace("{n}", "gw"); + item.replace("{p}", "Static Gateway"); + item.replace("{l}", "15"); + item.replace("{v}", _sta_static_gw.toString()); + + page += item; + + item = FPSTR(AP_HTTP_FORM_PARAM); + item.replace("{i}", "sn"); + item.replace("{n}", "sn"); + item.replace("{p}", "Subnet"); + item.replace("{l}", "15"); + item.replace("{v}", _sta_static_sn.toString()); + + page += item; + + page += "
"; + } + + page += FPSTR(AP_HTTP_FORM_END); + page += FPSTR(AP_HTTP_SCAN_LINK); + + page += FPSTR(AP_HTTP_END); + + server->sendHeader("Content-Length", String(page.length())); + server->send(200, "text/html", page); +} +void WiFiConnect::handleWifiSave() { + _lastAPPage = millis(); + DEBUG_WC(F("Sending WiFi Save")); + //SAVE/connect here + if (_debug) { + Serial.println(F("*WC Form Args")); + for (int x = 0; x < server->args(); x++) { + Serial.print(F("*WC ")); + Serial.print(server->argName(x)); + Serial.print(F("=")); + Serial.println(server->arg(x)); + } + } + _ssid = server->arg("s").c_str(); + _ssid.trim(); + DEBUG_WC(_ssid); + _password = server->arg("p").c_str(); + _password.trim(); + DEBUG_WC(_password); + //parameters + for (int i = 0; i < _paramsCount; i++) { + if (_params[i] == NULL) { + break; + } + //read parameter + String value = server->arg(_params[i]->getID()).c_str(); + //store it in array + value.toCharArray(_params[i]->_value, _params[i]->_length); + DEBUG_WC(F("Parameter")); + DEBUG_WC(_params[i]->getID()); + DEBUG_WC(value); + } + + if (server->arg("ip") != "") { + DEBUG_WC(F("static ip")); + DEBUG_WC(server->arg("ip")); + //_sta_static_ip.fromString(server->arg("ip")); + String ip = server->arg("ip"); + optionalIPFromString(&_sta_static_ip, ip.c_str()); + } + if (server->arg("gw") != "") { + DEBUG_WC(F("static gateway")); + DEBUG_WC(server->arg("gw")); + String gw = server->arg("gw"); + optionalIPFromString(&_sta_static_gw, gw.c_str()); + } + if (server->arg("sn") != "") { + DEBUG_WC(F("static netmask")); + DEBUG_WC(server->arg("sn")); + String sn = server->arg("sn"); + optionalIPFromString(&_sta_static_sn, sn.c_str()); + } + + String page = FPSTR(AP_HTTP_HEAD); + page.replace("{v}", "Credentials Saved"); + page += FPSTR(AP_HTTP_SCRIPT); + page += FPSTR(AP_HTTP_STYLE); + page += FPSTR(AP_HTTP_HEAD_END); + page += FPSTR(AP_HTTP_SAVED); + page.replace("{ap}", _ssid); + page += FPSTR(AP_HTTP_END); + + server->sendHeader("Content-Length", String(page.length())); + server->send(200, "text/html", page); + _readyToConnect = true; +} +/**************************************************************************/ +/*! + @brief Handles the web request and shows basic information about the chip. + TODO: ESP32 information still needs completing. +*/ +/**************************************************************************/ +void WiFiConnect::handleInfo() { + _lastAPPage = millis(); DEBUG_WC(F("Sending Info")); + + String page = FPSTR(AP_HTTP_HEAD); + page.replace("{v}", "Info"); + page += FPSTR(AP_HTTP_SCRIPT); + page += FPSTR(AP_HTTP_STYLE); + page += FPSTR(AP_HTTP_HEAD_END); + page += F("
"); + page += F("
Chip ID
"); + page += ESP_getChipId(); + page += F("
"); + page += F("
Flash Chip ID
"); +#if defined(ESP8266) + page += ESP.getFlashChipId(); +#else + // TODO + page += F("TODO"); +#endif + page += F("
"); + page += F("
IDE Flash Size
"); + page += ESP.getFlashChipSize(); + page += F(" bytes
"); + page += F("
Real Flash Size
"); +#if defined(ESP8266) + page += ESP.getFlashChipRealSize(); +#else + // TODO + page += F("TODO"); +#endif + page += F(" bytes
"); + page += F("
Soft AP IP
"); + page += WiFi.softAPIP().toString(); + page += F("
"); + page += F("
Soft AP MAC
"); + page += WiFi.softAPmacAddress(); + page += F("
"); + page += F("
Station MAC
"); + page += WiFi.macAddress(); + page += F("
"); + page += F("
"); + page += FPSTR(AP_HTTP_END); + + server->sendHeader("Content-Length", String(page.length())); + server->send(200, "text/html", page); + + DEBUG_WC(F("Sent info page")); +} +/**************************************************************************/ +/*! + @brief Handles the reset web request and and restarts the chip. + Does not clear settings. +*/ +/**************************************************************************/ +void WiFiConnect::handleReset() { + _lastAPPage = millis(); + DEBUG_WC(F("Sending Reset")); + + String page = FPSTR(AP_HTTP_HEAD); + page.replace("{v}", "Info"); + page += FPSTR(AP_HTTP_SCRIPT); + page += FPSTR(AP_HTTP_STYLE); + page += FPSTR(AP_HTTP_HEAD_END); + page += F("Module will reset in a few seconds."); + page += FPSTR(AP_HTTP_END); + + server->sendHeader("Content-Length", String(page.length())); + server->send(200, "text/html", page); + + DEBUG_WC(F("Sent reset page")); + delay(5000); +#if defined(ESP8266) + ESP.reset(); // causes boot loop +#else + ESP.restart(); +#endif + delay(2000); + +} +/**************************************************************************/ +/*! + @brief Handles the 204 web request from Android devices +*/ +/**************************************************************************/ +void WiFiConnect::handle204() { + _lastAPPage = millis(); + DEBUG_WC(F("Sending 204")); + handleRoot(); +} +/**************************************************************************/ +/*! + @brief Handles any web requests that are not found. +*/ +/**************************************************************************/ +void WiFiConnect::handleNotFound() { + if (captivePortal()) { // If captive portal redirect instead of displaying the error page. + DEBUG_WC(F("Webserver called handleNotFound();")); + return; + } + _lastAPPage = millis(); + DEBUG_WC(F("Sending Not Found")); + String message = "File Not Found\n\n"; + message += "URI: "; + message += server->uri(); + message += "\nMethod: "; + message += ( server->method() == HTTP_GET ) ? "GET" : "POST"; + message += "\nArguments: "; + message += server->args(); + message += "\n"; + + for ( uint8_t i = 0; i < server->args(); i++ ) { + message += " " + server->argName ( i ) + ": " + server->arg ( i ) + "\n"; + } + server->sendHeader("Cache-Control", "no-cache, no-store, must-revalidate"); + server->sendHeader("Pragma", "no-cache"); + server->sendHeader("Expires", "-1"); + server->sendHeader("Content-Length", String(message.length())); + server->send ( 404, "text/plain", message ); +} + +/**************************************************************************/ +/*! + @brief Redirect to captive portal if we get a request for another domain. + Return true in that case so the page handler do not try to + handle the request again. + @return True if we have redirected to our portal, else false and contimue + to handle request +*/ +/**************************************************************************/ +boolean WiFiConnect::captivePortal() { + DEBUG_WC(server->hostHeader()); + + if (!isIp(server->hostHeader()) ) { + _lastAPPage = millis(); + DEBUG_WC(F("Request redirected to captive portal")); + String msg = "redirect\n"; + server->sendHeader("Location", String("http://") + toStringIp(server->client().localIP()), true); + server->sendHeader("Content-Length", String(msg.length())); + server->send ( 302, "text/plain", msg); // Empty content inhibits Content-length header so we have to close the socket ourselves. + //server->client().stop(); // Stop is needed because we sent no content length + + return true; + } + return false; +} + +/**************************************************************************/ +/*! + @brief Sets the function to be called when we enter Access Point + for configuration. + Gets passed a the WiFiConnect for additional usage within function. + @param func + The function to be called. + +*/ +/**************************************************************************/ +void WiFiConnect::setAPCallback( void (*func)(WiFiConnect* myWiFiConnect) ) { + _apcallback = func; +} +/**************************************************************************/ +/*! + @brief Sets the function to be called when we need to save the + custom parameters/configuration. + @param func + The function to be called. + This function should be short running and ideally just set. + a flag for processing else where. +*/ +/**************************************************************************/ +void WiFiConnect::setSaveConfigCallback( void (*func)(void) ) { + _savecallback = func; +} +/**************************************************************************/ +/*! + @brief Function to convert RSSI into a usable range 0-100 so we can + use it to exclude poor networks. + @param RSSI + The quality/signal strength of the WiFi network. + @return Value in the range of 0-100 to network indicate quality. + +*/ +/**************************************************************************/ +int WiFiConnect::getRSSIasQuality(int RSSI) { + int quality = 0; + + if (RSSI <= -100) { + quality = 0; + } else if (RSSI >= -50) { + quality = 100; + } else { + quality = 2 * (RSSI + 100); + } + return quality; +} +/**************************************************************************/ +/*! + @brief Function to check if a given string is an IP Address. + @param str + The string to process. + @return True if inputted string is an IP Address. + +*/ +/**************************************************************************/ +boolean WiFiConnect::isIp(String str) { + for (unsigned int i = 0; i < str.length(); i++) { + int c = str.charAt(i); + if (c != '.' && (c < '0' || c > '9')) { + return false; + } + } + return true; +} +/**************************************************************************/ +/*! + @brief Function to return a IPAddress as a string representation. + @param ip + The IPAddress to process. + @return String version of the IPAddress + +*/ +/**************************************************************************/ +String WiFiConnect::toStringIp(IPAddress ip) { + String res = ""; + for (int i = 0; i < 3; i++) { + res += String((ip >> (8 * i)) & 0xFF) + "."; + } + res += String(((ip >> 8 * 3)) & 0xFF); + return res; +} +/**************************************************************************/ +/*! + @brief Used to turn on or off messages to the serial port/monitor. + @param isDebug + Set to true to enable messages. +*/ +/**************************************************************************/ +void WiFiConnect::setDebug(boolean isDebug) { + _debug = isDebug; +} + +/**************************************************************************/ +/*! + @brief Used to set a static ip/gateway and subnet mask for the access point. + IP and DNS Gateway are normally the same for a captive portal. + @param ip + The IP address that you want to access the portal through. + @param gw + The gateway address for the DNS server, usually the same as IP for + captive portal. + @param sn + The subnet mask for the access point, usually 255.255.255.0 +*/ +/**************************************************************************/ +void WiFiConnect::setAPStaticIPConfig(IPAddress ip, IPAddress gw, IPAddress sn) { + _ap_static_ip = ip; + _ap_static_gw = gw; + _ap_static_sn = sn; +} +/**************************************************************************/ +/*! + @brief Used to set a static ip/gateway and subnet mask for the local + network. + @param ip + The IP address that you want to use on the network. + @param gw + The gateway address for the DNS server or network router. + @param sn + The subnet mask for the access point, usually 255.255.255.0 + for local networks. +*/ +/**************************************************************************/ +void WiFiConnect::setSTAStaticIPConfig(IPAddress ip, IPAddress gw, IPAddress sn) { + _sta_static_ip = ip; + _sta_static_gw = gw; + _sta_static_sn = sn; +} diff --git a/src/WiFiConnect.h b/src/WiFiConnect.h new file mode 100644 index 0000000..719ebed --- /dev/null +++ b/src/WiFiConnect.h @@ -0,0 +1,187 @@ +/*! + @file WiFiConnect.h + + This is the documentation for WiFiConnect for the Arduino platform. + It is a WiFi connection manager for use with the popular ESP8266 and ESP32 chips. + It contains a captive portal to allow easy connection and changing of WiFi netwoks + via a web based interface and allows for additional user parameters. + + You can view the project at https://github.com/smurf0969/WiFiConnect. + Further information is also available in the project Wiki. + + This is a heavily customised version from the origional WiFiManager + developed by https://github.com/tzapu . + + This library depends on + ESP8266 Arduino Core and ESP32 Arduino Core being present on your system. + Please make sure you have installed the latest version before using this library. + + Written by Stuart Blair. + + GNU General Public License v3.0 licence, all text here must be included in any redistribution and you should receive a copy of the license file. + +*/ +#ifndef WiFiConnect_h +#define WiFiConnect_h +#include +#if defined(ESP8266) +#include +#include +#else +#include +#include +#endif +#include +#include + +#if defined(ESP8266) +extern "C" { +#include "user_interface.h" +} +#define ESP_getChipId() (ESP.getChipId()) ///< Gets an ID from the chip +#else +#include +#define ESP_getChipId() ((uint32_t)ESP.getEfuseMac())///< Gets an ID from the chip +#endif + +#include "WC_AP_HTML.h" +#include "WiFiConnectParam.h" +/** Options for how a access point should continue if no WiFi connected */ +enum AP_Continue { + AP_NONE, ///< No action, continues to run code + AP_LOOP, ///< Stalls execution with an infinate loop + AP_RESTART, ///< Restarts the chip, allowing it to try to setup again. Handy for sensors when wifi is lost. + AP_RESET, ///< Same as AP_RESTART + AP_WAIT // Keep the AP and webserver running, sit quietly and be patient. +}; +/**************************************************************************/ +/*! + @brief Class that helps to connect to WiFi networks, that also has + captive portal web interface for configuration. + This is the base class for WiFiConntectOLED which displays + information on a OLED display. +*/ +/**************************************************************************/ +class WiFiConnect { + public: + /// Create WiFiConnect class + WiFiConnect(); + + boolean startConfigurationPortal(); + boolean startConfigurationPortal(AP_Continue apcontinue); + boolean startConfigurationPortal(AP_Continue apcontinue, const char* apName, const char* apPassword = NULL, bool paramsMode = false); + + boolean startParamsPortal(); + boolean startParamsPortal(AP_Continue apcontinue); + boolean startParamsPortal(AP_Continue apcontinue, const char* apName, const char* apPassword = NULL); + + void addParameter(WiFiConnectParam *p); + + void setAPName(const char* apName); + const char* getAPName(); + + void resetSettings(); + + boolean autoConnect(); + boolean autoConnect(const char* ssidName, const char* ssidPassword = NULL, WiFiMode_t acWiFiMode = WIFI_STA); + + + //sets a custom ip /gateway /subnet configuration + void setAPStaticIPConfig(IPAddress ip, IPAddress gw, IPAddress sn); + //sets config for a static IP + void setSTAStaticIPConfig(IPAddress ip, IPAddress gw, IPAddress sn); + + //called when AP mode and config portal is started + void setAPCallback( void (*func)(WiFiConnect*) ); + //called when settings have been changed and connection was successful + void setSaveConfigCallback( void (*func)(void) ); + + void setDebug(boolean isDebug); + + void setRetryAttempts(int attempts); + void setConnectionTimeoutSecs(int timeout); + void setAPModeTimeoutMins(int mins); + + boolean captivePortal(); + + //helpers + const char* statusToString(int state); + int getRSSIasQuality(int RSSI); + boolean isIp(String str); + String toStringIp(IPAddress ip); + virtual void displayTurnOFF(int ms = 5000); ///< Virtual method overriden in WiFiConnectOLED + virtual void displayLoop(); ///< Virtual method overriden in WiFiConnectOLED + virtual void displayON(); ///< Virtual method overriden in WiFiConnectOLED + protected: + boolean _debug = false; ///< Flag to determine wheter to output mesages or not + template + void DEBUG_WC(Generic text); + virtual void displayConnecting(int attempt, int totalAttempts); ///< Virtual method overriden in WiFiConnectOLED + virtual void displayConnected(); ///< Virtual method overriden in WiFiConnectOLED + virtual void displayAP(); ///< Virtual method overriden in WiFiConnectOLED + virtual void displayParams(); ///< Virtual method overriden in WiFiConnectOLED + virtual void displayManualReset(); ///< Virtual method overriden in WiFiConnectOLED + + private: + + + int _retryAttempts = 3; ///< Number of attempts when trying to connect to WiFi network + int _connectionTimeoutSecs = 10; ///< How log to wait for the connection to succeed or fail + int _apTimeoutMins = 3; ///< The amount of minutes of inactivity before the access point exits it routine + // DNS server + const byte DNS_PORT = 53; ///< Standard DNS Port number + + long _lastAPPage = 0; ///< The last time a page was accessed in the portal. Used for the inactivity timeout. + boolean _removeDuplicateAPs = true; ///< Flag to remove duplicate networks from scan results. + int _minimumQuality = 8; ///< The minimum netqork quality to be included in scan results. + int _paramsCount = 0; ///< The amount of custom parameters added via addParameter + boolean _readyToConnect = false; ///< Flag used in access point to determine if it should try to connect to the network. + String _ssid = " "; ///< Tempory holder for the network ssid + String _password = " "; ///< Tempory holder for the network password + + WiFiConnectParam* _params[WiFiConnect_MAX_PARAMS]; ///< Array to hold custom parameters + + std::unique_ptr dnsServer; ///< DNS Server for captive portal to redirect to Access Point +#ifdef ESP8266 + std::unique_ptr server; ///< Web server for serving access point pages +#else + std::unique_ptr server; ///< Web server for serving access point pages +#endif + + char _apName[33] ; ///< Holder for the access point name + char _apPassword[65] ; ///< Holder for the access point password + + IPAddress _ap_static_ip; ///< Variable for holding Static IP Address for the access point + IPAddress _ap_static_gw; ///< Variable for holding Static Gateway IP Address for the access point + IPAddress _ap_static_sn; ///< Variable for holding Static Subnet Mask IP Address for the access point + IPAddress _sta_static_ip; ///< Variable for holding Static IP Address for the network connection + IPAddress _sta_static_gw; ///< Variable for holding Static Gateway IP Address for the network connection + IPAddress _sta_static_sn; ///< Variable for holding Static Subnet Mask IP Address for the network connection + + void (*_apcallback)(WiFiConnect*) = NULL; + void (*_savecallback)(void) = NULL; + + + + void handleRoot(); + void handleParamRoot(); + void handleParams(); + void handleWifi(boolean scan); + void handleWifiSave(); + void handleInfo(); + void handleReset(); + void handle204(); + void handleNotFound(); + + template + auto optionalIPFromString(T *obj, const char *s) -> decltype( obj->fromString(s) ) { + return obj->fromString(s); + } + auto optionalIPFromString(...) -> bool { + DEBUG_WC("NO fromString METHOD ON IPAddress, you need ESP8266 core 2.1.0 or newer for Custom IP configuration to work."); + return false; + } + +}; + +#endif diff --git a/src/WiFiConnectParam.cpp b/src/WiFiConnectParam.cpp new file mode 100644 index 0000000..dc3f553 --- /dev/null +++ b/src/WiFiConnectParam.cpp @@ -0,0 +1,166 @@ +/*! + @file WiFiConnectParam.cpp + + WiFi Connection Manager with Captive Portal + + Introduction + + This is the documentation for WiFiConnect for the Arduino platform. + It is a WiFi connection manager for use with the popular ESP8266 and ESP32 chips. + It contains a captive portal to allow easy connection and changing of WiFi netwoks + via a web based interface and allows for additional user parameters. + It can also display messages via a OLED screen see WiFiConnectOLED class. + + This is a heavily customised version from the original WiFiManager + developed by https://github.com/tzapu . + + Dependencies + + This library depends on + ESP8266 Arduino Core and ESP32 Arduino Core being present on your system. + Please make sure you have installed the latest version before using this library. + + + Written by Stuart Blair. + + License + + GNU General Public License v3.0 licence, all text here must be included in any redistribution and you should receive a copy of the license file. + +*/ +#include "WiFiConnectParam.h" +/**************************************************************************/ +/*! + @brief Class object initialiser + @param custom + Custom HTML to be displayed in the access point for this item. +*/ +/**************************************************************************/ +WiFiConnectParam::WiFiConnectParam(const char *custom) { + _id = NULL; + _placeholder = NULL; + _length = 0; + _value = NULL; + + _customHTML = custom; +} +/**************************************************************************/ +/*! + @brief Class object initialiser + @param id + The unique ID for the html input box markup for this item + @param placeholder + Text to be displayed as the input box placeholder for this item + @param defaultValue + Default text to be displayed in the input box for this item + @param length + The maximum input text length for this item +*/ +/**************************************************************************/ +WiFiConnectParam::WiFiConnectParam(const char *id, const char *placeholder, const char *defaultValue, int length) { + init(id, placeholder, defaultValue, length, ""); +} +/**************************************************************************/ +/*! + @brief Class object initialiser + @param id + The unique ID for the html input box markup for this item + @param placeholder + Text to be displayed as the input box placeholder for this item + @param defaultValue + Default text to be displayed in the input box for this item + @param length + The maximum input text length for this item + @param custom + Custom HTML to be displayed in the access point for this item. +*/ +/**************************************************************************/ +WiFiConnectParam::WiFiConnectParam(const char *id, const char *placeholder, const char *defaultValue, int length, const char *custom) { + init(id, placeholder, defaultValue, length, custom); +} +/**************************************************************************/ +/*! + @brief Initialiser method + @param id + The unique ID for the html input box markup for this item + @param placeholder + Text to be displayed as the input box placeholder for this item + @param defaultValue + Default text to be displayed in the input box for this item + @param length + The maximum input text length for this item + @param custom + Custom HTML to be displayed in the access point for this item. +*/ +/**************************************************************************/ +void WiFiConnectParam::init(const char *id, const char *placeholder, const char *defaultValue, int length, const char *custom) { + _id = id; + _placeholder = placeholder; + _length = length; + setValue(defaultValue); + + _customHTML = custom; +} +/**************************************************************************/ +/*! + @brief Method to change the current value of the item + @param newValue + The new string value for the item +*/ +/**************************************************************************/ +void WiFiConnectParam::setValue(const char *newValue){ + if(_length>0){ + _value = new char[_length + 1]; + for (int i = 0; i < _length; i++) { + _value[i] = 0; + } + if (newValue != NULL) { + strncpy(_value, newValue, _length); + } + } +} +/**************************************************************************/ +/*! + @brief Function to get the current value of the item + @return The current value +*/ +/**************************************************************************/ +const char* WiFiConnectParam::getValue() { + return _value; +} +/**************************************************************************/ +/*! + @brief Function to get the current id of the item + @return The current id +*/ +/**************************************************************************/ +const char* WiFiConnectParam::getID() { + return _id; +} +/**************************************************************************/ +/*! + @brief Function to get the current placeholder text of the item + @return The current placeholder text +*/ +/**************************************************************************/ +const char* WiFiConnectParam::getPlaceholder() { + return _placeholder; +} +/**************************************************************************/ +/*! + @brief Function to get the maximum length allowed for the value of the item + @return The current maximum value length +*/ +/**************************************************************************/ +int WiFiConnectParam::getValueLength() { + return _length; +} +/**************************************************************************/ +/*! + @brief Function to get the current custom html markup of the item + @return The current custom html markup +*/ +/**************************************************************************/ +const char* WiFiConnectParam::getCustomHTML() { + return _customHTML; +} diff --git a/src/WiFiConnectParam.h b/src/WiFiConnectParam.h new file mode 100644 index 0000000..29eb731 --- /dev/null +++ b/src/WiFiConnectParam.h @@ -0,0 +1,68 @@ +/*! + @file WiFiConnectParam.h + + WiFi Connection Manager with Captive Portal + + Introduction + + This is the documentation for WiFiConnect for the Arduino platform. + It is a WiFi connection manager for use with the popular ESP8266 and ESP32 chips. + It contains a captive portal to allow easy connection and changing of WiFi netwoks + via a web based interface and allows for additional user parameters. + It can also display messages via a OLED screen see WiFiConnectOLED class. + + This is a heavily customised version from the original WiFiManager + developed by https://github.com/tzapu . + + Dependencies + + This library depends on + ESP8266 Arduino Core and ESP32 Arduino Core being present on your system. + Please make sure you have installed the latest version before using this library. + + + Written by Stuart Blair. + + License + + GNU General Public License v3.0 licence, all text here must be included in any redistribution and you should receive a copy of the license file. + +*/ +#ifndef WIFI_CONNECT_PARAM +#define WIFI_CONNECT_PARAM + +#ifndef WiFiConnect_MAX_PARAMS +#define WiFiConnect_MAX_PARAMS 10 ///< The maximum size of the param array and how many custom parameters we may have +#endif + +#include +/**************************************************************************/ +/*! + @brief Class that stores a custom parameter +*/ +/**************************************************************************/ +class WiFiConnectParam { + public: + WiFiConnectParam(const char *custom); + WiFiConnectParam(const char *id, const char *placeholder, const char *defaultValue, int length); + WiFiConnectParam(const char *id, const char *placeholder, const char *defaultValue, int length, const char *custom); + + const char *getID(); + const char *getValue(); + const char *getPlaceholder(); + int getValueLength(); + const char *getCustomHTML(); + void setValue(const char *newValue); + private: + const char *_id; + const char *_placeholder; + char *_value; + int _length; + const char *_customHTML; + + void init(const char *id, const char *placeholder, const char *defaultValue, int length, const char *custom); + + friend class WiFiConnect; ///< Declarion for WiFiConnect class + +}; +#endif diff --git a/src/font.h b/src/font.h index 2508865..d3e6a77 100644 --- a/src/font.h +++ b/src/font.h @@ -1,3 +1,4 @@ +/* const uint8_t Aura2Regular12pt8bBitmaps[] PROGMEM = { 0xFF, 0xFF, 0xFF, 0xC2, 0xF0, 0xCF, 0x3C, 0xF3, 0x8E, 0x30, 0x08, 0xC0, 0x8C, 0x18, 0xC1, 0x8C, 0x7F, 0xF1, 0x8C, 0x18, 0x81, 0x88, 0x11, 0x83, @@ -5072,7 +5073,7 @@ const GFXfont Aura2Regular8pt8b PROGMEM = { (uint8_t *)Aura2Regular8pt8bBitmaps, (GFXglyph *)Aura2Regular8pt8bGlyphs, 0x20, 0x400, 20 }; - +*/ const uint8_t Aura2Pro_Light7pt8bBitmaps[] PROGMEM = { 0xFF, 0x60, 0x99, 0x99, 0x24, 0x48, 0x93, 0xF2, 0x49, 0x12, 0x24, 0xFC, 0x91, 0x20, 0x10, 0xE5, 0x64, 0x91, 0x43, 0x85, 0x14, 0x51, 0x7E, 0x10, diff --git a/src/language.h b/src/language.h index aa9d66e..a0a42b2 100644 --- a/src/language.h +++ b/src/language.h @@ -1,6 +1,6 @@ // [number of languages][number of texts][max. length of text] -const char myLanguage[9][51][100] = { +static const char myLanguage[9][58][78] = { { "English", // English "Rotary direction changed", "Please release button", @@ -51,7 +51,14 @@ const char myLanguage[9][51][100] = { "Europe", "USA", "Show underscore in RDS", - "USB mode" + "USB mode", + "Wi-Fi active", + "Configure Wi-Fi", + "Connect to: ", + "to configure Wi-Fi", + "Trying to connect with Wi-Fi", + "FAILED.. WiFi disabled", + "CONNECTED!" }, { "Nederlands", // Dutch @@ -104,7 +111,14 @@ const char myLanguage[9][51][100] = { "Europa", "USA", "Toon underscore in RDS", - "USB mode" + "USB mode", + "Wi-Fi aktief", + "Configureer Wi-Fi", + "Verbind met: ", + "om Wi-Fi in te stellen", + "Verbinden met Wi-Fi...", + "MISLUKT.. WiFi uitgeschakeld", + "VERBONDEN!" }, { "Polski", // Polish @@ -157,7 +171,14 @@ const char myLanguage[9][51][100] = { "Europa", "USA", "Pokaz podkreslenia w RDS", - "Tryb USB" + "Tryb USB", + "Wi-Fi active", + "Configure Wi-Fi", + "Connect to: ", + "to configure Wi-Fi", + "Trying to connect with Wi-Fi", + "FAILED.. WiFi disabled", + "CONNECTED!" }, { "Hrvatski", // Croatian @@ -210,7 +231,14 @@ const char myLanguage[9][51][100] = { "Europa", "SAD", "Prikaži donju crtu u RDS-u", - "Način rada USB-a" + "Način rada USB-a", + "Wi-Fi active", + "Configure Wi-Fi", + "Connect to: ", + "to configure Wi-Fi", + "Trying to connect with Wi-Fi", + "FAILED.. WiFi disabled", + "CONNECTED!" }, { "Ελληνικά", // Greek @@ -263,7 +291,14 @@ const char myLanguage[9][51][100] = { "Ευρώπη", "Η.Π.Α.", "Εμφάνιση κάτω παύλας στο RDS", - "USB mode" + "USB mode", + "Wi-Fi active", + "Configure Wi-Fi", + "Connect to: ", + "to configure Wi-Fi", + "Trying to connect with Wi-Fi", + "FAILED.. WiFi disabled", + "CONNECTED!" }, { "Romana", // Romana @@ -316,7 +351,14 @@ const char myLanguage[9][51][100] = { "Europa", "SUA", "Arata evidentiat in RDS", - "USB mode" + "USB mode", + "Wi-Fi active", + "Configure Wi-Fi", + "Connect to: ", + "to configure Wi-Fi", + "Trying to connect with Wi-Fi", + "FAILED.. WiFi disabled", + "CONNECTED!" }, { "Deutsch", // German @@ -369,7 +411,14 @@ const char myLanguage[9][51][100] = { "Europa", "USA", "Zeige Unterstrich im RDS", - "USB mode" + "USB mode", + "Wi-Fi active", + "Configure Wi-Fi", + "Connect to: ", + "to configure Wi-Fi", + "Trying to connect with Wi-Fi", + "FAILED.. WiFi disabled", + "CONNECTED!" }, { "Czech", // English "Směr enkóderu byl změněn", @@ -421,7 +470,14 @@ const char myLanguage[9][51][100] = { "Evropa", "USA", "Zobrazit podtržítka v RDS", - "USB mode" + "USB mode", + "Wi-Fi active", + "Configure Wi-Fi", + "Connect to: ", + "to configure Wi-Fi", + "Trying to connect with Wi-Fi", + "FAILED.. WiFi disabled", + "CONNECTED!" }, { "Slovak", // English "Smer enkóderu bol zmenený", @@ -473,6 +529,13 @@ const char myLanguage[9][51][100] = { "Európa", "USA", "Zobraziť podtržítka v RDS", - "USB mode" + "USB mode", + "Wi-Fi active", + "Configure Wi-Fi", + "Connect to: ", + "to configure Wi-Fi", + "Trying to connect with Wi-Fi", + "FAILED.. WiFi disabled", + "CONNECTED!" }, };