tamashii-mfrc522 1.0.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/lib/core_ext.rb +35 -0
- data/lib/exceptions.rb +14 -0
- data/lib/iso144434.rb +247 -0
- data/lib/mfrc522.rb +616 -0
- data/lib/mifare/classic.rb +132 -0
- data/lib/mifare/des_fire.rb +687 -0
- data/lib/mifare/key.rb +180 -0
- data/lib/mifare/ultralight.rb +58 -0
- data/lib/mifare/ultralight_c.rb +86 -0
- data/lib/picc.rb +28 -0
- metadata +76 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 440eac7bf93b99e1fdcca04eadc90bb01f325b49
|
4
|
+
data.tar.gz: 25602072e19601347e17e7b58e30f7919ae35628
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 3e476988e2fcd4bfa7c501ffa37132711b52c0af1a15cb39ca45257e181934e718dcccfe6ac892be8ed01ec8cfeee6495afab514c4181be6775da4c3f2cd3cb2
|
7
|
+
data.tar.gz: 88575cc6eb0330cba4bdfa7776e8bdde6da11693a03c9963c9bd838c900ca57924a5595169c08feaaf845cbecea48d4faa2514666f2ad6527f95dfacd09ca467
|
data/lib/core_ext.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
class Array
|
2
|
+
def append_uint(number, byte)
|
3
|
+
raise 'Only support unsigned integer' if number < 0
|
4
|
+
raise 'Insufficient bytes' if number.abs >= (1 << (byte * 8))
|
5
|
+
|
6
|
+
until byte == 0
|
7
|
+
self << (number & 0xFF)
|
8
|
+
number >>= 8
|
9
|
+
byte -= 1
|
10
|
+
end
|
11
|
+
self
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_uint
|
15
|
+
int = 0
|
16
|
+
self.each_with_index do |byte, index|
|
17
|
+
int |= (byte << (index * 8))
|
18
|
+
end
|
19
|
+
int
|
20
|
+
end
|
21
|
+
|
22
|
+
def append_sint(number, byte)
|
23
|
+
raise 'Insufficient bytes' if number.abs >= (1 << (byte * 8))
|
24
|
+
|
25
|
+
sign = (number < 0) ? 1 : 0
|
26
|
+
number &= (1 << ((byte * 8) - 1)) - 1
|
27
|
+
self.append_uint(number, byte)
|
28
|
+
self << (self.pop | (sign << 7))
|
29
|
+
end
|
30
|
+
|
31
|
+
def to_sint
|
32
|
+
sign = (self.last & 0x80 != 0) ? (-1 ^ ((1 << ((self.size * 8) - 1)) - 1)) : 0
|
33
|
+
sign | self.to_uint
|
34
|
+
end
|
35
|
+
end
|
data/lib/exceptions.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
class CommunicationError < StandardError; end
|
2
|
+
class PICCTimeoutError < CommunicationError; end
|
3
|
+
class PCDTimeoutError < CommunicationError; end
|
4
|
+
class IncorrectCRCError < CommunicationError; end
|
5
|
+
class CollisionError < CommunicationError; end
|
6
|
+
|
7
|
+
class UnexpectedDataError < StandardError; end
|
8
|
+
|
9
|
+
class MifareNakError < StandardError; end
|
10
|
+
|
11
|
+
class DESFireError < StandardError; end
|
12
|
+
class UnauthenticatedError < DESFireError; end
|
13
|
+
class ReceiptStatusError < DESFireError; end
|
14
|
+
class ReceiptIntegrityError < DESFireError; end
|
data/lib/iso144434.rb
ADDED
@@ -0,0 +1,247 @@
|
|
1
|
+
class ISO144434 < PICC
|
2
|
+
FSCI_to_FSC = [16, 24, 32, 40, 48, 64, 96, 128, 256]
|
3
|
+
|
4
|
+
CMD_RATS = 0xE0
|
5
|
+
CMD_PPS = 0xD0
|
6
|
+
CMD_DESELECT = 0xC2
|
7
|
+
CMD_SUCCESS = 0x00
|
8
|
+
CMD_ADDITIONAL_FRAME = 0xAF
|
9
|
+
|
10
|
+
def initialize(pcd, uid, sak)
|
11
|
+
super
|
12
|
+
|
13
|
+
@cid = 0x00 # We don't support CID
|
14
|
+
@fsc = 16 # Assume PICC only supports 16 bytes frame
|
15
|
+
@fwt = 256 # 77.33ms(256 ticks) default frame waiting time
|
16
|
+
|
17
|
+
@support_cid = false
|
18
|
+
@support_nad = false
|
19
|
+
@block_number = 0
|
20
|
+
@selected = false
|
21
|
+
end
|
22
|
+
|
23
|
+
# ISO/IEC 14443-4 select
|
24
|
+
def select
|
25
|
+
# Send RATS (Request for Answer To Select)
|
26
|
+
buffer = [CMD_RATS, 0x50 | @cid]
|
27
|
+
received_data = @pcd.picc_transceive(buffer)
|
28
|
+
|
29
|
+
dr, ds = process_ats(received_data)
|
30
|
+
|
31
|
+
# Send PPS (Protocol and Parameter Selection Request)
|
32
|
+
buffer = [CMD_PPS | @cid, 0x11, (ds << 2) | dr]
|
33
|
+
received_data = @pcd.picc_transceive(buffer)
|
34
|
+
raise UnexpectedDataError, 'Incorrect response' if received_data[0] != (0xD0 | @cid)
|
35
|
+
|
36
|
+
# Set PCD baud rate
|
37
|
+
@pcd.transceiver_baud_rate(:tx, dr)
|
38
|
+
@pcd.transceiver_baud_rate(:rx, ds)
|
39
|
+
|
40
|
+
@block_number = 0
|
41
|
+
@max_frame_size = [64, @fsc].min
|
42
|
+
@max_inf_size = @max_frame_size - 3 # PCB + CRC16
|
43
|
+
@max_inf_size -= 1 if @support_cid
|
44
|
+
@max_inf_size -= 1 if @support_nad
|
45
|
+
@selected = true
|
46
|
+
end
|
47
|
+
|
48
|
+
# Send S(DESELECT)
|
49
|
+
def deselect
|
50
|
+
buffer = [CMD_DESELECT]
|
51
|
+
received_data = @pcd.picc_transceive(buffer)
|
52
|
+
|
53
|
+
if received_data[0] & 0xF7 == CMD_DESELECT
|
54
|
+
@selected = false
|
55
|
+
true
|
56
|
+
else
|
57
|
+
false
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# Wrapper for handling ISO protocol
|
62
|
+
def transceive(send_data)
|
63
|
+
# Split data according to max buffer size
|
64
|
+
send_data = [send_data] unless send_data.is_a? Array
|
65
|
+
chained_data = send_data.each_slice(@max_inf_size).to_a
|
66
|
+
|
67
|
+
# Initialize I-block
|
68
|
+
pcb = 0x02
|
69
|
+
|
70
|
+
# Send chained data
|
71
|
+
until chained_data.empty?
|
72
|
+
pcb &= 0xEF # Reset chaining indicator
|
73
|
+
pcb |= 0x10 if chained_data.size > 1 # Set chaining
|
74
|
+
pcb |= @block_number # Set block number
|
75
|
+
data = chained_data.shift
|
76
|
+
|
77
|
+
buffer = [pcb] + data
|
78
|
+
|
79
|
+
finished = false
|
80
|
+
until finished
|
81
|
+
received_data = handle_wtx(buffer)
|
82
|
+
|
83
|
+
# Retreive response pcb from data
|
84
|
+
r_pcb = received_data[0]
|
85
|
+
|
86
|
+
# Received ACK
|
87
|
+
if r_pcb & 0xF6 == 0xA2
|
88
|
+
# If ACK matches current block number means success
|
89
|
+
# Otherwise transmit it again
|
90
|
+
if (pcb & 0x01) == (r_pcb & 0x01)
|
91
|
+
finished = true
|
92
|
+
end
|
93
|
+
else
|
94
|
+
finished = true
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
@block_number ^= 1 # toggle block number for next frame
|
99
|
+
end
|
100
|
+
|
101
|
+
received_chained_data = [received_data]
|
102
|
+
|
103
|
+
# Receive chained data
|
104
|
+
while r_pcb & 0x10 != 0
|
105
|
+
ack = 0xA2 | @block_number # Set block number
|
106
|
+
received_data = handle_wtx([ack]) # Send ACK to receive next frame
|
107
|
+
|
108
|
+
r_pcb = received_data[0]
|
109
|
+
|
110
|
+
received_chained_data << received_data
|
111
|
+
|
112
|
+
@block_number ^= 1 # toggle block number for next frame
|
113
|
+
end
|
114
|
+
|
115
|
+
# Collect INF from chain
|
116
|
+
inf = []
|
117
|
+
received_chained_data.each do |data|
|
118
|
+
inf_position = 1
|
119
|
+
inf_position += 1 if data[0] & 0x08 != 0 # CID present
|
120
|
+
inf_position += 1 if data[0] & 0x04 != 0 # NAD present
|
121
|
+
|
122
|
+
inf.concat(data[inf_position..-1])
|
123
|
+
end
|
124
|
+
|
125
|
+
inf
|
126
|
+
end
|
127
|
+
|
128
|
+
def resume_communication
|
129
|
+
deselect rescue nil
|
130
|
+
super
|
131
|
+
end
|
132
|
+
|
133
|
+
def halt
|
134
|
+
deselect rescue nil
|
135
|
+
super
|
136
|
+
end
|
137
|
+
|
138
|
+
private
|
139
|
+
|
140
|
+
def convert_iso_baud_rate_to_pcd_setting(value)
|
141
|
+
# ISO
|
142
|
+
# 0b000: 106kBd, 0b001: 212kBd, 0b010: 424kBd, 0b100: 848kBd
|
143
|
+
# MFRC522 register
|
144
|
+
# 0b000: 106kBd, 0b001: 212kBd, 0b010: 424kBd, 0b011: 848kBd
|
145
|
+
x = (value >> 2) & 0x01
|
146
|
+
y = (value >> 1) & 0x01
|
147
|
+
z = value & 0x01
|
148
|
+
|
149
|
+
((x | y) << 1) + (x | (~y & z))
|
150
|
+
end
|
151
|
+
|
152
|
+
# Gether information from ATS (Answer to Select)
|
153
|
+
def process_ats(ats)
|
154
|
+
position = 1
|
155
|
+
t0 = ats[position] # Format byte
|
156
|
+
|
157
|
+
fsci = t0 & 0x0F # PICC buffer size integer
|
158
|
+
y1 = (t0 >> 4) & 0x07 # Optional frame(TA, TB, TC) indicator
|
159
|
+
@fsc = FSCI_to_FSC.fetch(fsci) # Convert buffer size integer to bytes
|
160
|
+
dr = 0 # default baud rate 106kBd
|
161
|
+
ds = 0
|
162
|
+
|
163
|
+
# Frame: TA
|
164
|
+
if y1 & 0x01 != 0
|
165
|
+
position += 1
|
166
|
+
ta = ats[position]
|
167
|
+
|
168
|
+
dr = ta & 0x07 # PCD to PICC baud rate
|
169
|
+
ds = (ta >> 4) & 0x07 # PICC to PCD baud rate
|
170
|
+
|
171
|
+
# Convert fastest baud rate to PCD setting
|
172
|
+
# dr = convert_iso_baud_rate_to_pcd_setting(dr)
|
173
|
+
# ds = convert_iso_baud_rate_to_pcd_setting(ds)
|
174
|
+
|
175
|
+
# FIXME: baud rate fixed to 106kBd
|
176
|
+
# until author can confirm negotiation works
|
177
|
+
dr = 0
|
178
|
+
ds = 0
|
179
|
+
end
|
180
|
+
|
181
|
+
# Frame: TB
|
182
|
+
if y1 & 0x02 != 0
|
183
|
+
position += 1
|
184
|
+
tb = ats[position]
|
185
|
+
|
186
|
+
fwi = (tb >> 4) & 0x0F # Frame wating integer
|
187
|
+
sgfi = tb & 0x0F # Start-up frame guard integer
|
188
|
+
|
189
|
+
# Convert integers to real time
|
190
|
+
@fwt = (1 << fwi)
|
191
|
+
sgft = (1 << sgfi)
|
192
|
+
|
193
|
+
# Set frame waiting time
|
194
|
+
@pcd.internal_timer(@fwt)
|
195
|
+
end
|
196
|
+
|
197
|
+
# Get info about CID or NAD
|
198
|
+
if y1 & 0x04 != 0
|
199
|
+
position += 1
|
200
|
+
tc = ats[position]
|
201
|
+
|
202
|
+
@support_cid = true if tc & 0x02 != 0
|
203
|
+
@support_nad = true if tc & 0x01 != 0
|
204
|
+
end
|
205
|
+
|
206
|
+
# Start-up guard time
|
207
|
+
sleep 0.000302 * sgft
|
208
|
+
|
209
|
+
return dr, ds
|
210
|
+
end
|
211
|
+
|
212
|
+
def handle_wtx(data)
|
213
|
+
24.times do
|
214
|
+
begin
|
215
|
+
received_data = @pcd.picc_transceive(data)
|
216
|
+
rescue CommunicationError => e
|
217
|
+
raise e unless e.is_a? PICCTimeoutError
|
218
|
+
|
219
|
+
# Try sending NAK when timeout
|
220
|
+
nak = 0xB2 | @block_number
|
221
|
+
data = [nak]
|
222
|
+
next
|
223
|
+
end
|
224
|
+
|
225
|
+
pcb = received_data[0]
|
226
|
+
|
227
|
+
# WTX detected
|
228
|
+
if pcb & 0xF7 == 0xF2
|
229
|
+
inf_position = (pcb & 0x08 != 0) ? 2 : 1
|
230
|
+
wtxm = received_data[inf_position] & 0x3F
|
231
|
+
|
232
|
+
# Set temporary timer
|
233
|
+
@pcd.internal_timer(@fwt * wtxm)
|
234
|
+
|
235
|
+
# Set WTX response
|
236
|
+
data = [0xF2, wtxm]
|
237
|
+
else
|
238
|
+
# Set timer back to FWT
|
239
|
+
@pcd.internal_timer(@fwt)
|
240
|
+
|
241
|
+
return received_data
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
raise PICCTimeoutError
|
246
|
+
end
|
247
|
+
end
|
data/lib/mfrc522.rb
ADDED
@@ -0,0 +1,616 @@
|
|
1
|
+
require 'pi_piper'
|
2
|
+
|
3
|
+
require 'openssl'
|
4
|
+
require 'securerandom'
|
5
|
+
|
6
|
+
require 'core_ext'
|
7
|
+
require 'exceptions'
|
8
|
+
|
9
|
+
require 'picc'
|
10
|
+
require 'iso144434'
|
11
|
+
|
12
|
+
require 'mifare/key'
|
13
|
+
require 'mifare/classic'
|
14
|
+
require 'mifare/ultralight'
|
15
|
+
require 'mifare/ultralight_c'
|
16
|
+
require 'mifare/des_fire'
|
17
|
+
|
18
|
+
include PiPiper
|
19
|
+
|
20
|
+
class MFRC522
|
21
|
+
|
22
|
+
# PICC commands used by the PCD to manage communication with several PICCs (ISO 14443-3, Type A, section 6.4)
|
23
|
+
PICC_REQA = 0x26 # REQuest command, Type A. Invites PICCs in state IDLE to go to READY and prepare for anticollision or selection. 7 bit frame.
|
24
|
+
PICC_WUPA = 0x52 # Wake-UP command, Type A. Invites PICCs in state IDLE and HALT to go to READY(*) and prepare for anticollision or selection. 7 bit frame.
|
25
|
+
PICC_CT = 0x88 # Cascade Tag. Not really a command, but used during anti collision.
|
26
|
+
PICC_SEL_CL1 = 0x93 # Anti collision/Select, Cascade Level 1
|
27
|
+
PICC_SEL_CL2 = 0x95 # Anti collision/Select, Cascade Level 2
|
28
|
+
PICC_SEL_CL3 = 0x97 # Anti collision/Select, Cascade Level 3
|
29
|
+
PICC_HLTA = 0x50 # HaLT command, Type A. Instructs an ACTIVE PICC to go to state HALT.
|
30
|
+
# Mifare Acknowledge
|
31
|
+
PICC_MF_ACK = 0x0A
|
32
|
+
|
33
|
+
# PCD commands
|
34
|
+
PCD_Idle = 0x00 # no action, cancels current command execution
|
35
|
+
PCD_Mem = 0x01 # stores 25 bytes into the internal buffer
|
36
|
+
PCD_GenRandomID = 0x02 # generates a 10-byte random ID number
|
37
|
+
PCD_CalcCRC = 0x03 # activates the CRC coprocessor or performs a self test
|
38
|
+
PCD_Transmit = 0x04 # transmits data from the FIFO buffer
|
39
|
+
PCD_NoCmdChange = 0x07 # no command change, can be used to modify the CommandReg register bits without affecting the command, for example, the PowerDown bit
|
40
|
+
PCD_Receive = 0x08 # activates the receiver circuits
|
41
|
+
PCD_Transceive = 0x0C # transmits data from FIFO buffer to antenna and automatically activates the receiver after transmission
|
42
|
+
PCD_MFAuthent = 0x0E # performs the MIFARE standard authentication as a reader
|
43
|
+
PCD_SoftReset = 0x0F # resets the MFRC522
|
44
|
+
|
45
|
+
# PCD Command and Status Registers
|
46
|
+
CommandReg = 0x01 # starts and stops command execution
|
47
|
+
ComIEnReg = 0x02 # enable and disable interrupt request control bits
|
48
|
+
DivIEnReg = 0x03 # enable and disable interrupt request control bits
|
49
|
+
ComIrqReg = 0x04 # interrupt request bits
|
50
|
+
DivIrqReg = 0x05 # interrupt request bits
|
51
|
+
ErrorReg = 0x06 # error bits showing the error status of the last command executed
|
52
|
+
Status1Reg = 0x07 # communication status bits
|
53
|
+
Status2Reg = 0x08 # receiver and transmitter status bits
|
54
|
+
FIFODataReg = 0x09 # input and output of 64 byte FIFO buffer
|
55
|
+
FIFOLevelReg = 0x0A # number of bytes stored in the FIFO buffer
|
56
|
+
WaterLevelReg = 0x0B # level for FIFO underflow and overflow warning
|
57
|
+
ControlReg = 0x0C # miscellaneous control registers
|
58
|
+
BitFramingReg = 0x0D # adjustments for bit-oriented frames
|
59
|
+
CollReg = 0x0E # bit position of the first bit-collision detected on the RF interface
|
60
|
+
|
61
|
+
# PCD Command Registers
|
62
|
+
ModeReg = 0x11 # defines general modes for transmitting and receiving
|
63
|
+
TxModeReg = 0x12 # defines transmission data rate and framing
|
64
|
+
RxModeReg = 0x13 # defines reception data rate and framing
|
65
|
+
TxControlReg = 0x14 # controls the logical behavior of the antenna driver pins TX1 and TX2
|
66
|
+
TxASKReg = 0x15 # controls the setting of the transmission modulation
|
67
|
+
TxSelReg = 0x16 # selects the internal sources for the antenna driver
|
68
|
+
RxSelReg = 0x17 # selects internal receiver settings
|
69
|
+
RxThresholdReg = 0x18 # selects thresholds for the bit decoder
|
70
|
+
DemodReg = 0x19 # defines demodulator settings
|
71
|
+
MfTxReg = 0x1C # controls some MIFARE communication transmit parameters
|
72
|
+
MfRxReg = 0x1D # controls some MIFARE communication receive parameters
|
73
|
+
SerialSpeedReg = 0x1F # selects the speed of the serial UART interface
|
74
|
+
|
75
|
+
# PCD Configuration Registers
|
76
|
+
CRCResultRegH = 0x21 # shows the MSB and LSB values of the CRC calculation
|
77
|
+
CRCResultRegL = 0x22
|
78
|
+
ModWidthReg = 0x24 # controls the ModWidth setting?
|
79
|
+
RFCfgReg = 0x26 # configures the receiver gain
|
80
|
+
GsNReg = 0x27 # selects the conductance of the antenna driver pins TX1 and TX2 for modulation
|
81
|
+
CWGsPReg = 0x28 # defines the conductance of the p-driver output during periods of no modulation
|
82
|
+
ModGsPReg = 0x29 # defines the conductance of the p-driver output during periods of modulation
|
83
|
+
TModeReg = 0x2A # defines settings for the internal timer
|
84
|
+
TPrescalerReg = 0x2B # the lower 8 bits of the TPrescaler value. The 4 high bits are in TModeReg.
|
85
|
+
TReloadRegH = 0x2C # defines the 16-bit timer reload value
|
86
|
+
TReloadRegL = 0x2D
|
87
|
+
TCounterValueRegH = 0x2E # shows the 16-bit timer value
|
88
|
+
TCounterValueRegL = 0x2F
|
89
|
+
|
90
|
+
# PCD Test Registers
|
91
|
+
TestSel1Reg = 0x31 # general test signal configuration
|
92
|
+
TestSel2Reg = 0x32 # general test signal configuration
|
93
|
+
TestPinEnReg = 0x33 # enables pin output driver on pins D1 to D7
|
94
|
+
TestPinValueReg = 0x34 # defines the values for D1 to D7 when it is used as an I/O bus
|
95
|
+
TestBusReg = 0x35 # shows the status of the internal test bus
|
96
|
+
AutoTestReg = 0x36 # controls the digital self test
|
97
|
+
VersionReg = 0x37 # shows the software version
|
98
|
+
AnalogTestReg = 0x38 # controls the pins AUX1 and AUX2
|
99
|
+
TestDAC1Reg = 0x39 # defines the test value for TestDAC1
|
100
|
+
TestDAC2Reg = 0x3A # defines the test value for TestDAC2
|
101
|
+
TestADCReg = 0x3B # shows the value of ADC I and Q channels
|
102
|
+
|
103
|
+
def initialize(nrstpd = 24, chip = 0, spd = 1000000, timer = 256)
|
104
|
+
chip_option = { 0 => PiPiper::Spi::CHIP_SELECT_0,
|
105
|
+
1 => PiPiper::Spi::CHIP_SELECT_1,
|
106
|
+
2 => PiPiper::Spi::CHIP_SELECT_BOTH,
|
107
|
+
3 => PiPiper::Spi::CHIP_SELECT_NONE }
|
108
|
+
@spi_chip = chip_option[chip]
|
109
|
+
@spi_spd = spd
|
110
|
+
@timer = timer
|
111
|
+
|
112
|
+
# Power it up
|
113
|
+
@nrstpd = nrstpd
|
114
|
+
nrstpd_pin = PiPiper::Pin.new(pin: nrstpd, direction: :out)
|
115
|
+
nrstpd_pin.on
|
116
|
+
sleep 1.0 / 20.0 # Wait 50ms
|
117
|
+
|
118
|
+
soft_reset # Perform software reset
|
119
|
+
|
120
|
+
pcd_config_reset # Set default setting
|
121
|
+
|
122
|
+
antenna_on # Turn antenna on. They were disabled by the reset.
|
123
|
+
end
|
124
|
+
|
125
|
+
# Shutdown
|
126
|
+
def shutdown
|
127
|
+
antenna_off
|
128
|
+
# TODO: handle other platform?
|
129
|
+
if PiPiper::Platform.driver == PiPiper::Bcm2835
|
130
|
+
PiPiper::Platform.driver.unexport_pin(@nrstpd)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
# PCD software reset
|
135
|
+
def soft_reset
|
136
|
+
write_spi(CommandReg, PCD_SoftReset)
|
137
|
+
sleep 1.0 / 20.0 # wait 50ms
|
138
|
+
|
139
|
+
write_spi(TModeReg, 0x87) # Start timer by setting TAuto=1, and higher part of TPrescalerReg
|
140
|
+
write_spi(TPrescalerReg, 0xFF) # Set lower part of TPrescalerReg, and results in 302us timer (f_timer = 13.56 MHz / (2*TPreScaler+1))
|
141
|
+
|
142
|
+
write_spi(TxASKReg, 0x40) # Default 0x00. Force a 100 % ASK modulation independent of the ModGsPReg register setting
|
143
|
+
write_spi(ModeReg, 0x3D) # Default 0x3F. Set the preset value for the CRC coprocessor for the CalcCRC command to 0x6363 (ISO 14443-3 part 6.2.4)
|
144
|
+
end
|
145
|
+
|
146
|
+
# Reset PCD config to default
|
147
|
+
def pcd_config_reset
|
148
|
+
# Clear ValuesAfterColl bit
|
149
|
+
write_spi_clear_bitmask(CollReg, 0x80)
|
150
|
+
|
151
|
+
# Reset transceiver baud rate to 106 kBd
|
152
|
+
transceiver_baud_rate(:tx, 0)
|
153
|
+
transceiver_baud_rate(:rx, 0)
|
154
|
+
|
155
|
+
# Set PCD timer value for 302us default timer
|
156
|
+
internal_timer(@timer)
|
157
|
+
end
|
158
|
+
|
159
|
+
# Control transceive timeout value
|
160
|
+
def internal_timer(timer = nil)
|
161
|
+
if timer
|
162
|
+
write_spi(TReloadRegH, (timer >> 8) & 0xFF)
|
163
|
+
write_spi(TReloadRegL, (timer & 0xFF))
|
164
|
+
end
|
165
|
+
(read_spi(TReloadRegH) << 8) | read_spi(TReloadRegL)
|
166
|
+
end
|
167
|
+
|
168
|
+
# Control transceiver baud rate
|
169
|
+
# value = 0: 106kBd, 1: 212kBd, 2: 424kBd, 3: 848kBd
|
170
|
+
def transceiver_baud_rate(direction, value = nil)
|
171
|
+
reg = {tx: TxModeReg, rx: RxModeReg}
|
172
|
+
|
173
|
+
if value
|
174
|
+
value <<= 4
|
175
|
+
value |= 0x80 if value != 0
|
176
|
+
write_spi(reg.fetch(direction), value)
|
177
|
+
end
|
178
|
+
|
179
|
+
(read_spi(reg.fetch(direction)) >> 4) & 0x07
|
180
|
+
end
|
181
|
+
|
182
|
+
# Turn antenna on
|
183
|
+
def antenna_on
|
184
|
+
write_spi_set_bitmask(TxControlReg, 0x03)
|
185
|
+
end
|
186
|
+
|
187
|
+
# Turn antenna off
|
188
|
+
def antenna_off
|
189
|
+
write_spi_clear_bitmask(TxControlReg, 0x03)
|
190
|
+
end
|
191
|
+
|
192
|
+
# Modify and show antenna gain level
|
193
|
+
# level = 1: 18dB, 2: 23dB, 3: 33dB, 4: 38dB, 5: 43dB, 6: 48dB
|
194
|
+
def antenna_gain(level = nil)
|
195
|
+
unless level.nil?
|
196
|
+
level = 1 if level > 6 || level < 1
|
197
|
+
write_spi_set_bitmask(RFCfgReg, ((level + 1) << 4))
|
198
|
+
end
|
199
|
+
(read_spi(RFCfgReg) & 0x70) >> 4
|
200
|
+
end
|
201
|
+
|
202
|
+
# Wakes PICC from HALT or IDLE to ACTIVE state
|
203
|
+
# Accept PICC_REQA and PICC_WUPA command
|
204
|
+
def picc_request(picc_command)
|
205
|
+
pcd_config_reset
|
206
|
+
|
207
|
+
status, _received_data, valid_bits = communicate_with_picc(PCD_Transceive, picc_command, 0x07)
|
208
|
+
|
209
|
+
status == :status_ok && valid_bits == 0 # REQA or WUPA command return 16 bits(full byte)
|
210
|
+
end
|
211
|
+
|
212
|
+
# Instruct PICC in ACTIVE state go to HALT state
|
213
|
+
def picc_halt
|
214
|
+
buffer = append_crc([PICC_HLTA, 0])
|
215
|
+
|
216
|
+
status, _received_data, _valid_bits = communicate_with_picc(PCD_Transceive, buffer)
|
217
|
+
|
218
|
+
# PICC in HALT state will not respond
|
219
|
+
# If PICC sent reply, means it didn't acknowledge the command we sent
|
220
|
+
status == :status_picc_timeout
|
221
|
+
end
|
222
|
+
|
223
|
+
# Select PICC for further communication
|
224
|
+
#
|
225
|
+
# PICC must be in state ACTIVE
|
226
|
+
def picc_select
|
227
|
+
# Description of buffer structure:
|
228
|
+
#
|
229
|
+
# Byte 0: SEL Indicates the Cascade Level: PICC_CMD_SEL_CL1, PICC_CMD_SEL_CL2 or PICC_CMD_SEL_CL3
|
230
|
+
# Byte 1: NVB Number of Valid Bits (in complete command, not just the UID): High nibble: complete bytes, Low nibble: Extra bits.
|
231
|
+
# Byte 2: UID-data or Cascade Tag
|
232
|
+
# Byte 3: UID-data
|
233
|
+
# Byte 4: UID-data
|
234
|
+
# Byte 5: UID-data
|
235
|
+
# Byte 6: Block Check Character - XOR of bytes 2-5
|
236
|
+
# Byte 7: CRC_A
|
237
|
+
# Byte 8: CRC_A
|
238
|
+
# The BCC and CRC_A are only transmitted if we know all the UID bits of the current Cascade Level.
|
239
|
+
#
|
240
|
+
# Description of bytes 2-5
|
241
|
+
#
|
242
|
+
# UID size Cascade level Byte2 Byte3 Byte4 Byte5
|
243
|
+
# ======== ============= ===== ===== ===== =====
|
244
|
+
# 4 bytes 1 uid0 uid1 uid2 uid3
|
245
|
+
# 7 bytes 1 CT uid0 uid1 uid2
|
246
|
+
# 2 uid3 uid4 uid5 uid6
|
247
|
+
# 10 bytes 1 CT uid0 uid1 uid2
|
248
|
+
# 2 CT uid3 uid4 uid5
|
249
|
+
# 3 uid6 uid7 uid8 uid9
|
250
|
+
pcd_config_reset
|
251
|
+
|
252
|
+
cascade_levels = [PICC_SEL_CL1, PICC_SEL_CL2, PICC_SEL_CL3]
|
253
|
+
uid = []
|
254
|
+
sak = 0
|
255
|
+
|
256
|
+
cascade_levels.each do |cascade_level|
|
257
|
+
buffer = [cascade_level]
|
258
|
+
current_level_known_bits = 0
|
259
|
+
received_data = []
|
260
|
+
valid_bits = 0
|
261
|
+
timeout = true
|
262
|
+
|
263
|
+
# Maxmimum loop count is defined in ISO spec
|
264
|
+
32.times do
|
265
|
+
if current_level_known_bits >= 32 # Prepare to do a complete select if we knew everything
|
266
|
+
# Validate buffer content against non-numeric classes and incorrect size
|
267
|
+
buffer = buffer[0..5]
|
268
|
+
dirty_buffer = buffer.size != 6
|
269
|
+
dirty_buffer ||= buffer.any? do |byte|
|
270
|
+
if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('2.4.0')
|
271
|
+
!byte.is_a?(Numeric)
|
272
|
+
else
|
273
|
+
!byte.is_a?(Fixnum)
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
# Retry reading UID when buffer is dirty, but don't reset loop count to prevent infinite loop
|
278
|
+
if dirty_buffer
|
279
|
+
# Reinitialize all variables
|
280
|
+
buffer = [cascade_level]
|
281
|
+
current_level_known_bits = 0
|
282
|
+
received_data = []
|
283
|
+
valid_bits = 0
|
284
|
+
|
285
|
+
# Continue to next loop
|
286
|
+
next
|
287
|
+
end
|
288
|
+
|
289
|
+
tx_last_bits = 0
|
290
|
+
buffer[1] = 0x70 # NVB - We're sending full length byte[0..6]
|
291
|
+
buffer[6] = (buffer[2] ^ buffer[3] ^ buffer[4] ^ buffer[5]) # Block Check Character
|
292
|
+
|
293
|
+
# Append CRC to buffer
|
294
|
+
buffer = append_crc(buffer)
|
295
|
+
else
|
296
|
+
tx_last_bits = current_level_known_bits % 8
|
297
|
+
uid_full_byte = current_level_known_bits / 8
|
298
|
+
all_full_byte = 2 + uid_full_byte # length of SEL + NVB + UID
|
299
|
+
buffer[1] = (all_full_byte << 4) + tx_last_bits # NVB
|
300
|
+
|
301
|
+
buffer_length = all_full_byte + (tx_last_bits > 0 ? 1 : 0)
|
302
|
+
buffer = buffer[0...buffer_length]
|
303
|
+
end
|
304
|
+
|
305
|
+
framing_bit = (tx_last_bits << 4) + tx_last_bits
|
306
|
+
|
307
|
+
# Select it
|
308
|
+
status, received_data, valid_bits = communicate_with_picc(PCD_Transceive, buffer, framing_bit)
|
309
|
+
|
310
|
+
if status != :status_ok && status != :status_collision
|
311
|
+
raise CommunicationError, status
|
312
|
+
end
|
313
|
+
|
314
|
+
if received_data.empty?
|
315
|
+
raise UnexpectedDataError, 'Received empty UID data'
|
316
|
+
end
|
317
|
+
|
318
|
+
# Append received UID into buffer if not doing full select
|
319
|
+
if current_level_known_bits < 32
|
320
|
+
# Check for last collision
|
321
|
+
if tx_last_bits != 0
|
322
|
+
buffer[-1] |= received_data.shift
|
323
|
+
end
|
324
|
+
|
325
|
+
buffer += received_data
|
326
|
+
end
|
327
|
+
|
328
|
+
# Handle collision
|
329
|
+
if status == :status_collision
|
330
|
+
collision = read_spi(CollReg)
|
331
|
+
|
332
|
+
# CollPosNotValid - We don't know where collision happened
|
333
|
+
raise CollisionError if (collision & 0x20) != 0
|
334
|
+
|
335
|
+
collision_position = collision & 0x1F
|
336
|
+
collision_position = 32 if collision_position == 0 # Values 0-31, 0 means bit 32
|
337
|
+
raise CollisionError if collision_position <= current_level_known_bits
|
338
|
+
|
339
|
+
# Calculate positioin
|
340
|
+
current_level_known_bits = collision_position
|
341
|
+
uid_bit = (current_level_known_bits - 1) % 8
|
342
|
+
|
343
|
+
# Mark the collision bit
|
344
|
+
buffer[-1] |= (1 << uid_bit)
|
345
|
+
else
|
346
|
+
if current_level_known_bits >= 32
|
347
|
+
timeout = false
|
348
|
+
break
|
349
|
+
end
|
350
|
+
current_level_known_bits = 32 # We've already known all bits, loop again for a complete select
|
351
|
+
end
|
352
|
+
end
|
353
|
+
|
354
|
+
# Handle timeout after 32 loops
|
355
|
+
if timeout
|
356
|
+
raise UnexpectedDataError, 'Keep receiving incomplete UID until timeout'
|
357
|
+
end
|
358
|
+
|
359
|
+
# We've finished current cascade level
|
360
|
+
# Check and collect all uid stored in buffer
|
361
|
+
|
362
|
+
# Append UID
|
363
|
+
uid << buffer[2] if buffer[2] != PICC_CT
|
364
|
+
uid << buffer[3] << buffer[4] << buffer[5]
|
365
|
+
|
366
|
+
# Check the result of full select
|
367
|
+
# Select Acknowledge is 1 byte + CRC16
|
368
|
+
raise UnexpectedDataError, 'Unknown SAK format' if received_data.size != 3 || valid_bits != 0
|
369
|
+
raise IncorrectCRCError unless check_crc(received_data)
|
370
|
+
|
371
|
+
sak = received_data[0]
|
372
|
+
break if (sak & 0x04) == 0 # No more cascade level
|
373
|
+
end
|
374
|
+
|
375
|
+
return uid, sak
|
376
|
+
end
|
377
|
+
|
378
|
+
# Trying to restart picc
|
379
|
+
def reestablish_picc_communication(uid)
|
380
|
+
picc_halt
|
381
|
+
picc_request(PICC_WUPA)
|
382
|
+
|
383
|
+
begin
|
384
|
+
new_uid, _new_sak = picc_select
|
385
|
+
status = true
|
386
|
+
rescue CommunicationError
|
387
|
+
status = false
|
388
|
+
end
|
389
|
+
|
390
|
+
status && uid == new_uid
|
391
|
+
end
|
392
|
+
|
393
|
+
# Lookup PICC name using sak
|
394
|
+
def identify_model(sak)
|
395
|
+
# SAK coding separation reference:
|
396
|
+
# http://cache.nxp.com/documents/application_note/AN10833.pdf
|
397
|
+
# http://www.nxp.com/documents/application_note/130830.pdf
|
398
|
+
if sak & 0x04 != 0
|
399
|
+
return :picc_uid_not_complete
|
400
|
+
end
|
401
|
+
|
402
|
+
if sak & 0x02 != 0
|
403
|
+
return :picc_reserved_future_use
|
404
|
+
end
|
405
|
+
|
406
|
+
if sak & 0x08 != 0
|
407
|
+
if sak & 0x10 != 0
|
408
|
+
return :picc_mifare_4k
|
409
|
+
end
|
410
|
+
|
411
|
+
if sak & 0x01 != 0
|
412
|
+
return :picc_mifare_mini
|
413
|
+
end
|
414
|
+
|
415
|
+
return :picc_mifare_1k
|
416
|
+
end
|
417
|
+
|
418
|
+
if sak & 0x10 != 0
|
419
|
+
if sak & 0x01 != 0
|
420
|
+
return :picc_mifare_plus_4k_sl2
|
421
|
+
end
|
422
|
+
|
423
|
+
return :picc_mifare_plus_2k_sl2
|
424
|
+
end
|
425
|
+
|
426
|
+
if sak == 0x00
|
427
|
+
return :picc_mifare_ultralight
|
428
|
+
end
|
429
|
+
|
430
|
+
if sak & 0x20 != 0
|
431
|
+
return :picc_iso_14443_4
|
432
|
+
end
|
433
|
+
|
434
|
+
if sak & 0x40 != 0
|
435
|
+
return :picc_iso_18092
|
436
|
+
end
|
437
|
+
|
438
|
+
return :picc_unknown
|
439
|
+
end
|
440
|
+
|
441
|
+
# Start Crypto1 communication between reader and Mifare PICC
|
442
|
+
#
|
443
|
+
# PICC must be selected before calling for authentication
|
444
|
+
# Remember to deauthenticate after communication, or no new communication can be made
|
445
|
+
#
|
446
|
+
# Accept PICC_MF_AUTH_KEY_A or PICC_MF_AUTH_KEY_B command
|
447
|
+
# Checks datasheets for block address numbering of your PICC
|
448
|
+
#
|
449
|
+
def mifare_crypto1_authenticate(command, block_addr, sector_key, uid)
|
450
|
+
# Buffer[12]: {command, block_addr, sector_key[6], uid[4]}
|
451
|
+
buffer = [command, block_addr]
|
452
|
+
buffer.concat(sector_key[0..5])
|
453
|
+
buffer.concat(uid[0..3])
|
454
|
+
|
455
|
+
communicate_with_picc(PCD_MFAuthent, buffer)
|
456
|
+
|
457
|
+
# Check MFCrypto1On bit
|
458
|
+
(read_spi(Status2Reg) & 0x08) != 0
|
459
|
+
end
|
460
|
+
|
461
|
+
# Stop Crypto1 communication
|
462
|
+
def mifare_crypto1_deauthenticate
|
463
|
+
write_spi_clear_bitmask(Status2Reg, 0x08) # Clear MFCrypto1On bit
|
464
|
+
end
|
465
|
+
|
466
|
+
# Append CRC to buffer and check CRC or Mifare acknowledge
|
467
|
+
def picc_transceive(send_data, accept_timeout = false)
|
468
|
+
send_data = append_crc(send_data)
|
469
|
+
|
470
|
+
puts "Sending Data: #{send_data.map{|x|x.to_s(16).rjust(2,'0').upcase}}" if ENV['DEBUG']
|
471
|
+
|
472
|
+
# Transfer data
|
473
|
+
status, received_data, valid_bits = communicate_with_picc(PCD_Transceive, send_data)
|
474
|
+
return [] if status == :status_picc_timeout && accept_timeout
|
475
|
+
raise PICCTimeoutError if status == :status_picc_timeout
|
476
|
+
raise CommunicationError, status if status != :status_ok
|
477
|
+
|
478
|
+
puts "Received Data: #{received_data.map{|x|x.to_s(16).rjust(2,'0').upcase}}" if ENV['DEBUG']
|
479
|
+
|
480
|
+
# Data exists, check CRC and return
|
481
|
+
if received_data.size > 1
|
482
|
+
raise IncorrectCRCError unless check_crc(received_data)
|
483
|
+
|
484
|
+
return received_data[0..-3]
|
485
|
+
end
|
486
|
+
|
487
|
+
raise UnexpectedDataError, 'Incorrect Mifare ACK format' if received_data.size != 1 || valid_bits != 4 # ACK is 4 bits long
|
488
|
+
raise MifareNakError, received_data[0] if received_data[0] != PICC_MF_ACK
|
489
|
+
|
490
|
+
received_data
|
491
|
+
end
|
492
|
+
|
493
|
+
private
|
494
|
+
|
495
|
+
# Read from SPI communication
|
496
|
+
def read_spi(reg)
|
497
|
+
output = 0
|
498
|
+
PiPiper::Spi.begin do |spi|
|
499
|
+
spi.chip_select_active_low(true)
|
500
|
+
spi.bit_order Spi::MSBFIRST
|
501
|
+
spi.clock @spi_spd
|
502
|
+
|
503
|
+
spi.chip_select(@spi_chip) do
|
504
|
+
spi.write((reg << 1) & 0x7E | 0x80)
|
505
|
+
output = spi.read
|
506
|
+
end
|
507
|
+
end
|
508
|
+
output
|
509
|
+
end
|
510
|
+
|
511
|
+
# Write to SPI communication
|
512
|
+
def write_spi(reg, values)
|
513
|
+
PiPiper::Spi.begin do |spi|
|
514
|
+
spi.chip_select_active_low(true)
|
515
|
+
spi.bit_order Spi::MSBFIRST
|
516
|
+
spi.clock @spi_spd
|
517
|
+
|
518
|
+
spi.chip_select(@spi_chip) do
|
519
|
+
spi.write((reg << 1) & 0x7E, *values)
|
520
|
+
end
|
521
|
+
end
|
522
|
+
end
|
523
|
+
|
524
|
+
# Set bits by mask
|
525
|
+
def write_spi_set_bitmask(reg, mask)
|
526
|
+
value = read_spi(reg)
|
527
|
+
write_spi(reg, value | mask)
|
528
|
+
end
|
529
|
+
|
530
|
+
# Clear bits by mask
|
531
|
+
def write_spi_clear_bitmask(reg, mask)
|
532
|
+
value = read_spi(reg)
|
533
|
+
write_spi(reg, value & (~mask))
|
534
|
+
end
|
535
|
+
|
536
|
+
# PCD transceive helper
|
537
|
+
def communicate_with_picc(command, send_data, framing_bit = 0)
|
538
|
+
wait_irq = 0x00
|
539
|
+
wait_irq = 0x10 if command == PCD_MFAuthent
|
540
|
+
wait_irq = 0x30 if command == PCD_Transceive
|
541
|
+
|
542
|
+
write_spi(CommandReg, PCD_Idle) # Stop any active command.
|
543
|
+
write_spi(ComIrqReg, 0x7F) # Clear all seven interrupt request bits
|
544
|
+
write_spi_set_bitmask(FIFOLevelReg, 0x80) # FlushBuffer = 1, FIFO initialization
|
545
|
+
write_spi(FIFODataReg, send_data) # Write sendData to the FIFO
|
546
|
+
write_spi(BitFramingReg, framing_bit) # Bit adjustments
|
547
|
+
write_spi(CommandReg, command) # Execute the command
|
548
|
+
if command == PCD_Transceive
|
549
|
+
write_spi_set_bitmask(BitFramingReg, 0x80) # StartSend=1, transmission of data starts
|
550
|
+
end
|
551
|
+
|
552
|
+
# Wait for the command to complete
|
553
|
+
i = 2000
|
554
|
+
loop do
|
555
|
+
irq = read_spi(ComIrqReg)
|
556
|
+
break if (irq & wait_irq) != 0
|
557
|
+
return :status_picc_timeout if (irq & 0x01) != 0
|
558
|
+
return :status_pcd_timeout if i == 0
|
559
|
+
i -= 1
|
560
|
+
end
|
561
|
+
|
562
|
+
# Check for error
|
563
|
+
error = read_spi(ErrorReg)
|
564
|
+
return :status_error if (error & 0x13) != 0 # BufferOvfl ParityErr ProtocolErr
|
565
|
+
|
566
|
+
# Receiving data
|
567
|
+
received_data = []
|
568
|
+
data_length = read_spi(FIFOLevelReg)
|
569
|
+
while data_length > 0 do
|
570
|
+
data = read_spi(FIFODataReg)
|
571
|
+
received_data << data
|
572
|
+
data_length -=1
|
573
|
+
end
|
574
|
+
valid_bits = read_spi(ControlReg) & 0x07
|
575
|
+
|
576
|
+
status = :status_ok
|
577
|
+
status = :status_collision if (error & 0x08) != 0 # CollErr
|
578
|
+
|
579
|
+
return status, received_data, valid_bits
|
580
|
+
end
|
581
|
+
|
582
|
+
def calculate_crc(data)
|
583
|
+
write_spi(CommandReg, PCD_Idle) # Stop any active command.
|
584
|
+
write_spi(DivIrqReg, 0x04) # Clear the CRCIRq interrupt request bit
|
585
|
+
write_spi_set_bitmask(FIFOLevelReg, 0x80) # FlushBuffer = 1, FIFO initialization
|
586
|
+
write_spi(FIFODataReg, data) # Write data to the FIFO
|
587
|
+
write_spi(CommandReg, PCD_CalcCRC) # Start the calculation
|
588
|
+
|
589
|
+
# Wait for the command to complete
|
590
|
+
i = 5000
|
591
|
+
loop do
|
592
|
+
irq = read_spi(DivIrqReg)
|
593
|
+
break if (irq & 0x04) != 0
|
594
|
+
raise PCDTimeoutError, 'Error calculating CRC' if i == 0
|
595
|
+
i -= 1
|
596
|
+
end
|
597
|
+
|
598
|
+
write_spi(CommandReg, PCD_Idle) # Stop calculating CRC for new content in the FIFO.
|
599
|
+
|
600
|
+
[read_spi(CRCResultRegL), read_spi(CRCResultRegH)]
|
601
|
+
end
|
602
|
+
|
603
|
+
def append_crc(data)
|
604
|
+
data + calculate_crc(data)
|
605
|
+
end
|
606
|
+
|
607
|
+
def check_crc(data)
|
608
|
+
raise UnexpectedDataError, 'Data too short for CRC check' if data.size < 3
|
609
|
+
|
610
|
+
data = data.dup
|
611
|
+
crc = data.pop(2)
|
612
|
+
|
613
|
+
crc == calculate_crc(data)
|
614
|
+
end
|
615
|
+
|
616
|
+
end
|