| 1 | #include <TimeLib.h> |
| 2 | #include <Time.h> |
| 3 | #include <InputDebounce.h> |
| 4 | #include <Wiegand.h> |
| 5 | #include <ESP8266WiFi.h> |
| 6 | #include <WiFiUdp.h> |
| 7 | #include <DNSServer.h> |
| 8 | #include <ESP8266WebServer.h> |
| 9 | #include <ArduinoOTA.h> |
| 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 |
| 52 | // should contain #def's for www_username and www_password |
| 53 | #include "config.h" |
| 54 | |
| 55 | // files to store card/fob data in |
| 56 | #define CARD_TMPFILE "/cards.tmp" |
| 57 | #define CARD_FILE "/cards.dat" |
| 58 | #define LOG_FILE "/log.dat" |
| 59 | |
| 60 | // how long to hold the latch open in millis |
| 61 | #define LATCH_HOLD 5000 |
| 62 | |
| 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 | |
| 69 | // NTP retry time (mS) |
| 70 | #define NTP_RETRY 30000 |
| 71 | |
| 72 | /*************************** |
| 73 | * code below |
| 74 | */ |
| 75 | WIEGAND wg; |
| 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 | |
| 88 | unsigned long ntp_lastset = 0; |
| 89 | unsigned long ntp_lasttry = 0; |
| 90 | |
| 91 | bool ota_enabled = false; |
| 92 | |
| 93 | /* User requests to enable OTA mode */ |
| 94 | void enable_ota(void) |
| 95 | { |
| 96 | if (!ota_enabled) { |
| 97 | if (!server.authenticate(www_username, www_password)) |
| 98 | return server.requestAuthentication(); |
| 99 | |
| 100 | Serial.println("Enabling OTA Mode."); |
| 101 | ArduinoOTA.begin(); |
| 102 | ota_enabled = true; |
| 103 | } |
| 104 | handleRoot(); |
| 105 | } |
| 106 | |
| 107 | /* compose and send an NTP time request packet */ |
| 108 | void ntp_send() |
| 109 | { |
| 110 | if (ntpServerIP == INADDR_NONE) { |
| 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 | } |
| 123 | } |
| 124 | |
| 125 | ntp_lasttry = millis(); |
| 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(); |
| 153 | |
| 154 | uint32_t beginWait = millis(); |
| 155 | |
| 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 | |
| 166 | ntp_lastset = millis(); |
| 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 */ |
| 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 | |
| 188 | |
| 189 | /* HTTP page request for / */ |
| 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>\ |
| 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"; |
| 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 | |
| 244 | if (SPIFFS.exists(LOG_FILE)) { |
| 245 | out += "<li><a href=\"/wipelog\">Wipe log file</a>"; |
| 246 | out += "<li><a href=\"/viewlog?count=30\">View entry log</a>"; |
| 247 | out += "<li><a href=\"/download_logfile\">Download full logfile</a>"; |
| 248 | } |
| 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 | } |
| 255 | |
| 256 | out += "</ul>"; |
| 257 | out += "</body>\ |
| 258 | </html>"; |
| 259 | |
| 260 | server.send( 200, "text/html", out); |
| 261 | } |
| 262 | |
| 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 | |
| 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 | |
| 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 | |
| 305 | // need to do this chunked. |
| 306 | // https://github.com/luc-github/ESP3D/blob/master/esp3d/webinterface.cpp#L214-L394 |
| 307 | void handleDownloadLogfile() |
| 308 | { |
| 309 | if (!server.authenticate(www_username, www_password)) |
| 310 | return server.requestAuthentication(); |
| 311 | |
| 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(""); |
| 349 | } |
| 350 | |
| 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 | } |
| 429 | out += "</p><a href=\"/\">Back</a>"; |
| 430 | server.send(upload_code, "text/plain", out); |
| 431 | } |
| 432 | |
| 433 | |
| 434 | void returnOK() { |
| 435 | server.send(200, "text/plain", ""); |
| 436 | } |
| 437 | |
| 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; |
| 453 | } |
| 454 | |
| 455 | String getDate(time_t when) |
| 456 | { |
| 457 | String ans; |
| 458 | |
| 459 | ans += String(year(when)) + "-" + String(month(when)) + "-" + String(day(when)); |
| 460 | return ans; |
| 461 | } |
| 462 | |
| 463 | |
| 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 | |
| 535 | // add an entry to the log |
| 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 | |
| 553 | // produce a copy of the log file |
| 554 | String printLog(int last) |
| 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); |
| 568 | out += "Last " + String(last) + " log entries :-"; |
| 569 | } |
| 570 | out += "<ul>"; |
| 571 | |
| 572 | while (f.available()) { |
| 573 | f.read(entry, 8); |
| 574 | out += "<li> "; |
| 575 | out += getDate( data[0] ); |
| 576 | out += " "; |
| 577 | out += getTime( data[0] ); |
| 578 | out += " - "; |
| 579 | |
| 580 | if (data[1] == 0) { |
| 581 | out += "<i>"; |
| 582 | out += "Emergency Release"; |
| 583 | out += "</i>"; |
| 584 | } else { |
| 585 | String whom = findKeyfob(data[1]); |
| 586 | if (whom == "") { |
| 587 | out += "<i>by "; |
| 588 | out += "Unknown keyfob"; |
| 589 | out += "</i>"; |
| 590 | } else { |
| 591 | out += whom; |
| 592 | } |
| 593 | out += " (" + String(data[1]) + ")"; |
| 594 | } |
| 595 | out += "\n"; |
| 596 | } |
| 597 | f.close(); |
| 598 | out += "</ul>"; |
| 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 | |
| 612 | // The lock mechanism |
| 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); |
| 627 | |
| 628 | Serial.println("DoorLock. Testing WiFi config..."); |
| 629 | |
| 630 | // if we have no config, enter config mode |
| 631 | WiFiManager wfm; |
| 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... |
| 637 | wfm.autoConnect(MANAGER_AP); |
| 638 | |
| 639 | Serial.println("Configuring OTA update"); |
| 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 | }); |
| 658 | //ArduinoOTA.begin(); |
| 659 | |
| 660 | |
| 661 | Serial.println("Entering normal doorlock mode."); |
| 662 | |
| 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 ); |
| 668 | server.on( "/viewlog", handleViewLog ); |
| 669 | server.on( "/enable_ota", enable_ota ); |
| 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 |
| 688 | Serial.println("Configuring Wiegand keyfob reader"); |
| 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 | |
| 694 | Serial.println("Requesting time from network"); |
| 695 | // listener port for replies from NTP |
| 696 | udp.begin(localPort); |
| 697 | setSyncProvider(ntp_fetch); |
| 698 | |
| 699 | Serial.println("Hackspace doorlock v1.2 READY"); |
| 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() { |
| 723 | // is the latch held open ? |
| 724 | if (locktime != 0) { |
| 725 | if (locktime + LATCH_HOLD < millis()) { |
| 726 | locktime = 0; |
| 727 | digitalWrite(MOSFET, LOCK_CLOSE); |
| 728 | digitalWrite(DOORLED, DLED_RED); |
| 729 | } |
| 730 | } |
| 731 | // handle web requests |
| 732 | server.handleClient(); |
| 733 | if (ota_enabled) ArduinoOTA.handle(); |
| 734 | |
| 735 | unsigned int ertime = release_button.process(millis()); |
| 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(); |
| 743 | logEntry(now(), 0); |
| 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(); |
| 765 | logEntry(now(), code); |
| 766 | } |
| 767 | } |
| 768 | |
| 769 | // has ntp failed, do we need to try again? |
| 770 | if (ntp_lastset == 0 && ntp_lasttry + NTP_RETRY < millis()) { |
| 771 | Serial.println("Ask Time service to try again"); |
| 772 | setSyncProvider(ntp_fetch); |
| 773 | } |
| 774 | } |