blob: eef38cb309ef2defb2dd1565d5a021cec8db994f [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."""
Harald Welteeb05b2f2021-04-10 11:01:56 +020032 def __init__(self, device:str='/dev/ttyUSB0', baudrate:int=115200, **kwargs):
33 super().__init__(**kwargs)
Vadim Yanitskiy29ca8042020-05-09 21:23:37 +070034 self._sl = serial.Serial(device, baudrate, timeout=5)
35 self._device = device
36 self._atr = None
37
38 # Trigger initial reset
39 self.reset_card()
40
41 def __del__(self):
42 self._sl.close()
43
44 def send_at_cmd(self, cmd):
45 # Convert from string to bytes, if needed
46 bcmd = cmd if type(cmd) is bytes else cmd.encode()
47 bcmd += b'\r'
48
49 # Send command to the modem
50 log.debug('Sending AT command: %s' % cmd)
51 try:
52 wlen = self._sl.write(bcmd)
53 assert(wlen == len(bcmd))
54 except:
55 raise ReaderError('Failed to send AT command: %s' % cmd)
56
57 # Give the modem some time...
58 time.sleep(0.3)
59
60 # Read the response
61 try:
62 # Skip characters sent back
63 self._sl.read(wlen)
64 # Read the rest
65 rsp = self._sl.read_all()
66
67 # Strip '\r\n'
68 rsp = rsp.strip()
69 # Split into a list
70 rsp = rsp.split(b'\r\n\r\n')
71 except:
72 raise ReaderError('Failed parse response to AT command: %s' % cmd)
73
74 log.debug('Got response from modem: %s' % rsp)
75 return rsp
76
77 def reset_card(self):
78 # Make sure that we can talk to the modem
79 if self.send_at_cmd('AT') != [b'OK']:
80 raise ReaderError('Failed to connect to modem')
81
82 # Reset the modem, just to be sure
83 if self.send_at_cmd('ATZ') != [b'OK']:
84 raise ReaderError('Failed to reset the modem')
85
86 # Make sure that generic SIM access is supported
87 if self.send_at_cmd('AT+CSIM=?') != [b'OK']:
88 raise ReaderError('The modem does not seem to support SIM access')
89
90 log.info('Modem at \'%s\' is ready!' % self._device)
91
92 def connect(self):
93 pass # Nothing to do really ...
94
95 def disconnect(self):
96 pass # Nothing to do really ...
97
98 def wait_for_card(self, timeout=None, newcardonly=False):
99 pass # Nothing to do really ...
100
Harald Weltec34f9402021-04-10 10:55:24 +0200101 def _send_apdu_raw(self, pdu):
Robert Falkenberg8e15c182021-05-01 08:07:27 +0200102 # Make sure pdu has upper case hex digits [A-F]
103 pdu = pdu.upper()
104
Vadim Yanitskiy29ca8042020-05-09 21:23:37 +0700105 # Prepare the command as described in 8.17
106 cmd = 'AT+CSIM=%d,\"%s\"' % (len(pdu), pdu)
107
108 # Send AT+CSIM command to the modem
109 # TODO: also handle +CME ERROR: <err>
110 rsp = self.send_at_cmd(cmd)
111 if len(rsp) != 2 or rsp[-1] != b'OK':
112 raise ReaderError('APDU transfer failed: %s' % str(rsp))
113 rsp = rsp[0] # Get rid of b'OK'
114
115 # Make sure that the response has format: b'+CSIM: %d,\"%s\"'
116 try:
117 result = re.match(b'\+CSIM: (\d+),\"([0-9A-F]+)\"', rsp)
118 (rsp_pdu_len, rsp_pdu) = result.groups()
119 except:
120 raise ReaderError('Failed to parse response from modem: %s' % rsp)
121
122 # TODO: make sure we have at least SW
123 data = rsp_pdu[:-4].decode()
124 sw = rsp_pdu[-4:].decode()
125 return data, sw