commit 924ef93dd8059940918eaf44fdd00b5526dfa1b5 Author: Dominique Liberda Date: Tue Jun 20 16:21:00 2023 +0200 initial diff --git a/README.md b/README.md new file mode 100644 index 0000000..012809d --- /dev/null +++ b/README.md @@ -0,0 +1,58 @@ +# doorman2: electric boogaloo + +- `esp32/` contains the micropython source which talks w/ NFC module and keypad over UART (two channels) +- `keypad/` has some magical Arduino code (sorry) + +## syncing data from LDAP + +big TODO; currently, you need to: +1. clone the old doorman repo and patch the `doorman_ldap_sync` file (see my shitty patch attached below) + +``` +--- a/admin/bin/doorman_ldap_sync ++++ b/admin/bin/doorman_ldap_sync +@@ -63,14 +63,18 @@ def get_target_cards(c): + + if __name__ == "__main__": + url = argv[1] if len(argv) > 1 else options.url +- token = get_token() +- proto = Proto(url) ++ #token = get_token() ++ #proto = Proto(url) + + c = ldap.initialize('ldap://ldap.hackerspace.pl') + c.start_tls_s() +- c.simple_bind_s('uid=%s,ou=People,dc=hackerspace,dc=pl' % (getpass.getuser(),), getpass.getpass('LDAP password: ')) ++ c.simple_bind_s('uid=%s,ou=People,dc=hackerspace,dc=pl' % ('sdomi',), getpass.getpass('LDAP password: ')) + target = get_target_cards(c) +- cur = get_current_cards(token, proto) ++ pprint.pprint(target) ++ for h, u in target: ++ print(h) ++ ++ #cur = get_current_cards(token, proto) + + to_remove = cur - target + to_add = target - cur +``` + +2. launch the script, copy all the lines with the hashes and save them a file +3. `mpremote fs cp hashes :hashes` + +plans: web UI like vuko's design + +## esp <-> keypad protocol definition + +- one byte per command, no delimeters, keypad is supposed to be as stateless as possible +- the keypad can only send numbers and the hash symbol +- ESP can send the following commands: + - `H` ("happy" noise, success; turns on the green LED for a second and turns it back off) + - `S` ("sad" failure noise; likewise with the red LED) + - `F` ("flush"; turns off LEDs and tries to bring the env to a sane level) + - `G`, `R` ("green", "red"; turns on the specific LEDs) + - `Q` ("quiet"; turns on audible keypresses) + + +--- + +i am so sorry diff --git a/esp32/boot.py b/esp32/boot.py new file mode 100644 index 0000000..8936eec --- /dev/null +++ b/esp32/boot.py @@ -0,0 +1,53 @@ +from pn532 import PN532Uart +import utime +import machine +import hashlib + +DEBUG = True + +try: + door = machine.Pin(2, machine.Pin.OUT) + door.value(0) + + uart = machine.UART(1, tx=16, rx=17, baudrate=9600) + uart.write("F") + rf = PN532Uart(2, tx=22, rx=19) + rf.SAM_configuration() + ic, ver, rev, support = rf.get_firmware_version() + print('Found PN532 with firmware version: {0}.{1}'.format(ver, rev)) +except Exception as e: + rf = None + print('No NFC reader (PN532) detected') + +while rf is not None: + try: + uid = rf.read_passive_target() + print("Card UUID: " + ''.join('{:02x}'.format(x) for x in uid)) + card=''.join('{:02x}'.format(x) for x in uid) + uart.write("QG") + uart.flush() + c=0 + buf='' + success = False + while True: + if uart.any(): + c=c+1 + buf+=str(chr(uart.read()[0])) + if c == 4: + break + hash=hashlib.sha256(str("{0:#0{1}x}".format(int(buf),10)+":"+card[6:8]+card[4:6]+card[2:4]+card[0:2])[2:].encode()).digest().hex() + f = open("hashes") + for user in f: + if(user.strip() == hash): + success = True + if(success): + uart.write("FH") + door.value(1) + utime.sleep(2) + door.value(0) + else: + uart.write("FS") + utime.sleep(2) + buf='' + except Exception as e: + print('timeout!') diff --git a/esp32/pn532.py b/esp32/pn532.py new file mode 100644 index 0000000..c92d2c8 --- /dev/null +++ b/esp32/pn532.py @@ -0,0 +1,211 @@ +from micropython import const +import machine +import utime + +# from pn532uart import PN532_UART +# import PN532 + +# PN532 Commands +_WAKEUP = b'\x55\x55\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' +_COMMAND_GETFIRMWAREVERSION = const(0x02) +_COMMAND_INLISTPASSIVETARGET = const(0x4A) +_COMMAND_INRELEASE = const(0x52) +_COMMAND_SAMCONFIGURATION = const(0x14) + +# Send Frames +_PREAMBLE = const(0x00) +_STARTCODE1 = const(0x00) +_STARTCODE2 = const(0xFF) +_POSTAMBLE = const(0x00) + +# Message parts +_HOSTTOPN532 = const(0xD4) +_PN532TOHOST = const(0xD5) +_ACK = b'\x00\x00\xFF\x00\xFF\x00' +_FRAME_START = b'\x00\x00\xFF' + +# Codes +_MIFARE_ISO14443A = const(0x00) + +class PN532Uart(object): + """ + Class for interacting with the PN532 via the uart interface. + """ + def __init__(self, uart_no, tx=None, rx=None, debug=False): + if tx and rx: + self.uart = machine.UART(uart_no, baudrate=115200, tx=tx, rx=rx) + else: + self.uart = machine.UART(uart_no, baudrate=115200) + + self.debug = debug + # self.swriter = asyncio.StreamWriter(self.uart, {}) + # self.sreader = asyncio.StreamReader(self.uart) + + def wait_read_len(self, len): + timeout_ms = 1000 + start_time = utime.ticks_ms() + end_time = start_time + timeout_ms + while self.uart.any() < len and utime.ticks_ms() < end_time: + continue + + if(utime.ticks_ms() >= end_time): + raise RuntimeError('No response from PN532!') + + return self.uart.read(len) + + def _write_frame(self, data): + """Write a frame to the PN532 with the specified data bytearray.""" + assert data is not None and 1 < len(data) < 255, 'Data must be array of 1 to 255 bytes.' + + # Build frame to send as: + # - Preamble (0x00) + # - Start code (0x00, 0xFF) + # - Command length (1 byte) + # - Command length checksum + # - Command bytes + # - Checksum + # - Postamble (0x00) + length = len(data) + frame = bytearray(length+8) + frame[0] = _PREAMBLE + frame[1] = _STARTCODE1 + frame[2] = _STARTCODE2 + checksum = sum(frame[0:3]) + frame[3] = length & 0xFF + frame[4] = (~length + 1) & 0xFF + frame[5:-2] = data + checksum += sum(data) + frame[-2] = ~checksum & 0xFF + frame[-1] = _POSTAMBLE + + # Send frame. + if self.debug: + print('_write_frame: ', [hex(i) for i in frame]) + + # HACK! Timeouts can cause there to be data in the read buffer that was for an old command (ie read_passive_target). + # Additonally, the device needs to be woken sometimes, and it seems safe to do that before every command. + # TODO: Does WAKEUP cancel the passive read_command? + # Before sending the real command, clear the read buffer + self.uart.write(_WAKEUP) + + waiting = self.uart.any() + while waiting > 0: + if self.debug: + print("Removing %d bytes in the read buffer") + self.uart.read(waiting) + waiting = self.uart.any() + + self.uart.write(bytes(frame)) + #self.uart.flush() + + ack = self.wait_read_len(len(_ACK)) + + if self.debug: + print('_write_frame: ACK: ', [hex(i) for i in ack]) + if ack != _ACK: + raise RuntimeError('Did not receive expected ACK from PN532!') + + def _read_frame(self): + """ + Read a response frame from the PN532 and return the data inside the frame, + otherwise raises an exception if there is an error parsing the frame. + """ + # Read the Frame start and header + response = self.wait_read_len(len(_FRAME_START)+2) + if self.debug: + print('_read_frame: frame_start + header:', [hex(i) for i in response]) + + if len(response) < (len(_FRAME_START) + 2) or response[:-2] != _FRAME_START: + raise RuntimeError('Response does not begin with _FRAME_START!') + + # Read the header (length & length checksum) and make sure they match. + frame_len = response[-2] + frame_checksum = response[-1] + if (frame_len + frame_checksum) & 0xFF != 0: + raise RuntimeError('Response length checksum did not match length!') + + # read the frame (data + data checksum + end frame) & validate + data = self.wait_read_len(frame_len+2) + + if self.debug: + print('_read_frame: data: ', [hex(i) for i in data]) + + checksum = sum(data) & 0xFF + if checksum != 0: + raise RuntimeError('Response checksum did not match expected value: ', checksum) + + if data[-1] != 0x00: + raise RuntimeError('Response does not include Frame End') + + # Return frame data. + return data[0:frame_len] + + def call_function(self, command, params=[]): + """ + Send specified command to the PN532 and return the response. + Note: There is no timeout option. Use async.wait_for(function(), timeout) instead + """ + data = bytearray(2 + len(params)) + data[0] = _HOSTTOPN532 + data[1] = command & 0xFF + for i, val in enumerate(params): + data[2+i] = val + + # Send the frame and read the response + self._write_frame(data) + response = self._read_frame() + + if len(response) < 2: + raise RuntimeError('Received smaller than expected frame') + + if not(response[0] == _PN532TOHOST and response[1] == (command+1)): + raise RuntimeError('Received unexpected command response!') + + # Return response data. + return response[2:] + + def SAM_configuration(self): + if self.debug: + print("Sending SAM_CONFIGURATION") + + response = self.call_function(_COMMAND_SAMCONFIGURATION, params=[0x01, 0x14, 0x01]) + if self.debug: + print('SAM_configuration:', [hex(i) for i in response]) + + def get_firmware_version(self): + """ + Call PN532 GetFirmwareVersion function and return a tuple with the IC, + Ver, Rev, and Support values. + """ + if self.debug: + print("Sending GET_FIRMWARE_VERSION") + + response = self.call_function(_COMMAND_GETFIRMWAREVERSION) + if response is None: + raise RuntimeError('Failed to detect the PN532') + return tuple(response) + + def read_passive_target(self, card_baud=_MIFARE_ISO14443A): + """ + Wait for a MiFare card to be available and return its UID when found. + Will wait up to timeout seconds and return None if no card is found, + otherwise a bytearray with the UID of the found card is returned. + """ + if self.debug: + print("Sending INIT_PASSIVE_TARGET") + # Send passive read command for 1 card. Expect at most a 7 byte UUID. + response = self.call_function(_COMMAND_INLISTPASSIVETARGET, params=[0x01, card_baud]) + + # Check only 1 card with up to a 7 byte UID is present. + if response[0] != 0x01: + raise RuntimeError('More than one card detected!') + if response[5] > 7: + raise RuntimeError('Found card with unexpectedly long UID!') + + # Return UID of card. + return response[6:6+response[5]] + + def release_targets(self): + if self.debug: + print("Release Targets") + response = self.call_function(_COMMAND_INRELEASE, params=[0x00]) diff --git a/keypad/README.md b/keypad/README.md new file mode 100644 index 0000000..8a35667 --- /dev/null +++ b/keypad/README.md @@ -0,0 +1,7 @@ +# keypad howto + +1) flash MiniCore for atmega168pa +2) make sure you have the Keypad arduino library +3) if re-making the hw from scratch, you need to make some HW mods to the keypad: + - green led moved to a different pin (due to UART conflict) +4) verify the pin IDs diff --git a/keypad/keypad.ino b/keypad/keypad.ino new file mode 100644 index 0000000..07a94dc --- /dev/null +++ b/keypad/keypad.ino @@ -0,0 +1,157 @@ +/* @file HelloKeypad.pde +|| @version 1.0 +|| @author Alexander Brevig +|| @contact alexanderbrevig@gmail.com +|| +|| @description +|| | Demonstrates the simplest use of the matrix Keypad library. +|| # +*/ +#include +#define BUZZER PIN_PB2 +#define LED_RED PIN_PD4 +#define LED_GREEN PIN_PC5 + +const byte ROWS = 4; //four rows +const byte COLS = 3; //three columns +char keys[ROWS][COLS] = { + {'1','2','3'}, + {'4','5','6'}, + {'7','8','9'}, + {'*','0','#'} +}; +byte rowPins[ROWS] = {PIN_PC1, PIN_PC0, PIN_PB5, PIN_PB4}; //connect to the row pinouts of the keypad +byte colPins[COLS] = {PIN_PB3, PIN_PB0, PIN_PD5}; //connect to the column pinouts of the keypad + +bool quiet = true; +String buf; + +Keypad keypad = Keypad( makeKeymap(keys), rowPins, colPins, ROWS, COLS ); + +void(* reset) (void) = 0; + +void click() { + if (!quiet) { + pinMode(BUZZER, OUTPUT); + // too tired to check how to do proper PWM lol + for(int i=0;i<10;i++) { + digitalWrite(BUZZER, HIGH); + delay(1); + digitalWrite(BUZZER, LOW); + delay(1); + } + pinMode(BUZZER, INPUT); + } +} + +void sad() { + pinMode(BUZZER, OUTPUT); + for(int i=0;i<75;i++) { + digitalWrite(BUZZER, HIGH); + delay(1); + digitalWrite(BUZZER, LOW); + delay(1); + } + for(int i=0;i<50;i++) { + digitalWrite(BUZZER, HIGH); + delay(1); + digitalWrite(BUZZER, LOW); + delay(2); + } + pinMode(BUZZER, INPUT); +} + +void happy() { + pinMode(BUZZER, OUTPUT); + for(int i=0;i<25;i++) { + digitalWrite(BUZZER, HIGH); + delay(1); + digitalWrite(BUZZER, LOW); + delay(3); + } + for(int i=0;i<40;i++) { + digitalWrite(BUZZER, HIGH); + delay(1); + digitalWrite(BUZZER, LOW); + delay(2); + } + for(int i=0;i<45;i++) { + digitalWrite(BUZZER, HIGH); + delay(1); + digitalWrite(BUZZER, LOW); + delay(1); + } + pinMode(BUZZER, INPUT); +} + +void setup(){ + Serial.begin(9600); + keypad.setDebounceTime(1); + pinMode(LED_RED, OUTPUT); + pinMode(LED_GREEN, OUTPUT); + digitalWrite(LED_GREEN, 0); + delay(1000); + digitalWrite(LED_GREEN, 1); + happy(); +} + +void loop(){ + char key = keypad.getKey(); + + if (key) { + /* + we don't really have a use for #/* keys; thus, we can define + magic key combos! + + currently: + - *1337## resets the keypad + */ + if (key == '*') { + quiet = false; + buf=""; + int i=0; + while (true) { + key = keypad.getKey(); + if (key) { + click(); + buf+=key; + i++; + } + if (i>6) break; + if (buf == "1337##") { + reset(); + } + } + quiet = true; + } + click(); + Serial.println(key); + } + while (Serial.available() > 0) { + char a = Serial.read(); + if (a == 'H') { + digitalWrite(LED_RED, HIGH); + digitalWrite(LED_GREEN, LOW); + happy(); + delay(1000); + digitalWrite(LED_GREEN, HIGH); + } else if (a == 'S') { + digitalWrite(LED_GREEN, HIGH); + digitalWrite(LED_RED, LOW); + sad(); + delay(1000); + digitalWrite(LED_RED, HIGH); + } else if (a == 'F') { // flush + digitalWrite(LED_GREEN, HIGH); + digitalWrite(LED_RED, HIGH); + quiet = true; + pinMode(BUZZER, INPUT); // to be sure ;3 + } else if (a == 'G') { // green + digitalWrite(LED_GREEN, LOW); + } else if (a == 'R') { // red + digitalWrite(LED_RED, LOW); + } else if (a == 'Q') { + quiet = false; + } + } +}