Skip to content

Commit 7d97ddb

Browse files
committed
Add mifare classic value block operations
1 parent 2f3a0e7 commit 7d97ddb

File tree

2 files changed

+197
-0
lines changed

2 files changed

+197
-0
lines changed

adafruit_pn532/adafruit_pn532.py

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
"""
2828

2929
import time
30+
import struct
3031
from digitalio import Direction
3132
from micropython import const
3233

@@ -501,6 +502,101 @@ def mifare_classic_write_block(
501502
)
502503
return response[0] == 0x0
503504

505+
def mifare_classic_sub_value_block(self, block_number: int, amount: int) -> bool:
506+
"""Decrease the balance of a value block. Block number should be the block
507+
to change and amount should be an integer up to a maximum of 2147483647.
508+
If the value block is successfully updated then True is returned,
509+
otherwise False is returned.
510+
"""
511+
params = [0x01, MIFARE_CMD_DECREMENT, block_number & 0xFF]
512+
params.extend(list(amount.to_bytes(4, "little")))
513+
514+
response = self.call_function(
515+
_COMMAND_INDATAEXCHANGE, params=params, response_length=1
516+
)
517+
if response[0] != 0x00:
518+
return False
519+
520+
response = self.call_function(
521+
_COMMAND_INDATAEXCHANGE,
522+
params=[0x01, MIFARE_CMD_TRANSFER, block_number & 0xFF],
523+
response_length=1,
524+
)
525+
526+
return response[0] == 0x00
527+
528+
def mifare_classic_add_value_block(self, block_number: int, amount: int) -> bool:
529+
"""Increase the balance of a value block. Block number should be the block
530+
to change and amount should be an integer up to a maximum of 2147483647.
531+
If the value block is successfully updated then True is returned,
532+
otherwise False is returned.
533+
"""
534+
params = [0x01, MIFARE_CMD_INCREMENT, block_number & 0xFF]
535+
params.extend(list(amount.to_bytes(4, "little")))
536+
537+
response = self.call_function(
538+
_COMMAND_INDATAEXCHANGE, params=params, response_length=1
539+
)
540+
if response[0] != 0x00:
541+
return False
542+
543+
response = self.call_function(
544+
_COMMAND_INDATAEXCHANGE,
545+
params=[0x01, MIFARE_CMD_TRANSFER, block_number & 0xFF],
546+
response_length=1,
547+
)
548+
549+
return response[0] == 0x00
550+
551+
def mifare_classic_get_value_block(self, block_number: int) -> int:
552+
"""Read the contents of a value block and return a integer representing the
553+
current balance. Block number should be the block to read.
554+
"""
555+
block = self.mifare_classic_read_block(block_number=block_number)
556+
if block is None:
557+
return None
558+
559+
value = block[0:4]
560+
value_inverted = block[4:8]
561+
value_backup = block[8:12]
562+
if value != value_backup:
563+
raise RuntimeError(
564+
"Value block bytes 0-3 do not match 8-11: "
565+
+ "".join("%02x" % b for b in block)
566+
)
567+
if value_inverted != bytearray(map((lambda x: x ^ 0xFF), value)):
568+
raise RuntimeError(
569+
"Inverted value block bytes 4-7 not valid: "
570+
+ "".join("%02x" % b for b in block)
571+
)
572+
573+
return struct.unpack("<i", value)[0]
574+
575+
def mifare_classic_fmt_value_block(
576+
self, block_number: int, initial_value: int, address_block: int = 0
577+
) -> bool:
578+
"""Formats a block on the card so it is suitable for use as a value block.
579+
Block number should be the block to use. Initial value should be an integer
580+
up to a maximum of 2147483647. Address block is optional and can be used
581+
as part of backup management.
582+
"""
583+
data = bytearray()
584+
initial_value = initial_value.to_bytes(4, "little")
585+
# Value
586+
data.extend(initial_value)
587+
# Inverted value
588+
data.extend(bytearray(map((lambda x: x ^ 0xFF), initial_value)))
589+
# Duplicate of value
590+
data.extend(initial_value)
591+
592+
# Address
593+
address_block = address_block.to_bytes(1, "little")[0]
594+
data.extend(
595+
[address_block, address_block ^ 0xFF, address_block, address_block ^ 0xFF]
596+
)
597+
598+
return self.mifare_classic_write_block(block_number, data)
599+
504600
def ntag2xx_write_block(self, block_number: int, data: ReadableBuffer) -> bool:
505601
"""Write a block of data to the card. Block number should be the block
506602
to write and data should be a byte array of length 4 with the data to

examples/pn532_value_block_mifare.py

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
# SPDX-FileCopyrightText: <text> 2015 Tony DiCola, Roberto Laricchia,
2+
# and Francesco Crisafulli, for Adafruit Industries </text>
3+
4+
# SPDX-License-Identifier: MIT
5+
6+
# Example of detecting and reading a value block from a MiFare classic NFC card.
7+
8+
"""
9+
This example shows connecting to the PN532 and writing & reading a mifare classic
10+
type RFID tag
11+
"""
12+
13+
import board
14+
import busio
15+
16+
# Additional import needed for I2C/SPI
17+
# from digitalio import DigitalInOut
18+
#
19+
# NOTE: pick the import that matches the interface being used
20+
#
21+
from adafruit_pn532.adafruit_pn532 import MIFARE_CMD_AUTH_B
22+
from adafruit_pn532.i2c import PN532_I2C
23+
24+
# from adafruit_pn532.spi import PN532_SPI
25+
# from adafruit_pn532.uart import PN532_UART
26+
27+
# I2C connection:
28+
i2c = busio.I2C(board.SCL, board.SDA)
29+
30+
# Non-hardware reset/request with I2C
31+
pn532 = PN532_I2C(i2c, debug=False)
32+
33+
# With I2C, we recommend connecting RSTPD_N (reset) to a digital pin for manual
34+
# harware reset
35+
# reset_pin = DigitalInOut(board.D6)
36+
# On Raspberry Pi, you must also connect a pin to P32 "H_Request" for hardware
37+
# wakeup! this means we don't need to do the I2C clock-stretch thing
38+
# req_pin = DigitalInOut(board.D12)
39+
# pn532 = PN532_I2C(i2c, debug=False, reset=reset_pin, req=req_pin)
40+
41+
# SPI connection:
42+
# spi = busio.SPI(board.SCK, board.MOSI, board.MISO)
43+
# cs_pin = DigitalInOut(board.D5)
44+
# pn532 = PN532_SPI(spi, cs_pin, debug=False)
45+
46+
# UART connection
47+
# uart = busio.UART(board.TX, board.RX, baudrate=115200, timeout=0.1)
48+
# pn532 = PN532_UART(uart, debug=False)
49+
50+
ic, ver, rev, support = pn532.firmware_version
51+
print("Found PN532 with firmware version: {0}.{1}".format(ver, rev))
52+
53+
# Configure PN532 to communicate with MiFare cards
54+
pn532.SAM_configuration()
55+
56+
print("Waiting for RFID/NFC card to write to!")
57+
58+
key_a = b"\xFF\xFF\xFF\xFF\xFF\xFF"
59+
key_b = b"\xFF\xFF\xFF\xFF\xFF\xFF"
60+
61+
62+
while True:
63+
# Check if a card is available to read
64+
uid = pn532.read_passive_target(timeout=0.5)
65+
print(".", end="")
66+
# Try again if no card is available.
67+
if uid is not None:
68+
break
69+
70+
print("")
71+
72+
print("Found card with UID:", [hex(i) for i in uid])
73+
print("Authenticating block 4 ...")
74+
75+
authenticated = pn532.mifare_classic_authenticate_block(
76+
uid, 4, MIFARE_CMD_AUTH_B, key_b
77+
)
78+
if not authenticated:
79+
print("Authentication failed!")
80+
81+
# Format block and set initial balance of 100
82+
response = pn532.mifare_classic_fmt_value_block(4, 100)
83+
print(
84+
response,
85+
"Formatted block 4, balance is:",
86+
pn532.mifare_classic_get_value_block(4),
87+
)
88+
89+
response = pn532.mifare_classic_sub_value_block(4, 50)
90+
print(
91+
response,
92+
"Decrease by 50, new balance:",
93+
pn532.mifare_classic_get_value_block(4),
94+
)
95+
96+
response = pn532.mifare_classic_add_value_block(4, 1337)
97+
print(
98+
response,
99+
"Increase by 1337, new balance:",
100+
pn532.mifare_classic_get_value_block(4),
101+
)

0 commit comments

Comments
 (0)