forked from infinite-tree/pn532-async
-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathpn532.py
More file actions
211 lines (172 loc) · 7.44 KB
/
Copy pathpn532.py
File metadata and controls
211 lines (172 loc) · 7.44 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
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])