mfrc522 1.0.2 → 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/lib/core_ext.rb +38 -0
- data/lib/exceptions.rb +1 -0
- data/lib/mfrc522.rb +112 -160
- data/lib/mifare/classic.rb +38 -19
- data/lib/mifare/des_fire.rb +156 -157
- data/lib/mifare/key.rb +39 -12
- data/lib/mifare/plus.rb +124 -0
- data/lib/mifare/plus_sl0.rb +11 -0
- data/lib/mifare/plus_sl1.rb +0 -0
- data/lib/mifare/plus_sl3.rb +11 -0
- data/lib/mifare/ultralight.rb +68 -17
- data/lib/mifare/ultralight_c.rb +13 -14
- data/lib/mifare/ultralight_ev1.rb +40 -0
- data/lib/picc.rb +313 -9
- metadata +13 -15
- data/lib/iso144434.rb +0 -245
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: b180534b3c1fff35f759792d021ba7005b460838e82b43782341f358d314ae5c
|
4
|
+
data.tar.gz: 77f1e85d84082d57ed27dbba18aea6b6e1e9949327f62ed135e2ec1389e12efe
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d558480f55924d0e55e6028f87186008ed51346ab8cb1720dd2330613fe3c34ec1c6c8c33575d4e9bc228e9a9bd3be2f6826da3349c67ab12a4006c744e5ea49
|
7
|
+
data.tar.gz: 75554b9bcb105507d74f18ae96552bc1dd93b1d5b813f0dc5fba1e5cfdbf3ace56a54f25b623a5948ee268c1ea936b87466e87101900efba49da2d94ac763168
|
data/lib/core_ext.rb
CHANGED
@@ -32,4 +32,42 @@ class Array
|
|
32
32
|
sign = (self.last & 0x80 != 0) ? (-1 ^ ((1 << ((self.size * 8) - 1)) - 1)) : 0
|
33
33
|
sign | self.to_uint
|
34
34
|
end
|
35
|
+
|
36
|
+
def append_crc16
|
37
|
+
append_uint(crc16, 2)
|
38
|
+
end
|
39
|
+
|
40
|
+
def check_crc16(remove_after_check = false)
|
41
|
+
orig_crc = pop(2)
|
42
|
+
old_crc = (orig_crc[1] << 8) + orig_crc[0]
|
43
|
+
new_crc = crc16
|
44
|
+
concat(orig_crc) unless remove_after_check
|
45
|
+
old_crc == new_crc
|
46
|
+
end
|
47
|
+
|
48
|
+
def xor(array2)
|
49
|
+
zip(array2).map{|x, y| x ^ y }
|
50
|
+
end
|
51
|
+
|
52
|
+
def to_bytehex
|
53
|
+
map{|x| x.to_bytehex}
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def crc16
|
59
|
+
crc = 0x6363
|
60
|
+
self.each do |byte|
|
61
|
+
bb = (byte ^ crc) & 0xFF
|
62
|
+
bb = (bb ^ (bb << 4)) & 0xFF
|
63
|
+
crc = (crc >> 8) ^ (bb << 8) ^ (bb << 3) ^ (bb >> 4)
|
64
|
+
end
|
65
|
+
crc & 0xFFFF
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
class Numeric
|
70
|
+
def to_bytehex
|
71
|
+
self.to_s(16).rjust(2, '0').upcase
|
72
|
+
end
|
35
73
|
end
|
data/lib/exceptions.rb
CHANGED
data/lib/mfrc522.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require '
|
1
|
+
require 'fubuki'
|
2
2
|
|
3
3
|
require 'openssl'
|
4
4
|
require 'securerandom'
|
@@ -7,15 +7,14 @@ require 'core_ext'
|
|
7
7
|
require 'exceptions'
|
8
8
|
|
9
9
|
require 'picc'
|
10
|
-
require 'iso144434'
|
11
10
|
|
12
11
|
require 'mifare/key'
|
13
12
|
require 'mifare/classic'
|
13
|
+
require 'mifare/plus'
|
14
|
+
require 'mifare/des_fire'
|
14
15
|
require 'mifare/ultralight'
|
15
16
|
require 'mifare/ultralight_c'
|
16
|
-
require 'mifare/
|
17
|
-
|
18
|
-
include PiPiper
|
17
|
+
require 'mifare/ultralight_ev1'
|
19
18
|
|
20
19
|
class MFRC522
|
21
20
|
|
@@ -27,8 +26,6 @@ class MFRC522
|
|
27
26
|
PICC_SEL_CL2 = 0x95 # Anti collision/Select, Cascade Level 2
|
28
27
|
PICC_SEL_CL3 = 0x97 # Anti collision/Select, Cascade Level 3
|
29
28
|
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
29
|
|
33
30
|
# PCD commands
|
34
31
|
PCD_Idle = 0x00 # no action, cancels current command execution
|
@@ -100,19 +97,11 @@ class MFRC522
|
|
100
97
|
TestDAC2Reg = 0x3A # defines the test value for TestDAC2
|
101
98
|
TestADCReg = 0x3B # shows the value of ADC I and Q channels
|
102
99
|
|
103
|
-
def initialize(
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
@spi_chip = chip_option[chip]
|
109
|
-
@spi_spd = spd
|
110
|
-
@timer = timer
|
111
|
-
|
112
|
-
# Power it up
|
113
|
-
nrstpd_pin = PiPiper::Pin.new(pin: nrstpd, direction: :out)
|
114
|
-
nrstpd_pin.on
|
115
|
-
sleep 1.0 / 20.0 # Wait 50ms
|
100
|
+
def initialize(spi_bus: 0, spi_chip: 0, spi_spd: 1_000_000, spi_delay: 1, pcd_timer: 256)
|
101
|
+
@spi_driver = Fubuki::SPI.new(spi_bus, spi_chip)
|
102
|
+
@spi_speed = spi_spd
|
103
|
+
@spi_delay = spi_delay
|
104
|
+
@pcd_timer = pcd_timer
|
116
105
|
|
117
106
|
soft_reset # Perform software reset
|
118
107
|
|
@@ -135,6 +124,12 @@ class MFRC522
|
|
135
124
|
|
136
125
|
# Reset PCD config to default
|
137
126
|
def pcd_config_reset
|
127
|
+
# Stop current command
|
128
|
+
write_spi(CommandReg, PCD_Idle)
|
129
|
+
|
130
|
+
# Stop crypto1 communication
|
131
|
+
mifare_crypto1_deauthenticate
|
132
|
+
|
138
133
|
# Clear ValuesAfterColl bit
|
139
134
|
write_spi_clear_bitmask(CollReg, 0x80)
|
140
135
|
|
@@ -143,7 +138,7 @@ class MFRC522
|
|
143
138
|
transceiver_baud_rate(:rx, 0)
|
144
139
|
|
145
140
|
# Set PCD timer value for 302us default timer
|
146
|
-
internal_timer(@
|
141
|
+
internal_timer(@pcd_timer)
|
147
142
|
end
|
148
143
|
|
149
144
|
# Control transceive timeout value
|
@@ -159,10 +154,13 @@ class MFRC522
|
|
159
154
|
# value = 0: 106kBd, 1: 212kBd, 2: 424kBd, 3: 848kBd
|
160
155
|
def transceiver_baud_rate(direction, value = nil)
|
161
156
|
reg = {tx: TxModeReg, rx: RxModeReg}
|
157
|
+
mod = {0 => 0x26, 1 => 0x15, 2 => 0x0A, 3 => 0x05}
|
162
158
|
|
163
159
|
if value
|
160
|
+
@built_in_crc_disabled = (value == 0)
|
161
|
+
write_spi(ModWidthReg, mod.fetch(value))
|
164
162
|
value <<= 4
|
165
|
-
value |= 0x80
|
163
|
+
value |= 0x80 unless @built_in_crc_disabled
|
166
164
|
write_spi(reg.fetch(direction), value)
|
167
165
|
end
|
168
166
|
|
@@ -189,6 +187,10 @@ class MFRC522
|
|
189
187
|
(read_spi(RFCfgReg) & 0x70) >> 4
|
190
188
|
end
|
191
189
|
|
190
|
+
def buffer_size
|
191
|
+
64
|
192
|
+
end
|
193
|
+
|
192
194
|
# Wakes PICC from HALT or IDLE to ACTIVE state
|
193
195
|
# Accept PICC_REQA and PICC_WUPA command
|
194
196
|
def picc_request(picc_command)
|
@@ -201,7 +203,7 @@ class MFRC522
|
|
201
203
|
|
202
204
|
# Instruct PICC in ACTIVE state go to HALT state
|
203
205
|
def picc_halt
|
204
|
-
buffer =
|
206
|
+
buffer = [PICC_HLTA, 0].append_crc16
|
205
207
|
|
206
208
|
status, _received_data, _valid_bits = communicate_with_picc(PCD_Transceive, buffer)
|
207
209
|
|
@@ -213,7 +215,7 @@ class MFRC522
|
|
213
215
|
# Select PICC for further communication
|
214
216
|
#
|
215
217
|
# PICC must be in state ACTIVE
|
216
|
-
def picc_select
|
218
|
+
def picc_select(disable_anticollision = false)
|
217
219
|
# Description of buffer structure:
|
218
220
|
#
|
219
221
|
# Byte 0: SEL Indicates the Cascade Level: PICC_CMD_SEL_CL1, PICC_CMD_SEL_CL2 or PICC_CMD_SEL_CL3
|
@@ -248,27 +250,42 @@ class MFRC522
|
|
248
250
|
current_level_known_bits = 0
|
249
251
|
received_data = []
|
250
252
|
valid_bits = 0
|
253
|
+
timeout = true
|
251
254
|
|
252
|
-
loop
|
255
|
+
# Maxmimum loop count is defined in ISO spec
|
256
|
+
32.times do
|
253
257
|
if current_level_known_bits >= 32 # Prepare to do a complete select if we knew everything
|
254
|
-
#
|
255
|
-
|
256
|
-
|
258
|
+
# Validate buffer content against non-numeric classes and incorrect size
|
259
|
+
buffer = buffer[0..5]
|
260
|
+
dirty_buffer = buffer.size != 6
|
261
|
+
dirty_buffer ||= buffer.any?{|byte| !byte.is_a?(Integer) }
|
262
|
+
|
263
|
+
# Retry reading UID when buffer is dirty, but don't reset loop count to prevent infinite loop
|
264
|
+
if dirty_buffer
|
265
|
+
# Reinitialize all variables
|
257
266
|
buffer = [cascade_level]
|
267
|
+
current_level_known_bits = 0
|
268
|
+
received_data = []
|
269
|
+
valid_bits = 0
|
270
|
+
|
271
|
+
# Continue to next loop
|
258
272
|
next
|
259
273
|
end
|
260
274
|
|
261
275
|
tx_last_bits = 0
|
262
276
|
buffer[1] = 0x70 # NVB - We're sending full length byte[0..6]
|
263
|
-
buffer
|
277
|
+
buffer[6] = (buffer[2] ^ buffer[3] ^ buffer[4] ^ buffer[5]) # Block Check Character
|
264
278
|
|
265
279
|
# Append CRC to buffer
|
266
|
-
buffer
|
280
|
+
buffer.append_crc16
|
267
281
|
else
|
268
282
|
tx_last_bits = current_level_known_bits % 8
|
269
283
|
uid_full_byte = current_level_known_bits / 8
|
270
284
|
all_full_byte = 2 + uid_full_byte # length of SEL + NVB + UID
|
271
285
|
buffer[1] = (all_full_byte << 4) + tx_last_bits # NVB
|
286
|
+
|
287
|
+
buffer_length = all_full_byte + (tx_last_bits > 0 ? 1 : 0)
|
288
|
+
buffer = buffer[0...buffer_length]
|
272
289
|
end
|
273
290
|
|
274
291
|
framing_bit = (tx_last_bits << 4) + tx_last_bits
|
@@ -278,10 +295,23 @@ class MFRC522
|
|
278
295
|
|
279
296
|
if status != :status_ok && status != :status_collision
|
280
297
|
raise CommunicationError, status
|
298
|
+
elsif status == :status_collision && disable_anticollision
|
299
|
+
raise CollisionError
|
300
|
+
end
|
301
|
+
|
302
|
+
if received_data.empty?
|
303
|
+
raise UnexpectedDataError, 'Received empty UID data'
|
281
304
|
end
|
282
305
|
|
283
306
|
# Append received UID into buffer if not doing full select
|
284
|
-
|
307
|
+
if current_level_known_bits < 32
|
308
|
+
# Check for last collision
|
309
|
+
if tx_last_bits != 0
|
310
|
+
buffer[-1] |= received_data.shift
|
311
|
+
end
|
312
|
+
|
313
|
+
buffer += received_data
|
314
|
+
end
|
285
315
|
|
286
316
|
# Handle collision
|
287
317
|
if status == :status_collision
|
@@ -293,18 +323,27 @@ class MFRC522
|
|
293
323
|
collision_position = collision & 0x1F
|
294
324
|
collision_position = 32 if collision_position == 0 # Values 0-31, 0 means bit 32
|
295
325
|
raise CollisionError if collision_position <= current_level_known_bits
|
296
|
-
|
297
|
-
#
|
326
|
+
|
327
|
+
# Calculate positioin
|
298
328
|
current_level_known_bits = collision_position
|
299
329
|
uid_bit = (current_level_known_bits - 1) % 8
|
300
|
-
|
301
|
-
|
330
|
+
|
331
|
+
# Mark the collision bit
|
332
|
+
buffer[-1] |= (1 << uid_bit)
|
302
333
|
else
|
303
|
-
|
334
|
+
if current_level_known_bits >= 32
|
335
|
+
timeout = false
|
336
|
+
break
|
337
|
+
end
|
304
338
|
current_level_known_bits = 32 # We've already known all bits, loop again for a complete select
|
305
339
|
end
|
306
340
|
end
|
307
341
|
|
342
|
+
# Handle timeout after 32 loops
|
343
|
+
if timeout
|
344
|
+
raise UnexpectedDataError, 'Keep receiving incomplete UID until timeout'
|
345
|
+
end
|
346
|
+
|
308
347
|
# We've finished current cascade level
|
309
348
|
# Check and collect all uid stored in buffer
|
310
349
|
|
@@ -315,7 +354,7 @@ class MFRC522
|
|
315
354
|
# Check the result of full select
|
316
355
|
# Select Acknowledge is 1 byte + CRC16
|
317
356
|
raise UnexpectedDataError, 'Unknown SAK format' if received_data.size != 3 || valid_bits != 0
|
318
|
-
raise IncorrectCRCError unless
|
357
|
+
raise IncorrectCRCError unless received_data.check_crc16(true)
|
319
358
|
|
320
359
|
sak = received_data[0]
|
321
360
|
break if (sak & 0x04) == 0 # No more cascade level
|
@@ -339,54 +378,6 @@ class MFRC522
|
|
339
378
|
status && uid == new_uid
|
340
379
|
end
|
341
380
|
|
342
|
-
# Lookup PICC name using sak
|
343
|
-
def identify_model(sak)
|
344
|
-
# SAK coding separation reference:
|
345
|
-
# http://cache.nxp.com/documents/application_note/AN10833.pdf
|
346
|
-
# http://www.nxp.com/documents/application_note/130830.pdf
|
347
|
-
if sak & 0x04 != 0
|
348
|
-
return :picc_uid_not_complete
|
349
|
-
end
|
350
|
-
|
351
|
-
if sak & 0x02 != 0
|
352
|
-
return :picc_reserved_future_use
|
353
|
-
end
|
354
|
-
|
355
|
-
if sak & 0x08 != 0
|
356
|
-
if sak & 0x10 != 0
|
357
|
-
return :picc_mifare_4k
|
358
|
-
end
|
359
|
-
|
360
|
-
if sak & 0x01 != 0
|
361
|
-
return :picc_mifare_mini
|
362
|
-
end
|
363
|
-
|
364
|
-
return :picc_mifare_1k
|
365
|
-
end
|
366
|
-
|
367
|
-
if sak & 0x10 != 0
|
368
|
-
if sak & 0x01 != 0
|
369
|
-
return :picc_mifare_plus_4k_sl2
|
370
|
-
end
|
371
|
-
|
372
|
-
return :picc_mifare_plus_2k_sl2
|
373
|
-
end
|
374
|
-
|
375
|
-
if sak == 0x00
|
376
|
-
return :picc_mifare_ultralight
|
377
|
-
end
|
378
|
-
|
379
|
-
if sak & 0x20 != 0
|
380
|
-
return :picc_iso_14443_4
|
381
|
-
end
|
382
|
-
|
383
|
-
if sak & 0x40 != 0
|
384
|
-
return :picc_iso_18092
|
385
|
-
end
|
386
|
-
|
387
|
-
return :picc_unknown
|
388
|
-
end
|
389
|
-
|
390
381
|
# Start Crypto1 communication between reader and Mifare PICC
|
391
382
|
#
|
392
383
|
# PICC must be selected before calling for authentication
|
@@ -414,60 +405,55 @@ class MFRC522
|
|
414
405
|
|
415
406
|
# Append CRC to buffer and check CRC or Mifare acknowledge
|
416
407
|
def picc_transceive(send_data, accept_timeout = false)
|
417
|
-
send_data =
|
408
|
+
send_data = send_data.dup
|
409
|
+
send_data.append_crc16 if @built_in_crc_disabled
|
418
410
|
|
419
|
-
puts "Sending Data: #{send_data.
|
411
|
+
puts "Sending Data: #{send_data.to_bytehex}" if ENV['DEBUG']
|
420
412
|
|
421
413
|
# Transfer data
|
422
414
|
status, received_data, valid_bits = communicate_with_picc(PCD_Transceive, send_data)
|
423
|
-
return
|
415
|
+
return if status == :status_picc_timeout && accept_timeout
|
424
416
|
raise PICCTimeoutError if status == :status_picc_timeout
|
425
417
|
raise CommunicationError, status if status != :status_ok
|
426
418
|
|
427
|
-
puts "Received Data: #{received_data.
|
428
|
-
|
429
|
-
# Data exists, check CRC and return
|
430
|
-
if received_data.size > 1
|
431
|
-
raise IncorrectCRCError unless check_crc(received_data)
|
419
|
+
puts "Received Data: #{received_data.to_bytehex}" if ENV['DEBUG']
|
420
|
+
puts "Valid bits: #{valid_bits}" if ENV['DEBUG']
|
432
421
|
|
433
|
-
|
422
|
+
# Data exists, check CRC
|
423
|
+
if received_data.size > 2 && @built_in_crc_disabled
|
424
|
+
raise IncorrectCRCError unless received_data.check_crc16(true)
|
434
425
|
end
|
435
426
|
|
436
|
-
|
437
|
-
raise MifareNakError, received_data[0] if received_data[0] != PICC_MF_ACK
|
438
|
-
|
439
|
-
received_data
|
427
|
+
return received_data, valid_bits
|
440
428
|
end
|
441
429
|
|
442
|
-
private
|
443
|
-
|
444
430
|
# Read from SPI communication
|
445
431
|
def read_spi(reg)
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
432
|
+
read_spi_bulk(reg).first
|
433
|
+
end
|
434
|
+
|
435
|
+
def read_spi_bulk(*regs)
|
436
|
+
regs.flatten!
|
437
|
+
|
438
|
+
payload = regs.map{ |reg| ((reg & 0x3F) << 1) | 0x80 }
|
439
|
+
payload << 0x00
|
440
|
+
|
441
|
+
result = @spi_driver.transfer(payload, @spi_speed, @spi_delay)
|
442
|
+
|
443
|
+
# discard first byte
|
444
|
+
result.shift
|
445
|
+
|
446
|
+
result
|
458
447
|
end
|
459
448
|
|
460
449
|
# Write to SPI communication
|
461
450
|
def write_spi(reg, values)
|
462
|
-
|
463
|
-
|
464
|
-
spi.bit_order Spi::MSBFIRST
|
465
|
-
spi.clock @spi_spd
|
451
|
+
spi_addr = (reg & 0x3F) << 1
|
452
|
+
payload = [spi_addr, *values]
|
466
453
|
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
end
|
454
|
+
@spi_driver.transfer(payload, @spi_speed, @spi_delay)
|
455
|
+
|
456
|
+
true
|
471
457
|
end
|
472
458
|
|
473
459
|
# Set bits by mask
|
@@ -510,15 +496,16 @@ class MFRC522
|
|
510
496
|
|
511
497
|
# Check for error
|
512
498
|
error = read_spi(ErrorReg)
|
513
|
-
return :
|
499
|
+
return :status_buffer_overflow if (error & 0x10) != 0
|
500
|
+
return :status_crc_error if (error & 0x04) != 0
|
501
|
+
return :status_parity_error if (error & 0x02) != 0
|
502
|
+
return :status_protocol_error if (error & 0x01) != 0
|
514
503
|
|
515
504
|
# Receiving data
|
516
505
|
received_data = []
|
517
506
|
data_length = read_spi(FIFOLevelReg)
|
518
|
-
|
519
|
-
|
520
|
-
received_data << data
|
521
|
-
data_length -=1
|
507
|
+
if data_length > 0
|
508
|
+
received_data = read_spi_bulk(Array.new(data_length, FIFODataReg))
|
522
509
|
end
|
523
510
|
valid_bits = read_spi(ControlReg) & 0x07
|
524
511
|
|
@@ -527,39 +514,4 @@ class MFRC522
|
|
527
514
|
|
528
515
|
return status, received_data, valid_bits
|
529
516
|
end
|
530
|
-
|
531
|
-
def calculate_crc(data)
|
532
|
-
write_spi(CommandReg, PCD_Idle) # Stop any active command.
|
533
|
-
write_spi(DivIrqReg, 0x04) # Clear the CRCIRq interrupt request bit
|
534
|
-
write_spi_set_bitmask(FIFOLevelReg, 0x80) # FlushBuffer = 1, FIFO initialization
|
535
|
-
write_spi(FIFODataReg, data) # Write data to the FIFO
|
536
|
-
write_spi(CommandReg, PCD_CalcCRC) # Start the calculation
|
537
|
-
|
538
|
-
# Wait for the command to complete
|
539
|
-
i = 5000
|
540
|
-
loop do
|
541
|
-
irq = read_spi(DivIrqReg)
|
542
|
-
break if (irq & 0x04) != 0
|
543
|
-
raise PCDTimeoutError, 'Error calculating CRC' if i == 0
|
544
|
-
i -= 1
|
545
|
-
end
|
546
|
-
|
547
|
-
write_spi(CommandReg, PCD_Idle) # Stop calculating CRC for new content in the FIFO.
|
548
|
-
|
549
|
-
[read_spi(CRCResultRegL), read_spi(CRCResultRegH)]
|
550
|
-
end
|
551
|
-
|
552
|
-
def append_crc(data)
|
553
|
-
data + calculate_crc(data)
|
554
|
-
end
|
555
|
-
|
556
|
-
def check_crc(data)
|
557
|
-
raise UnexpectedDataError, 'Data too short for CRC check' if data.size < 3
|
558
|
-
|
559
|
-
data = data.dup
|
560
|
-
crc = data.pop(2)
|
561
|
-
|
562
|
-
crc == calculate_crc(data)
|
563
|
-
end
|
564
|
-
|
565
517
|
end
|