st-ten-1/src/lib/nfc/clf/acr122.py

243 lines
9.4 KiB
Python
Raw Normal View History

# -*- coding: latin-1 -*-
# -----------------------------------------------------------------------------
# Copyright 2011, 2017 Stephen Tiedemann <stephen.tiedemann@gmail.com>
#
# Licensed under the EUPL, Version 1.1 or - as soon they
# will be approved by the European Commission - subsequent
# versions of the EUPL (the "Licence");
# You may not use this work except in compliance with the
# Licence.
# You may obtain a copy of the Licence at:
#
# https://joinup.ec.europa.eu/software/page/eupl
#
# Unless required by applicable law or agreed to in
# writing, software distributed under the Licence is
# distributed on an "AS IS" basis,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
# express or implied.
# See the Licence for the specific language governing
# permissions and limitations under the Licence.
# -----------------------------------------------------------------------------
"""Device driver for the Arygon ACR122U contactless reader.
The Arygon ACR122U is a PC/SC compliant contactless reader that
connects via USB and uses the USB CCID profile. It is normally
intented to be used with a PC/SC stack but this driver interfaces
directly with the inbuilt PN532 chipset by tunneling commands through
the PC/SC Escape command. The driver is limited in functionality
because the embedded microprocessor (that implements the PC/SC stack)
also operates the PN532; it does not allow all commands to pass as
desired and reacts on chip responses with its own (legitimate)
interpretation of state.
========== ======= ============
function support remarks
========== ======= ============
sense_tta yes Type 1 (Topaz) Tags are not supported
sense_ttb yes ATTRIB by firmware voided with S(DESELECT)
sense_ttf yes
sense_dep yes
listen_tta no
listen_ttb no
listen_ttf no
listen_dep no
========== ======= ============
"""
import nfc.clf
from . import pn532
import os
import errno
import struct
from binascii import hexlify
import logging
log = logging.getLogger(__name__)
def init(transport):
device = Device(Chipset(transport))
device._vendor_name = transport.manufacturer_name
device._device_name = transport.product_name.split()[0]
return device
class Device(pn532.Device):
# Device driver class for the ACR122U.
def __init__(self, chipset):
super(Device, self).__init__(chipset, logger=log)
def sense_tta(self, target):
"""Activate the RF field and probe for a Type A Target at 106
kbps. Other bitrates are not supported. Type 1 Tags are not
supported because the device does not allow to send the
correct RID command (even though the PN532 does).
"""
return super(Device, self).sense_tta(target)
def sense_ttb(self, target):
"""Activate the RF field and probe for a Type B Target.
The RC-S956 can discover Type B Targets (Type 4B Tag) at 106
kbps. For a Type 4B Tag the firmware automatically sends an
ATTRIB command that configures the use of DID and 64 byte
maximum frame size. The driver reverts this configuration with
a DESELECT and WUPB command to return the target prepared for
activation (which nfcpy does in the tag activation code).
"""
return super(Device, self).sense_ttb(target)
def sense_ttf(self, target):
"""Activate the RF field and probe for a Type F Target. Bitrates 212
and 424 kpbs are supported.
"""
return super(Device, self).sense_ttf(target)
def sense_dep(self, target):
"""Search for a DEP Target. Both passive and passive communication
mode are supported.
"""
return super(Device, self).sense_dep(target)
def listen_tta(self, target, timeout):
"""Listen as Type A Target is not supported."""
info = "{device} does not support listen as Type A Target"
raise nfc.clf.UnsupportedTargetError(info.format(device=self))
def listen_ttb(self, target, timeout):
"""Listen as Type B Target is not supported."""
info = "{device} does not support listen as Type B Target"
raise nfc.clf.UnsupportedTargetError(info.format(device=self))
def listen_ttf(self, target, timeout):
"""Listen as Type F Target is not supported."""
info = "{device} does not support listen as Type F Target"
raise nfc.clf.UnsupportedTargetError(info.format(device=self))
def listen_dep(self, target, timeout):
"""Listen as DEP Target is not supported."""
info = "{device} does not support listen as DEP Target"
raise nfc.clf.UnsupportedTargetError(info.format(device=self))
def turn_on_led_and_buzzer(self):
"""Buzz and turn red."""
self.chipset.set_buzzer_and_led_to_active()
def turn_off_led_and_buzzer(self):
"""Back to green."""
self.chipset.set_buzzer_and_led_to_default()
class Chipset(pn532.Chipset):
# Maximum size of a host command frame to the contactless chip.
host_command_frame_max_size = 254
# Supported BrTy (baud rate / modulation type) values for the
# InListPassiveTarget command. Corresponds to 106 kbps Type A, 212
# kbps Type F, 424 kbps Type F, and 106 kbps Type B. The value for
# 106 kbps Innovision Jewel Tag (although supported by PN532) is
# removed because the RID command can not be send.
in_list_passive_target_brty_range = (0, 1, 2, 3)
def __init__(self, transport):
self.transport = transport
# read ACR122U firmware version string
reader_version = self.ccid_xfr_block(bytearray.fromhex("FF00480000"))
if not reader_version.startswith(b"ACR122U"):
log.error("failed to retrieve ACR122U version string")
raise IOError(errno.ENODEV, os.strerror(errno.ENODEV))
if int(chr(reader_version[7])) < 2:
log.error("{0} not supported, need 2.x".format(reader_version[7:]))
raise IOError(errno.ENODEV, os.strerror(errno.ENODEV))
log.debug("initialize " + reader_version.decode())
# set icc power on
log.debug("CCID ICC-POWER-ON")
frame = bytearray.fromhex("62000000000000000000")
transport.write(frame)
transport.read(100)
# disable autodetection
log.debug("Set PICC Operating Parameters")
self.ccid_xfr_block(bytearray.fromhex("FF00517F00"))
# switch red/green led off/on
log.debug("Configure Buzzer and LED")
self.set_buzzer_and_led_to_default()
super(Chipset, self).__init__(transport, logger=log)
def close(self):
self.ccid_xfr_block(bytearray.fromhex("FF00400C0400000000"))
self.transport.close()
self.transport = None
def set_buzzer_and_led_to_default(self):
"""Turn off buzzer and set LED to default (green only). """
self.ccid_xfr_block(bytearray.fromhex("FF00400E0400000000"))
def set_buzzer_and_led_to_active(self, duration_in_ms=300):
"""Turn on buzzer and set LED to red only. The timeout here must exceed
the total buzzer/flash duration defined in bytes 5-8. """
duration_in_tenths_of_second = int(min(duration_in_ms / 100, 255))
timeout_in_seconds = (duration_in_tenths_of_second + 1) / 10.0
data = "FF00400D04{:02X}000101".format(duration_in_tenths_of_second)
self.ccid_xfr_block(bytearray.fromhex(data),
timeout=timeout_in_seconds)
def send_ack(self):
# Send an ACK frame, usually to terminate most recent command.
self.ccid_xfr_block(Chipset.ACK)
def ccid_xfr_block(self, data, timeout=0.1):
"""Encapsulate host command *data* into an PC/SC Escape command to
send to the device and extract the chip response if received
within *timeout* seconds.
"""
frame = struct.pack("<BI5B", 0x6F, len(data), 0, 0, 0, 0, 0) + data
self.transport.write(bytearray(frame))
frame = self.transport.read(int(timeout * 1000))
if not frame or len(frame) < 10:
log.error("insufficient data for decoding ccid response")
raise IOError(errno.EIO, os.strerror(errno.EIO))
if frame[0] != 0x80:
log.error("expected a RDR_to_PC_DataBlock")
raise IOError(errno.EIO, os.strerror(errno.EIO))
if len(frame) != 10 + struct.unpack("<I", memoryview(frame)[1:5])[0]:
log.error("RDR_to_PC_DataBlock length mismatch")
raise IOError(errno.EIO, os.strerror(errno.EIO))
return frame[10:]
def command(self, cmd_code, cmd_data, timeout):
"""Send a host command and return the chip response.
"""
log.log(logging.DEBUG-1, "{} {}".format(self.CMD[cmd_code],
hexlify(cmd_data).decode()))
frame = bytearray([0xD4, cmd_code]) + bytearray(cmd_data)
frame = bytearray([0xFF, 0x00, 0x00, 0x00, len(frame)]) + frame
frame = self.ccid_xfr_block(frame, timeout)
if not frame or len(frame) < 4:
log.error("insufficient data for decoding chip response")
raise IOError(errno.EIO, os.strerror(errno.EIO))
if not (frame[0] == 0xD5 and frame[1] == cmd_code + 1):
log.error("received invalid chip response")
raise IOError(errno.EIO, os.strerror(errno.EIO))
if not (frame[-2] == 0x90 and frame[-1] == 0x00):
log.error("received pseudo apdu with error status")
raise IOError(errno.EIO, os.strerror(errno.EIO))
return frame[2:-2]