Add support for automatic card handling

When using the batch mode of pySim-prog, the user has to insert/remove
the cards from the cardreader manually. This is fine for small batches,
but for high volume batches this method is not applicable.

This patch adds support for the integration of an automatic card handler
machine. The user can freely configure a custom commandline that is
executed when a card should be inserted or moved to a good/bad
collection bin.

Change-Id: Icfed3cad7927b92816723d75603b78e1a4b87ef1
Related: SYS#4654
This commit is contained in:
Philipp Maier 2019-08-30 11:41:02 +02:00
parent e053da5a14
commit c5b422e2ca
3 changed files with 213 additions and 77 deletions

View File

@ -12,6 +12,7 @@ fi
virtualenv -p python2 venv --system-site-packages
. venv/bin/activate
pip install pytlv
pip install pyyaml
cd pysim-testdata
../tests/pysim-test.sh

View File

@ -30,6 +30,7 @@ import os
import random
import re
import sys
import traceback
try:
import json
@ -41,6 +42,7 @@ from pySim.commands import SimCardCommands
from pySim.cards import _cards_classes
from pySim.utils import h2b, swap_nibbles, rpad, derive_milenage_opc, calculate_luhn, dec_iccid
from pySim.ts_51_011 import EF
from pySim.card_handler import *
def parse_options():
@ -163,6 +165,9 @@ def parse_options():
help="Perform a 'dry run', don't actually program the card",
default=False, action="store_true")
parser.add_option("--card_handler", dest="card_handler", metavar="FILE",
help="Use automatic card handling machine")
(options, args) = parser.parse_args()
if options.type == 'list':
@ -610,6 +615,74 @@ def card_detect(opts, scc):
return card
def process_card(opts, first, card_handler):
if opts.dry_run is False:
# Connect transport
card_handler.get(first)
if opts.dry_run is False:
# Get card
card = card_detect(opts, scc)
if card is None:
print "No card detected!"
return -1
# Probe only
if opts.probe:
return 0
# Erase if requested
if opts.erase:
print "Formatting ..."
card.erase()
card.reset()
# Generate parameters
if opts.source == 'cmdline':
cp = gen_parameters(opts)
elif opts.source == 'csv':
imsi = None
iccid = None
if opts.read_iccid:
if opts.dry_run:
# Connect transport
card_handler.get(false)
(res,_) = scc.read_binary(['3f00', '2fe2'], length=10)
iccid = dec_iccid(res)
elif opts.read_imsi:
if opts.dry_run:
# Connect transport
card_handler.get(false)
(res,_) = scc.read_binary(EF['IMSI'])
imsi = swap_nibbles(res)[3:]
else:
imsi = opts.imsi
cp = read_params_csv(opts, imsi=imsi, iccid=iccid)
if cp is None:
print "Error reading parameters\n"
return 2
print_parameters(cp)
if opts.dry_run is False:
# Program the card
print "Programming ..."
card.program(cp)
else:
print "Dry Run: NOT PROGRAMMING!"
# Write parameters permanently
write_parameters(opts, cp)
# Batch mode state update and save
if opts.num is not None:
opts.num += 1
save_batch(opts)
card_handler.done()
return 0
if __name__ == '__main__':
# Parse options
@ -638,88 +711,42 @@ if __name__ == '__main__':
# Batch mode init
init_batch(opts)
if opts.card_handler:
card_handler = card_handler_auto(sl, opts.card_handler)
else:
card_handler = card_handler(sl)
# Iterate
done = False
first = True
card = None
while not done:
while 1:
try:
rc = process_card(opts, first, card_handler)
except (KeyboardInterrupt):
print ""
print "Terminated by user!"
sys.exit(0)
except (SystemExit):
raise
except:
print ""
print "Card programming failed with an execption:"
print "---------------------8<---------------------"
traceback.print_exc()
print "---------------------8<---------------------"
print ""
rc = -1
if opts.dry_run is False:
# Connect transport
print "Insert card now (or CTRL-C to cancel)"
sl.wait_for_card(newcardonly=not first)
# Something did not work as well as expected, however, lets
# make sure the card is pulled from the reader.
if rc != 0:
card_handler.error()
# If we are not in batch mode we are done in any case, so lets
# exit here.
if not opts.batch_mode:
sys.exit(rc)
# Not the first anymore !
first = False
if opts.dry_run is False:
# Get card
card = card_detect(opts, scc)
if card is None:
if opts.batch_mode:
first = False
continue
else:
sys.exit(-1)
# Probe only
if opts.probe:
break;
# Erase if requested
if opts.erase:
print "Formatting ..."
card.erase()
card.reset()
# Generate parameters
if opts.source == 'cmdline':
cp = gen_parameters(opts)
elif opts.source == 'csv':
imsi = None
iccid = None
if opts.read_iccid:
if opts.dry_run:
# Connect transport
print "Insert card now (or CTRL-C to cancel)"
sl.wait_for_card(newcardonly=not first)
(res,_) = scc.read_binary(['3f00', '2fe2'], length=10)
iccid = dec_iccid(res)
print iccid
elif opts.read_imsi:
if opts.dry_run:
# Connect transport
print "Insert card now (or CTRL-C to cancel)"
sl.wait_for_card(newcardonly=not first)
(res,_) = scc.read_binary(EF['IMSI'])
imsi = swap_nibbles(res)[3:]
else:
imsi = opts.imsi
cp = read_params_csv(opts, imsi=imsi, iccid=iccid)
if cp is None:
print "Error reading parameters\n"
sys.exit(2)
print_parameters(cp)
if opts.dry_run is False:
# Program the card
print "Programming ..."
if opts.dry_run is not True:
card.program(cp)
else:
print "Dry Run: NOT PROGRAMMING!"
# Write parameters permanently
write_parameters(opts, cp)
# Batch mode state update and save
if opts.num is not None:
opts.num += 1
save_batch(opts)
# Done for this card and maybe for everything ?
print "Done !\n"
if not opts.batch_mode:
done = True

108
pySim/card_handler.py Normal file
View File

@ -0,0 +1,108 @@
#!/usr/bin/env python2
# -*- coding: utf-8 -*-
""" pySim: card handler utilities
"""
#
# (C) 2019 by Sysmocom s.f.m.c. GmbH
# All Rights Reserved
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
import subprocess
import sys
import yaml
# Manual card handler: User is prompted to insert/remove card from the reader.
class card_handler:
sl = None
def __init__(self, sl):
self.sl = sl
def get(self, first = False):
print "Ready for Programming: Insert card now (or CTRL-C to cancel)"
self.sl.wait_for_card(newcardonly=not first)
def error(self):
print "Programming failed: Remove card from reader"
print ""
def done(self):
print "Programming successful: Remove card from reader"
print ""
# Automatic card handler: A machine is used to handle the cards.
class card_handler_auto:
sl = None
cmds = None
verbose = True
def __init__(self, sl, config_file):
print "Card handler Config-file: " + str(config_file)
self.sl = sl
with open(config_file) as cfg:
self.cmds = yaml.load(cfg, Loader=yaml.FullLoader)
self.verbose = (self.cmds.get('verbose') == True)
def __print_outout(self,out):
print ""
print "Card handler output:"
print "---------------------8<---------------------"
stdout = out[0].strip()
if len(stdout) > 0:
print "stdout:"
print stdout
stderr = out[1].strip()
if len(stderr) > 0:
print "stderr:"
print stderr
print "---------------------8<---------------------"
print ""
def __exec_cmd(self, command):
print "Card handler Commandline: " + str(command)
proc = subprocess.Popen([command], stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
out = proc.communicate()
rc = proc.returncode
if rc != 0 or self.verbose:
self.__print_outout(out)
if rc != 0:
print ""
print "Error: Card handler failure! (rc=" + str(rc) + ")"
sys.exit(rc)
def get(self, first = False):
print "Ready for Programming: Transporting card into the reader-bay..."
self.__exec_cmd(self.cmds['get'])
self.sl.connect()
def error(self):
print "Programming failed: Transporting card to the error-bin..."
self.__exec_cmd(self.cmds['error'])
print ""
def done(self):
print "Programming successful: Transporting card into the collector bin..."
self.__exec_cmd(self.cmds['done'])
print ""