Skip to content

Commit dc93959

Browse files
Add DNSServer examples which need WebServer
1 parent e7b3131 commit dc93959

File tree

10 files changed

+399
-14
lines changed

10 files changed

+399
-14
lines changed
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
#include <WiFi.h>
2+
#include <DNSServer.h>
3+
#include <WebServer.h>
4+
5+
const byte DNS_PORT = 53;
6+
IPAddress apIP(172, 217, 28, 1);
7+
DNSServer dnsServer;
8+
WebServer webServer(80);
9+
10+
String responseHTML = ""
11+
"<!DOCTYPE html><html lang='en'><head>"
12+
"<meta name='viewport' content='width=device-width'>"
13+
"<title>CaptivePortal</title></head><body>"
14+
"<h1>Hello World!</h1><p>This is a captive portal example."
15+
" All requests will be redirected here.</p></body></html>";
16+
17+
void setup() {
18+
WiFi.mode(WIFI_AP);
19+
WiFi.softAPConfig(apIP, apIP, IPAddress(255, 255, 255, 0));
20+
WiFi.softAP("DNSServer CaptivePortal example");
21+
22+
// if DNSServer is started with "*" for domain name, it will reply with
23+
// provided IP to all DNS request
24+
dnsServer.start(DNS_PORT, "*", apIP);
25+
26+
// replay to all requests with same HTML
27+
webServer.onNotFound([]() {
28+
webServer.send(200, "text/html", responseHTML);
29+
});
30+
webServer.begin();
31+
}
32+
33+
void loop() {
34+
dnsServer.processNextRequest();
35+
webServer.handleClient();
36+
}
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
#include <WiFi.h>
2+
#include <WiFiClient.h>
3+
#include <WebServer.h>
4+
#include <DNSServer.h>
5+
#include <LEAmDNS.h>
6+
#include <EEPROM.h>
7+
8+
/*
9+
This example serves a "hello world" on a WLAN and a SoftAP at the same time.
10+
The SoftAP allow you to configure WLAN parameters at run time. They are not setup in the sketch but saved on EEPROM.
11+
12+
Connect your computer or cell phone to wifi network pico_ap with password 12345678. A popup may appear and it allow you to go to WLAN config. If it does not then navigate to http://192.168.4.1/wifi and config it there.
13+
Then wait for the module to connect to your wifi and take note of the WLAN IP it got. Then you can disconnect from pico_ap and return to your regular WLAN.
14+
15+
Now the Pico is in your network. You can reach it through http://192.168.x.x/ (the IP you took note of) or maybe at http://picow.local too.
16+
17+
This is a captive portal because through the softAP it will redirect any http request to http://192.168.4.1/
18+
*/
19+
20+
/* Set these to your desired softAP credentials. They are not configurable at runtime */
21+
#ifndef APSSID
22+
#define APSSID "pico_ap"
23+
#define APPSK "12345678"
24+
#endif
25+
26+
const char *softAP_ssid = APSSID;
27+
const char *softAP_password = APPSK;
28+
29+
/* hostname for mDNS. Should work at least on windows. Try http://picow.local */
30+
const char *myHostname = "picow";
31+
32+
/* Don't set this wifi credentials. They are configurated at runtime and stored on EEPROM */
33+
char ssid[33] = "";
34+
char password[65] = "";
35+
36+
// DNS server
37+
const byte DNS_PORT = 53;
38+
DNSServer dnsServer;
39+
40+
// Web server
41+
WebServer server(80);
42+
43+
/* Soft AP network parameters */
44+
IPAddress apIP(172, 217, 28, 1);
45+
IPAddress netMsk(255, 255, 255, 0);
46+
47+
48+
/** Should I connect to WLAN asap? */
49+
boolean connect;
50+
51+
/** Last time I tried to connect to WLAN */
52+
unsigned long lastConnectTry = 0;
53+
54+
/** Current WLAN status */
55+
unsigned int status = WL_IDLE_STATUS;
56+
57+
void setup() {
58+
delay(1000);
59+
Serial.begin(115200);
60+
Serial.println();
61+
Serial.println("Configuring access point...");
62+
/* You can remove the password parameter if you want the AP to be open. */
63+
WiFi.softAPConfig(apIP, apIP, netMsk);
64+
WiFi.softAP(softAP_ssid, softAP_password);
65+
delay(500); // Without delay I've seen the IP address blank
66+
Serial.print("AP IP address: ");
67+
Serial.println(WiFi.softAPIP());
68+
69+
/* Setup the DNS server redirecting all the domains to the apIP */
70+
dnsServer.setErrorReplyCode(DNSReplyCode::NoError);
71+
dnsServer.start(DNS_PORT, "*", apIP);
72+
73+
/* Setup web pages: root, wifi config pages, SO captive portal detectors and not found. */
74+
server.on("/", handleRoot);
75+
server.on("/wifi", handleWifi);
76+
server.on("/wifisave", handleWifiSave);
77+
server.on("/generate_204", handleRoot); // Android captive portal. Maybe not needed. Might be handled by notFound handler.
78+
server.on("/fwlink", handleRoot); // Microsoft captive portal. Maybe not needed. Might be handled by notFound handler.
79+
server.onNotFound(handleNotFound);
80+
server.begin(); // Web server start
81+
Serial.println("HTTP server started");
82+
//loadCredentials(); // Load WLAN credentials from network
83+
ssid[0] = 0;
84+
password[0] = 0;
85+
connect = strlen(ssid) > 0; // Request WLAN connect if there is a SSID
86+
}
87+
88+
void connectWifi() {
89+
Serial.println("Connecting as wifi client...");
90+
WiFi.disconnect();
91+
WiFi.end();
92+
WiFi.begin(ssid, password);
93+
int connRes = WiFi.waitForConnectResult();
94+
Serial.print("connRes: ");
95+
Serial.println(connRes);
96+
}
97+
98+
void loop() {
99+
if (connect) {
100+
Serial.println("Connect requested");
101+
connect = false;
102+
connectWifi();
103+
lastConnectTry = millis();
104+
}
105+
{
106+
unsigned int s = WiFi.status();
107+
if (s == 0 && millis() > (lastConnectTry + 60000)) {
108+
/* If WLAN disconnected and idle try to connect */
109+
/* Don't set retry time too low as retry interfere the softAP operation */
110+
connect = true;
111+
}
112+
if (status != s) { // WLAN status change
113+
Serial.print("Status: ");
114+
Serial.println(s);
115+
status = s;
116+
if (s == WL_CONNECTED) {
117+
/* Just connected to WLAN */
118+
Serial.println("");
119+
Serial.print("Connected to ");
120+
Serial.println(ssid);
121+
Serial.print("IP address: ");
122+
Serial.println(WiFi.localIP());
123+
124+
// Setup MDNS responder
125+
if (!MDNS.begin(myHostname)) {
126+
Serial.println("Error setting up MDNS responder!");
127+
} else {
128+
Serial.println("mDNS responder started");
129+
// Add service to MDNS-SD
130+
MDNS.addService("http", "tcp", 80);
131+
}
132+
} else if (s == WL_NO_SSID_AVAIL) {
133+
WiFi.disconnect();
134+
}
135+
}
136+
if (s == WL_CONNECTED) { MDNS.update(); }
137+
}
138+
// Do work:
139+
// DNS
140+
dnsServer.processNextRequest();
141+
// HTTP
142+
server.handleClient();
143+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/** Load WLAN credentials from EEPROM */
2+
void loadCredentials() {
3+
EEPROM.begin(512);
4+
EEPROM.get(0, ssid);
5+
EEPROM.get(0 + sizeof(ssid), password);
6+
char ok[2 + 1];
7+
EEPROM.get(0 + sizeof(ssid) + sizeof(password), ok);
8+
EEPROM.end();
9+
if (String(ok) != String("OK")) {
10+
ssid[0] = 0;
11+
password[0] = 0;
12+
}
13+
Serial.println("Recovered credentials:");
14+
Serial.println(ssid);
15+
Serial.println(strlen(password) > 0 ? "********" : "<no password>");
16+
}
17+
18+
/** Store WLAN credentials to EEPROM */
19+
void saveCredentials() {
20+
EEPROM.begin(512);
21+
EEPROM.put(0, ssid);
22+
EEPROM.put(0 + sizeof(ssid), password);
23+
char ok[2 + 1] = "OK";
24+
EEPROM.put(0 + sizeof(ssid) + sizeof(password), ok);
25+
EEPROM.commit();
26+
EEPROM.end();
27+
}
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
/** Handle root or redirect to captive portal */
2+
void handleRoot() {
3+
if (captivePortal()) { // If caprive portal redirect instead of displaying the page.
4+
return;
5+
}
6+
server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate");
7+
server.sendHeader("Pragma", "no-cache");
8+
server.sendHeader("Expires", "-1");
9+
10+
String Page;
11+
Page += F("<!DOCTYPE html><html lang='en'><head>"
12+
"<meta name='viewport' content='width=device-width'>"
13+
"<title>CaptivePortal</title></head><body>"
14+
"<h1>HELLO WORLD!!</h1>");
15+
if (server.client()->localIP() == apIP) {
16+
Page += String(F("<p>You are connected through the soft AP: ")) + softAP_ssid + F("</p>");
17+
} else {
18+
Page += String(F("<p>You are connected through the wifi network: ")) + ssid + F("</p>");
19+
}
20+
Page += F("<p>You may want to <a href='/wifi'>config the wifi connection</a>.</p>"
21+
"</body></html>");
22+
23+
server.send(200, "text/html", Page);
24+
}
25+
26+
/** Redirect to captive portal if we got a request for another domain. Return true in that case so the page handler do not try to handle the request again. */
27+
boolean captivePortal() {
28+
if (!isIp(server.hostHeader()) && server.hostHeader() != (String(myHostname) + ".local")) {
29+
Serial.println("Request redirected to captive portal");
30+
server.sendHeader("Location", String("http://") + toStringIp(server.client()->localIP()), true);
31+
server.send(302, "text/plain", ""); // Empty content inhibits Content-length header so we have to close the socket ourselves.
32+
server.client()->stop(); // Stop is needed because we sent no content length
33+
return true;
34+
}
35+
return false;
36+
}
37+
38+
/** Wifi config page handler */
39+
void handleWifi() {
40+
server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate");
41+
server.sendHeader("Pragma", "no-cache");
42+
server.sendHeader("Expires", "-1");
43+
44+
String Page;
45+
Page += F("<!DOCTYPE html><html lang='en'><head>"
46+
"<meta name='viewport' content='width=device-width'>"
47+
"<title>CaptivePortal</title></head><body>"
48+
"<h1>Wifi config</h1>");
49+
if (server.client()->localIP() == apIP) {
50+
Page += String(F("<p>You are connected through the soft AP: ")) + softAP_ssid + F("</p>");
51+
} else {
52+
Page += String(F("<p>You are connected through the wifi network: ")) + ssid + F("</p>");
53+
}
54+
Page += String(F("\r\n<br />"
55+
"<table><tr><th align='left'>SoftAP config</th></tr>"
56+
"<tr><td>SSID "))
57+
+ String(softAP_ssid) + F("</td></tr>"
58+
"<tr><td>IP ")
59+
+ toStringIp(WiFi.softAPIP()) + F("</td></tr>"
60+
"</table>"
61+
"\r\n<br />"
62+
"<table><tr><th align='left'>WLAN config</th></tr>"
63+
"<tr><td>SSID ")
64+
+ String(ssid) + F("</td></tr>"
65+
"<tr><td>IP ")
66+
+ toStringIp(WiFi.localIP()) + F("</td></tr>"
67+
"</table>"
68+
"\r\n<br />"
69+
"<table><tr><th align='left'>WLAN list (refresh if any missing)</th></tr>");
70+
Serial.println("scan start");
71+
int n = WiFi.scanNetworks();
72+
Serial.println("scan done");
73+
if (n > 0) {
74+
for (int i = 0; i < n; i++) { Page += String(F("\r\n<tr><td>SSID ")) + WiFi.SSID(i) + ((WiFi.encryptionType(i) == ENC_TYPE_NONE) ? F(" ") : F(" *")) + F(" (") + WiFi.RSSI(i) + F(")</td></tr>"); }
75+
} else {
76+
Page += F("<tr><td>No WLAN found</td></tr>");
77+
}
78+
Page += F("</table>"
79+
"\r\n<br /><form method='POST' action='wifisave'><h4>Connect to network:</h4>"
80+
"<input type='text' placeholder='network' name='n'/>"
81+
"<br /><input type='password' placeholder='password' name='p'/>"
82+
"<br /><input type='submit' value='Connect/Disconnect'/></form>"
83+
"<p>You may want to <a href='/'>return to the home page</a>.</p>"
84+
"</body></html>");
85+
server.send(200, "text/html", Page);
86+
server.client()->stop(); // Stop is needed because we sent no content length
87+
}
88+
89+
/** Handle the WLAN save form and redirect to WLAN config page again */
90+
void handleWifiSave() {
91+
Serial.println("wifi save");
92+
server.arg("n").toCharArray(ssid, sizeof(ssid) - 1);
93+
server.arg("p").toCharArray(password, sizeof(password) - 1);
94+
server.sendHeader("Location", "wifi", true);
95+
server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate");
96+
server.sendHeader("Pragma", "no-cache");
97+
server.sendHeader("Expires", "-1");
98+
server.send(302, "text/plain", ""); // Empty content inhibits Content-length header so we have to close the socket ourselves.
99+
server.client()->stop(); // Stop is needed because we sent no content length
100+
saveCredentials();
101+
connect = strlen(ssid) > 0; // Request WLAN connect with new credentials if there is a SSID
102+
}
103+
104+
void handleNotFound() {
105+
if (captivePortal()) { // If caprive portal redirect instead of displaying the error page.
106+
return;
107+
}
108+
String message = F("File Not Found\n\n");
109+
message += F("URI: ");
110+
message += server.uri();
111+
message += F("\nMethod: ");
112+
message += (server.method() == HTTP_GET) ? "GET" : "POST";
113+
message += F("\nArguments: ");
114+
message += server.args();
115+
message += F("\n");
116+
117+
for (uint8_t i = 0; i < server.args(); i++) { message += String(F(" ")) + server.argName(i) + F(": ") + server.arg(i) + F("\n"); }
118+
server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate");
119+
server.sendHeader("Pragma", "no-cache");
120+
server.sendHeader("Expires", "-1");
121+
server.send(404, "text/plain", message);
122+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/** Is this an IP? */
2+
boolean isIp(String str) {
3+
for (size_t i = 0; i < str.length(); i++) {
4+
int c = str.charAt(i);
5+
if (c != '.' && (c < '0' || c > '9')) { return false; }
6+
}
7+
return true;
8+
}
9+
10+
/** IP to String? */
11+
String toStringIp(IPAddress ip) {
12+
String res = "";
13+
for (int i = 0; i < 3; i++) { res += String((ip >> (8 * i)) & 0xFF) + "."; }
14+
res += String(((ip >> 8 * 3)) & 0xFF);
15+
return res;
16+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
#include <ESP8266WiFi.h>
2+
#include <DNSServer.h>
3+
#include <WebServer.h>
4+
5+
const byte DNS_PORT = 53;
6+
IPAddress apIP(172, 217, 28, 1);
7+
DNSServer dnsServer;
8+
WebServer webServer(80);
9+
10+
void setup() {
11+
WiFi.mode(WIFI_AP);
12+
WiFi.softAPConfig(apIP, apIP, IPAddress(255, 255, 255, 0));
13+
WiFi.softAP("picow", "12345678");
14+
15+
// modify TTL associated with the domain name (in seconds)
16+
// default is 60 seconds
17+
dnsServer.setTTL(300);
18+
// set which return code will be used for all other domains (e.g. sending
19+
// ServerFailure instead of NonExistentDomain will reduce number of queries
20+
// sent by clients)
21+
// default is DNSReplyCode::NonExistentDomain
22+
dnsServer.setErrorReplyCode(DNSReplyCode::ServerFailure);
23+
24+
// start DNS server for a specific domain name
25+
dnsServer.start(DNS_PORT, "www.example.com", apIP);
26+
27+
// simple HTTP server to see that DNS server is working
28+
webServer.onNotFound([]() {
29+
String message = "Hello World!\n\n";
30+
message += "URI: ";
31+
message += webServer.uri();
32+
33+
webServer.send(200, "text/plain", message);
34+
});
35+
webServer.begin();
36+
}
37+
38+
void loop() {
39+
dnsServer.processNextRequest();
40+
webServer.handleClient();
41+
}

0 commit comments

Comments
 (0)