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