mfrc522 1.0.2 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|