From 5a43083ca2b0fd917da3e0ccd14cb3ddba2aa69d Mon Sep 17 00:00:00 2001 From: kibergus Date: Tue, 8 Jun 2010 21:05:20 +0000 Subject: [PATCH] Initial ussd-common integration git-svn-id: file:///svnroot/ussd-widget/trunk@34 d197f4d6-dc93-42ad-8354-0da1f58e353f --- ussd4all/debian/rules | 7 + ussd4all/src/src.pro | 4 - ussd4all/ussdquery/gsmdecode.py | 305 +++++++++++++++++++++++++++++++++++ ussd4all/ussdquery/ussdquery.py | 337 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 649 insertions(+), 4 deletions(-) create mode 100644 ussd4all/ussdquery/gsmdecode.py create mode 100755 ussd4all/ussdquery/ussdquery.py diff --git a/ussd4all/debian/rules b/ussd4all/debian/rules index a07efb6..f58f7bb 100755 --- a/ussd4all/debian/rules +++ b/ussd4all/debian/rules @@ -29,6 +29,13 @@ install: build # Add here commands to install the package into debian/your_appname cd builddir && $(MAKE) INSTALL_ROOT=$(CURDIR)/debian/$(APPNAME) install + + mkdir -p "$(CURDIR)/debian/ussd-common" + mkdir -p "$(CURDIR)/debian/ussd-common/usr/lib/python2.5/" + cp -a "ussdquery/gsmdecode.py" "$(CURDIR)/debian/ussd-common/usr/lib/python2.5/gsmdecode.py" + mkdir -p "$(CURDIR)/debian/ussd-common/usr/bin/" + cp -a "ussdquery/ussdquery.py" "$(CURDIR)/debian/ussd-common/usr/bin/ussdquery.py" + # Build architecture-independent files here. binary-indep: build install # We have nothing to do by default. diff --git a/ussd4all/src/src.pro b/ussd4all/src/src.pro index 5d2df32..7007ec7 100644 --- a/ussd4all/src/src.pro +++ b/ussd4all/src/src.pro @@ -4,10 +4,6 @@ TARGET = qussd QT += maemo5 -# install -target.path = $$[QT_INSTALL_EXAMPLES]/opt/maemo/usr/bin/qussd -INSTALLS += target - unix { #VARIABLES isEmpty(PREFIX) { diff --git a/ussd4all/ussdquery/gsmdecode.py b/ussd4all/ussdquery/gsmdecode.py new file mode 100644 index 0000000..5a8d3e3 --- /dev/null +++ b/ussd4all/ussdquery/gsmdecode.py @@ -0,0 +1,305 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published +## by the Free Software Foundation; version 2 and higer. +## +## Martin Grimme (martin.grimme # gmail.com) 2010 +## Guseynov Alexey (kibergus # gmail.com) 2010 + +LANG_DE = 0x0 +LANG_EN = 0x1 +LANG_IT = 0x2 +LANG_FR = 0x3 +LANG_ES = 0x4 +LANG_NL = 0x5 +LANG_SE = 0x6 +LANG_DA = 0x7 +LANG_PO = 0x8 +LANG_FI = 0x9 +LANG_NO = 0xa +LANG_GR = 0xb +LANG_TR = 0xc +LANG_UNSPECIFIED = 0xf + + +GSM_DEFAULT_ALPHABET = [ + u"@", + u"\u00a3", + u"$", + u"\u00a5", + u"\u00e8", + u"\u00e9", + u"\u00f9", + u"\u00ec", + u"\u00f2", + u"\u00c7", + u"\n", + u"\u00d8", + u"\u00f8", + u"\r", + u"\u00c5", + u"\u00e5", + + u"\u0394", + u"_", + u"\u03a6", + u"\u0393", + u"\u039b", + u"\u03a9", + u"\u03a0", + u"\u03a8", + u"\u03a3", + u"\u0398", + u"\u039e", + u" ", + u"\u00c6", + u"\u00e6", + u"\u00df", + u"\u00c9", + + u" ", + u"!", + u"\"", + u"#", + u"\u00a4", + u"%", + u"&", + u"'", + u"(", + u")", + u"*", + u"+", + u",", + u"-", + u".", + u"/", + + u"0", + u"1", + u"2", + u"3", + u"4", + u"5", + u"6", + u"7", + u"8", + u"9", + u":", + u";", + u"<", + u"=", + u">", + u"?", + + u"\u00a1", + u"A", + u"B", + u"C", + u"D", + u"E", + u"F", + u"G", + u"H", + u"I", + u"J", + u"K", + u"L", + u"M", + u"N", + u"O", + + u"P", + u"Q", + u"R", + u"S", + u"T", + u"U", + u"V", + u"W", + u"X", + u"Y", + u"Z", + u"\u00c4", + u"\u00d6", + u"\u00d1", + u"\u00dc", + u"\u00a7", + + u"\u00bf", + u"a", + u"b", + u"c", + u"d", + u"e", + u"f", + u"g", + u"h", + u"i", + u"j", + u"k", + u"l", + u"m", + u"n", + u"o", + + u"p", + u"q", + u"r", + u"s", + u"t", + u"u", + u"v", + u"w", + u"x", + u"y", + u"z", + u"\u00e4", + u"\u00f6", + u"\u00f1", + u"\u00fc", + u"\u00e0" +] + + +def decode(s, n): + """ + Decodes the given string using the given cell broadcast data coding scheme. + + @param s: string to decode + @param n: GSM cell broadcast data coding scheme + @return: UTF-8 string + """ + + # separate into nibbles + hbits = (n & 0xf0) >> 4 + lbits = (n & 0x0f) + + if (hbits == 0x0): + # language + return _decode_language(s, lbits) + + elif (0x1 <= hbits <= 0x3): + # reserved language + return s + + elif (0x4 <= hbits <= 0x7): + # general data coding indication + return _decode_general_data_coding(s, hbits, lbits) + + elif (0x8 <= hbits <= 0xe): + # reserved coding group + return s + + elif (hbits == 0xf): + # data coding / message handling + return s + + +def _decode_language(s, lang): + + return _decode_default_alphabet(s) + + +def _decode_default_alphabet(s): + + # ought to be all in the 7 bit GSM character map + # modem is in 8 bit mode, so it makes 7 bit unpacking itself + chars = [ GSM_DEFAULT_ALPHABET[ord(c)] for c in s ] + u_str = "".join(chars) + return u_str.encode("utf-8") + + +def _decode_hex(s): + + return s.decode("hex") + + +def _decode_usc2(s): + + return s.decode("hex").decode("utf-16-be").encode("utf-8") + + +def _decode_general_data_coding(s, h, l): + + is_compressed = (h & 0x2) + + alphabet = (l & 0xc) >> 2 + + if (alphabet == 0x0): + # default alphabet + return _decode_default_alphabet(s) + + elif (alphabet == 0x1): + # 8 bit + # actually, encoding is user-defined, but let's assume hex'd ASCII + # for now + return _decode_hex(s) + + elif (alphabet == 0x2): + # USC2 (16 bit, BE) + return _decode_usc2(s) + elif (alphabet == 0x3): + # reserved + return s + +def decode_number(number): + dnumber = "" + for i in number: + if i & 0xf < 10: + dnumber += str(int((i & 0xf))) + if (i & 0xf0) >> 4 < 10: + dnumber += str(int((i & 0xf0) >> 4)) + return dnumber + +def decode_timestamp(timestamp): + res = {} + res['year'] = str(timestamp[0] & 0xf) + str((timestamp[0] & 0xf0) >> 4) + res['month'] = str(timestamp[1] & 0xf) + str((timestamp[1] & 0xf0) >> 4) + res['day'] = str(timestamp[2] & 0xf) + str((timestamp[2] & 0xf0) >> 4) + res['hour'] = str(timestamp[3] & 0xf) + str((timestamp[3] & 0xf0) >> 4) + res['minute'] = str(timestamp[4] & 0xf) + str((timestamp[4] & 0xf0) >> 4) + res['second'] = str(timestamp[5] & 0xf) + str((timestamp[5] & 0xf0) >> 4) + res['timezone'] = str(timestamp[6] & 0xf) + str((timestamp[6] & 0xf0) >> 4) + return res + +def decode_pdu (pdumsg): + pdu = {} + pdu['type'] = int(pdumsg[0]) + if pdu['type'] & 0x3 == 0x0: + pdu['address_len'] = int(pdumsg[1]) + pdu['type_of_address'] = int(pdumsg[2]) + base = 3+(pdu['address_len']+1)/2 + pdu['sender'] = decode_number(pdumsg[3:base]) + pdu['pid'] = int(pdumsg[base]) + pdu['dcs'] = int(pdumsg[base+1]) + pdu['timestamp'] = decode_timestamp (pdumsg[base+2:base+9]); + pdu['udl'] = int(pdumsg[base+9]) + pdu['user_data'] = pdumsg[base+10:len(pdumsg)] + + alphabet = (pdu['dcs'] & 0xc) >> 2 + if alphabet == 0x0: + # This is 7-bit data. Taking of only pdu['udl'] bytes is important because we can't distinguish 0 at the end from @ if only one bit is used in last bite + pdu['user_data'] = _decode_default_alphabet(deoctify(pdu['user_data']))[0:pdu['udl']] + elif alphabet == 0x1: + # actually, encoding is user-defined, but let's assume ASCII + pdu['user_data'] = ''.join([chr(i) for i in pdu['user_data']]) + elif (alphabet == 0x2): + # USC2 (16 bit, BE) + pdu['user_data'] = (''.join([chr(i) for i in pdu['user_data'][6:len(pdu['user_data'])]])).decode("utf-16-be").encode("utf-8") + elif (alphabet == 0x3): + # reserved + pdu['user_data'] = ''.join([chr(i) for i in pdu['user_data']]) + + if pdu['type'] & 0x4 == 0: + pdu['part'] = True + else: + pdu['part'] = False + else: + # TODO support other types of messages + # This is not incoming message + # pdu['type'] & 0x3 == 2 means delivery report + return None + + return pdu + diff --git a/ussd4all/ussdquery/ussdquery.py b/ussd4all/ussdquery/ussdquery.py new file mode 100755 index 0000000..2705dad --- /dev/null +++ b/ussd4all/ussdquery/ussdquery.py @@ -0,0 +1,337 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published +## by the Free Software Foundation; version 2 and higer. +## +## Guseynov Alexey (kibergus bark-bark gmail.com) 2010 + +import pexpect +import time +from subprocess import * +import sys +import gsmdecode +import re +import fcntl +import os +import stat +import dbus +import gobject + +# Needed for correct output of utf-8 symbols. +sys.stdout=file("/dev/stdout", "wb") + +def check_number(number): + if number == "": + return False + for s in number : + if not (s in ["1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "+", "*", "#"]) : + return False + return True + +if len(sys.argv) == 1: + print "Usage:\nussdquery.py [options]\nussdquery.py interactive [options]\n"+\ +"Options:\n-l language. Allowed languages: German, English, Italian, French, Spanish, Dutch, Swedish, Danish, Portuguese, Finnish, Norwegian, Greek, Turkish, Reserved1, Reserved2, Unspecified\n"+\ +"-r retry count. 0 default. Use -1 for infinite.\n-f If specified, errors, which occur on last query are threated as fatal\n"+\ +"-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"+\ +"-d delimeter. Default is '\\n> \n'"+\ +"-m gain modem lock imidiately '"+\ +"-s show GUI message box on last reply"+\ +"For USSD menu navigation divide USSD number via spacebars for every next menu selection. Type exit in interactive mode to exit." + sys.exit() + +lockf = None + +def gain_lock (): + global lockf + lockf = open("/tmp/ussdquery.lock", 'a') + fcntl.flock(lockf,fcntl.LOCK_EX) + try: + os.chmod("/tmp/ussdquery.lock", stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IWGRP | stat.S_IROTH | stat.S_IWOTH) + except: + None + +def release_lock (): + global lockf + fcntl.flock(lockf,fcntl.LOCK_UN) + lockf.close() + +def init_modem(modem): + # We have only one modem, simultaneous acces wouldn't bring anything good + gain_lock() + response = "" + init_retry = 5 + while response != "OK" and init_retry > 0 : + if modem == None : + # OK response should be recieved shortly + modem = pexpect.spawn('pnatd', [], 2) + try : + modem.send('at\r'); + # Read our "at" command + modem.readline(); + # Read OK response + response = modem.readline().strip() + except pexpect.TIMEOUT: + modem.kill(9) + modem = None + response = "" + if response != "OK" : + time.sleep(0.5) + init_retry -= 1 + else: + try: + # Switch output encoding to GSM default encoding + modem.send('at+cscs="GSM"\r'); + # Read our command + modem.readline(); + # Read OK response + response = modem.readline().strip() + except pexpect.TIMEOUT: + modem.kill(9) + modem = None + response = "" + + if response != "OK" : + time.sleep(0.5) + init_retry -= 1 + + if response != "OK" : + print >> sys.stderr, "Couldn't init modem." + if modem != None: + modem.kill(9) + release_lock() + sys.exit (-1) + + modem.timeout = timeout + return modem + +def close_modem (modem): + modem.sendeof() + modem.kill(9) + release_lock() + +retry = 0 +allow_last_error = True +delimiter = "\n> " +language = 15 +timeout = 30 +show_qussd = False + +if sys.argv[1] == "interactive": + number = "interactive" +else: + number = sys.argv[1].split(" ") + for n in number: + if not check_number(n): + print >> sys.stderr, "Syntax error in USSD number." + sys.exit(-7) + +modem = None + +# Parsing command line options +arg = 1 +state = "arg" +while arg < len(sys.argv)-1: + arg += 1 + if state == "arg": + if sys.argv[arg] == "-l": + state = "lang" + continue + if sys.argv[arg] == "-r": + state = "retry" + continue + if sys.argv[arg] == "-t": + state = "timeout" + continue + if sys.argv[arg] == "-d": + state = "delim" + continue + if sys.argv[arg] == "-f": + allow_last_error = False + continue + if sys.argv[arg] == "-s": + show_qussd = True + continue + if sys.argv[arg] == "-m": + modem = init_modem(modem) + continue + + if state == "lang": + if sys.argv[arg] == "German": + language = 0 + elif sys.argv[arg] == "English": + language = 1 + elif sys.argv[arg] == "Italian": + language = 2 + elif sys.argv[arg] == "French": + language = 3 + elif sys.argv[arg] == "Spanish": + language = 4 + elif sys.argv[arg] == "Dutch": + language = 5 + elif sys.argv[arg] == "Swedish": + language = 6 + elif sys.argv[arg] == "Danish": + language = 7 + elif sys.argv[arg] == "Portuguese": + language = 8 + elif sys.argv[arg] == "Finnish": + language = 9 + elif sys.argv[arg] == "Norwegian": + language = 10 + elif sys.argv[arg] == "Greek": + language = 11 + elif sys.argv[arg] == "Turkish": + language = 12 + elif sys.argv[arg] == "Reserved1": + language = 13 + elif sys.argv[arg] == "Reserved2": + language = 14 + elif sys.argv[arg] == "Unspecified": + language = 15 + else: + print >> sys.stderr, "Language unknown, falling back to unspecified." + state = "arg" + continue + + if state == "delim": + if number == "interactive": + delimiter = sys.argv[arg] + else: + print >> sys.stderr, "Delimiter is only supported in interactive mode." + state = "arg" + continue + + if state == "retry": + if number == "interactive": + print >> sys.stderr, "Retry is only supported in normal mode." + else: + try: + retry = int(sys.argv[arg]) + if retry < -1: + print >> sys.stderr, "Number of allowed errors must be >= -1. -1 assumed." + retry = -1 + except: + print >> sys.stderr, "Retry must be an integer." + sys.exit(-5) + state = "arg" + continue + if state == "timeout": + try: + timeout = int(sys.argv[arg]) + except: + print >> sys.stderr, "Timeout must be an integer." + sys.exit(-5) + state = "arg" + continue + + print >> sys.stderr, "Unrecogmized argument: "+sys.argv[arg] + +if retry == -1: + retry_forever = True +else: + retry_forever = False + +bus = dbus.SystemBus() +ussdd = bus.get_object("su.kibergus.ussdd", "/su/kibergus/ussdd") +ussdd_int = dbus.Interface(ussdd, "su.kibergus.ussdd") + +# Now we are ready to send commands + +stage = 0 +if number == "interactive": + sys.stdout.write(delimiter) + sys.stdout.flush() +while number == "interactive" or stage < len(number): + if number == "interactive": + cnumber = sys.stdin.readline().strip() + if cnumber == "exit": + if modem != None: + close_modem (modem) + sys.exit (0) + if not check_number (cnumber): + sys.stdout.write ("Syntax error in USSD number"+delimiter) + sys.stdout.flush() + continue + else: + cnumber = number[stage] + + if modem == None: + modem = init_modem(modem) + + if retry == -1 and not retry_forever: + print >> sys.stderr, "Retry limit is over. Giving up." + break + + try : + if number != "interactive" and not show_qussd or stage != len(number)-1: + ussdd_int.skip_next() + + modem.send('at+cusd=1,"'+cnumber+'",'+str(language)+'\r') + # Read our query echoed back + modem.readline() + + #Read and parse reply + replystring = modem.readline().decode('string_escape') + # This will read out unneeded info from modem + modem.readline() + modem.readline() + except pexpect.TIMEOUT: + print >> sys.stderr, "Timeout. Modem didn't reply." + close_modem (modem) + ussdd_int.show_next() + sys.exit (-2) + + if replystring.strip() == "ERROR" : + retry -= 1 + print >> sys.stderr, "Modem returned ERROR. Query not executed." + ussdd_int.show_next() + continue + + try: + reresult = re.match("(?s)^\\+CUSD: (\\d+),\"(.*)\",(\\d+)$", replystring.strip()) + + # 0 no further user action required (network initiated USSD-Notify, or no further information needed after mobile initiated operation) + # 1 further user action required (network initiated USSD-Request, or further information needed after mobile initiated operation) + # 2 USSD terminated by network + # 3 other local client has responded + # 4 operation not supported + # 5 network time out + reply_status=reresult.group(1) + reply = reresult.group(2) + # See GSM 07.07 and GSM 03.38 + encoding = reresult.group(3) + except: + retry -= 1 + print >> sys.stderr, "Couldn't parse modem answer: "+replystring + continue + + if reply_status == 0: + # May be somebody else needs it + close_modem(modem) + modem = None + elif reply_status == 2: + print >> sys.stderr, "USSD terminated by network." + elif reply_status == 3: + print >> sys.stderr, "Error: other local client has responded." + elif reply_status == 4: + print >> sys.stderr, "Operation not supported." + elif reply_status == 5: + print >> sys.stderr, "Network time out." + + # Decoding ansver + reply = gsmdecode.decode(reply, int(encoding)) + + if number == "interactive": + # prints line feed + sys.stdout.write(reply+delimiter) + sys.stdout.flush() + else: + if stage == len(number)-1: + print reply + stage += 1 + if not allow_last_error and namber != "interactive" and stage == len(number) - 1: + retry = 0 + +if modem != None: + close_modem (modem) -- 1.7.9.5