1252 lines
52 KiB
Python
1252 lines
52 KiB
Python
# -*- coding: latin-1 -*-
|
|
# -----------------------------------------------------------------------------
|
|
# Copyright 2009, 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.
|
|
# -----------------------------------------------------------------------------
|
|
|
|
import src.lib.nfc.tag
|
|
import src.lib.nfc.dep
|
|
import src.lib.nfc.llcp
|
|
from . import device
|
|
|
|
import binascii
|
|
import os
|
|
import re
|
|
import time
|
|
import errno
|
|
import threading
|
|
|
|
import logging
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
|
def print_data(data):
|
|
return 'None' if data is None else binascii.hexlify(data).decode('latin')
|
|
|
|
|
|
class ContactlessFrontend(object):
|
|
"""This class is the main interface for working with contactless
|
|
devices. The :meth:`connect` method provides easy access to the
|
|
contactless functionality through automated discovery of remote
|
|
cards and devices and activation of appropiate upper level
|
|
protocols for further interaction. The :meth:`sense`,
|
|
:meth:`listen` and :meth:`exchange` methods provide a low-level
|
|
interface for more specialized tasks.
|
|
|
|
An instance of the :class:`ContactlessFrontend` class manages a
|
|
single contactless device locally connect through either USB, TTY
|
|
or COM port. A special UDP port driver allows for emulation of a
|
|
contactless device that connects through UDP to another emulated
|
|
contactless device for test and development of higher layer
|
|
functions.
|
|
|
|
A locally connected contactless device can be opened by either
|
|
supplying a *path* argument when an an instance of the contactless
|
|
frontend class is created or by calling :meth:`open` at a later
|
|
time. In either case the *path* argument must be constructed as
|
|
described in :meth:`open` and the same exceptions may occur. The
|
|
difference is that :meth:`open` returns False if a device could
|
|
not be found whereas the initialization method raises
|
|
:exc:`~exceptions.IOError` with :data:`errno.ENODEV`.
|
|
|
|
The methods of the :class:`ContactlessFrontend` class are
|
|
thread-safe.
|
|
|
|
"""
|
|
def __init__(self, path=None):
|
|
self.device = None
|
|
self.target = None
|
|
self.lock = threading.Lock()
|
|
if path and not self.open(path):
|
|
raise IOError(errno.ENODEV, os.strerror(errno.ENODEV))
|
|
|
|
def open(self, path):
|
|
"""Open a contactless reader identified by the search *path*.
|
|
|
|
The :meth:`open` method searches and then opens a contactless
|
|
reader device for further communication. The *path* argument
|
|
can be flexibly constructed to identify more or less precisely
|
|
the device to open. A *path* that only partially identifies a
|
|
device is completed by search. The first device that is found
|
|
and successfully opened causes :meth:`open` to return True. If
|
|
no device is found return value is False. If a device was
|
|
found but could not be opened then :meth:`open` returns False
|
|
if *path* was partial or raise :exc:`~exceptions.IOError` if
|
|
*path* was fully qualified. Typical I/O error reasons are
|
|
:data:`errno.EACCES` if the calling process has insufficient
|
|
access rights or :data:`errno.EBUSY` if the device is used by
|
|
another process.
|
|
|
|
A path is constructed as follows:
|
|
|
|
``usb[:vendor[:product]]``
|
|
|
|
with optional *vendor* and *product* as four digit
|
|
hexadecimal numbers. For example, ``usb:054c:06c3`` would
|
|
open the first Sony RC-S380 reader while ``usb:054c`` would
|
|
open the first Sony reader found on USB.
|
|
|
|
``usb[:bus[:device]]``
|
|
|
|
with optional *bus* and *device* number as three-digit
|
|
decimals. For example, ``usb:001:023`` would open the
|
|
device enumerated as number 23 on bus 1 while ``usb:001``
|
|
would open the first device found on bust 1. Note that a
|
|
new device number is generated every time the device is
|
|
plugged into USB. Bus and device numbers are shown by
|
|
``lsusb``.
|
|
|
|
``tty:port:driver``
|
|
|
|
with mandatory *port* and *driver* name. This is for Posix
|
|
systems to open the serial port ``/dev/tty<port>`` and use
|
|
the driver module ``nfc/dev/<driver>.py`` for access. For
|
|
example, ``tty:USB0:arygon`` would open ``/dev/ttyUSB0``
|
|
and load the Arygon APPx/ADRx driver.
|
|
|
|
``com:port:driver``
|
|
|
|
with mandatory *port* and *driver* name. This is for
|
|
Windows systems to open the serial port ``COM<port>`` and
|
|
use the driver module ``nfc/dev/<driver>.py`` for access.
|
|
|
|
``udp[:host][:port]``
|
|
|
|
with optional *host* name or address and *port*
|
|
number. This will emulate a communication channel over
|
|
UDP/IP. The defaults for *host* and *port* are
|
|
``localhost:54321``.
|
|
|
|
"""
|
|
if not isinstance(path, str):
|
|
raise TypeError("expecting a string type argument *path*")
|
|
if not len(path) > 0:
|
|
raise ValueError("argument *path* must not be empty")
|
|
|
|
# Close current device driver if this is not the first
|
|
# open. This allows to use several devices sequentially or
|
|
# re-initialize a device.
|
|
self.close()
|
|
|
|
# Acquire the lock and search for a device on *path*
|
|
with self.lock:
|
|
log.info("searching for reader on path " + path)
|
|
self.device = device.connect(path)
|
|
if self.device:
|
|
log.info("using {0}".format(self.device))
|
|
else:
|
|
log.error("no reader available on path " + path)
|
|
return bool(self.device)
|
|
|
|
def close(self):
|
|
"""Close the contacless reader device."""
|
|
with self.lock:
|
|
if self.device is not None:
|
|
try:
|
|
self.device.close()
|
|
except IOError:
|
|
pass
|
|
self.device = None
|
|
|
|
def connect(self, **options):
|
|
"""Connect with a Target or Initiator
|
|
|
|
The calling thread is blocked until a single activation and
|
|
deactivation has completed or a callback function supplied as
|
|
the keyword argument ``terminate`` returns a true value. The
|
|
example below makes :meth:`~connect()` return after 5 seconds,
|
|
regardless of whether a peer device was connected or not.
|
|
|
|
>>> import nfc, time
|
|
>>> clf = nfc.ContactlessFrontend('usb')
|
|
>>> after5s = lambda: time.time() - started > 5
|
|
>>> started = time.time(); clf.connect(llcp={}, terminate=after5s)
|
|
|
|
Connect options are given as keyword arguments with dictionary
|
|
values. Possible options are:
|
|
|
|
* ``rdwr={key: value, ...}`` - options for reader/writer
|
|
* ``llcp={key: value, ...}`` - options for peer to peer
|
|
* ``card={key: value, ...}`` - options for card emulation
|
|
|
|
**Reader/Writer Options**
|
|
|
|
'targets' : iterable
|
|
A list of bitrate and technology type strings that will
|
|
produce the :class:`~nfc.clf.RemoteTarget` objects to
|
|
discover. The default is ``('106A', '106B', '212F')``.
|
|
|
|
'on-startup' : function(targets)
|
|
This function is called before any attempt to discover a
|
|
remote card. The *targets* argument provides a list of
|
|
:class:`RemoteTarget` objects prepared from the 'targets'
|
|
bitrate and technology type strings. The function must
|
|
return a list of of those :class:`RemoteTarget` objects
|
|
that shall be finally used for discovery, those targets may
|
|
have additional attributes. An empty list or anything else
|
|
that evaluates false will remove the 'rdwr' option
|
|
completely.
|
|
|
|
'on-discover' : function(target)
|
|
This function is called when a :class:`RemoteTarget` has
|
|
been discovered. The *target* argument contains the
|
|
technology type specific discovery responses and should be
|
|
evaluated for multi-protocol support. The target will be
|
|
further activated only if this function returns a true
|
|
value. The default function depends on the 'llcp' option,
|
|
if present then the function returns True only if the
|
|
target does not indicate peer to peer protocol support,
|
|
otherwise it returns True for all targets.
|
|
|
|
'on-connect' : function(tag)
|
|
This function is called when a remote tag has been
|
|
activated. The *tag* argument is an instance of class
|
|
:class:`nfc.tag.Tag` and can be used for tag reading and
|
|
writing within the callback or in a separate thread. Any
|
|
true return value instructs :meth:`connect` to wait until
|
|
the tag is no longer present and then return True, any
|
|
false return value implies immediate return of the
|
|
:class:`nfc.tag.Tag` object.
|
|
|
|
'on-release' : function(tag)
|
|
This function is called when the presence check was run
|
|
(the 'on-connect' function returned a true value) and
|
|
determined that communication with the *tag* has become
|
|
impossible, or when the 'terminate' function returned a
|
|
true value. The *tag* object may be used for cleanup
|
|
actions but not for communication.
|
|
|
|
'iterations' : integer
|
|
This determines the number of sense cycles performed
|
|
between calls to the terminate function. Each iteration
|
|
searches once for all specified targets. The default value
|
|
is 5 iterations and between each iteration is a waiting
|
|
time determined by the 'interval' option described below.
|
|
As an effect of math there will be no waiting time if
|
|
iterations is set to 1.
|
|
|
|
'interval' : float
|
|
This determines the waiting time between iterations. The
|
|
default value of 0.5 seconds is considered a sensible
|
|
tradeoff between responsiveness in terms of tag discovery
|
|
and power consumption. It should be clear that changing
|
|
this value will impair one or the other. There is no free
|
|
beer.
|
|
|
|
'beep-on-connect': boolean
|
|
If the device supports beeping or flashing an LED,
|
|
automatically perform this functionality when a tag is
|
|
successfully detected AND the 'on-connect' function
|
|
returns a true value. Defaults to True.
|
|
|
|
.. sourcecode:: python
|
|
|
|
import nfc
|
|
|
|
def on_startup(targets):
|
|
for target in targets:
|
|
target.sensf_req = bytearray.fromhex("0012FC0000")
|
|
return targets
|
|
|
|
def on_connect(tag):
|
|
print(tag)
|
|
|
|
rdwr_options = {
|
|
'targets': ['212F', '424F'],
|
|
'on-startup': on_startup,
|
|
'on-connect': on_connect,
|
|
}
|
|
with nfc.ContactlessFrontend('usb') as clf:
|
|
tag = clf.connect(rdwr=rdwr_options)
|
|
if tag.ndef:
|
|
print(tag.ndef.message.pretty())
|
|
|
|
**Peer To Peer Options**
|
|
|
|
'on-startup' : function(llc)
|
|
This function is called before any attempt to establish
|
|
peer to peer communication. The *llc* argument provides the
|
|
:class:`~nfc.llcp.llc.LogicalLinkController` that may be
|
|
used to allocate and bind listen sockets for local
|
|
services. The function should return the *llc* object if
|
|
activation shall continue. Any other value removes the
|
|
'llcp' option.
|
|
|
|
'on-connect' : function(llc)
|
|
This function is called when peer to peer communication is
|
|
successfully established. The *llc* argument provides the
|
|
now activated :class:`~nfc.llcp.llc.LogicalLinkController`
|
|
ready for allocation of client communication sockets and
|
|
data exchange in separate work threads. The function should
|
|
a true value return more or less immediately, unless it
|
|
wishes to handle the logical link controller run loop by
|
|
itself and anytime later return a false value.
|
|
|
|
'on-release' : function(llc)
|
|
This function is called when the symmetry loop was run (the
|
|
'on-connect' function returned a true value) and determined
|
|
that communication with the remote peer has become
|
|
impossible, or when the 'terminate' function returned a
|
|
true value. The *llc* object may be used for cleanup
|
|
actions but not for communication.
|
|
|
|
'role' : string
|
|
This attribute determines whether the local device will
|
|
restrict itself to either ``'initiator'`` or ``'target'``
|
|
mode of operation. As Initiator the local device will try
|
|
to discover a remote device. As Target it waits for being
|
|
discovered. The default is to alternate between both roles.
|
|
|
|
'miu' : integer
|
|
This attribute sets the maximum information unit size that
|
|
is announced to the remote device during link activation.
|
|
The default and also smallest possible value is 128 bytes.
|
|
|
|
'lto' : integer
|
|
This attribute sets the link timeout value (given in
|
|
milliseconds) that is announced to the remote device during
|
|
link activation. It informs the remote device that if the
|
|
local device does not return a protocol data unit before
|
|
the timeout expires, the communication link is broken and
|
|
can not be recovered. The *lto* is an important part of the
|
|
user experience, it ultimately tells when the user should
|
|
no longer expect communication to continue. The default
|
|
value is 500 millisecond.
|
|
|
|
'agf' : boolean
|
|
Some early phone implementations did not properly handle
|
|
aggregated protocol data units. This attribute allows to
|
|
disable the use af aggregation at the cost of efficiency.
|
|
Aggregation is disabled with a false value. The default
|
|
is to use aggregation.
|
|
|
|
'brs' : integer
|
|
For the local device in Initiator role the bit rate
|
|
selector determines the the bitrate to negotiate with the
|
|
remote Target. The value may be 0, 1, or 2 for 106, 212, or
|
|
424 kbps, respectively. The default is to negotiate 424
|
|
kbps.
|
|
|
|
'acm' : boolean
|
|
For the local device in Initiator role this attribute
|
|
determines whether a remote Target may also be activated in
|
|
active communication mode. In active communication mode
|
|
both peer devices mutually generate a radio field when
|
|
sending. The default is to use passive communication mode.
|
|
|
|
'rwt' : float
|
|
For the local device in Target role this attribute sets the
|
|
response waiting time announced during link activation. The
|
|
response waiting time is a medium access layer (NFC-DEP)
|
|
value that indicates when the remote Initiator shall
|
|
attempt error recovery after missing a Target response. The
|
|
value is the waiting time index *wt* that determines the
|
|
effective response waiting time by the formula ``rwt =
|
|
4096/13.56E6 * pow(2, wt)``. The value shall not be greater
|
|
than 14. The default value is 8 and yields an effective
|
|
response waiting time of 77.33 ms.
|
|
|
|
'lri' : integer
|
|
For the local device in Initiator role this attribute sets
|
|
the length reduction for medium access layer (NFC-DEP)
|
|
information frames. The value may be 0, 1, 2, or 3 for a
|
|
maximum payload size of 64, 128, 192, or 254 bytes,
|
|
respectively. The default value is 3.
|
|
|
|
'lrt' : integer
|
|
For the local device in Target role this attribute sets
|
|
the length reduction for medium access layer (NFC-DEP)
|
|
information frames. The value may be 0, 1, 2, or 3 for a
|
|
maximum payload size of 64, 128, 192, or 254 bytes,
|
|
respectively. The default value is 3.
|
|
|
|
.. sourcecode:: python
|
|
|
|
import nfc
|
|
import nfc.llcp
|
|
import threading
|
|
|
|
def server(socket):
|
|
message, address = socket.recvfrom()
|
|
socket.sendto("It's me!", address)
|
|
socket.close()
|
|
|
|
def client(socket):
|
|
socket.sendto("Hi there!", address=32)
|
|
socket.close()
|
|
|
|
def on_startup(llc):
|
|
socket = nfc.llcp.Socket(llc, nfc.llcp.LOGICAL_DATA_LINK)
|
|
socket.bind(address=32)
|
|
threading.Thread(target=server, args=(socket,)).start()
|
|
return llc
|
|
|
|
def on_connect(llc):
|
|
socket = nfc.llcp.Socket(llc, nfc.llcp.LOGICAL_DATA_LINK)
|
|
threading.Thread(target=client, args=(socket,)).start()
|
|
return True
|
|
|
|
llcp_options = {
|
|
'on-startup': on_startup,
|
|
'on-connect': on_connect,
|
|
}
|
|
with nfc.ContactlessFrontend('usb') as clf:
|
|
clf.connect(llcp=llcp_options)
|
|
print("link terminated")
|
|
|
|
**Card Emulation Options**
|
|
|
|
'on-startup' : function(target)
|
|
This function is called to prepare a local target for
|
|
discovery. The input argument is a fresh instance of an
|
|
unspecific :class:`LocalTarget` that can be set to the
|
|
desired bitrate and modulation type and populated with the
|
|
type specific discovery responses (see :meth:`listen` for
|
|
response data that is needed). The fully specified target
|
|
object must then be returned.
|
|
|
|
'on-discover' : function(target)
|
|
This function is called when the :class:`LocalTarget` has
|
|
been discovered. The *target* argument contains the
|
|
technology type specific discovery commands. The target
|
|
will be further activated only if this function returns a
|
|
true value. The default function always returns True.
|
|
|
|
'on-connect' : function(tag)
|
|
This function is called when the local target was
|
|
discovered and a :class:`nfc.tag.TagEmulation` object
|
|
successfully initialized. The function receives the
|
|
emulated *tag* object which stores the first command
|
|
received after inialization as ``tag.cmd``. The function
|
|
should return a true value if the tag.process_command() and
|
|
tag.send_response() methods shall be called repeatedly
|
|
until either the remote device terminates communication or
|
|
the 'terminate' function returns a true value. The function
|
|
should return a false value if the :meth:`connect` method
|
|
shall return immediately with the emulated *tag* object.
|
|
|
|
'on-release' : function(tag)
|
|
This function is called when the Target was released by the
|
|
Initiator or simply moved away, or if the terminate
|
|
callback function has returned a true value. The emulated
|
|
*tag* object may be used for cleanup actions but not for
|
|
communication.
|
|
|
|
.. sourcecode:: python
|
|
|
|
import nfc
|
|
|
|
def on_startup(target):
|
|
idm = bytearray.fromhex("01010501b00ac30b")
|
|
pmm = bytearray.fromhex("03014b024f4993ff")
|
|
sys = bytearray.fromhex("1234")
|
|
target.brty = "212F"
|
|
target.sensf_res = chr(1) + idm + pmm + sys
|
|
return target
|
|
|
|
def on_connect(tag):
|
|
print("discovered by remote reader")
|
|
return True
|
|
|
|
def on_release(tag):
|
|
print("remote reader is gone")
|
|
return True
|
|
|
|
card_options = {
|
|
'on-startup': on_startup,
|
|
'on-connect': on_connect,
|
|
'on-release': on_release,
|
|
}
|
|
with nfc.ContactlessFrontend('usb') as clf:
|
|
clf.connect(card=card_options)
|
|
|
|
**Return Value**
|
|
|
|
The :meth:`connect` method returns :const:`None` if there were
|
|
no options left after the 'on-startup' functions have been
|
|
executed or when the 'terminate' function returned a true
|
|
value. It returns :const:`False` when terminated by any of the
|
|
following exceptions: :exc:`~exceptions.KeyboardInterrupt`,
|
|
:exc:`~exceptions.IOError`, :exc:`UnsupportedTargetError`.
|
|
|
|
The :meth:`connect` method returns a :class:`~nfc.tag.Tag`,
|
|
:class:`~nfc.llcp.llc.LogicalLinkController`, or
|
|
:class:`~nfc.tag.TagEmulation` object if the associated
|
|
'on-connect' function returned a false value to indicate that
|
|
it will handle presence check, peer to peer symmetry loop, or
|
|
command/response processing by itself.
|
|
|
|
"""
|
|
if self.device is None:
|
|
raise IOError(errno.ENODEV, os.strerror(errno.ENODEV))
|
|
|
|
log.debug("connect{0}".format(
|
|
tuple([k for k in options if options[k]])))
|
|
|
|
terminate = options.get('terminate', lambda: False)
|
|
rdwr_options = options.get('rdwr')
|
|
llcp_options = options.get('llcp')
|
|
card_options = options.get('card')
|
|
|
|
try:
|
|
assert isinstance(rdwr_options, (dict, type(None))), "rdwr"
|
|
assert isinstance(llcp_options, (dict, type(None))), "llcp"
|
|
assert isinstance(card_options, (dict, type(None))), "card"
|
|
except AssertionError as error:
|
|
raise TypeError("argument '%s' must be a dictionary" % error)
|
|
|
|
if llcp_options is not None:
|
|
llcp_options = dict(llcp_options)
|
|
llcp_options.setdefault('on-startup', lambda llc: llc)
|
|
llcp_options.setdefault('on-connect', lambda llc: True)
|
|
llcp_options.setdefault('on-release', lambda llc: True)
|
|
|
|
llc = nfc.llcp.llc.LogicalLinkController(**llcp_options)
|
|
llc = llcp_options['on-startup'](llc)
|
|
if isinstance(llc, nfc.llcp.llc.LogicalLinkController):
|
|
llcp_options['llc'] = llc
|
|
else:
|
|
log.debug("removing llcp_options after on-startup")
|
|
llcp_options = None
|
|
|
|
if rdwr_options is not None:
|
|
def on_discover(target):
|
|
if target.sel_res and target.sel_res[0] & 0x40:
|
|
return False
|
|
elif target.sensf_res and target.sensf_res[1:3] == b"\x01\xFE":
|
|
return False
|
|
else:
|
|
return True
|
|
|
|
rdwr_options = dict(rdwr_options)
|
|
rdwr_options.setdefault('targets', ['106A', '106B', '212F'])
|
|
rdwr_options.setdefault('on-startup', lambda targets: targets)
|
|
rdwr_options.setdefault('on-discover', on_discover)
|
|
rdwr_options.setdefault('on-connect', lambda tag: True)
|
|
rdwr_options.setdefault('on-release', lambda tag: True)
|
|
rdwr_options.setdefault('iterations', 5)
|
|
rdwr_options.setdefault('interval', 0.5)
|
|
rdwr_options.setdefault('beep-on-connect', True)
|
|
|
|
targets = [RemoteTarget(brty) for brty in rdwr_options['targets']]
|
|
targets = rdwr_options['on-startup'](targets)
|
|
if targets and all([isinstance(o, RemoteTarget) for o in targets]):
|
|
rdwr_options['targets'] = targets
|
|
else:
|
|
log.debug("removing rdwr_options after on-startup")
|
|
rdwr_options = None
|
|
|
|
if card_options is not None:
|
|
card_options = dict(card_options)
|
|
card_options.setdefault('on-startup', lambda target: None)
|
|
card_options.setdefault('on-discover', lambda target: True)
|
|
card_options.setdefault('on-connect', lambda tag: True)
|
|
card_options.setdefault('on-release', lambda tag: True)
|
|
|
|
target = nfc.clf.LocalTarget()
|
|
target = card_options['on-startup'](target)
|
|
if isinstance(target, LocalTarget):
|
|
card_options['target'] = target
|
|
else:
|
|
log.debug("removing card_options after on-startup")
|
|
card_options = None
|
|
|
|
if not (rdwr_options or llcp_options or card_options):
|
|
log.warning("no options to connect")
|
|
return None
|
|
|
|
log.debug("connect options after startup: %s",
|
|
', '.join(filter(bool, ["rdwr" if rdwr_options else None,
|
|
"llcp" if llcp_options else None,
|
|
"card" if card_options else None])))
|
|
|
|
try:
|
|
while not terminate():
|
|
if rdwr_options:
|
|
result = self._rdwr_connect(rdwr_options, terminate)
|
|
if bool(result) is True:
|
|
return result
|
|
if llcp_options:
|
|
result = self._llcp_connect(llcp_options, terminate)
|
|
if bool(result) is True:
|
|
return result
|
|
if card_options:
|
|
result = self._card_connect(card_options, terminate)
|
|
if bool(result) is True:
|
|
return result
|
|
except IOError as error:
|
|
log.error(error)
|
|
return False
|
|
except UnsupportedTargetError as error:
|
|
log.info(error)
|
|
return False
|
|
except KeyboardInterrupt:
|
|
log.debug("terminated by keyboard interrupt")
|
|
return False
|
|
|
|
def _rdwr_connect(self, options, terminate):
|
|
target = self.sense(*options['targets'],
|
|
iterations=options['iterations'],
|
|
interval=options['interval'])
|
|
if target is not None:
|
|
log.debug("discovered target {0}".format(target))
|
|
if options['on-discover'](target):
|
|
tag = nfc.tag.activate(self, target)
|
|
if tag is not None:
|
|
log.debug("connected to {0}".format(tag))
|
|
if options['on-connect'](tag):
|
|
if options['beep-on-connect']:
|
|
self.device.turn_on_led_and_buzzer()
|
|
while not terminate() and tag.is_present:
|
|
time.sleep(0.1)
|
|
self.device.turn_off_led_and_buzzer()
|
|
return options['on-release'](tag)
|
|
else:
|
|
return tag
|
|
|
|
def _llcp_connect(self, options, terminate):
|
|
llc = options['llc']
|
|
for role in ('target', 'initiator'):
|
|
if options.get('role') is None or options.get('role') == role:
|
|
DEP = eval("nfc.dep." + role.capitalize())
|
|
dep_cfg = ('brs', 'acm', 'rwt', 'lrt', 'lri')
|
|
dep_cfg = {k: options[k] for k in dep_cfg if k in options}
|
|
if llc.activate(mac=DEP(clf=self), **dep_cfg):
|
|
log.debug("connected {0}".format(llc))
|
|
if options['on-connect'](llc):
|
|
llc.run(terminate=terminate)
|
|
return options['on-release'](llc)
|
|
else:
|
|
return llc
|
|
|
|
def _card_connect(self, options, terminate):
|
|
timeout = options.get('timeout', 1.0)
|
|
target = self.listen(options['target'], timeout)
|
|
if target and options['on-discover'](target):
|
|
log.debug("activated as {0}".format(target))
|
|
tag = nfc.tag.emulate(self, target)
|
|
if isinstance(tag, nfc.tag.TagEmulation):
|
|
log.debug("connected as {0}".format(tag))
|
|
if options['on-connect'](tag):
|
|
tag_rsp = tag.process_command(tag.cmd)
|
|
while not terminate():
|
|
try:
|
|
tag_cmd = tag.send_response(tag_rsp, None)
|
|
tag_rsp = tag.process_command(tag_cmd)
|
|
except nfc.clf.BrokenLinkError as error:
|
|
log.debug(error)
|
|
break
|
|
except nfc.clf.CommunicationError as error:
|
|
log.debug(error)
|
|
tag_rsp = None
|
|
return options['on-release'](tag)
|
|
else:
|
|
return tag
|
|
|
|
def sense(self, *targets, **options):
|
|
"""Discover a contactless card or listening device.
|
|
|
|
.. note:: The :meth:`sense` method is intended for experts
|
|
with a good understanding of the commands and
|
|
responses exchanged during target activation (the
|
|
notion used for commands and responses follows the
|
|
NFC Forum Digital Specification). If the greater
|
|
level of control is not needed it is recommended to
|
|
use the :meth:`connect` method.
|
|
|
|
All positional arguments build the list of potential *targets*
|
|
to discover and must be of type :class:`RemoteTarget`. Keyword
|
|
argument *options* may be the number of ``iterations`` of the
|
|
sense loop set by *targets* and the ``interval`` between
|
|
iterations. The return value is either a :class:`RemoteTarget`
|
|
instance or :const:`None`.
|
|
|
|
>>> import nfc, nfc.clf
|
|
>>> from binascii import hexlify
|
|
>>> clf = nfc.ContactlessFrontend("usb")
|
|
>>> target1 = nfc.clf.RemoteTarget("106A")
|
|
>>> target2 = nfc.clf.RemoteTarget("212F")
|
|
>>> print(clf.sense(target1, target2, iterations=5, interval=0.2))
|
|
106A(sdd_res=04497622D93881, sel_res=00, sens_res=4400)
|
|
|
|
A **Type A Target** is specified with the technology letter
|
|
``A`` following the bitrate to be used for the SENS_REQ
|
|
command (almost always must the bitrate be 106 kbps). To
|
|
discover only a specific Type A target, the NFCID1 (UID) can
|
|
be set with a 4, 7, or 10 byte ``sel_req`` attribute (cascade
|
|
tags are handled internally).
|
|
|
|
>>> target = nfc.clf.RemoteTarget("106A")
|
|
>>> print(clf.sense(target))
|
|
106A sdd_res=04497622D93881 sel_res=00 sens_res=4400
|
|
>>> target.sel_req = bytearray.fromhex("04497622D93881")
|
|
>>> print(clf.sense(target))
|
|
106A sdd_res=04497622D93881 sel_res=00 sens_res=4400
|
|
>>> target.sel_req = bytearray.fromhex("04497622")
|
|
>>> print(clf.sense(target))
|
|
None
|
|
|
|
A **Type B Target** is specified with the technology letter
|
|
``B`` following the bitrate to be used for the SENSB_REQ
|
|
command (almost always must the bitrate be 106 kbps). A
|
|
specific application family identifier can be set with the
|
|
first byte of a ``sensb_req`` attribute (the second byte PARAM
|
|
is ignored when it can not be set to local device, 00h is a
|
|
safe value in all cases).
|
|
|
|
>>> target = nfc.clf.RemoteTarget("106B")
|
|
>>> print(clf.sense(target))
|
|
106B sens_res=50E5DD3DC900000011008185
|
|
>>> target.sensb_req = bytearray.fromhex("0000")
|
|
>>> print(clf.sense(target))
|
|
106B sens_res=50E5DD3DC900000011008185
|
|
>>> target.sensb_req = bytearray.fromhex("FF00")
|
|
>>> print(clf.sense(target))
|
|
None
|
|
|
|
A **Type F Target** is specified with the technology letter
|
|
``F`` following the bitrate to be used for the SENSF_REQ
|
|
command (the typically supported bitrates are 212 and 424
|
|
kbps). The default SENSF_REQ command allows all targets to
|
|
answer, requests system code information, and selects a single
|
|
time slot for the SENSF_RES response. This can be changed with
|
|
the ``sensf_req`` attribute.
|
|
|
|
>>> target = nfc.clf.RemoteTarget("212F")
|
|
>>> print(clf.sense(target))
|
|
212F sensf_res=0101010601B00ADE0B03014B024F4993FF12FC
|
|
>>> target.sensf_req = bytearray.fromhex("0012FC0000")
|
|
>>> print(clf.sense(target))
|
|
212F sensf_res=0101010601B00ADE0B03014B024F4993FF
|
|
>>> target.sensf_req = bytearray.fromhex("00ABCD0000")
|
|
>>> print(clf.sense(target))
|
|
None
|
|
|
|
An **Active Communication Mode P2P Target** search is selected
|
|
with an ``atr_req`` attribute. The choice of bitrate and
|
|
modulation type is 106A, 212F, and 424F.
|
|
|
|
>>> atr = bytearray.fromhex("D4000102030405060708091000000030")
|
|
>>> target = clf.sense(nfc.clf.RemoteTarget("106A", atr_req=atr))
|
|
>>> if target and target.atr_res:
|
|
>>> print(hexlify(target.atr_res).decode())
|
|
d501c023cae6b3182afe3dee0000000e3246666d01011103020013040196
|
|
>>> target = clf.sense(nfc.clf.RemoteTarget("424F", atr_req=atr))
|
|
>>> if target and target.atr_res:
|
|
>>> print(hexlify(target.atr_res).decode())
|
|
d501dc0104f04584e15769700000000e3246666d01011103020013040196
|
|
|
|
Some drivers must modify the ATR_REQ to cope with hardware
|
|
limitations, for example change length reduction value to
|
|
reduce the maximum size of target responses. The ATR_REQ that
|
|
has been send is given by the ``atr_req`` attribute of the
|
|
returned RemoteTarget object.
|
|
|
|
A **Passive Communication Mode P2P Target** responds to 106A
|
|
discovery with bit 6 of SEL_RES set to 1, and to 212F/424F
|
|
discovery (when the request code RC is 0 in the SENSF_REQ
|
|
command) with an NFCID2 that starts with 01FEh in the
|
|
SENSF_RES response. Responses below are from a Nexus 5
|
|
configured for NFC-DEP Protocol (SEL_RES bit 6 is set) and
|
|
Type 4A Tag (SEL_RES bit 5 is set).
|
|
|
|
>>> print(clf.sense(nfc.clf.RemoteTarget("106A")))
|
|
106A sdd_res=08796BEB sel_res=60 sens_res=0400
|
|
>>> sensf_req = bytearray.fromhex("00FFFF0000")
|
|
>>> print(clf.sense(nfc.clf.RemoteTarget("424F", sensf_req=sensf_req)))
|
|
424F sensf_res=0101FE1444EFB88FD50000000000000000
|
|
|
|
Errors found in the *targets* argument list raise exceptions
|
|
only if exactly one target is given. If multiple targets are
|
|
provided, any target that is not supported or has invalid
|
|
attributes is just ignored (but is logged as a debug message).
|
|
|
|
**Exceptions**
|
|
|
|
* :exc:`~exceptions.IOError` (ENODEV) when a local contacless
|
|
communication device has not been opened or communication
|
|
with the local device is no longer possible.
|
|
|
|
* :exc:`nfc.clf.UnsupportedTargetError` if the single target
|
|
supplied as input is not supported by the active driver.
|
|
This exception is never raised when :meth:`sense` is called
|
|
with multiple targets, those unsupported are then silently
|
|
ignored.
|
|
|
|
"""
|
|
def sense_tta(target):
|
|
if target.sel_req and len(target.sel_req) not in (4, 7, 10):
|
|
raise ValueError("sel_req must be 4, 7, or 10 byte")
|
|
target = self.device.sense_tta(target)
|
|
log.debug("found %s", target)
|
|
if target and len(target.sens_res) != 2:
|
|
error = "SENS Response Format Error (wrong length)"
|
|
log.debug(error)
|
|
raise ProtocolError(error)
|
|
if target and target.sens_res[0] & 0b00011111 == 0:
|
|
if target.sens_res[1] & 0b00001111 != 0b1100:
|
|
error = "SENS Response Data Error (T1T config)"
|
|
log.debug(error)
|
|
raise ProtocolError(error)
|
|
if not target.rid_res:
|
|
error = "RID Response Error (no response received)"
|
|
log.debug(error)
|
|
raise ProtocolError(error)
|
|
if len(target.rid_res) != 6:
|
|
error = "RID Response Format Error (wrong length)"
|
|
log.debug(error)
|
|
raise ProtocolError(error)
|
|
if target.rid_res[0] >> 4 != 0b0001:
|
|
error = "RID Response Data Error (invalid HR0)"
|
|
log.debug(error)
|
|
raise ProtocolError(error)
|
|
return target
|
|
|
|
def sense_ttb(target):
|
|
return self.device.sense_ttb(target)
|
|
|
|
def sense_ttf(target):
|
|
return self.device.sense_ttf(target)
|
|
|
|
def sense_dep(target):
|
|
if len(target.atr_req) < 16:
|
|
raise ValueError("minimum atr_req length is 16 byte")
|
|
if len(target.atr_req) > 64:
|
|
raise ValueError("maximum atr_req length is 64 byte")
|
|
return self.device.sense_dep(target)
|
|
|
|
for target in targets:
|
|
if not isinstance(target, RemoteTarget):
|
|
raise ValueError("invalid target argument type: %r" % target)
|
|
|
|
with self.lock:
|
|
if self.device is None:
|
|
raise IOError(errno.ENODEV, os.strerror(errno.ENODEV))
|
|
|
|
self.target = None # forget captured target
|
|
self.device.mute() # deactivate the rf field
|
|
|
|
for i in range(max(1, options.get('iterations', 1))):
|
|
started = time.time()
|
|
for target in targets:
|
|
log.debug("sense {0}".format(target))
|
|
try:
|
|
if target.atr_req is not None:
|
|
self.target = sense_dep(target)
|
|
elif target.brty.endswith('A'):
|
|
self.target = sense_tta(target)
|
|
elif target.brty.endswith('B'):
|
|
self.target = sense_ttb(target)
|
|
elif target.brty.endswith('F'):
|
|
self.target = sense_ttf(target)
|
|
else:
|
|
info = "unknown technology type in %r"
|
|
raise UnsupportedTargetError(info % target.brty)
|
|
except UnsupportedTargetError as error:
|
|
if len(targets) == 1:
|
|
raise error
|
|
else:
|
|
log.debug(error)
|
|
except CommunicationError as error:
|
|
log.debug(error)
|
|
else:
|
|
if self.target is not None:
|
|
log.debug("found {0}".format(self.target))
|
|
return self.target
|
|
if len(targets) > 0:
|
|
self.device.mute() # deactivate the rf field
|
|
if i < options.get('iterations', 1) - 1:
|
|
elapsed = time.time() - started
|
|
time.sleep(max(0, options.get('interval', 0.1)-elapsed))
|
|
|
|
def listen(self, target, timeout):
|
|
"""Listen *timeout* seconds to become activated as *target*.
|
|
|
|
.. note:: The :meth:`listen` method is intended for experts
|
|
with a good understanding of the commands and
|
|
responses exchanged during target activation (the
|
|
notion used for commands and responses follows the
|
|
NFC Forum Digital Specification). If the greater
|
|
level of control is not needed it is recommended to
|
|
use the :meth:`connect` method.
|
|
|
|
The *target* argument is a :class:`LocalTarget` object that
|
|
provides bitrate, technology type and response data
|
|
attributes. The return value is either a :class:`LocalTarget`
|
|
object with bitrate, technology type and request/response data
|
|
attributes or :const:`None`.
|
|
|
|
An **P2P Target** is selected when the ``atr_res`` attribute
|
|
is set. The bitrate and technology type are decided by the
|
|
Initiator and do not need to be specified. The ``sens_res``,
|
|
``sdd_res`` and ``sel_res`` attributes for Type A technology
|
|
as well as the ``sensf_res`` attribute for Type F technolgy
|
|
must all be set.
|
|
|
|
When activated, the bitrate and type are set to the current
|
|
communication values, the ``atr_req`` attribute contains the
|
|
ATR_REQ received from the Initiator and the ``dep_req``
|
|
attribute contains the first DEP_REQ received after
|
|
activation. If the Initiator has changed communication
|
|
parameters, the ``psl_req`` attribute holds the PSL_REQ that
|
|
was received. The ``atr_res`` (and the ``psl_res`` if
|
|
transmitted) are also made available.
|
|
|
|
If the local target was activated in passive communication
|
|
mode either the Type A response (``sens_res``, ``sdd_res``,
|
|
``sel_res``) or Type F response (``sensf_res``) attributes
|
|
will be present.
|
|
|
|
With a Nexus 5 on a reader connected via USB the following
|
|
code should be working and produce similar output (the Nexus 5
|
|
prioritizes active communication mode):
|
|
|
|
>>> import nfc, nfc.clf
|
|
>>> clf = nfc.ContactlessFrontend("usb")
|
|
>>> atr_res = "d50101fe0102030405060708000000083246666d010110"
|
|
>>> target = nfc.clf.LocalTarget()
|
|
>>> target.sensf_res = bytearray.fromhex("0101FE"+16*"FF")
|
|
>>> target.sens_res = bytearray.fromhex("0101")
|
|
>>> target.sdd_res = bytearray.fromhex("08010203")
|
|
>>> target.sel_res = bytearray.fromhex("40")
|
|
>>> target.atr_res = bytearray.fromhex(atr_res)
|
|
>>> print(clf.listen(target, timeout=2.5))
|
|
424F atr_res=D50101FE0102030405060708000000083246666D010110 ...
|
|
|
|
A **Type A Target** is selected when ``atr_res`` is not
|
|
present and the technology type is ``A``. The bitrate should
|
|
be set to 106 kbps, even if a driver supports higher bitrates
|
|
they would need to be set after activation. The ``sens_res``,
|
|
``sdd_res`` and ``sel_res`` attributes must all be provided.
|
|
|
|
>>> target = nfc.clf.Localtarget("106A")
|
|
>>> target.sens_res = bytearray.fromhex("0101"))
|
|
>>> target.sdd_res = bytearray.fromhex("08010203")
|
|
>>> target.sel_res = bytearray.fromhex("00")
|
|
>>> print(clf.listen(target, timeout=2.5))
|
|
106A sdd_res=08010203 sel_res=00 sens_res=0101 tt2_cmd=3000
|
|
|
|
A **Type B Target** is selected when ``atr_res`` is not
|
|
present and the technology type is ``B``. Unfortunately none
|
|
of the supported devices supports Type B technology for listen
|
|
and an :exc:`nfc.clf.UnsupportedTargetError` exception will be
|
|
the only result.
|
|
|
|
>>> target = nfc.clf.LocalTarget("106B")
|
|
>>> try: clf.listen(target, 2.5)
|
|
... except nfc.clf.UnsupportedTargetError: print("sorry")
|
|
...
|
|
sorry
|
|
|
|
A **Type F Target** is selected when ``atr_res`` is not
|
|
present and the technology type is ``F``. The bitrate may be
|
|
212 or 424 kbps. The ``sensf_res`` attribute must be provided.
|
|
|
|
>>> idm, pmm, sys = "02FE010203040506", "FFFFFFFFFFFFFFFF", "12FC"
|
|
>>> target = nfc.clf.LocalTarget("212F")
|
|
>>> target.sensf_res = bytearray.fromhex("01" + idm + pmm + sys)
|
|
>>> print(clf.listen(target, 2.5))
|
|
212F sensf_req=00FFFF0003 tt3_cmd=0C02FE010203040506 ...
|
|
|
|
**Exceptions**
|
|
|
|
* :exc:`~exceptions.IOError` (ENODEV) when a local contacless
|
|
communication device has not been opened or communication
|
|
with the local device is no longer possible.
|
|
|
|
* :exc:`nfc.clf.UnsupportedTargetError` if the single target
|
|
supplied as input is not supported by the active driver.
|
|
This exception is never raised when :meth:`sense` is called
|
|
with multiple targets, those unsupported are then silently
|
|
ignored.
|
|
|
|
"""
|
|
def listen_tta(target, timeout):
|
|
return self.device.listen_tta(target, timeout)
|
|
|
|
def listen_ttb(target, timeout):
|
|
return self.device.listen_ttb(target, timeout)
|
|
|
|
def listen_ttf(target, timeout):
|
|
return self.device.listen_ttf(target, timeout)
|
|
|
|
def listen_dep(target, timeout):
|
|
target = self.device.listen_dep(target, timeout)
|
|
if target and target.atr_req:
|
|
try:
|
|
assert len(target.atr_req) >= 16, "less than 16 byte"
|
|
assert len(target.atr_req) <= 64, "more than 64 byte"
|
|
return target
|
|
except AssertionError as error:
|
|
log.debug("atr_req is %s", str(error))
|
|
|
|
assert isinstance(target, LocalTarget), \
|
|
"invalid target argument type: %r" % target
|
|
|
|
with self.lock:
|
|
if self.device is None:
|
|
raise IOError(errno.ENODEV, os.strerror(errno.ENODEV))
|
|
|
|
self.target = None # forget captured target
|
|
self.device.mute() # deactivate the rf field
|
|
|
|
info = "listen %.3f seconds for %s"
|
|
if target.atr_res is not None:
|
|
log.debug(info, timeout, "DEP")
|
|
self.target = listen_dep(target, timeout)
|
|
elif target.brty in ('106A', '212A', '424A'):
|
|
log.debug(info, timeout, target)
|
|
self.target = listen_tta(target, timeout)
|
|
elif target.brty in ('106B', '212B', '424B', '848B'):
|
|
log.debug(info, timeout, target)
|
|
self.target = listen_ttb(target, timeout)
|
|
elif target.brty in ('212F', '424F'):
|
|
log.debug(info, timeout, target)
|
|
self.target = listen_ttf(target, timeout)
|
|
else:
|
|
errmsg = "unsupported bitrate technology type {}"
|
|
raise ValueError(errmsg.format(target.brty))
|
|
|
|
return self.target
|
|
|
|
def exchange(self, send_data, timeout):
|
|
"""Exchange data with an activated target (*send_data* is a command
|
|
frame) or as an activated target (*send_data* is a response
|
|
frame). Returns a target response frame (if data is send to an
|
|
activated target) or a next command frame (if data is send
|
|
from an activated target). Returns None if the communication
|
|
link broke during exchange (if data is sent as a target). The
|
|
timeout is the number of seconds to wait for data to return,
|
|
if the timeout expires an nfc.clf.TimeoutException is
|
|
raised. Other nfc.clf.CommunicationError exceptions may be raised if
|
|
an error is detected during communication.
|
|
|
|
"""
|
|
with self.lock:
|
|
if self.device is None:
|
|
raise IOError(errno.ENODEV, os.strerror(errno.ENODEV))
|
|
|
|
log.debug(">>> %s timeout=%s", print_data(send_data), str(timeout))
|
|
|
|
if isinstance(self.target, RemoteTarget):
|
|
exchange = self.device.send_cmd_recv_rsp
|
|
elif isinstance(self.target, LocalTarget):
|
|
exchange = self.device.send_rsp_recv_cmd
|
|
else:
|
|
log.error("no target for data exchange")
|
|
return None
|
|
|
|
send_time = time.time()
|
|
rcvd_data = exchange(self.target, send_data, timeout)
|
|
recv_time = time.time() - send_time
|
|
|
|
log.debug("<<< %s %.3fs", print_data(rcvd_data), recv_time)
|
|
return rcvd_data
|
|
|
|
@property
|
|
def max_send_data_size(self):
|
|
"""The maximum number of octets that can be send with the
|
|
:meth:`exchange` method in the established operating mode.
|
|
|
|
"""
|
|
with self.lock:
|
|
if self.device is None:
|
|
raise IOError(errno.ENODEV, os.strerror(errno.ENODEV))
|
|
else:
|
|
return self.device.get_max_send_data_size(self.target)
|
|
|
|
@property
|
|
def max_recv_data_size(self):
|
|
"""The maximum number of octets that can be received with the
|
|
:meth:`exchange` method in the established operating mode.
|
|
|
|
"""
|
|
with self.lock:
|
|
if self.device is None:
|
|
raise IOError(errno.ENODEV, os.strerror(errno.ENODEV))
|
|
else:
|
|
return self.device.get_max_recv_data_size(self.target)
|
|
|
|
def __enter__(self):
|
|
return self
|
|
|
|
def __exit__(self, exc_type, exc_value, traceback):
|
|
self.close()
|
|
|
|
def __str__(self):
|
|
if self.device is not None:
|
|
s = "{dev.vendor_name} {dev.product_name} on {dev.path}"
|
|
return s.format(dev=self.device)
|
|
else:
|
|
return self.__repr__()
|
|
|
|
|
|
###############################################################################
|
|
#
|
|
# Targets
|
|
#
|
|
###############################################################################
|
|
class Target(object):
|
|
def __init__(self, **kwargs):
|
|
for name in kwargs:
|
|
self.__dict__[name] = kwargs[name]
|
|
|
|
def __getattr__(self, name):
|
|
return None
|
|
|
|
def __eq__(self, other):
|
|
return self.__dict__ == other.__dict__
|
|
|
|
def __str__(self):
|
|
attrs = []
|
|
for name in sorted(self.__dict__.keys()):
|
|
if name.startswith('_'):
|
|
continue
|
|
value = self.__dict__[name]
|
|
if isinstance(value, (bytes, bytearray)):
|
|
value = binascii.hexlify(value).decode().upper()
|
|
attrs.append("{0}={1}".format(name, value))
|
|
return "{brty} {attrs}".format(brty=self.brty, attrs=' '.join(attrs))
|
|
|
|
|
|
class RemoteTarget(Target):
|
|
"""A RemoteTarget instance provides bitrate and technology type and
|
|
command/response data of a remote card or device that, when input
|
|
to :meth:`sense`, shall be attempted to discover and, when
|
|
returned by :meth:`sense`, has been discovered by the local
|
|
device. Command/response data attributes, whatever name, default
|
|
to None.
|
|
|
|
"""
|
|
brty_pattern = re.compile(r'(\d+[A-Z])(?:/(\d+[A-Z])|.*)')
|
|
|
|
def __init__(self, brty, **kwargs):
|
|
super(RemoteTarget, self).__init__(**kwargs)
|
|
self.brty = brty
|
|
|
|
@property
|
|
def brty(self):
|
|
"""A string that combines bitrate and technology type, e.g. '106A'."""
|
|
return self._brty_send
|
|
|
|
@brty.setter
|
|
def brty(self, value):
|
|
brty_pattern_match = self.brty_pattern.match(value)
|
|
if brty_pattern_match:
|
|
(self._brty_send, self._brty_recv) = brty_pattern_match.groups()
|
|
if not self._brty_recv:
|
|
self._brty_recv = self._brty_send
|
|
else:
|
|
raise ValueError("brty pattern does not match for %r" % value)
|
|
|
|
@property
|
|
def brty_send(self):
|
|
return self._brty_send
|
|
|
|
@property
|
|
def brty_recv(self):
|
|
return self._brty_recv
|
|
|
|
|
|
class LocalTarget(Target):
|
|
"""A LocalTarget instance provides bitrate and technology type and
|
|
command/response data of the local card or device that, when input
|
|
to :meth:`listen`, shall be made available for discovery and, when
|
|
returned by :meth:`listen`, has been discovered by a remote
|
|
device. Command/response data attributes, whatever name, default
|
|
to None.
|
|
|
|
"""
|
|
def __init__(self, brty='106A', **kwargs):
|
|
super(LocalTarget, self).__init__(**kwargs)
|
|
self.brty = brty
|
|
|
|
@property
|
|
def brty(self):
|
|
"""A string that combines bitrate and technology type, e.g. '106A'."""
|
|
return self._brty_send \
|
|
if self._brty_send == self._brty_recv \
|
|
else self._brty_send+"/"+self._brty_recv
|
|
|
|
@brty.setter
|
|
def brty(self, value):
|
|
self._brty_send = self._brty_recv = value
|
|
|
|
|
|
###############################################################################
|
|
#
|
|
# Exceptions
|
|
#
|
|
###############################################################################
|
|
class Error(Exception):
|
|
"""Base class for exceptions specific to the contacless frontend module.
|
|
|
|
- UnsupportedTargetError
|
|
- CommunicationError
|
|
|
|
- ProtocolError
|
|
- TransmissionError
|
|
- TimeoutError
|
|
- BrokenLinkError
|
|
|
|
"""
|
|
|
|
|
|
class UnsupportedTargetError(Error):
|
|
"""The :class:`RemoteTarget` input to
|
|
:meth:`ContactlessFrontend.sense` or :class:`LocalTarget` input to
|
|
:meth:`ContactlessFrontend.listen` is not supported by the local
|
|
device.
|
|
|
|
"""
|
|
|
|
|
|
class CommunicationError(Error):
|
|
"""Base class for communication errors.
|
|
|
|
"""
|
|
|
|
|
|
class ProtocolError(CommunicationError):
|
|
"""Raised when an NFC Forum Digital Specification protocol error
|
|
occured.
|
|
|
|
"""
|
|
|
|
|
|
class TransmissionError(CommunicationError):
|
|
"""Raised when an NFC Forum Digital Specification transmission error
|
|
occured.
|
|
|
|
"""
|
|
|
|
|
|
class TimeoutError(CommunicationError):
|
|
"""Raised when an NFC Forum Digital Specification timeout error
|
|
occured.
|
|
|
|
"""
|
|
|
|
|
|
class BrokenLinkError(CommunicationError):
|
|
"""The remote device (Reader/Writer or P2P Device) has deactivated the
|
|
RF field or is no longer within communication distance.
|
|
|
|
"""
|