Files
mykad-reader-api/example_cli.py
T
ISMAIL MASSERAN ca6e55f853 first init
2026-09-01 10:12:31 +08:00

173 lines
6.1 KiB
Python

"""
Simple CLI Example - Smart Card Command Tool
A command-line interface demonstrating how to use the API.py library
for sending APDU and escape commands to smart card readers.
Usage:
python example_cli.py
"""
from API import list_readers, connect_to_reader, send_command, disconnect_reader, parse_command_string
from smartcard.util import toHexString
from smartcard.Exceptions import NoCardException
def list_available_readers():
"""List and display all available smart card readers."""
readers = list_readers()
if not readers:
print("No smart card readers found.")
return []
print("Available readers:")
for index, reader in enumerate(readers):
print(f"{index + 1}: {reader}")
return readers
def connect_to_reader_with_fallback(reader):
"""
Connect to reader with automatic fallback logic.
Tries standard mode (APDU) first, falls back to direct mode (escape) if no card.
Returns:
tuple: (connection, is_direct_mode, error_msg, context)
"""
# Try standard connection first (APDU mode - requires card)
connection, error_msg, _ = connect_to_reader(reader, mode='share')
if connection:
print("Connected in standard mode (APDU)")
return connection, False, None, None
# If connection failed due to no card, try direct mode (escape commands)
if error_msg and "No card" in error_msg:
print("No card detected, attempting direct mode...")
hcard, error_msg, hcontext = connect_to_reader(reader, mode='direct')
if hcard:
print("Connected using Direct Mode (Escape commands)")
return hcard, True, None, hcontext
else:
print(f"Direct Mode Failed: {error_msg}")
return None, False, error_msg, None
else:
print(f"Standard Connection Failed: {error_msg}")
return None, False, error_msg, None
def transmit_apdu(connection, command_hex):
"""Send an APDU command and display the response."""
try:
success, response, sw1, sw2, error_msg = send_command(
connection,
command_hex,
mode='apdu'
)
if success:
print(f"Response: {toHexString(response)}")
print(f"Status words: {sw1:02X} {sw2:02X}")
else:
print(f"Transmit Failed: {error_msg}")
except Exception as e:
print(f"Transmit Failed: {str(e)}")
def transmit_escape(connection, command_hex):
"""Send an escape command and display the response."""
try:
command_bytes = parse_command_string(command_hex)
success, response, error_msg = send_command(
connection,
command_bytes,
mode='escape'
)
if success:
if response and len(response) > 0:
# Show first 50 bytes if response is long
if len(response) > 50:
response_str = toHexString(response[:50])
print(f"Response ({len(response)} bytes): {response_str}...")
else:
print(f"Response ({len(response)} bytes): {toHexString(response)}")
else:
print("Response: (empty)")
else:
print(f"Control Failed: {error_msg}")
except Exception as e:
print(f"Control Failed: {str(e)}")
def main():
"""Main interactive loop following apdu.py logic."""
print("=" * 80)
print("Smart Card Command Tool - CLI Example")
print("=" * 80)
print()
while True:
# List available readers
pcsc_readers = list_available_readers()
if not pcsc_readers:
print("No readers available, exiting...")
break
# Prompt user to select reader or quit
try:
user_input = input("\nSelect the reader to connect or press 'q' to exit: ").strip()
except KeyboardInterrupt:
print("\n\nExiting...")
break
if user_input.lower() == 'q':
break
if not user_input:
print("Please enter a number or 'q'")
continue
try:
reader_index = int(user_input) - 1
if 0 <= reader_index < len(pcsc_readers):
selected_reader = pcsc_readers[reader_index]
# Connect with automatic fallback
connection, is_direct, error_msg, hcontext = connect_to_reader_with_fallback(selected_reader)
if connection:
try:
if is_direct:
# Direct mode - escape commands
data = input("Enter the control data to transmit (in hex, e.g., 'E0 00 00 18 00'): ").strip()
if data:
transmit_escape(connection, data)
else:
# Standard mode - APDU commands
data = input("Enter the APDU to transmit (in hex, e.g., 'FF CA 00 00 00'): ").strip()
if data:
transmit_apdu(connection, data)
except KeyboardInterrupt:
print("\nCommand cancelled.")
except Exception as e:
print(f"Error: {str(e)}")
finally:
# Disconnect based on mode
if is_direct:
disconnect_reader(connection, hcontext)
else:
disconnect_reader(connection)
else:
print("Connection failed")
else:
print("Invalid reader index")
except ValueError:
print("Please enter a valid number or 'q'")
except KeyboardInterrupt:
print("\n\nExiting...")
break
print("\nProgram completed.")
if __name__ == "__main__":
main()