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