--- /dev/null
+#!/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
+
--- /dev/null
+#!/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 <ussd number> [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)