3 #include <InputDebounce.h>
5 #include <ESP8266WiFi.h>
8 #include <ESP8266WebServer.h>
9 #include <WiFiManager.h>
10 #include <ESP8266mDNS.h>
13 // MOSFET for door lock activation
14 // ON/HIGH == ground the output screw terminal
17 // Pin for LED / WS2812
20 // Wiegand keyfob reader pins
24 // Door lock sense pin
27 // emergency release switch
30 // Buzzer/LED on keyfob control
31 #define BUZZER 4 // Gnd to beep
32 #define DOORLED 5 // low = green, otherwise red
34 // orientation of some signals
35 #define DLED_GREEN LOW
37 #define LOCK_OPEN HIGH
38 #define LOCK_CLOSE LOW
44 /***********************
45 * Configuration parameters
47 // AP that it will apoear as for configuration
48 #define MANAGER_AP "DoorLock"
50 // Credentials required to reset or upload new info
51 #define www_username "admin"
52 #define www_password "wibble"
54 // files to store card/fob data in
55 #define CARD_TMPFILE "/cards.tmp"
56 #define CARD_FILE "/cards.dat"
57 #define LOG_FILE "/log.dat"
59 // how long to hold the latch open in millis
60 #define LATCH_HOLD 5000
62 // webserver for configuration portnumber
63 #define CONFIG_PORT 80
66 #define NTP_SERVER "1.uk.pool.ntp.org"
68 /***************************
72 ESP8266WebServer server(CONFIG_PORT);
74 const unsigned int localPort = 2390;
75 IPAddress ntpServerIP;
77 const int NTP_PACKET_SIZE = 48; // NTP time stamp is in the first 48 bytes of the message
79 byte packetBuffer[ NTP_PACKET_SIZE]; //buffer to hold incoming and outgoing packets
84 /* compose and send an NTP time request packet */
87 if (ntpServerIP == INADDR_NONE) {
88 WiFi.hostByName(NTP_SERVER, ntpServerIP);
89 Serial.print("Got NTP server " NTP_SERVER " address ");
90 Serial.println(ntpServerIP);
94 memset(packetBuffer, 0, NTP_PACKET_SIZE);
95 // Initialize values needed to form NTP request
96 // (see URL above for details on the packets)
97 packetBuffer[0] = 0b11100011; // LI, Version, Mode
98 packetBuffer[1] = 0; // Stratum, or type of clock
99 packetBuffer[2] = 6; // Polling Interval
100 packetBuffer[3] = 0xEC; // Peer Clock Precision
101 // 8 bytes of zero for Root Delay & Root Dispersion
102 packetBuffer[12] = 49;
103 packetBuffer[13] = 0x4E;
104 packetBuffer[14] = 49;
105 packetBuffer[15] = 52;
107 // all NTP fields have been given values, now
108 // you can send a packet requesting a timestamp:
109 udp.beginPacket(ntpServerIP, 123); //NTP requests are to port 123
110 udp.write(packetBuffer, NTP_PACKET_SIZE);
113 Serial.println("Sending NTP request");
116 /* request a time update from NTP and parse the result */
119 while (udp.parsePacket() > 0); // discard old udp packets
122 uint32_t beginWait = millis();
124 while (millis() - beginWait < 2500) {
125 int size = udp.parsePacket();
126 if (size >= NTP_PACKET_SIZE) {
127 udp.read(packetBuffer, NTP_PACKET_SIZE);
129 // this is NTP time (seconds since Jan 1 1900):
130 unsigned long secsSince1900 = packetBuffer[40] << 24 | packetBuffer[41] << 16 | packetBuffer[42] << 8 | packetBuffer[43];
131 const unsigned long seventyYears = 2208988800UL;
132 time_t unixtime = secsSince1900 - seventyYears;
134 Serial.print("NTP update unixtime=");
135 Serial.println(unixtime);
139 Serial.println("No NTP response");
143 /* how big is a file */
144 int fileSize(const char *filename)
147 File file = SPIFFS.open(filename, "r");
156 /* HTTP page request for / */
160 int sec = millis() / 1000;
165 snprintf(mtime, 16, "%dd %02d:%02d:%02d", day, hr % 24, mi % 60, sec % 60);
167 String out = "<html>\
169 <title>Door Lock</title>\
171 body { background-color: #cccccc; font-family: Arial, Helvetica, Sans-Serif; }\
176 <p>Uptime: " + (String)mtime + "</p>\n";
178 if (timeStatus() == timeSet) {
180 out += "<p>Time now: " + getDate(when) + " " + getTime(when) + "</p>\n";
185 if (SPIFFS.info(fs_info)) {
186 out += "<p>Onboard Flash disk: - Size:"+String(fs_info.totalBytes)+" Used:"+String(fs_info.usedBytes)+"</p>\n";
189 out += "<p>Lock is currently ";
190 if (digitalRead(SENSE) == HIGH) out += "LOCKED"; else out += "OPEN";
193 if (SPIFFS.exists(CARD_FILE)) {
195 out += "<p>Cardfile: " + String(CARD_FILE) + " is " + fileSize(CARD_FILE) + " bytes";
196 int count = sanityCheck(CARD_FILE);
198 out += ", in an invalid file";
200 out += ", contains " + String(count) + " keyfob IDs";
201 out += " - <a href=\"/download\">Download</a>";
208 <li><a href=\"/reset\">Reset Configuration</a>\
209 <li><a href=\"/upload\">Upload Cardlist</a>";
212 if (SPIFFS.exists(LOG_FILE)) {
213 out += "<li><a href=\"/wipelog\">Wipe log file</a>";
214 out += "<li><a href=\"/download_logfile\">Download full logfile</a>";
220 if (SPIFFS.exists(LOG_FILE)) out += printLog(true, 10);
225 server.send( 200, "text/html", out);
228 void handleDownload()
230 if (!server.authenticate(www_username, www_password))
231 return server.requestAuthentication();
233 if (!SPIFFS.exists(CARD_FILE)) {
234 server.send(404, "text/plain", "Card file not found");
238 File f = SPIFFS.open(CARD_FILE, "r");
239 server.streamFile(f, "text/csv");
245 if (!server.authenticate(www_username, www_password))
246 return server.requestAuthentication();
248 SPIFFS.remove(LOG_FILE);
249 server.send(200, "text/plain", "logfile deleted");
252 void handleDownloadLogfile()
254 if (!server.authenticate(www_username, www_password))
255 return server.requestAuthentication();
257 String result = printLog(false, 0);
258 server.send(200, "text/csv", result);
261 void handleNotFound() {
262 String out = "File Not found\n\n";
263 server.send(404, "text/plain", out);
266 // User wants to reset config
268 if (!server.authenticate(www_username, www_password))
269 return server.requestAuthentication();
271 server.send(200, "text/plain", "Rebooting to config manager...\n\n");
280 void handleUploadRequest() {
281 String out = "<html><head><title>Upload Keyfob list</title></head><body>\
282 <form enctype=\"multipart/form-data\" action=\"/upload\" method=\"POST\">\
283 <input type=\"hidden\" name=\"MAX_FILE_SIZE\" value=\"32000\" />\
284 Select file to upload: <input name=\"file\" type=\"file\" />\
285 <input type=\"submit\" value=\"Upload file\" />\
286 </form></body></html>";
287 server.send(200, "text/html", out);
293 int upload_code = 200;
295 void handleFileUpload()
297 if (server.uri() != "/upload") return;
299 if (!server.authenticate(www_username, www_password))
300 return server.requestAuthentication();
302 HTTPUpload& upload = server.upload();
303 if (upload.status == UPLOAD_FILE_START) {
306 uploadFile = SPIFFS.open(CARD_TMPFILE, "w");
308 upload_error = "error opening file";
309 Serial.println("Opening tmpfile failed!");
313 if (upload.status == UPLOAD_FILE_WRITE) {
315 if (uploadFile.write(upload.buf, upload.currentSize) != upload.currentSize) {
316 upload_error = "write error";
321 if (upload.status == UPLOAD_FILE_END) {
328 void handleUploadComplete()
330 String out = "Upload finished.";
331 if (upload_code != 200) {
332 out += "Error: "+upload_error;
335 // upload with no errors, replace old one
336 SPIFFS.remove(CARD_FILE);
337 SPIFFS.rename(CARD_TMPFILE, CARD_FILE);
339 out += "</p><a href=\"/\">Back</a>";
340 server.send(upload_code, "text/plain", out);
345 server.send(200, "text/plain", "");
348 String getTime(time_t when)
352 int m = minute(when);
353 int s = second(when);
355 if (h<10) ans += "0";
356 ans += String(h) + ":";
357 if (m<10) ans += "0";
358 ans += String(m) + ":";
359 if (s<10) ans += "0";
365 String getDate(time_t when)
369 ans += String(year(when)) + "-" + String(month(when)) + "-" + String(day(when));
374 int sanityCheck(const char * filename)
378 File f = SPIFFS.open(filename, "r");
380 Serial.print("Sanity Check: Could not open ");
381 Serial.println(filename);
384 while (f.available()) {
386 // skip comment lines
392 String wcode = f.readStringUntil(',');
393 String wname = f.readStringUntil('\n');
394 unsigned int newcode = wcode.toInt();
396 if (newcode != 0) count++;
403 String findKeyfob(unsigned int code)
405 File f = SPIFFS.open(CARD_FILE, "r");
407 Serial.println("Error opening card file " CARD_FILE);
412 while (f.available()) {
414 // skip comment lines
420 String wcode = f.readStringUntil(',');
421 String wname = f.readStringUntil('\n');
423 unsigned int newcode = wcode.toInt();
426 Serial.print("Line: code='");
429 Serial.print(newcode);
430 Serial.print(") name='");
434 if (code == newcode) {
435 // Serial.println(" - FOUND IT");
445 void logEntry(time_t when, uint32_t card)
447 unsigned char entry[8];
449 File f = SPIFFS.open(LOG_FILE, "a");
451 Serial.println("Error opening log file");
455 // compose the record to write
456 ((uint32_t *)entry)[0] = when;
457 ((uint32_t *)entry)[1] = card;
462 String printLog(int html, int last)
465 File f = SPIFFS.open(LOG_FILE, "r");
466 if (!f) return String("Could not open log file");
468 unsigned char entry[8];
469 uint32_t * data = (uint32_t *)entry;
472 // print only the last N items
473 int pos = f.size() / 8;
474 if (pos > last) pos -= last; else pos = 0;
475 f.seek( pos * 8, SeekSet);
476 if (html) out += "Last " + String(last) + " log entries :-";
478 if (html) out += "<ul>";
480 while (f.available()) {
482 if (html) out += "<li> ";
483 out += getDate( data[0] );
485 out += getTime( data[0] );
486 if (html) out += " - "; else out += "," + String(data[1]) + ",";
489 if (html) out += "<i>";
490 out += "Emergency Release";
491 if (html) out += "</i>";
493 String whom = findKeyfob(data[1]);
495 if (html) out += "<i>by ";
496 out += "Unknown keyfob";
497 if (html) out += "</i>";
501 if (html) out += " (" + String(data[1]) + ")";
506 if (html) out += "</ul>";
511 static InputDebounce release_button;
513 /********************************************
517 // some serial, for debug
518 Serial.begin(115200);
520 // The lock mechanism, set HIGH to turn on and connect ground to output pin
521 pinMode(MOSFET, OUTPUT);
522 digitalWrite(MOSFET, LOCK_CLOSE);
524 // lock sense microswitch
525 pinMode(SENSE, INPUT_PULLUP);
527 // emergency door release switch
528 pinMode(ERELEASE, INPUT);
530 // indicators on the keyfob reader
531 pinMode(BUZZER, OUTPUT);
532 pinMode(DOORLED, OUTPUT);
533 digitalWrite(BUZZER, BUZZ_OFF);
534 digitalWrite(DOORLED, DLED_RED);
536 Serial.println("DoorLock. Testing WiFi config...");
538 // if we have no config, enter config mode
540 wfm.autoConnect(MANAGER_AP);
542 // we have config, enable web server
543 server.on( "/", handleRoot );
544 server.on( "/reset", handleReset );
545 server.on( "/download", handleDownload );
546 server.on( "/wipelog", handleWipelog );
547 server.on( "/download_logfile", handleDownloadLogfile );
548 server.onFileUpload( handleFileUpload);
549 server.on( "/upload", HTTP_GET, handleUploadRequest);
550 server.on( "/upload", HTTP_POST, handleUploadComplete);
551 server.onNotFound( handleNotFound );
554 // advertise we exist via MDNS
555 if (!MDNS.begin("doorlock")) {
556 Serial.println("Error setting up MDNS responder.");
558 MDNS.addService("http", "tcp", 80);
561 // enable internal flash filesystem
564 // init wiegand keyfob reader
565 Serial.println("Starting Wiegand test reader");
566 wg.begin(WD0, WD0, WD1, WD1);
568 // setup button debounce for the release switch
569 release_button.setup(ERELEASE, 20, InputDebounce::PIM_EXT_PULL_DOWN_RES);
571 // listener port for replies from NTP
572 udp.begin(localPort);
573 setSyncProvider(ntp_fetch);
576 unsigned long locktime = 0;
581 digitalWrite(DOORLED, DLED_GREEN);
582 digitalWrite(MOSFET, LOCK_OPEN);
584 digitalWrite(BUZZER, BUZZ_ON);
586 digitalWrite(BUZZER, BUZZ_OFF);
588 digitalWrite(BUZZER, BUZZ_ON);
590 digitalWrite(BUZZER, BUZZ_OFF);
597 // is the latch held open ?
599 if (locktime + LATCH_HOLD < millis()) {
601 digitalWrite(MOSFET, LOCK_CLOSE);
602 digitalWrite(DOORLED, DLED_RED);
605 // handle web requests
606 server.handleClient();
608 unsigned int ertime = release_button.process(millis());
609 unsigned int count = release_button.getStateOnCount();
610 static unsigned last_count = 0;
612 if (count != last_count) {
614 Serial.println("Door Release button triggered.");
618 // buttons is still pressed, do nothing
622 // handle card swipes
623 if (wg.available()) {
624 unsigned long code = wg.getCode();
626 Serial.print("wiegand HEX = ");
627 Serial.print(code,HEX);
628 Serial.print(", DECIMAL= ");
630 Serial.print(", TYPE W");
631 Serial.println(wg.getWiegandType());
633 String who = findKeyfob(code);
635 Serial.print("Unlocking door for ");
638 logEntry(now(), code);