├── Firmware └── doorsim.ino ├── Hardware ├── 3DPrints │ ├── DoorSim-HandheldBottom.3mf │ ├── DoorSim-HandheldMain.3mf │ ├── DoorSim-LongboyBottom.3mf │ ├── DoorSim-LongboyMain.3mf │ ├── DoorSim-MaxBottom.3mf │ ├── DoorSim-MaxMain.3mf │ ├── DoorSim-RegularBottom.3mf │ ├── DoorSim-RegularMain.3mf │ └── SolderHelper.3mf └── PCB │ ├── BOM_doorsim-wiegand_2024-08-01.csv │ ├── Gerber_doorsim-wiegand_PCB_weigandRev2-ESP32Internal_2024-08-01.zip │ ├── PickAndPlace_PCB_weigandRev2-ESP32Internal_2024-08-01.csv │ └── easyEDA_ProjectBackup.zip ├── LICENSE ├── README.md └── meta ├── 20240801_150329.jpg ├── DSC02535-min.JPG ├── DSC02537-min.JPG ├── DSC02540-min.JPG ├── DSC02546-min.JPG ├── DSC02551-min.JPG ├── DSC02559-min.JPG ├── DSC02562-min.JPG ├── DSC02565-min.JPG ├── DSC02566-min.JPG ├── DSC02573-min.JPG ├── DSC02576-min.JPG ├── DSC02577-min.JPG ├── DSC02589-min.JPG ├── DSC02593-min.JPG ├── DSC02604-min.JPG ├── DSC02608-min.JPG ├── firefox_SsNUgBIJY4.png ├── firefox_sqk2iyN0NH.png └── firefox_tcPkSL7II0.png /Firmware/doorsim.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "ESPAsyncWebServer.h" 7 | #include 8 | #include "AsyncJson.h" 9 | #include "ArduinoJson.h" 10 | 11 | AsyncWebServer server(80); 12 | Preferences preferences; 13 | 14 | // Set the LCD I2C address 15 | LiquidCrystal_I2C lcd(0x20, 20, 4); 16 | 17 | // general device settings 18 | bool isCapturing = true; 19 | String MODE; 20 | 21 | // card reader config and variables 22 | 23 | // max number of bits 24 | #define MAX_BITS 100 25 | // time to wait for another weigand pulse 26 | #define WEIGAND_WAIT_TIME 3000 27 | 28 | // stores all of the data bits 29 | volatile unsigned char databits[MAX_BITS]; 30 | volatile unsigned int bitCount = 0; 31 | 32 | // stores the last written card's data bits 33 | unsigned char lastWrittenDatabits[MAX_BITS]; 34 | unsigned int lastWrittenBitCount = 0; 35 | 36 | // goes low when data is currently being captured 37 | volatile unsigned char flagDone; 38 | 39 | // countdown until we assume there are no more bits 40 | volatile unsigned int weigandCounter; 41 | 42 | // Display screen timer 43 | unsigned long displayTimeout = 30000; // 30 seconds 44 | unsigned long lastCardTime = 0; 45 | bool displayingCard = false; 46 | 47 | // Wifi Settings 48 | bool APMode; 49 | 50 | // AP Settings 51 | // ssid_hidden = broadcast ssid = 0, hidden = 1 52 | // ap_passphrase = NULL for open, min 8 chars, max 63 53 | 54 | String ap_ssid; 55 | String ap_passphrase; 56 | int ap_channel; 57 | int ssid_hidden; 58 | 59 | // Speaker and LED Settings 60 | int spkOnInvalid; 61 | int spkOnValid; 62 | int ledValid; 63 | 64 | // Custom Display Message 65 | String customWelcomeMessage; 66 | String welcomeMessageSelect; 67 | 68 | // decoded facility code and card code 69 | unsigned long facilityCode = 0; 70 | unsigned long cardNumber = 0; 71 | 72 | // hex data string 73 | String hexCardData; 74 | 75 | // raw data string 76 | String rawCardData; 77 | String status; 78 | String details; 79 | 80 | 81 | // store card data for later review 82 | struct CardData { 83 | unsigned int bitCount; 84 | unsigned long facilityCode; 85 | unsigned long cardNumber; 86 | String hexCardData; 87 | String rawCardData; 88 | String status; // Add status field 89 | String details; // Add details field 90 | }; 91 | 92 | // breaking up card value into 2 chunks to create 10 char HEX value 93 | volatile unsigned long bitHolder1 = 0; 94 | volatile unsigned long bitHolder2 = 0; 95 | unsigned long cardChunk1 = 0; 96 | unsigned long cardChunk2 = 0; 97 | 98 | // Define reader input pins 99 | // card reader DATA0 100 | #define DATA0 19 101 | // card reader DATA1 102 | #define DATA1 18 103 | 104 | //define reader output pins 105 | // LED Output for a GND tie back 106 | #define LED 32 107 | // Speaker Output for a GND tie back 108 | #define SPK 33 109 | 110 | //define relay modules 111 | #define RELAY1 25 112 | #define RELAY2 26 113 | 114 | // store card data for later review 115 | struct Credential { 116 | unsigned long facilityCode; 117 | unsigned long cardNumber; 118 | char name[50]; // Use a fixed-size array for the name 119 | }; 120 | 121 | const int MAX_CREDENTIALS = 100; 122 | Credential credentials[MAX_CREDENTIALS]; 123 | int validCount = 0; 124 | 125 | // maximum number of stored cards 126 | const int MAX_CARDS = 100; 127 | CardData cardDataArray[MAX_CARDS]; 128 | int cardDataIndex = 0; 129 | 130 | const char *index_html = R"rawliteral( 131 | 132 | 133 | 134 | 135 | 136 | Card Data 137 | 201 | 202 | 203 |
204 | Last Read 205 | CTF Mode 206 | Settings 207 |
208 |
209 |
210 |

Card Data

211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 |
#Bit LengthFacility CodeCard NumberHex DataRaw Data
225 |
226 | 261 | 331 |
332 | 619 | 620 | 621 | )rawliteral"; 622 | 623 | // Interrupts for card reader 624 | void ISR_INT0() { 625 | bitCount++; 626 | flagDone = 0; 627 | 628 | if (bitCount < 23) { 629 | bitHolder1 = bitHolder1 << 1; 630 | } else { 631 | bitHolder2 = bitHolder2 << 1; 632 | } 633 | weigandCounter = WEIGAND_WAIT_TIME; 634 | } 635 | 636 | // interrupt that happens when INT1 goes low (1 bit) 637 | void ISR_INT1() { 638 | if (bitCount < MAX_BITS) { 639 | databits[bitCount] = 1; 640 | bitCount++; 641 | } 642 | flagDone = 0; 643 | 644 | if (bitCount < 23) { 645 | bitHolder1 = bitHolder1 << 1; 646 | bitHolder1 |= 1; 647 | } else { 648 | bitHolder2 = bitHolder2 << 1; 649 | bitHolder2 |= 1; 650 | } 651 | 652 | weigandCounter = WEIGAND_WAIT_TIME; 653 | } 654 | 655 | void saveSettingsToPreferences() { 656 | preferences.begin("settings", false); 657 | preferences.putString("MODE", MODE); 658 | preferences.putULong("displayTimeout", displayTimeout); 659 | preferences.putBool("APMode", APMode); 660 | preferences.putString("ap_ssid", ap_ssid); 661 | preferences.putString("ap_passphrase", ap_passphrase); 662 | preferences.putInt("ap_channel", ap_channel); 663 | preferences.putInt("ssid_hidden", ssid_hidden); 664 | preferences.putInt("spkOnInvalid", spkOnInvalid); 665 | preferences.putInt("spkOnValid", spkOnValid); 666 | preferences.putInt("ledValid", ledValid); 667 | preferences.putString("customWelcomeMessage", customWelcomeMessage); 668 | preferences.putString("welcomeMessageSelect", welcomeMessageSelect); 669 | preferences.end(); 670 | } 671 | 672 | void loadSettingsFromPreferences() { 673 | preferences.begin("settings", false); 674 | MODE = preferences.getString("MODE", "CTF"); 675 | displayTimeout = preferences.getULong("displayTimeout", 30000); 676 | APMode = preferences.getBool("APMode", true); 677 | ap_ssid = preferences.getString("ap_ssid","doorsim"); 678 | ap_passphrase = preferences.getString("ap_passphrase", ""); 679 | ap_channel = preferences.getInt("ap_channel",1); 680 | ssid_hidden = preferences.getInt("ssid_hidden",0); 681 | spkOnInvalid = preferences.getInt("spkOnInvalid",1); 682 | spkOnValid = preferences.getInt("spkOnValid",1); 683 | ledValid = preferences.getInt("ledValid",1); 684 | customWelcomeMessage = preferences.getString("customWelcomeMessage",""); 685 | welcomeMessageSelect = preferences.getString("welcomeMessageSelect","default"); 686 | preferences.end(); 687 | } 688 | 689 | void saveCredentialsToPreferences() { 690 | preferences.begin("credentials", false); 691 | preferences.putInt("validCount", validCount); 692 | for (int i = 0; i < validCount; i++) { 693 | String fcKey = "fc" + String(i); 694 | String cnKey = "cn" + String(i); 695 | String nameKey = "name" + String(i); 696 | preferences.putUInt(fcKey.c_str(), credentials[i].facilityCode); 697 | preferences.putUInt(cnKey.c_str(), credentials[i].cardNumber); 698 | preferences.putString(nameKey.c_str(), credentials[i].name); 699 | } 700 | preferences.end(); 701 | Serial.println("Credentials saved to Preferences:"); 702 | for (int i = 0; i < validCount; i++) { 703 | Serial.print("Credential "); 704 | Serial.print(i); 705 | Serial.print(": FC="); 706 | Serial.print(credentials[i].facilityCode); 707 | Serial.print(", CN="); 708 | Serial.print(credentials[i].cardNumber); 709 | Serial.print(", Name="); 710 | Serial.println(credentials[i].name); 711 | } 712 | Serial.print("Valid Count: "); 713 | Serial.println(validCount); 714 | } 715 | 716 | void loadCredentialsFromPreferences() { 717 | preferences.begin("credentials", true); 718 | validCount = preferences.getInt("validCount", 0); 719 | for (int i = 0; i < validCount; i++) { 720 | String fcKey = "fc" + String(i); 721 | String cnKey = "cn" + String(i); 722 | String nameKey = "name" + String(i); 723 | credentials[i].facilityCode = preferences.getUInt(fcKey.c_str(), 0); 724 | credentials[i].cardNumber = preferences.getUInt(cnKey.c_str(), 0); 725 | String name = preferences.getString(nameKey.c_str(), ""); 726 | strncpy(credentials[i].name, name.c_str(), sizeof(credentials[i].name) - 1); 727 | credentials[i].name[sizeof(credentials[i].name) - 1] = '\0'; 728 | } 729 | preferences.end(); 730 | Serial.println("Credentials loaded from Preferences:"); 731 | for (int i = 0; i < validCount; i++) { 732 | Serial.print("Credential "); 733 | Serial.print(i); 734 | Serial.print(": FC="); 735 | Serial.print(credentials[i].facilityCode); 736 | Serial.print(", CN="); 737 | Serial.print(credentials[i].cardNumber); 738 | Serial.print(", Name="); 739 | Serial.println(credentials[i].name); 740 | } 741 | Serial.print("Valid Count: "); 742 | Serial.println(validCount); 743 | } 744 | 745 | // Check if credential is valid 746 | const Credential *checkCredential(uint16_t fc, uint16_t cn) { 747 | for (unsigned int i = 0; i < validCount; i++) { 748 | if (credentials[i].facilityCode == fc && credentials[i].cardNumber == cn) { 749 | // Found a matching credential, return a pointer to it 750 | return &credentials[i]; 751 | } 752 | } 753 | // No matching credential found, return nullptr 754 | return nullptr; 755 | } 756 | 757 | void printCardData() { 758 | if (MODE == "CTF") { 759 | const Credential* result = checkCredential(facilityCode, cardNumber); 760 | if (result != nullptr) { 761 | // Valid credential found 762 | Serial.println("Valid credential found:"); 763 | Serial.println("FC: " + String(result->facilityCode) + ", CN: " + String(result->cardNumber) + ", Name: " + result->name); 764 | lcd.clear(); 765 | lcd.setCursor(0, 0); 766 | lcd.print("Card Read: "); 767 | lcd.setCursor(11, 0); 768 | lcd.print("VALID"); 769 | lcd.setCursor(0, 1); 770 | lcd.print("FC: " + String(result->facilityCode)); 771 | lcd.setCursor(9, 1); 772 | lcd.print("CN:" + String(result->cardNumber)); 773 | lcd.setCursor(0, 3); 774 | lcd.print("Name: " + String(result->name)); 775 | ledOnValid(); 776 | speakerOnValid(); 777 | 778 | // Update card data status and details 779 | status = "Authorized"; 780 | details = result->name; 781 | } else { 782 | // No valid credential found 783 | Serial.println("Error: No valid credential found."); 784 | lcdInvalidCredentials(); 785 | speakerOnFailure(); 786 | 787 | // Update card data status and details 788 | status = "Unauthorized"; 789 | details = "FC: " + String(facilityCode) + ", CN: " + String(cardNumber); 790 | } 791 | } else { 792 | // ranges for "valid" bitCount are a bit larger for debugging 793 | if (bitCount > 20 && bitCount < 120) { 794 | // ignore data caused by noise 795 | Serial.print("[*] Bit length: "); 796 | Serial.println(bitCount); 797 | Serial.print("[*] Facility code: "); 798 | Serial.println(facilityCode); 799 | Serial.print("[*] Card number: "); 800 | Serial.println(cardNumber); 801 | Serial.print("[*] Hex: "); 802 | Serial.println(hexCardData); 803 | Serial.print("[*] Raw: "); 804 | Serial.println(rawCardData); 805 | 806 | // LCD Printing 807 | lcd.clear(); 808 | lcd.setCursor(0, 0); 809 | lcd.print("Card Read: "); 810 | lcd.setCursor(11, 0); 811 | lcd.print(bitCount); 812 | lcd.print("bits"); 813 | lcd.setCursor(0, 1); 814 | lcd.print("FC: "); 815 | lcd.print(facilityCode); 816 | lcd.setCursor(9, 1); 817 | lcd.print(" CN: "); 818 | lcd.print(cardNumber); 819 | lcd.setCursor(0, 3); 820 | lcd.print("Hex: "); 821 | hexCardData.toUpperCase(); 822 | lcd.print(hexCardData); 823 | 824 | // Update card data status and details 825 | status = "Read"; 826 | details = "Hex: " + hexCardData; 827 | } 828 | } 829 | 830 | // Store card data 831 | if (cardDataIndex < MAX_CARDS) { 832 | cardDataArray[cardDataIndex].bitCount = bitCount; 833 | cardDataArray[cardDataIndex].facilityCode = facilityCode; 834 | cardDataArray[cardDataIndex].cardNumber = cardNumber; 835 | cardDataArray[cardDataIndex].hexCardData = hexCardData; 836 | cardDataArray[cardDataIndex].rawCardData = rawCardData; 837 | cardDataArray[cardDataIndex].status = status; 838 | cardDataArray[cardDataIndex].details = details; 839 | cardDataIndex++; 840 | } 841 | 842 | // Start the display timer 843 | lastCardTime = millis(); 844 | displayingCard = true; 845 | } 846 | 847 | // Functions to handle invalid credentials 848 | void lcdInvalidCredentials() { 849 | lcd.clear(); 850 | lcd.setCursor(0, 0); 851 | lcd.print("Card Read: "); 852 | lcd.setCursor(11, 0); 853 | lcd.print("INVALID"); 854 | lcd.setCursor(0, 2); 855 | lcd.print(" THIS INCIDENT WILL"); 856 | lcd.setCursor(0, 3); 857 | lcd.print(" BE REPORTED "); 858 | } 859 | 860 | 861 | void speakerOnFailure() { 862 | switch (spkOnInvalid) { 863 | case 0: 864 | break; 865 | 866 | case 1: 867 | // Sad Beeps 868 | digitalWrite(SPK, LOW); 869 | delay(100); 870 | digitalWrite(SPK, HIGH); 871 | delay(50); 872 | digitalWrite(SPK, LOW); 873 | delay(100); 874 | digitalWrite(SPK, HIGH); 875 | delay(50); 876 | digitalWrite(SPK, LOW); 877 | delay(100); 878 | digitalWrite(SPK, HIGH); 879 | delay(50); 880 | digitalWrite(SPK, LOW); 881 | delay(100); 882 | digitalWrite(SPK, HIGH); 883 | break; 884 | 885 | } 886 | } 887 | 888 | // Functions to handle valid credentials 889 | void speakerOnValid() { 890 | switch (spkOnValid) { 891 | case 0 : 892 | break; 893 | 894 | case 1: 895 | // Nice Beeps LED 896 | digitalWrite(SPK, LOW); 897 | delay(100); 898 | digitalWrite(SPK, HIGH); 899 | delay(50); 900 | digitalWrite(SPK, LOW); 901 | delay(100); 902 | digitalWrite(SPK, HIGH); 903 | break; 904 | 905 | case 2: 906 | // Long Beeps 907 | digitalWrite(SPK, LOW); 908 | delay(2000); 909 | digitalWrite(SPK, HIGH); 910 | break; 911 | } 912 | } 913 | 914 | void ledOnValid() { 915 | switch (ledValid) { 916 | case 0: 917 | break; 918 | 919 | case 1: 920 | // Flashing LED 921 | digitalWrite(LED, LOW); 922 | delay(250); 923 | digitalWrite(LED, HIGH); 924 | delay(100); 925 | digitalWrite(LED, LOW); 926 | delay(250); 927 | digitalWrite(LED, HIGH); 928 | break; 929 | 930 | 931 | case 2: 932 | digitalWrite(LED, LOW); 933 | delay(2000); 934 | digitalWrite(LED, HIGH); 935 | break; 936 | 937 | } 938 | } 939 | 940 | // Process hid cards 941 | unsigned long decodeHIDFacilityCode(unsigned int start, unsigned int end) { 942 | unsigned long HIDFacilityCode = 0; 943 | for (unsigned int i = start; i < end; i++) { 944 | HIDFacilityCode = (HIDFacilityCode << 1) | databits[i]; 945 | } 946 | return HIDFacilityCode; 947 | } 948 | 949 | unsigned long decodeHIDCardNumber(unsigned int start, unsigned int end) { 950 | unsigned long HIDCardNumber = 0; 951 | for (unsigned int i = start; i < end; i++) { 952 | HIDCardNumber = (HIDCardNumber << 1) | databits[i]; 953 | } 954 | return HIDCardNumber; 955 | } 956 | 957 | // Card value processing functions 958 | // Function to append the card value (bitHolder1 and bitHolder2) to the 959 | // necessary array then translate that to the two chunks for the card value that 960 | // will be output 961 | void setCardChunkBits(unsigned int cardChunk1Offset, unsigned int bitHolderOffset, unsigned int cardChunk2Offset) { 962 | for (int i = 19; i >= 0; i--) { 963 | if (i == 13 || i == cardChunk1Offset) { 964 | bitWrite(cardChunk1, i, 1); 965 | } else if (i > cardChunk1Offset) { 966 | bitWrite(cardChunk1, i, 0); 967 | } else { 968 | bitWrite(cardChunk1, i, bitRead(bitHolder1, i + bitHolderOffset)); 969 | } 970 | if (i < bitHolderOffset) { 971 | bitWrite(cardChunk2, i + cardChunk2Offset, bitRead(bitHolder1, i)); 972 | } 973 | if (i < cardChunk2Offset) { 974 | bitWrite(cardChunk2, i, bitRead(bitHolder2, i)); 975 | } 976 | } 977 | } 978 | 979 | String prefixPad(const String &in, const char c, const size_t len) { 980 | String out = in; 981 | while (out.length() < len) { 982 | out = c + out; 983 | } 984 | return out; 985 | } 986 | 987 | void processHIDCard() { 988 | // bits to be decoded differently depending on card format length 989 | // see http://www.pagemac.com/projects/rfid/hid_data_formats for more info 990 | // also specifically: www.brivo.com/app/static_data/js/calculate.js 991 | // Example of full card value 992 | // |> preamble <| |> Actual card value <| 993 | // 000000100000000001 11 111000100000100100111000 994 | // |> write to chunk1 <| |> write to chunk2 <| 995 | 996 | unsigned int cardChunk1Offset, bitHolderOffset, cardChunk2Offset; 997 | 998 | switch (bitCount) { 999 | case 26: 1000 | facilityCode = decodeHIDFacilityCode(1, 9); 1001 | cardNumber = decodeHIDCardNumber(9, 25); 1002 | cardChunk1Offset = 2; 1003 | bitHolderOffset = 20; 1004 | cardChunk2Offset = 4; 1005 | break; 1006 | 1007 | case 27: 1008 | facilityCode = decodeHIDFacilityCode(1, 13); 1009 | cardNumber = decodeHIDCardNumber(13, 27); 1010 | cardChunk1Offset = 3; 1011 | bitHolderOffset = 19; 1012 | cardChunk2Offset = 5; 1013 | break; 1014 | 1015 | case 29: 1016 | facilityCode = decodeHIDFacilityCode(1, 13); 1017 | cardNumber = decodeHIDCardNumber(13, 29); 1018 | cardChunk1Offset = 5; 1019 | bitHolderOffset = 17; 1020 | cardChunk2Offset = 7; 1021 | break; 1022 | 1023 | case 30: 1024 | facilityCode = decodeHIDFacilityCode(1, 13); 1025 | cardNumber = decodeHIDCardNumber(13, 29); 1026 | cardChunk1Offset = 6; 1027 | bitHolderOffset = 16; 1028 | cardChunk2Offset = 8; 1029 | break; 1030 | 1031 | case 31: 1032 | facilityCode = decodeHIDFacilityCode(1, 5); 1033 | cardNumber = decodeHIDCardNumber(5, 28); 1034 | cardChunk1Offset = 7; 1035 | bitHolderOffset = 15; 1036 | cardChunk2Offset = 9; 1037 | break; 1038 | 1039 | // modified to wiegand 32 bit format instead of HID 1040 | case 32: 1041 | facilityCode = decodeHIDFacilityCode(5, 16); 1042 | cardNumber = decodeHIDCardNumber(17, 32); 1043 | cardChunk1Offset = 8; 1044 | bitHolderOffset = 14; 1045 | cardChunk2Offset = 10; 1046 | break; 1047 | 1048 | case 33: 1049 | facilityCode = decodeHIDFacilityCode(1, 8); 1050 | cardNumber = decodeHIDCardNumber(8, 32); 1051 | cardChunk1Offset = 9; 1052 | bitHolderOffset = 13; 1053 | cardChunk2Offset = 11; 1054 | break; 1055 | 1056 | case 34: 1057 | facilityCode = decodeHIDFacilityCode(1, 17); 1058 | cardNumber = decodeHIDCardNumber(17, 33); 1059 | cardChunk1Offset = 10; 1060 | bitHolderOffset = 12; 1061 | cardChunk2Offset = 12; 1062 | break; 1063 | 1064 | case 35: 1065 | facilityCode = decodeHIDFacilityCode(2, 14); 1066 | cardNumber = decodeHIDCardNumber(14, 34); 1067 | cardChunk1Offset = 11; 1068 | bitHolderOffset = 11; 1069 | cardChunk2Offset = 13; 1070 | break; 1071 | 1072 | case 36: 1073 | facilityCode = decodeHIDFacilityCode(21, 33); 1074 | cardNumber = decodeHIDCardNumber(1, 17); 1075 | cardChunk1Offset = 12; 1076 | bitHolderOffset = 10; 1077 | cardChunk2Offset = 14; 1078 | break; 1079 | 1080 | default: 1081 | Serial.println("[-] Unsupported bitCount for HID card"); 1082 | return; 1083 | } 1084 | 1085 | setCardChunkBits(cardChunk1Offset, bitHolderOffset, cardChunk2Offset); 1086 | hexCardData = String(cardChunk1, HEX) + prefixPad(String(cardChunk2, HEX), '0', 6); 1087 | //hexCardData = String(cardChunk1, HEX) + String(cardChunk2, HEX); 1088 | } 1089 | 1090 | void processCardData() { 1091 | rawCardData = ""; 1092 | for (unsigned int i = 0; i < bitCount; i++) { 1093 | rawCardData += String(databits[i]); 1094 | } 1095 | 1096 | if (bitCount >= 26 && bitCount <= 96) { 1097 | processHIDCard(); 1098 | } 1099 | } 1100 | 1101 | void clearDatabits() { 1102 | for (unsigned char i = 0; i < MAX_BITS; i++) { 1103 | databits[i] = 0; 1104 | } 1105 | } 1106 | 1107 | // reset variables and prepare for the next card read 1108 | void cleanupCardData() { 1109 | rawCardData = ""; 1110 | hexCardData = ""; 1111 | bitCount = 0; 1112 | facilityCode = 0; 1113 | cardNumber = 0; 1114 | bitHolder1 = 0; 1115 | bitHolder2 = 0; 1116 | cardChunk1 = 0; 1117 | cardChunk2 = 0; 1118 | status = ""; 1119 | details = ""; 1120 | 1121 | } 1122 | 1123 | bool allBitsAreOnes() { 1124 | for (int i = 0; i < MAX_BITS; i++) { 1125 | if (databits[i] != 0xFF) { // Check if each byte is not equal to 0xFF 1126 | return false; // If any byte is not 0xFF, not all bits are ones 1127 | } 1128 | } 1129 | return true; // All bytes were 0xFF, so all bits are ones 1130 | } 1131 | 1132 | String centerText(const String &text, int width) { 1133 | int len = text.length(); 1134 | if (len >= width) { 1135 | return text; 1136 | } 1137 | int padding = (width - len) / 2; 1138 | String spaces = ""; 1139 | for (int i = 0; i < padding; i++) { 1140 | spaces += " "; 1141 | } 1142 | return spaces + text; 1143 | } 1144 | 1145 | void printWelcomeMessage() { 1146 | if (MODE == "CTF") { 1147 | if (customWelcomeMessage != NULL) { 1148 | lcd.clear(); 1149 | lcd.setCursor(0, 0); 1150 | lcd.print(centerText(String(customWelcomeMessage), 20)); 1151 | lcd.setCursor(0, 2); 1152 | lcd.print(centerText("Present Card", 20)); 1153 | } 1154 | else { 1155 | lcd.clear(); 1156 | lcd.setCursor(0, 0); 1157 | lcd.print(centerText("CTF Mode", 20)); 1158 | lcd.setCursor(0, 2); 1159 | lcd.print(centerText("Present Card", 20)); 1160 | } 1161 | } else { 1162 | lcd.clear(); 1163 | lcd.setCursor(0, 0); 1164 | lcd.print(centerText("Door Sim - Ready", 20)); 1165 | lcd.setCursor(0, 2); 1166 | lcd.print(centerText("Present Card", 20)); 1167 | } 1168 | } 1169 | 1170 | void updateDisplay() { 1171 | if (displayingCard && (millis() - lastCardTime >= displayTimeout)) { 1172 | printWelcomeMessage(); 1173 | displayingCard = false; 1174 | } 1175 | } 1176 | 1177 | void printAllCardData() { 1178 | Serial.println("Previously read card data:"); 1179 | for (int i = 0; i < cardDataIndex; i++) { 1180 | Serial.print(i + 1); 1181 | Serial.print(": Bit length: "); 1182 | Serial.print(cardDataArray[i].bitCount); 1183 | Serial.print(", Facility code: "); 1184 | Serial.print(cardDataArray[i].facilityCode); 1185 | Serial.print(", Card number: "); 1186 | Serial.print(cardDataArray[i].cardNumber); 1187 | Serial.print(", Hex: "); 1188 | Serial.print(cardDataArray[i].hexCardData); 1189 | Serial.print(", Raw: "); 1190 | Serial.println(cardDataArray[i].rawCardData); 1191 | } 1192 | } 1193 | 1194 | void setupWifi() { 1195 | WiFi.softAP(ap_ssid, ap_passphrase, ap_channel, ssid_hidden); 1196 | } 1197 | 1198 | void setup() { 1199 | Serial.begin(115200); 1200 | lcd.begin(); 1201 | 1202 | pinMode(DATA0, INPUT); 1203 | pinMode(DATA1, INPUT); 1204 | 1205 | pinMode(LED, OUTPUT); 1206 | pinMode(SPK, OUTPUT); 1207 | 1208 | pinMode(RELAY1, OUTPUT); 1209 | pinMode(RELAY2, OUTPUT); 1210 | 1211 | digitalWrite(LED, HIGH); 1212 | digitalWrite(SPK, HIGH); 1213 | 1214 | attachInterrupt(DATA0, ISR_INT0, FALLING); 1215 | attachInterrupt(DATA1, ISR_INT1, FALLING); 1216 | 1217 | weigandCounter = WEIGAND_WAIT_TIME; 1218 | for (unsigned char i = 0; i < MAX_BITS; i++) { 1219 | lastWrittenDatabits[i] = 0; 1220 | } 1221 | 1222 | loadSettingsFromPreferences(); 1223 | loadCredentialsFromPreferences(); 1224 | 1225 | setupWifi(); 1226 | 1227 | printWelcomeMessage(); 1228 | 1229 | server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) { 1230 | request->send_P(200, "text/html", index_html); 1231 | }); 1232 | 1233 | server.on("/getCards", HTTP_GET, [](AsyncWebServerRequest *request) { 1234 | DynamicJsonDocument doc(4096); 1235 | JsonArray cards = doc.to(); 1236 | for (int i = 0; i < cardDataIndex; i++) { 1237 | JsonObject card = cards.createNestedObject(); 1238 | card["bitCount"] = cardDataArray[i].bitCount; 1239 | card["facilityCode"] = cardDataArray[i].facilityCode; 1240 | card["cardNumber"] = cardDataArray[i].cardNumber; 1241 | card["hexCardData"] = cardDataArray[i].hexCardData; 1242 | card["rawCardData"] = cardDataArray[i].rawCardData; 1243 | card["status"] = cardDataArray[i].status; 1244 | card["details"] = cardDataArray[i].details; 1245 | } 1246 | String response; 1247 | serializeJson(doc, response); 1248 | request->send(200, "application/json", response); 1249 | }); 1250 | 1251 | server.on("/getUsers", HTTP_GET, [](AsyncWebServerRequest *request) { 1252 | DynamicJsonDocument doc(4096); 1253 | JsonArray users = doc.to(); 1254 | for (int i = 0; i < validCount; i++) { 1255 | JsonObject user = users.createNestedObject(); 1256 | user["facilityCode"] = credentials[i].facilityCode; 1257 | user["cardNumber"] = credentials[i].cardNumber; 1258 | user["name"] = credentials[i].name; 1259 | } 1260 | String response; 1261 | serializeJson(doc, response); 1262 | request->send(200, "application/json", response); 1263 | }); 1264 | 1265 | server.on("/getSettings", HTTP_GET, [](AsyncWebServerRequest *request) { 1266 | DynamicJsonDocument doc(2048); 1267 | doc["mode"] = MODE; 1268 | doc["displayTimeout"] = displayTimeout; 1269 | doc["apSsid"] = ap_ssid; 1270 | doc["apPassphrase"] = ap_passphrase; 1271 | doc["ssidHidden"] = ssid_hidden; 1272 | doc["apChannel"] = ap_channel; 1273 | doc["welcomeMessageSelect"] = customWelcomeMessage.length() > 0 ? "custom" : "default"; 1274 | doc["customMessage"] = customWelcomeMessage; 1275 | doc["ledValid"] = ledValid; 1276 | doc["spkOnValid"] = spkOnValid; 1277 | doc["spkOnInvalid"] = spkOnInvalid; 1278 | String response; 1279 | serializeJson(doc, response); 1280 | request->send(200, "application/json", response); 1281 | }); 1282 | 1283 | AsyncCallbackJsonWebHandler* handler = new AsyncCallbackJsonWebHandler("/saveSettings", [](AsyncWebServerRequest *request, JsonVariant &json) { 1284 | JsonObject jsonObj = json.as(); 1285 | 1286 | // Parse the JSON and update settings 1287 | MODE = jsonObj["mode"] | "CTF"; 1288 | displayTimeout = jsonObj["displayTimeout"] | 30000; 1289 | ap_ssid = jsonObj["apSsid"] | "doorsim"; 1290 | ap_passphrase = jsonObj["apPassphrase"] | ""; 1291 | ap_channel = jsonObj["apChannel"] | 1; 1292 | ssid_hidden = jsonObj["ssidHidden"] | 0; 1293 | welcomeMessageSelect = jsonObj["welcomeMessageSelect"] | "default"; 1294 | customWelcomeMessage = jsonObj["customMessage"] | ""; 1295 | spkOnInvalid = jsonObj["spkOnInvalid"] | 1; 1296 | spkOnValid = jsonObj["spkOnValid"] | 1; 1297 | ledValid = jsonObj["ledValid"] | 1; 1298 | 1299 | saveSettingsToPreferences(); 1300 | setupWifi(); 1301 | 1302 | request->send(200, "application/json", "{\"status\":\"success\"}"); 1303 | }); 1304 | server.addHandler(handler); 1305 | 1306 | server.on("/addCard", HTTP_GET, [](AsyncWebServerRequest *request) { 1307 | if (validCount < MAX_CREDENTIALS) { 1308 | if (request->hasParam("facilityCode") && request->hasParam("cardNumber") && request->hasParam("name")) { 1309 | String facilityCodeStr = request->getParam("facilityCode")->value(); 1310 | String cardNumberStr = request->getParam("cardNumber")->value(); 1311 | String name = request->getParam("name")->value(); 1312 | 1313 | credentials[validCount].facilityCode = facilityCodeStr.toInt(); 1314 | credentials[validCount].cardNumber = cardNumberStr.toInt(); 1315 | strncpy(credentials[validCount].name, name.c_str(), sizeof(credentials[validCount].name) - 1); 1316 | credentials[validCount].name[sizeof(credentials[validCount].name) - 1] = '\0'; 1317 | validCount++; 1318 | saveCredentialsToPreferences(); 1319 | request->send(200, "text/plain", "Card added successfully"); 1320 | } else { 1321 | request->send(400, "text/plain", "Missing parameters"); 1322 | } 1323 | } else { 1324 | request->send(500, "text/plain", "Max number of credentials reached"); 1325 | } 1326 | }); 1327 | 1328 | server.on("/deleteCard", HTTP_GET, [](AsyncWebServerRequest *request) { 1329 | if (request->hasParam("index")) { 1330 | int index = request->getParam("index")->value().toInt(); 1331 | if (index >= 0 && index < validCount) { 1332 | for (int i = index; i < validCount - 1; i++) { 1333 | credentials[i] = credentials[i + 1]; 1334 | } 1335 | validCount--; 1336 | saveCredentialsToPreferences(); 1337 | request->send(200, "text/plain", "Card deleted successfully"); 1338 | } else { 1339 | request->send(400, "text/plain", "Invalid index"); 1340 | } 1341 | } else { 1342 | request->send(400, "text/plain", "Missing index parameter"); 1343 | } 1344 | }); 1345 | 1346 | server.on("/exportData", HTTP_GET, [](AsyncWebServerRequest *request) { 1347 | DynamicJsonDocument doc(4096); 1348 | JsonArray users = doc.createNestedArray("users"); 1349 | for (int i = 0; i < validCount; i++) { 1350 | JsonObject user = users.createNestedObject(); 1351 | user["facilityCode"] = credentials[i].facilityCode; 1352 | user["cardNumber"] = credentials[i].cardNumber; 1353 | user["name"] = credentials[i].name; 1354 | } 1355 | JsonArray cards = doc.createNestedArray("cards"); 1356 | for (int i = 0; i < cardDataIndex; i++) { 1357 | JsonObject card = cards.createNestedObject(); 1358 | card["bitCount"] = cardDataArray[i].bitCount; 1359 | card["facilityCode"] = cardDataArray[i].facilityCode; 1360 | card["cardNumber"] = cardDataArray[i].cardNumber; 1361 | card["hexCardData"] = cardDataArray[i].hexCardData; 1362 | card["rawCardData"] = cardDataArray[i].rawCardData; 1363 | } 1364 | String response; 1365 | serializeJson(doc, response); 1366 | request->send(200, "application/json", response); 1367 | }); 1368 | 1369 | server.begin(); 1370 | 1371 | digitalWrite(RELAY1, HIGH); 1372 | 1373 | } 1374 | 1375 | void loop() { 1376 | updateDisplay(); 1377 | 1378 | if (!flagDone) { 1379 | if (--weigandCounter == 0) 1380 | flagDone = 1; 1381 | } 1382 | 1383 | if (bitCount > 0 && flagDone) { 1384 | if (!allBitsAreOnes()) { 1385 | processCardData(); 1386 | if (bitCount >= 26 && bitCount <= 36 || bitCount == 96) { 1387 | printCardData(); 1388 | printAllCardData(); 1389 | } 1390 | } 1391 | 1392 | 1393 | cleanupCardData(); 1394 | clearDatabits(); 1395 | } 1396 | } 1397 | -------------------------------------------------------------------------------- /Hardware/3DPrints/DoorSim-HandheldBottom.3mf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evildaemond/doorsim/c2154b1a724cfc75c05a98308d95a0a7203c8ad3/Hardware/3DPrints/DoorSim-HandheldBottom.3mf -------------------------------------------------------------------------------- /Hardware/3DPrints/DoorSim-HandheldMain.3mf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evildaemond/doorsim/c2154b1a724cfc75c05a98308d95a0a7203c8ad3/Hardware/3DPrints/DoorSim-HandheldMain.3mf -------------------------------------------------------------------------------- /Hardware/3DPrints/DoorSim-LongboyBottom.3mf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evildaemond/doorsim/c2154b1a724cfc75c05a98308d95a0a7203c8ad3/Hardware/3DPrints/DoorSim-LongboyBottom.3mf -------------------------------------------------------------------------------- /Hardware/3DPrints/DoorSim-LongboyMain.3mf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evildaemond/doorsim/c2154b1a724cfc75c05a98308d95a0a7203c8ad3/Hardware/3DPrints/DoorSim-LongboyMain.3mf -------------------------------------------------------------------------------- /Hardware/3DPrints/DoorSim-MaxBottom.3mf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evildaemond/doorsim/c2154b1a724cfc75c05a98308d95a0a7203c8ad3/Hardware/3DPrints/DoorSim-MaxBottom.3mf -------------------------------------------------------------------------------- /Hardware/3DPrints/DoorSim-MaxMain.3mf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evildaemond/doorsim/c2154b1a724cfc75c05a98308d95a0a7203c8ad3/Hardware/3DPrints/DoorSim-MaxMain.3mf -------------------------------------------------------------------------------- /Hardware/3DPrints/DoorSim-RegularBottom.3mf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evildaemond/doorsim/c2154b1a724cfc75c05a98308d95a0a7203c8ad3/Hardware/3DPrints/DoorSim-RegularBottom.3mf -------------------------------------------------------------------------------- /Hardware/3DPrints/DoorSim-RegularMain.3mf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evildaemond/doorsim/c2154b1a724cfc75c05a98308d95a0a7203c8ad3/Hardware/3DPrints/DoorSim-RegularMain.3mf -------------------------------------------------------------------------------- /Hardware/3DPrints/SolderHelper.3mf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evildaemond/doorsim/c2154b1a724cfc75c05a98308d95a0a7203c8ad3/Hardware/3DPrints/SolderHelper.3mf -------------------------------------------------------------------------------- /Hardware/PCB/BOM_doorsim-wiegand_2024-08-01.csv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evildaemond/doorsim/c2154b1a724cfc75c05a98308d95a0a7203c8ad3/Hardware/PCB/BOM_doorsim-wiegand_2024-08-01.csv -------------------------------------------------------------------------------- /Hardware/PCB/Gerber_doorsim-wiegand_PCB_weigandRev2-ESP32Internal_2024-08-01.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evildaemond/doorsim/c2154b1a724cfc75c05a98308d95a0a7203c8ad3/Hardware/PCB/Gerber_doorsim-wiegand_PCB_weigandRev2-ESP32Internal_2024-08-01.zip -------------------------------------------------------------------------------- /Hardware/PCB/PickAndPlace_PCB_weigandRev2-ESP32Internal_2024-08-01.csv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evildaemond/doorsim/c2154b1a724cfc75c05a98308d95a0a7203c8ad3/Hardware/PCB/PickAndPlace_PCB_weigandRev2-ESP32Internal_2024-08-01.csv -------------------------------------------------------------------------------- /Hardware/PCB/easyEDA_ProjectBackup.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evildaemond/doorsim/c2154b1a724cfc75c05a98308d95a0a7203c8ad3/Hardware/PCB/easyEDA_ProjectBackup.zip -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # doorsim 2 | 3 | The DoorSim is a Open Source PACS (Physical Access Control System) built for research, training, and CTFs on RFID and Physical Security. 4 | 5 | ![doorsim](https://github.com/evildaemond/doorsim/blob/main/meta/20240801_150329.jpg) 6 | 7 | ## Features 8 | 9 | - Custom PCB with ESP32 Base 10 | - Wiegand interface with Industry Standard Connectors 11 | - 2004a Display for Ease of Reading 12 | - Web Interface for Card/Event Reading and Settings 13 | - Inbuilt CTF Mode with event logs 14 | - USB-C PD Power Supply 15 | - Expandable GPIO 16 | - Multiple 3d Printed Desktop Reader Cases 17 | 18 | ## Bill of Materials 19 | 20 | ### PCB 21 | 22 | |Quantity|Name|Link| 23 | |---|---|---| 24 | |1|PCB|![Repo Link](Hardware/PCB)| 25 | |1|ESP32-WROOM-32D Development Board|| 26 | |1|2004A Display (Bare without i2c converter)|| 27 | |1|USB-C PD Trigger Board|| 28 | |3|5.0-2P Screw Terminal Blocks|| 29 | |1|2.54mm 1x16 male-male header pins|| 30 | 31 | ### Cases 32 | 33 | #### Handheld Version 34 | 35 | |Quantity|Name|Link| 36 | |---|---|---| 37 | |1|Case Body|| 38 | |1|Case Bottom|| 39 | |2|M3 Standard CNC Kitchen Threaded Insert|https://cnckitchen.store/products/gewindeeinsatz-threaded-insert-m3-standard-100-stk-pcs| 40 | |4|M4 Standard CNC Kitchen Threaded Insert|https://cnckitchen.store/products/gewindeeinsatz-threaded-insert-m4-standard-50-stk-pcs| 41 | |4|M3x10 Round Head Screw|| 42 | |2|M4x10 Flat Head Screw|| 43 | 44 | #### All Others 45 | 46 | |Quantity|Name|Link| 47 | |---|---|---| 48 | |1|Case Body|| 49 | |1|Case Bottom|| 50 | |2|M3 Standard CNC Kitchen Threaded Insert|https://cnckitchen.store/products/gewindeeinsatz-threaded-insert-m3-standard-100-stk-pcs| 51 | |4|M4 Standard CNC Kitchen Threaded Insert|https://cnckitchen.store/products/gewindeeinsatz-threaded-insert-m4-standard-50-stk-pcs| 52 | |2|M3 Nut|| 53 | |2|M3x12 Flat Head Screw|| 54 | |4|M3x10 Round Head Screw|| 55 | |4|M4x10 Flat Head Screw|| 56 | 57 | ### RFID Reader 58 | 59 | The design of the DoorSim at this stage means you can use any RFID Reader which supports Wiegand Output. The only difference will be the 3d printed case you house the doorsim in, only if you intend to use it as a non-handheld version. 60 | 61 | ## Build Steps 62 | 63 | ### Soldering 64 | 65 | Soldering guide assumes minimal soldering experience and only through hole construction 66 | 67 | 1. Collect all required items in Main Board Bill of Materials 68 | ![step1](meta/DSC02535-min.JPG) 69 | 1. Insert legs of the ESP32-WROOM-32D Development Board into PCB 70 | ![step2](meta/DSC02540-min.JPG) 71 | 1. Apply flux and solder legs in place, apply light pressure from the back on the development board and start at each corner, try to keep the board flat 72 | ![step3](meta/DSC02546-min.JPG) 73 | 1. Trim down the pins legs to roughly 2mm (078740157 Inches) and clean any leftover residue lightly with isopropyl alcohol and a old toothbrush 74 | ![step4](meta/DSC02559-min.JPG) 75 | 1. Fit the male-to-male header pins between the 2004a display and the PCB, with the longer side facing up 76 | ![step5](meta/DSC02565-min.JPG) 77 | ![step6](meta/DSC02566-min.JPG) 78 | 1. Applying pressure to the left and top sides of the PCB and 2004a display, apply flux and solder the 2 pins of the opposing sides on the PCB. 79 | 1. Hint: They should be alligned with the holes on the board 80 | ![step7](meta/DSC02573-min.JPG) 81 | 1. Flip over the board, applying pressure on the same sides, and do the same on the 2004a display side. 82 | ![step8](meta/DSC02576-min.JPG) 83 | 1. Solder in the rest of the pins on both the display and PCB 84 | ![step9](meta/DSC02577-min.JPG) 85 | 1. Trim the PCB Side of the pin legs to roughly 2mm (078740157 Inches) and clean any leftover residue lightly with isopropyl alcohol and a old toothbrush 86 | ![step10](meta/DSC02604-min.JPG) 87 | ![step11](meta/DSC02593-min.JPG) 88 | 1. *Optional:* In some cases, you may need to trim or elongate the wires for the USB-PD Connector, recommendation is to measure and cut, remove old wire connectors from board, and tin the ends before inserting and screwing into terminal connectors 89 | 90 | ### Case 91 | 92 | 1. Using the M3 Standard Threaded Insert, insert 4 into the 2004a Display hole, applying with a soldering iron, try to keep it straight up as you insert, applying light pressure 93 | 1. Hint: Using a set of tweesers will help here 94 | 1. Using the M4 Standard Threaded Insert, insert them into the case posts, applying with a soldering iron, try to keep it straight up as you insert, applying light pressure 95 | 96 | ### Programming 97 | 98 | 1. Install Arduino IDE 99 | 1. Install Requirements 100 | 1. ESP32 Board Manager 101 | 2. [ArdunioJson by Benoit Blanchon](https://arduinojson.org/) 102 | 3. [AsyncTCP by dvarrel](https://github.com/dvarrel/AsyncTCP) 103 | 4. [ESPAsyncWebServer by Me-No-Dev](https://github.com/me-no-dev/ESPAsyncWebServer) 104 | 5. [LiquidCrystal_I2C](https://github.com/fdebrabander/Arduino-LiquidCrystal-I2C-library) 105 | 1. Git Clone and Open File 106 | 1. Choose Board, Choose Port 107 | 1. Click Upload 108 | 1. If required, when reached connecting, Hold Boot button 109 | 1. Once Complete, unplug device 110 | 111 | ### Fixtures 112 | 113 | #### USB-PD 114 | 115 | 1. Using either hot glue or double sided tape, apply the tape to the back of the PCB for the USB-PD Adaptor, and apply it to the hole for the USB-PD Adaptor (It may require a small amount of extra force to do this) 116 | 1. In the handheld design, this can be hard due to the space, inserting from the front instead is also an option 117 | 118 | #### PCB 119 | 120 | 1. Mount the PCB to the case of your choice, using 4 x M3x10 Round Head Screws (Preferabily Hex keyed) 121 | 1. Attach the USB-PD Leads to your screw terminal, with the Red being screwed into VIN, and the Black and Red stripe to GND 122 | 1. Insert the cables for your RFID reader into the Screw Terminal Blocks, using the supplied wiring diagram for your RFID Reader, then insert it into your PCB 123 | 124 | #### Case 125 | 126 | 1. Attach your RFID Readers mounting bracket using M3x12 M3x12 Flat Head Screws (Preferabily Hex keyed) and M3 Nuts (Washers can be used, but should be fine without) 127 | 1. Attach the Case Bottom to the Case using M4x10 Flat Head Screws (Preferabily Hex keyed) which can be screwed into the heat-set threaded inserts 128 | 129 | ### Testing 130 | 131 | 1. Unscrew the case bottom, exposing the PCB 132 | 1. Plug in a USB-PD Capible connector, such as a laptop charger 133 | 1. Flick the Backlight Switch from OFF to ON (may be marked as 1 instead of OFF), you should see the displays backlight turn on 134 | 1. Using a small flathead screwdriver, rotate the Contrast Potentiomiter to the right until the display reads well for you 135 | 1. If it feels too dark, or is hard to read, ajusting this sensor will fix that 136 | 1. Using a device with Wifi access, check for the Wireless Access Point "Doorsim", by default, this should have no password 137 | 1. Connect to the Wifi Network, and verify that the network can be accessed, and browse to `http://192.168.4.1` 138 | 1. Note: This may have issues on phones, this is how Android/iOS works with these networks, as they do not have internet. 139 | 140 | ## Use 141 | 142 | ### Web Interface 143 | 144 | - Accessable via Wifi Network Broadcasting on ESP32 145 | - Can be configured via the Settings menu on the Web Interface 146 | - Browse to `http://192.168.4.1` 147 | - Last Swiped Card Interface, displaying card information 148 | - Hint: Click on the Hex Code for the Card to copy it to clipboard 149 | - CTF Board will show the CTF interface, alongside the additional cards to be added, and the last swiped cards 150 | - Hint: CTF Configurations can be imported and exported using the buttons avalible 151 | - Settings menu allows you to change basic settings of the device 152 | 153 | ### Display 154 | 155 | - Displays generic messaging or CTF messaging based upon enabled mode 156 | - Can be configured via the Settings menu on the Web Interface 157 | - Displays either the card information of the last swiped card or the invalid/valid response of a card 158 | 159 | ### Modes 160 | 161 | #### CTF Mode 162 | 163 | - Presents Generic Message for DoorSim on Display 164 | - Can be customised 165 | - Displays Incorrect for cards not found in device 166 | - Web Interface shows all data in Last Swiped Cards 167 | - Web Interface CTF Board will show CN/FC of last invalid card swiped, shows users authorised with only their name 168 | 169 | #### Demo Mode 170 | 171 | - Presents Generic Message for DoorSim on Display 172 | - Display the Card Number and Facility Code for each card scanned on the 2004a display 173 | - Web Interface shows all data in Last Swiped Cards 174 | - Web Interface CTF Board will show HEX of cards swiped 175 | 176 | ## Screenshots 177 | 178 | ![Last Read Card Info](https://github.com/evildaemond/doorsim/blob/main/meta/firefox_sqk2iyN0NH.png) 179 | ![CTF Display](https://github.com/evildaemond/doorsim/blob/main/meta/firefox_SsNUgBIJY4.png) 180 | ![Settings Menu](https://github.com/evildaemond/doorsim/blob/main/meta/firefox_tcPkSL7II0.png) 181 | 182 | 183 | ## Acknowledgements 184 | 185 | - Core Weigand and card decoding based upon [Tusk](https://github.com/TeamWalrus/tusk) 186 | - Design inspiration by [RTA Door Simulator](https://shop.redteamalliance.com/products/rfid-hacking-and-defense-physical-access-control-systems-pacs-proxmark3-training) 187 | -------------------------------------------------------------------------------- /meta/20240801_150329.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evildaemond/doorsim/c2154b1a724cfc75c05a98308d95a0a7203c8ad3/meta/20240801_150329.jpg -------------------------------------------------------------------------------- /meta/DSC02535-min.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evildaemond/doorsim/c2154b1a724cfc75c05a98308d95a0a7203c8ad3/meta/DSC02535-min.JPG -------------------------------------------------------------------------------- /meta/DSC02537-min.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evildaemond/doorsim/c2154b1a724cfc75c05a98308d95a0a7203c8ad3/meta/DSC02537-min.JPG -------------------------------------------------------------------------------- /meta/DSC02540-min.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evildaemond/doorsim/c2154b1a724cfc75c05a98308d95a0a7203c8ad3/meta/DSC02540-min.JPG -------------------------------------------------------------------------------- /meta/DSC02546-min.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evildaemond/doorsim/c2154b1a724cfc75c05a98308d95a0a7203c8ad3/meta/DSC02546-min.JPG -------------------------------------------------------------------------------- /meta/DSC02551-min.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evildaemond/doorsim/c2154b1a724cfc75c05a98308d95a0a7203c8ad3/meta/DSC02551-min.JPG -------------------------------------------------------------------------------- /meta/DSC02559-min.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evildaemond/doorsim/c2154b1a724cfc75c05a98308d95a0a7203c8ad3/meta/DSC02559-min.JPG -------------------------------------------------------------------------------- /meta/DSC02562-min.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evildaemond/doorsim/c2154b1a724cfc75c05a98308d95a0a7203c8ad3/meta/DSC02562-min.JPG -------------------------------------------------------------------------------- /meta/DSC02565-min.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evildaemond/doorsim/c2154b1a724cfc75c05a98308d95a0a7203c8ad3/meta/DSC02565-min.JPG -------------------------------------------------------------------------------- /meta/DSC02566-min.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evildaemond/doorsim/c2154b1a724cfc75c05a98308d95a0a7203c8ad3/meta/DSC02566-min.JPG -------------------------------------------------------------------------------- /meta/DSC02573-min.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evildaemond/doorsim/c2154b1a724cfc75c05a98308d95a0a7203c8ad3/meta/DSC02573-min.JPG -------------------------------------------------------------------------------- /meta/DSC02576-min.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evildaemond/doorsim/c2154b1a724cfc75c05a98308d95a0a7203c8ad3/meta/DSC02576-min.JPG -------------------------------------------------------------------------------- /meta/DSC02577-min.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evildaemond/doorsim/c2154b1a724cfc75c05a98308d95a0a7203c8ad3/meta/DSC02577-min.JPG -------------------------------------------------------------------------------- /meta/DSC02589-min.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evildaemond/doorsim/c2154b1a724cfc75c05a98308d95a0a7203c8ad3/meta/DSC02589-min.JPG -------------------------------------------------------------------------------- /meta/DSC02593-min.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evildaemond/doorsim/c2154b1a724cfc75c05a98308d95a0a7203c8ad3/meta/DSC02593-min.JPG -------------------------------------------------------------------------------- /meta/DSC02604-min.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evildaemond/doorsim/c2154b1a724cfc75c05a98308d95a0a7203c8ad3/meta/DSC02604-min.JPG -------------------------------------------------------------------------------- /meta/DSC02608-min.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evildaemond/doorsim/c2154b1a724cfc75c05a98308d95a0a7203c8ad3/meta/DSC02608-min.JPG -------------------------------------------------------------------------------- /meta/firefox_SsNUgBIJY4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evildaemond/doorsim/c2154b1a724cfc75c05a98308d95a0a7203c8ad3/meta/firefox_SsNUgBIJY4.png -------------------------------------------------------------------------------- /meta/firefox_sqk2iyN0NH.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evildaemond/doorsim/c2154b1a724cfc75c05a98308d95a0a7203c8ad3/meta/firefox_sqk2iyN0NH.png -------------------------------------------------------------------------------- /meta/firefox_tcPkSL7II0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evildaemond/doorsim/c2154b1a724cfc75c05a98308d95a0a7203c8ad3/meta/firefox_tcPkSL7II0.png --------------------------------------------------------------------------------