9c42633dc6f2991752df558f7ad69f94e985ca20
[ussd-widget] / ussd-common / src / usr / bin / ussdquery.py
1 #!/usr/bin/python
2 # -*- coding: utf-8 -*-
3 ## This program is free software; you can redistribute it and/or modify
4 ## it under the terms of the GNU General Public License as published
5 ## by the Free Software Foundation; version 2 and higer.
6 ##
7 ## Guseynov Alexey (kibergus bark-bark gmail.com) 2010
8
9 import pexpect
10 import time
11 from subprocess import *
12 import sys
13 import gsmdecode
14 import re
15 import fcntl
16 import os
17 import stat
18
19 # Needed for correct output of utf-8 symbols.
20 sys.stdout=file("/dev/stdout", "wb")
21
22 def check_number(number):
23         if number == "":
24                 return False
25         for s in number :
26                 if not (s in ["1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "+", "*", "#"]) :
27                         return False
28         return True
29
30 if len(sys.argv) == 1:
31     print "Usage:\nussdquery.py <ussd number> [options]\nussdquery.py interactive [options]\n"+\
32 "Options:\n-l language. Allowed languages: German, English, Italian, French, Spanish, Dutch, Swedish, Danish, Portuguese, Finnish, Norwegian, Greek, Turkish, Reserved1, Reserved2, Unspecified\n"+\
33 "-r retry count. 0 default. Use -1 for infinite.\n-f If specified, errors, which occur on last query are threated as fatal\n"+\
34 "-t timeout in seconds. Default 30. Timeout is considered to be critical error because you can't be sure answer for what request was returned.\n"+\
35 "-d delimeter. Default is '\\n> '"+\
36 "For USSD menu navigation divide USSD number via spacebars for every next menu selection. Type exit in interactive mode to exit."
37     sys.exit()
38
39 lockf = None
40
41 def gain_lock ():
42         global lockf
43         lockf = open("/tmp/ussdquery.lock", 'a')
44         fcntl.flock(lockf,fcntl.LOCK_EX)
45         try:
46                 os.chmod("/tmp/ussdquery.lock", stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IWGRP | stat.S_IROTH | stat.S_IWOTH)
47         except:
48                 None
49
50 def release_lock ():
51         global lockf
52         fcntl.flock(lockf,fcntl.LOCK_UN)
53         lockf.close()
54
55 def init_modem(modem):
56         # We have only one modem, simultaneous acces wouldn't bring anything good
57         gain_lock()
58         response = ""
59         init_retry = 5
60         while response != "OK" and init_retry > 0 :
61                 if modem == None :
62                         # OK response should be recieved shortly
63                         modem = pexpect.spawn('pnatd', [], 2)
64                 try :
65                         modem.send('at\r');
66                         # Read our "at" command
67                         modem.readline();
68                         # Read OK response
69                         response = modem.readline().strip()
70                 except pexpect.TIMEOUT:
71                         modem.kill(9)
72                         modem = None
73                         response = ""
74                 if response != "OK" :
75                         time.sleep(0.5)
76                         init_retry -= 1
77                 else:
78                         try:
79                                 # Switch output encoding to GSM default encoding
80                                 modem.send('at+cscs="GSM"\r');
81                                 # Read our command
82                                 modem.readline();
83                                 # Read OK response
84                                 response = modem.readline().strip()
85                         except pexpect.TIMEOUT:
86                                 modem.kill(9)
87                                 modem = None
88                                 response = ""
89                         
90                 if response != "OK" :
91                         time.sleep(0.5)
92                         init_retry -= 1
93
94         if response != "OK" :
95                 print >> sys.stderr, "Couldn't init modem."
96                 if modem != None:
97                         modem.kill(9)
98                 release_lock()
99                 sys.exit (-1)
100
101         modem.timeout = timeout
102         return modem
103
104 def close_modem (modem):
105         modem.sendeof()
106         modem.kill(9)
107         release_lock()
108
109 retry = 0
110 allow_last_error = True
111 delimiter = "\n> "
112 language = 15
113 timeout = 30
114
115 if sys.argv[1] == "interactive":
116         number = "interactive"
117 else:
118         number = sys.argv[1].split(" ")
119         for n in number: 
120                 if not check_number(n):
121                         print >> sys.stderr, "Sintax error in USSD number."
122                         sys.exit(-7)
123
124 # Parsing command line options
125 arg = 1
126 state = "arg"
127 while arg < len(sys.argv)-1:
128         arg += 1
129         if state == "arg":
130                 if sys.argv[arg] == "-l":
131                         state = "lang"
132                         continue
133                 if sys.argv[arg] == "-r":
134                         state = "retry"
135                         continue
136                 if sys.argv[arg] == "-t":
137                         state = "timeout"
138                         continue
139                 if sys.argv[arg] == "-d":
140                         state = "delim"
141                         continue
142                 if sys.argv[arg] == "-f":
143                         allow_last_error = False
144                         continue
145
146         if state == "lang":
147                 if sys.argv[arg] == "German":
148                         language = 0
149                 elif sys.argv[arg] == "English":
150                         language = 1
151                 elif sys.argv[arg] == "Italian":
152                         language = 2
153                 elif sys.argv[arg] == "French":
154                         language = 3
155                 elif sys.argv[arg] == "Spanish":
156                         language = 4
157                 elif sys.argv[arg] == "Dutch":
158                         language = 5
159                 elif sys.argv[arg] == "Swedish":
160                         language = 6
161                 elif sys.argv[arg] == "Danish":
162                         language = 7
163                 elif sys.argv[arg] == "Portuguese":
164                         language = 8
165                 elif sys.argv[arg] == "Finnish":
166                         language = 9
167                 elif sys.argv[arg] == "Norwegian":
168                         language = 10
169                 elif sys.argv[arg] == "Greek":
170                         language = 11
171                 elif sys.argv[arg] == "Turkish":
172                         language = 12
173                 elif sys.argv[arg] == "Reserved1":
174                         language = 13
175                 elif sys.argv[arg] == "Reserved2":
176                         language = 14
177                 elif sys.argv[arg] == "Unspecified":
178                         language = 15
179                 else:
180                         print >> sys.stderr, "Language unknown, falling back to unspecified."
181                 state = "arg"
182                 continue
183
184         if state == "delim":
185                 if number == "interactive":
186                         delimiter = sys.argv[arg]
187                 else:
188                         print >> sys.stderr, "Delimiter is only supported in interactive mode."
189                 state = "arg"
190                 continue
191
192         if state == "retry":
193                 if number == "interactive":
194                         print >> sys.stderr, "Retry is only supported in normal mode."
195                 else:
196                         try:
197                                 retry = int(sys.argv[arg])
198                                 if retry < -1:
199                                         print >> sys.stderr, "Number of allowed errors must be >= -1. -1 assumed."
200                                 retry = -1
201                         except:
202                                 print >> sys.stderr, "Retry must be an integer."
203                                 sys.exit(-5)
204                 state = "arg"
205                 continue
206         if state == "timeout":
207                 try:
208                         timeout = int(sys.argv[arg])
209                 except:
210                         print >> sys.stderr, "Timeout must be an integer."
211                         sys.exit(-5)
212                 state = "arg"
213                 continue
214
215         print >> sys.stderr, "Unrecogmized argument: "+sys.argv[arg]
216         
217 if retry == -1:
218         retry_forever = True
219 else:
220         retry_forever = False
221
222 modem = None
223
224 # Now we are ready to send commands
225
226 stage = 0
227 if number == "interactive":
228         sys.stdout.write(delimiter)
229 while number == "interactive" or stage < len(number):
230         if modem == None:
231                 modem = init_modem(modem)
232
233         if number == "interactive":
234                 cnumber = sys.stdin.readline().strip()
235                 if cnumber == "exit":
236                         close_modem (modem)
237                         sys.exit (0)
238                 if not check_number (cnumber):
239                         sys.stdout.write ("Sintax error in USSD number"+delimiter)
240                         continue
241         else:
242                 cnumber = number[stage]
243
244         if retry == -1 and not retry_forever:
245                 print >> sys.stderr, "Retry limit is over. Giving up."
246                 break
247
248         try :
249                 modem.send('at+cusd=1,"'+cnumber+'",'+str(language)+'\r')
250                 # Read our query echoed back
251                 modem.readline()
252
253                 #Read and parse reply
254                 replystring = modem.readline().decode('string_escape')
255                 # This will read out unneeded info from modem
256                 modem.readline()
257                 modem.readline()
258         except pexpect.TIMEOUT:
259                 print >> sys.stderr, "Timeout. Modem didn't reply."
260                 close_modem (modem)
261                 sys.exit (-2)
262
263         if replystring.strip() == "ERROR" :
264                 retry -= 1
265                 print >> sys.stderr, "Modem returned ERROR. Query not executed."
266                 continue
267
268         try:
269                 reresult = re.match("(?s)^\\+CUSD: (\\d+),\"(.*)\",(\\d+)$", replystring.strip())
270
271                 # 0 no further user action required (network initiated USSD-Notify, or no further information needed after mobile initiated operation)
272                 # 1 further user action required (network initiated USSD-Request, or further information needed after mobile initiated operation)
273                 # 2 USSD terminated by network
274                 # 3 other local client has responded
275                 # 4 operation not supported
276                 # 5 network time out
277                 reply_status=reresult.group(1)
278                 reply = reresult.group(2)
279                 # See GSM 07.07 and GSM 03.38
280                 encoding = reresult.group(3)
281         except:
282                 retry -= 1
283                 print >> sys.stderr, "Couldn't parse modem answer: "+replystring
284                 continue
285
286         if reply_status == 0:
287                 # May be somebody else needs it
288                 close_modem(modem)
289                 modem = None
290         elif reply_status == 2:
291                 print >> sys.stderr, "USSD terminated by network."
292         elif reply_status == 3:
293                 print >> sys.stderr, "Error: other local client has responded."
294         elif reply_status == 4:
295                 print >> sys.stderr, "Operation not supported."
296         elif reply_status == 5:
297                 print >> sys.stderr, "Network time out."
298
299         # Decoding ansver
300         reply = gsmdecode.decode(reply, int(encoding))
301
302         if number == "interactive":
303                 # prints line feed
304                 sys.stdout.write(reply+delimiter)
305         else:
306                 if stage == len(number)-1:
307                         print reply
308         stage += 1
309         if not allow_last_error and namber != "interactive" and stage == len(number) - 1:
310                 retry = 0
311
312 if modem != None:
313         close_modem (modem)