class ISO144434

Constants

CMD_ADDITIONAL_FRAME
CMD_DESELECT
CMD_PPS
CMD_RATS
CMD_SUCCESS
FSCI_to_FSC

Public Class Methods

new(pcd, uid, sak) click to toggle source
Calls superclass method PICC::new
# File lib/iso144434.rb, line 10
def initialize(pcd, uid, sak)
  super

  @cid = 0x00 # We don't support CID
  @fsc = 16 # Assume PICC only supports 16 bytes frame
  @fwt = 256 # 77.33ms(256 ticks) default frame waiting time

  @support_cid = false
  @support_nad = false
  @block_number = 0
  @selected = false
end

Public Instance Methods

deselect() click to toggle source

Send S(DESELECT)

# File lib/iso144434.rb, line 49
def deselect
  buffer = [CMD_DESELECT]
  received_data = @pcd.picc_transceive(buffer)

  if received_data[0] & 0xF7 == CMD_DESELECT
    @selected = false
    true
  else
    false
  end
end
halt() click to toggle source
Calls superclass method PICC#halt
# File lib/iso144434.rb, line 133
def halt
  deselect rescue nil
  super
end
resume_communication() click to toggle source
Calls superclass method PICC#resume_communication
# File lib/iso144434.rb, line 128
def resume_communication
  deselect rescue nil
  super
end
select() click to toggle source

ISO/IEC 14443-4 select

# File lib/iso144434.rb, line 24
def select
  # Send RATS (Request for Answer To Select)
  buffer = [CMD_RATS, 0x50 | @cid]
  received_data = @pcd.picc_transceive(buffer)

  dr, ds = process_ats(received_data)

  # Send PPS (Protocol and Parameter Selection Request)
  buffer = [CMD_PPS | @cid, 0x11, (ds << 2) | dr]
  received_data = @pcd.picc_transceive(buffer)
  raise UnexpectedDataError, 'Incorrect response' if received_data[0] != (0xD0 | @cid)

  # Set PCD baud rate
  @pcd.transceiver_baud_rate(:tx, dr)
  @pcd.transceiver_baud_rate(:rx, ds)

  @block_number = 0
  @max_frame_size = [64, @fsc].min
  @max_inf_size = @max_frame_size - 3 # PCB + CRC16
  @max_inf_size -= 1 if @support_cid
  @max_inf_size -= 1 if @support_nad
  @selected = true
end
transceive(send_data) click to toggle source

Wrapper for handling ISO protocol

# File lib/iso144434.rb, line 62
def transceive(send_data)
  # Split data according to max buffer size
  send_data = [send_data] unless send_data.is_a? Array
  chained_data = send_data.each_slice(@max_inf_size).to_a

  # Initialize I-block
  pcb = 0x02

  # Send chained data
  until chained_data.empty?
    pcb &= 0xEF # Reset chaining indicator
    pcb |= 0x10 if chained_data.size > 1 # Set chaining
    pcb |= @block_number # Set block number
    data = chained_data.shift

    buffer = [pcb] + data

    finished = false
    until finished
      received_data = handle_wtx(buffer)

      # Retreive response pcb from data
      r_pcb = received_data[0]

      # Received ACK
      if r_pcb & 0xF6 == 0xA2
        # If ACK matches current block number means success
        # Otherwise transmit it again
        if (pcb & 0x01) == (r_pcb & 0x01)
          finished = true
        end
      else
        finished = true
      end
    end

    @block_number ^= 1 # toggle block number for next frame
  end

  received_chained_data = [received_data]

  # Receive chained data
  while r_pcb & 0x10 != 0
    ack = 0xA2 | @block_number # Set block number
    received_data = handle_wtx([ack]) # Send ACK to receive next frame

    r_pcb = received_data[0]

    received_chained_data << received_data

    @block_number ^= 1 # toggle block number for next frame
  end

  # Collect INF from chain
  inf = []
  received_chained_data.each do |data|
    inf_position = 1
    inf_position += 1 if data[0] & 0x08 != 0 # CID present
    inf_position += 1 if data[0] & 0x04 != 0 # NAD present

    inf.concat(data[inf_position..-1])
  end

  inf
end

Private Instance Methods

convert_iso_baud_rate_to_pcd_setting(value) click to toggle source
# File lib/iso144434.rb, line 140
def convert_iso_baud_rate_to_pcd_setting(value)
  # ISO
  # 0b000: 106kBd, 0b001: 212kBd, 0b010: 424kBd, 0b100: 848kBd
  # MFRC522 register
  # 0b000: 106kBd, 0b001: 212kBd, 0b010: 424kBd, 0b011: 848kBd
  x = (value >> 2) & 0x01
  y = (value >> 1) & 0x01
  z = value & 0x01

  ((x | y) << 1) + (x | (~y & z))
end
handle_wtx(data) click to toggle source
# File lib/iso144434.rb, line 212
def handle_wtx(data)
  24.times do
    begin
      received_data = @pcd.picc_transceive(data)
    rescue CommunicationError => e
      raise e unless e.is_a? PICCTimeoutError

      # Try sending NAK when timeout
      nak = 0xB2 | @block_number
      data = [nak]
      next
    end
    
    pcb = received_data[0]

    # WTX detected
    if pcb & 0xF7 == 0xF2
      inf_position = (pcb & 0x08 != 0) ? 2 : 1
      wtxm = received_data[inf_position] & 0x3F

      # Set temporary timer
      @pcd.internal_timer(@fwt * wtxm)

      # Set WTX response
      data = [0xF2, wtxm]
    else
      # Set timer back to FWT
      @pcd.internal_timer(@fwt)

      return received_data
    end
  end

  raise PICCTimeoutError
end
process_ats(ats) click to toggle source

Gether information from ATS (Answer to Select)

# File lib/iso144434.rb, line 153
def process_ats(ats)
  position = 1
  t0 = ats[position] # Format byte

  fsci = t0 & 0x0F # PICC buffer size integer
  y1 = (t0 >> 4) & 0x07 # Optional frame(TA, TB, TC) indicator
  @fsc = FSCI_to_FSC.fetch(fsci) # Convert buffer size integer to bytes
  dr = 0 # default baud rate 106kBd
  ds = 0

  # Frame: TA
  if y1 & 0x01 != 0
    position += 1
    ta = ats[position]

    dr = ta & 0x07 # PCD to PICC baud rate
    ds = (ta >> 4) & 0x07 # PICC to PCD baud rate

    # Convert fastest baud rate to PCD setting
    # dr = convert_iso_baud_rate_to_pcd_setting(dr)
    # ds = convert_iso_baud_rate_to_pcd_setting(ds)

    # FIXME: baud rate fixed to 106kBd
    # until author can confirm negotiation works
    dr = 0
    ds = 0
  end

  # Frame: TB
  if y1 & 0x02 != 0
    position += 1
    tb = ats[position]

    fwi = (tb >> 4) & 0x0F # Frame wating integer
    sgfi = tb & 0x0F # Start-up frame guard integer

    # Convert integers to real time
    @fwt = (1 << fwi)
    sgft = (1 << sgfi)

    # Set frame waiting time
    @pcd.internal_timer(@fwt)
  end

  # Get info about CID or NAD
  if y1 & 0x04 != 0
    position += 1
    tc = ats[position]

    @support_cid = true if tc & 0x02 != 0
    @support_nad = true if tc & 0x01 != 0
  end

  # Start-up guard time
  sleep 0.000302 * sgft

  return dr, ds
end