7843d0ef125f6fc93a2bac49a26995fc4c8dcafc
[doorlock_v1.git] / door_wiegand.ino
1 #include <InputDebounce.h>
2 #include <Wiegand.h>
3 #include <ESP8266WiFi.h>
4 #include <DNSServer.h>
5 #include <ESP8266WebServer.h>
6 #include <WiFiManager.h>
7 #include <ESP8266mDNS.h>
8 #include <FS.h>
9
10 // MOSFET for door lock activation
11 // ON/HIGH == ground the output screw terminal
12 #define MOSFET 16
13
14 // Pin for LED / WS2812
15 #define STATUSLED 2
16
17 // Wiegand keyfob reader pins
18 #define WD0 12
19 #define WD1 13
20
21 // Door lock sense pin
22 #define SENSE 14
23
24 // emergency release switch
25 #define ERELEASE 15
26
27 // Buzzer/LED on keyfob control
28 #define BUZZER 4   // Gnd to beep
29 #define DOORLED 5  // low = green, otherwise red
30
31 // orientation of some signals
32 #define DLED_GREEN LOW
33 #define DLED_RED HIGH
34 #define LOCK_OPEN HIGH
35 #define LOCK_CLOSE LOW
36 #define BUZZ_ON LOW
37 #define BUZZ_OFF HIGH
38
39
40
41 /***********************
42  * Configuration parameters
43  */
44 // AP that it will apoear as for configuration
45 #define MANAGER_AP "DoorLock"
46
47 // Credentials required to reset or upload new info
48 #define www_username "admin"
49 #define www_password "wibble"
50
51 // files to store card/fob data in
52 #define CARD_TMPFILE "/cards.tmp"
53 #define CARD_FILE "/cards.dat"
54
55 // how long to hold the latch open in millis
56 #define LATCH_HOLD 5000
57
58 /***************************
59  * code below
60  */
61 WIEGAND wg;
62 ESP8266WebServer server(80);
63
64
65 int fileSize(const char *filename)
66 {
67   int ret = -1;
68   File file = SPIFFS.open(filename, "r");
69   if (file) {
70     ret = file.size();
71     file.close();
72   }
73   return ret;
74 }
75
76 void handleRoot()
77 {
78   char mtime[16];
79   int sec = millis() / 1000;
80   int mi = sec / 60;
81   int hr = mi / 60;
82   int day = hr / 24;
83   
84   snprintf(mtime, 16, "%dd %02d:%02d:%02d", day, hr % 24, mi % 60, sec % 60);
85     
86   String out = "<html>\
87   <head>\
88     <title>Door Lock</title>\
89     <style>\
90       body { background-color: #cccccc; font-family: Arial, Helvetica, Sans-Serif; }\
91     </style>\
92   </head>\
93   <body>\
94     <h1>Door Lock!</h1>\
95     <p>Uptime: " + (String)mtime + "</p>";
96
97   if (SPIFFS.exists(CARD_FILE)) {
98     
99      out += "<p>Cardfile: " + String(CARD_FILE) + " is " + fileSize(CARD_FILE) + " bytes";
100      int count = sanityCheck(CARD_FILE);
101      if (count <= 0) {
102       out += ", in an invalid file";
103      } else {
104       out += ", contains " + String(count) + " keyfob IDs";
105       out += " - <a href=\"/download\">Download</a>";
106      }
107      
108      out += ".</p>";
109   }
110
111   out += "<ul>\
112       <li><a href=\"/reset\">Reset Configuration</a>\
113       <li><a href=\"/upload\">Upload Cardlist</a>";
114
115   FSInfo fs_info;
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);
118   }
119
120   out += "<li>Lock is currently ";
121   if (digitalRead(SENSE) == HIGH) out += "LOCKED"; else out += "OPEN";
122       
123   out += "</ul>\
124   </body>\
125 </html>";
126
127   server.send( 200, "text/html", out);
128 }
129
130 void handleDownload()
131 {
132   if (!server.authenticate(www_username, www_password))
133     return server.requestAuthentication();
134
135   if (!SPIFFS.exists(CARD_FILE)) {
136     server.send(404, "text/plain", "Card file not found");
137     return;
138   }
139
140   File f = SPIFFS.open(CARD_FILE, "r");
141   server.streamFile(f, "text/csv");
142   f.close();
143 }
144
145 void handleNotFound() {
146   String out = "File Not found\n\n";
147   server.send(404, "text/plain", out);
148 }
149
150 // User wants to reset config
151 void handleReset() {
152   if (!server.authenticate(www_username, www_password))
153     return server.requestAuthentication();
154
155   server.send(200, "text/plain", "Rebooting to config manager...\n\n");
156
157   WiFiManager wfm;
158   wfm.resetSettings();
159   WiFi.disconnect();
160   ESP.reset();
161   delay(5000);
162 }
163
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);
172 }
173
174 File uploadFile;
175
176 String upload_error;
177 int upload_code = 200;
178
179 void handleFileUpload()
180 {
181   if (server.uri() != "/upload") return;
182
183   if (!server.authenticate(www_username, www_password))
184     return server.requestAuthentication();
185
186   HTTPUpload& upload = server.upload();
187   if (upload.status == UPLOAD_FILE_START) {
188     upload_error = "";
189     upload_code = 200;
190     uploadFile = SPIFFS.open(CARD_TMPFILE, "w");
191     if (!uploadFile) {
192       upload_error = "error opening file";
193       Serial.println("Opening tmpfile failed!");
194       upload_code = 403;
195     }
196   }else
197   if (upload.status == UPLOAD_FILE_WRITE) {
198     if (uploadFile) {
199       if (uploadFile.write(upload.buf, upload.currentSize) != upload.currentSize) {
200         upload_error = "write error";
201         upload_code = 409;
202       }
203     }
204   }else
205   if (upload.status == UPLOAD_FILE_END) {
206     if (uploadFile) {
207       uploadFile.close();
208     }
209   }
210 }
211
212 void handleUploadComplete()
213 {
214   String out = "Upload finished.";
215   if (upload_code != 200) {
216     out += "Error: "+upload_error;
217   } else {
218     out += " Success";
219     // upload with no errors, replace old one
220     SPIFFS.remove(CARD_FILE);
221     SPIFFS.rename(CARD_TMPFILE, CARD_FILE);
222   }
223   server.send(upload_code, "text/plain", out);
224 }
225
226
227 void returnOK() {
228   server.send(200, "text/plain", "");
229 }
230
231
232 static InputDebounce release_button;
233
234 void setup() {
235   // some serial, for debug
236   Serial.begin(115200);
237
238   // The lock mechanism, set HIGH to turn on and connect ground to output pin
239   pinMode(MOSFET, OUTPUT);
240   digitalWrite(MOSFET, LOCK_CLOSE);
241
242   // lock sense microswitch
243   pinMode(SENSE, INPUT_PULLUP);
244   
245   // emergency door release switch
246   pinMode(ERELEASE, INPUT);
247
248   // indicators on the keyfob reader
249   pinMode(BUZZER, OUTPUT);
250   pinMode(DOORLED, OUTPUT);
251   digitalWrite(BUZZER, BUZZ_OFF);
252   digitalWrite(DOORLED, DLED_RED);
253
254   Serial.println("DoorLock. Testing WiFi config...");
255
256   // if we have no config, enter config mode
257   WiFiManager wfm;
258   wfm.autoConnect(MANAGER_AP);
259
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 );
268   server.begin();
269
270   // advertise we exist via MDNS
271   if (!MDNS.begin("doorlock")) {
272     Serial.println("Error setting up MDNS responder.");
273   } else {
274     MDNS.addService("http", "tcp", 80);
275   }
276
277   // enable internal flash filesystem
278   SPIFFS.begin();
279
280   // init wiegand keyfob reader
281   Serial.println("Starting Wiegand test reader");
282   wg.begin(WD0, WD0, WD1, WD1);
283
284   // setup button debounce for the release switch
285   release_button.setup(ERELEASE, 20, InputDebounce::PIM_EXT_PULL_DOWN_RES);
286 }
287
288 unsigned long  locktime = 0;
289
290
291 void unlock_door()
292 {
293   digitalWrite(DOORLED, DLED_GREEN);
294   digitalWrite(MOSFET, LOCK_OPEN);
295   if (locktime == 0) {
296     digitalWrite(BUZZER, BUZZ_ON);
297     delay(100);
298     digitalWrite(BUZZER, BUZZ_OFF);
299     delay(50);
300     digitalWrite(BUZZER, BUZZ_ON);
301     delay(100);
302     digitalWrite(BUZZER, BUZZ_OFF);
303   }
304   locktime = millis();
305 }
306
307 int sanityCheck(const char * filename)
308 {
309   int count = 0;
310   
311   File f = SPIFFS.open(filename, "r");
312   if (!f) {
313     Serial.print("Sanity Check: Could not open ");
314     Serial.println(filename);
315     return -1;
316   }
317   while (f.available()) {
318     char c = f.peek();
319     // skip comment lines
320     if (c == '#') {
321       f.find("\n");
322       continue;
323     }
324
325     String wcode = f.readStringUntil(',');
326     String wname = f.readStringUntil('\n');
327     unsigned int newcode = wcode.toInt();
328
329     if (newcode != 0) count++;
330   }
331   f.close();
332
333   return count; 
334 }
335
336 String findKeyfob(unsigned int code)
337 {
338   File f = SPIFFS.open(CARD_FILE, "r");
339   if (!f) {
340     Serial.println("Error opening card file " CARD_FILE);
341     return "";
342   }
343
344   String answer = "";
345   while (f.available()) {
346     char c = f.peek();
347     // skip comment lines
348     if (c == '#') {
349       f.find("\n");
350       continue;
351     }
352
353     String wcode = f.readStringUntil(',');
354     String wname = f.readStringUntil('\n');
355
356     unsigned int newcode = wcode.toInt();
357
358 /* debug
359     Serial.print("Line: code='");
360     Serial.print(wcode);
361     Serial.print("' (");
362     Serial.print(newcode);
363     Serial.print(") name='");
364     Serial.print(wname);
365     Serial.print("'");
366 */
367     if (code == newcode) {
368    //   Serial.println(" - FOUND IT");
369       answer = wname;
370       break;
371     }
372     //Serial.println();
373   }
374   f.close();
375   return answer;
376 }
377
378 void loop() {
379   unsigned long now = millis();
380
381   // is the latch held open ?
382   if (locktime != 0) {
383     if (locktime + LATCH_HOLD < now) {
384       locktime = 0;
385       digitalWrite(MOSFET, LOCK_CLOSE);
386       digitalWrite(DOORLED, DLED_RED);
387     }
388   }
389   // handle web requests
390   server.handleClient();
391
392   unsigned int ertime = release_button.process(now);
393   unsigned int count = release_button.getStateOnCount();
394   static unsigned last_count = 0;
395   if (ertime > 0) {
396     if (count != last_count) {
397       last_count = count;
398       Serial.println("Door Release button triggered.");
399       unlock_door();
400     } else {
401       // buttons is still pressed, do nothing
402     }
403   }
404
405   // handle card swipes
406   if (wg.available()) {
407     unsigned long code = wg.getCode();
408     
409     Serial.print("wiegand HEX = ");
410     Serial.print(code,HEX);
411     Serial.print(", DECIMAL= ");
412     Serial.print(code);
413     Serial.print(", TYPE W");
414     Serial.println(wg.getWiegandType());
415
416     String who = findKeyfob(code);
417     if (who != NULL) {
418       Serial.print("Unlocking door for ");
419       Serial.println(who);
420       unlock_door();
421     }
422   }
423
424 }