blob: f5a0f238eef3f5c40629cde60c6cfed1455fb171 [file] [log] [blame]
Vadim Yanitskiy29ca8042020-05-09 21:23:37 +07001# -*- coding: utf-8 -*-
2
Vadim Yanitskiy29ca8042020-05-09 21:23:37 +07003# Copyright (C) 2020 Vadim Yanitskiy <axilirator@gmail.com>
4#
5# This program is free software: you can redistribute it and/or modify
6# it under the terms of the GNU General Public License as published by
7# the Free Software Foundation, either version 2 of the License, or
8# (at your option) any later version.
9#
10# This program is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13# GNU General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License
16# along with this program. If not, see <http://www.gnu.org/licenses/>.
17#
18
Vadim Yanitskiy29ca8042020-05-09 21:23:37 +070019import logging as log
20import serial
21import time
22import re
23
24from pySim.transport import LinkBase
25from pySim.exceptions import *
26
27# HACK: if somebody needs to debug this thing
28# log.root.setLevel(log.DEBUG)
29
30class ModemATCommandLink(LinkBase):
Harald Welteee3501f2021-04-02 13:00:18 +020031 """Transport Link for 3GPP TS 27.007 compliant modems."""
32 def __init__(self, device:str='/dev/ttyUSB0', baudrate:int=115200):
Vadim Yanitskiy29ca8042020-05-09 21:23:37 +070033 self._sl = serial.Serial(device, baudrate, timeout=5)
34 self._device = device
35 self._atr = None
36
37 # Trigger initial reset
38 self.reset_card()
39
40 def __del__(self):
41 self._sl.close()
42
43 def send_at_cmd(self, cmd):
44 # Convert from string to bytes, if needed
45 bcmd = cmd if type(cmd) is bytes else cmd.encode()
46 bcmd += b'\r'
47
48 # Send command to the modem
49 log.debug('Sending AT command: %s' % cmd)
50 try:
51 wlen = self._sl.write(bcmd)
52 assert(wlen == len(bcmd))
53 except:
54 raise ReaderError('Failed to send AT command: %s' % cmd)
55
56 # Give the modem some time...
57 time.sleep(0.3)
58
59 # Read the response
60 try:
61 # Skip characters sent back
62 self._sl.read(wlen)
63 # Read the rest
64 rsp = self._sl.read_all()
65
66 # Strip '\r\n'
67 rsp = rsp.strip()
68 # Split into a list
69 rsp = rsp.split(b'\r\n\r\n')
70 except:
71 raise ReaderError('Failed parse response to AT command: %s' % cmd)
72
73 log.debug('Got response from modem: %s' % rsp)
74 return rsp
75
76 def reset_card(self):
77 # Make sure that we can talk to the modem
78 if self.send_at_cmd('AT') != [b'OK']:
79 raise ReaderError('Failed to connect to modem')
80
81 # Reset the modem, just to be sure
82 if self.send_at_cmd('ATZ') != [b'OK']:
83 raise ReaderError('Failed to reset the modem')
84
85 # Make sure that generic SIM access is supported
86 if self.send_at_cmd('AT+CSIM=?') != [b'OK']:
87 raise ReaderError('The modem does not seem to support SIM access')
88
89 log.info('Modem at \'%s\' is ready!' % self._device)
90
91 def connect(self):
92 pass # Nothing to do really ...
93
94 def disconnect(self):
95 pass # Nothing to do really ...
96
97 def wait_for_card(self, timeout=None, newcardonly=False):
98 pass # Nothing to do really ...
99
Harald Weltec34f9402021-04-10 10:55:24 +0200100 def _send_apdu_raw(self, pdu):
Vadim Yanitskiy29ca8042020-05-09 21:23:37 +0700101 # Prepare the command as described in 8.17
102 cmd = 'AT+CSIM=%d,\"%s\"' % (len(pdu), pdu)
103
104 # Send AT+CSIM command to the modem
105 # TODO: also handle +CME ERROR: <err>
106 rsp = self.send_at_cmd(cmd)
107 if len(rsp) != 2 or rsp[-1] != b'OK':
108 raise ReaderError('APDU transfer failed: %s' % str(rsp))
109 rsp = rsp[0] # Get rid of b'OK'
110
111 # Make sure that the response has format: b'+CSIM: %d,\"%s\"'
112 try:
113 result = re.match(b'\+CSIM: (\d+),\"([0-9A-F]+)\"', rsp)
114 (rsp_pdu_len, rsp_pdu) = result.groups()
115 except:
116 raise ReaderError('Failed to parse response from modem: %s' % rsp)
117
118 # TODO: make sure we have at least SW
119 data = rsp_pdu[:-4].decode()
120 sw = rsp_pdu[-4:].decode()
121 return data, sw