USSD menu navigation implementation
[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 def check_number(number):
20         if number == "":
21                 return False
22         for s in number :
23                 if not (s in ["1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "+", "*", "#"]) :
24                         return False
25         return True
26
27 if len(sys.argv) == 1:
28     print "Usage:\nussdquery.py <ussd number> [options]\nussdquery.py interactive [options]\n"+\
29 "Options:\n-l language. Allowed languages: German, English, Italian, French, Spanish, Dutch, Swedish, Danish, Portuguese, Finnish, Norwegian, Greek, Turkish, Reserved1, Reserved2, Unspecified\n"+\
30 "-r retry count. 0 default. Use -1 for infinite.\n-f If specified, errors, which occur on last query are threated as fatal\n"+\
31 "-t timeout in seconds. Default 30. Timeout is considered to be critical error because you can't be shure answer for what request was returned.\n"+\
32 "-d delimeter. Default is '\\n> '"+\
33 "For USSD menu navigation divide USSD number via spacebars for every nex menu selection. Type exit in interactive mode to exit."
34     sys.exit()
35
36 retry = 0
37 allow_last_error = True
38 delimiter = "\n> "
39 language = 15
40 timeout = 30
41
42 if sys.argv[1] == "interactive":
43         number = "interactive"
44 else:
45         number = sys.argv[1].split(" ")
46         for n in number: 
47                 if not check_number(n):
48                         print >> sys.stderr, "Sintax error in USSD number."
49                         sys.exit(-7)
50
51 # Parsing command line options
52 arg = 1
53 state = "arg"
54 while arg < len(sys.argv)-1:
55         arg += 1
56         if state == "arg":
57                 if sys.argv[arg] == "-l":
58                         state = "lang"
59                         continue
60                 if sys.argv[arg] == "-r":
61                         state = "retry"
62                         continue
63                 if sys.argv[arg] == "-t":
64                         state = "timeout"
65                         continue
66                 if sys.argv[arg] == "-d":
67                         state = "delim"
68                         continue
69                 if sys.argv[arg] == "-f":
70                         allow_last_error = False
71                         continue
72
73         if state == "lang":
74                 if sys.argv[arg] == "German":
75                         language = 0
76                 elif sys.argv[arg] == "English":
77                         language = 1
78                 elif sys.argv[arg] == "Italian":
79                         language = 2
80                 elif sys.argv[arg] == "French":
81                         language = 3
82                 elif sys.argv[arg] == "Spanish":
83                         language = 4
84                 elif sys.argv[arg] == "Dutch":
85                         language = 5
86                 elif sys.argv[arg] == "Swedish":
87                         language = 6
88                 elif sys.argv[arg] == "Danish":
89                         language = 7
90                 elif sys.argv[arg] == "Portuguese":
91                         language = 8
92                 elif sys.argv[arg] == "Finnish":
93                         language = 9
94                 elif sys.argv[arg] == "Norwegian":
95                         language = 10
96                 elif sys.argv[arg] == "Greek":
97                         language = 11
98                 elif sys.argv[arg] == "Turkish":
99                         language = 12
100                 elif sys.argv[arg] == "Reserved1":
101                         language = 13
102                 elif sys.argv[arg] == "Reserved2":
103                         language = 14
104                 elif sys.argv[arg] == "Unspecified":
105                         language = 15
106                 else:
107                         print >> sys.stderr, "Language unknown, falling back to unspecified."
108                 state = "arg"
109                 continue
110
111         if state == "delim":
112                 if number == "interactive":
113                         delimiter = sys.argv[arg]
114                 else:
115                         print >> sys.stderr, "Delimiter is only supported in interactive mode."
116                 state = "arg"
117                 continue
118
119         if state == "retry":
120                 if number == "interactive":
121                         print >> sys.stderr, "Retry is only supported in normal mode."
122                 else:
123                         try:
124                                 retry = int(sys.argv[arg])
125                                 if retry < -1:
126                                         print >> sys.stderr, "Number of allowed errors must be >= -1. -1 assumed."
127                                         retry = -1
128                         except:
129                                 print >> sys.stderr, "Retry must be an integer."
130                                 sys.exit(-5)
131                 state = "arg"
132                 continue
133         if state == "timeout":
134                 try:
135                         timeout = int(sys.argv[arg])
136                 except:
137                         print >> sys.stderr, "Timeout must be an integer."
138                         sys.exit(-5)
139                 state = "arg"
140                 continue
141
142         print >> sys.stderr, "Unrecogmized argument: "+sys.argv[arg]
143         
144 if retry == -1:
145         retry_forever = True
146 else:
147         retry_forever = False
148
149 # We have only one modem, simultaneous acces wouldn't bring anything good
150 lockf = open("/tmp/ussdquery.lock", 'a')
151 fcntl.flock(lockf,fcntl.LOCK_EX)
152 try:
153         os.chmod("/tmp/ussdquery.lock", stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IWGRP | stat.S_IROTH | stat.S_IWOTH)
154 except:
155         None
156 child = None
157 response = ""
158 init_retry = 5
159 while response != "OK" and init_retry > 0 :
160         if child == None :
161                 # OK response should be recieved shortly
162                 child = pexpect.spawn('pnatd', [], 2)
163         try :
164                 child.send('at\r');
165                 # Read our "at" command
166                 child.readline();
167                 # Read OK response
168                 response = child.readline().strip()
169         except pexpect.TIMEOUT:
170                 child.kill(9)
171                 child = None
172                 response = ""
173         if response != "OK" :
174                 time.sleep(0.5)
175                 init_retry -= 1
176
177 if response != "OK" :
178         print >> sys.stderr, "Couldn't init modem."
179         if child != None:
180                 child.kill(9)
181         fcntl.flock(lockf,fcntl.LOCK_UN)
182         lockf.close()
183         sys.exit (-1)
184
185 child.timeout = timeout
186 # Now we are ready to send commands
187
188 stage = 0
189 if number == "interactive":
190         sys.stdout.write(delimiter)
191 while number == "interactive" or stage < len(number):
192         if number == "interactive":
193                 cnumber = sys.stdin.readline().strip()
194                 if cnumber == "exit":
195                         child.kill(9)
196                         fcntl.flock(lockf,fcntl.LOCK_UN)
197                         lockf.close()
198                         sys.exit (-2)
199                 if not check_number (cnumber):
200                         sys.stdout.write ("Sintax error in USSD number"+delimiter)
201                         continue
202         else:
203                 cnumber = number[stage]
204
205         if retry == -1 and not retry_forever:
206                 print >> sys.stderr, "Retry limit is over. Giving up."
207                 break
208
209         try :
210                 child.send('at+cusd=1,"'+cnumber+'",'+str(language)+'\r')
211                 # Read our query echoed back
212                 child.readline()
213
214                 #Read and parse reply
215                 replystring = child.readline().decode('string_escape')
216                 # This will read out unneeded info from modem
217                 child.readline()
218                 child.readline()
219         except pexpect.TIMEOUT:
220                 print >> sys.stderr, "Timeout. Modem didn't reply."
221                 child.kill(9)
222                 fcntl.flock(lockf,fcntl.LOCK_UN)
223                 lockf.close()
224                 sys.exit (-2)
225
226         if replystring.strip() == "ERROR" :
227                 retry -= 1
228                 print >> sys.stderr, "Modem returned ERROR. Query not executed."
229                 continue
230
231         try:
232                 reresult = re.match("(?s)^\\+CUSD: \\d+,\"(.*)\",(\\d+)$", replystring.strip())
233                 reply = reresult.group(1)
234                 encoding = reresult.group(2)
235         except:
236                 retry -= 1
237                 print >> sys.stderr, "Couldn't parse modem answer."
238                 continue
239
240         # Decoding ansver
241         reply = gsmdecode.decode(reply, int(encoding))
242
243         if number == "interactive":
244                 # prints line feed
245                 sys.stdout.write(reply+delimiter)
246         else:
247                 if stage == len(number)-1:
248                         print reply
249         stage += 1
250         if not allow_last_error and namber != "interactive" and stage == len(number) - 1:
251                 retry = 0
252
253 child.sendeof()
254 fcntl.flock(lockf,fcntl.LOCK_UN)
255 lockf.close()