1 #include <InputDebounce.h>
3 #include <ESP8266WiFi.h>
5 #include <ESP8266WebServer.h>
6 #include <WiFiManager.h>
7 #include <ESP8266mDNS.h>
10 // MOSFET for door lock activation
11 // ON/HIGH == ground the output screw terminal
14 // Pin for LED / WS2812
17 // Wiegand keyfob reader pins
21 // Door lock sense pin
24 // emergency release switch
27 // Buzzer/LED on keyfob control
28 #define BUZZER 4 // Gnd to beep
29 #define DOORLED 5 // low = green, otherwise red
31 // orientation of some signals
32 #define DLED_GREEN LOW
34 #define LOCK_OPEN HIGH
35 #define LOCK_CLOSE LOW
41 /***********************
42 * Configuration parameters
44 // AP that it will apoear as for configuration
45 #define MANAGER_AP "DoorLock"
47 // Credentials required to reset or upload new info
48 #define www_username "admin"
49 #define www_password "wibble"
51 // files to store card/fob data in
52 #define CARD_TMPFILE "/cards.tmp"
53 #define CARD_FILE "/cards.dat"
55 // how long to hold the latch open in millis
56 #define LATCH_HOLD 5000
58 /***************************
62 ESP8266WebServer server(80);
65 int fileSize(const char *filename)
68 File file = SPIFFS.open(filename, "r");
79 int sec = millis() / 1000;
84 snprintf(mtime, 16, "%dd %02d:%02d:%02d", day, hr % 24, mi % 60, sec % 60);
88 <title>Door Lock</title>\
90 body { background-color: #cccccc; font-family: Arial, Helvetica, Sans-Serif; }\
95 <p>Uptime: " + (String)mtime + "</p>";
97 if (SPIFFS.exists(CARD_FILE)) {
99 out += "<p>Cardfile: " + String(CARD_FILE) + " is " + fileSize(CARD_FILE) + " bytes";
100 int count = sanityCheck(CARD_FILE);
102 out += ", in an invalid file";
104 out += ", contains " + String(count) + " keyfob IDs";
105 out += " - <a href=\"/download\">Download</a>";
112 <li><a href=\"/reset\">Reset Configuration</a>\
113 <li><a href=\"/upload\">Upload Cardlist</a>";
116 if (SPIFFS.info(fs_info)) {
117 out += "<li><a href=\"/format\">Format SPIFFS</a> - Size:"+String(fs_info.totalBytes)+" Used:"+String(fs_info.usedBytes);
120 out += "<li>Lock is currently ";
121 if (digitalRead(SENSE) == HIGH) out += "LOCKED"; else out += "OPEN";
127 server.send( 200, "text/html", out);
130 void handleDownload()
132 if (!server.authenticate(www_username, www_password))
133 return server.requestAuthentication();
135 if (!SPIFFS.exists(CARD_FILE)) {
136 server.send(404, "text/plain", "Card file not found");
140 File f = SPIFFS.open(CARD_FILE, "r");
141 server.streamFile(f, "text/csv");
145 void handleNotFound() {
146 String out = "File Not found\n\n";
147 server.send(404, "text/plain", out);
150 // User wants to reset config
152 if (!server.authenticate(www_username, www_password))
153 return server.requestAuthentication();
155 server.send(200, "text/plain", "Rebooting to config manager...\n\n");
164 void handleUploadRequest() {
165 String out = "<html><head><title>Upload Keyfob list</title></head><body>\
166 <form enctype=\"multipart/form-data\" action=\"/upload\" method=\"POST\">\
167 <input type=\"hidden\" name=\"MAX_FILE_SIZE\" value=\"32000\" />\
168 Select file to upload: <input name=\"file\" type=\"file\" />\
169 <input type=\"submit\" value=\"Upload file\" />\
170 </form></body></html>";
171 server.send(200, "text/html", out);
177 int upload_code = 200;
179 void handleFileUpload()
181 if (server.uri() != "/upload") return;
183 if (!server.authenticate(www_username, www_password))
184 return server.requestAuthentication();
186 HTTPUpload& upload = server.upload();
187 if (upload.status == UPLOAD_FILE_START) {
190 uploadFile = SPIFFS.open(CARD_TMPFILE, "w");
192 upload_error = "error opening file";
193 Serial.println("Opening tmpfile failed!");
197 if (upload.status == UPLOAD_FILE_WRITE) {
199 if (uploadFile.write(upload.buf, upload.currentSize) != upload.currentSize) {
200 upload_error = "write error";
205 if (upload.status == UPLOAD_FILE_END) {
212 void handleUploadComplete()
214 String out = "Upload finished.";
215 if (upload_code != 200) {
216 out += "Error: "+upload_error;
219 // upload with no errors, replace old one
220 SPIFFS.remove(CARD_FILE);
221 SPIFFS.rename(CARD_TMPFILE, CARD_FILE);
223 server.send(upload_code, "text/plain", out);
228 server.send(200, "text/plain", "");
232 static InputDebounce release_button;
235 // some serial, for debug
236 Serial.begin(115200);
238 // The lock mechanism, set HIGH to turn on and connect ground to output pin
239 pinMode(MOSFET, OUTPUT);
240 digitalWrite(MOSFET, LOCK_CLOSE);
242 // lock sense microswitch
243 pinMode(SENSE, INPUT_PULLUP);
245 // emergency door release switch
246 pinMode(ERELEASE, INPUT);
248 // indicators on the keyfob reader
249 pinMode(BUZZER, OUTPUT);
250 pinMode(DOORLED, OUTPUT);
251 digitalWrite(BUZZER, BUZZ_OFF);
252 digitalWrite(DOORLED, DLED_RED);
254 Serial.println("DoorLock. Testing WiFi config...");
256 // if we have no config, enter config mode
258 wfm.autoConnect(MANAGER_AP);
260 // we have config, enable web server
261 server.on( "/", handleRoot );
262 server.on( "/reset", handleReset );
263 server.onFileUpload( handleFileUpload);
264 server.on( "/upload", HTTP_GET, handleUploadRequest);
265 server.on( "/upload", HTTP_POST, handleUploadComplete);
266 server.on( "/download", handleDownload );
267 server.onNotFound( handleNotFound );
270 // advertise we exist via MDNS
271 if (!MDNS.begin("doorlock")) {
272 Serial.println("Error setting up MDNS responder.");
274 MDNS.addService("http", "tcp", 80);
277 // enable internal flash filesystem
280 // init wiegand keyfob reader
281 Serial.println("Starting Wiegand test reader");
282 wg.begin(WD0, WD0, WD1, WD1);
284 // setup button debounce for the release switch
285 release_button.setup(ERELEASE, 20, InputDebounce::PIM_EXT_PULL_DOWN_RES);
288 unsigned long locktime = 0;
293 digitalWrite(DOORLED, DLED_GREEN);
294 digitalWrite(MOSFET, LOCK_OPEN);
296 digitalWrite(BUZZER, BUZZ_ON);
298 digitalWrite(BUZZER, BUZZ_OFF);
300 digitalWrite(BUZZER, BUZZ_ON);
302 digitalWrite(BUZZER, BUZZ_OFF);
307 int sanityCheck(const char * filename)
311 File f = SPIFFS.open(filename, "r");
313 Serial.print("Sanity Check: Could not open ");
314 Serial.println(filename);
317 while (f.available()) {
319 // skip comment lines
325 String wcode = f.readStringUntil(',');
326 String wname = f.readStringUntil('\n');
327 unsigned int newcode = wcode.toInt();
329 if (newcode != 0) count++;
336 String findKeyfob(unsigned int code)
338 File f = SPIFFS.open(CARD_FILE, "r");
340 Serial.println("Error opening card file " CARD_FILE);
345 while (f.available()) {
347 // skip comment lines
353 String wcode = f.readStringUntil(',');
354 String wname = f.readStringUntil('\n');
356 unsigned int newcode = wcode.toInt();
359 Serial.print("Line: code='");
362 Serial.print(newcode);
363 Serial.print(") name='");
367 if (code == newcode) {
368 // Serial.println(" - FOUND IT");
379 unsigned long now = millis();
381 // is the latch held open ?
383 if (locktime + LATCH_HOLD < now) {
385 digitalWrite(MOSFET, LOCK_CLOSE);
386 digitalWrite(DOORLED, DLED_RED);
389 // handle web requests
390 server.handleClient();
392 unsigned int ertime = release_button.process(now);
393 unsigned int count = release_button.getStateOnCount();
394 static unsigned last_count = 0;
396 if (count != last_count) {
398 Serial.println("Door Release button triggered.");
401 // buttons is still pressed, do nothing
405 // handle card swipes
406 if (wg.available()) {
407 unsigned long code = wg.getCode();
409 Serial.print("wiegand HEX = ");
410 Serial.print(code,HEX);
411 Serial.print(", DECIMAL= ");
413 Serial.print(", TYPE W");
414 Serial.println(wg.getWiegandType());
416 String who = findKeyfob(code);
418 Serial.print("Unlocking door for ");