From e910c28ed80c7ddd9e020255062cf0caa0176667 Mon Sep 17 00:00:00 2001 From: Daniel Perron Date: Fri, 20 Sep 2019 15:12:02 -0400 Subject: [PATCH 01/26] Update README.md --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index ff50f49..d4ca12c 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,12 @@ # micropython-mfrc522 (Micro)Python class to access the MFRC522 RFID reader +Sept 20 2019 +Modification to be able to read 7 and 10 BYTES RFID +P.S. I didn't test the write mode. + + + Basic class to access RFID readers of the type [MFRC522](http://www.nxp.com/documents/data_sheet/MFRC522.pdf). This is basically a re-write of [this](https://github.com/mxgxw/MFRC522-python) Python port for the MFRC522. I tried to strip things down and make them more "pythonic" so the result is small enough to run on From 950cdee303fa3921a17b79678063ecccc86f79e5 Mon Sep 17 00:00:00 2001 From: danjperron Date: Fri, 20 Sep 2019 15:16:54 -0400 Subject: [PATCH 02/26] add 7/10 bytes ID --- examples/read.py | 87 ++++---- examples/write.py | 50 ----- mfrc522.py | 522 ++++++++++++++++++++++++++-------------------- 3 files changed, 334 insertions(+), 325 deletions(-) delete mode 100644 examples/write.py diff --git a/examples/read.py b/examples/read.py index 783d554..845ef37 100644 --- a/examples/read.py +++ b/examples/read.py @@ -1,46 +1,41 @@ -import mfrc522 -from os import uname - - -def do_read(): - - if uname()[0] == 'WiPy': - rdr = mfrc522.MFRC522("GP14", "GP16", "GP15", "GP22", "GP17") - elif uname()[0] == 'esp8266': - rdr = mfrc522.MFRC522(0, 2, 4, 5, 14) - else: - raise RuntimeError("Unsupported platform") - - print("") - print("Place card before reader to read from address 0x08") - print("") - - try: - while True: - - (stat, tag_type) = rdr.request(rdr.REQIDL) - - if stat == rdr.OK: - - (stat, raw_uid) = rdr.anticoll() - - if stat == rdr.OK: - print("New card detected") - print(" - tag type: 0x%02x" % tag_type) - print(" - uid : 0x%02x%02x%02x%02x" % (raw_uid[0], raw_uid[1], raw_uid[2], raw_uid[3])) - print("") - - if rdr.select_tag(raw_uid) == rdr.OK: - - key = [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF] - - if rdr.auth(rdr.AUTHENT1A, 8, key, raw_uid) == rdr.OK: - print("Address 8 data: %s" % rdr.read(8)) - rdr.stop_crypto1() - else: - print("Authentication error") - else: - print("Failed to select tag") - - except KeyboardInterrupt: - print("Bye") \ No newline at end of file +import mfrc522 +from os import uname + + +def uidToString(uid): + mystring = "" + for i in uid: + mystring = "%02X" % i + mystring + return mystring + +def do_read(): + + if uname()[0] == 'WiPy': + rdr = mfrc522.MFRC522("GP14", "GP16", "GP15", "GP22", "GP17") + elif uname()[0] == 'esp32': + rdr = mfrc522.MFRC522(sck=18,mosi=23,miso=19,rst=22,cs=21) + else: + raise RuntimeError("Unsupported platform") + + print("") + print("Place card before reader to read from address 0x08") + print("") + + try: + while True: + + (stat, tag_type) = rdr.request(rdr.REQIDL) + + if stat == rdr.OK: + + (stat, uid) = rdr.SelectTagSN() + + if stat == rdr.OK: + print("Card detected %s" % uidToString(uid)) + else: + print("Authentication error") + + except KeyboardInterrupt: + print("Bye") + + diff --git a/examples/write.py b/examples/write.py deleted file mode 100644 index 7878d83..0000000 --- a/examples/write.py +++ /dev/null @@ -1,50 +0,0 @@ -import mfrc522 -from os import uname - - -def do_write(): - - if uname()[0] == 'WiPy': - rdr = mfrc522.MFRC522("GP14", "GP16", "GP15", "GP22", "GP17") - elif uname()[0] == 'esp8266': - rdr = mfrc522.MFRC522(0, 2, 4, 5, 14) - else: - raise RuntimeError("Unsupported platform") - - print("") - print("Place card before reader to write address 0x08") - print("") - - try: - while True: - - (stat, tag_type) = rdr.request(rdr.REQIDL) - - if stat == rdr.OK: - - (stat, raw_uid) = rdr.anticoll() - - if stat == rdr.OK: - print("New card detected") - print(" - tag type: 0x%02x" % tag_type) - print(" - uid : 0x%02x%02x%02x%02x" % (raw_uid[0], raw_uid[1], raw_uid[2], raw_uid[3])) - print("") - - if rdr.select_tag(raw_uid) == rdr.OK: - - key = [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF] - - if rdr.auth(rdr.AUTHENT1A, 8, key, raw_uid) == rdr.OK: - stat = rdr.write(8, b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f") - rdr.stop_crypto1() - if stat == rdr.OK: - print("Data written to card") - else: - print("Failed to write data to card") - else: - print("Authentication error") - else: - print("Failed to select tag") - - except KeyboardInterrupt: - print("Bye") diff --git a/mfrc522.py b/mfrc522.py index 1833d75..c603b58 100644 --- a/mfrc522.py +++ b/mfrc522.py @@ -1,229 +1,293 @@ -from machine import Pin, SPI -from os import uname - - -class MFRC522: - - OK = 0 - NOTAGERR = 1 - ERR = 2 - - REQIDL = 0x26 - REQALL = 0x52 - AUTHENT1A = 0x60 - AUTHENT1B = 0x61 - - def __init__(self, sck, mosi, miso, rst, cs): - - self.sck = Pin(sck, Pin.OUT) - self.mosi = Pin(mosi, Pin.OUT) - self.miso = Pin(miso) - self.rst = Pin(rst, Pin.OUT) - self.cs = Pin(cs, Pin.OUT) - - self.rst.value(0) - self.cs.value(1) - - board = uname()[0] - - if board == 'WiPy' or board == 'LoPy' or board == 'FiPy': - self.spi = SPI(0) - self.spi.init(SPI.MASTER, baudrate=1000000, pins=(self.sck, self.mosi, self.miso)) - elif board == 'esp8266': - self.spi = SPI(baudrate=100000, polarity=0, phase=0, sck=self.sck, mosi=self.mosi, miso=self.miso) - self.spi.init() - else: - raise RuntimeError("Unsupported platform") - - self.rst.value(1) - self.init() - - def _wreg(self, reg, val): - - self.cs.value(0) - self.spi.write(b'%c' % int(0xff & ((reg << 1) & 0x7e))) - self.spi.write(b'%c' % int(0xff & val)) - self.cs.value(1) - - def _rreg(self, reg): - - self.cs.value(0) - self.spi.write(b'%c' % int(0xff & (((reg << 1) & 0x7e) | 0x80))) - val = self.spi.read(1) - self.cs.value(1) - - return val[0] - - def _sflags(self, reg, mask): - self._wreg(reg, self._rreg(reg) | mask) - - def _cflags(self, reg, mask): - self._wreg(reg, self._rreg(reg) & (~mask)) - - def _tocard(self, cmd, send): - - recv = [] - bits = irq_en = wait_irq = n = 0 - stat = self.ERR - - if cmd == 0x0E: - irq_en = 0x12 - wait_irq = 0x10 - elif cmd == 0x0C: - irq_en = 0x77 - wait_irq = 0x30 - - self._wreg(0x02, irq_en | 0x80) - self._cflags(0x04, 0x80) - self._sflags(0x0A, 0x80) - self._wreg(0x01, 0x00) - - for c in send: - self._wreg(0x09, c) - self._wreg(0x01, cmd) - - if cmd == 0x0C: - self._sflags(0x0D, 0x80) - - i = 2000 - while True: - n = self._rreg(0x04) - i -= 1 - if ~((i != 0) and ~(n & 0x01) and ~(n & wait_irq)): - break - - self._cflags(0x0D, 0x80) - - if i: - if (self._rreg(0x06) & 0x1B) == 0x00: - stat = self.OK - - if n & irq_en & 0x01: - stat = self.NOTAGERR - elif cmd == 0x0C: - n = self._rreg(0x0A) - lbits = self._rreg(0x0C) & 0x07 - if lbits != 0: - bits = (n - 1) * 8 + lbits - else: - bits = n * 8 - - if n == 0: - n = 1 - elif n > 16: - n = 16 - - for _ in range(n): - recv.append(self._rreg(0x09)) - else: - stat = self.ERR - - return stat, recv, bits - - def _crc(self, data): - - self._cflags(0x05, 0x04) - self._sflags(0x0A, 0x80) - - for c in data: - self._wreg(0x09, c) - - self._wreg(0x01, 0x03) - - i = 0xFF - while True: - n = self._rreg(0x05) - i -= 1 - if not ((i != 0) and not (n & 0x04)): - break - - return [self._rreg(0x22), self._rreg(0x21)] - - def init(self): - - self.reset() - self._wreg(0x2A, 0x8D) - self._wreg(0x2B, 0x3E) - self._wreg(0x2D, 30) - self._wreg(0x2C, 0) - self._wreg(0x15, 0x40) - self._wreg(0x11, 0x3D) - self.antenna_on() - - def reset(self): - self._wreg(0x01, 0x0F) - - def antenna_on(self, on=True): - - if on and ~(self._rreg(0x14) & 0x03): - self._sflags(0x14, 0x03) - else: - self._cflags(0x14, 0x03) - - def request(self, mode): - - self._wreg(0x0D, 0x07) - (stat, recv, bits) = self._tocard(0x0C, [mode]) - - if (stat != self.OK) | (bits != 0x10): - stat = self.ERR - - return stat, bits - - def anticoll(self): - - ser_chk = 0 - ser = [0x93, 0x20] - - self._wreg(0x0D, 0x00) - (stat, recv, bits) = self._tocard(0x0C, ser) - - if stat == self.OK: - if len(recv) == 5: - for i in range(4): - ser_chk = ser_chk ^ recv[i] - if ser_chk != recv[4]: - stat = self.ERR - else: - stat = self.ERR - - return stat, recv - - def select_tag(self, ser): - - buf = [0x93, 0x70] + ser[:5] - buf += self._crc(buf) - (stat, recv, bits) = self._tocard(0x0C, buf) - return self.OK if (stat == self.OK) and (bits == 0x18) else self.ERR - - def auth(self, mode, addr, sect, ser): - return self._tocard(0x0E, [mode, addr] + sect + ser[:4])[0] - - def stop_crypto1(self): - self._cflags(0x08, 0x08) - - def read(self, addr): - - data = [0x30, addr] - data += self._crc(data) - (stat, recv, _) = self._tocard(0x0C, data) - return recv if stat == self.OK else None - - def write(self, addr, data): - - buf = [0xA0, addr] - buf += self._crc(buf) - (stat, recv, bits) = self._tocard(0x0C, buf) - - if not (stat == self.OK) or not (bits == 4) or not ((recv[0] & 0x0F) == 0x0A): - stat = self.ERR - else: - buf = [] - for i in range(16): - buf.append(data[i]) - buf += self._crc(buf) - (stat, recv, bits) = self._tocard(0x0C, buf) - if not (stat == self.OK) or not (bits == 4) or not ((recv[0] & 0x0F) == 0x0A): - stat = self.ERR - - return stat +from machine import Pin, SPI +from os import uname + + +class MFRC522: + + DEBUG = False + OK = 0 + NOTAGERR = 1 + ERR = 2 + + REQIDL = 0x26 + REQALL = 0x52 + AUTHENT1A = 0x60 + AUTHENT1B = 0x61 + + PICC_ANTICOLL1 = 0x93 + PICC_ANTICOLL2 = 0x95 + PICC_ANTICOLL3 = 0x97 + + + def __init__(self, sck, mosi, miso, rst, cs): + + self.sck = Pin(sck, Pin.OUT) + self.mosi = Pin(mosi, Pin.OUT) + self.miso = Pin(miso) + self.rst = Pin(rst, Pin.OUT) + self.cs = Pin(cs, Pin.OUT) + + self.rst.value(0) + self.cs.value(1) + + board = uname()[0] + + if board == 'WiPy' or board == 'LoPy' or board == 'FiPy': + self.spi = SPI(0) + self.spi.init(SPI.MASTER, baudrate=1000000, pins=(self.sck, self.mosi, self.miso)) + elif (board == 'esp8266') or (board == 'esp32'): + self.spi = SPI(baudrate=100000, polarity=0, phase=0, sck=self.sck, mosi=self.mosi, miso=self.miso) + self.spi.init() + else: + raise RuntimeError("Unsupported platform") + + self.rst.value(1) + self.init() + + def _wreg(self, reg, val): + + self.cs.value(0) + self.spi.write(b'%c' % int(0xff & ((reg << 1) & 0x7e))) + self.spi.write(b'%c' % int(0xff & val)) + self.cs.value(1) + + def _rreg(self, reg): + + self.cs.value(0) + self.spi.write(b'%c' % int(0xff & (((reg << 1) & 0x7e) | 0x80))) + val = self.spi.read(1) + self.cs.value(1) + + return val[0] + + def _sflags(self, reg, mask): + self._wreg(reg, self._rreg(reg) | mask) + + def _cflags(self, reg, mask): + self._wreg(reg, self._rreg(reg) & (~mask)) + + def _tocard(self, cmd, send): + + recv = [] + bits = irq_en = wait_irq = n = 0 + stat = self.ERR + + if cmd == 0x0E: + irq_en = 0x12 + wait_irq = 0x10 + elif cmd == 0x0C: + irq_en = 0x77 + wait_irq = 0x30 + + self._wreg(0x02, irq_en | 0x80) + self._cflags(0x04, 0x80) + self._sflags(0x0A, 0x80) + self._wreg(0x01, 0x00) + + for c in send: + self._wreg(0x09, c) + self._wreg(0x01, cmd) + + if cmd == 0x0C: + self._sflags(0x0D, 0x80) + + i = 2000 + while True: + n = self._rreg(0x04) + i -= 1 + if ~((i != 0) and ~(n & 0x01) and ~(n & wait_irq)): + break + + self._cflags(0x0D, 0x80) + + if i: + if (self._rreg(0x06) & 0x1B) == 0x00: + stat = self.OK + + if n & irq_en & 0x01: + stat = self.NOTAGERR + elif cmd == 0x0C: + n = self._rreg(0x0A) + lbits = self._rreg(0x0C) & 0x07 + if lbits != 0: + bits = (n - 1) * 8 + lbits + else: + bits = n * 8 + + if n == 0: + n = 1 + elif n > 16: + n = 16 + + for _ in range(n): + recv.append(self._rreg(0x09)) + else: + stat = self.ERR + + return stat, recv, bits + + def _crc(self, data): + + self._cflags(0x05, 0x04) + self._sflags(0x0A, 0x80) + + for c in data: + self._wreg(0x09, c) + + self._wreg(0x01, 0x03) + + i = 0xFF + while True: + n = self._rreg(0x05) + i -= 1 + if not ((i != 0) and not (n & 0x04)): + break + + return [self._rreg(0x22), self._rreg(0x21)] + + def init(self): + + self.reset() + self._wreg(0x2A, 0x8D) + self._wreg(0x2B, 0x3E) + self._wreg(0x2D, 30) + self._wreg(0x2C, 0) + self._wreg(0x15, 0x40) + self._wreg(0x11, 0x3D) + self.antenna_on() + + def reset(self): + self._wreg(0x01, 0x0F) + + def antenna_on(self, on=True): + + if on and ~(self._rreg(0x14) & 0x03): + self._sflags(0x14, 0x03) + else: + self._cflags(0x14, 0x03) + + def request(self, mode): + + self._wreg(0x0D, 0x07) + (stat, recv, bits) = self._tocard(0x0C, [mode]) + + if (stat != self.OK) | (bits != 0x10): + stat = self.ERR + + return stat, bits + + def anticoll(self,anticolN): + + ser_chk = 0 + ser = [anticolN, 0x20] + + self._wreg(0x0D, 0x00) + (stat, recv, bits) = self._tocard(0x0C, ser) + + if stat == self.OK: + if len(recv) == 5: + for i in range(4): + ser_chk = ser_chk ^ recv[i] + if ser_chk != recv[4]: + stat = self.ERR + else: + stat = self.ERR + + return stat, recv + + + def PcdSelect(self, serNum,anticolN): + backData = [] + buf = [] + buf.append(anticolN) + buf.append(0x70) + i = 0 + while i<5: + buf.append(serNum[i]) + i = i + 1 + pOut = self._crc(buf) + buf.append(pOut[0]) + buf.append(pOut[1]) + (status, backData, backLen) = self._tocard( 0x0C, buf) + if (status == self.OK) and (backLen == 0x18): + return 1 + else: + return 0 + + + def SelectTagSN(self): + valid_uid=[] + (status,uid)= self.anticoll(self.PICC_ANTICOLL1) + if status != self.OK: + return (self.ERR,[]) + + if self.DEBUG: print("anticol(1) {}".format(uid)) + if self.PcdSelect(uid,self.PICC_ANTICOLL1) == 0: + return (self.MI_ERR,[]) + if self.DEBUG: print("pcdSelect(1) {}".format(uid)) + + #check if first byte is 0x88 + if uid[0] == 0x88 : + #ok we have another type of card + valid_uid.extend(uid[1:4]) + (status,uid)=self.anticoll(self.PICC_ANTICOLL2) + if status != self.OK: + return (self.ERR,[]) + if self.DEBUG: print("Anticol(2) {}".format(uid)) + rtn = self.PcdSelect(uid,self.PICC_ANTICOLL2) + if self.DEBUG: print("pcdSelect(2) return={} uid={}".format(rtn,uid)) + if rtn == 0: + return (self.ERR,[]) + if self.DEBUG: print("PcdSelect2() {}".format(uid)) + #now check again if uid[0] is 0x88 + if uid[0] == 0x88 : + valid_uid.extend(uid[1:4]) + (status , uid) = self.anticoll(self.PICC_ANTICOLL3) + if status != self.OK: + return (self.ERR,[]) + if self.DEBUG: print("Anticol(3) {}".format(uid)) + if self.MFRC522_PcdSelect(uid,self.PICC_ANTICOLL3) == 0: + return (self.ERR,[]) + if self.DEBUG: print("PcdSelect(3) {}".format(uid)) + valid_uid.extend(uid[0:4]) + return (self.OK , valid_uid) + + + + + + + def auth(self, mode, addr, sect, ser): + return self._tocard(0x0E, [mode, addr] + sect + ser[:4])[0] + + def stop_crypto1(self): + self._cflags(0x08, 0x08) + + def read(self, addr): + + data = [0x30, addr] + data += self._crc(data) + (stat, recv, _) = self._tocard(0x0C, data) + return recv if stat == self.OK else None + + def write(self, addr, data): + + buf = [0xA0, addr] + buf += self._crc(buf) + (stat, recv, bits) = self._tocard(0x0C, buf) + + if not (stat == self.OK) or not (bits == 4) or not ((recv[0] & 0x0F) == 0x0A): + stat = self.ERR + else: + buf = [] + for i in range(16): + buf.append(data[i]) + buf += self._crc(buf) + (stat, recv, bits) = self._tocard(0x0C, buf) + if not (stat == self.OK) or not (bits == 4) or not ((recv[0] & 0x0F) == 0x0A): + stat = self.ERR + + return stat + + + From b628e61983992c38c962fd9a42af1defbf33cc25 Mon Sep 17 00:00:00 2001 From: Daniel Perron Date: Fri, 20 Sep 2019 15:22:06 -0400 Subject: [PATCH 03/26] Update README.md --- README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index d4ca12c..f29aaf5 100644 --- a/README.md +++ b/README.md @@ -22,13 +22,13 @@ For the ESP8266 there are multiple solutions to do that. E.g. use the I used the following pins for my setup: -| Signal | GPIO ESP8266 | GPIO WiPy | Note | -| --------- | ------------ | -------------- | ------------------------------------ | -| sck | 0 | "GP14" | | -| mosi | 2 | "GP16" | | -| miso | 4 | "GP15" | | -| rst | 5 | "GP22" | | -| cs | 14 | "GP14" |Labeled SDA on most RFID-RC522 boards | +| Signal | GPIO ESP8266 | GPIO ESP32 | GPIO WiPy | Note | +| --------- | ------------ | ---------- | -------------- | ------------------------------------ | +| sck | 0 | 18 | "GP14" | | +| mosi | 2 | 23 | "GP16" | | +| miso | 4 | 19 | "GP15" | | +| rst | 5 | 22 | "GP22" | | +| cs | 14 | 21 | "GP14" |Labeled SDA on most RFID-RC522 boards | Now enter the REPL you could run one of the two exmaples: From 77071b6fd3878dcdf63be537684a95f1b2cc7c7b Mon Sep 17 00:00:00 2001 From: danjperron Date: Tue, 9 Feb 2021 22:20:54 -0500 Subject: [PATCH 04/26] Add Pico --- examples/Pico_read.py | 34 ++++++++++++++++++++++++++++++++++ mfrc522.py | 4 +++- 2 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 examples/Pico_read.py diff --git a/examples/Pico_read.py b/examples/Pico_read.py new file mode 100644 index 0000000..541c8b3 --- /dev/null +++ b/examples/Pico_read.py @@ -0,0 +1,34 @@ +from mfrc522 import MFRC522 + + +def uidToString(uid): + mystring = "" + for i in uid: + mystring = "%02X" % i + mystring + return mystring + + +reader = MFRC522(spi_id=0,sck=2,miso=4,mosi=3,cs=1,rst=0) + +print("") +print("Place card before reader to read from address 0x08") +print("") + +try: + while True: + + (stat, tag_type) = reader.request(reader.REQIDL) + + if stat == reader.OK: + + (stat, uid) = reader.SelectTagSN() + + if stat == reader.OK: + print("Card detected %s" % uidToString(uid)) + else: + print("Authentication error") + +except KeyboardInterrupt: + print("Bye") + + diff --git a/mfrc522.py b/mfrc522.py index c603b58..244850e 100644 --- a/mfrc522.py +++ b/mfrc522.py @@ -19,7 +19,7 @@ class MFRC522: PICC_ANTICOLL3 = 0x97 - def __init__(self, sck, mosi, miso, rst, cs): + def __init__(self, sck, mosi, miso, rst, cs,baudrate=1000000,spi_id=0): self.sck = Pin(sck, Pin.OUT) self.mosi = Pin(mosi, Pin.OUT) @@ -38,6 +38,8 @@ def __init__(self, sck, mosi, miso, rst, cs): elif (board == 'esp8266') or (board == 'esp32'): self.spi = SPI(baudrate=100000, polarity=0, phase=0, sck=self.sck, mosi=self.mosi, miso=self.miso) self.spi.init() + elif board == 'rp2': + self.spi = SPI(spi_id,baudrate=baudrate,sck=self.sck, mosi= self.mosi, miso= self.miso) else: raise RuntimeError("Unsupported platform") From 4425620918e129085a3fd8577b05fe81743bce85 Mon Sep 17 00:00:00 2001 From: Daniel Perron Date: Wed, 10 Feb 2021 14:59:34 -0500 Subject: [PATCH 05/26] Fix self.ERR line 228 Fix line 228 self.Err instead of self.MI_Err --- mfrc522.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mfrc522.py b/mfrc522.py index 244850e..3cf7b4b 100644 --- a/mfrc522.py +++ b/mfrc522.py @@ -225,7 +225,7 @@ def SelectTagSN(self): if self.DEBUG: print("anticol(1) {}".format(uid)) if self.PcdSelect(uid,self.PICC_ANTICOLL1) == 0: - return (self.MI_ERR,[]) + return (self.ERR,[]) if self.DEBUG: print("pcdSelect(1) {}".format(uid)) #check if first byte is 0x88 From 6dd14764b9a9eb5d262862e0886caddfd602e0a1 Mon Sep 17 00:00:00 2001 From: danjperron Date: Mon, 15 Feb 2021 12:09:53 -0500 Subject: [PATCH 06/26] add write example --- examples/Pico_write.py | 48 ++++ mfrc522.py | 492 +++++++++++++++++++++-------------------- 2 files changed, 301 insertions(+), 239 deletions(-) create mode 100644 examples/Pico_write.py diff --git a/examples/Pico_write.py b/examples/Pico_write.py new file mode 100644 index 0000000..380f4a2 --- /dev/null +++ b/examples/Pico_write.py @@ -0,0 +1,48 @@ +from mfrc522 import MFRC522 + + +def uidToString(uid): + mystring = "" + for i in uid: + mystring = "%02X" % i + mystring + return mystring + +reader = MFRC522(spi_id=0,sck=2,miso=4,mosi=3,cs=1,rst=0) + +print("") +print("Place card before reader to read from address 0x08") +print("") + +key = [0xFF,0xFF,0xFF,0xFF,0xFF,0xFF] + +try: + while True: + + (stat, tag_type) = reader.request(reader.REQIDL) + + if stat == reader.OK: + (stat, uid) = reader.SelectTagSN() + if stat == reader.OK: + print(uid) + print("Card detected %s" % uidToString(uid)) + reader.MFRC522_DumpClassic1K(key, uid) + print("Test ! writing sector 2, block 0 (absolute block(8)") + print("with [ 00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 ]") + absoluteBlock=8 + value=[] + for i in range(16): + value.append(i) + status = reader.auth(reader.AUTHENT1A, absoluteBlock, key, uid) + if status == reader.OK: + status = reader.write(absoluteBlock,value) + if status == reader.OK: + reader.MFRC522_DumpClassic1K(key, uid) + else: + print("unable to write") + else: + print("Authentication error for writing") + break +except KeyboardInterrupt: + print("Bye") + + diff --git a/mfrc522.py b/mfrc522.py index 3cf7b4b..ff53fe8 100644 --- a/mfrc522.py +++ b/mfrc522.py @@ -4,292 +4,306 @@ class MFRC522: - DEBUG = False - OK = 0 - NOTAGERR = 1 - ERR = 2 - - REQIDL = 0x26 - REQALL = 0x52 - AUTHENT1A = 0x60 - AUTHENT1B = 0x61 + DEBUG = False + OK = 0 + NOTAGERR = 1 + ERR = 2 + + REQIDL = 0x26 + REQALL = 0x52 + AUTHENT1A = 0x60 + AUTHENT1B = 0x61 - PICC_ANTICOLL1 = 0x93 - PICC_ANTICOLL2 = 0x95 - PICC_ANTICOLL3 = 0x97 + PICC_ANTICOLL1 = 0x93 + PICC_ANTICOLL2 = 0x95 + PICC_ANTICOLL3 = 0x97 - def __init__(self, sck, mosi, miso, rst, cs,baudrate=1000000,spi_id=0): - - self.sck = Pin(sck, Pin.OUT) - self.mosi = Pin(mosi, Pin.OUT) - self.miso = Pin(miso) - self.rst = Pin(rst, Pin.OUT) - self.cs = Pin(cs, Pin.OUT) - - self.rst.value(0) - self.cs.value(1) - - board = uname()[0] - - if board == 'WiPy' or board == 'LoPy' or board == 'FiPy': - self.spi = SPI(0) - self.spi.init(SPI.MASTER, baudrate=1000000, pins=(self.sck, self.mosi, self.miso)) - elif (board == 'esp8266') or (board == 'esp32'): - self.spi = SPI(baudrate=100000, polarity=0, phase=0, sck=self.sck, mosi=self.mosi, miso=self.miso) - self.spi.init() - elif board == 'rp2': - self.spi = SPI(spi_id,baudrate=baudrate,sck=self.sck, mosi= self.mosi, miso= self.miso) - else: - raise RuntimeError("Unsupported platform") - - self.rst.value(1) - self.init() - - def _wreg(self, reg, val): + def __init__(self, sck, mosi, miso, rst, cs,baudrate=1000000,spi_id=0): + + self.sck = Pin(sck, Pin.OUT) + self.mosi = Pin(mosi, Pin.OUT) + self.miso = Pin(miso) + self.rst = Pin(rst, Pin.OUT) + self.cs = Pin(cs, Pin.OUT) + + self.rst.value(0) + self.cs.value(1) + + board = uname()[0] + + if board == 'WiPy' or board == 'LoPy' or board == 'FiPy': + self.spi = SPI(0) + self.spi.init(SPI.MASTER, baudrate=1000000, pins=(self.sck, self.mosi, self.miso)) + elif (board == 'esp8266') or (board == 'esp32'): + self.spi = SPI(baudrate=100000, polarity=0, phase=0, sck=self.sck, mosi=self.mosi, miso=self.miso) + self.spi.init() + elif board == 'rp2': + self.spi = SPI(spi_id,baudrate=baudrate,sck=self.sck, mosi= self.mosi, miso= self.miso) + else: + raise RuntimeError("Unsupported platform") + + self.rst.value(1) + self.init() + + def _wreg(self, reg, val): - self.cs.value(0) - self.spi.write(b'%c' % int(0xff & ((reg << 1) & 0x7e))) - self.spi.write(b'%c' % int(0xff & val)) - self.cs.value(1) + self.cs.value(0) + self.spi.write(b'%c' % int(0xff & ((reg << 1) & 0x7e))) + self.spi.write(b'%c' % int(0xff & val)) + self.cs.value(1) - def _rreg(self, reg): - - self.cs.value(0) - self.spi.write(b'%c' % int(0xff & (((reg << 1) & 0x7e) | 0x80))) - val = self.spi.read(1) - self.cs.value(1) - - return val[0] - - def _sflags(self, reg, mask): - self._wreg(reg, self._rreg(reg) | mask) + def _rreg(self, reg): + + self.cs.value(0) + self.spi.write(b'%c' % int(0xff & (((reg << 1) & 0x7e) | 0x80))) + val = self.spi.read(1) + self.cs.value(1) + + return val[0] + + def _sflags(self, reg, mask): + self._wreg(reg, self._rreg(reg) | mask) - def _cflags(self, reg, mask): - self._wreg(reg, self._rreg(reg) & (~mask)) + def _cflags(self, reg, mask): + self._wreg(reg, self._rreg(reg) & (~mask)) - def _tocard(self, cmd, send): + def _tocard(self, cmd, send): - recv = [] - bits = irq_en = wait_irq = n = 0 - stat = self.ERR + recv = [] + bits = irq_en = wait_irq = n = 0 + stat = self.ERR - if cmd == 0x0E: - irq_en = 0x12 - wait_irq = 0x10 - elif cmd == 0x0C: - irq_en = 0x77 - wait_irq = 0x30 + if cmd == 0x0E: + irq_en = 0x12 + wait_irq = 0x10 + elif cmd == 0x0C: + irq_en = 0x77 + wait_irq = 0x30 - self._wreg(0x02, irq_en | 0x80) - self._cflags(0x04, 0x80) - self._sflags(0x0A, 0x80) - self._wreg(0x01, 0x00) + self._wreg(0x02, irq_en | 0x80) + self._cflags(0x04, 0x80) + self._sflags(0x0A, 0x80) + self._wreg(0x01, 0x00) - for c in send: - self._wreg(0x09, c) - self._wreg(0x01, cmd) + for c in send: + self._wreg(0x09, c) + self._wreg(0x01, cmd) - if cmd == 0x0C: - self._sflags(0x0D, 0x80) + if cmd == 0x0C: + self._sflags(0x0D, 0x80) - i = 2000 - while True: - n = self._rreg(0x04) - i -= 1 - if ~((i != 0) and ~(n & 0x01) and ~(n & wait_irq)): - break + i = 2000 + while True: + n = self._rreg(0x04) + i -= 1 + if ~((i != 0) and ~(n & 0x01) and ~(n & wait_irq)): + break - self._cflags(0x0D, 0x80) + self._cflags(0x0D, 0x80) - if i: - if (self._rreg(0x06) & 0x1B) == 0x00: - stat = self.OK + if i: + if (self._rreg(0x06) & 0x1B) == 0x00: + stat = self.OK - if n & irq_en & 0x01: - stat = self.NOTAGERR - elif cmd == 0x0C: - n = self._rreg(0x0A) - lbits = self._rreg(0x0C) & 0x07 - if lbits != 0: - bits = (n - 1) * 8 + lbits - else: - bits = n * 8 + if n & irq_en & 0x01: + stat = self.NOTAGERR + elif cmd == 0x0C: + n = self._rreg(0x0A) + lbits = self._rreg(0x0C) & 0x07 + if lbits != 0: + bits = (n - 1) * 8 + lbits + else: + bits = n * 8 - if n == 0: - n = 1 - elif n > 16: - n = 16 + if n == 0: + n = 1 + elif n > 16: + n = 16 - for _ in range(n): - recv.append(self._rreg(0x09)) - else: - stat = self.ERR + for _ in range(n): + recv.append(self._rreg(0x09)) + else: + stat = self.ERR - return stat, recv, bits + return stat, recv, bits - def _crc(self, data): + def _crc(self, data): - self._cflags(0x05, 0x04) - self._sflags(0x0A, 0x80) + self._cflags(0x05, 0x04) + self._sflags(0x0A, 0x80) - for c in data: - self._wreg(0x09, c) + for c in data: + self._wreg(0x09, c) - self._wreg(0x01, 0x03) + self._wreg(0x01, 0x03) - i = 0xFF - while True: - n = self._rreg(0x05) - i -= 1 - if not ((i != 0) and not (n & 0x04)): - break + i = 0xFF + while True: + n = self._rreg(0x05) + i -= 1 + if not ((i != 0) and not (n & 0x04)): + break - return [self._rreg(0x22), self._rreg(0x21)] + return [self._rreg(0x22), self._rreg(0x21)] - def init(self): + def init(self): - self.reset() - self._wreg(0x2A, 0x8D) - self._wreg(0x2B, 0x3E) - self._wreg(0x2D, 30) - self._wreg(0x2C, 0) - self._wreg(0x15, 0x40) - self._wreg(0x11, 0x3D) - self.antenna_on() + self.reset() + self._wreg(0x2A, 0x8D) + self._wreg(0x2B, 0x3E) + self._wreg(0x2D, 30) + self._wreg(0x2C, 0) + self._wreg(0x15, 0x40) + self._wreg(0x11, 0x3D) + self.antenna_on() - def reset(self): - self._wreg(0x01, 0x0F) + def reset(self): + self._wreg(0x01, 0x0F) - def antenna_on(self, on=True): + def antenna_on(self, on=True): - if on and ~(self._rreg(0x14) & 0x03): - self._sflags(0x14, 0x03) - else: - self._cflags(0x14, 0x03) + if on and ~(self._rreg(0x14) & 0x03): + self._sflags(0x14, 0x03) + else: + self._cflags(0x14, 0x03) - def request(self, mode): + def request(self, mode): - self._wreg(0x0D, 0x07) - (stat, recv, bits) = self._tocard(0x0C, [mode]) + self._wreg(0x0D, 0x07) + (stat, recv, bits) = self._tocard(0x0C, [mode]) - if (stat != self.OK) | (bits != 0x10): - stat = self.ERR + if (stat != self.OK) | (bits != 0x10): + stat = self.ERR - return stat, bits + return stat, bits - def anticoll(self,anticolN): + def anticoll(self,anticolN): - ser_chk = 0 - ser = [anticolN, 0x20] + ser_chk = 0 + ser = [anticolN, 0x20] - self._wreg(0x0D, 0x00) - (stat, recv, bits) = self._tocard(0x0C, ser) + self._wreg(0x0D, 0x00) + (stat, recv, bits) = self._tocard(0x0C, ser) - if stat == self.OK: - if len(recv) == 5: - for i in range(4): - ser_chk = ser_chk ^ recv[i] - if ser_chk != recv[4]: - stat = self.ERR - else: - stat = self.ERR + if stat == self.OK: + if len(recv) == 5: + for i in range(4): + ser_chk = ser_chk ^ recv[i] + if ser_chk != recv[4]: + stat = self.ERR + else: + stat = self.ERR - return stat, recv + return stat, recv - def PcdSelect(self, serNum,anticolN): - backData = [] - buf = [] - buf.append(anticolN) - buf.append(0x70) - i = 0 - while i<5: - buf.append(serNum[i]) - i = i + 1 - pOut = self._crc(buf) - buf.append(pOut[0]) - buf.append(pOut[1]) - (status, backData, backLen) = self._tocard( 0x0C, buf) - if (status == self.OK) and (backLen == 0x18): - return 1 - else: - return 0 + def PcdSelect(self, serNum,anticolN): + backData = [] + buf = [] + buf.append(anticolN) + buf.append(0x70) + i = 0 + while i<5: + buf.append(serNum[i]) + i = i + 1 + pOut = self._crc(buf) + buf.append(pOut[0]) + buf.append(pOut[1]) + (status, backData, backLen) = self._tocard( 0x0C, buf) + if (status == self.OK) and (backLen == 0x18): + return 1 + else: + return 0 - def SelectTagSN(self): - valid_uid=[] - (status,uid)= self.anticoll(self.PICC_ANTICOLL1) - if status != self.OK: - return (self.ERR,[]) - - if self.DEBUG: print("anticol(1) {}".format(uid)) - if self.PcdSelect(uid,self.PICC_ANTICOLL1) == 0: - return (self.ERR,[]) - if self.DEBUG: print("pcdSelect(1) {}".format(uid)) - - #check if first byte is 0x88 - if uid[0] == 0x88 : - #ok we have another type of card - valid_uid.extend(uid[1:4]) - (status,uid)=self.anticoll(self.PICC_ANTICOLL2) - if status != self.OK: - return (self.ERR,[]) - if self.DEBUG: print("Anticol(2) {}".format(uid)) - rtn = self.PcdSelect(uid,self.PICC_ANTICOLL2) - if self.DEBUG: print("pcdSelect(2) return={} uid={}".format(rtn,uid)) - if rtn == 0: - return (self.ERR,[]) - if self.DEBUG: print("PcdSelect2() {}".format(uid)) - #now check again if uid[0] is 0x88 - if uid[0] == 0x88 : - valid_uid.extend(uid[1:4]) - (status , uid) = self.anticoll(self.PICC_ANTICOLL3) - if status != self.OK: - return (self.ERR,[]) - if self.DEBUG: print("Anticol(3) {}".format(uid)) - if self.MFRC522_PcdSelect(uid,self.PICC_ANTICOLL3) == 0: - return (self.ERR,[]) - if self.DEBUG: print("PcdSelect(3) {}".format(uid)) - valid_uid.extend(uid[0:4]) - return (self.OK , valid_uid) + def SelectTagSN(self): + valid_uid=[] + (status,uid)= self.anticoll(self.PICC_ANTICOLL1) + if status != self.OK: + return (self.ERR,[]) + + if self.DEBUG: print("anticol(1) {}".format(uid)) + if self.PcdSelect(uid,self.PICC_ANTICOLL1) == 0: + return (self.MI_ERR,[]) + if self.DEBUG: print("pcdSelect(1) {}".format(uid)) + + #check if first byte is 0x88 + if uid[0] == 0x88 : + #ok we have another type of card + valid_uid.extend(uid[1:4]) + (status,uid)=self.anticoll(self.PICC_ANTICOLL2) + if status != self.OK: + return (self.ERR,[]) + if self.DEBUG: print("Anticol(2) {}".format(uid)) + rtn = self.PcdSelect(uid,self.PICC_ANTICOLL2) + if self.DEBUG: print("pcdSelect(2) return={} uid={}".format(rtn,uid)) + if rtn == 0: + return (self.ERR,[]) + if self.DEBUG: print("PcdSelect2() {}".format(uid)) + #now check again if uid[0] is 0x88 + if uid[0] == 0x88 : + valid_uid.extend(uid[1:4]) + (status , uid) = self.anticoll(self.PICC_ANTICOLL3) + if status != self.OK: + return (self.ERR,[]) + if self.DEBUG: print("Anticol(3) {}".format(uid)) + if self.MFRC522_PcdSelect(uid,self.PICC_ANTICOLL3) == 0: + return (self.ERR,[]) + if self.DEBUG: print("PcdSelect(3) {}".format(uid)) + valid_uid.extend(uid[0:5]) + return (self.OK , valid_uid) - def auth(self, mode, addr, sect, ser): - return self._tocard(0x0E, [mode, addr] + sect + ser[:4])[0] - - def stop_crypto1(self): - self._cflags(0x08, 0x08) - - def read(self, addr): - - data = [0x30, addr] - data += self._crc(data) - (stat, recv, _) = self._tocard(0x0C, data) - return recv if stat == self.OK else None - - def write(self, addr, data): - - buf = [0xA0, addr] - buf += self._crc(buf) - (stat, recv, bits) = self._tocard(0x0C, buf) - - if not (stat == self.OK) or not (bits == 4) or not ((recv[0] & 0x0F) == 0x0A): - stat = self.ERR - else: - buf = [] - for i in range(16): - buf.append(data[i]) - buf += self._crc(buf) - (stat, recv, bits) = self._tocard(0x0C, buf) - if not (stat == self.OK) or not (bits == 4) or not ((recv[0] & 0x0F) == 0x0A): - stat = self.ERR - - return stat - - + def auth(self, mode, addr, sect, ser): + return self._tocard(0x0E, [mode, addr] + sect + ser[:4])[0] + + def stop_crypto1(self): + self._cflags(0x08, 0x08) + + def read(self, addr): + + data = [0x30, addr] + data += self._crc(data) + (stat, recv, _) = self._tocard(0x0C, data) + return recv if stat == self.OK else None + + def write(self, addr, data): + + buf = [0xA0, addr] + buf += self._crc(buf) + (stat, recv, bits) = self._tocard(0x0C, buf) + + if not (stat == self.OK) or not (bits == 4) or not ((recv[0] & 0x0F) == 0x0A): + stat = self.ERR + else: + buf = [] + for i in range(16): + buf.append(data[i]) + buf += self._crc(buf) + (stat, recv, bits) = self._tocard(0x0C, buf) + if not (stat == self.OK) or not (bits == 4) or not ((recv[0] & 0x0F) == 0x0A): + stat = self.ERR + + return stat + + def MFRC522_DumpClassic1K(self, key, uid): + for i in range(64): + status = self.auth(self.AUTHENT1A, i, key, uid) + # Check if authenticated + print("{:02d}: ".format(i),end="") + if status == self.OK: + block = self.read(i) + if block is None: + print("-- ") + else: + for value in block: + print("{:02X} ".format(value),end="") + print("") + else: + print("Authentication error") From 11efed80c7feb19e22bf2affad320a1ea2ce0415 Mon Sep 17 00:00:00 2001 From: Daniel Perron Date: Mon, 15 Feb 2021 12:42:26 -0500 Subject: [PATCH 07/26] Update README.md --- README.md | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index f29aaf5..a5c5daf 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,11 @@ # micropython-mfrc522 (Micro)Python class to access the MFRC522 RFID reader +D.Perron +Feb. 2021 +Add Raspberry Pi Pico compatibility + +D.Perron Sept 20 2019 Modification to be able to read 7 and 10 BYTES RFID P.S. I didn't test the write mode. @@ -32,18 +37,11 @@ I used the following pins for my setup: Now enter the REPL you could run one of the two exmaples: -For detecting, authenticating and reading from a card: - - import read - read.do_read() - -This will wait for a MifareClassic 1k card. As soon the card is detected, it is authenticated, and -16 bytes are read from address 0x08. +The Pico_write example has been added but be aware of -For detecting, authenticating and writing to a card: +- The software treated the memory has 64 sectors of 16 Bytes. +- In reality the mifare card is 16 sectors of 4 block which are 16 byte. + The Block 0,1 and 2 are data block. The block 3 is the access block +- BE AWARE if you write sector (3,7,11,15...,63) then you should know what you do - import write - write.do_write() -This will wait for a MifareClassic 1k card. As soon the card is detected, it is authenticated, and -16 bytes written to address 0x08. From 73285c092767d7c09245424198b1490b7b2e6b85 Mon Sep 17 00:00:00 2001 From: Daniel Perron Date: Mon, 15 Feb 2021 12:46:02 -0500 Subject: [PATCH 08/26] Add notice on block access --- examples/Pico_write.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/examples/Pico_write.py b/examples/Pico_write.py index 380f4a2..cb1542e 100644 --- a/examples/Pico_write.py +++ b/examples/Pico_write.py @@ -1,5 +1,12 @@ from mfrc522 import MFRC522 +''' +BE AWARE that sectors(3,7,11,15,...,63) are access block. +if you want to change (sector % 4) == 3 you should +know how keys and permission work! +''' + + def uidToString(uid): mystring = "" From deb822825d11d03bd94b0d054f77464210072511 Mon Sep 17 00:00:00 2001 From: danjperron Date: Mon, 15 Feb 2021 20:18:54 -0500 Subject: [PATCH 09/26] remove extra iud xor checksum --- mfrc522.py | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/mfrc522.py b/mfrc522.py index ff53fe8..f73aec6 100644 --- a/mfrc522.py +++ b/mfrc522.py @@ -203,10 +203,13 @@ def PcdSelect(self, serNum,anticolN): buf = [] buf.append(anticolN) buf.append(0x70) - i = 0 - while i<5: - buf.append(serNum[i]) - i = i + 1 + #i = 0 + ###xorsum=0; + for i in serNum: + buf.append(i) + #while i<5: + # buf.append(serNum[i]) + # i = i + 1 pOut = self._crc(buf) buf.append(pOut[0]) buf.append(pOut[1]) @@ -225,7 +228,7 @@ def SelectTagSN(self): if self.DEBUG: print("anticol(1) {}".format(uid)) if self.PcdSelect(uid,self.PICC_ANTICOLL1) == 0: - return (self.MI_ERR,[]) + return (self.ERR,[]) if self.DEBUG: print("pcdSelect(1) {}".format(uid)) #check if first byte is 0x88 @@ -252,7 +255,11 @@ def SelectTagSN(self): return (self.ERR,[]) if self.DEBUG: print("PcdSelect(3) {}".format(uid)) valid_uid.extend(uid[0:5]) - return (self.OK , valid_uid) + # if we are here than the uid is ok + # let's remove the last BYTE whic is the XOR sum + + return (self.OK , valid_uid[:len(valid_uid)-1]) + #return (self.OK , valid_uid) @@ -261,6 +268,7 @@ def SelectTagSN(self): def auth(self, mode, addr, sect, ser): return self._tocard(0x0E, [mode, addr] + sect + ser[:4])[0] + def stop_crypto1(self): self._cflags(0x08, 0x08) @@ -291,8 +299,8 @@ def write(self, addr, data): return stat - def MFRC522_DumpClassic1K(self, key, uid): - for i in range(64): + def MFRC522_DumpClassic1K(self, key, uid, Start=0, End=64): + for i in range(Start,End): status = self.auth(self.AUTHENT1A, i, key, uid) # Check if authenticated print("{:02d}: ".format(i),end="") From 037be68272070107aaea04389a0a659aa40fabae Mon Sep 17 00:00:00 2001 From: Daniel Perron Date: Tue, 23 Feb 2021 19:07:27 -0500 Subject: [PATCH 10/26] Update README.md Add Pico pinout --- README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index a5c5daf..3757f74 100644 --- a/README.md +++ b/README.md @@ -27,13 +27,13 @@ For the ESP8266 there are multiple solutions to do that. E.g. use the I used the following pins for my setup: -| Signal | GPIO ESP8266 | GPIO ESP32 | GPIO WiPy | Note | -| --------- | ------------ | ---------- | -------------- | ------------------------------------ | -| sck | 0 | 18 | "GP14" | | -| mosi | 2 | 23 | "GP16" | | -| miso | 4 | 19 | "GP15" | | -| rst | 5 | 22 | "GP22" | | -| cs | 14 | 21 | "GP14" |Labeled SDA on most RFID-RC522 boards | +| Signal | GPIO ESP8266 | GPIO ESP32 | GPIO WiPy | GPIO Pico | Note | +| ------ | ------------ | ---------- | --------- | --------- | ----------------------------- | +| sck | 0 | 18 | "GP14" | "GP2" | | +| mosi | 2 | 23 | "GP16" | "GP3" | | +| miso | 4 | 19 | "GP15" | "GP4" | | +| rst | 5 | 22 | "GP22" | "GP0" | | +| cs | 14 | 21 | "GP14" | "GP1" | SDA on most RFID-RC522 boards | Now enter the REPL you could run one of the two exmaples: From 683af24b6e1d42706e8296f01f2d7282e8af0772 Mon Sep 17 00:00:00 2001 From: danjperron Date: Wed, 15 Sep 2021 12:48:10 -0400 Subject: [PATCH 11/26] add RfidAccess class --- Pico_example/CreateNdefTag.py | 91 ++++++++++ Pico_example/EraseNdefTag.py | 70 ++++++++ Pico_example/Pico_read.py | 45 +++++ {examples => Pico_example}/Pico_write.py | 6 +- Pico_example/ReadNdefTag.py | 47 +++++ RfidAccess.py | 209 +++++++++++++++++++++++ examples/Pico_read.py | 34 ---- mfrc522.py | 95 +++++++++-- 8 files changed, 546 insertions(+), 51 deletions(-) create mode 100644 Pico_example/CreateNdefTag.py create mode 100644 Pico_example/EraseNdefTag.py create mode 100644 Pico_example/Pico_read.py rename {examples => Pico_example}/Pico_write.py (86%) create mode 100644 Pico_example/ReadNdefTag.py create mode 100644 RfidAccess.py delete mode 100644 examples/Pico_read.py diff --git a/Pico_example/CreateNdefTag.py b/Pico_example/CreateNdefTag.py new file mode 100644 index 0000000..a6afd11 --- /dev/null +++ b/Pico_example/CreateNdefTag.py @@ -0,0 +1,91 @@ +from mfrc522 import MFRC522 +from RfidAccess import RfidAccess +import utime + + + + +reader = MFRC522(spi_id=0,sck=2,miso=4,mosi=3,cs=1,rst=0) +access = RfidAccess() + +print("") +print("Please place card on reader") +print("") + +def checksum(data): + crc = 0xc7 + for byte in data: + crc ^= byte + for _ in range(8): + msb = crc & 0x80 + crc = (crc << 1) & 0xff + if msb: + crc ^= 0x1d + return crc + + +PreviousCard = [0] + +try: + while True: + + reader.init() + (stat, tag_type) = reader.request(reader.REQIDL) + if stat == reader.OK: + (stat, uid) = reader.SelectTagSN() + if uid == PreviousCard: + continue + if stat == reader.OK: + print("Card detected {} uid={}".format(hex(int.from_bytes(bytes(uid),"little",False)).upper(),reader.tohexstring(uid))) + defaultKey = [255,255,255,255,255,255] + firstSectorKey = [0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5] + nextSectorKey = [0xD3, 0xF7, 0xD3, 0xF7, 0xD3, 0xF7] + + #set MAD sector + # first fill block permission + access.setTrailerAccess(keyA_Write=access.KEYB,access_Read=access.KEYAB,access_Write=access.KEYB, + keyB_Read=access.NEVER,keyB_Write=access.KEYB) + access.setBlockAccess(access.ALLBLOCK, access_Read=access.KEYAB, access_Write=access.KEYB, + access_Inc=access.NEVER, access_Dec=access.NEVER) + block3 = access.fillBlock3(keyA=firstSectorKey,keyB=defaultKey) + #Write the sector access + if reader.writeSectorBlock(uid,0,3,block3,keyA=defaultKey) == reader.ERR: + print("Writing MAD sector failed!") + else: + print(".",end="") + b1 = [0x14,0x01,0x03,0xE1,0x03,0xE1,0x03,0xE1,0x03,0xE1,0x03,0xE1,0x03,0xE1,0x03,0xE1] + b1[0] = checksum(b1[1:]) # I know this is already ok but just to demonstrate the CRC + reader.writeSectorBlock(uid,0,1,b1,keyB=defaultKey) + b2 = [0x03,0xE1,0x03,0xE1,0x03,0xE1,0x03,0xE1,0x03,0xE1,0x03,0xE1,0x03,0xE1,0x03,0xE1] + reader.writeSectorBlock(uid,0,2,b1,keyB=defaultKey) + #set permission for all other sectors + access.setTrailerAccess(keyA_Write=access.KEYB,access_Read=access.KEYAB,access_Write=access.KEYB, + keyB_Read=access.NEVER,keyB_Write=access.KEYB) + access.setBlockAccess(access.ALLBLOCK, access_Read=access.KEYAB, access_Write=access.KEYAB, + access_Inc=access.KEYAB, access_Dec=access.KEYAB) + block3 = access.fillBlock3(keyA=nextSectorKey,keyB=defaultKey) + #Write all next sectors access + for sector in range(1,16): + if reader.writeSectorBlock(uid,sector,3,block3,keyA=defaultKey) == reader.ERR: + print("\nWriting to sector ",sector," Failed!") + break + else: + print(".",end="") + #force sector 1 to be 1 record empty + block= 16 *[0] + block[2]=0xfe + if reader.writeSectorBlock(uid,1,0,block,keyB=defaultKey) == reader.ERR: + print("Unable to set first NDEF record!") + print("\nDone.") + + previousCard=uid + break + else: + PreviousCard=[0] + utime.sleep_ms(50) + +except KeyboardInterrupt: + print("Bye") + + + diff --git a/Pico_example/EraseNdefTag.py b/Pico_example/EraseNdefTag.py new file mode 100644 index 0000000..e47ddf1 --- /dev/null +++ b/Pico_example/EraseNdefTag.py @@ -0,0 +1,70 @@ +from mfrc522 import MFRC522 +from RfidAccess import RfidAccess +import utime + + + +reader = MFRC522(spi_id=0,sck=2,miso=4,mosi=3,cs=1,rst=0) +access = RfidAccess() + +print("") +print("Please place card on reader") +print("") + + + +PreviousCard = [0] + +try: + while True: + + reader.init() + (stat, tag_type) = reader.request(reader.REQIDL) + if stat == reader.OK: + (stat, uid) = reader.SelectTagSN() + if uid == PreviousCard: + continue + + if stat == reader.OK: + print("Card detected {} uid={}".format(hex(int.from_bytes(bytes(uid),"little",False)).upper(),reader.tohexstring(uid))) + firstSectorKey = [0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5] + nextSectorKey = [0xD3, 0xF7, 0xD3, 0xF7, 0xD3, 0xF7] + defaultKey = [255,255,255,255,255,255] + + # set default access + access.decodeAccess(0xff,0x07,0x80) + block3 = access.fillBlock3(keyA=defaultKey,keyB=defaultKey) + print("Reset Mad Sector (first sector)") + #reset first sector + reader.writeSectorBlock(uid,0,3,block3,keyB=defaultKey) + #erase block1 and 2 + datablock = 16 * [0] + reader.writeSectorBlock(uid,0,1,datablock,keyB=defaultKey) + reader.writeSectorBlock(uid,0,2,datablock,keyB=defaultKey) + + #reset all other sectors + for s in range(1,16): + # permission to default + print("Reset sector ",s) + reader.writeSectorBlock(uid,s,3,block3,keyB=defaultKey) + for b in range(3): + # put all data to zero block 0,1 and 2 + reader.writeSectorBlock(uid,s,b,datablock,keyB=defaultKey) + # ok dump new data + print("\n---- Dump card using defaultkeyB [0xff, 0xff, 0xff, 0xff, 0xff, 0xff]\n") + reader.MFRC522_DumpClassic1K(uid, Start=0, End=64, keyB=defaultKey) + PreviousCard = uid + break + else: + pass + #print("Authentication error") + else: + PreviousCard=[0] + utime.sleep_ms(50) + +except KeyboardInterrupt: + print("Bye") + +print("Card done!") + + diff --git a/Pico_example/Pico_read.py b/Pico_example/Pico_read.py new file mode 100644 index 0000000..8950f52 --- /dev/null +++ b/Pico_example/Pico_read.py @@ -0,0 +1,45 @@ +from mfrc522 import MFRC522 +import utime + + +def uidToString(uid): + mystring = "" + for i in uid: + mystring = "%02X" % i + mystring + return mystring + + +reader = MFRC522(spi_id=0,sck=2,miso=4,mosi=3,cs=1,rst=0) + +print("") +print("Please place card on reader") +print("") + + + +PreviousCard = [0] + +try: + while True: + + reader.init() + (stat, tag_type) = reader.request(reader.REQIDL) + #print('request stat:',stat,' tag_type:',tag_type) + if stat == reader.OK: + (stat, uid) = reader.SelectTagSN() + if uid == PreviousCard: + continue + if stat == reader.OK: + print("Card detected {} uid={}".format(hex(int.from_bytes(bytes(uid),"little",False)).upper(),reader.tohexstring(uid))) + defaultKey = [255,255,255,255,255,255] + reader.MFRC522_DumpClassic1K(uid, Start=0, End=64, keyA=defaultKey) + print("Done") + PreviousCard = uid + else: + pass + else: + PreviousCard=[0] + utime.sleep_ms(50) + +except KeyboardInterrupt: + pass \ No newline at end of file diff --git a/examples/Pico_write.py b/Pico_example/Pico_write.py similarity index 86% rename from examples/Pico_write.py rename to Pico_example/Pico_write.py index cb1542e..6cc04cd 100644 --- a/examples/Pico_write.py +++ b/Pico_example/Pico_write.py @@ -17,7 +17,7 @@ def uidToString(uid): reader = MFRC522(spi_id=0,sck=2,miso=4,mosi=3,cs=1,rst=0) print("") -print("Place card before reader to read from address 0x08") +print("Please place card on reader") print("") key = [0xFF,0xFF,0xFF,0xFF,0xFF,0xFF] @@ -32,7 +32,7 @@ def uidToString(uid): if stat == reader.OK: print(uid) print("Card detected %s" % uidToString(uid)) - reader.MFRC522_DumpClassic1K(key, uid) + reader.MFRC522_DumpClassic1K(uid,keyA=key) print("Test ! writing sector 2, block 0 (absolute block(8)") print("with [ 00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 ]") absoluteBlock=8 @@ -43,7 +43,7 @@ def uidToString(uid): if status == reader.OK: status = reader.write(absoluteBlock,value) if status == reader.OK: - reader.MFRC522_DumpClassic1K(key, uid) + reader.MFRC522_DumpClassic1K(uid,keyA=key) else: print("unable to write") else: diff --git a/Pico_example/ReadNdefTag.py b/Pico_example/ReadNdefTag.py new file mode 100644 index 0000000..860e2c8 --- /dev/null +++ b/Pico_example/ReadNdefTag.py @@ -0,0 +1,47 @@ +from mfrc522 import MFRC522 +import utime + + + + +reader = MFRC522(spi_id=0,sck=2,miso=4,mosi=3,cs=1,rst=0) + +print("") +print("Place card into reader") +print("") + + + +PreviousCard = [0] + +try: + while True: + + reader.init() + (stat, tag_type) = reader.request(reader.REQIDL) + if stat == reader.OK: + (stat, uid) = reader.SelectTagSN() + if uid == PreviousCard: + continue + + if stat == reader.OK: + print("Card detected {} uid={}".format(hex(int.from_bytes(bytes(uid),"little",False)).upper(),reader.tohexstring(uid))) + firstSectorKey = [0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5] + nextSectorKey = [0xD3, 0xF7, 0xD3, 0xF7, 0xD3, 0xF7] + #defaultKey = [255,255,255,255,255,255] + + #read MAD sector (first sector) + if reader.MFRC522_DumpClassic1K(uid, Start=0, End=4, keyA=firstSectorKey)== reader.OK: + #read the rest of the card + reader.MFRC522_DumpClassic1K(uid, Start=4, End=64, keyA=nextSectorKey) + print("Done") + PreviousCard = uid + else: + PreviousCard=[0] + utime.sleep_ms(50) + +except KeyboardInterrupt: + print("Bye") + + + diff --git a/RfidAccess.py b/RfidAccess.py new file mode 100644 index 0000000..89b6fbe --- /dev/null +++ b/RfidAccess.py @@ -0,0 +1,209 @@ +class RfidAccess: + NEVER = 0 + KEYA = 1 + KEYB = 2 + KEYAB = 3 + ALLBLOCK = -1 + + def __init__(self): + self.C1 = 0 + self.C2 = 0 + self.C3 = 8 + self.C1_Inv = 15 + self.C2_Inv = 15 + self.C3_Inv = 7 + self.Valid=False + + def findAccessIndex(self,table,mask,accessbits): + for i in range(8): + if table[i] == accessbits: + if (i & 1) == 0 : + self.C2 = self.C2 & ~mask + else: + self.C2 = self.C2 | mask + + if (i & 2) == 0 : + self.C1 = self.C1 & ~mask + else: + self.C1 = self.C1 | mask + + if (i & 4) == 0: + self.C3 = self.C3 & ~mask + else: + self.C3 = self.C3 | mask + + self.C1_Inv = self.C1 ^ 0xf + self.C2_Inv = self.C2 ^ 0xf + self.C3_Inv = self.C3 ^ 0xf + return True + return False + + + + + + def setTrailerAccess(self,keyA_Write=KEYA, access_Read=KEYA,access_Write=KEYA,keyB_Read=KEYA,keyB_Write=KEYA): + #C1 weight 2 , C2 weight 1 , C3 weight 4 + + # Key A Wr Access R Access W KeyB R KEYB W + access_Index = ( (self.KEYA, self.KEYA, self.NEVER, self.KEYA,self.KEYA), + (self.NEVER,self.KEYA,self.NEVER,self.KEYA,self.NEVER), + (self.KEYB, self.KEYAB,self.NEVER,self.NEVER,self.NEVER), + (self.NEVER,self.KEYAB,self.NEVER,self.NEVER,self.NEVER), + (self.KEYA,self.KEYA,self.KEYA,self.KEYA,self.KEYA), + (self.KEYB,self.KEYAB,self.KEYB,self.NEVER,self.KEYB), + (self.NEVER,self.KEYAB,self.KEYB,self.NEVER,self.NEVER), + (self.NEVER,self.KEYAB,self.NEVER,self.NEVER,self.NEVER)) + if self.findAccessIndex(access_Index,8, (keyA_Write,access_Read,access_Write,keyB_Read,keyB_Write)): + return True + else: + print("Access Trailer method not possible") + return False + + + def setBlockAccess(self, blockID, access_Read=KEYAB, access_Write=KEYAB, access_Inc=KEYAB, access_Dec=KEYAB): + # C1 weight 2 , C2 weight 1 , C3 weight 4 + # Key Read Key Write INCREment decrement/transfer/etc + access_Blk_idx=((self.KEYAB, self.KEYAB, self.KEYAB, self.KEYAB,None), + (self.KEYAB, self.NEVER, self.NEVER, self.NEVER,None), + (self.KEYAB, self.KEYB, self.NEVER, self.NEVER,None), + (self.KEYAB, self.KEYB, self.KEYB, self.KEYAB,None), + (self.KEYAB, self.NEVER, self.NEVER, self.KEYAB,None), + (self.KEYB, self.KEYB, self.NEVER, self.NEVER,None), + (self.KEYB, self.NEVER, self.NEVER, self.NEVER,None), + (self.NEVER, self.NEVER, self.NEVER, self.NEVER,None)) + + if(blockID == self.ALLBLOCK): + Mask = 7 + elif (BlockID<0) or (blockID >3): + return false + else: + Mask = 1 << blockID + + if self.findAccessIndex(access_Blk_idx,Mask,( access_Read, access_Write, access_Inc, access_Dec, None)): + return True + else: + print("**** Error Access Block method not possible") + return False + + + def encodeAccess(self): + self.C1 = self.C1 & 0xf + self.C2 = self.C2 & 0xf + self.C3 = self.C3 & 0xf + self.C1_INV = self.C1 ^ 0xf + self.C2_INV = self.C2 ^ 0xf + self.C3_INV = self.C3 ^ 0xf + byte6 = self.C2_INV << 4 | self.C1_INV + byte7 = self.C1 << 4 | self.C3_INV + byte8 = self.C3 << 4 | self.C2 + return (byte6, byte7, byte8) + + + def decodeAccess(self, byte6,byte7,byte8): + self.C1_Inv = byte6 & 0xf + self.C2_Inv = (byte6 & 0xf0) >> 4 + self.C3_Inv = byte7 & 0xf + self.C1 = (byte7 & 0xf0) >> 4 + self.C2 = byte8 & 0xf + self.C3 = (byte8 & 0xf0) >>4 + return ((self.C1 ^ self.C1_Inv) & (self.C2 ^ self.C2_Inv) & (self.C3 ^ self.C3_Inv)) & 0xf == 0xf + + + def showTrailerAccess(self): + KeyAWrite = ['key A', 'never', 'key B', 'never', 'key A', 'key B', 'never', 'never'] + AccessRead = ['key A', 'key A', 'key A|B', 'key A|B', 'key A','key A|B','key A|B','key A|B' ] + AccessWrite = ['never', 'never', 'never', 'never', 'key A', 'key B', 'key B', 'never'] + KeyBRead = ['key A data R/W', 'key A data R', 'never', 'never', 'key A data R/W', 'never', 'never', 'never'] + KeyBWrite = ['key A data R/W', 'never', 'key B', 'never', 'key A data R/W', 'key B', 'never', 'never'] + + index = 0 + + if self.C2 & 0x8 > 0: + index = 1 + if self.C1 & 0x8 > 0: + index = index + 2 + if self.C3 & 0x8 > 0: + index = index + 4 + + + #print("TrailerAccess idx=",index) + print('Access key A read => never') + print('Access key A write =>', KeyAWrite[index]) + print('Access key B read =>', KeyBRead[index]) + print('Access key B write =>', KeyBWrite[index]) + print('Access bits read =>', AccessRead[index]) + print('Access bits write =>', AccessWrite[index]) + + + def showBlockAccess(self,blockID): + BlockRead = ['key A|B','key A|B','key A|B','key A|B','key A|B','key B', 'key B', 'never' ] + BlockWrite = ['key A|B', 'never', 'key B', 'key B', 'never', 'key B','never', 'never'] + BlockIncrement = ['key A|B', 'never', 'never', 'key B', 'never', 'never', 'never', 'never'] + BlockDecrement = ['key A|B', 'never', 'never', 'key A|B', 'key A|B', 'never', 'never', 'never'] + if blockID == 3: + self.showTrailerAccess() + else: + mask = 1 << blockID + index = 0 + if (self.C2 & mask) > 0: + index =1 + if (self.C1 & mask) > 0: + index = index + 2 + if (self.C3 & mask) > 0: + index = index + 4 + + print("Block ", blockID, " read =>", BlockRead[index]) + print("Block ", blockID, " write =>", BlockWrite[index]) + print("Block ", blockID, " Increment =>", BlockIncrement[index]) + print("Block ", blockID, " Decrement+ =>", BlockDecrement[index]) + + def showAccess(self): + print("/--------- SHOW ACCESS ---------\\") + AccessB = self.encodeAccess() + print('Access Bytes (6,7,8)= (',hex(AccessB[0]),', ',hex(AccessB[1]),', ',hex(AccessB[2]),')') + print('(C1, C2, C3) = (',hex(self.C1),', ',hex(self.C2),', ',hex(self.C3),')') + for i in range(4): + self.showBlockAccess(i) + print("\\---------------------------------/") + + + def fillBlock3(self,keyA=None,keyB=None,block=None): + if block is None: + block = 16*[0xff] + if len(block) != 16: + block = 16*[0xff] + + if keyA is not None: + if len(keyA) == 6 : + block[0:6]=keyA + + if keyB is not None: + if len(keyB) == 6 : + block[10:16]=keyB + b6, b7, b8 = self.encodeAccess() + block[6]=b6 + block[7]=b7 + block[8]=b8 + return block + + + +if __name__ == "__main__": + rfid = RfidAccess() + + + print("\nEx: Decode access_bits for [0xff, 0x07, 0x80]") + if rfid.decodeAccess(0xFF,0x07,0x80): + rfid.showAccess() + else: + print('Invalid Access') + + print("\n\nSet block 0 to 3 to be read by KEYA but R/W with KEYB") + rfid.setBlockAccess(rfid.ALLBLOCK, access_Read=rfid.KEYAB, access_Write=rfid.KEYB, access_Inc=rfid.NEVER, access_Dec=rfid.NEVER) + print("Set sector trailer to be only set by Key B"); + rfid.setTrailerAccess(keyA_Write=rfid.KEYB,access_Read=rfid.KEYAB,access_Write=rfid.KEYB,keyB_Read=rfid.NEVER,keyB_Write=rfid.KEYB) + rfid.showAccess() + + + diff --git a/examples/Pico_read.py b/examples/Pico_read.py deleted file mode 100644 index 541c8b3..0000000 --- a/examples/Pico_read.py +++ /dev/null @@ -1,34 +0,0 @@ -from mfrc522 import MFRC522 - - -def uidToString(uid): - mystring = "" - for i in uid: - mystring = "%02X" % i + mystring - return mystring - - -reader = MFRC522(spi_id=0,sck=2,miso=4,mosi=3,cs=1,rst=0) - -print("") -print("Place card before reader to read from address 0x08") -print("") - -try: - while True: - - (stat, tag_type) = reader.request(reader.REQIDL) - - if stat == reader.OK: - - (stat, uid) = reader.SelectTagSN() - - if stat == reader.OK: - print("Card detected %s" % uidToString(uid)) - else: - print("Authentication error") - -except KeyboardInterrupt: - print("Bye") - - diff --git a/mfrc522.py b/mfrc522.py index f73aec6..e12ae77 100644 --- a/mfrc522.py +++ b/mfrc522.py @@ -220,22 +220,49 @@ def PcdSelect(self, serNum,anticolN): return 0 + def SelectTag(self, uid): + byte5 = 0 + + #(status,puid)= self.anticoll(self.PICC_ANTICOLL1) + #print("uid",uid,"puid",puid) + for i in uid: + byte5 = byte5 ^ i + puid = uid + [byte5] + + if self.PcdSelect(puid,self.PICC_ANTICOLL1) == 0: + return (self.ERR,[]) + return (self.OK , uid) + + def tohexstring(self,v): + s="[" + for i in v: + if i != v[0]: + s = s+ ", " + s=s+ "0x{:02X}".format(i) + s= s+ "]" + return s + + + + def SelectTagSN(self): valid_uid=[] (status,uid)= self.anticoll(self.PICC_ANTICOLL1) + #print("Select Tag 1:",self.tohexstring(uid)) if status != self.OK: return (self.ERR,[]) - + if self.DEBUG: print("anticol(1) {}".format(uid)) if self.PcdSelect(uid,self.PICC_ANTICOLL1) == 0: return (self.ERR,[]) if self.DEBUG: print("pcdSelect(1) {}".format(uid)) - + #check if first byte is 0x88 if uid[0] == 0x88 : #ok we have another type of card valid_uid.extend(uid[1:4]) (status,uid)=self.anticoll(self.PICC_ANTICOLL2) + #print("Select Tag 2:",self.tohexstring(uid)) if status != self.OK: return (self.ERR,[]) if self.DEBUG: print("Anticol(2) {}".format(uid)) @@ -248,6 +275,7 @@ def SelectTagSN(self): if uid[0] == 0x88 : valid_uid.extend(uid[1:4]) (status , uid) = self.anticoll(self.PICC_ANTICOLL3) + #print("Select Tag 3:",self.tohexstring(uid)) if status != self.OK: return (self.ERR,[]) if self.DEBUG: print("Anticol(3) {}".format(uid)) @@ -268,7 +296,15 @@ def SelectTagSN(self): def auth(self, mode, addr, sect, ser): return self._tocard(0x0E, [mode, addr] + sect + ser[:4])[0] - + + def authKeys(self,uid,addr,keyA=None, keyB=None): + status = self.ERR + if keyA is not None: + status = self.auth(self.AUTHENT1A, addr, keyA, uid) + elif keyB is not None: + status = self.auth(self.AUTHENT1B, addr, keyB, uid) + return status + def stop_crypto1(self): self._cflags(0x08, 0x08) @@ -278,7 +314,7 @@ def read(self, addr): data = [0x30, addr] data += self._crc(data) (stat, recv, _) = self._tocard(0x0C, data) - return recv if stat == self.OK else None + return stat, recv def write(self, addr, data): @@ -296,22 +332,53 @@ def write(self, addr, data): (stat, recv, bits) = self._tocard(0x0C, buf) if not (stat == self.OK) or not (bits == 4) or not ((recv[0] & 0x0F) == 0x0A): stat = self.ERR - return stat - def MFRC522_DumpClassic1K(self, key, uid, Start=0, End=64): - for i in range(Start,End): - status = self.auth(self.AUTHENT1A, i, key, uid) + + def writeSectorBlock(self,uid, sector, block, data, keyA=None, keyB = None): + absoluteBlock = sector * 4 + (block % 4) + if absoluteBlock > 63 : + return self.ERR + if len(data) != 16: + return self.ERR + if self.authKeys(uid,absoluteBlock,keyA,keyB) != self.ERR : + return self.write(absoluteBlock, data) + return self.ERR + + def readSectorBlock(self,uid ,sector, block, data,keyA=None, keyB = None): + absoluteBlock = sector * 4 + (block % 4) + if page > 63 : + return self.ERR, None + if len(data) != 16: + return self.ERR, None + if self.authKeys(uid,page,KeyA,KeyB) != self.ERR : + return self.read(page) + return self.ERR, None + + def MFRC522_DumpClassic1K(self,uid, Start=0, End=64, keyA=None, keyB=None): + for absoluteBlock in range(Start,End): + status = self.authKeys(uid,absoluteBlock,keyA,keyB) # Check if authenticated - print("{:02d}: ".format(i),end="") + print("{:02d} S{:02d} B{:1d}: ".format(absoluteBlock, absoluteBlock//4 , absoluteBlock % 4),end="") if status == self.OK: - block = self.read(i) - if block is None: - print("-- ") + status, block = self.read(absoluteBlock) + if status == self.ERR: + break else: for value in block: print("{:02X} ".format(value),end="") + print(" ",end="") + for value in block: + if (value > 0x20) and (value < 0x7f): + print(chr(value),end="") + else: + print('.',end="") print("") else: - print("Authentication error") - + break + if status == self.ERR: + print("Authentication error") + return self.ERR + return self.OK + + From 583410f7ff42da2921dcac8c2778f079dc189c13 Mon Sep 17 00:00:00 2001 From: Daniel Perron Date: Wed, 15 Sep 2021 13:03:01 -0400 Subject: [PATCH 12/26] Add RfidCall and examples --- README.md | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 3757f74..e0ea92d 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,13 @@ Sept 20 2019 Modification to be able to read 7 and 10 BYTES RFID P.S. I didn't test the write mode. +D.Perron +Sept 15 2021 +Addition of RfidAccess Class! this allows to set or read access bits. +Addition of read and write using sector/block and selecting KEYA or KEYB +Add more example on how to change access block#3 + + Basic class to access RFID readers of the type [MFRC522](http://www.nxp.com/documents/data_sheet/MFRC522.pdf). @@ -41,7 +48,11 @@ The Pico_write example has been added but be aware of - The software treated the memory has 64 sectors of 16 Bytes. - In reality the mifare card is 16 sectors of 4 block which are 16 byte. - The Block 0,1 and 2 are data block. The block 3 is the access block -- BE AWARE if you write sector (3,7,11,15...,63) then you should know what you do - + The Block 0,1 and 2 are data block. The block 3 is the access block. +- BE AWARE if you write sector (3,7,11,15...,63) then you should know what you do. +- CreateNdefTag.py Explain how to set access block on each sector. It will create an empty NDEF tag. +- EraseNdefTag.py Recreate a blank Mifare tag after you use CreateNdefTag.py. +- ReadNdefTag.py Dump the contents of a NDEF tag. +- Pico_read.py Dump the contents of a Mifare card using the default key. + From 96d8f9de41d89d9e5e34a9b593c86ee4d8245b25 Mon Sep 17 00:00:00 2001 From: Daniel Perron Date: Wed, 15 Sep 2021 13:04:31 -0400 Subject: [PATCH 13/26] Update README.md --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index e0ea92d..b6528a2 100644 --- a/README.md +++ b/README.md @@ -12,9 +12,9 @@ P.S. I didn't test the write mode. D.Perron Sept 15 2021 -Addition of RfidAccess Class! this allows to set or read access bits. -Addition of read and write using sector/block and selecting KEYA or KEYB -Add more example on how to change access block#3 +Addition of RfidAccess Class! this allows to set or read access bits.
+Addition of read and write using sector/block and selecting KEYA or KEYB.
+Add more examples on how to change access block. (Block3 of each sector>.
From 2517fee2d5f0bfc097dcc99a40570bf88b65d919 Mon Sep 17 00:00:00 2001 From: Daniel Perron Date: Wed, 15 Sep 2021 13:04:48 -0400 Subject: [PATCH 14/26] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b6528a2..db3f9c5 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ D.Perron Sept 15 2021 Addition of RfidAccess Class! this allows to set or read access bits.
Addition of read and write using sector/block and selecting KEYA or KEYB.
-Add more examples on how to change access block. (Block3 of each sector>.
+Add more examples on how to change access block. (Block3 of each sector).
From 7db6272c73e09acfb5597a5f80925eb2c72b0fc0 Mon Sep 17 00:00:00 2001 From: Daniel Perron Date: Wed, 15 Sep 2021 13:45:44 -0400 Subject: [PATCH 15/26] fix readSectorBlock --- mfrc522.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/mfrc522.py b/mfrc522.py index e12ae77..5cce231 100644 --- a/mfrc522.py +++ b/mfrc522.py @@ -345,14 +345,12 @@ def writeSectorBlock(self,uid, sector, block, data, keyA=None, keyB = None): return self.write(absoluteBlock, data) return self.ERR - def readSectorBlock(self,uid ,sector, block, data,keyA=None, keyB = None): + def readSectorBlock(self,uid ,sector, block, keyA=None, keyB = None): absoluteBlock = sector * 4 + (block % 4) - if page > 63 : - return self.ERR, None - if len(data) != 16: + if absoluteBlock > 63 : return self.ERR, None - if self.authKeys(uid,page,KeyA,KeyB) != self.ERR : - return self.read(page) + if self.authKeys(uid,absoluteBlock,keyA,keyB) != self.ERR : + return self.read(absoluteBlock) return self.ERR, None def MFRC522_DumpClassic1K(self,uid, Start=0, End=64, keyA=None, keyB=None): From fbb1a1ab58fc430f973f9e311fe1f59e4200029d Mon Sep 17 00:00:00 2001 From: Daniel Perron Date: Wed, 15 Sep 2021 16:19:29 -0400 Subject: [PATCH 16/26] Add decodeAccessFromBlock3 --- RfidAccess.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/RfidAccess.py b/RfidAccess.py index 89b6fbe..12040c5 100644 --- a/RfidAccess.py +++ b/RfidAccess.py @@ -108,6 +108,11 @@ def decodeAccess(self, byte6,byte7,byte8): self.C2 = byte8 & 0xf self.C3 = (byte8 & 0xf0) >>4 return ((self.C1 ^ self.C1_Inv) & (self.C2 ^ self.C2_Inv) & (self.C3 ^ self.C3_Inv)) & 0xf == 0xf + + def decodeAccessFromBlock3(self, block3): + if len(block3)!= 16: + return False + return self.decodeAccess(block3[6],block3[7],block3[8]) def showTrailerAccess(self): From 28023f0b1617eb63209f64627c2c9a3f68a4383a Mon Sep 17 00:00:00 2001 From: Daniel Perron Date: Sun, 20 Nov 2022 09:14:08 -0500 Subject: [PATCH 17/26] fix line 282 self.PcdSelect. Thanks andyboy-uk81 --- mfrc522.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mfrc522.py b/mfrc522.py index 5cce231..adf97ac 100644 --- a/mfrc522.py +++ b/mfrc522.py @@ -279,7 +279,7 @@ def SelectTagSN(self): if status != self.OK: return (self.ERR,[]) if self.DEBUG: print("Anticol(3) {}".format(uid)) - if self.MFRC522_PcdSelect(uid,self.PICC_ANTICOLL3) == 0: + if self.PcdSelect(uid,self.PICC_ANTICOLL3) == 0: return (self.ERR,[]) if self.DEBUG: print("PcdSelect(3) {}".format(uid)) valid_uid.extend(uid[0:5]) From f0bea07c75d0e263b2072b7c4b1387a56f54d2a0 Mon Sep 17 00:00:00 2001 From: Daniel Perron Date: Sat, 19 Aug 2023 16:25:11 -0400 Subject: [PATCH 18/26] Add multi card example --- examples/MultiReaders.py | 76 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 examples/MultiReaders.py diff --git a/examples/MultiReaders.py b/examples/MultiReaders.py new file mode 100644 index 0000000..942acea --- /dev/null +++ b/examples/MultiReaders.py @@ -0,0 +1,76 @@ +from mfrc522 import MFRC522 +import utime + + +def uidToString(uid): + mystring = "" + for i in uid: + mystring = "%02X" % i + mystring + return mystring + + +class Readers: + + def __init__(self): + self.reader = [] + self.readerId = [] + self.previousCard = [] + + + def add(self, reader, readerID=""): + self.reader.append(reader) + if len(readerID)==0: + readerID= "RFID #"+str(len(self.reader)) + self.readerId.append(readerID) + self.previousCard.append([0]) + + def checkReader(self,idx=0): + if len(self.reader)> idx: + self.reader[idx].init() + (stat, tag_type) = self.reader[idx].request(self.reader[idx].REQIDL) + if stat == self.reader[idx].OK: + (stat, uid) = self.reader[idx].SelectTagSN() + if uid != self.previousCard[idx]: + if stat == self.reader[idx].OK: + self.previousCard[idx] = uid + return (self.readerId[idx] , uid) + else: + self.previousCard[idx] = [0] + return (-1, [0]) + + + def checkAnyReader(self): + for idx in range(len(self.reader)): + (readerID, uid ) = self.checkReader(idx) + if readerID != -1: + return (readerID, uid) + return (-1 , [0]) + + +# define readers + +readers = Readers() + + +reader1 = MFRC522(spi_id=0,sck=2,miso=4,mosi=3,cs=1,rst=0) + +reader2 = MFRC522(spi_id=0,sck=2,miso=4,mosi=3,cs=5,rst=0) + +readers.add(reader1,"left reader") +readers.add(reader2,"right reader") + + +print("") +print("Please place card on any reader") +print("") + +try: + while True: + + (readerID, uid) = readers.checkAnyReader() + if readerID != -1: + print(" RFID card:", uidToString(uid), " from ",readerID) + utime.sleep_ms(50) + +except KeyboardInterrupt: + pass \ No newline at end of file From c2f484aaef179118094602bcc56e307fb6f94075 Mon Sep 17 00:00:00 2001 From: Daniel Perron Date: Sat, 19 Aug 2023 16:26:43 -0400 Subject: [PATCH 19/26] Update MultiReaders.py --- examples/MultiReaders.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/examples/MultiReaders.py b/examples/MultiReaders.py index 942acea..f73ad22 100644 --- a/examples/MultiReaders.py +++ b/examples/MultiReaders.py @@ -2,6 +2,8 @@ import utime +# youtube short video about it https://www.youtube.com/watch?v=wE2AamTF5dg + def uidToString(uid): mystring = "" for i in uid: @@ -73,4 +75,4 @@ def checkAnyReader(self): utime.sleep_ms(50) except KeyboardInterrupt: - pass \ No newline at end of file + pass From f50b0c513c63b30b851dba89961d8fdbe448c46b Mon Sep 17 00:00:00 2001 From: danjperron Date: Wed, 17 Jan 2024 16:20:18 -0500 Subject: [PATCH 20/26] add NTAG NFC --- Pico_example/Pico_read.py | 16 +++++-- mfrc522.py | 96 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 108 insertions(+), 4 deletions(-) diff --git a/Pico_example/Pico_read.py b/Pico_example/Pico_read.py index 8950f52..60acc16 100644 --- a/Pico_example/Pico_read.py +++ b/Pico_example/Pico_read.py @@ -23,6 +23,7 @@ def uidToString(uid): while True: reader.init() + (stat, tag_type) = reader.request(reader.REQIDL) #print('request stat:',stat,' tag_type:',tag_type) if stat == reader.OK: @@ -31,9 +32,18 @@ def uidToString(uid): continue if stat == reader.OK: print("Card detected {} uid={}".format(hex(int.from_bytes(bytes(uid),"little",False)).upper(),reader.tohexstring(uid))) - defaultKey = [255,255,255,255,255,255] - reader.MFRC522_DumpClassic1K(uid, Start=0, End=64, keyA=defaultKey) - print("Done") + + if reader.IsNTAG(): + print("Got NTAG{}".format(reader.NTAG)) + reader.MFRC522_Dump_NTAG(Start=0,End=reader.NTAG_MaxPage) + #print("Write Page 5 to 0x1,0x2,0x3,0x4 in 2 second") + #utime.sleep(2) + #data = [1,2,3,4] + #reader.writeNTAGPage(5,data) + #reader.MFRC522_Dump_NTAG(uid,Start=5,End=6) + else: + defaultKey = [255,255,255,255,255,255] + reader.MFRC522_DumpClassic1K(Start=0, End=64, keyA=defaultKey) PreviousCard = uid else: pass diff --git a/mfrc522.py b/mfrc522.py index adf97ac..1e891a3 100644 --- a/mfrc522.py +++ b/mfrc522.py @@ -9,6 +9,12 @@ class MFRC522: NOTAGERR = 1 ERR = 2 + + NTAG_213 = 213 + NTAG_215 = 215 + NTAG_216 = 216 + NTAG_NONE = 0 + REQIDL = 0x26 REQALL = 0x52 AUTHENT1A = 0x60 @@ -29,7 +35,8 @@ def __init__(self, sck, mosi, miso, rst, cs,baudrate=1000000,spi_id=0): self.rst.value(0) self.cs.value(1) - + self.NTAG = 0 + self.NTAG_MaxPage = 0 board = uname()[0] if board == 'WiPy' or board == 'LoPy' or board == 'FiPy': @@ -379,4 +386,91 @@ def MFRC522_DumpClassic1K(self,uid, Start=0, End=64, keyA=None, keyB=None): return self.ERR return self.OK + def MFRC522_Dump_NTAG(self,Start=0, End=135): + for absoluteBlock in range(Start,End,4): + MaxIndex = 4 * 135 + status = self.OK + print("Page {:02d}: ".format(absoluteBlock),end="") + if status == self.OK: + status, block = self.read(absoluteBlock) + if status == self.ERR: + break + else: + Index = absoluteBlock*4 + for i in range(len(block)): + if Index < MaxIndex : + print("{:02X} ".format(block[i]),end="") + else: + print(" ",end="") + if (i%4)==3: + print(" ",end="") + Index+=1 + print(" ",end="") + Index = absoluteBlock*4 + for value in block: + if Index < MaxIndex: + if (value > 0x20) and (value < 0x7f): + print(chr(value),end="") + else: + print('.',end="") + Index+=1 + print("") + else: + break + if status == self.ERR: + print("Authentication error") + return self.ERR + return self.OK + def writeNTAGPage(self,page,data): + if page>self.NTAG_MaxPage: + return self.ERR + if page < 4: + return self.ERR + if len(data) != 4: + return self.ERR + + return self.write(page,data+[0]*12) + + def getNTAGVersion(self): + buf = [0x60] + buf += self._crc(buf) + stat, recv,_ = self._tocard(0x0C, buf) + return stat, recv + + + #Version NTAG213 = [0x0 ,0x4, 0x4, 0x2, 0x1, 0x0,0x0f, 0x3] + #Version NTAG215 = [0x0 ,0x4, 0x4, 0x2, 0x1, 0x0,0x11, 0x3] + #Version NTAG216 = [0x0 ,0x4, 0x4, 0x2, 0x1, 0x0,0x13, 0x3] + + def IsNTAG(self): + self.NTAG = self.NTAG_NONE + self.NTAG_MaxPage=0 + (stat , rcv) = self.getNTAGVersion() + if stat == self.OK: + if len(rcv) < 8: + return False #do we have at least 8 bytes + if rcv[0] != 0: + return False #check header + if rcv[1] != 4: + return False #check Vendor ID + if rcv[2] != 4: + return False #check product type + if rcv[3] != 2: + return False #check subtype + if rcv[7] != 3: + return False #check protocol + if rcv[6] == 0xf: + self.NTAG= self.NTAG_213 + self.NTAG_MaxPage = 44 + return True + if rcv[6] == 0x11: + self.NTAG= self.NTAG_215 + self.NTAG_MaxPage = 134 + return True + if rcv[7] == 0x13: + self.NTAG= self.NTAG_216 + self.NTAG_MaxPage = 230 + return True + return false + From e1ffef98fdd6b7b14127f57d751f5e9364d1a2a5 Mon Sep 17 00:00:00 2001 From: Daniel Perron Date: Wed, 17 Jan 2024 16:30:31 -0500 Subject: [PATCH 21/26] Update NTAG info --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index db3f9c5..e603925 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,11 @@ # micropython-mfrc522 (Micro)Python class to access the MFRC522 RFID reader +D.Perron +Jan. 2024 + Add NTAG. mfrc522.py modified. + Pico_read.py example contains the NTAG DUMP. + D.Perron Feb. 2021 Add Raspberry Pi Pico compatibility From 12099fbd1dde9d7bbed5cdcec0bbca5ffd237401 Mon Sep 17 00:00:00 2001 From: Daniel Perron Date: Wed, 17 Jan 2024 16:32:00 -0500 Subject: [PATCH 22/26] Update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index e603925..45276a2 100644 --- a/README.md +++ b/README.md @@ -55,9 +55,10 @@ The Pico_write example has been added but be aware of - In reality the mifare card is 16 sectors of 4 block which are 16 byte. The Block 0,1 and 2 are data block. The block 3 is the access block. - BE AWARE if you write sector (3,7,11,15...,63) then you should know what you do. -- CreateNdefTag.py Explain how to set access block on each sector. It will create an empty NDEF tag. +- CreateNdefTag.py Explain how to set access block on each sector. It will create an empty NDEF tag. - EraseNdefTag.py Recreate a blank Mifare tag after you use CreateNdefTag.py. - ReadNdefTag.py Dump the contents of a NDEF tag. - Pico_read.py Dump the contents of a Mifare card using the default key. +- Pico_read.py Dump the contents of NTAG card . From db28b9fba20df3362bb6000c11c67ca3ed82990c Mon Sep 17 00:00:00 2001 From: Daniel Perron Date: Wed, 17 Jan 2024 18:29:44 -0500 Subject: [PATCH 23/26] Update Pico_read.py --- Pico_example/Pico_read.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Pico_example/Pico_read.py b/Pico_example/Pico_read.py index 60acc16..e06a82d 100644 --- a/Pico_example/Pico_read.py +++ b/Pico_example/Pico_read.py @@ -43,7 +43,7 @@ def uidToString(uid): #reader.MFRC522_Dump_NTAG(uid,Start=5,End=6) else: defaultKey = [255,255,255,255,255,255] - reader.MFRC522_DumpClassic1K(Start=0, End=64, keyA=defaultKey) + reader.MFRC522_DumpClassic1K(uid,Start=0, End=64, keyA=defaultKey) PreviousCard = uid else: pass @@ -52,4 +52,4 @@ def uidToString(uid): utime.sleep_ms(50) except KeyboardInterrupt: - pass \ No newline at end of file + pass From 12d4ac040813c4b54363e3ec64bd9653e0c09375 Mon Sep 17 00:00:00 2001 From: danjperron Date: Wed, 17 Jan 2024 19:48:38 -0500 Subject: [PATCH 24/26] fix mifare and NTAG cohabitation --- Pico_example/Pico_read.py | 10 ++++++++-- mfrc522.py | 2 +- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/Pico_example/Pico_read.py b/Pico_example/Pico_read.py index e06a82d..aa36c25 100644 --- a/Pico_example/Pico_read.py +++ b/Pico_example/Pico_read.py @@ -42,8 +42,14 @@ def uidToString(uid): #reader.writeNTAGPage(5,data) #reader.MFRC522_Dump_NTAG(uid,Start=5,End=6) else: - defaultKey = [255,255,255,255,255,255] - reader.MFRC522_DumpClassic1K(uid,Start=0, End=64, keyA=defaultKey) + (stat, tag_type) = reader.request(reader.REQIDL) + if stat == reader.OK: + (stat, uid2) = reader.SelectTagSN() + if stat == reader.OK: + if uid != uid2: + continue + defaultKey = [255,255,255,255,255,255] + reader.MFRC522_DumpClassic1K(uid,Start=0, End=64, keyA=defaultKey) PreviousCard = uid else: pass diff --git a/mfrc522.py b/mfrc522.py index 1e891a3..c35bd01 100644 --- a/mfrc522.py +++ b/mfrc522.py @@ -472,5 +472,5 @@ def IsNTAG(self): self.NTAG= self.NTAG_216 self.NTAG_MaxPage = 230 return True - return false + return False From c206c319ed08a27ff46dc07471545113b5ece8ef Mon Sep 17 00:00:00 2001 From: Daniel Perron Date: Tue, 15 Oct 2024 20:11:26 -0400 Subject: [PATCH 25/26] Update Pico_read.py --- Pico_example/Pico_read.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pico_example/Pico_read.py b/Pico_example/Pico_read.py index aa36c25..1639a33 100644 --- a/Pico_example/Pico_read.py +++ b/Pico_example/Pico_read.py @@ -40,7 +40,7 @@ def uidToString(uid): #utime.sleep(2) #data = [1,2,3,4] #reader.writeNTAGPage(5,data) - #reader.MFRC522_Dump_NTAG(uid,Start=5,End=6) + #reader.MFRC522_Dump_NTAG(Start=5,End=6) else: (stat, tag_type) = reader.request(reader.REQIDL) if stat == reader.OK: From 3d52d16f045558c1631e0b482c448e8ce0316c0a Mon Sep 17 00:00:00 2001 From: Daniel Perron Date: Thu, 6 Feb 2025 21:26:32 -0500 Subject: [PATCH 26/26] add example to use 4 readers on the same pico. --- Pico_example/Read4Readers.py | 69 ++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 Pico_example/Read4Readers.py diff --git a/Pico_example/Read4Readers.py b/Pico_example/Read4Readers.py new file mode 100644 index 0000000..3aec940 --- /dev/null +++ b/Pico_example/Read4Readers.py @@ -0,0 +1,69 @@ +from mfrc522 import MFRC522 +import utime + + +def uidToString(uid): + mystring = "" + for i in uid: + mystring = "%02X" % i + mystring + return mystring + + +class myRFIDReader(MFRC522): + def __init__(self,cs=6): + super().__init__(spi_id=0,sck=2,miso=4,mosi=3,cs=cs,rst=0) + self.key = None + self.keyIn = False + self.keyValidCount=0; + + def Read(self): + status, TagType = self.request(super().REQIDL) + if status == self.OK: + status, uid = self.SelectTagSN() + if status == self.OK: + self.keyIn=True + self.keyValidCount=2 + if self.key != uid: + self.key = uid + if uid is None: + return False + return True + else: + if self.keyIn: + if self.keyValidCount>0: + self.keyValidCount= self.keyValidCount - 1 + else: + self.keyIn=False + self.key=None + return False + + +reader1 = myRFIDReader(cs=1) +reader2 = myRFIDReader(cs=6) +reader3 = myRFIDReader(cs=7) +reader4 = myRFIDReader(cs=8) + +#because of reset to same pins we need to re-init reader +reader1.init() +reader2.init() +reader3.init() +reader4.init() + +print("") +print("Please place card on any reader") +print("") + + +try: + while True: + if reader1.Read(): + print("Reader1 : %s" %uidToString(reader1.key)) + if reader2.Read(): + print("Reader2 : %s" %uidToString(reader2.key)) + if reader3.Read(): + print("Reader3 : %s" %uidToString(reader3.key)) + if reader4.Read(): + print("Reader4 : %s" %uidToString(reader4.key)) + +except KeyboardInterrupt: + pass \ No newline at end of file