Commit | Line | Data |
---|---|---|
0d5d6475 B |
1 | #include <TimeLib.h> |
2 | #include <Time.h> | |
f1139a83 B |
3 | #include <InputDebounce.h> |
4 | #include <Wiegand.h> | |
5 | #include <ESP8266WiFi.h> | |
0d5d6475 | 6 | #include <WiFiUdp.h> |
f1139a83 B |
7 | #include <DNSServer.h> |
8 | #include <ESP8266WebServer.h> | |
bf22b081 | 9 | #include <ArduinoOTA.h> |
f1139a83 B |
10 | #include <WiFiManager.h> |
11 | #include <ESP8266mDNS.h> | |
12 | #include <FS.h> | |
13 | ||
14 | // MOSFET for door lock activation | |
15 | // ON/HIGH == ground the output screw terminal | |
16 | #define MOSFET 16 | |
17 | ||
18 | // Pin for LED / WS2812 | |
19 | #define STATUSLED 2 | |
20 | ||
21 | // Wiegand keyfob reader pins | |
22 | #define WD0 12 | |
23 | #define WD1 13 | |
24 | ||
25 | // Door lock sense pin | |
26 | #define SENSE 14 | |
27 | ||
28 | // emergency release switch | |
29 | #define ERELEASE 15 | |
30 | ||
31 | // Buzzer/LED on keyfob control | |
32 | #define BUZZER 4 // Gnd to beep | |
33 | #define DOORLED 5 // low = green, otherwise red | |
34 | ||
35 | // orientation of some signals | |
36 | #define DLED_GREEN LOW | |
37 | #define DLED_RED HIGH | |
38 | #define LOCK_OPEN HIGH | |
39 | #define LOCK_CLOSE LOW | |
40 | #define BUZZ_ON LOW | |
41 | #define BUZZ_OFF HIGH | |
42 | ||
43 | ||
44 | ||
45 | /*********************** | |
46 | * Configuration parameters | |
47 | */ | |
48 | // AP that it will apoear as for configuration | |
49 | #define MANAGER_AP "DoorLock" | |
50 | ||
51 | // Credentials required to reset or upload new info | |
bf22b081 B |
52 | // should contain #def's for www_username and www_password |
53 | #include "config.h" | |
f1139a83 B |
54 | |
55 | // files to store card/fob data in | |
56 | #define CARD_TMPFILE "/cards.tmp" | |
57 | #define CARD_FILE "/cards.dat" | |
0d5d6475 | 58 | #define LOG_FILE "/log.dat" |
f1139a83 B |
59 | |
60 | // how long to hold the latch open in millis | |
61 | #define LATCH_HOLD 5000 | |
62 | ||
0d5d6475 B |
63 | // webserver for configuration portnumber |
64 | #define CONFIG_PORT 80 | |
65 | ||
66 | // ntp server to use | |
67 | #define NTP_SERVER "1.uk.pool.ntp.org" | |
68 | ||
bf22b081 B |
69 | // NTP retry time (mS) |
70 | #define NTP_RETRY 30000 | |
71 | ||
f1139a83 B |
72 | /*************************** |
73 | * code below | |
74 | */ | |
75 | WIEGAND wg; | |
0d5d6475 B |
76 | ESP8266WebServer server(CONFIG_PORT); |
77 | ||
78 | const unsigned int localPort = 2390; | |
79 | IPAddress ntpServerIP; | |
80 | ||
81 | const int NTP_PACKET_SIZE = 48; // NTP time stamp is in the first 48 bytes of the message | |
82 | ||
83 | byte packetBuffer[ NTP_PACKET_SIZE]; //buffer to hold incoming and outgoing packets | |
84 | ||
85 | WiFiUDP udp; | |
86 | ||
87 | ||
bb055419 B |
88 | unsigned long ntp_lastset = 0; |
89 | unsigned long ntp_lasttry = 0; | |
90 | ||
b091c740 B |
91 | bool ota_enabled = false; |
92 | ||
742bcec9 | 93 | /* User requests to enable OTA mode */ |
b091c740 B |
94 | void enable_ota(void) |
95 | { | |
96 | if (!ota_enabled) { | |
742bcec9 B |
97 | if (!server.authenticate(www_username, www_password)) |
98 | return server.requestAuthentication(); | |
99 | ||
b091c740 B |
100 | Serial.println("Enabling OTA Mode."); |
101 | ArduinoOTA.begin(); | |
102 | ota_enabled = true; | |
103 | } | |
104 | handleRoot(); | |
105 | } | |
bb055419 | 106 | |
0d5d6475 B |
107 | /* compose and send an NTP time request packet */ |
108 | void ntp_send() | |
109 | { | |
110 | if (ntpServerIP == INADDR_NONE) { | |
bb055419 B |
111 | if (WiFi.hostByName(NTP_SERVER, ntpServerIP) == 1) { |
112 | if (ntpServerIP == IPAddress(1,0,0,0)) { | |
113 | Serial.println("DNS lookup failed for " NTP_SERVER " try again later."); | |
114 | ntpServerIP = INADDR_NONE; | |
115 | return; | |
116 | } | |
117 | Serial.print("Got NTP server " NTP_SERVER " address "); | |
118 | Serial.println(ntpServerIP); | |
119 | } else { | |
120 | Serial.println("DNS lookup of " NTP_SERVER " failed."); | |
121 | return; | |
122 | } | |
0d5d6475 B |
123 | } |
124 | ||
bb055419 | 125 | ntp_lasttry = millis(); |
0d5d6475 B |
126 | memset(packetBuffer, 0, NTP_PACKET_SIZE); |
127 | // Initialize values needed to form NTP request | |
128 | // (see URL above for details on the packets) | |
129 | packetBuffer[0] = 0b11100011; // LI, Version, Mode | |
130 | packetBuffer[1] = 0; // Stratum, or type of clock | |
131 | packetBuffer[2] = 6; // Polling Interval | |
132 | packetBuffer[3] = 0xEC; // Peer Clock Precision | |
133 | // 8 bytes of zero for Root Delay & Root Dispersion | |
134 | packetBuffer[12] = 49; | |
135 | packetBuffer[13] = 0x4E; | |
136 | packetBuffer[14] = 49; | |
137 | packetBuffer[15] = 52; | |
138 | ||
139 | // all NTP fields have been given values, now | |
140 | // you can send a packet requesting a timestamp: | |
141 | udp.beginPacket(ntpServerIP, 123); //NTP requests are to port 123 | |
142 | udp.write(packetBuffer, NTP_PACKET_SIZE); | |
143 | udp.endPacket(); | |
144 | ||
145 | Serial.println("Sending NTP request"); | |
146 | } | |
147 | ||
148 | /* request a time update from NTP and parse the result */ | |
149 | time_t ntp_fetch() | |
150 | { | |
151 | while (udp.parsePacket() > 0); // discard old udp packets | |
152 | ntp_send(); | |
f1139a83 | 153 | |
0d5d6475 | 154 | uint32_t beginWait = millis(); |
f1139a83 | 155 | |
0d5d6475 B |
156 | while (millis() - beginWait < 2500) { |
157 | int size = udp.parsePacket(); | |
158 | if (size >= NTP_PACKET_SIZE) { | |
159 | udp.read(packetBuffer, NTP_PACKET_SIZE); | |
160 | ||
161 | // this is NTP time (seconds since Jan 1 1900): | |
162 | unsigned long secsSince1900 = packetBuffer[40] << 24 | packetBuffer[41] << 16 | packetBuffer[42] << 8 | packetBuffer[43]; | |
163 | const unsigned long seventyYears = 2208988800UL; | |
164 | time_t unixtime = secsSince1900 - seventyYears; | |
165 | ||
bb055419 | 166 | ntp_lastset = millis(); |
0d5d6475 B |
167 | Serial.print("NTP update unixtime="); |
168 | Serial.println(unixtime); | |
169 | return unixtime; | |
170 | } | |
171 | } | |
172 | Serial.println("No NTP response"); | |
173 | return 0; | |
174 | } | |
175 | ||
176 | /* how big is a file */ | |
f1139a83 B |
177 | int fileSize(const char *filename) |
178 | { | |
179 | int ret = -1; | |
180 | File file = SPIFFS.open(filename, "r"); | |
181 | if (file) { | |
182 | ret = file.size(); | |
183 | file.close(); | |
184 | } | |
185 | return ret; | |
186 | } | |
187 | ||
0d5d6475 B |
188 | |
189 | /* HTTP page request for / */ | |
f1139a83 B |
190 | void handleRoot() |
191 | { | |
192 | char mtime[16]; | |
193 | int sec = millis() / 1000; | |
194 | int mi = sec / 60; | |
195 | int hr = mi / 60; | |
196 | int day = hr / 24; | |
197 | ||
198 | snprintf(mtime, 16, "%dd %02d:%02d:%02d", day, hr % 24, mi % 60, sec % 60); | |
199 | ||
200 | String out = "<html>\ | |
201 | <head>\ | |
202 | <title>Door Lock</title>\ | |
203 | <style>\ | |
204 | body { background-color: #cccccc; font-family: Arial, Helvetica, Sans-Serif; }\ | |
205 | </style>\ | |
206 | </head>\ | |
207 | <body>\ | |
208 | <h1>Door Lock!</h1>\ | |
0d5d6475 B |
209 | <p>Uptime: " + (String)mtime + "</p>\n"; |
210 | ||
211 | if (timeStatus() == timeSet) { | |
212 | time_t when = now(); | |
213 | out += "<p>Time now: " + getDate(when) + " " + getTime(when) + "</p>\n"; | |
214 | } | |
215 | ||
216 | ||
217 | FSInfo fs_info; | |
218 | if (SPIFFS.info(fs_info)) { | |
219 | out += "<p>Onboard Flash disk: - Size:"+String(fs_info.totalBytes)+" Used:"+String(fs_info.usedBytes)+"</p>\n"; | |
220 | } | |
221 | ||
222 | out += "<p>Lock is currently "; | |
223 | if (digitalRead(SENSE) == HIGH) out += "LOCKED"; else out += "OPEN"; | |
224 | out += "</p>\n"; | |
f1139a83 B |
225 | |
226 | if (SPIFFS.exists(CARD_FILE)) { | |
227 | ||
228 | out += "<p>Cardfile: " + String(CARD_FILE) + " is " + fileSize(CARD_FILE) + " bytes"; | |
229 | int count = sanityCheck(CARD_FILE); | |
230 | if (count <= 0) { | |
231 | out += ", in an invalid file"; | |
232 | } else { | |
233 | out += ", contains " + String(count) + " keyfob IDs"; | |
234 | out += " - <a href=\"/download\">Download</a>"; | |
235 | } | |
236 | ||
237 | out += ".</p>"; | |
238 | } | |
239 | ||
240 | out += "<ul>\ | |
241 | <li><a href=\"/reset\">Reset Configuration</a>\ | |
242 | <li><a href=\"/upload\">Upload Cardlist</a>"; | |
243 | ||
0d5d6475 B |
244 | if (SPIFFS.exists(LOG_FILE)) { |
245 | out += "<li><a href=\"/wipelog\">Wipe log file</a>"; | |
bf22b081 | 246 | out += "<li><a href=\"/viewlog?count=30\">View entry log</a>"; |
0d5d6475 | 247 | out += "<li><a href=\"/download_logfile\">Download full logfile</a>"; |
f1139a83 | 248 | } |
b091c740 B |
249 | |
250 | if (ota_enabled) { | |
251 | out += "<li>OTA Updates ENABLED."; | |
252 | } else { | |
253 | out += "<li><a href=\"/enable_ota\">Enable OTA Updates</a>"; | |
254 | } | |
0d5d6475 | 255 | |
0d5d6475 | 256 | out += "</ul>"; |
0d5d6475 | 257 | out += "</body>\ |
f1139a83 B |
258 | </html>"; |
259 | ||
260 | server.send( 200, "text/html", out); | |
261 | } | |
262 | ||
bf22b081 B |
263 | void handleViewLog() |
264 | { | |
265 | String out = "<html>"; | |
266 | out += "<head>"; | |
267 | out += "<title>Card entry log</title>"; | |
268 | out += "</head>"; | |
269 | out += "<body>"; | |
270 | ||
271 | int count = 10; | |
272 | if (server.hasArg("count")) { | |
273 | count = server.arg("count").toInt(); | |
274 | } | |
275 | out += printLog(count); | |
276 | ||
277 | out += "</body></html>"; | |
278 | server.send(200, "text/html", out); | |
279 | } | |
280 | ||
f1139a83 B |
281 | void handleDownload() |
282 | { | |
283 | if (!server.authenticate(www_username, www_password)) | |
284 | return server.requestAuthentication(); | |
285 | ||
286 | if (!SPIFFS.exists(CARD_FILE)) { | |
287 | server.send(404, "text/plain", "Card file not found"); | |
288 | return; | |
289 | } | |
290 | ||
291 | File f = SPIFFS.open(CARD_FILE, "r"); | |
292 | server.streamFile(f, "text/csv"); | |
293 | f.close(); | |
294 | } | |
295 | ||
0d5d6475 B |
296 | void handleWipelog() |
297 | { | |
298 | if (!server.authenticate(www_username, www_password)) | |
299 | return server.requestAuthentication(); | |
300 | ||
301 | SPIFFS.remove(LOG_FILE); | |
302 | server.send(200, "text/plain", "logfile deleted"); | |
303 | } | |
304 | ||
bf22b081 B |
305 | // need to do this chunked. |
306 | // https://github.com/luc-github/ESP3D/blob/master/esp3d/webinterface.cpp#L214-L394 | |
0d5d6475 B |
307 | void handleDownloadLogfile() |
308 | { | |
309 | if (!server.authenticate(www_username, www_password)) | |
310 | return server.requestAuthentication(); | |
311 | ||
bf22b081 B |
312 | File f = SPIFFS.open(LOG_FILE, "r"); |
313 | if (!f) { | |
314 | server.send(404, "text/plain", "logfile not found"); | |
315 | return; | |
316 | } | |
317 | ||
318 | server.setContentLength(CONTENT_LENGTH_UNKNOWN); | |
319 | server.sendHeader("Content-Type", "text/csv", true); | |
320 | server.sendHeader("Cache-Control", "no-cache"); | |
321 | server.send(200); | |
322 | ||
323 | unsigned char entry[8]; | |
324 | uint32_t * data = (uint32_t *)entry; | |
325 | ||
326 | while (f.available()) { | |
327 | String out; | |
328 | f.read(entry, 8); | |
329 | out += getDate( data[0] ); | |
330 | out += " "; | |
331 | out += getTime( data[0] ); | |
332 | out += "," + String(data[1]) + ","; | |
333 | ||
334 | if (data[1] == 0) { | |
335 | out += "Emergency Release"; | |
336 | } else { | |
337 | String whom = findKeyfob(data[1]); | |
338 | if (whom == "") { | |
339 | out += "Unknown keyfob"; | |
340 | } else { | |
341 | out += whom; | |
342 | } | |
343 | } | |
344 | out += "\n"; | |
345 | server.sendContent(out); | |
346 | } | |
347 | f.close(); | |
348 | server.sendContent(""); | |
0d5d6475 B |
349 | } |
350 | ||
f1139a83 B |
351 | void handleNotFound() { |
352 | String out = "File Not found\n\n"; | |
353 | server.send(404, "text/plain", out); | |
354 | } | |
355 | ||
356 | // User wants to reset config | |
357 | void handleReset() { | |
358 | if (!server.authenticate(www_username, www_password)) | |
359 | return server.requestAuthentication(); | |
360 | ||
361 | server.send(200, "text/plain", "Rebooting to config manager...\n\n"); | |
362 | ||
363 | WiFiManager wfm; | |
364 | wfm.resetSettings(); | |
365 | WiFi.disconnect(); | |
366 | ESP.reset(); | |
367 | delay(5000); | |
368 | } | |
369 | ||
370 | void handleUploadRequest() { | |
371 | String out = "<html><head><title>Upload Keyfob list</title></head><body>\ | |
372 | <form enctype=\"multipart/form-data\" action=\"/upload\" method=\"POST\">\ | |
373 | <input type=\"hidden\" name=\"MAX_FILE_SIZE\" value=\"32000\" />\ | |
374 | Select file to upload: <input name=\"file\" type=\"file\" />\ | |
375 | <input type=\"submit\" value=\"Upload file\" />\ | |
376 | </form></body></html>"; | |
377 | server.send(200, "text/html", out); | |
378 | } | |
379 | ||
380 | File uploadFile; | |
381 | ||
382 | String upload_error; | |
383 | int upload_code = 200; | |
384 | ||
385 | void handleFileUpload() | |
386 | { | |
387 | if (server.uri() != "/upload") return; | |
388 | ||
389 | if (!server.authenticate(www_username, www_password)) | |
390 | return server.requestAuthentication(); | |
391 | ||
392 | HTTPUpload& upload = server.upload(); | |
393 | if (upload.status == UPLOAD_FILE_START) { | |
394 | upload_error = ""; | |
395 | upload_code = 200; | |
396 | uploadFile = SPIFFS.open(CARD_TMPFILE, "w"); | |
397 | if (!uploadFile) { | |
398 | upload_error = "error opening file"; | |
399 | Serial.println("Opening tmpfile failed!"); | |
400 | upload_code = 403; | |
401 | } | |
402 | }else | |
403 | if (upload.status == UPLOAD_FILE_WRITE) { | |
404 | if (uploadFile) { | |
405 | if (uploadFile.write(upload.buf, upload.currentSize) != upload.currentSize) { | |
406 | upload_error = "write error"; | |
407 | upload_code = 409; | |
408 | } | |
409 | } | |
410 | }else | |
411 | if (upload.status == UPLOAD_FILE_END) { | |
412 | if (uploadFile) { | |
413 | uploadFile.close(); | |
414 | } | |
415 | } | |
416 | } | |
417 | ||
418 | void handleUploadComplete() | |
419 | { | |
420 | String out = "Upload finished."; | |
421 | if (upload_code != 200) { | |
422 | out += "Error: "+upload_error; | |
423 | } else { | |
424 | out += " Success"; | |
425 | // upload with no errors, replace old one | |
426 | SPIFFS.remove(CARD_FILE); | |
427 | SPIFFS.rename(CARD_TMPFILE, CARD_FILE); | |
428 | } | |
0d5d6475 | 429 | out += "</p><a href=\"/\">Back</a>"; |
f1139a83 B |
430 | server.send(upload_code, "text/plain", out); |
431 | } | |
432 | ||
433 | ||
434 | void returnOK() { | |
435 | server.send(200, "text/plain", ""); | |
436 | } | |
437 | ||
0d5d6475 B |
438 | String getTime(time_t when) |
439 | { | |
440 | String ans; | |
441 | int h = hour(when); | |
442 | int m = minute(when); | |
443 | int s = second(when); | |
444 | ||
445 | if (h<10) ans += "0"; | |
446 | ans += String(h) + ":"; | |
447 | if (m<10) ans += "0"; | |
448 | ans += String(m) + ":"; | |
449 | if (s<10) ans += "0"; | |
450 | ans += String(s); | |
451 | ||
452 | return ans; | |
f1139a83 B |
453 | } |
454 | ||
0d5d6475 | 455 | String getDate(time_t when) |
f1139a83 | 456 | { |
0d5d6475 B |
457 | String ans; |
458 | ||
459 | ans += String(year(when)) + "-" + String(month(when)) + "-" + String(day(when)); | |
460 | return ans; | |
f1139a83 B |
461 | } |
462 | ||
0d5d6475 | 463 | |
f1139a83 B |
464 | int sanityCheck(const char * filename) |
465 | { | |
466 | int count = 0; | |
467 | ||
468 | File f = SPIFFS.open(filename, "r"); | |
469 | if (!f) { | |
470 | Serial.print("Sanity Check: Could not open "); | |
471 | Serial.println(filename); | |
472 | return -1; | |
473 | } | |
474 | while (f.available()) { | |
475 | char c = f.peek(); | |
476 | // skip comment lines | |
477 | if (c == '#') { | |
478 | f.find("\n"); | |
479 | continue; | |
480 | } | |
481 | ||
482 | String wcode = f.readStringUntil(','); | |
483 | String wname = f.readStringUntil('\n'); | |
484 | unsigned int newcode = wcode.toInt(); | |
485 | ||
486 | if (newcode != 0) count++; | |
487 | } | |
488 | f.close(); | |
489 | ||
490 | return count; | |
491 | } | |
492 | ||
493 | String findKeyfob(unsigned int code) | |
494 | { | |
495 | File f = SPIFFS.open(CARD_FILE, "r"); | |
496 | if (!f) { | |
497 | Serial.println("Error opening card file " CARD_FILE); | |
498 | return ""; | |
499 | } | |
500 | ||
501 | String answer = ""; | |
502 | while (f.available()) { | |
503 | char c = f.peek(); | |
504 | // skip comment lines | |
505 | if (c == '#') { | |
506 | f.find("\n"); | |
507 | continue; | |
508 | } | |
509 | ||
510 | String wcode = f.readStringUntil(','); | |
511 | String wname = f.readStringUntil('\n'); | |
512 | ||
513 | unsigned int newcode = wcode.toInt(); | |
514 | ||
515 | /* debug | |
516 | Serial.print("Line: code='"); | |
517 | Serial.print(wcode); | |
518 | Serial.print("' ("); | |
519 | Serial.print(newcode); | |
520 | Serial.print(") name='"); | |
521 | Serial.print(wname); | |
522 | Serial.print("'"); | |
523 | */ | |
524 | if (code == newcode) { | |
525 | // Serial.println(" - FOUND IT"); | |
526 | answer = wname; | |
527 | break; | |
528 | } | |
529 | //Serial.println(); | |
530 | } | |
531 | f.close(); | |
532 | return answer; | |
533 | } | |
534 | ||
bb055419 | 535 | // add an entry to the log |
0d5d6475 B |
536 | void logEntry(time_t when, uint32_t card) |
537 | { | |
538 | unsigned char entry[8]; | |
539 | ||
540 | File f = SPIFFS.open(LOG_FILE, "a"); | |
541 | if (!f) { | |
542 | Serial.println("Error opening log file"); | |
543 | return; | |
544 | } | |
545 | ||
546 | // compose the record to write | |
547 | ((uint32_t *)entry)[0] = when; | |
548 | ((uint32_t *)entry)[1] = card; | |
549 | f.write(entry, 8); | |
550 | f.close(); | |
551 | } | |
552 | ||
bb055419 | 553 | // produce a copy of the log file |
bf22b081 | 554 | String printLog(int last) |
0d5d6475 B |
555 | { |
556 | String out; | |
557 | File f = SPIFFS.open(LOG_FILE, "r"); | |
558 | if (!f) return String("Could not open log file"); | |
559 | ||
560 | unsigned char entry[8]; | |
561 | uint32_t * data = (uint32_t *)entry; | |
562 | ||
563 | if (last != 0) { | |
564 | // print only the last N items | |
565 | int pos = f.size() / 8; | |
566 | if (pos > last) pos -= last; else pos = 0; | |
567 | f.seek( pos * 8, SeekSet); | |
bf22b081 | 568 | out += "Last " + String(last) + " log entries :-"; |
0d5d6475 | 569 | } |
bf22b081 | 570 | out += "<ul>"; |
0d5d6475 B |
571 | |
572 | while (f.available()) { | |
573 | f.read(entry, 8); | |
bf22b081 | 574 | out += "<li> "; |
0d5d6475 B |
575 | out += getDate( data[0] ); |
576 | out += " "; | |
577 | out += getTime( data[0] ); | |
bf22b081 | 578 | out += " - "; |
0d5d6475 B |
579 | |
580 | if (data[1] == 0) { | |
bf22b081 | 581 | out += "<i>"; |
0d5d6475 | 582 | out += "Emergency Release"; |
bf22b081 | 583 | out += "</i>"; |
0d5d6475 B |
584 | } else { |
585 | String whom = findKeyfob(data[1]); | |
586 | if (whom == "") { | |
bf22b081 | 587 | out += "<i>by "; |
0d5d6475 | 588 | out += "Unknown keyfob"; |
bf22b081 | 589 | out += "</i>"; |
0d5d6475 B |
590 | } else { |
591 | out += whom; | |
592 | } | |
bf22b081 | 593 | out += " (" + String(data[1]) + ")"; |
0d5d6475 B |
594 | } |
595 | out += "\n"; | |
596 | } | |
597 | f.close(); | |
bf22b081 | 598 | out += "</ul>"; |
0d5d6475 B |
599 | return out; |
600 | } | |
601 | ||
602 | ||
603 | static InputDebounce release_button; | |
604 | ||
605 | /******************************************** | |
606 | * Main setup routine | |
607 | */ | |
608 | void setup() { | |
609 | // some serial, for debug | |
610 | Serial.begin(115200); | |
611 | ||
bb055419 | 612 | // The lock mechanism |
0d5d6475 B |
613 | pinMode(MOSFET, OUTPUT); |
614 | digitalWrite(MOSFET, LOCK_CLOSE); | |
615 | ||
616 | // lock sense microswitch | |
617 | pinMode(SENSE, INPUT_PULLUP); | |
618 | ||
619 | // emergency door release switch | |
620 | pinMode(ERELEASE, INPUT); | |
621 | ||
622 | // indicators on the keyfob reader | |
623 | pinMode(BUZZER, OUTPUT); | |
624 | pinMode(DOORLED, OUTPUT); | |
625 | digitalWrite(BUZZER, BUZZ_OFF); | |
626 | digitalWrite(DOORLED, DLED_RED); | |
f1139a83 | 627 | |
0d5d6475 B |
628 | Serial.println("DoorLock. Testing WiFi config..."); |
629 | ||
630 | // if we have no config, enter config mode | |
631 | WiFiManager wfm; | |
bb055419 B |
632 | // Only wait in config mode for 3 minutes max |
633 | wfm.setConfigPortalTimeout(180); | |
634 | // Try to connect to the old Ap for this long | |
635 | wfm.setConnectTimeout(60); | |
636 | // okay, lets try and connect... | |
0d5d6475 B |
637 | wfm.autoConnect(MANAGER_AP); |
638 | ||
b091c740 | 639 | Serial.println("Configuring OTA update"); |
bf22b081 B |
640 | ArduinoOTA.setPassword(www_password); |
641 | ArduinoOTA.onStart([]() { | |
642 | Serial.println("Start"); | |
643 | }); | |
644 | ArduinoOTA.onEnd([]() { | |
645 | Serial.println("\nEnd"); | |
646 | }); | |
647 | ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) { | |
648 | Serial.printf("Progress: %u%%\r", (progress / (total / 100))); | |
649 | }); | |
650 | ArduinoOTA.onError([](ota_error_t error) { | |
651 | Serial.printf("Error[%u]: ", error); | |
652 | if (error == OTA_AUTH_ERROR) Serial.println("Auth Failed"); | |
653 | else if (error == OTA_BEGIN_ERROR) Serial.println("Begin Failed"); | |
654 | else if (error == OTA_CONNECT_ERROR) Serial.println("Connect Failed"); | |
655 | else if (error == OTA_RECEIVE_ERROR) Serial.println("Receive Failed"); | |
656 | else if (error == OTA_END_ERROR) Serial.println("End Failed"); | |
657 | }); | |
b091c740 B |
658 | //ArduinoOTA.begin(); |
659 | ||
660 | ||
bb055419 | 661 | Serial.println("Entering normal doorlock mode."); |
bf22b081 | 662 | |
0d5d6475 B |
663 | // we have config, enable web server |
664 | server.on( "/", handleRoot ); | |
665 | server.on( "/reset", handleReset ); | |
666 | server.on( "/download", handleDownload ); | |
667 | server.on( "/wipelog", handleWipelog ); | |
bf22b081 | 668 | server.on( "/viewlog", handleViewLog ); |
b091c740 | 669 | server.on( "/enable_ota", enable_ota ); |
0d5d6475 B |
670 | server.on( "/download_logfile", handleDownloadLogfile ); |
671 | server.onFileUpload( handleFileUpload); | |
672 | server.on( "/upload", HTTP_GET, handleUploadRequest); | |
673 | server.on( "/upload", HTTP_POST, handleUploadComplete); | |
674 | server.onNotFound( handleNotFound ); | |
675 | server.begin(); | |
676 | ||
677 | // advertise we exist via MDNS | |
678 | if (!MDNS.begin("doorlock")) { | |
679 | Serial.println("Error setting up MDNS responder."); | |
680 | } else { | |
681 | MDNS.addService("http", "tcp", 80); | |
682 | } | |
683 | ||
684 | // enable internal flash filesystem | |
685 | SPIFFS.begin(); | |
686 | ||
687 | // init wiegand keyfob reader | |
bb055419 | 688 | Serial.println("Configuring Wiegand keyfob reader"); |
0d5d6475 B |
689 | wg.begin(WD0, WD0, WD1, WD1); |
690 | ||
691 | // setup button debounce for the release switch | |
692 | release_button.setup(ERELEASE, 20, InputDebounce::PIM_EXT_PULL_DOWN_RES); | |
693 | ||
bb055419 | 694 | Serial.println("Requesting time from network"); |
0d5d6475 B |
695 | // listener port for replies from NTP |
696 | udp.begin(localPort); | |
697 | setSyncProvider(ntp_fetch); | |
bb055419 | 698 | |
b091c740 | 699 | Serial.println("Hackspace doorlock v1.2 READY"); |
0d5d6475 B |
700 | } |
701 | ||
702 | unsigned long locktime = 0; | |
703 | ||
704 | ||
705 | void unlock_door() | |
706 | { | |
707 | digitalWrite(DOORLED, DLED_GREEN); | |
708 | digitalWrite(MOSFET, LOCK_OPEN); | |
709 | if (locktime == 0) { | |
710 | digitalWrite(BUZZER, BUZZ_ON); | |
711 | delay(100); | |
712 | digitalWrite(BUZZER, BUZZ_OFF); | |
713 | delay(50); | |
714 | digitalWrite(BUZZER, BUZZ_ON); | |
715 | delay(100); | |
716 | digitalWrite(BUZZER, BUZZ_OFF); | |
717 | } | |
718 | locktime = millis(); | |
719 | } | |
720 | ||
721 | ||
722 | void loop() { | |
f1139a83 B |
723 | // is the latch held open ? |
724 | if (locktime != 0) { | |
0d5d6475 | 725 | if (locktime + LATCH_HOLD < millis()) { |
f1139a83 B |
726 | locktime = 0; |
727 | digitalWrite(MOSFET, LOCK_CLOSE); | |
728 | digitalWrite(DOORLED, DLED_RED); | |
729 | } | |
730 | } | |
731 | // handle web requests | |
732 | server.handleClient(); | |
b091c740 | 733 | if (ota_enabled) ArduinoOTA.handle(); |
f1139a83 | 734 | |
0d5d6475 | 735 | unsigned int ertime = release_button.process(millis()); |
f1139a83 B |
736 | unsigned int count = release_button.getStateOnCount(); |
737 | static unsigned last_count = 0; | |
738 | if (ertime > 0) { | |
739 | if (count != last_count) { | |
740 | last_count = count; | |
741 | Serial.println("Door Release button triggered."); | |
742 | unlock_door(); | |
0d5d6475 | 743 | logEntry(now(), 0); |
f1139a83 B |
744 | } else { |
745 | // buttons is still pressed, do nothing | |
746 | } | |
747 | } | |
748 | ||
749 | // handle card swipes | |
750 | if (wg.available()) { | |
751 | unsigned long code = wg.getCode(); | |
752 | ||
753 | Serial.print("wiegand HEX = "); | |
754 | Serial.print(code,HEX); | |
755 | Serial.print(", DECIMAL= "); | |
756 | Serial.print(code); | |
757 | Serial.print(", TYPE W"); | |
758 | Serial.println(wg.getWiegandType()); | |
759 | ||
760 | String who = findKeyfob(code); | |
761 | if (who != NULL) { | |
762 | Serial.print("Unlocking door for "); | |
763 | Serial.println(who); | |
764 | unlock_door(); | |
0d5d6475 | 765 | logEntry(now(), code); |
f1139a83 B |
766 | } |
767 | } | |
768 | ||
bb055419 | 769 | // has ntp failed, do we need to try again? |
bf22b081 | 770 | if (ntp_lastset == 0 && ntp_lasttry + NTP_RETRY < millis()) { |
bb055419 B |
771 | Serial.println("Ask Time service to try again"); |
772 | setSyncProvider(ntp_fetch); | |
773 | } | |
f1139a83 | 774 | } |