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