Resources release in PySCard
With PySCard version 2.2.2 (New version of PySCard: 2.2.2) it is now possible to release/free the PC/SC context used by some objects.
Sample
As see in PCSC sample in Python a typical PySCard code looks like:
#! /usr/bin/env python from smartcard.System import readers # define the APDUs used in this script SELECT = [0x00, 0xA4, 0x04, 0x00, 0x0A, 0xA0, 0x00, 0x00, 0x00, 0x62, 0x03, 0x01, 0x0C, 0x06, 0x01] COMMAND = [0x00, 0x00, 0x00, 0x00] # get all the available readers r = readers() print "Available readers:", r reader = r[0] print "Using:", reader connection = reader.createConnection() connection.connect() data, sw1, sw2 = connection.transmit(SELECT) print data print "Select Applet: %02X %02X" % (sw1, sw2) data, sw1, sw2 = connection.transmit(COMMAND) print data print "Command: %02X %02X" % (sw1, sw2)
You can see that a connection is created with connection = reader.createConnection()
and then the card is connected with connection.connect()
, but the
card is never explicitly disconnected and the connection is never
released.
It was already possible to use:
It is now also possible to use:
The code becomes (after convertion to Python 3):
#! /usr/bin/env python from smartcard.System import readers from smartcard.util import toASCIIString # define the APDUs used in this script SELECT = [0x00, 0xA4, 0x04, 0x00, 0x0A, 0xA0, 0x00, 0x00, 0x00, 0x62, 0x03, 0x01, 0x0C, 0x06, 0x01] COMMAND = [0x00, 0x00, 0x00, 0x00] # get all the available readers r = readers() print("Available readers:", r) reader = r[0] print("Using:", reader) connection = reader.createConnection() connection.connect() data, sw1, sw2 = connection.transmit(SELECT) print(data) print(f"Select Applet: {sw1:02X} {sw2:02X}") data, sw1, sw2 = connection.transmit(COMMAND) print(data) print(toASCIIString(data)) print(f"Command: {sw1:02X} {sw2:02X}") connection.disconnect() connection.release()
Output
Available readers: ['Gemalto PC Twin Reader'] Using: Gemalto PC Twin Reader [] Select Applet: 90 00 [72, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100, 33] Hello world! Command: 90 00
If you do not want to explicitly call .disconnect()
and
.release()
you can use a context manager and the Python with
statement.
[...] with reader.createConnection() as connection: connection.connect() data, sw1, sw2 = connection.transmit(SELECT) print(data) print(f"Select Applet: {sw1:02X} {sw2:02X}") data, sw1, sw2 = connection.transmit(COMMAND) print(data) print(toASCIIString(data)) print(f"Command: {sw1:02X} {sw2:02X}")
Or you can also use the del
instruction to call the object finalizer.
CardRequest
You can also use with
with a CardRequest
object.
#! /usr/bin/env python from smartcard.CardRequest import CardRequest print("Insert a card within 10 seconds") with CardRequest(timeout=10) as req: svc = req.waitforcard() svc.connection.connect() r = svc.connection.transmit([0,0,0,0]) print(r) svc.connection.disconnect() svc.connection.release()
Ouput
CardService
And also with a CardService
object.
Traces
It is possible and easy to trace the actions performed by PySCard using an smartcard.Observer object.
#! /usr/bin/env python from time import sleep from smartcard.CardRequest import CardRequest from smartcard.CardConnectionObserver import ConsoleCardConnectionObserver print("Insert a card within 10 seconds") with CardRequest(timeout=10) as req: with req.waitforcard() as svc: observer = ConsoleCardConnectionObserver() svc.connection.addObserver(observer) svc.connection.connect() r = svc.connection.transmit([0,0,0,0]) print(r) sleep(10) print("end")
Ouput
Insert a card within 10 seconds connecting to Gemalto PC Twin Reader > 00 00 00 00 < [] 6D 00 ([], 109, 0) disconnecting from Gemalto PC Twin Reader release from Gemalto PC Twin Reader end
You can see that .disconnect()
and .release()
methods are called
and traced.
The traces are displayed 10 seconds before the "end", i.e. when the
two with
exit, not when the program ends (and the garbage
collector is used to clean each object).
Why
The CardConnection().release()
method has been added to release the
PC/SC context and allow pcscd (the daemon used in pcsc-lite) to exit automatically before the end of
the Python program. See the issue PCSCCardConnection.__del__ method raises
exceptions that cannot be easily handled in python code. #223 and in
particular this comment
https://github.com/LudovicRousseau/pyscard/issues/223#issuecomment-2773120282:
pcscd should not auto exit before all the contexts have been released.
Btw this was indeed the other (only loosely related) issue that I mentioned at the end of initial post.
I.e. that using pyscard in a main process or a thread always holds up pcsc-lite running (unless I missed something, iirc with connection management in C code), and there seem to be no way to say "please close the damn context, time to read cards was gone 10 hours ago, and this silly NFC reader eats up 300 milliamps" :)
So would be nice to have a way to cleanly close such context/connection manually (allowing pcsc-lite to exit too, and without purging pyscard from memory with subprocess) for other reasons as well, but again, probably a separate issue.
And I think context != connection, as latter are managed somewhere in C, not in python, but might be wrong or misremembering.
The problem is that the user has a smart card reader that consumes 300 milliamps even when it's doing "nothing".
So it's a good idea to let pcscd exit so that the USB device will be put in low power thanks to the autosuspend Linux feature.
I haven't checked the behavior on macOS or Windows.
Conclusion
Not all users will need or use the .release()
method. If your
Python program runs for a long time and you only use the smart card at
certain times, it may be a good idea to take a look at your code and
update it.