~stephen-tiedemann/nfcpy/dev-phdc

« back to all changes in this revision

Viewing changes to nfc/clf.py

  • Committer: stephen.tiedemann at googlemail
  • Date: 2013-07-08 06:52:10 UTC
  • Revision ID: stephen.tiedemann@googlemail.com-20130708065210-u542703smznd9ucl
changed ContactlessFrontend() to call open() only if a path string was given; ContactlessFrontend.connect() returns the tag or llc instance if 'on-connect' callback returns False; improved documentation

Show diffs side-by-side

added added

removed removed

Lines of Context:
90
90
 
91
91
class ContactlessFrontend(object):
92
92
    """The contactless frontend is the main interface class for
93
 
    working with contactless reader devices. A reader device is opened
94
 
    automatically upon instance creation, see :meth:`.open` for how
95
 
    *path* is interpeted."""
 
93
    working with contactless reader devices.  A reader device may be
 
94
    opened when an instance is created by providing the *path*
 
95
    argument, see :meth:`nfc.ContactlessFrontend.open` for how it must
 
96
    be constructed.
 
97
 
 
98
    The initializer method raises :exc:`IOError(errno.ENODEV)` if a
 
99
    path is specified but no no reader are found.
 
100
    """
96
101
    
97
102
    def __init__(self, path=None):
 
103
        self.dev = None
98
104
        self.lock = threading.Lock()
99
 
        self.open(path)
100
 
        
101
 
    def open(self, path=None):
102
 
        """Open contactless reader device identified by *path*. If
103
 
        *path* is not :const:`None` (default) or an empty string, the
104
 
        first available device is used. Otherwise *path* must match
105
 
        one of the following expressions:
106
 
 
107
 
        * ``usb[:vendor[:product]]`` with *vendor* and *product* id (hex)
108
 
        * ``usb[:bus[:device]]`` with usb *bus* and *device* number (dec)
109
 
        * ``tty[:usb[:port]]`` with usb serial *port* number (dec)
110
 
        * ``tty[:com[:port]]`` with serial *port* number (dec)
111
 
        * ``udp[:host[:port]]`` with *host* IP or name and *port* number
112
 
 
113
 
        :raises `IOError(ENODEV)` if no available reader device is found.
 
105
        if path and not self.open(path):
 
106
            raise IOError(errno.ENODEV, os.strerror(errno.ENODEV))
 
107
        
 
108
    def open(self, path):
 
109
        """Open a contactless reader device identified by *path*.
 
110
 
 
111
        :param path: search path for contactless reader
 
112
        :returns True: if reader was found and activated
 
113
 
 
114
        **Path specification:**
 
115
        
 
116
          ``usb[:vendor[:product]]``
 
117
            with optional *vendor* and *product* as four digit
 
118
            hexadecimal numbers, like ``usb:054c:06c3`` would open the
 
119
            first Sony RC-S380 reader and ``usb:054c`` the first Sony
 
120
            reader.
 
121
        
 
122
          ``usb[:bus[:device]]``
 
123
            with optional *bus* and *device* number as three-digit
 
124
            decimal numbers, like ``usb:001:023`` would specifically
 
125
            mean the usb device with bus number 1 and device id 23
 
126
            whereas ``usb:001`` would mean to use the first available
 
127
            reader on bus number 1.
 
128
        
 
129
          ``tty:port:driver``
 
130
            with mandatory *port* and *driver* name should be used on
 
131
            Posix systems to open the serial port at device node
 
132
            ``/dev/tty<port>`` and load the driver from module
 
133
            ``nfc/dev/<driver>.py``. A typical example would be
 
134
            ``tty:USB0:arygon`` for the Arygon APPx/ADRx at
 
135
            ``/dev/ttyUSB0``.
 
136
        
 
137
          ``com:port:driver``
 
138
            with mandatory *port* and *driver* name should be used on
 
139
            Windows systems to open the serial port ``COM<port>`` and
 
140
            load the ``nfc/dev/<driver>.py`` driver module.
 
141
        
 
142
          ``udp[:host][:port]`` with optional *host* name or address
 
143
            and *port* number will use a fake communication channel over
 
144
            UDP/IP. Either value may be omitted in which case *host*
 
145
            defaults to 'localhost' and *port* defaults to 54321.
 
146
 
114
147
        """
115
 
        if not path: log.info("searching for a usable reader")
116
 
        else: log.info("searching for reader with path '{0}'".format(path))
 
148
        if not isinstance(path, str):
 
149
            raise TypeError("expecting a string type argument *path*")
 
150
        if not len(path) > 0:
 
151
             raise ValueError("argument *path* must not be empty")
 
152
    
 
153
        log.info("searching for reader with path '{0}'".format(path))
117
154
 
118
155
        with self.lock:
119
156
            self.dev = nfc.dev.connect(path)
120
 
            if self.dev is None:
121
 
                msg = "no reader found"
122
 
                log.error(msg + " at '{0}'".format(path) if path else msg)
123
 
                raise IOError(errno.ENODEV, os.strerror(errno.ENODEV))
124
 
 
 
157
        
 
158
        if self.dev is None:
 
159
            log.error("no reader found at '{0}'".format(path))
 
160
        else:
125
161
            log.info("using {0}".format(self.dev))
 
162
            
 
163
        return bool(self.dev)
126
164
 
127
165
    def close(self):
128
166
        """Close the contacless reader device."""
129
167
        with self.lock:
130
 
            self.dev.close()
131
 
            self.dev = None
 
168
            if self.dev:
 
169
                self.dev.close()
 
170
                self.dev = None
132
171
 
133
172
    def connect(self, **options):
134
173
        """Connect with a contactless target or become connected as a
135
 
        contactless target. Connect blocks the calling thread until an
 
174
        contactless target. Blocks the calling thread until a single
136
175
        activation and deactivation has completed. Connect options are
137
 
        given as keyword arguments with dictionary values and at least
138
 
        one option must be present. Possible options are:
 
176
        given as keyword arguments with dictionary values. Possible
 
177
        options are:
139
178
        
140
179
        * ``rdwr={key: value, ...}`` - options for reader/writer operation
141
180
        * ``llcp={key: value, ...}`` - options for peer to peer mode operation
255
294
        >>>
256
295
        >>> def connected(tag, command):
257
296
        ...     print tag
258
 
        ...     print str(command).enccode("hex")
 
297
        ...     print str(command).encode("hex")
259
298
        ...
260
299
        >>> clf = nfc.ContactlessFrontend()
261
300
        >>> idm = bytearray.fromhex("01010501b00ac30b")
267
306
        100601010501b00ac30b010b00018000
268
307
        True
269
308
        
 
309
        Connect returns :const:`None` if no options were to execute,
 
310
        :const:`False` if interrupted by a :exc:`KeyboardInterrupt`,
 
311
        or :const:`True` if terminated normally and the 'on-connect'
 
312
        callback function had returned :const:`True`. If the
 
313
        'on-connect' callback had returned :const:`False` the return
 
314
        value of connect() is the same parameters as were provided to
 
315
        the callback function.
270
316
        """
271
317
        log.debug("connect({0})".format(options))
272
318
        
273
319
        rdwr_options = options.get('rdwr')
274
320
        llcp_options = options.get('llcp')
275
321
        card_options = options.get('card')
276
 
        
 
322
 
277
323
        if isinstance(rdwr_options, dict):
278
324
            rdwr_options.setdefault('targets', [
279
325
                    TTA(br=106, cfg=None, uid=None), TTB(br=106),
288
334
                if not 'on-connect' in rdwr_options:
289
335
                    rdwr_options['on-connect'] = lambda tag: True
290
336
        elif rdwr_options is not None:
291
 
            raise TypeError("rdrw_options must be a dictionary")
 
337
            raise TypeError("argument *rdrw* must be a dictionary")
292
338
        
293
339
        if isinstance(llcp_options, dict):
294
340
            llc = nfc.llcp.llc.LogicalLinkController(
302
348
                if not 'on-connect' in llcp_options:
303
349
                    llcp_options['on-connect'] = lambda llc: True
304
350
        elif llcp_options is not None:
305
 
            raise TypeError("llcp_options must be a dictionary")
 
351
            raise TypeError("argument *llcp* must be a dictionary")
306
352
 
307
353
        if isinstance(card_options, dict):
308
354
            if 'on-startup' in card_options:
317
363
                if not 'on-connect' in card_options:
318
364
                    card_options['on-connect'] = lambda tag, command: True
319
365
        elif card_options is not None:
320
 
            raise TypeError("card_options must be a dictionary")
 
366
            raise TypeError("argument *card* must be a dictionary")
321
367
 
322
368
        some_options = rdwr_options or llcp_options or card_options
323
369
        if not some_options:
324
370
            log.warning("no options left to connect")
 
371
            return None
 
372
        
325
373
        try:
326
 
            while some_options:
327
 
                if ((llcp_options and self._llcp_connect(llcp_options, llc)) or
328
 
                    (rdwr_options and self._rdwr_connect(rdwr_options)) or
329
 
                    (card_options and self._card_connect(card_options))):
330
 
                    return True
 
374
            while True:
 
375
                if llcp_options:
 
376
                    result = self._llcp_connect(llcp_options, llc)
 
377
                    if bool(result): return result
 
378
                if rdwr_options:
 
379
                    result = self._rdwr_connect(rdwr_options)
 
380
                    if bool(result): return result
 
381
                if card_options:
 
382
                    result = self._card_connect(card_options)
 
383
                    if bool(result): return result
331
384
        except KeyboardInterrupt as error:
332
385
            log.debug(error)
333
386
            return False
343
396
            if tag is not None:
344
397
                log.debug("connected to {0}".format(tag))
345
398
                if options['on-connect'](tag=tag):
346
 
                    while tag.is_present: time.sleep(0.1)
347
 
                return True
 
399
                    while tag.is_present:
 
400
                        time.sleep(0.1)
 
401
                    return True
 
402
                else:
 
403
                    return tag
348
404
        
349
405
    def _llcp_connect(self, options, llc):
350
406
        for role in ('target', 'initiator'):
352
408
                DEP = eval("nfc.dep." + role.capitalize())
353
409
                if llc.activate(mac=DEP(clf=self)):
354
410
                    log.debug("connected {0}".format(llc))
355
 
                    if options['on-connect'](llc=llc): llc.run()
356
 
                    return True
 
411
                    if options['on-connect'](llc=llc):
 
412
                        llc.run()
 
413
                        return True
 
414
                    else:
 
415
                        return llc
357
416
        
358
417
    def _card_connect(self, options):
359
418
        timeout = options.get('timeout', 1.0)
365
424
                tag = nfc.tag.emulate(self, target)
366
425
                if tag is not None:
367
426
                    log.debug("connected as {0}".format(tag))
368
 
                    options['on-connect'](tag=tag, command=command)
369
 
                return True
 
427
                    if options['on-connect'](tag=tag, command=command):
 
428
                        while command is not None:
 
429
                            response = tag.process_command(command)
 
430
                            try:
 
431
                                command = tag.send_response(response, 1)
 
432
                            except nfc.clf.DigitalProtocolError:
 
433
                                break
 
434
                        return True
 
435
                    else:
 
436
                        return tag
370
437
        
371
438
    @property
372
439
    def capabilities(self):
389
456
 
390
457
    def listen(self, target, timeout):
391
458
        """Listen for *timeout* seconds to become initialized as a
392
 
        *target*. The *target* must be set to one of
393
 
        :class:`nfc.clf.TTA`, :class:`nfc.clf.TTB`,
394
 
        :class:`nfc.clf.TTF`, or :class:`nfc.clf.DEP` (note that
395
 
        target type support depends on the hardware capabilities). The
396
 
        return value is :const:`None` if *timeout* elapsed without
397
 
        activation or a tuple (target, command) where target is the
398
 
        activated target (which may differ from the requested target,
399
 
        see below) and command is the first command received from the
400
 
        initiator.
 
459
        *target*. The *target* must be one of :class:`nfc.clf.TTA`,
 
460
        :class:`nfc.clf.TTB`, :class:`nfc.clf.TTF`, or
 
461
        :class:`nfc.clf.DEP` (note that target type support depends on
 
462
        the hardware capabilities). The return value is :const:`None`
 
463
        if *timeout* elapsed without activation or a tuple (target,
 
464
        command) where target is the activated target (which may
 
465
        differ from the requested target, see below) and command is
 
466
        the first command received from the initiator.
401
467
 
402
468
        If an activated target is returned, the target type and
403
469
        attributes may differ from the *target* requested. This is