HID R10 / R15 Desktop RFID Arduino Door Simulator (Project Complete)

How is that connected?

IIRC, the wiegand reader that I have beeps when the cable is grounded. I suspect that you’re running into a problem with the overvoltage protection of the MCU.

Maybe add a transistor to drive that pin low when you want it to beep?

1 Like

THIS was the solution. Sort of. I had the code returning LOW to end the beep, which was sending it to ground. I just had to reverse the output to have low trigger the beep and high to end it and it works.

Well it didnt at first. i heard a small click. so i jammed one of the jaws of my tweezers into the beeper hole and now it actually beeps. so there was something in there stopping the beeper from working or it was stuck. Works great now.

2 Likes

Ah fair enough

I know, just nit-picking to help you learn best practices early! You’re welcome to tell me to fuck off :grin:

I linked an article that explains it. Basically instead of an array of values, you have a set of names that you can use.

instead of

const uint32_t modeCards[4] = {1, 2, 3, 4}; 

You could have

enum modeCards = {READ, DOOR, ADD, REMOVE}; 

Where READ, DOOR, ADD, REMOVE are arbitrary names. An emun doesn’t have length restrictions.

You use it like this:

modeCards currentMode = somthing();
String modeText = "";
 switch (currentMode) {
    case READ: modeText = "Read Mode"; break;
    case DOOR: modeText = "Door Mode"; break;
    case ADD: modeText = "Add Card Mode"; break;
    case REMOVE: modeText = "Remove Card Mode"; break;
}
// or
if( currentMode == DOOR){
    somethingElse();
}

Obviously you don’t need to do that, but it can help make the code easier to read. This helps others get up to speed on your code, and helps make debugging easier.

Nice! Glad you got it solved

This is a good very basic tutorial.

This is a good followup to the first tutorial.

With the repeater sticker, can you get a read on the xmagic?

2 Likes

Not sure about the wiegand issues unfortunately

1 Like

Could you please review and see if this makes sense?

#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <Wiegand.h>
#include <EEPROM.h>

#define SIGNAL_PIN 13    
#define BEEPER_PIN A3    

#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1  
#define SCREEN_ADDRESS 0x3C 

#define MAX_CARDS 20  

#define EEPROM_CARD_COUNT_ADDR 0              // EEPROM address to store the card count
#define EEPROM_CARD_DATA_ADDR  4              // Starting address for card data
#define CARD_SIZE              sizeof(uint32_t) // Size of each card entry (4 bytes for uint32_t)

#define RED_LED_PIN 5     // Define red LED pin
#define GREEN_LED_PIN 6   // Define green LED pin

Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
WIEGAND wg;

uint32_t authorizedCards[MAX_CARDS];
uint8_t cardCount = 0;

const uint32_t modeCards[4] = {1, 2, 3, 4}; 
uint8_t currentMode = 1;

// Function to count total bits in card data
uint8_t countBits(uint32_t data) {
  uint8_t count = 0;
  while (data) {
    data >>= 1;
    count++;
  }
  return count;
}

// Extract facility code (assuming 26-bit format)
uint8_t getFacilityCode(uint32_t data) {
  return (data >> 16) & 0xFF;
}

// Extract card number (assuming 26-bit format)
uint16_t getCardNumber(uint32_t data) {
  return data & 0xFFFF;
}

// Load authorized cards from EEPROM with validation
void loadAuthorizedCardsFromEEPROM() {
  cardCount = EEPROM.read(EEPROM_CARD_COUNT_ADDR);

  // Check if the card count is valid; if not, assume no cards are stored
  if (cardCount > MAX_CARDS) {
    cardCount = 0;  // Reset to 0 if invalid count
    return;         // Skip loading if there are no valid cards
  }

  // Load each card from EEPROM into the authorizedCards array
  for (uint8_t i = 0; i < cardCount; i++) {
    uint32_t cardData = 0;
    for (uint8_t j = 0; j < CARD_SIZE; j++) {
      cardData |= EEPROM.read(EEPROM_CARD_DATA_ADDR + i * CARD_SIZE + j) << (8 * j);
    }
    authorizedCards[i] = cardData;
  }
}
// Save authorized cards to EEPROM
void saveAuthorizedCardsToEEPROM() {
  EEPROM.write(EEPROM_CARD_COUNT_ADDR, cardCount);
  for (uint8_t i = 0; i < cardCount; i++) {
    uint32_t cardData = authorizedCards[i];
    for (uint8_t j = 0; j < CARD_SIZE; j++) {
      EEPROM.write(EEPROM_CARD_DATA_ADDR + i * CARD_SIZE + j, (cardData >> (8 * j)) & 0xFF);
    }
  }
}

void setup() {
  delay(500);
  Serial.begin(9600);

  // Load cards from EEPROM
  loadAuthorizedCardsFromEEPROM();

  // Initialize OLED display
  if (!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS, OLED_RESET)) {
    Serial.println(F("SSD1306 allocation failed"));
    for (;;);  
  }
  display.clearDisplay();
  display.display();  // Ensure display is clear
  display.setTextSize(2);
  display.setTextColor(WHITE);

  wg.begin();

  pinMode(SIGNAL_PIN, OUTPUT);
  digitalWrite(SIGNAL_PIN, LOW);

  pinMode(BEEPER_PIN, OUTPUT);
  digitalWrite(BEEPER_PIN, HIGH);  // Ensure beeper is off initially

  // Set LED pins as output and turn them off initially
  pinMode(RED_LED_PIN, OUTPUT);
  pinMode(GREEN_LED_PIN, OUTPUT);
  digitalWrite(RED_LED_PIN, HIGH);   // Turn off red LED initially
  digitalWrite(GREEN_LED_PIN, HIGH); // Turn off green LED initially

  // Display "Read Mode" directly on startup
  display.clearDisplay();
  display.setCursor(13, 0);
  display.print("Read Mode");
  display.display();
}

void loop() {
  if (wg.available()) {
    uint32_t cardData = wg.getCode();  
    uint8_t bitLength = countBits(cardData);  // Manually calculate bit length
    uint8_t facilityCode = getFacilityCode(cardData);  // Get facility code
    uint16_t cardNumber = getCardNumber(cardData);  // Get card number

    // Debugging: Print card data, bit length, and facility code to Serial Monitor
    Serial.print("Card Data: ");
    Serial.println(cardData);
    Serial.print("Bit Length: ");
    Serial.println(bitLength);
    Serial.print("Facility Code: ");
    Serial.println(facilityCode);
    Serial.print("Card Number: ");
    Serial.println(cardNumber);

    // Beep to indicate card read
    beep();

    // Check if the scanned card is a mode-changing card
    for (uint8_t i = 0; i < 4; i++) {
      if (cardData == modeCards[i]) {
        currentMode = i + 1;
        displayMode();
        return;
      }
    }

    // Perform actions based on the current mode
    switch (currentMode) {
      case 1:  // Read Mode
          display.clearDisplay();
          display.setCursor(13, 0);
          display.setTextSize(2);
          display.print("Read Mode");

          display.setTextSize(1);
          display.setCursor(0, 22);
          display.print("Card #: ");
          display.print(cardNumber);  // Show only the card number part

          display.setCursor(0, 32);
          display.print("FC: ");
          display.print(facilityCode);

          display.setCursor(0, 42);
          display.print("Bit Len: ");
          display.print(bitLength);

          display.setCursor(0, 52);
          display.print("Raw Data: ");
          display.print(cardData);

          display.display();
          break;
      case 2:  // Door Mode
        if (isAuthorized(cardData)) {
          digitalWrite(SIGNAL_PIN, LOW);  
          delay(50);                      
          digitalWrite(SIGNAL_PIN, HIGH);   
          displayData("Door Mode", "Authorized", "", "");
        } else {
          displayData("Door Mode", "Denied", "", "");
        }
        break;

      case 3:  // Add Card Mode
        addCard(cardData);
        displayData("Add Card Mode", "Card Added", "Card Data: " + String(cardData), "");
        break;

      case 4:  // Remove Card Mode
        removeCard(cardData);
        displayData("Remove Card Mode", "Card Removed", "Card Data: " + String(cardData), "");
        break;
    }
  }
}

// Function to produce a short beep sound
void beep() {
  digitalWrite(BEEPER_PIN, LOW);  
  delay(50);                     
  digitalWrite(BEEPER_PIN, HIGH);  
}

// Function to display the current mode on the OLED screen and control LEDs
void displayMode() {
  String modeText = "";

  // Set the LED state based on current mode
  if (currentMode == 3) {          // Add Card Mode
    digitalWrite(GREEN_LED_PIN, LOW);  // Turn on green LED
    digitalWrite(RED_LED_PIN, HIGH);   // Turn off red LED
    modeText = "Add Card Mode";
  } else if (currentMode == 4) {   // Remove Card Mode
    digitalWrite(RED_LED_PIN, LOW);   // Turn on red LED
    digitalWrite(GREEN_LED_PIN, HIGH); // Turn off green LED
    modeText = "Remove Card Mode";
  } else {
    // Turn off both LEDs for other modes
    digitalWrite(GREEN_LED_PIN, HIGH); // Turn off green LED
    digitalWrite(RED_LED_PIN, HIGH);   // Turn off red LED

    // Set mode text
    switch (currentMode) {
      case 1: modeText = "Read Mode"; break;
      case 2: modeText = "Door Mode"; break;
    }
  }

  displayData(modeText, "", "", "");
}

// Function to display text lines on the OLED
void displayData(String mode, String line1, String line2, String line3) {
  display.clearDisplay();   // Clear display buffer
  display.setTextSize(2);

  display.setCursor(13, 0);
  display.setTextColor(WHITE);
  display.print(mode);

  display.setTextSize(1);
  display.setCursor(0, 20);
  display.print(line1);
  display.setCursor(0, 30);
  display.print(line2);
  display.setCursor(0, 40);
  display.print(line3);

  display.display();        // Refresh display to show updated content
}

// Function to check if a card is authorized
bool isAuthorized(uint32_t data) {
  for (uint8_t i = 0; i < cardCount; i++) {
    if (authorizedCards[i] == data) {
      return true;
    }
  }
  return false;
}
// Function to add a card to the authorized list
void addCard(uint32_t data) {
  if (cardCount < MAX_CARDS && !isAuthorized(data)) {
    authorizedCards[cardCount] = data;
    cardCount++;
    saveAuthorizedCardsToEEPROM(); // Save changes to EEPROM
  }
}

// Function to remove a card from the authorized list
void removeCard(uint32_t data) {
  for (uint8_t i = 0; i < cardCount; i++) {
    if (authorizedCards[i] == data) {
      for (uint8_t j = i; j < cardCount - 1; j++) {
        authorizedCards[j] = authorizedCards[j + 1];
      }
      cardCount--;
      saveAuthorizedCardsToEEPROM(); // Save changes to EEPROM
      break;
    }
  }
}
1 Like

I can look at this later when I have more time. You have the right idea, I’m just not sure if the address math works out yet.

One easy thing to do would be to make a struct

And then use EEPROM.put() to store the whole struct in EEPROM

You can then move the authorizedCards array and card count to the struct. That way getting and storing the struct is one line and you don’t need to do address math.

I can come up with an example later this evening if you’d like.

I gotta say, I admire your drive to complete projects like this. The repeaters, and now coding, you’re learning a ton of complex stuff and following it through. It’s impressive

3 Likes

Youtube and ChatGpt are to thank for a lot of it. i understand a lot of major concepts i just dont have the formal education for the nit-picky stuff. I found if you can write out the base of the code, you can paste it into ChatGpt and it can format it correctly and tell you pretty much why it isnt compiling.

Youtube is a god-send for my learning style. If theres something you want to learn about you can only blame yourself for not knowing it given the tools that are available to you.

So the issues i am having is that the cards are not activating the other cases. I have 4 fobs.

each are written with Card Number 1, 2, 3, 4 and they are supposed to activate the other cases when scanned but do not. Should i make them 4 digit card numbers?

I also should add I do have some background in Python and c++ but nothing substantial at all

3 Likes
// Check if the scanned card is a mode-changing card
    for (uint8_t i = 0; i < 4; i++) {
      if (cardData == modeCards[i]) {
        currentMode = i + 1;
        displayMode();
        return;
      }
    }

Should be

// Check if the scanned card is a mode-changing card
    for (uint8_t i = 0; i < 4; i++) {
      if (cardData == modeCards[i]) {
        currentMode = modeCards[i];
        Serial.print("switching mode to: ");
        Serial.println(currentMode);
        displayMode();
        break;
      }
    }

No?

Edit:
Ah OK, I see what you’re doing with the i+1. I think that return is the issue. I haven’t sat down to look through the logic of the code yet though

Edit edit:
I’m not confident in the if statement. I don’t know what cardData is, I added a print statement for that sweet sweet print debugging

1 Like

YouTube is great. I’ve been pleased with chatgpt for short bash scripts, but its definitely difficult to debug when you don’t have a solid foundation in the language

2 Likes

cardData is the call from the wiegand library to return the raw card info. From there Facility, card number and Bit are extracted. in this particular function cardData represents the Card Number. So if Card Number = modeCards (the 4 card numbers i assigned as a mode card) then it should change the mode (case).

Again, not a coder but explaining my thought process as well as i can in how i wrote it.

1 Like

For the love of God, whatever you do, don’t shoehorn in longer card bit lengths into the 24 or 26 bit length weigand card data format… typically that means you just truncate additional bits from the card ID.

In my opinion, the 1970s era weigand format in general needs to die painful death. That said, if anybody writing anything having to do with weigand wishes to ignore any technologies that came out after 1980, at the very least they should ignore the leading bits. Unfortunately, pretty much every implementation I’ve ever seen will simply accept the first 24 or 26 bits and then ignore the rest. Brilliantly, this means if I produce a set of cards or fobs from a serialized waiver, the entire wafer is going to have the same identical leading 24 or 26 bits… therefore any card from that wafer which is added to an authentication list will result in the entire wafer full of cards or fobs being recognized as authenticated.

Can we just kill weigand please? A lot of people think that if they had a time machine they would go back and do something about hitler, but I’m seriously considering this weigand guy as a firm option.

6 Likes

I’m just trying to read card number. Display bits and facility code.

I didn’t have the know how to do anything else.

Hell I struggled for a day to figure out why the damn screen wouldn’t turn on before I found some random guy on Arduino forums recommend a 500ms delay.

4 Likes

Fair. I guess I’m just taking every opportunity to shit on such a crap standard :slight_smile:

4 Likes

The hid readers have a nifty interface that allows you to select the output.

The issue is finding a half decent Arduino library to interpret it.

I really should do a separate write up on these readers and how convenient the app is to use and configure them

You’d be surprised if how the “American industrial complex” is running … :roll_eyes:
I have customer buying machine out of dead plant to save parts out of machine from companies that died last century … And those people are making food, water and drugs …

2 Likes

Naa not surprised… American banking runs on fiserve insecure ftp servers and text files…

4 Likes

added in the pinout for the Arduino Uno and Oled to the R15 in the original post. it wasnt included originally because i wasnt sure there was going to be interest in this project but it appears i was wrong…

Also included the current Arduino code, since it varies slightly from the Metro.

3 Likes

So after a 5 month gap. This project is complete.

Follow the guide in the first post for the Pinout.

@Pilgrimsmaster can you please remove refference to the R15 Mini-Metro Code as that is outdated and go forward with this code. The forum doesnt allow me to edit the first post anymore :frowning:. I will update the other posts to remove mention of the metro. I might rewrite it with the updated code. But i love the dual color display so ill have to update the refference to the i2c display as well.

This is for an Arduino Nano (as is the cases that were made. Door striker signal wire is on Pin 7

Current code allows for storage of 20 cards. It can be changed.

Mode cards need to be configured as follows:

 0x1E6DC032, // Read mode
 0x1E6D9861, // Door mode
 0x1E6DFCA0, // Add mode
 0x1E6DE636  // Remove mode

Read Mode - Just acts as a regular Wiegand reader.

Door Mode will act as an entry access controller. If authorized will send signal to the signal pin (7) and activate a maglock or any relay you have setup.

Add Mode - When scanned and in the add card mode, scan cards and it will add them to eeprom to allow for additional cards to be added.

Remove mode - when in this mode you can scan the cards you want to remove from the eeprom.

#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <Wiegand.h>
#include <EEPROM.h>

#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
#define SCREEN_ADDRESS 0x3C

#define WIEGAND_D0_PIN 2 // Green wire (Data 0)
#define WIEGAND_D1_PIN 3 // White wire (Data 1)
#define BEEPER_PIN A3
#define SIGNAL_PIN 7 // Door strike signal pin

#define MAX_CARDS 20
#define EEPROM_CARD_COUNT_ADDR 0
#define EEPROM_CARD_DATA_ADDR 4
#define CARD_SIZE sizeof(unsigned long)

Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
WIEGAND wg;

bool cardDisplayed = false;
unsigned long lastCardData = 0;
uint8_t lastBitLength = 0;
uint8_t lastFacilityCode = 0;
uint16_t lastCardNumber = 0;

#define MODE_READ 1
#define MODE_DOOR 2
#define MODE_ADD 3
#define MODE_REMOVE 4
uint8_t currentMode = MODE_READ;

const unsigned long configCards[4] = {
  0x1E6DC032, // Read mode
  0x1E6D9861, // Door mode
  0x1E6DFCA0, // Add mode
  0x1E6DE636  // Remove mode
};

unsigned long authorizedCards[MAX_CARDS];
uint8_t cardCount = 0;

uint8_t countBits(uint32_t data) {
  uint8_t count = 0;
  while (data) {
    data >>= 1;
    count++;
  }
  return count;
}

void loadAuthorizedCards() {
  cardCount = EEPROM.read(EEPROM_CARD_COUNT_ADDR);
  if (cardCount > MAX_CARDS) {
    cardCount = 0;
    return;
  }
  for (uint8_t i = 0; i < cardCount; i++) {
    unsigned long cardData = 0;
    for (uint8_t j = 0; j < CARD_SIZE; j++) {
      cardData |= (unsigned long)EEPROM.read(EEPROM_CARD_DATA_ADDR + i * CARD_SIZE + j) << (8 * j);
    }
    authorizedCards[i] = cardData;
  }
}

void saveAuthorizedCards() {
  EEPROM.update(EEPROM_CARD_COUNT_ADDR, cardCount);
  for (uint8_t i = 0; i < cardCount; i++) {
    unsigned long cardData = authorizedCards[i];
    for (uint8_t j = 0; j < CARD_SIZE; j++) {
      EEPROM.update(EEPROM_CARD_DATA_ADDR + i * CARD_SIZE + j, (cardData >> (8 * j)) & 0xFF);
    }
  }
}

bool isAuthorized(unsigned long cardData) {
  for (uint8_t i = 0; i < cardCount; i++) {
    if (authorizedCards[i] == cardData) {
      return true;
    }
  }
  return false;
}

void addCard(unsigned long cardData) {
  if (cardCount < MAX_CARDS && !isAuthorized(cardData)) {
    authorizedCards[cardCount] = cardData;
    cardCount++;
    saveAuthorizedCards();
  }
}

void removeCard(unsigned long cardData) {
  for (uint8_t i = 0; i < cardCount; i++) {
    if (authorizedCards[i] == cardData) {
      for (uint8_t j = i; j < cardCount - 1; j++) {
        authorizedCards[j] = authorizedCards[j + 1];
      }
      cardCount--;
      saveAuthorizedCards();
      break;
    }
  }
}

void displayScanPrompt() {
  display.clearDisplay();
  display.setTextSize(2);
  display.setCursor(12, 0);
  switch (currentMode) {
    case MODE_READ:   display.println("READ MODE"); break;
    case MODE_DOOR:   display.println("DOOR MODE"); break;
    case MODE_ADD:    display.println("ADD MODE"); break;
    case MODE_REMOVE: display.println("DEL MODE"); break;
  }
  display.setTextSize(1);
  display.setCursor(25, 25);
  display.println("Scan a card...");
  display.display();
  cardDisplayed = false;
}

void displayCardData(unsigned long cardData, uint8_t bitLength, uint8_t facilityCode, uint16_t cardNumber, const char* message = "") {
  display.clearDisplay();
  display.setTextSize(2);
  display.setCursor(12, 0);
  switch (currentMode) {
    case MODE_READ:   display.println("READ MODE"); break;
    case MODE_DOOR:   display.println("DOOR MODE"); break;
    case MODE_ADD:    display.println("ADD MODE"); break;
    case MODE_REMOVE: display.println("DEL MODE"); break;
  }
  display.setTextSize(1);
  if (currentMode == MODE_READ) {
    display.setCursor(0, 20);
    display.print("Hex: 0x");
    display.println(cardData, HEX);
    display.setCursor(0, 30);
    display.print("Raw: ");
    display.println(cardData);
    display.setCursor(0, 40);
    display.print("Bits: ");
    display.print(bitLength);
    display.println(" (approx)");
    display.setCursor(0, 50);
    display.print("FC: ");
    display.print(facilityCode);
    display.print(" ID: ");
    display.println(cardNumber);
  } else {
    display.setCursor(30, 20); // Center message
    display.println(message);
  }
  display.display();
  cardDisplayed = true;
}

void setup() {
  if (!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
    while (1);
  }

  display.clearDisplay();
  display.setTextColor(SSD1306_WHITE);
  display.setTextSize(2);
  display.setCursor(25, 16);
  display.println("RFIDsim");
  display.setTextSize(1);
  display.setCursor(31, 40);
  display.println("Version v1.0");
  display.display();
  delay(1000);

  loadAuthorizedCards();
  displayScanPrompt();

  wg.begin(WIEGAND_D0_PIN, WIEGAND_D1_PIN);

  pinMode(BEEPER_PIN, OUTPUT);
  digitalWrite(BEEPER_PIN, HIGH);
  pinMode(SIGNAL_PIN, OUTPUT);
  digitalWrite(SIGNAL_PIN, LOW);
}

void loop() {
  if (wg.available()) {
    unsigned long cardData = wg.getCode();

    // Check for configuration card
    for (uint8_t i = 0; i < 4; i++) {
      if (cardData == configCards[i]) {
        currentMode = i + 1;
        displayScanPrompt();
        digitalWrite(BEEPER_PIN, LOW);
        delay(100);
        digitalWrite(BEEPER_PIN, HIGH);
        return;
      }
    }

    // Process card data
    uint8_t bitLength = countBits(cardData);
    uint8_t facilityCode = (cardData >> 16) & 0xFF;
    uint16_t cardNumber = cardData & 0xFFFF;

    digitalWrite(BEEPER_PIN, LOW);
    delay(50);
    digitalWrite(BEEPER_PIN, HIGH);

    lastCardData = cardData;
    lastBitLength = bitLength;
    lastFacilityCode = facilityCode;
    lastCardNumber = cardNumber;

    // Handle mode-specific actions
    switch (currentMode) {
      case MODE_READ:
        displayCardData(cardData, bitLength, facilityCode, cardNumber);
        break;
      case MODE_DOOR:
        if (isAuthorized(cardData)) {
          digitalWrite(SIGNAL_PIN, HIGH);
          displayCardData(cardData, bitLength, facilityCode, cardNumber, "Authorized");
          delay(5000); // Show for 5 seconds
          digitalWrite(SIGNAL_PIN, LOW);
          displayScanPrompt();
        } else {
          displayCardData(cardData, bitLength, facilityCode, cardNumber, "Access Denied");
          delay(5000); // Show for 5 seconds
          displayScanPrompt();
        }
        break;
      case MODE_ADD:
        addCard(cardData);
        displayCardData(cardData, bitLength, facilityCode, cardNumber, "Card Added");
        break;
      case MODE_REMOVE:
        removeCard(cardData);
        displayCardData(cardData, bitLength, facilityCode, cardNumber, "Card Removed");
        break;
    }
  } else if (cardDisplayed) {
    displayCardData(lastCardData, lastBitLength, lastFacilityCode, lastCardNumber);
  } else {
    displayScanPrompt();
  }
}

I will create a Git Project log for this so it can be all organized in one place, but this was made possible by a gent named Peekaboo (peekaboo.icu) he reviewed my code and helped fix my faults. Big shout out to him, not sure if he is in the forum but he helped tremendously from one of the Defcon Discords i am part of.

But for those that have built this and was waiting for the update here is the finished code! You can actually use this device as a secure access controller if needed. it currently runs for around 5+ days off of a 6,000mah powerbank.

@Jammyjellyfish if you want to upload this you can be my guinea pig

3 Likes

Nice! I’ll test this out as soon as my head cold fucks off

1 Like

5 posts were merged into an existing topic: The anti​:no_entry_sign:-derailment​:railway_car: & thread​:thread: hijacking​:gun: thread​:thread: :interrobang: