-
Notifications
You must be signed in to change notification settings - Fork 0
/
wakeonweb.ino
321 lines (286 loc) · 8.47 KB
/
wakeonweb.ino
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include <time.h>
// define WIFI_SSID and WIFI_PASS, optionally SITE_USER, SITE_PASS, SSH_USER and SSH_PASS (not your actual SSH credentials!) in credentials.h
#include "credentials.h"
#include "web.h"
// UPDATE CACHE_ETAG EVERY TIME A CACHEABLE RESPONSE BODY IS MODIFIED OR ADDED!
// cacheable URIs: "/", "/main.css", "/favicon.svg", "/favicon.ico"
#define CACHE_ETAG "0004"
#define CACHE_CONTROL "max-age=604800, immutable" // one week
/* ---- String constants for HTTP Date format construction ---- */
const char dayName0[] PROGMEM = "Mon";
const char dayName1[] PROGMEM = "Tue";
const char dayName2[] PROGMEM = "Wed";
const char dayName3[] PROGMEM = "Thu";
const char dayName4[] PROGMEM = "Fri";
const char dayName5[] PROGMEM = "Sat";
const char dayName6[] PROGMEM = "Sun";
const char monthName00[] PROGMEM = "Jan";
const char monthName01[] PROGMEM = "Feb";
const char monthName02[] PROGMEM = "Mar";
const char monthName03[] PROGMEM = "Apr";
const char monthName04[] PROGMEM = "May";
const char monthName05[] PROGMEM = "Jun";
const char monthName06[] PROGMEM = "Jul";
const char monthName07[] PROGMEM = "Aug";
const char monthName08[] PROGMEM = "Sep";
const char monthName09[] PROGMEM = "Oct";
const char monthName10[] PROGMEM = "Nov";
const char monthName11[] PROGMEM = "Dec";
const char *const dayTable[] = {dayName0, dayName1, dayName2, dayName3, dayName4, dayName5, dayName6};
const char *const monthTable[] =
{monthName00, monthName01, monthName02, monthName03, monthName04, monthName05, monthName06, monthName07, monthName08, monthName09, monthName10, monthName11};
char cacheDate[] = "DDD, dd MMM yyyy hh:mm:ss GMT";
/* ---- Runtime variables ---- */
ESP8266WebServer server(80);
boolean pwr = false;
boolean ssh = false;
/* ---- Setup functions ---- */
// Gets the current real time from the internet
void setupClock()
{
configTime(0, 0, "pool.ntp.org", "time.nist.gov");
Serial.println("Synchrozining time");
time_t now = time(nullptr);
while (now < 1)
{
delay(1000);
now = time(nullptr);
}
Serial.println("Synchronized");
// write GMT date and time to cacheDate in HTTP Date format
// "<day-name>, <day> <month-name> <year> <hour>:<minute>:<second> GMT"
struct tm timeInfo;
gmtime_r(&now, &timeInfo);
sprintf(
cacheDate,
"%s, %02d %s %04d %02d:%02d:%02d GMT",
dayTable[timeInfo.tm_wday - 1],
timeInfo.tm_mday,
monthTable[timeInfo.tm_mon],
timeInfo.tm_year + 1900,
timeInfo.tm_hour,
timeInfo.tm_min,
timeInfo.tm_sec);
Serial.println(cacheDate);
}
// Setup entry point
void setup()
{
pinMode(D7, INPUT);
pinMode(D8, OUTPUT);
digitalWrite(D8, LOW);
Serial.begin(115200);
// connect to WiFi
Serial.println("Connecting");
// put WIFI_SSID and WIFI_PASS definitions in "credentials.h"
WiFi.begin(WIFI_SSID, WIFI_PASS);
while (WiFi.status() != WL_CONNECTED)
{
delay(1000);
}
Serial.println();
Serial.println("Connected");
Serial.println(WiFi.localIP());
// date and time is used to generate "Date" header a single time at startup
setupClock();
// server setup
server.on("/", handleRoot);
server.on("/main.css", handleMainStylesheet);
server.on("/state.css", handleStateStylesheet);
server.on("/favicon.svg", handleFaviconSvg);
server.on("/favicon.ico", handleFaviconIco);
server.on("/t", handleTrigger);
server.on("/s", handleSshNotification);
server.onNotFound(handleNotFound);
server.begin();
Serial.println("Web server started");
}
/* ---- Main program loop ---- */
// Main program loop
void loop()
{
// wait for clients while checking for power
if (digitalRead(D7) == LOW)
{
pwr = true;
}
else
{
pwr = false;
ssh = false;
}
server.handleClient();
}
/* ---- Web server helper functions ---- */
// Validates the cache of a browser using the ETag
bool validateCache()
{
// if the header is missing, server.header(name) will return an empty string
if (server.header("If-None-Match") == CACHE_ETAG)
{
server.send(304, "*/*", "");
return true;
}
return false;
}
// Sets the headers required to tell browsers to cache the next response
void enableCaching()
{
server.sendHeader("Date", cacheDate, false);
server.sendHeader("ETag", CACHE_ETAG, false);
server.sendHeader("Cache-Control", CACHE_CONTROL, false);
}
// Sets the Cache-Control header to prevent caching of the next response
void disableCaching()
{
server.sendHeader("Cache-Control", "no-store");
}
// Checks the authentication for a website request (if credentials were configured)
bool authenticateSite()
{
#if defined(SITE_USER) && defined(SITE_PASS)
// put SITE_USER and SITE_PASS definitions in "credentials.h"
if (!server.authenticate(SITE_USER, SITE_PASS))
{
server.requestAuthentication(BASIC_AUTH, (const char *)__null, "401 Unauthorized");
return false;
}
#endif
return true;
}
// Checks the authentication for an 'SSH-ready' notification request (if credentials were configured)
bool authenticateSshNotification()
{
#if defined(SSH_USER) && defined(SSH_PASS)
// put SSH_USER and SSH_PASS definitions in "credentials.h" (not your actual SSH credentials!)
if (!server.authenticate(SSH_USER, SSH_PASS))
{
server.requestAuthentication(BASIC_AUTH, (const char *)__null, "401 Unauthorized");
return false;
}
#endif
return true;
}
/* ---- Web server request handling functions ---- */
// Handles request for "/" (cacheable)
void handleRoot()
{
if (validateCache())
{
return;
}
enableCaching();
server.send(200, "text/html", bodyRoot);
}
// Handles requests for "/main.css" (cacheable)
void handleMainStylesheet()
{
if (validateCache())
{
return;
}
enableCaching();
server.send(200, "text/css", bodyMainCss);
}
// Handles requests for "/state.css" (not cacheable, auth required, carries all server state information)
void handleStateStylesheet()
{
if (!authenticateSite())
{
return;
}
disableCaching();
/*
State display works by overwriting the "display" property of the elements we want to show or hide.
Per default (in "/main.css"), elements with class "unknown" are shown while all others are hidden.
With "/state.css" available, we want to hide all class="unknown" elements and show the ones that
represent the current server state. Note that when pwr == true, we only hide class="state unknown"
elements, because the disabled "Power on" button should still be shown.
*/
if (pwr)
{
if (ssh)
{
server.send(200, "text/css", ".state.unknown{display:none!important;}.pwron{display:initial;}.sshyes{display:initial;}");
}
else
{
server.send(200, "text/css", ".state.unknown{display:none!important;}.pwron{display:initial;}.sshno{display:initial;}");
}
}
else
{
if (ssh)
{
server.send(200, "text/css", ".unknown{display:none!important;}.pwroff{display:initial;}.sshyes{display:initial;}");
}
else
{
server.send(200, "text/css", ".unknown{display:none!important;}.pwroff{display:initial;}.sshno{display:initial;}");
}
}
}
// Handles requests for "/favicon.svg" (cacheable)
void handleFaviconSvg()
{
if (validateCache())
{
return;
}
enableCaching();
server.send(200, "image/svg+xml", bodyFaviconSvg);
}
// Handles requests for "/favicon.ico" (cacheable)
void handleFaviconIco()
{
if (validateCache())
{
return;
}
enableCaching();
// binary data needs to be sent with size explicitly specified
server.send(200, "image/x-icon", bodyFaviconIco, sizeof(bodyFaviconIco));
}
// Handles requests for "/t" (not cacheable, auth required, triggers power-on)
void handleTrigger()
{
if (!authenticateSite())
{
return;
}
disableCaching();
if (pwr)
{
server.send(409, "text/plain", "409 Conflict\n\nServer is already powered on.");
return;
}
powerOn();
server.sendHeader("Location", "/", false);
server.send(302, "text/plain", "302 Found\n\nServer powered on successfully.");
}
// Handles requests for "/s" (not cacheable, auth required, marks the SSH service as ready)
void handleSshNotification()
{
if (!authenticateSshNotification())
{
return;
}
disableCaching();
ssh = true;
server.send(200, "text/plain", "200 OK\n\nSSH service marked as ready.");
}
// Handles requests to unknown URIs by replying with a 404 error
void handleNotFound()
{
server.send(404, "text/plain", "404 Not Found");
}
/* ---- Actual power-on function ---- */
// Triggers the power button of the connected mainboard
void powerOn()
{
digitalWrite(D8, HIGH);
delay(50);
digitalWrite(D8, LOW);
}